@jait/gateway 0.1.411 → 0.1.414

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 (110) hide show
  1. package/dist/cli/doctor.d.ts.map +1 -1
  2. package/dist/cli/doctor.js +0 -3
  3. package/dist/cli/doctor.js.map +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +4 -10
  6. package/dist/index.js.map +1 -1
  7. package/dist/providers/acp-provider.d.ts +41 -0
  8. package/dist/providers/acp-provider.d.ts.map +1 -0
  9. package/dist/providers/acp-provider.js +507 -0
  10. package/dist/providers/acp-provider.js.map +1 -0
  11. package/dist/providers/codex-event-mapper.d.ts +2 -3
  12. package/dist/providers/codex-event-mapper.d.ts.map +1 -1
  13. package/dist/providers/codex-event-mapper.js +2 -3
  14. package/dist/providers/codex-event-mapper.js.map +1 -1
  15. package/dist/providers/contracts.d.ts +3 -8
  16. package/dist/providers/contracts.d.ts.map +1 -1
  17. package/dist/providers/contracts.js +2 -7
  18. package/dist/providers/contracts.js.map +1 -1
  19. package/dist/providers/index.d.ts +1 -2
  20. package/dist/providers/index.d.ts.map +1 -1
  21. package/dist/providers/index.js +1 -2
  22. package/dist/providers/index.js.map +1 -1
  23. package/dist/providers/registry.d.ts +1 -5
  24. package/dist/providers/registry.d.ts.map +1 -1
  25. package/dist/providers/registry.js +1 -5
  26. package/dist/providers/registry.js.map +1 -1
  27. package/dist/routes/threads.d.ts.map +1 -1
  28. package/dist/routes/threads.js +6 -32
  29. package/dist/routes/threads.js.map +1 -1
  30. package/dist/tools/thread-tools.d.ts.map +1 -1
  31. package/dist/tools/thread-tools.js +3 -13
  32. package/dist/tools/thread-tools.js.map +1 -1
  33. package/dist/voice-assistant/tools.js +4 -4
  34. package/dist/voice-assistant/tools.js.map +1 -1
  35. package/package.json +4 -2
  36. package/web-dist/assets/{_basePickBy-DTINQlKL.js → _basePickBy-BY3j0pLu.js} +1 -1
  37. package/web-dist/assets/{_baseUniq-C-MG6h2-.js → _baseUniq-BBKXdVtW.js} +1 -1
  38. package/web-dist/assets/{arc-CPWhVSIy.js → arc-DNJNlKJa.js} +1 -1
  39. package/web-dist/assets/{architectureDiagram-2XIMDMQ5-DiaJVGbw.js → architectureDiagram-2XIMDMQ5-DgvlzCLX.js} +1 -1
  40. package/web-dist/assets/{blockDiagram-WCTKOSBZ-BYYoraer.js → blockDiagram-WCTKOSBZ-LIk_pKGv.js} +1 -1
  41. package/web-dist/assets/{c4Diagram-IC4MRINW-Wj5akwx6.js → c4Diagram-IC4MRINW-Dd9l5l1P.js} +1 -1
  42. package/web-dist/assets/channel-CNnC30Rt.js +1 -0
  43. package/web-dist/assets/{chunk-4BX2VUAB-rGkhD2e4.js → chunk-4BX2VUAB-CxecMpnr.js} +1 -1
  44. package/web-dist/assets/{chunk-55IACEB6-BMbM4kBP.js → chunk-55IACEB6-iXe2sYvx.js} +1 -1
  45. package/web-dist/assets/{chunk-FMBD7UC4-CVCvmdxw.js → chunk-FMBD7UC4-SRiAxXsq.js} +1 -1
  46. package/web-dist/assets/{chunk-JSJVCQXG-D8Bh55Yo.js → chunk-JSJVCQXG-DhsSNEw_.js} +1 -1
  47. package/web-dist/assets/{chunk-KX2RTZJC-BijS5dHA.js → chunk-KX2RTZJC-CYISO9K3.js} +1 -1
  48. package/web-dist/assets/{chunk-NQ4KR5QH-BvPmzt7h.js → chunk-NQ4KR5QH-D9PfPLCM.js} +1 -1
  49. package/web-dist/assets/{chunk-QZHKN3VN-D3dtWzpN.js → chunk-QZHKN3VN-BeUfD_Us.js} +1 -1
  50. package/web-dist/assets/{chunk-WL4C6EOR-00I2m_Li.js → chunk-WL4C6EOR-CChE5ckt.js} +1 -1
  51. package/web-dist/assets/classDiagram-VBA2DB6C-CZRuAChC.js +1 -0
  52. package/web-dist/assets/classDiagram-v2-RAHNMMFH-CZRuAChC.js +1 -0
  53. package/web-dist/assets/clone-CfXgw5ZL.js +1 -0
  54. package/web-dist/assets/{cose-bilkent-S5V4N54A-Xa_L_SDR.js → cose-bilkent-S5V4N54A-Dx5SmdOo.js} +1 -1
  55. package/web-dist/assets/{dagre-KLK3FWXG-aKclx8fy.js → dagre-KLK3FWXG-DEwOQO7w.js} +1 -1
  56. package/web-dist/assets/{diagram-E7M64L7V-BajhtSmq.js → diagram-E7M64L7V-Do6ZcaTW.js} +1 -1
  57. package/web-dist/assets/{diagram-IFDJBPK2-CE87U4_E.js → diagram-IFDJBPK2-DorJcAX1.js} +1 -1
  58. package/web-dist/assets/{diagram-P4PSJMXO-BNMJ4h6h.js → diagram-P4PSJMXO-BC1D-fCW.js} +1 -1
  59. package/web-dist/assets/{erDiagram-INFDFZHY-DuvtE5nZ.js → erDiagram-INFDFZHY-CP0mSVaQ.js} +1 -1
  60. package/web-dist/assets/{flowDiagram-PKNHOUZH-BQHnBNw1.js → flowDiagram-PKNHOUZH-DizSRtGj.js} +1 -1
  61. package/web-dist/assets/{ganttDiagram-A5KZAMGK-6iCM2cum.js → ganttDiagram-A5KZAMGK-DCG3RnO6.js} +1 -1
  62. package/web-dist/assets/{gitGraphDiagram-K3NZZRJ6-BK3XRtfb.js → gitGraphDiagram-K3NZZRJ6-FaXWKyXz.js} +1 -1
  63. package/web-dist/assets/{graph-BwziVd0f.js → graph--TtVwd7t.js} +1 -1
  64. package/web-dist/assets/{index-DyvkTnNe.js → index-B2q-RZL4.js} +1 -1
  65. package/web-dist/assets/{index-Bfyg7a6D.js → index-CCQbeCpx.js} +1 -1
  66. package/web-dist/assets/{index-DmbDJ0zR.js → index-DLM62Alw.js} +356 -357
  67. package/web-dist/assets/{infoDiagram-LFFYTUFH-_lcH8o9D.js → infoDiagram-LFFYTUFH-Dv0alkoh.js} +1 -1
  68. package/web-dist/assets/{ishikawaDiagram-PHBUUO56-P9kGf_8a.js → ishikawaDiagram-PHBUUO56-C6dqReRm.js} +1 -1
  69. package/web-dist/assets/{journeyDiagram-4ABVD52K-1JBHr5Eh.js → journeyDiagram-4ABVD52K-CqGp9as4.js} +1 -1
  70. package/web-dist/assets/{kanban-definition-K7BYSVSG-BjbF9Ygm.js → kanban-definition-K7BYSVSG-DcY31-Uk.js} +1 -1
  71. package/web-dist/assets/{layout-BlDoqALI.js → layout-DeDqdHtL.js} +1 -1
  72. package/web-dist/assets/{linear-DHWXphYy.js → linear-Dndn1mXM.js} +1 -1
  73. package/web-dist/assets/{mindmap-definition-YRQLILUH-BGWYtXzC.js → mindmap-definition-YRQLILUH-pUyhhY_g.js} +1 -1
  74. package/web-dist/assets/{pieDiagram-SKSYHLDU-DP604Leh.js → pieDiagram-SKSYHLDU-0ld0kqTd.js} +1 -1
  75. package/web-dist/assets/{quadrantDiagram-337W2JSQ-C3_iyXnp.js → quadrantDiagram-337W2JSQ-CyGZ_o1t.js} +1 -1
  76. package/web-dist/assets/{requirementDiagram-Z7DCOOCP-q8KQZJh1.js → requirementDiagram-Z7DCOOCP-nRggwrRJ.js} +1 -1
  77. package/web-dist/assets/{sankeyDiagram-WA2Y5GQK-w_PH_gg2.js → sankeyDiagram-WA2Y5GQK-CuWIu-UB.js} +1 -1
  78. package/web-dist/assets/{sequenceDiagram-2WXFIKYE-DJ3Jm_vH.js → sequenceDiagram-2WXFIKYE-CF1317GT.js} +1 -1
  79. package/web-dist/assets/{stateDiagram-RAJIS63D-CisLxKjV.js → stateDiagram-RAJIS63D-DfI7H3Ui.js} +1 -1
  80. package/web-dist/assets/stateDiagram-v2-FVOUBMTO-BIB1nAmv.js +1 -0
  81. package/web-dist/assets/{timeline-definition-YZTLITO2-DVIz6ocS.js → timeline-definition-YZTLITO2-Dh7f4001.js} +1 -1
  82. package/web-dist/assets/{treemap-KZPCXAKY-DZpDLVT6.js → treemap-KZPCXAKY-DbkPp1H7.js} +1 -1
  83. package/web-dist/assets/{vennDiagram-LZ73GAT5-DSGr8xH_.js → vennDiagram-LZ73GAT5-BiY_0fvU.js} +1 -1
  84. package/web-dist/assets/{xychartDiagram-JWTSCODW-iheT-zlS.js → xychartDiagram-JWTSCODW-DEU3Uvdf.js} +1 -1
  85. package/web-dist/index.html +1 -1
  86. package/dist/providers/claude-code-provider.d.ts +0 -69
  87. package/dist/providers/claude-code-provider.d.ts.map +0 -1
  88. package/dist/providers/claude-code-provider.js +0 -801
  89. package/dist/providers/claude-code-provider.js.map +0 -1
  90. package/dist/providers/codex-provider.d.ts +0 -63
  91. package/dist/providers/codex-provider.d.ts.map +0 -1
  92. package/dist/providers/codex-provider.js +0 -660
  93. package/dist/providers/codex-provider.js.map +0 -1
  94. package/dist/providers/copilot-provider.d.ts +0 -45
  95. package/dist/providers/copilot-provider.d.ts.map +0 -1
  96. package/dist/providers/copilot-provider.js +0 -591
  97. package/dist/providers/copilot-provider.js.map +0 -1
  98. package/dist/providers/gemini-provider.d.ts +0 -53
  99. package/dist/providers/gemini-provider.d.ts.map +0 -1
  100. package/dist/providers/gemini-provider.js +0 -545
  101. package/dist/providers/gemini-provider.js.map +0 -1
  102. package/dist/providers/opencode-provider.d.ts +0 -44
  103. package/dist/providers/opencode-provider.d.ts.map +0 -1
  104. package/dist/providers/opencode-provider.js +0 -505
  105. package/dist/providers/opencode-provider.js.map +0 -1
  106. package/web-dist/assets/channel-B4UKdMu-.js +0 -1
  107. package/web-dist/assets/classDiagram-VBA2DB6C-DI8Uy9IN.js +0 -1
  108. package/web-dist/assets/classDiagram-v2-RAHNMMFH-DI8Uy9IN.js +0 -1
  109. package/web-dist/assets/clone-B_ua7cXE.js +0 -1
  110. package/web-dist/assets/stateDiagram-v2-FVOUBMTO-BdlIPPcF.js +0 -1
