fix: adjust item id stripping to happen prior to request signing (#31429)
This commit is contained in:
parent
6e84142b59
commit
a86ecf3bba
3 changed files with 109 additions and 40 deletions
|
|
@ -1692,26 +1692,6 @@ export const layer = Layer.effect(
|
||||||
const combined = signals.length === 0 ? null : signals.length === 1 ? signals[0] : AbortSignal.any(signals)
|
const combined = signals.length === 0 ? null : signals.length === 1 ? signals[0] : AbortSignal.any(signals)
|
||||||
if (combined) opts.signal = combined
|
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, {
|
const res = await fetchFn(input, {
|
||||||
...opts,
|
...opts,
|
||||||
// @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682
|
// @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>) {
|
export function message(msgs: ModelMessage[], model: Provider.Model, options: Record<string, unknown>) {
|
||||||
msgs = unsupportedParts(msgs, model)
|
msgs = unsupportedParts(msgs, model)
|
||||||
msgs = normalizeMessages(msgs, model, options)
|
msgs = normalizeMessages(msgs, model, options)
|
||||||
|
|
@ -438,18 +456,20 @@ export function message(msgs: ModelMessage[], model: Provider.Model, options: Re
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
msgs = msgs.map((msg) => {
|
msgs = mapProviderOptions(msgs, remap)
|
||||||
if (!Array.isArray(msg.content)) return { ...msg, providerOptions: remap(msg.providerOptions) }
|
}
|
||||||
return {
|
|
||||||
...msg,
|
// Strip Responses item IDs before serialization, following Codex and keeping signed request bodies immutable.
|
||||||
providerOptions: remap(msg.providerOptions),
|
if (
|
||||||
content: msg.content.map((part) => {
|
options.store !== true &&
|
||||||
if (part.type === "tool-approval-request" || part.type === "tool-approval-response") {
|
key &&
|
||||||
return { ...part }
|
["@ai-sdk/openai", "@ai-sdk/azure", "@ai-sdk/amazon-bedrock/mantle"].includes(model.api.npm)
|
||||||
}
|
) {
|
||||||
return { ...part, providerOptions: remap(part.providerOptions) }
|
msgs = mapProviderOptions(msgs, (options) => {
|
||||||
}),
|
if (!options?.[key] || !("itemId" in options[key])) return options
|
||||||
} as typeof msg
|
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: {},
|
headers: {},
|
||||||
} as any
|
} as any
|
||||||
|
|
||||||
test("preserves itemId and reasoningEncryptedContent when store=false", () => {
|
test("strips OpenAI itemId and preserves reasoningEncryptedContent when store=false", () => {
|
||||||
const msgs = [
|
const msgs = [
|
||||||
{
|
{
|
||||||
role: "assistant",
|
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[]
|
const result = ProviderTransform.message(msgs, openaiModel, { store: false }) as any[]
|
||||||
|
|
||||||
expect(result).toHaveLength(1)
|
expect(result).toHaveLength(1)
|
||||||
expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("rs_123")
|
expect(result[0].content[0].providerOptions?.openai?.itemId).toBeUndefined()
|
||||||
expect(result[0].content[1].providerOptions?.openai?.itemId).toBe("msg_456")
|
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 = {
|
const zenModel = {
|
||||||
...openaiModel,
|
...openaiModel,
|
||||||
providerID: "zen",
|
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[]
|
const result = ProviderTransform.message(msgs, zenModel, { store: false }) as any[]
|
||||||
|
|
||||||
expect(result).toHaveLength(1)
|
expect(result).toHaveLength(1)
|
||||||
expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("rs_123")
|
expect(result[0].content[0].providerOptions?.openai?.itemId).toBeUndefined()
|
||||||
expect(result[0].content[1].providerOptions?.openai?.itemId).toBe("msg_456")
|
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 = [
|
const msgs = [
|
||||||
{
|
{
|
||||||
role: "assistant",
|
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[]
|
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")
|
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", () => {
|
test("preserves metadata for openai package when store is true", () => {
|
||||||
const msgs = [
|
const msgs = [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue