From b47c6802daa8fb8eb87443e8f9cb19caa758c768 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 15 Oct 2026 02:13:14 -0500 Subject: [PATCH] fix(ai): accept null Responses function call namespace (#55631) Co-authored-by: kksingh000 --- packages/ai/src/protocols/open-responses.ts | 6 ++++-- .../openai-compatible-responses.test.ts | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/ai/src/protocols/open-responses.ts b/packages/ai/src/protocols/open-responses.ts index 1cc4c331ea2..423ba38de6f 100644 --- a/packages/ai/src/protocols/open-responses.ts +++ b/packages/ai/src/protocols/open-responses.ts @@ -334,14 +334,14 @@ export const StreamItem = Schema.StructWithRest( id: Schema.optional(Schema.String), call_id: Schema.optional(Schema.String), name: Schema.optional(Schema.String), - namespace: Schema.optional(Schema.String), + namespace: optionalNull(Schema.String), arguments: Schema.optional(Schema.String), encrypted_content: optionalNull(Schema.String), }), [Schema.Record(Schema.String, Schema.Unknown)], ) export type StreamItem = Schema.Schema.Type -export type OutputItem = StreamItem & { readonly id: string } +export type OutputItem = StreamItem & { readonly id: string; readonly namespace?: string } // Responses-compatible providers put error details at the top level, under `error`, or under // `response.error`, and gateways reshape them freely: strings, numeric codes, extra fields. Those @@ -946,6 +946,8 @@ const resolveItem = ( index: number | undefined, ): OutputItem => ({ ...item, + // Some gateways serialize an absent namespace as null. + namespace: item.namespace ?? undefined, id: item.id ?? (index === undefined ? undefined : state.outputItems[index]) ?? diff --git a/packages/ai/test/provider/openai-compatible-responses.test.ts b/packages/ai/test/provider/openai-compatible-responses.test.ts index 81860398d6d..2b1427aabc8 100644 --- a/packages/ai/test/provider/openai-compatible-responses.test.ts +++ b/packages/ai/test/provider/openai-compatible-responses.test.ts @@ -644,6 +644,27 @@ describe("Open Responses-compatible route", () => { }), ) + it.effect("treats a null function call namespace as absent", () => + Effect.gen(function* () { + const item = { type: "function_call", id: "fc_1", call_id: "call_1", name: "lookup", namespace: null } + const response = yield* LLMClient.generate(request).pipe( + Effect.provide( + fixedResponse( + sseEvents( + { type: "response.output_item.added", output_index: 0, item: { ...item, arguments: "" } }, + { type: "response.function_call_arguments.done", item_id: "fc_1", output_index: 0, arguments: "{}" }, + { type: "response.output_item.done", output_index: 0, item: { ...item, arguments: "{}" } }, + { type: "response.completed", response: { id: "resp_1", output: [{ ...item, arguments: "{}" }] } }, + ), + ), + ), + ) + + expect(response.toolCalls).toEqual([expect.objectContaining({ id: "call_1", name: "lookup", input: {} })]) + expect(response.toolCalls[0]?.namespace).toBeUndefined() + }), + ) + it.effect("rejects missing, null, and non-string event IDs even with a registered output index", () => Effect.gen(function* () { yield* Effect.forEach(