From a1a19364160ff1065415278588bab22ba44c27a8 Mon Sep 17 00:00:00 2001 From: Imanol Maiztegui Date: Mon, 18 May 2026 15:41:42 +0200 Subject: [PATCH] feat(kilocode): add worktree diff, FIM proxy, audio transcription, and session viewed endpoints Introduce several new Effect HttpApi endpoints across the kilo-gateway and experimental API groups to support agent manager workflows and enhanced gateway proxying. - Add worktree diff endpoints (diff, diff/summary, diff/file) for comparing worktree changes against a base branch - Add FIM completion proxy endpoint with streaming SSE response and timeout handling - Add audio transcriptions proxy endpoint forwarding to Kilo Gateway - Add organization modes endpoint to fetch custom modes config - Add session/viewed endpoint to track which sessions the user has open - Extend session list query with projectID and worktrees parameters - Register all new routes in Hono instance router - Regenerate SDK types and OpenAPI spec --- .../server/httpapi/groups/kilo-gateway.ts | 91 ++- .../server/httpapi/handlers/kilo-gateway.ts | 122 ++++ .../src/kilocode/server/httpapi/instance.ts | 3 + .../instance/httpapi/groups/experimental.ts | 53 ++ .../routes/instance/httpapi/groups/session.ts | 19 + .../instance/httpapi/handlers/experimental.ts | 115 ++- .../instance/httpapi/handlers/session.ts | 10 + .../src/server/routes/instance/index.ts | 4 + packages/sdk/js/src/v2/gen/sdk.gen.ts | 285 ++++++++ packages/sdk/js/src/v2/gen/types.gen.ts | 244 +++++++ packages/sdk/openapi.json | 675 ++++++++++++++++++ 11 files changed, 1604 insertions(+), 17 deletions(-) diff --git a/packages/opencode/src/kilocode/server/httpapi/groups/kilo-gateway.ts b/packages/opencode/src/kilocode/server/httpapi/groups/kilo-gateway.ts index 9a0a72caffb..d561d88325b 100644 --- a/packages/opencode/src/kilocode/server/httpapi/groups/kilo-gateway.ts +++ b/packages/opencode/src/kilocode/server/httpapi/groups/kilo-gateway.ts @@ -1,5 +1,5 @@ import { Schema } from "effect" -import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "@/server/routes/instance/httpapi/middleware/authorization" import { InstanceContextMiddleware } from "@/server/routes/instance/httpapi/middleware/instance-context" import { WorkspaceRoutingMiddleware } from "@/server/routes/instance/httpapi/middleware/workspace-routing" @@ -103,6 +103,61 @@ export const CloudSessionImportBody = Schema.Struct({ sessionId: Schema.String, }) +const GroupEntry = Schema.Union([ + Schema.String, + Schema.Tuple([ + Schema.String, + Schema.Struct({ + fileRegex: Schema.optional(Schema.String), + description: Schema.optional(Schema.String), + }), + ]), +]) + +export const OrganizationMode = Schema.Struct({ + id: Schema.String, + organization_id: Schema.String, + name: Schema.String, + slug: Schema.String, + created_by: Schema.String, + created_at: Schema.String, + updated_at: Schema.String, + config: Schema.Struct({ + roleDefinition: Schema.optional(Schema.String), + whenToUse: Schema.optional(Schema.String), + description: Schema.optional(Schema.String), + customInstructions: Schema.optional(Schema.String), + groups: Schema.optional(Schema.Array(GroupEntry)), + }), +}) + +export const OrganizationModes = Schema.Struct({ + modes: Schema.Array(OrganizationMode), +}) + +export const FimBody = Schema.Struct({ + prefix: Schema.String, + suffix: Schema.String, + model: Schema.optional(Schema.String), + maxTokens: Schema.optional(Schema.Finite), + temperature: Schema.optional(Schema.Finite), +}) + +export const AudioTranscriptionsBody = Schema.Struct({ + model: Schema.String, + input_audio: Schema.Struct({ + data: Schema.String, + format: Schema.String, + }), + language: Schema.optional(Schema.String), + temperature: Schema.optional(Schema.Finite), +}) + +export const TranscriptionResponse = Schema.Struct({ + text: Schema.String, + usage: Schema.optional(Schema.Unknown), +}) + export const CloudMessage = Schema.Struct({ info: Schema.Struct({ id: Schema.String, @@ -136,7 +191,10 @@ export const CloudSessionData = Schema.Struct({ }) export const KiloGatewayPaths = { + modes: `${root}/modes`, profile: `${root}/profile`, + fim: `${root}/fim`, + audioTranscriptions: `${root}/audio/transcriptions`, notifications: `${root}/notifications`, organization: `${root}/organization`, clawStatus: `${root}/claw/status`, @@ -160,6 +218,37 @@ export const KiloGatewayApi = HttpApi.make("kilo") description: "Fetch user profile and organizations from Kilo Gateway", }), ), + HttpApiEndpoint.get("modes", KiloGatewayPaths.modes, { + success: described(OrganizationModes, "Organization modes list"), + }).annotateMerge( + OpenApi.annotations({ + identifier: "kilo.modes", + summary: "Get organization custom modes", + description: "Fetch custom modes defined for the current organization", + }), + ), + HttpApiEndpoint.post("fim", KiloGatewayPaths.fim, { + payload: FimBody, + success: Schema.String.pipe(HttpApiSchema.asText({ contentType: "text/event-stream" })), + error: [HttpApiError.BadRequest, HttpApiError.Unauthorized], + }).annotateMerge( + OpenApi.annotations({ + identifier: "kilo.fim", + summary: "FIM completion", + description: "Proxy a Fill-in-the-Middle completion request to the Kilo Gateway", + }), + ), + HttpApiEndpoint.post("audioTranscriptions", KiloGatewayPaths.audioTranscriptions, { + payload: AudioTranscriptionsBody, + success: described(TranscriptionResponse, "Transcription response"), + error: [HttpApiError.BadRequest, HttpApiError.Unauthorized], + }).annotateMerge( + OpenApi.annotations({ + identifier: "kilo.audio.transcriptions", + summary: "Speech to text transcription", + description: "Proxy an audio transcription request to the Kilo Gateway", + }), + ), HttpApiEndpoint.get("notifications", KiloGatewayPaths.notifications, { success: described(Schema.Array(Notification), "Notifications list"), error: [HttpApiError.BadRequest, HttpApiError.Unauthorized], diff --git a/packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts b/packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts index 1b600b1a2a7..99df6a25b4b 100644 --- a/packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts +++ b/packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts @@ -8,6 +8,7 @@ import { importSessionToDb, } from "@kilocode/kilo-gateway" import { + HEADER_FEATURE, HEADER_ORGANIZATIONID, KILO_API_BASE, KILO_CHAT_URL, @@ -15,9 +16,13 @@ import { clearModesCache, fetchBalance, fetchKilocodeNotifications, + fetchOrganizationModes, fetchProfile, } from "@kilocode/kilo-gateway" +import { buildKiloHeaders } from "@kilocode/kilo-gateway" import { Effect } from "effect" +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 { Bus } from "@/bus" @@ -29,6 +34,9 @@ import { InstanceHttpApi } from "@/server/routes/instance/httpapi/api" import { MessageTable, PartTable, SessionTable } from "@/session/session.sql" import { Session } from "@/session/session" import { Database } from "@/storage/db" +import { AudioTranscriptionsBody, FimBody } from "../groups/kilo-gateway" + +const FIM_TIMEOUT_MS = 30_000 export const kiloGatewayHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilo", (handlers) => Effect.gen(function* () { @@ -47,6 +55,117 @@ export const kiloGatewayHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilo", return { profile, balance, currentOrgId } }) + const proxy = Effect.fn("KiloGatewayHttpApi.proxyAuth")(function* () { + const info = yield* auth.get("kilo").pipe(Effect.mapError(() => new HttpApiError.Unauthorized({}))) + return { + auth: info, + token: getToken(info), + organizationId: getOrganizationId(info), + } + }) + + const modes = Effect.fn("KiloGatewayHttpApi.modes")(function* () { + const info = yield* auth.get("kilo").pipe(Effect.mapError(() => new HttpApiError.BadRequest({}))) + if (!info || info.type !== "oauth" || !info.access || !info.accountId) return { modes: [] } + + return yield* Effect.promise(() => fetchOrganizationModes(info.access, info.accountId)).pipe( + Effect.map((modes) => ({ modes })), + Effect.catch(() => Effect.succeed({ modes: [] })), + ) + }) + + const fim = Effect.fn("KiloGatewayHttpApi.fim")(function* (ctx: { payload: typeof FimBody.Type }) { + const info = yield* proxy() + if (!info.auth) return yield* Effect.fail(new HttpApiError.Unauthorized({})) + if (!info.token) return yield* Effect.fail(new HttpApiError.Unauthorized({})) + + const request = yield* HttpServerRequest.HttpServerRequest + const endpoint = new URL("fim/completions", `${KILO_API_BASE}/api/`) + const signal = AbortSignal.any([request.source.signal, AbortSignal.timeout(FIM_TIMEOUT_MS)]) + const response = yield* Effect.promise(async () => { + try { + return await fetch(endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${info.token}`, + ...buildKiloHeaders(undefined, { kilocodeOrganizationId: info.organizationId }), + [HEADER_FEATURE]: "autocomplete", + }, + signal, + body: JSON.stringify({ + model: ctx.payload.model ?? "mistralai/codestral-2501", + prompt: ctx.payload.prefix, + suffix: ctx.payload.suffix, + max_tokens: ctx.payload.maxTokens ?? 256, + temperature: ctx.payload.temperature ?? 0.2, + stream: true, + }), + }) + } catch (err) { + if (err instanceof DOMException && err.name === "TimeoutError") + return Response.json({ error: "FIM request timed out" }, { status: 504 }) + if (signal.aborted) return Response.json({ error: "FIM request canceled" }, { status: 499 }) + throw err + } + }) + if (!response.ok) { + const text = yield* Effect.promise(() => response.text()) + return HttpServerResponse.jsonUnsafe( + { error: `FIM request failed: ${response.status} ${text}` }, + { status: response.status }, + ) + } + if (!response.body) return HttpServerResponse.raw(null, { status: response.status }) + + return HttpServerResponse.stream( + Stream.fromReadableStream({ + evaluate: () => response.body!, + onError: (err) => err, + }), + { + contentType: "text/event-stream", + headers: { + "Cache-Control": "no-cache", + Connection: "keep-alive", + }, + }, + ) + }) + + const audioTranscriptions = Effect.fn("KiloGatewayHttpApi.audioTranscriptions")(function* (ctx: { + payload: typeof AudioTranscriptionsBody.Type + }) { + const info = yield* proxy() + if (!info.auth) return yield* Effect.fail(new HttpApiError.Unauthorized({})) + if (!info.token) return yield* Effect.fail(new HttpApiError.Unauthorized({})) + + const request = yield* HttpServerRequest.HttpServerRequest + const response = yield* Effect.tryPromise({ + try: () => + fetch(`${KILO_API_BASE}/api/gateway/v1/audio/transcriptions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${info.token}`, + ...buildKiloHeaders(undefined, { kilocodeOrganizationId: info.organizationId }), + [HEADER_FEATURE]: "vscode-extension", + }, + signal: request.source.signal, + body: JSON.stringify(ctx.payload), + }), + catch: () => new HttpApiError.BadRequest({}), + }) + const text = yield* Effect.promise(() => response.text()) + return HttpServerResponse.raw(text, { + status: response.status, + contentType: response.headers.get("Content-Type") ?? "application/json", + headers: { + "Content-Type": response.headers.get("Content-Type") ?? "application/json", + }, + }) + }) + const notifications = Effect.fn("KiloGatewayHttpApi.notifications")(function* () { const info = yield* auth.get("kilo").pipe(Effect.mapError(() => new HttpApiError.BadRequest({}))) const token = getToken(info) @@ -178,6 +297,9 @@ export const kiloGatewayHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilo", return handlers .handle("profile", profile) + .handle("modes", modes) + .handle("fim", fim) + .handle("audioTranscriptions", audioTranscriptions) .handle("notifications", notifications) .handle("organization", organization) .handle("clawStatus", clawStatus) diff --git a/packages/opencode/src/kilocode/server/httpapi/instance.ts b/packages/opencode/src/kilocode/server/httpapi/instance.ts index 9c211f3e1a6..980b8f92ce1 100644 --- a/packages/opencode/src/kilocode/server/httpapi/instance.ts +++ b/packages/opencode/src/kilocode/server/httpapi/instance.ts @@ -35,6 +35,9 @@ export function register(app: Hono, handler: Handler, context: Context.Context handler(c.req.raw, context)) app.get(IndexingPaths.status, (c) => handler(c.req.raw, context)) app.get(KiloGatewayPaths.profile, (c) => handler(c.req.raw, context)) + app.get(KiloGatewayPaths.modes, (c) => handler(c.req.raw, context)) + app.post(KiloGatewayPaths.fim, (c) => handler(c.req.raw, context)) + app.post(KiloGatewayPaths.audioTranscriptions, (c) => handler(c.req.raw, context)) app.get(KiloGatewayPaths.notifications, (c) => handler(c.req.raw, context)) app.post(KiloGatewayPaths.organization, (c) => handler(c.req.raw, context)) app.get(KiloGatewayPaths.clawStatus, (c) => handler(c.req.raw, context)) diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/experimental.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/experimental.ts index e4a86ca1394..c6cf30cd84a 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/experimental.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/experimental.ts @@ -1,7 +1,9 @@ import { AccountID, OrgID } from "@/account/schema" +import { Snapshot } from "@/snapshot" // kilocode_change import { MCP } from "@/mcp" import { ProviderID, ModelID } from "@/provider/schema" import { Session } from "@/session/session" +import { WorktreeDiff } from "@/kilocode/review/worktree-diff" // kilocode_change import { Worktree } from "@/worktree" import { NonNegativeInt } from "@/util/schema" import { Schema, SchemaGetter } from "effect" @@ -55,7 +57,11 @@ const QueryBoolean = Schema.Literals(["true", "false"]).pipe( ) const WorktreeList = Schema.Array(Schema.String) export const SessionListQuery = Schema.Struct({ + // kilocode_change start + projectID: Schema.optional(Schema.String), directory: Schema.optional(Schema.String), + worktrees: Schema.optional(QueryBoolean), + // kilocode_change end roots: Schema.optional(QueryBoolean), start: Schema.optional(Schema.NumberFromString), cursor: Schema.optional(Schema.NumberFromString), @@ -63,6 +69,15 @@ export const SessionListQuery = Schema.Struct({ limit: Schema.optional(Schema.NumberFromString), archived: Schema.optional(QueryBoolean), }) +// kilocode_change start +export const WorktreeDiffQuery = Schema.Struct({ + base: Schema.optional(Schema.String), +}) +export const WorktreeDiffFileQuery = Schema.Struct({ + base: Schema.optional(Schema.String), + file: Schema.String, +}) +// kilocode_change end export const ExperimentalPaths = { console: "/experimental/console", @@ -71,6 +86,9 @@ export const ExperimentalPaths = { tool: "/experimental/tool", toolIDs: "/experimental/tool/ids", worktree: "/experimental/worktree", + worktreeDiff: "/experimental/worktree/diff", // kilocode_change + worktreeDiffFile: "/experimental/worktree/diff/file", // kilocode_change + worktreeDiffSummary: "/experimental/worktree/diff/summary", // kilocode_change worktreeReset: "/experimental/worktree/reset", session: "/experimental/session", resource: "/experimental/resource", @@ -174,6 +192,41 @@ export const ExperimentalApi = HttpApi.make("experimental") description: "Reset a worktree branch to the primary default branch.", }), ), + // kilocode_change start - worktree diff endpoints for agent manager + HttpApiEndpoint.get("worktreeDiff", ExperimentalPaths.worktreeDiff, { + query: WorktreeDiffQuery, + success: described(Schema.Array(Snapshot.FileDiff), "File diffs"), + error: HttpApiError.BadRequest, + }).annotateMerge( + OpenApi.annotations({ + identifier: "worktree.diff", + summary: "Get worktree diff", + description: "Get file diffs for a worktree compared to its base branch. Includes uncommitted changes.", + }), + ), + HttpApiEndpoint.get("worktreeDiffSummary", ExperimentalPaths.worktreeDiffSummary, { + query: WorktreeDiffQuery, + success: described(Schema.Array(WorktreeDiff.Item), "Diff summary items"), + error: HttpApiError.BadRequest, + }).annotateMerge( + OpenApi.annotations({ + identifier: "worktree.diffSummary", + summary: "Get worktree diff summary", + description: "Get lightweight file diff metadata for a worktree compared to its base branch.", + }), + ), + HttpApiEndpoint.get("worktreeDiffFile", ExperimentalPaths.worktreeDiffFile, { + query: WorktreeDiffFileQuery, + success: described(Schema.NullOr(WorktreeDiff.Item), "Diff detail item"), + error: HttpApiError.BadRequest, + }).annotateMerge( + OpenApi.annotations({ + identifier: "worktree.diffFile", + summary: "Get worktree diff detail", + description: "Get full diff contents for one worktree file compared to its base branch.", + }), + ), + // kilocode_change end HttpApiEndpoint.get("session", ExperimentalPaths.session, { query: SessionListQuery, success: described(Schema.Array(Session.GlobalInfo), "List of sessions"), diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts index 77d064ff5a2..52e71bb8b28 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts @@ -66,6 +66,12 @@ export const RevertPayload = Schema.Struct(Struct.omit(SessionRevert.RevertInput export const PermissionResponsePayload = Schema.Struct({ response: Permission.Reply, }) +// kilocode_change start +export const ViewedPayload = Schema.Struct({ + focused: Schema.optional(Schema.Array(Schema.String)), + open: Schema.optional(Schema.Array(Schema.String)), +}) +// kilocode_change end export const SessionPaths = { list: root, @@ -94,6 +100,7 @@ export const SessionPaths = { deleteMessage: `${root}/:sessionID/message/:messageID`, deletePart: `${root}/:sessionID/message/:messageID/part/:partID`, updatePart: `${root}/:sessionID/message/:messageID/part/:partID`, + viewed: `${root}/viewed`, // kilocode_change } as const export const SessionApi = HttpApi.make("session") @@ -409,6 +416,18 @@ export const SessionApi = HttpApi.make("session") description: "Update a part in a message.", }), ), + // kilocode_change start + HttpApiEndpoint.post("viewed", SessionPaths.viewed, { + payload: ViewedPayload, + success: described(Schema.Boolean, "Viewed sessions updated"), + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.viewed", + summary: "Set viewed sessions", + description: "Notify the server which sessions the user is currently viewing, or clear all.", + }), + ), + // kilocode_change end ) .annotateMerge( OpenApi.annotations({ diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts index cc958da3037..419aa7b1687 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts @@ -7,12 +7,24 @@ import { Project } from "@/project/project" import { Session } from "@/session/session" import { ToolRegistry } from "@/tool/registry" import * as EffectZod from "@/util/effect-zod" +import { Filesystem } from "@/util/filesystem" // kilocode_change +import { Review } from "@/kilocode/review/review" // kilocode_change +import { WorktreeDiff } from "@/kilocode/review/worktree-diff" // kilocode_change +import { WorktreeFamily } from "@/kilocode/worktree-family" // kilocode_change import { Worktree } from "@/worktree" import { Effect, Option } from "effect" import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse" import { HttpApiBuilder, HttpApiError } from "effect/unstable/httpapi" +import * as Log from "@opencode-ai/core/util/log" // kilocode_change +import path from "path" // kilocode_change import { InstanceHttpApi } from "../api" -import { ConsoleSwitchPayload, SessionListQuery, ToolListQuery } from "../groups/experimental" +import { + ConsoleSwitchPayload, + SessionListQuery, + ToolListQuery, + WorktreeDiffFileQuery, + WorktreeDiffQuery, +} from "../groups/experimental" export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "experimental", (handlers) => Effect.gen(function* () { @@ -113,11 +125,67 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper return true }) + // 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()) + }) + + const worktreeDiff = Effect.fn("ExperimentalHttpApi.worktreeDiff")(function* (ctx: { + query: typeof WorktreeDiffQuery.Type + }) { + const log = Log.create({ service: "worktree-diff" }) + const ref = yield* base(ctx.query) + const dir = yield* InstanceState.directory + log.info("computing diff", { dir, base: ref }) + const diffs = yield* Effect.promise(() => WorktreeDiff.full({ dir, base: ref, log })) + return diffs.map((diff) => ({ + file: diff.file, + before: diff.before, + after: diff.after, + patch: diff.patch, + additions: diff.additions, + deletions: diff.deletions, + status: diff.status, + })) + }) + + const worktreeDiffSummary = Effect.fn("ExperimentalHttpApi.worktreeDiffSummary")(function* (ctx: { + query: typeof WorktreeDiffQuery.Type + }) { + const log = Log.create({ service: "worktree-diff" }) + const ref = yield* base(ctx.query) + const dir = yield* InstanceState.directory + log.info("computing diff summary", { dir, base: ref }) + return yield* Effect.promise(() => WorktreeDiff.summary({ dir, base: ref, log })) + }) + + const worktreeDiffFile = Effect.fn("ExperimentalHttpApi.worktreeDiffFile")(function* (ctx: { + query: typeof WorktreeDiffFileQuery.Type + }) { + const log = Log.create({ service: "worktree-diff" }) + const ref = yield* base(ctx.query) + const dir = yield* InstanceState.directory + log.info("computing diff detail", { dir, base: ref, file: ctx.query.file }) + return yield* Effect.promise(() => WorktreeDiff.detail({ dir, base: ref, file: ctx.query.file, log })).pipe( + Effect.map((item) => item ?? null), + ) + }) + // kilocode_change end + const session = Effect.fn("ExperimentalHttpApi.session")(function* (ctx: { query: typeof SessionListQuery.Type }) { const limit = ctx.query.limit ?? 100 + // 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 sorted = directories ? [...directories].sort((a, b) => b.length - a.length) : undefined + // kilocode_change end const sessions = Array.from( Session.listGlobal({ - directory: ctx.query.directory, + projectID, // kilocode_change + directory: ctx.query.worktrees ? undefined : ctx.query.directory, // kilocode_change + directories, // kilocode_change roots: ctx.query.roots, start: ctx.query.start, cursor: ctx.query.cursor, @@ -126,10 +194,18 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper archived: ctx.query.archived, }), ) - const list = sessions.length > limit ? sessions.slice(0, limit) : sessions + // kilocode_change start - resolve worktree folder name for each session + const result = sorted + ? sessions.map((session) => { + const root = sorted.find((dir) => Filesystem.contains(dir, session.directory)) + return { ...session, worktreeName: path.basename(root ?? session.directory) } + }) + : sessions + const list = result.length > limit ? result.slice(0, limit) : result + // kilocode_change end return HttpServerResponse.jsonUnsafe(list, { headers: - sessions.length > limit && list.length > 0 + result.length > limit && list.length > 0 // kilocode_change ? { "x-next-cursor": String(list[list.length - 1].time.updated) } : undefined, }) @@ -139,17 +215,24 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper return yield* mcp.resources() }) - return handlers - .handle("console", getConsole) - .handle("consoleOrgs", listConsoleOrgs) - .handle("consoleSwitch", switchConsole) - .handle("tool", tool) - .handle("toolIDs", toolIDs) - .handle("worktree", worktree) - .handle("worktreeCreate", worktreeCreate) - .handle("worktreeRemove", worktreeRemove) - .handle("worktreeReset", worktreeReset) - .handle("session", session) - .handle("resource", resource) + return ( + handlers + .handle("console", getConsole) + .handle("consoleOrgs", listConsoleOrgs) + .handle("consoleSwitch", switchConsole) + .handle("tool", tool) + .handle("toolIDs", toolIDs) + .handle("worktree", worktree) + .handle("worktreeCreate", worktreeCreate) + .handle("worktreeRemove", worktreeRemove) + .handle("worktreeReset", worktreeReset) + // kilocode_change start + .handle("worktreeDiff", worktreeDiff) + .handle("worktreeDiffSummary", worktreeDiffSummary) + .handle("worktreeDiffFile", worktreeDiffFile) + // kilocode_change end + .handle("session", session) + .handle("resource", resource) + ) }), ) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts index 80c0684e1d4..1e8fe40b706 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts @@ -36,6 +36,7 @@ import { ShellPayload, SummarizePayload, UpdatePayload, + ViewedPayload, } from "../groups/session" const mapNotFound = (self: Effect.Effect) => @@ -360,6 +361,14 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", return yield* session.updatePart(payload) }) + // kilocode_change start + const viewed = Effect.fn("SessionHttpApi.viewed")(function* (ctx: { payload: typeof ViewedPayload.Type }) { + const { KiloSessions } = yield* Effect.promise(() => import("@/kilo-sessions/kilo-sessions")) + KiloSessions.setViewedSessions({ focused: ctx.payload.focused ?? [], open: ctx.payload.open ?? [] }) + return true + }) + // kilocode_change end + return handlers .handle("list", list) .handle("status", status) @@ -388,5 +397,6 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", .handle("deleteMessage", deleteMessage) .handle("deletePart", deletePart) .handle("updatePart", updatePart) + .handle("viewed", viewed) // kilocode_change }), ) diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index e60d2880273..4758e90732a 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -69,6 +69,9 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket, opts?: CorsOptions): H app.get(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) app.post(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) app.delete(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) + app.get(ExperimentalPaths.worktreeDiff, (c) => handler(c.req.raw, context)) // kilocode_change + app.get(ExperimentalPaths.worktreeDiffFile, (c) => handler(c.req.raw, context)) // kilocode_change + app.get(ExperimentalPaths.worktreeDiffSummary, (c) => handler(c.req.raw, context)) // kilocode_change app.post(ExperimentalPaths.worktreeReset, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.session, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.resource, (c) => handler(c.req.raw, context)) @@ -140,6 +143,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket, opts?: CorsOptions): H app.delete(SessionPaths.deleteMessage, (c) => handler(c.req.raw, context)) app.delete(SessionPaths.deletePart, (c) => handler(c.req.raw, context)) app.patch(SessionPaths.updatePart, (c) => handler(c.req.raw, context)) + app.post(SessionPaths.viewed, (c) => handler(c.req.raw, context)) // kilocode_change app.post(TuiPaths.appendPrompt, (c) => handler(c.req.raw, context)) app.post(TuiPaths.openHelp, (c) => handler(c.req.raw, context)) app.post(TuiPaths.openSessions, (c) => handler(c.req.raw, context)) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 1de79123bbe..42f4b56963e 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -62,6 +62,8 @@ import type { GlobalUpgradeResponses, IndexingStatusResponses, InstanceDisposeResponses, + KiloAudioTranscriptionsErrors, + KiloAudioTranscriptionsResponses, KiloClawChatCredentialsResponses, KiloClawStatusErrors, KiloClawStatusResponses, @@ -85,6 +87,9 @@ import type { KilocodeSessionImportProjectResponses, KilocodeSessionImportSessionErrors, KilocodeSessionImportSessionResponses, + KiloFimErrors, + KiloFimResponses, + KiloModesResponses, KiloNotificationsErrors, KiloNotificationsResponses, KiloOrganizationSetErrors, @@ -211,6 +216,7 @@ import type { SessionUnshareResponses, SessionUpdateErrors, SessionUpdateResponses, + SessionViewedResponses, SubtaskPartInput, SuggestionAcceptErrors, SuggestionAcceptResponses, @@ -261,6 +267,12 @@ import type { WorktreeCreateErrors, WorktreeCreateInput, WorktreeCreateResponses, + WorktreeDiffErrors, + WorktreeDiffFileErrors, + WorktreeDiffFileResponses, + WorktreeDiffResponses, + WorktreeDiffSummaryErrors, + WorktreeDiffSummaryResponses, WorktreeListResponses, WorktreeRemoveErrors, WorktreeRemoveInput, @@ -853,6 +865,8 @@ export class Session extends HeyApiClient { parameters?: { directory?: string workspace?: string + projectID?: string + worktrees?: "true" | "false" roots?: boolean | "true" | "false" start?: number cursor?: number @@ -869,6 +883,8 @@ export class Session extends HeyApiClient { args: [ { in: "query", key: "directory" }, { in: "query", key: "workspace" }, + { in: "query", key: "projectID" }, + { in: "query", key: "worktrees" }, { in: "query", key: "roots" }, { in: "query", key: "start" }, { in: "query", key: "cursor" }, @@ -1373,6 +1389,104 @@ export class Worktree extends HeyApiClient { }, }) } + + /** + * Get worktree diff + * + * Get file diffs for a worktree compared to its base branch. Includes uncommitted changes. + */ + public diff( + parameters?: { + directory?: string + workspace?: string + base?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "query", key: "base" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/experimental/worktree/diff", + ...options, + ...params, + }) + } + + /** + * Get worktree diff summary + * + * Get lightweight file diff metadata for a worktree compared to its base branch. + */ + public diffSummary( + parameters?: { + directory?: string + workspace?: string + base?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "query", key: "base" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/experimental/worktree/diff/summary", + ...options, + ...params, + }) + } + + /** + * Get worktree diff detail + * + * Get full diff contents for one worktree file compared to its base branch. + */ + public diffFile( + parameters: { + directory?: string + workspace?: string + base?: string + file: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "query", key: "base" }, + { in: "query", key: "file" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/experimental/worktree/diff/file", + ...options, + ...params, + }) + } } export class Find extends HeyApiClient { @@ -3941,6 +4055,45 @@ export class Session2 extends HeyApiClient { ...params, }) } + + /** + * Set viewed sessions + * + * Notify the server which sessions the user is currently viewing, or clear all. + */ + public viewed( + parameters?: { + directory?: string + workspace?: string + focused?: Array + open?: Array + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "focused" }, + { in: "body", key: "open" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/session/viewed", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } } export class Part extends HeyApiClient { @@ -4930,6 +5083,58 @@ export class Indexing extends HeyApiClient { } } +export class Audio extends HeyApiClient { + /** + * Speech to text transcription + * + * Proxy an audio transcription request to the Kilo Gateway + */ + public transcriptions( + parameters?: { + directory?: string + workspace?: string + model?: string + input_audio?: { + data: string + format: string + } + language?: string + temperature?: number + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "model" }, + { in: "body", key: "input_audio" }, + { in: "body", key: "language" }, + { in: "body", key: "temperature" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post< + KiloAudioTranscriptionsResponses, + KiloAudioTranscriptionsErrors, + ThrowOnError + >({ + url: "/kilo/audio/transcriptions", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } +} + export class Organization extends HeyApiClient { /** * Update Kilo Gateway organization @@ -5146,6 +5351,81 @@ export class Kilo extends HeyApiClient { }) } + /** + * Get organization custom modes + * + * Fetch custom modes defined for the current organization + */ + public modes( + parameters?: { + directory?: string + workspace?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/kilo/modes", + ...options, + ...params, + }) + } + + /** + * FIM completion + * + * Proxy a Fill-in-the-Middle completion request to the Kilo Gateway + */ + public fim( + parameters?: { + directory?: string + workspace?: string + prefix?: string + suffix?: string + model?: string + maxTokens?: number + temperature?: number + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "prefix" }, + { in: "body", key: "suffix" }, + { in: "body", key: "model" }, + { in: "body", key: "maxTokens" }, + { in: "body", key: "temperature" }, + ], + }, + ], + ) + return (options?.client ?? this.client).sse.post({ + url: "/kilo/fim", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + /** * Get Kilo notifications * @@ -5212,6 +5492,11 @@ export class Kilo extends HeyApiClient { }) } + private _audio?: Audio + get audio(): Audio { + return (this._audio ??= new Audio({ client: this.client })) + } + private _organization?: Organization get organization(): Organization { return (this._organization ??= new Organization({ client: this.client })) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 2a00757de5d..0dea7224a3b 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1465,6 +1465,20 @@ export type WorktreeResetInput = { directory: string } +export type WorktreeDiffItem = { + file: string + patch: string + additions: number + deletions: number + status?: "added" | "deleted" | "modified" + before: string + after: string + tracked: boolean + generatedLike: boolean + summarized: boolean + stamp: string +} + export type ProjectSummary = { id: string name?: string @@ -4077,12 +4091,102 @@ export type WorktreeResetResponses = { export type WorktreeResetResponse = WorktreeResetResponses[keyof WorktreeResetResponses] +export type WorktreeDiffData = { + body?: never + path?: never + query?: { + directory?: string + workspace?: string + base?: string + } + url: "/experimental/worktree/diff" +} + +export type WorktreeDiffErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type WorktreeDiffError = WorktreeDiffErrors[keyof WorktreeDiffErrors] + +export type WorktreeDiffResponses = { + /** + * File diffs + */ + 200: Array +} + +export type WorktreeDiffResponse = WorktreeDiffResponses[keyof WorktreeDiffResponses] + +export type WorktreeDiffSummaryData = { + body?: never + path?: never + query?: { + directory?: string + workspace?: string + base?: string + } + url: "/experimental/worktree/diff/summary" +} + +export type WorktreeDiffSummaryErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type WorktreeDiffSummaryError = WorktreeDiffSummaryErrors[keyof WorktreeDiffSummaryErrors] + +export type WorktreeDiffSummaryResponses = { + /** + * Diff summary items + */ + 200: Array +} + +export type WorktreeDiffSummaryResponse = WorktreeDiffSummaryResponses[keyof WorktreeDiffSummaryResponses] + +export type WorktreeDiffFileData = { + body?: never + path?: never + query: { + directory?: string + workspace?: string + base?: string + file: string + } + url: "/experimental/worktree/diff/file" +} + +export type WorktreeDiffFileErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type WorktreeDiffFileError = WorktreeDiffFileErrors[keyof WorktreeDiffFileErrors] + +export type WorktreeDiffFileResponses = { + /** + * Diff detail item + */ + 200: WorktreeDiffItem +} + +export type WorktreeDiffFileResponse = WorktreeDiffFileResponses[keyof WorktreeDiffFileResponses] + export type ExperimentalSessionListData = { body?: never path?: never query?: { directory?: string workspace?: string + projectID?: string + worktrees?: "true" | "false" roots?: boolean | "true" | "false" start?: number cursor?: number @@ -6322,6 +6426,28 @@ export type PartUpdateResponses = { export type PartUpdateResponse = PartUpdateResponses[keyof PartUpdateResponses] +export type SessionViewedData = { + body?: { + focused?: Array + open?: Array + } + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/session/viewed" +} + +export type SessionViewedResponses = { + /** + * Viewed sessions updated + */ + 200: boolean +} + +export type SessionViewedResponse = SessionViewedResponses[keyof SessionViewedResponses] + export type SyncStartData = { body?: never path?: never @@ -7172,6 +7298,124 @@ export type KiloProfileResponses = { export type KiloProfileResponse = KiloProfileResponses[keyof KiloProfileResponses] +export type KiloModesData = { + body?: never + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/kilo/modes" +} + +export type KiloModesResponses = { + /** + * Organization modes list + */ + 200: { + modes: Array<{ + id: string + organization_id: string + name: string + slug: string + created_by: string + created_at: string + updated_at: string + config: { + roleDefinition?: string + whenToUse?: string + description?: string + customInstructions?: string + groups?: Array< + | string + | [ + string, + { + fileRegex?: string | null + description?: string | null + }, + ] + > + } + }> + } +} + +export type KiloModesResponse = KiloModesResponses[keyof KiloModesResponses] + +export type KiloFimData = { + body?: { + prefix: string + suffix: string + model?: string + maxTokens?: number + temperature?: number + } + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/kilo/fim" +} + +export type KiloFimErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type KiloFimError = KiloFimErrors[keyof KiloFimErrors] + +export type KiloFimResponses = { + /** + * Success + */ + 200: string +} + +export type KiloFimResponse = KiloFimResponses[keyof KiloFimResponses] + +export type KiloAudioTranscriptionsData = { + body?: { + model: string + input_audio: { + data: string + format: string + } + language?: string + temperature?: number + } + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/kilo/audio/transcriptions" +} + +export type KiloAudioTranscriptionsErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type KiloAudioTranscriptionsError = KiloAudioTranscriptionsErrors[keyof KiloAudioTranscriptionsErrors] + +export type KiloAudioTranscriptionsResponses = { + /** + * Transcription response + */ + 200: { + text: string + usage?: unknown + } +} + +export type KiloAudioTranscriptionsResponse = KiloAudioTranscriptionsResponses[keyof KiloAudioTranscriptionsResponses] + export type KiloNotificationsData = { body?: never path?: never diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 41c446d2567..53097a4dde0 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -1253,6 +1253,208 @@ ] } }, + "/experimental/worktree/diff": { + "get": { + "tags": ["experimental"], + "operationId": "worktree.diff", + "parameters": [ + { + "name": "directory", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "base", + "in": "query", + "schema": { + "type": "string" + }, + "required": false + } + ], + "responses": { + "200": { + "description": "File diffs", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SnapshotFileDiff" + }, + "description": "File diffs" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "description": "Get file diffs for a worktree compared to its base branch. Includes uncommitted changes.", + "summary": "Get worktree diff", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.worktree.diff({\n ...\n})" + } + ] + } + }, + "/experimental/worktree/diff/summary": { + "get": { + "tags": ["experimental"], + "operationId": "worktree.diffSummary", + "parameters": [ + { + "name": "directory", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "base", + "in": "query", + "schema": { + "type": "string" + }, + "required": false + } + ], + "responses": { + "200": { + "description": "Diff summary items", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorktreeDiffItem" + }, + "description": "Diff summary items" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "description": "Get lightweight file diff metadata for a worktree compared to its base branch.", + "summary": "Get worktree diff summary", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.worktree.diffSummary({\n ...\n})" + } + ] + } + }, + "/experimental/worktree/diff/file": { + "get": { + "tags": ["experimental"], + "operationId": "worktree.diffFile", + "parameters": [ + { + "name": "directory", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "base", + "in": "query", + "schema": { + "type": "string" + }, + "required": false + }, + { + "name": "file", + "in": "query", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Diff detail item", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorktreeDiffItem" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "description": "Get full diff contents for one worktree file compared to its base branch.", + "summary": "Get worktree diff detail", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.worktree.diffFile({\n ...\n})" + } + ] + } + }, "/experimental/session": { "get": { "tags": ["experimental"], @@ -1274,6 +1476,23 @@ "type": "string" } }, + { + "name": "projectID", + "in": "query", + "schema": { + "type": "string" + }, + "required": false + }, + { + "name": "worktrees", + "in": "query", + "schema": { + "type": "string", + "enum": ["true", "false"] + }, + "required": false + }, { "name": "roots", "in": "query", @@ -6917,6 +7136,75 @@ ] } }, + "/session/viewed": { + "post": { + "tags": ["session"], + "operationId": "session.viewed", + "parameters": [ + { + "name": "directory", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Viewed sessions updated", + "content": { + "application/json": { + "schema": { + "type": "boolean", + "description": "Viewed sessions updated" + } + } + } + } + }, + "description": "Notify the server which sessions the user is currently viewing, or clear all.", + "summary": "Set viewed sessions", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "focused": { + "type": "array", + "items": { + "type": "string" + } + }, + "open": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.session.viewed({\n ...\n})" + } + ] + } + }, "/sync/start": { "post": { "tags": ["sync"], @@ -8967,6 +9255,339 @@ ] } }, + "/kilo/modes": { + "get": { + "tags": ["kilo"], + "operationId": "kilo.modes", + "parameters": [ + { + "name": "directory", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Organization modes list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "modes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "organization_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "config": { + "type": "object", + "properties": { + "roleDefinition": { + "type": "string" + }, + "whenToUse": { + "type": "string" + }, + "description": { + "type": "string" + }, + "customInstructions": { + "type": "string" + }, + "groups": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "prefixItems": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "fileRegex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + ], + "maxItems": 2, + "minItems": 2 + } + ] + } + } + }, + "additionalProperties": false + } + }, + "required": [ + "id", + "organization_id", + "name", + "slug", + "created_by", + "created_at", + "updated_at", + "config" + ], + "additionalProperties": false + } + } + }, + "required": ["modes"], + "additionalProperties": false, + "description": "Organization modes list" + } + } + } + } + }, + "description": "Fetch custom modes defined for the current organization", + "summary": "Get organization custom modes", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.kilo.modes({\n ...\n})" + } + ] + } + }, + "/kilo/fim": { + "post": { + "tags": ["kilo"], + "operationId": "kilo.fim", + "parameters": [ + { + "name": "directory", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "text/event-stream": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "description": "Proxy a Fill-in-the-Middle completion request to the Kilo Gateway", + "summary": "FIM completion", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "prefix": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "model": { + "type": "string" + }, + "maxTokens": { + "type": "number" + }, + "temperature": { + "type": "number" + } + }, + "required": ["prefix", "suffix"], + "additionalProperties": false + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.kilo.fim({\n ...\n})" + } + ] + } + }, + "/kilo/audio/transcriptions": { + "post": { + "tags": ["kilo"], + "operationId": "kilo.audio.transcriptions", + "parameters": [ + { + "name": "directory", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Transcription response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "usage": {} + }, + "required": ["text"], + "additionalProperties": false, + "description": "Transcription response" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "description": "Proxy an audio transcription request to the Kilo Gateway", + "summary": "Speech to text transcription", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "model": { + "type": "string" + }, + "input_audio": { + "type": "object", + "properties": { + "data": { + "type": "string" + }, + "format": { + "type": "string" + } + }, + "required": ["data", "format"], + "additionalProperties": false + }, + "language": { + "type": "string" + }, + "temperature": { + "type": "number" + } + }, + "required": ["model", "input_audio"], + "additionalProperties": false + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.kilo.audio.transcriptions({\n ...\n})" + } + ] + } + }, "/kilo/notifications": { "get": { "tags": ["kilo"], @@ -15586,6 +16207,60 @@ "required": ["directory"], "additionalProperties": false }, + "WorktreeDiffItem": { + "type": "object", + "properties": { + "file": { + "type": "string" + }, + "patch": { + "type": "string" + }, + "additions": { + "type": "integer", + "minimum": 0 + }, + "deletions": { + "type": "integer", + "minimum": 0 + }, + "status": { + "type": "string", + "enum": ["added", "deleted", "modified"] + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + }, + "tracked": { + "type": "boolean" + }, + "generatedLike": { + "type": "boolean" + }, + "summarized": { + "type": "boolean" + }, + "stamp": { + "type": "string" + } + }, + "required": [ + "file", + "patch", + "additions", + "deletions", + "before", + "after", + "tracked", + "generatedLike", + "summarized", + "stamp" + ], + "additionalProperties": false + }, "ProjectSummary": { "type": "object", "properties": {