codex-overleaf-link 1.9.13 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +24 -23
  2. package/extension/bootstrap/manifest.template.json +2 -1
  3. package/extension/runtime-manifest.json +13 -6
  4. package/extension/src/background.js +7 -0
  5. package/extension/src/content/composerPanel.js +13 -3
  6. package/extension/src/content/contentRuntime.js +130 -65
  7. package/extension/src/content/contextTray.js +173 -9
  8. package/extension/src/content/markdownText.js +16 -1
  9. package/extension/src/content/mathText.js +233 -0
  10. package/extension/src/content/modelPicker.js +117 -106
  11. package/extension/src/content/modelPickerSupport.js +115 -0
  12. package/extension/src/content/nativeChannel.js +20 -1
  13. package/extension/src/content/panelMaintenance.js +2 -0
  14. package/extension/src/content/projectSettingsCoordinator.js +2 -0
  15. package/extension/src/content/providerSettingsCoordinator.js +413 -0
  16. package/extension/src/content/providerSettingsDialog.js +981 -0
  17. package/extension/src/content/runTimelineView.js +24 -5
  18. package/extension/src/content/settingsPanel.js +33 -2
  19. package/extension/src/content/writebackOrchestrator.js +3 -1
  20. package/extension/src/page/overleafProjectSnapshot.js +29 -33
  21. package/extension/src/page/snapshotRouter.js +75 -8
  22. package/extension/src/page/trackedChangesLifecycle.js +154 -26
  23. package/extension/src/page/writebackRouter.js +31 -60
  24. package/extension/src/pageBridge.js +1 -1
  25. package/extension/src/shared/agentTranscript.js +130 -0
  26. package/extension/src/shared/compatibility.js +10 -7
  27. package/extension/src/shared/failureReasons.js +75 -0
  28. package/extension/src/shared/i18n.js +22 -0
  29. package/extension/src/shared/models.js +1 -0
  30. package/extension/src/shared/providerProfiles.js +283 -0
  31. package/extension/src/shared/sessionState.js +12 -2
  32. package/extension/src/shared/storageDb.js +27 -19
  33. package/extension/src/shared/storageRunActions.js +99 -0
  34. package/extension/styles/panel.css +587 -1
  35. package/extension/vendor/katex/LICENSE +21 -0
  36. package/extension/vendor/katex/VERSION +2 -0
  37. package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
  38. package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
  39. package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  40. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  41. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  42. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  43. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  44. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  45. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  46. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  47. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  48. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  49. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  50. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  51. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  52. package/extension/vendor/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
  53. package/extension/vendor/katex/fonts/KaTeX_Main-Bold.woff +0 -0
  54. package/extension/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  55. package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  56. package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  57. package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  58. package/extension/vendor/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
  59. package/extension/vendor/katex/fonts/KaTeX_Main-Italic.woff +0 -0
  60. package/extension/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  61. package/extension/vendor/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
  62. package/extension/vendor/katex/fonts/KaTeX_Main-Regular.woff +0 -0
  63. package/extension/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  64. package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  65. package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  66. package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  67. package/extension/vendor/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
  68. package/extension/vendor/katex/fonts/KaTeX_Math-Italic.woff +0 -0
  69. package/extension/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  70. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  71. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  72. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  73. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  74. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  75. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  76. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  77. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  78. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  79. package/extension/vendor/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
  80. package/extension/vendor/katex/fonts/KaTeX_Script-Regular.woff +0 -0
  81. package/extension/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  82. package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
  83. package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
  84. package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  85. package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
  86. package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
  87. package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  88. package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
  89. package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
  90. package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  91. package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
  92. package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
  93. package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  94. package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  95. package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  96. package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  97. package/extension/vendor/katex/katex.min.css +1 -0
  98. package/extension/vendor/katex/katex.min.js +1 -0
  99. package/native-host/src/anthropicBridgeRequest.js +435 -0
  100. package/native-host/src/anthropicBridgeResponse.js +431 -0
  101. package/native-host/src/anthropicMessagesBridge.js +281 -0
  102. package/native-host/src/chatBridgeRequest.js +274 -0
  103. package/native-host/src/chatBridgeResponse.js +594 -0
  104. package/native-host/src/chatCompletionsBridge.js +485 -0
  105. package/native-host/src/codexCommand.js +25 -0
  106. package/native-host/src/codexPromptAssembly.js +9 -0
  107. package/native-host/src/codexProviderLaunch.js +213 -0
  108. package/native-host/src/codexProviderTest.js +196 -0
  109. package/native-host/src/codexSessionRunner.js +29 -25
  110. package/native-host/src/managedInstall.js +1 -0
  111. package/native-host/src/providerBridgeRoutes.js +11 -0
  112. package/native-host/src/providerModelCatalog.js +107 -0
  113. package/native-host/src/providerProfile.js +454 -0
  114. package/native-host/src/providerReasoning.js +235 -0
  115. package/native-host/src/providerRedaction.js +18 -0
  116. package/native-host/src/providerRuntime.js +365 -0
  117. package/native-host/src/providerStore.js +691 -0
  118. package/native-host/src/readProgressGuard.js +360 -0
  119. package/native-host/src/taskRunnerRuntime.js +40 -4
  120. package/native-host/src/updateArchive.js +1 -0
  121. package/native-host/src/updateTrust.js +2 -1
  122. package/package.json +2 -1
@@ -0,0 +1,594 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const { requiresReasoningContentReplay } = require('./providerReasoning');
5
+
6
+ function convertChatResponse(chat = {}, context = {}) {
7
+ const choice = Array.isArray(chat.choices) ? chat.choices[0] || {} : {};
8
+ const message = choice.message || {};
9
+ assertNoLeakedToolControlTokens(message);
10
+ const split = splitReasoning(message.content, extractReasoning(message));
11
+ const output = [];
12
+ if (split.reasoning) output.push(buildReasoningItem(split.reasoning));
13
+ if (split.content) output.push(buildMessageItem(split.content));
14
+ const legacyCall = message.function_call
15
+ ? [{ id: randomId('call'), type: 'function', function: message.function_call }]
16
+ : [];
17
+ const toolCalls = normalizeToolCalls([...(message.tool_calls || []), ...legacyCall], context.toolKinds);
18
+ output.push(...toolCalls.map(call => call.item));
19
+ if (!output.length) {
20
+ throw responseError('The provider completed without usable text, reasoning, or tool calls.');
21
+ }
22
+ const terminal = mapChatFinishReason(choice.finish_reason);
23
+ const response = buildResponseEnvelope({
24
+ id: responseId(chat.id),
25
+ createdAt: Number(chat.created) || nowSeconds(),
26
+ model: chat.model || context.model,
27
+ output,
28
+ usage: normalizeUsage(chat.usage),
29
+ requestBody: context.requestBody,
30
+ ...terminal
31
+ });
32
+ return {
33
+ response,
34
+ assistantMessage: buildAssistantMessage(split, toolCalls, context.launch),
35
+ providerFinishReason: normalizeText(choice.finish_reason)
36
+ };
37
+ }
38
+
39
+ async function streamChatResponse({
40
+ upstream,
41
+ res,
42
+ context = {},
43
+ onComplete = () => {},
44
+ onActivity = () => {},
45
+ recoverInterrupted = () => ''
46
+ } = {}) {
47
+ const state = createStreamState(context);
48
+ beginSse(res);
49
+ emit(state, res, 'response.created', { response: streamEnvelope(state, 'in_progress') });
50
+ emit(state, res, 'response.in_progress', { response: streamEnvelope(state, 'in_progress') });
51
+ const decoder = new TextDecoder();
52
+ let buffer = '';
53
+ let done = false;
54
+ let interruptedReason = '';
55
+ try {
56
+ for await (const chunk of upstream.body || []) {
57
+ onActivity();
58
+ buffer += decoder.decode(chunk, { stream: true });
59
+ let boundary = findSseBoundary(buffer);
60
+ while (boundary) {
61
+ const block = buffer.slice(0, boundary.index);
62
+ buffer = buffer.slice(boundary.index + boundary.length);
63
+ const data = parseSseData(block);
64
+ if (data === '[DONE]') {
65
+ done = true;
66
+ break;
67
+ }
68
+ if (data) {
69
+ assertNoLeakedToolControlTokens(data);
70
+ processChatChunk(state, res, parseJson(data));
71
+ // Several OpenAI-compatible gateways emit a terminal
72
+ // finish_reason but keep the SSE connection open and omit [DONE].
73
+ // The finish_reason is the protocol terminal signal; waiting for the
74
+ // socket to close turns a completed answer into a transport timeout.
75
+ if (state.finishReason) {
76
+ done = true;
77
+ break;
78
+ }
79
+ }
80
+ boundary = findSseBoundary(buffer);
81
+ }
82
+ if (done) break;
83
+ }
84
+ } catch (error) {
85
+ interruptedReason = normalizeText(recoverInterrupted(error));
86
+ const hasRecoverablePartialOutput = interruptedReason
87
+ && (state.reasoning || state.message)
88
+ && state.tools.size === 0;
89
+ if (!hasRecoverablePartialOutput) throw error;
90
+ }
91
+ if (!done && !interruptedReason) {
92
+ buffer += decoder.decode();
93
+ const data = parseSseData(buffer);
94
+ if (data && data !== '[DONE]') {
95
+ assertNoLeakedToolControlTokens(data);
96
+ processChatChunk(state, res, parseJson(data));
97
+ }
98
+ }
99
+ const completed = completeStream(state, res, interruptedReason);
100
+ onComplete(completed);
101
+ res.end();
102
+ return completed.response;
103
+ }
104
+
105
+ function writeConvertedResponseAsSse(res, converted, context = {}, onComplete = () => {}) {
106
+ const state = { sequence: 0 };
107
+ beginSse(res);
108
+ const pending = {
109
+ ...converted.response,
110
+ status: 'in_progress',
111
+ incomplete_details: null,
112
+ output: [],
113
+ usage: null
114
+ };
115
+ emit(state, res, 'response.created', { response: pending });
116
+ emit(state, res, 'response.in_progress', { response: pending });
117
+ converted.response.output.forEach((item, outputIndex) => emitWholeItem(state, res, item, outputIndex));
118
+ emit(
119
+ state,
120
+ res,
121
+ converted.response.status === 'incomplete' ? 'response.incomplete' : 'response.completed',
122
+ { response: converted.response }
123
+ );
124
+ onComplete(converted);
125
+ res.end();
126
+ }
127
+
128
+ function processChatChunk(state, res, chunk) {
129
+ if (!chunk || typeof chunk !== 'object') return;
130
+ if (chunk.error) throw new Error(normalizeText(chunk.error.message) || 'Upstream provider returned an error.');
131
+ if (chunk.id && !state.upstreamId) {
132
+ state.upstreamId = chunk.id;
133
+ }
134
+ if (chunk.model) state.model = chunk.model;
135
+ if (chunk.usage) state.usage = normalizeUsage(chunk.usage);
136
+ const choice = Array.isArray(chunk.choices) ? chunk.choices[0] : null;
137
+ if (!choice) return;
138
+ if (choice.finish_reason) state.finishReason = String(choice.finish_reason);
139
+ const delta = choice.delta || choice.message || {};
140
+ const reasoning = extractReasoning(delta);
141
+ if (reasoning) appendReasoning(state, res, reasoning);
142
+ const content = normalizeStreamContent(delta.content);
143
+ if (content) appendMessage(state, res, content);
144
+ for (const call of Array.isArray(delta.tool_calls) ? delta.tool_calls : []) {
145
+ appendToolCall(state, res, call);
146
+ }
147
+ if (delta.function_call) {
148
+ appendToolCall(state, res, { index: 0, function: delta.function_call });
149
+ }
150
+ }
151
+
152
+ function appendReasoning(state, res, delta) {
153
+ if (!state.reasoning) {
154
+ const item = { id: randomId('rs'), type: 'reasoning', status: 'in_progress', summary: [] };
155
+ state.reasoning = { item, outputIndex: reserveItem(state, item), text: '' };
156
+ emit(state, res, 'response.output_item.added', { output_index: state.reasoning.outputIndex, item });
157
+ emit(state, res, 'response.reasoning_summary_part.added', {
158
+ item_id: item.id,
159
+ output_index: state.reasoning.outputIndex,
160
+ summary_index: 0,
161
+ part: { type: 'summary_text', text: '' }
162
+ });
163
+ }
164
+ state.reasoning.text += delta;
165
+ emit(state, res, 'response.reasoning_summary_text.delta', {
166
+ item_id: state.reasoning.item.id,
167
+ output_index: state.reasoning.outputIndex,
168
+ summary_index: 0,
169
+ delta
170
+ });
171
+ }
172
+
173
+ function appendMessage(state, res, delta) {
174
+ if (!state.message) {
175
+ const item = { id: randomId('msg'), type: 'message', status: 'in_progress', role: 'assistant', content: [] };
176
+ state.message = { item, outputIndex: reserveItem(state, item), text: '' };
177
+ emit(state, res, 'response.output_item.added', { output_index: state.message.outputIndex, item });
178
+ emit(state, res, 'response.content_part.added', {
179
+ item_id: item.id,
180
+ output_index: state.message.outputIndex,
181
+ content_index: 0,
182
+ part: { type: 'output_text', text: '', annotations: [] }
183
+ });
184
+ }
185
+ state.message.text += delta;
186
+ emit(state, res, 'response.output_text.delta', {
187
+ item_id: state.message.item.id,
188
+ output_index: state.message.outputIndex,
189
+ content_index: 0,
190
+ delta,
191
+ logprobs: []
192
+ });
193
+ }
194
+
195
+ function appendToolCall(state, res, delta) {
196
+ const index = Number.isInteger(delta.index) ? delta.index : state.tools.size;
197
+ let tool = state.tools.get(index);
198
+ if (!tool) {
199
+ tool = { index, callId: '', name: '', arguments: '', emittedArguments: 0, opened: false, kind: 'function' };
200
+ state.tools.set(index, tool);
201
+ }
202
+ if (delta.id) tool.callId = String(delta.id);
203
+ if (delta.function?.name) tool.name += String(delta.function.name);
204
+ if (delta.function?.arguments) tool.arguments += String(delta.function.arguments);
205
+ if (!tool.opened && tool.callId && tool.name) openTool(state, res, tool);
206
+ if (tool.opened && tool.kind === 'function' && tool.arguments.length > tool.emittedArguments) {
207
+ const argumentDelta = tool.arguments.slice(tool.emittedArguments);
208
+ tool.emittedArguments = tool.arguments.length;
209
+ emit(state, res, 'response.function_call_arguments.delta', {
210
+ item_id: tool.item.id,
211
+ output_index: tool.outputIndex,
212
+ delta: argumentDelta
213
+ });
214
+ }
215
+ }
216
+
217
+ function openTool(state, res, tool) {
218
+ tool.callId ||= randomId('call');
219
+ tool.name ||= 'tool';
220
+ const spec = state.context.toolKinds?.get(tool.name);
221
+ tool.chatName = tool.name;
222
+ tool.name = typeof spec === 'object' ? spec.name || tool.name : tool.name;
223
+ tool.namespace = typeof spec === 'object' ? spec.namespace || '' : '';
224
+ tool.kind = (typeof spec === 'object' ? spec.kind : spec) === 'custom' ? 'custom' : 'function';
225
+ tool.item = tool.kind === 'custom'
226
+ ? { id: randomId('ct'), type: 'custom_tool_call', status: 'in_progress', call_id: tool.callId, name: tool.name, input: '' }
227
+ : { id: randomId('fc'), type: 'function_call', status: 'in_progress', call_id: tool.callId, name: tool.name, arguments: '' };
228
+ if (tool.namespace) tool.item.namespace = tool.namespace;
229
+ tool.outputIndex = reserveItem(state, tool.item);
230
+ tool.opened = true;
231
+ emit(state, res, 'response.output_item.added', { output_index: tool.outputIndex, item: tool.item });
232
+ }
233
+
234
+ function completeStream(state, res, interruptedReason = '') {
235
+ if (!state.reasoning && !state.message && state.tools.size === 0) {
236
+ throw responseError(`The provider stream ended without usable output${state.finishReason ? ` (${state.finishReason})` : ''}.`);
237
+ }
238
+ if (state.reasoning) completeReasoning(state, res);
239
+ if (state.message) completeMessage(state, res);
240
+ const tools = Array.from(state.tools.values()).sort((a, b) => a.index - b.index);
241
+ for (const tool of tools) completeTool(state, res, tool);
242
+ const terminal = interruptedReason
243
+ ? { status: 'incomplete', incompleteReason: 'max_output_tokens' }
244
+ : mapChatFinishReason(state.finishReason);
245
+ const response = streamEnvelope(state, terminal.status, terminal.incompleteReason);
246
+ emit(
247
+ state,
248
+ res,
249
+ terminal.status === 'incomplete' ? 'response.incomplete' : 'response.completed',
250
+ { response }
251
+ );
252
+ const split = {
253
+ content: state.message?.text || '',
254
+ reasoning: state.reasoning?.text || ''
255
+ };
256
+ return {
257
+ response,
258
+ assistantMessage: buildAssistantMessage(split, tools.map(tool => ({
259
+ callId: tool.callId,
260
+ name: tool.name,
261
+ chatName: tool.chatName,
262
+ arguments: tool.arguments,
263
+ kind: tool.kind,
264
+ item: tool.item
265
+ })), state.context.launch),
266
+ providerFinishReason: interruptedReason
267
+ ? `bridge_${interruptedReason}`
268
+ : normalizeText(state.finishReason)
269
+ };
270
+ }
271
+
272
+ function completeReasoning(state, res) {
273
+ const { item, outputIndex, text } = state.reasoning;
274
+ item.status = 'completed';
275
+ item.summary = [{ type: 'summary_text', text }];
276
+ emit(state, res, 'response.reasoning_summary_text.done', { item_id: item.id, output_index: outputIndex, summary_index: 0, text });
277
+ emit(state, res, 'response.reasoning_summary_part.done', { item_id: item.id, output_index: outputIndex, summary_index: 0, part: item.summary[0] });
278
+ emit(state, res, 'response.output_item.done', { output_index: outputIndex, item });
279
+ state.items[outputIndex] = item;
280
+ }
281
+
282
+ function completeMessage(state, res) {
283
+ const { item, outputIndex, text } = state.message;
284
+ const part = { type: 'output_text', text, annotations: [] };
285
+ item.status = 'completed';
286
+ item.content = [part];
287
+ emit(state, res, 'response.output_text.done', { item_id: item.id, output_index: outputIndex, content_index: 0, text, logprobs: [] });
288
+ emit(state, res, 'response.content_part.done', { item_id: item.id, output_index: outputIndex, content_index: 0, part });
289
+ emit(state, res, 'response.output_item.done', { output_index: outputIndex, item });
290
+ state.items[outputIndex] = item;
291
+ }
292
+
293
+ function completeTool(state, res, tool) {
294
+ if (!tool.opened) openTool(state, res, tool);
295
+ tool.item.status = 'completed';
296
+ if (tool.kind === 'custom') {
297
+ const input = unwrapCustomArguments(tool.arguments);
298
+ tool.item.input = input;
299
+ if (input) emit(state, res, 'response.custom_tool_call_input.delta', { item_id: tool.item.id, output_index: tool.outputIndex, delta: input });
300
+ emit(state, res, 'response.custom_tool_call_input.done', { item_id: tool.item.id, output_index: tool.outputIndex, input });
301
+ } else {
302
+ tool.item.arguments = tool.arguments || '{}';
303
+ emit(state, res, 'response.function_call_arguments.done', {
304
+ item_id: tool.item.id,
305
+ output_index: tool.outputIndex,
306
+ arguments: tool.item.arguments
307
+ });
308
+ }
309
+ emit(state, res, 'response.output_item.done', { output_index: tool.outputIndex, item: tool.item });
310
+ state.items[tool.outputIndex] = tool.item;
311
+ }
312
+
313
+ function emitWholeItem(state, res, item, outputIndex) {
314
+ const pending = { ...item, status: 'in_progress' };
315
+ if (item.type === 'message') pending.content = [];
316
+ if (item.type === 'reasoning') pending.summary = [];
317
+ if (item.type === 'function_call') pending.arguments = '';
318
+ if (item.type === 'custom_tool_call') pending.input = '';
319
+ emit(state, res, 'response.output_item.added', { output_index: outputIndex, item: pending });
320
+ if (item.type === 'message') {
321
+ const part = item.content?.[0] || { type: 'output_text', text: '', annotations: [] };
322
+ emit(state, res, 'response.content_part.added', { item_id: item.id, output_index: outputIndex, content_index: 0, part: { ...part, text: '' } });
323
+ if (part.text) emit(state, res, 'response.output_text.delta', { item_id: item.id, output_index: outputIndex, content_index: 0, delta: part.text, logprobs: [] });
324
+ emit(state, res, 'response.output_text.done', { item_id: item.id, output_index: outputIndex, content_index: 0, text: part.text || '', logprobs: [] });
325
+ emit(state, res, 'response.content_part.done', { item_id: item.id, output_index: outputIndex, content_index: 0, part });
326
+ } else if (item.type === 'function_call') {
327
+ if (item.arguments) emit(state, res, 'response.function_call_arguments.delta', { item_id: item.id, output_index: outputIndex, delta: item.arguments });
328
+ emit(state, res, 'response.function_call_arguments.done', { item_id: item.id, output_index: outputIndex, arguments: item.arguments || '{}' });
329
+ } else if (item.type === 'custom_tool_call') {
330
+ if (item.input) emit(state, res, 'response.custom_tool_call_input.delta', { item_id: item.id, output_index: outputIndex, delta: item.input });
331
+ emit(state, res, 'response.custom_tool_call_input.done', { item_id: item.id, output_index: outputIndex, input: item.input || '' });
332
+ }
333
+ emit(state, res, 'response.output_item.done', { output_index: outputIndex, item });
334
+ }
335
+
336
+ function createStreamState(context) {
337
+ return {
338
+ id: responseId(''),
339
+ upstreamId: '',
340
+ createdAt: nowSeconds(),
341
+ model: context.model || context.launch?.modelId || '',
342
+ context,
343
+ items: [],
344
+ tools: new Map(),
345
+ reasoning: null,
346
+ message: null,
347
+ usage: normalizeUsage(),
348
+ sequence: 0
349
+ ,finishReason: ''
350
+ };
351
+ }
352
+
353
+ function streamEnvelope(state, status, incompleteReason = null) {
354
+ const terminal = status === 'completed' || status === 'incomplete';
355
+ return buildResponseEnvelope({
356
+ id: state.id,
357
+ createdAt: state.createdAt,
358
+ model: state.model,
359
+ output: terminal ? state.items.filter(Boolean) : [],
360
+ status,
361
+ usage: terminal ? state.usage : null,
362
+ requestBody: state.context.requestBody,
363
+ incompleteReason
364
+ });
365
+ }
366
+
367
+ function buildResponseEnvelope({
368
+ id,
369
+ createdAt,
370
+ model,
371
+ output,
372
+ status,
373
+ usage,
374
+ requestBody = {},
375
+ incompleteReason = null
376
+ }) {
377
+ return {
378
+ id,
379
+ object: 'response',
380
+ created_at: createdAt,
381
+ status,
382
+ error: null,
383
+ incomplete_details: incompleteReason ? { reason: incompleteReason } : null,
384
+ instructions: null,
385
+ model,
386
+ output: Array.isArray(output) ? output : [],
387
+ parallel_tool_calls: requestBody.parallel_tool_calls !== false,
388
+ previous_response_id: requestBody.previous_response_id || null,
389
+ reasoning: requestBody.reasoning || null,
390
+ store: false,
391
+ temperature: requestBody.temperature ?? null,
392
+ tool_choice: requestBody.tool_choice || 'auto',
393
+ tools: Array.isArray(requestBody.tools) ? requestBody.tools : [],
394
+ top_p: requestBody.top_p ?? null,
395
+ usage
396
+ };
397
+ }
398
+
399
+ function buildReasoningItem(text) {
400
+ return { id: randomId('rs'), type: 'reasoning', status: 'completed', summary: [{ type: 'summary_text', text }] };
401
+ }
402
+
403
+ function buildMessageItem(text) {
404
+ return {
405
+ id: randomId('msg'),
406
+ type: 'message',
407
+ status: 'completed',
408
+ role: 'assistant',
409
+ content: [{ type: 'output_text', text, annotations: [] }]
410
+ };
411
+ }
412
+
413
+ function normalizeToolCalls(values, toolKinds) {
414
+ return (Array.isArray(values) ? values : []).map(value => {
415
+ const name = normalizeText(value?.function?.name) || 'tool';
416
+ const callId = normalizeText(value?.id) || randomId('call');
417
+ const argumentsText = normalizeText(value?.function?.arguments) || '{}';
418
+ const spec = toolKinds?.get(name);
419
+ const kind = (typeof spec === 'object' ? spec.kind : spec) === 'custom' ? 'custom' : 'function';
420
+ const originalName = typeof spec === 'object' ? spec.name || name : name;
421
+ const namespace = typeof spec === 'object' ? spec.namespace || '' : '';
422
+ const item = kind === 'custom'
423
+ ? { id: randomId('ct'), type: 'custom_tool_call', status: 'completed', call_id: callId, name: originalName, input: unwrapCustomArguments(argumentsText) }
424
+ : { id: randomId('fc'), type: 'function_call', status: 'completed', call_id: callId, name: originalName, arguments: argumentsText };
425
+ if (namespace) item.namespace = namespace;
426
+ return { callId, name: originalName, chatName: name, namespace, arguments: argumentsText, kind, item };
427
+ });
428
+ }
429
+
430
+ function buildAssistantMessage(split, toolCalls, launch) {
431
+ const replayRequired = toolCalls.length > 0 && requiresReasoningContentReplay(launch, launch?.modelId);
432
+ const assistant = { role: 'assistant', content: split.content || (replayRequired ? '' : null) };
433
+ if (toolCalls.length) {
434
+ assistant.tool_calls = toolCalls.map(call => ({
435
+ id: call.callId,
436
+ type: 'function',
437
+ function: { name: call.chatName || call.name, arguments: call.arguments || '{}' }
438
+ }));
439
+ }
440
+ const replayReasoning = split.reasoning || (replayRequired ? ' ' : '');
441
+ if (replayReasoning) assistant.reasoning_content = replayReasoning;
442
+ return assistant;
443
+ }
444
+
445
+ function splitReasoning(contentValue, reasoningValue) {
446
+ let content = normalizeStreamContent(contentValue);
447
+ let reasoning = normalizeText(reasoningValue);
448
+ if (!reasoning) {
449
+ const match = content.match(/^\s*<think>([\s\S]*?)<\/think>\s*/i);
450
+ if (match) {
451
+ reasoning = match[1];
452
+ content = content.slice(match[0].length);
453
+ }
454
+ }
455
+ return { content, reasoning };
456
+ }
457
+
458
+ function extractReasoning(message = {}) {
459
+ const direct = message.reasoning_content ?? message.reasoning ?? message.thinking;
460
+ if (typeof direct === 'string') return direct;
461
+ if (direct && typeof direct === 'object') {
462
+ const text = normalizeText(direct.text || direct.content || direct.summary);
463
+ if (text) return text;
464
+ }
465
+ return (Array.isArray(message.reasoning_details) ? message.reasoning_details : [])
466
+ .map(item => typeof item === 'string' ? item : normalizeText(item?.text || item?.content || item?.summary))
467
+ .filter(Boolean)
468
+ .join('\n');
469
+ }
470
+
471
+ function mapChatFinishReason(value) {
472
+ const reason = normalizeText(value).trim().toLowerCase();
473
+ if (['length', 'max_tokens', 'max_output_tokens', 'model_context_window_exceeded'].includes(reason)) {
474
+ return { status: 'incomplete', incompleteReason: 'max_output_tokens' };
475
+ }
476
+ if (['content_filter', 'content-filter', 'refusal', 'safety'].includes(reason)) {
477
+ return { status: 'incomplete', incompleteReason: 'content_filter' };
478
+ }
479
+ if (['insufficient_system_resource', 'insufficient-system-resource', 'resource_exhausted'].includes(reason)) {
480
+ // The Responses API has no portable provider-resource incomplete reason.
481
+ // Treat the partial generation like an output truncation so Codex can
482
+ // continue it through the bounded history-anchor path.
483
+ return { status: 'incomplete', incompleteReason: 'max_output_tokens' };
484
+ }
485
+ return { status: 'completed', incompleteReason: null };
486
+ }
487
+
488
+ function responseError(message) {
489
+ const error = new Error(message);
490
+ error.code = 'provider_response_invalid';
491
+ return error;
492
+ }
493
+
494
+ function assertNoLeakedToolControlTokens(value) {
495
+ const text = typeof value === 'string' ? value : JSON.stringify(value || {});
496
+ if (!/<\|tool_call_(?:begin|argument_begin|end)\|>/i.test(text)) return;
497
+ const error = new Error('The provider leaked unparsed tool-call control tokens while streaming. Use buffered upstream responses for this model.');
498
+ error.code = 'provider_stream_tool_parse_failed';
499
+ throw error;
500
+ }
501
+
502
+ function normalizeUsage(value = {}) {
503
+ const input = positiveInteger(value.prompt_tokens ?? value.input_tokens);
504
+ const output = positiveInteger(value.completion_tokens ?? value.output_tokens);
505
+ const cached = positiveInteger(value.prompt_tokens_details?.cached_tokens ?? value.input_tokens_details?.cached_tokens);
506
+ const reasoning = positiveInteger(value.completion_tokens_details?.reasoning_tokens ?? value.output_tokens_details?.reasoning_tokens);
507
+ return {
508
+ input_tokens: input,
509
+ input_tokens_details: { cached_tokens: cached },
510
+ output_tokens: output,
511
+ output_tokens_details: { reasoning_tokens: reasoning },
512
+ total_tokens: positiveInteger(value.total_tokens) || input + output
513
+ };
514
+ }
515
+
516
+ function normalizeStreamContent(value) {
517
+ if (typeof value === 'string') return value;
518
+ if (!Array.isArray(value)) return '';
519
+ return value.map(part => normalizeText(part?.text || part?.content)).join('');
520
+ }
521
+
522
+ function unwrapCustomArguments(value) {
523
+ const text = normalizeText(value);
524
+ try {
525
+ const parsed = JSON.parse(text);
526
+ return typeof parsed?.input === 'string' ? parsed.input : text;
527
+ } catch (_error) {
528
+ return text;
529
+ }
530
+ }
531
+
532
+ function reserveItem(state, item) {
533
+ const index = state.items.length;
534
+ state.items.push(item);
535
+ return index;
536
+ }
537
+
538
+ function emit(state, res, type, payload) {
539
+ const data = { type, sequence_number: state.sequence++, ...payload };
540
+ res.write(`event: ${type}\ndata: ${JSON.stringify(data)}\n\n`);
541
+ }
542
+
543
+ function beginSse(res) {
544
+ res.writeHead(200, {
545
+ 'content-type': 'text/event-stream; charset=utf-8',
546
+ 'cache-control': 'no-cache, no-transform',
547
+ connection: 'keep-alive'
548
+ });
549
+ }
550
+
551
+ function findSseBoundary(value) {
552
+ const match = /\r?\n\r?\n/.exec(value);
553
+ return match ? { index: match.index, length: match[0].length } : null;
554
+ }
555
+
556
+ function parseSseData(block) {
557
+ return String(block || '').split(/\r?\n/)
558
+ .filter(line => line.startsWith('data:'))
559
+ .map(line => line.slice(5).trimStart())
560
+ .join('\n')
561
+ .trim();
562
+ }
563
+
564
+ function parseJson(value) {
565
+ try { return JSON.parse(value); } catch (_error) { return null; }
566
+ }
567
+
568
+ function responseId(value) {
569
+ const suffix = normalizeText(value).replace(/[^A-Za-z0-9_-]/g, '').slice(-48) || crypto.randomUUID().replaceAll('-', '');
570
+ return `resp_${suffix}`;
571
+ }
572
+
573
+ function randomId(prefix) {
574
+ return `${prefix}_${crypto.randomUUID().replaceAll('-', '')}`;
575
+ }
576
+
577
+ function positiveInteger(value) {
578
+ const number = Number(value);
579
+ return Number.isFinite(number) && number > 0 ? Math.floor(number) : 0;
580
+ }
581
+
582
+ function nowSeconds() {
583
+ return Math.floor(Date.now() / 1000);
584
+ }
585
+
586
+ function normalizeText(value) {
587
+ return typeof value === 'string' ? value : '';
588
+ }
589
+
590
+ module.exports = {
591
+ convertChatResponse,
592
+ streamChatResponse,
593
+ writeConvertedResponseAsSse
594
+ };