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.
Files changed (74) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +246 -0
  3. package/bin/kaizen +15 -0
  4. package/dist/client/apple-touch-icon.png +0 -0
  5. package/dist/client/assets/index-D-ORCGrq.js +603 -0
  6. package/dist/client/assets/index-r28mcHqz.css +32 -0
  7. package/dist/client/favicon.png +0 -0
  8. package/dist/client/fonts/body-medium.woff2 +0 -0
  9. package/dist/client/fonts/body-regular-italic.woff2 +0 -0
  10. package/dist/client/fonts/body-regular.woff2 +0 -0
  11. package/dist/client/fonts/body-semibold.woff2 +0 -0
  12. package/dist/client/index.html +22 -0
  13. package/dist/client/manifest-dark.webmanifest +24 -0
  14. package/dist/client/manifest.webmanifest +24 -0
  15. package/dist/client/pwa-192.png +0 -0
  16. package/dist/client/pwa-512.png +0 -0
  17. package/dist/client/pwa-icon.svg +15 -0
  18. package/dist/client/pwa-splash.png +0 -0
  19. package/dist/client/pwa-splash.svg +15 -0
  20. package/package.json +103 -0
  21. package/src/server/acp-shared.ts +315 -0
  22. package/src/server/agent.ts +1120 -0
  23. package/src/server/attachments.ts +133 -0
  24. package/src/server/backgrounds.ts +74 -0
  25. package/src/server/cli-runtime.ts +333 -0
  26. package/src/server/cli-supervisor.ts +81 -0
  27. package/src/server/cli.ts +68 -0
  28. package/src/server/codex-app-server-protocol.ts +453 -0
  29. package/src/server/codex-app-server.ts +1350 -0
  30. package/src/server/cursor-acp.ts +819 -0
  31. package/src/server/discovery.ts +322 -0
  32. package/src/server/event-store.ts +1369 -0
  33. package/src/server/events.ts +244 -0
  34. package/src/server/external-open.ts +272 -0
  35. package/src/server/gemini-acp.ts +844 -0
  36. package/src/server/gemini-cli.ts +525 -0
  37. package/src/server/generate-title.ts +36 -0
  38. package/src/server/git-manager.ts +79 -0
  39. package/src/server/git-repository.ts +101 -0
  40. package/src/server/harness-types.ts +20 -0
  41. package/src/server/keybindings.ts +177 -0
  42. package/src/server/machine-name.ts +22 -0
  43. package/src/server/paths.ts +112 -0
  44. package/src/server/process-utils.ts +22 -0
  45. package/src/server/project-icon.ts +344 -0
  46. package/src/server/project-metadata.ts +10 -0
  47. package/src/server/provider-catalog.ts +85 -0
  48. package/src/server/provider-settings.ts +155 -0
  49. package/src/server/quick-response.ts +153 -0
  50. package/src/server/read-models.ts +275 -0
  51. package/src/server/recovery.ts +507 -0
  52. package/src/server/restart.ts +30 -0
  53. package/src/server/server.ts +244 -0
  54. package/src/server/terminal-manager.ts +350 -0
  55. package/src/server/theme-settings.ts +179 -0
  56. package/src/server/update-manager.ts +230 -0
  57. package/src/server/usage/base-provider-usage.ts +57 -0
  58. package/src/server/usage/claude-usage.ts +558 -0
  59. package/src/server/usage/codex-usage.ts +144 -0
  60. package/src/server/usage/cursor-browser.ts +120 -0
  61. package/src/server/usage/cursor-cookies.ts +390 -0
  62. package/src/server/usage/cursor-usage.ts +490 -0
  63. package/src/server/usage/gemini-usage.ts +24 -0
  64. package/src/server/usage/provider-usage.ts +61 -0
  65. package/src/server/usage/test-helpers.ts +9 -0
  66. package/src/server/usage/types.ts +54 -0
  67. package/src/server/usage/utils.ts +325 -0
  68. package/src/server/ws-router.ts +717 -0
  69. package/src/shared/branding.ts +83 -0
  70. package/src/shared/dev-ports.ts +43 -0
  71. package/src/shared/ports.ts +2 -0
  72. package/src/shared/protocol.ts +152 -0
  73. package/src/shared/tools.ts +251 -0
  74. package/src/shared/types.ts +1028 -0
@@ -0,0 +1,325 @@
1
+ import type {
2
+ AgentProvider,
3
+ ChatUsageSnapshot,
4
+ ChatUsageWarning,
5
+ ProviderUsageAvailability,
6
+ ProviderUsageEntry,
7
+ TranscriptEntry,
8
+ } from "../../shared/types"
9
+ import type { NumericUsage, ProviderRateLimitSnapshot } from "./types"
10
+
11
+ export const WARNING_THRESHOLD = 75
12
+ export const CRITICAL_THRESHOLD = 90
13
+ export const STALE_AFTER_MS = 5 * 60 * 1000
14
+
15
+ export function asRecord(value: unknown): Record<string, unknown> | null {
16
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null
17
+ return value as Record<string, unknown>
18
+ }
19
+
20
+ export function parseJsonLine(line: string): Record<string, unknown> | null {
21
+ try {
22
+ return asRecord(JSON.parse(line))
23
+ } catch {
24
+ return null
25
+ }
26
+ }
27
+
28
+ export function toNumber(value: unknown): number | null {
29
+ return typeof value === "number" && Number.isFinite(value) ? value : null
30
+ }
31
+
32
+ export function toPercent(value: number | null) {
33
+ if (value === null || !Number.isFinite(value)) return null
34
+ return Math.max(0, Math.min(100, value))
35
+ }
36
+
37
+ export function usageWarnings(args: {
38
+ contextUsedPercent: number | null
39
+ sessionLimitUsedPercent: number | null
40
+ updatedAt: number | null
41
+ }): ChatUsageWarning[] {
42
+ const warnings: ChatUsageWarning[] = []
43
+
44
+ if (args.contextUsedPercent !== null) {
45
+ if (args.contextUsedPercent >= CRITICAL_THRESHOLD) {
46
+ warnings.push("context_critical")
47
+ } else if (args.contextUsedPercent >= WARNING_THRESHOLD) {
48
+ warnings.push("context_warning")
49
+ }
50
+ }
51
+
52
+ if (args.sessionLimitUsedPercent !== null) {
53
+ if (args.sessionLimitUsedPercent >= CRITICAL_THRESHOLD) {
54
+ warnings.push("rate_critical")
55
+ } else if (args.sessionLimitUsedPercent >= WARNING_THRESHOLD) {
56
+ warnings.push("rate_warning")
57
+ }
58
+ }
59
+
60
+ if (args.updatedAt !== null && Date.now() - args.updatedAt > STALE_AFTER_MS) {
61
+ warnings.push("stale")
62
+ }
63
+
64
+ return warnings
65
+ }
66
+
67
+ export function relevantMessagesForCurrentContext(messages: TranscriptEntry[]) {
68
+ let lastContextClearedIndex = -1
69
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
70
+ if (messages[index]?.kind === "context_cleared") {
71
+ lastContextClearedIndex = index
72
+ break
73
+ }
74
+ }
75
+
76
+ return lastContextClearedIndex >= 0 ? messages.slice(lastContextClearedIndex + 1) : messages
77
+ }
78
+
79
+ function estimateTokenCountFromText(text: string | undefined | null) {
80
+ if (!text) return 0
81
+ const trimmed = text.trim()
82
+ if (!trimmed) return 0
83
+ return Math.ceil(trimmed.length / 4)
84
+ }
85
+
86
+ function estimateTokenCountFromUnknown(value: unknown) {
87
+ if (typeof value === "string") {
88
+ return estimateTokenCountFromText(value)
89
+ }
90
+
91
+ try {
92
+ return estimateTokenCountFromText(JSON.stringify(value))
93
+ } catch {
94
+ return 0
95
+ }
96
+ }
97
+
98
+ export function estimateCurrentThreadTokens(messages: TranscriptEntry[]) {
99
+ let total = 0
100
+
101
+ for (const entry of relevantMessagesForCurrentContext(messages)) {
102
+ switch (entry.kind) {
103
+ case "user_prompt":
104
+ total += estimateTokenCountFromText(entry.content)
105
+ if (entry.attachments?.length) {
106
+ total += entry.attachments.length * 256
107
+ }
108
+ break
109
+ case "assistant_text":
110
+ total += estimateTokenCountFromText(entry.text)
111
+ break
112
+ case "compact_summary":
113
+ total += estimateTokenCountFromText(entry.summary)
114
+ break
115
+ case "result":
116
+ total += estimateTokenCountFromText(entry.result)
117
+ break
118
+ case "tool_call":
119
+ total += estimateTokenCountFromText(entry.tool.toolName)
120
+ total += estimateTokenCountFromUnknown(entry.tool.input)
121
+ break
122
+ case "tool_result":
123
+ total += estimateTokenCountFromUnknown(entry.content)
124
+ break
125
+ case "status":
126
+ total += estimateTokenCountFromText(entry.status)
127
+ break
128
+ default:
129
+ break
130
+ }
131
+ }
132
+
133
+ return total
134
+ }
135
+
136
+ export function buildSnapshot(args: {
137
+ provider: AgentProvider
138
+ threadTokens: number | null
139
+ contextWindowTokens: number | null
140
+ lastTurnTokens: number | null
141
+ inputTokens: number | null
142
+ outputTokens: number | null
143
+ cachedInputTokens: number | null
144
+ reasoningOutputTokens?: number | null
145
+ sessionLimitUsedPercent: number | null
146
+ rateLimitResetAt: number | null
147
+ source: ChatUsageSnapshot["source"]
148
+ updatedAt: number | null
149
+ }): ChatUsageSnapshot | null {
150
+ const contextUsedPercent = args.threadTokens !== null && args.contextWindowTokens && args.contextWindowTokens > 0
151
+ ? toPercent((args.threadTokens / args.contextWindowTokens) * 100)
152
+ : null
153
+
154
+ const snapshot: ChatUsageSnapshot = {
155
+ provider: args.provider,
156
+ threadTokens: args.threadTokens,
157
+ contextWindowTokens: args.contextWindowTokens,
158
+ contextUsedPercent,
159
+ lastTurnTokens: args.lastTurnTokens,
160
+ inputTokens: args.inputTokens,
161
+ outputTokens: args.outputTokens,
162
+ cachedInputTokens: args.cachedInputTokens,
163
+ reasoningOutputTokens: args.reasoningOutputTokens ?? null,
164
+ sessionLimitUsedPercent: toPercent(args.sessionLimitUsedPercent),
165
+ rateLimitResetAt: args.rateLimitResetAt,
166
+ source: args.source,
167
+ updatedAt: args.updatedAt,
168
+ warnings: usageWarnings({
169
+ contextUsedPercent,
170
+ sessionLimitUsedPercent: toPercent(args.sessionLimitUsedPercent),
171
+ updatedAt: args.updatedAt,
172
+ }),
173
+ }
174
+
175
+ const hasAnyData = [
176
+ snapshot.threadTokens,
177
+ snapshot.contextWindowTokens,
178
+ snapshot.lastTurnTokens,
179
+ snapshot.inputTokens,
180
+ snapshot.outputTokens,
181
+ snapshot.cachedInputTokens,
182
+ snapshot.reasoningOutputTokens ?? null,
183
+ snapshot.sessionLimitUsedPercent,
184
+ ].some((value) => value !== null)
185
+
186
+ return hasAnyData ? snapshot : null
187
+ }
188
+
189
+ export function usageTotals(usage: Record<string, unknown>): NumericUsage & { totalTokens: number } {
190
+ const inputTokens = toNumber(usage.input_tokens) ?? 0
191
+ const outputTokens = toNumber(usage.output_tokens) ?? 0
192
+ const cachedInputTokens = (toNumber(usage.cache_read_input_tokens) ?? 0) + (toNumber(usage.cache_creation_input_tokens) ?? 0)
193
+ const reasoningOutputTokens = toNumber(usage.reasoning_output_tokens) ?? 0
194
+
195
+ return {
196
+ inputTokens,
197
+ outputTokens,
198
+ cachedInputTokens,
199
+ reasoningOutputTokens,
200
+ totalTokens: inputTokens + outputTokens + cachedInputTokens + reasoningOutputTokens,
201
+ }
202
+ }
203
+
204
+ export function mergeUsageSnapshots(
205
+ reconstructed: ChatUsageSnapshot | null,
206
+ live: ChatUsageSnapshot | null
207
+ ): ChatUsageSnapshot | null {
208
+ if (!reconstructed) return live
209
+ if (!live) return reconstructed
210
+
211
+ const merged: ChatUsageSnapshot = {
212
+ ...reconstructed,
213
+ ...live,
214
+ provider: live.provider,
215
+ threadTokens: live.threadTokens ?? reconstructed.threadTokens,
216
+ contextWindowTokens: live.contextWindowTokens ?? reconstructed.contextWindowTokens,
217
+ contextUsedPercent: live.contextUsedPercent ?? reconstructed.contextUsedPercent,
218
+ lastTurnTokens: live.lastTurnTokens ?? reconstructed.lastTurnTokens,
219
+ inputTokens: live.inputTokens ?? reconstructed.inputTokens,
220
+ outputTokens: live.outputTokens ?? reconstructed.outputTokens,
221
+ cachedInputTokens: live.cachedInputTokens ?? reconstructed.cachedInputTokens,
222
+ reasoningOutputTokens: live.reasoningOutputTokens ?? reconstructed.reasoningOutputTokens,
223
+ sessionLimitUsedPercent: live.sessionLimitUsedPercent ?? reconstructed.sessionLimitUsedPercent,
224
+ rateLimitResetAt: live.rateLimitResetAt ?? reconstructed.rateLimitResetAt,
225
+ source: live.source === "live" ? "live" : reconstructed.source,
226
+ updatedAt: live.updatedAt ?? reconstructed.updatedAt,
227
+ warnings: [],
228
+ }
229
+
230
+ merged.warnings = usageWarnings({
231
+ contextUsedPercent: merged.contextUsedPercent,
232
+ sessionLimitUsedPercent: merged.sessionLimitUsedPercent,
233
+ updatedAt: merged.updatedAt,
234
+ })
235
+
236
+ return merged
237
+ }
238
+
239
+ export function applyThreadEstimate(
240
+ snapshot: ChatUsageSnapshot | null,
241
+ messages: TranscriptEntry[]
242
+ ): ChatUsageSnapshot | null {
243
+ const estimatedThreadTokens = estimateCurrentThreadTokens(messages)
244
+ if (!snapshot) return null
245
+
246
+ const contextUsedPercent = snapshot.contextWindowTokens && snapshot.contextWindowTokens > 0
247
+ ? toPercent((estimatedThreadTokens / snapshot.contextWindowTokens) * 100)
248
+ : null
249
+
250
+ return {
251
+ ...snapshot,
252
+ threadTokens: estimatedThreadTokens,
253
+ contextUsedPercent,
254
+ warnings: usageWarnings({
255
+ contextUsedPercent,
256
+ sessionLimitUsedPercent: snapshot.sessionLimitUsedPercent,
257
+ updatedAt: snapshot.updatedAt,
258
+ }),
259
+ }
260
+ }
261
+
262
+ export function deriveAvailability(updatedAt: number | null): ProviderUsageAvailability {
263
+ if (updatedAt === null) return "available"
264
+ if (updatedAt < Date.now() - STALE_AFTER_MS) return "stale"
265
+ if (Date.now() < updatedAt) return "stale"
266
+ return "available"
267
+ }
268
+
269
+ export function snapshotToEntry(provider: AgentProvider, snapshot: ChatUsageSnapshot | null): ProviderUsageEntry {
270
+ if (!snapshot) {
271
+ return {
272
+ provider,
273
+ sessionLimitUsedPercent: null,
274
+ apiLimitUsedPercent: null,
275
+ rateLimitResetAt: null,
276
+ rateLimitResetLabel: null,
277
+ weeklyLimitUsedPercent: null,
278
+ weeklyRateLimitResetAt: null,
279
+ weeklyRateLimitResetLabel: null,
280
+ statusDetail: null,
281
+ availability: "available",
282
+ updatedAt: null,
283
+ warnings: [],
284
+ }
285
+ }
286
+
287
+ const availability = deriveAvailability(snapshot.updatedAt)
288
+ const warnings = usageWarnings({
289
+ contextUsedPercent: null,
290
+ sessionLimitUsedPercent: snapshot.sessionLimitUsedPercent,
291
+ updatedAt: snapshot.updatedAt,
292
+ })
293
+
294
+ return {
295
+ provider,
296
+ sessionLimitUsedPercent: snapshot.sessionLimitUsedPercent,
297
+ apiLimitUsedPercent: null,
298
+ rateLimitResetAt: snapshot.rateLimitResetAt,
299
+ rateLimitResetLabel: (snapshot as ProviderRateLimitSnapshot).rateLimitResetLabel ?? null,
300
+ weeklyLimitUsedPercent: (snapshot as ProviderRateLimitSnapshot).weeklyLimitUsedPercent ?? null,
301
+ weeklyRateLimitResetAt: (snapshot as ProviderRateLimitSnapshot).weeklyRateLimitResetAt ?? null,
302
+ weeklyRateLimitResetLabel: (snapshot as ProviderRateLimitSnapshot).weeklyRateLimitResetLabel ?? null,
303
+ statusDetail: null,
304
+ availability,
305
+ updatedAt: snapshot.updatedAt,
306
+ warnings,
307
+ }
308
+ }
309
+
310
+ export function unavailableEntry(provider: AgentProvider): ProviderUsageEntry {
311
+ return {
312
+ provider,
313
+ sessionLimitUsedPercent: null,
314
+ apiLimitUsedPercent: null,
315
+ rateLimitResetAt: null,
316
+ rateLimitResetLabel: null,
317
+ weeklyLimitUsedPercent: null,
318
+ weeklyRateLimitResetAt: null,
319
+ weeklyRateLimitResetLabel: null,
320
+ statusDetail: null,
321
+ availability: "unavailable",
322
+ updatedAt: null,
323
+ warnings: [],
324
+ }
325
+ }