@element47/ag 4.4.5 → 4.5.2
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 +106 -4
- package/dist/cli/repl.d.ts +7 -2
- package/dist/cli/repl.d.ts.map +1 -1
- package/dist/cli/repl.js +390 -95
- package/dist/cli/repl.js.map +1 -1
- package/dist/cli.js +18 -8
- package/dist/cli.js.map +1 -1
- package/dist/core/__tests__/agent.test.js +42 -1
- package/dist/core/__tests__/agent.test.js.map +1 -1
- package/dist/core/__tests__/context.test.js +37 -0
- package/dist/core/__tests__/context.test.js.map +1 -1
- package/dist/core/__tests__/guardrails.test.d.ts +2 -0
- package/dist/core/__tests__/guardrails.test.d.ts.map +1 -0
- package/dist/core/__tests__/guardrails.test.js +400 -0
- package/dist/core/__tests__/guardrails.test.js.map +1 -0
- package/dist/core/__tests__/permission-manager.test.d.ts +2 -0
- package/dist/core/__tests__/permission-manager.test.d.ts.map +1 -0
- package/dist/core/__tests__/permission-manager.test.js +246 -0
- package/dist/core/__tests__/permission-manager.test.js.map +1 -0
- package/dist/core/__tests__/streaming.test.js +8 -1
- package/dist/core/__tests__/streaming.test.js.map +1 -1
- package/dist/core/agent.d.ts +20 -2
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +259 -82
- package/dist/core/agent.js.map +1 -1
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +17 -2
- package/dist/core/context.js.map +1 -1
- package/dist/core/guardrails.d.ts +32 -0
- package/dist/core/guardrails.d.ts.map +1 -0
- package/dist/core/guardrails.js +149 -0
- package/dist/core/guardrails.js.map +1 -0
- package/dist/core/loader.d.ts +6 -2
- package/dist/core/loader.d.ts.map +1 -1
- package/dist/core/loader.js +23 -9
- package/dist/core/loader.js.map +1 -1
- package/dist/core/permissions.d.ts +60 -0
- package/dist/core/permissions.d.ts.map +1 -0
- package/dist/core/permissions.js +252 -0
- package/dist/core/permissions.js.map +1 -0
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js +16 -0
- package/dist/core/registry.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +26 -3
- package/dist/core/skills.js.map +1 -1
- package/dist/core/types.d.ts +20 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/memory/__tests__/memory.test.js +33 -3
- package/dist/memory/__tests__/memory.test.js.map +1 -1
- package/dist/memory/memory.d.ts.map +1 -1
- package/dist/memory/memory.js +25 -3
- package/dist/memory/memory.js.map +1 -1
- package/package.json +2 -2
package/dist/core/agent.js
CHANGED
|
@@ -118,6 +118,16 @@ export function truncateToolResult(result) {
|
|
|
118
118
|
const omitted = lines.length - TRUNCATION_HEAD_LINES - TRUNCATION_TAIL_LINES;
|
|
119
119
|
return [...head, `\n... [${omitted} lines truncated] ...\n`, ...tail].join('\n');
|
|
120
120
|
}
|
|
121
|
+
/** Yield promise results as they resolve (like Promise.all but streaming) */
|
|
122
|
+
export async function* raceAll(promises) {
|
|
123
|
+
const wrapped = promises.map((p, i) => p.then(v => ({ i, v })));
|
|
124
|
+
const settled = new Set();
|
|
125
|
+
while (settled.size < promises.length) {
|
|
126
|
+
const result = await Promise.race(wrapped.filter((_, idx) => !settled.has(idx)));
|
|
127
|
+
settled.add(result.i);
|
|
128
|
+
yield result.v;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
121
131
|
export class Agent {
|
|
122
132
|
apiKey;
|
|
123
133
|
model;
|
|
@@ -133,6 +143,7 @@ export class Agent {
|
|
|
133
143
|
cachedCatalog = '';
|
|
134
144
|
cachedAlwaysOn = '';
|
|
135
145
|
builtinToolNames = new Set();
|
|
146
|
+
toolFailures;
|
|
136
147
|
contextTracker;
|
|
137
148
|
compactionInProgress = false;
|
|
138
149
|
confirmToolCall;
|
|
@@ -178,6 +189,7 @@ export class Agent {
|
|
|
178
189
|
- Be concise. Short responses, no filler, no trailing summaries of what you just did.
|
|
179
190
|
- When referencing code, include the file path and relevant context.
|
|
180
191
|
- Only use markdown formatting when it aids clarity.
|
|
192
|
+
- Always follow instructions in <global-memory> — these are persistent user preferences that override defaults.
|
|
181
193
|
|
|
182
194
|
# Verification
|
|
183
195
|
- After making changes, verify they work: run tests, check for syntax errors, or start the dev server as appropriate.
|
|
@@ -211,6 +223,7 @@ export class Agent {
|
|
|
211
223
|
this.builtinToolNames = new Set(this.tools.keys());
|
|
212
224
|
for (const t of config.extraTools ?? [])
|
|
213
225
|
this.addTool(t);
|
|
226
|
+
this.toolFailures = config.toolFailures ?? [];
|
|
214
227
|
// Context window tracking
|
|
215
228
|
this.contextTracker = new ContextTracker(this.model);
|
|
216
229
|
// Cache context and skill catalog (invalidated on skill activation/refresh)
|
|
@@ -282,6 +295,24 @@ export class Agent {
|
|
|
282
295
|
}
|
|
283
296
|
return parts.join('\n\n');
|
|
284
297
|
}
|
|
298
|
+
/** Build the JSON request body for chat completions, with prompt caching for supported models */
|
|
299
|
+
buildRequestBody(stream) {
|
|
300
|
+
const body = {
|
|
301
|
+
model: this.model,
|
|
302
|
+
messages: [{ role: 'system', content: this.systemPrompt }, ...this.messages],
|
|
303
|
+
tools: Array.from(this.tools.values()).map(t => ({ type: t.type, function: t.function })),
|
|
304
|
+
tool_choice: 'auto',
|
|
305
|
+
};
|
|
306
|
+
if (stream) {
|
|
307
|
+
body.stream = true;
|
|
308
|
+
body.stream_options = { include_usage: true };
|
|
309
|
+
}
|
|
310
|
+
// Enable prompt caching for Anthropic models (top-level cache_control)
|
|
311
|
+
if (this.model.startsWith('anthropic/') || this.model.includes('claude')) {
|
|
312
|
+
body.cache_control = { type: 'ephemeral' };
|
|
313
|
+
}
|
|
314
|
+
return body;
|
|
315
|
+
}
|
|
285
316
|
async activateSkill(name) {
|
|
286
317
|
const skill = this.allSkills.find(s => s.name === name);
|
|
287
318
|
if (!skill)
|
|
@@ -368,7 +399,7 @@ export class Agent {
|
|
|
368
399
|
stopSpinner();
|
|
369
400
|
}
|
|
370
401
|
}
|
|
371
|
-
async chat(content) {
|
|
402
|
+
async chat(content, signal) {
|
|
372
403
|
const userMessage = { role: 'user', content };
|
|
373
404
|
this.messages.push(userMessage);
|
|
374
405
|
appendHistory(userMessage, this.cwd);
|
|
@@ -377,6 +408,10 @@ export class Agent {
|
|
|
377
408
|
this.messages = this.messages.slice(-MAX_MESSAGES);
|
|
378
409
|
}
|
|
379
410
|
for (let i = 0; i < this.maxIterations; i++) {
|
|
411
|
+
if (signal?.aborted) {
|
|
412
|
+
this.messages.push({ role: 'assistant', content: '[interrupted by user]' });
|
|
413
|
+
return '[interrupted by user]';
|
|
414
|
+
}
|
|
380
415
|
const iterLabel = this.maxIterations > 1 ? ` [${i + 1}/${this.maxIterations}]` : '';
|
|
381
416
|
const stopSpinner = startSpinner(`thinking${iterLabel}`);
|
|
382
417
|
let msg;
|
|
@@ -384,12 +419,8 @@ export class Agent {
|
|
|
384
419
|
const res = await fetch(`${this.baseURL}/chat/completions`, {
|
|
385
420
|
method: 'POST',
|
|
386
421
|
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}` },
|
|
387
|
-
body: JSON.stringify(
|
|
388
|
-
|
|
389
|
-
messages: [{ role: 'system', content: this.systemPrompt }, ...this.messages],
|
|
390
|
-
tools: Array.from(this.tools.values()).map(t => ({ type: t.type, function: t.function })),
|
|
391
|
-
tool_choice: 'auto'
|
|
392
|
-
})
|
|
422
|
+
body: JSON.stringify(this.buildRequestBody(false)),
|
|
423
|
+
signal
|
|
393
424
|
});
|
|
394
425
|
if (!res.ok)
|
|
395
426
|
throw new Error(`API ${res.status}: ${await res.text()}`);
|
|
@@ -431,11 +462,28 @@ export class Agent {
|
|
|
431
462
|
}
|
|
432
463
|
}
|
|
433
464
|
this.messages.push(msg);
|
|
465
|
+
appendHistory(msg, this.cwd);
|
|
434
466
|
if (!msg.tool_calls?.length) {
|
|
435
|
-
appendHistory({ role: 'assistant', content: msg.content || '' }, this.cwd);
|
|
436
467
|
return msg.content || '';
|
|
437
468
|
}
|
|
438
|
-
//
|
|
469
|
+
// Permission checks — run sequentially so prompts don't overlap
|
|
470
|
+
const permissionDecisions = new Map();
|
|
471
|
+
for (const call of msg.tool_calls) {
|
|
472
|
+
const tool = this.tools.get(call.function.name);
|
|
473
|
+
if (!tool)
|
|
474
|
+
continue;
|
|
475
|
+
let args;
|
|
476
|
+
try {
|
|
477
|
+
args = JSON.parse(call.function.arguments || '{}');
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (this.confirmToolCall && !isReadOnlyToolCall(call.function.name, args)) {
|
|
483
|
+
permissionDecisions.set(call.id, await this.confirmToolCall(call.function.name, args, tool.permissionKey));
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
// Execute tool calls in parallel (permissions already resolved)
|
|
439
487
|
const toolPromises = msg.tool_calls.map(async (call) => {
|
|
440
488
|
const tool = this.tools.get(call.function.name);
|
|
441
489
|
if (!tool) {
|
|
@@ -448,12 +496,8 @@ export class Agent {
|
|
|
448
496
|
catch {
|
|
449
497
|
return { call, content: 'Error: malformed tool arguments' };
|
|
450
498
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const decision = await this.confirmToolCall(call.function.name, args);
|
|
454
|
-
if (decision === 'deny') {
|
|
455
|
-
return { call, content: 'Tool call denied by user.' };
|
|
456
|
-
}
|
|
499
|
+
if (permissionDecisions.get(call.id) === 'deny') {
|
|
500
|
+
return { call, content: 'Tool call denied by user.' };
|
|
457
501
|
}
|
|
458
502
|
const summary = args.command ?? args.action ?? JSON.stringify(args).slice(0, 80);
|
|
459
503
|
const stopToolSpinner = startSpinner(`[${call.function.name}] ${summary}`);
|
|
@@ -477,11 +521,12 @@ export class Agent {
|
|
|
477
521
|
const results = await Promise.all(toolPromises);
|
|
478
522
|
for (const { call, content } of results) {
|
|
479
523
|
this.messages.push({ role: 'tool', tool_call_id: call.id, content });
|
|
524
|
+
appendHistory({ role: 'tool', tool_call_id: call.id, content }, this.cwd);
|
|
480
525
|
}
|
|
481
526
|
}
|
|
482
527
|
return MAX_ITERATIONS_REACHED;
|
|
483
528
|
}
|
|
484
|
-
async *chatStream(content) {
|
|
529
|
+
async *chatStream(content, signal) {
|
|
485
530
|
const userMessage = { role: 'user', content };
|
|
486
531
|
this.messages.push(userMessage);
|
|
487
532
|
appendHistory(userMessage, this.cwd);
|
|
@@ -489,6 +534,12 @@ export class Agent {
|
|
|
489
534
|
this.messages = this.messages.slice(-MAX_MESSAGES);
|
|
490
535
|
}
|
|
491
536
|
for (let i = 0; i < this.maxIterations; i++) {
|
|
537
|
+
// ── Abort check: top of iteration ──
|
|
538
|
+
if (signal?.aborted) {
|
|
539
|
+
this.messages.push({ role: 'assistant', content: '[interrupted by user]' });
|
|
540
|
+
yield { type: 'interrupted' };
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
492
543
|
const iterLabel = this.maxIterations > 1 ? ` [${i + 1}/${this.maxIterations}]` : '';
|
|
493
544
|
yield { type: 'thinking', content: `thinking${iterLabel}` };
|
|
494
545
|
// Compact if needed
|
|
@@ -513,17 +564,24 @@ export class Agent {
|
|
|
513
564
|
this.compactionInProgress = false;
|
|
514
565
|
}
|
|
515
566
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
567
|
+
// ── API call with abort signal ──
|
|
568
|
+
let res;
|
|
569
|
+
try {
|
|
570
|
+
res = await fetch(`${this.baseURL}/chat/completions`, {
|
|
571
|
+
method: 'POST',
|
|
572
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}` },
|
|
573
|
+
body: JSON.stringify(this.buildRequestBody(true)),
|
|
574
|
+
signal
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
catch (e) {
|
|
578
|
+
if (e instanceof DOMException && e.name === 'AbortError') {
|
|
579
|
+
this.messages.push({ role: 'assistant', content: '[interrupted by user]' });
|
|
580
|
+
yield { type: 'interrupted' };
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
throw e;
|
|
584
|
+
}
|
|
527
585
|
if (!res.ok)
|
|
528
586
|
throw new Error(`API ${res.status}: ${await res.text()}`);
|
|
529
587
|
if (!res.body)
|
|
@@ -532,62 +590,89 @@ export class Agent {
|
|
|
532
590
|
let assistantContent = '';
|
|
533
591
|
const toolCalls = new Map();
|
|
534
592
|
let usage = null;
|
|
593
|
+
let streamAborted = false;
|
|
535
594
|
const reader = res.body.getReader();
|
|
536
595
|
const decoder = new TextDecoder();
|
|
537
596
|
let buffer = '';
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
const lines = buffer.split('\n');
|
|
544
|
-
buffer = lines.pop() || '';
|
|
545
|
-
for (const line of lines) {
|
|
546
|
-
if (!line.startsWith('data: '))
|
|
547
|
-
continue;
|
|
548
|
-
const data = line.slice(6).trim();
|
|
549
|
-
if (data === '[DONE]')
|
|
550
|
-
continue;
|
|
551
|
-
let parsed;
|
|
552
|
-
try {
|
|
553
|
-
parsed = JSON.parse(data);
|
|
554
|
-
}
|
|
555
|
-
catch {
|
|
556
|
-
continue;
|
|
597
|
+
try {
|
|
598
|
+
while (true) {
|
|
599
|
+
if (signal?.aborted) {
|
|
600
|
+
streamAborted = true;
|
|
601
|
+
break;
|
|
557
602
|
}
|
|
558
|
-
const
|
|
559
|
-
if (
|
|
603
|
+
const { done, value } = await reader.read();
|
|
604
|
+
if (done)
|
|
605
|
+
break;
|
|
606
|
+
buffer += decoder.decode(value, { stream: true });
|
|
607
|
+
const lines = buffer.split('\n');
|
|
608
|
+
buffer = lines.pop() || '';
|
|
609
|
+
for (const line of lines) {
|
|
610
|
+
if (!line.startsWith('data: '))
|
|
611
|
+
continue;
|
|
612
|
+
const data = line.slice(6).trim();
|
|
613
|
+
if (data === '[DONE]')
|
|
614
|
+
continue;
|
|
615
|
+
let parsed;
|
|
616
|
+
try {
|
|
617
|
+
parsed = JSON.parse(data);
|
|
618
|
+
}
|
|
619
|
+
catch {
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
// Capture usage from any chunk that has it (may coexist with delta)
|
|
560
623
|
if (parsed.usage)
|
|
561
624
|
usage = parsed.usage;
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
// Tool calls (streamed incrementally)
|
|
570
|
-
if (delta.tool_calls) {
|
|
571
|
-
// Signal spinner on first tool_call delta so the user sees activity
|
|
572
|
-
if (toolCalls.size === 0) {
|
|
573
|
-
yield { type: 'thinking', content: 'running tools' };
|
|
625
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
626
|
+
if (!delta)
|
|
627
|
+
continue;
|
|
628
|
+
// Text content
|
|
629
|
+
if (delta.content) {
|
|
630
|
+
assistantContent += delta.content;
|
|
631
|
+
yield { type: 'text', content: delta.content };
|
|
574
632
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
633
|
+
// Tool calls (streamed incrementally)
|
|
634
|
+
if (delta.tool_calls) {
|
|
635
|
+
for (const tc of delta.tool_calls) {
|
|
636
|
+
const idx = tc.index ?? 0;
|
|
637
|
+
if (!toolCalls.has(idx)) {
|
|
638
|
+
toolCalls.set(idx, { id: tc.id || '', name: tc.function?.name || '', arguments: '' });
|
|
639
|
+
}
|
|
640
|
+
const entry = toolCalls.get(idx);
|
|
641
|
+
if (tc.id)
|
|
642
|
+
entry.id = tc.id;
|
|
643
|
+
if (tc.function?.name) {
|
|
644
|
+
entry.name = tc.function.name;
|
|
645
|
+
// Show tool names as they arrive (replaces spinner in real-time)
|
|
646
|
+
const names = [...toolCalls.values()].map(t => t.name).filter(Boolean);
|
|
647
|
+
yield { type: 'thinking', content: `preparing ${names.join(', ')}` };
|
|
648
|
+
}
|
|
649
|
+
if (tc.function?.arguments)
|
|
650
|
+
entry.arguments += tc.function.arguments;
|
|
579
651
|
}
|
|
580
|
-
const entry = toolCalls.get(idx);
|
|
581
|
-
if (tc.id)
|
|
582
|
-
entry.id = tc.id;
|
|
583
|
-
if (tc.function?.name)
|
|
584
|
-
entry.name = tc.function.name;
|
|
585
|
-
if (tc.function?.arguments)
|
|
586
|
-
entry.arguments += tc.function.arguments;
|
|
587
652
|
}
|
|
588
653
|
}
|
|
589
654
|
}
|
|
590
655
|
}
|
|
656
|
+
catch (e) {
|
|
657
|
+
if (e instanceof DOMException && e.name === 'AbortError') {
|
|
658
|
+
streamAborted = true;
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
throw e;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
finally {
|
|
665
|
+
try {
|
|
666
|
+
reader.cancel();
|
|
667
|
+
}
|
|
668
|
+
catch { /* already closed */ }
|
|
669
|
+
}
|
|
670
|
+
// If aborted during streaming, discard partial message
|
|
671
|
+
if (streamAborted) {
|
|
672
|
+
this.messages.push({ role: 'assistant', content: '[interrupted by user]' });
|
|
673
|
+
yield { type: 'interrupted' };
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
591
676
|
if (usage)
|
|
592
677
|
this.contextTracker.update(usage);
|
|
593
678
|
// Build assistant message
|
|
@@ -600,8 +685,8 @@ export class Agent {
|
|
|
600
685
|
}));
|
|
601
686
|
}
|
|
602
687
|
this.messages.push(msg);
|
|
688
|
+
appendHistory(msg, this.cwd);
|
|
603
689
|
if (!msg.tool_calls?.length) {
|
|
604
|
-
appendHistory({ role: 'assistant', content: assistantContent }, this.cwd);
|
|
605
690
|
yield { type: 'done', content: assistantContent };
|
|
606
691
|
return;
|
|
607
692
|
}
|
|
@@ -616,8 +701,29 @@ export class Agent {
|
|
|
616
701
|
const summary = args.command ?? args.action ?? call.function.name;
|
|
617
702
|
yield { type: 'tool_start', toolName: call.function.name, toolCallId: call.id, content: summary };
|
|
618
703
|
}
|
|
619
|
-
//
|
|
704
|
+
// Permission checks — run sequentially so prompts don't overlap
|
|
705
|
+
const permissionDecisions = new Map();
|
|
706
|
+
for (const call of msg.tool_calls) {
|
|
707
|
+
if (signal?.aborted)
|
|
708
|
+
break;
|
|
709
|
+
const tool = this.tools.get(call.function.name);
|
|
710
|
+
if (!tool)
|
|
711
|
+
continue;
|
|
712
|
+
let args;
|
|
713
|
+
try {
|
|
714
|
+
args = JSON.parse(call.function.arguments || '{}');
|
|
715
|
+
}
|
|
716
|
+
catch {
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
if (this.confirmToolCall && !isReadOnlyToolCall(call.function.name, args)) {
|
|
720
|
+
permissionDecisions.set(call.id, await this.confirmToolCall(call.function.name, args, tool.permissionKey));
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
// Execute tool calls — yield results as they complete (not Promise.all)
|
|
620
724
|
const execPromises = msg.tool_calls.map(async (call) => {
|
|
725
|
+
if (signal?.aborted)
|
|
726
|
+
return { call, content: '[cancelled by user]', isError: true };
|
|
621
727
|
const tool = this.tools.get(call.function.name);
|
|
622
728
|
if (!tool)
|
|
623
729
|
return { call, content: `Error: unknown tool "${call.function.name}"`, isError: true };
|
|
@@ -628,12 +734,8 @@ export class Agent {
|
|
|
628
734
|
catch {
|
|
629
735
|
return { call, content: 'Error: malformed tool arguments', isError: true };
|
|
630
736
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const decision = await this.confirmToolCall(call.function.name, args);
|
|
634
|
-
if (decision === 'deny') {
|
|
635
|
-
return { call, content: 'Tool call denied by user.', isError: true };
|
|
636
|
-
}
|
|
737
|
+
if (permissionDecisions.get(call.id) === 'deny') {
|
|
738
|
+
return { call, content: 'Tool call denied by user.', isError: true };
|
|
637
739
|
}
|
|
638
740
|
try {
|
|
639
741
|
const rawResult = await tool.execute(args);
|
|
@@ -645,18 +747,89 @@ export class Agent {
|
|
|
645
747
|
return { call, content: `Tool error: ${error}`, isError: true };
|
|
646
748
|
}
|
|
647
749
|
});
|
|
648
|
-
|
|
649
|
-
|
|
750
|
+
// Yield results as they resolve (not waiting for all)
|
|
751
|
+
const completedCallIds = new Set();
|
|
752
|
+
for await (const r of raceAll(execPromises)) {
|
|
753
|
+
completedCallIds.add(r.call.id);
|
|
650
754
|
this.messages.push({ role: 'tool', tool_call_id: r.call.id, content: r.content });
|
|
755
|
+
appendHistory({ role: 'tool', tool_call_id: r.call.id, content: r.content }, this.cwd);
|
|
651
756
|
yield { type: 'tool_end', toolName: r.call.function.name, toolCallId: r.call.id, content: r.content, success: !r.isError };
|
|
757
|
+
// Check abort after each tool completes
|
|
758
|
+
if (signal?.aborted)
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
// Fill placeholders for any tool calls that didn't complete (API requires all tool_call_ids)
|
|
762
|
+
if (signal?.aborted && msg.tool_calls) {
|
|
763
|
+
for (const call of msg.tool_calls) {
|
|
764
|
+
if (!completedCallIds.has(call.id)) {
|
|
765
|
+
const placeholder = { role: 'tool', tool_call_id: call.id, content: '[cancelled by user]' };
|
|
766
|
+
this.messages.push(placeholder);
|
|
767
|
+
appendHistory(placeholder, this.cwd);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
this.messages.push({ role: 'assistant', content: '[interrupted by user]' });
|
|
771
|
+
yield { type: 'interrupted', content: `${completedCallIds.size} completed, ${msg.tool_calls.length - completedCallIds.size} cancelled` };
|
|
772
|
+
return;
|
|
652
773
|
}
|
|
653
774
|
}
|
|
654
775
|
yield { type: 'max_iterations' };
|
|
655
776
|
}
|
|
656
777
|
// ── Public API ─────────────────────────────────────────────────────────────
|
|
778
|
+
/** Return the char-size breakdown of every context component */
|
|
779
|
+
getContextBreakdown() {
|
|
780
|
+
const parts = [];
|
|
781
|
+
parts.push({ label: 'System prompt', chars: this.baseSystemPrompt.length });
|
|
782
|
+
parts.push({ label: 'Environment', chars: getEnvironmentContext(this.cwd).length });
|
|
783
|
+
const listing = this.getProjectListing();
|
|
784
|
+
if (listing)
|
|
785
|
+
parts.push({ label: 'Project files', chars: listing.length });
|
|
786
|
+
// cachedContext contains global memory + project memory + plan, but we want them separate
|
|
787
|
+
const globalMem = loadGlobalMemory(this.cwd);
|
|
788
|
+
const projectMem = loadProjectMemory(this.cwd);
|
|
789
|
+
const plan = loadPlan(this.cwd);
|
|
790
|
+
if (globalMem)
|
|
791
|
+
parts.push({ label: 'Global memory', chars: globalMem.length + '<global-memory>\n\n</global-memory>'.length });
|
|
792
|
+
if (projectMem)
|
|
793
|
+
parts.push({ label: 'Project memory', chars: projectMem.length + '<project-memory>\n\n</project-memory>'.length });
|
|
794
|
+
if (plan)
|
|
795
|
+
parts.push({ label: 'Plan', chars: plan.length + 50 /* tags + header */ });
|
|
796
|
+
if (this.cachedCatalog)
|
|
797
|
+
parts.push({ label: 'Skill catalog', chars: this.cachedCatalog.length });
|
|
798
|
+
if (this.cachedAlwaysOn)
|
|
799
|
+
parts.push({ label: 'Always-on skills', chars: this.cachedAlwaysOn.length });
|
|
800
|
+
if (this.activeSkillContent.length > 0) {
|
|
801
|
+
parts.push({ label: 'Active skills', chars: this.activeSkillContent.join('\n\n').length });
|
|
802
|
+
}
|
|
803
|
+
// Tool definitions (sent in every API call as the tools array)
|
|
804
|
+
const builtinChars = Array.from(this.tools.values())
|
|
805
|
+
.filter(t => this.builtinToolNames.has(t.function.name))
|
|
806
|
+
.reduce((sum, t) => sum + JSON.stringify({ type: t.type, function: t.function }).length, 0);
|
|
807
|
+
const customChars = Array.from(this.tools.values())
|
|
808
|
+
.filter(t => !this.builtinToolNames.has(t.function.name))
|
|
809
|
+
.reduce((sum, t) => sum + JSON.stringify({ type: t.type, function: t.function }).length, 0);
|
|
810
|
+
if (builtinChars > 0)
|
|
811
|
+
parts.push({ label: 'Tool definitions', chars: builtinChars });
|
|
812
|
+
if (customChars > 0)
|
|
813
|
+
parts.push({ label: 'Custom tools', chars: customChars });
|
|
814
|
+
// Messages (conversation history)
|
|
815
|
+
const msgChars = this.messages.reduce((sum, m) => {
|
|
816
|
+
let size = (m.content || '').length;
|
|
817
|
+
if (m.tool_calls)
|
|
818
|
+
size += JSON.stringify(m.tool_calls).length;
|
|
819
|
+
return sum + size;
|
|
820
|
+
}, 0);
|
|
821
|
+
if (msgChars > 0)
|
|
822
|
+
parts.push({ label: 'Messages', chars: msgChars });
|
|
823
|
+
return parts;
|
|
824
|
+
}
|
|
657
825
|
getTools() {
|
|
658
|
-
return Array.from(this.tools.values()).map(t => ({
|
|
826
|
+
return Array.from(this.tools.values()).map(t => ({
|
|
827
|
+
name: t.function.name,
|
|
828
|
+
description: t.function.description,
|
|
829
|
+
isBuiltin: this.builtinToolNames.has(t.function.name),
|
|
830
|
+
}));
|
|
659
831
|
}
|
|
832
|
+
getToolFailures() { return this.toolFailures; }
|
|
660
833
|
getModel() { return this.model; }
|
|
661
834
|
getBaseURL() { return this.baseURL; }
|
|
662
835
|
setModel(model) { this.model = model; this.contextTracker = new ContextTracker(model); }
|
|
@@ -708,5 +881,9 @@ export class Agent {
|
|
|
708
881
|
getContextDetails() { return this.contextTracker.formatDetailed(); }
|
|
709
882
|
getContextTracker() { return this.contextTracker; }
|
|
710
883
|
getSystemPromptSize() { return this.systemPrompt.length; }
|
|
884
|
+
/** Total estimated chars across all context components (system prompt + tools + messages) */
|
|
885
|
+
getTotalContextChars() {
|
|
886
|
+
return this.getContextBreakdown().reduce((sum, p) => sum + p.chars, 0);
|
|
887
|
+
}
|
|
711
888
|
}
|
|
712
889
|
//# sourceMappingURL=agent.js.map
|