@librechat/agents 3.1.85 → 3.1.87

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 (166) hide show
  1. package/README.md +69 -0
  2. package/dist/cjs/agents/AgentContext.cjs +7 -2
  3. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  4. package/dist/cjs/events.cjs +23 -0
  5. package/dist/cjs/events.cjs.map +1 -1
  6. package/dist/cjs/graphs/Graph.cjs +133 -18
  7. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs +1 -1
  9. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  10. package/dist/cjs/llm/anthropic/index.cjs +251 -53
  11. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  12. package/dist/cjs/llm/init.cjs +1 -5
  13. package/dist/cjs/llm/init.cjs.map +1 -1
  14. package/dist/cjs/llm/openai/index.cjs +113 -24
  15. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  16. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  17. package/dist/cjs/llm/openrouter/index.cjs +3 -1
  18. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  19. package/dist/cjs/main.cjs +18 -5
  20. package/dist/cjs/main.cjs.map +1 -1
  21. package/dist/cjs/openai/index.cjs +253 -0
  22. package/dist/cjs/openai/index.cjs.map +1 -0
  23. package/dist/cjs/responses/index.cjs +448 -0
  24. package/dist/cjs/responses/index.cjs.map +1 -0
  25. package/dist/cjs/run.cjs +108 -7
  26. package/dist/cjs/run.cjs.map +1 -1
  27. package/dist/cjs/session/AgentSession.cjs +1057 -0
  28. package/dist/cjs/session/AgentSession.cjs.map +1 -0
  29. package/dist/cjs/session/JsonlSessionStore.cjs +425 -0
  30. package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -0
  31. package/dist/cjs/session/handlers.cjs +221 -0
  32. package/dist/cjs/session/handlers.cjs.map +1 -0
  33. package/dist/cjs/session/ids.cjs +22 -0
  34. package/dist/cjs/session/ids.cjs.map +1 -0
  35. package/dist/cjs/session/messageSerialization.cjs +179 -0
  36. package/dist/cjs/session/messageSerialization.cjs.map +1 -0
  37. package/dist/cjs/stream.cjs +472 -11
  38. package/dist/cjs/stream.cjs.map +1 -1
  39. package/dist/cjs/summarization/node.cjs +1 -1
  40. package/dist/cjs/summarization/node.cjs.map +1 -1
  41. package/dist/cjs/tools/ToolNode.cjs +177 -59
  42. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  43. package/dist/cjs/tools/eagerEventExecution.cjs +113 -0
  44. package/dist/cjs/tools/eagerEventExecution.cjs.map +1 -0
  45. package/dist/cjs/tools/handlers.cjs +1 -1
  46. package/dist/cjs/tools/handlers.cjs.map +1 -1
  47. package/dist/cjs/tools/streamedToolCallSeals.cjs +42 -0
  48. package/dist/cjs/tools/streamedToolCallSeals.cjs.map +1 -0
  49. package/dist/esm/agents/AgentContext.mjs +7 -2
  50. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  51. package/dist/esm/events.mjs +23 -1
  52. package/dist/esm/events.mjs.map +1 -1
  53. package/dist/esm/graphs/Graph.mjs +133 -18
  54. package/dist/esm/graphs/Graph.mjs.map +1 -1
  55. package/dist/esm/graphs/MultiAgentGraph.mjs +1 -1
  56. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  57. package/dist/esm/llm/anthropic/index.mjs +251 -53
  58. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  59. package/dist/esm/llm/init.mjs +1 -5
  60. package/dist/esm/llm/init.mjs.map +1 -1
  61. package/dist/esm/llm/openai/index.mjs +113 -25
  62. package/dist/esm/llm/openai/index.mjs.map +1 -1
  63. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  64. package/dist/esm/llm/openrouter/index.mjs +4 -2
  65. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  66. package/dist/esm/main.mjs +5 -1
  67. package/dist/esm/main.mjs.map +1 -1
  68. package/dist/esm/openai/index.mjs +246 -0
  69. package/dist/esm/openai/index.mjs.map +1 -0
  70. package/dist/esm/responses/index.mjs +440 -0
  71. package/dist/esm/responses/index.mjs.map +1 -0
  72. package/dist/esm/run.mjs +108 -7
  73. package/dist/esm/run.mjs.map +1 -1
  74. package/dist/esm/session/AgentSession.mjs +1054 -0
  75. package/dist/esm/session/AgentSession.mjs.map +1 -0
  76. package/dist/esm/session/JsonlSessionStore.mjs +422 -0
  77. package/dist/esm/session/JsonlSessionStore.mjs.map +1 -0
  78. package/dist/esm/session/handlers.mjs +219 -0
  79. package/dist/esm/session/handlers.mjs.map +1 -0
  80. package/dist/esm/session/ids.mjs +17 -0
  81. package/dist/esm/session/ids.mjs.map +1 -0
  82. package/dist/esm/session/messageSerialization.mjs +173 -0
  83. package/dist/esm/session/messageSerialization.mjs.map +1 -0
  84. package/dist/esm/stream.mjs +473 -12
  85. package/dist/esm/stream.mjs.map +1 -1
  86. package/dist/esm/summarization/node.mjs +1 -1
  87. package/dist/esm/summarization/node.mjs.map +1 -1
  88. package/dist/esm/tools/ToolNode.mjs +177 -59
  89. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  90. package/dist/esm/tools/eagerEventExecution.mjs +107 -0
  91. package/dist/esm/tools/eagerEventExecution.mjs.map +1 -0
  92. package/dist/esm/tools/handlers.mjs +1 -1
  93. package/dist/esm/tools/handlers.mjs.map +1 -1
  94. package/dist/esm/tools/streamedToolCallSeals.mjs +36 -0
  95. package/dist/esm/tools/streamedToolCallSeals.mjs.map +1 -0
  96. package/dist/types/events.d.ts +1 -0
  97. package/dist/types/graphs/Graph.d.ts +24 -9
  98. package/dist/types/index.d.ts +1 -0
  99. package/dist/types/llm/openai/index.d.ts +1 -0
  100. package/dist/types/openai/index.d.ts +75 -0
  101. package/dist/types/responses/index.d.ts +97 -0
  102. package/dist/types/run.d.ts +2 -0
  103. package/dist/types/session/AgentSession.d.ts +32 -0
  104. package/dist/types/session/JsonlSessionStore.d.ts +67 -0
  105. package/dist/types/session/handlers.d.ts +8 -0
  106. package/dist/types/session/ids.d.ts +4 -0
  107. package/dist/types/session/index.d.ts +5 -0
  108. package/dist/types/session/messageSerialization.d.ts +7 -0
  109. package/dist/types/session/types.d.ts +191 -0
  110. package/dist/types/tools/ToolNode.d.ts +12 -1
  111. package/dist/types/tools/eagerEventExecution.d.ts +23 -0
  112. package/dist/types/tools/streamedToolCallSeals.d.ts +13 -0
  113. package/dist/types/types/hitl.d.ts +4 -0
  114. package/dist/types/types/run.d.ts +11 -1
  115. package/dist/types/types/tools.d.ts +36 -0
  116. package/package.json +19 -2
  117. package/src/__tests__/stream.eagerEventExecution.test.ts +2458 -0
  118. package/src/agents/AgentContext.ts +7 -2
  119. package/src/agents/__tests__/AgentContext.test.ts +254 -5
  120. package/src/events.ts +29 -0
  121. package/src/graphs/Graph.ts +224 -50
  122. package/src/graphs/MultiAgentGraph.ts +1 -1
  123. package/src/graphs/__tests__/composition.smoke.test.ts +30 -0
  124. package/src/index.ts +3 -0
  125. package/src/llm/anthropic/index.ts +356 -84
  126. package/src/llm/anthropic/llm.spec.ts +64 -0
  127. package/src/llm/custom-chat-models.smoke.test.ts +175 -4
  128. package/src/llm/openai/contentBlocks.test.ts +35 -0
  129. package/src/llm/openai/deepseek.test.ts +201 -2
  130. package/src/llm/openai/index.ts +171 -26
  131. package/src/llm/openai/utils/index.ts +22 -0
  132. package/src/llm/openrouter/index.ts +4 -2
  133. package/src/openai/__tests__/openai.test.ts +337 -0
  134. package/src/openai/index.ts +404 -0
  135. package/src/responses/__tests__/responses.test.ts +652 -0
  136. package/src/responses/index.ts +677 -0
  137. package/src/run.ts +158 -8
  138. package/src/scripts/compare_pi_vs_ours.ts +592 -173
  139. package/src/scripts/session_live.ts +548 -0
  140. package/src/session/AgentSession.ts +1432 -0
  141. package/src/session/JsonlSessionStore.ts +572 -0
  142. package/src/session/__tests__/JsonlSessionStore.test.ts +1410 -0
  143. package/src/session/__tests__/handlers.test.ts +161 -0
  144. package/src/session/handlers.ts +272 -0
  145. package/src/session/ids.ts +17 -0
  146. package/src/session/index.ts +44 -0
  147. package/src/session/messageSerialization.ts +207 -0
  148. package/src/session/types.ts +275 -0
  149. package/src/specs/custom-event-await.test.ts +89 -0
  150. package/src/specs/summarization.test.ts +1 -1
  151. package/src/stream.ts +755 -48
  152. package/src/summarization/node.ts +1 -1
  153. package/src/tools/ToolNode.ts +299 -126
  154. package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +373 -0
  155. package/src/tools/__tests__/handlers.test.ts +2 -1
  156. package/src/tools/__tests__/hitl.test.ts +206 -110
  157. package/src/tools/eagerEventExecution.ts +153 -0
  158. package/src/tools/handlers.ts +8 -4
  159. package/src/tools/streamedToolCallSeals.ts +57 -0
  160. package/src/types/hitl.ts +4 -0
  161. package/src/types/run.ts +11 -0
  162. package/src/types/tools.ts +36 -0
  163. package/dist/cjs/llm/text.cjs +0 -69
  164. package/dist/cjs/llm/text.cjs.map +0 -1
  165. package/dist/esm/llm/text.mjs +0 -67
  166. package/dist/esm/llm/text.mjs.map +0 -1
