fix(provider): type default model failures (#28881)

This commit is contained in:
Shoubhit Dash
2026-05-22 23:53:23 +05:30
committed by GitHub
parent 968aaa3cfe
commit 5f42351159
5 changed files with 60 additions and 9 deletions
+1 -1
View File
@@ -69,7 +69,7 @@ export interface Interface {
whenToUse: string whenToUse: string
systemPrompt: string systemPrompt: string
}, },
Provider.ModelNotFoundError Provider.DefaultModelError
> >
} }
+29 -3
View File
@@ -1,6 +1,6 @@
import { EOL } from "os" import { EOL } from "os"
import { basename } from "path" import { basename } from "path"
import { Effect } from "effect" import { Cause, Effect } from "effect"
import { Agent } from "../../../agent/agent" import { Agent } from "../../../agent/agent"
import { Provider } from "@/provider/provider" import { Provider } from "@/provider/provider"
import { Session } from "@/session/session" import { Session } from "@/session/session"
@@ -80,7 +80,21 @@ const run = Effect.fn("Cli.debug.agent.body")(function* (
const getAvailableTools = Effect.fn("Cli.debug.agent.getAvailableTools")(function* (agent: Agent.Info) { const getAvailableTools = Effect.fn("Cli.debug.agent.getAvailableTools")(function* (agent: Agent.Info) {
const provider = yield* Provider.Service const provider = yield* Provider.Service
const registry = yield* ToolRegistry.Service const registry = yield* ToolRegistry.Service
const model = agent.model ?? (yield* provider.defaultModel()) const model =
agent.model ??
(yield* provider.defaultModel().pipe(
Effect.matchCauseEffect({
onSuccess: Effect.succeed,
onFailure: (cause) => {
const error = Cause.squash(cause) as Provider.DefaultModelError
if (error instanceof Provider.ModelNotFoundError) {
return fail(`Model not found: ${error.providerID}/${error.modelID}`)
}
if (error instanceof Provider.NoModelsError) return fail(`No models found for provider ${error.providerID}`)
return fail("No providers found")
},
}),
))
return yield* registry.tools({ ...model, agent }) return yield* registry.tools({ ...model, agent })
}) })
@@ -133,7 +147,19 @@ const createToolContext = Effect.fn("Cli.debug.agent.createToolContext")(functio
? agent.model ? agent.model
: yield* Effect.gen(function* () { : yield* Effect.gen(function* () {
const provider = yield* Provider.Service const provider = yield* Provider.Service
return yield* provider.defaultModel() return yield* provider.defaultModel().pipe(
Effect.matchCauseEffect({
onSuccess: Effect.succeed,
onFailure: (cause) => {
const error = Cause.squash(cause) as Provider.DefaultModelError
if (error instanceof Provider.ModelNotFoundError) {
return fail(`Model not found: ${error.providerID}/${error.modelID}`)
}
if (error instanceof Provider.NoModelsError) return fail(`No models found for provider ${error.providerID}`)
return fail("No providers found")
},
}),
)
}) })
const now = Date.now() const now = Date.now()
const message: MessageV2.Assistant = { const message: MessageV2.Assistant = {
+19 -4
View File
@@ -994,7 +994,22 @@ export class InitError extends Schema.TaggedErrorClass<InitError>()("ProviderIni
} }
} }
export type Error = ModelNotFoundError | InitError export class NoProvidersError extends Schema.TaggedErrorClass<NoProvidersError>()("ProviderNoProvidersError", {}) {
static isInstance(input: unknown): input is NoProvidersError {
return input instanceof NoProvidersError
}
}
export class NoModelsError extends Schema.TaggedErrorClass<NoModelsError>()("ProviderNoModelsError", {
providerID: ProviderID,
}) {
static isInstance(input: unknown): input is NoModelsError {
return input instanceof NoModelsError
}
}
export type DefaultModelError = ModelNotFoundError | NoProvidersError | NoModelsError
export type Error = ModelNotFoundError | InitError | NoProvidersError | NoModelsError
export interface Interface { export interface Interface {
readonly list: () => Effect.Effect<Record<ProviderID, Info>> readonly list: () => Effect.Effect<Record<ProviderID, Info>>
@@ -1006,7 +1021,7 @@ export interface Interface {
query: string[], query: string[],
) => Effect.Effect<{ providerID: ProviderID; modelID: string } | undefined> ) => Effect.Effect<{ providerID: ProviderID; modelID: string } | undefined>
readonly getSmallModel: (providerID: ProviderID) => Effect.Effect<Model | undefined> readonly getSmallModel: (providerID: ProviderID) => Effect.Effect<Model | undefined>
readonly defaultModel: () => Effect.Effect<{ providerID: ProviderID; modelID: ModelID }> readonly defaultModel: () => Effect.Effect<{ providerID: ProviderID; modelID: ModelID }, DefaultModelError>
} }
interface State { interface State {
@@ -1821,9 +1836,9 @@ export const layer = Layer.effect(
} }
const provider = Object.values(s.providers).find((p) => !cfg.provider || Object.keys(cfg.provider).includes(p.id)) const provider = Object.values(s.providers).find((p) => !cfg.provider || Object.keys(cfg.provider).includes(p.id))
if (!provider) throw new Error("no providers found") if (!provider) return yield* new NoProvidersError()
const [model] = sort(Object.values(provider.models)) const [model] = sort(Object.values(provider.models))
if (!model) throw new Error("no models found") if (!model) return yield* new NoModelsError({ providerID: provider.id })
return { return {
providerID: provider.id, providerID: provider.id,
modelID: model.id, modelID: model.id,
+1 -1
View File
@@ -682,7 +682,7 @@ export const layer = Layer.effect(
.findMessage(sessionID, (m) => m.info.role === "user" && !!m.info.model) .findMessage(sessionID, (m) => m.info.role === "user" && !!m.info.model)
.pipe(Effect.orDie) .pipe(Effect.orDie)
if (Option.isSome(match) && match.value.info.role === "user") return match.value.info.model if (Option.isSome(match) && match.value.info.role === "user") return match.value.info.model
return yield* provider.defaultModel() return yield* provider.defaultModel().pipe(Effect.orDie)
}) })
const createUserMessage = Effect.fn("SessionPrompt.createUserMessage")(function* (input: PromptInput) { const createUserMessage = Effect.fn("SessionPrompt.createUserMessage")(function* (input: PromptInput) {
@@ -351,6 +351,16 @@ it.instance(
{ config: { model: "anthropic/claude-sonnet-4-20250514" } }, { config: { model: "anthropic/claude-sonnet-4-20250514" } },
) )
it.instance(
"defaultModel returns a typed error when config excludes every provider",
Effect.gen(function* () {
const error = yield* Provider.use.defaultModel().pipe(Effect.flip)
expect(error).toBeInstanceOf(Provider.NoProvidersError)
expect(error._tag).toBe("ProviderNoProvidersError")
}),
{ config: { enabled_providers: [] } },
)
it.instance( it.instance(
"provider with baseURL from config", "provider with baseURL from config",
Effect.gen(function* () { Effect.gen(function* () {