mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-03 08:46:15 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f28cc51f12 |
@@ -163,7 +163,12 @@ export type SessionMessageProviderState7 = { [x: string]: any }
|
|||||||
|
|
||||||
export type EventLogSynced = { type: "log.synced"; aggregateID: string; seq?: number }
|
export type EventLogSynced = { type: "log.synced"; aggregateID: string; seq?: number }
|
||||||
|
|
||||||
export type ModelCapabilities = { tools: boolean; input: Array<string>; output: Array<string> }
|
export type ModelInterleavedField =
|
||||||
|
| "reasoning"
|
||||||
|
| "reasoning_content"
|
||||||
|
| "reasoning_text"
|
||||||
|
| "reasoning_details"
|
||||||
|
| (string & {})
|
||||||
|
|
||||||
export type ModelVariant = {
|
export type ModelVariant = {
|
||||||
id: string
|
id: string
|
||||||
@@ -1094,7 +1099,7 @@ export type TuiCommandExecute = {
|
|||||||
| "prompt.clear"
|
| "prompt.clear"
|
||||||
| "prompt.submit"
|
| "prompt.submit"
|
||||||
| "agent.cycle"
|
| "agent.cycle"
|
||||||
| string
|
| (string & {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1401,6 +1406,8 @@ export type SessionToolFailed = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ModelInterleaved = true | { field: ModelInterleavedField }
|
||||||
|
|
||||||
export type ModelCost = {
|
export type ModelCost = {
|
||||||
tier?: { type: "context"; size: number }
|
tier?: { type: "context"; size: number }
|
||||||
input: MoneyUSDPerMillionTokens
|
input: MoneyUSDPerMillionTokens
|
||||||
@@ -1885,23 +1892,11 @@ export type SessionMessageCompaction =
|
|||||||
| SessionMessageCompactionCompleted
|
| SessionMessageCompactionCompleted
|
||||||
| SessionMessageCompactionFailed
|
| SessionMessageCompactionFailed
|
||||||
|
|
||||||
export type ModelInfo = {
|
export type ModelCapabilities = {
|
||||||
id: string
|
tools: boolean
|
||||||
modelID: string
|
input: Array<string>
|
||||||
providerID: string
|
output: Array<string>
|
||||||
family?: string
|
interleaved?: ModelInterleaved
|
||||||
name: string
|
|
||||||
package?: string
|
|
||||||
settings?: { [x: string]: JsonValue }
|
|
||||||
headers?: { [x: string]: string }
|
|
||||||
body?: { [x: string]: JsonValue }
|
|
||||||
capabilities: ModelCapabilities
|
|
||||||
variants: Array<ModelVariant>
|
|
||||||
time: { released: number }
|
|
||||||
cost: Array<ModelCost>
|
|
||||||
status: "alpha" | "beta" | "deprecated" | "active"
|
|
||||||
enabled: boolean
|
|
||||||
limit: { context: number; input?: number; output: number }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IntegrationOAuthMethod = {
|
export type IntegrationOAuthMethod = {
|
||||||
@@ -2031,6 +2026,25 @@ export type SessionMessageAssistantTool = {
|
|||||||
time: { created: number; ran?: number; completed?: number }
|
time: { created: number; ran?: number; completed?: number }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ModelInfo = {
|
||||||
|
id: string
|
||||||
|
modelID: string
|
||||||
|
providerID: string
|
||||||
|
family?: string
|
||||||
|
name: string
|
||||||
|
package?: string
|
||||||
|
settings?: { [x: string]: JsonValue }
|
||||||
|
headers?: { [x: string]: string }
|
||||||
|
body?: { [x: string]: JsonValue }
|
||||||
|
capabilities: ModelCapabilities
|
||||||
|
variants: Array<ModelVariant>
|
||||||
|
time: { released: number }
|
||||||
|
cost: Array<ModelCost>
|
||||||
|
status: "alpha" | "beta" | "deprecated" | "active"
|
||||||
|
enabled: boolean
|
||||||
|
limit: { context: number; input?: number; output: number }
|
||||||
|
}
|
||||||
|
|
||||||
export type IntegrationMethod =
|
export type IntegrationMethod =
|
||||||
| IntegrationOAuthMethod
|
| IntegrationOAuthMethod
|
||||||
| IntegrationCommandMethod
|
| IntegrationCommandMethod
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ export const Plugin = define({
|
|||||||
tools: config.capabilities.tools,
|
tools: config.capabilities.tools,
|
||||||
input: [...config.capabilities.input],
|
input: [...config.capabilities.input],
|
||||||
output: [...config.capabilities.output],
|
output: [...config.capabilities.output],
|
||||||
|
...(config.capabilities.interleaved !== undefined
|
||||||
|
? { interleaved: config.capabilities.interleaved }
|
||||||
|
: {}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (config.variants !== undefined) {
|
if (config.variants !== undefined) {
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ export type VariantID = typeof VariantID.Type
|
|||||||
export const Family = Model.Family
|
export const Family = Model.Family
|
||||||
export type Family = Model.Family
|
export type Family = Model.Family
|
||||||
|
|
||||||
|
export const InterleavedField = Model.InterleavedField
|
||||||
|
export type InterleavedField = Model.InterleavedField
|
||||||
|
|
||||||
|
export const Interleaved = Model.Interleaved
|
||||||
|
export type Interleaved = Model.Interleaved
|
||||||
|
|
||||||
export const Capabilities = Model.Capabilities
|
export const Capabilities = Model.Capabilities
|
||||||
export type Capabilities = Model.Capabilities
|
export type Capabilities = Model.Capabilities
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ type SourceModel = {
|
|||||||
readonly reasoning_options?: readonly ReasoningOption[]
|
readonly reasoning_options?: readonly ReasoningOption[]
|
||||||
readonly temperature?: boolean
|
readonly temperature?: boolean
|
||||||
readonly tool_call: boolean
|
readonly tool_call: boolean
|
||||||
readonly interleaved?: true | { readonly field: "reasoning" | "reasoning_content" | "reasoning_details" }
|
readonly interleaved?: ModelV2.Interleaved
|
||||||
readonly cost?: Cost
|
readonly cost?: Cost
|
||||||
readonly limit: { readonly context: number; readonly input?: number; readonly output: number }
|
readonly limit: { readonly context: number; readonly input?: number; readonly output: number }
|
||||||
readonly modalities?: { readonly input: readonly Modality[]; readonly output: readonly Modality[] }
|
readonly modalities?: { readonly input: readonly Modality[]; readonly output: readonly Modality[] }
|
||||||
@@ -505,6 +505,7 @@ function modelInfo(
|
|||||||
tools: model.tool_call,
|
tools: model.tool_call,
|
||||||
input: [...(model.modalities?.input ?? [])],
|
input: [...(model.modalities?.input ?? [])],
|
||||||
output: [...(model.modalities?.output ?? [])],
|
output: [...(model.modalities?.output ?? [])],
|
||||||
|
...(model.interleaved !== undefined ? { interleaved: model.interleaved } : {}),
|
||||||
},
|
},
|
||||||
variants: [...(input.variants ?? [])],
|
variants: [...(input.variants ?? [])],
|
||||||
time: { released: released(model.release_date) },
|
time: { released: released(model.release_date) },
|
||||||
|
|||||||
@@ -271,8 +271,16 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type) {
|
|||||||
: []),
|
: []),
|
||||||
]
|
]
|
||||||
const capabilities =
|
const capabilities =
|
||||||
info.tool_call !== undefined || info.modalities?.input !== undefined || info.modalities?.output !== undefined
|
info.tool_call !== undefined ||
|
||||||
? { tools: info.tool_call ?? false, input: info.modalities?.input ?? [], output: info.modalities?.output ?? [] }
|
info.modalities?.input !== undefined ||
|
||||||
|
info.modalities?.output !== undefined ||
|
||||||
|
info.interleaved !== undefined
|
||||||
|
? {
|
||||||
|
tools: info.tool_call ?? false,
|
||||||
|
input: info.modalities?.input ?? [],
|
||||||
|
output: info.modalities?.output ?? [],
|
||||||
|
...(info.interleaved !== undefined ? { interleaved: info.interleaved } : {}),
|
||||||
|
}
|
||||||
: undefined
|
: undefined
|
||||||
return {
|
return {
|
||||||
modelID: info.id,
|
modelID: info.id,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export * as ConfigProviderV1 from "./provider"
|
export * as ConfigProviderV1 from "./provider"
|
||||||
|
|
||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
|
import { Interleaved } from "@opencode-ai/schema/model"
|
||||||
import { PositiveInt } from "../../schema"
|
import { PositiveInt } from "../../schema"
|
||||||
|
|
||||||
export const ModelStatus = Schema.Literals(["alpha", "beta", "deprecated", "active"])
|
export const ModelStatus = Schema.Literals(["alpha", "beta", "deprecated", "active"])
|
||||||
@@ -14,14 +15,7 @@ export const Model = Schema.Struct({
|
|||||||
reasoning: Schema.optional(Schema.Boolean),
|
reasoning: Schema.optional(Schema.Boolean),
|
||||||
temperature: Schema.optional(Schema.Boolean),
|
temperature: Schema.optional(Schema.Boolean),
|
||||||
tool_call: Schema.optional(Schema.Boolean),
|
tool_call: Schema.optional(Schema.Boolean),
|
||||||
interleaved: Schema.optional(
|
interleaved: Schema.optional(Interleaved),
|
||||||
Schema.Union([
|
|
||||||
Schema.Literal(true),
|
|
||||||
Schema.Struct({
|
|
||||||
field: Schema.Literals(["reasoning", "reasoning_content", "reasoning_details"]),
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
cost: Schema.optional(
|
cost: Schema.optional(
|
||||||
Schema.Struct({
|
Schema.Struct({
|
||||||
input: Schema.Finite,
|
input: Schema.Finite,
|
||||||
|
|||||||
@@ -289,6 +289,24 @@ describe("Config", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("migrates provider-specific interleaved reasoning fields", () =>
|
||||||
|
Effect.sync(() => {
|
||||||
|
const migrated = ConfigMigrateV1.migrate({
|
||||||
|
provider: {
|
||||||
|
custom: {
|
||||||
|
models: {
|
||||||
|
chat: { interleaved: { field: "vendor_reasoning" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(migrated.providers?.custom?.models?.chat?.capabilities?.interleaved).toEqual({
|
||||||
|
field: "vendor_reasoning",
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("migrates v1 provider lists to policies", () =>
|
it.effect("migrates v1 provider lists to policies", () =>
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
@@ -170,7 +170,12 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||||||
models: {
|
models: {
|
||||||
chat: {
|
chat: {
|
||||||
name: "First",
|
name: "First",
|
||||||
capabilities: { tools: true, input: ["text"], output: ["text"] },
|
capabilities: {
|
||||||
|
tools: true,
|
||||||
|
input: ["text"],
|
||||||
|
output: ["text"],
|
||||||
|
interleaved: { field: "vendor_reasoning" },
|
||||||
|
},
|
||||||
disabled: true,
|
disabled: true,
|
||||||
limit: { context: 100, output: 50 },
|
limit: { context: 100, output: 50 },
|
||||||
cost: { input: 1, output: 2 },
|
cost: { input: 1, output: 2 },
|
||||||
@@ -251,7 +256,12 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||||||
expect(model.id).toBe(modelID)
|
expect(model.id).toBe(modelID)
|
||||||
expect(model.modelID).toBe(ModelV2.ID.make("api-chat"))
|
expect(model.modelID).toBe(ModelV2.ID.make("api-chat"))
|
||||||
expect(model.name).toBe("Last")
|
expect(model.name).toBe("Last")
|
||||||
expect(model.capabilities).toEqual({ tools: true, input: ["text"], output: ["text"] })
|
expect(model.capabilities).toEqual({
|
||||||
|
tools: true,
|
||||||
|
input: ["text"],
|
||||||
|
output: ["text"],
|
||||||
|
interleaved: { field: "vendor_reasoning" },
|
||||||
|
})
|
||||||
expect(model.enabled).toBe(false)
|
expect(model.enabled).toBe(false)
|
||||||
expect(model.limit).toEqual({ context: 100, output: 75 })
|
expect(model.limit).toEqual({ context: 100, output: 75 })
|
||||||
expect(model.cost).toEqual([
|
expect(model.cost).toEqual([
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ const fixture = {
|
|||||||
reasoning: false,
|
reasoning: false,
|
||||||
temperature: true,
|
temperature: true,
|
||||||
tool_call: true,
|
tool_call: true,
|
||||||
|
interleaved: { field: "vendor_reasoning" },
|
||||||
limit: { context: 128000, output: 8192 },
|
limit: { context: 128000, output: 8192 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -69,7 +70,7 @@ const fixtureSnapshot = [
|
|||||||
family: undefined,
|
family: undefined,
|
||||||
package: undefined,
|
package: undefined,
|
||||||
settings: undefined,
|
settings: undefined,
|
||||||
capabilities: { tools: true, input: [], output: [] },
|
capabilities: { tools: true, input: [], output: [], interleaved: { field: "vendor_reasoning" } },
|
||||||
variants: [],
|
variants: [],
|
||||||
time: { released: Date.parse("2026-01-01") },
|
time: { released: Date.parse("2026-01-01") },
|
||||||
cost: [
|
cost: [
|
||||||
|
|||||||
@@ -830,7 +830,7 @@ function structuralTypes(schemas: ReadonlyArray<Schema.Top>, mutable: boolean, r
|
|||||||
.replaceAll(/ & Brand\.Brand<"[^"]+">/g, "")
|
.replaceAll(/ & Brand\.Brand<"[^"]+">/g, "")
|
||||||
.replaceAll("Schema.Json", "JsonValue")
|
.replaceAll("Schema.Json", "JsonValue")
|
||||||
.replaceAll(/(?<!["'])\bunknown\b(?!["'])/g, "any")
|
.replaceAll(/(?<!["'])\bunknown\b(?!["'])/g, "any")
|
||||||
return mutable ? mutableType(output) : output
|
return mutable ? mutableType(preserveStringSuggestions(output)) : preserveStringSuggestions(output)
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
types: document.codes.map((code) => render(code.Type)),
|
types: document.codes.map((code) => render(code.Type)),
|
||||||
@@ -870,9 +870,15 @@ function structuralType(schema: Schema.Top) {
|
|||||||
}
|
}
|
||||||
return type
|
return type
|
||||||
}
|
}
|
||||||
return expand(document.codes[0].Type)
|
return preserveStringSuggestions(
|
||||||
.replaceAll(/ & Brand\.Brand<"[^"]+">/g, "")
|
expand(document.codes[0].Type)
|
||||||
.replaceAll("Schema.Json", "JsonValue")
|
.replaceAll(/ & Brand\.Brand<"[^"]+">/g, "")
|
||||||
|
.replaceAll("Schema.Json", "JsonValue"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function preserveStringSuggestions(type: string) {
|
||||||
|
return type.replaceAll(/((?:"(?:\\.|[^"\\])*"\s*\|\s*)+)string\b/g, "$1(string & {})")
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePromiseClientContent(content: string, groups: ReadonlyArray<Group>) {
|
function normalizePromiseClientContent(content: string, groups: ReadonlyArray<Group>) {
|
||||||
|
|||||||
@@ -503,6 +503,19 @@ describe("HttpApiCodegen.generate", () => {
|
|||||||
expect(types).not.toContain("Brand")
|
expect(types).not.toContain("Brand")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("preserves suggestions for open string unions in Promise wire types", () => {
|
||||||
|
const Field = Schema.Union([Schema.Literals(["reasoning", "reasoning_content"]), Schema.String]).annotate({
|
||||||
|
identifier: "Field",
|
||||||
|
})
|
||||||
|
const output = emitPromise(
|
||||||
|
compileContract(api(HttpApiEndpoint.get("get", "/model", { success: Schema.Struct({ field: Field }) }))),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(output.files.find((file) => file.path === "types.ts")?.content).toContain(
|
||||||
|
'export type Field = "reasoning" | "reasoning_content" | (string & {})',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test("retains non-recursive references in Promise wire types", () => {
|
test("retains non-recursive references in Promise wire types", () => {
|
||||||
const Referenced = Schema.Struct({ value: Schema.String }).annotate({ identifier: "Referenced" })
|
const Referenced = Schema.Struct({ value: Schema.String }).annotate({ identifier: "Referenced" })
|
||||||
const output = emitPromise(
|
const output = emitPromise(
|
||||||
|
|||||||
@@ -41,11 +41,29 @@ export interface Ref extends Schema.Schema.Type<typeof Ref> {}
|
|||||||
export const Family = Schema.String.pipe(Schema.brand("Model.Family"))
|
export const Family = Schema.String.pipe(Schema.brand("Model.Family"))
|
||||||
export type Family = typeof Family.Type
|
export type Family = typeof Family.Type
|
||||||
|
|
||||||
|
export type InterleavedField =
|
||||||
|
| "reasoning"
|
||||||
|
| "reasoning_content"
|
||||||
|
| "reasoning_text"
|
||||||
|
| "reasoning_details"
|
||||||
|
| (string & {})
|
||||||
|
export const InterleavedField: Schema.Codec<InterleavedField> = Schema.Union([
|
||||||
|
Schema.Literals(["reasoning", "reasoning_content", "reasoning_text", "reasoning_details"]),
|
||||||
|
Schema.String,
|
||||||
|
]).annotate({ identifier: "Model.InterleavedField" })
|
||||||
|
|
||||||
|
export type Interleaved = true | { readonly field: InterleavedField }
|
||||||
|
export const Interleaved: Schema.Codec<Interleaved> = Schema.Union([
|
||||||
|
Schema.Literal(true),
|
||||||
|
Schema.Struct({ field: InterleavedField }),
|
||||||
|
]).annotate({ identifier: "Model.Interleaved" })
|
||||||
|
|
||||||
export interface Capabilities extends Schema.Schema.Type<typeof Capabilities> {}
|
export interface Capabilities extends Schema.Schema.Type<typeof Capabilities> {}
|
||||||
export const Capabilities = Schema.Struct({
|
export const Capabilities = Schema.Struct({
|
||||||
tools: Schema.Boolean,
|
tools: Schema.Boolean,
|
||||||
input: Schema.Array(Schema.String),
|
input: Schema.Array(Schema.String),
|
||||||
output: Schema.Array(Schema.String),
|
output: Schema.Array(Schema.String),
|
||||||
|
interleaved: Interleaved.pipe(optional),
|
||||||
}).annotate({ identifier: "Model.Capabilities" })
|
}).annotate({ identifier: "Model.Capabilities" })
|
||||||
|
|
||||||
export interface Cost extends Schema.Schema.Type<typeof Cost> {}
|
export interface Cost extends Schema.Schema.Type<typeof Cost> {}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import { Schema } from "effect"
|
||||||
import { Model } from "../src/model.js"
|
import { Model } from "../src/model.js"
|
||||||
|
|
||||||
describe("Model.Ref", () => {
|
describe("Model.Ref", () => {
|
||||||
@@ -20,3 +21,13 @@ describe("Model.Ref", () => {
|
|||||||
expect(() => Model.Ref.parse("openai/gpt-5#high#extra")).toThrow()
|
expect(() => Model.Ref.parse("openai/gpt-5#high#extra")).toThrow()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("Model.Interleaved", () => {
|
||||||
|
test("accepts known and provider-specific fields", () => {
|
||||||
|
const decode = Schema.decodeUnknownSync(Model.Interleaved)
|
||||||
|
const fields = ["reasoning", "reasoning_content", "reasoning_text", "reasoning_details", "vendor_reasoning"]
|
||||||
|
|
||||||
|
for (const field of fields) expect(decode({ field })).toEqual({ field })
|
||||||
|
expect(decode(true)).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user