fix(agent-manager): scope branch naming to sessions

This commit is contained in:
marius-kilocode
2026-06-29 11:51:17 +02:00
parent 536f278f86
commit 2ffbfa0348
9 changed files with 71 additions and 22 deletions
@@ -10,10 +10,11 @@ import { described } from "@/server/routes/instance/httpapi/groups/metadata"
import { Schema } from "effect"
import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
const root = "/branch-name"
export const BranchNamePaths = {
generate: "/session/:sessionID/branch-name",
} as const
export const BranchNamePayload = Schema.Struct({
sessionID: SessionID,
prompt: Schema.String,
providerID: Schema.optional(ProviderID),
modelID: Schema.optional(ModelID),
@@ -27,7 +28,8 @@ export const BranchNameApi = HttpApi.make("branch-name")
.add(
HttpApiGroup.make("branch-name")
.add(
HttpApiEndpoint.post("generate", root, {
HttpApiEndpoint.post("generate", BranchNamePaths.generate, {
params: { sessionID: SessionID },
query: WorkspaceRoutingQuery,
payload: BranchNamePayload,
success: described(BranchNameResponse, "Generated branch name or null when the task is not clear yet"),
@@ -1,6 +1,7 @@
import * as Log from "@opencode-ai/core/util/log"
import { generate, messages } from "@/kilocode/branch-name"
import { Session } from "@/session/session"
import { SessionID } from "@/session/schema"
import { InstanceHttpApi } from "@/server/routes/instance/httpapi/api"
import { Cause, Effect, Option } from "effect"
import { HttpApiBuilder } from "effect/unstable/httpapi"
@@ -12,11 +13,14 @@ export const branchNameHandlers = HttpApiBuilder.group(InstanceHttpApi, "branch-
Effect.gen(function* () {
const session = yield* Session.Service
const handle = Effect.fn("BranchNameHttpApi.generate")(function* (ctx: { payload: typeof BranchNamePayload.Type }) {
const handle = Effect.fn("BranchNameHttpApi.generate")(function* (ctx: {
params: { sessionID: SessionID }
payload: typeof BranchNamePayload.Type
}) {
const branch = yield* Effect.gen(function* () {
const history = yield* session.messages({ sessionID: ctx.payload.sessionID })
const history = yield* session.messages({ sessionID: ctx.params.sessionID })
return yield* generate({
sessionID: ctx.payload.sessionID,
sessionID: ctx.params.sessionID,
messages: messages(history, ctx.payload.prompt),
providerID: ctx.payload.providerID,
modelID: ctx.payload.modelID,
@@ -79,6 +79,11 @@ export function matchLegacyKiloOpenApi(input: Record<string, unknown>) {
const pty = spec.components?.schemas?.Pty?.properties
if (pty?.sessionID) pty.sessionID = nullable(pty.sessionID)
const out = spec.paths?.["/session/{sessionID}/branch-name"]?.post?.responses?.["200"]?.content?.[
"application/json"
]?.schema?.properties
if (out?.branch) out.branch = nullable(out.branch)
const update = spec.paths?.["/pty/{ptyID}"]?.put?.requestBody?.content?.["application/json"]?.schema
const name = update?.$ref?.replace("#/components/schemas/", "")
const fields = name ? spec.components?.schemas?.[name]?.properties : update?.properties
@@ -64,4 +64,10 @@ describe("branch name generation helpers", () => {
const history = [user("one"), user("two"), user("three"), user("four"), user("five")]
expect(messages(history, "six")).toEqual(["three", "four", "five", "six"])
})
test("truncates large messages before generation", () => {
const big = "x".repeat(2_000)
expect(messages([user(big)], big)).toEqual(["x".repeat(1_000)])
})
})
@@ -257,6 +257,14 @@ export const kiloScenarios: Scenario[] = [
.post("/commit-message", "commitMessage.generate")
.at((ctx) => ({ path: "/commit-message", headers: ctx.headers(), body: {} }))
.status(400),
http.protected
.post("/session/{sessionID}/branch-name", "branchName.generate")
.at((ctx) => ({
path: route("/session/{sessionID}/branch-name", { sessionID: "ses_httpapi_missing" }),
headers: ctx.headers(),
body: {},
}))
.status(400),
http.protected
.post("/enhance-prompt", "enhancePrompt.enhance")
.at((ctx) => ({ path: "/enhance-prompt", headers: ctx.headers(), body: { text: "" } }))
@@ -3,6 +3,7 @@ import { Result, Schema as EffectSchema } from "effect"
import { OpenApi } from "effect/unstable/httpapi"
import { AgentBuilderPaths } from "../../../src/kilocode/server/httpapi/groups/agent-builder"
import { BackgroundProcessPaths } from "../../../src/kilocode/server/httpapi/groups/background-process"
import { BranchNamePaths } from "../../../src/kilocode/server/httpapi/groups/branch-name"
import { ConfigConsolePaths } from "../../../src/kilocode/server/httpapi/groups/config-console"
import { IndexingPaths, KiloEmbeddingModel } from "../../../src/kilocode/server/httpapi/groups/indexing"
import { KiloGatewayPaths } from "../../../src/kilocode/server/httpapi/groups/kilo-gateway"
@@ -138,6 +139,7 @@ describe("Kilo PublicApi OpenAPI contract", () => {
{ method: "get", path: ConfigConsolePaths.tuiKeybinds },
{ method: "patch", path: ConfigConsolePaths.tuiConfig },
{ method: "get", path: KilocodePaths.sessionModelUsage },
{ method: "post", path: BranchNamePaths.generate },
] satisfies Array<{ method: Method; path: string }>
for (const route of routes) {
@@ -157,6 +159,15 @@ describe("Kilo PublicApi OpenAPI contract", () => {
expect(props?.organizationId).toEqual({ anyOf: [{ type: "string" }, { type: "null" }] })
})
test("keeps branch-name responses nullable", () => {
const spec = OpenApi.fromApi(PublicApi)
const path = BranchNamePaths.generate.replace(/:([A-Za-z0-9_]+)/g, "{$1}")
const body = spec.paths[path]?.post?.responses?.["200"] as Body | undefined
const branch = body?.content?.["application/json"]?.schema?.properties?.branch
expect(branch).toEqual({ anyOf: [{ type: "string" }, { type: "null" }] })
})
test("keeps Kilo gateway responses nullable", () => {
const spec = OpenApi.fromApi(PublicApi)
const response = (path: string) => {
+4 -4
View File
@@ -6244,10 +6244,10 @@ export class BranchName extends HeyApiClient {
* Generate a task-focused branch name from the current conversation.
*/
public generate<ThrowOnError extends boolean = false>(
parameters?: {
parameters: {
sessionID: string
directory?: string
workspace?: string
sessionID?: string
prompt?: string
providerID?: string
modelID?: string
@@ -6259,9 +6259,9 @@ export class BranchName extends HeyApiClient {
[
{
args: [
{ in: "path", key: "sessionID" },
{ in: "query", key: "directory" },
{ in: "query", key: "workspace" },
{ in: "body", key: "sessionID" },
{ in: "body", key: "prompt" },
{ in: "body", key: "providerID" },
{ in: "body", key: "modelID" },
@@ -6270,7 +6270,7 @@ export class BranchName extends HeyApiClient {
],
)
return (options?.client ?? this.client).post<BranchNameGenerateResponses, BranchNameGenerateErrors, ThrowOnError>({
url: "/branch-name",
url: "/session/{sessionID}/branch-name",
...options,
...params,
headers: {
+5 -4
View File
@@ -9632,17 +9632,18 @@ export type BackgroundProcessStopSessionResponse =
export type BranchNameGenerateData = {
body?: {
sessionID: string
prompt: string
providerID?: string
modelID?: string
}
path?: never
path: {
sessionID: string
}
query?: {
directory?: string
workspace?: string
}
url: "/branch-name"
url: "/session/{sessionID}/branch-name"
}
export type BranchNameGenerateErrors = {
@@ -9659,7 +9660,7 @@ export type BranchNameGenerateResponses = {
* Generated branch name or null when the task is not clear yet
*/
200: {
branch: string
branch: string | null
}
}
+20 -8
View File
@@ -11800,11 +11800,20 @@
]
}
},
"/branch-name": {
"/session/{sessionID}/branch-name": {
"post": {
"tags": ["branch-name"],
"operationId": "branchName.generate",
"parameters": [
{
"name": "sessionID",
"in": "path",
"schema": {
"type": "string",
"pattern": "^ses.*"
},
"required": true
},
{
"name": "directory",
"in": "query",
@@ -11831,7 +11840,14 @@
"type": "object",
"properties": {
"branch": {
"type": "string"
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": ["branch"],
@@ -11860,10 +11876,6 @@
"schema": {
"type": "object",
"properties": {
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"prompt": {
"type": "string"
},
@@ -11874,7 +11886,7 @@
"type": "string"
}
},
"required": ["sessionID", "prompt"],
"required": ["prompt"],
"additionalProperties": false
}
}
@@ -11883,7 +11895,7 @@
"x-codeSamples": [
{
"lang": "js",
"source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.branchName.generate({\n ...\n})"
"source": "import { createKiloClient } from \"@kilocode/sdk\"\n\nconst client = createKiloClient()\nawait client.branchName.generate({\n ...\n})"
}
]
}