chore: sync native provider stack
This commit is contained in:
commit
315ba3720e
29 changed files with 249 additions and 787 deletions
|
|
@ -57,13 +57,6 @@ The bounded projection of a Core-executed tool result persisted in Session histo
|
|||
**Managed Tool Output File**:
|
||||
A temporary file created under OpenCode's shared tool-output directory to retain complete output that was too large for Session history.
|
||||
|
||||
**Model Request Options**:
|
||||
Provider-semantic model settings selected from the Catalog and active Session variant before the LLM protocol adapter encodes them for a provider request.
|
||||
_Avoid_: Request body, wire options
|
||||
|
||||
**Generation Controls**:
|
||||
Provider-neutral sampling and output controls, partitioned from provider semantics and compatibility wire fields when model metadata enters the Catalog.
|
||||
|
||||
**PTY Environment**:
|
||||
The host-supplied environment overlay applied by the server when creating a PTY, observed for the request Location and resolved PTY working directory.
|
||||
|
||||
|
|
@ -114,8 +107,6 @@ The host-supplied environment overlay applied by the server when creating a PTY,
|
|||
- A **Baseline System Context** durably preserves the exact joined text used for the active provider-cache prefix.
|
||||
- Completed compaction starts a new **Context Epoch** on the next provider attempt, folding the current complete **System Context** into a fresh baseline and removing earlier **Mid-Conversation System Messages** from active model history.
|
||||
- A model/provider switch preserves the current **Context Epoch** and chronological conversation history; the new selection applies to the next provider turn.
|
||||
- **Model Request Options** remain provider-semantic through Catalog resolution. The Session runner maps them into the LLM package's provider-option namespace; the selected protocol adapter alone owns provider wire encoding.
|
||||
- **Generation Controls**, protocol-semantic **Model Request Options**, and compatibility request body fields are separate Catalog domains. A shared ingestion adapter partitions legacy and models.dev AI-SDK-shaped options before routing.
|
||||
- The **PTY Environment** is a server concern rather than a Core PTY concern. PTY creation merges caller values, then the host overlay, then Core-forced terminal invariants such as `TERM` and `OPENCODE_TERMINAL`.
|
||||
- A **PTY Environment** adapter observes plugins in the request Location while passing the resolved PTY working directory to the hook; standalone servers use an empty adapter.
|
||||
- A **Mid-Conversation System Message** lowers to the provider's native chronological instruction role when supported and to a wrapped chronological fallback otherwise.
|
||||
|
|
|
|||
|
|
@ -42,11 +42,12 @@ const inferenceEventTable = new aws.s3tables.Table(
|
|||
{ name: "request", type: "string", required: false },
|
||||
{ name: "client", type: "string", required: false },
|
||||
{ name: "user_agent", type: "string", required: false },
|
||||
{ name: "model", type: "string", required: false },
|
||||
{ name: "model_tier", type: "string", required: false },
|
||||
{ name: "model_variant", type: "string", required: false },
|
||||
{ name: "source", type: "string", required: false },
|
||||
{ name: "provider", type: "string", required: false },
|
||||
{ name: "provider_model", type: "string", required: false },
|
||||
{ name: "model", type: "string", required: false },
|
||||
{ name: "llm_error_code", type: "int", required: false },
|
||||
{ name: "llm_error_message", type: "string", required: false },
|
||||
{ name: "error_response", type: "string", required: false },
|
||||
|
|
|
|||
|
|
@ -118,11 +118,12 @@ function toLakeEvent(time: string, data: Record<string, unknown>) {
|
|||
request: string(data, "request"),
|
||||
client: string(data, "client"),
|
||||
user_agent: string(data, "user_agent"),
|
||||
model: string(data, "model"),
|
||||
model_tier: string(data, "model.tier"),
|
||||
model_variant: string(data, "model.variant"),
|
||||
source: string(data, "source"),
|
||||
provider: string(data, "provider"),
|
||||
provider_model: string(data, "provider.model"),
|
||||
model: string(data, "model"),
|
||||
llm_error_code: integer(data, "llm.error.code"),
|
||||
llm_error_message: string(data, "llm.error.message"),
|
||||
error_response: string(data, "error.response"),
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ export * as Catalog from "./catalog"
|
|||
|
||||
import { Array, Context, Effect, Layer, Option, Order, pipe, Schema } from "effect"
|
||||
import { ModelV2 } from "./model"
|
||||
import { ModelRequest } from "./model-request"
|
||||
import { ProviderV2 } from "./provider"
|
||||
import { EventV2 } from "./event"
|
||||
import { Policy } from "./policy"
|
||||
import { State } from "./state"
|
||||
import { Integration } from "./integration"
|
||||
import { ProviderOverlay } from "./provider-overlay"
|
||||
|
||||
export type ProviderRecord = {
|
||||
provider: ProviderV2.MutableInfo
|
||||
|
|
@ -83,26 +83,27 @@ export const layer = Layer.effect(
|
|||
...provider.api,
|
||||
id: model.api.id,
|
||||
url: model.api.url ?? provider.api.url,
|
||||
settings: ModelRequest.mergeRecords(provider.api.settings, model.api.settings),
|
||||
settings: ProviderOverlay.mergeRecords(provider.api.settings, model.api.settings),
|
||||
}
|
||||
: model.api.type === "native" && provider.api.type === "native" && !model.api.url
|
||||
? {
|
||||
...model.api,
|
||||
package: model.api.package ?? provider.api.package,
|
||||
url: provider.api.url,
|
||||
settings: ModelRequest.mergeRecords(provider.api.settings, model.api.settings),
|
||||
settings: ProviderOverlay.mergeRecords(provider.api.settings, model.api.settings),
|
||||
}
|
||||
: model.api.type === "aisdk" && provider.api.type === "aisdk" && !model.api.url
|
||||
? {
|
||||
...model.api,
|
||||
url: provider.api.url,
|
||||
settings: ModelRequest.mergeRecords(provider.api.settings, model.api.settings),
|
||||
settings: ProviderOverlay.mergeRecords(provider.api.settings, model.api.settings),
|
||||
}
|
||||
: model.api.type === "aisdk" && provider.api.type === "aisdk"
|
||||
? { ...model.api, settings: ModelRequest.mergeRecords(provider.api.settings, model.api.settings) }
|
||||
? { ...model.api, settings: ProviderOverlay.mergeRecords(provider.api.settings, model.api.settings) }
|
||||
: model.api
|
||||
const request = {
|
||||
...ModelRequest.merge({ ...provider.request, generation: {}, options: {} }, model.request),
|
||||
headers: ProviderOverlay.mergeHeaders(provider.request.headers, model.request.headers),
|
||||
body: ProviderOverlay.mergeRecords(provider.request.body, model.request.body),
|
||||
variant: model.request.variant,
|
||||
}
|
||||
return ModelV2.Info.make({
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { define } from "../../plugin/internal"
|
|||
import { Effect } from "effect"
|
||||
import { Config } from "../../config"
|
||||
import { ModelV2 } from "../../model"
|
||||
import { ModelRequest } from "../../model-request"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { ProviderOverlay } from "../../provider-overlay"
|
||||
|
||||
export const Plugin = define({
|
||||
id: "config-provider",
|
||||
|
|
@ -57,7 +57,7 @@ export const Plugin = define({
|
|||
catalog.provider.update(providerID, (provider) => {
|
||||
if (item.name !== undefined) provider.name = item.name
|
||||
if (item.package !== undefined) {
|
||||
const settings = ModelRequest.mergeRecords(provider.api.settings, item.settings)
|
||||
const settings = ProviderOverlay.mergeRecords(provider.api.settings, item.settings)
|
||||
const url =
|
||||
item.settings && Object.hasOwn(item.settings, "baseURL")
|
||||
? typeof settings.baseURL === "string"
|
||||
|
|
@ -68,7 +68,7 @@ export const Plugin = define({
|
|||
? { type: "aisdk", package: item.package, ...(url === undefined ? {} : { url }), settings }
|
||||
: { type: "native", package: item.package, ...(url === undefined ? {} : { url }), settings }
|
||||
} else if (item.settings !== undefined) {
|
||||
provider.api.settings = ModelRequest.mergeRecords(provider.api.settings, item.settings)
|
||||
provider.api.settings = ProviderOverlay.mergeRecords(provider.api.settings, item.settings)
|
||||
if (Object.hasOwn(item.settings, "baseURL")) {
|
||||
provider.api.url =
|
||||
typeof provider.api.settings.baseURL === "string" ? provider.api.settings.baseURL : undefined
|
||||
|
|
@ -82,11 +82,9 @@ export const Plugin = define({
|
|||
provider.api = { ...provider.api, type: "native", settings: provider.api.settings ?? {} }
|
||||
}
|
||||
}
|
||||
ModelRequest.assign(provider.request, { headers: item.headers, body: item.body })
|
||||
ProviderOverlay.assign(provider.request, { headers: item.headers, body: item.body })
|
||||
})
|
||||
const providerApi = catalog.provider.get(providerID)?.provider.api
|
||||
const providerPackage = providerApi?.type === "aisdk" ? providerApi.package : undefined
|
||||
|
||||
for (const [id, config] of Object.entries(item.models ?? {})) {
|
||||
const modelKey = `${providerID}/${id}`
|
||||
if (config.aiSDK !== undefined) modelAiSDK.set(modelKey, config.aiSDK)
|
||||
|
|
@ -97,7 +95,7 @@ export const Plugin = define({
|
|||
if (config.package !== undefined) {
|
||||
const aiSDK =
|
||||
modelAiSDK.get(modelKey) ?? providerAiSDK.get(providerID) ?? providerApi?.type === "aisdk"
|
||||
const settings = ModelRequest.mergeRecords(model.api.settings, config.settings)
|
||||
const settings = ProviderOverlay.mergeRecords(model.api.settings, config.settings)
|
||||
const url =
|
||||
config.settings && Object.hasOwn(config.settings, "baseURL")
|
||||
? typeof settings.baseURL === "string"
|
||||
|
|
@ -120,7 +118,7 @@ export const Plugin = define({
|
|||
settings,
|
||||
}
|
||||
} else if (config.settings !== undefined) {
|
||||
model.api.settings = ModelRequest.mergeRecords(model.api.settings, config.settings)
|
||||
model.api.settings = ProviderOverlay.mergeRecords(model.api.settings, config.settings)
|
||||
if (Object.hasOwn(config.settings, "baseURL")) {
|
||||
model.api.url =
|
||||
typeof model.api.settings.baseURL === "string" ? model.api.settings.baseURL : undefined
|
||||
|
|
@ -134,9 +132,6 @@ export const Plugin = define({
|
|||
model.api = { ...model.api, type: "native", settings: model.api.settings ?? {} }
|
||||
}
|
||||
}
|
||||
const packageName = model.api.type === "aisdk" ? model.api.package : providerPackage
|
||||
const aiSDK =
|
||||
modelAiSDK.get(modelKey) ?? providerAiSDK.get(providerID) ?? providerApi?.type === "aisdk"
|
||||
if (config.capabilities !== undefined) {
|
||||
model.capabilities = {
|
||||
tools: config.capabilities.tools,
|
||||
|
|
@ -145,12 +140,7 @@ export const Plugin = define({
|
|||
}
|
||||
}
|
||||
if (config.headers !== undefined || config.body !== undefined) {
|
||||
ModelRequest.assign(model.request, {
|
||||
headers: config.headers,
|
||||
...(aiSDK
|
||||
? ModelRequest.normalizeAiSdkOptions(packageName, config.body ?? {})
|
||||
: { body: config.body }),
|
||||
})
|
||||
ProviderOverlay.assign(model.request, { headers: config.headers, body: config.body })
|
||||
}
|
||||
if (config.variant !== undefined) model.request.variant = config.variant
|
||||
if (config.variants !== undefined) {
|
||||
|
|
@ -162,18 +152,11 @@ export const Plugin = define({
|
|||
settings: {},
|
||||
headers: {},
|
||||
body: {},
|
||||
generation: {},
|
||||
options: {},
|
||||
}
|
||||
model.variants.push(existing)
|
||||
}
|
||||
existing.settings = ModelRequest.mergeRecords(existing.settings, variant.settings)
|
||||
ModelRequest.assign(existing, {
|
||||
headers: variant.headers,
|
||||
...(aiSDK
|
||||
? ModelRequest.normalizeAiSdkOptions(packageName, variant.body ?? {})
|
||||
: { body: variant.body }),
|
||||
})
|
||||
existing.settings = ProviderOverlay.mergeRecords(existing.settings, variant.settings)
|
||||
ProviderOverlay.assign(existing, { headers: variant.headers, body: variant.body })
|
||||
}
|
||||
}
|
||||
if (config.cost !== undefined) {
|
||||
|
|
|
|||
|
|
@ -1,127 +0,0 @@
|
|||
export * as ModelRequest from "./model-request"
|
||||
|
||||
import { ModelRequest } from "@opencode-ai/schema/model-request"
|
||||
|
||||
export const Generation = ModelRequest.Generation
|
||||
export type Generation = ModelRequest.Generation
|
||||
|
||||
export const Request = ModelRequest.Request
|
||||
export type Request = ModelRequest.Request
|
||||
|
||||
interface MutableRequest {
|
||||
headers: Record<string, string>
|
||||
body: Record<string, unknown>
|
||||
generation?: Record<string, unknown>
|
||||
options?: Record<string, unknown>
|
||||
}
|
||||
|
||||
const generationKeys = new Map<string, keyof Generation>([
|
||||
["maxOutputTokens", "maxTokens"],
|
||||
["maxTokens", "maxTokens"],
|
||||
["temperature", "temperature"],
|
||||
["topP", "topP"],
|
||||
["topK", "topK"],
|
||||
["frequencyPenalty", "frequencyPenalty"],
|
||||
["presencePenalty", "presencePenalty"],
|
||||
["seed", "seed"],
|
||||
["stopSequences", "stop"],
|
||||
["stop", "stop"],
|
||||
])
|
||||
|
||||
interface Profile {
|
||||
readonly namespace: string
|
||||
readonly semantics: ReadonlyMap<string, string>
|
||||
}
|
||||
|
||||
const profiles = new Map<string, Profile>([
|
||||
[
|
||||
"@ai-sdk/openai",
|
||||
{
|
||||
namespace: "openai",
|
||||
semantics: new Map([
|
||||
["store", "store"],
|
||||
["promptCacheKey", "promptCacheKey"],
|
||||
["reasoningEffort", "reasoningEffort"],
|
||||
["reasoningSummary", "reasoningSummary"],
|
||||
["include", "include"],
|
||||
["textVerbosity", "textVerbosity"],
|
||||
["serviceTier", "serviceTier"],
|
||||
["service_tier", "serviceTier"],
|
||||
]),
|
||||
},
|
||||
],
|
||||
[
|
||||
"@ai-sdk/openai-compatible",
|
||||
{
|
||||
namespace: "openai",
|
||||
semantics: new Map([
|
||||
["store", "store"],
|
||||
["promptCacheKey", "promptCacheKey"],
|
||||
["reasoningEffort", "reasoningEffort"],
|
||||
["reasoning_effort", "reasoningEffort"],
|
||||
]),
|
||||
},
|
||||
],
|
||||
["@ai-sdk/anthropic", { namespace: "anthropic", semantics: new Map([["thinking", "thinking"]]) }],
|
||||
])
|
||||
|
||||
export const namespace = (packageName: string) => profiles.get(packageName)?.namespace
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
|
||||
export const mergeRecords = (...items: ReadonlyArray<Readonly<Record<string, unknown>> | undefined>) => {
|
||||
const result: Record<string, unknown> = {}
|
||||
for (const item of items) {
|
||||
for (const [key, value] of Object.entries(item ?? {})) {
|
||||
result[key] = isRecord(result[key]) && isRecord(value) ? mergeRecords(result[key], value) : value
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const mergeHeaders = (...items: ReadonlyArray<Readonly<Record<string, string>> | undefined>) => {
|
||||
const result = new Map<string, readonly [string, string]>()
|
||||
for (const item of items) {
|
||||
for (const entry of Object.entries(item ?? {})) result.set(entry[0].toLowerCase(), entry)
|
||||
}
|
||||
return Object.fromEntries(result.values())
|
||||
}
|
||||
|
||||
export const merge = (base: Request, override: Partial<Request>) => ({
|
||||
headers: mergeHeaders(base.headers, override.headers),
|
||||
body: mergeRecords(base.body, override.body),
|
||||
generation: { ...base.generation, ...override.generation },
|
||||
options: { ...base.options, ...override.options },
|
||||
})
|
||||
|
||||
export const assign = (target: MutableRequest, override: Partial<Request>) => {
|
||||
const headers = mergeHeaders(target.headers, override.headers)
|
||||
Object.keys(target.headers).forEach((key) => delete target.headers[key])
|
||||
Object.assign(target.headers, headers)
|
||||
const body = mergeRecords(target.body, override.body)
|
||||
Object.keys(target.body).forEach((key) => delete target.body[key])
|
||||
Object.assign(target.body, body)
|
||||
Object.assign((target.generation ??= {}), override.generation)
|
||||
Object.assign((target.options ??= {}), override.options)
|
||||
}
|
||||
|
||||
/** Partitions AI-SDK-shaped request options before they enter the Catalog. */
|
||||
export function normalizeAiSdkOptions(packageName: string | undefined, input: Readonly<Record<string, unknown>>) {
|
||||
const generation: Record<string, number | ReadonlyArray<string>> = {}
|
||||
const options: Record<string, unknown> = {}
|
||||
const body: Record<string, unknown> = {}
|
||||
const semantics = profiles.get(packageName ?? "")?.semantics
|
||||
|
||||
for (const [key, value] of Object.entries(input)) {
|
||||
const generationKey = generationKeys.get(key)
|
||||
if (generationKey === "stop" && Array.isArray(value) && value.every((item) => typeof item === "string"))
|
||||
generation[generationKey] = value
|
||||
else if (generationKey !== undefined && generationKey !== "stop" && typeof value === "number")
|
||||
generation[generationKey] = value
|
||||
else if (semantics?.has(key)) options[semantics.get(key)!] = value
|
||||
else body[key] = value
|
||||
}
|
||||
|
||||
return { generation, options, body }
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ import { CommandPlugin } from "./command"
|
|||
import { ModelsDevPlugin } from "./models-dev"
|
||||
import { ProviderPlugins } from "./provider"
|
||||
import { SkillPlugin } from "./skill"
|
||||
import { VariantPlugin } from "./variant"
|
||||
|
||||
export type Requirements =
|
||||
| AgentV2.Service
|
||||
|
|
@ -107,12 +108,13 @@ export const locationLayer = Layer.effectDiscard(
|
|||
yield* add(CommandPlugin.Plugin)
|
||||
yield* add(SkillPlugin.Plugin)
|
||||
yield* add(ModelsDevPlugin)
|
||||
yield* add(ConfigProviderPlugin.Plugin)
|
||||
yield* add(ConfigAgentPlugin.Plugin)
|
||||
yield* add(ConfigCommandPlugin.Plugin)
|
||||
yield* add(ConfigSkillPlugin.Plugin)
|
||||
for (const item of ProviderPlugins) yield* add(item)
|
||||
yield* add(ConfigExternalPlugin.Plugin)
|
||||
yield* add(ConfigProviderPlugin.Plugin)
|
||||
yield* add(VariantPlugin.Plugin)
|
||||
}).pipe(Effect.withSpan("PluginInternal.boot"), Effect.forkScoped({ startImmediately: true }))
|
||||
}),
|
||||
).pipe(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { define } from "./internal"
|
|||
import { Effect, Stream } from "effect"
|
||||
import { EventV2 } from "../event"
|
||||
import { ModelV2 } from "../model"
|
||||
import { ModelRequest } from "../model-request"
|
||||
import { ModelsDev } from "../models-dev"
|
||||
import { ProviderV2 } from "../provider"
|
||||
|
||||
|
|
@ -38,15 +37,12 @@ function cost(input: ModelsDev.Model["cost"]) {
|
|||
]
|
||||
}
|
||||
|
||||
function variants(model: ModelsDev.Model, packageName?: string) {
|
||||
return Object.entries(model.experimental?.modes ?? {}).map(([id, item]) => {
|
||||
const request = ModelRequest.normalizeAiSdkOptions(packageName, item.provider?.body ?? {})
|
||||
return {
|
||||
id: ModelV2.VariantID.make(id),
|
||||
headers: { ...(item.provider?.headers ?? {}) },
|
||||
...request,
|
||||
}
|
||||
})
|
||||
function variants(model: ModelsDev.Model) {
|
||||
return Object.entries(model.experimental?.modes ?? {}).map(([id, item]) => ({
|
||||
id: ModelV2.VariantID.make(id),
|
||||
headers: { ...(item.provider?.headers ?? {}) },
|
||||
body: { ...(item.provider?.body ?? {}) },
|
||||
}))
|
||||
}
|
||||
|
||||
export const ModelsDevPlugin = define({
|
||||
|
|
@ -115,7 +111,7 @@ export const ModelsDevPlugin = define({
|
|||
input: [...(model.modalities?.input ?? [])],
|
||||
output: [...(model.modalities?.output ?? [])],
|
||||
}
|
||||
draft.variants = variants(model, model.provider?.npm ?? item.npm)
|
||||
draft.variants = variants(model)
|
||||
draft.time.released = released(model.release_date)
|
||||
draft.cost = cost(model.cost)
|
||||
draft.status = model.status ?? "active"
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ import { EventV2 } from "../../event"
|
|||
import { Credential } from "../../credential"
|
||||
import { Integration } from "../../integration"
|
||||
import { ModelV2 } from "../../model"
|
||||
import { ModelRequest } from "../../model-request"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { ConfigProviderV1 } from "../../v1/config/provider"
|
||||
import { ConfigProviderOptionsV1 } from "../../v1/config/provider-options"
|
||||
import { ConfigV1 } from "../../v1/config/config"
|
||||
|
||||
const defaultServer = "https://console.opencode.ai"
|
||||
|
|
@ -142,15 +142,14 @@ export const OpencodePlugin = define<HttpClient.HttpClient | EventV2.Service | S
|
|||
if (config.modalities?.input !== undefined) model.capabilities.input = [...config.modalities.input]
|
||||
if (config.modalities?.output !== undefined) model.capabilities.output = [...config.modalities.output]
|
||||
const packageName = config.provider?.npm ?? item.npm
|
||||
ModelRequest.assign(model.request, {
|
||||
headers: config.headers,
|
||||
...ModelRequest.normalizeAiSdkOptions(packageName, withoutCredentials(config.options)),
|
||||
})
|
||||
const lowerer = ConfigProviderOptionsV1.get(packageName)
|
||||
Object.assign(model.request.headers, config.headers)
|
||||
Object.assign(model.request.body, lowerer.request(withoutCredentials(config.options)))
|
||||
if (config.variants !== undefined) {
|
||||
model.variants = Object.entries(config.variants).map(([id, options]) => ({
|
||||
id: ModelV2.VariantID.make(id),
|
||||
headers: { ...(options.headers ?? {}) },
|
||||
...ModelRequest.normalizeAiSdkOptions(packageName, withoutCredentials(options)),
|
||||
body: lowerer.request(withoutCredentials(options)),
|
||||
}))
|
||||
}
|
||||
if (config.release_date !== undefined) {
|
||||
|
|
|
|||
39
packages/core/src/plugin/variant.ts
Normal file
39
packages/core/src/plugin/variant.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
export * as VariantPlugin from "./variant"
|
||||
|
||||
import type { ModelV2Info } from "@opencode-ai/sdk/v2/types"
|
||||
import { Effect } from "effect"
|
||||
import { define } from "./internal"
|
||||
|
||||
export const Plugin = define({
|
||||
id: "variant",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((catalog) => {
|
||||
for (const record of catalog.provider.list()) {
|
||||
for (const model of record.models.values()) {
|
||||
catalog.model.update(model.providerID, model.id, (draft) => {
|
||||
const generated = generate(draft)
|
||||
if (generated.length === 0) return
|
||||
|
||||
const explicit = new Map(draft.variants.map((variant) => [variant.id, variant]))
|
||||
const generatedIDs = new Set(generated.map((variant) => variant.id))
|
||||
draft.variants = [
|
||||
...generated.map((variant) => explicit.get(variant.id) ?? variant),
|
||||
...draft.variants.filter((variant) => !generatedIDs.has(variant.id)),
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
})
|
||||
|
||||
export function generate(model: ModelV2Info): ModelV2Info["variants"] {
|
||||
if (model.api.type !== "aisdk" || model.api.package !== "@ai-sdk/openai-compatible") return []
|
||||
const ids = `${model.id} ${model.api.id}`.toLowerCase()
|
||||
if (!["glm-5.2", "glm-5-2", "glm-5p2"].some((name) => ids.includes(name))) return []
|
||||
return ["high", "max"].map((id) => ({
|
||||
id,
|
||||
headers: {},
|
||||
body: { reasoning_effort: id },
|
||||
}))
|
||||
}
|
||||
34
packages/core/src/provider-overlay.ts
Normal file
34
packages/core/src/provider-overlay.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
export * as ProviderOverlay from "./provider-overlay"
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
|
||||
export const mergeRecords = (...items: ReadonlyArray<Readonly<Record<string, unknown>> | undefined>) => {
|
||||
const result: Record<string, unknown> = {}
|
||||
for (const item of items) {
|
||||
for (const [key, value] of Object.entries(item ?? {})) {
|
||||
result[key] = isRecord(result[key]) && isRecord(value) ? mergeRecords(result[key], value) : value
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const mergeHeaders = (...items: ReadonlyArray<Readonly<Record<string, string>> | undefined>) => {
|
||||
const result = new Map<string, readonly [string, string]>()
|
||||
for (const item of items) {
|
||||
for (const entry of Object.entries(item ?? {})) result.set(entry[0].toLowerCase(), entry)
|
||||
}
|
||||
return Object.fromEntries(result.values())
|
||||
}
|
||||
|
||||
export const assign = (
|
||||
target: { headers: Record<string, string>; body: Record<string, unknown> },
|
||||
overlay: { readonly headers?: Readonly<Record<string, string>>; readonly body?: Readonly<Record<string, unknown>> },
|
||||
) => {
|
||||
const headers = mergeHeaders(target.headers, overlay.headers)
|
||||
Object.keys(target.headers).forEach((key) => delete target.headers[key])
|
||||
Object.assign(target.headers, headers)
|
||||
const body = mergeRecords(target.body, overlay.body)
|
||||
Object.keys(target.body).forEach((key) => delete target.body[key])
|
||||
Object.assign(target.body, body)
|
||||
}
|
||||
|
|
@ -11,8 +11,8 @@ import { Catalog } from "../../catalog"
|
|||
import { Credential } from "../../credential"
|
||||
import { Integration } from "../../integration"
|
||||
import { ModelV2 } from "../../model"
|
||||
import { ModelRequest } from "../../model-request"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { ProviderOverlay } from "../../provider-overlay"
|
||||
import { SessionSchema } from "../schema"
|
||||
|
||||
export class ModelNotSelectedError extends Schema.TaggedErrorClass<ModelNotSelectedError>()(
|
||||
|
|
@ -88,8 +88,6 @@ const apiKey = (model: ModelV2.Info, credential?: Credential.Value) => {
|
|||
}
|
||||
|
||||
const withDefaults = (model: ModelV2.Info, route: AnyRoute) => {
|
||||
const options = model.request.options ?? {}
|
||||
const namespace = model.api.type === "aisdk" ? ModelRequest.namespace(model.api.package) : undefined
|
||||
const body = model.request.body
|
||||
const httpBody = Object.hasOwn(body, "apiKey")
|
||||
? Object.fromEntries(Object.entries(body).filter(([key]) => key !== "apiKey"))
|
||||
|
|
@ -98,8 +96,6 @@ const withDefaults = (model: ModelV2.Info, route: AnyRoute) => {
|
|||
provider: model.providerID,
|
||||
endpoint: model.api.url === undefined ? undefined : { baseURL: model.api.url },
|
||||
headers: model.request.headers,
|
||||
generation: model.request.generation,
|
||||
providerOptions: namespace && Object.keys(options).length > 0 ? { [namespace]: options } : undefined,
|
||||
http: { body: httpBody },
|
||||
limits: { context: model.limit.context, output: model.limit.output },
|
||||
})
|
||||
|
|
@ -123,12 +119,12 @@ const withVariant = (
|
|||
variant
|
||||
? produce(model, (draft) => {
|
||||
if (variant.settings !== undefined) {
|
||||
draft.api.settings = ModelRequest.mergeRecords(draft.api.settings, variant.settings)
|
||||
draft.api.settings = ProviderOverlay.mergeRecords(draft.api.settings, variant.settings)
|
||||
if (Object.hasOwn(variant.settings, "baseURL")) {
|
||||
draft.api.url = typeof draft.api.settings.baseURL === "string" ? draft.api.settings.baseURL : undefined
|
||||
}
|
||||
}
|
||||
ModelRequest.assign(draft.request, variant)
|
||||
ProviderOverlay.assign(draft.request, variant)
|
||||
})
|
||||
: model,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { ConfigMCPV1 } from "./mcp"
|
|||
import { ConfigPermissionV1 } from "./permission"
|
||||
import { ConfigProviderV1 } from "./provider"
|
||||
import { ConfigProviderOptionsV1 } from "./provider-options"
|
||||
import { ModelRequest } from "../../model-request"
|
||||
|
||||
const keys = new Set([
|
||||
"logLevel",
|
||||
|
|
@ -191,11 +190,7 @@ function migrateProvider(info: ConfigProviderV1.Info) {
|
|||
function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: string) {
|
||||
const packageID = info.provider?.npm ?? packageName
|
||||
const lowerer = ConfigProviderOptionsV1.get(packageID)
|
||||
const ingest = (options: Readonly<Record<string, unknown>>) => {
|
||||
const request = ModelRequest.normalizeAiSdkOptions(packageID, options)
|
||||
return { ...lowerer.request(request.body), ...request.generation, ...request.options }
|
||||
}
|
||||
const request = info.options && ingest(info.options)
|
||||
const request = info.options && lowerer.request(info.options)
|
||||
const costs = info.cost && [
|
||||
{
|
||||
input: info.cost.input,
|
||||
|
|
@ -231,7 +226,7 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: st
|
|||
info.variants &&
|
||||
Object.entries(info.variants).map(([id, options]) => ({
|
||||
id,
|
||||
body: ingest(options),
|
||||
body: lowerer.request(options),
|
||||
})),
|
||||
cost: costs,
|
||||
disabled: info.status === "deprecated" ? true : undefined,
|
||||
|
|
|
|||
|
|
@ -234,16 +234,13 @@ describe("CatalogV2", () => {
|
|||
model.request.headers.shared = "model"
|
||||
model.request.body.model = true
|
||||
model.request.body.request = true
|
||||
const options = (model.request.options ??= {})
|
||||
options.shared = "model"
|
||||
options.model = true
|
||||
model.request.body.shared = "model"
|
||||
})
|
||||
})
|
||||
|
||||
const model = required(yield* catalog.model.get(providerID, modelID))
|
||||
expect(model.request.headers).toEqual({ provider: "provider", shared: "model", model: "model" })
|
||||
expect(model.request.body).toEqual({ provider: true, model: true, request: true })
|
||||
expect(model.request.options).toEqual({ shared: "model", model: true })
|
||||
expect(model.request.body).toEqual({ provider: true, model: true, request: true, shared: "model" })
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -597,8 +597,8 @@ describe("Config", () => {
|
|||
headers: { Authorization: "Bearer secret", "OpenAI-Organization": "org" },
|
||||
models: {
|
||||
model: {
|
||||
body: { temperature: 0.3, reasoningEffort: "high", serviceTier: "priority" },
|
||||
variants: [{ id: "high", body: { reasoningEffort: "high", reasoningSummary: "auto" } }],
|
||||
body: { temperature: 0.3, reasoning_effort: "high", service_tier: "priority" },
|
||||
variants: [{ id: "high", body: { reasoning_effort: "high", reasoning_summary: "auto" } }],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("partitions existing model variant bodies without changing config shape", () =>
|
||||
it.effect("keeps configured model variant bodies unchanged", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
const providerID = ProviderV2.ID.opencode
|
||||
|
|
@ -172,8 +172,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||
expect(model.variants).toMatchObject([
|
||||
{
|
||||
id: "high",
|
||||
body: {},
|
||||
options: {
|
||||
body: {
|
||||
reasoningEffort: "high",
|
||||
reasoningSummary: "auto",
|
||||
include: ["reasoning.encrypted_content"],
|
||||
|
|
@ -183,7 +182,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("uses the effective provider package across layered config", () =>
|
||||
it.effect("keeps layered model variant bodies unchanged", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
const providerID = ProviderV2.ID.opencode
|
||||
|
|
@ -225,8 +224,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||
const model = required(yield* catalog.model.get(providerID, modelID))
|
||||
expect(model.variants[0]).toMatchObject({
|
||||
id: "high",
|
||||
body: {},
|
||||
options: { reasoningEffort: "high" },
|
||||
body: { reasoningEffort: "high" },
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { ModelRequest } from "@opencode-ai/core/model-request"
|
||||
|
||||
describe("ModelRequest", () => {
|
||||
test("partitions AI SDK model and models.dev mode options", () => {
|
||||
expect(
|
||||
ModelRequest.normalizeAiSdkOptions("@ai-sdk/openai", {
|
||||
maxOutputTokens: 4096,
|
||||
temperature: 0.2,
|
||||
reasoningEffort: "high",
|
||||
serviceTier: "priority",
|
||||
custom_extension: { enabled: true },
|
||||
}),
|
||||
).toEqual({
|
||||
generation: { maxTokens: 4096, temperature: 0.2 },
|
||||
options: { reasoningEffort: "high", serviceTier: "priority" },
|
||||
body: { custom_extension: { enabled: true } },
|
||||
})
|
||||
})
|
||||
|
||||
test("keeps unknown-provider options as compatibility fields", () => {
|
||||
expect(ModelRequest.normalizeAiSdkOptions(undefined, { temperature: 0.2, reasoningEffort: "high" })).toEqual({
|
||||
generation: { temperature: 0.2 },
|
||||
options: {},
|
||||
body: { reasoningEffort: "high" },
|
||||
})
|
||||
})
|
||||
|
||||
test("does not consult inherited package-name properties", () => {
|
||||
expect(ModelRequest.normalizeAiSdkOptions("__proto__", { reasoningEffort: "high" })).toEqual({
|
||||
generation: {},
|
||||
options: {},
|
||||
body: { reasoningEffort: "high" },
|
||||
})
|
||||
})
|
||||
|
||||
test("normalizes models.dev wire aliases owned by native protocols", () => {
|
||||
expect(ModelRequest.normalizeAiSdkOptions("@ai-sdk/openai", { service_tier: "priority" })).toEqual({
|
||||
generation: {},
|
||||
options: { serviceTier: "priority" },
|
||||
body: {},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -290,21 +290,11 @@ function modelInfo(value: ModelV2.Info | ModelV2.MutableInfo) {
|
|||
...value.request,
|
||||
headers: { ...value.request.headers },
|
||||
body: { ...value.request.body },
|
||||
generation: value.request.generation && {
|
||||
...value.request.generation,
|
||||
stop: value.request.generation.stop && [...value.request.generation.stop],
|
||||
},
|
||||
options: value.request.options && { ...value.request.options },
|
||||
},
|
||||
variants: value.variants.map((variant) => ({
|
||||
...variant,
|
||||
headers: { ...variant.headers },
|
||||
body: { ...variant.body },
|
||||
generation: variant.generation && {
|
||||
...variant.generation,
|
||||
stop: variant.generation.stop && [...variant.generation.stop],
|
||||
},
|
||||
options: variant.options && { ...variant.options },
|
||||
})),
|
||||
time: { ...value.time },
|
||||
cost: value.cost.map((cost) => ({ ...cost, tier: cost.tier && { ...cost.tier }, cache: { ...cost.cache } })),
|
||||
|
|
|
|||
|
|
@ -150,15 +150,12 @@ describe("OpencodePlugin", () => {
|
|||
cost: [{ input: 1, output: 2, cache: { read: 0.1, write: 0 } }],
|
||||
limit: { context: 1000, output: 100 },
|
||||
})
|
||||
expect(model.request).toMatchObject({ body: { custom: "value" }, generation: { temperature: 0.5 } })
|
||||
expect(model.request.body).toEqual({ custom: "value" })
|
||||
expect(model.request.body).toEqual({ custom: "value", temperature: 0.5 })
|
||||
expect(model.variants).toEqual([
|
||||
{
|
||||
id: ModelV2.VariantID.make("high"),
|
||||
headers: {},
|
||||
body: {},
|
||||
generation: { temperature: 0.2 },
|
||||
options: {},
|
||||
body: { temperature: 0.2 },
|
||||
},
|
||||
])
|
||||
expect(
|
||||
|
|
|
|||
79
packages/core/test/plugin/variant.test.ts
Normal file
79
packages/core/test/plugin/variant.test.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import { describe, expect } from "bun:test"
|
||||
import { Catalog } from "@opencode-ai/core/catalog"
|
||||
import { Credential } from "@opencode-ai/core/credential"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { Integration } from "@opencode-ai/core/integration"
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { Policy } from "@opencode-ai/core/policy"
|
||||
import { VariantPlugin } from "@opencode-ai/core/plugin/variant"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { location } from "../fixture/location"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { catalogHost, host } from "./host"
|
||||
|
||||
const events = EventV2.defaultLayer
|
||||
const locationLayer = Layer.succeed(
|
||||
Location.Service,
|
||||
Location.Service.of(location({ directory: AbsolutePath.make(import.meta.dir) })),
|
||||
)
|
||||
const connections = Credential.defaultLayer.pipe(Layer.fresh)
|
||||
const integrations = Integration.locationLayer.pipe(Layer.provide(events), Layer.provide(connections))
|
||||
const catalog = Catalog.layer.pipe(
|
||||
Layer.provide(
|
||||
Layer.mergeAll(events, locationLayer, Policy.layer.pipe(Layer.provide(locationLayer)), connections, integrations),
|
||||
),
|
||||
)
|
||||
const it = testEffect(
|
||||
Layer.mergeAll(catalog.pipe(Layer.provide(connections)), integrations, connections, events, locationLayer),
|
||||
)
|
||||
|
||||
describe("VariantPlugin", () => {
|
||||
it.effect("adds GLM 5.2 variants after catalog sources", () =>
|
||||
Effect.gen(function* () {
|
||||
const service = yield* Catalog.Service
|
||||
yield* service.transform((catalog) => {
|
||||
catalog.provider.update(ProviderV2.ID.opencode, (provider) => {
|
||||
provider.api = { type: "aisdk", package: "@ai-sdk/openai-compatible" }
|
||||
})
|
||||
catalog.model.update(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2"), (model) => {
|
||||
model.api = {
|
||||
id: ModelV2.ID.make("glm-5.2"),
|
||||
type: "aisdk",
|
||||
package: "@ai-sdk/openai-compatible",
|
||||
}
|
||||
})
|
||||
})
|
||||
yield* VariantPlugin.Plugin.effect(host({ catalog: catalogHost(service) }))
|
||||
|
||||
expect((yield* service.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2")))?.variants).toEqual([
|
||||
expect.objectContaining({ id: "high", body: { reasoning_effort: "high" } }),
|
||||
expect.objectContaining({ id: "max", body: { reasoning_effort: "max" } }),
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("keeps explicit variants over generated defaults", () =>
|
||||
Effect.gen(function* () {
|
||||
const service = yield* Catalog.Service
|
||||
yield* service.transform((catalog) => {
|
||||
catalog.model.update(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2"), (model) => {
|
||||
model.api = {
|
||||
id: ModelV2.ID.make("glm-5.2"),
|
||||
type: "aisdk",
|
||||
package: "@ai-sdk/openai-compatible",
|
||||
}
|
||||
model.variants = [{ id: ModelV2.VariantID.make("high"), headers: { custom: "true" }, body: {} }]
|
||||
})
|
||||
})
|
||||
yield* VariantPlugin.Plugin.effect(host({ catalog: catalogHost(service) }))
|
||||
|
||||
expect((yield* service.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2")))?.variants).toEqual([
|
||||
expect.objectContaining({ id: "high", headers: { custom: "true" } }),
|
||||
expect.objectContaining({ id: "max", body: { reasoning_effort: "max" } }),
|
||||
])
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
@ -31,8 +31,6 @@ const model = (api: Api, variants: ModelV2.Info["variants"] = []) =>
|
|||
request: {
|
||||
headers: { "x-test": "header" },
|
||||
body: { apiKey: "secret", custom_extension: { enabled: true } },
|
||||
generation: { temperature: 0.7 },
|
||||
options: { store: false, serviceTier: "priority" },
|
||||
},
|
||||
variants,
|
||||
time: { released: 0 },
|
||||
|
|
@ -56,8 +54,6 @@ describe("SessionRunnerModel", () => {
|
|||
defaults: {
|
||||
headers: { "x-test": "header" },
|
||||
limits: { context: 100, output: 20 },
|
||||
generation: { temperature: 0.7 },
|
||||
providerOptions: { openai: { store: false, serviceTier: "priority" } },
|
||||
http: { body: { custom_extension: { enabled: true } } },
|
||||
},
|
||||
})
|
||||
|
|
@ -86,7 +82,7 @@ describe("SessionRunnerModel", () => {
|
|||
url: "https://compatible.example/v1",
|
||||
settings: { apiKey: "settings-secret", compatibility: "strict" },
|
||||
}),
|
||||
request: { headers: {}, body: {}, generation: {}, options: {} },
|
||||
request: { headers: {}, body: {} },
|
||||
}),
|
||||
)
|
||||
const request = LLM.request({ model: resolved, prompt: "Hello" })
|
||||
|
|
@ -103,21 +99,20 @@ describe("SessionRunnerModel", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("lowers selected OpenAI Session variants into Responses options", () =>
|
||||
it.effect("overlays selected OpenAI Session variant bodies", () =>
|
||||
Effect.gen(function* () {
|
||||
const base = model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }, [
|
||||
const catalog = model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }, [
|
||||
{
|
||||
id: ModelV2.VariantID.make("high"),
|
||||
headers: { "x-variant": "high" },
|
||||
body: {},
|
||||
generation: { temperature: 0.2 },
|
||||
options: { reasoningEffort: "high" },
|
||||
body: {
|
||||
store: false,
|
||||
service_tier: "priority",
|
||||
temperature: 0.2,
|
||||
reasoning: { effort: "high" },
|
||||
},
|
||||
},
|
||||
])
|
||||
const catalog = ModelV2.Info.make({
|
||||
...base,
|
||||
request: { ...base.request, options: { ...base.request.options, reasoningEffort: "medium" } },
|
||||
})
|
||||
const session = SessionV2.Info.make({
|
||||
id: SessionV2.ID.make("ses_model_variant"),
|
||||
projectID: ProjectV2.ID.global,
|
||||
|
|
@ -134,17 +129,15 @@ describe("SessionRunnerModel", () => {
|
|||
})
|
||||
|
||||
const resolved = yield* SessionRunnerModel.resolve(session, catalog)
|
||||
const prepared = yield* LLMClient.prepare(LLM.request({ model: resolved, prompt: "Hello" }))
|
||||
|
||||
expect(resolved.route.defaults.headers).toMatchObject({ "x-test": "header", "x-variant": "high" })
|
||||
expect(resolved.route.defaults.http?.body).toEqual({ custom_extension: { enabled: true } })
|
||||
expect(prepared.body).toMatchObject({
|
||||
expect(resolved.route.defaults.http?.body).toEqual({
|
||||
custom_extension: { enabled: true },
|
||||
store: false,
|
||||
service_tier: "priority",
|
||||
temperature: 0.2,
|
||||
reasoning: { effort: "high" },
|
||||
})
|
||||
expect(prepared.body).not.toHaveProperty("reasoningEffort")
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
@ -158,8 +151,6 @@ describe("SessionRunnerModel", () => {
|
|||
settings: { baseURL: "https://regional.example/v1" },
|
||||
headers: {},
|
||||
body: {},
|
||||
generation: {},
|
||||
options: {},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
|
@ -173,16 +164,13 @@ describe("SessionRunnerModel", () => {
|
|||
time: { created: DateTime.makeUnsafe(0), updated: DateTime.makeUnsafe(0) },
|
||||
location: { directory: AbsolutePath.make("/project") },
|
||||
})
|
||||
const resolved = yield* SessionRunnerModel.resolve(
|
||||
session,
|
||||
base,
|
||||
)
|
||||
const resolved = yield* SessionRunnerModel.resolve(session, base)
|
||||
|
||||
expect(resolved.route.endpoint.baseURL).toBe("https://regional.example/v1")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("lowers selected OpenAI-compatible Session variants into Chat options", () =>
|
||||
it.effect("overlays selected OpenAI-compatible Session variant bodies", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = model(
|
||||
{ type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://compatible.example/v1" },
|
||||
|
|
@ -190,9 +178,7 @@ describe("SessionRunnerModel", () => {
|
|||
{
|
||||
id: ModelV2.VariantID.make("high"),
|
||||
headers: {},
|
||||
body: {},
|
||||
generation: {},
|
||||
options: { reasoningEffort: "high" },
|
||||
body: { store: false, reasoning_effort: "high" },
|
||||
},
|
||||
],
|
||||
)
|
||||
|
|
@ -208,14 +194,12 @@ describe("SessionRunnerModel", () => {
|
|||
})
|
||||
|
||||
const resolved = yield* SessionRunnerModel.resolve(session, catalog)
|
||||
const prepared = yield* LLMClient.prepare(LLM.request({ model: resolved, prompt: "Hello" }))
|
||||
|
||||
expect(resolved.route.defaults.http?.body).toEqual({ custom_extension: { enabled: true } })
|
||||
expect(prepared.body).toMatchObject({
|
||||
expect(resolved.route.defaults.http?.body).toEqual({
|
||||
custom_extension: { enabled: true },
|
||||
store: false,
|
||||
reasoning_effort: "high",
|
||||
})
|
||||
expect(prepared.body).not.toHaveProperty("reasoningEffort")
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
@ -249,15 +233,13 @@ describe("SessionRunnerModel", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("lowers selected Anthropic Session variants into Messages options", () =>
|
||||
it.effect("overlays selected Anthropic Session variant bodies", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = model({ type: "aisdk", package: "@ai-sdk/anthropic", url: "https://anthropic.example/v1" }, [
|
||||
{
|
||||
id: ModelV2.VariantID.make("high"),
|
||||
headers: {},
|
||||
body: {},
|
||||
generation: {},
|
||||
options: { thinking: { type: "enabled", budgetTokens: 12000 } },
|
||||
body: { thinking: { type: "enabled", budget_tokens: 12000 } },
|
||||
},
|
||||
])
|
||||
const session = SessionV2.Info.make({
|
||||
|
|
@ -272,13 +254,11 @@ describe("SessionRunnerModel", () => {
|
|||
})
|
||||
|
||||
const resolved = yield* SessionRunnerModel.resolve(session, catalog)
|
||||
const prepared = yield* LLMClient.prepare(LLM.request({ model: resolved, prompt: "Hello" }))
|
||||
|
||||
expect(resolved.route.defaults.http?.body).toEqual({ custom_extension: { enabled: true } })
|
||||
expect(prepared.body).toMatchObject({
|
||||
expect(resolved.route.defaults.http?.body).toEqual({
|
||||
custom_extension: { enabled: true },
|
||||
thinking: { type: "enabled", budget_tokens: 12000 },
|
||||
})
|
||||
expect(JSON.stringify(prepared.body)).not.toContain("budgetTokens")
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
@ -300,7 +280,7 @@ describe("SessionRunnerModel", () => {
|
|||
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
||||
ModelV2.Info.make({
|
||||
...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }),
|
||||
request: { headers: {}, body: {}, generation: {}, options: {} },
|
||||
request: { headers: {}, body: {} },
|
||||
}),
|
||||
Credential.Key.make({ type: "key", key: "secret" }),
|
||||
)
|
||||
|
|
@ -323,7 +303,7 @@ describe("SessionRunnerModel", () => {
|
|||
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
||||
ModelV2.Info.make({
|
||||
...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }),
|
||||
request: { headers: {}, body: { apiKey: "configured-secret" }, generation: {}, options: {} },
|
||||
request: { headers: {}, body: { apiKey: "configured-secret" } },
|
||||
}),
|
||||
credential,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import { Credential } from "@opencode-ai/schema/credential"
|
|||
import { FileSystem } from "@opencode-ai/schema/filesystem"
|
||||
import { Integration } from "@opencode-ai/schema/integration"
|
||||
import { LLM } from "@opencode-ai/schema/llm"
|
||||
import { ModelRequest } from "@opencode-ai/schema/model-request"
|
||||
import { Permission } from "@opencode-ai/schema/permission"
|
||||
import { Reference } from "@opencode-ai/schema/reference"
|
||||
import { Skill } from "@opencode-ai/schema/skill"
|
||||
|
|
@ -35,7 +34,6 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||
coreIntegration,
|
||||
coreLocation,
|
||||
coreLLM,
|
||||
coreModelRequest,
|
||||
corePermission,
|
||||
coreProject,
|
||||
coreReference,
|
||||
|
|
@ -53,7 +51,6 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||
import("@opencode-ai/core/integration"),
|
||||
import("@opencode-ai/core/location"),
|
||||
import("@opencode-ai/llm"),
|
||||
import("@opencode-ai/core/model-request"),
|
||||
import("@opencode-ai/core/permission"),
|
||||
import("@opencode-ai/core/project/schema"),
|
||||
import("@opencode-ai/core/reference"),
|
||||
|
|
@ -113,8 +110,6 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||
[ProviderV2.Api, Provider.Api],
|
||||
[ProviderV2.Request, Provider.Request],
|
||||
[ProviderV2.Info, Provider.Info],
|
||||
[coreModelRequest.Generation, ModelRequest.Generation],
|
||||
[coreModelRequest.Request, ModelRequest.Request],
|
||||
[corePermission.Effect, Permission.Effect],
|
||||
[corePermission.Rule, Permission.Rule],
|
||||
[corePermission.Ruleset, Permission.Ruleset],
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ export { Integration } from "./integration"
|
|||
export { LLM } from "./llm"
|
||||
export { Location } from "./location"
|
||||
export { Model } from "./model"
|
||||
export { ModelRequest } from "./model-request"
|
||||
export { Permission } from "./permission"
|
||||
export { Project } from "./project"
|
||||
export { Provider } from "./provider"
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
export * as ModelRequest from "./model-request"
|
||||
|
||||
import { Effect, Schema } from "effect"
|
||||
import { Provider } from "./provider"
|
||||
|
||||
export interface Generation extends Schema.Schema.Type<typeof Generation> {}
|
||||
export const Generation = Schema.Struct({
|
||||
maxTokens: Schema.Number.pipe(Schema.optional),
|
||||
temperature: Schema.Number.pipe(Schema.optional),
|
||||
topP: Schema.Number.pipe(Schema.optional),
|
||||
topK: Schema.Number.pipe(Schema.optional),
|
||||
frequencyPenalty: Schema.Number.pipe(Schema.optional),
|
||||
presencePenalty: Schema.Number.pipe(Schema.optional),
|
||||
seed: Schema.Number.pipe(Schema.optional),
|
||||
stop: Schema.String.pipe(Schema.Array, Schema.mutable, Schema.optional),
|
||||
})
|
||||
|
||||
export interface Request extends Schema.Schema.Type<typeof Request> {}
|
||||
export const Request = Schema.Struct({
|
||||
...Provider.Request.fields,
|
||||
generation: Generation.pipe(
|
||||
Schema.optionalKey,
|
||||
Schema.withConstructorDefault(Effect.succeed({})),
|
||||
Schema.withDecodingDefaultKey(Effect.succeed({})),
|
||||
),
|
||||
options: Schema.Record(Schema.String, Schema.Any).pipe(
|
||||
Schema.optionalKey,
|
||||
Schema.withConstructorDefault(Effect.succeed({})),
|
||||
Schema.withDecodingDefaultKey(Effect.succeed({})),
|
||||
),
|
||||
})
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
export * as Model from "./model"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { ModelRequest } from "./model-request"
|
||||
import { Provider } from "./provider"
|
||||
import { withStatics } from "./schema"
|
||||
|
||||
|
|
@ -71,13 +70,13 @@ export const Info = Schema.Struct({
|
|||
api: Api,
|
||||
capabilities: Capabilities,
|
||||
request: Schema.Struct({
|
||||
...ModelRequest.Request.fields,
|
||||
...Provider.Request.fields,
|
||||
variant: Schema.String.pipe(Schema.optional),
|
||||
}),
|
||||
variants: Schema.Struct({
|
||||
id: VariantID,
|
||||
settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
...ModelRequest.Request.fields,
|
||||
...Provider.Request.fields,
|
||||
}).pipe(Schema.Array, Schema.mutable),
|
||||
time: Schema.Struct({
|
||||
released: Schema.Finite,
|
||||
|
|
@ -101,7 +100,7 @@ export const Info = Schema.Struct({
|
|||
name: modelID,
|
||||
api: { id: modelID, type: "native", settings: {} },
|
||||
capabilities: { tools: false, input: [], output: [] },
|
||||
request: { headers: {}, body: {}, generation: {}, options: {} },
|
||||
request: { headers: {}, body: {} },
|
||||
variants: [],
|
||||
time: { released: 0 },
|
||||
cost: [],
|
||||
|
|
|
|||
|
|
@ -4020,19 +4020,6 @@ export type ModelV2Info = {
|
|||
body: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
generation?: {
|
||||
maxTokens?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
temperature?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
topP?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
topK?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
frequencyPenalty?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
presencePenalty?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
seed?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
stop?: Array<string>
|
||||
}
|
||||
options?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
variant?: string
|
||||
}
|
||||
variants: Array<{
|
||||
|
|
@ -4046,19 +4033,6 @@ export type ModelV2Info = {
|
|||
body: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
generation?: {
|
||||
maxTokens?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
temperature?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
topP?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
topK?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
frequencyPenalty?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
presencePenalty?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
seed?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
stop?: Array<string>
|
||||
}
|
||||
options?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}>
|
||||
time: {
|
||||
released: number
|
||||
|
|
|
|||
|
|
@ -26833,182 +26833,6 @@
|
|||
"body": {
|
||||
"type": "object"
|
||||
},
|
||||
"generation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"maxTokens": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["NaN"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["-Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity", "-Infinity", "NaN"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"temperature": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["NaN"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["-Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity", "-Infinity", "NaN"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"topP": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["NaN"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["-Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity", "-Infinity", "NaN"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"topK": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["NaN"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["-Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity", "-Infinity", "NaN"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"frequencyPenalty": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["NaN"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["-Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity", "-Infinity", "NaN"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"presencePenalty": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["NaN"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["-Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity", "-Infinity", "NaN"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"seed": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["NaN"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["-Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity", "-Infinity", "NaN"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"stop": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"options": {
|
||||
"type": "object"
|
||||
},
|
||||
"variant": {
|
||||
"type": "string"
|
||||
}
|
||||
|
|
@ -27032,182 +26856,6 @@
|
|||
},
|
||||
"body": {
|
||||
"type": "object"
|
||||
},
|
||||
"generation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"maxTokens": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["NaN"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["-Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity", "-Infinity", "NaN"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"temperature": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["NaN"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["-Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity", "-Infinity", "NaN"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"topP": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["NaN"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["-Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity", "-Infinity", "NaN"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"topK": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["NaN"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["-Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity", "-Infinity", "NaN"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"frequencyPenalty": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["NaN"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["-Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity", "-Infinity", "NaN"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"presencePenalty": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["NaN"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["-Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity", "-Infinity", "NaN"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"seed": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["NaN"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["-Infinity"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["Infinity", "-Infinity", "NaN"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"stop": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"options": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": ["id", "headers", "body"],
|
||||
|
|
|
|||
10
packages/session-ui/sst-env.d.ts
vendored
Normal file
10
packages/session-ui/sst-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/* This file is auto-generated by SST. Do not edit. */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/* deno-fmt-ignore-file */
|
||||
/* biome-ignore-all lint: auto-generated */
|
||||
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
64
sst-env.d.ts
vendored
64
sst-env.d.ts
vendored
|
|
@ -26,6 +26,14 @@ declare module "sst" {
|
|||
"AuthApi": import("@cloudflare/workers-types").Service
|
||||
"AuthStorage": import("@cloudflare/workers-types").KVNamespace
|
||||
"Bucket": import("@cloudflare/workers-types").R2Bucket
|
||||
"CLOUDFLARE_API_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"Console": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
|
|
@ -91,37 +99,6 @@ declare module "sst" {
|
|||
"type": "random.index/randomPassword.RandomPassword"
|
||||
"value": string
|
||||
}
|
||||
"InferenceEvent": {
|
||||
"catalog": string
|
||||
"database": string
|
||||
"region": string
|
||||
"table": string
|
||||
"tableBucket": string
|
||||
"type": "sst.sst.Linkable"
|
||||
"workgroup": string
|
||||
}
|
||||
"LakeIngest": {
|
||||
"secret": string
|
||||
"type": "sst.sst.Linkable"
|
||||
"url": string
|
||||
}
|
||||
"LakeIngestConfig": {
|
||||
"secret": string
|
||||
"streamName": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"LakeIngestSecret": {
|
||||
"type": "random.index/randomPassword.RandomPassword"
|
||||
"value": string
|
||||
}
|
||||
"LakeIngestService": {
|
||||
"service": string
|
||||
"type": "sst.aws.Service"
|
||||
"url": string
|
||||
}
|
||||
"LakeVpc": {
|
||||
"type": "sst.aws.Vpc"
|
||||
}
|
||||
"LogProcessor": import("@cloudflare/workers-types").Service
|
||||
"R2AccessKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
|
|
@ -155,28 +132,11 @@ declare module "sst" {
|
|||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
"SUPPORT_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"Stat": import("@cloudflare/workers-types").Service
|
||||
"Stats": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
"StatsDatabase": {
|
||||
"database": string
|
||||
"host": string
|
||||
"password": string
|
||||
"port": number
|
||||
"type": "sst.sst.Linkable"
|
||||
"url": string
|
||||
"username": string
|
||||
}
|
||||
"StatsSyncConfig": {
|
||||
"dataset": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"StatsSyncService": {
|
||||
"service": string
|
||||
"type": "sst.aws.Service"
|
||||
}
|
||||
"Teams": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue