@genesislcap/ai-assistant 15.12.0 → 15.13.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/dist/ai-assistant.api.json +245 -3
- package/dist/ai-assistant.d.ts +127 -7
- package/dist/chat-driver.cjs +79 -13
- package/dist/chat-driver.cjs.map +2 -2
- package/dist/chat-driver.mjs +76 -12
- package/dist/chat-driver.mjs.map +2 -2
- package/dist/custom-elements.json +99 -6
- package/dist/dts/chat-driver-node.d.ts +2 -2
- package/dist/dts/chat-driver-node.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +45 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts +2 -0
- package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts.map +1 -0
- package/dist/dts/main/main.d.ts +20 -1
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/state/debug-event-log.d.ts.map +1 -1
- package/dist/dts/state/persistence/diagnostics.d.ts +65 -8
- package/dist/dts/state/persistence/diagnostics.d.ts.map +1 -1
- package/dist/dts/state/persistence/index.d.ts +1 -1
- package/dist/dts/state/persistence/index.d.ts.map +1 -1
- package/dist/dts/state/persistence/session-persister.d.ts +4 -3
- package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
- package/dist/dts/utils/sum-usage.d.ts +20 -0
- package/dist/dts/utils/sum-usage.d.ts.map +1 -1
- package/dist/esm/chat-driver-node.js +12 -2
- package/dist/esm/components/chat-driver/chat-driver.js +60 -5
- package/dist/esm/components/chat-driver/chat-driver.turn-usage.test.js +268 -0
- package/dist/esm/main/main.js +53 -28
- package/dist/esm/state/debug-event-log.js +7 -2
- package/dist/esm/state/persistence/diagnostics.js +79 -16
- package/dist/esm/state/persistence/diagnostics.test.js +174 -1
- package/dist/esm/state/persistence/index.js +1 -1
- package/dist/esm/state/persistence/session-persister.js +13 -5
- package/dist/esm/state/persistence/session-persister.test.js +31 -0
- package/dist/esm/utils/sum-usage.js +43 -0
- package/dist/esm/utils/sum-usage.test.js +45 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -17
- package/src/chat-driver-node.ts +12 -2
- package/src/components/chat-driver/chat-driver.ts +107 -6
- package/src/components/chat-driver/chat-driver.turn-usage.test.ts +362 -0
- package/src/main/main.ts +52 -23
- package/src/state/debug-event-log.ts +7 -2
- package/src/state/persistence/diagnostics.test.ts +208 -1
- package/src/state/persistence/diagnostics.ts +117 -15
- package/src/state/persistence/index.ts +1 -1
- package/src/state/persistence/session-persister.test.ts +37 -0
- package/src/state/persistence/session-persister.ts +13 -5
- package/src/utils/sum-usage.test.ts +52 -1
- package/src/utils/sum-usage.ts +45 -0
|
@@ -46,6 +46,49 @@ export function addUsage(a, b) {
|
|
|
46
46
|
outputTokens: a.outputTokens + b.outputTokens,
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* The four buckets plus USD for ONE message — the per-message fields projected into
|
|
51
|
+
* the disjoint, addable shape, or `undefined` when the message reports no usage at
|
|
52
|
+
* all (a user turn, a display-only reasoning/narration split, a provider that
|
|
53
|
+
* reports none).
|
|
54
|
+
*
|
|
55
|
+
* Shallow by design: it does NOT recurse into `toolCalls[].subAgentTrace` and does
|
|
56
|
+
* NOT count `compaction.rolledUpUsage`, so it answers "what did this one request
|
|
57
|
+
* cost" rather than "what did this message and everything under it cost". Use
|
|
58
|
+
* {@link sumUsage} for the latter — passing a whole transcript through this one
|
|
59
|
+
* message at a time would silently drop both.
|
|
60
|
+
*
|
|
61
|
+
* `costUsd` folds in `externalCostUsd` (non-LLM spend a widget reported), matching
|
|
62
|
+
* `sumUsage`. It is `0` when the provider priced nothing, which is NOT a claim that
|
|
63
|
+
* the call was free — see {@link AggregateUsage}; `UsageRow.costUsd` is the
|
|
64
|
+
* shape that keeps unpriced distinguishable, and is the right tool for a ledger.
|
|
65
|
+
*
|
|
66
|
+
* @beta
|
|
67
|
+
*/
|
|
68
|
+
export function messageUsage(m) {
|
|
69
|
+
var _a, _b, _c, _d, _e, _f;
|
|
70
|
+
if (m.cost == null &&
|
|
71
|
+
m.externalCostUsd == null &&
|
|
72
|
+
m.inputTokens == null &&
|
|
73
|
+
m.outputTokens == null &&
|
|
74
|
+
m.cacheReadTokens == null &&
|
|
75
|
+
m.cacheWriteTokens == null) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const cacheReadTokens = (_a = m.cacheReadTokens) !== null && _a !== void 0 ? _a : 0;
|
|
79
|
+
const cacheWriteTokens = (_b = m.cacheWriteTokens) !== null && _b !== void 0 ? _b : 0;
|
|
80
|
+
return {
|
|
81
|
+
costUsd: ((_c = m.cost) !== null && _c !== void 0 ? _c : 0) + ((_d = m.externalCostUsd) !== null && _d !== void 0 ? _d : 0),
|
|
82
|
+
// The uncached REMAINDER, clamped — the same arithmetic `accumulate` below applies,
|
|
83
|
+
// for the same reasons. The two are deliberately separate implementations (that walk
|
|
84
|
+
// mutates one accumulator rather than allocating per message) and must agree;
|
|
85
|
+
// `sum-usage.test.ts` pins them together.
|
|
86
|
+
uncachedInputTokens: Math.max(0, ((_e = m.inputTokens) !== null && _e !== void 0 ? _e : 0) - cacheReadTokens - cacheWriteTokens),
|
|
87
|
+
cacheReadTokens,
|
|
88
|
+
cacheWriteTokens,
|
|
89
|
+
outputTokens: (_f = m.outputTokens) !== null && _f !== void 0 ? _f : 0,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
49
92
|
/**
|
|
50
93
|
* Sum cost and per-bucket token usage across a message list.
|
|
51
94
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { addUsage, emptyUsage, sumUsage, totalTokens } from './sum-usage';
|
|
2
|
+
import { addUsage, emptyUsage, messageUsage, sumUsage, totalTokens } from './sum-usage';
|
|
3
3
|
const suite = createLogicSuite('sumUsage');
|
|
4
4
|
const msg = (over = {}) => (Object.assign({ role: 'assistant', content: '' }, over));
|
|
5
5
|
suite('returns zeroes when no message carries usage', () => {
|
|
@@ -117,4 +117,48 @@ suite('addUsage with an empty operand is the identity', () => {
|
|
|
117
117
|
assert.equal(addUsage(a, emptyUsage()), a);
|
|
118
118
|
assert.equal(addUsage(emptyUsage(), a), a);
|
|
119
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
|
+
});
|
|
120
164
|
suite.run();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/chat-driver-node.ts","../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/flowing-waves-indicator.ts","../src/components/halo-overlay.ts","../src/components/plasma-orb-indicator.ts","../src/components/waves-indicator.ts","../src/components/activity-halo/activity-halo.ts","../src/components/agent-picker/agent-picker.constants.ts","../src/components/agent-picker/agent-picker.styles.ts","../src/components/agent-picker/agent-picker.template.ts","../src/components/agent-picker/agent-picker.ts","../src/components/agent-picker/index.ts","../src/components/ai-driver/ai-driver.ts","../src/components/ai-driver/index.ts","../src/components/chat-bubble/chat-bubble.styles.ts","../src/components/chat-bubble/chat-bubble.template.ts","../src/components/chat-bubble/chat-bubble.ts","../src/components/chat-bubble/index.ts","../src/components/chat-driver/align-event-globals.ts","../src/components/chat-driver/chat-driver.compact.test.ts","../src/components/chat-driver/chat-driver.invocation-scope.test.ts","../src/components/chat-driver/chat-driver.test.ts","../src/components/chat-driver/chat-driver.thinking-policy.test.ts","../src/components/chat-driver/chat-driver.trace-capture.test.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/index.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts","../src/components/chat-interaction-wrapper/index.ts","../src/components/chat-markdown/chat-markdown.ts","../src/components/chat-markdown/index.ts","../src/components/orchestrating-driver/index.ts","../src/components/orchestrating-driver/orchestrating-driver.budget.test.ts","../src/components/orchestrating-driver/orchestrating-driver.pin.test.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/components/settings-modal/index.ts","../src/components/settings-modal/settings-modal.styles.test.ts","../src/components/settings-modal/settings-modal.styles.ts","../src/components/settings-modal/settings-modal.template.test.ts","../src/components/settings-modal/settings-modal.template.ts","../src/config/config.ts","../src/config/define-stateful-agent.test.ts","../src/config/define-stateful-agent.ts","../src/config/fallback-agents.ts","../src/config/index.ts","../src/config/validate-providers.test.ts","../src/config/validate-providers.ts","../src/main/blocked-state.test.ts","../src/main/budget-meter.test.ts","../src/main/cost-session-banking.test.ts","../src/main/index.ts","../src/main/main.styles.test.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/main/popout-interaction-gate.test.ts","../src/provider/ai-provider-switcher.ts","../src/provider/assistant-app-settings.ts","../src/state/ai-assistant-slice.test.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.test.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.test.ts","../src/state/driver-registry.ts","../src/state/interaction-context.test.ts","../src/state/interaction-context.ts","../src/state/session-store.ts","../src/state/persistence/build-timeline-entries.ts","../src/state/persistence/diagnostics-cursors.test.ts","../src/state/persistence/diagnostics-cursors.ts","../src/state/persistence/diagnostics.test.ts","../src/state/persistence/diagnostics.ts","../src/state/persistence/index.ts","../src/state/persistence/persister-registry.ts","../src/state/persistence/session-persistence-provider.test.ts","../src/state/persistence/session-persistence-provider.ts","../src/state/persistence/session-persistence.integration.test.ts","../src/state/persistence/session-persister.test.ts","../src/state/persistence/session-persister.ts","../src/state/persistence/session-snapshot.test.ts","../src/state/persistence/session-snapshot.ts","../src/state/persistence/stateful-restore.e2e.test.ts","../src/styles/ai-colours.ts","../src/styles/settings-section.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/types/interaction-context.ts","../src/utils/animated-panel-toggle.ts","../src/utils/animation-exclusivity.test.ts","../src/utils/animation-exclusivity.ts","../src/utils/banked-usage-baselines.ts","../src/utils/collect-session-models.test.ts","../src/utils/collect-session-models.ts","../src/utils/condense-history.test.ts","../src/utils/condense-history.ts","../src/utils/cost-session-history.test.ts","../src/utils/cost-session-history.ts","../src/utils/derive-cost-session-title.test.ts","../src/utils/derive-cost-session-title.ts","../src/utils/flatten-sub-agent-messages.test.ts","../src/utils/flatten-sub-agent-messages.ts","../src/utils/format-usd.ts","../src/utils/history-transform.test.ts","../src/utils/history-transform.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/message-partition.test.ts","../src/utils/message-partition.ts","../src/utils/resolve-cost-history-config.test.ts","../src/utils/resolve-cost-history-config.ts","../src/utils/resolve-preference-baseline.test.ts","../src/utils/resolve-preference-baseline.ts","../src/utils/strip-agent-handlers.test.ts","../src/utils/strip-agent-handlers.ts","../src/utils/sum-costs.test.ts","../src/utils/sum-costs.ts","../src/utils/sum-tokens.test.ts","../src/utils/sum-tokens.ts","../src/utils/sum-usage.test.ts","../src/utils/sum-usage.ts","../src/utils/tool-fold.ts","../src/utils/usage-rows.test.ts","../src/utils/usage-rows.ts","../src/utils/with-timeout.ts"],"version":"5.9.2"}
|
|
1
|
+
{"root":["../src/chat-driver-node.ts","../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/flowing-waves-indicator.ts","../src/components/halo-overlay.ts","../src/components/plasma-orb-indicator.ts","../src/components/waves-indicator.ts","../src/components/activity-halo/activity-halo.ts","../src/components/agent-picker/agent-picker.constants.ts","../src/components/agent-picker/agent-picker.styles.ts","../src/components/agent-picker/agent-picker.template.ts","../src/components/agent-picker/agent-picker.ts","../src/components/agent-picker/index.ts","../src/components/ai-driver/ai-driver.ts","../src/components/ai-driver/index.ts","../src/components/chat-bubble/chat-bubble.styles.ts","../src/components/chat-bubble/chat-bubble.template.ts","../src/components/chat-bubble/chat-bubble.ts","../src/components/chat-bubble/index.ts","../src/components/chat-driver/align-event-globals.ts","../src/components/chat-driver/chat-driver.compact.test.ts","../src/components/chat-driver/chat-driver.invocation-scope.test.ts","../src/components/chat-driver/chat-driver.test.ts","../src/components/chat-driver/chat-driver.thinking-policy.test.ts","../src/components/chat-driver/chat-driver.trace-capture.test.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/chat-driver.turn-usage.test.ts","../src/components/chat-driver/index.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts","../src/components/chat-interaction-wrapper/index.ts","../src/components/chat-markdown/chat-markdown.ts","../src/components/chat-markdown/index.ts","../src/components/orchestrating-driver/index.ts","../src/components/orchestrating-driver/orchestrating-driver.budget.test.ts","../src/components/orchestrating-driver/orchestrating-driver.pin.test.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/components/settings-modal/index.ts","../src/components/settings-modal/settings-modal.styles.test.ts","../src/components/settings-modal/settings-modal.styles.ts","../src/components/settings-modal/settings-modal.template.test.ts","../src/components/settings-modal/settings-modal.template.ts","../src/config/config.ts","../src/config/define-stateful-agent.test.ts","../src/config/define-stateful-agent.ts","../src/config/fallback-agents.ts","../src/config/index.ts","../src/config/validate-providers.test.ts","../src/config/validate-providers.ts","../src/main/blocked-state.test.ts","../src/main/budget-meter.test.ts","../src/main/cost-session-banking.test.ts","../src/main/index.ts","../src/main/main.styles.test.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/main/popout-interaction-gate.test.ts","../src/provider/ai-provider-switcher.ts","../src/provider/assistant-app-settings.ts","../src/state/ai-assistant-slice.test.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.test.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.test.ts","../src/state/driver-registry.ts","../src/state/interaction-context.test.ts","../src/state/interaction-context.ts","../src/state/session-store.ts","../src/state/persistence/build-timeline-entries.ts","../src/state/persistence/diagnostics-cursors.test.ts","../src/state/persistence/diagnostics-cursors.ts","../src/state/persistence/diagnostics.test.ts","../src/state/persistence/diagnostics.ts","../src/state/persistence/index.ts","../src/state/persistence/persister-registry.ts","../src/state/persistence/session-persistence-provider.test.ts","../src/state/persistence/session-persistence-provider.ts","../src/state/persistence/session-persistence.integration.test.ts","../src/state/persistence/session-persister.test.ts","../src/state/persistence/session-persister.ts","../src/state/persistence/session-snapshot.test.ts","../src/state/persistence/session-snapshot.ts","../src/state/persistence/stateful-restore.e2e.test.ts","../src/styles/ai-colours.ts","../src/styles/settings-section.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/types/interaction-context.ts","../src/utils/animated-panel-toggle.ts","../src/utils/animation-exclusivity.test.ts","../src/utils/animation-exclusivity.ts","../src/utils/banked-usage-baselines.ts","../src/utils/collect-session-models.test.ts","../src/utils/collect-session-models.ts","../src/utils/condense-history.test.ts","../src/utils/condense-history.ts","../src/utils/cost-session-history.test.ts","../src/utils/cost-session-history.ts","../src/utils/derive-cost-session-title.test.ts","../src/utils/derive-cost-session-title.ts","../src/utils/flatten-sub-agent-messages.test.ts","../src/utils/flatten-sub-agent-messages.ts","../src/utils/format-usd.ts","../src/utils/history-transform.test.ts","../src/utils/history-transform.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/message-partition.test.ts","../src/utils/message-partition.ts","../src/utils/resolve-cost-history-config.test.ts","../src/utils/resolve-cost-history-config.ts","../src/utils/resolve-preference-baseline.test.ts","../src/utils/resolve-preference-baseline.ts","../src/utils/strip-agent-handlers.test.ts","../src/utils/strip-agent-handlers.ts","../src/utils/sum-costs.test.ts","../src/utils/sum-costs.ts","../src/utils/sum-tokens.test.ts","../src/utils/sum-tokens.ts","../src/utils/sum-usage.test.ts","../src/utils/sum-usage.ts","../src/utils/tool-fold.ts","../src/utils/usage-rows.test.ts","../src/utils/usage-rows.ts","../src/utils/with-timeout.ts"],"version":"5.9.2"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genesislcap/ai-assistant",
|
|
3
3
|
"description": "Genesis AI Assistant micro-frontend",
|
|
4
|
-
"version": "15.
|
|
4
|
+
"version": "15.13.1",
|
|
5
5
|
"license": "SEE LICENSE IN license.txt",
|
|
6
6
|
"main": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/ai-assistant.d.ts",
|
|
@@ -73,26 +73,26 @@
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@genesislcap/foundation-testing": "15.
|
|
77
|
-
"@genesislcap/genx": "15.
|
|
78
|
-
"@genesislcap/rollup-builder": "15.
|
|
79
|
-
"@genesislcap/ts-builder": "15.
|
|
80
|
-
"@genesislcap/uvu-playwright-builder": "15.
|
|
81
|
-
"@genesislcap/vite-builder": "15.
|
|
82
|
-
"@genesislcap/webpack-builder": "15.
|
|
76
|
+
"@genesislcap/foundation-testing": "15.13.1",
|
|
77
|
+
"@genesislcap/genx": "15.13.1",
|
|
78
|
+
"@genesislcap/rollup-builder": "15.13.1",
|
|
79
|
+
"@genesislcap/ts-builder": "15.13.1",
|
|
80
|
+
"@genesislcap/uvu-playwright-builder": "15.13.1",
|
|
81
|
+
"@genesislcap/vite-builder": "15.13.1",
|
|
82
|
+
"@genesislcap/webpack-builder": "15.13.1",
|
|
83
83
|
"@types/dompurify": "^3.0.5",
|
|
84
84
|
"@types/marked": "^5.0.2",
|
|
85
85
|
"esbuild": "0.25.12"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"@genesislcap/foundation-ai": "15.
|
|
89
|
-
"@genesislcap/foundation-logger": "15.
|
|
90
|
-
"@genesislcap/foundation-notifications": "15.
|
|
91
|
-
"@genesislcap/foundation-redux": "15.
|
|
92
|
-
"@genesislcap/foundation-ui": "15.
|
|
93
|
-
"@genesislcap/foundation-utils": "15.
|
|
94
|
-
"@genesislcap/rapid-design-system": "15.
|
|
95
|
-
"@genesislcap/web-core": "15.
|
|
88
|
+
"@genesislcap/foundation-ai": "15.13.1",
|
|
89
|
+
"@genesislcap/foundation-logger": "15.13.1",
|
|
90
|
+
"@genesislcap/foundation-notifications": "15.13.1",
|
|
91
|
+
"@genesislcap/foundation-redux": "15.13.1",
|
|
92
|
+
"@genesislcap/foundation-ui": "15.13.1",
|
|
93
|
+
"@genesislcap/foundation-utils": "15.13.1",
|
|
94
|
+
"@genesislcap/rapid-design-system": "15.13.1",
|
|
95
|
+
"@genesislcap/web-core": "15.13.1",
|
|
96
96
|
"dompurify": "^3.3.1",
|
|
97
97
|
"marked": "^17.0.3"
|
|
98
98
|
},
|
|
@@ -105,5 +105,5 @@
|
|
|
105
105
|
"access": "public"
|
|
106
106
|
},
|
|
107
107
|
"customElements": "dist/custom-elements.json",
|
|
108
|
-
"gitHead": "
|
|
108
|
+
"gitHead": "4ac0cfb22051b17da81586baf5166c9b605fae10"
|
|
109
109
|
}
|
package/src/chat-driver-node.ts
CHANGED
|
@@ -105,7 +105,13 @@ export type {
|
|
|
105
105
|
// `addUsage`/`emptyUsage` are the composition primitives for a conversation that CONTINUES across
|
|
106
106
|
// rounds — see the guidance on `sumUsage` itself. `totalTokens` exists because the four buckets of
|
|
107
107
|
// an `AggregateUsage` are disjoint and safe to add, which the per-message fields are NOT.
|
|
108
|
-
|
|
108
|
+
//
|
|
109
|
+
// `messageUsage` is the single-message projection of the same arithmetic — what ONE request
|
|
110
|
+
// cost, in the same four-bucket shape. It is what prices a turn snapshot (see
|
|
111
|
+
// `TurnSnapshot.usage`), and the reason a consumer needs it separately is that the response
|
|
112
|
+
// to a *failed* attempt never reaches history, so `sumUsage` over the transcript cannot see
|
|
113
|
+
// that spend at all.
|
|
114
|
+
export { addUsage, emptyUsage, messageUsage, sumUsage, totalTokens } from './utils/sum-usage';
|
|
109
115
|
|
|
110
116
|
// Per-call projection, for the cases an aggregate cannot serve: usage rows for
|
|
111
117
|
// per-project attribution, a cost dashboard, or auditing which turns came back
|
|
@@ -128,5 +134,9 @@ export { clearSession, getMetaEvents } from './state/debug-event-log';
|
|
|
128
134
|
export type { MetaEvent } from './state/debug-event-log';
|
|
129
135
|
export { buildTimelineEntries } from './state/persistence/build-timeline-entries';
|
|
130
136
|
export type { TurnSnapshotLike } from './state/persistence/build-timeline-entries';
|
|
131
|
-
|
|
137
|
+
// `withFreshMetaSnapshot` matters to a headless consumer that stitches a STORED stream: the
|
|
138
|
+
// newest stored `meta-snapshot` is frozen at the last config-signature change, so its context
|
|
139
|
+
// and cost figures are as old as that. Pass a fresh snapshot of your own through this before
|
|
140
|
+
// `assembleDebugLog`, or state in your own output that the totals are historical.
|
|
141
|
+
export { assembleDebugLog, withFreshMetaSnapshot } from './state/persistence/diagnostics';
|
|
132
142
|
export type { DebugLog, DiagnosticEntry } from './state/persistence/diagnostics';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AggregateUsage,
|
|
2
3
|
AIProvider,
|
|
3
4
|
AIProviderRegistry,
|
|
4
5
|
AIProviderType,
|
|
@@ -70,7 +71,7 @@ import {
|
|
|
70
71
|
normalizeForProvider,
|
|
71
72
|
} from '../../utils/history-transform';
|
|
72
73
|
import { logger } from '../../utils/logger';
|
|
73
|
-
import { sumUsage } from '../../utils/sum-usage';
|
|
74
|
+
import { messageUsage, sumUsage } from '../../utils/sum-usage';
|
|
74
75
|
import { TOOL_FOLD_SYMBOL, type ToolFold } from '../../utils/tool-fold';
|
|
75
76
|
import type { AiDriver, AllAgentSummary } from '../ai-driver/ai-driver';
|
|
76
77
|
|
|
@@ -241,6 +242,46 @@ export interface TurnSnapshot {
|
|
|
241
242
|
agentLabel?: string;
|
|
242
243
|
/** Agent-supplied snapshot — machine state/context for stateful agents, undefined otherwise. */
|
|
243
244
|
agentSnapshot?: unknown;
|
|
245
|
+
/**
|
|
246
|
+
* Concrete model that ran this call (e.g. `'claude-sonnet-4-6'`) — the serving model
|
|
247
|
+
* where the provider reports one, else the model the resolved tier was configured
|
|
248
|
+
* with. Undefined when the provider exposes no `getStatus` and the transport stamped
|
|
249
|
+
* nothing.
|
|
250
|
+
*
|
|
251
|
+
* Recorded per call, so an agent whose `provider` selector varies by state (a tier
|
|
252
|
+
* switch between steps of a flow) has each step attributed to the model that actually
|
|
253
|
+
* ran it — without the reader having to join the turn to the message after it, which
|
|
254
|
+
* is impossible for a call that produced no message.
|
|
255
|
+
*/
|
|
256
|
+
model?: string;
|
|
257
|
+
/**
|
|
258
|
+
* Registry slot the provider resolved under for this call — a tier name like
|
|
259
|
+
* `'high'`/`'low'`, or the registry default's name. Kept alongside {@link
|
|
260
|
+
* TurnSnapshot.model} because they answer different questions: the slot is what the
|
|
261
|
+
* agent asked for, the model is what served it, and repointing a slot at a new model
|
|
262
|
+
* mid-session is only visible when both are recorded.
|
|
263
|
+
*/
|
|
264
|
+
providerName?: string;
|
|
265
|
+
/** Vendor behind the resolved slot (`'anthropic'`, `'gemini'`, …), when the provider reports it. */
|
|
266
|
+
provider?: AIProviderType;
|
|
267
|
+
/**
|
|
268
|
+
* What this one LLM call cost — the four disjoint token buckets plus USD, derived
|
|
269
|
+
* from the response's usage by `messageUsage`. Back-filled when the response lands
|
|
270
|
+
* (the rest of the snapshot is captured *before* the call), so it is `undefined`
|
|
271
|
+
* while the call is in flight, on providers that report no usage, and on a call that
|
|
272
|
+
* threw rather than returning — a malformed-call/truncation/402 error carries no usage
|
|
273
|
+
* block, so any tokens the provider billed for it are not recoverable here.
|
|
274
|
+
*
|
|
275
|
+
* A snapshot is one **model call**, not one user turn: every tool-loop iteration and
|
|
276
|
+
* every retried attempt records its own. That makes this the only record of spend on
|
|
277
|
+
* an attempt that produced no message — a blank or refused response is billed and
|
|
278
|
+
* then discarded (see the empty-response retries), so summing the transcript alone
|
|
279
|
+
* under-reports the turn.
|
|
280
|
+
*
|
|
281
|
+
* For a call that DID produce a message, this is the same money as that message's
|
|
282
|
+
* `cost`/token fields, not additional money — never add turn usage to message usage.
|
|
283
|
+
*/
|
|
284
|
+
usage?: AggregateUsage;
|
|
244
285
|
}
|
|
245
286
|
|
|
246
287
|
interface FoldStackFrame {
|
|
@@ -1140,7 +1181,14 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1140
1181
|
if (resolvedName !== this.lastDispatchedProviderName) {
|
|
1141
1182
|
this.lastDispatchedProviderName = resolvedName;
|
|
1142
1183
|
recordMetaEvent(this.sessionKey, 'provider.selected', {
|
|
1184
|
+
// `provider` is the registry SLOT (a tier name like 'high'), kept under that key
|
|
1185
|
+
// for compatibility; `model` and `vendor` are what it resolved to. Recording all
|
|
1186
|
+
// three is the difference between "the agent switched to its high tier" and
|
|
1187
|
+
// knowing which model that actually was — a tier can be repointed mid-session,
|
|
1188
|
+
// and a slot name alone cannot distinguish anthropic from gemini.
|
|
1143
1189
|
provider: resolvedName,
|
|
1190
|
+
model: status.model,
|
|
1191
|
+
vendor: status.provider,
|
|
1144
1192
|
agent: this.activeAgentName,
|
|
1145
1193
|
});
|
|
1146
1194
|
this.dispatchEvent(
|
|
@@ -1304,13 +1352,17 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1304
1352
|
* Push one snapshot to the ring buffer. Called inside `runToolLoop` just
|
|
1305
1353
|
* before each LLM call — that's the latest point where the prompt, tool
|
|
1306
1354
|
* surface, and agent state line up with what the model is about to see.
|
|
1355
|
+
*
|
|
1356
|
+
* Returns the pushed object so the caller can back-fill what only the response
|
|
1357
|
+
* knows (`usage`). Mutating it after the fact is safe whether or not the ring
|
|
1358
|
+
* buffer has since evicted it — an evicted snapshot is simply no longer exported.
|
|
1307
1359
|
*/
|
|
1308
1360
|
private recordTurnSnapshot(
|
|
1309
1361
|
resolvedSystemPrompt: string | undefined,
|
|
1310
1362
|
temperature: number | undefined,
|
|
1311
1363
|
toolChoice: ChatToolChoice | undefined,
|
|
1312
1364
|
tailContext: string | undefined,
|
|
1313
|
-
):
|
|
1365
|
+
): TurnSnapshot {
|
|
1314
1366
|
let agentSnapshot: unknown;
|
|
1315
1367
|
if (this.debugSnapshotter) {
|
|
1316
1368
|
try {
|
|
@@ -1324,7 +1376,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1324
1376
|
}
|
|
1325
1377
|
const turnIndex = String(this.globalTurnIndex);
|
|
1326
1378
|
this.globalTurnIndex += 1;
|
|
1327
|
-
|
|
1379
|
+
const snapshot: TurnSnapshot = {
|
|
1328
1380
|
turnIndex,
|
|
1329
1381
|
timestamp: new Date().toISOString(),
|
|
1330
1382
|
agentName: this.activeAgentName,
|
|
@@ -1335,10 +1387,12 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1335
1387
|
temperature,
|
|
1336
1388
|
toolChoice,
|
|
1337
1389
|
agentSnapshot,
|
|
1338
|
-
}
|
|
1390
|
+
};
|
|
1391
|
+
this.turnSnapshots.push(snapshot);
|
|
1339
1392
|
if (this.turnSnapshots.length > this.maxTurnSnapshots) {
|
|
1340
1393
|
this.turnSnapshots.shift();
|
|
1341
1394
|
}
|
|
1395
|
+
return snapshot;
|
|
1342
1396
|
}
|
|
1343
1397
|
|
|
1344
1398
|
/**
|
|
@@ -2759,7 +2813,12 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
2759
2813
|
// force when no tools are advertised.)
|
|
2760
2814
|
const effectiveToolChoice = resolvedToolChoice ?? (this.isSubAgent ? 'required' : undefined);
|
|
2761
2815
|
|
|
2762
|
-
this.recordTurnSnapshot(
|
|
2816
|
+
const turnSnapshot = this.recordTurnSnapshot(
|
|
2817
|
+
systemPrompt,
|
|
2818
|
+
resolvedTemperature,
|
|
2819
|
+
effectiveToolChoice,
|
|
2820
|
+
tailContext,
|
|
2821
|
+
);
|
|
2763
2822
|
|
|
2764
2823
|
// Capture the pending user input, then clear the slots BEFORE the chat
|
|
2765
2824
|
// call. `sendMessage` already appended the user message to `this.history`,
|
|
@@ -2809,6 +2868,20 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
2809
2868
|
// oxlint-disable-next-line no-await-in-loop
|
|
2810
2869
|
const activeProvider = await this.resolveProviderForTurn(promptCtx);
|
|
2811
2870
|
|
|
2871
|
+
// Attribute the turn to the tier/model it resolved. Stamped HERE, not inside
|
|
2872
|
+
// `recordTurnSnapshot`: the snapshot is taken before this line runs, so reading
|
|
2873
|
+
// `lastResolved*` there yields the PREVIOUS call's model — wrong on precisely the
|
|
2874
|
+
// turn where an agent's per-state `provider` selector switches tier, which is the
|
|
2875
|
+
// turn a reader is looking for. `model` is refined to the serving model once the
|
|
2876
|
+
// response lands (see below); until then — and on a call that throws — it is the
|
|
2877
|
+
// model we ASKED for, which is the only thing knowable at that point.
|
|
2878
|
+
if (this.lastResolvedProviderName !== undefined) {
|
|
2879
|
+
turnSnapshot.providerName = this.lastResolvedProviderName;
|
|
2880
|
+
}
|
|
2881
|
+
if (this.lastResolvedProvider !== undefined)
|
|
2882
|
+
turnSnapshot.provider = this.lastResolvedProvider;
|
|
2883
|
+
if (this.lastResolvedModel !== undefined) turnSnapshot.model = this.lastResolvedModel;
|
|
2884
|
+
|
|
2812
2885
|
let response: ChatMessage;
|
|
2813
2886
|
try {
|
|
2814
2887
|
// oxlint-disable-next-line no-await-in-loop
|
|
@@ -2995,6 +3068,19 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
2995
3068
|
response.providerName = this.lastResolvedProviderName;
|
|
2996
3069
|
}
|
|
2997
3070
|
|
|
3071
|
+
// Back-fill what this call cost onto the snapshot taken just before it, so the
|
|
3072
|
+
// exported debug log prices each model call next to the prompt/tools/state that
|
|
3073
|
+
// produced it (GENC-1480 follow-up). Stamped BEFORE the empty-response branch
|
|
3074
|
+
// below deliberately: a blank or refused response is billed and then thrown away,
|
|
3075
|
+
// so the snapshot is the only place that spend is ever recorded.
|
|
3076
|
+
turnSnapshot.usage = messageUsage(response);
|
|
3077
|
+
// Take the SERVING model over the requested one, now that it is known. `response.model`
|
|
3078
|
+
// was just filled from `lastResolvedModel` if the transport left it unset, so this is
|
|
3079
|
+
// the same rule the message gets — which is the point: a turn and the message it
|
|
3080
|
+
// produced must never disagree about which model ran, including when a server-side
|
|
3081
|
+
// fallback chain answered on a different model than the one we asked for.
|
|
3082
|
+
if (response.model !== undefined) turnSnapshot.model = response.model;
|
|
3083
|
+
|
|
2998
3084
|
const isThinkingStep = response.content && response.toolCalls?.length;
|
|
2999
3085
|
const isEmptyResponse = !response.content?.trim() && !response.toolCalls?.length;
|
|
3000
3086
|
// A pre-output refusal (safety-classifier decline, e.g. Fable 5 `stop_reason: 'refusal'`)
|
|
@@ -3060,8 +3146,23 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
3060
3146
|
// `sumCosts`/`sumTokens` don't double-count and `contextTokens` reads it. Reasoning/narration are
|
|
3061
3147
|
// display-only (usage undefined) and are skipped when building the provider request. `model` /
|
|
3062
3148
|
// `provider` / `providerName` stay on every split message so each is still attributed.
|
|
3149
|
+
//
|
|
3150
|
+
// EVERY usage field has to be cleared here, not just the three the invariant was
|
|
3151
|
+
// originally written against — keep this list in step with the usage fields on
|
|
3152
|
+
// `ChatMessage`. The cache buckets arrived later (GENC-1475) and were left riding
|
|
3153
|
+
// along on the copies, so a response carrying reasoning AND narration counted its
|
|
3154
|
+
// cache read/write volume three times in `sumUsage`/`usageRows` — invisible in the
|
|
3155
|
+
// cost total (which comes from `cost`) but wrong in every bucket display and in the
|
|
3156
|
+
// exported log.
|
|
3063
3157
|
const { reasoning, ...rest } = response;
|
|
3064
|
-
const displayOnly = {
|
|
3158
|
+
const displayOnly = {
|
|
3159
|
+
cost: undefined,
|
|
3160
|
+
externalCostUsd: undefined,
|
|
3161
|
+
inputTokens: undefined,
|
|
3162
|
+
outputTokens: undefined,
|
|
3163
|
+
cacheReadTokens: undefined,
|
|
3164
|
+
cacheWriteTokens: undefined,
|
|
3165
|
+
};
|
|
3065
3166
|
if (reasoning) {
|
|
3066
3167
|
this.appendToHistory({
|
|
3067
3168
|
...rest,
|