@@ -0,0 +1,677 @@
1
+ import { GraphEvents } from '@/common';
2
+ import type { UsageMetadata } from '@langchain/core/messages';
3
+ import type * as t from '@/types';
4
+
5
+ export interface ResponsesCompatibleWriter {
6
+ write(data: string): void | Promise<void>;
7
+ }
8
+
9
+ export type ResponseStatus =
10
+ | 'in_progress'
11
+ | 'completed'
12
+ | 'failed'
13
+ | 'incomplete';
14
+ export type ItemStatus = 'in_progress' | 'incomplete' | 'completed';
15
+
16
+ export interface ResponseContext {
17
+ responseId: string;
18
+ model: string;
19
+ createdAt: number;
20
+ previousResponseId?: string;
21
+ instructions?: string;
22
+ }
23
+
24
+ export interface ResponseOutputTextContent {
25
+ type: 'output_text';
26
+ text: string;
27
+ annotations: [];
28
+ logprobs: [];
29
+ }
30
+
31
+ export interface ResponseMessageItem {
32
+ type: 'message';
33
+ id: string;
34
+ role: 'assistant';
35
+ status: ItemStatus;
36
+ content: ResponseOutputTextContent[];
37
+ }
38
+
39
+ export interface ResponseFunctionCallItem {
40
+ type: 'function_call';
41
+ id: string;
42
+ call_id: string;
43
+ name: string;
44
+ arguments: string;
45
+ status: ItemStatus;
46
+ }
47
+
48
+ export interface ResponseReasoningItem {
49
+ type: 'reasoning';
50
+ id: string;
51
+ status: ItemStatus;
52
+ content: Array<{ type: 'reasoning_text'; text: string }>;
53
+ summary: [];
54
+ }
55
+
56
+ export type ResponseOutputItem =
57
+ | ResponseMessageItem
58
+ | ResponseFunctionCallItem
59
+ | ResponseReasoningItem;
60
+
61
+ export interface ResponseObject {
62
+ id: string;
63
+ object: 'response';
64
+ created_at: number;
65
+ completed_at: number | null;
66
+ status: ResponseStatus;
67
+ model: string;
68
+ previous_response_id: string | null;
69
+ instructions: string | null;
70
+ output: ResponseOutputItem[];
71
+ error: { type: string; message: string; code?: string } | null;
72
+ usage: {
73
+ input_tokens: number;
74
+ output_tokens: number;
75
+ total_tokens: number;
76
+ } | null;
77
+ }
78
+
79
+ export interface ResponseEvent {
80
+ type: string;
81
+ sequence_number: number;
82
+ [key: string]: unknown;
83
+ }
84
+
85
+ export interface ResponseTracker {
86
+ sequenceNumber: number;
87
+ items: ResponseOutputItem[];
88
+ message: ResponseMessageItem | undefined;
89
+ reasoning: ResponseReasoningItem | undefined;
90
+ functionCalls: Map<string, ResponseFunctionCallItem>;
91
+ functionCallsByStep: Map<string, Array<ResponseFunctionCallItem | undefined>>;
92
+ responseCreated: boolean;
93
+ usage: {
94
+ inputTokens: number;
95
+ outputTokens: number;
96
+ };
97
+ nextSequence(): number;
98
+ }
99
+
100
+ export interface ResponsesHandlerConfig {
101
+ writer: ResponsesCompatibleWriter;
102
+ context: ResponseContext;
103
+ tracker: ResponseTracker;
104
+ }
105
+
106
+ let responseItemId = 0;
107
+
108
+ function createItemId(prefix: string): string {
109
+ return `${prefix}_${Date.now().toString(36)}${(responseItemId++).toString(36)}`;
110
+ }
111
+
112
+ export function createResponseTracker(): ResponseTracker {
113
+ const tracker: ResponseTracker = {
114
+ sequenceNumber: 0,
115
+ items: [],
116
+ message: undefined,
117
+ reasoning: undefined,
118
+ functionCalls: new Map(),
119
+ functionCallsByStep: new Map(),
120
+ responseCreated: false,
121
+ usage: {
122
+ inputTokens: 0,
123
+ outputTokens: 0,
124
+ },
125
+ nextSequence: () => tracker.sequenceNumber++,
126
+ };
127
+ return tracker;
128
+ }
129
+
130
+ function getTokenCount(value: number | null | undefined): number {
131
+ return typeof value === 'number' && Number.isFinite(value) ? value : 0;
132
+ }
133
+
134
+ interface ResponseToolCallFragment {
135
+ index?: number;
136
+ id?: string;
137
+ name?: string;
138
+ args?: string | object;
139
+ function?: {
140
+ name?: string;
141
+ arguments?: string | object;
142
+ };
143
+ }
144
+
145
+ function getToolCallIndex(
146
+ toolCall: ResponseToolCallFragment,
147
+ fallbackIndex: number
148
+ ): number {
149
+ return typeof toolCall.index === 'number' ? toolCall.index : fallbackIndex;
150
+ }
151
+
152
+ function getToolCallPositionKey(
153
+ stepId: string,
154
+ toolCall: ResponseToolCallFragment,
155
+ fallbackIndex: number
156
+ ): string {
157
+ const index = getToolCallIndex(toolCall, fallbackIndex);
158
+ if (stepId !== '') {
159
+ return `${stepId}:${index}`;
160
+ }
161
+ return `tool:${index}`;
162
+ }
163
+
164
+ function getToolCallIdKey(
165
+ toolCall: ResponseToolCallFragment
166
+ ): string | undefined {
167
+ return toolCall.id != null && toolCall.id !== '' ? toolCall.id : undefined;
168
+ }
169
+
170
+ function getExistingFunctionCall(
171
+ config: ResponsesHandlerConfig,
172
+ idKey: string | undefined,
173
+ positionKey: string | undefined,
174
+ stepId: string,
175
+ toolCall: ResponseToolCallFragment
176
+ ): ResponseFunctionCallItem | undefined {
177
+ const keyed =
178
+ idKey == null
179
+ ? undefined
180
+ : (config.tracker.functionCalls.get(idKey) ??
181
+ (positionKey == null
182
+ ? undefined
183
+ : config.tracker.functionCalls.get(positionKey)));
184
+ if (keyed != null) {
185
+ return keyed;
186
+ }
187
+ const positioned =
188
+ positionKey == null
189
+ ? undefined
190
+ : config.tracker.functionCalls.get(positionKey);
191
+ if (positioned != null) {
192
+ return positioned;
193
+ }
194
+ if (stepId === '') {
195
+ return undefined;
196
+ }
197
+ return findFunctionCallByStep(config, stepId, toolCall);
198
+ }
199
+
200
+ function findFunctionCallByStep(
201
+ config: ResponsesHandlerConfig,
202
+ stepId: string,
203
+ toolCall: ResponseToolCallFragment
204
+ ): ResponseFunctionCallItem | undefined {
205
+ const items = config.tracker.functionCallsByStep.get(stepId);
206
+ if (items == null) {
207
+ return undefined;
208
+ }
209
+ const index = typeof toolCall.index === 'number' ? toolCall.index : undefined;
210
+ if (index != null) {
211
+ return items[index];
212
+ }
213
+ const name = getToolCallName(toolCall);
214
+ let fallback: ResponseFunctionCallItem | undefined;
215
+ for (const item of items) {
216
+ if (item == null || item.status === 'completed') {
217
+ continue;
218
+ }
219
+ if (name !== '' && item.name !== name) {
220
+ continue;
221
+ }
222
+ if (fallback != null) {
223
+ return undefined;
224
+ }
225
+ fallback = item;
226
+ }
227
+ return fallback;
228
+ }
229
+
230
+ function trackFunctionCallKeys(
231
+ config: ResponsesHandlerConfig,
232
+ item: ResponseFunctionCallItem,
233
+ idKey: string | undefined,
234
+ positionKey: string
235
+ ): void {
236
+ config.tracker.functionCalls.set(positionKey, item);
237
+ if (idKey != null) {
238
+ config.tracker.functionCalls.set(idKey, item);
239
+ }
240
+ }
241
+
242
+ function trackFunctionCallStep(
243
+ config: ResponsesHandlerConfig,
244
+ stepId: string,
245
+ item: ResponseFunctionCallItem,
246
+ fallbackIndex: number
247
+ ): void {
248
+ if (stepId === '') {
249
+ return;
250
+ }
251
+ const items = config.tracker.functionCallsByStep.get(stepId) ?? [];
252
+ items[fallbackIndex] = item;
253
+ config.tracker.functionCallsByStep.set(stepId, items);
254
+ }
255
+
256
+ function getToolCallName(toolCall: ResponseToolCallFragment): string {
257
+ return toolCall.name ?? toolCall.function?.name ?? '';
258
+ }
259
+
260
+ function getToolCallArguments(toolCall: ResponseToolCallFragment): string {
261
+ const args = toolCall.args ?? toolCall.function?.arguments;
262
+ if (args == null) {
263
+ return '';
264
+ }
265
+ if (typeof args === 'string') {
266
+ return args;
267
+ }
268
+ return JSON.stringify(args);
269
+ }
270
+
271
+ async function ensureFunctionCall(
272
+ config: ResponsesHandlerConfig,
273
+ stepId: string,
274
+ toolCall: ResponseToolCallFragment,
275
+ fallbackIndex: number
276
+ ): Promise<ResponseFunctionCallItem> {
277
+ await ensureResponseCreated(config);
278
+ const positionKey = getToolCallPositionKey(stepId, toolCall, fallbackIndex);
279
+ const idKey = getToolCallIdKey(toolCall);
280
+ const existing = getExistingFunctionCall(
281
+ config,
282
+ idKey,
283
+ positionKey,
284
+ stepId,
285
+ toolCall
286
+ );
287
+ const toolCallIndex = getToolCallIndex(toolCall, fallbackIndex);
288
+ const name = getToolCallName(toolCall);
289
+ if (existing) {
290
+ trackFunctionCallKeys(config, existing, idKey, positionKey);
291
+ trackFunctionCallStep(config, stepId, existing, toolCallIndex);
292
+ if (idKey != null) {
293
+ existing.call_id = idKey;
294
+ }
295
+ if (name !== '') {
296
+ existing.name = name;
297
+ }
298
+ return existing;
299
+ }
300
+ const item: ResponseFunctionCallItem = {
301
+ type: 'function_call',
302
+ id: createItemId('fc'),
303
+ call_id: idKey ?? positionKey,
304
+ name,
305
+ arguments: '',
306
+ status: 'in_progress',
307
+ };
308
+ trackFunctionCallKeys(config, item, idKey, positionKey);
309
+ trackFunctionCallStep(config, stepId, item, toolCallIndex);
310
+ config.tracker.items.push(item);
311
+ await writeResponseEvent(config.writer, {
312
+ type: 'response.output_item.added',
313
+ sequence_number: config.tracker.nextSequence(),
314
+ output_index: config.tracker.items.length - 1,
315
+ item,
316
+ });
317
+ return item;
318
+ }
319
+
320
+ async function emitFunctionCallArgumentsDelta(params: {
321
+ config: ResponsesHandlerConfig;
322
+ item: ResponseFunctionCallItem;
323
+ delta: string;
324
+ }): Promise<void> {
325
+ if (params.delta === '') {
326
+ return;
327
+ }
328
+ params.item.arguments += params.delta;
329
+ await writeResponseEvent(params.config.writer, {
330
+ type: 'response.function_call_arguments.delta',
331
+ sequence_number: params.config.tracker.nextSequence(),
332
+ item_id: params.item.id,
333
+ output_index: params.config.tracker.items.indexOf(params.item),
334
+ call_id: params.item.call_id,
335
+ delta: params.delta,
336
+ });
337
+ }
338
+
339
+ async function emitFunctionCallArgumentsDone(
340
+ config: ResponsesHandlerConfig,
341
+ item: ResponseFunctionCallItem
342
+ ): Promise<void> {
343
+ await writeResponseEvent(config.writer, {
344
+ type: 'response.function_call_arguments.done',
345
+ sequence_number: config.tracker.nextSequence(),
346
+ item_id: item.id,
347
+ output_index: config.tracker.items.indexOf(item),
348
+ call_id: item.call_id,
349
+ name: item.name,
350
+ arguments: item.arguments,
351
+ });
352
+ }
353
+
354
+ async function completeFunctionCall(
355
+ config: ResponsesHandlerConfig,
356
+ stepId: string,
357
+ toolCall: ResponseToolCallFragment
358
+ ): Promise<void> {
359
+ const fallbackIndex =
360
+ typeof toolCall.index === 'number' ? toolCall.index : undefined;
361
+ const positionKey =
362
+ fallbackIndex == null
363
+ ? undefined
364
+ : getToolCallPositionKey(stepId, toolCall, fallbackIndex);
365
+ const item =
366
+ getExistingFunctionCall(
367
+ config,
368
+ getToolCallIdKey(toolCall),
369
+ positionKey,
370
+ stepId,
371
+ toolCall
372
+ ) ??
373
+ (await ensureFunctionCall(config, stepId, toolCall, fallbackIndex ?? 0));
374
+ if (item.status === 'completed') {
375
+ return;
376
+ }
377
+ const finalArguments = getToolCallArguments(toolCall);
378
+ if (
379
+ finalArguments !== '' &&
380
+ finalArguments !== item.arguments &&
381
+ finalArguments.startsWith(item.arguments)
382
+ ) {
383
+ await emitFunctionCallArgumentsDelta({
384
+ config,
385
+ item,
386
+ delta: finalArguments.slice(item.arguments.length),
387
+ });
388
+ } else if (finalArguments !== '') {
389
+ item.arguments = finalArguments;
390
+ }
391
+ await emitFunctionCallArgumentsDone(config, item);
392
+ item.status = 'completed';
393
+ await writeResponseEvent(config.writer, {
394
+ type: 'response.output_item.done',
395
+ sequence_number: config.tracker.nextSequence(),
396
+ output_index: config.tracker.items.indexOf(item),
397
+ item,
398
+ });
399
+ }
400
+
401
+ export function buildResponse(
402
+ context: ResponseContext,
403
+ tracker: ResponseTracker,
404
+ status: ResponseStatus = 'in_progress'
405
+ ): ResponseObject {
406
+ const completed = status === 'completed';
407
+ return {
408
+ id: context.responseId,
409
+ object: 'response',
410
+ created_at: context.createdAt,
411
+ completed_at: completed ? Math.floor(Date.now() / 1000) : null,
412
+ status,
413
+ model: context.model,
414
+ previous_response_id: context.previousResponseId ?? null,
415
+ instructions: context.instructions ?? null,
416
+ output: tracker.items,
417
+ error: null,
418
+ usage: completed
419
+ ? {
420
+ input_tokens: tracker.usage.inputTokens,
421
+ output_tokens: tracker.usage.outputTokens,
422
+ total_tokens: tracker.usage.inputTokens + tracker.usage.outputTokens,
423
+ }
424
+ : null,
425
+ };
426
+ }
427
+
428
+ export async function writeResponseEvent(
429
+ writer: ResponsesCompatibleWriter,
430
+ event: ResponseEvent
431
+ ): Promise<void> {
432
+ await writer.write(`event: ${event.type}\n`);
433
+ await writer.write(`data: ${JSON.stringify(event)}\n\n`);
434
+ }
435
+
436
+ export async function writeResponsesDone(
437
+ writer: ResponsesCompatibleWriter
438
+ ): Promise<void> {
439
+ await writer.write('data: [DONE]\n\n');
440
+ }
441
+
442
+ export async function ensureResponseCreated(
443
+ config: ResponsesHandlerConfig
444
+ ): Promise<void> {
445
+ if (config.tracker.responseCreated) {
446
+ return;
447
+ }
448
+ config.tracker.responseCreated = true;
449
+ await writeResponseEvent(config.writer, {
450
+ type: 'response.created',
451
+ sequence_number: config.tracker.nextSequence(),
452
+ response: buildResponse(config.context, config.tracker),
453
+ });
454
+ }
455
+
456
+ async function emitOutputContentDone(
457
+ config: ResponsesHandlerConfig,
458
+ item: ResponseMessageItem | ResponseReasoningItem
459
+ ): Promise<void> {
460
+ const outputIndex = config.tracker.items.indexOf(item);
461
+ if (item.type === 'message') {
462
+ await writeResponseEvent(config.writer, {
463
+ type: 'response.output_text.done',
464
+ sequence_number: config.tracker.nextSequence(),
465
+ item_id: item.id,
466
+ output_index: outputIndex,
467
+ content_index: 0,
468
+ text: item.content[0].text,
469
+ });
470
+ return;
471
+ }
472
+ await writeResponseEvent(config.writer, {
473
+ type: 'response.reasoning_text.done',
474
+ sequence_number: config.tracker.nextSequence(),
475
+ item_id: item.id,
476
+ output_index: outputIndex,
477
+ content_index: 0,
478
+ text: item.content[0].text,
479
+ });
480
+ }
481
+
482
+ async function ensureMessage(
483
+ config: ResponsesHandlerConfig
484
+ ): Promise<ResponseMessageItem> {
485
+ await ensureResponseCreated(config);
486
+ if (config.tracker.message) {
487
+ return config.tracker.message;
488
+ }
489
+ const item: ResponseMessageItem = {
490
+ type: 'message',
491
+ id: createItemId('msg'),
492
+ role: 'assistant',
493
+ status: 'in_progress',
494
+ content: [{ type: 'output_text', text: '', annotations: [], logprobs: [] }],
495
+ };
496
+ config.tracker.message = item;
497
+ config.tracker.items.push(item);
498
+ await writeResponseEvent(config.writer, {
499
+ type: 'response.output_item.added',
500
+ sequence_number: config.tracker.nextSequence(),
501
+ output_index: config.tracker.items.length - 1,
502
+ item,
503
+ });
504
+ return item;
505
+ }
506
+
507
+ async function ensureReasoning(
508
+ config: ResponsesHandlerConfig
509
+ ): Promise<ResponseReasoningItem> {
510
+ await ensureResponseCreated(config);
511
+ if (config.tracker.reasoning) {
512
+ return config.tracker.reasoning;
513
+ }
514
+ const item: ResponseReasoningItem = {
515
+ type: 'reasoning',
516
+ id: createItemId('reason'),
517
+ status: 'in_progress',
518
+ content: [{ type: 'reasoning_text', text: '' }],
519
+ summary: [],
520
+ };
521
+ config.tracker.reasoning = item;
522
+ config.tracker.items.push(item);
523
+ await writeResponseEvent(config.writer, {
524
+ type: 'response.output_item.added',
525
+ sequence_number: config.tracker.nextSequence(),
526
+ output_index: config.tracker.items.length - 1,
527
+ item,
528
+ });
529
+ return item;
530
+ }
531
+
532
+ export function createResponsesEventHandlers(
533
+ config: ResponsesHandlerConfig
534
+ ): Record<string, t.EventHandler> {
535
+ return {
536
+ [GraphEvents.ON_MESSAGE_DELTA]: {
537
+ handle: async (_event, data): Promise<void> => {
538
+ const item = await ensureMessage(config);
539
+ for (const part of (data as t.MessageDeltaEvent).delta.content ?? []) {
540
+ if (!('text' in part) || typeof part.text !== 'string') {
541
+ continue;
542
+ }
543
+ item.content[0].text += part.text;
544
+ await writeResponseEvent(config.writer, {
545
+ type: 'response.output_text.delta',
546
+ sequence_number: config.tracker.nextSequence(),
547
+ item_id: item.id,
548
+ output_index: config.tracker.items.indexOf(item),
549
+ content_index: 0,
550
+ delta: part.text,
551
+ });
552
+ }
553
+ },
554
+ },
555
+ [GraphEvents.ON_REASONING_DELTA]: {
556
+ handle: async (_event, data): Promise<void> => {
557
+ const item = await ensureReasoning(config);
558
+ for (const part of (data as t.ReasoningDeltaEvent).delta.content ??
559
+ []) {
560
+ let text: string | undefined;
561
+ if ('think' in part && typeof part.think === 'string') {
562
+ text = part.think;
563
+ } else if ('text' in part && typeof part.text === 'string') {
564
+ text = part.text;
565
+ }
566
+ if (typeof text !== 'string') {
567
+ continue;
568
+ }
569
+ item.content[0].text += text;
570
+ await writeResponseEvent(config.writer, {
571
+ type: 'response.reasoning_text.delta',
572
+ sequence_number: config.tracker.nextSequence(),
573
+ item_id: item.id,
574
+ output_index: config.tracker.items.indexOf(item),
575
+ content_index: 0,
576
+ delta: text,
577
+ });
578
+ }
579
+ },
580
+ },
581
+ [GraphEvents.ON_RUN_STEP]: {
582
+ handle: async (_event, data): Promise<void> => {
583
+ const runStep = data as t.RunStep;
584
+ if (runStep.stepDetails.type !== 'tool_calls') {
585
+ return;
586
+ }
587
+ const toolCalls = runStep.stepDetails.tool_calls ?? [];
588
+ for (let index = 0; index < toolCalls.length; index++) {
589
+ await ensureFunctionCall(config, runStep.id, toolCalls[index], index);
590
+ }
591
+ },
592
+ },
593
+ [GraphEvents.ON_RUN_STEP_DELTA]: {
594
+ handle: async (_event, data): Promise<void> => {
595
+ const runStepDelta = data as t.RunStepDeltaEvent;
596
+ if (runStepDelta.delta.type !== 'tool_calls') {
597
+ return;
598
+ }
599
+ const toolCalls = runStepDelta.delta.tool_calls ?? [];
600
+ for (let index = 0; index < toolCalls.length; index++) {
601
+ const item = await ensureFunctionCall(
602
+ config,
603
+ runStepDelta.id,
604
+ toolCalls[index],
605
+ index
606
+ );
607
+ await emitFunctionCallArgumentsDelta({
608
+ config,
609
+ item,
610
+ delta: getToolCallArguments(toolCalls[index]),
611
+ });
612
+ }
613
+ },
614
+ },
615
+ [GraphEvents.ON_RUN_STEP_COMPLETED]: {
616
+ handle: async (_event, data): Promise<void> => {
617
+ const completed = data as {
618
+ result?: {
619
+ id?: string;
620
+ index?: number;
621
+ type?: string;
622
+ tool_call?: ResponseToolCallFragment;
623
+ };
624
+ };
625
+ if (!completed.result?.tool_call) {
626
+ return;
627
+ }
628
+ await completeFunctionCall(
629
+ config,
630
+ completed.result.id ?? '',
631
+ completed.result.tool_call
632
+ );
633
+ },
634
+ },
635
+ [GraphEvents.CHAT_MODEL_END]: {
636
+ handle: (_event, data): void => {
637
+ const usage = (data as t.ModelEndData)?.output?.usage_metadata as
638
+ | Partial<UsageMetadata>
639
+ | undefined;
640
+ if (!usage) {
641
+ return;
642
+ }
643
+ config.tracker.usage.inputTokens += getTokenCount(usage.input_tokens);
644
+ config.tracker.usage.outputTokens += getTokenCount(usage.output_tokens);
645
+ },
646
+ },
647
+ };
648
+ }
649
+
650
+ export async function emitResponseCompleted(
651
+ config: ResponsesHandlerConfig
652
+ ): Promise<void> {
653
+ await ensureResponseCreated(config);
654
+ for (const item of config.tracker.items) {
655
+ if (item.status === 'completed') {
656
+ continue;
657
+ }
658
+ if (item.type === 'message' || item.type === 'reasoning') {
659
+ await emitOutputContentDone(config, item);
660
+ } else {
661
+ await emitFunctionCallArgumentsDone(config, item);
662
+ }
663
+ item.status = 'completed';
664
+ await writeResponseEvent(config.writer, {
665
+ type: 'response.output_item.done',
666
+ sequence_number: config.tracker.nextSequence(),
667
+ output_index: config.tracker.items.indexOf(item),
668
+ item,
669
+ });
670
+ }
671
+ await writeResponseEvent(config.writer, {
672
+ type: 'response.completed',
673
+ sequence_number: config.tracker.nextSequence(),
674
+ response: buildResponse(config.context, config.tracker, 'completed'),
675
+ });
676
+ await writeResponsesDone(config.writer);
677
+ }