feat(core): route providers through native AI (#39615)
This commit is contained in:
parent
0ffec67ca3
commit
7625cbdf47
6 changed files with 148 additions and 32 deletions
|
|
@ -1,38 +1,62 @@
|
||||||
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
|
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
|
||||||
import type { RouteDefaultsInput } from "../route/client"
|
import { Route, type RouteDefaultsInput } from "../route/client"
|
||||||
import { HttpOptions, ProviderID, type ModelID } from "../schema"
|
import { Endpoint } from "../route/endpoint"
|
||||||
|
import { HttpOptions, ProviderID, type ModelID, type ProviderOptions } from "../schema"
|
||||||
import * as OpenAICompatibleProfiles from "./openai-compatible-profile"
|
import * as OpenAICompatibleProfiles from "./openai-compatible-profile"
|
||||||
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat"
|
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat"
|
||||||
|
import * as OpenAIChat from "../protocols/openai-chat"
|
||||||
import * as OpenAIResponses from "../protocols/openai-responses"
|
import * as OpenAIResponses from "../protocols/openai-responses"
|
||||||
import { XAIImages } from "../protocols/xai-images"
|
import { XAIImages } from "../protocols/xai-images"
|
||||||
import type { OpenAIProviderOptionsInput } from "./openai-options"
|
import type { OpenAIOptionsInput } from "./openai-options"
|
||||||
import type { ProviderPackage } from "../provider-package"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
|
|
||||||
export const id = ProviderID.make("xai")
|
export const id = ProviderID.make("xai")
|
||||||
|
|
||||||
|
export type XAIProviderOptionsInput = ProviderOptions & {
|
||||||
|
readonly xai?: OpenAIOptionsInput
|
||||||
|
}
|
||||||
|
|
||||||
export type ModelOptions = Omit<RouteDefaultsInput, "providerOptions"> &
|
export type ModelOptions = Omit<RouteDefaultsInput, "providerOptions"> &
|
||||||
ProviderAuthOption<"optional"> & {
|
ProviderAuthOption<"optional"> & {
|
||||||
readonly baseURL?: string
|
readonly baseURL?: string
|
||||||
readonly providerOptions?: OpenAIProviderOptionsInput
|
readonly providerOptions?: XAIProviderOptionsInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Settings extends ProviderPackage.Settings {
|
export interface Settings extends ProviderPackage.Settings {
|
||||||
readonly apiKey?: string
|
readonly apiKey?: string
|
||||||
readonly baseURL?: string
|
readonly baseURL?: string
|
||||||
readonly providerOptions?: OpenAIProviderOptionsInput
|
readonly providerOptions?: XAIProviderOptionsInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { XAIImageOptions } from "../protocols/xai-images"
|
export type { XAIImageOptions } from "../protocols/xai-images"
|
||||||
|
|
||||||
export const routes = [OpenAIResponses.route, OpenAICompatibleChat.route]
|
const responsesRoute = Route.make({
|
||||||
|
id: "openai-responses",
|
||||||
|
provider: id,
|
||||||
|
providerMetadataKey: "xai",
|
||||||
|
protocol: OpenAIResponses.protocol,
|
||||||
|
endpoint: Endpoint.path("/responses", { baseURL: OpenAICompatibleProfiles.profiles.xai.baseURL }),
|
||||||
|
transport: OpenAIResponses.httpTransport,
|
||||||
|
defaults: { providerOptions: { xai: { store: false } } },
|
||||||
|
})
|
||||||
|
|
||||||
|
const chatRoute = Route.make({
|
||||||
|
id: "openai-compatible-chat",
|
||||||
|
provider: id,
|
||||||
|
providerMetadataKey: "xai",
|
||||||
|
protocol: OpenAIChat.protocol,
|
||||||
|
endpoint: Endpoint.path("/chat/completions", { baseURL: OpenAICompatibleProfiles.profiles.xai.baseURL }),
|
||||||
|
transport: OpenAICompatibleChat.route.transport,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const routes = [responsesRoute, chatRoute]
|
||||||
|
|
||||||
const auth = (options: ProviderAuthOption<"optional">) => AuthOptions.bearer(options, "XAI_API_KEY")
|
const auth = (options: ProviderAuthOption<"optional">) => AuthOptions.bearer(options, "XAI_API_KEY")
|
||||||
|
|
||||||
const configuredResponsesRoute = (input: ModelOptions) => {
|
const configuredResponsesRoute = (input: ModelOptions) => {
|
||||||
const { apiKey: _, auth: _auth, baseURL, ...rest } = input
|
const { apiKey: _, auth: _auth, baseURL, ...rest } = input
|
||||||
return OpenAIResponses.route.with({
|
return responsesRoute.with({
|
||||||
...rest,
|
...rest,
|
||||||
provider: id,
|
|
||||||
endpoint: { baseURL: baseURL ?? OpenAICompatibleProfiles.profiles.xai.baseURL },
|
endpoint: { baseURL: baseURL ?? OpenAICompatibleProfiles.profiles.xai.baseURL },
|
||||||
auth: auth(input),
|
auth: auth(input),
|
||||||
})
|
})
|
||||||
|
|
@ -40,9 +64,8 @@ const configuredResponsesRoute = (input: ModelOptions) => {
|
||||||
|
|
||||||
const configuredChatRoute = (input: ModelOptions) => {
|
const configuredChatRoute = (input: ModelOptions) => {
|
||||||
const { apiKey: _, auth: _auth, baseURL, ...rest } = input
|
const { apiKey: _, auth: _auth, baseURL, ...rest } = input
|
||||||
return OpenAICompatibleChat.route.with({
|
return chatRoute.with({
|
||||||
...rest,
|
...rest,
|
||||||
provider: id,
|
|
||||||
endpoint: { baseURL: baseURL ?? OpenAICompatibleProfiles.profiles.xai.baseURL },
|
endpoint: { baseURL: baseURL ?? OpenAICompatibleProfiles.profiles.xai.baseURL },
|
||||||
auth: auth(input),
|
auth: auth(input),
|
||||||
})
|
})
|
||||||
|
|
@ -51,8 +74,8 @@ const configuredChatRoute = (input: ModelOptions) => {
|
||||||
export const configure = (input: ModelOptions = {}) => {
|
export const configure = (input: ModelOptions = {}) => {
|
||||||
const responsesRoute = configuredResponsesRoute(input)
|
const responsesRoute = configuredResponsesRoute(input)
|
||||||
const chatRoute = configuredChatRoute(input)
|
const chatRoute = configuredChatRoute(input)
|
||||||
const responses = (modelID: string | ModelID) => responsesRoute.model<OpenAIProviderOptionsInput>({ id: modelID })
|
const responses = (modelID: string | ModelID) => responsesRoute.model<XAIProviderOptionsInput>({ id: modelID })
|
||||||
const chat = (modelID: string | ModelID) => chatRoute.model<OpenAIProviderOptionsInput>({ id: modelID })
|
const chat = (modelID: string | ModelID) => chatRoute.model<XAIProviderOptionsInput>({ id: modelID })
|
||||||
const image = (modelID: string | ModelID) =>
|
const image = (modelID: string | ModelID) =>
|
||||||
XAIImages.model({
|
XAIImages.model({
|
||||||
id: modelID,
|
id: modelID,
|
||||||
|
|
@ -72,7 +95,7 @@ export const configure = (input: ModelOptions = {}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const provider = configure()
|
export const provider = configure()
|
||||||
export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["model"] = (modelID, settings) =>
|
export const model: ProviderPackage.Definition<Settings, XAIProviderOptionsInput>["model"] = (modelID, settings) =>
|
||||||
configure({
|
configure({
|
||||||
apiKey: settings.apiKey,
|
apiKey: settings.apiKey,
|
||||||
baseURL: settings.baseURL,
|
baseURL: settings.baseURL,
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ import { XAI } from "../../src/providers"
|
||||||
|
|
||||||
const model = XAI.provider.model("grok-4")
|
const model = XAI.provider.model("grok-4")
|
||||||
|
|
||||||
LLM.request({ model, prompt: "Hello", providerOptions: { openai: { reasoningEffort: "high" } } })
|
LLM.request({ model, prompt: "Hello", providerOptions: { xai: { reasoningEffort: "high" } } })
|
||||||
|
|
||||||
LLM.request({
|
LLM.request({
|
||||||
model,
|
model,
|
||||||
prompt: "Hello",
|
prompt: "Hello",
|
||||||
// @ts-expect-error xAI's OpenAI-compatible reasoning effort must be a string.
|
// @ts-expect-error xAI's OpenAI-compatible reasoning effort must be a string.
|
||||||
providerOptions: { openai: { reasoningEffort: true } },
|
providerOptions: { xai: { reasoningEffort: true } },
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ describe("provider package entrypoints", () => {
|
||||||
})
|
})
|
||||||
const xai = XAI.model("grok-4", {
|
const xai = XAI.model("grok-4", {
|
||||||
...settings,
|
...settings,
|
||||||
providerOptions: { openai: { reasoningEffort: "high" } },
|
providerOptions: { xai: { reasoningEffort: "high" } },
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const selected of [openrouter, xai]) {
|
for (const selected of [openrouter, xai]) {
|
||||||
|
|
@ -57,7 +57,7 @@ describe("provider package entrypoints", () => {
|
||||||
expect(selected.route.defaults.limits).toEqual(settings.limits)
|
expect(selected.route.defaults.limits).toEqual(settings.limits)
|
||||||
}
|
}
|
||||||
expect(openrouter.route.defaults.providerOptions).toEqual({ openrouter: { usage: true } })
|
expect(openrouter.route.defaults.providerOptions).toEqual({ openrouter: { usage: true } })
|
||||||
expect(xai.route.defaults.providerOptions).toEqual({ openai: { reasoningEffort: "high", store: false } })
|
expect(xai.route.defaults.providerOptions).toMatchObject({ xai: { reasoningEffort: "high", store: false } })
|
||||||
})
|
})
|
||||||
|
|
||||||
test("maps package settings onto the executable model", () => {
|
test("maps package settings onto the executable model", () => {
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,8 @@ export const fromCatalogModel = (
|
||||||
.model({ id: resolved.modelID ?? resolved.id, compatibility: resolved.compatibility }),
|
.model({ id: resolved.modelID ?? resolved.id, compatibility: resolved.compatibility }),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (Provider.isAISDK(resolved.package)) {
|
const native = Provider.isAISDK(resolved.package) ? nativePackage(packageName) : resolved.package
|
||||||
|
if (Provider.isAISDK(resolved.package) && !native) {
|
||||||
if (!dependencies?.loadAISDK) return Effect.fail(unsupported(resolved))
|
if (!dependencies?.loadAISDK) return Effect.fail(unsupported(resolved))
|
||||||
const runtime = produce(resolved, (draft) => {
|
const runtime = produce(resolved, (draft) => {
|
||||||
draft.settings = Provider.mergeOverlay(draft.settings, {
|
draft.settings = Provider.mergeOverlay(draft.settings, {
|
||||||
|
|
@ -193,20 +194,22 @@ export const fromCatalogModel = (
|
||||||
})
|
})
|
||||||
return dependencies.loadAISDK(runtime).pipe(Effect.mapError(() => unsupported(resolved)))
|
return dependencies.loadAISDK(runtime).pipe(Effect.mapError(() => unsupported(resolved)))
|
||||||
}
|
}
|
||||||
if (!resolved.package) return Effect.fail(unsupported(resolved))
|
if (!native) return Effect.fail(unsupported(resolved))
|
||||||
|
|
||||||
const specifier = resolved.package
|
const specifier = native
|
||||||
return Effect.gen(function* () {
|
return Effect.gen(function* () {
|
||||||
const module = yield* (dependencies?.loadPackage ?? Provider.loadPackage)(specifier).pipe(
|
const module = yield* (dependencies?.loadPackage ?? Provider.loadPackage)(specifier).pipe(
|
||||||
Effect.mapError(() => unsupported(resolved)),
|
Effect.mapError(() => unsupported(resolved)),
|
||||||
)
|
)
|
||||||
const configured = { ...resolved.settings, ...credential?.metadata }
|
const configured = { ...resolved.settings, ...credential?.metadata }
|
||||||
|
const providerOptions = nativeProviderOptions(packageName, configured)
|
||||||
const settings = {
|
const settings = {
|
||||||
...(credential ? withoutNativeAuthSettings(configured) : configured),
|
...(credential ? withoutNativeAuthSettings(configured) : configured),
|
||||||
...nativeCredentialSettings(specifier, credential),
|
...nativeCredentialSettings(specifier, credential),
|
||||||
headers: resolved.headers,
|
headers: resolved.headers,
|
||||||
body: resolved.body,
|
body: resolved.body,
|
||||||
limits: { context: resolved.limit.context, output: resolved.limit.output },
|
limits: { context: resolved.limit.context, output: resolved.limit.output },
|
||||||
|
...(providerOptions ? { providerOptions } : {}),
|
||||||
}
|
}
|
||||||
return yield* Effect.try({
|
return yield* Effect.try({
|
||||||
try: () => {
|
try: () => {
|
||||||
|
|
@ -223,6 +226,26 @@ export const fromCatalogModel = (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nativePackage = (packageName: string | undefined) => {
|
||||||
|
if (packageName === "@ai-sdk/google") return "@opencode-ai/ai/providers/google"
|
||||||
|
if (packageName === "@openrouter/ai-sdk-provider") return "@opencode-ai/ai/providers/openrouter"
|
||||||
|
if (packageName === "@ai-sdk/xai") return "@opencode-ai/ai/providers/xai"
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const nativeProviderOptions = (packageName: string | undefined, settings: Readonly<Record<string, unknown>>) => {
|
||||||
|
const values = Object.fromEntries(
|
||||||
|
Object.entries(settings).filter(
|
||||||
|
([key]) => !["apiKey", "authToken", "baseURL", "chunkTimeout", "fetch", "timeout"].includes(key),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (Object.keys(values).length === 0) return undefined
|
||||||
|
if (packageName === "@ai-sdk/google") return { gemini: values }
|
||||||
|
if (packageName === "@openrouter/ai-sdk-provider") return { openrouter: values }
|
||||||
|
if (packageName === "@ai-sdk/xai") return { xai: values }
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
const isNativeOpenAI = (packageName: string | undefined) =>
|
const isNativeOpenAI = (packageName: string | undefined) =>
|
||||||
packageName === "@opencode-ai/ai/providers/openai" ||
|
packageName === "@opencode-ai/ai/providers/openai" ||
|
||||||
packageName?.startsWith("@opencode-ai/ai/providers/openai/") === true
|
packageName?.startsWith("@opencode-ai/ai/providers/openai/") === true
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import { testEffect } from "./lib/effect"
|
||||||
|
|
||||||
const selected = Info.make({
|
const selected = Info.make({
|
||||||
...Info.default(Provider.ID.make("test-provider"), ID.make("gemini")),
|
...Info.default(Provider.ID.make("test-provider"), ID.make("gemini")),
|
||||||
package: Provider.aisdk("@ai-sdk/google"),
|
package: Provider.aisdk("@ai-sdk/mistral"),
|
||||||
})
|
})
|
||||||
const runtime = Model.make({ id: "gemini", provider: "test-provider", route: OpenAIChat.route })
|
const runtime = Model.make({ id: "gemini", provider: "test-provider", route: OpenAIChat.route })
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -546,6 +546,76 @@ describe("ModelResolver", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("routes supported AISDK catalog packages through native provider packages", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const native = yield* ModelResolver.fromCatalogModel(model(Provider.aisdk("@ai-sdk/openai")))
|
||||||
|
const packages = [
|
||||||
|
["@ai-sdk/google", "@opencode-ai/ai/providers/google", "gemini"],
|
||||||
|
["@openrouter/ai-sdk-provider", "@opencode-ai/ai/providers/openrouter", "openrouter"],
|
||||||
|
["@ai-sdk/xai", "@opencode-ai/ai/providers/xai", "xai"],
|
||||||
|
] as const
|
||||||
|
|
||||||
|
yield* Effect.forEach(packages, ([catalogPackage, nativePackage, optionKey]) =>
|
||||||
|
ModelResolver.fromCatalogModel(
|
||||||
|
model(Provider.aisdk(catalogPackage), {
|
||||||
|
modelID: "api-model",
|
||||||
|
settings: { baseURL: "https://provider.example/v1", reasoningEffort: "high" },
|
||||||
|
headers: { "x-provider": "header" },
|
||||||
|
body: { custom: true },
|
||||||
|
}),
|
||||||
|
Credential.Key.make({ type: "key", key: "secret" }),
|
||||||
|
{
|
||||||
|
loadPackage: (specifier) => {
|
||||||
|
expect(specifier).toBe(nativePackage)
|
||||||
|
return Effect.succeed({
|
||||||
|
model: (modelID, settings) => {
|
||||||
|
expect(modelID).toBe("api-model")
|
||||||
|
expect(settings).toMatchObject({
|
||||||
|
apiKey: "secret",
|
||||||
|
baseURL: "https://provider.example/v1",
|
||||||
|
headers: { "x-provider": "header" },
|
||||||
|
body: { custom: true },
|
||||||
|
limits: { context: 100, output: 20 },
|
||||||
|
providerOptions: { [optionKey]: { reasoningEffort: "high" } },
|
||||||
|
})
|
||||||
|
return Model.make({ id: modelID, provider: "native-provider", route: native.route })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loadAISDK: () => Effect.die("AI SDK loader should not be called"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("loads supported AISDK catalog packages as native routes", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const google = yield* ModelResolver.fromCatalogModel(
|
||||||
|
model(Provider.aisdk("@ai-sdk/google"), { settings: { thinkingConfig: { thinkingBudget: 1_024 } } }),
|
||||||
|
)
|
||||||
|
const openrouter = yield* ModelResolver.fromCatalogModel(
|
||||||
|
model(Provider.aisdk("@openrouter/ai-sdk-provider"), {
|
||||||
|
settings: { reasoning: { effort: "high" } },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const xai = yield* ModelResolver.fromCatalogModel(
|
||||||
|
model(Provider.aisdk("@ai-sdk/xai"), { settings: { reasoningEffort: "high" } }),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(google.route.id).toBe("gemini")
|
||||||
|
expect(google.route.defaults.providerOptions).toEqual({
|
||||||
|
gemini: { thinkingConfig: { thinkingBudget: 1_024 } },
|
||||||
|
})
|
||||||
|
expect(openrouter.route.id).toBe("openrouter")
|
||||||
|
expect(openrouter.route.defaults.providerOptions).toEqual({ openrouter: { reasoning: { effort: "high" } } })
|
||||||
|
expect(xai.route.id).toBe("openai-responses")
|
||||||
|
expect(xai.route.defaults.providerOptions).toEqual({
|
||||||
|
xai: { reasoningEffort: "high", store: false },
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("loads arbitrary AISDK packages through the injected AISDK loader", () =>
|
it.effect("loads arbitrary AISDK packages through the injected AISDK loader", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const native = yield* ModelResolver.fromCatalogModel(
|
const native = yield* ModelResolver.fromCatalogModel(
|
||||||
|
|
@ -554,8 +624,8 @@ describe("ModelResolver", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const resolved = yield* ModelResolver.fromCatalogModel(
|
const resolved = yield* ModelResolver.fromCatalogModel(
|
||||||
model(Provider.aisdk("@ai-sdk/google"), {
|
model(Provider.aisdk("@ai-sdk/mistral"), {
|
||||||
modelID: "gemini-api-model",
|
modelID: "mistral-api-model",
|
||||||
settings: { project: "test" },
|
settings: { project: "test" },
|
||||||
headers: { "x-aisdk": "header" },
|
headers: { "x-aisdk": "header" },
|
||||||
body: { custom: true },
|
body: { custom: true },
|
||||||
|
|
@ -566,9 +636,9 @@ describe("ModelResolver", () => {
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
expect(runtime).toMatchObject({
|
expect(runtime).toMatchObject({
|
||||||
id: "test-model",
|
id: "test-model",
|
||||||
modelID: "gemini-api-model",
|
modelID: "mistral-api-model",
|
||||||
providerID: "test-provider",
|
providerID: "test-provider",
|
||||||
package: Provider.aisdk("@ai-sdk/google"),
|
package: Provider.aisdk("@ai-sdk/mistral"),
|
||||||
settings: { project: "test", apiKey: "fallback-secret" },
|
settings: { project: "test", apiKey: "fallback-secret" },
|
||||||
headers: { "x-aisdk": "header" },
|
headers: { "x-aisdk": "header" },
|
||||||
body: { custom: true },
|
body: { custom: true },
|
||||||
|
|
@ -582,15 +652,15 @@ describe("ModelResolver", () => {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(resolved).toMatchObject({ id: "gemini-api-model", provider: "test-provider" })
|
expect(resolved).toMatchObject({ id: "mistral-api-model", provider: "test-provider" })
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("rejects AISDK packages without an available loader", () =>
|
it.effect("rejects AISDK packages without an available loader", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const failure = yield* ModelResolver.fromCatalogModel(
|
const failure = yield* ModelResolver.fromCatalogModel(
|
||||||
model(Provider.aisdk("@ai-sdk/google"), {
|
model(Provider.aisdk("@ai-sdk/mistral"), {
|
||||||
settings: { baseURL: "https://google.example/v1" },
|
settings: { baseURL: "https://mistral.example/v1" },
|
||||||
}),
|
}),
|
||||||
).pipe(Effect.flip)
|
).pipe(Effect.flip)
|
||||||
|
|
||||||
|
|
@ -598,9 +668,9 @@ describe("ModelResolver", () => {
|
||||||
_tag: "SessionRunnerModel.UnsupportedPackageError",
|
_tag: "SessionRunnerModel.UnsupportedPackageError",
|
||||||
providerID: "test-provider",
|
providerID: "test-provider",
|
||||||
modelID: "test-model",
|
modelID: "test-model",
|
||||||
package: "aisdk:@ai-sdk/google",
|
package: "aisdk:@ai-sdk/mistral",
|
||||||
})
|
})
|
||||||
expect(failure.message).toBe("Unsupported package for test-provider/test-model: aisdk:@ai-sdk/google")
|
expect(failure.message).toBe("Unsupported package for test-provider/test-model: aisdk:@ai-sdk/mistral")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -612,8 +682,8 @@ describe("ModelResolver", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
yield* ModelResolver.fromCatalogModel(
|
yield* ModelResolver.fromCatalogModel(
|
||||||
model(Provider.aisdk("@ai-sdk/google"), {
|
model(Provider.aisdk("@ai-sdk/mistral"), {
|
||||||
settings: { apiKey: "", baseURL: "https://google.example/v1" },
|
settings: { apiKey: "", baseURL: "https://mistral.example/v1" },
|
||||||
}),
|
}),
|
||||||
undefined,
|
undefined,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue