Compare commits

..
Author SHA1 Message Date
Kit Langton 2aa6e501c8 fix(server): restore /config toJsonSafe projection
Reverts the redundant-cleanup hypothesis. The reproducer was upgraded
to exercise the actual gemini-auth bug shape — function and bigint
values under provider.options (a Schema.Any-typed declared field, not
an excess property) — and that fails /config encode without the
projection. Effect's onExcessProperty handles excess keys for free,
but Schema.Any pass-through plus encode validation doesn't, so the
toJsonSafe clone at the HTTP boundary is load-bearing for this case.
2026-05-09 15:19:04 -04:00
Kit Langton c6871e047b refactor(json-safe): add cycle detection to toJsonSafe 2026-05-09 15:11:51 -04:00
Kit Langton 6833070a57 fix(server): project /config response to JSON-safe at the HTTP boundary
Mirrors #26550 (Provider.toPublicInfo): internal runtime objects may
legitimately carry function/symbol/bigint/undefined values, but the
HTTP API contract is JSON-safe data matching the typed schema.

Plugin `config:` hooks are documented to mutate cfg in place
(registering agents/providers); that contract is preserved. The
projection happens in ConfigHttpApi.get only, so internal consumers
keep raw cfg and HTTP clients see a clean response.

Active reproducer asserts the actual product invariant — GET /config
returns a JSON-safe body even when a plugin attached a function to
cfg. Three trigger-output reproducers stay skipped: per-hook design
needed because the documented mutation contract makes a blanket
clone unsafe and `tool.parameters` is a live Effect Schema with
function-valued own keys that recursive in-place scrub would corrupt.
2026-05-09 15:08:55 -04:00
126 changed files with 591 additions and 1222 deletions
+17 -17
View File
@@ -29,7 +29,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
"version": "1.14.44",
"version": "1.14.43",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/core": "workspace:*",
@@ -85,7 +85,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.14.44",
"version": "1.14.43",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -120,7 +120,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.14.44",
"version": "1.14.43",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -147,7 +147,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.14.44",
"version": "1.14.43",
"dependencies": {
"@ai-sdk/anthropic": "3.0.64",
"@ai-sdk/openai": "3.0.48",
@@ -171,7 +171,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.14.44",
"version": "1.14.43",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -195,7 +195,7 @@
},
"packages/core": {
"name": "@opencode-ai/core",
"version": "1.14.44",
"version": "1.14.43",
"bin": {
"opencode": "./bin/opencode",
},
@@ -229,7 +229,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.14.44",
"version": "1.14.43",
"dependencies": {
"drizzle-orm": "catalog:",
"effect": "catalog:",
@@ -283,7 +283,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.14.44",
"version": "1.14.43",
"dependencies": {
"@opencode-ai/core": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -313,7 +313,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.14.44",
"version": "1.14.43",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -329,7 +329,7 @@
},
"packages/http-recorder": {
"name": "@opencode-ai/http-recorder",
"version": "1.14.44",
"version": "1.14.43",
"dependencies": {
"@effect/platform-node": "catalog:",
"effect": "catalog:",
@@ -342,7 +342,7 @@
},
"packages/llm": {
"name": "@opencode-ai/llm",
"version": "1.14.44",
"version": "1.14.43",
"dependencies": {
"@smithy/eventstream-codec": "4.2.14",
"@smithy/util-utf8": "4.2.2",
@@ -360,7 +360,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.14.44",
"version": "1.14.43",
"bin": {
"opencode": "./bin/opencode",
},
@@ -495,7 +495,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.14.44",
"version": "1.14.43",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"effect": "catalog:",
@@ -533,7 +533,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.14.44",
"version": "1.14.43",
"dependencies": {
"cross-spawn": "catalog:",
},
@@ -548,7 +548,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.14.44",
"version": "1.14.43",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -583,7 +583,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.14.44",
"version": "1.14.43",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/core": "workspace:*",
@@ -632,7 +632,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.14.44",
"version": "1.14.43",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
"version": "1.14.44",
"version": "1.14.43",
"description": "",
"type": "module",
"exports": {
@@ -28,12 +28,6 @@ import { createOpenSessionFileTab, createSessionTabs, getTabReorderIndex, type S
import { setSessionHandoff } from "@/pages/session/handoff"
import { useSessionLayout } from "@/pages/session/session-layout"
type RenderDiff = (SnapshotFileDiff & { file: string }) | VcsFileDiff
function renderDiff(value: SnapshotFileDiff | VcsFileDiff): value is RenderDiff {
return typeof value.file === "string"
}
export function SessionSidePanel(props: {
canReview: () => boolean
diffs: () => (SnapshotFileDiff | VcsFileDiff)[]
@@ -76,8 +70,7 @@ export function SessionSidePanel(props: {
})
const treeWidth = createMemo(() => (fileOpen() ? `${layout.fileTree.width()}px` : "0px"))
const diffs = createMemo(() => props.diffs().filter(renderDiff))
const diffFiles = createMemo(() => diffs().map((d) => d.file))
const diffFiles = createMemo(() => props.diffs().map((d) => d.file))
const kinds = createMemo(() => {
const merge = (a: "add" | "del" | "mix" | undefined, b: "add" | "del" | "mix") => {
if (!a) return b
@@ -88,7 +81,7 @@ export function SessionSidePanel(props: {
const normalize = (p: string) => p.replaceAll("\\\\", "/").replace(/\/+$/, "")
const out = new Map<string, "add" | "del" | "mix">()
for (const diff of diffs()) {
for (const diff of props.diffs()) {
const file = normalize(diff.file)
const kind = diff.status === "added" ? "add" : diff.status === "deleted" ? "del" : "mix"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
"version": "1.14.44",
"version": "1.14.43",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.14.44",
"version": "1.14.43",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.14.44",
"version": "1.14.43",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.14.44",
"version": "1.14.43",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.14.44",
"version": "1.14.43",
"name": "@opencode-ai/core",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
"version": "1.14.44",
"version": "1.14.43",
"type": "module",
"license": "MIT",
"homepage": "https://opencode.ai",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
"version": "1.14.44",
"version": "1.14.43",
"private": true,
"type": "module",
"license": "MIT",
+6 -6
View File
@@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The open source coding agent."
version = "1.14.44"
version = "1.14.43"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/anomalyco/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.44/opencode-darwin-arm64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.43/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.44/opencode-darwin-x64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.43/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.44/opencode-linux-arm64.tar.gz"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.43/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.44/opencode-linux-x64.tar.gz"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.43/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.44/opencode-windows-x64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.43/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
"version": "1.14.44",
"version": "1.14.43",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.14.44",
"version": "1.14.43",
"name": "@opencode-ai/http-recorder",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.14.44",
"version": "1.14.43",
"name": "@opencode-ai/llm",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.14.44",
"version": "1.14.43",
"name": "opencode",
"type": "module",
"license": "MIT",
+2 -2
View File
@@ -59,10 +59,10 @@ Rules:
## Schema → Zod interop
When a service uses Effect Schema internally but needs Zod schemas for the HTTP layer, derive Zod from Schema using the `zod()` helper from `@opencode-ai/core/effect-zod`:
When a service uses Effect Schema internally but needs Zod schemas for the HTTP layer, derive Zod from Schema using the `zod()` helper from `@/util/effect-zod`:
```ts
import { zod } from "@opencode-ai/core/effect-zod"
import { zod } from "@/util/effect-zod"
export const ZodInfo = zod(Info) // derives z.ZodType from Schema.Union
```
+3 -3
View File
@@ -8,7 +8,7 @@ Zod-first definitions to Effect Schema with Zod compatibility shims.
Use Effect Schema as the source of truth for domain models, IDs, inputs,
outputs, and typed errors. Keep Zod available at existing HTTP, tool, and
compatibility boundaries by exposing a `.zod` static derived from the Effect
schema via `@opencode-ai/core/effect-zod`.
schema via `@/util/effect-zod`.
The long-term driver is `specs/effect/http-api.md` — once the HTTP server
moves to `@effect/platform`, every Schema-first DTO can flow through
@@ -97,7 +97,7 @@ creating a parallel schema source of truth.
## Escape hatches
The walker in `@opencode-ai/core/effect-zod` exposes two explicit escape hatches for
The walker in `@/util/effect-zod` exposes two explicit escape hatches for
cases the pure-Schema path cannot express. Each one stays in the codebase
only as long as its upstream or local dependency requires it — inline
comments document when each can be deleted.
@@ -389,7 +389,7 @@ piecewise.
## Notes
- Use `@opencode-ai/core/effect-zod` for all Schema → Zod conversion.
- Use `@/util/effect-zod` for all Schema → Zod conversion.
- Prefer one canonical schema definition. Avoid maintaining parallel Zod and
Effect definitions for the same domain type.
- Keep the migration incremental. Converting the domain model first is more
@@ -1,67 +0,0 @@
# TUI Command Shim Removal
Problem:
- v1 keeps a deprecated `api.command` TUI plugin shim so older plugins do not fail during initialization
- v2 should expose only the keymap command API
- tests and fixtures should not encode legacy command behavior as expected behavior
## Remove Public Types
In `packages/plugin/src/tui.ts`, remove:
- `TuiCommand`
- `TuiCommandApi`
- `TuiPluginApi.command`
Keep `api.keymap` as the only TUI command registration and execution surface.
## Remove Runtime Shim
Delete `packages/opencode/src/cli/cmd/tui/plugin/command-shim.ts`.
In `packages/opencode/src/cli/cmd/tui/plugin/api.tsx`, remove:
- the `createCommandShim` import
- the `command: createCommandShim(...)` field from `createTuiApi(...)`
In `packages/opencode/src/cli/cmd/tui/plugin/runtime.ts`, remove:
- the `createCommandShim` import
- the `command: createCommandShim(...)` field from `pluginApi(...)`
## Migration Target
Plugin authors should replace old calls with keymap calls:
```ts
api.keymap.registerLayer({
commands: [
{
name: "plugin.command",
title: "Plugin Command",
namespace: "palette",
slashName: "plugin",
run() {
api.ui.dialog.clear()
},
},
],
bindings: [{ key: "ctrl+shift+p", cmd: "plugin.command" }],
})
```
Direct replacements:
- `api.command.register(cb)` -> `api.keymap.registerLayer({ commands, bindings })`
- `api.command.trigger(name)` -> `api.keymap.dispatchCommand(name)`
- `api.command.show()` -> `api.keymap.dispatchCommand("command.palette.show")`
- `onSelect(dialog)` -> use `api.ui.dialog` from the plugin API closure
## Verification
After removal, run from package directories:
- `bun typecheck` in `packages/plugin`
- `bun typecheck` in `packages/opencode`
- TUI plugin loader tests in `packages/opencode` if runtime plugin API wiring changed
+2 -2
View File
@@ -24,8 +24,8 @@ import { Effect, Context, Layer, Schema } from "effect"
import { InstanceState } from "@/effect/instance-state"
import * as Option from "effect/Option"
import * as OtelTracer from "@effect/opentelemetry/Tracer"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics, type DeepMutable } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics, type DeepMutable } from "@/util/schema"
type ReferenceEntry = NonNullable<Config.Info["reference"]>[string]
type ResolvedReference = { kind: "git"; repository: string; branch?: string } | { kind: "local"; path: string }
+2 -2
View File
@@ -1,7 +1,7 @@
import path from "path"
import { Effect, Layer, Record, Result, Schema, Context } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { NonNegativeInt } from "@/util/schema"
import { Global } from "@opencode-ai/core/global"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
+3 -3
View File
@@ -23,11 +23,11 @@ function span(id: string, value: { value: string; start: number; end: number })
}
}
function diff(kind: string, diffs: { file?: string; patch?: string }[] | undefined) {
function diff(kind: string, diffs: { file: string; patch: string }[] | undefined) {
return diffs?.map((item, i) => ({
...item,
file: item.file === undefined ? undefined : redact(`${kind}-file`, String(i), item.file),
patch: item.patch === undefined ? undefined : redact(`${kind}-patch`, String(i), item.patch),
file: redact(`${kind}-file`, String(i), item.file),
patch: redact(`${kind}-patch`, String(i), item.patch),
}))
}
@@ -15,17 +15,13 @@ export function useEvent() {
// Special hack for truly global events
if (event.directory === "global") {
handler(event.payload)
return
}
// Workspace-scoped events match on workspace identity. Events without a
// workspace label fall through to the directory check — a session with
// no workspaceID can be live in the same directory as the TUI even when
// the TUI itself is attached to a workspace (#26671).
if (event.workspace !== undefined) {
if (project.workspace.current()) {
if (event.workspace === project.workspace.current()) {
handler(event.payload)
}
return
}
@@ -526,12 +526,10 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
if (match.found) draft.session[match.index] = session.data!
if (!match.found) draft.session.splice(match.index, 0, session.data!)
draft.todo[sessionID] = todo.data ?? []
const infos: (typeof draft.message)[string] = []
for (const message of messages.data ?? []) {
infos.push(message.info)
draft.message[sessionID] = messages.data!.map((x) => x.info)
for (const message of messages.data!) {
draft.part[message.info.id] = message.parts
}
draft.message[sessionID] = infos
draft.session_diff[sessionID] = diff.data ?? []
}),
)
+1 -1
View File
@@ -1,6 +1,6 @@
import { BusEvent } from "@/bus/bus-event"
import { SessionID } from "@/session/schema"
import { PositiveInt } from "@opencode-ai/core/schema"
import { PositiveInt } from "@/util/schema"
import { Effect, Schema } from "effect"
const DEFAULT_TOAST_DURATION = 5000
@@ -17,7 +17,6 @@ import { Slot as HostSlot } from "./slots"
import type { useToast } from "../ui/toast"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import * as Keymap from "../keymap"
import { createCommandShim } from "./command-shim"
type RouteEntry = {
key: symbol
@@ -148,9 +147,7 @@ function stateApi(sync: ReturnType<typeof useSync>): TuiPluginApi["state"] {
return sync.data.session.length
},
diff(sessionID) {
return (sync.data.session_diff[sessionID] ?? []).flatMap((item) =>
item.file === undefined ? [] : [{ ...item, file: item.file }],
)
return sync.data.session_diff[sessionID] ?? []
},
todo(sessionID) {
return sync.data.todo[sessionID] ?? []
@@ -203,8 +200,6 @@ export function createTuiApi(input: Input): TuiPluginApi {
}
return {
app: appApi(),
// Keep deprecated `api.command` working for v1 plugins; remove in v2.
command: createCommandShim(input.keymap, input.dialog, input.tuiConfig.keybinds),
keys: {
formatSequence(parts) {
return Keymap.formatKeySequence(parts, input.tuiConfig)
@@ -1,109 +0,0 @@
// Legacy `api.command` bridge for v1 plugins; remove in v2.
import type { TuiCommand, TuiPluginApi } from "@opencode-ai/plugin/tui"
import { TuiKeybind } from "../config/keybind"
import type { DialogContext } from "../ui/dialog"
const COMMAND_PALETTE_SHOW = "command.palette.show"
const warned = new Set<string>()
type Warn = (api: string, replacement: string) => void
type LegacyDialog = TuiPluginApi["ui"]["dialog"]
type CommandShimDialog = DialogContext | LegacyDialog
type LegacyKeybinds = TuiPluginApi["tuiConfig"]["keybinds"]
function warnCommandShim(api: string, replacement: string) {
// Warn v1 plugins about deprecated `api.command`; remove this shim path in v2.
console.warn("[tui.plugin] deprecated TUI plugin API", { api, replacement })
}
function createCommandShimDialog(dialog: CommandShimDialog): LegacyDialog {
if (!("stack" in dialog)) return dialog
return {
replace(render, onClose) {
dialog.replace(render, onClose)
},
clear() {
dialog.clear()
},
setSize(size) {
dialog.setSize(size)
},
get size() {
return dialog.size
},
get depth() {
return dialog.stack.length
},
get open() {
return dialog.stack.length > 0
},
}
}
function warnOnce(api: string, replacement: string, warn: Warn) {
if (warned.has(api)) return
warned.add(api)
warn(api, replacement)
}
function toCommand(item: TuiCommand, dialog: LegacyDialog) {
return {
namespace: "palette",
name: item.value,
title: item.title,
desc: item.description,
category: item.category,
suggested: item.suggested,
hidden: item.hidden,
enabled: item.enabled,
slashName: item.slash?.name,
slashAliases: item.slash?.aliases,
run() {
return item.onSelect?.(dialog)
},
}
}
function toBindings(commands: TuiCommand[], keybinds: LegacyKeybinds) {
return commands.flatMap((item) =>
item.keybind
? keybinds.has(TuiKeybind.CommandMap[item.keybind as keyof typeof TuiKeybind.CommandMap] ?? item.keybind)
? keybinds
.get(TuiKeybind.CommandMap[item.keybind as keyof typeof TuiKeybind.CommandMap] ?? item.keybind)
.map((binding) => ({ ...binding, cmd: item.value, desc: binding.desc ?? item.title }))
: [
{
key: item.keybind,
cmd: item.value,
desc: item.title,
},
]
: [],
)
}
export function createCommandShim(
keymap: TuiPluginApi["keymap"],
dialog: CommandShimDialog,
keybinds: LegacyKeybinds,
): TuiPluginApi["command"] {
const shimDialog = createCommandShimDialog(dialog)
return {
register(cb) {
warnOnce("api.command.register", "api.keymap.registerLayer({ commands, bindings })", warnCommandShim)
const commands = cb()
return keymap.registerLayer({
commands: commands.map((item) => toCommand(item, shimDialog)),
bindings: toBindings(commands, keybinds),
})
},
trigger(value) {
warnOnce("api.command.trigger", "api.keymap.dispatchCommand(name)", warnCommandShim)
keymap.dispatchCommand(value)
},
show() {
warnOnce("api.command.show", `api.keymap.dispatchCommand("${COMMAND_PALETTE_SHOW}")`, warnCommandShim)
keymap.dispatchCommand(COMMAND_PALETTE_SHOW)
},
}
}
@@ -39,7 +39,6 @@ import { INTERNAL_TUI_PLUGINS, type InternalTuiPlugin } from "./internal"
import { setupSlots, Slot as View } from "./slots"
import type { HostPluginApi, HostSlots } from "./slots"
import { ConfigPlugin } from "@/config/plugin"
import { createCommandShim } from "./command-shim"
ensureRuntimePluginSupport({ additional: keymapRuntimeModules })
@@ -577,8 +576,6 @@ function pluginApi(runtime: RuntimeState, plugin: PluginEntry, scope: PluginScop
return {
app: api.app,
// Keep deprecated `api.command` working for v1 plugins; remove in v2.
command: createCommandShim(keymap, api.ui.dialog, api.tuiConfig.keybinds),
keys: api.keys,
keymap,
route,
+2 -2
View File
@@ -5,8 +5,8 @@ import type { InstanceContext } from "@/project/instance"
import { SessionID, MessageID } from "@/session/schema"
import { Effect, Layer, Context, Schema } from "effect"
import z from "zod"
import { zod, ZodOverride } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod, ZodOverride } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { Config } from "@/config/config"
import { MCP } from "../mcp"
import { Skill } from "../skill"
+2 -2
View File
@@ -2,8 +2,8 @@ export * as ConfigAgent from "./agent"
import { Exit, Schema, SchemaGetter } from "effect"
import { Bus } from "@/bus"
import { zod } from "@opencode-ai/core/effect-zod"
import { PositiveInt, withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { PositiveInt, withStatics } from "@/util/schema"
import * as Log from "@opencode-ai/core/util/log"
import { NamedError } from "@opencode-ai/core/util/error"
import { Glob } from "@opencode-ai/core/util/glob"
+2 -2
View File
@@ -5,8 +5,8 @@ import { Schema } from "effect"
import { NamedError } from "@opencode-ai/core/util/error"
import { Glob } from "@opencode-ai/core/util/glob"
import { Bus } from "@/bus"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { configEntryNameFromPath } from "./entry-name"
import { InvalidError } from "./error"
import * as ConfigMarkdown from "./markdown"
+3 -3
View File
@@ -22,8 +22,8 @@ import { InstanceState } from "@/effect/instance-state"
import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "effect"
import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
import { containsPath } from "../project/instance-context"
import { zod } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt, PositiveInt, withStatics, type DeepMutable } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { NonNegativeInt, PositiveInt, withStatics, type DeepMutable } from "@/util/schema"
import { ConfigAgent } from "./agent"
import { ConfigCommand } from "./command"
import { ConfigFormatter } from "./formatter"
@@ -307,7 +307,7 @@ export const Info = Schema.Struct({
})),
)
// Uses the shared `DeepMutable` from `@opencode-ai/core/schema`. See the definition
// Uses the shared `DeepMutable` from `@/util/schema`. See the definition
// there for why the local variant is needed over `Types.DeepMutable` from
// effect-smol (the upstream version collapses `unknown` to `{}`).
export type Info = DeepMutable<Schema.Schema.Type<typeof Info>> & {
@@ -1,6 +1,6 @@
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { NonNegativeInt } from "@/util/schema"
export class ConsoleState extends Schema.Class<ConsoleState>("ConsoleState")({
consoleManagedProviders: Schema.mutable(Schema.Array(Schema.String)),
+2 -2
View File
@@ -1,8 +1,8 @@
export * as ConfigFormatter from "./formatter"
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
export const Entry = Schema.Struct({
disabled: Schema.optional(Schema.Boolean),
+2 -2
View File
@@ -1,6 +1,6 @@
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
export const Layout = Schema.Literals(["auto", "stretch"])
.annotate({ identifier: "LayoutConfig" })
+2 -2
View File
@@ -1,8 +1,8 @@
export * as ConfigLSP from "./lsp"
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import * as LSPServer from "../lsp/server"
export const Disabled = Schema.Struct({
+2 -2
View File
@@ -1,6 +1,6 @@
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { PositiveInt, withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { PositiveInt, withStatics } from "@/util/schema"
export const Local = Schema.Struct({
type: Schema.Literal("local").annotate({ description: "Type of MCP server connection" }),
+2 -2
View File
@@ -1,7 +1,7 @@
import { Schema } from "effect"
import z from "zod"
import { zod, ZodOverride } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod, ZodOverride } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
// The original Zod schema carried an external $ref pointing at the models.dev
// JSON schema. That external reference is not a named SDK component — it is a
+1 -1
View File
@@ -3,7 +3,7 @@ export * as ConfigParse from "./parse"
import { type ParseError as JsoncParseError, parse as parseJsoncImpl, printParseErrorCode } from "jsonc-parser"
import { Cause, Exit, Schema as EffectSchema, SchemaIssue } from "effect"
import z from "zod"
import type { DeepMutable } from "@opencode-ai/core/schema"
import type { DeepMutable } from "@/util/schema"
import { InvalidError, JsonError } from "./error"
type ZodSchema<T> = z.ZodType<T>
+2 -2
View File
@@ -1,7 +1,7 @@
export * as ConfigPermission from "./permission"
import { Schema, SchemaGetter } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
export const Action = Schema.Literals(["ask", "allow", "deny"])
.annotate({ identifier: "PermissionActionConfig" })
+2 -2
View File
@@ -2,8 +2,8 @@ import { Glob } from "@opencode-ai/core/util/glob"
import { Schema } from "effect"
import { pathToFileURL } from "url"
import { isPathPluginSpec, parsePluginSpecifier, resolvePathPluginTarget } from "@/plugin/shared"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import path from "path"
export const Options = Schema.Record(Schema.String, Schema.Unknown).pipe(withStatics((s) => ({ zod: zod(s) })))
+2 -2
View File
@@ -1,6 +1,6 @@
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { PositiveInt, withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { PositiveInt, withStatics } from "@/util/schema"
export const Model = Schema.Struct({
id: Schema.optional(Schema.String),
+2 -2
View File
@@ -1,8 +1,8 @@
export * as ConfigReference from "./reference"
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
const Git = Schema.Struct({
repository: Schema.String.annotate({
+2 -2
View File
@@ -1,6 +1,6 @@
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { PositiveInt, withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { PositiveInt, withStatics } from "@/util/schema"
export const Server = Schema.Struct({
port: Schema.optional(PositiveInt).annotate({
+2 -2
View File
@@ -1,6 +1,6 @@
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
export const Info = Schema.Struct({
paths: Schema.optional(Schema.Array(Schema.String)).annotate({
@@ -1,8 +1,8 @@
import { Schema } from "effect"
import { Identifier } from "@/id/id"
import { zod, ZodOverride } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod, ZodOverride } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
const workspaceIdSchema = Schema.String.annotate({ [ZodOverride]: Identifier.schema("workspace") }).pipe(
Schema.brand("WorkspaceID"),
+1 -1
View File
@@ -1,7 +1,7 @@
import { Schema, Struct } from "effect"
import { ProjectID } from "@/project/schema"
import { WorkspaceID } from "./schema"
import type { DeepMutable } from "@opencode-ai/core/schema"
import type { DeepMutable } from "@/util/schema"
export const WorkspaceInfo = Schema.Struct({
id: WorkspaceID,
+2 -2
View File
@@ -14,8 +14,8 @@ import { containsPath } from "../project/instance-context"
import * as Log from "@opencode-ai/core/util/log"
import { Protected } from "./protected"
import { Ripgrep } from "./ripgrep"
import { zod } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt, type DeepMutable, withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { NonNegativeInt, type DeepMutable, withStatics } from "@/util/schema"
export const Info = Schema.Struct({
path: Schema.String,
+2 -2
View File
@@ -11,8 +11,8 @@ import { Global } from "@opencode-ai/core/global"
import * as Log from "@opencode-ai/core/util/log"
import { sanitizedProcessEnv } from "@opencode-ai/core/util/opencode-process"
import { which } from "@/util/which"
import { zod } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { NonNegativeInt, withStatics } from "@/util/schema"
const log = Log.create({ service: "ripgrep" })
const VERSION = "15.1.0"
+2 -2
View File
@@ -7,8 +7,8 @@ import { mergeDeep } from "remeda"
import { Config } from "@/config/config"
import * as Log from "@opencode-ai/core/util/log"
import * as Formatter from "./formatter"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
const log = Log.create({ service: "format" })
+2 -2
View File
@@ -13,8 +13,8 @@ import { spawn as lspspawn } from "./launch"
import { Effect, Layer, Context, Schema } from "effect"
import { InstanceState } from "@/effect/instance-state"
import { containsPath } from "@/project/instance-context"
import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema"
import { zod, ZodOverride } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt, withStatics } from "@/util/schema"
import { zod, ZodOverride } from "@/util/effect-zod"
const log = Log.create({ service: "lsp" })
+2 -2
View File
@@ -30,8 +30,8 @@ import { EffectBridge } from "@/effect/bridge"
import { InstanceState } from "@/effect/instance-state"
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { zod as effectZod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod as effectZod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
const log = Log.create({ service: "mcp" })
const DEFAULT_TIMEOUT = 30_000
+2 -2
View File
@@ -7,9 +7,9 @@ import { MessageID, SessionID } from "@/session/schema"
import { PermissionTable } from "@/session/session.sql"
import { Database } from "@/storage/db"
import { eq } from "drizzle-orm"
import { zod } from "@opencode-ai/core/effect-zod"
import { zod } from "@/util/effect-zod"
import * as Log from "@opencode-ai/core/util/log"
import { withStatics } from "@opencode-ai/core/schema"
import { withStatics } from "@/util/schema"
import { Wildcard } from "@/util/wildcard"
import { Deferred, Effect, Layer, Schema, Context } from "effect"
import os from "os"
+2 -2
View File
@@ -1,8 +1,8 @@
import { Schema } from "effect"
import { Identifier } from "@/id/id"
import { zod, ZodOverride } from "@opencode-ai/core/effect-zod"
import { Newtype } from "@opencode-ai/core/schema"
import { zod, ZodOverride } from "@/util/effect-zod"
import { Newtype } from "@/util/schema"
export class PermissionID extends Newtype<PermissionID>()(
"PermissionID",
+2 -2
View File
@@ -18,8 +18,8 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
import { NodePath } from "@effect/platform-node"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { zod } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt, optionalOmitUndefined, withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { NonNegativeInt, optionalOmitUndefined, withStatics } from "@/util/schema"
import { serviceUse } from "@/effect/service-use"
const log = Log.create({ service: "project" })
+2 -2
View File
@@ -1,7 +1,7 @@
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
const projectIdSchema = Schema.String.pipe(Schema.brand("ProjectID"))
+3 -6
View File
@@ -6,8 +6,8 @@ import { InstanceState } from "@/effect/instance-state"
import { FileWatcher } from "@/file/watcher"
import { Git } from "@/git"
import * as Log from "@opencode-ai/core/util/log"
import { zod, zodObject } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema"
import { zod, zodObject } from "@/util/effect-zod"
import { NonNegativeInt, withStatics } from "@/util/schema"
const log = Log.create({ service: "vcs" })
const PATCH_CONTEXT_LINES = 2_147_483_647
@@ -230,10 +230,7 @@ export type Info = Schema.Schema.Type<typeof Info>
export const FileDiff = Schema.Struct({
file: Schema.String,
// Mirrors Snapshot.FileDiff (see #26574). The current producer always
// populates patch, but loosening matches the sibling schema so a
// future code path that omits it can't crash /instance/vcs/diff.
patch: Schema.optional(Schema.String),
patch: Schema.String,
additions: NonNegativeInt,
deletions: NonNegativeInt,
status: Schema.optional(Schema.Literals(["added", "deleted", "modified"])),
+2 -2
View File
@@ -1,9 +1,9 @@
import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin"
import { Auth } from "@/auth"
import { InstanceState } from "@/effect/instance-state"
import { zod } from "@opencode-ai/core/effect-zod"
import { zod } from "@/util/effect-zod"
import { namedSchemaError } from "@/util/named-schema-error"
import { optionalOmitUndefined, withStatics } from "@opencode-ai/core/schema"
import { optionalOmitUndefined, withStatics } from "@/util/schema"
import { Plugin } from "../plugin"
import { ProviderID } from "./schema"
import { Array as Arr, Effect, Layer, Record, Result, Context, Schema } from "effect"
+3 -3
View File
@@ -13,7 +13,7 @@ import { Auth } from "../auth"
import { Env } from "../env"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { Flag } from "@opencode-ai/core/flag/flag"
import { zod } from "@opencode-ai/core/effect-zod"
import { zod } from "@/util/effect-zod"
import { namedSchemaError } from "@/util/named-schema-error"
import { iife } from "@/util/iife"
import { Global } from "@opencode-ai/core/global"
@@ -24,7 +24,7 @@ import { EffectBridge } from "@/effect/bridge"
import { InstanceState } from "@/effect/instance-state"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { isRecord } from "@/util/record"
import { optionalOmitUndefined, withStatics } from "@opencode-ai/core/schema"
import { optionalOmitUndefined, withStatics } from "@/util/schema"
import * as ProviderTransform from "./transform"
import { ModelID, ProviderID } from "./schema"
@@ -1162,7 +1162,7 @@ const layer: Layer.Layer<
const pluginAuth = yield* auth.get(providerID).pipe(Effect.orDie)
provider.models = yield* Effect.promise(async () => {
const next = await models(toPublicInfo(provider), { auth: pluginAuth })
const next = await models(provider, { auth: pluginAuth })
return Object.fromEntries(
Object.entries(next).map(([id, model]) => [
id,
+2 -2
View File
@@ -1,7 +1,7 @@
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
const providerIdSchema = Schema.String.pipe(Schema.brand("ProviderID"))
+2 -2
View File
@@ -10,8 +10,8 @@ import type { Proc } from "#pty"
import * as Log from "@opencode-ai/core/util/log"
import { PtyID } from "./schema"
import { Effect, Layer, Context, Schema, Types } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt, PositiveInt, withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { NonNegativeInt, PositiveInt, withStatics } from "@/util/schema"
const log = Log.create({ service: "pty" })
+2 -2
View File
@@ -1,8 +1,8 @@
import { Schema } from "effect"
import { Identifier } from "@/id/id"
import { zod, ZodOverride } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod, ZodOverride } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
const ptyIdSchema = Schema.String.annotate({ [ZodOverride]: Identifier.schema("pty") }).pipe(Schema.brand("PtyID"))
+1 -1
View File
@@ -3,7 +3,7 @@ export * as PtyTicket from "./ticket"
import { WorkspaceID } from "@/control-plane/schema"
import { InstanceRef, WorkspaceRef } from "@/effect/instance-ref"
import { PtyID } from "@/pty/schema"
import { PositiveInt } from "@opencode-ai/core/schema"
import { PositiveInt } from "@/util/schema"
import { Cache, Context, Duration, Effect, Layer, Schema } from "effect"
const DEFAULT_TTL = Duration.seconds(60)
+2 -2
View File
@@ -3,9 +3,9 @@ import { Bus } from "@/bus"
import { BusEvent } from "@/bus/bus-event"
import { InstanceState } from "@/effect/instance-state"
import { SessionID, MessageID } from "@/session/schema"
import { zod } from "@opencode-ai/core/effect-zod"
import { zod } from "@/util/effect-zod"
import * as Log from "@opencode-ai/core/util/log"
import { withStatics } from "@opencode-ai/core/schema"
import { withStatics } from "@/util/schema"
import { QuestionID } from "./schema"
const log = Log.create({ service: "question" })
+2 -2
View File
@@ -1,8 +1,8 @@
import { Schema } from "effect"
import { Identifier } from "@/id/id"
import { zod, ZodOverride } from "@opencode-ai/core/effect-zod"
import { Newtype } from "@opencode-ai/core/schema"
import { zod, ZodOverride } from "@/util/effect-zod"
import { Newtype } from "@/util/schema"
export class QuestionID extends Newtype<QuestionID>()(
"QuestionID",
@@ -3,12 +3,12 @@ import { MCP } from "@/mcp"
import { ProviderID, ModelID } from "@/provider/schema"
import { Session } from "@/session/session"
import { Worktree } from "@/worktree"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { NonNegativeInt } from "@/util/schema"
import { Schema, SchemaGetter } from "effect"
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
import { Authorization } from "../middleware/authorization"
import { InstanceContextMiddleware } from "../middleware/instance-context"
import { WorkspaceRoutingMiddleware, WorkspaceRoutingQueryFields } from "../middleware/workspace-routing"
import { WorkspaceRoutingMiddleware } from "../middleware/workspace-routing"
import { described } from "./metadata"
const ConsoleStateResponse = Schema.Struct({
@@ -43,7 +43,6 @@ const ToolListItem = Schema.Struct({
}).annotate({ identifier: "ToolListItem" })
const ToolList = Schema.Array(ToolListItem).annotate({ identifier: "ToolList" })
export const ToolListQuery = Schema.Struct({
...WorkspaceRoutingQueryFields,
provider: ProviderID,
model: ModelID,
})
@@ -56,7 +55,7 @@ const QueryBoolean = Schema.Literals(["true", "false"]).pipe(
)
const WorktreeList = Schema.Array(Schema.String)
export const SessionListQuery = Schema.Struct({
...WorkspaceRoutingQueryFields,
directory: Schema.optional(Schema.String),
roots: Schema.optional(QueryBoolean),
start: Schema.optional(Schema.NumberFromString),
cursor: Schema.optional(Schema.NumberFromString),
@@ -5,21 +5,18 @@ import { Schema } from "effect"
import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
import { Authorization } from "../middleware/authorization"
import { InstanceContextMiddleware } from "../middleware/instance-context"
import { WorkspaceRoutingMiddleware, WorkspaceRoutingQueryFields } from "../middleware/workspace-routing"
import { WorkspaceRoutingMiddleware } from "../middleware/workspace-routing"
import { described } from "./metadata"
export const FileQuery = Schema.Struct({
...WorkspaceRoutingQueryFields,
path: Schema.String,
})
export const FindTextQuery = Schema.Struct({
...WorkspaceRoutingQueryFields,
pattern: Schema.String,
})
export const FindFileQuery = Schema.Struct({
...WorkspaceRoutingQueryFields,
query: Schema.String,
dirs: Schema.optional(Schema.Literals(["true", "false"])),
type: Schema.optional(Schema.Literals(["file", "directory"])),
@@ -29,7 +26,6 @@ export const FindFileQuery = Schema.Struct({
})
export const FindSymbolQuery = Schema.Struct({
...WorkspaceRoutingQueryFields,
query: Schema.String,
})
@@ -8,7 +8,7 @@ import { Schema } from "effect"
import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
import { Authorization } from "../middleware/authorization"
import { InstanceContextMiddleware } from "../middleware/instance-context"
import { WorkspaceRoutingMiddleware, WorkspaceRoutingQueryFields } from "../middleware/workspace-routing"
import { WorkspaceRoutingMiddleware } from "../middleware/workspace-routing"
import { described } from "./metadata"
const PathInfo = Schema.Struct({
@@ -20,7 +20,6 @@ const PathInfo = Schema.Struct({
}).annotate({ identifier: "Path" })
export const VcsDiffQuery = Schema.Struct({
...WorkspaceRoutingQueryFields,
mode: Vcs.Mode,
})
@@ -5,16 +5,13 @@ import { Schema } from "effect"
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
import { Authorization } from "../middleware/authorization"
import { InstanceContextMiddleware } from "../middleware/instance-context"
import { WorkspaceRoutingMiddleware, WorkspaceRoutingQueryFields } from "../middleware/workspace-routing"
import { WorkspaceRoutingMiddleware } from "../middleware/workspace-routing"
import { ApiNotFoundError } from "../errors"
import { described } from "./metadata"
const root = "/pty"
export const Params = Schema.Struct({ ptyID: PtyID })
export const CursorQuery = Schema.Struct({
...WorkspaceRoutingQueryFields,
cursor: Schema.optional(Schema.String),
})
export const CursorQuery = Schema.Struct({ cursor: Schema.optional(Schema.String) })
export const ShellItem = Schema.Struct({
path: Schema.String,
name: Schema.String,
@@ -14,7 +14,7 @@ import { Schema, SchemaGetter, Struct } from "effect"
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
import { Authorization } from "../middleware/authorization"
import { InstanceContextMiddleware } from "../middleware/instance-context"
import { WorkspaceRoutingMiddleware, WorkspaceRoutingQueryFields } from "../middleware/workspace-routing"
import { WorkspaceRoutingMiddleware } from "../middleware/workspace-routing"
import { ApiNotFoundError } from "../errors"
import { described } from "./metadata"
@@ -26,7 +26,7 @@ const QueryBoolean = Schema.Literals(["true", "false"]).pipe(
}),
)
export const ListQuery = Schema.Struct({
...WorkspaceRoutingQueryFields,
directory: Schema.optional(Schema.String),
scope: Schema.optional(Schema.Literals(["project"])),
path: Schema.optional(Schema.String),
roots: Schema.optional(QueryBoolean),
@@ -34,12 +34,8 @@ export const ListQuery = Schema.Struct({
search: Schema.optional(Schema.String),
limit: Schema.optional(Schema.NumberFromString),
})
export const DiffQuery = Schema.Struct({
...WorkspaceRoutingQueryFields,
...Struct.omit(SessionSummary.DiffInput.fields, ["sessionID"]),
})
export const DiffQuery = Schema.Struct(Struct.omit(SessionSummary.DiffInput.fields, ["sessionID"]))
export const MessagesQuery = Schema.Struct({
...WorkspaceRoutingQueryFields,
limit: Schema.optional(Schema.NumberFromString.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0))),
before: Schema.optional(Schema.String),
})
@@ -1,4 +1,4 @@
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { NonNegativeInt } from "@/util/schema"
import { SessionID } from "@/session/schema"
import { Schema } from "effect"
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
@@ -1,6 +1,7 @@
import { Config } from "@/config/config"
import { Provider } from "@/provider/provider"
import * as InstanceState from "@/effect/instance-state"
import { toJsonSafe } from "@/util/json-safe"
import { Effect } from "effect"
import { HttpApiBuilder } from "effect/unstable/httpapi"
import { InstanceHttpApi } from "../api"
@@ -12,7 +13,12 @@ export const configHandlers = HttpApiBuilder.group(InstanceHttpApi, "config", (h
const configSvc = yield* Config.Service
const get = Effect.fn("ConfigHttpApi.get")(function* () {
return yield* configSvc.get()
// Plugin `config` hooks may attach runtime-only values (functions,
// bigints, etc.) under schema-allowed fields like provider.options
// (Schema.Any). Project a JSON-safe copy at the HTTP boundary so
// response encode succeeds and clients see the typed schema shape
// (mirrors Provider.toPublicInfo in #26550).
return toJsonSafe(yield* configSvc.get())
})
const update = Effect.fn("ConfigHttpApi.update")(function* (ctx) {
@@ -6,7 +6,7 @@ import { MCP } from "@/mcp"
import { Project } from "@/project/project"
import { Session } from "@/session/session"
import { ToolRegistry } from "@/tool/registry"
import * as EffectZod from "@opencode-ai/core/effect-zod"
import * as EffectZod from "@/util/effect-zod"
import { Worktree } from "@/worktree"
import { Effect, Option } from "effect"
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"
@@ -9,21 +9,11 @@ import * as Fence from "@/server/shared/fence"
import { getWorkspaceRouteSessionID, isLocalWorkspaceRoute, workspaceProxyURL } from "@/server/shared/workspace-routing"
import { NotFoundError } from "@/storage/storage"
import { Flag } from "@opencode-ai/core/flag/flag"
import { Context, Data, Effect, Layer, Schema } from "effect"
import { Context, Data, Effect, Layer } from "effect"
import { HttpClient, HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
import { HttpApiMiddleware } from "effect/unstable/httpapi"
import * as Socket from "effect/unstable/socket/Socket"
// Query fields this middleware reads from the URL. Spread into every
// endpoint query schema in groups that apply WorkspaceRoutingMiddleware,
// otherwise HttpApi rejects requests carrying these params with 400.
// HttpApiMiddleware in effect-smol cannot declare query params today —
// remove this once upstream supports middleware-declared query schemas.
export const WorkspaceRoutingQueryFields = {
directory: Schema.optional(Schema.String),
workspace: Schema.optional(Schema.String),
}
type RemoteTarget = Extract<Target, { type: "remote" }>
type RequestPlan = Data.TaggedEnum<{
+2 -2
View File
@@ -23,8 +23,8 @@ import type { SystemError } from "bun"
import type { Provider } from "@/provider/provider"
import { ModelID, ProviderID } from "@/provider/schema"
import { Effect, Schema, Types } from "effect"
import { zod, ZodOverride } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema"
import { zod, ZodOverride } from "@/util/effect-zod"
import { NonNegativeInt, withStatics } from "@/util/schema"
import { namedSchemaError } from "@/util/named-schema-error"
import * as EffectLogger from "@opencode-ai/core/effect/logger"
+2 -2
View File
@@ -1,8 +1,8 @@
import { Schema } from "effect"
import { SessionID } from "./schema"
import { ModelID, ProviderID } from "../provider/schema"
import { zod } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { NonNegativeInt, withStatics } from "@/util/schema"
import { namedSchemaError } from "@/util/named-schema-error"
export const OutputLengthError = namedSchemaError("MessageOutputLengthError", {})
+3 -3
View File
@@ -1,6 +1,6 @@
import path from "path"
import os from "os"
import * as EffectZod from "@opencode-ai/core/effect-zod"
import * as EffectZod from "@/util/effect-zod"
import { SessionID, MessageID, PartID } from "./schema"
import { MessageV2 } from "./message-v2"
import * as Log from "@opencode-ai/core/util/log"
@@ -46,8 +46,8 @@ import { Truncate } from "@/tool/truncate"
import { decodeDataUrl } from "@/util/data-url"
import { Process } from "@/util/process"
import { Cause, Effect, Exit, Latch, Layer, Option, Scope, Context, Schema, Types } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import * as EffectLogger from "@opencode-ai/core/effect/logger"
import { InstanceState } from "@/effect/instance-state"
import { TaskTool, type TaskPromptOps } from "@/tool/task"
+2 -2
View File
@@ -4,8 +4,8 @@ import { Snapshot } from "../snapshot"
import { Storage } from "@/storage/storage"
import { SyncEvent } from "../sync"
import * as Log from "@opencode-ai/core/util/log"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import * as Session from "./session"
import { MessageV2 } from "./message-v2"
import { SessionID, MessageID, PartID } from "./schema"
+2 -2
View File
@@ -1,8 +1,8 @@
import { Schema } from "effect"
import { Identifier } from "@/id/id"
import { zod, ZodOverride } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod, ZodOverride } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
export const SessionID = Schema.String.annotate({ [ZodOverride]: Identifier.schema("session") }).pipe(
Schema.brand("SessionID"),
+2 -2
View File
@@ -37,8 +37,8 @@ import type { Provider } from "@/provider/provider"
import { Permission } from "@/permission"
import { Global } from "@opencode-ai/core/global"
import { Effect, Layer, Option, Context, Schema, Types } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt, optionalOmitUndefined, withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { NonNegativeInt, optionalOmitUndefined, withStatics } from "@/util/schema"
const log = Log.create({ service: "session" })
+2 -2
View File
@@ -2,8 +2,8 @@ import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import { InstanceState } from "@/effect/instance-state"
import { SessionID } from "./schema"
import { zod } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { NonNegativeInt, withStatics } from "@/util/schema"
import { Effect, Layer, Context, Schema } from "effect"
import z from "zod"
+2 -3
View File
@@ -2,8 +2,8 @@ import { Effect, Layer, Context, Schema } from "effect"
import { Bus } from "@/bus"
import { Snapshot } from "@/snapshot"
import { Storage } from "@/storage/storage"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import * as Session from "./session"
import { MessageV2 } from "./message-v2"
import { SessionID, MessageID } from "./schema"
@@ -134,7 +134,6 @@ export const layer = Layer.effect(
.read<Snapshot.FileDiff[]>(["session_diff", input.sessionID])
.pipe(Effect.catch(() => Effect.succeed([] as Snapshot.FileDiff[])))
const next = diffs.map((item) => {
if (item.file === undefined) return item
const file = unquoteGitPath(item.file)
if (file === item.file) return item
return { ...item, file }
+2 -2
View File
@@ -1,8 +1,8 @@
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import { SessionID } from "./schema"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { Effect, Layer, Context, Schema } from "effect"
import z from "zod"
import { Database } from "@/storage/db"
+2 -2
View File
@@ -2,8 +2,8 @@ import path from "path"
import { pathToFileURL } from "url"
import z from "zod"
import { Effect, Layer, Context, Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { NamedError } from "@opencode-ai/core/util/error"
import type { Agent } from "@/agent/agent"
import { Bus } from "@/bus"
+4 -7
View File
@@ -10,8 +10,8 @@ import { Hash } from "@opencode-ai/core/util/hash"
import { Config } from "@/config/config"
import { Global } from "@opencode-ai/core/global"
import * as Log from "@opencode-ai/core/util/log"
import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema"
import { zod } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt, withStatics } from "@/util/schema"
import { zod } from "@/util/effect-zod"
export const Patch = Schema.Struct({
hash: Schema.String,
@@ -20,11 +20,8 @@ export const Patch = Schema.Struct({
export type Patch = typeof Patch.Type
export const FileDiff = Schema.Struct({
// Optional because legacy/imported `summary_diffs` on disk may omit
// file details and patch text. Required Schema rejected the whole
// session response and broke session loading on Desktop.
file: Schema.optional(Schema.String),
patch: Schema.optional(Schema.String),
file: Schema.String,
patch: Schema.String,
additions: NonNegativeInt,
deletions: NonNegativeInt,
status: Schema.optional(Schema.Literals(["added", "deleted", "modified"])),
+1 -1
View File
@@ -5,7 +5,7 @@ import { NamedError } from "@opencode-ai/core/util/error"
import z from "zod"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { Effect, Exit, Layer, Option, RcMap, Schema, Context, TxReentrantLock } from "effect"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { NonNegativeInt } from "@/util/schema"
import { Git } from "@/git"
const log = Log.create({ service: "storage" })
+1 -1
View File
@@ -9,7 +9,7 @@ import type { WorkspaceID } from "@/control-plane/schema"
import { EventID } from "./schema"
import { Flag } from "@opencode-ai/core/flag/flag"
import { Context, Effect, Layer, Schema as EffectSchema } from "effect"
import type { DeepMutable } from "@opencode-ai/core/schema"
import type { DeepMutable } from "@/util/schema"
import { makeRuntime } from "@/effect/run-service"
import { serviceUse } from "@/effect/service-use"
import { InstanceState } from "@/effect/instance-state"
+2 -2
View File
@@ -1,8 +1,8 @@
import { Schema } from "effect"
import { Identifier } from "@/id/id"
import { zod, ZodOverride } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod, ZodOverride } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
export const EventID = Schema.String.annotate({ [ZodOverride]: Identifier.schema("event") }).pipe(
Schema.brand("EventID"),
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Option, Schema, Scope } from "effect"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { NonNegativeInt } from "@/util/schema"
import { createReadStream } from "fs"
import * as path from "path"
import { createInterface } from "readline"
+1 -1
View File
@@ -17,7 +17,7 @@ import { Config } from "@/config/config"
import { type ToolContext as PluginToolContext, type ToolDefinition } from "@opencode-ai/plugin"
import { Schema } from "effect"
import z from "zod"
import { ZodOverride } from "@opencode-ai/core/effect-zod"
import { ZodOverride } from "@/util/effect-zod"
import { Plugin } from "../plugin"
import { Provider } from "@/provider/provider"
import { ProviderID, type ModelID } from "../provider/schema"
+2 -2
View File
@@ -1,8 +1,8 @@
import { Schema } from "effect"
import { Identifier } from "@/id/id"
import { zod, ZodOverride } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { zod, ZodOverride } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
const toolIdSchema = Schema.String.annotate({ [ZodOverride]: Identifier.schema("tool") }).pipe(Schema.brand("ToolID"))
+1 -1
View File
@@ -1,6 +1,6 @@
import { Schema } from "effect"
import DESCRIPTION from "./shell.txt"
import { PositiveInt } from "@opencode-ai/core/schema"
import { PositiveInt } from "@/util/schema"
import { Global } from "@opencode-ai/core/global"
import { ShellID } from "./id"
+13
View File
@@ -0,0 +1,13 @@
export function toJsonSafe<T>(value: T): T {
const ancestors: object[] = []
const json = JSON.stringify(value, function (this: unknown, _key, v) {
if (typeof v === "function" || typeof v === "symbol" || v === undefined) return undefined
if (typeof v === "bigint") return v.toString()
if (v === null || typeof v !== "object") return v
while (ancestors.length > 0 && ancestors.at(-1) !== this) ancestors.pop()
if (ancestors.includes(v)) return undefined
ancestors.push(v)
return v
})
return json === undefined ? (undefined as T) : JSON.parse(json)
}
@@ -1,6 +1,6 @@
import { Schema } from "effect"
import z from "zod"
import { zod } from "@opencode-ai/core/effect-zod"
import { zod } from "@/util/effect-zod"
/**
* Create a Schema-backed NamedError-shaped class.
+1 -1
View File
@@ -1,7 +1,7 @@
import path from "path"
import { Effect, Layer, Option, Schema, Context, SynchronizedRef } from "effect"
import { Identifier } from "@opencode-ai/core/util/identifier"
import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema"
import { NonNegativeInt, withStatics } from "@/util/schema"
import { Global } from "@opencode-ai/core/global"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
+1 -1
View File
@@ -1,6 +1,6 @@
import { Identifier } from "@/id/id"
import { SyncEvent } from "@/sync"
import { withStatics } from "@opencode-ai/core/schema"
import { withStatics } from "@/util/schema"
import { Flag } from "@opencode-ai/core/flag/flag"
import * as Schema from "effect/Schema"
+1 -1
View File
@@ -1,4 +1,4 @@
import { withStatics } from "@opencode-ai/core/schema"
import { withStatics } from "@/util/schema"
import { Array, Context, Effect, HashMap, Layer, Option, Order, pipe, Schema } from "effect"
import { DateTimeUtcFromMillis } from "effect/Schema"
+1 -1
View File
@@ -1,5 +1,5 @@
import { SessionID } from "@/session/schema"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { NonNegativeInt } from "@/util/schema"
import { EventV2 } from "./event"
import { FileAttachment, Prompt } from "./session-prompt"
import { Schema } from "effect"
+1 -1
View File
@@ -10,7 +10,7 @@ import { EventV2 } from "./event"
import { ProjectID } from "@/project/schema"
import { SessionEvent } from "./session-event"
import { V2Schema } from "./schema"
import { optionalOmitUndefined } from "@opencode-ai/core/schema"
import { optionalOmitUndefined } from "@/util/schema"
import { Modelv2 } from "./model"
export const Delivery = Schema.Literals(["immediate", "deferred"]).annotate({

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