@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,69 +0,0 @@
|
|
|
1
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { resolvePreferenceBaseline } from './resolve-preference-baseline';
|
|
3
|
-
// GENC-1351 #2: the preference baseline resolves with precedence
|
|
4
|
-
// slice defaults < chatConfig.ui defaults < stored user preferences.
|
|
5
|
-
// The regression this guards: when stored prefs are absent (first load / wiped
|
|
6
|
-
// backend) the result must fall through to the chatConfig.ui defaults, NOT to
|
|
7
|
-
// everything-disabled.
|
|
8
|
-
const FULL_UI = {
|
|
9
|
-
showToolCalls: true,
|
|
10
|
-
showThinkingSteps: true,
|
|
11
|
-
showNarration: true,
|
|
12
|
-
showAgentSwitchIndicator: true,
|
|
13
|
-
animations: { userConfigurable: true },
|
|
14
|
-
};
|
|
15
|
-
const Suite = createLogicSuite('resolvePreferenceBaseline');
|
|
16
|
-
Suite('with no stored prefs, falls back to chatConfig.ui defaults (the nuke case)', () => {
|
|
17
|
-
const r = resolvePreferenceBaseline(FULL_UI, null);
|
|
18
|
-
assert.is(r.showToolCalls, true);
|
|
19
|
-
assert.is(r.showThinkingSteps, true);
|
|
20
|
-
assert.is(r.showNarration, true);
|
|
21
|
-
assert.is(r.showAgentSwitchIndicator, true);
|
|
22
|
-
assert.ok(r.enabledAnimations.length > 0, 'config animations enabled by default');
|
|
23
|
-
assert.is(r.enabled, undefined, 'no stored enabled → leave host default untouched');
|
|
24
|
-
});
|
|
25
|
-
Suite('with no stored prefs AND empty/unbound chatConfig, everything is off', () => {
|
|
26
|
-
// The transient state during connect before chatConfig binds — all-false is
|
|
27
|
-
// correct HERE; the element re-runs the baseline once chatConfig arrives.
|
|
28
|
-
const r = resolvePreferenceBaseline(undefined, null);
|
|
29
|
-
assert.equal([r.showToolCalls, r.showThinkingSteps, r.showNarration, r.showAgentSwitchIndicator], [false, false, false, false]);
|
|
30
|
-
assert.equal(r.enabledAnimations, []);
|
|
31
|
-
});
|
|
32
|
-
Suite('stored prefs override the config defaults field-by-field', () => {
|
|
33
|
-
const stored = {
|
|
34
|
-
enabled: true,
|
|
35
|
-
showToolCalls: false,
|
|
36
|
-
showThinkingSteps: false,
|
|
37
|
-
showNarration: true,
|
|
38
|
-
showAgentSwitchIndicator: false,
|
|
39
|
-
animations: ['halo'],
|
|
40
|
-
};
|
|
41
|
-
const r = resolvePreferenceBaseline(FULL_UI, stored);
|
|
42
|
-
assert.is(r.showToolCalls, false, 'stored false beats config true');
|
|
43
|
-
assert.is(r.showThinkingSteps, false);
|
|
44
|
-
assert.is(r.showNarration, true);
|
|
45
|
-
assert.is(r.showAgentSwitchIndicator, false);
|
|
46
|
-
assert.equal(r.enabledAnimations, ['halo'], 'stored animations applied as-is');
|
|
47
|
-
assert.is(r.enabled, true, 'stored enabled surfaces');
|
|
48
|
-
});
|
|
49
|
-
Suite('a partial stored blob overrides only present fields; others keep config default', () => {
|
|
50
|
-
const stored = { showToolCalls: false };
|
|
51
|
-
const r = resolvePreferenceBaseline(FULL_UI, stored);
|
|
52
|
-
assert.is(r.showToolCalls, false, 'present field overridden');
|
|
53
|
-
assert.is(r.showThinkingSteps, true, 'absent field keeps config default');
|
|
54
|
-
assert.is(r.showAgentSwitchIndicator, true);
|
|
55
|
-
assert.is(r.enabled, undefined, 'absent enabled stays undefined');
|
|
56
|
-
});
|
|
57
|
-
Suite('stored prefs over an empty config still apply (config layer is all-false)', () => {
|
|
58
|
-
const stored = { showToolCalls: true, showThinkingSteps: true };
|
|
59
|
-
const r = resolvePreferenceBaseline(undefined, stored);
|
|
60
|
-
assert.is(r.showToolCalls, true);
|
|
61
|
-
assert.is(r.showThinkingSteps, true);
|
|
62
|
-
assert.is(r.showNarration, false, 'not in stored, not in config → false');
|
|
63
|
-
});
|
|
64
|
-
Suite('de-dupes stored animations (heals blobs from the old settings-modal append bug)', () => {
|
|
65
|
-
const stored = { animations: ['loading', 'halo', 'loading', 'halo'] };
|
|
66
|
-
const r = resolvePreferenceBaseline(FULL_UI, stored);
|
|
67
|
-
assert.equal(r.enabledAnimations, ['loading', 'halo'], 'duplicates collapsed, order preserved');
|
|
68
|
-
});
|
|
69
|
-
Suite.run();
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { __awaiter } from "tslib";
|
|
2
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
3
|
-
import { stripAgentHandlers } from './strip-agent-handlers';
|
|
4
|
-
const cfg = (overrides) => (Object.assign({ name: 'agent' }, overrides));
|
|
5
|
-
/** Mirrors what redux's serializable-state check does: find any function anywhere. */
|
|
6
|
-
const hasFunctionDeep = (value) => {
|
|
7
|
-
if (typeof value === 'function')
|
|
8
|
-
return true;
|
|
9
|
-
if (Array.isArray(value))
|
|
10
|
-
return value.some(hasFunctionDeep);
|
|
11
|
-
if (value !== null && typeof value === 'object') {
|
|
12
|
-
return Object.values(value).some(hasFunctionDeep);
|
|
13
|
-
}
|
|
14
|
-
return false;
|
|
15
|
-
};
|
|
16
|
-
const suite = createLogicSuite('stripAgentHandlers');
|
|
17
|
-
suite('drops object-form toolHandlers (the redux-leak regression)', () => {
|
|
18
|
-
const stripped = stripAgentHandlers(cfg({ toolHandlers: { vfs_list: () => __awaiter(void 0, void 0, void 0, function* () { return 'x'; }), vfs_read: () => __awaiter(void 0, void 0, void 0, function* () { return 'y'; }) } }));
|
|
19
|
-
// The whole handler bag is gone — not left behind with live functions inside.
|
|
20
|
-
assert.not.ok(stripped.toolHandlers);
|
|
21
|
-
assert.not.ok(hasFunctionDeep(stripped));
|
|
22
|
-
});
|
|
23
|
-
suite('drops factory-function toolHandlers and the lifecycle/dispatch hooks', () => {
|
|
24
|
-
const stripped = stripAgentHandlers(cfg({
|
|
25
|
-
toolHandlers: () => ({ t: () => __awaiter(void 0, void 0, void 0, function* () { return 'x'; }) }),
|
|
26
|
-
onActivate: () => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
27
|
-
onDeactivate: () => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
28
|
-
getDebugSnapshot: () => ({}),
|
|
29
|
-
onUnresolvedTool: () => 'redirect',
|
|
30
|
-
}));
|
|
31
|
-
assert.not.ok(stripped.toolHandlers);
|
|
32
|
-
assert.not.ok(stripped.onActivate);
|
|
33
|
-
assert.not.ok(stripped.onDeactivate);
|
|
34
|
-
assert.not.ok(stripped.getDebugSnapshot);
|
|
35
|
-
assert.not.ok(stripped.onUnresolvedTool);
|
|
36
|
-
});
|
|
37
|
-
suite('drops function-form resolvers but keeps static forms and plain data', () => {
|
|
38
|
-
const stripped = stripAgentHandlers(cfg({
|
|
39
|
-
systemPrompt: () => 'dynamic', // function → dropped
|
|
40
|
-
displayName: 'Static Name', // string → kept
|
|
41
|
-
temperature: 0.5, // number → kept
|
|
42
|
-
toolChoice: 'auto', // string → kept
|
|
43
|
-
toolDefinitions: [{ name: 'x', description: 'd', parameters: {} }], // data array → kept
|
|
44
|
-
manualSelection: { enabled: true, hint: 'pick me' }, // data object → kept
|
|
45
|
-
}));
|
|
46
|
-
assert.not.ok(stripped.systemPrompt);
|
|
47
|
-
assert.is(stripped.displayName, 'Static Name');
|
|
48
|
-
assert.is(stripped.temperature, 0.5);
|
|
49
|
-
assert.is(stripped.toolChoice, 'auto');
|
|
50
|
-
assert.equal(stripped.toolDefinitions, [{ name: 'x', description: 'd', parameters: {} }]);
|
|
51
|
-
assert.equal(stripped.manualSelection, { enabled: true, hint: 'pick me' });
|
|
52
|
-
});
|
|
53
|
-
suite('recurses into subAgents, stripping their object-form toolHandlers', () => {
|
|
54
|
-
const stripped = stripAgentHandlers(cfg({
|
|
55
|
-
name: 'boss',
|
|
56
|
-
toolHandlers: () => ({}),
|
|
57
|
-
subAgents: [
|
|
58
|
-
cfg({
|
|
59
|
-
name: 'worker',
|
|
60
|
-
toolHandlers: { vfs_list: () => __awaiter(void 0, void 0, void 0, function* () { return 'x'; }), vfs_read: () => __awaiter(void 0, void 0, void 0, function* () { return 'y'; }) },
|
|
61
|
-
}),
|
|
62
|
-
],
|
|
63
|
-
}));
|
|
64
|
-
const subs = stripped.subAgents;
|
|
65
|
-
assert.is(subs.length, 1);
|
|
66
|
-
assert.is(subs[0].name, 'worker');
|
|
67
|
-
assert.not.ok(subs[0].toolHandlers, 'sub-agent toolHandlers must be stripped (the reported bug)');
|
|
68
|
-
// Nothing function-valued survives anywhere in the projection.
|
|
69
|
-
assert.not.ok(hasFunctionDeep(stripped));
|
|
70
|
-
});
|
|
71
|
-
suite('produces a projection with no function at any depth', () => {
|
|
72
|
-
const stripped = stripAgentHandlers(cfg({
|
|
73
|
-
name: 'boss',
|
|
74
|
-
onActivate: () => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
75
|
-
subAgents: [cfg({ name: 'w', toolHandlers: { t: () => __awaiter(void 0, void 0, void 0, function* () { }) } })],
|
|
76
|
-
}));
|
|
77
|
-
assert.not.ok(hasFunctionDeep(stripped));
|
|
78
|
-
// Round-trips to the expected serializable shape.
|
|
79
|
-
assert.equal(JSON.parse(JSON.stringify(stripped)), { name: 'boss', subAgents: [{ name: 'w' }] });
|
|
80
|
-
});
|
|
81
|
-
suite.run();
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { sumCosts } from './sum-costs';
|
|
3
|
-
const userMsg = (overrides = {}) => (Object.assign({ role: 'user', content: '' }, overrides));
|
|
4
|
-
const assistantMsg = (overrides = {}) => (Object.assign({ role: 'assistant', content: '' }, overrides));
|
|
5
|
-
const suite = createLogicSuite('sumCosts');
|
|
6
|
-
suite('returns 0 for an empty message list', () => {
|
|
7
|
-
assert.is(sumCosts([]), 0);
|
|
8
|
-
});
|
|
9
|
-
suite('returns 0 when no message carries cost (e.g. all from a no-usage provider)', () => {
|
|
10
|
-
assert.is(sumCosts([userMsg(), assistantMsg({ content: 'hi' })]), 0);
|
|
11
|
-
});
|
|
12
|
-
suite('sums top-level message costs', () => {
|
|
13
|
-
// Use integer-cents arithmetic to avoid float-equality flake; the production
|
|
14
|
-
// path only ever adds floats here, so any rounding lives in display, not sum.
|
|
15
|
-
const cost = sumCosts([
|
|
16
|
-
assistantMsg({ cost: 0.5 }),
|
|
17
|
-
assistantMsg({ cost: 1.5 }),
|
|
18
|
-
userMsg(), // no cost — ignored
|
|
19
|
-
]);
|
|
20
|
-
assert.is(cost, 2);
|
|
21
|
-
});
|
|
22
|
-
suite('includes sub-agent trace costs attached to a tool call', () => {
|
|
23
|
-
const parentToolCallMsg = assistantMsg({
|
|
24
|
-
cost: 0.1,
|
|
25
|
-
toolCalls: [
|
|
26
|
-
{
|
|
27
|
-
id: 'tc1',
|
|
28
|
-
name: 'spawn_sub',
|
|
29
|
-
args: {},
|
|
30
|
-
subAgentTrace: [assistantMsg({ cost: 0.5 }), assistantMsg({ cost: 0.4 })],
|
|
31
|
-
},
|
|
32
|
-
],
|
|
33
|
-
});
|
|
34
|
-
// 0.1 (parent) + 0.5 + 0.4 (sub) = 1.0
|
|
35
|
-
assert.is(sumCosts([parentToolCallMsg]), 1);
|
|
36
|
-
});
|
|
37
|
-
suite('recurses into nested sub-agent traces', () => {
|
|
38
|
-
const innerTrace = [assistantMsg({ cost: 0.25 })];
|
|
39
|
-
const middleTrace = [
|
|
40
|
-
assistantMsg({
|
|
41
|
-
cost: 0.5,
|
|
42
|
-
toolCalls: [{ id: 'inner', name: 'spawn_inner', args: {}, subAgentTrace: innerTrace }],
|
|
43
|
-
}),
|
|
44
|
-
];
|
|
45
|
-
const top = assistantMsg({
|
|
46
|
-
cost: 1,
|
|
47
|
-
toolCalls: [{ id: 'middle', name: 'spawn_middle', args: {}, subAgentTrace: middleTrace }],
|
|
48
|
-
});
|
|
49
|
-
// 1 + 0.5 + 0.25 = 1.75
|
|
50
|
-
assert.is(sumCosts([top]), 1.75);
|
|
51
|
-
});
|
|
52
|
-
suite('handles a mix of tool calls with and without subAgentTrace', () => {
|
|
53
|
-
const cost = sumCosts([
|
|
54
|
-
assistantMsg({
|
|
55
|
-
cost: 1,
|
|
56
|
-
toolCalls: [
|
|
57
|
-
{ id: 'a', name: 'plain_tool', args: {} }, // no trace — skipped
|
|
58
|
-
{
|
|
59
|
-
id: 'b',
|
|
60
|
-
name: 'sub_tool',
|
|
61
|
-
args: {},
|
|
62
|
-
subAgentTrace: [assistantMsg({ cost: 2 })],
|
|
63
|
-
},
|
|
64
|
-
],
|
|
65
|
-
}),
|
|
66
|
-
]);
|
|
67
|
-
assert.is(cost, 3);
|
|
68
|
-
});
|
|
69
|
-
suite('ignores missing cost fields inside an otherwise-populated trace', () => {
|
|
70
|
-
const cost = sumCosts([
|
|
71
|
-
assistantMsg({
|
|
72
|
-
toolCalls: [
|
|
73
|
-
{
|
|
74
|
-
id: 'tc',
|
|
75
|
-
name: 'sub',
|
|
76
|
-
args: {},
|
|
77
|
-
subAgentTrace: [
|
|
78
|
-
userMsg(), // role=user, no cost
|
|
79
|
-
assistantMsg({ content: 'partial', cost: 0.3 }),
|
|
80
|
-
assistantMsg({ content: 'unbilled' }), // no cost field
|
|
81
|
-
],
|
|
82
|
-
},
|
|
83
|
-
],
|
|
84
|
-
}),
|
|
85
|
-
]);
|
|
86
|
-
assert.is(cost, 0.3);
|
|
87
|
-
});
|
|
88
|
-
suite('adds widget-reported externalCostUsd alongside provider cost', () => {
|
|
89
|
-
// A message can carry both: provider LLM `cost` and a widget's `externalCostUsd`.
|
|
90
|
-
const cost = sumCosts([
|
|
91
|
-
assistantMsg({ cost: 0.5 }), // provider only
|
|
92
|
-
assistantMsg({ externalCostUsd: 0.25 }), // external only (e.g. an interaction message)
|
|
93
|
-
assistantMsg({ cost: 0.1, externalCostUsd: 0.4 }), // both
|
|
94
|
-
userMsg(), // neither — ignored
|
|
95
|
-
]);
|
|
96
|
-
// 0.5 + 0.25 + 0.1 + 0.4 = 1.25
|
|
97
|
-
assert.is(cost, 1.25);
|
|
98
|
-
});
|
|
99
|
-
suite('includes externalCostUsd inside a sub-agent trace', () => {
|
|
100
|
-
const cost = sumCosts([
|
|
101
|
-
assistantMsg({
|
|
102
|
-
toolCalls: [
|
|
103
|
-
{
|
|
104
|
-
id: 'tc',
|
|
105
|
-
name: 'sub',
|
|
106
|
-
args: {},
|
|
107
|
-
subAgentTrace: [assistantMsg({ cost: 0.5, externalCostUsd: 0.5 })],
|
|
108
|
-
},
|
|
109
|
-
],
|
|
110
|
-
}),
|
|
111
|
-
]);
|
|
112
|
-
assert.is(cost, 1);
|
|
113
|
-
});
|
|
114
|
-
suite.run();
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { formatCompactTokenCount, formatCompactTokenCountSafe, sumTokens } from './sum-tokens';
|
|
3
|
-
const assistantMsg = (overrides = {}) => (Object.assign({ role: 'assistant', content: '' }, overrides));
|
|
4
|
-
const suite = createLogicSuite('sumTokens');
|
|
5
|
-
suite('returns 0 for an empty message list', () => {
|
|
6
|
-
assert.is(sumTokens([]), 0);
|
|
7
|
-
});
|
|
8
|
-
suite('sums input and output tokens on top-level messages', () => {
|
|
9
|
-
assert.is(sumTokens([
|
|
10
|
-
assistantMsg({ inputTokens: 100, outputTokens: 50 }),
|
|
11
|
-
assistantMsg({ inputTokens: 200, outputTokens: 0 }),
|
|
12
|
-
]), 350);
|
|
13
|
-
});
|
|
14
|
-
suite('includes sub-agent trace tokens attached to a tool call', () => {
|
|
15
|
-
assert.is(sumTokens([
|
|
16
|
-
assistantMsg({
|
|
17
|
-
inputTokens: 10,
|
|
18
|
-
outputTokens: 5,
|
|
19
|
-
toolCalls: [
|
|
20
|
-
{
|
|
21
|
-
id: 'tc1',
|
|
22
|
-
name: 'spawn_sub',
|
|
23
|
-
args: {},
|
|
24
|
-
subAgentTrace: [assistantMsg({ inputTokens: 1000, outputTokens: 400 })],
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
}),
|
|
28
|
-
]), 1415);
|
|
29
|
-
});
|
|
30
|
-
const formatSuite = createLogicSuite('formatCompactTokenCount');
|
|
31
|
-
formatSuite('formats millions with one decimal when needed', () => {
|
|
32
|
-
assert.is(formatCompactTokenCount(1400000), '<span class="value">1.4</span><span class="suffix">M</span>');
|
|
33
|
-
});
|
|
34
|
-
formatSuite('formats whole millions without a decimal', () => {
|
|
35
|
-
assert.is(formatCompactTokenCount(2000000), '<span class="value">2</span><span class="suffix">M</span>');
|
|
36
|
-
});
|
|
37
|
-
formatSuite('formats thousands compactly', () => {
|
|
38
|
-
assert.is(formatCompactTokenCount(3200), '<span class="value">3.2</span><span class="suffix">K</span>');
|
|
39
|
-
});
|
|
40
|
-
formatSuite('formats small counts with grouping', () => {
|
|
41
|
-
assert.is(formatCompactTokenCount(842), '842');
|
|
42
|
-
});
|
|
43
|
-
formatSuite('rolls K suffix up to M when rounding would yield 1000K', () => {
|
|
44
|
-
assert.is(formatCompactTokenCount(999950), '<span class="value">1</span><span class="suffix">M</span>');
|
|
45
|
-
assert.is(formatCompactTokenCount(999949), '<span class="value">999.9</span><span class="suffix">K</span>');
|
|
46
|
-
});
|
|
47
|
-
formatSuite('safe formatter preserves compact markup', () => {
|
|
48
|
-
assert.is(formatCompactTokenCountSafe(1400000), '<span class="value">1.4</span><span class="suffix">M</span>');
|
|
49
|
-
});
|
|
50
|
-
suite.run();
|
|
51
|
-
formatSuite.run();
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { addUsage, emptyUsage, messageUsage, sumUsage, totalTokens } from './sum-usage';
|
|
3
|
-
const suite = createLogicSuite('sumUsage');
|
|
4
|
-
const msg = (over = {}) => (Object.assign({ role: 'assistant', content: '' }, over));
|
|
5
|
-
suite('returns zeroes when no message carries usage', () => {
|
|
6
|
-
const usage = sumUsage([msg({ role: 'user', content: 'hi' })]);
|
|
7
|
-
assert.equal(usage, emptyUsage());
|
|
8
|
-
assert.is(totalTokens(usage), 0);
|
|
9
|
-
});
|
|
10
|
-
suite('splits the prompt into three disjoint input buckets', () => {
|
|
11
|
-
// `inputTokens` is the WHOLE prompt; the cache fields break it down. So uncached is
|
|
12
|
-
// the remainder, and the three must re-add to the prompt exactly — that identity is
|
|
13
|
-
// what stops the buckets double-counting the prompt when summed.
|
|
14
|
-
const usage = sumUsage([
|
|
15
|
-
msg({ inputTokens: 1000, cacheReadTokens: 700, cacheWriteTokens: 200, outputTokens: 50 }),
|
|
16
|
-
]);
|
|
17
|
-
assert.is(usage.uncachedInputTokens, 100);
|
|
18
|
-
assert.is(usage.cacheReadTokens, 700);
|
|
19
|
-
assert.is(usage.cacheWriteTokens, 200);
|
|
20
|
-
assert.is(usage.outputTokens, 50);
|
|
21
|
-
assert.is(usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens, 1000, 'input buckets re-add to the prompt total');
|
|
22
|
-
assert.is(totalTokens(usage), 1050);
|
|
23
|
-
});
|
|
24
|
-
suite('treats the whole prompt as uncached when no cache split is reported', () => {
|
|
25
|
-
// Providers that report no cache breakdown (and messages persisted before the fields
|
|
26
|
-
// existed) must still contribute their full prompt, not drop it.
|
|
27
|
-
const usage = sumUsage([msg({ inputTokens: 400, outputTokens: 60 })]);
|
|
28
|
-
assert.is(usage.uncachedInputTokens, 400);
|
|
29
|
-
assert.is(usage.cacheReadTokens, 0);
|
|
30
|
-
assert.is(usage.cacheWriteTokens, 0);
|
|
31
|
-
assert.is(totalTokens(usage), 460);
|
|
32
|
-
});
|
|
33
|
-
suite('clamps uncached input at zero when buckets exceed the prompt total', () => {
|
|
34
|
-
// Malformed or hand-edited history must not be able to drive a cost display negative.
|
|
35
|
-
const usage = sumUsage([
|
|
36
|
-
msg({ inputTokens: 100, cacheReadTokens: 90, cacheWriteTokens: 90, outputTokens: 0 }),
|
|
37
|
-
]);
|
|
38
|
-
assert.is(usage.uncachedInputTokens, 0);
|
|
39
|
-
});
|
|
40
|
-
suite('sums LLM cost and widget-reported external cost', () => {
|
|
41
|
-
const usage = sumUsage([msg({ cost: 0.25 }), msg({ externalCostUsd: 0.1, cost: 0.05 })]);
|
|
42
|
-
assert.is(Math.round(usage.costUsd * 100) / 100, 0.4);
|
|
43
|
-
});
|
|
44
|
-
suite('recurses into sub-agent traces', () => {
|
|
45
|
-
// A sub-agent's whole conversation is embedded in the parent's tool call, so this is
|
|
46
|
-
// the only place its spend is visible to a host total.
|
|
47
|
-
const usage = sumUsage([
|
|
48
|
-
msg({
|
|
49
|
-
toolCalls: [
|
|
50
|
-
{
|
|
51
|
-
id: 'tc-1',
|
|
52
|
-
name: 'delegate',
|
|
53
|
-
args: {},
|
|
54
|
-
subAgentTrace: [
|
|
55
|
-
msg({ cost: 0.2, inputTokens: 300, cacheReadTokens: 100, outputTokens: 40 }),
|
|
56
|
-
],
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
}),
|
|
60
|
-
]);
|
|
61
|
-
assert.is(usage.costUsd, 0.2);
|
|
62
|
-
assert.is(usage.uncachedInputTokens, 200);
|
|
63
|
-
assert.is(usage.cacheReadTokens, 100);
|
|
64
|
-
assert.is(usage.outputTokens, 40);
|
|
65
|
-
});
|
|
66
|
-
suite('counts usage banked onto a compaction summary', () => {
|
|
67
|
-
// Compaction destroys the messages it summarises. Without the banked rollup, a total
|
|
68
|
-
// re-derived from the transcript would fall by whatever those turns had spent.
|
|
69
|
-
const before = sumUsage([
|
|
70
|
-
msg({ cost: 0.5, inputTokens: 1000, cacheReadTokens: 400, outputTokens: 100 }),
|
|
71
|
-
msg({ cost: 0.3, inputTokens: 500, outputTokens: 50 }),
|
|
72
|
-
]);
|
|
73
|
-
const afterCompaction = sumUsage([
|
|
74
|
-
msg({
|
|
75
|
-
role: 'compacted-summary',
|
|
76
|
-
content: 'summary of the above',
|
|
77
|
-
compaction: {
|
|
78
|
-
compactedCount: 2,
|
|
79
|
-
createdAt: '2026-06-14T12:00:00.000Z',
|
|
80
|
-
rolledUpUsage: before,
|
|
81
|
-
},
|
|
82
|
-
}),
|
|
83
|
-
]);
|
|
84
|
-
assert.equal(afterCompaction, before, 'compaction preserves the running total');
|
|
85
|
-
});
|
|
86
|
-
suite('ignores tokensBefore, which is a context reading rather than spend', () => {
|
|
87
|
-
// `tokensBefore` is one turn's context-window size. Counting it as cumulative spend
|
|
88
|
-
// would silently inflate the session total on every compaction.
|
|
89
|
-
const usage = sumUsage([
|
|
90
|
-
msg({
|
|
91
|
-
role: 'compacted-summary',
|
|
92
|
-
content: 'summary',
|
|
93
|
-
compaction: {
|
|
94
|
-
compactedCount: 3,
|
|
95
|
-
createdAt: '2026-06-14T12:00:00.000Z',
|
|
96
|
-
tokensBefore: 90000,
|
|
97
|
-
},
|
|
98
|
-
}),
|
|
99
|
-
]);
|
|
100
|
-
assert.is(totalTokens(usage), 0);
|
|
101
|
-
});
|
|
102
|
-
suite('addUsage adds every bucket and mutates neither operand', () => {
|
|
103
|
-
const a = Object.assign(Object.assign({}, emptyUsage()), { costUsd: 1.5, uncachedInputTokens: 100, outputTokens: 10 });
|
|
104
|
-
const b = Object.assign(Object.assign({}, emptyUsage()), { costUsd: 0.25, cacheReadTokens: 900, outputTokens: 5 });
|
|
105
|
-
const sum = addUsage(a, b);
|
|
106
|
-
assert.is(sum.costUsd, 1.75);
|
|
107
|
-
assert.is(sum.uncachedInputTokens, 100);
|
|
108
|
-
assert.is(sum.cacheReadTokens, 900);
|
|
109
|
-
assert.is(sum.outputTokens, 15);
|
|
110
|
-
// Both operands are live state (a row's banked figure and the session total), so a
|
|
111
|
-
// caller must be able to add without either being altered underneath it.
|
|
112
|
-
assert.is(a.costUsd, 1.5);
|
|
113
|
-
assert.is(b.cacheReadTokens, 900);
|
|
114
|
-
});
|
|
115
|
-
suite('addUsage with an empty operand is the identity', () => {
|
|
116
|
-
const a = Object.assign(Object.assign({}, emptyUsage()), { costUsd: 3, cacheWriteTokens: 42 });
|
|
117
|
-
assert.equal(addUsage(a, emptyUsage()), a);
|
|
118
|
-
assert.equal(addUsage(emptyUsage(), a), a);
|
|
119
|
-
});
|
|
120
|
-
suite('messageUsage is undefined for a message reporting nothing', () => {
|
|
121
|
-
// A user turn, or a display-only reasoning/narration split. Distinct from a zeroed
|
|
122
|
-
// usage: "this call reported nothing" is not "this call cost nothing".
|
|
123
|
-
assert.is(messageUsage(msg({ role: 'user', content: 'hi' })), undefined);
|
|
124
|
-
assert.is(messageUsage(msg({ content: 'thinking…', category: 'reasoning' })), undefined);
|
|
125
|
-
});
|
|
126
|
-
suite('messageUsage agrees with sumUsage over the same single message', () => {
|
|
127
|
-
// The two derivations are deliberately separate — `accumulate` mutates one
|
|
128
|
-
// accumulator rather than allocating per message — so pin them together. A drift
|
|
129
|
-
// here would put one figure on a turn entry and a different one in the session
|
|
130
|
-
// total, both looking plausible.
|
|
131
|
-
const cases = [
|
|
132
|
-
{ inputTokens: 1000, cacheReadTokens: 700, cacheWriteTokens: 200, outputTokens: 50, cost: 0.4 },
|
|
133
|
-
{ inputTokens: 400, outputTokens: 60 },
|
|
134
|
-
{ inputTokens: 100, cacheReadTokens: 90, cacheWriteTokens: 90 },
|
|
135
|
-
{ cost: 0.01, externalCostUsd: 0.02 },
|
|
136
|
-
];
|
|
137
|
-
for (const over of cases) {
|
|
138
|
-
const m = msg(over);
|
|
139
|
-
assert.equal(messageUsage(m), sumUsage([m]), `messageUsage must match sumUsage for ${JSON.stringify(over)}`);
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
suite('messageUsage is shallow — it prices the one request, not the tree', () => {
|
|
143
|
-
// Deliberately blind to sub-agent traces and banked compaction usage: it answers
|
|
144
|
-
// "what did this call cost", which is what a turn snapshot needs. `sumUsage` is the
|
|
145
|
-
// function that walks everything underneath.
|
|
146
|
-
const m = msg({
|
|
147
|
-
inputTokens: 100,
|
|
148
|
-
outputTokens: 10,
|
|
149
|
-
cost: 0.05,
|
|
150
|
-
toolCalls: [
|
|
151
|
-
{
|
|
152
|
-
id: 't1',
|
|
153
|
-
name: 'delegate',
|
|
154
|
-
args: {},
|
|
155
|
-
subAgentTrace: [msg({ inputTokens: 900, outputTokens: 90, cost: 0.5 })],
|
|
156
|
-
},
|
|
157
|
-
],
|
|
158
|
-
});
|
|
159
|
-
const usage = messageUsage(m);
|
|
160
|
-
assert.is(usage.costUsd, 0.05, 'the child conversation is not folded in');
|
|
161
|
-
assert.is(usage.uncachedInputTokens, 100);
|
|
162
|
-
assert.ok(sumUsage([m]).costUsd > usage.costUsd, 'sumUsage does fold it in');
|
|
163
|
-
});
|
|
164
|
-
suite.run();
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { sumUsage } from './sum-usage';
|
|
3
|
-
import { usageRows } from './usage-rows';
|
|
4
|
-
const msg = (over) => (Object.assign({ role: 'assistant', content: '' }, over));
|
|
5
|
-
const rolled = (over) => (Object.assign({ costUsd: 0, uncachedInputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, outputTokens: 0 }, over));
|
|
6
|
-
/** Every place spend can hide, in one transcript: a delegation and a compaction. */
|
|
7
|
-
const richHistory = () => [
|
|
8
|
-
msg({
|
|
9
|
-
role: 'compacted-summary',
|
|
10
|
-
content: 'summary',
|
|
11
|
-
compaction: {
|
|
12
|
-
compactedCount: 4,
|
|
13
|
-
rolledUpUsage: rolled({ costUsd: 0.05, uncachedInputTokens: 400, outputTokens: 60 }),
|
|
14
|
-
// The summariser's model — NOT what the banked spend ran on.
|
|
15
|
-
model: 'claude-haiku-4-5-20251001',
|
|
16
|
-
},
|
|
17
|
-
}),
|
|
18
|
-
msg({ role: 'user', content: 'go' }),
|
|
19
|
-
msg({
|
|
20
|
-
model: 'claude-sonnet-5',
|
|
21
|
-
agentName: 'boss',
|
|
22
|
-
cost: 0.01,
|
|
23
|
-
inputTokens: 1000,
|
|
24
|
-
cacheReadTokens: 900,
|
|
25
|
-
outputTokens: 50,
|
|
26
|
-
toolCalls: [
|
|
27
|
-
{
|
|
28
|
-
id: 'tc1',
|
|
29
|
-
name: 'delegate',
|
|
30
|
-
args: {},
|
|
31
|
-
subAgentTrace: [
|
|
32
|
-
msg({
|
|
33
|
-
model: 'gemini-2.5-flash',
|
|
34
|
-
agentName: 'worker',
|
|
35
|
-
cost: 0.12,
|
|
36
|
-
inputTokens: 500,
|
|
37
|
-
outputTokens: 200,
|
|
38
|
-
}),
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
}),
|
|
43
|
-
];
|
|
44
|
-
const suite = createLogicSuite('usageRows');
|
|
45
|
-
suite('reconciles exactly with sumUsage over the same input', () => {
|
|
46
|
-
// THE guarantee. A per-call view that disagrees with the total is worse than none,
|
|
47
|
-
// because both look right in isolation — which is how the consumer's own copy
|
|
48
|
-
// under-reported for a month.
|
|
49
|
-
const history = richHistory();
|
|
50
|
-
const rows = usageRows(history);
|
|
51
|
-
const total = sumUsage(history);
|
|
52
|
-
const rowCost = rows.reduce((n, r) => { var _a, _b; return n + ((_a = r.costUsd) !== null && _a !== void 0 ? _a : 0) + ((_b = r.externalCostUsd) !== null && _b !== void 0 ? _b : 0); }, 0);
|
|
53
|
-
assert.ok(Math.abs(rowCost - total.costUsd) < 1e-12, `${rowCost} vs ${total.costUsd}`);
|
|
54
|
-
const bucket = (k) => rows.reduce((n, r) => n + r[k], 0);
|
|
55
|
-
assert.is(bucket('uncachedInputTokens'), total.uncachedInputTokens, 'uncached input');
|
|
56
|
-
assert.is(bucket('cacheReadTokens'), total.cacheReadTokens, 'cache read');
|
|
57
|
-
assert.is(bucket('cacheWriteTokens'), total.cacheWriteTokens, 'cache write');
|
|
58
|
-
assert.is(bucket('outputTokens'), total.outputTokens, 'output');
|
|
59
|
-
});
|
|
60
|
-
suite('emits a compaction row so banked spend is not lost from a per-call view', () => {
|
|
61
|
-
// The shortfall this function exists to close: the summary message carries no
|
|
62
|
-
// `cost` and no token fields of its own, so a naive per-message walk drops the
|
|
63
|
-
// spend of every turn the compaction deleted.
|
|
64
|
-
const rows = usageRows(richHistory());
|
|
65
|
-
const compactionRows = rows.filter((r) => r.source === 'compaction');
|
|
66
|
-
assert.is(compactionRows.length, 1);
|
|
67
|
-
assert.is(compactionRows[0].costUsd, 0.05);
|
|
68
|
-
assert.is(compactionRows[0].uncachedInputTokens, 400);
|
|
69
|
-
assert.is(compactionRows[0].model, undefined, 'no model — the banked spend may span several, and compaction.model is the summariser');
|
|
70
|
-
});
|
|
71
|
-
suite('recurses into sub-agent traces, tagging depth and the spawning call', () => {
|
|
72
|
-
const rows = usageRows(richHistory());
|
|
73
|
-
const child = rows.find((r) => r.model === 'gemini-2.5-flash');
|
|
74
|
-
assert.ok(child, 'the sub-agent turn produced a row');
|
|
75
|
-
assert.is(child.subAgentDepth, 1);
|
|
76
|
-
assert.is(child.subAgentOf, 'tc1');
|
|
77
|
-
assert.is(child.agentName, 'worker');
|
|
78
|
-
const parent = rows.find((r) => r.model === 'claude-sonnet-5');
|
|
79
|
-
assert.is(parent.subAgentDepth, 0);
|
|
80
|
-
assert.is(parent.subAgentOf, undefined);
|
|
81
|
-
});
|
|
82
|
-
suite('prefers the provider the driver stamped over re-deriving it from the model', () => {
|
|
83
|
-
// `ChatMessage.provider` comes from the resolved provider's own status, so it is
|
|
84
|
-
// authoritative and knows vendors the model allowlists do not. Re-deriving from the
|
|
85
|
-
// model id alone drops attribution for a server-proxied turn — exactly the rows a
|
|
86
|
-
// usage ledger cares most about getting right. PR review.
|
|
87
|
-
const rows = usageRows([
|
|
88
|
-
msg({ model: 'gpt-5', provider: 'openai', cost: 1, inputTokens: 1 }),
|
|
89
|
-
// Stamped provider disagrees with what the model id would derive: the stamp still wins.
|
|
90
|
-
msg({
|
|
91
|
-
model: 'claude-sonnet-5',
|
|
92
|
-
provider: 'chrome',
|
|
93
|
-
cost: 1,
|
|
94
|
-
inputTokens: 1,
|
|
95
|
-
}),
|
|
96
|
-
]);
|
|
97
|
-
assert.equal(rows.map((r) => r.provider), ['openai', 'chrome']);
|
|
98
|
-
});
|
|
99
|
-
suite('resolves the provider from the model, and leaves it unset when unknown', () => {
|
|
100
|
-
const rows = usageRows([
|
|
101
|
-
msg({ model: 'claude-sonnet-5', cost: 1, inputTokens: 1 }),
|
|
102
|
-
msg({ model: 'gemini-2.5-flash', cost: 1, inputTokens: 1 }),
|
|
103
|
-
msg({ model: 'gpt-5', cost: 1, inputTokens: 1 }),
|
|
104
|
-
msg({ cost: 1, inputTokens: 1 }),
|
|
105
|
-
]);
|
|
106
|
-
assert.equal(rows.map((r) => r.provider), ['anthropic', 'gemini', undefined, undefined], 'an unrecognised or absent model leaves provider unset rather than guessing');
|
|
107
|
-
});
|
|
108
|
-
suite('splits the prompt into buckets the same way sumUsage does', () => {
|
|
109
|
-
const [row] = usageRows([
|
|
110
|
-
msg({
|
|
111
|
-
model: 'claude-sonnet-5',
|
|
112
|
-
inputTokens: 1000,
|
|
113
|
-
cacheReadTokens: 600,
|
|
114
|
-
cacheWriteTokens: 300,
|
|
115
|
-
}),
|
|
116
|
-
]);
|
|
117
|
-
assert.is(row.uncachedInputTokens, 100, 'the remainder, not an addition');
|
|
118
|
-
assert.is(row.cacheReadTokens, 600);
|
|
119
|
-
assert.is(row.cacheWriteTokens, 300);
|
|
120
|
-
});
|
|
121
|
-
suite('clamps a prompt whose cache buckets exceed the total', () => {
|
|
122
|
-
const [row] = usageRows([msg({ inputTokens: 100, cacheReadTokens: 900 })]);
|
|
123
|
-
assert.is(row.uncachedInputTokens, 0, 'never negative');
|
|
124
|
-
});
|
|
125
|
-
suite('leaves costUsd undefined when nothing was reported, rather than zero', () => {
|
|
126
|
-
// What the "carried usage but no cost" audit keys off. A zero here would read as a
|
|
127
|
-
// free call and the alarm would never fire.
|
|
128
|
-
const [row] = usageRows([msg({ model: 'claude-sonnet-5', inputTokens: 500, outputTokens: 10 })]);
|
|
129
|
-
assert.is(row.costUsd, undefined);
|
|
130
|
-
assert.is(row.source, 'request');
|
|
131
|
-
});
|
|
132
|
-
suite('records external (non-LLM) cost and counts it in the reconciliation', () => {
|
|
133
|
-
const history = [msg({ externalCostUsd: 0.4 }), msg({ model: 'claude-sonnet-5', cost: 0.1 })];
|
|
134
|
-
const rows = usageRows(history);
|
|
135
|
-
assert.is(rows.find((r) => r.externalCostUsd != null).externalCostUsd, 0.4);
|
|
136
|
-
const rowCost = rows.reduce((n, r) => { var _a, _b; return n + ((_a = r.costUsd) !== null && _a !== void 0 ? _a : 0) + ((_b = r.externalCostUsd) !== null && _b !== void 0 ? _b : 0); }, 0);
|
|
137
|
-
assert.ok(Math.abs(rowCost - sumUsage(history).costUsd) < 1e-12);
|
|
138
|
-
});
|
|
139
|
-
suite('produces no row for a message carrying no usage', () => {
|
|
140
|
-
assert.equal(usageRows([
|
|
141
|
-
msg({ role: 'user', content: 'hello' }),
|
|
142
|
-
msg({ content: 'thinking out loud', category: 'reasoning' }),
|
|
143
|
-
]), []);
|
|
144
|
-
});
|
|
145
|
-
suite('is empty for an empty transcript', () => {
|
|
146
|
-
assert.equal(usageRows([]), []);
|
|
147
|
-
});
|
|
148
|
-
suite('reconciles on a nested delegation two levels deep', () => {
|
|
149
|
-
const history = [
|
|
150
|
-
msg({
|
|
151
|
-
model: 'claude-sonnet-5',
|
|
152
|
-
cost: 0.01,
|
|
153
|
-
inputTokens: 100,
|
|
154
|
-
toolCalls: [
|
|
155
|
-
{
|
|
156
|
-
id: 'a',
|
|
157
|
-
name: 'delegate',
|
|
158
|
-
args: {},
|
|
159
|
-
subAgentTrace: [
|
|
160
|
-
msg({
|
|
161
|
-
model: 'claude-sonnet-5',
|
|
162
|
-
cost: 0.02,
|
|
163
|
-
inputTokens: 200,
|
|
164
|
-
toolCalls: [
|
|
165
|
-
{
|
|
166
|
-
id: 'b',
|
|
167
|
-
name: 'delegate',
|
|
168
|
-
args: {},
|
|
169
|
-
subAgentTrace: [msg({ model: 'gemini-2.5-flash', cost: 0.04, inputTokens: 400 })],
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
}),
|
|
173
|
-
],
|
|
174
|
-
},
|
|
175
|
-
],
|
|
176
|
-
}),
|
|
177
|
-
];
|
|
178
|
-
const rows = usageRows(history);
|
|
179
|
-
assert.equal(rows.map((r) => r.subAgentDepth), [0, 1, 2]);
|
|
180
|
-
const rowCost = rows.reduce((n, r) => { var _a; return n + ((_a = r.costUsd) !== null && _a !== void 0 ? _a : 0); }, 0);
|
|
181
|
-
assert.ok(Math.abs(rowCost - sumUsage(history).costUsd) < 1e-12, 'still reconciles');
|
|
182
|
-
});
|
|
183
|
-
suite('does not mutate the transcript', () => {
|
|
184
|
-
const history = richHistory();
|
|
185
|
-
const before = JSON.stringify(history);
|
|
186
|
-
usageRows(history);
|
|
187
|
-
assert.is(JSON.stringify(history), before, 'a projection, not a transform');
|
|
188
|
-
});
|
|
189
|
-
suite.run();
|