Skip to content

Commit 252d23d

Browse files
committed
feat: replicate OpenCode adapter parity improvements to Kilo adapter
Kilo is a fork of OpenCode sharing the same SDK and SSE API. Apply the same modular extraction and feature parity improvements: - Extract monolithic kiloServerManager.ts into kilo/ module directory (types, errors, utils, eventHandlers, serverLifecycle) - Emit session.configured and session.exited lifecycle events - Forward session.diff as turn.diff.updated with unified diff conversion - Handle new SSE events: session.compacted, session.updated, vcs.branch.updated, file.edited, command.executed, message.part.removed - Enrich events: todo priority, isRetryable, permission title/metadata - Implement rollbackThread via session.revert API with message ID tracking - Fix flaky KiloAdapter stream forwarding test (same race condition fix)
1 parent 540a3ef commit 252d23d

8 files changed

Lines changed: 2311 additions & 1425 deletions

File tree

apps/server/src/kilo/errors.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import type { EventSessionError } from "./types.ts";
2+
3+
/**
4+
* Maps an Kilo error name to a runtime error class used by the
5+
* orchestration layer to categorize errors for display.
6+
*/
7+
export function sessionErrorClass(
8+
errorName: string | undefined,
9+
): "provider_error" | "transport_error" | "permission_error" | "validation_error" | "unknown" {
10+
switch (errorName) {
11+
case "ProviderAuthError":
12+
return "permission_error";
13+
case "APIError":
14+
case "ContextOverflowError":
15+
case "MessageOutputLengthError":
16+
case "StructuredOutputError":
17+
return "provider_error";
18+
case "MessageAbortedError":
19+
return "transport_error";
20+
case "UnknownError":
21+
default:
22+
return "unknown";
23+
}
24+
}
25+
26+
/**
27+
* Returns a human-readable label for the Kilo error name.
28+
*/
29+
export function sessionErrorLabel(errorName: string): string {
30+
switch (errorName) {
31+
case "ProviderAuthError":
32+
return "Authentication failed";
33+
case "UnknownError":
34+
return "Unknown error";
35+
case "MessageAbortedError":
36+
return "Message aborted";
37+
case "StructuredOutputError":
38+
return "Structured output error";
39+
case "ContextOverflowError":
40+
return "Context window exceeded";
41+
case "APIError":
42+
return "API error";
43+
case "MessageOutputLengthError":
44+
return "Response exceeded output length";
45+
default:
46+
return errorName;
47+
}
48+
}
49+
50+
/**
51+
* Returns whether an Kilo error is retryable, if the information is
52+
* available (currently only `APIError` carries `isRetryable`).
53+
*/
54+
export function sessionErrorIsRetryable(
55+
error: EventSessionError["properties"]["error"],
56+
): boolean | undefined {
57+
if (!error) {
58+
return undefined;
59+
}
60+
if (error.name === "APIError") {
61+
const data = error.data as Record<string, unknown> | undefined;
62+
return typeof data?.isRetryable === "boolean" ? data.isRetryable : undefined;
63+
}
64+
return undefined;
65+
}
66+
67+
/**
68+
* Extracts a human-readable error message from an Kilo `session.error`
69+
* event, combining the error label with any detail from the payload.
70+
*
71+
* Each Kilo error type has a specific `data` shape (from the SDK):
72+
* - ProviderAuthError: { providerID, message }
73+
* - UnknownError: { message }
74+
* - MessageAbortedError: { message }
75+
* - StructuredOutputError: { message, retries }
76+
* - ContextOverflowError: { message, responseBody? }
77+
* - APIError: { message, statusCode?, isRetryable, responseHeaders?, responseBody?, metadata? }
78+
* - MessageOutputLengthError: { [key: string]: unknown }
79+
*/
80+
export function sessionErrorMessage(
81+
error: EventSessionError["properties"]["error"],
82+
): string | undefined {
83+
if (!error) {
84+
return undefined;
85+
}
86+
87+
const data = error.data as Record<string, unknown> | undefined;
88+
const label = sessionErrorLabel(error.name);
89+
const detail = typeof data?.message === "string" ? data.message : undefined;
90+
91+
switch (error.name) {
92+
case "ProviderAuthError": {
93+
const providerID = typeof data?.providerID === "string" ? data.providerID : undefined;
94+
const prefix = providerID ? `${label} (${providerID})` : label;
95+
return detail ? `${prefix}: ${detail}` : prefix;
96+
}
97+
case "APIError": {
98+
const statusCode = typeof data?.statusCode === "number" ? data.statusCode : undefined;
99+
const prefix = statusCode ? `${label} ${statusCode}` : label;
100+
return detail ? `${prefix}: ${detail}` : prefix;
101+
}
102+
case "StructuredOutputError": {
103+
const retries = typeof data?.retries === "number" ? data.retries : undefined;
104+
const suffix = retries != null ? ` (after ${retries} retries)` : "";
105+
return detail ? `${label}: ${detail}${suffix}` : `${label}${suffix}`;
106+
}
107+
default: {
108+
return detail ? `${label}: ${detail}` : label;
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)