Add native LLM core foundation (#24712)

This commit is contained in:
Kit Langton
2026-05-08 16:56:20 -04:00
committed by GitHub
parent dc7d665e94
commit 5bb7b23440
144 changed files with 17052 additions and 2 deletions
@@ -0,0 +1,48 @@
import { Route, type RouteModelInput } from "../route/client"
import { Provider } from "../provider"
import { ProviderID, type ModelID } from "../schema"
import * as BedrockConverse from "../protocols/bedrock-converse"
import type { BedrockCredentials } from "../protocols/bedrock-converse"
export const id = ProviderID.make("amazon-bedrock")
export type ModelOptions = Omit<RouteModelInput, "id" | "baseURL"> & {
readonly apiKey?: string
readonly headers?: Record<string, string>
readonly credentials?: BedrockCredentials
/** AWS region. Defaults to `us-east-1` when neither this nor `credentials.region` is set. */
readonly region?: string
/** Override the computed `https://bedrock-runtime.<region>.amazonaws.com` URL. */
readonly baseURL?: string
}
type ModelInput = ModelOptions & Pick<RouteModelInput, "id">
export const routes = [BedrockConverse.route]
const bedrockBaseURL = (region: string) => `https://bedrock-runtime.${region}.amazonaws.com`
const converseModel = Route.model<ModelInput>(
BedrockConverse.route,
{
provider: "amazon-bedrock",
},
{
mapInput: (input) => {
const { credentials, region, baseURL, ...rest } = input
const resolvedRegion = region ?? credentials?.region ?? "us-east-1"
return {
...rest,
baseURL: baseURL ?? bedrockBaseURL(resolvedRegion),
native: BedrockConverse.nativeCredentials(input.native, credentials),
}
},
},
)
export const model = (modelID: string | ModelID, options: ModelOptions = {}) =>
converseModel({ ...options, id: modelID })
export const provider = Provider.make({
id,
model,
})
+16
View File
@@ -0,0 +1,16 @@
import type { RouteModelInput } from "../route/client"
import { Provider } from "../provider"
import { ProviderID, type ModelID } from "../schema"
import * as AnthropicMessages from "../protocols/anthropic-messages"
export const id = ProviderID.make("anthropic")
export const routes = [AnthropicMessages.route]
export const model = (id: string | ModelID, options: Omit<RouteModelInput, "id" | "baseURL"> & { readonly baseURL?: string } = {}) =>
AnthropicMessages.model({ ...options, id })
export const provider = Provider.make({
id,
model,
})
+83
View File
@@ -0,0 +1,83 @@
import { Auth } from "../route/auth"
import { type AtLeastOne, type ProviderAuthOption } from "../route/auth-options"
import { Route } from "../route/client"
import type { ModelInput } from "../llm"
import { Provider } from "../provider"
import { ProviderID, type ModelID } from "../schema"
import * as OpenAIChat from "../protocols/openai-chat"
import * as OpenAIResponses from "../protocols/openai-responses"
import { withOpenAIOptions, type OpenAIProviderOptionsInput } from "./openai-options"
export const id = ProviderID.make("azure")
const routeAuth = Auth.remove("authorization").andThen(Auth.apiKeyHeader("api-key"))
// Azure needs the customer's resource URL; supply either `resourceName`
// (helper builds the URL) or `baseURL` directly.
type AzureURL = AtLeastOne<{ readonly resourceName: string; readonly baseURL: string }>
export type ModelOptions = AzureURL &
Omit<ModelInput, "id" | "provider" | "route" | "apiKey" | "auth" | "baseURL"> &
ProviderAuthOption<"optional"> & {
readonly apiVersion?: string
readonly useCompletionUrls?: boolean
readonly providerOptions?: OpenAIProviderOptionsInput
}
type AzureModelInput = ModelOptions & Pick<ModelInput, "id">
const resourceBaseURL = (resourceName: string) => `https://${resourceName.trim()}.openai.azure.com/openai/v1`
const responsesRoute = OpenAIResponses.route.with({
id: "azure-openai-responses",
provider: id,
transport: OpenAIResponses.httpTransport.with({ auth: routeAuth }),
})
const chatRoute = OpenAIChat.route.with({
id: "azure-openai-chat",
provider: id,
transport: OpenAIChat.httpTransport.with({ auth: routeAuth }),
})
export const routes = [responsesRoute, chatRoute]
const mapInput = (input: AzureModelInput) => {
const { apiKey: _, apiVersion, resourceName, useCompletionUrls, ...rest } = input
return {
...withOpenAIOptions(input.id, rest),
auth:
"auth" in input && input.auth
? input.auth
: Auth.remove("authorization").andThen(
Auth.optional("apiKey" in input ? input.apiKey : undefined, "apiKey")
.orElse(Auth.config("AZURE_OPENAI_API_KEY"))
.pipe(Auth.header("api-key")),
),
// AtLeastOne guarantees at least one is set; baseURL wins if both are.
baseURL: rest.baseURL ?? resourceBaseURL(resourceName!),
queryParams: {
...rest.queryParams,
"api-version": apiVersion ?? rest.queryParams?.["api-version"] ?? "v1",
},
}
}
const chatModel = Route.model<AzureModelInput>(chatRoute, {}, { mapInput })
const responsesModel = Route.model<AzureModelInput>(responsesRoute, {}, { mapInput })
export const responses = (modelID: string | ModelID, options: ModelOptions) =>
responsesModel({ ...options, id: modelID })
export const chat = (modelID: string | ModelID, options: ModelOptions) => chatModel({ ...options, id: modelID })
export const model = (modelID: string | ModelID, options: ModelOptions) => {
if (options.useCompletionUrls === true) return chat(modelID, options)
return responses(modelID, options)
}
export const provider = Provider.make({
id,
model,
apis: { responses, chat },
})
export const apis = provider.apis
+139
View File
@@ -0,0 +1,139 @@
import type { Config, Redacted } from "effect"
import { type ModelInput } from "../llm"
import { Provider } from "../provider"
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat"
import { Auth } from "../route/auth"
import { AuthOptions, type AtLeastOne, type ProviderAuthOption } from "../route/auth-options"
import { Route } from "../route/client"
import { ProviderID, type ModelID } from "../schema"
export const aiGatewayID = ProviderID.make("cloudflare-ai-gateway")
export const workersAIID = ProviderID.make("cloudflare-workers-ai")
export const id = aiGatewayID
export const aiGatewayAuthEnvVars = ["CLOUDFLARE_API_TOKEN", "CF_AIG_TOKEN"] as const
export const workersAIAuthEnvVars = ["CLOUDFLARE_API_KEY", "CLOUDFLARE_WORKERS_AI_TOKEN"] as const
type CloudflareSecret = string | Redacted.Redacted<string> | Config.Config<string | Redacted.Redacted<string>>
type GatewayURL = AtLeastOne<{
readonly accountId: string
readonly baseURL: string
}> & {
readonly gatewayId?: string
}
export type AIGatewayOptions = GatewayURL &
Omit<ModelInput, "id" | "provider" | "route" | "baseURL" | "apiKey" | "auth"> &
ProviderAuthOption<"optional"> & {
/** Cloudflare AI Gateway authentication token. Sent as `cf-aig-authorization`. */
readonly gatewayApiKey?: CloudflareSecret
}
type AIGatewayInput = AIGatewayOptions & Pick<ModelInput, "id">
type WorkersAIURL = AtLeastOne<{
readonly accountId: string
readonly baseURL: string
}>
export type WorkersAIOptions = WorkersAIURL &
Omit<ModelInput, "id" | "provider" | "route" | "baseURL" | "apiKey" | "auth"> &
ProviderAuthOption<"optional">
type WorkersAIInput = WorkersAIOptions & Pick<ModelInput, "id">
export const aiGatewayBaseURL = (input: GatewayURL) => {
if (input.baseURL) return input.baseURL
if (!input.accountId) throw new Error("Cloudflare.aiGateway requires accountId unless baseURL is supplied")
return `https://gateway.ai.cloudflare.com/v1/${encodeURIComponent(input.accountId)}/${encodeURIComponent(input.gatewayId?.trim() || "default")}/compat`
}
const aiGatewayAuth = (input: AIGatewayInput) => {
if ("auth" in input && input.auth) return input.auth
const gateway = Auth.optional(input.gatewayApiKey, "gatewayApiKey")
.orElse(Auth.config("CLOUDFLARE_API_TOKEN"))
.orElse(Auth.config("CF_AIG_TOKEN"))
.pipe(Auth.bearerHeader("cf-aig-authorization"))
if (!("apiKey" in input) || input.apiKey === undefined) return gateway
if (input.gatewayApiKey === undefined) return Auth.bearer(input.apiKey)
return Auth.bearerHeader("cf-aig-authorization", input.gatewayApiKey).andThen(Auth.bearer(input.apiKey))
}
export const workersAIBaseURL = (input: WorkersAIURL) => {
if (input.baseURL) return input.baseURL
if (!input.accountId) throw new Error("Cloudflare.workersAI requires accountId unless baseURL is supplied")
return `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(input.accountId)}/ai/v1`
}
const workersAIAuth = (input: WorkersAIInput) => {
return AuthOptions.bearer(input, workersAIAuthEnvVars)
}
export const aiGatewayRoute = OpenAICompatibleChat.route.with({
id: "cloudflare-ai-gateway",
provider: aiGatewayID,
})
export const workersAIRoute = OpenAICompatibleChat.route.with({
id: "cloudflare-workers-ai",
provider: workersAIID,
})
export const routes = [aiGatewayRoute, workersAIRoute]
const aiGatewayModel = Route.model<AIGatewayInput>(
aiGatewayRoute,
{
provider: id,
},
{
mapInput: (input) => {
const {
accountId: _accountId,
gatewayId: _gatewayId,
apiKey: _apiKey,
gatewayApiKey: _gatewayApiKey,
auth: _auth,
...rest
} = input
return {
...rest,
auth: aiGatewayAuth(input),
baseURL: aiGatewayBaseURL(input),
}
},
},
)
const workersAIModel = Route.model<WorkersAIInput>(
workersAIRoute,
{
provider: workersAIID,
},
{
mapInput: (input) => {
const { accountId: _accountId, apiKey: _apiKey, auth: _auth, ...rest } = input
return {
...rest,
auth: workersAIAuth(input),
baseURL: workersAIBaseURL(input),
}
},
},
)
export const aiGateway = (modelID: string | ModelID, options: AIGatewayOptions) =>
aiGatewayModel({ ...options, id: modelID })
export const workersAI = (modelID: string | ModelID, options: WorkersAIOptions) =>
workersAIModel({ ...options, id: modelID })
export const model = aiGateway
export const provider = Provider.make({
id,
model,
apis: { aiGateway, workersAI },
})
export const apis = provider.apis
@@ -0,0 +1,48 @@
import { Route } from "../route/client"
import type { ModelInput } from "../llm"
import { Provider } from "../provider"
import { ProviderID, type ModelID } from "../schema"
import * as OpenAIChat from "../protocols/openai-chat"
import * as OpenAIResponses from "../protocols/openai-responses"
import { withOpenAIOptions, type OpenAIProviderOptionsInput } from "./openai-options"
export const id = ProviderID.make("github-copilot")
// GitHub Copilot has no canonical public URL — callers (opencode, etc.) must
// supply `baseURL` explicitly.
export type ModelOptions = Omit<ModelInput, "id" | "provider" | "route"> & {
readonly providerOptions?: OpenAIProviderOptionsInput
}
type CopilotModelInput = ModelOptions & Pick<ModelInput, "id">
export const shouldUseResponsesApi = (modelID: string | ModelID) => {
const model = String(modelID)
const match = /^gpt-(\d+)/.exec(model)
if (!match) return false
return Number(match[1]) >= 5 && !model.startsWith("gpt-5-mini")
}
export const routes = [OpenAIResponses.route, OpenAIChat.route]
const mapInput = (input: CopilotModelInput) => withOpenAIOptions(input.id, input)
const chatModel = Route.model<CopilotModelInput>(OpenAIChat.route, { provider: id }, { mapInput })
const responsesModel = Route.model<CopilotModelInput>(OpenAIResponses.route, { provider: id }, { mapInput })
export const responses = (modelID: string | ModelID, options: ModelOptions) =>
responsesModel({ ...options, id: modelID })
export const chat = (modelID: string | ModelID, options: ModelOptions) => chatModel({ ...options, id: modelID })
export const model = (modelID: string | ModelID, options: ModelOptions) => {
const create = shouldUseResponsesApi(modelID) ? responsesModel : chatModel
return create({ ...options, id: modelID })
}
export const provider = Provider.make({
id,
model,
apis: { responses, chat },
})
export const apis = provider.apis
+16
View File
@@ -0,0 +1,16 @@
import type { RouteModelInput } from "../route/client"
import { Provider } from "../provider"
import { ProviderID, type ModelID } from "../schema"
import * as Gemini from "../protocols/gemini"
export const id = ProviderID.make("google")
export const routes = [Gemini.route]
export const model = (id: string | ModelID, options: Omit<RouteModelInput, "id" | "baseURL"> & { readonly baseURL?: string } = {}) =>
Gemini.model({ ...options, id })
export const provider = Provider.make({
id,
model,
})
+10
View File
@@ -0,0 +1,10 @@
export * as Anthropic from "./anthropic"
export * as AmazonBedrock from "./amazon-bedrock"
export * as Azure from "./azure"
export * as Cloudflare from "./cloudflare"
export * as GitHubCopilot from "./github-copilot"
export * as Google from "./google"
export * as OpenAI from "./openai"
export * as OpenAICompatible from "./openai-compatible"
export * as OpenRouter from "./openrouter"
export * as XAI from "./xai"
@@ -0,0 +1,20 @@
export interface OpenAICompatibleProfile {
readonly provider: string
readonly baseURL: string
}
export const profiles = {
baseten: { provider: "baseten", baseURL: "https://inference.baseten.co/v1" },
cerebras: { provider: "cerebras", baseURL: "https://api.cerebras.ai/v1" },
deepinfra: { provider: "deepinfra", baseURL: "https://api.deepinfra.com/v1/openai" },
deepseek: { provider: "deepseek", baseURL: "https://api.deepseek.com/v1" },
fireworks: { provider: "fireworks", baseURL: "https://api.fireworks.ai/inference/v1" },
groq: { provider: "groq", baseURL: "https://api.groq.com/openai/v1" },
openrouter: { provider: "openrouter", baseURL: "https://openrouter.ai/api/v1" },
togetherai: { provider: "togetherai", baseURL: "https://api.together.xyz/v1" },
xai: { provider: "xai", baseURL: "https://api.x.ai/v1" },
} as const satisfies Record<string, OpenAICompatibleProfile>
export const byProvider: Record<string, OpenAICompatibleProfile> = Object.fromEntries(
Object.values(profiles).map((profile) => [profile.provider, profile]),
)
@@ -0,0 +1,61 @@
import { Provider } from "../provider"
import { ProviderID, type ModelID } from "../schema"
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat"
import type { OpenAICompatibleChatModelInput } from "../protocols/openai-compatible-chat"
import { profiles, type OpenAICompatibleProfile } from "./openai-compatible-profile"
export const id = ProviderID.make("openai-compatible")
export type ModelOptions = Omit<OpenAICompatibleChatModelInput, "id" | "provider"> & {
readonly provider: string
}
type GenericModelOptions = Omit<ModelOptions, "provider"> & {
readonly provider?: string
}
export type FamilyModelOptions = Omit<OpenAICompatibleChatModelInput, "id" | "provider" | "baseURL"> & {
readonly baseURL?: string
}
export const routes = [OpenAICompatibleChat.route]
export const model = (id: string | ModelID, options: ModelOptions) => {
return OpenAICompatibleChat.model({
...options,
id,
provider: ProviderID.make(options.provider),
})
}
export const profileModel = (
profile: OpenAICompatibleProfile,
id: string | ModelID,
options: FamilyModelOptions = {},
) =>
OpenAICompatibleChat.model({
...options,
id,
provider: profile.provider,
baseURL: options.baseURL ?? profile.baseURL,
})
const define = (profile: OpenAICompatibleProfile) =>
Provider.make({
id: ProviderID.make(profile.provider),
model: (id: string | ModelID, options: FamilyModelOptions = {}) => profileModel(profile, id, options),
})
export const provider = Provider.make({
id,
model: (id: string | ModelID, options: GenericModelOptions) =>
model(id, { ...options, provider: options.provider ?? "openai-compatible" }),
})
export const baseten = define(profiles.baseten)
export const cerebras = define(profiles.cerebras)
export const deepinfra = define(profiles.deepinfra)
export const deepseek = define(profiles.deepseek)
export const fireworks = define(profiles.fireworks)
export const groq = define(profiles.groq)
export const togetherai = define(profiles.togetherai)
@@ -0,0 +1,70 @@
import type { ProviderOptions, ReasoningEffort, TextVerbosity } from "../schema"
import { mergeProviderOptions } from "../schema"
export interface OpenAIOptionsInput {
readonly [key: string]: unknown
readonly store?: boolean
readonly promptCacheKey?: string
readonly reasoningEffort?: ReasoningEffort
readonly reasoningSummary?: "auto"
readonly includeEncryptedReasoning?: boolean
readonly textVerbosity?: TextVerbosity
}
export type OpenAIProviderOptionsInput = ProviderOptions & {
readonly openai?: OpenAIOptionsInput
}
const definedEntries = (input: Record<string, unknown>) =>
Object.entries(input).filter((entry) => entry[1] !== undefined)
const openAIProviderOptions = (options: OpenAIOptionsInput | undefined): ProviderOptions | undefined => {
const openai = Object.fromEntries(
definedEntries({
store: options?.store,
promptCacheKey: options?.promptCacheKey,
reasoningEffort: options?.reasoningEffort,
reasoningSummary: options?.reasoningSummary,
includeEncryptedReasoning: options?.includeEncryptedReasoning,
textVerbosity: options?.textVerbosity,
}),
)
if (Object.keys(openai).length === 0) return undefined
return { openai }
}
export const gpt5DefaultOptions = (
modelID: string,
options: { readonly textVerbosity?: boolean } = {},
): ProviderOptions | undefined => {
const id = modelID.toLowerCase()
if (!id.includes("gpt-5") || id.includes("gpt-5-chat") || id.includes("gpt-5-pro")) return undefined
return openAIProviderOptions({
reasoningEffort: "medium",
reasoningSummary: "auto",
textVerbosity:
options.textVerbosity === true && id.includes("gpt-5.") && !id.includes("codex") && !id.includes("-chat")
? "low"
: undefined,
})
}
export const openAIDefaultOptions = (
modelID: string,
options: { readonly textVerbosity?: boolean } = {},
): ProviderOptions | undefined =>
mergeProviderOptions(openAIProviderOptions({ store: false }), gpt5DefaultOptions(modelID, options))
export const withOpenAIOptions = <Options extends { readonly providerOptions?: OpenAIProviderOptionsInput }>(
modelID: string,
options: Options,
defaults: { readonly textVerbosity?: boolean } = {},
): Options & { readonly id: string; readonly providerOptions?: ProviderOptions } => {
return {
...options,
id: modelID,
providerOptions: mergeProviderOptions(openAIDefaultOptions(modelID, defaults), options.providerOptions),
}
}
export * as OpenAIProviderOptions from "./openai-options"
+53
View File
@@ -0,0 +1,53 @@
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
import type { RouteModelInput } from "../route/client"
import { Provider } from "../provider"
import { ProviderID, type ModelID } from "../schema"
import * as OpenAIChat from "../protocols/openai-chat"
import * as OpenAIResponses from "../protocols/openai-responses"
import { withOpenAIOptions, type OpenAIProviderOptionsInput } from "./openai-options"
export type { OpenAIOptionsInput } from "./openai-options"
export const id = ProviderID.make("openai")
export const routes = [OpenAIResponses.route, OpenAIResponses.webSocketRoute, OpenAIChat.route]
// This provider facade wraps the lower-level Responses and Chat model factories
// with OpenAI-specific conveniences: typed options, API-key sugar, env fallback,
// and default option normalization.
type OpenAIModelInput<ModelInput> = Omit<ModelInput, "apiKey" | "auth" | "baseURL"> &
ProviderAuthOption<"optional"> & {
readonly baseURL?: string
readonly providerOptions?: OpenAIProviderOptionsInput
}
const auth = (options: ProviderAuthOption<"optional">) => AuthOptions.bearer(options, "OPENAI_API_KEY")
export const responses = (id: string | ModelID, options: OpenAIModelInput<Omit<RouteModelInput, "id">> = {}) => {
const { apiKey: _, ...rest } = options
return OpenAIResponses.model(withOpenAIOptions(id, { ...rest, auth: auth(options) }, { textVerbosity: true }))
}
export const responsesWebSocket = (
id: string | ModelID,
options: OpenAIModelInput<Omit<RouteModelInput, "id">> = {},
) => {
const { apiKey: _, ...rest } = options
return OpenAIResponses.webSocketModel(
withOpenAIOptions(id, { ...rest, auth: auth(options) }, { textVerbosity: true }),
)
}
export const chat = (id: string | ModelID, options: OpenAIModelInput<Omit<RouteModelInput, "id">> = {}) => {
const { apiKey: _, ...rest } = options
return OpenAIChat.model(withOpenAIOptions(id, { ...rest, auth: auth(options) }))
}
export const provider = Provider.make({
id,
model: responses,
apis: { responses, responsesWebSocket, chat },
})
export const model = provider.model
export const apis = provider.apis
+88
View File
@@ -0,0 +1,88 @@
import { Effect, Schema } from "effect"
import { Route, type RouteModelInput } from "../route/client"
import { Endpoint } from "../route/endpoint"
import { Framing } from "../route/framing"
import { Provider } from "../provider"
import { Protocol } from "../route/protocol"
import { ProviderID, type ModelID, type ProviderOptions } from "../schema"
import * as OpenAICompatibleProfiles from "./openai-compatible-profile"
import * as OpenAIChat from "../protocols/openai-chat"
import { isRecord } from "../protocols/shared"
export const profile = OpenAICompatibleProfiles.profiles.openrouter
export const id = ProviderID.make(profile.provider)
const ADAPTER = "openrouter"
export interface OpenRouterOptions {
readonly [key: string]: unknown
readonly usage?: boolean | Record<string, unknown>
readonly reasoning?: Record<string, unknown>
readonly promptCacheKey?: string
}
export type OpenRouterProviderOptionsInput = ProviderOptions & {
readonly openrouter?: OpenRouterOptions
}
export type ModelOptions = Omit<RouteModelInput, "id" | "baseURL" | "providerOptions"> & {
readonly baseURL?: string
readonly providerOptions?: OpenRouterProviderOptionsInput
}
type ModelInput = ModelOptions & Pick<RouteModelInput, "id">
const OpenRouterBody = Schema.StructWithRest(Schema.Struct(OpenAIChat.bodyFields), [
Schema.Record(Schema.String, Schema.Any),
])
export type OpenRouterBody = Schema.Schema.Type<typeof OpenRouterBody>
export const protocol = Protocol.make({
id: "openrouter-chat",
body: {
schema: OpenRouterBody,
from: (request) =>
OpenAIChat.protocol.body.from(request).pipe(
Effect.map(
(body) =>
({
...body,
...bodyOptions(request.providerOptions?.openrouter),
}) as OpenRouterBody,
),
),
},
stream: OpenAIChat.protocol.stream,
})
const bodyOptions = (input: unknown) => {
const openrouter = isRecord(input) ? input : {}
return {
...(openrouter.usage === true
? { usage: { include: true } }
: isRecord(openrouter.usage)
? { usage: openrouter.usage }
: {}),
...(isRecord(openrouter.reasoning) ? { reasoning: openrouter.reasoning } : {}),
...(typeof openrouter.promptCacheKey === "string" ? { prompt_cache_key: openrouter.promptCacheKey } : {}),
}
}
export const route = Route.make({
id: ADAPTER,
protocol,
endpoint: Endpoint.path("/chat/completions"),
framing: Framing.sse,
})
export const routes = [route]
const modelRef = Route.model<ModelInput>(route, {
provider: profile.provider,
baseURL: profile.baseURL,
})
export const model = (id: string | ModelID, options: ModelOptions = {}) => modelRef({ ...options, id })
export const provider = Provider.make({
id,
model,
})
+52
View File
@@ -0,0 +1,52 @@
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
import { Route } from "../route/client"
import type { RouteModelInput } from "../route/client"
import { Provider } from "../provider"
import { ProviderID, type ModelID } from "../schema"
import * as OpenAICompatibleProfiles from "./openai-compatible-profile"
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat"
import * as OpenAIResponses from "../protocols/openai-responses"
export const id = ProviderID.make("xai")
export type ModelOptions = Omit<RouteModelInput, "id" | "apiKey" | "auth" | "baseURL"> &
ProviderAuthOption<"optional"> & {
readonly baseURL?: string
}
export const routes = [OpenAIResponses.route, OpenAICompatibleChat.route]
const responsesModel = Route.model(OpenAIResponses.route, { provider: id })
const chatModel = OpenAICompatibleChat.model
const auth = (options: ProviderAuthOption<"optional">) => AuthOptions.bearer(options, "XAI_API_KEY")
export const responses = (modelID: string | ModelID, options: ModelOptions = {}) => {
const { apiKey: _, ...rest } = options
return responsesModel({
...rest,
auth: auth(options),
id: modelID,
baseURL: options.baseURL ?? OpenAICompatibleProfiles.profiles.xai.baseURL,
})
}
export const chat = (modelID: string | ModelID, options: ModelOptions = {}) => {
const { apiKey: _, ...rest } = options
return chatModel({
...rest,
auth: auth(options),
id: modelID,
provider: id,
baseURL: options.baseURL ?? OpenAICompatibleProfiles.profiles.xai.baseURL,
})
}
export const provider = Provider.make({
id,
model: responses,
apis: { responses, chat },
})
export const model = provider.model
export const apis = provider.apis