99 lines
3.0 KiB
TypeScript
99 lines
3.0 KiB
TypeScript
import { Effect, Schema } from "effect"
|
|
import { Route, type RouteDefaultsInput } from "../route/client"
|
|
import { Endpoint } from "../route/endpoint"
|
|
import { Framing } from "../route/framing"
|
|
import { Protocol } from "../route/protocol"
|
|
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
|
|
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<RouteDefaultsInput, "providerOptions"> &
|
|
ProviderAuthOption<"optional"> & {
|
|
readonly baseURL?: string
|
|
readonly providerOptions?: OpenRouterProviderOptionsInput
|
|
}
|
|
|
|
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,
|
|
provider: profile.provider,
|
|
protocol,
|
|
endpoint: Endpoint.path("/chat/completions", { baseURL: profile.baseURL }),
|
|
framing: Framing.sse,
|
|
})
|
|
|
|
export const routes = [route]
|
|
|
|
const configuredRoute = (input: ModelOptions) => {
|
|
const { apiKey: _, auth: _auth, baseURL, ...rest } = input
|
|
return route.with({
|
|
...rest,
|
|
endpoint: { baseURL: baseURL ?? profile.baseURL },
|
|
auth: AuthOptions.bearer(input, "OPENROUTER_API_KEY"),
|
|
})
|
|
}
|
|
|
|
export const configure = (input: ModelOptions = {}) => {
|
|
const route = configuredRoute(input)
|
|
return {
|
|
id,
|
|
model: (modelID: string | ModelID) => route.model({ id: modelID }),
|
|
configure,
|
|
}
|
|
}
|
|
|
|
export const provider = configure()
|
|
export const model = provider.model
|