merge main (0.6.0) into fix/omp-developer-messages

Align the developer-message fix with the Provider API transport router.
The fix stays scoped to the legacy /alpha/generate converter, and the
OMP advisory compat test now forces the upgrade_required fallback so the
advisory is asserted on the generate request body.
This commit is contained in:
warc0s
2026-08-25 22:52:02 +02:00
48 changed files with 4325 additions and 490 deletions
+69
View File
@@ -0,0 +1,69 @@
import { existsSync, readFileSync } from "node:fs"
import { homedir } from "node:os"
import { join } from "node:path"
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value)
}
function stringValue(value: unknown): string | undefined {
return typeof value === "string" ? value : undefined
}
function defaultAuthPaths(home: string): string[] {
return [
join(home, ".commandcode", "auth.json"),
join(home, ".pi", "agent", "auth.json"),
join(home, ".omp", "agent", "auth.json"),
]
}
function apiKeyFromCredential(value: unknown): string | undefined {
if (!isRecord(value)) return undefined
if (stringValue(value.type) === "oauth") return stringValue(value.access)
if (stringValue(value.type) === "api") return stringValue(value.key)
return stringValue(value.access) ?? stringValue(value.key)
}
export function getConfiguredApiKey(
options: {
env?: NodeJS.ProcessEnv
authPaths?: readonly string[]
homeDir?: () => string
} = {},
): string | undefined {
const env = options.env ?? process.env
if (env.COMMAND_CODE_API_KEY) return env.COMMAND_CODE_API_KEY
if (env.COMMANDCODE_API_KEY) return env.COMMANDCODE_API_KEY
const home = options.homeDir?.() ?? homedir()
const authPaths = options.authPaths ?? defaultAuthPaths(home)
for (const authPath of authPaths) {
try {
if (!existsSync(authPath)) continue
const parsed: unknown = JSON.parse(readFileSync(authPath, "utf-8"))
if (!isRecord(parsed)) continue
const apiKey = stringValue(parsed.apiKey)
if (apiKey) return apiKey
const commandcode = stringValue(parsed.commandcode)
if (commandcode) return commandcode
const providerKey = apiKeyFromCredential(parsed.commandcode)
if (providerKey) return providerKey
const commandCode = stringValue(parsed["command-code"])
if (commandCode) return commandCode
const commandCodeKey = apiKeyFromCredential(parsed["command-code"])
if (commandCodeKey) return commandCodeKey
} catch {
// Ignore malformed or unreadable auth files.
}
}
return undefined
}
+7
View File
@@ -28,6 +28,7 @@ export interface AuthServer {
export interface AuthServerOptions {
startPort?: number
portRange?: number
expectedState?: string
}
function listenOnAvailablePort(
@@ -181,6 +182,12 @@ export async function startAuthServer(options: AuthServerOptions = {}): Promise<
return
}
if (options.expectedState !== undefined && state !== options.expectedState) {
res.writeHead(403)
res.end(JSON.stringify({ success: false, error: "Invalid state token" }))
return
}
res.writeHead(200)
res.end(JSON.stringify({ success: true }))
+141
View File
@@ -0,0 +1,141 @@
export const COMMAND_CODE_CLI_VERSION = "1.32.2"
export type CommandCodeInputType = "text" | "image"
export type CommandCodeReasoningEffort = "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
/**
* Generated from command-code@1.32.2 by `npm run sync:commandcode-catalog`.
* Do not edit manually.
*/
export const MODEL_INPUT_MODALITIES: Readonly<Record<string, readonly CommandCodeInputType[]>> = {
"claude-fable-5": ["text", "image"],
"claude-haiku-4-5-20251001": ["text", "image"],
"claude-opus-4-7": ["text", "image"],
"claude-opus-4-8": ["text", "image"],
"claude-opus-5": ["text", "image"],
"claude-sonnet-4-6": ["text", "image"],
"claude-sonnet-5": ["text", "image"],
"deepseek/deepseek-v4-flash-vision-exp": ["text", "image"],
"google/gemini-3.1-flash-lite": ["text", "image"],
"google/gemini-3.5-flash": ["text", "image"],
"google/gemini-3.5-flash-lite": ["text", "image"],
"google/gemini-3.6-flash": ["text", "image"],
"google/gemini-3.7-flash": ["text", "image"],
"gpt-5.3-codex": ["text", "image"],
"gpt-5.4": ["text", "image"],
"gpt-5.4-mini": ["text", "image"],
"gpt-5.5": ["text", "image"],
"gpt-5.6-luna": ["text", "image"],
"gpt-5.6-sol": ["text", "image"],
"gpt-5.6-terra": ["text", "image"],
"meta/muse-spark-1.1": ["text", "image"],
"meta/muse-spark-1.2": ["text", "image"],
"meta/muse-spark-1.2-contributor": ["text", "image"],
"MiniMaxAI/MiniMax-M3": ["text", "image"],
"moonshotai/Kimi-K2.5": ["text", "image"],
"moonshotai/Kimi-K2.6": ["text", "image"],
"moonshotai/Kimi-K2.7-Code": ["text", "image"],
"moonshotai/Kimi-K2.7-Code-Highspeed": ["text", "image"],
"moonshotai/Kimi-K3": ["text", "image"],
"Qwen/Qwen3.6-Plus": ["text", "image"],
"Qwen/Qwen3.7-Flash": ["text", "image"],
"Qwen/Qwen3.7-Plus": ["text", "image"],
"Qwen/Qwen3.8-27B": ["text", "image"],
"Qwen/Qwen3.8-Max": ["text", "image"],
"sakana/fugu-ultra": ["text", "image"],
"stealth/ox-alpha": ["text", "image"],
"stepfun/Step-3.7-Flash": ["text", "image"],
"thinkingmachines/inkling": ["text", "image"],
"thinkingmachines/inkling-small": ["text", "image"],
"xai/grok-4.5": ["text", "image"],
"xiaomi/mimo-v2.5": ["text", "image"],
}
export const MODEL_REASONING: Readonly<Record<string, true>> = {
"claude-fable-5": true,
"claude-opus-4-7": true,
"claude-opus-4-8": true,
"claude-opus-5": true,
"claude-sonnet-4-6": true,
"claude-sonnet-5": true,
"deepseek/deepseek-v4-flash": true,
"deepseek/deepseek-v4-flash-vision-exp": true,
"deepseek/deepseek-v4-pro": true,
"google/gemini-3.1-flash-lite": true,
"google/gemini-3.5-flash": true,
"google/gemini-3.5-flash-lite": true,
"google/gemini-3.6-flash": true,
"google/gemini-3.7-flash": true,
"gpt-5.3-codex": true,
"gpt-5.4": true,
"gpt-5.4-mini": true,
"gpt-5.5": true,
"gpt-5.6-luna": true,
"gpt-5.6-sol": true,
"gpt-5.6-terra": true,
"meta/muse-spark-1.1": true,
"meta/muse-spark-1.2": true,
"meta/muse-spark-1.2-contributor": true,
"MiniMaxAI/MiniMax-M3": true,
"moonshotai/Kimi-K2.7-Code": true,
"moonshotai/Kimi-K2.7-Code-Highspeed": true,
"moonshotai/Kimi-K3": true,
"nvidia/nemotron-3-ultra-550b-a55b": true,
"poolside/laguna-s-2.1-free": true,
"Qwen/Qwen3.6-Max-Preview": true,
"Qwen/Qwen3.6-Plus": true,
"Qwen/Qwen3.7-Flash": true,
"Qwen/Qwen3.7-Max": true,
"Qwen/Qwen3.7-Plus": true,
"Qwen/Qwen3.8-27B": true,
"Qwen/Qwen3.8-Max": true,
"sakana/fugu-ultra": true,
"stealth/ox-alpha": true,
"stepfun/Step-3.5-Flash": true,
"stepfun/Step-3.7-Flash": true,
"tencent/hy3-paid": true,
"thinkingmachines/inkling": true,
"thinkingmachines/inkling-small": true,
"xai/grok-4.5": true,
"xai/grok-4.6": true,
"zai-org/GLM-5.2": true,
"zai-org/GLM-5.3": true,
}
export const MODEL_EFFORTS: Readonly<Record<string, readonly CommandCodeReasoningEffort[]>> = {
"claude-fable-5": ["low", "medium", "high", "xhigh", "max"],
"claude-opus-4-7": ["low", "medium", "high", "xhigh", "max"],
"claude-opus-4-8": ["low", "medium", "high", "xhigh", "max"],
"claude-opus-5": ["low", "medium", "high", "xhigh", "max"],
"claude-sonnet-4-6": ["low", "medium", "high", "xhigh", "max"],
"claude-sonnet-5": ["low", "medium", "high", "xhigh", "max"],
"deepseek/deepseek-v4-flash": ["high", "max"],
"deepseek/deepseek-v4-flash-vision-exp": ["high", "max"],
"deepseek/deepseek-v4-pro": ["high", "max"],
"google/gemini-3.1-flash-lite": ["low", "medium", "high"],
"google/gemini-3.5-flash": ["low", "medium", "high"],
"google/gemini-3.5-flash-lite": ["low", "medium", "high"],
"google/gemini-3.6-flash": ["low", "medium", "high"],
"google/gemini-3.7-flash": ["low", "medium", "high"],
"gpt-5.3-codex": ["low", "medium", "high", "xhigh"],
"gpt-5.4": ["low", "medium", "high", "xhigh"],
"gpt-5.4-mini": ["low", "medium", "high"],
"gpt-5.5": ["low", "medium", "high", "xhigh"],
"gpt-5.6-luna": ["low", "medium", "high", "xhigh", "max"],
"gpt-5.6-sol": ["low", "medium", "high", "xhigh", "max"],
"gpt-5.6-terra": ["low", "medium", "high", "xhigh", "max"],
"Qwen/Qwen3.8-27B": ["low", "medium", "xhigh"],
"Qwen/Qwen3.8-Max": ["low", "medium", "xhigh"],
"sakana/fugu-ultra": ["high", "xhigh"],
"stealth/ox-alpha": ["low", "high", "max"],
"xai/grok-4.5": ["low", "medium", "high"],
"xai/grok-4.6": ["low", "medium", "high", "xhigh"],
"zai-org/GLM-5.2": ["high", "max"],
"zai-org/GLM-5.3": ["low", "high", "max"],
}
export const MODEL_MAX_OUTPUT_TOKENS: Readonly<Record<string, number>> = {
"poolside/laguna-s-2.1-free": 32_768,
"Qwen/Qwen3.8-27B": 32_768,
"stealth/ox-alpha": 131_072,
}
+72 -16
View File
@@ -66,9 +66,8 @@ function imageContentError(role: string): Error {
export function assertTextOnlyMessages(messages?: readonly MessageLike[]): void {
for (const message of messages ?? []) {
if (imageParts(message.content).length > 0) {
const role = message.role === "toolResult" ? "tool results" : `${message.role} messages`
throw imageContentError(role)
if (message.role !== "toolResult" && imageParts(message.content).length > 0) {
throw imageContentError(`${message.role} messages`)
}
}
}
@@ -107,6 +106,7 @@ export function getApiKey(
} = {},
): string | undefined {
const env = options.env ?? process.env
if (env.COMMAND_CODE_API_KEY) return env.COMMAND_CODE_API_KEY
if (env.COMMANDCODE_API_KEY) return env.COMMANDCODE_API_KEY
const home = options.homeDir?.() ?? homedir()
@@ -138,7 +138,41 @@ export function getApiKey(
return undefined
}
// Hosts such as OMP may pass a literal env-var name as the "resolved" registry
// key instead of the actual credential. Treat those as unresolved.
export const COMMAND_CODE_PLACEHOLDER_KEYS = new Set([
"$COMMAND_CODE_API_KEY",
"COMMAND_CODE_API_KEY",
"$COMMANDCODE_API_KEY",
"COMMANDCODE_API_KEY",
])
/**
* Pick the real API key from a host registry value and/or the env/auth-file
* fallback, never returning a literal placeholder or an empty/whitespace value.
* Pure/testable.
*/
export function pickCommandCodeApiKey(
registryKey: string | undefined,
hostKey: string | undefined,
): string | undefined {
const trimmed = typeof registryKey === "string" ? registryKey.trim() : undefined
if (!trimmed) return hostKey
if (COMMAND_CODE_PLACEHOLDER_KEYS.has(trimmed)) return hostKey
return trimmed
}
export function textContent(message: { content?: unknown }): string {
if (typeof message.content === "string") return message.content
if (message.content === null || message.content === undefined) return ""
if (!Array.isArray(message.content)) {
try {
return JSON.stringify(message.content) ?? String(message.content)
} catch {
return String(message.content)
}
}
return recordArray(message.content)
.filter((part) => part.type === "text")
.map((part) => stringValue(part.text) ?? "")
@@ -159,7 +193,12 @@ export function toolsToJson(tools?: readonly ToolLike[]): unknown[] {
}))
}
function completeToolCallIds(messages?: readonly MessageLike[]): Set<string> {
interface ToolCallState {
callIds: ReadonlySet<string>
resultIds: ReadonlySet<string>
}
function toolCallState(messages?: readonly MessageLike[]): ToolCallState {
const callIds = new Set<string>()
const resultIds = new Set<string>()
@@ -171,12 +210,12 @@ function completeToolCallIds(messages?: readonly MessageLike[]): Set<string> {
if (id) callIds.add(id)
}
}
} else if (message.role === "toolResult") {
if (message.toolCallId) resultIds.add(message.toolCallId)
} else if (message.role === "toolResult" && message.toolCallId) {
resultIds.add(message.toolCallId)
}
}
return new Set([...callIds].filter((id) => resultIds.has(id)))
return { callIds, resultIds }
}
export function messagesToCC(
@@ -187,7 +226,7 @@ export function messagesToCC(
if (!allowImages) assertTextOnlyMessages(messages)
const out: unknown[] = []
const pairedToolCallIds = completeToolCallIds(messages)
const { callIds, resultIds } = toolCallState(messages)
for (const message of messages ?? []) {
if (message.role === "user" || message.role === "developer") {
@@ -202,23 +241,42 @@ export function messagesToCC(
})
} else if (message.role === "assistant") {
const parts: unknown[] = []
const missingResults: unknown[] = []
for (const content of recordArray(message.content)) {
if (content.type === "text") {
parts.push({ type: "text", text: stringValue(content.text) ?? "" })
} else if (content.type === "toolCall") {
const toolCallId = stringValue(content.id) ?? ""
if (!pairedToolCallIds.has(toolCallId)) continue
const toolName = stringValue(content.name) ?? ""
if (!toolCallId) continue
parts.push({
type: "tool-call",
toolCallId,
toolName: stringValue(content.name) ?? "",
toolName,
input: recordOrEmpty(content.arguments),
})
if (!resultIds.has(toolCallId)) {
missingResults.push({
type: "tool-result",
toolCallId,
toolName,
output: {
type: "error-text",
value: "No result — the tool call did not complete (interrupted or lost).",
},
})
}
}
}
if (parts.length > 0) out.push({ role: "assistant", content: parts })
if (missingResults.length > 0) out.push({ role: "tool", content: missingResults })
} else if (message.role === "toolResult") {
if (!message.toolCallId || !pairedToolCallIds.has(message.toolCallId)) continue
if (!message.toolCallId || !callIds.has(message.toolCallId)) continue
const images = imageParts(message.content)
const text = textContent(message)
const outputText =
text ||
(images.length > 0 && !allowImages ? "[Image omitted: model does not support images]" : "")
out.push({
role: "tool",
content: [
@@ -227,15 +285,13 @@ export function messagesToCC(
toolCallId: message.toolCallId,
toolName: message.toolName,
output: message.isError
? { type: "error-text", value: textContent(message) }
: { type: "text", value: textContent(message) },
? { type: "error-text", value: outputText }
: { type: "text", value: outputText },
},
],
})
const images = imageParts(message.content)
if (images.length > 0) {
if (!allowImages) throw imageContentError("tool results")
if (images.length > 0 && allowImages) {
out.push({
role: "user",
content: images.map(imageToCommandCode),
+122 -28
View File
@@ -7,6 +7,7 @@
import { randomUUID } from "node:crypto"
import { COMMAND_CODE_CLI_VERSION } from "./commandcode-catalog.ts"
import { commandCodeErrorMessage, redactCommandCodeErrorText } from "./overflow.ts"
import { modelSupportsImageInput } from "./models.ts"
import {
@@ -43,7 +44,7 @@ export * from "./overflow.ts"
export * from "./types.ts"
export const DEFAULT_API_BASE = "https://api.commandcode.ai"
export const COMMAND_CODE_CLI_VERSION = "1.15.1"
export { COMMAND_CODE_CLI_VERSION }
const DEFAULT_GENERATE_MAX_TOKENS = 64_000
const DEFAULT_MAX_RETRIES = 0
@@ -147,6 +148,10 @@ function mappedReasoningEffort(model: ModelLike, options?: StreamOptions): strin
return typeof mapped === "string" && mapped !== "off" ? mapped : undefined
}
function isUuid(value: string): boolean {
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)
}
export function projectSlugFromPath(pathName: string): string {
const slug = pathName
.toLowerCase()
@@ -231,17 +236,15 @@ export function createStreamCommandCode(deps: CoreDependencies) {
const stream = deps.createStream()
async function run() {
// OMP may pass the legacy env-var name "COMMANDCODE_API_KEY" (old pi)
// or "$COMMANDCODE_API_KEY" (new pi) as the apiKey value instead of
// resolving it. Filter out these specific strings.
const LEGACY_API_KEY_REF = "$COMMANDCODE_API_KEY"
const OLD_API_KEY_REF = "COMMANDCODE_API_KEY"
// Some hosts pass a literal env-var reference instead of resolving it.
const PLACEHOLDER_API_KEYS = new Set([
"$COMMAND_CODE_API_KEY",
"COMMAND_CODE_API_KEY",
"$COMMANDCODE_API_KEY",
"COMMANDCODE_API_KEY",
])
const hostKey =
options?.apiKey &&
options.apiKey !== LEGACY_API_KEY_REF &&
options.apiKey !== OLD_API_KEY_REF
? options.apiKey
: undefined
options?.apiKey && !PLACEHOLDER_API_KEYS.has(options.apiKey) ? options.apiKey : undefined
const apiKey =
hostKey ??
@@ -261,7 +264,7 @@ export function createStreamCommandCode(deps: CoreDependencies) {
usage: defaultUsage(),
stopReason: "error",
errorMessage:
"No Command Code API key. Run /login and select Command Code, set the COMMANDCODE_API_KEY env var, or configure ~/.commandcode/auth.json, ~/.pi/agent/auth.json or ~/.omp/agent/auth.json",
"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",
timestamp: now(),
}
stream.push({ type: "error", reason: "error", error: msg })
@@ -285,6 +288,10 @@ export function createStreamCommandCode(deps: CoreDependencies) {
let textBlock: TextContent | undefined
let currentTextIdx = -1
let thinkingIdx = -1
const streamingToolCalls = new Map<
string,
{ contentIndex: number; toolCall: ToolCallContent; partialArgs: string }
>()
let finished = false
const abortUpstream = () => {
@@ -397,25 +404,81 @@ export function createStreamCommandCode(deps: CoreDependencies) {
break
}
case "tool-input-start": {
endTextBlock()
endThinking()
const id = stringValue(event.id)
if (!id || streamingToolCalls.has(id)) break
const toolCall: ToolCallContent = {
type: "toolCall",
id,
name: stringValue(event.toolName) ?? "",
arguments: {},
}
output.content.push(toolCall)
const contentIndex = output.content.length - 1
streamingToolCalls.set(id, { contentIndex, toolCall, partialArgs: "" })
stream.push({
type: "toolcall_start",
contentIndex,
partial: output,
})
break
}
case "tool-input-delta": {
const id = stringValue(event.id)
const delta = stringValue(event.delta)
if (!id || delta === undefined) break
const active = streamingToolCalls.get(id)
if (!active) break
active.partialArgs += delta
active.toolCall.arguments = recordOrEmpty(active.partialArgs)
stream.push({
type: "toolcall_delta",
contentIndex: active.contentIndex,
delta,
partial: output,
})
break
}
case "tool-input-end": {
break
}
case "tool-call": {
endTextBlock()
endThinking()
const toolCall: ToolCallContent = {
const id = stringValue(event.toolCallId) ?? ""
const active = streamingToolCalls.get(id)
const toolCall: ToolCallContent = active?.toolCall ?? {
type: "toolCall",
id: stringValue(event.toolCallId) ?? "",
id,
name: stringValue(event.toolName) ?? "",
arguments: recordOrEmpty(event.input ?? event.args ?? event.arguments),
arguments: {},
}
toolCall.name = stringValue(event.toolName) ?? toolCall.name
toolCall.arguments = recordOrEmpty(event.input ?? event.args ?? event.arguments)
let contentIndex: number
if (active) {
contentIndex = active.contentIndex
streamingToolCalls.delete(id)
} else {
output.content.push(toolCall)
contentIndex = output.content.length - 1
stream.push({
type: "toolcall_start",
contentIndex,
partial: output,
})
}
output.content.push(toolCall)
const idx = output.content.length - 1
stream.push({
type: "toolcall_start",
contentIndex: idx,
partial: output,
})
stream.push({
type: "toolcall_end",
contentIndex: idx,
contentIndex,
toolCall,
partial: output,
})
@@ -423,6 +486,15 @@ export function createStreamCommandCode(deps: CoreDependencies) {
}
case "finish": {
const rawFinishReason = stringValue(event.rawFinishReason)
if (
rawFinishReason &&
/^(?:network|connection|upstream)[-_\s]?error$/i.test(rawFinishReason)
) {
throw new Error(
`Provider finished with reason "${rawFinishReason}" — upstream connection failed mid-stream`,
)
}
const usage = commandCodeUsage(event)
if (usage) {
const details = commandCodeInputTokenDetails(usage)
@@ -446,6 +518,10 @@ export function createStreamCommandCode(deps: CoreDependencies) {
break
}
case "abort": {
throw abortError("Request aborted")
}
case "error": {
const message =
commandCodeErrorMessage(event.error) ??
@@ -463,7 +539,11 @@ export function createStreamCommandCode(deps: CoreDependencies) {
if (controller.signal.aborted) throw abortError("Aborted")
const workingDir = cwd()
const threadId = uuid()
const threadId = options?.sessionId
? isUuid(options.sessionId)
? options.sessionId
: undefined
: uuid()
const reasoningEffort = mappedReasoningEffort(model, options)
const timeoutMs = options?.timeoutMs
@@ -491,8 +571,8 @@ export function createStreamCommandCode(deps: CoreDependencies) {
tools: toolsToJson(context.tools),
system: systemPromptToText(context.systemPrompt),
max_tokens: generateMaxTokens(model, options),
temperature: 0.3,
stream: true,
...(options?.temperature !== undefined ? { temperature: options.temperature } : {}),
...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),
},
threadId,
@@ -522,7 +602,8 @@ export function createStreamCommandCode(deps: CoreDependencies) {
"x-cli-environment": "production",
"x-project-slug": projectSlugFromPath(workingDir),
"x-taste-learning": "true",
"x-co-flag": "false",
...(options?.sessionId ? { "x-session-id": options.sessionId } : {}),
"User-Agent": "cli",
...options?.headers,
}
const bodyStr = JSON.stringify(body)
@@ -635,6 +716,11 @@ export function createStreamCommandCode(deps: CoreDependencies) {
const { done, value } = await raceAbort(reader.read(), attemptController.signal)
if (done) {
if (buffer.trim()) handleEvent(parseStreamEventLine(buffer))
if (!finished) {
throw new Error(
"Stream ended unexpectedly before completion (no finish event) — response was truncated",
)
}
break
}
if (controller.signal.aborted) throw abortError("Aborted")
@@ -658,7 +744,12 @@ export function createStreamCommandCode(deps: CoreDependencies) {
} catch {}
reader = undefined
if (controller.signal.aborted) throw streamError
if (
controller.signal.aborted ||
(streamError instanceof Error && streamError.name === "AbortError")
) {
throw streamError
}
// Never retry after visible content was emitted (including timeout mid-stream).
const canRetry = output.content.length === 0 && attempt < maxRetries
@@ -695,7 +786,10 @@ export function createStreamCommandCode(deps: CoreDependencies) {
}
}
} catch (error: unknown) {
const reason: ErrorReason = controller.signal.aborted ? "aborted" : "error"
const reason: ErrorReason =
controller.signal.aborted || (error instanceof Error && error.name === "AbortError")
? "aborted"
: "error"
output.stopReason = reason
output.errorMessage =
reason === "aborted"
+50 -97
View File
@@ -1,58 +1,26 @@
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
import { dirname } from "node:path"
export const DEFAULT_MODELS_URL = "https://api.commandcode.ai/provider/v1/models"
import {
MODEL_EFFORTS,
MODEL_INPUT_MODALITIES,
MODEL_MAX_OUTPUT_TOKENS,
MODEL_REASONING,
type CommandCodeInputType,
type CommandCodeReasoningEffort,
} from "./commandcode-catalog.ts"
export { MODEL_EFFORTS, MODEL_INPUT_MODALITIES, MODEL_MAX_OUTPUT_TOKENS, MODEL_REASONING }
export type { CommandCodeInputType }
export const DEFAULT_PROVIDER_API_BASE = "https://api.commandcode.ai/provider/v1"
export const DEFAULT_MODELS_URL = `${DEFAULT_PROVIDER_API_BASE}/models`
export const DEFAULT_MODELS_TIMEOUT_MS = 10_000
const DEFAULT_MAX_OUTPUT_TOKENS = 65_536
const MODEL_CACHE_VERSION = 1
export type CommandCodeInputType = "text" | "image"
/**
* Model input modalities from the command-code@1.15.1 bundled catalog.
* Models omitted here remain text-only so newly discovered IDs never claim
* image support without upstream evidence.
*/
export const MODEL_INPUT_MODALITIES: Readonly<Record<string, readonly CommandCodeInputType[]>> = {
"MiniMaxAI/MiniMax-M3": ["text", "image"],
"Qwen/Qwen3.6-Plus": ["text", "image"],
"Qwen/Qwen3.7-Flash": ["text", "image"],
"Qwen/Qwen3.7-Plus": ["text", "image"],
"Qwen/Qwen3.8-Max": ["text", "image"],
"claude-fable-5": ["text", "image"],
"claude-haiku-4-5-20251001": ["text", "image"],
"claude-opus-4-7": ["text", "image"],
"claude-opus-4-8": ["text", "image"],
"claude-opus-5": ["text", "image"],
"claude-sonnet-4-6": ["text", "image"],
"claude-sonnet-5": ["text", "image"],
"google/gemini-3.1-flash-lite": ["text", "image"],
"google/gemini-3.5-flash": ["text", "image"],
"google/gemini-3.5-flash-lite": ["text", "image"],
"google/gemini-3.6-flash": ["text", "image"],
"gpt-5.3-codex": ["text", "image"],
"gpt-5.4": ["text", "image"],
"gpt-5.4-mini": ["text", "image"],
"gpt-5.5": ["text", "image"],
"gpt-5.6-luna": ["text", "image"],
"gpt-5.6-sol": ["text", "image"],
"gpt-5.6-terra": ["text", "image"],
"meta/muse-spark-1.1": ["text", "image"],
"meta/muse-spark-1.2": ["text", "image"],
"meta/muse-spark-1.2-contributor": ["text", "image"],
"moonshotai/Kimi-K2.5": ["text", "image"],
"moonshotai/Kimi-K2.6": ["text", "image"],
"moonshotai/Kimi-K2.7-Code": ["text", "image"],
"moonshotai/Kimi-K2.7-Code-Highspeed": ["text", "image"],
"moonshotai/Kimi-K3": ["text", "image"],
"sakana/fugu-ultra": ["text", "image"],
"stepfun/Step-3.7-Flash": ["text", "image"],
"thinkingmachines/inkling": ["text", "image"],
"thinkingmachines/inkling-small": ["text", "image"],
"xai/grok-4.5": ["text", "image"],
"xiaomi/mimo-v2.5": ["text", "image"],
}
export type CommandCodeApi = "openai-completions" | "anthropic-messages"
const TEXT_INPUT_ONLY = ["text"] as const
@@ -66,43 +34,6 @@ export function modelSupportsImageInput(modelId: string): boolean {
export type PiThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
type CommandCodeReasoningEffort = Exclude<PiThinkingLevel, "off">
/**
* Per-model reasoning efforts supported by Command Code's generate endpoint.
*
* The Provider API does not expose reasoning metadata. This is an exact
* snapshot of `reasoningEfforts` from the command-code@1.15.1 model catalog
* (`packages/shared/src/model-catalog.ts`, also published in the generated
* `dist/bundled/command-code-knowledge/reference/models.md`). Models omitted
* here let Command Code choose their reasoning depth, matching the CLI.
*/
export const MODEL_EFFORTS: Readonly<Record<string, readonly CommandCodeReasoningEffort[]>> = {
"Qwen/Qwen3.8-Max": ["low", "medium", "xhigh"],
"claude-fable-5": ["low", "medium", "high", "xhigh", "max"],
"claude-opus-4-7": ["low", "medium", "high", "xhigh", "max"],
"claude-opus-4-8": ["low", "medium", "high", "xhigh", "max"],
"claude-opus-5": ["low", "medium", "high", "xhigh", "max"],
"claude-sonnet-4-6": ["low", "medium", "high", "xhigh", "max"],
"claude-sonnet-5": ["low", "medium", "high", "xhigh", "max"],
"deepseek/deepseek-v4-flash": ["high", "max"],
"deepseek/deepseek-v4-pro": ["high", "max"],
"gpt-5.3-codex": ["low", "medium", "high", "xhigh"],
"gpt-5.4": ["low", "medium", "high", "xhigh"],
"gpt-5.4-mini": ["low", "medium", "high"],
"gpt-5.5": ["low", "medium", "high", "xhigh"],
"gpt-5.6-luna": ["low", "medium", "high", "xhigh", "max"],
"gpt-5.6-sol": ["low", "medium", "high", "xhigh", "max"],
"gpt-5.6-terra": ["low", "medium", "high", "xhigh", "max"],
"google/gemini-3.1-flash-lite": ["low", "medium", "high"],
"google/gemini-3.5-flash": ["low", "medium", "high"],
"google/gemini-3.5-flash-lite": ["low", "medium", "high"],
"google/gemini-3.6-flash": ["low", "medium", "high"],
"sakana/fugu-ultra": ["high", "xhigh"],
"xai/grok-4.5": ["low", "medium", "high"],
"zai-org/GLM-5.2": ["high", "max"],
}
const PI_THINKING_LEVELS: readonly PiThinkingLevel[] = [
"off",
"minimal",
@@ -126,7 +57,7 @@ export function thinkingLevelMapForEfforts(
export interface ThinkingMetadata {
thinkingLevelMap: Partial<Record<PiThinkingLevel, string | null>>
thinking: {
thinking?: {
mode: "effort"
effortMap: Partial<Record<CommandCodeReasoningEffort, string>>
efforts: readonly CommandCodeReasoningEffort[]
@@ -135,19 +66,26 @@ export interface ThinkingMetadata {
export function thinkingMetadataForModel(modelId: string): ThinkingMetadata | undefined {
const efforts = MODEL_EFFORTS[modelId]
if (!efforts) return undefined
return {
thinkingLevelMap: thinkingLevelMapForEfforts(efforts),
thinking: {
mode: "effort",
effortMap: Object.fromEntries(efforts.map((effort) => [effort, effort])),
efforts,
},
if (efforts) {
return {
thinkingLevelMap: thinkingLevelMapForEfforts(efforts),
thinking: {
mode: "effort",
effortMap: Object.fromEntries(efforts.map((effort) => [effort, effort])),
efforts,
},
}
}
if (!isReasoningModel(modelId)) return undefined
return { thinkingLevelMap: thinkingLevelMapForEfforts([]) }
}
function isReasoningModel(modelId: string): boolean {
return MODEL_EFFORTS[modelId] !== undefined
return MODEL_REASONING[modelId] === true
}
function maxOutputTokensForModel(modelId: string, contextLength: number): number {
return Math.min(contextLength, MODEL_MAX_OUTPUT_TOKENS[modelId] ?? DEFAULT_MAX_OUTPUT_TOKENS)
}
interface ApiModel {
@@ -159,11 +97,22 @@ interface ApiModel {
export interface CommandCodeModel {
id: string
name: string
api: CommandCodeApi
reasoning: boolean
contextWindow: number
maxTokens: number
}
export function apiForModelId(id: string): CommandCodeApi {
return id.startsWith("claude-") ? "anthropic-messages" : "openai-completions"
}
export function baseUrlForModel(apiBase: string, api: CommandCodeApi): string {
const normalized = apiBase.replace(/\/+$/g, "")
if (api !== "anthropic-messages") return normalized
return normalized.endsWith("/v1") ? normalized.slice(0, -3) : normalized
}
interface FetchCommandCodeModelsOptions {
url?: string
fetchImpl?: typeof fetch
@@ -222,12 +171,15 @@ function parseCachedModel(value: unknown): CommandCodeModel {
const id = stringField(value, "id")
booleanField(value, "reasoning")
positiveNumberField(value, "maxTokens")
const contextWindow = positiveNumberField(value, "contextWindow")
return {
id,
name: stringField(value, "name"),
api: apiForModelId(id),
reasoning: isReasoningModel(id),
contextWindow: positiveNumberField(value, "contextWindow"),
maxTokens: positiveNumberField(value, "maxTokens"),
contextWindow,
maxTokens: maxOutputTokensForModel(id, contextWindow),
}
}
@@ -329,9 +281,10 @@ export function commandCodeModelsFromApiResponse(value: unknown): readonly Comma
return data.map(parseApiModel).map((model) => ({
id: model.id,
name: `${model.name} (CC)`,
api: apiForModelId(model.id),
reasoning: isReasoningModel(model.id),
contextWindow: model.contextLength,
maxTokens: Math.min(model.contextLength, DEFAULT_MAX_OUTPUT_TOKENS),
maxTokens: maxOutputTokensForModel(model.id, model.contextLength),
}))
}
+84 -22
View File
@@ -1,13 +1,13 @@
/**
* Command Code OAuth provider for pi's /login flow.
*
* Implements a browser-assisted API key retrieval flow:
* 1. Starts a local HTTP server on a Command Code CLI-compatible port
* 2. Opens the Command Code Studio auth page in the browser
* 3. The user authenticates on the Command Code website
* 4. The website POSTs the API key back to the local server
* 5. If browser transfer fails, the user can paste the API key manually
* 6. The API key is stored in pi's auth.json as OAuth credentials
* Implements two API key retrieval flows:
* 1. Browser-assisted login opens Command Code Studio and waits for the
* website to POST the API key back to a local callback server.
* 2. Direct API key login prompts the user to paste a Studio API key.
*
* If browser transfer fails, the user can still paste the API key manually.
* The API key is stored in pi's auth.json as OAuth credentials.
*
* Since Command Code API keys don't expire, we store them as
* OAuth credentials with a far-future expiry.
@@ -18,7 +18,8 @@ import { startAuthServer } from "./auth-server.ts"
const STUDIO_BASE_URL = "https://commandcode.ai"
const TEN_YEARS_MS = 10 * 365 * 24 * 60 * 60 * 1000 // API keys don't expire
const DEFAULT_AUTH_TIMEOUT_MS = 15_000
const DEFAULT_AUTH_TIMEOUT_MS = 120_000
const DEFAULT_API_BASE = "https://api.commandcode.ai"
export interface OAuthLoginCallbacks {
onAuth(params: { url: string }): void
@@ -95,22 +96,70 @@ export function sanitizeApiKey(input: string): string {
.trim()
}
export async function validateApiKey(
apiKey: string,
options: { fetchImpl?: typeof fetch; apiBase?: string } = {},
): Promise<void> {
let response: Response
try {
response = await (options.fetchImpl ?? fetch)(
`${options.apiBase ?? DEFAULT_API_BASE}/alpha/whoami`,
{
headers: { Authorization: `Bearer ${apiKey}` },
},
)
} catch (error) {
throw new Error(
`Could not validate the Command Code API key: ${error instanceof Error ? error.message : String(error)}`,
)
}
if (response.status === 401) throw new Error("Invalid Command Code API key")
if (!response.ok) {
throw new Error(`Could not validate the Command Code API key (${response.status})`)
}
}
async function promptForApiKey(callbacks: OAuthLoginCallbacks, message: string) {
const apiKey = sanitizeApiKey(await callbacks.onPrompt({ message }))
if (!apiKey) throw new Error("No Command Code API key provided")
await validateApiKey(apiKey)
return credentialsFromApiKey(apiKey)
}
/**
* Starts the browser-based login flow for Command Code.
*
* Returns OAuth credentials where access == refresh == the user's API key.
* The keys don't expire, so we set a far-future expiry.
*/
export async function login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
type LoginChoice = { type: "browser" } | { type: "prompt" } | { type: "apiKey"; apiKey: string }
async function chooseLoginFlow(callbacks: OAuthLoginCallbacks): Promise<LoginChoice> {
const input = sanitizeApiKey(
await callbacks.onPrompt({
message:
"Command Code login: press Enter for browser login, type 'key' to paste an API key, or paste the API key directly:",
}),
)
const normalized = input.toLowerCase()
if (!input || normalized === "1" || normalized === "b" || normalized === "browser") {
return { type: "browser" }
}
if (
normalized === "2" ||
normalized === "k" ||
normalized === "key" ||
normalized === "api" ||
normalized === "paste"
) {
return { type: "prompt" }
}
return { type: "apiKey", apiKey: input }
}
async function browserLogin(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
const stateToken = generateStateToken()
let authServer
try {
authServer = await startAuthServer()
authServer = await startAuthServer({ expectedState: stateToken })
} catch {
return promptForApiKey(
callbacks,
@@ -118,7 +167,6 @@ export async function login(callbacks: OAuthLoginCallbacks): Promise<OAuthCreden
)
}
const stateToken = generateStateToken()
const callbackUrl = `http://localhost:${authServer.port}/callback`
const authUrl = `${STUDIO_BASE_URL}/studio/auth/cli?callback=${encodeURIComponent(callbackUrl)}&state=${encodeURIComponent(stateToken)}`
@@ -142,13 +190,27 @@ export async function login(callbacks: OAuthLoginCallbacks): Promise<OAuthCreden
throw error
}
// Validate state token to prevent CSRF.
if (callback.state !== stateToken) {
authServer.server.close()
throw new Error("State token mismatch. Authentication may have been tampered with.")
return credentialsFromApiKey(callback.apiKey)
}
/**
* Starts the login flow for Command Code.
*
* Returns OAuth credentials where access == refresh == the user's API key.
* The keys don't expire, so we set a far-future expiry.
*/
export async function login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
const choice = await chooseLoginFlow(callbacks)
if (choice.type === "apiKey") {
await validateApiKey(choice.apiKey)
return credentialsFromApiKey(choice.apiKey)
}
if (choice.type === "prompt") {
return promptForApiKey(callbacks, "Paste your Command Code API key:")
}
return credentialsFromApiKey(callback.apiKey)
return browserLogin(callbacks)
}
/**
+53 -45
View File
@@ -20,7 +20,7 @@ export interface TemporaryPricing {
}
export const PRICING_SOURCE_URL = "https://commandcode.ai/docs/resources/pricing-limits"
export const PRICING_LAST_VERIFIED = "2026-08-04"
export const PRICING_LAST_VERIFIED = "2026-08-25"
export const ZERO_MODEL_COST: CommandCodeModelCost = {
input: 0,
@@ -40,7 +40,7 @@ export const ZERO_MODEL_COST: CommandCodeModelCost = {
export const MODEL_COSTS: Readonly<Record<string, CommandCodeModelCost>> = {
// Free models
"poolside/laguna-s-2.1-free": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
"inclusionai/ling-3.0-flash-free": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
"stealth/ox-alpha": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
// Open and open-weight models
"tencent/hy3-paid": { input: 0.14, output: 0.58, cacheRead: 0.035, cacheWrite: 0 },
@@ -54,6 +54,7 @@ export const MODEL_COSTS: Readonly<Record<string, CommandCodeModelCost>> = {
},
"moonshotai/Kimi-K2.6": { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0 },
"moonshotai/Kimi-K2.5": { input: 0.6, output: 3, cacheRead: 0.1, cacheWrite: 0 },
"zai-org/GLM-5.3": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
"zai-org/GLM-5.2": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
"zai-org/GLM-5.2-Fast": { input: 3, output: 10.25, cacheRead: 0.5, cacheWrite: 0 },
"zai-org/GLM-5.1": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
@@ -61,20 +62,28 @@ export const MODEL_COSTS: Readonly<Record<string, CommandCodeModelCost>> = {
"MiniMaxAI/MiniMax-M3": { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
"MiniMaxAI/MiniMax-M2.7": { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
"MiniMaxAI/MiniMax-M2.5": { input: 0.3, output: 1.2, cacheRead: 0.03, cacheWrite: 0 },
// Permanent 75% discount.
// DeepSeek V4 uses time-dependent rates. Display the documented off-peak
// rates, which apply for 17 hours per day; the Usage page remains authoritative.
"deepseek/deepseek-v4-pro": {
input: 0.435,
output: 0.87,
cacheRead: 0.003625,
input: 0.66,
output: 1.98,
cacheRead: 0.022,
cacheWrite: 0,
},
"deepseek/deepseek-v4-flash": {
input: 0.14,
output: 0.28,
cacheRead: 0.0028,
input: 0.22,
output: 0.66,
cacheRead: 0.007,
cacheWrite: 0,
},
"deepseek/deepseek-v4-flash-vision-exp": {
input: 0.22,
output: 0.66,
cacheRead: 0.007,
cacheWrite: 0,
},
"Qwen/Qwen3.8-Max": { input: 2, output: 6, cacheRead: 0.25, cacheWrite: 2.5 },
"Qwen/Qwen3.8-27B": { input: 0.4, output: 3, cacheRead: 0.04, cacheWrite: 0 },
"Qwen/Qwen3.7-Max": { input: 2.5, output: 7.5, cacheRead: 0.5, cacheWrite: 3.13 },
"Qwen/Qwen3.7-Plus": {
input: 0.4,
@@ -140,6 +149,13 @@ export const MODEL_COSTS: Readonly<Record<string, CommandCodeModelCost>> = {
cacheWrite: 0,
},
"meta/muse-spark-1.1": { input: 1.25, output: 4.25, cacheRead: 0.15, cacheWrite: 0 },
"meta/muse-spark-1.2": { input: 1.25, output: 4.25, cacheRead: 0.15, cacheWrite: 0 },
"meta/muse-spark-1.2-contributor": {
input: 0.1,
output: 0.2,
cacheRead: 0.002,
cacheWrite: 0,
},
// Anthropic
// Introductory pricing through 2026-08-31.
@@ -158,43 +174,20 @@ export const MODEL_COSTS: Readonly<Record<string, CommandCodeModelCost>> = {
// OpenAI
"gpt-5.6-sol": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 },
// Discounted rates through 2026-08-14.
"gpt-5.6-terra": {
input: 1,
output: 6,
cacheRead: 0.1,
cacheWrite: 1.25,
tiers: [
{
inputTokensAbove: 272_000,
input: 2,
output: 9,
cacheRead: 0.2,
cacheWrite: 2.5,
},
],
},
"gpt-5.6-luna": {
input: 0.1,
output: 0.6,
cacheRead: 0.01,
cacheWrite: 0.125,
tiers: [
{
inputTokensAbove: 272_000,
input: 0.2,
output: 0.9,
cacheRead: 0.02,
cacheWrite: 0.25,
},
],
},
"gpt-5.6-terra": { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 2.5 },
"gpt-5.6-luna": { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite: 0.25 },
"gpt-5.5": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
"gpt-5.4": { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 },
"gpt-5.3-codex": { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 0 },
"gpt-5.4-mini": { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },
// Google and xAI
"google/gemini-3.7-flash": {
input: 0.75,
output: 3.75,
cacheRead: 0.075,
cacheWrite: 0.04167,
},
"google/gemini-3.6-flash": { input: 1.5, output: 7.5, cacheRead: 0.15, cacheWrite: 0 },
"google/gemini-3.5-flash": { input: 1.5, output: 9, cacheRead: 0.15, cacheWrite: 0 },
"google/gemini-3.5-flash-lite": {
@@ -210,17 +203,32 @@ export const MODEL_COSTS: Readonly<Record<string, CommandCodeModelCost>> = {
cacheWrite: 0,
},
"xai/grok-4.5": { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 },
"xai/grok-4.6": {
input: 2,
output: 6,
cacheRead: 0.5,
cacheWrite: 0,
tiers: [
{
inputTokensAbove: 200_000,
input: 4,
output: 12,
cacheRead: 1,
cacheWrite: 0,
},
],
},
}
export const TEMPORARY_PRICING: readonly TemporaryPricing[] = [
{
models: ["gpt-5.6-terra", "gpt-5.6-luna"],
expiresOn: "2026-08-14",
description: "50% promotional rates",
},
{
models: ["claude-sonnet-5"],
expiresOn: "2026-08-31",
description: "introductory pricing",
},
{
models: ["google/gemini-3.7-flash"],
expiresOn: "2026-12-31",
description: "50% promotional pricing",
},
]
+66
View File
@@ -0,0 +1,66 @@
import { getConfiguredApiKey } from "./api-key.ts"
import { pickCommandCodeApiKey } from "./converters.ts"
import { fetchCommandCodeQuota, redactValue } from "./quota.ts"
import { formatQuota } from "./quota-format.ts"
export interface QuotaCommandContext {
waitForIdle?: () => Promise<void>
modelRegistry?: {
getApiKeyForProvider?: (provider: string) => Promise<string | undefined>
}
ui: {
notify(message: string, type?: "info" | "warning" | "error"): void
}
}
interface QuotaCommandApi {
registerCommand(
name: string,
options: {
description: string
handler: (args: string, ctx: QuotaCommandContext) => Promise<void>
},
): void
}
interface RegisterQuotaCommandOptions {
apiBase: string
headers?: Record<string, string>
getConfiguredKey?: () => string | undefined
fetchQuota?: typeof fetchCommandCodeQuota
}
export function registerCommandCodeQuota(
pi: QuotaCommandApi,
options: RegisterQuotaCommandOptions,
): void {
const getConfiguredKey = options.getConfiguredKey ?? getConfiguredApiKey
const fetchQuota = options.fetchQuota ?? fetchCommandCodeQuota
pi.registerCommand("commandcode-quota", {
description: "Show Command Code account usage and quota",
handler: async (_args, ctx) => {
await ctx.waitForIdle?.()
const registryKey = await ctx.modelRegistry?.getApiKeyForProvider?.("commandcode")
const apiKey = pickCommandCodeApiKey(registryKey, getConfiguredKey())
if (!apiKey) {
ctx.ui.notify(
"Command Code quota requires an API key. Run /login and select Command Code, or set COMMAND_CODE_API_KEY.",
"warning",
)
return
}
const result = await fetchQuota({
apiKey,
baseUrl: options.apiBase,
extraHeaders: options.headers,
})
if (!result.ok) {
ctx.ui.notify(redactValue(result.error.message), "error")
return
}
ctx.ui.notify(formatQuota(result.quota), "info")
},
})
}
+111
View File
@@ -0,0 +1,111 @@
import type {
CommandCodeCredits,
CommandCodeQuota,
CommandCodeSubscription,
CommandCodeWindowLimit,
} from "./quota-types.ts"
export function formatWindowLimits(
limits: readonly CommandCodeWindowLimit[],
now: () => number = Date.now,
): string[] {
const labels: Record<CommandCodeWindowLimit["window"], string> = {
fiveHour: "5-hour",
weekly: "Weekly",
}
return limits.map((limit) => {
const used = limit.used.toFixed(2)
const cap = limit.cap.toFixed(2)
const percent = limit.cap > 0 ? Math.round((limit.used / limit.cap) * 100) : 0
const reset = limit.resetAt === null ? "" : ` (resets ${formatResetClock(limit.resetAt, now)})`
return `${labels[limit.window]}: ${used} / ${cap} credits (${percent}% used)${reset}`
})
}
function formatResetClock(resetAtSeconds: number, now: () => number): string {
const date = new Date(resetAtSeconds * 1000)
if (Number.isNaN(date.getTime())) return "unknown"
const diffMs = date.getTime() - now()
if (diffMs <= 0) return "soon"
const minutes = Math.ceil(diffMs / 60_000)
if (minutes < 60) return `in ${minutes}m`
const hours = Math.floor(minutes / 60)
const remainingMinutes = minutes % 60
if (hours < 24) {
return remainingMinutes > 0 ? `in ${hours}h ${remainingMinutes}m` : `in ${hours}h`
}
const days = Math.floor(hours / 24)
return days === 1 ? "in 1 day" : `in ${days} days`
}
function creditsDetail(credits: CommandCodeCredits | null): string | undefined {
if (!credits) return undefined
const parts = [
`monthly $${credits.monthlyCredits.toFixed(2)}`,
`purchased $${credits.purchasedCredits.toFixed(2)}`,
]
if (credits.freeCredits > 0) parts.push(`free $${credits.freeCredits.toFixed(2)}`)
return `Sources: ${parts.join(" / ")}`
}
function subscriptionLine(subscription: CommandCodeSubscription): string {
const plan = (subscription.planId ?? "Unknown").replace(/[_-]+/g, " ").trim()
const status = subscription.status ? ` (${subscription.status})` : ""
return `Plan: ${plan}${status}`
}
function formatTokens(tokens: number): string {
if (tokens >= 1_000_000_000) return `${(tokens / 1_000_000_000).toFixed(1)}B`
if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`
if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}k`
return String(tokens)
}
export function formatQuota(quota: CommandCodeQuota, now: () => number = Date.now): string {
const lines: string[] = []
const remaining = quota.credits?.remainingCredits ?? 0
const spent = quota.summary?.totalCost ?? 0
const pool = remaining + spent
if (quota.credits || quota.summary) {
lines.push("Credits")
lines.push(` Remaining: $${remaining.toFixed(2)} of $${pool.toFixed(2)}`)
lines.push(` Used: $${spent.toFixed(2)}`)
lines.push(` ${pool > 0 ? Math.round((spent / pool) * 100) : 0}% used`)
}
const detail = creditsDetail(quota.credits)
if (detail) lines.push(detail)
if (quota.subscription) lines.push(subscriptionLine(quota.subscription))
if (quota.summary) {
lines.push("")
lines.push(quota.subscription?.currentPeriodStart ? "Usage (billing period)" : "Usage")
lines.push(` Cost: $${quota.summary.totalCost.toFixed(2)}`)
lines.push(` Requests: ${quota.summary.totalCount.toLocaleString("en-US")}`)
if (quota.summary.totalTokens !== undefined) {
lines.push(` Tokens: ${formatTokens(quota.summary.totalTokens)}`)
}
}
lines.push("")
lines.push("Account")
lines.push(` ${quota.account.keyName ?? quota.account.login}`)
const limits = quota.credits?.windowLimits ?? []
if (limits.length > 0) {
lines.push("")
lines.push("Usage windows:")
lines.push(...formatWindowLimits(limits, now).map((line) => ` ${line}`))
}
if ((quota.unavailable?.length ?? 0) > 0) {
lines.push("")
lines.push(`Unavailable: ${quota.unavailable?.join(", ")}`)
}
lines.push("")
lines.push("Full detail: https://commandcode.ai/usage")
return lines.join("\n")
}
+47
View File
@@ -0,0 +1,47 @@
export interface CommandCodeWindowLimit {
window: "fiveHour" | "weekly"
used: number
cap: number
resetAt: number | null
}
export interface CommandCodeCredits {
monthlyCredits: number
purchasedCredits: number
freeCredits: number
remainingCredits: number
windowLimits: CommandCodeWindowLimit[]
}
export interface CommandCodeSubscription {
planId: string | null
status: string | null
currentPeriodStart: string | null
currentPeriodEnd: string | null
}
export interface CommandCodeUsageSummary {
totalCost: number
totalCount: number
totalTokens?: number
}
export type CommandCodeQuotaSection = "credits" | "subscription" | "usage"
export interface CommandCodeQuota {
account: {
login: string
orgId: string | null
keyName?: string
}
credits: CommandCodeCredits | null
subscription: CommandCodeSubscription | null
summary: CommandCodeUsageSummary | null
unavailable?: readonly CommandCodeQuotaSection[]
}
export type CommandCodeQuotaErrorKind = "config" | "http" | "network" | "timeout"
export type CommandCodeQuotaResult =
| { ok: true; quota: CommandCodeQuota }
| { ok: false; error: { message: string; kind: CommandCodeQuotaErrorKind } }
+335
View File
@@ -0,0 +1,335 @@
import { redactCommandCodeErrorText } from "./overflow.ts"
import type {
CommandCodeCredits,
CommandCodeQuotaResult,
CommandCodeQuotaSection,
CommandCodeSubscription,
CommandCodeUsageSummary,
CommandCodeWindowLimit,
} from "./quota-types.ts"
export const DEFAULT_API_BASE = "https://api.commandcode.ai"
export const QUOTA_TIMEOUT_MS = 15_000
interface FetchOptions {
apiKey: string
baseUrl?: string
fetchImpl?: typeof fetch
timeoutMs?: number
extraHeaders?: Record<string, string>
}
interface HttpErrorShape {
__httpError: true
message: string
status: number
body: string
}
interface QuotaErrorShape {
__quotaError: true
kind: "timeout" | "network"
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value)
}
function numberValue(value: unknown): number | undefined {
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined
}
function stringValue(value: unknown): string | undefined {
return typeof value === "string" && value.length > 0 ? value : undefined
}
function errorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error)
}
function normalizeResetAt(value: unknown): number | null {
let timestamp: number | undefined
if (typeof value === "number" && Number.isFinite(value)) timestamp = value
if (typeof value === "string" && value.length > 0) {
const trimmed = value.trim()
timestamp = /^\d+$/.test(trimmed) ? Number(trimmed) : Date.parse(trimmed)
}
if (timestamp === undefined || !Number.isFinite(timestamp) || timestamp < 0) return null
return timestamp >= 1e12 ? Math.round(timestamp / 1000) : timestamp
}
export function windowLimitsFromCredits(value: unknown): CommandCodeWindowLimit[] {
if (!isRecord(value)) return []
const limits: CommandCodeWindowLimit[] = []
for (const [window, entry] of [
["fiveHour", value.fiveHour],
["weekly", value.weekly],
] as const) {
if (!isRecord(entry)) continue
const used = numberValue(entry.used)
const cap = numberValue(entry.cap)
if (used === undefined || cap === undefined || (used === 0 && cap === 0)) continue
limits.push({ window, used, cap, resetAt: normalizeResetAt(entry.resetAt) })
}
return limits
}
function parseCredits(value: unknown): CommandCodeCredits | null {
if (!isRecord(value) || !isRecord(value.credits)) return null
const credits = value.credits
const monthlyCredits = numberValue(credits.monthlyCredits)
const purchasedCredits = numberValue(credits.purchasedCredits)
const freeCredits = numberValue(credits.freeCredits)
if (monthlyCredits === undefined && purchasedCredits === undefined && freeCredits === undefined) {
return null
}
const monthly = monthlyCredits ?? 0
const purchased = purchasedCredits ?? 0
const free = freeCredits ?? 0
return {
monthlyCredits: monthly,
purchasedCredits: purchased,
freeCredits: free,
remainingCredits: monthly + purchased + free,
windowLimits: windowLimitsFromCredits(value.windowLimits),
}
}
function parseSubscription(value: unknown): CommandCodeSubscription | null {
if (!isRecord(value) || !isRecord(value.data)) return null
const data = value.data
const planId = stringValue(data.planId)
const status = stringValue(data.status)
const currentPeriodStart = stringValue(data.currentPeriodStart)
const currentPeriodEnd = stringValue(data.currentPeriodEnd)
if (!planId && !status && !currentPeriodStart && !currentPeriodEnd) return null
return {
planId: planId ?? null,
status: status ?? null,
currentPeriodStart: currentPeriodStart ?? null,
currentPeriodEnd: currentPeriodEnd ?? null,
}
}
function parseSummary(value: unknown): CommandCodeUsageSummary | null {
if (!isRecord(value)) return null
const totalCost = numberValue(value.totalCost)
const totalCount = numberValue(value.totalCount)
if (totalCost === undefined || totalCount === undefined) return null
const totalTokens = numberValue(value.totalTokens) ?? numberValue(value.tokens)
return { totalCost, totalCount, ...(totalTokens === undefined ? {} : { totalTokens }) }
}
function parseWhoami(value: unknown): {
login: string
orgId: string | null
keyName?: string
} | null {
if (!isRecord(value)) return null
const org = isRecord(value.org) ? value.org : undefined
const user = isRecord(value.user) ? value.user : undefined
const login =
(org ? stringValue(org.login) : undefined) ??
(user ? (stringValue(user.userName) ?? stringValue(user.name)) : undefined)
if (!login) return null
const orgId = org ? stringValue(org.id) : undefined
const keyName = user ? (stringValue(user.keyName) ?? stringValue(user.displayName)) : undefined
return { login, orgId: orgId ?? null, ...(keyName ? { keyName } : {}) }
}
function buildUrl(path: string, params: Record<string, string | undefined>): string {
const search = new URLSearchParams()
for (const [key, value] of Object.entries(params)) {
if (value) search.set(key, value)
}
const query = search.toString()
return `${path}${query ? `?${query}` : ""}`
}
function isHttpError(value: unknown): value is HttpErrorShape {
return (
isRecord(value) &&
value.__httpError === true &&
typeof value.message === "string" &&
typeof value.status === "number" &&
typeof value.body === "string"
)
}
function isQuotaError(value: unknown): value is QuotaErrorShape {
return (
isRecord(value) &&
value.__quotaError === true &&
(value.kind === "timeout" || value.kind === "network")
)
}
function isBlockingHttpError(error: HttpErrorShape): boolean {
return error.status === 401 || error.status === 403
}
function httpFailure(error: HttpErrorShape, context: string): CommandCodeQuotaResult {
const detail = error.body.trim().slice(0, 200)
return {
ok: false,
error: {
kind: "http",
message: redactValue(
`${context} request failed (${error.status}): ${detail || error.message}`,
),
},
}
}
class QuotaTimeoutError extends Error {}
export async function fetchCommandCodeQuota(
options: FetchOptions,
): Promise<CommandCodeQuotaResult> {
if (!options.apiKey) {
return { ok: false, error: { message: "No Command Code API key found", kind: "config" } }
}
const baseUrl = options.baseUrl ?? DEFAULT_API_BASE
const fetchImpl = options.fetchImpl ?? fetch
const timeoutMs = options.timeoutMs ?? QUOTA_TIMEOUT_MS
const overallController = new AbortController()
const overallTimer = setTimeout(() => overallController.abort(), timeoutMs)
const headers = {
accept: "application/json",
Authorization: `Bearer ${options.apiKey}`,
...options.extraHeaders,
}
const request = async (path: string): Promise<unknown> => {
if (overallController.signal.aborted) throw new QuotaTimeoutError()
try {
const response = await fetchImpl(`${baseUrl}${path}`, {
method: "GET",
headers,
signal: overallController.signal,
})
if (!response.ok) {
return {
__httpError: true,
message:
response.status === 401 || response.status === 403
? "Command Code rejected the API key"
: response.statusText,
status: response.status,
body: await response.text().catch(() => ""),
} satisfies HttpErrorShape
}
return await response.json()
} catch (error) {
if (overallController.signal.aborted) throw new QuotaTimeoutError()
throw error
}
}
const safeRequest = async (path: string): Promise<unknown> => {
try {
return await request(path)
} catch (error) {
return {
__quotaError: true,
kind: error instanceof QuotaTimeoutError ? "timeout" : "network",
} satisfies QuotaErrorShape
}
}
try {
const whoamiRaw = await request("/alpha/whoami")
if (isHttpError(whoamiRaw)) return httpFailure(whoamiRaw, "whoami")
const account = parseWhoami(whoamiRaw)
if (!account) {
return {
ok: false,
error: { kind: "http", message: "Command Code returned an unrecognized account response" },
}
}
const orgId = account.orgId ?? undefined
const [creditsRaw, subscriptionRaw] = await Promise.all([
safeRequest(buildUrl("/alpha/billing/credits", { orgId })),
safeRequest(buildUrl("/alpha/billing/subscriptions", { orgId })),
])
if (isHttpError(creditsRaw) && isBlockingHttpError(creditsRaw)) {
return httpFailure(creditsRaw, "credits")
}
if (isHttpError(subscriptionRaw) && isBlockingHttpError(subscriptionRaw)) {
return httpFailure(subscriptionRaw, "subscription")
}
const unavailable: CommandCodeQuotaSection[] = []
const credits =
isHttpError(creditsRaw) || isQuotaError(creditsRaw) ? null : parseCredits(creditsRaw)
if (!credits) unavailable.push("credits")
const subscription =
isHttpError(subscriptionRaw) || isQuotaError(subscriptionRaw)
? null
: parseSubscription(subscriptionRaw)
if (!subscription) unavailable.push("subscription")
const summaryRaw = await safeRequest(
buildUrl("/alpha/usage/summary", {
orgId,
since: subscription?.currentPeriodStart ?? undefined,
}),
)
if (isHttpError(summaryRaw) && isBlockingHttpError(summaryRaw)) {
return httpFailure(summaryRaw, "summary")
}
const summary =
isHttpError(summaryRaw) || isQuotaError(summaryRaw) ? null : parseSummary(summaryRaw)
if (!summary) unavailable.push("usage")
if (!credits && !subscription && !summary) {
return {
ok: false,
error: {
kind: overallController.signal.aborted ? "timeout" : "http",
message: overallController.signal.aborted
? "Command Code quota request timed out"
: "Command Code returned no recognized usage data for the account",
},
}
}
return {
ok: true,
quota: {
account,
credits,
subscription,
summary,
...(unavailable.length > 0 ? { unavailable } : {}),
},
}
} catch (error) {
if (error instanceof QuotaTimeoutError || overallController.signal.aborted) {
return {
ok: false,
error: { message: "Command Code quota request timed out", kind: "timeout" },
}
}
return {
ok: false,
error: {
message: redactValue(`Failed to fetch Command Code quota: ${errorMessage(error)}`),
kind: "network",
},
}
} finally {
clearTimeout(overallTimer)
}
}
export function redactValue(value: string): string {
return redactCommandCodeErrorText(value)
.replace(
/("\s*(?:api[-_ ]?key|apikey|access[-_ ]?token|refresh[-_ ]?token|token|secret|password|authorization)\s*"\s*:\s*")([^"]{8,})/gi,
"$1[redacted]",
)
.trim()
}
+10 -5
View File
@@ -28,11 +28,13 @@ export interface CommandCodeRuntimeOptions<TProviderConfig> {
cachePath: string
loadModels: () => Promise<LoadCommandCodeModelsResult>
createProviderConfig: (models: readonly CommandCodeModel[]) => TProviderConfig
getTransport?: () => "unknown" | "provider" | "generate"
now?: () => number
logWarning?: (message: string) => void
}
export interface CommandCodeRuntimeStatus {
transport: "unknown" | "provider" | "generate"
source: LoadCommandCodeModelsResult["source"]
modelCount: number
lastSuccess?: number
@@ -86,6 +88,7 @@ function formatTimestamp(timestamp: number | undefined): string {
export function formatCommandCodeStatus(status: CommandCodeRuntimeStatus): string {
const lines = [
`transport: ${status.transport}`,
`source: ${status.source}`,
`model count: ${status.modelCount}`,
`last success: ${formatTimestamp(status.lastSuccess)}`,
@@ -113,6 +116,7 @@ export class CommandCodeRuntime<TProviderConfig, TContext extends CommandCodeCom
this.now = options.now ?? Date.now
this.logWarning = options.logWarning ?? ((message) => console.warn(`[commandcode] ${message}`))
const initialStatus: CommandCodeRuntimeStatus = {
transport: "unknown",
source: "empty",
modelCount: 0,
cachePath: options.cachePath,
@@ -123,7 +127,10 @@ export class CommandCodeRuntime<TProviderConfig, TContext extends CommandCodeCom
}
getStatus(): CommandCodeRuntimeStatus {
return { ...this.status }
return {
...this.status,
transport: this.options.getTransport?.() ?? "unknown",
}
}
async initialize(): Promise<void> {
@@ -259,10 +266,8 @@ export class CommandCodeRuntime<TProviderConfig, TContext extends CommandCodeCom
this.pi.registerCommand("commandcode-status", {
description: "Show redacted Command Code provider diagnostics",
handler: async (_args, ctx) => {
ctx.ui.notify(
formatCommandCodeStatus(this.status),
this.status.warning ? "warning" : "info",
)
const status = this.getStatus()
ctx.ui.notify(formatCommandCodeStatus(status), status.warning ? "warning" : "info")
},
})
}
+140
View File
@@ -0,0 +1,140 @@
import type {
AssistantMessageEvent,
AssistantMessageEventStreamLike,
ContextLike,
ModelLike,
StreamOptions,
} from "./types.ts"
export type CommandCodeTransport = "unknown" | "provider" | "generate"
interface TransportDependencies {
createStream: () => AssistantMessageEventStreamLike
streamProvider: (
model: ModelLike,
context: ContextLike,
options?: StreamOptions,
) => AssistantMessageEventStreamLike
streamGenerate: (
model: ModelLike,
context: ContextLike,
options?: StreamOptions,
) => AssistantMessageEventStreamLike
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value)
}
async function isUpgradeRequired(response: Response): Promise<boolean> {
if (response.status !== 403) return false
try {
const body: unknown = await response.clone().json()
if (!isRecord(body)) return false
const error = isRecord(body.error) ? body.error : body
return error.code === "upgrade_required"
} catch {
return false
}
}
export function createCommandCodeTransportRouter(deps: TransportDependencies) {
let transport: CommandCodeTransport = "unknown"
let apiKey: string | undefined
function pipe(
source: AssistantMessageEventStreamLike,
target: AssistantMessageEventStreamLike,
): Promise<void> {
return (async () => {
for await (const event of source) target.push(event)
})()
}
return {
getTransport(): CommandCodeTransport {
return transport
},
reset(): void {
transport = "unknown"
apiKey = undefined
},
stream(
model: ModelLike,
context: ContextLike,
options?: StreamOptions,
): AssistantMessageEventStreamLike {
if (options?.apiKey !== apiKey) {
apiKey = options?.apiKey
transport = "unknown"
}
const requestApiKey = options?.apiKey
if (transport === "generate") return deps.streamGenerate(model, context, options)
const output = deps.createStream()
let upgradeRequired = false
const fetchImpl = options?.fetch ?? fetch
const providerOptions: StreamOptions = {
...options,
fetch: async (input, init) => {
const response = await fetchImpl(input, init)
if (await isUpgradeRequired(response)) upgradeRequired = true
return response
},
onResponse: async (response, responseModel) => {
if (upgradeRequired) return
await options?.onResponse?.(response, responseModel)
},
}
const run = async () => {
const providerStream = deps.streamProvider(model, context, providerOptions)
for await (const event of providerStream) {
if (!upgradeRequired) {
if (apiKey === requestApiKey) transport = "provider"
output.push(event)
}
}
if (upgradeRequired) {
if (apiKey === requestApiKey) transport = "generate"
await pipe(deps.streamGenerate(model, context, options), output)
}
output.end()
}
run().catch((error: unknown) => {
const message = error instanceof Error ? error.message : String(error)
output.push({
type: "error",
reason: "error",
error: {
role: "assistant",
content: [],
api: model.api,
provider: model.provider,
model: model.id,
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "error",
errorMessage: message,
timestamp: Date.now(),
},
})
output.end()
})
return output
},
}
}
+9
View File
@@ -110,7 +110,10 @@ export interface StreamOptions {
apiKey?: string
signal?: AbortSignal
headers?: Record<string, string>
fetch?: typeof fetch
maxTokens?: number
temperature?: number
sessionId?: string
/** Resolved pi thinking level; forwarded only through the model's map. */
reasoning?: string
onPayload?: (payload: unknown, model: ModelLike) => unknown | Promise<unknown>
@@ -171,6 +174,12 @@ export type AssistantMessageEvent =
contentIndex: number
partial: AssistantMessageLike
}
| {
type: "toolcall_delta"
contentIndex: number
delta: string
partial: AssistantMessageLike
}
| {
type: "toolcall_end"
contentIndex: number