|
@@ -21,6 +21,7 @@ import { pathToFileURL } from "url"
|
|
|
import { Effect, Layer, Context, Schema, Types } from "effect"
|
|
import { Effect, Layer, Context, Schema, Types } from "effect"
|
|
|
import { EffectBridge } from "@/effect/bridge"
|
|
import { EffectBridge } from "@/effect/bridge"
|
|
|
import { InstanceState } from "@/effect/instance-state"
|
|
import { InstanceState } from "@/effect/instance-state"
|
|
|
|
|
+import { EffectPromise } from "@/effect/promise"
|
|
|
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
|
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
|
|
import { isRecord } from "@/util/record"
|
|
import { isRecord } from "@/util/record"
|
|
|
import { optionalOmitUndefined } from "@opencode-ai/core/schema"
|
|
import { optionalOmitUndefined } from "@opencode-ai/core/schema"
|
|
@@ -963,11 +964,24 @@ export function defaultModelIDs<T extends { models: Record<string, { id: string
|
|
|
return mapValues(providers, (item) => sort(Object.values(item.models))[0].id)
|
|
return mapValues(providers, (item) => sort(Object.values(item.models))[0].id)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+export class ModelNotFoundError extends Schema.TaggedErrorClass<ModelNotFoundError>()("ProviderModelNotFoundError", {
|
|
|
|
|
+ providerID: ProviderID,
|
|
|
|
|
+ modelID: ModelID,
|
|
|
|
|
+ suggestions: Schema.optional(Schema.Array(Schema.String)),
|
|
|
|
|
+ cause: Schema.optional(Schema.Defect),
|
|
|
|
|
+}) {
|
|
|
|
|
+ static isInstance(input: unknown): input is ModelNotFoundError {
|
|
|
|
|
+ return input instanceof ModelNotFoundError
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export type Error = ModelNotFoundError
|
|
|
|
|
+
|
|
|
export interface Interface {
|
|
export interface Interface {
|
|
|
readonly list: () => Effect.Effect<Record<ProviderID, Info>>
|
|
readonly list: () => Effect.Effect<Record<ProviderID, Info>>
|
|
|
readonly getProvider: (providerID: ProviderID) => Effect.Effect<Info>
|
|
readonly getProvider: (providerID: ProviderID) => Effect.Effect<Info>
|
|
|
- readonly getModel: (providerID: ProviderID, modelID: ModelID) => Effect.Effect<Model>
|
|
|
|
|
- readonly getLanguage: (model: Model) => Effect.Effect<LanguageModelV3>
|
|
|
|
|
|
|
+ readonly getModel: (providerID: ProviderID, modelID: ModelID) => Effect.Effect<Model, ModelNotFoundError>
|
|
|
|
|
+ readonly getLanguage: (model: Model) => Effect.Effect<LanguageModelV3, ModelNotFoundError>
|
|
|
readonly closest: (
|
|
readonly closest: (
|
|
|
providerID: ProviderID,
|
|
providerID: ProviderID,
|
|
|
query: string[],
|
|
query: string[],
|
|
@@ -1638,14 +1652,14 @@ const layer = Layer.effect(
|
|
|
: fuzzysort
|
|
: fuzzysort
|
|
|
.go(providerID, Object.keys({ ...s.catalog, ...s.providers }), { limit: 3, threshold: -10000 })
|
|
.go(providerID, Object.keys({ ...s.catalog, ...s.providers }), { limit: 3, threshold: -10000 })
|
|
|
.map((m) => m.target)
|
|
.map((m) => m.target)
|
|
|
- throw new ModelNotFoundError({ providerID, modelID, suggestions })
|
|
|
|
|
|
|
+ return yield* new ModelNotFoundError({ providerID, modelID, suggestions })
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const info = provider.models[modelID]
|
|
const info = provider.models[modelID]
|
|
|
if (!info) {
|
|
if (!info) {
|
|
|
const current = modelSuggestions(provider, modelID)
|
|
const current = modelSuggestions(provider, modelID)
|
|
|
const suggestions = current.length ? current : modelSuggestions(s.catalog[providerID], modelID)
|
|
const suggestions = current.length ? current : modelSuggestions(s.catalog[providerID], modelID)
|
|
|
- throw new ModelNotFoundError({ providerID, modelID, suggestions })
|
|
|
|
|
|
|
+ return yield* new ModelNotFoundError({ providerID, modelID, suggestions })
|
|
|
}
|
|
}
|
|
|
return info
|
|
return info
|
|
|
})
|
|
})
|
|
@@ -1656,11 +1670,10 @@ const layer = Layer.effect(
|
|
|
const key = `${model.providerID}/${model.id}`
|
|
const key = `${model.providerID}/${model.id}`
|
|
|
if (s.models.has(key)) return s.models.get(key)!
|
|
if (s.models.has(key)) return s.models.get(key)!
|
|
|
|
|
|
|
|
- return yield* Effect.promise(async () => {
|
|
|
|
|
- const provider = s.providers[model.providerID]
|
|
|
|
|
- const sdk = await resolveSDK(model, s, envs)
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
|
|
+ const provider = s.providers[model.providerID]
|
|
|
|
|
+ return yield* EffectPromise.refineRejection(
|
|
|
|
|
+ async () => {
|
|
|
|
|
+ const sdk = await resolveSDK(model, s, envs)
|
|
|
const language = s.modelLoaders[model.providerID]
|
|
const language = s.modelLoaders[model.providerID]
|
|
|
? await s.modelLoaders[model.providerID](sdk, model.api.id, {
|
|
? await s.modelLoaders[model.providerID](sdk, model.api.id, {
|
|
|
...provider.options,
|
|
...provider.options,
|
|
@@ -1669,18 +1682,12 @@ const layer = Layer.effect(
|
|
|
: sdk.languageModel(model.api.id)
|
|
: sdk.languageModel(model.api.id)
|
|
|
s.models.set(key, language)
|
|
s.models.set(key, language)
|
|
|
return language
|
|
return language
|
|
|
- } catch (e) {
|
|
|
|
|
- if (e instanceof NoSuchModelError)
|
|
|
|
|
- throw new ModelNotFoundError(
|
|
|
|
|
- {
|
|
|
|
|
- modelID: model.id,
|
|
|
|
|
- providerID: model.providerID,
|
|
|
|
|
- },
|
|
|
|
|
- { cause: e },
|
|
|
|
|
- )
|
|
|
|
|
- throw e
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ (cause) =>
|
|
|
|
|
+ cause instanceof NoSuchModelError
|
|
|
|
|
+ ? new ModelNotFoundError({ modelID: model.id, providerID: model.providerID, cause })
|
|
|
|
|
+ : undefined,
|
|
|
|
|
+ )
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const closest = Effect.fn("Provider.closest")(function* (providerID: ProviderID, query: string[]) {
|
|
const closest = Effect.fn("Provider.closest")(function* (providerID: ProviderID, query: string[]) {
|
|
@@ -1700,7 +1707,7 @@ const layer = Layer.effect(
|
|
|
|
|
|
|
|
if (cfg.small_model) {
|
|
if (cfg.small_model) {
|
|
|
const parsed = parseModel(cfg.small_model)
|
|
const parsed = parseModel(cfg.small_model)
|
|
|
- return yield* getModel(parsed.providerID, parsed.modelID)
|
|
|
|
|
|
|
+ return yield* getModel(parsed.providerID, parsed.modelID).pipe(Effect.orDie)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const s = yield* InstanceState.get(state)
|
|
const s = yield* InstanceState.get(state)
|
|
@@ -1728,22 +1735,22 @@ const layer = Layer.effect(
|
|
|
const candidates = Object.keys(provider.models).filter((m) => m.includes(item))
|
|
const candidates = Object.keys(provider.models).filter((m) => m.includes(item))
|
|
|
|
|
|
|
|
const globalMatch = candidates.find((m) => m.startsWith("global."))
|
|
const globalMatch = candidates.find((m) => m.startsWith("global."))
|
|
|
- if (globalMatch) return yield* getModel(providerID, ModelID.make(globalMatch))
|
|
|
|
|
|
|
+ if (globalMatch) return yield* getModel(providerID, ModelID.make(globalMatch)).pipe(Effect.orDie)
|
|
|
|
|
|
|
|
const region = provider.options?.region
|
|
const region = provider.options?.region
|
|
|
if (region) {
|
|
if (region) {
|
|
|
const regionPrefix = region.split("-")[0]
|
|
const regionPrefix = region.split("-")[0]
|
|
|
if (regionPrefix === "us" || regionPrefix === "eu") {
|
|
if (regionPrefix === "us" || regionPrefix === "eu") {
|
|
|
const regionalMatch = candidates.find((m) => m.startsWith(`${regionPrefix}.`))
|
|
const regionalMatch = candidates.find((m) => m.startsWith(`${regionPrefix}.`))
|
|
|
- if (regionalMatch) return yield* getModel(providerID, ModelID.make(regionalMatch))
|
|
|
|
|
|
|
+ if (regionalMatch) return yield* getModel(providerID, ModelID.make(regionalMatch)).pipe(Effect.orDie)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const unprefixed = candidates.find((m) => !crossRegionPrefixes.some((p) => m.startsWith(p)))
|
|
const unprefixed = candidates.find((m) => !crossRegionPrefixes.some((p) => m.startsWith(p)))
|
|
|
- if (unprefixed) return yield* getModel(providerID, ModelID.make(unprefixed))
|
|
|
|
|
|
|
+ if (unprefixed) return yield* getModel(providerID, ModelID.make(unprefixed)).pipe(Effect.orDie)
|
|
|
} else {
|
|
} else {
|
|
|
for (const model of Object.keys(provider.models)) {
|
|
for (const model of Object.keys(provider.models)) {
|
|
|
- if (model.includes(item)) return yield* getModel(providerID, ModelID.make(model))
|
|
|
|
|
|
|
+ if (model.includes(item)) return yield* getModel(providerID, ModelID.make(model)).pipe(Effect.orDie)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -1818,12 +1825,6 @@ export function parseModel(model: string) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-export const ModelNotFoundError = NamedError.create("ProviderModelNotFoundError", {
|
|
|
|
|
- providerID: ProviderID,
|
|
|
|
|
- modelID: ModelID,
|
|
|
|
|
- suggestions: Schema.optional(Schema.Array(Schema.String)),
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
export const InitError = NamedError.create("ProviderInitError", {
|
|
export const InitError = NamedError.create("ProviderInitError", {
|
|
|
providerID: ProviderID,
|
|
providerID: ProviderID,
|
|
|
})
|
|
})
|