@adminforth/completion-adapter-openai-responses 1.0.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/.woodpecker/buildRelease.sh +9 -0
- package/.woodpecker/buildSlackNotify.sh +44 -0
- package/.woodpecker/release.yml +56 -0
- package/Changelog.md +6 -0
- package/LICENSE +21 -0
- package/README.md +47 -0
- package/build.log +4 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +613 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.js +1 -0
- package/index.ts +818 -0
- package/package.json +62 -0
- package/tsconfig.json +14 -0
- package/types.ts +42 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
11
|
+
var t = {};
|
|
12
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
13
|
+
t[p] = s[p];
|
|
14
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
15
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
16
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
17
|
+
t[p[i]] = s[p[i]];
|
|
18
|
+
}
|
|
19
|
+
return t;
|
|
20
|
+
};
|
|
21
|
+
import { AIMessage } from "@langchain/core/messages";
|
|
22
|
+
import { ChatOpenAI } from "@langchain/openai";
|
|
23
|
+
import { createMiddleware } from "langchain";
|
|
24
|
+
import { encoding_for_model } from "tiktoken";
|
|
25
|
+
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
26
|
+
const RAW_REQUEST_LOG_PREFIX = "[CompletionAdapterOpenAIResponses] Raw /responses request";
|
|
27
|
+
function extractOutputText(data) {
|
|
28
|
+
var _a;
|
|
29
|
+
let text = "";
|
|
30
|
+
for (const item of (_a = data.output) !== null && _a !== void 0 ? _a : []) {
|
|
31
|
+
if (item.type !== "message" || !Array.isArray(item.content))
|
|
32
|
+
continue;
|
|
33
|
+
for (const part of item.content) {
|
|
34
|
+
if (part.type === "output_text" && typeof part.text === "string") {
|
|
35
|
+
text += part.text;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return text;
|
|
40
|
+
}
|
|
41
|
+
function extractReasoning(data) {
|
|
42
|
+
var _a, _b, _c;
|
|
43
|
+
let reasoning = "";
|
|
44
|
+
for (const item of (_a = data.output) !== null && _a !== void 0 ? _a : []) {
|
|
45
|
+
if (item.type !== "reasoning")
|
|
46
|
+
continue;
|
|
47
|
+
for (const part of (_b = item.summary) !== null && _b !== void 0 ? _b : []) {
|
|
48
|
+
if ((part === null || part === void 0 ? void 0 : part.type) === "summary_text" && typeof part.text === "string") {
|
|
49
|
+
reasoning += part.text;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!reasoning) {
|
|
53
|
+
for (const part of (_c = item.content) !== null && _c !== void 0 ? _c : []) {
|
|
54
|
+
if ((part === null || part === void 0 ? void 0 : part.type) === "reasoning_text" && typeof part.text === "string") {
|
|
55
|
+
reasoning += part.text;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return reasoning || undefined;
|
|
61
|
+
}
|
|
62
|
+
function extractFunctionCall(data) {
|
|
63
|
+
var _a;
|
|
64
|
+
for (const item of (_a = data.output) !== null && _a !== void 0 ? _a : []) {
|
|
65
|
+
if (item.type === "function_call") {
|
|
66
|
+
return item;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
function extractUsedTokens(data) {
|
|
72
|
+
var _a, _b;
|
|
73
|
+
const usage = data.usage;
|
|
74
|
+
if (!usage) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
const inputCached = (_b = (_a = usage.input_tokens_details) === null || _a === void 0 ? void 0 : _a.cached_tokens) !== null && _b !== void 0 ? _b : 0;
|
|
78
|
+
return {
|
|
79
|
+
input_uncached: Math.max(usage.input_tokens - inputCached, 0),
|
|
80
|
+
input_cached: inputCached,
|
|
81
|
+
output: usage.output_tokens,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function executeToolCall(toolCall, tools) {
|
|
85
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
86
|
+
const tool = tools === null || tools === void 0 ? void 0 : tools.find((candidate) => candidate.name === toolCall.name);
|
|
87
|
+
if (!tool) {
|
|
88
|
+
throw new Error(`Tool "${toolCall.name}" not found`);
|
|
89
|
+
}
|
|
90
|
+
const toolResult = yield tool.handler(JSON.parse(toolCall.arguments));
|
|
91
|
+
if (typeof toolResult === "string")
|
|
92
|
+
return toolResult;
|
|
93
|
+
if (typeof toolResult === "undefined")
|
|
94
|
+
return "";
|
|
95
|
+
return JSON.stringify(toolResult);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function parseSseBlock(block) {
|
|
99
|
+
let event;
|
|
100
|
+
let data = "";
|
|
101
|
+
for (const rawLine of block.split("\n")) {
|
|
102
|
+
const line = rawLine.trimEnd();
|
|
103
|
+
if (!line)
|
|
104
|
+
continue;
|
|
105
|
+
if (line.startsWith("event:"))
|
|
106
|
+
event = line.slice(6).trim();
|
|
107
|
+
if (line.startsWith("data:"))
|
|
108
|
+
data += line.slice(5).trim();
|
|
109
|
+
}
|
|
110
|
+
return data ? { event, data } : null;
|
|
111
|
+
}
|
|
112
|
+
function getAgentReasoningEffort(purpose) {
|
|
113
|
+
return purpose === "summary" ? "minimal" : "low";
|
|
114
|
+
}
|
|
115
|
+
function buildReasoningConfig(params) {
|
|
116
|
+
var _a;
|
|
117
|
+
return Object.assign({ summary: "auto", effort: params.effort }, ((_a = params.reasoning) !== null && _a !== void 0 ? _a : {}));
|
|
118
|
+
}
|
|
119
|
+
function getTurnKey(context) {
|
|
120
|
+
return `${context.sessionId}:${context.turnId}`;
|
|
121
|
+
}
|
|
122
|
+
function getResponseId(message) {
|
|
123
|
+
var _a;
|
|
124
|
+
const metadata = message.response_metadata;
|
|
125
|
+
return (_a = metadata === null || metadata === void 0 ? void 0 : metadata.id) !== null && _a !== void 0 ? _a : null;
|
|
126
|
+
}
|
|
127
|
+
function getPreviousResponseId(modelSettings) {
|
|
128
|
+
return modelSettings === null || modelSettings === void 0 ? void 0 : modelSettings.previous_response_id;
|
|
129
|
+
}
|
|
130
|
+
function getContinuationMessages(messages, previousResponseId) {
|
|
131
|
+
var _a;
|
|
132
|
+
let continuationStartIndex = null;
|
|
133
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
134
|
+
const message = messages[index];
|
|
135
|
+
if (AIMessage.isInstance(message) &&
|
|
136
|
+
((_a = message.response_metadata) === null || _a === void 0 ? void 0 : _a.id) ===
|
|
137
|
+
previousResponseId) {
|
|
138
|
+
continuationStartIndex = index + 1;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (continuationStartIndex === null) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
return messages.slice(continuationStartIndex);
|
|
146
|
+
}
|
|
147
|
+
function createOpenAiResponsesContinuationMiddleware() {
|
|
148
|
+
const responseIdsByTurn = new Map();
|
|
149
|
+
return createMiddleware({
|
|
150
|
+
name: "OpenAiResponsesContinuationMiddleware",
|
|
151
|
+
wrapModelCall(request, handler) {
|
|
152
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
153
|
+
var _a;
|
|
154
|
+
const context = request.runtime.context;
|
|
155
|
+
const turnKey = getTurnKey(context);
|
|
156
|
+
const previousResponseId = (_a = getPreviousResponseId(request.modelSettings)) !== null && _a !== void 0 ? _a : responseIdsByTurn.get(turnKey);
|
|
157
|
+
const continuationMessages = previousResponseId
|
|
158
|
+
? getContinuationMessages(request.messages, previousResponseId)
|
|
159
|
+
: null;
|
|
160
|
+
const response = (yield handler(previousResponseId && continuationMessages
|
|
161
|
+
? Object.assign(Object.assign({}, request), { messages: continuationMessages, modelSettings: Object.assign(Object.assign({}, request.modelSettings), { previous_response_id: previousResponseId }) }) : request));
|
|
162
|
+
const responseId = getResponseId(response);
|
|
163
|
+
if (responseId) {
|
|
164
|
+
responseIdsByTurn.set(turnKey, responseId);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
responseIdsByTurn.delete(turnKey);
|
|
168
|
+
}
|
|
169
|
+
return response;
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
export default class CompletionAdapterOpenAIResponses {
|
|
175
|
+
constructor(options) {
|
|
176
|
+
this.activeAbortController = null;
|
|
177
|
+
this.complete = (requestOrContent_1, ...args_1) => __awaiter(this, [requestOrContent_1, ...args_1], void 0, function* (requestOrContent, maxTokens = 50, outputSchema, reasoningEffort = "low", toolsOrOnChunk, onChunk) {
|
|
178
|
+
var _a, _b, _c;
|
|
179
|
+
const request = typeof requestOrContent === "string"
|
|
180
|
+
? {
|
|
181
|
+
content: requestOrContent,
|
|
182
|
+
maxTokens,
|
|
183
|
+
outputSchema,
|
|
184
|
+
reasoningEffort,
|
|
185
|
+
tools: Array.isArray(toolsOrOnChunk) ? toolsOrOnChunk : undefined,
|
|
186
|
+
onChunk: typeof toolsOrOnChunk === "function"
|
|
187
|
+
? toolsOrOnChunk
|
|
188
|
+
: onChunk,
|
|
189
|
+
}
|
|
190
|
+
: requestOrContent;
|
|
191
|
+
const { content, maxTokens: requestMaxTokens = 50, outputSchema: requestOutputSchema, reasoningEffort: requestReasoningEffort = "low", tools, onChunk: streamChunkCallback, signal: requestSignal, } = request;
|
|
192
|
+
const model = this.options.model || "gpt-5-nano";
|
|
193
|
+
const isStreaming = typeof streamChunkCallback === "function";
|
|
194
|
+
const extra = this.options.extraRequestBodyParameters;
|
|
195
|
+
const _d = extra !== null && extra !== void 0 ? extra : {}, { reasoning: extraReasoning } = _d, extraWithoutReasoning = __rest(_d, ["reasoning"]);
|
|
196
|
+
let openAiTools = undefined;
|
|
197
|
+
if (tools && tools.length > 0) {
|
|
198
|
+
openAiTools = tools.map((tool) => ({
|
|
199
|
+
type: "function",
|
|
200
|
+
name: tool.name,
|
|
201
|
+
description: tool.description,
|
|
202
|
+
parameters: tool.input_schema,
|
|
203
|
+
strict: false,
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
const body = Object.assign({ model, input: content, max_output_tokens: requestMaxTokens, stream: isStreaming, text: requestOutputSchema
|
|
207
|
+
? {
|
|
208
|
+
format: Object.assign({ type: "json_schema" }, requestOutputSchema),
|
|
209
|
+
}
|
|
210
|
+
: {
|
|
211
|
+
format: {
|
|
212
|
+
type: "text",
|
|
213
|
+
},
|
|
214
|
+
}, reasoning: Object.assign({}, buildReasoningConfig({
|
|
215
|
+
reasoning: extraReasoning,
|
|
216
|
+
effort: requestReasoningEffort,
|
|
217
|
+
})), tools: openAiTools }, extraWithoutReasoning);
|
|
218
|
+
const serializedBody = JSON.stringify(body);
|
|
219
|
+
if (this.shouldDumpRawRequest()) {
|
|
220
|
+
this.dumpRawRequest(this.getResponsesUrl(), serializedBody);
|
|
221
|
+
}
|
|
222
|
+
const abortController = new AbortController();
|
|
223
|
+
this.activeAbortController = abortController;
|
|
224
|
+
const abortFromRequestSignal = () => abortController.abort(requestSignal === null || requestSignal === void 0 ? void 0 : requestSignal.reason);
|
|
225
|
+
if (requestSignal === null || requestSignal === void 0 ? void 0 : requestSignal.aborted) {
|
|
226
|
+
abortController.abort(requestSignal.reason);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
requestSignal === null || requestSignal === void 0 ? void 0 : requestSignal.addEventListener("abort", abortFromRequestSignal, { once: true });
|
|
230
|
+
}
|
|
231
|
+
let resp = null;
|
|
232
|
+
try {
|
|
233
|
+
resp = yield fetch(this.getResponsesUrl(), {
|
|
234
|
+
method: "POST",
|
|
235
|
+
headers: {
|
|
236
|
+
"Content-Type": "application/json",
|
|
237
|
+
Authorization: `Bearer ${this.options.openAiApiKey}`,
|
|
238
|
+
},
|
|
239
|
+
body: serializedBody,
|
|
240
|
+
signal: abortController.signal,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
if (this.activeAbortController === abortController) {
|
|
245
|
+
this.activeAbortController = null;
|
|
246
|
+
}
|
|
247
|
+
if (abortController.signal.aborted) {
|
|
248
|
+
return {
|
|
249
|
+
error: (error === null || error === void 0 ? void 0 : error.message) || "Generation aborted",
|
|
250
|
+
finishReason: "aborted",
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
error: (error === null || error === void 0 ? void 0 : error.message) || "OpenAI request failed",
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
requestSignal === null || requestSignal === void 0 ? void 0 : requestSignal.removeEventListener("abort", abortFromRequestSignal);
|
|
259
|
+
}
|
|
260
|
+
if (!resp) {
|
|
261
|
+
if (this.activeAbortController === abortController) {
|
|
262
|
+
this.activeAbortController = null;
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
error: "OpenAI request failed",
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
if (!resp.ok) {
|
|
270
|
+
let errorMessage = `OpenAI request failed with status ${resp.status}`;
|
|
271
|
+
try {
|
|
272
|
+
const errorData = (yield resp.json());
|
|
273
|
+
if ((_a = errorData.error) === null || _a === void 0 ? void 0 : _a.message)
|
|
274
|
+
errorMessage = errorData.error.message;
|
|
275
|
+
}
|
|
276
|
+
catch (_e) { }
|
|
277
|
+
return { error: errorMessage };
|
|
278
|
+
}
|
|
279
|
+
if (!isStreaming) {
|
|
280
|
+
const json = yield resp.json();
|
|
281
|
+
const data = json;
|
|
282
|
+
if (data.error) {
|
|
283
|
+
return { error: data.error.message };
|
|
284
|
+
}
|
|
285
|
+
const usedTokens = extractUsedTokens(data);
|
|
286
|
+
const toolCall = extractFunctionCall(data);
|
|
287
|
+
if (toolCall) {
|
|
288
|
+
try {
|
|
289
|
+
const toolResult = yield executeToolCall(toolCall, tools);
|
|
290
|
+
return {
|
|
291
|
+
content: toolResult,
|
|
292
|
+
finishReason: "tool_call",
|
|
293
|
+
used_tokens: usedTokens,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
return {
|
|
298
|
+
error: (error === null || error === void 0 ? void 0 : error.message) || "Tool execution failed",
|
|
299
|
+
finishReason: "tool_call",
|
|
300
|
+
used_tokens: usedTokens,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const parsedContent = extractOutputText(data);
|
|
305
|
+
return {
|
|
306
|
+
content: parsedContent,
|
|
307
|
+
finishReason: ((_b = data.incomplete_details) === null || _b === void 0 ? void 0 : _b.reason)
|
|
308
|
+
? data.incomplete_details.reason
|
|
309
|
+
: undefined,
|
|
310
|
+
used_tokens: usedTokens,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
if (!resp.body) {
|
|
314
|
+
return { error: "Response body is empty" };
|
|
315
|
+
}
|
|
316
|
+
const reader = resp.body.getReader();
|
|
317
|
+
const decoder = new TextDecoder("utf-8");
|
|
318
|
+
let buffer = "";
|
|
319
|
+
let fullContent = "";
|
|
320
|
+
let fullReasoning = "";
|
|
321
|
+
let finishReason;
|
|
322
|
+
let completedResponse;
|
|
323
|
+
let usedTokens;
|
|
324
|
+
const handleEvent = (event, eventType) => __awaiter(this, void 0, void 0, function* () {
|
|
325
|
+
var _a, _b, _c, _d;
|
|
326
|
+
const type = (event === null || event === void 0 ? void 0 : event.type) || eventType;
|
|
327
|
+
if (type === "response.output_text.delta") {
|
|
328
|
+
const delta = (event === null || event === void 0 ? void 0 : event.delta) || "";
|
|
329
|
+
if (!delta)
|
|
330
|
+
return;
|
|
331
|
+
fullContent += delta;
|
|
332
|
+
yield (streamChunkCallback === null || streamChunkCallback === void 0 ? void 0 : streamChunkCallback(delta, { type: "output", delta, text: fullContent }));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (type === "response.reasoning_summary_text.delta" ||
|
|
336
|
+
type === "response.reasoning_text.delta") {
|
|
337
|
+
const delta = (event === null || event === void 0 ? void 0 : event.delta) || "";
|
|
338
|
+
if (!delta)
|
|
339
|
+
return;
|
|
340
|
+
fullReasoning += delta;
|
|
341
|
+
yield (streamChunkCallback === null || streamChunkCallback === void 0 ? void 0 : streamChunkCallback(delta, {
|
|
342
|
+
type: "reasoning",
|
|
343
|
+
delta,
|
|
344
|
+
text: fullReasoning,
|
|
345
|
+
}));
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (type === "response.completed" || type === "response.incomplete") {
|
|
349
|
+
const response = event === null || event === void 0 ? void 0 : event.response;
|
|
350
|
+
if (!response)
|
|
351
|
+
return;
|
|
352
|
+
const finalContent = extractOutputText(response);
|
|
353
|
+
if (finalContent.startsWith(fullContent)) {
|
|
354
|
+
const delta = finalContent.slice(fullContent.length);
|
|
355
|
+
if (delta) {
|
|
356
|
+
fullContent = finalContent;
|
|
357
|
+
yield (streamChunkCallback === null || streamChunkCallback === void 0 ? void 0 : streamChunkCallback(delta, {
|
|
358
|
+
type: "output",
|
|
359
|
+
delta,
|
|
360
|
+
text: fullContent,
|
|
361
|
+
}));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const finalReasoning = extractReasoning(response) || "";
|
|
365
|
+
if (finalReasoning.startsWith(fullReasoning)) {
|
|
366
|
+
const delta = finalReasoning.slice(fullReasoning.length);
|
|
367
|
+
if (delta) {
|
|
368
|
+
fullReasoning = finalReasoning;
|
|
369
|
+
yield (streamChunkCallback === null || streamChunkCallback === void 0 ? void 0 : streamChunkCallback(delta, {
|
|
370
|
+
type: "reasoning",
|
|
371
|
+
delta,
|
|
372
|
+
text: fullReasoning,
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
finishReason =
|
|
377
|
+
((_a = response.incomplete_details) === null || _a === void 0 ? void 0 : _a.reason) || response.status || finishReason;
|
|
378
|
+
completedResponse = response;
|
|
379
|
+
usedTokens = extractUsedTokens(response);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (type === "response.failed") {
|
|
383
|
+
throw new Error(((_c = (_b = event === null || event === void 0 ? void 0 : event.response) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.message) ||
|
|
384
|
+
((_d = event === null || event === void 0 ? void 0 : event.error) === null || _d === void 0 ? void 0 : _d.message) ||
|
|
385
|
+
"Response failed");
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
try {
|
|
389
|
+
while (true) {
|
|
390
|
+
const { value, done } = yield reader.read();
|
|
391
|
+
if (done)
|
|
392
|
+
break;
|
|
393
|
+
buffer += decoder.decode(value, { stream: true });
|
|
394
|
+
const blocks = buffer.split("\n\n");
|
|
395
|
+
buffer = blocks.pop() || "";
|
|
396
|
+
for (const block of blocks) {
|
|
397
|
+
const parsedBlock = parseSseBlock(block);
|
|
398
|
+
if (!(parsedBlock === null || parsedBlock === void 0 ? void 0 : parsedBlock.data) || parsedBlock.data === "[DONE]")
|
|
399
|
+
continue;
|
|
400
|
+
let event;
|
|
401
|
+
try {
|
|
402
|
+
event = JSON.parse(parsedBlock.data);
|
|
403
|
+
}
|
|
404
|
+
catch (_f) {
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if ((_c = event === null || event === void 0 ? void 0 : event.error) === null || _c === void 0 ? void 0 : _c.message) {
|
|
408
|
+
return { error: event.error.message };
|
|
409
|
+
}
|
|
410
|
+
yield handleEvent(event, parsedBlock.event);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (buffer.trim()) {
|
|
414
|
+
const parsedBlock = parseSseBlock(buffer.trim());
|
|
415
|
+
if ((parsedBlock === null || parsedBlock === void 0 ? void 0 : parsedBlock.data) && parsedBlock.data !== "[DONE]") {
|
|
416
|
+
try {
|
|
417
|
+
yield handleEvent(JSON.parse(parsedBlock.data), parsedBlock.event);
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
return {
|
|
421
|
+
error: (error === null || error === void 0 ? void 0 : error.message) || "Streaming failed",
|
|
422
|
+
content: fullContent || undefined,
|
|
423
|
+
finishReason,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (completedResponse) {
|
|
429
|
+
const toolCall = extractFunctionCall(completedResponse);
|
|
430
|
+
if (toolCall) {
|
|
431
|
+
try {
|
|
432
|
+
const toolResult = yield executeToolCall(toolCall, tools);
|
|
433
|
+
if (toolResult) {
|
|
434
|
+
const delta = toolResult.startsWith(fullContent)
|
|
435
|
+
? toolResult.slice(fullContent.length)
|
|
436
|
+
: toolResult;
|
|
437
|
+
if (delta) {
|
|
438
|
+
yield (streamChunkCallback === null || streamChunkCallback === void 0 ? void 0 : streamChunkCallback(delta, {
|
|
439
|
+
type: "output",
|
|
440
|
+
delta,
|
|
441
|
+
text: toolResult,
|
|
442
|
+
}));
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return {
|
|
446
|
+
content: toolResult,
|
|
447
|
+
finishReason: "tool_call",
|
|
448
|
+
used_tokens: usedTokens,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
return {
|
|
453
|
+
error: (error === null || error === void 0 ? void 0 : error.message) || "Tool execution failed",
|
|
454
|
+
content: fullContent || undefined,
|
|
455
|
+
finishReason: "tool_call",
|
|
456
|
+
used_tokens: usedTokens,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
content: fullContent || undefined,
|
|
463
|
+
finishReason,
|
|
464
|
+
used_tokens: usedTokens,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
catch (error) {
|
|
468
|
+
if (abortController.signal.aborted) {
|
|
469
|
+
return {
|
|
470
|
+
error: (error === null || error === void 0 ? void 0 : error.message) || "Generation aborted",
|
|
471
|
+
content: fullContent || undefined,
|
|
472
|
+
finishReason: "aborted",
|
|
473
|
+
used_tokens: usedTokens,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
error: (error === null || error === void 0 ? void 0 : error.message) || "Streaming failed",
|
|
478
|
+
content: fullContent || undefined,
|
|
479
|
+
finishReason,
|
|
480
|
+
used_tokens: usedTokens,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
finally {
|
|
484
|
+
reader.releaseLock();
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
finally {
|
|
488
|
+
if (this.activeAbortController === abortController) {
|
|
489
|
+
this.activeAbortController = null;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
this.options = options;
|
|
494
|
+
try {
|
|
495
|
+
this.encoding = encoding_for_model((this.options.model || "gpt-5-nano"));
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
// console.warn(
|
|
499
|
+
// `Failed to initialize tiktoken tokenizer for model "${this.options.model}", falling back to "gpt-5-nano". Error:`,
|
|
500
|
+
// );
|
|
501
|
+
this.encoding = encoding_for_model("gpt-5-nano");
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
validate() {
|
|
505
|
+
if (!this.options.openAiApiKey) {
|
|
506
|
+
throw new Error("openAiApiKey is required");
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
measureTokensCount(content) {
|
|
510
|
+
return this.encoding.encode(content).length;
|
|
511
|
+
}
|
|
512
|
+
abort() {
|
|
513
|
+
var _a;
|
|
514
|
+
(_a = this.activeAbortController) === null || _a === void 0 ? void 0 : _a.abort();
|
|
515
|
+
}
|
|
516
|
+
isGenerationInProgress() {
|
|
517
|
+
return Boolean(this.activeAbortController);
|
|
518
|
+
}
|
|
519
|
+
getConfiguredBaseUrl() {
|
|
520
|
+
return this.options.baseUrl;
|
|
521
|
+
}
|
|
522
|
+
shouldUseComplitionApi() {
|
|
523
|
+
if (typeof this.options.useComplitionApi === "boolean") {
|
|
524
|
+
return this.options.useComplitionApi;
|
|
525
|
+
}
|
|
526
|
+
return Boolean(this.getConfiguredBaseUrl());
|
|
527
|
+
}
|
|
528
|
+
shouldDumpRawRequest() {
|
|
529
|
+
return this.options.dumpRawRequest === true;
|
|
530
|
+
}
|
|
531
|
+
getClientConfiguration() {
|
|
532
|
+
const configuredBaseUrl = this.getConfiguredBaseUrl();
|
|
533
|
+
const debugFetch = this.shouldDumpRawRequest()
|
|
534
|
+
? this.createResponsesDebugFetch()
|
|
535
|
+
: undefined;
|
|
536
|
+
if (!configuredBaseUrl && !debugFetch) {
|
|
537
|
+
return undefined;
|
|
538
|
+
}
|
|
539
|
+
return Object.assign(Object.assign({}, (configuredBaseUrl ? { baseURL: configuredBaseUrl } : {})), (debugFetch ? { fetch: debugFetch } : {}));
|
|
540
|
+
}
|
|
541
|
+
createResponsesDebugFetch() {
|
|
542
|
+
return (input, init) => __awaiter(this, void 0, void 0, function* () {
|
|
543
|
+
const url = this.getFetchUrl(input);
|
|
544
|
+
if (this.isResponsesUrl(url) && typeof (init === null || init === void 0 ? void 0 : init.body) === "string") {
|
|
545
|
+
this.dumpRawRequest(url, init.body);
|
|
546
|
+
}
|
|
547
|
+
return fetch(input, init);
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
getFetchUrl(input) {
|
|
551
|
+
if (typeof input === "string") {
|
|
552
|
+
return input;
|
|
553
|
+
}
|
|
554
|
+
if (input instanceof URL) {
|
|
555
|
+
return input.toString();
|
|
556
|
+
}
|
|
557
|
+
return input.url;
|
|
558
|
+
}
|
|
559
|
+
isResponsesUrl(url) {
|
|
560
|
+
try {
|
|
561
|
+
return new URL(url).pathname.endsWith("/responses");
|
|
562
|
+
}
|
|
563
|
+
catch (_a) {
|
|
564
|
+
return url.endsWith("/responses") || url.includes("/responses?");
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
dumpRawRequest(url, body) {
|
|
568
|
+
console.info(`${RAW_REQUEST_LOG_PREFIX} ${url}`);
|
|
569
|
+
try {
|
|
570
|
+
console.info(JSON.stringify(JSON.parse(body), null, 2));
|
|
571
|
+
}
|
|
572
|
+
catch (_a) {
|
|
573
|
+
console.info(body);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
getResponsesUrl() {
|
|
577
|
+
const baseUrl = this.getConfiguredBaseUrl() || DEFAULT_OPENAI_BASE_URL;
|
|
578
|
+
const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
579
|
+
return new URL("responses", normalizedBaseUrl).toString();
|
|
580
|
+
}
|
|
581
|
+
getLangChainAgentSpec(params) {
|
|
582
|
+
const extraRequestBodyParameters = (this.options.extraRequestBodyParameters || {});
|
|
583
|
+
const { reasoning } = extraRequestBodyParameters, modelKwargs = __rest(extraRequestBodyParameters, ["reasoning"]);
|
|
584
|
+
const configuredBaseUrl = this.getConfiguredBaseUrl();
|
|
585
|
+
const normalizedModelKwargs = Object.assign({}, modelKwargs);
|
|
586
|
+
const clientConfiguration = this.getClientConfiguration();
|
|
587
|
+
const useComplitionApi = this.shouldUseComplitionApi();
|
|
588
|
+
const chatOpenAiOptions = {
|
|
589
|
+
model: this.options.model || "gpt-5-nano",
|
|
590
|
+
apiKey: this.options.openAiApiKey,
|
|
591
|
+
maxTokens: params.maxTokens,
|
|
592
|
+
reasoning: buildReasoningConfig({
|
|
593
|
+
reasoning,
|
|
594
|
+
effort: getAgentReasoningEffort(params.purpose),
|
|
595
|
+
}),
|
|
596
|
+
modelKwargs: normalizedModelKwargs,
|
|
597
|
+
};
|
|
598
|
+
chatOpenAiOptions.useResponsesApi = !useComplitionApi;
|
|
599
|
+
let supportsResponseContinuation = true;
|
|
600
|
+
if (configuredBaseUrl || useComplitionApi) {
|
|
601
|
+
supportsResponseContinuation = false;
|
|
602
|
+
}
|
|
603
|
+
if (clientConfiguration) {
|
|
604
|
+
chatOpenAiOptions.configuration = clientConfiguration;
|
|
605
|
+
}
|
|
606
|
+
return {
|
|
607
|
+
model: new ChatOpenAI(chatOpenAiOptions),
|
|
608
|
+
middleware: params.purpose === "primary" && supportsResponseContinuation
|
|
609
|
+
? [createOpenAiResponsesContinuationMiddleware()]
|
|
610
|
+
: [],
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface AdapterOptions {
|
|
2
|
+
/**
|
|
3
|
+
* OpenAI API key. Go to https://platform.openai.com/, go to Dashboard -> API keys -> Create new secret key
|
|
4
|
+
* Paste value in your .env file OPENAI_API_KEY=your_key
|
|
5
|
+
* Set openAiApiKey: process.env.OPENAI_API_KEY to access it
|
|
6
|
+
*/
|
|
7
|
+
openAiApiKey: string;
|
|
8
|
+
/**
|
|
9
|
+
* Optional OpenAI-compatible base URL.
|
|
10
|
+
*
|
|
11
|
+
* Example: `https://oai.endpoints.kepler.ai.cloud.ovh.net/v1`
|
|
12
|
+
*/
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Forces LangChain agent mode to use the Chat Completions API instead of the
|
|
16
|
+
* Responses API.
|
|
17
|
+
*
|
|
18
|
+
* When omitted, the adapter keeps the current default behavior:
|
|
19
|
+
* - official OpenAI uses the Responses API
|
|
20
|
+
* - custom `baseUrl` providers use the Chat Completions API
|
|
21
|
+
*/
|
|
22
|
+
useComplitionApi?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Model name. Go to https://platform.openai.com/docs/models, select model and copy name.
|
|
25
|
+
* Default is `gpt-5-nano`.
|
|
26
|
+
*/
|
|
27
|
+
model?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Additional request body parameters to include in the API request.
|
|
30
|
+
*/
|
|
31
|
+
extraRequestBodyParameters?: Record<string, unknown>;
|
|
32
|
+
/**
|
|
33
|
+
* Logs the exact JSON body sent to the OpenAI Responses endpoint.
|
|
34
|
+
* Authorization headers are not logged.
|
|
35
|
+
*/
|
|
36
|
+
dumpRawRequest?: boolean;
|
|
37
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|