@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.
Files changed (50) hide show
  1. package/dist/ai-assistant.api.json +245 -3
  2. package/dist/ai-assistant.d.ts +127 -7
  3. package/dist/chat-driver.cjs +79 -13
  4. package/dist/chat-driver.cjs.map +2 -2
  5. package/dist/chat-driver.mjs +76 -12
  6. package/dist/chat-driver.mjs.map +2 -2
  7. package/dist/custom-elements.json +99 -6
  8. package/dist/dts/chat-driver-node.d.ts +2 -2
  9. package/dist/dts/chat-driver-node.d.ts.map +1 -1
  10. package/dist/dts/components/chat-driver/chat-driver.d.ts +45 -1
  11. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  12. package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts +2 -0
  13. package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts.map +1 -0
  14. package/dist/dts/main/main.d.ts +20 -1
  15. package/dist/dts/main/main.d.ts.map +1 -1
  16. package/dist/dts/state/debug-event-log.d.ts.map +1 -1
  17. package/dist/dts/state/persistence/diagnostics.d.ts +65 -8
  18. package/dist/dts/state/persistence/diagnostics.d.ts.map +1 -1
  19. package/dist/dts/state/persistence/index.d.ts +1 -1
  20. package/dist/dts/state/persistence/index.d.ts.map +1 -1
  21. package/dist/dts/state/persistence/session-persister.d.ts +4 -3
  22. package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
  23. package/dist/dts/utils/sum-usage.d.ts +20 -0
  24. package/dist/dts/utils/sum-usage.d.ts.map +1 -1
  25. package/dist/esm/chat-driver-node.js +12 -2
  26. package/dist/esm/components/chat-driver/chat-driver.js +60 -5
  27. package/dist/esm/components/chat-driver/chat-driver.turn-usage.test.js +268 -0
  28. package/dist/esm/main/main.js +53 -28
  29. package/dist/esm/state/debug-event-log.js +7 -2
  30. package/dist/esm/state/persistence/diagnostics.js +79 -16
  31. package/dist/esm/state/persistence/diagnostics.test.js +174 -1
  32. package/dist/esm/state/persistence/index.js +1 -1
  33. package/dist/esm/state/persistence/session-persister.js +13 -5
  34. package/dist/esm/state/persistence/session-persister.test.js +31 -0
  35. package/dist/esm/utils/sum-usage.js +43 -0
  36. package/dist/esm/utils/sum-usage.test.js +45 -1
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +17 -17
  39. package/src/chat-driver-node.ts +12 -2
  40. package/src/components/chat-driver/chat-driver.ts +107 -6
  41. package/src/components/chat-driver/chat-driver.turn-usage.test.ts +362 -0
  42. package/src/main/main.ts +52 -23
  43. package/src/state/debug-event-log.ts +7 -2
  44. package/src/state/persistence/diagnostics.test.ts +208 -1
  45. package/src/state/persistence/diagnostics.ts +117 -15
  46. package/src/state/persistence/index.ts +1 -1
  47. package/src/state/persistence/session-persister.test.ts +37 -0
  48. package/src/state/persistence/session-persister.ts +13 -5
  49. package/src/utils/sum-usage.test.ts +52 -1
  50. package/src/utils/sum-usage.ts +45 -0
