mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
feat(kilocode): introduce EffectBridge for async-to-effect interop and lazify runtime constructors
Replace raw `Effect.promise` calls across HTTP API handlers with `EffectBridge.fromPromise` / `EffectBridge.make` to ensure proper error propagation and context forwarding when bridging imperative async code into the Effect runtime. Wrap top-level `makeRuntime` calls in `plan-followup.ts` with `lazy()` to defer service instantiation until first access, avoiding eager initialization side-effects during module loading. Also reorder SDK event union members and type definitions to reflect updated OpenAPI schema generation output.
This commit is contained in:
@@ -19,39 +19,40 @@ import { Todo } from "@/session/todo"
|
||||
import { makeRuntime } from "@/effect/run-service"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { KiloSessionPromptQueue } from "@/kilocode/session/prompt-queue"
|
||||
import { lazy } from "@/util/lazy"
|
||||
import path from "path"
|
||||
import z from "zod"
|
||||
|
||||
const agents = makeRuntime(Agent.Service, Agent.defaultLayer)
|
||||
const providers = makeRuntime(Provider.Service, Provider.defaultLayer)
|
||||
const questions = makeRuntime(Question.Service, Question.defaultLayer)
|
||||
const todo = makeRuntime(Todo.Service, Todo.defaultLayer)
|
||||
const agents = lazy(() => makeRuntime(Agent.Service, Agent.defaultLayer))
|
||||
const providers = lazy(() => makeRuntime(Provider.Service, Provider.defaultLayer))
|
||||
const questions = lazy(() => makeRuntime(Question.Service, Question.defaultLayer))
|
||||
const todo = lazy(() => makeRuntime(Todo.Service, Todo.defaultLayer))
|
||||
const pending = new Map<SessionID, AbortController>()
|
||||
|
||||
export const PlanFollowupRuntime = {
|
||||
agent(name: string): Promise<Agent.Info | undefined> {
|
||||
return agents.runPromise((svc) => svc.get(name))
|
||||
return agents().runPromise((svc) => svc.get(name))
|
||||
},
|
||||
model(providerID: ProviderID, modelID: ModelID): Promise<Provider.Model> {
|
||||
return providers.runPromise((svc) => svc.getModel(providerID, modelID))
|
||||
return providers().runPromise((svc) => svc.getModel(providerID, modelID))
|
||||
},
|
||||
question: {
|
||||
ask(input: Parameters<Question.Interface["ask"]>[0]) {
|
||||
return questions.runPromise((svc) => svc.ask(input))
|
||||
return questions().runPromise((svc) => svc.ask(input))
|
||||
},
|
||||
list() {
|
||||
return questions.runPromise((svc) => svc.list())
|
||||
return questions().runPromise((svc) => svc.list())
|
||||
},
|
||||
reject(requestID: Parameters<Question.Interface["reject"]>[0]) {
|
||||
return questions.runPromise((svc) => svc.reject(requestID))
|
||||
return questions().runPromise((svc) => svc.reject(requestID))
|
||||
},
|
||||
},
|
||||
todo: {
|
||||
get(sessionID: SessionID) {
|
||||
return todo.runPromise((svc) => svc.get(sessionID))
|
||||
return todo().runPromise((svc) => svc.get(sessionID))
|
||||
},
|
||||
update(input: Parameters<Todo.Interface["update"]>[0]) {
|
||||
return todo.runPromise((svc) => svc.update(input))
|
||||
return todo().runPromise((svc) => svc.update(input))
|
||||
},
|
||||
},
|
||||
async loop(sessionID: SessionID) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { EffectBridge } from "@/effect/bridge"
|
||||
import { InstanceHttpApi } from "@/server/routes/instance/httpapi/api"
|
||||
import { Config } from "@/config/config"
|
||||
import { generateCommitMessage } from "@/kilocode/commit-message"
|
||||
@@ -14,7 +15,7 @@ export const commitMessageHandlers = HttpApiBuilder.group(InstanceHttpApi, "comm
|
||||
}) {
|
||||
const cfg = yield* config.get()
|
||||
const prompt = cfg.commit_message?.prompt || undefined
|
||||
const result = yield* Effect.promise(() =>
|
||||
const result = yield* EffectBridge.fromPromise(() =>
|
||||
generateCommitMessage({
|
||||
path: ctx.payload.path,
|
||||
selectedFiles: ctx.payload.selectedFiles ? [...ctx.payload.selectedFiles] : undefined,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { EffectBridge } from "@/effect/bridge"
|
||||
import { InstanceHttpApi } from "@/server/routes/instance/httpapi/api"
|
||||
import { enhancePrompt } from "@/kilocode/enhance-prompt"
|
||||
import { EnhancePromptPayload } from "../groups/enhance-prompt"
|
||||
@@ -9,7 +10,7 @@ export const enhancePromptHandlers = HttpApiBuilder.group(InstanceHttpApi, "enha
|
||||
const enhance = Effect.fn("EnhancePromptHttpApi.enhance")(function* (ctx: {
|
||||
payload: typeof EnhancePromptPayload.Type
|
||||
}) {
|
||||
const text = yield* Effect.promise(() => enhancePrompt(ctx.payload.text))
|
||||
const text = yield* EffectBridge.fromPromise(() => enhancePrompt(ctx.payload.text))
|
||||
return { text }
|
||||
})
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { EffectBridge } from "@/effect/bridge"
|
||||
import { InstanceHttpApi } from "@/server/routes/instance/httpapi/api"
|
||||
|
||||
export const indexingHandlers = HttpApiBuilder.group(InstanceHttpApi, "indexing", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const status = Effect.fn("IndexingHttpApi.status")(function* () {
|
||||
const mod = yield* Effect.promise(() => import("@/kilocode/indexing"))
|
||||
const current = yield* Effect.promise(() => mod.KiloIndexing.current())
|
||||
const current = yield* EffectBridge.fromPromise(() => mod.KiloIndexing.current())
|
||||
return current
|
||||
})
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import * as Stream from "effect/Stream"
|
||||
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||
import { HttpApiBuilder, HttpApiError } from "effect/unstable/httpapi"
|
||||
import { Auth } from "@/auth"
|
||||
import { EffectBridge } from "@/effect/bridge"
|
||||
import { Bus } from "@/bus"
|
||||
import { Identifier } from "@/id/id"
|
||||
import { Instance } from "@/project/instance"
|
||||
@@ -279,19 +280,24 @@ export const kiloGatewayHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilo",
|
||||
if (!fetched.ok) return yield* Effect.fail(new HttpApiError.BadRequest({}))
|
||||
if (!fetched.data?.info?.id) return yield* Effect.fail(new HttpApiError.BadRequest({}))
|
||||
|
||||
return yield* Effect.try({
|
||||
const bridge = yield* EffectBridge.make()
|
||||
return yield* Effect.tryPromise({
|
||||
try: () =>
|
||||
importSessionToDb(fetched.data, {
|
||||
Database,
|
||||
Instance,
|
||||
SessionTable,
|
||||
MessageTable,
|
||||
PartTable,
|
||||
SessionToRow: Session.toRow,
|
||||
Bus,
|
||||
SessionCreatedEvent: Session.Event.Created,
|
||||
Identifier,
|
||||
}),
|
||||
bridge.promise(
|
||||
Effect.sync(() =>
|
||||
importSessionToDb(fetched.data, {
|
||||
Database,
|
||||
Instance,
|
||||
SessionTable,
|
||||
MessageTable,
|
||||
PartTable,
|
||||
SessionToRow: Session.toRow,
|
||||
Bus,
|
||||
SessionCreatedEvent: Session.Event.Created,
|
||||
Identifier,
|
||||
}),
|
||||
),
|
||||
),
|
||||
catch: () => new HttpApiError.BadRequest({}),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { EffectBridge } from "@/effect/bridge"
|
||||
import { HeapSnapshot } from "@/kilocode/cli/heap-snapshot"
|
||||
import { InstanceHttpApi } from "@/server/routes/instance/httpapi/api"
|
||||
import { Skill } from "@/skill"
|
||||
@@ -22,7 +23,7 @@ export const kilocodeHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilocode"
|
||||
const removeAgent = Effect.fn("KilocodeHttpApi.removeAgent")(function* (ctx: {
|
||||
payload: typeof RemoveAgentPayload.Type
|
||||
}) {
|
||||
yield* Effect.promise(() => Agent.remove(ctx.payload.name))
|
||||
yield* EffectBridge.fromPromise(() => Agent.remove(ctx.payload.name))
|
||||
return true
|
||||
})
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { EffectBridge } from "@/effect/bridge"
|
||||
import { QuestionID } from "@/question/schema"
|
||||
import { SessionNetwork } from "@/session/network"
|
||||
import { InstanceHttpApi } from "@/server/routes/instance/httpapi/api"
|
||||
@@ -7,16 +8,16 @@ import { InstanceHttpApi } from "@/server/routes/instance/httpapi/api"
|
||||
export const networkHandlers = HttpApiBuilder.group(InstanceHttpApi, "network", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const list = Effect.fn("NetworkHttpApi.list")(function* () {
|
||||
return yield* Effect.promise(() => SessionNetwork.list())
|
||||
return yield* EffectBridge.fromPromise(() => SessionNetwork.list())
|
||||
})
|
||||
|
||||
const reply = Effect.fn("NetworkHttpApi.reply")(function* (ctx: { params: { requestID: QuestionID } }) {
|
||||
yield* Effect.promise(() => SessionNetwork.reply({ requestID: ctx.params.requestID }))
|
||||
yield* EffectBridge.fromPromise(() => SessionNetwork.reply({ requestID: ctx.params.requestID }))
|
||||
return true
|
||||
})
|
||||
|
||||
const reject = Effect.fn("NetworkHttpApi.reject")(function* (ctx: { params: { requestID: QuestionID } }) {
|
||||
yield* Effect.promise(() => SessionNetwork.reject({ requestID: ctx.params.requestID }))
|
||||
yield* EffectBridge.fromPromise(() => SessionNetwork.reject({ requestID: ctx.params.requestID }))
|
||||
return true
|
||||
})
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder, HttpApiError } from "effect/unstable/httpapi"
|
||||
import { EffectBridge } from "@/effect/bridge"
|
||||
import { KiloSessions } from "@/kilo-sessions/kilo-sessions"
|
||||
import { InstanceHttpApi } from "@/server/routes/instance/httpapi/api"
|
||||
|
||||
export const remoteHandlers = HttpApiBuilder.group(InstanceHttpApi, "remote", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const enable = Effect.fn("RemoteHttpApi.enable")(function* () {
|
||||
const bridge = yield* EffectBridge.make()
|
||||
yield* Effect.tryPromise({
|
||||
try: () => KiloSessions.enableRemote(),
|
||||
try: () => bridge.promise(Effect.promise(() => KiloSessions.enableRemote())),
|
||||
catch: () => new HttpApiError.Unauthorized(),
|
||||
})
|
||||
return KiloSessions.remoteStatus()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Account } from "@/account/account"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { Config } from "@/config/config"
|
||||
import { EffectBridge } from "@/effect/bridge" // kilocode_change
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { MCP } from "@/mcp"
|
||||
import { Project } from "@/project/project"
|
||||
@@ -128,7 +129,7 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper
|
||||
// kilocode_change start - worktree diff endpoints for agent manager
|
||||
const base = Effect.fn("ExperimentalHttpApi.worktreeDiffBase")(function* (input: { base?: string }) {
|
||||
if (input.base) return input.base
|
||||
return yield* Effect.promise(() => Review.getBaseBranch())
|
||||
return yield* EffectBridge.fromPromise(() => Review.getBaseBranch())
|
||||
})
|
||||
|
||||
const worktreeDiff = Effect.fn("ExperimentalHttpApi.worktreeDiff")(function* (ctx: {
|
||||
@@ -178,7 +179,7 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper
|
||||
// kilocode_change start
|
||||
const state = yield* InstanceState.context
|
||||
const projectID = ctx.query.worktrees && !ctx.query.projectID ? state.project.id : ctx.query.projectID
|
||||
const directories = ctx.query.worktrees ? yield* Effect.promise(() => WorktreeFamily.list()) : undefined
|
||||
const directories = ctx.query.worktrees ? yield* EffectBridge.fromPromise(() => WorktreeFamily.list()) : undefined
|
||||
const sorted = directories ? [...directories].sort((a, b) => b.length - a.length) : undefined
|
||||
// kilocode_change end
|
||||
const sessions = Array.from(
|
||||
|
||||
@@ -35,8 +35,6 @@ export type Event =
|
||||
| EventInstallationUpdateAvailable
|
||||
| EventFileEdited
|
||||
| EventFileWatcherUpdated
|
||||
| EventCommandExecuted
|
||||
| EventProjectUpdated
|
||||
| EventTodoUpdated
|
||||
| EventSessionStatus
|
||||
| EventSessionIdle
|
||||
@@ -44,6 +42,8 @@ export type Event =
|
||||
| EventSuggestionAccepted
|
||||
| EventSuggestionDismissed
|
||||
| EventSessionCompacted
|
||||
| EventCommandExecuted
|
||||
| EventProjectUpdated
|
||||
| EventKilocodeAgentManagerStart
|
||||
| EventVcsBranchUpdated
|
||||
| EventWorkspaceReady
|
||||
@@ -328,30 +328,6 @@ export type ApiError = {
|
||||
}
|
||||
}
|
||||
|
||||
export type Project = {
|
||||
id: string
|
||||
worktree: string
|
||||
vcs?: "git"
|
||||
name?: string
|
||||
icon?: {
|
||||
url?: string
|
||||
override?: string
|
||||
color?: string
|
||||
}
|
||||
commands?: {
|
||||
/**
|
||||
* Startup script to run when creating a new workspace (worktree)
|
||||
*/
|
||||
start?: string
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
updated: number
|
||||
initialized?: number
|
||||
}
|
||||
sandboxes: Array<string>
|
||||
}
|
||||
|
||||
export type Todo = {
|
||||
/**
|
||||
* Brief description of the task
|
||||
@@ -408,6 +384,30 @@ export type SuggestionRequest = {
|
||||
}
|
||||
}
|
||||
|
||||
export type Project = {
|
||||
id: string
|
||||
worktree: string
|
||||
vcs?: "git"
|
||||
name?: string
|
||||
icon?: {
|
||||
url?: string
|
||||
override?: string
|
||||
color?: string
|
||||
}
|
||||
commands?: {
|
||||
/**
|
||||
* Startup script to run when creating a new workspace (worktree)
|
||||
*/
|
||||
start?: string
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
updated: number
|
||||
initialized?: number
|
||||
}
|
||||
sandboxes: Array<string>
|
||||
}
|
||||
|
||||
export type Pty = {
|
||||
id: string
|
||||
title: string
|
||||
@@ -880,8 +880,6 @@ export type GlobalEvent = {
|
||||
| EventInstallationUpdateAvailable
|
||||
| EventFileEdited
|
||||
| EventFileWatcherUpdated
|
||||
| EventCommandExecuted
|
||||
| EventProjectUpdated
|
||||
| EventTodoUpdated
|
||||
| EventSessionStatus
|
||||
| EventSessionIdle
|
||||
@@ -889,6 +887,8 @@ export type GlobalEvent = {
|
||||
| EventSuggestionAccepted
|
||||
| EventSuggestionDismissed
|
||||
| EventSessionCompacted
|
||||
| EventCommandExecuted
|
||||
| EventProjectUpdated
|
||||
| EventKilocodeAgentManagerStart
|
||||
| EventVcsBranchUpdated
|
||||
| EventWorkspaceReady
|
||||
@@ -2687,23 +2687,6 @@ export type EventFileWatcherUpdated = {
|
||||
}
|
||||
}
|
||||
|
||||
export type EventCommandExecuted = {
|
||||
id: string
|
||||
type: "command.executed"
|
||||
properties: {
|
||||
name: string
|
||||
sessionID: string
|
||||
arguments: string
|
||||
messageID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventProjectUpdated = {
|
||||
id: string
|
||||
type: "project.updated"
|
||||
properties: Project
|
||||
}
|
||||
|
||||
export type EventTodoUpdated = {
|
||||
id: string
|
||||
type: "todo.updated"
|
||||
@@ -2774,6 +2757,23 @@ export type EventSessionCompacted = {
|
||||
}
|
||||
}
|
||||
|
||||
export type EventCommandExecuted = {
|
||||
id: string
|
||||
type: "command.executed"
|
||||
properties: {
|
||||
name: string
|
||||
sessionID: string
|
||||
arguments: string
|
||||
messageID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventProjectUpdated = {
|
||||
id: string
|
||||
type: "project.updated"
|
||||
properties: Project
|
||||
}
|
||||
|
||||
export type EventKilocodeAgentManagerStart = {
|
||||
id: string
|
||||
type: "kilocode.agent_manager.start"
|
||||
|
||||
Reference in New Issue
Block a user