feat(id): brand ProviderID and ModelID (#17110)
This commit is contained in:
parent
2eeba53b07
commit
c45467964c
23 changed files with 157 additions and 107 deletions
|
|
@ -13,6 +13,7 @@ const seed = async () => {
|
||||||
const { Session } = await import("../src/session")
|
const { Session } = await import("../src/session")
|
||||||
const { MessageID, PartID } = await import("../src/session/schema")
|
const { MessageID, PartID } = await import("../src/session/schema")
|
||||||
const { Project } = await import("../src/project/project")
|
const { Project } = await import("../src/project/project")
|
||||||
|
const { ModelID, ProviderID } = await import("../src/provider/schema")
|
||||||
|
|
||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: dir,
|
directory: dir,
|
||||||
|
|
@ -28,8 +29,8 @@ const seed = async () => {
|
||||||
time: { created: now },
|
time: { created: now },
|
||||||
agent: "build",
|
agent: "build",
|
||||||
model: {
|
model: {
|
||||||
providerID,
|
providerID: ProviderID.make(providerID),
|
||||||
modelID,
|
modelID: ModelID.make(modelID),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const part = {
|
const part = {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import { Hash } from "../util/hash"
|
||||||
import { ACPSessionManager } from "./session"
|
import { ACPSessionManager } from "./session"
|
||||||
import type { ACPConfig } from "./types"
|
import type { ACPConfig } from "./types"
|
||||||
import { Provider } from "../provider/provider"
|
import { Provider } from "../provider/provider"
|
||||||
|
import { ProviderID } from "../provider/schema"
|
||||||
import { Agent as AgentModule } from "../agent/agent"
|
import { Agent as AgentModule } from "../agent/agent"
|
||||||
import { Installation } from "@/installation"
|
import { Installation } from "@/installation"
|
||||||
import { MessageV2 } from "@/session/message-v2"
|
import { MessageV2 } from "@/session/message-v2"
|
||||||
|
|
@ -590,7 +591,7 @@ export namespace ACP {
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = MessageV2.fromError(e, {
|
const error = MessageV2.fromError(e, {
|
||||||
providerID: this.config.defaultModel?.providerID ?? "unknown",
|
providerID: ProviderID.make(this.config.defaultModel?.providerID ?? "unknown"),
|
||||||
})
|
})
|
||||||
if (LoadAPIKeyError.isInstance(error)) {
|
if (LoadAPIKeyError.isInstance(error)) {
|
||||||
throw RequestError.authRequired()
|
throw RequestError.authRequired()
|
||||||
|
|
@ -655,7 +656,7 @@ export namespace ACP {
|
||||||
return result
|
return result
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = MessageV2.fromError(e, {
|
const error = MessageV2.fromError(e, {
|
||||||
providerID: this.config.defaultModel?.providerID ?? "unknown",
|
providerID: ProviderID.make(this.config.defaultModel?.providerID ?? "unknown"),
|
||||||
})
|
})
|
||||||
if (LoadAPIKeyError.isInstance(error)) {
|
if (LoadAPIKeyError.isInstance(error)) {
|
||||||
throw RequestError.authRequired()
|
throw RequestError.authRequired()
|
||||||
|
|
@ -700,7 +701,7 @@ export namespace ACP {
|
||||||
return response
|
return response
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = MessageV2.fromError(e, {
|
const error = MessageV2.fromError(e, {
|
||||||
providerID: this.config.defaultModel?.providerID ?? "unknown",
|
providerID: ProviderID.make(this.config.defaultModel?.providerID ?? "unknown"),
|
||||||
})
|
})
|
||||||
if (LoadAPIKeyError.isInstance(error)) {
|
if (LoadAPIKeyError.isInstance(error)) {
|
||||||
throw RequestError.authRequired()
|
throw RequestError.authRequired()
|
||||||
|
|
@ -765,7 +766,7 @@ export namespace ACP {
|
||||||
return mode
|
return mode
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = MessageV2.fromError(e, {
|
const error = MessageV2.fromError(e, {
|
||||||
providerID: this.config.defaultModel?.providerID ?? "unknown",
|
providerID: ProviderID.make(this.config.defaultModel?.providerID ?? "unknown"),
|
||||||
})
|
})
|
||||||
if (LoadAPIKeyError.isInstance(error)) {
|
if (LoadAPIKeyError.isInstance(error)) {
|
||||||
throw RequestError.authRequired()
|
throw RequestError.authRequired()
|
||||||
|
|
@ -796,7 +797,7 @@ export namespace ACP {
|
||||||
return result
|
return result
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = MessageV2.fromError(e, {
|
const error = MessageV2.fromError(e, {
|
||||||
providerID: this.config.defaultModel?.providerID ?? "unknown",
|
providerID: ProviderID.make(this.config.defaultModel?.providerID ?? "unknown"),
|
||||||
})
|
})
|
||||||
if (LoadAPIKeyError.isInstance(error)) {
|
if (LoadAPIKeyError.isInstance(error)) {
|
||||||
throw RequestError.authRequired()
|
throw RequestError.authRequired()
|
||||||
|
|
@ -1666,7 +1667,8 @@ export namespace ACP {
|
||||||
): ModelOption[] {
|
): ModelOption[] {
|
||||||
const includeVariants = options.includeVariants ?? false
|
const includeVariants = options.includeVariants ?? false
|
||||||
return providers.flatMap((provider) => {
|
return providers.flatMap((provider) => {
|
||||||
const models = Provider.sort(Object.values(provider.models) as any)
|
const unsorted: Array<{ id: string; name: string; variants?: Record<string, any> }> = Object.values(provider.models)
|
||||||
|
const models = Provider.sort(unsorted)
|
||||||
return models.flatMap((model) => {
|
return models.flatMap((model) => {
|
||||||
const base: ModelOption = {
|
const base: ModelOption = {
|
||||||
modelId: `${provider.id}/${model.id}`,
|
modelId: `${provider.id}/${model.id}`,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Config } from "../config/config"
|
import { Config } from "../config/config"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Provider } from "../provider/provider"
|
import { Provider } from "../provider/provider"
|
||||||
|
import { ModelID, ProviderID } from "../provider/schema"
|
||||||
import { generateObject, streamObject, type ModelMessage } from "ai"
|
import { generateObject, streamObject, type ModelMessage } from "ai"
|
||||||
import { SystemPrompt } from "../session/system"
|
import { SystemPrompt } from "../session/system"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
|
|
@ -34,8 +35,8 @@ export namespace Agent {
|
||||||
permission: PermissionNext.Ruleset,
|
permission: PermissionNext.Ruleset,
|
||||||
model: z
|
model: z
|
||||||
.object({
|
.object({
|
||||||
modelID: z.string(),
|
modelID: ModelID.zod,
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
variant: z.string().optional(),
|
variant: z.string().optional(),
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { Installation } from "../installation"
|
||||||
import { Auth, OAUTH_DUMMY_KEY } from "../auth"
|
import { Auth, OAUTH_DUMMY_KEY } from "../auth"
|
||||||
import os from "os"
|
import os from "os"
|
||||||
import { ProviderTransform } from "@/provider/transform"
|
import { ProviderTransform } from "@/provider/transform"
|
||||||
|
import { ModelID, ProviderID } from "@/provider/schema"
|
||||||
import { setTimeout as sleep } from "node:timers/promises"
|
import { setTimeout as sleep } from "node:timers/promises"
|
||||||
|
|
||||||
const log = Log.create({ service: "plugin.codex" })
|
const log = Log.create({ service: "plugin.codex" })
|
||||||
|
|
@ -375,8 +376,8 @@ export async function CodexAuthPlugin(input: PluginInput): Promise<Hooks> {
|
||||||
|
|
||||||
if (!provider.models["gpt-5.3-codex"]) {
|
if (!provider.models["gpt-5.3-codex"]) {
|
||||||
const model = {
|
const model = {
|
||||||
id: "gpt-5.3-codex",
|
id: ModelID.make("gpt-5.3-codex"),
|
||||||
providerID: "openai",
|
providerID: ProviderID.make("openai"),
|
||||||
api: {
|
api: {
|
||||||
id: "gpt-5.3-codex",
|
id: "gpt-5.3-codex",
|
||||||
url: "https://chatgpt.com/backend-api/codex",
|
url: "https://chatgpt.com/backend-api/codex",
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { fn } from "@/util/fn"
|
||||||
import type { AuthOuathResult, Hooks } from "@opencode-ai/plugin"
|
import type { AuthOuathResult, Hooks } from "@opencode-ai/plugin"
|
||||||
import { NamedError } from "@opencode-ai/util/error"
|
import { NamedError } from "@opencode-ai/util/error"
|
||||||
import { Auth } from "@/auth"
|
import { Auth } from "@/auth"
|
||||||
|
import { ProviderID } from "./schema"
|
||||||
|
|
||||||
export namespace ProviderAuth {
|
export namespace ProviderAuth {
|
||||||
const state = Instance.state(async () => {
|
const state = Instance.state(async () => {
|
||||||
|
|
@ -53,7 +54,7 @@ export namespace ProviderAuth {
|
||||||
|
|
||||||
export const authorize = fn(
|
export const authorize = fn(
|
||||||
z.object({
|
z.object({
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
method: z.number(),
|
method: z.number(),
|
||||||
}),
|
}),
|
||||||
async (input): Promise<Authorization | undefined> => {
|
async (input): Promise<Authorization | undefined> => {
|
||||||
|
|
@ -73,7 +74,7 @@ export namespace ProviderAuth {
|
||||||
|
|
||||||
export const callback = fn(
|
export const callback = fn(
|
||||||
z.object({
|
z.object({
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
method: z.number(),
|
method: z.number(),
|
||||||
code: z.string().optional(),
|
code: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
|
|
@ -119,7 +120,7 @@ export namespace ProviderAuth {
|
||||||
|
|
||||||
export const api = fn(
|
export const api = fn(
|
||||||
z.object({
|
z.object({
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
key: z.string(),
|
key: z.string(),
|
||||||
}),
|
}),
|
||||||
async (input) => {
|
async (input) => {
|
||||||
|
|
@ -133,13 +134,13 @@ export namespace ProviderAuth {
|
||||||
export const OauthMissing = NamedError.create(
|
export const OauthMissing = NamedError.create(
|
||||||
"ProviderAuthOauthMissing",
|
"ProviderAuthOauthMissing",
|
||||||
z.object({
|
z.object({
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
export const OauthCodeMissing = NamedError.create(
|
export const OauthCodeMissing = NamedError.create(
|
||||||
"ProviderAuthOauthCodeMissing",
|
"ProviderAuthOauthCodeMissing",
|
||||||
z.object({
|
z.object({
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
|
||||||
import { GoogleAuth } from "google-auth-library"
|
import { GoogleAuth } from "google-auth-library"
|
||||||
import { ProviderTransform } from "./transform"
|
import { ProviderTransform } from "./transform"
|
||||||
import { Installation } from "../installation"
|
import { Installation } from "../installation"
|
||||||
|
import { ModelID, ProviderID } from "./schema"
|
||||||
|
|
||||||
const DEFAULT_CHUNK_TIMEOUT = 120_000
|
const DEFAULT_CHUNK_TIMEOUT = 120_000
|
||||||
|
|
||||||
|
|
@ -673,8 +674,8 @@ export namespace Provider {
|
||||||
|
|
||||||
export const Model = z
|
export const Model = z
|
||||||
.object({
|
.object({
|
||||||
id: z.string(),
|
id: ModelID.zod,
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
api: z.object({
|
api: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
url: z.string(),
|
url: z.string(),
|
||||||
|
|
@ -744,7 +745,7 @@ export namespace Provider {
|
||||||
|
|
||||||
export const Info = z
|
export const Info = z
|
||||||
.object({
|
.object({
|
||||||
id: z.string(),
|
id: ProviderID.zod,
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
source: z.enum(["env", "config", "custom", "api"]),
|
source: z.enum(["env", "config", "custom", "api"]),
|
||||||
env: z.string().array(),
|
env: z.string().array(),
|
||||||
|
|
@ -759,8 +760,8 @@ export namespace Provider {
|
||||||
|
|
||||||
function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
|
function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
|
||||||
const m: Model = {
|
const m: Model = {
|
||||||
id: model.id,
|
id: ModelID.make(model.id),
|
||||||
providerID: provider.id,
|
providerID: ProviderID.make(provider.id),
|
||||||
name: model.name,
|
name: model.name,
|
||||||
family: model.family,
|
family: model.family,
|
||||||
api: {
|
api: {
|
||||||
|
|
@ -826,7 +827,7 @@ export namespace Provider {
|
||||||
|
|
||||||
export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
|
export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
|
||||||
return {
|
return {
|
||||||
id: provider.id,
|
id: ProviderID.make(provider.id),
|
||||||
source: "custom",
|
source: "custom",
|
||||||
name: provider.name,
|
name: provider.name,
|
||||||
env: provider.env ?? [],
|
env: provider.env ?? [],
|
||||||
|
|
@ -866,11 +867,11 @@ export namespace Provider {
|
||||||
const githubCopilot = database["github-copilot"]
|
const githubCopilot = database["github-copilot"]
|
||||||
database["github-copilot-enterprise"] = {
|
database["github-copilot-enterprise"] = {
|
||||||
...githubCopilot,
|
...githubCopilot,
|
||||||
id: "github-copilot-enterprise",
|
id: ProviderID.make("github-copilot-enterprise"),
|
||||||
name: "GitHub Copilot Enterprise",
|
name: "GitHub Copilot Enterprise",
|
||||||
models: mapValues(githubCopilot.models, (model) => ({
|
models: mapValues(githubCopilot.models, (model) => ({
|
||||||
...model,
|
...model,
|
||||||
providerID: "github-copilot-enterprise",
|
providerID: ProviderID.make("github-copilot-enterprise"),
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -892,7 +893,7 @@ export namespace Provider {
|
||||||
for (const [providerID, provider] of configProviders) {
|
for (const [providerID, provider] of configProviders) {
|
||||||
const existing = database[providerID]
|
const existing = database[providerID]
|
||||||
const parsed: Info = {
|
const parsed: Info = {
|
||||||
id: providerID,
|
id: ProviderID.make(providerID),
|
||||||
name: provider.name ?? existing?.name ?? providerID,
|
name: provider.name ?? existing?.name ?? providerID,
|
||||||
env: provider.env ?? existing?.env ?? [],
|
env: provider.env ?? existing?.env ?? [],
|
||||||
options: mergeDeep(existing?.options ?? {}, provider.options ?? {}),
|
options: mergeDeep(existing?.options ?? {}, provider.options ?? {}),
|
||||||
|
|
@ -908,7 +909,7 @@ export namespace Provider {
|
||||||
return existingModel?.name ?? modelID
|
return existingModel?.name ?? modelID
|
||||||
})
|
})
|
||||||
const parsedModel: Model = {
|
const parsedModel: Model = {
|
||||||
id: modelID,
|
id: ModelID.make(modelID),
|
||||||
api: {
|
api: {
|
||||||
id: model.id ?? existingModel?.api.id ?? modelID,
|
id: model.id ?? existingModel?.api.id ?? modelID,
|
||||||
npm:
|
npm:
|
||||||
|
|
@ -921,7 +922,7 @@ export namespace Provider {
|
||||||
},
|
},
|
||||||
status: model.status ?? existingModel?.status ?? "active",
|
status: model.status ?? existingModel?.status ?? "active",
|
||||||
name,
|
name,
|
||||||
providerID,
|
providerID: ProviderID.make(providerID),
|
||||||
capabilities: {
|
capabilities: {
|
||||||
temperature: model.temperature ?? existingModel?.capabilities.temperature ?? false,
|
temperature: model.temperature ?? existingModel?.capabilities.temperature ?? false,
|
||||||
reasoning: model.reasoning ?? existingModel?.capabilities.reasoning ?? false,
|
reasoning: model.reasoning ?? existingModel?.capabilities.reasoning ?? false,
|
||||||
|
|
@ -1356,7 +1357,7 @@ export namespace Provider {
|
||||||
}
|
}
|
||||||
|
|
||||||
const priority = ["gpt-5", "claude-sonnet-4", "big-pickle", "gemini-3-pro"]
|
const priority = ["gpt-5", "claude-sonnet-4", "big-pickle", "gemini-3-pro"]
|
||||||
export function sort(models: Model[]) {
|
export function sort<T extends { id: string }>(models: T[]) {
|
||||||
return sortBy(
|
return sortBy(
|
||||||
models,
|
models,
|
||||||
[(model) => priority.findIndex((filter) => model.id.includes(filter)), "desc"],
|
[(model) => priority.findIndex((filter) => model.id.includes(filter)), "desc"],
|
||||||
|
|
@ -1370,11 +1371,11 @@ export namespace Provider {
|
||||||
if (cfg.model) return parseModel(cfg.model)
|
if (cfg.model) return parseModel(cfg.model)
|
||||||
|
|
||||||
const providers = await list()
|
const providers = await list()
|
||||||
const recent = (await Filesystem.readJson<{ recent?: { providerID: string; modelID: string }[] }>(
|
const recent = (await Filesystem.readJson<{ recent?: { providerID: ProviderID; modelID: ModelID }[] }>(
|
||||||
path.join(Global.Path.state, "model.json"),
|
path.join(Global.Path.state, "model.json"),
|
||||||
)
|
)
|
||||||
.then((x) => (Array.isArray(x.recent) ? x.recent : []))
|
.then((x) => (Array.isArray(x.recent) ? x.recent : []))
|
||||||
.catch(() => [])) as { providerID: string; modelID: string }[]
|
.catch(() => [])) as { providerID: ProviderID; modelID: ModelID }[]
|
||||||
for (const entry of recent) {
|
for (const entry of recent) {
|
||||||
const provider = providers[entry.providerID]
|
const provider = providers[entry.providerID]
|
||||||
if (!provider) continue
|
if (!provider) continue
|
||||||
|
|
@ -1395,16 +1396,16 @@ export namespace Provider {
|
||||||
export function parseModel(model: string) {
|
export function parseModel(model: string) {
|
||||||
const [providerID, ...rest] = model.split("/")
|
const [providerID, ...rest] = model.split("/")
|
||||||
return {
|
return {
|
||||||
providerID: providerID,
|
providerID: ProviderID.make(providerID),
|
||||||
modelID: rest.join("/"),
|
modelID: ModelID.make(rest.join("/")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModelNotFoundError = NamedError.create(
|
export const ModelNotFoundError = NamedError.create(
|
||||||
"ProviderModelNotFoundError",
|
"ProviderModelNotFoundError",
|
||||||
z.object({
|
z.object({
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
modelID: z.string(),
|
modelID: ModelID.zod,
|
||||||
suggestions: z.array(z.string()).optional(),
|
suggestions: z.array(z.string()).optional(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
@ -1412,7 +1413,7 @@ export namespace Provider {
|
||||||
export const InitError = NamedError.create(
|
export const InitError = NamedError.create(
|
||||||
"ProviderInitError",
|
"ProviderInitError",
|
||||||
z.object({
|
z.object({
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
packages/opencode/src/provider/schema.ts
Normal file
26
packages/opencode/src/provider/schema.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { Schema } from "effect"
|
||||||
|
import z from "zod"
|
||||||
|
|
||||||
|
import { withStatics } from "@/util/schema"
|
||||||
|
|
||||||
|
const providerIdSchema = Schema.String.pipe(Schema.brand("ProviderID"))
|
||||||
|
|
||||||
|
export type ProviderID = typeof providerIdSchema.Type
|
||||||
|
|
||||||
|
export const ProviderID = providerIdSchema.pipe(
|
||||||
|
withStatics((schema: typeof providerIdSchema) => ({
|
||||||
|
make: (id: string) => schema.makeUnsafe(id),
|
||||||
|
zod: z.string().pipe(z.custom<ProviderID>()),
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
|
const modelIdSchema = Schema.String.pipe(Schema.brand("ModelID"))
|
||||||
|
|
||||||
|
export type ModelID = typeof modelIdSchema.Type
|
||||||
|
|
||||||
|
export const ModelID = modelIdSchema.pipe(
|
||||||
|
withStatics((schema: typeof modelIdSchema) => ({
|
||||||
|
make: (id: string) => schema.makeUnsafe(id),
|
||||||
|
zod: z.string().pipe(z.custom<ModelID>()),
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
@ -5,6 +5,7 @@ import { Config } from "../../config/config"
|
||||||
import { Provider } from "../../provider/provider"
|
import { Provider } from "../../provider/provider"
|
||||||
import { ModelsDev } from "../../provider/models"
|
import { ModelsDev } from "../../provider/models"
|
||||||
import { ProviderAuth } from "../../provider/auth"
|
import { ProviderAuth } from "../../provider/auth"
|
||||||
|
import { ProviderID } from "../../provider/schema"
|
||||||
import { mapValues } from "remeda"
|
import { mapValues } from "remeda"
|
||||||
import { errors } from "../error"
|
import { errors } from "../error"
|
||||||
import { lazy } from "../../util/lazy"
|
import { lazy } from "../../util/lazy"
|
||||||
|
|
@ -101,7 +102,7 @@ export const ProviderRoutes = lazy(() =>
|
||||||
validator(
|
validator(
|
||||||
"param",
|
"param",
|
||||||
z.object({
|
z.object({
|
||||||
providerID: z.string().meta({ description: "Provider ID" }),
|
providerID: ProviderID.zod.meta({ description: "Provider ID" }),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
validator(
|
validator(
|
||||||
|
|
@ -141,7 +142,7 @@ export const ProviderRoutes = lazy(() =>
|
||||||
validator(
|
validator(
|
||||||
"param",
|
"param",
|
||||||
z.object({
|
z.object({
|
||||||
providerID: z.string().meta({ description: "Provider ID" }),
|
providerID: ProviderID.zod.meta({ description: "Provider ID" }),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
validator(
|
validator(
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import { Snapshot } from "@/snapshot"
|
||||||
import { Log } from "../../util/log"
|
import { Log } from "../../util/log"
|
||||||
import { PermissionNext } from "@/permission/next"
|
import { PermissionNext } from "@/permission/next"
|
||||||
import { PermissionID } from "@/permission/schema"
|
import { PermissionID } from "@/permission/schema"
|
||||||
|
import { ModelID, ProviderID } from "@/provider/schema"
|
||||||
import { errors } from "../error"
|
import { errors } from "../error"
|
||||||
import { lazy } from "../../util/lazy"
|
import { lazy } from "../../util/lazy"
|
||||||
|
|
||||||
|
|
@ -510,8 +511,8 @@ export const SessionRoutes = lazy(() =>
|
||||||
validator(
|
validator(
|
||||||
"json",
|
"json",
|
||||||
z.object({
|
z.object({
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
modelID: z.string(),
|
modelID: ModelID.zod,
|
||||||
auto: z.boolean().optional().default(false),
|
auto: z.boolean().optional().default(false),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import { Command } from "../command"
|
||||||
import { Global } from "../global"
|
import { Global } from "../global"
|
||||||
import { WorkspaceContext } from "../control-plane/workspace-context"
|
import { WorkspaceContext } from "../control-plane/workspace-context"
|
||||||
import { WorkspaceID } from "../control-plane/schema"
|
import { WorkspaceID } from "../control-plane/schema"
|
||||||
|
import { ProviderID } from "../provider/schema"
|
||||||
import { WorkspaceRouterMiddleware } from "../control-plane/workspace-router-middleware"
|
import { WorkspaceRouterMiddleware } from "../control-plane/workspace-router-middleware"
|
||||||
import { ProjectRoutes } from "./routes/project"
|
import { ProjectRoutes } from "./routes/project"
|
||||||
import { SessionRoutes } from "./routes/session"
|
import { SessionRoutes } from "./routes/session"
|
||||||
|
|
@ -148,7 +149,7 @@ export namespace Server {
|
||||||
validator(
|
validator(
|
||||||
"param",
|
"param",
|
||||||
z.object({
|
z.object({
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
validator("json", Auth.Info),
|
validator("json", Auth.Info),
|
||||||
|
|
@ -180,7 +181,7 @@ export namespace Server {
|
||||||
validator(
|
validator(
|
||||||
"param",
|
"param",
|
||||||
z.object({
|
z.object({
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { Agent } from "@/agent/agent"
|
||||||
import { Plugin } from "@/plugin"
|
import { Plugin } from "@/plugin"
|
||||||
import { Config } from "@/config/config"
|
import { Config } from "@/config/config"
|
||||||
import { ProviderTransform } from "@/provider/transform"
|
import { ProviderTransform } from "@/provider/transform"
|
||||||
|
import { ModelID, ProviderID } from "@/provider/schema"
|
||||||
|
|
||||||
export namespace SessionCompaction {
|
export namespace SessionCompaction {
|
||||||
const log = Log.create({ service: "session.compaction" })
|
const log = Log.create({ service: "session.compaction" })
|
||||||
|
|
@ -298,8 +299,8 @@ When constructing the summary, try to stick to this template:
|
||||||
sessionID: SessionID.zod,
|
sessionID: SessionID.zod,
|
||||||
agent: z.string(),
|
agent: z.string(),
|
||||||
model: z.object({
|
model: z.object({
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
modelID: z.string(),
|
modelID: ModelID.zod,
|
||||||
}),
|
}),
|
||||||
auto: z.boolean(),
|
auto: z.boolean(),
|
||||||
overflow: z.boolean().optional(),
|
overflow: z.boolean().optional(),
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import { WorkspaceID } from "../control-plane/schema"
|
||||||
import { SessionID, MessageID, PartID } from "./schema"
|
import { SessionID, MessageID, PartID } from "./schema"
|
||||||
|
|
||||||
import type { Provider } from "@/provider/provider"
|
import type { Provider } from "@/provider/provider"
|
||||||
|
import { ModelID, ProviderID } from "@/provider/schema"
|
||||||
import { PermissionNext } from "@/permission/next"
|
import { PermissionNext } from "@/permission/next"
|
||||||
import { Global } from "@/global"
|
import { Global } from "@/global"
|
||||||
import type { LanguageModelV2Usage } from "@ai-sdk/provider"
|
import type { LanguageModelV2Usage } from "@ai-sdk/provider"
|
||||||
|
|
@ -875,8 +876,8 @@ export namespace Session {
|
||||||
export const initialize = fn(
|
export const initialize = fn(
|
||||||
z.object({
|
z.object({
|
||||||
sessionID: SessionID.zod,
|
sessionID: SessionID.zod,
|
||||||
modelID: z.string(),
|
modelID: ModelID.zod,
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
messageID: MessageID.zod,
|
messageID: MessageID.zod,
|
||||||
}),
|
}),
|
||||||
async (input) => {
|
async (input) => {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { ProviderError } from "@/provider/error"
|
||||||
import { iife } from "@/util/iife"
|
import { iife } from "@/util/iife"
|
||||||
import { type SystemError } from "bun"
|
import { type SystemError } from "bun"
|
||||||
import type { Provider } from "@/provider/provider"
|
import type { Provider } from "@/provider/provider"
|
||||||
|
import { ModelID, ProviderID } from "@/provider/schema"
|
||||||
|
|
||||||
export namespace MessageV2 {
|
export namespace MessageV2 {
|
||||||
export function isMedia(mime: string) {
|
export function isMedia(mime: string) {
|
||||||
|
|
@ -213,8 +214,8 @@ export namespace MessageV2 {
|
||||||
agent: z.string(),
|
agent: z.string(),
|
||||||
model: z
|
model: z
|
||||||
.object({
|
.object({
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
modelID: z.string(),
|
modelID: ModelID.zod,
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
command: z.string().optional(),
|
command: z.string().optional(),
|
||||||
|
|
@ -362,8 +363,8 @@ export namespace MessageV2 {
|
||||||
.optional(),
|
.optional(),
|
||||||
agent: z.string(),
|
agent: z.string(),
|
||||||
model: z.object({
|
model: z.object({
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
modelID: z.string(),
|
modelID: ModelID.zod,
|
||||||
}),
|
}),
|
||||||
system: z.string().optional(),
|
system: z.string().optional(),
|
||||||
tools: z.record(z.string(), z.boolean()).optional(),
|
tools: z.record(z.string(), z.boolean()).optional(),
|
||||||
|
|
@ -411,8 +412,8 @@ export namespace MessageV2 {
|
||||||
])
|
])
|
||||||
.optional(),
|
.optional(),
|
||||||
parentID: MessageID.zod,
|
parentID: MessageID.zod,
|
||||||
modelID: z.string(),
|
modelID: ModelID.zod,
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
|
|
@ -824,7 +825,7 @@ export namespace MessageV2 {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fromError(e: unknown, ctx: { providerID: string }) {
|
export function fromError(e: unknown, ctx: { providerID: ProviderID }): NonNullable<Assistant["error"]> {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case e instanceof DOMException && e.name === "AbortError":
|
case e instanceof DOMException && e.name === "AbortError":
|
||||||
return new MessageV2.AbortedError(
|
return new MessageV2.AbortedError(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { SessionID } from "./schema"
|
import { SessionID } from "./schema"
|
||||||
|
import { ModelID, ProviderID } from "../provider/schema"
|
||||||
import { NamedError } from "@opencode-ai/util/error"
|
import { NamedError } from "@opencode-ai/util/error"
|
||||||
|
|
||||||
export namespace Message {
|
export namespace Message {
|
||||||
|
|
@ -160,8 +161,8 @@ export namespace Message {
|
||||||
assistant: z
|
assistant: z
|
||||||
.object({
|
.object({
|
||||||
system: z.string().array(),
|
system: z.string().array(),
|
||||||
modelID: z.string(),
|
modelID: ModelID.zod,
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
path: z.object({
|
path: z.object({
|
||||||
cwd: z.string(),
|
cwd: z.string(),
|
||||||
root: z.string(),
|
root: z.string(),
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { SessionRevert } from "./revert"
|
||||||
import { Session } from "."
|
import { Session } from "."
|
||||||
import { Agent } from "../agent/agent"
|
import { Agent } from "../agent/agent"
|
||||||
import { Provider } from "../provider/provider"
|
import { Provider } from "../provider/provider"
|
||||||
|
import { ModelID, ProviderID } from "../provider/schema"
|
||||||
import { type Tool as AITool, tool, jsonSchema, type ToolCallOptions, asSchema } from "ai"
|
import { type Tool as AITool, tool, jsonSchema, type ToolCallOptions, asSchema } from "ai"
|
||||||
import { SessionCompaction } from "./compaction"
|
import { SessionCompaction } from "./compaction"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
|
|
@ -94,8 +95,8 @@ export namespace SessionPrompt {
|
||||||
messageID: MessageID.zod.optional(),
|
messageID: MessageID.zod.optional(),
|
||||||
model: z
|
model: z
|
||||||
.object({
|
.object({
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
modelID: z.string(),
|
modelID: ModelID.zod,
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
agent: z.string().optional(),
|
agent: z.string().optional(),
|
||||||
|
|
@ -1471,8 +1472,8 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
||||||
agent: z.string(),
|
agent: z.string(),
|
||||||
model: z
|
model: z
|
||||||
.object({
|
.object({
|
||||||
providerID: z.string(),
|
providerID: ProviderID.zod,
|
||||||
modelID: z.string(),
|
modelID: ModelID.zod,
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
command: z.string(),
|
command: z.string(),
|
||||||
|
|
|
||||||
|
|
@ -137,8 +137,8 @@ test("custom agent from config creates new agent", async () => {
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const custom = await Agent.get("my_custom_agent")
|
const custom = await Agent.get("my_custom_agent")
|
||||||
expect(custom).toBeDefined()
|
expect(custom).toBeDefined()
|
||||||
expect(custom?.model?.providerID).toBe("openai")
|
expect(String(custom?.model?.providerID)).toBe("openai")
|
||||||
expect(custom?.model?.modelID).toBe("gpt-4")
|
expect(String(custom?.model?.modelID)).toBe("gpt-4")
|
||||||
expect(custom?.description).toBe("My custom agent")
|
expect(custom?.description).toBe("My custom agent")
|
||||||
expect(custom?.temperature).toBe(0.5)
|
expect(custom?.temperature).toBe(0.5)
|
||||||
expect(custom?.topP).toBe(0.9)
|
expect(custom?.topP).toBe(0.9)
|
||||||
|
|
@ -166,8 +166,8 @@ test("custom agent config overrides native agent properties", async () => {
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const build = await Agent.get("build")
|
const build = await Agent.get("build")
|
||||||
expect(build).toBeDefined()
|
expect(build).toBeDefined()
|
||||||
expect(build?.model?.providerID).toBe("anthropic")
|
expect(String(build?.model?.providerID)).toBe("anthropic")
|
||||||
expect(build?.model?.modelID).toBe("claude-3")
|
expect(String(build?.model?.modelID)).toBe("claude-3")
|
||||||
expect(build?.description).toBe("Custom build agent")
|
expect(build?.description).toBe("Custom build agent")
|
||||||
expect(build?.temperature).toBe(0.7)
|
expect(build?.temperature).toBe(0.7)
|
||||||
expect(build?.color).toBe("#FF0000")
|
expect(build?.color).toBe("#FF0000")
|
||||||
|
|
|
||||||
|
|
@ -302,8 +302,8 @@ test("getModel returns model for valid provider/model", async () => {
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const model = await Provider.getModel("anthropic", "claude-sonnet-4-20250514")
|
const model = await Provider.getModel("anthropic", "claude-sonnet-4-20250514")
|
||||||
expect(model).toBeDefined()
|
expect(model).toBeDefined()
|
||||||
expect(model.providerID).toBe("anthropic")
|
expect(String(model.providerID)).toBe("anthropic")
|
||||||
expect(model.id).toBe("claude-sonnet-4-20250514")
|
expect(String(model.id)).toBe("claude-sonnet-4-20250514")
|
||||||
const language = await Provider.getLanguage(model)
|
const language = await Provider.getLanguage(model)
|
||||||
expect(language).toBeDefined()
|
expect(language).toBeDefined()
|
||||||
},
|
},
|
||||||
|
|
@ -353,14 +353,14 @@ test("getModel throws ModelNotFoundError for invalid provider", async () => {
|
||||||
|
|
||||||
test("parseModel correctly parses provider/model string", () => {
|
test("parseModel correctly parses provider/model string", () => {
|
||||||
const result = Provider.parseModel("anthropic/claude-sonnet-4")
|
const result = Provider.parseModel("anthropic/claude-sonnet-4")
|
||||||
expect(result.providerID).toBe("anthropic")
|
expect(String(result.providerID)).toBe("anthropic")
|
||||||
expect(result.modelID).toBe("claude-sonnet-4")
|
expect(String(result.modelID)).toBe("claude-sonnet-4")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("parseModel handles model IDs with slashes", () => {
|
test("parseModel handles model IDs with slashes", () => {
|
||||||
const result = Provider.parseModel("openrouter/anthropic/claude-3-opus")
|
const result = Provider.parseModel("openrouter/anthropic/claude-3-opus")
|
||||||
expect(result.providerID).toBe("openrouter")
|
expect(String(result.providerID)).toBe("openrouter")
|
||||||
expect(result.modelID).toBe("anthropic/claude-3-opus")
|
expect(String(result.modelID)).toBe("anthropic/claude-3-opus")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("defaultModel returns first available model when no config set", async () => {
|
test("defaultModel returns first available model when no config set", async () => {
|
||||||
|
|
@ -406,8 +406,8 @@ test("defaultModel respects config model setting", async () => {
|
||||||
},
|
},
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const model = await Provider.defaultModel()
|
const model = await Provider.defaultModel()
|
||||||
expect(model.providerID).toBe("anthropic")
|
expect(String(model.providerID)).toBe("anthropic")
|
||||||
expect(model.modelID).toBe("claude-sonnet-4-20250514")
|
expect(String(model.modelID)).toBe("claude-sonnet-4-20250514")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -632,7 +632,7 @@ test("getModel uses realIdByKey for aliased models", async () => {
|
||||||
|
|
||||||
const model = await Provider.getModel("anthropic", "my-sonnet")
|
const model = await Provider.getModel("anthropic", "my-sonnet")
|
||||||
expect(model).toBeDefined()
|
expect(model).toBeDefined()
|
||||||
expect(model.id).toBe("my-sonnet")
|
expect(String(model.id)).toBe("my-sonnet")
|
||||||
expect(model.name).toBe("My Sonnet Alias")
|
expect(model.name).toBe("My Sonnet Alias")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -960,8 +960,8 @@ test("getSmallModel respects config small_model override", async () => {
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const model = await Provider.getSmallModel("anthropic")
|
const model = await Provider.getSmallModel("anthropic")
|
||||||
expect(model).toBeDefined()
|
expect(model).toBeDefined()
|
||||||
expect(model?.providerID).toBe("anthropic")
|
expect(String(model?.providerID)).toBe("anthropic")
|
||||||
expect(model?.id).toBe("claude-sonnet-4-20250514")
|
expect(String(model?.id)).toBe("claude-sonnet-4-20250514")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -1605,7 +1605,7 @@ test("getProvider returns provider info", async () => {
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const provider = await Provider.getProvider("anthropic")
|
const provider = await Provider.getProvider("anthropic")
|
||||||
expect(provider).toBeDefined()
|
expect(provider).toBeDefined()
|
||||||
expect(provider?.id).toBe("anthropic")
|
expect(String(provider?.id)).toBe("anthropic")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import { ProviderTransform } from "../../src/provider/transform"
|
import { ProviderTransform } from "../../src/provider/transform"
|
||||||
|
import { ModelID, ProviderID } from "../../src/provider/schema"
|
||||||
|
|
||||||
const OUTPUT_TOKEN_MAX = 32000
|
const OUTPUT_TOKEN_MAX = 32000
|
||||||
|
|
||||||
|
|
@ -740,8 +741,8 @@ describe("ProviderTransform.message - DeepSeek reasoning content", () => {
|
||||||
const result = ProviderTransform.message(
|
const result = ProviderTransform.message(
|
||||||
msgs,
|
msgs,
|
||||||
{
|
{
|
||||||
id: "deepseek/deepseek-chat",
|
id: ModelID.make("deepseek/deepseek-chat"),
|
||||||
providerID: "deepseek",
|
providerID: ProviderID.make("deepseek"),
|
||||||
api: {
|
api: {
|
||||||
id: "deepseek-chat",
|
id: "deepseek-chat",
|
||||||
url: "https://api.deepseek.com",
|
url: "https://api.deepseek.com",
|
||||||
|
|
@ -802,8 +803,8 @@ describe("ProviderTransform.message - DeepSeek reasoning content", () => {
|
||||||
const result = ProviderTransform.message(
|
const result = ProviderTransform.message(
|
||||||
msgs,
|
msgs,
|
||||||
{
|
{
|
||||||
id: "openai/gpt-4",
|
id: ModelID.make("openai/gpt-4"),
|
||||||
providerID: "openai",
|
providerID: ProviderID.make("openai"),
|
||||||
api: {
|
api: {
|
||||||
id: "gpt-4",
|
id: "gpt-4",
|
||||||
url: "https://api.openai.com",
|
url: "https://api.openai.com",
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { Instance } from "../../src/project/instance"
|
||||||
import { Provider } from "../../src/provider/provider"
|
import { Provider } from "../../src/provider/provider"
|
||||||
import { ProviderTransform } from "../../src/provider/transform"
|
import { ProviderTransform } from "../../src/provider/transform"
|
||||||
import { ModelsDev } from "../../src/provider/models"
|
import { ModelsDev } from "../../src/provider/models"
|
||||||
|
import { ProviderID } from "../../src/provider/schema"
|
||||||
import { Filesystem } from "../../src/util/filesystem"
|
import { Filesystem } from "../../src/util/filesystem"
|
||||||
import { tmpdir } from "../fixture/fixture"
|
import { tmpdir } from "../fixture/fixture"
|
||||||
import type { Agent } from "../../src/agent/agent"
|
import type { Agent } from "../../src/agent/agent"
|
||||||
|
|
@ -282,7 +283,7 @@ describe("session.llm.stream", () => {
|
||||||
role: "user",
|
role: "user",
|
||||||
time: { created: Date.now() },
|
time: { created: Date.now() },
|
||||||
agent: agent.name,
|
agent: agent.name,
|
||||||
model: { providerID, modelID: resolved.id },
|
model: { providerID: ProviderID.make(providerID), modelID: resolved.id },
|
||||||
variant: "high",
|
variant: "high",
|
||||||
} satisfies MessageV2.User
|
} satisfies MessageV2.User
|
||||||
|
|
||||||
|
|
@ -411,7 +412,7 @@ describe("session.llm.stream", () => {
|
||||||
role: "user",
|
role: "user",
|
||||||
time: { created: Date.now() },
|
time: { created: Date.now() },
|
||||||
agent: agent.name,
|
agent: agent.name,
|
||||||
model: { providerID: "openai", modelID: resolved.id },
|
model: { providerID: ProviderID.make("openai"), modelID: resolved.id },
|
||||||
variant: "high",
|
variant: "high",
|
||||||
} satisfies MessageV2.User
|
} satisfies MessageV2.User
|
||||||
|
|
||||||
|
|
@ -534,7 +535,7 @@ describe("session.llm.stream", () => {
|
||||||
role: "user",
|
role: "user",
|
||||||
time: { created: Date.now() },
|
time: { created: Date.now() },
|
||||||
agent: agent.name,
|
agent: agent.name,
|
||||||
model: { providerID, modelID: resolved.id },
|
model: { providerID: ProviderID.make(providerID), modelID: resolved.id },
|
||||||
} satisfies MessageV2.User
|
} satisfies MessageV2.User
|
||||||
|
|
||||||
const stream = await LLM.stream({
|
const stream = await LLM.stream({
|
||||||
|
|
@ -635,7 +636,7 @@ describe("session.llm.stream", () => {
|
||||||
role: "user",
|
role: "user",
|
||||||
time: { created: Date.now() },
|
time: { created: Date.now() },
|
||||||
agent: agent.name,
|
agent: agent.name,
|
||||||
model: { providerID, modelID: resolved.id },
|
model: { providerID: ProviderID.make(providerID), modelID: resolved.id },
|
||||||
} satisfies MessageV2.User
|
} satisfies MessageV2.User
|
||||||
|
|
||||||
const stream = await LLM.stream({
|
const stream = await LLM.stream({
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,14 @@ import { describe, expect, test } from "bun:test"
|
||||||
import { APICallError } from "ai"
|
import { APICallError } from "ai"
|
||||||
import { MessageV2 } from "../../src/session/message-v2"
|
import { MessageV2 } from "../../src/session/message-v2"
|
||||||
import type { Provider } from "../../src/provider/provider"
|
import type { Provider } from "../../src/provider/provider"
|
||||||
|
import { ModelID, ProviderID } from "../../src/provider/schema"
|
||||||
import { SessionID, MessageID, PartID } from "../../src/session/schema"
|
import { SessionID, MessageID, PartID } from "../../src/session/schema"
|
||||||
|
|
||||||
const sessionID = SessionID.make("session")
|
const sessionID = SessionID.make("session")
|
||||||
|
const providerID = ProviderID.make("test")
|
||||||
const model: Provider.Model = {
|
const model: Provider.Model = {
|
||||||
id: "test-model",
|
id: ModelID.make("test-model"),
|
||||||
providerID: "test",
|
providerID,
|
||||||
api: {
|
api: {
|
||||||
id: "test-model",
|
id: "test-model",
|
||||||
url: "https://example.com",
|
url: "https://example.com",
|
||||||
|
|
@ -61,7 +63,7 @@ function userInfo(id: string): MessageV2.User {
|
||||||
role: "user",
|
role: "user",
|
||||||
time: { created: 0 },
|
time: { created: 0 },
|
||||||
agent: "user",
|
agent: "user",
|
||||||
model: { providerID: "test", modelID: "test" },
|
model: { providerID, modelID: ModelID.make("test") },
|
||||||
tools: {},
|
tools: {},
|
||||||
mode: "",
|
mode: "",
|
||||||
} as unknown as MessageV2.User
|
} as unknown as MessageV2.User
|
||||||
|
|
@ -795,7 +797,7 @@ describe("session.message-v2.fromError", () => {
|
||||||
code: "context_length_exceeded",
|
code: "context_length_exceeded",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const result = MessageV2.fromError(input, { providerID: "test" })
|
const result = MessageV2.fromError(input, { providerID })
|
||||||
|
|
||||||
expect(result).toStrictEqual({
|
expect(result).toStrictEqual({
|
||||||
name: "ContextOverflowError",
|
name: "ContextOverflowError",
|
||||||
|
|
@ -830,7 +832,7 @@ describe("session.message-v2.fromError", () => {
|
||||||
message: item.code === "invalid_prompt" ? item.message : undefined,
|
message: item.code === "invalid_prompt" ? item.message : undefined,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const result = MessageV2.fromError(input, { providerID: "test" })
|
const result = MessageV2.fromError(input, { providerID })
|
||||||
|
|
||||||
expect(result).toStrictEqual({
|
expect(result).toStrictEqual({
|
||||||
name: "APIError",
|
name: "APIError",
|
||||||
|
|
@ -862,7 +864,7 @@ describe("session.message-v2.fromError", () => {
|
||||||
responseHeaders: { "content-type": "application/json" },
|
responseHeaders: { "content-type": "application/json" },
|
||||||
isRetryable: false,
|
isRetryable: false,
|
||||||
})
|
})
|
||||||
const result = MessageV2.fromError(error, { providerID: "test" })
|
const result = MessageV2.fromError(error, { providerID })
|
||||||
expect(MessageV2.ContextOverflowError.isInstance(result)).toBe(true)
|
expect(MessageV2.ContextOverflowError.isInstance(result)).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -877,14 +879,14 @@ describe("session.message-v2.fromError", () => {
|
||||||
responseHeaders: { "content-type": "application/json" },
|
responseHeaders: { "content-type": "application/json" },
|
||||||
isRetryable: false,
|
isRetryable: false,
|
||||||
}),
|
}),
|
||||||
{ providerID: "test" },
|
{ providerID },
|
||||||
)
|
)
|
||||||
expect(MessageV2.ContextOverflowError.isInstance(result)).toBe(false)
|
expect(MessageV2.ContextOverflowError.isInstance(result)).toBe(false)
|
||||||
expect(MessageV2.APIError.isInstance(result)).toBe(true)
|
expect(MessageV2.APIError.isInstance(result)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("serializes unknown inputs", () => {
|
test("serializes unknown inputs", () => {
|
||||||
const result = MessageV2.fromError(123, { providerID: "test" })
|
const result = MessageV2.fromError(123, { providerID })
|
||||||
|
|
||||||
expect(result).toStrictEqual({
|
expect(result).toStrictEqual({
|
||||||
name: "UnknownError",
|
name: "UnknownError",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import path from "path"
|
||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import { fileURLToPath } from "url"
|
import { fileURLToPath } from "url"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
|
import { ModelID, ProviderID } from "../../src/provider/schema"
|
||||||
import { Session } from "../../src/session"
|
import { Session } from "../../src/session"
|
||||||
import { MessageV2 } from "../../src/session/message-v2"
|
import { MessageV2 } from "../../src/session/message-v2"
|
||||||
import { SessionPrompt } from "../../src/session/prompt"
|
import { SessionPrompt } from "../../src/session/prompt"
|
||||||
|
|
@ -173,7 +174,7 @@ describe("session.prompt agent variant", () => {
|
||||||
const other = await SessionPrompt.prompt({
|
const other = await SessionPrompt.prompt({
|
||||||
sessionID: session.id,
|
sessionID: session.id,
|
||||||
agent: "build",
|
agent: "build",
|
||||||
model: { providerID: "opencode", modelID: "kimi-k2.5-free" },
|
model: { providerID: ProviderID.make("opencode"), modelID: ModelID.make("kimi-k2.5-free") },
|
||||||
noReply: true,
|
noReply: true,
|
||||||
parts: [{ type: "text", text: "hello" }],
|
parts: [{ type: "text", text: "hello" }],
|
||||||
})
|
})
|
||||||
|
|
@ -187,7 +188,7 @@ describe("session.prompt agent variant", () => {
|
||||||
parts: [{ type: "text", text: "hello again" }],
|
parts: [{ type: "text", text: "hello again" }],
|
||||||
})
|
})
|
||||||
if (match.info.role !== "user") throw new Error("expected user message")
|
if (match.info.role !== "user") throw new Error("expected user message")
|
||||||
expect(match.info.model).toEqual({ providerID: "openai", modelID: "gpt-5.2" })
|
expect(match.info.model).toEqual({ providerID: ProviderID.make("openai"), modelID: ModelID.make("gpt-5.2") })
|
||||||
expect(match.info.variant).toBe("xhigh")
|
expect(match.info.variant).toBe("xhigh")
|
||||||
|
|
||||||
const override = await SessionPrompt.prompt({
|
const override = await SessionPrompt.prompt({
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import { APICallError } from "ai"
|
||||||
import { setTimeout as sleep } from "node:timers/promises"
|
import { setTimeout as sleep } from "node:timers/promises"
|
||||||
import { SessionRetry } from "../../src/session/retry"
|
import { SessionRetry } from "../../src/session/retry"
|
||||||
import { MessageV2 } from "../../src/session/message-v2"
|
import { MessageV2 } from "../../src/session/message-v2"
|
||||||
|
import { ProviderID } from "../../src/provider/schema"
|
||||||
|
|
||||||
|
const providerID = ProviderID.make("test")
|
||||||
|
|
||||||
function apiError(headers?: Record<string, string>): MessageV2.APIError {
|
function apiError(headers?: Record<string, string>): MessageV2.APIError {
|
||||||
return new MessageV2.APIError({
|
return new MessageV2.APIError({
|
||||||
|
|
@ -150,7 +153,7 @@ describe("session.message-v2.fromError", () => {
|
||||||
.then((res) => res.text())
|
.then((res) => res.text())
|
||||||
.catch((e) => e)
|
.catch((e) => e)
|
||||||
|
|
||||||
const result = MessageV2.fromError(error, { providerID: "test" })
|
const result = MessageV2.fromError(error, { providerID })
|
||||||
|
|
||||||
expect(MessageV2.APIError.isInstance(result)).toBe(true)
|
expect(MessageV2.APIError.isInstance(result)).toBe(true)
|
||||||
expect((result as MessageV2.APIError).data.isRetryable).toBe(true)
|
expect((result as MessageV2.APIError).data.isRetryable).toBe(true)
|
||||||
|
|
@ -183,7 +186,7 @@ describe("session.message-v2.fromError", () => {
|
||||||
responseBody: '{"error":"boom"}',
|
responseBody: '{"error":"boom"}',
|
||||||
isRetryable: false,
|
isRetryable: false,
|
||||||
})
|
})
|
||||||
const result = MessageV2.fromError(error, { providerID: "openai" }) as MessageV2.APIError
|
const result = MessageV2.fromError(error, { providerID: ProviderID.make("openai") }) as MessageV2.APIError
|
||||||
expect(result.data.isRetryable).toBe(true)
|
expect(result.data.isRetryable).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { describe, expect, test, beforeEach, afterEach } from "bun:test"
|
import { describe, expect, test, beforeEach, afterEach } from "bun:test"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Session } from "../../src/session"
|
import { Session } from "../../src/session"
|
||||||
|
import { ModelID, ProviderID } from "../../src/provider/schema"
|
||||||
import { SessionRevert } from "../../src/session/revert"
|
import { SessionRevert } from "../../src/session/revert"
|
||||||
import { SessionCompaction } from "../../src/session/compaction"
|
import { SessionCompaction } from "../../src/session/compaction"
|
||||||
import { MessageV2 } from "../../src/session/message-v2"
|
import { MessageV2 } from "../../src/session/message-v2"
|
||||||
|
|
@ -29,8 +30,8 @@ describe("revert + compact workflow", () => {
|
||||||
sessionID,
|
sessionID,
|
||||||
agent: "default",
|
agent: "default",
|
||||||
model: {
|
model: {
|
||||||
providerID: "openai",
|
providerID: ProviderID.make("openai"),
|
||||||
modelID: "gpt-4",
|
modelID: ModelID.make("gpt-4"),
|
||||||
},
|
},
|
||||||
time: {
|
time: {
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
|
|
@ -64,8 +65,8 @@ describe("revert + compact workflow", () => {
|
||||||
reasoning: 0,
|
reasoning: 0,
|
||||||
cache: { read: 0, write: 0 },
|
cache: { read: 0, write: 0 },
|
||||||
},
|
},
|
||||||
modelID: "gpt-4",
|
modelID: ModelID.make("gpt-4"),
|
||||||
providerID: "openai",
|
providerID: ProviderID.make("openai"),
|
||||||
parentID: userMsg1.id,
|
parentID: userMsg1.id,
|
||||||
time: {
|
time: {
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
|
|
@ -90,8 +91,8 @@ describe("revert + compact workflow", () => {
|
||||||
sessionID,
|
sessionID,
|
||||||
agent: "default",
|
agent: "default",
|
||||||
model: {
|
model: {
|
||||||
providerID: "openai",
|
providerID: ProviderID.make("openai"),
|
||||||
modelID: "gpt-4",
|
modelID: ModelID.make("gpt-4"),
|
||||||
},
|
},
|
||||||
time: {
|
time: {
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
|
|
@ -124,8 +125,8 @@ describe("revert + compact workflow", () => {
|
||||||
reasoning: 0,
|
reasoning: 0,
|
||||||
cache: { read: 0, write: 0 },
|
cache: { read: 0, write: 0 },
|
||||||
},
|
},
|
||||||
modelID: "gpt-4",
|
modelID: ModelID.make("gpt-4"),
|
||||||
providerID: "openai",
|
providerID: ProviderID.make("openai"),
|
||||||
parentID: userMsg2.id,
|
parentID: userMsg2.id,
|
||||||
time: {
|
time: {
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
|
|
@ -205,8 +206,8 @@ describe("revert + compact workflow", () => {
|
||||||
sessionID,
|
sessionID,
|
||||||
agent: "default",
|
agent: "default",
|
||||||
model: {
|
model: {
|
||||||
providerID: "openai",
|
providerID: ProviderID.make("openai"),
|
||||||
modelID: "gpt-4",
|
modelID: ModelID.make("gpt-4"),
|
||||||
},
|
},
|
||||||
time: {
|
time: {
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
|
|
@ -238,8 +239,8 @@ describe("revert + compact workflow", () => {
|
||||||
reasoning: 0,
|
reasoning: 0,
|
||||||
cache: { read: 0, write: 0 },
|
cache: { read: 0, write: 0 },
|
||||||
},
|
},
|
||||||
modelID: "gpt-4",
|
modelID: ModelID.make("gpt-4"),
|
||||||
providerID: "openai",
|
providerID: ProviderID.make("openai"),
|
||||||
parentID: userMsg.id,
|
parentID: userMsg.id,
|
||||||
time: {
|
time: {
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue