@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 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(`Failed to initialize tiktoken tokenizer for model "${this.options.model}", falling back to "gpt-5-nano". Error:`);
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
- `Failed to initialize tiktoken tokenizer for model "${this.options.model}", falling back to "gpt-5-nano". Error:`,
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.23",
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
  },