@rlynjb/aptkit-core 0.1.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.
Files changed (132) hide show
  1. package/README.md +19 -0
  2. package/dist/src/index.d.ts +12 -0
  3. package/dist/src/index.js +9 -0
  4. package/node_modules/@aptkit/agent-anomaly-monitoring/README.md +13 -0
  5. package/node_modules/@aptkit/agent-anomaly-monitoring/dist/src/categories.d.ts +11 -0
  6. package/node_modules/@aptkit/agent-anomaly-monitoring/dist/src/categories.js +100 -0
  7. package/node_modules/@aptkit/agent-anomaly-monitoring/dist/src/fixture-provider.d.ts +10 -0
  8. package/node_modules/@aptkit/agent-anomaly-monitoring/dist/src/fixture-provider.js +18 -0
  9. package/node_modules/@aptkit/agent-anomaly-monitoring/dist/src/index.d.ts +6 -0
  10. package/node_modules/@aptkit/agent-anomaly-monitoring/dist/src/index.js +6 -0
  11. package/node_modules/@aptkit/agent-anomaly-monitoring/dist/src/monitoring-agent.d.ts +32 -0
  12. package/node_modules/@aptkit/agent-anomaly-monitoring/dist/src/monitoring-agent.js +88 -0
  13. package/node_modules/@aptkit/agent-anomaly-monitoring/dist/src/schema-summary.d.ts +2 -0
  14. package/node_modules/@aptkit/agent-anomaly-monitoring/dist/src/schema-summary.js +7 -0
  15. package/node_modules/@aptkit/agent-anomaly-monitoring/dist/src/types.d.ts +32 -0
  16. package/node_modules/@aptkit/agent-anomaly-monitoring/dist/src/types.js +1 -0
  17. package/node_modules/@aptkit/agent-anomaly-monitoring/dist/src/validate.d.ts +9 -0
  18. package/node_modules/@aptkit/agent-anomaly-monitoring/dist/src/validate.js +34 -0
  19. package/node_modules/@aptkit/agent-anomaly-monitoring/fixtures/promoted/sp-revenue-monitoring-fixture-promoted-2026-06-18-18-37-26.json +229 -0
  20. package/node_modules/@aptkit/agent-anomaly-monitoring/fixtures/sp-revenue-monitoring.json +136 -0
  21. package/node_modules/@aptkit/agent-anomaly-monitoring/package.json +33 -0
  22. package/node_modules/@aptkit/agent-diagnostic-investigation/README.md +11 -0
  23. package/node_modules/@aptkit/agent-diagnostic-investigation/dist/src/diagnostic-agent.d.ts +27 -0
  24. package/node_modules/@aptkit/agent-diagnostic-investigation/dist/src/diagnostic-agent.js +95 -0
  25. package/node_modules/@aptkit/agent-diagnostic-investigation/dist/src/fixture-provider.d.ts +10 -0
  26. package/node_modules/@aptkit/agent-diagnostic-investigation/dist/src/fixture-provider.js +18 -0
  27. package/node_modules/@aptkit/agent-diagnostic-investigation/dist/src/index.d.ts +5 -0
  28. package/node_modules/@aptkit/agent-diagnostic-investigation/dist/src/index.js +5 -0
  29. package/node_modules/@aptkit/agent-diagnostic-investigation/dist/src/schema-summary.d.ts +1 -0
  30. package/node_modules/@aptkit/agent-diagnostic-investigation/dist/src/schema-summary.js +1 -0
  31. package/node_modules/@aptkit/agent-diagnostic-investigation/dist/src/types.d.ts +37 -0
  32. package/node_modules/@aptkit/agent-diagnostic-investigation/dist/src/types.js +1 -0
  33. package/node_modules/@aptkit/agent-diagnostic-investigation/dist/src/validate.d.ts +10 -0
  34. package/node_modules/@aptkit/agent-diagnostic-investigation/dist/src/validate.js +49 -0
  35. package/node_modules/@aptkit/agent-diagnostic-investigation/fixtures/promoted/sp-revenue-diagnostic-fixture-promoted-2026-06-18-19-04-28.json +230 -0
  36. package/node_modules/@aptkit/agent-diagnostic-investigation/fixtures/sp-revenue-diagnostic.json +148 -0
  37. package/node_modules/@aptkit/agent-diagnostic-investigation/package.json +33 -0
  38. package/node_modules/@aptkit/agent-query/README.md +11 -0
  39. package/node_modules/@aptkit/agent-query/dist/src/fixture-provider.d.ts +10 -0
  40. package/node_modules/@aptkit/agent-query/dist/src/fixture-provider.js +18 -0
  41. package/node_modules/@aptkit/agent-query/dist/src/index.d.ts +6 -0
  42. package/node_modules/@aptkit/agent-query/dist/src/index.js +6 -0
  43. package/node_modules/@aptkit/agent-query/dist/src/intent.d.ts +6 -0
  44. package/node_modules/@aptkit/agent-query/dist/src/intent.js +23 -0
  45. package/node_modules/@aptkit/agent-query/dist/src/query-agent.d.ts +27 -0
  46. package/node_modules/@aptkit/agent-query/dist/src/query-agent.js +81 -0
  47. package/node_modules/@aptkit/agent-query/dist/src/schema-summary.d.ts +1 -0
  48. package/node_modules/@aptkit/agent-query/dist/src/schema-summary.js +1 -0
  49. package/node_modules/@aptkit/agent-query/dist/src/types.d.ts +5 -0
  50. package/node_modules/@aptkit/agent-query/dist/src/types.js +1 -0
  51. package/node_modules/@aptkit/agent-query/dist/src/validate.d.ts +7 -0
  52. package/node_modules/@aptkit/agent-query/dist/src/validate.js +9 -0
  53. package/node_modules/@aptkit/agent-query/fixtures/promoted/revenue-by-state-query-fixture-promoted-2026-06-18-19-29-11.json +138 -0
  54. package/node_modules/@aptkit/agent-query/fixtures/revenue-by-state-query.json +79 -0
  55. package/node_modules/@aptkit/agent-query/package.json +33 -0
  56. package/node_modules/@aptkit/agent-recommendation/README.md +109 -0
  57. package/node_modules/@aptkit/agent-recommendation/dist/src/fixture-provider.d.ts +10 -0
  58. package/node_modules/@aptkit/agent-recommendation/dist/src/fixture-provider.js +18 -0
  59. package/node_modules/@aptkit/agent-recommendation/dist/src/index.d.ts +4 -0
  60. package/node_modules/@aptkit/agent-recommendation/dist/src/index.js +4 -0
  61. package/node_modules/@aptkit/agent-recommendation/dist/src/recommendation-agent.d.ts +30 -0
  62. package/node_modules/@aptkit/agent-recommendation/dist/src/recommendation-agent.js +85 -0
  63. package/node_modules/@aptkit/agent-recommendation/dist/src/schema-summary.d.ts +1 -0
  64. package/node_modules/@aptkit/agent-recommendation/dist/src/schema-summary.js +1 -0
  65. package/node_modules/@aptkit/agent-recommendation/dist/src/types.d.ts +68 -0
  66. package/node_modules/@aptkit/agent-recommendation/dist/src/types.js +3 -0
  67. package/node_modules/@aptkit/agent-recommendation/dist/src/validate.d.ts +3 -0
  68. package/node_modules/@aptkit/agent-recommendation/dist/src/validate.js +54 -0
  69. package/node_modules/@aptkit/agent-recommendation/fixtures/electronics-spike.json +84 -0
  70. package/node_modules/@aptkit/agent-recommendation/fixtures/promoted/voucher-dropoff-w10-on-openai-promoted-2026-06-18-16-53-02.json +166 -0
  71. package/node_modules/@aptkit/agent-recommendation/fixtures/promoted/voucher-dropoff-w10-on-openai-promoted-2026-06-18-17-20-55.json +157 -0
  72. package/node_modules/@aptkit/agent-recommendation/fixtures/sp-revenue-drop.json +83 -0
  73. package/node_modules/@aptkit/agent-recommendation/fixtures/voucher-dropoff.json +84 -0
  74. package/node_modules/@aptkit/agent-recommendation/package.json +34 -0
  75. package/node_modules/@aptkit/context/README.md +15 -0
  76. package/node_modules/@aptkit/context/dist/src/index.d.ts +2 -0
  77. package/node_modules/@aptkit/context/dist/src/index.js +2 -0
  78. package/node_modules/@aptkit/context/dist/src/workspace-descriptor.d.ts +25 -0
  79. package/node_modules/@aptkit/context/dist/src/workspace-descriptor.js +1 -0
  80. package/node_modules/@aptkit/context/dist/src/workspace-summary.d.ts +9 -0
  81. package/node_modules/@aptkit/context/dist/src/workspace-summary.js +38 -0
  82. package/node_modules/@aptkit/context/package.json +24 -0
  83. package/node_modules/@aptkit/evals/dist/src/assertions.d.ts +13 -0
  84. package/node_modules/@aptkit/evals/dist/src/assertions.js +351 -0
  85. package/node_modules/@aptkit/evals/dist/src/detection-scorer.d.ts +25 -0
  86. package/node_modules/@aptkit/evals/dist/src/detection-scorer.js +72 -0
  87. package/node_modules/@aptkit/evals/dist/src/index.d.ts +3 -0
  88. package/node_modules/@aptkit/evals/dist/src/index.js +3 -0
  89. package/node_modules/@aptkit/evals/dist/src/replay-runner.d.ts +29 -0
  90. package/node_modules/@aptkit/evals/dist/src/replay-runner.js +72 -0
  91. package/node_modules/@aptkit/evals/dist/src/structural-diff.d.ts +50 -0
  92. package/node_modules/@aptkit/evals/dist/src/structural-diff.js +143 -0
  93. package/node_modules/@aptkit/evals/package.json +27 -0
  94. package/node_modules/@aptkit/prompts/README.md +7 -0
  95. package/node_modules/@aptkit/prompts/dist/src/diagnostic.d.ts +3 -0
  96. package/node_modules/@aptkit/prompts/dist/src/diagnostic.js +85 -0
  97. package/node_modules/@aptkit/prompts/dist/src/index.d.ts +5 -0
  98. package/node_modules/@aptkit/prompts/dist/src/index.js +5 -0
  99. package/node_modules/@aptkit/prompts/dist/src/monitoring.d.ts +3 -0
  100. package/node_modules/@aptkit/prompts/dist/src/monitoring.js +57 -0
  101. package/node_modules/@aptkit/prompts/dist/src/query.d.ts +3 -0
  102. package/node_modules/@aptkit/prompts/dist/src/query.js +86 -0
  103. package/node_modules/@aptkit/prompts/dist/src/recommendation.d.ts +3 -0
  104. package/node_modules/@aptkit/prompts/dist/src/recommendation.js +110 -0
  105. package/node_modules/@aptkit/prompts/dist/src/types.d.ts +21 -0
  106. package/node_modules/@aptkit/prompts/dist/src/types.js +6 -0
  107. package/node_modules/@aptkit/prompts/package.json +24 -0
  108. package/node_modules/@aptkit/runtime/dist/src/events.d.ts +44 -0
  109. package/node_modules/@aptkit/runtime/dist/src/events.js +3 -0
  110. package/node_modules/@aptkit/runtime/dist/src/index.d.ts +6 -0
  111. package/node_modules/@aptkit/runtime/dist/src/index.js +6 -0
  112. package/node_modules/@aptkit/runtime/dist/src/json-output.d.ts +10 -0
  113. package/node_modules/@aptkit/runtime/dist/src/json-output.js +32 -0
  114. package/node_modules/@aptkit/runtime/dist/src/model-provider.d.ts +49 -0
  115. package/node_modules/@aptkit/runtime/dist/src/model-provider.js +1 -0
  116. package/node_modules/@aptkit/runtime/dist/src/ndjson-stream.d.ts +43 -0
  117. package/node_modules/@aptkit/runtime/dist/src/ndjson-stream.js +128 -0
  118. package/node_modules/@aptkit/runtime/dist/src/run-agent-loop.d.ts +42 -0
  119. package/node_modules/@aptkit/runtime/dist/src/run-agent-loop.js +138 -0
  120. package/node_modules/@aptkit/runtime/dist/src/usage-ledger.d.ts +29 -0
  121. package/node_modules/@aptkit/runtime/dist/src/usage-ledger.js +61 -0
  122. package/node_modules/@aptkit/runtime/package.json +23 -0
  123. package/node_modules/@aptkit/tools/dist/src/coverage-gate.d.ts +32 -0
  124. package/node_modules/@aptkit/tools/dist/src/coverage-gate.js +43 -0
  125. package/node_modules/@aptkit/tools/dist/src/index.d.ts +3 -0
  126. package/node_modules/@aptkit/tools/dist/src/index.js +3 -0
  127. package/node_modules/@aptkit/tools/dist/src/tool-policy.d.ts +9 -0
  128. package/node_modules/@aptkit/tools/dist/src/tool-policy.js +11 -0
  129. package/node_modules/@aptkit/tools/dist/src/tool-registry.d.ts +27 -0
  130. package/node_modules/@aptkit/tools/dist/src/tool-registry.js +25 -0
  131. package/node_modules/@aptkit/tools/package.json +26 -0
  132. package/package.json +53 -0