@@ -3583,7 +3583,7 @@
3583
3583
  {
3584
3584
  "kind": "Function",
3585
3585
  "canonicalReference": "@genesislcap/ai-assistant!assembleDebugLog:function(1)",
3586
- "docComment": "/**\n * Reassemble diagnostic entries into the `{ readme, timeline, meta }` debug-log shape (GENC-1351 §5.8). `timeline` = the `message`/`turn`/`event` entries sorted by ISO `timestamp` (kind-rank tie-break). `meta` = the **newest** `meta-snapshot`'s block only — a \"state at export\" photo, matching the single-page log. The older snapshots are intentionally dropped: the per-turn evolution they would show is already in the timeline (`turn.agentSnapshot` + `context.updated` events), so keeping them would just repeat the bulky, near-static `agentSummary`. `readme` = the passed (current) constant. Pure — reused for both the live current-page log and the reassembled lifetime log stitched from the persisted stream, so both come out shape-identical.\n */\n",
3586
+ "docComment": "/**\n * Reassemble diagnostic entries into the `{ readme, sessionUsage, timeline, meta }` debug-log shape (GENC-1351 §5.8). `timeline` = the `message`/`turn`/`event` entries sorted by ISO `timestamp` (kind-rank tie-break). `meta` = the **newest** `meta-snapshot`'s block only — a \"state at export\" photo, matching the single-page log. The older snapshots are intentionally dropped: the per-turn evolution they would show is already in the timeline (`turn.agentSnapshot` + `context.updated` events), so keeping them would just repeat the bulky, near-static `agentSummary`. `readme` = the passed (current) constant. `sessionUsage` = that same newest snapshot's `meta.context.sessionUsage`, lifted to the top level. Pure — reused for both the live current-page log and the reassembled lifetime log stitched from the persisted stream, so both come out shape-identical.\n *\n * NOTE for a caller stitching a STORED stream (a headless consumer harvesting its own lifetime log, say): the newest stored `meta-snapshot` is frozen at the last config-signature change, so `meta` and `sessionUsage` are as old as that unless you pass a fresh snapshot of your own — {@link withFreshMetaSnapshot} does exactly that, and the element's download path goes through it.\n */\n",
3587
3587
  "excerptTokens": [
3588
3588
  {
3589
3589
  "kind": "Content",
@@ -6578,6 +6578,34 @@
6578
6578
  "endIndex": 2
6579
6579
  }
6580
6580
  },
6581
+ {
6582
+ "kind": "PropertySignature",
6583
+ "canonicalReference": "@genesislcap/ai-assistant!DebugLog#sessionUsage:member",
6584
+ "docComment": "/**\n * Session usage — the four token buckets plus USD cost — lifted out of the newest `meta-snapshot`'s `meta.context.sessionUsage` so the headline spend figures sit at the top of an exported log rather than buried under the (large) `meta` block. The same object, not a second derivation.\n *\n * `undefined` when the stream carries no `meta-snapshot`, or one written before the field existed. Never re-derived from the timeline: the per-request costs the transports stamped are authoritative, and a ring-buffered timeline can have lost entries the total still legitimately counts.\n *\n * **As current as the snapshot it came from, which is not automatically \"now\".** The forward-capture delta only re-appends a `meta-snapshot` when the near-static config signature changes, so the newest STORED snapshot of a session whose config never changed is its first — with the totals frozen there. A caller reassembling a stored stream on its own therefore gets stale figures unless it appends an export-time snapshot first: see {@link withFreshMetaSnapshot}, which is what the assistant's own download path does. `meta.timestamp` against the newest timeline entry tells you which case you are holding.\n */\n",
6585
+ "excerptTokens": [
6586
+ {
6587
+ "kind": "Content",
6588
+ "text": "sessionUsage?: "
6589
+ },
6590
+ {
6591
+ "kind": "Reference",
6592
+ "text": "AggregateUsage",
6593
+ "canonicalReference": "@genesislcap/foundation-ai!AggregateUsage:interface"
6594
+ },
6595
+ {
6596
+ "kind": "Content",
6597
+ "text": ";"
6598
+ }
6599
+ ],
6600
+ "isReadonly": false,
6601
+ "isOptional": true,
6602
+ "releaseTag": "Public",
6603
+ "name": "sessionUsage",
6604
+ "propertyTypeTokenRange": {
6605
+ "startIndex": 1,
6606
+ "endIndex": 2
6607
+ }
6608
+ },
6581
6609
  {
6582
6610
  "kind": "PropertySignature",
6583
6611
  "canonicalReference": "@genesislcap/ai-assistant!DebugLog#timeline:member",
@@ -6747,7 +6775,7 @@
6747
6775
  {
6748
6776
  "kind": "Interface",
6749
6777
  "canonicalReference": "@genesislcap/ai-assistant!DiagnosticEntry:interface",
6750
- "docComment": "/**\n * One entry in the diagnostics stream — the debug-log timeline-entry union (`message` / `turn` / `event`, as `getDebugLog()` builds) plus a point-in-time **`meta-snapshot`** carrying the export-time `meta` block (agent summary, context, active debug snapshot, …). Kept permissive: a provider stores and returns these opaquely, and reassembly reads only `kind`, `timestamp` (for ordering), and `meta` (on a snapshot).\n *\n * @public\n */\n",
6778
+ "docComment": "/**\n * One entry in the diagnostics stream — the debug-log timeline-entry union (`message` / `turn` / `event`, as `getDebugLog()` builds) plus a point-in-time **`meta-snapshot`** carrying the export-time `meta` block (agent summary, context, active debug snapshot, …). Kept permissive: a provider stores and returns these opaquely, and reassembly reads only `kind`, `timestamp` (for ordering), `usage` (on a turn) and `meta` (on a snapshot).\n *\n * @public\n */\n",
6751
6779
  "excerptTokens": [
6752
6780
  {
6753
6781
  "kind": "Content",
@@ -6910,6 +6938,34 @@
6910
6938
  "startIndex": 1,
6911
6939
  "endIndex": 2
6912
6940
  }
6941
+ },
6942
+ {
6943
+ "kind": "PropertySignature",
6944
+ "canonicalReference": "@genesislcap/ai-assistant!DiagnosticEntry#usage:member",
6945
+ "docComment": "/**\n * Present on a `turn` entry once its model call has returned and reported usage — the four token buckets plus USD for that one call (`TurnSnapshot.usage`).\n *\n * Read by the forward-capture delta as well as by readers: the turn entry is created *before* the call, so a flush that lands mid-call persists it without usage, and the delta re-emits it once (see `collectDiagnosticsDelta`) so the priced copy reaches the stream. {@link assembleDebugLog} then collapses the pair.\n */\n",
6946
+ "excerptTokens": [
6947
+ {
6948
+ "kind": "Content",
6949
+ "text": "usage?: "
6950
+ },
6951
+ {
6952
+ "kind": "Reference",
6953
+ "text": "AggregateUsage",
6954
+ "canonicalReference": "@genesislcap/foundation-ai!AggregateUsage:interface"
6955
+ },
6956
+ {
6957
+ "kind": "Content",
6958
+ "text": ";"
6959
+ }
6960
+ ],
6961
+ "isReadonly": false,
6962
+ "isOptional": true,
6963
+ "releaseTag": "Public",
6964
+ "name": "usage",
6965
+ "propertyTypeTokenRange": {
6966
+ "startIndex": 1,
6967
+ "endIndex": 2
6968
+ }
6913
6969
  }
6914
6970
  ],
6915
6971
  "extendsTokenRanges": []
@@ -8858,7 +8914,7 @@
8858
8914
  {
8859
8915
  "kind": "Method",
8860
8916
  "canonicalReference": "@genesislcap/ai-assistant!FoundationAiAssistant#getDebugLog:member(1)",
8861
- "docComment": "/**\n * The live current-page debug log (`{ readme, timeline, meta }`).\n *\n * @public\n */\n",
8917
+ "docComment": "/**\n * The live current-page debug log (`{ readme, sessionUsage, timeline, meta }`).\n *\n * @public\n */\n",
8862
8918
  "excerptTokens": [
8863
8919
  {
8864
8920
  "kind": "Content",
@@ -19101,6 +19157,88 @@
19101
19157
  "endIndex": 2
19102
19158
  }
19103
19159
  },
19160
+ {
19161
+ "kind": "PropertySignature",
19162
+ "canonicalReference": "@genesislcap/ai-assistant!TurnSnapshot#model:member",
19163
+ "docComment": "/**\n * Concrete model that ran this call (e.g. `'claude-sonnet-4-6'`) — the serving model where the provider reports one, else the model the resolved tier was configured with. Undefined when the provider exposes no `getStatus` and the transport stamped nothing.\n *\n * Recorded per call, so an agent whose `provider` selector varies by state (a tier switch between steps of a flow) has each step attributed to the model that actually ran it — without the reader having to join the turn to the message after it, which is impossible for a call that produced no message.\n */\n",
19164
+ "excerptTokens": [
19165
+ {
19166
+ "kind": "Content",
19167
+ "text": "model?: "
19168
+ },
19169
+ {
19170
+ "kind": "Content",
19171
+ "text": "string"
19172
+ },
19173
+ {
19174
+ "kind": "Content",
19175
+ "text": ";"
19176
+ }
19177
+ ],
19178
+ "isReadonly": false,
19179
+ "isOptional": true,
19180
+ "releaseTag": "Beta",
19181
+ "name": "model",
19182
+ "propertyTypeTokenRange": {
19183
+ "startIndex": 1,
19184
+ "endIndex": 2
19185
+ }
19186
+ },
19187
+ {
19188
+ "kind": "PropertySignature",
19189
+ "canonicalReference": "@genesislcap/ai-assistant!TurnSnapshot#provider:member",
19190
+ "docComment": "/**\n * Vendor behind the resolved slot (`'anthropic'`, `'gemini'`, …), when the provider reports it.\n */\n",
19191
+ "excerptTokens": [
19192
+ {
19193
+ "kind": "Content",
19194
+ "text": "provider?: "
19195
+ },
19196
+ {
19197
+ "kind": "Reference",
19198
+ "text": "AIProviderType",
19199
+ "canonicalReference": "@genesislcap/foundation-ai!AIProviderType:type"
19200
+ },
19201
+ {
19202
+ "kind": "Content",
19203
+ "text": ";"
19204
+ }
19205
+ ],
19206
+ "isReadonly": false,
19207
+ "isOptional": true,
19208
+ "releaseTag": "Beta",
19209
+ "name": "provider",
19210
+ "propertyTypeTokenRange": {
19211
+ "startIndex": 1,
19212
+ "endIndex": 2
19213
+ }
19214
+ },
19215
+ {
19216
+ "kind": "PropertySignature",
19217
+ "canonicalReference": "@genesislcap/ai-assistant!TurnSnapshot#providerName:member",
19218
+ "docComment": "/**\n * Registry slot the provider resolved under for this call — a tier name like `'high'`/`'low'`, or the registry default's name. Kept alongside {@link TurnSnapshot.model} because they answer different questions: the slot is what the agent asked for, the model is what served it, and repointing a slot at a new model mid-session is only visible when both are recorded.\n */\n",
19219
+ "excerptTokens": [
19220
+ {
19221
+ "kind": "Content",
19222
+ "text": "providerName?: "
19223
+ },
19224
+ {
19225
+ "kind": "Content",
19226
+ "text": "string"
19227
+ },
19228
+ {
19229
+ "kind": "Content",
19230
+ "text": ";"
19231
+ }
19232
+ ],
19233
+ "isReadonly": false,
19234
+ "isOptional": true,
19235
+ "releaseTag": "Beta",
19236
+ "name": "providerName",
19237
+ "propertyTypeTokenRange": {
19238
+ "startIndex": 1,
19239
+ "endIndex": 2
19240
+ }
19241
+ },
19104
19242
  {
19105
19243
  "kind": "PropertySignature",
19106
19244
  "canonicalReference": "@genesislcap/ai-assistant!TurnSnapshot#systemPrompt:member",
@@ -19290,6 +19428,34 @@
19290
19428
  "startIndex": 1,
19291
19429
  "endIndex": 2
19292
19430
  }
19431
+ },
19432
+ {
19433
+ "kind": "PropertySignature",
19434
+ "canonicalReference": "@genesislcap/ai-assistant!TurnSnapshot#usage:member",
19435
+ "docComment": "/**\n * What this one LLM call cost — the four disjoint token buckets plus USD, derived from the response's usage by `messageUsage`. Back-filled when the response lands (the rest of the snapshot is captured *before* the call), so it is `undefined` while the call is in flight, on providers that report no usage, and on a call that threw rather than returning — a malformed-call/truncation/402 error carries no usage block, so any tokens the provider billed for it are not recoverable here.\n *\n * A snapshot is one **model call**, not one user turn: every tool-loop iteration and every retried attempt records its own. That makes this the only record of spend on an attempt that produced no message — a blank or refused response is billed and then discarded (see the empty-response retries), so summing the transcript alone under-reports the turn.\n *\n * For a call that DID produce a message, this is the same money as that message's `cost`/token fields, not additional money — never add turn usage to message usage.\n */\n",
19436
+ "excerptTokens": [
19437
+ {
19438
+ "kind": "Content",
19439
+ "text": "usage?: "
19440
+ },
19441
+ {
19442
+ "kind": "Reference",
19443
+ "text": "AggregateUsage",
19444
+ "canonicalReference": "@genesislcap/foundation-ai!AggregateUsage:interface"
19445
+ },
19446
+ {
19447
+ "kind": "Content",
19448
+ "text": ";"
19449
+ }
19450
+ ],
19451
+ "isReadonly": false,
19452
+ "isOptional": true,
19453
+ "releaseTag": "Beta",
19454
+ "name": "usage",
19455
+ "propertyTypeTokenRange": {
19456
+ "startIndex": 1,
19457
+ "endIndex": 2
19458
+ }
19293
19459
  }
19294
19460
  ],
19295
19461
  "extendsTokenRanges": []
@@ -19892,6 +20058,82 @@
19892
20058
  "endIndex": 2
19893
20059
  }
