Browse Source

fix(config): omit unset optional values (#40918)

Dax 1 week ago
parent
commit
20fa444f31

+ 109 - 131
packages/client/src/promise/generated/types.ts

@@ -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"]
 }

+ 64 - 47
packages/core/src/v1/config/migrate.ts

@@ -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>>) {

+ 1 - 1
packages/core/test/config/config.test.ts

@@ -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
+ 715 - 1535
packages/protocol/openapi.json


+ 30 - 30
packages/schema/src/config.ts

@@ -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,
 }) {}
 

+ 11 - 11
packages/schema/src/config/agent.ts

@@ -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),
 }) {}

+ 5 - 4
packages/schema/src/config/command.ts

@@ -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),
 }) {}

+ 5 - 5
packages/schema/src/config/compaction.ts

@@ -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),
 }) {}

+ 3 - 3
packages/schema/src/config/experimental.ts

@@ -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",
   }),
 }) {}

+ 5 - 4
packages/schema/src/config/formatter.ts

@@ -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)])

+ 5 - 4
packages/schema/src/config/lsp.ts

@@ -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])

+ 3 - 2
packages/schema/src/config/mcp.ts

@@ -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),
 }) {}

+ 6 - 6
packages/schema/src/config/media.ts

@@ -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),
 }) {}

+ 2 - 1
packages/schema/src/config/model.ts

@@ -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(/^[^/#]+\/[^#]+(?:#[^#]+)?$/))

+ 2 - 1
packages/schema/src/config/plugin.ts

@@ -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])

+ 25 - 24
packages/schema/src/config/provider.ts

@@ -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),
 }) {}

+ 6 - 5
packages/schema/src/config/reference.ts

@@ -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])

+ 3 - 3
packages/schema/src/config/tool-output.ts

@@ -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),
 }) {}

+ 4 - 3
packages/schema/src/config/warming.ts

@@ -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")',
   }),
 }) {}

+ 2 - 1
packages/schema/src/config/watcher.ts

@@ -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),
 }) {}

+ 18 - 18
packages/schema/src/mcp.ts

@@ -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"))

+ 47 - 1
packages/schema/test/config.test.ts

@@ -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")
+  })
 })

+ 20 - 4
packages/server/test/config.test.ts

@@ -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
+ 715 - 1535
packages/www/openapi.json


File diff suppressed because it is too large
+ 715 - 1535
packages/www/public/openapi.json


Some files were not shown because too many files changed in this diff