maka-agent 0.2.0-dev.43.20260919 → 0.2.0-dev.45.20260921
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/LICENSE +12 -6
- package/dist/mcp-capability-provider.js +7 -0
- package/dist/runtime-host-service-manager.js +1 -6
- package/dist/runtime-host-tui-command.js +1 -1
- package/dist/runtime-host-update-command.js +39 -27
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/core/dist/artifacts.js +1 -0
- package/node_modules/@maka/core/dist/chat-model-choice.js +3 -0
- package/node_modules/@maka/core/dist/llm-connections.js +3 -21
- package/node_modules/@maka/core/dist/model-catalog.js +8 -20
- package/node_modules/@maka/core/dist/model-metadata.generated.js +304 -304
- package/node_modules/@maka/core/dist/model-metadata.js +1 -2
- package/node_modules/@maka/core/dist/model-thinking.js +16 -1
- package/node_modules/@maka/core/dist/models-dev-projection.js +0 -1
- package/node_modules/@maka/core/dist/provider-auth.js +1 -7
- package/node_modules/@maka/core/dist/provider-registry.js +20 -58
- package/node_modules/@maka/core/dist/recall.js +5 -1
- package/node_modules/@maka/core/dist/runtime-policy/connection-catalog-codec.js +7 -0
- package/node_modules/@maka/core/dist/runtime-policy/model-catalog-entry-codec.js +14 -1
- package/node_modules/@maka/core/dist/session-start-mode.js +2 -5
- package/node_modules/@maka/core/dist/session.js +0 -1
- package/node_modules/@maka/core/dist/settings.js +19 -3
- package/node_modules/@maka/core/dist/tool-quiet-preview.js +2 -16
- package/node_modules/@maka/core/dist/transcript-search.js +46 -0
- package/node_modules/@maka/core/package.json +1 -4
- package/node_modules/@maka/mcp/dist/index.js +22 -0
- package/node_modules/@maka/mcp/dist/oauth.js +4 -2
- package/node_modules/@maka/mcp/dist/tool-progress.js +45 -0
- package/node_modules/@maka/runtime/dist/model-factory.js +0 -16
- package/node_modules/@maka/runtime/dist/model-fetcher.js +1 -4
- package/node_modules/@maka/runtime/dist/model-runtime.js +0 -8
- package/node_modules/@maka/runtime/dist/opencode-session-header.js +2 -3
- package/node_modules/@maka/runtime/dist/recall-tools.js +4 -0
- package/node_modules/@maka/runtime/dist/session-manager.js +5 -57
- package/node_modules/@maka/runtime/dist/subagent-tools.js +69 -11
- package/node_modules/@maka/runtime/dist/test-connection.js +4 -114
- package/node_modules/@maka/runtime/package.json +0 -1
- package/node_modules/@maka/runtime-host/dist/client/client-capability-channel.js +44 -9
- package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +25 -18
- package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +3 -1
- package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
- package/node_modules/@maka/runtime-host/dist/client/session-subscription.js +33 -3
- package/node_modules/@maka/runtime-host/dist/protocol/connection-effects.js +0 -108
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +12 -1
- package/node_modules/@maka/runtime-host/dist/protocol/message.js +10 -0
- package/node_modules/@maka/runtime-host/dist/protocol/operations.js +4 -6
- package/node_modules/@maka/runtime-host/dist/protocol/recall.js +270 -0
- package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +1 -1
- package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +1 -7
- package/node_modules/@maka/runtime-host/dist/protocol/turn.js +0 -33
- package/node_modules/@maka/runtime-host/dist/protocol/usage-screen.js +12 -4
- package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +6 -0
- package/node_modules/@maka/runtime-host/dist/server/bootstrap-runtime-policy.js +31 -104
- package/node_modules/@maka/runtime-host/dist/server/connection-effect-coordinator.js +1 -57
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +11 -35
- package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +3 -20
- package/node_modules/@maka/runtime-host/dist/server/interactive-turn-coordinator.js +1 -16
- package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +46 -28
- package/node_modules/@maka/runtime-host/dist/server/recall-coordinator.js +117 -0
- package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +5 -16
- package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +21 -12
- package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +0 -4
- package/node_modules/@maka/runtime-host/dist/server/session-turn-access-request-coordinator.js +7 -17
- package/node_modules/@maka/storage/dist/artifact-metadata-codec.js +4 -3
- package/node_modules/@maka/storage/dist/artifact-store.js +1 -11
- package/node_modules/@maka/storage/dist/process-lifetime-file-update-lock.js +0 -41
- package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +0 -62
- package/node_modules/@maka/storage/dist/runtime-policy/coordinator.js +0 -31
- package/node_modules/@maka/storage/dist/runtime-policy-stores.js +0 -3
- package/node_modules/@maka/storage/dist/sqlite-usage-schema.js +29 -5
- package/node_modules/@maka/storage/dist/sqlite-workflow-schema.js +2 -0
- package/node_modules/@maka/storage/dist/storage-writer-composition.js +0 -3
- package/node_modules/@maka/storage/dist/telemetry-file-schema.js +5 -3
- package/node_modules/@maka/storage/dist/usage-screen.js +5 -6
- package/node_modules/@maka/storage/dist/usage-stores.js +14 -4
- package/node_modules/@maka/storage/package.json +0 -2
- package/package.json +1 -1
- package/node_modules/@maka/core/dist/connection-usage.js +0 -27
- package/node_modules/@maka/core/dist/deep-research-client-progress.js +0 -132
- package/node_modules/@maka/core/dist/deep-research-run.js +0 -625
- package/node_modules/@maka/core/dist/deep-research.js +0 -208
- package/node_modules/@maka/core/dist/thread-search.js +0 -498
- package/node_modules/@maka/runtime/dist/commandcode-cli-language-model.js +0 -837
- package/node_modules/@maka/runtime/dist/connection-usage.js +0 -232
- package/node_modules/@maka/runtime/dist/deep-research-tools.js +0 -732
- package/node_modules/@maka/runtime-host/dist/protocol/deep-research.js +0 -233
- package/node_modules/@maka/runtime-host/dist/server/deep-research-coordinator.js +0 -116
- package/node_modules/@maka/storage/dist/deep-research-authority.js +0 -129
- package/node_modules/@maka/storage/dist/deep-research-store.js +0 -343
|
@@ -1,837 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
-
* or more contributor license agreements. See the NOTICE file
|
|
4
|
-
* distributed with this work for additional information
|
|
5
|
-
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
-
* to you under the Apache License, Version 2.0 (the
|
|
7
|
-
* "License"); you may not use this file except in compliance
|
|
8
|
-
* with the License. You may obtain a copy of the License at
|
|
9
|
-
*
|
|
10
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
-
*
|
|
12
|
-
* Unless required by applicable law or agreed to in writing,
|
|
13
|
-
* software distributed under the License is distributed on an
|
|
14
|
-
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
-
* KIND, either express or implied. See the License for the
|
|
16
|
-
* specific language governing permissions and limitations
|
|
17
|
-
* under the License.
|
|
18
|
-
*/
|
|
19
|
-
/**
|
|
20
|
-
* Command Code CLI transport: the `/alpha/generate` wire, exposed as an AI SDK
|
|
21
|
-
* language model. The Go plan's keys are answered by
|
|
22
|
-
* `/provider/v1/chat/completions` with `403 upgrade_required`, so this is the
|
|
23
|
-
* wire that serves them.
|
|
24
|
-
*
|
|
25
|
-
* Shape (see also the MIT-licensed `pi-commandcode-provider` and
|
|
26
|
-
* `dsh-commandcode-provider`):
|
|
27
|
-
* POST {apiBase}/alpha/generate
|
|
28
|
-
* body { config, memory, taste, skills, params: { model, messages, tools,
|
|
29
|
-
* system, max_tokens, temperature, stream, reasoning_effort? }, threadId }
|
|
30
|
-
* events SSE `data:` lines carrying AI SDK stream parts: `text-delta`,
|
|
31
|
-
* `reasoning-start|delta|end`, `tool-call`, `finish`, `error`.
|
|
32
|
-
* errors 403 `upgrade_required` is a plan gate, not a bad key.
|
|
33
|
-
*/
|
|
34
|
-
import { randomUUID } from 'node:crypto';
|
|
35
|
-
import { APICallError, } from '@ai-sdk/provider';
|
|
36
|
-
/** The official CLI release whose wire this mirrors; sent as its version header. */
|
|
37
|
-
export const COMMANDCODE_CLI_VERSION = '1.54.0';
|
|
38
|
-
const DEFAULT_MAX_TOKENS = 64_000;
|
|
39
|
-
const DEFAULT_TEMPERATURE = 0.3;
|
|
40
|
-
/** The gateway rejects `call_id` values longer than this. */
|
|
41
|
-
const MAX_WIRE_TOOL_CALL_ID_LENGTH = 64;
|
|
42
|
-
const SCHEMA_NORMALIZE_MAX_DEPTH = 8;
|
|
43
|
-
export function commandCodeCliGenerateUrl(apiBase) {
|
|
44
|
-
return `${apiBase.replace(/\/+$/u, '')}/alpha/generate`;
|
|
45
|
-
}
|
|
46
|
-
/** The identity headers the official CLI sends; the gateway keys plan gating on them. */
|
|
47
|
-
export function commandCodeCliHeaders(apiKey, workingDir) {
|
|
48
|
-
return {
|
|
49
|
-
'content-type': 'application/json',
|
|
50
|
-
authorization: `Bearer ${apiKey}`,
|
|
51
|
-
'x-command-code-version': COMMANDCODE_CLI_VERSION,
|
|
52
|
-
'x-cli-environment': 'production',
|
|
53
|
-
'x-project-slug': projectSlugFromPath(workingDir),
|
|
54
|
-
'x-taste-learning': 'true',
|
|
55
|
-
'x-co-flag': 'false',
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
export function projectSlugFromPath(pathName) {
|
|
59
|
-
const slug = pathName
|
|
60
|
-
.toLowerCase()
|
|
61
|
-
.replace(/^[a-z]:/iu, '')
|
|
62
|
-
.replace(/[^a-z0-9]+/gu, '-')
|
|
63
|
-
.replace(/^-+|(?<!-)-+$/gu, '');
|
|
64
|
-
return slug || 'project';
|
|
65
|
-
}
|
|
66
|
-
export class CommandCodeCliLanguageModel {
|
|
67
|
-
specificationVersion = 'v4';
|
|
68
|
-
provider = 'commandcode-cli';
|
|
69
|
-
modelId;
|
|
70
|
-
supportedUrls = {};
|
|
71
|
-
#config;
|
|
72
|
-
constructor(config) {
|
|
73
|
-
this.#config = config;
|
|
74
|
-
this.modelId = config.modelId;
|
|
75
|
-
}
|
|
76
|
-
async doGenerate(options) {
|
|
77
|
-
const { stream, request, response } = await this.doStream(options);
|
|
78
|
-
const content = [];
|
|
79
|
-
const warnings = [];
|
|
80
|
-
let finishReason = { unified: 'other', raw: undefined };
|
|
81
|
-
let usage = emptyUsage();
|
|
82
|
-
let openText;
|
|
83
|
-
let openReasoning;
|
|
84
|
-
const reader = stream.getReader();
|
|
85
|
-
for (;;) {
|
|
86
|
-
const { done, value: part } = await reader.read();
|
|
87
|
-
if (done)
|
|
88
|
-
break;
|
|
89
|
-
switch (part.type) {
|
|
90
|
-
case 'stream-start':
|
|
91
|
-
warnings.push(...part.warnings);
|
|
92
|
-
break;
|
|
93
|
-
case 'text-start':
|
|
94
|
-
openText = { id: part.id, text: '' };
|
|
95
|
-
break;
|
|
96
|
-
case 'text-delta':
|
|
97
|
-
if (openText)
|
|
98
|
-
openText.text += part.delta;
|
|
99
|
-
break;
|
|
100
|
-
case 'text-end':
|
|
101
|
-
if (openText)
|
|
102
|
-
content.push({ type: 'text', text: openText.text });
|
|
103
|
-
openText = undefined;
|
|
104
|
-
break;
|
|
105
|
-
case 'reasoning-start':
|
|
106
|
-
openReasoning = { id: part.id, text: '' };
|
|
107
|
-
break;
|
|
108
|
-
case 'reasoning-delta':
|
|
109
|
-
if (openReasoning)
|
|
110
|
-
openReasoning.text += part.delta;
|
|
111
|
-
break;
|
|
112
|
-
case 'reasoning-end':
|
|
113
|
-
if (openReasoning)
|
|
114
|
-
content.push({ type: 'reasoning', text: openReasoning.text });
|
|
115
|
-
openReasoning = undefined;
|
|
116
|
-
break;
|
|
117
|
-
case 'tool-call':
|
|
118
|
-
content.push(part);
|
|
119
|
-
break;
|
|
120
|
-
case 'finish':
|
|
121
|
-
finishReason = part.finishReason;
|
|
122
|
-
usage = part.usage;
|
|
123
|
-
break;
|
|
124
|
-
case 'error':
|
|
125
|
-
throw part.error;
|
|
126
|
-
default:
|
|
127
|
-
break;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return {
|
|
131
|
-
content,
|
|
132
|
-
finishReason,
|
|
133
|
-
usage,
|
|
134
|
-
warnings,
|
|
135
|
-
request,
|
|
136
|
-
response: { headers: response?.headers },
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
async doStream(options) {
|
|
140
|
-
const { body, warnings } = buildCommandCodeCliRequest(options, this.#config);
|
|
141
|
-
const url = commandCodeCliGenerateUrl(this.#config.apiBase);
|
|
142
|
-
const fetchFn = this.#config.fetch ?? globalThis.fetch;
|
|
143
|
-
const headers = {
|
|
144
|
-
...commandCodeCliHeaders(this.#config.apiKey, this.#config.workingDir ?? DEFAULT_WORKING_DIR),
|
|
145
|
-
...definedHeaders(options.headers),
|
|
146
|
-
};
|
|
147
|
-
let response;
|
|
148
|
-
try {
|
|
149
|
-
response = await fetchFn(url, {
|
|
150
|
-
method: 'POST',
|
|
151
|
-
headers,
|
|
152
|
-
body: JSON.stringify(body),
|
|
153
|
-
signal: options.abortSignal,
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
catch (error) {
|
|
157
|
-
if (options.abortSignal?.aborted)
|
|
158
|
-
throw error;
|
|
159
|
-
throw new APICallError({
|
|
160
|
-
message: `Command Code GO request failed: ${errorMessage(error)}`,
|
|
161
|
-
url,
|
|
162
|
-
requestBodyValues: body,
|
|
163
|
-
cause: error,
|
|
164
|
-
isRetryable: true,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
const responseHeaders = headersToRecord(response.headers);
|
|
168
|
-
if (!response.ok) {
|
|
169
|
-
const responseBody = await response.text().catch(() => '');
|
|
170
|
-
throw new APICallError({
|
|
171
|
-
message: `Command Code GO rejected the request (${response.status}): ${responseBody.slice(0, 500)}`,
|
|
172
|
-
url,
|
|
173
|
-
requestBodyValues: body,
|
|
174
|
-
statusCode: response.status,
|
|
175
|
-
responseHeaders,
|
|
176
|
-
responseBody,
|
|
177
|
-
isRetryable: response.status === 429 || response.status >= 500,
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
if (!response.body) {
|
|
181
|
-
throw new APICallError({
|
|
182
|
-
message: 'Command Code GO returned no body',
|
|
183
|
-
url,
|
|
184
|
-
requestBodyValues: body,
|
|
185
|
-
statusCode: response.status,
|
|
186
|
-
responseHeaders,
|
|
187
|
-
isRetryable: true,
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
const stream = response.body
|
|
191
|
-
.pipeThrough(new TextDecoderStream())
|
|
192
|
-
.pipeThrough(sseDataLines())
|
|
193
|
-
.pipeThrough(cliEventsToStreamParts({ warnings, url, body, modelId: this.modelId }));
|
|
194
|
-
return { stream, request: { body }, response: { headers: responseHeaders } };
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
const DEFAULT_WORKING_DIR = 'maka';
|
|
198
|
-
// ---------------------------------------------------------------------------
|
|
199
|
-
// Request
|
|
200
|
-
// ---------------------------------------------------------------------------
|
|
201
|
-
export function buildCommandCodeCliRequest(options, config) {
|
|
202
|
-
const warnings = [];
|
|
203
|
-
const unsupported = [
|
|
204
|
-
['topP', options.topP],
|
|
205
|
-
['topK', options.topK],
|
|
206
|
-
['presencePenalty', options.presencePenalty],
|
|
207
|
-
['frequencyPenalty', options.frequencyPenalty],
|
|
208
|
-
['stopSequences', options.stopSequences],
|
|
209
|
-
['seed', options.seed],
|
|
210
|
-
];
|
|
211
|
-
for (const [setting, value] of unsupported) {
|
|
212
|
-
if (value !== undefined)
|
|
213
|
-
warnings.push({ type: 'unsupported', feature: setting });
|
|
214
|
-
}
|
|
215
|
-
if (options.responseFormat && options.responseFormat.type !== 'text') {
|
|
216
|
-
warnings.push({ type: 'unsupported', feature: 'responseFormat' });
|
|
217
|
-
}
|
|
218
|
-
if (options.toolChoice && options.toolChoice.type !== 'auto') {
|
|
219
|
-
warnings.push({
|
|
220
|
-
type: 'unsupported',
|
|
221
|
-
feature: 'toolChoice',
|
|
222
|
-
details: 'Command Code GO always lets the model choose',
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
const { system, messages } = convertPrompt(options.prompt, warnings);
|
|
226
|
-
const tools = (options.tools ?? []).flatMap((tool) => {
|
|
227
|
-
if (tool.type !== 'function') {
|
|
228
|
-
warnings.push({ type: 'unsupported', feature: `provider tool ${tool.name}` });
|
|
229
|
-
return [];
|
|
230
|
-
}
|
|
231
|
-
return [
|
|
232
|
-
{
|
|
233
|
-
type: 'function',
|
|
234
|
-
name: tool.name,
|
|
235
|
-
description: tool.description,
|
|
236
|
-
input_schema: toolParametersSchema(tool.inputSchema),
|
|
237
|
-
},
|
|
238
|
-
];
|
|
239
|
-
});
|
|
240
|
-
const reasoningEffort = resolveReasoningEffort(options);
|
|
241
|
-
const now = config.now?.() ?? new Date();
|
|
242
|
-
const body = {
|
|
243
|
-
config: {
|
|
244
|
-
workingDir: config.workingDir ?? DEFAULT_WORKING_DIR,
|
|
245
|
-
date: now.toISOString().split('T')[0],
|
|
246
|
-
environment: `${process.platform}-${process.arch}, Node.js ${process.version}`,
|
|
247
|
-
structure: [],
|
|
248
|
-
isGitRepo: false,
|
|
249
|
-
currentBranch: '',
|
|
250
|
-
mainBranch: '',
|
|
251
|
-
gitStatus: '',
|
|
252
|
-
recentCommits: [],
|
|
253
|
-
},
|
|
254
|
-
memory: null,
|
|
255
|
-
taste: null,
|
|
256
|
-
skills: null,
|
|
257
|
-
params: {
|
|
258
|
-
model: config.modelId,
|
|
259
|
-
messages,
|
|
260
|
-
tools,
|
|
261
|
-
system,
|
|
262
|
-
max_tokens: options.maxOutputTokens ?? DEFAULT_MAX_TOKENS,
|
|
263
|
-
temperature: options.temperature ?? DEFAULT_TEMPERATURE,
|
|
264
|
-
stream: true,
|
|
265
|
-
...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),
|
|
266
|
-
},
|
|
267
|
-
threadId: config.threadId?.() ?? randomUUID(),
|
|
268
|
-
};
|
|
269
|
-
return { body, warnings };
|
|
270
|
-
}
|
|
271
|
-
function resolveReasoningEffort(options) {
|
|
272
|
-
const override = options.providerOptions?.['commandcode-cli']?.reasoningEffort;
|
|
273
|
-
if (typeof override === 'string' && override !== '')
|
|
274
|
-
return override;
|
|
275
|
-
const level = options.reasoning;
|
|
276
|
-
if (level === undefined || level === 'provider-default' || level === 'none')
|
|
277
|
-
return undefined;
|
|
278
|
-
return level;
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* The CLI wire's message shape, converted from the AI SDK prompt. System
|
|
282
|
-
* messages fold into `params.system`; only tool calls with a paired result
|
|
283
|
-
* are replayed; assistant reasoning IS replayed (the gateway rebuilds the
|
|
284
|
-
* upstream request from these blocks and DeepSeek thinking mode rejects a
|
|
285
|
-
* tool loop whose history lacks it).
|
|
286
|
-
*/
|
|
287
|
-
function convertPrompt(prompt, warnings) {
|
|
288
|
-
const systemParts = [];
|
|
289
|
-
const callIds = new Set();
|
|
290
|
-
const callNames = new Map();
|
|
291
|
-
const resultIds = new Set();
|
|
292
|
-
for (const message of prompt) {
|
|
293
|
-
if (message.role === 'assistant') {
|
|
294
|
-
for (const part of message.content) {
|
|
295
|
-
if (part.type === 'tool-call') {
|
|
296
|
-
callIds.add(part.toolCallId);
|
|
297
|
-
callNames.set(part.toolCallId, part.toolName);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
if (message.role === 'tool') {
|
|
302
|
-
for (const part of message.content) {
|
|
303
|
-
if (part.type === 'tool-result')
|
|
304
|
-
resultIds.add(part.toolCallId);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
const paired = new Set([...callIds].filter((id) => resultIds.has(id)));
|
|
309
|
-
const wireIds = wireToolCallIds(paired);
|
|
310
|
-
const messages = [];
|
|
311
|
-
for (const message of prompt) {
|
|
312
|
-
switch (message.role) {
|
|
313
|
-
case 'system':
|
|
314
|
-
systemParts.push(message.content);
|
|
315
|
-
break;
|
|
316
|
-
case 'user': {
|
|
317
|
-
const parts = [];
|
|
318
|
-
for (const part of message.content) {
|
|
319
|
-
if (part.type === 'text') {
|
|
320
|
-
parts.push({ type: 'text', text: part.text });
|
|
321
|
-
continue;
|
|
322
|
-
}
|
|
323
|
-
const image = imagePart(part, warnings);
|
|
324
|
-
if (image)
|
|
325
|
-
parts.push(image);
|
|
326
|
-
}
|
|
327
|
-
if (parts.length > 0)
|
|
328
|
-
messages.push({ role: 'user', content: parts });
|
|
329
|
-
break;
|
|
330
|
-
}
|
|
331
|
-
case 'assistant': {
|
|
332
|
-
const parts = [];
|
|
333
|
-
for (const part of message.content) {
|
|
334
|
-
if (part.type === 'text')
|
|
335
|
-
parts.push({ type: 'text', text: part.text });
|
|
336
|
-
else if (part.type === 'reasoning')
|
|
337
|
-
parts.push({ type: 'reasoning', text: part.text });
|
|
338
|
-
else if (part.type === 'tool-call' && paired.has(part.toolCallId)) {
|
|
339
|
-
parts.push({
|
|
340
|
-
type: 'tool-call',
|
|
341
|
-
toolCallId: wireIds.get(part.toolCallId) ?? part.toolCallId,
|
|
342
|
-
toolName: part.toolName,
|
|
343
|
-
input: recordOrEmpty(part.input),
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
if (parts.length > 0)
|
|
348
|
-
messages.push({ role: 'assistant', content: parts });
|
|
349
|
-
break;
|
|
350
|
-
}
|
|
351
|
-
case 'tool': {
|
|
352
|
-
for (const part of message.content) {
|
|
353
|
-
if (part.type !== 'tool-result' || !paired.has(part.toolCallId))
|
|
354
|
-
continue;
|
|
355
|
-
const { text, isError } = toolResultText(part.output, warnings);
|
|
356
|
-
messages.push({
|
|
357
|
-
role: 'tool',
|
|
358
|
-
content: [
|
|
359
|
-
{
|
|
360
|
-
type: 'tool-result',
|
|
361
|
-
toolCallId: wireIds.get(part.toolCallId) ?? part.toolCallId,
|
|
362
|
-
toolName: callNames.get(part.toolCallId) || part.toolName || 'unknown',
|
|
363
|
-
output: isError
|
|
364
|
-
? { type: 'error-text', value: text }
|
|
365
|
-
: { type: 'text', value: text },
|
|
366
|
-
},
|
|
367
|
-
],
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
break;
|
|
371
|
-
}
|
|
372
|
-
default:
|
|
373
|
-
break;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
return { system: systemParts.join('\n\n'), messages };
|
|
377
|
-
}
|
|
378
|
-
function imagePart(part, warnings) {
|
|
379
|
-
if (!part.mediaType.startsWith('image/')) {
|
|
380
|
-
warnings.push({ type: 'unsupported', feature: `file input ${part.mediaType}` });
|
|
381
|
-
return undefined;
|
|
382
|
-
}
|
|
383
|
-
if (part.data.type !== 'data') {
|
|
384
|
-
warnings.push({
|
|
385
|
-
type: 'unsupported',
|
|
386
|
-
feature: `image ${part.data.type} input`,
|
|
387
|
-
details: 'Command Code GO carries images inline only',
|
|
388
|
-
});
|
|
389
|
-
return undefined;
|
|
390
|
-
}
|
|
391
|
-
const encoded = typeof part.data.data === 'string'
|
|
392
|
-
? part.data.data
|
|
393
|
-
: Buffer.from(part.data.data).toString('base64');
|
|
394
|
-
// The pinned CLI serializes an image block in the AI SDK message shape it
|
|
395
|
-
// builds its request from — a data URL under `image`, beside `mimeType` —
|
|
396
|
-
// not Anthropic's `source` object. The rest of this wire is AI SDK shaped
|
|
397
|
-
// too (`toolCallId`, `toolName`, `input`), and for an unpublished endpoint
|
|
398
|
-
// the pinned CLI is the protocol authority.
|
|
399
|
-
return {
|
|
400
|
-
type: 'image',
|
|
401
|
-
image: encoded.startsWith('data:') ? encoded : `data:${part.mediaType};base64,${encoded}`,
|
|
402
|
-
mimeType: part.mediaType,
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
function toolResultText(output, warnings) {
|
|
406
|
-
switch (output.type) {
|
|
407
|
-
case 'text':
|
|
408
|
-
return { text: output.value, isError: false };
|
|
409
|
-
case 'error-text':
|
|
410
|
-
return { text: output.value, isError: true };
|
|
411
|
-
case 'json':
|
|
412
|
-
return { text: JSON.stringify(output.value), isError: false };
|
|
413
|
-
case 'error-json':
|
|
414
|
-
return { text: JSON.stringify(output.value), isError: true };
|
|
415
|
-
case 'execution-denied':
|
|
416
|
-
return { text: output.reason ?? 'Tool execution was denied.', isError: true };
|
|
417
|
-
case 'content': {
|
|
418
|
-
const texts = [];
|
|
419
|
-
for (const item of output.value) {
|
|
420
|
-
if (item.type === 'text')
|
|
421
|
-
texts.push(item.text);
|
|
422
|
-
else
|
|
423
|
-
warnings.push({ type: 'unsupported', feature: `tool result ${item.type}` });
|
|
424
|
-
}
|
|
425
|
-
return { text: texts.join('\n'), isError: false };
|
|
426
|
-
}
|
|
427
|
-
default:
|
|
428
|
-
return { text: '', isError: false };
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
/** Overlong paired ids get a per-request `cc-<n>` alias; the pair resolves through one map. */
|
|
432
|
-
export function wireToolCallIds(paired) {
|
|
433
|
-
const wire = new Map();
|
|
434
|
-
const taken = new Set();
|
|
435
|
-
for (const id of paired) {
|
|
436
|
-
if (id.length <= MAX_WIRE_TOOL_CALL_ID_LENGTH) {
|
|
437
|
-
wire.set(id, id);
|
|
438
|
-
taken.add(id);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
let seq = 1;
|
|
442
|
-
for (const id of paired) {
|
|
443
|
-
if (wire.has(id))
|
|
444
|
-
continue;
|
|
445
|
-
let alias = `cc-${seq++}`;
|
|
446
|
-
while (taken.has(alias))
|
|
447
|
-
alias = `cc-${seq++}`;
|
|
448
|
-
wire.set(id, alias);
|
|
449
|
-
taken.add(alias);
|
|
450
|
-
}
|
|
451
|
-
return wire;
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* The gateway validates a tool schema's root `type` against the literal
|
|
455
|
-
* string "object". Schemas generators emit as `["object","null"]`, a bare
|
|
456
|
-
* `$ref`, or a combinator get the declared type they mean.
|
|
457
|
-
*/
|
|
458
|
-
export function toolParametersSchema(parameters, depth = 0) {
|
|
459
|
-
if (!isRecord(parameters))
|
|
460
|
-
return { type: 'object', properties: {}, additionalProperties: true };
|
|
461
|
-
if (parameters.type === 'object')
|
|
462
|
-
return parameters;
|
|
463
|
-
if (Array.isArray(parameters.type) && parameters.type.includes('object')) {
|
|
464
|
-
return { ...parameters, type: 'object' };
|
|
465
|
-
}
|
|
466
|
-
if (parameters.type === undefined || parameters.type === null) {
|
|
467
|
-
if (typeof parameters.$ref === 'string' && depth < SCHEMA_NORMALIZE_MAX_DEPTH) {
|
|
468
|
-
const target = resolveLocalRef(parameters, parameters.$ref);
|
|
469
|
-
if (target !== undefined) {
|
|
470
|
-
const { $ref: _ref, ...rest } = parameters;
|
|
471
|
-
return toolParametersSchema({ ...target, ...rest }, depth + 1);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
if (isRecord(parameters.properties) || parameters.additionalProperties !== undefined) {
|
|
475
|
-
return { ...parameters, type: 'object' };
|
|
476
|
-
}
|
|
477
|
-
for (const key of ['allOf', 'anyOf', 'oneOf']) {
|
|
478
|
-
const branches = parameters[key];
|
|
479
|
-
if (Array.isArray(branches) && depth < SCHEMA_NORMALIZE_MAX_DEPTH) {
|
|
480
|
-
const merged = branches.map((branch) => toolParametersSchema(branch, depth + 1));
|
|
481
|
-
const properties = Object.assign({}, ...merged.map((b) => b.properties ?? {}));
|
|
482
|
-
const required = [
|
|
483
|
-
...new Set(merged.flatMap((b) => (Array.isArray(b.required) ? b.required : []))),
|
|
484
|
-
];
|
|
485
|
-
return {
|
|
486
|
-
type: 'object',
|
|
487
|
-
properties,
|
|
488
|
-
...(required.length > 0 ? { required } : {}),
|
|
489
|
-
additionalProperties: true,
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
if (typeof parameters.$ref === 'string')
|
|
494
|
-
return { ...parameters, type: 'object' };
|
|
495
|
-
}
|
|
496
|
-
return { type: 'object', properties: {}, additionalProperties: true };
|
|
497
|
-
}
|
|
498
|
-
function resolveLocalRef(root, ref) {
|
|
499
|
-
if (!ref.startsWith('#/'))
|
|
500
|
-
return undefined;
|
|
501
|
-
let current = root;
|
|
502
|
-
for (const segment of ref.slice(2).split('/')) {
|
|
503
|
-
if (!isRecord(current))
|
|
504
|
-
return undefined;
|
|
505
|
-
current = current[segment.replace(/~1/gu, '/').replace(/~0/gu, '~')];
|
|
506
|
-
}
|
|
507
|
-
return isRecord(current) ? current : undefined;
|
|
508
|
-
}
|
|
509
|
-
// ---------------------------------------------------------------------------
|
|
510
|
-
// Response
|
|
511
|
-
// ---------------------------------------------------------------------------
|
|
512
|
-
/** Splits SSE text into the JSON payload of each `data:` line, dropping comments and `[DONE]`. */
|
|
513
|
-
/** One SSE line's event payload, or undefined when the line carries none. */
|
|
514
|
-
export function parseCommandCodeCliEventLine(line) {
|
|
515
|
-
let trimmed = line.trim();
|
|
516
|
-
if (!trimmed || trimmed.startsWith(':') || trimmed.startsWith('event:'))
|
|
517
|
-
return undefined;
|
|
518
|
-
if (trimmed.startsWith('data:'))
|
|
519
|
-
trimmed = trimmed.slice(5).trim();
|
|
520
|
-
if (!trimmed || trimmed === '[DONE]')
|
|
521
|
-
return undefined;
|
|
522
|
-
try {
|
|
523
|
-
return JSON.parse(trimmed);
|
|
524
|
-
}
|
|
525
|
-
catch {
|
|
526
|
-
// A non-JSON line is not an event on this wire.
|
|
527
|
-
return undefined;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
/**
|
|
531
|
-
* Reads one complete CLI stream body the way {@link CommandCodeCliLanguageModel}
|
|
532
|
-
* reads it incrementally. HTTP 200 is only the handshake on this wire, so a
|
|
533
|
-
* caller that stops at the status (the connection probe) would accept a body
|
|
534
|
-
* whose first event is a rejection.
|
|
535
|
-
*/
|
|
536
|
-
export function summarizeCommandCodeCliStream(body) {
|
|
537
|
-
for (const line of body.split('\n')) {
|
|
538
|
-
const event = parseCommandCodeCliEventLine(line);
|
|
539
|
-
if (!isRecord(event))
|
|
540
|
-
continue;
|
|
541
|
-
if (event.type === 'error') {
|
|
542
|
-
const { message, statusCode } = streamErrorFacts(event);
|
|
543
|
-
return {
|
|
544
|
-
finished: false,
|
|
545
|
-
error: { message, ...(statusCode === undefined ? {} : { statusCode }) },
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
if (event.type === 'finish')
|
|
549
|
-
return { finished: true };
|
|
550
|
-
}
|
|
551
|
-
return { finished: false };
|
|
552
|
-
}
|
|
553
|
-
function sseDataLines() {
|
|
554
|
-
let buffer = '';
|
|
555
|
-
const emit = (line, controller) => {
|
|
556
|
-
const event = parseCommandCodeCliEventLine(line);
|
|
557
|
-
if (event !== undefined)
|
|
558
|
-
controller.enqueue(event);
|
|
559
|
-
};
|
|
560
|
-
return new TransformStream({
|
|
561
|
-
transform(chunk, controller) {
|
|
562
|
-
buffer += chunk;
|
|
563
|
-
const lines = buffer.split('\n');
|
|
564
|
-
buffer = lines.pop() ?? '';
|
|
565
|
-
for (const line of lines)
|
|
566
|
-
emit(line, controller);
|
|
567
|
-
},
|
|
568
|
-
flush(controller) {
|
|
569
|
-
if (buffer)
|
|
570
|
-
emit(buffer, controller);
|
|
571
|
-
},
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
function cliEventsToStreamParts(input) {
|
|
575
|
-
let textId;
|
|
576
|
-
let reasoningId;
|
|
577
|
-
let nextId = 0;
|
|
578
|
-
let finished = false;
|
|
579
|
-
// The wire splits one call's usage across two events: `finish-step` carries
|
|
580
|
-
// the provider's OWN body (`prompt_tokens`, `prompt_cache_hit_tokens`, …)
|
|
581
|
-
// under `usage.raw`, and `finish` carries only the normalized totals. The
|
|
582
|
-
// provider keys are what telemetry's cache accounting reads, so keep the
|
|
583
|
-
// body here until the finish event can carry it out.
|
|
584
|
-
let providerUsageRaw;
|
|
585
|
-
const closeText = (controller) => {
|
|
586
|
-
if (textId === undefined)
|
|
587
|
-
return;
|
|
588
|
-
controller.enqueue({ type: 'text-end', id: textId });
|
|
589
|
-
textId = undefined;
|
|
590
|
-
};
|
|
591
|
-
const closeReasoning = (controller) => {
|
|
592
|
-
if (reasoningId === undefined)
|
|
593
|
-
return;
|
|
594
|
-
controller.enqueue({ type: 'reasoning-end', id: reasoningId });
|
|
595
|
-
reasoningId = undefined;
|
|
596
|
-
};
|
|
597
|
-
return new TransformStream({
|
|
598
|
-
start(controller) {
|
|
599
|
-
controller.enqueue({ type: 'stream-start', warnings: input.warnings });
|
|
600
|
-
controller.enqueue({ type: 'response-metadata', modelId: input.modelId });
|
|
601
|
-
},
|
|
602
|
-
transform(event, controller) {
|
|
603
|
-
if (!isRecord(event) || finished)
|
|
604
|
-
return;
|
|
605
|
-
switch (event.type) {
|
|
606
|
-
case 'text-delta': {
|
|
607
|
-
closeReasoning(controller);
|
|
608
|
-
if (textId === undefined) {
|
|
609
|
-
textId = `text-${nextId++}`;
|
|
610
|
-
controller.enqueue({ type: 'text-start', id: textId });
|
|
611
|
-
}
|
|
612
|
-
controller.enqueue({
|
|
613
|
-
type: 'text-delta',
|
|
614
|
-
id: textId,
|
|
615
|
-
delta: stringValue(event.text) ?? '',
|
|
616
|
-
});
|
|
617
|
-
break;
|
|
618
|
-
}
|
|
619
|
-
case 'text-end':
|
|
620
|
-
closeText(controller);
|
|
621
|
-
break;
|
|
622
|
-
case 'reasoning-start':
|
|
623
|
-
closeText(controller);
|
|
624
|
-
break;
|
|
625
|
-
case 'reasoning-delta': {
|
|
626
|
-
closeText(controller);
|
|
627
|
-
if (reasoningId === undefined) {
|
|
628
|
-
reasoningId = `reasoning-${nextId++}`;
|
|
629
|
-
controller.enqueue({ type: 'reasoning-start', id: reasoningId });
|
|
630
|
-
}
|
|
631
|
-
controller.enqueue({
|
|
632
|
-
type: 'reasoning-delta',
|
|
633
|
-
id: reasoningId,
|
|
634
|
-
delta: stringValue(event.text) ?? '',
|
|
635
|
-
});
|
|
636
|
-
break;
|
|
637
|
-
}
|
|
638
|
-
case 'reasoning-end':
|
|
639
|
-
closeReasoning(controller);
|
|
640
|
-
break;
|
|
641
|
-
case 'tool-call': {
|
|
642
|
-
closeText(controller);
|
|
643
|
-
closeReasoning(controller);
|
|
644
|
-
const id = stringValue(event.toolCallId) ?? randomUUID();
|
|
645
|
-
const toolName = stringValue(event.toolName) ?? '';
|
|
646
|
-
const args = JSON.stringify(recordOrEmpty(event.input ?? event.args ?? event.arguments));
|
|
647
|
-
controller.enqueue({ type: 'tool-input-start', id, toolName });
|
|
648
|
-
controller.enqueue({ type: 'tool-input-delta', id, delta: args });
|
|
649
|
-
controller.enqueue({ type: 'tool-input-end', id });
|
|
650
|
-
controller.enqueue({ type: 'tool-call', toolCallId: id, toolName, input: args });
|
|
651
|
-
break;
|
|
652
|
-
}
|
|
653
|
-
case 'finish-step': {
|
|
654
|
-
// Carries the provider's own usage body; `finish` normalizes it away.
|
|
655
|
-
const usage = isRecord(event.usage) ? event.usage : undefined;
|
|
656
|
-
if (isRecord(usage?.raw))
|
|
657
|
-
providerUsageRaw = usage.raw;
|
|
658
|
-
break;
|
|
659
|
-
}
|
|
660
|
-
case 'finish': {
|
|
661
|
-
closeText(controller);
|
|
662
|
-
closeReasoning(controller);
|
|
663
|
-
finished = true;
|
|
664
|
-
controller.enqueue({
|
|
665
|
-
type: 'finish',
|
|
666
|
-
usage: usageFromEvent(event.totalUsage, providerUsageRaw),
|
|
667
|
-
finishReason: mapFinishReason(event.finishReason),
|
|
668
|
-
});
|
|
669
|
-
break;
|
|
670
|
-
}
|
|
671
|
-
case 'error': {
|
|
672
|
-
closeText(controller);
|
|
673
|
-
closeReasoning(controller);
|
|
674
|
-
finished = true;
|
|
675
|
-
controller.enqueue({ type: 'error', error: streamErrorToApiCallError(event, input) });
|
|
676
|
-
break;
|
|
677
|
-
}
|
|
678
|
-
default:
|
|
679
|
-
break;
|
|
680
|
-
}
|
|
681
|
-
},
|
|
682
|
-
flush(controller) {
|
|
683
|
-
closeText(controller);
|
|
684
|
-
closeReasoning(controller);
|
|
685
|
-
if (!finished) {
|
|
686
|
-
// The connection closed before the wire said it was done: report the
|
|
687
|
-
// truncation instead of a clean stop so the caller can retry.
|
|
688
|
-
controller.enqueue({
|
|
689
|
-
type: 'error',
|
|
690
|
-
error: new APICallError({
|
|
691
|
-
message: 'Command Code GO stream ended without a finish event',
|
|
692
|
-
url: input.url,
|
|
693
|
-
requestBodyValues: input.body,
|
|
694
|
-
isRetryable: true,
|
|
695
|
-
}),
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
},
|
|
699
|
-
});
|
|
700
|
-
}
|
|
701
|
-
function usageFromEvent(value, providerRaw) {
|
|
702
|
-
if (!isRecord(value))
|
|
703
|
-
return emptyUsage();
|
|
704
|
-
const inputDetails = isRecord(value.inputTokenDetails) ? value.inputTokenDetails : undefined;
|
|
705
|
-
const outputDetails = isRecord(value.outputTokenDetails) ? value.outputTokenDetails : undefined;
|
|
706
|
-
const total = numberValue(value.inputTokens);
|
|
707
|
-
const cacheRead = numberValue(inputDetails?.cacheReadTokens);
|
|
708
|
-
const cacheWrite = numberValue(inputDetails?.cacheWriteTokens);
|
|
709
|
-
const noCache = numberValue(inputDetails?.noCacheTokens) ??
|
|
710
|
-
(total === undefined ? undefined : Math.max(0, total - (cacheRead ?? 0) - (cacheWrite ?? 0)));
|
|
711
|
-
return {
|
|
712
|
-
inputTokens: { total, noCache, cacheRead, cacheWrite },
|
|
713
|
-
outputTokens: {
|
|
714
|
-
total: numberValue(value.outputTokens),
|
|
715
|
-
text: undefined,
|
|
716
|
-
reasoning: numberValue(outputDetails?.reasoningTokens),
|
|
717
|
-
},
|
|
718
|
-
// `raw` must stay the PROVIDER's own usage body, which is where the
|
|
719
|
-
// OpenAI/Anthropic cache keys live (`prompt_tokens`,
|
|
720
|
-
// `prompt_tokens_details.cached_tokens`, `cache_read_input_tokens`, …).
|
|
721
|
-
// Only `finish-step` carries that body; `finish.totalUsage` is already
|
|
722
|
-
// normalized, so carrying the envelope here left telemetry's strict reader
|
|
723
|
-
// looking for `prompt_tokens` on an object that had only `inputTokens` —
|
|
724
|
-
// every attempt settled as `usageBasis: 'missing'` and the composer's
|
|
725
|
-
// context gauge never moved.
|
|
726
|
-
//
|
|
727
|
-
// The fallback keeps a body-less event readable on the normalized keys
|
|
728
|
-
// rather than handing telemetry `undefined`.
|
|
729
|
-
raw: (providerRaw ?? value),
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
function emptyUsage() {
|
|
733
|
-
return {
|
|
734
|
-
inputTokens: {
|
|
735
|
-
total: undefined,
|
|
736
|
-
noCache: undefined,
|
|
737
|
-
cacheRead: undefined,
|
|
738
|
-
cacheWrite: undefined,
|
|
739
|
-
},
|
|
740
|
-
outputTokens: { total: undefined, text: undefined, reasoning: undefined },
|
|
741
|
-
};
|
|
742
|
-
}
|
|
743
|
-
export function mapFinishReason(reason) {
|
|
744
|
-
const raw = stringValue(reason);
|
|
745
|
-
if (raw === 'tool-calls' || raw === 'tool_calls')
|
|
746
|
-
return { unified: 'tool-calls', raw };
|
|
747
|
-
if (raw === 'length' ||
|
|
748
|
-
raw === 'max_tokens' ||
|
|
749
|
-
raw === 'max-tokens' ||
|
|
750
|
-
raw === 'max_output_tokens') {
|
|
751
|
-
return { unified: 'length', raw };
|
|
752
|
-
}
|
|
753
|
-
if (raw === 'content-filter' || raw === 'content_filter')
|
|
754
|
-
return { unified: 'content-filter', raw };
|
|
755
|
-
if (raw === 'error')
|
|
756
|
-
return { unified: 'error', raw };
|
|
757
|
-
return { unified: 'stop', raw };
|
|
758
|
-
}
|
|
759
|
-
/**
|
|
760
|
-
* An in-band `error` event becomes the same error shape a rejected HTTP
|
|
761
|
-
* response produces, so the runtime's provider-error classification reads
|
|
762
|
-
* status and structured code from one place.
|
|
763
|
-
*/
|
|
764
|
-
/** What one in-band `error` event states, shared by the stream and the probe. */
|
|
765
|
-
function streamErrorFacts(event) {
|
|
766
|
-
const detail = isRecord(event.error) ? event.error : undefined;
|
|
767
|
-
const message = stringValue(detail?.message) ??
|
|
768
|
-
stringValue(event.message) ??
|
|
769
|
-
(detail ? JSON.stringify(detail) : stringValue(event.error)) ??
|
|
770
|
-
'Stream error';
|
|
771
|
-
const statusCode = numberValue(detail?.statusCode) ?? numberValue(detail?.status);
|
|
772
|
-
return {
|
|
773
|
-
message,
|
|
774
|
-
...(statusCode !== undefined ? { statusCode } : {}),
|
|
775
|
-
...(detail !== undefined ? { detail } : {}),
|
|
776
|
-
...(typeof detail?.isRetryable === 'boolean' ? { explicitRetryable: detail.isRetryable } : {}),
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
|
-
function streamErrorToApiCallError(event, input) {
|
|
780
|
-
const { message, statusCode, detail, explicitRetryable } = streamErrorFacts(event);
|
|
781
|
-
const isRetryable = explicitRetryable ??
|
|
782
|
-
(statusCode !== undefined ? statusCode === 429 || statusCode >= 500 : false);
|
|
783
|
-
return new APICallError({
|
|
784
|
-
message: `Command Code GO stream error: ${message}`,
|
|
785
|
-
url: input.url,
|
|
786
|
-
requestBodyValues: input.body,
|
|
787
|
-
...(statusCode !== undefined ? { statusCode } : {}),
|
|
788
|
-
responseBody: JSON.stringify({ error: detail ?? { message } }),
|
|
789
|
-
isRetryable,
|
|
790
|
-
data: detail,
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
|
-
// ---------------------------------------------------------------------------
|
|
794
|
-
// Small helpers
|
|
795
|
-
// ---------------------------------------------------------------------------
|
|
796
|
-
function isRecord(value) {
|
|
797
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
798
|
-
}
|
|
799
|
-
function stringValue(value) {
|
|
800
|
-
return typeof value === 'string' ? value : undefined;
|
|
801
|
-
}
|
|
802
|
-
function numberValue(value) {
|
|
803
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
804
|
-
}
|
|
805
|
-
function recordOrEmpty(value) {
|
|
806
|
-
if (isRecord(value))
|
|
807
|
-
return value;
|
|
808
|
-
if (typeof value === 'string') {
|
|
809
|
-
try {
|
|
810
|
-
const parsed = JSON.parse(value);
|
|
811
|
-
if (isRecord(parsed))
|
|
812
|
-
return parsed;
|
|
813
|
-
}
|
|
814
|
-
catch {
|
|
815
|
-
// Not JSON: the model produced no usable arguments.
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
return {};
|
|
819
|
-
}
|
|
820
|
-
function definedHeaders(headers) {
|
|
821
|
-
const out = {};
|
|
822
|
-
for (const [key, value] of Object.entries(headers ?? {})) {
|
|
823
|
-
if (value !== undefined)
|
|
824
|
-
out[key] = value;
|
|
825
|
-
}
|
|
826
|
-
return out;
|
|
827
|
-
}
|
|
828
|
-
function headersToRecord(headers) {
|
|
829
|
-
const out = {};
|
|
830
|
-
headers.forEach((value, key) => {
|
|
831
|
-
out[key] = value;
|
|
832
|
-
});
|
|
833
|
-
return out;
|
|
834
|
-
}
|
|
835
|
-
function errorMessage(error) {
|
|
836
|
-
return error instanceof Error ? error.message : String(error);
|
|
837
|
-
}
|