From bbf67d0fff28b8d26d7ffb11c347f519308944b0 Mon Sep 17 00:00:00 2001 From: James Long Date: Thu, 23 Apr 2026 11:24:40 -0400 Subject: [PATCH 01/59] fix(tui): render all non-synthetic text parts of a user message (#24009) --- .../src/cli/cmd/tui/routes/session/index.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 2f5da1d231..c04e58acec 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -1250,7 +1250,17 @@ function UserMessage(props: { }) { const ctx = use() const local = useLocal() - const text = createMemo(() => props.parts.flatMap((x) => (x.type === "text" && !x.synthetic ? [x] : []))[0]) + const text = createMemo(() => { + const texts = props.parts + .map((x) => { + if (x.type === "text" && !x.synthetic) { + return x.text + } + return null + }) + .filter(Boolean) + return texts.join("\n\n") + }) const files = createMemo(() => props.parts.flatMap((x) => (x.type === "file" ? [x] : []))) const { theme } = useTheme() const [hover, setHover] = createSignal(false) @@ -1285,7 +1295,7 @@ function UserMessage(props: { backgroundColor={hover() ? theme.backgroundElement : theme.backgroundPanel} flexShrink={0} > - {text()?.text} + {text()} From 0517ab4695e6918a379d1e44cc6cd04a9dc80c06 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 11:30:02 -0400 Subject: [PATCH 02/59] refactor(session): migrate session domain to Effect Schema (#24005) --- packages/opencode/specs/effect/schema.md | 21 +- packages/opencode/src/acp/agent.ts | 4 +- packages/opencode/src/cli/cmd/import.ts | 5 +- .../server/routes/instance/experimental.ts | 2 +- .../src/server/routes/instance/session.ts | 72 ++-- packages/opencode/src/session/message.ts | 353 +++++++++--------- packages/opencode/src/session/projectors.ts | 2 +- packages/opencode/src/session/prompt.ts | 149 ++++---- packages/opencode/src/session/revert.ts | 17 +- packages/opencode/src/session/session.ts | 193 +++++----- packages/opencode/src/session/status.ts | 42 +-- packages/opencode/src/session/summary.ts | 14 +- packages/opencode/src/session/todo.ts | 24 +- packages/opencode/src/tool/todo.ts | 15 +- packages/opencode/src/util/effect-zod.ts | 27 ++ .../test/server/global-session-list.test.ts | 2 +- .../opencode/test/session/compaction.test.ts | 2 +- .../test/session/schema-decoding.test.ts | 310 +++++++++++++++ 18 files changed, 812 insertions(+), 442 deletions(-) create mode 100644 packages/opencode/test/session/schema-decoding.test.ts diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index 9ff6859cee..3f2c3b4c96 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -159,6 +159,7 @@ Schema at source. These are the highest-priority next targets. Each is a small, self-contained schema module with a clear domain. +- [x] `src/account/schema.ts` - [x] `src/control-plane/schema.ts` - [x] `src/permission/schema.ts` - [x] `src/project/schema.ts` @@ -166,8 +167,10 @@ schema module with a clear domain. - [x] `src/pty/schema.ts` - [x] `src/question/schema.ts` - [x] `src/session/schema.ts` +- [x] `src/storage/schema.ts` - [x] `src/sync/schema.ts` - [x] `src/tool/schema.ts` +- [x] `src/util/schema.ts` ### Session domain @@ -248,15 +251,15 @@ Possible later tightening after the Schema-first migration is stable: - promote repeated opaque strings and timestamp numbers into branded/newtype leaf schemas where that adds domain value without changing the wire format -- [ ] `src/session/compaction.ts` -- [ ] `src/session/message-v2.ts` -- [ ] `src/session/message.ts` -- [ ] `src/session/prompt.ts` -- [ ] `src/session/revert.ts` -- [ ] `src/session/session.ts` -- [ ] `src/session/status.ts` -- [ ] `src/session/summary.ts` -- [ ] `src/session/todo.ts` +- [x] `src/session/compaction.ts` +- [x] `src/session/message-v2.ts` +- [x] `src/session/message.ts` +- [x] `src/session/prompt.ts` +- [x] `src/session/revert.ts` +- [x] `src/session/session.ts` +- [x] `src/session/status.ts` +- [x] `src/session/summary.ts` +- [x] `src/session/todo.ts` ### Provider domain diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index f12328153b..672b93f6ce 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -372,7 +372,7 @@ export class Agent implements ACPAgent { } if (part.tool === "todowrite") { - const parsedTodos = z.array(Todo.Info).safeParse(JSON.parse(part.state.output)) + const parsedTodos = z.array(Todo.Info.zod).safeParse(JSON.parse(part.state.output)) if (parsedTodos.success) { await this.connection .sessionUpdate({ @@ -901,7 +901,7 @@ export class Agent implements ACPAgent { } if (part.tool === "todowrite") { - const parsedTodos = z.array(Todo.Info).safeParse(JSON.parse(part.state.output)) + const parsedTodos = z.array(Todo.Info.zod).safeParse(JSON.parse(part.state.output)) if (parsedTodos.success) { await this.connection .sessionUpdate({ diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts index 309ec6d950..e52120f1af 100644 --- a/packages/opencode/src/cli/cmd/import.ts +++ b/packages/opencode/src/cli/cmd/import.ts @@ -11,6 +11,7 @@ import { ShareNext } from "../../share" import { EOL } from "os" import { Filesystem } from "../../util" import { AppRuntime } from "@/effect/app-runtime" +import { Schema } from "effect" /** Discriminated union returned by the ShareNext API (GET /api/shares/:id/data) */ export type ShareData = @@ -154,10 +155,10 @@ export const ImportCommand = cmd({ return } - const info = Session.Info.parse({ + const info = Schema.decodeUnknownSync(Session.Info)({ ...exportData.info, projectID: Instance.project.id, - }) + }) as Session.Info const row = Session.toRow(info) Database.use((db) => db diff --git a/packages/opencode/src/server/routes/instance/experimental.ts b/packages/opencode/src/server/routes/instance/experimental.ts index 9c86494987..93a5d98c94 100644 --- a/packages/opencode/src/server/routes/instance/experimental.ts +++ b/packages/opencode/src/server/routes/instance/experimental.ts @@ -335,7 +335,7 @@ export const ExperimentalRoutes = lazy(() => description: "List of sessions", content: { "application/json": { - schema: resolver(Session.GlobalInfo.array()), + schema: resolver(Session.GlobalInfo.zod.array()), }, }, }, diff --git a/packages/opencode/src/server/routes/instance/session.ts b/packages/opencode/src/server/routes/instance/session.ts index 8d03024260..4f4f8ed86e 100644 --- a/packages/opencode/src/server/routes/instance/session.ts +++ b/packages/opencode/src/server/routes/instance/session.ts @@ -23,6 +23,7 @@ import { PermissionID } from "@/permission/schema" import { ModelID, ProviderID } from "@/provider/schema" import { errors } from "../../error" import { lazy } from "@/util/lazy" +import { zodObject } from "@/util/effect-zod" import { Bus } from "@/bus" import { NamedError } from "@opencode-ai/shared/util/error" import { jsonRequest, runRequest } from "./trace" @@ -42,7 +43,7 @@ export const SessionRoutes = lazy(() => description: "List of sessions", content: { "application/json": { - schema: resolver(Session.Info.array()), + schema: resolver(Session.Info.zod.array()), }, }, }, @@ -87,7 +88,7 @@ export const SessionRoutes = lazy(() => description: "Get session status", content: { "application/json": { - schema: resolver(z.record(z.string(), SessionStatus.Info)), + schema: resolver(z.record(z.string(), SessionStatus.Info.zod)), }, }, }, @@ -112,7 +113,7 @@ export const SessionRoutes = lazy(() => description: "Get session", content: { "application/json": { - schema: resolver(Session.Info), + schema: resolver(Session.Info.zod), }, }, }, @@ -122,7 +123,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: Session.GetInput, + sessionID: Session.GetInput.zod, }), ), async (c) => { @@ -145,7 +146,7 @@ export const SessionRoutes = lazy(() => description: "List of children", content: { "application/json": { - schema: resolver(Session.Info.array()), + schema: resolver(Session.Info.zod.array()), }, }, }, @@ -155,7 +156,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: Session.ChildrenInput, + sessionID: Session.ChildrenInput.zod, }), ), async (c) => { @@ -177,7 +178,7 @@ export const SessionRoutes = lazy(() => description: "Todo list", content: { "application/json": { - schema: resolver(Todo.Info.array()), + schema: resolver(Todo.Info.zod.array()), }, }, }, @@ -210,13 +211,13 @@ export const SessionRoutes = lazy(() => description: "Successfully created session", content: { "application/json": { - schema: resolver(Session.Info), + schema: resolver(Session.Info.zod), }, }, }, }, }), - validator("json", Session.CreateInput), + validator("json", Session.CreateInput.zod), async (c) => jsonRequest("SessionRoutes.create", c, function* () { const body = c.req.valid("json") ?? {} @@ -245,7 +246,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: Session.RemoveInput, + sessionID: Session.RemoveInput.zod, }), ), async (c) => @@ -267,7 +268,7 @@ export const SessionRoutes = lazy(() => description: "Successfully updated session", content: { "application/json": { - schema: resolver(Session.Info), + schema: resolver(Session.Info.zod), }, }, }, @@ -375,7 +376,7 @@ export const SessionRoutes = lazy(() => description: "200", content: { "application/json": { - schema: resolver(Session.Info), + schema: resolver(Session.Info.zod), }, }, }, @@ -384,14 +385,14 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: Session.ForkInput.shape.sessionID, + sessionID: SessionID.zod, }), ), - validator("json", Session.ForkInput.omit({ sessionID: true })), + validator("json", zodObject(Session.ForkInput).omit({ sessionID: true })), async (c) => jsonRequest("SessionRoutes.fork", c, function* () { const sessionID = c.req.valid("param").sessionID - const body = c.req.valid("json") + const body = c.req.valid("json") as { messageID?: MessageID } const svc = yield* Session.Service return yield* svc.fork({ ...body, sessionID }) }), @@ -438,7 +439,7 @@ export const SessionRoutes = lazy(() => description: "Successfully shared session", content: { "application/json": { - schema: resolver(Session.Info), + schema: resolver(Session.Info.zod), }, }, }, @@ -480,18 +481,13 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: SessionSummary.DiffInput.shape.sessionID, - }), - ), - validator( - "query", - z.object({ - messageID: SessionSummary.DiffInput.shape.messageID, + sessionID: SessionID.zod, }), ), + validator("query", zodObject(SessionSummary.DiffInput).omit({ sessionID: true })), async (c) => jsonRequest("SessionRoutes.diff", c, function* () { - const query = c.req.valid("query") + const query = c.req.valid("query") as Omit const params = c.req.valid("param") const summary = yield* SessionSummary.Service return yield* summary.diff({ @@ -511,7 +507,7 @@ export const SessionRoutes = lazy(() => description: "Successfully unshared session", content: { "application/json": { - schema: resolver(Session.Info), + schema: resolver(Session.Info.zod), }, }, }, @@ -872,7 +868,7 @@ export const SessionRoutes = lazy(() => sessionID: SessionID.zod, }), ), - validator("json", SessionPrompt.PromptInput.omit({ sessionID: true })), + validator("json", zodObject(SessionPrompt.PromptInput).omit({ sessionID: true })), async (c) => { c.status(200) c.header("Content-Type", "application/json") @@ -910,7 +906,7 @@ export const SessionRoutes = lazy(() => sessionID: SessionID.zod, }), ), - validator("json", SessionPrompt.PromptInput.omit({ sessionID: true })), + validator("json", zodObject(SessionPrompt.PromptInput).omit({ sessionID: true })), async (c) => { const sessionID = c.req.valid("param").sessionID const body = c.req.valid("json") @@ -960,11 +956,11 @@ export const SessionRoutes = lazy(() => sessionID: SessionID.zod, }), ), - validator("json", SessionPrompt.CommandInput.omit({ sessionID: true })), + validator("json", zodObject(SessionPrompt.CommandInput).omit({ sessionID: true })), async (c) => jsonRequest("SessionRoutes.command", c, function* () { const sessionID = c.req.valid("param").sessionID - const body = c.req.valid("json") + const body = c.req.valid("json") as Omit const svc = yield* SessionPrompt.Service return yield* svc.command({ ...body, sessionID }) }), @@ -993,11 +989,11 @@ export const SessionRoutes = lazy(() => sessionID: SessionID.zod, }), ), - validator("json", SessionPrompt.ShellInput.omit({ sessionID: true })), + validator("json", zodObject(SessionPrompt.ShellInput).omit({ sessionID: true })), async (c) => jsonRequest("SessionRoutes.shell", c, function* () { const sessionID = c.req.valid("param").sessionID - const body = c.req.valid("json") + const body = c.req.valid("json") as Omit const svc = yield* SessionPrompt.Service return yield* svc.shell({ ...body, sessionID }) }), @@ -1013,7 +1009,7 @@ export const SessionRoutes = lazy(() => description: "Updated session", content: { "application/json": { - schema: resolver(Session.Info), + schema: resolver(Session.Info.zod), }, }, }, @@ -1026,16 +1022,14 @@ export const SessionRoutes = lazy(() => sessionID: SessionID.zod, }), ), - validator("json", SessionRevert.RevertInput.omit({ sessionID: true })), + validator("json", zodObject(SessionRevert.RevertInput).omit({ sessionID: true })), async (c) => { const sessionID = c.req.valid("param").sessionID - log.info("revert", c.req.valid("json")) + const body = c.req.valid("json") as Omit + log.info("revert", body) return jsonRequest("SessionRoutes.revert", c, function* () { const svc = yield* SessionRevert.Service - return yield* svc.revert({ - sessionID, - ...c.req.valid("json"), - }) + return yield* svc.revert({ sessionID, ...body }) }) }, ) @@ -1050,7 +1044,7 @@ export const SessionRoutes = lazy(() => description: "Updated session", content: { "application/json": { - schema: resolver(Session.Info), + schema: resolver(Session.Info.zod), }, }, }, diff --git a/packages/opencode/src/session/message.ts b/packages/opencode/src/session/message.ts index ced04b8e9d..b1b2453431 100644 --- a/packages/opencode/src/session/message.ts +++ b/packages/opencode/src/session/message.ts @@ -1,191 +1,192 @@ -import z from "zod" +import { Schema } from "effect" import { SessionID } from "./schema" import { ModelID, ProviderID } from "../provider/schema" -import { NamedError } from "@opencode-ai/shared/util/error" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" +import { namedSchemaError } from "@/util/named-schema-error" -export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({})) -export const AuthError = NamedError.create( - "ProviderAuthError", - z.object({ - providerID: z.string(), - message: z.string(), - }), -) - -export const ToolCall = z - .object({ - state: z.literal("call"), - step: z.number().optional(), - toolCallId: z.string(), - toolName: z.string(), - args: z.custom>(), - }) - .meta({ - ref: "ToolCall", - }) -export type ToolCall = z.infer - -export const ToolPartialCall = z - .object({ - state: z.literal("partial-call"), - step: z.number().optional(), - toolCallId: z.string(), - toolName: z.string(), - args: z.custom>(), - }) - .meta({ - ref: "ToolPartialCall", - }) -export type ToolPartialCall = z.infer - -export const ToolResult = z - .object({ - state: z.literal("result"), - step: z.number().optional(), - toolCallId: z.string(), - toolName: z.string(), - args: z.custom>(), - result: z.string(), - }) - .meta({ - ref: "ToolResult", - }) -export type ToolResult = z.infer - -export const ToolInvocation = z.discriminatedUnion("state", [ToolCall, ToolPartialCall, ToolResult]).meta({ - ref: "ToolInvocation", +export const OutputLengthError = namedSchemaError("MessageOutputLengthError", {}) +export const AuthError = namedSchemaError("ProviderAuthError", { + providerID: Schema.String, + message: Schema.String, }) -export type ToolInvocation = z.infer -export const TextPart = z - .object({ - type: z.literal("text"), - text: z.string(), - }) - .meta({ - ref: "TextPart", - }) -export type TextPart = z.infer +const AuthErrorEffect = Schema.Struct({ + name: Schema.Literal("ProviderAuthError"), + data: Schema.Struct({ + providerID: Schema.String, + message: Schema.String, + }), +}) -export const ReasoningPart = z - .object({ - type: z.literal("reasoning"), - text: z.string(), - providerMetadata: z.record(z.string(), z.any()).optional(), - }) - .meta({ - ref: "ReasoningPart", - }) -export type ReasoningPart = z.infer +const OutputLengthErrorEffect = Schema.Struct({ + name: Schema.Literal("MessageOutputLengthError"), + data: Schema.Struct({}), +}) -export const ToolInvocationPart = z - .object({ - type: z.literal("tool-invocation"), - toolInvocation: ToolInvocation, - }) - .meta({ - ref: "ToolInvocationPart", - }) -export type ToolInvocationPart = z.infer +const UnknownErrorEffect = Schema.Struct({ + name: Schema.Literal("UnknownError"), + data: Schema.Struct({ + message: Schema.String, + }), +}) -export const SourceUrlPart = z - .object({ - type: z.literal("source-url"), - sourceId: z.string(), - url: z.string(), - title: z.string().optional(), - providerMetadata: z.record(z.string(), z.any()).optional(), - }) - .meta({ - ref: "SourceUrlPart", - }) -export type SourceUrlPart = z.infer +export const ToolCall = Schema.Struct({ + state: Schema.Literal("call"), + step: Schema.optional(Schema.Number), + toolCallId: Schema.String, + toolName: Schema.String, + args: Schema.Unknown, +}) + .annotate({ identifier: "ToolCall" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolCall = Schema.Schema.Type -export const FilePart = z - .object({ - type: z.literal("file"), - mediaType: z.string(), - filename: z.string().optional(), - url: z.string(), - }) - .meta({ - ref: "FilePart", - }) -export type FilePart = z.infer +export const ToolPartialCall = Schema.Struct({ + state: Schema.Literal("partial-call"), + step: Schema.optional(Schema.Number), + toolCallId: Schema.String, + toolName: Schema.String, + args: Schema.Unknown, +}) + .annotate({ identifier: "ToolPartialCall" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolPartialCall = Schema.Schema.Type -export const StepStartPart = z - .object({ - type: z.literal("step-start"), - }) - .meta({ - ref: "StepStartPart", - }) -export type StepStartPart = z.infer +export const ToolResult = Schema.Struct({ + state: Schema.Literal("result"), + step: Schema.optional(Schema.Number), + toolCallId: Schema.String, + toolName: Schema.String, + args: Schema.Unknown, + result: Schema.String, +}) + .annotate({ identifier: "ToolResult" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolResult = Schema.Schema.Type -export const MessagePart = z - .discriminatedUnion("type", [TextPart, ReasoningPart, ToolInvocationPart, SourceUrlPart, FilePart, StepStartPart]) - .meta({ - ref: "MessagePart", - }) -export type MessagePart = z.infer +export const ToolInvocation = Schema.Union([ToolCall, ToolPartialCall, ToolResult]) + .annotate({ identifier: "ToolInvocation", discriminator: "state" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolInvocation = Schema.Schema.Type -export const Info = z - .object({ - id: z.string(), - role: z.enum(["user", "assistant"]), - parts: z.array(MessagePart), - metadata: z - .object({ - time: z.object({ - created: z.number(), - completed: z.number().optional(), +export const TextPart = Schema.Struct({ + type: Schema.Literal("text"), + text: Schema.String, +}) + .annotate({ identifier: "TextPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type TextPart = Schema.Schema.Type + +export const ReasoningPart = Schema.Struct({ + type: Schema.Literal("reasoning"), + text: Schema.String, + providerMetadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), +}) + .annotate({ identifier: "ReasoningPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ReasoningPart = Schema.Schema.Type + +export const ToolInvocationPart = Schema.Struct({ + type: Schema.Literal("tool-invocation"), + toolInvocation: ToolInvocation, +}) + .annotate({ identifier: "ToolInvocationPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolInvocationPart = Schema.Schema.Type + +export const SourceUrlPart = Schema.Struct({ + type: Schema.Literal("source-url"), + sourceId: Schema.String, + url: Schema.String, + title: Schema.optional(Schema.String), + providerMetadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), +}) + .annotate({ identifier: "SourceUrlPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type SourceUrlPart = Schema.Schema.Type + +export const FilePart = Schema.Struct({ + type: Schema.Literal("file"), + mediaType: Schema.String, + filename: Schema.optional(Schema.String), + url: Schema.String, +}) + .annotate({ identifier: "FilePart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type FilePart = Schema.Schema.Type + +export const StepStartPart = Schema.Struct({ + type: Schema.Literal("step-start"), +}) + .annotate({ identifier: "StepStartPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type StepStartPart = Schema.Schema.Type + +export const MessagePart = Schema.Union([ + TextPart, + ReasoningPart, + ToolInvocationPart, + SourceUrlPart, + FilePart, + StepStartPart, +]) + .annotate({ identifier: "MessagePart", discriminator: "type" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type MessagePart = Schema.Schema.Type + +export const Info = Schema.Struct({ + id: Schema.String, + role: Schema.Literals(["user", "assistant"]), + parts: Schema.Array(MessagePart), + metadata: Schema.Struct({ + time: Schema.Struct({ + created: Schema.Number, + completed: Schema.optional(Schema.Number), + }), + error: Schema.optional(Schema.Union([AuthErrorEffect, UnknownErrorEffect, OutputLengthErrorEffect])), + sessionID: SessionID, + tool: Schema.Record( + Schema.String, + Schema.StructWithRest( + Schema.Struct({ + title: Schema.String, + snapshot: Schema.optional(Schema.String), + time: Schema.Struct({ + start: Schema.Number, + end: Schema.Number, + }), }), - error: z - .discriminatedUnion("name", [AuthError.Schema, NamedError.Unknown.Schema, OutputLengthError.Schema]) - .optional(), - sessionID: SessionID.zod, - tool: z.record( - z.string(), - z - .object({ - title: z.string(), - snapshot: z.string().optional(), - time: z.object({ - start: z.number(), - end: z.number(), - }), - }) - .catchall(z.any()), - ), - assistant: z - .object({ - system: z.string().array(), - modelID: ModelID.zod, - providerID: ProviderID.zod, - path: z.object({ - cwd: z.string(), - root: z.string(), - }), - cost: z.number(), - summary: z.boolean().optional(), - tokens: z.object({ - input: z.number(), - output: z.number(), - reasoning: z.number(), - cache: z.object({ - read: z.number(), - write: z.number(), - }), - }), - }) - .optional(), - snapshot: z.string().optional(), - }) - .meta({ ref: "MessageMetadata" }), - }) - .meta({ - ref: "Message", - }) -export type Info = z.infer + [Schema.Record(Schema.String, Schema.Unknown)], + ), + ), + assistant: Schema.optional( + Schema.Struct({ + system: Schema.Array(Schema.String), + modelID: ModelID, + providerID: ProviderID, + path: Schema.Struct({ + cwd: Schema.String, + root: Schema.String, + }), + cost: Schema.Number, + summary: Schema.optional(Schema.Boolean), + tokens: Schema.Struct({ + input: Schema.Number, + output: Schema.Number, + reasoning: Schema.Number, + cache: Schema.Struct({ + read: Schema.Number, + write: Schema.Number, + }), + }), + }), + ), + snapshot: Schema.optional(Schema.String), + }).annotate({ identifier: "MessageMetadata" }), +}) + .annotate({ identifier: "Message" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Info = Schema.Schema.Type export * as Message from "./message" diff --git a/packages/opencode/src/session/projectors.ts b/packages/opencode/src/session/projectors.ts index fb8354dda1..b55f9dcc7a 100644 --- a/packages/opencode/src/session/projectors.ts +++ b/packages/opencode/src/session/projectors.ts @@ -62,7 +62,7 @@ export function toPartialRow(info: DeepPartial) { export default [ SyncEvent.project(Session.Event.Created, (db, data) => { - db.insert(SessionTable).values(Session.toRow(data.info)).run() + db.insert(SessionTable).values(Session.toRow(data.info as Session.Info)).run() }), SyncEvent.project(Session.Event.Updated, (db, data) => { diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 508c72cc8f..0f48eb64ec 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -43,7 +43,9 @@ import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Truncate } from "@/tool" import { decodeDataUrl } from "@/util/data-url" import { Process } from "@/util" -import { Cause, Effect, Exit, Layer, Option, Scope, Context } from "effect" +import { Cause, Effect, Exit, Layer, Option, Scope, Context, Schema } from "effect" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" import { EffectLogger } from "@/effect" import { InstanceState } from "@/effect" import { TaskTool, type TaskPromptOps } from "@/tool/task" @@ -69,7 +71,7 @@ const elog = EffectLogger.create({ service: "session.prompt" }) export interface Interface { readonly cancel: (sessionID: SessionID) => Effect.Effect readonly prompt: (input: PromptInput) => Effect.Effect - readonly loop: (input: z.infer) => Effect.Effect + readonly loop: (input: LoopInput) => Effect.Effect readonly shell: (input: ShellInput) => Effect.Effect readonly command: (input: CommandInput) => Effect.Effect readonly resolvePromptParts: (template: string) => Effect.Effect @@ -1532,9 +1534,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the }, ) - const loop: (input: z.infer) => Effect.Effect = Effect.fn( - "SessionPrompt.loop", - )(function* (input: z.infer) { + const loop: (input: LoopInput) => Effect.Effect = Effect.fn("SessionPrompt.loop")(function* ( + input: LoopInput, + ) { return yield* state.ensureRunning(input.sessionID, lastAssistant(input.sessionID), runLoop(input.sessionID)) }) @@ -1701,91 +1703,88 @@ export const defaultLayer = Layer.suspend(() => ), ), ) -export const PromptInput = z.object({ - sessionID: SessionID.zod, - messageID: MessageID.zod.optional(), - model: z - .object({ - providerID: ProviderID.zod, - modelID: ModelID.zod, - }) - .optional(), - agent: z.string().optional(), - noReply: z.boolean().optional(), - tools: z - .record(z.string(), z.boolean()) - .optional() - .describe("@deprecated tools and permissions have been merged, you can set permissions on the session itself now"), - format: MessageV2.Format.zod.optional(), - system: z.string().optional(), - variant: z.string().optional(), - parts: z.array( - z.discriminatedUnion("type", [ - MessageV2.TextPartInput.zod as unknown as z.ZodObject, - MessageV2.FilePartInput.zod as unknown as z.ZodObject, - MessageV2.AgentPartInput.zod as unknown as z.ZodObject, - MessageV2.SubtaskPartInput.zod as unknown as z.ZodObject, - ]), - ), +const ModelRef = Schema.Struct({ + providerID: ProviderID, + modelID: ModelID, }) + +export const PromptInput = Schema.Struct({ + sessionID: SessionID, + messageID: Schema.optional(MessageID), + model: Schema.optional(ModelRef), + agent: Schema.optional(Schema.String), + noReply: Schema.optional(Schema.Boolean), + tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)).annotate({ + description: + "@deprecated tools and permissions have been merged, you can set permissions on the session itself now", + }), + format: Schema.optional(MessageV2.Format), + system: Schema.optional(Schema.String), + variant: Schema.optional(Schema.String), + parts: Schema.Array( + Schema.Union([ + MessageV2.TextPartInput, + MessageV2.FilePartInput, + MessageV2.AgentPartInput, + MessageV2.SubtaskPartInput, + ]).annotate({ discriminator: "type" }), + ), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) // `z.discriminatedUnion` erases the discriminated members' shapes back to -// `{}` because the derived `.zod` on each input is typed as an opaque -// `z.ZodType`. Restore the precise `parts` type from the exported Schema -// input types so callers see a proper tagged union. +// `{}` when walked from the generic `z.ZodType` input. Restore the precise +// `parts` type from the exported Schema input types so callers see a proper +// tagged union. type PartInputUnion = | MessageV2.TextPartInput | MessageV2.FilePartInput | MessageV2.AgentPartInput | MessageV2.SubtaskPartInput -export type PromptInput = Omit, "parts"> & { +export type PromptInput = Omit, "parts"> & { parts: PartInputUnion[] } -export const LoopInput = z.object({ - sessionID: SessionID.zod, -}) +export class LoopInput extends Schema.Class("SessionPrompt.LoopInput")({ + sessionID: SessionID, +}) { + static readonly zod = zod(this) +} -export const ShellInput = z.object({ - sessionID: SessionID.zod, - messageID: MessageID.zod.optional(), - agent: z.string(), - model: z - .object({ - providerID: ProviderID.zod, - modelID: ModelID.zod, - }) - .optional(), - command: z.string(), -}) -export type ShellInput = z.infer +export const ShellInput = Schema.Struct({ + sessionID: SessionID, + messageID: Schema.optional(MessageID), + agent: Schema.String, + model: Schema.optional(ModelRef), + command: Schema.String, +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ShellInput = Schema.Schema.Type -export const CommandInput = z.object({ - messageID: MessageID.zod.optional(), - sessionID: SessionID.zod, - agent: z.string().optional(), - model: z.string().optional(), - arguments: z.string(), - command: z.string(), - variant: z.string().optional(), - // Inlined (no `.meta({ ref })`) to keep the original SDK output — the +export const CommandInput = Schema.Struct({ + messageID: Schema.optional(MessageID), + sessionID: SessionID, + agent: Schema.optional(Schema.String), + model: Schema.optional(Schema.String), + arguments: Schema.String, + command: Schema.String, + variant: Schema.optional(Schema.String), + // Inlined (no identifier annotation) to keep the original SDK output — the // PromptInput call site below references FilePartInput by ref via the // Schema export in message-v2.ts. - parts: z - .array( - z.discriminatedUnion("type", [ - z.object({ - id: PartID.zod.optional(), - type: z.literal("file"), - mime: z.string(), - filename: z.string().optional(), - url: z.string(), - source: MessageV2.FilePartSource.zod.optional(), + parts: Schema.optional( + Schema.Array( + Schema.Union([ + Schema.Struct({ + id: Schema.optional(PartID), + type: Schema.Literal("file"), + mime: Schema.String, + filename: Schema.optional(Schema.String), + url: Schema.String, + source: Schema.optional(MessageV2.FilePartSource), }), - ]), - ) - .optional(), -}) -export type CommandInput = z.infer + ]).annotate({ discriminator: "type" }), + ), + ), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export type CommandInput = Schema.Schema.Type /** @internal Exported for testing */ export function createStructuredOutputTool(input: { diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts index c7e5220f12..e1db26510d 100644 --- a/packages/opencode/src/session/revert.ts +++ b/packages/opencode/src/session/revert.ts @@ -1,10 +1,11 @@ -import z from "zod" -import { Effect, Layer, Context } from "effect" +import { Effect, Layer, Context, Schema } from "effect" import { Bus } from "../bus" import { Snapshot } from "../snapshot" import { Storage } from "@/storage" import { SyncEvent } from "../sync" import { Log } from "../util" +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" @@ -13,12 +14,12 @@ import { SessionSummary } from "./summary" const log = Log.create({ service: "session.revert" }) -export const RevertInput = z.object({ - sessionID: SessionID.zod, - messageID: MessageID.zod, - partID: PartID.zod.optional(), -}) -export type RevertInput = z.infer +export const RevertInput = Schema.Struct({ + sessionID: SessionID, + messageID: MessageID, + partID: Schema.optional(PartID), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export type RevertInput = Schema.Schema.Type export interface Interface { readonly revert: (input: RevertInput) => Effect.Effect diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index a7607798ba..d2bdbccb7d 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -27,7 +27,9 @@ import { SessionID, MessageID, PartID } from "./schema" import type { Provider } from "@/provider" import { Permission } from "@/permission" import { Global } from "@/global" -import { Effect, Layer, Option, Context } from "effect" +import { Effect, Layer, Option, Context, Schema, Types } from "effect" +import { zod, zodObject } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" const log = Log.create({ service: "session" }) @@ -114,91 +116,104 @@ function getForkedTitle(title: string): string { return `${title} (fork #1)` } -export const Info = z - .object({ - id: SessionID.zod, - slug: z.string(), - projectID: ProjectID.zod, - workspaceID: WorkspaceID.zod.optional(), - directory: z.string(), - parentID: SessionID.zod.optional(), - summary: z - .object({ - additions: z.number(), - deletions: z.number(), - files: z.number(), - diffs: Snapshot.FileDiff.zod.array().optional(), - }) - .optional(), - share: z - .object({ - url: z.string(), - }) - .optional(), - title: z.string(), - version: z.string(), - time: z.object({ - created: z.number(), - updated: z.number(), - compacting: z.number().optional(), - archived: z.number().optional(), - }), - permission: Permission.Ruleset.zod.optional(), - revert: z - .object({ - messageID: MessageID.zod, - partID: PartID.zod.optional(), - snapshot: z.string().optional(), - diff: z.string().optional(), - }) - .optional(), - }) - .meta({ - ref: "Session", - }) -export type Info = z.output - -export const ProjectInfo = z - .object({ - id: ProjectID.zod, - name: z.string().optional(), - worktree: z.string(), - }) - .meta({ - ref: "ProjectSummary", - }) -export type ProjectInfo = z.output - -export const GlobalInfo = Info.extend({ - project: ProjectInfo.nullable(), -}).meta({ - ref: "GlobalSession", +const Summary = Schema.Struct({ + additions: Schema.Number, + deletions: Schema.Number, + files: Schema.Number, + diffs: Schema.optional(Schema.Array(Snapshot.FileDiff)), }) -export type GlobalInfo = z.output -export const CreateInput = z - .object({ - parentID: SessionID.zod.optional(), - title: z.string().optional(), - permission: Info.shape.permission, - workspaceID: WorkspaceID.zod.optional(), - }) - .optional() -export type CreateInput = z.output - -export const ForkInput = z.object({ sessionID: SessionID.zod, messageID: MessageID.zod.optional() }) -export const GetInput = SessionID.zod -export const ChildrenInput = SessionID.zod -export const RemoveInput = SessionID.zod -export const SetTitleInput = z.object({ sessionID: SessionID.zod, title: z.string() }) -export const SetArchivedInput = z.object({ sessionID: SessionID.zod, time: z.number().optional() }) -export const SetPermissionInput = z.object({ sessionID: SessionID.zod, permission: Permission.Ruleset.zod }) -export const SetRevertInput = z.object({ - sessionID: SessionID.zod, - revert: Info.shape.revert, - summary: Info.shape.summary, +const Share = Schema.Struct({ + url: Schema.String, }) -export const MessagesInput = z.object({ sessionID: SessionID.zod, limit: z.number().optional() }) + +const Time = Schema.Struct({ + created: Schema.Number, + updated: Schema.Number, + compacting: Schema.optional(Schema.Number), + archived: Schema.optional(Schema.Number), +}) + +const Revert = Schema.Struct({ + messageID: MessageID, + partID: Schema.optional(PartID), + snapshot: Schema.optional(Schema.String), + diff: Schema.optional(Schema.String), +}) + +export const Info = Schema.Struct({ + id: SessionID, + slug: Schema.String, + projectID: ProjectID, + workspaceID: Schema.optional(WorkspaceID), + directory: Schema.String, + parentID: Schema.optional(SessionID), + summary: Schema.optional(Summary), + share: Schema.optional(Share), + title: Schema.String, + version: Schema.String, + time: Time, + permission: Schema.optional(Permission.Ruleset), + revert: Schema.optional(Revert), +}) + .annotate({ identifier: "Session" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Info = Types.DeepMutable> + +export const ProjectInfo = Schema.Struct({ + id: ProjectID, + name: Schema.optional(Schema.String), + worktree: Schema.String, +}) + .annotate({ identifier: "ProjectSummary" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ProjectInfo = Types.DeepMutable> + +export const GlobalInfo = Schema.Struct({ + ...Info.fields, + project: Schema.NullOr(ProjectInfo), +}) + .annotate({ identifier: "GlobalSession" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type GlobalInfo = Types.DeepMutable> + +export const CreateInput = Schema.optional( + Schema.Struct({ + parentID: Schema.optional(SessionID), + title: Schema.optional(Schema.String), + permission: Schema.optional(Permission.Ruleset), + workspaceID: Schema.optional(WorkspaceID), + }), +).pipe(withStatics((s) => ({ zod: zod(s) }))) +export type CreateInput = Types.DeepMutable> + +export const ForkInput = Schema.Struct({ + sessionID: SessionID, + messageID: Schema.optional(MessageID), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export const GetInput = SessionID +export const ChildrenInput = SessionID +export const RemoveInput = SessionID +export const SetTitleInput = Schema.Struct({ sessionID: SessionID, title: Schema.String }).pipe( + withStatics((s) => ({ zod: zod(s) })), +) +export const SetArchivedInput = Schema.Struct({ + sessionID: SessionID, + time: Schema.optional(Schema.Number), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export const SetPermissionInput = Schema.Struct({ + sessionID: SessionID, + permission: Permission.Ruleset, +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export const SetRevertInput = Schema.Struct({ + sessionID: SessionID, + revert: Schema.optional(Revert), + summary: Schema.optional(Summary), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export const MessagesInput = Schema.Struct({ + sessionID: SessionID, + limit: Schema.optional(Schema.Number), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) export const Event = { Created: SyncEvent.define({ @@ -207,7 +222,7 @@ export const Event = { aggregate: "sessionID", schema: z.object({ sessionID: SessionID.zod, - info: Info, + info: Info.zod, }), }), Updated: SyncEvent.define({ @@ -216,14 +231,14 @@ export const Event = { aggregate: "sessionID", schema: z.object({ sessionID: SessionID.zod, - info: updateSchema(Info).extend({ - share: updateSchema(Info.shape.share.unwrap()).optional(), - time: updateSchema(Info.shape.time).optional(), + info: updateSchema(zodObject(Info)).extend({ + share: updateSchema(zodObject(Share)).optional(), + time: updateSchema(zodObject(Time)).optional(), }), }), busSchema: z.object({ sessionID: SessionID.zod, - info: Info, + info: Info.zod, }), }), Deleted: SyncEvent.define({ @@ -232,7 +247,7 @@ export const Event = { aggregate: "sessionID", schema: z.object({ sessionID: SessionID.zod, - info: Info, + info: Info.zod, }), }), Diff: BusEvent.define( diff --git a/packages/opencode/src/session/status.ts b/packages/opencode/src/session/status.ts index 7f46c70a8a..b9b9fd7e74 100644 --- a/packages/opencode/src/session/status.ts +++ b/packages/opencode/src/session/status.ts @@ -2,35 +2,35 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" import { InstanceState } from "@/effect" import { SessionID } from "./schema" -import { Effect, Layer, Context } from "effect" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" +import { Effect, Layer, Context, Schema } from "effect" import z from "zod" -export const Info = z - .union([ - z.object({ - type: z.literal("idle"), - }), - z.object({ - type: z.literal("retry"), - attempt: z.number(), - message: z.string(), - next: z.number(), - }), - z.object({ - type: z.literal("busy"), - }), - ]) - .meta({ - ref: "SessionStatus", - }) -export type Info = z.infer +export const Info = Schema.Union([ + Schema.Struct({ + type: Schema.Literal("idle"), + }), + Schema.Struct({ + type: Schema.Literal("retry"), + attempt: Schema.Number, + message: Schema.String, + next: Schema.Number, + }), + Schema.Struct({ + type: Schema.Literal("busy"), + }), +]) + .annotate({ identifier: "SessionStatus" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Info = Schema.Schema.Type export const Event = { Status: BusEvent.define( "session.status", z.object({ sessionID: SessionID.zod, - status: Info, + status: Info.zod, }), ), // deprecated diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index 70b3102f6e..04a24d2c2b 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -1,8 +1,9 @@ -import z from "zod" -import { Effect, Layer, Context } from "effect" +import { Effect, Layer, Context, Schema } from "effect" import { Bus } from "@/bus" import { Snapshot } from "@/snapshot" import { Storage } from "@/storage" +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" @@ -155,9 +156,10 @@ export const defaultLayer = Layer.suspend(() => ), ) -export const DiffInput = z.object({ - sessionID: SessionID.zod, - messageID: MessageID.zod.optional(), -}) +export const DiffInput = Schema.Struct({ + sessionID: SessionID, + messageID: Schema.optional(MessageID), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export type DiffInput = Schema.Schema.Type export * as SessionSummary from "./summary" diff --git a/packages/opencode/src/session/todo.ts b/packages/opencode/src/session/todo.ts index 4840f86a3d..257b586ed7 100644 --- a/packages/opencode/src/session/todo.ts +++ b/packages/opencode/src/session/todo.ts @@ -1,26 +1,30 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" import { SessionID } from "./schema" -import { Effect, Layer, Context } from "effect" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" +import { Effect, Layer, Context, Schema } from "effect" import z from "zod" import { Database, eq, asc } from "../storage" import { TodoTable } from "./session.sql" -export const Info = z - .object({ - content: z.string().describe("Brief description of the task"), - status: z.string().describe("Current status of the task: pending, in_progress, completed, cancelled"), - priority: z.string().describe("Priority level of the task: high, medium, low"), - }) - .meta({ ref: "Todo" }) -export type Info = z.infer +export const Info = Schema.Struct({ + content: Schema.String.annotate({ description: "Brief description of the task" }), + status: Schema.String.annotate({ + description: "Current status of the task: pending, in_progress, completed, cancelled", + }), + priority: Schema.String.annotate({ description: "Priority level of the task: high, medium, low" }), +}) + .annotate({ identifier: "Todo" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Info = Schema.Schema.Type export const Event = { Updated: BusEvent.define( "todo.updated", z.object({ sessionID: SessionID.zod, - todos: z.array(Info), + todos: z.array(Info.zod), }), ), } diff --git a/packages/opencode/src/tool/todo.ts b/packages/opencode/src/tool/todo.ts index 5090f17a7c..c08fb04119 100644 --- a/packages/opencode/src/tool/todo.ts +++ b/packages/opencode/src/tool/todo.ts @@ -4,8 +4,21 @@ import * as Tool from "./tool" import DESCRIPTION_WRITE from "./todowrite.txt" import { Todo } from "../session/todo" +// Parameters are kept inline rather than derived from Todo.Info because +// Tool.define requires z.ZodObject-typed parameters for execute() inference, +// and zodObject(Todo.Info) returns ZodObject — reaching into .shape would +// erase field types. Tool schemas migrate to Effect Schema as a separate slice +// per specs/effect/schema.md. const parameters = z.object({ - todos: z.array(z.object(Todo.Info.shape)).describe("The updated todo list"), + todos: z + .array( + z.object({ + content: z.string().describe("Brief description of the task"), + status: z.string().describe("Current status of the task: pending, in_progress, completed, cancelled"), + priority: z.string().describe("Priority level of the task: high, medium, low"), + }), + ) + .describe("The updated todo list"), }) type Metadata = { diff --git a/packages/opencode/src/util/effect-zod.ts b/packages/opencode/src/util/effect-zod.ts index f6d2c5e5c0..edbbf4d542 100644 --- a/packages/opencode/src/util/effect-zod.ts +++ b/packages/opencode/src/util/effect-zod.ts @@ -22,6 +22,33 @@ export function zod(schema: S): z.ZodType> } +/** + * Derive a Zod value from an Effect Schema (or a Schema-backed export with a + * `.zod` static) and narrow the result to `z.ZodObject` so `.shape`, + * `.omit`, `.extend`, and friends are accessible. + * + * The `zod()` walker returns `z.ZodType` because not every AST node decodes + * to an object; this helper keeps the "I started from a `Schema.Struct`" cast + * in one place instead of sprinkling `as unknown as z.ZodObject` across + * call sites. + * + * The return is intentionally loose — carrying Schema field types through the + * mapped `.omit()` / `.extend()` surface triggers brand-intersection + * explosions for branded primitives (`string & Brand<"SessionID">` extends + * `object` via the brand and gets walked into the prototype by `DeepPartial`, + * `updateSchema`, etc.), and zod's inference through `z.ZodType` + * wrappers also can't reconstruct `T` cleanly. Consumers that care about the + * post-`.omit()` shape should cast `c.req.valid(...)` to the expected type. + */ +export function zodObject(schema: S): z.ZodObject { + const derived: z.ZodTypeAny = "zod" in schema && isZodType(schema.zod) ? schema.zod : walk(schema.ast) + return derived as unknown as z.ZodObject +} + +function isZodType(value: unknown): value is z.ZodTypeAny { + return typeof value === "object" && value !== null && "_zod" in value +} + function walk(ast: SchemaAST.AST): z.ZodTypeAny { const cached = walkCache.get(ast) if (cached) return cached diff --git a/packages/opencode/test/server/global-session-list.test.ts b/packages/opencode/test/server/global-session-list.test.ts index d0f71b8fd3..03b1a0346a 100644 --- a/packages/opencode/test/server/global-session-list.test.ts +++ b/packages/opencode/test/server/global-session-list.test.ts @@ -18,7 +18,7 @@ const svc = { create(input?: SessionNs.CreateInput) { return run(SessionNs.Service.use((svc) => svc.create(input))) }, - setArchived(input: z.output) { + setArchived(input: z.output) { return run(SessionNs.Service.use((svc) => svc.setArchived(input))) }, } diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 037613d469..4fe9c15511 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -38,7 +38,7 @@ const svc = { create(input?: SessionNs.CreateInput) { return run(SessionNs.Service.use((svc) => svc.create(input))) }, - messages(input: z.output) { + messages(input: z.output) { return run(SessionNs.Service.use((svc) => svc.messages(input))) }, updateMessage(msg: T) { diff --git a/packages/opencode/test/session/schema-decoding.test.ts b/packages/opencode/test/session/schema-decoding.test.ts new file mode 100644 index 0000000000..5894b26154 --- /dev/null +++ b/packages/opencode/test/session/schema-decoding.test.ts @@ -0,0 +1,310 @@ +import { describe, expect, test } from "bun:test" +import { Schema } from "effect" + +import { Session } from "../../src/session" +import { SessionPrompt } from "../../src/session/prompt" +import { SessionRevert } from "../../src/session/revert" +import { SessionStatus } from "../../src/session/status" +import { SessionSummary } from "../../src/session/summary" +import { Todo } from "../../src/session/todo" +import { SessionID, MessageID, PartID } from "../../src/session/schema" +import { ProjectID } from "../../src/project/schema" +import { WorkspaceID } from "../../src/control-plane/schema" + +// Covers the session-domain Effect Schema migration. For each migrated +// schema we assert: +// 1. The Effect decoder (`Schema.decodeUnknownSync`) accepts valid input. +// 2. The derived Zod (`X.zod.parse`) accepts the same input and returns the +// same shape. +// 3. Clearly-invalid input is rejected by both paths. +// +// The point is to lock down the Schema <-> Zod bridge so a future edit to +// any input schema can't silently drop or widen a field on one side. + +// Representative valid IDs — the branded schemas require the right prefix +// (see src/id/id.ts). +const sessionID = SessionID.zod.parse("ses_01J5Y5H0AH4Q4NXJ6P4C3P5V2K") +const sessionIDChild = SessionID.zod.parse("ses_01J5Y5H0AH4Q4NXJ6P4C3P5V2L") +const messageID = MessageID.zod.parse("msg_01J5Y5H0AH4Q4NXJ6P4C3P5V2M") +const partID = PartID.zod.parse("prt_01J5Y5H0AH4Q4NXJ6P4C3P5V2N") +const projectID = ProjectID.zod.parse("proj-alpha") +const workspaceID = WorkspaceID.zod.parse("wrk-primary") + +function decodeUnknown(schema: S) { + const decode = Schema.decodeUnknownSync(schema as any) + return (input: unknown): Schema.Schema.Type => decode(input) as Schema.Schema.Type +} + +describe("Session.Info", () => { + const decode = decodeUnknown(Session.Info) + + test("accepts minimal session", () => { + const input = { + id: sessionID, + slug: "hello", + projectID, + directory: "/tmp/proj", + title: "First session", + version: "0.1.0", + time: { created: 1, updated: 2 }, + } + expect(decode(input)).toEqual(input) + expect(Session.Info.zod.parse(input)).toEqual(input) + }) + + test("round-trips every optional field", () => { + const input = { + id: sessionID, + slug: "fullshape", + projectID, + workspaceID, + directory: "/tmp/proj", + parentID: sessionIDChild, + summary: { + additions: 10, + deletions: 5, + files: 2, + diffs: [{ additions: 1, deletions: 0, file: "a.ts", patch: "--- a/a.ts" }], + }, + share: { url: "https://share.example.com/s/1" }, + title: "Full session", + version: "1.0.0", + time: { created: 100, updated: 200, compacting: 150, archived: 300 }, + permission: [{ action: "allow" as const, pattern: "*", permission: "read" }], + revert: { + messageID, + partID, + snapshot: "snap-1", + diff: "diff-1", + }, + } + expect(decode(input)).toEqual(input) + expect(Session.Info.zod.parse(input)).toEqual(input) + }) + + test("rejects unbranded session id", () => { + const bad = { id: "not-a-session-id" } as unknown + expect(() => decode(bad)).toThrow() + expect(() => Session.Info.zod.parse(bad)).toThrow() + }) + + test("rejects missing required fields", () => { + const bad = { id: sessionID } as unknown + expect(() => decode(bad)).toThrow() + expect(() => Session.Info.zod.parse(bad)).toThrow() + }) +}) + +describe("Session.ProjectInfo", () => { + const decode = decodeUnknown(Session.ProjectInfo) + + test("accepts with and without optional name", () => { + const noName = { id: projectID, worktree: "/tmp/wt" } + const withName = { ...noName, name: "alpha" } + expect(decode(noName)).toEqual(noName) + expect(decode(withName)).toEqual(withName) + expect(Session.ProjectInfo.zod.parse(noName)).toEqual(noName) + expect(Session.ProjectInfo.zod.parse(withName)).toEqual(withName) + }) +}) + +describe("Session.GlobalInfo", () => { + const decode = decodeUnknown(Session.GlobalInfo) + + test("accepts null project", () => { + const input = { + id: sessionID, + slug: "global", + projectID, + directory: "/tmp/proj", + title: "global", + version: "0", + time: { created: 0, updated: 0 }, + project: null, + } + expect(decode(input)).toEqual(input) + expect(Session.GlobalInfo.zod.parse(input)).toEqual(input) + }) + + test("accepts populated project", () => { + const input = { + id: sessionID, + slug: "global", + projectID, + directory: "/tmp/proj", + title: "global", + version: "0", + time: { created: 0, updated: 0 }, + project: { id: projectID, worktree: "/tmp/wt", name: "alpha" }, + } + expect(decode(input)).toEqual(input) + expect(Session.GlobalInfo.zod.parse(input)).toEqual(input) + }) +}) + +describe("Session input schemas", () => { + test("CreateInput accepts undefined and populated forms", () => { + const decode = decodeUnknown(Session.CreateInput) + expect(decode(undefined)).toBeUndefined() + expect(Session.CreateInput.zod.parse(undefined)).toBeUndefined() + + const populated = { + parentID: sessionID, + title: "child", + permission: [{ action: "ask" as const, pattern: "*", permission: "bash" }], + workspaceID, + } + expect(decode(populated)).toEqual(populated) + expect(Session.CreateInput.zod.parse(populated)).toEqual(populated) + }) + + test("ForkInput round-trips", () => { + const decode = decodeUnknown(Session.ForkInput) + const input = { sessionID, messageID } + expect(decode(input)).toEqual(input) + expect(Session.ForkInput.zod.parse(input)).toEqual(input) + // messageID is optional + const bare = { sessionID } + expect(decode(bare)).toEqual(bare) + expect(Session.ForkInput.zod.parse(bare)).toEqual(bare) + }) + + test("SetTitleInput rejects missing title", () => { + expect(() => decodeUnknown(Session.SetTitleInput)({ sessionID })).toThrow() + expect(() => Session.SetTitleInput.zod.parse({ sessionID })).toThrow() + }) + + test("SetArchivedInput accepts both with and without time", () => { + const decode = decodeUnknown(Session.SetArchivedInput) + expect(decode({ sessionID })).toEqual({ sessionID }) + expect(decode({ sessionID, time: 123 })).toEqual({ sessionID, time: 123 }) + }) + + test("SetPermissionInput requires a ruleset", () => { + const decode = decodeUnknown(Session.SetPermissionInput) + const input = { sessionID, permission: [{ action: "deny" as const, pattern: "*", permission: "write" }] } + expect(decode(input)).toEqual(input) + expect(() => decode({ sessionID })).toThrow() + }) + + test("MessagesInput accepts optional limit", () => { + const decode = decodeUnknown(Session.MessagesInput) + expect(decode({ sessionID })).toEqual({ sessionID }) + expect(decode({ sessionID, limit: 50 })).toEqual({ sessionID, limit: 50 }) + }) +}) + +describe("SessionRevert.RevertInput", () => { + const decode = decodeUnknown(SessionRevert.RevertInput) + + test("messageID is required, partID is optional", () => { + const withPart = { sessionID, messageID, partID } + expect(decode(withPart)).toEqual(withPart) + expect(SessionRevert.RevertInput.zod.parse(withPart)).toEqual(withPart) + + const noPart = { sessionID, messageID } + expect(decode(noPart)).toEqual(noPart) + expect(SessionRevert.RevertInput.zod.parse(noPart)).toEqual(noPart) + + expect(() => decode({ sessionID })).toThrow() + expect(() => SessionRevert.RevertInput.zod.parse({ sessionID })).toThrow() + }) +}) + +describe("SessionSummary.DiffInput", () => { + const decode = decodeUnknown(SessionSummary.DiffInput) + + test("messageID optional", () => { + expect(decode({ sessionID })).toEqual({ sessionID }) + expect(decode({ sessionID, messageID })).toEqual({ sessionID, messageID }) + }) +}) + +describe("SessionStatus.Info", () => { + const decode = decodeUnknown(SessionStatus.Info) + + test("idle / busy discriminators", () => { + expect(decode({ type: "idle" })).toEqual({ type: "idle" }) + expect(decode({ type: "busy" })).toEqual({ type: "busy" }) + expect(SessionStatus.Info.zod.parse({ type: "idle" })).toEqual({ type: "idle" }) + }) + + test("retry carries attempt/message/next", () => { + const input = { type: "retry" as const, attempt: 1, message: "transient", next: 500 } + expect(decode(input)).toEqual(input) + expect(SessionStatus.Info.zod.parse(input)).toEqual(input) + }) + + test("rejects unknown type", () => { + expect(() => decode({ type: "bogus" })).toThrow() + expect(() => SessionStatus.Info.zod.parse({ type: "bogus" })).toThrow() + }) +}) + +describe("Todo.Info", () => { + const decode = decodeUnknown(Todo.Info) + + test("three-field round-trip", () => { + const input = { content: "do a thing", status: "pending", priority: "high" } + expect(decode(input)).toEqual(input) + expect(Todo.Info.zod.parse(input)).toEqual(input) + }) +}) + +describe("SessionPrompt input schemas", () => { + test("LoopInput is just sessionID", () => { + const decode = decodeUnknown(SessionPrompt.LoopInput) + expect(decode({ sessionID })).toEqual({ sessionID }) + expect(SessionPrompt.LoopInput.zod.parse({ sessionID } as unknown)).toEqual({ sessionID }) + }) + + test("ShellInput requires agent + command", () => { + const decode = decodeUnknown(SessionPrompt.ShellInput) + const expected = { sessionID, agent: "build", command: "echo hi" } + const input: unknown = expected + expect(decode(input)).toEqual(expected) + expect(SessionPrompt.ShellInput.zod.parse(input as unknown)).toEqual(expected) + expect(() => decode({ sessionID })).toThrow() + }) + + test("PromptInput accepts a text part and a file part", () => { + const decode = decodeUnknown(SessionPrompt.PromptInput) + const expected = { + sessionID, + parts: [ + { type: "text" as const, text: "hello" }, + { type: "file" as const, mime: "image/png", url: "data:image/png;base64,AAAA" }, + ], + } + const input: unknown = expected + const decoded = decode(input) + expect(decoded.parts).toHaveLength(2) + expect(decoded.parts[0]).toMatchObject({ type: "text", text: "hello" }) + expect(decoded.parts[1]).toMatchObject({ type: "file", mime: "image/png" }) + + const viaZod = SessionPrompt.PromptInput.zod.parse(input) + expect(viaZod.parts).toHaveLength(2) + }) + + test("PromptInput rejects unknown part type", () => { + const decode = decodeUnknown(SessionPrompt.PromptInput) + const bad = { + sessionID, + parts: [{ type: "nonsense", payload: 42 }], + } + expect(() => decode(bad)).toThrow() + expect(() => SessionPrompt.PromptInput.zod.parse(bad)).toThrow() + }) + + test("CommandInput round-trips core fields", () => { + const decode = decodeUnknown(SessionPrompt.CommandInput) + const expected = { + sessionID, + arguments: "--flag", + command: "deploy", + } + const input: unknown = expected + expect(decode(input)).toEqual(expected) + expect(SessionPrompt.CommandInput.zod.parse(input)).toEqual(expected) + }) +}) From 2cd89d64e9c392a59cda5871d5b2c1ef0929ff7e Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 15:31:16 +0000 Subject: [PATCH 03/59] chore: generate --- packages/opencode/src/session/projectors.ts | 4 +- packages/sdk/openapi.json | 50 ++++++++++----------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/packages/opencode/src/session/projectors.ts b/packages/opencode/src/session/projectors.ts index b55f9dcc7a..7b3b0a7f21 100644 --- a/packages/opencode/src/session/projectors.ts +++ b/packages/opencode/src/session/projectors.ts @@ -62,7 +62,9 @@ export function toPartialRow(info: DeepPartial) { export default [ SyncEvent.project(Session.Event.Created, (db, data) => { - db.insert(SessionTable).values(Session.toRow(data.info as Session.Info)).run() + db.insert(SessionTable) + .values(Session.toRow(data.info as Session.Info)) + .run() }), SyncEvent.project(Session.Event.Updated, (db, data) => { diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index d9954d915f..55570d47dd 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -4174,34 +4174,30 @@ "parts": { "type": "array", "items": { - "anyOf": [ - { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^prt.*" - }, - "type": { - "type": "string", - "const": "file" - }, - "mime": { - "type": "string" - }, - "filename": { - "type": "string" - }, - "url": { - "type": "string" - }, - "source": { - "$ref": "#/components/schemas/FilePartSource" - } - }, - "required": ["type", "mime", "url"] + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^prt.*" + }, + "type": { + "type": "string", + "const": "file" + }, + "mime": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "url": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/FilePartSource" } - ] + }, + "required": ["type", "mime", "url"] } } }, From eb7555d3c62a3b3cb61fc87bc124ee7309e9aaab Mon Sep 17 00:00:00 2001 From: opencode Date: Thu, 23 Apr 2026 15:56:49 +0000 Subject: [PATCH 04/59] sync release versions for v1.14.22 --- bun.lock | 32 +++++++++++++------------- packages/app/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/desktop-electron/package.json | 2 +- packages/desktop/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/extensions/zed/extension.toml | 12 +++++----- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/shared/package.json | 2 +- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index addecfff27..06a5cfdb4c 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -83,7 +83,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -117,7 +117,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -144,7 +144,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -168,7 +168,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -192,7 +192,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -225,7 +225,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -269,7 +269,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@opencode-ai/shared": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -298,7 +298,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -314,7 +314,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.14.21", + "version": "1.14.22", "bin": { "opencode": "./bin/opencode", }, @@ -459,7 +459,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@opencode-ai/sdk": "workspace:*", "effect": "catalog:", @@ -494,7 +494,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "cross-spawn": "catalog:", }, @@ -509,7 +509,7 @@ }, "packages/shared": { "name": "@opencode-ai/shared", - "version": "1.14.21", + "version": "1.14.22", "bin": { "opencode": "./bin/opencode", }, @@ -533,7 +533,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -568,7 +568,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -617,7 +617,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index 7572f0b228..0ab5e304a2 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.14.21", + "version": "1.14.22", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 1182f17948..3d37527508 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.14.21", + "version": "1.14.22", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index f79ef3ecb0..70d44b3820 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.14.21", + "version": "1.14.22", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index c3c549004d..55791a3072 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.14.21", + "version": "1.14.22", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index f4c59c1967..4966bf3e63 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 3f5ca841b0..a528a9a3ac 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.14.21", + "version": "1.14.22", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 0e349f81c3..32b63fff13 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.14.21", + "version": "1.14.22", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 77b0ec2002..e4428f0cb2 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.14.21", + "version": "1.14.22", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 5a9d2dc1ac..6449b8df65 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.14.21" +version = "1.14.22" 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.21/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/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.21/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.21/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/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.21/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/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.21/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 925e458cf0..5b06d03685 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.14.21", + "version": "1.14.22", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index cd9c0f0a83..ea8724ceb8 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.21", + "version": "1.14.22", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index d719010661..ee8210ec14 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.14.21", + "version": "1.14.22", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 3d07efef49..62a50b7434 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.14.21", + "version": "1.14.22", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/shared/package.json b/packages/shared/package.json index 117c12b981..48ae0fb78d 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.21", + "version": "1.14.22", "name": "@opencode-ai/shared", "type": "module", "license": "MIT", diff --git a/packages/slack/package.json b/packages/slack/package.json index 2eb380dc74..a3516932ab 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.14.21", + "version": "1.14.22", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index a15918dc85..ba0fb01494 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.14.21", + "version": "1.14.22", "type": "module", "license": "MIT", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index 9924f2e516..818cdf721e 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.14.21", + "version": "1.14.22", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index a43348670b..e238b36702 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.14.21", + "version": "1.14.22", "publisher": "sst-dev", "repository": { "type": "git", From 9df7c78ebe051a3e71ec6aa27e38a4baa0bbb4bc Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Thu, 23 Apr 2026 21:28:54 +0530 Subject: [PATCH 05/59] fix(npm): respect npmrc for version lookups (#24016) --- packages/opencode/src/installation/index.ts | 19 +++-- packages/opencode/src/npm/index.ts | 56 ++++++++----- .../test/installation/installation.test.ts | 46 ++++++++-- packages/opencode/test/npm.test.ts | 84 +++++++++++++++++++ 4 files changed, 171 insertions(+), 34 deletions(-) diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index babde9dc47..e39b14b8f3 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -132,6 +132,15 @@ export const layer: Layer.Layer Effect.succeed({ code: ChildProcessSpawner.ExitCode(1), stdout: "", stderr: "" })), ) + const viewVersion = Effect.fnUntraced(function* (method: "npm" | "pnpm" | "bun", spec: string) { + const args = method === "bun" ? ["pm", "view", spec, "version", "--json"] : ["view", spec, "version", "--json"] + const result = yield* run([method, ...args]) + if (result.code !== 0 || !result.stdout.trim()) { + return yield* new UpgradeFailedError({ stderr: result.stderr || result.stdout || `Failed to resolve ${spec}` }) + } + return yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.String))(result.stdout) + }) + const getBrewFormula = Effect.fnUntraced(function* () { const tapFormula = yield* text(["brew", "list", "--formula", "anomalyco/tap/opencode"]) if (tapFormula.includes("opencode")) return "anomalyco/tap/opencode" @@ -217,15 +226,7 @@ export const layer: Layer.Layer()("NpmInstallFailedError", { @@ -106,7 +108,36 @@ export const layer = Layer.effect( const global = yield* Global.Service const fs = yield* FileSystem.FileSystem const flock = yield* EffectFlock.Service + const spawner = yield* ChildProcessSpawner.ChildProcessSpawner const directory = (pkg: string) => path.join(global.cache, "packages", sanitize(pkg)) + const runView = Effect.fnUntraced( + function* (cmd: string[]) { + const handle = yield* spawner.spawn( + ChildProcess.make(cmd[0], cmd.slice(1), { + extendEnv: true, + }), + ) + const [stdout, stderr] = yield* Effect.all( + [Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))], + { concurrency: 2 }, + ) + const code = yield* handle.exitCode + if (code !== 0 || !stdout.trim()) { + return yield* Effect.fail(stderr || stdout || `Failed to run ${cmd.join(" ")}`) + } + return yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.String))(stdout) + }, + Effect.scoped, + ) + const viewLatestVersion = Effect.fnUntraced(function* (pkg: string) { + return yield* runView(["npm", "view", pkg, "dist-tags.latest", "--json"]).pipe( + Effect.catch(() => + runView(["pnpm", "view", pkg, "dist-tags.latest", "--json"]).pipe( + Effect.catch(() => runView(["bun", "pm", "view", pkg, "dist-tags.latest", "--json"])), + ), + ), + ) + }) const reify = (input: { dir: string; add?: string[] }) => Effect.gen(function* () { yield* flock.acquire(`npm-install:${input.dir}`) @@ -143,29 +174,15 @@ export const layer = Layer.effect( ) const outdated = Effect.fn("Npm.outdated")(function* (pkg: string, cachedVersion: string) { - const response = yield* Effect.tryPromise({ - try: () => fetch(`https://registry.npmjs.org/${pkg}`), - catch: () => undefined, - }).pipe(Effect.orElseSucceed(() => undefined)) - - if (!response || !response.ok) { - return false - } - - const data = yield* Effect.tryPromise({ - try: () => response.json() as Promise<{ "dist-tags"?: { latest?: string } }>, - catch: () => undefined, - }).pipe(Effect.orElseSucceed(() => undefined)) - - const latestVersion = data?.["dist-tags"]?.latest - if (!latestVersion) { + const latestVersion = yield* viewLatestVersion(pkg).pipe(Effect.option) + if (Option.isNone(latestVersion)) { return false } const range = /[\s^~*xX<>|=]/.test(cachedVersion) - if (range) return !semver.satisfies(latestVersion, cachedVersion) + if (range) return !semver.satisfies(latestVersion.value, cachedVersion) - return semver.lt(cachedVersion, latestVersion) + return semver.lt(cachedVersion, latestVersion.value) }) const add = Effect.fn("Npm.add")(function* (pkg: string) { @@ -304,6 +321,7 @@ export const defaultLayer = layer.pipe( Layer.provide(AppFileSystem.layer), Layer.provide(Global.layer), Layer.provide(NodeFileSystem.layer), + Layer.provide(CrossSpawnSpawner.defaultLayer), ) const { runPromise } = makeRuntime(Service, defaultLayer) diff --git a/packages/opencode/test/installation/installation.test.ts b/packages/opencode/test/installation/installation.test.ts index 2b04c38585..0d3e92989d 100644 --- a/packages/opencode/test/installation/installation.test.ts +++ b/packages/opencode/test/installation/installation.test.ts @@ -3,6 +3,7 @@ import { Effect, Layer, Stream } from "effect" import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { Installation } from "../../src/installation" +import { InstallationChannel } from "../../src/installation/version" const encoder = new TextEncoder() @@ -68,11 +69,15 @@ describe("installation", () => { expect(result).toBe("4.0.0-beta.1") }) - test("reads npm registry versions", async () => { + test("reads npm versions via npm view", async () => { + const calls: string[][] = [] const layer = testLayer( - () => jsonResponse({ version: "1.5.0" }), + () => { + throw new Error("unexpected http request") + }, (cmd, args) => { - if (cmd === "npm" && args.includes("registry")) return "https://registry.npmjs.org\n" + calls.push([cmd, ...args]) + if (cmd === "npm" && args[0] === "view") return '"1.5.0"\n' return "" }, ) @@ -81,18 +86,47 @@ describe("installation", () => { Installation.Service.use((svc) => svc.latest("npm")).pipe(Effect.provide(layer)), ) expect(result).toBe("1.5.0") + expect(calls).toContainEqual(["npm", "view", `opencode-ai@${InstallationChannel}`, "version", "--json"]) }) - test("reads npm registry versions for bun method", async () => { + test("reads npm versions via bun pm view", async () => { + const calls: string[][] = [] const layer = testLayer( - () => jsonResponse({ version: "1.6.0" }), - () => "", + () => { + throw new Error("unexpected http request") + }, + (cmd, args) => { + calls.push([cmd, ...args]) + if (cmd === "bun" && args[0] === "pm") return '"1.6.0"\n' + return "" + }, ) const result = await Effect.runPromise( Installation.Service.use((svc) => svc.latest("bun")).pipe(Effect.provide(layer)), ) expect(result).toBe("1.6.0") + expect(calls).toContainEqual(["bun", "pm", "view", `opencode-ai@${InstallationChannel}`, "version", "--json"]) + }) + + test("reads npm versions via pnpm view", async () => { + const calls: string[][] = [] + const layer = testLayer( + () => { + throw new Error("unexpected http request") + }, + (cmd, args) => { + calls.push([cmd, ...args]) + if (cmd === "pnpm" && args[0] === "view") return '"1.7.0"\n' + return "" + }, + ) + + const result = await Effect.runPromise( + Installation.Service.use((svc) => svc.latest("pnpm")).pipe(Effect.provide(layer)), + ) + expect(result).toBe("1.7.0") + expect(calls).toContainEqual(["pnpm", "view", `opencode-ai@${InstallationChannel}`, "version", "--json"]) }) test("reads scoop manifest versions", async () => { diff --git a/packages/opencode/test/npm.test.ts b/packages/opencode/test/npm.test.ts index a8ec92c2a7..b7680bb70d 100644 --- a/packages/opencode/test/npm.test.ts +++ b/packages/opencode/test/npm.test.ts @@ -1,10 +1,50 @@ import fs from "fs/promises" import path from "path" import { describe, expect, test } from "bun:test" +import { Effect, Layer, Stream } from "effect" +import { NodeFileSystem } from "@effect/platform-node" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { Global } from "@opencode-ai/shared/global" +import { EffectFlock } from "@opencode-ai/shared/util/effect-flock" +import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { Npm } from "../src/npm" import { tmpdir } from "./fixture/fixture" const win = process.platform === "win32" +const encoder = new TextEncoder() +function mockSpawner(handler: (cmd: string, args: readonly string[]) => string = () => "") { + const spawner = ChildProcessSpawner.make((command) => { + const std = ChildProcess.isStandardCommand(command) ? command : undefined + const output = handler(std?.command ?? "", std?.args ?? []) + return Effect.succeed( + ChildProcessSpawner.makeHandle({ + pid: ChildProcessSpawner.ProcessId(0), + exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)), + isRunning: Effect.succeed(false), + kill: () => Effect.void, + stdin: { [Symbol.for("effect/Sink/TypeId")]: Symbol.for("effect/Sink/TypeId") } as any, + stdout: output ? Stream.make(encoder.encode(output)) : Stream.empty, + stderr: Stream.empty, + all: Stream.empty, + getInputFd: () => ({ [Symbol.for("effect/Sink/TypeId")]: Symbol.for("effect/Sink/TypeId") }) as any, + getOutputFd: () => Stream.empty, + unref: Effect.succeed(Effect.void), + }), + ) + }) + return Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner) +} + +function testLayer(spawnHandler?: (cmd: string, args: readonly string[]) => string) { + return Npm.layer.pipe( + Layer.provide(mockSpawner(spawnHandler)), + Layer.provide(EffectFlock.layer), + Layer.provide(AppFileSystem.layer), + Layer.provide(Global.layer), + Layer.provide(NodeFileSystem.layer), + ) +} + const writePackage = (dir: string, pkg: Record) => Bun.write( path.join(dir, "package.json"), @@ -53,3 +93,47 @@ describe("Npm.install", () => { await expect(fs.stat(path.join(tmp.path, "node_modules", "dev-pkg"))).rejects.toThrow() }) }) + +describe("Npm.outdated", () => { + test("checks latest via npm view", async () => { + const calls: string[][] = [] + const layer = testLayer((cmd, args) => { + calls.push([cmd, ...args]) + if (cmd === "npm" && args[0] === "view") return '"2.0.0"\n' + return "" + }) + + const result = await Effect.runPromise(Npm.Service.use((svc) => svc.outdated("example", "1.0.0")).pipe(Effect.provide(layer))) + + expect(result).toBe(true) + expect(calls).toContainEqual(["npm", "view", "example", "dist-tags.latest", "--json"]) + }) + + test("keeps range comparison behavior", async () => { + const layer = testLayer((cmd, args) => { + if (cmd === "npm" && args[0] === "view") return '"2.3.0"\n' + return "" + }) + + const result = await Effect.runPromise( + Npm.Service.use((svc) => svc.outdated("example", "^2.0.0")).pipe(Effect.provide(layer)), + ) + + expect(result).toBe(false) + }) + + test("falls back when npm view is unavailable", async () => { + const calls: string[][] = [] + const layer = testLayer((cmd, args) => { + calls.push([cmd, ...args]) + if (cmd === "pnpm" && args[0] === "view") return '"2.0.0"\n' + return "" + }) + + const result = await Effect.runPromise(Npm.Service.use((svc) => svc.outdated("example", "1.0.0")).pipe(Effect.provide(layer))) + + expect(result).toBe(true) + expect(calls).toContainEqual(["npm", "view", "example", "dist-tags.latest", "--json"]) + expect(calls).toContainEqual(["pnpm", "view", "example", "dist-tags.latest", "--json"]) + }) +}) From 353532b1c16bd5677efd2c352f502178f0c5094c Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 16:00:01 +0000 Subject: [PATCH 06/59] chore: generate --- packages/opencode/src/installation/index.ts | 4 ++- packages/opencode/src/npm/index.ts | 35 ++++++++++----------- packages/opencode/test/npm.test.ts | 8 +++-- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index e39b14b8f3..787f9ea8c5 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -136,7 +136,9 @@ export const layer: Layer.Layer path.join(global.cache, "packages", sanitize(pkg)) - const runView = Effect.fnUntraced( - function* (cmd: string[]) { - const handle = yield* spawner.spawn( - ChildProcess.make(cmd[0], cmd.slice(1), { - extendEnv: true, - }), - ) - const [stdout, stderr] = yield* Effect.all( - [Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))], - { concurrency: 2 }, - ) - const code = yield* handle.exitCode - if (code !== 0 || !stdout.trim()) { - return yield* Effect.fail(stderr || stdout || `Failed to run ${cmd.join(" ")}`) - } - return yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.String))(stdout) - }, - Effect.scoped, - ) + const runView = Effect.fnUntraced(function* (cmd: string[]) { + const handle = yield* spawner.spawn( + ChildProcess.make(cmd[0], cmd.slice(1), { + extendEnv: true, + }), + ) + const [stdout, stderr] = yield* Effect.all( + [Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))], + { concurrency: 2 }, + ) + const code = yield* handle.exitCode + if (code !== 0 || !stdout.trim()) { + return yield* Effect.fail(stderr || stdout || `Failed to run ${cmd.join(" ")}`) + } + return yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.String))(stdout) + }, Effect.scoped) const viewLatestVersion = Effect.fnUntraced(function* (pkg: string) { return yield* runView(["npm", "view", pkg, "dist-tags.latest", "--json"]).pipe( Effect.catch(() => diff --git a/packages/opencode/test/npm.test.ts b/packages/opencode/test/npm.test.ts index b7680bb70d..b27d668c8c 100644 --- a/packages/opencode/test/npm.test.ts +++ b/packages/opencode/test/npm.test.ts @@ -103,7 +103,9 @@ describe("Npm.outdated", () => { return "" }) - const result = await Effect.runPromise(Npm.Service.use((svc) => svc.outdated("example", "1.0.0")).pipe(Effect.provide(layer))) + const result = await Effect.runPromise( + Npm.Service.use((svc) => svc.outdated("example", "1.0.0")).pipe(Effect.provide(layer)), + ) expect(result).toBe(true) expect(calls).toContainEqual(["npm", "view", "example", "dist-tags.latest", "--json"]) @@ -130,7 +132,9 @@ describe("Npm.outdated", () => { return "" }) - const result = await Effect.runPromise(Npm.Service.use((svc) => svc.outdated("example", "1.0.0")).pipe(Effect.provide(layer))) + const result = await Effect.runPromise( + Npm.Service.use((svc) => svc.outdated("example", "1.0.0")).pipe(Effect.provide(layer)), + ) expect(result).toBe(true) expect(calls).toContainEqual(["npm", "view", "example", "dist-tags.latest", "--json"]) From c50d65b4d6b956cad44663d9d31b7b5eb01c8e57 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 12:43:08 -0400 Subject: [PATCH 07/59] refactor(sync): make session events schema-first (#24019) --- packages/opencode/src/bus/index.ts | 24 ++++++-- packages/opencode/src/server/projectors.ts | 3 +- packages/opencode/src/session/message-v2.ts | 44 ++++++++------ packages/opencode/src/session/projectors.ts | 2 +- packages/opencode/src/session/session.ts | 65 ++++++++++++++------- packages/opencode/src/share/share-next.ts | 2 +- packages/opencode/src/sync/index.ts | 44 ++++++++++---- packages/opencode/test/sync/index.test.ts | 6 +- packages/sdk/js/src/v2/gen/types.gen.ts | 32 +++++----- 9 files changed, 141 insertions(+), 81 deletions(-) diff --git a/packages/opencode/src/bus/index.ts b/packages/opencode/src/bus/index.ts index 8a9579b599..a2f9f5ccba 100644 --- a/packages/opencode/src/bus/index.ts +++ b/packages/opencode/src/bus/index.ts @@ -1,5 +1,5 @@ import z from "zod" -import { Effect, Exit, Layer, PubSub, Scope, Context, Stream } from "effect" +import { Effect, Exit, Layer, PubSub, Scope, Context, Stream, Schema as EffectSchema, Types } from "effect" import { EffectBridge } from "@/effect" import { Log } from "../util" import { BusEvent } from "./bus-event" @@ -9,6 +9,12 @@ import { makeRuntime } from "@/effect/run-service" const log = Log.create({ service: "bus" }) +type BusProperties = D extends { + effectProperties: infer Properties extends EffectSchema.Top +} + ? Types.DeepMutable> + : z.infer + export const InstanceDisposed = BusEvent.define( "server.instance.disposed", z.object({ @@ -18,7 +24,7 @@ export const InstanceDisposed = BusEvent.define( type Payload = { type: D["type"] - properties: z.infer + properties: BusProperties } type State = { @@ -29,7 +35,7 @@ type State = { export interface Interface { readonly publish: ( def: D, - properties: z.output, + properties: BusProperties, ) => Effect.Effect readonly subscribe: (def: D) => Stream.Stream> readonly subscribeAll: () => Stream.Stream @@ -79,7 +85,10 @@ export const layer = Layer.effect( }) } - function publish(def: D, properties: z.output) { + function publish( + def: D, + properties: BusProperties, + ) { return Effect.gen(function* () { const s = yield* InstanceState.get(state) const payload: Payload = { type: def.type, properties } @@ -175,13 +184,16 @@ const { runPromise, runSync } = makeRuntime(Service, layer) // runSync is safe here because the subscribe chain (InstanceState.get, PubSub.subscribe, // Scope.make, Effect.forkScoped) is entirely synchronous. If any step becomes async, this will throw. -export async function publish(def: D, properties: z.output) { +export async function publish( + def: D, + properties: BusProperties, +) { return runPromise((svc) => svc.publish(def, properties)) } export function subscribe( def: D, - callback: (event: { type: D["type"]; properties: z.infer }) => unknown, + callback: (event: Payload) => unknown, ) { return runSync((svc) => svc.subscribeCallback(def, callback)) } diff --git a/packages/opencode/src/server/projectors.ts b/packages/opencode/src/server/projectors.ts index cfecce5265..18c273d587 100644 --- a/packages/opencode/src/server/projectors.ts +++ b/packages/opencode/src/server/projectors.ts @@ -1,4 +1,3 @@ -import z from "zod" import sessionProjectors from "../session/projectors" import { SyncEvent } from "@/sync" import { Session } from "@/session" @@ -10,7 +9,7 @@ export function initProjectors() { projectors: sessionProjectors, convertEvent: (type, data) => { if (type === "session.updated") { - const id = (data as z.infer).sessionID + const id = (data as SyncEvent.Event["data"]).sessionID const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get()) if (!row) return data diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 980dd4da84..a4b25d95ea 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -576,34 +576,46 @@ export const Info = Object.assign(_Info, { }) export type Info = User | Assistant +const UpdatedEventSchema = Schema.Struct({ + sessionID: SessionID, + info: _Info, +}) + +const RemovedEventSchema = Schema.Struct({ + sessionID: SessionID, + messageID: MessageID, +}) + +const PartUpdatedEventSchema = Schema.Struct({ + sessionID: SessionID, + part: _Part, + time: Schema.Number, +}) + +const PartRemovedEventSchema = Schema.Struct({ + sessionID: SessionID, + messageID: MessageID, + partID: PartID, +}) + export const Event = { Updated: SyncEvent.define({ type: "message.updated", version: 1, aggregate: "sessionID", - schema: z.object({ - sessionID: SessionID.zod, - info: Info.zod, - }), + schema: UpdatedEventSchema, }), Removed: SyncEvent.define({ type: "message.removed", version: 1, aggregate: "sessionID", - schema: z.object({ - sessionID: SessionID.zod, - messageID: MessageID.zod, - }), + schema: RemovedEventSchema, }), PartUpdated: SyncEvent.define({ type: "message.part.updated", version: 1, aggregate: "sessionID", - schema: z.object({ - sessionID: SessionID.zod, - part: Part.zod, - time: z.number(), - }), + schema: PartUpdatedEventSchema, }), PartDelta: BusEvent.define( "message.part.delta", @@ -619,11 +631,7 @@ export const Event = { type: "message.part.removed", version: 1, aggregate: "sessionID", - schema: z.object({ - sessionID: SessionID.zod, - messageID: MessageID.zod, - partID: PartID.zod, - }), + schema: PartRemovedEventSchema, }), } diff --git a/packages/opencode/src/session/projectors.ts b/packages/opencode/src/session/projectors.ts index 7b3b0a7f21..3a5fd0d8c9 100644 --- a/packages/opencode/src/session/projectors.ts +++ b/packages/opencode/src/session/projectors.ts @@ -71,7 +71,7 @@ export default [ const info = data.info const row = db .update(SessionTable) - .set(toPartialRow(info)) + .set(toPartialRow(info as Session.Patch)) .where(eq(SessionTable.id, data.sessionID)) .returning() .get() diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index d2bdbccb7d..1e046fdf79 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -15,7 +15,6 @@ import { PartTable, SessionTable } from "./session.sql" import { ProjectTable } from "../project/project.sql" import { Storage } from "@/storage" import { Log } from "../util" -import { updateSchema } from "../util/update-schema" import { MessageV2 } from "./message-v2" import { Instance } from "../project/instance" import { InstanceState } from "@/effect" @@ -28,7 +27,7 @@ import type { Provider } from "@/provider" import { Permission } from "@/permission" import { Global } from "@/global" import { Effect, Layer, Option, Context, Schema, Types } from "effect" -import { zod, zodObject } from "@/util/effect-zod" +import { zod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" const log = Log.create({ service: "session" }) @@ -215,40 +214,62 @@ export const MessagesInput = Schema.Struct({ limit: Schema.optional(Schema.Number), }).pipe(withStatics((s) => ({ zod: zod(s) }))) +const CreatedEventSchema = Schema.Struct({ + sessionID: SessionID, + info: Info, +}) + +const UpdatedShare = Schema.Struct({ + url: Schema.optional(Schema.NullOr(Schema.String)), +}) + +const UpdatedTime = Schema.Struct({ + created: Schema.optional(Schema.NullOr(Schema.Number)), + updated: Schema.optional(Schema.NullOr(Schema.Number)), + compacting: Schema.optional(Schema.NullOr(Schema.Number)), + archived: Schema.optional(Schema.NullOr(Schema.Number)), +}) + +const UpdatedInfo = Schema.Struct({ + id: Schema.optional(Schema.NullOr(SessionID)), + slug: Schema.optional(Schema.NullOr(Schema.String)), + projectID: Schema.optional(Schema.NullOr(ProjectID)), + workspaceID: Schema.optional(Schema.NullOr(WorkspaceID)), + directory: Schema.optional(Schema.NullOr(Schema.String)), + parentID: Schema.optional(Schema.NullOr(SessionID)), + summary: Schema.optional(Schema.NullOr(Summary)), + share: Schema.optional(UpdatedShare), + title: Schema.optional(Schema.NullOr(Schema.String)), + version: Schema.optional(Schema.NullOr(Schema.String)), + time: Schema.optional(UpdatedTime), + permission: Schema.optional(Schema.NullOr(Permission.Ruleset)), + revert: Schema.optional(Schema.NullOr(Revert)), +}) + +const UpdatedEventSchema = Schema.Struct({ + sessionID: SessionID, + info: UpdatedInfo, +}) + export const Event = { Created: SyncEvent.define({ type: "session.created", version: 1, aggregate: "sessionID", - schema: z.object({ - sessionID: SessionID.zod, - info: Info.zod, - }), + schema: CreatedEventSchema, }), Updated: SyncEvent.define({ type: "session.updated", version: 1, aggregate: "sessionID", - schema: z.object({ - sessionID: SessionID.zod, - info: updateSchema(zodObject(Info)).extend({ - share: updateSchema(zodObject(Share)).optional(), - time: updateSchema(zodObject(Time)).optional(), - }), - }), - busSchema: z.object({ - sessionID: SessionID.zod, - info: Info.zod, - }), + schema: UpdatedEventSchema, + busSchema: CreatedEventSchema, }), Deleted: SyncEvent.define({ type: "session.deleted", version: 1, aggregate: "sessionID", - schema: z.object({ - sessionID: SessionID.zod, - info: Info.zod, - }), + schema: CreatedEventSchema, }), Diff: BusEvent.define( "session.diff", @@ -394,7 +415,7 @@ export interface Interface { export class Service extends Context.Service()("@opencode/Session") {} -type Patch = z.infer["info"] +export type Patch = Types.DeepMutable["data"]["info"]> const db = (fn: (d: Parameters[0] extends (trx: infer D) => any ? D : never) => T) => Effect.sync(() => Database.use(fn)) diff --git a/packages/opencode/src/share/share-next.ts b/packages/opencode/src/share/share-next.ts index 2622f4f7f0..f26a085c22 100644 --- a/packages/opencode/src/share/share-next.ts +++ b/packages/opencode/src/share/share-next.ts @@ -181,7 +181,7 @@ export const layer = Layer.effect( yield* watch(Session.Event.Updated, (evt) => Effect.gen(function* () { - const info = yield* session.get(evt.properties.sessionID) + const info = evt.properties.info yield* sync(info.id, [{ type: "session", data: info }]) }), ) diff --git a/packages/opencode/src/sync/index.ts b/packages/opencode/src/sync/index.ts index 125d8c9550..5a4078402d 100644 --- a/packages/opencode/src/sync/index.ts +++ b/packages/opencode/src/sync/index.ts @@ -1,5 +1,4 @@ import z from "zod" -import type { ZodObject } from "zod" import { Database, eq } from "@/storage" import { GlobalBus } from "@/bus/global" import { Bus as ProjectBus } from "@/bus" @@ -9,11 +8,16 @@ import { EventSequenceTable, EventTable } from "./event.sql" import { WorkspaceContext } from "@/control-plane/workspace-context" import { EventID } from "./schema" import { Flag } from "@/flag/flag" +import { Schema as EffectSchema, Types } from "effect" +import { zodObject } from "@/util/effect-zod" +import { isRecord } from "@/util/record" -export type Definition = { +export type Definition = { type: string version: number aggregate: string + effectSchema: Schema + effectProperties: BusSchema schema: z.ZodObject // This is temporary and only exists for compatibility with bus @@ -25,9 +29,13 @@ export type Event = { id: string seq: number aggregateID: string - data: z.infer + data: Types.DeepMutable> } +export type Properties = Types.DeepMutable< + EffectSchema.Schema.Type +> + export type SerializedEvent = Event & { type: string } type ProjectorFunc = (db: Database.TxOrDb, data: unknown) => void @@ -36,7 +44,12 @@ export const registry = new Map() let projectors: Map | undefined const versions = new Map() let frozen = false -let convertEvent: (type: string, event: Event["data"]) => Promise> | Record +let convertEvent: (type: string, event: Event["data"]) => Promise | unknown + +function asRecord(input: unknown) { + if (isRecord(input)) return input + throw new Error(`SyncEvent.convertEvent must return an object, got: ${JSON.stringify(input)}`) +} export function reset() { frozen = false @@ -54,7 +67,7 @@ export function init(input: { projectors: Array<[Definition, ProjectorFunc]>; co for (let [type, version] of versions.entries()) { let def = registry.get(versionedType(type, version))! - BusEvent.define(def.type, def.properties || def.schema) + BusEvent.define(def.type, def.properties) } // Freeze the system so it clearly errors if events are defined @@ -72,19 +85,26 @@ export function versionedType(type: string, version?: number) { export function define< Type extends string, Agg extends string, - Schema extends ZodObject>>, - BusSchema extends ZodObject = Schema, ->(input: { type: Type; version: number; aggregate: Agg; schema: Schema; busSchema?: BusSchema }) { + Schema extends EffectSchema.Top, + BusSchema extends EffectSchema.Top = Schema, +>(input: { type: Type; version: number; aggregate: Agg; schema: Schema; busSchema?: BusSchema }): Definition< + Schema, + BusSchema +> { if (frozen) { throw new Error("Error defining sync event: sync system has been frozen") } + const effectProperties = (input.busSchema ?? input.schema) as BusSchema + const def = { type: input.type, version: input.version, aggregate: input.aggregate, - schema: input.schema, - properties: input.busSchema ? input.busSchema : input.schema, + effectSchema: input.schema, + effectProperties, + schema: zodObject(input.schema), + properties: zodObject(effectProperties), } versions.set(def.type, Math.max(def.version, versions.get(def.type) || 0)) @@ -143,10 +163,10 @@ function process(def: Def, event: Event, options: { const result = convertEvent(def.type, event.data) if (result instanceof Promise) { void result.then((data) => { - void ProjectBus.publish({ type: def.type, properties: def.schema }, data) + void ProjectBus.publish({ type: def.type, properties: def.properties }, asRecord(data)) }) } else { - void ProjectBus.publish({ type: def.type, properties: def.schema }, result) + void ProjectBus.publish({ type: def.type, properties: def.properties }, asRecord(result)) } GlobalBus.emit("event", { diff --git a/packages/opencode/test/sync/index.test.ts b/packages/opencode/test/sync/index.test.ts index 866bcaa31a..d50f0d7c94 100644 --- a/packages/opencode/test/sync/index.test.ts +++ b/packages/opencode/test/sync/index.test.ts @@ -1,6 +1,6 @@ import { describe, test, expect, beforeEach, afterEach, afterAll } from "bun:test" import { tmpdir } from "../fixture/fixture" -import z from "zod" +import { Schema } from "effect" import { Bus } from "../../src/bus" import { Instance } from "../../src/project/instance" import { SyncEvent } from "../../src/sync" @@ -43,13 +43,13 @@ describe("SyncEvent", () => { type: "item.created", version: 1, aggregate: "id", - schema: z.object({ id: z.string(), name: z.string() }), + schema: Schema.Struct({ id: Schema.String, name: Schema.String }), }) const Sent = SyncEvent.define({ type: "item.sent", version: 1, aggregate: "item_id", - schema: z.object({ item_id: z.string(), to: z.string() }), + schema: Schema.Struct({ item_id: Schema.String, to: Schema.String }), }) SyncEvent.init({ diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 1fcab2eda6..d28ce25794 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1058,31 +1058,31 @@ export type SyncEventSessionUpdated = { data: { sessionID: string info: { - id: string | null - slug: string | null - projectID: string | null - workspaceID: string | null - directory: string | null - parentID: string | null - summary: { + id?: string | null + slug?: string | null + projectID?: string | null + workspaceID?: string | null + directory?: string | null + parentID?: string | null + summary?: { additions: number deletions: number files: number diffs?: Array } | null share?: { - url: string | null + url?: string | null } - title: string | null - version: string | null + title?: string | null + version?: string | null time?: { - created: number | null - updated: number | null - compacting: number | null - archived: number | null + created?: number | null + updated?: number | null + compacting?: number | null + archived?: number | null } - permission: PermissionRuleset | null - revert: { + permission?: PermissionRuleset | null + revert?: { messageID: string partID?: string snapshot?: string From aed03078f88af5c28035a28b6edbbf10228a49ed Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 16:44:32 +0000 Subject: [PATCH 08/59] chore: generate --- packages/opencode/src/bus/index.ts | 20 ++++---------------- packages/opencode/src/sync/index.ts | 16 +++++++++++----- packages/sdk/openapi.json | 21 +++------------------ 3 files changed, 18 insertions(+), 39 deletions(-) diff --git a/packages/opencode/src/bus/index.ts b/packages/opencode/src/bus/index.ts index a2f9f5ccba..9183ff72a4 100644 --- a/packages/opencode/src/bus/index.ts +++ b/packages/opencode/src/bus/index.ts @@ -33,10 +33,7 @@ type State = { } export interface Interface { - readonly publish: ( - def: D, - properties: BusProperties, - ) => Effect.Effect + readonly publish: (def: D, properties: BusProperties) => Effect.Effect readonly subscribe: (def: D) => Stream.Stream> readonly subscribeAll: () => Stream.Stream readonly subscribeCallback: ( @@ -85,10 +82,7 @@ export const layer = Layer.effect( }) } - function publish( - def: D, - properties: BusProperties, - ) { + function publish(def: D, properties: BusProperties) { return Effect.gen(function* () { const s = yield* InstanceState.get(state) const payload: Payload = { type: def.type, properties } @@ -184,17 +178,11 @@ const { runPromise, runSync } = makeRuntime(Service, layer) // runSync is safe here because the subscribe chain (InstanceState.get, PubSub.subscribe, // Scope.make, Effect.forkScoped) is entirely synchronous. If any step becomes async, this will throw. -export async function publish( - def: D, - properties: BusProperties, -) { +export async function publish(def: D, properties: BusProperties) { return runPromise((svc) => svc.publish(def, properties)) } -export function subscribe( - def: D, - callback: (event: Payload) => unknown, -) { +export function subscribe(def: D, callback: (event: Payload) => unknown) { return runSync((svc) => svc.subscribeCallback(def, callback)) } diff --git a/packages/opencode/src/sync/index.ts b/packages/opencode/src/sync/index.ts index 5a4078402d..482ad4fbb6 100644 --- a/packages/opencode/src/sync/index.ts +++ b/packages/opencode/src/sync/index.ts @@ -12,7 +12,10 @@ import { Schema as EffectSchema, Types } from "effect" import { zodObject } from "@/util/effect-zod" import { isRecord } from "@/util/record" -export type Definition = { +export type Definition< + Schema extends EffectSchema.Top = EffectSchema.Top, + BusSchema extends EffectSchema.Top = Schema, +> = { type: string version: number aggregate: string @@ -87,10 +90,13 @@ export function define< Agg extends string, Schema extends EffectSchema.Top, BusSchema extends EffectSchema.Top = Schema, ->(input: { type: Type; version: number; aggregate: Agg; schema: Schema; busSchema?: BusSchema }): Definition< - Schema, - BusSchema -> { +>(input: { + type: Type + version: number + aggregate: Agg + schema: Schema + busSchema?: BusSchema +}): Definition { if (frozen) { throw new Error("Error defining sync event: sync system has been frozen") } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 55570d47dd..e54648e198 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -10570,8 +10570,7 @@ } ] } - }, - "required": ["url"] + } }, "title": { "anyOf": [ @@ -10636,8 +10635,7 @@ } ] } - }, - "required": ["created", "updated", "compacting", "archived"] + } }, "permission": { "anyOf": [ @@ -10676,20 +10674,7 @@ } ] } - }, - "required": [ - "id", - "slug", - "projectID", - "workspaceID", - "directory", - "parentID", - "summary", - "title", - "version", - "permission", - "revert" - ] + } } }, "required": ["sessionID", "info"] From 8b2f8355b22414a768c4a594d7a7101a4f72b00f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 12:54:46 -0400 Subject: [PATCH 09/59] docs(schema): mark sync/index.ts migrated with compat-bridge note (#24024) --- packages/opencode/specs/effect/schema.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index 3f2c3b4c96..0abdd9ed66 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -147,6 +147,17 @@ import `z` do so only for local `ZodOverride` bridges or for `z.ZodType` type annotations — the `export const ` values are all Effect Schema at source. +A file is considered "done" when: + +- its exported schema values (`Info`, `Input`, `Event`, `Definition`, etc.) + are authored as Effect Schema +- any remaining zod is either a derived compat bridge (via `zod()` / + `zodObject()`), a `z.ZodType` type annotation, or a documented + `ZodOverride` escape hatch — never a hand-written parallel source of truth + +Files that meet this bar but still carry a compat bridge are checked off +with an inline note describing the bridge and what unblocks its removal. + - [x] skills, formatter, console-state, mcp, lsp, permission (leaves), model-id, command, plugin, provider - [x] server, layout - [x] keybinds @@ -365,7 +376,7 @@ piecewise. - [ ] `src/snapshot/index.ts` - [ ] `src/storage/db.ts` - [ ] `src/storage/storage.ts` -- [ ] `src/sync/index.ts` +- [x] `src/sync/index.ts` — public API (`SyncEvent.define`) is Schema-first; still stores derived zod `schema`/`properties` on each `Definition` as a compat bridge for `BusEvent` until `bus/bus-event.ts` migrates - [ ] `src/util/fn.ts` - [ ] `src/util/log.ts` - [ ] `src/util/update-schema.ts` From 1e439b8226a66dcec0d65751f5cdb50a2b7bc979 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 23 Apr 2026 13:13:26 -0400 Subject: [PATCH 10/59] sync --- packages/console/app/src/routes/zen/util/handler.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index d9dc450012..87d4a4d0ab 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -187,6 +187,8 @@ export async function handler( // Try another provider => stop retrying if using fallback provider if ( res.status !== 200 && + // ie. 400 error is usually provider error like malformed request + res.status !== 400 && // ie. openai 404 error: Item with id 'msg_0ead8b004a3b165d0069436a6b6834819896da85b63b196a3f' not found. res.status !== 404 && // ie. cannot change codex model providers mid-session @@ -226,7 +228,7 @@ export async function handler( logger.debug("STATUS: " + res.status + " " + res.statusText) // Handle non-streaming response - if (!isStream || res.status === 429) { + if (!isStream || [400, 404, 429].includes(res.status)) { const json = await res.json() await rateLimiter?.track() if (json.usage) { @@ -238,6 +240,9 @@ export async function handler( await reload(billingSource, authInfo, costInfo) json.cost = calculateOccurredCost(billingSource, costInfo) } + if (res.status === 400) { + logger.metric({ "error.response": JSON.stringify(json) }) + } if (json.error?.message) { json.error.message = `Error from provider${providerInfo.displayName ? ` (${providerInfo.displayName})` : ""}: ${json.error.message}` } @@ -393,7 +398,7 @@ export async function handler( type: "error", error: { type: "error", - message: error.message, + message: "Internal server error", }, }), { status: 500 }, From 93940a1859530b8fe46df376e7d7ba74fcd3e0d1 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 13:17:33 -0400 Subject: [PATCH 11/59] refactor(provider): migrate provider domain to Effect Schema (#24027) --- packages/opencode/specs/effect/schema.md | 6 +- packages/opencode/src/provider/auth.ts | 23 ++-- packages/opencode/src/provider/models.ts | 150 ++++++++++----------- packages/opencode/src/provider/provider.ts | 27 ++-- 4 files changed, 93 insertions(+), 113 deletions(-) diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index 0abdd9ed66..0d451f4a99 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -274,9 +274,9 @@ Possible later tightening after the Schema-first migration is stable: ### Provider domain -- [ ] `src/provider/auth.ts` -- [ ] `src/provider/models.ts` -- [ ] `src/provider/provider.ts` +- [x] `src/provider/auth.ts` +- [x] `src/provider/models.ts` +- [x] `src/provider/provider.ts` ### Tool schemas diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts index 5d8b2765de..0b4ac995a8 100644 --- a/packages/opencode/src/provider/auth.ts +++ b/packages/opencode/src/provider/auth.ts @@ -1,13 +1,12 @@ import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin" -import { NamedError } from "@opencode-ai/shared/util/error" import { Auth } from "@/auth" import { InstanceState } from "@/effect" import { zod } from "@/util/effect-zod" +import { namedSchemaError } from "@/util/named-schema-error" import { withStatics } from "@/util/schema" import { Plugin } from "../plugin" import { ProviderID } from "./schema" import { Array as Arr, Effect, Layer, Record, Result, Context, Schema } from "effect" -import z from "zod" const When = Schema.Struct({ key: Schema.String, @@ -70,22 +69,16 @@ export const CallbackInput = Schema.Struct({ }).pipe(withStatics((s) => ({ zod: zod(s) }))) export type CallbackInput = Schema.Schema.Type -export const OauthMissing = NamedError.create("ProviderAuthOauthMissing", z.object({ providerID: ProviderID.zod })) +export const OauthMissing = namedSchemaError("ProviderAuthOauthMissing", { providerID: ProviderID }) -export const OauthCodeMissing = NamedError.create( - "ProviderAuthOauthCodeMissing", - z.object({ providerID: ProviderID.zod }), -) +export const OauthCodeMissing = namedSchemaError("ProviderAuthOauthCodeMissing", { providerID: ProviderID }) -export const OauthCallbackFailed = NamedError.create("ProviderAuthOauthCallbackFailed", z.object({})) +export const OauthCallbackFailed = namedSchemaError("ProviderAuthOauthCallbackFailed", {}) -export const ValidationFailed = NamedError.create( - "ProviderAuthValidationFailed", - z.object({ - field: z.string(), - message: z.string(), - }), -) +export const ValidationFailed = namedSchemaError("ProviderAuthValidationFailed", { + field: Schema.String, + message: Schema.String, +}) export type Error = | Auth.AuthError diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index 2924666c0e..36c4d8c23c 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -1,7 +1,7 @@ import { Global } from "../global" import { Log } from "../util" import path from "path" -import z from "zod" +import { Schema } from "effect" import { Installation } from "../installation" import { Flag } from "../flag/flag" import { lazy } from "@/util/lazy" @@ -21,91 +21,85 @@ const filepath = path.join( ) const ttl = 5 * 60 * 1000 -type JsonValue = string | number | boolean | null | { [key: string]: JsonValue } | JsonValue[] - -const JsonValue: z.ZodType = z.lazy(() => - z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(JsonValue), z.record(z.string(), JsonValue)]), -) - -const Cost = z.object({ - input: z.number(), - output: z.number(), - cache_read: z.number().optional(), - cache_write: z.number().optional(), - context_over_200k: z - .object({ - input: z.number(), - output: z.number(), - cache_read: z.number().optional(), - cache_write: z.number().optional(), - }) - .optional(), +const Cost = Schema.Struct({ + input: Schema.Number, + output: Schema.Number, + cache_read: Schema.optional(Schema.Number), + cache_write: Schema.optional(Schema.Number), + context_over_200k: Schema.optional( + Schema.Struct({ + input: Schema.Number, + output: Schema.Number, + cache_read: Schema.optional(Schema.Number), + cache_write: Schema.optional(Schema.Number), + }), + ), }) -export const Model = z.object({ - id: z.string(), - name: z.string(), - family: z.string().optional(), - release_date: z.string(), - attachment: z.boolean(), - reasoning: z.boolean(), - temperature: z.boolean(), - tool_call: z.boolean(), - interleaved: z - .union([ - z.literal(true), - z - .object({ - field: z.enum(["reasoning_content", "reasoning_details"]), - }) - .strict(), - ]) - .optional(), - cost: Cost.optional(), - limit: z.object({ - context: z.number(), - input: z.number().optional(), - output: z.number(), +export const Model = Schema.Struct({ + id: Schema.String, + name: Schema.String, + family: Schema.optional(Schema.String), + release_date: Schema.String, + attachment: Schema.Boolean, + reasoning: Schema.Boolean, + temperature: Schema.Boolean, + tool_call: Schema.Boolean, + interleaved: Schema.optional( + Schema.Union([ + Schema.Literal(true), + Schema.Struct({ + field: Schema.Literals(["reasoning_content", "reasoning_details"]), + }), + ]), + ), + cost: Schema.optional(Cost), + limit: Schema.Struct({ + context: Schema.Number, + input: Schema.optional(Schema.Number), + output: Schema.Number, }), - modalities: z - .object({ - input: z.array(z.enum(["text", "audio", "image", "video", "pdf"])), - output: z.array(z.enum(["text", "audio", "image", "video", "pdf"])), - }) - .optional(), - experimental: z - .object({ - modes: z - .record( - z.string(), - z.object({ - cost: Cost.optional(), - provider: z - .object({ - body: z.record(z.string(), JsonValue).optional(), - headers: z.record(z.string(), z.string()).optional(), - }) - .optional(), + modalities: Schema.optional( + Schema.Struct({ + input: Schema.Array(Schema.Literals(["text", "audio", "image", "video", "pdf"])), + output: Schema.Array(Schema.Literals(["text", "audio", "image", "video", "pdf"])), + }), + ), + experimental: Schema.optional( + Schema.Struct({ + modes: Schema.optional( + Schema.Record( + Schema.String, + Schema.Struct({ + cost: Schema.optional(Cost), + provider: Schema.optional( + Schema.Struct({ + body: Schema.optional(Schema.Record(Schema.String, Schema.MutableJson)), + headers: Schema.optional(Schema.Record(Schema.String, Schema.String)), + }), + ), }), - ) - .optional(), - }) - .optional(), - status: z.enum(["alpha", "beta", "deprecated"]).optional(), - provider: z.object({ npm: z.string().optional(), api: z.string().optional() }).optional(), + ), + ), + }), + ), + status: Schema.optional(Schema.Literals(["alpha", "beta", "deprecated"])), + provider: Schema.optional( + Schema.Struct({ npm: Schema.optional(Schema.String), api: Schema.optional(Schema.String) }), + ), }) -export type Model = z.infer +export type Model = Schema.Schema.Type -export const Provider = z.object({ - api: z.string().optional(), - name: z.string(), - env: z.array(z.string()), - id: z.string(), - npm: z.string().optional(), - models: z.record(z.string(), Model), +export const Provider = Schema.Struct({ + api: Schema.optional(Schema.String), + name: Schema.String, + env: Schema.Array(Schema.String), + id: Schema.String, + npm: Schema.optional(Schema.String), + models: Schema.Record(Schema.String, Model), }) -export type Provider = z.infer +export type Provider = Schema.Schema.Type function url() { return Flag.OPENCODE_MODELS_URL || "https://models.dev" diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index d643f25373..d826f6b350 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1,4 +1,3 @@ -import z from "zod" import os from "os" import fuzzysort from "fuzzysort" import { Config } from "../config" @@ -8,7 +7,6 @@ import { Log } from "../util" import { Npm } from "../npm" import { Hash } from "@opencode-ai/shared/util/hash" import { Plugin } from "../plugin" -import { NamedError } from "@opencode-ai/shared/util/error" import { type LanguageModelV3 } from "@ai-sdk/provider" import * as ModelsDev from "./models" import { Auth } from "../auth" @@ -16,6 +14,7 @@ import { Env } from "../env" import { InstallationVersion } from "../installation/version" import { Flag } from "../flag/flag" import { zod } from "@/util/effect-zod" +import { namedSchemaError } from "@/util/named-schema-error" import { iife } from "@/util/iife" import { Global } from "../global" import path from "path" @@ -1047,7 +1046,7 @@ export function fromModelsDevProvider(provider: ModelsDev.Provider): Info { id: ProviderID.make(provider.id), source: "custom", name: provider.name, - env: provider.env ?? [], + env: [...(provider.env ?? [])], options: {}, models, } @@ -1713,18 +1712,12 @@ export function parseModel(model: string) { } } -export const ModelNotFoundError = NamedError.create( - "ProviderModelNotFoundError", - z.object({ - providerID: ProviderID.zod, - modelID: ModelID.zod, - suggestions: z.array(z.string()).optional(), - }), -) +export const ModelNotFoundError = namedSchemaError("ProviderModelNotFoundError", { + providerID: ProviderID, + modelID: ModelID, + suggestions: Schema.optional(Schema.Array(Schema.String)), +}) -export const InitError = NamedError.create( - "ProviderInitError", - z.object({ - providerID: ProviderID.zod, - }), -) +export const InitError = namedSchemaError("ProviderInitError", { + providerID: ProviderID, +}) From 0590452456a746457d5255e2ce3ecd9c5a4a621d Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 13:22:48 -0400 Subject: [PATCH 12/59] refactor(schema): use Schema.Int and consolidate PositiveInt/NonNegativeInt (#24029) --- packages/opencode/src/config/agent.ts | 3 +-- packages/opencode/src/config/config.ts | 5 +---- packages/opencode/src/config/provider.ts | 4 +--- packages/opencode/src/config/server.ts | 4 ++-- packages/opencode/src/session/message-v2.ts | 20 +++++++++----------- packages/opencode/src/util/schema.ts | 10 ++++++++++ 6 files changed, 24 insertions(+), 22 deletions(-) diff --git a/packages/opencode/src/config/agent.ts b/packages/opencode/src/config/agent.ts index 85021407c7..2978916b57 100644 --- a/packages/opencode/src/config/agent.ts +++ b/packages/opencode/src/config/agent.ts @@ -4,6 +4,7 @@ import { Schema } from "effect" import z from "zod" import { Bus } from "@/bus" import { zod } from "@/util/effect-zod" +import { PositiveInt } from "@/util/schema" import { Log } from "../util" import { NamedError } from "@opencode-ai/shared/util/error" import { Glob } from "@opencode-ai/shared/util/glob" @@ -15,8 +16,6 @@ import { ConfigPermission } from "./permission" const log = Log.create({ service: "config" }) -const PositiveInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)) - const Color = Schema.Union([ Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/)), Schema.Literals(["primary", "secondary", "accent", "success", "warning", "error", "info"]), diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 5423ba3baf..9814017d10 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -25,7 +25,7 @@ import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "e import { EffectFlock } from "@opencode-ai/shared/util/effect-flock" import { InstanceRef } from "@/effect/instance-ref" import { zod, ZodOverride } from "@/util/effect-zod" -import { withStatics } from "@/util/schema" +import { NonNegativeInt, PositiveInt, withStatics } from "@/util/schema" import { ConfigAgent } from "./agent" import { ConfigCommand } from "./command" import { ConfigFormatter } from "./formatter" @@ -88,9 +88,6 @@ export type Layout = ConfigLayout.Layout const AgentRef = Schema.Any.annotate({ [ZodOverride]: ConfigAgent.Info }) const LogLevelRef = Schema.Any.annotate({ [ZodOverride]: Log.Level }) -const PositiveInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)) -const NonNegativeInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) - // The Effect Schema is the canonical source of truth. The `.zod` compatibility // surface is derived so existing Hono validators keep working without a parallel // Zod definition. diff --git a/packages/opencode/src/config/provider.ts b/packages/opencode/src/config/provider.ts index bd6ae35996..cd7469435c 100644 --- a/packages/opencode/src/config/provider.ts +++ b/packages/opencode/src/config/provider.ts @@ -1,8 +1,6 @@ import { Schema } from "effect" import { zod } from "@/util/effect-zod" -import { withStatics } from "@/util/schema" - -const PositiveInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)) +import { PositiveInt, withStatics } from "@/util/schema" export const Model = Schema.Struct({ id: Schema.optional(Schema.String), diff --git a/packages/opencode/src/config/server.ts b/packages/opencode/src/config/server.ts index 3ce4fe6262..3f13698269 100644 --- a/packages/opencode/src/config/server.ts +++ b/packages/opencode/src/config/server.ts @@ -1,9 +1,9 @@ import { Schema } from "effect" import { zod } from "@/util/effect-zod" -import { withStatics } from "@/util/schema" +import { PositiveInt, withStatics } from "@/util/schema" export const Server = Schema.Struct({ - port: Schema.optional(Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0))).annotate({ + port: Schema.optional(PositiveInt).annotate({ description: "Port to listen on", }), hostname: Schema.optional(Schema.String).annotate({ description: "Hostname to listen on" }), diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index a4b25d95ea..c5a35c7b41 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -17,7 +17,7 @@ import type { Provider } from "@/provider" import { ModelID, ProviderID } from "@/provider/schema" import { Effect, Schema, Types } from "effect" import { zod, ZodOverride } from "@/util/effect-zod" -import { withStatics } from "@/util/schema" +import { NonNegativeInt, withStatics } from "@/util/schema" import { namedSchemaError } from "@/util/named-schema-error" import { EffectLogger } from "@/effect" @@ -64,9 +64,7 @@ export class OutputFormatText extends Schema.Class("OutputForm export class OutputFormatJsonSchema extends Schema.Class("OutputFormatJsonSchema")({ type: Schema.Literal("json_schema"), schema: Schema.Record(Schema.String, Schema.Any).annotate({ identifier: "JSONSchema" }), - retryCount: Schema.Number.check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)) - .pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(2))), + retryCount: NonNegativeInt.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(2))), }) { static readonly zod = zod(this) } @@ -138,8 +136,8 @@ export type ReasoningPart = Types.DeepMutable ({ zod: zod(s) }))) @@ -196,8 +194,8 @@ export const AgentPart = Schema.Struct({ source: Schema.optional( Schema.Struct({ value: Schema.String, - start: Schema.Number.check(Schema.isInt()), - end: Schema.Number.check(Schema.isInt()), + start: Schema.Int, + end: Schema.Int, }), ), }) @@ -501,8 +499,8 @@ export const AgentPartInput = Schema.Struct({ source: Schema.optional( Schema.Struct({ value: Schema.String, - start: Schema.Number.check(Schema.isInt()), - end: Schema.Number.check(Schema.isInt()), + start: Schema.Int, + end: Schema.Int, }), ), }) diff --git a/packages/opencode/src/util/schema.ts b/packages/opencode/src/util/schema.ts index 405f6a7182..7f94ee5d1e 100644 --- a/packages/opencode/src/util/schema.ts +++ b/packages/opencode/src/util/schema.ts @@ -1,5 +1,15 @@ import { Schema } from "effect" +/** + * Integer greater than zero. + */ +export const PositiveInt = Schema.Int.check(Schema.isGreaterThan(0)) + +/** + * Integer greater than or equal to zero. + */ +export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)) + /** * Attach static methods to a schema object. Designed to be used with `.pipe()`: * From cd93533b1f5eae9362a485b0072f98a05d1ca161 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 15:37:44 -0400 Subject: [PATCH 13/59] refactor(bus): migrate BusEvent to Effect Schema (#24040) --- packages/opencode/src/bus/bus-event.ts | 20 +++-- packages/opencode/src/bus/index.ts | 13 +--- packages/opencode/src/cli/cmd/tui/event.ts | 26 +++---- .../opencode/src/cli/cmd/tui/ui/toast.tsx | 4 +- packages/opencode/src/command/index.ts | 12 +-- packages/opencode/src/config/config.ts | 25 +------ .../opencode/src/control-plane/workspace.ts | 28 +++---- packages/opencode/src/file/index.ts | 6 +- packages/opencode/src/file/watcher.ts | 8 +- packages/opencode/src/ide/index.ts | 5 +- packages/opencode/src/installation/index.ts | 8 +- packages/opencode/src/lsp/client.ts | 7 +- packages/opencode/src/lsp/lsp.ts | 2 +- packages/opencode/src/mcp/index.ts | 12 +-- packages/opencode/src/permission/index.ts | 14 ++-- packages/opencode/src/project/project.ts | 2 +- packages/opencode/src/project/vcs.ts | 6 +- packages/opencode/src/pty/index.ts | 75 ++++++++++--------- packages/opencode/src/question/index.ts | 8 +- packages/opencode/src/server/event.ts | 6 +- .../src/server/routes/control/workspace.ts | 3 +- packages/opencode/src/server/routes/global.ts | 4 +- .../src/server/routes/instance/pty.ts | 16 ++-- .../src/server/routes/instance/tui.ts | 22 +++--- packages/opencode/src/session/compaction.ts | 6 +- packages/opencode/src/session/message-v2.ts | 12 +-- packages/opencode/src/session/session.ts | 15 ++-- packages/opencode/src/session/status.ts | 10 +-- packages/opencode/src/session/todo.ts | 6 +- packages/opencode/src/sync/index.ts | 60 +++++++-------- packages/opencode/src/util/effect-zod.ts | 17 +++-- packages/opencode/src/util/schema.ts | 47 ++++++++++-- packages/opencode/src/worktree/index.ts | 12 +-- packages/opencode/test/bus/bus-effect.test.ts | 7 +- .../opencode/test/bus/bus-integration.test.ts | 6 +- packages/opencode/test/bus/bus.test.ts | 6 +- .../opencode/test/session/session.test.ts | 5 +- 37 files changed, 281 insertions(+), 260 deletions(-) diff --git a/packages/opencode/src/bus/bus-event.ts b/packages/opencode/src/bus/bus-event.ts index efaed94406..f27d263354 100644 --- a/packages/opencode/src/bus/bus-event.ts +++ b/packages/opencode/src/bus/bus-event.ts @@ -1,15 +1,19 @@ import z from "zod" -import type { ZodType } from "zod" +import { Schema } from "effect" +import { zodObject } from "@/util/effect-zod" -export type Definition = ReturnType +export type Definition = { + type: Type + properties: Properties +} const registry = new Map() -export function define(type: Type, properties: Properties) { - const result = { - type, - properties, - } +export function define( + type: Type, + properties: Properties, +): Definition { + const result = { type, properties } registry.set(type, result) return result } @@ -21,7 +25,7 @@ export function payloads() { return z .object({ type: z.literal(type), - properties: def.properties, + properties: zodObject(def.properties), }) .meta({ ref: `Event.${def.type}`, diff --git a/packages/opencode/src/bus/index.ts b/packages/opencode/src/bus/index.ts index 9183ff72a4..12251f26c7 100644 --- a/packages/opencode/src/bus/index.ts +++ b/packages/opencode/src/bus/index.ts @@ -1,5 +1,4 @@ -import z from "zod" -import { Effect, Exit, Layer, PubSub, Scope, Context, Stream, Schema as EffectSchema, Types } from "effect" +import { Effect, Exit, Layer, PubSub, Scope, Context, Stream, Schema } from "effect" import { EffectBridge } from "@/effect" import { Log } from "../util" import { BusEvent } from "./bus-event" @@ -9,16 +8,12 @@ import { makeRuntime } from "@/effect/run-service" const log = Log.create({ service: "bus" }) -type BusProperties = D extends { - effectProperties: infer Properties extends EffectSchema.Top -} - ? Types.DeepMutable> - : z.infer +type BusProperties> = Schema.Schema.Type export const InstanceDisposed = BusEvent.define( "server.instance.disposed", - z.object({ - directory: z.string(), + Schema.Struct({ + directory: Schema.String, }), ) diff --git a/packages/opencode/src/cli/cmd/tui/event.ts b/packages/opencode/src/cli/cmd/tui/event.ts index fa164d53e8..ab85b1e645 100644 --- a/packages/opencode/src/cli/cmd/tui/event.ts +++ b/packages/opencode/src/cli/cmd/tui/event.ts @@ -1,14 +1,14 @@ import { BusEvent } from "@/bus/bus-event" import { SessionID } from "@/session/schema" -import z from "zod" +import { Schema } from "effect" export const TuiEvent = { - PromptAppend: BusEvent.define("tui.prompt.append", z.object({ text: z.string() })), + PromptAppend: BusEvent.define("tui.prompt.append", Schema.Struct({ text: Schema.String })), CommandExecute: BusEvent.define( "tui.command.execute", - z.object({ - command: z.union([ - z.enum([ + Schema.Struct({ + command: Schema.Union([ + Schema.Literals([ "session.list", "session.new", "session.share", @@ -26,23 +26,23 @@ export const TuiEvent = { "prompt.submit", "agent.cycle", ]), - z.string(), + Schema.String, ]), }), ), ToastShow: BusEvent.define( "tui.toast.show", - z.object({ - title: z.string().optional(), - message: z.string(), - variant: z.enum(["info", "success", "warning", "error"]), - duration: z.number().default(5000).optional().describe("Duration in milliseconds"), + Schema.Struct({ + title: Schema.optional(Schema.String), + message: Schema.String, + variant: Schema.Literals(["info", "success", "warning", "error"]), + duration: Schema.optional(Schema.Number).annotate({ description: "Duration in milliseconds" }), }), ), SessionSelect: BusEvent.define( "tui.session.select", - z.object({ - sessionID: SessionID.zod.describe("Session ID to navigate to"), + Schema.Struct({ + sessionID: SessionID.annotate({ description: "Session ID to navigate to" }), }), ), } diff --git a/packages/opencode/src/cli/cmd/tui/ui/toast.tsx b/packages/opencode/src/cli/cmd/tui/ui/toast.tsx index f534d90b77..69674ba7ce 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/toast.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/toast.tsx @@ -4,10 +4,10 @@ import { useTheme } from "@tui/context/theme" import { useTerminalDimensions } from "@opentui/solid" import { SplitBorder } from "../component/border" import { TextAttributes } from "@opentui/core" -import z from "zod" +import { Schema } from "effect" import { type TuiEvent } from "../event" -export type ToastOptions = z.infer +export type ToastOptions = Schema.Schema.Type export function Toast() { const toast = useToast() diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index 27ba357ecc..478a12f664 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -3,7 +3,7 @@ import { InstanceState } from "@/effect" import { EffectBridge } from "@/effect" import type { InstanceContext } from "@/project/instance" import { SessionID, MessageID } from "@/session/schema" -import { Effect, Layer, Context } from "effect" +import { Effect, Layer, Context, Schema } from "effect" import z from "zod" import { Config } from "../config" import { MCP } from "../mcp" @@ -18,11 +18,11 @@ type State = { export const Event = { Executed: BusEvent.define( "command.executed", - z.object({ - name: z.string(), - sessionID: SessionID.zod, - arguments: z.string(), - messageID: MessageID.zod, + Schema.Struct({ + name: Schema.String, + sessionID: SessionID, + arguments: Schema.String, + messageID: MessageID, }), ), } diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 9814017d10..eab199232a 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -25,7 +25,7 @@ import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "e import { EffectFlock } from "@opencode-ai/shared/util/effect-flock" import { InstanceRef } from "@/effect/instance-ref" import { zod, ZodOverride } from "@/util/effect-zod" -import { NonNegativeInt, PositiveInt, withStatics } from "@/util/schema" +import { NonNegativeInt, PositiveInt, withStatics, type DeepMutable } from "@/util/schema" import { ConfigAgent } from "./agent" import { ConfigCommand } from "./command" import { ConfigFormatter } from "./formatter" @@ -249,26 +249,9 @@ export const Info = Schema.Struct({ })), ) -// Schema.Struct produces readonly types by default, but the service code -// below mutates Info objects directly (e.g. `config.mode = ...`). Strip the -// readonly recursively so callers get the same mutable shape zod inferred. -// -// `Types.DeepMutable` from effect-smol would be a drop-in, but its fallback -// branch `{ -readonly [K in keyof T]: ... }` collapses `unknown` to `{}` -// (since `keyof unknown = never`), which widens `Record` -// fields like `ConfigPlugin.Options`. The local version gates on -// `extends object` so `unknown` passes through. -// -// Tuple branch preserves `ConfigPlugin.Spec`'s `readonly [string, Options]` -// shape (otherwise the general array branch widens it to an array). -type DeepMutable = T extends readonly [unknown, ...unknown[]] - ? { -readonly [K in keyof T]: DeepMutable } - : T extends readonly (infer U)[] - ? DeepMutable[] - : T extends object - ? { -readonly [K in keyof T]: DeepMutable } - : T - +// 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> & { // plugin_origins is derived state, not a persisted config field. It keeps each winning plugin spec together // with the file and scope it came from so later runtime code can make location-sensitive decisions. diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index eb689df025..8519cb06c1 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -1,4 +1,5 @@ import z from "zod" +import { Schema } from "effect" import { setTimeout as sleep } from "node:timers/promises" import { fn } from "@/util/fn" import { Database, asc, eq, inArray } from "@/storage" @@ -25,36 +26,37 @@ import { errorData } from "@/util/error" import { AppRuntime } from "@/effect/app-runtime" import { waitEvent } from "./util" import { WorkspaceContext } from "./workspace-context" +import { NonNegativeInt } from "@/util/schema" export const Info = WorkspaceInfo.meta({ ref: "Workspace", }) export type Info = z.infer -export const ConnectionStatus = z.object({ - workspaceID: WorkspaceID.zod, - status: z.enum(["connected", "connecting", "disconnected", "error"]), +export const ConnectionStatus = Schema.Struct({ + workspaceID: WorkspaceID, + status: Schema.Literals(["connected", "connecting", "disconnected", "error"]), }) -export type ConnectionStatus = z.infer +export type ConnectionStatus = Schema.Schema.Type -const Restore = z.object({ - workspaceID: WorkspaceID.zod, - sessionID: SessionID.zod, - total: z.number().int().min(0), - step: z.number().int().min(0), +const Restore = Schema.Struct({ + workspaceID: WorkspaceID, + sessionID: SessionID, + total: NonNegativeInt, + step: NonNegativeInt, }) export const Event = { Ready: BusEvent.define( "workspace.ready", - z.object({ - name: z.string(), + Schema.Struct({ + name: Schema.String, }), ), Failed: BusEvent.define( "workspace.failed", - z.object({ - message: z.string(), + Schema.Struct({ + message: Schema.String, }), ), Restore: BusEvent.define("workspace.restore", Restore), diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index af4fbf76c8..ca791e4128 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -3,7 +3,7 @@ import { InstanceState } from "@/effect" import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Git } from "@/git" -import { Effect, Layer, Context, Scope } from "effect" +import { Effect, Layer, Context, Schema, Scope } from "effect" import * as Stream from "effect/Stream" import { formatPatch, structuredPatch } from "diff" import fuzzysort from "fuzzysort" @@ -76,8 +76,8 @@ export type Content = z.infer export const Event = { Edited: BusEvent.define( "file.edited", - z.object({ - file: z.string(), + Schema.Struct({ + file: Schema.String, }), ), } diff --git a/packages/opencode/src/file/watcher.ts b/packages/opencode/src/file/watcher.ts index dc20333758..0ac98b9c2d 100644 --- a/packages/opencode/src/file/watcher.ts +++ b/packages/opencode/src/file/watcher.ts @@ -1,4 +1,4 @@ -import { Cause, Effect, Layer, Context } from "effect" +import { Cause, Effect, Layer, Context, Schema } from "effect" // @ts-ignore import { createWrapper } from "@parcel/watcher/wrapper" import type ParcelWatcher from "@parcel/watcher" @@ -25,9 +25,9 @@ const SUBSCRIBE_TIMEOUT_MS = 10_000 export const Event = { Updated: BusEvent.define( "file.watcher.updated", - z.object({ - file: z.string(), - event: z.union([z.literal("add"), z.literal("change"), z.literal("unlink")]), + Schema.Struct({ + file: Schema.String, + event: Schema.Literals(["add", "change", "unlink"]), }), ), } diff --git a/packages/opencode/src/ide/index.ts b/packages/opencode/src/ide/index.ts index ee80c34741..f9ce1ec635 100644 --- a/packages/opencode/src/ide/index.ts +++ b/packages/opencode/src/ide/index.ts @@ -1,5 +1,6 @@ import { BusEvent } from "@/bus/bus-event" import z from "zod" +import { Schema } from "effect" import { NamedError } from "@opencode-ai/shared/util/error" import { Log } from "../util" import { Process } from "@/util" @@ -17,8 +18,8 @@ const log = Log.create({ service: "ide" }) export const Event = { Installed: BusEvent.define( "ide.installed", - z.object({ - ide: z.string(), + Schema.Struct({ + ide: Schema.String, }), ), } diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index 787f9ea8c5..bb3de3f3b5 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -21,14 +21,14 @@ export type ReleaseType = "patch" | "minor" | "major" export const Event = { Updated: BusEvent.define( "installation.updated", - z.object({ - version: z.string(), + Schema.Struct({ + version: Schema.String, }), ), UpdateAvailable: BusEvent.define( "installation.update-available", - z.object({ - version: z.string(), + Schema.Struct({ + version: Schema.String, }), ), } diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts index f6d5110a6c..e8050babfd 100644 --- a/packages/opencode/src/lsp/client.ts +++ b/packages/opencode/src/lsp/client.ts @@ -8,6 +8,7 @@ import { Log } from "../util" import { Process } from "../util" import { LANGUAGE_EXTENSIONS } from "./language" import z from "zod" +import { Schema } from "effect" import type * as LSPServer from "./server" import { NamedError } from "@opencode-ai/shared/util/error" import { withTimeout } from "../util/timeout" @@ -41,9 +42,9 @@ export const InitializeError = NamedError.create( export const Event = { Diagnostics: BusEvent.define( "lsp.client.diagnostics", - z.object({ - serverID: z.string(), - path: z.string(), + Schema.Struct({ + serverID: Schema.String, + path: Schema.String, }), ), } diff --git a/packages/opencode/src/lsp/lsp.ts b/packages/opencode/src/lsp/lsp.ts index 4c46cd9aa7..7741ff60e5 100644 --- a/packages/opencode/src/lsp/lsp.ts +++ b/packages/opencode/src/lsp/lsp.ts @@ -19,7 +19,7 @@ import { zod, ZodOverride } from "@/util/effect-zod" const log = Log.create({ service: "lsp" }) export const Event = { - Updated: BusEvent.define("lsp.updated", z.object({})), + Updated: BusEvent.define("lsp.updated", Schema.Struct({})), } const Position = Schema.Struct({ diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 09fcfc756a..385d7782a6 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -25,7 +25,7 @@ import { BusEvent } from "../bus/bus-event" import { Bus } from "@/bus" import { TuiEvent } from "@/cli/cmd/tui/event" import open from "open" -import { Effect, Exit, Layer, Option, Context, Stream } from "effect" +import { Effect, Exit, Layer, Option, Context, Schema, Stream } from "effect" import { EffectBridge } from "@/effect" import { InstanceState } from "@/effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" @@ -47,16 +47,16 @@ export type Resource = z.infer export const ToolsChanged = BusEvent.define( "mcp.tools.changed", - z.object({ - server: z.string(), + Schema.Struct({ + server: Schema.String, }), ) export const BrowserOpenFailed = BusEvent.define( "mcp.browser.open.failed", - z.object({ - mcpName: z.string(), - url: z.string(), + Schema.Struct({ + mcpName: Schema.String, + url: Schema.String, }), ) diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index 6943b3d93b..05c832016d 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -73,16 +73,14 @@ export class Approval extends Schema.Class("PermissionApproval")({ } export const Event = { - Asked: BusEvent.define("permission.asked", Request.zod), + Asked: BusEvent.define("permission.asked", Request), Replied: BusEvent.define( "permission.replied", - zod( - Schema.Struct({ - sessionID: SessionID, - requestID: PermissionID, - reply: Reply, - }), - ), + Schema.Struct({ + sessionID: SessionID, + requestID: PermissionID, + reply: Reply, + }), ), } diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index ab60cff7aa..70a9590640 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -53,7 +53,7 @@ export const Info = Schema.Struct({ export type Info = Types.DeepMutable> export const Event = { - Updated: BusEvent.define("project.updated", Info.zod), + Updated: BusEvent.define("project.updated", Info), } type Row = typeof ProjectTable.$inferSelect diff --git a/packages/opencode/src/project/vcs.ts b/packages/opencode/src/project/vcs.ts index ba028f7e8e..e8c6ff2ac7 100644 --- a/packages/opencode/src/project/vcs.ts +++ b/packages/opencode/src/project/vcs.ts @@ -1,4 +1,4 @@ -import { Effect, Layer, Context, Stream, Scope } from "effect" +import { Effect, Layer, Context, Schema, Stream, Scope } from "effect" import { formatPatch, structuredPatch } from "diff" import path from "path" import { Bus } from "@/bus" @@ -107,8 +107,8 @@ export type Mode = z.infer export const Event = { BranchUpdated: BusEvent.define( "vcs.branch.updated", - z.object({ - branch: z.string().optional(), + Schema.Struct({ + branch: Schema.optional(Schema.String), }), ), } diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index 3d00de596a..604fa77fbb 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -3,13 +3,14 @@ import { Bus } from "@/bus" import { InstanceState } from "@/effect" import { Instance } from "@/project/instance" import type { Proc } from "#pty" -import z from "zod" import { Log } from "../util" import { lazy } from "@opencode-ai/shared/util/lazy" import { Shell } from "@/shell/shell" import { Plugin } from "@/plugin" import { PtyID } from "./schema" -import { Effect, Layer, Context } from "effect" +import { Effect, Layer, Context, Schema, Types } from "effect" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" import { EffectBridge } from "@/effect" const log = Log.create({ service: "pty" }) @@ -53,47 +54,47 @@ const meta = (cursor: number) => { const pty = lazy(() => import("#pty")) -export const Info = z - .object({ - id: PtyID.zod, - title: z.string(), - command: z.string(), - args: z.array(z.string()), - cwd: z.string(), - status: z.enum(["running", "exited"]), - pid: z.number(), - }) - .meta({ ref: "Pty" }) - -export type Info = z.infer - -export const CreateInput = z.object({ - command: z.string().optional(), - args: z.array(z.string()).optional(), - cwd: z.string().optional(), - title: z.string().optional(), - env: z.record(z.string(), z.string()).optional(), +export const Info = Schema.Struct({ + id: PtyID, + title: Schema.String, + command: Schema.String, + args: Schema.Array(Schema.String), + cwd: Schema.String, + status: Schema.Literals(["running", "exited"]), + pid: Schema.Number, }) + .annotate({ identifier: "Pty" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) -export type CreateInput = z.infer +export type Info = Types.DeepMutable> -export const UpdateInput = z.object({ - title: z.string().optional(), - size: z - .object({ - rows: z.number(), - cols: z.number(), - }) - .optional(), -}) +export const CreateInput = Schema.Struct({ + command: Schema.optional(Schema.String), + args: Schema.optional(Schema.Array(Schema.String)), + cwd: Schema.optional(Schema.String), + title: Schema.optional(Schema.String), + env: Schema.optional(Schema.Record(Schema.String, Schema.String)), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) -export type UpdateInput = z.infer +export type CreateInput = Types.DeepMutable> + +export const UpdateInput = Schema.Struct({ + title: Schema.optional(Schema.String), + size: Schema.optional( + Schema.Struct({ + rows: Schema.Number, + cols: Schema.Number, + }), + ), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) + +export type UpdateInput = Types.DeepMutable> export const Event = { - Created: BusEvent.define("pty.created", z.object({ info: Info })), - Updated: BusEvent.define("pty.updated", z.object({ info: Info })), - Exited: BusEvent.define("pty.exited", z.object({ id: PtyID.zod, exitCode: z.number() })), - Deleted: BusEvent.define("pty.deleted", z.object({ id: PtyID.zod })), + Created: BusEvent.define("pty.created", Schema.Struct({ info: Info })), + Updated: BusEvent.define("pty.updated", Schema.Struct({ info: Info })), + Exited: BusEvent.define("pty.exited", Schema.Struct({ id: PtyID, exitCode: Schema.Number })), + Deleted: BusEvent.define("pty.deleted", Schema.Struct({ id: PtyID })), } export interface Interface { diff --git a/packages/opencode/src/question/index.ts b/packages/opencode/src/question/index.ts index 3b377c9827..626c71826c 100644 --- a/packages/opencode/src/question/index.ts +++ b/packages/opencode/src/question/index.ts @@ -94,9 +94,9 @@ class Rejected extends Schema.Class("QuestionRejected")({ }) {} export const Event = { - Asked: BusEvent.define("question.asked", Request.zod), - Replied: BusEvent.define("question.replied", zod(Replied)), - Rejected: BusEvent.define("question.rejected", zod(Rejected)), + Asked: BusEvent.define("question.asked", Request), + Replied: BusEvent.define("question.replied", Replied), + Rejected: BusEvent.define("question.rejected", Rejected), } export class RejectedError extends Schema.TaggedErrorClass()("QuestionRejectedError", {}) { @@ -194,7 +194,7 @@ export const layer = Layer.effect( yield* bus.publish(Event.Replied, { sessionID: existing.info.sessionID, requestID: existing.info.id, - answers: input.answers, + answers: input.answers.map((a) => [...a]), }) yield* Deferred.succeed(existing.deferred, input.answers) }) diff --git a/packages/opencode/src/server/event.ts b/packages/opencode/src/server/event.ts index 49325b2bb6..d5f10f47db 100644 --- a/packages/opencode/src/server/event.ts +++ b/packages/opencode/src/server/event.ts @@ -1,7 +1,7 @@ import { BusEvent } from "@/bus/bus-event" -import z from "zod" +import { Schema } from "effect" export const Event = { - Connected: BusEvent.define("server.connected", z.object({})), - Disposed: BusEvent.define("global.disposed", z.object({})), + Connected: BusEvent.define("server.connected", Schema.Struct({})), + Disposed: BusEvent.define("global.disposed", Schema.Struct({})), } diff --git a/packages/opencode/src/server/routes/control/workspace.ts b/packages/opencode/src/server/routes/control/workspace.ts index 9ff747b68a..d48c687f31 100644 --- a/packages/opencode/src/server/routes/control/workspace.ts +++ b/packages/opencode/src/server/routes/control/workspace.ts @@ -3,6 +3,7 @@ import { describeRoute, resolver, validator } from "hono-openapi" import z from "zod" import { listAdaptors } from "@/control-plane/adaptors" import { Workspace } from "@/control-plane/workspace" +import { zodObject } from "@/util/effect-zod" import { Instance } from "@/project/instance" import { errors } from "../../error" import { lazy } from "@/util/lazy" @@ -107,7 +108,7 @@ export const WorkspaceRoutes = lazy(() => description: "Workspace status", content: { "application/json": { - schema: resolver(z.array(Workspace.ConnectionStatus)), + schema: resolver(z.array(zodObject(Workspace.ConnectionStatus))), }, }, }, diff --git a/packages/opencode/src/server/routes/global.ts b/packages/opencode/src/server/routes/global.ts index 54f9972e02..a1199a4691 100644 --- a/packages/opencode/src/server/routes/global.ts +++ b/packages/opencode/src/server/routes/global.ts @@ -1,7 +1,7 @@ import { Hono, type Context } from "hono" import { describeRoute, resolver, validator } from "hono-openapi" import { streamSSE } from "hono/streaming" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import z from "zod" import { BusEvent } from "@/bus/bus-event" import { SyncEvent } from "@/sync" @@ -18,7 +18,7 @@ import { errors } from "../error" const log = Log.create({ service: "server" }) -export const GlobalDisposedEvent = BusEvent.define("global.disposed", z.object({})) +export const GlobalDisposedEvent = BusEvent.define("global.disposed", Schema.Struct({})) async function streamEvents(c: Context, subscribe: (q: AsyncQueue) => () => void) { return streamSSE(c, async (stream) => { diff --git a/packages/opencode/src/server/routes/instance/pty.ts b/packages/opencode/src/server/routes/instance/pty.ts index a25b66e9ff..51c4699241 100644 --- a/packages/opencode/src/server/routes/instance/pty.ts +++ b/packages/opencode/src/server/routes/instance/pty.ts @@ -23,7 +23,7 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) { description: "List of sessions", content: { "application/json": { - schema: resolver(Pty.Info.array()), + schema: resolver(Pty.Info.zod.array()), }, }, }, @@ -46,18 +46,18 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) { description: "Created session", content: { "application/json": { - schema: resolver(Pty.Info), + schema: resolver(Pty.Info.zod), }, }, }, ...errors(400), }, }), - validator("json", Pty.CreateInput), + validator("json", Pty.CreateInput.zod), async (c) => jsonRequest("PtyRoutes.create", c, function* () { const pty = yield* Pty.Service - return yield* pty.create(c.req.valid("json")) + return yield* pty.create(c.req.valid("json") as Pty.CreateInput) }), ) .get( @@ -71,7 +71,7 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) { description: "Session info", content: { "application/json": { - schema: resolver(Pty.Info), + schema: resolver(Pty.Info.zod), }, }, }, @@ -105,7 +105,7 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) { description: "Updated session", content: { "application/json": { - schema: resolver(Pty.Info), + schema: resolver(Pty.Info.zod), }, }, }, @@ -113,11 +113,11 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) { }, }), validator("param", z.object({ ptyID: PtyID.zod })), - validator("json", Pty.UpdateInput), + validator("json", Pty.UpdateInput.zod), async (c) => jsonRequest("PtyRoutes.update", c, function* () { const pty = yield* Pty.Service - return yield* pty.update(c.req.valid("param").ptyID, c.req.valid("json")) + return yield* pty.update(c.req.valid("param").ptyID, c.req.valid("json") as Pty.UpdateInput) }), ) .delete( diff --git a/packages/opencode/src/server/routes/instance/tui.ts b/packages/opencode/src/server/routes/instance/tui.ts index d6add67b97..a610590e87 100644 --- a/packages/opencode/src/server/routes/instance/tui.ts +++ b/packages/opencode/src/server/routes/instance/tui.ts @@ -1,9 +1,12 @@ import { Hono, type Context } from "hono" import { describeRoute, validator, resolver } from "hono-openapi" +import { Schema } from "effect" import z from "zod" import { Bus } from "@/bus" import { Session } from "@/session" +import type { SessionID } from "@/session/schema" import { TuiEvent } from "@/cli/cmd/tui/event" +import { zodObject } from "@/util/effect-zod" import { AsyncQueue } from "@/util/queue" import { errors } from "../../error" import { lazy } from "@/util/lazy" @@ -96,9 +99,9 @@ export const TuiRoutes = lazy(() => ...errors(400), }, }), - validator("json", TuiEvent.PromptAppend.properties), + validator("json", zodObject(TuiEvent.PromptAppend.properties)), async (c) => { - await Bus.publish(TuiEvent.PromptAppend, c.req.valid("json")) + await Bus.publish(TuiEvent.PromptAppend, c.req.valid("json") as { text: string }) return c.json(true) }, ) @@ -305,9 +308,9 @@ export const TuiRoutes = lazy(() => }, }, }), - validator("json", TuiEvent.ToastShow.properties), + validator("json", zodObject(TuiEvent.ToastShow.properties)), async (c) => { - await Bus.publish(TuiEvent.ToastShow, c.req.valid("json")) + await Bus.publish(TuiEvent.ToastShow, c.req.valid("json") as Schema.Schema.Type) return c.json(true) }, ) @@ -336,7 +339,7 @@ export const TuiRoutes = lazy(() => return z .object({ type: z.literal(def.type), - properties: def.properties, + properties: zodObject(def.properties), }) .meta({ ref: `Event.${def.type}`, @@ -345,8 +348,9 @@ export const TuiRoutes = lazy(() => ), ), async (c) => { - const evt = c.req.valid("json") - await Bus.publish(Object.values(TuiEvent).find((def) => def.type === evt.type)!, evt.properties) + const evt = c.req.valid("json") as { type: string; properties: Record } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await Bus.publish(Object.values(TuiEvent).find((def) => def.type === evt.type)! as any, evt.properties as any) return c.json(true) }, ) @@ -368,9 +372,9 @@ export const TuiRoutes = lazy(() => ...errors(400, 404), }, }), - validator("json", TuiEvent.SessionSelect.properties), + validator("json", zodObject(TuiEvent.SessionSelect.properties)), async (c) => { - const { sessionID } = c.req.valid("json") + const { sessionID } = c.req.valid("json") as { sessionID: SessionID } await runRequest( "TuiRoutes.sessionSelect", c, diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index defdb870d7..dc126e6837 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -13,7 +13,7 @@ import { Plugin } from "@/plugin" import { Config } from "@/config" import { NotFoundError } from "@/storage" import { ModelID, ProviderID } from "@/provider/schema" -import { Effect, Layer, Context } from "effect" +import { Effect, Layer, Context, Schema } from "effect" import { InstanceState } from "@/effect" import { isOverflow as overflow, usable } from "./overflow" import { makeRuntime } from "@/effect/run-service" @@ -24,8 +24,8 @@ const log = Log.create({ service: "session.compaction" }) export const Event = { Compacted: BusEvent.define( "session.compacted", - z.object({ - sessionID: SessionID.zod, + Schema.Struct({ + sessionID: SessionID, }), ), } diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index c5a35c7b41..d04645b736 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -617,12 +617,12 @@ export const Event = { }), PartDelta: BusEvent.define( "message.part.delta", - z.object({ - sessionID: SessionID.zod, - messageID: MessageID.zod, - partID: PartID.zod, - field: z.string(), - delta: z.string(), + Schema.Struct({ + sessionID: SessionID, + messageID: MessageID, + partID: PartID, + field: Schema.String, + delta: Schema.String, }), ), PartRemoved: SyncEvent.define({ diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index 1e046fdf79..f4fe3bf8bd 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -273,17 +273,18 @@ export const Event = { }), Diff: BusEvent.define( "session.diff", - z.object({ - sessionID: SessionID.zod, - diff: Snapshot.FileDiff.zod.array(), + Schema.Struct({ + sessionID: SessionID, + diff: Schema.Array(Snapshot.FileDiff), }), ), Error: BusEvent.define( "session.error", - z.object({ - sessionID: SessionID.zod.optional(), - // z.lazy defers access to break circular dep: session → message-v2 → provider → plugin → session - error: z.lazy(() => (MessageV2.Assistant.zod as unknown as z.ZodObject).shape.error), + Schema.Struct({ + sessionID: Schema.optional(SessionID), + // Reuses MessageV2.Assistant.fields.error (already Schema.optional) so + // the derived zod keeps the same discriminated-union shape on the bus. + error: MessageV2.Assistant.fields.error, }), ), } diff --git a/packages/opencode/src/session/status.ts b/packages/opencode/src/session/status.ts index b9b9fd7e74..e5165a7879 100644 --- a/packages/opencode/src/session/status.ts +++ b/packages/opencode/src/session/status.ts @@ -28,16 +28,16 @@ export type Info = Schema.Schema.Type export const Event = { Status: BusEvent.define( "session.status", - z.object({ - sessionID: SessionID.zod, - status: Info.zod, + Schema.Struct({ + sessionID: SessionID, + status: Info, }), ), // deprecated Idle: BusEvent.define( "session.idle", - z.object({ - sessionID: SessionID.zod, + Schema.Struct({ + sessionID: SessionID, }), ), } diff --git a/packages/opencode/src/session/todo.ts b/packages/opencode/src/session/todo.ts index 257b586ed7..c3a9b106b1 100644 --- a/packages/opencode/src/session/todo.ts +++ b/packages/opencode/src/session/todo.ts @@ -22,9 +22,9 @@ export type Info = Schema.Schema.Type export const Event = { Updated: BusEvent.define( "todo.updated", - z.object({ - sessionID: SessionID.zod, - todos: z.array(Info.zod), + Schema.Struct({ + sessionID: SessionID, + todos: Schema.Array(Info), }), ), } diff --git a/packages/opencode/src/sync/index.ts b/packages/opencode/src/sync/index.ts index 482ad4fbb6..35a5abd0b1 100644 --- a/packages/opencode/src/sync/index.ts +++ b/packages/opencode/src/sync/index.ts @@ -8,51 +8,48 @@ import { EventSequenceTable, EventTable } from "./event.sql" import { WorkspaceContext } from "@/control-plane/workspace-context" import { EventID } from "./schema" import { Flag } from "@/flag/flag" -import { Schema as EffectSchema, Types } from "effect" +import { Schema as EffectSchema } from "effect" import { zodObject } from "@/util/effect-zod" -import { isRecord } from "@/util/record" +import type { DeepMutable } from "@/util/schema" + +// Keep `Event["data"]` mutable because projectors mutate the persisted shape +// when writing to the database. Bus payloads (`Properties`) stay readonly — +// subscribers only read. export type Definition< + Type extends string = string, Schema extends EffectSchema.Top = EffectSchema.Top, BusSchema extends EffectSchema.Top = Schema, > = { - type: string + type: Type version: number aggregate: string - effectSchema: Schema - effectProperties: BusSchema - schema: z.ZodObject - - // This is temporary and only exists for compatibility with bus - // event definitions - properties: z.ZodObject + schema: Schema + // Bus event payload schema. Defaults to `schema` unless `busSchema` was + // passed at definition time (see `session.updated`, whose projector + // expands the persisted data to a `{ sessionID, info }` bus payload). + properties: BusSchema } export type Event = { id: string seq: number aggregateID: string - data: Types.DeepMutable> + data: DeepMutable> } -export type Properties = Types.DeepMutable< - EffectSchema.Schema.Type -> +export type Properties = EffectSchema.Schema.Type export type SerializedEvent = Event & { type: string } type ProjectorFunc = (db: Database.TxOrDb, data: unknown) => void +type ConvertEvent = (type: string, data: Event["data"]) => unknown | Promise export const registry = new Map() let projectors: Map | undefined const versions = new Map() let frozen = false -let convertEvent: (type: string, event: Event["data"]) => Promise | unknown - -function asRecord(input: unknown) { - if (isRecord(input)) return input - throw new Error(`SyncEvent.convertEvent must return an object, got: ${JSON.stringify(input)}`) -} +let convertEvent: ConvertEvent export function reset() { frozen = false @@ -60,7 +57,7 @@ export function reset() { convertEvent = (_, data) => data } -export function init(input: { projectors: Array<[Definition, ProjectorFunc]>; convertEvent?: typeof convertEvent }) { +export function init(input: { projectors: Array<[Definition, ProjectorFunc]>; convertEvent?: ConvertEvent }) { projectors = new Map(input.projectors) // Install all the latest event defs to the bus. We only ever emit @@ -76,7 +73,7 @@ export function init(input: { projectors: Array<[Definition, ProjectorFunc]>; co // Freeze the system so it clearly errors if events are defined // after `init` which would cause bugs frozen = true - convertEvent = input.convertEvent || ((_, data) => data) + convertEvent = input.convertEvent ?? ((_, data) => data) } export function versionedType(type: A): A @@ -96,21 +93,17 @@ export function define< aggregate: Agg schema: Schema busSchema?: BusSchema -}): Definition { +}): Definition { if (frozen) { throw new Error("Error defining sync event: sync system has been frozen") } - const effectProperties = (input.busSchema ?? input.schema) as BusSchema - const def = { type: input.type, version: input.version, aggregate: input.aggregate, - effectSchema: input.schema, - effectProperties, - schema: zodObject(input.schema), - properties: zodObject(effectProperties), + schema: input.schema, + properties: (input.busSchema ?? input.schema) as BusSchema, } versions.set(def.type, Math.max(def.version, versions.get(def.type) || 0)) @@ -167,12 +160,11 @@ function process(def: Def, event: Event, options: { Database.effect(() => { if (options?.publish) { const result = convertEvent(def.type, event.data) + const publish = (data: unknown) => ProjectBus.publish(def, data as Properties) if (result instanceof Promise) { - void result.then((data) => { - void ProjectBus.publish({ type: def.type, properties: def.properties }, asRecord(data)) - }) + void result.then(publish) } else { - void ProjectBus.publish({ type: def.type, properties: def.properties }, asRecord(result)) + void publish(result) } GlobalBus.emit("event", { @@ -292,7 +284,7 @@ export function payloads() { id: z.string(), seq: z.number(), aggregateID: z.literal(def.aggregate), - data: def.schema, + data: zodObject(def.schema), }) .meta({ ref: `SyncEvent.${def.type}`, diff --git a/packages/opencode/src/util/effect-zod.ts b/packages/opencode/src/util/effect-zod.ts index edbbf4d542..24ff9a10e1 100644 --- a/packages/opencode/src/util/effect-zod.ts +++ b/packages/opencode/src/util/effect-zod.ts @@ -59,8 +59,17 @@ function walk(ast: SchemaAST.AST): z.ZodTypeAny { function walkUncached(ast: SchemaAST.AST): z.ZodTypeAny { const override = (ast.annotations as any)?.[ZodOverride] as z.ZodTypeAny | undefined - if (override) return override + // `description` annotations layer on top of an override so callers can + // reuse a shared override schema (e.g. `SessionID`) and still add a + // per-field description on the outer wrapper. + const base = override ?? bodyWithChecks(ast) + const desc = SchemaAST.resolveDescription(ast) + const ref = SchemaAST.resolveIdentifier(ast) + const described = desc ? base.describe(desc) : base + return ref ? described.meta({ ref }) : described +} +function bodyWithChecks(ast: SchemaAST.AST): z.ZodTypeAny { // Schema.Class wraps its fields in a Declaration AST plus an encoding that // constructs the class instance. For the Zod derivation we want the plain // field shape (the decoded/consumer view), not the class instance — so @@ -74,11 +83,7 @@ function walkUncached(ast: SchemaAST.AST): z.ZodTypeAny { const hasEncoding = ast.encoding?.length && ast._tag !== "Declaration" const hasTransform = hasEncoding && !(SchemaAST.isOptional(ast) && extractDefault(ast) !== undefined) const base = hasTransform ? encoded(ast) : body(ast) - const checked = ast.checks?.length ? applyChecks(base, ast.checks, ast) : base - const desc = SchemaAST.resolveDescription(ast) - const ref = SchemaAST.resolveIdentifier(ast) - const described = desc ? checked.describe(desc) : checked - return ref ? described.meta({ ref }) : described + return ast.checks?.length ? applyChecks(base, ast.checks, ast) : base } // Walk the encoded side and apply each link's decode to produce the decoded diff --git a/packages/opencode/src/util/schema.ts b/packages/opencode/src/util/schema.ts index 7f94ee5d1e..0c50482bbd 100644 --- a/packages/opencode/src/util/schema.ts +++ b/packages/opencode/src/util/schema.ts @@ -10,6 +10,34 @@ export const PositiveInt = Schema.Int.check(Schema.isGreaterThan(0)) */ export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)) +/** + * Strip `readonly` from a nested type. Stand-in for `effect`'s `Types.DeepMutable` + * until `effect:core/x228my` ("Types.DeepMutable widens unknown to `{}`") lands. + * + * The upstream version falls through `unknown` into `{ -readonly [K in keyof T]: ... }` + * where `keyof unknown = never`, so `unknown` collapses to `{}`. This local + * version gates the object branch on `extends object` (which `unknown` does + * not) so `unknown` passes through untouched. + * + * Primitive bailout matches upstream — without it, branded strings like + * `string & Brand<"SessionID">` fall into the object branch and get their + * prototype methods walked. + * + * Tuple branch preserves readonly tuples (e.g. `ConfigPlugin.Spec`'s + * `readonly [string, Options]`); the general array branch would otherwise + * widen them to unbounded arrays. + */ +// eslint-disable-next-line @typescript-eslint/ban-types +export type DeepMutable = T extends string | number | boolean | bigint | symbol | Function + ? T + : T extends readonly [unknown, ...unknown[]] + ? { -readonly [K in keyof T]: DeepMutable } + : T extends readonly (infer U)[] + ? DeepMutable[] + : T extends object + ? { -readonly [K in keyof T]: DeepMutable } + : T + /** * Attach static methods to a schema object. Designed to be used with `.pipe()`: * @@ -26,13 +54,16 @@ export const withStatics = (schema: S): S & M => Object.assign(schema, methods(schema)) -declare const NewtypeBrand: unique symbol -type NewtypeBrand = { readonly [NewtypeBrand]: Tag } - /** * Nominal wrapper for scalar types. The class itself is a valid schema — * pass it directly to `Schema.decode`, `Schema.decodeEffect`, etc. * + * Overrides `~type.make` on the derived `Schema.Opaque` so `Schema.Schema.Type` + * of a field using this newtype resolves to `Self` rather than the underlying + * branded phantom. Without that override, passing a class instance to code + * typed against `Schema.Schema.Type` would require a cast even + * though the values are structurally equivalent at runtime. + * * @example * class QuestionID extends Newtype()("QuestionID", Schema.String) { * static make(id: string): QuestionID { @@ -44,10 +75,8 @@ type NewtypeBrand = { readonly [NewtypeBrand]: Tag } */ export function Newtype() { return (tag: Tag, schema: S) => { - type Branded = NewtypeBrand - abstract class Base { - declare readonly [NewtypeBrand]: Tag + declare readonly _newtype: Tag static make(value: Schema.Schema.Type): Self { return value as unknown as Self @@ -56,8 +85,10 @@ export function Newtype() { Object.setPrototypeOf(Base, schema) - return Base as unknown as (abstract new (_: never) => Branded) & { + return Base as unknown as (abstract new (_: never) => { readonly _newtype: Tag }) & { readonly make: (value: Schema.Schema.Type) => Self - } & Omit, "make"> + } & Omit, "make" | "~type.make"> & { + readonly "~type.make": Self + } } } diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index bbebeaa496..e122fe453b 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -13,7 +13,7 @@ import { errorMessage } from "../util/error" import { BusEvent } from "@/bus/bus-event" import { GlobalBus } from "@/bus/global" import { Git } from "@/git" -import { Effect, Layer, Path, Scope, Context, Stream } from "effect" +import { Effect, Layer, Path, Schema, Scope, Context, Stream } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" import { AppFileSystem } from "@opencode-ai/shared/filesystem" @@ -26,15 +26,15 @@ const log = Log.create({ service: "worktree" }) export const Event = { Ready: BusEvent.define( "worktree.ready", - z.object({ - name: z.string(), - branch: z.string(), + Schema.Struct({ + name: Schema.String, + branch: Schema.String, }), ), Failed: BusEvent.define( "worktree.failed", - z.object({ - message: z.string(), + Schema.Struct({ + message: Schema.String, }), ), } diff --git a/packages/opencode/test/bus/bus-effect.test.ts b/packages/opencode/test/bus/bus-effect.test.ts index 6f96a89c87..3d602ae6fd 100644 --- a/packages/opencode/test/bus/bus-effect.test.ts +++ b/packages/opencode/test/bus/bus-effect.test.ts @@ -1,6 +1,5 @@ import { describe, expect } from "bun:test" -import { Deferred, Effect, Layer, Stream } from "effect" -import z from "zod" +import { Deferred, Effect, Layer, Schema, Stream } from "effect" import { Bus } from "../../src/bus" import { BusEvent } from "../../src/bus/bus-event" import { Instance } from "../../src/project/instance" @@ -9,8 +8,8 @@ import { provideInstance, provideTmpdirInstance, tmpdirScoped } from "../fixture import { testEffect } from "../lib/effect" const TestEvent = { - Ping: BusEvent.define("test.effect.ping", z.object({ value: z.number() })), - Pong: BusEvent.define("test.effect.pong", z.object({ message: z.string() })), + Ping: BusEvent.define("test.effect.ping", Schema.Struct({ value: Schema.Number })), + Pong: BusEvent.define("test.effect.pong", Schema.Struct({ message: Schema.String })), } const node = CrossSpawnSpawner.defaultLayer diff --git a/packages/opencode/test/bus/bus-integration.test.ts b/packages/opencode/test/bus/bus-integration.test.ts index e42bd5299e..2808344577 100644 --- a/packages/opencode/test/bus/bus-integration.test.ts +++ b/packages/opencode/test/bus/bus-integration.test.ts @@ -1,11 +1,11 @@ import { afterEach, describe, expect, test } from "bun:test" -import z from "zod" +import { Schema } from "effect" import { Bus } from "../../src/bus" import { BusEvent } from "../../src/bus/bus-event" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" -const TestEvent = BusEvent.define("test.integration", z.object({ value: z.number() })) +const TestEvent = BusEvent.define("test.integration", Schema.Struct({ value: Schema.Number })) function withInstance(directory: string, fn: () => Promise) { return Instance.provide({ directory, fn }) @@ -42,7 +42,7 @@ describe("Bus integration: acquireRelease subscriber pattern", () => { await using tmp = await tmpdir() const received: Array<{ type: string; value?: number }> = [] - const OtherEvent = BusEvent.define("test.other", z.object({ value: z.number() })) + const OtherEvent = BusEvent.define("test.other", Schema.Struct({ value: Schema.Number })) await withInstance(tmp.path, async () => { Bus.subscribeAll((evt) => { diff --git a/packages/opencode/test/bus/bus.test.ts b/packages/opencode/test/bus/bus.test.ts index 3df179787d..cdacdd5179 100644 --- a/packages/opencode/test/bus/bus.test.ts +++ b/packages/opencode/test/bus/bus.test.ts @@ -1,13 +1,13 @@ import { afterEach, describe, expect, test } from "bun:test" -import z from "zod" +import { Schema } from "effect" import { Bus } from "../../src/bus" import { BusEvent } from "../../src/bus/bus-event" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" const TestEvent = { - Ping: BusEvent.define("test.ping", z.object({ value: z.number() })), - Pong: BusEvent.define("test.pong", z.object({ message: z.string() })), + Ping: BusEvent.define("test.ping", Schema.Struct({ value: Schema.Number })), + Pong: BusEvent.define("test.pong", Schema.Struct({ message: Schema.String })), } function withInstance(directory: string, fn: () => Promise) { diff --git a/packages/opencode/test/session/session.test.ts b/packages/opencode/test/session/session.test.ts index f63ad9beed..d4a1d711d8 100644 --- a/packages/opencode/test/session/session.test.ts +++ b/packages/opencode/test/session/session.test.ts @@ -111,9 +111,12 @@ describe("step-finish token propagation via Bus event", () => { mode: "", } as unknown as MessageV2.Info) + // Bus subscribers receive readonly Schema.Type payloads; `MessageV2.Part` + // is the mutable domain type. Cast bridges the two — safe because the + // test only reads the value afterwards. let received: MessageV2.Part | undefined const unsub = Bus.subscribe(MessageV2.Event.PartUpdated, (event) => { - received = event.properties.part + received = event.properties.part as MessageV2.Part }) const tokens = { From 24892559ae10e8df5bde8211b7e4c7a6851f3de6 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 19:38:54 +0000 Subject: [PATCH 14/59] chore: generate --- .../opencode/src/server/routes/instance/tui.ts | 5 ++++- packages/sdk/openapi.json | 18 ++---------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/tui.ts b/packages/opencode/src/server/routes/instance/tui.ts index a610590e87..932cf509eb 100644 --- a/packages/opencode/src/server/routes/instance/tui.ts +++ b/packages/opencode/src/server/routes/instance/tui.ts @@ -310,7 +310,10 @@ export const TuiRoutes = lazy(() => }), validator("json", zodObject(TuiEvent.ToastShow.properties)), async (c) => { - await Bus.publish(TuiEvent.ToastShow, c.req.valid("json") as Schema.Schema.Type) + await Bus.publish( + TuiEvent.ToastShow, + c.req.valid("json") as Schema.Schema.Type, + ) return c.json(true) }, ) diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index e54648e198..0c3d189722 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -6804,7 +6804,6 @@ }, "duration": { "description": "Duration in milliseconds", - "default": 5000, "type": "number" } }, @@ -7638,20 +7637,8 @@ "type": "string" }, "event": { - "anyOf": [ - { - "type": "string", - "const": "add" - }, - { - "type": "string", - "const": "change" - }, - { - "type": "string", - "const": "unlink" - } - ] + "type": "string", + "enum": ["add", "change", "unlink"] } }, "required": ["file", "event"] @@ -8507,7 +8494,6 @@ }, "duration": { "description": "Duration in milliseconds", - "default": 5000, "type": "number" } }, From 3910a6e527c87f514c479a1c1c3d6d5f4d1aa315 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 16:09:34 -0400 Subject: [PATCH 15/59] refactor(tool): migrate tool framework + all 18 built-in tools to Effect Schema (#23244) --- .../server/routes/instance/experimental.ts | 3 +- packages/opencode/src/session/prompt.ts | 3 +- packages/opencode/src/tool/apply_patch.ts | 13 +- packages/opencode/src/tool/bash.ts | 26 +- packages/opencode/src/tool/codesearch.ts | 35 +- packages/opencode/src/tool/edit.ts | 19 +- packages/opencode/src/tool/glob.ts | 20 +- packages/opencode/src/tool/grep.ts | 18 +- packages/opencode/src/tool/invalid.ts | 13 +- packages/opencode/src/tool/lsp.ts | 21 +- packages/opencode/src/tool/plan.ts | 7 +- packages/opencode/src/tool/question.ts | 13 +- packages/opencode/src/tool/read.ts | 26 +- packages/opencode/src/tool/registry.ts | 12 +- packages/opencode/src/tool/skill.ts | 9 +- packages/opencode/src/tool/task.ts | 27 +- packages/opencode/src/tool/todo.ts | 37 +- packages/opencode/src/tool/tool.ts | 55 +- packages/opencode/src/tool/webfetch.ts | 22 +- packages/opencode/src/tool/websearch.ts | 35 +- packages/opencode/src/tool/write.ts | 14 +- packages/opencode/src/util/effect-zod.ts | 11 + .../__snapshots__/parameters.test.ts.snap | 495 ++++++++++++++++++ .../opencode/test/tool/parameters.test.ts | 260 +++++++++ .../opencode/test/tool/tool-define.test.ts | 46 +- 25 files changed, 1035 insertions(+), 205 deletions(-) create mode 100644 packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap create mode 100644 packages/opencode/test/tool/parameters.test.ts diff --git a/packages/opencode/src/server/routes/instance/experimental.ts b/packages/opencode/src/server/routes/instance/experimental.ts index 93a5d98c94..f13003cb4e 100644 --- a/packages/opencode/src/server/routes/instance/experimental.ts +++ b/packages/opencode/src/server/routes/instance/experimental.ts @@ -1,6 +1,7 @@ import { Hono } from "hono" import { describeRoute, validator, resolver } from "hono-openapi" import z from "zod" +import * as EffectZod from "@/util/effect-zod" import { ProviderID, ModelID } from "@/provider/schema" import { ToolRegistry } from "@/tool" import { Worktree } from "@/worktree" @@ -213,7 +214,7 @@ export const ExperimentalRoutes = lazy(() => tools.map((t) => ({ id: t.id, description: t.description, - parameters: z.toJSONSchema(t.parameters), + parameters: EffectZod.toJsonSchema(t.parameters), })), ) }, diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 0f48eb64ec..5f3530bcef 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1,6 +1,7 @@ import path from "path" import os from "os" import z from "zod" +import * as EffectZod from "@/util/effect-zod" import { SessionID, MessageID, PartID } from "./schema" import { MessageV2 } from "./message-v2" import { Log } from "../util" @@ -405,7 +406,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the providerID: input.model.providerID, agent: input.agent, })) { - const schema = ProviderTransform.schema(input.model, z.toJSONSchema(item.parameters)) + const schema = ProviderTransform.schema(input.model, EffectZod.toJsonSchema(item.parameters)) tools[item.id] = tool({ description: item.description, inputSchema: jsonSchema(schema), diff --git a/packages/opencode/src/tool/apply_patch.ts b/packages/opencode/src/tool/apply_patch.ts index 33112c43c5..effc428c71 100644 --- a/packages/opencode/src/tool/apply_patch.ts +++ b/packages/opencode/src/tool/apply_patch.ts @@ -1,6 +1,5 @@ -import z from "zod" import * as path from "path" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import * as Tool from "./tool" import { Bus } from "../bus" import { FileWatcher } from "../file/watcher" @@ -16,8 +15,8 @@ import { File } from "../file" import { Format } from "../format" import * as Bom from "@/util/bom" -const PatchParams = z.object({ - patchText: z.string().describe("The full patch text that describes all changes to be made"), +export const Parameters = Schema.Struct({ + patchText: Schema.String.annotate({ description: "The full patch text that describes all changes to be made" }), }) export const ApplyPatchTool = Tool.define( @@ -28,7 +27,7 @@ export const ApplyPatchTool = Tool.define( const format = yield* Format.Service const bus = yield* Bus.Service - const run = Effect.fn("ApplyPatchTool.execute")(function* (params: z.infer, ctx: Tool.Context) { + const run = Effect.fn("ApplyPatchTool.execute")(function* (params: Schema.Schema.Type, ctx: Tool.Context) { if (!params.patchText) { return yield* Effect.fail(new Error("patchText is required")) } @@ -297,8 +296,8 @@ export const ApplyPatchTool = Tool.define( return { description: DESCRIPTION, - parameters: PatchParams, - execute: (params: z.infer, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), + parameters: Parameters, + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), } }), ) diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index 6260b22216..2d4d59b1bf 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -1,4 +1,4 @@ -import z from "zod" +import { Schema } from "effect" import os from "os" import { createWriteStream } from "node:fs" import * as Tool from "./tool" @@ -50,20 +50,16 @@ const FILES = new Set([ const FLAGS = new Set(["-destination", "-literalpath", "-path"]) const SWITCHES = new Set(["-confirm", "-debug", "-force", "-nonewline", "-recurse", "-verbose", "-whatif"]) -const Parameters = z.object({ - command: z.string().describe("The command to execute"), - timeout: z.number().describe("Optional timeout in milliseconds").optional(), - workdir: z - .string() - .describe( - `The working directory to run the command in. Defaults to the current directory. Use this instead of 'cd' commands.`, - ) - .optional(), - description: z - .string() - .describe( +export const Parameters = Schema.Struct({ + command: Schema.String.annotate({ description: "The command to execute" }), + timeout: Schema.optional(Schema.Number).annotate({ description: "Optional timeout in milliseconds" }), + workdir: Schema.optional(Schema.String).annotate({ + description: `The working directory to run the command in. Defaults to the current directory. Use this instead of 'cd' commands.`, + }), + description: Schema.String.annotate({ + description: "Clear, concise description of what this command does in 5-10 words. Examples:\nInput: ls\nOutput: Lists files in current directory\n\nInput: git status\nOutput: Shows working tree status\n\nInput: npm install\nOutput: Installs package dependencies\n\nInput: mkdir foo\nOutput: Creates directory 'foo'", - ), + }), }) type Part = { @@ -587,7 +583,7 @@ export const BashTool = Tool.define( .replaceAll("${maxLines}", String(Truncate.MAX_LINES)) .replaceAll("${maxBytes}", String(Truncate.MAX_BYTES)), parameters: Parameters, - execute: (params: z.infer, ctx: Tool.Context) => + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { const cwd = params.workdir ? yield* resolvePath(params.workdir, Instance.directory, shell) diff --git a/packages/opencode/src/tool/codesearch.ts b/packages/opencode/src/tool/codesearch.ts index ac9961e250..e10d21175e 100644 --- a/packages/opencode/src/tool/codesearch.ts +++ b/packages/opencode/src/tool/codesearch.ts @@ -1,10 +1,23 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import { HttpClient } from "effect/unstable/http" import * as Tool from "./tool" import * as McpExa from "./mcp-exa" import DESCRIPTION from "./codesearch.txt" +export const Parameters = Schema.Struct({ + query: Schema.String.annotate({ + description: + "Search query to find relevant context for APIs, Libraries, and SDKs. For example, 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware', 'Next js partial prerendering configuration'", + }), + tokensNum: Schema.Number.check(Schema.isGreaterThanOrEqualTo(1000)) + .check(Schema.isLessThanOrEqualTo(50000)) + .pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(5000))) + .annotate({ + description: + "Number of tokens to return (1000-50000). Default is 5000 tokens. Adjust this value based on how much context you need - use lower values for focused queries and higher values for comprehensive documentation.", + }), +}) + export const CodeSearchTool = Tool.define( "codesearch", Effect.gen(function* () { @@ -12,21 +25,7 @@ export const CodeSearchTool = Tool.define( return { description: DESCRIPTION, - parameters: z.object({ - query: z - .string() - .describe( - "Search query to find relevant context for APIs, Libraries, and SDKs. For example, 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware', 'Next js partial prerendering configuration'", - ), - tokensNum: z - .number() - .min(1000) - .max(50000) - .default(5000) - .describe( - "Number of tokens to return (1000-50000). Default is 5000 tokens. Adjust this value based on how much context you need - use lower values for focused queries and higher values for comprehensive documentation.", - ), - }), + parameters: Parameters, execute: (params: { query: string; tokensNum: number }, ctx: Tool.Context) => Effect.gen(function* () { yield* ctx.ask({ @@ -45,7 +44,7 @@ export const CodeSearchTool = Tool.define( McpExa.CodeArgs, { query: params.query, - tokensNum: params.tokensNum || 5000, + tokensNum: params.tokensNum, }, "30 seconds", ) diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index 35dd85b476..cfff5a0a30 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -3,9 +3,8 @@ // https://github.com/google-gemini/gemini-cli/blob/main/packages/core/src/utils/editCorrector.ts // https://github.com/cline/cline/blob/main/evals/diff-edits/diff-apply/diff-06-26-25.ts -import z from "zod" import * as path from "path" -import { Effect, Semaphore } from "effect" +import { Effect, Schema, Semaphore } from "effect" import * as Tool from "./tool" import { LSP } from "../lsp" import { createTwoFilesPatch, diffLines } from "diff" @@ -45,11 +44,15 @@ function lock(filePath: string) { return next } -const Parameters = z.object({ - filePath: z.string().describe("The absolute path to the file to modify"), - oldString: z.string().describe("The text to replace"), - newString: z.string().describe("The text to replace it with (must be different from oldString)"), - replaceAll: z.boolean().optional().describe("Replace all occurrences of oldString (default false)"), +export const Parameters = Schema.Struct({ + filePath: Schema.String.annotate({ description: "The absolute path to the file to modify" }), + oldString: Schema.String.annotate({ description: "The text to replace" }), + newString: Schema.String.annotate({ + description: "The text to replace it with (must be different from oldString)", + }), + replaceAll: Schema.optional(Schema.Boolean).annotate({ + description: "Replace all occurrences of oldString (default false)", + }), }) export const EditTool = Tool.define( @@ -63,7 +66,7 @@ export const EditTool = Tool.define( return { description: DESCRIPTION, parameters: Parameters, - execute: (params: z.infer, ctx: Tool.Context) => + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { if (!params.filePath) { throw new Error("filePath is required") diff --git a/packages/opencode/src/tool/glob.ts b/packages/opencode/src/tool/glob.ts index 673bb9cc8f..aeecfecb72 100644 --- a/packages/opencode/src/tool/glob.ts +++ b/packages/opencode/src/tool/glob.ts @@ -1,6 +1,5 @@ import path from "path" -import z from "zod" -import { Effect, Option } from "effect" +import { Effect, Option, Schema } from "effect" import * as Stream from "effect/Stream" import { InstanceState } from "@/effect" import { AppFileSystem } from "@opencode-ai/shared/filesystem" @@ -9,6 +8,13 @@ import { assertExternalDirectoryEffect } from "./external-directory" import DESCRIPTION from "./glob.txt" import * as Tool from "./tool" +export const Parameters = Schema.Struct({ + pattern: Schema.String.annotate({ description: "The glob pattern to match files against" }), + path: Schema.optional(Schema.String).annotate({ + description: `The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - simply omit it for the default behavior. Must be a valid directory path if provided.`, + }), +}) + export const GlobTool = Tool.define( "glob", Effect.gen(function* () { @@ -17,15 +23,7 @@ export const GlobTool = Tool.define( return { description: DESCRIPTION, - parameters: z.object({ - pattern: z.string().describe("The glob pattern to match files against"), - path: z - .string() - .optional() - .describe( - `The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - simply omit it for the default behavior. Must be a valid directory path if provided.`, - ), - }), + parameters: Parameters, execute: (params: { pattern: string; path?: string }, ctx: Tool.Context) => Effect.gen(function* () { const ins = yield* InstanceState.context diff --git a/packages/opencode/src/tool/grep.ts b/packages/opencode/src/tool/grep.ts index caa75edad5..4160054311 100644 --- a/packages/opencode/src/tool/grep.ts +++ b/packages/opencode/src/tool/grep.ts @@ -1,5 +1,5 @@ import path from "path" -import z from "zod" +import { Schema } from "effect" import { Effect, Option } from "effect" import { InstanceState } from "@/effect" import { AppFileSystem } from "@opencode-ai/shared/filesystem" @@ -10,6 +10,16 @@ import * as Tool from "./tool" const MAX_LINE_LENGTH = 2000 +export const Parameters = Schema.Struct({ + pattern: Schema.String.annotate({ description: "The regex pattern to search for in file contents" }), + path: Schema.optional(Schema.String).annotate({ + description: "The directory to search in. Defaults to the current working directory.", + }), + include: Schema.optional(Schema.String).annotate({ + description: 'File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")', + }), +}) + export const GrepTool = Tool.define( "grep", Effect.gen(function* () { @@ -18,11 +28,7 @@ export const GrepTool = Tool.define( return { description: DESCRIPTION, - parameters: z.object({ - pattern: z.string().describe("The regex pattern to search for in file contents"), - path: z.string().optional().describe("The directory to search in. Defaults to the current working directory."), - include: z.string().optional().describe('File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")'), - }), + parameters: Parameters, execute: (params: { pattern: string; path?: string; include?: string }, ctx: Tool.Context) => Effect.gen(function* () { const empty = { diff --git a/packages/opencode/src/tool/invalid.ts b/packages/opencode/src/tool/invalid.ts index aca3618b6d..b8d145d0be 100644 --- a/packages/opencode/src/tool/invalid.ts +++ b/packages/opencode/src/tool/invalid.ts @@ -1,15 +1,16 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import * as Tool from "./tool" +export const Parameters = Schema.Struct({ + tool: Schema.String, + error: Schema.String, +}) + export const InvalidTool = Tool.define( "invalid", Effect.succeed({ description: "Do not use", - parameters: z.object({ - tool: z.string(), - error: z.string(), - }), + parameters: Parameters, execute: (params: { tool: string; error: string }) => Effect.succeed({ title: "Invalid Tool", diff --git a/packages/opencode/src/tool/lsp.ts b/packages/opencode/src/tool/lsp.ts index 0a0edc61ed..29c6a8d843 100644 --- a/packages/opencode/src/tool/lsp.ts +++ b/packages/opencode/src/tool/lsp.ts @@ -1,5 +1,4 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import * as Tool from "./tool" import path from "path" import { LSP } from "../lsp" @@ -21,6 +20,17 @@ const operations = [ "outgoingCalls", ] as const +export const Parameters = Schema.Struct({ + operation: Schema.Literals(operations).annotate({ description: "The LSP operation to perform" }), + filePath: Schema.String.annotate({ description: "The absolute or relative path to the file" }), + line: Schema.Number.check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(1)) + .annotate({ description: "The line number (1-based, as shown in editors)" }), + character: Schema.Number.check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(1)) + .annotate({ description: "The character offset (1-based, as shown in editors)" }), +}) + export const LspTool = Tool.define( "lsp", Effect.gen(function* () { @@ -29,12 +39,7 @@ export const LspTool = Tool.define( return { description: DESCRIPTION, - parameters: z.object({ - operation: z.enum(operations).describe("The LSP operation to perform"), - filePath: z.string().describe("The absolute or relative path to the file"), - line: z.number().int().min(1).describe("The line number (1-based, as shown in editors)"), - character: z.number().int().min(1).describe("The character offset (1-based, as shown in editors)"), - }), + parameters: Parameters, execute: ( args: { operation: (typeof operations)[number]; filePath: string; line: number; character: number }, ctx: Tool.Context, diff --git a/packages/opencode/src/tool/plan.ts b/packages/opencode/src/tool/plan.ts index fd7276e09c..8e2f11360e 100644 --- a/packages/opencode/src/tool/plan.ts +++ b/packages/opencode/src/tool/plan.ts @@ -1,6 +1,5 @@ -import z from "zod" import path from "path" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import * as Tool from "./tool" import { Question } from "../question" import { Session } from "../session" @@ -17,6 +16,8 @@ function getLastModel(sessionID: SessionID) { return undefined } +export const Parameters = Schema.Struct({}) + export const PlanExitTool = Tool.define( "plan_exit", Effect.gen(function* () { @@ -26,7 +27,7 @@ export const PlanExitTool = Tool.define( return { description: EXIT_DESCRIPTION, - parameters: z.object({}), + parameters: Parameters, execute: (_params: {}, ctx: Tool.Context) => Effect.gen(function* () { const info = yield* session.get(ctx.sessionID) diff --git a/packages/opencode/src/tool/question.ts b/packages/opencode/src/tool/question.ts index e5bb33aa69..51f1e71e28 100644 --- a/packages/opencode/src/tool/question.ts +++ b/packages/opencode/src/tool/question.ts @@ -1,26 +1,25 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import * as Tool from "./tool" import { Question } from "../question" import DESCRIPTION from "./question.txt" -const parameters = z.object({ - questions: z.array(Question.Prompt.zod).describe("Questions to ask"), +export const Parameters = Schema.Struct({ + questions: Schema.mutable(Schema.Array(Question.Prompt)).annotate({ description: "Questions to ask" }), }) type Metadata = { answers: ReadonlyArray } -export const QuestionTool = Tool.define( +export const QuestionTool = Tool.define( "question", Effect.gen(function* () { const question = yield* Question.Service return { description: DESCRIPTION, - parameters, - execute: (params: z.infer, ctx: Tool.Context) => + parameters: Parameters, + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { const answers = yield* question.ask({ sessionID: ctx.sessionID, diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts index a9b95346a1..e7bfc6af33 100644 --- a/packages/opencode/src/tool/read.ts +++ b/packages/opencode/src/tool/read.ts @@ -1,5 +1,4 @@ -import z from "zod" -import { Effect, Option, Scope } from "effect" +import { Effect, Option, Schema, Scope } from "effect" import { createReadStream } from "fs" import * as path from "path" import { createInterface } from "readline" @@ -19,10 +18,19 @@ const MAX_BYTES = 50 * 1024 const MAX_BYTES_LABEL = `${MAX_BYTES / 1024} KB` const SAMPLE_BYTES = 4096 -const parameters = z.object({ - filePath: z.string().describe("The absolute path to the file or directory to read"), - offset: z.coerce.number().describe("The line number to start reading from (1-indexed)").optional(), - limit: z.coerce.number().describe("The maximum number of lines to read (defaults to 2000)").optional(), +// `offset` and `limit` were originally `z.coerce.number()` — the runtime +// coercion was useful when the tool was called from a shell but serves no +// purpose in the LLM tool-call path (the model emits typed JSON). The JSON +// Schema output is identical (`type: "number"`), so the LLM view is +// unchanged; purely CLI-facing uses must now send numbers rather than strings. +export const Parameters = Schema.Struct({ + filePath: Schema.String.annotate({ description: "The absolute path to the file or directory to read" }), + offset: Schema.optional(Schema.Number).annotate({ + description: "The line number to start reading from (1-indexed)", + }), + limit: Schema.optional(Schema.Number).annotate({ + description: "The maximum number of lines to read (defaults to 2000)", + }), }) export const ReadTool = Tool.define( @@ -140,7 +148,7 @@ export const ReadTool = Tool.define( return nonPrintableCount / bytes.length > 0.3 } - const run = Effect.fn("ReadTool.execute")(function* (params: z.infer, ctx: Tool.Context) { + const run = Effect.fn("ReadTool.execute")(function* (params: Schema.Schema.Type, ctx: Tool.Context) { if (params.offset !== undefined && params.offset < 1) { return yield* Effect.fail(new Error("offset must be greater than or equal to 1")) } @@ -275,8 +283,8 @@ export const ReadTool = Tool.define( return { description: DESCRIPTION, - parameters, - execute: (params: z.infer, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), + parameters: Parameters, + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), } }), ) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 0211e33bcb..539ad63202 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -15,7 +15,9 @@ import { SkillTool } from "./skill" import * as Tool from "./tool" import { Config } from "../config" import { type ToolContext as PluginToolContext, type ToolDefinition } from "@opencode-ai/plugin" +import { Schema } from "effect" import z from "zod" +import { ZodOverride } from "@/util/effect-zod" import { Plugin } from "../plugin" import { Provider } from "../provider" import { ProviderID, type ModelID } from "../provider/schema" @@ -120,9 +122,17 @@ export const layer: Layer.Layer< const custom: Tool.Def[] = [] function fromPlugin(id: string, def: ToolDefinition): Tool.Def { + // Plugin tools define their args as a raw Zod shape. Wrap the + // derived Zod object in a `Schema.declare` so it slots into the + // Schema-typed framework, and annotate with `ZodOverride` so the + // walker emits the original Zod object for LLM JSON Schema. + const zodParams = z.object(def.args) + const parameters = Schema.declare((u): u is unknown => zodParams.safeParse(u).success).annotate({ + [ZodOverride]: zodParams, + }) return { id, - parameters: z.object(def.args), + parameters, description: def.description, execute: (args, toolCtx) => Effect.gen(function* () { diff --git a/packages/opencode/src/tool/skill.ts b/packages/opencode/src/tool/skill.ts index d86faec2b4..8c41077be5 100644 --- a/packages/opencode/src/tool/skill.ts +++ b/packages/opencode/src/tool/skill.ts @@ -1,15 +1,14 @@ import path from "path" import { pathToFileURL } from "url" -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import * as Stream from "effect/Stream" import { Ripgrep } from "../file/ripgrep" import { Skill } from "../skill" import * as Tool from "./tool" import DESCRIPTION from "./skill.txt" -const Parameters = z.object({ - name: z.string().describe("The name of the skill from available_skills"), +export const Parameters = Schema.Struct({ + name: Schema.String.annotate({ description: "The name of the skill from available_skills" }), }) export const SkillTool = Tool.define( @@ -21,7 +20,7 @@ export const SkillTool = Tool.define( return { description: DESCRIPTION, parameters: Parameters, - execute: (params: z.infer, ctx: Tool.Context) => + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { const info = yield* skill.get(params.name) if (!info) { diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index 3da0664f3d..98f6bdd98f 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -1,13 +1,12 @@ import * as Tool from "./tool" import DESCRIPTION from "./task.txt" -import z from "zod" import { Session } from "../session" import { SessionID, MessageID } from "../session/schema" import { MessageV2 } from "../session/message-v2" import { Agent } from "../agent/agent" import type { SessionPrompt } from "../session/prompt" import { Config } from "../config" -import { Effect } from "effect" +import { Effect, Schema } from "effect" export interface TaskPromptOps { cancel(sessionID: SessionID): void @@ -17,17 +16,15 @@ export interface TaskPromptOps { const id = "task" -const parameters = z.object({ - description: z.string().describe("A short (3-5 words) description of the task"), - prompt: z.string().describe("The task for the agent to perform"), - subagent_type: z.string().describe("The type of specialized agent to use for this task"), - task_id: z - .string() - .describe( +export const Parameters = Schema.Struct({ + description: Schema.String.annotate({ description: "A short (3-5 words) description of the task" }), + prompt: Schema.String.annotate({ description: "The task for the agent to perform" }), + subagent_type: Schema.String.annotate({ description: "The type of specialized agent to use for this task" }), + task_id: Schema.optional(Schema.String).annotate({ + description: "This should only be set if you mean to resume a previous task (you can pass a prior task_id and the task will continue the same subagent session as before instead of creating a fresh one)", - ) - .optional(), - command: z.string().describe("The command that triggered this task").optional(), + }), + command: Schema.optional(Schema.String).annotate({ description: "The command that triggered this task" }), }) export const TaskTool = Tool.define( @@ -37,7 +34,7 @@ export const TaskTool = Tool.define( const config = yield* Config.Service const sessions = yield* Session.Service - const run = Effect.fn("TaskTool.execute")(function* (params: z.infer, ctx: Tool.Context) { + const run = Effect.fn("TaskTool.execute")(function* (params: Schema.Schema.Type, ctx: Tool.Context) { const cfg = yield* config.get() if (!ctx.extra?.bypassAgentCheck) { @@ -168,8 +165,8 @@ export const TaskTool = Tool.define( return { description: DESCRIPTION, - parameters, - execute: (params: z.infer, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), + parameters: Parameters, + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), } }), ) diff --git a/packages/opencode/src/tool/todo.ts b/packages/opencode/src/tool/todo.ts index c08fb04119..c493d3a71a 100644 --- a/packages/opencode/src/tool/todo.ts +++ b/packages/opencode/src/tool/todo.ts @@ -1,39 +1,34 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import * as Tool from "./tool" import DESCRIPTION_WRITE from "./todowrite.txt" import { Todo } from "../session/todo" -// Parameters are kept inline rather than derived from Todo.Info because -// Tool.define requires z.ZodObject-typed parameters for execute() inference, -// and zodObject(Todo.Info) returns ZodObject — reaching into .shape would -// erase field types. Tool schemas migrate to Effect Schema as a separate slice -// per specs/effect/schema.md. -const parameters = z.object({ - todos: z - .array( - z.object({ - content: z.string().describe("Brief description of the task"), - status: z.string().describe("Current status of the task: pending, in_progress, completed, cancelled"), - priority: z.string().describe("Priority level of the task: high, medium, low"), - }), - ) - .describe("The updated todo list"), +// Todo.Info is still a zod schema (session/todo.ts). Inline the field shape +// here rather than referencing its `.shape` — the LLM-visible JSON Schema is +// identical, and it removes the last zod dependency from this tool. +const TodoItem = Schema.Struct({ + content: Schema.String.annotate({ description: "Brief description of the task" }), + status: Schema.String.annotate({ description: "Current status of the task: pending, in_progress, completed, cancelled" }), + priority: Schema.String.annotate({ description: "Priority level of the task: high, medium, low" }), +}) + +export const Parameters = Schema.Struct({ + todos: Schema.mutable(Schema.Array(TodoItem)).annotate({ description: "The updated todo list" }), }) type Metadata = { todos: Todo.Info[] } -export const TodoWriteTool = Tool.define( +export const TodoWriteTool = Tool.define( "todowrite", Effect.gen(function* () { const todo = yield* Todo.Service return { description: DESCRIPTION_WRITE, - parameters, - execute: (params: z.infer, ctx: Tool.Context) => + parameters: Parameters, + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { yield* ctx.ask({ permission: "todowrite", @@ -55,6 +50,6 @@ export const TodoWriteTool = Tool.define + } satisfies Tool.DefWithoutID }), ) diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts index 179149afd2..c9115e9ff1 100644 --- a/packages/opencode/src/tool/tool.ts +++ b/packages/opencode/src/tool/tool.ts @@ -1,5 +1,4 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import type { MessageV2 } from "../session/message-v2" import type { Permission } from "../permission" import type { SessionID, MessageID } from "../session/schema" @@ -32,29 +31,33 @@ export interface ExecuteResult { attachments?: Omit[] } -export interface Def { +export interface Def = Schema.Decoder, M extends Metadata = Metadata> { id: string description: string parameters: Parameters - execute(args: z.infer, ctx: Context): Effect.Effect> - formatValidationError?(error: z.ZodError): string + execute(args: Schema.Schema.Type, ctx: Context): Effect.Effect> + formatValidationError?(error: unknown): string } -export type DefWithoutID = Omit< +export type DefWithoutID = Schema.Decoder, M extends Metadata = Metadata> = Omit< Def, "id" > -export interface Info { +export interface Info = Schema.Decoder, M extends Metadata = Metadata> { id: string init: () => Effect.Effect> } -type Init = +type Init, M extends Metadata> = | DefWithoutID | (() => Effect.Effect>) export type InferParameters = - T extends Info ? z.infer

: T extends Effect.Effect, any, any> ? z.infer

: never + T extends Info + ? Schema.Schema.Type

+ : T extends Effect.Effect, any, any> + ? Schema.Schema.Type

+ : never export type InferMetadata = T extends Info ? M : T extends Effect.Effect, any, any> ? M : never @@ -65,7 +68,7 @@ export type InferDef = ? Def : never -function wrap( +function wrap, Result extends Metadata>( id: string, init: Init, truncate: Truncate.Interface, @@ -74,6 +77,10 @@ function wrap( return () => Effect.gen(function* () { const toolInfo = typeof init === "function" ? { ...(yield* init()) } : { ...init } + // Compile the parser closure once per tool init; `decodeUnknownEffect` + // allocates a new closure per call, so hoisting avoids re-closing it for + // every LLM tool invocation. + const decode = Schema.decodeUnknownEffect(toolInfo.parameters) const execute = toolInfo.execute toolInfo.execute = (args, ctx) => { const attrs = { @@ -83,19 +90,17 @@ function wrap( ...(ctx.callID ? { "tool.call_id": ctx.callID } : {}), } return Effect.gen(function* () { - yield* Effect.try({ - try: () => toolInfo.parameters.parse(args), - catch: (error) => { - if (error instanceof z.ZodError && toolInfo.formatValidationError) { - return new Error(toolInfo.formatValidationError(error), { cause: error }) - } - return new Error( - `The ${id} tool was called with invalid arguments: ${error}.\nPlease rewrite the input so it satisfies the expected schema.`, - { cause: error }, - ) - }, - }) - const result = yield* execute(args, ctx) + const decoded = yield* decode(args).pipe( + Effect.mapError((error) => + toolInfo.formatValidationError + ? new Error(toolInfo.formatValidationError(error), { cause: error }) + : new Error( + `The ${id} tool was called with invalid arguments: ${error}.\nPlease rewrite the input so it satisfies the expected schema.`, + { cause: error }, + ), + ), + ) + const result = yield* execute(decoded as Schema.Schema.Type, ctx) if (result.metadata.truncated !== undefined) { return result } @@ -116,7 +121,7 @@ function wrap( }) } -export function define( +export function define, Result extends Metadata, R, ID extends string = string>( id: ID, init: Effect.Effect, never, R>, ): Effect.Effect, never, R | Truncate.Service | Agent.Service> & { id: ID } { @@ -131,7 +136,7 @@ export function define(info: Info): Effect.Effect> { +export function init

, M extends Metadata>(info: Info): Effect.Effect> { return Effect.gen(function* () { const init = yield* info.init() return { diff --git a/packages/opencode/src/tool/webfetch.ts b/packages/opencode/src/tool/webfetch.ts index 1d988b8d4f..d2561a1301 100644 --- a/packages/opencode/src/tool/webfetch.ts +++ b/packages/opencode/src/tool/webfetch.ts @@ -1,5 +1,4 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import { HttpClient, HttpClientRequest } from "effect/unstable/http" import * as Tool from "./tool" import TurndownService from "turndown" @@ -10,13 +9,14 @@ const MAX_RESPONSE_SIZE = 5 * 1024 * 1024 // 5MB const DEFAULT_TIMEOUT = 30 * 1000 // 30 seconds const MAX_TIMEOUT = 120 * 1000 // 2 minutes -const parameters = z.object({ - url: z.string().describe("The URL to fetch content from"), - format: z - .enum(["text", "markdown", "html"]) - .default("markdown") - .describe("The format to return the content in (text, markdown, or html). Defaults to markdown."), - timeout: z.number().describe("Optional timeout in seconds (max 120)").optional(), +export const Parameters = Schema.Struct({ + url: Schema.String.annotate({ description: "The URL to fetch content from" }), + format: Schema.Literals(["text", "markdown", "html"]) + .pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("markdown" as const))) + .annotate({ + description: "The format to return the content in (text, markdown, or html). Defaults to markdown.", + }), + timeout: Schema.optional(Schema.Number).annotate({ description: "Optional timeout in seconds (max 120)" }), }) export const WebFetchTool = Tool.define( @@ -27,8 +27,8 @@ export const WebFetchTool = Tool.define( return { description: DESCRIPTION, - parameters, - execute: (params: z.infer, ctx: Tool.Context) => + parameters: Parameters, + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { if (!params.url.startsWith("http://") && !params.url.startsWith("https://")) { throw new Error("URL must start with http:// or https://") diff --git a/packages/opencode/src/tool/websearch.ts b/packages/opencode/src/tool/websearch.ts index 34cefd031f..ff4c696a25 100644 --- a/packages/opencode/src/tool/websearch.ts +++ b/packages/opencode/src/tool/websearch.ts @@ -1,27 +1,24 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import { HttpClient } from "effect/unstable/http" import * as Tool from "./tool" import * as McpExa from "./mcp-exa" import DESCRIPTION from "./websearch.txt" -const Parameters = z.object({ - query: z.string().describe("Websearch query"), - numResults: z.number().optional().describe("Number of search results to return (default: 8)"), - livecrawl: z - .enum(["fallback", "preferred"]) - .optional() - .describe( +export const Parameters = Schema.Struct({ + query: Schema.String.annotate({ description: "Websearch query" }), + numResults: Schema.optional(Schema.Number).annotate({ + description: "Number of search results to return (default: 8)", + }), + livecrawl: Schema.optional(Schema.Literals(["fallback", "preferred"])).annotate({ + description: "Live crawl mode - 'fallback': use live crawling as backup if cached content unavailable, 'preferred': prioritize live crawling (default: 'fallback')", - ), - type: z - .enum(["auto", "fast", "deep"]) - .optional() - .describe("Search type - 'auto': balanced search (default), 'fast': quick results, 'deep': comprehensive search"), - contextMaxCharacters: z - .number() - .optional() - .describe("Maximum characters for context string optimized for LLMs (default: 10000)"), + }), + type: Schema.optional(Schema.Literals(["auto", "fast", "deep"])).annotate({ + description: "Search type - 'auto': balanced search (default), 'fast': quick results, 'deep': comprehensive search", + }), + contextMaxCharacters: Schema.optional(Schema.Number).annotate({ + description: "Maximum characters for context string optimized for LLMs (default: 10000)", + }), }) export const WebSearchTool = Tool.define( @@ -34,7 +31,7 @@ export const WebSearchTool = Tool.define( return DESCRIPTION.replace("{{year}}", new Date().getFullYear().toString()) }, parameters: Parameters, - execute: (params: z.infer, ctx: Tool.Context) => + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { yield* ctx.ask({ permission: "websearch", diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts index 80198f4555..b52f4a164c 100644 --- a/packages/opencode/src/tool/write.ts +++ b/packages/opencode/src/tool/write.ts @@ -1,4 +1,4 @@ -import z from "zod" +import { Schema } from "effect" import * as path from "path" import { Effect } from "effect" import * as Tool from "./tool" @@ -17,6 +17,13 @@ import * as Bom from "@/util/bom" const MAX_PROJECT_DIAGNOSTICS_FILES = 5 +export const Parameters = Schema.Struct({ + content: Schema.String.annotate({ description: "The content to write to the file" }), + filePath: Schema.String.annotate({ + description: "The absolute path to the file to write (must be absolute, not relative)", + }), +}) + export const WriteTool = Tool.define( "write", Effect.gen(function* () { @@ -27,10 +34,7 @@ export const WriteTool = Tool.define( return { description: DESCRIPTION, - parameters: z.object({ - content: z.string().describe("The content to write to the file"), - filePath: z.string().describe("The absolute path to the file to write (must be absolute, not relative)"), - }), + parameters: Parameters, execute: (params: { content: string; filePath: string }, ctx: Tool.Context) => Effect.gen(function* () { const filepath = path.isAbsolute(params.filePath) diff --git a/packages/opencode/src/util/effect-zod.ts b/packages/opencode/src/util/effect-zod.ts index 24ff9a10e1..2bbad2dd75 100644 --- a/packages/opencode/src/util/effect-zod.ts +++ b/packages/opencode/src/util/effect-zod.ts @@ -49,6 +49,17 @@ function isZodType(value: unknown): value is z.ZodTypeAny { return typeof value === "object" && value !== null && "_zod" in value } +/** + * Emit a JSON Schema for a tool/route parameter schema — derives the zod form + * via the walker so Effect Schema inputs flow through the same zod-openapi + * pipeline the LLM/SDK layer already depends on. `io: "input"` mirrors what + * `session/prompt.ts` has always passed to `ai`'s `jsonSchema()` helper. + */ +export function toJsonSchema(schema: S) { + return z.toJSONSchema(zod(schema), { io: "input" }) +} + + function walk(ast: SchemaAST.AST): z.ZodTypeAny { const cached = walkCache.get(ast) if (cached) return cached diff --git a/packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap b/packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap new file mode 100644 index 0000000000..eb3fe6cce4 --- /dev/null +++ b/packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap @@ -0,0 +1,495 @@ +// Bun Snapshot v1, https://bun.sh/docs/test/snapshots + +exports[`tool parameters JSON Schema (wire shape) apply_patch 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "patchText": { + "description": "The full patch text that describes all changes to be made", + "type": "string", + }, + }, + "required": [ + "patchText", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) bash 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "command": { + "description": "The command to execute", + "type": "string", + }, + "description": { + "description": +"Clear, concise description of what this command does in 5-10 words. Examples: +Input: ls +Output: Lists files in current directory + +Input: git status +Output: Shows working tree status + +Input: npm install +Output: Installs package dependencies + +Input: mkdir foo +Output: Creates directory 'foo'" +, + "type": "string", + }, + "timeout": { + "description": "Optional timeout in milliseconds", + "type": "number", + }, + "workdir": { + "description": "The working directory to run the command in. Defaults to the current directory. Use this instead of 'cd' commands.", + "type": "string", + }, + }, + "required": [ + "command", + "description", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) codesearch 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "query": { + "description": "Search query to find relevant context for APIs, Libraries, and SDKs. For example, 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware', 'Next js partial prerendering configuration'", + "type": "string", + }, + "tokensNum": { + "default": 5000, + "description": "Number of tokens to return (1000-50000). Default is 5000 tokens. Adjust this value based on how much context you need - use lower values for focused queries and higher values for comprehensive documentation.", + "maximum": 50000, + "minimum": 1000, + "type": "number", + }, + }, + "required": [ + "query", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) edit 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "filePath": { + "description": "The absolute path to the file to modify", + "type": "string", + }, + "newString": { + "description": "The text to replace it with (must be different from oldString)", + "type": "string", + }, + "oldString": { + "description": "The text to replace", + "type": "string", + }, + "replaceAll": { + "description": "Replace all occurrences of oldString (default false)", + "type": "boolean", + }, + }, + "required": [ + "filePath", + "oldString", + "newString", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) glob 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "path": { + "description": "The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - simply omit it for the default behavior. Must be a valid directory path if provided.", + "type": "string", + }, + "pattern": { + "description": "The glob pattern to match files against", + "type": "string", + }, + }, + "required": [ + "pattern", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) grep 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "include": { + "description": "File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")", + "type": "string", + }, + "path": { + "description": "The directory to search in. Defaults to the current working directory.", + "type": "string", + }, + "pattern": { + "description": "The regex pattern to search for in file contents", + "type": "string", + }, + }, + "required": [ + "pattern", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) invalid 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "error": { + "type": "string", + }, + "tool": { + "type": "string", + }, + }, + "required": [ + "tool", + "error", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) lsp 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "character": { + "description": "The character offset (1-based, as shown in editors)", + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer", + }, + "filePath": { + "description": "The absolute or relative path to the file", + "type": "string", + }, + "line": { + "description": "The line number (1-based, as shown in editors)", + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer", + }, + "operation": { + "description": "The LSP operation to perform", + "enum": [ + "goToDefinition", + "findReferences", + "hover", + "documentSymbol", + "workspaceSymbol", + "goToImplementation", + "prepareCallHierarchy", + "incomingCalls", + "outgoingCalls", + ], + "type": "string", + }, + }, + "required": [ + "operation", + "filePath", + "line", + "character", + ], + "type": "object", +} +`; + + +exports[`tool parameters JSON Schema (wire shape) plan 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {}, + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) question 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "questions": { + "description": "Questions to ask", + "items": { + "properties": { + "header": { + "description": "Very short label (max 30 chars)", + "type": "string", + }, + "multiple": { + "description": "Allow selecting multiple choices", + "type": "boolean", + }, + "options": { + "description": "Available choices", + "items": { + "properties": { + "description": { + "description": "Explanation of choice", + "type": "string", + }, + "label": { + "description": "Display text (1-5 words, concise)", + "type": "string", + }, + }, + "ref": "QuestionOption", + "required": [ + "label", + "description", + ], + "type": "object", + }, + "type": "array", + }, + "question": { + "description": "Complete question", + "type": "string", + }, + }, + "ref": "QuestionPrompt", + "required": [ + "question", + "header", + "options", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": [ + "questions", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) read 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "filePath": { + "description": "The absolute path to the file or directory to read", + "type": "string", + }, + "limit": { + "description": "The maximum number of lines to read (defaults to 2000)", + "type": "number", + }, + "offset": { + "description": "The line number to start reading from (1-indexed)", + "type": "number", + }, + }, + "required": [ + "filePath", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) skill 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "name": { + "description": "The name of the skill from available_skills", + "type": "string", + }, + }, + "required": [ + "name", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) task 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "command": { + "description": "The command that triggered this task", + "type": "string", + }, + "description": { + "description": "A short (3-5 words) description of the task", + "type": "string", + }, + "prompt": { + "description": "The task for the agent to perform", + "type": "string", + }, + "subagent_type": { + "description": "The type of specialized agent to use for this task", + "type": "string", + }, + "task_id": { + "description": "This should only be set if you mean to resume a previous task (you can pass a prior task_id and the task will continue the same subagent session as before instead of creating a fresh one)", + "type": "string", + }, + }, + "required": [ + "description", + "prompt", + "subagent_type", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) todo 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "todos": { + "description": "The updated todo list", + "items": { + "properties": { + "content": { + "description": "Brief description of the task", + "type": "string", + }, + "priority": { + "description": "Priority level of the task: high, medium, low", + "type": "string", + }, + "status": { + "description": "Current status of the task: pending, in_progress, completed, cancelled", + "type": "string", + }, + }, + "required": [ + "content", + "status", + "priority", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": [ + "todos", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) webfetch 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "format": { + "default": "markdown", + "description": "The format to return the content in (text, markdown, or html). Defaults to markdown.", + "enum": [ + "text", + "markdown", + "html", + ], + "type": "string", + }, + "timeout": { + "description": "Optional timeout in seconds (max 120)", + "type": "number", + }, + "url": { + "description": "The URL to fetch content from", + "type": "string", + }, + }, + "required": [ + "url", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) websearch 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "contextMaxCharacters": { + "description": "Maximum characters for context string optimized for LLMs (default: 10000)", + "type": "number", + }, + "livecrawl": { + "description": "Live crawl mode - 'fallback': use live crawling as backup if cached content unavailable, 'preferred': prioritize live crawling (default: 'fallback')", + "enum": [ + "fallback", + "preferred", + ], + "type": "string", + }, + "numResults": { + "description": "Number of search results to return (default: 8)", + "type": "number", + }, + "query": { + "description": "Websearch query", + "type": "string", + }, + "type": { + "description": "Search type - 'auto': balanced search (default), 'fast': quick results, 'deep': comprehensive search", + "enum": [ + "auto", + "fast", + "deep", + ], + "type": "string", + }, + }, + "required": [ + "query", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) write 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "content": { + "description": "The content to write to the file", + "type": "string", + }, + "filePath": { + "description": "The absolute path to the file to write (must be absolute, not relative)", + "type": "string", + }, + }, + "required": [ + "content", + "filePath", + ], + "type": "object", +} +`; diff --git a/packages/opencode/test/tool/parameters.test.ts b/packages/opencode/test/tool/parameters.test.ts new file mode 100644 index 0000000000..8ea008a457 --- /dev/null +++ b/packages/opencode/test/tool/parameters.test.ts @@ -0,0 +1,260 @@ +import { describe, expect, test } from "bun:test" +import { Result, Schema } from "effect" +import { toJsonSchema } from "../../src/util/effect-zod" + +// Each tool exports its parameters schema at module scope so this test can +// import them without running the tool's Effect-based init. The JSON Schema +// snapshot captures what the LLM sees; the parse assertions pin down the +// accepts/rejects contract. `toJsonSchema` is the same helper `session/ +// prompt.ts` uses to emit tool schemas to the LLM, so the snapshots stay +// byte-identical regardless of whether a tool has migrated from zod to Schema. + +import { Parameters as ApplyPatch } from "../../src/tool/apply_patch" +import { Parameters as Bash } from "../../src/tool/bash" +import { Parameters as CodeSearch } from "../../src/tool/codesearch" +import { Parameters as Edit } from "../../src/tool/edit" +import { Parameters as Glob } from "../../src/tool/glob" +import { Parameters as Grep } from "../../src/tool/grep" +import { Parameters as Invalid } from "../../src/tool/invalid" +import { Parameters as Lsp } from "../../src/tool/lsp" +import { Parameters as Plan } from "../../src/tool/plan" +import { Parameters as Question } from "../../src/tool/question" +import { Parameters as Read } from "../../src/tool/read" +import { Parameters as Skill } from "../../src/tool/skill" +import { Parameters as Task } from "../../src/tool/task" +import { Parameters as Todo } from "../../src/tool/todo" +import { Parameters as WebFetch } from "../../src/tool/webfetch" +import { Parameters as WebSearch } from "../../src/tool/websearch" +import { Parameters as Write } from "../../src/tool/write" + +const parse = >(schema: S, input: unknown): S["Type"] => + Schema.decodeUnknownSync(schema)(input) + +const accepts = (schema: Schema.Decoder, input: unknown): boolean => + Result.isSuccess(Schema.decodeUnknownResult(schema)(input)) + +describe("tool parameters", () => { + describe("JSON Schema (wire shape)", () => { + test("apply_patch", () => expect(toJsonSchema(ApplyPatch)).toMatchSnapshot()) + test("bash", () => expect(toJsonSchema(Bash)).toMatchSnapshot()) + test("codesearch", () => expect(toJsonSchema(CodeSearch)).toMatchSnapshot()) + test("edit", () => expect(toJsonSchema(Edit)).toMatchSnapshot()) + test("glob", () => expect(toJsonSchema(Glob)).toMatchSnapshot()) + test("grep", () => expect(toJsonSchema(Grep)).toMatchSnapshot()) + test("invalid", () => expect(toJsonSchema(Invalid)).toMatchSnapshot()) + test("lsp", () => expect(toJsonSchema(Lsp)).toMatchSnapshot()) + test("plan", () => expect(toJsonSchema(Plan)).toMatchSnapshot()) + test("question", () => expect(toJsonSchema(Question)).toMatchSnapshot()) + test("read", () => expect(toJsonSchema(Read)).toMatchSnapshot()) + test("skill", () => expect(toJsonSchema(Skill)).toMatchSnapshot()) + test("task", () => expect(toJsonSchema(Task)).toMatchSnapshot()) + test("todo", () => expect(toJsonSchema(Todo)).toMatchSnapshot()) + test("webfetch", () => expect(toJsonSchema(WebFetch)).toMatchSnapshot()) + test("websearch", () => expect(toJsonSchema(WebSearch)).toMatchSnapshot()) + test("write", () => expect(toJsonSchema(Write)).toMatchSnapshot()) + }) + + describe("apply_patch", () => { + test("accepts patchText", () => { + expect(parse(ApplyPatch, { patchText: "*** Begin Patch\n*** End Patch" })).toEqual({ + patchText: "*** Begin Patch\n*** End Patch", + }) + }) + test("rejects missing patchText", () => { + expect(accepts(ApplyPatch, {})).toBe(false) + }) + test("rejects non-string patchText", () => { + expect(accepts(ApplyPatch, { patchText: 123 })).toBe(false) + }) + }) + + describe("bash", () => { + test("accepts minimum: command + description", () => { + expect(parse(Bash, { command: "ls", description: "list" })).toEqual({ command: "ls", description: "list" }) + }) + test("accepts optional timeout + workdir", () => { + const parsed = parse(Bash, { command: "ls", description: "list", timeout: 5000, workdir: "/tmp" }) + expect(parsed.timeout).toBe(5000) + expect(parsed.workdir).toBe("/tmp") + }) + test("rejects missing description (required by zod)", () => { + expect(accepts(Bash, { command: "ls" })).toBe(false) + }) + test("rejects missing command", () => { + expect(accepts(Bash, { description: "list" })).toBe(false) + }) + }) + + describe("codesearch", () => { + test("accepts query; tokensNum defaults to 5000", () => { + expect(parse(CodeSearch, { query: "hooks" })).toEqual({ query: "hooks", tokensNum: 5000 }) + }) + test("accepts override tokensNum", () => { + expect(parse(CodeSearch, { query: "hooks", tokensNum: 10000 }).tokensNum).toBe(10000) + }) + test("rejects tokensNum under 1000", () => { + expect(accepts(CodeSearch, { query: "x", tokensNum: 500 })).toBe(false) + }) + test("rejects tokensNum over 50000", () => { + expect(accepts(CodeSearch, { query: "x", tokensNum: 60000 })).toBe(false) + }) + }) + + describe("edit", () => { + test("accepts all four fields", () => { + expect(parse(Edit, { filePath: "/a", oldString: "x", newString: "y", replaceAll: true })).toEqual({ + filePath: "/a", + oldString: "x", + newString: "y", + replaceAll: true, + }) + }) + test("replaceAll is optional", () => { + const parsed = parse(Edit, { filePath: "/a", oldString: "x", newString: "y" }) + expect(parsed.replaceAll).toBeUndefined() + }) + test("rejects missing filePath", () => { + expect(accepts(Edit, { oldString: "x", newString: "y" })).toBe(false) + }) + }) + + describe("glob", () => { + test("accepts pattern-only", () => { + expect(parse(Glob, { pattern: "**/*.ts" })).toEqual({ pattern: "**/*.ts" }) + }) + test("accepts optional path", () => { + expect(parse(Glob, { pattern: "**/*.ts", path: "/tmp" }).path).toBe("/tmp") + }) + test("rejects missing pattern", () => { + expect(accepts(Glob, {})).toBe(false) + }) + }) + + describe("grep", () => { + test("accepts pattern-only", () => { + expect(parse(Grep, { pattern: "TODO" })).toEqual({ pattern: "TODO" }) + }) + test("accepts optional path + include", () => { + const parsed = parse(Grep, { pattern: "TODO", path: "/tmp", include: "*.ts" }) + expect(parsed.path).toBe("/tmp") + expect(parsed.include).toBe("*.ts") + }) + test("rejects missing pattern", () => { + expect(accepts(Grep, {})).toBe(false) + }) + }) + + describe("invalid", () => { + test("accepts tool + error", () => { + expect(parse(Invalid, { tool: "foo", error: "bar" })).toEqual({ tool: "foo", error: "bar" }) + }) + test("rejects missing fields", () => { + expect(accepts(Invalid, { tool: "foo" })).toBe(false) + expect(accepts(Invalid, { error: "bar" })).toBe(false) + }) + }) + + describe("lsp", () => { + test("accepts all fields", () => { + const parsed = parse(Lsp, { operation: "hover", filePath: "/a.ts", line: 1, character: 1 }) + expect(parsed.operation).toBe("hover") + }) + test("rejects line < 1", () => { + expect(accepts(Lsp, { operation: "hover", filePath: "/a.ts", line: 0, character: 1 })).toBe(false) + }) + test("rejects character < 1", () => { + expect(accepts(Lsp, { operation: "hover", filePath: "/a.ts", line: 1, character: 0 })).toBe(false) + }) + test("rejects unknown operation", () => { + expect(accepts(Lsp, { operation: "bogus", filePath: "/a.ts", line: 1, character: 1 })).toBe(false) + }) + }) + + describe("plan", () => { + test("accepts empty object", () => { + expect(parse(Plan, {})).toEqual({}) + }) + }) + + describe("question", () => { + test("accepts questions array", () => { + const parsed = parse(Question, { + questions: [ + { + question: "pick one", + header: "Header", + custom: false, + options: [{ label: "a", description: "desc" }], + }, + ], + }) + expect(parsed.questions.length).toBe(1) + }) + test("rejects missing questions", () => { + expect(accepts(Question, {})).toBe(false) + }) + }) + + describe("read", () => { + test("accepts filePath-only", () => { + expect(parse(Read, { filePath: "/a" }).filePath).toBe("/a") + }) + test("accepts optional offset + limit", () => { + const parsed = parse(Read, { filePath: "/a", offset: 10, limit: 100 }) + expect(parsed.offset).toBe(10) + expect(parsed.limit).toBe(100) + }) + }) + + describe("skill", () => { + test("accepts name", () => { + expect(parse(Skill, { name: "foo" }).name).toBe("foo") + }) + test("rejects missing name", () => { + expect(accepts(Skill, {})).toBe(false) + }) + }) + + describe("task", () => { + test("accepts description + prompt + subagent_type", () => { + const parsed = parse(Task, { description: "d", prompt: "p", subagent_type: "general" }) + expect(parsed.subagent_type).toBe("general") + }) + test("rejects missing prompt", () => { + expect(accepts(Task, { description: "d", subagent_type: "general" })).toBe(false) + }) + }) + + describe("todo", () => { + test("accepts todos array", () => { + const parsed = parse(Todo, { + todos: [{ id: "t1", content: "do x", status: "pending", priority: "medium" }], + }) + expect(parsed.todos.length).toBe(1) + }) + test("rejects missing todos", () => { + expect(accepts(Todo, {})).toBe(false) + }) + }) + + describe("webfetch", () => { + test("accepts url-only", () => { + expect(parse(WebFetch, { url: "https://example.com" }).url).toBe("https://example.com") + }) + }) + + describe("websearch", () => { + test("accepts query", () => { + expect(parse(WebSearch, { query: "opencode" }).query).toBe("opencode") + }) + }) + + describe("write", () => { + test("accepts content + filePath", () => { + expect(parse(Write, { content: "hi", filePath: "/a" })).toEqual({ content: "hi", filePath: "/a" }) + }) + test("rejects missing filePath", () => { + expect(accepts(Write, { content: "hi" })).toBe(false) + }) + }) +}) diff --git a/packages/opencode/test/tool/tool-define.test.ts b/packages/opencode/test/tool/tool-define.test.ts index 00d1e039a7..283708767d 100644 --- a/packages/opencode/test/tool/tool-define.test.ts +++ b/packages/opencode/test/tool/tool-define.test.ts @@ -1,13 +1,13 @@ import { describe, test, expect } from "bun:test" -import { Effect, Layer, ManagedRuntime } from "effect" -import z from "zod" +import { Effect, Layer, ManagedRuntime, Schema } from "effect" import { Agent } from "../../src/agent/agent" +import { MessageID, SessionID } from "../../src/session/schema" import { Tool } from "../../src/tool" import { Truncate } from "../../src/tool" const runtime = ManagedRuntime.make(Layer.mergeAll(Truncate.defaultLayer, Agent.defaultLayer)) -const params = z.object({ input: z.string() }) +const params = Schema.Struct({ input: Schema.String }) function makeTool(id: string, executeFn?: () => void) { return { @@ -56,4 +56,44 @@ describe("Tool.define", () => { expect(first).not.toBe(second) }) + + test("execute receives decoded parameters", async () => { + const parameters = Schema.Struct({ + count: Schema.NumberFromString.pipe(Schema.optional, Schema.withDecodingDefaultType(Effect.succeed(5))), + }) + const calls: Array> = [] + const info = await runtime.runPromise( + Tool.define( + "test-decoded", + Effect.succeed({ + description: "test tool", + parameters, + execute(args: Schema.Schema.Type) { + calls.push(args) + return Effect.succeed({ title: "test", output: "ok", metadata: { truncated: false } }) + }, + }), + ), + ) + const ctx: Tool.Context = { + sessionID: SessionID.descending(), + messageID: MessageID.ascending(), + agent: "build", + abort: new AbortController().signal, + messages: [], + metadata() { + return Effect.void + }, + ask() { + return Effect.void + }, + } + const tool = await Effect.runPromise(info.init()) + const execute = tool.execute as unknown as (args: unknown, ctx: Tool.Context) => ReturnType + + await Effect.runPromise(execute({}, ctx)) + await Effect.runPromise(execute({ count: "7" }, ctx)) + + expect(calls).toEqual([{ count: 5 }, { count: 7 }]) + }) }) From 3f8c659056c3ddf3e9074efb8a1ea57018c356ab Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 20:10:56 +0000 Subject: [PATCH 16/59] chore: generate --- packages/opencode/src/tool/apply_patch.ts | 8 +++++-- packages/opencode/src/tool/read.ts | 8 +++++-- packages/opencode/src/tool/task.ts | 8 +++++-- packages/opencode/src/tool/todo.ts | 4 +++- packages/opencode/src/tool/tool.ts | 29 ++++++++++++++++------- packages/opencode/src/util/effect-zod.ts | 1 - 6 files changed, 42 insertions(+), 16 deletions(-) diff --git a/packages/opencode/src/tool/apply_patch.ts b/packages/opencode/src/tool/apply_patch.ts index effc428c71..72f24a3f60 100644 --- a/packages/opencode/src/tool/apply_patch.ts +++ b/packages/opencode/src/tool/apply_patch.ts @@ -27,7 +27,10 @@ export const ApplyPatchTool = Tool.define( const format = yield* Format.Service const bus = yield* Bus.Service - const run = Effect.fn("ApplyPatchTool.execute")(function* (params: Schema.Schema.Type, ctx: Tool.Context) { + const run = Effect.fn("ApplyPatchTool.execute")(function* ( + params: Schema.Schema.Type, + ctx: Tool.Context, + ) { if (!params.patchText) { return yield* Effect.fail(new Error("patchText is required")) } @@ -297,7 +300,8 @@ export const ApplyPatchTool = Tool.define( return { description: DESCRIPTION, parameters: Parameters, - execute: (params: Schema.Schema.Type, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => + run(params, ctx).pipe(Effect.orDie), } }), ) diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts index e7bfc6af33..d0995626c0 100644 --- a/packages/opencode/src/tool/read.ts +++ b/packages/opencode/src/tool/read.ts @@ -148,7 +148,10 @@ export const ReadTool = Tool.define( return nonPrintableCount / bytes.length > 0.3 } - const run = Effect.fn("ReadTool.execute")(function* (params: Schema.Schema.Type, ctx: Tool.Context) { + const run = Effect.fn("ReadTool.execute")(function* ( + params: Schema.Schema.Type, + ctx: Tool.Context, + ) { if (params.offset !== undefined && params.offset < 1) { return yield* Effect.fail(new Error("offset must be greater than or equal to 1")) } @@ -284,7 +287,8 @@ export const ReadTool = Tool.define( return { description: DESCRIPTION, parameters: Parameters, - execute: (params: Schema.Schema.Type, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => + run(params, ctx).pipe(Effect.orDie), } }), ) diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index 98f6bdd98f..5cb0dc6a83 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -34,7 +34,10 @@ export const TaskTool = Tool.define( const config = yield* Config.Service const sessions = yield* Session.Service - const run = Effect.fn("TaskTool.execute")(function* (params: Schema.Schema.Type, ctx: Tool.Context) { + const run = Effect.fn("TaskTool.execute")(function* ( + params: Schema.Schema.Type, + ctx: Tool.Context, + ) { const cfg = yield* config.get() if (!ctx.extra?.bypassAgentCheck) { @@ -166,7 +169,8 @@ export const TaskTool = Tool.define( return { description: DESCRIPTION, parameters: Parameters, - execute: (params: Schema.Schema.Type, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => + run(params, ctx).pipe(Effect.orDie), } }), ) diff --git a/packages/opencode/src/tool/todo.ts b/packages/opencode/src/tool/todo.ts index c493d3a71a..18d21cf61e 100644 --- a/packages/opencode/src/tool/todo.ts +++ b/packages/opencode/src/tool/todo.ts @@ -8,7 +8,9 @@ import { Todo } from "../session/todo" // identical, and it removes the last zod dependency from this tool. const TodoItem = Schema.Struct({ content: Schema.String.annotate({ description: "Brief description of the task" }), - status: Schema.String.annotate({ description: "Current status of the task: pending, in_progress, completed, cancelled" }), + status: Schema.String.annotate({ + description: "Current status of the task: pending, in_progress, completed, cancelled", + }), priority: Schema.String.annotate({ description: "Priority level of the task: high, medium, low" }), }) diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts index c9115e9ff1..7e753cb9bc 100644 --- a/packages/opencode/src/tool/tool.ts +++ b/packages/opencode/src/tool/tool.ts @@ -31,19 +31,25 @@ export interface ExecuteResult { attachments?: Omit[] } -export interface Def = Schema.Decoder, M extends Metadata = Metadata> { +export interface Def< + Parameters extends Schema.Decoder = Schema.Decoder, + M extends Metadata = Metadata, +> { id: string description: string parameters: Parameters execute(args: Schema.Schema.Type, ctx: Context): Effect.Effect> formatValidationError?(error: unknown): string } -export type DefWithoutID = Schema.Decoder, M extends Metadata = Metadata> = Omit< - Def, - "id" -> +export type DefWithoutID< + Parameters extends Schema.Decoder = Schema.Decoder, + M extends Metadata = Metadata, +> = Omit, "id"> -export interface Info = Schema.Decoder, M extends Metadata = Metadata> { +export interface Info< + Parameters extends Schema.Decoder = Schema.Decoder, + M extends Metadata = Metadata, +> { id: string init: () => Effect.Effect> } @@ -121,7 +127,12 @@ function wrap, Result extends Metadat }) } -export function define, Result extends Metadata, R, ID extends string = string>( +export function define< + Parameters extends Schema.Decoder, + Result extends Metadata, + R, + ID extends string = string, +>( id: ID, init: Effect.Effect, never, R>, ): Effect.Effect, never, R | Truncate.Service | Agent.Service> & { id: ID } { @@ -136,7 +147,9 @@ export function define, Result extend ) } -export function init

, M extends Metadata>(info: Info): Effect.Effect> { +export function init

, M extends Metadata>( + info: Info, +): Effect.Effect> { return Effect.gen(function* () { const init = yield* info.init() return { diff --git a/packages/opencode/src/util/effect-zod.ts b/packages/opencode/src/util/effect-zod.ts index 2bbad2dd75..332a5c76eb 100644 --- a/packages/opencode/src/util/effect-zod.ts +++ b/packages/opencode/src/util/effect-zod.ts @@ -59,7 +59,6 @@ export function toJsonSchema(schema: S) { return z.toJSONSchema(zod(schema), { io: "input" }) } - function walk(ast: SchemaAST.AST): z.ZodTypeAny { const cached = walkCache.get(ast) if (cached) return cached From 98ea5b6e7e907b6d4eb52b294b9a7770bbd0f18e Mon Sep 17 00:00:00 2001 From: James Long Date: Thu, 23 Apr 2026 16:19:19 -0400 Subject: [PATCH 17/59] feat(tui): support builtin protocol for handling context from editors (#24034) --- packages/opencode/src/cli/cmd/tui/app.tsx | 5 +- .../cmd/tui/component/prompt/autocomplete.tsx | 104 ++++-- .../cli/cmd/tui/component/prompt/index.tsx | 50 ++- .../src/cli/cmd/tui/context/editor.ts | 318 ++++++++++++++++++ 4 files changed, 448 insertions(+), 29 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/tui/context/editor.ts diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 2b31d078cb..eb5cb44e8d 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -24,6 +24,7 @@ import { DialogProvider as DialogProviderList } from "@tui/component/dialog-prov import { ErrorComponent } from "@tui/component/error-component" import { PluginRouteMissing } from "@tui/component/plugin-route-missing" import { ProjectProvider } from "@tui/context/project" +import { EditorContextProvider } from "@tui/context/editor" import { useEvent } from "@tui/context/event" import { SDKProvider, useSDK } from "@tui/context/sdk" import { StartupLoading } from "@tui/component/startup-loading" @@ -177,7 +178,9 @@ export function tui(input: { - + + + diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index 305d076223..4a5e1a4ca6 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -1,9 +1,11 @@ import type { BoxRenderable, TextareaRenderable, KeyEvent, ScrollBoxRenderable } from "@opentui/core" import { pathToFileURL } from "bun" import fuzzysort from "fuzzysort" +import path from "path" import { firstBy } from "remeda" import { createMemo, createResource, createEffect, onMount, onCleanup, Index, Show, createSignal } from "solid-js" import { createStore } from "solid-js/store" +import { useEditorContext } from "@tui/context/editor" import { useSDK } from "@tui/context/sdk" import { useSync } from "@tui/context/sync" import { getScrollAcceleration } from "../../util/scroll" @@ -77,6 +79,7 @@ export function Autocomplete(props: { agentStyleId: number promptPartTypeId: () => number }) { + const editor = useEditorContext() const sdk = useSDK() const sync = useSync() const command = useCommandDialog() @@ -221,6 +224,70 @@ export function Autocomplete(props: { } } + function createFilePart(item: string, lineRange?: { startLine: number; endLine?: number }) { + const baseDir = (sync.path.directory || process.cwd()).replace(/\/+$/, "") + const fullPath = path.isAbsolute(item) ? item : path.join(baseDir, item) + const urlObj = pathToFileURL(fullPath) + const filename = + lineRange && !item.endsWith("/") + ? `${item}#${lineRange.startLine}${lineRange.endLine ? `-${lineRange.endLine}` : ""}` + : item + + if (lineRange && !item.endsWith("/")) { + urlObj.searchParams.set("start", String(lineRange.startLine)) + if (lineRange.endLine !== undefined) { + urlObj.searchParams.set("end", String(lineRange.endLine)) + } + } + + return { + filename, + url: urlObj.href, + part: { + type: "file" as const, + mime: "text/plain", + filename, + url: urlObj.href, + source: { + type: "file" as const, + text: { + start: 0, + end: 0, + value: "", + }, + path: item, + }, + }, + } + } + + function normalizeMentionPath(filePath: string) { + const baseDir = sync.path.directory || process.cwd() + const absolute = path.resolve(filePath) + const relative = path.relative(baseDir, absolute) + + if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) { + return relative.split(path.sep).join("/") + } + + return absolute.split(path.sep).join("/") + } + + function insertFileMention(input: { filePath: string; lineStart: number; lineEnd: number }) { + const item = normalizeMentionPath(input.filePath) + const lineRange = { + startLine: input.lineStart, + endLine: input.lineEnd > input.lineStart ? input.lineEnd : undefined, + } + const { filename, part } = createFilePart(item, lineRange) + const index = store.visible === "@" ? store.index : props.input().cursorOffset + + command.keybinds(true) + setStore("visible", false) + setStore("index", index) + insertPart(filename, part) + } + const [files] = createResource( () => search(), async (query) => { @@ -250,18 +317,7 @@ export function Autocomplete(props: { const width = props.anchor().width - 4 options.push( ...sortedFiles.map((item): AutocompleteOption => { - const baseDir = (sync.path.directory || process.cwd()).replace(/\/+$/, "") - const fullPath = `${baseDir}/${item}` - const urlObj = pathToFileURL(fullPath) - let filename = item - if (lineRange && !item.endsWith("/")) { - filename = `${item}#${lineRange.startLine}${lineRange.endLine ? `-${lineRange.endLine}` : ""}` - urlObj.searchParams.set("start", String(lineRange.startLine)) - if (lineRange.endLine !== undefined) { - urlObj.searchParams.set("end", String(lineRange.endLine)) - } - } - const url = urlObj.href + const { filename, url, part } = createFilePart(item, lineRange) const isDir = item.endsWith("/") return { @@ -270,21 +326,7 @@ export function Autocomplete(props: { isDirectory: isDir, path: item, onSelect: () => { - insertPart(filename, { - type: "file", - mime: "text/plain", - filename, - url, - source: { - type: "file", - text: { - start: 0, - end: 0, - value: "", - }, - path: item, - }, - }) + insertPart(filename, part) }, } }), @@ -501,6 +543,14 @@ export function Autocomplete(props: { } onMount(() => { + const unsubscribeMention = editor.onMention((mention) => { + insertFileMention(mention) + }) + + onCleanup(() => { + unsubscribeMention() + }) + props.ref({ get visible() { return store.visible diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 2e08e66a4a..5288a819b3 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -12,6 +12,7 @@ import { useRoute } from "@tui/context/route" import { useProject } from "@tui/context/project" import { useSync } from "@tui/context/sync" import { useEvent } from "@tui/context/event" +import { useEditorContext } from "@tui/context/editor" import { MessageID, PartID } from "@/session/schema" import { createStore, produce, unwrap } from "solid-js/store" import { useKeybind } from "@tui/context/keybind" @@ -21,7 +22,7 @@ import { usePromptStash } from "./stash" import { DialogStash } from "../dialog-stash" import { type AutocompleteRef, Autocomplete } from "./autocomplete" import { useCommandDialog } from "../dialog-command" -import { useRenderer, type JSX } from "@opentui/solid" +import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid" import * as Editor from "@tui/util/editor" import { useExit } from "../../context/exit" import * as Clipboard from "../../util/clipboard" @@ -94,6 +95,7 @@ export function Prompt(props: PromptProps) { const local = useLocal() const args = useArgs() const sdk = useSDK() + const editor = useEditorContext() const route = useRoute() const project = useProject() const sync = useSync() @@ -104,11 +106,34 @@ export function Prompt(props: PromptProps) { const stash = usePromptStash() const command = useCommandDialog() const renderer = useRenderer() + const dimensions = useTerminalDimensions() const { theme, syntax } = useTheme() const kv = useKV() const animationsEnabled = createMemo(() => kv.get("animations_enabled", true)) const list = createMemo(() => props.placeholders?.normal ?? []) const shell = createMemo(() => props.placeholders?.shell ?? []) + const editorPath = createMemo(() => editor.selection()?.filePath) + const editorSelectionLabel = createMemo(() => { + const selection = editor.selection()?.selection + if (!selection) return + if (selection.start.line === selection.end.line && selection.start.character === selection.end.character) return + if (selection.start.line === selection.end.line) return `#${selection.start.line}` + return `#${selection.start.line}-${selection.end.line}` + }) + const editorFileLabel = createMemo(() => { + const value = editorPath() + if (!value) return + const filename = path.basename(value) + const file = /^index\.[^./]+$/.test(filename) + ? [path.basename(path.dirname(value)), filename].filter(Boolean).join("/") + : filename + return `${file.split(path.sep).join("/")}${editorSelectionLabel() ?? ""}` + }) + const editorFileLabelDisplay = createMemo(() => { + const file = editorFileLabel() + if (!file) return + return Locale.truncateMiddle(file, Math.max(12, Math.min(48, Math.floor(dimensions().width / 3)))) + }) const [auto, setAuto] = createSignal() const currentProviderLabel = createMemo(() => local.model.parsed().provider) const hasRightContent = createMemo(() => Boolean(props.right)) @@ -721,6 +746,27 @@ export function Prompt(props: PromptProps) { // Capture mode before it gets reset const currentMode = store.mode const variant = local.model.variant.current() + const editorSelection = editor.selection() + const editorParts = editorSelection + ? [ + { + id: PartID.ascending(), + type: "text" as const, + text: (() => { + const start = editorSelection.selection.start + const end = editorSelection.selection.end + if (start.line === end.line && start.character === end.character) { + return `Note: The user opened the file "${editorSelection.filePath}".` + } + if (start.line === end.line) { + return `Note: The user selected line ${start.line} from "${editorSelection.filePath}": ${editorSelection.text}` + } + return `Note: The user selected lines ${start.line} to ${end.line} from "${editorSelection.filePath}": ${editorSelection.text}` + })(), + synthetic: true, + }, + ] + : [] if (store.mode === "shell") { void sdk.client.session.shell({ @@ -773,6 +819,7 @@ export function Prompt(props: PromptProps) { model: selectedModel, variant, parts: [ + ...editorParts, { id: PartID.ascending(), type: "text", @@ -1332,6 +1379,7 @@ export function Prompt(props: PromptProps) { + {(file) => {file()}} diff --git a/packages/opencode/src/cli/cmd/tui/context/editor.ts b/packages/opencode/src/cli/cmd/tui/context/editor.ts new file mode 100644 index 0000000000..663b0f0fac --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/context/editor.ts @@ -0,0 +1,318 @@ +import { readdirSync, readFileSync, statSync } from "node:fs" +import os from "node:os" +import path from "node:path" +import { onCleanup, onMount } from "solid-js" +import { createStore } from "solid-js/store" +import z from "zod" +import { createSimpleContext } from "./helper" + +const MCP_PROTOCOL_VERSION = "2025-11-25" + +const JsonRpcMessageSchema = z.object({ + id: z.union([z.number(), z.string(), z.null()]).optional(), + method: z.string().optional(), + params: z.unknown().optional(), + result: z.unknown().optional(), + error: z + .object({ + code: z.number().optional(), + message: z.string().optional(), + }) + .optional(), +}) + +const PositionSchema = z.object({ + line: z.number(), + character: z.number(), +}) + +const EditorSelectionSchema = z.object({ + text: z.string(), + filePath: z.string(), + selection: z.object({ + start: PositionSchema, + end: PositionSchema, + }), +}) + +const EditorMentionSchema = z.object({ + filePath: z.string(), + lineStart: z.number(), + lineEnd: z.number(), +}) + +const EditorServerInfoSchema = z.object({ + protocolVersion: z.string().optional(), + serverInfo: z + .object({ + name: z.string().optional(), + version: z.string().optional(), + }) + .optional(), +}) + +type JsonRpcMessage = z.infer +export type EditorSelection = z.infer +export type EditorMention = z.infer +type EditorServerInfo = z.infer + +type EditorConnection = { + url: string + authToken?: string + source: string +} + +type EditorLockFile = { + port: number + authToken?: string + transport?: string + workspaceFolders: string[] + mtimeMs: number +} + +export const { use: useEditorContext, provider: EditorContextProvider } = createSimpleContext({ + name: "EditorContext", + init: () => { + const mentionListeners = new Set<(mention: EditorMention) => void>() + const [store, setStore] = createStore<{ + status: "disabled" | "connecting" | "connected" + selection: EditorSelection | undefined + server: EditorServerInfo | undefined + }>({ + status: "disabled", + selection: undefined, + server: undefined, + }) + + onMount(() => { + let socket: WebSocket | undefined + let closed = false + let reconnect: ReturnType | undefined + let attempt = 0 + let requestID = 0 + const pending = new Map() + + const send = (payload: JsonRpcMessage) => { + if (!socket || socket.readyState !== WebSocket.OPEN) return + socket.send(JSON.stringify({ jsonrpc: "2.0", ...payload })) + } + + const request = (method: string, params?: unknown) => { + requestID += 1 + pending.set(requestID, method) + send({ id: requestID, method, params }) + } + + const scheduleReconnect = (delay: number) => { + if (closed) return + if (reconnect) clearTimeout(reconnect) + reconnect = setTimeout(connect, delay) + } + + const connect = () => { + if (closed) return + + const connection = resolveEditorConnection() + if (!connection) { + setStore("status", "disabled") + scheduleReconnect(1000) + return + } + + setStore("status", "connecting") + const current = openEditorSocket(connection) + socket = current + + current.addEventListener("open", () => { + if (socket !== current) { + current.close() + return + } + + attempt = 0 + setStore("status", "connected") + request("initialize", { + protocolVersion: MCP_PROTOCOL_VERSION, + capabilities: {}, + clientInfo: { name: "opencode", version: "0.0.0" }, + }) + }) + + current.addEventListener("message", (event) => { + const message = parseMessage(event.data) + if (!message) return + + const selection = message.method === "selection_changed" ? EditorSelectionSchema.safeParse(message.params) : undefined + if (selection?.success) { + setStore("selection", selection.data) + return + } + + const mention = message.method === "at_mentioned" ? EditorMentionSchema.safeParse(message.params) : undefined + if (mention?.success) { + mentionListeners.forEach((listener) => listener(mention.data)) + return + } + + if (typeof message.id !== "number") return + + const method = pending.get(message.id) + if (!method) return + + pending.delete(message.id) + if (message.error) return + + const initialize = method === "initialize" ? EditorServerInfoSchema.safeParse(message.result) : undefined + if (initialize?.success) { + setStore("server", initialize.data) + send({ method: "notifications/initialized" }) + return + } + }) + + current.addEventListener("close", () => { + if (socket !== current) return + + socket = undefined + pending.clear() + if (closed) return + + setStore("status", "connecting") + attempt += 1 + const delay = Math.min(1000 * 2 ** (attempt - 1), 30000) + scheduleReconnect(delay) + }) + } + + scheduleReconnect(0) + + onCleanup(() => { + closed = true + if (reconnect) clearTimeout(reconnect) + socket?.close() + }) + }) + + return { + enabled() { + return Boolean(resolveEditorConnection()) + }, + connected() { + return store.status === "connected" + }, + selection() { + return store.selection + }, + onMention(listener: (mention: EditorMention) => void) { + mentionListeners.add(listener) + return () => mentionListeners.delete(listener) + }, + server() { + return store.server + }, + } + }, +}) + +function parsePort(value: string | undefined) { + if (!value) return + + const parsed = Number.parseInt(value, 10) + if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) return + return parsed +} + +function resolveEditorConnection(): EditorConnection | undefined { + const lock = resolveEditorLockFile() + if (lock) { + return { + url: `ws://127.0.0.1:${lock.port}`, + authToken: lock.authToken, + source: `lock:${lock.port}`, + } + } + + const port = parsePort(process.env.CLAUDE_CODE_SSE_PORT || process.env.OPENCODE_EDITOR_SSE_PORT) + if (!port) return + return { + url: `ws://127.0.0.1:${port}`, + source: `env:${port}`, + } +} + +function resolveEditorLockFile() { + const directory = path.join(os.homedir(), ".claude", "ide") + let entries: string[] + + try { + entries = readdirSync(directory) + } catch { + return + } + + const cwd = process.cwd() + const locks = entries + .filter((entry) => entry.endsWith(".lock")) + .map((entry) => readEditorLockFile(path.join(directory, entry))) + .filter((entry): entry is EditorLockFile => Boolean(entry)) + .sort((left, right) => scoreEditorLock(right, cwd) - scoreEditorLock(left, cwd)) + + return locks[0] +} + +function readEditorLockFile(filePath: string): EditorLockFile | undefined { + const port = parsePort(path.basename(filePath, ".lock")) + if (!port) return + + try { + const parsed = JSON.parse(readFileSync(filePath, "utf-8")) as unknown + if (!isRecord(parsed)) return + if (parsed.transport !== undefined && parsed.transport !== "ws") return + + return { + port, + authToken: typeof parsed.authToken === "string" ? parsed.authToken : undefined, + transport: typeof parsed.transport === "string" ? parsed.transport : undefined, + workspaceFolders: Array.isArray(parsed.workspaceFolders) + ? parsed.workspaceFolders.filter((value): value is string => typeof value === "string") + : [], + mtimeMs: statSync(filePath).mtimeMs, + } + } catch { + return + } +} + +function scoreEditorLock(lock: EditorLockFile, cwd: string) { + const workspaceMatch = lock.workspaceFolders.some((folder) => pathContains(folder, cwd)) ? 1 : 0 + return workspaceMatch * 1_000_000_000_000 + lock.mtimeMs +} + +function pathContains(parent: string, child: string) { + const relative = path.relative(path.resolve(parent), path.resolve(child)) + return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative)) +} + +function openEditorSocket(connection: EditorConnection) { + if (!connection.authToken) return new WebSocket(connection.url) + + return new WebSocket(connection.url, { + headers: { + "x-claude-code-ide-authorization": connection.authToken, + }, + } as any) +} + +function parseMessage(value: unknown) { + if (typeof value !== "string") return + + try { + return JsonRpcMessageSchema.parse(JSON.parse(value)) + } catch { + return + } +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value) +} From 4c3e65c87728b04888ccbf3879aca13a3857bb08 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 20:20:26 +0000 Subject: [PATCH 18/59] chore: generate --- packages/opencode/src/cli/cmd/tui/context/editor.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/context/editor.ts b/packages/opencode/src/cli/cmd/tui/context/editor.ts index 663b0f0fac..4e6c97f6e5 100644 --- a/packages/opencode/src/cli/cmd/tui/context/editor.ts +++ b/packages/opencode/src/cli/cmd/tui/context/editor.ts @@ -142,7 +142,8 @@ export const { use: useEditorContext, provider: EditorContextProvider } = create const message = parseMessage(event.data) if (!message) return - const selection = message.method === "selection_changed" ? EditorSelectionSchema.safeParse(message.params) : undefined + const selection = + message.method === "selection_changed" ? EditorSelectionSchema.safeParse(message.params) : undefined if (selection?.success) { setStore("selection", selection.data) return From 814e83ffecfddeb60cc88bba03b0c00629eecf5e Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 16:23:45 -0400 Subject: [PATCH 19/59] docs: update effect schema migration tracker (#24054) --- packages/opencode/specs/effect/schema.md | 50 ++++++++++++------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index 0d451f4a99..04f994e310 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -254,8 +254,8 @@ Working rule for this cluster: 5. Errors and event payloads last - `NamedError.create(...)` shapes can stay temporarily if converting them to `Schema.TaggedErrorClass` would force unrelated churn - - `SyncEvent.define(...)` and `BusEvent.define(...)` payloads can keep using - derived `.zod` until the sync/bus layers are migrated + - `SyncEvent.define(...)` and `BusEvent.define(...)` payloads can use + derived `.zod` at remaining zod-based HTTP/OpenAPI boundaries Possible later tightening after the Schema-first migration is stable: @@ -284,25 +284,25 @@ Each tool declares its parameters via a zod schema. Tools are consumed by both the in-process runtime and the AI SDK's tool-calling layer, so the emitted JSON Schema must stay byte-identical. -- [ ] `src/tool/apply_patch.ts` -- [ ] `src/tool/bash.ts` -- [ ] `src/tool/codesearch.ts` -- [ ] `src/tool/edit.ts` -- [ ] `src/tool/glob.ts` -- [ ] `src/tool/grep.ts` -- [ ] `src/tool/invalid.ts` -- [ ] `src/tool/lsp.ts` -- [ ] `src/tool/plan.ts` -- [ ] `src/tool/question.ts` -- [ ] `src/tool/read.ts` -- [ ] `src/tool/registry.ts` -- [ ] `src/tool/skill.ts` -- [ ] `src/tool/task.ts` -- [ ] `src/tool/todo.ts` -- [ ] `src/tool/tool.ts` -- [ ] `src/tool/webfetch.ts` -- [ ] `src/tool/websearch.ts` -- [ ] `src/tool/write.ts` +- [x] `src/tool/apply_patch.ts` +- [x] `src/tool/bash.ts` +- [x] `src/tool/codesearch.ts` +- [x] `src/tool/edit.ts` +- [x] `src/tool/glob.ts` +- [x] `src/tool/grep.ts` +- [x] `src/tool/invalid.ts` +- [x] `src/tool/lsp.ts` +- [x] `src/tool/plan.ts` +- [x] `src/tool/question.ts` +- [x] `src/tool/read.ts` +- [x] `src/tool/registry.ts` +- [x] `src/tool/skill.ts` +- [x] `src/tool/task.ts` +- [x] `src/tool/todo.ts` +- [x] `src/tool/tool.ts` +- [x] `src/tool/webfetch.ts` +- [x] `src/tool/websearch.ts` +- [x] `src/tool/write.ts` ### HTTP route boundaries @@ -313,8 +313,8 @@ which means touching them is largely mechanical once the domain side is done. - [ ] `src/server/error.ts` -- [ ] `src/server/event.ts` -- [ ] `src/server/projectors.ts` +- [x] `src/server/event.ts` +- [x] `src/server/projectors.ts` - [ ] `src/server/routes/control/index.ts` - [ ] `src/server/routes/control/workspace.ts` - [ ] `src/server/routes/global.ts` @@ -346,7 +346,7 @@ piecewise. - [ ] `src/acp/agent.ts` - [ ] `src/agent/agent.ts` -- [ ] `src/bus/bus-event.ts` +- [x] `src/bus/bus-event.ts` - [ ] `src/bus/index.ts` - [ ] `src/cli/cmd/tui/config/tui-migrate.ts` - [ ] `src/cli/cmd/tui/config/tui-schema.ts` @@ -376,7 +376,7 @@ piecewise. - [ ] `src/snapshot/index.ts` - [ ] `src/storage/db.ts` - [ ] `src/storage/storage.ts` -- [x] `src/sync/index.ts` — public API (`SyncEvent.define`) is Schema-first; still stores derived zod `schema`/`properties` on each `Definition` as a compat bridge for `BusEvent` until `bus/bus-event.ts` migrates +- [x] `src/sync/index.ts` — public API (`SyncEvent.define`) is Schema-first; `payloads()` still derives zod for the remaining HTTP/OpenAPI boundary - [ ] `src/util/fn.ts` - [ ] `src/util/log.ts` - [ ] `src/util/update-schema.ts` From 31d01d404afd4f82628b3f388d5c73debf250ffd Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 16:48:56 -0400 Subject: [PATCH 20/59] refactor(control-plane): migrate workspace DTO schemas (#24056) --- packages/opencode/specs/effect/schema.md | 6 +-- .../src/control-plane/adaptors/index.ts | 8 +--- .../src/control-plane/adaptors/worktree.ts | 20 +++++----- packages/opencode/src/control-plane/types.ts | 31 +++++++++----- .../opencode/src/control-plane/workspace.ts | 40 +++++++++---------- .../src/server/routes/control/workspace.ts | 27 +++++-------- 6 files changed, 67 insertions(+), 65 deletions(-) diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index 04f994e310..0319df4a0e 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -354,9 +354,9 @@ piecewise. - [ ] `src/cli/cmd/tui/event.ts` - [ ] `src/cli/ui.ts` - [ ] `src/command/index.ts` -- [ ] `src/control-plane/adaptors/worktree.ts` -- [ ] `src/control-plane/types.ts` -- [ ] `src/control-plane/workspace.ts` +- [x] `src/control-plane/adaptors/worktree.ts` +- [x] `src/control-plane/types.ts` +- [x] `src/control-plane/workspace.ts` - [ ] `src/file/index.ts` - [ ] `src/file/ripgrep.ts` - [ ] `src/file/watcher.ts` diff --git a/packages/opencode/src/control-plane/adaptors/index.ts b/packages/opencode/src/control-plane/adaptors/index.ts index 291e392eab..651d09cc21 100644 --- a/packages/opencode/src/control-plane/adaptors/index.ts +++ b/packages/opencode/src/control-plane/adaptors/index.ts @@ -1,12 +1,6 @@ import { lazy } from "@/util/lazy" import type { ProjectID } from "@/project/schema" -import type { WorkspaceAdaptor } from "../types" - -export type WorkspaceAdaptorEntry = { - type: string - name: string - description: string -} +import type { WorkspaceAdaptor, WorkspaceAdaptorEntry } from "../types" const BUILTIN: Record Promise> = { worktree: lazy(async () => (await import("./worktree")).WorktreeAdaptor), diff --git a/packages/opencode/src/control-plane/adaptors/worktree.ts b/packages/opencode/src/control-plane/adaptors/worktree.ts index 2bfb7debaa..8d421b9a33 100644 --- a/packages/opencode/src/control-plane/adaptors/worktree.ts +++ b/packages/opencode/src/control-plane/adaptors/worktree.ts @@ -1,13 +1,15 @@ -import z from "zod" +import { Schema } from "effect" import { AppRuntime } from "@/effect/app-runtime" import { Worktree } from "@/worktree" import { type WorkspaceAdaptor, WorkspaceInfo } from "../types" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" -const WorktreeConfig = z.object({ - name: WorkspaceInfo.shape.name, - branch: WorkspaceInfo.shape.branch.unwrap(), - directory: WorkspaceInfo.shape.directory.unwrap(), -}) +const WorktreeConfig = Schema.Struct({ + name: WorkspaceInfo.fields.name, + branch: Schema.String, + directory: Schema.String, +}).pipe(withStatics((s) => ({ zod: zod(s) }))) export const WorktreeAdaptor: WorkspaceAdaptor = { name: "Worktree", @@ -22,7 +24,7 @@ export const WorktreeAdaptor: WorkspaceAdaptor = { } }, async create(info) { - const config = WorktreeConfig.parse(info) + const config = WorktreeConfig.zod.parse(info) await AppRuntime.runPromise( Worktree.Service.use((svc) => svc.createFromInfo({ @@ -34,11 +36,11 @@ export const WorktreeAdaptor: WorkspaceAdaptor = { ) }, async remove(info) { - const config = WorktreeConfig.parse(info) + const config = WorktreeConfig.zod.parse(info) await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.remove({ directory: config.directory }))) }, target(info) { - const config = WorktreeConfig.parse(info) + const config = WorktreeConfig.zod.parse(info) return { type: "local", directory: config.directory, diff --git a/packages/opencode/src/control-plane/types.ts b/packages/opencode/src/control-plane/types.ts index 07acd5ce58..af16c04902 100644 --- a/packages/opencode/src/control-plane/types.ts +++ b/packages/opencode/src/control-plane/types.ts @@ -1,17 +1,28 @@ -import z from "zod" +import { Schema } from "effect" import { ProjectID } from "@/project/schema" import { WorkspaceID } from "./schema" +import { zod } from "@/util/effect-zod" +import { type DeepMutable, withStatics } from "@/util/schema" -export const WorkspaceInfo = z.object({ - id: WorkspaceID.zod, - type: z.string(), - name: z.string(), - branch: z.string().nullable(), - directory: z.string().nullable(), - extra: z.unknown().nullable(), - projectID: ProjectID.zod, +export const WorkspaceInfo = Schema.Struct({ + id: WorkspaceID, + type: Schema.String, + name: Schema.String, + branch: Schema.NullOr(Schema.String), + directory: Schema.NullOr(Schema.String), + extra: Schema.NullOr(Schema.Unknown), + projectID: ProjectID, }) -export type WorkspaceInfo = z.infer + .annotate({ identifier: "Workspace" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type WorkspaceInfo = DeepMutable> + +export const WorkspaceAdaptorEntry = Schema.Struct({ + type: Schema.String, + name: Schema.String, + description: Schema.String, +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export type WorkspaceAdaptorEntry = Schema.Schema.Type export type Target = | { diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index 8519cb06c1..107f2d9903 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -1,4 +1,3 @@ -import z from "zod" import { Schema } from "effect" import { setTimeout as sleep } from "node:timers/promises" import { fn } from "@/util/fn" @@ -16,7 +15,7 @@ import { ProjectID } from "@/project/schema" import { Slug } from "@opencode-ai/shared/util/slug" import { WorkspaceTable } from "./workspace.sql" import { getAdaptor } from "./adaptors" -import { WorkspaceInfo } from "./types" +import { type WorkspaceInfo, WorkspaceInfo as WorkspaceInfoSchema } from "./types" import { WorkspaceID } from "./schema" import { parseSSE } from "./sse" import { Session } from "@/session" @@ -26,12 +25,11 @@ import { errorData } from "@/util/error" import { AppRuntime } from "@/effect/app-runtime" import { waitEvent } from "./util" import { WorkspaceContext } from "./workspace-context" -import { NonNegativeInt } from "@/util/schema" +import { NonNegativeInt, withStatics } from "@/util/schema" +import { zod as effectZod, zodObject } from "@/util/effect-zod" -export const Info = WorkspaceInfo.meta({ - ref: "Workspace", -}) -export type Info = z.infer +export const Info = WorkspaceInfoSchema +export type Info = WorkspaceInfo export const ConnectionStatus = Schema.Struct({ workspaceID: WorkspaceID, @@ -75,15 +73,16 @@ function fromRow(row: typeof WorkspaceTable.$inferSelect): Info { } } -const CreateInput = z.object({ - id: WorkspaceID.zod.optional(), - type: Info.shape.type, - branch: Info.shape.branch, - projectID: ProjectID.zod, - extra: Info.shape.extra, -}) +export const CreateInput = Schema.Struct({ + id: Schema.optional(WorkspaceID), + type: Info.fields.type, + branch: Info.fields.branch, + projectID: ProjectID, + extra: Info.fields.extra, +}).pipe(withStatics((s) => ({ zod: effectZod(s), zodObject: zodObject(s) }))) +export type CreateInput = Schema.Schema.Type -export const create = fn(CreateInput, async (input) => { +export const create = fn(CreateInput.zod, async (input) => { const id = WorkspaceID.ascending(input.id) const adaptor = await getAdaptor(input.projectID, input.type) @@ -139,12 +138,13 @@ export const create = fn(CreateInput, async (input) => { return info }) -const SessionRestoreInput = z.object({ - workspaceID: WorkspaceID.zod, - sessionID: SessionID.zod, -}) +export const SessionRestoreInput = Schema.Struct({ + workspaceID: WorkspaceID, + sessionID: SessionID, +}).pipe(withStatics((s) => ({ zod: effectZod(s), zodObject: zodObject(s) }))) +export type SessionRestoreInput = Schema.Schema.Type -export const sessionRestore = fn(SessionRestoreInput, async (input) => { +export const sessionRestore = fn(SessionRestoreInput.zod, async (input) => { log.info("session restore requested", { workspaceID: input.workspaceID, sessionID: input.sessionID, diff --git a/packages/opencode/src/server/routes/control/workspace.ts b/packages/opencode/src/server/routes/control/workspace.ts index d48c687f31..ffbaa6009c 100644 --- a/packages/opencode/src/server/routes/control/workspace.ts +++ b/packages/opencode/src/server/routes/control/workspace.ts @@ -3,6 +3,7 @@ import { describeRoute, resolver, validator } from "hono-openapi" import z from "zod" import { listAdaptors } from "@/control-plane/adaptors" import { Workspace } from "@/control-plane/workspace" +import { WorkspaceAdaptorEntry } from "@/control-plane/types" import { zodObject } from "@/util/effect-zod" import { Instance } from "@/project/instance" import { errors } from "../../error" @@ -26,13 +27,7 @@ export const WorkspaceRoutes = lazy(() => content: { "application/json": { schema: resolver( - z.array( - z.object({ - type: z.string(), - name: z.string(), - description: z.string(), - }), - ), + z.array(zodObject(WorkspaceAdaptorEntry)), ), }, }, @@ -54,7 +49,7 @@ export const WorkspaceRoutes = lazy(() => description: "Workspace created", content: { "application/json": { - schema: resolver(Workspace.Info), + schema: resolver(Workspace.Info.zod), }, }, }, @@ -63,12 +58,12 @@ export const WorkspaceRoutes = lazy(() => }), validator( "json", - Workspace.create.schema.omit({ + Workspace.CreateInput.zodObject.omit({ projectID: true, }), ), async (c) => { - const body = c.req.valid("json") + const body = c.req.valid("json") as Omit const workspace = await Workspace.create({ projectID: Instance.project.id, ...body, @@ -87,7 +82,7 @@ export const WorkspaceRoutes = lazy(() => description: "Workspaces", content: { "application/json": { - schema: resolver(z.array(Workspace.Info)), + schema: resolver(z.array(Workspace.Info.zod)), }, }, }, @@ -130,7 +125,7 @@ export const WorkspaceRoutes = lazy(() => description: "Workspace removed", content: { "application/json": { - schema: resolver(Workspace.Info.optional()), + schema: resolver(Workspace.Info.zod.optional()), }, }, }, @@ -140,7 +135,7 @@ export const WorkspaceRoutes = lazy(() => validator( "param", z.object({ - id: Workspace.Info.shape.id, + id: zodObject(Workspace.Info).shape.id, }), ), async (c) => { @@ -170,11 +165,11 @@ export const WorkspaceRoutes = lazy(() => ...errors(400), }, }), - validator("param", z.object({ id: Workspace.Info.shape.id })), - validator("json", Workspace.sessionRestore.schema.omit({ workspaceID: true })), + validator("param", z.object({ id: zodObject(Workspace.Info).shape.id })), + validator("json", Workspace.SessionRestoreInput.zodObject.omit({ workspaceID: true })), async (c) => { const { id } = c.req.valid("param") - const body = c.req.valid("json") + const body = c.req.valid("json") as Omit log.info("session restore route requested", { workspaceID: id, sessionID: body.sessionID, From a771859362ab61983671ca9a82ec1ccfa34fc689 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 20:50:19 +0000 Subject: [PATCH 21/59] chore: generate --- packages/opencode/src/server/routes/control/workspace.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/opencode/src/server/routes/control/workspace.ts b/packages/opencode/src/server/routes/control/workspace.ts index ffbaa6009c..bf5584347d 100644 --- a/packages/opencode/src/server/routes/control/workspace.ts +++ b/packages/opencode/src/server/routes/control/workspace.ts @@ -26,9 +26,7 @@ export const WorkspaceRoutes = lazy(() => description: "Workspace adaptors", content: { "application/json": { - schema: resolver( - z.array(zodObject(WorkspaceAdaptorEntry)), - ), + schema: resolver(z.array(zodObject(WorkspaceAdaptorEntry))), }, }, }, From 87321942fee25786448f02284ebeaee1caf4e86c Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:08:46 -0400 Subject: [PATCH 22/59] chore: update copilot readme to symlink to an agents md to prevent dumbass agents from touching these files (#24057) --- packages/opencode/src/provider/sdk/copilot/AGENTS.md | 1 + packages/opencode/src/provider/sdk/copilot/README.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 120000 packages/opencode/src/provider/sdk/copilot/AGENTS.md diff --git a/packages/opencode/src/provider/sdk/copilot/AGENTS.md b/packages/opencode/src/provider/sdk/copilot/AGENTS.md new file mode 120000 index 0000000000..42061c01a1 --- /dev/null +++ b/packages/opencode/src/provider/sdk/copilot/AGENTS.md @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/packages/opencode/src/provider/sdk/copilot/README.md b/packages/opencode/src/provider/sdk/copilot/README.md index 8ce03d6140..d1051a4da0 100644 --- a/packages/opencode/src/provider/sdk/copilot/README.md +++ b/packages/opencode/src/provider/sdk/copilot/README.md @@ -1,5 +1,5 @@ This is a temporary package used primarily for GitHub Copilot compatibility. -Avoid making changes to these files unless you only want to affect the Copilot provider. +These DO NOT apply for openai-compatible providers or majority of providers supporting completions/responses apis. THIS IS ONLY FOR GITHUB COPILOT!!! -Also, this should ONLY be used for the Copilot provider. +Avoid making edits to these files From 334ab4707c809172e77619ae7d6b22c5577c7238 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:31:43 -0400 Subject: [PATCH 23/59] fix: account for additional openai retry case (#24063) --- packages/opencode/src/provider/error.ts | 12 ++++++++-- .../opencode/test/session/message-v2.test.ts | 24 +++++++++++++++++++ packages/opencode/test/session/retry.test.ts | 22 +++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/provider/error.ts b/packages/opencode/src/provider/error.ts index a2409559f5..a4f629caf3 100644 --- a/packages/opencode/src/provider/error.ts +++ b/packages/opencode/src/provider/error.ts @@ -111,12 +111,13 @@ export type ParsedStreamError = | { type: "api_error" message: string - isRetryable: false + isRetryable: boolean responseBody: string } export function parseStreamError(input: unknown): ParsedStreamError | undefined { - const body = json(input) + const raw = json(input) + const body = typeof raw?.message === "string" ? (json(raw.message) ?? raw) : raw if (!body) return const responseBody = JSON.stringify(body) @@ -150,6 +151,13 @@ export function parseStreamError(input: unknown): ParsedStreamError | undefined isRetryable: false, responseBody, } + case "server_error": + return { + type: "api_error", + message: typeof body?.error?.message === "string" ? body?.error?.message : "Server error.", + isRetryable: true, + responseBody, + } } } diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index 231d58c21a..abada013df 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -1075,6 +1075,30 @@ describe("session.message-v2.fromError", () => { }) }) + test("serializes OpenAI response server_error stream chunks as retryable APIError", () => { + const body = { + type: "error", + sequence_number: 2, + error: { + type: "server_error", + code: "server_error", + message: + "An error occurred while processing your request. You can retry your request, or contact us through our help center at help.openai.com if the error persists. Please include the request ID req_77eccd008d984bf6bf82d1b2c2b68715 in your message.", + param: null, + }, + } + const result = MessageV2.fromError({ message: JSON.stringify(body) }, { providerID }) + + expect(result).toStrictEqual({ + name: "APIError", + data: { + message: body.error.message, + isRetryable: true, + responseBody: JSON.stringify(body), + }, + }) + }) + test("detects context overflow from APICallError provider messages", () => { const cases = [ "prompt is too long: 213462 tokens > 200000 maximum", diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts index ade2647869..6ca8775f30 100644 --- a/packages/opencode/test/session/retry.test.ts +++ b/packages/opencode/test/session/retry.test.ts @@ -294,4 +294,26 @@ describe("session.message-v2.fromError", () => { const result = MessageV2.fromError(error, { providerID: ProviderID.make("openai") }) as MessageV2.APIError expect(result.data.isRetryable).toBe(true) }) + + test("converts OpenAI server_error stream chunks to retryable APIError", () => { + const result = MessageV2.fromError( + { + message: JSON.stringify({ + type: "error", + sequence_number: 2, + error: { + type: "server_error", + code: "server_error", + message: "An error occurred while processing your request.", + param: null, + }, + }), + }, + { providerID: ProviderID.make("openai") }, + ) + + expect(MessageV2.APIError.isInstance(result)).toBe(true) + expect((result as MessageV2.APIError).data.isRetryable).toBe(true) + expect(SessionRetry.retryable(result)).toBe("An error occurred while processing your request.") + }) }) From e50a688ca309ba4c992fd8e47b5b75a11aef025e Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 17:32:02 -0400 Subject: [PATCH 24/59] feat(httpapi): bridge workspace read endpoints (#24062) --- packages/opencode/specs/effect/http-api.md | 4 +- .../server/routes/instance/httpapi/server.ts | 3 + .../routes/instance/httpapi/workspace.ts | 82 +++++++++++++++++++ packages/opencode/src/server/server.ts | 25 ++++-- .../test/server/httpapi-workspace.test.ts | 55 +++++++++++++ 5 files changed, 160 insertions(+), 9 deletions(-) create mode 100644 packages/opencode/src/server/routes/instance/httpapi/workspace.ts create mode 100644 packages/opencode/test/server/httpapi-workspace.test.ts diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index d882857ba1..6c80dc65a2 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -409,7 +409,7 @@ Current instance route inventory: - `project` - `bridged` (partial) bridged endpoints: `GET /project`, `GET /project/current` defer git-init mutation first -- `workspace` - `next` +- `workspace` - `bridged` best small reads: `GET /experimental/workspace/adaptor`, `GET /experimental/workspace`, `GET /experimental/workspace/status` defer create/remove mutations first - `file` - `later` @@ -448,7 +448,7 @@ Recommended near-term sequence: - [x] port `config` providers read endpoint - [x] port `project` read endpoints (`GET /project`, `GET /project/current`) - [x] port `GET /config` full read endpoint -- [ ] port `workspace` read endpoints +- [x] port `workspace` read endpoints - [ ] port `file` JSON read endpoints - [ ] decide when to remove the flag and make Effect routes the default diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index d012e2c166..7b131d4000 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -14,6 +14,7 @@ import { PermissionApi, permissionHandlers } from "./permission" import { ProjectApi, projectHandlers } from "./project" import { ProviderApi, providerHandlers } from "./provider" import { QuestionApi, questionHandlers } from "./question" +import { WorkspaceApi, workspaceHandlers } from "./workspace" import { memoMap } from "@/effect/memo-map" const Query = Schema.Struct({ @@ -112,6 +113,7 @@ const PermissionSecured = PermissionApi.middleware(Authorization) const ProjectSecured = ProjectApi.middleware(Authorization) const ProviderSecured = ProviderApi.middleware(Authorization) const ConfigSecured = ConfigApi.middleware(Authorization) +const WorkspaceSecured = WorkspaceApi.middleware(Authorization) export const routes = Layer.mergeAll( HttpApiBuilder.layer(ConfigSecured).pipe(Layer.provide(configHandlers)), @@ -119,6 +121,7 @@ export const routes = Layer.mergeAll( HttpApiBuilder.layer(QuestionSecured).pipe(Layer.provide(questionHandlers)), HttpApiBuilder.layer(PermissionSecured).pipe(Layer.provide(permissionHandlers)), HttpApiBuilder.layer(ProviderSecured).pipe(Layer.provide(providerHandlers)), + HttpApiBuilder.layer(WorkspaceSecured).pipe(Layer.provide(workspaceHandlers)), ).pipe( Layer.provide(auth), Layer.provide(normalize), diff --git a/packages/opencode/src/server/routes/instance/httpapi/workspace.ts b/packages/opencode/src/server/routes/instance/httpapi/workspace.ts new file mode 100644 index 0000000000..596545073e --- /dev/null +++ b/packages/opencode/src/server/routes/instance/httpapi/workspace.ts @@ -0,0 +1,82 @@ +import { listAdaptors } from "@/control-plane/adaptors" +import { Workspace } from "@/control-plane/workspace" +import { WorkspaceAdaptorEntry } from "@/control-plane/types" +import * as InstanceState from "@/effect/instance-state" +import { Effect, Layer, Schema } from "effect" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" + +const root = "/experimental/workspace" +export const WorkspacePaths = { + adaptors: `${root}/adaptor`, + list: root, + status: `${root}/status`, +} as const + +export const WorkspaceApi = HttpApi.make("workspace") + .add( + HttpApiGroup.make("workspace") + .add( + HttpApiEndpoint.get("adaptors", WorkspacePaths.adaptors, { + success: Schema.Array(WorkspaceAdaptorEntry), + }).annotateMerge( + OpenApi.annotations({ + identifier: "experimental.workspace.adaptor.list", + summary: "List workspace adaptors", + description: "List all available workspace adaptors for the current project.", + }), + ), + HttpApiEndpoint.get("list", WorkspacePaths.list, { + success: Schema.Array(Workspace.Info), + }).annotateMerge( + OpenApi.annotations({ + identifier: "experimental.workspace.list", + summary: "List workspaces", + description: "List all workspaces.", + }), + ), + HttpApiEndpoint.get("status", WorkspacePaths.status, { + success: Schema.Array(Workspace.ConnectionStatus), + }).annotateMerge( + OpenApi.annotations({ + identifier: "experimental.workspace.status", + summary: "Workspace status", + description: "Get connection status for workspaces in the current project.", + }), + ), + ) + .annotateMerge( + OpenApi.annotations({ + title: "workspace", + description: "Experimental HttpApi workspace routes.", + }), + ), + ) + .annotateMerge( + OpenApi.annotations({ + title: "opencode experimental HttpApi", + version: "0.0.1", + description: "Experimental HttpApi surface for selected instance routes.", + }), + ) + +export const workspaceHandlers = Layer.unwrap( + Effect.gen(function* () { + const adaptors = Effect.fn("WorkspaceHttpApi.adaptors")(function* () { + const ctx = yield* InstanceState.context + return yield* Effect.promise(() => listAdaptors(ctx.project.id)) + }) + + const list = Effect.fn("WorkspaceHttpApi.list")(function* () { + return Workspace.list((yield* InstanceState.context).project) + }) + + const status = Effect.fn("WorkspaceHttpApi.status")(function* () { + const ids = new Set(Workspace.list((yield* InstanceState.context).project).map((item) => item.id)) + return Workspace.status().filter((item) => ids.has(item.workspaceID)) + }) + + return HttpApiBuilder.group(WorkspaceApi, "workspace", (handlers) => + handlers.handle("adaptors", adaptors).handle("list", list).handle("status", status), + ) + }), +) diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 8b1f1aee10..d74de559dc 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -16,6 +16,9 @@ import { GlobalRoutes } from "./routes/global" import { WorkspaceRouterMiddleware } from "./workspace" import { InstanceMiddleware } from "./routes/instance/middleware" import { WorkspaceRoutes } from "./routes/control/workspace" +import { ExperimentalHttpApiServer } from "./routes/instance/httpapi/server" +import { WorkspacePaths } from "./routes/instance/httpapi/workspace" +import { Context } from "effect" // @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85 globalThis.AI_SDK_LOG_WARNINGS = false @@ -54,16 +57,24 @@ function create(opts: { cors?: string[] }) { } } + const workspaceApp = new Hono() + const workspaceLegacyApp = new Hono() + .use(InstanceMiddleware()) + .route("/experimental/workspace", WorkspaceRoutes()) + .use(WorkspaceRouterMiddleware(runtime.upgradeWebSocket)) + if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) { + const handler = ExperimentalHttpApiServer.webHandler().handler + const context = Context.empty() as Context.Context + workspaceApp.get(WorkspacePaths.adaptors, (c) => handler(c.req.raw, context)) + workspaceApp.get(WorkspacePaths.list, (c) => handler(c.req.raw, context)) + workspaceApp.get(WorkspacePaths.status, (c) => handler(c.req.raw, context)) + } + workspaceApp.route("/", workspaceLegacyApp) + return { app: app .route("/", ControlPlaneRoutes()) - .route( - "/", - new Hono() - .use(InstanceMiddleware()) - .route("/experimental/workspace", WorkspaceRoutes()) - .use(WorkspaceRouterMiddleware(runtime.upgradeWebSocket)), - ) + .route("/", workspaceApp) .route("/", InstanceRoutes(runtime.upgradeWebSocket)) .route("/", UIRoutes()), runtime, diff --git a/packages/opencode/test/server/httpapi-workspace.test.ts b/packages/opencode/test/server/httpapi-workspace.test.ts new file mode 100644 index 0000000000..8256d8330f --- /dev/null +++ b/packages/opencode/test/server/httpapi-workspace.test.ts @@ -0,0 +1,55 @@ +import { afterEach, describe, expect, test } from "bun:test" +import { Context } from "effect" +import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server" +import { WorkspacePaths } from "../../src/server/routes/instance/httpapi/workspace" +import { Log } from "../../src/util" +import { resetDatabase } from "../fixture/db" +import { tmpdir } from "../fixture/fixture" +import { Instance } from "../../src/project/instance" + +void Log.init({ print: false }) + +const context = Context.empty() as Context.Context + +function request(path: string, directory: string) { + return ExperimentalHttpApiServer.webHandler().handler( + new Request(`http://localhost${path}`, { + headers: { + "x-opencode-directory": directory, + }, + }), + context, + ) +} + +afterEach(async () => { + await Instance.disposeAll() + await resetDatabase() +}) + +describe("workspace HttpApi", () => { + test("serves read endpoints", async () => { + await using tmp = await tmpdir({ git: true }) + + const [adaptors, workspaces, status] = await Promise.all([ + request(WorkspacePaths.adaptors, tmp.path), + request(WorkspacePaths.list, tmp.path), + request(WorkspacePaths.status, tmp.path), + ]) + + expect(adaptors.status).toBe(200) + expect(await adaptors.json()).toEqual([ + { + type: "worktree", + name: "Worktree", + description: "Create a git worktree", + }, + ]) + + expect(workspaces.status).toBe(200) + expect(await workspaces.json()).toEqual([]) + + expect(status.status).toBe(200) + expect(await status.json()).toEqual([]) + }) +}) From f8c6ddd4cb08aab493fcaab69d44c5085083152e Mon Sep 17 00:00:00 2001 From: rahul Date: Thu, 23 Apr 2026 17:43:33 -0400 Subject: [PATCH 25/59] feat(truncate): allow configuring tool output truncation limits (#23770) Co-authored-by: rgs_ramp Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- packages/opencode/src/config/config.ts | 13 ++++ packages/opencode/src/tool/bash.ts | 17 ++--- packages/opencode/src/tool/truncate.ts | 24 +++++-- .../opencode/test/tool/truncation.test.ts | 64 +++++++++++++++++++ 4 files changed, 106 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index eab199232a..f1ceb1b4ed 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -201,6 +201,19 @@ export const Info = Schema.Struct({ url: Schema.optional(Schema.String).annotate({ description: "Enterprise URL" }), }), ), + tool_output: Schema.optional( + Schema.Struct({ + max_lines: Schema.optional(PositiveInt).annotate({ + description: "Maximum lines of tool output before it is truncated and saved to disk (default: 2000)", + }), + max_bytes: Schema.optional(PositiveInt).annotate({ + description: "Maximum bytes of tool output before it is truncated and saved to disk (default: 51200)", + }), + }), + ).annotate({ + description: + "Thresholds for truncating tool output. When output exceeds either limit, the full text is written to the truncation directory and a preview is returned.", + }), compaction: Schema.optional( Schema.Struct({ auto: Schema.optional(Schema.Boolean).annotate({ diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index 2d4d59b1bf..0a7e1a6dc2 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -416,9 +416,8 @@ export const BashTool = Tool.define( }, ctx: Tool.Context, ) { - const bytes = Truncate.MAX_BYTES - const lines = Truncate.MAX_LINES - const keep = bytes * 2 + const limits = yield* trunc.limits() + const keep = limits.maxBytes * 2 let full = "" let last = "" const list: Chunk[] = [] @@ -458,7 +457,7 @@ export const BashTool = Tool.define( sink?.write(chunk) } else { full += chunk - if (Buffer.byteLength(full, "utf-8") > bytes) { + if (Buffer.byteLength(full, "utf-8") > limits.maxBytes) { return trunc.write(full).pipe( Effect.andThen((next) => Effect.sync(() => { @@ -525,7 +524,7 @@ export const BashTool = Tool.define( } if (aborted) meta.push("User aborted the command") const raw = list.map((item) => item.text).join("") - const end = tail(raw, lines, bytes) + const end = tail(raw, limits.maxLines, limits.maxBytes) if (end.cut) cut = true if (!file && end.cut) { file = yield* trunc.write(raw) @@ -566,7 +565,7 @@ export const BashTool = Tool.define( }) return () => - Effect.sync(() => { + Effect.gen(function* () { const shell = Shell.acceptable() const name = Shell.name(shell) const chain = @@ -575,13 +574,15 @@ export const BashTool = Tool.define( : "If the commands depend on each other and must run sequentially, use a single Bash call with '&&' to chain them together (e.g., `git add . && git commit -m \"message\" && git push`). For instance, if one operation must complete before another starts (like mkdir before cp, Write before Bash for git operations, or git add before git commit), run these operations sequentially instead." log.info("bash tool using shell", { shell }) + const limits = yield* trunc.limits() + return { description: DESCRIPTION.replaceAll("${directory}", Instance.directory) .replaceAll("${os}", process.platform) .replaceAll("${shell}", name) .replaceAll("${chaining}", chain) - .replaceAll("${maxLines}", String(Truncate.MAX_LINES)) - .replaceAll("${maxBytes}", String(Truncate.MAX_BYTES)), + .replaceAll("${maxLines}", String(limits.maxLines)) + .replaceAll("${maxBytes}", String(limits.maxBytes)), parameters: Parameters, execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { diff --git a/packages/opencode/src/tool/truncate.ts b/packages/opencode/src/tool/truncate.ts index d990e7adf7..e0d846858e 100644 --- a/packages/opencode/src/tool/truncate.ts +++ b/packages/opencode/src/tool/truncate.ts @@ -1,9 +1,10 @@ import { NodePath } from "@effect/platform-node" -import { Cause, Duration, Effect, Layer, Schedule, Context } from "effect" +import { Cause, Duration, Effect, Layer, Option, Schedule, Context } from "effect" import path from "path" import type { Agent } from "../agent/agent" import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { evaluate } from "@/permission/evaluate" +import { Config } from "../config" import { Identifier } from "../id/id" import { Log } from "../util" import { ToolID } from "./schema" @@ -38,6 +39,10 @@ export interface Interface { * to the truncation directory and returns a preview plus a hint to inspect the saved file. */ readonly output: (text: string, options?: Options, agent?: Agent.Info) => Effect.Effect + /** + * Resolved truncation limits: values from `tool_output` in opencode config, or MAX_LINES / MAX_BYTES if unset. + */ + readonly limits: () => Effect.Effect<{ maxLines: number; maxBytes: number }> } export class Service extends Context.Service()("@opencode/Truncate") {} @@ -68,9 +73,20 @@ export const layer = Layer.effect( return file }) + const limits = Effect.fn("Truncate.limits")(function* () { + const configSvc = yield* Effect.serviceOption(Config.Service) + if (Option.isNone(configSvc)) return { maxLines: MAX_LINES, maxBytes: MAX_BYTES } + const cfg = yield* configSvc.value.get().pipe(Effect.catch(() => Effect.succeed(undefined))) + return { + maxLines: cfg?.tool_output?.max_lines ?? MAX_LINES, + maxBytes: cfg?.tool_output?.max_bytes ?? MAX_BYTES, + } + }) + const output = Effect.fn("Truncate.output")(function* (text: string, options: Options = {}, agent?: Agent.Info) { - const maxLines = options.maxLines ?? MAX_LINES - const maxBytes = options.maxBytes ?? MAX_BYTES + const resolved = yield* limits() + const maxLines = options.maxLines ?? resolved.maxLines + const maxBytes = options.maxBytes ?? resolved.maxBytes const direction = options.direction ?? "head" const lines = text.split("\n") const totalBytes = Buffer.byteLength(text, "utf-8") @@ -135,7 +151,7 @@ export const layer = Layer.effect( Effect.forkScoped, ) - return Service.of({ cleanup, write, output }) + return Service.of({ cleanup, write, output, limits }) }), ) diff --git a/packages/opencode/test/tool/truncation.test.ts b/packages/opencode/test/tool/truncation.test.ts index d3cec4cd9e..369ad2d581 100644 --- a/packages/opencode/test/tool/truncation.test.ts +++ b/packages/opencode/test/tool/truncation.test.ts @@ -2,6 +2,7 @@ import { describe, test, expect } from "bun:test" import { NodeFileSystem } from "@effect/platform-node" import { Effect, FileSystem, Layer } from "effect" import { Truncate } from "../../src/tool" +import { Config } from "../../src/config" import { Identifier } from "../../src/id/id" import { Process } from "../../src/util" import { Filesystem } from "../../src/util" @@ -14,6 +15,14 @@ const ROOT = path.resolve(import.meta.dir, "..", "..") const it = testEffect(Layer.mergeAll(Truncate.defaultLayer, NodeFileSystem.layer)) +const configuredLayer = (cfg: Config.Info) => + Layer.mergeAll( + Truncate.defaultLayer, + NodeFileSystem.layer, + Layer.mock(Config.Service)({ get: () => Effect.succeed(cfg) }), + ) +const configuredIt = (cfg: Config.Info) => testEffect(configuredLayer(cfg)) + describe("Truncate", () => { describe("output", () => { it.live("truncates large json file by bytes", () => @@ -94,6 +103,61 @@ describe("Truncate", () => { expect(Truncate.MAX_BYTES).toBe(50 * 1024) }) + it.live("limits() falls back to MAX_LINES/MAX_BYTES when Config is not provided", () => + Effect.gen(function* () { + const svc = yield* Truncate.Service + const resolved = yield* svc.limits() + expect(resolved.maxLines).toBe(Truncate.MAX_LINES) + expect(resolved.maxBytes).toBe(Truncate.MAX_BYTES) + }), + ) + + describe("with tool_output config", () => { + const limitsIt = configuredIt({ tool_output: { max_lines: 123, max_bytes: 456 } }) + limitsIt.live("limits() reflects config overrides", () => + Effect.gen(function* () { + const resolved = yield* (yield* Truncate.Service).limits() + expect(resolved.maxLines).toBe(123) + expect(resolved.maxBytes).toBe(456) + }), + ) + + // Huge byte budget isolates line truncation. 100 lines against max_lines: 10 + // proves the configured line limit is what `output()` enforces. + const lineIt = configuredIt({ tool_output: { max_lines: 10, max_bytes: 1024 * 1024 } }) + lineIt.live("output() truncates to configured max_lines", () => + Effect.gen(function* () { + const content = Array.from({ length: 100 }, (_, i) => `line${i}`).join("\n") + const result = yield* (yield* Truncate.Service).output(content) + expect(result.truncated).toBe(true) + expect(result.content).toContain("...90 lines truncated...") + }), + ) + + // Huge line budget isolates byte truncation. + const byteIt = configuredIt({ tool_output: { max_lines: 1_000_000, max_bytes: 100 } }) + byteIt.live("output() truncates to configured max_bytes", () => + Effect.gen(function* () { + const content = "a".repeat(1000) + const result = yield* (yield* Truncate.Service).output(content) + expect(result.truncated).toBe(true) + expect(result.content).toContain("bytes truncated...") + }), + ) + + const overrideIt = configuredIt({ tool_output: { max_lines: 10, max_bytes: 100 } }) + overrideIt.live("per-call options still override config", () => + Effect.gen(function* () { + const content = Array.from({ length: 50 }, (_, i) => `line${i}`).join("\n") + const result = yield* (yield* Truncate.Service).output(content, { + maxLines: 1000, + maxBytes: 1024 * 1024, + }) + expect(result.truncated).toBe(false) + }), + ) + }) + it.live("large single-line file truncates with byte message", () => Effect.gen(function* () { const svc = yield* Truncate.Service From 5c5069b6227ce6c4cbc1b6daca65da69daf43f84 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 21:44:44 +0000 Subject: [PATCH 26/59] chore: generate --- packages/sdk/js/src/v2/gen/types.gen.ts | 13 +++++++++++++ packages/sdk/openapi.json | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index d28ce25794..40e661b46a 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1633,6 +1633,19 @@ export type Config = { */ url?: string } + /** + * Thresholds for truncating tool output. When output exceeds either limit, the full text is written to the truncation directory and a preview is returned. + */ + tool_output?: { + /** + * Maximum lines of tool output before it is truncated and saved to disk (default: 2000) + */ + max_lines?: number + /** + * Maximum bytes of tool output before it is truncated and saved to disk (default: 51200) + */ + max_bytes?: number + } compaction?: { /** * Enable automatic compaction when context is full (default: true) diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 0c3d189722..cd7b381d83 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -11822,6 +11822,24 @@ } } }, + "tool_output": { + "description": "Thresholds for truncating tool output. When output exceeds either limit, the full text is written to the truncation directory and a preview is returned.", + "type": "object", + "properties": { + "max_lines": { + "description": "Maximum lines of tool output before it is truncated and saved to disk (default: 2000)", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "max_bytes": { + "description": "Maximum bytes of tool output before it is truncated and saved to disk (default: 51200)", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + } + } + }, "compaction": { "type": "object", "properties": { From 3bfe6a1ef6cf41bc7f05339d63ab8d6032c6e8e1 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Fri, 24 Apr 2026 12:50:34 +0800 Subject: [PATCH 27/59] ci: add platform-specific bun install flags (#23822) --- .github/actions/setup-bun/action.yml | 9 +++++++-- .github/workflows/publish.yml | 8 ++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml index d1e3bfc25d..9859174a2e 100644 --- a/.github/actions/setup-bun/action.yml +++ b/.github/actions/setup-bun/action.yml @@ -1,5 +1,10 @@ name: "Setup Bun" description: "Setup Bun with caching and install dependencies" +inputs: + install-flags: + description: "Additional flags to pass to 'bun install'" + required: false + default: "" runs: using: "composite" steps: @@ -46,8 +51,8 @@ runs: # e.g. ./patches/ for standard-openapi # https://github.com/oven-sh/bun/issues/28147 if [ "$RUNNER_OS" = "Windows" ]; then - bun install --linker hoisted + bun install --linker hoisted ${{ inputs.install-flags }} else - bun install + bun install ${{ inputs.install-flags }} fi shell: bash diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index af008f6b17..6cb6af0a8d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -402,12 +402,14 @@ jobs: fail-fast: false matrix: settings: - - host: macos-latest + - host: macos-26-intel target: x86_64-apple-darwin platform_flag: --mac --x64 - - host: macos-latest + bun_install_flags: --os=darwin --cpu=x64 + - host: macos-26 target: aarch64-apple-darwin platform_flag: --mac --arm64 + bun_install_flags: --os=darwin --cpu=arm64 # github-hosted: blacksmith lacks ARM64 MSVC cross-compilation toolchain - host: "windows-2025" target: aarch64-pc-windows-msvc @@ -437,6 +439,8 @@ jobs: run: echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8 - uses: ./.github/actions/setup-bun + with: + install-flags: ${{ matrix.settings.bun_install_flags }} - name: Azure login if: runner.os == 'Windows' From 2e156b8990a1e72cfb231eadafe76e4e60c096ea Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:27:36 +0800 Subject: [PATCH 28/59] fix(desktop): avoid relaunching without installing updates (#23806) --- .../app/src/components/settings-general.tsx | 5 ++--- packages/app/src/context/platform.tsx | 6 +++--- packages/app/src/pages/error.tsx | 5 ++--- packages/app/src/pages/layout.tsx | 5 ++--- packages/desktop-electron/src/main/index.ts | 21 +++++++++++++++---- .../desktop-electron/src/renderer/index.tsx | 2 +- packages/desktop/src/index.tsx | 9 ++++++-- 7 files changed, 34 insertions(+), 19 deletions(-) diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 13651aac06..86b8c317c9 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -129,13 +129,12 @@ export const SettingsGeneral: Component = () => { } const actions = - platform.update && platform.restart + platform.updateAndRestart ? [ { label: language.t("toast.update.action.installRestart"), onClick: async () => { - await platform.update!() - await platform.restart!() + await platform.updateAndRestart!() }, }, { diff --git a/packages/app/src/context/platform.tsx b/packages/app/src/context/platform.tsx index 3bdc46391b..fd89bf51ba 100644 --- a/packages/app/src/context/platform.tsx +++ b/packages/app/src/context/platform.tsx @@ -49,11 +49,11 @@ export type Platform = { /** Storage mechanism, defaults to localStorage */ storage?: (name?: string) => SyncStorage | AsyncStorage - /** Check for updates (Tauri only) */ + /** Check for a downloadable desktop update */ checkUpdate?(): Promise - /** Install updates (Tauri only) */ - update?(): Promise + /** Install the downloaded update using the platform restart flow */ + updateAndRestart?(): Promise /** Fetch override */ fetch?: typeof fetch diff --git a/packages/app/src/pages/error.tsx b/packages/app/src/pages/error.tsx index 11284b3d2d..ba0045ec93 100644 --- a/packages/app/src/pages/error.tsx +++ b/packages/app/src/pages/error.tsx @@ -244,10 +244,9 @@ export const ErrorPage: Component = (props) => { } async function installUpdate() { - if (!platform.update || !platform.restart) return + if (!platform.updateAndRestart) return await platform - .update() - .then(() => platform.restart!()) + .updateAndRestart() .then(() => setStore("actionError", undefined)) .catch((err) => { setStore("actionError", formatError(err, language.t)) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 3d3bd5e97b..ac5cf104aa 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -366,7 +366,7 @@ export default function Layout(props: ParentProps) { const useUpdatePolling = () => onMount(() => { - if (!platform.checkUpdate || !platform.update || !platform.restart) return + if (!platform.checkUpdate || !platform.updateAndRestart) return let toastId: number | undefined let interval: ReturnType | undefined @@ -384,8 +384,7 @@ export default function Layout(props: ParentProps) { { label: language.t("toast.update.action.installRestart"), onClick: async () => { - await platform.update!() - await platform.restart!() + await platform.updateAndRestart!() }, }, { diff --git a/packages/desktop-electron/src/main/index.ts b/packages/desktop-electron/src/main/index.ts index ae9f581186..c9f16606ae 100644 --- a/packages/desktop-electron/src/main/index.ts +++ b/packages/desktop-electron/src/main/index.ts @@ -337,11 +337,16 @@ function setupAutoUpdater() { }) } -let updateReady = false +let downloadedUpdateVersion: string | undefined async function checkUpdate() { if (!UPDATER_ENABLED) return { updateAvailable: false } - updateReady = false + if (downloadedUpdateVersion) { + logger.log("returning cached downloaded update", { + version: downloadedUpdateVersion, + }) + return { updateAvailable: true, version: downloadedUpdateVersion } + } logger.log("checking for updates", { currentVersion: app.getVersion(), channel: autoUpdater.channel, @@ -367,7 +372,7 @@ async function checkUpdate() { logger.log("update available", { version }) await autoUpdater.downloadUpdate() logger.log("update download completed", { version }) - updateReady = true + downloadedUpdateVersion = version return { updateAvailable: true, version } } catch (error) { logger.error("update check failed", error) @@ -376,7 +381,15 @@ async function checkUpdate() { } async function installUpdate() { - if (!updateReady) return + if (!downloadedUpdateVersion) { + logger.log("install update skipped", { + reason: "no downloaded update ready", + }) + return + } + logger.log("installing downloaded update", { + version: downloadedUpdateVersion, + }) killSidecar() autoUpdater.quitAndInstall() } diff --git a/packages/desktop-electron/src/renderer/index.tsx b/packages/desktop-electron/src/renderer/index.tsx index 56fe9fa513..91ea1ae077 100644 --- a/packages/desktop-electron/src/renderer/index.tsx +++ b/packages/desktop-electron/src/renderer/index.tsx @@ -170,7 +170,7 @@ const createPlatform = (): Platform => { return window.api.checkUpdate() }, - update: async () => { + updateAndRestart: async () => { const config = await window.api.getWindowConfig().catch(() => ({ updaterEnabled: false })) if (!config.updaterEnabled) return await window.api.installUpdate() diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx index d6a0ad74f8..a760cb4091 100644 --- a/packages/desktop/src/index.tsx +++ b/packages/desktop/src/index.tsx @@ -297,10 +297,15 @@ const createPlatform = (): Platform => { return { updateAvailable: true, version: next.version } }, - update: async () => { + updateAndRestart: async () => { if (!UPDATER_ENABLED || !update) return if (ostype() === "windows") await commands.killSidecar().catch(() => undefined) - await update.install().catch(() => undefined) + const installed = await update + .install() + .then(() => true) + .catch(() => false) + if (!installed) return + await relaunch() }, restart: async () => { From 6c1268f3b18ed289bc524ed10add8c3caa6131d2 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Fri, 24 Apr 2026 05:28:43 +0000 Subject: [PATCH 29/59] chore: generate --- .../app/src/components/settings-general.tsx | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 86b8c317c9..f38442379d 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -128,26 +128,25 @@ export const SettingsGeneral: Component = () => { return } - const actions = - platform.updateAndRestart - ? [ - { - label: language.t("toast.update.action.installRestart"), - onClick: async () => { - await platform.updateAndRestart!() - }, + const actions = platform.updateAndRestart + ? [ + { + label: language.t("toast.update.action.installRestart"), + onClick: async () => { + await platform.updateAndRestart!() }, - { - label: language.t("toast.update.action.notYet"), - onClick: "dismiss" as const, - }, - ] - : [ - { - label: language.t("toast.update.action.notYet"), - onClick: "dismiss" as const, - }, - ] + }, + { + label: language.t("toast.update.action.notYet"), + onClick: "dismiss" as const, + }, + ] + : [ + { + label: language.t("toast.update.action.notYet"), + onClick: "dismiss" as const, + }, + ] showToast({ persistent: true, From 4712f0f3c185fc3a348e2609689130fd2d901954 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Fri, 24 Apr 2026 14:04:55 +0800 Subject: [PATCH 30/59] feat(prompt): add shell mode UI with cancel button, custom icon, and example placeholder (#24105) --- packages/app/src/components/prompt-input.tsx | 27 ++++++++++++------- .../components/prompt-input/placeholder.ts | 2 +- packages/app/src/i18n/ar.ts | 2 +- packages/app/src/i18n/br.ts | 2 +- packages/app/src/i18n/bs.ts | 2 +- packages/app/src/i18n/da.ts | 2 +- packages/app/src/i18n/de.ts | 2 +- packages/app/src/i18n/en.ts | 2 +- packages/app/src/i18n/es.ts | 2 +- packages/app/src/i18n/fr.ts | 2 +- packages/app/src/i18n/ja.ts | 2 +- packages/app/src/i18n/ko.ts | 2 +- packages/app/src/i18n/no.ts | 2 +- packages/app/src/i18n/pl.ts | 2 +- packages/app/src/i18n/ru.ts | 2 +- packages/app/src/i18n/th.ts | 2 +- packages/app/src/i18n/tr.ts | 2 +- packages/app/src/i18n/zh.ts | 2 +- packages/app/src/i18n/zht.ts | 2 +- .../.storybook/mocks/app/context/language.ts | 2 +- packages/ui/src/components/icon.tsx | 4 ++- 21 files changed, 40 insertions(+), 29 deletions(-) diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 06c91c2922..8b69f3b2ab 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -270,7 +270,7 @@ export const PromptInput: Component = (props) => { const buttonsSpring = useSpring(() => (store.mode === "normal" ? 1 : 0), { visualDuration: 0.2, bounce: 0 }) const motion = (value: number) => ({ opacity: value, - transform: `scale(${0.95 + value * 0.05})`, + transform: `scale(${0.98 + value * 0.02})`, filter: `blur(${(1 - value) * 2}px)`, "pointer-events": value > 0.5 ? ("auto" as const) : ("none" as const), }) @@ -345,7 +345,7 @@ export const PromptInput: Component = (props) => { promptPlaceholder({ mode: store.mode, commentCount: commentCount(), - example: suggest() ? language.t(EXAMPLES[store.placeholder]) : "", + example: suggest() ? (store.mode === "shell" ? "git status" : language.t(EXAMPLES[store.placeholder])) : "", suggest: suggest(), t: (key, params) => language.t(key as Parameters[0], params as never), }), @@ -1403,12 +1403,11 @@ export const PromptInput: Component = (props) => { @@ -1451,14 +1450,24 @@ export const PromptInput: Component = (props) => {

- {language.t("prompt.mode.shell")} -
+ + {language.t("prompt.mode.shell")} +
+
diff --git a/packages/app/src/components/prompt-input/placeholder.ts b/packages/app/src/components/prompt-input/placeholder.ts index 395fee51b1..6669f13614 100644 --- a/packages/app/src/components/prompt-input/placeholder.ts +++ b/packages/app/src/components/prompt-input/placeholder.ts @@ -7,7 +7,7 @@ type PromptPlaceholderInput = { } export function promptPlaceholder(input: PromptPlaceholderInput) { - if (input.mode === "shell") return input.t("prompt.placeholder.shell") + if (input.mode === "shell") return input.t("prompt.placeholder.shell", { example: input.example }) if (input.commentCount > 1) return input.t("prompt.placeholder.summarizeComments") if (input.commentCount === 1) return input.t("prompt.placeholder.summarizeComment") if (!input.suggest) return input.t("prompt.placeholder.simple") diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index efb2919a5b..702210e4d3 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -210,7 +210,7 @@ export const dict = { "common.saving": "جارٍ الحفظ...", "common.default": "افتراضي", "common.attachment": "مرفق", - "prompt.placeholder.shell": "أدخل أمر shell...", + "prompt.placeholder.shell": "أدخل أمر shell... {{example}}", "prompt.placeholder.normal": 'اسأل أي شيء... "{{example}}"', "prompt.placeholder.simple": "اسأل أي شيء...", "prompt.placeholder.summarizeComments": "لخّص التعليقات…", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index 022d012984..b414fff36e 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -210,7 +210,7 @@ export const dict = { "common.saving": "Salvando...", "common.default": "Padrão", "common.attachment": "anexo", - "prompt.placeholder.shell": "Digite comando do shell...", + "prompt.placeholder.shell": "Digite comando do shell... {{example}}", "prompt.placeholder.normal": 'Pergunte qualquer coisa... "{{example}}"', "prompt.placeholder.simple": "Pergunte qualquer coisa...", "prompt.placeholder.summarizeComments": "Resumir comentários…", diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts index 15d8376ab6..e316f87da2 100644 --- a/packages/app/src/i18n/bs.ts +++ b/packages/app/src/i18n/bs.ts @@ -228,7 +228,7 @@ export const dict = { "common.default": "Podrazumijevano", "common.attachment": "prilog", - "prompt.placeholder.shell": "Unesi shell naredbu...", + "prompt.placeholder.shell": "Unesi shell naredbu... {{example}}", "prompt.placeholder.normal": 'Pitaj bilo šta... "{{example}}"', "prompt.placeholder.simple": "Pitaj bilo šta...", "prompt.placeholder.summarizeComments": "Sažmi komentare…", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index 03cfe2b786..d368f292d2 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -226,7 +226,7 @@ export const dict = { "common.default": "Standard", "common.attachment": "vedhæftning", - "prompt.placeholder.shell": "Indtast shell-kommando...", + "prompt.placeholder.shell": "Indtast shell-kommando... {{example}}", "prompt.placeholder.normal": 'Spørg om hvad som helst... "{{example}}"', "prompt.placeholder.simple": "Spørg om hvad som helst...", "prompt.placeholder.summarizeComments": "Opsummér kommentarer…", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index ccb88e9f41..a2b049c880 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -215,7 +215,7 @@ export const dict = { "common.saving": "Speichert...", "common.default": "Standard", "common.attachment": "Anhang", - "prompt.placeholder.shell": "Shell-Befehl eingeben...", + "prompt.placeholder.shell": "Shell-Befehl eingeben... {{example}}", "prompt.placeholder.normal": 'Fragen Sie alles... "{{example}}"', "prompt.placeholder.simple": "Fragen Sie alles...", "prompt.placeholder.summarizeComments": "Kommentare zusammenfassen…", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index ed80b38ce4..7326f7c8bb 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -230,7 +230,7 @@ export const dict = { "common.default": "Default", "common.attachment": "attachment", - "prompt.placeholder.shell": "Enter shell command...", + "prompt.placeholder.shell": "Enter shell command... {{example}}", "prompt.placeholder.normal": 'Ask anything... "{{example}}"', "prompt.placeholder.simple": "Ask anything...", "prompt.placeholder.summarizeComments": "Summarize comments…", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 0b4789c2aa..8dc644bb8e 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -227,7 +227,7 @@ export const dict = { "common.default": "Predeterminado", "common.attachment": "adjunto", - "prompt.placeholder.shell": "Introduce comando de shell...", + "prompt.placeholder.shell": "Introduce comando de shell... {{example}}", "prompt.placeholder.normal": 'Pregunta cualquier cosa... "{{example}}"', "prompt.placeholder.simple": "Pregunta cualquier cosa...", "prompt.placeholder.summarizeComments": "Resumir comentarios…", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index 4d73f626b2..1b4916c7d9 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -210,7 +210,7 @@ export const dict = { "common.saving": "Enregistrement...", "common.default": "Défaut", "common.attachment": "pièce jointe", - "prompt.placeholder.shell": "Entrez une commande shell...", + "prompt.placeholder.shell": "Entrez une commande shell... {{example}}", "prompt.placeholder.normal": 'Demandez n\'importe quoi... "{{example}}"', "prompt.placeholder.simple": "Demandez n'importe quoi...", "prompt.placeholder.summarizeComments": "Résumer les commentaires…", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 493b1f17ff..979f94203d 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -209,7 +209,7 @@ export const dict = { "common.saving": "保存中...", "common.default": "デフォルト", "common.attachment": "添付ファイル", - "prompt.placeholder.shell": "シェルコマンドを入力...", + "prompt.placeholder.shell": "シェルコマンドを入力... {{example}}", "prompt.placeholder.normal": '何でも聞いてください... "{{example}}"', "prompt.placeholder.simple": "何でも聞いてください...", "prompt.placeholder.summarizeComments": "コメントを要約…", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 0218cc1a9e..56ce374a96 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -209,7 +209,7 @@ export const dict = { "common.saving": "저장 중...", "common.default": "기본값", "common.attachment": "첨부 파일", - "prompt.placeholder.shell": "셸 명령어 입력...", + "prompt.placeholder.shell": "셸 명령어 입력... {{example}}", "prompt.placeholder.normal": '무엇이든 물어보세요... "{{example}}"', "prompt.placeholder.simple": "무엇이든 물어보세요...", "prompt.placeholder.summarizeComments": "댓글 요약…", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index 43aa844200..d14dd6f98b 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -230,7 +230,7 @@ export const dict = { "common.default": "Standard", "common.attachment": "vedlegg", - "prompt.placeholder.shell": "Skriv inn shell-kommando...", + "prompt.placeholder.shell": "Skriv inn shell-kommando... {{example}}", "prompt.placeholder.normal": 'Spør om hva som helst... "{{example}}"', "prompt.placeholder.simple": "Spør om hva som helst...", "prompt.placeholder.summarizeComments": "Oppsummer kommentarer…", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index 6c6d4dddc1..9859ea0ae6 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -211,7 +211,7 @@ export const dict = { "common.saving": "Zapisywanie...", "common.default": "Domyślny", "common.attachment": "załącznik", - "prompt.placeholder.shell": "Wpisz polecenie terminala...", + "prompt.placeholder.shell": "Wpisz polecenie terminala... {{example}}", "prompt.placeholder.normal": 'Zapytaj o cokolwiek... "{{example}}"', "prompt.placeholder.simple": "Zapytaj o cokolwiek...", "prompt.placeholder.summarizeComments": "Podsumuj komentarze…", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index e0b094877a..6e6ca32030 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -227,7 +227,7 @@ export const dict = { "common.default": "По умолчанию", "common.attachment": "вложение", - "prompt.placeholder.shell": "Введите команду оболочки...", + "prompt.placeholder.shell": "Введите команду оболочки... {{example}}", "prompt.placeholder.normal": 'Спросите что угодно... "{{example}}"', "prompt.placeholder.simple": "Спросите что угодно...", "prompt.placeholder.summarizeComments": "Суммировать комментарии…", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 8a15f29c0b..84e5d3ff21 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -227,7 +227,7 @@ export const dict = { "common.default": "ค่าเริ่มต้น", "common.attachment": "ไฟล์แนบ", - "prompt.placeholder.shell": "ป้อนคำสั่งเชลล์...", + "prompt.placeholder.shell": "ป้อนคำสั่งเชลล์... {{example}}", "prompt.placeholder.normal": 'ถามอะไรก็ได้... "{{example}}"', "prompt.placeholder.simple": "ถามอะไรก็ได้...", "prompt.placeholder.summarizeComments": "สรุปความคิดเห็น…", diff --git a/packages/app/src/i18n/tr.ts b/packages/app/src/i18n/tr.ts index f20c05000d..06e233cb51 100644 --- a/packages/app/src/i18n/tr.ts +++ b/packages/app/src/i18n/tr.ts @@ -232,7 +232,7 @@ export const dict = { "common.default": "Varsayılan", "common.attachment": "ek", - "prompt.placeholder.shell": "Kabuk komutu girin...", + "prompt.placeholder.shell": "Kabuk komutu girin... {{example}}", "prompt.placeholder.normal": 'Bir şeyler sorun... "{{example}}"', "prompt.placeholder.simple": "Bir şeyler sorun...", "prompt.placeholder.summarizeComments": "Yorumları özetle…", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 05310df965..fa83707e8d 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -249,7 +249,7 @@ export const dict = { "common.default": "默认", "common.attachment": "附件", - "prompt.placeholder.shell": "输入 shell 命令...", + "prompt.placeholder.shell": "输入 shell 命令... {{example}}", "prompt.placeholder.normal": '随便问点什么... "{{example}}"', "prompt.placeholder.simple": "随便问点什么...", "prompt.placeholder.summarizeComments": "总结评论…", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 43681c7793..e9d265acc0 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -227,7 +227,7 @@ export const dict = { "common.default": "預設", "common.attachment": "附件", - "prompt.placeholder.shell": "輸入 shell 命令...", + "prompt.placeholder.shell": "輸入 shell 命令... {{example}}", "prompt.placeholder.normal": '隨便問點什麼... "{{example}}"', "prompt.placeholder.simple": "隨便問點什麼...", "prompt.placeholder.summarizeComments": "摘要評論…", diff --git a/packages/storybook/.storybook/mocks/app/context/language.ts b/packages/storybook/.storybook/mocks/app/context/language.ts index c3317ca2e9..df28d79fbd 100644 --- a/packages/storybook/.storybook/mocks/app/context/language.ts +++ b/packages/storybook/.storybook/mocks/app/context/language.ts @@ -5,7 +5,7 @@ const dict: Record = { "prompt.loading": "Loading prompt...", "prompt.placeholder.normal": "Ask anything...", "prompt.placeholder.simple": "Ask anything...", - "prompt.placeholder.shell": "Run a shell command...", + "prompt.placeholder.shell": "Run a shell command... {{example}}", "prompt.placeholder.summarizeComment": "Summarize this comment", "prompt.placeholder.summarizeComments": "Summarize these comments", "prompt.action.attachFile": "Attach files", diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index 08726d0ff2..2e4d1f53b7 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -102,6 +102,7 @@ const icons = { link: ``, providers: ``, models: ``, + "arrow-undo-down": ``, } export interface IconProps extends ComponentProps<"svg"> { @@ -111,7 +112,8 @@ export interface IconProps extends ComponentProps<"svg"> { export function Icon(props: IconProps) { const [local, others] = splitProps(props, ["name", "size", "class", "classList"]) - const viewBox = () => (local.name === "magnifying-glass" ? "0 0 16 16" : "0 0 20 20") + const viewBox = () => + local.name === "magnifying-glass" || local.name === "arrow-undo-down" ? "0 0 16 16" : "0 0 20 20" return (
Date: Fri, 24 Apr 2026 02:01:37 -0400 Subject: [PATCH 31/59] sync --- .../console/app/src/routes/zen/util/provider/anthropic.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/console/app/src/routes/zen/util/provider/anthropic.ts b/packages/console/app/src/routes/zen/util/provider/anthropic.ts index de49cddc1b..d93bc58d82 100644 --- a/packages/console/app/src/routes/zen/util/provider/anthropic.ts +++ b/packages/console/app/src/routes/zen/util/provider/anthropic.ts @@ -53,9 +53,7 @@ export const anthropicHelper: ProviderHelper = ({ reqModel, providerModel }) => anthropic_version: "bedrock-2023-05-31", anthropic_beta: supports1m ? ["context-1m-2025-08-07"] : undefined, } - : { - service_tier: "standard_only", - }), + : {}), }), createBinaryStreamDecoder: () => { if (!isBedrock) return undefined From a4bd88ab9702e1275438540812c1fb7de3ce87b5 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 24 Apr 2026 02:09:14 -0400 Subject: [PATCH 32/59] zen: deepseek v4 pro --- packages/web/src/content/docs/ar/providers.mdx | 2 +- packages/web/src/content/docs/bs/providers.mdx | 2 +- packages/web/src/content/docs/da/providers.mdx | 2 +- packages/web/src/content/docs/de/providers.mdx | 2 +- packages/web/src/content/docs/es/providers.mdx | 2 +- packages/web/src/content/docs/fr/providers.mdx | 2 +- packages/web/src/content/docs/it/providers.mdx | 2 +- packages/web/src/content/docs/ja/providers.mdx | 2 +- packages/web/src/content/docs/ko/providers.mdx | 2 +- packages/web/src/content/docs/nb/providers.mdx | 2 +- packages/web/src/content/docs/pl/providers.mdx | 2 +- packages/web/src/content/docs/providers.mdx | 2 +- packages/web/src/content/docs/pt-br/providers.mdx | 2 +- packages/web/src/content/docs/ru/providers.mdx | 2 +- packages/web/src/content/docs/th/providers.mdx | 2 +- packages/web/src/content/docs/tr/providers.mdx | 2 +- packages/web/src/content/docs/zh-cn/providers.mdx | 2 +- packages/web/src/content/docs/zh-tw/providers.mdx | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/web/src/content/docs/ar/providers.mdx b/packages/web/src/content/docs/ar/providers.mdx index 951d6701cb..a43357d107 100644 --- a/packages/web/src/content/docs/ar/providers.mdx +++ b/packages/web/src/content/docs/ar/providers.mdx @@ -575,7 +575,7 @@ OpenCode Go هي خطة اشتراك منخفضة التكلفة توفّر وص └ enter ``` -4. شغّل الأمر `/models` لاختيار نموذج من DeepSeek مثل _DeepSeek Reasoner_. +4. شغّل الأمر `/models` لاختيار نموذج من DeepSeek مثل _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/bs/providers.mdx b/packages/web/src/content/docs/bs/providers.mdx index 1aae0a93a9..306653f75a 100644 --- a/packages/web/src/content/docs/bs/providers.mdx +++ b/packages/web/src/content/docs/bs/providers.mdx @@ -580,7 +580,7 @@ Također možete dodati modele kroz svoju opencode konfiguraciju. └ enter ``` -4. Pokrenite naredbu `/models` da odaberete DeepSeek model kao što je _DeepSeek Reasoner_. +4. Pokrenite naredbu `/models` da odaberete DeepSeek model kao što je _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/da/providers.mdx b/packages/web/src/content/docs/da/providers.mdx index bcd82c8454..0bc9baa5fa 100644 --- a/packages/web/src/content/docs/da/providers.mdx +++ b/packages/web/src/content/docs/da/providers.mdx @@ -571,7 +571,7 @@ Cloudflare AI Gateway lader dig få adgang til modeller fra OpenAI, Anthropic, W └ enter ``` -4. Kør kommandoen `/models` for at vælge en DeepSeek-model som _DeepSeek Reasoner_. +4. Kør kommandoen `/models` for at vælge en DeepSeek-model som _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/de/providers.mdx b/packages/web/src/content/docs/de/providers.mdx index c012a78fc2..41a15fa0b9 100644 --- a/packages/web/src/content/docs/de/providers.mdx +++ b/packages/web/src/content/docs/de/providers.mdx @@ -577,7 +577,7 @@ Mit dem Cloudflare AI Gateway können Sie über einen einheitlichen Endpunkt auf └ enter ``` -4. Führen Sie den Befehl `/models` aus, um ein DeepSeek-Modell wie _DeepSeek Reasoner_ auszuwählen. +4. Führen Sie den Befehl `/models` aus, um ein DeepSeek-Modell wie _DeepSeek V4 Pro_ auszuwählen. ```txt /models diff --git a/packages/web/src/content/docs/es/providers.mdx b/packages/web/src/content/docs/es/providers.mdx index b812971409..f325a0cd4c 100644 --- a/packages/web/src/content/docs/es/providers.mdx +++ b/packages/web/src/content/docs/es/providers.mdx @@ -578,7 +578,7 @@ Cloudflare AI Gateway le permite acceder a modelos de OpenAI, Anthropic, Workers └ enter ``` -4. Ejecute el comando `/models` para seleccionar un modelo de DeepSeek como _DeepSeek Reasoner_. +4. Ejecute el comando `/models` para seleccionar un modelo de DeepSeek como _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/fr/providers.mdx b/packages/web/src/content/docs/fr/providers.mdx index d7b0657971..2896df9592 100644 --- a/packages/web/src/content/docs/fr/providers.mdx +++ b/packages/web/src/content/docs/fr/providers.mdx @@ -581,7 +581,7 @@ Vous pouvez également ajouter des modèles via votre configuration opencode. └ enter ``` -4. Exécutez la commande `/models` pour sélectionner un modèle DeepSeek tel que _DeepSeek Reasoner_. +4. Exécutez la commande `/models` pour sélectionner un modèle DeepSeek tel que _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/it/providers.mdx b/packages/web/src/content/docs/it/providers.mdx index 58bb28407f..26bf19fd6f 100644 --- a/packages/web/src/content/docs/it/providers.mdx +++ b/packages/web/src/content/docs/it/providers.mdx @@ -555,7 +555,7 @@ Cloudflare AI Gateway ti permette di accedere a modelli di OpenAI, Anthropic, Wo └ enter ``` -4. Esegui il comando `/models` per selezionare un modello DeepSeek come _DeepSeek Reasoner_. +4. Esegui il comando `/models` per selezionare un modello DeepSeek come _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/ja/providers.mdx b/packages/web/src/content/docs/ja/providers.mdx index f7e88d8c5d..9fbb301ba5 100644 --- a/packages/web/src/content/docs/ja/providers.mdx +++ b/packages/web/src/content/docs/ja/providers.mdx @@ -585,7 +585,7 @@ OpenCode 設定を通じてモデルを追加することもできます。 └ enter ``` -4. `/models` コマンドを実行して、_DeepSeek Reasoner_ のような DeepSeek モデルを選択します。 +4. `/models` コマンドを実行して、_DeepSeek V4 Pro_ のような DeepSeek モデルを選択します。 ```txt /models diff --git a/packages/web/src/content/docs/ko/providers.mdx b/packages/web/src/content/docs/ko/providers.mdx index ccbbc48386..ad4aaae7e6 100644 --- a/packages/web/src/content/docs/ko/providers.mdx +++ b/packages/web/src/content/docs/ko/providers.mdx @@ -581,7 +581,7 @@ Cloudflare AI Gateway는 OpenAI, Anthropic, Workers AI 등의 모델에 액세 └ enter ``` -4. `/models` 명령을 실행하여 DeepSeek 모델(예: _DeepSeek Reasoner_)을 선택하십시오. +4. `/models` 명령을 실행하여 DeepSeek 모델(예: _DeepSeek V4 Pro_)을 선택하십시오. ```txt /models diff --git a/packages/web/src/content/docs/nb/providers.mdx b/packages/web/src/content/docs/nb/providers.mdx index 9e025a96f8..ea6f1cc1ab 100644 --- a/packages/web/src/content/docs/nb/providers.mdx +++ b/packages/web/src/content/docs/nb/providers.mdx @@ -579,7 +579,7 @@ Cloudflare AI Gateway lar deg få tilgang til modeller fra OpenAI, Anthropic, Wo └ enter ``` -4. Kjør kommandoen `/models` for å velge en DeepSeek-modell som _DeepSeek Reasoner_. +4. Kjør kommandoen `/models` for å velge en DeepSeek-modell som _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/pl/providers.mdx b/packages/web/src/content/docs/pl/providers.mdx index aeb6272331..e2b4c70e6f 100644 --- a/packages/web/src/content/docs/pl/providers.mdx +++ b/packages/web/src/content/docs/pl/providers.mdx @@ -577,7 +577,7 @@ Cloudflare AI Gateway umożliwia dostęp do modeli z OpenAI, Anthropic, Workers └ enter ``` -4. Uruchom polecenie `/models`, aby wybrać model DeepSeek, np. _DeepSeek Reasoner_. +4. Uruchom polecenie `/models`, aby wybrać model DeepSeek, np. _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/providers.mdx b/packages/web/src/content/docs/providers.mdx index 030a50ba00..752f6c054f 100644 --- a/packages/web/src/content/docs/providers.mdx +++ b/packages/web/src/content/docs/providers.mdx @@ -648,7 +648,7 @@ Cloudflare Workers AI lets you run AI models on Cloudflare's global network dire └ enter ``` -4. Run the `/models` command to select a DeepSeek model like _DeepSeek Reasoner_. +4. Run the `/models` command to select a DeepSeek model like _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/pt-br/providers.mdx b/packages/web/src/content/docs/pt-br/providers.mdx index 4424a55fc0..58305a2737 100644 --- a/packages/web/src/content/docs/pt-br/providers.mdx +++ b/packages/web/src/content/docs/pt-br/providers.mdx @@ -581,7 +581,7 @@ O Cloudflare AI Gateway permite que você acesse modelos do OpenAI, Anthropic, W └ enter ``` -4. Execute o comando `/models` para selecionar um modelo DeepSeek como _DeepSeek Reasoner_. +4. Execute o comando `/models` para selecionar um modelo DeepSeek como _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/ru/providers.mdx b/packages/web/src/content/docs/ru/providers.mdx index 1cb3873c32..144ce2ac5a 100644 --- a/packages/web/src/content/docs/ru/providers.mdx +++ b/packages/web/src/content/docs/ru/providers.mdx @@ -577,7 +577,7 @@ Cloudflare AI Gateway позволяет вам получать доступ к └ enter ``` -4. Запустите команду `/models`, чтобы выбрать модель DeepSeek, например _DeepSeek Reasoner_. +4. Запустите команду `/models`, чтобы выбрать модель DeepSeek, например _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/th/providers.mdx b/packages/web/src/content/docs/th/providers.mdx index 73489b96ad..c4fdb5ce34 100644 --- a/packages/web/src/content/docs/th/providers.mdx +++ b/packages/web/src/content/docs/th/providers.mdx @@ -577,7 +577,7 @@ Cloudflare AI Gateway ช่วยให้คุณเข้าถึงโม └ enter ``` -4. รันคำสั่ง `/models` เพื่อเลือกโมเดล DeepSeek เช่น _DeepSeek Reasoner_ +4. รันคำสั่ง `/models` เพื่อเลือกโมเดล DeepSeek เช่น _DeepSeek V4 Pro_ ```txt /models diff --git a/packages/web/src/content/docs/tr/providers.mdx b/packages/web/src/content/docs/tr/providers.mdx index 871f9e128c..cf073e3095 100644 --- a/packages/web/src/content/docs/tr/providers.mdx +++ b/packages/web/src/content/docs/tr/providers.mdx @@ -579,7 +579,7 @@ Cloudflare AI Gateway, OpenAI, Anthropic, Workers AI ve daha fazlasındaki model └ enter ``` -4. _DeepSeek Reasoner_ gibi bir DeepSeek modeli seçmek için `/models` komutunu çalıştırın. +4. _DeepSeek V4 Pro_ gibi bir DeepSeek modeli seçmek için `/models` komutunu çalıştırın. ```txt /models diff --git a/packages/web/src/content/docs/zh-cn/providers.mdx b/packages/web/src/content/docs/zh-cn/providers.mdx index 25b7d03a4d..e22ba3ad80 100644 --- a/packages/web/src/content/docs/zh-cn/providers.mdx +++ b/packages/web/src/content/docs/zh-cn/providers.mdx @@ -551,7 +551,7 @@ Cloudflare AI Gateway 允许你通过统一端点访问来自 OpenAI、Anthropic └ enter ``` -4. 执行 `/models` 命令选择 DeepSeek 模型,例如 _DeepSeek Reasoner_。 +4. 执行 `/models` 命令选择 DeepSeek 模型,例如 _DeepSeek V4 Pro_。 ```txt /models diff --git a/packages/web/src/content/docs/zh-tw/providers.mdx b/packages/web/src/content/docs/zh-tw/providers.mdx index 1024444d18..8af6f01d4a 100644 --- a/packages/web/src/content/docs/zh-tw/providers.mdx +++ b/packages/web/src/content/docs/zh-tw/providers.mdx @@ -572,7 +572,7 @@ Cloudflare AI Gateway 允許您透過統一端點存取來自 OpenAI、Anthropic └ enter ``` -4. 執行 `/models` 指令選擇 DeepSeek 模型,例如 _DeepSeek Reasoner_。 +4. 執行 `/models` 指令選擇 DeepSeek 模型,例如 _DeepSeek V4 Pro_。 ```txt /models From f033d2d8fbb76d654e30697c99e4b7a0beb2be8a Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:20:53 +0800 Subject: [PATCH 33/59] fix(app): conditionally show model variant selector (#24115) --- packages/app/src/components/prompt-input.tsx | 54 ++++++++++---------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 8b69f3b2ab..0a18096164 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -1574,33 +1574,35 @@ export const PromptInput: Component = (props) => {
-
- 2}> +
- (x === "default" ? language.t("common.default") : x)} + onSelect={(value) => { + local.model.variant.set(value === "default" ? undefined : value) + restoreFocus() + }} + class="capitalize max-w-[160px] text-text-base" + valueClass="truncate text-13-regular text-text-base" + triggerStyle={control()} + triggerProps={{ "data-action": "prompt-model-variant" }} + variant="ghost" + /> + +
+
From 2cda629c8d28baae41cf8c861a95453b9271adf6 Mon Sep 17 00:00:00 2001 From: Simon Klee Date: Fri, 24 Apr 2026 13:00:11 +0200 Subject: [PATCH 34/59] test(prompt): align shell placeholder expectation (#24147) --- packages/app/src/components/prompt-input/placeholder.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/components/prompt-input/placeholder.test.ts b/packages/app/src/components/prompt-input/placeholder.test.ts index 5f6aa59e9a..d4caead0d2 100644 --- a/packages/app/src/components/prompt-input/placeholder.test.ts +++ b/packages/app/src/components/prompt-input/placeholder.test.ts @@ -12,7 +12,7 @@ describe("promptPlaceholder", () => { suggest: true, t, }) - expect(value).toBe("prompt.placeholder.shell") + expect(value).toBe("prompt.placeholder.shell:example") }) test("returns summarize placeholders for comment context", () => { From a882e958b3d9bf736d9e16d65989265999674150 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:34:48 -0400 Subject: [PATCH 35/59] fix: deepseek variants (#24157) --- packages/opencode/src/provider/transform.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 1d84c7c931..6f24370ab0 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -405,7 +405,10 @@ export function variants(model: Provider.Model): Record Date: Fri, 24 Apr 2026 20:42:57 +0800 Subject: [PATCH 36/59] fix: preserve empty reasoning_content for DeepSeek V4 thinking mode (#24146) Co-authored-by: Simon Klee --- packages/opencode/src/provider/transform.ts | 25 +++++++++------------ 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 6f24370ab0..0a2fc7fc26 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -185,24 +185,19 @@ function normalizeMessages( // Filter out reasoning parts from content const filteredContent = msg.content.filter((part: any) => part.type !== "reasoning") - // Include reasoning_content | reasoning_details directly on the message for all assistant messages - if (reasoningText) { - return { - ...msg, - content: filteredContent, - providerOptions: { - ...msg.providerOptions, - openaiCompatible: { - ...msg.providerOptions?.openaiCompatible, - [field]: reasoningText, - }, - }, - } - } - + // Include reasoning_content | reasoning_details directly on the message for all assistant messages. + // Always set the field even when empty — some providers (e.g. DeepSeek) may return empty + // reasoning_content which still needs to be sent back in subsequent requests. return { ...msg, content: filteredContent, + providerOptions: { + ...msg.providerOptions, + openaiCompatible: { + ...msg.providerOptions?.openaiCompatible, + [field]: reasoningText, + }, + }, } } From f8e939d96fe2f2f3e347fdfd8a8f567ceb6edb01 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:48:52 -0400 Subject: [PATCH 37/59] fix: support `max` for deepseek (#24163) --- packages/opencode/src/provider/transform.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 0a2fc7fc26..7fcfcd2501 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -529,7 +529,11 @@ export function variants(model: Provider.Model): Record [effort, { reasoningEffort: effort }])) + const efforts = [...WIDELY_SUPPORTED_EFFORTS] + if (model.api.id.includes("deepseek-v4")) { + efforts.push("max") + } + return Object.fromEntries(efforts.map((effort) => [effort, { reasoningEffort: effort }])) case "@ai-sdk/azure": // https://v5.ai-sdk.dev/providers/ai-sdk-providers/azure From 3d31ae28b041ecad9d8f59351b1977fe5434b4a5 Mon Sep 17 00:00:00 2001 From: opencode Date: Fri, 24 Apr 2026 13:17:01 +0000 Subject: [PATCH 38/59] release: v1.14.23 --- bun.lock | 32 +++++++++++++------------- packages/app/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/desktop-electron/package.json | 2 +- packages/desktop/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/extensions/zed/extension.toml | 12 +++++----- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/shared/package.json | 2 +- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index 06a5cfdb4c..16ada2be04 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -83,7 +83,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -117,7 +117,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -144,7 +144,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -168,7 +168,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -192,7 +192,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -225,7 +225,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -269,7 +269,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@opencode-ai/shared": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -298,7 +298,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -314,7 +314,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.14.22", + "version": "1.14.23", "bin": { "opencode": "./bin/opencode", }, @@ -459,7 +459,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@opencode-ai/sdk": "workspace:*", "effect": "catalog:", @@ -494,7 +494,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "cross-spawn": "catalog:", }, @@ -509,7 +509,7 @@ }, "packages/shared": { "name": "@opencode-ai/shared", - "version": "1.14.22", + "version": "1.14.23", "bin": { "opencode": "./bin/opencode", }, @@ -533,7 +533,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -568,7 +568,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -617,7 +617,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index 0ab5e304a2..b01b9b9d1a 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.14.22", + "version": "1.14.23", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 3d37527508..acd7024a6d 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.14.22", + "version": "1.14.23", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 70d44b3820..a8ba8c5398 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.14.22", + "version": "1.14.23", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 55791a3072..b917ee2b59 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.14.22", + "version": "1.14.23", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index 4966bf3e63..17fd13d0f3 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index a528a9a3ac..bd286786bb 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.14.22", + "version": "1.14.23", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 32b63fff13..561a974700 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.14.22", + "version": "1.14.23", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index e4428f0cb2..11a6ea7ef6 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.14.22", + "version": "1.14.23", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 6449b8df65..2943614d38 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.14.22" +version = "1.14.23" 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.22/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.23/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.22/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.23/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.23/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.22/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.23/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.22/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.23/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 5b06d03685..1501f3df40 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.14.22", + "version": "1.14.23", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index ea8724ceb8..5225ff54cc 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.22", + "version": "1.14.23", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index ee8210ec14..16c5e236b8 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.14.22", + "version": "1.14.23", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 62a50b7434..9f9658eb15 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.14.22", + "version": "1.14.23", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/shared/package.json b/packages/shared/package.json index 48ae0fb78d..08bb009297 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.22", + "version": "1.14.23", "name": "@opencode-ai/shared", "type": "module", "license": "MIT", diff --git a/packages/slack/package.json b/packages/slack/package.json index a3516932ab..5149dbffe9 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.14.22", + "version": "1.14.23", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index ba0fb01494..f4d90f1460 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.14.22", + "version": "1.14.23", "type": "module", "license": "MIT", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index 818cdf721e..386078d171 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.14.22", + "version": "1.14.23", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index e238b36702..035a45eb7a 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.14.22", + "version": "1.14.23", "publisher": "sst-dev", "repository": { "type": "git", From 108dfe3238c8dc186323c5000541f9e2dcb61cad Mon Sep 17 00:00:00 2001 From: Imanol Maiztegui Date: Mon, 27 Apr 2026 13:56:08 +0200 Subject: [PATCH 39/59] refactor: kilo compat for v1.14.23 --- README.ar.md | 4 +- README.bn.md | 4 +- README.br.md | 4 +- README.bs.md | 4 +- README.da.md | 4 +- README.de.md | 4 +- README.es.md | 4 +- README.fr.md | 4 +- README.gr.md | 4 +- README.it.md | 4 +- README.ja.md | 4 +- README.ko.md | 4 +- README.no.md | 4 +- README.pl.md | 4 +- README.ru.md | 4 +- README.th.md | 4 +- README.tr.md | 4 +- README.uk.md | 4 +- README.vi.md | 4 +- README.zh.md | 4 +- README.zht.md | 4 +- github/index.ts | 8 +- github/package.json | 5 +- infra/enterprise.ts | 10 +- package.json | 57 +++++--- packages/app/package.json | 11 +- packages/app/playwright.config.ts | 4 +- .../components/dialog-connect-provider.tsx | 2 +- .../src/components/dialog-custom-provider.tsx | 2 +- packages/app/src/components/dialog-fork.tsx | 2 +- packages/app/src/components/file-tree.tsx | 2 +- .../prompt-input/build-request-parts.ts | 2 +- .../components/prompt-input/submit.test.ts | 4 +- .../app/src/components/prompt-input/submit.ts | 2 +- .../session/session-context-breakdown.test.ts | 2 +- .../session/session-context-breakdown.ts | 2 +- .../session/session-context-metrics.test.ts | 2 +- .../session/session-context-metrics.ts | 2 +- .../session/session-context-tab.tsx | 2 +- .../src/components/session/session-header.tsx | 2 +- .../app/src/components/settings-general.tsx | 4 +- packages/app/src/components/titlebar.tsx | 6 +- .../app/src/context/file/content-cache.ts | 2 +- packages/app/src/context/file/tree-store.ts | 2 +- packages/app/src/context/file/types.ts | 2 +- packages/app/src/context/file/watcher.ts | 2 +- packages/app/src/context/global-sdk.tsx | 2 +- packages/app/src/context/global-sync.tsx | 6 +- .../app/src/context/global-sync/bootstrap.ts | 18 +-- .../src/context/global-sync/child-store.ts | 2 +- .../context/global-sync/event-reducer.test.ts | 2 +- .../src/context/global-sync/event-reducer.ts | 2 +- .../context/global-sync/session-cache.test.ts | 2 +- .../src/context/global-sync/session-cache.ts | 2 +- .../context/global-sync/session-trim.test.ts | 2 +- .../src/context/global-sync/session-trim.ts | 2 +- packages/app/src/context/global-sync/types.ts | 2 +- .../app/src/context/global-sync/utils.test.ts | 2 +- packages/app/src/context/global-sync/utils.ts | 2 +- packages/app/src/context/highlights.tsx | 2 +- packages/app/src/context/layout.tsx | 2 +- packages/app/src/context/notification.tsx | 2 +- .../context/permission-auto-respond.test.ts | 2 +- packages/app/src/context/permission.tsx | 2 +- packages/app/src/context/sdk.tsx | 2 +- .../app/src/context/sync-optimistic.test.ts | 2 +- packages/app/src/context/sync.tsx | 2 +- packages/app/src/entry.tsx | 2 +- packages/app/src/env.d.ts | 6 +- packages/app/src/i18n/ar.ts | 34 ++--- packages/app/src/i18n/br.ts | 34 ++--- packages/app/src/i18n/bs.ts | 34 ++--- packages/app/src/i18n/da.ts | 34 ++--- packages/app/src/i18n/de.ts | 34 ++--- packages/app/src/i18n/en.ts | 34 ++--- packages/app/src/i18n/es.ts | 34 ++--- packages/app/src/i18n/fr.ts | 34 ++--- packages/app/src/i18n/ja.ts | 34 ++--- packages/app/src/i18n/ko.ts | 34 ++--- packages/app/src/i18n/no.ts | 34 ++--- packages/app/src/i18n/pl.ts | 34 ++--- packages/app/src/i18n/ru.ts | 34 ++--- packages/app/src/i18n/th.ts | 34 ++--- packages/app/src/i18n/tr.ts | 34 ++--- packages/app/src/i18n/zh.ts | 34 ++--- packages/app/src/i18n/zht.ts | 34 ++--- packages/app/src/pages/error.tsx | 2 +- packages/app/src/pages/layout.tsx | 4 +- packages/app/src/pages/layout/helpers.test.ts | 2 +- packages/app/src/pages/layout/helpers.ts | 2 +- .../app/src/pages/layout/sidebar-items.tsx | 8 +- .../src/pages/layout/sidebar-workspace.tsx | 2 +- packages/app/src/pages/session.tsx | 2 +- .../composer/session-composer-state.test.ts | 2 +- .../composer/session-composer-state.ts | 2 +- .../composer/session-permission-dock.tsx | 2 +- .../composer/session-question-dock.tsx | 2 +- .../session/composer/session-request-tree.ts | 2 +- .../session/composer/session-todo-dock.tsx | 2 +- .../src/pages/session/message-timeline.tsx | 2 +- packages/app/src/pages/session/review-tab.tsx | 2 +- .../session/session-model-helpers.test.ts | 2 +- .../pages/session/session-model-helpers.ts | 2 +- .../src/pages/session/session-side-panel.tsx | 4 +- .../pages/session/use-session-commands.tsx | 4 +- .../pages/session/use-session-hash-scroll.ts | 2 +- packages/app/src/utils/diffs.test.ts | 4 +- packages/app/src/utils/diffs.ts | 4 +- packages/app/src/utils/prompt.test.ts | 2 +- packages/app/src/utils/prompt.ts | 2 +- packages/app/src/utils/server.ts | 6 +- packages/console/app/package.json | 2 +- packages/console/app/src/lib/language.ts | 2 +- .../app/src/routes/zen/util/handler.ts | 16 +-- .../zen/util/provider/openai-compatible.ts | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/console/resource/package.json | 3 +- .../electron-builder.config.ts | 2 +- .../desktop-electron/electron.vite.config.ts | 14 +- packages/desktop-electron/package.json | 15 +- .../scripts/finalize-latest-yml.ts | 4 +- packages/desktop-electron/scripts/predev.ts | 2 +- packages/desktop-electron/scripts/utils.ts | 2 +- .../desktop-electron/src/main/constants.ts | 2 +- packages/desktop-electron/src/main/env.d.ts | 2 +- packages/desktop-electron/src/main/index.ts | 6 +- packages/desktop-electron/src/main/server.ts | 10 +- .../src/main/shell-env.test.ts | 4 +- .../desktop-electron/src/renderer/index.tsx | 6 +- packages/desktop/package.json | 2 +- .../desktop/scripts/finalize-latest-json.ts | 8 +- packages/desktop/scripts/prepare.ts | 2 +- packages/desktop/src-tauri/Cargo.toml | 4 +- packages/desktop/src-tauri/src/cli.rs | 32 ++--- packages/desktop/src-tauri/src/constants.rs | 2 +- packages/desktop/src-tauri/src/lib.rs | 8 +- .../desktop/src-tauri/src/linux_display.rs | 4 +- packages/desktop/src-tauri/src/main.rs | 2 +- packages/desktop/src-tauri/src/windows.rs | 6 +- packages/desktop/src-tauri/tauri.conf.json | 8 +- .../desktop/src-tauri/tauri.prod.conf.json | 6 +- packages/desktop/src/i18n/ar.ts | 10 +- packages/desktop/src/i18n/br.ts | 10 +- packages/desktop/src/i18n/bs.ts | 10 +- packages/desktop/src/i18n/da.ts | 10 +- packages/desktop/src/i18n/de.ts | 10 +- packages/desktop/src/i18n/en.ts | 10 +- packages/desktop/src/i18n/es.ts | 10 +- packages/desktop/src/i18n/fr.ts | 10 +- packages/desktop/src/i18n/ja.ts | 10 +- packages/desktop/src/i18n/ko.ts | 10 +- packages/desktop/src/i18n/no.ts | 10 +- packages/desktop/src/i18n/pl.ts | 10 +- packages/desktop/src/i18n/ru.ts | 10 +- packages/desktop/src/i18n/zh.ts | 10 +- packages/desktop/src/i18n/zht.ts | 10 +- packages/desktop/src/index.tsx | 12 +- packages/desktop/src/menu.ts | 6 +- packages/desktop/src/updater.ts | 2 +- packages/enterprise/package.json | 4 +- packages/enterprise/src/core/share.ts | 2 +- packages/enterprise/src/core/storage.ts | 18 +-- .../enterprise/src/routes/share/[shareID].tsx | 2 +- packages/enterprise/vite.config.ts | 4 +- packages/extensions/zed/extension.toml | 18 +-- packages/function/package.json | 2 +- packages/opencode/drizzle.config.ts | 2 +- packages/opencode/package.json | 40 ++++-- packages/opencode/script/build-node.ts | 4 +- packages/opencode/script/build.ts | 10 +- packages/opencode/script/generate.ts | 2 +- packages/opencode/script/publish.ts | 18 +-- packages/opencode/script/trace-imports.ts | 2 +- packages/opencode/specs/effect/http-api.md | 14 +- .../opencode/specs/effect/server-package.md | 2 +- packages/opencode/specs/tui-plugins.md | 14 +- packages/opencode/src/acp/README.md | 2 +- packages/opencode/src/acp/agent.ts | 10 +- packages/opencode/src/acp/session.ts | 6 +- packages/opencode/src/acp/types.ts | 4 +- packages/opencode/src/auth/index.ts | 4 +- packages/opencode/src/cli/cmd/acp.ts | 6 +- packages/opencode/src/cli/cmd/generate.ts | 4 +- packages/opencode/src/cli/cmd/import.ts | 2 +- packages/opencode/src/cli/cmd/providers.ts | 2 +- packages/opencode/src/cli/cmd/run.ts | 20 +-- packages/opencode/src/cli/cmd/serve.ts | 4 +- packages/opencode/src/cli/cmd/session.ts | 4 +- packages/opencode/src/cli/cmd/tui/app.tsx | 12 +- packages/opencode/src/cli/cmd/tui/attach.ts | 4 +- .../cmd/tui/component/dialog-console-org.tsx | 2 +- .../cli/cmd/tui/component/dialog-provider.tsx | 2 +- .../cmd/tui/component/dialog-session-list.tsx | 4 +- .../tui/component/dialog-workspace-create.tsx | 4 +- .../cli/cmd/tui/component/prompt/history.tsx | 2 +- .../cli/cmd/tui/component/prompt/index.tsx | 2 +- .../src/cli/cmd/tui/config/tui-migrate.ts | 2 +- .../opencode/src/cli/cmd/tui/config/tui.ts | 18 +-- .../src/cli/cmd/tui/context/editor.ts | 2 +- .../opencode/src/cli/cmd/tui/context/event.ts | 2 +- .../src/cli/cmd/tui/context/project.tsx | 2 +- .../src/cli/cmd/tui/context/route.tsx | 4 +- .../opencode/src/cli/cmd/tui/context/sdk.tsx | 10 +- .../opencode/src/cli/cmd/tui/context/sync.tsx | 4 +- .../src/cli/cmd/tui/context/theme.tsx | 2 +- .../cmd/tui/feature-plugins/home/footer.tsx | 2 +- .../cli/cmd/tui/feature-plugins/home/tips.tsx | 2 +- .../tui/feature-plugins/sidebar/context.tsx | 4 +- .../cmd/tui/feature-plugins/sidebar/files.tsx | 2 +- .../tui/feature-plugins/sidebar/footer.tsx | 2 +- .../cmd/tui/feature-plugins/sidebar/lsp.tsx | 2 +- .../cmd/tui/feature-plugins/sidebar/mcp.tsx | 2 +- .../cmd/tui/feature-plugins/sidebar/todo.tsx | 2 +- .../tui/feature-plugins/system/plugins.tsx | 2 +- .../opencode/src/cli/cmd/tui/plugin/api.tsx | 2 +- .../src/cli/cmd/tui/plugin/internal.ts | 2 +- .../src/cli/cmd/tui/plugin/runtime.ts | 6 +- .../opencode/src/cli/cmd/tui/plugin/slots.tsx | 2 +- .../session/dialog-fork-from-timeline.tsx | 2 +- .../tui/routes/session/dialog-timeline.tsx | 2 +- .../src/cli/cmd/tui/routes/session/index.tsx | 6 +- .../cli/cmd/tui/routes/session/permission.tsx | 2 +- .../cli/cmd/tui/routes/session/question.tsx | 2 +- .../tui/routes/session/subagent-footer.tsx | 2 +- packages/opencode/src/cli/cmd/tui/thread.ts | 14 +- .../opencode/src/cli/cmd/tui/ui/dialog.tsx | 4 +- .../opencode/src/cli/cmd/tui/util/model.ts | 2 +- .../src/cli/cmd/tui/util/transcript.ts | 2 +- .../src/cli/cmd/tui/validate-session.ts | 4 +- packages/opencode/src/cli/cmd/tui/worker.ts | 4 +- packages/opencode/src/cli/cmd/web.ts | 4 +- packages/opencode/src/cli/heap.ts | 2 +- packages/opencode/src/cli/upgrade.ts | 4 +- packages/opencode/src/config/config.ts | 38 ++--- packages/opencode/src/config/managed.ts | 2 +- packages/opencode/src/config/paths.ts | 4 +- .../dev/debug-workspace-plugin.ts | 2 +- .../opencode/src/control-plane/workspace.ts | 8 +- packages/opencode/src/effect/observability.ts | 2 +- packages/opencode/src/file/watcher.ts | 8 +- packages/opencode/src/flag/flag.ts | 132 +++++++++--------- packages/opencode/src/format/formatter.ts | 2 +- packages/opencode/src/global/index.ts | 4 +- packages/opencode/src/ide/index.ts | 2 +- packages/opencode/src/index.ts | 6 +- packages/opencode/src/installation/index.ts | 2 +- packages/opencode/src/installation/version.ts | 8 +- packages/opencode/src/lsp/lsp.ts | 4 +- packages/opencode/src/lsp/server.ts | 46 +++--- packages/opencode/src/plugin/cloudflare.ts | 2 +- packages/opencode/src/plugin/codex.ts | 2 +- .../src/plugin/github-copilot/copilot.ts | 4 +- .../src/plugin/github-copilot/models.ts | 2 +- packages/opencode/src/plugin/index.ts | 14 +- packages/opencode/src/plugin/meta.ts | 2 +- packages/opencode/src/project/project.ts | 4 +- packages/opencode/src/provider/auth.ts | 2 +- packages/opencode/src/provider/models.ts | 10 +- packages/opencode/src/provider/provider.ts | 2 +- packages/opencode/src/provider/transform.ts | 2 +- packages/opencode/src/pty/index.ts | 2 +- packages/opencode/src/server/fence.ts | 2 +- packages/opencode/src/server/middleware.ts | 4 +- packages/opencode/src/server/proxy.ts | 8 +- .../server/routes/instance/httpapi/server.ts | 10 +- .../src/server/routes/instance/index.ts | 2 +- .../src/server/routes/instance/middleware.ts | 2 +- packages/opencode/src/server/routes/ui.ts | 2 +- packages/opencode/src/server/server.ts | 6 +- packages/opencode/src/server/workspace.ts | 4 +- packages/opencode/src/session/instruction.ts | 18 +-- packages/opencode/src/session/llm.ts | 8 +- packages/opencode/src/session/prompt.ts | 2 +- packages/opencode/src/session/session.ts | 4 +- packages/opencode/src/share/session.ts | 2 +- packages/opencode/src/share/share-next.ts | 4 +- packages/opencode/src/shell/shell.ts | 2 +- packages/opencode/src/skill/index.ts | 6 +- packages/opencode/src/storage/db.ts | 20 +-- packages/opencode/src/sync/index.ts | 2 +- packages/opencode/src/tool/bash.ts | 2 +- packages/opencode/src/tool/registry.ts | 10 +- .../opencode/src/util/opencode-process.ts | 8 +- .../test/acp/event-subscription.test.ts | 2 +- packages/opencode/test/agent/agent.test.ts | 6 +- .../test/cli/plugin-auth-picker.test.ts | 2 +- .../opencode/test/cli/tui/plugin-add.test.ts | 8 +- .../test/cli/tui/plugin-install.test.ts | 4 +- .../cli/tui/plugin-loader-entrypoint.test.ts | 32 ++--- .../test/cli/tui/plugin-loader-pure.test.ts | 16 +-- .../test/cli/tui/plugin-loader.test.ts | 14 +- .../test/cli/tui/plugin-toggle.test.ts | 8 +- .../opencode/test/cli/tui/transcript.test.ts | 2 +- .../opencode/test/cli/tui/use-event.test.tsx | 2 +- packages/opencode/test/config/config.test.ts | 116 +++++++-------- packages/opencode/test/config/tui.test.ts | 16 +-- .../test/effect/cross-spawn-spawner.test.ts | 6 +- .../test/effect/observability.test.ts | 8 +- packages/opencode/test/file/watcher.test.ts | 4 +- .../test/fixture/plugin-meta-worker.ts | 2 +- packages/opencode/test/fixture/tui-plugin.ts | 4 +- packages/opencode/test/fixture/tui-runtime.ts | 4 +- packages/opencode/test/ide/ide.test.ts | 12 +- .../test/plugin/loader-shared.test.ts | 16 +-- packages/opencode/test/plugin/meta.test.ts | 14 +- packages/opencode/test/plugin/trigger.test.ts | 8 +- .../test/plugin/workspace-adaptor.test.ts | 14 +- packages/opencode/test/preload.ts | 14 +- .../test/server/httpapi-workspace.test.ts | 2 +- .../test/server/project-init-git.test.ts | 8 +- .../test/server/session-messages.test.ts | 8 +- .../opencode/test/session/instruction.test.ts | 28 ++-- packages/opencode/test/session/system.test.ts | 6 +- packages/opencode/test/skill/skill.test.ts | 6 +- packages/opencode/test/storage/db.test.ts | 2 +- packages/opencode/test/sync/index.test.ts | 6 +- packages/opencode/test/tool/bash.test.ts | 2 +- packages/opencode/test/tool/registry.test.ts | 4 +- packages/opencode/test/tool/skill.test.ts | 6 +- packages/opencode/test/util/process.test.ts | 10 +- .../test/workspace/workspace-restore.test.ts | 6 +- packages/plugin/package.json | 15 +- packages/plugin/src/example-workspace.ts | 2 +- packages/plugin/src/index.ts | 8 +- packages/plugin/src/tui.ts | 6 +- packages/script/package.json | 4 +- packages/script/src/index.ts | 20 +-- packages/sdk/js/example/example.ts | 6 +- packages/sdk/js/package.json | 10 +- packages/sdk/js/script/build.ts | 2 +- packages/sdk/js/src/client.ts | 14 +- packages/sdk/js/src/gen/sdk.gen.ts | 2 +- packages/sdk/js/src/index.ts | 10 +- packages/sdk/js/src/process.ts | 2 +- packages/sdk/js/src/server.ts | 8 +- packages/sdk/js/src/v2/client.ts | 20 +-- packages/sdk/js/src/v2/gen/sdk.gen.ts | 8 +- packages/sdk/js/src/v2/index.ts | 10 +- packages/sdk/js/src/v2/server.ts | 8 +- packages/shared/package.json | 2 +- packages/shared/src/global.ts | 2 +- packages/slack/package.json | 4 +- packages/slack/src/index.ts | 4 +- packages/storybook/package.json | 5 +- packages/ui/package.json | 12 +- packages/ui/src/components/favicon.tsx | 2 +- packages/ui/src/components/file-media.tsx | 2 +- packages/ui/src/components/font.stories.tsx | 4 +- packages/ui/src/components/icon.stories.tsx | 2 +- packages/ui/src/components/logo.stories.tsx | 2 +- .../ui/src/components/message-file.test.ts | 2 +- packages/ui/src/components/message-file.ts | 2 +- packages/ui/src/components/message-nav.tsx | 2 +- packages/ui/src/components/message-part.tsx | 2 +- packages/ui/src/components/session-diff.ts | 2 +- packages/ui/src/components/session-retry.tsx | 2 +- packages/ui/src/components/session-review.tsx | 2 +- packages/ui/src/components/session-turn.tsx | 4 +- .../timeline-playground.stories.tsx | 2 +- .../components/todo-panel-motion.stories.tsx | 2 +- packages/ui/src/context/data.tsx | 2 +- packages/ui/src/context/marked.tsx | 12 +- packages/ui/src/pierre/media.ts | 2 +- packages/ui/vite.config.ts | 2 +- packages/web/package.json | 2 +- script/duplicate-pr.ts | 4 +- script/raw-changelog.ts | 2 +- script/stats.ts | 4 +- sdks/vscode/package.json | 8 +- sdks/vscode/src/extension.ts | 6 +- turbo.json | 4 +- 373 files changed, 1446 insertions(+), 1383 deletions(-) diff --git a/README.ar.md b/README.ar.md index beb44589e6..850c24b512 100644 --- a/README.ar.md +++ b/README.ar.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop يحترم سكربت التثبيت ترتيب الاولوية التالي لمسار التثبيت: -1. `$OPENCODE_INSTALL_DIR` - مجلد تثبيت مخصص +1. `$KILO_INSTALL_DIR` - مجلد تثبيت مخصص 2. `$XDG_BIN_DIR` - مسار متوافق مع مواصفات XDG Base Directory 3. `$HOME/bin` - مجلد الثنائيات القياسي للمستخدم (ان وجد او امكن انشاؤه) 4. `$HOME/.opencode/bin` - المسار الافتراضي الاحتياطي ```bash # امثلة -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.bn.md b/README.bn.md index c7abc7346a..a59385ceb4 100644 --- a/README.bn.md +++ b/README.bn.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop ইনস্টল স্ক্রিপ্টটি ইনস্টলেশন পাতের জন্য নিম্নলিখিত অগ্রাধিকার ক্রম মেনে চলে: -1. `$OPENCODE_INSTALL_DIR` - কাস্টম ইনস্টলেশন ডিরেক্টরি +1. `$KILO_INSTALL_DIR` - কাস্টম ইনস্টলেশন ডিরেক্টরি 2. `$XDG_BIN_DIR` - XDG বেস ডিরেক্টরি স্পেসিফিকেশন সমর্থিত পাথ 3. `$HOME/bin` - সাধারণ ব্যবহারকারী বাইনারি ডিরেক্টরি (যদি বিদ্যমান থাকে বা তৈরি করা যায়) 4. `$HOME/.opencode/bin` - ডিফল্ট ফলব্যাক ```bash # উদাহরণ -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.br.md b/README.br.md index 6d1de21562..c2508f2b5d 100644 --- a/README.br.md +++ b/README.br.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop O script de instalação respeita a seguinte ordem de prioridade para o caminho de instalação: -1. `$OPENCODE_INSTALL_DIR` - Diretório de instalação personalizado +1. `$KILO_INSTALL_DIR` - Diretório de instalação personalizado 2. `$XDG_BIN_DIR` - Caminho compatível com a especificação XDG Base Directory 3. `$HOME/bin` - Diretório binário padrão do usuário (se existir ou puder ser criado) 4. `$HOME/.opencode/bin` - Fallback padrão ```bash # Exemplos -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.bs.md b/README.bs.md index 2cff8e0279..a4923b8204 100644 --- a/README.bs.md +++ b/README.bs.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop Instalacijska skripta koristi sljedeći redoslijed prioriteta za putanju instalacije: -1. `$OPENCODE_INSTALL_DIR` - Prilagođeni instalacijski direktorij +1. `$KILO_INSTALL_DIR` - Prilagođeni instalacijski direktorij 2. `$XDG_BIN_DIR` - Putanja usklađena sa XDG Base Directory specifikacijom 3. `$HOME/bin` - Standardni korisnički bin direktorij (ako postoji ili se može kreirati) 4. `$HOME/.opencode/bin` - Podrazumijevana rezervna lokacija ```bash # Primjeri -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.da.md b/README.da.md index ac522f29c4..47a9f41a05 100644 --- a/README.da.md +++ b/README.da.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop Installationsscriptet bruger følgende prioriteringsrækkefølge for installationsstien: -1. `$OPENCODE_INSTALL_DIR` - Tilpasset installationsmappe +1. `$KILO_INSTALL_DIR` - Tilpasset installationsmappe 2. `$XDG_BIN_DIR` - Sti der følger XDG Base Directory Specification 3. `$HOME/bin` - Standard bruger-bin-mappe (hvis den findes eller kan oprettes) 4. `$HOME/.opencode/bin` - Standard fallback ```bash # Eksempler -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.de.md b/README.de.md index 87a670f3fc..ee324385f5 100644 --- a/README.de.md +++ b/README.de.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop Das Installationsskript beachtet die folgende Prioritätsreihenfolge für den Installationspfad: -1. `$OPENCODE_INSTALL_DIR` - Benutzerdefiniertes Installationsverzeichnis +1. `$KILO_INSTALL_DIR` - Benutzerdefiniertes Installationsverzeichnis 2. `$XDG_BIN_DIR` - XDG Base Directory Specification-konformer Pfad 3. `$HOME/bin` - Standard-Binärverzeichnis des Users (falls vorhanden oder erstellbar) 4. `$HOME/.opencode/bin` - Standard-Fallback ```bash # Beispiele -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.es.md b/README.es.md index 9e456af1c0..233cec3628 100644 --- a/README.es.md +++ b/README.es.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop El script de instalación respeta el siguiente orden de prioridad para la ruta de instalación: -1. `$OPENCODE_INSTALL_DIR` - Directorio de instalación personalizado +1. `$KILO_INSTALL_DIR` - Directorio de instalación personalizado 2. `$XDG_BIN_DIR` - Ruta compatible con la especificación XDG Base Directory 3. `$HOME/bin` - Directorio binario estándar del usuario (si existe o se puede crear) 4. `$HOME/.opencode/bin` - Alternativa por defecto ```bash # Ejemplos -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.fr.md b/README.fr.md index c1fca23376..a147206047 100644 --- a/README.fr.md +++ b/README.fr.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop Le script d'installation respecte l'ordre de priorité suivant pour le chemin d'installation : -1. `$OPENCODE_INSTALL_DIR` - Répertoire d'installation personnalisé +1. `$KILO_INSTALL_DIR` - Répertoire d'installation personnalisé 2. `$XDG_BIN_DIR` - Chemin conforme à la spécification XDG Base Directory 3. `$HOME/bin` - Répertoire binaire utilisateur standard (s'il existe ou peut être créé) 4. `$HOME/.opencode/bin` - Repli par défaut ```bash # Exemples -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.gr.md b/README.gr.md index 2b2c2679d8..2e047ac125 100644 --- a/README.gr.md +++ b/README.gr.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop Το script εγκατάστασης τηρεί την ακόλουθη σειρά προτεραιότητας για τη διαδρομή εγκατάστασης: -1. `$OPENCODE_INSTALL_DIR` - Προσαρμοσμένος κατάλογος εγκατάστασης +1. `$KILO_INSTALL_DIR` - Προσαρμοσμένος κατάλογος εγκατάστασης 2. `$XDG_BIN_DIR` - Διαδρομή συμβατή με τις προδιαγραφές XDG Base Directory 3. `$HOME/bin` - Τυπικός κατάλογος εκτελέσιμων αρχείων χρήστη (εάν υπάρχει ή μπορεί να δημιουργηθεί) 4. `$HOME/.opencode/bin` - Προεπιλεγμένη εφεδρική διαδρομή ```bash # Παραδείγματα -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.it.md b/README.it.md index 3e516a9027..d748c4346f 100644 --- a/README.it.md +++ b/README.it.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop Lo script di installazione rispetta il seguente ordine di priorità per il percorso di installazione: -1. `$OPENCODE_INSTALL_DIR` – Directory di installazione personalizzata +1. `$KILO_INSTALL_DIR` – Directory di installazione personalizzata 2. `$XDG_BIN_DIR` – Percorso conforme alla XDG Base Directory Specification 3. `$HOME/bin` – Directory binaria standard dell’utente (se esiste o può essere creata) 4. `$HOME/.opencode/bin` – Fallback predefinito ```bash # Esempi -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.ja.md b/README.ja.md index 144dc7b6f8..a8325fc0ec 100644 --- a/README.ja.md +++ b/README.ja.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop インストールスクリプトは、インストール先パスを次の優先順位で決定します。 -1. `$OPENCODE_INSTALL_DIR` - カスタムのインストールディレクトリ +1. `$KILO_INSTALL_DIR` - カスタムのインストールディレクトリ 2. `$XDG_BIN_DIR` - XDG Base Directory Specification に準拠したパス 3. `$HOME/bin` - 標準のユーザー用バイナリディレクトリ(存在する場合、または作成できる場合) 4. `$HOME/.opencode/bin` - デフォルトのフォールバック ```bash # 例 -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.ko.md b/README.ko.md index 32defc0a5e..edd3b96f12 100644 --- a/README.ko.md +++ b/README.ko.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop 설치 스크립트는 설치 경로를 다음 우선순위로 결정합니다. -1. `$OPENCODE_INSTALL_DIR` - 사용자 지정 설치 디렉터리 +1. `$KILO_INSTALL_DIR` - 사용자 지정 설치 디렉터리 2. `$XDG_BIN_DIR` - XDG Base Directory Specification 준수 경로 3. `$HOME/bin` - 표준 사용자 바이너리 디렉터리 (존재하거나 생성 가능할 경우) 4. `$HOME/.opencode/bin` - 기본 폴백 ```bash # 예시 -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.no.md b/README.no.md index c3348286b2..886ff57219 100644 --- a/README.no.md +++ b/README.no.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop Installasjonsskriptet bruker følgende prioritet for installasjonsstien: -1. `$OPENCODE_INSTALL_DIR` - Egendefinert installasjonsmappe +1. `$KILO_INSTALL_DIR` - Egendefinert installasjonsmappe 2. `$XDG_BIN_DIR` - Sti som følger XDG Base Directory Specification 3. `$HOME/bin` - Standard brukerbinar-mappe (hvis den finnes eller kan opprettes) 4. `$HOME/.opencode/bin` - Standard fallback ```bash # Eksempler -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.pl.md b/README.pl.md index 4c5a076656..5a60e0b287 100644 --- a/README.pl.md +++ b/README.pl.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop Skrypt instalacyjny stosuje następujący priorytet wyboru ścieżki instalacji: -1. `$OPENCODE_INSTALL_DIR` - Własny katalog instalacji +1. `$KILO_INSTALL_DIR` - Własny katalog instalacji 2. `$XDG_BIN_DIR` - Ścieżka zgodna ze specyfikacją XDG Base Directory 3. `$HOME/bin` - Standardowy katalog binarny użytkownika (jeśli istnieje lub można go utworzyć) 4. `$HOME/.opencode/bin` - Domyślny fallback ```bash # Przykłady -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.ru.md b/README.ru.md index e507be70e6..b68a03907b 100644 --- a/README.ru.md +++ b/README.ru.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop Скрипт установки выбирает путь установки в следующем порядке приоритета: -1. `$OPENCODE_INSTALL_DIR` - Пользовательский каталог установки +1. `$KILO_INSTALL_DIR` - Пользовательский каталог установки 2. `$XDG_BIN_DIR` - Путь, совместимый со спецификацией XDG Base Directory 3. `$HOME/bin` - Стандартный каталог пользовательских бинарников (если существует или можно создать) 4. `$HOME/.opencode/bin` - Fallback по умолчанию ```bash # Примеры -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.th.md b/README.th.md index 4a4ea62c95..4b90a8b921 100644 --- a/README.th.md +++ b/README.th.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop สคริปต์การติดตั้งจะใช้ลำดับความสำคัญตามเส้นทางการติดตั้ง: -1. `$OPENCODE_INSTALL_DIR` - ไดเรกทอรีการติดตั้งที่กำหนดเอง +1. `$KILO_INSTALL_DIR` - ไดเรกทอรีการติดตั้งที่กำหนดเอง 2. `$XDG_BIN_DIR` - เส้นทางที่สอดคล้องกับ XDG Base Directory Specification 3. `$HOME/bin` - ไดเรกทอรีไบนารีผู้ใช้มาตรฐาน (หากมีอยู่หรือสามารถสร้างได้) 4. `$HOME/.opencode/bin` - ค่าสำรองเริ่มต้น ```bash # ตัวอย่าง -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.tr.md b/README.tr.md index e88b40f875..d3b474a07b 100644 --- a/README.tr.md +++ b/README.tr.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop Kurulum betiği (install script), kurulum yolu (installation path) için aşağıdaki öncelik sırasını takip eder: -1. `$OPENCODE_INSTALL_DIR` - Özel kurulum dizini +1. `$KILO_INSTALL_DIR` - Özel kurulum dizini 2. `$XDG_BIN_DIR` - XDG Base Directory Specification uyumlu yol 3. `$HOME/bin` - Standart kullanıcı binary dizini (varsa veya oluşturulabiliyorsa) 4. `$HOME/.opencode/bin` - Varsayılan yedek konum ```bash # Örnekler -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.uk.md b/README.uk.md index a1a0259b6d..1471aa6fdd 100644 --- a/README.uk.md +++ b/README.uk.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop Скрипт встановлення дотримується такого порядку пріоритету для шляху встановлення: -1. `$OPENCODE_INSTALL_DIR` - Користувацький каталог встановлення +1. `$KILO_INSTALL_DIR` - Користувацький каталог встановлення 2. `$XDG_BIN_DIR` - Шлях, сумісний зі специфікацією XDG Base Directory 3. `$HOME/bin` - Стандартний каталог користувацьких бінарників (якщо існує або його можна створити) 4. `$HOME/.opencode/bin` - Резервний варіант за замовчуванням ```bash # Приклади -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.vi.md b/README.vi.md index 0932c50f78..a4eaf22301 100644 --- a/README.vi.md +++ b/README.vi.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop Tập lệnh cài đặt tuân theo thứ tự ưu tiên sau cho đường dẫn cài đặt: -1. `$OPENCODE_INSTALL_DIR` - Thư mục cài đặt tùy chỉnh +1. `$KILO_INSTALL_DIR` - Thư mục cài đặt tùy chỉnh 2. `$XDG_BIN_DIR` - Đường dẫn tuân thủ XDG Base Directory Specification 3. `$HOME/bin` - Thư mục nhị phân tiêu chuẩn của người dùng (nếu tồn tại hoặc có thể tạo) 4. `$HOME/.opencode/bin` - Mặc định dự phòng ```bash # Ví dụ -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.zh.md b/README.zh.md index 46d9f761cb..928378213d 100644 --- a/README.zh.md +++ b/README.zh.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop 安装脚本按照以下优先级决定安装路径: -1. `$OPENCODE_INSTALL_DIR` - 自定义安装目录 +1. `$KILO_INSTALL_DIR` - 自定义安装目录 2. `$XDG_BIN_DIR` - 符合 XDG 基础目录规范的路径 3. `$HOME/bin` - 如果存在或可创建的用户二进制目录 4. `$HOME/.opencode/bin` - 默认备用路径 ```bash # 示例 -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/README.zht.md b/README.zht.md index 7ef51d8fdd..88c1c54565 100644 --- a/README.zht.md +++ b/README.zht.md @@ -86,14 +86,14 @@ scoop bucket add extras; scoop install extras/opencode-desktop 安裝腳本會依據以下優先順序決定安裝路徑: -1. `$OPENCODE_INSTALL_DIR` - 自定義安裝目錄 +1. `$KILO_INSTALL_DIR` - 自定義安裝目錄 2. `$XDG_BIN_DIR` - 符合 XDG 基礎目錄規範的路徑 3. `$HOME/bin` - 標準使用者執行檔目錄 (若存在或可建立) 4. `$HOME/.opencode/bin` - 預設備用路徑 ```bash # 範例 -OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash +KILO_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash ``` diff --git a/github/index.ts b/github/index.ts index 51ee2a46a5..38a61086a5 100644 --- a/github/index.ts +++ b/github/index.ts @@ -6,7 +6,7 @@ import * as core from "@actions/core" import * as github from "@actions/github" import type { Context as GitHubContext } from "@actions/github/lib/context" import type { IssueCommentEvent, PullRequestReviewCommentEvent } from "@octokit/webhooks-types" -import { createOpencodeClient } from "@opencode-ai/sdk" +import { createKiloClient } from "@kilocode/sdk" import { spawn } from "node:child_process" import { setTimeout as sleep } from "node:timers/promises" @@ -113,7 +113,7 @@ type IssueQueryResponse = { } } -const { client, server } = createOpencode() +const { client, server } = createKilo() let accessToken: string let octoRest: Octokit let octoGraph: typeof graphql @@ -228,12 +228,12 @@ try { } process.exit(exitCode) -function createOpencode() { +function createKilo() { const host = "127.0.0.1" const port = 4096 const url = `http://${host}:${port}` const proc = spawn(`opencode`, [`serve`, `--hostname=${host}`, `--port=${port}`]) - const client = createOpencodeClient({ baseUrl: url }) + const client = createKiloClient({ baseUrl: url }) return { server: { url, close: () => proc.kill() }, diff --git a/github/package.json b/github/package.json index e1b913abed..674340f87e 100644 --- a/github/package.json +++ b/github/package.json @@ -15,6 +15,7 @@ "@actions/github": "6.0.1", "@octokit/graphql": "9.0.1", "@octokit/rest": "catalog:", - "@opencode-ai/sdk": "workspace:*" - } + "@kilocode/sdk": "workspace:*" + }, + "version": "7.2.25" } diff --git a/infra/enterprise.ts b/infra/enterprise.ts index dc336a6843..c1c1ca68de 100644 --- a/infra/enterprise.ts +++ b/infra/enterprise.ts @@ -8,10 +8,10 @@ new sst.cloudflare.x.SolidStart("Teams", { path: "packages/enterprise", buildCommand: "bun run build:cloudflare", environment: { - OPENCODE_STORAGE_ADAPTER: "r2", - OPENCODE_STORAGE_ACCOUNT_ID: sst.cloudflare.DEFAULT_ACCOUNT_ID, - OPENCODE_STORAGE_ACCESS_KEY_ID: SECRET.R2AccessKey.value, - OPENCODE_STORAGE_SECRET_ACCESS_KEY: SECRET.R2SecretKey.value, - OPENCODE_STORAGE_BUCKET: storage.name, + KILO_STORAGE_ADAPTER: "r2", + KILO_STORAGE_ACCOUNT_ID: sst.cloudflare.DEFAULT_ACCOUNT_ID, + KILO_STORAGE_ACCESS_KEY_ID: SECRET.R2AccessKey.value, + KILO_STORAGE_SECRET_ACCESS_KEY: SECRET.R2SecretKey.value, + KILO_STORAGE_BUCKET: storage.name, }, }) diff --git a/package.json b/package.json index f918bcd025..59f07920b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "name": "opencode", + "name": "@kilocode/kilo", "description": "AI-powered development tool", "private": true, "type": "module", @@ -17,14 +17,13 @@ "prepare": "husky", "random": "echo 'Random script'", "hello": "echo 'Hello World!'", - "test": "echo 'do not run tests from root' && exit 1" + "test": "echo 'do not run tests from root' && exit 1", + "extension": "bun --cwd packages/kilo-vscode script/launch.ts" }, "workspaces": { "packages": [ "packages/*", - "packages/console/*", - "packages/sdk/js", - "packages/slack" + "packages/sdk/js" ], "catalog": { "@effect/opentelemetry": "4.0.0-beta.48", @@ -48,14 +47,14 @@ "@pierre/diffs": "1.1.0-beta.18", "@solid-primitives/storage": "4.3.3", "@tailwindcss/vite": "4.1.11", - "diff": "8.0.2", - "dompurify": "3.3.1", + "diff": "8.0.4", + "dompurify": "3.3.3", "drizzle-kit": "1.0.0-beta.19-d95b7a4", "drizzle-orm": "1.0.0-beta.19-d95b7a4", "effect": "4.0.0-beta.48", "ai": "6.0.168", "cross-spawn": "7.0.6", - "hono": "4.10.7", + "hono": "4.12.12", "hono-openapi": "1.1.2", "fuzzysort": "3.1.0", "luxon": "3.6.1", @@ -65,18 +64,18 @@ "@playwright/test": "1.59.1", "semver": "7.7.4", "typescript": "5.8.2", - "@typescript/native-preview": "7.0.0-dev.20251207.1", + "@typescript/native-preview": "7.0.0-dev.20260316.1", "zod": "4.1.8", "remeda": "2.26.0", "shiki": "3.20.0", "solid-list": "0.3.0", "tailwindcss": "4.1.11", "virtua": "0.42.3", - "vite": "7.1.4", + "vite": "7.3.2", "@solidjs/meta": "0.29.4", "@solidjs/router": "0.15.4", "@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020", - "solid-js": "1.9.10", + "solid-js": "1.9.12", "vite-plugin-solid": "2.11.10", "@lydell/node-pty": "1.2.0-beta.10" } @@ -93,19 +92,22 @@ "prettier": "3.6.2", "semver": "^7.6.0", "sst": "3.18.10", - "turbo": "2.8.13" + "turbo": "2.8.13", + "@changesets/changelog-github": "^0.5.1", + "@changesets/cli": "^2.27.10" }, "dependencies": { - "@aws-sdk/client-s3": "3.933.0", - "@opencode-ai/plugin": "workspace:*", + "@aws-sdk/client-s3": "3.1025.0", + "@kilocode/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", - "@opencode-ai/sdk": "workspace:*", + "@kilocode/sdk": "workspace:*", "heap-snapshot-toolkit": "1.1.3", - "typescript": "catalog:" + "typescript": "catalog:", + "@morphllm/morphsdk": "0.2.166" }, "repository": { "type": "git", - "url": "https://github.com/anomalyco/opencode" + "url": "https://github.com/Kilo-Org/kilocode" }, "license": "MIT", "prettier": { @@ -124,11 +126,26 @@ ], "overrides": { "@types/bun": "catalog:", - "@types/node": "catalog:" + "@types/node": "catalog:", + "effect": "catalog:", + "@effect/platform-node-shared": "4.0.0-beta.46", + "path-to-regexp": ">=8.4.0", + "picomatch": ">=2.3.2", + "defu": "6.1.6", + "lodash": "4.18.1", + "@xmldom/xmldom": ">=0.8.12", + "smol-toml": ">=1.6.1", + "fastify": ">=5.8.3", + "diff": "8.0.4", + "dompurify": "3.3.3", + "happy-dom": ">=20.8.9" }, "patchedDependencies": { "@npmcli/agent@4.0.0": "patches/@npmcli%2Fagent@4.0.0.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", - "solid-js@1.9.10": "patches/solid-js@1.9.10.patch" - } + "solid-js@1.9.10": "patches/solid-js@1.9.10.patch", + "stream-chat@9.38.0": "patches/stream-chat@9.38.0.patch" + }, + "version": "7.2.25", + "peerDependencies": {} } diff --git a/packages/app/package.json b/packages/app/package.json index b01b9b9d1a..25f5742850 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.14.23", + "version": "7.2.25", "description": "", "type": "module", "exports": { @@ -40,7 +40,7 @@ }, "dependencies": { "@kobalte/core": "catalog:", - "@opencode-ai/sdk": "workspace:*", + "@kilocode/sdk": "workspace:*", "@opencode-ai/ui": "workspace:*", "@opencode-ai/shared": "workspace:*", "@shikijs/transformers": "3.9.2", @@ -72,6 +72,9 @@ "solid-list": "catalog:", "tailwindcss": "catalog:", "virtua": "catalog:", - "zod": "catalog:" - } + "zod": "catalog:", + "@kilocode/kilo-ui": "workspace:*", + "@kilocode/kilo-i18n": "workspace:*" + }, + "peerDependencies": {} } diff --git a/packages/app/playwright.config.ts b/packages/app/playwright.config.ts index e9fb1cfe4e..0a1e56bb4f 100644 --- a/packages/app/playwright.config.ts +++ b/packages/app/playwright.config.ts @@ -31,8 +31,8 @@ export default defineConfig({ reuseExistingServer: reuse, timeout: 120_000, env: { - VITE_OPENCODE_SERVER_HOST: serverHost, - VITE_OPENCODE_SERVER_PORT: serverPort, + VITE_KILO_SERVER_HOST: serverHost, + VITE_KILO_SERVER_PORT: serverPort, }, }, use: { diff --git a/packages/app/src/components/dialog-connect-provider.tsx b/packages/app/src/components/dialog-connect-provider.tsx index e305743799..1ed4786b98 100644 --- a/packages/app/src/components/dialog-connect-provider.tsx +++ b/packages/app/src/components/dialog-connect-provider.tsx @@ -1,4 +1,4 @@ -import type { ProviderAuthAuthorization, ProviderAuthMethod } from "@opencode-ai/sdk/v2/client" +import type { ProviderAuthAuthorization, ProviderAuthMethod } from "@kilocode/sdk/v2/client" import { Button } from "@opencode-ai/ui/button" import { useDialog } from "@opencode-ai/ui/context/dialog" import { Dialog } from "@opencode-ai/ui/dialog" diff --git a/packages/app/src/components/dialog-custom-provider.tsx b/packages/app/src/components/dialog-custom-provider.tsx index 53b66fb451..6f38651e6d 100644 --- a/packages/app/src/components/dialog-custom-provider.tsx +++ b/packages/app/src/components/dialog-custom-provider.tsx @@ -183,7 +183,7 @@ export function DialogCustomProvider(props: Props) {

{language.t("provider.custom.description.prefix")} - + {language.t("provider.custom.description.link")} {language.t("provider.custom.description.suffix")} diff --git a/packages/app/src/components/dialog-fork.tsx b/packages/app/src/components/dialog-fork.tsx index 710618c301..4cdcca33ac 100644 --- a/packages/app/src/components/dialog-fork.tsx +++ b/packages/app/src/components/dialog-fork.tsx @@ -8,7 +8,7 @@ import { Dialog } from "@opencode-ai/ui/dialog" import { List } from "@opencode-ai/ui/list" import { showToast } from "@opencode-ai/ui/toast" import { extractPromptFromParts } from "@/utils/prompt" -import type { TextPart as SDKTextPart } from "@opencode-ai/sdk/v2/client" +import type { TextPart as SDKTextPart } from "@kilocode/sdk/v2/client" import { base64Encode } from "@opencode-ai/shared/util/encode" import { useLanguage } from "@/context/language" diff --git a/packages/app/src/components/file-tree.tsx b/packages/app/src/components/file-tree.tsx index 211ce05ef0..f4148dfe4e 100644 --- a/packages/app/src/components/file-tree.tsx +++ b/packages/app/src/components/file-tree.tsx @@ -17,7 +17,7 @@ import { type ParentProps, } from "solid-js" import { Dynamic } from "solid-js/web" -import type { FileNode } from "@opencode-ai/sdk/v2" +import type { FileNode } from "@kilocode/sdk/v2" const MAX_DEPTH = 128 diff --git a/packages/app/src/components/prompt-input/build-request-parts.ts b/packages/app/src/components/prompt-input/build-request-parts.ts index c268af35ee..9a62d0b71d 100644 --- a/packages/app/src/components/prompt-input/build-request-parts.ts +++ b/packages/app/src/components/prompt-input/build-request-parts.ts @@ -1,5 +1,5 @@ import { getFilename } from "@opencode-ai/shared/util/path" -import { type AgentPartInput, type FilePartInput, type Part, type TextPartInput } from "@opencode-ai/sdk/v2/client" +import { type AgentPartInput, type FilePartInput, type Part, type TextPartInput } from "@kilocode/sdk/v2/client" import type { FileSelection } from "@/context/file" import { encodeFilePath } from "@/context/file/path" import type { AgentPart, FileAttachmentPart, ImageAttachmentPart, Prompt } from "@/context/prompt" diff --git a/packages/app/src/components/prompt-input/submit.test.ts b/packages/app/src/components/prompt-input/submit.test.ts index cf99497232..e1b959e8dd 100644 --- a/packages/app/src/components/prompt-input/submit.test.ts +++ b/packages/app/src/components/prompt-input/submit.test.ts @@ -63,8 +63,8 @@ beforeAll(async () => { useParams: () => params, })) - mock.module("@opencode-ai/sdk/v2/client", () => ({ - createOpencodeClient: (input: { directory: string }) => { + mock.module("@kilocode/sdk/v2/client", () => ({ + createKiloClient: (input: { directory: string }) => { createdClients.push(input.directory) return clientFor(input.directory) }, diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts index 6805f619c1..fdd2889efa 100644 --- a/packages/app/src/components/prompt-input/submit.ts +++ b/packages/app/src/components/prompt-input/submit.ts @@ -1,4 +1,4 @@ -import type { Message, Session } from "@opencode-ai/sdk/v2/client" +import type { Message, Session } from "@kilocode/sdk/v2/client" import { showToast } from "@opencode-ai/ui/toast" import { base64Encode } from "@opencode-ai/shared/util/encode" import { Binary } from "@opencode-ai/shared/util/binary" diff --git a/packages/app/src/components/session/session-context-breakdown.test.ts b/packages/app/src/components/session/session-context-breakdown.test.ts index f38aecb55d..36c4384345 100644 --- a/packages/app/src/components/session/session-context-breakdown.test.ts +++ b/packages/app/src/components/session/session-context-breakdown.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import type { Message, Part } from "@opencode-ai/sdk/v2/client" +import type { Message, Part } from "@kilocode/sdk/v2/client" import { estimateSessionContextBreakdown } from "./session-context-breakdown" const user = (id: string) => { diff --git a/packages/app/src/components/session/session-context-breakdown.ts b/packages/app/src/components/session/session-context-breakdown.ts index e263b2957b..1b11fadcce 100644 --- a/packages/app/src/components/session/session-context-breakdown.ts +++ b/packages/app/src/components/session/session-context-breakdown.ts @@ -1,4 +1,4 @@ -import type { Message, Part } from "@opencode-ai/sdk/v2/client" +import type { Message, Part } from "@kilocode/sdk/v2/client" export type SessionContextBreakdownKey = "system" | "user" | "assistant" | "tool" | "other" diff --git a/packages/app/src/components/session/session-context-metrics.test.ts b/packages/app/src/components/session/session-context-metrics.test.ts index 0e109a71bd..be1bbca258 100644 --- a/packages/app/src/components/session/session-context-metrics.test.ts +++ b/packages/app/src/components/session/session-context-metrics.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import type { Message } from "@opencode-ai/sdk/v2/client" +import type { Message } from "@kilocode/sdk/v2/client" import { getSessionContextMetrics } from "./session-context-metrics" const assistant = ( diff --git a/packages/app/src/components/session/session-context-metrics.ts b/packages/app/src/components/session/session-context-metrics.ts index 0789b05f17..4bdcff52eb 100644 --- a/packages/app/src/components/session/session-context-metrics.ts +++ b/packages/app/src/components/session/session-context-metrics.ts @@ -1,4 +1,4 @@ -import type { AssistantMessage, Message } from "@opencode-ai/sdk/v2/client" +import type { AssistantMessage, Message } from "@kilocode/sdk/v2/client" type Provider = { id: string diff --git a/packages/app/src/components/session/session-context-tab.tsx b/packages/app/src/components/session/session-context-tab.tsx index abf4c93346..2fa44f1c7e 100644 --- a/packages/app/src/components/session/session-context-tab.tsx +++ b/packages/app/src/components/session/session-context-tab.tsx @@ -10,7 +10,7 @@ import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header" import { File } from "@opencode-ai/ui/file" import { Markdown } from "@opencode-ai/ui/markdown" import { ScrollView } from "@opencode-ai/ui/scroll-view" -import type { Message, Part, UserMessage } from "@opencode-ai/sdk/v2/client" +import type { Message, Part, UserMessage } from "@kilocode/sdk/v2/client" import { useLanguage } from "@/context/language" import { useProviders } from "@/hooks/use-providers" import { useSessionLayout } from "@/pages/session/session-layout" diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 021e5be67e..d61335d9f0 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -153,7 +153,7 @@ export function SessionHeader() { }) const hotkey = createMemo(() => command.keybind("file.open")) const os = createMemo(() => detectOS(platform)) - const isDesktopBeta = platform.platform === "desktop" && import.meta.env.VITE_OPENCODE_CHANNEL === "beta" + const isDesktopBeta = platform.platform === "desktop" && import.meta.env.VITE_KILO_CHANNEL === "beta" const search = createMemo(() => !isDesktopBeta || settings.general.showSearch()) const tree = createMemo(() => !isDesktopBeta || settings.general.showFileTree()) const term = createMemo(() => !isDesktopBeta || settings.general.showTerminal()) diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index f38442379d..50c511d0dd 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -395,7 +395,7 @@ export const SettingsGeneral: Component = () => { description={ <> {language.t("settings.general.row.theme.description")}{" "} - {language.t("common.learnMore")} + {language.t("common.learnMore")} } > @@ -717,7 +717,7 @@ export const SettingsGeneral: Component = () => { }} - +

diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index 409fcbeff6..eb9bd3ebf1 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -80,7 +80,7 @@ export function Titlebar() { const canBack = createMemo(() => history.index > 0) const canForward = createMemo(() => history.index < history.stack.length - 1) const hasProjects = createMemo(() => layout.projects.list().length > 0) - const nav = createMemo(() => import.meta.env.VITE_OPENCODE_CHANNEL !== "beta" || settings.general.showNavigation()) + const nav = createMemo(() => import.meta.env.VITE_KILO_CHANNEL !== "beta" || settings.general.showNavigation()) const back = () => { const next = backPath(history) @@ -288,9 +288,9 @@ export function Titlebar() {
- {["beta", "dev"].includes(import.meta.env.VITE_OPENCODE_CHANNEL) && ( + {["beta", "dev"].includes(import.meta.env.VITE_KILO_CHANNEL) && (
- {import.meta.env.VITE_OPENCODE_CHANNEL.toUpperCase()} + {import.meta.env.VITE_KILO_CHANNEL.toUpperCase()}
)}
diff --git a/packages/app/src/context/file/content-cache.ts b/packages/app/src/context/file/content-cache.ts index 4b72406883..98de196ab6 100644 --- a/packages/app/src/context/file/content-cache.ts +++ b/packages/app/src/context/file/content-cache.ts @@ -1,4 +1,4 @@ -import type { FileContent } from "@opencode-ai/sdk/v2" +import type { FileContent } from "@kilocode/sdk/v2" const MAX_FILE_CONTENT_ENTRIES = 40 const MAX_FILE_CONTENT_BYTES = 20 * 1024 * 1024 diff --git a/packages/app/src/context/file/tree-store.ts b/packages/app/src/context/file/tree-store.ts index a86051d286..dc4e58f7a2 100644 --- a/packages/app/src/context/file/tree-store.ts +++ b/packages/app/src/context/file/tree-store.ts @@ -1,5 +1,5 @@ import { createStore, produce, reconcile } from "solid-js/store" -import type { FileNode } from "@opencode-ai/sdk/v2" +import type { FileNode } from "@kilocode/sdk/v2" type DirectoryState = { expanded: boolean diff --git a/packages/app/src/context/file/types.ts b/packages/app/src/context/file/types.ts index 7ce8a37c25..ee1cc49f31 100644 --- a/packages/app/src/context/file/types.ts +++ b/packages/app/src/context/file/types.ts @@ -1,4 +1,4 @@ -import type { FileContent } from "@opencode-ai/sdk/v2" +import type { FileContent } from "@kilocode/sdk/v2" export type FileSelection = { startLine: number diff --git a/packages/app/src/context/file/watcher.ts b/packages/app/src/context/file/watcher.ts index fbf7199279..57bc68edc9 100644 --- a/packages/app/src/context/file/watcher.ts +++ b/packages/app/src/context/file/watcher.ts @@ -1,4 +1,4 @@ -import type { FileNode } from "@opencode-ai/sdk/v2" +import type { FileNode } from "@kilocode/sdk/v2" type WatcherEvent = { type: string diff --git a/packages/app/src/context/global-sdk.tsx b/packages/app/src/context/global-sdk.tsx index e53d60d5a0..5c81174426 100644 --- a/packages/app/src/context/global-sdk.tsx +++ b/packages/app/src/context/global-sdk.tsx @@ -1,4 +1,4 @@ -import type { Event } from "@opencode-ai/sdk/v2/client" +import type { Event } from "@kilocode/sdk/v2/client" import { createSimpleContext } from "@opencode-ai/ui/context" import { createGlobalEmitter } from "@solid-primitives/event-bus" import { makeEventListener } from "@solid-primitives/event-listener" diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index b742667d72..edebef2b90 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -1,12 +1,12 @@ import type { Config, - OpencodeClient, + KiloClient, Path, Project, ProviderAuthResponse, ProviderListResponse, Todo, -} from "@opencode-ai/sdk/v2/client" +} from "@kilocode/sdk/v2/client" import { showToast } from "@opencode-ai/ui/toast" import { getFilename } from "@opencode-ai/shared/util/path" import { batch, createContext, getOwner, onCleanup, onMount, type ParentProps, untrack, useContext } from "solid-js" @@ -49,7 +49,7 @@ function createGlobalSync() { const owner = getOwner() if (!owner) throw new Error("GlobalSync must be created within owner") - const sdkCache = new Map() + const sdkCache = new Map() const booting = new Map>() const sessionLoads = new Map>() const sessionMeta = new Map() diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts index be789a5e53..0b4d843026 100644 --- a/packages/app/src/context/global-sync/bootstrap.ts +++ b/packages/app/src/context/global-sync/bootstrap.ts @@ -1,6 +1,6 @@ import type { Config, - OpencodeClient, + KiloClient, Path, PermissionRequest, Project, @@ -9,7 +9,7 @@ import type { QuestionRequest, Session, Todo, -} from "@opencode-ai/sdk/v2/client" +} from "@kilocode/sdk/v2/client" import { showToast } from "@opencode-ai/ui/toast" import { getFilename } from "@opencode-ai/shared/util/path" import { retry } from "@opencode-ai/shared/util/retry" @@ -67,7 +67,7 @@ function runAll(list: Array<() => Promise>) { } export async function bootstrapGlobal(input: { - globalSDK: OpencodeClient + globalSDK: KiloClient requestFailedTitle: string translate: (key: string, vars?: Record) => string formatMoreCount: (count: number) => string @@ -163,7 +163,7 @@ function warmSessions(input: { ids: string[] store: Store setStore: SetStoreFunction - sdk: OpencodeClient + sdk: KiloClient }) { const known = new Set(input.store.session.map((item) => item.id)) const ids = [...new Set(input.ids)].filter((id) => !!id && !known.has(id)) @@ -184,8 +184,8 @@ export const loadProvidersQuery = (directory: string | null) => export const loadAgentsQuery = ( directory: string | null, - sdk?: OpencodeClient, - transform?: (x: Awaited>) => void, + sdk?: KiloClient, + transform?: (x: Awaited>) => void, ) => queryOptions({ queryKey: [directory, "agents"], @@ -203,8 +203,8 @@ export const loadAgentsQuery = ( export const loadPathQuery = ( directory: string | null, - sdk?: OpencodeClient, - transform?: (x: Awaited>) => void, + sdk?: KiloClient, + transform?: (x: Awaited>) => void, ) => queryOptions({ queryKey: [directory, "path"], @@ -222,7 +222,7 @@ export const loadPathQuery = ( export async function bootstrapDirectory(input: { directory: string - sdk: OpencodeClient + sdk: KiloClient store: Store setStore: SetStoreFunction vcsCache: VcsCache diff --git a/packages/app/src/context/global-sync/child-store.ts b/packages/app/src/context/global-sync/child-store.ts index f3b613a7f2..1545fb4208 100644 --- a/packages/app/src/context/global-sync/child-store.ts +++ b/packages/app/src/context/global-sync/child-store.ts @@ -1,7 +1,7 @@ import { createRoot, getOwner, onCleanup, runWithOwner, type Owner } from "solid-js" import { createStore, type SetStoreFunction, type Store } from "solid-js/store" import { Persist, persisted } from "@/utils/persist" -import type { VcsInfo } from "@opencode-ai/sdk/v2/client" +import type { VcsInfo } from "@kilocode/sdk/v2/client" import { DIR_IDLE_TTL_MS, MAX_DIR_STORES, diff --git a/packages/app/src/context/global-sync/event-reducer.test.ts b/packages/app/src/context/global-sync/event-reducer.test.ts index 892129788e..f66e7ede31 100644 --- a/packages/app/src/context/global-sync/event-reducer.test.ts +++ b/packages/app/src/context/global-sync/event-reducer.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import type { Message, Part, PermissionRequest, Project, QuestionRequest, Session } from "@opencode-ai/sdk/v2/client" +import type { Message, Part, PermissionRequest, Project, QuestionRequest, Session } from "@kilocode/sdk/v2/client" import { createStore } from "solid-js/store" import type { State } from "./types" import { applyDirectoryEvent, applyGlobalEvent, cleanupDroppedSessionCaches } from "./event-reducer" diff --git a/packages/app/src/context/global-sync/event-reducer.ts b/packages/app/src/context/global-sync/event-reducer.ts index 82408fdfe9..9196d5b4f7 100644 --- a/packages/app/src/context/global-sync/event-reducer.ts +++ b/packages/app/src/context/global-sync/event-reducer.ts @@ -10,7 +10,7 @@ import type { SessionStatus, SnapshotFileDiff, Todo, -} from "@opencode-ai/sdk/v2/client" +} from "@kilocode/sdk/v2/client" import type { State, VcsCache } from "./types" import { trimSessions } from "./session-trim" import { dropSessionCaches } from "./session-cache" diff --git a/packages/app/src/context/global-sync/session-cache.test.ts b/packages/app/src/context/global-sync/session-cache.test.ts index 472ac219e9..471c632d80 100644 --- a/packages/app/src/context/global-sync/session-cache.test.ts +++ b/packages/app/src/context/global-sync/session-cache.test.ts @@ -7,7 +7,7 @@ import type { SessionStatus, SnapshotFileDiff, Todo, -} from "@opencode-ai/sdk/v2/client" +} from "@kilocode/sdk/v2/client" import { dropSessionCaches, pickSessionCacheEvictions } from "./session-cache" const msg = (id: string, sessionID: string) => diff --git a/packages/app/src/context/global-sync/session-cache.ts b/packages/app/src/context/global-sync/session-cache.ts index 6f4d81062b..b578997307 100644 --- a/packages/app/src/context/global-sync/session-cache.ts +++ b/packages/app/src/context/global-sync/session-cache.ts @@ -6,7 +6,7 @@ import type { SessionStatus, SnapshotFileDiff, Todo, -} from "@opencode-ai/sdk/v2/client" +} from "@kilocode/sdk/v2/client" export const SESSION_CACHE_LIMIT = 40 diff --git a/packages/app/src/context/global-sync/session-trim.test.ts b/packages/app/src/context/global-sync/session-trim.test.ts index be12c074b5..da9ef3498f 100644 --- a/packages/app/src/context/global-sync/session-trim.test.ts +++ b/packages/app/src/context/global-sync/session-trim.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import type { PermissionRequest, Session } from "@opencode-ai/sdk/v2/client" +import type { PermissionRequest, Session } from "@kilocode/sdk/v2/client" import { trimSessions } from "./session-trim" const session = (input: { id: string; parentID?: string; created: number; updated?: number; archived?: number }) => diff --git a/packages/app/src/context/global-sync/session-trim.ts b/packages/app/src/context/global-sync/session-trim.ts index 800ba74a68..8aa3140021 100644 --- a/packages/app/src/context/global-sync/session-trim.ts +++ b/packages/app/src/context/global-sync/session-trim.ts @@ -1,4 +1,4 @@ -import type { PermissionRequest, Session } from "@opencode-ai/sdk/v2/client" +import type { PermissionRequest, Session } from "@kilocode/sdk/v2/client" import { cmp } from "./utils" import { SESSION_RECENT_LIMIT, SESSION_RECENT_WINDOW } from "./types" diff --git a/packages/app/src/context/global-sync/types.ts b/packages/app/src/context/global-sync/types.ts index e3ec83c5ee..a1a5184268 100644 --- a/packages/app/src/context/global-sync/types.ts +++ b/packages/app/src/context/global-sync/types.ts @@ -15,7 +15,7 @@ import type { SnapshotFileDiff, Todo, VcsInfo, -} from "@opencode-ai/sdk/v2/client" +} from "@kilocode/sdk/v2/client" import type { Accessor } from "solid-js" import type { SetStoreFunction, Store } from "solid-js/store" diff --git a/packages/app/src/context/global-sync/utils.test.ts b/packages/app/src/context/global-sync/utils.test.ts index 6d44ac9a89..24f1547c27 100644 --- a/packages/app/src/context/global-sync/utils.test.ts +++ b/packages/app/src/context/global-sync/utils.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import type { Agent } from "@opencode-ai/sdk/v2/client" +import type { Agent } from "@kilocode/sdk/v2/client" import { normalizeAgentList } from "./utils" const agent = (name = "build") => diff --git a/packages/app/src/context/global-sync/utils.ts b/packages/app/src/context/global-sync/utils.ts index cac58f3174..e6fd6f64ba 100644 --- a/packages/app/src/context/global-sync/utils.ts +++ b/packages/app/src/context/global-sync/utils.ts @@ -1,4 +1,4 @@ -import type { Agent, Project, ProviderListResponse } from "@opencode-ai/sdk/v2/client" +import type { Agent, Project, ProviderListResponse } from "@kilocode/sdk/v2/client" export const cmp = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0) diff --git a/packages/app/src/context/highlights.tsx b/packages/app/src/context/highlights.tsx index 058f7cc4b6..11ee613e64 100644 --- a/packages/app/src/context/highlights.tsx +++ b/packages/app/src/context/highlights.tsx @@ -7,7 +7,7 @@ import { useSettings } from "@/context/settings" import { persisted } from "@/utils/persist" import { DialogReleaseNotes, type Highlight } from "@/components/dialog-release-notes" -const CHANGELOG_URL = "https://opencode.ai/changelog.json" +const CHANGELOG_URL = "https://kilo.ai/changelog.json" type Store = { version?: string diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index 97d9cacbbe..603c3c5961 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -6,7 +6,7 @@ import { useGlobalSync } from "./global-sync" import { useGlobalSDK } from "./global-sdk" import { useServer } from "./server" import { usePlatform } from "./platform" -import { Project } from "@opencode-ai/sdk/v2" +import { Project } from "@kilocode/sdk/v2" import { Persist, persisted, removePersisted } from "@/utils/persist" import { decode64 } from "@/utils/base64" import { same } from "@/utils/same" diff --git a/packages/app/src/context/notification.tsx b/packages/app/src/context/notification.tsx index 251b67b06c..9c30ed269d 100644 --- a/packages/app/src/context/notification.tsx +++ b/packages/app/src/context/notification.tsx @@ -10,7 +10,7 @@ import { useSettings } from "@/context/settings" import { Binary } from "@opencode-ai/shared/util/binary" import { base64Encode } from "@opencode-ai/shared/util/encode" import { decode64 } from "@/utils/base64" -import { EventSessionError } from "@opencode-ai/sdk/v2" +import { EventSessionError } from "@kilocode/sdk/v2" import { Persist, persisted } from "@/utils/persist" import { playSoundById } from "@/utils/sound" diff --git a/packages/app/src/context/permission-auto-respond.test.ts b/packages/app/src/context/permission-auto-respond.test.ts index 2f8ca6265e..cb927bbd31 100644 --- a/packages/app/src/context/permission-auto-respond.test.ts +++ b/packages/app/src/context/permission-auto-respond.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import type { PermissionRequest, Session } from "@opencode-ai/sdk/v2/client" +import type { PermissionRequest, Session } from "@kilocode/sdk/v2/client" import { base64Encode } from "@opencode-ai/shared/util/encode" import { autoRespondsPermission, isDirectoryAutoAccepting } from "./permission-auto-respond" diff --git a/packages/app/src/context/permission.tsx b/packages/app/src/context/permission.tsx index 672f84f82a..6f890da7c9 100644 --- a/packages/app/src/context/permission.tsx +++ b/packages/app/src/context/permission.tsx @@ -1,7 +1,7 @@ import { createEffect, createMemo, onCleanup } from "solid-js" import { createStore, produce } from "solid-js/store" import { createSimpleContext } from "@opencode-ai/ui/context" -import type { PermissionRequest } from "@opencode-ai/sdk/v2/client" +import type { PermissionRequest } from "@kilocode/sdk/v2/client" import { Persist, persisted } from "@/utils/persist" import { useGlobalSDK } from "@/context/global-sdk" import { useGlobalSync } from "./global-sync" diff --git a/packages/app/src/context/sdk.tsx b/packages/app/src/context/sdk.tsx index bc97ea13ac..2eaf4627c0 100644 --- a/packages/app/src/context/sdk.tsx +++ b/packages/app/src/context/sdk.tsx @@ -1,4 +1,4 @@ -import type { Event } from "@opencode-ai/sdk/v2/client" +import type { Event } from "@kilocode/sdk/v2/client" import { createSimpleContext } from "@opencode-ai/ui/context" import { createGlobalEmitter } from "@solid-primitives/event-bus" import { type Accessor, createEffect, createMemo, onCleanup } from "solid-js" diff --git a/packages/app/src/context/sync-optimistic.test.ts b/packages/app/src/context/sync-optimistic.test.ts index 94324f8a06..3c1b8f4644 100644 --- a/packages/app/src/context/sync-optimistic.test.ts +++ b/packages/app/src/context/sync-optimistic.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import type { Message, Part } from "@opencode-ai/sdk/v2/client" +import type { Message, Part } from "@kilocode/sdk/v2/client" import { applyOptimisticAdd, applyOptimisticRemove, mergeOptimisticPage } from "./sync" type Text = Extract diff --git a/packages/app/src/context/sync.tsx b/packages/app/src/context/sync.tsx index 29b7fe68c5..3fb37e4b26 100644 --- a/packages/app/src/context/sync.tsx +++ b/packages/app/src/context/sync.tsx @@ -11,7 +11,7 @@ import { } from "./global-sync/session-prefetch" import { useGlobalSync } from "./global-sync" import { useSDK } from "./sdk" -import type { Message, Part } from "@opencode-ai/sdk/v2/client" +import type { Message, Part } from "@kilocode/sdk/v2/client" import { SESSION_CACHE_LIMIT, dropSessionCaches, pickSessionCacheEvictions } from "./global-sync/session-cache" import { diffs as list, message as clean } from "@/utils/diffs" diff --git a/packages/app/src/entry.tsx b/packages/app/src/entry.tsx index b5cbed6e75..84b2b799da 100644 --- a/packages/app/src/entry.tsx +++ b/packages/app/src/entry.tsx @@ -100,7 +100,7 @@ if (!(root instanceof HTMLElement) && import.meta.env.DEV) { const getCurrentUrl = () => { if (location.hostname.includes("opencode.ai")) return "http://localhost:4096" if (import.meta.env.DEV) - return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}` + return `http://${import.meta.env.VITE_KILO_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_KILO_SERVER_PORT ?? "4096"}` return location.origin } diff --git a/packages/app/src/env.d.ts b/packages/app/src/env.d.ts index 9b03d336f9..1197383a3c 100644 --- a/packages/app/src/env.d.ts +++ b/packages/app/src/env.d.ts @@ -1,7 +1,7 @@ interface ImportMetaEnv { - readonly VITE_OPENCODE_SERVER_HOST: string - readonly VITE_OPENCODE_SERVER_PORT: string - readonly VITE_OPENCODE_CHANNEL?: "dev" | "beta" | "prod" + readonly VITE_KILO_SERVER_HOST: string + readonly VITE_KILO_SERVER_PORT: string + readonly VITE_KILO_CHANNEL?: "dev" | "beta" | "prod" } interface ImportMeta { diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index 702210e4d3..6c0aa38e7a 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -105,7 +105,7 @@ export const dict = { "dialog.model.manage": "إدارة النماذج", "dialog.model.manage.description": "تخصيص النماذج التي تظهر في محدد النماذج.", "dialog.model.manage.provider.toggle": "تبديل جميع نماذج {{provider}}", - "dialog.model.unpaid.freeModels.title": "نماذج مجانية مقدمة من OpenCode", + "dialog.model.unpaid.freeModels.title": "نماذج مجانية مقدمة من Kilo", "dialog.model.unpaid.addMore.title": "إضافة المزيد من النماذج من موفرين مشهورين", "dialog.provider.viewAll": "عرض المزيد من الموفرين", "provider.connect.title": "اتصال {{provider}}", @@ -116,7 +116,7 @@ export const dict = { "provider.connect.status.waiting": "في انتظار التفويض...", "provider.connect.status.failed": "فشل التفويض: {{error}}", "provider.connect.apiKey.description": - "أدخل مفتاح واجهة برمجة تطبيقات {{provider}} الخاص بك لتوصيل حسابك واستخدام نماذج {{provider}} في OpenCode.", + "أدخل مفتاح واجهة برمجة تطبيقات {{provider}} الخاص بك لتوصيل حسابك واستخدام نماذج {{provider}} في Kilo.", "provider.connect.apiKey.label": "مفتاح واجهة برمجة تطبيقات {{provider}}", "provider.connect.apiKey.placeholder": "مفتاح API", "provider.connect.apiKey.required": "مفتاح API مطلوب", @@ -130,7 +130,7 @@ export const dict = { "provider.connect.oauth.code.visit.prefix": "قم بزيارة ", "provider.connect.oauth.code.visit.link": "هذا الرابط", "provider.connect.oauth.code.visit.suffix": - " للحصول على رمز التفويض الخاص بك لتوصيل حسابك واستخدام نماذج {{provider}} في OpenCode.", + " للحصول على رمز التفويض الخاص بك لتوصيل حسابك واستخدام نماذج {{provider}} في Kilo.", "provider.connect.oauth.code.label": "رمز تفويض {{method}}", "provider.connect.oauth.code.placeholder": "رمز التفويض", "provider.connect.oauth.code.required": "رمز التفويض مطلوب", @@ -138,7 +138,7 @@ export const dict = { "provider.connect.oauth.auto.visit.prefix": "قم بزيارة ", "provider.connect.oauth.auto.visit.link": "هذا الرابط", "provider.connect.oauth.auto.visit.suffix": - " وأدخل الرمز أدناه لتوصيل حسابك واستخدام نماذج {{provider}} في OpenCode.", + " وأدخل الرمز أدناه لتوصيل حسابك واستخدام نماذج {{provider}} في Kilo.", "provider.connect.oauth.auto.confirmationCode": "رمز التأكيد", "provider.connect.toast.connected.title": "تم توصيل {{provider}}", "provider.connect.toast.connected.description": "نماذج {{provider}} متاحة الآن للاستخدام.", @@ -281,7 +281,7 @@ export const dict = { "dialog.directory.search.placeholder": "البحث في المجلدات", "dialog.directory.empty": "لم يتم العثور على مجلدات", "dialog.server.title": "الخوادم", - "dialog.server.description": "تبديل خادم OpenCode الذي يتصل به هذا التطبيق.", + "dialog.server.description": "تبديل خادم Kilo الذي يتصل به هذا التطبيق.", "dialog.server.search.placeholder": "البحث في الخوادم", "dialog.server.empty": "لا توجد خوادم بعد", "dialog.server.add.title": "إضافة خادم", @@ -395,7 +395,7 @@ export const dict = { "toast.session.unshare.failed.description": "حدث خطأ أثناء إلغاء مشاركة الجلسة", "toast.session.listFailed.title": "فشل تحميل الجلسات لـ {{project}}", "toast.update.title": "تحديث متاح", - "toast.update.description": "نسخة جديدة من OpenCode ({{version}}) متاحة الآن للتثبيت.", + "toast.update.description": "نسخة جديدة من Kilo ({{version}}) متاحة الآن للتثبيت.", "toast.update.action.installRestart": "تثبيت وإعادة تشغيل", "toast.update.action.notYet": "ليس الآن", "error.page.title": "حدث خطأ ما", @@ -405,7 +405,7 @@ export const dict = { "error.page.action.checking": "جارٍ التحقق...", "error.page.action.checkUpdates": "التحقق من وجود تحديثات", "error.page.action.updateTo": "تحديث إلى {{version}}", - "error.page.report.prefix": "يرجى الإبلاغ عن هذا الخطأ لفريق OpenCode", + "error.page.report.prefix": "يرجى الإبلاغ عن هذا الخطأ لفريق Kilo", "error.page.report.discord": "على Discord", "error.page.version": "الإصدار: {{version}}", "error.dev.rootNotFound": @@ -421,7 +421,7 @@ export const dict = { "error.chain.didYouMean": "هل كنت تعني: {{suggestions}}", "error.chain.modelNotFound": "النموذج غير موجود: {{provider}}/{{model}}", "error.chain.checkConfig": "تحقق من أسماء الموفر/النموذج في التكوين (opencode.json)", - "error.chain.mcpFailed": 'فشل خادم MCP "{{name}}". لاحظ أن OpenCode لا يدعم مصادقة MCP بعد.', + "error.chain.mcpFailed": 'فشل خادم MCP "{{name}}". لاحظ أن Kilo لا يدعم مصادقة MCP بعد.', "error.chain.providerAuthFailed": "فشلت مصادقة الموفر ({{provider}}): {{message}}", "error.chain.providerInitFailed": 'فشل تهيئة الموفر "{{provider}}". تحقق من بيانات الاعتماد والتكوين.', "error.chain.configJsonInvalid": "ملف التكوين في {{path}} ليس JSON(C) صالحًا", @@ -537,19 +537,19 @@ export const dict = { "sidebar.workspaces.enable": "تمكين مساحات العمل", "sidebar.workspaces.disable": "تعطيل مساحات العمل", "sidebar.gettingStarted.title": "البدء", - "sidebar.gettingStarted.line1": "يتضمن OpenCode نماذج مجانية حتى تتمكن من البدء فورًا.", + "sidebar.gettingStarted.line1": "يتضمن Kilo نماذج مجانية حتى تتمكن من البدء فورًا.", "sidebar.gettingStarted.line2": "قم بتوصيل أي موفر لاستخدام النماذج، بما في ذلك Claude و GPT و Gemini وما إلى ذلك.", "sidebar.project.recentSessions": "الجلسات الحديثة", "sidebar.project.viewAllSessions": "عرض جميع الجلسات", "sidebar.project.clearNotifications": "مسح الإشعارات", - "app.name.desktop": "OpenCode Desktop", + "app.name.desktop": "Kilo Desktop", "settings.section.desktop": "سطح المكتب", "settings.section.server": "الخادم", "settings.tab.general": "عام", "settings.tab.shortcuts": "اختصارات", "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "تكامل WSL", - "settings.desktop.wsl.description": "تشغيل خادم OpenCode داخل WSL على Windows.", + "settings.desktop.wsl.description": "تشغيل خادم Kilo داخل WSL على Windows.", "settings.general.section.appearance": "المظهر", "settings.general.section.notifications": "إشعارات النظام", "settings.general.section.updates": "التحديثات", @@ -557,13 +557,13 @@ export const dict = { "settings.general.section.feed": "الخلاصة", "settings.general.section.display": "شاشة العرض", "settings.general.row.language.title": "اللغة", - "settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode", + "settings.general.row.language.description": "تغيير لغة العرض لـ Kilo", "settings.general.row.appearance.title": "المظهر", - "settings.general.row.appearance.description": "تخصيص كيفية ظهور OpenCode على جهازك", + "settings.general.row.appearance.description": "تخصيص كيفية ظهور Kilo على جهازك", "settings.general.row.colorScheme.title": "مخطط الألوان", - "settings.general.row.colorScheme.description": "اختر ما إذا كان OpenCode يتبع سمة النظام أو الفاتح أو الداكن", + "settings.general.row.colorScheme.description": "اختر ما إذا كان Kilo يتبع سمة النظام أو الفاتح أو الداكن", "settings.general.row.theme.title": "السمة", - "settings.general.row.theme.description": "تخصيص سمة OpenCode.", + "settings.general.row.theme.description": "تخصيص سمة Kilo.", "settings.general.row.font.title": "خط الكود", "settings.general.row.font.description": "خصّص الخط المستخدم في كتل التعليمات البرمجية", "settings.general.row.terminalFont.title": "Terminal Font", @@ -591,13 +591,13 @@ export const dict = { "settings.general.row.releaseNotes.title": "ملاحظات الإصدار", "settings.general.row.releaseNotes.description": 'عرض نوافذ "ما الجديد" المنبثقة بعد التحديثات', "settings.updates.row.startup.title": "التحقق من التحديثات عند بدء التشغيل", - "settings.updates.row.startup.description": "التحقق تلقائيًا من التحديثات عند تشغيل OpenCode", + "settings.updates.row.startup.description": "التحقق تلقائيًا من التحديثات عند تشغيل Kilo", "settings.updates.row.check.title": "التحقق من التحديثات", "settings.updates.row.check.description": "التحقق يدويًا من التحديثات وتثبيتها إذا كانت متاحة", "settings.updates.action.checkNow": "تحقق الآن", "settings.updates.action.checking": "جارٍ التحقق...", "settings.updates.toast.latest.title": "أنت على آخر إصدار", - "settings.updates.toast.latest.description": "أنت تستخدم أحدث إصدار من OpenCode.", + "settings.updates.toast.latest.description": "أنت تستخدم أحدث إصدار من Kilo.", "sound.option.none": "بلا", "sound.option.alert01": "تنبيه 01", "sound.option.alert02": "تنبيه 02", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index b414fff36e..101eb7ce83 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -105,7 +105,7 @@ export const dict = { "dialog.model.manage": "Gerenciar modelos", "dialog.model.manage.description": "Personalizar quais modelos aparecem no seletor de modelos.", "dialog.model.manage.provider.toggle": "Alternar todos os modelos {{provider}}", - "dialog.model.unpaid.freeModels.title": "Modelos gratuitos fornecidos pelo OpenCode", + "dialog.model.unpaid.freeModels.title": "Modelos gratuitos fornecidos pelo Kilo", "dialog.model.unpaid.addMore.title": "Adicionar mais modelos de provedores populares", "dialog.provider.viewAll": "Ver mais provedores", "provider.connect.title": "Conectar {{provider}}", @@ -116,7 +116,7 @@ export const dict = { "provider.connect.status.waiting": "Aguardando autorização...", "provider.connect.status.failed": "Autorização falhou: {{error}}", "provider.connect.apiKey.description": - "Digite sua chave de API do {{provider}} para conectar sua conta e usar modelos do {{provider}} no OpenCode.", + "Digite sua chave de API do {{provider}} para conectar sua conta e usar modelos do {{provider}} no Kilo.", "provider.connect.apiKey.label": "Chave de API do {{provider}}", "provider.connect.apiKey.placeholder": "Chave de API", "provider.connect.apiKey.required": "A chave de API é obrigatória", @@ -130,7 +130,7 @@ export const dict = { "provider.connect.oauth.code.visit.prefix": "Visite ", "provider.connect.oauth.code.visit.link": "este link", "provider.connect.oauth.code.visit.suffix": - " para obter seu código de autorização e conectar sua conta para usar modelos do {{provider}} no OpenCode.", + " para obter seu código de autorização e conectar sua conta para usar modelos do {{provider}} no Kilo.", "provider.connect.oauth.code.label": "Código de autorização {{method}}", "provider.connect.oauth.code.placeholder": "Código de autorização", "provider.connect.oauth.code.required": "O código de autorização é obrigatório", @@ -138,7 +138,7 @@ export const dict = { "provider.connect.oauth.auto.visit.prefix": "Visite ", "provider.connect.oauth.auto.visit.link": "este link", "provider.connect.oauth.auto.visit.suffix": - " e digite o código abaixo para conectar sua conta e usar modelos do {{provider}} no OpenCode.", + " e digite o código abaixo para conectar sua conta e usar modelos do {{provider}} no Kilo.", "provider.connect.oauth.auto.confirmationCode": "Código de confirmação", "provider.connect.toast.connected.title": "{{provider}} conectado", "provider.connect.toast.connected.description": "Modelos do {{provider}} agora estão disponíveis para uso.", @@ -281,7 +281,7 @@ export const dict = { "dialog.directory.search.placeholder": "Buscar pastas", "dialog.directory.empty": "Nenhuma pasta encontrada", "dialog.server.title": "Servidores", - "dialog.server.description": "Trocar para qual servidor OpenCode este aplicativo se conecta.", + "dialog.server.description": "Trocar para qual servidor Kilo este aplicativo se conecta.", "dialog.server.search.placeholder": "Buscar servidores", "dialog.server.empty": "Nenhum servidor ainda", "dialog.server.add.title": "Adicionar um servidor", @@ -396,7 +396,7 @@ export const dict = { "toast.session.unshare.failed.description": "Ocorreu um erro ao parar de compartilhar a sessão", "toast.session.listFailed.title": "Falha ao carregar sessões para {{project}}", "toast.update.title": "Atualização disponível", - "toast.update.description": "Uma nova versão do OpenCode ({{version}}) está disponível para instalação.", + "toast.update.description": "Uma nova versão do Kilo ({{version}}) está disponível para instalação.", "toast.update.action.installRestart": "Instalar e reiniciar", "toast.update.action.notYet": "Agora não", "error.page.title": "Algo deu errado", @@ -406,7 +406,7 @@ export const dict = { "error.page.action.checking": "Verificando...", "error.page.action.checkUpdates": "Verificar atualizações", "error.page.action.updateTo": "Atualizar para {{version}}", - "error.page.report.prefix": "Por favor, reporte este erro para a equipe do OpenCode", + "error.page.report.prefix": "Por favor, reporte este erro para a equipe do Kilo", "error.page.report.discord": "no Discord", "error.page.version": "Versão: {{version}}", "error.dev.rootNotFound": @@ -422,7 +422,7 @@ export const dict = { "error.chain.didYouMean": "Você quis dizer: {{suggestions}}", "error.chain.modelNotFound": "Modelo não encontrado: {{provider}}/{{model}}", "error.chain.checkConfig": "Verifique os nomes de provedor/modelo na sua configuração (opencode.json)", - "error.chain.mcpFailed": 'Servidor MCP "{{name}}" falhou. Nota: OpenCode ainda não suporta autenticação MCP.', + "error.chain.mcpFailed": 'Servidor MCP "{{name}}" falhou. Nota: Kilo ainda não suporta autenticação MCP.', "error.chain.providerAuthFailed": "Autenticação do provedor falhou ({{provider}}): {{message}}", "error.chain.providerInitFailed": 'Falha ao inicializar provedor "{{provider}}". Verifique credenciais e configuração.', @@ -544,19 +544,19 @@ export const dict = { "sidebar.workspaces.enable": "Habilitar espaços de trabalho", "sidebar.workspaces.disable": "Desabilitar espaços de trabalho", "sidebar.gettingStarted.title": "Começando", - "sidebar.gettingStarted.line1": "OpenCode inclui modelos gratuitos para você começar imediatamente.", + "sidebar.gettingStarted.line1": "Kilo inclui modelos gratuitos para você começar imediatamente.", "sidebar.gettingStarted.line2": "Conecte qualquer provedor para usar modelos, incluindo Claude, GPT, Gemini etc.", "sidebar.project.recentSessions": "Sessões recentes", "sidebar.project.viewAllSessions": "Ver todas as sessões", "sidebar.project.clearNotifications": "Limpar notificações", - "app.name.desktop": "OpenCode Desktop", + "app.name.desktop": "Kilo Desktop", "settings.section.desktop": "Desktop", "settings.section.server": "Servidor", "settings.tab.general": "Geral", "settings.tab.shortcuts": "Atalhos", "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "WSL integration", - "settings.desktop.wsl.description": "Executar o servidor OpenCode dentro do WSL no Windows.", + "settings.desktop.wsl.description": "Executar o servidor Kilo dentro do WSL no Windows.", "settings.general.section.appearance": "Aparência", "settings.general.section.notifications": "Notificações do sistema", "settings.general.section.updates": "Atualizações", @@ -564,13 +564,13 @@ export const dict = { "settings.general.section.feed": "Feed", "settings.general.section.display": "Tela", "settings.general.row.language.title": "Idioma", - "settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode", + "settings.general.row.language.description": "Alterar o idioma de exibição do Kilo", "settings.general.row.appearance.title": "Aparência", - "settings.general.row.appearance.description": "Personalize como o OpenCode aparece no seu dispositivo", + "settings.general.row.appearance.description": "Personalize como o Kilo aparece no seu dispositivo", "settings.general.row.colorScheme.title": "Esquema de cores", - "settings.general.row.colorScheme.description": "Escolha se o OpenCode segue o tema do sistema, claro ou escuro", + "settings.general.row.colorScheme.description": "Escolha se o Kilo segue o tema do sistema, claro ou escuro", "settings.general.row.theme.title": "Tema", - "settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.", + "settings.general.row.theme.description": "Personalize como o Kilo é tematizado.", "settings.general.row.font.title": "Fonte de código", "settings.general.row.font.description": "Personalize a fonte usada em blocos de código", "settings.general.row.terminalFont.title": "Terminal Font", @@ -600,13 +600,13 @@ export const dict = { "settings.general.row.releaseNotes.title": "Notas da versão", "settings.general.row.releaseNotes.description": 'Mostrar pop-ups de "Novidades" após atualizações', "settings.updates.row.startup.title": "Verificar atualizações ao iniciar", - "settings.updates.row.startup.description": "Verificar atualizações automaticamente quando o OpenCode iniciar", + "settings.updates.row.startup.description": "Verificar atualizações automaticamente quando o Kilo iniciar", "settings.updates.row.check.title": "Verificar atualizações", "settings.updates.row.check.description": "Verificar atualizações manualmente e instalar se houver", "settings.updates.action.checkNow": "Verificar agora", "settings.updates.action.checking": "Verificando...", "settings.updates.toast.latest.title": "Você está atualizado", - "settings.updates.toast.latest.description": "Você está usando a versão mais recente do OpenCode.", + "settings.updates.toast.latest.description": "Você está usando a versão mais recente do Kilo.", "sound.option.none": "Nenhum", "sound.option.alert01": "Alerta 01", "sound.option.alert02": "Alerta 02", diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts index e316f87da2..a52b04c437 100644 --- a/packages/app/src/i18n/bs.ts +++ b/packages/app/src/i18n/bs.ts @@ -115,7 +115,7 @@ export const dict = { "dialog.model.manage.description": "Prilagodi koji se modeli prikazuju u izborniku modela.", "dialog.model.manage.provider.toggle": "Uključi/isključi sve {{provider}} modele", - "dialog.model.unpaid.freeModels.title": "Besplatni modeli koje obezbjeđuje OpenCode", + "dialog.model.unpaid.freeModels.title": "Besplatni modeli koje obezbjeđuje Kilo", "dialog.model.unpaid.addMore.title": "Dodaj još modela od popularnih provajdera", "dialog.provider.viewAll": "Prikaži više provajdera", @@ -128,7 +128,7 @@ export const dict = { "provider.connect.status.waiting": "Čekanje na autorizaciju...", "provider.connect.status.failed": "Autorizacija nije uspjela: {{error}}", "provider.connect.apiKey.description": - "Unesi svoj {{provider}} API ključ da povežeš račun i koristiš {{provider}} modele u OpenCode-u.", + "Unesi svoj {{provider}} API ključ da povežeš račun i koristiš {{provider}} modele u Kilo-u.", "provider.connect.apiKey.label": "{{provider}} API ključ", "provider.connect.apiKey.placeholder": "API ključ", "provider.connect.apiKey.required": "API ključ je obavezan", @@ -142,7 +142,7 @@ export const dict = { "provider.connect.oauth.code.visit.prefix": "Posjeti ", "provider.connect.oauth.code.visit.link": "ovaj link", "provider.connect.oauth.code.visit.suffix": - " da preuzmeš autorizacijski kod i povežeš račun te koristiš {{provider}} modele u OpenCode-u.", + " da preuzmeš autorizacijski kod i povežeš račun te koristiš {{provider}} modele u Kilo-u.", "provider.connect.oauth.code.label": "{{method}} autorizacijski kod", "provider.connect.oauth.code.placeholder": "Autorizacijski kod", "provider.connect.oauth.code.required": "Autorizacijski kod je obavezan", @@ -150,7 +150,7 @@ export const dict = { "provider.connect.oauth.auto.visit.prefix": "Posjeti ", "provider.connect.oauth.auto.visit.link": "ovaj link", "provider.connect.oauth.auto.visit.suffix": - " i unesi kod ispod da povežeš račun i koristiš {{provider}} modele u OpenCode-u.", + " i unesi kod ispod da povežeš račun i koristiš {{provider}} modele u Kilo-u.", "provider.connect.oauth.auto.confirmationCode": "Kod za potvrdu", "provider.connect.toast.connected.title": "{{provider}} povezan", "provider.connect.toast.connected.description": "{{provider}} modeli su sada dostupni za korištenje.", @@ -308,7 +308,7 @@ export const dict = { "dialog.directory.empty": "Nema pronađenih foldera", "dialog.server.title": "Serveri", - "dialog.server.description": "Promijeni na koji se OpenCode server ova aplikacija povezuje.", + "dialog.server.description": "Promijeni na koji se Kilo server ova aplikacija povezuje.", "dialog.server.search.placeholder": "Pretraži servere", "dialog.server.empty": "Još nema servera", "dialog.server.add.title": "Dodaj server", @@ -441,7 +441,7 @@ export const dict = { "toast.session.listFailed.title": "Neuspjelo učitavanje sesija za {{project}}", "toast.update.title": "Dostupno ažuriranje", - "toast.update.description": "Nova verzija OpenCode-a ({{version}}) je dostupna za instalaciju.", + "toast.update.description": "Nova verzija Kilo-a ({{version}}) je dostupna za instalaciju.", "toast.update.action.installRestart": "Instaliraj i restartuj", "toast.update.action.notYet": "Ne još", @@ -452,7 +452,7 @@ export const dict = { "error.page.action.checking": "Provjera...", "error.page.action.checkUpdates": "Provjeri ažuriranja", "error.page.action.updateTo": "Ažuriraj na {{version}}", - "error.page.report.prefix": "Molimo prijavi ovu grešku OpenCode timu", + "error.page.report.prefix": "Molimo prijavi ovu grešku Kilo timu", "error.page.report.discord": "na Discordu", "error.page.version": "Verzija: {{version}}", @@ -471,7 +471,7 @@ export const dict = { "error.chain.didYouMean": "Da li si mislio: {{suggestions}}", "error.chain.modelNotFound": "Model nije pronađen: {{provider}}/{{model}}", "error.chain.checkConfig": "Provjeri konfiguraciju (opencode.json) provider/model names", - "error.chain.mcpFailed": 'MCP server "{{name}}" nije uspio. Napomena: OpenCode još ne podržava MCP autentifikaciju.', + "error.chain.mcpFailed": 'MCP server "{{name}}" nije uspio. Napomena: Kilo još ne podržava MCP autentifikaciju.', "error.chain.providerAuthFailed": "Autentifikacija provajdera nije uspjela ({{provider}}): {{message}}", "error.chain.providerInitFailed": 'Neuspjelo inicijalizovanje provajdera "{{provider}}". Provjeri kredencijale i konfiguraciju.', @@ -605,13 +605,13 @@ export const dict = { "sidebar.workspaces.enable": "Omogući radne prostore", "sidebar.workspaces.disable": "Onemogući radne prostore", "sidebar.gettingStarted.title": "Početak", - "sidebar.gettingStarted.line1": "OpenCode uključuje besplatne modele, tako da možeš odmah početi.", + "sidebar.gettingStarted.line1": "Kilo uključuje besplatne modele, tako da možeš odmah početi.", "sidebar.gettingStarted.line2": "Poveži bilo kojeg provajdera da koristiš modele, npr. Claude, GPT, Gemini itd.", "sidebar.project.recentSessions": "Nedavne sesije", "sidebar.project.viewAllSessions": "Prikaži sve sesije", "sidebar.project.clearNotifications": "Očisti obavijesti", - "app.name.desktop": "OpenCode Desktop", + "app.name.desktop": "Kilo Desktop", "settings.section.desktop": "Desktop", "settings.section.server": "Server", @@ -619,7 +619,7 @@ export const dict = { "settings.tab.shortcuts": "Prečice", "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "WSL integracija", - "settings.desktop.wsl.description": "Pokreni OpenCode server unutar WSL-a na Windowsu.", + "settings.desktop.wsl.description": "Pokreni Kilo server unutar WSL-a na Windowsu.", "settings.general.section.appearance": "Izgled", "settings.general.section.notifications": "Sistemske obavijesti", @@ -629,13 +629,13 @@ export const dict = { "settings.general.section.display": "Prikaz", "settings.general.row.language.title": "Jezik", - "settings.general.row.language.description": "Promijeni jezik prikaza u OpenCode-u", + "settings.general.row.language.description": "Promijeni jezik prikaza u Kilo-u", "settings.general.row.appearance.title": "Izgled", - "settings.general.row.appearance.description": "Prilagodi kako OpenCode izgleda na tvom uređaju", + "settings.general.row.appearance.description": "Prilagodi kako Kilo izgleda na tvom uređaju", "settings.general.row.colorScheme.title": "Šema boja", - "settings.general.row.colorScheme.description": "Odaberi da li OpenCode prati sistemsku, svijetlu ili tamnu temu", + "settings.general.row.colorScheme.description": "Odaberi da li Kilo prati sistemsku, svijetlu ili tamnu temu", "settings.general.row.theme.title": "Tema", - "settings.general.row.theme.description": "Prilagodi temu OpenCode-a.", + "settings.general.row.theme.description": "Prilagodi temu Kilo-a.", "settings.general.row.font.title": "Font za kod", "settings.general.row.font.description": "Prilagodi font koji se koristi u blokovima koda", "settings.general.row.terminalFont.title": "Terminal Font", @@ -667,13 +667,13 @@ export const dict = { "settings.general.row.releaseNotes.description": 'Prikaži iskačuće prozore "Šta je novo" nakon ažuriranja', "settings.updates.row.startup.title": "Provjeri ažuriranja pri pokretanju", - "settings.updates.row.startup.description": "Automatski provjerava ažuriranja kada se OpenCode pokrene", + "settings.updates.row.startup.description": "Automatski provjerava ažuriranja kada se Kilo pokrene", "settings.updates.row.check.title": "Provjeri ažuriranja", "settings.updates.row.check.description": "Ručno provjeri ažuriranja i instaliraj ako su dostupna", "settings.updates.action.checkNow": "Provjeri sada", "settings.updates.action.checking": "Provjera...", "settings.updates.toast.latest.title": "Sve je ažurno", - "settings.updates.toast.latest.description": "Koristiš najnoviju verziju OpenCode-a.", + "settings.updates.toast.latest.description": "Koristiš najnoviju verziju Kilo-a.", "sound.option.none": "Nijedan", "sound.option.alert01": "Upozorenje 01", "sound.option.alert02": "Upozorenje 02", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index d368f292d2..a281c96e42 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -115,7 +115,7 @@ export const dict = { "dialog.model.manage.description": "Tilpas hvilke modeller der vises i modelvælgeren.", "dialog.model.manage.provider.toggle": "Skift alle {{provider}}-modeller", - "dialog.model.unpaid.freeModels.title": "Gratis modeller leveret af OpenCode", + "dialog.model.unpaid.freeModels.title": "Gratis modeller leveret af Kilo", "dialog.model.unpaid.addMore.title": "Tilføj flere modeller fra populære udbydere", "dialog.provider.viewAll": "Vis flere udbydere", @@ -128,7 +128,7 @@ export const dict = { "provider.connect.status.waiting": "Venter på godkendelse...", "provider.connect.status.failed": "Godkendelse mislykkedes: {{error}}", "provider.connect.apiKey.description": - "Indtast din {{provider}} API-nøgle for at forbinde din konto og bruge {{provider}} modeller i OpenCode.", + "Indtast din {{provider}} API-nøgle for at forbinde din konto og bruge {{provider}} modeller i Kilo.", "provider.connect.apiKey.label": "{{provider}} API-nøgle", "provider.connect.apiKey.placeholder": "API-nøgle", "provider.connect.apiKey.required": "API-nøgle er påkrævet", @@ -142,7 +142,7 @@ export const dict = { "provider.connect.oauth.code.visit.prefix": "Besøg ", "provider.connect.oauth.code.visit.link": "dette link", "provider.connect.oauth.code.visit.suffix": - " for at hente din godkendelseskode for at forbinde din konto og bruge {{provider}} modeller i OpenCode.", + " for at hente din godkendelseskode for at forbinde din konto og bruge {{provider}} modeller i Kilo.", "provider.connect.oauth.code.label": "{{method}} godkendelseskode", "provider.connect.oauth.code.placeholder": "Godkendelseskode", "provider.connect.oauth.code.required": "Godkendelseskode er påkrævet", @@ -150,7 +150,7 @@ export const dict = { "provider.connect.oauth.auto.visit.prefix": "Besøg ", "provider.connect.oauth.auto.visit.link": "dette link", "provider.connect.oauth.auto.visit.suffix": - " og indtast koden nedenfor for at forbinde din konto og bruge {{provider}} modeller i OpenCode.", + " og indtast koden nedenfor for at forbinde din konto og bruge {{provider}} modeller i Kilo.", "provider.connect.oauth.auto.confirmationCode": "Bekræftelseskode", "provider.connect.toast.connected.title": "{{provider}} forbundet", "provider.connect.toast.connected.description": "{{provider}} modeller er nu tilgængelige.", @@ -306,7 +306,7 @@ export const dict = { "dialog.directory.empty": "Ingen mapper fundet", "dialog.server.title": "Servere", - "dialog.server.description": "Skift hvilken OpenCode-server denne app forbinder til.", + "dialog.server.description": "Skift hvilken Kilo-server denne app forbinder til.", "dialog.server.search.placeholder": "Søg servere", "dialog.server.empty": "Ingen servere endnu", "dialog.server.add.title": "Tilføj en server", @@ -438,7 +438,7 @@ export const dict = { "toast.session.listFailed.title": "Kunne ikke indlæse sessioner for {{project}}", "toast.update.title": "Opdatering tilgængelig", - "toast.update.description": "En ny version af OpenCode ({{version}}) er nu tilgængelig til installation.", + "toast.update.description": "En ny version af Kilo ({{version}}) er nu tilgængelig til installation.", "toast.update.action.installRestart": "Installer og genstart", "toast.update.action.notYet": "Ikke endnu", @@ -449,7 +449,7 @@ export const dict = { "error.page.action.checking": "Tjekker...", "error.page.action.checkUpdates": "Tjek for opdateringer", "error.page.action.updateTo": "Opdater til {{version}}", - "error.page.report.prefix": "Rapporter venligst denne fejl til OpenCode-teamet", + "error.page.report.prefix": "Rapporter venligst denne fejl til Kilo-teamet", "error.page.report.discord": "på Discord", "error.page.version": "Version: {{version}}", @@ -468,7 +468,7 @@ export const dict = { "error.chain.didYouMean": "Mente du: {{suggestions}}", "error.chain.modelNotFound": "Model ikke fundet: {{provider}}/{{model}}", "error.chain.checkConfig": "Tjek dine konfigurations (opencode.json) udbyder/modelnavne", - "error.chain.mcpFailed": 'MCP-server "{{name}}" fejlede. Bemærk, OpenCode understøtter ikke MCP-godkendelse endnu.', + "error.chain.mcpFailed": 'MCP-server "{{name}}" fejlede. Bemærk, Kilo understøtter ikke MCP-godkendelse endnu.', "error.chain.providerAuthFailed": "Udbydergodkendelse mislykkedes ({{provider}}): {{message}}", "error.chain.providerInitFailed": 'Kunne ikke initialisere udbyder "{{provider}}". Tjek legitimationsoplysninger og konfiguration.', @@ -601,20 +601,20 @@ export const dict = { "sidebar.workspaces.enable": "Aktiver arbejdsområder", "sidebar.workspaces.disable": "Deaktiver arbejdsområder", "sidebar.gettingStarted.title": "Kom i gang", - "sidebar.gettingStarted.line1": "OpenCode inkluderer gratis modeller så du kan starte med det samme.", + "sidebar.gettingStarted.line1": "Kilo inkluderer gratis modeller så du kan starte med det samme.", "sidebar.gettingStarted.line2": "Forbind enhver udbyder for at bruge modeller, inkl. Claude, GPT, Gemini osv.", "sidebar.project.recentSessions": "Seneste sessioner", "sidebar.project.viewAllSessions": "Vis alle sessioner", "sidebar.project.clearNotifications": "Ryd notifikationer", - "app.name.desktop": "OpenCode Desktop", + "app.name.desktop": "Kilo Desktop", "settings.section.desktop": "Desktop", "settings.section.server": "Server", "settings.tab.general": "Generelt", "settings.tab.shortcuts": "Genveje", "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "WSL integration", - "settings.desktop.wsl.description": "Kør OpenCode-serveren inde i WSL på Windows.", + "settings.desktop.wsl.description": "Kør Kilo-serveren inde i WSL på Windows.", "settings.general.section.appearance": "Udseende", "settings.general.section.notifications": "Systemmeddelelser", @@ -624,13 +624,13 @@ export const dict = { "settings.general.section.display": "Skærm", "settings.general.row.language.title": "Sprog", - "settings.general.row.language.description": "Ændr visningssproget for OpenCode", + "settings.general.row.language.description": "Ændr visningssproget for Kilo", "settings.general.row.appearance.title": "Udseende", - "settings.general.row.appearance.description": "Tilpas hvordan OpenCode ser ud på din enhed", + "settings.general.row.appearance.description": "Tilpas hvordan Kilo ser ud på din enhed", "settings.general.row.colorScheme.title": "Farveskema", - "settings.general.row.colorScheme.description": "Vælg om OpenCode følger systemets, lyst eller mørkt tema", + "settings.general.row.colorScheme.description": "Vælg om Kilo følger systemets, lyst eller mørkt tema", "settings.general.row.theme.title": "Tema", - "settings.general.row.theme.description": "Tilpas hvordan OpenCode er temabestemt.", + "settings.general.row.theme.description": "Tilpas hvordan Kilo er temabestemt.", "settings.general.row.font.title": "Kode-skrifttype", "settings.general.row.font.description": "Tilpas skrifttypen, der bruges i kodeblokke", "settings.general.row.terminalFont.title": "Terminal Font", @@ -661,13 +661,13 @@ export const dict = { "settings.general.row.releaseNotes.description": 'Vis "Hvad er nyt"-popups efter opdateringer', "settings.updates.row.startup.title": "Tjek for opdateringer ved opstart", - "settings.updates.row.startup.description": "Tjek automatisk for opdateringer, når OpenCode starter", + "settings.updates.row.startup.description": "Tjek automatisk for opdateringer, når Kilo starter", "settings.updates.row.check.title": "Tjek for opdateringer", "settings.updates.row.check.description": "Tjek manuelt for opdateringer og installer, hvis tilgængelig", "settings.updates.action.checkNow": "Tjek nu", "settings.updates.action.checking": "Tjekker...", "settings.updates.toast.latest.title": "Du er opdateret", - "settings.updates.toast.latest.description": "Du kører den nyeste version af OpenCode.", + "settings.updates.toast.latest.description": "Du kører den nyeste version af Kilo.", "sound.option.none": "Ingen", "sound.option.alert01": "Alarm 01", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index a2b049c880..5575b886c3 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -109,7 +109,7 @@ export const dict = { "dialog.model.manage": "Modelle verwalten", "dialog.model.manage.description": "Anpassen, welche Modelle in der Modellauswahl erscheinen.", "dialog.model.manage.provider.toggle": "Alle {{provider}}-Modelle umschalten", - "dialog.model.unpaid.freeModels.title": "Kostenlose Modelle von OpenCode", + "dialog.model.unpaid.freeModels.title": "Kostenlose Modelle von Kilo", "dialog.model.unpaid.addMore.title": "Weitere Modelle von beliebten Anbietern hinzufügen", "dialog.provider.viewAll": "Mehr Anbieter anzeigen", "provider.connect.title": "{{provider}} verbinden", @@ -120,7 +120,7 @@ export const dict = { "provider.connect.status.waiting": "Warten auf Autorisierung...", "provider.connect.status.failed": "Autorisierung fehlgeschlagen: {{error}}", "provider.connect.apiKey.description": - "Geben Sie Ihren {{provider}} API-Schlüssel ein, um Ihr Konto zu verbinden und {{provider}} Modelle in OpenCode zu nutzen.", + "Geben Sie Ihren {{provider}} API-Schlüssel ein, um Ihr Konto zu verbinden und {{provider}} Modelle in Kilo zu nutzen.", "provider.connect.apiKey.label": "{{provider}} API-Schlüssel", "provider.connect.apiKey.placeholder": "API-Schlüssel", "provider.connect.apiKey.required": "API-Schlüssel ist erforderlich", @@ -134,7 +134,7 @@ export const dict = { "provider.connect.oauth.code.visit.prefix": "Besuchen Sie ", "provider.connect.oauth.code.visit.link": "diesen Link", "provider.connect.oauth.code.visit.suffix": - ", um Ihren Autorisierungscode zu erhalten, Ihr Konto zu verbinden und {{provider}} Modelle in OpenCode zu nutzen.", + ", um Ihren Autorisierungscode zu erhalten, Ihr Konto zu verbinden und {{provider}} Modelle in Kilo zu nutzen.", "provider.connect.oauth.code.label": "{{method}} Autorisierungscode", "provider.connect.oauth.code.placeholder": "Autorisierungscode", "provider.connect.oauth.code.required": "Autorisierungscode ist erforderlich", @@ -142,7 +142,7 @@ export const dict = { "provider.connect.oauth.auto.visit.prefix": "Besuchen Sie ", "provider.connect.oauth.auto.visit.link": "diesen Link", "provider.connect.oauth.auto.visit.suffix": - " und geben Sie den untenstehenden Code ein, um Ihr Konto zu verbinden und {{provider}} Modelle in OpenCode zu nutzen.", + " und geben Sie den untenstehenden Code ein, um Ihr Konto zu verbinden und {{provider}} Modelle in Kilo zu nutzen.", "provider.connect.oauth.auto.confirmationCode": "Bestätigungscode", "provider.connect.toast.connected.title": "{{provider}} verbunden", "provider.connect.toast.connected.description": "{{provider}} Modelle sind jetzt verfügbar.", @@ -287,7 +287,7 @@ export const dict = { "dialog.directory.search.placeholder": "Ordner durchsuchen", "dialog.directory.empty": "Keine Ordner gefunden", "dialog.server.title": "Server", - "dialog.server.description": "Wechseln Sie den OpenCode-Server, mit dem sich diese App verbindet.", + "dialog.server.description": "Wechseln Sie den Kilo-Server, mit dem sich diese App verbindet.", "dialog.server.search.placeholder": "Server durchsuchen", "dialog.server.empty": "Noch keine Server", "dialog.server.add.title": "Server hinzufügen", @@ -403,7 +403,7 @@ export const dict = { "toast.session.unshare.failed.description": "Beim Aufheben des Teilens ist ein Fehler aufgetreten", "toast.session.listFailed.title": "Sitzungen für {{project}} konnten nicht geladen werden", "toast.update.title": "Update verfügbar", - "toast.update.description": "Eine neue Version von OpenCode ({{version}}) ist zur Installation verfügbar.", + "toast.update.description": "Eine neue Version von Kilo ({{version}}) ist zur Installation verfügbar.", "toast.update.action.installRestart": "Installieren und neu starten", "toast.update.action.notYet": "Noch nicht", "error.page.title": "Etwas ist schiefgelaufen", @@ -413,7 +413,7 @@ export const dict = { "error.page.action.checking": "Prüfen...", "error.page.action.checkUpdates": "Nach Updates suchen", "error.page.action.updateTo": "Auf {{version}} aktualisieren", - "error.page.report.prefix": "Bitte melden Sie diesen Fehler dem OpenCode-Team", + "error.page.report.prefix": "Bitte melden Sie diesen Fehler dem Kilo-Team", "error.page.report.discord": "auf Discord", "error.page.version": "Version: {{version}}", "error.dev.rootNotFound": @@ -430,7 +430,7 @@ export const dict = { "error.chain.modelNotFound": "Modell nicht gefunden: {{provider}}/{{model}}", "error.chain.checkConfig": "Überprüfen Sie Ihre Konfiguration (opencode.json) auf Anbieter-/Modellnamen", "error.chain.mcpFailed": - 'MCP-Server "{{name}}" fehlgeschlagen. Hinweis: OpenCode unterstützt noch keine MCP-Authentifizierung.', + 'MCP-Server "{{name}}" fehlgeschlagen. Hinweis: Kilo unterstützt noch keine MCP-Authentifizierung.', "error.chain.providerAuthFailed": "Anbieter-Authentifizierung fehlgeschlagen ({{provider}}): {{message}}", "error.chain.providerInitFailed": 'Anbieter "{{provider}}" konnte nicht initialisiert werden. Überprüfen Sie Anmeldeinformationen und Konfiguration.', @@ -552,20 +552,20 @@ export const dict = { "sidebar.workspaces.enable": "Arbeitsbereiche aktivieren", "sidebar.workspaces.disable": "Arbeitsbereiche deaktivieren", "sidebar.gettingStarted.title": "Erste Schritte", - "sidebar.gettingStarted.line1": "OpenCode enthält kostenlose Modelle, damit Sie sofort loslegen können.", + "sidebar.gettingStarted.line1": "Kilo enthält kostenlose Modelle, damit Sie sofort loslegen können.", "sidebar.gettingStarted.line2": "Verbinden Sie einen beliebigen Anbieter, um Modelle wie Claude, GPT, Gemini usw. zu nutzen.", "sidebar.project.recentSessions": "Letzte Sitzungen", "sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen", "sidebar.project.clearNotifications": "Benachrichtigungen löschen", - "app.name.desktop": "OpenCode Desktop", + "app.name.desktop": "Kilo Desktop", "settings.section.desktop": "Desktop", "settings.section.server": "Server", "settings.tab.general": "Allgemein", "settings.tab.shortcuts": "Tastenkombinationen", "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "WSL-Integration", - "settings.desktop.wsl.description": "OpenCode-Server innerhalb von WSL unter Windows ausführen.", + "settings.desktop.wsl.description": "Kilo-Server innerhalb von WSL unter Windows ausführen.", "settings.general.section.appearance": "Erscheinungsbild", "settings.general.section.notifications": "Systembenachrichtigungen", "settings.general.section.updates": "Updates", @@ -573,14 +573,14 @@ export const dict = { "settings.general.section.feed": "Feed", "settings.general.section.display": "Anzeige", "settings.general.row.language.title": "Sprache", - "settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern", + "settings.general.row.language.description": "Die Anzeigesprache für Kilo ändern", "settings.general.row.appearance.title": "Erscheinungsbild", - "settings.general.row.appearance.description": "Anpassen, wie OpenCode auf Ihrem Gerät aussieht", + "settings.general.row.appearance.description": "Anpassen, wie Kilo auf Ihrem Gerät aussieht", "settings.general.row.colorScheme.title": "Farbschema", "settings.general.row.colorScheme.description": - "Wählen Sie, ob OpenCode dem System-, hellen oder dunklen Thema folgt", + "Wählen Sie, ob Kilo dem System-, hellen oder dunklen Thema folgt", "settings.general.row.theme.title": "Thema", - "settings.general.row.theme.description": "Das Thema von OpenCode anpassen.", + "settings.general.row.theme.description": "Das Thema von Kilo anpassen.", "settings.general.row.font.title": "Code-Schriftart", "settings.general.row.font.description": "Die in Codeblöcken verwendete Schriftart anpassen", "settings.general.row.terminalFont.title": "Terminal Font", @@ -611,13 +611,13 @@ export const dict = { "settings.general.row.releaseNotes.title": "Versionshinweise", "settings.general.row.releaseNotes.description": '"Neuigkeiten"-Pop-ups nach Updates anzeigen', "settings.updates.row.startup.title": "Beim Start nach Updates suchen", - "settings.updates.row.startup.description": "Beim Start von OpenCode automatisch nach Updates suchen", + "settings.updates.row.startup.description": "Beim Start von Kilo automatisch nach Updates suchen", "settings.updates.row.check.title": "Nach Updates suchen", "settings.updates.row.check.description": "Manuell nach Updates suchen und installieren, wenn verfügbar", "settings.updates.action.checkNow": "Jetzt prüfen", "settings.updates.action.checking": "Wird geprüft...", "settings.updates.toast.latest.title": "Du bist auf dem neuesten Stand", - "settings.updates.toast.latest.description": "Du verwendest die aktuelle Version von OpenCode.", + "settings.updates.toast.latest.description": "Du verwendest die aktuelle Version von Kilo.", "sound.option.none": "Keine", "sound.option.alert01": "Alarm 01", "sound.option.alert02": "Alarm 02", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 7326f7c8bb..6cde974188 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -117,7 +117,7 @@ export const dict = { "dialog.model.manage.description": "Customize which models appear in the model selector.", "dialog.model.manage.provider.toggle": "Toggle all {{provider}} models", - "dialog.model.unpaid.freeModels.title": "Free models provided by OpenCode", + "dialog.model.unpaid.freeModels.title": "Free models provided by Kilo", "dialog.model.unpaid.addMore.title": "Add more models from popular providers", "dialog.provider.viewAll": "Show more providers", @@ -130,7 +130,7 @@ export const dict = { "provider.connect.status.waiting": "Waiting for authorization...", "provider.connect.status.failed": "Authorization failed: {{error}}", "provider.connect.apiKey.description": - "Enter your {{provider}} API key to connect your account and use {{provider}} models in OpenCode.", + "Enter your {{provider}} API key to connect your account and use {{provider}} models in Kilo.", "provider.connect.apiKey.label": "{{provider}} API key", "provider.connect.apiKey.placeholder": "API key", "provider.connect.apiKey.required": "API key is required", @@ -144,7 +144,7 @@ export const dict = { "provider.connect.oauth.code.visit.prefix": "Visit ", "provider.connect.oauth.code.visit.link": "this link", "provider.connect.oauth.code.visit.suffix": - " to collect your authorization code to connect your account and use {{provider}} models in OpenCode.", + " to collect your authorization code to connect your account and use {{provider}} models in Kilo.", "provider.connect.oauth.code.label": "{{method}} authorization code", "provider.connect.oauth.code.placeholder": "Authorization code", "provider.connect.oauth.code.required": "Authorization code is required", @@ -152,7 +152,7 @@ export const dict = { "provider.connect.oauth.auto.visit.prefix": "Visit ", "provider.connect.oauth.auto.visit.link": "this link", "provider.connect.oauth.auto.visit.suffix": - " and enter the code below to connect your account and use {{provider}} models in OpenCode.", + " and enter the code below to connect your account and use {{provider}} models in Kilo.", "provider.connect.oauth.auto.confirmationCode": "Confirmation code", "provider.connect.toast.connected.title": "{{provider}} connected", "provider.connect.toast.connected.description": "{{provider}} models are now available to use.", @@ -316,7 +316,7 @@ export const dict = { "app.server.otherServers": "Other servers", "dialog.server.title": "Servers", - "dialog.server.description": "Switch which OpenCode server this app connects to.", + "dialog.server.description": "Switch which Kilo server this app connects to.", "dialog.server.search.placeholder": "Search servers", "dialog.server.empty": "No servers yet", "dialog.server.add.title": "Add server", @@ -457,7 +457,7 @@ export const dict = { "toast.project.reloadFailed.title": "Failed to reload {{project}}", "toast.update.title": "Update available", - "toast.update.description": "A new version of OpenCode ({{version}}) is now available to install.", + "toast.update.description": "A new version of Kilo ({{version}}) is now available to install.", "toast.update.action.installRestart": "Install and restart", "toast.update.action.notYet": "Not yet", @@ -469,7 +469,7 @@ export const dict = { "error.page.action.checkUpdates": "Check for updates", "error.page.action.updateTo": "Update to {{version}}", "error.page.circular": "[Circular]", - "error.page.report.prefix": "Please report this error to the OpenCode team", + "error.page.report.prefix": "Please report this error to the Kilo team", "error.page.report.discord": "on Discord", "error.page.version": "Version: {{version}}", @@ -495,7 +495,7 @@ export const dict = { "error.chain.didYouMean": "Did you mean: {{suggestions}}", "error.chain.modelNotFound": "Model not found: {{provider}}/{{model}}", "error.chain.checkConfig": "Check your config (opencode.json) provider/model names", - "error.chain.mcpFailed": 'MCP server "{{name}}" failed. Note, OpenCode does not support MCP authentication yet.', + "error.chain.mcpFailed": 'MCP server "{{name}}" failed. Note, Kilo does not support MCP authentication yet.', "error.chain.providerAuthFailed": "Provider authentication failed ({{provider}}): {{message}}", "error.chain.providerInitFailed": 'Failed to initialize provider "{{provider}}". Check credentials and configuration.', @@ -676,7 +676,7 @@ export const dict = { "sidebar.workspaces.enable": "Enable workspaces", "sidebar.workspaces.disable": "Disable workspaces", "sidebar.gettingStarted.title": "Getting started", - "sidebar.gettingStarted.line1": "OpenCode includes free models so you can start immediately.", + "sidebar.gettingStarted.line1": "Kilo includes free models so you can start immediately.", "sidebar.gettingStarted.line2": "Connect any provider to use models, inc. Claude, GPT, Gemini etc.", "sidebar.project.recentSessions": "Recent sessions", "sidebar.project.viewAllSessions": "View all sessions", @@ -708,7 +708,7 @@ export const dict = { "debugBar.mem.tipUnavailable": "Used JS heap vs heap limit. Chromium only.", "debugBar.mem.tip": "Used JS heap vs heap limit. {{used}} of {{limit}}.", - "app.name.desktop": "OpenCode Desktop", + "app.name.desktop": "Kilo Desktop", "settings.section.desktop": "Desktop", "settings.section.server": "Server", @@ -716,7 +716,7 @@ export const dict = { "settings.tab.shortcuts": "Shortcuts", "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "WSL integration", - "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.", + "settings.desktop.wsl.description": "Run the Kilo server inside WSL on Windows.", "settings.general.section.appearance": "Appearance", "settings.general.section.advanced": "Advanced", @@ -727,13 +727,13 @@ export const dict = { "settings.general.section.display": "Display", "settings.general.row.language.title": "Language", - "settings.general.row.language.description": "Change the display language for OpenCode", + "settings.general.row.language.description": "Change the display language for Kilo", "settings.general.row.appearance.title": "Appearance", - "settings.general.row.appearance.description": "Customise how OpenCode looks on your device", + "settings.general.row.appearance.description": "Customise how Kilo looks on your device", "settings.general.row.colorScheme.title": "Color scheme", - "settings.general.row.colorScheme.description": "Choose whether OpenCode follows the system, light, or dark theme", + "settings.general.row.colorScheme.description": "Choose whether Kilo follows the system, light, or dark theme", "settings.general.row.theme.title": "Theme", - "settings.general.row.theme.description": "Customise how OpenCode is themed.", + "settings.general.row.theme.description": "Customise how Kilo is themed.", "settings.general.row.font.title": "Code Font", "settings.general.row.font.description": "Customise the font used in code blocks", "settings.general.row.terminalFont.title": "Terminal Font", @@ -775,13 +775,13 @@ export const dict = { "settings.general.row.releaseNotes.description": "Show What's New popups after updates", "settings.updates.row.startup.title": "Check for updates on startup", - "settings.updates.row.startup.description": "Automatically check for updates when OpenCode launches", + "settings.updates.row.startup.description": "Automatically check for updates when Kilo launches", "settings.updates.row.check.title": "Check for updates", "settings.updates.row.check.description": "Manually check for updates and install if available", "settings.updates.action.checkNow": "Check now", "settings.updates.action.checking": "Checking...", "settings.updates.toast.latest.title": "You're up to date", - "settings.updates.toast.latest.description": "You're running the latest version of OpenCode.", + "settings.updates.toast.latest.description": "You're running the latest version of Kilo.", "sound.option.none": "None", "sound.option.alert01": "Alert 01", "sound.option.alert02": "Alert 02", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 8dc644bb8e..9ed93e813e 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -115,7 +115,7 @@ export const dict = { "dialog.model.manage.description": "Personalizar qué modelos aparecen en el selector de modelos.", "dialog.model.manage.provider.toggle": "Alternar todos los modelos de {{provider}}", - "dialog.model.unpaid.freeModels.title": "Modelos gratuitos proporcionados por OpenCode", + "dialog.model.unpaid.freeModels.title": "Modelos gratuitos proporcionados por Kilo", "dialog.model.unpaid.addMore.title": "Añadir más modelos de proveedores populares", "dialog.provider.viewAll": "Ver más proveedores", @@ -128,7 +128,7 @@ export const dict = { "provider.connect.status.waiting": "Esperando autorización...", "provider.connect.status.failed": "Autorización fallida: {{error}}", "provider.connect.apiKey.description": - "Introduce tu clave API de {{provider}} para conectar tu cuenta y usar modelos de {{provider}} en OpenCode.", + "Introduce tu clave API de {{provider}} para conectar tu cuenta y usar modelos de {{provider}} en Kilo.", "provider.connect.apiKey.label": "Clave API de {{provider}}", "provider.connect.apiKey.placeholder": "Clave API", "provider.connect.apiKey.required": "La clave API es obligatoria", @@ -142,7 +142,7 @@ export const dict = { "provider.connect.oauth.code.visit.prefix": "Visita ", "provider.connect.oauth.code.visit.link": "este enlace", "provider.connect.oauth.code.visit.suffix": - " para obtener tu código de autorización para conectar tu cuenta y usar modelos de {{provider}} en OpenCode.", + " para obtener tu código de autorización para conectar tu cuenta y usar modelos de {{provider}} en Kilo.", "provider.connect.oauth.code.label": "Código de autorización {{method}}", "provider.connect.oauth.code.placeholder": "Código de autorización", "provider.connect.oauth.code.required": "El código de autorización es obligatorio", @@ -150,7 +150,7 @@ export const dict = { "provider.connect.oauth.auto.visit.prefix": "Visita ", "provider.connect.oauth.auto.visit.link": "este enlace", "provider.connect.oauth.auto.visit.suffix": - " e introduce el código a continuación para conectar tu cuenta y usar modelos de {{provider}} en OpenCode.", + " e introduce el código a continuación para conectar tu cuenta y usar modelos de {{provider}} en Kilo.", "provider.connect.oauth.auto.confirmationCode": "Código de confirmación", "provider.connect.toast.connected.title": "{{provider}} conectado", "provider.connect.toast.connected.description": "Los modelos de {{provider}} ahora están disponibles para usar.", @@ -307,7 +307,7 @@ export const dict = { "dialog.directory.empty": "No se encontraron carpetas", "dialog.server.title": "Servidores", - "dialog.server.description": "Cambiar a qué servidor de OpenCode se conecta esta app.", + "dialog.server.description": "Cambiar a qué servidor de Kilo se conecta esta app.", "dialog.server.search.placeholder": "Buscar servidores", "dialog.server.empty": "No hay servidores aún", "dialog.server.add.title": "Añadir un servidor", @@ -441,7 +441,7 @@ export const dict = { "toast.session.listFailed.title": "Fallo al cargar sesiones para {{project}}", "toast.update.title": "Actualización disponible", - "toast.update.description": "Una nueva versión de OpenCode ({{version}}) está disponible para instalar.", + "toast.update.description": "Una nueva versión de Kilo ({{version}}) está disponible para instalar.", "toast.update.action.installRestart": "Instalar y reiniciar", "toast.update.action.notYet": "Todavía no", @@ -452,7 +452,7 @@ export const dict = { "error.page.action.checking": "Comprobando...", "error.page.action.checkUpdates": "Buscar actualizaciones", "error.page.action.updateTo": "Actualizar a {{version}}", - "error.page.report.prefix": "Por favor reporta este error al equipo de OpenCode", + "error.page.report.prefix": "Por favor reporta este error al equipo de Kilo", "error.page.report.discord": "en Discord", "error.page.version": "Versión: {{version}}", @@ -471,7 +471,7 @@ export const dict = { "error.chain.didYouMean": "¿Quisiste decir: {{suggestions}}", "error.chain.modelNotFound": "Modelo no encontrado: {{provider}}/{{model}}", "error.chain.checkConfig": "Comprueba los nombres de proveedor/modelo en tu configuración (opencode.json)", - "error.chain.mcpFailed": 'El servidor MCP "{{name}}" falló. Nota, OpenCode no soporta autenticación MCP todavía.', + "error.chain.mcpFailed": 'El servidor MCP "{{name}}" falló. Nota, Kilo no soporta autenticación MCP todavía.', "error.chain.providerAuthFailed": "Autenticación de proveedor fallida ({{provider}}): {{message}}", "error.chain.providerInitFailed": 'Fallo al inicializar proveedor "{{provider}}". Comprueba credenciales y configuración.', @@ -608,13 +608,13 @@ export const dict = { "sidebar.workspaces.enable": "Habilitar espacios de trabajo", "sidebar.workspaces.disable": "Deshabilitar espacios de trabajo", "sidebar.gettingStarted.title": "Empezando", - "sidebar.gettingStarted.line1": "OpenCode incluye modelos gratuitos para que puedas empezar inmediatamente.", + "sidebar.gettingStarted.line1": "Kilo incluye modelos gratuitos para que puedas empezar inmediatamente.", "sidebar.gettingStarted.line2": "Conecta cualquier proveedor para usar modelos, inc. Claude, GPT, Gemini etc.", "sidebar.project.recentSessions": "Sesiones recientes", "sidebar.project.viewAllSessions": "Ver todas las sesiones", "sidebar.project.clearNotifications": "Borrar notificaciones", - "app.name.desktop": "OpenCode Desktop", + "app.name.desktop": "Kilo Desktop", "settings.section.desktop": "Escritorio", "settings.section.server": "Servidor", @@ -622,7 +622,7 @@ export const dict = { "settings.tab.shortcuts": "Atajos", "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "Integración con WSL", - "settings.desktop.wsl.description": "Ejecutar el servidor OpenCode dentro de WSL en Windows.", + "settings.desktop.wsl.description": "Ejecutar el servidor Kilo dentro de WSL en Windows.", "settings.general.section.appearance": "Apariencia", "settings.general.section.notifications": "Notificaciones del sistema", @@ -632,13 +632,13 @@ export const dict = { "settings.general.section.display": "Pantalla", "settings.general.row.language.title": "Idioma", - "settings.general.row.language.description": "Cambiar el idioma de visualización para OpenCode", + "settings.general.row.language.description": "Cambiar el idioma de visualización para Kilo", "settings.general.row.appearance.title": "Apariencia", - "settings.general.row.appearance.description": "Personaliza cómo se ve OpenCode en tu dispositivo", + "settings.general.row.appearance.description": "Personaliza cómo se ve Kilo en tu dispositivo", "settings.general.row.colorScheme.title": "Esquema de color", - "settings.general.row.colorScheme.description": "Elige si OpenCode sigue el tema del sistema, claro u oscuro", + "settings.general.row.colorScheme.description": "Elige si Kilo sigue el tema del sistema, claro u oscuro", "settings.general.row.theme.title": "Tema", - "settings.general.row.theme.description": "Personaliza el tema de OpenCode.", + "settings.general.row.theme.description": "Personaliza el tema de Kilo.", "settings.general.row.font.title": "Fuente de código", "settings.general.row.font.description": "Personaliza la fuente usada en bloques de código", "settings.general.row.terminalFont.title": "Terminal Font", @@ -672,13 +672,13 @@ export const dict = { 'Mostrar ventanas emergentes de "Novedades" después de las actualizaciones', "settings.updates.row.startup.title": "Buscar actualizaciones al iniciar", - "settings.updates.row.startup.description": "Buscar actualizaciones automáticamente cuando se inicia OpenCode", + "settings.updates.row.startup.description": "Buscar actualizaciones automáticamente cuando se inicia Kilo", "settings.updates.row.check.title": "Buscar actualizaciones", "settings.updates.row.check.description": "Buscar actualizaciones manualmente e instalarlas si hay alguna", "settings.updates.action.checkNow": "Buscar ahora", "settings.updates.action.checking": "Buscando...", "settings.updates.toast.latest.title": "Estás al día", - "settings.updates.toast.latest.description": "Estás usando la última versión de OpenCode.", + "settings.updates.toast.latest.description": "Estás usando la última versión de Kilo.", "sound.option.none": "Ninguno", "sound.option.alert01": "Alerta 01", "sound.option.alert02": "Alerta 02", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index 1b4916c7d9..5337ed482a 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -105,7 +105,7 @@ export const dict = { "dialog.model.manage": "Gérer les modèles", "dialog.model.manage.description": "Personnalisez les modèles qui apparaissent dans le sélecteur.", "dialog.model.manage.provider.toggle": "Basculer tous les modèles {{provider}}", - "dialog.model.unpaid.freeModels.title": "Modèles gratuits fournis par OpenCode", + "dialog.model.unpaid.freeModels.title": "Modèles gratuits fournis par Kilo", "dialog.model.unpaid.addMore.title": "Ajouter plus de modèles de fournisseurs populaires", "dialog.provider.viewAll": "Voir plus de fournisseurs", "provider.connect.title": "Connecter {{provider}}", @@ -116,7 +116,7 @@ export const dict = { "provider.connect.status.waiting": "En attente d'autorisation...", "provider.connect.status.failed": "Échec de l'autorisation : {{error}}", "provider.connect.apiKey.description": - "Entrez votre clé API {{provider}} pour connecter votre compte et utiliser les modèles {{provider}} dans OpenCode.", + "Entrez votre clé API {{provider}} pour connecter votre compte et utiliser les modèles {{provider}} dans Kilo.", "provider.connect.apiKey.label": "Clé API {{provider}}", "provider.connect.apiKey.placeholder": "Clé API", "provider.connect.apiKey.required": "La clé API est requise", @@ -130,7 +130,7 @@ export const dict = { "provider.connect.oauth.code.visit.prefix": "Visitez ", "provider.connect.oauth.code.visit.link": "ce lien", "provider.connect.oauth.code.visit.suffix": - " pour récupérer votre code d'autorisation afin de connecter votre compte et utiliser les modèles {{provider}} dans OpenCode.", + " pour récupérer votre code d'autorisation afin de connecter votre compte et utiliser les modèles {{provider}} dans Kilo.", "provider.connect.oauth.code.label": "Code d'autorisation {{method}}", "provider.connect.oauth.code.placeholder": "Code d'autorisation", "provider.connect.oauth.code.required": "Le code d'autorisation est requis", @@ -138,7 +138,7 @@ export const dict = { "provider.connect.oauth.auto.visit.prefix": "Visitez ", "provider.connect.oauth.auto.visit.link": "ce lien", "provider.connect.oauth.auto.visit.suffix": - " et entrez le code ci-dessous pour connecter votre compte et utiliser les modèles {{provider}} dans OpenCode.", + " et entrez le code ci-dessous pour connecter votre compte et utiliser les modèles {{provider}} dans Kilo.", "provider.connect.oauth.auto.confirmationCode": "Code de confirmation", "provider.connect.toast.connected.title": "{{provider}} connecté", "provider.connect.toast.connected.description": "Les modèles {{provider}} sont maintenant disponibles.", @@ -282,7 +282,7 @@ export const dict = { "dialog.directory.search.placeholder": "Rechercher des dossiers", "dialog.directory.empty": "Aucun dossier trouvé", "dialog.server.title": "Serveurs", - "dialog.server.description": "Changez le serveur OpenCode auquel cette application se connecte.", + "dialog.server.description": "Changez le serveur Kilo auquel cette application se connecte.", "dialog.server.search.placeholder": "Rechercher des serveurs", "dialog.server.empty": "Aucun serveur pour l'instant", "dialog.server.add.title": "Ajouter un serveur", @@ -399,7 +399,7 @@ export const dict = { "toast.session.listFailed.title": "Échec du chargement des sessions pour {{project}}", "toast.update.title": "Mise à jour disponible", "toast.update.description": - "Une nouvelle version d'OpenCode ({{version}}) est maintenant disponible pour installation.", + "Une nouvelle version d'Kilo ({{version}}) est maintenant disponible pour installation.", "toast.update.action.installRestart": "Installer et redémarrer", "toast.update.action.notYet": "Pas encore", "error.page.title": "Quelque chose s'est mal passé", @@ -409,7 +409,7 @@ export const dict = { "error.page.action.checking": "Vérification...", "error.page.action.checkUpdates": "Vérifier les mises à jour", "error.page.action.updateTo": "Mettre à jour vers {{version}}", - "error.page.report.prefix": "Veuillez signaler cette erreur à l'équipe OpenCode", + "error.page.report.prefix": "Veuillez signaler cette erreur à l'équipe Kilo", "error.page.report.discord": "sur Discord", "error.page.version": "Version : {{version}}", "error.dev.rootNotFound": @@ -427,7 +427,7 @@ export const dict = { "error.chain.modelNotFound": "Modèle introuvable : {{provider}}/{{model}}", "error.chain.checkConfig": "Vérifiez votre configuration (opencode.json) pour les noms de fournisseur/modèle", "error.chain.mcpFailed": - "Le serveur MCP \"{{name}}\" a échoué. Notez qu'OpenCode ne supporte pas encore l'authentification MCP.", + "Le serveur MCP \"{{name}}\" a échoué. Notez qu'Kilo ne supporte pas encore l'authentification MCP.", "error.chain.providerAuthFailed": "Échec de l'authentification du fournisseur ({{provider}}) : {{message}}", "error.chain.providerInitFailed": 'Échec de l\'initialisation du fournisseur "{{provider}}". Vérifiez les identifiants et la configuration.', @@ -550,20 +550,20 @@ export const dict = { "sidebar.workspaces.disable": "Désactiver les espaces de travail", "sidebar.gettingStarted.title": "Commencer", "sidebar.gettingStarted.line1": - "OpenCode inclut des modèles gratuits pour que vous puissiez commencer immédiatement.", + "Kilo inclut des modèles gratuits pour que vous puissiez commencer immédiatement.", "sidebar.gettingStarted.line2": "Connectez n'importe quel fournisseur pour utiliser des modèles, y compris Claude, GPT, Gemini etc.", "sidebar.project.recentSessions": "Sessions récentes", "sidebar.project.viewAllSessions": "Voir toutes les sessions", "sidebar.project.clearNotifications": "Effacer les notifications", - "app.name.desktop": "OpenCode Desktop", + "app.name.desktop": "Kilo Desktop", "settings.section.desktop": "Bureau", "settings.section.server": "Serveur", "settings.tab.general": "Général", "settings.tab.shortcuts": "Raccourcis", "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "Intégration WSL", - "settings.desktop.wsl.description": "Exécuter le serveur OpenCode dans WSL sur Windows.", + "settings.desktop.wsl.description": "Exécuter le serveur Kilo dans WSL sur Windows.", "settings.general.section.appearance": "Apparence", "settings.general.section.notifications": "Notifications système", "settings.general.section.updates": "Mises à jour", @@ -571,13 +571,13 @@ export const dict = { "settings.general.section.feed": "Flux", "settings.general.section.display": "Affichage", "settings.general.row.language.title": "Langue", - "settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode", + "settings.general.row.language.description": "Changer la langue d'affichage pour Kilo", "settings.general.row.appearance.title": "Apparence", - "settings.general.row.appearance.description": "Personnaliser l'apparence d'OpenCode sur votre appareil", + "settings.general.row.appearance.description": "Personnaliser l'apparence d'Kilo sur votre appareil", "settings.general.row.colorScheme.title": "Schéma de couleurs", - "settings.general.row.colorScheme.description": "Choisissez si OpenCode suit le thème système, clair ou sombre", + "settings.general.row.colorScheme.description": "Choisissez si Kilo suit le thème système, clair ou sombre", "settings.general.row.theme.title": "Thème", - "settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.", + "settings.general.row.theme.description": "Personnaliser le thème d'Kilo.", "settings.general.row.font.title": "Police de code", "settings.general.row.font.description": "Personnaliser la police utilisée dans les blocs de code", "settings.general.row.terminalFont.title": "Terminal Font", @@ -608,13 +608,13 @@ export const dict = { "settings.general.row.releaseNotes.title": "Notes de version", "settings.general.row.releaseNotes.description": 'Afficher des pop-ups "Quoi de neuf" après les mises à jour', "settings.updates.row.startup.title": "Vérifier les mises à jour au démarrage", - "settings.updates.row.startup.description": "Vérifier automatiquement les mises à jour au lancement d'OpenCode", + "settings.updates.row.startup.description": "Vérifier automatiquement les mises à jour au lancement d'Kilo", "settings.updates.row.check.title": "Vérifier les mises à jour", "settings.updates.row.check.description": "Vérifier manuellement les mises à jour et installer si disponible", "settings.updates.action.checkNow": "Vérifier maintenant", "settings.updates.action.checking": "Vérification...", "settings.updates.toast.latest.title": "Vous êtes à jour", - "settings.updates.toast.latest.description": "Vous utilisez la dernière version d'OpenCode.", + "settings.updates.toast.latest.description": "Vous utilisez la dernière version d'Kilo.", "sound.option.none": "Aucun", "sound.option.alert01": "Alerte 01", "sound.option.alert02": "Alerte 02", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 979f94203d..49b8d652d7 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -105,7 +105,7 @@ export const dict = { "dialog.model.manage": "モデルを管理", "dialog.model.manage.description": "モデルセレクターに表示するモデルをカスタマイズします。", "dialog.model.manage.provider.toggle": "すべての{{provider}}モデルを切り替え", - "dialog.model.unpaid.freeModels.title": "OpenCodeが提供する無料モデル", + "dialog.model.unpaid.freeModels.title": "Kiloが提供する無料モデル", "dialog.model.unpaid.addMore.title": "人気のプロバイダーからモデルを追加", "dialog.provider.viewAll": "さらにプロバイダーを表示", "provider.connect.title": "{{provider}}を接続", @@ -116,7 +116,7 @@ export const dict = { "provider.connect.status.waiting": "認証を待機中...", "provider.connect.status.failed": "認証に失敗しました: {{error}}", "provider.connect.apiKey.description": - "{{provider}}のAPIキーを入力してアカウントを接続し、OpenCodeで{{provider}}モデルを使用します。", + "{{provider}}のAPIキーを入力してアカウントを接続し、Kiloで{{provider}}モデルを使用します。", "provider.connect.apiKey.label": "{{provider}} APIキー", "provider.connect.apiKey.placeholder": "APIキー", "provider.connect.apiKey.required": "APIキーが必要です", @@ -129,7 +129,7 @@ export const dict = { "provider.connect.oauth.code.visit.prefix": " ", "provider.connect.oauth.code.visit.link": "このリンク", "provider.connect.oauth.code.visit.suffix": - " にアクセスして認証コードを取得し、アカウントを接続してOpenCodeで{{provider}}モデルを使用してください。", + " にアクセスして認証コードを取得し、アカウントを接続してKiloで{{provider}}モデルを使用してください。", "provider.connect.oauth.code.label": "{{method}} 認証コード", "provider.connect.oauth.code.placeholder": "認証コード", "provider.connect.oauth.code.required": "認証コードが必要です", @@ -137,7 +137,7 @@ export const dict = { "provider.connect.oauth.auto.visit.prefix": " ", "provider.connect.oauth.auto.visit.link": "このリンク", "provider.connect.oauth.auto.visit.suffix": - " にアクセスし、以下のコードを入力してアカウントを接続し、OpenCodeで{{provider}}モデルを使用してください。", + " にアクセスし、以下のコードを入力してアカウントを接続し、Kiloで{{provider}}モデルを使用してください。", "provider.connect.oauth.auto.confirmationCode": "確認コード", "provider.connect.toast.connected.title": "{{provider}}が接続されました", "provider.connect.toast.connected.description": "{{provider}}モデルが使用可能になりました。", @@ -280,7 +280,7 @@ export const dict = { "dialog.directory.search.placeholder": "フォルダを検索", "dialog.directory.empty": "フォルダが見つかりません", "dialog.server.title": "サーバー", - "dialog.server.description": "このアプリが接続するOpenCodeサーバーを切り替えます。", + "dialog.server.description": "このアプリが接続するKiloサーバーを切り替えます。", "dialog.server.search.placeholder": "サーバーを検索", "dialog.server.empty": "サーバーはまだありません", "dialog.server.add.title": "サーバーを追加", @@ -395,7 +395,7 @@ export const dict = { "toast.session.unshare.failed.description": "セッションの共有解除中にエラーが発生しました", "toast.session.listFailed.title": "{{project}}のセッション読み込みに失敗しました", "toast.update.title": "アップデートが利用可能です", - "toast.update.description": "OpenCodeの新しいバージョン ({{version}}) がインストール可能です。", + "toast.update.description": "Kiloの新しいバージョン ({{version}}) がインストール可能です。", "toast.update.action.installRestart": "インストールして再起動", "toast.update.action.notYet": "今はしない", "error.page.title": "問題が発生しました", @@ -405,7 +405,7 @@ export const dict = { "error.page.action.checking": "確認中...", "error.page.action.checkUpdates": "アップデートを確認", "error.page.action.updateTo": "{{version}}にアップデート", - "error.page.report.prefix": "このエラーをOpenCodeチームに報告してください: ", + "error.page.report.prefix": "このエラーをKiloチームに報告してください: ", "error.page.report.discord": "Discord", "error.page.version": "バージョン: {{version}}", "error.dev.rootNotFound": @@ -421,7 +421,7 @@ export const dict = { "error.chain.didYouMean": "もしかして: {{suggestions}}", "error.chain.modelNotFound": "モデルが見つかりません: {{provider}}/{{model}}", "error.chain.checkConfig": "config (opencode.json) のプロバイダー/モデル名を確認してください", - "error.chain.mcpFailed": 'MCPサーバー "{{name}}" が失敗しました。注意: OpenCodeはまだMCP認証をサポートしていません。', + "error.chain.mcpFailed": 'MCPサーバー "{{name}}" が失敗しました。注意: KiloはまだMCP認証をサポートしていません。', "error.chain.providerAuthFailed": "プロバイダー認証に失敗しました ({{provider}}): {{message}}", "error.chain.providerInitFailed": 'プロバイダー "{{provider}}" の初期化に失敗しました。認証情報と設定を確認してください。', @@ -541,19 +541,19 @@ export const dict = { "sidebar.workspaces.enable": "ワークスペースを有効化", "sidebar.workspaces.disable": "ワークスペースを無効化", "sidebar.gettingStarted.title": "はじめに", - "sidebar.gettingStarted.line1": "OpenCodeには無料モデルが含まれているため、すぐに開始できます。", + "sidebar.gettingStarted.line1": "Kiloには無料モデルが含まれているため、すぐに開始できます。", "sidebar.gettingStarted.line2": "プロバイダーを接続して、Claude、GPT、Geminiなどのモデルを使用できます。", "sidebar.project.recentSessions": "最近のセッション", "sidebar.project.viewAllSessions": "すべてのセッションを表示", "sidebar.project.clearNotifications": "通知をクリア", - "app.name.desktop": "OpenCode Desktop", + "app.name.desktop": "Kilo Desktop", "settings.section.desktop": "デスクトップ", "settings.section.server": "サーバー", "settings.tab.general": "一般", "settings.tab.shortcuts": "ショートカット", "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "WSL連携", - "settings.desktop.wsl.description": "WindowsのWSL環境でOpenCodeサーバーを実行します。", + "settings.desktop.wsl.description": "WindowsのWSL環境でKiloサーバーを実行します。", "settings.general.section.appearance": "外観", "settings.general.section.notifications": "システム通知", "settings.general.section.updates": "アップデート", @@ -561,13 +561,13 @@ export const dict = { "settings.general.section.feed": "フィード", "settings.general.section.display": "ディスプレイ", "settings.general.row.language.title": "言語", - "settings.general.row.language.description": "OpenCodeの表示言語を変更します", + "settings.general.row.language.description": "Kiloの表示言語を変更します", "settings.general.row.appearance.title": "外観", - "settings.general.row.appearance.description": "デバイスでのOpenCodeの表示をカスタマイズします", + "settings.general.row.appearance.description": "デバイスでのKiloの表示をカスタマイズします", "settings.general.row.colorScheme.title": "配色", - "settings.general.row.colorScheme.description": "OpenCodeがシステム、ライト、またはダークテーマに従うかを選択します", + "settings.general.row.colorScheme.description": "Kiloがシステム、ライト、またはダークテーマに従うかを選択します", "settings.general.row.theme.title": "テーマ", - "settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。", + "settings.general.row.theme.description": "Kiloのテーマをカスタマイズします。", "settings.general.row.font.title": "コードフォント", "settings.general.row.font.description": "コードブロックで使用するフォントをカスタマイズします", "settings.general.row.terminalFont.title": "Terminal Font", @@ -597,13 +597,13 @@ export const dict = { "settings.general.row.releaseNotes.title": "リリースノート", "settings.general.row.releaseNotes.description": "アップデート後に「新機能」ポップアップを表示", "settings.updates.row.startup.title": "起動時にアップデートを確認", - "settings.updates.row.startup.description": "OpenCode の起動時に自動でアップデートを確認します", + "settings.updates.row.startup.description": "Kilo の起動時に自動でアップデートを確認します", "settings.updates.row.check.title": "アップデートを確認", "settings.updates.row.check.description": "手動でアップデートを確認し、利用可能ならインストールします", "settings.updates.action.checkNow": "今すぐ確認", "settings.updates.action.checking": "確認中...", "settings.updates.toast.latest.title": "最新です", - "settings.updates.toast.latest.description": "OpenCode は最新バージョンです。", + "settings.updates.toast.latest.description": "Kilo は最新バージョンです。", "sound.option.none": "なし", "sound.option.alert01": "アラート 01", "sound.option.alert02": "アラート 02", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 56ce374a96..c1c6f397cc 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -105,7 +105,7 @@ export const dict = { "dialog.model.manage": "모델 관리", "dialog.model.manage.description": "모델 선택기에 표시할 모델 사용자 지정", "dialog.model.manage.provider.toggle": "모든 {{provider}} 모델 토글", - "dialog.model.unpaid.freeModels.title": "OpenCode에서 제공하는 무료 모델", + "dialog.model.unpaid.freeModels.title": "Kilo에서 제공하는 무료 모델", "dialog.model.unpaid.addMore.title": "인기 공급자의 모델 추가", "dialog.provider.viewAll": "더 많은 공급자 보기", "provider.connect.title": "{{provider}} 연결", @@ -116,7 +116,7 @@ export const dict = { "provider.connect.status.waiting": "인증 대기 중...", "provider.connect.status.failed": "인증 실패: {{error}}", "provider.connect.apiKey.description": - "{{provider}} API 키를 입력하여 계정을 연결하고 OpenCode에서 {{provider}} 모델을 사용하세요.", + "{{provider}} API 키를 입력하여 계정을 연결하고 Kilo에서 {{provider}} 모델을 사용하세요.", "provider.connect.apiKey.label": "{{provider}} API 키", "provider.connect.apiKey.placeholder": "API 키", "provider.connect.apiKey.required": "API 키가 필요합니다", @@ -129,7 +129,7 @@ export const dict = { "provider.connect.oauth.code.visit.prefix": "다음 ", "provider.connect.oauth.code.visit.link": "이 링크", "provider.connect.oauth.code.visit.suffix": - "를 방문하여 인증 코드를 받아 계정을 연결하고 OpenCode에서 {{provider}} 모델을 사용하세요.", + "를 방문하여 인증 코드를 받아 계정을 연결하고 Kilo에서 {{provider}} 모델을 사용하세요.", "provider.connect.oauth.code.label": "{{method}} 인증 코드", "provider.connect.oauth.code.placeholder": "인증 코드", "provider.connect.oauth.code.required": "인증 코드가 필요합니다", @@ -137,7 +137,7 @@ export const dict = { "provider.connect.oauth.auto.visit.prefix": "다음 ", "provider.connect.oauth.auto.visit.link": "이 링크", "provider.connect.oauth.auto.visit.suffix": - "를 방문하고 아래 코드를 입력하여 계정을 연결하고 OpenCode에서 {{provider}} 모델을 사용하세요.", + "를 방문하고 아래 코드를 입력하여 계정을 연결하고 Kilo에서 {{provider}} 모델을 사용하세요.", "provider.connect.oauth.auto.confirmationCode": "확인 코드", "provider.connect.toast.connected.title": "{{provider}} 연결됨", "provider.connect.toast.connected.description": "이제 {{provider}} 모델을 사용할 수 있습니다.", @@ -280,7 +280,7 @@ export const dict = { "dialog.directory.search.placeholder": "폴더 검색", "dialog.directory.empty": "폴더 없음", "dialog.server.title": "서버", - "dialog.server.description": "이 앱이 연결할 OpenCode 서버를 전환합니다.", + "dialog.server.description": "이 앱이 연결할 Kilo 서버를 전환합니다.", "dialog.server.search.placeholder": "서버 검색", "dialog.server.empty": "서버 없음", "dialog.server.add.title": "서버 추가", @@ -394,7 +394,7 @@ export const dict = { "toast.session.unshare.failed.description": "세션 공유를 해제하는 동안 오류가 발생했습니다", "toast.session.listFailed.title": "{{project}}에 대한 세션을 로드하지 못했습니다", "toast.update.title": "업데이트 가능", - "toast.update.description": "OpenCode의 새 버전({{version}})을 설치할 수 있습니다.", + "toast.update.description": "Kilo의 새 버전({{version}})을 설치할 수 있습니다.", "toast.update.action.installRestart": "설치 및 다시 시작", "toast.update.action.notYet": "나중에", "error.page.title": "문제가 발생했습니다", @@ -404,7 +404,7 @@ export const dict = { "error.page.action.checking": "확인 중...", "error.page.action.checkUpdates": "업데이트 확인", "error.page.action.updateTo": "{{version}} 버전으로 업데이트", - "error.page.report.prefix": "이 오류를 OpenCode 팀에 제보해 주세요: ", + "error.page.report.prefix": "이 오류를 Kilo 팀에 제보해 주세요: ", "error.page.report.discord": "Discord", "error.page.version": "버전: {{version}}", "error.dev.rootNotFound": @@ -420,7 +420,7 @@ export const dict = { "error.chain.didYouMean": "혹시 {{suggestions}}을(를) 의미하셨나요?", "error.chain.modelNotFound": "모델을 찾을 수 없음: {{provider}}/{{model}}", "error.chain.checkConfig": "구성(opencode.json)의 공급자/모델 이름을 확인하세요", - "error.chain.mcpFailed": 'MCP 서버 "{{name}}" 실패. 참고: OpenCode는 아직 MCP 인증을 지원하지 않습니다.', + "error.chain.mcpFailed": 'MCP 서버 "{{name}}" 실패. 참고: Kilo는 아직 MCP 인증을 지원하지 않습니다.', "error.chain.providerAuthFailed": "공급자 인증 실패 ({{provider}}): {{message}}", "error.chain.providerInitFailed": '공급자 "{{provider}}" 초기화 실패. 자격 증명과 구성을 확인하세요.', "error.chain.configJsonInvalid": "{{path}}의 구성 파일이 유효한 JSON(C)가 아닙니다", @@ -538,19 +538,19 @@ export const dict = { "sidebar.workspaces.enable": "작업 공간 활성화", "sidebar.workspaces.disable": "작업 공간 비활성화", "sidebar.gettingStarted.title": "시작하기", - "sidebar.gettingStarted.line1": "OpenCode에는 무료 모델이 포함되어 있어 즉시 시작할 수 있습니다.", + "sidebar.gettingStarted.line1": "Kilo에는 무료 모델이 포함되어 있어 즉시 시작할 수 있습니다.", "sidebar.gettingStarted.line2": "Claude, GPT, Gemini 등을 포함한 모델을 사용하려면 공급자를 연결하세요.", "sidebar.project.recentSessions": "최근 세션", "sidebar.project.viewAllSessions": "모든 세션 보기", "sidebar.project.clearNotifications": "알림 지우기", - "app.name.desktop": "OpenCode Desktop", + "app.name.desktop": "Kilo Desktop", "settings.section.desktop": "데스크톱", "settings.section.server": "서버", "settings.tab.general": "일반", "settings.tab.shortcuts": "단축키", "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "WSL 통합", - "settings.desktop.wsl.description": "Windows의 WSL 내부에서 OpenCode 서버를 실행합니다.", + "settings.desktop.wsl.description": "Windows의 WSL 내부에서 Kilo 서버를 실행합니다.", "settings.general.section.appearance": "모양", "settings.general.section.notifications": "시스템 알림", "settings.general.section.updates": "업데이트", @@ -558,13 +558,13 @@ export const dict = { "settings.general.section.feed": "피드", "settings.general.section.display": "디스플레이", "settings.general.row.language.title": "언어", - "settings.general.row.language.description": "OpenCode 표시 언어 변경", + "settings.general.row.language.description": "Kilo 표시 언어 변경", "settings.general.row.appearance.title": "모양", - "settings.general.row.appearance.description": "기기에서 OpenCode가 보이는 방식 사용자 지정", + "settings.general.row.appearance.description": "기기에서 Kilo가 보이는 방식 사용자 지정", "settings.general.row.colorScheme.title": "색상 테마", - "settings.general.row.colorScheme.description": "OpenCode가 시스템, 라이트 또는 다크 테마를 따를지 선택하세요", + "settings.general.row.colorScheme.description": "Kilo가 시스템, 라이트 또는 다크 테마를 따를지 선택하세요", "settings.general.row.theme.title": "테마", - "settings.general.row.theme.description": "OpenCode 테마 사용자 지정", + "settings.general.row.theme.description": "Kilo 테마 사용자 지정", "settings.general.row.font.title": "코드 글꼴", "settings.general.row.font.description": "코드 블록에 사용되는 글꼴을 사용자 지정", "settings.general.row.terminalFont.title": "Terminal Font", @@ -593,13 +593,13 @@ export const dict = { "settings.general.row.releaseNotes.title": "릴리스 노트", "settings.general.row.releaseNotes.description": "업데이트 후 '새 소식' 팝업 표시", "settings.updates.row.startup.title": "시작 시 업데이트 확인", - "settings.updates.row.startup.description": "OpenCode를 실행할 때 업데이트를 자동으로 확인합니다", + "settings.updates.row.startup.description": "Kilo를 실행할 때 업데이트를 자동으로 확인합니다", "settings.updates.row.check.title": "업데이트 확인", "settings.updates.row.check.description": "업데이트를 수동으로 확인하고, 사용 가능하면 설치합니다", "settings.updates.action.checkNow": "지금 확인", "settings.updates.action.checking": "확인 중...", "settings.updates.toast.latest.title": "최신 상태입니다", - "settings.updates.toast.latest.description": "현재 최신 버전의 OpenCode를 사용 중입니다.", + "settings.updates.toast.latest.description": "현재 최신 버전의 Kilo를 사용 중입니다.", "sound.option.none": "없음", "sound.option.alert01": "알림 01", "sound.option.alert02": "알림 02", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index d14dd6f98b..1551f95682 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -118,7 +118,7 @@ export const dict = { "dialog.model.manage.description": "Tilpass hvilke modeller som vises i modellvelgeren.", "dialog.model.manage.provider.toggle": "Veksle alle {{provider}}-modeller", - "dialog.model.unpaid.freeModels.title": "Gratis modeller levert av OpenCode", + "dialog.model.unpaid.freeModels.title": "Gratis modeller levert av Kilo", "dialog.model.unpaid.addMore.title": "Legg til flere modeller fra populære leverandører", "dialog.provider.viewAll": "Vis flere leverandører", @@ -131,7 +131,7 @@ export const dict = { "provider.connect.status.waiting": "Venter på autorisering...", "provider.connect.status.failed": "Autorisering mislyktes: {{error}}", "provider.connect.apiKey.description": - "Skriv inn din {{provider}} API-nøkkel for å koble til kontoen din og bruke {{provider}}-modeller i OpenCode.", + "Skriv inn din {{provider}} API-nøkkel for å koble til kontoen din og bruke {{provider}}-modeller i Kilo.", "provider.connect.apiKey.label": "{{provider}} API-nøkkel", "provider.connect.apiKey.placeholder": "API-nøkkel", "provider.connect.apiKey.required": "API-nøkkel er påkrevd", @@ -145,7 +145,7 @@ export const dict = { "provider.connect.oauth.code.visit.prefix": "Besøk ", "provider.connect.oauth.code.visit.link": "denne lenken", "provider.connect.oauth.code.visit.suffix": - " for å hente autorisasjonskoden din for å koble til kontoen din og bruke {{provider}}-modeller i OpenCode.", + " for å hente autorisasjonskoden din for å koble til kontoen din og bruke {{provider}}-modeller i Kilo.", "provider.connect.oauth.code.label": "{{method}} autorisasjonskode", "provider.connect.oauth.code.placeholder": "Autorisasjonskode", "provider.connect.oauth.code.required": "Autorisasjonskode er påkrevd", @@ -153,7 +153,7 @@ export const dict = { "provider.connect.oauth.auto.visit.prefix": "Besøk ", "provider.connect.oauth.auto.visit.link": "denne lenken", "provider.connect.oauth.auto.visit.suffix": - " og skriv inn koden nedenfor for å koble til kontoen din og bruke {{provider}}-modeller i OpenCode.", + " og skriv inn koden nedenfor for å koble til kontoen din og bruke {{provider}}-modeller i Kilo.", "provider.connect.oauth.auto.confirmationCode": "Bekreftelseskode", "provider.connect.toast.connected.title": "{{provider}} tilkoblet", "provider.connect.toast.connected.description": "{{provider}}-modeller er nå tilgjengelige.", @@ -310,7 +310,7 @@ export const dict = { "dialog.directory.empty": "Ingen mapper funnet", "dialog.server.title": "Servere", - "dialog.server.description": "Bytt hvilken OpenCode-server denne appen kobler til.", + "dialog.server.description": "Bytt hvilken Kilo-server denne appen kobler til.", "dialog.server.search.placeholder": "Søk etter servere", "dialog.server.empty": "Ingen servere ennå", "dialog.server.add.title": "Legg til en server", @@ -442,7 +442,7 @@ export const dict = { "toast.session.listFailed.title": "Kunne ikke laste sesjoner for {{project}}", "toast.update.title": "Oppdatering tilgjengelig", - "toast.update.description": "En ny versjon av OpenCode ({{version}}) er nå tilgjengelig for installasjon.", + "toast.update.description": "En ny versjon av Kilo ({{version}}) er nå tilgjengelig for installasjon.", "toast.update.action.installRestart": "Installer og start på nytt", "toast.update.action.notYet": "Ikke nå", @@ -453,7 +453,7 @@ export const dict = { "error.page.action.checking": "Sjekker...", "error.page.action.checkUpdates": "Se etter oppdateringer", "error.page.action.updateTo": "Oppdater til {{version}}", - "error.page.report.prefix": "Vennligst rapporter denne feilen til OpenCode-teamet", + "error.page.report.prefix": "Vennligst rapporter denne feilen til Kilo-teamet", "error.page.report.discord": "på Discord", "error.page.version": "Versjon: {{version}}", @@ -472,7 +472,7 @@ export const dict = { "error.chain.didYouMean": "Mente du: {{suggestions}}", "error.chain.modelNotFound": "Modell ikke funnet: {{provider}}/{{model}}", "error.chain.checkConfig": "Sjekk leverandør-/modellnavnene i konfigurasjonen din (opencode.json)", - "error.chain.mcpFailed": 'MCP-server "{{name}}" mislyktes. Merk at OpenCode ikke støtter MCP-autentisering ennå.', + "error.chain.mcpFailed": 'MCP-server "{{name}}" mislyktes. Merk at Kilo ikke støtter MCP-autentisering ennå.', "error.chain.providerAuthFailed": "Leverandørautentisering mislyktes ({{provider}}): {{message}}", "error.chain.providerInitFailed": 'Kunne ikke initialisere leverandør "{{provider}}". Sjekk legitimasjon og konfigurasjon.', @@ -608,13 +608,13 @@ export const dict = { "sidebar.workspaces.enable": "Aktiver arbeidsområder", "sidebar.workspaces.disable": "Deaktiver arbeidsområder", "sidebar.gettingStarted.title": "Kom i gang", - "sidebar.gettingStarted.line1": "OpenCode inkluderer gratis modeller så du kan starte umiddelbart.", + "sidebar.gettingStarted.line1": "Kilo inkluderer gratis modeller så du kan starte umiddelbart.", "sidebar.gettingStarted.line2": "Koble til en leverandør for å bruke modeller, inkl. Claude, GPT, Gemini osv.", "sidebar.project.recentSessions": "Nylige sesjoner", "sidebar.project.viewAllSessions": "Vis alle sesjoner", "sidebar.project.clearNotifications": "Fjern varsler", - "app.name.desktop": "OpenCode Desktop", + "app.name.desktop": "Kilo Desktop", "settings.section.desktop": "Skrivebord", "settings.section.server": "Server", @@ -622,7 +622,7 @@ export const dict = { "settings.tab.shortcuts": "Snarveier", "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "WSL-integrasjon", - "settings.desktop.wsl.description": "Kjør OpenCode-serveren i WSL på Windows.", + "settings.desktop.wsl.description": "Kjør Kilo-serveren i WSL på Windows.", "settings.general.section.appearance": "Utseende", "settings.general.section.notifications": "Systemvarsler", @@ -632,13 +632,13 @@ export const dict = { "settings.general.section.display": "Skjerm", "settings.general.row.language.title": "Språk", - "settings.general.row.language.description": "Endre visningsspråket for OpenCode", + "settings.general.row.language.description": "Endre visningsspråket for Kilo", "settings.general.row.appearance.title": "Utseende", - "settings.general.row.appearance.description": "Tilpass hvordan OpenCode ser ut på enheten din", + "settings.general.row.appearance.description": "Tilpass hvordan Kilo ser ut på enheten din", "settings.general.row.colorScheme.title": "Fargevalg", - "settings.general.row.colorScheme.description": "Velg om OpenCode skal følge systemets, lyst eller mørkt tema", + "settings.general.row.colorScheme.description": "Velg om Kilo skal følge systemets, lyst eller mørkt tema", "settings.general.row.theme.title": "Tema", - "settings.general.row.theme.description": "Tilpass hvordan OpenCode er tematisert.", + "settings.general.row.theme.description": "Tilpass hvordan Kilo er tematisert.", "settings.general.row.font.title": "Kodefont", "settings.general.row.font.description": "Tilpass skrifttypen som brukes i kodeblokker", "settings.general.row.terminalFont.title": "Terminal Font", @@ -668,13 +668,13 @@ export const dict = { "settings.general.row.releaseNotes.description": 'Vis "Hva er nytt"-vinduer etter oppdateringer', "settings.updates.row.startup.title": "Se etter oppdateringer ved oppstart", - "settings.updates.row.startup.description": "Se automatisk etter oppdateringer når OpenCode starter", + "settings.updates.row.startup.description": "Se automatisk etter oppdateringer når Kilo starter", "settings.updates.row.check.title": "Se etter oppdateringer", "settings.updates.row.check.description": "Se etter oppdateringer manuelt og installer hvis tilgjengelig", "settings.updates.action.checkNow": "Sjekk nå", "settings.updates.action.checking": "Sjekker...", "settings.updates.toast.latest.title": "Du er oppdatert", - "settings.updates.toast.latest.description": "Du bruker den nyeste versjonen av OpenCode.", + "settings.updates.toast.latest.description": "Du bruker den nyeste versjonen av Kilo.", "sound.option.none": "Ingen", "sound.option.alert01": "Varsel 01", "sound.option.alert02": "Varsel 02", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index 9859ea0ae6..49e7e12d99 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -105,7 +105,7 @@ export const dict = { "dialog.model.manage": "Zarządzaj modelami", "dialog.model.manage.description": "Dostosuj, które modele pojawiają się w wyborze modelu.", "dialog.model.manage.provider.toggle": "Przełącz wszystkie modele {{provider}}", - "dialog.model.unpaid.freeModels.title": "Darmowe modele dostarczane przez OpenCode", + "dialog.model.unpaid.freeModels.title": "Darmowe modele dostarczane przez Kilo", "dialog.model.unpaid.addMore.title": "Dodaj więcej modeli od popularnych dostawców", "dialog.provider.viewAll": "Zobacz więcej dostawców", "provider.connect.title": "Połącz {{provider}}", @@ -116,7 +116,7 @@ export const dict = { "provider.connect.status.waiting": "Oczekiwanie na autoryzację...", "provider.connect.status.failed": "Autoryzacja nie powiodła się: {{error}}", "provider.connect.apiKey.description": - "Wprowadź swój klucz API {{provider}}, aby połączyć konto i używać modeli {{provider}} w OpenCode.", + "Wprowadź swój klucz API {{provider}}, aby połączyć konto i używać modeli {{provider}} w Kilo.", "provider.connect.apiKey.label": "Klucz API {{provider}}", "provider.connect.apiKey.placeholder": "Klucz API", "provider.connect.apiKey.required": "Klucz API jest wymagany", @@ -130,7 +130,7 @@ export const dict = { "provider.connect.oauth.code.visit.prefix": "Odwiedź ", "provider.connect.oauth.code.visit.link": "ten link", "provider.connect.oauth.code.visit.suffix": - ", aby odebrać kod autoryzacyjny, połączyć konto i używać modeli {{provider}} w OpenCode.", + ", aby odebrać kod autoryzacyjny, połączyć konto i używać modeli {{provider}} w Kilo.", "provider.connect.oauth.code.label": "Kod autoryzacyjny {{method}}", "provider.connect.oauth.code.placeholder": "Kod autoryzacyjny", "provider.connect.oauth.code.required": "Kod autoryzacyjny jest wymagany", @@ -138,7 +138,7 @@ export const dict = { "provider.connect.oauth.auto.visit.prefix": "Odwiedź ", "provider.connect.oauth.auto.visit.link": "ten link", "provider.connect.oauth.auto.visit.suffix": - " i wprowadź poniższy kod, aby połączyć konto i używać modeli {{provider}} w OpenCode.", + " i wprowadź poniższy kod, aby połączyć konto i używać modeli {{provider}} w Kilo.", "provider.connect.oauth.auto.confirmationCode": "Kod potwierdzający", "provider.connect.toast.connected.title": "Połączono {{provider}}", "provider.connect.toast.connected.description": "Modele {{provider}} są teraz dostępne do użycia.", @@ -282,7 +282,7 @@ export const dict = { "dialog.directory.search.placeholder": "Szukaj folderów", "dialog.directory.empty": "Nie znaleziono folderów", "dialog.server.title": "Serwery", - "dialog.server.description": "Przełącz serwer OpenCode, z którym łączy się ta aplikacja.", + "dialog.server.description": "Przełącz serwer Kilo, z którym łączy się ta aplikacja.", "dialog.server.search.placeholder": "Szukaj serwerów", "dialog.server.empty": "Brak serwerów", "dialog.server.add.title": "Dodaj serwer", @@ -396,7 +396,7 @@ export const dict = { "toast.session.unshare.failed.description": "Wystąpił błąd podczas zatrzymywania udostępniania sesji", "toast.session.listFailed.title": "Nie udało się załadować sesji dla {{project}}", "toast.update.title": "Dostępna aktualizacja", - "toast.update.description": "Nowa wersja OpenCode ({{version}}) jest teraz dostępna do instalacji.", + "toast.update.description": "Nowa wersja Kilo ({{version}}) jest teraz dostępna do instalacji.", "toast.update.action.installRestart": "Zainstaluj i zrestartuj", "toast.update.action.notYet": "Jeszcze nie", "error.page.title": "Coś poszło nie tak", @@ -406,7 +406,7 @@ export const dict = { "error.page.action.checking": "Sprawdzanie...", "error.page.action.checkUpdates": "Sprawdź aktualizacje", "error.page.action.updateTo": "Zaktualizuj do {{version}}", - "error.page.report.prefix": "Proszę zgłosić ten błąd do zespołu OpenCode", + "error.page.report.prefix": "Proszę zgłosić ten błąd do zespołu Kilo", "error.page.report.discord": "na Discordzie", "error.page.version": "Wersja: {{version}}", "error.dev.rootNotFound": @@ -422,7 +422,7 @@ export const dict = { "error.chain.didYouMean": "Czy miałeś na myśli: {{suggestions}}", "error.chain.modelNotFound": "Model nie znaleziony: {{provider}}/{{model}}", "error.chain.checkConfig": "Sprawdź swoją konfigurację (opencode.json) nazwy dostawców/modeli", - "error.chain.mcpFailed": 'MCP server "{{name}}" failed. Note, OpenCode does not support MCP authentication yet.', + "error.chain.mcpFailed": 'MCP server "{{name}}" failed. Note, Kilo does not support MCP authentication yet.', "error.chain.providerAuthFailed": "Uwierzytelnianie dostawcy nie powiodło się ({{provider}}): {{message}}", "error.chain.providerInitFailed": 'Nie udało się zainicjować dostawcy "{{provider}}". Sprawdź poświadczenia i konfigurację.', @@ -542,19 +542,19 @@ export const dict = { "sidebar.workspaces.enable": "Włącz przestrzenie robocze", "sidebar.workspaces.disable": "Wyłącz przestrzenie robocze", "sidebar.gettingStarted.title": "Pierwsze kroki", - "sidebar.gettingStarted.line1": "OpenCode zawiera darmowe modele, więc możesz zacząć od razu.", + "sidebar.gettingStarted.line1": "Kilo zawiera darmowe modele, więc możesz zacząć od razu.", "sidebar.gettingStarted.line2": "Połącz dowolnego dostawcę, aby używać modeli, w tym Claude, GPT, Gemini itp.", "sidebar.project.recentSessions": "Ostatnie sesje", "sidebar.project.viewAllSessions": "Zobacz wszystkie sesje", "sidebar.project.clearNotifications": "Wyczyść powiadomienia", - "app.name.desktop": "OpenCode Desktop", + "app.name.desktop": "Kilo Desktop", "settings.section.desktop": "Pulpit", "settings.section.server": "Serwer", "settings.tab.general": "Ogólne", "settings.tab.shortcuts": "Skróty", "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "WSL integration", - "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.", + "settings.desktop.wsl.description": "Run the Kilo server inside WSL on Windows.", "settings.general.section.appearance": "Wygląd", "settings.general.section.notifications": "Powiadomienia systemowe", "settings.general.section.updates": "Aktualizacje", @@ -562,14 +562,14 @@ export const dict = { "settings.general.section.feed": "Kanał", "settings.general.section.display": "Ekran", "settings.general.row.language.title": "Język", - "settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode", + "settings.general.row.language.description": "Zmień język wyświetlania dla Kilo", "settings.general.row.appearance.title": "Wygląd", - "settings.general.row.appearance.description": "Dostosuj wygląd OpenCode na swoim urządzeniu", + "settings.general.row.appearance.description": "Dostosuj wygląd Kilo na swoim urządzeniu", "settings.general.row.colorScheme.title": "Schemat kolorów", "settings.general.row.colorScheme.description": - "Wybierz, czy OpenCode ma używać motywu systemowego, jasnego czy ciemnego", + "Wybierz, czy Kilo ma używać motywu systemowego, jasnego czy ciemnego", "settings.general.row.theme.title": "Motyw", - "settings.general.row.theme.description": "Dostosuj motyw OpenCode.", + "settings.general.row.theme.description": "Dostosuj motyw Kilo.", "settings.general.row.font.title": "Czcionka kodu", "settings.general.row.font.description": "Dostosuj czcionkę używaną w blokach kodu", "settings.general.row.terminalFont.title": "Terminal Font", @@ -598,13 +598,13 @@ export const dict = { "settings.general.row.releaseNotes.title": "Informacje o wydaniu", "settings.general.row.releaseNotes.description": 'Pokazuj wyskakujące okna "Co nowego" po aktualizacjach', "settings.updates.row.startup.title": "Sprawdzaj aktualizacje przy uruchomieniu", - "settings.updates.row.startup.description": "Automatycznie sprawdzaj aktualizacje podczas uruchamiania OpenCode", + "settings.updates.row.startup.description": "Automatycznie sprawdzaj aktualizacje podczas uruchamiania Kilo", "settings.updates.row.check.title": "Sprawdź aktualizacje", "settings.updates.row.check.description": "Ręcznie sprawdź aktualizacje i zainstaluj, jeśli są dostępne", "settings.updates.action.checkNow": "Sprawdź teraz", "settings.updates.action.checking": "Sprawdzanie...", "settings.updates.toast.latest.title": "Masz najnowszą wersję", - "settings.updates.toast.latest.description": "Korzystasz z najnowszej wersji OpenCode.", + "settings.updates.toast.latest.description": "Korzystasz z najnowszej wersji Kilo.", "sound.option.none": "Brak", "sound.option.alert01": "Alert 01", "sound.option.alert02": "Alert 02", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index 6e6ca32030..6436f62c1f 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -115,7 +115,7 @@ export const dict = { "dialog.model.manage.description": "Настройте какие модели появляются в выборе модели", "dialog.model.manage.provider.toggle": "Переключить все модели {{provider}}", - "dialog.model.unpaid.freeModels.title": "Бесплатные модели от OpenCode", + "dialog.model.unpaid.freeModels.title": "Бесплатные модели от Kilo", "dialog.model.unpaid.addMore.title": "Добавьте больше моделей от популярных провайдеров", "dialog.provider.viewAll": "Показать больше провайдеров", @@ -128,7 +128,7 @@ export const dict = { "provider.connect.status.waiting": "Ожидание авторизации...", "provider.connect.status.failed": "Ошибка авторизации: {{error}}", "provider.connect.apiKey.description": - "Введите ваш API ключ {{provider}} для подключения аккаунта и использования моделей {{provider}} в OpenCode.", + "Введите ваш API ключ {{provider}} для подключения аккаунта и использования моделей {{provider}} в Kilo.", "provider.connect.apiKey.label": "{{provider}} API ключ", "provider.connect.apiKey.placeholder": "API ключ", "provider.connect.apiKey.required": "API ключ обязателен", @@ -142,7 +142,7 @@ export const dict = { "provider.connect.oauth.code.visit.prefix": "Посетите ", "provider.connect.oauth.code.visit.link": "эту ссылку", "provider.connect.oauth.code.visit.suffix": - " чтобы получить код авторизации для подключения аккаунта и использования моделей {{provider}} в OpenCode.", + " чтобы получить код авторизации для подключения аккаунта и использования моделей {{provider}} в Kilo.", "provider.connect.oauth.code.label": "{{method}} код авторизации", "provider.connect.oauth.code.placeholder": "Код авторизации", "provider.connect.oauth.code.required": "Код авторизации обязателен", @@ -150,7 +150,7 @@ export const dict = { "provider.connect.oauth.auto.visit.prefix": "Посетите ", "provider.connect.oauth.auto.visit.link": "эту ссылку", "provider.connect.oauth.auto.visit.suffix": - " и введите код ниже для подключения аккаунта и использования моделей {{provider}} в OpenCode.", + " и введите код ниже для подключения аккаунта и использования моделей {{provider}} в Kilo.", "provider.connect.oauth.auto.confirmationCode": "Код подтверждения", "provider.connect.toast.connected.title": "{{provider}} подключён", "provider.connect.toast.connected.description": "Модели {{provider}} теперь доступны.", @@ -307,7 +307,7 @@ export const dict = { "dialog.directory.empty": "Папки не найдены", "dialog.server.title": "Серверы", - "dialog.server.description": "Переключите сервер OpenCode к которому подключается приложение.", + "dialog.server.description": "Переключите сервер Kilo к которому подключается приложение.", "dialog.server.search.placeholder": "Поиск серверов", "dialog.server.empty": "Серверов пока нет", "dialog.server.add.title": "Добавить сервер", @@ -440,7 +440,7 @@ export const dict = { "toast.session.listFailed.title": "Не удалось загрузить сессии для {{project}}", "toast.update.title": "Доступно обновление", - "toast.update.description": "Новая версия OpenCode ({{version}}) доступна для установки.", + "toast.update.description": "Новая версия Kilo ({{version}}) доступна для установки.", "toast.update.action.installRestart": "Установить и перезапустить", "toast.update.action.notYet": "Пока нет", @@ -451,7 +451,7 @@ export const dict = { "error.page.action.checking": "Проверка...", "error.page.action.checkUpdates": "Проверить обновления", "error.page.action.updateTo": "Обновить до {{version}}", - "error.page.report.prefix": "Пожалуйста, сообщите об этой ошибке команде OpenCode", + "error.page.report.prefix": "Пожалуйста, сообщите об этой ошибке команде Kilo", "error.page.report.discord": "в Discord", "error.page.version": "Версия: {{version}}", @@ -471,7 +471,7 @@ export const dict = { "error.chain.modelNotFound": "Модель не найдена: {{provider}}/{{model}}", "error.chain.checkConfig": "Проверьте названия провайдера/модели в конфиге (opencode.json)", "error.chain.mcpFailed": - 'MCP сервер "{{name}}" завершился с ошибкой. Обратите внимание, что OpenCode пока не поддерживает MCP авторизацию.', + 'MCP сервер "{{name}}" завершился с ошибкой. Обратите внимание, что Kilo пока не поддерживает MCP авторизацию.', "error.chain.providerAuthFailed": "Ошибка аутентификации провайдера ({{provider}}): {{message}}", "error.chain.providerInitFailed": 'Не удалось инициализировать провайдера "{{provider}}". Проверьте учётные данные и конфигурацию.', @@ -605,21 +605,21 @@ export const dict = { "sidebar.workspaces.enable": "Включить рабочие пространства", "sidebar.workspaces.disable": "Отключить рабочие пространства", "sidebar.gettingStarted.title": "Начало работы", - "sidebar.gettingStarted.line1": "OpenCode включает бесплатные модели, чтобы вы могли начать сразу.", + "sidebar.gettingStarted.line1": "Kilo включает бесплатные модели, чтобы вы могли начать сразу.", "sidebar.gettingStarted.line2": "Подключите любого провайдера для использования моделей, включая Claude, GPT, Gemini и др.", "sidebar.project.recentSessions": "Недавние сессии", "sidebar.project.viewAllSessions": "Посмотреть все сессии", "sidebar.project.clearNotifications": "Очистить уведомления", - "app.name.desktop": "OpenCode Desktop", + "app.name.desktop": "Kilo Desktop", "settings.section.desktop": "Приложение", "settings.section.server": "Сервер", "settings.tab.general": "Основные", "settings.tab.shortcuts": "Горячие клавиши", "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "Интеграция с WSL", - "settings.desktop.wsl.description": "Запускать сервер OpenCode внутри WSL на Windows.", + "settings.desktop.wsl.description": "Запускать сервер Kilo внутри WSL на Windows.", "settings.general.section.appearance": "Внешний вид", "settings.general.section.notifications": "Системные уведомления", @@ -629,13 +629,13 @@ export const dict = { "settings.general.section.display": "Дисплей", "settings.general.row.language.title": "Язык", - "settings.general.row.language.description": "Изменить язык отображения OpenCode", + "settings.general.row.language.description": "Изменить язык отображения Kilo", "settings.general.row.appearance.title": "Внешний вид", - "settings.general.row.appearance.description": "Настройте как OpenCode выглядит на вашем устройстве", + "settings.general.row.appearance.description": "Настройте как Kilo выглядит на вашем устройстве", "settings.general.row.colorScheme.title": "Цветовая схема", - "settings.general.row.colorScheme.description": "Выберите, следует ли OpenCode системной, светлой или тёмной теме", + "settings.general.row.colorScheme.description": "Выберите, следует ли Kilo системной, светлой или тёмной теме", "settings.general.row.theme.title": "Тема", - "settings.general.row.theme.description": "Настройте оформление OpenCode.", + "settings.general.row.theme.description": "Настройте оформление Kilo.", "settings.general.row.font.title": "Шрифт кода", "settings.general.row.font.description": "Настройте шрифт, используемый в блоках кода", "settings.general.row.terminalFont.title": "Terminal Font", @@ -668,13 +668,13 @@ export const dict = { "settings.general.row.releaseNotes.description": 'Показывать всплывающие окна "Что нового" после обновлений', "settings.updates.row.startup.title": "Проверять обновления при запуске", - "settings.updates.row.startup.description": "Автоматически проверять обновления при запуске OpenCode", + "settings.updates.row.startup.description": "Автоматически проверять обновления при запуске Kilo", "settings.updates.row.check.title": "Проверить обновления", "settings.updates.row.check.description": "Проверить обновления вручную и установить, если доступны", "settings.updates.action.checkNow": "Проверить сейчас", "settings.updates.action.checking": "Проверка...", "settings.updates.toast.latest.title": "У вас последняя версия", - "settings.updates.toast.latest.description": "Вы используете последнюю версию OpenCode.", + "settings.updates.toast.latest.description": "Вы используете последнюю версию Kilo.", "sound.option.none": "Нет", "sound.option.alert01": "Alert 01", "sound.option.alert02": "Alert 02", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 84e5d3ff21..8188f671fb 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -115,7 +115,7 @@ export const dict = { "dialog.model.manage.description": "ปรับแต่งโมเดลที่จะปรากฏในตัวเลือกโมเดล", "dialog.model.manage.provider.toggle": "สลับโมเดลทั้งหมดของ {{provider}}", - "dialog.model.unpaid.freeModels.title": "โมเดลฟรีที่จัดหาให้โดย OpenCode", + "dialog.model.unpaid.freeModels.title": "โมเดลฟรีที่จัดหาให้โดย Kilo", "dialog.model.unpaid.addMore.title": "เพิ่มโมเดลเพิ่มเติมจากผู้ให้บริการยอดนิยม", "dialog.provider.viewAll": "แสดงผู้ให้บริการเพิ่มเติม", @@ -128,7 +128,7 @@ export const dict = { "provider.connect.status.waiting": "รอการอนุญาต...", "provider.connect.status.failed": "การอนุญาตล้มเหลว: {{error}}", "provider.connect.apiKey.description": - "ป้อนคีย์ API ของ {{provider}} เพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน OpenCode", + "ป้อนคีย์ API ของ {{provider}} เพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน Kilo", "provider.connect.apiKey.label": "คีย์ API ของ {{provider}}", "provider.connect.apiKey.placeholder": "คีย์ API", "provider.connect.apiKey.required": "ต้องใช้คีย์ API", @@ -142,7 +142,7 @@ export const dict = { "provider.connect.oauth.code.visit.prefix": "เยี่ยมชม ", "provider.connect.oauth.code.visit.link": "ลิงก์นี้", "provider.connect.oauth.code.visit.suffix": - " เพื่อรวบรวมรหัสการอนุญาตของคุณเพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน OpenCode", + " เพื่อรวบรวมรหัสการอนุญาตของคุณเพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน Kilo", "provider.connect.oauth.code.label": "รหัสการอนุญาต {{method}}", "provider.connect.oauth.code.placeholder": "รหัสการอนุญาต", "provider.connect.oauth.code.required": "ต้องใช้รหัสการอนุญาต", @@ -150,7 +150,7 @@ export const dict = { "provider.connect.oauth.auto.visit.prefix": "เยี่ยมชม ", "provider.connect.oauth.auto.visit.link": "ลิงก์นี้", "provider.connect.oauth.auto.visit.suffix": - " และป้อนรหัสด้านล่างเพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน OpenCode", + " และป้อนรหัสด้านล่างเพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน Kilo", "provider.connect.oauth.auto.confirmationCode": "รหัสยืนยัน", "provider.connect.toast.connected.title": "{{provider}} ที่เชื่อมต่อแล้ว", "provider.connect.toast.connected.description": "โมเดล {{provider}} พร้อมใช้งานแล้ว", @@ -307,7 +307,7 @@ export const dict = { "dialog.directory.empty": "ไม่พบโฟลเดอร์", "dialog.server.title": "เซิร์ฟเวอร์", - "dialog.server.description": "สลับเซิร์ฟเวอร์ OpenCode ที่แอปนี้เชื่อมต่อด้วย", + "dialog.server.description": "สลับเซิร์ฟเวอร์ Kilo ที่แอปนี้เชื่อมต่อด้วย", "dialog.server.search.placeholder": "ค้นหาเซิร์ฟเวอร์", "dialog.server.empty": "ยังไม่มีเซิร์ฟเวอร์", "dialog.server.add.title": "เพิ่มเซิร์ฟเวอร์", @@ -439,7 +439,7 @@ export const dict = { "toast.session.listFailed.title": "ไม่สามารถโหลดเซสชันสำหรับ {{project}}", "toast.update.title": "มีการอัปเดต", - "toast.update.description": "เวอร์ชันใหม่ของ OpenCode ({{version}}) พร้อมใช้งานสำหรับติดตั้ง", + "toast.update.description": "เวอร์ชันใหม่ของ Kilo ({{version}}) พร้อมใช้งานสำหรับติดตั้ง", "toast.update.action.installRestart": "ติดตั้งและรีสตาร์ท", "toast.update.action.notYet": "ยังไม่", @@ -450,7 +450,7 @@ export const dict = { "error.page.action.checking": "กำลังตรวจสอบ...", "error.page.action.checkUpdates": "ตรวจสอบการอัปเดต", "error.page.action.updateTo": "อัปเดตเป็น {{version}}", - "error.page.report.prefix": "โปรดรายงานข้อผิดพลาดนี้ให้ทีม OpenCode", + "error.page.report.prefix": "โปรดรายงานข้อผิดพลาดนี้ให้ทีม Kilo", "error.page.report.discord": "บน Discord", "error.page.version": "เวอร์ชัน: {{version}}", @@ -468,7 +468,7 @@ export const dict = { "error.chain.didYouMean": "คุณหมายถึง: {{suggestions}}", "error.chain.modelNotFound": "ไม่พบโมเดล: {{provider}}/{{model}}", "error.chain.checkConfig": "ตรวจสอบการกำหนดค่าของคุณ (opencode.json) ชื่อผู้ให้บริการ/โมเดล", - "error.chain.mcpFailed": 'เซิร์ฟเวอร์ MCP "{{name}}" ล้มเหลว โปรดทราบว่า OpenCode ยังไม่รองรับการตรวจสอบสิทธิ์ MCP', + "error.chain.mcpFailed": 'เซิร์ฟเวอร์ MCP "{{name}}" ล้มเหลว โปรดทราบว่า Kilo ยังไม่รองรับการตรวจสอบสิทธิ์ MCP', "error.chain.providerAuthFailed": "การตรวจสอบสิทธิ์ผู้ให้บริการล้มเหลว ({{provider}}): {{message}}", "error.chain.providerInitFailed": 'ไม่สามารถเริ่มต้นผู้ให้บริการ "{{provider}}" ตรวจสอบข้อมูลรับรองและการกำหนดค่า', "error.chain.configJsonInvalid": "ไฟล์กำหนดค่าที่ {{path}} ไม่ใช่ JSON(C) ที่ถูกต้อง", @@ -599,13 +599,13 @@ export const dict = { "sidebar.workspaces.enable": "เปิดใช้งานพื้นที่ทำงาน", "sidebar.workspaces.disable": "ปิดใช้งานพื้นที่ทำงาน", "sidebar.gettingStarted.title": "เริ่มต้นใช้งาน", - "sidebar.gettingStarted.line1": "OpenCode รวมถึงโมเดลฟรีเพื่อให้คุณเริ่มต้นได้ทันที", + "sidebar.gettingStarted.line1": "Kilo รวมถึงโมเดลฟรีเพื่อให้คุณเริ่มต้นได้ทันที", "sidebar.gettingStarted.line2": "เชื่อมต่อผู้ให้บริการใด ๆ เพื่อใช้โมเดล รวมถึง Claude, GPT, Gemini ฯลฯ", "sidebar.project.recentSessions": "เซสชันล่าสุด", "sidebar.project.viewAllSessions": "ดูเซสชันทั้งหมด", "sidebar.project.clearNotifications": "ล้างการแจ้งเตือน", - "app.name.desktop": "OpenCode Desktop", + "app.name.desktop": "Kilo Desktop", "settings.section.desktop": "เดสก์ท็อป", "settings.section.server": "เซิร์ฟเวอร์", @@ -613,7 +613,7 @@ export const dict = { "settings.tab.shortcuts": "ทางลัด", "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "การรวม WSL", - "settings.desktop.wsl.description": "เรียกใช้เซิร์ฟเวอร์ OpenCode ภายใน WSL บน Windows", + "settings.desktop.wsl.description": "เรียกใช้เซิร์ฟเวอร์ Kilo ภายใน WSL บน Windows", "settings.general.section.appearance": "รูปลักษณ์", "settings.general.section.notifications": "การแจ้งเตือนระบบ", @@ -623,13 +623,13 @@ export const dict = { "settings.general.section.display": "การแสดงผล", "settings.general.row.language.title": "ภาษา", - "settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ OpenCode", + "settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ Kilo", "settings.general.row.appearance.title": "รูปลักษณ์", - "settings.general.row.appearance.description": "ปรับแต่งวิธีการที่ OpenCode มีลักษณะบนอุปกรณ์ของคุณ", + "settings.general.row.appearance.description": "ปรับแต่งวิธีการที่ Kilo มีลักษณะบนอุปกรณ์ของคุณ", "settings.general.row.colorScheme.title": "โทนสี", - "settings.general.row.colorScheme.description": "เลือกว่าจะให้ OpenCode ใช้ธีมตามระบบ สว่าง หรือมืด", + "settings.general.row.colorScheme.description": "เลือกว่าจะให้ Kilo ใช้ธีมตามระบบ สว่าง หรือมืด", "settings.general.row.theme.title": "ธีม", - "settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม", + "settings.general.row.theme.description": "ปรับแต่งวิธีการที่ Kilo มีธีม", "settings.general.row.font.title": "ฟอนต์โค้ด", "settings.general.row.font.description": "ปรับแต่งฟอนต์ที่ใช้ในบล็อกโค้ด", "settings.general.row.terminalFont.title": "Terminal Font", @@ -658,13 +658,13 @@ export const dict = { "settings.general.row.releaseNotes.description": "แสดงป๊อปอัพ What's New หลังจากอัปเดต", "settings.updates.row.startup.title": "ตรวจสอบการอัปเดตเมื่อเริ่มต้น", - "settings.updates.row.startup.description": "ตรวจสอบการอัปเดตโดยอัตโนมัติเมื่อ OpenCode เปิดใช้งาน", + "settings.updates.row.startup.description": "ตรวจสอบการอัปเดตโดยอัตโนมัติเมื่อ Kilo เปิดใช้งาน", "settings.updates.row.check.title": "ตรวจสอบการอัปเดต", "settings.updates.row.check.description": "ตรวจสอบการอัปเดตด้วยตนเองและติดตั้งหากมี", "settings.updates.action.checkNow": "ตรวจสอบทันที", "settings.updates.action.checking": "กำลังตรวจสอบ...", "settings.updates.toast.latest.title": "คุณเป็นเวอร์ชันล่าสุดแล้ว", - "settings.updates.toast.latest.description": "คุณกำลังใช้งาน OpenCode เวอร์ชันล่าสุด", + "settings.updates.toast.latest.description": "คุณกำลังใช้งาน Kilo เวอร์ชันล่าสุด", "sound.option.none": "ไม่มี", "sound.option.alert01": "เสียงเตือน 01", diff --git a/packages/app/src/i18n/tr.ts b/packages/app/src/i18n/tr.ts index 06e233cb51..266749d68c 100644 --- a/packages/app/src/i18n/tr.ts +++ b/packages/app/src/i18n/tr.ts @@ -119,7 +119,7 @@ export const dict = { "dialog.model.manage.description": "Model seçicide hangi modellerin görüneceğini özelleştirin.", "dialog.model.manage.provider.toggle": "Tüm {{provider}} modellerini aç/kapat", - "dialog.model.unpaid.freeModels.title": "OpenCode tarafından sunulan ücretsiz modeller", + "dialog.model.unpaid.freeModels.title": "Kilo tarafından sunulan ücretsiz modeller", "dialog.model.unpaid.addMore.title": "Popüler sağlayıcılardan daha fazla model ekleyin", "dialog.provider.viewAll": "Daha fazla sağlayıcı göster", @@ -132,7 +132,7 @@ export const dict = { "provider.connect.status.waiting": "Yetkilendirme bekleniyor...", "provider.connect.status.failed": "Yetkilendirme başarısız: {{error}}", "provider.connect.apiKey.description": - "{{provider}} hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için {{provider}} API anahtarınızı girin.", + "{{provider}} hesabınızı bağlamak ve Kilo'da {{provider}} modellerini kullanmak için {{provider}} API anahtarınızı girin.", "provider.connect.apiKey.label": "{{provider}} API anahtarı", "provider.connect.apiKey.placeholder": "API anahtarı", "provider.connect.apiKey.required": "API anahtarı gerekli", @@ -144,7 +144,7 @@ export const dict = { "provider.connect.opencodeZen.visit.link": "opencode.ai/zen", "provider.connect.opencodeZen.visit.suffix": " adresini ziyaret ederek API anahtarınızı alın.", "provider.connect.oauth.code.visit.prefix": - "Hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için ", + "Hesabınızı bağlamak ve Kilo'da {{provider}} modellerini kullanmak için ", "provider.connect.oauth.code.visit.link": "bu bağlantıya", "provider.connect.oauth.code.visit.suffix": " tıklayarak yetkilendirme kodunuzu alın.", "provider.connect.oauth.code.label": "{{method}} yetkilendirme kodu", @@ -154,7 +154,7 @@ export const dict = { "provider.connect.oauth.auto.visit.prefix": "", "provider.connect.oauth.auto.visit.link": "Bu bağlantıya", "provider.connect.oauth.auto.visit.suffix": - " tıklayarak aşağıdaki kodu girin ve hesabınızı bağlayarak OpenCode'da {{provider}} modellerini kullanın.", + " tıklayarak aşağıdaki kodu girin ve hesabınızı bağlayarak Kilo'da {{provider}} modellerini kullanın.", "provider.connect.oauth.auto.confirmationCode": "Onay kodu", "provider.connect.toast.connected.title": "{{provider}} bağlandı", "provider.connect.toast.connected.description": "{{provider}} modelleri artık kullanımda.", @@ -312,7 +312,7 @@ export const dict = { "dialog.directory.empty": "Klasör bulunamadı", "dialog.server.title": "Sunucular", - "dialog.server.description": "Bu uygulamanın hangi OpenCode sunucusuna bağlanacağını değiştirin.", + "dialog.server.description": "Bu uygulamanın hangi Kilo sunucusuna bağlanacağını değiştirin.", "dialog.server.search.placeholder": "Sunucu ara", "dialog.server.empty": "Henüz sunucu yok", "dialog.server.add.title": "Sunucu ekle", @@ -444,7 +444,7 @@ export const dict = { "toast.session.listFailed.title": "{{project}} için oturumlar yüklenemedi", "toast.update.title": "Güncelleme mevcut", - "toast.update.description": "OpenCode'un yeni bir sürümü ({{version}}) yüklemeye hazır.", + "toast.update.description": "Kilo'un yeni bir sürümü ({{version}}) yüklemeye hazır.", "toast.update.action.installRestart": "Yükle ve yeniden başlat", "toast.update.action.notYet": "Şimdi değil", @@ -455,7 +455,7 @@ export const dict = { "error.page.action.checking": "Kontrol ediliyor...", "error.page.action.checkUpdates": "Güncellemeleri kontrol et", "error.page.action.updateTo": "{{version}} sürümüne güncelle", - "error.page.report.prefix": "Lütfen bu hatayı OpenCode ekibine bildirin", + "error.page.report.prefix": "Lütfen bu hatayı Kilo ekibine bildirin", "error.page.report.discord": "Discord üzerinden", "error.page.version": "Sürüm: {{version}}", @@ -475,7 +475,7 @@ export const dict = { "error.chain.modelNotFound": "Model bulunamadı: {{provider}}/{{model}}", "error.chain.checkConfig": "Yapılandırma dosyanızı (opencode.json) sağlayıcı/model adlarını kontrol edin", "error.chain.mcpFailed": - 'MCP sunucusu "{{name}}" başarısız oldu. Not: OpenCode henüz MCP kimlik doğrulamasını desteklemiyor.', + 'MCP sunucusu "{{name}}" başarısız oldu. Not: Kilo henüz MCP kimlik doğrulamasını desteklemiyor.', "error.chain.providerAuthFailed": "Sağlayıcı kimlik doğrulaması başarısız ({{provider}}): {{message}}", "error.chain.providerInitFailed": '"{{provider}}" sağlayıcısı başlatılamadı. Kimlik bilgilerini ve yapılandırmayı kontrol edin.', @@ -611,13 +611,13 @@ export const dict = { "sidebar.workspaces.enable": "Çalışma alanlarını etkinleştir", "sidebar.workspaces.disable": "Çalışma alanlarını devre dışı bırak", "sidebar.gettingStarted.title": "Başlarken", - "sidebar.gettingStarted.line1": "OpenCode ücretsiz modeller içerir, böylece hemen başlayabilirsiniz.", + "sidebar.gettingStarted.line1": "Kilo ücretsiz modeller içerir, böylece hemen başlayabilirsiniz.", "sidebar.gettingStarted.line2": "Claude, GPT, Gemini vb. modelleri kullanmak için herhangi bir sağlayıcı bağlayın.", "sidebar.project.recentSessions": "Son oturumlar", "sidebar.project.viewAllSessions": "Tüm oturumları görüntüle", "sidebar.project.clearNotifications": "Bildirimleri temizle", - "app.name.desktop": "OpenCode Masaüstü", + "app.name.desktop": "Kilo Masaüstü", "settings.section.desktop": "Masaüstü", "settings.section.server": "Sunucu", @@ -625,7 +625,7 @@ export const dict = { "settings.tab.shortcuts": "Kısayollar", "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "WSL entegrasyonu", - "settings.desktop.wsl.description": "OpenCode sunucusunu Windows'ta WSL içinde çalıştırın.", + "settings.desktop.wsl.description": "Kilo sunucusunu Windows'ta WSL içinde çalıştırın.", "settings.general.section.appearance": "Görünüm", "settings.general.section.notifications": "Sistem bildirimleri", @@ -635,14 +635,14 @@ export const dict = { "settings.general.section.display": "Ekran", "settings.general.row.language.title": "Dil", - "settings.general.row.language.description": "OpenCode'un görünüm dilini değiştirin", + "settings.general.row.language.description": "Kilo'un görünüm dilini değiştirin", "settings.general.row.appearance.title": "Görünüm", - "settings.general.row.appearance.description": "OpenCode'un cihazınızdaki görünümünü özelleştirin", + "settings.general.row.appearance.description": "Kilo'un cihazınızdaki görünümünü özelleştirin", "settings.general.row.colorScheme.title": "Renk şeması", "settings.general.row.colorScheme.description": - "OpenCode'un sistem, açık veya koyu temayı takip etip etmeyeceğini seçin", + "Kilo'un sistem, açık veya koyu temayı takip etip etmeyeceğini seçin", "settings.general.row.theme.title": "Tema", - "settings.general.row.theme.description": "OpenCode'un temasını özelleştirin.", + "settings.general.row.theme.description": "Kilo'un temasını özelleştirin.", "settings.general.row.font.title": "Kod Yazı Tipi", "settings.general.row.font.description": "Kod bloklarında kullanılan yazı tipini özelleştirin", "settings.general.row.terminalFont.title": "Terminal Font", @@ -677,13 +677,13 @@ export const dict = { "settings.general.row.releaseNotes.description": "Güncellemelerden sonra Yenilikler bildirimlerini göster", "settings.updates.row.startup.title": "Başlangıçta güncellemeleri kontrol et", - "settings.updates.row.startup.description": "OpenCode başladığında otomatik güncelleme kontrolü yap", + "settings.updates.row.startup.description": "Kilo başladığında otomatik güncelleme kontrolü yap", "settings.updates.row.check.title": "Güncellemeleri kontrol et", "settings.updates.row.check.description": "Elle güncelleme kontrolü yap ve varsa yükle", "settings.updates.action.checkNow": "Şimdi kontrol et", "settings.updates.action.checking": "Kontrol ediliyor...", "settings.updates.toast.latest.title": "Güncelsiniz", - "settings.updates.toast.latest.description": "OpenCode'un en son sürümünü kullanıyorsunuz.", + "settings.updates.toast.latest.description": "Kilo'un en son sürümünü kullanıyorsunuz.", "sound.option.none": "Yok", "sound.option.alert01": "Uyarı 01", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index fa83707e8d..52c20f9b5e 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -141,7 +141,7 @@ export const dict = { "dialog.model.manage": "管理模型", "dialog.model.manage.description": "自定义模型选择器中显示的模型。", "dialog.model.manage.provider.toggle": "切换所有 {{provider}} 模型", - "dialog.model.unpaid.freeModels.title": "OpenCode 提供的免费模型", + "dialog.model.unpaid.freeModels.title": "Kilo 提供的免费模型", "dialog.model.unpaid.addMore.title": "从热门提供商添加更多模型", "dialog.provider.viewAll": "查看更多提供商", @@ -154,7 +154,7 @@ export const dict = { "provider.connect.status.waiting": "等待授权...", "provider.connect.status.failed": "授权失败:{{error}}", "provider.connect.apiKey.description": - "输入你的 {{provider}} API 密钥以连接帐户,并在 OpenCode 中使用 {{provider}} 模型。", + "输入你的 {{provider}} API 密钥以连接帐户,并在 Kilo 中使用 {{provider}} 模型。", "provider.connect.apiKey.label": "{{provider}} API 密钥", "provider.connect.apiKey.placeholder": "API 密钥", "provider.connect.apiKey.required": "API 密钥为必填项", @@ -165,14 +165,14 @@ export const dict = { "provider.connect.opencodeZen.visit.suffix": " 获取你的 API 密钥。", "provider.connect.oauth.code.visit.prefix": "访问 ", "provider.connect.oauth.code.visit.link": "此链接", - "provider.connect.oauth.code.visit.suffix": " 获取授权码,以连接你的帐户并在 OpenCode 中使用 {{provider}} 模型。", + "provider.connect.oauth.code.visit.suffix": " 获取授权码,以连接你的帐户并在 Kilo 中使用 {{provider}} 模型。", "provider.connect.oauth.code.label": "{{method}} 授权码", "provider.connect.oauth.code.placeholder": "授权码", "provider.connect.oauth.code.required": "授权码为必填项", "provider.connect.oauth.code.invalid": "授权码无效", "provider.connect.oauth.auto.visit.prefix": "访问 ", "provider.connect.oauth.auto.visit.link": "此链接", - "provider.connect.oauth.auto.visit.suffix": " 并输入以下代码,以连接你的帐户并在 OpenCode 中使用 {{provider}} 模型。", + "provider.connect.oauth.auto.visit.suffix": " 并输入以下代码,以连接你的帐户并在 Kilo 中使用 {{provider}} 模型。", "provider.connect.oauth.auto.confirmationCode": "确认码", "provider.connect.toast.connected.title": "{{provider}} 已连接", "provider.connect.toast.connected.description": "现在可以使用 {{provider}} 模型了。", @@ -327,7 +327,7 @@ export const dict = { "dialog.directory.empty": "未找到文件夹", "dialog.server.title": "服务器", - "dialog.server.description": "切换此应用连接的 OpenCode 服务器。", + "dialog.server.description": "切换此应用连接的 Kilo 服务器。", "dialog.server.search.placeholder": "搜索服务器", "dialog.server.empty": "暂无服务器", "dialog.server.add.title": "添加服务器", @@ -444,7 +444,7 @@ export const dict = { "toast.session.unshare.failed.description": "取消分享会话时发生错误", "toast.session.listFailed.title": "无法加载 {{project}} 的会话", "toast.update.title": "有可用更新", - "toast.update.description": "OpenCode 有新版本 ({{version}}) 可安装。", + "toast.update.description": "Kilo 有新版本 ({{version}}) 可安装。", "toast.update.action.installRestart": "安装并重启", "toast.update.action.notYet": "稍后", @@ -455,7 +455,7 @@ export const dict = { "error.page.action.checking": "检查中...", "error.page.action.checkUpdates": "检查更新", "error.page.action.updateTo": "更新到 {{version}}", - "error.page.report.prefix": "请将此错误报告给 OpenCode 团队", + "error.page.report.prefix": "请将此错误报告给 Kilo 团队", "error.page.report.discord": "在 Discord 上", "error.page.version": "版本:{{version}}", "error.dev.rootNotFound": "未找到根元素。你是不是忘了把它添加到 index.html?或者 id 属性拼写错了?", @@ -472,7 +472,7 @@ export const dict = { "error.chain.didYouMean": "你是不是想输入:{{suggestions}}", "error.chain.modelNotFound": "未找到模型:{{provider}}/{{model}}", "error.chain.checkConfig": "请检查你的配置 (opencode.json) 中的 provider/model 名称", - "error.chain.mcpFailed": 'MCP 服务器 "{{name}}" 启动失败。注意: OpenCode 暂不支持 MCP 认证。', + "error.chain.mcpFailed": 'MCP 服务器 "{{name}}" 启动失败。注意: Kilo 暂不支持 MCP 认证。', "error.chain.providerAuthFailed": "提供商认证失败({{provider}}):{{message}}", "error.chain.providerInitFailed": '无法初始化提供商 "{{provider}}"。请检查凭据和配置。', "error.chain.configJsonInvalid": "配置文件 {{path}} 不是有效的 JSON(C)", @@ -598,13 +598,13 @@ export const dict = { "sidebar.workspaces.enable": "启用工作区", "sidebar.workspaces.disable": "禁用工作区", "sidebar.gettingStarted.title": "入门", - "sidebar.gettingStarted.line1": "OpenCode 提供免费模型,你可以立即开始使用。", + "sidebar.gettingStarted.line1": "Kilo 提供免费模型,你可以立即开始使用。", "sidebar.gettingStarted.line2": "连接任意提供商即可使用更多模型,如 Claude、GPT、Gemini 等。", "sidebar.project.recentSessions": "最近会话", "sidebar.project.viewAllSessions": "查看全部会话", "sidebar.project.clearNotifications": "清除通知", - "app.name.desktop": "OpenCode Desktop", + "app.name.desktop": "Kilo Desktop", "settings.section.desktop": "桌面", "settings.section.server": "服务器", @@ -614,7 +614,7 @@ export const dict = { "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "WSL 集成", - "settings.desktop.wsl.description": "在 Windows 的 WSL 环境中运行 OpenCode 服务器。", + "settings.desktop.wsl.description": "在 Windows 的 WSL 环境中运行 Kilo 服务器。", "settings.general.section.appearance": "外观", "settings.general.section.notifications": "系统通知", @@ -623,13 +623,13 @@ export const dict = { "settings.general.section.feed": "动态", "settings.general.section.display": "显示", "settings.general.row.language.title": "语言", - "settings.general.row.language.description": "更改 OpenCode 的显示语言", + "settings.general.row.language.description": "更改 Kilo 的显示语言", "settings.general.row.appearance.title": "外观", - "settings.general.row.appearance.description": "自定义 OpenCode 在你的设备上的外观", + "settings.general.row.appearance.description": "自定义 Kilo 在你的设备上的外观", "settings.general.row.colorScheme.title": "配色方案", - "settings.general.row.colorScheme.description": "选择 OpenCode 跟随系统、浅色或深色主题", + "settings.general.row.colorScheme.description": "选择 Kilo 跟随系统、浅色或深色主题", "settings.general.row.theme.title": "主题", - "settings.general.row.theme.description": "自定义 OpenCode 的主题。", + "settings.general.row.theme.description": "自定义 Kilo 的主题。", "settings.general.row.font.title": "代码字体", "settings.general.row.font.description": "自定义代码块使用的字体", "settings.general.row.terminalFont.title": "Terminal Font", @@ -655,13 +655,13 @@ export const dict = { "settings.general.row.releaseNotes.description": "更新后显示“新功能”弹窗", "settings.updates.row.startup.title": "启动时检查更新", - "settings.updates.row.startup.description": "在 OpenCode 启动时自动检查更新", + "settings.updates.row.startup.description": "在 Kilo 启动时自动检查更新", "settings.updates.row.check.title": "检查更新", "settings.updates.row.check.description": "手动检查更新并在有更新时安装", "settings.updates.action.checkNow": "立即检查", "settings.updates.action.checking": "正在检查...", "settings.updates.toast.latest.title": "已是最新版本", - "settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。", + "settings.updates.toast.latest.description": "你正在使用最新版本的 Kilo。", "sound.option.none": "无", "sound.option.alert01": "警报 01", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index e9d265acc0..61f5059868 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -119,7 +119,7 @@ export const dict = { "dialog.model.manage.description": "自訂模型選擇器中顯示的模型。", "dialog.model.manage.provider.toggle": "切換所有 {{provider}} 模型", - "dialog.model.unpaid.freeModels.title": "OpenCode 提供的免費模型", + "dialog.model.unpaid.freeModels.title": "Kilo 提供的免費模型", "dialog.model.unpaid.addMore.title": "從熱門提供者新增更多模型", "dialog.provider.viewAll": "查看更多提供者", @@ -132,7 +132,7 @@ export const dict = { "provider.connect.status.waiting": "等待授權...", "provider.connect.status.failed": "授權失敗: {{error}}", "provider.connect.apiKey.description": - "輸入你的 {{provider}} API 金鑰以連線帳戶,並在 OpenCode 中使用 {{provider}} 模型。", + "輸入你的 {{provider}} API 金鑰以連線帳戶,並在 Kilo 中使用 {{provider}} 模型。", "provider.connect.apiKey.label": "{{provider}} API 金鑰", "provider.connect.apiKey.placeholder": "API 金鑰", "provider.connect.apiKey.required": "API 金鑰為必填", @@ -143,7 +143,7 @@ export const dict = { "provider.connect.opencodeZen.visit.suffix": " 取得你的 API 金鑰。", "provider.connect.oauth.code.visit.prefix": "造訪 ", "provider.connect.oauth.code.visit.link": "此連結", - "provider.connect.oauth.code.visit.suffix": " 取得授權碼,以連線你的帳戶並在 OpenCode 中使用 {{provider}} 模型。", + "provider.connect.oauth.code.visit.suffix": " 取得授權碼,以連線你的帳戶並在 Kilo 中使用 {{provider}} 模型。", "provider.connect.oauth.code.label": "{{method}} 授權碼", "provider.connect.oauth.code.placeholder": "授權碼", "provider.connect.oauth.code.required": "授權碼為必填", @@ -151,7 +151,7 @@ export const dict = { "provider.connect.oauth.auto.visit.prefix": "造訪 ", "provider.connect.oauth.auto.visit.link": "此連結", "provider.connect.oauth.auto.visit.suffix": - " 並輸入以下程式碼,以連線你的帳戶並在 OpenCode 中使用 {{provider}} 模型。", + " 並輸入以下程式碼,以連線你的帳戶並在 Kilo 中使用 {{provider}} 模型。", "provider.connect.oauth.auto.confirmationCode": "確認碼", "provider.connect.toast.connected.title": "{{provider}} 已連線", "provider.connect.toast.connected.description": "現在可以使用 {{provider}} 模型了。", @@ -307,7 +307,7 @@ export const dict = { "dialog.directory.empty": "找不到資料夾", "dialog.server.title": "伺服器", - "dialog.server.description": "切換此應用程式連線的 OpenCode 伺服器。", + "dialog.server.description": "切換此應用程式連線的 Kilo 伺服器。", "dialog.server.search.placeholder": "搜尋伺服器", "dialog.server.empty": "暫無伺服器", "dialog.server.add.title": "新增伺服器", @@ -437,7 +437,7 @@ export const dict = { "toast.session.listFailed.title": "無法載入 {{project}} 的工作階段", "toast.update.title": "有可用更新", - "toast.update.description": "OpenCode 有新版本 ({{version}}) 可安裝。", + "toast.update.description": "Kilo 有新版本 ({{version}}) 可安裝。", "toast.update.action.installRestart": "安裝並重新啟動", "toast.update.action.notYet": "稍後", @@ -448,7 +448,7 @@ export const dict = { "error.page.action.checking": "檢查中...", "error.page.action.checkUpdates": "檢查更新", "error.page.action.updateTo": "更新到 {{version}}", - "error.page.report.prefix": "請將此錯誤回報給 OpenCode 團隊", + "error.page.report.prefix": "請將此錯誤回報給 Kilo 團隊", "error.page.report.discord": "在 Discord 上", "error.page.version": "版本: {{version}}", @@ -466,7 +466,7 @@ export const dict = { "error.chain.didYouMean": "你是不是想輸入: {{suggestions}}", "error.chain.modelNotFound": "找不到模型: {{provider}}/{{model}}", "error.chain.checkConfig": "請檢查你的設定 (opencode.json) 中的 provider/model 名稱", - "error.chain.mcpFailed": 'MCP 伺服器 "{{name}}" 啟動失敗。注意: OpenCode 暫不支援 MCP 認證。', + "error.chain.mcpFailed": 'MCP 伺服器 "{{name}}" 啟動失敗。注意: Kilo 暫不支援 MCP 認證。', "error.chain.providerAuthFailed": "提供者認證失敗 ({{provider}}): {{message}}", "error.chain.providerInitFailed": '無法初始化提供者 "{{provider}}"。請檢查憑證和設定。', "error.chain.configJsonInvalid": "設定檔 {{path}} 不是有效的 JSON(C)", @@ -595,20 +595,20 @@ export const dict = { "sidebar.workspaces.enable": "啟用工作區", "sidebar.workspaces.disable": "停用工作區", "sidebar.gettingStarted.title": "開始使用", - "sidebar.gettingStarted.line1": "OpenCode 提供免費模型,你可以立即開始使用。", + "sidebar.gettingStarted.line1": "Kilo 提供免費模型,你可以立即開始使用。", "sidebar.gettingStarted.line2": "連線任意提供者即可使用更多模型,如 Claude、GPT、Gemini 等。", "sidebar.project.recentSessions": "最近工作階段", "sidebar.project.viewAllSessions": "查看全部工作階段", "sidebar.project.clearNotifications": "清除通知", - "app.name.desktop": "OpenCode Desktop", + "app.name.desktop": "Kilo Desktop", "settings.section.desktop": "桌面", "settings.section.server": "伺服器", "settings.tab.general": "一般", "settings.tab.shortcuts": "快速鍵", "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "WSL 整合", - "settings.desktop.wsl.description": "在 Windows 上的 WSL 中執行 OpenCode 伺服器。", + "settings.desktop.wsl.description": "在 Windows 上的 WSL 中執行 Kilo 伺服器。", "settings.general.section.appearance": "外觀", "settings.general.section.notifications": "系統通知", @@ -618,13 +618,13 @@ export const dict = { "settings.general.section.display": "顯示", "settings.general.row.language.title": "語言", - "settings.general.row.language.description": "變更 OpenCode 的顯示語言", + "settings.general.row.language.description": "變更 Kilo 的顯示語言", "settings.general.row.appearance.title": "外觀", - "settings.general.row.appearance.description": "自訂 OpenCode 在你的裝置上的外觀", + "settings.general.row.appearance.description": "自訂 Kilo 在你的裝置上的外觀", "settings.general.row.colorScheme.title": "配色方案", - "settings.general.row.colorScheme.description": "選擇 OpenCode 要跟隨系統、淺色或深色主題", + "settings.general.row.colorScheme.description": "選擇 Kilo 要跟隨系統、淺色或深色主題", "settings.general.row.theme.title": "主題", - "settings.general.row.theme.description": "自訂 OpenCode 的主題。", + "settings.general.row.theme.description": "自訂 Kilo 的主題。", "settings.general.row.font.title": "程式碼字型", "settings.general.row.font.description": "自訂程式碼區塊使用的字型", "settings.general.row.terminalFont.title": "Terminal Font", @@ -652,13 +652,13 @@ export const dict = { "settings.general.row.releaseNotes.description": "更新後顯示「新功能」彈出視窗", "settings.updates.row.startup.title": "啟動時檢查更新", - "settings.updates.row.startup.description": "在 OpenCode 啟動時自動檢查更新", + "settings.updates.row.startup.description": "在 Kilo 啟動時自動檢查更新", "settings.updates.row.check.title": "檢查更新", "settings.updates.row.check.description": "手動檢查更新並在有更新時安裝", "settings.updates.action.checkNow": "立即檢查", "settings.updates.action.checking": "檢查中...", "settings.updates.toast.latest.title": "已是最新版本", - "settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。", + "settings.updates.toast.latest.description": "你正在使用最新版本的 Kilo。", "sound.option.none": "無", "sound.option.alert01": "警報 01", diff --git a/packages/app/src/pages/error.tsx b/packages/app/src/pages/error.tsx index ba0045ec93..bae8f44e54 100644 --- a/packages/app/src/pages/error.tsx +++ b/packages/app/src/pages/error.tsx @@ -300,7 +300,7 @@ export const ErrorPage: Component = (props) => {