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.
- package/README.md +24 -23
- package/extension/bootstrap/manifest.template.json +2 -1
- package/extension/runtime-manifest.json +12 -6
- package/extension/src/background.js +7 -0
- package/extension/src/content/composerPanel.js +13 -3
- package/extension/src/content/contentRuntime.js +129 -64
- package/extension/src/content/contextTray.js +173 -9
- package/extension/src/content/markdownText.js +16 -1
- package/extension/src/content/mathText.js +233 -0
- package/extension/src/content/modelPicker.js +117 -106
- package/extension/src/content/modelPickerSupport.js +115 -0
- package/extension/src/content/nativeChannel.js +20 -1
- package/extension/src/content/panelMaintenance.js +2 -0
- package/extension/src/content/projectSettingsCoordinator.js +2 -0
- package/extension/src/content/providerSettingsCoordinator.js +413 -0
- package/extension/src/content/providerSettingsDialog.js +981 -0
- package/extension/src/content/runTimelineView.js +24 -5
- package/extension/src/content/settingsPanel.js +33 -2
- package/extension/src/content/writebackOrchestrator.js +3 -1
- package/extension/src/page/overleafProjectSnapshot.js +29 -33
- package/extension/src/page/snapshotRouter.js +75 -8
- package/extension/src/page/writebackRouter.js +20 -33
- package/extension/src/pageBridge.js +1 -1
- package/extension/src/shared/agentTranscript.js +130 -0
- package/extension/src/shared/compatibility.js +3 -2
- package/extension/src/shared/failureReasons.js +75 -0
- package/extension/src/shared/i18n.js +22 -0
- package/extension/src/shared/models.js +1 -0
- package/extension/src/shared/providerProfiles.js +283 -0
- package/extension/src/shared/sessionState.js +12 -2
- package/extension/src/shared/storageDb.js +1 -1
- package/extension/styles/panel.css +587 -1
- package/extension/vendor/katex/LICENSE +21 -0
- package/extension/vendor/katex/VERSION +2 -0
- package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Bold.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Italic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-Italic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Script-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/extension/vendor/katex/katex.min.css +1 -0
- package/extension/vendor/katex/katex.min.js +1 -0
- package/native-host/src/anthropicBridgeRequest.js +435 -0
- package/native-host/src/anthropicBridgeResponse.js +431 -0
- package/native-host/src/anthropicMessagesBridge.js +281 -0
- package/native-host/src/chatBridgeRequest.js +274 -0
- package/native-host/src/chatBridgeResponse.js +594 -0
- package/native-host/src/chatCompletionsBridge.js +485 -0
- package/native-host/src/codexCommand.js +25 -0
- package/native-host/src/codexPromptAssembly.js +9 -0
- package/native-host/src/codexProviderLaunch.js +213 -0
- package/native-host/src/codexProviderTest.js +196 -0
- package/native-host/src/codexSessionRunner.js +29 -25
- package/native-host/src/managedInstall.js +1 -0
- package/native-host/src/providerBridgeRoutes.js +11 -0
- package/native-host/src/providerModelCatalog.js +107 -0
- package/native-host/src/providerProfile.js +454 -0
- package/native-host/src/providerReasoning.js +235 -0
- package/native-host/src/providerRedaction.js +18 -0
- package/native-host/src/providerRuntime.js +365 -0
- package/native-host/src/providerStore.js +691 -0
- package/native-host/src/readProgressGuard.js +360 -0
- package/native-host/src/taskRunnerRuntime.js +39 -3
- package/native-host/src/updateArchive.js +1 -0
- 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
|
+
};
|