fix: adjust item id stripping to happen prior to request signing (#31429)
This commit is contained in:
parent
6e84142b59
commit
a86ecf3bba
@ -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
|
||||
|
||||
@ -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 }
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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 = [
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user