@@ -1,801 +0,0 @@
1
- /**
2
- * Claude Code CLI Provider — wraps Anthropic's Claude Code CLI as a Jait provider.
3
- *
4
- * Spawns `claude --print --output-format stream-json --include-partial-messages --verbose`
5
- * and parses newline-delimited JSON events from stdout.
6
- *
7
- * Event types from Claude Code stream-json:
8
- * { type: "system", subtype: "init", model, tools, ... }
9
- * { type: "stream_event", event: { type: "content_block_delta", delta: { type: "text_delta", text } } }
10
- * { type: "stream_event", event: { type: "content_block_start", content_block: { type: "tool_use", ... } } }
11
- * { type: "assistant", message: { content: [...blocks...] } } — partial/full message snapshots
12
- * { type: "user", message: { content: [{ type: "tool_result", ... }] } }
13
- * { type: "result", result: "...", usage: {...} }
14
- */
15
- import { spawn, spawnSync } from "node:child_process";
16
- import { EventEmitter } from "node:events";
17
- import { existsSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
18
- import { join } from "node:path";
19
- import { homedir, tmpdir } from "node:os";
20
- import { uuidv7 } from "../db/uuidv7.js";
21
- import { DEVICE_PROVIDER_AUTH, killChildTree as killAuthChildTree, runAuthCommand, startDeviceLoginCommand, } from "./provider-auth.js";
22
- // ── Provider implementation ──────────────────────────────────────────
23
- export class ClaudeCodeProvider {
24
- id = "claude-code";
25
- info = {
26
- id: "claude-code",
27
- name: "Claude Code",
28
- description: "Anthropic Claude Code CLI agent with agentic coding and MCP support",
29
- available: false,
30
- modes: ["full-access", "supervised"],
31
- auth: DEVICE_PROVIDER_AUTH,
32
- };
33
- sessions = new Map();
34
- emitter = new EventEmitter();
35
- claudePath = null;
36
- authLoginProcess = null;
37
- async checkAvailability() {
38
- try {
39
- const paths = ["claude"];
40
- for (const cmd of paths) {
41
- const available = await this.testCommand(cmd);
42
- if (available) {
43
- this.claudePath = cmd;
44
- this.info.available = true;
45
- return true;
46
- }
47
- }
48
- if (!this.claudePath) {
49
- this.info.available = false;
50
- this.info.unavailableReason = "Claude Code CLI not found. Install from https://docs.anthropic.com/en/docs/claude-code";
51
- return false;
52
- }
53
- const hasApiKey = !!process.env.ANTHROPIC_API_KEY?.trim();
54
- const hasClaudeConfig = this.hasClaudeConfig();
55
- if (!hasApiKey && !hasClaudeConfig) {
56
- this.info.available = false;
57
- this.info.unavailableReason = "Claude Code is not configured. Run `claude` once or set ANTHROPIC_API_KEY.";
58
- return false;
59
- }
60
- this.info.available = true;
61
- this.info.unavailableReason = undefined;
62
- return true;
63
- }
64
- catch {
65
- this.info.available = false;
66
- this.info.unavailableReason = "Failed to check Claude Code CLI availability";
67
- return false;
68
- }
69
- }
70
- async getAuthStatus() {
71
- const status = await runAuthCommand(this.id, this.claudePath ?? "claude", ["auth", "status"], 10_000);
72
- const envConfigured = !!process.env.ANTHROPIC_API_KEY?.trim();
73
- const authenticated = envConfigured || status.ok;
74
- return {
75
- ...DEVICE_PROVIDER_AUTH,
76
- authenticated,
77
- detail: authenticated ? "Claude Code credentials are configured." : status.rawOutput ?? "Claude Code is not authenticated.",
78
- };
79
- }
80
- async startLogin() {
81
- if (this.authLoginProcess) {
82
- killAuthChildTree(this.authLoginProcess);
83
- this.authLoginProcess = null;
84
- }
85
- const { result, child } = await startDeviceLoginCommand({
86
- providerId: this.id,
87
- label: "Claude Code",
88
- commandLine: this.claudePath ?? "claude",
89
- args: ["auth", "login", "--claudeai"],
90
- timeoutMs: 30_000,
91
- });
92
- if (child) {
93
- this.authLoginProcess = child;
94
- child.on("exit", () => {
95
- if (this.authLoginProcess === child)
96
- this.authLoginProcess = null;
97
- void this.checkAvailability();
98
- });
99
- }
100
- return result;
101
- }
102
- async logout() {
103
- if (this.authLoginProcess) {
104
- killAuthChildTree(this.authLoginProcess);
105
- this.authLoginProcess = null;
106
- }
107
- const result = await runAuthCommand(this.id, this.claudePath ?? "claude", ["auth", "logout"]);
108
- await this.checkAvailability().catch(() => false);
109
- return {
110
- ...result,
111
- message: result.ok ? "Claude Code logout completed." : result.message,
112
- };
113
- }
114
- /**
115
- * Dynamically discover available models by probing the Claude CLI.
116
- *
117
- * Spawns a short-lived `claude --print` for each candidate alias in parallel,
118
- * reads the `system` init event to get the resolved model name, and deduplicates
119
- * so that aliases pointing to the same underlying model only appear once.
120
- */
121
- async listModels() {
122
- // Candidate aliases to probe — short aliases first (preferred for display)
123
- const candidates = ["sonnet", "opus", "haiku"];
124
- // Also parse --help for any additional aliases we might not know about
125
- try {
126
- const helpAliases = await this.parseAliasesFromHelp();
127
- for (const alias of helpAliases) {
128
- if (!candidates.includes(alias))
129
- candidates.push(alias);
130
- }
131
- }
132
- catch { /* best effort */ }
133
- // Probe all candidates in parallel
134
- const results = await Promise.all(candidates.map(alias => this.probeModel(alias)));
135
- // Deduplicate by resolved model name: keep the first (shortest) alias
136
- const seen = new Map();
137
- for (const r of results) {
138
- if (r && !seen.has(r.resolvedModel)) {
139
- seen.set(r.resolvedModel, r);
140
- }
141
- }
142
- const models = [];
143
- let isFirst = true;
144
- for (const { alias, resolvedModel } of seen.values()) {
145
- models.push({
146
- id: alias,
147
- name: alias.replace(/-/g, " ").replace(/\b\w/g, c => c.toUpperCase()),
148
- description: resolvedModel,
149
- ...(isFirst ? { isDefault: true } : {}),
150
- });
151
- isFirst = false;
152
- }
153
- // Fallback if probing failed entirely
154
- if (models.length === 0) {
155
- return [
156
- { id: "sonnet", name: "Sonnet", description: "Claude Sonnet", isDefault: true },
157
- { id: "opus", name: "Opus", description: "Claude Opus" },
158
- { id: "haiku", name: "Haiku", description: "Claude Haiku" },
159
- ];
160
- }
161
- return models;
162
- }
163
- async startSession(options) {
164
- const sessionId = uuidv7();
165
- const session = {
166
- id: sessionId,
167
- providerId: "claude-code",
168
- threadId: options.threadId,
169
- status: "starting",
170
- runtimeMode: options.mode,
171
- startedAt: new Date().toISOString(),
172
- };
173
- const env = {
174
- ...process.env,
175
- ...options.env,
176
- };
177
- const state = {
178
- session,
179
- process: null,
180
- buffer: "",
181
- workingDirectory: options.workingDirectory,
182
- env,
183
- model: options.model,
184
- mcpConfigPath: this.buildClaudeMcpConfig(options.mcpServers, sessionId),
185
- exitMode: "normal",
186
- pendingToolCalls: [],
187
- hasStreamedTokens: false,
188
- hasSentFirstTurn: false,
189
- pendingDrip: null,
190
- };
191
- this.sessions.set(sessionId, state);
192
- state.session.status = "running";
193
- this.emit({ type: "session.started", sessionId });
194
- return session;
195
- }
196
- async sendTurn(sessionId, message, _attachments) {
197
- const state = this.getState(sessionId);
198
- if (state.process) {
199
- throw new Error("Claude Code turn already running");
200
- }
201
- const isFirstTurn = !state.hasSentFirstTurn;
202
- const args = [
203
- "--print",
204
- "--output-format", "stream-json",
205
- "--include-partial-messages",
206
- "--verbose",
207
- ];
208
- // First turn: create a new session. Subsequent turns: resume it.
209
- if (isFirstTurn) {
210
- args.push("--session-id", state.session.id);
211
- }
212
- else {
213
- args.push("--resume", state.session.id);
214
- }
215
- if (state.session.runtimeMode === "full-access") {
216
- args.push("--dangerously-skip-permissions");
217
- }
218
- else {
219
- args.push("--permission-mode", "default");
220
- }
221
- if (state.model) {
222
- args.push("--model", state.model);
223
- }
224
- if (state.mcpConfigPath) {
225
- args.push("--mcp-config", state.mcpConfigPath);
226
- }
227
- // Use -- to separate options from the positional message argument.
228
- // Without this, variadic flags like --mcp-config consume the message.
229
- args.push("--", message);
230
- state.hasSentFirstTurn = true;
231
- const cmd = this.claudePath ?? "claude";
232
- const child = spawn(cmd, args, {
233
- cwd: state.workingDirectory,
234
- env: state.env,
235
- stdio: ["pipe", "pipe", "pipe"],
236
- windowsHide: true,
237
- });
238
- state.process = child;
239
- state.buffer = "";
240
- state.exitMode = "normal";
241
- state.hasStreamedTokens = false;
242
- state.session.status = "running";
243
- this.emit({ type: "turn.started", sessionId });
244
- // Suppress EPIPE errors when child exits before we finish writing
245
- child.stdin?.on("error", () => { });
246
- // Close stdin — the prompt is passed as a CLI argument, not via stdin
247
- child.stdin?.end();
248
- child.stdout?.on("data", (data) => {
249
- state.buffer += data.toString();
250
- this.processBuffer(sessionId, state);
251
- });
252
- child.stderr?.on("data", (data) => {
253
- const text = data.toString().trim();
254
- if (text) {
255
- console.error(`[claude-code:${sessionId}] stderr: ${text}`);
256
- }
257
- });
258
- // Handle process lifecycle via events (non-blocking, like Codex provider).
259
- // The caller listens for turn.completed / session.error via onEvent().
260
- child.on("exit", (code, signal) => {
261
- state.process = null;
262
- const exitMode = state.exitMode;
263
- state.exitMode = "normal";
264
- if (exitMode === "stop") {
265
- state.session.status = "completed";
266
- state.session.completedAt = new Date().toISOString();
267
- this.emit({ type: "session.completed", sessionId });
268
- return;
269
- }
270
- if (exitMode === "interrupt") {
271
- state.session.status = "interrupted";
272
- this.emit({ type: "turn.completed", sessionId });
273
- return;
274
- }
275
- if (code === 0) {
276
- state.session.status = "idle";
277
- state.session.error = undefined;
278
- this.emit({ type: "turn.completed", sessionId });
279
- return;
280
- }
281
- const error = `Claude Code exited with code ${code}${signal ? ` (signal=${signal})` : ""}`;
282
- state.session.status = "error";
283
- state.session.error = error;
284
- this.emit({ type: "session.error", sessionId, error });
285
- });
286
- child.on("error", (err) => {
287
- state.process = null;
288
- state.exitMode = "normal";
289
- state.session.status = "error";
290
- state.session.error = err.message;
291
- this.emit({ type: "session.error", sessionId, error: err.message });
292
- });
293
- }
294
- async interruptTurn(sessionId) {
295
- const state = this.getState(sessionId);
296
- if (state.process) {
297
- state.exitMode = "interrupt";
298
- killChildTree(state.process, "SIGINT");
299
- }
300
- }
301
- async respondToApproval(_sessionId, _requestId, _approved) {
302
- // Claude Code's --print mode doesn't support interactive approvals.
303
- // In supervised mode, the CLI would pause; we'd need to write to stdin.
304
- console.warn("Claude Code approval response not yet implemented for --print mode");
305
- }
306
- async stopSession(sessionId) {
307
- const state = this.sessions.get(sessionId);
308
- if (!state)
309
- return;
310
- if (state.process) {
311
- state.exitMode = "stop";
312
- killChildTree(state.process, "SIGTERM");
313
- await new Promise((resolve) => {
314
- const timeout = setTimeout(() => {
315
- if (state.process) {
316
- killChildTree(state.process, "SIGKILL");
317
- }
318
- resolve();
319
- }, 3000);
320
- state.process?.on("exit", () => {
321
- clearTimeout(timeout);
322
- resolve();
323
- });
324
- });
325
- }
326
- else {
327
- state.session.status = "completed";
328
- state.session.completedAt = new Date().toISOString();
329
- this.emit({ type: "session.completed", sessionId });
330
- }
331
- // Clean up MCP config temp file
332
- if (state.mcpConfigPath) {
333
- try {
334
- rmSync(join(state.mcpConfigPath, ".."), { recursive: true, force: true });
335
- }
336
- catch { /* best effort */ }
337
- }
338
- this.sessions.delete(sessionId);
339
- }
340
- onEvent(handler) {
341
- this.emitter.on("event", handler);
342
- return () => this.emitter.off("event", handler);
343
- }
344
- // ── Private helpers ────────────────────────────────────────────────
345
- emit(event) {
346
- this.emitter.emit("event", event);
347
- }
348
- getState(sessionId) {
349
- const state = this.sessions.get(sessionId);
350
- if (!state)
351
- throw new Error(`No Claude Code session found: ${sessionId}`);
352
- return state;
353
- }
354
- processBuffer(_sessionId, state) {
355
- const lines = state.buffer.split("\n");
356
- state.buffer = lines.pop() ?? "";
357
- for (const line of lines) {
358
- const trimmed = line.trim();
359
- if (!trimmed)
360
- continue;
361
- try {
362
- const event = JSON.parse(trimmed);
363
- this.handleEvent(state, event);
364
- }
365
- catch {
366
- // Not JSON — could be status output
367
- }
368
- }
369
- }
370
- handleEvent(state, event) {
371
- const sessionId = state.session.id;
372
- const type = event["type"];
373
- switch (type) {
374
- // ── stream_event: wrapper around raw Anthropic API streaming events ──
375
- case "stream_event": {
376
- const inner = event["event"];
377
- if (!inner)
378
- break;
379
- this.handleStreamEvent(state, inner);
380
- break;
381
- }
382
- // ── assistant: complete/partial message snapshot (emitted alongside stream_event) ──
383
- case "assistant": {
384
- // When streaming with --include-partial-messages, these are snapshots
385
- // of the full message so far. We only use them for tool_use blocks
386
- // (the text tokens are already streamed via stream_event deltas).
387
- const message = event["message"];
388
- const content = message?.["content"];
389
- if (Array.isArray(content)) {
390
- for (const block of content) {
391
- const b = block;
392
- if (b["type"] === "tool_use" && b["name"]) {
393
- this.handleToolUseBlock(state, b);
394
- }
395
- }
396
- }
397
- else if (typeof content === "string" && content) {
398
- // Non-streaming fallback: if no stream_event deltas were seen, emit the full text
399
- if (!state.hasStreamedTokens) {
400
- this.emit({ type: "token", sessionId, content });
401
- }
402
- }
403
- break;
404
- }
405
- // ── user: tool result feedback from Claude's own tool execution ──
406
- case "user": {
407
- const message = event["message"];
408
- const userContent = message?.["content"];
409
- if (Array.isArray(userContent)) {
410
- for (const block of userContent) {
411
- const b = block;
412
- if (b["type"] === "tool_result") {
413
- const toolUseId = String(b["tool_use_id"] ?? "");
414
- const isError = b["is_error"] === true;
415
- const resultText = extractResultText(event["tool_use_result"] ?? b["content"] ?? "");
416
- const match = toolUseId
417
- ? state.pendingToolCalls.find(p => p.providerCallId === toolUseId)
418
- : state.pendingToolCalls[0];
419
- if (match) {
420
- const idx = state.pendingToolCalls.indexOf(match);
421
- if (idx !== -1)
422
- state.pendingToolCalls.splice(idx, 1);
423
- }
424
- this.emit({
425
- type: "tool.result",
426
- sessionId,
427
- tool: match ? match.normalizedTool : "unknown",
428
- ok: !isError,
429
- message: resultText,
430
- ...(match ? { callId: match.callId, data: match.args } : {}),
431
- });
432
- }
433
- }
434
- }
435
- break;
436
- }
437
- // ── result: final summary when the CLI process finishes a turn ──
438
- case "result": {
439
- const resultContent = event["result"];
440
- if (resultContent) {
441
- this.emit({ type: "message", sessionId, role: "assistant", content: resultContent });
442
- }
443
- // Extract usage info if available
444
- const usage = event["usage"];
445
- if (usage) {
446
- this.emit({
447
- type: "activity",
448
- sessionId,
449
- kind: "usage",
450
- summary: `Cost: $${event["total_cost_usd"] ?? "?"}`,
451
- payload: { usage, cost: event["total_cost_usd"], duration: event["duration_ms"] },
452
- });
453
- }
454
- break;
455
- }
456
- // ── system: init event with model info, tools, etc. ──
457
- case "system":
458
- break;
459
- // ── rate_limit_event: informational, ignore ──
460
- case "rate_limit_event":
461
- break;
462
- default:
463
- this.emit({
464
- type: "activity",
465
- sessionId,
466
- kind: type ?? "unknown",
467
- summary: `Claude Code: ${type}`,
468
- payload: event,
469
- });
470
- }
471
- }
472
- /**
473
- * Handle unwrapped Anthropic API streaming events from inside stream_event wrappers.
474
- * These follow the Anthropic Messages API streaming format.
475
- */
476
- handleStreamEvent(state, event) {
477
- const sessionId = state.session.id;
478
- const eventType = event["type"];
479
- switch (eventType) {
480
- case "content_block_start":
481
- // tool_use blocks arrive here with input:{} (always empty in streaming).
482
- // We wait for the full `assistant` snapshot to emit tool.start with complete args.
483
- break;
484
- case "content_block_delta": {
485
- const delta = event["delta"];
486
- if (!delta)
487
- break;
488
- const deltaType = delta["type"];
489
- if (deltaType === "text_delta") {
490
- const text = String(delta["text"] ?? "");
491
- if (text) {
492
- state.hasStreamedTokens = true;
493
- this.emit({ type: "token", sessionId, content: text });
494
- }
495
- }
496
- else if (deltaType === "thinking_delta") {
497
- const thinking = String(delta["thinking"] ?? "");
498
- if (thinking) {
499
- this.emit({ type: "activity", sessionId, kind: "thinking", summary: thinking, payload: delta });
500
- }
501
- }
502
- // input_json_delta, signature_delta: ignored (tool input streamed incrementally, not needed)
503
- break;
504
- }
505
- case "content_block_stop":
506
- case "message_start":
507
- case "message_delta":
508
- case "message_stop":
509
- // Lifecycle markers — no action needed
510
- break;
511
- }
512
- }
513
- /**
514
- * Register a tool_use content block as a pending tool call and emit tool.start.
515
- * Only called from `assistant` message snapshots where input is complete.
516
- */
517
- handleToolUseBlock(state, block) {
518
- const sessionId = state.session.id;
519
- const rawTool = String(block["name"] ?? "");
520
- const providerCallId = String(block["id"] ?? "");
521
- const input = block["input"] ?? {};
522
- const hasInput = Object.keys(input).length > 0;
523
- // If we already registered this tool call, update its args if the snapshot
524
- // now has complete input (the first snapshot often has input:{}).
525
- const existing = providerCallId
526
- ? state.pendingToolCalls.find(p => p.providerCallId === providerCallId)
527
- : undefined;
528
- if (existing) {
529
- if (hasInput && Object.keys(existing.args).length === 0) {
530
- existing.args = normalizeClaudeToolArgs(rawTool, input);
531
- }
532
- return;
533
- }
534
- const normalizedTool = normalizeClaudeToolName(rawTool);
535
- const args = normalizeClaudeToolArgs(rawTool, input);
536
- const callId = uuidv7();
537
- state.pendingToolCalls.push({
538
- callId,
539
- rawTool,
540
- normalizedTool,
541
- args,
542
- providerCallId: providerCallId || undefined,
543
- });
544
- this.emit({
545
- type: "tool.start",
546
- sessionId,
547
- tool: normalizedTool,
548
- args,
549
- callId,
550
- });
551
- }
552
- buildClaudeMcpConfig(servers, sessionId) {
553
- if (!servers || servers.length === 0)
554
- return undefined;
555
- const config = { mcpServers: {} };
556
- const mcpServers = config["mcpServers"];
557
- for (const server of servers) {
558
- if (server.transport === "stdio" && server.command) {
559
- mcpServers[server.name] = {
560
- command: server.command,
561
- args: server.args ?? [],
562
- env: server.env ?? {},
563
- };
564
- }
565
- else if (server.transport === "sse" && server.url) {
566
- // Claude Code only supports stdio MCP servers.
567
- // Use npx mcp-remote to bridge HTTP/SSE endpoints.
568
- mcpServers[server.name] = {
569
- command: "npx",
570
- args: ["mcp-remote", server.url],
571
- env: server.env ?? {},
572
- };
573
- }
574
- }
575
- // Write to a temp file and return the path
576
- const configDir = join(tmpdir(), "jait-claude-code", sessionId);
577
- mkdirSync(configDir, { recursive: true });
578
- const configPath = join(configDir, "mcp-config.json");
579
- writeFileSync(configPath, JSON.stringify(config, null, 2));
580
- return configPath;
581
- }
582
- hasClaudeConfig() {
583
- const claudeDir = join(homedir(), ".claude");
584
- const claudeState = join(homedir(), ".claude.json");
585
- return existsSync(claudeDir) || existsSync(claudeState);
586
- }
587
- /**
588
- * Probe a single model alias by spawning `claude --print` with a trivial prompt
589
- * and reading the system init event. Returns the alias and resolved model name,
590
- * or null if the alias is invalid.
591
- */
592
- probeModel(alias) {
593
- return new Promise((resolve) => {
594
- const cmd = this.claudePath ?? "claude";
595
- const child = spawn(cmd, [
596
- "--print", "--output-format", "stream-json", "--verbose",
597
- "--no-session-persistence", "--dangerously-skip-permissions",
598
- "--max-budget-usd", "0.001",
599
- "--model", alias,
600
- ".",
601
- ], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
602
- child.stdin?.on("error", () => { });
603
- let resolved = false;
604
- const timer = setTimeout(() => {
605
- if (!resolved) {
606
- resolved = true;
607
- child.kill();
608
- resolve(null);
609
- }
610
- }, 8000);
611
- child.stdout?.on("data", (d) => {
612
- if (resolved)
613
- return;
614
- try {
615
- const firstLine = d.toString().split("\n")[0];
616
- const init = JSON.parse(firstLine ?? "");
617
- if (init["type"] === "system" && typeof init["model"] === "string") {
618
- resolved = true;
619
- clearTimeout(timer);
620
- child.kill();
621
- resolve({ alias, resolvedModel: init["model"] });
622
- }
623
- }
624
- catch { /* not valid JSON yet */ }
625
- });
626
- child.on("exit", (code) => {
627
- if (!resolved) {
628
- resolved = true;
629
- clearTimeout(timer);
630
- resolve(code === 0 ? { alias, resolvedModel: alias } : null);
631
- }
632
- });
633
- child.on("error", () => {
634
- if (!resolved) {
635
- resolved = true;
636
- clearTimeout(timer);
637
- resolve(null);
638
- }
639
- });
640
- });
641
- }
642
- /**
643
- * Parse `claude --help` to discover model alias names mentioned in the --model description.
644
- */
645
- parseAliasesFromHelp() {
646
- return new Promise((resolve) => {
647
- const cmd = this.claudePath ?? "claude";
648
- const child = spawn(cmd, ["--help"], { stdio: "pipe", windowsHide: true });
649
- let output = "";
650
- const timer = setTimeout(() => { child.kill(); resolve([]); }, 5000);
651
- child.stdout?.on("data", (d) => { output += d.toString(); });
652
- child.stderr?.on("data", (d) => { output += d.toString(); });
653
- child.on("exit", () => {
654
- clearTimeout(timer);
655
- const aliases = [];
656
- const modelSection = output.match(/--model\s+<model>\s+(.*?)(?:\n\s*-|\n\n)/s);
657
- if (modelSection?.[1]) {
658
- const matches = [...modelSection[1].matchAll(/'([a-z][a-z0-9-]*)'/g)];
659
- for (const m of matches) {
660
- if (m[1] && !aliases.includes(m[1]))
661
- aliases.push(m[1]);
662
- }
663
- }
664
- resolve(aliases);
665
- });
666
- child.on("error", () => { clearTimeout(timer); resolve([]); });
667
- });
668
- }
669
- testCommand(cmd) {
670
- return new Promise((resolve) => {
671
- const child = spawn(cmd, ["--version"], {
672
- stdio: "pipe",
673
- windowsHide: true,
674
- });
675
- const timer = setTimeout(() => { child.kill(); resolve(false); }, 5000);
676
- child.on("exit", (code) => { clearTimeout(timer); resolve(code === 0); });
677
- child.on("error", () => { clearTimeout(timer); resolve(false); });
678
- });
679
- }
680
- }
681
- function normalizeClaudeToolName(tool) {
682
- // MCP tools arrive as mcp__servername__toolname
683
- if (tool.startsWith("mcp__"))
684
- return "mcp-tool";
685
- const normalized = tool.trim().toLowerCase();
686
- if (normalized === "edit")
687
- return "edit";
688
- if (normalized === "multiedit")
689
- return "edit";
690
- if (normalized === "write")
691
- return "file.write";
692
- if (normalized === "read")
693
- return "read";
694
- if (normalized === "notebookedit" || normalized === "notebookread")
695
- return "edit";
696
- if (normalized === "websearch")
697
- return "web";
698
- if (normalized === "webfetch")
699
- return "web";
700
- if (normalized === "bash")
701
- return "execute";
702
- if (normalized === "glob")
703
- return "search";
704
- if (normalized === "grep")
705
- return "search";
706
- if (normalized === "lsp" || normalized === "toolsearch")
707
- return "search";
708
- if (normalized === "todowrite")
709
- return "todo";
710
- if (normalized === "agent")
711
- return "agent";
712
- if (normalized === "task" || normalized === "taskcreate" || normalized === "taskget"
713
- || normalized === "tasklist" || normalized === "taskoutput" || normalized === "taskstop"
714
- || normalized === "taskupdate")
715
- return "agent";
716
- return tool;
717
- }
718
- /** Extract plain text from a Claude Code tool result content value (string or content-block array). */
719
- function extractResultText(content) {
720
- if (typeof content === "string")
721
- return content;
722
- if (Array.isArray(content)) {
723
- return content
724
- .filter((b) => Boolean(b && typeof b === "object"))
725
- .map((b) => (typeof b["text"] === "string" ? b["text"] : ""))
726
- .join("");
727
- }
728
- return "";
729
- }
730
- function normalizeClaudeToolArgs(tool, input) {
731
- const normalized = normalizeClaudeToolName(tool);
732
- if (normalized === "edit" || normalized === "file.write" || normalized === "read") {
733
- return {
734
- path: String(input["path"] ?? input["file_path"] ?? input["filePath"] ?? input["file"] ?? ""),
735
- ...(input["old_string"] != null ? { search: input["old_string"] } : {}),
736
- ...(input["new_string"] != null ? { replace: input["new_string"] } : {}),
737
- ...(input["content"] != null ? { content: input["content"] } : {}),
738
- ...(input["new_file_contents"] != null ? { content: input["new_file_contents"] } : {}),
739
- ...input,
740
- };
741
- }
742
- if (normalized === "web") {
743
- return {
744
- query: String(input["query"] ?? input["search_query"] ?? input["q"] ?? ""),
745
- ...(input["url"] != null ? { url: input["url"] } : {}),
746
- ...input,
747
- };
748
- }
749
- if (normalized === "execute") {
750
- return {
751
- command: String(input["command"] ?? ""),
752
- ...input,
753
- };
754
- }
755
- if (normalized === "search") {
756
- return {
757
- pattern: String(input["pattern"] ?? input["query"] ?? input["command"] ?? ""),
758
- ...input,
759
- };
760
- }
761
- if (normalized === "mcp-tool") {
762
- // Preserve the original mcp__server__tool name for the frontend to parse
763
- const parts = tool.split("__");
764
- return {
765
- recipient_name: tool,
766
- ...(parts.length >= 3 ? { server: parts[1], tool: parts.slice(2).join("__") } : {}),
767
- ...input,
768
- };
769
- }
770
- if (normalized === "todo") {
771
- // Claude Code TodoWrite uses {todos: [{id, content, status, priority}]}
772
- // Jait's todo tool uses {todoList: [{id, title, status}]}
773
- const claudeTodos = Array.isArray(input["todos"]) ? input["todos"] : [];
774
- const todoList = claudeTodos.map((item) => {
775
- const t = (item && typeof item === "object" ? item : {});
776
- const rawStatus = String(t["status"] ?? "pending");
777
- const status = rawStatus === "completed" ? "completed"
778
- : rawStatus === "in_progress" || rawStatus === "in-progress" ? "in-progress"
779
- : "not-started";
780
- return { id: Number(t["id"]) || 0, title: String(t["content"] ?? t["title"] ?? ""), status };
781
- });
782
- return { todoList, ...input };
783
- }
784
- return input;
785
- }
786
- function killChildTree(child, signal = "SIGTERM") {
787
- if (process.platform === "win32" && child.pid !== undefined) {
788
- try {
789
- const taskkillArgs = signal === "SIGINT"
790
- ? ["/pid", String(child.pid), "/T", "/F"]
791
- : ["/pid", String(child.pid), "/T", "/F"];
792
- spawnSync("taskkill", taskkillArgs, { stdio: "ignore" });
793
- return;
794
- }
795
- catch {
796
- // Fall through to the default kill path.
797
- }
798
- }
799
- child.kill(signal);
800
- }
801
- //# sourceMappingURL=claude-code-provider.js.map