diff --git a/packages/opencode/src/kilo-sessions/kilo-sessions.ts b/packages/opencode/src/kilo-sessions/kilo-sessions.ts index 59cf4a90bb4..32719a1c92a 100644 --- a/packages/opencode/src/kilo-sessions/kilo-sessions.ts +++ b/packages/opencode/src/kilo-sessions/kilo-sessions.ts @@ -14,6 +14,7 @@ import { IngestQueue } from "@/kilo-sessions/ingest-queue" import { clearInFlightCache, withInFlightCache } from "@/kilo-sessions/inflight-cache" import type * as SDK from "@kilocode/sdk/v2" import z from "zod" +import { Schema } from "effect" import { KILO_API_BASE } from "@kilocode/kilo-gateway" import { Config } from "@/config" import { Instance } from "@/project/instance" @@ -31,9 +32,9 @@ export namespace KiloSessions { export const Event = { RemoteStatusChanged: BusEvent.define( "kilo-sessions.remote-status-changed", - z.object({ - enabled: z.boolean(), - connected: z.boolean(), + Schema.Struct({ + enabled: Schema.Boolean, + connected: Schema.Boolean, }), ), } diff --git a/packages/opencode/src/kilo-sessions/remote-sender.ts b/packages/opencode/src/kilo-sessions/remote-sender.ts index 40feff1afe5..0148d859573 100644 --- a/packages/opencode/src/kilo-sessions/remote-sender.ts +++ b/packages/opencode/src/kilo-sessions/remote-sender.ts @@ -17,6 +17,7 @@ import { QuestionID } from "@/question/schema" import { ModelID, ProviderID } from "@/provider/schema" import { Log } from "@/util" import z from "zod" +import { zodObject } from "@/util/effect-zod" const QuestionData = z.object({ requestID: z.string(), @@ -36,9 +37,9 @@ const SuggestionData = z.object({ // kilocode_change start — lazy init to avoid circular dependency // (Server → RemoteRoutes → RemoteSender → SessionPrompt at module load time) -let _remotePromptInput: ReturnType | undefined +let _remotePromptInput: z.ZodObject | undefined function getRemotePromptInput() { - return (_remotePromptInput ??= SessionPrompt.PromptInput.extend({ + return (_remotePromptInput ??= zodObject(SessionPrompt.PromptInput).extend({ model: z.string().optional(), })) } @@ -265,7 +266,7 @@ export namespace RemoteSender { }) return } - const input = SessionPrompt.PromptInput.safeParse( + const input = SessionPrompt.PromptInput.zod.safeParse( normalizePrompt(parsed.data as SessionPrompt.PromptInput & { model?: string }), ) if (!input.success) { diff --git a/packages/opencode/src/kilocode/session/index.ts b/packages/opencode/src/kilocode/session/index.ts index 152aee872c5..47019233897 100644 --- a/packages/opencode/src/kilocode/session/index.ts +++ b/packages/opencode/src/kilocode/session/index.ts @@ -1,6 +1,7 @@ // kilocode_change - new file import { remapChildren as _remapChildren } from "./fork" import z from "zod" +import { Schema } from "effect" import { BusEvent } from "@/bus/bus-event" import { Session } from "@/session" import { MessageID, SessionID } from "@/session/schema" @@ -23,23 +24,25 @@ export namespace KiloSession { // Events // --------------------------------------------------------------------------- + const CloseReasonSchema = Schema.Literals(["completed", "error", "interrupted"]) + export const Event = { TurnOpen: BusEvent.define( "session.turn.open", - z.object({ - sessionID: z.string(), + Schema.Struct({ + sessionID: SessionID, }), ), TurnClose: BusEvent.define( "session.turn.close", - z.object({ - sessionID: z.string(), - reason: z.enum(["completed", "error", "interrupted"]), + Schema.Struct({ + sessionID: SessionID, + reason: CloseReasonSchema, }), ), } - export type CloseReason = z.infer["reason"] + export type CloseReason = Schema.Schema.Type // --------------------------------------------------------------------------- // Per-session platform override (telemetry attribution) diff --git a/packages/opencode/src/kilocode/suggestion/index.ts b/packages/opencode/src/kilocode/suggestion/index.ts index 4607d09602c..ac34b618fb1 100644 --- a/packages/opencode/src/kilocode/suggestion/index.ts +++ b/packages/opencode/src/kilocode/suggestion/index.ts @@ -2,8 +2,10 @@ import { Bus } from "../../bus" import { BusEvent } from "../../bus/bus-event" import { Identifier } from "../../id/id" import { SessionID } from "../../session/schema" +import { ZodOverride } from "../../util/effect-zod" import { Log } from "../../util" import z from "zod" +import { Schema } from "effect" import { KiloSessionPromptQueue } from "../session/prompt-queue" export namespace Suggestion { @@ -20,6 +22,18 @@ export namespace Suggestion { }) export type Action = z.infer + export const ActionSchema = Schema.Struct({ + label: Schema.String.annotate({ description: "Button or option label (1-5 words)" }), + description: Schema.optional(Schema.String).annotate({ + description: "Brief explanation of what this action does", + }), + prompt: Schema.String.annotate({ + description: "Synthetic user prompt to inject when this action is accepted", + }), + }) + + const SuggestionIDSchema = Schema.String.annotate({ [ZodOverride]: Identifier.schema("suggestion") }) + export const Info = z .object({ text: z.string().describe("Suggestion text shown to the user"), @@ -54,27 +68,41 @@ export namespace Suggestion { }) export type Request = z.infer + const RequestSchema = Schema.Struct({ + id: SuggestionIDSchema, + sessionID: SessionID, + text: Schema.String, + actions: Schema.Array(ActionSchema).check(Schema.isMinLength(1), Schema.isMaxLength(2)), + blocking: Schema.optional(Schema.Boolean), + tool: Schema.optional( + Schema.Struct({ + messageID: Schema.String, + callID: Schema.String, + }), + ), + }) + export const Accept = z.object({ index: z.number().int().nonnegative().describe("Zero-based action index to accept"), }) export type Accept = z.infer export const Event = { - Shown: BusEvent.define("suggestion.shown", Request), + Shown: BusEvent.define("suggestion.shown", RequestSchema), Accepted: BusEvent.define( "suggestion.accepted", - z.object({ - sessionID: z.string(), - requestID: z.string(), - index: z.number().int().nonnegative(), - action: Action, + Schema.Struct({ + sessionID: SessionID, + requestID: SuggestionIDSchema, + index: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)), + action: ActionSchema, }), ), Dismissed: BusEvent.define( "suggestion.dismissed", - z.object({ - sessionID: z.string(), - requestID: z.string(), + Schema.Struct({ + sessionID: SessionID, + requestID: SuggestionIDSchema, }), ), } @@ -124,7 +152,7 @@ export namespace Suggestion { resolve, reject, } - Bus.publish(Event.Shown, info) + Bus.publish(Event.Shown, { ...info, sessionID: SessionID.make(info.sessionID) }) }) } @@ -149,7 +177,7 @@ export namespace Suggestion { log.info("accepted", { requestID: input.requestID, index: input.index, label: action.label }) Bus.publish(Event.Accepted, { - sessionID: existing.info.sessionID, + sessionID: SessionID.make(existing.info.sessionID), requestID: existing.info.id, index: input.index, action, @@ -171,7 +199,7 @@ export namespace Suggestion { log.info("dismissed", { requestID }) Bus.publish(Event.Dismissed, { - sessionID: existing.info.sessionID, + sessionID: SessionID.make(existing.info.sessionID), requestID: existing.info.id, }) @@ -192,7 +220,7 @@ export namespace Suggestion { delete s.pending[id] log.info("dismissed", { requestID: id }) Bus.publish(Event.Dismissed, { - sessionID: entry.info.sessionID, + sessionID: SessionID.make(entry.info.sessionID), requestID: entry.info.id, }) entry.reject(new DismissedError()) diff --git a/packages/opencode/src/kilocode/suggestion/tool.ts b/packages/opencode/src/kilocode/suggestion/tool.ts index a0f59e6bd49..30e1cd78d93 100644 --- a/packages/opencode/src/kilocode/suggestion/tool.ts +++ b/packages/opencode/src/kilocode/suggestion/tool.ts @@ -1,7 +1,6 @@ import { Command } from "../../command" import { Log } from "../../util" -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import DESCRIPTION from "./tool.txt" import { Tool } from "../../tool" import { Suggestion } from "./index" @@ -10,9 +9,11 @@ import { SessionID } from "../../session/schema" const log = Log.create({ service: "tool.suggest" }) -const Params = z.object({ - suggest: z.string().describe("Short suggestion text shown to the user"), - actions: z.array(Suggestion.Action).min(1).max(2).describe("Available actions the user can take"), +const Params = Schema.Struct({ + suggest: Schema.String.annotate({ description: "Short suggestion text shown to the user" }), + actions: Schema.Array(Suggestion.ActionSchema) + .check(Schema.isMinLength(1), Schema.isMaxLength(2)) + .annotate({ description: "Available actions the user can take" }), }) type Meta = { @@ -62,7 +63,7 @@ export const SuggestTool = Tool.define( const promise = Suggestion.show({ sessionID: ctx.sessionID, text: params.suggest, - actions: params.actions, + actions: params.actions.map((a) => ({ ...a })), blocking: false, // render above an active input; VS Code does the same tool: ctx.callID ? { messageID: ctx.messageID, callID: ctx.callID } : undefined, }) diff --git a/packages/opencode/src/server/routes/instance/network.ts b/packages/opencode/src/server/routes/instance/network.ts index d72303a0ff9..da4826e995e 100644 --- a/packages/opencode/src/server/routes/instance/network.ts +++ b/packages/opencode/src/server/routes/instance/network.ts @@ -5,6 +5,7 @@ import z from "zod" import { lazy } from "@/util/lazy" import { errors } from "../../error" import { SessionNetwork } from "@/session/network" +import { QuestionID } from "@/question/schema" export const NetworkRoutes = lazy(() => new Hono() @@ -19,7 +20,7 @@ export const NetworkRoutes = lazy(() => description: "List of pending network reconnect requests", content: { "application/json": { - schema: resolver(SessionNetwork.Wait.array()), + schema: resolver(SessionNetwork.Wait.zod.array()), }, }, }, @@ -51,7 +52,7 @@ export const NetworkRoutes = lazy(() => validator( "param", z.object({ - requestID: z.string(), + requestID: QuestionID.zod, }), ), async (c) => { @@ -81,7 +82,7 @@ export const NetworkRoutes = lazy(() => validator( "param", z.object({ - requestID: z.string(), + requestID: QuestionID.zod, }), ), async (c) => { diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index acf80147474..1de74a39f0a 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -370,6 +370,16 @@ const messageBase = { sessionID: SessionID, } +// kilocode_change start - shared editor context schema (used by MessageV2.User and SessionPrompt.PromptInput) +export const EditorContext = Schema.Struct({ + visibleFiles: Schema.optional(Schema.Array(Schema.String)), + openTabs: Schema.optional(Schema.Array(Schema.String)), + activeFile: Schema.optional(Schema.String), + shell: Schema.optional(Schema.String), +}) +export type EditorContext = Types.DeepMutable> +// kilocode_change end + export const User = Schema.Struct({ ...messageBase, role: Schema.Literal("user"), @@ -393,14 +403,7 @@ export const User = Schema.Struct({ system: Schema.optional(Schema.String), tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)), // kilocode_change start - editorContext: Schema.optional( - Schema.Struct({ - visibleFiles: Schema.optional(Schema.Array(Schema.String)), - openTabs: Schema.optional(Schema.Array(Schema.String)), - activeFile: Schema.optional(Schema.String), - shell: Schema.optional(Schema.String), - }), - ), + editorContext: Schema.optional(EditorContext), // kilocode_change end }) .annotate({ identifier: "UserMessage" }) diff --git a/packages/opencode/src/session/network.ts b/packages/opencode/src/session/network.ts index f2816ea05f8..8074ff11220 100644 --- a/packages/opencode/src/session/network.ts +++ b/packages/opencode/src/session/network.ts @@ -1,13 +1,16 @@ // kilocode_change - new file -import { Context, Effect, Layer } from "effect" +import { Context, Effect, Layer, Schema, Types } from "effect" import { Bus } from "../bus" import { BusEvent } from "../bus/bus-event" -import { Identifier } from "../id/id" +import { QuestionID } from "../question/schema" +import { SessionID } from "../session/schema" import { InstanceState } from "@/effect" import { makeRuntime } from "@/effect/run-service" import { Log } from "../util" import { fn } from "../util/fn" import { MCP } from "../mcp" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" import z from "zod" export namespace SessionNetwork { @@ -39,51 +42,49 @@ export namespace SessionNetwork { }) } - export const Wait = z - .object({ - id: Identifier.schema("question"), - sessionID: Identifier.schema("session"), - message: z.string(), - restored: z.boolean(), - time: z.object({ - created: z.number(), - }), - }) - .meta({ - ref: "SessionNetworkWait", - }) - export type Wait = z.infer + export const Wait = Schema.Struct({ + id: QuestionID, + sessionID: SessionID, + message: Schema.String, + restored: Schema.Boolean, + time: Schema.Struct({ + created: Schema.Number, + }), + }) + .annotate({ identifier: "SessionNetworkWait" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) + export type Wait = Schema.Schema.Type export const Event = { Asked: BusEvent.define("session.network.asked", Wait), Replied: BusEvent.define( "session.network.replied", - z.object({ - sessionID: z.string(), - requestID: z.string(), + Schema.Struct({ + sessionID: SessionID, + requestID: QuestionID, }), ), Rejected: BusEvent.define( "session.network.rejected", - z.object({ - sessionID: z.string(), - requestID: z.string(), + Schema.Struct({ + sessionID: SessionID, + requestID: QuestionID, }), ), Restored: BusEvent.define( "session.network.restored", - z.object({ - sessionID: z.string(), - requestID: z.string(), + Schema.Struct({ + sessionID: SessionID, + requestID: QuestionID, }), ), } interface StateShape { - pending: Record< - string, + pending: Map< + QuestionID, { - info: Wait + info: Types.Mutable resolve: () => void reject: (e: unknown) => void } @@ -99,7 +100,7 @@ export namespace SessionNetwork { Effect.gen(function* () { const is = yield* InstanceState.make( Effect.fn("SessionNetwork.state")(function* () { - return { pending: {} } as StateShape + return { pending: new Map() } as StateShape }), ) return StateService.of({ @@ -161,12 +162,12 @@ export namespace SessionNetwork { return info.length > 0 } - async function watch(input: { requestID: string; abort: AbortSignal }) { + async function watch(input: { requestID: QuestionID; abort: AbortSignal }) { while (!input.abort.aborted) { await Bun.sleep(POLL_MS) if (input.abort.aborted) return const s = await state() - const req = s.pending[input.requestID] + const req = s.pending.get(input.requestID) if (!req || req.info.restored) return const ok = await probe().catch(() => false) if (!ok) continue @@ -175,9 +176,9 @@ export namespace SessionNetwork { } } - export async function ask(input: { sessionID: string; message: string; abort: AbortSignal }) { + export async function ask(input: { sessionID: SessionID; message: string; abort: AbortSignal }) { const s = await state() - const id = Identifier.ascending("question") + const id = QuestionID.ascending() const info: Wait = { id, sessionID: input.sessionID, @@ -190,16 +191,16 @@ export namespace SessionNetwork { const promise = new Promise((resolve, reject) => { const onAbort = () => { - if (!s.pending[id]) return + if (!s.pending.has(id)) return input.abort.removeEventListener("abort", onAbort) - delete s.pending[id] + s.pending.delete(id) Bus.publish(Event.Rejected, { sessionID: input.sessionID, requestID: id, }) reject(new DOMException("Aborted", "AbortError")) } - s.pending[id] = { + s.pending.set(id, { info, resolve: () => { input.abort.removeEventListener("abort", onAbort) @@ -209,7 +210,7 @@ export namespace SessionNetwork { input.abort.removeEventListener("abort", onAbort) reject(err) }, - } + }) input.abort.addEventListener("abort", onAbort, { once: true }) if (input.abort.aborted) { onAbort() @@ -226,14 +227,15 @@ export namespace SessionNetwork { export const restore = fn( z.object({ - requestID: z.string(), + requestID: QuestionID.zod, }), async (input) => { const s = await state() - const req = s.pending[input.requestID] + const requestID = input.requestID as QuestionID + const req = s.pending.get(requestID) if (!req || req.info.restored) return req.info.restored = true - log.info("network restored", { sessionID: req.info.sessionID, requestID: input.requestID }) + log.info("network restored", { sessionID: req.info.sessionID, requestID }) Bus.publish(Event.Restored, { sessionID: req.info.sessionID, requestID: req.info.id, @@ -243,16 +245,17 @@ export namespace SessionNetwork { export const reply = fn( z.object({ - requestID: z.string(), + requestID: QuestionID.zod, }), async (input) => { const s = await state() - const req = s.pending[input.requestID] + const requestID = input.requestID as QuestionID + const req = s.pending.get(requestID) if (!req) { - log.warn("reply for unknown request", { requestID: input.requestID }) + log.warn("reply for unknown request", { requestID }) return } - delete s.pending[input.requestID] + s.pending.delete(requestID) // kilocode_change start — reconnect failed remote MCP servers after network recovery void MCP.status() .then((statuses) => { @@ -278,16 +281,17 @@ export namespace SessionNetwork { export const reject = fn( z.object({ - requestID: z.string(), + requestID: QuestionID.zod, }), async (input) => { const s = await state() - const req = s.pending[input.requestID] + const requestID = input.requestID as QuestionID + const req = s.pending.get(requestID) if (!req) { - log.warn("reject for unknown request", { requestID: input.requestID }) + log.warn("reject for unknown request", { requestID }) return } - delete s.pending[input.requestID] + s.pending.delete(requestID) Bus.publish(Event.Rejected, { sessionID: req.info.sessionID, requestID: req.info.id, @@ -297,7 +301,7 @@ export namespace SessionNetwork { ) export async function list() { - return state().then((s) => Object.values(s.pending).map((item) => item.info)) + return state().then((s) => Array.from(s.pending.values()).map((item) => item.info)) } export class RejectedError extends Error { diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 40c0b6e3ca1..3fc8032269a 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1858,15 +1858,8 @@ export const PromptInput = Schema.Struct({ format: Schema.optional(MessageV2.Format), system: Schema.optional(Schema.String), variant: Schema.optional(Schema.String), - // kilocode_change start - editorContext: Schema.optional( - Schema.Struct({ - visibleFiles: Schema.optional(Schema.Array(Schema.String)), - openTabs: Schema.optional(Schema.Array(Schema.String)), - activeFile: Schema.optional(Schema.String), - shell: Schema.optional(Schema.String), - }), - ), + // kilocode_change start - reuse shared editor context schema + editorContext: Schema.optional(MessageV2.EditorContext), // kilocode_change end parts: Schema.Array( Schema.Union([ @@ -1886,8 +1879,9 @@ type PartInputUnion = | MessageV2.FilePartInput | MessageV2.AgentPartInput | MessageV2.SubtaskPartInput -export type PromptInput = Omit, "parts"> & { +export type PromptInput = Omit, "parts" | "editorContext"> & { parts: PartInputUnion[] + editorContext?: MessageV2.EditorContext } export class LoopInput extends Schema.Class("SessionPrompt.LoopInput")({ @@ -1970,7 +1964,7 @@ const quoteTrimRegex = /^["']|["']$/g // kilocode_change start - legacy promise helpers for Kilo callsites const { runPromise } = makeRuntime(Service, defaultLayer) export const prompt = (input: PromptInput) => runPromise((svc) => svc.prompt(input)) -export const loop = (input: z.infer) => runPromise((svc) => svc.loop(input)) +export const loop = (input: LoopInput) => runPromise((svc) => svc.loop(input)) export const cancel = (sessionID: SessionID) => runPromise((svc) => svc.cancel(sessionID)) // kilocode_change end diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index e30d0f60400..3d44ae53b7d 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -861,18 +861,37 @@ export function* listGlobal(input?: { // kilocode_change start - keep legacy promise helpers for Kilo callsites const { runPromise } = makeRuntime(Service, defaultLayer) -export const create = fn(CreateInput, (input) => runPromise((svc) => svc.create(input))) +const decodeCreate = Schema.decodeUnknownSync(CreateInput) +const decodeGet = Schema.decodeUnknownSync(GetInput) +const decodeSetTitle = Schema.decodeUnknownSync(SetTitleInput) +const decodeSetArchived = Schema.decodeUnknownSync(SetArchivedInput) +const decodeSetPermission = Schema.decodeUnknownSync(SetPermissionInput) +const decodeSetRevert = Schema.decodeUnknownSync(SetRevertInput) +const decodeMessages = Schema.decodeUnknownSync(MessagesInput) +const decodeChildren = Schema.decodeUnknownSync(ChildrenInput) +const decodeRemove = Schema.decodeUnknownSync(RemoveInput) + +export const create = (input?: CreateInput) => runPromise((svc) => svc.create(decodeCreate(input) as CreateInput)) export const fork = kiloSessionFork -export const get = fn(GetInput, (id) => runPromise((svc) => svc.get(id))) -export const setTitle = fn(SetTitleInput, (input) => runPromise((svc) => svc.setTitle(input))) -export const setArchived = fn(SetArchivedInput, (input) => runPromise((svc) => svc.setArchived(input))) -export const setPermission = fn(SetPermissionInput, (input) => runPromise((svc) => svc.setPermission(input))) -export const setRevert = fn(SetRevertInput, (input) => - runPromise((svc) => svc.setRevert({ sessionID: input.sessionID, revert: input.revert, summary: input.summary })), -) -export const messages = fn(MessagesInput, (input) => runPromise((svc) => svc.messages(input))) -export const children = fn(ChildrenInput, (id) => runPromise((svc) => svc.children(id))) -export const remove = fn(RemoveInput, (id) => runPromise((svc) => svc.remove(id))) +export const get = (id: SessionID) => runPromise((svc) => svc.get(decodeGet(id))) +export const setTitle = (input: { sessionID: SessionID; title: string }) => + runPromise((svc) => svc.setTitle(decodeSetTitle(input))) +export const setArchived = (input: { sessionID: SessionID; time?: number }) => + runPromise((svc) => svc.setArchived(decodeSetArchived(input))) +export const setPermission = (input: { sessionID: SessionID; permission: Permission.Ruleset }) => + runPromise((svc) => + svc.setPermission(decodeSetPermission(input) as { sessionID: SessionID; permission: Permission.Ruleset }), + ) +export const setRevert = (input: { sessionID: SessionID; revert?: Info["revert"]; summary?: Info["summary"] }) => { + const parsed = decodeSetRevert(input) as { sessionID: SessionID; revert?: Info["revert"]; summary?: Info["summary"] } + return runPromise((svc) => + svc.setRevert({ sessionID: parsed.sessionID, revert: parsed.revert, summary: parsed.summary }), + ) +} +export const messages = (input: { sessionID: SessionID; limit?: number }) => + runPromise((svc) => svc.messages(decodeMessages(input))) +export const children = (id: SessionID) => runPromise((svc) => svc.children(decodeChildren(id))) +export const remove = (id: SessionID) => runPromise((svc) => svc.remove(decodeRemove(id))) export async function updateMessage(msg: T): Promise { MessageV2.Info.zod.parse(msg) // kilocode_change return runPromise((svc) => svc.updateMessage(msg)) diff --git a/packages/opencode/src/session/status.ts b/packages/opencode/src/session/status.ts index 826660a1202..6c1d4f3d81f 100644 --- a/packages/opencode/src/session/status.ts +++ b/packages/opencode/src/session/status.ts @@ -2,6 +2,7 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" import { InstanceState } from "@/effect" import { SessionID } from "./schema" +import { QuestionID } from "@/question/schema" // kilocode_change import { makeRuntime } from "@/effect/run-service" // kilocode_change import { zod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" @@ -24,7 +25,7 @@ export const Info = Schema.Union([ // kilocode_change start Schema.Struct({ type: Schema.Literal("offline"), - requestID: Schema.String, + requestID: QuestionID, message: Schema.String, }), // kilocode_change end diff --git a/packages/opencode/src/tool/recall.ts b/packages/opencode/src/tool/recall.ts index d5e9338fdac..8681d5d4588 100644 --- a/packages/opencode/src/tool/recall.ts +++ b/packages/opencode/src/tool/recall.ts @@ -1,6 +1,5 @@ // kilocode_change - new file -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import * as Tool from "./tool" import { Instance } from "../project/instance" import { Locale } from "../util" @@ -8,11 +7,19 @@ import { Filesystem } from "../util" // kilocode_change import { WorktreeFamily } from "../kilocode/worktree-family" // kilocode_change import DESCRIPTION from "./recall.txt" -const Parameters = z.object({ - mode: z.enum(["search", "read"]).describe("'search' to find sessions by title, 'read' to get a session transcript"), - query: z.string().optional().describe("Search query to match against session titles (required for search mode)"), - sessionID: z.string().optional().describe("Session ID to read the transcript of (required for read mode)"), - limit: z.number().optional().describe("Maximum number of search results to return (default: 20, max: 50)"), +const Parameters = Schema.Struct({ + mode: Schema.Literals(["search", "read"]).annotate({ + description: "'search' to find sessions by title, 'read' to get a session transcript", + }), + query: Schema.optional(Schema.String).annotate({ + description: "Search query to match against session titles (required for search mode)", + }), + sessionID: Schema.optional(Schema.String).annotate({ + description: "Session ID to read the transcript of (required for read mode)", + }), + limit: Schema.optional(Schema.Number).annotate({ + description: "Maximum number of search results to return (default: 20, max: 50)", + }), }) export const RecallTool = Tool.define( @@ -21,7 +28,7 @@ export const RecallTool = 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.mode === "search") { return yield* Effect.promise(() => search(params, ctx)) diff --git a/packages/opencode/src/tool/warpgrep.ts b/packages/opencode/src/tool/warpgrep.ts index 7ebe4a1b911..dcffa997b6e 100644 --- a/packages/opencode/src/tool/warpgrep.ts +++ b/packages/opencode/src/tool/warpgrep.ts @@ -1,5 +1,4 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import * as Tool from "./tool" import { WarpGrepClient } from "@morphllm/morphsdk/tools/warp-grep/client" // kilocode_change import { Instance } from "../project/instance" @@ -12,10 +11,10 @@ import DESCRIPTION from "./warpgrep.txt" // return an error when it is missing. const KILO_WARPGREP_PROXY_URL = "https://api.kilo.ai/api/gateway" -const Parameters = z.object({ - query: z - .string() - .describe("Search query describing what code you are looking for. Be specific and descriptive for best results."), // kilocode_change +const Parameters = Schema.Struct({ + query: Schema.String.annotate({ + description: "Search query describing what code you are looking for. Be specific and descriptive for best results.", // kilocode_change + }), }) export const CodebaseSearchTool = Tool.define( @@ -24,7 +23,7 @@ export const CodebaseSearchTool = 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* () { yield* ctx.ask({ permission: "codebase_search", diff --git a/packages/opencode/test/session/network.test.ts b/packages/opencode/test/session/network.test.ts index 63586932d7a..d3e7c0e9729 100644 --- a/packages/opencode/test/session/network.test.ts +++ b/packages/opencode/test/session/network.test.ts @@ -4,6 +4,7 @@ import { Bus } from "../../src/bus" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" import { SessionNetwork } from "../../src/session/network" +import { SessionID } from "../../src/session/schema" describe("session.network", () => { test("detects common network disconnect codes", () => { @@ -45,7 +46,7 @@ describe("session.network", () => { directory: tmp.path, fn: async () => { const { promise } = await SessionNetwork.ask({ - sessionID: "ses_test", + sessionID: SessionID.make("ses_test"), message: "Connection refused", abort: new AbortController().signal, }) @@ -64,7 +65,7 @@ describe("session.network", () => { directory: tmp.path, fn: async () => { const { promise } = await SessionNetwork.ask({ - sessionID: "ses_test", + sessionID: SessionID.make("ses_test"), message: "Connection timed out", abort: new AbortController().signal, }) @@ -90,7 +91,7 @@ describe("session.network", () => { try { const { promise } = await SessionNetwork.ask({ - sessionID: "ses_test", + sessionID: SessionID.make("ses_test"), message: "Connection timed out", abort: abort.signal, }) @@ -115,7 +116,7 @@ describe("session.network", () => { fn: async () => { const abort = new AbortController() const { promise } = await SessionNetwork.ask({ - sessionID: "ses_test", + sessionID: SessionID.make("ses_test"), message: "Connection refused", abort: abort.signal, })