refactor(ai): align multimodal naming (#40073)
This commit is contained in:
parent
5d729521d3
commit
634b9b21dd
91 changed files with 563 additions and 500 deletions
|
|
@ -18,8 +18,8 @@ import {
|
|||
FinishReason,
|
||||
InvalidProviderOutputReason,
|
||||
LLMEvent,
|
||||
LLMError,
|
||||
Model,
|
||||
AIError,
|
||||
LanguageModel,
|
||||
ProviderID,
|
||||
ProviderMetadata,
|
||||
ToolResultValue,
|
||||
|
|
@ -182,7 +182,7 @@ export interface Interface {
|
|||
readonly runSDK: (event: SDKEvent) => Effect.Effect<SDKEvent>
|
||||
readonly runLanguage: (event: LanguageEvent) => Effect.Effect<LanguageEvent>
|
||||
readonly language: (model: Info) => Effect.Effect<LanguageModelV3, InitError>
|
||||
readonly model: (model: Info) => Effect.Effect<Model, InitError>
|
||||
readonly model: (model: Info) => Effect.Effect<LanguageModel, InitError>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/AISDK") {}
|
||||
|
|
@ -338,11 +338,12 @@ function modelFromLanguage(info: Info, language: LanguageModelV3) {
|
|||
from: (request) => Effect.succeed(callOptions(request)),
|
||||
},
|
||||
with: () => route,
|
||||
model: (input) => Model.make({ ...input, provider: "provider" in input ? input.provider : info.providerID, route }),
|
||||
model: (input) =>
|
||||
LanguageModel.make({ ...input, provider: "provider" in input ? input.provider : info.providerID, route }),
|
||||
prepareTransport: (body) => Effect.succeed(body),
|
||||
streamPrepared: (prepared) => streamLanguage(language, prepared as LanguageModelV3CallOptions),
|
||||
}
|
||||
return Model.make({
|
||||
return LanguageModel.make({
|
||||
id: info.modelID ?? info.id,
|
||||
provider: info.providerID,
|
||||
route,
|
||||
|
|
@ -555,7 +556,7 @@ function streamLanguage(language: LanguageModelV3, options: LanguageModelV3CallO
|
|||
function streamPartEvents(
|
||||
state: { step: number; toolNames: Record<string, string> },
|
||||
event: LanguageModelV3StreamPart,
|
||||
): Effect.Effect<ReadonlyArray<LLMEvent>, LLMError> {
|
||||
): Effect.Effect<ReadonlyArray<LLMEvent>, AIError> {
|
||||
switch (event.type) {
|
||||
case "stream-start":
|
||||
case "response-metadata":
|
||||
|
|
@ -720,10 +721,10 @@ function messageValue(input: unknown) {
|
|||
|
||||
function llmError(method: string, error: unknown) {
|
||||
const reason =
|
||||
error instanceof LLMError
|
||||
error instanceof AIError
|
||||
? new InvalidProviderOutputReason({ message: error.message })
|
||||
: new UnknownProviderReason({ message: error instanceof Error ? error.message : String(error) })
|
||||
return new LLMError({
|
||||
return new AIError({
|
||||
module: "AISDK",
|
||||
method,
|
||||
reason,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export * as Generate from "./generate"
|
||||
|
||||
import { LLM, LLMClient, LLMError } from "@opencode-ai/ai"
|
||||
import { LLM, LLMClient, AIError } from "@opencode-ai/ai"
|
||||
import { Context, Effect, Layer, Schema } from "effect"
|
||||
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
||||
import { llmClient } from "./effect/app-node-platform"
|
||||
|
|
@ -57,7 +57,7 @@ export const layer = Layer.effect(
|
|||
})
|
||||
const response = yield* llm.generate(LLM.request({ model: resolved.model, prompt: input.prompt })).pipe(
|
||||
Effect.mapError(
|
||||
(error: LLMError) =>
|
||||
(error: AIError) =>
|
||||
new UnavailableError({
|
||||
message: error.message,
|
||||
service: resolved.ref.providerID,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
export * as ModelResolver from "./model-resolver"
|
||||
|
||||
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
||||
import { Model } from "@opencode-ai/ai"
|
||||
import { LanguageModel } from "@opencode-ai/ai"
|
||||
// ast-grep-ignore: no-star-import
|
||||
import * as AnthropicMessages from "@opencode-ai/ai/protocols/anthropic-messages"
|
||||
// ast-grep-ignore: no-star-import
|
||||
|
|
@ -50,7 +50,7 @@ export type Error = VariantUnavailableError | UnsupportedPackageError | Integrat
|
|||
|
||||
export interface Resolved {
|
||||
/** Route-level model for provider requests; its id is the provider API model id, which may differ from the catalog id. */
|
||||
readonly model: Model
|
||||
readonly model: LanguageModel
|
||||
/** Selected catalog identity. Durable records and displays must use this, never the API model id. */
|
||||
readonly ref: Ref
|
||||
/** Catalog capabilities used to shape requests before provider lowering. */
|
||||
|
|
@ -134,14 +134,14 @@ export const withVariant = (
|
|||
|
||||
export interface Dependencies {
|
||||
readonly loadPackage?: (specifier: string) => Effect.Effect<Provider.ProviderPackage, Provider.LoadError>
|
||||
readonly loadAISDK?: (model: Info) => Effect.Effect<Model, AISDK.InitError>
|
||||
readonly loadAISDK?: (model: Info) => Effect.Effect<LanguageModel, AISDK.InitError>
|
||||
}
|
||||
|
||||
export const fromCatalogModel = (
|
||||
model: Info,
|
||||
credential?: Credential.Value,
|
||||
dependencies?: Dependencies,
|
||||
): Effect.Effect<Model, UnsupportedPackageError> => {
|
||||
): Effect.Effect<LanguageModel, UnsupportedPackageError> => {
|
||||
const resolved = produce(model, (draft) => {
|
||||
if (draft.settings?.apiKey === "") delete draft.settings.apiKey
|
||||
if (credential?.type === "key" && credential.metadata !== undefined)
|
||||
|
|
@ -207,7 +207,7 @@ export const fromCatalogModel = (
|
|||
return yield* Effect.try({
|
||||
try: () => {
|
||||
const runtime = module.model(resolved.modelID ?? resolved.id, settings)
|
||||
return Model.update(runtime, {
|
||||
return LanguageModel.update(runtime, {
|
||||
provider: resolved.providerID,
|
||||
compatibility: resolved.compatibility
|
||||
? Object.assign({}, runtime.compatibility, resolved.compatibility)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export * as SessionCompaction from "./compaction"
|
||||
|
||||
import { LLM, LLMClient, LLMError, LLMEvent, Message, type LLMRequest, type Model } from "@opencode-ai/ai"
|
||||
import { LLM, LLMClient, AIError, LLMEvent, Message, type LLMRequest, type LanguageModel } from "@opencode-ai/ai"
|
||||
import { SessionError } from "@opencode-ai/schema/session-error"
|
||||
import { Context, Effect, Layer, Stream } from "effect"
|
||||
import { Config } from "../config"
|
||||
|
|
@ -64,7 +64,7 @@ type Dependencies = {
|
|||
readonly app: App.Info
|
||||
readonly bus: Bus.Interface
|
||||
readonly llm: {
|
||||
readonly stream: (request: LLMRequest) => Stream.Stream<LLMEvent, LLMError>
|
||||
readonly stream: (request: LLMRequest) => Stream.Stream<LLMEvent, AIError>
|
||||
}
|
||||
readonly models: SessionRunnerModel.Interface
|
||||
readonly config: Settings
|
||||
|
|
@ -73,7 +73,7 @@ type Dependencies = {
|
|||
export type AutoInput = {
|
||||
readonly session: SessionSchema.Info
|
||||
readonly messages: readonly SessionMessage.Info[]
|
||||
readonly model: Model
|
||||
readonly model: LanguageModel
|
||||
readonly cost: Info["cost"]
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ export type ManualInput = {
|
|||
|
||||
type Plan = {
|
||||
readonly session: SessionSchema.Info
|
||||
readonly model: Model
|
||||
readonly model: LanguageModel
|
||||
readonly cost: Info["cost"]
|
||||
readonly reason: SessionMessage.Compaction["reason"]
|
||||
readonly prompt: string
|
||||
|
|
@ -282,7 +282,7 @@ const make = (dependencies: Dependencies) => {
|
|||
}
|
||||
return Effect.void
|
||||
}),
|
||||
Effect.catchTag("LLM.Error", (error) =>
|
||||
Effect.catchTag("AI.Error", (error) =>
|
||||
Effect.sync(() => {
|
||||
failure = toSessionError(error)
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
export * as SessionGenerate from "./generate"
|
||||
|
||||
import type { LLMError } from "@opencode-ai/ai"
|
||||
import type { AIError } from "@opencode-ai/ai"
|
||||
import { Context, type Effect } from "effect"
|
||||
import type { Instructions } from "../instructions"
|
||||
import type { AgentNotFoundError } from "./error"
|
||||
import type { SessionRunnerModel } from "./runner/model"
|
||||
import type { SessionSchema } from "./schema"
|
||||
|
||||
export type Error = AgentNotFoundError | Instructions.InitializationBlocked | SessionRunnerModel.Error | LLMError
|
||||
export type Error = AgentNotFoundError | Instructions.InitializationBlocked | SessionRunnerModel.Error | AIError
|
||||
|
||||
export interface Interface {
|
||||
/** Generates text from current Session context without mutating the Session. */
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export * as SessionRunner from "./index"
|
||||
|
||||
import type { LLMError } from "@opencode-ai/ai"
|
||||
import type { AIError } from "@opencode-ai/ai"
|
||||
import { Context, Effect } from "effect"
|
||||
import { SessionSchema } from "../schema"
|
||||
import type { AgentNotFoundError, MessageDecodeError, StepFailedError, UserInterruptedError } from "../error"
|
||||
|
|
@ -8,7 +8,7 @@ import { SessionRunnerModel } from "./model"
|
|||
import type { Instructions } from "../../instructions/index"
|
||||
|
||||
export type RunError =
|
||||
| LLMError
|
||||
| AIError
|
||||
| SessionRunnerModel.Error
|
||||
| MessageDecodeError
|
||||
| AgentNotFoundError
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
export * as SessionRunnerLLM from "./llm"
|
||||
|
||||
import { LLMClient, LLMError, LLMEvent, isContextOverflowFailure, type ProviderErrorEvent, type ToolCall } from "@opencode-ai/ai"
|
||||
import {
|
||||
LLMClient,
|
||||
AIError,
|
||||
LLMEvent,
|
||||
isContextOverflowFailure,
|
||||
type ProviderErrorEvent,
|
||||
type ToolCall,
|
||||
} from "@opencode-ai/ai"
|
||||
import { Cause, Data, Effect, Exit, Fiber, FiberSet, Layer, Option, Pull, Schedule, Stream } from "effect"
|
||||
import { Database } from "../../database/database"
|
||||
import { Bus } from "../../bus"
|
||||
|
|
@ -60,16 +67,20 @@ const classifyToolExits = (
|
|||
: [],
|
||||
)
|
||||
const causes =
|
||||
settled._tag === "Failure" ? [settled.cause] : exits.flatMap((exit) => (exit._tag === "Failure" ? [exit.cause] : []))
|
||||
settled._tag === "Failure"
|
||||
? [settled.cause]
|
||||
: exits.flatMap((exit) => (exit._tag === "Failure" ? [exit.cause] : []))
|
||||
// The first non-interrupt, non-decline failure, rebuilt without decline reasons so the
|
||||
// drain's error channel never carries a decline.
|
||||
const failure = causes.flatMap((cause) => {
|
||||
if (Cause.hasInterrupts(cause)) return []
|
||||
const reasons = cause.reasons.flatMap((reason): Array<Cause.Reason<never>> =>
|
||||
Cause.isFailReason(reason) ? [] : [reason],
|
||||
)
|
||||
return reasons.length > 0 ? [Cause.fromReasons(reasons)] : []
|
||||
}).at(0)
|
||||
const failure = causes
|
||||
.flatMap((cause) => {
|
||||
if (Cause.hasInterrupts(cause)) return []
|
||||
const reasons = cause.reasons.flatMap(
|
||||
(reason): Array<Cause.Reason<never>> => (Cause.isFailReason(reason) ? [] : [reason]),
|
||||
)
|
||||
return reasons.length > 0 ? [Cause.fromReasons(reasons)] : []
|
||||
})
|
||||
.at(0)
|
||||
return {
|
||||
interrupted: causes.some(Cause.hasInterrupts),
|
||||
declines,
|
||||
|
|
@ -356,9 +367,14 @@ const layer = Layer.effect(
|
|||
if (overflowFailure) yield* publisher.publish(overflowFailure)
|
||||
// A thrown LLM failure not already recorded as the provider error either
|
||||
// escapes as a scheduled retry or fails the assistant durably.
|
||||
const llmFailure = streamFailure instanceof LLMError ? streamFailure : undefined
|
||||
const llmFailure = streamFailure instanceof AIError ? streamFailure : undefined
|
||||
const llmError = llmFailure && !publisher.record().providerFailed ? toSessionError(llmFailure) : undefined
|
||||
if (llmFailure && llmError && SessionRunnerRetry.isRetryable(llmFailure) && !publisher.record().outputStarted) {
|
||||
if (
|
||||
llmFailure &&
|
||||
llmError &&
|
||||
SessionRunnerRetry.isRetryable(llmFailure) &&
|
||||
!publisher.record().outputStarted
|
||||
) {
|
||||
// RetryScheduled and Step.Failed fold onto an existing assistant message, so
|
||||
// Step.Started must be durable before the failure escapes.
|
||||
yield* publisher.startAssistant()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
export * as SessionRunnerModel from "./model"
|
||||
|
||||
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
||||
import { Model } from "@opencode-ai/ai"
|
||||
import { LanguageModel } from "@opencode-ai/ai"
|
||||
import { Context, Effect, Layer, Schema } from "effect"
|
||||
import { Catalog } from "../../catalog"
|
||||
import { ModelResolver } from "../../model-resolver"
|
||||
|
|
@ -42,7 +42,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Se
|
|||
|
||||
/** Builds a Resolved whose catalog identity mirrors the route model. Test or embedding seam. */
|
||||
export const resolved = (
|
||||
model: Model,
|
||||
model: LanguageModel,
|
||||
options: {
|
||||
readonly capabilities: Capabilities
|
||||
readonly variant?: VariantID
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export * as SessionRunnerRetry from "./retry"
|
||||
|
||||
import { LLMError } from "@opencode-ai/ai"
|
||||
import { AIError } from "@opencode-ai/ai"
|
||||
import { SessionError } from "@opencode-ai/schema/session-error"
|
||||
import { Data, Duration, Effect, Schedule } from "effect"
|
||||
import { Bus } from "../../bus"
|
||||
|
|
@ -9,12 +9,12 @@ import { SessionMessage } from "../message"
|
|||
import { SessionSchema } from "../schema"
|
||||
|
||||
export class RetryableFailure extends Data.TaggedError("SessionRunner.RetryableFailure")<{
|
||||
readonly cause: LLMError
|
||||
readonly cause: AIError
|
||||
readonly error: SessionError.Error
|
||||
readonly step: number
|
||||
}> {}
|
||||
|
||||
export function isRetryable(error: LLMError) {
|
||||
export function isRetryable(error: AIError) {
|
||||
switch (error.reason._tag) {
|
||||
case "RateLimit":
|
||||
case "ProviderInternal":
|
||||
|
|
@ -41,7 +41,11 @@ const retryAfter = (failure: RetryableFailure) => {
|
|||
return undefined
|
||||
}
|
||||
|
||||
export const schedule = (bus: Bus.Interface, sessionID: SessionSchema.ID, assistantMessageID: () => SessionMessage.ID) =>
|
||||
export const schedule = (
|
||||
bus: Bus.Interface,
|
||||
sessionID: SessionSchema.ID,
|
||||
assistantMessageID: () => SessionMessage.ID,
|
||||
) =>
|
||||
Schedule.max([Schedule.exponential("2 seconds"), Schedule.recurs(4)]).pipe(
|
||||
Schedule.setInputType<RetryableFailure>(),
|
||||
Schedule.modifyDelay(({ input: failure, duration: delay }) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export * as SessionTitle from "./title"
|
||||
|
||||
import { LLM, LLMClient, LLMError, LLMEvent, Message, type LLMRequest } from "@opencode-ai/ai"
|
||||
import { LLM, LLMClient, AIError, LLMEvent, Message, type LLMRequest } from "@opencode-ai/ai"
|
||||
import { Context, DateTime, Effect, Layer, Stream } from "effect"
|
||||
import { Agent } from "../agent"
|
||||
import { Database } from "../database/database"
|
||||
|
|
@ -24,7 +24,7 @@ type Dependencies = {
|
|||
readonly app: App.Info
|
||||
readonly bus: Bus.Interface
|
||||
readonly llm: {
|
||||
readonly stream: (request: LLMRequest) => Stream.Stream<LLMEvent, LLMError>
|
||||
readonly stream: (request: LLMRequest) => Stream.Stream<LLMEvent, AIError>
|
||||
}
|
||||
readonly agents: Agent.Interface
|
||||
readonly models: SessionRunnerModel.Interface
|
||||
|
|
@ -97,7 +97,7 @@ const make = (dependencies: Dependencies) => {
|
|||
return Effect.void
|
||||
}),
|
||||
Effect.as(true),
|
||||
Effect.catchTag("LLM.Error", () => Effect.succeed(false)),
|
||||
Effect.catchTag("AI.Error", () => Effect.succeed(false)),
|
||||
Effect.onInterrupt(() => recordUsage.pipe(Effect.asVoid)),
|
||||
)
|
||||
yield* recordUsage
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { LLMError, ToolFailure } from "@opencode-ai/ai"
|
||||
import { AIError, ToolFailure } from "@opencode-ai/ai"
|
||||
import { Tool } from "@opencode-ai/schema/tool"
|
||||
import { SessionError } from "@opencode-ai/schema/session-error"
|
||||
import { Permission } from "../permission"
|
||||
|
|
@ -8,7 +8,7 @@ import { AgentNotFoundError, StepFailedError, UserInterruptedError } from "./err
|
|||
import { SessionRunnerModel } from "./runner/model"
|
||||
|
||||
export function toSessionError(cause: unknown): SessionError.Error {
|
||||
if (cause instanceof LLMError) {
|
||||
if (cause instanceof AIError) {
|
||||
switch (cause.reason._tag) {
|
||||
case "RateLimit":
|
||||
return providerError("provider.rate-limit", cause.reason)
|
||||
|
|
@ -59,7 +59,7 @@ export function toSessionError(cause: unknown): SessionError.Error {
|
|||
return { type: "unknown", message: cause instanceof Error ? cause.message : String(cause) }
|
||||
}
|
||||
|
||||
function providerError(type: string, reason: LLMError["reason"]): SessionError.Error {
|
||||
function providerError(type: string, reason: AIError["reason"]): SessionError.Error {
|
||||
const status =
|
||||
("http" in reason ? reason.http?.response?.status : undefined) ?? ("status" in reason ? reason.status : undefined)
|
||||
return { type, message: reason.message, ...(status === undefined ? {} : { status }) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { LanguageModelV3, LanguageModelV3StreamPart } from "@ai-sdk/provide
|
|||
import { AISDK } from "@opencode-ai/core/aisdk"
|
||||
import { Model } from "@opencode-ai/core/model"
|
||||
import { Provider } from "@opencode-ai/core/provider"
|
||||
import { LLM, LLMError, LLMEvent, Message } from "@opencode-ai/ai"
|
||||
import { LLM, AIError, LLMEvent, Message } from "@opencode-ai/ai"
|
||||
import { LLMClient, RequestExecutor } from "@opencode-ai/ai/route"
|
||||
import { compileRequest } from "@opencode-ai/ai/route/client"
|
||||
import { expect } from "bun:test"
|
||||
|
|
@ -316,7 +316,7 @@ it.effect("keeps malformed provider-executed AI SDK input terminal", () =>
|
|||
Effect.flip,
|
||||
)
|
||||
|
||||
expect(error).toBeInstanceOf(LLMError)
|
||||
expect(error).toBeInstanceOf(AIError)
|
||||
expect(error.message).toContain("Invalid JSON input for aisdk tool call web_search")
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { expect } from "bun:test"
|
||||
import { Model } from "@opencode-ai/ai"
|
||||
import { LanguageModel } from "@opencode-ai/ai"
|
||||
import { OpenAIChat } from "@opencode-ai/ai/protocols"
|
||||
import { TestLLM } from "@opencode-ai/ai/testing"
|
||||
import { AISDK } from "@opencode-ai/core/aisdk"
|
||||
|
|
@ -17,7 +17,7 @@ const selected = Info.make({
|
|||
...Info.default(Provider.ID.make("test-provider"), ID.make("gemini")),
|
||||
package: Provider.aisdk("@ai-sdk/mistral"),
|
||||
})
|
||||
const runtime = Model.make({ id: "gemini", provider: "test-provider", route: OpenAIChat.route })
|
||||
const runtime = LanguageModel.make({ id: "gemini", provider: "test-provider", route: OpenAIChat.route })
|
||||
|
||||
const catalog = Layer.mock(Catalog.Service, {
|
||||
provider: {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect } from "bun:test"
|
||||
import { LLM, Model } from "@opencode-ai/ai"
|
||||
import { LLM, LanguageModel } from "@opencode-ai/ai"
|
||||
import { OpenAIChat } from "@opencode-ai/ai/protocols"
|
||||
import { compileRequest } from "@opencode-ai/ai/route/client"
|
||||
import { Effect } from "effect"
|
||||
|
|
@ -439,7 +439,7 @@ describe("ModelResolver", () => {
|
|||
body: { custom: true },
|
||||
limits: { context: 100, output: 20 },
|
||||
})
|
||||
return Model.make({ id: modelID, provider: "package-provider", route: native.route })
|
||||
return LanguageModel.make({ id: modelID, provider: "package-provider", route: native.route })
|
||||
},
|
||||
})
|
||||
},
|
||||
|
|
@ -481,7 +481,7 @@ describe("ModelResolver", () => {
|
|||
model: (modelID, settings) => {
|
||||
expect(settings).toMatchObject({ [key]: "oauth-token" })
|
||||
expect(settings).not.toHaveProperty("apiKey")
|
||||
return Model.make({ id: modelID, provider: "package-provider", route: native.route })
|
||||
return LanguageModel.make({ id: modelID, provider: "package-provider", route: native.route })
|
||||
},
|
||||
}),
|
||||
}),
|
||||
|
|
@ -536,7 +536,7 @@ describe("ModelResolver", () => {
|
|||
limits: { context: 100, output: 20 },
|
||||
providerOptions,
|
||||
})
|
||||
return Model.make({ id: modelID, provider: "native-provider", route: native.route })
|
||||
return LanguageModel.make({ id: modelID, provider: "native-provider", route: native.route })
|
||||
},
|
||||
})
|
||||
},
|
||||
|
|
@ -571,7 +571,7 @@ describe("ModelResolver", () => {
|
|||
transforms: ["middle-out"],
|
||||
provider: { sort: "price", only: ["anthropic"] },
|
||||
})
|
||||
return Model.make({ id: modelID, provider: "openrouter", route: OpenAIChat.route })
|
||||
return LanguageModel.make({ id: modelID, provider: "openrouter", route: OpenAIChat.route })
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
|
@ -632,7 +632,7 @@ describe("ModelResolver", () => {
|
|||
headers: { "x-aisdk": "header" },
|
||||
body: { custom: true },
|
||||
})
|
||||
return Model.make({
|
||||
return LanguageModel.make({
|
||||
id: runtime.modelID ?? runtime.id,
|
||||
provider: runtime.providerID,
|
||||
route: native.route,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { GenerationOptions, LLM, LLMRequest, Message, Model, ToolDefinition } from "@opencode-ai/ai"
|
||||
import { GenerationOptions, LLM, LLMRequest, Message, LanguageModel, ToolDefinition } from "@opencode-ai/ai"
|
||||
import { OpenAIChat } from "@opencode-ai/ai/protocols"
|
||||
import { PromptCacheDiagnostics } from "@opencode-ai/core/session/prompt-cache-diagnostics"
|
||||
|
||||
const model = Model.make({ id: "test", provider: "test", route: OpenAIChat.route })
|
||||
const model = LanguageModel.make({ id: "test", provider: "test", route: OpenAIChat.route })
|
||||
const tool = ToolDefinition.make({
|
||||
name: "read",
|
||||
description: "Read a file",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect } from "bun:test"
|
||||
import { LLMClient, LLMEvent, Model, type LLMRequest } from "@opencode-ai/ai"
|
||||
import { LLMClient, LLMEvent, LanguageModel, type LLMRequest } from "@opencode-ai/ai"
|
||||
import { OpenAIChat } from "@opencode-ai/ai/protocols"
|
||||
import { Config } from "@opencode-ai/core/config"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
|
|
@ -25,7 +25,7 @@ import { DateTime, Effect, Layer, LayerMap, Stream } from "effect"
|
|||
import { testEffect } from "./lib/effect"
|
||||
|
||||
const location = Location.Ref.make({ directory: AbsolutePath.make("/project") })
|
||||
const model = Model.make({
|
||||
const model = LanguageModel.make({
|
||||
id: "summary-model",
|
||||
provider: "test",
|
||||
route: OpenAIChat.route.with({ limits: { context: 10_000, output: 1_000 } }),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { expect, test } from "bun:test"
|
||||
import { LLMClient, LLMEvent, Model, type LLMRequest } from "@opencode-ai/ai"
|
||||
import { LLMClient, LLMEvent, LanguageModel, type LLMRequest } from "@opencode-ai/ai"
|
||||
import { OpenAIChat } from "@opencode-ai/ai/protocols"
|
||||
import { Config } from "@opencode-ai/core/config"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
|
|
@ -28,7 +28,7 @@ import { asc, eq } from "drizzle-orm"
|
|||
import { testEffect } from "./lib/effect"
|
||||
|
||||
let requests: LLMRequest[] = []
|
||||
const model = Model.make({
|
||||
const model = LanguageModel.make({
|
||||
id: "summary-model",
|
||||
provider: "test",
|
||||
route: OpenAIChat.route.with({ limits: { context: 10_000, output: 1_000 } }),
|
||||
|
|
@ -145,7 +145,7 @@ it.effect("auto compaction reserves a buffer below the prompt ceiling", () =>
|
|||
})
|
||||
const input = (tokens: number, limits: { context: number; input?: number; output: number }) => ({
|
||||
session,
|
||||
model: Model.make({
|
||||
model: LanguageModel.make({
|
||||
id: "test-model",
|
||||
provider: "test-provider",
|
||||
route: OpenAIChat.route.with({ limits }),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import {
|
|||
ContentPolicyReason,
|
||||
InvalidProviderOutputReason,
|
||||
InvalidRequestReason,
|
||||
LLMError,
|
||||
AIError,
|
||||
NoRouteReason,
|
||||
ModelID,
|
||||
ProviderID,
|
||||
|
|
@ -23,10 +23,10 @@ import { Tool } from "@opencode-ai/schema/tool"
|
|||
import { toSessionError } from "@opencode-ai/core/session/to-session-error"
|
||||
import { SessionRunnerRetry } from "@opencode-ai/core/session/runner/retry"
|
||||
|
||||
const llm = (reason: LLMError["reason"]) => new LLMError({ module: "test", method: "stream", reason })
|
||||
const llm = (reason: AIError["reason"]) => new AIError({ module: "test", method: "stream", reason })
|
||||
|
||||
describe("toSessionError", () => {
|
||||
test("maps every LLM reason to the open wire type", () => {
|
||||
test("maps every AI error reason to the open wire type", () => {
|
||||
expect(toSessionError(llm(new RateLimitReason({ message: "rate", retryAfterMs: 123 })))).toEqual({
|
||||
type: "provider.rate-limit",
|
||||
message: "rate",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { LLMError, TransportReason } from "@opencode-ai/ai"
|
||||
import { AIError, TransportReason } from "@opencode-ai/ai"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
|
||||
|
|
@ -27,7 +27,7 @@ describe("SessionExecution lifecycle", () => {
|
|||
expect(
|
||||
SessionExecution.terminal(
|
||||
Exit.fail(
|
||||
new LLMError({
|
||||
new AIError({
|
||||
module: "test",
|
||||
method: "stream",
|
||||
reason: new TransportReason({ message: "Disconnected" }),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
import { expect } from "bun:test"
|
||||
import { LLMClient, LLMEvent, LLMResponse, Model, SystemPart, ToolDefinition, type LLMRequest } from "@opencode-ai/ai"
|
||||
import {
|
||||
LLMClient,
|
||||
LLMEvent,
|
||||
LLMResponse,
|
||||
LanguageModel,
|
||||
SystemPart,
|
||||
ToolDefinition,
|
||||
type LLMRequest,
|
||||
} from "@opencode-ai/ai"
|
||||
import { OpenAIChat } from "@opencode-ai/ai/protocols"
|
||||
import { Agent } from "@opencode-ai/core/agent"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
|
|
@ -46,7 +54,7 @@ const requests: LLMRequest[] = []
|
|||
let instruction: string | Instructions.Unavailable = "Initial context"
|
||||
const sessionID = SessionSchema.ID.make("ses_generate_test")
|
||||
|
||||
const model = Model.make({ id: "generate-model", provider: "test", route: OpenAIChat.route })
|
||||
const model = LanguageModel.make({ id: "generate-model", provider: "test", route: OpenAIChat.route })
|
||||
const client = Layer.mock(LLMClient.Service)({
|
||||
stream: () => Stream.die(new Error("unused")),
|
||||
generate: (request) =>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import {
|
||||
LLMError,
|
||||
AIError,
|
||||
LLMEvent,
|
||||
LLMRequest,
|
||||
Message,
|
||||
Model,
|
||||
LanguageModel,
|
||||
SystemPart,
|
||||
ToolFailure,
|
||||
TransportReason,
|
||||
|
|
@ -130,25 +130,25 @@ const testLLM = TestLLM.layer({
|
|||
}),
|
||||
})
|
||||
const client = TestLLM.clientLayer
|
||||
const model = Model.make({ id: "fake-model", provider: "fake", route: OpenAIChat.route })
|
||||
const model = LanguageModel.make({ id: "fake-model", provider: "fake", route: OpenAIChat.route })
|
||||
const defaultSystem = PROMPT_DEFAULT
|
||||
const replacementModel = Model.make({ id: "replacement", provider: "fake", route: OpenAIChat.route })
|
||||
const compactModel = Model.make({
|
||||
const replacementModel = LanguageModel.make({ id: "replacement", provider: "fake", route: OpenAIChat.route })
|
||||
const compactModel = LanguageModel.make({
|
||||
id: "compact",
|
||||
provider: "fake",
|
||||
route: OpenAIChat.route.with({ limits: { context: 4_000, output: 50 } }),
|
||||
})
|
||||
const fullOutputModel = Model.make({
|
||||
const fullOutputModel = LanguageModel.make({
|
||||
id: "full-output",
|
||||
provider: "fake",
|
||||
route: OpenAIChat.route.with({ limits: { context: 262_144, output: 262_144 } }),
|
||||
})
|
||||
const undersizedContextModel = Model.make({
|
||||
const undersizedContextModel = LanguageModel.make({
|
||||
id: "undersized-context",
|
||||
provider: "fake",
|
||||
route: OpenAIChat.route.with({ limits: { context: 1, output: 1_000 } }),
|
||||
})
|
||||
const recoveryModel = Model.make({
|
||||
const recoveryModel = LanguageModel.make({
|
||||
id: "recovery",
|
||||
provider: "fake",
|
||||
route: OpenAIChat.route.with({ limits: { context: 20_000, output: 1_000 } }),
|
||||
|
|
@ -230,9 +230,7 @@ const permission = Layer.succeed(
|
|||
)
|
||||
const transformTools = (registry: Tool.Interface, tools: Readonly<Record<string, Info>>, options?: Tool.Options) =>
|
||||
registry.transform((draft) =>
|
||||
Object.entries(tools).forEach(([name, tool]) =>
|
||||
draft.add({ ...tool, name, options: options ?? tool.options }),
|
||||
),
|
||||
Object.entries(tools).forEach(([name, tool]) => draft.add({ ...tool, name, options: options ?? tool.options })),
|
||||
)
|
||||
const echo = Layer.effectDiscard(
|
||||
Tool.Service.use((registry) =>
|
||||
|
|
@ -509,21 +507,21 @@ const setup = Effect.gen(function* () {
|
|||
})
|
||||
|
||||
const providerUnavailable = () =>
|
||||
new LLMError({
|
||||
new AIError({
|
||||
module: "test",
|
||||
method: "stream",
|
||||
reason: new TransportReason({ message: "Provider unavailable" }),
|
||||
})
|
||||
|
||||
const invalidRequest = () =>
|
||||
new LLMError({
|
||||
new AIError({
|
||||
module: "test",
|
||||
method: "stream",
|
||||
reason: new InvalidRequestReason({ message: "Invalid request" }),
|
||||
})
|
||||
|
||||
const rateLimited = (retryAfterMs?: number) =>
|
||||
new LLMError({
|
||||
new AIError({
|
||||
module: "test",
|
||||
method: "stream",
|
||||
reason: new RateLimitReason({ message: "Rate limited", retryAfterMs }),
|
||||
|
|
@ -1307,7 +1305,7 @@ describe("SessionRunnerLLM", () => {
|
|||
it.effect("uses the selected model family prompt when the agent does not override it", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* setup
|
||||
currentModel = Model.make({ id: "gpt-5", provider: "openai", route: OpenAIChat.route })
|
||||
currentModel = LanguageModel.make({ id: "gpt-5", provider: "openai", route: OpenAIChat.route })
|
||||
yield* admit(session, "First")
|
||||
|
||||
yield* TestLLM.push(TestLLM.text("Done", "text-provider-prompt"))
|
||||
|
|
@ -1323,7 +1321,7 @@ describe("SessionRunnerLLM", () => {
|
|||
it.effect("uses the selected model family prompt when the agent system override is empty", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* setup
|
||||
currentModel = Model.make({ id: "gpt-5", provider: "openai", route: OpenAIChat.route })
|
||||
currentModel = LanguageModel.make({ id: "gpt-5", provider: "openai", route: OpenAIChat.route })
|
||||
const agent = yield* Agent.Service
|
||||
yield* agent.transform((editor) =>
|
||||
editor.update(Agent.ID.make("build"), (agent) => {
|
||||
|
|
@ -2149,7 +2147,7 @@ describe("SessionRunnerLLM", () => {
|
|||
const session = yield* setupOverflowRecovery
|
||||
yield* TestLLM.push(
|
||||
Stream.fail(
|
||||
new LLMError({
|
||||
new AIError({
|
||||
module: "test",
|
||||
method: "stream",
|
||||
reason: new InvalidRequestReason({
|
||||
|
|
@ -4076,7 +4074,7 @@ describe("SessionRunnerLLM", () => {
|
|||
it.effect("settles malformed streamed tool input before the provider failure", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* setup
|
||||
const failure = new LLMError({
|
||||
const failure = new AIError({
|
||||
module: "test",
|
||||
method: "stream",
|
||||
reason: new InvalidProviderOutputReason({ message: "Invalid JSON input for tool call echo" }),
|
||||
|
|
@ -4290,7 +4288,7 @@ describe("SessionRunnerLLM", () => {
|
|||
it.effect("records malformed provider-executed input as executed", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* setup
|
||||
const failure = new LLMError({
|
||||
const failure = new AIError({
|
||||
module: "test",
|
||||
method: "stream",
|
||||
reason: new InvalidProviderOutputReason({ message: "Invalid hosted tool input" }),
|
||||
|
|
@ -4322,7 +4320,7 @@ describe("SessionRunnerLLM", () => {
|
|||
it.effect("records a provider failure after malformed input", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* setup
|
||||
const failure = new LLMError({
|
||||
const failure = new AIError({
|
||||
module: "test",
|
||||
method: "stream",
|
||||
reason: new InvalidProviderOutputReason({ message: "Provider failed after malformed input" }),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { expect } from "bun:test"
|
||||
import { LLMClient, LLMEvent, Model, type LLMRequest } from "@opencode-ai/ai"
|
||||
import { LLMClient, LLMEvent, LanguageModel, type LLMRequest } from "@opencode-ai/ai"
|
||||
import { OpenAIChat } from "@opencode-ai/ai/protocols"
|
||||
import { Agent } from "@opencode-ai/core/agent"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
|
|
@ -24,7 +24,7 @@ import { Deferred, Effect, Fiber, Layer, Stream } from "effect"
|
|||
import { testEffect } from "./lib/effect"
|
||||
|
||||
let requests: LLMRequest[] = []
|
||||
const model = Model.make({
|
||||
const model = LanguageModel.make({
|
||||
id: "title-model",
|
||||
provider: "test",
|
||||
route: OpenAIChat.route.with({ limits: { context: 10_000, output: 1_000 } }),
|
||||
|
|
@ -77,14 +77,7 @@ const models = Layer.mock(SessionRunnerModel.Service)({
|
|||
})
|
||||
const it = testEffect(
|
||||
AppNodeBuilder.build(
|
||||
LayerNode.group([
|
||||
Database.node,
|
||||
Bus.node,
|
||||
SessionProjector.node,
|
||||
SessionStore.node,
|
||||
Agent.node,
|
||||
SessionTitle.node,
|
||||
]),
|
||||
LayerNode.group([Database.node, Bus.node, SessionProjector.node, SessionStore.node, Agent.node, SessionTitle.node]),
|
||||
[
|
||||
[llmClient, client],
|
||||
[SessionRunnerModel.node, models],
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { Connection } from "@opencode-ai/schema/connection"
|
|||
import { Credential } from "@opencode-ai/schema/credential"
|
||||
import { FileSystem } from "@opencode-ai/schema/filesystem"
|
||||
import { Integration } from "@opencode-ai/schema/integration"
|
||||
import { AI } from "@opencode-ai/schema/ai"
|
||||
import { LLM } from "@opencode-ai/schema/llm"
|
||||
import { Permission } from "@opencode-ai/schema/permission"
|
||||
import { Pty } from "@opencode-ai/schema/pty"
|
||||
|
|
@ -34,7 +35,7 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||
coreFileSystem,
|
||||
coreIntegration,
|
||||
coreLocation,
|
||||
coreLLM,
|
||||
coreAI,
|
||||
coreModel,
|
||||
corePermission,
|
||||
corePermissionV1,
|
||||
|
|
@ -100,8 +101,8 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||
[coreIntegration.Inputs, Integration.Inputs],
|
||||
[coreIntegration.Ref, Integration.Ref],
|
||||
[coreLocation.Ref, Location.Ref],
|
||||
[coreLLM.ProviderMetadata, LLM.ProviderMetadata],
|
||||
[coreLLM.FinishReason, LLM.FinishReason],
|
||||
[coreAI.ProviderMetadata, AI.ProviderMetadata],
|
||||
[coreAI.FinishReason, LLM.FinishReason],
|
||||
[coreModel.ID, Model.ID],
|
||||
[coreModel.VariantID, Model.VariantID],
|
||||
[coreModel.Ref, Model.Ref],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue