kaizenai 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +246 -0
- package/bin/kaizen +15 -0
- package/dist/client/apple-touch-icon.png +0 -0
- package/dist/client/assets/index-D-ORCGrq.js +603 -0
- package/dist/client/assets/index-r28mcHqz.css +32 -0
- package/dist/client/favicon.png +0 -0
- package/dist/client/fonts/body-medium.woff2 +0 -0
- package/dist/client/fonts/body-regular-italic.woff2 +0 -0
- package/dist/client/fonts/body-regular.woff2 +0 -0
- package/dist/client/fonts/body-semibold.woff2 +0 -0
- package/dist/client/index.html +22 -0
- package/dist/client/manifest-dark.webmanifest +24 -0
- package/dist/client/manifest.webmanifest +24 -0
- package/dist/client/pwa-192.png +0 -0
- package/dist/client/pwa-512.png +0 -0
- package/dist/client/pwa-icon.svg +15 -0
- package/dist/client/pwa-splash.png +0 -0
- package/dist/client/pwa-splash.svg +15 -0
- package/package.json +103 -0
- package/src/server/acp-shared.ts +315 -0
- package/src/server/agent.ts +1120 -0
- package/src/server/attachments.ts +133 -0
- package/src/server/backgrounds.ts +74 -0
- package/src/server/cli-runtime.ts +333 -0
- package/src/server/cli-supervisor.ts +81 -0
- package/src/server/cli.ts +68 -0
- package/src/server/codex-app-server-protocol.ts +453 -0
- package/src/server/codex-app-server.ts +1350 -0
- package/src/server/cursor-acp.ts +819 -0
- package/src/server/discovery.ts +322 -0
- package/src/server/event-store.ts +1369 -0
- package/src/server/events.ts +244 -0
- package/src/server/external-open.ts +272 -0
- package/src/server/gemini-acp.ts +844 -0
- package/src/server/gemini-cli.ts +525 -0
- package/src/server/generate-title.ts +36 -0
- package/src/server/git-manager.ts +79 -0
- package/src/server/git-repository.ts +101 -0
- package/src/server/harness-types.ts +20 -0
- package/src/server/keybindings.ts +177 -0
- package/src/server/machine-name.ts +22 -0
- package/src/server/paths.ts +112 -0
- package/src/server/process-utils.ts +22 -0
- package/src/server/project-icon.ts +344 -0
- package/src/server/project-metadata.ts +10 -0
- package/src/server/provider-catalog.ts +85 -0
- package/src/server/provider-settings.ts +155 -0
- package/src/server/quick-response.ts +153 -0
- package/src/server/read-models.ts +275 -0
- package/src/server/recovery.ts +507 -0
- package/src/server/restart.ts +30 -0
- package/src/server/server.ts +244 -0
- package/src/server/terminal-manager.ts +350 -0
- package/src/server/theme-settings.ts +179 -0
- package/src/server/update-manager.ts +230 -0
- package/src/server/usage/base-provider-usage.ts +57 -0
- package/src/server/usage/claude-usage.ts +558 -0
- package/src/server/usage/codex-usage.ts +144 -0
- package/src/server/usage/cursor-browser.ts +120 -0
- package/src/server/usage/cursor-cookies.ts +390 -0
- package/src/server/usage/cursor-usage.ts +490 -0
- package/src/server/usage/gemini-usage.ts +24 -0
- package/src/server/usage/provider-usage.ts +61 -0
- package/src/server/usage/test-helpers.ts +9 -0
- package/src/server/usage/types.ts +54 -0
- package/src/server/usage/utils.ts +325 -0
- package/src/server/ws-router.ts +717 -0
- package/src/shared/branding.ts +83 -0
- package/src/shared/dev-ports.ts +43 -0
- package/src/shared/ports.ts +2 -0
- package/src/shared/protocol.ts +152 -0
- package/src/shared/tools.ts +251 -0
- package/src/shared/types.ts +1028 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto"
|
|
2
|
+
import type { NormalizedToolCall, TranscriptEntry } from "../shared/types"
|
|
3
|
+
import { normalizeToolCall } from "../shared/tools"
|
|
4
|
+
|
|
5
|
+
export type JsonRpcId = string | number
|
|
6
|
+
|
|
7
|
+
export interface JsonRpcRequest {
|
|
8
|
+
jsonrpc: "2.0"
|
|
9
|
+
id: JsonRpcId
|
|
10
|
+
method: string
|
|
11
|
+
params?: unknown
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface JsonRpcNotification {
|
|
15
|
+
jsonrpc: "2.0"
|
|
16
|
+
method: string
|
|
17
|
+
params?: unknown
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface JsonRpcResponse {
|
|
21
|
+
jsonrpc: "2.0"
|
|
22
|
+
id: JsonRpcId
|
|
23
|
+
result?: unknown
|
|
24
|
+
error?: {
|
|
25
|
+
code: number
|
|
26
|
+
message: string
|
|
27
|
+
data?: unknown
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type JsonRpcMessage = JsonRpcRequest | JsonRpcNotification | JsonRpcResponse
|
|
32
|
+
|
|
33
|
+
export interface PendingRequest<TResult> {
|
|
34
|
+
method: string
|
|
35
|
+
resolve: (value: TResult) => void
|
|
36
|
+
reject: (error: Error) => void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type AcpToolCallUpdate = {
|
|
40
|
+
toolCallId: string
|
|
41
|
+
title?: string | null
|
|
42
|
+
kind?: string | null
|
|
43
|
+
locations?: Array<{ path?: string | null }> | null
|
|
44
|
+
content?: Array<Record<string, unknown>> | null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function asRecord(value: unknown): Record<string, unknown> | null {
|
|
48
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null
|
|
49
|
+
return value as Record<string, unknown>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function timestamped<T extends Omit<TranscriptEntry, "_id" | "createdAt">>(
|
|
53
|
+
entry: T,
|
|
54
|
+
createdAt = Date.now()
|
|
55
|
+
): TranscriptEntry {
|
|
56
|
+
return {
|
|
57
|
+
_id: randomUUID(),
|
|
58
|
+
createdAt,
|
|
59
|
+
...entry,
|
|
60
|
+
} as TranscriptEntry
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function errorMessage(error: unknown) {
|
|
64
|
+
if (error instanceof Error) return error.message
|
|
65
|
+
return String(error)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function stringifyJson(value: unknown) {
|
|
69
|
+
if (typeof value === "string") return value
|
|
70
|
+
if (value == null) return ""
|
|
71
|
+
try {
|
|
72
|
+
return JSON.stringify(value, null, 2)
|
|
73
|
+
} catch {
|
|
74
|
+
return String(value)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function parseJsonLine(line: string): JsonRpcMessage | null {
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(line) as JsonRpcMessage
|
|
81
|
+
if (parsed && typeof parsed === "object" && parsed.jsonrpc === "2.0") {
|
|
82
|
+
return parsed
|
|
83
|
+
}
|
|
84
|
+
return null
|
|
85
|
+
} catch {
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function isJsonRpcResponse(message: JsonRpcMessage): message is JsonRpcResponse {
|
|
91
|
+
return "id" in message && ("result" in message || "error" in message)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function inferToolNameFromUpdate(toolCall: {
|
|
95
|
+
title?: string | null
|
|
96
|
+
kind?: string | null
|
|
97
|
+
locations?: Array<{ path?: string | null }> | null
|
|
98
|
+
content?: Array<Record<string, unknown>> | null
|
|
99
|
+
}) {
|
|
100
|
+
const title = (toolCall.title ?? "").toLowerCase()
|
|
101
|
+
if (title.startsWith("asking user:")) return "AskUserQuestion"
|
|
102
|
+
if (title.startsWith("requesting plan approval for:")) return "ExitPlanMode"
|
|
103
|
+
if (title === "create plan" || title.startsWith("create plan:")) return "ExitPlanMode"
|
|
104
|
+
if (title === "plan" || title.startsWith("plan:")) return "ExitPlanMode"
|
|
105
|
+
if (title === "update todos" || title.startsWith("update todos:")) return "TodoWrite"
|
|
106
|
+
|
|
107
|
+
switch (toolCall.kind) {
|
|
108
|
+
case "read":
|
|
109
|
+
return "Read"
|
|
110
|
+
case "edit": {
|
|
111
|
+
const firstDiff = toolCall.content?.find((entry) => entry.type === "diff")
|
|
112
|
+
const oldText = typeof firstDiff?.oldText === "string" ? firstDiff.oldText : null
|
|
113
|
+
const newText = typeof firstDiff?.newText === "string" ? firstDiff.newText : null
|
|
114
|
+
if (!oldText && newText) return "Write"
|
|
115
|
+
return "Edit"
|
|
116
|
+
}
|
|
117
|
+
case "delete":
|
|
118
|
+
return "Edit"
|
|
119
|
+
case "move":
|
|
120
|
+
return "Edit"
|
|
121
|
+
case "search":
|
|
122
|
+
return title.includes("web") ? "WebSearch" : "Grep"
|
|
123
|
+
case "execute":
|
|
124
|
+
return "Bash"
|
|
125
|
+
case "fetch":
|
|
126
|
+
return title.includes("web") ? "WebFetch" : "Read"
|
|
127
|
+
case "switch_mode":
|
|
128
|
+
return "ExitPlanMode"
|
|
129
|
+
default:
|
|
130
|
+
if (toolCall.locations?.[0]?.path) return "Read"
|
|
131
|
+
return "Tool"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function inferToolInput(toolName: string, toolCall: {
|
|
136
|
+
title?: string | null
|
|
137
|
+
locations?: Array<{ path?: string | null }> | null
|
|
138
|
+
content?: Array<Record<string, unknown>> | null
|
|
139
|
+
}) {
|
|
140
|
+
const firstLocationPath = typeof toolCall.locations?.[0]?.path === "string"
|
|
141
|
+
? toolCall.locations[0].path
|
|
142
|
+
: undefined
|
|
143
|
+
|
|
144
|
+
if (toolName === "AskUserQuestion") {
|
|
145
|
+
const questionText = (toolCall.title ?? "Agent requested user input").replace(/^Asking user:\s*/i, "").trim()
|
|
146
|
+
return {
|
|
147
|
+
questions: [{ question: questionText || "Agent requested user input." }],
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (toolName === "ExitPlanMode") {
|
|
152
|
+
const planPath = (toolCall.title ?? "")
|
|
153
|
+
.replace(/^Requesting plan approval for:\s*/i, "")
|
|
154
|
+
.replace(/^Create plan:?\s*/i, "")
|
|
155
|
+
.replace(/^Plan:?\s*/i, "")
|
|
156
|
+
.trim()
|
|
157
|
+
return {
|
|
158
|
+
summary: planPath || undefined,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (toolName === "TodoWrite") {
|
|
163
|
+
return {
|
|
164
|
+
todos: [],
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (toolName === "Read") {
|
|
169
|
+
return { file_path: firstLocationPath ?? "" }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (toolName === "Write" || toolName === "Edit") {
|
|
173
|
+
const firstDiff = toolCall.content?.find((entry) => entry.type === "diff")
|
|
174
|
+
const diffPath = typeof firstDiff?.path === "string" ? firstDiff.path : firstLocationPath ?? ""
|
|
175
|
+
return {
|
|
176
|
+
file_path: diffPath,
|
|
177
|
+
old_string: typeof firstDiff?.oldText === "string" ? firstDiff.oldText : "",
|
|
178
|
+
new_string: typeof firstDiff?.newText === "string" ? firstDiff.newText : "",
|
|
179
|
+
content: typeof firstDiff?.newText === "string" ? firstDiff.newText : "",
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (toolName === "Bash") {
|
|
184
|
+
return {
|
|
185
|
+
command: typeof toolCall.title === "string" ? toolCall.title : "",
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (toolName === "WebSearch") {
|
|
190
|
+
return {
|
|
191
|
+
query: typeof toolCall.title === "string" ? toolCall.title : "",
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (toolName === "WebFetch") {
|
|
196
|
+
return {
|
|
197
|
+
file_path: firstLocationPath ?? "",
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
payload: {
|
|
203
|
+
title: toolCall.title ?? undefined,
|
|
204
|
+
locations: toolCall.locations ?? [],
|
|
205
|
+
content: toolCall.content ?? [],
|
|
206
|
+
},
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function normalizeAcpToolCall(toolCall: AcpToolCallUpdate): NormalizedToolCall {
|
|
211
|
+
const toolName = inferToolNameFromUpdate(toolCall)
|
|
212
|
+
const input = inferToolInput(toolName, toolCall)
|
|
213
|
+
return normalizeToolCall({
|
|
214
|
+
toolName,
|
|
215
|
+
toolId: toolCall.toolCallId,
|
|
216
|
+
input,
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function populateExitPlanFromAssistantText(
|
|
221
|
+
tool: NormalizedToolCall,
|
|
222
|
+
assistantText: string | null | undefined
|
|
223
|
+
): NormalizedToolCall {
|
|
224
|
+
if (tool.toolKind !== "exit_plan_mode" || tool.input.plan) {
|
|
225
|
+
return tool
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const plan = assistantText?.trim()
|
|
229
|
+
if (!plan) return tool
|
|
230
|
+
|
|
231
|
+
return normalizeToolCall({
|
|
232
|
+
toolName: "ExitPlanMode",
|
|
233
|
+
toolId: tool.toolId,
|
|
234
|
+
input: {
|
|
235
|
+
plan,
|
|
236
|
+
summary: tool.input.summary,
|
|
237
|
+
},
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function stringifyToolCallContent(content: Array<Record<string, unknown>> | null | undefined) {
|
|
242
|
+
if (!content?.length) return ""
|
|
243
|
+
return content.map((entry) => {
|
|
244
|
+
if (entry.type === "content") {
|
|
245
|
+
const inner = asRecord(entry.content)
|
|
246
|
+
if (typeof inner?.text === "string") return inner.text
|
|
247
|
+
}
|
|
248
|
+
if (entry.type === "diff") {
|
|
249
|
+
const path = typeof entry.path === "string" ? entry.path : "unknown"
|
|
250
|
+
return `Updated ${path}`
|
|
251
|
+
}
|
|
252
|
+
return stringifyJson(entry)
|
|
253
|
+
}).filter(Boolean).join("\n\n")
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function createResultEntry(result: { stopReason?: unknown }): TranscriptEntry {
|
|
257
|
+
const stopReason = typeof result.stopReason === "string" ? result.stopReason : "end_turn"
|
|
258
|
+
if (stopReason === "cancelled") {
|
|
259
|
+
return timestamped({
|
|
260
|
+
kind: "result",
|
|
261
|
+
subtype: "cancelled",
|
|
262
|
+
isError: false,
|
|
263
|
+
durationMs: 0,
|
|
264
|
+
result: "",
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return timestamped({
|
|
269
|
+
kind: "result",
|
|
270
|
+
subtype: "success",
|
|
271
|
+
isError: false,
|
|
272
|
+
durationMs: 0,
|
|
273
|
+
result: "",
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export class AsyncQueue<T> implements AsyncIterable<T> {
|
|
278
|
+
private values: T[] = []
|
|
279
|
+
private resolvers: Array<(value: IteratorResult<T>) => void> = []
|
|
280
|
+
private done = false
|
|
281
|
+
|
|
282
|
+
push(value: T) {
|
|
283
|
+
if (this.done) return
|
|
284
|
+
const resolver = this.resolvers.shift()
|
|
285
|
+
if (resolver) {
|
|
286
|
+
resolver({ value, done: false })
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
this.values.push(value)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
finish() {
|
|
293
|
+
if (this.done) return
|
|
294
|
+
this.done = true
|
|
295
|
+
while (this.resolvers.length > 0) {
|
|
296
|
+
this.resolvers.shift()?.({ value: undefined as T, done: true })
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
[Symbol.asyncIterator](): AsyncIterator<T> {
|
|
301
|
+
return {
|
|
302
|
+
next: () => {
|
|
303
|
+
if (this.values.length > 0) {
|
|
304
|
+
return Promise.resolve({ value: this.values.shift() as T, done: false })
|
|
305
|
+
}
|
|
306
|
+
if (this.done) {
|
|
307
|
+
return Promise.resolve({ value: undefined as T, done: true })
|
|
308
|
+
}
|
|
309
|
+
return new Promise<IteratorResult<T>>((resolve) => {
|
|
310
|
+
this.resolvers.push(resolve)
|
|
311
|
+
})
|
|
312
|
+
},
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|