@adminforth/completion-adapter-openai-responses 2.0.23 → 2.0.24
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/dist/index.d.ts +9 -0
- package/dist/index.js +95 -1
- package/index.ts +133 -3
- package/package.json +5 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { AdapterOptions } from "./types.js";
|
|
2
2
|
import type { CompletionAdapter, CompletionStreamEvent, CompletionTool } from "adminforth";
|
|
3
|
+
import { ChatOpenAI } from "@langchain/openai";
|
|
3
4
|
export type { AdapterOptions } from "./types.js";
|
|
4
5
|
type StreamChunkCallback = (chunk: string, event?: CompletionStreamEvent) => void | Promise<void>;
|
|
5
6
|
type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
7
|
+
type AgentModelPurpose = "primary" | "summary";
|
|
6
8
|
type CompletionRequestInput = {
|
|
7
9
|
content: string;
|
|
8
10
|
maxTokens?: number;
|
|
@@ -17,6 +19,13 @@ export default class CompletionAdapterOpenAIResponses implements CompletionAdapt
|
|
|
17
19
|
constructor(options: AdapterOptions);
|
|
18
20
|
validate(): void;
|
|
19
21
|
measureTokensCount(content: string): number;
|
|
22
|
+
getLangChainAgentSpec(params: {
|
|
23
|
+
maxTokens: number;
|
|
24
|
+
purpose: AgentModelPurpose;
|
|
25
|
+
}): {
|
|
26
|
+
model: ChatOpenAI<import("@langchain/openai").ChatOpenAICallOptions>;
|
|
27
|
+
middleware: import("langchain").AgentMiddleware<undefined, undefined, unknown, readonly (import("@langchain/core/tools").ClientTool | import("@langchain/core/tools").ServerTool)[]>[];
|
|
28
|
+
};
|
|
20
29
|
complete: (requestOrContent: CompletionRequestInput | string, maxTokens?: number, outputSchema?: any, reasoningEffort?: ReasoningEffort, toolsOrOnChunk?: CompletionTool[] | StreamChunkCallback, onChunk?: StreamChunkCallback) => Promise<{
|
|
21
30
|
content?: string;
|
|
22
31
|
finishReason?: string;
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
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";
|
|
10
24
|
import { encoding_for_model } from "tiktoken";
|
|
11
25
|
function extractOutputText(data) {
|
|
12
26
|
var _a;
|
|
@@ -80,6 +94,64 @@ function parseSseBlock(block) {
|
|
|
80
94
|
}
|
|
81
95
|
return data ? { event, data } : null;
|
|
82
96
|
}
|
|
97
|
+
function getAgentReasoningEffort(purpose) {
|
|
98
|
+
return purpose === "summary" ? "minimal" : "low";
|
|
99
|
+
}
|
|
100
|
+
function getTurnKey(context) {
|
|
101
|
+
return `${context.sessionId}:${context.turnId}`;
|
|
102
|
+
}
|
|
103
|
+
function getResponseId(message) {
|
|
104
|
+
var _a;
|
|
105
|
+
const metadata = message.response_metadata;
|
|
106
|
+
return (_a = metadata === null || metadata === void 0 ? void 0 : metadata.id) !== null && _a !== void 0 ? _a : null;
|
|
107
|
+
}
|
|
108
|
+
function getPreviousResponseId(modelSettings) {
|
|
109
|
+
return modelSettings === null || modelSettings === void 0 ? void 0 : modelSettings.previous_response_id;
|
|
110
|
+
}
|
|
111
|
+
function getContinuationMessages(messages, previousResponseId) {
|
|
112
|
+
var _a;
|
|
113
|
+
let continuationStartIndex = null;
|
|
114
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
115
|
+
const message = messages[index];
|
|
116
|
+
if (AIMessage.isInstance(message) &&
|
|
117
|
+
((_a = message.response_metadata) === null || _a === void 0 ? void 0 : _a.id) ===
|
|
118
|
+
previousResponseId) {
|
|
119
|
+
continuationStartIndex = index + 1;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (continuationStartIndex === null) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return messages.slice(continuationStartIndex);
|
|
127
|
+
}
|
|
128
|
+
function createOpenAiResponsesContinuationMiddleware() {
|
|
129
|
+
const responseIdsByTurn = new Map();
|
|
130
|
+
return createMiddleware({
|
|
131
|
+
name: "OpenAiResponsesContinuationMiddleware",
|
|
132
|
+
wrapModelCall(request, handler) {
|
|
133
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
134
|
+
var _a;
|
|
135
|
+
const context = request.runtime.context;
|
|
136
|
+
const turnKey = getTurnKey(context);
|
|
137
|
+
const previousResponseId = (_a = getPreviousResponseId(request.modelSettings)) !== null && _a !== void 0 ? _a : responseIdsByTurn.get(turnKey);
|
|
138
|
+
const continuationMessages = previousResponseId
|
|
139
|
+
? getContinuationMessages(request.messages, previousResponseId)
|
|
140
|
+
: null;
|
|
141
|
+
const response = (yield handler(previousResponseId && continuationMessages
|
|
142
|
+
? Object.assign(Object.assign({}, request), { messages: continuationMessages, modelSettings: Object.assign(Object.assign({}, request.modelSettings), { previous_response_id: previousResponseId }) }) : request));
|
|
143
|
+
const responseId = getResponseId(response);
|
|
144
|
+
if (responseId) {
|
|
145
|
+
responseIdsByTurn.set(turnKey, responseId);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
responseIdsByTurn.delete(turnKey);
|
|
149
|
+
}
|
|
150
|
+
return response;
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
83
155
|
export default class CompletionAdapterOpenAIResponses {
|
|
84
156
|
constructor(options) {
|
|
85
157
|
this.complete = (requestOrContent_1, ...args_1) => __awaiter(this, [requestOrContent_1, ...args_1], void 0, function* (requestOrContent, maxTokens = 50, outputSchema, reasoningEffort = "low", toolsOrOnChunk, onChunk) {
|
|
@@ -335,7 +407,9 @@ export default class CompletionAdapterOpenAIResponses {
|
|
|
335
407
|
this.encoding = encoding_for_model((this.options.model || "gpt-5-nano"));
|
|
336
408
|
}
|
|
337
409
|
catch (error) {
|
|
338
|
-
console.warn(
|
|
410
|
+
// console.warn(
|
|
411
|
+
// `Failed to initialize tiktoken tokenizer for model "${this.options.model}", falling back to "gpt-5-nano". Error:`,
|
|
412
|
+
// );
|
|
339
413
|
this.encoding = encoding_for_model("gpt-5-nano");
|
|
340
414
|
}
|
|
341
415
|
}
|
|
@@ -347,4 +421,24 @@ export default class CompletionAdapterOpenAIResponses {
|
|
|
347
421
|
measureTokensCount(content) {
|
|
348
422
|
return this.encoding.encode(content).length;
|
|
349
423
|
}
|
|
424
|
+
getLangChainAgentSpec(params) {
|
|
425
|
+
const extraRequestBodyParameters = (this.options.extraRequestBodyParameters || {});
|
|
426
|
+
const { reasoning } = extraRequestBodyParameters, modelKwargs = __rest(extraRequestBodyParameters, ["reasoning"]);
|
|
427
|
+
return {
|
|
428
|
+
model: new ChatOpenAI({
|
|
429
|
+
model: this.options.model || "gpt-5-nano",
|
|
430
|
+
apiKey: this.options.openAiApiKey,
|
|
431
|
+
useResponsesApi: true,
|
|
432
|
+
maxTokens: params.maxTokens,
|
|
433
|
+
reasoning: reasoning !== null && reasoning !== void 0 ? reasoning : {
|
|
434
|
+
effort: getAgentReasoningEffort(params.purpose),
|
|
435
|
+
summary: "auto",
|
|
436
|
+
},
|
|
437
|
+
modelKwargs,
|
|
438
|
+
}),
|
|
439
|
+
middleware: params.purpose === "primary"
|
|
440
|
+
? [createOpenAiResponsesContinuationMiddleware()]
|
|
441
|
+
: [],
|
|
442
|
+
};
|
|
443
|
+
}
|
|
350
444
|
}
|
package/index.ts
CHANGED
|
@@ -4,6 +4,9 @@ import type {
|
|
|
4
4
|
CompletionStreamEvent,
|
|
5
5
|
CompletionTool,
|
|
6
6
|
} from "adminforth";
|
|
7
|
+
import { AIMessage } from "@langchain/core/messages";
|
|
8
|
+
import { ChatOpenAI } from "@langchain/openai";
|
|
9
|
+
import { createMiddleware } from "langchain";
|
|
7
10
|
import { encoding_for_model, type TiktokenModel } from "tiktoken";
|
|
8
11
|
import type OpenAI from "openai";
|
|
9
12
|
|
|
@@ -22,6 +25,8 @@ type ReasoningEffort =
|
|
|
22
25
|
| "high"
|
|
23
26
|
| "xhigh";
|
|
24
27
|
|
|
28
|
+
type AgentModelPurpose = "primary" | "summary";
|
|
29
|
+
|
|
25
30
|
type CompletionRequestInput = {
|
|
26
31
|
content: string;
|
|
27
32
|
maxTokens?: number;
|
|
@@ -47,6 +52,15 @@ type OpenAIFunctionCall = Extract<
|
|
|
47
52
|
{ type: "function_call" }
|
|
48
53
|
>;
|
|
49
54
|
|
|
55
|
+
type OpenAiResponsesMetadata = {
|
|
56
|
+
id?: string;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
type OpenAiResponsesContext = {
|
|
60
|
+
sessionId: string;
|
|
61
|
+
turnId: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
50
64
|
function extractOutputText(data: OpenAIResponsesSuccess): string {
|
|
51
65
|
let text = "";
|
|
52
66
|
|
|
@@ -127,6 +141,93 @@ function parseSseBlock(block: string) {
|
|
|
127
141
|
return data ? { event, data } : null;
|
|
128
142
|
}
|
|
129
143
|
|
|
144
|
+
function getAgentReasoningEffort(
|
|
145
|
+
purpose: AgentModelPurpose,
|
|
146
|
+
): Exclude<ReasoningEffort, "none"> {
|
|
147
|
+
return purpose === "summary" ? "minimal" : "low";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getTurnKey(context: OpenAiResponsesContext) {
|
|
151
|
+
return `${context.sessionId}:${context.turnId}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getResponseId(message: AIMessage) {
|
|
155
|
+
const metadata = message.response_metadata as OpenAiResponsesMetadata | undefined;
|
|
156
|
+
return metadata?.id ?? null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function getPreviousResponseId(modelSettings?: Record<string, unknown>) {
|
|
160
|
+
return (modelSettings as { previous_response_id?: string } | undefined)
|
|
161
|
+
?.previous_response_id;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function getContinuationMessages<T extends { response_metadata?: unknown }>(
|
|
165
|
+
messages: T[],
|
|
166
|
+
previousResponseId: string,
|
|
167
|
+
) {
|
|
168
|
+
let continuationStartIndex: number | null = null;
|
|
169
|
+
|
|
170
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
171
|
+
const message = messages[index];
|
|
172
|
+
|
|
173
|
+
if (
|
|
174
|
+
AIMessage.isInstance(message) &&
|
|
175
|
+
(message.response_metadata as OpenAiResponsesMetadata | undefined)?.id ===
|
|
176
|
+
previousResponseId
|
|
177
|
+
) {
|
|
178
|
+
continuationStartIndex = index + 1;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (continuationStartIndex === null) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return messages.slice(continuationStartIndex);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function createOpenAiResponsesContinuationMiddleware() {
|
|
191
|
+
const responseIdsByTurn = new Map<string, string>();
|
|
192
|
+
|
|
193
|
+
return createMiddleware({
|
|
194
|
+
name: "OpenAiResponsesContinuationMiddleware",
|
|
195
|
+
async wrapModelCall(request, handler) {
|
|
196
|
+
const context = request.runtime.context as OpenAiResponsesContext;
|
|
197
|
+
const turnKey = getTurnKey(context);
|
|
198
|
+
const previousResponseId =
|
|
199
|
+
getPreviousResponseId(request.modelSettings) ??
|
|
200
|
+
responseIdsByTurn.get(turnKey);
|
|
201
|
+
const continuationMessages = previousResponseId
|
|
202
|
+
? getContinuationMessages(request.messages, previousResponseId)
|
|
203
|
+
: null;
|
|
204
|
+
|
|
205
|
+
const response = (await handler(
|
|
206
|
+
previousResponseId && continuationMessages
|
|
207
|
+
? {
|
|
208
|
+
...request,
|
|
209
|
+
messages: continuationMessages,
|
|
210
|
+
modelSettings: {
|
|
211
|
+
...request.modelSettings,
|
|
212
|
+
previous_response_id: previousResponseId,
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
: request,
|
|
216
|
+
)) as AIMessage;
|
|
217
|
+
|
|
218
|
+
const responseId = getResponseId(response);
|
|
219
|
+
|
|
220
|
+
if (responseId) {
|
|
221
|
+
responseIdsByTurn.set(turnKey, responseId);
|
|
222
|
+
} else {
|
|
223
|
+
responseIdsByTurn.delete(turnKey);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return response;
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
130
231
|
export default class CompletionAdapterOpenAIResponses
|
|
131
232
|
implements CompletionAdapter
|
|
132
233
|
{
|
|
@@ -140,9 +241,9 @@ export default class CompletionAdapterOpenAIResponses
|
|
|
140
241
|
(this.options.model || "gpt-5-nano") as TiktokenModel,
|
|
141
242
|
);
|
|
142
243
|
} catch (error) {
|
|
143
|
-
console.warn(
|
|
144
|
-
|
|
145
|
-
);
|
|
244
|
+
// console.warn(
|
|
245
|
+
// `Failed to initialize tiktoken tokenizer for model "${this.options.model}", falling back to "gpt-5-nano". Error:`,
|
|
246
|
+
// );
|
|
146
247
|
this.encoding = encoding_for_model("gpt-5-nano" as TiktokenModel);
|
|
147
248
|
}
|
|
148
249
|
}
|
|
@@ -157,6 +258,35 @@ export default class CompletionAdapterOpenAIResponses
|
|
|
157
258
|
return this.encoding.encode(content).length;
|
|
158
259
|
}
|
|
159
260
|
|
|
261
|
+
getLangChainAgentSpec(params: {
|
|
262
|
+
maxTokens: number;
|
|
263
|
+
purpose: AgentModelPurpose;
|
|
264
|
+
}) {
|
|
265
|
+
const extraRequestBodyParameters =
|
|
266
|
+
(this.options.extraRequestBodyParameters || {}) as Record<string, unknown> & {
|
|
267
|
+
reasoning?: Record<string, unknown>;
|
|
268
|
+
};
|
|
269
|
+
const { reasoning, ...modelKwargs } = extraRequestBodyParameters;
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
model: new ChatOpenAI({
|
|
273
|
+
model: this.options.model || "gpt-5-nano",
|
|
274
|
+
apiKey: this.options.openAiApiKey,
|
|
275
|
+
useResponsesApi: true,
|
|
276
|
+
maxTokens: params.maxTokens,
|
|
277
|
+
reasoning: reasoning ?? {
|
|
278
|
+
effort: getAgentReasoningEffort(params.purpose),
|
|
279
|
+
summary: "auto",
|
|
280
|
+
},
|
|
281
|
+
modelKwargs,
|
|
282
|
+
} as any),
|
|
283
|
+
middleware:
|
|
284
|
+
params.purpose === "primary"
|
|
285
|
+
? [createOpenAiResponsesContinuationMiddleware()]
|
|
286
|
+
: [],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
160
290
|
complete = async (
|
|
161
291
|
requestOrContent: CompletionRequestInput | string,
|
|
162
292
|
maxTokens = 50,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminforth/completion-adapter-openai-responses",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.24",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -9,13 +9,16 @@
|
|
|
9
9
|
"rollout": "npm run build && npm publish --access public"
|
|
10
10
|
},
|
|
11
11
|
"keywords": [],
|
|
12
|
-
"author": "",
|
|
12
|
+
"author": "DevForth (https://devforth.io)",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"description": "AdminForth completion adapter for the OpenAI Responses API.",
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"typescript": "^5.9.3"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"@langchain/core": "^1.1.41",
|
|
20
|
+
"@langchain/openai": "1.4.4",
|
|
21
|
+
"langchain": "^1.3.4",
|
|
19
22
|
"openai": "^6.34.0",
|
|
20
23
|
"tiktoken": "^1.0.22"
|
|
21
24
|
},
|