@ank1015/agents-provider-openai 0.1.0
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/CHANGELOG.md +7 -0
- package/README.md +159 -0
- package/dist/adapter.d.ts +33 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +697 -0
- package/dist/adapter.js.map +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +66 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/mapping.d.ts +8 -0
- package/dist/mapping.d.ts.map +1 -0
- package/dist/mapping.js +210 -0
- package/dist/mapping.js.map +1 -0
- package/package.json +60 -0
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { createEventStream } from '@ank1015/agents-contracts';
|
|
3
|
+
import { OPENAI_PROVIDER, getOpenAIModel, openAIProviderConfigSchema, } from '@ank1015/agents-provider-openai-spec';
|
|
4
|
+
import { getOpenAIErrorDetails, isAbortError } from './errors.js';
|
|
5
|
+
import { buildOpenAIResponseParams } from './mapping.js';
|
|
6
|
+
export class OpenAIProviderAdapter {
|
|
7
|
+
provider = OPENAI_PROVIDER;
|
|
8
|
+
#config;
|
|
9
|
+
#client;
|
|
10
|
+
#createMessageId;
|
|
11
|
+
#now;
|
|
12
|
+
#resolveModel;
|
|
13
|
+
constructor(config, deps = {}) {
|
|
14
|
+
this.#config = openAIProviderConfigSchema.parse(config);
|
|
15
|
+
this.#client =
|
|
16
|
+
deps.client ?? deps.createClient?.(this.#config) ?? createOpenAIClient(this.#config);
|
|
17
|
+
this.#createMessageId = deps.createMessageId ?? createDefaultMessageId;
|
|
18
|
+
this.#now = deps.now ?? Date.now;
|
|
19
|
+
this.#resolveModel = deps.resolveModel ?? getOpenAIModel;
|
|
20
|
+
}
|
|
21
|
+
stream(request) {
|
|
22
|
+
const stream = createEventStream();
|
|
23
|
+
void this.#produce(request, stream);
|
|
24
|
+
return stream;
|
|
25
|
+
}
|
|
26
|
+
async #produce(request, stream) {
|
|
27
|
+
const model = this.#resolveModel(request.modelId);
|
|
28
|
+
if (!model) {
|
|
29
|
+
const message = this.#buildUnknownModelMessage(request);
|
|
30
|
+
stream.push({ type: 'error', reason: 'error', message });
|
|
31
|
+
stream.end(message);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const state = {
|
|
35
|
+
request,
|
|
36
|
+
model,
|
|
37
|
+
stream,
|
|
38
|
+
startedAt: this.#now(),
|
|
39
|
+
content: [],
|
|
40
|
+
statesByOutputIndex: new Map(),
|
|
41
|
+
statesByItemId: new Map(),
|
|
42
|
+
sawRefusal: false,
|
|
43
|
+
};
|
|
44
|
+
try {
|
|
45
|
+
const params = buildOpenAIResponseParams(request, model);
|
|
46
|
+
const requestOptions = request.signal ? { signal: request.signal } : undefined;
|
|
47
|
+
const openAIStream = await this.#client.responses.create(params, requestOptions);
|
|
48
|
+
stream.push({ type: 'start' });
|
|
49
|
+
for await (const event of openAIStream) {
|
|
50
|
+
handleStreamEvent(state, event);
|
|
51
|
+
}
|
|
52
|
+
if (request.signal?.aborted) {
|
|
53
|
+
const message = this.#buildAbortedMessage(state);
|
|
54
|
+
stream.push({ type: 'aborted', reason: 'aborted', message });
|
|
55
|
+
stream.end(message);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
closeOpenBlocks(state);
|
|
59
|
+
if (!state.finalResponse) {
|
|
60
|
+
throw new Error('OpenAI stream ended without a final response.');
|
|
61
|
+
}
|
|
62
|
+
const message = this.#buildSuccessfulMessage(state);
|
|
63
|
+
stream.push({ type: 'done', reason: message.stopReason, message });
|
|
64
|
+
stream.end(message);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
closeOpenBlocks(state, { allowInvalidToolJson: true });
|
|
68
|
+
if (request.signal?.aborted || isAbortError(error)) {
|
|
69
|
+
const message = this.#buildAbortedMessage(state);
|
|
70
|
+
stream.push({ type: 'aborted', reason: 'aborted', message });
|
|
71
|
+
stream.end(message);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const message = this.#buildFailedMessage(state, error);
|
|
75
|
+
stream.push({ type: 'error', reason: 'error', message });
|
|
76
|
+
stream.end(message);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
#buildSuccessfulMessage(state) {
|
|
80
|
+
const finalResponse = state.finalResponse;
|
|
81
|
+
if (!finalResponse) {
|
|
82
|
+
throw new Error('Cannot build a successful OpenAI message without a response.');
|
|
83
|
+
}
|
|
84
|
+
hydrateContentFromResponse(state, finalResponse);
|
|
85
|
+
return {
|
|
86
|
+
role: 'assistant',
|
|
87
|
+
id: this.#createMessageId(),
|
|
88
|
+
model: modelRef(state),
|
|
89
|
+
usage: usageFromResponse(finalResponse, state.model),
|
|
90
|
+
duration: this.#now() - state.startedAt,
|
|
91
|
+
nativeMessage: finalResponse,
|
|
92
|
+
content: state.content,
|
|
93
|
+
timestamp: this.#now(),
|
|
94
|
+
stopReason: mapDoneStopReason(finalResponse, state.content, state.sawRefusal),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
#buildFailedMessage(state, error) {
|
|
98
|
+
const message = {
|
|
99
|
+
role: 'assistant',
|
|
100
|
+
id: this.#createMessageId(),
|
|
101
|
+
model: modelRef(state),
|
|
102
|
+
usage: state.finalResponse ? usageFromResponse(state.finalResponse, state.model) : undefined,
|
|
103
|
+
duration: this.#now() - state.startedAt,
|
|
104
|
+
content: state.content,
|
|
105
|
+
timestamp: this.#now(),
|
|
106
|
+
stopReason: 'error',
|
|
107
|
+
error: getOpenAIErrorDetails(error),
|
|
108
|
+
};
|
|
109
|
+
if (state.finalResponse) {
|
|
110
|
+
message.nativeMessage = state.finalResponse;
|
|
111
|
+
}
|
|
112
|
+
return message;
|
|
113
|
+
}
|
|
114
|
+
#buildAbortedMessage(state) {
|
|
115
|
+
const message = {
|
|
116
|
+
role: 'assistant',
|
|
117
|
+
id: this.#createMessageId(),
|
|
118
|
+
model: modelRef(state),
|
|
119
|
+
usage: state.finalResponse ? usageFromResponse(state.finalResponse, state.model) : undefined,
|
|
120
|
+
duration: this.#now() - state.startedAt,
|
|
121
|
+
content: state.content,
|
|
122
|
+
timestamp: this.#now(),
|
|
123
|
+
stopReason: 'aborted',
|
|
124
|
+
};
|
|
125
|
+
if (state.finalResponse) {
|
|
126
|
+
message.nativeMessage = state.finalResponse;
|
|
127
|
+
}
|
|
128
|
+
return message;
|
|
129
|
+
}
|
|
130
|
+
#buildUnknownModelMessage(request) {
|
|
131
|
+
return {
|
|
132
|
+
role: 'assistant',
|
|
133
|
+
id: this.#createMessageId(),
|
|
134
|
+
model: { provider: OPENAI_PROVIDER, id: request.modelId },
|
|
135
|
+
duration: 0,
|
|
136
|
+
content: [],
|
|
137
|
+
timestamp: this.#now(),
|
|
138
|
+
stopReason: 'error',
|
|
139
|
+
error: {
|
|
140
|
+
message: `Model "${request.modelId}" is not a known OpenAI model.`,
|
|
141
|
+
canRetry: false,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export class OpenAIProviderAdapterFactory {
|
|
147
|
+
provider = OPENAI_PROVIDER;
|
|
148
|
+
#deps;
|
|
149
|
+
constructor(deps = {}) {
|
|
150
|
+
this.#deps = deps;
|
|
151
|
+
}
|
|
152
|
+
create(config) {
|
|
153
|
+
return new OpenAIProviderAdapter(config, this.#deps);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export function createOpenAIProviderAdapter(config, deps) {
|
|
157
|
+
return new OpenAIProviderAdapter(config, deps);
|
|
158
|
+
}
|
|
159
|
+
export function createOpenAIProviderAdapterFactory(deps) {
|
|
160
|
+
return new OpenAIProviderAdapterFactory(deps);
|
|
161
|
+
}
|
|
162
|
+
function createOpenAIClient(config) {
|
|
163
|
+
return new OpenAI({
|
|
164
|
+
apiKey: resolveSecret(config.apiKey),
|
|
165
|
+
baseURL: config.baseUrl,
|
|
166
|
+
defaultHeaders: config.headers,
|
|
167
|
+
organization: config.organization,
|
|
168
|
+
project: config.project,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function resolveSecret(secret) {
|
|
172
|
+
if (secret.type === 'value') {
|
|
173
|
+
return secret.value;
|
|
174
|
+
}
|
|
175
|
+
return resolveEnvSecret(secret.name);
|
|
176
|
+
}
|
|
177
|
+
function resolveEnvSecret(name) {
|
|
178
|
+
const processLike = globalThis.process;
|
|
179
|
+
const value = processLike?.env?.[name];
|
|
180
|
+
if (!value) {
|
|
181
|
+
throw new Error(`OpenAI API key environment variable "${name}" is not set.`);
|
|
182
|
+
}
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
function handleStreamEvent(state, event) {
|
|
186
|
+
switch (event.type) {
|
|
187
|
+
case 'response.output_item.added':
|
|
188
|
+
handleOutputItemAdded(state, event.output_index, event.item);
|
|
189
|
+
break;
|
|
190
|
+
case 'response.content_part.added':
|
|
191
|
+
if (event.part.type === 'refusal') {
|
|
192
|
+
state.sawRefusal = true;
|
|
193
|
+
}
|
|
194
|
+
if (event.part.type === 'output_text' || event.part.type === 'refusal') {
|
|
195
|
+
ensureResponseBlock(state, event.output_index, event.item_id);
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
case 'response.output_text.delta':
|
|
199
|
+
appendTextDelta(state, event.output_index, event.item_id, event.delta, false);
|
|
200
|
+
break;
|
|
201
|
+
case 'response.output_text.done':
|
|
202
|
+
setTextContent(state, event.output_index, event.item_id, event.text, false);
|
|
203
|
+
break;
|
|
204
|
+
case 'response.refusal.delta':
|
|
205
|
+
state.sawRefusal = true;
|
|
206
|
+
appendTextDelta(state, event.output_index, event.item_id, event.delta, true);
|
|
207
|
+
break;
|
|
208
|
+
case 'response.refusal.done':
|
|
209
|
+
state.sawRefusal = true;
|
|
210
|
+
setTextContent(state, event.output_index, event.item_id, event.refusal, true);
|
|
211
|
+
break;
|
|
212
|
+
case 'response.reasoning_summary_text.delta':
|
|
213
|
+
case 'response.reasoning_text.delta':
|
|
214
|
+
appendThinkingDelta(state, event.output_index, event.item_id, event.delta);
|
|
215
|
+
break;
|
|
216
|
+
case 'response.reasoning_summary_text.done':
|
|
217
|
+
case 'response.reasoning_text.done':
|
|
218
|
+
setThinkingText(state, event.output_index, event.item_id, event.text);
|
|
219
|
+
break;
|
|
220
|
+
case 'response.function_call_arguments.delta':
|
|
221
|
+
appendFunctionCallDelta(state, event.output_index, event.item_id, event.delta);
|
|
222
|
+
break;
|
|
223
|
+
case 'response.function_call_arguments.done':
|
|
224
|
+
setFunctionCallArguments(state, event.output_index, event.item_id, event.arguments);
|
|
225
|
+
break;
|
|
226
|
+
case 'response.custom_tool_call_input.delta':
|
|
227
|
+
appendCustomToolCallDelta(state, event.output_index, event.item_id, event.delta);
|
|
228
|
+
break;
|
|
229
|
+
case 'response.custom_tool_call_input.done':
|
|
230
|
+
setCustomToolCallInput(state, event.output_index, event.item_id, event.input);
|
|
231
|
+
break;
|
|
232
|
+
case 'response.output_item.done':
|
|
233
|
+
handleOutputItemDone(state, event.output_index, event.item);
|
|
234
|
+
break;
|
|
235
|
+
case 'response.completed':
|
|
236
|
+
case 'response.incomplete':
|
|
237
|
+
state.finalResponse = event.response;
|
|
238
|
+
break;
|
|
239
|
+
case 'response.failed':
|
|
240
|
+
state.finalResponse = event.response;
|
|
241
|
+
throw Object.assign(new Error(event.response.error?.message ?? 'OpenAI response failed.'), {
|
|
242
|
+
code: event.response.error?.code,
|
|
243
|
+
});
|
|
244
|
+
case 'error':
|
|
245
|
+
throw new Error(event.message);
|
|
246
|
+
default:
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function handleOutputItemAdded(state, outputIndex, item) {
|
|
251
|
+
switch (item.type) {
|
|
252
|
+
case 'message':
|
|
253
|
+
ensureResponseBlock(state, outputIndex, item.id);
|
|
254
|
+
break;
|
|
255
|
+
case 'reasoning':
|
|
256
|
+
ensureThinkingBlock(state, outputIndex, item.id);
|
|
257
|
+
break;
|
|
258
|
+
case 'function_call':
|
|
259
|
+
ensureFunctionCallBlock(state, outputIndex, item);
|
|
260
|
+
break;
|
|
261
|
+
case 'custom_tool_call':
|
|
262
|
+
ensureCustomToolCallBlock(state, outputIndex, item);
|
|
263
|
+
break;
|
|
264
|
+
default:
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function handleOutputItemDone(state, outputIndex, item) {
|
|
269
|
+
switch (item.type) {
|
|
270
|
+
case 'message':
|
|
271
|
+
finishResponseBlock(state, outputIndex, item);
|
|
272
|
+
break;
|
|
273
|
+
case 'reasoning':
|
|
274
|
+
finishThinkingBlock(state, outputIndex, item);
|
|
275
|
+
break;
|
|
276
|
+
case 'function_call':
|
|
277
|
+
finishFunctionCallBlock(state, outputIndex, item);
|
|
278
|
+
break;
|
|
279
|
+
case 'custom_tool_call':
|
|
280
|
+
finishCustomToolCallBlock(state, outputIndex, item);
|
|
281
|
+
break;
|
|
282
|
+
default:
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function ensureResponseBlock(state, outputIndex, itemId) {
|
|
287
|
+
const existing = state.statesByOutputIndex.get(outputIndex);
|
|
288
|
+
if (existing?.type === 'response') {
|
|
289
|
+
bindItemId(state, existing, itemId);
|
|
290
|
+
return existing;
|
|
291
|
+
}
|
|
292
|
+
const content = {
|
|
293
|
+
type: 'response',
|
|
294
|
+
response: {
|
|
295
|
+
type: 'text',
|
|
296
|
+
content: '',
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
const block = {
|
|
300
|
+
type: 'response',
|
|
301
|
+
contentIndex: state.content.push(content) - 1,
|
|
302
|
+
itemId,
|
|
303
|
+
content,
|
|
304
|
+
ended: false,
|
|
305
|
+
refusal: false,
|
|
306
|
+
};
|
|
307
|
+
registerState(state, outputIndex, block);
|
|
308
|
+
state.stream.push({ type: 'text_start', contentIndex: block.contentIndex });
|
|
309
|
+
return block;
|
|
310
|
+
}
|
|
311
|
+
function ensureThinkingBlock(state, outputIndex, itemId) {
|
|
312
|
+
const existing = state.statesByOutputIndex.get(outputIndex);
|
|
313
|
+
if (existing?.type === 'thinking') {
|
|
314
|
+
bindItemId(state, existing, itemId);
|
|
315
|
+
return existing;
|
|
316
|
+
}
|
|
317
|
+
const content = {
|
|
318
|
+
type: 'thinking',
|
|
319
|
+
thinkingText: '',
|
|
320
|
+
};
|
|
321
|
+
const block = {
|
|
322
|
+
type: 'thinking',
|
|
323
|
+
contentIndex: state.content.push(content) - 1,
|
|
324
|
+
itemId,
|
|
325
|
+
content,
|
|
326
|
+
ended: false,
|
|
327
|
+
};
|
|
328
|
+
registerState(state, outputIndex, block);
|
|
329
|
+
state.stream.push({ type: 'thinking_start', contentIndex: block.contentIndex });
|
|
330
|
+
return block;
|
|
331
|
+
}
|
|
332
|
+
function ensureFunctionCallBlock(state, outputIndex, item) {
|
|
333
|
+
const existing = state.statesByOutputIndex.get(outputIndex);
|
|
334
|
+
if (existing?.type === 'function_call') {
|
|
335
|
+
bindItemId(state, existing, item.id);
|
|
336
|
+
return existing;
|
|
337
|
+
}
|
|
338
|
+
const toolCall = {
|
|
339
|
+
type: 'toolCall',
|
|
340
|
+
toolCallId: item.call_id,
|
|
341
|
+
name: item.name,
|
|
342
|
+
arguments: parsePartialJsonObject(item.arguments),
|
|
343
|
+
};
|
|
344
|
+
const block = {
|
|
345
|
+
type: 'function_call',
|
|
346
|
+
contentIndex: state.content.push(toolCall) - 1,
|
|
347
|
+
itemId: item.id,
|
|
348
|
+
name: item.name,
|
|
349
|
+
partialJson: item.arguments,
|
|
350
|
+
toolCallId: item.call_id,
|
|
351
|
+
ended: false,
|
|
352
|
+
};
|
|
353
|
+
registerState(state, outputIndex, block);
|
|
354
|
+
state.stream.push({
|
|
355
|
+
type: 'toolcall_start',
|
|
356
|
+
contentIndex: block.contentIndex,
|
|
357
|
+
toolCallId: block.toolCallId,
|
|
358
|
+
name: block.name,
|
|
359
|
+
});
|
|
360
|
+
return block;
|
|
361
|
+
}
|
|
362
|
+
function ensureCustomToolCallBlock(state, outputIndex, item) {
|
|
363
|
+
const existing = state.statesByOutputIndex.get(outputIndex);
|
|
364
|
+
if (existing?.type === 'custom_tool_call') {
|
|
365
|
+
bindItemId(state, existing, item.id);
|
|
366
|
+
return existing;
|
|
367
|
+
}
|
|
368
|
+
const toolCall = {
|
|
369
|
+
type: 'toolCall',
|
|
370
|
+
toolCallId: item.call_id,
|
|
371
|
+
name: item.name,
|
|
372
|
+
arguments: { input: item.input },
|
|
373
|
+
};
|
|
374
|
+
const block = {
|
|
375
|
+
type: 'custom_tool_call',
|
|
376
|
+
contentIndex: state.content.push(toolCall) - 1,
|
|
377
|
+
itemId: item.id,
|
|
378
|
+
name: item.name,
|
|
379
|
+
partialInput: item.input,
|
|
380
|
+
toolCallId: item.call_id,
|
|
381
|
+
ended: false,
|
|
382
|
+
};
|
|
383
|
+
registerState(state, outputIndex, block);
|
|
384
|
+
state.stream.push({
|
|
385
|
+
type: 'toolcall_start',
|
|
386
|
+
contentIndex: block.contentIndex,
|
|
387
|
+
toolCallId: block.toolCallId,
|
|
388
|
+
name: block.name,
|
|
389
|
+
});
|
|
390
|
+
return block;
|
|
391
|
+
}
|
|
392
|
+
function appendTextDelta(state, outputIndex, itemId, delta, refusal) {
|
|
393
|
+
const block = ensureResponseBlock(state, outputIndex, itemId);
|
|
394
|
+
block.refusal = block.refusal || refusal;
|
|
395
|
+
block.content.response.content += delta;
|
|
396
|
+
state.stream.push({ type: 'text_delta', contentIndex: block.contentIndex, delta });
|
|
397
|
+
}
|
|
398
|
+
function setTextContent(state, outputIndex, itemId, text, refusal) {
|
|
399
|
+
const block = ensureResponseBlock(state, outputIndex, itemId);
|
|
400
|
+
block.refusal = block.refusal || refusal;
|
|
401
|
+
block.content.response.content = text;
|
|
402
|
+
}
|
|
403
|
+
function appendThinkingDelta(state, outputIndex, itemId, delta) {
|
|
404
|
+
const block = ensureThinkingBlock(state, outputIndex, itemId);
|
|
405
|
+
block.content.thinkingText += delta;
|
|
406
|
+
state.stream.push({ type: 'thinking_delta', contentIndex: block.contentIndex, delta });
|
|
407
|
+
}
|
|
408
|
+
function setThinkingText(state, outputIndex, itemId, text) {
|
|
409
|
+
const block = ensureThinkingBlock(state, outputIndex, itemId);
|
|
410
|
+
block.content.thinkingText = text;
|
|
411
|
+
}
|
|
412
|
+
function appendFunctionCallDelta(state, outputIndex, itemId, delta) {
|
|
413
|
+
const block = getBlockByOutputOrItem(state, outputIndex, itemId);
|
|
414
|
+
if (!block || block.type !== 'function_call') {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
bindItemId(state, block, itemId);
|
|
418
|
+
block.partialJson += delta;
|
|
419
|
+
const content = state.content[block.contentIndex];
|
|
420
|
+
if (content?.type === 'toolCall') {
|
|
421
|
+
content.arguments = parsePartialJsonObject(block.partialJson);
|
|
422
|
+
}
|
|
423
|
+
state.stream.push({ type: 'toolcall_delta', contentIndex: block.contentIndex, delta });
|
|
424
|
+
}
|
|
425
|
+
function setFunctionCallArguments(state, outputIndex, itemId, argumentsJson) {
|
|
426
|
+
const block = getBlockByOutputOrItem(state, outputIndex, itemId);
|
|
427
|
+
if (block?.type === 'function_call') {
|
|
428
|
+
bindItemId(state, block, itemId);
|
|
429
|
+
block.partialJson = argumentsJson;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
function appendCustomToolCallDelta(state, outputIndex, itemId, delta) {
|
|
433
|
+
const block = getBlockByOutputOrItem(state, outputIndex, itemId);
|
|
434
|
+
if (!block || block.type !== 'custom_tool_call') {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
bindItemId(state, block, itemId);
|
|
438
|
+
block.partialInput += delta;
|
|
439
|
+
const content = state.content[block.contentIndex];
|
|
440
|
+
if (content?.type === 'toolCall') {
|
|
441
|
+
content.arguments = { input: block.partialInput };
|
|
442
|
+
}
|
|
443
|
+
state.stream.push({ type: 'toolcall_delta', contentIndex: block.contentIndex, delta });
|
|
444
|
+
}
|
|
445
|
+
function setCustomToolCallInput(state, outputIndex, itemId, input) {
|
|
446
|
+
const block = getBlockByOutputOrItem(state, outputIndex, itemId);
|
|
447
|
+
if (block?.type === 'custom_tool_call') {
|
|
448
|
+
bindItemId(state, block, itemId);
|
|
449
|
+
block.partialInput = input;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function finishResponseBlock(state, outputIndex, item) {
|
|
453
|
+
const block = ensureResponseBlock(state, outputIndex, item.id);
|
|
454
|
+
const text = textFromOutputMessage(item);
|
|
455
|
+
block.refusal = block.refusal || item.content.some((part) => part.type === 'refusal');
|
|
456
|
+
state.sawRefusal = state.sawRefusal || block.refusal;
|
|
457
|
+
block.content.response.content = text;
|
|
458
|
+
finishTextBlock(state, block);
|
|
459
|
+
}
|
|
460
|
+
function finishThinkingBlock(state, outputIndex, item) {
|
|
461
|
+
const block = ensureThinkingBlock(state, outputIndex, item.id);
|
|
462
|
+
const text = thinkingTextFromReasoningItem(item);
|
|
463
|
+
if (text) {
|
|
464
|
+
block.content.thinkingText = text;
|
|
465
|
+
}
|
|
466
|
+
finishThinkingState(state, block);
|
|
467
|
+
}
|
|
468
|
+
function finishFunctionCallBlock(state, outputIndex, item, options = {}) {
|
|
469
|
+
const block = ensureFunctionCallBlock(state, outputIndex, item);
|
|
470
|
+
const args = options.allowInvalidToolJson
|
|
471
|
+
? parsePartialJsonObject(item.arguments || block.partialJson)
|
|
472
|
+
: parseJsonObject(item.arguments || block.partialJson);
|
|
473
|
+
const toolCall = {
|
|
474
|
+
type: 'toolCall',
|
|
475
|
+
toolCallId: item.call_id,
|
|
476
|
+
name: item.name,
|
|
477
|
+
arguments: args,
|
|
478
|
+
};
|
|
479
|
+
state.content[block.contentIndex] = toolCall;
|
|
480
|
+
finishToolCallBlock(state, block, toolCall);
|
|
481
|
+
}
|
|
482
|
+
function finishCustomToolCallBlock(state, outputIndex, item) {
|
|
483
|
+
const block = ensureCustomToolCallBlock(state, outputIndex, item);
|
|
484
|
+
const toolCall = {
|
|
485
|
+
type: 'toolCall',
|
|
486
|
+
toolCallId: item.call_id,
|
|
487
|
+
name: item.name,
|
|
488
|
+
arguments: { input: item.input },
|
|
489
|
+
};
|
|
490
|
+
state.content[block.contentIndex] = toolCall;
|
|
491
|
+
finishToolCallBlock(state, block, toolCall);
|
|
492
|
+
}
|
|
493
|
+
function finishTextBlock(state, block) {
|
|
494
|
+
if (block.ended) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
block.ended = true;
|
|
498
|
+
state.stream.push({
|
|
499
|
+
type: 'text_end',
|
|
500
|
+
contentIndex: block.contentIndex,
|
|
501
|
+
content: block.content,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
function finishThinkingState(state, block) {
|
|
505
|
+
if (block.ended) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
block.ended = true;
|
|
509
|
+
state.stream.push({
|
|
510
|
+
type: 'thinking_end',
|
|
511
|
+
contentIndex: block.contentIndex,
|
|
512
|
+
content: block.content,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
function finishToolCallBlock(state, block, toolCall) {
|
|
516
|
+
if (block.ended) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
block.ended = true;
|
|
520
|
+
state.stream.push({
|
|
521
|
+
type: 'toolcall_end',
|
|
522
|
+
contentIndex: block.contentIndex,
|
|
523
|
+
toolCall,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
function closeOpenBlocks(state, options = {}) {
|
|
527
|
+
for (const block of state.statesByOutputIndex.values()) {
|
|
528
|
+
if (block.ended) {
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
if (block.type === 'response') {
|
|
532
|
+
finishTextBlock(state, block);
|
|
533
|
+
}
|
|
534
|
+
else if (block.type === 'thinking') {
|
|
535
|
+
finishThinkingState(state, block);
|
|
536
|
+
}
|
|
537
|
+
else if (block.type === 'function_call') {
|
|
538
|
+
const args = options.allowInvalidToolJson
|
|
539
|
+
? parsePartialJsonObject(block.partialJson)
|
|
540
|
+
: parseJsonObject(block.partialJson);
|
|
541
|
+
const toolCall = {
|
|
542
|
+
type: 'toolCall',
|
|
543
|
+
toolCallId: block.toolCallId,
|
|
544
|
+
name: block.name,
|
|
545
|
+
arguments: args,
|
|
546
|
+
};
|
|
547
|
+
const content = state.content[block.contentIndex];
|
|
548
|
+
if (content?.type === 'toolCall') {
|
|
549
|
+
content.arguments = args;
|
|
550
|
+
}
|
|
551
|
+
finishToolCallBlock(state, block, toolCall);
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
const toolCall = {
|
|
555
|
+
type: 'toolCall',
|
|
556
|
+
toolCallId: block.toolCallId,
|
|
557
|
+
name: block.name,
|
|
558
|
+
arguments: { input: block.partialInput },
|
|
559
|
+
};
|
|
560
|
+
state.content[block.contentIndex] = toolCall;
|
|
561
|
+
finishToolCallBlock(state, block, toolCall);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function registerState(state, outputIndex, block) {
|
|
566
|
+
state.statesByOutputIndex.set(outputIndex, block);
|
|
567
|
+
if (block.itemId) {
|
|
568
|
+
state.statesByItemId.set(block.itemId, block);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
function bindItemId(state, block, itemId) {
|
|
572
|
+
if (!itemId || block.itemId === itemId) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
block.itemId = itemId;
|
|
576
|
+
state.statesByItemId.set(itemId, block);
|
|
577
|
+
}
|
|
578
|
+
function getBlockByOutputOrItem(state, outputIndex, itemId) {
|
|
579
|
+
return state.statesByOutputIndex.get(outputIndex) ?? state.statesByItemId.get(itemId);
|
|
580
|
+
}
|
|
581
|
+
function hydrateContentFromResponse(state, response) {
|
|
582
|
+
if (state.content.length > 0) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
for (const output of response.output) {
|
|
586
|
+
if (output.type === 'message') {
|
|
587
|
+
const text = textFromOutputMessage(output);
|
|
588
|
+
state.sawRefusal =
|
|
589
|
+
state.sawRefusal || output.content.some((part) => part.type === 'refusal');
|
|
590
|
+
state.content.push({
|
|
591
|
+
type: 'response',
|
|
592
|
+
response: { type: 'text', content: text },
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
else if (output.type === 'reasoning') {
|
|
596
|
+
state.content.push({
|
|
597
|
+
type: 'thinking',
|
|
598
|
+
thinkingText: thinkingTextFromReasoningItem(output),
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
else if (output.type === 'function_call') {
|
|
602
|
+
state.content.push({
|
|
603
|
+
type: 'toolCall',
|
|
604
|
+
name: output.name,
|
|
605
|
+
toolCallId: output.call_id,
|
|
606
|
+
arguments: parseJsonObject(output.arguments),
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
else if (output.type === 'custom_tool_call') {
|
|
610
|
+
state.content.push({
|
|
611
|
+
type: 'toolCall',
|
|
612
|
+
name: output.name,
|
|
613
|
+
toolCallId: output.call_id,
|
|
614
|
+
arguments: { input: output.input },
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function textFromOutputMessage(message) {
|
|
620
|
+
return message.content
|
|
621
|
+
.map((part) => (part.type === 'output_text' ? part.text : part.refusal))
|
|
622
|
+
.join('');
|
|
623
|
+
}
|
|
624
|
+
function thinkingTextFromReasoningItem(item) {
|
|
625
|
+
const summary = item.summary.map((part) => part.text).filter(Boolean).join('\n\n');
|
|
626
|
+
if (summary) {
|
|
627
|
+
return summary;
|
|
628
|
+
}
|
|
629
|
+
return item.content?.map((part) => part.text).filter(Boolean).join('\n\n') ?? '';
|
|
630
|
+
}
|
|
631
|
+
function parsePartialJsonObject(value) {
|
|
632
|
+
try {
|
|
633
|
+
return parseJsonObject(value);
|
|
634
|
+
}
|
|
635
|
+
catch {
|
|
636
|
+
return {};
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function parseJsonObject(value) {
|
|
640
|
+
const parsed = JSON.parse(value || '{}');
|
|
641
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
642
|
+
throw new Error('OpenAI function call arguments must be a JSON object.');
|
|
643
|
+
}
|
|
644
|
+
return parsed;
|
|
645
|
+
}
|
|
646
|
+
function mapDoneStopReason(response, content, sawRefusal) {
|
|
647
|
+
if (content.some((part) => part.type === 'toolCall')) {
|
|
648
|
+
return 'toolUse';
|
|
649
|
+
}
|
|
650
|
+
if (response.status === 'incomplete') {
|
|
651
|
+
return response.incomplete_details?.reason === 'content_filter'
|
|
652
|
+
? 'contentFilter'
|
|
653
|
+
: 'length';
|
|
654
|
+
}
|
|
655
|
+
if (sawRefusal) {
|
|
656
|
+
return 'refusal';
|
|
657
|
+
}
|
|
658
|
+
return 'stop';
|
|
659
|
+
}
|
|
660
|
+
function usageFromResponse(response, model) {
|
|
661
|
+
if (!response.usage) {
|
|
662
|
+
return undefined;
|
|
663
|
+
}
|
|
664
|
+
const cacheRead = response.usage.input_tokens_details.cached_tokens;
|
|
665
|
+
const input = Math.max(response.usage.input_tokens - cacheRead, 0);
|
|
666
|
+
const output = response.usage.output_tokens;
|
|
667
|
+
const cacheWrite = 0;
|
|
668
|
+
const cost = {
|
|
669
|
+
input: costForTokens(input, model.cost.input),
|
|
670
|
+
output: costForTokens(output, model.cost.output),
|
|
671
|
+
cacheRead: costForTokens(cacheRead, model.cost.cacheRead),
|
|
672
|
+
cacheWrite: costForTokens(cacheWrite, model.cost.cacheWrite),
|
|
673
|
+
total: 0,
|
|
674
|
+
};
|
|
675
|
+
cost.total = cost.input + cost.output + cost.cacheRead + cost.cacheWrite;
|
|
676
|
+
return {
|
|
677
|
+
input,
|
|
678
|
+
output,
|
|
679
|
+
cacheRead,
|
|
680
|
+
cacheWrite,
|
|
681
|
+
cost,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
function costForTokens(tokens, costPerMillion) {
|
|
685
|
+
return (tokens * costPerMillion) / 1_000_000;
|
|
686
|
+
}
|
|
687
|
+
function modelRef(state) {
|
|
688
|
+
return {
|
|
689
|
+
provider: OPENAI_PROVIDER,
|
|
690
|
+
id: state.request.modelId,
|
|
691
|
+
name: state.model.name,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function createDefaultMessageId() {
|
|
695
|
+
return `msg_${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}_${Math.random().toString(36).slice(2)}`}`;
|
|
696
|
+
}
|
|
697
|
+
//# sourceMappingURL=adapter.js.map
|