@skroyc/librarian 0.1.0 → 0.2.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.
- package/README.md +4 -16
- package/dist/agents/context-schema.d.ts +1 -1
- package/dist/agents/context-schema.d.ts.map +1 -1
- package/dist/agents/context-schema.js +5 -2
- package/dist/agents/context-schema.js.map +1 -1
- package/dist/agents/react-agent.d.ts.map +1 -1
- package/dist/agents/react-agent.js +36 -27
- package/dist/agents/react-agent.js.map +1 -1
- package/dist/agents/tool-runtime.d.ts.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +53 -49
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +115 -69
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +246 -150
- package/dist/index.js.map +1 -1
- package/dist/tools/file-finding.tool.d.ts +1 -1
- package/dist/tools/file-finding.tool.d.ts.map +1 -1
- package/dist/tools/file-finding.tool.js +70 -130
- package/dist/tools/file-finding.tool.js.map +1 -1
- package/dist/tools/file-listing.tool.d.ts +7 -1
- package/dist/tools/file-listing.tool.d.ts.map +1 -1
- package/dist/tools/file-listing.tool.js +96 -80
- package/dist/tools/file-listing.tool.js.map +1 -1
- package/dist/tools/file-reading.tool.d.ts +4 -1
- package/dist/tools/file-reading.tool.d.ts.map +1 -1
- package/dist/tools/file-reading.tool.js +107 -45
- package/dist/tools/file-reading.tool.js.map +1 -1
- package/dist/tools/grep-content.tool.d.ts +13 -1
- package/dist/tools/grep-content.tool.d.ts.map +1 -1
- package/dist/tools/grep-content.tool.js +186 -144
- package/dist/tools/grep-content.tool.js.map +1 -1
- package/dist/utils/error-utils.d.ts +9 -0
- package/dist/utils/error-utils.d.ts.map +1 -0
- package/dist/utils/error-utils.js +61 -0
- package/dist/utils/error-utils.js.map +1 -0
- package/dist/utils/file-utils.d.ts +1 -0
- package/dist/utils/file-utils.d.ts.map +1 -1
- package/dist/utils/file-utils.js +81 -9
- package/dist/utils/file-utils.js.map +1 -1
- package/dist/utils/format-utils.d.ts +25 -0
- package/dist/utils/format-utils.d.ts.map +1 -0
- package/dist/utils/format-utils.js +111 -0
- package/dist/utils/format-utils.js.map +1 -0
- package/dist/utils/gitignore-service.d.ts +10 -0
- package/dist/utils/gitignore-service.d.ts.map +1 -0
- package/dist/utils/gitignore-service.js +91 -0
- package/dist/utils/gitignore-service.js.map +1 -0
- package/dist/utils/logger.d.ts +2 -2
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +35 -34
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/path-utils.js +3 -3
- package/dist/utils/path-utils.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/context-schema.ts +5 -2
- package/src/agents/react-agent.ts +667 -641
- package/src/agents/tool-runtime.ts +4 -4
- package/src/cli.ts +95 -57
- package/src/config.ts +192 -90
- package/src/index.ts +402 -180
- package/src/tools/file-finding.tool.ts +198 -310
- package/src/tools/file-listing.tool.ts +245 -202
- package/src/tools/file-reading.tool.ts +225 -138
- package/src/tools/grep-content.tool.ts +387 -307
- package/src/utils/error-utils.ts +95 -0
- package/src/utils/file-utils.ts +104 -19
- package/src/utils/format-utils.ts +190 -0
- package/src/utils/gitignore-service.ts +123 -0
- package/src/utils/logger.ts +112 -77
- package/src/utils/path-utils.ts +3 -3
|
@@ -1,96 +1,98 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { Readable } from "node:stream";
|
|
6
|
+
import { ChatAnthropic } from "@langchain/anthropic";
|
|
7
|
+
import { HumanMessage } from "@langchain/core/messages";
|
|
8
|
+
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
|
9
|
+
import { ChatOpenAI } from "@langchain/openai";
|
|
1
10
|
import {
|
|
2
|
-
createAgent,
|
|
3
11
|
anthropicPromptCachingMiddleware,
|
|
12
|
+
createAgent,
|
|
13
|
+
type DynamicStructuredTool,
|
|
4
14
|
todoListMiddleware,
|
|
5
|
-
tool as createTool,
|
|
6
|
-
type DynamicStructuredTool
|
|
7
15
|
} from "langchain";
|
|
8
16
|
import type { z } from "zod";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { ChatOpenAI } from "@langchain/openai";
|
|
14
|
-
import { ChatAnthropic } from "@langchain/anthropic";
|
|
15
|
-
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
|
16
|
-
import { HumanMessage } from "@langchain/core/messages";
|
|
17
|
+
import { findTool } from "../tools/file-finding.tool.js";
|
|
18
|
+
import { listTool } from "../tools/file-listing.tool.js";
|
|
19
|
+
import { viewTool } from "../tools/file-reading.tool.js";
|
|
20
|
+
import { grepTool } from "../tools/grep-content.tool.js";
|
|
17
21
|
import { logger } from "../utils/logger.js";
|
|
18
|
-
import os from "node:os";
|
|
19
|
-
import { mkdir, rm } from "node:fs/promises";
|
|
20
|
-
import path from "node:path";
|
|
21
|
-
import { spawn } from "node:child_process";
|
|
22
|
-
import { Readable } from "node:stream";
|
|
23
22
|
import type { AgentContext } from "./context-schema.js";
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
25
|
* Configuration interface for ReactAgent
|
|
27
26
|
*/
|
|
28
27
|
export interface ReactAgentConfig {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
28
|
+
/** AI provider configuration including type, API key, and optional model/base URL */
|
|
29
|
+
aiProvider: {
|
|
30
|
+
type:
|
|
31
|
+
| "openai"
|
|
32
|
+
| "anthropic"
|
|
33
|
+
| "google"
|
|
34
|
+
| "openai-compatible"
|
|
35
|
+
| "anthropic-compatible"
|
|
36
|
+
| "claude-code"
|
|
37
|
+
| "gemini-cli";
|
|
38
|
+
apiKey: string;
|
|
39
|
+
model?: string;
|
|
40
|
+
baseURL?: string;
|
|
41
|
+
};
|
|
42
|
+
/** Working directory where the agent operates */
|
|
43
|
+
workingDir: string;
|
|
44
|
+
/** Optional technology context for dynamic system prompt construction */
|
|
45
|
+
technology?: {
|
|
46
|
+
name: string;
|
|
47
|
+
repository: string;
|
|
48
|
+
branch: string;
|
|
49
|
+
};
|
|
50
|
+
/** Optional context schema for runtime context validation */
|
|
51
|
+
contextSchema?: z.ZodType | undefined;
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
export class ReactAgent {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
55
|
+
private readonly aiModel?:
|
|
56
|
+
| ChatOpenAI
|
|
57
|
+
| ChatAnthropic
|
|
58
|
+
| ChatGoogleGenerativeAI;
|
|
59
|
+
private readonly tools: DynamicStructuredTool[];
|
|
60
|
+
private agent?: ReturnType<typeof createAgent>;
|
|
61
|
+
private readonly config: ReactAgentConfig;
|
|
62
|
+
private readonly contextSchema?: z.ZodType | undefined;
|
|
63
|
+
|
|
64
|
+
constructor(config: ReactAgentConfig) {
|
|
65
|
+
this.config = config;
|
|
66
|
+
this.contextSchema = config.contextSchema;
|
|
67
|
+
|
|
68
|
+
if (
|
|
69
|
+
config.aiProvider.type !== "claude-code" &&
|
|
70
|
+
config.aiProvider.type !== "gemini-cli"
|
|
71
|
+
) {
|
|
72
|
+
this.aiModel = this.createAIModel(config.aiProvider);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Initialize tools - modernized tool pattern
|
|
76
|
+
this.tools = [listTool, viewTool, grepTool, findTool];
|
|
77
|
+
|
|
78
|
+
logger.info("AGENT", "Initializing ReactAgent", {
|
|
79
|
+
aiProviderType: config.aiProvider.type,
|
|
80
|
+
model: config.aiProvider.model,
|
|
81
|
+
workingDir: config.workingDir.replace(os.homedir(), "~"),
|
|
82
|
+
toolCount: this.tools.length,
|
|
83
|
+
hasContextSchema: !!this.contextSchema,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Creates a dynamic system prompt based on current configuration and technology context
|
|
89
|
+
* @returns A context-aware system prompt string
|
|
90
|
+
*/
|
|
91
|
+
createDynamicSystemPrompt(): string {
|
|
92
|
+
const { workingDir, technology } = this.config;
|
|
93
|
+
|
|
94
|
+
// Dynamic system prompt generation code
|
|
95
|
+
let prompt = `
|
|
94
96
|
You are a **Codebase Investigator** specializing in technology exploration and architectural analysis. Your core purpose is to provide deep technical insights grounded in actual source code evidence. You approach every question as an investigation, requiring verification before drawing conclusions.
|
|
95
97
|
|
|
96
98
|
**Your Key Traits:**
|
|
@@ -348,581 +350,605 @@ Remember: ALL tool calls MUST be executed using absolute path in \`[WORKING_DIRE
|
|
|
348
350
|
**Before responding to any user query, verify you have sufficient evidence to support your claims. When in doubt, read more files rather than speculate.**
|
|
349
351
|
`;
|
|
350
352
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
353
|
+
// Add technology context if available
|
|
354
|
+
if (technology) {
|
|
355
|
+
prompt = prompt.replace(
|
|
356
|
+
"<context_block>",
|
|
357
|
+
`You have been provided the **${technology.name}** repository.
|
|
356
358
|
Repository: ${technology.repository}
|
|
357
359
|
Your Working Directory: ${workingDir}
|
|
358
360
|
|
|
359
|
-
Remember that ALL tool calls MUST be executed using absolute path in \`${workingDir}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
Remember that ALL tool calls MUST be executed using absolute path in \`${workingDir}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
361
|
+
Remember that ALL tool calls MUST be executed using absolute path in \`${workingDir}\``
|
|
362
|
+
);
|
|
363
|
+
prompt = prompt.replace("</context_block>", "");
|
|
364
|
+
} else {
|
|
365
|
+
prompt = prompt.replace(
|
|
366
|
+
"<context_block>",
|
|
367
|
+
`You have been provided several related repositories to work with grouped in the following working directory: ${workingDir}
|
|
368
|
+
|
|
369
|
+
Remember that ALL tool calls MUST be executed using absolute path in \`${workingDir}\``
|
|
370
|
+
);
|
|
371
|
+
prompt = prompt.replace("</context_block>", "");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
logger.debug("AGENT", "Dynamic system prompt generated", {
|
|
375
|
+
hasTechnologyContext: !!technology,
|
|
376
|
+
promptLength: prompt.length,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
return prompt;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private async createGeminiTempDir(): Promise<string> {
|
|
383
|
+
const tempDir = path.join(os.tmpdir(), `librarian-gemini-${Date.now()}`);
|
|
384
|
+
await mkdir(tempDir, { recursive: true });
|
|
385
|
+
return tempDir;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private async setupGeminiConfig(
|
|
389
|
+
tempDir: string,
|
|
390
|
+
systemPrompt: string,
|
|
391
|
+
_model: string
|
|
392
|
+
): Promise<{ systemPromptPath: string; settingsPath: string }> {
|
|
393
|
+
const systemPromptPath = path.join(tempDir, "system.md");
|
|
394
|
+
const settingsPath = path.join(tempDir, "settings.json");
|
|
395
|
+
|
|
396
|
+
await Bun.write(systemPromptPath, systemPrompt);
|
|
397
|
+
|
|
398
|
+
const settings = {
|
|
399
|
+
tools: {
|
|
400
|
+
core: ["list_directory", "read_file", "glob", "search_file_content"],
|
|
401
|
+
autoAccept: true,
|
|
402
|
+
},
|
|
403
|
+
mcpServers: {},
|
|
404
|
+
mcp: {
|
|
405
|
+
excluded: ["*"],
|
|
406
|
+
},
|
|
407
|
+
experimental: {
|
|
408
|
+
enableAgents: false,
|
|
409
|
+
},
|
|
410
|
+
output: {
|
|
411
|
+
format: "json",
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
await Bun.write(settingsPath, JSON.stringify(settings, null, 2));
|
|
415
|
+
|
|
416
|
+
return { systemPromptPath, settingsPath };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private buildGeminiEnv(
|
|
420
|
+
tempDir: string,
|
|
421
|
+
model: string
|
|
422
|
+
): Record<string, string | undefined> {
|
|
423
|
+
const settingsPath = path.join(tempDir, "settings.json");
|
|
424
|
+
const systemPromptPath = path.join(tempDir, "system.md");
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
...Bun.env,
|
|
428
|
+
GEMINI_SYSTEM_MD: systemPromptPath,
|
|
429
|
+
GEMINI_CLI_SYSTEM_DEFAULTS_PATH: settingsPath,
|
|
430
|
+
GEMINI_CLI_SYSTEM_SETTINGS_PATH: settingsPath,
|
|
431
|
+
GEMINI_MODEL: model,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private async cleanupGeminiTempDir(tempDir: string): Promise<void> {
|
|
436
|
+
try {
|
|
437
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
438
|
+
} catch (err) {
|
|
439
|
+
logger.warn("AGENT", "Failed to cleanup Gemini temp files", {
|
|
440
|
+
error: err,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private async *streamClaudeCli(
|
|
446
|
+
query: string,
|
|
447
|
+
context?: AgentContext
|
|
448
|
+
): AsyncGenerator<string, void, unknown> {
|
|
449
|
+
const workingDir = context?.workingDir || this.config.workingDir;
|
|
450
|
+
const systemPrompt = this.createDynamicSystemPrompt();
|
|
451
|
+
|
|
452
|
+
const args = [
|
|
453
|
+
"-p",
|
|
454
|
+
query,
|
|
455
|
+
"--system-prompt",
|
|
456
|
+
systemPrompt,
|
|
457
|
+
"--tools",
|
|
458
|
+
"Read,Glob,Grep",
|
|
459
|
+
"--dangerously-skip-permissions",
|
|
460
|
+
"--output-format",
|
|
461
|
+
"stream-json",
|
|
462
|
+
];
|
|
463
|
+
|
|
464
|
+
const env = {
|
|
465
|
+
...Bun.env,
|
|
466
|
+
CLAUDE_PROJECT_DIR: workingDir,
|
|
467
|
+
...(this.config.aiProvider.model && {
|
|
468
|
+
ANTHROPIC_MODEL: this.config.aiProvider.model,
|
|
469
|
+
}),
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
logger.debug("AGENT", "Spawning Claude CLI", {
|
|
473
|
+
args: args.map((a) => (a.length > 100 ? `${a.substring(0, 100)}...` : a)),
|
|
474
|
+
workingDir,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const proc = spawn("claude", args, {
|
|
478
|
+
cwd: workingDir,
|
|
479
|
+
env,
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
let buffer = "";
|
|
483
|
+
|
|
484
|
+
if (!proc.stdout) {
|
|
485
|
+
throw new Error("Failed to capture Claude CLI output");
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const readable = Readable.from(proc.stdout);
|
|
489
|
+
|
|
490
|
+
for await (const chunk of readable) {
|
|
491
|
+
buffer += chunk.toString();
|
|
492
|
+
const lines = buffer.split("\n");
|
|
493
|
+
buffer = lines.pop() || "";
|
|
494
|
+
|
|
495
|
+
for (const line of lines) {
|
|
496
|
+
if (!line.trim()) {
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
try {
|
|
500
|
+
const data = JSON.parse(line);
|
|
501
|
+
// Filter for text content blocks in the stream
|
|
502
|
+
if (data.type === "text" && data.content) {
|
|
503
|
+
yield data.content;
|
|
504
|
+
} else if (data.type === "content_block_delta" && data.delta?.text) {
|
|
505
|
+
yield data.delta.text;
|
|
506
|
+
} else if (data.type === "message" && Array.isArray(data.content)) {
|
|
507
|
+
// Final message might come as a whole
|
|
508
|
+
for (const block of data.content) {
|
|
509
|
+
if (block.type === "text" && block.text) {
|
|
510
|
+
yield block.text;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
} catch {
|
|
515
|
+
// Silent fail for non-JSON or partial lines
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Wait for process to exit
|
|
521
|
+
await new Promise<void>((resolve, reject) => {
|
|
522
|
+
proc.on("exit", (code) => {
|
|
523
|
+
if (code === 0) {
|
|
524
|
+
resolve();
|
|
525
|
+
} else {
|
|
526
|
+
reject(new Error(`Claude CLI exited with code ${code}`));
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
proc.on("error", reject);
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
private async *streamGeminiCli(
|
|
534
|
+
query: string,
|
|
535
|
+
context?: AgentContext
|
|
536
|
+
): AsyncGenerator<string, void, unknown> {
|
|
537
|
+
const workingDir = context?.workingDir || this.config.workingDir;
|
|
538
|
+
const systemPrompt = this.createDynamicSystemPrompt();
|
|
539
|
+
|
|
540
|
+
const tempDir = await this.createGeminiTempDir();
|
|
541
|
+
const model = this.config.aiProvider.model || "gemini-2.5-flash";
|
|
542
|
+
|
|
543
|
+
try {
|
|
544
|
+
await this.setupGeminiConfig(tempDir, systemPrompt, model);
|
|
545
|
+
|
|
546
|
+
const args = [
|
|
547
|
+
"gemini",
|
|
548
|
+
"-p",
|
|
549
|
+
query,
|
|
550
|
+
"--output-format",
|
|
551
|
+
"stream-json",
|
|
552
|
+
"--yolo",
|
|
553
|
+
];
|
|
554
|
+
|
|
555
|
+
const env = this.buildGeminiEnv(tempDir, model);
|
|
556
|
+
|
|
557
|
+
logger.debug("AGENT", "Spawning Gemini CLI", {
|
|
558
|
+
args,
|
|
559
|
+
workingDir,
|
|
560
|
+
model,
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
const proc = Bun.spawn(args, {
|
|
564
|
+
cwd: workingDir,
|
|
565
|
+
env,
|
|
566
|
+
stdout: "pipe",
|
|
567
|
+
stderr: "pipe",
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
const reader = proc.stdout.getReader();
|
|
571
|
+
let buffer = "";
|
|
572
|
+
|
|
573
|
+
while (true) {
|
|
574
|
+
const { done, value } = await reader.read();
|
|
575
|
+
if (done) {
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
buffer += new TextDecoder().decode(value);
|
|
580
|
+
const lines = buffer.split("\n");
|
|
581
|
+
buffer = lines.pop() || "";
|
|
582
|
+
|
|
583
|
+
for (const line of lines) {
|
|
584
|
+
if (!line.trim()) {
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
try {
|
|
588
|
+
const data = JSON.parse(line);
|
|
589
|
+
const text = this.parseGeminiStreamLine(data);
|
|
590
|
+
if (text) {
|
|
591
|
+
yield text;
|
|
592
|
+
}
|
|
593
|
+
} catch {
|
|
594
|
+
// Silent fail for non-JSON or partial lines
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const exitCode = await proc.exited;
|
|
600
|
+
if (exitCode !== 0) {
|
|
601
|
+
throw new Error(`Gemini CLI exited with code ${exitCode}`);
|
|
602
|
+
}
|
|
603
|
+
} finally {
|
|
604
|
+
await this.cleanupGeminiTempDir(tempDir);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
private parseGeminiStreamLine(data: unknown): string | null {
|
|
609
|
+
if (
|
|
610
|
+
data &&
|
|
611
|
+
typeof data === "object" &&
|
|
612
|
+
"type" in data &&
|
|
613
|
+
"role" in data &&
|
|
614
|
+
"content" in data
|
|
615
|
+
) {
|
|
616
|
+
const typedData = data as { type: string; role: string; content: string };
|
|
617
|
+
if (
|
|
618
|
+
typedData.type === "message" &&
|
|
619
|
+
typedData.role === "assistant" &&
|
|
620
|
+
typedData.content
|
|
621
|
+
) {
|
|
622
|
+
return typedData.content;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
private createAIModel(
|
|
629
|
+
aiProvider: ReactAgentConfig["aiProvider"]
|
|
630
|
+
): ChatOpenAI | ChatAnthropic | ChatGoogleGenerativeAI {
|
|
631
|
+
const { type, apiKey, model, baseURL } = aiProvider;
|
|
632
|
+
|
|
633
|
+
logger.debug("AGENT", "Creating AI model instance", {
|
|
634
|
+
type,
|
|
635
|
+
model,
|
|
636
|
+
hasBaseURL: !!baseURL,
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
switch (type) {
|
|
640
|
+
case "openai":
|
|
641
|
+
return new ChatOpenAI({
|
|
642
|
+
apiKey,
|
|
643
|
+
modelName: model || "gpt-5.2",
|
|
644
|
+
});
|
|
645
|
+
case "openai-compatible":
|
|
646
|
+
return new ChatOpenAI({
|
|
647
|
+
apiKey,
|
|
648
|
+
modelName: model || "gpt-5.2",
|
|
649
|
+
configuration: {
|
|
650
|
+
baseURL: baseURL || "https://api.openai.com/v1",
|
|
651
|
+
},
|
|
652
|
+
});
|
|
653
|
+
case "anthropic":
|
|
654
|
+
return new ChatAnthropic({
|
|
655
|
+
apiKey,
|
|
656
|
+
modelName: model || "claude-sonnet-4-5",
|
|
657
|
+
});
|
|
658
|
+
case "anthropic-compatible":
|
|
659
|
+
if (!baseURL) {
|
|
660
|
+
throw new Error(
|
|
661
|
+
"baseURL is required for anthropic-compatible provider"
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
if (!model) {
|
|
665
|
+
throw new Error(
|
|
666
|
+
"model is required for anthropic-compatible provider"
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
return new ChatAnthropic({
|
|
670
|
+
apiKey,
|
|
671
|
+
modelName: model,
|
|
672
|
+
anthropicApiUrl: baseURL,
|
|
673
|
+
});
|
|
674
|
+
case "google":
|
|
675
|
+
return new ChatGoogleGenerativeAI({
|
|
676
|
+
apiKey,
|
|
677
|
+
model: model || "gemini-3-flash-preview",
|
|
678
|
+
});
|
|
679
|
+
default:
|
|
680
|
+
logger.error(
|
|
681
|
+
"AGENT",
|
|
682
|
+
"Unsupported AI provider type",
|
|
683
|
+
new Error(`Unsupported AI provider type: ${type}`),
|
|
684
|
+
{ type }
|
|
685
|
+
);
|
|
686
|
+
throw new Error(`Unsupported AI provider type: ${type}`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
initialize(): Promise<void> {
|
|
691
|
+
if (
|
|
692
|
+
this.config.aiProvider.type === "claude-code" ||
|
|
693
|
+
this.config.aiProvider.type === "gemini-cli"
|
|
694
|
+
) {
|
|
695
|
+
logger.info(
|
|
696
|
+
"AGENT",
|
|
697
|
+
`${this.config.aiProvider.type} CLI mode initialized (skipping LangChain setup)`
|
|
698
|
+
);
|
|
699
|
+
return Promise.resolve();
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (!this.aiModel) {
|
|
703
|
+
throw new Error("AI model not created for non-CLI provider");
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Create the agent using LangChain's createAgent function with dynamic system prompt
|
|
707
|
+
this.agent = createAgent({
|
|
708
|
+
model: this.aiModel,
|
|
709
|
+
tools: this.tools,
|
|
710
|
+
systemPrompt: this.createDynamicSystemPrompt(),
|
|
711
|
+
middleware: [
|
|
712
|
+
todoListMiddleware(),
|
|
713
|
+
...(this.config.aiProvider.type === "anthropic" ||
|
|
714
|
+
this.config.aiProvider.type === "anthropic-compatible"
|
|
715
|
+
? [anthropicPromptCachingMiddleware()]
|
|
716
|
+
: []),
|
|
717
|
+
],
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
logger.info("AGENT", "Agent initialized successfully", {
|
|
721
|
+
toolCount: this.tools.length,
|
|
722
|
+
hasContextSchema: !!this.contextSchema,
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
return Promise.resolve();
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Query repository with a given query and optional context
|
|
730
|
+
*
|
|
731
|
+
* @param repoPath - The repository path (deprecated, for compatibility)
|
|
732
|
+
* @param query - The query string
|
|
733
|
+
* @param context - Optional context object containing working directory and metadata
|
|
734
|
+
* @returns The agent's response as a string
|
|
735
|
+
*/
|
|
736
|
+
async queryRepository(
|
|
737
|
+
_repoPath: string,
|
|
738
|
+
query: string,
|
|
739
|
+
context?: AgentContext
|
|
740
|
+
): Promise<string> {
|
|
741
|
+
logger.info("AGENT", "Query started", {
|
|
742
|
+
queryLength: query.length,
|
|
743
|
+
hasContext: !!context,
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
if (this.config.aiProvider.type === "claude-code") {
|
|
747
|
+
let fullContent = "";
|
|
748
|
+
for await (const chunk of this.streamClaudeCli(query, context)) {
|
|
749
|
+
fullContent += chunk;
|
|
750
|
+
}
|
|
751
|
+
return fullContent;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (this.config.aiProvider.type === "gemini-cli") {
|
|
755
|
+
let fullContent = "";
|
|
756
|
+
for await (const chunk of this.streamGeminiCli(query, context)) {
|
|
757
|
+
fullContent += chunk;
|
|
758
|
+
}
|
|
759
|
+
return fullContent;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const timingId = logger.timingStart("agentQuery");
|
|
763
|
+
|
|
764
|
+
if (!this.agent) {
|
|
765
|
+
logger.error(
|
|
766
|
+
"AGENT",
|
|
767
|
+
"Agent not initialized",
|
|
768
|
+
new Error("Agent not initialized. Call initialize() first.")
|
|
769
|
+
);
|
|
770
|
+
throw new Error("Agent not initialized. Call initialize() first.");
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// Prepare the messages for the agent - system prompt already set during initialization
|
|
774
|
+
const messages = [new HumanMessage(query)];
|
|
775
|
+
|
|
776
|
+
logger.debug("AGENT", "Invoking agent with messages", {
|
|
777
|
+
messageCount: messages.length,
|
|
778
|
+
hasContext: !!context,
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
// Execute the agent with optional context
|
|
782
|
+
const result = await this.agent.invoke(
|
|
783
|
+
{
|
|
784
|
+
messages,
|
|
785
|
+
},
|
|
786
|
+
context ? { context, recursionLimit: 100 } : { recursionLimit: 100 }
|
|
787
|
+
);
|
|
788
|
+
|
|
789
|
+
// Extract the last message content from the state
|
|
790
|
+
const lastMessage = result.messages.at(-1);
|
|
791
|
+
const content =
|
|
792
|
+
typeof lastMessage.content === "string"
|
|
793
|
+
? lastMessage.content
|
|
794
|
+
: JSON.stringify(lastMessage.content);
|
|
795
|
+
|
|
796
|
+
logger.timingEnd(timingId, "AGENT", "Query completed");
|
|
797
|
+
logger.info("AGENT", "Query result received", {
|
|
798
|
+
responseLength: content.length,
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
return content;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Stream repository query with optional context
|
|
806
|
+
*
|
|
807
|
+
* @param repoPath - The repository path (deprecated, for compatibility)
|
|
808
|
+
* @param query - The query string
|
|
809
|
+
* @param context - Optional context object containing working directory and metadata
|
|
810
|
+
* @returns Async generator yielding string chunks
|
|
811
|
+
*/
|
|
812
|
+
async *streamRepository(
|
|
813
|
+
_repoPath: string,
|
|
814
|
+
query: string,
|
|
815
|
+
context?: AgentContext
|
|
816
|
+
): AsyncGenerator<string, void, unknown> {
|
|
817
|
+
logger.info("AGENT", "Stream started", {
|
|
818
|
+
queryLength: query.length,
|
|
819
|
+
hasContext: !!context,
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
if (this.config.aiProvider.type === "claude-code") {
|
|
823
|
+
yield* this.streamClaudeCli(query, context);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (this.config.aiProvider.type === "gemini-cli") {
|
|
828
|
+
yield* this.streamGeminiCli(query, context);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const timingId = logger.timingStart("agentStream");
|
|
833
|
+
|
|
834
|
+
if (!this.agent) {
|
|
835
|
+
logger.error(
|
|
836
|
+
"AGENT",
|
|
837
|
+
"Agent not initialized",
|
|
838
|
+
new Error("Agent not initialized. Call initialize() first.")
|
|
839
|
+
);
|
|
840
|
+
throw new Error("Agent not initialized. Call initialize() first.");
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const messages = [new HumanMessage(query)];
|
|
844
|
+
|
|
845
|
+
logger.debug("AGENT", "Invoking agent stream with messages", {
|
|
846
|
+
messageCount: messages.length,
|
|
847
|
+
hasContext: !!context,
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
const cleanup = () => {
|
|
851
|
+
// Signal interruption for potential future use
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
logger.debug("AGENT", "Setting up interruption handlers for streaming");
|
|
855
|
+
process.on("SIGINT", cleanup);
|
|
856
|
+
process.on("SIGTERM", cleanup);
|
|
857
|
+
|
|
858
|
+
try {
|
|
859
|
+
const result = await this.agent.invoke(
|
|
860
|
+
{ messages },
|
|
861
|
+
context ? { context, recursionLimit: 100 } : { recursionLimit: 100 }
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
const content = extractMessageContent(result);
|
|
865
|
+
if (content) {
|
|
866
|
+
yield content;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
yield "\n";
|
|
870
|
+
} catch (error) {
|
|
871
|
+
const errorMessage = getStreamingErrorMessage(error);
|
|
872
|
+
logger.error(
|
|
873
|
+
"AGENT",
|
|
874
|
+
"Streaming error",
|
|
875
|
+
error instanceof Error ? error : new Error(errorMessage)
|
|
876
|
+
);
|
|
877
|
+
yield `\n\n[Error: ${errorMessage}]`;
|
|
878
|
+
throw error;
|
|
879
|
+
} finally {
|
|
880
|
+
process.removeListener("SIGINT", cleanup);
|
|
881
|
+
process.removeListener("SIGTERM", cleanup);
|
|
882
|
+
logger.timingEnd(timingId, "AGENT", "Streaming completed");
|
|
883
|
+
}
|
|
884
|
+
}
|
|
867
885
|
}
|
|
868
886
|
|
|
869
887
|
/**
|
|
870
888
|
* Extract content from the last message in the result
|
|
871
889
|
*/
|
|
872
|
-
function extractMessageContent(result: {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
890
|
+
function extractMessageContent(result: {
|
|
891
|
+
messages?: Array<{ content: unknown }>;
|
|
892
|
+
}): string | null {
|
|
893
|
+
if (!result.messages || result.messages.length === 0) {
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const lastMessage = result.messages.at(-1);
|
|
898
|
+
if (!lastMessage?.content) {
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
const content = lastMessage.content;
|
|
903
|
+
if (typeof content === "string") {
|
|
904
|
+
return content;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
if (Array.isArray(content)) {
|
|
908
|
+
const parts: string[] = [];
|
|
909
|
+
for (const block of content) {
|
|
910
|
+
if (block && typeof block === "object") {
|
|
911
|
+
const blockObj = block as { type?: string; text?: unknown };
|
|
912
|
+
if (blockObj.type === "text" && typeof blockObj.text === "string") {
|
|
913
|
+
parts.push(blockObj.text);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return parts.length > 0 ? parts.join("") : null;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
return null;
|
|
901
921
|
}
|
|
902
922
|
|
|
903
923
|
/**
|
|
904
924
|
* Get user-friendly error message for streaming errors
|
|
905
925
|
*/
|
|
906
926
|
function getStreamingErrorMessage(error: unknown): string {
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
927
|
+
if (!(error instanceof Error)) {
|
|
928
|
+
return "Unknown streaming error";
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (error.message.includes("timeout")) {
|
|
932
|
+
return "Streaming timeout - request took too long to complete";
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
if (
|
|
936
|
+
error.message.includes("network") ||
|
|
937
|
+
error.message.includes("ENOTFOUND")
|
|
938
|
+
) {
|
|
939
|
+
return "Network error - unable to connect to AI provider";
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
if (error.message.includes("rate limit")) {
|
|
943
|
+
return "Rate limit exceeded - please try again later";
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
if (
|
|
947
|
+
error.message.includes("authentication") ||
|
|
948
|
+
error.message.includes("unauthorized")
|
|
949
|
+
) {
|
|
950
|
+
return "Authentication error - check your API credentials";
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
return `Streaming error: ${error.message}`;
|
|
928
954
|
}
|