@librechat/agents 3.1.86 → 3.1.88

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 (160) hide show
  1. package/README.md +69 -0
  2. package/dist/cjs/events.cjs +23 -0
  3. package/dist/cjs/events.cjs.map +1 -1
  4. package/dist/cjs/graphs/Graph.cjs +133 -18
  5. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  6. package/dist/cjs/graphs/MultiAgentGraph.cjs +1 -1
  7. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  8. package/dist/cjs/llm/anthropic/index.cjs +251 -53
  9. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  10. package/dist/cjs/llm/init.cjs +1 -5
  11. package/dist/cjs/llm/init.cjs.map +1 -1
  12. package/dist/cjs/llm/openai/index.cjs +113 -24
  13. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  14. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  15. package/dist/cjs/llm/openrouter/index.cjs +3 -1
  16. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  17. package/dist/cjs/main.cjs +18 -5
  18. package/dist/cjs/main.cjs.map +1 -1
  19. package/dist/cjs/openai/index.cjs +253 -0
  20. package/dist/cjs/openai/index.cjs.map +1 -0
  21. package/dist/cjs/responses/index.cjs +448 -0
  22. package/dist/cjs/responses/index.cjs.map +1 -0
  23. package/dist/cjs/run.cjs +108 -7
  24. package/dist/cjs/run.cjs.map +1 -1
  25. package/dist/cjs/session/AgentSession.cjs +1057 -0
  26. package/dist/cjs/session/AgentSession.cjs.map +1 -0
  27. package/dist/cjs/session/JsonlSessionStore.cjs +425 -0
  28. package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -0
  29. package/dist/cjs/session/handlers.cjs +221 -0
  30. package/dist/cjs/session/handlers.cjs.map +1 -0
  31. package/dist/cjs/session/ids.cjs +22 -0
  32. package/dist/cjs/session/ids.cjs.map +1 -0
  33. package/dist/cjs/session/messageSerialization.cjs +179 -0
  34. package/dist/cjs/session/messageSerialization.cjs.map +1 -0
  35. package/dist/cjs/stream.cjs +475 -11
  36. package/dist/cjs/stream.cjs.map +1 -1
  37. package/dist/cjs/summarization/node.cjs +1 -1
  38. package/dist/cjs/summarization/node.cjs.map +1 -1
  39. package/dist/cjs/tools/ToolNode.cjs +177 -59
  40. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  41. package/dist/cjs/tools/eagerEventExecution.cjs +113 -0
  42. package/dist/cjs/tools/eagerEventExecution.cjs.map +1 -0
  43. package/dist/cjs/tools/handlers.cjs +1 -1
  44. package/dist/cjs/tools/handlers.cjs.map +1 -1
  45. package/dist/cjs/tools/streamedToolCallSeals.cjs +42 -0
  46. package/dist/cjs/tools/streamedToolCallSeals.cjs.map +1 -0
  47. package/dist/esm/events.mjs +23 -1
  48. package/dist/esm/events.mjs.map +1 -1
  49. package/dist/esm/graphs/Graph.mjs +133 -18
  50. package/dist/esm/graphs/Graph.mjs.map +1 -1
  51. package/dist/esm/graphs/MultiAgentGraph.mjs +1 -1
  52. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  53. package/dist/esm/llm/anthropic/index.mjs +251 -53
  54. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  55. package/dist/esm/llm/init.mjs +1 -5
  56. package/dist/esm/llm/init.mjs.map +1 -1
  57. package/dist/esm/llm/openai/index.mjs +113 -25
  58. package/dist/esm/llm/openai/index.mjs.map +1 -1
  59. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  60. package/dist/esm/llm/openrouter/index.mjs +4 -2
  61. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  62. package/dist/esm/main.mjs +5 -1
  63. package/dist/esm/main.mjs.map +1 -1
  64. package/dist/esm/openai/index.mjs +246 -0
  65. package/dist/esm/openai/index.mjs.map +1 -0
  66. package/dist/esm/responses/index.mjs +440 -0
  67. package/dist/esm/responses/index.mjs.map +1 -0
  68. package/dist/esm/run.mjs +108 -7
  69. package/dist/esm/run.mjs.map +1 -1
  70. package/dist/esm/session/AgentSession.mjs +1054 -0
  71. package/dist/esm/session/AgentSession.mjs.map +1 -0
  72. package/dist/esm/session/JsonlSessionStore.mjs +422 -0
  73. package/dist/esm/session/JsonlSessionStore.mjs.map +1 -0
  74. package/dist/esm/session/handlers.mjs +219 -0
  75. package/dist/esm/session/handlers.mjs.map +1 -0
  76. package/dist/esm/session/ids.mjs +17 -0
  77. package/dist/esm/session/ids.mjs.map +1 -0
  78. package/dist/esm/session/messageSerialization.mjs +173 -0
  79. package/dist/esm/session/messageSerialization.mjs.map +1 -0
  80. package/dist/esm/stream.mjs +476 -12
  81. package/dist/esm/stream.mjs.map +1 -1
  82. package/dist/esm/summarization/node.mjs +1 -1
  83. package/dist/esm/summarization/node.mjs.map +1 -1
  84. package/dist/esm/tools/ToolNode.mjs +177 -59
  85. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  86. package/dist/esm/tools/eagerEventExecution.mjs +107 -0
  87. package/dist/esm/tools/eagerEventExecution.mjs.map +1 -0
  88. package/dist/esm/tools/handlers.mjs +1 -1
  89. package/dist/esm/tools/handlers.mjs.map +1 -1
  90. package/dist/esm/tools/streamedToolCallSeals.mjs +36 -0
  91. package/dist/esm/tools/streamedToolCallSeals.mjs.map +1 -0
  92. package/dist/types/events.d.ts +1 -0
  93. package/dist/types/graphs/Graph.d.ts +24 -9
  94. package/dist/types/index.d.ts +1 -0
  95. package/dist/types/llm/openai/index.d.ts +1 -0
  96. package/dist/types/openai/index.d.ts +75 -0
  97. package/dist/types/responses/index.d.ts +97 -0
  98. package/dist/types/run.d.ts +2 -0
  99. package/dist/types/session/AgentSession.d.ts +32 -0
  100. package/dist/types/session/JsonlSessionStore.d.ts +67 -0
  101. package/dist/types/session/handlers.d.ts +8 -0
  102. package/dist/types/session/ids.d.ts +4 -0
  103. package/dist/types/session/index.d.ts +5 -0
  104. package/dist/types/session/messageSerialization.d.ts +7 -0
  105. package/dist/types/session/types.d.ts +191 -0
  106. package/dist/types/tools/ToolNode.d.ts +12 -1
  107. package/dist/types/tools/eagerEventExecution.d.ts +23 -0
  108. package/dist/types/tools/streamedToolCallSeals.d.ts +13 -0
  109. package/dist/types/types/hitl.d.ts +4 -0
  110. package/dist/types/types/run.d.ts +11 -1
  111. package/dist/types/types/tools.d.ts +36 -0
  112. package/package.json +19 -2
  113. package/src/__tests__/stream.eagerEventExecution.test.ts +2571 -0
  114. package/src/events.ts +29 -0
  115. package/src/graphs/Graph.ts +224 -50
  116. package/src/graphs/MultiAgentGraph.ts +1 -1
  117. package/src/graphs/__tests__/composition.smoke.test.ts +30 -0
  118. package/src/index.ts +3 -0
  119. package/src/llm/anthropic/index.ts +356 -84
  120. package/src/llm/anthropic/llm.spec.ts +64 -0
  121. package/src/llm/custom-chat-models.smoke.test.ts +175 -4
  122. package/src/llm/openai/contentBlocks.test.ts +35 -0
  123. package/src/llm/openai/deepseek.test.ts +201 -2
  124. package/src/llm/openai/index.ts +171 -26
  125. package/src/llm/openai/utils/index.ts +22 -0
  126. package/src/llm/openrouter/index.ts +4 -2
  127. package/src/openai/__tests__/openai.test.ts +337 -0
  128. package/src/openai/index.ts +404 -0
  129. package/src/responses/__tests__/responses.test.ts +652 -0
  130. package/src/responses/index.ts +677 -0
  131. package/src/run.ts +158 -8
  132. package/src/scripts/compare_pi_vs_ours.ts +592 -173
  133. package/src/scripts/session_live.ts +548 -0
  134. package/src/session/AgentSession.ts +1432 -0
  135. package/src/session/JsonlSessionStore.ts +572 -0
  136. package/src/session/__tests__/JsonlSessionStore.test.ts +1410 -0
  137. package/src/session/__tests__/handlers.test.ts +161 -0
  138. package/src/session/handlers.ts +272 -0
  139. package/src/session/ids.ts +17 -0
  140. package/src/session/index.ts +44 -0
  141. package/src/session/messageSerialization.ts +207 -0
  142. package/src/session/types.ts +275 -0
  143. package/src/specs/custom-event-await.test.ts +89 -0
  144. package/src/specs/summarization.test.ts +1 -1
  145. package/src/stream.ts +756 -48
  146. package/src/summarization/node.ts +1 -1
  147. package/src/tools/ToolNode.ts +299 -126
  148. package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +373 -0
  149. package/src/tools/__tests__/handlers.test.ts +2 -1
  150. package/src/tools/__tests__/hitl.test.ts +206 -110
  151. package/src/tools/eagerEventExecution.ts +153 -0
  152. package/src/tools/handlers.ts +8 -4
  153. package/src/tools/streamedToolCallSeals.ts +57 -0
  154. package/src/types/hitl.ts +4 -0
  155. package/src/types/run.ts +11 -0
  156. package/src/types/tools.ts +36 -0
  157. package/dist/cjs/llm/text.cjs +0 -69
  158. package/dist/cjs/llm/text.cjs.map +0 -1
  159. package/dist/esm/llm/text.mjs +0 -67
  160. package/dist/esm/llm/text.mjs.map +0 -1
