@jsonstudio/llms 0.6.938 → 0.6.1164
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/dist/conversion/hub/operation-table/operation-table-runner.d.ts +18 -0
- package/dist/conversion/hub/operation-table/operation-table-runner.js +158 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.d.ts +8 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +303 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.d.ts +8 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +413 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.d.ts +7 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +841 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.d.ts +21 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +535 -0
- package/dist/conversion/hub/ops/operations.d.ts +19 -0
- package/dist/conversion/hub/ops/operations.js +126 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +9 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +533 -24
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +6 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +6 -3
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +11 -0
- package/dist/conversion/hub/policy/policy-engine.js +41 -9
- package/dist/conversion/hub/policy/protocol-spec.d.ts +25 -0
- package/dist/conversion/hub/policy/protocol-spec.js +73 -23
- package/dist/conversion/hub/process/chat-process.js +252 -41
- package/dist/conversion/hub/response/provider-response.js +175 -2
- package/dist/conversion/hub/response/response-runtime.js +1 -1
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +1 -8
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +1 -365
- package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +1 -8
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +1 -436
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +1 -7
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +1 -894
- package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +1 -21
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +1 -593
- package/dist/conversion/hub/tool-surface/tool-surface-engine.d.ts +18 -0
- package/dist/conversion/hub/tool-surface/tool-surface-engine.js +571 -0
- package/dist/conversion/responses/responses-openai-bridge.js +14 -2
- package/dist/conversion/shared/bridge-message-utils.js +2 -8
- package/dist/conversion/shared/bridge-policies.js +5 -105
- package/dist/conversion/shared/gemini-tool-utils.js +121 -4
- package/dist/conversion/shared/protocol-field-allowlists.d.ts +7 -0
- package/dist/conversion/shared/protocol-field-allowlists.js +145 -0
- package/dist/conversion/shared/reasoning-tool-normalizer.js +4 -2
- package/dist/conversion/shared/snapshot-hooks.js +166 -3
- package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
- package/dist/conversion/shared/text-markup-normalizer.js +345 -9
- package/dist/conversion/shared/thought-signature-validator.d.ts +52 -0
- package/dist/conversion/shared/thought-signature-validator.js +170 -0
- package/dist/conversion/shared/tool-argument-repairer.d.ts +39 -0
- package/dist/conversion/shared/tool-argument-repairer.js +56 -0
- package/dist/conversion/shared/tool-call-id-manager.d.ts +113 -0
- package/dist/conversion/shared/tool-call-id-manager.js +231 -0
- package/dist/conversion/shared/tool-canonicalizer.js +2 -11
- package/dist/router/virtual-router/bootstrap.js +54 -5
- package/dist/router/virtual-router/engine-selection.js +132 -42
- package/dist/router/virtual-router/engine.d.ts +3 -0
- package/dist/router/virtual-router/engine.js +142 -33
- package/dist/router/virtual-router/health-weighted.d.ts +25 -0
- package/dist/router/virtual-router/health-weighted.js +63 -0
- package/dist/router/virtual-router/load-balancer.d.ts +2 -0
- package/dist/router/virtual-router/load-balancer.js +45 -16
- package/dist/router/virtual-router/routing-instructions.js +17 -1
- package/dist/router/virtual-router/sticky-session-store.js +136 -24
- package/dist/router/virtual-router/stop-message-file-resolver.d.ts +1 -0
- package/dist/router/virtual-router/stop-message-file-resolver.js +74 -0
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +15 -0
- package/dist/router/virtual-router/stop-message-state-sync.js +57 -0
- package/dist/router/virtual-router/types.d.ts +70 -0
- package/dist/servertool/clock/config.d.ts +7 -0
- package/dist/servertool/clock/config.js +27 -0
- package/dist/servertool/clock/daemon.d.ts +3 -0
- package/dist/servertool/clock/daemon.js +79 -0
- package/dist/servertool/clock/io.d.ts +2 -0
- package/dist/servertool/clock/io.js +13 -0
- package/dist/servertool/clock/paths.d.ts +4 -0
- package/dist/servertool/clock/paths.js +25 -0
- package/dist/servertool/clock/session-store.d.ts +3 -0
- package/dist/servertool/clock/session-store.js +56 -0
- package/dist/servertool/clock/state.d.ts +5 -0
- package/dist/servertool/clock/state.js +62 -0
- package/dist/servertool/clock/task-store.d.ts +5 -0
- package/dist/servertool/clock/task-store.js +4 -0
- package/dist/servertool/clock/tasks.d.ts +17 -0
- package/dist/servertool/clock/tasks.js +221 -0
- package/dist/servertool/clock/types.d.ts +36 -0
- package/dist/servertool/clock/types.js +1 -0
- package/dist/servertool/engine.d.ts +2 -0
- package/dist/servertool/engine.js +164 -8
- package/dist/servertool/followup-shadow.d.ts +16 -0
- package/dist/servertool/followup-shadow.js +145 -0
- package/dist/servertool/handlers/apply-patch-guard.js +1 -265
- package/dist/servertool/handlers/clock-auto.d.ts +1 -0
- package/dist/servertool/handlers/clock-auto.js +160 -0
- package/dist/servertool/handlers/clock.d.ts +1 -0
- package/dist/servertool/handlers/clock.js +197 -0
- package/dist/servertool/handlers/exec-command-guard.js +7 -555
- package/dist/servertool/handlers/followup-request-builder.d.ts +15 -7
- package/dist/servertool/handlers/followup-request-builder.js +248 -28
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +62 -169
- package/dist/servertool/handlers/iflow-model-error-retry.js +18 -28
- package/dist/servertool/handlers/recursive-detection-guard.d.ts +1 -0
- package/dist/servertool/handlers/recursive-detection-guard.js +333 -0
- package/dist/servertool/handlers/stop-message-auto.js +47 -175
- package/dist/servertool/handlers/vision.d.ts +7 -1
- package/dist/servertool/handlers/vision.js +61 -117
- package/dist/servertool/handlers/web-search.d.ts +7 -1
- package/dist/servertool/handlers/web-search.js +122 -105
- package/dist/servertool/reenter-backend.d.ts +23 -0
- package/dist/servertool/reenter-backend.js +18 -0
- package/dist/servertool/server-side-tools.d.ts +3 -2
- package/dist/servertool/server-side-tools.js +64 -10
- package/dist/servertool/types.d.ts +92 -3
- package/dist/sse/json-to-sse/event-generators/responses.js +3 -21
- package/dist/sse/shared/serializers/responses-event-serializer.d.ts +8 -0
- package/dist/sse/shared/serializers/responses-event-serializer.js +19 -0
- package/dist/sse/shared/writer.js +24 -7
- package/dist/tools/apply-patch/execution-capturer.js +3 -1
- package/dist/tools/apply-patch/json/parse-loose.d.ts +3 -0
- package/dist/tools/apply-patch/json/parse-loose.js +139 -0
- package/dist/tools/apply-patch/patch-text/context-diff.d.ts +1 -0
- package/dist/tools/apply-patch/patch-text/context-diff.js +173 -0
- package/dist/tools/apply-patch/patch-text/git-diff.d.ts +1 -0
- package/dist/tools/apply-patch/patch-text/git-diff.js +138 -0
- package/dist/tools/apply-patch/patch-text/looks-like-patch.d.ts +1 -0
- package/dist/tools/apply-patch/patch-text/looks-like-patch.js +13 -0
- package/dist/tools/apply-patch/patch-text/normalize.d.ts +3 -0
- package/dist/tools/apply-patch/patch-text/normalize.js +262 -0
- package/dist/tools/apply-patch/structured/coercion.d.ts +3 -0
- package/dist/tools/apply-patch/structured/coercion.js +82 -0
- package/dist/tools/apply-patch/validation/shared.d.ts +3 -0
- package/dist/tools/apply-patch/validation/shared.js +6 -0
- package/dist/tools/apply-patch/validator.d.ts +2 -2
- package/dist/tools/apply-patch/validator.js +6 -556
- package/package.json +1 -1
|
@@ -1,105 +1,5 @@
|
|
|
1
1
|
import { RESPONSES_INSTRUCTIONS_REASONING_FIELD } from './reasoning-normalizer.js';
|
|
2
|
-
|
|
3
|
-
'messages',
|
|
4
|
-
'tools',
|
|
5
|
-
'tool_outputs',
|
|
6
|
-
'model',
|
|
7
|
-
'temperature',
|
|
8
|
-
'top_p',
|
|
9
|
-
'top_k',
|
|
10
|
-
'max_tokens',
|
|
11
|
-
'frequency_penalty',
|
|
12
|
-
'presence_penalty',
|
|
13
|
-
'logit_bias',
|
|
14
|
-
'response_format',
|
|
15
|
-
'parallel_tool_calls',
|
|
16
|
-
'tool_choice',
|
|
17
|
-
'seed',
|
|
18
|
-
'user',
|
|
19
|
-
'metadata',
|
|
20
|
-
'stop',
|
|
21
|
-
'stop_sequences',
|
|
22
|
-
'stream'
|
|
23
|
-
];
|
|
24
|
-
const ANTHROPIC_ALLOWED_FIELDS = [
|
|
25
|
-
'model',
|
|
26
|
-
'messages',
|
|
27
|
-
'tools',
|
|
28
|
-
'system',
|
|
29
|
-
'stop_sequences',
|
|
30
|
-
'temperature',
|
|
31
|
-
'top_p',
|
|
32
|
-
'top_k',
|
|
33
|
-
'max_tokens',
|
|
34
|
-
'max_output_tokens',
|
|
35
|
-
'metadata',
|
|
36
|
-
'stream',
|
|
37
|
-
'tool_choice'
|
|
38
|
-
];
|
|
39
|
-
const RESPONSES_ALLOWED_FIELDS = [
|
|
40
|
-
'id',
|
|
41
|
-
'object',
|
|
42
|
-
'created_at',
|
|
43
|
-
'model',
|
|
44
|
-
'status',
|
|
45
|
-
'input',
|
|
46
|
-
'instructions',
|
|
47
|
-
'output',
|
|
48
|
-
'output_text',
|
|
49
|
-
'required_action',
|
|
50
|
-
'response_id',
|
|
51
|
-
'previous_response_id',
|
|
52
|
-
'tool_outputs',
|
|
53
|
-
'tools',
|
|
54
|
-
'metadata',
|
|
55
|
-
'include',
|
|
56
|
-
'store',
|
|
57
|
-
'user',
|
|
58
|
-
'response_format',
|
|
59
|
-
'temperature',
|
|
60
|
-
'top_p',
|
|
61
|
-
'top_k',
|
|
62
|
-
'max_tokens',
|
|
63
|
-
'max_output_tokens',
|
|
64
|
-
'logit_bias',
|
|
65
|
-
'seed',
|
|
66
|
-
'parallel_tool_calls',
|
|
67
|
-
'tool_choice',
|
|
68
|
-
'stream',
|
|
69
|
-
'instructions_is_raw'
|
|
70
|
-
];
|
|
71
|
-
const GEMINI_ALLOWED_FIELDS = [
|
|
72
|
-
'model',
|
|
73
|
-
'contents',
|
|
74
|
-
'systemInstruction',
|
|
75
|
-
'system_instruction',
|
|
76
|
-
'generationConfig',
|
|
77
|
-
'generation_config',
|
|
78
|
-
'safetySettings',
|
|
79
|
-
'safety_settings',
|
|
80
|
-
'metadata',
|
|
81
|
-
'toolConfig',
|
|
82
|
-
'tool_config',
|
|
83
|
-
'tools',
|
|
84
|
-
'tool_choice',
|
|
85
|
-
'parallelToolCalls',
|
|
86
|
-
'parallel_tool_calls',
|
|
87
|
-
'responseMimeType',
|
|
88
|
-
'response_mime_type',
|
|
89
|
-
'stopSequences',
|
|
90
|
-
'stop_sequences',
|
|
91
|
-
'cachedContent',
|
|
92
|
-
'prompt',
|
|
93
|
-
'response',
|
|
94
|
-
'candidates',
|
|
95
|
-
'usageMetadata',
|
|
96
|
-
'responseMetadata',
|
|
97
|
-
'promptFeedback',
|
|
98
|
-
'modelVersion',
|
|
99
|
-
'client',
|
|
100
|
-
'user',
|
|
101
|
-
'stream'
|
|
102
|
-
];
|
|
2
|
+
import { ANTHROPIC_ALLOWED_FIELDS, GEMINI_ALLOWED_FIELDS, OPENAI_CHAT_ALLOWED_FIELDS, OPENAI_RESPONSES_ALLOWED_FIELDS } from './protocol-field-allowlists.js';
|
|
103
3
|
function reasoningAction(idPrefix) {
|
|
104
4
|
return {
|
|
105
5
|
name: 'reasoning.extract',
|
|
@@ -134,7 +34,7 @@ const RESPONSES_POLICY = {
|
|
|
134
34
|
reasoningAction('responses_reasoning'),
|
|
135
35
|
toolCallNormalizationAction('responses_tool_call'),
|
|
136
36
|
{ name: 'tools.ensure-placeholders' },
|
|
137
|
-
{ name: 'metadata.extra-fields', options: { allowedKeys:
|
|
37
|
+
{ name: 'metadata.extra-fields', options: { allowedKeys: OPENAI_RESPONSES_ALLOWED_FIELDS } },
|
|
138
38
|
{ name: 'metadata.provider-field', options: { field: 'metadata', target: 'providerMetadata' } },
|
|
139
39
|
{ name: 'metadata.provider-sentinel', options: { sentinel: '__rcc_provider_metadata', target: 'providerMetadata' } }
|
|
140
40
|
],
|
|
@@ -146,7 +46,7 @@ const RESPONSES_POLICY = {
|
|
|
146
46
|
{ name: 'messages.ensure-output-fields', options: { toolFallback: 'Tool call completed (no output).' } },
|
|
147
47
|
{ name: 'messages.ensure-system-instruction' },
|
|
148
48
|
reasoningAction('responses_reasoning'),
|
|
149
|
-
{ name: 'metadata.extra-fields', options: { allowedKeys:
|
|
49
|
+
{ name: 'metadata.extra-fields', options: { allowedKeys: OPENAI_RESPONSES_ALLOWED_FIELDS } },
|
|
150
50
|
{ name: 'metadata.provider-field', options: { field: 'metadata', target: 'providerMetadata' } },
|
|
151
51
|
{ name: 'metadata.provider-sentinel', options: { sentinel: '__rcc_provider_metadata', target: 'providerMetadata' } }
|
|
152
52
|
]
|
|
@@ -156,12 +56,12 @@ const RESPONSES_POLICY = {
|
|
|
156
56
|
{ name: 'reasoning.attach-output' },
|
|
157
57
|
reasoningAction('responses_reasoning'),
|
|
158
58
|
toolCallNormalizationAction('responses_tool_call'),
|
|
159
|
-
{ name: 'metadata.extra-fields', options: { allowedKeys:
|
|
59
|
+
{ name: 'metadata.extra-fields', options: { allowedKeys: OPENAI_RESPONSES_ALLOWED_FIELDS } }
|
|
160
60
|
],
|
|
161
61
|
outbound: [
|
|
162
62
|
reasoningAction('responses_reasoning'),
|
|
163
63
|
toolCallNormalizationAction('responses_tool_call'),
|
|
164
|
-
{ name: 'metadata.extra-fields', options: { allowedKeys:
|
|
64
|
+
{ name: 'metadata.extra-fields', options: { allowedKeys: OPENAI_RESPONSES_ALLOWED_FIELDS } }
|
|
165
65
|
]
|
|
166
66
|
}
|
|
167
67
|
};
|
|
@@ -73,8 +73,8 @@ function cloneParameters(value) {
|
|
|
73
73
|
continue;
|
|
74
74
|
if (key === 'exclusiveMinimum' || key === 'exclusiveMaximum' || key === 'propertyNames')
|
|
75
75
|
continue;
|
|
76
|
-
// Keep Gemini tool schemas permissive to avoid upstream MALFORMED_FUNCTION_CALL on strict validation.
|
|
77
|
-
//
|
|
76
|
+
// Keep Gemini tool schemas mostly permissive to avoid upstream MALFORMED_FUNCTION_CALL on strict validation.
|
|
77
|
+
// We selectively re-introduce safe required fields for critical tools in `buildGeminiToolsFromBridge`.
|
|
78
78
|
if (key === 'required' || key === 'additionalProperties')
|
|
79
79
|
continue;
|
|
80
80
|
// Combinators are handled at the node level above.
|
|
@@ -166,6 +166,123 @@ export function buildGeminiToolsFromBridge(defs) {
|
|
|
166
166
|
return undefined;
|
|
167
167
|
}
|
|
168
168
|
const tools = [];
|
|
169
|
+
const applyFixups = (name, parameters) => {
|
|
170
|
+
if (!parameters || typeof parameters !== 'object' || Array.isArray(parameters)) {
|
|
171
|
+
return parameters;
|
|
172
|
+
}
|
|
173
|
+
const params = parameters;
|
|
174
|
+
const propsRaw = params.properties;
|
|
175
|
+
const props = isPlainRecord(propsRaw) ? propsRaw : {};
|
|
176
|
+
const lowered = String(name || '').trim().toLowerCase();
|
|
177
|
+
if (lowered === 'exec_command') {
|
|
178
|
+
// Keep Gemini tool schema aligned with both historical Codex and our runtime tool signature:
|
|
179
|
+
// - Codex CLI: { cmd, workdir }
|
|
180
|
+
// - Some history: { command, workdir }
|
|
181
|
+
// Strict Gemini validation (and/or tool selection) is sensitive to schema/arg drift.
|
|
182
|
+
if (!Object.prototype.hasOwnProperty.call(props, 'cmd') && Object.prototype.hasOwnProperty.call(props, 'command')) {
|
|
183
|
+
props.cmd = props.command;
|
|
184
|
+
}
|
|
185
|
+
if (!Object.prototype.hasOwnProperty.call(props, 'command') && Object.prototype.hasOwnProperty.call(props, 'cmd')) {
|
|
186
|
+
props.command = props.cmd;
|
|
187
|
+
}
|
|
188
|
+
if (!Object.prototype.hasOwnProperty.call(props, 'cmd')) {
|
|
189
|
+
props.cmd = { type: 'string' };
|
|
190
|
+
}
|
|
191
|
+
if (!Object.prototype.hasOwnProperty.call(props, 'command')) {
|
|
192
|
+
props.command = { type: 'string' };
|
|
193
|
+
}
|
|
194
|
+
if (!Object.prototype.hasOwnProperty.call(props, 'workdir')) {
|
|
195
|
+
props.workdir = { type: 'string' };
|
|
196
|
+
}
|
|
197
|
+
params.properties = props;
|
|
198
|
+
// Avoid hard required keys for Gemini: the model may emit either alias (cmd/command),
|
|
199
|
+
// and "required" mismatch surfaces as MALFORMED_FUNCTION_CALL (empty reply) upstream.
|
|
200
|
+
try {
|
|
201
|
+
delete params.required;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
params.required = undefined;
|
|
205
|
+
}
|
|
206
|
+
return params;
|
|
207
|
+
}
|
|
208
|
+
if (lowered === 'write_stdin') {
|
|
209
|
+
// Keep both aliases to avoid strict validation failures when the model echoes history.
|
|
210
|
+
if (!Object.prototype.hasOwnProperty.call(props, 'chars') && Object.prototype.hasOwnProperty.call(props, 'text')) {
|
|
211
|
+
props.chars = props.text;
|
|
212
|
+
}
|
|
213
|
+
if (!Object.prototype.hasOwnProperty.call(props, 'text') && Object.prototype.hasOwnProperty.call(props, 'chars')) {
|
|
214
|
+
props.text = props.chars;
|
|
215
|
+
}
|
|
216
|
+
if (!Object.prototype.hasOwnProperty.call(props, 'session_id')) {
|
|
217
|
+
props.session_id = { type: 'number' };
|
|
218
|
+
}
|
|
219
|
+
if (!Object.prototype.hasOwnProperty.call(props, 'chars')) {
|
|
220
|
+
props.chars = { type: 'string' };
|
|
221
|
+
}
|
|
222
|
+
params.properties = props;
|
|
223
|
+
try {
|
|
224
|
+
delete params.required;
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
params.required = undefined;
|
|
228
|
+
}
|
|
229
|
+
return params;
|
|
230
|
+
}
|
|
231
|
+
if (lowered === 'apply_patch') {
|
|
232
|
+
// Gemini function calling needs JSON args, but Codex tool docs often describe apply_patch as "freeform".
|
|
233
|
+
// To prevent MALFORMED_FUNCTION_CALL, accept common aliases used by various bridge layers:
|
|
234
|
+
// - patch: canonical patch text
|
|
235
|
+
// - input/instructions/text: historical aliases containing patch text
|
|
236
|
+
if (!Object.prototype.hasOwnProperty.call(props, 'patch')) {
|
|
237
|
+
props.patch = {
|
|
238
|
+
type: 'string',
|
|
239
|
+
description: 'Patch text (*** Begin Patch / *** End Patch or GNU unified diff).'
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (!Object.prototype.hasOwnProperty.call(props, 'input')) {
|
|
243
|
+
props.input = {
|
|
244
|
+
type: 'string',
|
|
245
|
+
description: 'Alias of patch (patch text). Prefer patch.'
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (!Object.prototype.hasOwnProperty.call(props, 'instructions')) {
|
|
249
|
+
props.instructions = {
|
|
250
|
+
type: 'string',
|
|
251
|
+
description: 'Alias of patch (patch text). Prefer patch.'
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if (!Object.prototype.hasOwnProperty.call(props, 'text')) {
|
|
255
|
+
props.text = {
|
|
256
|
+
type: 'string',
|
|
257
|
+
description: 'Alias of patch (patch text). Prefer patch.'
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
params.properties = props;
|
|
261
|
+
try {
|
|
262
|
+
delete params.required;
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
params.required = undefined;
|
|
266
|
+
}
|
|
267
|
+
return params;
|
|
268
|
+
}
|
|
269
|
+
return params;
|
|
270
|
+
};
|
|
271
|
+
const rewriteDescription = (name, description) => {
|
|
272
|
+
const lowered = String(name || '').trim().toLowerCase();
|
|
273
|
+
if (lowered === 'apply_patch') {
|
|
274
|
+
return ('Edit files by providing patch text in `patch` (string). ' +
|
|
275
|
+
'Supports "*** Begin Patch" / "*** End Patch" or GNU unified diff. ' +
|
|
276
|
+
'`input`/`instructions`/`text` are accepted as aliases.');
|
|
277
|
+
}
|
|
278
|
+
if (lowered === 'exec_command') {
|
|
279
|
+
return ('Run a shell command. Provide `cmd` (string) (alias: `command`) and optional `workdir` (string).');
|
|
280
|
+
}
|
|
281
|
+
if (lowered === 'write_stdin') {
|
|
282
|
+
return 'Write to an existing exec session. Provide `session_id` (number) and optional `chars` (string).';
|
|
283
|
+
}
|
|
284
|
+
return description;
|
|
285
|
+
};
|
|
169
286
|
defs.forEach((def) => {
|
|
170
287
|
if (!def || typeof def !== 'object') {
|
|
171
288
|
return;
|
|
@@ -184,12 +301,12 @@ export function buildGeminiToolsFromBridge(defs) {
|
|
|
184
301
|
: typeof def.description === 'string'
|
|
185
302
|
? def.description
|
|
186
303
|
: undefined;
|
|
187
|
-
const parameters = cloneParameters(fnNode?.parameters ?? def.parameters ?? { type: 'object', properties: {} });
|
|
304
|
+
const parameters = applyFixups(name, cloneParameters(fnNode?.parameters ?? def.parameters ?? { type: 'object', properties: {} }));
|
|
188
305
|
tools.push({
|
|
189
306
|
functionDeclarations: [
|
|
190
307
|
{
|
|
191
308
|
name,
|
|
192
|
-
description,
|
|
309
|
+
description: rewriteDescription(name, description),
|
|
193
310
|
parameters
|
|
194
311
|
}
|
|
195
312
|
]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const OPENAI_CHAT_ALLOWED_FIELDS: readonly ["messages", "tools", "tool_outputs", "model", "temperature", "top_p", "top_k", "max_tokens", "frequency_penalty", "presence_penalty", "logit_bias", "response_format", "parallel_tool_calls", "tool_choice", "seed", "user", "metadata", "stop", "stop_sequences", "stream"];
|
|
2
|
+
export declare const ANTHROPIC_ALLOWED_FIELDS: readonly ["model", "messages", "tools", "system", "stop_sequences", "temperature", "top_p", "top_k", "max_tokens", "max_output_tokens", "metadata", "stream", "tool_choice"];
|
|
3
|
+
export declare const OPENAI_RESPONSES_ALLOWED_FIELDS: readonly ["id", "object", "created_at", "model", "status", "input", "instructions", "output", "output_text", "required_action", "response_id", "previous_response_id", "tool_outputs", "tools", "metadata", "include", "store", "user", "response_format", "temperature", "top_p", "top_k", "max_tokens", "max_output_tokens", "logit_bias", "seed", "parallel_tool_calls", "tool_choice", "stream", "instructions_is_raw"];
|
|
4
|
+
export declare const GEMINI_ALLOWED_FIELDS: readonly ["model", "contents", "systemInstruction", "system_instruction", "generationConfig", "generation_config", "safetySettings", "safety_settings", "metadata", "toolConfig", "tool_config", "tools", "tool_choice", "parallelToolCalls", "parallel_tool_calls", "responseMimeType", "response_mime_type", "stopSequences", "stop_sequences", "cachedContent", "prompt", "response", "candidates", "usageMetadata", "responseMetadata", "promptFeedback", "modelVersion", "client", "user", "stream"];
|
|
5
|
+
export declare const OPENAI_RESPONSES_PARAMETERS_WRAPPER_ALLOW_KEYS: readonly ["temperature", "top_p", "max_output_tokens", "seed", "logit_bias", "user", "parallel_tool_calls", "tool_choice", "response_format", "stream", "stop", "stop_sequences", "modalities", "top_k"];
|
|
6
|
+
export declare const OPENAI_CHAT_PARAMETERS_WRAPPER_ALLOW_KEYS: readonly ["temperature", "top_p", "top_k", "max_tokens", "frequency_penalty", "presence_penalty", "logit_bias", "seed", "user", "parallel_tool_calls", "tool_choice", "response_format", "stream", "stop", "stop_sequences"];
|
|
7
|
+
export declare const ANTHROPIC_PARAMETERS_WRAPPER_ALLOW_KEYS: readonly ["stop_sequences", "temperature", "top_p", "top_k", "max_tokens", "max_output_tokens", "metadata", "stream", "tool_choice"];
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
export const OPENAI_CHAT_ALLOWED_FIELDS = [
|
|
2
|
+
'messages',
|
|
3
|
+
'tools',
|
|
4
|
+
'tool_outputs',
|
|
5
|
+
'model',
|
|
6
|
+
'temperature',
|
|
7
|
+
'top_p',
|
|
8
|
+
'top_k',
|
|
9
|
+
'max_tokens',
|
|
10
|
+
'frequency_penalty',
|
|
11
|
+
'presence_penalty',
|
|
12
|
+
'logit_bias',
|
|
13
|
+
'response_format',
|
|
14
|
+
'parallel_tool_calls',
|
|
15
|
+
'tool_choice',
|
|
16
|
+
'seed',
|
|
17
|
+
'user',
|
|
18
|
+
'metadata',
|
|
19
|
+
'stop',
|
|
20
|
+
'stop_sequences',
|
|
21
|
+
'stream'
|
|
22
|
+
];
|
|
23
|
+
export const ANTHROPIC_ALLOWED_FIELDS = [
|
|
24
|
+
'model',
|
|
25
|
+
'messages',
|
|
26
|
+
'tools',
|
|
27
|
+
'system',
|
|
28
|
+
'stop_sequences',
|
|
29
|
+
'temperature',
|
|
30
|
+
'top_p',
|
|
31
|
+
'top_k',
|
|
32
|
+
'max_tokens',
|
|
33
|
+
'max_output_tokens',
|
|
34
|
+
'metadata',
|
|
35
|
+
'stream',
|
|
36
|
+
'tool_choice'
|
|
37
|
+
];
|
|
38
|
+
export const OPENAI_RESPONSES_ALLOWED_FIELDS = [
|
|
39
|
+
'id',
|
|
40
|
+
'object',
|
|
41
|
+
'created_at',
|
|
42
|
+
'model',
|
|
43
|
+
'status',
|
|
44
|
+
'input',
|
|
45
|
+
'instructions',
|
|
46
|
+
'output',
|
|
47
|
+
'output_text',
|
|
48
|
+
'required_action',
|
|
49
|
+
'response_id',
|
|
50
|
+
'previous_response_id',
|
|
51
|
+
'tool_outputs',
|
|
52
|
+
'tools',
|
|
53
|
+
'metadata',
|
|
54
|
+
'include',
|
|
55
|
+
'store',
|
|
56
|
+
'user',
|
|
57
|
+
'response_format',
|
|
58
|
+
'temperature',
|
|
59
|
+
'top_p',
|
|
60
|
+
'top_k',
|
|
61
|
+
'max_tokens',
|
|
62
|
+
'max_output_tokens',
|
|
63
|
+
'logit_bias',
|
|
64
|
+
'seed',
|
|
65
|
+
'parallel_tool_calls',
|
|
66
|
+
'tool_choice',
|
|
67
|
+
'stream',
|
|
68
|
+
'instructions_is_raw'
|
|
69
|
+
];
|
|
70
|
+
export const GEMINI_ALLOWED_FIELDS = [
|
|
71
|
+
'model',
|
|
72
|
+
'contents',
|
|
73
|
+
'systemInstruction',
|
|
74
|
+
'system_instruction',
|
|
75
|
+
'generationConfig',
|
|
76
|
+
'generation_config',
|
|
77
|
+
'safetySettings',
|
|
78
|
+
'safety_settings',
|
|
79
|
+
'metadata',
|
|
80
|
+
'toolConfig',
|
|
81
|
+
'tool_config',
|
|
82
|
+
'tools',
|
|
83
|
+
'tool_choice',
|
|
84
|
+
'parallelToolCalls',
|
|
85
|
+
'parallel_tool_calls',
|
|
86
|
+
'responseMimeType',
|
|
87
|
+
'response_mime_type',
|
|
88
|
+
'stopSequences',
|
|
89
|
+
'stop_sequences',
|
|
90
|
+
'cachedContent',
|
|
91
|
+
'prompt',
|
|
92
|
+
'response',
|
|
93
|
+
'candidates',
|
|
94
|
+
'usageMetadata',
|
|
95
|
+
'responseMetadata',
|
|
96
|
+
'promptFeedback',
|
|
97
|
+
'modelVersion',
|
|
98
|
+
'client',
|
|
99
|
+
'user',
|
|
100
|
+
'stream'
|
|
101
|
+
];
|
|
102
|
+
export const OPENAI_RESPONSES_PARAMETERS_WRAPPER_ALLOW_KEYS = [
|
|
103
|
+
'temperature',
|
|
104
|
+
'top_p',
|
|
105
|
+
'max_output_tokens',
|
|
106
|
+
'seed',
|
|
107
|
+
'logit_bias',
|
|
108
|
+
'user',
|
|
109
|
+
'parallel_tool_calls',
|
|
110
|
+
'tool_choice',
|
|
111
|
+
'response_format',
|
|
112
|
+
'stream',
|
|
113
|
+
'stop',
|
|
114
|
+
'stop_sequences',
|
|
115
|
+
'modalities',
|
|
116
|
+
'top_k'
|
|
117
|
+
];
|
|
118
|
+
export const OPENAI_CHAT_PARAMETERS_WRAPPER_ALLOW_KEYS = [
|
|
119
|
+
'temperature',
|
|
120
|
+
'top_p',
|
|
121
|
+
'top_k',
|
|
122
|
+
'max_tokens',
|
|
123
|
+
'frequency_penalty',
|
|
124
|
+
'presence_penalty',
|
|
125
|
+
'logit_bias',
|
|
126
|
+
'seed',
|
|
127
|
+
'user',
|
|
128
|
+
'parallel_tool_calls',
|
|
129
|
+
'tool_choice',
|
|
130
|
+
'response_format',
|
|
131
|
+
'stream',
|
|
132
|
+
'stop',
|
|
133
|
+
'stop_sequences'
|
|
134
|
+
];
|
|
135
|
+
export const ANTHROPIC_PARAMETERS_WRAPPER_ALLOW_KEYS = [
|
|
136
|
+
'stop_sequences',
|
|
137
|
+
'temperature',
|
|
138
|
+
'top_p',
|
|
139
|
+
'top_k',
|
|
140
|
+
'max_tokens',
|
|
141
|
+
'max_output_tokens',
|
|
142
|
+
'metadata',
|
|
143
|
+
'stream',
|
|
144
|
+
'tool_choice'
|
|
145
|
+
];
|
|
@@ -97,11 +97,13 @@ export function normalizeMessageReasoningTools(message, options) {
|
|
|
97
97
|
const trimmed = (cleanedText || '').trim();
|
|
98
98
|
writeReasoningContent(message, trimmed);
|
|
99
99
|
// Chat 统一行为:如果 message 本身没有正文内容,但存在非空 reasoning_content,
|
|
100
|
-
// 则将思考内容提升到正文,前后包裹 [思考]
|
|
100
|
+
// 则将思考内容提升到正文,前后包裹 [思考] 标记,避免客户端只看到"空回答"。
|
|
101
101
|
const rawContent = message.content;
|
|
102
102
|
if ((typeof rawContent !== 'string' || rawContent.trim().length === 0) &&
|
|
103
103
|
trimmed.length > 0) {
|
|
104
|
-
|
|
104
|
+
// 检查是否已经包含 [思考] 标签,避免重复嵌套
|
|
105
|
+
const hasThinkingTags = trimmed.includes('[思考]') && trimmed.includes('[/思考]');
|
|
106
|
+
message.content = hasThinkingTags ? trimmed : `[思考]\n${trimmed}\n[/思考]`;
|
|
105
107
|
}
|
|
106
108
|
if (!Array.isArray(toolCalls) || toolCalls.length === 0) {
|
|
107
109
|
return { toolCallsAdded: 0, cleanedReasoning: trimmed };
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
const DEFAULT_SNAPSHOT_ROOT = path.join(os.homedir(), '.routecodex', 'codex-samples');
|
|
5
|
+
const DEFAULT_ERRORSAMPLES_ROOT = path.join(os.homedir(), '.routecodex', 'errorsamples');
|
|
5
6
|
const PENDING_PROVIDER_DIR = '__pending__';
|
|
6
7
|
const POLICY_VIOLATIONS_DIR = '__policy_violations__';
|
|
7
8
|
function resolveSnapshotRoot() {
|
|
@@ -12,6 +13,75 @@ function resolveSnapshotRoot() {
|
|
|
12
13
|
}
|
|
13
14
|
return DEFAULT_SNAPSHOT_ROOT;
|
|
14
15
|
}
|
|
16
|
+
function resolveErrorsamplesRoot() {
|
|
17
|
+
const envOverride = process.env.ROUTECODEX_ERRORSAMPLES_DIR ||
|
|
18
|
+
process.env.ROUTECODEX_ERROR_SAMPLES_DIR;
|
|
19
|
+
if (envOverride && String(envOverride).trim()) {
|
|
20
|
+
return path.resolve(String(envOverride).trim());
|
|
21
|
+
}
|
|
22
|
+
return DEFAULT_ERRORSAMPLES_ROOT;
|
|
23
|
+
}
|
|
24
|
+
function safeErrorsampleName(name) {
|
|
25
|
+
return String(name || 'sample').replace(/[^\w.-]/g, '_');
|
|
26
|
+
}
|
|
27
|
+
async function cleanupZeroByteJsonFiles(dir) {
|
|
28
|
+
let entries = [];
|
|
29
|
+
try {
|
|
30
|
+
entries = await fs.readdir(dir);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const candidates = entries.filter((name) => name.endsWith('.json'));
|
|
36
|
+
if (candidates.length === 0) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
await Promise.allSettled(candidates.map(async (name) => {
|
|
40
|
+
const full = path.join(dir, name);
|
|
41
|
+
try {
|
|
42
|
+
const st = await fs.stat(full);
|
|
43
|
+
if (!st.isFile() || st.size > 0) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
await fs.unlink(full);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// ignore cleanup failures
|
|
50
|
+
}
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
async function writeUniqueErrorsampleFile(dir, baseName, contents) {
|
|
54
|
+
const parsed = path.parse(baseName);
|
|
55
|
+
const ext = parsed.ext || '.json';
|
|
56
|
+
const stem = parsed.name || 'sample';
|
|
57
|
+
const tmpDir = path.join(dir, '_tmp');
|
|
58
|
+
try {
|
|
59
|
+
await fs.mkdir(tmpDir, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// ignore; fallback to direct writes
|
|
63
|
+
}
|
|
64
|
+
for (let i = 0; i < 32; i += 1) {
|
|
65
|
+
const suffix = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
66
|
+
const fileName = `${stem}_${suffix}${ext}`;
|
|
67
|
+
try {
|
|
68
|
+
const dest = path.join(dir, fileName);
|
|
69
|
+
const tmp = path.join(tmpDir, `${fileName}.tmp`);
|
|
70
|
+
await fs.writeFile(tmp, contents, { encoding: 'utf-8', flag: 'wx' });
|
|
71
|
+
await fs.rename(tmp, dest);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (toErrorCode(error) === 'EEXIST') {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// last resort (best-effort overwrite into a timestamped file)
|
|
82
|
+
const fallback = `${stem}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}${ext}`;
|
|
83
|
+
await fs.writeFile(path.join(dir, fallback), contents, { encoding: 'utf-8' });
|
|
84
|
+
}
|
|
15
85
|
function resolveSnapshotFolder(endpoint) {
|
|
16
86
|
const lowered = (endpoint || '').toLowerCase();
|
|
17
87
|
if (lowered.includes('/responses')) {
|
|
@@ -177,6 +247,9 @@ function readNumberField(value) {
|
|
|
177
247
|
function isHubPolicyStage(stage) {
|
|
178
248
|
return typeof stage === 'string' && stage.startsWith('hub_policy.');
|
|
179
249
|
}
|
|
250
|
+
function isHubToolSurfaceStage(stage) {
|
|
251
|
+
return typeof stage === 'string' && stage.startsWith('hub_toolsurface.');
|
|
252
|
+
}
|
|
180
253
|
function hasPolicyViolations(value) {
|
|
181
254
|
if (!value || typeof value !== 'object') {
|
|
182
255
|
return false;
|
|
@@ -195,6 +268,24 @@ function hasPolicyViolations(value) {
|
|
|
195
268
|
}
|
|
196
269
|
return false;
|
|
197
270
|
}
|
|
271
|
+
function hasToolSurfaceDiff(value) {
|
|
272
|
+
if (!value || typeof value !== 'object') {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
const obj = value;
|
|
276
|
+
const diffCount = readNumberField(obj.diffCount);
|
|
277
|
+
if (typeof diffCount === 'number' && diffCount > 0) {
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
// Response-side tool surface mismatch detection records expected/detected protocol
|
|
281
|
+
// without a numeric diffCount; treat any protocol mismatch as a diff.
|
|
282
|
+
const expected = readStringField(obj.expectedProtocol);
|
|
283
|
+
const detected = readStringField(obj.detectedProtocol);
|
|
284
|
+
if (expected && detected && expected !== detected) {
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
198
289
|
function hasPolicyEnforcementChanges(value) {
|
|
199
290
|
if (!value || typeof value !== 'object') {
|
|
200
291
|
return false;
|
|
@@ -214,11 +305,25 @@ async function writeUniqueFile(dir, baseName, contents) {
|
|
|
214
305
|
const parsed = path.parse(baseName);
|
|
215
306
|
const ext = parsed.ext || '.json';
|
|
216
307
|
const stem = parsed.name || 'snapshot';
|
|
308
|
+
const tmpPrefix = `.__tmp_${stem}_${process.pid}_${Date.now()}`;
|
|
217
309
|
for (let i = 0; i < 64; i += 1) {
|
|
218
310
|
const name = i === 0 ? `${stem}${ext}` : `${stem}_${i}${ext}`;
|
|
219
311
|
try {
|
|
220
|
-
|
|
221
|
-
|
|
312
|
+
const dest = path.join(dir, name);
|
|
313
|
+
const tmp = path.join(dir, `${tmpPrefix}_${Math.random().toString(36).slice(2, 8)}${ext}`);
|
|
314
|
+
await fs.writeFile(tmp, contents, { encoding: 'utf-8', flag: 'wx' });
|
|
315
|
+
try {
|
|
316
|
+
await fs.link(tmp, dest);
|
|
317
|
+
await fs.unlink(tmp).catch(() => { });
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
await fs.unlink(tmp).catch(() => { });
|
|
322
|
+
if (toErrorCode(error) === 'EEXIST') {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
222
327
|
}
|
|
223
328
|
catch (error) {
|
|
224
329
|
if (toErrorCode(error) === 'EEXIST') {
|
|
@@ -228,7 +333,15 @@ async function writeUniqueFile(dir, baseName, contents) {
|
|
|
228
333
|
}
|
|
229
334
|
}
|
|
230
335
|
const fallback = `${stem}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}${ext}`;
|
|
231
|
-
|
|
336
|
+
const dest = path.join(dir, fallback);
|
|
337
|
+
const tmp = path.join(dir, `${tmpPrefix}_${Math.random().toString(36).slice(2, 8)}${ext}`);
|
|
338
|
+
await fs.writeFile(tmp, contents, { encoding: 'utf-8', flag: 'wx' });
|
|
339
|
+
try {
|
|
340
|
+
await fs.link(tmp, dest);
|
|
341
|
+
}
|
|
342
|
+
finally {
|
|
343
|
+
await fs.unlink(tmp).catch(() => { });
|
|
344
|
+
}
|
|
232
345
|
}
|
|
233
346
|
const requestProviderIndex = globalThis
|
|
234
347
|
.__routecodexSnapshotProviderIndex ||
|
|
@@ -313,6 +426,23 @@ async function writeSnapshotFile(options, rootOverride) {
|
|
|
313
426
|
}
|
|
314
427
|
const dir = path.join(root, folder, providerToken, groupRequestToken);
|
|
315
428
|
await fs.mkdir(dir, { recursive: true });
|
|
429
|
+
// Write a stable runtime marker for this request folder (best-effort).
|
|
430
|
+
try {
|
|
431
|
+
const metaPath = path.join(dir, '__runtime.json');
|
|
432
|
+
const payload = JSON.stringify({
|
|
433
|
+
timestamp: new Date().toISOString(),
|
|
434
|
+
versions: {
|
|
435
|
+
routecodex: process.env.ROUTECODEX_VERSION,
|
|
436
|
+
routecodexBuildTime: process.env.ROUTECODEX_BUILD_TIME,
|
|
437
|
+
llmswitchCore: process.env.ROUTECODEX_LLMSWITCH_CORE_VERSION,
|
|
438
|
+
node: process.version
|
|
439
|
+
}
|
|
440
|
+
}, null, 2);
|
|
441
|
+
await fs.writeFile(metaPath, payload, { encoding: 'utf-8', flag: 'wx' }).catch(() => { });
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
// ignore
|
|
445
|
+
}
|
|
316
446
|
const spacing = options.verbosity === 'minimal' ? undefined : 2;
|
|
317
447
|
const payload = spacing !== undefined
|
|
318
448
|
? JSON.stringify(options.data, null, spacing)
|
|
@@ -342,4 +472,37 @@ export async function writeSnapshotViaHooks(options) {
|
|
|
342
472
|
catch {
|
|
343
473
|
// never block callers
|
|
344
474
|
}
|
|
475
|
+
// 3) Tool surface diffs (copy-on-diff only) → ~/.routecodex/errorsamples/tool-surface/
|
|
476
|
+
try {
|
|
477
|
+
if (isHubToolSurfaceStage(stage) && hasToolSurfaceDiff(options.data)) {
|
|
478
|
+
const root = resolveErrorsamplesRoot();
|
|
479
|
+
const dir = path.join(root, safeErrorsampleName('tool-surface'));
|
|
480
|
+
await fs.mkdir(dir, { recursive: true });
|
|
481
|
+
// Best-effort cleanup: when the process exits abruptly, an in-flight async
|
|
482
|
+
// write can leave behind 0-byte placeholder files. Keep the observable
|
|
483
|
+
// directory clean so operators can tail it.
|
|
484
|
+
await cleanupZeroByteJsonFiles(dir);
|
|
485
|
+
const stamp = new Date().toISOString();
|
|
486
|
+
const payload = {
|
|
487
|
+
kind: 'hub_toolsurface_diff',
|
|
488
|
+
timestamp: stamp,
|
|
489
|
+
endpoint: options.endpoint,
|
|
490
|
+
stage,
|
|
491
|
+
requestId: options.requestId,
|
|
492
|
+
providerKey: options.providerKey,
|
|
493
|
+
groupRequestId: options.groupRequestId,
|
|
494
|
+
runtime: {
|
|
495
|
+
routecodexVersion: process.env.ROUTECODEX_VERSION,
|
|
496
|
+
routecodexBuildTime: process.env.ROUTECODEX_BUILD_TIME,
|
|
497
|
+
llmswitchCore: process.env.ROUTECODEX_LLMSWITCH_CORE_VERSION,
|
|
498
|
+
node: process.version
|
|
499
|
+
},
|
|
500
|
+
observation: options.data
|
|
501
|
+
};
|
|
502
|
+
await writeUniqueErrorsampleFile(dir, `${safeErrorsampleName(stage)}.json`, JSON.stringify(payload, null, 2));
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
// never block callers
|
|
507
|
+
}
|
|
345
508
|
}
|