Compare commits

..

4 Commits

Author SHA1 Message Date
Dax Raad
129fe1e350 ci 2026-03-20 17:00:05 -04:00
Kit Langton
214a6c6cf1 fix: switch consumers to service imports to break bundle cycles (#18438) 2026-03-20 20:55:46 +00:00
Dax Raad
3f249aba6d commit and push 2026-03-20 16:36:19 -04:00
Dax Raad
5c6ec1caac fix question cross out 2026-03-20 15:50:04 -04:00
22 changed files with 63 additions and 72 deletions

View File

@@ -89,8 +89,6 @@
"@ai-sdk/xai": "2.0.51",
"@aws-sdk/credential-providers": "3.993.0",
"@clack/prompts": "1.0.0-alpha.1",
"gitlab-ai-provider": "5.2.2",
"opencode-gitlab-auth": "2.0.0",
"@effect/platform-node": "catalog:",
"@hono/standard-validator": "0.1.5",
"@hono/zod-validator": "catalog:",
@@ -123,6 +121,7 @@
"drizzle-orm": "catalog:",
"effect": "catalog:",
"fuzzysort": "3.1.0",
"gitlab-ai-provider": "5.2.2",
"glob": "13.0.5",
"google-auth-library": "10.5.0",
"gray-matter": "4.0.3",
@@ -133,6 +132,7 @@
"mime-types": "3.0.2",
"minimatch": "10.0.3",
"open": "10.1.2",
"opencode-gitlab-auth": "2.0.0",
"opentui-spinner": "0.0.6",
"partial-json": "0.1.7",
"remeda": "catalog:",

View File

@@ -199,6 +199,19 @@ for (const item of targets) {
},
})
// Smoke test: only run if binary is for current platform
if (item.os === process.platform && item.arch === process.arch && !item.abi) {
const binaryPath = `dist/${name}/bin/opencode`
console.log(`Running smoke test: ${binaryPath} --version`)
try {
const versionOutput = await $`${binaryPath} --version`.text()
console.log(`Smoke test passed: ${versionOutput.trim()}`)
} catch (e) {
console.error(`Smoke test failed for ${name}:`, e)
process.exit(1)
}
}
await $`rm -rf ./dist/${name}/bin/tui`
await Bun.file(`dist/${name}/package.json`).write(
JSON.stringify(

View File

@@ -123,7 +123,6 @@ Done now:
- [x] `Discovery`
- [x] `File`
- [x] `Snapshot`
- [x] `SessionStatus`
Still open and likely worth migrating:

View File

@@ -14,7 +14,7 @@ import PROMPT_COMPACTION from "./prompt/compaction.txt"
import PROMPT_EXPLORE from "./prompt/explore.txt"
import PROMPT_SUMMARY from "./prompt/summary.txt"
import PROMPT_TITLE from "./prompt/title.txt"
import { PermissionNext } from "@/permission"
import { Permission as PermissionNext } from "@/permission/service"
import { mergeDeep, pipe, sortBy, values } from "remeda"
import { Global } from "@/global"
import path from "path"

View File

@@ -22,7 +22,7 @@ import { createStore, produce, reconcile } from "solid-js/store"
import { useSDK } from "@tui/context/sdk"
import { Binary } from "@opencode-ai/util/binary"
import { createSimpleContext } from "./helper"
import type { Snapshot } from "@/snapshot"
import type { Snapshot } from "@/snapshot/service"
import { useExit } from "./exit"
import { useArgs } from "./args"
import { batch, onMount } from "solid-js"

View File

@@ -1667,6 +1667,7 @@ function InlineTool(props: {
const denied = createMemo(
() =>
error()?.includes("QuestionRejectedError") ||
error()?.includes("rejected permission") ||
error()?.includes("specified a rule") ||
error()?.includes("user dismissed"),

View File

@@ -8,7 +8,6 @@ import { Instance } from "@/project/instance"
import { Vcs } from "@/project/vcs"
import { ProviderAuth } from "@/provider/auth-service"
import { Question } from "@/question/service"
import { SessionStatus } from "@/session/status"
import { Skill } from "@/skill/service"
import { Snapshot } from "@/snapshot/service"
import { InstanceContext } from "./instance-context"
@@ -25,7 +24,6 @@ export type InstanceServices =
| FileTime.Service
| Format.Service
| File.Service
| SessionStatus.Service
| Skill.Service
| Snapshot.Service
@@ -46,7 +44,6 @@ function lookup(_key: string) {
FileTime.layer,
Format.layer,
File.layer,
SessionStatus.layer,
Skill.defaultLayer,
Snapshot.defaultLayer,
).pipe(Layer.provide(ctx))

View File

@@ -20,10 +20,6 @@ export function runPromiseInstance<A, E>(effect: Effect.Effect<A, E, InstanceSer
return runtime.runPromise(effect.pipe(Effect.provide(Instances.get(Instance.directory))))
}
export function runSyncInstance<A, E>(effect: Effect.Effect<A, E, InstanceServices>) {
return runtime.runSync(effect.pipe(Effect.provide(Instances.get(Instance.directory))))
}
export function disposeRuntime() {
return runtime.dispose()
}

View File

@@ -12,7 +12,7 @@ import { SessionStatus } from "@/session/status"
import { SessionSummary } from "@/session/summary"
import { Todo } from "../../session/todo"
import { Agent } from "../../agent/agent"
import { Snapshot } from "@/snapshot"
import { Snapshot } from "@/snapshot/service"
import { Log } from "../../util/log"
import { PermissionNext } from "@/permission"
import { PermissionID } from "@/permission/schema"

View File

@@ -20,7 +20,7 @@ import { Instance } from "../project/instance"
import { SessionPrompt } from "./prompt"
import { fn } from "@/util/fn"
import { Command } from "../command"
import { Snapshot } from "@/snapshot"
import { Snapshot } from "@/snapshot/service"
import { WorkspaceContext } from "../control-plane/workspace-context"
import { ProjectID } from "../project/schema"
import { WorkspaceID } from "../control-plane/schema"
@@ -28,7 +28,7 @@ import { SessionID, MessageID, PartID } from "./schema"
import type { Provider } from "@/provider/provider"
import { ModelID, ProviderID } from "@/provider/schema"
import { PermissionNext } from "@/permission"
import { Permission as PermissionNext } from "@/permission/service"
import { Global } from "@/global"
import type { LanguageModelV2Usage } from "@ai-sdk/provider"
import { iife } from "@/util/iife"

View File

@@ -21,7 +21,7 @@ import type { MessageV2 } from "./message-v2"
import { Plugin } from "@/plugin"
import { SystemPrompt } from "./system"
import { Flag } from "@/flag/flag"
import { PermissionNext } from "@/permission"
import { Permission as PermissionNext } from "@/permission/service"
import { Auth } from "@/auth"
export namespace LLM {

View File

@@ -4,7 +4,7 @@ import z from "zod"
import { NamedError } from "@opencode-ai/util/error"
import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai"
import { LSP } from "../lsp"
import { Snapshot } from "@/snapshot"
import { Snapshot } from "@/snapshot/service"
import { fn } from "@/util/fn"
import { Database, NotFoundError, and, desc, eq, inArray, lt, or } from "@/storage/db"
import { MessageTable, PartTable, SessionTable } from "./session.sql"

View File

@@ -13,7 +13,7 @@ import { LLM } from "./llm"
import { Config } from "@/config/config"
import { SessionCompaction } from "./compaction"
import { PermissionNext } from "@/permission"
import { Question } from "@/question"
import { Question } from "@/question/service"
import { PartID } from "./schema"
import type { SessionID, MessageID } from "./schema"

View File

@@ -1,8 +1,8 @@
import { sqliteTable, text, integer, index, primaryKey } from "drizzle-orm/sqlite-core"
import { ProjectTable } from "../project/project.sql"
import type { MessageV2 } from "./message-v2"
import type { Snapshot } from "../snapshot"
import type { PermissionNext } from "../permission"
import type { Snapshot } from "../snapshot/service"
import type { Permission as PermissionNext } from "../permission/service"
import type { ProjectID } from "../project/schema"
import type { SessionID, MessageID, PartID } from "./schema"
import type { WorkspaceID } from "../control-plane/schema"

View File

@@ -1,8 +1,7 @@
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import { runSyncInstance } from "@/effect/runtime"
import { Instance } from "@/project/instance"
import { SessionID } from "./schema"
import { Effect, Layer, ServiceMap } from "effect"
import z from "zod"
export namespace SessionStatus {
@@ -43,51 +42,36 @@ export namespace SessionStatus {
),
}
export interface Interface {
readonly get: (sessionID: SessionID) => Effect.Effect<Info>
readonly list: () => Effect.Effect<Record<string, Info>>
readonly set: (sessionID: SessionID, status: Info) => Effect.Effect<void>
const state = Instance.state(() => {
const data: Record<string, Info> = {}
return data
})
export function get(sessionID: SessionID) {
return (
state()[sessionID] ?? {
type: "idle",
}
)
}
export class Service extends ServiceMap.Service<Service, Interface>()("@opencode/SessionStatus") {}
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const data = new Map<SessionID, Info>()
const get = Effect.fn("SessionStatus.get")(function* (sessionID: SessionID) {
return data.get(sessionID) ?? { type: "idle" as const }
})
const list = Effect.fn("SessionStatus.list")(function* () {
return Object.fromEntries(data)
})
const set = Effect.fn("SessionStatus.set")(function* (sessionID: SessionID, status: Info) {
Bus.publish(Event.Status, { sessionID, status })
if (status.type === "idle") {
// deprecated
Bus.publish(Event.Idle, { sessionID })
data.delete(sessionID)
return
}
data.set(sessionID, status)
})
return Service.of({ get, list, set })
}),
)
export function get(sessionID: SessionID): Info {
return runSyncInstance(Service.use((svc) => svc.get(sessionID)))
}
export function list(): Record<string, Info> {
return runSyncInstance(Service.use((svc) => svc.list()))
export function list() {
return state()
}
export function set(sessionID: SessionID, status: Info) {
runSyncInstance(Service.use((svc) => svc.set(sessionID, status)))
Bus.publish(Event.Status, {
sessionID,
status,
})
if (status.type === "idle") {
// deprecated
Bus.publish(Event.Idle, {
sessionID,
})
delete state()[sessionID]
return
}
state()[sessionID] = status
}
}

View File

@@ -11,7 +11,7 @@ import PROMPT_CODEX from "./prompt/codex.txt"
import PROMPT_TRINITY from "./prompt/trinity.txt"
import type { Provider } from "@/provider/provider"
import type { Agent } from "@/agent/agent"
import { PermissionNext } from "@/permission"
import { Permission as PermissionNext } from "@/permission/service"
import { Skill } from "@/skill"
export namespace SystemPrompt {

View File

@@ -12,7 +12,7 @@ import { trimDiff } from "./edit"
import { LSP } from "../lsp"
import { Filesystem } from "../util/filesystem"
import DESCRIPTION from "./apply_patch.txt"
import { File } from "../file"
import { File } from "../file/service"
const PatchParams = z.object({
patchText: z.string().describe("The full patch text that describes all changes to be made"),

View File

@@ -9,13 +9,13 @@ import { Tool } from "./tool"
import { LSP } from "../lsp"
import { createTwoFilesPatch, diffLines } from "diff"
import DESCRIPTION from "./edit.txt"
import { File } from "../file"
import { File } from "../file/service"
import { FileWatcher } from "../file/watcher"
import { Bus } from "../bus"
import { FileTime } from "../file/time"
import { Filesystem } from "../util/filesystem"
import { Instance } from "../project/instance"
import { Snapshot } from "@/snapshot"
import { Snapshot } from "@/snapshot/service"
import { assertExternalDirectory } from "./external-directory"
const MAX_DIAGNOSTICS_PER_FILE = 20

View File

@@ -1,6 +1,7 @@
import z from "zod"
import { Tool } from "./tool"
import { Question } from "../question"
import { Question } from "../question/service"
import { Question as QuestionApi } from "../question"
import DESCRIPTION from "./question.txt"
export const QuestionTool = Tool.define("question", {
@@ -9,7 +10,7 @@ export const QuestionTool = Tool.define("question", {
questions: z.array(Question.Info.omit({ custom: true })).describe("Questions to ask"),
}),
async execute(params, ctx) {
const answers = await Question.ask({
const answers = await QuestionApi.ask({
sessionID: ctx.sessionID,
questions: params.questions,
tool: ctx.callID ? { messageID: ctx.messageID, callID: ctx.callID } : undefined,

View File

@@ -10,7 +10,7 @@ import { SessionPrompt } from "../session/prompt"
import { iife } from "@/util/iife"
import { defer } from "@/util/defer"
import { Config } from "../config/config"
import { PermissionNext } from "@/permission"
import { Permission as PermissionNext } from "@/permission/service"
const parameters = z.object({
description: z.string().describe("A short (3-5 words) description of the task"),

View File

@@ -1,7 +1,7 @@
import z from "zod"
import type { MessageV2 } from "../session/message-v2"
import type { Agent } from "../agent/agent"
import type { PermissionNext } from "../permission"
import type { Permission as PermissionNext } from "../permission/service"
import type { SessionID, MessageID } from "../session/schema"
import { Truncate } from "./truncate"

View File

@@ -5,7 +5,7 @@ import { LSP } from "../lsp"
import { createTwoFilesPatch } from "diff"
import DESCRIPTION from "./write.txt"
import { Bus } from "../bus"
import { File } from "../file"
import { File } from "../file/service"
import { FileWatcher } from "../file/watcher"
import { FileTime } from "../file/time"
import { Filesystem } from "../util/filesystem"