19894
20060
  ]
20061
+ },
20062
+ {
20063
+ "kind": "Function",
20064
+ "canonicalReference": "@genesislcap/ai-assistant!withFreshMetaSnapshot:function(1)",
20065
+ "docComment": "/**\n * A stored diagnostics stream with its `meta-snapshot`s replaced by `fresh` — what to pass {@link assembleDebugLog} when reassembling a persisted stream for export.\n *\n * The stored snapshots are dropped rather than out-timestamped. `assembleDebugLog` keeps whichever claims the later time, and a stream written on another machine can carry a skewed clock; `fresh` is built from live state and so is the more current by construction. Only one snapshot ever survives reassembly, so the count is unchanged — though a snapshot taken while the driver is unwired (mid-popout) can leave `activeFoldStack`/`activeDebugSnapshot` unset where a stored one had them. The figures this exists for — the context block and `sessionUsage` — read the session store and element props, which survive an unwire.\n *\n * Pure, and separate from the element for that reason: the swap it performs is the fix for a silent bug (a lifetime log reporting near-zero spend against a transcript full of priced messages), so it needs to be testable without mounting anything.\n */\n",
20066
+ "excerptTokens": [
20067
+ {
20068
+ "kind": "Content",
20069
+ "text": "export declare function withFreshMetaSnapshot(stored: "
20070
+ },
20071
+ {
20072
+ "kind": "Content",
20073
+ "text": "readonly "
20074
+ },
20075
+ {
20076
+ "kind": "Reference",
20077
+ "text": "DiagnosticEntry",
20078
+ "canonicalReference": "@genesislcap/ai-assistant!DiagnosticEntry:interface"
20079
+ },
20080
+ {
20081
+ "kind": "Content",
20082
+ "text": "[]"
20083
+ },
20084
+ {
20085
+ "kind": "Content",
20086
+ "text": ", fresh: "
20087
+ },
20088
+ {
20089
+ "kind": "Reference",
20090
+ "text": "DiagnosticEntry",
20091
+ "canonicalReference": "@genesislcap/ai-assistant!DiagnosticEntry:interface"
20092
+ },
20093
+ {
20094
+ "kind": "Content",
20095
+ "text": "): "
20096
+ },
20097
+ {
20098
+ "kind": "Reference",
20099
+ "text": "DiagnosticEntry",
20100
+ "canonicalReference": "@genesislcap/ai-assistant!DiagnosticEntry:interface"
20101
+ },
20102
+ {
20103
+ "kind": "Content",
20104
+ "text": "[]"
20105
+ },
20106
+ {
20107
+ "kind": "Content",
20108
+ "text": ";"
20109
+ }
20110
+ ],
20111
+ "fileUrlPath": "src/state/persistence/diagnostics.ts",
20112
+ "returnTypeTokenRange": {
20113
+ "startIndex": 7,
20114
+ "endIndex": 9
20115
+ },
20116
+ "releaseTag": "Public",
20117
+ "overloadIndex": 1,
20118
+ "parameters": [
20119
+ {
20120
+ "parameterName": "stored",
20121
+ "parameterTypeTokenRange": {
20122
+ "startIndex": 1,
20123
+ "endIndex": 4
20124
+ },
20125
+ "isOptional": false
20126
+ },
20127
+ {
20128
+ "parameterName": "fresh",
20129
+ "parameterTypeTokenRange": {
20130
+ "startIndex": 5,
20131
+ "endIndex": 6
20132
+ },
20133
+ "isOptional": false
20134
+ }
20135
+ ],
20136
+ "name": "withFreshMetaSnapshot"
19895
20137
  }
