assistant-stream 0.3.20 → 0.3.22
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/core/accumulators/assistant-message-accumulator.d.ts.map +1 -1
- package/dist/core/accumulators/assistant-message-accumulator.js +5 -0
- package/dist/core/accumulators/assistant-message-accumulator.js.map +1 -1
- package/dist/core/converters/toGenericMessages.js.map +1 -1
- package/dist/core/object/ObjectStreamResponse.js.map +1 -1
- package/dist/core/object/types.d.ts +1 -0
- package/dist/core/object/types.d.ts.map +1 -1
- package/dist/core/serialization/PlainText.js.map +1 -1
- package/dist/core/serialization/assistant-transport/AssistantTransport.js.map +1 -1
- package/dist/core/serialization/data-stream/DataStream.js.map +1 -1
- package/dist/core/serialization/ui-message-stream/UIMessageStream.js.map +1 -1
- package/dist/core/tool/ToolCallReader.d.ts +1 -0
- package/dist/core/tool/ToolCallReader.d.ts.map +1 -1
- package/dist/core/tool/ToolCallReader.js.map +1 -1
- package/dist/core/tool/ToolExecutionStream.js.map +1 -1
- package/dist/core/tool/schema-utils.js.map +1 -1
- package/dist/core/tool/tool-types.d.ts.map +1 -1
- package/dist/core/tool/toolResultStream.js.map +1 -1
- package/dist/core/utils/stream/AssistantTransformStream.js.map +1 -1
- package/dist/core/utils/stream/SSE.js.map +1 -1
- package/dist/core/utils/stream/merge.js.map +1 -1
- package/dist/core/utils/types.d.ts +12 -1
- package/dist/core/utils/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/resumable/ResumableStreamContext.js.map +1 -1
- package/dist/resumable/createResumableAssistantStreamResponse.js.map +1 -1
- package/dist/resumable/stores/InMemoryResumableStreamStore.js.map +1 -1
- package/dist/resumable/stores/ioredis.js.map +1 -1
- package/dist/utils/json/fix-json.js.map +1 -1
- package/dist/utils/json/is-json.js.map +1 -1
- package/package.json +4 -4
- package/src/core/accumulators/assistant-message-accumulator.test.ts +51 -0
- package/src/core/accumulators/assistant-message-accumulator.ts +9 -0
- package/src/core/utils/types.ts +14 -0
- package/src/index.ts +1 -0
- package/src/resumable/__tests__/integration.test.ts +12 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"is-json.js","names":[],"sources":["../../../src/utils/json/is-json.ts"],"sourcesContent":["import type {\n ReadonlyJSONArray,\n ReadonlyJSONObject,\n ReadonlyJSONValue,\n} from \"./json-value\";\n\nexport function isJSONValue(value: unknown): value is ReadonlyJSONValue {\n if (\n value === null ||\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return true;\n }\n\n if (Array.isArray(value)) {\n return value.every(isJSONValue);\n }\n\n if (typeof value === \"object\") {\n return Object.entries(value).every(\n ([key, val]) => typeof key === \"string\" && isJSONValue(val),\n );\n }\n\n return false;\n}\n\nexport function isJSONArray(value: unknown): value is ReadonlyJSONArray {\n return Array.isArray(value) && value.every(isJSONValue);\n}\n\nexport function isJSONObject(value: unknown): value is ReadonlyJSONObject {\n return (\n value != null &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.entries(value).every(\n ([key, val]) => typeof key === \"string\" && isJSONValue(val),\n )\n );\n}\n"],"mappings":";AAMA,SAAgB,YAAY,OAA4C;CACtE,IACE,UAAU,QACV,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,WAEjB,OAAO;CAGT,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,MAAM,WAAW;CAGhC,IAAI,OAAO,UAAU,UACnB,OAAO,OAAO,QAAQ,KAAK,
|
|
1
|
+
{"version":3,"file":"is-json.js","names":[],"sources":["../../../src/utils/json/is-json.ts"],"sourcesContent":["import type {\n ReadonlyJSONArray,\n ReadonlyJSONObject,\n ReadonlyJSONValue,\n} from \"./json-value\";\n\nexport function isJSONValue(value: unknown): value is ReadonlyJSONValue {\n if (\n value === null ||\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return true;\n }\n\n if (Array.isArray(value)) {\n return value.every(isJSONValue);\n }\n\n if (typeof value === \"object\") {\n return Object.entries(value).every(\n ([key, val]) => typeof key === \"string\" && isJSONValue(val),\n );\n }\n\n return false;\n}\n\nexport function isJSONArray(value: unknown): value is ReadonlyJSONArray {\n return Array.isArray(value) && value.every(isJSONValue);\n}\n\nexport function isJSONObject(value: unknown): value is ReadonlyJSONObject {\n return (\n value != null &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.entries(value).every(\n ([key, val]) => typeof key === \"string\" && isJSONValue(val),\n )\n );\n}\n"],"mappings":";AAMA,SAAgB,YAAY,OAA4C;CACtE,IACE,UAAU,QACV,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,WAEjB,OAAO;CAGT,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,MAAM,WAAW;CAGhC,IAAI,OAAO,UAAU,UACnB,OAAO,OAAO,QAAQ,KAAK,CAAC,CAAC,OAC1B,CAAC,KAAK,SAAS,OAAO,QAAQ,YAAY,YAAY,GAAG,CAC5D;CAGF,OAAO;AACT;AAEA,SAAgB,YAAY,OAA4C;CACtE,OAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,WAAW;AACxD;AAEA,SAAgB,aAAa,OAA6C;CACxE,OACE,SAAS,QACT,OAAO,UAAU,YACjB,CAAC,MAAM,QAAQ,KAAK,KACpB,OAAO,QAAQ,KAAK,CAAC,CAAC,OACnB,CAAC,KAAK,SAAS,OAAO,QAAQ,YAAY,YAAY,GAAG,CAC5D;AAEJ"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assistant-stream",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.22",
|
|
4
4
|
"description": "Streaming utilities for AI assistants",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -64,10 +64,10 @@
|
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/json-schema": "^7.0.15",
|
|
67
|
-
"ioredis": "^5.11.
|
|
67
|
+
"ioredis": "^5.11.1",
|
|
68
68
|
"redis": "^6.0.0",
|
|
69
|
-
"vitest": "^4.1.
|
|
70
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
69
|
+
"vitest": "^4.1.8",
|
|
70
|
+
"@assistant-ui/x-buildutils": "0.0.13"
|
|
71
71
|
},
|
|
72
72
|
"publishConfig": {
|
|
73
73
|
"access": "public",
|
|
@@ -101,6 +101,57 @@ describe("AssistantMessageAccumulator timing", () => {
|
|
|
101
101
|
expect(last.metadata.timing!.toolCallCount).toBe(1);
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
it("should record per-tool-call timing on the part", async () => {
|
|
105
|
+
const before = Date.now();
|
|
106
|
+
const chunks: AssistantStreamChunk[] = [
|
|
107
|
+
{
|
|
108
|
+
type: "part-start",
|
|
109
|
+
path: [0],
|
|
110
|
+
part: {
|
|
111
|
+
type: "tool-call",
|
|
112
|
+
toolCallId: "tc-1",
|
|
113
|
+
toolName: "search",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{ type: "text-delta", path: [0], textDelta: '{"q":"test"}' },
|
|
117
|
+
{ type: "tool-call-args-text-finish", path: [0] },
|
|
118
|
+
{
|
|
119
|
+
type: "result",
|
|
120
|
+
path: [0],
|
|
121
|
+
result: "found",
|
|
122
|
+
isError: false,
|
|
123
|
+
},
|
|
124
|
+
{ type: "part-finish", path: [0] },
|
|
125
|
+
{
|
|
126
|
+
type: "message-finish",
|
|
127
|
+
path: [],
|
|
128
|
+
finishReason: "stop",
|
|
129
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
const messages = await collectStream(chunks);
|
|
134
|
+
const after = Date.now();
|
|
135
|
+
|
|
136
|
+
const runningPart = messages
|
|
137
|
+
.find((m) =>
|
|
138
|
+
m.parts.some((p) => p.type === "tool-call" && p.state !== "result"),
|
|
139
|
+
)!
|
|
140
|
+
.parts.find((p) => p.type === "tool-call")!;
|
|
141
|
+
expect(runningPart.timing).toBeDefined();
|
|
142
|
+
expect(runningPart.timing!.startedAt).toBeGreaterThanOrEqual(before);
|
|
143
|
+
expect(runningPart.timing!.completedAt).toBeUndefined();
|
|
144
|
+
|
|
145
|
+
const settledPart = messages
|
|
146
|
+
.at(-1)!
|
|
147
|
+
.parts.find((p) => p.type === "tool-call")!;
|
|
148
|
+
expect(settledPart.timing).toBeDefined();
|
|
149
|
+
expect(settledPart.timing!.completedAt).toBeGreaterThanOrEqual(
|
|
150
|
+
settledPart.timing!.startedAt,
|
|
151
|
+
);
|
|
152
|
+
expect(settledPart.timing!.completedAt).toBeLessThanOrEqual(after);
|
|
153
|
+
});
|
|
154
|
+
|
|
104
155
|
it("should include timing on flush when stream closes without message-finish", async () => {
|
|
105
156
|
const chunks: AssistantStreamChunk[] = [
|
|
106
157
|
{ type: "part-start", path: [0], part: { type: "text" } },
|
|
@@ -92,6 +92,7 @@ const handlePartStart = (
|
|
|
92
92
|
toolName: partInit.toolName,
|
|
93
93
|
argsText: "",
|
|
94
94
|
args: {},
|
|
95
|
+
timing: { startedAt: Date.now() },
|
|
95
96
|
...(partInit.parentId && { parentId: partInit.parentId }),
|
|
96
97
|
};
|
|
97
98
|
return {
|
|
@@ -213,6 +214,14 @@ const handleResult = (
|
|
|
213
214
|
return {
|
|
214
215
|
...part,
|
|
215
216
|
state: "result",
|
|
217
|
+
...(part.timing !== undefined
|
|
218
|
+
? {
|
|
219
|
+
timing: {
|
|
220
|
+
...part.timing,
|
|
221
|
+
completedAt: part.timing.completedAt ?? Date.now(),
|
|
222
|
+
},
|
|
223
|
+
}
|
|
224
|
+
: {}),
|
|
216
225
|
...(chunk.artifact !== undefined ? { artifact: chunk.artifact } : {}),
|
|
217
226
|
result: chunk.result,
|
|
218
227
|
isError: chunk.isError ?? false,
|
package/src/core/utils/types.ts
CHANGED
|
@@ -53,6 +53,19 @@ type ToolCallStatus =
|
|
|
53
53
|
reason: "cancelled" | "length" | "content-filter" | "other";
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Wall-clock timing of a tool call. Accumulator-populated timings are
|
|
58
|
+
* measured by the consuming accumulator, so resumed or replayed streams
|
|
59
|
+
* re-measure them; hosts that need authoritative timings supply the field
|
|
60
|
+
* themselves.
|
|
61
|
+
*/
|
|
62
|
+
export type ToolCallTiming = {
|
|
63
|
+
/** Epoch milliseconds when the tool call started streaming or executing. */
|
|
64
|
+
readonly startedAt: number;
|
|
65
|
+
/** Epoch milliseconds when the result landed. Absent while the call runs. */
|
|
66
|
+
readonly completedAt?: number;
|
|
67
|
+
};
|
|
68
|
+
|
|
56
69
|
type ToolCallPartBase = {
|
|
57
70
|
type: "tool-call";
|
|
58
71
|
status: ToolCallStatus;
|
|
@@ -60,6 +73,7 @@ type ToolCallPartBase = {
|
|
|
60
73
|
toolName: string;
|
|
61
74
|
argsText: string;
|
|
62
75
|
args: ReadonlyJSONObject;
|
|
76
|
+
timing?: ToolCallTiming;
|
|
63
77
|
artifact?: ReadonlyJSONValue;
|
|
64
78
|
result?: ReadonlyJSONValue;
|
|
65
79
|
modelContent?: readonly ToolModelContentPart[];
|
package/src/index.ts
CHANGED
|
@@ -145,7 +145,17 @@ describe("resumable integration", () => {
|
|
|
145
145
|
new AssistantTransportDecoder(),
|
|
146
146
|
);
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
// Tool-call timing is measured by the consuming accumulator, so a replay
|
|
149
|
+
// re-measures it; compare parts modulo timing.
|
|
150
|
+
const withoutTiming = (parts: typeof replayMessage.parts) =>
|
|
151
|
+
parts.map((part) =>
|
|
152
|
+
part.type === "tool-call"
|
|
153
|
+
? (({ timing: _timing, ...rest }) => rest)(part)
|
|
154
|
+
: part,
|
|
155
|
+
);
|
|
156
|
+
expect(withoutTiming(replayMessage.parts)).toEqual(
|
|
157
|
+
withoutTiming(producerMessage.parts),
|
|
158
|
+
);
|
|
149
159
|
expect(replayMessage.status).toEqual(producerMessage.status);
|
|
150
160
|
const toolPart = replayMessage.parts.find((p) => p.type === "tool-call");
|
|
151
161
|
expect(toolPart).toBeDefined();
|
|
@@ -154,6 +164,7 @@ describe("resumable integration", () => {
|
|
|
154
164
|
toolCallId: "tool-1",
|
|
155
165
|
args: { query: "weather" },
|
|
156
166
|
result: { temperature: 72 },
|
|
167
|
+
timing: { startedAt: expect.any(Number) },
|
|
157
168
|
});
|
|
158
169
|
});
|
|
159
170
|
});
|