chatkit-bun 0.0.2

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 (52) hide show
  1. package/README.md +202 -0
  2. package/package.json +40 -0
  3. package/src/actions.ts +39 -0
  4. package/src/agents/accumulate.ts +43 -0
  5. package/src/agents/annotations.ts +157 -0
  6. package/src/agents/context.ts +190 -0
  7. package/src/agents/converter.ts +290 -0
  8. package/src/agents/index.ts +25 -0
  9. package/src/agents/stream.ts +1053 -0
  10. package/src/agents/types.ts +30 -0
  11. package/src/agents/workflows.ts +220 -0
  12. package/src/errors.ts +19 -0
  13. package/src/http.ts +60 -0
  14. package/src/index.ts +11 -0
  15. package/src/serialization.ts +75 -0
  16. package/src/server.ts +874 -0
  17. package/src/sqlite-store.ts +400 -0
  18. package/src/store.ts +98 -0
  19. package/src/types/core.ts +322 -0
  20. package/src/types/server.ts +396 -0
  21. package/src/widgets/components.ts +188 -0
  22. package/src/widgets/diff.ts +151 -0
  23. package/src/widgets/index.ts +6 -0
  24. package/src/widgets/serialization.ts +46 -0
  25. package/src/widgets/stream.ts +104 -0
  26. package/src/widgets/template.ts +180 -0
  27. package/src/widgets/types.ts +52 -0
  28. package/types/actions.d.ts +19 -0
  29. package/types/agents/accumulate.d.ts +4 -0
  30. package/types/agents/annotations.d.ts +21 -0
  31. package/types/agents/context.d.ts +35 -0
  32. package/types/agents/converter.d.ts +60 -0
  33. package/types/agents/index.d.ts +9 -0
  34. package/types/agents/stream.d.ts +4 -0
  35. package/types/agents/types.d.ts +26 -0
  36. package/types/agents/workflows.d.ts +34 -0
  37. package/types/errors.d.ts +11 -0
  38. package/types/http.d.ts +6 -0
  39. package/types/index.d.ts +11 -0
  40. package/types/serialization.d.ts +8 -0
  41. package/types/server.d.ts +73 -0
  42. package/types/sqlite-store.d.ts +43 -0
  43. package/types/store.d.ts +45 -0
  44. package/types/types/core.d.ts +1220 -0
  45. package/types/types/server.d.ts +5841 -0
  46. package/types/widgets/components.d.ts +144 -0
  47. package/types/widgets/diff.d.ts +7 -0
  48. package/types/widgets/index.d.ts +6 -0
  49. package/types/widgets/serialization.d.ts +2 -0
  50. package/types/widgets/stream.d.ts +10 -0
  51. package/types/widgets/template.d.ts +19 -0
  52. package/types/widgets/types.d.ts +24 -0