19896
20138
  ]
19897
20139
  }
@@ -766,16 +766,23 @@ export declare const ANIMATION_DEFS: {
766
766
  };
767
767
 
768
768
  /**
769
- * Reassemble diagnostic entries into the `{ readme, timeline, meta }` debug-log
770
- * shape (GENC-1351 §5.8). `timeline` = the `message`/`turn`/`event` entries sorted
769
+ * Reassemble diagnostic entries into the `{ readme, sessionUsage, timeline, meta }`
770
+ * debug-log shape (GENC-1351 §5.8). `timeline` = the `message`/`turn`/`event` entries sorted
771
771
  * by ISO `timestamp` (kind-rank tie-break). `meta` = the **newest** `meta-snapshot`'s
772
772
  * block only — a "state at export" photo, matching the single-page log. The older
773
773
  * snapshots are intentionally dropped: the per-turn evolution they would show is
774
774
  * already in the timeline (`turn.agentSnapshot` + `context.updated` events), so
775
775
  * keeping them would just repeat the bulky, near-static `agentSummary`. `readme` =
776
- * the passed (current) constant. Pure reused for both the live current-page log
777
- * and the reassembled lifetime log stitched from the persisted stream, so both
778
- * come out shape-identical.
776
+ * the passed (current) constant. `sessionUsage` = that same newest snapshot's
777
+ * `meta.context.sessionUsage`, lifted to the top level. Pure reused for both the
778
+ * live current-page log and the reassembled lifetime log stitched from the persisted
779
+ * stream, so both come out shape-identical.
780
+ *
781
+ * NOTE for a caller stitching a STORED stream (a headless consumer harvesting its own
782
+ * lifetime log, say): the newest stored `meta-snapshot` is frozen at the last
783
+ * config-signature change, so `meta` and `sessionUsage` are as old as that unless you
784
+ * pass a fresh snapshot of your own — {@link withFreshMetaSnapshot} does exactly that,
785
+ * and the element's download path goes through it.
779
786
  */
