Skip to content

Commit 260ab60

Browse files
fix: track reasoning by output_index for copilot compatibility (anomalyco#9124)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
1 parent e2f1f4d commit 260ab60

2 files changed

Lines changed: 46 additions & 27 deletions

File tree

packages/opencode/src/provider/provider.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -615,13 +615,13 @@ export namespace Provider {
615615
},
616616
experimentalOver200K: model.cost?.context_over_200k
617617
? {
618-
cache: {
619-
read: model.cost.context_over_200k.cache_read ?? 0,
620-
write: model.cost.context_over_200k.cache_write ?? 0,
621-
},
622-
input: model.cost.context_over_200k.input,
623-
output: model.cost.context_over_200k.output,
624-
}
618+
cache: {
619+
read: model.cost.context_over_200k.cache_read ?? 0,
620+
write: model.cost.context_over_200k.cache_write ?? 0,
621+
},
622+
input: model.cost.context_over_200k.input,
623+
output: model.cost.context_over_200k.output,
624+
}
625625
: undefined,
626626
},
627627
limit: {

packages/opencode/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -815,14 +815,20 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 {
815815
// flag that checks if there have been client-side tool calls (not executed by openai)
816816
let hasFunctionCall = false
817817

818+
// Track reasoning by output_index instead of item_id
819+
// GitHub Copilot rotates encrypted item IDs on every event
818820
const activeReasoning: Record<
819-
string,
821+
number,
820822
{
823+
canonicalId: string // the item.id from output_item.added
821824
encryptedContent?: string | null
822825
summaryParts: number[]
823826
}
824827
> = {}
825828

829+
// Track current active reasoning output_index for correlating summary events
830+
let currentReasoningOutputIndex: number | null = null
831+
826832
// Track a stable text part id for the current assistant message.
827833
// Copilot may change item_id across text deltas; normalize to one id.
828834
let currentTextId: string | null = null
@@ -933,10 +939,12 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 {
933939
},
934940
})
935941
} else if (isResponseOutputItemAddedReasoningChunk(value)) {
936-
activeReasoning[value.item.id] = {
942+
activeReasoning[value.output_index] = {
943+
canonicalId: value.item.id,
937944
encryptedContent: value.item.encrypted_content,
938945
summaryParts: [0],
939946
}
947+
currentReasoningOutputIndex = value.output_index
940948

941949
controller.enqueue({
942950
type: "reasoning-start",
@@ -1091,22 +1099,25 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 {
10911099
currentTextId = null
10921100
}
10931101
} else if (isResponseOutputItemDoneReasoningChunk(value)) {
1094-
const activeReasoningPart = activeReasoning[value.item.id]
1102+
const activeReasoningPart = activeReasoning[value.output_index]
10951103
if (activeReasoningPart) {
10961104
for (const summaryIndex of activeReasoningPart.summaryParts) {
10971105
controller.enqueue({
10981106
type: "reasoning-end",
1099-
id: `${value.item.id}:${summaryIndex}`,
1107+
id: `${activeReasoningPart.canonicalId}:${summaryIndex}`,
11001108
providerMetadata: {
11011109
openai: {
1102-
itemId: value.item.id,
1110+
itemId: activeReasoningPart.canonicalId,
11031111
reasoningEncryptedContent: value.item.encrypted_content ?? null,
11041112
},
11051113
},
11061114
})
11071115
}
1116+
delete activeReasoning[value.output_index]
1117+
if (currentReasoningOutputIndex === value.output_index) {
1118+
currentReasoningOutputIndex = null
1119+
}
11081120
}
1109-
delete activeReasoning[value.item.id]
11101121
}
11111122
} else if (isResponseFunctionCallArgumentsDeltaChunk(value)) {
11121123
const toolCall = ongoingToolCalls[value.output_index]
@@ -1198,32 +1209,40 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 {
11981209
logprobs.push(value.logprobs)
11991210
}
12001211
} else if (isResponseReasoningSummaryPartAddedChunk(value)) {
1212+
const activeItem =
1213+
currentReasoningOutputIndex !== null ? activeReasoning[currentReasoningOutputIndex] : null
1214+
12011215
// the first reasoning start is pushed in isResponseOutputItemAddedReasoningChunk.
1202-
if (value.summary_index > 0) {
1203-
activeReasoning[value.item_id]?.summaryParts.push(value.summary_index)
1216+
if (activeItem && value.summary_index > 0) {
1217+
activeItem.summaryParts.push(value.summary_index)
12041218

12051219
controller.enqueue({
12061220
type: "reasoning-start",
1207-
id: `${value.item_id}:${value.summary_index}`,
1221+
id: `${activeItem.canonicalId}:${value.summary_index}`,
12081222
providerMetadata: {
12091223
openai: {
1210-
itemId: value.item_id,
1211-
reasoningEncryptedContent: activeReasoning[value.item_id]?.encryptedContent ?? null,
1224+
itemId: activeItem.canonicalId,
1225+
reasoningEncryptedContent: activeItem.encryptedContent ?? null,
12121226
},
12131227
},
12141228
})
12151229
}
12161230
} else if (isResponseReasoningSummaryTextDeltaChunk(value)) {
1217-
controller.enqueue({
1218-
type: "reasoning-delta",
1219-
id: `${value.item_id}:${value.summary_index}`,
1220-
delta: value.delta,
1221-
providerMetadata: {
1222-
openai: {
1223-
itemId: value.item_id,
1231+
const activeItem =
1232+
currentReasoningOutputIndex !== null ? activeReasoning[currentReasoningOutputIndex] : null
1233+
1234+
if (activeItem) {
1235+
controller.enqueue({
1236+
type: "reasoning-delta",
1237+
id: `${activeItem.canonicalId}:${value.summary_index}`,
1238+
delta: value.delta,
1239+
providerMetadata: {
1240+
openai: {
1241+
itemId: activeItem.canonicalId,
1242+
},
12241243
},
1225-
},
1226-
})
1244+
})
1245+
}
12271246
} else if (isResponseFinishedChunk(value)) {
12281247
finishReason = mapOpenAIResponseFinishReason({
12291248
finishReason: value.response.incomplete_details?.reason,

0 commit comments

Comments
 (0)