fix(core): preserve request fidelity and provider errors

This commit is contained in:
Patrick Wozniak
2026-08-07 14:05:03 +02:00
parent 7e9ecc563a
commit 7efca20d16
9 changed files with 1188 additions and 127 deletions
+27 -78
View File
@@ -3,6 +3,9 @@ import { homedir } from "node:os"
import { join } from "node:path"
import type { MessageLike, StopReason, ToolLike } from "./types.ts"
import { toJsonSchema } from "./json-schema.ts"
export { toJsonSchema } from "./json-schema.ts"
export function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value)
@@ -12,10 +15,6 @@ export function stringValue(value: unknown): string | undefined {
return typeof value === "string" ? value : undefined
}
function booleanValue(value: unknown): boolean | undefined {
return typeof value === "boolean" ? value : undefined
}
export function recordArray(value: unknown): readonly Record<string, unknown>[] {
if (!Array.isArray(value)) return []
return value.filter(isRecord)
@@ -56,6 +55,26 @@ function apiKeyFromCredentialRecord(value: unknown): string | undefined {
return stringValue(value.key) ?? stringValue(value.access)
}
function hasImageContent(value: unknown): boolean {
if (isRecord(value)) return value.type === "image"
return recordArray(value).some((part) => part.type === "image")
}
function imageContentError(role: string): Error {
return new Error(
`Command Code does not support image content in ${role}; refusing to send it to avoid lossy handling`,
)
}
export function assertTextOnlyMessages(messages?: readonly MessageLike[]): void {
for (const message of messages ?? []) {
if (hasImageContent(message.content)) {
const role = message.role === "toolResult" ? "tool results" : `${message.role} messages`
throw imageContentError(role)
}
}
}
export function getApiKey(
options: {
env?: NodeJS.ProcessEnv
@@ -96,6 +115,8 @@ export function getApiKey(
}
export function textContent(message: { content?: unknown }): string {
if (hasImageContent(message.content)) throw imageContentError("tool results")
return recordArray(message.content)
.filter((part) => part.type === "text")
.map((part) => stringValue(part.text) ?? "")
@@ -106,80 +127,6 @@ export function getEnvironmentInfo(): string {
return `${process.platform}-${process.arch}, Node.js ${process.version}`
}
export function toJsonSchema(schema: unknown): unknown {
if (!isRecord(schema)) return {}
const kind = stringValue(schema.kind) ?? stringValue(schema.type)
const enumValues = Array.isArray(schema.enum) ? schema.enum : undefined
if (enumValues) {
return { type: typeof enumValues[0], enum: enumValues }
}
switch (kind) {
case "string":
case "String":
return { type: "string" }
case "number":
case "Number":
return { type: "number" }
case "boolean":
case "Boolean":
return { type: "boolean" }
case "object":
case "Object": {
const properties: Record<string, unknown> = {}
const inferredRequired: string[] = []
const sourceProperties = isRecord(schema.properties) ? schema.properties : undefined
const optional = Array.isArray(schema.optional)
? schema.optional.filter((item): item is string => typeof item === "string")
: []
if (sourceProperties) {
for (const [key, value] of Object.entries(sourceProperties)) {
properties[key] = toJsonSchema(value)
const valueRecord = isRecord(value) ? value : undefined
if (booleanValue(valueRecord?.optional) !== true && !optional.includes(key)) {
inferredRequired.push(key)
}
}
}
const explicitRequired = Array.isArray(schema.required)
? schema.required.filter((item): item is string => typeof item === "string")
: undefined
const required = explicitRequired ?? inferredRequired
const out: Record<string, unknown> = { type: "object" }
if (Object.keys(properties).length > 0) out.properties = properties
if (required.length > 0) out.required = required
return out
}
case "array":
case "Array":
return {
type: "array",
items: toJsonSchema(schema.items ?? schema.element),
}
case "union":
case "Union": {
const variants = Array.isArray(schema.variants)
? schema.variants
: Array.isArray(schema.anyOf)
? schema.anyOf
: []
for (const variant of variants) {
const converted = toJsonSchema(variant)
if (isRecord(converted) && Object.keys(converted).length > 0) return converted
}
return {}
}
case "optional":
case "Optional":
return toJsonSchema(schema.wrapped ?? schema.inner)
default:
return {}
}
}
export function toolsToJson(tools?: readonly ToolLike[]): unknown[] {
if (!tools) return []
return tools.map((tool) => ({
@@ -211,6 +158,8 @@ function completeToolCallIds(messages?: readonly MessageLike[]): Set<string> {
}
export function messagesToCC(messages?: readonly MessageLike[]): unknown[] {
assertTextOnlyMessages(messages)
const out: unknown[] = []
const pairedToolCallIds = completeToolCallIds(messages)
+91 -26
View File
@@ -7,10 +7,12 @@
import { randomUUID } from "node:crypto"
import { commandCodeErrorMessage, redactCommandCodeErrorText } from "./overflow.ts"
import {
getApiKey,
getEnvironmentInfo,
isRecord,
assertTextOnlyMessages,
mapFinishReason,
messagesToCC,
numberValue,
@@ -36,10 +38,17 @@ import type {
} from "./types.ts"
export * from "./converters.ts"
export * from "./overflow.ts"
export * from "./types.ts"
export const DEFAULT_API_BASE = "https://api.commandcode.ai"
export const COMMAND_CODE_CLI_VERSION = "0.29.0"
/**
* The legacy /alpha/generate request path used by this provider has no
* documented image-part contract. Keep the advertised capability text-only
* until Command Code documents and tests image handling for this endpoint.
*/
export const COMMAND_CODE_INPUT_TYPES = ["text"] as const
const DEFAULT_GENERATE_MAX_TOKENS = 64_000
const DEFAULT_MAX_RETRIES = 0
@@ -194,6 +203,31 @@ export function createStreamCommandCode(deps: CoreDependencies) {
})
}
function raceAbortWithTimeout<T>(
promise: Promise<T>,
controller: AbortController,
timeoutMs: number | undefined,
): Promise<T> {
if (timeoutMs === undefined) return raceAbort(promise, controller.signal)
return new Promise<T>((resolve, reject) => {
const timer = setTimeout(() => {
controller.abort()
reject(timeoutError(timeoutMs))
}, timeoutMs)
raceAbort(promise, controller.signal).then(
(value) => {
clearTimeout(timer)
resolve(value)
},
(error: unknown) => {
clearTimeout(timer)
reject(error)
},
)
})
}
return function streamCommandCode(
model: ModelLike,
context: ContextLike,
@@ -418,9 +452,10 @@ export function createStreamCommandCode(deps: CoreDependencies) {
}
case "error": {
const errorRecord = isRecord(event.error) ? event.error : undefined
const message =
stringValue(errorRecord?.message) ?? stringValue(event.error) ?? "Stream error"
commandCodeErrorMessage(event.error) ??
commandCodeErrorMessage(event.message) ??
"Stream error"
output.stopReason = "error"
output.errorMessage = message
throw new Error(message)
@@ -430,10 +465,14 @@ export function createStreamCommandCode(deps: CoreDependencies) {
try {
stream.push({ type: "start", partial: output })
if (controller.signal.aborted) throw abortError("Aborted")
const workingDir = cwd()
const threadId = uuid()
const reasoningEffort = mappedReasoningEffort(model, options)
const timeoutMs = options?.timeoutMs
assertTextOnlyMessages(context.messages)
let body: unknown = {
config: {
@@ -463,15 +502,23 @@ export function createStreamCommandCode(deps: CoreDependencies) {
threadId,
}
const nextBody = await raceAbort(
Promise.resolve(options?.onPayload?.(body, model)),
controller.signal,
)
const payloadController = new AbortController()
const onPayloadAbort = () => payloadController.abort()
controller.signal.addEventListener("abort", onPayloadAbort, { once: true })
let nextBody: unknown
try {
nextBody = await raceAbortWithTimeout(
Promise.resolve(options?.onPayload?.(body, model)),
payloadController,
timeoutMs,
)
} finally {
controller.signal.removeEventListener("abort", onPayloadAbort)
}
if (nextBody !== undefined) body = nextBody
const maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES
const maxRetryDelayMs = effectiveMaxRetryDelayMs(options?.maxRetryDelayMs)
const timeoutMs = options?.timeoutMs
const requestHeaders = {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
@@ -505,6 +552,11 @@ export function createStreamCommandCode(deps: CoreDependencies) {
}
const onOuterAbort = () => attemptController.abort()
controller.signal.addEventListener("abort", onOuterAbort, { once: true })
const raceAttempt = <T>(promise: Promise<T>): Promise<T> =>
raceAbort(promise, attemptController.signal).catch((error: unknown) => {
if (attemptTimedOut) throw timeoutError(timeoutMs)
throw error
})
try {
try {
@@ -540,25 +592,38 @@ export function createStreamCommandCode(deps: CoreDependencies) {
}
}
await raceAbort(
Promise.resolve(
options?.onResponse?.(
{
status: response.status,
headers: headersToRecord(response.headers),
},
model,
try {
await raceAttempt(
Promise.resolve(
options?.onResponse?.(
{
status: response.status,
headers: headersToRecord(response.headers),
},
model,
),
),
),
controller.signal,
)
)
} catch (error: unknown) {
if (attemptTimedOut && attempt < maxRetries) continue retryLoop
throw error
}
if (!response.ok) {
const errBody = await raceAbort(
response.text().catch(() => ""),
controller.signal,
const errBody = await raceAttempt(response.text().catch(() => ""))
let errorDetail: string | undefined
try {
const parsedBody: unknown = JSON.parse(errBody)
errorDetail = commandCodeErrorMessage(parsedBody)
} catch {
// Preserve useful plain-text provider errors only after secret
// redaction; upstream/proxy bodies may echo credentials.
}
const safeBody = redactCommandCodeErrorText(errBody).slice(0, 500)
const detail = redactCommandCodeErrorText(
errorDetail ?? (safeBody || "Provider returned an error"),
)
throw new Error(`Command Code API error ${response.status}: ${errBody.slice(0, 500)}`)
throw new Error(`Command Code API error ${response.status}: ${detail}`)
}
// --- Read response stream ---
@@ -639,9 +704,7 @@ export function createStreamCommandCode(deps: CoreDependencies) {
output.errorMessage =
reason === "aborted"
? "Request aborted"
: error instanceof Error
? error.message
: String(error)
: redactCommandCodeErrorText(error instanceof Error ? error.message : String(error))
stream.push({ type: "error", reason, error: output })
stream.end()
} finally {
@@ -668,7 +731,9 @@ export function createStreamCommandCode(deps: CoreDependencies) {
model: model.id,
usage: defaultUsage(),
stopReason: "error",
errorMessage: error instanceof Error ? error.message : String(error),
errorMessage: redactCommandCodeErrorText(
error instanceof Error ? error.message : String(error),
),
timestamp: now(),
}
stream.push({ type: "error", reason: "error", error: msg })
+382
View File
@@ -0,0 +1,382 @@
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 booleanValue(value: unknown): boolean | undefined {
return typeof value === "boolean" ? value : undefined
}
type JsonSchemaValue = boolean | Record<string, unknown>
const JSON_SCHEMA_TYPES = new Set([
"array",
"boolean",
"integer",
"null",
"number",
"object",
"string",
])
const LEGACY_KINDS = new Set([
"any",
"array",
"boolean",
"enum",
"integer",
"intersect",
"intersection",
"literal",
"never",
"null",
"nullable",
"number",
"object",
"optional",
"string",
"undefined",
"union",
"unknown",
])
const LEGACY_FIELDS = new Set([
"element",
"kind",
"inner",
"optional",
"value",
"values",
"variants",
"wrapped",
])
const SCHEMA_MAP_FIELDS = new Set([
"$defs",
"definitions",
"dependentSchemas",
"patternProperties",
"properties",
])
const SCHEMA_ARRAY_FIELDS = new Set(["allOf", "anyOf", "oneOf", "prefixItems"])
const SCHEMA_VALUE_FIELDS = new Set([
"additionalItems",
"additionalProperties",
"contains",
"contentSchema",
"else",
"if",
"items",
"not",
"propertyNames",
"then",
"unevaluatedItems",
"unevaluatedProperties",
])
const SCHEMA_KEYWORDS = new Set([
"$anchor",
"$comment",
"$defs",
"$dynamicAnchor",
"$dynamicRef",
"$id",
"$ref",
"$schema",
"$vocabulary",
"additionalItems",
"additionalProperties",
"allOf",
"anyOf",
"const",
"contains",
"contentEncoding",
"contentMediaType",
"contentSchema",
"default",
"definitions",
"dependentRequired",
"dependentSchemas",
"description",
"else",
"enum",
"examples",
"exclusiveMaximum",
"exclusiveMinimum",
"format",
"if",
"items",
"maxContains",
"maxItems",
"maxLength",
"maxProperties",
"maximum",
"minContains",
"minItems",
"minLength",
"minProperties",
"minimum",
"multipleOf",
"not",
"oneOf",
"pattern",
"patternProperties",
"prefixItems",
"properties",
"propertyNames",
"readOnly",
"required",
"title",
"type",
"unevaluatedItems",
"unevaluatedProperties",
"uniqueItems",
"writeOnly",
])
function stringArray(value: unknown): string[] | undefined {
if (!Array.isArray(value)) return undefined
const values = value.filter((item): item is string => typeof item === "string")
return values.length === value.length ? values : undefined
}
function validSchemaType(value: unknown): boolean {
if (typeof value === "string") return JSON_SCHEMA_TYPES.has(value)
if (!Array.isArray(value) || value.length === 0) return false
return value.every((item) => typeof item === "string" && JSON_SCHEMA_TYPES.has(item))
}
function legacyKind(schema: Record<string, unknown>): string | undefined {
const explicitKind = stringValue(schema.kind)?.toLowerCase()
if (explicitKind && LEGACY_KINDS.has(explicitKind)) return explicitKind
const type = stringValue(schema.type)
const normalized = type?.toLowerCase()
if (!normalized || !LEGACY_KINDS.has(normalized)) return undefined
if (!validSchemaType(type) || Object.keys(schema).some((key) => LEGACY_FIELDS.has(key))) {
return normalized
}
return undefined
}
function looksLikeJsonSchema(schema: Record<string, unknown>): boolean {
if (Object.keys(schema).length === 0) return true
if (schema.type !== undefined && !validSchemaType(schema.type)) return false
return Object.keys(schema).some((key) => SCHEMA_KEYWORDS.has(key))
}
function isOptionalSchema(schema: unknown): boolean {
if (!isRecord(schema)) return false
if (booleanValue(schema.optional) === true) return true
const kind = legacyKind(schema)
if (kind === "optional") return true
if (kind !== "union") return false
const variants = Array.isArray(schema.variants)
? schema.variants
: Array.isArray(schema.anyOf)
? schema.anyOf
: []
return variants.some((variant) => legacyKind(isRecord(variant) ? variant : {}) === "undefined")
}
function schemaValue(value: unknown, seen: WeakSet<object>): JsonSchemaValue {
if (typeof value === "boolean") return value
if (!isRecord(value)) return {}
return convertSchema(value, seen)
}
function setSchemaProperty(target: Record<string, unknown>, key: string, value: unknown): void {
Object.defineProperty(target, key, {
configurable: true,
enumerable: true,
value,
writable: true,
})
}
function schemaMap(value: unknown, seen: WeakSet<object>): Record<string, unknown> {
if (!isRecord(value)) return {}
const out: Record<string, unknown> = {}
for (const [key, item] of Object.entries(value)) {
setSchemaProperty(out, key, schemaValue(item, seen))
}
return out
}
function schemaArray(value: unknown, seen: WeakSet<object>): unknown[] {
if (!Array.isArray(value)) return []
return value.map((item) => schemaValue(item, seen))
}
function isSchemaValue(value: unknown): value is JsonSchemaValue {
return typeof value === "boolean" || isRecord(value)
}
function copySchemaObject(
source: Record<string, unknown>,
seen: WeakSet<object>,
legacy: boolean,
forcedType?: string,
): JsonSchemaValue {
const out: Record<string, unknown> = {}
for (const [key, value] of Object.entries(source)) {
if (legacy && LEGACY_FIELDS.has(key)) continue
if (key === "nullable" || (forcedType !== undefined && key === "type")) continue
if (key === "required") {
const required = stringArray(value)
if (required) out.required = required
} else if (SCHEMA_MAP_FIELDS.has(key)) {
out[key] = schemaMap(value, seen)
} else if (SCHEMA_ARRAY_FIELDS.has(key)) {
out[key] = schemaArray(value, seen)
} else if (SCHEMA_VALUE_FIELDS.has(key)) {
out[key] =
Array.isArray(value) && key === "items"
? schemaArray(value, seen)
: schemaValue(value, seen)
} else {
out[key] = value
}
}
if (forcedType !== undefined) out.type = forcedType
if (booleanValue(source.nullable) === true) return makeNullable(out)
return out
}
function makeNullable(schema: Record<string, unknown>): Record<string, unknown> {
const type = schema.type
if (typeof type === "string") {
if (type === "null") return schema
return { ...schema, type: [type, "null"] }
}
if (Array.isArray(type) && !type.includes("null")) {
return { ...schema, type: [...type, "null"] }
}
if (Array.isArray(schema.anyOf)) {
return { ...schema, anyOf: [...schema.anyOf, { type: "null" }] }
}
return { anyOf: [schema, { type: "null" }] }
}
function legacyVariants(schema: Record<string, unknown>): unknown[] {
if (Array.isArray(schema.variants)) return schema.variants
if (Array.isArray(schema.anyOf)) return schema.anyOf
return []
}
function convertLegacySchema(
source: Record<string, unknown>,
kind: string,
seen: WeakSet<object>,
): JsonSchemaValue {
if (kind === "optional") return schemaValue(source.wrapped ?? source.inner, seen)
if (kind === "nullable") {
const wrapped = schemaValue(source.wrapped ?? source.inner, seen)
return typeof wrapped === "boolean" ? wrapped : makeNullable(wrapped)
}
if (kind === "undefined" || kind === "never" || kind === "any" || kind === "unknown") return {}
if (kind === "union" || kind === "intersect" || kind === "intersection") {
const variants = legacyVariants(source)
.map((variant) => schemaValue(variant, seen))
.filter(
(variant) =>
isSchemaValue(variant) &&
(typeof variant === "boolean" || Object.keys(variant).length > 0),
)
if (variants.length === 0) return copySchemaObject(source, seen, true)
if (variants.length === 1) return variants[0] ?? {}
const out = copySchemaObject(source, seen, true)
if (typeof out !== "boolean") out[kind === "union" ? "anyOf" : "allOf"] = variants
return out
}
if (kind === "object") {
const converted = copySchemaObject(source, seen, true, "object")
if (typeof converted === "boolean") return converted
const out = converted
const sourceProperties = isRecord(source.properties) ? source.properties : undefined
if (!sourceProperties) return out
const properties: Record<string, unknown> = {}
const optional = stringArray(source.optional) ?? []
for (const [key, value] of Object.entries(sourceProperties)) {
setSchemaProperty(properties, key, schemaValue(value, seen))
}
out.properties = properties
const explicitRequired = stringArray(source.required)
const required =
explicitRequired ??
Object.entries(sourceProperties)
.filter(([key, value]) => !optional.includes(key) && !isOptionalSchema(value))
.map(([key]) => key)
if (required.length > 0) out.required = required
else delete out.required
return out
}
if (kind === "array") {
const converted = copySchemaObject(source, seen, true, "array")
if (typeof converted === "boolean") return converted
const out = converted
if (!("items" in source) && "element" in source) out.items = schemaValue(source.element, seen)
return out
}
if (kind === "enum") {
const converted = copySchemaObject(source, seen, true)
if (typeof converted === "boolean") return converted
const out = converted
if (!("enum" in out) && Array.isArray(source.values)) out.enum = source.values
return out
}
if (kind === "literal") {
const converted = copySchemaObject(source, seen, true)
if (typeof converted === "boolean") return converted
const out = converted
if (!("const" in out) && "value" in source) out.const = source.value
return out
}
const scalarType =
kind === "string" ||
kind === "number" ||
kind === "boolean" ||
kind === "integer" ||
kind === "null"
? kind
: undefined
return scalarType ? copySchemaObject(source, seen, true, scalarType) : {}
}
function convertSchema(source: Record<string, unknown>, seen: WeakSet<object>): JsonSchemaValue {
if (seen.has(source)) return {}
seen.add(source)
try {
const kind = legacyKind(source)
if (kind) return convertLegacySchema(source, kind, seen)
if (!looksLikeJsonSchema(source)) return {}
return copySchemaObject(source, seen, false)
} finally {
seen.delete(source)
}
}
export function toJsonSchema(schema: unknown): unknown {
if (typeof schema === "boolean") return schema
if (!isRecord(schema)) return {}
return convertSchema(schema, new WeakSet<object>())
}
+120
View File
@@ -0,0 +1,120 @@
const COMMAND_CODE_PROVIDER = "commandcode"
const CONTEXT_OVERFLOW_PREFIX = "context_length_exceeded:"
const COMMAND_CODE_OVERFLOW_PATTERNS = [
/\b(?:context[_\s-]*(?:length|window)|model[_\s-]*context[_\s-]*window)[_\s-]*(?:exceeded|overflow(?:ed)?|too[_\s-]*(?:large|long))\b/i,
/\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,
/\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,
/\b(?:prompt|input|context)\b[\s\S]{0,32}\btoo\s+(?:large|long)\b/i,
/\b(?:prompt|input)[_\s-]*too[_\s-]*(?:large|long)\b/i,
/\b(?:prompt|input)[_\s-]*tokens?[_\s-]*(?:limit|maximum|max)[_\s-]*(?:exceeded|reached)\b/i,
/\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,
/\b(?:maximum|limit)[_\s-]+(?:allowed[_\s-]+)?(?:context|prompt|input)[_\s-]*(?:length|window|size|tokens?)\b/i,
]
const NON_OVERFLOW_PATTERNS = [
/\brate[_\s-]*limit\b/i,
/\btoo\s+many\s+requests\b/i,
/\b(?:capacity|quota|throttl(?:e|ed|ing)?|concurren(?:cy|t)|overloaded)\b/i,
/\b(?:service|temporarily)\s+unavailable\b/i,
/\bstatus(?:[_\s-]*code)?\s*[:=]\s*429\b/i,
]
const CONTEXT_OVERFLOW_PREFIX_PATTERN = /context_length_exceeded/i
const HTTP_RATE_LIMIT_STATUS_PATTERNS = [
/\b(?:api\s+error|http|status(?:[_\s-]*code)?|status[_\s-]*code)\s*[:(]?\s*429\b/i,
/["']?(?:status|status[_\s-]*code)["']?\s*:\s*429\b/i,
]
const BEARER_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi
const CREDENTIAL_PATTERN =
/\b(?:api[-_ ]?key|apikey|access[-_ ]?token|refresh[-_ ]?token|token|secret|password|authorization)\s*[=:]\s*[^\s,;)]+/gi
const USER_TOKEN_PATTERN = /\b(?:user|cc)_[A-Za-z0-9_-]{8,}\b/gi
const QUERY_SECRET_PATTERN =
/([?&](?:api[-_ ]?key|apikey|access_token|refresh_token|token|secret|password)=)[^&#\s]+/gi
const STANDALONE_SECRET_PATTERN =
/\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
export function redactCommandCodeErrorText(value: string): string {
return value
.replace(BEARER_PATTERN, "Bearer [redacted]")
.replace(CREDENTIAL_PATTERN, (match) => {
const separatorIndex = match.search(/[=:]/)
return separatorIndex < 0 ? "[redacted]" : `${match.slice(0, separatorIndex + 1)}[redacted]`
})
.replace(USER_TOKEN_PATTERN, "[redacted]")
.replace(QUERY_SECRET_PATTERN, "$1[redacted]")
.replace(STANDALONE_SECRET_PATTERN, "[redacted]")
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null
}
export interface CommandCodeMessageLike {
role: string
provider: string
stopReason: string
errorMessage?: string
}
export function commandCodeErrorMessage(value: unknown): string | undefined {
if (typeof value === "string") return value
if (!isRecord(value)) return undefined
const record = value
const parts: string[] = []
for (const key of [
"message",
"errorMessage",
"error",
"detail",
"details",
"code",
"type",
"reason",
]) {
const part = commandCodeErrorMessage(record[key])
if (part && !parts.includes(part)) parts.push(part)
}
for (const key of ["status", "statusCode", "httpStatus"]) {
const status = record[key]
if (typeof status === "string" || typeof status === "number") {
const statusPart = `status: ${status}`
if (!parts.includes(statusPart)) parts.push(statusPart)
}
}
return parts.length > 0 ? redactCommandCodeErrorText(parts.join(": ")) : undefined
}
export function normalizeCommandCodeErrorMessage(
errorMessage: string | undefined,
): string | undefined {
if (!errorMessage) return undefined
if (CONTEXT_OVERFLOW_PREFIX_PATTERN.test(errorMessage)) return undefined
if (NON_OVERFLOW_PATTERNS.some((pattern) => pattern.test(errorMessage))) return undefined
if (HTTP_RATE_LIMIT_STATUS_PATTERNS.some((pattern) => pattern.test(errorMessage)))
return undefined
if (!COMMAND_CODE_OVERFLOW_PATTERNS.some((pattern) => pattern.test(errorMessage)))
return undefined
return `${CONTEXT_OVERFLOW_PREFIX} ${errorMessage}`
}
export function normalizeCommandCodeMessage<T extends CommandCodeMessageLike>(
message: T,
modelProvider?: string,
): { message: T & { errorMessage: string } } | undefined {
if (message.role !== "assistant" || message.stopReason !== "error") return undefined
if (message.provider !== COMMAND_CODE_PROVIDER && modelProvider !== COMMAND_CODE_PROVIDER) {
return undefined
}
const errorMessage = normalizeCommandCodeErrorMessage(message.errorMessage)
if (!errorMessage) return undefined
return { message: { ...message, errorMessage } }
}