opc-agent 4.0.9 → 4.0.11
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/dist/providers/index.js +99 -3
- package/package.json +1 -1
- package/src/providers/index.ts +100 -3
package/dist/providers/index.js
CHANGED
|
@@ -406,9 +406,105 @@ class ClaudeCLIProvider {
|
|
|
406
406
|
}
|
|
407
407
|
}
|
|
408
408
|
async *chatStream(messages, systemPrompt) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
409
|
+
const args = ['-p', '--output-format', 'stream-json', '--include-partial-messages'];
|
|
410
|
+
if (this.model) {
|
|
411
|
+
args.push('--model', this.model);
|
|
412
|
+
}
|
|
413
|
+
// Write system prompt to temp file if needed
|
|
414
|
+
let tmpFile;
|
|
415
|
+
if (systemPrompt) {
|
|
416
|
+
const { writeFileSync } = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
417
|
+
const { join } = await Promise.resolve().then(() => __importStar(require('path')));
|
|
418
|
+
const { tmpdir } = await Promise.resolve().then(() => __importStar(require('os')));
|
|
419
|
+
tmpFile = join(tmpdir(), `opc-claude-stream-${Date.now()}.txt`);
|
|
420
|
+
writeFileSync(tmpFile, systemPrompt);
|
|
421
|
+
args.push('--system-prompt-file', tmpFile);
|
|
422
|
+
}
|
|
423
|
+
const lastMsg = messages[messages.length - 1];
|
|
424
|
+
args.push(lastMsg?.content ?? '');
|
|
425
|
+
const { spawn } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
426
|
+
try {
|
|
427
|
+
const proc = spawn('claude', args, {
|
|
428
|
+
env: { ...process.env },
|
|
429
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
430
|
+
});
|
|
431
|
+
proc.stdin.end();
|
|
432
|
+
let buffer = '';
|
|
433
|
+
let lastContent = '';
|
|
434
|
+
for await (const chunk of proc.stdout) {
|
|
435
|
+
buffer += chunk.toString();
|
|
436
|
+
const lines = buffer.split('\n');
|
|
437
|
+
buffer = lines.pop() ?? '';
|
|
438
|
+
for (const line of lines) {
|
|
439
|
+
const trimmed = line.trim();
|
|
440
|
+
if (!trimmed)
|
|
441
|
+
continue;
|
|
442
|
+
try {
|
|
443
|
+
const event = JSON.parse(trimmed);
|
|
444
|
+
// Handle partial message chunks (content_block_delta style)
|
|
445
|
+
if (event.type === 'content' && event.content) {
|
|
446
|
+
const newContent = event.content;
|
|
447
|
+
if (newContent.length > lastContent.length) {
|
|
448
|
+
yield newContent.slice(lastContent.length);
|
|
449
|
+
lastContent = newContent;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// Handle assistant message with content array
|
|
453
|
+
if (event.type === 'assistant' && event.message?.content) {
|
|
454
|
+
for (const block of event.message.content) {
|
|
455
|
+
if (block.type === 'text' && block.text) {
|
|
456
|
+
const newText = block.text;
|
|
457
|
+
if (newText.length > lastContent.length) {
|
|
458
|
+
yield newText.slice(lastContent.length);
|
|
459
|
+
lastContent = newText;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// Handle result message
|
|
465
|
+
if (event.type === 'result' && event.result) {
|
|
466
|
+
const resultText = typeof event.result === 'string' ? event.result : '';
|
|
467
|
+
if (resultText && resultText.length > lastContent.length) {
|
|
468
|
+
yield resultText.slice(lastContent.length);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
// Not JSON, might be raw text
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// Process remaining buffer
|
|
478
|
+
if (buffer.trim()) {
|
|
479
|
+
try {
|
|
480
|
+
const event = JSON.parse(buffer.trim());
|
|
481
|
+
if (event.type === 'result' && event.result) {
|
|
482
|
+
const resultText = typeof event.result === 'string' ? event.result : '';
|
|
483
|
+
if (resultText && resultText.length > lastContent.length) {
|
|
484
|
+
yield resultText.slice(lastContent.length);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
catch {
|
|
489
|
+
// If not JSON, yield as raw text if we haven't yielded anything
|
|
490
|
+
if (!lastContent && buffer.trim()) {
|
|
491
|
+
yield buffer.trim();
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
await new Promise((resolve) => {
|
|
496
|
+
proc.on('close', () => resolve());
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
finally {
|
|
500
|
+
if (tmpFile) {
|
|
501
|
+
try {
|
|
502
|
+
const { unlinkSync } = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
503
|
+
unlinkSync(tmpFile);
|
|
504
|
+
}
|
|
505
|
+
catch { }
|
|
506
|
+
}
|
|
507
|
+
}
|
|
412
508
|
}
|
|
413
509
|
}
|
|
414
510
|
function createProvider(name = 'openai', model, baseUrl, apiKey) {
|
package/package.json
CHANGED
package/src/providers/index.ts
CHANGED
|
@@ -410,9 +410,106 @@ class ClaudeCLIProvider implements LLMProvider {
|
|
|
410
410
|
}
|
|
411
411
|
|
|
412
412
|
async *chatStream(messages: Message[], systemPrompt?: string): AsyncIterable<string> {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
413
|
+
const args = ['-p', '--output-format', 'stream-json', '--include-partial-messages'];
|
|
414
|
+
if (this.model) {
|
|
415
|
+
args.push('--model', this.model);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Write system prompt to temp file if needed
|
|
419
|
+
let tmpFile: string | undefined;
|
|
420
|
+
if (systemPrompt) {
|
|
421
|
+
const { writeFileSync } = await import('fs');
|
|
422
|
+
const { join } = await import('path');
|
|
423
|
+
const { tmpdir } = await import('os');
|
|
424
|
+
tmpFile = join(tmpdir(), `opc-claude-stream-${Date.now()}.txt`);
|
|
425
|
+
writeFileSync(tmpFile, systemPrompt);
|
|
426
|
+
args.push('--system-prompt-file', tmpFile);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const lastMsg = messages[messages.length - 1];
|
|
430
|
+
args.push(lastMsg?.content ?? '');
|
|
431
|
+
|
|
432
|
+
const { spawn } = await import('child_process');
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
const proc = spawn('claude', args, {
|
|
436
|
+
env: { ...process.env },
|
|
437
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
438
|
+
});
|
|
439
|
+
proc.stdin.end();
|
|
440
|
+
|
|
441
|
+
let buffer = '';
|
|
442
|
+
let lastContent = '';
|
|
443
|
+
|
|
444
|
+
for await (const chunk of proc.stdout) {
|
|
445
|
+
buffer += (chunk as Buffer).toString();
|
|
446
|
+
const lines = buffer.split('\n');
|
|
447
|
+
buffer = lines.pop() ?? '';
|
|
448
|
+
|
|
449
|
+
for (const line of lines) {
|
|
450
|
+
const trimmed = line.trim();
|
|
451
|
+
if (!trimmed) continue;
|
|
452
|
+
try {
|
|
453
|
+
const event = JSON.parse(trimmed);
|
|
454
|
+
// Handle partial message chunks (content_block_delta style)
|
|
455
|
+
if (event.type === 'content' && event.content) {
|
|
456
|
+
const newContent = event.content;
|
|
457
|
+
if (newContent.length > lastContent.length) {
|
|
458
|
+
yield newContent.slice(lastContent.length);
|
|
459
|
+
lastContent = newContent;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
// Handle assistant message with content array
|
|
463
|
+
if (event.type === 'assistant' && event.message?.content) {
|
|
464
|
+
for (const block of event.message.content) {
|
|
465
|
+
if (block.type === 'text' && block.text) {
|
|
466
|
+
const newText = block.text;
|
|
467
|
+
if (newText.length > lastContent.length) {
|
|
468
|
+
yield newText.slice(lastContent.length);
|
|
469
|
+
lastContent = newText;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
// Handle result message
|
|
475
|
+
if (event.type === 'result' && event.result) {
|
|
476
|
+
const resultText = typeof event.result === 'string' ? event.result : '';
|
|
477
|
+
if (resultText && resultText.length > lastContent.length) {
|
|
478
|
+
yield resultText.slice(lastContent.length);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
} catch {
|
|
482
|
+
// Not JSON, might be raw text
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Process remaining buffer
|
|
488
|
+
if (buffer.trim()) {
|
|
489
|
+
try {
|
|
490
|
+
const event = JSON.parse(buffer.trim());
|
|
491
|
+
if (event.type === 'result' && event.result) {
|
|
492
|
+
const resultText = typeof event.result === 'string' ? event.result : '';
|
|
493
|
+
if (resultText && resultText.length > lastContent.length) {
|
|
494
|
+
yield resultText.slice(lastContent.length);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
} catch {
|
|
498
|
+
// If not JSON, yield as raw text if we haven't yielded anything
|
|
499
|
+
if (!lastContent && buffer.trim()) {
|
|
500
|
+
yield buffer.trim();
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
await new Promise<void>((resolve) => {
|
|
506
|
+
proc.on('close', () => resolve());
|
|
507
|
+
});
|
|
508
|
+
} finally {
|
|
509
|
+
if (tmpFile) {
|
|
510
|
+
try { const { unlinkSync } = await import('fs'); unlinkSync(tmpFile); } catch {}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
416
513
|
}
|
|
417
514
|
}
|
|
418
515
|
|