mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-08 19:13:19 +00:00
fix(config): omit unset optional values (#40918)
This commit is contained in:
parent
c66d84169a
commit
20fa444f31
25 changed files with 2407 additions and 4821 deletions
|
|
@ -1693,44 +1693,43 @@ export type AgentInfo = {
|
|||
export type ConfigEntry =
|
||||
| {
|
||||
type: "document"
|
||||
path?: string | null
|
||||
path?: string
|
||||
info: {
|
||||
$schema?: string | null
|
||||
shell?: string | null
|
||||
model?: string | { providerID: string; model: string; variant?: string | null } | null
|
||||
default_agent?: string | null
|
||||
autoupdate?: boolean | "notify" | null
|
||||
share?: "manual" | "auto" | "disabled" | null
|
||||
enterprise?: { url?: string | null } | null
|
||||
username?: string | null
|
||||
permissions?: PermissionRuleset | null
|
||||
$schema?: string
|
||||
shell?: string
|
||||
model?: string | { providerID: string; model: string; variant?: string }
|
||||
default_agent?: string
|
||||
autoupdate?: boolean | "notify"
|
||||
share?: "manual" | "auto" | "disabled"
|
||||
enterprise?: { url?: string }
|
||||
username?: string
|
||||
permissions?: PermissionRuleset
|
||||
agents?: {
|
||||
[x: string]: {
|
||||
model?: string | { providerID: string; model: string; variant?: string | null } | null
|
||||
request?: { headers?: { [x: string]: string } | null; body?: { [x: string]: JsonValue } | null } | null
|
||||
system?: string | null
|
||||
description?: string | null
|
||||
mode?: "subagent" | "primary" | "all" | null
|
||||
hidden?: boolean | null
|
||||
color?: string | null
|
||||
steps?: number | null
|
||||
disabled?: boolean | null
|
||||
permissions?: PermissionRuleset | null
|
||||
model?: string | { providerID: string; model: string; variant?: string }
|
||||
request?: { headers?: { [x: string]: string }; body?: { [x: string]: JsonValue } }
|
||||
system?: string
|
||||
description?: string
|
||||
mode?: "subagent" | "primary" | "all"
|
||||
hidden?: boolean
|
||||
color?: string
|
||||
steps?: number
|
||||
disabled?: boolean
|
||||
permissions?: PermissionRuleset
|
||||
}
|
||||
} | null
|
||||
snapshots?: boolean | null
|
||||
watcher?: { ignore?: Array<string> | null } | null
|
||||
}
|
||||
snapshots?: boolean
|
||||
watcher?: { ignore?: Array<string> }
|
||||
formatter?:
|
||||
| boolean
|
||||
| {
|
||||
[x: string]: {
|
||||
disabled?: boolean | null
|
||||
command?: Array<string> | null
|
||||
environment?: { [x: string]: string } | null
|
||||
extensions?: Array<string> | null
|
||||
disabled?: boolean
|
||||
command?: Array<string>
|
||||
environment?: { [x: string]: string }
|
||||
extensions?: Array<string>
|
||||
}
|
||||
}
|
||||
| null
|
||||
lsp?:
|
||||
| boolean
|
||||
| {
|
||||
|
|
@ -1738,125 +1737,117 @@ export type ConfigEntry =
|
|||
| { disabled: true }
|
||||
| {
|
||||
command: Array<string>
|
||||
extensions?: Array<string> | null
|
||||
disabled?: boolean | null
|
||||
env?: { [x: string]: string } | null
|
||||
initialization?: { [x: string]: JsonValue } | null
|
||||
extensions?: Array<string>
|
||||
disabled?: boolean
|
||||
env?: { [x: string]: string }
|
||||
initialization?: { [x: string]: JsonValue }
|
||||
}
|
||||
}
|
||||
| null
|
||||
media?: {
|
||||
image?: {
|
||||
auto_resize?: boolean | null
|
||||
max_width?: number | null
|
||||
max_height?: number | null
|
||||
max_base64_bytes?: number | null
|
||||
} | null
|
||||
} | null
|
||||
tool_output?: { max_lines?: number | null; max_bytes?: number | null } | null
|
||||
image?: { auto_resize?: boolean; max_width?: number; max_height?: number; max_base64_bytes?: number }
|
||||
}
|
||||
tool_output?: { max_lines?: number; max_bytes?: number }
|
||||
mcp?: {
|
||||
timeout?: { startup?: number | null; catalog?: number | null; execution?: number | null } | null
|
||||
timeout?: { startup?: number; catalog?: number; execution?: number }
|
||||
servers?: {
|
||||
[x: string]:
|
||||
| {
|
||||
type: "local"
|
||||
command: Array<string>
|
||||
cwd?: string | null
|
||||
environment?: { [x: string]: string } | null
|
||||
disabled?: boolean | null
|
||||
codemode?: boolean | null
|
||||
timeout?: { startup?: number | null; catalog?: number | null; execution?: number | null } | null
|
||||
cwd?: string
|
||||
environment?: { [x: string]: string }
|
||||
disabled?: boolean
|
||||
codemode?: boolean
|
||||
timeout?: { startup?: number; catalog?: number; execution?: number }
|
||||
}
|
||||
| {
|
||||
type: "remote"
|
||||
url: string
|
||||
headers?: { [x: string]: string } | null
|
||||
headers?: { [x: string]: string }
|
||||
oauth?:
|
||||
| {
|
||||
client_id?: string | null
|
||||
client_secret?: string | null
|
||||
scope?: string | null
|
||||
callback_port?: number | null
|
||||
redirect_uri?: string | null
|
||||
client_id?: string
|
||||
client_secret?: string
|
||||
scope?: string
|
||||
callback_port?: number
|
||||
redirect_uri?: string
|
||||
}
|
||||
| false
|
||||
| null
|
||||
disabled?: boolean | null
|
||||
codemode?: boolean | null
|
||||
timeout?: { startup?: number | null; catalog?: number | null; execution?: number | null } | null
|
||||
disabled?: boolean
|
||||
codemode?: boolean
|
||||
timeout?: { startup?: number; catalog?: number; execution?: number }
|
||||
}
|
||||
} | null
|
||||
} | null
|
||||
compaction?: { auto?: boolean | null; keep?: { tokens?: number | null } | null; buffer?: number | null } | null
|
||||
skills?: Array<string> | null
|
||||
}
|
||||
}
|
||||
compaction?: { auto?: boolean; keep?: { tokens?: number }; buffer?: number }
|
||||
skills?: Array<string>
|
||||
commands?: {
|
||||
[x: string]: {
|
||||
template: string
|
||||
description?: string | null
|
||||
agent?: string | null
|
||||
model?: string | { providerID: string; model: string; variant?: string | null } | null
|
||||
subtask?: boolean | null
|
||||
description?: string
|
||||
agent?: string
|
||||
model?: string | { providerID: string; model: string; variant?: string }
|
||||
subtask?: boolean
|
||||
}
|
||||
} | null
|
||||
instructions?: Array<string> | null
|
||||
}
|
||||
instructions?: Array<string>
|
||||
references?: {
|
||||
[x: string]:
|
||||
| string
|
||||
| { repository: string; branch?: string | null; description?: string | null; hidden?: boolean | null }
|
||||
| { path: string; description?: string | null; hidden?: boolean | null }
|
||||
} | null
|
||||
websearch?: { provider: string } | null
|
||||
plugins?: Array<string | { package: string; options?: { [x: string]: JsonValue } | null }> | null
|
||||
warming?: boolean | { prompt?: string | null; interval?: string | null; duration?: string | null } | null
|
||||
| { repository: string; branch?: string; description?: string; hidden?: boolean }
|
||||
| { path: string; description?: string; hidden?: boolean }
|
||||
}
|
||||
websearch?: { provider: string }
|
||||
plugins?: Array<string | { package: string; options?: { [x: string]: JsonValue } }>
|
||||
warming?: boolean | { prompt?: string; interval?: string; duration?: string }
|
||||
providers?: {
|
||||
[x: string]: {
|
||||
name?: string | null
|
||||
env?: Array<string> | null
|
||||
package?: string | null
|
||||
settings?: { [x: string]: JsonValue } | null
|
||||
headers?: { [x: string]: string } | null
|
||||
body?: { [x: string]: JsonValue } | null
|
||||
name?: string
|
||||
env?: Array<string>
|
||||
package?: string
|
||||
settings?: { [x: string]: JsonValue }
|
||||
headers?: { [x: string]: string }
|
||||
body?: { [x: string]: JsonValue }
|
||||
models?: {
|
||||
[x: string]: {
|
||||
modelID?: string | null
|
||||
family?: string | null
|
||||
name?: string | null
|
||||
compatibility?: ModelCompatibility | null
|
||||
package?: string | null
|
||||
settings?: { [x: string]: JsonValue } | null
|
||||
headers?: { [x: string]: string } | null
|
||||
body?: { [x: string]: JsonValue } | null
|
||||
capabilities?: ModelCapabilities | null
|
||||
modelID?: string
|
||||
family?: string
|
||||
name?: string
|
||||
compatibility?: ModelCompatibility
|
||||
package?: string
|
||||
settings?: { [x: string]: JsonValue }
|
||||
headers?: { [x: string]: string }
|
||||
body?: { [x: string]: JsonValue }
|
||||
capabilities?: ModelCapabilities
|
||||
variants?: Array<{
|
||||
id: string
|
||||
settings?: { [x: string]: JsonValue } | null
|
||||
headers?: { [x: string]: string } | null
|
||||
body?: { [x: string]: JsonValue } | null
|
||||
}> | null
|
||||
settings?: { [x: string]: JsonValue }
|
||||
headers?: { [x: string]: string }
|
||||
body?: { [x: string]: JsonValue }
|
||||
}>
|
||||
cost?:
|
||||
| {
|
||||
tier?: { type: "context"; size: number } | null
|
||||
tier?: { type: "context"; size: number }
|
||||
input: MoneyUSDPerMillionTokens
|
||||
output: MoneyUSDPerMillionTokens
|
||||
cache?: { read?: MoneyUSDPerMillionTokens | null; write?: MoneyUSDPerMillionTokens | null } | null
|
||||
cache?: { read?: MoneyUSDPerMillionTokens; write?: MoneyUSDPerMillionTokens }
|
||||
}
|
||||
| Array<{
|
||||
tier?: { type: "context"; size: number } | null
|
||||
tier?: { type: "context"; size: number }
|
||||
input: MoneyUSDPerMillionTokens
|
||||
output: MoneyUSDPerMillionTokens
|
||||
cache?: { read?: MoneyUSDPerMillionTokens | null; write?: MoneyUSDPerMillionTokens | null } | null
|
||||
cache?: { read?: MoneyUSDPerMillionTokens; write?: MoneyUSDPerMillionTokens }
|
||||
}>
|
||||
| null
|
||||
disabled?: boolean | null
|
||||
limit?: { context?: number | null; input?: number | null; output?: number | null } | null
|
||||
disabled?: boolean
|
||||
limit?: { context?: number; input?: number; output?: number }
|
||||
}
|
||||
} | null
|
||||
}
|
||||
}
|
||||
} | null
|
||||
}
|
||||
experimental?: {
|
||||
subagent_depth?: number | null
|
||||
policies?: Array<{ action: "provider.use"; resource: string; effect: "allow" | "deny" }> | null
|
||||
} | null
|
||||
subagent_depth?: number
|
||||
policies?: Array<{ action: "provider.use"; resource: string; effect: "allow" | "deny" }>
|
||||
}
|
||||
}
|
||||
}
|
||||
| { type: "directory"; path: string }
|
||||
|
|
@ -3242,41 +3233,28 @@ export type McpAddInput = {
|
|||
| {
|
||||
readonly type: "local"
|
||||
readonly command: ReadonlyArray<string>
|
||||
readonly cwd?: string | undefined
|
||||
readonly environment?: { readonly [x: string]: string } | undefined
|
||||
readonly disabled?: boolean | undefined
|
||||
readonly codemode?: boolean | undefined
|
||||
readonly timeout?:
|
||||
| {
|
||||
readonly startup?: number | undefined
|
||||
readonly catalog?: number | undefined
|
||||
readonly execution?: number | undefined
|
||||
}
|
||||
| undefined
|
||||
readonly cwd?: string
|
||||
readonly environment?: { readonly [x: string]: string }
|
||||
readonly disabled?: boolean
|
||||
readonly codemode?: boolean
|
||||
readonly timeout?: { readonly startup?: number; readonly catalog?: number; readonly execution?: number }
|
||||
}
|
||||
| {
|
||||
readonly type: "remote"
|
||||
readonly url: string
|
||||
readonly headers?: { readonly [x: string]: string } | undefined
|
||||
readonly headers?: { readonly [x: string]: string }
|
||||
readonly oauth?:
|
||||
| {
|
||||
readonly client_id?: string | undefined
|
||||
readonly client_secret?: string | undefined
|
||||
readonly scope?: string | undefined
|
||||
readonly callback_port?: number | undefined
|
||||
readonly redirect_uri?: string | undefined
|
||||
readonly client_id?: string
|
||||
readonly client_secret?: string
|
||||
readonly scope?: string
|
||||
readonly callback_port?: number
|
||||
readonly redirect_uri?: string
|
||||
}
|
||||
| false
|
||||
| undefined
|
||||
readonly disabled?: boolean | undefined
|
||||
readonly codemode?: boolean | undefined
|
||||
readonly timeout?:
|
||||
| {
|
||||
readonly startup?: number | undefined
|
||||
readonly catalog?: number | undefined
|
||||
readonly execution?: number | undefined
|
||||
}
|
||||
| undefined
|
||||
readonly disabled?: boolean
|
||||
readonly codemode?: boolean
|
||||
readonly timeout?: { readonly startup?: number; readonly catalog?: number; readonly execution?: number }
|
||||
}
|
||||
}["config"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
export * as ConfigMigrateV1 from "./migrate"
|
||||
|
||||
import { Info } from "@opencode-ai/schema/config"
|
||||
import { ConfigAgent } from "@opencode-ai/schema/config/agent"
|
||||
import { Schema } from "effect"
|
||||
import { ConfigV1 } from "./config"
|
||||
import { ConfigAgentV1 } from "./agent"
|
||||
import { ConfigCommandV1 } from "./command"
|
||||
|
|
@ -10,6 +13,12 @@ import { ConfigProviderOptionsV1 } from "./provider-options"
|
|||
import { Provider } from "../../provider"
|
||||
import { Model } from "../../model"
|
||||
|
||||
const decodeOptions = { errors: "all", onExcessProperty: "ignore", propertyOrder: "original" } as const
|
||||
const decodeInfo = Schema.decodeUnknownSync(Schema.fromJsonString(Info), decodeOptions)
|
||||
const encodeInfo = Schema.encodeSync(Info)
|
||||
const decodeAgent = Schema.decodeUnknownSync(Schema.fromJsonString(ConfigAgent.Info), decodeOptions)
|
||||
const encodeAgent = Schema.encodeSync(ConfigAgent.Info)
|
||||
|
||||
const keys = new Set([
|
||||
"logLevel",
|
||||
"server",
|
||||
|
|
@ -48,42 +57,46 @@ export function isV1(input: unknown) {
|
|||
}
|
||||
|
||||
export function migrate(info: typeof ConfigV1.Info.Type) {
|
||||
return {
|
||||
$schema: info.$schema,
|
||||
shell: info.shell,
|
||||
model: modelSelection(info.model),
|
||||
default_agent: info.default_agent,
|
||||
autoupdate: info.autoupdate,
|
||||
share: info.share ?? (info.autoshare ? "auto" : undefined),
|
||||
enterprise: info.enterprise,
|
||||
username: info.username,
|
||||
permissions: permissions(info.permission, info.tools),
|
||||
agents: agents(info),
|
||||
snapshots: info.snapshot,
|
||||
watcher: info.watcher,
|
||||
formatter: info.formatter,
|
||||
lsp: info.lsp,
|
||||
media: info.attachment,
|
||||
tool_output: info.tool_output,
|
||||
mcp: mcp(info),
|
||||
compaction: info.compaction && {
|
||||
auto: info.compaction.auto,
|
||||
prune: info.compaction.prune,
|
||||
keep: {
|
||||
tokens: info.compaction.preserve_recent_tokens,
|
||||
},
|
||||
buffer: info.compaction.reserved,
|
||||
},
|
||||
skills: info.skills && [...(info.skills.paths ?? []), ...(info.skills.urls ?? [])],
|
||||
commands: commands(info.command),
|
||||
instructions: info.instructions,
|
||||
references: info.references ?? info.reference,
|
||||
experimental: experimental(info),
|
||||
plugins: info.plugin?.map((plugin) =>
|
||||
typeof plugin === "string" ? plugin : { package: plugin[0], options: plugin[1] },
|
||||
return encodeInfo(
|
||||
decodeInfo(
|
||||
JSON.stringify({
|
||||
$schema: info.$schema,
|
||||
shell: info.shell,
|
||||
model: modelSelection(info.model),
|
||||
default_agent: info.default_agent,
|
||||
autoupdate: info.autoupdate,
|
||||
share: info.share ?? (info.autoshare ? "auto" : undefined),
|
||||
enterprise: info.enterprise,
|
||||
username: info.username,
|
||||
permissions: permissions(info.permission, info.tools),
|
||||
agents: agents(info),
|
||||
snapshots: info.snapshot,
|
||||
watcher: info.watcher,
|
||||
formatter: info.formatter,
|
||||
lsp: info.lsp,
|
||||
media: info.attachment,
|
||||
tool_output: info.tool_output,
|
||||
mcp: mcp(info),
|
||||
compaction: info.compaction && {
|
||||
auto: info.compaction.auto,
|
||||
prune: info.compaction.prune,
|
||||
keep: {
|
||||
tokens: info.compaction.preserve_recent_tokens,
|
||||
},
|
||||
buffer: info.compaction.reserved,
|
||||
},
|
||||
skills: info.skills && [...(info.skills.paths ?? []), ...(info.skills.urls ?? [])],
|
||||
commands: commands(info.command),
|
||||
instructions: info.instructions,
|
||||
references: info.references ?? info.reference,
|
||||
experimental: experimental(info),
|
||||
plugins: info.plugin?.map((plugin) =>
|
||||
typeof plugin === "string" ? plugin : { package: plugin[0], options: plugin[1] },
|
||||
),
|
||||
providers: providers(info.provider),
|
||||
}),
|
||||
),
|
||||
providers: providers(info.provider),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function experimental(info: typeof ConfigV1.Info.Type) {
|
||||
|
|
@ -154,18 +167,22 @@ export function migrateAgent(info: ConfigAgentV1.Info) {
|
|||
...(info.temperature === undefined ? {} : { temperature: info.temperature }),
|
||||
...(info.top_p === undefined ? {} : { top_p: info.top_p }),
|
||||
}
|
||||
return {
|
||||
model: modelSelection(info.model, info.variant),
|
||||
request: Object.keys(body).length ? { body } : undefined,
|
||||
system: info.prompt,
|
||||
description: info.description,
|
||||
mode: info.mode,
|
||||
hidden: info.hidden,
|
||||
color: info.color === undefined ? undefined : info.color.startsWith("#") ? info.color : "#aaaaaa",
|
||||
steps: info.steps,
|
||||
disabled: info.disable,
|
||||
permissions: permissions(info.permission),
|
||||
}
|
||||
return encodeAgent(
|
||||
decodeAgent(
|
||||
JSON.stringify({
|
||||
model: modelSelection(info.model, info.variant),
|
||||
request: Object.keys(body).length ? { body } : undefined,
|
||||
system: info.prompt,
|
||||
description: info.description,
|
||||
mode: info.mode,
|
||||
hidden: info.hidden,
|
||||
color: info.color === undefined ? undefined : info.color.startsWith("#") ? info.color : "#aaaaaa",
|
||||
steps: info.steps,
|
||||
disabled: info.disable,
|
||||
permissions: permissions(info.permission),
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
function commands(info?: Readonly<Record<string, ConfigCommandV1.Info>>) {
|
||||
|
|
|
|||
|
|
@ -516,12 +516,12 @@ describe("Config", () => {
|
|||
})
|
||||
expect(migrated.providers?.["azure-cognitive-services"]).toBeUndefined()
|
||||
expect(migrated.providers?.["google-vertex"]).toMatchObject({
|
||||
package: undefined,
|
||||
settings: { project: "test-project", location: "us-central1" },
|
||||
models: {
|
||||
"claude-sonnet": { package: Provider.aisdk("@ai-sdk/google-vertex/anthropic") },
|
||||
},
|
||||
})
|
||||
expect(migrated.providers?.["google-vertex"]).not.toHaveProperty("package")
|
||||
expect(migrated.providers?.["google-vertex-anthropic"]).toBeUndefined()
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -3,7 +3,7 @@ export * as Config from "./config.js"
|
|||
import { Schema } from "effect"
|
||||
import { ephemeral, inventory } from "./event.js"
|
||||
import { Permission } from "./permission.js"
|
||||
import { AbsolutePath } from "./schema.js"
|
||||
import { AbsolutePath, optional } from "./schema.js"
|
||||
import { ConfigAgent } from "./config/agent.js"
|
||||
import { ConfigMedia } from "./config/media.js"
|
||||
import { ConfigCompaction } from "./config/compaction.js"
|
||||
|
|
@ -22,94 +22,94 @@ import { ConfigWatcher } from "./config/watcher.js"
|
|||
import { ConfigWarming } from "./config/warming.js"
|
||||
|
||||
export class Info extends Schema.Class<Info>("Config.Info")({
|
||||
$schema: Schema.optional(Schema.String).annotate({
|
||||
$schema: optional(Schema.String).annotate({
|
||||
description: "JSON schema reference for configuration validation",
|
||||
}),
|
||||
shell: Schema.String.pipe(Schema.optional).annotate({
|
||||
shell: Schema.String.pipe(optional).annotate({
|
||||
description: "Default shell to use for terminal and shell tool execution",
|
||||
}),
|
||||
model: ConfigModel.Selection.pipe(Schema.optional).annotate({
|
||||
model: ConfigModel.Selection.pipe(optional).annotate({
|
||||
description: "Default model to use when no session or agent model is selected",
|
||||
}),
|
||||
default_agent: Schema.String.pipe(Schema.optional).annotate({
|
||||
default_agent: Schema.String.pipe(optional).annotate({
|
||||
description: "Default primary agent to use when no session agent is selected",
|
||||
}),
|
||||
autoupdate: Schema.Union([Schema.Boolean, Schema.Literal("notify")])
|
||||
.pipe(Schema.optional)
|
||||
.pipe(optional)
|
||||
.annotate({
|
||||
description: "Automatically update or notify when a new version is available",
|
||||
}),
|
||||
share: Schema.Literals(["manual", "auto", "disabled"]).pipe(Schema.optional).annotate({
|
||||
share: Schema.Literals(["manual", "auto", "disabled"]).pipe(optional).annotate({
|
||||
description: "Control whether sessions may be shared manually, automatically, or not at all",
|
||||
}),
|
||||
enterprise: Schema.Struct({
|
||||
url: Schema.String.pipe(Schema.optional),
|
||||
url: Schema.String.pipe(optional),
|
||||
})
|
||||
.pipe(Schema.optional)
|
||||
.pipe(optional)
|
||||
.annotate({
|
||||
description: "Enterprise sharing service configuration",
|
||||
}),
|
||||
username: Schema.String.pipe(Schema.optional).annotate({
|
||||
username: Schema.String.pipe(optional).annotate({
|
||||
description: "Username displayed in conversations and used for telemetry identity",
|
||||
}),
|
||||
permissions: Permission.Ruleset.pipe(Schema.optional).annotate({
|
||||
permissions: Permission.Ruleset.pipe(optional).annotate({
|
||||
description: "Ordered tool permission rules applied to agent tool use",
|
||||
}),
|
||||
agents: Schema.Record(Schema.String, ConfigAgent.Info).pipe(Schema.optional).annotate({
|
||||
agents: Schema.Record(Schema.String, ConfigAgent.Info).pipe(optional).annotate({
|
||||
description: "Named built-in agent overrides and custom agent definitions",
|
||||
}),
|
||||
snapshots: Schema.Boolean.pipe(Schema.optional).annotate({
|
||||
snapshots: Schema.Boolean.pipe(optional).annotate({
|
||||
description: "Enable snapshots used for undo and revert behavior",
|
||||
}),
|
||||
watcher: ConfigWatcher.Info.pipe(Schema.optional).annotate({
|
||||
watcher: ConfigWatcher.Info.pipe(optional).annotate({
|
||||
description: "Filesystem watcher configuration",
|
||||
}),
|
||||
formatter: ConfigFormatter.Info.pipe(Schema.optional).annotate({
|
||||
formatter: ConfigFormatter.Info.pipe(optional).annotate({
|
||||
description: "Enable built-in formatters or configure formatter overrides",
|
||||
}),
|
||||
lsp: ConfigLSP.Info.pipe(Schema.optional).annotate({
|
||||
lsp: ConfigLSP.Info.pipe(optional).annotate({
|
||||
description: "Enable built-in language servers or configure server overrides",
|
||||
}),
|
||||
media: ConfigMedia.Info.pipe(Schema.optional).annotate({
|
||||
media: ConfigMedia.Info.pipe(optional).annotate({
|
||||
description: "Media processing configuration",
|
||||
}),
|
||||
tool_output: ConfigToolOutput.Info.pipe(Schema.optional).annotate({
|
||||
tool_output: ConfigToolOutput.Info.pipe(optional).annotate({
|
||||
description: "Tool output truncation thresholds",
|
||||
}),
|
||||
mcp: ConfigMCP.Info.pipe(Schema.optional).annotate({
|
||||
mcp: ConfigMCP.Info.pipe(optional).annotate({
|
||||
description: "MCP server configuration",
|
||||
}),
|
||||
compaction: ConfigCompaction.Info.pipe(Schema.optional).annotate({
|
||||
compaction: ConfigCompaction.Info.pipe(optional).annotate({
|
||||
description: "Conversation compaction behavior",
|
||||
}),
|
||||
skills: Schema.String.pipe(Schema.Array, Schema.optional).annotate({
|
||||
skills: Schema.String.pipe(Schema.Array, optional).annotate({
|
||||
description: "Additional paths or URLs to discover skills from",
|
||||
}),
|
||||
commands: Schema.Record(Schema.String, ConfigCommand.Info).pipe(Schema.optional).annotate({
|
||||
commands: Schema.Record(Schema.String, ConfigCommand.Info).pipe(optional).annotate({
|
||||
description: "Named slash command definitions",
|
||||
}),
|
||||
instructions: Schema.String.pipe(Schema.Array, Schema.optional).annotate({
|
||||
instructions: Schema.String.pipe(Schema.Array, optional).annotate({
|
||||
description: "Additional paths or URLs supplying ambient instructions",
|
||||
}),
|
||||
references: ConfigReference.Info.pipe(Schema.optional).annotate({
|
||||
references: ConfigReference.Info.pipe(optional).annotate({
|
||||
description: "Named local directories or Git repositories available as external context",
|
||||
}),
|
||||
websearch: ConfigWebSearch.Info.pipe(Schema.optional).annotate({
|
||||
websearch: ConfigWebSearch.Info.pipe(optional).annotate({
|
||||
description: "Web search provider selection",
|
||||
}),
|
||||
plugins: ConfigPlugin.Plugins.pipe(Schema.optional).annotate({
|
||||
plugins: ConfigPlugin.Plugins.pipe(optional).annotate({
|
||||
description: "Ordered plugin enablement directives and external package declarations",
|
||||
}),
|
||||
warming: ConfigWarming.Warming.pipe(Schema.optional).annotate({
|
||||
warming: ConfigWarming.Warming.pipe(optional).annotate({
|
||||
description: "Keep recently active sessions warm with transient model requests (default: false)",
|
||||
}),
|
||||
providers: Schema.Record(Schema.String, ConfigProvider.Info).pipe(Schema.optional),
|
||||
experimental: ConfigExperimental.Info.pipe(Schema.optional),
|
||||
providers: Schema.Record(Schema.String, ConfigProvider.Info).pipe(optional),
|
||||
experimental: ConfigExperimental.Info.pipe(optional),
|
||||
}) {}
|
||||
|
||||
export class Document extends Schema.Class<Document>("Config.Document")({
|
||||
type: Schema.Literal("document"),
|
||||
path: Schema.String.pipe(Schema.optional),
|
||||
path: Schema.String.pipe(optional),
|
||||
info: Info,
|
||||
}) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,21 +2,21 @@ export * as ConfigAgent from "./agent.js"
|
|||
|
||||
import { Schema } from "effect"
|
||||
import { Permission } from "../permission.js"
|
||||
import { PositiveInt } from "../schema.js"
|
||||
import { optional, PositiveInt } from "../schema.js"
|
||||
import { ConfigModel } from "./model.js"
|
||||
import { ConfigProvider } from "./provider.js"
|
||||
|
||||
export const Color = Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/))
|
||||
|
||||
export class Info extends Schema.Class<Info>("Config.Agent")({
|
||||
model: ConfigModel.Selection.pipe(Schema.optional),
|
||||
request: ConfigProvider.Request.pipe(Schema.optional),
|
||||
system: Schema.String.pipe(Schema.optional),
|
||||
description: Schema.String.pipe(Schema.optional),
|
||||
mode: Schema.Literals(["subagent", "primary", "all"]).pipe(Schema.optional),
|
||||
hidden: Schema.Boolean.pipe(Schema.optional),
|
||||
color: Color.pipe(Schema.optional),
|
||||
steps: PositiveInt.pipe(Schema.optional),
|
||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||
permissions: Permission.Ruleset.pipe(Schema.optional),
|
||||
model: ConfigModel.Selection.pipe(optional),
|
||||
request: ConfigProvider.Request.pipe(optional),
|
||||
system: Schema.String.pipe(optional),
|
||||
description: Schema.String.pipe(optional),
|
||||
mode: Schema.Literals(["subagent", "primary", "all"]).pipe(optional),
|
||||
hidden: Schema.Boolean.pipe(optional),
|
||||
color: Color.pipe(optional),
|
||||
steps: PositiveInt.pipe(optional),
|
||||
disabled: Schema.Boolean.pipe(optional),
|
||||
permissions: Permission.Ruleset.pipe(optional),
|
||||
}) {}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
export * as ConfigCommand from "./command.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { optional } from "../schema.js"
|
||||
import { ConfigModel } from "./model.js"
|
||||
|
||||
export class Info extends Schema.Class<Info>("Config.Command")({
|
||||
template: Schema.String,
|
||||
description: Schema.String.pipe(Schema.optional),
|
||||
agent: Schema.String.pipe(Schema.optional),
|
||||
model: ConfigModel.Selection.pipe(Schema.optional),
|
||||
subtask: Schema.Boolean.pipe(Schema.optional),
|
||||
description: Schema.String.pipe(optional),
|
||||
agent: Schema.String.pipe(optional),
|
||||
model: ConfigModel.Selection.pipe(optional),
|
||||
subtask: Schema.Boolean.pipe(optional),
|
||||
}) {}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
export * as ConfigCompaction from "./compaction.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { NonNegativeInt } from "../schema.js"
|
||||
import { NonNegativeInt, optional } from "../schema.js"
|
||||
|
||||
export class Keep extends Schema.Class<Keep>("Config.Compaction.Keep")({
|
||||
tokens: NonNegativeInt.pipe(Schema.optional),
|
||||
tokens: NonNegativeInt.pipe(optional),
|
||||
}) {}
|
||||
|
||||
export class Info extends Schema.Class<Info>("Config.Compaction")({
|
||||
auto: Schema.Boolean.pipe(Schema.optional),
|
||||
keep: Keep.pipe(Schema.optional),
|
||||
buffer: NonNegativeInt.pipe(Schema.optional),
|
||||
auto: Schema.Boolean.pipe(optional),
|
||||
keep: Keep.pipe(optional),
|
||||
buffer: NonNegativeInt.pipe(optional),
|
||||
}) {}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
export * as ConfigExperimental from "./experimental.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { NonNegativeInt } from "../schema.js"
|
||||
import { NonNegativeInt, optional } from "../schema.js"
|
||||
import { ConfigPolicy } from "./policy.js"
|
||||
|
||||
export class Info extends Schema.Class<Info>("ConfigExperimental.Info")({
|
||||
subagent_depth: NonNegativeInt.pipe(Schema.optional).annotate({
|
||||
subagent_depth: NonNegativeInt.pipe(optional).annotate({
|
||||
description: "Maximum subagent nesting depth. Defaults to 1.",
|
||||
}),
|
||||
policies: ConfigPolicy.Info.pipe(Schema.Array, Schema.optional).annotate({
|
||||
policies: ConfigPolicy.Info.pipe(Schema.Array, optional).annotate({
|
||||
description: "Ordered policies controlling access to configured resources",
|
||||
}),
|
||||
}) {}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
export * as ConfigFormatter from "./formatter.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { optional } from "../schema.js"
|
||||
|
||||
export class Entry extends Schema.Class<Entry>("Config.Formatter.Entry")({
|
||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||
command: Schema.String.pipe(Schema.Array, Schema.optional),
|
||||
environment: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
|
||||
extensions: Schema.String.pipe(Schema.Array, Schema.optional),
|
||||
disabled: Schema.Boolean.pipe(optional),
|
||||
command: Schema.String.pipe(Schema.Array, optional),
|
||||
environment: Schema.Record(Schema.String, Schema.String).pipe(optional),
|
||||
extensions: Schema.String.pipe(Schema.Array, optional),
|
||||
}) {}
|
||||
|
||||
export const Info = Schema.Union([Schema.Boolean, Schema.Record(Schema.String, Entry)])
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
export * as ConfigLSP from "./lsp.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { optional } from "../schema.js"
|
||||
|
||||
export const Disabled = Schema.Struct({
|
||||
disabled: Schema.Literal(true),
|
||||
|
|
@ -8,10 +9,10 @@ export const Disabled = Schema.Struct({
|
|||
|
||||
export class Server extends Schema.Class<Server>("Config.LSP.Server")({
|
||||
command: Schema.String.pipe(Schema.Array),
|
||||
extensions: Schema.String.pipe(Schema.Array, Schema.optional),
|
||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||
env: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
|
||||
initialization: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
extensions: Schema.String.pipe(Schema.Array, optional),
|
||||
disabled: Schema.Boolean.pipe(optional),
|
||||
env: Schema.Record(Schema.String, Schema.String).pipe(optional),
|
||||
initialization: Schema.Record(Schema.String, Schema.Unknown).pipe(optional),
|
||||
}) {}
|
||||
|
||||
export const Entry = Schema.Union([Disabled, Server])
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ export * as ConfigMCP from "./mcp.js"
|
|||
|
||||
import { Schema } from "effect"
|
||||
import { Mcp } from "../mcp.js"
|
||||
import { optional } from "../schema.js"
|
||||
|
||||
export const Timeout = Mcp.TimeoutConfig
|
||||
export type Timeout = Mcp.TimeoutConfig
|
||||
|
|
@ -14,6 +15,6 @@ export type Remote = Mcp.RemoteConfig
|
|||
export const Server = Mcp.ServerConfig
|
||||
|
||||
export class Info extends Schema.Class<Info>("Config.MCP")({
|
||||
timeout: Timeout.pipe(Schema.optional),
|
||||
servers: Schema.Record(Schema.String, Server).pipe(Schema.optional),
|
||||
timeout: Timeout.pipe(optional),
|
||||
servers: Schema.Record(Schema.String, Server).pipe(optional),
|
||||
}) {}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
export * as ConfigMedia from "./media.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { PositiveInt } from "../schema.js"
|
||||
import { optional, PositiveInt } from "../schema.js"
|
||||
|
||||
export class Image extends Schema.Class<Image>("Config.Media.Image")({
|
||||
auto_resize: Schema.Boolean.pipe(Schema.optional),
|
||||
max_width: PositiveInt.pipe(Schema.optional),
|
||||
max_height: PositiveInt.pipe(Schema.optional),
|
||||
max_base64_bytes: PositiveInt.pipe(Schema.optional),
|
||||
auto_resize: Schema.Boolean.pipe(optional),
|
||||
max_width: PositiveInt.pipe(optional),
|
||||
max_height: PositiveInt.pipe(optional),
|
||||
max_base64_bytes: PositiveInt.pipe(optional),
|
||||
}) {}
|
||||
|
||||
export class Info extends Schema.Class<Info>("Config.Media")({
|
||||
image: Image.pipe(Schema.optional),
|
||||
image: Image.pipe(optional),
|
||||
}) {}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ export * as ConfigModel from "./model.js"
|
|||
import { Schema, SchemaGetter } from "effect"
|
||||
import { Model } from "../model.js"
|
||||
import { Provider } from "../provider.js"
|
||||
import { optional } from "../schema.js"
|
||||
|
||||
const ProviderID = Provider.ID.check(Schema.isPattern(/^[^/#]+$/))
|
||||
const ModelID = Model.ID.check(Schema.isPattern(/^[^#]+$/))
|
||||
|
|
@ -11,7 +12,7 @@ const VariantID = Model.VariantID.check(Schema.isPattern(/^[^#]+$/))
|
|||
const Explicit = Schema.Struct({
|
||||
providerID: ProviderID,
|
||||
model: ModelID,
|
||||
variant: VariantID.pipe(Schema.optional),
|
||||
variant: VariantID.pipe(optional),
|
||||
})
|
||||
|
||||
const Short = Schema.String.check(Schema.isPattern(/^[^/#]+\/[^#]+(?:#[^#]+)?$/))
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
export * as ConfigPlugin from "./plugin.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { optional } from "../schema.js"
|
||||
|
||||
export class Entry extends Schema.Class<Entry>("Config.Plugin.Entry")({
|
||||
package: Schema.String,
|
||||
options: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
options: Schema.Record(Schema.String, Schema.Unknown).pipe(optional),
|
||||
}) {}
|
||||
|
||||
export const Plugin = Schema.Union([Schema.String, Entry])
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@ export * as ConfigProvider from "./provider.js"
|
|||
import { Schema } from "effect"
|
||||
import { Money } from "../money.js"
|
||||
import { Capabilities, Compatibility, Family, ID, VariantID } from "../model.js"
|
||||
import { optional } from "../schema.js"
|
||||
|
||||
const JsonRecord = Schema.Record(Schema.String, Schema.Json)
|
||||
|
||||
export const Overlays = {
|
||||
settings: JsonRecord.pipe(Schema.optional),
|
||||
headers: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
|
||||
body: JsonRecord.pipe(Schema.optional),
|
||||
settings: JsonRecord.pipe(optional),
|
||||
headers: Schema.Record(Schema.String, Schema.String).pipe(optional),
|
||||
body: JsonRecord.pipe(optional),
|
||||
}
|
||||
|
||||
export class Request extends Schema.Class<Request>("Config.Provider.Request")({
|
||||
|
|
@ -18,47 +19,47 @@ export class Request extends Schema.Class<Request>("Config.Provider.Request")({
|
|||
}) {}
|
||||
|
||||
class Cache extends Schema.Class<Cache>("Config.Model.Cost.Cache")({
|
||||
read: Money.USDPerMillionTokens.pipe(Schema.optional),
|
||||
write: Money.USDPerMillionTokens.pipe(Schema.optional),
|
||||
read: Money.USDPerMillionTokens.pipe(optional),
|
||||
write: Money.USDPerMillionTokens.pipe(optional),
|
||||
}) {}
|
||||
|
||||
class Cost extends Schema.Class<Cost>("Config.Model.Cost")({
|
||||
tier: Schema.Struct({
|
||||
type: Schema.Literal("context"),
|
||||
size: Schema.Int,
|
||||
}).pipe(Schema.optional),
|
||||
}).pipe(optional),
|
||||
input: Money.USDPerMillionTokens,
|
||||
output: Money.USDPerMillionTokens,
|
||||
cache: Cache.pipe(Schema.optional),
|
||||
cache: Cache.pipe(optional),
|
||||
}) {}
|
||||
|
||||
class Limit extends Schema.Class<Limit>("Config.Model.Limit")({
|
||||
context: Schema.Int.pipe(Schema.optional),
|
||||
input: Schema.Int.pipe(Schema.optional),
|
||||
output: Schema.Int.pipe(Schema.optional),
|
||||
context: Schema.Int.pipe(optional),
|
||||
input: Schema.Int.pipe(optional),
|
||||
output: Schema.Int.pipe(optional),
|
||||
}) {}
|
||||
|
||||
class Model extends Schema.Class<Model>("Config.Model")({
|
||||
modelID: ID.pipe(Schema.optional),
|
||||
family: Family.pipe(Schema.optional),
|
||||
name: Schema.String.pipe(Schema.optional),
|
||||
compatibility: Compatibility.pipe(Schema.optional),
|
||||
package: Schema.String.pipe(Schema.optional),
|
||||
modelID: ID.pipe(optional),
|
||||
family: Family.pipe(optional),
|
||||
name: Schema.String.pipe(optional),
|
||||
compatibility: Compatibility.pipe(optional),
|
||||
package: Schema.String.pipe(optional),
|
||||
...Overlays,
|
||||
capabilities: Capabilities.pipe(Schema.optional),
|
||||
capabilities: Capabilities.pipe(optional),
|
||||
variants: Schema.Struct({
|
||||
id: VariantID,
|
||||
...Overlays,
|
||||
}).pipe(Schema.Array, Schema.optional),
|
||||
cost: Schema.Union([Cost, Cost.pipe(Schema.Array)]).pipe(Schema.optional),
|
||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||
limit: Limit.pipe(Schema.optional),
|
||||
}).pipe(Schema.Array, optional),
|
||||
cost: Schema.Union([Cost, Cost.pipe(Schema.Array)]).pipe(optional),
|
||||
disabled: Schema.Boolean.pipe(optional),
|
||||
limit: Limit.pipe(optional),
|
||||
}) {}
|
||||
|
||||
export class Info extends Schema.Class<Info>("Config.Provider")({
|
||||
name: Schema.String.pipe(Schema.optional),
|
||||
env: Schema.String.pipe(Schema.Array, Schema.optional),
|
||||
package: Schema.String.pipe(Schema.optional),
|
||||
name: Schema.String.pipe(optional),
|
||||
env: Schema.String.pipe(Schema.Array, optional),
|
||||
package: Schema.String.pipe(optional),
|
||||
...Overlays,
|
||||
models: Schema.Record(Schema.String, Model).pipe(Schema.optional),
|
||||
models: Schema.Record(Schema.String, Model).pipe(optional),
|
||||
}) {}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
export * as ConfigReference from "./reference.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { optional } from "../schema.js"
|
||||
|
||||
export class Git extends Schema.Class<Git>("Config.Reference.Git")({
|
||||
repository: Schema.String,
|
||||
branch: Schema.String.pipe(Schema.optional),
|
||||
description: Schema.String.pipe(Schema.optional),
|
||||
hidden: Schema.Boolean.pipe(Schema.optional),
|
||||
branch: Schema.String.pipe(optional),
|
||||
description: Schema.String.pipe(optional),
|
||||
hidden: Schema.Boolean.pipe(optional),
|
||||
}) {}
|
||||
|
||||
export class Local extends Schema.Class<Local>("Config.Reference.Local")({
|
||||
path: Schema.String,
|
||||
description: Schema.String.pipe(Schema.optional),
|
||||
hidden: Schema.Boolean.pipe(Schema.optional),
|
||||
description: Schema.String.pipe(optional),
|
||||
hidden: Schema.Boolean.pipe(optional),
|
||||
}) {}
|
||||
|
||||
export const Entry = Schema.Union([Schema.String, Git, Local])
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
export * as ConfigToolOutput from "./tool-output.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { PositiveInt } from "../schema.js"
|
||||
import { optional, PositiveInt } from "../schema.js"
|
||||
|
||||
export class Info extends Schema.Class<Info>("Config.ToolOutput")({
|
||||
max_lines: PositiveInt.pipe(Schema.optional),
|
||||
max_bytes: PositiveInt.pipe(Schema.optional),
|
||||
max_lines: PositiveInt.pipe(optional),
|
||||
max_bytes: PositiveInt.pipe(optional),
|
||||
}) {}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
export * as ConfigWarming from "./warming.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { optional } from "../schema.js"
|
||||
|
||||
export class Info extends Schema.Class<Info>("Config.Warming")({
|
||||
prompt: Schema.String.pipe(Schema.optional).annotate({
|
||||
prompt: Schema.String.pipe(optional).annotate({
|
||||
description: "Prompt sent for keep-alive requests",
|
||||
}),
|
||||
interval: Schema.DurationFromString.pipe(Schema.optional).annotate({
|
||||
interval: Schema.DurationFromString.pipe(optional).annotate({
|
||||
description: 'Idle time between keep-alive requests (default: "4 minutes")',
|
||||
}),
|
||||
duration: Schema.DurationFromString.pipe(Schema.optional).annotate({
|
||||
duration: Schema.DurationFromString.pipe(optional).annotate({
|
||||
description: 'Time after the last active request to keep a session warm (default: "30 minutes")',
|
||||
}),
|
||||
}) {}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
export * as ConfigWatcher from "./watcher.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { optional } from "../schema.js"
|
||||
|
||||
export class Info extends Schema.Class<Info>("Config.Watcher")({
|
||||
ignore: Schema.String.pipe(Schema.Array, Schema.optional),
|
||||
ignore: Schema.String.pipe(Schema.Array, optional),
|
||||
}) {}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ import { optional, PositiveInt } from "./schema.js"
|
|||
import { IntegrationID } from "./integration-id.js"
|
||||
|
||||
export class TimeoutConfig extends Schema.Class<TimeoutConfig>("Mcp.TimeoutConfig")({
|
||||
startup: PositiveInt.pipe(Schema.optional).annotate({
|
||||
startup: PositiveInt.pipe(optional).annotate({
|
||||
description: "Maximum time in milliseconds to establish and initialize the MCP server.",
|
||||
}),
|
||||
catalog: PositiveInt.pipe(Schema.optional).annotate({
|
||||
catalog: PositiveInt.pipe(optional).annotate({
|
||||
description: "Maximum time in milliseconds to wait for MCP discovery requests such as tools/list and prompts/list.",
|
||||
}),
|
||||
execution: PositiveInt.pipe(Schema.optional).annotate({
|
||||
execution: PositiveInt.pipe(optional).annotate({
|
||||
description: "Maximum time in milliseconds to wait for MCP tool and prompt execution.",
|
||||
}),
|
||||
}) {}
|
||||
|
|
@ -19,35 +19,35 @@ export class TimeoutConfig extends Schema.Class<TimeoutConfig>("Mcp.TimeoutConfi
|
|||
export class LocalConfig extends Schema.Class<LocalConfig>("Mcp.LocalConfig")({
|
||||
type: Schema.Literal("local"),
|
||||
command: Schema.String.pipe(Schema.Array),
|
||||
cwd: Schema.String.pipe(Schema.optional).annotate({
|
||||
cwd: Schema.String.pipe(optional).annotate({
|
||||
description: "Working directory for the MCP server process. Relative paths resolve from the workspace directory.",
|
||||
}),
|
||||
environment: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
|
||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||
codemode: Schema.Boolean.pipe(Schema.optional).annotate({
|
||||
environment: Schema.Record(Schema.String, Schema.String).pipe(optional),
|
||||
disabled: Schema.Boolean.pipe(optional),
|
||||
codemode: Schema.Boolean.pipe(optional).annotate({
|
||||
description: "Expose this server's tools through Code Mode. Defaults to true.",
|
||||
}),
|
||||
timeout: TimeoutConfig.pipe(Schema.optional),
|
||||
timeout: TimeoutConfig.pipe(optional),
|
||||
}) {}
|
||||
|
||||
export class OAuthConfig extends Schema.Class<OAuthConfig>("Mcp.OAuthConfig")({
|
||||
client_id: Schema.String.pipe(Schema.optional),
|
||||
client_secret: Schema.String.pipe(Schema.optional),
|
||||
scope: Schema.String.pipe(Schema.optional),
|
||||
callback_port: Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 })).pipe(Schema.optional),
|
||||
redirect_uri: Schema.String.pipe(Schema.optional),
|
||||
client_id: Schema.String.pipe(optional),
|
||||
client_secret: Schema.String.pipe(optional),
|
||||
scope: Schema.String.pipe(optional),
|
||||
callback_port: Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 })).pipe(optional),
|
||||
redirect_uri: Schema.String.pipe(optional),
|
||||
}) {}
|
||||
|
||||
export class RemoteConfig extends Schema.Class<RemoteConfig>("Mcp.RemoteConfig")({
|
||||
type: Schema.Literal("remote"),
|
||||
url: Schema.String,
|
||||
headers: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
|
||||
oauth: Schema.Union([OAuthConfig, Schema.Literal(false)]).pipe(Schema.optional),
|
||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||
codemode: Schema.Boolean.pipe(Schema.optional).annotate({
|
||||
headers: Schema.Record(Schema.String, Schema.String).pipe(optional),
|
||||
oauth: Schema.Union([OAuthConfig, Schema.Literal(false)]).pipe(optional),
|
||||
disabled: Schema.Boolean.pipe(optional),
|
||||
codemode: Schema.Boolean.pipe(optional).annotate({
|
||||
description: "Expose this server's tools through Code Mode. Defaults to true.",
|
||||
}),
|
||||
timeout: TimeoutConfig.pipe(Schema.optional),
|
||||
timeout: TimeoutConfig.pipe(optional),
|
||||
}) {}
|
||||
|
||||
export const ServerConfig = Schema.Union([LocalConfig, RemoteConfig]).pipe(Schema.toTaggedUnion("type"))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { Schema } from "effect"
|
||||
import { Config } from "../src/config.js"
|
||||
import { ConfigAgent } from "../src/config/agent.js"
|
||||
import { ConfigMCP } from "../src/config/mcp.js"
|
||||
import { ConfigProvider } from "../src/config/provider.js"
|
||||
import { Mcp } from "../src/mcp.js"
|
||||
import { AbsolutePath } from "../src/schema.js"
|
||||
|
||||
describe("Config.Entry", () => {
|
||||
|
|
@ -29,7 +33,14 @@ describe("Config.Entry", () => {
|
|||
expect(decoded).toEqual(entries)
|
||||
expect(decoded[0]).toBeInstanceOf(Config.Document)
|
||||
expect(decoded[1]).not.toHaveProperty("path")
|
||||
expect(decoded.map((entry) => entry.type)).toEqual(["document", "document", "directory", "file", "agents", "claude"])
|
||||
expect(decoded.map((entry) => entry.type)).toEqual([
|
||||
"document",
|
||||
"document",
|
||||
"directory",
|
||||
"file",
|
||||
"agents",
|
||||
"claude",
|
||||
])
|
||||
expect(decoded[0]?.type === "document" ? decoded[0].info.permissions : undefined).toEqual([
|
||||
{ action: "shell", resource: "*", effect: "ask" },
|
||||
{ action: "shell", resource: "git status", effect: "allow" },
|
||||
|
|
@ -39,4 +50,39 @@ describe("Config.Entry", () => {
|
|||
test("has a stable public identifier", () => {
|
||||
expect(Config.Entry.ast.annotations?.identifier).toBe("Config.Entry")
|
||||
})
|
||||
|
||||
test("omits undefined optional properties while encoding", () => {
|
||||
const entry = new Config.Document({
|
||||
type: "document",
|
||||
path: undefined,
|
||||
info: new Config.Info({
|
||||
default_agent: undefined,
|
||||
agents: { reviewer: new ConfigAgent.Info({ description: undefined }) },
|
||||
mcp: new ConfigMCP.Info({
|
||||
timeout: undefined,
|
||||
servers: {
|
||||
docs: new Mcp.RemoteConfig({
|
||||
type: "remote",
|
||||
url: "https://example.com/mcp",
|
||||
headers: undefined,
|
||||
oauth: new Mcp.OAuthConfig({ client_id: undefined }),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
providers: { custom: new ConfigProvider.Info({ headers: undefined }) },
|
||||
}),
|
||||
})
|
||||
const encoded = Schema.encodeSync(Config.Entry)(entry)
|
||||
if (encoded.type !== "document") throw new Error("Expected a config document")
|
||||
|
||||
expect(encoded).not.toHaveProperty("path")
|
||||
expect(encoded.info).not.toHaveProperty("default_agent")
|
||||
expect(encoded.info.agents?.reviewer).not.toHaveProperty("description")
|
||||
expect(encoded.info.mcp).not.toHaveProperty("timeout")
|
||||
const docs = encoded.info.mcp?.servers?.docs
|
||||
if (docs?.type !== "remote" || docs.oauth === false) throw new Error("Expected a remote MCP server")
|
||||
expect(docs).not.toHaveProperty("headers")
|
||||
expect(docs.oauth).not.toHaveProperty("client_id")
|
||||
expect(encoded.info.providers?.custom).not.toHaveProperty("headers")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ it.live("returns ordered config entries for the requested directory", () =>
|
|||
const global = path.join(tmp.path, "global")
|
||||
const project = path.join(tmp.path, "project")
|
||||
const config = path.join(project, "opencode.json")
|
||||
yield* Effect.promise(() => Promise.all([fs.mkdir(global, { recursive: true }), fs.mkdir(project, { recursive: true })]))
|
||||
yield* Effect.promise(() =>
|
||||
Promise.all([fs.mkdir(global, { recursive: true }), fs.mkdir(project, { recursive: true })]),
|
||||
)
|
||||
yield* Effect.promise(() =>
|
||||
fs.writeFile(
|
||||
config,
|
||||
|
|
@ -25,6 +27,7 @@ it.live("returns ordered config entries for the requested directory", () =>
|
|||
{ action: "shell", resource: "*", effect: "ask" },
|
||||
{ action: "shell", resource: "git status", effect: "allow" },
|
||||
],
|
||||
mcp: { servers: { docs: { type: "remote", url: "https://example.com/mcp" } } },
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
|
@ -42,9 +45,8 @@ it.live("returns ordered config entries for the requested directory", () =>
|
|||
const response = yield* Effect.promise(() =>
|
||||
fetch(url, { headers: { authorization: `Basic ${btoa("opencode:secret")}` } }),
|
||||
)
|
||||
const entries = Schema.decodeUnknownSync(Schema.Array(Config.Entry))(
|
||||
yield* Effect.promise(() => response.json()),
|
||||
)
|
||||
const body: unknown = yield* Effect.promise(() => response.json())
|
||||
const entries = Schema.decodeUnknownSync(Schema.Array(Config.Entry))(body)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(Array.isArray(entries)).toBe(true)
|
||||
|
|
@ -56,7 +58,21 @@ it.live("returns ordered config entries for the requested directory", () =>
|
|||
{ action: "shell", resource: "git status", effect: "allow" },
|
||||
])
|
||||
expect(entries.some((entry) => entry.type === "file" && entry.path === config)).toBe(true)
|
||||
if (!Array.isArray(body)) throw new Error("Expected a config entry array")
|
||||
const raw = body.find((entry) => isRecord(entry) && entry["type"] === "document" && entry["path"] === config)
|
||||
if (!isRecord(raw) || !isRecord(raw["info"])) throw new Error("Expected a config document")
|
||||
expect(raw["info"]).not.toHaveProperty("default_agent")
|
||||
expect(raw["info"]).not.toHaveProperty("model")
|
||||
const mcp = raw["info"]["mcp"]
|
||||
if (!isRecord(mcp) || !isRecord(mcp["servers"]) || !isRecord(mcp["servers"]["docs"]))
|
||||
throw new Error("Expected an MCP server config")
|
||||
expect(mcp["servers"]["docs"]).not.toHaveProperty("headers")
|
||||
expect(mcp["servers"]["docs"]).not.toHaveProperty("oauth")
|
||||
}),
|
||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||
),
|
||||
)
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue