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 99df6a25b4b..868755c1347 100644 --- a/packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts +++ b/packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts @@ -55,7 +55,7 @@ export const kiloGatewayHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilo", return { profile, balance, currentOrgId } }) - const proxy = Effect.fn("KiloGatewayHttpApi.proxyAuth")(function* () { + const proxyAuth = Effect.fn("KiloGatewayHttpApi.proxyAuth")(function* () { const info = yield* auth.get("kilo").pipe(Effect.mapError(() => new HttpApiError.Unauthorized({}))) return { auth: info, @@ -65,7 +65,7 @@ export const kiloGatewayHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilo", }) const modes = Effect.fn("KiloGatewayHttpApi.modes")(function* () { - const info = yield* auth.get("kilo").pipe(Effect.mapError(() => new HttpApiError.BadRequest({}))) + const info = yield* auth.get("kilo").pipe(Effect.catch(() => Effect.succeed(undefined))) if (!info || info.type !== "oauth" || !info.access || !info.accountId) return { modes: [] } return yield* Effect.promise(() => fetchOrganizationModes(info.access, info.accountId)).pipe( @@ -75,7 +75,7 @@ export const kiloGatewayHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilo", }) const fim = Effect.fn("KiloGatewayHttpApi.fim")(function* (ctx: { payload: typeof FimBody.Type }) { - const info = yield* proxy() + const info = yield* proxyAuth() if (!info.auth) return yield* Effect.fail(new HttpApiError.Unauthorized({})) if (!info.token) return yield* Effect.fail(new HttpApiError.Unauthorized({})) @@ -136,7 +136,7 @@ export const kiloGatewayHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilo", const audioTranscriptions = Effect.fn("KiloGatewayHttpApi.audioTranscriptions")(function* (ctx: { payload: typeof AudioTranscriptionsBody.Type }) { - const info = yield* proxy() + const info = yield* proxyAuth() if (!info.auth) return yield* Effect.fail(new HttpApiError.Unauthorized({})) if (!info.token) return yield* Effect.fail(new HttpApiError.Unauthorized({})) @@ -160,9 +160,6 @@ export const kiloGatewayHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilo", 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", - }, }) }) diff --git a/packages/opencode/src/server/routes/instance/httpapi/public.ts b/packages/opencode/src/server/routes/instance/httpapi/public.ts index fdcd84afe4c..fee4a2b4c0f 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/public.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/public.ts @@ -75,6 +75,7 @@ const QueryNumberParameters = new Set(["start", "cursor", "limit", "method"]) const QueryBooleanParameters = new Set(["roots", "archived"]) const QueryParameterSchemas = { "GET /find/file limit": { type: "integer", minimum: 1, maximum: 200 }, + "GET /experimental/session worktrees": { type: "boolean" }, // kilocode_change "GET /kilo/cloud-sessions cursor": { type: "string" }, // kilocode_change "GET /session/{sessionID}/diff messageID": { type: "string", pattern: "^msg.*" }, "GET /session/{sessionID}/message limit": { type: "integer", minimum: 0, maximum: Number.MAX_SAFE_INTEGER }, @@ -523,6 +524,8 @@ function pathParameterSchema(route: string, name: string) { if (name === "id" && route.startsWith("POST /experimental/workspace/")) return { type: "string", pattern: "^wrk.*" } if (name === "requestID" && route.startsWith("POST /permission/")) return { type: "string", pattern: "^per.*" } if (name === "requestID" && route.startsWith("POST /question/")) return { type: "string", pattern: "^que.*" } + // /network/* reuses QuestionID (prefix "que"), not a separate brand. // kilocode_change + if (name === "requestID" && route.startsWith("POST /network/")) return { type: "string", pattern: "^que.*" } // kilocode_change return undefined } diff --git a/packages/opencode/test/server/httpapi-bridge.test.ts b/packages/opencode/test/server/httpapi-bridge.test.ts index 2fd067d1bf9..1c639387196 100644 --- a/packages/opencode/test/server/httpapi-bridge.test.ts +++ b/packages/opencode/test/server/httpapi-bridge.test.ts @@ -2,10 +2,12 @@ import { afterEach, describe, expect, test } from "bun:test" import { Flag } from "@opencode-ai/core/flag/flag" import { Instance } from "../../src/project/instance" import { ControlPaths } from "../../src/server/routes/instance/httpapi/groups/control" +import { ExperimentalPaths } from "../../src/server/routes/instance/httpapi/groups/experimental" import { FilePaths } from "../../src/server/routes/instance/httpapi/groups/file" import { GlobalPaths } from "../../src/server/routes/instance/httpapi/groups/global" import { KiloGatewayPaths } from "../../src/kilocode/server/httpapi/groups/kilo-gateway" import { PublicApi } from "../../src/server/routes/instance/httpapi/public" +import { SessionPaths } from "../../src/server/routes/instance/httpapi/groups/session" import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server" import { Server } from "../../src/server/server" import * as Log from "@opencode-ai/core/util/log" @@ -222,13 +224,8 @@ describe("HttpApi server", () => { expect(Server.backend()).toEqual({ backend: "effect-httpapi", reason: "env" }) }) - // kilocode_change start - skip Effect HttpApi parity tests until Kilo overlay routes are migrated. - // These tests verify every Hono route has an Effect HttpApi contract. Kilo-specific routes - // (/config/warnings, /indexing/status, /kilo/claw/*, /kilo/cloud-sessions, /experimental/worktree/diff*) - // aren't yet wired into PublicApi. The Effect HttpApi bridge is gated behind KILO_EXPERIMENTAL_HTTPAPI - // and is not enabled in any production client (VS Code extension, JetBrains, TUI, desktop all use Hono). - // Follow-up: migrate Kilo overlay routes onto the Effect HttpApi bridge. - test.skip("covers every generated OpenAPI route with Effect HttpApi contracts", async () => { + // kilocode_change start - Effect HttpApi route parity. + test("covers every generated OpenAPI route with Effect HttpApi contracts", async () => { const honoRoutes = openApiRouteKeys(await Server.openapiHono()) const effectRoutes = openApiRouteKeys(effectOpenApi()) @@ -237,13 +234,14 @@ describe("HttpApi server", () => { "GET /api/session", "GET /api/session/{sessionID}/context", "GET /api/session/{sessionID}/message", + "GET /indexing/status", "POST /api/session/{sessionID}/compact", "POST /api/session/{sessionID}/prompt", "POST /api/session/{sessionID}/wait", ]) }) - test.skip("matches generated OpenAPI route parameters", async () => { + test("matches generated OpenAPI route parameters", async () => { const hono = openApiParameters(await Server.openapiHono()) const effect = openApiParameters(effectOpenApi()) @@ -254,7 +252,7 @@ describe("HttpApi server", () => { ).toEqual([]) }) - test.skip("matches generated OpenAPI request body shape", async () => { + test("matches generated OpenAPI request body shape", async () => { const hono = openApiRequestBodies(await Server.openapiHono()) const effect = openApiRequestBodies(effectOpenApi()) @@ -270,17 +268,24 @@ describe("HttpApi server", () => { test("Kilo overlay routes are mirrored in Hono and Effect specs", async () => { const hono = new Set(openApiRouteKeys(await Server.openapiHono())) const effect = new Set(openApiRouteKeys(effectOpenApi())) - // The 22 Kilo overlay paths ported to Effect HttpApi. Both backends must serve each. + // Kilo overlay paths ported to Effect HttpApi. Both backends must serve each. const kilo = [ "POST /permission/allow-everything", "POST /enhance-prompt", "POST /commit-message", + `GET ${ExperimentalPaths.worktreeDiff}`, + `GET ${ExperimentalPaths.worktreeDiffFile}`, + `GET ${ExperimentalPaths.worktreeDiffSummary}`, "GET /network", "POST /network/{requestID}/reply", "POST /network/{requestID}/reject", + `GET ${KiloGatewayPaths.modes}`, + `POST ${KiloGatewayPaths.fim}`, + `POST ${KiloGatewayPaths.audioTranscriptions}`, "POST /remote/enable", "POST /remote/disable", "GET /remote/status", + `POST ${SessionPaths.viewed}`, "POST /telemetry/capture", "POST /telemetry/setEnabled", "GET /suggestion", diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 42f4b56963e..66bed8c41b5 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -866,7 +866,7 @@ export class Session extends HeyApiClient { directory?: string workspace?: string projectID?: string - worktrees?: "true" | "false" + worktrees?: boolean roots?: boolean | "true" | "false" start?: number cursor?: number diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 0dea7224a3b..b0ec63e53fe 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -4186,7 +4186,7 @@ export type ExperimentalSessionListData = { directory?: string workspace?: string projectID?: string - worktrees?: "true" | "false" + worktrees?: boolean roots?: boolean | "true" | "false" start?: number cursor?: number diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 53097a4dde0..7d767fe7066 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -1488,8 +1488,7 @@ "name": "worktrees", "in": "query", "schema": { - "type": "string", - "enum": ["true", "false"] + "type": "boolean" }, "required": false }, @@ -10564,7 +10563,8 @@ "name": "requestID", "in": "path", "schema": { - "type": "string" + "type": "string", + "pattern": "^que.*" }, "required": true } @@ -10637,7 +10637,8 @@ "name": "requestID", "in": "path", "schema": { - "type": "string" + "type": "string", + "pattern": "^que.*" }, "required": true }