From 8921a7a26bb9255233283f333a8b4bab09677cf6 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 5 Apr 2026 20:09:45 -0400 Subject: [PATCH] core: split v2 design notes into topic docs for easier review --- packages/opencode/specs/v2/keymappings.md | 10 +++ .../specs/{v2.md => v2/message-shape.md} | 73 ++++++++++++++----- 2 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 packages/opencode/specs/v2/keymappings.md rename packages/opencode/specs/{v2.md => v2/message-shape.md} (66%) diff --git a/packages/opencode/specs/v2/keymappings.md b/packages/opencode/specs/v2/keymappings.md new file mode 100644 index 0000000000..5b23db7954 --- /dev/null +++ b/packages/opencode/specs/v2/keymappings.md @@ -0,0 +1,10 @@ +# Keybindings vs. Keymappings + +Make it `keymappings`, closer to neovim. Can be layered like `abc`. Commands don't define their binding, but have an id that a key can be mapped to like + +```ts +{ key: "ctrl+w", cmd: string | function, description } +``` + +_Why_ +Currently its keybindings that have an `id` like `message_redo` and then a command can use that or define it's own binding. While some keybindings are just used with `.match` in arbitrary key handlers and there is no info what the key is used for, except the binding id maybe. It also is unknown in which context/scope what binding is active, so a plugin like `which-key` is nearly impossible to get right. diff --git a/packages/opencode/specs/v2.md b/packages/opencode/specs/v2/message-shape.md similarity index 66% rename from packages/opencode/specs/v2.md rename to packages/opencode/specs/v2/message-shape.md index 897b98ba6a..965498f190 100644 --- a/packages/opencode/specs/v2.md +++ b/packages/opencode/specs/v2/message-shape.md @@ -1,19 +1,4 @@ -# 2.0 - -What we would change if we could - -## Keybindings vs. Keymappings - -Make it `keymappings`, closer to neovim. Can be layered like `abc`. Commands don't define their binding, but have an id that a key can be mapped to like - -```ts -{ key: "ctrl+w", cmd: string | function, description } -``` - -_Why_ -Currently its keybindings that have an `id` like `message_redo` and then a command can use that or define it's own binding. While some keybindings are just used with `.match` in arbitrary key handlers and there is no info what the key is used for, except the binding id maybe. It also is unknown in which context/scope what binding is active, so a plugin like `which-key` is nearly impossible to get right. - -## Message Shape +# Message Shape Problem: @@ -21,7 +6,7 @@ Problem: - prompt hooks often just want to append a synthetic user/assistant message - today that means faking ids, timestamps, and request metadata -### Option 1: Two Message Shapes +## Option 1: Two Message Shapes Keep `User` / `Assistant` for stored history, but clean them up. @@ -67,7 +52,7 @@ prompt.push({ Tradeoff: prompt hooks get easy lightweight messages, but there are now two message shapes. -### Option 2: Prompt Mutators +## Option 2: Prompt Mutators Keep `User` / `Assistant` as the stored history model. @@ -97,3 +82,55 @@ prompt.appendTo("last-user", [{ type: "text", text: BUILD_SWITCH }]) ``` Tradeoff: avoids a second full message type and avoids fake ids/timestamps, but moves more magic into the hook API. + +## Option 3: Separate Turn State + +Move execution settings out of `User` and into a separate turn/request object. + +```ts +type Turn = { + id: string + request: { + agent: string + model: ModelRef + variant?: string + format?: OutputFormat + system?: string + tools?: Record + } +} + +type User = { + role: "user" + turnID: string + time: { created: number } +} + +type Assistant = { + role: "assistant" + turnID: string + usage: { cost: number; tokens: Tokens } + result: { finish?: string; error?: Error; structured?: unknown; kind: "reply" | "summary" } +} +``` + +Examples: + +```ts +const turn = { + request: { + agent: "build", + model: { providerID: "openai", modelID: "gpt-5" }, + }, +} +``` + +```ts +const msg = { + role: "user", + turnID: turn.id, + parts: [{ type: "text", text: "Summarize the tool output above and continue." }], +} +``` + +Tradeoff: stored messages get much smaller and cleaner, but replay now has to join messages with turn state and prompt hooks still need a way to pick which turn they belong to.