@genesislcap/ai-assistant 15.19.0 → 15.19.1
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 +17 -18
- package/api-extractor.json +0 -11
- package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.invocation-scope.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.invocation-scope.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts.map +0 -1
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts +0 -2
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts.map +0 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts +0 -2
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts.map +0 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.cost.test.d.ts +0 -2
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.cost.test.d.ts.map +0 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts +0 -2
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts.map +0 -1
- package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts +0 -2
- package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts.map +0 -1
- package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts +0 -2
- package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts.map +0 -1
- package/dist/dts/config/define-stateful-agent.test.d.ts +0 -2
- package/dist/dts/config/define-stateful-agent.test.d.ts.map +0 -1
- package/dist/dts/config/validate-providers.test.d.ts +0 -2
- package/dist/dts/config/validate-providers.test.d.ts.map +0 -1
- package/dist/dts/main/blocked-state.test.d.ts +0 -2
- package/dist/dts/main/blocked-state.test.d.ts.map +0 -1
- package/dist/dts/main/budget-meter.test.d.ts +0 -2
- package/dist/dts/main/budget-meter.test.d.ts.map +0 -1
- package/dist/dts/main/cost-session-banking.test.d.ts +0 -2
- package/dist/dts/main/cost-session-banking.test.d.ts.map +0 -1
- package/dist/dts/main/file-attachments.test.d.ts +0 -2
- package/dist/dts/main/file-attachments.test.d.ts.map +0 -1
- package/dist/dts/main/interaction-cost.test.d.ts +0 -2
- package/dist/dts/main/interaction-cost.test.d.ts.map +0 -1
- package/dist/dts/main/main.styles.test.d.ts +0 -2
- package/dist/dts/main/main.styles.test.d.ts.map +0 -1
- package/dist/dts/main/persistence-broken-sources.test.d.ts +0 -2
- package/dist/dts/main/persistence-broken-sources.test.d.ts.map +0 -1
- package/dist/dts/main/popout-interaction-gate.test.d.ts +0 -2
- package/dist/dts/main/popout-interaction-gate.test.d.ts.map +0 -1
- package/dist/dts/state/ai-assistant-slice.test.d.ts +0 -2
- package/dist/dts/state/ai-assistant-slice.test.d.ts.map +0 -1
- package/dist/dts/state/debug-event-log.test.d.ts +0 -2
- package/dist/dts/state/debug-event-log.test.d.ts.map +0 -1
- package/dist/dts/state/driver-registry.test.d.ts +0 -2
- package/dist/dts/state/driver-registry.test.d.ts.map +0 -1
- package/dist/dts/state/interaction-context.test.d.ts +0 -2
- package/dist/dts/state/interaction-context.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/diagnostics-cursors.test.d.ts +0 -2
- package/dist/dts/state/persistence/diagnostics-cursors.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/diagnostics.test.d.ts +0 -2
- package/dist/dts/state/persistence/diagnostics.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/session-persistence-provider.test.d.ts +0 -2
- package/dist/dts/state/persistence/session-persistence-provider.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/session-persistence.integration.test.d.ts +0 -2
- package/dist/dts/state/persistence/session-persistence.integration.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/session-persister.test.d.ts +0 -2
- package/dist/dts/state/persistence/session-persister.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/session-snapshot.test.d.ts +0 -2
- package/dist/dts/state/persistence/session-snapshot.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/stateful-restore.e2e.test.d.ts +0 -2
- package/dist/dts/state/persistence/stateful-restore.e2e.test.d.ts.map +0 -1
- package/dist/dts/utils/animation-exclusivity.test.d.ts +0 -2
- package/dist/dts/utils/animation-exclusivity.test.d.ts.map +0 -1
- package/dist/dts/utils/collect-session-models.test.d.ts +0 -2
- package/dist/dts/utils/collect-session-models.test.d.ts.map +0 -1
- package/dist/dts/utils/condense-history.test.d.ts +0 -2
- package/dist/dts/utils/condense-history.test.d.ts.map +0 -1
- package/dist/dts/utils/cost-session-history.test.d.ts +0 -2
- package/dist/dts/utils/cost-session-history.test.d.ts.map +0 -1
- package/dist/dts/utils/derive-cost-session-title.test.d.ts +0 -2
- package/dist/dts/utils/derive-cost-session-title.test.d.ts.map +0 -1
- package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts +0 -2
- package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts.map +0 -1
- package/dist/dts/utils/history-transform.test.d.ts +0 -2
- package/dist/dts/utils/history-transform.test.d.ts.map +0 -1
- package/dist/dts/utils/message-partition.test.d.ts +0 -2
- package/dist/dts/utils/message-partition.test.d.ts.map +0 -1
- package/dist/dts/utils/resolve-cost-history-config.test.d.ts +0 -2
- package/dist/dts/utils/resolve-cost-history-config.test.d.ts.map +0 -1
- package/dist/dts/utils/resolve-preference-baseline.test.d.ts +0 -2
- package/dist/dts/utils/resolve-preference-baseline.test.d.ts.map +0 -1
- package/dist/dts/utils/strip-agent-handlers.test.d.ts +0 -2
- package/dist/dts/utils/strip-agent-handlers.test.d.ts.map +0 -1
- package/dist/dts/utils/sum-costs.test.d.ts +0 -2
- package/dist/dts/utils/sum-costs.test.d.ts.map +0 -1
- package/dist/dts/utils/sum-tokens.test.d.ts +0 -2
- package/dist/dts/utils/sum-tokens.test.d.ts.map +0 -1
- package/dist/dts/utils/sum-usage.test.d.ts +0 -2
- package/dist/dts/utils/sum-usage.test.d.ts.map +0 -1
- package/dist/dts/utils/usage-rows.test.d.ts +0 -2
- package/dist/dts/utils/usage-rows.test.d.ts.map +0 -1
- package/dist/esm/components/chat-driver/chat-driver.compact.test.js +0 -93
- package/dist/esm/components/chat-driver/chat-driver.invocation-scope.test.js +0 -238
- package/dist/esm/components/chat-driver/chat-driver.test.js +0 -2770
- package/dist/esm/components/chat-driver/chat-driver.thinking-policy.test.js +0 -137
- package/dist/esm/components/chat-driver/chat-driver.trace-capture.test.js +0 -200
- package/dist/esm/components/chat-driver/chat-driver.turn-usage.test.js +0 -268
- package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.test.js +0 -104
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.budget.test.js +0 -312
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.cost.test.js +0 -76
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.pin.test.js +0 -61
- package/dist/esm/components/settings-modal/settings-modal.styles.test.js +0 -80
- package/dist/esm/components/settings-modal/settings-modal.template.test.js +0 -91
- package/dist/esm/config/define-stateful-agent.test.js +0 -53
- package/dist/esm/config/validate-providers.test.js +0 -100
- package/dist/esm/main/blocked-state.test.js +0 -1065
- package/dist/esm/main/budget-meter.test.js +0 -359
- package/dist/esm/main/cost-session-banking.test.js +0 -538
- package/dist/esm/main/file-attachments.test.js +0 -164
- package/dist/esm/main/interaction-cost.test.js +0 -107
- package/dist/esm/main/main.styles.test.js +0 -161
- package/dist/esm/main/persistence-broken-sources.test.js +0 -180
- package/dist/esm/main/popout-interaction-gate.test.js +0 -54
- package/dist/esm/state/ai-assistant-slice.test.js +0 -229
- package/dist/esm/state/debug-event-log.test.js +0 -117
- package/dist/esm/state/driver-registry.test.js +0 -55
- package/dist/esm/state/interaction-context.test.js +0 -78
- package/dist/esm/state/persistence/diagnostics-cursors.test.js +0 -30
- package/dist/esm/state/persistence/diagnostics.test.js +0 -280
- package/dist/esm/state/persistence/session-persistence-provider.test.js +0 -110
- package/dist/esm/state/persistence/session-persistence.integration.test.js +0 -113
- package/dist/esm/state/persistence/session-persister.test.js +0 -613
- package/dist/esm/state/persistence/session-snapshot.test.js +0 -256
- package/dist/esm/state/persistence/stateful-restore.e2e.test.js +0 -158
- package/dist/esm/utils/animation-exclusivity.test.js +0 -52
- package/dist/esm/utils/collect-session-models.test.js +0 -53
- package/dist/esm/utils/condense-history.test.js +0 -547
- package/dist/esm/utils/cost-session-history.test.js +0 -174
- package/dist/esm/utils/derive-cost-session-title.test.js +0 -37
- package/dist/esm/utils/flatten-sub-agent-messages.test.js +0 -139
- package/dist/esm/utils/history-transform.test.js +0 -158
- package/dist/esm/utils/message-partition.test.js +0 -90
- package/dist/esm/utils/resolve-cost-history-config.test.js +0 -32
- package/dist/esm/utils/resolve-preference-baseline.test.js +0 -69
- package/dist/esm/utils/strip-agent-handlers.test.js +0 -81
- package/dist/esm/utils/sum-costs.test.js +0 -114
- package/dist/esm/utils/sum-tokens.test.js +0 -51
- package/dist/esm/utils/sum-usage.test.js +0 -164
- package/dist/esm/utils/usage-rows.test.js +0 -189
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/license.txt +0 -46
- package/sandbox/tsconfig.json +0 -8
- package/src/channel/ai-activity-bus.ts +0 -209
- package/src/channel/ai-activity-channel.ts +0 -146
- package/src/chat-driver-node.ts +0 -159
- package/src/components/activity-halo/activity-halo.ts +0 -106
- package/src/components/agent-picker/agent-picker.constants.ts +0 -2
- package/src/components/agent-picker/agent-picker.styles.ts +0 -24
- package/src/components/agent-picker/agent-picker.template.ts +0 -54
- package/src/components/agent-picker/agent-picker.ts +0 -99
- package/src/components/agent-picker/index.ts +0 -1
- package/src/components/ai-driver/ai-driver.ts +0 -166
- package/src/components/ai-driver/index.ts +0 -1
- package/src/components/chat-bubble/chat-bubble.styles.ts +0 -194
- package/src/components/chat-bubble/chat-bubble.template.ts +0 -67
- package/src/components/chat-bubble/chat-bubble.ts +0 -436
- package/src/components/chat-bubble/index.ts +0 -1
- package/src/components/chat-driver/align-event-globals.ts +0 -23
- package/src/components/chat-driver/chat-driver.compact.test.ts +0 -131
- package/src/components/chat-driver/chat-driver.invocation-scope.test.ts +0 -285
- package/src/components/chat-driver/chat-driver.test.ts +0 -3679
- package/src/components/chat-driver/chat-driver.thinking-policy.test.ts +0 -185
- package/src/components/chat-driver/chat-driver.trace-capture.test.ts +0 -251
- package/src/components/chat-driver/chat-driver.ts +0 -3925
- package/src/components/chat-driver/chat-driver.turn-usage.test.ts +0 -362
- package/src/components/chat-driver/index.ts +0 -1
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts +0 -7
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts +0 -6
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts +0 -144
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts +0 -243
- package/src/components/chat-interaction-wrapper/index.ts +0 -3
- package/src/components/chat-markdown/chat-markdown.ts +0 -89
- package/src/components/chat-markdown/index.ts +0 -1
- package/src/components/flowing-waves-indicator.ts +0 -286
- package/src/components/halo-overlay.ts +0 -184
- package/src/components/orchestrating-driver/index.ts +0 -1
- package/src/components/orchestrating-driver/orchestrating-driver.budget.test.ts +0 -438
- package/src/components/orchestrating-driver/orchestrating-driver.cost.test.ts +0 -107
- package/src/components/orchestrating-driver/orchestrating-driver.pin.test.ts +0 -96
- package/src/components/orchestrating-driver/orchestrating-driver.ts +0 -813
- package/src/components/plasma-orb-indicator.ts +0 -281
- package/src/components/popout-manager/index.ts +0 -1
- package/src/components/popout-manager/popout-manager.ts +0 -212
- package/src/components/settings-modal/index.ts +0 -2
- package/src/components/settings-modal/settings-modal.styles.test.ts +0 -94
- package/src/components/settings-modal/settings-modal.styles.ts +0 -853
- package/src/components/settings-modal/settings-modal.template.test.ts +0 -124
- package/src/components/settings-modal/settings-modal.template.ts +0 -943
- package/src/components/waves-indicator.ts +0 -241
- package/src/config/config.ts +0 -608
- package/src/config/define-stateful-agent.test.ts +0 -64
- package/src/config/define-stateful-agent.ts +0 -642
- package/src/config/fallback-agents.ts +0 -29
- package/src/config/index.ts +0 -2
- package/src/config/validate-providers.test.ts +0 -148
- package/src/config/validate-providers.ts +0 -58
- package/src/index.ts +0 -29
- package/src/main/blocked-state.test.ts +0 -1444
- package/src/main/budget-meter.test.ts +0 -477
- package/src/main/cost-session-banking.test.ts +0 -679
- package/src/main/file-attachments.test.ts +0 -215
- package/src/main/index.ts +0 -3
- package/src/main/interaction-cost.test.ts +0 -140
- package/src/main/main.styles.test.ts +0 -216
- package/src/main/main.styles.ts +0 -995
- package/src/main/main.template.ts +0 -1119
- package/src/main/main.ts +0 -5432
- package/src/main/main.types.ts +0 -225
- package/src/main/persistence-broken-sources.test.ts +0 -219
- package/src/main/popout-interaction-gate.test.ts +0 -87
- package/src/provider/ai-provider-switcher.ts +0 -40
- package/src/provider/assistant-app-settings.ts +0 -84
- package/src/state/ai-assistant-slice.test.ts +0 -336
- package/src/state/ai-assistant-slice.ts +0 -604
- package/src/state/debug-event-log.test.ts +0 -154
- package/src/state/debug-event-log.ts +0 -358
- package/src/state/driver-registry.test.ts +0 -70
- package/src/state/driver-registry.ts +0 -79
- package/src/state/interaction-context.test.ts +0 -90
- package/src/state/interaction-context.ts +0 -101
- package/src/state/persistence/build-timeline-entries.ts +0 -66
- package/src/state/persistence/diagnostics-cursors.test.ts +0 -53
- package/src/state/persistence/diagnostics-cursors.ts +0 -80
- package/src/state/persistence/diagnostics.test.ts +0 -346
- package/src/state/persistence/diagnostics.ts +0 -220
- package/src/state/persistence/index.ts +0 -10
- package/src/state/persistence/persister-registry.ts +0 -55
- package/src/state/persistence/session-persistence-provider.test.ts +0 -136
- package/src/state/persistence/session-persistence-provider.ts +0 -226
- package/src/state/persistence/session-persistence.integration.test.ts +0 -141
- package/src/state/persistence/session-persister.test.ts +0 -787
- package/src/state/persistence/session-persister.ts +0 -917
- package/src/state/persistence/session-snapshot.test.ts +0 -352
- package/src/state/persistence/session-snapshot.ts +0 -344
- package/src/state/persistence/stateful-restore.e2e.test.ts +0 -229
- package/src/state/session-store.ts +0 -56
- package/src/styles/ai-colours.ts +0 -30
- package/src/styles/menu.ts +0 -187
- package/src/styles/settings-section.ts +0 -40
- package/src/suggestions/chat-suggestions.ts +0 -186
- package/src/tags/index.ts +0 -1
- package/src/types/ai-chat-widget.ts +0 -67
- package/src/types/interaction-context.ts +0 -82
- package/src/utils/animated-panel-toggle.ts +0 -62
- package/src/utils/animation-exclusivity.test.ts +0 -72
- package/src/utils/animation-exclusivity.ts +0 -40
- package/src/utils/banked-usage-baselines.ts +0 -90
- package/src/utils/collect-session-models.test.ts +0 -82
- package/src/utils/collect-session-models.ts +0 -65
- package/src/utils/condense-history.test.ts +0 -746
- package/src/utils/condense-history.ts +0 -448
- package/src/utils/cost-session-history.test.ts +0 -226
- package/src/utils/cost-session-history.ts +0 -218
- package/src/utils/derive-cost-session-title.test.ts +0 -63
- package/src/utils/derive-cost-session-title.ts +0 -76
- package/src/utils/flatten-sub-agent-messages.test.ts +0 -163
- package/src/utils/flatten-sub-agent-messages.ts +0 -88
- package/src/utils/format-usd.ts +0 -26
- package/src/utils/history-transform.test.ts +0 -228
- package/src/utils/history-transform.ts +0 -163
- package/src/utils/index.ts +0 -3
- package/src/utils/logger.ts +0 -3
- package/src/utils/message-partition.test.ts +0 -129
- package/src/utils/message-partition.ts +0 -115
- package/src/utils/resolve-cost-history-config.test.ts +0 -38
- package/src/utils/resolve-cost-history-config.ts +0 -38
- package/src/utils/resolve-preference-baseline.test.ts +0 -84
- package/src/utils/resolve-preference-baseline.ts +0 -80
- package/src/utils/strip-agent-handlers.test.ts +0 -99
- package/src/utils/strip-agent-handlers.ts +0 -53
- package/src/utils/sum-costs.test.ts +0 -136
- package/src/utils/sum-costs.ts +0 -18
- package/src/utils/sum-tokens.test.ts +0 -93
- package/src/utils/sum-tokens.ts +0 -68
- package/src/utils/sum-usage.test.ts +0 -191
- package/src/utils/sum-usage.ts +0 -208
- package/src/utils/tool-fold.ts +0 -181
- package/src/utils/usage-rows.test.ts +0 -237
- package/src/utils/usage-rows.ts +0 -187
- package/src/utils/with-timeout.ts +0 -24
- package/tsconfig.json +0 -9
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import type { ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
3
|
-
import { addUsage, emptyUsage, messageUsage, sumUsage, totalTokens } from './sum-usage';
|
|
4
|
-
|
|
5
|
-
const suite = createLogicSuite('sumUsage');
|
|
6
|
-
|
|
7
|
-
const msg = (over: Partial<ChatMessage> = {}): ChatMessage =>
|
|
8
|
-
({ role: 'assistant', content: '', ...over }) as ChatMessage;
|
|
9
|
-
|
|
10
|
-
suite('returns zeroes when no message carries usage', () => {
|
|
11
|
-
const usage = sumUsage([msg({ role: 'user', content: 'hi' })]);
|
|
12
|
-
assert.equal(usage, emptyUsage());
|
|
13
|
-
assert.is(totalTokens(usage), 0);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
suite('splits the prompt into three disjoint input buckets', () => {
|
|
17
|
-
// `inputTokens` is the WHOLE prompt; the cache fields break it down. So uncached is
|
|
18
|
-
// the remainder, and the three must re-add to the prompt exactly — that identity is
|
|
19
|
-
// what stops the buckets double-counting the prompt when summed.
|
|
20
|
-
const usage = sumUsage([
|
|
21
|
-
msg({ inputTokens: 1000, cacheReadTokens: 700, cacheWriteTokens: 200, outputTokens: 50 }),
|
|
22
|
-
]);
|
|
23
|
-
assert.is(usage.uncachedInputTokens, 100);
|
|
24
|
-
assert.is(usage.cacheReadTokens, 700);
|
|
25
|
-
assert.is(usage.cacheWriteTokens, 200);
|
|
26
|
-
assert.is(usage.outputTokens, 50);
|
|
27
|
-
assert.is(
|
|
28
|
-
usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens,
|
|
29
|
-
1000,
|
|
30
|
-
'input buckets re-add to the prompt total',
|
|
31
|
-
);
|
|
32
|
-
assert.is(totalTokens(usage), 1050);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
suite('treats the whole prompt as uncached when no cache split is reported', () => {
|
|
36
|
-
// Providers that report no cache breakdown (and messages persisted before the fields
|
|
37
|
-
// existed) must still contribute their full prompt, not drop it.
|
|
38
|
-
const usage = sumUsage([msg({ inputTokens: 400, outputTokens: 60 })]);
|
|
39
|
-
assert.is(usage.uncachedInputTokens, 400);
|
|
40
|
-
assert.is(usage.cacheReadTokens, 0);
|
|
41
|
-
assert.is(usage.cacheWriteTokens, 0);
|
|
42
|
-
assert.is(totalTokens(usage), 460);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
suite('clamps uncached input at zero when buckets exceed the prompt total', () => {
|
|
46
|
-
// Malformed or hand-edited history must not be able to drive a cost display negative.
|
|
47
|
-
const usage = sumUsage([
|
|
48
|
-
msg({ inputTokens: 100, cacheReadTokens: 90, cacheWriteTokens: 90, outputTokens: 0 }),
|
|
49
|
-
]);
|
|
50
|
-
assert.is(usage.uncachedInputTokens, 0);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
suite('sums LLM cost and widget-reported external cost', () => {
|
|
54
|
-
const usage = sumUsage([msg({ cost: 0.25 }), msg({ externalCostUsd: 0.1, cost: 0.05 })]);
|
|
55
|
-
assert.is(Math.round(usage.costUsd * 100) / 100, 0.4);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
suite('recurses into sub-agent traces', () => {
|
|
59
|
-
// A sub-agent's whole conversation is embedded in the parent's tool call, so this is
|
|
60
|
-
// the only place its spend is visible to a host total.
|
|
61
|
-
const usage = sumUsage([
|
|
62
|
-
msg({
|
|
63
|
-
toolCalls: [
|
|
64
|
-
{
|
|
65
|
-
id: 'tc-1',
|
|
66
|
-
name: 'delegate',
|
|
67
|
-
args: {},
|
|
68
|
-
subAgentTrace: [
|
|
69
|
-
msg({ cost: 0.2, inputTokens: 300, cacheReadTokens: 100, outputTokens: 40 }),
|
|
70
|
-
],
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
}),
|
|
74
|
-
]);
|
|
75
|
-
assert.is(usage.costUsd, 0.2);
|
|
76
|
-
assert.is(usage.uncachedInputTokens, 200);
|
|
77
|
-
assert.is(usage.cacheReadTokens, 100);
|
|
78
|
-
assert.is(usage.outputTokens, 40);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
suite('counts usage banked onto a compaction summary', () => {
|
|
82
|
-
// Compaction destroys the messages it summarises. Without the banked rollup, a total
|
|
83
|
-
// re-derived from the transcript would fall by whatever those turns had spent.
|
|
84
|
-
const before = sumUsage([
|
|
85
|
-
msg({ cost: 0.5, inputTokens: 1000, cacheReadTokens: 400, outputTokens: 100 }),
|
|
86
|
-
msg({ cost: 0.3, inputTokens: 500, outputTokens: 50 }),
|
|
87
|
-
]);
|
|
88
|
-
const afterCompaction = sumUsage([
|
|
89
|
-
msg({
|
|
90
|
-
role: 'compacted-summary',
|
|
91
|
-
content: 'summary of the above',
|
|
92
|
-
compaction: {
|
|
93
|
-
compactedCount: 2,
|
|
94
|
-
createdAt: '2026-06-14T12:00:00.000Z',
|
|
95
|
-
rolledUpUsage: before,
|
|
96
|
-
},
|
|
97
|
-
}),
|
|
98
|
-
]);
|
|
99
|
-
assert.equal(afterCompaction, before, 'compaction preserves the running total');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
suite('ignores tokensBefore, which is a context reading rather than spend', () => {
|
|
103
|
-
// `tokensBefore` is one turn's context-window size. Counting it as cumulative spend
|
|
104
|
-
// would silently inflate the session total on every compaction.
|
|
105
|
-
const usage = sumUsage([
|
|
106
|
-
msg({
|
|
107
|
-
role: 'compacted-summary',
|
|
108
|
-
content: 'summary',
|
|
109
|
-
compaction: {
|
|
110
|
-
compactedCount: 3,
|
|
111
|
-
createdAt: '2026-06-14T12:00:00.000Z',
|
|
112
|
-
tokensBefore: 90_000,
|
|
113
|
-
},
|
|
114
|
-
}),
|
|
115
|
-
]);
|
|
116
|
-
assert.is(totalTokens(usage), 0);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
suite('addUsage adds every bucket and mutates neither operand', () => {
|
|
120
|
-
const a = { ...emptyUsage(), costUsd: 1.5, uncachedInputTokens: 100, outputTokens: 10 };
|
|
121
|
-
const b = { ...emptyUsage(), costUsd: 0.25, cacheReadTokens: 900, outputTokens: 5 };
|
|
122
|
-
const sum = addUsage(a, b);
|
|
123
|
-
|
|
124
|
-
assert.is(sum.costUsd, 1.75);
|
|
125
|
-
assert.is(sum.uncachedInputTokens, 100);
|
|
126
|
-
assert.is(sum.cacheReadTokens, 900);
|
|
127
|
-
assert.is(sum.outputTokens, 15);
|
|
128
|
-
// Both operands are live state (a row's banked figure and the session total), so a
|
|
129
|
-
// caller must be able to add without either being altered underneath it.
|
|
130
|
-
assert.is(a.costUsd, 1.5);
|
|
131
|
-
assert.is(b.cacheReadTokens, 900);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
suite('addUsage with an empty operand is the identity', () => {
|
|
135
|
-
const a = { ...emptyUsage(), costUsd: 3, cacheWriteTokens: 42 };
|
|
136
|
-
assert.equal(addUsage(a, emptyUsage()), a);
|
|
137
|
-
assert.equal(addUsage(emptyUsage(), a), a);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
suite('messageUsage is undefined for a message reporting nothing', () => {
|
|
141
|
-
// A user turn, or a display-only reasoning/narration split. Distinct from a zeroed
|
|
142
|
-
// usage: "this call reported nothing" is not "this call cost nothing".
|
|
143
|
-
assert.is(messageUsage(msg({ role: 'user', content: 'hi' })), undefined);
|
|
144
|
-
assert.is(messageUsage(msg({ content: 'thinking…', category: 'reasoning' })), undefined);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
suite('messageUsage agrees with sumUsage over the same single message', () => {
|
|
148
|
-
// The two derivations are deliberately separate — `accumulate` mutates one
|
|
149
|
-
// accumulator rather than allocating per message — so pin them together. A drift
|
|
150
|
-
// here would put one figure on a turn entry and a different one in the session
|
|
151
|
-
// total, both looking plausible.
|
|
152
|
-
const cases: Partial<ChatMessage>[] = [
|
|
153
|
-
{ inputTokens: 1000, cacheReadTokens: 700, cacheWriteTokens: 200, outputTokens: 50, cost: 0.4 },
|
|
154
|
-
{ inputTokens: 400, outputTokens: 60 },
|
|
155
|
-
{ inputTokens: 100, cacheReadTokens: 90, cacheWriteTokens: 90 },
|
|
156
|
-
{ cost: 0.01, externalCostUsd: 0.02 },
|
|
157
|
-
];
|
|
158
|
-
for (const over of cases) {
|
|
159
|
-
const m = msg(over);
|
|
160
|
-
assert.equal(
|
|
161
|
-
messageUsage(m),
|
|
162
|
-
sumUsage([m]),
|
|
163
|
-
`messageUsage must match sumUsage for ${JSON.stringify(over)}`,
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
suite('messageUsage is shallow — it prices the one request, not the tree', () => {
|
|
169
|
-
// Deliberately blind to sub-agent traces and banked compaction usage: it answers
|
|
170
|
-
// "what did this call cost", which is what a turn snapshot needs. `sumUsage` is the
|
|
171
|
-
// function that walks everything underneath.
|
|
172
|
-
const m = msg({
|
|
173
|
-
inputTokens: 100,
|
|
174
|
-
outputTokens: 10,
|
|
175
|
-
cost: 0.05,
|
|
176
|
-
toolCalls: [
|
|
177
|
-
{
|
|
178
|
-
id: 't1',
|
|
179
|
-
name: 'delegate',
|
|
180
|
-
args: {},
|
|
181
|
-
subAgentTrace: [msg({ inputTokens: 900, outputTokens: 90, cost: 0.5 })],
|
|
182
|
-
},
|
|
183
|
-
],
|
|
184
|
-
});
|
|
185
|
-
const usage = messageUsage(m)!;
|
|
186
|
-
assert.is(usage.costUsd, 0.05, 'the child conversation is not folded in');
|
|
187
|
-
assert.is(usage.uncachedInputTokens, 100);
|
|
188
|
-
assert.ok(sumUsage([m]).costUsd > usage.costUsd, 'sumUsage does fold it in');
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
suite.run();
|
package/src/utils/sum-usage.ts
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import type { AggregateUsage, ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Zeroed {@link AggregateUsage}. A factory, not a shared const, because callers
|
|
5
|
-
* accumulate into the returned object.
|
|
6
|
-
*
|
|
7
|
-
* @beta
|
|
8
|
-
*/
|
|
9
|
-
export function emptyUsage(): AggregateUsage {
|
|
10
|
-
return {
|
|
11
|
-
costUsd: 0,
|
|
12
|
-
uncachedInputTokens: 0,
|
|
13
|
-
cacheReadTokens: 0,
|
|
14
|
-
cacheWriteTokens: 0,
|
|
15
|
-
outputTokens: 0,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Total tokens processed — the four buckets added up. Safe to sum because the
|
|
21
|
-
* buckets are disjoint (see {@link AggregateUsage}); the equivalent sum over
|
|
22
|
-
* per-message fields would double-count the prompt.
|
|
23
|
-
*
|
|
24
|
-
* Bear in mind what this number is: because every turn resends the conversation,
|
|
25
|
-
* a turn's prompt is counted again in the next turn's. It is billed throughput,
|
|
26
|
-
* NOT the count of distinct tokens in the conversation, so it grows
|
|
27
|
-
* super-linearly with turn count and is expected to dwarf the context size.
|
|
28
|
-
*
|
|
29
|
-
* @beta
|
|
30
|
-
*/
|
|
31
|
-
export function totalTokens(usage: AggregateUsage): number {
|
|
32
|
-
return (
|
|
33
|
-
usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens + usage.outputTokens
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* `a + b`, per bucket, as a new object.
|
|
39
|
-
*
|
|
40
|
-
* Used to lay a row's banked (pre-transcript) spend over what the live transcript
|
|
41
|
-
* proves. Pure rather than in-place because both operands are live state a caller
|
|
42
|
-
* must not mutate.
|
|
43
|
-
*
|
|
44
|
-
* @beta
|
|
45
|
-
*/
|
|
46
|
-
export function addUsage(a: AggregateUsage, b: AggregateUsage): AggregateUsage {
|
|
47
|
-
return {
|
|
48
|
-
costUsd: a.costUsd + b.costUsd,
|
|
49
|
-
uncachedInputTokens: a.uncachedInputTokens + b.uncachedInputTokens,
|
|
50
|
-
cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens,
|
|
51
|
-
cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens,
|
|
52
|
-
outputTokens: a.outputTokens + b.outputTokens,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* The four buckets plus USD for ONE message — the per-message fields projected into
|
|
58
|
-
* the disjoint, addable shape, or `undefined` when the message reports no usage at
|
|
59
|
-
* all (a user turn, a display-only reasoning/narration split, a provider that
|
|
60
|
-
* reports none).
|
|
61
|
-
*
|
|
62
|
-
* Shallow by design: it does NOT recurse into `toolCalls[].subAgentTrace` and does
|
|
63
|
-
* NOT count `compaction.rolledUpUsage`, so it answers "what did this one request
|
|
64
|
-
* cost" rather than "what did this message and everything under it cost". Use
|
|
65
|
-
* {@link sumUsage} for the latter — passing a whole transcript through this one
|
|
66
|
-
* message at a time would silently drop both.
|
|
67
|
-
*
|
|
68
|
-
* `costUsd` folds in `externalCostUsd` (non-LLM spend a widget reported), matching
|
|
69
|
-
* `sumUsage`. It is `0` when the provider priced nothing, which is NOT a claim that
|
|
70
|
-
* the call was free — see {@link AggregateUsage}; `UsageRow.costUsd` is the
|
|
71
|
-
* shape that keeps unpriced distinguishable, and is the right tool for a ledger.
|
|
72
|
-
*
|
|
73
|
-
* @beta
|
|
74
|
-
*/
|
|
75
|
-
export function messageUsage(m: ChatMessage): AggregateUsage | undefined {
|
|
76
|
-
if (
|
|
77
|
-
m.cost == null &&
|
|
78
|
-
m.externalCostUsd == null &&
|
|
79
|
-
m.inputTokens == null &&
|
|
80
|
-
m.outputTokens == null &&
|
|
81
|
-
m.cacheReadTokens == null &&
|
|
82
|
-
m.cacheWriteTokens == null
|
|
83
|
-
) {
|
|
84
|
-
return undefined;
|
|
85
|
-
}
|
|
86
|
-
const cacheReadTokens = m.cacheReadTokens ?? 0;
|
|
87
|
-
const cacheWriteTokens = m.cacheWriteTokens ?? 0;
|
|
88
|
-
return {
|
|
89
|
-
costUsd: (m.cost ?? 0) + (m.externalCostUsd ?? 0),
|
|
90
|
-
// The uncached REMAINDER, clamped — the same arithmetic `accumulate` below applies,
|
|
91
|
-
// for the same reasons. The two are deliberately separate implementations (that walk
|
|
92
|
-
// mutates one accumulator rather than allocating per message) and must agree;
|
|
93
|
-
// `sum-usage.test.ts` pins them together.
|
|
94
|
-
uncachedInputTokens: Math.max(0, (m.inputTokens ?? 0) - cacheReadTokens - cacheWriteTokens),
|
|
95
|
-
cacheReadTokens,
|
|
96
|
-
cacheWriteTokens,
|
|
97
|
-
outputTokens: m.outputTokens ?? 0,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Sum cost and per-bucket token usage across a message list.
|
|
103
|
-
*
|
|
104
|
-
* One walk produces everything a usage display needs, replacing separate cost and
|
|
105
|
-
* token passes over the same (potentially deep) transcript. Recurses into each
|
|
106
|
-
* tool call's `subAgentTrace`, so LLM work a sub-agent did — possibly on another
|
|
107
|
-
* provider at other rates — lands in the total; the driver embeds a whole
|
|
108
|
-
* sub-agent conversation in the parent's tool result, so this is the only place
|
|
109
|
-
* that spend is visible.
|
|
110
|
-
*
|
|
111
|
-
* Splitting the prompt into buckets is the point: `cost` already has the cache
|
|
112
|
-
* discount applied, but a single token total cannot show that a session was cheap
|
|
113
|
-
* because most of its prompt was a cache hit. The three input buckets are derived
|
|
114
|
-
* from the per-message fields, where `inputTokens` is the whole prompt and the
|
|
115
|
-
* cache fields break it down — so uncached input is the remainder, never an
|
|
116
|
-
* addition.
|
|
117
|
-
*
|
|
118
|
-
* Also counts `compaction.rolledUpUsage`: compaction destroys the messages it
|
|
119
|
-
* summarises, and without their banked usage every total re-derived from the
|
|
120
|
-
* transcript would drop by whatever those turns had already spent.
|
|
121
|
-
*
|
|
122
|
-
* Returns a zeroed total when no message carries usage — providers that report
|
|
123
|
-
* none (e.g. Chrome built-in) are skipped silently rather than guessed at.
|
|
124
|
-
*
|
|
125
|
-
* @remarks
|
|
126
|
-
* This sums the WHOLE array, which is the right answer only when the array is the
|
|
127
|
-
* whole of the work being billed. A driver whose conversation **continues across
|
|
128
|
-
* rounds** opens its history with turns an earlier round already paid for, so
|
|
129
|
-
* summing it again bills them twice — and the session total then grows
|
|
130
|
-
* quadratically in the number of rounds while every individual figure in it stays
|
|
131
|
-
* correct. Nothing about the result looks wrong, which is why it is called out
|
|
132
|
-
* here rather than left to the caller to notice.
|
|
133
|
-
*
|
|
134
|
-
* Do NOT solve this with a "sum from this message onwards" marker. Compaction
|
|
135
|
-
* *replaces* the messages it summarises — and compaction is one of this function's
|
|
136
|
-
* own call sites — so the marker message can be absent from history by the time
|
|
137
|
-
* the sum runs, leaving the caller to either fail or silently sum everything,
|
|
138
|
-
* which is the double-count the marker was meant to prevent.
|
|
139
|
-
*
|
|
140
|
-
* Instead hold a **banked baseline**: spend that predates the current transcript,
|
|
141
|
-
* carried as a value, with `usage = banked + live` composed via {@link addUsage}.
|
|
142
|
-
* `resolveBankedUsage` in `utils/cost-session-history.ts` is the worked form, and
|
|
143
|
-
* the shape of its signature is the load-bearing part. It separates three cases:
|
|
144
|
-
*
|
|
145
|
-
* 1. no prior record — a genuine first round, so nothing is banked;
|
|
146
|
-
* 2. prior record, and the live transcript already accounts for it (a restored
|
|
147
|
-
* session) — carry the prior record's own banked figure forward;
|
|
148
|
-
* 3. prior record, and the transcript starts empty — bank the prior record's
|
|
149
|
-
* whole total, because nothing in the transcript can re-derive it.
|
|
150
|
-
*
|
|
151
|
-
* Case 1 and case 3 take the same arithmetic and mean opposite things, so the
|
|
152
|
-
* prior record must be a **required** input with no zero-defaulting path: a
|
|
153
|
-
* caller who carries a transcript and forgets its baseline reports the entire
|
|
154
|
-
* prior conversation as this round's spend. Distinguishing 2 from 3 is a separate
|
|
155
|
-
* decision, and getting it wrong is destructive in the other direction — it
|
|
156
|
-
* rewrites a total down to the current page-load and drops earlier spend.
|
|
157
|
-
*
|
|
158
|
-
* @beta
|
|
159
|
-
*/
|
|
160
|
-
export function sumUsage(messages: readonly ChatMessage[]): AggregateUsage {
|
|
161
|
-
const total = emptyUsage();
|
|
162
|
-
accumulate(messages, total);
|
|
163
|
-
return total;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/** Add `from` into `into`, in place. */
|
|
167
|
-
function addInto(into: AggregateUsage, from: AggregateUsage): void {
|
|
168
|
-
into.costUsd += from.costUsd;
|
|
169
|
-
into.uncachedInputTokens += from.uncachedInputTokens;
|
|
170
|
-
into.cacheReadTokens += from.cacheReadTokens;
|
|
171
|
-
into.cacheWriteTokens += from.cacheWriteTokens;
|
|
172
|
-
into.outputTokens += from.outputTokens;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Walk `messages`, accumulating into `into`. Mutates a single accumulator rather
|
|
177
|
-
* than merging returned objects — this runs on every transcript change, so the
|
|
178
|
-
* per-message allocation is worth avoiding.
|
|
179
|
-
*/
|
|
180
|
-
function accumulate(messages: readonly ChatMessage[], into: AggregateUsage): void {
|
|
181
|
-
for (const m of messages) {
|
|
182
|
-
// Provider/LLM cost plus any non-LLM cost a widget reported for its own
|
|
183
|
-
// external service calls. Both are real spend on this session.
|
|
184
|
-
if (m.cost != null) into.costUsd += m.cost;
|
|
185
|
-
if (m.externalCostUsd != null) into.costUsd += m.externalCostUsd;
|
|
186
|
-
|
|
187
|
-
const cacheRead = m.cacheReadTokens ?? 0;
|
|
188
|
-
const cacheWrite = m.cacheWriteTokens ?? 0;
|
|
189
|
-
into.cacheReadTokens += cacheRead;
|
|
190
|
-
into.cacheWriteTokens += cacheWrite;
|
|
191
|
-
// The uncached remainder of the prompt. Clamped at zero so malformed or
|
|
192
|
-
// hand-edited history whose buckets exceed the prompt total can't drive the
|
|
193
|
-
// session total negative — a wrong-but-plausible number is worse than a
|
|
194
|
-
// conservative one here, since this feeds a cost display.
|
|
195
|
-
into.uncachedInputTokens += Math.max(0, (m.inputTokens ?? 0) - cacheRead - cacheWrite);
|
|
196
|
-
into.outputTokens += m.outputTokens ?? 0;
|
|
197
|
-
|
|
198
|
-
// Spend banked by a compaction on this summary's behalf. NOTE: deliberately
|
|
199
|
-
// not `compaction.tokensBefore` — that is a one-turn context-window reading,
|
|
200
|
-
// not cumulative spend, and counting it here would inflate the total.
|
|
201
|
-
const rolled = m.compaction?.rolledUpUsage;
|
|
202
|
-
if (rolled) addInto(into, rolled);
|
|
203
|
-
|
|
204
|
-
for (const tc of m.toolCalls ?? []) {
|
|
205
|
-
if (tc.subAgentTrace) accumulate(tc.subAgentTrace, into);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
package/src/utils/tool-fold.ts
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import type { ChatToolDefinition, ChatToolHandlers } from '@genesislcap/foundation-ai';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Symbol used to attach ToolFold metadata to a facade handler function.
|
|
5
|
-
* The ChatDriver inspects this to detect fold facades at runtime.
|
|
6
|
-
*
|
|
7
|
-
* @internal
|
|
8
|
-
*/
|
|
9
|
-
export const TOOL_FOLD_SYMBOL = Symbol('toolFold');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Internal metadata for a tool fold. Attached to the facade handler via TOOL_FOLD_SYMBOL.
|
|
13
|
-
*
|
|
14
|
-
* @internal
|
|
15
|
-
*/
|
|
16
|
-
export interface ToolFold {
|
|
17
|
-
/** Facade tool name. */
|
|
18
|
-
name: string;
|
|
19
|
-
/** Description shown to the model. */
|
|
20
|
-
description: string;
|
|
21
|
-
/** Optional guidance injected in the fold-open response. */
|
|
22
|
-
usageNotes?: string;
|
|
23
|
-
/** Tool definitions inside this fold. */
|
|
24
|
-
tools: ChatToolDefinition[];
|
|
25
|
-
/** Handlers for the inner tools (keyed by tool name). */
|
|
26
|
-
handlers: ChatToolHandlers;
|
|
27
|
-
/**
|
|
28
|
-
* When true (default), all other tools are removed when this fold opens —
|
|
29
|
-
* the model can only see this fold's tools until it closes.
|
|
30
|
-
* When false, sibling tools remain alongside the inner tools.
|
|
31
|
-
*/
|
|
32
|
-
exclusive: boolean;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* The value returned by `createToolFold`.
|
|
37
|
-
*
|
|
38
|
-
* @beta
|
|
39
|
-
*/
|
|
40
|
-
export interface ToolFoldResult {
|
|
41
|
-
/**
|
|
42
|
-
* The facade tool definition. Add this to your agent's `toolDefinitions`.
|
|
43
|
-
* It is a regular `ChatToolDefinition` with an empty parameter schema.
|
|
44
|
-
*/
|
|
45
|
-
definition: ChatToolDefinition;
|
|
46
|
-
/**
|
|
47
|
-
* The facade handler — a single-entry map keyed by the fold name.
|
|
48
|
-
* Spread this into your agent's `toolHandlers`.
|
|
49
|
-
* Inner tool handlers are NOT included here — they are encapsulated inside
|
|
50
|
-
* the fold metadata and installed by the driver only when the fold opens.
|
|
51
|
-
*/
|
|
52
|
-
handler: ChatToolHandlers;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Creates a tool fold — a facade that hides a group of related tools behind a single
|
|
57
|
-
* named entry, revealing them progressively when the model invokes the facade.
|
|
58
|
-
*
|
|
59
|
-
* The `tools` and `handlers` parameters accept the exact same types as `AgentConfig.toolDefinitions`
|
|
60
|
-
* and `AgentConfig.toolHandlers`, so existing tool creation functions can be passed in unchanged.
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* ```typescript
|
|
64
|
-
* const tradingTools = createToolFold({
|
|
65
|
-
* name: 'trading_tools',
|
|
66
|
-
* tools: createTradesToolDefinitions(),
|
|
67
|
-
* handlers: createTradesToolHandlers(connect),
|
|
68
|
-
* });
|
|
69
|
-
*
|
|
70
|
-
* const agent: AgentConfig = {
|
|
71
|
-
* name: 'Trades',
|
|
72
|
-
* systemPrompt: '...',
|
|
73
|
-
* toolDefinitions: [getCurrentDateDef, tradingTools.definition],
|
|
74
|
-
* toolHandlers: { get_current_date: handler, ...tradingTools.handler },
|
|
75
|
-
* };
|
|
76
|
-
* ```
|
|
77
|
-
*
|
|
78
|
-
* @example Nesting folds
|
|
79
|
-
* ```typescript
|
|
80
|
-
* const insertFold = createToolFold({
|
|
81
|
-
* name: 'trade_insertion',
|
|
82
|
-
* tools: [validateDef, insertDef],
|
|
83
|
-
* handlers: { validate_trade: vHandler, insert_trade: iHandler },
|
|
84
|
-
* });
|
|
85
|
-
*
|
|
86
|
-
* const tradingFold = createToolFold({
|
|
87
|
-
* name: 'trading_tools',
|
|
88
|
-
* tools: [searchDef, insertFold.definition],
|
|
89
|
-
* handlers: { search_trades: sHandler, ...insertFold.handler },
|
|
90
|
-
* });
|
|
91
|
-
* ```
|
|
92
|
-
*
|
|
93
|
-
* @beta
|
|
94
|
-
*/
|
|
95
|
-
export function createToolFold(config: {
|
|
96
|
-
/** Facade tool name — used as the tool name the model calls to open this fold. */
|
|
97
|
-
name: string;
|
|
98
|
-
/**
|
|
99
|
-
* Description shown to the model for the facade tool.
|
|
100
|
-
* If omitted, auto-generated from the inner tool names:
|
|
101
|
-
* "Contains: tool_a, tool_b. Invoke to access these tools."
|
|
102
|
-
*/
|
|
103
|
-
description?: string;
|
|
104
|
-
/**
|
|
105
|
-
* Optional usage guidance injected into the fold-open response message.
|
|
106
|
-
* Not included in the facade description — only visible once the fold is open.
|
|
107
|
-
*/
|
|
108
|
-
usageNotes?: string;
|
|
109
|
-
/** Inner tool definitions. Same type as `AgentConfig.toolDefinitions`. */
|
|
110
|
-
tools: ChatToolDefinition[];
|
|
111
|
-
/** Inner tool handlers. Same type as `AgentConfig.toolHandlers`. */
|
|
112
|
-
handlers: ChatToolHandlers;
|
|
113
|
-
/**
|
|
114
|
-
* When true (default), all other tools are removed when this fold opens.
|
|
115
|
-
* When false, sibling tools remain available alongside the inner tools.
|
|
116
|
-
*/
|
|
117
|
-
exclusive?: boolean;
|
|
118
|
-
}): ToolFoldResult {
|
|
119
|
-
const description =
|
|
120
|
-
config.description ??
|
|
121
|
-
`Contains: ${config.tools.map((t) => t.name).join(', ')}. Invoke to access these tools.`;
|
|
122
|
-
|
|
123
|
-
const fold: ToolFold = {
|
|
124
|
-
name: config.name,
|
|
125
|
-
description,
|
|
126
|
-
usageNotes: config.usageNotes,
|
|
127
|
-
tools: config.tools,
|
|
128
|
-
handlers: config.handlers,
|
|
129
|
-
exclusive: config.exclusive ?? true,
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const definition: ChatToolDefinition = {
|
|
133
|
-
name: config.name,
|
|
134
|
-
description,
|
|
135
|
-
parameters: { type: 'object', properties: {} },
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
// The facade handler body is never actually called — the driver intercepts it
|
|
139
|
-
// via TOOL_FOLD_SYMBOL before dispatch. The body is a no-op fallback.
|
|
140
|
-
const facadeHandler = async (): Promise<string> => `Fold: ${config.name}`;
|
|
141
|
-
(facadeHandler as any)[TOOL_FOLD_SYMBOL] = fold;
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
definition,
|
|
145
|
-
handler: {
|
|
146
|
-
[config.name]: facadeHandler,
|
|
147
|
-
},
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* A tool entry in the expanded debug tree.
|
|
153
|
-
* Folds are represented as nodes with a `tools` array; regular tools have none.
|
|
154
|
-
*
|
|
155
|
-
* @beta
|
|
156
|
-
*/
|
|
157
|
-
export interface ToolTreeNode extends ChatToolDefinition {
|
|
158
|
-
/** Present when this tool is a fold — contains its inner tools recursively expanded. */
|
|
159
|
-
tools?: ToolTreeNode[];
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Expands a flat list of tool definitions into a nested tree by resolving fold
|
|
164
|
-
* metadata from the corresponding handlers. Use this for debug output so the
|
|
165
|
-
* full tool hierarchy is visible rather than just the top-level facade names.
|
|
166
|
-
*
|
|
167
|
-
* @beta
|
|
168
|
-
*/
|
|
169
|
-
export function expandToolTree(
|
|
170
|
-
definitions: ChatToolDefinition[],
|
|
171
|
-
handlers: ChatToolHandlers,
|
|
172
|
-
): ToolTreeNode[] {
|
|
173
|
-
return definitions.map((def) => {
|
|
174
|
-
const handler = handlers[def.name];
|
|
175
|
-
const fold: ToolFold | undefined = handler ? (handler as any)[TOOL_FOLD_SYMBOL] : undefined;
|
|
176
|
-
if (fold) {
|
|
177
|
-
return { ...def, tools: expandToolTree(fold.tools, fold.handlers) };
|
|
178
|
-
}
|
|
179
|
-
return { ...def };
|
|
180
|
-
});
|
|
181
|
-
}
|