pi-commandcode-provider 0.5.1 → 0.6.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/CHANGELOG.md +30 -0
- package/CONTRIBUTING.md +10 -0
- package/README.md +38 -12
- package/index.ts +72 -36
- package/package.json +13 -4
- package/scripts/live-e2e-profile.mjs +87 -0
- package/scripts/pi-authenticated.mjs +1 -0
- package/scripts/pi-isolated.mjs +1 -0
- package/src/api-key.ts +69 -0
- package/src/auth-server.ts +7 -0
- package/src/commandcode-catalog.ts +141 -0
- package/src/converters.ts +72 -16
- package/src/core.ts +117 -23
- package/src/models.ts +50 -97
- package/src/oauth.ts +84 -22
- package/src/pricing.ts +53 -45
- package/src/quota-command.ts +66 -0
- package/src/quota-format.ts +111 -0
- package/src/quota-types.ts +47 -0
- package/src/quota.ts +335 -0
- package/src/runtime.ts +10 -5
- package/src/transport.ts +140 -0
- package/src/types.ts +9 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export const COMMAND_CODE_CLI_VERSION = "1.32.2"
|
|
2
|
+
|
|
3
|
+
export type CommandCodeInputType = "text" | "image"
|
|
4
|
+
export type CommandCodeReasoningEffort = "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generated from command-code@1.32.2 by `npm run sync:commandcode-catalog`.
|
|
8
|
+
* Do not edit manually.
|
|
9
|
+
*/
|
|
10
|
+
export const MODEL_INPUT_MODALITIES: Readonly<Record<string, readonly CommandCodeInputType[]>> = {
|
|
11
|
+
"claude-fable-5": ["text", "image"],
|
|
12
|
+
"claude-haiku-4-5-20251001": ["text", "image"],
|
|
13
|
+
"claude-opus-4-7": ["text", "image"],
|
|
14
|
+
"claude-opus-4-8": ["text", "image"],
|
|
15
|
+
"claude-opus-5": ["text", "image"],
|
|
16
|
+
"claude-sonnet-4-6": ["text", "image"],
|
|
17
|
+
"claude-sonnet-5": ["text", "image"],
|
|
18
|
+
"deepseek/deepseek-v4-flash-vision-exp": ["text", "image"],
|
|
19
|
+
"google/gemini-3.1-flash-lite": ["text", "image"],
|
|
20
|
+
"google/gemini-3.5-flash": ["text", "image"],
|
|
21
|
+
"google/gemini-3.5-flash-lite": ["text", "image"],
|
|
22
|
+
"google/gemini-3.6-flash": ["text", "image"],
|
|
23
|
+
"google/gemini-3.7-flash": ["text", "image"],
|
|
24
|
+
"gpt-5.3-codex": ["text", "image"],
|
|
25
|
+
"gpt-5.4": ["text", "image"],
|
|
26
|
+
"gpt-5.4-mini": ["text", "image"],
|
|
27
|
+
"gpt-5.5": ["text", "image"],
|
|
28
|
+
"gpt-5.6-luna": ["text", "image"],
|
|
29
|
+
"gpt-5.6-sol": ["text", "image"],
|
|
30
|
+
"gpt-5.6-terra": ["text", "image"],
|
|
31
|
+
"meta/muse-spark-1.1": ["text", "image"],
|
|
32
|
+
"meta/muse-spark-1.2": ["text", "image"],
|
|
33
|
+
"meta/muse-spark-1.2-contributor": ["text", "image"],
|
|
34
|
+
"MiniMaxAI/MiniMax-M3": ["text", "image"],
|
|
35
|
+
"moonshotai/Kimi-K2.5": ["text", "image"],
|
|
36
|
+
"moonshotai/Kimi-K2.6": ["text", "image"],
|
|
37
|
+
"moonshotai/Kimi-K2.7-Code": ["text", "image"],
|
|
38
|
+
"moonshotai/Kimi-K2.7-Code-Highspeed": ["text", "image"],
|
|
39
|
+
"moonshotai/Kimi-K3": ["text", "image"],
|
|
40
|
+
"Qwen/Qwen3.6-Plus": ["text", "image"],
|
|
41
|
+
"Qwen/Qwen3.7-Flash": ["text", "image"],
|
|
42
|
+
"Qwen/Qwen3.7-Plus": ["text", "image"],
|
|
43
|
+
"Qwen/Qwen3.8-27B": ["text", "image"],
|
|
44
|
+
"Qwen/Qwen3.8-Max": ["text", "image"],
|
|
45
|
+
"sakana/fugu-ultra": ["text", "image"],
|
|
46
|
+
"stealth/ox-alpha": ["text", "image"],
|
|
47
|
+
"stepfun/Step-3.7-Flash": ["text", "image"],
|
|
48
|
+
"thinkingmachines/inkling": ["text", "image"],
|
|
49
|
+
"thinkingmachines/inkling-small": ["text", "image"],
|
|
50
|
+
"xai/grok-4.5": ["text", "image"],
|
|
51
|
+
"xiaomi/mimo-v2.5": ["text", "image"],
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const MODEL_REASONING: Readonly<Record<string, true>> = {
|
|
55
|
+
"claude-fable-5": true,
|
|
56
|
+
"claude-opus-4-7": true,
|
|
57
|
+
"claude-opus-4-8": true,
|
|
58
|
+
"claude-opus-5": true,
|
|
59
|
+
"claude-sonnet-4-6": true,
|
|
60
|
+
"claude-sonnet-5": true,
|
|
61
|
+
"deepseek/deepseek-v4-flash": true,
|
|
62
|
+
"deepseek/deepseek-v4-flash-vision-exp": true,
|
|
63
|
+
"deepseek/deepseek-v4-pro": true,
|
|
64
|
+
"google/gemini-3.1-flash-lite": true,
|
|
65
|
+
"google/gemini-3.5-flash": true,
|
|
66
|
+
"google/gemini-3.5-flash-lite": true,
|
|
67
|
+
"google/gemini-3.6-flash": true,
|
|
68
|
+
"google/gemini-3.7-flash": true,
|
|
69
|
+
"gpt-5.3-codex": true,
|
|
70
|
+
"gpt-5.4": true,
|
|
71
|
+
"gpt-5.4-mini": true,
|
|
72
|
+
"gpt-5.5": true,
|
|
73
|
+
"gpt-5.6-luna": true,
|
|
74
|
+
"gpt-5.6-sol": true,
|
|
75
|
+
"gpt-5.6-terra": true,
|
|
76
|
+
"meta/muse-spark-1.1": true,
|
|
77
|
+
"meta/muse-spark-1.2": true,
|
|
78
|
+
"meta/muse-spark-1.2-contributor": true,
|
|
79
|
+
"MiniMaxAI/MiniMax-M3": true,
|
|
80
|
+
"moonshotai/Kimi-K2.7-Code": true,
|
|
81
|
+
"moonshotai/Kimi-K2.7-Code-Highspeed": true,
|
|
82
|
+
"moonshotai/Kimi-K3": true,
|
|
83
|
+
"nvidia/nemotron-3-ultra-550b-a55b": true,
|
|
84
|
+
"poolside/laguna-s-2.1-free": true,
|
|
85
|
+
"Qwen/Qwen3.6-Max-Preview": true,
|
|
86
|
+
"Qwen/Qwen3.6-Plus": true,
|
|
87
|
+
"Qwen/Qwen3.7-Flash": true,
|
|
88
|
+
"Qwen/Qwen3.7-Max": true,
|
|
89
|
+
"Qwen/Qwen3.7-Plus": true,
|
|
90
|
+
"Qwen/Qwen3.8-27B": true,
|
|
91
|
+
"Qwen/Qwen3.8-Max": true,
|
|
92
|
+
"sakana/fugu-ultra": true,
|
|
93
|
+
"stealth/ox-alpha": true,
|
|
94
|
+
"stepfun/Step-3.5-Flash": true,
|
|
95
|
+
"stepfun/Step-3.7-Flash": true,
|
|
96
|
+
"tencent/hy3-paid": true,
|
|
97
|
+
"thinkingmachines/inkling": true,
|
|
98
|
+
"thinkingmachines/inkling-small": true,
|
|
99
|
+
"xai/grok-4.5": true,
|
|
100
|
+
"xai/grok-4.6": true,
|
|
101
|
+
"zai-org/GLM-5.2": true,
|
|
102
|
+
"zai-org/GLM-5.3": true,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const MODEL_EFFORTS: Readonly<Record<string, readonly CommandCodeReasoningEffort[]>> = {
|
|
106
|
+
"claude-fable-5": ["low", "medium", "high", "xhigh", "max"],
|
|
107
|
+
"claude-opus-4-7": ["low", "medium", "high", "xhigh", "max"],
|
|
108
|
+
"claude-opus-4-8": ["low", "medium", "high", "xhigh", "max"],
|
|
109
|
+
"claude-opus-5": ["low", "medium", "high", "xhigh", "max"],
|
|
110
|
+
"claude-sonnet-4-6": ["low", "medium", "high", "xhigh", "max"],
|
|
111
|
+
"claude-sonnet-5": ["low", "medium", "high", "xhigh", "max"],
|
|
112
|
+
"deepseek/deepseek-v4-flash": ["high", "max"],
|
|
113
|
+
"deepseek/deepseek-v4-flash-vision-exp": ["high", "max"],
|
|
114
|
+
"deepseek/deepseek-v4-pro": ["high", "max"],
|
|
115
|
+
"google/gemini-3.1-flash-lite": ["low", "medium", "high"],
|
|
116
|
+
"google/gemini-3.5-flash": ["low", "medium", "high"],
|
|
117
|
+
"google/gemini-3.5-flash-lite": ["low", "medium", "high"],
|
|
118
|
+
"google/gemini-3.6-flash": ["low", "medium", "high"],
|
|
119
|
+
"google/gemini-3.7-flash": ["low", "medium", "high"],
|
|
120
|
+
"gpt-5.3-codex": ["low", "medium", "high", "xhigh"],
|
|
121
|
+
"gpt-5.4": ["low", "medium", "high", "xhigh"],
|
|
122
|
+
"gpt-5.4-mini": ["low", "medium", "high"],
|
|
123
|
+
"gpt-5.5": ["low", "medium", "high", "xhigh"],
|
|
124
|
+
"gpt-5.6-luna": ["low", "medium", "high", "xhigh", "max"],
|
|
125
|
+
"gpt-5.6-sol": ["low", "medium", "high", "xhigh", "max"],
|
|
126
|
+
"gpt-5.6-terra": ["low", "medium", "high", "xhigh", "max"],
|
|
127
|
+
"Qwen/Qwen3.8-27B": ["low", "medium", "xhigh"],
|
|
128
|
+
"Qwen/Qwen3.8-Max": ["low", "medium", "xhigh"],
|
|
129
|
+
"sakana/fugu-ultra": ["high", "xhigh"],
|
|
130
|
+
"stealth/ox-alpha": ["low", "high", "max"],
|
|
131
|
+
"xai/grok-4.5": ["low", "medium", "high"],
|
|
132
|
+
"xai/grok-4.6": ["low", "medium", "high", "xhigh"],
|
|
133
|
+
"zai-org/GLM-5.2": ["high", "max"],
|
|
134
|
+
"zai-org/GLM-5.3": ["low", "high", "max"],
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const MODEL_MAX_OUTPUT_TOKENS: Readonly<Record<string, number>> = {
|
|
138
|
+
"poolside/laguna-s-2.1-free": 32_768,
|
|
139
|
+
"Qwen/Qwen3.8-27B": 32_768,
|
|
140
|
+
"stealth/ox-alpha": 131_072,
|
|
141
|
+
}
|
package/src/converters.ts
CHANGED
|
@@ -66,9 +66,8 @@ function imageContentError(role: string): Error {
|
|
|
66
66
|
|
|
67
67
|
export function assertTextOnlyMessages(messages?: readonly MessageLike[]): void {
|
|
68
68
|
for (const message of messages ?? []) {
|
|
69
|
-
if (imageParts(message.content).length > 0) {
|
|
70
|
-
|
|
71
|
-
throw imageContentError(role)
|
|
69
|
+
if (message.role !== "toolResult" && imageParts(message.content).length > 0) {
|
|
70
|
+
throw imageContentError(`${message.role} messages`)
|
|
72
71
|
}
|
|
73
72
|
}
|
|
74
73
|
}
|
|
@@ -107,6 +106,7 @@ export function getApiKey(
|
|
|
107
106
|
} = {},
|
|
108
107
|
): string | undefined {
|
|
109
108
|
const env = options.env ?? process.env
|
|
109
|
+
if (env.COMMAND_CODE_API_KEY) return env.COMMAND_CODE_API_KEY
|
|
110
110
|
if (env.COMMANDCODE_API_KEY) return env.COMMANDCODE_API_KEY
|
|
111
111
|
|
|
112
112
|
const home = options.homeDir?.() ?? homedir()
|
|
@@ -138,7 +138,41 @@ export function getApiKey(
|
|
|
138
138
|
return undefined
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
// Hosts such as OMP may pass a literal env-var name as the "resolved" registry
|
|
142
|
+
// key instead of the actual credential. Treat those as unresolved.
|
|
143
|
+
export const COMMAND_CODE_PLACEHOLDER_KEYS = new Set([
|
|
144
|
+
"$COMMAND_CODE_API_KEY",
|
|
145
|
+
"COMMAND_CODE_API_KEY",
|
|
146
|
+
"$COMMANDCODE_API_KEY",
|
|
147
|
+
"COMMANDCODE_API_KEY",
|
|
148
|
+
])
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Pick the real API key from a host registry value and/or the env/auth-file
|
|
152
|
+
* fallback, never returning a literal placeholder or an empty/whitespace value.
|
|
153
|
+
* Pure/testable.
|
|
154
|
+
*/
|
|
155
|
+
export function pickCommandCodeApiKey(
|
|
156
|
+
registryKey: string | undefined,
|
|
157
|
+
hostKey: string | undefined,
|
|
158
|
+
): string | undefined {
|
|
159
|
+
const trimmed = typeof registryKey === "string" ? registryKey.trim() : undefined
|
|
160
|
+
if (!trimmed) return hostKey
|
|
161
|
+
if (COMMAND_CODE_PLACEHOLDER_KEYS.has(trimmed)) return hostKey
|
|
162
|
+
return trimmed
|
|
163
|
+
}
|
|
164
|
+
|
|
141
165
|
export function textContent(message: { content?: unknown }): string {
|
|
166
|
+
if (typeof message.content === "string") return message.content
|
|
167
|
+
if (message.content === null || message.content === undefined) return ""
|
|
168
|
+
if (!Array.isArray(message.content)) {
|
|
169
|
+
try {
|
|
170
|
+
return JSON.stringify(message.content) ?? String(message.content)
|
|
171
|
+
} catch {
|
|
172
|
+
return String(message.content)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
142
176
|
return recordArray(message.content)
|
|
143
177
|
.filter((part) => part.type === "text")
|
|
144
178
|
.map((part) => stringValue(part.text) ?? "")
|
|
@@ -159,7 +193,12 @@ export function toolsToJson(tools?: readonly ToolLike[]): unknown[] {
|
|
|
159
193
|
}))
|
|
160
194
|
}
|
|
161
195
|
|
|
162
|
-
|
|
196
|
+
interface ToolCallState {
|
|
197
|
+
callIds: ReadonlySet<string>
|
|
198
|
+
resultIds: ReadonlySet<string>
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function toolCallState(messages?: readonly MessageLike[]): ToolCallState {
|
|
163
202
|
const callIds = new Set<string>()
|
|
164
203
|
const resultIds = new Set<string>()
|
|
165
204
|
|
|
@@ -171,12 +210,12 @@ function completeToolCallIds(messages?: readonly MessageLike[]): Set<string> {
|
|
|
171
210
|
if (id) callIds.add(id)
|
|
172
211
|
}
|
|
173
212
|
}
|
|
174
|
-
} else if (message.role === "toolResult") {
|
|
175
|
-
|
|
213
|
+
} else if (message.role === "toolResult" && message.toolCallId) {
|
|
214
|
+
resultIds.add(message.toolCallId)
|
|
176
215
|
}
|
|
177
216
|
}
|
|
178
217
|
|
|
179
|
-
return
|
|
218
|
+
return { callIds, resultIds }
|
|
180
219
|
}
|
|
181
220
|
|
|
182
221
|
export function messagesToCC(
|
|
@@ -187,7 +226,7 @@ export function messagesToCC(
|
|
|
187
226
|
if (!allowImages) assertTextOnlyMessages(messages)
|
|
188
227
|
|
|
189
228
|
const out: unknown[] = []
|
|
190
|
-
const
|
|
229
|
+
const { callIds, resultIds } = toolCallState(messages)
|
|
191
230
|
|
|
192
231
|
for (const message of messages ?? []) {
|
|
193
232
|
if (message.role === "user") {
|
|
@@ -197,23 +236,42 @@ export function messagesToCC(
|
|
|
197
236
|
})
|
|
198
237
|
} else if (message.role === "assistant") {
|
|
199
238
|
const parts: unknown[] = []
|
|
239
|
+
const missingResults: unknown[] = []
|
|
200
240
|
for (const content of recordArray(message.content)) {
|
|
201
241
|
if (content.type === "text") {
|
|
202
242
|
parts.push({ type: "text", text: stringValue(content.text) ?? "" })
|
|
203
243
|
} else if (content.type === "toolCall") {
|
|
204
244
|
const toolCallId = stringValue(content.id) ?? ""
|
|
205
|
-
|
|
245
|
+
const toolName = stringValue(content.name) ?? ""
|
|
246
|
+
if (!toolCallId) continue
|
|
206
247
|
parts.push({
|
|
207
248
|
type: "tool-call",
|
|
208
249
|
toolCallId,
|
|
209
|
-
toolName
|
|
250
|
+
toolName,
|
|
210
251
|
input: recordOrEmpty(content.arguments),
|
|
211
252
|
})
|
|
253
|
+
if (!resultIds.has(toolCallId)) {
|
|
254
|
+
missingResults.push({
|
|
255
|
+
type: "tool-result",
|
|
256
|
+
toolCallId,
|
|
257
|
+
toolName,
|
|
258
|
+
output: {
|
|
259
|
+
type: "error-text",
|
|
260
|
+
value: "No result — the tool call did not complete (interrupted or lost).",
|
|
261
|
+
},
|
|
262
|
+
})
|
|
263
|
+
}
|
|
212
264
|
}
|
|
213
265
|
}
|
|
214
266
|
if (parts.length > 0) out.push({ role: "assistant", content: parts })
|
|
267
|
+
if (missingResults.length > 0) out.push({ role: "tool", content: missingResults })
|
|
215
268
|
} else if (message.role === "toolResult") {
|
|
216
|
-
if (!message.toolCallId || !
|
|
269
|
+
if (!message.toolCallId || !callIds.has(message.toolCallId)) continue
|
|
270
|
+
const images = imageParts(message.content)
|
|
271
|
+
const text = textContent(message)
|
|
272
|
+
const outputText =
|
|
273
|
+
text ||
|
|
274
|
+
(images.length > 0 && !allowImages ? "[Image omitted: model does not support images]" : "")
|
|
217
275
|
out.push({
|
|
218
276
|
role: "tool",
|
|
219
277
|
content: [
|
|
@@ -222,15 +280,13 @@ export function messagesToCC(
|
|
|
222
280
|
toolCallId: message.toolCallId,
|
|
223
281
|
toolName: message.toolName,
|
|
224
282
|
output: message.isError
|
|
225
|
-
? { type: "error-text", value:
|
|
226
|
-
: { type: "text", value:
|
|
283
|
+
? { type: "error-text", value: outputText }
|
|
284
|
+
: { type: "text", value: outputText },
|
|
227
285
|
},
|
|
228
286
|
],
|
|
229
287
|
})
|
|
230
288
|
|
|
231
|
-
|
|
232
|
-
if (images.length > 0) {
|
|
233
|
-
if (!allowImages) throw imageContentError("tool results")
|
|
289
|
+
if (images.length > 0 && allowImages) {
|
|
234
290
|
out.push({
|
|
235
291
|
role: "user",
|
|
236
292
|
content: images.map(imageToCommandCode),
|
package/src/core.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { randomUUID } from "node:crypto"
|
|
9
9
|
|
|
10
|
+
import { COMMAND_CODE_CLI_VERSION } from "./commandcode-catalog.ts"
|
|
10
11
|
import { commandCodeErrorMessage, redactCommandCodeErrorText } from "./overflow.ts"
|
|
11
12
|
import { modelSupportsImageInput } from "./models.ts"
|
|
12
13
|
import {
|
|
@@ -43,7 +44,7 @@ export * from "./overflow.ts"
|
|
|
43
44
|
export * from "./types.ts"
|
|
44
45
|
|
|
45
46
|
export const DEFAULT_API_BASE = "https://api.commandcode.ai"
|
|
46
|
-
export
|
|
47
|
+
export { COMMAND_CODE_CLI_VERSION }
|
|
47
48
|
|
|
48
49
|
const DEFAULT_GENERATE_MAX_TOKENS = 64_000
|
|
49
50
|
const DEFAULT_MAX_RETRIES = 0
|
|
@@ -147,6 +148,10 @@ function mappedReasoningEffort(model: ModelLike, options?: StreamOptions): strin
|
|
|
147
148
|
return typeof mapped === "string" && mapped !== "off" ? mapped : undefined
|
|
148
149
|
}
|
|
149
150
|
|
|
151
|
+
function isUuid(value: string): boolean {
|
|
152
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)
|
|
153
|
+
}
|
|
154
|
+
|
|
150
155
|
export function projectSlugFromPath(pathName: string): string {
|
|
151
156
|
const slug = pathName
|
|
152
157
|
.toLowerCase()
|
|
@@ -231,17 +236,15 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
231
236
|
const stream = deps.createStream()
|
|
232
237
|
|
|
233
238
|
async function run() {
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
+
// Some hosts pass a literal env-var reference instead of resolving it.
|
|
240
|
+
const PLACEHOLDER_API_KEYS = new Set([
|
|
241
|
+
"$COMMAND_CODE_API_KEY",
|
|
242
|
+
"COMMAND_CODE_API_KEY",
|
|
243
|
+
"$COMMANDCODE_API_KEY",
|
|
244
|
+
"COMMANDCODE_API_KEY",
|
|
245
|
+
])
|
|
239
246
|
const hostKey =
|
|
240
|
-
options?.apiKey &&
|
|
241
|
-
options.apiKey !== LEGACY_API_KEY_REF &&
|
|
242
|
-
options.apiKey !== OLD_API_KEY_REF
|
|
243
|
-
? options.apiKey
|
|
244
|
-
: undefined
|
|
247
|
+
options?.apiKey && !PLACEHOLDER_API_KEYS.has(options.apiKey) ? options.apiKey : undefined
|
|
245
248
|
|
|
246
249
|
const apiKey =
|
|
247
250
|
hostKey ??
|
|
@@ -261,7 +264,7 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
261
264
|
usage: defaultUsage(),
|
|
262
265
|
stopReason: "error",
|
|
263
266
|
errorMessage:
|
|
264
|
-
"No Command Code API key. Run /login and select Command Code, set
|
|
267
|
+
"No Command Code API key. Run /login and select Command Code, set COMMAND_CODE_API_KEY (or legacy COMMANDCODE_API_KEY), or configure ~/.commandcode/auth.json, ~/.pi/agent/auth.json or ~/.omp/agent/auth.json",
|
|
265
268
|
timestamp: now(),
|
|
266
269
|
}
|
|
267
270
|
stream.push({ type: "error", reason: "error", error: msg })
|
|
@@ -285,6 +288,10 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
285
288
|
let textBlock: TextContent | undefined
|
|
286
289
|
let currentTextIdx = -1
|
|
287
290
|
let thinkingIdx = -1
|
|
291
|
+
const streamingToolCalls = new Map<
|
|
292
|
+
string,
|
|
293
|
+
{ contentIndex: number; toolCall: ToolCallContent; partialArgs: string }
|
|
294
|
+
>()
|
|
288
295
|
let finished = false
|
|
289
296
|
|
|
290
297
|
const abortUpstream = () => {
|
|
@@ -397,25 +404,81 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
397
404
|
break
|
|
398
405
|
}
|
|
399
406
|
|
|
400
|
-
case "tool-
|
|
407
|
+
case "tool-input-start": {
|
|
401
408
|
endTextBlock()
|
|
402
409
|
endThinking()
|
|
410
|
+
const id = stringValue(event.id)
|
|
411
|
+
if (!id || streamingToolCalls.has(id)) break
|
|
412
|
+
|
|
403
413
|
const toolCall: ToolCallContent = {
|
|
404
414
|
type: "toolCall",
|
|
405
|
-
id
|
|
415
|
+
id,
|
|
406
416
|
name: stringValue(event.toolName) ?? "",
|
|
407
|
-
arguments:
|
|
417
|
+
arguments: {},
|
|
408
418
|
}
|
|
409
419
|
output.content.push(toolCall)
|
|
410
|
-
const
|
|
420
|
+
const contentIndex = output.content.length - 1
|
|
421
|
+
streamingToolCalls.set(id, { contentIndex, toolCall, partialArgs: "" })
|
|
411
422
|
stream.push({
|
|
412
423
|
type: "toolcall_start",
|
|
413
|
-
contentIndex
|
|
424
|
+
contentIndex,
|
|
414
425
|
partial: output,
|
|
415
426
|
})
|
|
427
|
+
break
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
case "tool-input-delta": {
|
|
431
|
+
const id = stringValue(event.id)
|
|
432
|
+
const delta = stringValue(event.delta)
|
|
433
|
+
if (!id || delta === undefined) break
|
|
434
|
+
const active = streamingToolCalls.get(id)
|
|
435
|
+
if (!active) break
|
|
436
|
+
|
|
437
|
+
active.partialArgs += delta
|
|
438
|
+
active.toolCall.arguments = recordOrEmpty(active.partialArgs)
|
|
439
|
+
stream.push({
|
|
440
|
+
type: "toolcall_delta",
|
|
441
|
+
contentIndex: active.contentIndex,
|
|
442
|
+
delta,
|
|
443
|
+
partial: output,
|
|
444
|
+
})
|
|
445
|
+
break
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
case "tool-input-end": {
|
|
449
|
+
break
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
case "tool-call": {
|
|
453
|
+
endTextBlock()
|
|
454
|
+
endThinking()
|
|
455
|
+
const id = stringValue(event.toolCallId) ?? ""
|
|
456
|
+
const active = streamingToolCalls.get(id)
|
|
457
|
+
const toolCall: ToolCallContent = active?.toolCall ?? {
|
|
458
|
+
type: "toolCall",
|
|
459
|
+
id,
|
|
460
|
+
name: stringValue(event.toolName) ?? "",
|
|
461
|
+
arguments: {},
|
|
462
|
+
}
|
|
463
|
+
toolCall.name = stringValue(event.toolName) ?? toolCall.name
|
|
464
|
+
toolCall.arguments = recordOrEmpty(event.input ?? event.args ?? event.arguments)
|
|
465
|
+
|
|
466
|
+
let contentIndex: number
|
|
467
|
+
if (active) {
|
|
468
|
+
contentIndex = active.contentIndex
|
|
469
|
+
streamingToolCalls.delete(id)
|
|
470
|
+
} else {
|
|
471
|
+
output.content.push(toolCall)
|
|
472
|
+
contentIndex = output.content.length - 1
|
|
473
|
+
stream.push({
|
|
474
|
+
type: "toolcall_start",
|
|
475
|
+
contentIndex,
|
|
476
|
+
partial: output,
|
|
477
|
+
})
|
|
478
|
+
}
|
|
416
479
|
stream.push({
|
|
417
480
|
type: "toolcall_end",
|
|
418
|
-
contentIndex
|
|
481
|
+
contentIndex,
|
|
419
482
|
toolCall,
|
|
420
483
|
partial: output,
|
|
421
484
|
})
|
|
@@ -423,6 +486,15 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
423
486
|
}
|
|
424
487
|
|
|
425
488
|
case "finish": {
|
|
489
|
+
const rawFinishReason = stringValue(event.rawFinishReason)
|
|
490
|
+
if (
|
|
491
|
+
rawFinishReason &&
|
|
492
|
+
/^(?:network|connection|upstream)[-_\s]?error$/i.test(rawFinishReason)
|
|
493
|
+
) {
|
|
494
|
+
throw new Error(
|
|
495
|
+
`Provider finished with reason "${rawFinishReason}" — upstream connection failed mid-stream`,
|
|
496
|
+
)
|
|
497
|
+
}
|
|
426
498
|
const usage = commandCodeUsage(event)
|
|
427
499
|
if (usage) {
|
|
428
500
|
const details = commandCodeInputTokenDetails(usage)
|
|
@@ -446,6 +518,10 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
446
518
|
break
|
|
447
519
|
}
|
|
448
520
|
|
|
521
|
+
case "abort": {
|
|
522
|
+
throw abortError("Request aborted")
|
|
523
|
+
}
|
|
524
|
+
|
|
449
525
|
case "error": {
|
|
450
526
|
const message =
|
|
451
527
|
commandCodeErrorMessage(event.error) ??
|
|
@@ -463,7 +539,11 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
463
539
|
if (controller.signal.aborted) throw abortError("Aborted")
|
|
464
540
|
|
|
465
541
|
const workingDir = cwd()
|
|
466
|
-
const threadId =
|
|
542
|
+
const threadId = options?.sessionId
|
|
543
|
+
? isUuid(options.sessionId)
|
|
544
|
+
? options.sessionId
|
|
545
|
+
: undefined
|
|
546
|
+
: uuid()
|
|
467
547
|
const reasoningEffort = mappedReasoningEffort(model, options)
|
|
468
548
|
const timeoutMs = options?.timeoutMs
|
|
469
549
|
|
|
@@ -491,8 +571,8 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
491
571
|
tools: toolsToJson(context.tools),
|
|
492
572
|
system: systemPromptToText(context.systemPrompt),
|
|
493
573
|
max_tokens: generateMaxTokens(model, options),
|
|
494
|
-
temperature: 0.3,
|
|
495
574
|
stream: true,
|
|
575
|
+
...(options?.temperature !== undefined ? { temperature: options.temperature } : {}),
|
|
496
576
|
...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),
|
|
497
577
|
},
|
|
498
578
|
threadId,
|
|
@@ -522,7 +602,8 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
522
602
|
"x-cli-environment": "production",
|
|
523
603
|
"x-project-slug": projectSlugFromPath(workingDir),
|
|
524
604
|
"x-taste-learning": "true",
|
|
525
|
-
"x-
|
|
605
|
+
...(options?.sessionId ? { "x-session-id": options.sessionId } : {}),
|
|
606
|
+
"User-Agent": "cli",
|
|
526
607
|
...options?.headers,
|
|
527
608
|
}
|
|
528
609
|
const bodyStr = JSON.stringify(body)
|
|
@@ -635,6 +716,11 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
635
716
|
const { done, value } = await raceAbort(reader.read(), attemptController.signal)
|
|
636
717
|
if (done) {
|
|
637
718
|
if (buffer.trim()) handleEvent(parseStreamEventLine(buffer))
|
|
719
|
+
if (!finished) {
|
|
720
|
+
throw new Error(
|
|
721
|
+
"Stream ended unexpectedly before completion (no finish event) — response was truncated",
|
|
722
|
+
)
|
|
723
|
+
}
|
|
638
724
|
break
|
|
639
725
|
}
|
|
640
726
|
if (controller.signal.aborted) throw abortError("Aborted")
|
|
@@ -658,7 +744,12 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
658
744
|
} catch {}
|
|
659
745
|
reader = undefined
|
|
660
746
|
|
|
661
|
-
if (
|
|
747
|
+
if (
|
|
748
|
+
controller.signal.aborted ||
|
|
749
|
+
(streamError instanceof Error && streamError.name === "AbortError")
|
|
750
|
+
) {
|
|
751
|
+
throw streamError
|
|
752
|
+
}
|
|
662
753
|
|
|
663
754
|
// Never retry after visible content was emitted (including timeout mid-stream).
|
|
664
755
|
const canRetry = output.content.length === 0 && attempt < maxRetries
|
|
@@ -695,7 +786,10 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
695
786
|
}
|
|
696
787
|
}
|
|
697
788
|
} catch (error: unknown) {
|
|
698
|
-
const reason: ErrorReason =
|
|
789
|
+
const reason: ErrorReason =
|
|
790
|
+
controller.signal.aborted || (error instanceof Error && error.name === "AbortError")
|
|
791
|
+
? "aborted"
|
|
792
|
+
: "error"
|
|
699
793
|
output.stopReason = reason
|
|
700
794
|
output.errorMessage =
|
|
701
795
|
reason === "aborted"
|