mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-01 09:54:28 +00:00
fix(ai): typecheck explicit compaction capability
This commit is contained in:
parent
cd7447e834
commit
dbe2998fce
12 changed files with 213 additions and 40 deletions
|
|
@ -323,6 +323,8 @@ The corresponding package entrypoint is `@opencode-ai/ai/providers/amazon-bedroc
|
|||
|
||||
`LLMClient.compact(request)` performs exactly one HTTP call to `/responses/compact`, using the selected route's endpoint, credentials, query, and HTTP middleware. It returns a `CompactionResponse` containing replacement `messages` and usage, not a normal generation response.
|
||||
|
||||
The selected model carries explicit-compaction capability through request construction and updates. Calls using unsupported routes fail type checking. When the model is selected dynamically, narrow the request with `LLMClient.canCompact(request)` before calling `LLMClient.compact`; a model or route switch does not inherit the old capability. Runtime validation still rejects unsupported calls from untyped consumers. Capability describes the route's API, not whether every model or custom deployment supports the operation.
|
||||
|
||||
```ts
|
||||
const compacted = yield * LLMClient.compact(request)
|
||||
const next = LLMRequest.update(request, {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { LanguageModel, ProviderOptions } from "./schema/index.js"
|
||||
import type { CompactOperation } from "./route/client.js"
|
||||
|
||||
export interface Settings extends Readonly<Record<string, unknown>> {
|
||||
readonly baseURL?: string
|
||||
|
|
@ -9,8 +10,9 @@ export interface Settings extends Readonly<Record<string, unknown>> {
|
|||
export interface Definition<
|
||||
ProviderSettings extends Settings = Settings,
|
||||
Options extends ProviderOptions = ProviderOptions,
|
||||
Compact extends CompactOperation | undefined = CompactOperation | undefined,
|
||||
> {
|
||||
readonly model: (modelID: string, settings: ProviderSettings) => LanguageModel<Options>
|
||||
readonly model: (modelID: string, settings: ProviderSettings) => LanguageModel<Options, Compact>
|
||||
}
|
||||
|
||||
export * as ProviderPackage from "./provider-package.js"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Headers } from "effect/unstable/http"
|
||||
import { Auth } from "../route/auth.js"
|
||||
import { type AtLeastOne, type ProviderAuthOption } from "../route/auth-options.js"
|
||||
import type { Route as RouteDef, RouteDefaultsInput } from "../route/client.js"
|
||||
import type { Route, RouteDefaultsInput, CompactOperation } from "../route/client.js"
|
||||
import type { ProviderPackage } from "../provider-package.js"
|
||||
import { ProviderID, type ModelID } from "../schema/index.js"
|
||||
import * as OpenAIChat from "../protocols/openai-chat.js"
|
||||
|
|
@ -102,7 +102,11 @@ const auth = (input: Config) => {
|
|||
)
|
||||
}
|
||||
|
||||
const configuredRoute = <Body, Prepared>(route: RouteDef<Body, Prepared>, input: Config, modelID: string | ModelID) =>
|
||||
const configuredRoute = <Body, Prepared, Compact extends CompactOperation | undefined>(
|
||||
route: Route<Body, Prepared, Compact>,
|
||||
input: Config,
|
||||
modelID: string | ModelID,
|
||||
) =>
|
||||
route.with({
|
||||
auth: auth(input),
|
||||
endpoint: endpoint(input, modelID),
|
||||
|
|
@ -161,10 +165,11 @@ const config = (settings: Settings): Config => {
|
|||
throw new Error("Azure requires resourceName or baseURL")
|
||||
}
|
||||
|
||||
export const responsesModel: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["model"] = (
|
||||
modelID,
|
||||
settings,
|
||||
) => configure(config(settings)).responses(modelID)
|
||||
export const responsesModel: ProviderPackage.Definition<
|
||||
Settings,
|
||||
OpenAIProviderOptionsInput,
|
||||
CompactOperation
|
||||
>["model"] = (modelID, settings) => configure(config(settings)).responses(modelID)
|
||||
export const chatModel: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["model"] = (
|
||||
modelID,
|
||||
settings,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options.js"
|
||||
import type { Route, RouteDefaultsInput } from "../route/client.js"
|
||||
import type { Route, RouteDefaultsInput, CompactOperation } from "../route/client.js"
|
||||
import type { ProviderPackage } from "../provider-package.js"
|
||||
import { HttpOptions, ProviderID, ToolDefinition, mergeHttpOptions, type ModelID } from "../schema/index.js"
|
||||
import * as OpenAIChat from "../protocols/openai-chat.js"
|
||||
|
|
@ -73,7 +73,10 @@ const defaults = (input: Config) => {
|
|||
return rest
|
||||
}
|
||||
|
||||
const configuredRoute = <Body, Prepared>(route: Route<Body, Prepared>, input: Config) =>
|
||||
const configuredRoute = <Body, Prepared, Compact extends CompactOperation | undefined>(
|
||||
route: Route<Body, Prepared, Compact>,
|
||||
input: Config,
|
||||
) =>
|
||||
route.with({
|
||||
auth: auth(input),
|
||||
endpoint: { baseURL: input.baseURL, query: input.queryParams },
|
||||
|
|
@ -129,7 +132,10 @@ const config = (settings: Settings): Config => {
|
|||
}
|
||||
}
|
||||
|
||||
export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["model"] = (modelID, settings) => {
|
||||
export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput, CompactOperation>["model"] = (
|
||||
modelID,
|
||||
settings,
|
||||
) => {
|
||||
return configure(config(settings)).responses(modelID)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options.js"
|
||||
import { Route, type RouteDefaultsInput } from "../route/client.js"
|
||||
import { Route, type RouteDefaultsInput, type CompactOperation } from "../route/client.js"
|
||||
import { Endpoint } from "../route/endpoint.js"
|
||||
import { HttpOptions, ProviderID, type ModelID } from "../schema/index.js"
|
||||
import * as OpenAICompatibleProfiles from "./openai-compatible-profile.js"
|
||||
|
|
@ -103,7 +103,10 @@ export const configure = (input: LanguageModelOptions = {}) => {
|
|||
}
|
||||
|
||||
export const provider = configure()
|
||||
export const model: ProviderPackage.Definition<Settings, XAIProviderOptionsInput>["model"] = (modelID, settings) =>
|
||||
export const model: ProviderPackage.Definition<Settings, XAIProviderOptionsInput, CompactOperation>["model"] = (
|
||||
modelID,
|
||||
settings,
|
||||
) =>
|
||||
configure({
|
||||
apiKey: settings.apiKey,
|
||||
baseURL: settings.baseURL,
|
||||
|
|
|
|||
|
|
@ -35,8 +35,12 @@ export interface RouteBody<Body> {
|
|||
readonly from: (request: LLMRequest) => Effect.Effect<Body, AIError>
|
||||
}
|
||||
|
||||
export interface Route<Body, Prepared = unknown> {
|
||||
readonly compact?: CompactOperation
|
||||
export interface Route<
|
||||
Body,
|
||||
Prepared = unknown,
|
||||
Compact extends CompactOperation | undefined = CompactOperation | undefined,
|
||||
> {
|
||||
readonly compact: Compact
|
||||
readonly id: string
|
||||
readonly provider?: ProviderID
|
||||
/** ProviderMetadata namespace emitted and consumed by this route. */
|
||||
|
|
@ -49,10 +53,10 @@ export interface Route<Body, Prepared = unknown> {
|
|||
readonly transport: Transport<Body, Prepared, unknown>
|
||||
readonly defaults: RouteDefaults
|
||||
readonly body: RouteBody<Body>
|
||||
readonly with: (patch: RoutePatch<Body, Prepared>) => Route<Body, Prepared>
|
||||
readonly with: (patch: RoutePatch<Body, Prepared>) => Route<Body, Prepared, Compact>
|
||||
readonly model: <Options extends ProviderOptions = ProviderOptions>(
|
||||
input: RouteMappedLanguageModelInput,
|
||||
) => LanguageModel<Options>
|
||||
) => LanguageModel<Options, Compact>
|
||||
readonly prepareTransport: (
|
||||
body: Body,
|
||||
request: LLMRequest,
|
||||
|
|
@ -70,7 +74,11 @@ export interface Route<Body, Prepared = unknown> {
|
|||
// Normal call sites use `OpenAIChat.route`; callers only need body types
|
||||
// when preparing a request with a protocol-specific type assertion.
|
||||
// oxlint-disable-next-line typescript-eslint/no-explicit-any
|
||||
export type AnyRoute = Route<any, any>
|
||||
export type AnyRoute<Compact extends CompactOperation | undefined = CompactOperation | undefined> = Route<
|
||||
any,
|
||||
any,
|
||||
Compact
|
||||
>
|
||||
|
||||
export type HttpOptionsInput = HttpOptions.Input
|
||||
|
||||
|
|
@ -103,15 +111,15 @@ export interface RoutePatch<Body, Prepared> extends RouteDefaultsInput {
|
|||
|
||||
type RouteMappedLanguageModelInput = RouteLanguageModelInput | RouteRoutedLanguageModelInput
|
||||
|
||||
const makeRouteLanguageModel = <Options extends ProviderOptions = ProviderOptions>(
|
||||
route: AnyRoute,
|
||||
const makeRouteLanguageModel = <Options extends ProviderOptions, Compact extends CompactOperation | undefined>(
|
||||
route: AnyRoute<Compact>,
|
||||
mapped: RouteMappedLanguageModelInput,
|
||||
) => {
|
||||
const provider = route.provider ?? ("provider" in mapped ? mapped.provider : undefined)
|
||||
if (!provider) throw new Error(`Route.model(${route.id}) requires a provider`)
|
||||
if (!endpointBaseURL(route.endpoint))
|
||||
throw new Error(`Route.model(${route.id}) requires an endpoint baseURL — configure it on the route first`)
|
||||
return LanguageModel.make<Options>({
|
||||
return LanguageModel.make<Options, Compact>({
|
||||
...mapped,
|
||||
provider,
|
||||
route,
|
||||
|
|
@ -155,7 +163,7 @@ export const httpOptions = (input: HttpOptionsInput | undefined) => {
|
|||
|
||||
export interface Interface {
|
||||
readonly compact: (
|
||||
request: LLMRequest,
|
||||
request: CompactionRequest,
|
||||
options?: Pick<StreamOptions, "http">,
|
||||
) => Effect.Effect<CompactionResponse, AIError>
|
||||
readonly stream: StreamMethod
|
||||
|
|
@ -181,6 +189,11 @@ export type CompactOperation = (
|
|||
options?: Pick<StreamOptions, "http">,
|
||||
) => Effect.Effect<CompactionResponse, AIError>
|
||||
|
||||
export type CompactionRequest = LLMRequest<LanguageModel<ProviderOptions, CompactOperation>>
|
||||
|
||||
export const canCompact = (request: LLMRequest): request is CompactionRequest =>
|
||||
request.model.route.compact !== undefined
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/LLMClient") {}
|
||||
|
||||
const resolveRequestOptions = (request: LLMRequest) => {
|
||||
|
|
@ -328,7 +341,7 @@ function makeFromTransport<Body, Prepared, Frame, Event, State>(
|
|||
})
|
||||
},
|
||||
model: <Options extends ProviderOptions = ProviderOptions>(input: RouteMappedLanguageModelInput) =>
|
||||
makeRouteLanguageModel<Options>(route, input),
|
||||
makeRouteLanguageModel<Options, CompactOperation | undefined>(route, input),
|
||||
prepareTransport: (body, request, options) =>
|
||||
routeInput.transport.prepare({
|
||||
body,
|
||||
|
|
@ -425,6 +438,12 @@ function makeFromTransport<Body, Prepared, Frame, Event, State>(
|
|||
return build({ ...input, defaults: mergeRouteDefaults(undefined, input.defaults ?? {}) })
|
||||
}
|
||||
|
||||
export function make<Body, Prepared, Frame, Event, State>(
|
||||
input: MakeTransportInput<Body, Prepared, Frame, Event, State> & { readonly compact: CompactOperation },
|
||||
): Route<Body, Prepared, CompactOperation>
|
||||
export function make<Body, Frame, Event, State>(
|
||||
input: MakeInput<Body, Frame, Event, State> & { readonly compact: CompactOperation },
|
||||
): Route<Body, HttpTransport.HttpPrepared<Frame>, CompactOperation>
|
||||
export function make<Body, Prepared, Frame, Event, State>(
|
||||
input: MakeTransportInput<Body, Prepared, Frame, Event, State>,
|
||||
): Route<Body, Prepared>
|
||||
|
|
@ -537,7 +556,7 @@ export function generate(request: LLMRequest, options?: StreamOptions): Effect.E
|
|||
}
|
||||
|
||||
export const compact = (
|
||||
request: LLMRequest,
|
||||
request: CompactionRequest,
|
||||
options?: Pick<StreamOptions, "http">,
|
||||
): Effect.Effect<CompactionResponse, AIError, Service> =>
|
||||
Effect.gen(function* () {
|
||||
|
|
@ -576,6 +595,7 @@ export const layer: Layer.Layer<Service, never, RequestExecutor.Service> = Layer
|
|||
export const Route = { make } as const
|
||||
|
||||
export const LLMClient = {
|
||||
canCompact,
|
||||
compact,
|
||||
Service,
|
||||
layer,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
HttpOptions,
|
||||
JsonSchema,
|
||||
LanguageModelSchema,
|
||||
type LanguageModel,
|
||||
ProviderOptions,
|
||||
} from "./options.js"
|
||||
import { ProviderID } from "./ids.js"
|
||||
|
|
@ -306,7 +307,7 @@ export namespace ToolChoice {
|
|||
}
|
||||
}
|
||||
|
||||
export class LLMRequest extends Schema.Class<LLMRequest>("LLM.Request")({
|
||||
const requestSchema = Schema.Struct({
|
||||
id: Schema.optional(Schema.String),
|
||||
model: LanguageModelSchema,
|
||||
system: Schema.Array(SystemPart),
|
||||
|
|
@ -320,12 +321,26 @@ export class LLMRequest extends Schema.Class<LLMRequest>("LLM.Request")({
|
|||
// Stable cache affinity for protocols that support provider-managed prompt caching.
|
||||
promptCacheKey: Schema.optional(Schema.String),
|
||||
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
}) {}
|
||||
})
|
||||
|
||||
export class LLMRequest<Model extends LanguageModel = LanguageModel> extends Schema.Class<LLMRequest>("LLM.Request")(
|
||||
requestSchema.fields,
|
||||
) {
|
||||
declare readonly model: Model
|
||||
|
||||
// Preserve model inference instead of inheriting the schema's erased constructor signature.
|
||||
// oxlint-disable-next-line no-useless-constructor
|
||||
constructor(input: LLMRequest.Input<Model>) {
|
||||
super(input)
|
||||
}
|
||||
}
|
||||
|
||||
export namespace LLMRequest {
|
||||
export type Input = ConstructorParameters<typeof LLMRequest>[0]
|
||||
export type Input<Model extends LanguageModel = LanguageModel> = Omit<typeof requestSchema.Type, "model"> & {
|
||||
readonly model: Model
|
||||
}
|
||||
|
||||
export const input = (request: LLMRequest): Input => ({
|
||||
export const input = <Model extends LanguageModel>(request: LLMRequest<Model>): Input<Model> => ({
|
||||
id: request.id,
|
||||
model: request.model,
|
||||
system: request.system,
|
||||
|
|
@ -340,7 +355,16 @@ export namespace LLMRequest {
|
|||
metadata: request.metadata,
|
||||
})
|
||||
|
||||
export const update = (request: LLMRequest, patch: Partial<Input>) => {
|
||||
export function update<Model extends LanguageModel>(
|
||||
request: LLMRequest,
|
||||
patch: Partial<Input<Model>> & { readonly model: Model },
|
||||
): LLMRequest<Model>
|
||||
export function update<Model extends LanguageModel>(
|
||||
request: LLMRequest<Model>,
|
||||
patch: Partial<Omit<Input, "model">> & { readonly model?: undefined },
|
||||
): LLMRequest<Model>
|
||||
export function update(request: LLMRequest, patch: Partial<Input>): LLMRequest
|
||||
export function update(request: LLMRequest, patch: Partial<Input>) {
|
||||
if (Object.keys(patch).length === 0) return request
|
||||
return new LLMRequest({
|
||||
...input(request),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Schema } from "effect"
|
||||
import { ModelID, ProviderID } from "./ids.js"
|
||||
import type { AnyRoute } from "../route/client.js"
|
||||
import type { AnyRoute, CompactOperation } from "../route/client.js"
|
||||
import { isRecord } from "../utils/record.js"
|
||||
|
||||
export const JsonSchema = Schema.Record(Schema.String, Schema.Unknown)
|
||||
|
|
@ -173,15 +173,18 @@ export namespace LanguageModelCompatibility {
|
|||
input instanceof LanguageModelCompatibility ? input : new LanguageModelCompatibility(input)
|
||||
}
|
||||
|
||||
export class LanguageModel<Options extends ProviderOptions = ProviderOptions> {
|
||||
export class LanguageModel<
|
||||
Options extends ProviderOptions = ProviderOptions,
|
||||
Compact extends CompactOperation | undefined = CompactOperation | undefined,
|
||||
> {
|
||||
declare protected readonly _ProviderOptions: Options
|
||||
readonly id: ModelID
|
||||
readonly provider: ProviderID
|
||||
readonly route: AnyRoute
|
||||
readonly route: AnyRoute<Compact>
|
||||
readonly defaults?: LanguageModelDefaults
|
||||
readonly compatibility?: LanguageModelCompatibility
|
||||
|
||||
constructor(input: LanguageModel.ConstructorInput) {
|
||||
constructor(input: LanguageModel.ConstructorInput<Compact>) {
|
||||
this.id = input.id
|
||||
this.provider = input.provider
|
||||
this.route = input.route
|
||||
|
|
@ -189,8 +192,11 @@ export class LanguageModel<Options extends ProviderOptions = ProviderOptions> {
|
|||
this.compatibility = input.compatibility
|
||||
}
|
||||
|
||||
static make<Options extends ProviderOptions = ProviderOptions>(input: LanguageModel.Input) {
|
||||
return new LanguageModel<Options>({
|
||||
static make<
|
||||
Options extends ProviderOptions = ProviderOptions,
|
||||
Compact extends CompactOperation | undefined = CompactOperation | undefined,
|
||||
>(input: LanguageModel.Input<Compact>) {
|
||||
return new LanguageModel<Options, Compact>({
|
||||
id: ModelID.make(input.id),
|
||||
provider: ProviderID.make(input.provider),
|
||||
route: input.route,
|
||||
|
|
@ -200,7 +206,9 @@ export class LanguageModel<Options extends ProviderOptions = ProviderOptions> {
|
|||
})
|
||||
}
|
||||
|
||||
static input<Options extends ProviderOptions>(model: LanguageModel<Options>): LanguageModel.ConstructorInput {
|
||||
static input<Options extends ProviderOptions, Compact extends CompactOperation | undefined>(
|
||||
model: LanguageModel<Options, Compact>,
|
||||
): LanguageModel.ConstructorInput<Compact> {
|
||||
return {
|
||||
id: model.id,
|
||||
provider: model.provider,
|
||||
|
|
@ -210,25 +218,41 @@ export class LanguageModel<Options extends ProviderOptions = ProviderOptions> {
|
|||
}
|
||||
}
|
||||
|
||||
static update<Options extends ProviderOptions, Compact extends CompactOperation | undefined>(
|
||||
model: LanguageModel<Options>,
|
||||
patch: Partial<LanguageModel.Input<Compact>> & { readonly route: AnyRoute<Compact> },
|
||||
): LanguageModel<Options, Compact>
|
||||
static update<Options extends ProviderOptions, Compact extends CompactOperation | undefined>(
|
||||
model: LanguageModel<Options, Compact>,
|
||||
patch: Partial<Omit<LanguageModel.Input, "route">> & { readonly route?: undefined },
|
||||
): LanguageModel<Options, Compact>
|
||||
static update<Options extends ProviderOptions>(
|
||||
model: LanguageModel<Options>,
|
||||
patch: Partial<LanguageModel.Input>,
|
||||
): LanguageModel<Options>
|
||||
static update<Options extends ProviderOptions>(model: LanguageModel<Options>, patch: Partial<LanguageModel.Input>) {
|
||||
if (Object.keys(patch).length === 0) return model
|
||||
return LanguageModel.make<Options>({
|
||||
...LanguageModel.input(model),
|
||||
...patch,
|
||||
route: patch.route ?? model.route,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export namespace LanguageModel {
|
||||
export type ConstructorInput = {
|
||||
export type ConstructorInput<Compact extends CompactOperation | undefined = CompactOperation | undefined> = {
|
||||
readonly id: ModelID
|
||||
readonly provider: ProviderID
|
||||
readonly route: AnyRoute
|
||||
readonly route: AnyRoute<Compact>
|
||||
readonly defaults?: LanguageModelDefaults
|
||||
readonly compatibility?: LanguageModelCompatibility
|
||||
}
|
||||
|
||||
export type Input = Omit<ConstructorInput, "id" | "provider" | "defaults" | "compatibility"> & {
|
||||
export type Input<Compact extends CompactOperation | undefined = CompactOperation | undefined> = Omit<
|
||||
ConstructorInput<Compact>,
|
||||
"id" | "provider" | "defaults" | "compatibility"
|
||||
> & {
|
||||
readonly id: string | ModelID
|
||||
readonly provider: string | ProviderID
|
||||
readonly defaults?: LanguageModelDefaults.Input
|
||||
|
|
|
|||
|
|
@ -1,6 +1,21 @@
|
|||
import { expect, test } from "bun:test"
|
||||
import { Schema } from "effect"
|
||||
import { CompactionPart, LLMEvent, LLMResponse, Message, ProviderID } from "../src/schema/index.js"
|
||||
import { LLM, LLMClient, LLMRequest, LanguageModel } from "../src/index.js"
|
||||
import { OpenAI, Anthropic } from "../src/providers.js"
|
||||
|
||||
test("runtime capability checks follow model and route updates", () => {
|
||||
const supported = OpenAI.configure({ apiKey: "test" }).responses("fixture")
|
||||
const unsupported = Anthropic.configure({ apiKey: "test" }).model("fixture")
|
||||
const request = LLM.request({ model: supported, prompt: "hello" })
|
||||
expect(LLMClient.canCompact(request)).toBe(true)
|
||||
expect(LLMClient.canCompact(LLMRequest.update(request, { messages: [] }))).toBe(true)
|
||||
expect(LLMClient.canCompact(LLMRequest.update(request, { model: unsupported }))).toBe(false)
|
||||
expect(
|
||||
LLMClient.canCompact(LLM.request({ model: LanguageModel.update(supported, { route: unsupported.route }) })),
|
||||
).toBe(false)
|
||||
expect(LLMClient.canCompact(LLM.request({ model: LanguageModel.update(supported, { route: undefined }) }))).toBe(true)
|
||||
})
|
||||
|
||||
test("compaction survives event assembly and message serialization without becoming text", () => {
|
||||
const part = CompactionPart.make({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,23 @@
|
|||
import { Effect } from "effect"
|
||||
import { CompactionPart, LLM, LLMClient, LLMEvent, Message, ProviderID } from "../../src/index.js"
|
||||
import { OpenAI, Anthropic, AmazonBedrock } from "../../src/providers.js"
|
||||
import {
|
||||
CompactionPart,
|
||||
LanguageModel,
|
||||
LLM,
|
||||
LLMClient,
|
||||
LLMEvent,
|
||||
LLMRequest,
|
||||
Message,
|
||||
ProviderID,
|
||||
} from "../../src/index.js"
|
||||
import {
|
||||
OpenAI,
|
||||
Azure,
|
||||
XAI,
|
||||
Anthropic,
|
||||
AmazonBedrock,
|
||||
AmazonBedrockMantle,
|
||||
OpenAICompatibleResponses,
|
||||
} from "../../src/providers.js"
|
||||
|
||||
const openai = OpenAI.configure({
|
||||
apiKey: "test",
|
||||
|
|
@ -8,6 +25,59 @@ const openai = OpenAI.configure({
|
|||
}).responses("gpt-5.3-codex")
|
||||
LLMClient.compact(LLM.request({ model: openai, prompt: "hello" }))
|
||||
|
||||
for (const model of [
|
||||
OpenAI.configure().responses("fixture"),
|
||||
Azure.configure({ resourceName: "test" }).responses("fixture"),
|
||||
XAI.configure().responses("fixture"),
|
||||
OpenAI.model("fixture", {}),
|
||||
Azure.responsesModel("fixture", { resourceName: "test" }),
|
||||
XAI.model("fixture", {}),
|
||||
openai.route.with({ headers: { "x-test": "test" } }).model({ id: "fixture" }),
|
||||
LanguageModel.update(openai, { defaults: { generation: { maxTokens: 100 } } }),
|
||||
LanguageModel.make(LanguageModel.input(openai)),
|
||||
]) {
|
||||
LLMClient.compact(LLM.request({ model, prompt: "hello" }))
|
||||
}
|
||||
|
||||
for (const model of [
|
||||
Anthropic.configure().model("fixture"),
|
||||
OpenAI.configure().chat("fixture"),
|
||||
Azure.configure({ resourceName: "test" }).chat("fixture"),
|
||||
XAI.configure().chat("fixture"),
|
||||
AmazonBedrock.configure().model("fixture"),
|
||||
AmazonBedrock.configure().messages("fixture"),
|
||||
AmazonBedrockMantle.configure().responses("fixture"),
|
||||
OpenAICompatibleResponses.configure({ baseURL: "https://example.com" }).model("fixture"),
|
||||
]) {
|
||||
// @ts-expect-error This route does not guarantee an explicit compaction endpoint.
|
||||
LLMClient.compact(LLM.request({ model, prompt: "hello" }))
|
||||
LLMClient.Service.use((client) => {
|
||||
// @ts-expect-error The service enforces the same capability as the convenience function.
|
||||
return client.compact(LLM.request({ model, prompt: "hello" }))
|
||||
})
|
||||
}
|
||||
|
||||
const request = LLM.request({ model: openai, prompt: "hello" })
|
||||
LLMClient.compact(LLMRequest.update(request, { messages: [Message.user("continue")] }))
|
||||
LLMClient.compact(new LLMRequest(LLMRequest.input(request)))
|
||||
const switched = LLMRequest.update(request, { model: Anthropic.configure().model("fixture") })
|
||||
// @ts-expect-error Switching models replaces, rather than inherits, the capability.
|
||||
LLMClient.compact(switched)
|
||||
LLMClient.compact(LLMRequest.update(switched, { model: openai }))
|
||||
LLMClient.compact(
|
||||
// @ts-expect-error Replacing the route also replaces compaction capability.
|
||||
LLM.request({ model: LanguageModel.update(openai, { route: Anthropic.configure().model("fixture").route }) }),
|
||||
)
|
||||
|
||||
declare const dynamicModel: LanguageModel
|
||||
declare const dynamicPatch: Partial<LLMRequest.Input>
|
||||
const dynamicRequest = LLM.request({ model: dynamicModel, prompt: "hello" })
|
||||
// @ts-expect-error A dynamically selected model must be narrowed first.
|
||||
LLMClient.compact(dynamicRequest)
|
||||
if (LLMClient.canCompact(dynamicRequest)) LLMClient.compact(dynamicRequest)
|
||||
// @ts-expect-error An optional model override cannot retain the old capability statically.
|
||||
LLMClient.compact(LLMRequest.update(request, dynamicPatch))
|
||||
|
||||
const checkpoint = CompactionPart.make({ provider: ProviderID.make("openai"), id: "cmp_1", encrypted: "opaque" })
|
||||
const provider = ProviderID.make("anthropic")
|
||||
CompactionPart.make({ provider, text: "summary" })
|
||||
|
|
|
|||
|
|
@ -357,6 +357,7 @@ for (const model of [
|
|||
`${model.route.id} does not inherit an unsupported compact endpoint`,
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
// @ts-expect-error Untyped callers must still receive the runtime capability error.
|
||||
const error = yield* LLMClient.compact(LLM.request({ model, prompt: "hello" })).pipe(Effect.flip)
|
||||
expect(error.reason._tag).toBe("InvalidRequest")
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -303,6 +303,7 @@ function modelFromLanguage(info: Info, language: LanguageModelV3) {
|
|||
const projected = mapBodyToProviderOptions(info, packageName)
|
||||
const optionKey = providerOptionKey(packageName, info.providerID)
|
||||
const route: AnyRoute = {
|
||||
compact: undefined,
|
||||
id: `ai-sdk:${packageName}`,
|
||||
provider: ProviderID.make(info.providerID),
|
||||
providerMetadataKey: optionKey,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue