basalt-ui 2.4.0 → 2.5.0

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 (49) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/MIGRATING.md +34 -12
  3. package/agent/rules/basalt-state.md +1 -1
  4. package/dist/agent/ai-sdk-transport.d.ts.map +1 -1
  5. package/dist/agent/ai-sdk-transport.js +4 -1
  6. package/dist/agent/index.d.ts +1 -1
  7. package/dist/agent/index.d.ts.map +1 -1
  8. package/dist/agent/run-records.d.ts +12 -3
  9. package/dist/agent/run-records.d.ts.map +1 -1
  10. package/dist/agent/run-records.js +16 -3
  11. package/dist/agent/transport.d.ts +10 -3
  12. package/dist/agent/transport.d.ts.map +1 -1
  13. package/dist/agent/use-agent-thread-runs.d.ts.map +1 -1
  14. package/dist/agent/use-agent-thread-runs.js +23 -7
  15. package/dist/charts/DualPanel.d.ts.map +1 -1
  16. package/dist/charts/DualPanel.js +14 -5
  17. package/dist/charts/MirroredBars.d.ts.map +1 -1
  18. package/dist/charts/MirroredBars.js +6 -4
  19. package/dist/charts/compile.d.ts +4 -0
  20. package/dist/charts/compile.d.ts.map +1 -1
  21. package/dist/charts/compile.js +40 -11
  22. package/dist/charts/panel.d.ts +6 -0
  23. package/dist/charts/panel.d.ts.map +1 -1
  24. package/dist/charts/panel.js +10 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/provider/index.d.ts.map +1 -1
  28. package/dist/provider/index.js +8 -2
  29. package/dist/state/fields.d.ts +10 -15
  30. package/dist/state/fields.d.ts.map +1 -1
  31. package/dist/state/fields.js +27 -1
  32. package/dist/telemetry.d.ts +58 -4
  33. package/dist/telemetry.d.ts.map +1 -1
  34. package/dist/telemetry.js +13 -5
  35. package/llms.txt +2 -2
  36. package/package.json +1 -1
  37. package/src/agent/ai-sdk-transport.ts +5 -2
  38. package/src/agent/index.ts +1 -1
  39. package/src/agent/run-records.ts +27 -3
  40. package/src/agent/transport.ts +11 -5
  41. package/src/agent/use-agent-thread-runs.ts +35 -8
  42. package/src/charts/DualPanel.tsx +13 -4
  43. package/src/charts/MirroredBars.tsx +5 -3
  44. package/src/charts/compile.ts +59 -12
  45. package/src/charts/panel.tsx +16 -1
  46. package/src/index.ts +1 -1
  47. package/src/provider/index.tsx +8 -2
  48. package/src/state/fields.ts +41 -19
  49. package/src/telemetry.ts +70 -7
package/src/telemetry.ts CHANGED
@@ -65,7 +65,61 @@ export type BasaltEvent =
65
65
  | Ok<'chat.turn', ChatAttrs>
66
66
  | Err<'chat.turn', ChatAttrs, Span>
67
67
 
68
- export type Telemetry = (event: BasaltEvent) => void
68
+ /** W3C trace-context request headers a sink contributes to one chat turn's request. */
69
+ export type TraceHeaders = {
70
+ readonly traceparent: string
71
+ readonly tracestate?: string | undefined
72
+ }
73
+
74
+ /**
75
+ * How a traced chat turn ended: its `chat.turn` event fired (`settled`/`failed`), or `stopped` — a
76
+ * `stop()`, an unmount or a newer run taking the thread over — which emits no event. The one
77
+ * lifecycle vocabulary of the run records too.
78
+ */
79
+ export type ChatTurnEnd = 'settled' | 'failed' | 'stopped'
80
+
81
+ /** The turn a sink is asked to trace. */
82
+ type ChatTurnInit = { readonly threadId: string; readonly messageId: string }
83
+
84
+ /**
85
+ * What a sink returns from `startChatTurn`. `settle` runs once, synchronously, right after the
86
+ * turn's `chat.turn` event (`settled`/`failed`), or when the turn is cut short (`stopped`) — the
87
+ * point to end a span opened in `startChatTurn`. It runs even when `headers` is absent or
88
+ * malformed (dropped, the request goes out untraced), so the span never leaks.
89
+ */
90
+ export type ChatTurnTrace = {
91
+ readonly headers?: TraceHeaders | undefined
92
+ readonly settle?: ((end: ChatTurnEnd) => void) | undefined
93
+ }
94
+
95
+ /**
96
+ * The event sink. It may carry one optional hook, `startChatTurn`, called when a new chat turn
97
+ * begins (not on a resume): open your span there and return its `traceparent` (and `tracestate`)
98
+ * and `aiSdkTransport` sends them with that turn's request to the chat endpoint — never anywhere
99
+ * else. A sink without the hook, or no sink, changes nothing on the wire. basalt has no OTel
100
+ * dependency; the hook is where the consumer's SDK does the work.
101
+ *
102
+ * @example
103
+ * export const telemetry: Telemetry = Object.assign(onEvent, {
104
+ * startChatTurn({ threadId }) {
105
+ * const span = tracer.startSpan('chat.turn', {
106
+ * attributes: { 'basalt.chat.thread_id': threadId },
107
+ * })
108
+ * const carrier: Record<string, string> = {}
109
+ * propagation.inject(trace.setSpan(context.active(), span), carrier)
110
+ * return {
111
+ * headers: { traceparent: carrier.traceparent ?? '', tracestate: carrier.tracestate },
112
+ * settle(end) {
113
+ * if (end === 'failed') span.setStatus({ code: SpanStatusCode.ERROR })
114
+ * span.end()
115
+ * },
116
+ * }
117
+ * },
118
+ * })
119
+ */
120
+ export type Telemetry = ((event: BasaltEvent) => void) & {
121
+ readonly startChatTurn?: ((turn: ChatTurnInit) => ChatTurnTrace | undefined) | undefined
122
+ }
69
123
 
70
124
  export const now = (): number => performance.timeOrigin + performance.now()
71
125
 
@@ -73,18 +127,27 @@ export const now = (): number => performance.timeOrigin + performance.now()
73
127
  export const errorName = (error: unknown): string => (error instanceof Error ? error.name : 'Error')
74
128
 
75
129
  /**
76
- * Wrap a sink so it is called synchronously and a throwing sink never breaks a render. No sink, no
77
- * events.
130
+ * The wrapped emitter over a sink read at call time (a ref), so a swapped sink is never stale. It is
131
+ * called synchronously and a throwing sink never breaks a render; no sink, no events. Its
132
+ * `startChatTurn` forwards the sink's hook; the hook's consumer validates the headers.
78
133
  */
79
- export function createEmitter(sink: Telemetry | undefined): Telemetry {
80
- if (sink === undefined) return noop
81
- return (event) => {
134
+ export function createLiveEmitter(read: () => Telemetry | undefined): Telemetry {
135
+ const emit = (event: BasaltEvent): void => {
82
136
  try {
83
- sink(event)
137
+ read()?.(event)
84
138
  } catch {
85
139
  // a throwing sink must never break the app (§ 7 delivery rules)
86
140
  }
87
141
  }
142
+ return Object.assign(emit, {
143
+ startChatTurn(turn: ChatTurnInit): ChatTurnTrace | undefined {
144
+ try {
145
+ return read()?.startChatTurn?.(turn)
146
+ } catch {
147
+ return undefined
148
+ }
149
+ },
150
+ })
88
151
  }
89
152
 
90
153
  function noop(): void {}