Merge pull request #3071 from code-yeongyu/refactor/deslop-shared-config

refactor(shared): remove AI slop from shared and config modules
This commit is contained in:
YeonGyu-Kim
2026-04-03 21:37:41 +09:00
committed by GitHub
18 changed files with 586 additions and 653 deletions
-1
View File
@@ -5,7 +5,6 @@ export const ExperimentalConfigSchema = z.object({
aggressive_truncation: z.boolean().optional(),
auto_resume: z.boolean().optional(),
preemptive_compaction: z.boolean().optional(),
/** Truncate all tool outputs, not just whitelisted tools (default: false). Tool output truncator is enabled by default - disable via disabled_hooks. */
truncate_all_tool_outputs: z.boolean().optional(),
/** Dynamic context pruning configuration */
dynamic_context_pruning: DynamicContextPruningConfigSchema.optional(),
+29 -93
View File
@@ -1,7 +1,6 @@
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs"
import { join } from "path"
import { log } from "./logger"
import * as dataPath from "./data-path"
import { createJsonFileCacheStore } from "./json-file-cache-store"
const CONNECTED_PROVIDERS_CACHE_FILE = "connected-providers.json"
const PROVIDER_MODELS_CACHE_FILE = "provider-models.json"
@@ -47,115 +46,52 @@ function isRecord(value: unknown): value is Record<string, unknown> {
export function createConnectedProvidersCacheStore(
getCacheDir: () => string = dataPath.getOmoOpenCodeCacheDir
) {
function getCacheFilePath(filename: string): string {
return join(getCacheDir(), filename)
}
let memConnected: string[] | null | undefined
let memProviderModels: ProviderModelsCache | null | undefined
function ensureCacheDir(): void {
const cacheDir = getCacheDir()
if (!existsSync(cacheDir)) {
mkdirSync(cacheDir, { recursive: true })
}
}
const connectedProvidersCacheStore = createJsonFileCacheStore<ConnectedProvidersCache>({
getCacheDir,
filename: CONNECTED_PROVIDERS_CACHE_FILE,
logPrefix: "connected-providers-cache",
cacheLabel: "Cache",
describe: (value) => ({ count: value.connected.length, updatedAt: value.updatedAt }),
})
const providerModelsCacheStore = createJsonFileCacheStore<ProviderModelsCache>({
getCacheDir,
filename: PROVIDER_MODELS_CACHE_FILE,
logPrefix: "connected-providers-cache",
cacheLabel: "Provider-models cache",
describe: (value) => ({
providerCount: Object.keys(value.models).length,
updatedAt: value.updatedAt,
}),
})
function readConnectedProvidersCache(): string[] | null {
if (memConnected !== undefined) return memConnected
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE)
if (!existsSync(cacheFile)) {
log("[connected-providers-cache] Cache file not found", { cacheFile })
memConnected = null
return null
}
try {
const content = readFileSync(cacheFile, "utf-8")
const data = JSON.parse(content) as ConnectedProvidersCache
log("[connected-providers-cache] Read cache", { count: data.connected.length, updatedAt: data.updatedAt })
memConnected = data.connected
return data.connected
} catch (err) {
log("[connected-providers-cache] Error reading cache", { error: String(err) })
memConnected = null
return null
}
return connectedProvidersCacheStore.read()?.connected ?? null
}
function hasConnectedProvidersCache(): boolean {
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE)
return existsSync(cacheFile)
return connectedProvidersCacheStore.has()
}
function writeConnectedProvidersCache(connected: string[]): void {
ensureCacheDir()
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE)
const data: ConnectedProvidersCache = {
connectedProvidersCacheStore.write({
connected,
updatedAt: new Date().toISOString(),
}
try {
writeFileSync(cacheFile, JSON.stringify(data, null, 2))
memConnected = connected
log("[connected-providers-cache] Cache written", { count: connected.length })
} catch (err) {
log("[connected-providers-cache] Error writing cache", { error: String(err) })
}
})
}
function readProviderModelsCache(): ProviderModelsCache | null {
if (memProviderModels !== undefined) return memProviderModels
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE)
if (!existsSync(cacheFile)) {
log("[connected-providers-cache] Provider-models cache file not found", { cacheFile })
memProviderModels = null
return null
}
try {
const content = readFileSync(cacheFile, "utf-8")
const data = JSON.parse(content) as ProviderModelsCache
log("[connected-providers-cache] Read provider-models cache", {
providerCount: Object.keys(data.models).length,
updatedAt: data.updatedAt,
})
memProviderModels = data
return data
} catch (err) {
log("[connected-providers-cache] Error reading provider-models cache", { error: String(err) })
memProviderModels = null
return null
}
return providerModelsCacheStore.read()
}
function hasProviderModelsCache(): boolean {
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE)
return existsSync(cacheFile)
return providerModelsCacheStore.has()
}
function writeProviderModelsCache(data: { models: Record<string, string[] | ModelMetadata[]>; connected: string[] }): void {
ensureCacheDir()
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE)
const cacheData: ProviderModelsCache = {
providerModelsCacheStore.write({
...data,
updatedAt: new Date().toISOString(),
}
try {
writeFileSync(cacheFile, JSON.stringify(cacheData, null, 2))
memProviderModels = cacheData
log("[connected-providers-cache] Provider-models cache written", {
providerCount: Object.keys(data.models).length,
})
} catch (err) {
log("[connected-providers-cache] Error writing provider-models cache", { error: String(err) })
}
})
}
async function updateConnectedProvidersCache(client: {
@@ -223,8 +159,8 @@ export function createConnectedProvidersCacheStore(
}
function _resetMemCacheForTesting(): void {
memConnected = undefined
memProviderModels = undefined
connectedProvidersCacheStore.resetMemory()
providerModelsCacheStore.resetMemory()
}
return {
@@ -256,7 +192,7 @@ export function findProviderModelMetadata(
continue
}
if (entry?.id === modelID) {
if (entry.id === modelID) {
return entry
}
}
@@ -41,7 +41,6 @@ describe("resolveActualContextLimit", () => {
modelContextLimitsCache,
})
// then — models.dev reports 1M for GA models, resolver should respect it
expect(actualLimit).toBe(1_000_000)
})
@@ -89,7 +88,6 @@ describe("resolveActualContextLimit", () => {
modelContextLimitsCache,
})
// then — explicit 1M flag overrides cached 200K
expect(actualLimit).toBe(1_000_000)
})
+98
View File
@@ -0,0 +1,98 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
import { join } from "node:path"
import { log } from "./logger"
type JsonFileCacheStoreOptions<TValue> = {
getCacheDir: () => string
filename: string
logPrefix: string
cacheLabel: string
describe: (value: TValue) => Record<string, unknown>
serialize?: (value: TValue) => string
}
type JsonFileCacheStore<TValue> = {
read: () => TValue | null
has: () => boolean
write: (value: TValue) => void
resetMemory: () => void
}
function toLogLabel(cacheLabel: string): string {
return cacheLabel.toLowerCase()
}
export function createJsonFileCacheStore<TValue>(
options: JsonFileCacheStoreOptions<TValue>,
): JsonFileCacheStore<TValue> {
let memoryValue: TValue | null | undefined
function getCacheFilePath(): string {
return join(options.getCacheDir(), options.filename)
}
function ensureCacheDir(): void {
const cacheDir = options.getCacheDir()
if (!existsSync(cacheDir)) {
mkdirSync(cacheDir, { recursive: true })
}
}
function read(): TValue | null {
if (memoryValue !== undefined) {
return memoryValue
}
const cacheFile = getCacheFilePath()
if (!existsSync(cacheFile)) {
memoryValue = null
log(`[${options.logPrefix}] ${options.cacheLabel} file not found`, { cacheFile })
return null
}
try {
const content = readFileSync(cacheFile, "utf-8")
const value = JSON.parse(content) as TValue
memoryValue = value
log(`[${options.logPrefix}] Read ${toLogLabel(options.cacheLabel)}`, options.describe(value))
return value
} catch (error) {
memoryValue = null
log(`[${options.logPrefix}] Error reading ${toLogLabel(options.cacheLabel)}`, {
error: String(error),
})
return null
}
}
function has(): boolean {
return existsSync(getCacheFilePath())
}
function write(value: TValue): void {
ensureCacheDir()
const cacheFile = getCacheFilePath()
try {
writeFileSync(cacheFile, options.serialize?.(value) ?? JSON.stringify(value, null, 2))
memoryValue = value
log(`[${options.logPrefix}] ${options.cacheLabel} written`, options.describe(value))
} catch (error) {
log(`[${options.logPrefix}] Error writing ${toLogLabel(options.cacheLabel)}`, {
error: String(error),
})
}
}
function resetMemory(): void {
memoryValue = undefined
}
return {
read,
has,
write,
resetMemory,
}
}
-2
View File
@@ -69,8 +69,6 @@ describe("checkForLegacyPluginEntry", () => {
})
it("returns no warning data when config is missing", () => {
// given — empty dir, no config files
// when
const result = checkForLegacyPluginEntry(testConfigDir)
+1 -1
View File
@@ -118,7 +118,7 @@ export function migrateConfigFile(
fs.copyFileSync(configPath, backupPath)
backupSucceeded = true
} catch {
// Original file may not exist yet — skip backup
backupSucceeded = false
}
let writeSucceeded = false
+15 -50
View File
@@ -1,7 +1,5 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"
import { join } from "path"
import * as dataPath from "./data-path"
import { log } from "./logger"
import { createJsonFileCacheStore } from "./json-file-cache-store"
import type { ModelCapabilitiesSnapshot, ModelCapabilitiesSnapshotEntry } from "./model-capabilities"
export const MODELS_DEV_SOURCE_URL = "https://models.dev/api.json"
@@ -162,61 +160,28 @@ export async function fetchModelCapabilitiesSnapshot(args: {
export function createModelCapabilitiesCacheStore(
getCacheDir: () => string = dataPath.getOmoOpenCodeCacheDir,
) {
let memSnapshot: ModelCapabilitiesSnapshot | null | undefined
function getCacheFilePath(): string {
return join(getCacheDir(), MODEL_CAPABILITIES_CACHE_FILE)
}
function ensureCacheDir(): void {
const cacheDir = getCacheDir()
if (!existsSync(cacheDir)) {
mkdirSync(cacheDir, { recursive: true })
}
}
const snapshotCacheStore = createJsonFileCacheStore<ModelCapabilitiesSnapshot>({
getCacheDir,
filename: MODEL_CAPABILITIES_CACHE_FILE,
logPrefix: "model-capabilities-cache",
cacheLabel: "Cache",
describe: (snapshot) => ({
modelCount: Object.keys(snapshot.models).length,
generatedAt: snapshot.generatedAt,
}),
serialize: (snapshot) => `${JSON.stringify(snapshot, null, 2)}\n`,
})
function readModelCapabilitiesCache(): ModelCapabilitiesSnapshot | null {
if (memSnapshot !== undefined) {
return memSnapshot
}
const cacheFile = getCacheFilePath()
if (!existsSync(cacheFile)) {
memSnapshot = null
log("[model-capabilities-cache] Cache file not found", { cacheFile })
return null
}
try {
const content = readFileSync(cacheFile, "utf-8")
const snapshot = JSON.parse(content) as ModelCapabilitiesSnapshot
memSnapshot = snapshot
log("[model-capabilities-cache] Read cache", {
modelCount: Object.keys(snapshot.models).length,
generatedAt: snapshot.generatedAt,
})
return snapshot
} catch (error) {
memSnapshot = null
log("[model-capabilities-cache] Error reading cache", { error: String(error) })
return null
}
return snapshotCacheStore.read()
}
function hasModelCapabilitiesCache(): boolean {
return existsSync(getCacheFilePath())
return snapshotCacheStore.has()
}
function writeModelCapabilitiesCache(snapshot: ModelCapabilitiesSnapshot): void {
ensureCacheDir()
const cacheFile = getCacheFilePath()
writeFileSync(cacheFile, JSON.stringify(snapshot, null, 2) + "\n")
memSnapshot = snapshot
log("[model-capabilities-cache] Cache written", {
modelCount: Object.keys(snapshot.models).length,
generatedAt: snapshot.generatedAt,
})
snapshotCacheStore.write(snapshot)
}
async function refreshModelCapabilitiesCache(args: {
-462
View File
@@ -1,462 +0,0 @@
import bundledModelCapabilitiesSnapshotJson from "../generated/model-capabilities.generated.json"
import { findProviderModelMetadata, type ModelMetadata } from "./connected-providers-cache"
import { resolveModelIDAlias } from "./model-capability-aliases"
import { detectHeuristicModelFamily } from "./model-capability-heuristics"
export type ModelCapabilitiesSnapshotEntry = {
id: string
family?: string
reasoning?: boolean
temperature?: boolean
toolCall?: boolean
modalities?: {
input?: string[]
output?: string[]
}
limit?: {
context?: number
input?: number
output?: number
}
}
export type ModelCapabilitiesSnapshot = {
generatedAt: string
sourceUrl: string
models: Record<string, ModelCapabilitiesSnapshotEntry>
}
export type ModelCapabilities = {
requestedModelID: string
canonicalModelID: string
family?: string
variants?: string[]
reasoningEfforts?: string[]
reasoning?: boolean
supportsThinking?: boolean
supportsTemperature?: boolean
supportsTopP?: boolean
maxOutputTokens?: number
toolCall?: boolean
modalities?: {
input?: string[]
output?: string[]
}
diagnostics: ModelCapabilitiesDiagnostics
}
type GetModelCapabilitiesInput = {
providerID: string
modelID: string
runtimeModel?: ModelMetadata | Record<string, unknown>
runtimeSnapshot?: ModelCapabilitiesSnapshot
bundledSnapshot?: ModelCapabilitiesSnapshot
}
type ModelCapabilityOverride = {
variants?: string[]
reasoningEfforts?: string[]
supportsThinking?: boolean
supportsTemperature?: boolean
supportsTopP?: boolean
}
type DiagnosticSource =
| "none"
| "runtime"
| "runtime-snapshot"
| "bundled-snapshot"
| "override"
| "heuristic"
| "canonical"
| "exact-alias"
| "pattern-alias"
export type ModelCapabilitiesDiagnostics = {
resolutionMode: "snapshot-backed" | "alias-backed" | "heuristic-backed" | "unknown"
canonicalization: {
source: "canonical" | "exact-alias" | "pattern-alias"
ruleID?: string
}
snapshot: {
source: "runtime-snapshot" | "bundled-snapshot" | "none"
}
family: { source: "snapshot" | "heuristic" | "none" }
variants: { source: Exclude<DiagnosticSource, "runtime-snapshot" | "bundled-snapshot" | "exact-alias" | "pattern-alias"> }
reasoningEfforts: { source: Exclude<DiagnosticSource, "runtime-snapshot" | "bundled-snapshot" | "canonical" | "exact-alias" | "pattern-alias" | "runtime"> }
reasoning: { source: "runtime" | "runtime-snapshot" | "bundled-snapshot" | "none" }
supportsThinking: { source: "runtime" | "override" | "heuristic" | "runtime-snapshot" | "bundled-snapshot" | "none" }
supportsTemperature: { source: "runtime" | "override" | "runtime-snapshot" | "bundled-snapshot" | "none" }
supportsTopP: { source: "runtime" | "override" | "none" }
maxOutputTokens: { source: "runtime" | "runtime-snapshot" | "bundled-snapshot" | "none" }
toolCall: { source: "runtime" | "runtime-snapshot" | "bundled-snapshot" | "none" }
modalities: { source: "runtime" | "runtime-snapshot" | "bundled-snapshot" | "none" }
}
const MODEL_ID_OVERRIDES: Record<string, ModelCapabilityOverride> = {}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value)
}
function normalizeLookupModelID(modelID: string): string {
return modelID.trim().toLowerCase()
}
function readBoolean(value: unknown): boolean | undefined {
return typeof value === "boolean" ? value : undefined
}
function readNumber(value: unknown): number | undefined {
return typeof value === "number" ? value : undefined
}
function readStringArray(value: unknown): string[] | undefined {
if (!Array.isArray(value)) {
return undefined
}
const strings = value.filter((item): item is string => typeof item === "string")
return strings.length > 0 ? strings : undefined
}
function normalizeVariantKeys(value: unknown): string[] | undefined {
const arrayVariants = readStringArray(value)
if (arrayVariants) {
return arrayVariants.map((variant) => variant.toLowerCase())
}
if (!isRecord(value)) {
return undefined
}
const variants = Object.keys(value).map((variant) => variant.toLowerCase())
return variants.length > 0 ? variants : undefined
}
function readModalityKeys(value: unknown): string[] | undefined {
const stringArray = readStringArray(value)
if (stringArray) {
return stringArray.map((entry) => entry.toLowerCase())
}
if (!isRecord(value)) {
return undefined
}
const enabled = Object.entries(value)
.filter(([, supported]) => supported === true)
.map(([modality]) => modality.toLowerCase())
return enabled.length > 0 ? enabled : undefined
}
function normalizeModalities(value: unknown): ModelCapabilities["modalities"] | undefined {
if (!isRecord(value)) {
return undefined
}
const input = readModalityKeys(value.input)
const output = readModalityKeys(value.output)
if (!input && !output) {
return undefined
}
return {
...(input ? { input } : {}),
...(output ? { output } : {}),
}
}
function normalizeSnapshot(snapshot: ModelCapabilitiesSnapshot | typeof bundledModelCapabilitiesSnapshotJson): ModelCapabilitiesSnapshot {
return snapshot as ModelCapabilitiesSnapshot
}
function getOverride(modelID: string): ModelCapabilityOverride | undefined {
return MODEL_ID_OVERRIDES[normalizeLookupModelID(modelID)]
}
function readRuntimeModelCapabilities(runtimeModel: Record<string, unknown> | undefined): Record<string, unknown> | undefined {
return isRecord(runtimeModel?.capabilities) ? runtimeModel.capabilities : undefined
}
function readRuntimeModelLimitOutput(runtimeModel: Record<string, unknown> | undefined): number | undefined {
if (!runtimeModel) {
return undefined
}
const limit = isRecord(runtimeModel.limit)
? runtimeModel.limit
: readRuntimeModelCapabilities(runtimeModel)?.limit
if (!isRecord(limit)) {
return undefined
}
return readNumber(limit.output)
}
function readRuntimeModelBoolean(runtimeModel: Record<string, unknown> | undefined, keys: string[]): boolean | undefined {
if (!runtimeModel) {
return undefined
}
const runtimeCapabilities = readRuntimeModelCapabilities(runtimeModel)
for (const key of keys) {
const value = runtimeModel[key]
if (typeof value === "boolean") {
return value
}
const capabilityValue = runtimeCapabilities?.[key]
if (typeof capabilityValue === "boolean") {
return capabilityValue
}
}
return undefined
}
function readRuntimeModelModalities(runtimeModel: Record<string, unknown> | undefined): ModelCapabilities["modalities"] | undefined {
if (!runtimeModel) {
return undefined
}
const rootModalities = normalizeModalities(runtimeModel.modalities)
if (rootModalities) {
return rootModalities
}
const runtimeCapabilities = readRuntimeModelCapabilities(runtimeModel)
if (!runtimeCapabilities) {
return undefined
}
const nestedModalities = normalizeModalities(runtimeCapabilities.modalities)
if (nestedModalities) {
return nestedModalities
}
const capabilityModalities = normalizeModalities(runtimeCapabilities)
if (capabilityModalities) {
return capabilityModalities
}
return undefined
}
function readRuntimeModelVariants(runtimeModel: Record<string, unknown> | undefined): string[] | undefined {
if (!runtimeModel) {
return undefined
}
const rootVariants = normalizeVariantKeys(runtimeModel.variants)
if (rootVariants) {
return rootVariants
}
const runtimeCapabilities = readRuntimeModelCapabilities(runtimeModel)
if (!runtimeCapabilities) {
return undefined
}
return normalizeVariantKeys(runtimeCapabilities.variants)
}
function readRuntimeModelTopPSupport(runtimeModel: Record<string, unknown> | undefined): boolean | undefined {
return readRuntimeModelBoolean(runtimeModel, ["topP", "top_p"])
}
function readRuntimeModelToolCallSupport(runtimeModel: Record<string, unknown> | undefined): boolean | undefined {
return readRuntimeModelBoolean(runtimeModel, ["toolCall", "tool_call", "toolcall"])
}
function readRuntimeModelReasoningSupport(runtimeModel: Record<string, unknown> | undefined): boolean | undefined {
return readRuntimeModelBoolean(runtimeModel, ["reasoning"])
}
function readRuntimeModelTemperatureSupport(runtimeModel: Record<string, unknown> | undefined): boolean | undefined {
return readRuntimeModelBoolean(runtimeModel, ["temperature"])
}
function readRuntimeModelThinkingSupport(runtimeModel: Record<string, unknown> | undefined): boolean | undefined {
const capabilityValue = readRuntimeModelReasoningSupport(runtimeModel)
if (capabilityValue !== undefined) {
return capabilityValue
}
const rootThinkingSupport = readRuntimeModelBoolean(runtimeModel, ["thinking", "supportsThinking"])
if (rootThinkingSupport !== undefined) {
return rootThinkingSupport
}
const runtimeCapabilities = readRuntimeModelCapabilities(runtimeModel)
if (!runtimeCapabilities) {
return undefined
}
for (const key of ["thinking", "supportsThinking"] as const) {
const value = runtimeCapabilities[key]
if (typeof value === "boolean") {
return value
}
}
return undefined
}
function readRuntimeModel(runtimeModel: ModelMetadata | Record<string, unknown> | undefined): Record<string, unknown> | undefined {
return isRecord(runtimeModel) ? runtimeModel : undefined
}
const bundledModelCapabilitiesSnapshot = normalizeSnapshot(bundledModelCapabilitiesSnapshotJson)
export function getBundledModelCapabilitiesSnapshot(): ModelCapabilitiesSnapshot {
return bundledModelCapabilitiesSnapshot
}
export function getModelCapabilities(input: GetModelCapabilitiesInput): ModelCapabilities {
const canonicalization = resolveModelIDAlias(input.modelID)
const requestedModelID = canonicalization.requestedModelID
const canonicalModelID = canonicalization.canonicalModelID
const override = getOverride(input.modelID)
const runtimeModel = readRuntimeModel(
input.runtimeModel ?? findProviderModelMetadata(input.providerID, input.modelID),
)
const runtimeSnapshot = input.runtimeSnapshot
const bundledSnapshot = input.bundledSnapshot ?? bundledModelCapabilitiesSnapshot
const snapshotEntry = runtimeSnapshot?.models?.[canonicalModelID] ?? bundledSnapshot.models[canonicalModelID]
const heuristicFamily = detectHeuristicModelFamily(canonicalModelID)
const runtimeVariants = readRuntimeModelVariants(runtimeModel)
const snapshotSource: ModelCapabilitiesDiagnostics["snapshot"]["source"] =
runtimeSnapshot?.models?.[canonicalModelID]
? "runtime-snapshot"
: bundledSnapshot.models[canonicalModelID]
? "bundled-snapshot"
: "none"
const familySource: ModelCapabilitiesDiagnostics["family"]["source"] =
snapshotEntry?.family
? "snapshot"
: heuristicFamily?.family
? "heuristic"
: "none"
const variantsSource: ModelCapabilitiesDiagnostics["variants"]["source"] =
runtimeVariants
? "runtime"
: override?.variants
? "override"
: heuristicFamily?.variants
? "heuristic"
: "none"
const reasoningEffortsSource: ModelCapabilitiesDiagnostics["reasoningEfforts"]["source"] =
override?.reasoningEfforts
? "override"
: heuristicFamily?.reasoningEfforts
? "heuristic"
: "none"
const reasoningSource: ModelCapabilitiesDiagnostics["reasoning"]["source"] =
readRuntimeModelReasoningSupport(runtimeModel) !== undefined
? "runtime"
: snapshotEntry?.reasoning !== undefined
? snapshotSource
: "none"
const supportsThinkingSource: ModelCapabilitiesDiagnostics["supportsThinking"]["source"] =
override?.supportsThinking !== undefined
? "override"
: heuristicFamily?.supportsThinking !== undefined
? "heuristic"
: readRuntimeModelThinkingSupport(runtimeModel) !== undefined
? "runtime"
: snapshotEntry?.reasoning !== undefined
? snapshotSource
: "none"
const supportsTemperatureSource: ModelCapabilitiesDiagnostics["supportsTemperature"]["source"] =
readRuntimeModelTemperatureSupport(runtimeModel) !== undefined
? "runtime"
: override?.supportsTemperature !== undefined
? "override"
: snapshotEntry?.temperature !== undefined
? snapshotSource
: "none"
const supportsTopPSource: ModelCapabilitiesDiagnostics["supportsTopP"]["source"] =
readRuntimeModelTopPSupport(runtimeModel) !== undefined
? "runtime"
: override?.supportsTopP !== undefined
? "override"
: "none"
const maxOutputTokensSource: ModelCapabilitiesDiagnostics["maxOutputTokens"]["source"] =
readRuntimeModelLimitOutput(runtimeModel) !== undefined
? "runtime"
: snapshotEntry?.limit?.output !== undefined
? snapshotSource
: "none"
const toolCallSource: ModelCapabilitiesDiagnostics["toolCall"]["source"] =
readRuntimeModelToolCallSupport(runtimeModel) !== undefined
? "runtime"
: snapshotEntry?.toolCall !== undefined
? snapshotSource
: "none"
const modalitiesSource: ModelCapabilitiesDiagnostics["modalities"]["source"] =
readRuntimeModelModalities(runtimeModel) !== undefined
? "runtime"
: snapshotEntry?.modalities !== undefined
? snapshotSource
: "none"
const resolutionMode: ModelCapabilitiesDiagnostics["resolutionMode"] =
snapshotSource !== "none" && canonicalization.source === "canonical"
? "snapshot-backed"
: snapshotSource !== "none"
? "alias-backed"
: familySource === "heuristic" || variantsSource === "heuristic" || reasoningEffortsSource === "heuristic"
? "heuristic-backed"
: "unknown"
return {
requestedModelID,
canonicalModelID,
family: snapshotEntry?.family ?? heuristicFamily?.family,
variants: runtimeVariants ?? override?.variants ?? heuristicFamily?.variants,
reasoningEfforts: override?.reasoningEfforts ?? heuristicFamily?.reasoningEfforts,
reasoning: readRuntimeModelReasoningSupport(runtimeModel) ?? snapshotEntry?.reasoning,
supportsThinking:
override?.supportsThinking
?? heuristicFamily?.supportsThinking
?? readRuntimeModelThinkingSupport(runtimeModel)
?? snapshotEntry?.reasoning,
supportsTemperature:
readRuntimeModelTemperatureSupport(runtimeModel)
?? override?.supportsTemperature
?? snapshotEntry?.temperature,
supportsTopP:
readRuntimeModelTopPSupport(runtimeModel)
?? override?.supportsTopP,
maxOutputTokens:
readRuntimeModelLimitOutput(runtimeModel)
?? snapshotEntry?.limit?.output,
toolCall:
readRuntimeModelToolCallSupport(runtimeModel)
?? snapshotEntry?.toolCall,
modalities:
readRuntimeModelModalities(runtimeModel)
?? snapshotEntry?.modalities,
diagnostics: {
resolutionMode,
canonicalization: {
source: canonicalization.source,
...(canonicalization.ruleID ? { ruleID: canonicalization.ruleID } : {}),
},
snapshot: { source: snapshotSource },
family: { source: familySource },
variants: { source: variantsSource },
reasoningEfforts: { source: reasoningEffortsSource },
reasoning: { source: reasoningSource },
supportsThinking: { source: supportsThinkingSource },
supportsTemperature: { source: supportsTemperatureSource },
supportsTopP: { source: supportsTopPSource },
maxOutputTokens: { source: maxOutputTokensSource },
toolCall: { source: toolCallSource },
modalities: { source: modalitiesSource },
},
}
}
@@ -0,0 +1,15 @@
import bundledModelCapabilitiesSnapshotJson from "../../generated/model-capabilities.generated.json"
import type { ModelCapabilitiesSnapshot } from "./types"
function normalizeSnapshot(
snapshot: ModelCapabilitiesSnapshot | typeof bundledModelCapabilitiesSnapshotJson,
): ModelCapabilitiesSnapshot {
return snapshot as ModelCapabilitiesSnapshot
}
const bundledModelCapabilitiesSnapshot = normalizeSnapshot(bundledModelCapabilitiesSnapshotJson)
export function getBundledModelCapabilitiesSnapshot(): ModelCapabilitiesSnapshot {
return bundledModelCapabilitiesSnapshot
}
@@ -0,0 +1,140 @@
import { findProviderModelMetadata } from "../connected-providers-cache"
import { resolveModelIDAlias } from "../model-capability-aliases"
import { detectHeuristicModelFamily } from "../model-capability-heuristics"
import { getBundledModelCapabilitiesSnapshot } from "./bundled-snapshot"
import {
readRuntimeModel,
readRuntimeModelLimitOutput,
readRuntimeModelModalities,
readRuntimeModelReasoningSupport,
readRuntimeModelTemperatureSupport,
readRuntimeModelThinkingSupport,
readRuntimeModelToolCallSupport,
readRuntimeModelTopPSupport,
readRuntimeModelVariants,
} from "./runtime-model-readers"
import type {
GetModelCapabilitiesInput,
ModelCapabilities,
ModelCapabilitiesDiagnostics,
ModelCapabilityOverride,
} from "./types"
const MODEL_ID_OVERRIDES: Record<string, ModelCapabilityOverride> = {}
function normalizeLookupModelID(modelID: string): string {
return modelID.trim().toLowerCase()
}
function getOverride(modelID: string): ModelCapabilityOverride | undefined {
return MODEL_ID_OVERRIDES[normalizeLookupModelID(modelID)]
}
export function getModelCapabilities(input: GetModelCapabilitiesInput): ModelCapabilities {
const canonicalization = resolveModelIDAlias(input.modelID)
const override = getOverride(input.modelID)
const runtimeModel = readRuntimeModel(
input.runtimeModel ?? findProviderModelMetadata(input.providerID, input.modelID),
)
const runtimeSnapshot = input.runtimeSnapshot
const bundledSnapshot = input.bundledSnapshot ?? getBundledModelCapabilitiesSnapshot()
const snapshotEntry = runtimeSnapshot?.models?.[canonicalization.canonicalModelID]
?? bundledSnapshot.models[canonicalization.canonicalModelID]
const heuristicFamily = detectHeuristicModelFamily(canonicalization.canonicalModelID)
const runtimeVariants = readRuntimeModelVariants(runtimeModel)
const runtimeReasoning = readRuntimeModelReasoningSupport(runtimeModel)
const runtimeThinking = readRuntimeModelThinkingSupport(runtimeModel)
const runtimeTemperature = readRuntimeModelTemperatureSupport(runtimeModel)
const runtimeTopP = readRuntimeModelTopPSupport(runtimeModel)
const runtimeMaxOutputTokens = readRuntimeModelLimitOutput(runtimeModel)
const runtimeToolCall = readRuntimeModelToolCallSupport(runtimeModel)
const runtimeModalities = readRuntimeModelModalities(runtimeModel)
const snapshotSource: ModelCapabilitiesDiagnostics["snapshot"]["source"] =
runtimeSnapshot?.models?.[canonicalization.canonicalModelID]
? "runtime-snapshot"
: bundledSnapshot.models[canonicalization.canonicalModelID]
? "bundled-snapshot"
: "none"
const familySource: ModelCapabilitiesDiagnostics["family"]["source"] =
snapshotEntry?.family ? "snapshot" : heuristicFamily?.family ? "heuristic" : "none"
const variantsSource: ModelCapabilitiesDiagnostics["variants"]["source"] =
runtimeVariants ? "runtime" : override?.variants ? "override" : heuristicFamily?.variants ? "heuristic" : "none"
const reasoningEffortsSource: ModelCapabilitiesDiagnostics["reasoningEfforts"]["source"] =
override?.reasoningEfforts ? "override" : heuristicFamily?.reasoningEfforts ? "heuristic" : "none"
const reasoningSource: ModelCapabilitiesDiagnostics["reasoning"]["source"] =
runtimeReasoning === undefined ? snapshotEntry?.reasoning === undefined ? "none" : snapshotSource : "runtime"
const supportsThinkingSource: ModelCapabilitiesDiagnostics["supportsThinking"]["source"] =
override?.supportsThinking !== undefined
? "override"
: heuristicFamily?.supportsThinking !== undefined
? "heuristic"
: runtimeThinking !== undefined
? "runtime"
: snapshotEntry?.reasoning !== undefined
? snapshotSource
: "none"
const supportsTemperatureSource: ModelCapabilitiesDiagnostics["supportsTemperature"]["source"] =
runtimeTemperature !== undefined
? "runtime"
: override?.supportsTemperature !== undefined
? "override"
: snapshotEntry?.temperature !== undefined
? snapshotSource
: "none"
const supportsTopPSource: ModelCapabilitiesDiagnostics["supportsTopP"]["source"] =
runtimeTopP !== undefined ? "runtime" : override?.supportsTopP !== undefined ? "override" : "none"
const maxOutputTokensSource: ModelCapabilitiesDiagnostics["maxOutputTokens"]["source"] =
runtimeMaxOutputTokens !== undefined
? "runtime"
: snapshotEntry?.limit?.output !== undefined
? snapshotSource
: "none"
const toolCallSource: ModelCapabilitiesDiagnostics["toolCall"]["source"] =
runtimeToolCall !== undefined ? "runtime" : snapshotEntry?.toolCall !== undefined ? snapshotSource : "none"
const modalitiesSource: ModelCapabilitiesDiagnostics["modalities"]["source"] =
runtimeModalities !== undefined ? "runtime" : snapshotEntry?.modalities !== undefined ? snapshotSource : "none"
const resolutionMode: ModelCapabilitiesDiagnostics["resolutionMode"] =
snapshotSource !== "none" && canonicalization.source === "canonical"
? "snapshot-backed"
: snapshotSource !== "none"
? "alias-backed"
: familySource === "heuristic" || variantsSource === "heuristic" || reasoningEffortsSource === "heuristic"
? "heuristic-backed"
: "unknown"
return {
requestedModelID: canonicalization.requestedModelID,
canonicalModelID: canonicalization.canonicalModelID,
family: snapshotEntry?.family ?? heuristicFamily?.family,
variants: runtimeVariants ?? override?.variants ?? heuristicFamily?.variants,
reasoningEfforts: override?.reasoningEfforts ?? heuristicFamily?.reasoningEfforts,
reasoning: runtimeReasoning ?? snapshotEntry?.reasoning,
supportsThinking: override?.supportsThinking ?? heuristicFamily?.supportsThinking ?? runtimeThinking ?? snapshotEntry?.reasoning,
supportsTemperature: runtimeTemperature ?? override?.supportsTemperature ?? snapshotEntry?.temperature,
supportsTopP: runtimeTopP ?? override?.supportsTopP,
maxOutputTokens: runtimeMaxOutputTokens ?? snapshotEntry?.limit?.output,
toolCall: runtimeToolCall ?? snapshotEntry?.toolCall,
modalities: runtimeModalities ?? snapshotEntry?.modalities,
diagnostics: {
resolutionMode,
canonicalization: {
source: canonicalization.source,
...(canonicalization.ruleID ? { ruleID: canonicalization.ruleID } : {}),
},
snapshot: { source: snapshotSource },
family: { source: familySource },
variants: { source: variantsSource },
reasoningEfforts: { source: reasoningEffortsSource },
reasoning: { source: reasoningSource },
supportsThinking: { source: supportsThinkingSource },
supportsTemperature: { source: supportsTemperatureSource },
supportsTopP: { source: supportsTopPSource },
maxOutputTokens: { source: maxOutputTokensSource },
toolCall: { source: toolCallSource },
modalities: { source: modalitiesSource },
},
}
}
+9
View File
@@ -0,0 +1,9 @@
export { getBundledModelCapabilitiesSnapshot } from "./bundled-snapshot"
export { getModelCapabilities } from "./get-model-capabilities"
export type {
GetModelCapabilitiesInput,
ModelCapabilities,
ModelCapabilitiesDiagnostics,
ModelCapabilitiesSnapshot,
ModelCapabilitiesSnapshotEntry,
} from "./types"
@@ -0,0 +1,190 @@
import type { ModelMetadata } from "../connected-providers-cache"
import type { ModelCapabilities } from "./types"
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value)
}
function readNumber(value: unknown): number | undefined {
return typeof value === "number" ? value : undefined
}
function readStringArray(value: unknown): string[] | undefined {
if (!Array.isArray(value)) {
return undefined
}
const strings = value.filter((item): item is string => typeof item === "string")
return strings.length > 0 ? strings : undefined
}
function normalizeVariantKeys(value: unknown): string[] | undefined {
const arrayVariants = readStringArray(value)
if (arrayVariants) {
return arrayVariants.map((variant) => variant.toLowerCase())
}
if (!isRecord(value)) {
return undefined
}
const variants = Object.keys(value).map((variant) => variant.toLowerCase())
return variants.length > 0 ? variants : undefined
}
function readModalityKeys(value: unknown): string[] | undefined {
const stringArray = readStringArray(value)
if (stringArray) {
return stringArray.map((entry) => entry.toLowerCase())
}
if (!isRecord(value)) {
return undefined
}
const enabled = Object.entries(value)
.filter(([, supported]) => supported === true)
.map(([modality]) => modality.toLowerCase())
return enabled.length > 0 ? enabled : undefined
}
function normalizeModalities(value: unknown): ModelCapabilities["modalities"] | undefined {
if (!isRecord(value)) {
return undefined
}
const input = readModalityKeys(value.input)
const output = readModalityKeys(value.output)
if (!input && !output) {
return undefined
}
return {
...(input ? { input } : {}),
...(output ? { output } : {}),
}
}
function readRuntimeModelCapabilities(
runtimeModel: Record<string, unknown> | undefined,
): Record<string, unknown> | undefined {
return isRecord(runtimeModel?.capabilities) ? runtimeModel.capabilities : undefined
}
function readRuntimeModelBoolean(
runtimeModel: Record<string, unknown> | undefined,
keys: string[],
): boolean | undefined {
const runtimeCapabilities = readRuntimeModelCapabilities(runtimeModel)
for (const key of keys) {
const value = runtimeModel?.[key]
if (typeof value === "boolean") {
return value
}
const capabilityValue = runtimeCapabilities?.[key]
if (typeof capabilityValue === "boolean") {
return capabilityValue
}
}
return undefined
}
export function readRuntimeModel(
runtimeModel: ModelMetadata | Record<string, unknown> | undefined,
): Record<string, unknown> | undefined {
return isRecord(runtimeModel) ? runtimeModel : undefined
}
export function readRuntimeModelVariants(
runtimeModel: Record<string, unknown> | undefined,
): string[] | undefined {
const rootVariants = normalizeVariantKeys(runtimeModel?.variants)
if (rootVariants) {
return rootVariants
}
return normalizeVariantKeys(readRuntimeModelCapabilities(runtimeModel)?.variants)
}
export function readRuntimeModelModalities(
runtimeModel: Record<string, unknown> | undefined,
): ModelCapabilities["modalities"] | undefined {
const rootModalities = normalizeModalities(runtimeModel?.modalities)
if (rootModalities) {
return rootModalities
}
const runtimeCapabilities = readRuntimeModelCapabilities(runtimeModel)
return (
normalizeModalities(runtimeCapabilities?.modalities)
?? normalizeModalities(runtimeCapabilities)
)
}
export function readRuntimeModelReasoningSupport(
runtimeModel: Record<string, unknown> | undefined,
): boolean | undefined {
return readRuntimeModelBoolean(runtimeModel, ["reasoning"])
}
export function readRuntimeModelThinkingSupport(
runtimeModel: Record<string, unknown> | undefined,
): boolean | undefined {
const capabilityValue = readRuntimeModelReasoningSupport(runtimeModel)
if (capabilityValue !== undefined) {
return capabilityValue
}
const thinkingSupport = readRuntimeModelBoolean(runtimeModel, ["thinking", "supportsThinking"])
if (thinkingSupport !== undefined) {
return thinkingSupport
}
const runtimeCapabilities = readRuntimeModelCapabilities(runtimeModel)
for (const key of ["thinking", "supportsThinking"] as const) {
const value = runtimeCapabilities?.[key]
if (typeof value === "boolean") {
return value
}
}
return undefined
}
export function readRuntimeModelTemperatureSupport(
runtimeModel: Record<string, unknown> | undefined,
): boolean | undefined {
return readRuntimeModelBoolean(runtimeModel, ["temperature"])
}
export function readRuntimeModelTopPSupport(
runtimeModel: Record<string, unknown> | undefined,
): boolean | undefined {
return readRuntimeModelBoolean(runtimeModel, ["topP", "top_p"])
}
export function readRuntimeModelToolCallSupport(
runtimeModel: Record<string, unknown> | undefined,
): boolean | undefined {
return readRuntimeModelBoolean(runtimeModel, ["toolCall", "tool_call", "toolcall"])
}
export function readRuntimeModelLimitOutput(
runtimeModel: Record<string, unknown> | undefined,
): number | undefined {
const limit = isRecord(runtimeModel?.limit)
? runtimeModel.limit
: readRuntimeModelCapabilities(runtimeModel)?.limit
if (!isRecord(limit)) {
return undefined
}
return readNumber(limit.output)
}
+80
View File
@@ -0,0 +1,80 @@
import type { ModelMetadata } from "../connected-providers-cache"
export type ModelCapabilitiesSnapshotEntry = {
id: string
family?: string
reasoning?: boolean
temperature?: boolean
toolCall?: boolean
modalities?: {
input?: string[]
output?: string[]
}
limit?: {
context?: number
input?: number
output?: number
}
}
export type ModelCapabilitiesSnapshot = {
generatedAt: string
sourceUrl: string
models: Record<string, ModelCapabilitiesSnapshotEntry>
}
export type ModelCapabilitiesDiagnostics = {
resolutionMode: "snapshot-backed" | "alias-backed" | "heuristic-backed" | "unknown"
canonicalization: {
source: "canonical" | "exact-alias" | "pattern-alias"
ruleID?: string
}
snapshot: {
source: "runtime-snapshot" | "bundled-snapshot" | "none"
}
family: { source: "snapshot" | "heuristic" | "none" }
variants: { source: "none" | "runtime" | "override" | "heuristic" | "canonical" }
reasoningEfforts: { source: "none" | "override" | "heuristic" }
reasoning: { source: "runtime" | "runtime-snapshot" | "bundled-snapshot" | "none" }
supportsThinking: { source: "runtime" | "override" | "heuristic" | "runtime-snapshot" | "bundled-snapshot" | "none" }
supportsTemperature: { source: "runtime" | "override" | "runtime-snapshot" | "bundled-snapshot" | "none" }
supportsTopP: { source: "runtime" | "override" | "none" }
maxOutputTokens: { source: "runtime" | "runtime-snapshot" | "bundled-snapshot" | "none" }
toolCall: { source: "runtime" | "runtime-snapshot" | "bundled-snapshot" | "none" }
modalities: { source: "runtime" | "runtime-snapshot" | "bundled-snapshot" | "none" }
}
export type ModelCapabilities = {
requestedModelID: string
canonicalModelID: string
family?: string
variants?: string[]
reasoningEfforts?: string[]
reasoning?: boolean
supportsThinking?: boolean
supportsTemperature?: boolean
supportsTopP?: boolean
maxOutputTokens?: number
toolCall?: boolean
modalities?: {
input?: string[]
output?: string[]
}
diagnostics: ModelCapabilitiesDiagnostics
}
export type GetModelCapabilitiesInput = {
providerID: string
modelID: string
runtimeModel?: ModelMetadata | Record<string, unknown>
runtimeSnapshot?: ModelCapabilitiesSnapshot
bundledSnapshot?: ModelCapabilitiesSnapshot
}
export type ModelCapabilityOverride = {
variants?: string[]
reasoningEfforts?: string[]
supportsThinking?: boolean
supportsTemperature?: boolean
supportsTopP?: boolean
}
+1 -2
View File
@@ -92,7 +92,7 @@ export function flattenToFallbackModelStrings(
// invalid strings like "provider/model high(low)".
const model = entry.model
.replace(/\([^()]+\)\s*$/, "")
.replace(/\s+([a-z][a-z0-9_-]*)\s*$/i, (match, suffix) => {
.replace(/\s+([a-z][a-z0-9_-]*)\s*$/i, (match: string, suffix: string) => {
const normalized = String(suffix).toLowerCase()
return KNOWN_VARIANTS.has(normalized)
? ""
@@ -101,7 +101,6 @@ export function flattenToFallbackModelStrings(
.trim()
return `${model}(${variant})`
}
// No explicit variant — preserve model string as-is (including any inline variant)
return entry.model
})
}
@@ -244,10 +244,6 @@ describe("resolveCompatibleModelSettings", () => {
expect(result.changes).toEqual([])
})
// -----------------------------------------------------------------------
// Registry coverage — every model family from FAMILY_CAPABILITIES
// -----------------------------------------------------------------------
describe("model family registry coverage", () => {
const familyCases: Array<{
name: string
@@ -309,7 +305,6 @@ describe("resolveCompatibleModelSettings", () => {
}
})
// GPT-5 specific: supports xhigh variant and xhigh reasoningEffort
test("GPT-5 keeps xhigh variant and reasoningEffort", () => {
const result = resolveCompatibleModelSettings({
providerID: "openai",
@@ -345,7 +340,6 @@ describe("resolveCompatibleModelSettings", () => {
})
})
// Reasoning effort: "none" and "minimal" are valid per Vercel AI SDK
test("GPT-5 keeps none reasoningEffort", () => {
const result = resolveCompatibleModelSettings({
providerID: "openai",
@@ -388,7 +382,6 @@ describe("resolveCompatibleModelSettings", () => {
})
})
// Reasoning effort downgrade within families that support it
test("o-series downgrades xhigh reasoningEffort to high", () => {
const result = resolveCompatibleModelSettings({
providerID: "openai",
@@ -408,9 +401,6 @@ describe("resolveCompatibleModelSettings", () => {
})
test("GPT-5 keeps xhigh but would downgrade a hypothetical beyond-max level", () => {
// GPT-5 supports up to "xhigh" — verify the ladder works by requesting
// a value that IS in the ladder but NOT in the family's allowed list.
// Since "xhigh" is the max for GPT-5 reasoningEffort, we verify it stays.
const result = resolveCompatibleModelSettings({
providerID: "openai",
modelID: "gpt-5.4",
+1 -13
View File
@@ -51,10 +51,6 @@ export type ModelSettingsCompatibilityResult = {
const VARIANT_LADDER = ["low", "medium", "high", "xhigh", "max"]
const REASONING_LADDER = ["none", "minimal", "low", "medium", "high", "xhigh"]
// ---------------------------------------------------------------------------
// Generic resolution — one function for both fields
// ---------------------------------------------------------------------------
function downgradeWithinLadder(value: string, allowed: string[], ladder: string[]): string | undefined {
const requestedIndex = ladder.indexOf(value)
if (requestedIndex === -1) return undefined
@@ -91,7 +87,6 @@ function resolveField(
familyKnown: boolean,
metadataOverride?: string[],
): FieldResolution {
// Priority 1: runtime metadata from provider
if (metadataOverride) {
if (metadataOverride.includes(normalized)) return { value: normalized }
return {
@@ -100,7 +95,6 @@ function resolveField(
}
}
// Priority 2: family heuristic from registry
if (familyCaps) {
if (familyCaps.includes(normalized)) return { value: normalized }
return {
@@ -109,24 +103,18 @@ function resolveField(
}
}
// Known family but field not in registry (e.g. Claude + reasoningEffort)
if (familyKnown) {
return { value: undefined, reason: "unsupported-by-model-family" }
}
// Unknown family — drop the value
return { value: undefined, reason: "unknown-model-family" }
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
export function resolveCompatibleModelSettings(
input: ModelSettingsCompatibilityInput,
): ModelSettingsCompatibilityResult {
const family = detectHeuristicModelFamily(input.modelID)
const familyKnown = family !== undefined
const familyKnown = Boolean(family)
const changes: ModelSettingsCompatibilityChange[] = []
const metadataVariants = normalizeCapabilitiesVariants(input.capabilities)
const metadataReasoningEfforts = normalizeCapabilitiesReasoningEfforts(input.capabilities)
-10
View File
@@ -93,7 +93,6 @@ export async function promptWithModelSuggestionRetry(
): Promise<void> {
const timeoutMs = options.timeoutMs ?? PROMPT_TIMEOUT_MS
const timeoutContext = createPromptTimeoutContext(args, timeoutMs)
// NOTE: Model suggestion retry removed — promptAsync returns 204 immediately,
// model errors happen asynchronously server-side and cannot be caught here
const promptPromise = client.session.promptAsync({
...args,
@@ -115,15 +114,6 @@ export async function promptWithModelSuggestionRetry(
}
}
/**
* Synchronous variant of promptWithModelSuggestionRetry.
*
* Uses `session.prompt` (blocking HTTP call that waits for the LLM response)
* instead of `promptAsync` (fire-and-forget HTTP 204).
*
* Required by callers that need the response to be available immediately after
* the call returns — e.g. look_at, which reads session messages right away.
*/
export async function promptSyncWithModelSuggestionRetry(
client: Client,
args: PromptArgs,
@@ -108,21 +108,21 @@ describe("isSqliteBackend", () => {
//#given
versionReturnValue = true
//#when: first call DB does not exist
//#when: first call, DB does not exist
const first = isSqliteBackend()
//#then
expect(first).toBe(false)
expect(versionCheckCalls.length).toBe(1)
//#when: second call DB still does not exist (retry)
//#when: second call, DB still does not exist (retry)
const second = isSqliteBackend()
//#then: retried once
expect(second).toBe(false)
expect(versionCheckCalls.length).toBe(2)
//#when: third call no more retries
//#when: third call, no more retries
const third = isSqliteBackend()
//#then: no further checks
@@ -134,7 +134,7 @@ describe("isSqliteBackend", () => {
//#given
versionReturnValue = true
//#when: first call DB does not exist
//#when: first call, DB does not exist
const first = isSqliteBackend()
//#then
@@ -144,18 +144,18 @@ describe("isSqliteBackend", () => {
mkdirSync(join(TEST_DATA_DIR, "opencode"), { recursive: true })
writeFileSync(DB_PATH, "")
//#when: second call retry finds DB
//#when: second call, retry finds DB
const second = isSqliteBackend()
//#then: recovers to true and caches permanently
expect(second).toBe(true)
expect(versionCheckCalls.length).toBe(2)
//#when: third call cached true
//#when: third call, cached true
const third = isSqliteBackend()
//#then: no further checks
expect(third).toBe(true)
expect(versionCheckCalls.length).toBe(2)
})
})
})