pi-commandcode-provider 0.4.3 → 0.5.1
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 +28 -0
- package/CONTRIBUTING.md +18 -0
- package/README.md +100 -72
- package/index.ts +84 -91
- package/package.json +15 -6
- package/scripts/pi-authenticated.mjs +49 -0
- package/scripts/pi-isolated.mjs +78 -0
- package/src/converters.ts +64 -85
- package/src/core.ts +107 -31
- package/src/cost.ts +16 -4
- package/src/json-schema.ts +382 -0
- package/src/models.ts +251 -15
- package/src/overflow.ts +120 -0
- package/src/pricing.ts +226 -0
- package/src/runtime.ts +279 -0
- package/src/types.ts +19 -1
package/src/models.ts
CHANGED
|
@@ -2,10 +2,154 @@ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
|
|
|
2
2
|
import { dirname } from "node:path"
|
|
3
3
|
|
|
4
4
|
export const DEFAULT_MODELS_URL = "https://api.commandcode.ai/provider/v1/models"
|
|
5
|
+
export const DEFAULT_MODELS_TIMEOUT_MS = 10_000
|
|
5
6
|
|
|
6
7
|
const DEFAULT_MAX_OUTPUT_TOKENS = 65_536
|
|
7
8
|
const MODEL_CACHE_VERSION = 1
|
|
8
9
|
|
|
10
|
+
export type CommandCodeInputType = "text" | "image"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Model input modalities from the command-code@1.15.1 bundled catalog.
|
|
14
|
+
* Models omitted here remain text-only so newly discovered IDs never claim
|
|
15
|
+
* image support without upstream evidence.
|
|
16
|
+
*/
|
|
17
|
+
export const MODEL_INPUT_MODALITIES: Readonly<Record<string, readonly CommandCodeInputType[]>> = {
|
|
18
|
+
"MiniMaxAI/MiniMax-M3": ["text", "image"],
|
|
19
|
+
"Qwen/Qwen3.6-Plus": ["text", "image"],
|
|
20
|
+
"Qwen/Qwen3.7-Flash": ["text", "image"],
|
|
21
|
+
"Qwen/Qwen3.7-Plus": ["text", "image"],
|
|
22
|
+
"Qwen/Qwen3.8-Max": ["text", "image"],
|
|
23
|
+
"claude-fable-5": ["text", "image"],
|
|
24
|
+
"claude-haiku-4-5-20251001": ["text", "image"],
|
|
25
|
+
"claude-opus-4-7": ["text", "image"],
|
|
26
|
+
"claude-opus-4-8": ["text", "image"],
|
|
27
|
+
"claude-opus-5": ["text", "image"],
|
|
28
|
+
"claude-sonnet-4-6": ["text", "image"],
|
|
29
|
+
"claude-sonnet-5": ["text", "image"],
|
|
30
|
+
"google/gemini-3.1-flash-lite": ["text", "image"],
|
|
31
|
+
"google/gemini-3.5-flash": ["text", "image"],
|
|
32
|
+
"google/gemini-3.5-flash-lite": ["text", "image"],
|
|
33
|
+
"google/gemini-3.6-flash": ["text", "image"],
|
|
34
|
+
"gpt-5.3-codex": ["text", "image"],
|
|
35
|
+
"gpt-5.4": ["text", "image"],
|
|
36
|
+
"gpt-5.4-mini": ["text", "image"],
|
|
37
|
+
"gpt-5.5": ["text", "image"],
|
|
38
|
+
"gpt-5.6-luna": ["text", "image"],
|
|
39
|
+
"gpt-5.6-sol": ["text", "image"],
|
|
40
|
+
"gpt-5.6-terra": ["text", "image"],
|
|
41
|
+
"meta/muse-spark-1.1": ["text", "image"],
|
|
42
|
+
"meta/muse-spark-1.2": ["text", "image"],
|
|
43
|
+
"meta/muse-spark-1.2-contributor": ["text", "image"],
|
|
44
|
+
"moonshotai/Kimi-K2.5": ["text", "image"],
|
|
45
|
+
"moonshotai/Kimi-K2.6": ["text", "image"],
|
|
46
|
+
"moonshotai/Kimi-K2.7-Code": ["text", "image"],
|
|
47
|
+
"moonshotai/Kimi-K2.7-Code-Highspeed": ["text", "image"],
|
|
48
|
+
"moonshotai/Kimi-K3": ["text", "image"],
|
|
49
|
+
"sakana/fugu-ultra": ["text", "image"],
|
|
50
|
+
"stepfun/Step-3.7-Flash": ["text", "image"],
|
|
51
|
+
"thinkingmachines/inkling": ["text", "image"],
|
|
52
|
+
"thinkingmachines/inkling-small": ["text", "image"],
|
|
53
|
+
"xai/grok-4.5": ["text", "image"],
|
|
54
|
+
"xiaomi/mimo-v2.5": ["text", "image"],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const TEXT_INPUT_ONLY = ["text"] as const
|
|
58
|
+
|
|
59
|
+
export function inputModalitiesForModel(modelId: string): readonly CommandCodeInputType[] {
|
|
60
|
+
return MODEL_INPUT_MODALITIES[modelId] ?? TEXT_INPUT_ONLY
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function modelSupportsImageInput(modelId: string): boolean {
|
|
64
|
+
return inputModalitiesForModel(modelId).includes("image")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type PiThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
|
|
68
|
+
|
|
69
|
+
type CommandCodeReasoningEffort = Exclude<PiThinkingLevel, "off">
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Per-model reasoning efforts supported by Command Code's generate endpoint.
|
|
73
|
+
*
|
|
74
|
+
* The Provider API does not expose reasoning metadata. This is an exact
|
|
75
|
+
* snapshot of `reasoningEfforts` from the command-code@1.15.1 model catalog
|
|
76
|
+
* (`packages/shared/src/model-catalog.ts`, also published in the generated
|
|
77
|
+
* `dist/bundled/command-code-knowledge/reference/models.md`). Models omitted
|
|
78
|
+
* here let Command Code choose their reasoning depth, matching the CLI.
|
|
79
|
+
*/
|
|
80
|
+
export const MODEL_EFFORTS: Readonly<Record<string, readonly CommandCodeReasoningEffort[]>> = {
|
|
81
|
+
"Qwen/Qwen3.8-Max": ["low", "medium", "xhigh"],
|
|
82
|
+
"claude-fable-5": ["low", "medium", "high", "xhigh", "max"],
|
|
83
|
+
"claude-opus-4-7": ["low", "medium", "high", "xhigh", "max"],
|
|
84
|
+
"claude-opus-4-8": ["low", "medium", "high", "xhigh", "max"],
|
|
85
|
+
"claude-opus-5": ["low", "medium", "high", "xhigh", "max"],
|
|
86
|
+
"claude-sonnet-4-6": ["low", "medium", "high", "xhigh", "max"],
|
|
87
|
+
"claude-sonnet-5": ["low", "medium", "high", "xhigh", "max"],
|
|
88
|
+
"deepseek/deepseek-v4-flash": ["high", "max"],
|
|
89
|
+
"deepseek/deepseek-v4-pro": ["high", "max"],
|
|
90
|
+
"gpt-5.3-codex": ["low", "medium", "high", "xhigh"],
|
|
91
|
+
"gpt-5.4": ["low", "medium", "high", "xhigh"],
|
|
92
|
+
"gpt-5.4-mini": ["low", "medium", "high"],
|
|
93
|
+
"gpt-5.5": ["low", "medium", "high", "xhigh"],
|
|
94
|
+
"gpt-5.6-luna": ["low", "medium", "high", "xhigh", "max"],
|
|
95
|
+
"gpt-5.6-sol": ["low", "medium", "high", "xhigh", "max"],
|
|
96
|
+
"gpt-5.6-terra": ["low", "medium", "high", "xhigh", "max"],
|
|
97
|
+
"google/gemini-3.1-flash-lite": ["low", "medium", "high"],
|
|
98
|
+
"google/gemini-3.5-flash": ["low", "medium", "high"],
|
|
99
|
+
"google/gemini-3.5-flash-lite": ["low", "medium", "high"],
|
|
100
|
+
"google/gemini-3.6-flash": ["low", "medium", "high"],
|
|
101
|
+
"sakana/fugu-ultra": ["high", "xhigh"],
|
|
102
|
+
"xai/grok-4.5": ["low", "medium", "high"],
|
|
103
|
+
"zai-org/GLM-5.2": ["high", "max"],
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const PI_THINKING_LEVELS: readonly PiThinkingLevel[] = [
|
|
107
|
+
"off",
|
|
108
|
+
"minimal",
|
|
109
|
+
"low",
|
|
110
|
+
"medium",
|
|
111
|
+
"high",
|
|
112
|
+
"xhigh",
|
|
113
|
+
"max",
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
export function thinkingLevelMapForEfforts(
|
|
117
|
+
efforts: readonly string[],
|
|
118
|
+
): Partial<Record<PiThinkingLevel, string | null>> {
|
|
119
|
+
const map: Partial<Record<PiThinkingLevel, string | null>> = {}
|
|
120
|
+
for (const level of PI_THINKING_LEVELS) {
|
|
121
|
+
if (level === "off") continue
|
|
122
|
+
map[level] = efforts.includes(level) ? level : null
|
|
123
|
+
}
|
|
124
|
+
return map
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface ThinkingMetadata {
|
|
128
|
+
thinkingLevelMap: Partial<Record<PiThinkingLevel, string | null>>
|
|
129
|
+
thinking: {
|
|
130
|
+
mode: "effort"
|
|
131
|
+
effortMap: Partial<Record<CommandCodeReasoningEffort, string>>
|
|
132
|
+
efforts: readonly CommandCodeReasoningEffort[]
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function thinkingMetadataForModel(modelId: string): ThinkingMetadata | undefined {
|
|
137
|
+
const efforts = MODEL_EFFORTS[modelId]
|
|
138
|
+
if (!efforts) return undefined
|
|
139
|
+
return {
|
|
140
|
+
thinkingLevelMap: thinkingLevelMapForEfforts(efforts),
|
|
141
|
+
thinking: {
|
|
142
|
+
mode: "effort",
|
|
143
|
+
effortMap: Object.fromEntries(efforts.map((effort) => [effort, effort])),
|
|
144
|
+
efforts,
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function isReasoningModel(modelId: string): boolean {
|
|
150
|
+
return MODEL_EFFORTS[modelId] !== undefined
|
|
151
|
+
}
|
|
152
|
+
|
|
9
153
|
interface ApiModel {
|
|
10
154
|
id: string
|
|
11
155
|
name: string
|
|
@@ -23,6 +167,8 @@ export interface CommandCodeModel {
|
|
|
23
167
|
interface FetchCommandCodeModelsOptions {
|
|
24
168
|
url?: string
|
|
25
169
|
fetchImpl?: typeof fetch
|
|
170
|
+
signal?: AbortSignal
|
|
171
|
+
timeoutMs?: number
|
|
26
172
|
}
|
|
27
173
|
|
|
28
174
|
interface LoadCommandCodeModelsOptions extends FetchCommandCodeModelsOptions {
|
|
@@ -74,10 +220,12 @@ function parseApiModel(value: unknown): ApiModel {
|
|
|
74
220
|
function parseCachedModel(value: unknown): CommandCodeModel {
|
|
75
221
|
if (!isRecord(value)) throw new Error("Expected cached model entry to be an object")
|
|
76
222
|
|
|
223
|
+
const id = stringField(value, "id")
|
|
224
|
+
booleanField(value, "reasoning")
|
|
77
225
|
return {
|
|
78
|
-
id
|
|
226
|
+
id,
|
|
79
227
|
name: stringField(value, "name"),
|
|
80
|
-
reasoning:
|
|
228
|
+
reasoning: isReasoningModel(id),
|
|
81
229
|
contextWindow: positiveNumberField(value, "contextWindow"),
|
|
82
230
|
maxTokens: positiveNumberField(value, "maxTokens"),
|
|
83
231
|
}
|
|
@@ -92,6 +240,85 @@ function errorMessage(error: unknown): string {
|
|
|
92
240
|
return error instanceof Error ? error.message : String(error)
|
|
93
241
|
}
|
|
94
242
|
|
|
243
|
+
function abortError(reason: unknown): Error {
|
|
244
|
+
if (reason instanceof Error) return reason
|
|
245
|
+
return new DOMException("The operation was aborted", "AbortError")
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function configuredTimeoutMs(timeoutMs: number | undefined): number {
|
|
249
|
+
return timeoutMs !== undefined && Number.isFinite(timeoutMs) && timeoutMs > 0
|
|
250
|
+
? timeoutMs
|
|
251
|
+
: DEFAULT_MODELS_TIMEOUT_MS
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function getModelsTimeoutMs(env: NodeJS.ProcessEnv = process.env): number {
|
|
255
|
+
const raw = env.COMMANDCODE_MODELS_TIMEOUT_MS
|
|
256
|
+
if (!raw) return DEFAULT_MODELS_TIMEOUT_MS
|
|
257
|
+
|
|
258
|
+
const parsed = Number(raw)
|
|
259
|
+
return configuredTimeoutMs(parsed)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
class ModelDiscoveryTimeoutError extends Error {
|
|
263
|
+
constructor(timeoutMs: number) {
|
|
264
|
+
super(`Command Code model discovery timed out after ${timeoutMs}ms`)
|
|
265
|
+
this.name = "ModelDiscoveryTimeoutError"
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function runWithTimeout<T>(
|
|
270
|
+
operation: (signal: AbortSignal) => Promise<T>,
|
|
271
|
+
timeoutMs: number,
|
|
272
|
+
externalSignal: AbortSignal | undefined,
|
|
273
|
+
): Promise<T> {
|
|
274
|
+
const controller = new AbortController()
|
|
275
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
276
|
+
let settled = false
|
|
277
|
+
let onExternalAbort: (() => void) | undefined
|
|
278
|
+
|
|
279
|
+
return new Promise<T>((resolve, reject) => {
|
|
280
|
+
const cleanup = () => {
|
|
281
|
+
if (timer !== undefined) clearTimeout(timer)
|
|
282
|
+
if (onExternalAbort && externalSignal) {
|
|
283
|
+
externalSignal.removeEventListener("abort", onExternalAbort)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const resolveOnce = (value: T) => {
|
|
288
|
+
if (settled) return
|
|
289
|
+
settled = true
|
|
290
|
+
cleanup()
|
|
291
|
+
resolve(value)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const rejectOnce = (error: unknown) => {
|
|
295
|
+
if (settled) return
|
|
296
|
+
settled = true
|
|
297
|
+
cleanup()
|
|
298
|
+
reject(error)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const abort = (reason: unknown) => {
|
|
302
|
+
const error = abortError(reason)
|
|
303
|
+
controller.abort(error)
|
|
304
|
+
rejectOnce(error)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (externalSignal?.aborted) {
|
|
308
|
+
abort(externalSignal.reason)
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
onExternalAbort = () => abort(externalSignal?.reason)
|
|
313
|
+
externalSignal?.addEventListener("abort", onExternalAbort, { once: true })
|
|
314
|
+
timer = setTimeout(() => abort(new ModelDiscoveryTimeoutError(timeoutMs)), timeoutMs)
|
|
315
|
+
|
|
316
|
+
Promise.resolve()
|
|
317
|
+
.then(() => operation(controller.signal))
|
|
318
|
+
.then(resolveOnce, rejectOnce)
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
|
|
95
322
|
export function commandCodeModelsFromApiResponse(value: unknown): readonly CommandCodeModel[] {
|
|
96
323
|
if (!isRecord(value)) throw new Error("Expected models response to be an object")
|
|
97
324
|
if (value.object !== "list") throw new Error("Expected models response object to be 'list'")
|
|
@@ -102,7 +329,7 @@ export function commandCodeModelsFromApiResponse(value: unknown): readonly Comma
|
|
|
102
329
|
return data.map(parseApiModel).map((model) => ({
|
|
103
330
|
id: model.id,
|
|
104
331
|
name: `${model.name} (CC)`,
|
|
105
|
-
reasoning:
|
|
332
|
+
reasoning: isReasoningModel(model.id),
|
|
106
333
|
contextWindow: model.contextLength,
|
|
107
334
|
maxTokens: Math.min(model.contextLength, DEFAULT_MAX_OUTPUT_TOKENS),
|
|
108
335
|
}))
|
|
@@ -123,19 +350,26 @@ export async function fetchCommandCodeModels(
|
|
|
123
350
|
): Promise<readonly CommandCodeModel[]> {
|
|
124
351
|
const url = options.url ?? DEFAULT_MODELS_URL
|
|
125
352
|
const fetchImpl = options.fetchImpl ?? fetch
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
353
|
+
const body: unknown = await runWithTimeout(
|
|
354
|
+
async (signal) => {
|
|
355
|
+
const response = await fetchImpl(url, {
|
|
356
|
+
headers: {
|
|
357
|
+
accept: "application/json",
|
|
358
|
+
},
|
|
359
|
+
signal,
|
|
360
|
+
})
|
|
131
361
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
362
|
+
if (!response.ok) {
|
|
363
|
+
throw new Error(
|
|
364
|
+
`Failed to fetch Command Code models: ${response.status} ${response.statusText}`,
|
|
365
|
+
)
|
|
366
|
+
}
|
|
137
367
|
|
|
138
|
-
|
|
368
|
+
return await response.json()
|
|
369
|
+
},
|
|
370
|
+
configuredTimeoutMs(options.timeoutMs),
|
|
371
|
+
options.signal,
|
|
372
|
+
)
|
|
139
373
|
return requireModels(commandCodeModelsFromApiResponse(body))
|
|
140
374
|
}
|
|
141
375
|
|
|
@@ -187,6 +421,8 @@ export async function loadCommandCodeModels(
|
|
|
187
421
|
}
|
|
188
422
|
}
|
|
189
423
|
} catch (liveError) {
|
|
424
|
+
if (options.signal?.aborted) throw abortError(options.signal.reason ?? liveError)
|
|
425
|
+
|
|
190
426
|
try {
|
|
191
427
|
const models = await readCommandCodeModelsCache(cachePath)
|
|
192
428
|
return {
|
|
@@ -198,7 +434,7 @@ export async function loadCommandCodeModels(
|
|
|
198
434
|
return {
|
|
199
435
|
models: [],
|
|
200
436
|
source: "empty",
|
|
201
|
-
warning: `Could not refresh the Command Code model catalog (${errorMessage(liveError)}), and no valid cached catalog is available at ${cachePath} (${errorMessage(cacheError)}). Command Code models will remain unavailable until /
|
|
437
|
+
warning: `Could not refresh the Command Code model catalog (${errorMessage(liveError)}), and no valid cached catalog is available at ${cachePath} (${errorMessage(cacheError)}). Command Code models will remain unavailable until /commandcode-refresh succeeds.`,
|
|
202
438
|
}
|
|
203
439
|
}
|
|
204
440
|
}
|
package/src/overflow.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const COMMAND_CODE_PROVIDER = "commandcode"
|
|
2
|
+
const CONTEXT_OVERFLOW_PREFIX = "context_length_exceeded:"
|
|
3
|
+
|
|
4
|
+
const COMMAND_CODE_OVERFLOW_PATTERNS = [
|
|
5
|
+
/\b(?:context[_\s-]*(?:length|window)|model[_\s-]*context[_\s-]*window)[_\s-]*(?:exceeded|overflow(?:ed)?|too[_\s-]*(?:large|long))\b/i,
|
|
6
|
+
/\b(?:context|prompt|input)[_\s-]*(?:length|window|size|tokens?|limit|maximum)\b[\s\S]{0,120}\b(?:exceed(?:ed|s)?|overflow(?:ed|s)?|too\s+(?:large|long)|(?:maximum|limit)\s+(?:reached|exceeded|hit))\b/i,
|
|
7
|
+
/\b(?:exceed(?:ed|s)?|overflow(?:ed|s)?|too\s+(?:large|long))\b[\s\S]{0,120}\b(?:context|prompt|input)[_\s-]*(?:length|window|size|tokens?|limit|maximum)\b/i,
|
|
8
|
+
/\b(?:prompt|input|context)\b[\s\S]{0,32}\btoo\s+(?:large|long)\b/i,
|
|
9
|
+
/\b(?:prompt|input)[_\s-]*too[_\s-]*(?:large|long)\b/i,
|
|
10
|
+
/\b(?:prompt|input)[_\s-]*tokens?[_\s-]*(?:limit|maximum|max)[_\s-]*(?:exceeded|reached)\b/i,
|
|
11
|
+
/\b(?:prompt|input)[_\s-]*(?:tokens?|length|size)\b[\s\S]{0,120}\b(?:limit|maximum)\b[\s\S]{0,40}\b(?:exceed(?:ed|s)?|reached|hit)\b/i,
|
|
12
|
+
/\b(?:maximum|limit)[_\s-]+(?:allowed[_\s-]+)?(?:context|prompt|input)[_\s-]*(?:length|window|size|tokens?)\b/i,
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
const NON_OVERFLOW_PATTERNS = [
|
|
16
|
+
/\brate[_\s-]*limit\b/i,
|
|
17
|
+
/\btoo\s+many\s+requests\b/i,
|
|
18
|
+
/\b(?:capacity|quota|throttl(?:e|ed|ing)?|concurren(?:cy|t)|overloaded)\b/i,
|
|
19
|
+
/\b(?:service|temporarily)\s+unavailable\b/i,
|
|
20
|
+
/\bstatus(?:[_\s-]*code)?\s*[:=]\s*429\b/i,
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const CONTEXT_OVERFLOW_PREFIX_PATTERN = /context_length_exceeded/i
|
|
24
|
+
|
|
25
|
+
const HTTP_RATE_LIMIT_STATUS_PATTERNS = [
|
|
26
|
+
/\b(?:api\s+error|http|status(?:[_\s-]*code)?|status[_\s-]*code)\s*[:(]?\s*429\b/i,
|
|
27
|
+
/["']?(?:status|status[_\s-]*code)["']?\s*:\s*429\b/i,
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
const BEARER_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi
|
|
31
|
+
const CREDENTIAL_PATTERN =
|
|
32
|
+
/\b(?:api[-_ ]?key|apikey|access[-_ ]?token|refresh[-_ ]?token|token|secret|password|authorization)\s*[=:]\s*[^\s,;)]+/gi
|
|
33
|
+
const USER_TOKEN_PATTERN = /\b(?:user|cc)_[A-Za-z0-9_-]{8,}\b/gi
|
|
34
|
+
const QUERY_SECRET_PATTERN =
|
|
35
|
+
/([?&](?:api[-_ ]?key|apikey|access_token|refresh_token|token|secret|password)=)[^&#\s]+/gi
|
|
36
|
+
const STANDALONE_SECRET_PATTERN =
|
|
37
|
+
/\b(?:sk|rk|ghp|github_pat|xox[baprs])[-_A-Za-z0-9]{16,}\b|\beyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g
|
|
38
|
+
|
|
39
|
+
export function redactCommandCodeErrorText(value: string): string {
|
|
40
|
+
return value
|
|
41
|
+
.replace(BEARER_PATTERN, "Bearer [redacted]")
|
|
42
|
+
.replace(CREDENTIAL_PATTERN, (match) => {
|
|
43
|
+
const separatorIndex = match.search(/[=:]/)
|
|
44
|
+
return separatorIndex < 0 ? "[redacted]" : `${match.slice(0, separatorIndex + 1)}[redacted]`
|
|
45
|
+
})
|
|
46
|
+
.replace(USER_TOKEN_PATTERN, "[redacted]")
|
|
47
|
+
.replace(QUERY_SECRET_PATTERN, "$1[redacted]")
|
|
48
|
+
.replace(STANDALONE_SECRET_PATTERN, "[redacted]")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
52
|
+
return typeof value === "object" && value !== null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface CommandCodeMessageLike {
|
|
56
|
+
role: string
|
|
57
|
+
provider: string
|
|
58
|
+
stopReason: string
|
|
59
|
+
errorMessage?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function commandCodeErrorMessage(value: unknown): string | undefined {
|
|
63
|
+
if (typeof value === "string") return value
|
|
64
|
+
if (!isRecord(value)) return undefined
|
|
65
|
+
|
|
66
|
+
const record = value
|
|
67
|
+
const parts: string[] = []
|
|
68
|
+
for (const key of [
|
|
69
|
+
"message",
|
|
70
|
+
"errorMessage",
|
|
71
|
+
"error",
|
|
72
|
+
"detail",
|
|
73
|
+
"details",
|
|
74
|
+
"code",
|
|
75
|
+
"type",
|
|
76
|
+
"reason",
|
|
77
|
+
]) {
|
|
78
|
+
const part = commandCodeErrorMessage(record[key])
|
|
79
|
+
if (part && !parts.includes(part)) parts.push(part)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const key of ["status", "statusCode", "httpStatus"]) {
|
|
83
|
+
const status = record[key]
|
|
84
|
+
if (typeof status === "string" || typeof status === "number") {
|
|
85
|
+
const statusPart = `status: ${status}`
|
|
86
|
+
if (!parts.includes(statusPart)) parts.push(statusPart)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return parts.length > 0 ? redactCommandCodeErrorText(parts.join(": ")) : undefined
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function normalizeCommandCodeErrorMessage(
|
|
94
|
+
errorMessage: string | undefined,
|
|
95
|
+
): string | undefined {
|
|
96
|
+
if (!errorMessage) return undefined
|
|
97
|
+
if (CONTEXT_OVERFLOW_PREFIX_PATTERN.test(errorMessage)) return undefined
|
|
98
|
+
if (NON_OVERFLOW_PATTERNS.some((pattern) => pattern.test(errorMessage))) return undefined
|
|
99
|
+
if (HTTP_RATE_LIMIT_STATUS_PATTERNS.some((pattern) => pattern.test(errorMessage)))
|
|
100
|
+
return undefined
|
|
101
|
+
if (!COMMAND_CODE_OVERFLOW_PATTERNS.some((pattern) => pattern.test(errorMessage)))
|
|
102
|
+
return undefined
|
|
103
|
+
|
|
104
|
+
return `${CONTEXT_OVERFLOW_PREFIX} ${errorMessage}`
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function normalizeCommandCodeMessage<T extends CommandCodeMessageLike>(
|
|
108
|
+
message: T,
|
|
109
|
+
modelProvider?: string,
|
|
110
|
+
): { message: T & { errorMessage: string } } | undefined {
|
|
111
|
+
if (message.role !== "assistant" || message.stopReason !== "error") return undefined
|
|
112
|
+
if (message.provider !== COMMAND_CODE_PROVIDER && modelProvider !== COMMAND_CODE_PROVIDER) {
|
|
113
|
+
return undefined
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const errorMessage = normalizeCommandCodeErrorMessage(message.errorMessage)
|
|
117
|
+
if (!errorMessage) return undefined
|
|
118
|
+
|
|
119
|
+
return { message: { ...message, errorMessage } }
|
|
120
|
+
}
|
package/src/pricing.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
export interface CommandCodeModelCostRates {
|
|
2
|
+
input: number
|
|
3
|
+
output: number
|
|
4
|
+
cacheRead: number
|
|
5
|
+
cacheWrite: number
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CommandCodeModelCostTier extends CommandCodeModelCostRates {
|
|
9
|
+
inputTokensAbove: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CommandCodeModelCost extends CommandCodeModelCostRates {
|
|
13
|
+
tiers?: readonly CommandCodeModelCostTier[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface TemporaryPricing {
|
|
17
|
+
models: readonly string[]
|
|
18
|
+
expiresOn: string
|
|
19
|
+
description: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const PRICING_SOURCE_URL = "https://commandcode.ai/docs/resources/pricing-limits"
|
|
23
|
+
export const PRICING_LAST_VERIFIED = "2026-08-04"
|
|
24
|
+
|
|
25
|
+
export const ZERO_MODEL_COST: CommandCodeModelCost = {
|
|
26
|
+
input: 0,
|
|
27
|
+
output: 0,
|
|
28
|
+
cacheRead: 0,
|
|
29
|
+
cacheWrite: 0,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Display prices in USD per million tokens.
|
|
34
|
+
*
|
|
35
|
+
* Context-dependent rates use pi's request-wide input pricing tiers. The
|
|
36
|
+
* highest threshold exceeded by input + cache reads + cache writes applies to
|
|
37
|
+
* the full request. The Command Code usage page remains authoritative for the
|
|
38
|
+
* amount billed for an individual request.
|
|
39
|
+
*/
|
|
40
|
+
export const MODEL_COSTS: Readonly<Record<string, CommandCodeModelCost>> = {
|
|
41
|
+
// Free models
|
|
42
|
+
"poolside/laguna-s-2.1-free": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
43
|
+
"inclusionai/ling-3.0-flash-free": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
44
|
+
|
|
45
|
+
// Open and open-weight models
|
|
46
|
+
"tencent/hy3-paid": { input: 0.14, output: 0.58, cacheRead: 0.035, cacheWrite: 0 },
|
|
47
|
+
"moonshotai/Kimi-K3": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 0 },
|
|
48
|
+
"moonshotai/Kimi-K2.7-Code": { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite: 0 },
|
|
49
|
+
"moonshotai/Kimi-K2.7-Code-Highspeed": {
|
|
50
|
+
input: 1.9,
|
|
51
|
+
output: 8,
|
|
52
|
+
cacheRead: 0.38,
|
|
53
|
+
cacheWrite: 0,
|
|
54
|
+
},
|
|
55
|
+
"moonshotai/Kimi-K2.6": { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0 },
|
|
56
|
+
"moonshotai/Kimi-K2.5": { input: 0.6, output: 3, cacheRead: 0.1, cacheWrite: 0 },
|
|
57
|
+
"zai-org/GLM-5.2": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
|
|
58
|
+
"zai-org/GLM-5.2-Fast": { input: 3, output: 10.25, cacheRead: 0.5, cacheWrite: 0 },
|
|
59
|
+
"zai-org/GLM-5.1": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
|
|
60
|
+
"zai-org/GLM-5": { input: 1, output: 3.2, cacheRead: 0.2, cacheWrite: 0 },
|
|
61
|
+
"MiniMaxAI/MiniMax-M3": { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
|
|
62
|
+
"MiniMaxAI/MiniMax-M2.7": { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
|
|
63
|
+
"MiniMaxAI/MiniMax-M2.5": { input: 0.3, output: 1.2, cacheRead: 0.03, cacheWrite: 0 },
|
|
64
|
+
// Permanent 75% discount.
|
|
65
|
+
"deepseek/deepseek-v4-pro": {
|
|
66
|
+
input: 0.435,
|
|
67
|
+
output: 0.87,
|
|
68
|
+
cacheRead: 0.003625,
|
|
69
|
+
cacheWrite: 0,
|
|
70
|
+
},
|
|
71
|
+
"deepseek/deepseek-v4-flash": {
|
|
72
|
+
input: 0.14,
|
|
73
|
+
output: 0.28,
|
|
74
|
+
cacheRead: 0.0028,
|
|
75
|
+
cacheWrite: 0,
|
|
76
|
+
},
|
|
77
|
+
"Qwen/Qwen3.8-Max": { input: 2, output: 6, cacheRead: 0.25, cacheWrite: 2.5 },
|
|
78
|
+
"Qwen/Qwen3.7-Max": { input: 2.5, output: 7.5, cacheRead: 0.5, cacheWrite: 3.13 },
|
|
79
|
+
"Qwen/Qwen3.7-Plus": {
|
|
80
|
+
input: 0.4,
|
|
81
|
+
output: 1.6,
|
|
82
|
+
cacheRead: 0.08,
|
|
83
|
+
cacheWrite: 0.5,
|
|
84
|
+
tiers: [
|
|
85
|
+
{
|
|
86
|
+
inputTokensAbove: 256_000,
|
|
87
|
+
input: 1.2,
|
|
88
|
+
output: 4.8,
|
|
89
|
+
cacheRead: 0.24,
|
|
90
|
+
cacheWrite: 1.5,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
"Qwen/Qwen3.7-Flash": {
|
|
95
|
+
input: 0.03,
|
|
96
|
+
output: 0.13,
|
|
97
|
+
cacheRead: 0.006,
|
|
98
|
+
cacheWrite: 0.038,
|
|
99
|
+
tiers: [
|
|
100
|
+
{
|
|
101
|
+
inputTokensAbove: 32_000,
|
|
102
|
+
input: 0.1,
|
|
103
|
+
output: 0.4,
|
|
104
|
+
cacheRead: 0.02,
|
|
105
|
+
cacheWrite: 0.125,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
inputTokensAbove: 256_000,
|
|
109
|
+
input: 0.2,
|
|
110
|
+
output: 0.8,
|
|
111
|
+
cacheRead: 0.04,
|
|
112
|
+
cacheWrite: 0.25,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
"Qwen/Qwen3.6-Max-Preview": {
|
|
117
|
+
input: 1.3,
|
|
118
|
+
output: 7.8,
|
|
119
|
+
cacheRead: 0.26,
|
|
120
|
+
cacheWrite: 1.63,
|
|
121
|
+
},
|
|
122
|
+
"Qwen/Qwen3.6-Plus": { input: 0.5, output: 3, cacheRead: 0.1, cacheWrite: 0 },
|
|
123
|
+
"stepfun/Step-3.7-Flash": { input: 0.2, output: 1.15, cacheRead: 0.04, cacheWrite: 0 },
|
|
124
|
+
"stepfun/Step-3.5-Flash": { input: 0.1, output: 0.3, cacheRead: 0.02, cacheWrite: 0 },
|
|
125
|
+
// Permanent discounted rates.
|
|
126
|
+
"xiaomi/mimo-v2.5-pro": { input: 0.435, output: 0.87, cacheRead: 0.0036, cacheWrite: 0 },
|
|
127
|
+
"xiaomi/mimo-v2.5": { input: 0.14, output: 0.28, cacheRead: 0.0028, cacheWrite: 0 },
|
|
128
|
+
"nvidia/nemotron-3-ultra-550b-a55b": {
|
|
129
|
+
input: 0.6,
|
|
130
|
+
output: 2.4,
|
|
131
|
+
cacheRead: 0.12,
|
|
132
|
+
cacheWrite: 0,
|
|
133
|
+
},
|
|
134
|
+
"sakana/fugu-ultra": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
|
|
135
|
+
"thinkingmachines/inkling": { input: 1, output: 4.05, cacheRead: 0.17, cacheWrite: 0 },
|
|
136
|
+
"thinkingmachines/inkling-small": {
|
|
137
|
+
input: 0.5,
|
|
138
|
+
output: 1.2,
|
|
139
|
+
cacheRead: 0.1,
|
|
140
|
+
cacheWrite: 0,
|
|
141
|
+
},
|
|
142
|
+
"meta/muse-spark-1.1": { input: 1.25, output: 4.25, cacheRead: 0.15, cacheWrite: 0 },
|
|
143
|
+
|
|
144
|
+
// Anthropic
|
|
145
|
+
// Introductory pricing through 2026-08-31.
|
|
146
|
+
"claude-sonnet-5": { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 },
|
|
147
|
+
"claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
148
|
+
"claude-fable-5": { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
|
|
149
|
+
"claude-opus-5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
150
|
+
"claude-opus-4-8": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
151
|
+
"claude-opus-4-7": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
152
|
+
"claude-haiku-4-5-20251001": {
|
|
153
|
+
input: 1,
|
|
154
|
+
output: 5,
|
|
155
|
+
cacheRead: 0.1,
|
|
156
|
+
cacheWrite: 1.25,
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
// OpenAI
|
|
160
|
+
"gpt-5.6-sol": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
161
|
+
// Discounted rates through 2026-08-14.
|
|
162
|
+
"gpt-5.6-terra": {
|
|
163
|
+
input: 1,
|
|
164
|
+
output: 6,
|
|
165
|
+
cacheRead: 0.1,
|
|
166
|
+
cacheWrite: 1.25,
|
|
167
|
+
tiers: [
|
|
168
|
+
{
|
|
169
|
+
inputTokensAbove: 272_000,
|
|
170
|
+
input: 2,
|
|
171
|
+
output: 9,
|
|
172
|
+
cacheRead: 0.2,
|
|
173
|
+
cacheWrite: 2.5,
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
"gpt-5.6-luna": {
|
|
178
|
+
input: 0.1,
|
|
179
|
+
output: 0.6,
|
|
180
|
+
cacheRead: 0.01,
|
|
181
|
+
cacheWrite: 0.125,
|
|
182
|
+
tiers: [
|
|
183
|
+
{
|
|
184
|
+
inputTokensAbove: 272_000,
|
|
185
|
+
input: 0.2,
|
|
186
|
+
output: 0.9,
|
|
187
|
+
cacheRead: 0.02,
|
|
188
|
+
cacheWrite: 0.25,
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
"gpt-5.5": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
|
|
193
|
+
"gpt-5.4": { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 },
|
|
194
|
+
"gpt-5.3-codex": { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 0 },
|
|
195
|
+
"gpt-5.4-mini": { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },
|
|
196
|
+
|
|
197
|
+
// Google and xAI
|
|
198
|
+
"google/gemini-3.6-flash": { input: 1.5, output: 7.5, cacheRead: 0.15, cacheWrite: 0 },
|
|
199
|
+
"google/gemini-3.5-flash": { input: 1.5, output: 9, cacheRead: 0.15, cacheWrite: 0 },
|
|
200
|
+
"google/gemini-3.5-flash-lite": {
|
|
201
|
+
input: 0.3,
|
|
202
|
+
output: 2.5,
|
|
203
|
+
cacheRead: 0.03,
|
|
204
|
+
cacheWrite: 0,
|
|
205
|
+
},
|
|
206
|
+
"google/gemini-3.1-flash-lite": {
|
|
207
|
+
input: 0.25,
|
|
208
|
+
output: 1.5,
|
|
209
|
+
cacheRead: 0.03,
|
|
210
|
+
cacheWrite: 0,
|
|
211
|
+
},
|
|
212
|
+
"xai/grok-4.5": { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 },
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export const TEMPORARY_PRICING: readonly TemporaryPricing[] = [
|
|
216
|
+
{
|
|
217
|
+
models: ["gpt-5.6-terra", "gpt-5.6-luna"],
|
|
218
|
+
expiresOn: "2026-08-14",
|
|
219
|
+
description: "50% promotional rates",
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
models: ["claude-sonnet-5"],
|
|
223
|
+
expiresOn: "2026-08-31",
|
|
224
|
+
description: "introductory pricing",
|
|
225
|
+
},
|
|
226
|
+
]
|