@@ -0,0 +1,110 @@
1
+ export const RECOMMENDATION_PROMPT = `You are a recommendation agent for an ecommerce workspace. You are read-only: you do NOT execute anything. Your recommendations are suggestions for a human to act on.
2
+
3
+ ## Role
4
+
5
+ Given a diagnosis of why something changed, propose 2-3 concrete actions the merchant can take.
6
+
7
+ Frame each action in the language of the available action taxonomy:
8
+
9
+ - scenario: automated, triggered flows such as cart recovery or win-back.
10
+ - segment: define a customer group to target or analyse.
11
+ - campaign: a one-off or scheduled broadcast.
12
+ - voucher: a discount or incentive.
13
+ - experiment: an A/B test to validate a fix before rollout.
14
+
15
+ ## Hard rules
16
+
17
+ 1. Pass project_id: {project_id} to every tool call when a tool accepts project context.
18
+ 2. Make at most 4 tool calls. Mostly reason from the diagnosis; optionally check what already exists so you do not duplicate live work.
19
+ 3. Check existing scenarios first when scenario tools are available.
20
+ 4. Each recommendation MUST set bloomreachFeature to exactly one configured action feature.
21
+ 5. Feature-discovery tools may return empty results. Propose new actions grounded in the feature type regardless of whether examples already exist.
22
+
23
+ ## Available feature-discovery tools
24
+
25
+ Use whichever of these are available in the supplied tool registry:
26
+
27
+ - list_scenarios, get_scenario
28
+ - list_initiatives, get_initiative_items
29
+ - list_recommendations, get_recommendation
30
+ - list_segmentations
31
+ - list_email_campaigns
32
+ - list_voucher_pools
33
+ - get_frequency_policies
34
+
35
+ ## The diagnosis to act on
36
+
37
+ {diagnosis}
38
+
39
+ ## How to propose
40
+
41
+ 1. Read the diagnosis: what changed, where, for whom, and why.
42
+ 2. Optionally check existing scenarios or segments so your proposals do not duplicate what is already running.
43
+ 3. Pick the action feature that best fits.
44
+ 4. Write human-readable steps a marketer could follow.
45
+ 5. Estimate impact in dollars when the diagnosis provides enough numbers. State the assumption.
46
+ 6. Estimate effort, timeToSetUpMinutes, and readResultInDays.
47
+ 7. List up to 3 prerequisites with satisfied true or false.
48
+ 8. Give a successMetric with a baseline and target.
49
+ 9. Order recommendations by predicted impact, highest first.
50
+ 10. Mark confidence honestly.
51
+
52
+ ## Output
53
+
54
+ Return ONLY a JSON array in a json fenced block of at most 3 objects. Do NOT include an id field. The system assigns ids after validation.
55
+
56
+ Each object must have:
57
+
58
+ - title: string
59
+ - rationale: string
60
+ - bloomreachFeature: scenario | segment | campaign | voucher | experiment
61
+ - steps: string[]
62
+ - estimatedImpact: string OR { range: string, rangeUsd?: { low: number, high: number }, assumption: string }
63
+ - confidence: high | medium | low
64
+ - effort?: low | medium | high
65
+ - timeToSetUpMinutes?: number
66
+ - readResultInDays?: number
67
+ - prerequisites?: { label: string, satisfied: boolean }[]
68
+ - successMetric?: string
69
+
70
+ If you cannot propose grounded actions, return [].
71
+
72
+ ## Workspace schema
73
+
74
+ {schema}`;
75
+ export const recommendationPromptPackage = {
76
+ id: 'recommendation-agent.default',
77
+ version: '0.1.0',
78
+ capabilityId: 'recommendation-agent',
79
+ description: 'Action recommendation generation from a supported diagnosis and available feature catalog.',
80
+ system: RECOMMENDATION_PROMPT,
81
+ variables: [
82
+ {
83
+ name: 'schema',
84
+ description: 'Workspace schema summary with data horizon and available fields.',
85
+ required: true,
86
+ },
87
+ {
88
+ name: 'project_id',
89
+ description: 'Host workspace project id for providers that require project context.',
90
+ required: true,
91
+ },
92
+ {
93
+ name: 'diagnosis',
94
+ description: 'JSON serialized diagnosis object to act on.',
95
+ required: true,
96
+ },
97
+ ],
98
+ examples: [
99
+ {
100
+ name: 'voucher-dropoff-recommendations',
101
+ input: {
102
+ diagnosis: {
103
+ conclusion: 'Voucher orders declined after a discount pool expired.',
104
+ evidence: ['voucher orders down 32% versus baseline'],
105
+ },
106
+ },
107
+ expectedContains: ['bloomreachFeature', 'successMetric'],
108
+ },
109
+ ],
110
+ };
@@ -0,0 +1,21 @@
1
+ export type PromptVariable = {
2
+ name: string;
3
+ description: string;
4
+ required: boolean;
5
+ };
6
+ export type PromptExample = {
7
+ name: string;
8
+ input: Record<string, unknown>;
9
+ expectedContains?: string[];
10
+ };
11
+ export type PromptPackage = {
12
+ id: string;
13
+ version: string;
14
+ capabilityId: string;
15
+ description: string;
16
+ system: string;
17
+ compactSystem?: string;
18
+ variables: PromptVariable[];
19
+ examples: PromptExample[];
20
+ };
21
+ export declare function renderPromptTemplate(template: string, variables: Record<string, string>): string;
@@ -0,0 +1,6 @@
1
+ export function renderPromptTemplate(template, variables) {
2
+ return template.replace(/\{([a-zA-Z0-9_]+)\}/g, (match, name) => {
3
+ const value = variables[name];
4
+ return value === undefined ? match : value;
5
+ });
6
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@aptkit/prompts",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "main": "./dist/src/index.js",
6
+ "types": "./dist/src/index.d.ts",
7
+ "files": [
8
+ "README.md",
9
+ "dist/src"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/src/index.d.ts",
14
+ "import": "./dist/src/index.js"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "build": "tsc -p tsconfig.json",
19
+ "test": "npm run build && node --test dist/test/*.test.js"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^20.0.0"
23
+ }
24
+ }
@@ -0,0 +1,44 @@
1
+ export type CapabilityEvent = {
2
+ type: 'step';
3
+ capabilityId: string;
4
+ role: string;
5
+ content: string;
6
+ timestamp: string;
7
+ } | {
8
+ type: 'tool_call_start';
9
+ capabilityId: string;
10
+ toolName: string;
11
+ args: unknown;
12
+ timestamp: string;
13
+ } | {
14
+ type: 'tool_call_end';
15
+ capabilityId: string;
16
+ toolName: string;
17
+ result?: unknown;
18
+ error?: string;
19
+ durationMs: number;
20
+ timestamp: string;
21
+ } | {
22
+ type: 'model_usage';
23
+ capabilityId: string;
24
+ provider: string;
25
+ model: string;
26
+ inputTokens?: number;
27
+ outputTokens?: number;
28
+ estimated?: boolean;
29
+ timestamp: string;
30
+ } | {
31
+ type: 'warning';
32
+ capabilityId: string;
33
+ message: string;
34
+ timestamp: string;
35
+ } | {
36
+ type: 'error';
37
+ capabilityId: string;
38
+ message: string;
39
+ timestamp: string;
40
+ };
41
+ export type CapabilityTraceSink = {
42
+ emit(event: CapabilityEvent): void;
43
+ };
44
+ export declare function timestamp(): string;
@@ -0,0 +1,3 @@
1
+ export function timestamp() {
2
+ return new Date().toISOString();
3
+ }
@@ -0,0 +1,6 @@
1
+ export * from './events.js';
2
+ export * from './json-output.js';
3
+ export * from './model-provider.js';
4
+ export * from './ndjson-stream.js';
5
+ export * from './run-agent-loop.js';
6
+ export * from './usage-ledger.js';
@@ -0,0 +1,6 @@
1
+ export * from './events.js';
2
+ export * from './json-output.js';
3
+ export * from './model-provider.js';
4
+ export * from './ndjson-stream.js';
5
+ export * from './run-agent-loop.js';
6
+ export * from './usage-ledger.js';
@@ -0,0 +1,10 @@
1
+ export type JsonValidation<T> = {
2
+ ok: true;
3
+ value: T;
4
+ } | {
5
+ ok: false;
6
+ error: string;
7
+ };
8
+ export type JsonValidator<T> = (value: unknown) => JsonValidation<T>;
9
+ export declare function parseAgentJson(text: string): unknown;
10
+ export declare function parseValidatedJson<T>(text: string, validate: JsonValidator<T>): JsonValidation<T>;
@@ -0,0 +1,32 @@
1
+ export function parseAgentJson(text) {
2
+ const fence = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
3
+ const candidate = (fence ? fence[1] : text).trim();
4
+ try {
5
+ return JSON.parse(candidate);
6
+ }
7
+ catch {
8
+ // Fall through to a bounded substring scan.
9
+ }
10
+ const objectStart = candidate.indexOf('{');
11
+ const arrayStart = candidate.indexOf('[');
12
+ const starts = [objectStart, arrayStart].filter((index) => index >= 0);
13
+ const start = starts.length > 0 ? Math.min(...starts) : -1;
14
+ const end = Math.max(candidate.lastIndexOf('}'), candidate.lastIndexOf(']'));
15
+ if (start >= 0 && end > start) {
16
+ return JSON.parse(candidate.slice(start, end + 1));
17
+ }
18
+ throw new Error('no parseable json in model output');
19
+ }
20
+ export function parseValidatedJson(text, validate) {
21
+ let parsed;
22
+ try {
23
+ parsed = parseAgentJson(text);
24
+ }
25
+ catch (error) {
26
+ return {
27
+ ok: false,
28
+ error: error instanceof Error ? error.message : String(error),
29
+ };
30
+ }
31
+ return validate(parsed);
32
+ }
@@ -0,0 +1,49 @@
1
+ export type ModelTextBlock = {
2
+ type: 'text';
3
+ text: string;
4
+ };
5
+ export type ModelToolUseBlock = {
6
+ type: 'tool_use';
7
+ id: string;
8
+ name: string;
9
+ input: Record<string, unknown>;
10
+ };
11
+ export type ModelToolResultBlock = {
12
+ type: 'tool_result';
13
+ toolUseId: string;
14
+ content: string;
15
+ isError?: boolean;
16
+ };
17
+ export type ModelContentBlock = ModelTextBlock | ModelToolUseBlock;
18
+ export type ModelMessage = {
19
+ role: 'user' | 'assistant';
20
+ content: string | ModelContentBlock[] | ModelToolResultBlock[];
21
+ };
22
+ export type ModelTool = {
23
+ name: string;
24
+ description?: string;
25
+ inputSchema: object;
26
+ };
27
+ export type ModelUsage = {
28
+ inputTokens?: number;
29
+ outputTokens?: number;
30
+ estimated?: boolean;
31
+ };
32
+ export type ModelRequest = {
33
+ system?: string;
34
+ messages: ModelMessage[];
35
+ tools?: ModelTool[];
36
+ maxTokens?: number;
37
+ temperature?: number;
38
+ signal?: AbortSignal;
39
+ };
40
+ export type ModelResponse = {
41
+ content: ModelContentBlock[];
42
+ usage?: ModelUsage;
43
+ model?: string;
44
+ };
45
+ export type ModelProvider = {
46
+ id: string;
47
+ defaultModel?: string;
48
+ complete(request: ModelRequest): Promise<ModelResponse>;
49
+ };
@@ -0,0 +1,43 @@
1
+ import type { CapabilityEvent } from './events.js';
2
+ export type NdjsonDecodeWarning = {
3
+ type: 'malformed_line';
4
+ line: number;
5
+ raw: string;
6
+ error: string;
7
+ };
8
+ export type NdjsonDecodeResult<T> = {
9
+ ok: true;
10
+ line: number;
11
+ value: T;
12
+ } | {
13
+ ok: false;
14
+ line: number;
15
+ warning: NdjsonDecodeWarning;
16
+ };
17
+ export type NdjsonDecodeOptions<T> = {
18
+ validate?: (value: unknown) => value is T;
19
+ maxWarnings?: number;
20
+ };
21
+ export type NdjsonDecodeSummary<T> = {
22
+ values: T[];
23
+ warnings: NdjsonDecodeWarning[];
24
+ };
25
+ export type NdjsonStreamDecodeOptions<T> = NdjsonDecodeOptions<T> & {
26
+ signal?: AbortSignal;
27
+ };
28
+ /** Serializes one JSON-compatible value as an NDJSON record. */
29
+ export declare function encodeNdjsonRecord(value: unknown): string;
30
+ /** Serializes one capability trace event as an NDJSON record. */
31
+ export declare function encodeCapabilityEvent(event: CapabilityEvent): string;
32
+ /** Runtime guard for the shared capability trace event envelope. */
33
+ export declare function isCapabilityEvent(value: unknown): value is CapabilityEvent;
34
+ /** Decodes one NDJSON line, returning a bounded warning shape instead of throwing. */
35
+ export declare function decodeNdjsonLine<T = unknown>(line: string, lineNumber: number, options?: NdjsonDecodeOptions<T>): NdjsonDecodeResult<T> | null;
36
+ /** Decodes a string containing complete NDJSON records into values and warnings. */
37
+ export declare function decodeNdjsonLines<T = unknown>(input: string, options?: NdjsonDecodeOptions<T>): NdjsonDecodeSummary<T>;
38
+ /** Decodes async NDJSON chunks while preserving partial lines across chunk boundaries. */
39
+ export declare function decodeNdjsonStream<T = unknown>(chunks: AsyncIterable<string | Uint8Array>, options?: NdjsonStreamDecodeOptions<T>): AsyncGenerator<NdjsonDecodeResult<T>>;
40
+ /** Collects decoded async stream records into values and bounded malformed-line warnings. */
41
+ export declare function collectNdjsonStream<T = unknown>(chunks: AsyncIterable<string | Uint8Array>, options?: NdjsonStreamDecodeOptions<T>): Promise<NdjsonDecodeSummary<T>>;
42
+ /** Decodes complete capability event records from a string payload. */
43
+ export declare function decodeCapabilityEventLines(input: string): NdjsonDecodeSummary<CapabilityEvent>;
@@ -0,0 +1,128 @@
1
+ const DEFAULT_MAX_WARNINGS = 25;
2
+ /** Serializes one JSON-compatible value as an NDJSON record. */
3
+ export function encodeNdjsonRecord(value) {
4
+ return `${JSON.stringify(value)}\n`;
5
+ }
6
+ /** Serializes one capability trace event as an NDJSON record. */
7
+ export function encodeCapabilityEvent(event) {
8
+ return encodeNdjsonRecord(event);
9
+ }
10
+ /** Runtime guard for the shared capability trace event envelope. */
11
+ export function isCapabilityEvent(value) {
12
+ if (!isRecord(value))
13
+ return false;
14
+ if (typeof value.type !== 'string')
15
+ return false;
16
+ if (typeof value.capabilityId !== 'string')
17
+ return false;
18
+ if (typeof value.timestamp !== 'string')
19
+ return false;
20
+ switch (value.type) {
21
+ case 'step':
22
+ return typeof value.role === 'string' && typeof value.content === 'string';
23
+ case 'tool_call_start':
24
+ return typeof value.toolName === 'string' && 'args' in value;
25
+ case 'tool_call_end':
26
+ return typeof value.toolName === 'string' && typeof value.durationMs === 'number';
27
+ case 'model_usage':
28
+ return typeof value.provider === 'string' && typeof value.model === 'string';
29
+ case 'warning':
30
+ case 'error':
31
+ return typeof value.message === 'string';
32
+ default:
33
+ return false;
34
+ }
35
+ }
36
+ /** Decodes one NDJSON line, returning a bounded warning shape instead of throwing. */
37
+ export function decodeNdjsonLine(line, lineNumber, options = {}) {
38
+ const raw = line.trim();
39
+ if (!raw)
40
+ return null;
41
+ try {
42
+ const parsed = JSON.parse(raw);
43
+ if (options.validate && !options.validate(parsed)) {
44
+ return malformedLine(lineNumber, line, 'record failed validation');
45
+ }
46
+ return { ok: true, line: lineNumber, value: parsed };
47
+ }
48
+ catch (error) {
49
+ return malformedLine(lineNumber, line, error instanceof Error ? error.message : String(error));
50
+ }
51
+ }
52
+ /** Decodes a string containing complete NDJSON records into values and warnings. */
53
+ export function decodeNdjsonLines(input, options = {}) {
54
+ const values = [];
55
+ const warnings = [];
56
+ const maxWarnings = options.maxWarnings ?? DEFAULT_MAX_WARNINGS;
57
+ const lines = input.split(/\r?\n/);
58
+ lines.forEach((line, index) => {
59
+ const result = decodeNdjsonLine(line, index + 1, options);
60
+ collectDecodeResult(result, values, warnings, maxWarnings);
61
+ });
62
+ return { values, warnings };
63
+ }
64
+ /** Decodes async NDJSON chunks while preserving partial lines across chunk boundaries. */
65
+ export async function* decodeNdjsonStream(chunks, options = {}) {
66
+ const decoder = new TextDecoder();
67
+ let buffer = '';
68
+ let lineNumber = 0;
69
+ for await (const chunk of chunks) {
70
+ options.signal?.throwIfAborted();
71
+ buffer += typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true });
72
+ let newlineIndex = buffer.search(/\r?\n/);
73
+ while (newlineIndex >= 0) {
74
+ const line = buffer.slice(0, newlineIndex);
75
+ const newlineLength = buffer[newlineIndex] === '\r' && buffer[newlineIndex + 1] === '\n' ? 2 : 1;
76
+ buffer = buffer.slice(newlineIndex + newlineLength);
77
+ lineNumber += 1;
78
+ const result = decodeNdjsonLine(line, lineNumber, options);
79
+ if (result)
80
+ yield result;
81
+ options.signal?.throwIfAborted();
82
+ newlineIndex = buffer.search(/\r?\n/);
83
+ }
84
+ }
85
+ const finalText = decoder.decode();
86
+ if (finalText)
87
+ buffer += finalText;
88
+ if (buffer.trim()) {
89
+ lineNumber += 1;
90
+ const result = decodeNdjsonLine(buffer, lineNumber, options);
91
+ if (result)
92
+ yield result;
93
+ }
94
+ }
95
+ /** Collects decoded async stream records into values and bounded malformed-line warnings. */
96
+ export async function collectNdjsonStream(chunks, options = {}) {
97
+ const values = [];
98
+ const warnings = [];
99
+ const maxWarnings = options.maxWarnings ?? DEFAULT_MAX_WARNINGS;
100
+ for await (const result of decodeNdjsonStream(chunks, options)) {
101
+ collectDecodeResult(result, values, warnings, maxWarnings);
102
+ }
103
+ return { values, warnings };
104
+ }
105
+ /** Decodes complete capability event records from a string payload. */
106
+ export function decodeCapabilityEventLines(input) {
107
+ return decodeNdjsonLines(input, { validate: isCapabilityEvent });
108
+ }
109
+ function collectDecodeResult(result, values, warnings, maxWarnings) {
110
+ if (!result)
111
+ return;
112
+ if (result.ok) {
113
+ values.push(result.value);
114
+ return;
115
+ }
116
+ if (warnings.length < maxWarnings)
117
+ warnings.push(result.warning);
118
+ }
119
+ function malformedLine(line, raw, error) {
120
+ return {
121
+ ok: false,
122
+ line,
123
+ warning: { type: 'malformed_line', line, raw, error },
124
+ };
125
+ }
126
+ function isRecord(value) {
127
+ return typeof value === 'object' && value !== null;
128
+ }
@@ -0,0 +1,42 @@
1
+ import { type CapabilityTraceSink } from './events.js';
2
+ import type { ModelProvider, ModelTool } from './model-provider.js';
3
+ export type ToolCallRecord = {
4
+ id: string;
5
+ capabilityId: string;
6
+ toolName: string;
7
+ args: Record<string, unknown>;
8
+ result?: unknown;
9
+ durationMs?: number;
10
+ error?: string;
11
+ };
12
+ export type ToolExecutor = {
13
+ callTool(name: string, args: Record<string, unknown>, options?: {
14
+ signal?: AbortSignal;
15
+ }): Promise<{
16
+ result: unknown;
17
+ durationMs: number;
18
+ }>;
19
+ };
20
+ export type AgentRunResult<T = null> = {
21
+ finalText: string;
22
+ toolCalls: ToolCallRecord[];
23
+ parsed: T | null;
24
+ };
25
+ export type RunAgentLoopOptions<T> = {
26
+ capabilityId: string;
27
+ model: ModelProvider;
28
+ tools: ToolExecutor;
29
+ system: string;
30
+ userPrompt: string;
31
+ toolSchemas: ModelTool[];
32
+ trace?: CapabilityTraceSink;
33
+ maxTurns?: number;
34
+ maxTokens?: number;
35
+ maxToolCalls?: number;
36
+ synthesisInstruction?: string;
37
+ signal?: AbortSignal;
38
+ parseResult?: (finalText: string) => T | null;
39
+ recoveryPrompt?: (toolCalls: ToolCallRecord[]) => string;
40
+ };
41
+ export declare function buildSynthesisInstruction(middle: string): string;
42
+ export declare function runAgentLoop<T = null>(options: RunAgentLoopOptions<T>): Promise<AgentRunResult<T>>;
@@ -0,0 +1,138 @@
1
+ import { timestamp } from './events.js';
2
+ const MAX_TOOL_RESULT_CHARS = 16_000;
3
+ function truncate(value) {
4
+ if (value.length <= MAX_TOOL_RESULT_CHARS)
5
+ return value;
6
+ return `${value.slice(0, MAX_TOOL_RESULT_CHARS)}\n...[truncated]`;
7
+ }
8
+ function textFromContent(content) {
9
+ return content
10
+ .filter((block) => block.type === 'text')
11
+ .map((block) => block.text)
12
+ .join('');
13
+ }
14
+ function toolUsesFromContent(content) {
15
+ return content.filter((block) => block.type === 'tool_use');
16
+ }
17
+ export function buildSynthesisInstruction(middle) {
18
+ return `You have NO more tool calls available. ${middle} Do not say you need more queries.`;
19
+ }
20
+ export async function runAgentLoop(options) {
21
+ const { capabilityId, model, tools, system, userPrompt, toolSchemas, trace, maxTurns = 8, maxTokens = 4096, maxToolCalls, synthesisInstruction, signal, } = options;
22
+ const messages = [{ role: 'user', content: userPrompt }];
23
+ const toolCalls = [];
24
+ let finalText = '';
25
+ for (let turn = 0; turn < maxTurns; turn += 1) {
26
+ signal?.throwIfAborted();
27
+ const budgetSpent = maxToolCalls !== undefined && toolCalls.length >= maxToolCalls;
28
+ const forceFinal = turn === maxTurns - 1 || budgetSpent;
29
+ const response = await model.complete({
30
+ system: forceFinal && synthesisInstruction ? `${system}\n\n${synthesisInstruction}` : system,
31
+ messages,
32
+ tools: forceFinal ? undefined : toolSchemas,
33
+ maxTokens,
34
+ signal,
35
+ });
36
+ if (response.usage) {
37
+ trace?.emit({
38
+ type: 'model_usage',
39
+ capabilityId,
40
+ provider: model.id,
41
+ model: response.model ?? model.defaultModel ?? 'unknown',
42
+ inputTokens: response.usage.inputTokens,
43
+ outputTokens: response.usage.outputTokens,
44
+ estimated: response.usage.estimated,
45
+ timestamp: timestamp(),
46
+ });
47
+ }
48
+ messages.push({ role: 'assistant', content: response.content });
49
+ const text = textFromContent(response.content);
50
+ if (text) {
51
+ trace?.emit({ type: 'step', capabilityId, role: 'assistant', content: text, timestamp: timestamp() });
52
+ }
53
+ const toolUses = toolUsesFromContent(response.content);
54
+ if (toolUses.length === 0) {
55
+ finalText = text;
56
+ break;
57
+ }
58
+ const toolResults = [];
59
+ for (const toolUse of toolUses) {
60
+ const toolCall = {
61
+ id: toolUse.id,
62
+ capabilityId,
63
+ toolName: toolUse.name,
64
+ args: toolUse.input,
65
+ };
66
+ trace?.emit({
67
+ type: 'tool_call_start',
68
+ capabilityId,
69
+ toolName: toolUse.name,
70
+ args: toolUse.input,
71
+ timestamp: timestamp(),
72
+ });
73
+ let isError = false;
74
+ let resultContent;
75
+ try {
76
+ const { result, durationMs } = await tools.callTool(toolUse.name, toolUse.input, { signal });
77
+ toolCall.result = result;
78
+ toolCall.durationMs = durationMs;
79
+ resultContent = truncate(JSON.stringify(result));
80
+ }
81
+ catch (error) {
82
+ isError = true;
83
+ const message = error instanceof Error ? error.message : String(error);
84
+ toolCall.error = message;
85
+ resultContent = truncate(JSON.stringify({ error: message }));
86
+ }
87
+ toolCalls.push(toolCall);
88
+ trace?.emit({
89
+ type: 'tool_call_end',
90
+ capabilityId,
91
+ toolName: toolUse.name,
92
+ result: toolCall.result,
93
+ error: toolCall.error,
94
+ durationMs: toolCall.durationMs ?? 0,
95
+ timestamp: timestamp(),
96
+ });
97
+ toolResults.push({
98
+ type: 'tool_result',
99
+ toolUseId: toolUse.id,
100
+ content: resultContent,
101
+ ...(isError ? { isError: true } : {}),
102
+ });
103
+ }
104
+ messages.push({ role: 'user', content: toolResults });
105
+ }
106
+ let parsed = null;
107
+ if (options.parseResult) {
108
+ parsed = options.parseResult(finalText);
109
+ if (parsed === null && options.recoveryPrompt) {
110
+ const recoveryText = await runRecoveryTurn(options, options.recoveryPrompt(toolCalls));
111
+ parsed = recoveryText === null ? null : options.parseResult(recoveryText);
112
+ }
113
+ }
114
+ return { finalText, toolCalls, parsed };
115
+ }
116
+ async function runRecoveryTurn(options, userPrompt) {
117
+ try {
118
+ options.signal?.throwIfAborted();
119
+ const response = await options.model.complete({
120
+ system: 'You are concluding a completed investigation. Output ONLY the structured answer in the requested shape. Never ask for more data.',
121
+ messages: [{ role: 'user', content: userPrompt }],
122
+ maxTokens: 2048,
123
+ signal: options.signal,
124
+ });
125
+ return textFromContent(response.content);
126
+ }
127
+ catch (error) {
128
+ if (error instanceof DOMException && error.name === 'AbortError')
129
+ throw error;
130
+ options.trace?.emit({
131
+ type: 'warning',
132
+ capabilityId: options.capabilityId,
133
+ message: error instanceof Error ? error.message : String(error),
134
+ timestamp: timestamp(),
135
+ });
136
+ return null;
137
+ }
138
+ }