codex-overleaf-link 2.0.0 → 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 (119) hide show
  1. package/README.md +24 -23
  2. package/extension/bootstrap/manifest.template.json +2 -1
  3. package/extension/runtime-manifest.json +12 -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 +129 -64
  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/writebackRouter.js +20 -33
  23. package/extension/src/pageBridge.js +1 -1
  24. package/extension/src/shared/agentTranscript.js +130 -0
  25. package/extension/src/shared/compatibility.js +3 -2
  26. package/extension/src/shared/failureReasons.js +75 -0
  27. package/extension/src/shared/i18n.js +22 -0
  28. package/extension/src/shared/models.js +1 -0
  29. package/extension/src/shared/providerProfiles.js +283 -0
  30. package/extension/src/shared/sessionState.js +12 -2
  31. package/extension/src/shared/storageDb.js +1 -1
  32. package/extension/styles/panel.css +587 -1
  33. package/extension/vendor/katex/LICENSE +21 -0
  34. package/extension/vendor/katex/VERSION +2 -0
  35. package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
  36. package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
  37. package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  38. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  39. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  40. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  41. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  42. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  43. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  44. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  45. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  46. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  47. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  48. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  49. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  50. package/extension/vendor/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
  51. package/extension/vendor/katex/fonts/KaTeX_Main-Bold.woff +0 -0
  52. package/extension/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  53. package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  54. package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  55. package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  56. package/extension/vendor/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
  57. package/extension/vendor/katex/fonts/KaTeX_Main-Italic.woff +0 -0
  58. package/extension/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  59. package/extension/vendor/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
  60. package/extension/vendor/katex/fonts/KaTeX_Main-Regular.woff +0 -0
  61. package/extension/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  62. package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  63. package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  64. package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  65. package/extension/vendor/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
  66. package/extension/vendor/katex/fonts/KaTeX_Math-Italic.woff +0 -0
  67. package/extension/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  68. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  69. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  70. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  71. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  72. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  73. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  74. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  75. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  76. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  77. package/extension/vendor/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
  78. package/extension/vendor/katex/fonts/KaTeX_Script-Regular.woff +0 -0
  79. package/extension/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  80. package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
  81. package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
  82. package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  83. package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
  84. package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
  85. package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  86. package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
  87. package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
  88. package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  89. package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
  90. package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
  91. package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  92. package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  93. package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  94. package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  95. package/extension/vendor/katex/katex.min.css +1 -0
  96. package/extension/vendor/katex/katex.min.js +1 -0
  97. package/native-host/src/anthropicBridgeRequest.js +435 -0
  98. package/native-host/src/anthropicBridgeResponse.js +431 -0
  99. package/native-host/src/anthropicMessagesBridge.js +281 -0
  100. package/native-host/src/chatBridgeRequest.js +274 -0
  101. package/native-host/src/chatBridgeResponse.js +594 -0
  102. package/native-host/src/chatCompletionsBridge.js +485 -0
  103. package/native-host/src/codexCommand.js +25 -0
  104. package/native-host/src/codexPromptAssembly.js +9 -0
  105. package/native-host/src/codexProviderLaunch.js +213 -0
  106. package/native-host/src/codexProviderTest.js +196 -0
  107. package/native-host/src/codexSessionRunner.js +29 -25
  108. package/native-host/src/managedInstall.js +1 -0
  109. package/native-host/src/providerBridgeRoutes.js +11 -0
  110. package/native-host/src/providerModelCatalog.js +107 -0
  111. package/native-host/src/providerProfile.js +454 -0
  112. package/native-host/src/providerReasoning.js +235 -0
  113. package/native-host/src/providerRedaction.js +18 -0
  114. package/native-host/src/providerRuntime.js +365 -0
  115. package/native-host/src/providerStore.js +691 -0
  116. package/native-host/src/readProgressGuard.js +360 -0
  117. package/native-host/src/taskRunnerRuntime.js +39 -3
  118. package/native-host/src/updateArchive.js +1 -0
  119. package/package.json +2 -1
