provider.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. import z from "zod"
  2. import path from "path"
  3. import { Config } from "../config/config"
  4. import { mergeDeep, sortBy } from "remeda"
  5. import { NoSuchModelError, type LanguageModel, type Provider as SDK } from "ai"
  6. import { Log } from "../util/log"
  7. import { BunProc } from "../bun"
  8. import { Plugin } from "../plugin"
  9. import { ModelsDev } from "./models"
  10. import { NamedError } from "../util/error"
  11. import { Auth } from "../auth"
  12. import { Instance } from "../project/instance"
  13. import { Global } from "../global"
  14. import { Flag } from "../flag/flag"
  15. import { iife } from "@/util/iife"
  16. export namespace Provider {
  17. const log = Log.create({ service: "provider" })
  18. type CustomLoader = (provider: ModelsDev.Provider) => Promise<{
  19. autoload: boolean
  20. getModel?: (sdk: any, modelID: string, options?: Record<string, any>) => Promise<any>
  21. options?: Record<string, any>
  22. }>
  23. type Source = "env" | "config" | "custom" | "api"
  24. const CUSTOM_LOADERS: Record<string, CustomLoader> = {
  25. async anthropic() {
  26. return {
  27. autoload: false,
  28. options: {
  29. headers: {
  30. "anthropic-beta":
  31. "claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
  32. },
  33. },
  34. }
  35. },
  36. async opencode(input) {
  37. const hasKey = await (async () => {
  38. if (input.env.some((item) => process.env[item])) return true
  39. if (await Auth.get(input.id)) return true
  40. return false
  41. })()
  42. if (!hasKey) {
  43. for (const [key, value] of Object.entries(input.models)) {
  44. if (value.cost.input === 0) continue
  45. delete input.models[key]
  46. }
  47. }
  48. return {
  49. autoload: Object.keys(input.models).length > 0,
  50. options: hasKey ? {} : { apiKey: "public" },
  51. }
  52. },
  53. openai: async () => {
  54. return {
  55. autoload: false,
  56. async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
  57. return sdk.responses(modelID)
  58. },
  59. options: {},
  60. }
  61. },
  62. azure: async () => {
  63. return {
  64. autoload: false,
  65. async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
  66. if (options?.["useCompletionUrls"]) {
  67. return sdk.chat(modelID)
  68. } else {
  69. return sdk.responses(modelID)
  70. }
  71. },
  72. options: {},
  73. }
  74. },
  75. "azure-cognitive-services": async () => {
  76. const resourceName = process.env["AZURE_COGNITIVE_SERVICES_RESOURCE_NAME"]
  77. return {
  78. autoload: false,
  79. async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
  80. if (options?.["useCompletionUrls"]) {
  81. return sdk.chat(modelID)
  82. } else {
  83. return sdk.responses(modelID)
  84. }
  85. },
  86. options: {
  87. baseURL: resourceName ? `https://${resourceName}.cognitiveservices.azure.com/openai` : undefined,
  88. },
  89. }
  90. },
  91. "amazon-bedrock": async () => {
  92. if (!process.env["AWS_PROFILE"] && !process.env["AWS_ACCESS_KEY_ID"] && !process.env["AWS_BEARER_TOKEN_BEDROCK"])
  93. return { autoload: false }
  94. const region = process.env["AWS_REGION"] ?? "us-east-1"
  95. const { fromNodeProviderChain } = await import(await BunProc.install("@aws-sdk/credential-providers"))
  96. return {
  97. autoload: true,
  98. options: {
  99. region,
  100. credentialProvider: fromNodeProviderChain(),
  101. },
  102. async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
  103. let regionPrefix = region.split("-")[0]
  104. switch (regionPrefix) {
  105. case "us": {
  106. const modelRequiresPrefix = [
  107. "nova-micro",
  108. "nova-lite",
  109. "nova-pro",
  110. "nova-premier",
  111. "claude",
  112. "deepseek",
  113. ].some((m) => modelID.includes(m))
  114. const isGovCloud = region.startsWith("us-gov")
  115. if (modelRequiresPrefix && !isGovCloud) {
  116. modelID = `${regionPrefix}.${modelID}`
  117. }
  118. break
  119. }
  120. case "eu": {
  121. const regionRequiresPrefix = [
  122. "eu-west-1",
  123. "eu-west-2",
  124. "eu-west-3",
  125. "eu-north-1",
  126. "eu-central-1",
  127. "eu-south-1",
  128. "eu-south-2",
  129. ].some((r) => region.includes(r))
  130. const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "llama3", "pixtral"].some((m) =>
  131. modelID.includes(m),
  132. )
  133. if (regionRequiresPrefix && modelRequiresPrefix) {
  134. modelID = `${regionPrefix}.${modelID}`
  135. }
  136. break
  137. }
  138. case "ap": {
  139. const isAustraliaRegion = ["ap-southeast-2", "ap-southeast-4"].includes(region)
  140. if (
  141. isAustraliaRegion &&
  142. ["anthropic.claude-sonnet-4-5", "anthropic.claude-haiku"].some((m) => modelID.includes(m))
  143. ) {
  144. regionPrefix = "au"
  145. modelID = `${regionPrefix}.${modelID}`
  146. } else {
  147. const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "nova-pro"].some((m) =>
  148. modelID.includes(m),
  149. )
  150. if (modelRequiresPrefix) {
  151. regionPrefix = "apac"
  152. modelID = `${regionPrefix}.${modelID}`
  153. }
  154. }
  155. break
  156. }
  157. }
  158. return sdk.languageModel(modelID)
  159. },
  160. }
  161. },
  162. openrouter: async () => {
  163. return {
  164. autoload: false,
  165. options: {
  166. headers: {
  167. "HTTP-Referer": "https://opencode.ai/",
  168. "X-Title": "opencode",
  169. },
  170. },
  171. }
  172. },
  173. vercel: async () => {
  174. return {
  175. autoload: false,
  176. options: {
  177. headers: {
  178. "http-referer": "https://opencode.ai/",
  179. "x-title": "opencode",
  180. },
  181. },
  182. }
  183. },
  184. "google-vertex": async () => {
  185. const project = process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCP_PROJECT"] ?? process.env["GCLOUD_PROJECT"]
  186. const location = process.env["GOOGLE_CLOUD_LOCATION"] ?? process.env["VERTEX_LOCATION"] ?? "us-east5"
  187. const autoload = Boolean(project)
  188. if (!autoload) return { autoload: false }
  189. return {
  190. autoload: true,
  191. options: {
  192. project,
  193. location,
  194. },
  195. async getModel(sdk: any, modelID: string) {
  196. const id = String(modelID).trim()
  197. return sdk.languageModel(id)
  198. },
  199. }
  200. },
  201. "google-vertex-anthropic": async () => {
  202. const project = process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCP_PROJECT"] ?? process.env["GCLOUD_PROJECT"]
  203. const location = process.env["GOOGLE_CLOUD_LOCATION"] ?? process.env["VERTEX_LOCATION"] ?? "global"
  204. const autoload = Boolean(project)
  205. if (!autoload) return { autoload: false }
  206. return {
  207. autoload: true,
  208. options: {
  209. project,
  210. location,
  211. },
  212. async getModel(sdk: any, modelID: string) {
  213. const id = String(modelID).trim()
  214. return sdk.languageModel(id)
  215. },
  216. }
  217. },
  218. zenmux: async () => {
  219. return {
  220. autoload: false,
  221. options: {
  222. headers: {
  223. "HTTP-Referer": "https://opencode.ai/",
  224. "X-Title": "opencode",
  225. },
  226. },
  227. }
  228. },
  229. }
  230. const state = Instance.state(async () => {
  231. using _ = log.time("state")
  232. const config = await Config.get()
  233. const database = await ModelsDev.get()
  234. const providers: {
  235. [providerID: string]: {
  236. source: Source
  237. info: ModelsDev.Provider
  238. getModel?: (sdk: any, modelID: string, options?: Record<string, any>) => Promise<any>
  239. options: Record<string, any>
  240. }
  241. } = {}
  242. const models = new Map<
  243. string,
  244. {
  245. providerID: string
  246. modelID: string
  247. info: ModelsDev.Model
  248. language: LanguageModel
  249. npm?: string
  250. }
  251. >()
  252. const sdk = new Map<number, SDK>()
  253. // Maps `${provider}/${key}` to the provider’s actual model ID for custom aliases.
  254. const realIdByKey = new Map<string, string>()
  255. log.info("init")
  256. function mergeProvider(
  257. id: string,
  258. options: Record<string, any>,
  259. source: Source,
  260. getModel?: (sdk: any, modelID: string, options?: Record<string, any>) => Promise<any>,
  261. ) {
  262. const provider = providers[id]
  263. if (!provider) {
  264. const info = database[id]
  265. if (!info) return
  266. if (info.api && !options["baseURL"]) options["baseURL"] = info.api
  267. providers[id] = {
  268. source,
  269. info,
  270. options,
  271. getModel,
  272. }
  273. return
  274. }
  275. provider.options = mergeDeep(provider.options, options)
  276. provider.source = source
  277. provider.getModel = getModel ?? provider.getModel
  278. }
  279. const configProviders = Object.entries(config.provider ?? {})
  280. // Add GitHub Copilot Enterprise provider that inherits from GitHub Copilot
  281. if (database["github-copilot"]) {
  282. const githubCopilot = database["github-copilot"]
  283. database["github-copilot-enterprise"] = {
  284. ...githubCopilot,
  285. id: "github-copilot-enterprise",
  286. name: "GitHub Copilot Enterprise",
  287. // Enterprise uses a different API endpoint - will be set dynamically based on auth
  288. api: undefined,
  289. }
  290. }
  291. for (const [providerID, provider] of configProviders) {
  292. const existing = database[providerID]
  293. const parsed: ModelsDev.Provider = {
  294. id: providerID,
  295. npm: provider.npm ?? existing?.npm,
  296. name: provider.name ?? existing?.name ?? providerID,
  297. env: provider.env ?? existing?.env ?? [],
  298. api: provider.api ?? existing?.api,
  299. models: existing?.models ?? {},
  300. }
  301. for (const [modelID, model] of Object.entries(provider.models ?? {})) {
  302. const existing = parsed.models[model.id ?? modelID]
  303. const name = iife(() => {
  304. if (model.name) return model.name
  305. if (model.id && model.id !== modelID) return modelID
  306. return existing?.name ?? modelID
  307. })
  308. const parsedModel: ModelsDev.Model = {
  309. id: modelID,
  310. name,
  311. release_date: model.release_date ?? existing?.release_date,
  312. attachment: model.attachment ?? existing?.attachment ?? false,
  313. reasoning: model.reasoning ?? existing?.reasoning ?? false,
  314. temperature: model.temperature ?? existing?.temperature ?? false,
  315. tool_call: model.tool_call ?? existing?.tool_call ?? true,
  316. cost:
  317. !model.cost && !existing?.cost
  318. ? {
  319. input: 0,
  320. output: 0,
  321. cache_read: 0,
  322. cache_write: 0,
  323. }
  324. : {
  325. cache_read: 0,
  326. cache_write: 0,
  327. ...existing?.cost,
  328. ...model.cost,
  329. },
  330. options: {
  331. ...existing?.options,
  332. ...model.options,
  333. },
  334. limit: model.limit ??
  335. existing?.limit ?? {
  336. context: 0,
  337. output: 0,
  338. },
  339. modalities: model.modalities ??
  340. existing?.modalities ?? {
  341. input: ["text"],
  342. output: ["text"],
  343. },
  344. headers: model.headers,
  345. provider: model.provider ?? existing?.provider,
  346. }
  347. if (model.id && model.id !== modelID) {
  348. realIdByKey.set(`${providerID}/${modelID}`, model.id)
  349. }
  350. parsed.models[modelID] = parsedModel
  351. }
  352. database[providerID] = parsed
  353. }
  354. const disabled = await Config.get().then((cfg) => new Set(cfg.disabled_providers ?? []))
  355. // load env
  356. for (const [providerID, provider] of Object.entries(database)) {
  357. if (disabled.has(providerID)) continue
  358. const apiKey = provider.env.map((item) => process.env[item]).at(0)
  359. if (!apiKey) continue
  360. mergeProvider(
  361. providerID,
  362. // only include apiKey if there's only one potential option
  363. provider.env.length === 1 ? { apiKey } : {},
  364. "env",
  365. )
  366. }
  367. // load apikeys
  368. for (const [providerID, provider] of Object.entries(await Auth.all())) {
  369. if (disabled.has(providerID)) continue
  370. if (provider.type === "api") {
  371. mergeProvider(providerID, { apiKey: provider.key }, "api")
  372. }
  373. }
  374. // load custom
  375. for (const [providerID, fn] of Object.entries(CUSTOM_LOADERS)) {
  376. if (disabled.has(providerID)) continue
  377. const result = await fn(database[providerID])
  378. if (result && (result.autoload || providers[providerID])) {
  379. mergeProvider(providerID, result.options ?? {}, "custom", result.getModel)
  380. }
  381. }
  382. for (const plugin of await Plugin.list()) {
  383. if (!plugin.auth) continue
  384. const providerID = plugin.auth.provider
  385. if (disabled.has(providerID)) continue
  386. // For github-copilot plugin, check if auth exists for either github-copilot or github-copilot-enterprise
  387. let hasAuth = false
  388. const auth = await Auth.get(providerID)
  389. if (auth) hasAuth = true
  390. // Special handling for github-copilot: also check for enterprise auth
  391. if (providerID === "github-copilot" && !hasAuth) {
  392. const enterpriseAuth = await Auth.get("github-copilot-enterprise")
  393. if (enterpriseAuth) hasAuth = true
  394. }
  395. if (!hasAuth) continue
  396. if (!plugin.auth.loader) continue
  397. // Load for the main provider if auth exists
  398. if (auth) {
  399. const options = await plugin.auth.loader(() => Auth.get(providerID) as any, database[plugin.auth.provider])
  400. mergeProvider(plugin.auth.provider, options ?? {}, "custom")
  401. }
  402. // If this is github-copilot plugin, also register for github-copilot-enterprise if auth exists
  403. if (providerID === "github-copilot") {
  404. const enterpriseProviderID = "github-copilot-enterprise"
  405. if (!disabled.has(enterpriseProviderID)) {
  406. const enterpriseAuth = await Auth.get(enterpriseProviderID)
  407. if (enterpriseAuth) {
  408. const enterpriseOptions = await plugin.auth.loader(
  409. () => Auth.get(enterpriseProviderID) as any,
  410. database[enterpriseProviderID],
  411. )
  412. mergeProvider(enterpriseProviderID, enterpriseOptions ?? {}, "custom")
  413. }
  414. }
  415. }
  416. }
  417. // load config
  418. for (const [providerID, provider] of configProviders) {
  419. mergeProvider(providerID, provider.options ?? {}, "config")
  420. }
  421. for (const [providerID, provider] of Object.entries(providers)) {
  422. const filteredModels = Object.fromEntries(
  423. Object.entries(provider.info.models)
  424. // Filter out blacklisted models
  425. .filter(
  426. ([modelID]) =>
  427. modelID !== "gpt-5-chat-latest" && !(providerID === "openrouter" && modelID === "openai/gpt-5-chat"),
  428. )
  429. // Filter out experimental models
  430. .filter(
  431. ([, model]) =>
  432. ((!model.experimental && model.status !== "alpha") || Flag.OPENCODE_ENABLE_EXPERIMENTAL_MODELS) &&
  433. model.status !== "deprecated",
  434. ),
  435. )
  436. provider.info.models = filteredModels
  437. if (Object.keys(provider.info.models).length === 0) {
  438. delete providers[providerID]
  439. continue
  440. }
  441. log.info("found", { providerID })
  442. }
  443. return {
  444. models,
  445. providers,
  446. sdk,
  447. realIdByKey,
  448. }
  449. })
  450. export async function list() {
  451. return state().then((state) => state.providers)
  452. }
  453. async function getSDK(provider: ModelsDev.Provider, model: ModelsDev.Model) {
  454. return (async () => {
  455. using _ = log.time("getSDK", {
  456. providerID: provider.id,
  457. })
  458. const s = await state()
  459. const pkg = model.provider?.npm ?? provider.npm ?? provider.id
  460. const options = { ...s.providers[provider.id]?.options }
  461. if (pkg.includes("@ai-sdk/openai-compatible") && options["includeUsage"] === undefined) {
  462. options["includeUsage"] = true
  463. }
  464. const key = Bun.hash.xxHash32(JSON.stringify({ pkg, options }))
  465. const existing = s.sdk.get(key)
  466. if (existing) return existing
  467. let installedPath: string
  468. if (!pkg.startsWith("file://")) {
  469. installedPath = await BunProc.install(pkg, "latest")
  470. } else {
  471. log.info("loading local provider", { pkg })
  472. installedPath = pkg
  473. }
  474. // The `google-vertex-anthropic` provider points to the `@ai-sdk/google-vertex` package.
  475. // Ref: https://github.com/sst/models.dev/blob/0a87de42ab177bebad0620a889e2eb2b4a5dd4ab/providers/google-vertex-anthropic/provider.toml
  476. // However, the actual export is at the subpath `@ai-sdk/google-vertex/anthropic`.
  477. // Ref: https://ai-sdk.dev/providers/ai-sdk-providers/google-vertex#google-vertex-anthropic-provider-usage
  478. // In addition, Bun's dynamic import logic does not support subpath imports,
  479. // so we patch the import path to load directly from `dist`.
  480. const modPath =
  481. provider.id === "google-vertex-anthropic" ? `${installedPath}/dist/anthropic/index.mjs` : installedPath
  482. const mod = await import(modPath)
  483. if (options["timeout"] !== undefined && options["timeout"] !== null) {
  484. // Preserve custom fetch if it exists, wrap it with timeout logic
  485. const customFetch = options["fetch"]
  486. options["fetch"] = async (input: any, init?: BunFetchRequestInit) => {
  487. const { signal, ...rest } = init ?? {}
  488. const signals: AbortSignal[] = []
  489. if (signal) signals.push(signal)
  490. if (options["timeout"] !== false) signals.push(AbortSignal.timeout(options["timeout"]))
  491. const combined = signals.length > 1 ? AbortSignal.any(signals) : signals[0]
  492. const fetchFn = customFetch ?? fetch
  493. return fetchFn(input, {
  494. ...rest,
  495. signal: combined,
  496. // @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682
  497. timeout: false,
  498. })
  499. }
  500. }
  501. const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
  502. const loaded = fn({
  503. name: provider.id,
  504. ...options,
  505. })
  506. s.sdk.set(key, loaded)
  507. return loaded as SDK
  508. })().catch((e) => {
  509. throw new InitError({ providerID: provider.id }, { cause: e })
  510. })
  511. }
  512. export async function getProvider(providerID: string) {
  513. return state().then((s) => s.providers[providerID])
  514. }
  515. export async function getModel(providerID: string, modelID: string) {
  516. const key = `${providerID}/${modelID}`
  517. const s = await state()
  518. if (s.models.has(key)) return s.models.get(key)!
  519. log.info("getModel", {
  520. providerID,
  521. modelID,
  522. })
  523. const provider = s.providers[providerID]
  524. if (!provider) throw new ModelNotFoundError({ providerID, modelID })
  525. const info = provider.info.models[modelID]
  526. if (!info) throw new ModelNotFoundError({ providerID, modelID })
  527. const sdk = await getSDK(provider.info, info)
  528. try {
  529. const keyReal = `${providerID}/${modelID}`
  530. const realID = s.realIdByKey.get(keyReal) ?? info.id
  531. const language = provider.getModel
  532. ? await provider.getModel(sdk, realID, provider.options)
  533. : sdk.languageModel(realID)
  534. log.info("found", { providerID, modelID })
  535. s.models.set(key, {
  536. providerID,
  537. modelID,
  538. info,
  539. language,
  540. npm: info.provider?.npm ?? provider.info.npm,
  541. })
  542. return {
  543. modelID,
  544. providerID,
  545. info,
  546. language,
  547. npm: info.provider?.npm ?? provider.info.npm,
  548. }
  549. } catch (e) {
  550. if (e instanceof NoSuchModelError)
  551. throw new ModelNotFoundError(
  552. {
  553. modelID: modelID,
  554. providerID,
  555. },
  556. { cause: e },
  557. )
  558. throw e
  559. }
  560. }
  561. export async function getSmallModel(providerID: string) {
  562. const cfg = await Config.get()
  563. if (cfg.small_model) {
  564. const parsed = parseModel(cfg.small_model)
  565. return getModel(parsed.providerID, parsed.modelID)
  566. }
  567. const provider = await state().then((state) => state.providers[providerID])
  568. if (!provider) return
  569. let priority = ["claude-haiku-4-5", "claude-haiku-4.5", "3-5-haiku", "3.5-haiku", "gemini-2.5-flash", "gpt-5-nano"]
  570. // claude-haiku-4.5 is considered a premium model in github copilot, we shouldn't use premium requests for title gen
  571. if (providerID === "github-copilot") {
  572. priority = priority.filter((m) => m !== "claude-haiku-4.5")
  573. }
  574. if (providerID === "opencode" || providerID === "local") {
  575. priority = ["gpt-5-nano"]
  576. }
  577. for (const item of priority) {
  578. for (const model of Object.keys(provider.info.models)) {
  579. if (model.includes(item)) return getModel(providerID, model)
  580. }
  581. }
  582. }
  583. const priority = ["gpt-5", "claude-sonnet-4", "big-pickle", "gemini-3-pro"]
  584. export function sort(models: ModelsDev.Model[]) {
  585. return sortBy(
  586. models,
  587. [(model) => priority.findIndex((filter) => model.id.includes(filter)), "desc"],
  588. [(model) => (model.id.includes("latest") ? 0 : 1), "asc"],
  589. [(model) => model.id, "desc"],
  590. )
  591. }
  592. export async function defaultModel() {
  593. const cfg = await Config.get()
  594. if (cfg.model) return parseModel(cfg.model)
  595. const provider = await list()
  596. .then((val) => Object.values(val))
  597. .then((x) => x.find((p) => !cfg.provider || Object.keys(cfg.provider).includes(p.info.id)))
  598. if (!provider) throw new Error("no providers found")
  599. const [model] = sort(Object.values(provider.info.models))
  600. if (!model) throw new Error("no models found")
  601. return {
  602. providerID: provider.info.id,
  603. modelID: model.id,
  604. }
  605. }
  606. export function parseModel(model: string) {
  607. const [providerID, ...rest] = model.split("/")
  608. return {
  609. providerID: providerID,
  610. modelID: rest.join("/"),
  611. }
  612. }
  613. export const ModelNotFoundError = NamedError.create(
  614. "ProviderModelNotFoundError",
  615. z.object({
  616. providerID: z.string(),
  617. modelID: z.string(),
  618. }),
  619. )
  620. export const InitError = NamedError.create(
  621. "ProviderInitError",
  622. z.object({
  623. providerID: z.string(),
  624. }),
  625. )
  626. }