@inixiative/foundry-core 0.2.0 → 0.3.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/package.json +1 -6
- package/src/action-prompt.ts +47 -28
- package/src/adapters/file-memory.ts +314 -122
- package/src/adapters/http-memory.ts +19 -24
- package/src/adapters/index.ts +4 -4
- package/src/adapters/markdown-docs.ts +15 -15
- package/src/adapters/sqlite-memory.ts +29 -27
- package/src/base-agent.ts +5 -7
- package/src/cache-lifecycle.ts +14 -14
- package/src/capability.ts +76 -83
- package/src/clarifier.ts +46 -0
- package/src/classifier.ts +6 -9
- package/src/context-layer.ts +125 -78
- package/src/context-stack.ts +15 -14
- package/src/credentials.ts +4 -1
- package/src/decider.ts +7 -8
- package/src/delivery-evidence.ts +206 -55
- package/src/event-stream.ts +34 -20
- package/src/executor.ts +81 -43
- package/src/harness.ts +225 -126
- package/src/hooks.ts +26 -26
- package/src/hydrator.ts +6 -8
- package/src/id.ts +3 -3
- package/src/index.ts +248 -218
- package/src/intervention.ts +21 -12
- package/src/messages.ts +73 -62
- package/src/middleware.ts +9 -19
- package/src/model-registry.ts +1587 -0
- package/src/native-evidence.ts +124 -30
- package/src/permission-middleware.ts +16 -28
- package/src/retry.ts +6 -6
- package/src/router.ts +5 -5
- package/src/signal.ts +16 -11
- package/src/thread.ts +138 -61
- package/src/token-counts.ts +9 -1
- package/src/token-tracker.ts +43 -34
- package/src/tools.ts +138 -95
- package/src/trace.ts +44 -23
- package/src/types.ts +33 -24
package/src/tools.ts
CHANGED
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
// without bloating their context with tool descriptions upfront
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
20
|
|
|
21
|
-
import type { Capability } from
|
|
22
|
-
import type { OwnershipScope } from
|
|
23
|
-
import type { ToolDefinition } from
|
|
21
|
+
import type { Capability } from './capability';
|
|
22
|
+
import type { OwnershipScope } from './scope';
|
|
23
|
+
import type { ToolDefinition } from './types';
|
|
24
24
|
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
26
|
// Tool result — every tool returns this wrapper
|
|
@@ -47,7 +47,11 @@ export interface ToolCallObservation {
|
|
|
47
47
|
/** 1-based position of this call within its dispatch. */
|
|
48
48
|
readonly sequence: number;
|
|
49
49
|
/** Original lengths of any field that was cut to its bound. */
|
|
50
|
-
readonly truncated?: {
|
|
50
|
+
readonly truncated?: {
|
|
51
|
+
readonly input?: number;
|
|
52
|
+
readonly output?: number;
|
|
53
|
+
readonly error?: number;
|
|
54
|
+
};
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
export interface ToolResult<T = unknown> {
|
|
@@ -93,7 +97,7 @@ export interface NavigateOpts {
|
|
|
93
97
|
|
|
94
98
|
export interface BrowserTool {
|
|
95
99
|
readonly id: string;
|
|
96
|
-
readonly kind:
|
|
100
|
+
readonly kind: 'browser';
|
|
97
101
|
|
|
98
102
|
/** Required capabilities for each operation. */
|
|
99
103
|
readonly capabilities: {
|
|
@@ -137,13 +141,13 @@ export interface BrowserTool {
|
|
|
137
141
|
|
|
138
142
|
export interface ApiRequest {
|
|
139
143
|
url: string;
|
|
140
|
-
method?:
|
|
144
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD';
|
|
141
145
|
headers?: Record<string, string>;
|
|
142
146
|
body?: unknown;
|
|
143
147
|
/** Timeout in ms. Default: 30000. */
|
|
144
148
|
timeout?: number;
|
|
145
149
|
/** Parse response body as this type. Default: "json". */
|
|
146
|
-
responseType?:
|
|
150
|
+
responseType?: 'json' | 'text' | 'buffer';
|
|
147
151
|
}
|
|
148
152
|
|
|
149
153
|
export interface ApiResponse<T = unknown> {
|
|
@@ -172,7 +176,7 @@ export interface ApiToolConfig {
|
|
|
172
176
|
|
|
173
177
|
export interface ApiTool {
|
|
174
178
|
readonly id: string;
|
|
175
|
-
readonly kind:
|
|
179
|
+
readonly kind: 'api';
|
|
176
180
|
|
|
177
181
|
/** Required capability. */
|
|
178
182
|
readonly capability: Capability;
|
|
@@ -184,16 +188,30 @@ export interface ApiTool {
|
|
|
184
188
|
request<T = unknown>(req: ApiRequest): Promise<ToolResult<ApiResponse<T>>>;
|
|
185
189
|
|
|
186
190
|
/** Convenience: GET request. */
|
|
187
|
-
get<T = unknown>(
|
|
191
|
+
get<T = unknown>(
|
|
192
|
+
url: string,
|
|
193
|
+
headers?: Record<string, string>,
|
|
194
|
+
): Promise<ToolResult<ApiResponse<T>>>;
|
|
188
195
|
|
|
189
196
|
/** Convenience: POST request with JSON body. */
|
|
190
|
-
post<T = unknown>(
|
|
197
|
+
post<T = unknown>(
|
|
198
|
+
url: string,
|
|
199
|
+
body: unknown,
|
|
200
|
+
headers?: Record<string, string>,
|
|
201
|
+
): Promise<ToolResult<ApiResponse<T>>>;
|
|
191
202
|
|
|
192
203
|
/** Convenience: PUT request with JSON body. */
|
|
193
|
-
put<T = unknown>(
|
|
204
|
+
put<T = unknown>(
|
|
205
|
+
url: string,
|
|
206
|
+
body: unknown,
|
|
207
|
+
headers?: Record<string, string>,
|
|
208
|
+
): Promise<ToolResult<ApiResponse<T>>>;
|
|
194
209
|
|
|
195
210
|
/** Convenience: DELETE request. */
|
|
196
|
-
delete<T = unknown>(
|
|
211
|
+
delete<T = unknown>(
|
|
212
|
+
url: string,
|
|
213
|
+
headers?: Record<string, string>,
|
|
214
|
+
): Promise<ToolResult<ApiResponse<T>>>;
|
|
197
215
|
}
|
|
198
216
|
|
|
199
217
|
// ---------------------------------------------------------------------------
|
|
@@ -237,7 +255,7 @@ export interface ShellOpts {
|
|
|
237
255
|
|
|
238
256
|
export interface ShellTool {
|
|
239
257
|
readonly id: string;
|
|
240
|
-
readonly kind:
|
|
258
|
+
readonly kind: 'shell';
|
|
241
259
|
|
|
242
260
|
/** Required capability. */
|
|
243
261
|
readonly capability: Capability;
|
|
@@ -277,7 +295,7 @@ export interface ScriptOpts {
|
|
|
277
295
|
|
|
278
296
|
export interface ScriptTool {
|
|
279
297
|
readonly id: string;
|
|
280
|
-
readonly kind:
|
|
298
|
+
readonly kind: 'script';
|
|
281
299
|
|
|
282
300
|
/** Required capability. */
|
|
283
301
|
readonly capability: Capability;
|
|
@@ -307,7 +325,7 @@ export interface ScriptTool {
|
|
|
307
325
|
* Entries without a visibility are unowned legacy records: hidden from every
|
|
308
326
|
* scoped read unless a source explicitly opts in.
|
|
309
327
|
*/
|
|
310
|
-
export type MemoryVisibility =
|
|
328
|
+
export type MemoryVisibility = 'thread' | 'project' | 'global';
|
|
311
329
|
|
|
312
330
|
export interface MemoryEntry {
|
|
313
331
|
id: string;
|
|
@@ -333,7 +351,7 @@ export interface MemorySearchOpts {
|
|
|
333
351
|
|
|
334
352
|
export interface MemoryTool {
|
|
335
353
|
readonly id: string;
|
|
336
|
-
readonly kind:
|
|
354
|
+
readonly kind: 'memory';
|
|
337
355
|
/** The underlying system name (e.g., "file", "sqlite", "redis", "supermemory"). */
|
|
338
356
|
readonly system: string;
|
|
339
357
|
|
|
@@ -375,7 +393,7 @@ export interface MemoryTool {
|
|
|
375
393
|
export type Tool = BrowserTool | ApiTool | ShellTool | ScriptTool | MemoryTool;
|
|
376
394
|
|
|
377
395
|
/** Tool kind discriminator. */
|
|
378
|
-
export type ToolKind = Tool[
|
|
396
|
+
export type ToolKind = Tool['kind'];
|
|
379
397
|
|
|
380
398
|
/** Metadata about a registered tool (what agents see before using it). */
|
|
381
399
|
export interface ToolInfo {
|
|
@@ -406,19 +424,15 @@ export class ToolRegistry {
|
|
|
406
424
|
this._tools.set(tool.id, tool);
|
|
407
425
|
|
|
408
426
|
const capabilities: Capability[] = [];
|
|
409
|
-
if (tool.kind ===
|
|
427
|
+
if (tool.kind === 'browser') {
|
|
410
428
|
capabilities.push(
|
|
411
429
|
tool.capabilities.navigate,
|
|
412
430
|
tool.capabilities.interact,
|
|
413
431
|
tool.capabilities.execute,
|
|
414
432
|
tool.capabilities.screenshot,
|
|
415
433
|
);
|
|
416
|
-
} else if (tool.kind ===
|
|
417
|
-
capabilities.push(
|
|
418
|
-
tool.capabilities.read,
|
|
419
|
-
tool.capabilities.write,
|
|
420
|
-
tool.capabilities.delete,
|
|
421
|
-
);
|
|
434
|
+
} else if (tool.kind === 'memory') {
|
|
435
|
+
capabilities.push(tool.capabilities.read, tool.capabilities.write, tool.capabilities.delete);
|
|
422
436
|
} else {
|
|
423
437
|
capabilities.push(tool.capability);
|
|
424
438
|
}
|
|
@@ -472,10 +486,8 @@ export class ToolRegistry {
|
|
|
472
486
|
/** Get a compact summary string for injecting into agent context. */
|
|
473
487
|
summary(): string {
|
|
474
488
|
const tools = this.list();
|
|
475
|
-
if (tools.length === 0) return
|
|
476
|
-
return tools
|
|
477
|
-
.map((t) => `- ${t.id} (${t.kind}): ${t.description}`)
|
|
478
|
-
.join("\n");
|
|
489
|
+
if (tools.length === 0) return 'No tools available.';
|
|
490
|
+
return tools.map((t) => `- ${t.id} (${t.kind}): ${t.description}`).join('\n');
|
|
479
491
|
}
|
|
480
492
|
|
|
481
493
|
/**
|
|
@@ -489,128 +501,132 @@ export class ToolRegistry {
|
|
|
489
501
|
const desc = this._info.get(id)?.description ?? id;
|
|
490
502
|
|
|
491
503
|
switch (tool.kind) {
|
|
492
|
-
case
|
|
504
|
+
case 'shell':
|
|
493
505
|
defs.push({
|
|
494
506
|
name: `${id}_exec`,
|
|
495
507
|
description: `[${id}] ${desc} — execute a shell command`,
|
|
496
508
|
inputSchema: {
|
|
497
|
-
type:
|
|
509
|
+
type: 'object',
|
|
498
510
|
properties: {
|
|
499
|
-
command: { type:
|
|
511
|
+
command: { type: 'string', description: 'Shell command to execute' },
|
|
500
512
|
},
|
|
501
|
-
required: [
|
|
513
|
+
required: ['command'],
|
|
502
514
|
},
|
|
503
515
|
});
|
|
504
516
|
break;
|
|
505
517
|
|
|
506
|
-
case
|
|
518
|
+
case 'memory':
|
|
507
519
|
defs.push(
|
|
508
520
|
{
|
|
509
521
|
name: `${id}_search`,
|
|
510
522
|
description: `[${id}] Search ${desc}`,
|
|
511
523
|
inputSchema: {
|
|
512
|
-
type:
|
|
524
|
+
type: 'object',
|
|
513
525
|
properties: {
|
|
514
|
-
query: { type:
|
|
515
|
-
kind: { type:
|
|
516
|
-
limit: { type:
|
|
526
|
+
query: { type: 'string', description: 'Search query' },
|
|
527
|
+
kind: { type: 'string', description: 'Filter by entry kind (optional)' },
|
|
528
|
+
limit: { type: 'number', description: 'Max results (default: 20)' },
|
|
517
529
|
},
|
|
518
|
-
required: [
|
|
530
|
+
required: ['query'],
|
|
519
531
|
},
|
|
520
532
|
},
|
|
521
533
|
{
|
|
522
534
|
name: `${id}_get`,
|
|
523
535
|
description: `[${id}] Get entry by ID from ${desc}`,
|
|
524
536
|
inputSchema: {
|
|
525
|
-
type:
|
|
526
|
-
properties: { id: { type:
|
|
527
|
-
required: [
|
|
537
|
+
type: 'object',
|
|
538
|
+
properties: { id: { type: 'string' } },
|
|
539
|
+
required: ['id'],
|
|
528
540
|
},
|
|
529
541
|
},
|
|
530
542
|
{
|
|
531
543
|
name: `${id}_write`,
|
|
532
544
|
description: `[${id}] Write an entry to ${desc}`,
|
|
533
545
|
inputSchema: {
|
|
534
|
-
type:
|
|
546
|
+
type: 'object',
|
|
535
547
|
properties: {
|
|
536
|
-
id: { type:
|
|
537
|
-
kind: { type:
|
|
538
|
-
content: { type:
|
|
548
|
+
id: { type: 'string' },
|
|
549
|
+
kind: { type: 'string' },
|
|
550
|
+
content: { type: 'string' },
|
|
539
551
|
visibility: {
|
|
540
|
-
type:
|
|
541
|
-
enum: [
|
|
542
|
-
description:
|
|
552
|
+
type: 'string',
|
|
553
|
+
enum: ['thread', 'project', 'global'],
|
|
554
|
+
description:
|
|
555
|
+
'Who may read it. Default: thread (private). project/global publish it deliberately.',
|
|
543
556
|
},
|
|
544
557
|
},
|
|
545
|
-
required: [
|
|
558
|
+
required: ['id', 'kind', 'content'],
|
|
546
559
|
},
|
|
547
560
|
},
|
|
548
561
|
);
|
|
549
562
|
break;
|
|
550
563
|
|
|
551
|
-
case
|
|
564
|
+
case 'script':
|
|
552
565
|
defs.push({
|
|
553
566
|
name: `${id}_evaluate`,
|
|
554
567
|
description: `[${id}] ${desc} — execute code and return result`,
|
|
555
568
|
inputSchema: {
|
|
556
|
-
type:
|
|
569
|
+
type: 'object',
|
|
557
570
|
properties: {
|
|
558
|
-
code: {
|
|
571
|
+
code: {
|
|
572
|
+
type: 'string',
|
|
573
|
+
description: "TypeScript/JS code to execute. Use 'return' to return a value.",
|
|
574
|
+
},
|
|
559
575
|
},
|
|
560
|
-
required: [
|
|
576
|
+
required: ['code'],
|
|
561
577
|
},
|
|
562
578
|
});
|
|
563
579
|
break;
|
|
564
580
|
|
|
565
|
-
case
|
|
581
|
+
case 'api':
|
|
566
582
|
defs.push({
|
|
567
583
|
name: `${id}_request`,
|
|
568
584
|
description: `[${id}] ${desc} — make an HTTP request`,
|
|
569
585
|
inputSchema: {
|
|
570
|
-
type:
|
|
586
|
+
type: 'object',
|
|
571
587
|
properties: {
|
|
572
|
-
url: { type:
|
|
573
|
-
method: { type:
|
|
574
|
-
body: { description:
|
|
575
|
-
headers: { type:
|
|
588
|
+
url: { type: 'string' },
|
|
589
|
+
method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'DELETE'] },
|
|
590
|
+
body: { description: 'Request body (JSON)' },
|
|
591
|
+
headers: { type: 'object', description: 'Additional headers' },
|
|
576
592
|
},
|
|
577
|
-
required: [
|
|
593
|
+
required: ['url'],
|
|
578
594
|
},
|
|
579
595
|
});
|
|
580
596
|
break;
|
|
581
597
|
|
|
582
|
-
case
|
|
598
|
+
case 'browser':
|
|
583
599
|
defs.push(
|
|
584
600
|
{
|
|
585
601
|
name: `${id}_navigate`,
|
|
586
602
|
description: `[${id}] Navigate to a URL`,
|
|
587
603
|
inputSchema: {
|
|
588
|
-
type:
|
|
589
|
-
properties: { url: { type:
|
|
590
|
-
required: [
|
|
604
|
+
type: 'object',
|
|
605
|
+
properties: { url: { type: 'string' } },
|
|
606
|
+
required: ['url'],
|
|
591
607
|
},
|
|
592
608
|
},
|
|
593
609
|
{
|
|
594
610
|
name: `${id}_snapshot`,
|
|
595
611
|
description: `[${id}] Get page accessibility snapshot`,
|
|
596
|
-
inputSchema: { type:
|
|
612
|
+
inputSchema: { type: 'object', properties: {} },
|
|
597
613
|
},
|
|
598
614
|
{
|
|
599
615
|
name: `${id}_click`,
|
|
600
616
|
description: `[${id}] Click an element`,
|
|
601
617
|
inputSchema: {
|
|
602
|
-
type:
|
|
603
|
-
properties: { ref: { type:
|
|
604
|
-
required: [
|
|
618
|
+
type: 'object',
|
|
619
|
+
properties: { ref: { type: 'string', description: 'CSS selector or ref' } },
|
|
620
|
+
required: ['ref'],
|
|
605
621
|
},
|
|
606
622
|
},
|
|
607
623
|
{
|
|
608
624
|
name: `${id}_evaluate`,
|
|
609
625
|
description: `[${id}] Execute JavaScript in page`,
|
|
610
626
|
inputSchema: {
|
|
611
|
-
type:
|
|
612
|
-
properties: { script: { type:
|
|
613
|
-
required: [
|
|
627
|
+
type: 'object',
|
|
628
|
+
properties: { script: { type: 'string' } },
|
|
629
|
+
required: ['script'],
|
|
614
630
|
},
|
|
615
631
|
},
|
|
616
632
|
);
|
|
@@ -631,16 +647,16 @@ export class ToolRegistry {
|
|
|
631
647
|
opts?: { cwd?: string; scope?: OwnershipScope },
|
|
632
648
|
): Promise<ToolResult> {
|
|
633
649
|
// Parse "toolId_method" format
|
|
634
|
-
const lastUnderscore = toolName.lastIndexOf(
|
|
650
|
+
const lastUnderscore = toolName.lastIndexOf('_');
|
|
635
651
|
if (lastUnderscore === -1) {
|
|
636
|
-
return { ok: false, summary: `Unknown tool: ${toolName}`, error:
|
|
652
|
+
return { ok: false, summary: `Unknown tool: ${toolName}`, error: 'Invalid tool name format' };
|
|
637
653
|
}
|
|
638
654
|
|
|
639
655
|
// Try progressively shorter prefixes (tool IDs can contain underscores)
|
|
640
656
|
let tool: Tool | undefined;
|
|
641
|
-
let method =
|
|
657
|
+
let method = '';
|
|
642
658
|
for (let i = toolName.length - 1; i >= 0; i--) {
|
|
643
|
-
if (toolName[i] ===
|
|
659
|
+
if (toolName[i] === '_') {
|
|
644
660
|
const candidateId = toolName.slice(0, i);
|
|
645
661
|
const candidateMethod = toolName.slice(i + 1);
|
|
646
662
|
if (this._tools.has(candidateId)) {
|
|
@@ -652,24 +668,37 @@ export class ToolRegistry {
|
|
|
652
668
|
}
|
|
653
669
|
|
|
654
670
|
if (!tool) {
|
|
655
|
-
return {
|
|
671
|
+
return {
|
|
672
|
+
ok: false,
|
|
673
|
+
summary: `Unknown tool: ${toolName}`,
|
|
674
|
+
error: `No registered tool matches "${toolName}"`,
|
|
675
|
+
};
|
|
656
676
|
}
|
|
657
677
|
|
|
658
678
|
try {
|
|
659
679
|
switch (tool.kind) {
|
|
660
|
-
case
|
|
661
|
-
if (method ===
|
|
680
|
+
case 'shell':
|
|
681
|
+
if (method === 'exec')
|
|
682
|
+
return await tool.exec(input.command as string, { cwd: opts?.cwd });
|
|
662
683
|
break;
|
|
663
684
|
|
|
664
|
-
case
|
|
685
|
+
case 'memory': {
|
|
665
686
|
// A scoped dispatch may only reach a tool that can honor the scope.
|
|
666
687
|
const memory = opts?.scope ? tool.scoped?.(opts.scope) : tool;
|
|
667
688
|
if (!memory) {
|
|
668
|
-
return {
|
|
689
|
+
return {
|
|
690
|
+
ok: false,
|
|
691
|
+
summary: `Memory tool "${tool.id}" cannot scope reads to a thread`,
|
|
692
|
+
error: 'Unscoped memory tool',
|
|
693
|
+
};
|
|
669
694
|
}
|
|
670
|
-
if (method ===
|
|
671
|
-
|
|
672
|
-
|
|
695
|
+
if (method === 'search')
|
|
696
|
+
return await memory.search(input.query as string, {
|
|
697
|
+
kind: input.kind as string,
|
|
698
|
+
limit: input.limit as number,
|
|
699
|
+
});
|
|
700
|
+
if (method === 'get') return await memory.get(input.id as string);
|
|
701
|
+
if (method === 'write') {
|
|
673
702
|
const visibility = input.visibility as MemoryVisibility | undefined;
|
|
674
703
|
return await memory.write({
|
|
675
704
|
id: input.id as string,
|
|
@@ -682,28 +711,42 @@ export class ToolRegistry {
|
|
|
682
711
|
break;
|
|
683
712
|
}
|
|
684
713
|
|
|
685
|
-
case
|
|
686
|
-
if (method ===
|
|
714
|
+
case 'script':
|
|
715
|
+
if (method === 'evaluate')
|
|
716
|
+
return await tool.evaluate(input.code as string, { cwd: opts?.cwd });
|
|
687
717
|
break;
|
|
688
718
|
|
|
689
|
-
case
|
|
690
|
-
if (method ===
|
|
719
|
+
case 'api':
|
|
720
|
+
if (method === 'request') {
|
|
691
721
|
const api = tool.scoped ? tool.scoped(opts?.scope ?? {}) : tool;
|
|
692
|
-
return await api.request({
|
|
722
|
+
return await api.request({
|
|
723
|
+
url: input.url as string,
|
|
724
|
+
method: (input.method as any) ?? 'GET',
|
|
725
|
+
body: input.body,
|
|
726
|
+
headers: input.headers as Record<string, string>,
|
|
727
|
+
});
|
|
693
728
|
}
|
|
694
729
|
break;
|
|
695
730
|
|
|
696
|
-
case
|
|
697
|
-
if (method ===
|
|
698
|
-
if (method ===
|
|
699
|
-
if (method ===
|
|
700
|
-
if (method ===
|
|
731
|
+
case 'browser':
|
|
732
|
+
if (method === 'navigate') return await tool.navigate(input.url as string);
|
|
733
|
+
if (method === 'snapshot') return await tool.snapshot();
|
|
734
|
+
if (method === 'click') return await tool.click(input.ref as string);
|
|
735
|
+
if (method === 'evaluate') return await tool.evaluate(input.script as string);
|
|
701
736
|
break;
|
|
702
737
|
}
|
|
703
738
|
|
|
704
|
-
return {
|
|
739
|
+
return {
|
|
740
|
+
ok: false,
|
|
741
|
+
summary: `Unknown method "${method}" for tool kind "${tool.kind}"`,
|
|
742
|
+
error: 'Method not found',
|
|
743
|
+
};
|
|
705
744
|
} catch (err) {
|
|
706
|
-
return {
|
|
745
|
+
return {
|
|
746
|
+
ok: false,
|
|
747
|
+
summary: `Tool error: ${(err as Error).message}`,
|
|
748
|
+
error: (err as Error).message,
|
|
749
|
+
};
|
|
707
750
|
}
|
|
708
751
|
}
|
|
709
752
|
|
package/src/trace.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TokenCounts } from
|
|
1
|
+
import type { TokenCounts } from './token-counts';
|
|
2
2
|
/**
|
|
3
3
|
* A single span in a message's journey through the system.
|
|
4
4
|
* Each stage (classify, route, dispatch, writeback, middleware) creates a span.
|
|
@@ -47,24 +47,24 @@ export interface Span {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export type SpanKind =
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
|
59
|
-
|
|
|
50
|
+
| 'ingress'
|
|
51
|
+
| 'classify'
|
|
52
|
+
| 'route'
|
|
53
|
+
| 'dispatch'
|
|
54
|
+
| 'execute'
|
|
55
|
+
| 'decide'
|
|
56
|
+
| 'middleware'
|
|
57
|
+
| 'writeback'
|
|
58
|
+
| 'egress'
|
|
59
|
+
| 'fan'
|
|
60
60
|
| (string & {});
|
|
61
61
|
|
|
62
|
-
export type SpanStatus =
|
|
62
|
+
export type SpanStatus = 'running' | 'ok' | 'error';
|
|
63
63
|
|
|
64
|
-
import { newId } from
|
|
64
|
+
import { newId } from './id';
|
|
65
65
|
|
|
66
66
|
function nextSpanId(): string {
|
|
67
|
-
return newId(
|
|
67
|
+
return newId('span');
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
@@ -93,12 +93,12 @@ export class Trace {
|
|
|
93
93
|
/** Current active span stack — the innermost is where new children attach. */
|
|
94
94
|
private _stack: Span[] = [];
|
|
95
95
|
|
|
96
|
-
constructor(messageId: string, detail?: Pick<Span,
|
|
97
|
-
this.id = newId(
|
|
96
|
+
constructor(messageId: string, detail?: Pick<Span, 'input' | 'threadId'>) {
|
|
97
|
+
this.id = newId('trace');
|
|
98
98
|
this.messageId = messageId;
|
|
99
99
|
this.startedAt = performance.now();
|
|
100
100
|
|
|
101
|
-
this.root = this._createSpan(
|
|
101
|
+
this.root = this._createSpan('ingress', 'ingress', undefined, detail);
|
|
102
102
|
this._stack.push(this.root);
|
|
103
103
|
}
|
|
104
104
|
|
|
@@ -122,10 +122,10 @@ export class Trace {
|
|
|
122
122
|
span.durationMs = span.endedAt - span.startedAt;
|
|
123
123
|
|
|
124
124
|
if (error) {
|
|
125
|
-
span.status =
|
|
125
|
+
span.status = 'error';
|
|
126
126
|
span.error = error;
|
|
127
127
|
} else {
|
|
128
|
-
span.status =
|
|
128
|
+
span.status = 'ok';
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
if (output !== undefined) {
|
|
@@ -135,6 +135,27 @@ export class Trace {
|
|
|
135
135
|
return span;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Start a span that runs concurrently with its siblings: a child of the current
|
|
140
|
+
* span that does not become current. End it with endSpan().
|
|
141
|
+
*/
|
|
142
|
+
startDetached(name: string, kind: SpanKind, detail?: Partial<Span>): Span {
|
|
143
|
+
const parent = this._stack[this._stack.length - 1];
|
|
144
|
+
const span = this._createSpan(name, kind, parent?.id, detail);
|
|
145
|
+
if (parent) parent.children.push(span);
|
|
146
|
+
return span;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** End a span started with startDetached(). */
|
|
150
|
+
endSpan(span: Span, output?: unknown, error?: unknown): Span {
|
|
151
|
+
span.endedAt = performance.now();
|
|
152
|
+
span.durationMs = span.endedAt - span.startedAt;
|
|
153
|
+
span.status = error ? 'error' : 'ok';
|
|
154
|
+
if (error) span.error = error;
|
|
155
|
+
if (output !== undefined) (span as { output: unknown }).output = output;
|
|
156
|
+
return span;
|
|
157
|
+
}
|
|
158
|
+
|
|
138
159
|
/** End the entire trace. Closes any remaining open spans. */
|
|
139
160
|
finish(): void {
|
|
140
161
|
const now = performance.now();
|
|
@@ -145,8 +166,8 @@ export class Trace {
|
|
|
145
166
|
span.endedAt = now;
|
|
146
167
|
span.durationMs = span.endedAt - span.startedAt;
|
|
147
168
|
}
|
|
148
|
-
if (span.status ===
|
|
149
|
-
span.status =
|
|
169
|
+
if (span.status === 'running') {
|
|
170
|
+
span.status = 'ok';
|
|
150
171
|
}
|
|
151
172
|
}
|
|
152
173
|
this.endedAt = now;
|
|
@@ -217,7 +238,7 @@ export class Trace {
|
|
|
217
238
|
name: string,
|
|
218
239
|
kind: SpanKind,
|
|
219
240
|
parentId?: string,
|
|
220
|
-
detail?: Partial<Span
|
|
241
|
+
detail?: Partial<Span>,
|
|
221
242
|
): Span {
|
|
222
243
|
const span: Span = {
|
|
223
244
|
id: nextSpanId(),
|
|
@@ -231,7 +252,7 @@ export class Trace {
|
|
|
231
252
|
input: detail?.input,
|
|
232
253
|
output: undefined,
|
|
233
254
|
startedAt: performance.now(),
|
|
234
|
-
status:
|
|
255
|
+
status: 'running',
|
|
235
256
|
annotations: detail?.annotations ?? {},
|
|
236
257
|
children: [],
|
|
237
258
|
};
|