@providerprotocol/agents 0.0.2 → 0.0.4
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/LICENSE +21 -0
- package/dist/checkpoint/index.d.ts +43 -0
- package/dist/checkpoint/index.js +73 -0
- package/dist/checkpoint/index.js.map +1 -0
- package/{src/execution/loop.ts → dist/chunk-4ESYN66B.js} +54 -162
- package/dist/chunk-4ESYN66B.js.map +1 -0
- package/dist/chunk-EKRXMSDX.js +8 -0
- package/dist/chunk-EKRXMSDX.js.map +1 -0
- package/dist/chunk-T47B3VAF.js +427 -0
- package/dist/chunk-T47B3VAF.js.map +1 -0
- package/dist/execution/index.d.ts +105 -0
- package/dist/execution/index.js +679 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/index-qsPwbY86.d.ts +65 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +218 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/index.d.ts +23 -0
- package/dist/middleware/index.js +82 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/thread-tree/index.d.ts +115 -0
- package/dist/thread-tree/index.js +4 -0
- package/dist/thread-tree/index.js.map +1 -0
- package/dist/types-2Vsthzyu.d.ts +163 -0
- package/dist/types-BiyEVOnf.d.ts +65 -0
- package/dist/types-D1egxttz.d.ts +270 -0
- package/dist/types-DChRdQoX.d.ts +98 -0
- package/package.json +41 -9
- package/.claude/settings.local.json +0 -29
- package/AGENTS.md +0 -681
- package/CLAUDE.md +0 -681
- package/bun.lock +0 -472
- package/eslint.config.js +0 -75
- package/index.ts +0 -1
- package/llms.md +0 -796
- package/specs/UAP-1.0.md +0 -2355
- package/src/agent/index.ts +0 -384
- package/src/agent/types.ts +0 -91
- package/src/checkpoint/file.ts +0 -126
- package/src/checkpoint/index.ts +0 -40
- package/src/checkpoint/types.ts +0 -95
- package/src/execution/index.ts +0 -37
- package/src/execution/plan.ts +0 -497
- package/src/execution/react.ts +0 -340
- package/src/execution/tool-ordering.ts +0 -186
- package/src/execution/types.ts +0 -315
- package/src/index.ts +0 -80
- package/src/middleware/index.ts +0 -7
- package/src/middleware/logging.ts +0 -123
- package/src/middleware/types.ts +0 -69
- package/src/state/index.ts +0 -301
- package/src/state/types.ts +0 -173
- package/src/thread-tree/index.ts +0 -249
- package/src/thread-tree/types.ts +0 -29
- package/src/utils/uuid.ts +0 -7
- package/tests/live/agent-anthropic.test.ts +0 -288
- package/tests/live/agent-strategy-hooks.test.ts +0 -268
- package/tests/live/checkpoint.test.ts +0 -243
- package/tests/live/execution-strategies.test.ts +0 -255
- package/tests/live/plan-strategy.test.ts +0 -160
- package/tests/live/subagent-events.live.test.ts +0 -249
- package/tests/live/thread-tree.test.ts +0 -186
- package/tests/unit/agent.test.ts +0 -703
- package/tests/unit/checkpoint.test.ts +0 -232
- package/tests/unit/execution/equivalence.test.ts +0 -402
- package/tests/unit/execution/loop.test.ts +0 -437
- package/tests/unit/execution/plan.test.ts +0 -590
- package/tests/unit/execution/react.test.ts +0 -604
- package/tests/unit/execution/subagent-events.test.ts +0 -235
- package/tests/unit/execution/tool-ordering.test.ts +0 -310
- package/tests/unit/middleware/logging.test.ts +0 -276
- package/tests/unit/state.test.ts +0 -573
- package/tests/unit/thread-tree.test.ts +0 -249
- package/tsconfig.json +0 -29
|
@@ -1,437 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, mock, beforeEach } from 'bun:test';
|
|
2
|
-
import { UserMessage, AssistantMessage } from '@providerprotocol/ai';
|
|
3
|
-
import type { Turn, LLMInstance, Tool, ToolCall, ToolExecution } from '@providerprotocol/ai';
|
|
4
|
-
import { loop } from '../../../src/execution/loop.ts';
|
|
5
|
-
import { AgentState } from '../../../src/state/index.ts';
|
|
6
|
-
import type { ExecutionContext } from '../../../src/execution/types.ts';
|
|
7
|
-
|
|
8
|
-
// Mock Turn factory
|
|
9
|
-
function createMockTurn(options: {
|
|
10
|
-
text?: string;
|
|
11
|
-
toolCalls?: ToolCall[];
|
|
12
|
-
toolExecutions?: ToolExecution[];
|
|
13
|
-
}): Turn {
|
|
14
|
-
const response = new AssistantMessage(options.text ?? 'Hello', options.toolCalls);
|
|
15
|
-
return {
|
|
16
|
-
response,
|
|
17
|
-
messages: [response],
|
|
18
|
-
toolExecutions: options.toolExecutions ?? [],
|
|
19
|
-
usage: {
|
|
20
|
-
inputTokens: 10,
|
|
21
|
-
outputTokens: 20,
|
|
22
|
-
totalTokens: 30,
|
|
23
|
-
},
|
|
24
|
-
cycles: 1,
|
|
25
|
-
} as unknown as Turn;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Mock LLM factory
|
|
29
|
-
function createMockLLM(turns: Turn[]): LLMInstance {
|
|
30
|
-
let callIndex = 0;
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
generate: mock(async () => {
|
|
34
|
-
const turn = turns[callIndex] ?? turns[turns.length - 1];
|
|
35
|
-
callIndex++;
|
|
36
|
-
return turn;
|
|
37
|
-
}),
|
|
38
|
-
stream: mock(() => {
|
|
39
|
-
const turn = turns[callIndex] ?? turns[turns.length - 1];
|
|
40
|
-
callIndex++;
|
|
41
|
-
|
|
42
|
-
const events: Array<{ type: string; delta?: { text?: string } }> = [];
|
|
43
|
-
if (turn) {
|
|
44
|
-
events.push({ type: 'text_delta', delta: { text: turn.response.text } });
|
|
45
|
-
events.push({ type: 'message_stop' });
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
async *[Symbol.asyncIterator] () {
|
|
50
|
-
for (const event of events) {
|
|
51
|
-
yield event;
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
turn: Promise.resolve(turn),
|
|
55
|
-
};
|
|
56
|
-
}),
|
|
57
|
-
} as unknown as LLMInstance;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
describe('loop() execution strategy', () => {
|
|
61
|
-
let state: AgentState;
|
|
62
|
-
let input: UserMessage;
|
|
63
|
-
let tools: Tool[];
|
|
64
|
-
|
|
65
|
-
beforeEach(() => {
|
|
66
|
-
state = AgentState.initial();
|
|
67
|
-
input = new UserMessage('Hello');
|
|
68
|
-
tools = [];
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe('execute()', () => {
|
|
72
|
-
test('returns turn and updated state', async () => {
|
|
73
|
-
const mockTurn = createMockTurn({ text: 'Hi there!' });
|
|
74
|
-
const llm = createMockLLM([mockTurn]);
|
|
75
|
-
|
|
76
|
-
const strategy = loop();
|
|
77
|
-
const context: ExecutionContext = {
|
|
78
|
-
agent: { id: 'test-agent' },
|
|
79
|
-
llm,
|
|
80
|
-
input,
|
|
81
|
-
state,
|
|
82
|
-
tools,
|
|
83
|
-
strategy: {},
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const result = await strategy.execute(context);
|
|
87
|
-
|
|
88
|
-
expect(result.turn).toBe(mockTurn);
|
|
89
|
-
expect(result.state.messages.length).toBeGreaterThan(0);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('loops on tool calls', async () => {
|
|
93
|
-
const toolCall: ToolCall = {
|
|
94
|
-
toolCallId: 'call-1',
|
|
95
|
-
toolName: 'test_tool',
|
|
96
|
-
arguments: { arg: 'value' },
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const turn1 = createMockTurn({
|
|
100
|
-
text: 'Calling tool...',
|
|
101
|
-
toolCalls: [toolCall],
|
|
102
|
-
toolExecutions: [{ toolCallId: 'call-1', toolName: 'test_tool', arguments: { arg: 'value' }, result: 'done', duration: 100, isError: false }],
|
|
103
|
-
});
|
|
104
|
-
const turn2 = createMockTurn({ text: 'Done!', toolCalls: [] });
|
|
105
|
-
|
|
106
|
-
const llm = createMockLLM([turn1, turn2]);
|
|
107
|
-
|
|
108
|
-
const strategy = loop();
|
|
109
|
-
const context: ExecutionContext = {
|
|
110
|
-
agent: { id: 'test-agent' },
|
|
111
|
-
llm,
|
|
112
|
-
input,
|
|
113
|
-
state,
|
|
114
|
-
tools,
|
|
115
|
-
strategy: {},
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const result = await strategy.execute(context);
|
|
119
|
-
|
|
120
|
-
expect(llm.generate).toHaveBeenCalledTimes(2);
|
|
121
|
-
expect(result.turn.response.text).toBe('Done!');
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test('respects maxIterations', async () => {
|
|
125
|
-
const toolCall: ToolCall = {
|
|
126
|
-
toolCallId: 'call-1',
|
|
127
|
-
toolName: 'test_tool',
|
|
128
|
-
arguments: {},
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const turnWithTool = createMockTurn({
|
|
132
|
-
text: 'Looping...',
|
|
133
|
-
toolCalls: [toolCall],
|
|
134
|
-
toolExecutions: [{ toolCallId: 'call-1', toolName: 'test_tool', arguments: {}, result: 'ok', duration: 10, isError: false }],
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const llm = createMockLLM([turnWithTool, turnWithTool, turnWithTool, turnWithTool, turnWithTool]);
|
|
138
|
-
|
|
139
|
-
const strategy = loop({ maxIterations: 3 });
|
|
140
|
-
const context: ExecutionContext = {
|
|
141
|
-
agent: { id: 'test-agent' },
|
|
142
|
-
llm,
|
|
143
|
-
input,
|
|
144
|
-
state,
|
|
145
|
-
tools,
|
|
146
|
-
strategy: {},
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
await strategy.execute(context);
|
|
150
|
-
|
|
151
|
-
expect(llm.generate).toHaveBeenCalledTimes(3);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test('defaults to Infinity maxIterations', async () => {
|
|
155
|
-
const strategy = loop();
|
|
156
|
-
expect(strategy.name).toBe('loop');
|
|
157
|
-
// The default is Infinity, but we can't easily test infinite behavior
|
|
158
|
-
// We test this by verifying it loops more than a small number
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test('calls onStepStart hook', async () => {
|
|
162
|
-
const mockTurn = createMockTurn({ text: 'Response' });
|
|
163
|
-
const llm = createMockLLM([mockTurn]);
|
|
164
|
-
const onStepStart = mock(() => {});
|
|
165
|
-
|
|
166
|
-
const strategy = loop();
|
|
167
|
-
const context: ExecutionContext = {
|
|
168
|
-
agent: { id: 'test-agent' },
|
|
169
|
-
llm,
|
|
170
|
-
input,
|
|
171
|
-
state,
|
|
172
|
-
tools,
|
|
173
|
-
strategy: { onStepStart },
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
await strategy.execute(context);
|
|
177
|
-
|
|
178
|
-
expect(onStepStart).toHaveBeenCalledTimes(1);
|
|
179
|
-
// Verify the hook was called with step number and a state object
|
|
180
|
-
expect(onStepStart).toHaveBeenCalled();
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test('calls onStepEnd hook', async () => {
|
|
184
|
-
const mockTurn = createMockTurn({ text: 'Response' });
|
|
185
|
-
const llm = createMockLLM([mockTurn]);
|
|
186
|
-
const onStepEnd = mock(() => {});
|
|
187
|
-
|
|
188
|
-
const strategy = loop();
|
|
189
|
-
const context: ExecutionContext = {
|
|
190
|
-
agent: { id: 'test-agent' },
|
|
191
|
-
llm,
|
|
192
|
-
input,
|
|
193
|
-
state,
|
|
194
|
-
tools,
|
|
195
|
-
strategy: { onStepEnd },
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
await strategy.execute(context);
|
|
199
|
-
|
|
200
|
-
expect(onStepEnd).toHaveBeenCalledTimes(1);
|
|
201
|
-
// Verify the hook was called with step number and result object
|
|
202
|
-
expect(onStepEnd).toHaveBeenCalled();
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
test('calls onAct hook on tool calls', async () => {
|
|
206
|
-
const toolCall: ToolCall = {
|
|
207
|
-
toolCallId: 'call-1',
|
|
208
|
-
toolName: 'test_tool',
|
|
209
|
-
arguments: {},
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
const turn1 = createMockTurn({
|
|
213
|
-
text: 'Using tool',
|
|
214
|
-
toolCalls: [toolCall],
|
|
215
|
-
});
|
|
216
|
-
const turn2 = createMockTurn({ text: 'Done' }); // Terminating turn
|
|
217
|
-
const llm = createMockLLM([turn1, turn2]);
|
|
218
|
-
const onAct = mock(() => {});
|
|
219
|
-
|
|
220
|
-
const strategy = loop();
|
|
221
|
-
const context: ExecutionContext = {
|
|
222
|
-
agent: { id: 'test-agent' },
|
|
223
|
-
llm,
|
|
224
|
-
input,
|
|
225
|
-
state,
|
|
226
|
-
tools,
|
|
227
|
-
strategy: { onAct },
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
await strategy.execute(context);
|
|
231
|
-
|
|
232
|
-
expect(onAct).toHaveBeenCalledWith(1, [toolCall]);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
test('calls onObserve hook on tool executions', async () => {
|
|
236
|
-
const execution: ToolExecution = {
|
|
237
|
-
toolCallId: 'call-1',
|
|
238
|
-
toolName: 'test_tool',
|
|
239
|
-
arguments: {},
|
|
240
|
-
result: 'result',
|
|
241
|
-
duration: 100,
|
|
242
|
-
isError: false,
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
// Turn with tool execution but no tool calls (so it terminates)
|
|
246
|
-
const turn = createMockTurn({
|
|
247
|
-
text: 'Done',
|
|
248
|
-
toolExecutions: [execution],
|
|
249
|
-
});
|
|
250
|
-
const llm = createMockLLM([turn]);
|
|
251
|
-
const onObserve = mock(() => {});
|
|
252
|
-
|
|
253
|
-
const strategy = loop();
|
|
254
|
-
const context: ExecutionContext = {
|
|
255
|
-
agent: { id: 'test-agent' },
|
|
256
|
-
llm,
|
|
257
|
-
input,
|
|
258
|
-
state,
|
|
259
|
-
tools,
|
|
260
|
-
strategy: { onObserve },
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
await strategy.execute(context);
|
|
264
|
-
|
|
265
|
-
expect(onObserve).toHaveBeenCalledWith(1, [execution]);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
test('calls onComplete hook', async () => {
|
|
269
|
-
const mockTurn = createMockTurn({ text: 'Response' });
|
|
270
|
-
const llm = createMockLLM([mockTurn]);
|
|
271
|
-
const onComplete = mock(() => {});
|
|
272
|
-
|
|
273
|
-
const strategy = loop();
|
|
274
|
-
const context: ExecutionContext = {
|
|
275
|
-
agent: { id: 'test-agent' },
|
|
276
|
-
llm,
|
|
277
|
-
input,
|
|
278
|
-
state,
|
|
279
|
-
tools,
|
|
280
|
-
strategy: { onComplete },
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
await strategy.execute(context);
|
|
284
|
-
|
|
285
|
-
expect(onComplete).toHaveBeenCalledTimes(1);
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
test('respects stopCondition', async () => {
|
|
289
|
-
const toolCall: ToolCall = {
|
|
290
|
-
toolCallId: 'call-1',
|
|
291
|
-
toolName: 'test_tool',
|
|
292
|
-
arguments: {},
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
const turnWithTool = createMockTurn({
|
|
296
|
-
text: 'Looping...',
|
|
297
|
-
toolCalls: [toolCall],
|
|
298
|
-
toolExecutions: [{ toolCallId: 'call-1', toolName: 'test_tool', arguments: {}, result: 'ok', duration: 10, isError: false }],
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
const llm = createMockLLM([turnWithTool, turnWithTool, turnWithTool]);
|
|
302
|
-
|
|
303
|
-
const stopAfter = 2;
|
|
304
|
-
const strategy = loop();
|
|
305
|
-
const context: ExecutionContext = {
|
|
306
|
-
agent: { id: 'test-agent' },
|
|
307
|
-
llm,
|
|
308
|
-
input,
|
|
309
|
-
state,
|
|
310
|
-
tools,
|
|
311
|
-
strategy: {
|
|
312
|
-
stopCondition: (s) => s.step >= stopAfter,
|
|
313
|
-
},
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
await strategy.execute(context);
|
|
317
|
-
|
|
318
|
-
expect(llm.generate).toHaveBeenCalledTimes(2);
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
test('updates state step number', async () => {
|
|
322
|
-
const toolCall: ToolCall = {
|
|
323
|
-
toolCallId: 'call-1',
|
|
324
|
-
toolName: 'test_tool',
|
|
325
|
-
arguments: {},
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
const turn1 = createMockTurn({
|
|
329
|
-
text: 'Step 1',
|
|
330
|
-
toolCalls: [toolCall],
|
|
331
|
-
toolExecutions: [{ toolCallId: 'call-1', toolName: 'test_tool', arguments: {}, result: 'ok', duration: 10, isError: false }],
|
|
332
|
-
});
|
|
333
|
-
const turn2 = createMockTurn({ text: 'Step 2' });
|
|
334
|
-
|
|
335
|
-
const llm = createMockLLM([turn1, turn2]);
|
|
336
|
-
|
|
337
|
-
const strategy = loop();
|
|
338
|
-
const context: ExecutionContext = {
|
|
339
|
-
agent: { id: 'test-agent' },
|
|
340
|
-
llm,
|
|
341
|
-
input,
|
|
342
|
-
state,
|
|
343
|
-
tools,
|
|
344
|
-
strategy: {},
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
const result = await strategy.execute(context);
|
|
348
|
-
|
|
349
|
-
expect(result.state.step).toBe(2);
|
|
350
|
-
});
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
describe('stream()', () => {
|
|
354
|
-
test('yields events and resolves result', async () => {
|
|
355
|
-
const mockTurn = createMockTurn({ text: 'Streamed response' });
|
|
356
|
-
const llm = createMockLLM([mockTurn]);
|
|
357
|
-
|
|
358
|
-
const strategy = loop();
|
|
359
|
-
const context: ExecutionContext = {
|
|
360
|
-
agent: { id: 'test-agent' },
|
|
361
|
-
llm,
|
|
362
|
-
input,
|
|
363
|
-
state,
|
|
364
|
-
tools,
|
|
365
|
-
strategy: {},
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
const streamResult = strategy.stream(context);
|
|
369
|
-
const events: unknown[] = [];
|
|
370
|
-
|
|
371
|
-
for await (const event of streamResult) {
|
|
372
|
-
events.push(event);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const result = await streamResult.result;
|
|
376
|
-
|
|
377
|
-
expect(events.length).toBeGreaterThan(0);
|
|
378
|
-
expect(result.turn).toBeDefined();
|
|
379
|
-
expect(result.state).toBeDefined();
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
test('emits step_start and step_end events', async () => {
|
|
383
|
-
const mockTurn = createMockTurn({ text: 'Response' });
|
|
384
|
-
const llm = createMockLLM([mockTurn]);
|
|
385
|
-
|
|
386
|
-
const strategy = loop();
|
|
387
|
-
const context: ExecutionContext = {
|
|
388
|
-
agent: { id: 'test-agent' },
|
|
389
|
-
llm,
|
|
390
|
-
input,
|
|
391
|
-
state,
|
|
392
|
-
tools,
|
|
393
|
-
strategy: {},
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
const streamResult = strategy.stream(context);
|
|
397
|
-
const uapEvents: Array<{ source: string; uap?: { type: string } }> = [];
|
|
398
|
-
|
|
399
|
-
for await (const event of streamResult) {
|
|
400
|
-
if (event.source === 'uap') {
|
|
401
|
-
uapEvents.push(event as { source: string; uap?: { type: string } });
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
await streamResult.result;
|
|
406
|
-
|
|
407
|
-
const stepStart = uapEvents.find((e) => e.uap?.type === 'step_start');
|
|
408
|
-
const stepEnd = uapEvents.find((e) => e.uap?.type === 'step_end');
|
|
409
|
-
|
|
410
|
-
expect(stepStart).toBeDefined();
|
|
411
|
-
expect(stepEnd).toBeDefined();
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
test('supports abort()', async () => {
|
|
415
|
-
const mockTurn = createMockTurn({ text: 'Response' });
|
|
416
|
-
const llm = createMockLLM([mockTurn]);
|
|
417
|
-
|
|
418
|
-
const strategy = loop();
|
|
419
|
-
const context: ExecutionContext = {
|
|
420
|
-
agent: { id: 'test-agent' },
|
|
421
|
-
llm,
|
|
422
|
-
input,
|
|
423
|
-
state,
|
|
424
|
-
tools,
|
|
425
|
-
strategy: {},
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
const streamResult = strategy.stream(context);
|
|
429
|
-
|
|
430
|
-
// Abort immediately
|
|
431
|
-
streamResult.abort();
|
|
432
|
-
|
|
433
|
-
// The stream should handle abort gracefully
|
|
434
|
-
// Note: exact behavior depends on timing
|
|
435
|
-
});
|
|
436
|
-
});
|
|
437
|
-
});
|