mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-10-30 19:32:39 +08:00
refactor(opencode): migrate zod schemas to effect Schema across kilo modules
Replace zod schema definitions with effect Schema equivalents in BusEvent definitions, tool parameters, session network, suggestions, and related modules. This aligns Kilo-specific code with the upstream v1.14.23 migration from zod to effect Schema. - Convert BusEvent property schemas from zod objects to Schema.Struct - Migrate tool parameter definitions (recall, warpgrep, suggest) to Schema.Struct with annotated descriptions - Replace zod-based SessionNetwork.Wait with effect Schema and add zod interop via withStatics helper - Extract shared EditorContext schema in message-v2 and reuse in prompt input - Replace `fn()` wrappers in session.ts with explicit Schema.decodeUnknownSync calls for type safety - Use branded SessionID/QuestionID types in place of raw z.string() - Update pending map in SessionNetwork from Record to Map<QuestionID> - Update tests to use SessionID.make() for branded identifiers
This commit is contained in:
@@ -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,
|
||||
}),
|
||||
),
|
||||
}
|
||||
|
||||
@@ -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<typeof SessionPrompt.PromptInput.extend> | undefined
|
||||
let _remotePromptInput: z.ZodObject<any> | 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) {
|
||||
|
||||
@@ -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<typeof Event.TurnClose.properties>["reason"]
|
||||
export type CloseReason = Schema.Schema.Type<typeof CloseReasonSchema>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Per-session platform override (telemetry attribution)
|
||||
|
||||
@@ -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<typeof Action>
|
||||
|
||||
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<typeof Request>
|
||||
|
||||
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<typeof Accept>
|
||||
|
||||
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())
|
||||
|
||||
@@ -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<typeof Params, Meta, never, "suggest">(
|
||||
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,
|
||||
})
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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<Schema.Schema.Type<typeof EditorContext>>
|
||||
// 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" })
|
||||
|
||||
@@ -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<typeof Wait>
|
||||
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<typeof Wait>
|
||||
|
||||
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<Wait>
|
||||
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<void>((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 {
|
||||
|
||||
@@ -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<Schema.Schema.Type<typeof PromptInput>, "parts"> & {
|
||||
export type PromptInput = Omit<Schema.Schema.Type<typeof PromptInput>, "parts" | "editorContext"> & {
|
||||
parts: PartInputUnion[]
|
||||
editorContext?: MessageV2.EditorContext
|
||||
}
|
||||
|
||||
export class LoopInput extends Schema.Class<LoopInput>("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<typeof LoopInput>) => 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
|
||||
|
||||
|
||||
@@ -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<T extends MessageV2.Info>(msg: T): Promise<T> {
|
||||
MessageV2.Info.zod.parse(msg) // kilocode_change
|
||||
return runPromise((svc) => svc.updateMessage(msg))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<typeof Parameters>, ctx: Tool.Context) =>
|
||||
execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) =>
|
||||
Effect.gen(function* () {
|
||||
if (params.mode === "search") {
|
||||
return yield* Effect.promise(() => search(params, ctx))
|
||||
|
||||
@@ -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<typeof Parameters>, ctx: Tool.Context) =>
|
||||
execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) =>
|
||||
Effect.gen(function* () {
|
||||
yield* ctx.ask({
|
||||
permission: "codebase_search",
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user