Skip to content

Commit a86ecf3

Browse files
authored
fix: adjust item id stripping to happen prior to request signing (anomalyco#31429)
1 parent 6e84142 commit a86ecf3

3 files changed

Lines changed: 109 additions & 40 deletions

File tree

packages/opencode/src/provider/provider.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1692,26 +1692,6 @@ export const layer = Layer.effect(
16921692
const combined = signals.length === 0 ? null : signals.length === 1 ? signals[0] : AbortSignal.any(signals)
16931693
if (combined) opts.signal = combined
16941694

1695-
// Strip openai itemId metadata following what codex does
1696-
if (
1697-
(model.api.npm === "@ai-sdk/openai" ||
1698-
model.api.npm === "@ai-sdk/azure" ||
1699-
model.api.npm === "@ai-sdk/amazon-bedrock/mantle") &&
1700-
opts.body &&
1701-
opts.method === "POST"
1702-
) {
1703-
const body = JSON.parse(opts.body as string)
1704-
const keepIds = body.store === true
1705-
if (!keepIds && Array.isArray(body.input)) {
1706-
for (const item of body.input) {
1707-
if ("id" in item) {
1708-
delete item.id
1709-
}
1710-
}
1711-
opts.body = JSON.stringify(body)
1712-
}
1713-
}
1714-
17151695
const res = await fetchFn(input, {
17161696
...opts,
17171697
// @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682

packages/opencode/src/provider/transform.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,24 @@ function unsupportedParts(msgs: ModelMessage[], model: Provider.Model): ModelMes
409409
})
410410
}
411411

412+
function mapProviderOptions(
413+
msgs: ModelMessage[],
414+
transform: (options: Record<string, any> | undefined) => Record<string, any> | undefined,
415+
) {
416+
return msgs.map((msg) => {
417+
if (!Array.isArray(msg.content)) return { ...msg, providerOptions: transform(msg.providerOptions) }
418+
return {
419+
...msg,
420+
providerOptions: transform(msg.providerOptions),
421+
content: msg.content.map((part) =>
422+
part.type === "tool-approval-request" || part.type === "tool-approval-response"
423+
? part
424+
: { ...part, providerOptions: transform(part.providerOptions) },
425+
),
426+
} as typeof msg
427+
})
428+
}
429+
412430
export function message(msgs: ModelMessage[], model: Provider.Model, options: Record<string, unknown>) {
413431
msgs = unsupportedParts(msgs, model)
414432
msgs = normalizeMessages(msgs, model, options)
@@ -438,18 +456,20 @@ export function message(msgs: ModelMessage[], model: Provider.Model, options: Re
438456
return result
439457
}
440458

441-
msgs = msgs.map((msg) => {
442-
if (!Array.isArray(msg.content)) return { ...msg, providerOptions: remap(msg.providerOptions) }
443-
return {
444-
...msg,
445-
providerOptions: remap(msg.providerOptions),
446-
content: msg.content.map((part) => {
447-
if (part.type === "tool-approval-request" || part.type === "tool-approval-response") {
448-
return { ...part }
449-
}
450-
return { ...part, providerOptions: remap(part.providerOptions) }
451-
}),
452-
} as typeof msg
459+
msgs = mapProviderOptions(msgs, remap)
460+
}
461+
462+
// Strip Responses item IDs before serialization, following Codex and keeping signed request bodies immutable.
463+
if (
464+
options.store !== true &&
465+
key &&
466+
["@ai-sdk/openai", "@ai-sdk/azure", "@ai-sdk/amazon-bedrock/mantle"].includes(model.api.npm)
467+
) {
468+
msgs = mapProviderOptions(msgs, (options) => {
469+
if (!options?.[key] || !("itemId" in options[key])) return options
470+
const metadata = { ...options[key] }
471+
delete metadata.itemId
472+
return { ...options, [key]: metadata }
453473
})
454474
}
455475

packages/opencode/test/provider/transform.test.ts

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1805,7 +1805,7 @@ describe("ProviderTransform.message - strip openai metadata when store=false", (
18051805
headers: {},
18061806
} as any
18071807

1808-
test("preserves itemId and reasoningEncryptedContent when store=false", () => {
1808+
test("strips OpenAI itemId and preserves reasoningEncryptedContent when store=false", () => {
18091809
const msgs = [
18101810
{
18111811
role: "assistant",
@@ -1836,11 +1836,12 @@ describe("ProviderTransform.message - strip openai metadata when store=false", (
18361836
const result = ProviderTransform.message(msgs, openaiModel, { store: false }) as any[]
18371837

18381838
expect(result).toHaveLength(1)
1839-
expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("rs_123")
1840-
expect(result[0].content[1].providerOptions?.openai?.itemId).toBe("msg_456")
1839+
expect(result[0].content[0].providerOptions?.openai?.itemId).toBeUndefined()
1840+
expect(result[0].content[0].providerOptions?.openai?.reasoningEncryptedContent).toBe("encrypted")
1841+
expect(result[0].content[1].providerOptions?.openai?.itemId).toBeUndefined()
18411842
})
18421843

1843-
test("preserves itemId and reasoningEncryptedContent when store=false even when not openai", () => {
1844+
test("uses the SDK package namespace rather than provider ID", () => {
18441845
const zenModel = {
18451846
...openaiModel,
18461847
providerID: "zen",
@@ -1875,11 +1876,12 @@ describe("ProviderTransform.message - strip openai metadata when store=false", (
18751876
const result = ProviderTransform.message(msgs, zenModel, { store: false }) as any[]
18761877

18771878
expect(result).toHaveLength(1)
1878-
expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("rs_123")
1879-
expect(result[0].content[1].providerOptions?.openai?.itemId).toBe("msg_456")
1879+
expect(result[0].content[0].providerOptions?.openai?.itemId).toBeUndefined()
1880+
expect(result[0].content[0].providerOptions?.openai?.reasoningEncryptedContent).toBe("encrypted")
1881+
expect(result[0].content[1].providerOptions?.openai?.itemId).toBeUndefined()
18801882
})
18811883

1882-
test("preserves other openai options including itemId", () => {
1884+
test("preserves other OpenAI options", () => {
18831885
const msgs = [
18841886
{
18851887
role: "assistant",
@@ -1900,10 +1902,77 @@ describe("ProviderTransform.message - strip openai metadata when store=false", (
19001902

19011903
const result = ProviderTransform.message(msgs, openaiModel, { store: false }) as any[]
19021904

1903-
expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_123")
1905+
expect(result[0].content[0].providerOptions?.openai?.itemId).toBeUndefined()
19041906
expect(result[0].content[0].providerOptions?.openai?.otherOption).toBe("value")
19051907
})
19061908

1909+
test("strips Azure itemId from the Azure namespace", () => {
1910+
const azureModel = {
1911+
...openaiModel,
1912+
providerID: "azure",
1913+
api: {
1914+
id: "gpt-5",
1915+
url: "https://example.openai.azure.com",
1916+
npm: "@ai-sdk/azure",
1917+
},
1918+
}
1919+
const msgs = [
1920+
{
1921+
role: "assistant",
1922+
content: [
1923+
{
1924+
type: "text",
1925+
text: "Hello",
1926+
providerOptions: {
1927+
azure: { itemId: "msg_123", otherOption: "value" },
1928+
openai: { itemId: "msg_openai" },
1929+
},
1930+
},
1931+
],
1932+
},
1933+
] as any[]
1934+
1935+
const result = ProviderTransform.message(msgs, azureModel, { store: false }) as any[]
1936+
1937+
expect(result[0].content[0].providerOptions?.azure?.itemId).toBeUndefined()
1938+
expect(result[0].content[0].providerOptions?.azure?.otherOption).toBe("value")
1939+
expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_openai")
1940+
})
1941+
1942+
test("strips Bedrock Mantle itemId from the OpenAI namespace", () => {
1943+
const mantleModel = {
1944+
...openaiModel,
1945+
providerID: "amazon-bedrock",
1946+
api: {
1947+
id: "openai.gpt-5.5",
1948+
url: "https://bedrock-mantle.us-east-2.api.aws/openai/v1",
1949+
npm: "@ai-sdk/amazon-bedrock/mantle",
1950+
},
1951+
}
1952+
const msgs = [
1953+
{
1954+
role: "assistant",
1955+
providerOptions: { openai: { itemId: "msg_root", otherOption: "root-value" } },
1956+
content: [
1957+
{
1958+
type: "reasoning",
1959+
text: "thinking...",
1960+
providerOptions: {
1961+
openai: { itemId: "rs_123", reasoningEncryptedContent: "encrypted" },
1962+
},
1963+
},
1964+
],
1965+
},
1966+
] as any[]
1967+
1968+
const result = ProviderTransform.message(msgs, mantleModel, { store: false }) as any[]
1969+
1970+
expect(result[0].providerOptions?.openai?.itemId).toBeUndefined()
1971+
expect(result[0].providerOptions?.openai?.otherOption).toBe("root-value")
1972+
expect(result[0].content[0].providerOptions?.openai?.itemId).toBeUndefined()
1973+
expect(result[0].content[0].providerOptions?.openai?.reasoningEncryptedContent).toBe("encrypted")
1974+
})
1975+
19071976
test("preserves metadata for openai package when store is true", () => {
19081977
const msgs = [
19091978
{

0 commit comments

Comments
 (0)