@hienlh/ppm 0.5.11 → 0.5.12
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/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/src/providers/claude-agent-sdk.ts +143 -113
- package/test-sdk.mjs +106 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.12] - 2026-03-18
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Windows: direct CLI fallback for chat** — on Windows, bypass SDK `query()` (broken due to Bun subprocess pipe buffering) and spawn `claude -p --verbose --output-format stream-json` directly. Same event format, same features — streaming, tools, session resume all work
|
|
7
|
+
- Removed SDK timeout/diagnostic code (no longer needed with direct CLI fallback)
|
|
8
|
+
|
|
3
9
|
## [0.5.11] - 2026-03-18
|
|
4
10
|
|
|
5
11
|
### Fixed
|
package/package.json
CHANGED
|
@@ -88,6 +88,100 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
88
88
|
} catch { return {}; }
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Direct CLI fallback for Windows — spawns `claude -p` with stream-json output.
|
|
93
|
+
* Workaround for Bun + Windows SDK subprocess pipe buffering issue.
|
|
94
|
+
* Returns an async generator yielding the same event types as SDK query().
|
|
95
|
+
*/
|
|
96
|
+
private async *queryDirectCli(opts: {
|
|
97
|
+
prompt: string;
|
|
98
|
+
cwd: string;
|
|
99
|
+
sessionId: string;
|
|
100
|
+
sdkId: string;
|
|
101
|
+
isFirstMessage: boolean;
|
|
102
|
+
shouldFork: boolean;
|
|
103
|
+
env: Record<string, string | undefined>;
|
|
104
|
+
providerConfig: Partial<import("../types/config.ts").AIProviderConfig>;
|
|
105
|
+
}): AsyncGenerator<any> {
|
|
106
|
+
const args = ["-p", opts.prompt, "--verbose", "--output-format", "stream-json"];
|
|
107
|
+
|
|
108
|
+
// Session management
|
|
109
|
+
if (!opts.isFirstMessage || opts.shouldFork) {
|
|
110
|
+
args.push("--resume", opts.sdkId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Config-driven options
|
|
114
|
+
if (opts.providerConfig.model) args.push("--model", opts.providerConfig.model);
|
|
115
|
+
const maxTurns = opts.providerConfig.max_turns ?? 100;
|
|
116
|
+
args.push("--max-turns", String(maxTurns));
|
|
117
|
+
if (opts.providerConfig.effort) args.push("--effort", opts.providerConfig.effort);
|
|
118
|
+
|
|
119
|
+
// Permission mode
|
|
120
|
+
args.push("--permission-mode", "bypassPermissions", "--dangerously-skip-permissions");
|
|
121
|
+
|
|
122
|
+
console.log(`[sdk-cli] spawning: claude ${args.slice(0, 6).join(" ")}... cwd=${opts.cwd}`);
|
|
123
|
+
|
|
124
|
+
const proc = Bun.spawn({
|
|
125
|
+
cmd: ["claude", ...args],
|
|
126
|
+
cwd: opts.cwd,
|
|
127
|
+
stdout: "pipe",
|
|
128
|
+
stderr: "pipe",
|
|
129
|
+
env: opts.env as Record<string, string>,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Store proc for abort support
|
|
133
|
+
const abortHandle = { close: () => { try { proc.kill(); } catch {} } };
|
|
134
|
+
this.activeQueries.set(opts.sessionId, abortHandle as any);
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const reader = proc.stdout.getReader();
|
|
138
|
+
const decoder = new TextDecoder();
|
|
139
|
+
let buffer = "";
|
|
140
|
+
|
|
141
|
+
while (true) {
|
|
142
|
+
const { done, value } = await reader.read();
|
|
143
|
+
if (done) break;
|
|
144
|
+
|
|
145
|
+
buffer += decoder.decode(value, { stream: true });
|
|
146
|
+
const lines = buffer.split("\n");
|
|
147
|
+
buffer = lines.pop() ?? ""; // Keep incomplete last line in buffer
|
|
148
|
+
|
|
149
|
+
for (const line of lines) {
|
|
150
|
+
const trimmed = line.trim();
|
|
151
|
+
if (!trimmed) continue;
|
|
152
|
+
try {
|
|
153
|
+
const event = JSON.parse(trimmed);
|
|
154
|
+
yield event;
|
|
155
|
+
} catch {
|
|
156
|
+
// Skip non-JSON lines (e.g. progress indicators)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Process remaining buffer
|
|
162
|
+
if (buffer.trim()) {
|
|
163
|
+
try { yield JSON.parse(buffer.trim()); } catch {}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Wait for process to exit
|
|
167
|
+
const exitCode = await proc.exited;
|
|
168
|
+
console.log(`[sdk-cli] process exited: code=${exitCode}`);
|
|
169
|
+
|
|
170
|
+
// Read stderr if process failed
|
|
171
|
+
if (exitCode !== 0) {
|
|
172
|
+
try {
|
|
173
|
+
const errReader = proc.stderr.getReader();
|
|
174
|
+
const { value: errBytes } = await errReader.read();
|
|
175
|
+
const stderr = errBytes ? new TextDecoder().decode(errBytes).trim() : "";
|
|
176
|
+
if (stderr) console.error(`[sdk-cli] stderr: ${stderr.slice(0, 500)}`);
|
|
177
|
+
} catch {}
|
|
178
|
+
}
|
|
179
|
+
} finally {
|
|
180
|
+
this.activeQueries.delete(opts.sessionId);
|
|
181
|
+
try { proc.kill(); } catch {}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
91
185
|
/** Read current provider config from yaml (fresh each call) */
|
|
92
186
|
private getProviderConfig(): Partial<import("../types/config.ts").AIProviderConfig> {
|
|
93
187
|
const ai = configService.get("ai");
|
|
@@ -289,9 +383,6 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
289
383
|
let resultSubtype: string | undefined;
|
|
290
384
|
let resultNumTurns: number | undefined;
|
|
291
385
|
let resultContextWindowPct: number | undefined;
|
|
292
|
-
let firstEventReceived = false;
|
|
293
|
-
let sdkTimeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
294
|
-
|
|
295
386
|
try {
|
|
296
387
|
const providerConfig = this.getProviderConfig();
|
|
297
388
|
// Resolve SDK's actual session ID for resume (may differ from PPM's UUID)
|
|
@@ -300,113 +391,67 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
300
391
|
// Fallback cwd: SDK needs a valid working directory even when no project is selected.
|
|
301
392
|
// On Windows daemons, undefined cwd can cause the subprocess to fail silently.
|
|
302
393
|
const effectiveCwd = meta.projectPath || homedir();
|
|
394
|
+
const queryEnv = { ...process.env, ...this.getProjectEnvOverrides(meta.projectPath) };
|
|
303
395
|
console.log(`[sdk] query: session=${sessionId} sdkId=${sdkId} isFirst=${isFirstMessage} fork=${shouldFork} cwd=${effectiveCwd} platform=${process.platform}`);
|
|
304
396
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
sessionId: isFirstMessage && !shouldFork ? sessionId : undefined,
|
|
309
|
-
resume: (isFirstMessage && !shouldFork) ? undefined : sdkId,
|
|
310
|
-
...(shouldFork && { forkSession: true }),
|
|
311
|
-
cwd: effectiveCwd,
|
|
312
|
-
// Use full Claude Code system prompt (coding guidelines, security, response style)
|
|
313
|
-
systemPrompt: { type: "preset", preset: "claude_code" },
|
|
314
|
-
// Load skills/settings from both user (~/.claude) and project directory
|
|
315
|
-
settingSources: ["user", "project"],
|
|
316
|
-
// Neutralize project .env keys that would override user's global auth.
|
|
317
|
-
// Only clear if the project's .env contains them (prevents .env poisoning).
|
|
318
|
-
// Keep global env vars intact for API key auth users.
|
|
319
|
-
env: {
|
|
320
|
-
...process.env,
|
|
321
|
-
...this.getProjectEnvOverrides(meta.projectPath),
|
|
322
|
-
},
|
|
323
|
-
// Override project-local Claude settings that may restrict tool permissions
|
|
324
|
-
settings: { permissions: { allow: [], deny: [] } },
|
|
325
|
-
allowedTools: [
|
|
326
|
-
"Read", "Write", "Edit", "Bash", "Glob", "Grep",
|
|
327
|
-
"WebSearch", "WebFetch", "AskUserQuestion",
|
|
328
|
-
"Agent", "Skill", "TodoWrite", "ToolSearch",
|
|
329
|
-
],
|
|
330
|
-
permissionMode: "bypassPermissions",
|
|
331
|
-
allowDangerouslySkipPermissions: true,
|
|
332
|
-
// Config-driven values from ppm.yaml
|
|
333
|
-
...(providerConfig.model && { model: providerConfig.model }),
|
|
334
|
-
...(providerConfig.effort && { effort: providerConfig.effort }),
|
|
335
|
-
maxTurns: providerConfig.max_turns ?? 100,
|
|
336
|
-
...(providerConfig.max_budget_usd && { maxBudgetUsd: providerConfig.max_budget_usd }),
|
|
337
|
-
...(providerConfig.thinking_budget_tokens != null && {
|
|
338
|
-
thinkingBudgetTokens: providerConfig.thinking_budget_tokens,
|
|
339
|
-
}),
|
|
340
|
-
canUseTool,
|
|
341
|
-
includePartialMessages: true,
|
|
342
|
-
} as any,
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
// Track active query for abort support
|
|
346
|
-
this.activeQueries.set(sessionId, q);
|
|
347
|
-
console.log(`[sdk] query object created, starting iteration...`);
|
|
397
|
+
// On Windows, use direct CLI fallback (SDK query() hangs due to Bun subprocess pipe buffering)
|
|
398
|
+
const useDirectCli = process.platform === "win32";
|
|
399
|
+
let eventSource: AsyncIterable<any>;
|
|
348
400
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
stdout: "pipe", stderr: "pipe",
|
|
354
|
-
});
|
|
355
|
-
const claudePath = which.stdout.toString().trim().split("\n")[0];
|
|
356
|
-
console.log(`[sdk] claude CLI: ${claudePath || "(not found in PATH)"}`);
|
|
357
|
-
} catch { console.log("[sdk] claude CLI: check failed"); }
|
|
358
|
-
|
|
359
|
-
// Quick CLI version check — verify the binary actually runs from this process
|
|
360
|
-
try {
|
|
361
|
-
const verProc = Bun.spawnSync({
|
|
362
|
-
cmd: ["claude", "--version"],
|
|
363
|
-
stdout: "pipe", stderr: "pipe",
|
|
401
|
+
if (useDirectCli) {
|
|
402
|
+
console.log(`[sdk] Windows detected — using direct CLI fallback (bypasses SDK pipe issue)`);
|
|
403
|
+
eventSource = this.queryDirectCli({
|
|
404
|
+
prompt: message,
|
|
364
405
|
cwd: effectiveCwd,
|
|
406
|
+
sessionId,
|
|
407
|
+
sdkId,
|
|
408
|
+
isFirstMessage,
|
|
409
|
+
shouldFork,
|
|
410
|
+
env: queryEnv,
|
|
411
|
+
providerConfig,
|
|
365
412
|
});
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
413
|
+
} else {
|
|
414
|
+
const q = query({
|
|
415
|
+
prompt: message,
|
|
416
|
+
options: {
|
|
417
|
+
sessionId: isFirstMessage && !shouldFork ? sessionId : undefined,
|
|
418
|
+
resume: (isFirstMessage && !shouldFork) ? undefined : sdkId,
|
|
419
|
+
...(shouldFork && { forkSession: true }),
|
|
420
|
+
cwd: effectiveCwd,
|
|
421
|
+
systemPrompt: { type: "preset", preset: "claude_code" },
|
|
422
|
+
settingSources: ["user", "project"],
|
|
423
|
+
env: queryEnv,
|
|
424
|
+
settings: { permissions: { allow: [], deny: [] } },
|
|
425
|
+
allowedTools: [
|
|
426
|
+
"Read", "Write", "Edit", "Bash", "Glob", "Grep",
|
|
427
|
+
"WebSearch", "WebFetch", "AskUserQuestion",
|
|
428
|
+
"Agent", "Skill", "TodoWrite", "ToolSearch",
|
|
429
|
+
],
|
|
430
|
+
permissionMode: "bypassPermissions",
|
|
431
|
+
allowDangerouslySkipPermissions: true,
|
|
432
|
+
...(providerConfig.model && { model: providerConfig.model }),
|
|
433
|
+
...(providerConfig.effort && { effort: providerConfig.effort }),
|
|
434
|
+
maxTurns: providerConfig.max_turns ?? 100,
|
|
435
|
+
...(providerConfig.max_budget_usd && { maxBudgetUsd: providerConfig.max_budget_usd }),
|
|
436
|
+
...(providerConfig.thinking_budget_tokens != null && {
|
|
437
|
+
thinkingBudgetTokens: providerConfig.thinking_budget_tokens,
|
|
438
|
+
}),
|
|
439
|
+
canUseTool,
|
|
440
|
+
includePartialMessages: true,
|
|
441
|
+
} as any,
|
|
442
|
+
});
|
|
443
|
+
this.activeQueries.set(sessionId, q);
|
|
444
|
+
eventSource = q;
|
|
372
445
|
}
|
|
373
446
|
|
|
374
|
-
// Log env keys relevant to SDK auth (values redacted)
|
|
375
|
-
const authKeys = ["ANTHROPIC_API_KEY", "CLAUDE_CODE_USE_BEDROCK", "CLAUDE_CODE_USE_VERTEX", "CLAUDE_CODE_USE_FOUNDRY"];
|
|
376
|
-
const envStatus = authKeys.map(k => `${k}=${process.env[k] ? "SET" : "unset"}`).join(" ");
|
|
377
|
-
console.log(`[sdk] env auth: ${envStatus}`);
|
|
378
|
-
|
|
379
447
|
let lastPartialText = "";
|
|
380
448
|
/** Number of tool_use blocks pending results (top-level tools only, not subagent children) */
|
|
381
449
|
let pendingToolCount = 0;
|
|
382
450
|
|
|
383
|
-
// First-event timeout: if SDK hangs (common on Windows/Bun), close query and run diagnostics
|
|
384
|
-
const SDK_TIMEOUT_MS = 30_000;
|
|
385
|
-
sdkTimeoutId = setTimeout(async () => {
|
|
386
|
-
if (firstEventReceived) return;
|
|
387
|
-
console.error(`[sdk] TIMEOUT: no events after ${SDK_TIMEOUT_MS / 1000}s — closing query and running diagnostics`);
|
|
388
|
-
try { q.close(); } catch {}
|
|
389
|
-
|
|
390
|
-
// Direct CLI test to determine if the issue is SDK-specific or CLI-wide
|
|
391
|
-
try {
|
|
392
|
-
const directProc = Bun.spawnSync({
|
|
393
|
-
cmd: ["claude", "-p", "say ok", "--output-format", "stream-json", "--max-turns", "1"],
|
|
394
|
-
stdout: "pipe", stderr: "pipe",
|
|
395
|
-
cwd: effectiveCwd,
|
|
396
|
-
env: { ...process.env, ...this.getProjectEnvOverrides(meta.projectPath) },
|
|
397
|
-
});
|
|
398
|
-
console.log(`[sdk] direct CLI test: exit=${directProc.exitCode} stdout=${directProc.stdout.toString().trim().slice(0, 500)} stderr=${directProc.stderr.toString().trim().slice(0, 300)}`);
|
|
399
|
-
} catch (e) {
|
|
400
|
-
console.error(`[sdk] direct CLI test failed: ${(e as Error).message}`);
|
|
401
|
-
}
|
|
402
|
-
}, SDK_TIMEOUT_MS);
|
|
403
|
-
|
|
404
451
|
let sdkEventCount = 0;
|
|
405
|
-
for await (const msg of
|
|
452
|
+
for await (const msg of eventSource) {
|
|
406
453
|
sdkEventCount++;
|
|
407
454
|
if (sdkEventCount === 1) {
|
|
408
|
-
firstEventReceived = true;
|
|
409
|
-
clearTimeout(sdkTimeoutId);
|
|
410
455
|
console.log(`[sdk] first event received: type=${(msg as any).type} subtype=${(msg as any).subtype ?? "none"}`);
|
|
411
456
|
}
|
|
412
457
|
// Extract parent_tool_use_id from SDK message (present on subagent-scoped messages)
|
|
@@ -647,31 +692,16 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
647
692
|
yield approvalEvents.shift()!;
|
|
648
693
|
}
|
|
649
694
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
yield {
|
|
653
|
-
type: "error",
|
|
654
|
-
message: "Claude SDK did not respond (query timed out). This may be a Bun + Windows compatibility issue. Try: `ppm chat` from terminal, or run with Node.js: `npx tsx src/index.ts start -f`",
|
|
655
|
-
};
|
|
695
|
+
if (sdkEventCount === 0) {
|
|
696
|
+
yield { type: "error", message: "Claude did not respond. Check that 'claude' CLI works in your terminal." };
|
|
656
697
|
}
|
|
657
698
|
} catch (e) {
|
|
658
699
|
const msg = (e as Error).message ?? String(e);
|
|
659
|
-
const stack = (e as Error).stack ?? "";
|
|
660
700
|
console.error(`[sdk] error: ${msg}`);
|
|
661
|
-
if (stack) console.error(`[sdk] stack: ${stack}`);
|
|
662
|
-
// Don't yield error for intentional abort or timeout-triggered close
|
|
663
701
|
if (!msg.includes("abort") && !msg.includes("closed")) {
|
|
664
702
|
yield { type: "error", message: `SDK error: ${msg}` };
|
|
665
703
|
}
|
|
666
|
-
// If closed by timeout (no events received), provide user-facing error
|
|
667
|
-
if (!firstEventReceived) {
|
|
668
|
-
yield {
|
|
669
|
-
type: "error",
|
|
670
|
-
message: "Claude SDK did not respond (query timed out). This may be a Bun + Windows compatibility issue. Try: `ppm chat` from terminal, or run with Node.js: `npx tsx src/index.ts start -f`",
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
704
|
} finally {
|
|
674
|
-
clearTimeout(sdkTimeoutId);
|
|
675
705
|
this.activeQueries.delete(sessionId);
|
|
676
706
|
}
|
|
677
707
|
|
package/test-sdk.mjs
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal SDK test — run on Windows to diagnose Bun + SDK issue.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* bun test-sdk.mjs
|
|
6
|
+
* node --experimental-strip-types test-sdk.mjs
|
|
7
|
+
*/
|
|
8
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
|
|
12
|
+
// Remove CLAUDECODE to avoid nested session error
|
|
13
|
+
delete process.env.CLAUDECODE;
|
|
14
|
+
|
|
15
|
+
const cwd = homedir();
|
|
16
|
+
|
|
17
|
+
console.log("=== SDK Test ===");
|
|
18
|
+
console.log(`Platform: ${process.platform}`);
|
|
19
|
+
console.log(`Runtime: ${typeof Bun !== "undefined" ? `Bun ${Bun.version}` : `Node ${process.version}`}`);
|
|
20
|
+
console.log(`CWD: ${cwd}`);
|
|
21
|
+
console.log(`API_KEY: ${process.env.ANTHROPIC_API_KEY ? "SET" : "unset"}`);
|
|
22
|
+
|
|
23
|
+
// Test 1: claude --version
|
|
24
|
+
console.log("\n--- Test 1: claude --version ---");
|
|
25
|
+
try {
|
|
26
|
+
const ver = spawnSync("claude", ["--version"], { encoding: "utf-8", timeout: 10000 });
|
|
27
|
+
console.log(`exit=${ver.status} stdout="${ver.stdout?.trim()}" stderr="${ver.stderr?.trim()}"`);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
console.log(`FAILED: ${e.message}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Test 2: claude -p (direct CLI)
|
|
33
|
+
console.log("\n--- Test 2: claude -p (direct spawn) ---");
|
|
34
|
+
try {
|
|
35
|
+
const direct = spawnSync("claude", ["-p", "say ok", "--output-format", "text", "--max-turns", "1"], {
|
|
36
|
+
encoding: "utf-8",
|
|
37
|
+
timeout: 30000,
|
|
38
|
+
cwd,
|
|
39
|
+
env: process.env,
|
|
40
|
+
});
|
|
41
|
+
console.log(`exit=${direct.status}`);
|
|
42
|
+
console.log(`stdout="${direct.stdout?.trim().slice(0, 200)}"`);
|
|
43
|
+
if (direct.stderr?.trim()) console.log(`stderr="${direct.stderr.trim().slice(0, 200)}"`);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.log(`FAILED: ${e.message}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Test 3: SDK query()
|
|
49
|
+
console.log("\n--- Test 3: SDK query() ---");
|
|
50
|
+
const startTime = Date.now();
|
|
51
|
+
const TIMEOUT = 15000;
|
|
52
|
+
let gotEvent = false;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const q = query({
|
|
56
|
+
prompt: "say ok",
|
|
57
|
+
options: {
|
|
58
|
+
cwd,
|
|
59
|
+
maxTurns: 1,
|
|
60
|
+
permissionMode: "bypassPermissions",
|
|
61
|
+
allowDangerouslySkipPermissions: true,
|
|
62
|
+
systemPrompt: { type: "custom", custom: "Reply only with: ok" },
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Race first event against timeout
|
|
67
|
+
const iterator = q[Symbol.asyncIterator]();
|
|
68
|
+
const result = await Promise.race([
|
|
69
|
+
iterator.next(),
|
|
70
|
+
new Promise((resolve) => setTimeout(() => resolve("TIMEOUT"), TIMEOUT)),
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
if (result === "TIMEOUT") {
|
|
74
|
+
console.log(`TIMEOUT: no events after ${TIMEOUT / 1000}s`);
|
|
75
|
+
console.log(">>> This confirms the Bun + Windows SDK issue <<<");
|
|
76
|
+
try { q.close(); } catch {}
|
|
77
|
+
} else {
|
|
78
|
+
gotEvent = true;
|
|
79
|
+
const elapsed = Date.now() - startTime;
|
|
80
|
+
const msg = result.value;
|
|
81
|
+
console.log(`First event in ${elapsed}ms: type=${msg?.type} subtype=${msg?.subtype ?? "none"}`);
|
|
82
|
+
|
|
83
|
+
// Read remaining events
|
|
84
|
+
let count = 1;
|
|
85
|
+
for await (const ev of { [Symbol.asyncIterator]: () => iterator }) {
|
|
86
|
+
count++;
|
|
87
|
+
if (ev.type === "assistant") {
|
|
88
|
+
const text = ev.message?.content?.find((b) => b.type === "text")?.text ?? "";
|
|
89
|
+
console.log(`Event #${count}: assistant text="${text.slice(0, 100)}"`);
|
|
90
|
+
} else if (ev.type === "result") {
|
|
91
|
+
console.log(`Event #${count}: result subtype=${ev.subtype}`);
|
|
92
|
+
break;
|
|
93
|
+
} else {
|
|
94
|
+
console.log(`Event #${count}: ${ev.type}`);
|
|
95
|
+
}
|
|
96
|
+
if (count > 20) { console.log("(stopping after 20 events)"); break; }
|
|
97
|
+
}
|
|
98
|
+
console.log(`\nSUCCESS: SDK works! Total events: ${count}`);
|
|
99
|
+
}
|
|
100
|
+
} catch (e) {
|
|
101
|
+
console.log(`ERROR: ${e.message}`);
|
|
102
|
+
if (e.stack) console.log(e.stack);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(`\nTotal time: ${((Date.now() - startTime) / 1000).toFixed(1)}s`);
|
|
106
|
+
process.exit(gotEvent ? 0 : 1);
|