feat(models): add model-aware runtime metadata
This commit is contained in:
+11
@@ -134,6 +134,15 @@ function generateMaxTokens(model: ModelLike, options?: StreamOptions): number {
|
||||
)
|
||||
}
|
||||
|
||||
function mappedReasoningEffort(model: ModelLike, options?: StreamOptions): string | undefined {
|
||||
const level = options?.reasoning
|
||||
if (!level || level === "off" || !model.reasoning) return undefined
|
||||
|
||||
const effortMap = model.thinking?.effortMap ?? model.thinkingLevelMap
|
||||
const mapped = effortMap?.[level]
|
||||
return typeof mapped === "string" && mapped !== "off" ? mapped : undefined
|
||||
}
|
||||
|
||||
export function projectSlugFromPath(pathName: string): string {
|
||||
const slug = pathName
|
||||
.toLowerCase()
|
||||
@@ -424,6 +433,7 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
||||
|
||||
const workingDir = cwd()
|
||||
const threadId = uuid()
|
||||
const reasoningEffort = mappedReasoningEffort(model, options)
|
||||
|
||||
let body: unknown = {
|
||||
config: {
|
||||
@@ -448,6 +458,7 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
||||
max_tokens: generateMaxTokens(model, options),
|
||||
temperature: 0.3,
|
||||
stream: true,
|
||||
...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),
|
||||
},
|
||||
threadId,
|
||||
}
|
||||
|
||||
+198
-16
@@ -2,10 +2,100 @@ 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"
|
||||
export const DEFAULT_MODELS_TIMEOUT_MS = 10_000
|
||||
|
||||
const DEFAULT_MAX_OUTPUT_TOKENS = 65_536
|
||||
const MODEL_CACHE_VERSION = 1
|
||||
|
||||
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. These entries are
|
||||
* maintained from the official Command Code CLI model catalog, so a model is
|
||||
* marked reasoning-capable only when its upstream effort support is known.
|
||||
*/
|
||||
export const MODEL_EFFORTS: Readonly<Record<string, readonly CommandCodeReasoningEffort[]>> = {
|
||||
"claude-fable-5": ["low", "medium", "high", "xhigh", "max"],
|
||||
"claude-haiku-4-5-20251001": ["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"],
|
||||
"meta/muse-spark-1.1": ["low", "medium", "high"],
|
||||
"moonshotai/Kimi-K2.5": ["high", "max"],
|
||||
"moonshotai/Kimi-K2.6": ["high", "max"],
|
||||
"sakana/fugu-ultra": ["high", "xhigh"],
|
||||
"tencent/hy3-paid": ["low", "medium", "high"],
|
||||
"xai/grok-4.5": ["low", "medium", "high"],
|
||||
"zai-org/GLM-5.2": ["high", "max"],
|
||||
}
|
||||
|
||||
const PI_THINKING_LEVELS: readonly PiThinkingLevel[] = [
|
||||
"off",
|
||||
"minimal",
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh",
|
||||
"max",
|
||||
]
|
||||
|
||||
export function thinkingLevelMapForEfforts(
|
||||
efforts: readonly string[],
|
||||
): Partial<Record<PiThinkingLevel, string | null>> {
|
||||
const map: Partial<Record<PiThinkingLevel, string | null>> = {}
|
||||
for (const level of PI_THINKING_LEVELS) {
|
||||
if (level === "off") continue
|
||||
map[level] = efforts.includes(level) ? level : null
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
export interface ThinkingMetadata {
|
||||
thinkingLevelMap: Partial<Record<PiThinkingLevel, string | null>>
|
||||
thinking: {
|
||||
effortMap: Partial<Record<PiThinkingLevel, string | null>>
|
||||
efforts: readonly CommandCodeReasoningEffort[]
|
||||
defaultLevel: CommandCodeReasoningEffort
|
||||
}
|
||||
}
|
||||
|
||||
export function thinkingMetadataForModel(modelId: string): ThinkingMetadata | undefined {
|
||||
const efforts = MODEL_EFFORTS[modelId]
|
||||
if (!efforts) return undefined
|
||||
const effortMap = thinkingLevelMapForEfforts(efforts)
|
||||
return {
|
||||
thinkingLevelMap: effortMap,
|
||||
thinking: {
|
||||
effortMap,
|
||||
efforts,
|
||||
defaultLevel: efforts[efforts.length - 2] ?? efforts[0],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function isReasoningModel(modelId: string): boolean {
|
||||
return MODEL_EFFORTS[modelId] !== undefined
|
||||
}
|
||||
|
||||
interface ApiModel {
|
||||
id: string
|
||||
name: string
|
||||
@@ -23,6 +113,8 @@ export interface CommandCodeModel {
|
||||
interface FetchCommandCodeModelsOptions {
|
||||
url?: string
|
||||
fetchImpl?: typeof fetch
|
||||
signal?: AbortSignal
|
||||
timeoutMs?: number
|
||||
}
|
||||
|
||||
interface LoadCommandCodeModelsOptions extends FetchCommandCodeModelsOptions {
|
||||
@@ -74,10 +166,12 @@ function parseApiModel(value: unknown): ApiModel {
|
||||
function parseCachedModel(value: unknown): CommandCodeModel {
|
||||
if (!isRecord(value)) throw new Error("Expected cached model entry to be an object")
|
||||
|
||||
const id = stringField(value, "id")
|
||||
booleanField(value, "reasoning")
|
||||
return {
|
||||
id: stringField(value, "id"),
|
||||
id,
|
||||
name: stringField(value, "name"),
|
||||
reasoning: booleanField(value, "reasoning"),
|
||||
reasoning: isReasoningModel(id),
|
||||
contextWindow: positiveNumberField(value, "contextWindow"),
|
||||
maxTokens: positiveNumberField(value, "maxTokens"),
|
||||
}
|
||||
@@ -92,6 +186,85 @@ function errorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
|
||||
function abortError(reason: unknown): Error {
|
||||
if (reason instanceof Error) return reason
|
||||
return new DOMException("The operation was aborted", "AbortError")
|
||||
}
|
||||
|
||||
function configuredTimeoutMs(timeoutMs: number | undefined): number {
|
||||
return timeoutMs !== undefined && Number.isFinite(timeoutMs) && timeoutMs > 0
|
||||
? timeoutMs
|
||||
: DEFAULT_MODELS_TIMEOUT_MS
|
||||
}
|
||||
|
||||
export function getModelsTimeoutMs(env: NodeJS.ProcessEnv = process.env): number {
|
||||
const raw = env.COMMANDCODE_MODELS_TIMEOUT_MS
|
||||
if (!raw) return DEFAULT_MODELS_TIMEOUT_MS
|
||||
|
||||
const parsed = Number(raw)
|
||||
return configuredTimeoutMs(parsed)
|
||||
}
|
||||
|
||||
class ModelDiscoveryTimeoutError extends Error {
|
||||
constructor(timeoutMs: number) {
|
||||
super(`Command Code model discovery timed out after ${timeoutMs}ms`)
|
||||
this.name = "ModelDiscoveryTimeoutError"
|
||||
}
|
||||
}
|
||||
|
||||
function runWithTimeout<T>(
|
||||
operation: (signal: AbortSignal) => Promise<T>,
|
||||
timeoutMs: number,
|
||||
externalSignal: AbortSignal | undefined,
|
||||
): Promise<T> {
|
||||
const controller = new AbortController()
|
||||
let timer: ReturnType<typeof setTimeout> | undefined
|
||||
let settled = false
|
||||
let onExternalAbort: (() => void) | undefined
|
||||
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const cleanup = () => {
|
||||
if (timer !== undefined) clearTimeout(timer)
|
||||
if (onExternalAbort && externalSignal) {
|
||||
externalSignal.removeEventListener("abort", onExternalAbort)
|
||||
}
|
||||
}
|
||||
|
||||
const resolveOnce = (value: T) => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
cleanup()
|
||||
resolve(value)
|
||||
}
|
||||
|
||||
const rejectOnce = (error: unknown) => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
cleanup()
|
||||
reject(error)
|
||||
}
|
||||
|
||||
const abort = (reason: unknown) => {
|
||||
const error = abortError(reason)
|
||||
controller.abort(error)
|
||||
rejectOnce(error)
|
||||
}
|
||||
|
||||
if (externalSignal?.aborted) {
|
||||
abort(externalSignal.reason)
|
||||
return
|
||||
}
|
||||
|
||||
onExternalAbort = () => abort(externalSignal?.reason)
|
||||
externalSignal?.addEventListener("abort", onExternalAbort, { once: true })
|
||||
timer = setTimeout(() => abort(new ModelDiscoveryTimeoutError(timeoutMs)), timeoutMs)
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => operation(controller.signal))
|
||||
.then(resolveOnce, rejectOnce)
|
||||
})
|
||||
}
|
||||
|
||||
export function commandCodeModelsFromApiResponse(value: unknown): readonly CommandCodeModel[] {
|
||||
if (!isRecord(value)) throw new Error("Expected models response to be an object")
|
||||
if (value.object !== "list") throw new Error("Expected models response object to be 'list'")
|
||||
@@ -102,7 +275,7 @@ export function commandCodeModelsFromApiResponse(value: unknown): readonly Comma
|
||||
return data.map(parseApiModel).map((model) => ({
|
||||
id: model.id,
|
||||
name: `${model.name} (CC)`,
|
||||
reasoning: true,
|
||||
reasoning: isReasoningModel(model.id),
|
||||
contextWindow: model.contextLength,
|
||||
maxTokens: Math.min(model.contextLength, DEFAULT_MAX_OUTPUT_TOKENS),
|
||||
}))
|
||||
@@ -123,19 +296,26 @@ export async function fetchCommandCodeModels(
|
||||
): Promise<readonly CommandCodeModel[]> {
|
||||
const url = options.url ?? DEFAULT_MODELS_URL
|
||||
const fetchImpl = options.fetchImpl ?? fetch
|
||||
const response = await fetchImpl(url, {
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
const body: unknown = await runWithTimeout(
|
||||
async (signal) => {
|
||||
const response = await fetchImpl(url, {
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
},
|
||||
signal,
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch Command Code models: ${response.status} ${response.statusText}`,
|
||||
)
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch Command Code models: ${response.status} ${response.statusText}`,
|
||||
)
|
||||
}
|
||||
|
||||
const body: unknown = await response.json()
|
||||
configuredTimeoutMs(options.timeoutMs),
|
||||
options.signal,
|
||||
)
|
||||
return requireModels(commandCodeModelsFromApiResponse(body))
|
||||
}
|
||||
|
||||
@@ -187,6 +367,8 @@ export async function loadCommandCodeModels(
|
||||
}
|
||||
}
|
||||
} catch (liveError) {
|
||||
if (options.signal?.aborted) throw abortError(options.signal.reason ?? liveError)
|
||||
|
||||
try {
|
||||
const models = await readCommandCodeModelsCache(cachePath)
|
||||
return {
|
||||
@@ -198,7 +380,7 @@ export async function loadCommandCodeModels(
|
||||
return {
|
||||
models: [],
|
||||
source: "empty",
|
||||
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 /reload succeeds.`,
|
||||
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.`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
import type { CommandCodeModel, LoadCommandCodeModelsResult } from "./models.ts"
|
||||
|
||||
export interface CommandCodeUi {
|
||||
notify(message: string, type?: "info" | "warning" | "error"): void
|
||||
}
|
||||
|
||||
export interface CommandCodeCommandContext {
|
||||
ui: CommandCodeUi
|
||||
waitForIdle?: () => Promise<void>
|
||||
}
|
||||
|
||||
export interface CommandCodeRuntimeApi<
|
||||
TProviderConfig,
|
||||
TContext extends CommandCodeCommandContext,
|
||||
> {
|
||||
registerProvider(name: string, config: TProviderConfig): void
|
||||
registerCommand(
|
||||
name: string,
|
||||
options: {
|
||||
description: string
|
||||
handler: (args: string, ctx: TContext) => Promise<void>
|
||||
},
|
||||
): void
|
||||
}
|
||||
|
||||
export interface CommandCodeRuntimeOptions<TProviderConfig> {
|
||||
endpoint: string
|
||||
cachePath: string
|
||||
loadModels: () => Promise<LoadCommandCodeModelsResult>
|
||||
createProviderConfig: (models: readonly CommandCodeModel[]) => TProviderConfig
|
||||
now?: () => number
|
||||
logWarning?: (message: string) => void
|
||||
}
|
||||
|
||||
export interface CommandCodeRuntimeStatus {
|
||||
source: LoadCommandCodeModelsResult["source"]
|
||||
modelCount: number
|
||||
lastSuccess?: number
|
||||
lastAttempt?: number
|
||||
cachePath: string
|
||||
endpoint: string
|
||||
warning?: string
|
||||
refreshing: boolean
|
||||
}
|
||||
|
||||
export interface CommandCodeRefreshResult {
|
||||
refreshed: boolean
|
||||
source: CommandCodeRuntimeStatus["source"]
|
||||
modelCount: number
|
||||
warning?: string
|
||||
}
|
||||
|
||||
const REDACTED = "[redacted]"
|
||||
|
||||
function errorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
|
||||
function redactUrl(value: string): string {
|
||||
try {
|
||||
const url = new URL(value)
|
||||
return `${url.protocol}//${url.host}${url.pathname}`
|
||||
} catch {
|
||||
return REDACTED
|
||||
}
|
||||
}
|
||||
|
||||
export function redactDiagnosticText(value: string): string {
|
||||
const redactedUrls = value.replace(/https?:\/\/[^\s)]+/gi, (match) => redactUrl(match))
|
||||
return redactedUrls
|
||||
.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, `Bearer ${REDACTED}`)
|
||||
.replace(/\b(?:user|cc)_[A-Za-z0-9_-]{8,}\b/gi, REDACTED)
|
||||
.replace(/\b(?:api[-_ ]?key|token|secret|password)\s*[=:]\s*[^\s,;)]+/gi, (match) => {
|
||||
const separator = match.match(/\s*[=:]\s*/)?.[0] ?? "="
|
||||
return `${match.slice(0, match.indexOf(separator))}${separator}${REDACTED}`
|
||||
})
|
||||
}
|
||||
|
||||
export function redactEndpoint(value: string): string {
|
||||
return redactUrl(value)
|
||||
}
|
||||
|
||||
function formatTimestamp(timestamp: number | undefined): string {
|
||||
return timestamp === undefined ? "never" : new Date(timestamp).toISOString()
|
||||
}
|
||||
|
||||
export function formatCommandCodeStatus(status: CommandCodeRuntimeStatus): string {
|
||||
const lines = [
|
||||
`source: ${status.source}`,
|
||||
`model count: ${status.modelCount}`,
|
||||
`last success: ${formatTimestamp(status.lastSuccess)}`,
|
||||
`last attempt: ${formatTimestamp(status.lastAttempt)}`,
|
||||
`cache path: ${status.cachePath}`,
|
||||
`endpoint: ${redactEndpoint(status.endpoint)}`,
|
||||
`refresh: ${status.refreshing ? "in progress" : "idle"}`,
|
||||
]
|
||||
|
||||
lines.push(`warning: ${status.warning ? redactDiagnosticText(status.warning) : "none"}`)
|
||||
return lines.join("\n")
|
||||
}
|
||||
|
||||
export class CommandCodeRuntime<TProviderConfig, TContext extends CommandCodeCommandContext> {
|
||||
private readonly now: () => number
|
||||
private readonly logWarning: (message: string) => void
|
||||
private status: CommandCodeRuntimeStatus
|
||||
private providerRegistered = false
|
||||
private refreshPromise: Promise<CommandCodeRefreshResult> | undefined
|
||||
|
||||
constructor(
|
||||
private readonly pi: CommandCodeRuntimeApi<TProviderConfig, TContext>,
|
||||
private readonly options: CommandCodeRuntimeOptions<TProviderConfig>,
|
||||
) {
|
||||
this.now = options.now ?? Date.now
|
||||
this.logWarning = options.logWarning ?? ((message) => console.warn(`[commandcode] ${message}`))
|
||||
const initialStatus: CommandCodeRuntimeStatus = {
|
||||
source: "empty",
|
||||
modelCount: 0,
|
||||
cachePath: options.cachePath,
|
||||
endpoint: options.endpoint,
|
||||
refreshing: false,
|
||||
}
|
||||
this.status = { ...initialStatus }
|
||||
}
|
||||
|
||||
getStatus(): CommandCodeRuntimeStatus {
|
||||
return { ...this.status }
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
this.registerCommands()
|
||||
await this.refresh()
|
||||
}
|
||||
|
||||
refresh(): Promise<CommandCodeRefreshResult> {
|
||||
if (this.refreshPromise) return this.refreshPromise
|
||||
|
||||
const refreshPromise = this.refreshCatalog().finally(() => {
|
||||
if (this.refreshPromise === refreshPromise) this.refreshPromise = undefined
|
||||
})
|
||||
this.refreshPromise = refreshPromise
|
||||
return refreshPromise
|
||||
}
|
||||
|
||||
private async refreshCatalog(): Promise<CommandCodeRefreshResult> {
|
||||
this.status = {
|
||||
...this.status,
|
||||
lastAttempt: this.now(),
|
||||
refreshing: true,
|
||||
}
|
||||
|
||||
try {
|
||||
const loaded = await this.options.loadModels()
|
||||
const warning = loaded.warning ? redactDiagnosticText(loaded.warning) : undefined
|
||||
|
||||
const shouldRegister =
|
||||
!this.providerRegistered ||
|
||||
loaded.source === "live" ||
|
||||
(this.status.modelCount === 0 && loaded.models.length > 0)
|
||||
|
||||
if (shouldRegister) {
|
||||
this.pi.registerProvider("commandcode", this.options.createProviderConfig(loaded.models))
|
||||
this.providerRegistered = true
|
||||
|
||||
if (loaded.models.length === 0) {
|
||||
const preservedWarning = warning ?? "Model catalog refresh returned no models"
|
||||
this.status = {
|
||||
...this.status,
|
||||
source: loaded.source,
|
||||
modelCount: 0,
|
||||
warning: preservedWarning,
|
||||
refreshing: false,
|
||||
}
|
||||
this.warn(preservedWarning)
|
||||
return {
|
||||
refreshed: false,
|
||||
source: loaded.source,
|
||||
modelCount: 0,
|
||||
warning: preservedWarning,
|
||||
}
|
||||
}
|
||||
|
||||
this.status = {
|
||||
...this.status,
|
||||
source: loaded.source,
|
||||
modelCount: loaded.models.length,
|
||||
lastSuccess: this.now(),
|
||||
warning,
|
||||
refreshing: false,
|
||||
}
|
||||
if (warning) this.warn(warning)
|
||||
return {
|
||||
refreshed: true,
|
||||
source: loaded.source,
|
||||
modelCount: loaded.models.length,
|
||||
warning,
|
||||
}
|
||||
}
|
||||
|
||||
const preservedWarning = warning ?? "Model catalog refresh returned no models"
|
||||
this.status = {
|
||||
...this.status,
|
||||
warning: preservedWarning,
|
||||
refreshing: false,
|
||||
}
|
||||
this.warn(preservedWarning)
|
||||
return {
|
||||
refreshed: false,
|
||||
source: this.status.source,
|
||||
modelCount: this.status.modelCount,
|
||||
warning: preservedWarning,
|
||||
}
|
||||
} catch (error) {
|
||||
const warning = redactDiagnosticText(
|
||||
`Could not refresh the Command Code model catalog: ${errorMessage(error)}`,
|
||||
)
|
||||
this.status = {
|
||||
...this.status,
|
||||
warning,
|
||||
refreshing: false,
|
||||
}
|
||||
this.warn(warning)
|
||||
return {
|
||||
refreshed: false,
|
||||
source: this.status.source,
|
||||
modelCount: this.status.modelCount,
|
||||
warning,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private warn(message: string): void {
|
||||
try {
|
||||
this.logWarning(redactDiagnosticText(message))
|
||||
} catch {
|
||||
// Diagnostics must never make a catalog refresh fail.
|
||||
}
|
||||
}
|
||||
|
||||
private registerCommands(): void {
|
||||
this.pi.registerCommand("commandcode-refresh", {
|
||||
description: "Refresh the Command Code model catalog",
|
||||
handler: async (_args, ctx) => {
|
||||
await ctx.waitForIdle?.()
|
||||
const result = await this.refresh()
|
||||
if (result.refreshed) {
|
||||
ctx.ui.notify(
|
||||
`Command Code model catalog refreshed (${result.modelCount} models from ${result.source}).`,
|
||||
"info",
|
||||
)
|
||||
} else {
|
||||
ctx.ui.notify(
|
||||
`Command Code model catalog unchanged (${result.modelCount} models remain available).${result.warning ? ` ${result.warning}` : ""}`,
|
||||
"warning",
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
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",
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function createCommandCodeRuntime<
|
||||
TProviderConfig,
|
||||
TContext extends CommandCodeCommandContext,
|
||||
>(
|
||||
pi: CommandCodeRuntimeApi<TProviderConfig, TContext>,
|
||||
options: CommandCodeRuntimeOptions<TProviderConfig>,
|
||||
): CommandCodeRuntime<TProviderConfig, TContext> {
|
||||
return new CommandCodeRuntime(pi, options)
|
||||
}
|
||||
@@ -72,6 +72,13 @@ export interface ModelLike {
|
||||
provider: string
|
||||
maxTokens: number
|
||||
cost: ModelCost
|
||||
reasoning?: boolean
|
||||
thinkingLevelMap?: Partial<Record<string, string | null>>
|
||||
thinking?: {
|
||||
effortMap?: Partial<Record<string, string | null>>
|
||||
efforts?: readonly string[]
|
||||
defaultLevel?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface MessageLike {
|
||||
@@ -104,6 +111,8 @@ export interface StreamOptions {
|
||||
signal?: AbortSignal
|
||||
headers?: Record<string, string>
|
||||
maxTokens?: number
|
||||
/** Resolved pi thinking level; forwarded only through the model's map. */
|
||||
reasoning?: string
|
||||
onPayload?: (payload: unknown, model: ModelLike) => unknown | Promise<unknown>
|
||||
onResponse?: (response: ProviderResponseInfo, model: ModelLike) => void | Promise<void>
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user