Quellcode durchsuchen

core: refactor config schemas into separate modules for better maintainability

Dax Raad vor 1 Woche
Ursprung
Commit
9bf2dfea35

+ 7 - 281
packages/opencode/src/config/config.ts

@@ -40,6 +40,8 @@ import { ConfigPlugin } from "./plugin"
 import { ConfigManaged } from "./managed"
 import { ConfigCommand } from "./command"
 import { ConfigPermission } from "./permission"
+import { ConfigProvider } from "./provider"
+import { ConfigSkills } from "./skills"
 
 const log = Log.create({ service: "config" })
 
@@ -52,168 +54,6 @@ function mergeConfigConcatArrays(target: Info, source: Info): Info {
   return merged
 }
 
-export const Skills = z.object({
-  paths: z.array(z.string()).optional().describe("Additional paths to skill folders"),
-  urls: z
-    .array(z.string())
-    .optional()
-    .describe("URLs to fetch skills from (e.g., https://example.com/.well-known/skills/)"),
-})
-export type Skills = z.infer<typeof Skills>
-
-export const Keybinds = z
-  .object({
-    leader: z.string().optional().default("ctrl+x").describe("Leader key for keybind combinations"),
-    app_exit: z.string().optional().default("ctrl+c,ctrl+d,<leader>q").describe("Exit the application"),
-    editor_open: z.string().optional().default("<leader>e").describe("Open external editor"),
-    theme_list: z.string().optional().default("<leader>t").describe("List available themes"),
-    sidebar_toggle: z.string().optional().default("<leader>b").describe("Toggle sidebar"),
-    scrollbar_toggle: z.string().optional().default("none").describe("Toggle session scrollbar"),
-    username_toggle: z.string().optional().default("none").describe("Toggle username visibility"),
-    status_view: z.string().optional().default("<leader>s").describe("View status"),
-    session_export: z.string().optional().default("<leader>x").describe("Export session to editor"),
-    session_new: z.string().optional().default("<leader>n").describe("Create a new session"),
-    session_list: z.string().optional().default("<leader>l").describe("List all sessions"),
-    session_timeline: z.string().optional().default("<leader>g").describe("Show session timeline"),
-    session_fork: z.string().optional().default("none").describe("Fork session from message"),
-    session_rename: z.string().optional().default("ctrl+r").describe("Rename session"),
-    session_delete: z.string().optional().default("ctrl+d").describe("Delete session"),
-    stash_delete: z.string().optional().default("ctrl+d").describe("Delete stash entry"),
-    model_provider_list: z.string().optional().default("ctrl+a").describe("Open provider list from model dialog"),
-    model_favorite_toggle: z.string().optional().default("ctrl+f").describe("Toggle model favorite status"),
-    session_share: z.string().optional().default("none").describe("Share current session"),
-    session_unshare: z.string().optional().default("none").describe("Unshare current session"),
-    session_interrupt: z.string().optional().default("escape").describe("Interrupt current session"),
-    session_compact: z.string().optional().default("<leader>c").describe("Compact the session"),
-    messages_page_up: z.string().optional().default("pageup,ctrl+alt+b").describe("Scroll messages up by one page"),
-    messages_page_down: z
-      .string()
-      .optional()
-      .default("pagedown,ctrl+alt+f")
-      .describe("Scroll messages down by one page"),
-    messages_line_up: z.string().optional().default("ctrl+alt+y").describe("Scroll messages up by one line"),
-    messages_line_down: z.string().optional().default("ctrl+alt+e").describe("Scroll messages down by one line"),
-    messages_half_page_up: z.string().optional().default("ctrl+alt+u").describe("Scroll messages up by half page"),
-    messages_half_page_down: z.string().optional().default("ctrl+alt+d").describe("Scroll messages down by half page"),
-    messages_first: z.string().optional().default("ctrl+g,home").describe("Navigate to first message"),
-    messages_last: z.string().optional().default("ctrl+alt+g,end").describe("Navigate to last message"),
-    messages_next: z.string().optional().default("none").describe("Navigate to next message"),
-    messages_previous: z.string().optional().default("none").describe("Navigate to previous message"),
-    messages_last_user: z.string().optional().default("none").describe("Navigate to last user message"),
-    messages_copy: z.string().optional().default("<leader>y").describe("Copy message"),
-    messages_undo: z.string().optional().default("<leader>u").describe("Undo message"),
-    messages_redo: z.string().optional().default("<leader>r").describe("Redo message"),
-    messages_toggle_conceal: z
-      .string()
-      .optional()
-      .default("<leader>h")
-      .describe("Toggle code block concealment in messages"),
-    tool_details: z.string().optional().default("none").describe("Toggle tool details visibility"),
-    model_list: z.string().optional().default("<leader>m").describe("List available models"),
-    model_cycle_recent: z.string().optional().default("f2").describe("Next recently used model"),
-    model_cycle_recent_reverse: z.string().optional().default("shift+f2").describe("Previous recently used model"),
-    model_cycle_favorite: z.string().optional().default("none").describe("Next favorite model"),
-    model_cycle_favorite_reverse: z.string().optional().default("none").describe("Previous favorite model"),
-    command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
-    agent_list: z.string().optional().default("<leader>a").describe("List agents"),
-    agent_cycle: z.string().optional().default("tab").describe("Next agent"),
-    agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
-    variant_cycle: z.string().optional().default("ctrl+t").describe("Cycle model variants"),
-    variant_list: z.string().optional().default("none").describe("List model variants"),
-    input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
-    input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
-    input_submit: z.string().optional().default("return").describe("Submit input"),
-    input_newline: z
-      .string()
-      .optional()
-      .default("shift+return,ctrl+return,alt+return,ctrl+j")
-      .describe("Insert newline in input"),
-    input_move_left: z.string().optional().default("left,ctrl+b").describe("Move cursor left in input"),
-    input_move_right: z.string().optional().default("right,ctrl+f").describe("Move cursor right in input"),
-    input_move_up: z.string().optional().default("up").describe("Move cursor up in input"),
-    input_move_down: z.string().optional().default("down").describe("Move cursor down in input"),
-    input_select_left: z.string().optional().default("shift+left").describe("Select left in input"),
-    input_select_right: z.string().optional().default("shift+right").describe("Select right in input"),
-    input_select_up: z.string().optional().default("shift+up").describe("Select up in input"),
-    input_select_down: z.string().optional().default("shift+down").describe("Select down in input"),
-    input_line_home: z.string().optional().default("ctrl+a").describe("Move to start of line in input"),
-    input_line_end: z.string().optional().default("ctrl+e").describe("Move to end of line in input"),
-    input_select_line_home: z.string().optional().default("ctrl+shift+a").describe("Select to start of line in input"),
-    input_select_line_end: z.string().optional().default("ctrl+shift+e").describe("Select to end of line in input"),
-    input_visual_line_home: z.string().optional().default("alt+a").describe("Move to start of visual line in input"),
-    input_visual_line_end: z.string().optional().default("alt+e").describe("Move to end of visual line in input"),
-    input_select_visual_line_home: z
-      .string()
-      .optional()
-      .default("alt+shift+a")
-      .describe("Select to start of visual line in input"),
-    input_select_visual_line_end: z
-      .string()
-      .optional()
-      .default("alt+shift+e")
-      .describe("Select to end of visual line in input"),
-    input_buffer_home: z.string().optional().default("home").describe("Move to start of buffer in input"),
-    input_buffer_end: z.string().optional().default("end").describe("Move to end of buffer in input"),
-    input_select_buffer_home: z
-      .string()
-      .optional()
-      .default("shift+home")
-      .describe("Select to start of buffer in input"),
-    input_select_buffer_end: z.string().optional().default("shift+end").describe("Select to end of buffer in input"),
-    input_delete_line: z.string().optional().default("ctrl+shift+d").describe("Delete line in input"),
-    input_delete_to_line_end: z.string().optional().default("ctrl+k").describe("Delete to end of line in input"),
-    input_delete_to_line_start: z.string().optional().default("ctrl+u").describe("Delete to start of line in input"),
-    input_backspace: z.string().optional().default("backspace,shift+backspace").describe("Backspace in input"),
-    input_delete: z.string().optional().default("ctrl+d,delete,shift+delete").describe("Delete character in input"),
-    input_undo: z.string().optional().default("ctrl+-,super+z").describe("Undo in input"),
-    input_redo: z.string().optional().default("ctrl+.,super+shift+z").describe("Redo in input"),
-    input_word_forward: z
-      .string()
-      .optional()
-      .default("alt+f,alt+right,ctrl+right")
-      .describe("Move word forward in input"),
-    input_word_backward: z
-      .string()
-      .optional()
-      .default("alt+b,alt+left,ctrl+left")
-      .describe("Move word backward in input"),
-    input_select_word_forward: z
-      .string()
-      .optional()
-      .default("alt+shift+f,alt+shift+right")
-      .describe("Select word forward in input"),
-    input_select_word_backward: z
-      .string()
-      .optional()
-      .default("alt+shift+b,alt+shift+left")
-      .describe("Select word backward in input"),
-    input_delete_word_forward: z
-      .string()
-      .optional()
-      .default("alt+d,alt+delete,ctrl+delete")
-      .describe("Delete word forward in input"),
-    input_delete_word_backward: z
-      .string()
-      .optional()
-      .default("ctrl+w,ctrl+backspace,alt+backspace")
-      .describe("Delete word backward in input"),
-    history_previous: z.string().optional().default("up").describe("Previous history item"),
-    history_next: z.string().optional().default("down").describe("Next history item"),
-    session_child_first: z.string().optional().default("<leader>down").describe("Go to first child session"),
-    session_child_cycle: z.string().optional().default("right").describe("Go to next child session"),
-    session_child_cycle_reverse: z.string().optional().default("left").describe("Go to previous child session"),
-    session_parent: z.string().optional().default("up").describe("Go to parent session"),
-    terminal_suspend: z.string().optional().default("ctrl+z").describe("Suspend terminal"),
-    terminal_title_toggle: z.string().optional().default("none").describe("Toggle terminal title"),
-    tips_toggle: z.string().optional().default("<leader>h").describe("Toggle tips on home screen"),
-    plugin_manager: z.string().optional().default("none").describe("Open plugin manager dialog"),
-    display_thinking: z.string().optional().default("none").describe("Toggle thinking blocks visibility"),
-  })
-  .strict()
-  .meta({
-    ref: "KeybindsConfig",
-  })
-
 export const Server = z
   .object({
     port: z.number().int().positive().optional().describe("Port to listen on"),
@@ -232,123 +72,6 @@ export const Layout = z.enum(["auto", "stretch"]).meta({
 })
 export type Layout = z.infer<typeof Layout>
 
-export const Model = z
-  .object({
-    id: z.string(),
-    name: z.string(),
-    family: z.string().optional(),
-    release_date: z.string(),
-    attachment: z.boolean(),
-    reasoning: z.boolean(),
-    temperature: z.boolean(),
-    tool_call: z.boolean(),
-    interleaved: z
-      .union([
-        z.literal(true),
-        z
-          .object({
-            field: z.enum(["reasoning_content", "reasoning_details"]),
-          })
-          .strict(),
-      ])
-      .optional(),
-    cost: z
-      .object({
-        input: z.number(),
-        output: z.number(),
-        cache_read: z.number().optional(),
-        cache_write: z.number().optional(),
-        context_over_200k: z
-          .object({
-            input: z.number(),
-            output: z.number(),
-            cache_read: z.number().optional(),
-            cache_write: z.number().optional(),
-          })
-          .optional(),
-      })
-      .optional(),
-    limit: z.object({
-      context: z.number(),
-      input: z.number().optional(),
-      output: z.number(),
-    }),
-    modalities: z
-      .object({
-        input: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
-        output: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
-      })
-      .optional(),
-    experimental: z.boolean().optional(),
-    status: z.enum(["alpha", "beta", "deprecated"]).optional(),
-    provider: z.object({ npm: z.string().optional(), api: z.string().optional() }).optional(),
-    options: z.record(z.string(), z.any()),
-    headers: z.record(z.string(), z.string()).optional(),
-    variants: z
-      .record(
-        z.string(),
-        z
-          .object({
-            disabled: z.boolean().optional().describe("Disable this variant for the model"),
-          })
-          .catchall(z.any()),
-      )
-      .optional()
-      .describe("Variant-specific configuration"),
-  })
-  .partial()
-
-export const Provider = z
-  .object({
-    api: z.string().optional(),
-    name: z.string(),
-    env: z.array(z.string()),
-    id: z.string(),
-    npm: z.string().optional(),
-    whitelist: z.array(z.string()).optional(),
-    blacklist: z.array(z.string()).optional(),
-    options: z
-      .object({
-        apiKey: z.string().optional(),
-        baseURL: z.string().optional(),
-        enterpriseUrl: z.string().optional().describe("GitHub Enterprise URL for copilot authentication"),
-        setCacheKey: z.boolean().optional().describe("Enable promptCacheKey for this provider (default false)"),
-        timeout: z
-          .union([
-            z
-              .number()
-              .int()
-              .positive()
-              .describe(
-                "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
-              ),
-            z.literal(false).describe("Disable timeout for this provider entirely."),
-          ])
-          .optional()
-          .describe(
-            "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
-          ),
-        chunkTimeout: z
-          .number()
-          .int()
-          .positive()
-          .optional()
-          .describe(
-            "Timeout in milliseconds between streamed SSE chunks for this provider. If no chunk arrives within this window, the request is aborted.",
-          ),
-      })
-      .catchall(z.any())
-      .optional(),
-    models: z.record(z.string(), Model).optional(),
-  })
-  .partial()
-  .strict()
-  .meta({
-    ref: "ProviderConfig",
-  })
-
-export type Provider = z.infer<typeof Provider>
-
 export const Info = z
   .object({
     $schema: z.string().optional().describe("JSON schema reference for configuration validation"),
@@ -358,7 +81,7 @@ export const Info = z
       .record(z.string(), ConfigCommand.Info)
       .optional()
       .describe("Command configuration, see https://opencode.ai/docs/commands"),
-    skills: Skills.optional().describe("Additional skill folder paths"),
+    skills: ConfigSkills.Info.optional().describe("Additional skill folder paths"),
     watcher: z
       .object({
         ignore: z.array(z.string()).optional(),
@@ -427,7 +150,10 @@ export const Info = z
       .catchall(ConfigAgent.Info)
       .optional()
       .describe("Agent configuration, see https://opencode.ai/docs/agents"),
-    provider: z.record(z.string(), Provider).optional().describe("Custom provider configurations and model overrides"),
+    provider: z
+      .record(z.string(), ConfigProvider.Info)
+      .optional()
+      .describe("Custom provider configurations and model overrides"),
     mcp: z
       .record(
         z.string(),

+ 2 - 0
packages/opencode/src/config/index.ts

@@ -7,3 +7,5 @@ export * as ConfigMCP from "./mcp"
 export { ConfigModelID } from "./model-id"
 export * as ConfigPermission from "./permission"
 export * as ConfigPaths from "./paths"
+export * as ConfigProvider from "./provider"
+export * as ConfigSkills from "./skills"

+ 154 - 162
packages/opencode/src/config/keybinds.ts

@@ -1,164 +1,156 @@
+export * as ConfigKeybinds from "./keybinds"
+
 import z from "zod"
 
-export namespace ConfigKeybinds {
-  export const Keybinds = z
-    .object({
-      leader: z.string().optional().default("ctrl+x").describe("Leader key for keybind combinations"),
-      app_exit: z.string().optional().default("ctrl+c,ctrl+d,<leader>q").describe("Exit the application"),
-      editor_open: z.string().optional().default("<leader>e").describe("Open external editor"),
-      theme_list: z.string().optional().default("<leader>t").describe("List available themes"),
-      sidebar_toggle: z.string().optional().default("<leader>b").describe("Toggle sidebar"),
-      scrollbar_toggle: z.string().optional().default("none").describe("Toggle session scrollbar"),
-      username_toggle: z.string().optional().default("none").describe("Toggle username visibility"),
-      status_view: z.string().optional().default("<leader>s").describe("View status"),
-      session_export: z.string().optional().default("<leader>x").describe("Export session to editor"),
-      session_new: z.string().optional().default("<leader>n").describe("Create a new session"),
-      session_list: z.string().optional().default("<leader>l").describe("List all sessions"),
-      session_timeline: z.string().optional().default("<leader>g").describe("Show session timeline"),
-      session_fork: z.string().optional().default("none").describe("Fork session from message"),
-      session_rename: z.string().optional().default("ctrl+r").describe("Rename session"),
-      session_delete: z.string().optional().default("ctrl+d").describe("Delete session"),
-      stash_delete: z.string().optional().default("ctrl+d").describe("Delete stash entry"),
-      model_provider_list: z.string().optional().default("ctrl+a").describe("Open provider list from model dialog"),
-      model_favorite_toggle: z.string().optional().default("ctrl+f").describe("Toggle model favorite status"),
-      session_share: z.string().optional().default("none").describe("Share current session"),
-      session_unshare: z.string().optional().default("none").describe("Unshare current session"),
-      session_interrupt: z.string().optional().default("escape").describe("Interrupt current session"),
-      session_compact: z.string().optional().default("<leader>c").describe("Compact the session"),
-      messages_page_up: z.string().optional().default("pageup,ctrl+alt+b").describe("Scroll messages up by one page"),
-      messages_page_down: z
-        .string()
-        .optional()
-        .default("pagedown,ctrl+alt+f")
-        .describe("Scroll messages down by one page"),
-      messages_line_up: z.string().optional().default("ctrl+alt+y").describe("Scroll messages up by one line"),
-      messages_line_down: z.string().optional().default("ctrl+alt+e").describe("Scroll messages down by one line"),
-      messages_half_page_up: z.string().optional().default("ctrl+alt+u").describe("Scroll messages up by half page"),
-      messages_half_page_down: z
-        .string()
-        .optional()
-        .default("ctrl+alt+d")
-        .describe("Scroll messages down by half page"),
-      messages_first: z.string().optional().default("ctrl+g,home").describe("Navigate to first message"),
-      messages_last: z.string().optional().default("ctrl+alt+g,end").describe("Navigate to last message"),
-      messages_next: z.string().optional().default("none").describe("Navigate to next message"),
-      messages_previous: z.string().optional().default("none").describe("Navigate to previous message"),
-      messages_last_user: z.string().optional().default("none").describe("Navigate to last user message"),
-      messages_copy: z.string().optional().default("<leader>y").describe("Copy message"),
-      messages_undo: z.string().optional().default("<leader>u").describe("Undo message"),
-      messages_redo: z.string().optional().default("<leader>r").describe("Redo message"),
-      messages_toggle_conceal: z
-        .string()
-        .optional()
-        .default("<leader>h")
-        .describe("Toggle code block concealment in messages"),
-      tool_details: z.string().optional().default("none").describe("Toggle tool details visibility"),
-      model_list: z.string().optional().default("<leader>m").describe("List available models"),
-      model_cycle_recent: z.string().optional().default("f2").describe("Next recently used model"),
-      model_cycle_recent_reverse: z.string().optional().default("shift+f2").describe("Previous recently used model"),
-      model_cycle_favorite: z.string().optional().default("none").describe("Next favorite model"),
-      model_cycle_favorite_reverse: z.string().optional().default("none").describe("Previous favorite model"),
-      command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
-      agent_list: z.string().optional().default("<leader>a").describe("List agents"),
-      agent_cycle: z.string().optional().default("tab").describe("Next agent"),
-      agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
-      variant_cycle: z.string().optional().default("ctrl+t").describe("Cycle model variants"),
-      variant_list: z.string().optional().default("none").describe("List model variants"),
-      input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
-      input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
-      input_submit: z.string().optional().default("return").describe("Submit input"),
-      input_newline: z
-        .string()
-        .optional()
-        .default("shift+return,ctrl+return,alt+return,ctrl+j")
-        .describe("Insert newline in input"),
-      input_move_left: z.string().optional().default("left,ctrl+b").describe("Move cursor left in input"),
-      input_move_right: z.string().optional().default("right,ctrl+f").describe("Move cursor right in input"),
-      input_move_up: z.string().optional().default("up").describe("Move cursor up in input"),
-      input_move_down: z.string().optional().default("down").describe("Move cursor down in input"),
-      input_select_left: z.string().optional().default("shift+left").describe("Select left in input"),
-      input_select_right: z.string().optional().default("shift+right").describe("Select right in input"),
-      input_select_up: z.string().optional().default("shift+up").describe("Select up in input"),
-      input_select_down: z.string().optional().default("shift+down").describe("Select down in input"),
-      input_line_home: z.string().optional().default("ctrl+a").describe("Move to start of line in input"),
-      input_line_end: z.string().optional().default("ctrl+e").describe("Move to end of line in input"),
-      input_select_line_home: z
-        .string()
-        .optional()
-        .default("ctrl+shift+a")
-        .describe("Select to start of line in input"),
-      input_select_line_end: z.string().optional().default("ctrl+shift+e").describe("Select to end of line in input"),
-      input_visual_line_home: z.string().optional().default("alt+a").describe("Move to start of visual line in input"),
-      input_visual_line_end: z.string().optional().default("alt+e").describe("Move to end of visual line in input"),
-      input_select_visual_line_home: z
-        .string()
-        .optional()
-        .default("alt+shift+a")
-        .describe("Select to start of visual line in input"),
-      input_select_visual_line_end: z
-        .string()
-        .optional()
-        .default("alt+shift+e")
-        .describe("Select to end of visual line in input"),
-      input_buffer_home: z.string().optional().default("home").describe("Move to start of buffer in input"),
-      input_buffer_end: z.string().optional().default("end").describe("Move to end of buffer in input"),
-      input_select_buffer_home: z
-        .string()
-        .optional()
-        .default("shift+home")
-        .describe("Select to start of buffer in input"),
-      input_select_buffer_end: z.string().optional().default("shift+end").describe("Select to end of buffer in input"),
-      input_delete_line: z.string().optional().default("ctrl+shift+d").describe("Delete line in input"),
-      input_delete_to_line_end: z.string().optional().default("ctrl+k").describe("Delete to end of line in input"),
-      input_delete_to_line_start: z.string().optional().default("ctrl+u").describe("Delete to start of line in input"),
-      input_backspace: z.string().optional().default("backspace,shift+backspace").describe("Backspace in input"),
-      input_delete: z.string().optional().default("ctrl+d,delete,shift+delete").describe("Delete character in input"),
-      input_undo: z.string().optional().default("ctrl+-,super+z").describe("Undo in input"),
-      input_redo: z.string().optional().default("ctrl+.,super+shift+z").describe("Redo in input"),
-      input_word_forward: z
-        .string()
-        .optional()
-        .default("alt+f,alt+right,ctrl+right")
-        .describe("Move word forward in input"),
-      input_word_backward: z
-        .string()
-        .optional()
-        .default("alt+b,alt+left,ctrl+left")
-        .describe("Move word backward in input"),
-      input_select_word_forward: z
-        .string()
-        .optional()
-        .default("alt+shift+f,alt+shift+right")
-        .describe("Select word forward in input"),
-      input_select_word_backward: z
-        .string()
-        .optional()
-        .default("alt+shift+b,alt+shift+left")
-        .describe("Select word backward in input"),
-      input_delete_word_forward: z
-        .string()
-        .optional()
-        .default("alt+d,alt+delete,ctrl+delete")
-        .describe("Delete word forward in input"),
-      input_delete_word_backward: z
-        .string()
-        .optional()
-        .default("ctrl+w,ctrl+backspace,alt+backspace")
-        .describe("Delete word backward in input"),
-      history_previous: z.string().optional().default("up").describe("Previous history item"),
-      history_next: z.string().optional().default("down").describe("Next history item"),
-      session_child_first: z.string().optional().default("<leader>down").describe("Go to first child session"),
-      session_child_cycle: z.string().optional().default("right").describe("Go to next child session"),
-      session_child_cycle_reverse: z.string().optional().default("left").describe("Go to previous child session"),
-      session_parent: z.string().optional().default("up").describe("Go to parent session"),
-      terminal_suspend: z.string().optional().default("ctrl+z").describe("Suspend terminal"),
-      terminal_title_toggle: z.string().optional().default("none").describe("Toggle terminal title"),
-      tips_toggle: z.string().optional().default("<leader>h").describe("Toggle tips on home screen"),
-      plugin_manager: z.string().optional().default("none").describe("Open plugin manager dialog"),
-      display_thinking: z.string().optional().default("none").describe("Toggle thinking blocks visibility"),
-    })
-    .strict()
-    .meta({
-      ref: "KeybindsConfig",
-    })
-}
+export const Keybinds = z
+  .object({
+    leader: z.string().optional().default("ctrl+x").describe("Leader key for keybind combinations"),
+    app_exit: z.string().optional().default("ctrl+c,ctrl+d,<leader>q").describe("Exit the application"),
+    editor_open: z.string().optional().default("<leader>e").describe("Open external editor"),
+    theme_list: z.string().optional().default("<leader>t").describe("List available themes"),
+    sidebar_toggle: z.string().optional().default("<leader>b").describe("Toggle sidebar"),
+    scrollbar_toggle: z.string().optional().default("none").describe("Toggle session scrollbar"),
+    username_toggle: z.string().optional().default("none").describe("Toggle username visibility"),
+    status_view: z.string().optional().default("<leader>s").describe("View status"),
+    session_export: z.string().optional().default("<leader>x").describe("Export session to editor"),
+    session_new: z.string().optional().default("<leader>n").describe("Create a new session"),
+    session_list: z.string().optional().default("<leader>l").describe("List all sessions"),
+    session_timeline: z.string().optional().default("<leader>g").describe("Show session timeline"),
+    session_fork: z.string().optional().default("none").describe("Fork session from message"),
+    session_rename: z.string().optional().default("ctrl+r").describe("Rename session"),
+    session_delete: z.string().optional().default("ctrl+d").describe("Delete session"),
+    stash_delete: z.string().optional().default("ctrl+d").describe("Delete stash entry"),
+    model_provider_list: z.string().optional().default("ctrl+a").describe("Open provider list from model dialog"),
+    model_favorite_toggle: z.string().optional().default("ctrl+f").describe("Toggle model favorite status"),
+    session_share: z.string().optional().default("none").describe("Share current session"),
+    session_unshare: z.string().optional().default("none").describe("Unshare current session"),
+    session_interrupt: z.string().optional().default("escape").describe("Interrupt current session"),
+    session_compact: z.string().optional().default("<leader>c").describe("Compact the session"),
+    messages_page_up: z.string().optional().default("pageup,ctrl+alt+b").describe("Scroll messages up by one page"),
+    messages_page_down: z
+      .string()
+      .optional()
+      .default("pagedown,ctrl+alt+f")
+      .describe("Scroll messages down by one page"),
+    messages_line_up: z.string().optional().default("ctrl+alt+y").describe("Scroll messages up by one line"),
+    messages_line_down: z.string().optional().default("ctrl+alt+e").describe("Scroll messages down by one line"),
+    messages_half_page_up: z.string().optional().default("ctrl+alt+u").describe("Scroll messages up by half page"),
+    messages_half_page_down: z.string().optional().default("ctrl+alt+d").describe("Scroll messages down by half page"),
+    messages_first: z.string().optional().default("ctrl+g,home").describe("Navigate to first message"),
+    messages_last: z.string().optional().default("ctrl+alt+g,end").describe("Navigate to last message"),
+    messages_next: z.string().optional().default("none").describe("Navigate to next message"),
+    messages_previous: z.string().optional().default("none").describe("Navigate to previous message"),
+    messages_last_user: z.string().optional().default("none").describe("Navigate to last user message"),
+    messages_copy: z.string().optional().default("<leader>y").describe("Copy message"),
+    messages_undo: z.string().optional().default("<leader>u").describe("Undo message"),
+    messages_redo: z.string().optional().default("<leader>r").describe("Redo message"),
+    messages_toggle_conceal: z
+      .string()
+      .optional()
+      .default("<leader>h")
+      .describe("Toggle code block concealment in messages"),
+    tool_details: z.string().optional().default("none").describe("Toggle tool details visibility"),
+    model_list: z.string().optional().default("<leader>m").describe("List available models"),
+    model_cycle_recent: z.string().optional().default("f2").describe("Next recently used model"),
+    model_cycle_recent_reverse: z.string().optional().default("shift+f2").describe("Previous recently used model"),
+    model_cycle_favorite: z.string().optional().default("none").describe("Next favorite model"),
+    model_cycle_favorite_reverse: z.string().optional().default("none").describe("Previous favorite model"),
+    command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
+    agent_list: z.string().optional().default("<leader>a").describe("List agents"),
+    agent_cycle: z.string().optional().default("tab").describe("Next agent"),
+    agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
+    variant_cycle: z.string().optional().default("ctrl+t").describe("Cycle model variants"),
+    variant_list: z.string().optional().default("none").describe("List model variants"),
+    input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
+    input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
+    input_submit: z.string().optional().default("return").describe("Submit input"),
+    input_newline: z
+      .string()
+      .optional()
+      .default("shift+return,ctrl+return,alt+return,ctrl+j")
+      .describe("Insert newline in input"),
+    input_move_left: z.string().optional().default("left,ctrl+b").describe("Move cursor left in input"),
+    input_move_right: z.string().optional().default("right,ctrl+f").describe("Move cursor right in input"),
+    input_move_up: z.string().optional().default("up").describe("Move cursor up in input"),
+    input_move_down: z.string().optional().default("down").describe("Move cursor down in input"),
+    input_select_left: z.string().optional().default("shift+left").describe("Select left in input"),
+    input_select_right: z.string().optional().default("shift+right").describe("Select right in input"),
+    input_select_up: z.string().optional().default("shift+up").describe("Select up in input"),
+    input_select_down: z.string().optional().default("shift+down").describe("Select down in input"),
+    input_line_home: z.string().optional().default("ctrl+a").describe("Move to start of line in input"),
+    input_line_end: z.string().optional().default("ctrl+e").describe("Move to end of line in input"),
+    input_select_line_home: z.string().optional().default("ctrl+shift+a").describe("Select to start of line in input"),
+    input_select_line_end: z.string().optional().default("ctrl+shift+e").describe("Select to end of line in input"),
+    input_visual_line_home: z.string().optional().default("alt+a").describe("Move to start of visual line in input"),
+    input_visual_line_end: z.string().optional().default("alt+e").describe("Move to end of visual line in input"),
+    input_select_visual_line_home: z
+      .string()
+      .optional()
+      .default("alt+shift+a")
+      .describe("Select to start of visual line in input"),
+    input_select_visual_line_end: z
+      .string()
+      .optional()
+      .default("alt+shift+e")
+      .describe("Select to end of visual line in input"),
+    input_buffer_home: z.string().optional().default("home").describe("Move to start of buffer in input"),
+    input_buffer_end: z.string().optional().default("end").describe("Move to end of buffer in input"),
+    input_select_buffer_home: z
+      .string()
+      .optional()
+      .default("shift+home")
+      .describe("Select to start of buffer in input"),
+    input_select_buffer_end: z.string().optional().default("shift+end").describe("Select to end of buffer in input"),
+    input_delete_line: z.string().optional().default("ctrl+shift+d").describe("Delete line in input"),
+    input_delete_to_line_end: z.string().optional().default("ctrl+k").describe("Delete to end of line in input"),
+    input_delete_to_line_start: z.string().optional().default("ctrl+u").describe("Delete to start of line in input"),
+    input_backspace: z.string().optional().default("backspace,shift+backspace").describe("Backspace in input"),
+    input_delete: z.string().optional().default("ctrl+d,delete,shift+delete").describe("Delete character in input"),
+    input_undo: z.string().optional().default("ctrl+-,super+z").describe("Undo in input"),
+    input_redo: z.string().optional().default("ctrl+.,super+shift+z").describe("Redo in input"),
+    input_word_forward: z
+      .string()
+      .optional()
+      .default("alt+f,alt+right,ctrl+right")
+      .describe("Move word forward in input"),
+    input_word_backward: z
+      .string()
+      .optional()
+      .default("alt+b,alt+left,ctrl+left")
+      .describe("Move word backward in input"),
+    input_select_word_forward: z
+      .string()
+      .optional()
+      .default("alt+shift+f,alt+shift+right")
+      .describe("Select word forward in input"),
+    input_select_word_backward: z
+      .string()
+      .optional()
+      .default("alt+shift+b,alt+shift+left")
+      .describe("Select word backward in input"),
+    input_delete_word_forward: z
+      .string()
+      .optional()
+      .default("alt+d,alt+delete,ctrl+delete")
+      .describe("Delete word forward in input"),
+    input_delete_word_backward: z
+      .string()
+      .optional()
+      .default("ctrl+w,ctrl+backspace,alt+backspace")
+      .describe("Delete word backward in input"),
+    history_previous: z.string().optional().default("up").describe("Previous history item"),
+    history_next: z.string().optional().default("down").describe("Next history item"),
+    session_child_first: z.string().optional().default("<leader>down").describe("Go to first child session"),
+    session_child_cycle: z.string().optional().default("right").describe("Go to next child session"),
+    session_child_cycle_reverse: z.string().optional().default("left").describe("Go to previous child session"),
+    session_parent: z.string().optional().default("up").describe("Go to parent session"),
+    terminal_suspend: z.string().optional().default("ctrl+z").describe("Suspend terminal"),
+    terminal_title_toggle: z.string().optional().default("none").describe("Toggle terminal title"),
+    tips_toggle: z.string().optional().default("<leader>h").describe("Toggle tips on home screen"),
+    plugin_manager: z.string().optional().default("none").describe("Open plugin manager dialog"),
+    display_thinking: z.string().optional().default("none").describe("Toggle thinking blocks visibility"),
+  })
+  .strict()
+  .meta({
+    ref: "KeybindsConfig",
+  })

+ 120 - 0
packages/opencode/src/config/provider.ts

@@ -0,0 +1,120 @@
+import z from "zod"
+
+export namespace ConfigProvider {
+  export const Model = z
+    .object({
+      id: z.string(),
+      name: z.string(),
+      family: z.string().optional(),
+      release_date: z.string(),
+      attachment: z.boolean(),
+      reasoning: z.boolean(),
+      temperature: z.boolean(),
+      tool_call: z.boolean(),
+      interleaved: z
+        .union([
+          z.literal(true),
+          z
+            .object({
+              field: z.enum(["reasoning_content", "reasoning_details"]),
+            })
+            .strict(),
+        ])
+        .optional(),
+      cost: z
+        .object({
+          input: z.number(),
+          output: z.number(),
+          cache_read: z.number().optional(),
+          cache_write: z.number().optional(),
+          context_over_200k: z
+            .object({
+              input: z.number(),
+              output: z.number(),
+              cache_read: z.number().optional(),
+              cache_write: z.number().optional(),
+            })
+            .optional(),
+        })
+        .optional(),
+      limit: z.object({
+        context: z.number(),
+        input: z.number().optional(),
+        output: z.number(),
+      }),
+      modalities: z
+        .object({
+          input: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
+          output: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
+        })
+        .optional(),
+      experimental: z.boolean().optional(),
+      status: z.enum(["alpha", "beta", "deprecated"]).optional(),
+      provider: z.object({ npm: z.string().optional(), api: z.string().optional() }).optional(),
+      options: z.record(z.string(), z.any()),
+      headers: z.record(z.string(), z.string()).optional(),
+      variants: z
+        .record(
+          z.string(),
+          z
+            .object({
+              disabled: z.boolean().optional().describe("Disable this variant for the model"),
+            })
+            .catchall(z.any()),
+        )
+        .optional()
+        .describe("Variant-specific configuration"),
+    })
+    .partial()
+
+  export const Info = z
+    .object({
+      api: z.string().optional(),
+      name: z.string(),
+      env: z.array(z.string()),
+      id: z.string(),
+      npm: z.string().optional(),
+      whitelist: z.array(z.string()).optional(),
+      blacklist: z.array(z.string()).optional(),
+      options: z
+        .object({
+          apiKey: z.string().optional(),
+          baseURL: z.string().optional(),
+          enterpriseUrl: z.string().optional().describe("GitHub Enterprise URL for copilot authentication"),
+          setCacheKey: z.boolean().optional().describe("Enable promptCacheKey for this provider (default false)"),
+          timeout: z
+            .union([
+              z
+                .number()
+                .int()
+                .positive()
+                .describe(
+                  "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
+                ),
+              z.literal(false).describe("Disable timeout for this provider entirely."),
+            ])
+            .optional()
+            .describe(
+              "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
+            ),
+          chunkTimeout: z
+            .number()
+            .int()
+            .positive()
+            .optional()
+            .describe(
+              "Timeout in milliseconds between streamed SSE chunks for this provider. If no chunk arrives within this window, the request is aborted.",
+            ),
+        })
+        .catchall(z.any())
+        .optional(),
+      models: z.record(z.string(), Model).optional(),
+    })
+    .partial()
+    .strict()
+    .meta({
+      ref: "ProviderConfig",
+    })
+
+  export type Info = z.infer<typeof Info>
+}

+ 13 - 0
packages/opencode/src/config/skills.ts

@@ -0,0 +1,13 @@
+import z from "zod"
+
+export namespace ConfigSkills {
+  export const Info = z.object({
+    paths: z.array(z.string()).optional().describe("Additional paths to skill folders"),
+    urls: z
+      .array(z.string())
+      .optional()
+      .describe("URLs to fetch skills from (e.g., https://example.com/.well-known/skills/)"),
+  })
+
+  export type Info = z.infer<typeof Info>
+}