diff --git a/packages/ai/src/protocols/anthropic-messages.ts b/packages/ai/src/protocols/anthropic-messages.ts index 1e8aeb84f03..fc15f99a036 100644 --- a/packages/ai/src/protocols/anthropic-messages.ts +++ b/packages/ai/src/protocols/anthropic-messages.ts @@ -53,7 +53,6 @@ const SSE_EVENTS = new Set([ ]) export const framing = Framing.sseEvents(SSE_EVENTS) -export type ThinkingBlockBinding = typeof AnthropicThinkingBlockBinding.Type export type ThinkingInput = typeof Thinking.Encoded /** Caller-facing provider options; unknown keys are accepted and ignored. `Options.Type` is the wire-ready form. */ export type OptionsInput = ProviderOptions & typeof Options.Encoded @@ -818,7 +817,7 @@ const requireThinkingSignature = (request: LLMRequest) => { // Opus 4.8 and every Claude 5 model accept mid-conversation system messages; later versions inherit support. const supportsNativeSystemUpdates = (request: LLMRequest) => { - const version = claudeVersion(String(request.model.id)) + const version = claudeVersion(request.model.id) if (version === undefined) return false if (version.family === "opus" && version.major === 4) return version.minor >= 8 return version.major >= 5 @@ -871,7 +870,7 @@ const appendToUserTurn = (messages: AnthropicMessage[], block: AnthropicUserBloc const lowerMessages = Effect.fnUntraced(function* (request: LLMRequest, breakpoints: Cache.Breakpoints) { const messages: AnthropicMessage[] = [] - const providerMetadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider) + const providerMetadataKey = request.model.route.providerMetadataKey ?? request.model.provider // Text updates stay where they are unless a user turn follows them; then they move after the latest // user turn, the nearest spot where Anthropic accepts a native system message. const holdUpdates = supportsNativeSystemUpdates(request) @@ -1602,7 +1601,7 @@ export const protocol = Protocol.make({ initial: (request) => ({ provider: request.model.provider, compactions: {}, - providerMetadataKey: request.model.route.providerMetadataKey ?? String(request.model.provider), + providerMetadataKey: request.model.route.providerMetadataKey ?? request.model.provider, tools: ToolStream.empty(), reasoningSignatures: {}, lifecycle: Lifecycle.initial(), diff --git a/packages/ai/src/protocols/bedrock-converse.ts b/packages/ai/src/protocols/bedrock-converse.ts index 69030465f60..01d4bd9dfdd 100644 --- a/packages/ai/src/protocols/bedrock-converse.ts +++ b/packages/ai/src/protocols/bedrock-converse.ts @@ -331,7 +331,7 @@ const lowerMessages = Effect.fnUntraced(function* ( const documentNames = new Set() // Mistral can reject replay IDs even when they satisfy Converse's broader ID syntax. const normalizeID = request.model.id.includes("mistral.") ? MistralToolID.normalizer(request) : (id: string) => id - const providerMetadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider) + const providerMetadataKey = request.model.route.providerMetadataKey ?? request.model.provider const hoistImages = !keepToolImagesInline(request.model.id) // Bedrock expects parallel tool results before any images hoisted beside them. const pendingImages: BedrockMedia.ImageBlock[] = [] @@ -862,7 +862,7 @@ export const protocol = Protocol.make({ stream: { event: BedrockEventStream.Frame.pipe(Schema.decodeTo(BedrockEvent)), initial: (request) => ({ - providerMetadataKey: request.model.route.providerMetadataKey ?? String(request.model.provider), + providerMetadataKey: request.model.route.providerMetadataKey ?? request.model.provider, tools: ToolStream.empty(), finishReason: undefined, usage: undefined, @@ -891,6 +891,4 @@ export const route = Route.make({ framing, }) -export const sigV4Auth = BedrockAuth.sigV4 - export * as BedrockConverse from "./bedrock-converse.js" diff --git a/packages/ai/src/protocols/gemini.ts b/packages/ai/src/protocols/gemini.ts index 5bcf833a396..02cb83395eb 100644 --- a/packages/ai/src/protocols/gemini.ts +++ b/packages/ai/src/protocols/gemini.ts @@ -321,7 +321,7 @@ const lowerToolCall = (part: ToolCallPart, omitIds: boolean, metadataKey: string const lowerMessages = Effect.fnUntraced(function* (request: LLMRequest) { const contents: GeminiContent[] = [] - const metadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider) + const metadataKey = request.model.route.providerMetadataKey ?? request.model.provider const omitCallIds = omitsFunctionCallIds(request.model.id) const legacyToolMedia = routesLegacyToolMedia(request.model.id) let pendingMedia: GeminiInlineDataPart[] | undefined @@ -960,7 +960,7 @@ export const protocol = Protocol.make({ event: Schema.Union([Schema.instanceOf(NextRequest), GeminiEvent]), initial: (request): ParserState => ({ route: `${request.model.provider}/${request.model.route.id}`, - providerMetadataKey: request.model.route.providerMetadataKey ?? String(request.model.provider), + providerMetadataKey: request.model.route.providerMetadataKey ?? request.model.provider, hasToolCalls: false, lifecycle: Lifecycle.initial(), nextReasoningId: 0, diff --git a/packages/ai/src/protocols/google-interactions.ts b/packages/ai/src/protocols/google-interactions.ts index dd74422c06d..1721beaa3fd 100644 --- a/packages/ai/src/protocols/google-interactions.ts +++ b/packages/ai/src/protocols/google-interactions.ts @@ -236,7 +236,7 @@ const signature = (metadata: ProviderMetadata | undefined, key: string) => { const lowerMessages = Effect.fnUntraced(function* (request: LLMRequest) { const steps: InputStep[] = [] - const key = request.model.route.providerMetadataKey ?? String(request.model.provider) + const key = request.model.route.providerMetadataKey ?? request.model.provider for (const message of request.messages) { if (message.role === "system") { const part = yield* ProviderShared.wrappedSystemUpdate("Google Interactions", message) @@ -562,7 +562,7 @@ export const protocol = Protocol.make({ event: Protocol.jsonEvent(Event), initial: (request): ParserState => ({ route: `${request.model.provider}/${ADAPTER}`, - metadataKey: request.model.route.providerMetadataKey ?? String(request.model.provider), + metadataKey: request.model.route.providerMetadataKey ?? request.model.provider, lifecycle: Lifecycle.initial(), steps: {}, tools: ToolStream.empty(), diff --git a/packages/ai/src/protocols/openai-chat.ts b/packages/ai/src/protocols/openai-chat.ts index 1b80e93739a..b3ce1d3f66c 100644 --- a/packages/ai/src/protocols/openai-chat.ts +++ b/packages/ai/src/protocols/openai-chat.ts @@ -577,7 +577,7 @@ const lowerMessages = Effect.fnUntraced(function* (request: LLMRequest, options: const mistral = ["mistral", "devstral", "codestral", "pixtral", "mixtral"].some((family) => modelID.includes(family)) const lowering = { ...options, - providerMetadataKey: request.model.route.providerMetadataKey ?? String(request.model.provider), + providerMetadataKey: request.model.route.providerMetadataKey ?? request.model.provider, toolCallID: (id: string) => { if (mistral) return id @@ -783,7 +783,7 @@ export const fromRequest = Effect.fn("OpenAIChat.fromRequest")(function* ( ) const generation = request.generation const flattened = ProviderShared.flattenToolRequest(request) - const provider = String(request.model.provider) + const provider = request.model.provider const baseURL = request.model.route.endpoint.baseURL const detectedMaxTokensField = detectMaxTokensField(provider, baseURL) const maxTokensField = request.model.compatibility?.maxTokensField ?? detectedMaxTokensField @@ -1252,7 +1252,7 @@ export const protocol = Protocol.make({ stream: { event: OpenAIChatStreamEvent, initial: (request) => ({ - providerMetadataKey: request.model.route.providerMetadataKey ?? String(request.model.provider), + providerMetadataKey: request.model.route.providerMetadataKey ?? request.model.provider, tools: ToolStream.empty(), pendingTools: {}, toolCallEvents: [], diff --git a/packages/ai/src/protocols/openai-compatible-chat.ts b/packages/ai/src/protocols/openai-compatible-chat.ts index a9749f6f759..bef6848c101 100644 --- a/packages/ai/src/protocols/openai-compatible-chat.ts +++ b/packages/ai/src/protocols/openai-compatible-chat.ts @@ -1,11 +1,9 @@ -import { Route, type RouteRoutedLanguageModelInput } from "../route/client.js" +import { Route } from "../route/client.js" import { Endpoint } from "../route/endpoint.js" import * as OpenAIChat from "./openai-chat.js" const ADAPTER = "openai-compatible-chat" -export type OpenAICompatibleChatLanguageModelInput = RouteRoutedLanguageModelInput - /** * Route for non-OpenAI providers that expose an OpenAI Chat-compatible * `/chat/completions` endpoint. Reuses `OpenAIChat.protocol` end-to-end and diff --git a/packages/ai/src/protocols/openai-compatible-responses.ts b/packages/ai/src/protocols/openai-compatible-responses.ts index 262954dc368..26c35fc49c1 100644 --- a/packages/ai/src/protocols/openai-compatible-responses.ts +++ b/packages/ai/src/protocols/openai-compatible-responses.ts @@ -1,11 +1,9 @@ -import { Route, type RouteRoutedLanguageModelInput } from "../route/client.js" +import { Route } from "../route/client.js" import { Endpoint } from "../route/endpoint.js" import { OpenResponses } from "./open-responses.js" const ADAPTER = "openai-compatible-responses" -export type OpenAICompatibleResponsesLanguageModelInput = RouteRoutedLanguageModelInput - /** * Deployment adapter for providers that expose an Open Responses-compatible * `/responses` endpoint. Provider helpers configure identity, endpoint, and diff --git a/packages/ai/src/protocols/utils/partial-json-options.ts b/packages/ai/src/protocols/utils/partial-json-options.ts index 2021d54bf3d..25c3cc0dac4 100644 --- a/packages/ai/src/protocols/utils/partial-json-options.ts +++ b/packages/ai/src/protocols/utils/partial-json-options.ts @@ -60,5 +60,3 @@ export const ALL = ATOM | COLLECTION * The default is to allow all types to be partially parsed, which in most cases is the best option. */ export const Allow = { STR, NUM, ARR, OBJ, NULL, BOOL, NAN, INFINITY, _INFINITY, INF, SPECIAL, ATOM, COLLECTION, ALL } - -export default Allow diff --git a/packages/ai/src/providers/cloudflare-ai-gateway.ts b/packages/ai/src/providers/cloudflare-ai-gateway.ts index 9ed255f992a..7ca071656e9 100644 --- a/packages/ai/src/providers/cloudflare-ai-gateway.ts +++ b/packages/ai/src/providers/cloudflare-ai-gateway.ts @@ -120,8 +120,7 @@ export const configure = (input: LanguageModelOptions) => { const chat = route.with(rest) return { id, - model: (input: string | ModelID) => { - const value = String(input) + model: (value: string | ModelID) => { if (value.startsWith("openai/")) return responses.model({ id: native ? value.slice("openai/".length) : value }) if (value.startsWith("anthropic/")) diff --git a/packages/ai/src/providers/google-vertex.ts b/packages/ai/src/providers/google-vertex.ts index 085d93c575f..622ec50ca5e 100644 --- a/packages/ai/src/providers/google-vertex.ts +++ b/packages/ai/src/providers/google-vertex.ts @@ -60,7 +60,7 @@ const route = Route.make({ providerMetadataKey: "vertex", protocol, endpoint: Endpoint.path(({ request }) => { - const model = String(request.model.id) + const model = request.model.id return `/${model.startsWith("endpoints/") ? model : `models/${model}`}:streamGenerateContent?alt=sse` }), auth: Auth.none, @@ -84,7 +84,7 @@ const configuredRoute = (input: Config, modelID: string | ModelID) => { ...rest } = input const apiKey = GoogleVertexShared.apiKey(input) - const endpointModel = String(modelID).startsWith("endpoints/") + const endpointModel = modelID.startsWith("endpoints/") if (apiKey !== undefined && endpointModel) throw new ProviderConfigurationError({ provider: id, diff --git a/packages/ai/src/providers/open-responses-options.ts b/packages/ai/src/providers/open-responses-options.ts index e31f1e3d84d..6fb0ea63483 100644 --- a/packages/ai/src/providers/open-responses-options.ts +++ b/packages/ai/src/providers/open-responses-options.ts @@ -2,5 +2,3 @@ import type { Options } from "../protocols/utils/open-responses-options.js" export type OpenResponsesOptionsInput = Options & { readonly [key: string]: unknown } export type OpenResponsesProviderOptionsInput = OpenResponsesOptionsInput - -export * as OpenResponsesProviderOptions from "./open-responses-options.js" diff --git a/packages/ai/src/providers/openai-compatible.ts b/packages/ai/src/providers/openai-compatible.ts index 7be2101376c..40fa5d50848 100644 --- a/packages/ai/src/providers/openai-compatible.ts +++ b/packages/ai/src/providers/openai-compatible.ts @@ -34,8 +34,7 @@ export const configure = (input: GenericModelOptions) => { }) return { id: ProviderID.make(provider), - model: (modelID: string | ModelID) => - route.model({ id: modelID, provider: ProviderID.make(provider) }), + model: (modelID: string | ModelID) => route.model({ id: modelID }), configure, } } diff --git a/packages/ai/src/providers/openai-options.ts b/packages/ai/src/providers/openai-options.ts index 8b248afe1c2..9bcf36791f3 100644 --- a/packages/ai/src/providers/openai-options.ts +++ b/packages/ai/src/providers/openai-options.ts @@ -62,5 +62,3 @@ export const withOpenAIOptions = = | AuthOverride | (Mode extends "optional" ? OptionalApiKeyAuth : RequiredApiKeyAuth) -export type LanguageModelOptions = Omit & - ProviderAuthOption - -export type LanguageModelArgs = Mode extends "optional" - ? readonly [options?: LanguageModelOptions] - : readonly [options: LanguageModelOptions] - -export type LanguageModelFactory = ( - id: string, - ...args: LanguageModelArgs -) => LanguageModel - /** * Require at least one of the keys in `T`. Use for option shapes where any * subset of fields is acceptable but at least one must be present (e.g. Azure diff --git a/packages/ai/src/route/auth.ts b/packages/ai/src/route/auth.ts index 9a8a4d9a217..e3192903d20 100644 --- a/packages/ai/src/route/auth.ts +++ b/packages/ai/src/route/auth.ts @@ -40,9 +40,6 @@ export interface Definition { readonly pipe: (f: (self: Definition) => A) => A } -export const isAuth = (input: unknown): input is Definition => - typeof input === "object" && input !== null && "apply" in input && typeof input.apply === "function" - const credential = (load: Effect.Effect): Credential => { const self: Credential = { load, @@ -124,8 +121,6 @@ export function bearer(source: Secret | Credential) { return credentialInput(source).bearer() } -export const apiKey = bearer - export function header(name: string): (source: Secret | Credential) => Definition export function header(name: string, source: Secret | Credential): Definition export function header(name: string, source?: Secret | Credential) { diff --git a/packages/ai/src/route/client.ts b/packages/ai/src/route/client.ts index 0cf44a29bce..c8d319018ef 100644 --- a/packages/ai/src/route/client.ts +++ b/packages/ai/src/route/client.ts @@ -94,8 +94,6 @@ export type AnyRoute -export type HttpOptionsInput = HttpOptions.Input - export type RouteLanguageModelInput = Omit export type RouteRoutedLanguageModelInput = Omit @@ -150,7 +148,7 @@ const mergeRouteDefaults = (base: RouteDefaults | undefined, patch: RouteDefault ...base, ...patch, headers, - generation: mergeGenerationOptions(generationOptions(base?.generation), generationOptions(patch.generation)), + generation: mergeGenerationOptions(base?.generation, patch.generation), providerOptions: mergeProviderOptions(base?.providerOptions, patch.providerOptions), http: mergeHttpOptions( base?.http, @@ -168,9 +166,6 @@ const mergeHeaders = (...items: ReadonlyArray | undefined return Object.fromEntries(entries) } -export const generationOptions = (input: GenerationOptions.Input | undefined) => - input === undefined ? undefined : GenerationOptions.make(input) - export interface Interface { readonly compact: CompactMethod readonly stream: StreamMethod @@ -396,8 +391,8 @@ function makeFromTransport( provider: provider ?? routeInput.provider, providerMetadataKey: providerMetadataKey ?? - (provider !== undefined && String(provider) !== String(routeInput.provider) - ? String(provider) + (provider !== undefined && provider !== routeInput.provider + ? provider : routeInput.providerMetadataKey), auth: auth ?? routeInput.auth, endpoint: endpoint ? Endpoint.merge(routeInput.endpoint, endpoint) : routeInput.endpoint, diff --git a/packages/ai/src/route/executor-service.ts b/packages/ai/src/route/executor-service.ts index d81d4e20bb7..20539b6635c 100644 --- a/packages/ai/src/route/executor-service.ts +++ b/packages/ai/src/route/executor-service.ts @@ -26,7 +26,7 @@ export const Service = RequestExecutorService export type Service = RequestExecutorService export const headerDetails = (headers: Headers.Headers) => - Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, String(value)])) + Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, value])) export const responseHttp = (response: HttpClientResponse.HttpClientResponse) => new HttpContext({ diff --git a/packages/ai/test/auth-options.types.ts b/packages/ai/test/auth-options.types.ts index dcebd156e00..fd9fe4332c0 100644 --- a/packages/ai/test/auth-options.types.ts +++ b/packages/ai/test/auth-options.types.ts @@ -1,6 +1,6 @@ import { Config } from "effect" import { Auth } from "../src/route.js" -import type { LanguageModelFactory } from "../src/route/auth-options.js" +import type { ProviderAuthOption } from "../src/route/auth-options.js" import * as OpenAIChat from "../src/protocols/openai-chat.js" import * as AmazonBedrock from "../src/providers/amazon-bedrock.js" import * as Anthropic from "../src/providers/anthropic.js" @@ -27,8 +27,8 @@ type LanguageModel = { } declare const auth: Auth.Definition -declare const optionalAuthModel: LanguageModelFactory -declare const requiredAuthModel: LanguageModelFactory +declare const optionalAuthModel: (id: string, options?: BaseOptions & ProviderAuthOption<"optional">) => LanguageModel +declare const requiredAuthModel: (id: string, options: BaseOptions & ProviderAuthOption<"required">) => LanguageModel const configApiKey = Config.Redacted("OPENAI_API_KEY") OpenAIChat.route.model({ id: "gpt-4.1-mini" })