@@ -0,0 +1,1053 @@
1
+ import {
2
+ InputGuardrailTripwireTriggered,
3
+ OutputGuardrailTripwireTriggered,
4
+ ToolInputGuardrailTripwireTriggered,
5
+ ToolOutputGuardrailTripwireTriggered,
6
+ } from "@openai/agents";
7
+ import type { AssistantMessageContent, ThreadItem } from "../types/core";
8
+ import { ThreadStreamEventSchema, type ThreadStreamEvent } from "../types/server";
9
+ import { convertTextContentPart, defaultResponseStreamConverter } from "./annotations";
10
+ import type { ResponseStreamConverter } from "./annotations";
11
+ import type { AgentContext } from "./context";
12
+ import type { AgentStreamInput, StreamAgentResponseOptions, ToolCallMetadata } from "./types";
13
+ import {
14
+ appendWorkflowTask,
15
+ createReasoningWorkflowItem,
16
+ createThoughtTask,
17
+ finishWorkflow,
18
+ isWorkflowItem,
19
+ persistOpenWorkflow,
20
+ resumeWorkflowFromThreadItems,
21
+ shouldAutoEndWorkflowForItem,
22
+ type ThoughtTask,
23
+ updateWorkflowTaskEvent,
24
+ workflowAddedEvent,
25
+ } from "./workflows";
26
+
27
+ type UnknownRecord = Record<string, unknown>;
28
+
29
+ type GeneratedImageItem = Extract<ThreadItem, { type: "generated_image" }>;
30
+
31
+ interface GeneratedImageState {
32
+ callId: string | null;
33
+ item: GeneratedImageItem;
34
+ }
35
+
36
+ interface StreamingThoughtState {
37
+ itemId: string | null;
38
+ summaryIndex: number;
39
+ task: ThoughtTask;
40
+ }
41
+
42
+ interface AssistantTextState {
43
+ activeItemId: string | null;
44
+ textByPart: Map<string, string>;
45
+ annotationCountByPart: Map<string, number>;
46
+ lastNormalizedTextDelta: {
47
+ itemId: string;
48
+ contentIndex: number;
49
+ delta: string;
50
+ } | null;
51
+ generatedImage: GeneratedImageState | null;
52
+ streamingThought: StreamingThoughtState | null;
53
+ }
54
+
55
+ type StreamSource = "sdk" | "context";
56
+ type RawResponseSource =
57
+ | "direct_response_event"
58
+ | "nested_model_event"
59
+ | "raw_model_stream_event"
60
+ | "raw_response_event";
61
+
62
+ interface RawResponseData {
63
+ data: UnknownRecord;
64
+ source: RawResponseSource;
65
+ }
66
+
67
+ interface TaggedNextResult<T> {
68
+ source: StreamSource;
69
+ result: IteratorResult<T>;
70
+ }
71
+
72
+ interface TaggedNext<T> {
73
+ promise: Promise<TaggedNextResult<T>>;
74
+ result: TaggedNextResult<T> | null;
75
+ error: unknown | null;
76
+ }
77
+
78
+ interface NamedToolCallMetadata {
79
+ name: string;
80
+ metadata: ToolCallMetadata;
81
+ }
82
+
83
+ function isRecord(value: unknown): value is UnknownRecord {
84
+ return typeof value === "object" && value !== null;
85
+ }
86
+
87
+ function isAsyncIterable(value: unknown): value is AsyncIterable<unknown> {
88
+ return (
89
+ isRecord(value) &&
90
+ typeof (value as { [Symbol.asyncIterator]?: unknown })[Symbol.asyncIterator] === "function"
91
+ );
92
+ }
93
+
94
+ function stringValue(value: unknown): string | null {
95
+ return typeof value === "string" ? value : null;
96
+ }
97
+
98
+ function numberValue(value: unknown): number | null {
99
+ return typeof value === "number" && Number.isInteger(value) ? value : null;
100
+ }
101
+
102
+ function firstStringValue(...values: unknown[]): string | null {
103
+ for (const value of values) {
104
+ const text = stringValue(value);
105
+
106
+ if (text !== null) {
107
+ return text;
108
+ }
109
+ }
110
+
111
+ return null;
112
+ }
113
+
114
+ function normalizeStream(streamedRun: AgentStreamInput | AsyncIterable<unknown>): AsyncIterable<unknown> {
115
+ if (isRecord(streamedRun) && typeof streamedRun.toStream === "function") {
116
+ return streamedRun.toStream();
117
+ }
118
+
119
+ if (isAsyncIterable(streamedRun)) {
120
+ return streamedRun;
121
+ }
122
+
123
+ throw new Error("streamAgentResponse requires an async iterable or an object with toStream().");
124
+ }
125
+
126
+ function rawResponseData(event: unknown): RawResponseData | null {
127
+ if (!isRecord(event)) {
128
+ return null;
129
+ }
130
+
131
+ if (
132
+ (event.type === "raw_response_event" || event.type === "raw_model_stream_event") &&
133
+ isRecord(event.data)
134
+ ) {
135
+ if (event.data.type === "model" && isRecord(event.data.event)) {
136
+ const nested = rawResponseData(event.data.event);
137
+ return nested ? { data: nested.data, source: "nested_model_event" } : null;
138
+ }
139
+
140
+ return {
141
+ data: event.data,
142
+ source: event.type === "raw_model_stream_event" ? "raw_model_stream_event" : "raw_response_event",
143
+ };
144
+ }
145
+
146
+ if (typeof event.type === "string" && event.type.startsWith("response.")) {
147
+ return { data: event, source: "direct_response_event" };
148
+ }
149
+
150
+ return null;
151
+ }
152
+
153
+ function partKey(itemId: string, contentIndex: number): string {
154
+ return `${itemId}:${contentIndex}`;
155
+ }
156
+
157
+ function nextAnnotationIndex(state: AssistantTextState, itemId: string, contentIndex: number): number {
158
+ const key = partKey(itemId, contentIndex);
159
+ const index = state.annotationCountByPart.get(key) ?? 0;
160
+ state.annotationCountByPart.set(key, index + 1);
161
+ return index;
162
+ }
163
+
164
+ function clearAssistantTextState(state: AssistantTextState, itemId: string): void {
165
+ for (const key of state.textByPart.keys()) {
166
+ if (key.startsWith(`${itemId}:`)) {
167
+ state.textByPart.delete(key);
168
+ }
169
+ }
170
+
171
+ for (const key of state.annotationCountByPart.keys()) {
172
+ if (key.startsWith(`${itemId}:`)) {
173
+ state.annotationCountByPart.delete(key);
174
+ }
175
+ }
176
+
177
+ if (state.activeItemId === itemId) {
178
+ state.activeItemId = null;
179
+ }
180
+ }
181
+
182
+ function assistantItem<TContext>(
183
+ context: AgentContext<TContext>,
184
+ itemId: string,
185
+ content: AssistantMessageContent[],
186
+ ): Extract<ThreadStreamEvent, { type: "thread.item.added" }>["item"] {
187
+ return {
188
+ id: itemId,
189
+ thread_id: context.thread.id,
190
+ created_at: context.createdAt(),
191
+ type: "assistant_message",
192
+ content,
193
+ };
194
+ }
195
+
196
+ function generatedImageItem<TContext>(
197
+ context: AgentContext<TContext>,
198
+ itemId: string,
199
+ image: GeneratedImageItem["image"],
200
+ ): GeneratedImageItem {
201
+ return {
202
+ id: itemId,
203
+ thread_id: context.thread.id,
204
+ created_at: context.createdAt(),
205
+ type: "generated_image",
206
+ image,
207
+ };
208
+ }
209
+
210
+ function matchingStreamingThought(
211
+ state: AssistantTextState,
212
+ itemId: string | null,
213
+ summaryIndex: number,
214
+ ): StreamingThoughtState | null {
215
+ const streamingThought = state.streamingThought;
216
+
217
+ if (
218
+ streamingThought &&
219
+ streamingThought.itemId === itemId &&
220
+ streamingThought.summaryIndex === summaryIndex
221
+ ) {
222
+ return streamingThought;
223
+ }
224
+
225
+ return null;
226
+ }
227
+
228
+ function assistantContentFromItem(
229
+ item: UnknownRecord,
230
+ fallbackText: string,
231
+ converter: ResponseStreamConverter,
232
+ ): AssistantMessageContent[] {
233
+ const rawContent = Array.isArray(item.content) ? item.content : [];
234
+ const content = rawContent.flatMap((part) => {
235
+ const converted = convertTextContentPart(part, converter);
236
+ return converted ? [converted] : [];
237
+ });
238
+
239
+ if (content.length > 0) {
240
+ return content;
241
+ }
242
+
243
+ return fallbackText.length > 0
244
+ ? [{ type: "output_text", text: fallbackText, annotations: [] }]
245
+ : [];
246
+ }
247
+
248
+ function firstAssistantMessageOutput(response: UnknownRecord): UnknownRecord | null {
249
+ const output = Array.isArray(response.output) ? response.output : [];
250
+
251
+ for (const item of output) {
252
+ if (
253
+ isRecord(item) &&
254
+ item.type === "message" &&
255
+ (item.role === undefined || item.role === "assistant")
256
+ ) {
257
+ return item;
258
+ }
259
+ }
260
+
261
+ return null;
262
+ }
263
+
264
+ function ensureAssistantMessageAdded<TContext>(
265
+ context: AgentContext<TContext>,
266
+ state: AssistantTextState,
267
+ ): { itemId: string; events: ThreadStreamEvent[] } {
268
+ const itemId = context.store.generateItemId("message", context.thread, context.context);
269
+
270
+ return {
271
+ itemId,
272
+ events: assistantMessageAddedEvents(context, state, itemId),
273
+ };
274
+ }
275
+
276
+ function assistantMessageAddedEvents<TContext>(
277
+ context: AgentContext<TContext>,
278
+ state: AssistantTextState,
279
+ itemId: string,
280
+ content: AssistantMessageContent[] = [],
281
+ ): ThreadStreamEvent[] {
282
+ state.activeItemId = itemId;
283
+ const events: ThreadStreamEvent[] = [];
284
+ const workflowDone = finishWorkflow(context);
285
+
286
+ if (workflowDone) {
287
+ events.push(workflowDone);
288
+ }
289
+
290
+ events.push({
291
+ type: "thread.item.added",
292
+ item: assistantItem(context, itemId, content),
293
+ });
294
+
295
+ return events;
296
+ }
297
+
298
+ function toolCallName(item: UnknownRecord, rawItem: UnknownRecord): string | null {
299
+ return firstStringValue(
300
+ rawItem.name,
301
+ rawItem.toolName,
302
+ rawItem.tool_name,
303
+ item.toolName,
304
+ item.tool_name,
305
+ item.name,
306
+ );
307
+ }
308
+
309
+ function toolCallMetadata(event: unknown): NamedToolCallMetadata | null {
310
+ if (!isRecord(event) || event.type !== "run_item_stream_event" || !isRecord(event.item)) {
311
+ return null;
312
+ }
313
+
314
+ const item = event.item;
315
+
316
+ if (item.type !== "tool_call_item") {
317
+ return null;
318
+ }
319
+
320
+ const rawItem = isRecord(item.raw_item)
321
+ ? item.raw_item
322
+ : isRecord(item.rawItem)
323
+ ? item.rawItem
324
+ : item;
325
+
326
+ const name = toolCallName(item, rawItem);
327
+
328
+ if (name === null) {
329
+ return null;
330
+ }
331
+
332
+ return {
333
+ name,
334
+ metadata: {
335
+ itemId: firstStringValue(rawItem.id, item.id),
336
+ callId: firstStringValue(rawItem.call_id, rawItem.callId, item.call_id, item.callId),
337
+ },
338
+ };
339
+ }
340
+
341
+ function tagNext<T>(source: StreamSource, promise: PromiseLike<IteratorResult<T>>): TaggedNext<T> {
342
+ const tagged: TaggedNext<T> = {
343
+ promise: Promise.resolve(null as never),
344
+ result: null,
345
+ error: null,
346
+ };
347
+
348
+ tagged.promise = Promise.resolve(promise).then(
349
+ (result) => {
350
+ const taggedResult = { source, result };
351
+ tagged.result = taggedResult;
352
+ return taggedResult;
353
+ },
354
+ (error: unknown) => {
355
+ tagged.error = error;
356
+ throw error;
357
+ },
358
+ );
359
+ tagged.promise.catch(() => undefined);
360
+
361
+ return tagged;
362
+ }
363
+
364
+ function isGuardrailTripwire(error: unknown): boolean {
365
+ return (
366
+ error instanceof InputGuardrailTripwireTriggered ||
367
+ error instanceof OutputGuardrailTripwireTriggered ||
368
+ error instanceof ToolInputGuardrailTripwireTriggered ||
369
+ error instanceof ToolOutputGuardrailTripwireTriggered
370
+ );
371
+ }
372
+
373
+ function trackProducedItemId(
374
+ producedItemIds: Set<string>,
375
+ existingItemIds: ReadonlySet<string>,
376
+ event: ThreadStreamEvent,
377
+ ): void {
378
+ if (event.type === "thread.item.added") {
379
+ producedItemIds.add(event.item.id);
380
+ return;
381
+ }
382
+
383
+ if (
384
+ event.type === "thread.item.done" &&
385
+ (!existingItemIds.has(event.item.id) || producedItemIds.has(event.item.id))
386
+ ) {
387
+ producedItemIds.add(event.item.id);
388
+ }
389
+ }
390
+
391
+ function parseAndTrackProducedItem(
392
+ producedItemIds: Set<string>,
393
+ existingItemIds: ReadonlySet<string>,
394
+ event: ThreadStreamEvent,
395
+ ): ThreadStreamEvent {
396
+ const parsedEvent = ThreadStreamEventSchema.parse(event);
397
+ trackProducedItemId(producedItemIds, existingItemIds, parsedEvent);
398
+ return parsedEvent;
399
+ }
400
+
401
+ function rollbackProducedItemEvents(producedItemIds: ReadonlySet<string>): ThreadStreamEvent[] {
402
+ return [...producedItemIds].map((itemId) =>
403
+ ThreadStreamEventSchema.parse({ type: "thread.item.removed", item_id: itemId }),
404
+ );
405
+ }
406
+
407
+ async function returnIterator<T>(iterator: AsyncIterator<T>): Promise<void> {
408
+ try {
409
+ await iterator.return?.();
410
+ } catch {
411
+ // Iterator cleanup is best-effort and must not mask the stream error.
412
+ }
413
+ }
414
+
415
+ async function convertSdkEvent<TContext>(
416
+ context: AgentContext<TContext>,
417
+ state: AssistantTextState,
418
+ event: unknown,
419
+ converter: ResponseStreamConverter,
420
+ ): Promise<ThreadStreamEvent[]> {
421
+ const rawResponse = rawResponseData(event);
422
+
423
+ if (!rawResponse) {
424
+ return [];
425
+ }
426
+
427
+ const { data: rawData, source } = rawResponse;
428
+ if (
429
+ rawData.type !== "output_text_delta" &&
430
+ !(rawData.type === "response.output_text.delta" && source === "nested_model_event")
431
+ ) {
432
+ state.lastNormalizedTextDelta = null;
433
+ }
434
+
435
+ switch (rawData.type) {
436
+ case "output_text_delta": {
437
+ const events: ThreadStreamEvent[] = [];
438
+ const explicitItemId = stringValue(rawData.item_id);
439
+ let itemId = explicitItemId ?? state.activeItemId;
440
+
441
+ if (!itemId) {
442
+ const added = ensureAssistantMessageAdded(context, state);
443
+ events.push(...added.events);
444
+ itemId = added.itemId;
445
+ }
446
+ state.activeItemId ??= itemId;
447
+
448
+ const contentIndex = numberValue(rawData.content_index) ?? 0;
449
+ const delta = stringValue(rawData.delta) ?? "";
450
+ const key = partKey(itemId, contentIndex);
451
+ state.textByPart.set(key, `${state.textByPart.get(key) ?? ""}${delta}`);
452
+ state.lastNormalizedTextDelta = { itemId, contentIndex, delta };
453
+
454
+ events.push({
455
+ type: "thread.item.updated",
456
+ item_id: itemId,
457
+ update: {
458
+ type: "assistant_message.content_part.text_delta",
459
+ content_index: contentIndex,
460
+ delta,
461
+ },
462
+ });
463
+
464
+ return events;
465
+ }
466
+
467
+ case "response_done": {
468
+ const response = isRecord(rawData.response) ? rawData.response : rawData;
469
+ const item = firstAssistantMessageOutput(response);
470
+ if (!item) {
471
+ return [];
472
+ }
473
+
474
+ const itemId =
475
+ state.activeItemId ??
476
+ stringValue(item.id) ??
477
+ stringValue(response.id) ??
478
+ context.store.generateItemId("message", context.thread, context.context);
479
+ const fallbackText = state.textByPart.get(partKey(itemId, 0)) ?? "";
480
+ const doneEvent: ThreadStreamEvent = {
481
+ type: "thread.item.done",
482
+ item: assistantItem(
483
+ context,
484
+ itemId,
485
+ assistantContentFromItem(item, fallbackText, converter),
486
+ ),
487
+ };
488
+
489
+ clearAssistantTextState(state, itemId);
490
+
491
+ return [doneEvent];
492
+ }
493
+
494
+ case "response.output_item.added": {
495
+ const item = isRecord(rawData.item) ? rawData.item : null;
496
+
497
+ if (!item) {
498
+ return [];
499
+ }
500
+
501
+ if (item.type === "reasoning") {
502
+ if (context.workflowItem) {
503
+ return [];
504
+ }
505
+
506
+ const workflow = createReasoningWorkflowItem(context);
507
+ context.workflowItem = workflow;
508
+
509
+ return [workflowAddedEvent(workflow)];
510
+ }
511
+
512
+ if (item.type === "image_generation_call") {
513
+ const callId = stringValue(item.id);
514
+ const itemId = context.store.generateItemId("message", context.thread, context.context);
515
+ const generated = generatedImageItem(context, itemId, null);
516
+ state.generatedImage = { callId, item: generated };
517
+
518
+ return [
519
+ {
520
+ type: "thread.item.added",
521
+ item: generated,
522
+ },
523
+ ];
524
+ }
525
+
526
+ if (item.type !== "message") {
527
+ return [];
528
+ }
529
+
530
+ const itemId =
531
+ stringValue(item.id) ?? context.store.generateItemId("message", context.thread, context.context);
532
+ const content = assistantContentFromItem(item, "", converter);
533
+ return assistantMessageAddedEvents(context, state, itemId, content);
534
+ }
535
+
536
+ case "response.content_part.added": {
537
+ const itemId = stringValue(rawData.item_id) ?? state.activeItemId;
538
+ if (!itemId) {
539
+ return [];
540
+ }
541
+
542
+ const part = rawData.part;
543
+ if (isRecord(part) && part.type === "reasoning_text") {
544
+ return [];
545
+ }
546
+
547
+ const content = convertTextContentPart(part, converter);
548
+ if (!content) {
549
+ return [];
550
+ }
551
+
552
+ return [
553
+ {
554
+ type: "thread.item.updated",
555
+ item_id: itemId,
556
+ update: {
557
+ type: "assistant_message.content_part.added",
558
+ content_index: numberValue(rawData.content_index) ?? 0,
559
+ content,
560
+ },
561
+ },
562
+ ];
563
+ }
564
+
565
+ case "response.output_text.delta":
566
+ case "response.refusal.delta": {
567
+ const itemId = stringValue(rawData.item_id) ?? state.activeItemId;
568
+
569
+ if (!itemId) {
570
+ return [];
571
+ }
572
+
573
+ const contentIndex = numberValue(rawData.content_index) ?? 0;
574
+ const delta = stringValue(rawData.delta) ?? "";
575
+ const key = partKey(itemId, contentIndex);
576
+ const previousNormalizedDelta = state.lastNormalizedTextDelta;
577
+ if (
578
+ rawData.type === "response.output_text.delta" &&
579
+ source === "nested_model_event" &&
580
+ previousNormalizedDelta &&
581
+ previousNormalizedDelta.itemId === itemId &&
582
+ previousNormalizedDelta.contentIndex === contentIndex &&
583
+ previousNormalizedDelta.delta === delta
584
+ ) {
585
+ state.lastNormalizedTextDelta = null;
586
+ return [];
587
+ }
588
+
589
+ state.lastNormalizedTextDelta = null;
590
+ state.textByPart.set(key, `${state.textByPart.get(key) ?? ""}${delta}`);
591
+
592
+ return [
593
+ {
594
+ type: "thread.item.updated",
595
+ item_id: itemId,
596
+ update: {
597
+ type: "assistant_message.content_part.text_delta",
598
+ content_index: contentIndex,
599
+ delta,
600
+ },
601
+ },
602
+ ];
603
+ }
604
+
605
+ case "response.refusal.done": {
606
+ const itemId = stringValue(rawData.item_id) ?? state.activeItemId;
607
+
608
+ if (!itemId) {
609
+ return [];
610
+ }
611
+
612
+ const contentIndex = numberValue(rawData.content_index) ?? 0;
613
+ const text = stringValue(rawData.refusal) ?? state.textByPart.get(partKey(itemId, contentIndex)) ?? "";
614
+
615
+ return [
616
+ {
617
+ type: "thread.item.updated",
618
+ item_id: itemId,
619
+ update: {
620
+ type: "assistant_message.content_part.done",
621
+ content_index: contentIndex,
622
+ content: { type: "output_text", text, annotations: [] },
623
+ },
624
+ },
625
+ ];
626
+ }
627
+
628
+ case "response.reasoning_summary_text.delta": {
629
+ const workflow = context.workflowItem;
630
+ const itemId = stringValue(rawData.item_id);
631
+ const summaryIndex = numberValue(rawData.summary_index);
632
+ const delta = stringValue(rawData.delta) ?? "";
633
+
634
+ if (!workflow || summaryIndex === null) {
635
+ return [];
636
+ }
637
+
638
+ if (workflow.workflow.type === "reasoning" && workflow.workflow.tasks.length === 0) {
639
+ const task = createThoughtTask(delta);
640
+ const event = appendWorkflowTask(workflow, task);
641
+ state.streamingThought = { itemId, summaryIndex, task: workflow.workflow.tasks[0] as ThoughtTask };
642
+
643
+ return [event];
644
+ }
645
+
646
+ const streamingThought = matchingStreamingThought(state, itemId, summaryIndex);
647
+
648
+ if (!streamingThought) {
649
+ return [];
650
+ }
651
+
652
+ streamingThought.task.content += delta;
653
+ const taskIndex = workflow.workflow.tasks.indexOf(streamingThought.task);
654
+
655
+ if (taskIndex < 0) {
656
+ return [];
657
+ }
658
+
659
+ const event = updateWorkflowTaskEvent(workflow, streamingThought.task, taskIndex);
660
+ streamingThought.task = workflow.workflow.tasks[taskIndex] as ThoughtTask;
661
+
662
+ return [event];
663
+ }
664
+
665
+ case "response.reasoning_summary_text.done": {
666
+ const workflow = context.workflowItem;
667
+ const itemId = stringValue(rawData.item_id);
668
+ const summaryIndex = numberValue(rawData.summary_index);
669
+ const text = stringValue(rawData.text) ?? "";
670
+
671
+ if (!workflow || summaryIndex === null) {
672
+ return [];
673
+ }
674
+
675
+ const streamingThought = matchingStreamingThought(state, itemId, summaryIndex);
676
+
677
+ if (streamingThought) {
678
+ streamingThought.task.content = text;
679
+ state.streamingThought = null;
680
+ const taskIndex = workflow.workflow.tasks.indexOf(streamingThought.task);
681
+
682
+ if (taskIndex < 0) {
683
+ return [];
684
+ }
685
+
686
+ return [updateWorkflowTaskEvent(workflow, streamingThought.task, taskIndex)];
687
+ }
688
+
689
+ const task = createThoughtTask(text);
690
+ return [appendWorkflowTask(workflow, task)];
691
+ }
692
+
693
+ case "response.image_generation_call.partial_image": {
694
+ const imageId = stringValue(rawData.item_id);
695
+ const base64Image = stringValue(rawData.partial_image_b64);
696
+ const partialImageIndex = numberValue(rawData.partial_image_index);
697
+ const generatedImage = state.generatedImage;
698
+
699
+ if (!generatedImage || !imageId || !base64Image || partialImageIndex === null) {
700
+ return [];
701
+ }
702
+
703
+ if (generatedImage.callId !== null && imageId !== generatedImage.callId) {
704
+ return [];
705
+ }
706
+
707
+ const image = {
708
+ id: imageId,
709
+ url: await converter.base64ImageToUrl(imageId, base64Image, partialImageIndex),
710
+ };
711
+ state.generatedImage = { ...generatedImage, item: { ...generatedImage.item, image } };
712
+
713
+ return [
714
+ {
715
+ type: "thread.item.updated",
716
+ item_id: generatedImage.item.id,
717
+ update: {
718
+ type: "generated_image.updated",
719
+ image,
720
+ progress: converter.partialImageIndexToProgress(partialImageIndex),
721
+ },
722
+ },
723
+ ];
724
+ }
725
+
726
+ case "response.output_text.annotation.added": {
727
+ const itemId = stringValue(rawData.item_id) ?? state.activeItemId;
728
+ if (!itemId) {
729
+ return [];
730
+ }
731
+
732
+ const annotation = converter.convertAnnotation(rawData.annotation);
733
+ if (!annotation) {
734
+ return [];
735
+ }
736
+
737
+ const contentIndex = numberValue(rawData.content_index) ?? 0;
738
+
739
+ return [
740
+ {
741
+ type: "thread.item.updated",
742
+ item_id: itemId,
743
+ update: {
744
+ type: "assistant_message.content_part.annotation_added",
745
+ content_index: contentIndex,
746
+ annotation_index: nextAnnotationIndex(state, itemId, contentIndex),
747
+ annotation,
748
+ },
749
+ },
750
+ ];
751
+ }
752
+
753
+ case "response.output_text.done": {
754
+ const itemId = stringValue(rawData.item_id) ?? state.activeItemId;
755
+
756
+ if (!itemId) {
757
+ return [];
758
+ }
759
+
760
+ const contentIndex = numberValue(rawData.content_index) ?? 0;
761
+ const text = stringValue(rawData.text) ?? state.textByPart.get(partKey(itemId, contentIndex)) ?? "";
762
+
763
+ return [
764
+ {
765
+ type: "thread.item.updated",
766
+ item_id: itemId,
767
+ update: {
768
+ type: "assistant_message.content_part.done",
769
+ content_index: contentIndex,
770
+ content: { type: "output_text", text, annotations: [] },
771
+ },
772
+ },
773
+ ];
774
+ }
775
+
776
+ case "response.output_item.done": {
777
+ const item = isRecord(rawData.item) ? rawData.item : null;
778
+
779
+ if (!item) {
780
+ return [];
781
+ }
782
+
783
+ if (item.type === "image_generation_call") {
784
+ const imageId = stringValue(item.id);
785
+ const result = stringValue(item.result);
786
+ const generatedImage = state.generatedImage;
787
+
788
+ if (!generatedImage) {
789
+ return [];
790
+ }
791
+
792
+ if (generatedImage.callId !== null && imageId !== generatedImage.callId) {
793
+ return [];
794
+ }
795
+
796
+ if (!result) {
797
+ state.generatedImage = null;
798
+ return [];
799
+ }
800
+
801
+ if (!imageId) {
802
+ state.generatedImage = null;
803
+ return [];
804
+ }
805
+
806
+ const image = {
807
+ id: imageId,
808
+ url: await converter.base64ImageToUrl(imageId, result, null),
809
+ };
810
+ const doneItem = { ...generatedImage.item, image };
811
+ state.generatedImage = null;
812
+
813
+ return [
814
+ {
815
+ type: "thread.item.done",
816
+ item: doneItem,
817
+ },
818
+ ];
819
+ }
820
+
821
+ if (item.type !== "message") {
822
+ return [];
823
+ }
824
+
825
+ const itemId = stringValue(item.id) ?? state.activeItemId;
826
+
827
+ if (!itemId) {
828
+ return [];
829
+ }
830
+
831
+ const fallbackText = state.textByPart.get(partKey(itemId, 0)) ?? "";
832
+
833
+ return [
834
+ {
835
+ type: "thread.item.done",
836
+ item: assistantItem(context, itemId, assistantContentFromItem(item, fallbackText, converter)),
837
+ },
838
+ ];
839
+ }
840
+
841
+ default:
842
+ return [];
843
+ }
844
+ }
845
+
846
+ function pendingClientToolCallEvent<TContext>(
847
+ context: AgentContext<TContext>,
848
+ metadataByToolName: ReadonlyMap<string, ToolCallMetadata>,
849
+ ): ThreadStreamEvent | null {
850
+ const toolCall = context.getClientToolCall();
851
+
852
+ if (!toolCall) {
853
+ return null;
854
+ }
855
+
856
+ const metadata = metadataByToolName.get(toolCall.name) ?? null;
857
+ const id =
858
+ metadata?.itemId ?? context.store.generateItemId("tool_call", context.thread, context.context);
859
+
860
+ return {
861
+ type: "thread.item.done",
862
+ item: {
863
+ id,
864
+ thread_id: context.thread.id,
865
+ created_at: context.createdAt(),
866
+ type: "client_tool_call",
867
+ status: "pending",
868
+ call_id: metadata?.callId ?? id,
869
+ name: toolCall.name,
870
+ arguments: toolCall.arguments,
871
+ },
872
+ };
873
+ }
874
+
875
+ function contextEventsWithWorkflowLifecycle<TContext>(
876
+ context: AgentContext<TContext>,
877
+ event: ThreadStreamEvent,
878
+ ): ThreadStreamEvent[] {
879
+ if (event.type !== "thread.item.added" && event.type !== "thread.item.done") {
880
+ return [event];
881
+ }
882
+
883
+ const events: ThreadStreamEvent[] = [];
884
+
885
+ if (shouldAutoEndWorkflowForItem(context, event.item)) {
886
+ const workflowDone = finishWorkflow(context);
887
+
888
+ if (workflowDone) {
889
+ events.push(workflowDone);
890
+ }
891
+ }
892
+
893
+ if (
894
+ event.type === "thread.item.added" &&
895
+ isWorkflowItem(event.item) &&
896
+ context.workflowItem?.id !== event.item.id
897
+ ) {
898
+ context.workflowItem = event.item;
899
+ }
900
+
901
+ if (
902
+ event.type === "thread.item.done" &&
903
+ isWorkflowItem(event.item) &&
904
+ context.workflowItem?.id === event.item.id
905
+ ) {
906
+ context.workflowItem = null;
907
+ }
908
+
909
+ events.push(event);
910
+ return events;
911
+ }
912
+
913
+ export async function* streamAgentResponse<TContext>(
914
+ context: AgentContext<TContext>,
915
+ streamedRun: AgentStreamInput | AsyncIterable<unknown>,
916
+ options: StreamAgentResponseOptions = {},
917
+ ): AsyncIterable<ThreadStreamEvent> {
918
+ const converter = options.converter ?? defaultResponseStreamConverter;
919
+ const recentItems = await context.store.loadThreadItems(
920
+ context.thread.id,
921
+ null,
922
+ 2,
923
+ "desc",
924
+ context.context,
925
+ );
926
+ resumeWorkflowFromThreadItems(context, recentItems.data);
927
+ const existingItemIds = new Set(recentItems.data.map((item) => item.id));
928
+
929
+ const sdkIterator = normalizeStream(streamedRun)[Symbol.asyncIterator]();
930
+ const contextIterator = context.events()[Symbol.asyncIterator]();
931
+ const state: AssistantTextState = {
932
+ activeItemId: null,
933
+ textByPart: new Map(),
934
+ annotationCountByPart: new Map(),
935
+ lastNormalizedTextDelta: null,
936
+ generatedImage: null,
937
+ streamingThought: null,
938
+ };
939
+ const toolCallMetadataByName = new Map<string, ToolCallMetadata>();
940
+ const producedItemIds = new Set<string>();
941
+ let sdkDone = false;
942
+ let contextDone = false;
943
+ let sdkNext = tagNext("sdk", sdkIterator.next());
944
+ let contextNext = tagNext("context", contextIterator.next());
945
+
946
+ try {
947
+ while (!sdkDone || !contextDone) {
948
+ await Promise.resolve();
949
+
950
+ if (!sdkDone && isGuardrailTripwire(sdkNext.error)) {
951
+ throw sdkNext.error;
952
+ }
953
+
954
+ if (!contextDone && contextNext.result) {
955
+ if (contextNext.result.result.done) {
956
+ contextDone = true;
957
+ } else {
958
+ const value = contextNext.result.result.value;
959
+ contextNext = tagNext("context", contextIterator.next());
960
+ for (const event of contextEventsWithWorkflowLifecycle(context, value)) {
961
+ yield parseAndTrackProducedItem(producedItemIds, existingItemIds, event);
962
+ }
963
+ }
964
+ continue;
965
+ }
966
+
967
+ const contenders: Array<Promise<TaggedNextResult<unknown>>> = [];
968
+
969
+ if (!contextDone) {
970
+ contenders.push(contextNext.promise);
971
+ }
972
+
973
+ if (!sdkDone) {
974
+ contenders.push(sdkNext.promise);
975
+ }
976
+
977
+ if (contenders.length === 0) {
978
+ break;
979
+ }
980
+
981
+ const next = await Promise.race(contenders);
982
+
983
+ if (next.source === "sdk" && !contextDone) {
984
+ await Promise.resolve();
985
+
986
+ if (contextNext.result) {
987
+ if (contextNext.result.result.done) {
988
+ contextDone = true;
989
+ } else {
990
+ const value = contextNext.result.result.value;
991
+ contextNext = tagNext("context", contextIterator.next());
992
+ for (const event of contextEventsWithWorkflowLifecycle(context, value)) {
993
+ yield parseAndTrackProducedItem(producedItemIds, existingItemIds, event);
994
+ }
995
+ continue;
996
+ }
997
+ }
998
+ }
999
+
1000
+ if (next.source === "context") {
1001
+ if (next.result.done) {
1002
+ contextDone = true;
1003
+ } else {
1004
+ contextNext = tagNext("context", contextIterator.next());
1005
+ const value = next.result.value as ThreadStreamEvent;
1006
+
1007
+ for (const event of contextEventsWithWorkflowLifecycle(context, value)) {
1008
+ yield parseAndTrackProducedItem(producedItemIds, existingItemIds, event);
1009
+ }
1010
+ }
1011
+ continue;
1012
+ }
1013
+
1014
+ if (next.result.done) {
1015
+ sdkDone = true;
1016
+ context.closeEvents();
1017
+ continue;
1018
+ }
1019
+
1020
+ sdkNext = tagNext("sdk", sdkIterator.next());
1021
+ const metadata = toolCallMetadata(next.result.value);
1022
+
1023
+ if (metadata) {
1024
+ toolCallMetadataByName.set(metadata.name, metadata.metadata);
1025
+ }
1026
+
1027
+ for (const event of await convertSdkEvent(context, state, next.result.value, converter)) {
1028
+ yield parseAndTrackProducedItem(producedItemIds, existingItemIds, event);
1029
+ }
1030
+ }
1031
+
1032
+ await persistOpenWorkflow(context);
1033
+ const clientToolCallEvent = pendingClientToolCallEvent(context, toolCallMetadataByName);
1034
+
1035
+ if (clientToolCallEvent) {
1036
+ yield parseAndTrackProducedItem(producedItemIds, existingItemIds, clientToolCallEvent);
1037
+ }
1038
+ } catch (error) {
1039
+ if (!isGuardrailTripwire(error)) {
1040
+ throw error;
1041
+ }
1042
+
1043
+ for (const event of rollbackProducedItemEvents(producedItemIds)) {
1044
+ yield event;
1045
+ }
1046
+
1047
+ throw error;
1048
+ } finally {
1049
+ context.closeEvents();
1050
+ await returnIterator(sdkIterator);
1051
+ await returnIterator(contextIterator);
1052
+ }
1053
+ }