@@ -0,0 +1,435 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+
5
+ const THINKING_PREFIX = 'codex-overleaf-anthropic-thinking-v1:';
6
+ const DEFAULT_MAX_TOKENS = 32768;
7
+
8
+ function buildAnthropicRequest({ requestBody = {}, launch = {}, historyMessages = [] } = {}) {
9
+ const toolContext = buildToolContext(requestBody.tools);
10
+ const messages = clone(historyMessages);
11
+ const systemParts = [];
12
+ const instructions = text(requestBody.instructions);
13
+ if (launch.impersonateClaudeCode) {
14
+ systemParts.push("You are Claude Code, Anthropic's official command-line coding agent.");
15
+ }
16
+ if (meaningful(instructions)) systemParts.push(instructions.trim());
17
+ appendResponsesInput(messages, systemParts, requestBody.input, toolContext);
18
+ normalizeMessages(messages);
19
+ if (!messages.length) {
20
+ const error = new Error('Cannot convert an empty Codex request to Anthropic Messages.');
21
+ error.code = 'provider_request_invalid';
22
+ throw error;
23
+ }
24
+
25
+ const model = text(requestBody.model) || launch.modelId;
26
+ const maxTokens = clampInteger(
27
+ requestBody.max_output_tokens || launch.maxOutputTokens,
28
+ 256,
29
+ 200000,
30
+ DEFAULT_MAX_TOKENS
31
+ );
32
+ const body = {
33
+ ...(launch.bodyOverrides || {}),
34
+ model,
35
+ max_tokens: maxTokens,
36
+ messages,
37
+ stream: requestBody.stream !== false
38
+ };
39
+ if (systemParts.length) {
40
+ body.system = launch.anthropicPromptCaching
41
+ ? systemParts.map((value, index) => ({
42
+ type: 'text',
43
+ text: value,
44
+ ...(index === systemParts.length - 1 ? { cache_control: { type: 'ephemeral' } } : {})
45
+ }))
46
+ : systemParts.join('\n\n');
47
+ }
48
+ if (toolContext.tools.length && requestBody.tool_choice !== 'none') body.tools = toolContext.tools;
49
+ const toolChoice = convertToolChoice(requestBody.tool_choice, toolContext, launch);
50
+ if (toolChoice) body.tool_choice = toolChoice;
51
+
52
+ const thinkingEnabled = applyAnthropicThinking(body, requestBody, launch, maxTokens);
53
+ if (!thinkingEnabled) {
54
+ copyNumber(requestBody, body, 'temperature');
55
+ copyNumber(requestBody, body, 'top_p');
56
+ }
57
+ if (launch.anthropicPromptCaching) markLatestUserCachePoint(messages);
58
+ return { body, messages, toolContext };
59
+ }
60
+
61
+ function appendResponsesInput(messages, systemParts, input, toolContext) {
62
+ if (typeof input === 'string') {
63
+ if (meaningful(input)) pushBlock(messages, 'user', { type: 'text', text: input });
64
+ return;
65
+ }
66
+ if (!Array.isArray(input)) return;
67
+ for (const item of input) {
68
+ if (!item || typeof item !== 'object') continue;
69
+ if (item.type === 'reasoning') {
70
+ const block = decodeAnthropicThinkingBlock(item.encrypted_content);
71
+ if (block) pushBlock(messages, 'assistant', block);
72
+ continue;
73
+ }
74
+ if (item.type === 'function_call' || item.type === 'custom_tool_call' || item.type === 'tool_search_call') {
75
+ const spec = findToolSpec(toolContext, item);
76
+ const callId = text(item.call_id || item.id) || randomId('toolu');
77
+ const inputValue = item.type === 'custom_tool_call'
78
+ ? { input: text(item.input) }
79
+ : parseObject(item.arguments);
80
+ pushBlock(messages, 'assistant', {
81
+ type: 'tool_use',
82
+ id: callId,
83
+ name: spec?.upstreamName || text(item.name) || 'tool',
84
+ input: inputValue
85
+ });
86
+ continue;
87
+ }
88
+ if (['function_call_output', 'custom_tool_call_output', 'computer_call_output', 'tool_search_output'].includes(item.type)) {
89
+ pushBlock(messages, 'user', {
90
+ type: 'tool_result',
91
+ tool_use_id: text(item.call_id || item.id),
92
+ content: convertToolResultContent(item.output),
93
+ ...(item.status === 'failed' ? { is_error: true } : {})
94
+ });
95
+ continue;
96
+ }
97
+ if (item.type === 'message' || item.role) {
98
+ const role = normalizeRole(item.role);
99
+ const blocks = convertContent(item.content);
100
+ if (role === 'system') {
101
+ for (const block of blocks) {
102
+ if (block.type === 'text' && meaningful(block.text)) systemParts.push(block.text.trim());
103
+ }
104
+ } else {
105
+ for (const block of blocks) pushBlock(messages, role, block);
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ function buildToolContext(values) {
112
+ const tools = [];
113
+ const byUpstreamName = new Map();
114
+ for (const value of Array.isArray(values) ? values : []) {
115
+ if (value?.type === 'namespace' && Array.isArray(value.tools)) {
116
+ for (const child of value.tools) addTool(tools, byUpstreamName, child, text(value.name));
117
+ } else {
118
+ addTool(tools, byUpstreamName, value, '');
119
+ }
120
+ }
121
+ return { tools, byUpstreamName };
122
+ }
123
+
124
+ function addTool(tools, byUpstreamName, value, namespace) {
125
+ const originalName = text(value?.name || value?.function?.name)
126
+ || (value?.type === 'tool_search' ? 'tool_search' : '');
127
+ if (!originalName) return;
128
+ const kind = value.type === 'custom'
129
+ ? 'custom'
130
+ : value.type === 'tool_search'
131
+ ? 'tool_search'
132
+ : 'function';
133
+ const upstreamName = uniqueToolName(namespace, originalName, byUpstreamName);
134
+ const inputSchema = kind === 'custom'
135
+ ? {
136
+ type: 'object',
137
+ properties: { input: { type: 'string' } },
138
+ required: ['input'],
139
+ additionalProperties: false
140
+ }
141
+ : kind === 'tool_search'
142
+ ? {
143
+ type: 'object',
144
+ properties: { query: { type: 'string' } },
145
+ required: ['query'],
146
+ additionalProperties: false
147
+ }
148
+ : normalizeSchema(value.parameters || value.function?.parameters);
149
+ const spec = { kind, name: originalName, namespace, upstreamName };
150
+ byUpstreamName.set(upstreamName, spec);
151
+ tools.push({
152
+ name: upstreamName,
153
+ description: text(value.description || value.function?.description) || `Codex tool ${originalName}`,
154
+ input_schema: inputSchema
155
+ });
156
+ }
157
+
158
+ function findToolSpec(context, item) {
159
+ const name = text(item?.name || item?.function?.name);
160
+ const namespace = text(item?.namespace);
161
+ for (const spec of context.byUpstreamName.values()) {
162
+ if (spec.name === name && (!namespace || namespace === spec.namespace)) return spec;
163
+ }
164
+ return null;
165
+ }
166
+
167
+ function convertToolChoice(value, context, launch) {
168
+ const disableParallel = !launch.supportsParallelToolCalls;
169
+ if (!value || value === 'auto') return { type: 'auto', disable_parallel_tool_use: disableParallel };
170
+ if (value === 'required') return { type: 'any', disable_parallel_tool_use: disableParallel };
171
+ if (value === 'none') return null;
172
+ const spec = findToolSpec(context, value);
173
+ const name = spec?.upstreamName || text(value?.name || value?.function?.name);
174
+ return name ? { type: 'tool', name, disable_parallel_tool_use: disableParallel } : null;
175
+ }
176
+
177
+ function applyAnthropicThinking(body, requestBody, launch, maxTokens) {
178
+ const effort = text(requestBody?.reasoning?.effort || launch.reasoningEffort).toLowerCase();
179
+ if (!effort || ['none', 'off', 'disabled'].includes(effort) || launch.anthropicThinkingMode === 'none') {
180
+ return false;
181
+ }
182
+ const mode = launch.anthropicThinkingMode === 'adaptive' ? 'adaptive' : 'budget';
183
+ if (mode === 'adaptive') {
184
+ body.thinking = { type: 'adaptive' };
185
+ body.output_config = { effort: effort === 'xhigh' ? 'max' : effort === 'minimal' ? 'low' : effort };
186
+ return true;
187
+ }
188
+ const requested = {
189
+ minimal: 2048,
190
+ low: 2048,
191
+ medium: 8192,
192
+ high: 16384,
193
+ xhigh: 24576,
194
+ max: 24576
195
+ }[effort];
196
+ if (!requested) return false;
197
+ const budget = Math.min(requested, Math.floor(maxTokens / 2), maxTokens - 1);
198
+ if (budget < 1024) return false;
199
+ body.thinking = { type: 'enabled', budget_tokens: budget };
200
+ return true;
201
+ }
202
+
203
+ function rectifyAnthropicRequestForRetry(body, errorText) {
204
+ const message = String(errorText || '').toLowerCase();
205
+ if (/signature|redacted_thinking|expected.+thinking|thinking.+(invalid|illegal)/i.test(message)) {
206
+ let changed = false;
207
+ for (const item of body.messages || []) {
208
+ if (!Array.isArray(item.content)) continue;
209
+ const filtered = item.content.filter(block => !['thinking', 'redacted_thinking'].includes(block?.type));
210
+ if (filtered.length !== item.content.length) changed = true;
211
+ item.content = filtered;
212
+ }
213
+ if (body.thinking) changed = true;
214
+ delete body.thinking;
215
+ delete body.output_config;
216
+ normalizeMessages(body.messages || []);
217
+ return changed;
218
+ }
219
+ if (/budget_tokens|thinking budget|must be less than max_tokens|at least 1024/i.test(message)) {
220
+ body.thinking = { type: 'enabled', budget_tokens: 32000 };
221
+ body.max_tokens = Math.max(Number(body.max_tokens) || 0, 64000);
222
+ delete body.output_config;
223
+ return true;
224
+ }
225
+ return false;
226
+ }
227
+
228
+ function normalizeMessages(messages) {
229
+ for (const message of messages) {
230
+ message.content = (Array.isArray(message.content) ? message.content : [])
231
+ .filter(block => block && (
232
+ block.type !== 'text' || meaningful(block.text)
233
+ ));
234
+ }
235
+ const toolUseIds = new Set();
236
+ for (const message of messages) {
237
+ for (const block of message.content || []) {
238
+ if (block.type === 'tool_use' && block.id) toolUseIds.add(block.id);
239
+ }
240
+ }
241
+ for (const message of messages) {
242
+ message.content = (message.content || []).filter(block => (
243
+ block.type !== 'tool_result' || toolUseIds.has(block.tool_use_id)
244
+ ));
245
+ }
246
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
247
+ if (!messages[index].content.length) messages.splice(index, 1);
248
+ }
249
+ mergeAdjacentMessages(messages);
250
+ if (messages[0]?.role === 'assistant') {
251
+ messages.unshift({ role: 'user', content: [{ type: 'text', text: 'Continue the task.' }] });
252
+ }
253
+ }
254
+
255
+ function mergeAdjacentMessages(messages) {
256
+ for (let index = 1; index < messages.length;) {
257
+ if (messages[index - 1].role === messages[index].role) {
258
+ messages[index - 1].content.push(...messages[index].content);
259
+ messages.splice(index, 1);
260
+ } else {
261
+ index += 1;
262
+ }
263
+ }
264
+ }
265
+
266
+ function pushBlock(messages, role, block) {
267
+ if (!block) return;
268
+ let message = messages[messages.length - 1];
269
+ if (!message || message.role !== role) {
270
+ message = { role, content: [] };
271
+ messages.push(message);
272
+ }
273
+ if (role === 'user' && block.type === 'tool_result') {
274
+ const firstNonResult = message.content.findIndex(item => item.type !== 'tool_result');
275
+ if (firstNonResult >= 0) message.content.splice(firstNonResult, 0, block);
276
+ else message.content.push(block);
277
+ } else {
278
+ message.content.push(block);
279
+ }
280
+ }
281
+
282
+ function convertContent(value) {
283
+ if (typeof value === 'string') return meaningful(value) ? [{ type: 'text', text: value }] : [];
284
+ if (!Array.isArray(value)) return [];
285
+ const result = [];
286
+ for (const part of value) {
287
+ if (!part || typeof part !== 'object') continue;
288
+ if (['input_text', 'output_text', 'text'].includes(part.type)) {
289
+ if (meaningful(part.text)) result.push({ type: 'text', text: part.text });
290
+ } else if (part.type === 'input_image' || part.type === 'image_url') {
291
+ const image = convertImage(part.image_url?.url || part.image_url || part.url);
292
+ if (image) result.push(image);
293
+ } else if (part.type === 'input_file' || part.type === 'document') {
294
+ const document = convertDocument(part);
295
+ if (document) result.push(document);
296
+ }
297
+ }
298
+ return result;
299
+ }
300
+
301
+ function convertToolResultContent(value) {
302
+ if (typeof value === 'string') return value;
303
+ if (Array.isArray(value)) {
304
+ const blocks = convertContent(value);
305
+ return blocks.length ? blocks : json(value);
306
+ }
307
+ return json(value);
308
+ }
309
+
310
+ function convertImage(value) {
311
+ const url = text(value);
312
+ const dataMatch = /^data:([^;,]+);base64,([A-Za-z0-9+/=\s]+)$/.exec(url);
313
+ if (dataMatch) {
314
+ return { type: 'image', source: { type: 'base64', media_type: dataMatch[1], data: dataMatch[2].replace(/\s/g, '') } };
315
+ }
316
+ if (/^https?:\/\//i.test(url)) return { type: 'image', source: { type: 'url', url } };
317
+ return null;
318
+ }
319
+
320
+ function convertDocument(part) {
321
+ const value = text(part.file_data || part.file_url || part.url || part.source?.url);
322
+ const dataMatch = /^data:([^;,]+);base64,([A-Za-z0-9+/=\s]+)$/.exec(value);
323
+ let source;
324
+ if (dataMatch) {
325
+ source = { type: 'base64', media_type: dataMatch[1], data: dataMatch[2].replace(/\s/g, '') };
326
+ } else if (/^https?:\/\//i.test(value)) {
327
+ source = { type: 'url', url: value };
328
+ } else if (part.source?.type === 'base64' && part.source.data) {
329
+ source = {
330
+ type: 'base64',
331
+ media_type: part.source.media_type || part.media_type || 'application/pdf',
332
+ data: part.source.data
333
+ };
334
+ }
335
+ if (!source) return null;
336
+ return {
337
+ type: 'document',
338
+ source,
339
+ ...(part.filename || part.title ? { title: part.filename || part.title } : {})
340
+ };
341
+ }
342
+
343
+ function markLatestUserCachePoint(messages) {
344
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
345
+ if (messages[index].role !== 'user' || !messages[index].content.length) continue;
346
+ messages[index].content[messages[index].content.length - 1].cache_control = { type: 'ephemeral' };
347
+ return;
348
+ }
349
+ }
350
+
351
+ function encodeAnthropicThinkingBlock(block) {
352
+ if (!block || !['thinking', 'redacted_thinking'].includes(block.type)) return '';
353
+ return `${THINKING_PREFIX}${Buffer.from(JSON.stringify(block)).toString('base64url')}`;
354
+ }
355
+
356
+ function decodeAnthropicThinkingBlock(value) {
357
+ const encoded = text(value);
358
+ if (!encoded.startsWith(THINKING_PREFIX)) return null;
359
+ try {
360
+ const block = JSON.parse(Buffer.from(encoded.slice(THINKING_PREFIX.length), 'base64url').toString('utf8'));
361
+ return ['thinking', 'redacted_thinking'].includes(block?.type) ? block : null;
362
+ } catch (_error) {
363
+ return null;
364
+ }
365
+ }
366
+
367
+ function uniqueToolName(namespace, name, existing) {
368
+ const raw = [namespace, name].filter(Boolean).join('__').replace(/[^A-Za-z0-9_-]/g, '_');
369
+ const base = raw.length <= 64
370
+ ? raw
371
+ : `${raw.slice(0, 51)}_${crypto.createHash('sha256').update(raw).digest('hex').slice(0, 12)}`;
372
+ if (!existing.has(base)) return base;
373
+ return `${base.slice(0, 55)}_${crypto.createHash('sha256').update(`${namespace}\0${name}`).digest('hex').slice(0, 8)}`;
374
+ }
375
+
376
+ function normalizeSchema(value) {
377
+ return value && typeof value === 'object'
378
+ ? value
379
+ : { type: 'object', properties: {}, additionalProperties: true };
380
+ }
381
+
382
+ function normalizeRole(value) {
383
+ const role = text(value).toLowerCase();
384
+ if (role === 'assistant') return 'assistant';
385
+ if (role === 'system' || role === 'developer') return 'system';
386
+ return 'user';
387
+ }
388
+
389
+ function parseObject(value) {
390
+ if (value && typeof value === 'object' && !Array.isArray(value)) return value;
391
+ try {
392
+ const parsed = JSON.parse(text(value) || '{}');
393
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
394
+ } catch (_error) {
395
+ return {};
396
+ }
397
+ }
398
+
399
+ function clone(value) {
400
+ try { return JSON.parse(JSON.stringify(value)); } catch (_error) { return []; }
401
+ }
402
+
403
+ function json(value) {
404
+ if (value === undefined || value === null) return '';
405
+ try { return JSON.stringify(value); } catch (_error) { return String(value); }
406
+ }
407
+
408
+ function copyNumber(source, target, key) {
409
+ const value = Number(source?.[key]);
410
+ if (Number.isFinite(value)) target[key] = value;
411
+ }
412
+
413
+ function clampInteger(value, min, max, fallback) {
414
+ const number = Number(value);
415
+ return Number.isFinite(number) ? Math.min(max, Math.max(min, Math.floor(number))) : fallback;
416
+ }
417
+
418
+ function meaningful(value) {
419
+ return typeof value === 'string' && value.trim().length > 0;
420
+ }
421
+
422
+ function text(value) {
423
+ return typeof value === 'string' ? value : '';
424
+ }
425
+
426
+ function randomId(prefix) {
427
+ return `${prefix}_${crypto.randomUUID().replaceAll('-', '')}`;
428
+ }
429
+
430
+ module.exports = {
431
+ buildAnthropicRequest,
432
+ decodeAnthropicThinkingBlock,
433
+ encodeAnthropicThinkingBlock,
434
+ rectifyAnthropicRequestForRetry
435
+ };