@namzu/sdk 0.4.2 → 0.4.3
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/CHANGELOG.md +10 -0
- package/dist/advisory/context.test.d.ts +16 -0
- package/dist/advisory/context.test.d.ts.map +1 -0
- package/dist/advisory/context.test.js +92 -0
- package/dist/advisory/context.test.js.map +1 -0
- package/dist/advisory/evaluator.test.d.ts +34 -0
- package/dist/advisory/evaluator.test.d.ts.map +1 -0
- package/dist/advisory/evaluator.test.js +172 -0
- package/dist/advisory/evaluator.test.js.map +1 -0
- package/dist/advisory/executor.test.d.ts +35 -0
- package/dist/advisory/executor.test.d.ts.map +1 -0
- package/dist/advisory/executor.test.js +233 -0
- package/dist/advisory/executor.test.js.map +1 -0
- package/dist/advisory/registry.test.d.ts +16 -0
- package/dist/advisory/registry.test.d.ts.map +1 -0
- package/dist/advisory/registry.test.js +62 -0
- package/dist/advisory/registry.test.js.map +1 -0
- package/dist/bridge/a2a/agent-card.test.d.ts +24 -0
- package/dist/bridge/a2a/agent-card.test.d.ts.map +1 -0
- package/dist/bridge/a2a/agent-card.test.js +118 -0
- package/dist/bridge/a2a/agent-card.test.js.map +1 -0
- package/dist/bridge/a2a/mapper.test.d.ts +29 -0
- package/dist/bridge/a2a/mapper.test.d.ts.map +1 -0
- package/dist/bridge/a2a/mapper.test.js +265 -0
- package/dist/bridge/a2a/mapper.test.js.map +1 -0
- package/dist/bridge/a2a/message.test.d.ts +20 -0
- package/dist/bridge/a2a/message.test.d.ts.map +1 -0
- package/dist/bridge/a2a/message.test.js +116 -0
- package/dist/bridge/a2a/message.test.js.map +1 -0
- package/dist/bridge/a2a/task.test.d.ts +29 -0
- package/dist/bridge/a2a/task.test.d.ts.map +1 -0
- package/dist/bridge/a2a/task.test.js +198 -0
- package/dist/bridge/a2a/task.test.js.map +1 -0
- package/dist/bridge/mcp/connector/adapter.test.d.ts +27 -0
- package/dist/bridge/mcp/connector/adapter.test.d.ts.map +1 -0
- package/dist/bridge/mcp/connector/adapter.test.js +203 -0
- package/dist/bridge/mcp/connector/adapter.test.js.map +1 -0
- package/dist/bridge/sse/mapper.test.d.ts +27 -0
- package/dist/bridge/sse/mapper.test.d.ts.map +1 -0
- package/dist/bridge/sse/mapper.test.js +271 -0
- package/dist/bridge/sse/mapper.test.js.map +1 -0
- package/dist/bridge/tools/connector/adapter.test.d.ts +28 -0
- package/dist/bridge/tools/connector/adapter.test.d.ts.map +1 -0
- package/dist/bridge/tools/connector/adapter.test.js +182 -0
- package/dist/bridge/tools/connector/adapter.test.js.map +1 -0
- package/dist/bridge/tools/connector/definitions.test.d.ts +23 -0
- package/dist/bridge/tools/connector/definitions.test.d.ts.map +1 -0
- package/dist/bridge/tools/connector/definitions.test.js +158 -0
- package/dist/bridge/tools/connector/definitions.test.js.map +1 -0
- package/dist/bridge/tools/connector/router.test.d.ts +21 -0
- package/dist/bridge/tools/connector/router.test.d.ts.map +1 -0
- package/dist/bridge/tools/connector/router.test.js +139 -0
- package/dist/bridge/tools/connector/router.test.js.map +1 -0
- package/dist/bus/breaker.test.d.ts +41 -0
- package/dist/bus/breaker.test.d.ts.map +1 -0
- package/dist/bus/breaker.test.js +242 -0
- package/dist/bus/breaker.test.js.map +1 -0
- package/dist/bus/index.test.d.ts +25 -0
- package/dist/bus/index.test.d.ts.map +1 -0
- package/dist/bus/index.test.js +151 -0
- package/dist/bus/index.test.js.map +1 -0
- package/dist/bus/lock.test.d.ts +44 -0
- package/dist/bus/lock.test.d.ts.map +1 -0
- package/dist/bus/lock.test.js +226 -0
- package/dist/bus/lock.test.js.map +1 -0
- package/dist/bus/ownership.test.d.ts +26 -0
- package/dist/bus/ownership.test.d.ts.map +1 -0
- package/dist/bus/ownership.test.js +205 -0
- package/dist/bus/ownership.test.js.map +1 -0
- package/dist/connector/BaseConnector.test.d.ts +21 -0
- package/dist/connector/BaseConnector.test.d.ts.map +1 -0
- package/dist/connector/BaseConnector.test.js +108 -0
- package/dist/connector/BaseConnector.test.js.map +1 -0
- package/dist/connector/builtins/http.test.d.ts +30 -0
- package/dist/connector/builtins/http.test.d.ts.map +1 -0
- package/dist/connector/builtins/http.test.js +232 -0
- package/dist/connector/builtins/http.test.js.map +1 -0
- package/dist/connector/builtins/webhook.test.d.ts +20 -0
- package/dist/connector/builtins/webhook.test.d.ts.map +1 -0
- package/dist/connector/builtins/webhook.test.js +113 -0
- package/dist/connector/builtins/webhook.test.js.map +1 -0
- package/dist/connector/execution/factory.test.d.ts +16 -0
- package/dist/connector/execution/factory.test.d.ts.map +1 -0
- package/dist/connector/execution/factory.test.js +64 -0
- package/dist/connector/execution/factory.test.js.map +1 -0
- package/dist/connector/execution/remote.test.d.ts +16 -0
- package/dist/connector/execution/remote.test.d.ts.map +1 -0
- package/dist/connector/execution/remote.test.js +53 -0
- package/dist/connector/execution/remote.test.js.map +1 -0
- package/dist/connector/mcp/adapter.test.d.ts +34 -0
- package/dist/connector/mcp/adapter.test.d.ts.map +1 -0
- package/dist/connector/mcp/adapter.test.js +199 -0
- package/dist/connector/mcp/adapter.test.js.map +1 -0
- package/dist/rag/chunking.test.d.ts +20 -0
- package/dist/rag/chunking.test.d.ts.map +1 -0
- package/dist/rag/chunking.test.js +92 -0
- package/dist/rag/chunking.test.js.map +1 -0
- package/dist/rag/context-assembler.test.d.ts +19 -0
- package/dist/rag/context-assembler.test.d.ts.map +1 -0
- package/dist/rag/context-assembler.test.js +98 -0
- package/dist/rag/context-assembler.test.js.map +1 -0
- package/dist/rag/embedding.test.d.ts +19 -0
- package/dist/rag/embedding.test.d.ts.map +1 -0
- package/dist/rag/embedding.test.js +115 -0
- package/dist/rag/embedding.test.js.map +1 -0
- package/dist/rag/ingestion.test.d.ts +22 -0
- package/dist/rag/ingestion.test.d.ts.map +1 -0
- package/dist/rag/ingestion.test.js +99 -0
- package/dist/rag/ingestion.test.js.map +1 -0
- package/dist/rag/knowledge-base.test.d.ts +17 -0
- package/dist/rag/knowledge-base.test.d.ts.map +1 -0
- package/dist/rag/knowledge-base.test.js +77 -0
- package/dist/rag/knowledge-base.test.js.map +1 -0
- package/dist/rag/rag-tool.test.d.ts +21 -0
- package/dist/rag/rag-tool.test.d.ts.map +1 -0
- package/dist/rag/rag-tool.test.js +149 -0
- package/dist/rag/rag-tool.test.js.map +1 -0
- package/dist/rag/retriever.test.d.ts +26 -0
- package/dist/rag/retriever.test.d.ts.map +1 -0
- package/dist/rag/retriever.test.js +180 -0
- package/dist/rag/retriever.test.js.map +1 -0
- package/dist/rag/vector-store.test.d.ts +38 -0
- package/dist/rag/vector-store.test.d.ts.map +1 -0
- package/dist/rag/vector-store.test.js +175 -0
- package/dist/rag/vector-store.test.js.map +1 -0
- package/dist/registry/ManagedRegistry.test.d.ts +21 -0
- package/dist/registry/ManagedRegistry.test.d.ts.map +1 -0
- package/dist/registry/ManagedRegistry.test.js +98 -0
- package/dist/registry/ManagedRegistry.test.js.map +1 -0
- package/dist/registry/Registry.test.d.ts +18 -0
- package/dist/registry/Registry.test.d.ts.map +1 -0
- package/dist/registry/Registry.test.js +79 -0
- package/dist/registry/Registry.test.js.map +1 -0
- package/dist/registry/agent/definitions.test.d.ts +15 -0
- package/dist/registry/agent/definitions.test.d.ts.map +1 -0
- package/dist/registry/agent/definitions.test.js +84 -0
- package/dist/registry/agent/definitions.test.js.map +1 -0
- package/dist/registry/connector/definitions.test.d.ts +13 -0
- package/dist/registry/connector/definitions.test.d.ts.map +1 -0
- package/dist/registry/connector/definitions.test.js +41 -0
- package/dist/registry/connector/definitions.test.js.map +1 -0
- package/dist/registry/connector/scoped.test.d.ts +21 -0
- package/dist/registry/connector/scoped.test.d.ts.map +1 -0
- package/dist/registry/connector/scoped.test.js +115 -0
- package/dist/registry/connector/scoped.test.js.map +1 -0
- package/dist/registry/plugin/index.test.d.ts +12 -0
- package/dist/registry/plugin/index.test.d.ts.map +1 -0
- package/dist/registry/plugin/index.test.js +69 -0
- package/dist/registry/plugin/index.test.js.map +1 -0
- package/dist/registry/tool/execute.test.d.ts +42 -0
- package/dist/registry/tool/execute.test.d.ts.map +1 -0
- package/dist/registry/tool/execute.test.js +281 -0
- package/dist/registry/tool/execute.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/advisory.test.d.ts +42 -0
- package/dist/runtime/query/iteration/phases/advisory.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/advisory.test.js +334 -0
- package/dist/runtime/query/iteration/phases/advisory.test.js.map +1 -0
- package/dist/test-setup.d.ts +22 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/test-setup.js +23 -0
- package/dist/test-setup.js.map +1 -0
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +5 -0
- package/dist/utils/logger.js.map +1 -1
- package/package.json +4 -1
- package/src/advisory/context.test.ts +109 -0
- package/src/advisory/evaluator.test.ts +192 -0
- package/src/advisory/executor.test.ts +272 -0
- package/src/advisory/registry.test.ts +75 -0
- package/src/bridge/a2a/agent-card.test.ts +140 -0
- package/src/bridge/a2a/mapper.test.ts +293 -0
- package/src/bridge/a2a/message.test.ts +138 -0
- package/src/bridge/a2a/task.test.ts +235 -0
- package/src/bridge/mcp/connector/adapter.test.ts +230 -0
- package/src/bridge/sse/mapper.test.ts +422 -0
- package/src/bridge/tools/connector/adapter.test.ts +224 -0
- package/src/bridge/tools/connector/definitions.test.ts +183 -0
- package/src/bridge/tools/connector/router.test.ts +159 -0
- package/src/bus/breaker.test.ts +274 -0
- package/src/bus/index.test.ts +183 -0
- package/src/bus/lock.test.ts +265 -0
- package/src/bus/ownership.test.ts +243 -0
- package/src/connector/BaseConnector.test.ts +130 -0
- package/src/connector/builtins/http.test.ts +290 -0
- package/src/connector/builtins/webhook.test.ts +138 -0
- package/src/connector/execution/factory.test.ts +75 -0
- package/src/connector/execution/remote.test.ts +63 -0
- package/src/connector/mcp/adapter.test.ts +249 -0
- package/src/rag/chunking.test.ts +107 -0
- package/src/rag/context-assembler.test.ts +114 -0
- package/src/rag/embedding.test.ts +130 -0
- package/src/rag/ingestion.test.ts +114 -0
- package/src/rag/knowledge-base.test.ts +106 -0
- package/src/rag/rag-tool.test.ts +167 -0
- package/src/rag/retriever.test.ts +210 -0
- package/src/rag/vector-store.test.ts +196 -0
- package/src/registry/ManagedRegistry.test.ts +118 -0
- package/src/registry/Registry.test.ts +91 -0
- package/src/registry/agent/definitions.test.ts +100 -0
- package/src/registry/connector/definitions.test.ts +51 -0
- package/src/registry/connector/scoped.test.ts +129 -0
- package/src/registry/plugin/index.test.ts +85 -0
- package/src/registry/tool/execute.test.ts +330 -0
- package/src/runtime/query/iteration/phases/advisory.test.ts +412 -0
- package/src/test-setup.ts +24 -0
- package/src/utils/logger.ts +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ddd0aad: Test-side hardening from ses_006 pre-freeze fix.
|
|
8
|
+
|
|
9
|
+
- **New test: `runtime/query/iteration/phases/advisory.test.ts`** — pins the advisory-phase mutation boundary where fired advisories inject user messages via `runMgr.pushMessage(createUserMessage(...))`. 13 assertions covering early-return paths, happy-path exactly-once calls, envelope format, warnings + decisions rendering, and trigger-selection semantics. Before this test a regression removing the `pushMessage` call at `advisory.ts:154` would pass typecheck, lint, the coverage gate, and every existing `src/advisory/*` test. It now fails deterministically.
|
|
10
|
+
- **`LogLevel` gains `'silent'`** — purely additive; the value short-circuits every `log()` call. Used by the SDK's vitest setup to suppress unmocked `getRootLogger()` stderr writes so GitHub Actions stops annotating `[ERROR]`-level log lines as workflow errors. Consumer impact: zero unless you pass `'silent'` to `configureLogger()` yourself.
|
|
11
|
+
- No runtime behavior change. No public surface additions beyond the one `LogLevel` union member.
|
|
12
|
+
|
|
3
13
|
## 0.4.2
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Current-code invariants asserted (2026-04-21, ses_006 Phase 6):
|
|
3
|
+
*
|
|
4
|
+
* - `AdvisoryContext` composes a registry + executor + evaluator +
|
|
5
|
+
* optional budget. It is a dumb container + a call-history log.
|
|
6
|
+
* - `recordCall(record)` appends to `callHistory` in call order.
|
|
7
|
+
* - `getBudgetStatus()`:
|
|
8
|
+
* - `used` = `callHistory.length`.
|
|
9
|
+
* - `total` = `budget?.maxCallsPerRun` (undefined when no budget).
|
|
10
|
+
* - `remaining` = total − used when total defined; undefined else.
|
|
11
|
+
* - `checkBudget()`:
|
|
12
|
+
* - Allowed when no budget OR remaining > 0.
|
|
13
|
+
* - Denied with reason when remaining ≤ 0.
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=context.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.test.d.ts","sourceRoot":"","sources":["../../src/advisory/context.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Current-code invariants asserted (2026-04-21, ses_006 Phase 6):
|
|
3
|
+
*
|
|
4
|
+
* - `AdvisoryContext` composes a registry + executor + evaluator +
|
|
5
|
+
* optional budget. It is a dumb container + a call-history log.
|
|
6
|
+
* - `recordCall(record)` appends to `callHistory` in call order.
|
|
7
|
+
* - `getBudgetStatus()`:
|
|
8
|
+
* - `used` = `callHistory.length`.
|
|
9
|
+
* - `total` = `budget?.maxCallsPerRun` (undefined when no budget).
|
|
10
|
+
* - `remaining` = total − used when total defined; undefined else.
|
|
11
|
+
* - `checkBudget()`:
|
|
12
|
+
* - Allowed when no budget OR remaining > 0.
|
|
13
|
+
* - Denied with reason when remaining ≤ 0.
|
|
14
|
+
*/
|
|
15
|
+
import { describe, expect, it } from 'vitest';
|
|
16
|
+
import { AdvisoryContext } from './context.js';
|
|
17
|
+
function stubRegistry() {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
function stubExecutor() {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
function stubEvaluator() {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
function callRecord(id) {
|
|
27
|
+
return {
|
|
28
|
+
advisorId: id,
|
|
29
|
+
request: { question: 'q' },
|
|
30
|
+
result: { advice: 'a' },
|
|
31
|
+
usage: {
|
|
32
|
+
promptTokens: 0,
|
|
33
|
+
completionTokens: 0,
|
|
34
|
+
totalTokens: 0,
|
|
35
|
+
cachedTokens: 0,
|
|
36
|
+
cacheWriteTokens: 0,
|
|
37
|
+
},
|
|
38
|
+
cost: { inputCostPer1M: 0, outputCostPer1M: 0, totalCost: 0, cacheDiscount: 0 },
|
|
39
|
+
durationMs: 0,
|
|
40
|
+
iteration: 0,
|
|
41
|
+
timestamp: Date.now(),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
describe('AdvisoryContext', () => {
|
|
45
|
+
it('starts with empty callHistory', () => {
|
|
46
|
+
const ctx = new AdvisoryContext(stubRegistry(), stubExecutor(), stubEvaluator());
|
|
47
|
+
expect(ctx.callHistory).toEqual([]);
|
|
48
|
+
});
|
|
49
|
+
it('recordCall appends in order', () => {
|
|
50
|
+
const ctx = new AdvisoryContext(stubRegistry(), stubExecutor(), stubEvaluator());
|
|
51
|
+
ctx.recordCall(callRecord('a'));
|
|
52
|
+
ctx.recordCall(callRecord('b'));
|
|
53
|
+
expect(ctx.callHistory.map((r) => r.advisorId)).toEqual(['a', 'b']);
|
|
54
|
+
});
|
|
55
|
+
describe('getBudgetStatus', () => {
|
|
56
|
+
it('no budget → total + remaining undefined; used reflects history length', () => {
|
|
57
|
+
const ctx = new AdvisoryContext(stubRegistry(), stubExecutor(), stubEvaluator());
|
|
58
|
+
ctx.recordCall(callRecord('a'));
|
|
59
|
+
expect(ctx.getBudgetStatus()).toEqual({ used: 1, total: undefined, remaining: undefined });
|
|
60
|
+
});
|
|
61
|
+
it('with budget → total + remaining computed', () => {
|
|
62
|
+
const ctx = new AdvisoryContext(stubRegistry(), stubExecutor(), stubEvaluator(), {
|
|
63
|
+
maxCallsPerRun: 3,
|
|
64
|
+
});
|
|
65
|
+
ctx.recordCall(callRecord('a'));
|
|
66
|
+
expect(ctx.getBudgetStatus()).toEqual({ used: 1, total: 3, remaining: 2 });
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('checkBudget', () => {
|
|
70
|
+
it('allowed when no budget', () => {
|
|
71
|
+
const ctx = new AdvisoryContext(stubRegistry(), stubExecutor(), stubEvaluator());
|
|
72
|
+
expect(ctx.checkBudget()).toEqual({ allowed: true });
|
|
73
|
+
});
|
|
74
|
+
it('allowed when remaining > 0', () => {
|
|
75
|
+
const ctx = new AdvisoryContext(stubRegistry(), stubExecutor(), stubEvaluator(), {
|
|
76
|
+
maxCallsPerRun: 2,
|
|
77
|
+
});
|
|
78
|
+
ctx.recordCall(callRecord('a'));
|
|
79
|
+
expect(ctx.checkBudget()).toEqual({ allowed: true });
|
|
80
|
+
});
|
|
81
|
+
it('denied when remaining <= 0', () => {
|
|
82
|
+
const ctx = new AdvisoryContext(stubRegistry(), stubExecutor(), stubEvaluator(), {
|
|
83
|
+
maxCallsPerRun: 1,
|
|
84
|
+
});
|
|
85
|
+
ctx.recordCall(callRecord('a'));
|
|
86
|
+
const result = ctx.checkBudget();
|
|
87
|
+
expect(result.allowed).toBe(false);
|
|
88
|
+
expect(result.reason).toMatch(/budget exhausted/);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
//# sourceMappingURL=context.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.test.js","sourceRoot":"","sources":["../../src/advisory/context.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Current-code invariants asserted (2026-04-21, ses_006 Phase 6):
|
|
3
|
+
*
|
|
4
|
+
* - Constructor filters OUT triggers with `enabled: false` + sorts
|
|
5
|
+
* by `priority` desc (higher first). Triggers without priority
|
|
6
|
+
* sort at 0.
|
|
7
|
+
* - `evaluate(state)` returns every trigger whose condition matches
|
|
8
|
+
* AND whose cooldown has elapsed. Order reflects the sorted
|
|
9
|
+
* trigger list.
|
|
10
|
+
* - Budget exhausted (callCount ≥ maxCallsPerRun) → evaluate returns [].
|
|
11
|
+
* - `recordFiring(id, iteration)` updates both `lastFiredMap` (for
|
|
12
|
+
* cooldown) and `callCount` (for budget).
|
|
13
|
+
* - Condition matchers:
|
|
14
|
+
* - `on_error`: matches iff state.lastError is set; if categories
|
|
15
|
+
* are given, requires at least one category substring match.
|
|
16
|
+
* - `on_iteration`: `state.iteration % everyN === 0`.
|
|
17
|
+
* - `on_context_percent`: `state.contextWindowPercent >= threshold`.
|
|
18
|
+
* - `on_tool_category`: `state.lastToolCategory` present + in
|
|
19
|
+
* `condition.categories`.
|
|
20
|
+
* - `on_cost_percent`: costBudgetPercent present + ≥ threshold.
|
|
21
|
+
* - `on_complexity`: totalToolCalls ≥ toolCallThreshold.
|
|
22
|
+
* - `custom`: calls the predicate.
|
|
23
|
+
* - Cooldown: trigger does NOT fire if `iteration - lastFired <
|
|
24
|
+
* cooldownIterations`. No cooldown → always eligible.
|
|
25
|
+
*
|
|
26
|
+
* - **Purity:** evaluate() is pure relative to the advisory phase —
|
|
27
|
+
* it returns triggers; it does NOT inject messages or mutate
|
|
28
|
+
* external state. The injection happens in the advisory phase
|
|
29
|
+
* (runtime/query/iteration/phases/advisory.ts), which pushes a
|
|
30
|
+
* user message via `runMgr.pushMessage`. That file is OUT of
|
|
31
|
+
* scope for this test file (covered by the phase test).
|
|
32
|
+
*/
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=evaluator.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluator.test.d.ts","sourceRoot":"","sources":["../../src/advisory/evaluator.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Current-code invariants asserted (2026-04-21, ses_006 Phase 6):
|
|
3
|
+
*
|
|
4
|
+
* - Constructor filters OUT triggers with `enabled: false` + sorts
|
|
5
|
+
* by `priority` desc (higher first). Triggers without priority
|
|
6
|
+
* sort at 0.
|
|
7
|
+
* - `evaluate(state)` returns every trigger whose condition matches
|
|
8
|
+
* AND whose cooldown has elapsed. Order reflects the sorted
|
|
9
|
+
* trigger list.
|
|
10
|
+
* - Budget exhausted (callCount ≥ maxCallsPerRun) → evaluate returns [].
|
|
11
|
+
* - `recordFiring(id, iteration)` updates both `lastFiredMap` (for
|
|
12
|
+
* cooldown) and `callCount` (for budget).
|
|
13
|
+
* - Condition matchers:
|
|
14
|
+
* - `on_error`: matches iff state.lastError is set; if categories
|
|
15
|
+
* are given, requires at least one category substring match.
|
|
16
|
+
* - `on_iteration`: `state.iteration % everyN === 0`.
|
|
17
|
+
* - `on_context_percent`: `state.contextWindowPercent >= threshold`.
|
|
18
|
+
* - `on_tool_category`: `state.lastToolCategory` present + in
|
|
19
|
+
* `condition.categories`.
|
|
20
|
+
* - `on_cost_percent`: costBudgetPercent present + ≥ threshold.
|
|
21
|
+
* - `on_complexity`: totalToolCalls ≥ toolCallThreshold.
|
|
22
|
+
* - `custom`: calls the predicate.
|
|
23
|
+
* - Cooldown: trigger does NOT fire if `iteration - lastFired <
|
|
24
|
+
* cooldownIterations`. No cooldown → always eligible.
|
|
25
|
+
*
|
|
26
|
+
* - **Purity:** evaluate() is pure relative to the advisory phase —
|
|
27
|
+
* it returns triggers; it does NOT inject messages or mutate
|
|
28
|
+
* external state. The injection happens in the advisory phase
|
|
29
|
+
* (runtime/query/iteration/phases/advisory.ts), which pushes a
|
|
30
|
+
* user message via `runMgr.pushMessage`. That file is OUT of
|
|
31
|
+
* scope for this test file (covered by the phase test).
|
|
32
|
+
*/
|
|
33
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
34
|
+
import { TriggerEvaluator } from './evaluator.js';
|
|
35
|
+
function trigger(id, overrides = {}) {
|
|
36
|
+
return {
|
|
37
|
+
id,
|
|
38
|
+
condition: { type: 'on_iteration', everyN: 1 },
|
|
39
|
+
...overrides,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function state(overrides = {}) {
|
|
43
|
+
return {
|
|
44
|
+
iteration: 1,
|
|
45
|
+
totalToolCalls: 0,
|
|
46
|
+
totalTokens: 0,
|
|
47
|
+
contextWindowPercent: 0,
|
|
48
|
+
totalCostUsd: 0,
|
|
49
|
+
costBudgetPercent: undefined,
|
|
50
|
+
lastError: undefined,
|
|
51
|
+
lastToolCategory: undefined,
|
|
52
|
+
advisoryCallCount: 0,
|
|
53
|
+
...overrides,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
describe('TriggerEvaluator — constructor', () => {
|
|
57
|
+
it('filters out triggers with enabled: false', () => {
|
|
58
|
+
const e = new TriggerEvaluator([trigger('a'), trigger('b', { enabled: false })]);
|
|
59
|
+
expect(e.evaluate(state()).map((t) => t.id)).toEqual(['a']);
|
|
60
|
+
});
|
|
61
|
+
it('sorts triggers by priority descending', () => {
|
|
62
|
+
const e = new TriggerEvaluator([
|
|
63
|
+
trigger('low', { priority: 1 }),
|
|
64
|
+
trigger('high', { priority: 10 }),
|
|
65
|
+
trigger('mid', { priority: 5 }),
|
|
66
|
+
]);
|
|
67
|
+
expect(e.evaluate(state()).map((t) => t.id)).toEqual(['high', 'mid', 'low']);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('TriggerEvaluator — budget', () => {
|
|
71
|
+
it('returns [] when callCount >= maxCallsPerRun', () => {
|
|
72
|
+
const e = new TriggerEvaluator([trigger('a')], { maxCallsPerRun: 1 });
|
|
73
|
+
e.recordFiring('a', 1);
|
|
74
|
+
expect(e.evaluate(state({ iteration: 2 }))).toEqual([]);
|
|
75
|
+
});
|
|
76
|
+
it('ignores budget when maxCallsPerRun is not set', () => {
|
|
77
|
+
const e = new TriggerEvaluator([trigger('a')]);
|
|
78
|
+
e.recordFiring('a', 1);
|
|
79
|
+
e.recordFiring('a', 2);
|
|
80
|
+
expect(e.evaluate(state({ iteration: 3 })).length).toBeGreaterThan(0);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe('TriggerEvaluator — cooldown', () => {
|
|
84
|
+
it('blocks triggers in their cooldown window', () => {
|
|
85
|
+
const e = new TriggerEvaluator([trigger('a', { cooldownIterations: 3 })]);
|
|
86
|
+
e.recordFiring('a', 1);
|
|
87
|
+
expect(e.evaluate(state({ iteration: 2 })).map((t) => t.id)).toEqual([]);
|
|
88
|
+
expect(e.evaluate(state({ iteration: 3 })).map((t) => t.id)).toEqual([]);
|
|
89
|
+
expect(e.evaluate(state({ iteration: 4 })).map((t) => t.id)).toEqual(['a']);
|
|
90
|
+
});
|
|
91
|
+
it('always fires when no cooldown is configured', () => {
|
|
92
|
+
const e = new TriggerEvaluator([trigger('a')]);
|
|
93
|
+
e.recordFiring('a', 1);
|
|
94
|
+
expect(e.evaluate(state({ iteration: 2 })).map((t) => t.id)).toEqual(['a']);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe('TriggerEvaluator — condition matchers', () => {
|
|
98
|
+
it('on_error: requires lastError to be set', () => {
|
|
99
|
+
const e = new TriggerEvaluator([trigger('t', { condition: { type: 'on_error' } })]);
|
|
100
|
+
expect(e.evaluate(state())).toEqual([]);
|
|
101
|
+
expect(e.evaluate(state({ lastError: 'boom' })).map((t) => t.id)).toEqual(['t']);
|
|
102
|
+
});
|
|
103
|
+
it('on_error with categories: at least one substring match', () => {
|
|
104
|
+
const e = new TriggerEvaluator([
|
|
105
|
+
trigger('t', { condition: { type: 'on_error', categories: ['timeout', 'permission'] } }),
|
|
106
|
+
]);
|
|
107
|
+
expect(e.evaluate(state({ lastError: 'Connection timeout' })).map((t) => t.id)).toEqual(['t']);
|
|
108
|
+
expect(e.evaluate(state({ lastError: 'Syntax error' }))).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
it('on_iteration: iteration % everyN === 0', () => {
|
|
111
|
+
const e = new TriggerEvaluator([
|
|
112
|
+
trigger('t', { condition: { type: 'on_iteration', everyN: 3 } }),
|
|
113
|
+
]);
|
|
114
|
+
expect(e.evaluate(state({ iteration: 1 }))).toEqual([]);
|
|
115
|
+
expect(e.evaluate(state({ iteration: 3 })).map((t) => t.id)).toEqual(['t']);
|
|
116
|
+
expect(e.evaluate(state({ iteration: 6 })).map((t) => t.id)).toEqual(['t']);
|
|
117
|
+
});
|
|
118
|
+
it('on_context_percent: contextWindowPercent >= threshold', () => {
|
|
119
|
+
const e = new TriggerEvaluator([
|
|
120
|
+
trigger('t', { condition: { type: 'on_context_percent', threshold: 80 } }),
|
|
121
|
+
]);
|
|
122
|
+
expect(e.evaluate(state({ contextWindowPercent: 70 }))).toEqual([]);
|
|
123
|
+
expect(e.evaluate(state({ contextWindowPercent: 80 })).map((t) => t.id)).toEqual(['t']);
|
|
124
|
+
});
|
|
125
|
+
it('on_tool_category: lastToolCategory present + in categories list', () => {
|
|
126
|
+
const e = new TriggerEvaluator([
|
|
127
|
+
trigger('t', {
|
|
128
|
+
condition: { type: 'on_tool_category', categories: ['network', 'filesystem'] },
|
|
129
|
+
}),
|
|
130
|
+
]);
|
|
131
|
+
expect(e.evaluate(state())).toEqual([]);
|
|
132
|
+
expect(e.evaluate(state({ lastToolCategory: 'other' }))).toEqual([]);
|
|
133
|
+
expect(e.evaluate(state({ lastToolCategory: 'network' })).map((t) => t.id)).toEqual(['t']);
|
|
134
|
+
});
|
|
135
|
+
it('on_cost_percent: costBudgetPercent present + >= threshold', () => {
|
|
136
|
+
const e = new TriggerEvaluator([
|
|
137
|
+
trigger('t', { condition: { type: 'on_cost_percent', threshold: 75 } }),
|
|
138
|
+
]);
|
|
139
|
+
expect(e.evaluate(state())).toEqual([]); // undefined costBudgetPercent
|
|
140
|
+
expect(e.evaluate(state({ costBudgetPercent: 50 }))).toEqual([]);
|
|
141
|
+
expect(e.evaluate(state({ costBudgetPercent: 80 })).map((t) => t.id)).toEqual(['t']);
|
|
142
|
+
});
|
|
143
|
+
it('on_complexity: totalToolCalls >= toolCallThreshold', () => {
|
|
144
|
+
const e = new TriggerEvaluator([
|
|
145
|
+
trigger('t', { condition: { type: 'on_complexity', toolCallThreshold: 10 } }),
|
|
146
|
+
]);
|
|
147
|
+
expect(e.evaluate(state({ totalToolCalls: 5 }))).toEqual([]);
|
|
148
|
+
expect(e.evaluate(state({ totalToolCalls: 10 })).map((t) => t.id)).toEqual(['t']);
|
|
149
|
+
});
|
|
150
|
+
it('custom: calls the predicate with state', () => {
|
|
151
|
+
const predicate = vi.fn(() => true);
|
|
152
|
+
const e = new TriggerEvaluator([trigger('t', { condition: { type: 'custom', predicate } })]);
|
|
153
|
+
const s = state({ iteration: 42 });
|
|
154
|
+
expect(e.evaluate(s).map((t) => t.id)).toEqual(['t']);
|
|
155
|
+
expect(predicate).toHaveBeenCalledWith(s);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
describe('TriggerEvaluator — recordFiring', () => {
|
|
159
|
+
it('updates lastFiredMap + callCount', () => {
|
|
160
|
+
const e = new TriggerEvaluator([trigger('a', { cooldownIterations: 5 })], {
|
|
161
|
+
maxCallsPerRun: 2,
|
|
162
|
+
});
|
|
163
|
+
e.recordFiring('a', 1);
|
|
164
|
+
// cooldown active: no fires in iterations 2–5
|
|
165
|
+
expect(e.evaluate(state({ iteration: 2 }))).toEqual([]);
|
|
166
|
+
// cooldown lifted at 6, but budget allows one more
|
|
167
|
+
e.recordFiring('a', 6);
|
|
168
|
+
// budget exhausted
|
|
169
|
+
expect(e.evaluate(state({ iteration: 11 }))).toEqual([]);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
//# sourceMappingURL=evaluator.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluator.test.js","sourceRoot":"","sources":["../../src/advisory/evaluator.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAIjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAEjD,SAAS,OAAO,CAAC,EAAU,EAAE,YAAsC,EAAE;IACpE,OAAO;QACN,EAAE;QACF,SAAS,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,EAAE;QAC9C,GAAG,SAAS;KACZ,CAAA;AACF,CAAC;AAED,SAAS,KAAK,CAAC,YAA6C,EAAE;IAC7D,OAAO;QACN,SAAS,EAAE,CAAC;QACZ,cAAc,EAAE,CAAC;QACjB,WAAW,EAAE,CAAC;QACd,oBAAoB,EAAE,CAAC;QACvB,YAAY,EAAE,CAAC;QACf,iBAAiB,EAAE,SAAS;QAC5B,SAAS,EAAE,SAAS;QACpB,gBAAgB,EAAE,SAAS;QAC3B,iBAAiB,EAAE,CAAC;QACpB,GAAG,SAAS;KACZ,CAAA;AACF,CAAC;AAED,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC/C,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;QAChF,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC;YAC9B,OAAO,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;YAC/B,OAAO,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;YACjC,OAAO,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;SAC/B,CAAC,CAAA;QACF,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;IAC7E,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC,CAAA;QACrE,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACtB,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACxD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QAC9C,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACtB,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACtB,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;IACtE,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;IAC5C,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,kBAAkB,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QACzE,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACtB,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACxE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACxE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAC5E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QAC9C,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACtB,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAC5E,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,uCAAuC,EAAE,GAAG,EAAE;IACtD,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;QACnF,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACvC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IACjF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QACjE,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC;YAC9B,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,EAAE,CAAC;SACxF,CAAC,CAAA;QACF,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,oBAAoB,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QAC9F,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACrE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC;YAC9B,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;SAChE,CAAC,CAAA;QACF,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACvD,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QAC3E,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAC5E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAChE,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC;YAC9B,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC;SAC1E,CAAC,CAAA;QACF,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,oBAAoB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACnE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,oBAAoB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IACxF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QAC1E,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC;YAC9B,OAAO,CAAC,GAAG,EAAE;gBACZ,SAAS,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,UAAU,EAAE,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE;aAC9E,CAAC;SACF,CAAC,CAAA;QACF,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACvC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACpE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAC3F,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACpE,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC;YAC9B,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC;SACvE,CAAC,CAAA;QACF,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA,CAAC,8BAA8B;QACtE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAChE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IACrF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC;YAC9B,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,iBAAiB,EAAE,EAAE,EAAE,EAAE,CAAC;SAC7E,CAAC,CAAA;QACF,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAC5D,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAClF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QACjD,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;QACnC,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;QAC5F,MAAM,CAAC,GAAG,KAAK,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAA;QAClC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QACrD,MAAM,CAAC,SAAS,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAA;IAC1C,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAChD,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC3C,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,kBAAkB,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE;YACzE,cAAc,EAAE,CAAC;SACjB,CAAC,CAAA;QACF,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACtB,8CAA8C;QAC9C,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACvD,mDAAmD;QACnD,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACtB,mBAAmB;QACnB,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACzD,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Current-code invariants asserted (2026-04-21, ses_006 Phase 6):
|
|
3
|
+
*
|
|
4
|
+
* - `AdvisoryExecutor.consult(advisor, request, callCtx)`:
|
|
5
|
+
* - Builds a system prompt (see buildSystemPrompt tests).
|
|
6
|
+
* - Builds a context message block (see buildContext tests).
|
|
7
|
+
* - Concatenates `[system, ...context, user(question)]` and calls
|
|
8
|
+
* `advisor.provider.chat(...)` with `toolChoice: 'none'`.
|
|
9
|
+
* - Parses the response via a passthrough `{ advice: rawContent }`
|
|
10
|
+
* shape (no structured parsing yet — advisor.ts line 166-168).
|
|
11
|
+
* - Returns `{result, usage, cost, durationMs}`. `cost` is a
|
|
12
|
+
* zero-value `CostInfo` — pricing is provider-specific and not
|
|
13
|
+
* applied here.
|
|
14
|
+
*
|
|
15
|
+
* - `buildSystemPrompt` priority:
|
|
16
|
+
* 1. `advisor.systemPrompt` (verbatim).
|
|
17
|
+
* 2. `advisor.persona` (via `assembleSystemPrompt`).
|
|
18
|
+
* 3. Fallback: "You are <name>, an advisory agent." + optional
|
|
19
|
+
* domains line + "Provide concise, actionable advice..."
|
|
20
|
+
*
|
|
21
|
+
* - `buildContext`:
|
|
22
|
+
* - Returns [] when `request.includeContext === false`.
|
|
23
|
+
* - Includes workingStateSummary when present.
|
|
24
|
+
* - Includes toolCatalog names when present + non-empty.
|
|
25
|
+
* - Includes truncated conversation context (most-recent-first
|
|
26
|
+
* walk, bounded by `advisor.maxContextTokens * CHARS_PER_TOKEN`).
|
|
27
|
+
* - Returns [] when no context parts were assembled.
|
|
28
|
+
*
|
|
29
|
+
* - `truncateMessages(msgs, maxTokens)` walks right-to-left and
|
|
30
|
+
* includes messages until the char budget is exhausted (in token
|
|
31
|
+
* terms). Returns the included subset preserving original order.
|
|
32
|
+
* No limit when `maxTokens` is undefined.
|
|
33
|
+
*/
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=executor.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.test.d.ts","sourceRoot":"","sources":["../../src/advisory/executor.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Current-code invariants asserted (2026-04-21, ses_006 Phase 6):
|
|
3
|
+
*
|
|
4
|
+
* - `AdvisoryExecutor.consult(advisor, request, callCtx)`:
|
|
5
|
+
* - Builds a system prompt (see buildSystemPrompt tests).
|
|
6
|
+
* - Builds a context message block (see buildContext tests).
|
|
7
|
+
* - Concatenates `[system, ...context, user(question)]` and calls
|
|
8
|
+
* `advisor.provider.chat(...)` with `toolChoice: 'none'`.
|
|
9
|
+
* - Parses the response via a passthrough `{ advice: rawContent }`
|
|
10
|
+
* shape (no structured parsing yet — advisor.ts line 166-168).
|
|
11
|
+
* - Returns `{result, usage, cost, durationMs}`. `cost` is a
|
|
12
|
+
* zero-value `CostInfo` — pricing is provider-specific and not
|
|
13
|
+
* applied here.
|
|
14
|
+
*
|
|
15
|
+
* - `buildSystemPrompt` priority:
|
|
16
|
+
* 1. `advisor.systemPrompt` (verbatim).
|
|
17
|
+
* 2. `advisor.persona` (via `assembleSystemPrompt`).
|
|
18
|
+
* 3. Fallback: "You are <name>, an advisory agent." + optional
|
|
19
|
+
* domains line + "Provide concise, actionable advice..."
|
|
20
|
+
*
|
|
21
|
+
* - `buildContext`:
|
|
22
|
+
* - Returns [] when `request.includeContext === false`.
|
|
23
|
+
* - Includes workingStateSummary when present.
|
|
24
|
+
* - Includes toolCatalog names when present + non-empty.
|
|
25
|
+
* - Includes truncated conversation context (most-recent-first
|
|
26
|
+
* walk, bounded by `advisor.maxContextTokens * CHARS_PER_TOKEN`).
|
|
27
|
+
* - Returns [] when no context parts were assembled.
|
|
28
|
+
*
|
|
29
|
+
* - `truncateMessages(msgs, maxTokens)` walks right-to-left and
|
|
30
|
+
* includes messages until the char budget is exhausted (in token
|
|
31
|
+
* terms). Returns the included subset preserving original order.
|
|
32
|
+
* No limit when `maxTokens` is undefined.
|
|
33
|
+
*/
|
|
34
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
35
|
+
import { AdvisoryExecutor } from './executor.js';
|
|
36
|
+
function mockProvider(response = {}) {
|
|
37
|
+
const chat = vi.fn(async () => ({
|
|
38
|
+
id: 'resp_1',
|
|
39
|
+
model: 'm',
|
|
40
|
+
message: { role: 'assistant', content: 'advice text' },
|
|
41
|
+
usage: {
|
|
42
|
+
promptTokens: 100,
|
|
43
|
+
completionTokens: 50,
|
|
44
|
+
totalTokens: 150,
|
|
45
|
+
cachedTokens: 0,
|
|
46
|
+
cacheWriteTokens: 0,
|
|
47
|
+
},
|
|
48
|
+
finishReason: 'stop',
|
|
49
|
+
...response,
|
|
50
|
+
}));
|
|
51
|
+
return {
|
|
52
|
+
id: 'mock',
|
|
53
|
+
name: 'Mock',
|
|
54
|
+
chat,
|
|
55
|
+
chatStream: vi.fn(),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function advisor(overrides = {}) {
|
|
59
|
+
return {
|
|
60
|
+
id: 'adv',
|
|
61
|
+
name: 'Adv',
|
|
62
|
+
provider: mockProvider(),
|
|
63
|
+
model: 'm',
|
|
64
|
+
...overrides,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function ctx(overrides = {}) {
|
|
68
|
+
return {
|
|
69
|
+
messages: [],
|
|
70
|
+
iteration: 1,
|
|
71
|
+
...overrides,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const req = { question: 'what next?' };
|
|
75
|
+
describe('AdvisoryExecutor — consult happy path', () => {
|
|
76
|
+
it('calls provider.chat with system + question, toolChoice none', async () => {
|
|
77
|
+
const provider = mockProvider();
|
|
78
|
+
const e = new AdvisoryExecutor();
|
|
79
|
+
const a = advisor({ provider, systemPrompt: 'You are Adv.' });
|
|
80
|
+
await e.consult(a, req, ctx());
|
|
81
|
+
const call = vi.mocked(provider.chat).mock.calls[0]?.[0];
|
|
82
|
+
expect(call.model).toBe('m');
|
|
83
|
+
expect(call.toolChoice).toBe('none');
|
|
84
|
+
const roles = call.messages.map((m) => m.role);
|
|
85
|
+
expect(roles[0]).toBe('system');
|
|
86
|
+
expect(roles.at(-1)).toBe('user');
|
|
87
|
+
});
|
|
88
|
+
it('returns {result, usage, cost, durationMs}; cost is zero-valued', async () => {
|
|
89
|
+
const e = new AdvisoryExecutor();
|
|
90
|
+
const out = await e.consult(advisor(), req, ctx());
|
|
91
|
+
expect(out.result.advice).toBe('advice text');
|
|
92
|
+
expect(out.usage.totalTokens).toBe(150);
|
|
93
|
+
expect(out.cost).toEqual({
|
|
94
|
+
inputCostPer1M: 0,
|
|
95
|
+
outputCostPer1M: 0,
|
|
96
|
+
totalCost: 0,
|
|
97
|
+
cacheDiscount: 0,
|
|
98
|
+
});
|
|
99
|
+
expect(typeof out.durationMs).toBe('number');
|
|
100
|
+
});
|
|
101
|
+
it('parseResult is currently a passthrough — advice = content verbatim', async () => {
|
|
102
|
+
const provider = mockProvider({
|
|
103
|
+
message: { role: 'assistant', content: 'Raw text with **markdown**' },
|
|
104
|
+
});
|
|
105
|
+
const e = new AdvisoryExecutor();
|
|
106
|
+
const out = await e.consult(advisor({ provider }), req, ctx());
|
|
107
|
+
expect(out.result.advice).toBe('Raw text with **markdown**');
|
|
108
|
+
});
|
|
109
|
+
it('handles null provider content as empty string', async () => {
|
|
110
|
+
const provider = mockProvider({
|
|
111
|
+
message: { role: 'assistant', content: null },
|
|
112
|
+
});
|
|
113
|
+
const e = new AdvisoryExecutor();
|
|
114
|
+
const out = await e.consult(advisor({ provider }), req, ctx());
|
|
115
|
+
expect(out.result.advice).toBe('');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
describe('AdvisoryExecutor — buildSystemPrompt', () => {
|
|
119
|
+
it('uses advisor.systemPrompt verbatim when set', async () => {
|
|
120
|
+
const provider = mockProvider();
|
|
121
|
+
const e = new AdvisoryExecutor();
|
|
122
|
+
await e.consult(advisor({ provider, systemPrompt: 'FIXED PROMPT' }), req, ctx());
|
|
123
|
+
const call = vi.mocked(provider.chat).mock.calls[0]?.[0];
|
|
124
|
+
expect(call.messages[0]?.content).toBe('FIXED PROMPT');
|
|
125
|
+
});
|
|
126
|
+
it('falls back to name + domains + boilerplate when no systemPrompt or persona', async () => {
|
|
127
|
+
const provider = mockProvider();
|
|
128
|
+
const e = new AdvisoryExecutor();
|
|
129
|
+
await e.consult(advisor({ provider, name: 'Architect', domains: ['security', 'performance'] }), req, ctx());
|
|
130
|
+
const call = vi.mocked(provider.chat).mock.calls[0]?.[0];
|
|
131
|
+
const systemContent = call.messages[0]?.content ?? '';
|
|
132
|
+
expect(systemContent).toContain('Architect');
|
|
133
|
+
expect(systemContent).toContain('security, performance');
|
|
134
|
+
expect(systemContent).toContain('concise, actionable advice');
|
|
135
|
+
});
|
|
136
|
+
it('fallback without domains omits the domains line', async () => {
|
|
137
|
+
const provider = mockProvider();
|
|
138
|
+
const e = new AdvisoryExecutor();
|
|
139
|
+
await e.consult(advisor({ provider, name: 'Adv' }), req, ctx());
|
|
140
|
+
const call = vi.mocked(provider.chat).mock.calls[0]?.[0];
|
|
141
|
+
const systemContent = call.messages[0]?.content ?? '';
|
|
142
|
+
expect(systemContent).not.toContain('domains of expertise');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
describe('AdvisoryExecutor — buildContext', () => {
|
|
146
|
+
it('returns no context message when request.includeContext is false', async () => {
|
|
147
|
+
const provider = mockProvider();
|
|
148
|
+
const e = new AdvisoryExecutor();
|
|
149
|
+
await e.consult(advisor({ provider }), { question: 'q', includeContext: false }, ctx({ workingStateSummary: 'should be ignored' }));
|
|
150
|
+
const call = vi.mocked(provider.chat).mock.calls[0]?.[0];
|
|
151
|
+
// Only system + user(question)
|
|
152
|
+
expect(call.messages).toHaveLength(2);
|
|
153
|
+
});
|
|
154
|
+
it('includes workingStateSummary + toolCatalog names when present', async () => {
|
|
155
|
+
const provider = mockProvider();
|
|
156
|
+
const e = new AdvisoryExecutor();
|
|
157
|
+
await e.consult(advisor({ provider }), req, {
|
|
158
|
+
messages: [],
|
|
159
|
+
iteration: 1,
|
|
160
|
+
workingStateSummary: 'state summary here',
|
|
161
|
+
toolCatalog: [
|
|
162
|
+
{
|
|
163
|
+
type: 'function',
|
|
164
|
+
function: { name: 'read_file', description: 'read', parameters: {} },
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
type: 'function',
|
|
168
|
+
function: { name: 'write_file', description: 'write', parameters: {} },
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
});
|
|
172
|
+
const call = vi.mocked(provider.chat).mock.calls[0]?.[0];
|
|
173
|
+
const contextMsg = call.messages[1]?.content ?? '';
|
|
174
|
+
expect(contextMsg).toContain('Working State');
|
|
175
|
+
expect(contextMsg).toContain('state summary here');
|
|
176
|
+
expect(contextMsg).toContain('Available Tools');
|
|
177
|
+
expect(contextMsg).toContain('read_file, write_file');
|
|
178
|
+
});
|
|
179
|
+
it('includes conversation context (no truncation when no maxContextTokens)', async () => {
|
|
180
|
+
const provider = mockProvider();
|
|
181
|
+
const messages = [
|
|
182
|
+
{ role: 'user', content: 'hi' },
|
|
183
|
+
{ role: 'assistant', content: 'hello' },
|
|
184
|
+
];
|
|
185
|
+
const e = new AdvisoryExecutor();
|
|
186
|
+
await e.consult(advisor({ provider }), req, ctx({ messages }));
|
|
187
|
+
const call = vi.mocked(provider.chat).mock.calls[0]?.[0];
|
|
188
|
+
const contextMsg = call.messages[1]?.content ?? '';
|
|
189
|
+
expect(contextMsg).toContain('Conversation Context');
|
|
190
|
+
expect(contextMsg).toContain('[user]: hi');
|
|
191
|
+
expect(contextMsg).toContain('[assistant]: hello');
|
|
192
|
+
});
|
|
193
|
+
it('truncates conversation from the back when maxContextTokens is set', async () => {
|
|
194
|
+
const provider = mockProvider();
|
|
195
|
+
const messages = [
|
|
196
|
+
{ role: 'user', content: 'a'.repeat(100) }, // oldest — should be dropped
|
|
197
|
+
{ role: 'user', content: 'recent' },
|
|
198
|
+
];
|
|
199
|
+
const e = new AdvisoryExecutor();
|
|
200
|
+
// maxContextTokens=5 → 5*4=20 char budget; only 'recent' (6 chars) fits.
|
|
201
|
+
await e.consult(advisor({ provider, maxContextTokens: 5 }), req, ctx({ messages }));
|
|
202
|
+
const call = vi.mocked(provider.chat).mock.calls[0]?.[0];
|
|
203
|
+
const contextMsg = call.messages[1]?.content ?? '';
|
|
204
|
+
expect(contextMsg).toContain('recent');
|
|
205
|
+
expect(contextMsg).not.toContain('a'.repeat(100));
|
|
206
|
+
});
|
|
207
|
+
it('omits the context message entirely when there are no context parts', async () => {
|
|
208
|
+
const provider = mockProvider();
|
|
209
|
+
const e = new AdvisoryExecutor();
|
|
210
|
+
await e.consult(advisor({ provider }), req, ctx());
|
|
211
|
+
const call = vi.mocked(provider.chat).mock.calls[0]?.[0];
|
|
212
|
+
expect(call.messages).toHaveLength(2);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
describe('AdvisoryExecutor — tool calls in context', () => {
|
|
216
|
+
it('represents assistant messages with tool calls as "(tool calls)" stub', async () => {
|
|
217
|
+
const provider = mockProvider();
|
|
218
|
+
const e = new AdvisoryExecutor();
|
|
219
|
+
await e.consult(advisor({ provider }), req, ctx({
|
|
220
|
+
messages: [
|
|
221
|
+
{
|
|
222
|
+
role: 'assistant',
|
|
223
|
+
content: null,
|
|
224
|
+
toolCalls: [{ id: 't1', type: 'function', function: { name: 'x', arguments: '{}' } }],
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
}));
|
|
228
|
+
const call = vi.mocked(provider.chat).mock.calls[0]?.[0];
|
|
229
|
+
const contextMsg = call.messages[1]?.content ?? '';
|
|
230
|
+
expect(contextMsg).toContain('[assistant]: (tool calls)');
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
//# sourceMappingURL=executor.test.js.map
|