diff --git a/samples/client/angular/projects/contact/src/app/client.ts b/samples/client/angular/projects/contact/src/app/client.ts index 15a9dc43a..34da78996 100644 --- a/samples/client/angular/projects/contact/src/app/client.ts +++ b/samples/client/angular/projects/contact/src/app/client.ts @@ -21,6 +21,7 @@ import { Injectable, inject, signal } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class Client { private processor = inject(MessageProcessor); + private contextId?: string; readonly isLoading = signal(false); @@ -43,8 +44,13 @@ export class Client { // Clear surfaces at the start of a new request this.processor.clearSurfaces(); + const isString = typeof request === 'string'; + const bodyData = isString + ? { query: request, contextId: this.contextId } + : { event: request, contextId: this.contextId }; + const response = await fetch('/a2a', { - body: JSON.stringify(request as Types.A2UIClientEventMessage), + body: JSON.stringify(bodyData), method: 'POST', }); @@ -96,18 +102,22 @@ export class Client { if (line.startsWith("data: ")) { const jsonStr = line.slice(6); try { - const data = JSON.parse(jsonStr) as A2AServerPayload; - console.log(`[client] [${now.toFixed(2)}ms] Received SSE data:`, data); + const responseData = JSON.parse(jsonStr); + console.log(`[client] [${now.toFixed(2)}ms] Received SSE data:`, responseData); - if ('error' in data) { - throw new Error(data.error); + if (responseData.error) { + throw new Error(responseData.error); } else { + if (responseData.contextId) { + this.contextId = responseData.contextId; + } + const parts = responseData.parts || (Array.isArray(responseData) ? responseData : []); console.log( - `[client] [${performance.now().toFixed(2)}ms] Scheduling processing for ${data.length} parts` + `[client] [${performance.now().toFixed(2)}ms] Scheduling processing for ${parts.length} parts` ); // Use a microtask to ensure we don't block the stream reader await Promise.resolve(); - const newMessages = this.processParts(data as any[]); + const newMessages = this.processParts(parts); messages.push(...newMessages); } } catch (e) { @@ -122,9 +132,14 @@ export class Client { response: Response, messages: Types.ServerToClientMessage[] ): Promise { - const data = (await response.json()) as any[]; - console.log(`[client] Received JSON response:`, data); - const newMessages = this.processParts(data); + const responseData = await response.json(); + console.log(`[client] Received JSON response:`, responseData); + + if (responseData.contextId) { + this.contextId = responseData.contextId; + } + const parts = responseData.parts || (Array.isArray(responseData) ? responseData : []); + const newMessages = this.processParts(parts); messages.push(...newMessages); } diff --git a/samples/client/angular/projects/contact/src/server.ts b/samples/client/angular/projects/contact/src/server.ts index 0d6ee3d28..675ee34ea 100644 --- a/samples/client/angular/projects/contact/src/server.ts +++ b/samples/client/angular/projects/contact/src/server.ts @@ -51,25 +51,58 @@ app.post('/a2a', (req, res) => { let sendParams: MessageSendParams; if (isJson(originalBody)) { - console.log('[a2a-middleware] Received JSON UI event:', originalBody); - - const clientEvent = JSON.parse(originalBody); - sendParams = { - message: { - messageId: uuidv4(), - role: 'user', - parts: [ - { - kind: 'data', - data: clientEvent, - metadata: { 'mimeType': 'application/json+a2ui' }, - } as Part, - ], - kind: 'message', - }, - }; + const requestData = JSON.parse(originalBody); + const contextId = requestData.contextId; + + if (requestData.event) { + console.log('[a2a-middleware] Received JSON UI event:', requestData.event); + sendParams = { + message: { + messageId: uuidv4(), + contextId, + role: 'user', + parts: [ + { + kind: 'data', + data: requestData.event, + metadata: { 'mimeType': 'application/json+a2ui' }, + } as Part, + ], + kind: 'message', + }, + }; + } else if (requestData.query) { + console.log('[a2a-middleware] Received text query:', requestData.query); + sendParams = { + message: { + messageId: uuidv4(), + contextId, + role: 'user', + parts: [{ kind: 'text', text: requestData.query }], + kind: 'message', + }, + }; + } else { + // Fallback for legacy JSON event where the body is the event itself + console.log('[a2a-middleware] Received legacy JSON event:', originalBody); + sendParams = { + message: { + messageId: uuidv4(), + contextId, + role: 'user', + parts: [ + { + kind: 'data', + data: requestData, + metadata: { 'mimeType': 'application/json+a2ui' }, + } as Part, + ], + kind: 'message', + }, + }; + } } else { - console.log('[a2a-middleware] Received text query:', originalBody); + console.log('[a2a-middleware] Received plain text query:', originalBody); sendParams = { message: { messageId: uuidv4(), @@ -121,7 +154,11 @@ async function handleStreamingResponse(client: A2AClient, sendParams: MessageSen if (parts.length > 0) { console.log(`[server] Streaming ${parts.length} parts to client`); - res.write(`data: ${JSON.stringify(parts)}\n\n`); + const responseData = { + parts, + contextId: (event as any).contextId || (event as any).status?.message?.contextId + }; + res.write(`data: ${JSON.stringify(responseData)}\n\n`); } } res.end(); @@ -140,7 +177,10 @@ async function handleNonStreamingResponse(client: A2AClient, sendParams: Message } const result = (response as SendMessageSuccessResponse).result as Task; - res.json(result.kind === 'task' ? result.status.message?.parts || [] : []); + res.json({ + parts: result.kind === 'task' ? result.status.message?.parts || [] : [], + contextId: result.contextId + }); } app.use((req, res, next) => { diff --git a/samples/client/angular/projects/orchestrator/src/server.ts b/samples/client/angular/projects/orchestrator/src/server.ts index 8db1068e8..1aacd931f 100644 --- a/samples/client/angular/projects/orchestrator/src/server.ts +++ b/samples/client/angular/projects/orchestrator/src/server.ts @@ -52,10 +52,12 @@ app.post('/a2a', (req, res) => { console.log('[a2a-middleware] Received data:', data); const parts: Part[] = data['parts']; + const contextId: string | undefined = data['contextId']; const sendParams: MessageSendParams = { message: { messageId: uuidv4(), + contextId, role: 'user', parts, kind: 'message', diff --git a/samples/client/angular/projects/orchestrator/src/services/a2a-service-impl.spec.ts b/samples/client/angular/projects/orchestrator/src/services/a2a-service-impl.spec.ts new file mode 100644 index 000000000..691e63755 --- /dev/null +++ b/samples/client/angular/projects/orchestrator/src/services/a2a-service-impl.spec.ts @@ -0,0 +1,105 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TestBed } from '@angular/core/testing'; +import { A2aServiceImpl } from './a2a-service-impl'; + +describe('A2aServiceImpl', () => { + let service: A2aServiceImpl; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [A2aServiceImpl], + }); + service = TestBed.inject(A2aServiceImpl); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should send contextId in request after receiving it from server', async () => { + // Mock first response to return a contextId + const mockResponse1 = { + contextId: 'test-session-123', + parts: [], + }; + + const fetchSpy = spyOn(globalThis, 'fetch').and.returnValue( + Promise.resolve({ + ok: true, + json: () => Promise.resolve(mockResponse1), + } as Response) + ); + + // First call should NOT send contextId (it doesn't have it yet) + await service.sendMessage([]); + + let lastCall = fetchSpy.calls.mostRecent(); + let body = JSON.parse(lastCall.args[1]!.body as string); + expect(body.contextId).toBeUndefined(); + + // Mock second response (just to complete the call) + const mockResponse2 = { + parts: [], + }; + fetchSpy.and.returnValue( + Promise.resolve({ + ok: true, + json: () => Promise.resolve(mockResponse2), + } as Response) + ); + + // Second call SHOULD send contextId + await service.sendMessage([]); + + lastCall = fetchSpy.calls.mostRecent(); + body = JSON.parse(lastCall.args[1]!.body as string); + expect(body.contextId).toBe('test-session-123'); + }); + + it('should update contextId from data.result.contextId if contextId is missing', async () => { + const mockResponse = { + result: { + contextId: 'test-session-456', + }, + parts: [], + }; + + const fetchSpy = spyOn(globalThis, 'fetch').and.returnValue( + Promise.resolve({ + ok: true, + json: () => Promise.resolve(mockResponse), + } as Response) + ); + + await service.sendMessage([]); + + // Call again to see if it sends it + fetchSpy.and.returnValue( + Promise.resolve({ + ok: true, + json: () => Promise.resolve({}), + } as Response) + ); + + await service.sendMessage([]); + + const lastCall = fetchSpy.calls.mostRecent(); + const body = JSON.parse(lastCall.args[1]!.body as string); + expect(body.contextId).toBe('test-session-456'); + }); +}); diff --git a/samples/client/angular/projects/orchestrator/src/services/a2a-service-impl.ts b/samples/client/angular/projects/orchestrator/src/services/a2a-service-impl.ts index 55ac70497..4e7eae67c 100644 --- a/samples/client/angular/projects/orchestrator/src/services/a2a-service-impl.ts +++ b/samples/client/angular/projects/orchestrator/src/services/a2a-service-impl.ts @@ -22,16 +22,23 @@ import { Injectable } from '@angular/core'; providedIn: 'root', }) export class A2aServiceImpl implements A2aService { + private contextId?: string; async sendMessage(parts: Part[], signal?: AbortSignal): Promise { const response = await fetch('/a2a', { - body: JSON.stringify({ parts: parts }), + body: JSON.stringify({ + parts: parts, + contextId: this.contextId + }), method: 'POST', signal, }); if (response.ok) { const data = await response.json(); + if (data.contextId || data.result?.contextId) { + this.contextId = data.contextId || data.result?.contextId; + } return data; } diff --git a/samples/client/angular/projects/restaurant/src/app/client.ts b/samples/client/angular/projects/restaurant/src/app/client.ts index 6784f7286..85abed510 100644 --- a/samples/client/angular/projects/restaurant/src/app/client.ts +++ b/samples/client/angular/projects/restaurant/src/app/client.ts @@ -21,6 +21,7 @@ import { inject, Injectable, signal } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class Client { private processor = inject(MessageProcessor); + private contextId?: string; readonly isLoading = signal(false); @@ -43,8 +44,13 @@ export class Client { // Clear surfaces at the start of a new request this.processor.clearSurfaces(); + const isString = typeof request === 'string'; + const bodyData = isString + ? { query: request, contextId: this.contextId } + : { event: request, contextId: this.contextId }; + const response = await fetch('/a2a', { - body: JSON.stringify(request as Types.A2UIClientEventMessage), + body: JSON.stringify(bodyData), method: 'POST', }); @@ -96,18 +102,22 @@ export class Client { if (line.startsWith('data: ')) { const jsonStr = line.slice(6); try { - const data = JSON.parse(jsonStr) as A2AServerPayload; - console.log(`[client] [${now.toFixed(2)}ms] Received SSE data:`, data); + const responseData = JSON.parse(jsonStr); + console.log(`[client] [${now.toFixed(2)}ms] Received SSE data:`, responseData); - if ('error' in data) { - throw new Error(data.error); + if (responseData.error) { + throw new Error(responseData.error); } else { + if (responseData.contextId) { + this.contextId = responseData.contextId; + } + const parts = responseData.parts || (Array.isArray(responseData) ? responseData : []); console.log( - `[client] [${performance.now().toFixed(2)}ms] Scheduling processing for ${data.length} parts` + `[client] [${performance.now().toFixed(2)}ms] Scheduling processing for ${parts.length} parts` ); // Use a microtask to ensure we don't block the stream reader await Promise.resolve(); - const newMessages = this.processParts(data as any[]); + const newMessages = this.processParts(parts); messages.push(...newMessages); } } catch (e) { @@ -122,9 +132,14 @@ export class Client { response: Response, messages: Types.ServerToClientMessage[] ): Promise { - const data = (await response.json()) as any[]; - console.log(`[client] Received JSON response:`, data); - const newMessages = this.processParts(data); + const responseData = await response.json(); + console.log(`[client] Received JSON response:`, responseData); + + if (responseData.contextId) { + this.contextId = responseData.contextId; + } + const parts = responseData.parts || (Array.isArray(responseData) ? responseData : []); + const newMessages = this.processParts(parts); messages.push(...newMessages); } diff --git a/samples/client/angular/projects/restaurant/src/server.ts b/samples/client/angular/projects/restaurant/src/server.ts index 86f92730a..7185f9d99 100644 --- a/samples/client/angular/projects/restaurant/src/server.ts +++ b/samples/client/angular/projects/restaurant/src/server.ts @@ -51,25 +51,58 @@ app.post('/a2a', (req, res) => { let sendParams: MessageSendParams; if (isJson(originalBody)) { - console.log('[a2a-middleware] Received JSON UI event:', originalBody); - - const clientEvent = JSON.parse(originalBody); - sendParams = { - message: { - messageId: uuidv4(), - role: 'user', - parts: [ - { - kind: 'data', - data: clientEvent, - metadata: { 'mimeType': 'application/json+a2ui' }, - } as Part, - ], - kind: 'message', - }, - }; + const requestData = JSON.parse(originalBody); + const contextId = requestData.contextId; + + if (requestData.event) { + console.log('[a2a-middleware] Received JSON UI event:', requestData.event); + sendParams = { + message: { + messageId: uuidv4(), + contextId, + role: 'user', + parts: [ + { + kind: 'data', + data: requestData.event, + metadata: { 'mimeType': 'application/json+a2ui' }, + } as Part, + ], + kind: 'message', + }, + }; + } else if (requestData.query) { + console.log('[a2a-middleware] Received text query:', requestData.query); + sendParams = { + message: { + messageId: uuidv4(), + contextId, + role: 'user', + parts: [{ kind: 'text', text: requestData.query }], + kind: 'message', + }, + }; + } else { + // Fallback for legacy JSON event + console.log('[a2a-middleware] Received legacy JSON event:', originalBody); + sendParams = { + message: { + messageId: uuidv4(), + contextId, + role: 'user', + parts: [ + { + kind: 'data', + data: requestData, + metadata: { 'mimeType': 'application/json+a2ui' }, + } as Part, + ], + kind: 'message', + }, + }; + } } else { - console.log('[a2a-middleware] Received text query:', originalBody); + console.log('[a2a-middleware] Received plain text query:', originalBody); sendParams = { message: { messageId: uuidv4(), @@ -122,7 +155,11 @@ async function handleStreamingResponse(client: A2AClient, sendParams: MessageSen if (parts.length > 0) { console.log(`[server] Streaming ${parts.length} parts to client`); console.log(`[server] Streaming parts: ${JSON.stringify(parts)}`); - res.write(`data: ${JSON.stringify(parts)}\n\n`); + const responseData = { + parts, + contextId: (event as any).contextId || (event as any).status?.message?.contextId + }; + res.write(`data: ${JSON.stringify(responseData)}\n\n`); } } res.end(); @@ -141,7 +178,10 @@ async function handleNonStreamingResponse(client: A2AClient, sendParams: Message } const result = (response as SendMessageSuccessResponse).result as Task; - res.json(result.kind === 'task' ? result.status.message?.parts || [] : []); + res.json({ + parts: result.kind === 'task' ? result.status.message?.parts || [] : [], + contextId: result.contextId + }); } app.use((req, res, next) => { diff --git a/samples/client/angular/projects/rizzcharts/src/server.ts b/samples/client/angular/projects/rizzcharts/src/server.ts index 5ad4046fd..5bf454a1e 100644 --- a/samples/client/angular/projects/rizzcharts/src/server.ts +++ b/samples/client/angular/projects/rizzcharts/src/server.ts @@ -53,7 +53,7 @@ app.post('/a2a', (req, res) => { const parts: Part[] = data['parts']; const metadata: Record = data['metadata']; - const contextId: string | undefined = data['context_id']; + const contextId: string | undefined = data['contextId']; const sendParams: MessageSendParams = { message: { diff --git a/samples/client/angular/projects/rizzcharts/src/services/a2a_service.ts b/samples/client/angular/projects/rizzcharts/src/services/a2a_service.ts index 8a37a3696..1f501d259 100644 --- a/samples/client/angular/projects/rizzcharts/src/services/a2a_service.ts +++ b/samples/client/angular/projects/rizzcharts/src/services/a2a_service.ts @@ -36,16 +36,16 @@ export class A2aService implements A2aServiceInterface { "supportedCatalogIds": currentCatalogUris } }, - 'context_id': this.contextId + 'contextId': this.contextId }), method: 'POST', signal, }); if (response.ok) { - const json = await response.json() as SendMessageSuccessResponse & { context_id?: string }; - if (json.context_id) { - this.contextId = json.context_id; + const json = await response.json() as SendMessageSuccessResponse & { contextId?: string }; + if (json.contextId || json.result?.contextId) { + this.contextId = json.contextId || json.result?.contextId; } return json; } diff --git a/samples/client/lit/component_gallery/client.ts b/samples/client/lit/component_gallery/client.ts index e4343cdce..935f0ae13 100644 --- a/samples/client/lit/component_gallery/client.ts +++ b/samples/client/lit/component_gallery/client.ts @@ -34,6 +34,8 @@ import { componentRegistry } from "@a2ui/lit/ui"; export class A2UIClient { #ready: Promise = Promise.resolve(); + #contextId?: string; + get ready() { return this.#ready; } @@ -50,17 +52,25 @@ export class A2UIClient { }; const response = await fetch("/a2a", { - body: JSON.stringify(finalMessage), + body: JSON.stringify({ + event: finalMessage, + contextId: this.#contextId + }), method: "POST", }); if (response.ok) { - const data = (await response.json()) as A2AServerPayload; + const responseData = await response.json(); + if (responseData.contextId) { + this.#contextId = responseData.contextId; + } + const parts = Array.isArray(responseData) ? responseData : (responseData.parts || []); const messages: v0_8.Types.ServerToClientMessage[] = []; - if ("error" in data) { - throw new Error(data.error); + if (responseData.error) { + throw new Error(responseData.error); } else { - for (const item of data) { + const items = Array.isArray(parts) ? parts : [parts]; + for (const item of items) { if (item.kind === "text") continue; messages.push(item.data); } diff --git a/samples/client/lit/component_gallery/middleware/a2a.ts b/samples/client/lit/component_gallery/middleware/a2a.ts index d5a05fd21..fbba480a9 100644 --- a/samples/client/lit/component_gallery/middleware/a2a.ts +++ b/samples/client/lit/component_gallery/middleware/a2a.ts @@ -83,10 +83,14 @@ export const plugin = (): Plugin => { originalBody ); - const clientEvent = JSON.parse(originalBody); + const requestData = JSON.parse(originalBody); + const contextId = requestData.contextId; + const clientEvent = requestData.event || requestData; + sendParams = { message: { messageId: uuidv4(), + contextId, role: "user", parts: [ { @@ -132,7 +136,11 @@ export const plugin = (): Plugin => { if (result.kind === "task") { res.statusCode = 200; res.setHeader("Content-Type", "application/json"); - res.end(JSON.stringify(result.status.message?.parts)); + const responseData = { + parts: result.status.message?.parts || [], + contextId: result.contextId + }; + res.end(JSON.stringify(responseData)); return; } } diff --git a/samples/client/lit/custom-components-example/client.ts b/samples/client/lit/custom-components-example/client.ts index 3228e4969..ccd31027d 100644 --- a/samples/client/lit/custom-components-example/client.ts +++ b/samples/client/lit/custom-components-example/client.ts @@ -34,6 +34,8 @@ import { componentRegistry } from "@a2ui/lit/ui"; export class A2UIClient { #ready: Promise = Promise.resolve(); + #contextId?: string; + get ready() { return this.#ready; } @@ -53,7 +55,10 @@ export class A2UIClient { }; const response = await fetch("/a2a", { - body: JSON.stringify(finalMessage), + body: JSON.stringify({ + event: finalMessage, + contextId: this.#contextId + }), method: "POST", }); @@ -83,11 +88,15 @@ export class A2UIClient { if (line.startsWith("data: ")) { const jsonStr = line.replace(/^data:\s*/, ""); try { - const parsed = JSON.parse(jsonStr); - if ("error" in parsed) { - throw new Error(parsed.error); + const responseData = JSON.parse(jsonStr); + if (responseData.error) { + throw new Error(responseData.error); } else { - const chunkMessages = this.#extractMessages(parsed); + if (responseData.contextId) { + this.#contextId = responseData.contextId; + } + const parts = responseData.parts || responseData; + const chunkMessages = this.#extractMessages(parts); if (chunkMessages.length > 0) { messages.push(...chunkMessages); onChunk?.(chunkMessages); @@ -102,11 +111,15 @@ export class A2UIClient { return messages; } - const data = (await response.json()) as any; - if (data && typeof data === 'object' && "error" in data) { - throw new Error(data.error); + const responseData = (await response.json()) as any; + if (responseData && typeof responseData === 'object' && "error" in responseData) { + throw new Error(responseData.error); } else { - const extracted = this.#extractMessages(data); + if (responseData.contextId) { + this.#contextId = responseData.contextId; + } + const parts = responseData.parts || responseData; + const extracted = this.#extractMessages(parts); messages.push(...extracted); if (messages.length > 0) { onChunk?.(messages); diff --git a/samples/client/lit/custom-components-example/middleware/a2a.ts b/samples/client/lit/custom-components-example/middleware/a2a.ts index e0f38eddd..17f515ec5 100644 --- a/samples/client/lit/custom-components-example/middleware/a2a.ts +++ b/samples/client/lit/custom-components-example/middleware/a2a.ts @@ -84,10 +84,14 @@ export const plugin = (): Plugin => { originalBody ); - const clientEvent = JSON.parse(originalBody); + const requestData = JSON.parse(originalBody); + const contextId = requestData.contextId; + const clientEvent = requestData.event || requestData; // fallback if it's old format + sendParams = { message: { messageId: uuidv4(), + contextId, role: "user", parts: [ { @@ -131,10 +135,19 @@ export const plugin = (): Plugin => { for await (const chunk of stream) { // A2AClient unpacks the JSON-RPC, so chunk is an A2AStreamEventData + let parts: Part[] = []; if (chunk.kind === "status-update" && chunk.status.message?.parts) { - res.write(`data: ${JSON.stringify(chunk.status.message.parts)}\n\n`); + parts = chunk.status.message.parts; } else if (chunk.kind === "message" && chunk.parts) { - res.write(`data: ${JSON.stringify(chunk.parts)}\n\n`); + parts = chunk.parts; + } + + if (parts.length > 0) { + const responseData = { + parts, + contextId: (chunk as any).contextId || (chunk as any).status?.message?.contextId + }; + res.write(`data: ${JSON.stringify(responseData)}\n\n`); } } res.end(); @@ -149,7 +162,11 @@ export const plugin = (): Plugin => { const result = (response as SendMessageSuccessResponse).result as Task; res.statusCode = 200; res.setHeader("Content-Type", "application/json"); - res.end(JSON.stringify(result.kind === "task" ? result.status.message?.parts || [] : [])); + const responseData = { + parts: result.kind === "task" ? result.status.message?.parts || [] : [], + contextId: result.contextId + }; + res.end(JSON.stringify(responseData)); } } } catch (e: any) { diff --git a/samples/client/lit/shell/client.ts b/samples/client/lit/shell/client.ts index 165da4df4..d84bea3a7 100644 --- a/samples/client/lit/shell/client.ts +++ b/samples/client/lit/shell/client.ts @@ -23,6 +23,7 @@ const A2UI_MIME_TYPE = "application/json+a2ui"; export class A2UIClient { #serverUrl: string; #client: A2AClient | null = null; + #contextId?: string; constructor(serverUrl: string = "") { this.#serverUrl = serverUrl; @@ -86,6 +87,7 @@ export class A2UIClient { const response = await client.sendMessage({ message: { messageId: crypto.randomUUID(), + contextId: this.#contextId, role: "user", parts: parts, kind: "message", @@ -97,6 +99,14 @@ export class A2UIClient { } const result = (response as SendMessageSuccessResponse).result as Task; + + // Extract contextId + if (result.contextId) { + this.#contextId = result.contextId; + } else if ("contextId" in response && response.contextId) { + this.#contextId = response.contextId as string; + } + if (result.kind === "task" && result.status.message?.parts) { const messages: v0_8.Types.ServerToClientMessage[] = []; for (const part of result.status.message.parts) {