Compare commits

..
33 changed files with 101 additions and 67 deletions
+2
View File
@@ -359,6 +359,8 @@
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/server": "workspace:*",
"@openrouter/ai-sdk-provider": "2.5.1",
"@opentelemetry/api": "1.9.0",
"@opentelemetry/context-async-hooks": "2.6.1",
"@opentelemetry/exporter-trace-otlp-http": "0.214.0",
"@opentelemetry/sdk-trace-base": "2.6.1",
"@opentelemetry/sdk-trace-node": "2.6.1",
+4 -2
View File
@@ -113,13 +113,15 @@
"@octokit/rest": "catalog:",
"@openauthjs/openauth": "catalog:",
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/server": "workspace:*",
"@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/server": "workspace:*",
"@openrouter/ai-sdk-provider": "2.5.1",
"@opentelemetry/api": "1.9.0",
"@opentelemetry/context-async-hooks": "2.6.1",
"@opentelemetry/exporter-trace-otlp-http": "0.214.0",
"@opentelemetry/sdk-trace-base": "2.6.1",
"@opentelemetry/sdk-trace-node": "2.6.1",
"@openrouter/ai-sdk-provider": "2.5.1",
"@opentui/core": "0.1.99",
"@opentui/solid": "0.1.99",
"@parcel/watcher": "2.5.1",
+1 -1
View File
@@ -2,7 +2,7 @@
import { z } from "zod"
import { Config } from "../src/config/config"
import { TuiConfig } from "../test/cli/tui/config/tui"
import { TuiConfig } from "../src/config/tui"
function generate(schema: z.ZodType) {
const result = z.toJSONSchema(schema, {
+1 -1
View File
@@ -57,7 +57,7 @@ import { ArgsProvider, useArgs, type Args } from "./context/args"
import open from "open"
import { PromptRefProvider, usePromptRef } from "./context/prompt"
import { TuiConfigProvider, useTuiConfig } from "./context/tui-config"
import { TuiConfig } from "@test/cli/tui/config/tui"
import { TuiConfig } from "@/config/tui"
import { createTuiApi, TuiPluginRuntime, type RouteMap } from "./plugin"
import { FormatError, FormatUnknownError } from "@/cli/error"
+1 -1
View File
@@ -2,7 +2,7 @@ import { cmd } from "../cmd"
import { UI } from "@/cli/ui"
import { tui } from "./app"
import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
import { TuiConfig } from "@test/cli/tui/config/tui"
import { TuiConfig } from "@/config/tui"
import { Instance } from "@/project/instance"
import { existsSync } from "fs"
@@ -1,7 +1,7 @@
import { createMemo } from "solid-js"
import { Keybind } from "@/util/keybind"
import { pipe, mapValues } from "remeda"
import type { TuiConfig } from "@test/cli/tui/config/tui"
import type { TuiConfig } from "@/config/tui"
import type { ParsedKey, Renderable } from "@opentui/core"
import { createStore } from "solid-js/store"
import { useKeyboard, useRenderer } from "@opentui/solid"
@@ -29,11 +29,7 @@ import { useExit } from "./exit"
import { useArgs } from "./args"
import { batch, createEffect, on } from "solid-js"
import { Log } from "@/util/log"
import {
ConsoleState,
emptyConsoleState,
type ConsoleState as ConsoleStateType,
} from "@test/cli/tui/config/console-state"
import { ConsoleState, emptyConsoleState, type ConsoleState as ConsoleStateType } from "@/config/console-state"
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
name: "Sync",
@@ -1,4 +1,4 @@
import { TuiConfig } from "@test/cli/tui/config/tui"
import { TuiConfig } from "@/config/tui"
import { createSimpleContext } from "./helper"
export const { use: useTuiConfig, provider: TuiConfigProvider } = createSimpleContext({
@@ -8,7 +8,7 @@ import type { useSDK } from "@tui/context/sdk"
import type { useSync } from "@tui/context/sync"
import type { useTheme } from "@tui/context/theme"
import { Dialog as DialogUI, type useDialog } from "@tui/ui/dialog"
import type { TuiConfig } from "@test/cli/tui/config/tui"
import type { TuiConfig } from "@/config/tui"
import { createPluginKeybind } from "../context/plugin-keybinds"
import type { useKV } from "../context/kv"
import { DialogAlert } from "../ui/dialog-alert"
@@ -14,7 +14,7 @@ import path from "path"
import { fileURLToPath } from "url"
import { Config } from "@/config/config"
import { TuiConfig } from "@test/cli/tui/config/tui"
import { TuiConfig } from "@/config/tui"
import { Log } from "@/util/log"
import { errorData, errorMessage } from "@/util/error"
import { isRecord } from "@/util/record"
+1 -1
View File
@@ -13,7 +13,7 @@ import { Filesystem } from "@/util/filesystem"
import type { GlobalEvent } from "@opencode-ai/sdk/v2"
import type { EventSource } from "./context/sdk"
import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
import { TuiConfig } from "@test/cli/tui/config/tui"
import { TuiConfig } from "@/config/tui"
import { Instance } from "@/project/instance"
import { writeHeapSnapshot } from "v8"
@@ -1,5 +1,5 @@
import { MacOSScrollAccel, type ScrollAcceleration } from "@opentui/core"
import type { TuiConfig } from "@test/cli/tui/config/tui"
import type { TuiConfig } from "@/config/tui"
export class CustomSpeedScroll implements ScrollAcceleration {
constructor(private speed: number) {}
+1 -1
View File
@@ -30,7 +30,7 @@ import { Glob } from "@opencode-ai/shared/util/glob"
import { Account } from "@/account"
import { isRecord } from "@/util/record"
import { ConfigPaths } from "./paths"
import type { ConsoleState } from "@test/cli/tui/config/console-state"
import type { ConsoleState } from "./console-state"
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { InstanceState } from "@/effect/instance-state"
import { Context, Duration, Effect, Exit, Fiber, Layer, Option } from "effect"
@@ -2,7 +2,7 @@ import path from "path"
import { type ParseError as JsoncParseError, applyEdits, modify, parse as parseJsonc } from "jsonc-parser"
import { unique } from "remeda"
import z from "zod"
import { ConfigPaths } from "@/config/paths"
import { ConfigPaths } from "./paths"
import { TuiInfo, TuiOptions } from "./tui-schema"
import { Instance } from "@/project/instance"
import { Flag } from "@/flag/flag"
@@ -1,5 +1,5 @@
import z from "zod"
import { Config } from "@/config/config"
import { Config } from "./config"
const KeybindOverride = z
.object(
@@ -2,8 +2,8 @@ import { existsSync } from "fs"
import z from "zod"
import { mergeDeep, unique } from "remeda"
import { Context, Effect, Fiber, Layer } from "effect"
import { Config } from "@/config/config"
import { ConfigPaths } from "@/config/paths"
import { Config } from "./config"
import { ConfigPaths } from "./paths"
import { migrateTuiConfig } from "./tui-migrate"
import { TuiInfo } from "./tui-schema"
import { Flag } from "@/flag/flag"
@@ -46,6 +46,18 @@ export namespace Observability {
const OTLP = await import("@opentelemetry/exporter-trace-otlp-http")
const SdkBase = await import("@opentelemetry/sdk-trace-base")
// @effect/opentelemetry creates a NodeTracerProvider but never calls
// register(), so the global @opentelemetry/api context manager stays
// as the no-op default. Non-Effect code (like the AI SDK) that calls
// tracer.startActiveSpan() relies on context.active() to find the
// parent span — without a real context manager every span starts a
// new trace. Registering AsyncLocalStorageContextManager fixes this.
const { AsyncLocalStorageContextManager } = await import("@opentelemetry/context-async-hooks")
const { context } = await import("@opentelemetry/api")
const mgr = new AsyncLocalStorageContextManager()
mgr.enable()
context.setGlobalContextManager(mgr)
return NodeSdk.layer(() => ({
resource,
spanProcessor: new SdkBase.BatchSpanProcessor(
+11 -17
View File
@@ -5,12 +5,10 @@ import { Config } from "../../config/config"
import { Provider } from "../../provider/provider"
import { mapValues } from "remeda"
import { errors } from "../error"
import { Log } from "../../util/log"
import { lazy } from "../../util/lazy"
import { AppRuntime } from "../../effect/app-runtime"
import { Effect } from "effect"
const log = Log.create({ service: "server" })
import { jsonRequest } from "./trace"
export const ConfigRoutes = lazy(() =>
new Hono()
@@ -31,9 +29,10 @@ export const ConfigRoutes = lazy(() =>
},
},
}),
async (c) => {
return c.json(await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.get())))
},
jsonRequest("ConfigRoutes.get", function* () {
const cfg = yield* Config.Service
return yield* cfg.get()
}),
)
.patch(
"/",
@@ -82,18 +81,13 @@ export const ConfigRoutes = lazy(() =>
},
},
}),
async (c) => {
using _ = log.time("providers")
const providers = await AppRuntime.runPromise(
Effect.gen(function* () {
const svc = yield* Provider.Service
return mapValues(yield* svc.list(), (item) => item)
}),
)
return c.json({
jsonRequest("ConfigRoutes.providers", function* () {
const svc = yield* Provider.Service
const providers = mapValues(yield* svc.list(), (item) => item)
return {
providers: Object.values(providers),
default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
})
},
}
}),
),
)
@@ -9,7 +9,7 @@ import { Project } from "../../project/project"
import { MCP } from "../../mcp"
import { Session } from "../../session"
import { Config } from "../../config/config"
import { ConsoleState } from "@test/cli/tui/config/console-state"
import { ConsoleState } from "../../config/console-state"
import { Account, AccountID, OrgID } from "../../account"
import { AppRuntime } from "../../effect/app-runtime"
import { zodToJsonSchema } from "zod-to-json-schema"
@@ -26,6 +26,7 @@ import { errors } from "../error"
import { lazy } from "../../util/lazy"
import { Bus } from "../../bus"
import { NamedError } from "@opencode-ai/shared/util/error"
import { jsonRequest } from "./trace"
const log = Log.create({ service: "server" })
@@ -94,10 +95,10 @@ export const SessionRoutes = lazy(() =>
...errors(400),
},
}),
async (c) => {
const result = await AppRuntime.runPromise(SessionStatus.Service.use((svc) => svc.list()))
return c.json(Object.fromEntries(result))
},
jsonRequest("SessionRoutes.status", function* () {
const svc = yield* SessionStatus.Service
return Object.fromEntries(yield* svc.list())
}),
)
.get(
"/:sessionID",
@@ -124,11 +125,11 @@ export const SessionRoutes = lazy(() =>
sessionID: Session.GetInput,
}),
),
async (c) => {
jsonRequest("SessionRoutes.get", function* (c) {
const sessionID = c.req.valid("param").sessionID
const session = await AppRuntime.runPromise(Session.Service.use((svc) => svc.get(sessionID)))
return c.json(session)
},
const session = yield* Session.Service
return yield* session.get(sessionID)
}),
)
.get(
"/:sessionID/children",
@@ -155,11 +156,11 @@ export const SessionRoutes = lazy(() =>
sessionID: Session.ChildrenInput,
}),
),
async (c) => {
jsonRequest("SessionRoutes.children", function* (c) {
const sessionID = c.req.valid("param").sessionID
const session = await AppRuntime.runPromise(Session.Service.use((svc) => svc.children(sessionID)))
return c.json(session)
},
const session = yield* Session.Service
return yield* session.children(sessionID)
}),
)
.get(
"/:sessionID/todo",
@@ -185,11 +186,11 @@ export const SessionRoutes = lazy(() =>
sessionID: SessionID.zod,
}),
),
async (c) => {
jsonRequest("SessionRoutes.todo", function* (c) {
const sessionID = c.req.valid("param").sessionID
const todos = await AppRuntime.runPromise(Todo.Service.use((svc) => svc.get(sessionID)))
return c.json(todos)
},
const todo = yield* Todo.Service
return yield* todo.get(sessionID)
}),
)
.post(
"/",
@@ -0,0 +1,28 @@
import type { Context } from "hono"
import { Effect } from "effect"
import { AppRuntime } from "../../effect/app-runtime"
export function runRequest<A, E>(name: string, c: Context, effect: Effect.Effect<A, E, any>) {
const url = new URL(c.req.url)
return AppRuntime.runPromise(
effect.pipe(
Effect.withSpan(name, {
attributes: {
"http.method": c.req.method,
"http.path": url.pathname,
},
}),
),
)
}
export function jsonRequest<A, E>(name: string, effect: (c: any) => Effect.gen.Return<A, E, any>) {
return async (c: Context) =>
c.json(
await runRequest(
name,
c,
Effect.gen(() => effect(c)),
),
)
}
@@ -4,7 +4,7 @@ import path from "path"
import { pathToFileURL } from "url"
import { tmpdir } from "../../fixture/fixture"
import { createTuiPluginApi } from "../../fixture/tui-plugin"
import { TuiConfig } from "./config/tui"
import { TuiConfig } from "../../../src/config/tui"
const { TuiPluginRuntime } = await import("../../../src/cli/cmd/tui/plugin/runtime")
@@ -4,7 +4,7 @@ import path from "path"
import { pathToFileURL } from "url"
import { tmpdir } from "../../fixture/fixture"
import { createTuiPluginApi } from "../../fixture/tui-plugin"
import { TuiConfig } from "./config/tui"
import { TuiConfig } from "../../../src/config/tui"
const { TuiPluginRuntime } = await import("../../../src/cli/cmd/tui/plugin/runtime")
@@ -5,7 +5,7 @@ import { pathToFileURL } from "url"
import { tmpdir } from "../../fixture/fixture"
import { createTuiPluginApi } from "../../fixture/tui-plugin"
import { mockTuiRuntime } from "../../fixture/tui-runtime"
import { TuiConfig } from "./config/tui"
import { TuiConfig } from "../../../src/config/tui"
const { TuiPluginRuntime } = await import("../../../src/cli/cmd/tui/plugin/runtime")
@@ -4,7 +4,7 @@ import path from "path"
import { pathToFileURL } from "url"
import { tmpdir } from "../../fixture/fixture"
import { createTuiPluginApi } from "../../fixture/tui-plugin"
import { TuiConfig } from "./config/tui"
import { TuiConfig } from "../../../src/config/tui"
import { Npm } from "../../../src/npm"
const { TuiPluginRuntime } = await import("../../../src/cli/cmd/tui/plugin/runtime")
@@ -4,7 +4,7 @@ import path from "path"
import { pathToFileURL } from "url"
import { tmpdir } from "../../fixture/fixture"
import { createTuiPluginApi } from "../../fixture/tui-plugin"
import { TuiConfig } from "./config/tui"
import { TuiConfig } from "../../../src/config/tui"
const { TuiPluginRuntime } = await import("../../../src/cli/cmd/tui/plugin/runtime")
@@ -5,7 +5,7 @@ import { pathToFileURL } from "url"
import { tmpdir } from "../../fixture/fixture"
import { createTuiPluginApi } from "../../fixture/tui-plugin"
import { Global } from "../../../src/global"
import { TuiConfig } from "./config/tui"
import { TuiConfig } from "../../../src/config/tui"
import { Filesystem } from "../../../src/util/filesystem"
const { allThemes, addTheme } = await import("../../../src/cli/cmd/tui/context/theme")
@@ -4,7 +4,7 @@ import path from "path"
import { pathToFileURL } from "url"
import { tmpdir } from "../../fixture/fixture"
import { createTuiPluginApi } from "../../fixture/tui-plugin"
import { TuiConfig } from "./config/tui"
import { TuiConfig } from "../../../src/config/tui"
const { TuiPluginRuntime } = await import("../../../src/cli/cmd/tui/plugin/runtime")
@@ -8,7 +8,7 @@ import { UI } from "../../../src/cli/ui"
import * as Timeout from "../../../src/util/timeout"
import * as Network from "../../../src/cli/network"
import * as Win32 from "../../../src/cli/cmd/tui/win32"
import { TuiConfig } from "./config/tui"
import { TuiConfig } from "../../../src/config/tui"
import { Instance } from "../../../src/project/instance"
const stop = new Error("stop")
+1 -1
View File
@@ -4,7 +4,7 @@ import fs from "fs/promises"
import { tmpdir } from "../fixture/fixture"
import { Instance } from "../../src/project/instance"
import { Config } from "../../src/config/config"
import { TuiConfig } from "../cli/tui/config/tui"
import { TuiConfig } from "../../src/config/tui"
import { Global } from "../../src/global"
import { Filesystem } from "../../src/util/filesystem"
import { AppRuntime } from "../../src/effect/app-runtime"
@@ -1,6 +1,6 @@
import { spyOn } from "bun:test"
import path from "path"
import { TuiConfig } from "../cli/tui/config/tui"
import { TuiConfig } from "../../src/config/tui"
type PluginSpec = string | [string, Record<string, unknown>]
+1 -2
View File
@@ -10,8 +10,7 @@
"customConditions": ["browser"],
"paths": {
"@/*": ["./src/*"],
"@tui/*": ["./src/cli/cmd/tui/*"],
"@test/*": ["./test/*"]
"@tui/*": ["./src/cli/cmd/tui/*"]
},
"plugins": [
{