fix: adjust item id stripping to happen prior to request signing (#31429)

This commit is contained in:
Aiden Cline 2026-06-08 17:43:48 -05:00 committed by GitHub
parent 6e84142b59
commit a86ecf3bba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 109 additions and 40 deletions

View File

@ -1692,26 +1692,6 @@ export const layer = Layer.effect(
const combined = signals.length === 0 ? null : signals.length === 1 ? signals[0] : AbortSignal.any(signals)
if (combined) opts.signal = combined
// Strip openai itemId metadata following what codex does
if (
(model.api.npm === "@ai-sdk/openai" ||
model.api.npm === "@ai-sdk/azure" ||
model.api.npm === "@ai-sdk/amazon-bedrock/mantle") &&
opts.body &&
opts.method === "POST"
) {
const body = JSON.parse(opts.body as string)
const keepIds = body.store === true
if (!keepIds && Array.isArray(body.input)) {
for (const item of body.input) {
if ("id" in item) {
delete item.id
}
}
opts.body = JSON.stringify(body)
}
}
const res = await fetchFn(input, {
...opts,
// @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682

View File

@ -409,6 +409,24 @@ function unsupportedParts(msgs: ModelMessage[], model: Provider.Model): ModelMes
})
}
function mapProviderOptions(
msgs: ModelMessage[],
transform: (options: Record<string, any> | undefined) => Record<string, any> | undefined,
) {
return msgs.map((msg) => {
if (!Array.isArray(msg.content)) return { ...msg, providerOptions: transform(msg.providerOptions) }
return {
...msg,
providerOptions: transform(msg.providerOptions),
content: msg.content.map((part) =>
part.type === "tool-approval-request" || part.type === "tool-approval-response"
? part
: { ...part, providerOptions: transform(part.providerOptions) },
),
} as typeof msg
})
}
export function message(msgs: ModelMessage[], model: Provider.Model, options: Record<string, unknown>) {
msgs = unsupportedParts(msgs, model)
msgs = normalizeMessages(msgs, model, options)
@ -438,18 +456,20 @@ export function message(msgs: ModelMessage[], model: Provider.Model, options: Re
return result
}
msgs = msgs.map((msg) => {
if (!Array.isArray(msg.content)) return { ...msg, providerOptions: remap(msg.providerOptions) }
return {
...msg,
providerOptions: remap(msg.providerOptions),
content: msg.content.map((part) => {
if (part.type === "tool-approval-request" || part.type === "tool-approval-response") {
return { ...part }
}
return { ...part, providerOptions: remap(part.providerOptions) }
}),
} as typeof msg
msgs = mapProviderOptions(msgs, remap)
}
// Strip Responses item IDs before serialization, following Codex and keeping signed request bodies immutable.
if (
options.store !== true &&
key &&
["@ai-sdk/openai", "@ai-sdk/azure", "@ai-sdk/amazon-bedrock/mantle"].includes(model.api.npm)
) {
msgs = mapProviderOptions(msgs, (options) => {
if (!options?.[key] || !("itemId" in options[key])) return options
const metadata = { ...options[key] }
delete metadata.itemId
return { ...options, [key]: metadata }
})
}

View File

@ -1805,7 +1805,7 @@ describe("ProviderTransform.message - strip openai metadata when store=false", (
headers: {},
} as any
test("preserves itemId and reasoningEncryptedContent when store=false", () => {
test("strips OpenAI itemId and preserves reasoningEncryptedContent when store=false", () => {
const msgs = [
{
role: "assistant",
@ -1836,11 +1836,12 @@ describe("ProviderTransform.message - strip openai metadata when store=false", (
const result = ProviderTransform.message(msgs, openaiModel, { store: false }) as any[]
expect(result).toHaveLength(1)
expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("rs_123")
expect(result[0].content[1].providerOptions?.openai?.itemId).toBe("msg_456")
expect(result[0].content[0].providerOptions?.openai?.itemId).toBeUndefined()
expect(result[0].content[0].providerOptions?.openai?.reasoningEncryptedContent).toBe("encrypted")
expect(result[0].content[1].providerOptions?.openai?.itemId).toBeUndefined()
})
test("preserves itemId and reasoningEncryptedContent when store=false even when not openai", () => {
test("uses the SDK package namespace rather than provider ID", () => {
const zenModel = {
...openaiModel,
providerID: "zen",
@ -1875,11 +1876,12 @@ describe("ProviderTransform.message - strip openai metadata when store=false", (
const result = ProviderTransform.message(msgs, zenModel, { store: false }) as any[]
expect(result).toHaveLength(1)
expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("rs_123")
expect(result[0].content[1].providerOptions?.openai?.itemId).toBe("msg_456")
expect(result[0].content[0].providerOptions?.openai?.itemId).toBeUndefined()
expect(result[0].content[0].providerOptions?.openai?.reasoningEncryptedContent).toBe("encrypted")
expect(result[0].content[1].providerOptions?.openai?.itemId).toBeUndefined()
})
test("preserves other openai options including itemId", () => {
test("preserves other OpenAI options", () => {
const msgs = [
{
role: "assistant",
@ -1900,10 +1902,77 @@ describe("ProviderTransform.message - strip openai metadata when store=false", (
const result = ProviderTransform.message(msgs, openaiModel, { store: false }) as any[]
expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_123")
expect(result[0].content[0].providerOptions?.openai?.itemId).toBeUndefined()
expect(result[0].content[0].providerOptions?.openai?.otherOption).toBe("value")
})
test("strips Azure itemId from the Azure namespace", () => {
const azureModel = {
...openaiModel,
providerID: "azure",
api: {
id: "gpt-5",
url: "https://example.openai.azure.com",
npm: "@ai-sdk/azure",
},
}
const msgs = [
{
role: "assistant",
content: [
{
type: "text",
text: "Hello",
providerOptions: {
azure: { itemId: "msg_123", otherOption: "value" },
openai: { itemId: "msg_openai" },
},
},
],
},
] as any[]
const result = ProviderTransform.message(msgs, azureModel, { store: false }) as any[]
expect(result[0].content[0].providerOptions?.azure?.itemId).toBeUndefined()
expect(result[0].content[0].providerOptions?.azure?.otherOption).toBe("value")
expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_openai")
})
test("strips Bedrock Mantle itemId from the OpenAI namespace", () => {
const mantleModel = {
...openaiModel,
providerID: "amazon-bedrock",
api: {
id: "openai.gpt-5.5",
url: "https://bedrock-mantle.us-east-2.api.aws/openai/v1",
npm: "@ai-sdk/amazon-bedrock/mantle",
},
}
const msgs = [
{
role: "assistant",
providerOptions: { openai: { itemId: "msg_root", otherOption: "root-value" } },
content: [
{
type: "reasoning",
text: "thinking...",
providerOptions: {
openai: { itemId: "rs_123", reasoningEncryptedContent: "encrypted" },
},
},
],
},
] as any[]
const result = ProviderTransform.message(msgs, mantleModel, { store: false }) as any[]
expect(result[0].providerOptions?.openai?.itemId).toBeUndefined()
expect(result[0].providerOptions?.openai?.otherOption).toBe("root-value")
expect(result[0].content[0].providerOptions?.openai?.itemId).toBeUndefined()
expect(result[0].content[0].providerOptions?.openai?.reasoningEncryptedContent).toBe("encrypted")
})
test("preserves metadata for openai package when store is true", () => {
const msgs = [
{