@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,38 +0,0 @@
|
|
|
1
|
-
import type { ChatConfig } from '@genesislcap/foundation-ai';
|
|
2
|
-
|
|
3
|
-
/** Resolved cost-history settings from `chatConfig.costHistory`. */
|
|
4
|
-
export interface ResolvedCostHistoryConfig {
|
|
5
|
-
/** When `true`, sessions are persisted automatically and history is shown. */
|
|
6
|
-
enabled: boolean;
|
|
7
|
-
/** Host label for the active session (e.g. project name). */
|
|
8
|
-
title?: string;
|
|
9
|
-
/** `localStorage` scope key; falls back to element `id` then `'default'`. */
|
|
10
|
-
scope?: string;
|
|
11
|
-
/** Show running session USD on the Usage tab. Default: `true`. */
|
|
12
|
-
showCost: boolean;
|
|
13
|
-
/** Show cumulative session tokens on the Usage tab. Default: `true`. */
|
|
14
|
-
showTokens: boolean;
|
|
15
|
-
/** Live-session badge label on the Usage tab summary. */
|
|
16
|
-
badgeLabel: string;
|
|
17
|
-
/**
|
|
18
|
-
* Host-supplied disclosure line for the Usage tab. Stays optional — unlike
|
|
19
|
-
* `badgeLabel` there is no default, because only the host knows what its `scope`
|
|
20
|
-
* spans, and the library must not assert it.
|
|
21
|
-
*/
|
|
22
|
-
summaryHint?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Resolve `chatConfig.costHistory` with defaults applied. */
|
|
26
|
-
export function resolveCostHistoryConfig(chatConfig: ChatConfig): ResolvedCostHistoryConfig {
|
|
27
|
-
const cost = chatConfig.costHistory;
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
enabled: cost?.enabled === true,
|
|
31
|
-
title: cost?.title?.trim() || undefined,
|
|
32
|
-
scope: cost?.scope?.trim() || undefined,
|
|
33
|
-
showCost: cost?.showCost !== false,
|
|
34
|
-
showTokens: cost?.showTokens !== false,
|
|
35
|
-
badgeLabel: cost?.badgeLabel ?? 'Currently building',
|
|
36
|
-
summaryHint: cost?.summaryHint?.trim() || undefined,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import type { ChatConfig } from '@genesislcap/foundation-ai';
|
|
2
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
3
|
-
import type { SessionPreferences } from '../state/persistence/session-persistence-provider';
|
|
4
|
-
import { resolvePreferenceBaseline } from './resolve-preference-baseline';
|
|
5
|
-
|
|
6
|
-
// GENC-1351 #2: the preference baseline resolves with precedence
|
|
7
|
-
// slice defaults < chatConfig.ui defaults < stored user preferences.
|
|
8
|
-
// The regression this guards: when stored prefs are absent (first load / wiped
|
|
9
|
-
// backend) the result must fall through to the chatConfig.ui defaults, NOT to
|
|
10
|
-
// everything-disabled.
|
|
11
|
-
|
|
12
|
-
const FULL_UI: ChatConfig['ui'] = {
|
|
13
|
-
showToolCalls: true,
|
|
14
|
-
showThinkingSteps: true,
|
|
15
|
-
showNarration: true,
|
|
16
|
-
showAgentSwitchIndicator: true,
|
|
17
|
-
animations: { userConfigurable: true },
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const Suite = createLogicSuite('resolvePreferenceBaseline');
|
|
21
|
-
|
|
22
|
-
Suite('with no stored prefs, falls back to chatConfig.ui defaults (the nuke case)', () => {
|
|
23
|
-
const r = resolvePreferenceBaseline(FULL_UI, null);
|
|
24
|
-
assert.is(r.showToolCalls, true);
|
|
25
|
-
assert.is(r.showThinkingSteps, true);
|
|
26
|
-
assert.is(r.showNarration, true);
|
|
27
|
-
assert.is(r.showAgentSwitchIndicator, true);
|
|
28
|
-
assert.ok(r.enabledAnimations.length > 0, 'config animations enabled by default');
|
|
29
|
-
assert.is(r.enabled, undefined, 'no stored enabled → leave host default untouched');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
Suite('with no stored prefs AND empty/unbound chatConfig, everything is off', () => {
|
|
33
|
-
// The transient state during connect before chatConfig binds — all-false is
|
|
34
|
-
// correct HERE; the element re-runs the baseline once chatConfig arrives.
|
|
35
|
-
const r = resolvePreferenceBaseline(undefined, null);
|
|
36
|
-
assert.equal(
|
|
37
|
-
[r.showToolCalls, r.showThinkingSteps, r.showNarration, r.showAgentSwitchIndicator],
|
|
38
|
-
[false, false, false, false],
|
|
39
|
-
);
|
|
40
|
-
assert.equal(r.enabledAnimations, []);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
Suite('stored prefs override the config defaults field-by-field', () => {
|
|
44
|
-
const stored: SessionPreferences = {
|
|
45
|
-
enabled: true,
|
|
46
|
-
showToolCalls: false,
|
|
47
|
-
showThinkingSteps: false,
|
|
48
|
-
showNarration: true,
|
|
49
|
-
showAgentSwitchIndicator: false,
|
|
50
|
-
animations: ['halo'],
|
|
51
|
-
};
|
|
52
|
-
const r = resolvePreferenceBaseline(FULL_UI, stored);
|
|
53
|
-
assert.is(r.showToolCalls, false, 'stored false beats config true');
|
|
54
|
-
assert.is(r.showThinkingSteps, false);
|
|
55
|
-
assert.is(r.showNarration, true);
|
|
56
|
-
assert.is(r.showAgentSwitchIndicator, false);
|
|
57
|
-
assert.equal(r.enabledAnimations, ['halo'], 'stored animations applied as-is');
|
|
58
|
-
assert.is(r.enabled, true, 'stored enabled surfaces');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
Suite('a partial stored blob overrides only present fields; others keep config default', () => {
|
|
62
|
-
const stored = { showToolCalls: false } as SessionPreferences;
|
|
63
|
-
const r = resolvePreferenceBaseline(FULL_UI, stored);
|
|
64
|
-
assert.is(r.showToolCalls, false, 'present field overridden');
|
|
65
|
-
assert.is(r.showThinkingSteps, true, 'absent field keeps config default');
|
|
66
|
-
assert.is(r.showAgentSwitchIndicator, true);
|
|
67
|
-
assert.is(r.enabled, undefined, 'absent enabled stays undefined');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
Suite('stored prefs over an empty config still apply (config layer is all-false)', () => {
|
|
71
|
-
const stored: SessionPreferences = { showToolCalls: true, showThinkingSteps: true };
|
|
72
|
-
const r = resolvePreferenceBaseline(undefined, stored);
|
|
73
|
-
assert.is(r.showToolCalls, true);
|
|
74
|
-
assert.is(r.showThinkingSteps, true);
|
|
75
|
-
assert.is(r.showNarration, false, 'not in stored, not in config → false');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
Suite('de-dupes stored animations (heals blobs from the old settings-modal append bug)', () => {
|
|
79
|
-
const stored = { animations: ['loading', 'halo', 'loading', 'halo'] } as SessionPreferences;
|
|
80
|
-
const r = resolvePreferenceBaseline(FULL_UI, stored);
|
|
81
|
-
assert.equal(r.enabledAnimations, ['loading', 'halo'], 'duplicates collapsed, order preserved');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
Suite.run();
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import type { ChatConfig } from '@genesislcap/foundation-ai';
|
|
2
|
-
import { DEFAULT_ANIMATIONS, type AiAssistantAnimation } from '../main/main.types';
|
|
3
|
-
import type { SessionPreferences } from '../state/persistence/session-persistence-provider';
|
|
4
|
-
import { resolveExclusiveLoadingStyle } from './animation-exclusivity';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* The effective user-facing preference state for a freshly created session store
|
|
8
|
-
* (GENC-1351 #2), resolved from the two inputs that feed it.
|
|
9
|
-
*
|
|
10
|
-
* @internal
|
|
11
|
-
*/
|
|
12
|
-
export interface ResolvedPreferenceBaseline {
|
|
13
|
-
showToolCalls: boolean;
|
|
14
|
-
showThinkingSteps: boolean;
|
|
15
|
-
showNarration: boolean;
|
|
16
|
-
showAgentSwitchIndicator: boolean;
|
|
17
|
-
enabledAnimations: AiAssistantAnimation[];
|
|
18
|
-
/**
|
|
19
|
-
* The remembered "remember chat" toggle — present only when stored prefs carried
|
|
20
|
-
* an `enabled` value. Left `undefined` otherwise so the caller keeps the
|
|
21
|
-
* host-configured default rather than forcing a value.
|
|
22
|
-
*/
|
|
23
|
-
enabled?: boolean;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Resolve the effective preference baseline for a fresh store, applying the
|
|
28
|
-
* precedence (lowest → highest):
|
|
29
|
-
*
|
|
30
|
-
* slice defaults < `chatConfig.ui` defaults < stored user preferences
|
|
31
|
-
*
|
|
32
|
-
* The `chatConfig.ui` layer is the fallback when the user has no stored prefs
|
|
33
|
-
* (e.g. a first-ever load, or after the persistence backend is wiped) — the case
|
|
34
|
-
* the old "read config once on connect" path got wrong whenever `chatConfig` had
|
|
35
|
-
* not bound yet. Stored prefs, when present, override only the fields they carry;
|
|
36
|
-
* absent fields keep the config default. Pure, so the precedence is unit-testable
|
|
37
|
-
* independent of the element/store wiring.
|
|
38
|
-
*
|
|
39
|
-
* Config-supplied animations are normalised through {@link resolveExclusiveLoadingStyle}
|
|
40
|
-
* (a raw config list may name both loading styles); stored animations are applied
|
|
41
|
-
* as-is, since they were already normalised when saved.
|
|
42
|
-
*
|
|
43
|
-
* @internal
|
|
44
|
-
*/
|
|
45
|
-
export function resolvePreferenceBaseline(
|
|
46
|
-
ui: ChatConfig['ui'],
|
|
47
|
-
stored: SessionPreferences | null | undefined,
|
|
48
|
-
): ResolvedPreferenceBaseline {
|
|
49
|
-
const u = ui ?? {};
|
|
50
|
-
// Layer 1 — chatConfig.ui defaults.
|
|
51
|
-
const baseline: ResolvedPreferenceBaseline = {
|
|
52
|
-
showToolCalls: u.showToolCalls === true,
|
|
53
|
-
showThinkingSteps: u.showThinkingSteps === true,
|
|
54
|
-
showNarration: u.showNarration === true,
|
|
55
|
-
showAgentSwitchIndicator: u.showAgentSwitchIndicator === true,
|
|
56
|
-
enabledAnimations: resolveExclusiveLoadingStyle(
|
|
57
|
-
(u.animations?.enabled as AiAssistantAnimation[]) ??
|
|
58
|
-
(u.animations ? [...DEFAULT_ANIMATIONS] : []),
|
|
59
|
-
),
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
// Layer 2 — stored user preferences override the fields they carry.
|
|
63
|
-
if (stored) {
|
|
64
|
-
if (stored.enabled !== undefined) baseline.enabled = stored.enabled;
|
|
65
|
-
if (stored.showToolCalls !== undefined) baseline.showToolCalls = stored.showToolCalls;
|
|
66
|
-
if (stored.showThinkingSteps !== undefined)
|
|
67
|
-
baseline.showThinkingSteps = stored.showThinkingSteps;
|
|
68
|
-
if (stored.showNarration !== undefined) baseline.showNarration = stored.showNarration;
|
|
69
|
-
if (stored.showAgentSwitchIndicator !== undefined) {
|
|
70
|
-
baseline.showAgentSwitchIndicator = stored.showAgentSwitchIndicator;
|
|
71
|
-
}
|
|
72
|
-
if (Array.isArray(stored.animations)) {
|
|
73
|
-
// De-dupe defensively — historical blobs may hold duplicates from the old
|
|
74
|
-
// settings-modal append bug (now fixed at the `setEnabledAnimations` source).
|
|
75
|
-
baseline.enabledAnimations = [...new Set(stored.animations)] as AiAssistantAnimation[];
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return baseline;
|
|
80
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import type { AgentConfig } from '../config/config';
|
|
3
|
-
import { stripAgentHandlers } from './strip-agent-handlers';
|
|
4
|
-
|
|
5
|
-
const cfg = (overrides: Record<string, unknown>): AgentConfig =>
|
|
6
|
-
({ name: 'agent', ...overrides }) as unknown as AgentConfig;
|
|
7
|
-
|
|
8
|
-
/** Mirrors what redux's serializable-state check does: find any function anywhere. */
|
|
9
|
-
const hasFunctionDeep = (value: unknown): boolean => {
|
|
10
|
-
if (typeof value === 'function') return true;
|
|
11
|
-
if (Array.isArray(value)) return value.some(hasFunctionDeep);
|
|
12
|
-
if (value !== null && typeof value === 'object') {
|
|
13
|
-
return Object.values(value).some(hasFunctionDeep);
|
|
14
|
-
}
|
|
15
|
-
return false;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const suite = createLogicSuite('stripAgentHandlers');
|
|
19
|
-
|
|
20
|
-
suite('drops object-form toolHandlers (the redux-leak regression)', () => {
|
|
21
|
-
const stripped = stripAgentHandlers(
|
|
22
|
-
cfg({ toolHandlers: { vfs_list: async () => 'x', vfs_read: async () => 'y' } }),
|
|
23
|
-
) as Record<string, unknown>;
|
|
24
|
-
// The whole handler bag is gone — not left behind with live functions inside.
|
|
25
|
-
assert.not.ok(stripped.toolHandlers);
|
|
26
|
-
assert.not.ok(hasFunctionDeep(stripped));
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
suite('drops factory-function toolHandlers and the lifecycle/dispatch hooks', () => {
|
|
30
|
-
const stripped = stripAgentHandlers(
|
|
31
|
-
cfg({
|
|
32
|
-
toolHandlers: () => ({ t: async () => 'x' }),
|
|
33
|
-
onActivate: async () => {},
|
|
34
|
-
onDeactivate: async () => {},
|
|
35
|
-
getDebugSnapshot: () => ({}),
|
|
36
|
-
onUnresolvedTool: () => 'redirect',
|
|
37
|
-
}),
|
|
38
|
-
) as Record<string, unknown>;
|
|
39
|
-
assert.not.ok(stripped.toolHandlers);
|
|
40
|
-
assert.not.ok(stripped.onActivate);
|
|
41
|
-
assert.not.ok(stripped.onDeactivate);
|
|
42
|
-
assert.not.ok(stripped.getDebugSnapshot);
|
|
43
|
-
assert.not.ok(stripped.onUnresolvedTool);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
suite('drops function-form resolvers but keeps static forms and plain data', () => {
|
|
47
|
-
const stripped = stripAgentHandlers(
|
|
48
|
-
cfg({
|
|
49
|
-
systemPrompt: () => 'dynamic', // function → dropped
|
|
50
|
-
displayName: 'Static Name', // string → kept
|
|
51
|
-
temperature: 0.5, // number → kept
|
|
52
|
-
toolChoice: 'auto', // string → kept
|
|
53
|
-
toolDefinitions: [{ name: 'x', description: 'd', parameters: {} }], // data array → kept
|
|
54
|
-
manualSelection: { enabled: true, hint: 'pick me' }, // data object → kept
|
|
55
|
-
}),
|
|
56
|
-
) as Record<string, unknown>;
|
|
57
|
-
assert.not.ok(stripped.systemPrompt);
|
|
58
|
-
assert.is(stripped.displayName, 'Static Name');
|
|
59
|
-
assert.is(stripped.temperature, 0.5);
|
|
60
|
-
assert.is(stripped.toolChoice, 'auto');
|
|
61
|
-
assert.equal(stripped.toolDefinitions, [{ name: 'x', description: 'd', parameters: {} }]);
|
|
62
|
-
assert.equal(stripped.manualSelection, { enabled: true, hint: 'pick me' });
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
suite('recurses into subAgents, stripping their object-form toolHandlers', () => {
|
|
66
|
-
const stripped = stripAgentHandlers(
|
|
67
|
-
cfg({
|
|
68
|
-
name: 'boss',
|
|
69
|
-
toolHandlers: () => ({}),
|
|
70
|
-
subAgents: [
|
|
71
|
-
cfg({
|
|
72
|
-
name: 'worker',
|
|
73
|
-
toolHandlers: { vfs_list: async () => 'x', vfs_read: async () => 'y' },
|
|
74
|
-
}),
|
|
75
|
-
],
|
|
76
|
-
}),
|
|
77
|
-
) as Record<string, unknown>;
|
|
78
|
-
const subs = stripped.subAgents as Array<Record<string, unknown>>;
|
|
79
|
-
assert.is(subs.length, 1);
|
|
80
|
-
assert.is(subs[0].name, 'worker');
|
|
81
|
-
assert.not.ok(subs[0].toolHandlers, 'sub-agent toolHandlers must be stripped (the reported bug)');
|
|
82
|
-
// Nothing function-valued survives anywhere in the projection.
|
|
83
|
-
assert.not.ok(hasFunctionDeep(stripped));
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
suite('produces a projection with no function at any depth', () => {
|
|
87
|
-
const stripped = stripAgentHandlers(
|
|
88
|
-
cfg({
|
|
89
|
-
name: 'boss',
|
|
90
|
-
onActivate: async () => {},
|
|
91
|
-
subAgents: [cfg({ name: 'w', toolHandlers: { t: async () => {} } })],
|
|
92
|
-
}),
|
|
93
|
-
);
|
|
94
|
-
assert.not.ok(hasFunctionDeep(stripped));
|
|
95
|
-
// Round-trips to the expected serializable shape.
|
|
96
|
-
assert.equal(JSON.parse(JSON.stringify(stripped)), { name: 'boss', subAgents: [{ name: 'w' }] });
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
suite.run();
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import type { AgentConfig } from '../config/config';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Project an `AgentConfig` down to a JSON-serializable shape for the redux
|
|
5
|
-
* session store. Drops two kinds of field:
|
|
6
|
-
*
|
|
7
|
-
* 1. **Directly function-valued** — the lifecycle/dispatch hooks (`onActivate`,
|
|
8
|
-
* `onDeactivate`, `getDebugSnapshot`, `onUnresolvedTool`) and the function
|
|
9
|
-
* form of the per-turn resolvers (`systemPrompt`, `toolDefinitions`,
|
|
10
|
-
* `displayName`, `provider`, `temperature`, `toolChoice`, `cachePolicy`,
|
|
11
|
-
* `thinkingPolicy`, `toolHandlers`).
|
|
12
|
-
* 2. **Object "handler bags" whose *values* are functions** — `toolHandlers` in
|
|
13
|
-
* its object form is `{ name: handler }`, so `typeof` is `'object'`, not
|
|
14
|
-
* `'function'`. A by-value check on the field alone misses it, leaking a live
|
|
15
|
-
* handler into store state and tripping redux's serializability check (the
|
|
16
|
-
* regression this guards against — top-level agents tend to use the factory
|
|
17
|
-
* function form, but sub-agents commonly declare an object literal).
|
|
18
|
-
*
|
|
19
|
-
* `subAgents` are projected recursively. Static forms (string / number / array /
|
|
20
|
-
* plain data object) pass through unchanged.
|
|
21
|
-
*
|
|
22
|
-
* Filtering by value rather than an explicit field list means a new
|
|
23
|
-
* function-valued field on `AgentConfig` is handled automatically — no denylist
|
|
24
|
-
* to maintain. The live config on the driver stays the source of truth; the
|
|
25
|
-
* slice only holds this serializable projection and functions are never read
|
|
26
|
-
* back from it.
|
|
27
|
-
*
|
|
28
|
-
* @internal
|
|
29
|
-
*/
|
|
30
|
-
export function stripAgentHandlers(agent: AgentConfig): Omit<AgentConfig, 'toolHandlers'> {
|
|
31
|
-
const serializable: Record<string, unknown> = {};
|
|
32
|
-
for (const [key, value] of Object.entries(agent)) {
|
|
33
|
-
// Projected recursively below.
|
|
34
|
-
if (key === 'subAgents') continue;
|
|
35
|
-
// Directly function-valued field.
|
|
36
|
-
if (typeof value === 'function') continue;
|
|
37
|
-
// Object/array whose values include a function (e.g. `toolHandlers` in object
|
|
38
|
-
// form). Drop the whole field — the projection type omits it and a function
|
|
39
|
-
// must never reach the store.
|
|
40
|
-
if (
|
|
41
|
-
value !== null &&
|
|
42
|
-
typeof value === 'object' &&
|
|
43
|
-
Object.values(value).some((v) => typeof v === 'function')
|
|
44
|
-
) {
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
serializable[key] = value;
|
|
48
|
-
}
|
|
49
|
-
if (agent.subAgents?.length) {
|
|
50
|
-
serializable.subAgents = agent.subAgents.map(stripAgentHandlers);
|
|
51
|
-
}
|
|
52
|
-
return serializable as unknown as Omit<AgentConfig, 'toolHandlers'>;
|
|
53
|
-
}
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import type { ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
3
|
-
import { sumCosts } from './sum-costs';
|
|
4
|
-
|
|
5
|
-
const userMsg = (overrides: Partial<ChatMessage> = {}): ChatMessage => ({
|
|
6
|
-
role: 'user',
|
|
7
|
-
content: '',
|
|
8
|
-
...overrides,
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
const assistantMsg = (overrides: Partial<ChatMessage> = {}): ChatMessage => ({
|
|
12
|
-
role: 'assistant',
|
|
13
|
-
content: '',
|
|
14
|
-
...overrides,
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const suite = createLogicSuite('sumCosts');
|
|
18
|
-
|
|
19
|
-
suite('returns 0 for an empty message list', () => {
|
|
20
|
-
assert.is(sumCosts([]), 0);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
suite('returns 0 when no message carries cost (e.g. all from a no-usage provider)', () => {
|
|
24
|
-
assert.is(sumCosts([userMsg(), assistantMsg({ content: 'hi' })]), 0);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
suite('sums top-level message costs', () => {
|
|
28
|
-
// Use integer-cents arithmetic to avoid float-equality flake; the production
|
|
29
|
-
// path only ever adds floats here, so any rounding lives in display, not sum.
|
|
30
|
-
const cost = sumCosts([
|
|
31
|
-
assistantMsg({ cost: 0.5 }),
|
|
32
|
-
assistantMsg({ cost: 1.5 }),
|
|
33
|
-
userMsg(), // no cost — ignored
|
|
34
|
-
]);
|
|
35
|
-
assert.is(cost, 2);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
suite('includes sub-agent trace costs attached to a tool call', () => {
|
|
39
|
-
const parentToolCallMsg = assistantMsg({
|
|
40
|
-
cost: 0.1,
|
|
41
|
-
toolCalls: [
|
|
42
|
-
{
|
|
43
|
-
id: 'tc1',
|
|
44
|
-
name: 'spawn_sub',
|
|
45
|
-
args: {},
|
|
46
|
-
subAgentTrace: [assistantMsg({ cost: 0.5 }), assistantMsg({ cost: 0.4 })],
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
});
|
|
50
|
-
// 0.1 (parent) + 0.5 + 0.4 (sub) = 1.0
|
|
51
|
-
assert.is(sumCosts([parentToolCallMsg]), 1);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
suite('recurses into nested sub-agent traces', () => {
|
|
55
|
-
const innerTrace: ChatMessage[] = [assistantMsg({ cost: 0.25 })];
|
|
56
|
-
const middleTrace: ChatMessage[] = [
|
|
57
|
-
assistantMsg({
|
|
58
|
-
cost: 0.5,
|
|
59
|
-
toolCalls: [{ id: 'inner', name: 'spawn_inner', args: {}, subAgentTrace: innerTrace }],
|
|
60
|
-
}),
|
|
61
|
-
];
|
|
62
|
-
const top = assistantMsg({
|
|
63
|
-
cost: 1,
|
|
64
|
-
toolCalls: [{ id: 'middle', name: 'spawn_middle', args: {}, subAgentTrace: middleTrace }],
|
|
65
|
-
});
|
|
66
|
-
// 1 + 0.5 + 0.25 = 1.75
|
|
67
|
-
assert.is(sumCosts([top]), 1.75);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
suite('handles a mix of tool calls with and without subAgentTrace', () => {
|
|
71
|
-
const cost = sumCosts([
|
|
72
|
-
assistantMsg({
|
|
73
|
-
cost: 1,
|
|
74
|
-
toolCalls: [
|
|
75
|
-
{ id: 'a', name: 'plain_tool', args: {} }, // no trace — skipped
|
|
76
|
-
{
|
|
77
|
-
id: 'b',
|
|
78
|
-
name: 'sub_tool',
|
|
79
|
-
args: {},
|
|
80
|
-
subAgentTrace: [assistantMsg({ cost: 2 })],
|
|
81
|
-
},
|
|
82
|
-
],
|
|
83
|
-
}),
|
|
84
|
-
]);
|
|
85
|
-
assert.is(cost, 3);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
suite('ignores missing cost fields inside an otherwise-populated trace', () => {
|
|
89
|
-
const cost = sumCosts([
|
|
90
|
-
assistantMsg({
|
|
91
|
-
toolCalls: [
|
|
92
|
-
{
|
|
93
|
-
id: 'tc',
|
|
94
|
-
name: 'sub',
|
|
95
|
-
args: {},
|
|
96
|
-
subAgentTrace: [
|
|
97
|
-
userMsg(), // role=user, no cost
|
|
98
|
-
assistantMsg({ content: 'partial', cost: 0.3 }),
|
|
99
|
-
assistantMsg({ content: 'unbilled' }), // no cost field
|
|
100
|
-
],
|
|
101
|
-
},
|
|
102
|
-
],
|
|
103
|
-
}),
|
|
104
|
-
]);
|
|
105
|
-
assert.is(cost, 0.3);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
suite('adds widget-reported externalCostUsd alongside provider cost', () => {
|
|
109
|
-
// A message can carry both: provider LLM `cost` and a widget's `externalCostUsd`.
|
|
110
|
-
const cost = sumCosts([
|
|
111
|
-
assistantMsg({ cost: 0.5 }), // provider only
|
|
112
|
-
assistantMsg({ externalCostUsd: 0.25 }), // external only (e.g. an interaction message)
|
|
113
|
-
assistantMsg({ cost: 0.1, externalCostUsd: 0.4 }), // both
|
|
114
|
-
userMsg(), // neither — ignored
|
|
115
|
-
]);
|
|
116
|
-
// 0.5 + 0.25 + 0.1 + 0.4 = 1.25
|
|
117
|
-
assert.is(cost, 1.25);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
suite('includes externalCostUsd inside a sub-agent trace', () => {
|
|
121
|
-
const cost = sumCosts([
|
|
122
|
-
assistantMsg({
|
|
123
|
-
toolCalls: [
|
|
124
|
-
{
|
|
125
|
-
id: 'tc',
|
|
126
|
-
name: 'sub',
|
|
127
|
-
args: {},
|
|
128
|
-
subAgentTrace: [assistantMsg({ cost: 0.5, externalCostUsd: 0.5 })],
|
|
129
|
-
},
|
|
130
|
-
],
|
|
131
|
-
}),
|
|
132
|
-
]);
|
|
133
|
-
assert.is(cost, 1);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
suite.run();
|
package/src/utils/sum-costs.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
-
import { sumUsage } from './sum-usage';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Sum cost across a message list: each message's provider-reported LLM `cost`
|
|
6
|
-
* plus any `externalCostUsd` a widget reported for its own external service
|
|
7
|
-
* calls, recursing into each tool call's `subAgentTrace` so LLM calls made by
|
|
8
|
-
* sub-agents (potentially on a different provider with different rates)
|
|
9
|
-
* contribute to the total.
|
|
10
|
-
*
|
|
11
|
-
* Returns 0 when no messages carry a cost — providers that don't report usage
|
|
12
|
-
* metadata (e.g. Chrome built-in) are skipped silently.
|
|
13
|
-
*
|
|
14
|
-
* @internal
|
|
15
|
-
*/
|
|
16
|
-
export function sumCosts(messages: readonly ChatMessage[]): number {
|
|
17
|
-
return sumUsage(messages).costUsd;
|
|
18
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import type { ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
3
|
-
import { formatCompactTokenCount, formatCompactTokenCountSafe, sumTokens } from './sum-tokens';
|
|
4
|
-
|
|
5
|
-
const assistantMsg = (overrides: Partial<ChatMessage> = {}): ChatMessage => ({
|
|
6
|
-
role: 'assistant',
|
|
7
|
-
content: '',
|
|
8
|
-
...overrides,
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
const suite = createLogicSuite('sumTokens');
|
|
12
|
-
|
|
13
|
-
suite('returns 0 for an empty message list', () => {
|
|
14
|
-
assert.is(sumTokens([]), 0);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
suite('sums input and output tokens on top-level messages', () => {
|
|
18
|
-
assert.is(
|
|
19
|
-
sumTokens([
|
|
20
|
-
assistantMsg({ inputTokens: 100, outputTokens: 50 }),
|
|
21
|
-
assistantMsg({ inputTokens: 200, outputTokens: 0 }),
|
|
22
|
-
]),
|
|
23
|
-
350,
|
|
24
|
-
);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
suite('includes sub-agent trace tokens attached to a tool call', () => {
|
|
28
|
-
assert.is(
|
|
29
|
-
sumTokens([
|
|
30
|
-
assistantMsg({
|
|
31
|
-
inputTokens: 10,
|
|
32
|
-
outputTokens: 5,
|
|
33
|
-
toolCalls: [
|
|
34
|
-
{
|
|
35
|
-
id: 'tc1',
|
|
36
|
-
name: 'spawn_sub',
|
|
37
|
-
args: {},
|
|
38
|
-
subAgentTrace: [assistantMsg({ inputTokens: 1000, outputTokens: 400 })],
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
}),
|
|
42
|
-
]),
|
|
43
|
-
1415,
|
|
44
|
-
);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const formatSuite = createLogicSuite('formatCompactTokenCount');
|
|
48
|
-
|
|
49
|
-
formatSuite('formats millions with one decimal when needed', () => {
|
|
50
|
-
assert.is(
|
|
51
|
-
formatCompactTokenCount(1_400_000),
|
|
52
|
-
'<span class="value">1.4</span><span class="suffix">M</span>',
|
|
53
|
-
);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
formatSuite('formats whole millions without a decimal', () => {
|
|
57
|
-
assert.is(
|
|
58
|
-
formatCompactTokenCount(2_000_000),
|
|
59
|
-
'<span class="value">2</span><span class="suffix">M</span>',
|
|
60
|
-
);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
formatSuite('formats thousands compactly', () => {
|
|
64
|
-
assert.is(
|
|
65
|
-
formatCompactTokenCount(3_200),
|
|
66
|
-
'<span class="value">3.2</span><span class="suffix">K</span>',
|
|
67
|
-
);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
formatSuite('formats small counts with grouping', () => {
|
|
71
|
-
assert.is(formatCompactTokenCount(842), '842');
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
formatSuite('rolls K suffix up to M when rounding would yield 1000K', () => {
|
|
75
|
-
assert.is(
|
|
76
|
-
formatCompactTokenCount(999_950),
|
|
77
|
-
'<span class="value">1</span><span class="suffix">M</span>',
|
|
78
|
-
);
|
|
79
|
-
assert.is(
|
|
80
|
-
formatCompactTokenCount(999_949),
|
|
81
|
-
'<span class="value">999.9</span><span class="suffix">K</span>',
|
|
82
|
-
);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
formatSuite('safe formatter preserves compact markup', () => {
|
|
86
|
-
assert.is(
|
|
87
|
-
formatCompactTokenCountSafe(1_400_000),
|
|
88
|
-
'<span class="value">1.4</span><span class="suffix">M</span>',
|
|
89
|
-
);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
suite.run();
|
|
93
|
-
formatSuite.run();
|
package/src/utils/sum-tokens.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import type { ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
-
import DOMPurify from 'dompurify';
|
|
3
|
-
import { sumUsage, totalTokens } from './sum-usage';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Total tokens across a message list — the headline figure, with the per-bucket
|
|
7
|
-
* split available from {@link sumUsage} for anything that needs to show *why* a
|
|
8
|
-
* large token count cost little.
|
|
9
|
-
*
|
|
10
|
-
* Identical arithmetic to the old per-message `inputTokens + outputTokens` sum:
|
|
11
|
-
* the three input buckets partition `inputTokens`, so they re-add to it exactly.
|
|
12
|
-
* It does now additionally count usage banked by a compaction, which is the point
|
|
13
|
-
* — that spend used to vanish when the summarised turns left the transcript.
|
|
14
|
-
*
|
|
15
|
-
* Returns 0 when no message carries token metadata.
|
|
16
|
-
*
|
|
17
|
-
* @internal
|
|
18
|
-
*/
|
|
19
|
-
export function sumTokens(messages: readonly ChatMessage[]): number {
|
|
20
|
-
return totalTokens(sumUsage(messages));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const TOKEN_SCALE_DECIMAL_FACTOR = 10;
|
|
24
|
-
const TOKENS_PER_MILLION = 1_000_000;
|
|
25
|
-
const TOKENS_PER_THOUSAND = 1_000;
|
|
26
|
-
const SUFFIX_ROLLOVER_THRESHOLD = 1000;
|
|
27
|
-
|
|
28
|
-
const roundScaled = (scaled: number): number =>
|
|
29
|
-
Math.round(scaled * TOKEN_SCALE_DECIMAL_FACTOR) / TOKEN_SCALE_DECIMAL_FACTOR;
|
|
30
|
-
|
|
31
|
-
const formatScaledTokenMarkup = (rounded: number, suffix: string): string => {
|
|
32
|
-
const value = Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
|
|
33
|
-
return `<span class="value">${value}</span><span class="suffix">${suffix}</span>`;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const scaledSuffixLabel = (tokens: number): string | undefined => {
|
|
37
|
-
if (tokens >= TOKENS_PER_MILLION) return 'M';
|
|
38
|
-
if (tokens >= TOKENS_PER_THOUSAND) {
|
|
39
|
-
const kRounded = roundScaled(tokens / TOKENS_PER_THOUSAND);
|
|
40
|
-
if (kRounded >= SUFFIX_ROLLOVER_THRESHOLD) return 'M';
|
|
41
|
-
return 'K';
|
|
42
|
-
}
|
|
43
|
-
return undefined;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const scaledNumericValue = (tokens: number): number | undefined => {
|
|
47
|
-
const suffix = scaledSuffixLabel(tokens);
|
|
48
|
-
if (!suffix) return undefined;
|
|
49
|
-
if (suffix === 'M') return roundScaled(tokens / TOKENS_PER_MILLION);
|
|
50
|
-
return roundScaled(tokens / TOKENS_PER_THOUSAND);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Compact display for large token counts (e.g. 1_400_000 → value "1.4" + suffix "M").
|
|
55
|
-
* Returns HTML markup so the suffix can be styled separately in the settings modal.
|
|
56
|
-
*
|
|
57
|
-
* @internal
|
|
58
|
-
*/
|
|
59
|
-
export function formatCompactTokenCount(tokens: number): string {
|
|
60
|
-
const suffix = scaledSuffixLabel(tokens);
|
|
61
|
-
if (!suffix) return tokens.toLocaleString();
|
|
62
|
-
return formatScaledTokenMarkup(scaledNumericValue(tokens)!, suffix);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** {@link formatCompactTokenCount} passed through DOMPurify for `:innerHTML` bindings. */
|
|
66
|
-
export function formatCompactTokenCountSafe(tokens: number): string {
|
|
67
|
-
return DOMPurify.sanitize(formatCompactTokenCount(tokens));
|
|
68
|
-
}
|