780
787
  export declare function assembleDebugLog(entries: DiagnosticEntry[], readme: readonly string[]): DebugLog;
781
788
 
@@ -1754,6 +1761,10 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
1754
1761
  * Push one snapshot to the ring buffer. Called inside `runToolLoop` just
1755
1762
  * before each LLM call — that's the latest point where the prompt, tool
1756
1763
  * surface, and agent state line up with what the model is about to see.
1764
+ *
1765
+ * Returns the pushed object so the caller can back-fill what only the response
1766
+ * knows (`usage`). Mutating it after the fact is safe whether or not the ring
1767
+ * buffer has since evicted it — an evicted snapshot is simply no longer exported.
1757
1768
  */
1758
1769
  private recordTurnSnapshot;
1759
1770
  /**
@@ -2150,6 +2161,27 @@ export declare function createToolFold(config: {
2150
2161
  /** The reassembled debug log — the exact shape `getDebugLog()` returns. @public */
2151
2162
  export declare interface DebugLog {
2152
2163
  readme: readonly string[];
2164
+ /**
2165
+ * Session usage — the four token buckets plus USD cost — lifted out of the newest
2166
+ * `meta-snapshot`'s `meta.context.sessionUsage` so the headline spend figures sit at
2167
+ * the top of an exported log rather than buried under the (large) `meta` block. The
2168
+ * same object, not a second derivation.
2169
+ *
2170
+ * `undefined` when the stream carries no `meta-snapshot`, or one written before the
2171
+ * field existed. Never re-derived from the timeline: the per-request costs the
2172
+ * transports stamped are authoritative, and a ring-buffered timeline can have lost
2173
+ * entries the total still legitimately counts.
2174
+ *
2175
+ * **As current as the snapshot it came from, which is not automatically "now".** The
2176
+ * forward-capture delta only re-appends a `meta-snapshot` when the near-static config
2177
+ * signature changes, so the newest STORED snapshot of a session whose config never
2178
+ * changed is its first — with the totals frozen there. A caller reassembling a stored
2179
+ * stream on its own therefore gets stale figures unless it appends an export-time
2180
+ * snapshot first: see {@link withFreshMetaSnapshot}, which is what the assistant's own
2181
+ * download path does. `meta.timestamp` against the newest timeline entry tells you
2182
+ * which case you are holding.
2183
+ */
2184
+ sessionUsage?: AggregateUsage;
2153
2185
  timeline: DiagnosticEntry[];
2154
2186
  meta: unknown;
2155
2187
  }
