@assistant-ui/core 0.2.5 → 0.2.7
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/index.d.ts +4 -2
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/duplicate-detection.d.ts +5 -0
- package/dist/internal/duplicate-detection.d.ts.map +1 -0
- package/dist/internal/duplicate-detection.js +11 -0
- package/dist/internal/duplicate-detection.js.map +1 -0
- package/dist/react/AssistantProvider.d.ts.map +1 -1
- package/dist/react/AssistantProvider.js.map +1 -1
- package/dist/react/index.d.ts +3 -2
- package/dist/react/index.js +2 -2
- package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.d.ts +25 -21
- package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.js +6 -7
- package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
- package/dist/react/primitives/message/MessageParts.d.ts +2 -1
- package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js +9 -4
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/providers/TextMessagePartProvider.d.ts.map +1 -1
- package/dist/react/providers/TextMessagePartProvider.js +3 -0
- package/dist/react/providers/TextMessagePartProvider.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +3 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +3 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/external-message-converter.d.ts +1 -1
- package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
- package/dist/react/runtimes/external-message-converter.js +7 -3
- package/dist/react/runtimes/external-message-converter.js.map +1 -1
- package/dist/react/types/MessagePartComponentTypes.d.ts +8 -0
- package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
- package/dist/react/utils/groupParts.d.ts +40 -12
- package/dist/react/utils/groupParts.d.ts.map +1 -1
- package/dist/react/utils/groupParts.js +51 -9
- package/dist/react/utils/groupParts.js.map +1 -1
- package/dist/runtime/api/attachment-runtime.d.ts.map +1 -1
- package/dist/runtime/api/attachment-runtime.js.map +1 -1
- package/dist/runtime/api/message-part-runtime.d.ts +8 -0
- package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-part-runtime.js +13 -0
- package/dist/runtime/api/message-part-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +2 -1
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +15 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/utils/thread-message-like.d.ts +10 -0
- package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
- package/dist/runtime/utils/thread-message-like.js.map +1 -1
- package/dist/runtimes/external-store/external-store-adapter.d.ts +33 -1
- package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +27 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js +98 -3
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +2 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +3 -0
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +1 -0
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +3 -0
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.js +3 -0
- package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +168 -0
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -0
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +449 -0
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -0
- package/dist/store/clients/thread-message-client.d.ts.map +1 -1
- package/dist/store/clients/thread-message-client.js +3 -0
- package/dist/store/clients/thread-message-client.js.map +1 -1
- package/dist/store/runtime-clients/message-part-runtime-client.js +1 -0
- package/dist/store/runtime-clients/message-part-runtime-client.js.map +1 -1
- package/dist/store/scopes/part.d.ts +7 -0
- package/dist/store/scopes/part.d.ts.map +1 -1
- package/dist/subscribable/subscribable.d.ts.map +1 -1
- package/dist/subscribable/subscribable.js.map +1 -1
- package/dist/types/message.d.ts +6 -0
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js.map +1 -1
- package/package.json +4 -4
- package/src/adapters/index.ts +1 -4
- package/src/index.ts +11 -0
- package/src/internal/duplicate-detection.ts +26 -0
- package/src/react/AssistantProvider.tsx +2 -3
- package/src/react/index.ts +2 -6
- package/src/react/primitives/chainOfThought/ChainOfThoughtParts.tsx +1 -2
- package/src/react/primitives/message/MessageAttachments.test.tsx +1 -1
- package/src/react/primitives/message/MessageGroupedParts.tsx +38 -31
- package/src/react/primitives/message/MessageParts.tsx +14 -1
- package/src/react/providers/TextMessagePartProvider.tsx +3 -0
- package/src/react/runtimes/external-message-converter.ts +26 -13
- package/src/react/types/MessagePartComponentTypes.ts +8 -0
- package/src/react/utils/groupParts.ts +67 -22
- package/src/runtime/api/attachment-runtime.ts +1 -2
- package/src/runtime/api/message-part-runtime.ts +26 -0
- package/src/runtime/base/base-thread-runtime-core.ts +4 -0
- package/src/runtime/interfaces/thread-runtime-core.ts +15 -0
- package/src/runtime/internal.ts +1 -4
- package/src/runtime/utils/thread-message-like.ts +7 -0
- package/src/runtimes/external-store/external-store-adapter.ts +37 -0
- package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +1 -3
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +168 -4
- package/src/runtimes/local/local-thread-runtime-core.ts +5 -0
- package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +4 -0
- package/src/runtimes/remote-thread-list/empty-thread-core.ts +4 -0
- package/src/runtimes/tool-invocations/EDGE_CASES.md +194 -0
- package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +1054 -0
- package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +783 -0
- package/src/store/clients/thread-message-client.ts +3 -0
- package/src/store/runtime-clients/message-part-runtime-client.ts +2 -0
- package/src/store/scopes/part.ts +4 -0
- package/src/subscribable/subscribable.ts +3 -3
- package/src/tests/OptimisticState-delete-crash.test.ts +2 -0
- package/src/tests/OptimisticState-list-race.test.ts +2 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +5 -5
- package/src/tests/auiV0Encode.test.ts +1 -1
- package/src/tests/composer-can-send.test.ts +8 -4
- package/src/tests/duplicate-detection.test.ts +34 -0
- package/src/tests/external-store-thread-list-runtime-core.test.ts +1 -1
- package/src/tests/external-store-thread-runtime-core.test.ts +7 -6
- package/src/tests/groupParts.test.ts +118 -32
- package/src/tests/no-unsafe-process-env.test.ts +1 -0
- package/src/tests/remote-thread-list-isLoading.test.ts +2 -0
- package/src/tests/thread-message-like.test.ts +4 -1
- package/src/types/index.ts +1 -4
- package/src/types/message.ts +7 -0
- package/dist/react/runtimes/useToolInvocations.d.ts +0 -53
- package/dist/react/runtimes/useToolInvocations.d.ts.map +0 -1
- package/dist/react/runtimes/useToolInvocations.js +0 -380
- package/dist/react/runtimes/useToolInvocations.js.map +0 -1
- package/src/react/runtimes/useToolInvocations.ts +0 -694
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# `ToolInvocationTracker` — known state-transition edge cases
|
|
2
|
+
|
|
3
|
+
This document captures the non-trivial state transitions the tracker may
|
|
4
|
+
observe via `setState(snapshot)` and what the current behavior is.
|
|
5
|
+
|
|
6
|
+
## Hard contract
|
|
7
|
+
|
|
8
|
+
> **`streamCall` (and `execute`) fires exactly once per logical
|
|
9
|
+
> `toolCallId`.** No matter how the host's snapshot mutates after that
|
|
10
|
+
> first observation — args regress, args change after first completion,
|
|
11
|
+
> result is replaced, result is cleared, key order shuffles — the tracker
|
|
12
|
+
> never invokes the host's tool callback a second time.
|
|
13
|
+
|
|
14
|
+
This guarantees host-side side effects (the typical reason `streamCall` /
|
|
15
|
+
`execute` exists at all) can't double-run. The cost: post-completion
|
|
16
|
+
mutations are not surfaced to the host through the tool callback.
|
|
17
|
+
Consumers that need to observe them will opt into the planned
|
|
18
|
+
`reader.events()` API.
|
|
19
|
+
|
|
20
|
+
The tracker also never throws. Every public method that observes runtime
|
|
21
|
+
state (`setState`, `reset`, `abort`, `resume`) wraps its work in
|
|
22
|
+
try/catch and logs to `console.error`. The tracker is built into the hot
|
|
23
|
+
message-processing path; a malformed snapshot must never crash the host
|
|
24
|
+
runtime.
|
|
25
|
+
|
|
26
|
+
## A. Tool changes shape after first observation
|
|
27
|
+
|
|
28
|
+
### A.1. Args grow (normal streaming case)
|
|
29
|
+
Each snapshot's `argsText` is a longer prefix of the previous. The
|
|
30
|
+
tracker appends the delta into the active controller's `argsText`
|
|
31
|
+
stream. No re-fire.
|
|
32
|
+
|
|
33
|
+
### A.2. Args regress mid-stream (snapshot regression)
|
|
34
|
+
A later snapshot's `argsText` is shorter than what we already streamed,
|
|
35
|
+
or otherwise *not* a prefix of it. Under the exactly-once contract, the
|
|
36
|
+
tracker does **not** restart the stream. The controller keeps whatever
|
|
37
|
+
prefix already streamed. The regression is logged in non-prod. The
|
|
38
|
+
host's view diverges from the snapshot until `reader.events()` ships.
|
|
39
|
+
|
|
40
|
+
Subsequent snapshots that *are* prefixes of the new (regressed) snapshot
|
|
41
|
+
also won't be appended, because `entry.argsText` still points at the
|
|
42
|
+
pre-regression value used for delta calculation.
|
|
43
|
+
|
|
44
|
+
### A.3. Args complete then equivalent-JSON key reorder
|
|
45
|
+
Both old and new `argsText` parse to equivalent JSON values (e.g. keys
|
|
46
|
+
reordered by the backend). The tracker updates its tracked `argsText`
|
|
47
|
+
silently. No re-fire.
|
|
48
|
+
|
|
49
|
+
### A.4. Args complete then change to non-equivalent value
|
|
50
|
+
The tracker does **not** restart the stream and does **not** invoke
|
|
51
|
+
`streamCall` a second time. Logs the divergence in non-prod. The host's
|
|
52
|
+
existing `streamCall` keeps its original args view.
|
|
53
|
+
|
|
54
|
+
### A.5. First resolution (`result` becomes defined)
|
|
55
|
+
The tracker calls `setResponse` on the active controller and closes it.
|
|
56
|
+
`reader.response.get()` resolves. If the tool also had a frontend
|
|
57
|
+
`execute`, the executor is short-circuited via `_skipExecuteStreamIds`.
|
|
58
|
+
Single fire.
|
|
59
|
+
|
|
60
|
+
### A.6. Previously-resolved tool's `result` is replaced
|
|
61
|
+
Silently ignored — `entry.hasResult` short-circuits both the
|
|
62
|
+
re-`setResponse` path and the downstream result-chunk handler. The host
|
|
63
|
+
sees only the first result.
|
|
64
|
+
|
|
65
|
+
### A.7. Previously-resolved tool loses its `result` (back to undefined)
|
|
66
|
+
Silently ignored. The entry stays in the resolved phase internally.
|
|
67
|
+
|
|
68
|
+
## B. Tool call disappears from snapshot
|
|
69
|
+
|
|
70
|
+
### B.1. Tool call removed entirely (rollback, branch switch)
|
|
71
|
+
The tracker does not auto-clean entries that disappear from the
|
|
72
|
+
snapshot. The entry persists in `_entries` until the next `reset()`.
|
|
73
|
+
|
|
74
|
+
Auto-cleanup is intentionally avoided: if the same `toolCallId` ever
|
|
75
|
+
reappears in a later snapshot, treating it as new would re-fire
|
|
76
|
+
`streamCall`, violating the exactly-once contract. The cost is a bounded
|
|
77
|
+
memory accumulation across the tracker's lifetime; `reset()` clears it.
|
|
78
|
+
|
|
79
|
+
## C. Initial snapshot vs. live snapshot
|
|
80
|
+
|
|
81
|
+
### C.1. Tool call present in the initial snapshot
|
|
82
|
+
While `_pendingRestore === true` (either by construction, or because
|
|
83
|
+
`snapshot.isLoading === true`), tool calls are recorded as restored
|
|
84
|
+
entries with no controller. `streamCall` / `execute` do not fire.
|
|
85
|
+
|
|
86
|
+
### C.2. Restored entry observed in a live snapshot, unchanged
|
|
87
|
+
Silently kept as restored. Recursion into `content.messages` still
|
|
88
|
+
happens so any nested live tool calls are processed.
|
|
89
|
+
|
|
90
|
+
### C.3. Restored entry observed in a live snapshot, signature changed
|
|
91
|
+
The restored entry is deleted and a new active entry starts via
|
|
92
|
+
`_startActiveEntry`. This is PR #4057's promotion path. `streamCall`
|
|
93
|
+
fires once — its first and only fire for this `toolCallId`.
|
|
94
|
+
|
|
95
|
+
### C.4. `isLoading` transitions `true → false` while messages are stable
|
|
96
|
+
The next `setState` call sees `isLoading === false` and processes
|
|
97
|
+
messages as live. Snapshots observed while `isLoading` was true seeded
|
|
98
|
+
restored entries. The first live snapshot promotes any whose signature
|
|
99
|
+
changed.
|
|
100
|
+
|
|
101
|
+
### C.5. `isLoading` transitions `false → true` mid-session
|
|
102
|
+
Treated as a return to the historical-loading window. Subsequent
|
|
103
|
+
snapshots are recorded as restored. Tool calls observed live before the
|
|
104
|
+
transition keep their active controllers — the tracker does not unwind
|
|
105
|
+
them.
|
|
106
|
+
|
|
107
|
+
## D. Nested tool calls (PTC sub-tools via `content.messages`)
|
|
108
|
+
|
|
109
|
+
### D.1. Parent tool's nested messages are observed
|
|
110
|
+
The tracker recurses via `_processMessages(content.messages)`. Nested
|
|
111
|
+
tool calls go through the same restore / live / promotion logic as
|
|
112
|
+
top-level ones, all under the same exactly-once contract.
|
|
113
|
+
|
|
114
|
+
### D.2. Nested tool's parent gets a new `result`
|
|
115
|
+
Handled like A.5 for the parent; the recursion into `content.messages`
|
|
116
|
+
still runs in the same pass, so nested tool calls also get processed.
|
|
117
|
+
|
|
118
|
+
### D.3. Nested tool's `content.messages` itself changes
|
|
119
|
+
Identity is by `toolCallId`, not index. A different `toolCallId` at
|
|
120
|
+
the same nested position is a fresh tool call. Same id with different
|
|
121
|
+
shape goes through A.1–A.4.
|
|
122
|
+
|
|
123
|
+
## E. Malformed snapshot
|
|
124
|
+
|
|
125
|
+
### E.1. `message` is null/undefined or `message.content` is not an array
|
|
126
|
+
Skipped silently. The rest of the snapshot still processes.
|
|
127
|
+
|
|
128
|
+
### E.2. `content` item is null or not a tool-call part
|
|
129
|
+
Skipped silently. Other parts in the same `message.content` still process.
|
|
130
|
+
|
|
131
|
+
### E.3. Different `messages` reference, identical contents
|
|
132
|
+
The tracker re-walks the array on every non-identity snapshot. The
|
|
133
|
+
reference-equality fast path in `setState` rarely fires for class
|
|
134
|
+
consumers (external-store rebuilds the array on every adapter update).
|
|
135
|
+
|
|
136
|
+
### E.4. `setState` throws inside `_processMessages`
|
|
137
|
+
The top-level try/catch in `setState` swallows the error and logs.
|
|
138
|
+
`_lastSnapshot` and `_isRunning` mutations are deferred until *after*
|
|
139
|
+
successful processing, so a transient failure does not corrupt the
|
|
140
|
+
tracker's view of "what we last observed". The next snapshot retries.
|
|
141
|
+
|
|
142
|
+
## F. Concurrency and lifecycle
|
|
143
|
+
|
|
144
|
+
### F.1. `reset()` called while `execute()` invocations are in flight
|
|
145
|
+
`abort()` is invoked, in-flight executions reject with
|
|
146
|
+
`Tool execution aborted`. Once they settle, the cleanup logic clears
|
|
147
|
+
`_executing`. The settled-resolver promises fire so the abort promise
|
|
148
|
+
resolves.
|
|
149
|
+
|
|
150
|
+
### F.2. `setState` called during `reset()`'s in-flight abort
|
|
151
|
+
The new snapshot is processed against an empty `_entries`. Tool calls
|
|
152
|
+
in it are seeded as restored (because `reset()` re-armed
|
|
153
|
+
`_pendingRestore`). Eventual cancellation `result` chunks for the
|
|
154
|
+
aborted executions are dropped via `_skipExecuteStreamIds`.
|
|
155
|
+
|
|
156
|
+
### F.3. `resume(toolCallId, payload)` for an unknown id
|
|
157
|
+
Silently no-ops. (The pre-class hook *threw*; the tracker softens this
|
|
158
|
+
to match the never-throw guarantee.)
|
|
159
|
+
|
|
160
|
+
### F.4. Assistant-stream pipeline itself errors
|
|
161
|
+
The `.pipeTo(...).catch(...)` handler logs and flips `_pipelineDead`.
|
|
162
|
+
The next `setState` call recreates the pipeline once per tracker
|
|
163
|
+
lifetime: existing active entries are *demoted to restored* (so the
|
|
164
|
+
rebuilt pipeline does not re-fire `streamCall` for them) and the
|
|
165
|
+
snapshot is processed against the fresh pipeline. Repeated failures
|
|
166
|
+
keep the tracker dead with a visible error to avoid restart loops.
|
|
167
|
+
|
|
168
|
+
## Known limitations
|
|
169
|
+
|
|
170
|
+
### Result delivery after args regression (A.2 + A.5 in the same snapshot)
|
|
171
|
+
When a snapshot has both a regressed `argsText` *and* a backend result
|
|
172
|
+
on the same tool call, `activeController.setResponse(result)` closes
|
|
173
|
+
`argsText` before enqueueing the result chunk. The args-text-finish
|
|
174
|
+
chunk reaches `ToolExecutionStream` first, attempts to parse the
|
|
175
|
+
(stale) accumulated argsText, fails, and emits a parse-error result
|
|
176
|
+
that beats the backend result to the reader's response promise.
|
|
177
|
+
|
|
178
|
+
The tracker's `entry.hasResult` short-circuit *does* suppress both
|
|
179
|
+
result chunks at the `onResult` callback level (no double-fire), but
|
|
180
|
+
the reader's `response.get()` already resolved with the parse error.
|
|
181
|
+
|
|
182
|
+
Fixable upstream in `ToolCallStreamControllerImpl.setResponse` by
|
|
183
|
+
enqueueing the result chunk before closing argsText. Tracked separately;
|
|
184
|
+
out of scope for the tracker layer.
|
|
185
|
+
|
|
186
|
+
### Host callback throws
|
|
187
|
+
`onResult` and `onStatusesChange` are invoked through wrappers that
|
|
188
|
+
catch and log. The tracker continues to function; the host's bad
|
|
189
|
+
callback is isolated.
|
|
190
|
+
|
|
191
|
+
### Args-stream divergence after A.2 / A.4
|
|
192
|
+
Documented in the corresponding sections. The host's `streamCall` may
|
|
193
|
+
operate on stale args. The `reader.events()` follow-up gives consumers
|
|
194
|
+
a way to observe and react to these post-completion transitions.
|