| 110 | * pending-tool-call scan on the next request never force-executes them. |
| 111 | */ |
| 112 | export async function* translateOpencodeStream( |
| 113 | events: AsyncIterable<OpencodeStreamEvent>, |
| 114 | ctx: TranslateContext, |
| 115 | ): AsyncIterable<StreamChunk> { |
| 116 | const { model, runId, threadId, genId } = ctx |
| 117 | const now = () => Date.now() |
| 118 | |
| 119 | let runStarted = false |
| 120 | /** Tool calls started but with no result yet, keyed by callID. */ |
| 121 | const unresolvedToolCalls = new Set<string>() |
| 122 | /** Tool call ids that already emitted TOOL_CALL_START/ARGS/END. */ |
| 123 | const openedToolCalls = new Set<string>() |
| 124 | /** Tool call ids that already emitted a TOOL_CALL_RESULT. */ |
| 125 | const resolvedToolCalls = new Set<string>() |
| 126 | |
| 127 | /** Accumulated text per text-part id, for delta derivation. */ |
| 128 | const textAccumulators = new Map<string, string>() |
| 129 | let openTextId: string | null = null |
| 130 | let openReasoningId: string | null = null |
| 131 | |
| 132 | function* startRun(): Generator<StreamChunk> { |
| 133 | if (runStarted) return |
| 134 | runStarted = true |
| 135 | yield { |
| 136 | type: EventType.RUN_STARTED, |
| 137 | runId, |
| 138 | threadId, |
| 139 | model, |
| 140 | timestamp: now(), |
| 141 | ...(ctx.parentRunId !== undefined && { parentRunId: ctx.parentRunId }), |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | function* closeText(): Generator<StreamChunk> { |
| 146 | if (openTextId !== null) { |
| 147 | yield { |
| 148 | type: EventType.TEXT_MESSAGE_END, |
| 149 | messageId: openTextId, |
| 150 | model, |
| 151 | timestamp: now(), |
| 152 | } |
| 153 | openTextId = null |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | function* closeReasoning(): Generator<StreamChunk> { |
| 158 | if (openReasoningId !== null) { |
| 159 | yield { |
| 160 | type: EventType.REASONING_MESSAGE_END, |
| 161 | messageId: openReasoningId, |
| 162 | model, |
| 163 | timestamp: now(), |
| 164 | } |
| 165 | yield { |
| 166 | type: EventType.REASONING_END, |
| 167 | messageId: openReasoningId, |
| 168 | model, |
| 169 | timestamp: now(), |