@@ -2213,7 +2245,7 @@ export declare function defineStatefulAgent<S>(opts: StatefulAgentInit<S>): Agen
2213
2245
  * **`meta-snapshot`** carrying the export-time `meta` block (agent summary,
2214
2246
  * context, active debug snapshot, …). Kept permissive: a provider stores and
2215
2247
  * returns these opaquely, and reassembly reads only `kind`, `timestamp` (for
2216
- * ordering), and `meta` (on a snapshot).
2248
+ * ordering), `usage` (on a turn) and `meta` (on a snapshot).
2217
2249
  *
2218
2250
  * @public
2219
2251
  */
@@ -2223,6 +2255,16 @@ export declare interface DiagnosticEntry {
2223
2255
  timestamp?: string;
2224
2256
  /** Present on `meta-snapshot` entries: the export-time `meta` block. */
2225
2257
  meta?: unknown;
2258
+ /**
2259
+ * Present on a `turn` entry once its model call has returned and reported usage —
2260
+ * the four token buckets plus USD for that one call (`TurnSnapshot.usage`).
2261
+ *
2262
+ * Read by the forward-capture delta as well as by readers: the turn entry is
2263
+ * created *before* the call, so a flush that lands mid-call persists it without
2264
+ * usage, and the delta re-emits it once (see `collectDiagnosticsDelta`) so the
2265
+ * priced copy reaches the stream. {@link assembleDebugLog} then collapses the pair.
2266
+ */
2267
+ usage?: AggregateUsage;
2226
2268
  /**
2227
2269
  * Present on `meta-snapshot` entries: a stable signature of the near-static config
2228
2270
  * (excludes volatile timestamp/context/debug-snapshot), used by the forward-capture
@@ -3732,7 +3774,7 @@ export declare class FoundationAiAssistant extends GenesisElement {
3732
3774
  toggleShowNarration(): void;
3733
3775
  toggleShowAgentSwitchIndicator(): void;
3734
3776
  setEnabledAnimations(animations: AiAssistantAnimation[]): void;
3735
- /** The live current-page debug log (`{ readme, timeline, meta }`). @public */
3777
+ /** The live current-page debug log (`{ readme, sessionUsage, timeline, meta }`). @public */
3736
3778
  getDebugLog(): DebugLog;
3737
3779
  /**
3738
3780
  * Build the current page's diagnostic entries (GENC-1351 §5.8) — the debug-log
@@ -3743,6 +3785,17 @@ export declare class FoundationAiAssistant extends GenesisElement {
3743
3785
  * been appended to the persisted stream.
3744
3786
  */
3745
3787
  private buildDiagnosticEntries;
3788
+ /**
3789
+ * The single `meta-snapshot` entry for right now — the export-time `meta` block
3790
+ * (agent summary, active prompt/state, context + cost) plus the `dedupSignature`
3791
+ * the forward-capture delta keys on.
3792
+ *
3793
+ * Separate from `buildDiagnosticEntries` because the download path needs a
3794
+ * FRESH one on its own: the persisted stream only re-appends this block when the
3795
+ * near-static config changes, so the newest stored snapshot's volatile half — the
3796
+ * `context` figures especially — is typically frozen at the session's first flush.
3797
+ */
3798
+ private buildMetaSnapshot;
3746
3799
  downloadDebugLog(): Promise<void>;
3747
3800
  /**
3748
3801
  * The log to download (GENC-1351 §5.8). When the provider persists diagnostics,
@@ -3752,6 +3805,14 @@ export declare class FoundationAiAssistant extends GenesisElement {
3752
3805
  * chat" — diagnostics are a forensic stream keyed on provider capability, not on
3753
3806
  * whether the *chat* is remembered (like preferences). Falls back to the live
3754
3807
  * current-page log when diagnostics isn't available or the fetch fails.
3808
+ *
3809
+ * The stored `meta-snapshot`s are replaced by a fresh one (`withFreshMetaSnapshot`)
3810
+ * before reassembly. The persisted stream only re-appends that block when the
3811
+ * near-static config signature changes (see `collectDiagnosticsDelta`), so on a session
3812
+ * whose config never changes the newest STORED snapshot is the first one — its
3813
+ * `context` half (session cost/usage, context tokens, live agent state) frozen seconds
3814
+ * into the session, which is how a lifetime log came out reporting near-zero spend
3815
+ * against a transcript full of priced messages.
3755
3816
  */
3756
3817
  private buildDownloadLog;
3757
3818
  /**
@@ -5127,6 +5188,46 @@ export declare interface TurnSnapshot {
5127
5188
  agentLabel?: string;
5128
5189
  /** Agent-supplied snapshot — machine state/context for stateful agents, undefined otherwise. */
5129
5190
  agentSnapshot?: unknown;
5191
+ /**
5192
+ * Concrete model that ran this call (e.g. `'claude-sonnet-4-6'`) — the serving model
5193
+ * where the provider reports one, else the model the resolved tier was configured
5194
+ * with. Undefined when the provider exposes no `getStatus` and the transport stamped
5195
+ * nothing.
5196
+ *
5197
+ * Recorded per call, so an agent whose `provider` selector varies by state (a tier
5198
+ * switch between steps of a flow) has each step attributed to the model that actually
5199
+ * ran it — without the reader having to join the turn to the message after it, which
5200
+ * is impossible for a call that produced no message.
5201
+ */
5202
+ model?: string;
5203
+ /**
5204
+ * Registry slot the provider resolved under for this call — a tier name like
5205
+ * `'high'`/`'low'`, or the registry default's name. Kept alongside {@link
5206
+ * TurnSnapshot.model} because they answer different questions: the slot is what the
5207
+ * agent asked for, the model is what served it, and repointing a slot at a new model
5208
+ * mid-session is only visible when both are recorded.
5209
+ */
5210
+ providerName?: string;
5211
+ /** Vendor behind the resolved slot (`'anthropic'`, `'gemini'`, …), when the provider reports it. */
5212
+ provider?: AIProviderType;
5213
+ /**
5214
+ * What this one LLM call cost — the four disjoint token buckets plus USD, derived
5215
+ * from the response's usage by `messageUsage`. Back-filled when the response lands
5216
+ * (the rest of the snapshot is captured *before* the call), so it is `undefined`
5217
+ * while the call is in flight, on providers that report no usage, and on a call that
5218
+ * threw rather than returning — a malformed-call/truncation/402 error carries no usage
5219
+ * block, so any tokens the provider billed for it are not recoverable here.
5220
+ *
5221
+ * A snapshot is one **model call**, not one user turn: every tool-loop iteration and
5222
+ * every retried attempt records its own. That makes this the only record of spend on
5223
+ * an attempt that produced no message — a blank or refused response is billed and
5224
+ * then discarded (see the empty-response retries), so summing the transcript alone
5225
+ * under-reports the turn.
5226
+ *
5227
+ * For a call that DID produce a message, this is the same money as that message's
5228
+ * `cost`/token fields, not additional money — never add turn usage to message usage.
5229
+ */
5230
+ usage?: AggregateUsage;
5130
5231
  }
