opc-agent 4.0.9 → 4.0.10

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.
@@ -406,9 +406,65 @@ class ClaudeCLIProvider {
406
406
  }
407
407
  }
408
408
  async *chatStream(messages, systemPrompt) {
409
- // Claude CLI --print doesn't support streaming well, so we do single-shot
410
- const result = await this.chat(messages, systemPrompt);
411
- yield result;
409
+ // Build prompt same as chat()
410
+ const parts = [];
411
+ if (systemPrompt) {
412
+ parts.push(`[System]: ${systemPrompt}`);
413
+ }
414
+ for (const m of messages) {
415
+ const role = m.role === 'user' ? 'Human' : 'Assistant';
416
+ parts.push(`${role}: ${m.content}`);
417
+ }
418
+ const prompt = parts.join('\n\n');
419
+ const args = ['-p', '--output-format', 'text'];
420
+ if (this.model) {
421
+ args.push('--model', this.model);
422
+ }
423
+ // Write system prompt to temp file if needed
424
+ let tmpFile;
425
+ if (systemPrompt) {
426
+ const { writeFileSync } = await Promise.resolve().then(() => __importStar(require('fs')));
427
+ const { join } = await Promise.resolve().then(() => __importStar(require('path')));
428
+ const { tmpdir } = await Promise.resolve().then(() => __importStar(require('os')));
429
+ tmpFile = join(tmpdir(), `opc-claude-stream-${Date.now()}.txt`);
430
+ writeFileSync(tmpFile, systemPrompt);
431
+ args.push('--system-prompt-file', tmpFile);
432
+ }
433
+ const lastMsg = messages[messages.length - 1];
434
+ args.push(lastMsg?.content ?? prompt);
435
+ const { spawn } = await Promise.resolve().then(() => __importStar(require('child_process')));
436
+ try {
437
+ const proc = spawn('claude', args, {
438
+ env: { ...process.env },
439
+ stdio: ['pipe', 'pipe', 'pipe'],
440
+ });
441
+ proc.stdin.end();
442
+ // Yield chunks as they arrive from stdout
443
+ const readable = proc.stdout;
444
+ for await (const chunk of readable) {
445
+ yield chunk.toString();
446
+ }
447
+ // Wait for process to finish
448
+ await new Promise((resolve, reject) => {
449
+ proc.on('close', (code) => {
450
+ if (code !== 0 && code !== null) {
451
+ // Already yielded content, just log
452
+ console.warn(`[ClaudeCLI] Process exited with code ${code}`);
453
+ }
454
+ resolve();
455
+ });
456
+ proc.on('error', reject);
457
+ });
458
+ }
459
+ finally {
460
+ if (tmpFile) {
461
+ try {
462
+ const { unlinkSync } = await Promise.resolve().then(() => __importStar(require('fs')));
463
+ unlinkSync(tmpFile);
464
+ }
465
+ catch { }
466
+ }
467
+ }
412
468
  }
413
469
  }
414
470
  function createProvider(name = 'openai', model, baseUrl, apiKey) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opc-agent",
3
- "version": "4.0.9",
3
+ "version": "4.0.10",
4
4
  "description": "Open Agent Framework — Build, test, and run AI Agents for business workstations",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -410,9 +410,67 @@ class ClaudeCLIProvider implements LLMProvider {
410
410
  }
411
411
 
412
412
  async *chatStream(messages: Message[], systemPrompt?: string): AsyncIterable<string> {
413
- // Claude CLI --print doesn't support streaming well, so we do single-shot
414
- const result = await this.chat(messages, systemPrompt);
415
- yield result;
413
+ // Build prompt same as chat()
414
+ const parts: string[] = [];
415
+ if (systemPrompt) {
416
+ parts.push(`[System]: ${systemPrompt}`);
417
+ }
418
+ for (const m of messages) {
419
+ const role = m.role === 'user' ? 'Human' : 'Assistant';
420
+ parts.push(`${role}: ${m.content}`);
421
+ }
422
+ const prompt = parts.join('\n\n');
423
+
424
+ const args = ['-p', '--output-format', 'text'];
425
+ if (this.model) {
426
+ args.push('--model', this.model);
427
+ }
428
+
429
+ // Write system prompt to temp file if needed
430
+ let tmpFile: string | undefined;
431
+ if (systemPrompt) {
432
+ const { writeFileSync } = await import('fs');
433
+ const { join } = await import('path');
434
+ const { tmpdir } = await import('os');
435
+ tmpFile = join(tmpdir(), `opc-claude-stream-${Date.now()}.txt`);
436
+ writeFileSync(tmpFile, systemPrompt);
437
+ args.push('--system-prompt-file', tmpFile);
438
+ }
439
+
440
+ const lastMsg = messages[messages.length - 1];
441
+ args.push(lastMsg?.content ?? prompt);
442
+
443
+ const { spawn } = await import('child_process');
444
+
445
+ try {
446
+ const proc = spawn('claude', args, {
447
+ env: { ...process.env },
448
+ stdio: ['pipe', 'pipe', 'pipe'],
449
+ });
450
+ proc.stdin.end();
451
+
452
+ // Yield chunks as they arrive from stdout
453
+ const readable = proc.stdout;
454
+ for await (const chunk of readable) {
455
+ yield (chunk as Buffer).toString();
456
+ }
457
+
458
+ // Wait for process to finish
459
+ await new Promise<void>((resolve, reject) => {
460
+ proc.on('close', (code) => {
461
+ if (code !== 0 && code !== null) {
462
+ // Already yielded content, just log
463
+ console.warn(`[ClaudeCLI] Process exited with code ${code}`);
464
+ }
465
+ resolve();
466
+ });
467
+ proc.on('error', reject);
468
+ });
469
+ } finally {
470
+ if (tmpFile) {
471
+ try { const { unlinkSync } = await import('fs'); unlinkSync(tmpFile); } catch {}
472
+ }
473
+ }
416
474
  }
417
475
  }
418
476