@@ -0,0 +1,404 @@
1
+ import { GraphEvents } from '@/common';
2
+ import type { UsageMetadata } from '@langchain/core/messages';
3
+ import type * as t from '@/types';
4
+
5
+ export interface OpenAICompatibleWriter {
6
+ write(data: string): void | Promise<void>;
7
+ }
8
+
9
+ export interface OpenAIResponseContext {
10
+ requestId: string;
11
+ model: string;
12
+ created: number;
13
+ }
14
+
15
+ export interface OpenAICompletionUsage {
16
+ prompt_tokens: number;
17
+ completion_tokens: number;
18
+ total_tokens: number;
19
+ completion_tokens_details?: {
20
+ reasoning_tokens?: number;
21
+ };
22
+ }
23
+
24
+ export interface OpenAIToolCall {
25
+ id: string;
26
+ type: 'function';
27
+ function: {
28
+ name: string;
29
+ arguments: string;
30
+ };
31
+ }
32
+
33
+ export interface OpenAIChatCompletionChunkChoice {
34
+ index: number;
35
+ delta: {
36
+ role?: 'assistant';
37
+ content?: string | null;
38
+ reasoning?: string | null;
39
+ tool_calls?: Array<{
40
+ index: number;
41
+ id?: string;
42
+ type?: 'function';
43
+ function?: {
44
+ name?: string;
45
+ arguments?: string;
46
+ };
47
+ }>;
48
+ };
49
+ finish_reason: 'stop' | 'length' | 'tool_calls' | 'content_filter' | null;
50
+ }
51
+
52
+ export interface OpenAIChatCompletionChunk {
53
+ id: string;
54
+ object: 'chat.completion.chunk';
55
+ created: number;
56
+ model: string;
57
+ choices: OpenAIChatCompletionChunkChoice[];
58
+ usage?: OpenAICompletionUsage;
59
+ }
60
+
61
+ export interface OpenAIStreamTracker {
62
+ hasRole: boolean;
63
+ hasText: boolean;
64
+ hasReasoning: boolean;
65
+ lastChunkKind?: 'text' | 'reasoning' | 'tool_call';
66
+ toolCalls: Map<number, OpenAIToolCall>;
67
+ toolCallsByStep?: Map<string, Map<number, OpenAIToolCall>>;
68
+ usage: {
69
+ promptTokens: number;
70
+ completionTokens: number;
71
+ reasoningTokens: number;
72
+ };
73
+ }
74
+
75
+ export interface OpenAIHandlerConfig {
76
+ writer: OpenAICompatibleWriter;
77
+ context: OpenAIResponseContext;
78
+ tracker: OpenAIStreamTracker;
79
+ }
80
+
81
+ interface OpenAIToolCallFragment {
82
+ index?: number;
83
+ id?: string;
84
+ name?: string;
85
+ args?: string | object;
86
+ function?: {
87
+ name?: string;
88
+ arguments?: string | object;
89
+ };
90
+ }
91
+
92
+ export function createOpenAIStreamTracker(): OpenAIStreamTracker {
93
+ return {
94
+ hasRole: false,
95
+ hasText: false,
96
+ hasReasoning: false,
97
+ toolCalls: new Map(),
98
+ toolCallsByStep: new Map(),
99
+ usage: {
100
+ promptTokens: 0,
101
+ completionTokens: 0,
102
+ reasoningTokens: 0,
103
+ },
104
+ };
105
+ }
106
+
107
+ function getTokenCount(value: number | null | undefined): number {
108
+ return typeof value === 'number' && Number.isFinite(value) ? value : 0;
109
+ }
110
+
111
+ function getReasoningTokenCount(usage: Partial<UsageMetadata>): number {
112
+ return getTokenCount(usage.output_token_details?.reasoning);
113
+ }
114
+
115
+ function getToolCallIndex(
116
+ toolCall: OpenAIToolCallFragment,
117
+ fallbackIndex: number
118
+ ): number {
119
+ return typeof toolCall.index === 'number' ? toolCall.index : fallbackIndex;
120
+ }
121
+
122
+ function getToolCallName(toolCall: OpenAIToolCallFragment): string {
123
+ return toolCall.name ?? toolCall.function?.name ?? '';
124
+ }
125
+
126
+ function getToolCallArguments(toolCall: OpenAIToolCallFragment): string {
127
+ const args = toolCall.args ?? toolCall.function?.arguments;
128
+ if (args == null) {
129
+ return '';
130
+ }
131
+ if (typeof args === 'string') {
132
+ return args;
133
+ }
134
+ return JSON.stringify(args);
135
+ }
136
+
137
+ function getStepToolCalls(
138
+ tracker: OpenAIStreamTracker,
139
+ stepId: string
140
+ ): Map<number, OpenAIToolCall> {
141
+ const toolCallsByStep = tracker.toolCallsByStep ?? new Map();
142
+ tracker.toolCallsByStep = toolCallsByStep;
143
+ const existing = toolCallsByStep.get(stepId);
144
+ if (existing != null) {
145
+ return existing;
146
+ }
147
+ const stepToolCalls = new Map<number, OpenAIToolCall>();
148
+ toolCallsByStep.set(stepId, stepToolCalls);
149
+ return stepToolCalls;
150
+ }
151
+
152
+ export function createChatCompletionChunk(
153
+ context: OpenAIResponseContext,
154
+ delta: OpenAIChatCompletionChunkChoice['delta'],
155
+ finishReason: OpenAIChatCompletionChunkChoice['finish_reason'] = null,
156
+ usage?: OpenAICompletionUsage
157
+ ): OpenAIChatCompletionChunk {
158
+ return {
159
+ id: context.requestId,
160
+ object: 'chat.completion.chunk',
161
+ created: context.created,
162
+ model: context.model,
163
+ choices: [{ index: 0, delta, finish_reason: finishReason }],
164
+ ...(usage ? { usage } : {}),
165
+ };
166
+ }
167
+
168
+ export function createChatCompletionUsageChunk(
169
+ context: OpenAIResponseContext,
170
+ usage: OpenAICompletionUsage
171
+ ): OpenAIChatCompletionChunk {
172
+ return {
173
+ id: context.requestId,
174
+ object: 'chat.completion.chunk',
175
+ created: context.created,
176
+ model: context.model,
177
+ choices: [],
178
+ usage,
179
+ };
180
+ }
181
+
182
+ export async function writeOpenAISSE(
183
+ writer: OpenAICompatibleWriter,
184
+ data: OpenAIChatCompletionChunk | '[DONE]'
185
+ ): Promise<void> {
186
+ await writer.write(
187
+ `data: ${data === '[DONE]' ? data : JSON.stringify(data)}\n\n`
188
+ );
189
+ }
190
+
191
+ function getTextParts(
192
+ data: t.MessageDeltaEvent | t.ReasoningDeltaEvent
193
+ ): string[] {
194
+ const parts = data.delta.content ?? [];
195
+ const text: string[] = [];
196
+ for (const part of parts) {
197
+ if ('text' in part && typeof part.text === 'string') {
198
+ text.push(part.text);
199
+ continue;
200
+ }
201
+ if ('think' in part && typeof part.think === 'string') {
202
+ text.push(part.think);
203
+ }
204
+ }
205
+ return text;
206
+ }
207
+
208
+ async function emitAssistantRoleChunk(
209
+ config: OpenAIHandlerConfig
210
+ ): Promise<void> {
211
+ if (config.tracker.hasRole) {
212
+ return;
213
+ }
214
+ config.tracker.hasRole = true;
215
+ await writeOpenAISSE(
216
+ config.writer,
217
+ createChatCompletionChunk(config.context, { role: 'assistant' })
218
+ );
219
+ }
220
+
221
+ async function emitToolCallChunk(params: {
222
+ config: OpenAIHandlerConfig;
223
+ stepId: string;
224
+ toolCall: OpenAIToolCallFragment;
225
+ fallbackIndex: number;
226
+ completed: boolean;
227
+ }): Promise<void> {
228
+ const { config, stepId, toolCall, fallbackIndex, completed } = params;
229
+ const index = getToolCallIndex(toolCall, fallbackIndex);
230
+ const stepToolCalls = getStepToolCalls(config.tracker, stepId);
231
+ const existing = stepToolCalls.get(index);
232
+ const current = existing ?? {
233
+ id: toolCall.id ?? '',
234
+ type: 'function' as const,
235
+ function: { name: '', arguments: '' },
236
+ };
237
+ const name = getToolCallName(toolCall);
238
+ const args = getToolCallArguments(toolCall);
239
+ const idChanged =
240
+ toolCall.id != null && toolCall.id !== '' && current.id !== toolCall.id;
241
+ const nameChanged = name !== '' && current.function.name !== name;
242
+ let argumentDelta = '';
243
+ if (completed) {
244
+ if (args !== '' && args !== current.function.arguments) {
245
+ argumentDelta = args.startsWith(current.function.arguments)
246
+ ? args.slice(current.function.arguments.length)
247
+ : args;
248
+ current.function.arguments = args;
249
+ }
250
+ } else if (args !== '') {
251
+ argumentDelta = args;
252
+ current.function.arguments += args;
253
+ }
254
+ if (toolCall.id != null && toolCall.id !== '') {
255
+ current.id = toolCall.id;
256
+ }
257
+ if (name !== '') {
258
+ current.function.name = name;
259
+ }
260
+ stepToolCalls.set(index, current);
261
+ config.tracker.toolCalls.set(index, current);
262
+ if (!idChanged && !nameChanged && argumentDelta === '' && existing) {
263
+ return;
264
+ }
265
+ config.tracker.lastChunkKind = 'tool_call';
266
+ const functionDelta: { name?: string; arguments?: string } = {};
267
+ if (nameChanged || (!existing && current.function.name !== '')) {
268
+ functionDelta.name = current.function.name;
269
+ }
270
+ if (argumentDelta !== '') {
271
+ functionDelta.arguments = argumentDelta;
272
+ }
273
+ await emitAssistantRoleChunk(config);
274
+ await writeOpenAISSE(
275
+ config.writer,
276
+ createChatCompletionChunk(config.context, {
277
+ tool_calls: [
278
+ {
279
+ index,
280
+ ...(current.id !== '' ? { id: current.id } : {}),
281
+ type: 'function',
282
+ ...(functionDelta.name != null || functionDelta.arguments != null
283
+ ? { function: functionDelta }
284
+ : {}),
285
+ },
286
+ ],
287
+ })
288
+ );
289
+ }
290
+
291
+ export function createOpenAIHandlers(
292
+ config: OpenAIHandlerConfig
293
+ ): Record<string, t.EventHandler> {
294
+ return {
295
+ [GraphEvents.ON_MESSAGE_DELTA]: {
296
+ handle: async (_event, data): Promise<void> => {
297
+ for (const text of getTextParts(data as t.MessageDeltaEvent)) {
298
+ config.tracker.hasText = true;
299
+ config.tracker.lastChunkKind = 'text';
300
+ await emitAssistantRoleChunk(config);
301
+ await writeOpenAISSE(
302
+ config.writer,
303
+ createChatCompletionChunk(config.context, { content: text })
304
+ );
305
+ }
306
+ },
307
+ },
308
+ [GraphEvents.ON_REASONING_DELTA]: {
309
+ handle: async (_event, data): Promise<void> => {
310
+ for (const text of getTextParts(data as t.ReasoningDeltaEvent)) {
311
+ config.tracker.hasReasoning = true;
312
+ config.tracker.lastChunkKind = 'reasoning';
313
+ await emitAssistantRoleChunk(config);
314
+ await writeOpenAISSE(
315
+ config.writer,
316
+ createChatCompletionChunk(config.context, { reasoning: text })
317
+ );
318
+ }
319
+ },
320
+ },
321
+ [GraphEvents.ON_RUN_STEP_DELTA]: {
322
+ handle: async (_event, data): Promise<void> => {
323
+ const runStepDelta = data as t.RunStepDeltaEvent;
324
+ const delta = runStepDelta.delta;
325
+ if (delta.type !== 'tool_calls') {
326
+ return;
327
+ }
328
+ const toolCalls = delta.tool_calls ?? [];
329
+ for (let index = 0; index < toolCalls.length; index++) {
330
+ await emitToolCallChunk({
331
+ config,
332
+ stepId: runStepDelta.id,
333
+ toolCall: toolCalls[index],
334
+ fallbackIndex: index,
335
+ completed: false,
336
+ });
337
+ }
338
+ },
339
+ },
340
+ [GraphEvents.ON_RUN_STEP]: {
341
+ handle: async (_event, data): Promise<void> => {
342
+ const runStep = data as t.RunStep;
343
+ if (runStep.stepDetails.type !== 'tool_calls') {
344
+ return;
345
+ }
346
+ const toolCalls = runStep.stepDetails.tool_calls ?? [];
347
+ for (let index = 0; index < toolCalls.length; index++) {
348
+ await emitToolCallChunk({
349
+ config,
350
+ stepId: runStep.id,
351
+ toolCall: toolCalls[index],
352
+ fallbackIndex: index,
353
+ completed: true,
354
+ });
355
+ }
356
+ },
357
+ },
358
+ [GraphEvents.CHAT_MODEL_END]: {
359
+ handle: (_event, data): void => {
360
+ const usage = (data as t.ModelEndData)?.output?.usage_metadata as
361
+ | Partial<UsageMetadata>
362
+ | undefined;
363
+ if (!usage) {
364
+ return;
365
+ }
366
+ config.tracker.usage.promptTokens += getTokenCount(usage.input_tokens);
367
+ config.tracker.usage.completionTokens += getTokenCount(
368
+ usage.output_tokens
369
+ );
370
+ config.tracker.usage.reasoningTokens += getReasoningTokenCount(usage);
371
+ },
372
+ },
373
+ };
374
+ }
375
+
376
+ export async function sendOpenAIFinalChunk(
377
+ config: OpenAIHandlerConfig,
378
+ finishReason?: OpenAIChatCompletionChunkChoice['finish_reason']
379
+ ): Promise<void> {
380
+ const resolvedFinishReason =
381
+ finishReason ??
382
+ (config.tracker.lastChunkKind === 'tool_call' ? 'tool_calls' : 'stop');
383
+ const usage: OpenAICompletionUsage = {
384
+ prompt_tokens: config.tracker.usage.promptTokens,
385
+ completion_tokens: config.tracker.usage.completionTokens,
386
+ total_tokens:
387
+ config.tracker.usage.promptTokens + config.tracker.usage.completionTokens,
388
+ };
389
+ if (config.tracker.usage.reasoningTokens > 0) {
390
+ usage.completion_tokens_details = {
391
+ reasoning_tokens: config.tracker.usage.reasoningTokens,
392
+ };
393
+ }
394
+ await emitAssistantRoleChunk(config);
395
+ await writeOpenAISSE(
396
+ config.writer,
397
+ createChatCompletionChunk(config.context, {}, resolvedFinishReason)
398
+ );
399
+ await writeOpenAISSE(
400
+ config.writer,
401
+ createChatCompletionUsageChunk(config.context, usage)
402
+ );
403
+ await writeOpenAISSE(config.writer, '[DONE]');
404
+ }