5131
5232
 
5132
5233
  /**
@@ -5222,4 +5323,23 @@ export declare class WebStorageSessionProvider implements SessionPersistenceProv
5222
5323
  savePreferences(sessionKey: string, preferences: SessionPreferences): Promise<void>;
5223
5324
  }
5224
5325
 
5326
+ /**
5327
+ * A stored diagnostics stream with its `meta-snapshot`s replaced by `fresh` — what to
5328
+ * pass {@link assembleDebugLog} when reassembling a persisted stream for export.
5329
+ *
5330
+ * The stored snapshots are dropped rather than out-timestamped. `assembleDebugLog` keeps
5331
+ * whichever claims the later time, and a stream written on another machine can carry a
5332
+ * skewed clock; `fresh` is built from live state and so is the more current by
5333
+ * construction. Only one snapshot ever survives reassembly, so the count is unchanged —
5334
+ * though a snapshot taken while the driver is unwired (mid-popout) can leave
5335
+ * `activeFoldStack`/`activeDebugSnapshot` unset where a stored one had them. The figures
5336
+ * this exists for — the context block and `sessionUsage` — read the session store and
5337
+ * element props, which survive an unwire.
5338
+ *
5339
+ * Pure, and separate from the element for that reason: the swap it performs is the fix
5340
+ * for a silent bug (a lifetime log reporting near-zero spend against a transcript full of
5341
+ * priced messages), so it needs to be testable without mounting anything.
5342
+ */
5343
+ export declare function withFreshMetaSnapshot(stored: readonly DiagnosticEntry[], fresh: DiagnosticEntry): DiagnosticEntry[];
5344
+
5225
5345
  export { }