@adminforth/completion-adapter-openai-responses 2.0.21 → 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,14 +1,32 @@
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>;
6
+ type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
7
+ type AgentModelPurpose = "primary" | "summary";
8
+ type CompletionRequestInput = {
9
+ content: string;
10
+ maxTokens?: number;
11
+ outputSchema?: any;
12
+ reasoningEffort?: ReasoningEffort;
13
+ tools?: CompletionTool[];
14
+ onChunk?: StreamChunkCallback;
15
+ };
5
16
  export default class CompletionAdapterOpenAIResponses implements CompletionAdapter {
6
17
  options: AdapterOptions;
7
18
  private encoding;
8
19
  constructor(options: AdapterOptions);
9
20
  validate(): void;
10
21
  measureTokensCount(content: string): number;
11
- complete: (content: string, maxTokens?: number, outputSchema?: any, reasoningEffort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh", toolsOrOnChunk?: CompletionTool[] | StreamChunkCallback, onChunk?: StreamChunkCallback) => Promise<{
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
+ };
29
+ complete: (requestOrContent: CompletionRequestInput | string, maxTokens?: number, outputSchema?: any, reasoningEffort?: ReasoningEffort, toolsOrOnChunk?: CompletionTool[] | StreamChunkCallback, onChunk?: StreamChunkCallback) => Promise<{
12
30
  content?: string;
13
31
  finishReason?: string;
14
32
  error?: 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,13 +94,82 @@ 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
- this.complete = (content_1, ...args_1) => __awaiter(this, [content_1, ...args_1], void 0, function* (content, maxTokens = 50, outputSchema, reasoningEffort = "low", toolsOrOnChunk, onChunk) {
157
+ this.complete = (requestOrContent_1, ...args_1) => __awaiter(this, [requestOrContent_1, ...args_1], void 0, function* (requestOrContent, maxTokens = 50, outputSchema, reasoningEffort = "low", toolsOrOnChunk, onChunk) {
86
158
  var _a, _b, _c;
159
+ const request = typeof requestOrContent === "string"
160
+ ? {
161
+ content: requestOrContent,
162
+ maxTokens,
163
+ outputSchema,
164
+ reasoningEffort,
165
+ tools: Array.isArray(toolsOrOnChunk) ? toolsOrOnChunk : undefined,
166
+ onChunk: typeof toolsOrOnChunk === "function"
167
+ ? toolsOrOnChunk
168
+ : onChunk,
169
+ }
170
+ : requestOrContent;
171
+ const { content, maxTokens: requestMaxTokens = 50, outputSchema: requestOutputSchema, reasoningEffort: requestReasoningEffort = "low", tools, onChunk: streamChunkCallback, } = request;
87
172
  const model = this.options.model || "gpt-5-nano";
88
- const tools = Array.isArray(toolsOrOnChunk) ? toolsOrOnChunk : undefined;
89
- const streamChunkCallback = typeof toolsOrOnChunk === "function" ? toolsOrOnChunk : onChunk;
90
173
  const isStreaming = typeof streamChunkCallback === "function";
91
174
  const extra = this.options.extraRequestBodyParameters;
92
175
  let openAiTools = undefined;
@@ -99,16 +182,16 @@ export default class CompletionAdapterOpenAIResponses {
99
182
  strict: true,
100
183
  }));
101
184
  }
102
- const body = Object.assign({ model, input: content, max_output_tokens: maxTokens, stream: isStreaming, text: outputSchema
185
+ const body = Object.assign({ model, input: content, max_output_tokens: requestMaxTokens, stream: isStreaming, text: requestOutputSchema
103
186
  ? {
104
- format: Object.assign({ type: "json_schema" }, outputSchema),
187
+ format: Object.assign({ type: "json_schema" }, requestOutputSchema),
105
188
  }
106
189
  : {
107
190
  format: {
108
191
  type: "text",
109
192
  },
110
193
  }, reasoning: {
111
- effort: reasoningEffort,
194
+ effort: requestReasoningEffort,
112
195
  summary: "auto",
113
196
  }, tools: openAiTools }, extra);
114
197
  const resp = yield fetch("https://api.openai.com/v1/responses", {
@@ -324,7 +407,9 @@ export default class CompletionAdapterOpenAIResponses {
324
407
  this.encoding = encoding_for_model((this.options.model || "gpt-5-nano"));
325
408
  }
326
409
  catch (error) {
327
- 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
+ // );
328
413
  this.encoding = encoding_for_model("gpt-5-nano");
329
414
  }
330
415
  }
@@ -336,4 +421,24 @@ export default class CompletionAdapterOpenAIResponses {
336
421
  measureTokensCount(content) {
337
422
  return this.encoding.encode(content).length;
338
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
+ }
339
444
  }
package/index.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  import type { AdapterOptions } from "./types.js";
2
- import type { CompletionAdapter, CompletionStreamEvent, CompletionTool } from "adminforth";
2
+ import type {
3
+ CompletionAdapter,
4
+ CompletionStreamEvent,
5
+ CompletionTool,
6
+ } from "adminforth";
7
+ import { AIMessage } from "@langchain/core/messages";
8
+ import { ChatOpenAI } from "@langchain/openai";
9
+ import { createMiddleware } from "langchain";
3
10
  import { encoding_for_model, type TiktokenModel } from "tiktoken";
4
11
  import type OpenAI from "openai";
5
12
 
@@ -10,6 +17,25 @@ type StreamChunkCallback = (
10
17
  event?: CompletionStreamEvent,
11
18
  ) => void | Promise<void>;
12
19
 
20
+ type ReasoningEffort =
21
+ | "none"
22
+ | "minimal"
23
+ | "low"
24
+ | "medium"
25
+ | "high"
26
+ | "xhigh";
27
+
28
+ type AgentModelPurpose = "primary" | "summary";
29
+
30
+ type CompletionRequestInput = {
31
+ content: string;
32
+ maxTokens?: number;
33
+ outputSchema?: any;
34
+ reasoningEffort?: ReasoningEffort;
35
+ tools?: CompletionTool[];
36
+ onChunk?: StreamChunkCallback;
37
+ };
38
+
13
39
  type ResponseCreateBody = OpenAI.Responses.ResponseCreateParams;
14
40
  type OpenAIResponsesSuccess = OpenAI.Responses.Response;
15
41
  type OpenAIErrorResponse = {
@@ -26,6 +52,15 @@ type OpenAIFunctionCall = Extract<
26
52
  { type: "function_call" }
27
53
  >;
28
54
 
55
+ type OpenAiResponsesMetadata = {
56
+ id?: string;
57
+ };
58
+
59
+ type OpenAiResponsesContext = {
60
+ sessionId: string;
61
+ turnId: string;
62
+ };
63
+
29
64
  function extractOutputText(data: OpenAIResponsesSuccess): string {
30
65
  let text = "";
31
66
 
@@ -106,6 +141,93 @@ function parseSseBlock(block: string) {
106
141
  return data ? { event, data } : null;
107
142
  }
108
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
+
109
231
  export default class CompletionAdapterOpenAIResponses
110
232
  implements CompletionAdapter
111
233
  {
@@ -119,9 +241,9 @@ export default class CompletionAdapterOpenAIResponses
119
241
  (this.options.model || "gpt-5-nano") as TiktokenModel,
120
242
  );
121
243
  } catch (error) {
122
- console.warn(
123
- `Failed to initialize tiktoken tokenizer for model "${this.options.model}", falling back to "gpt-5-nano". Error:`,
124
- );
244
+ // console.warn(
245
+ // `Failed to initialize tiktoken tokenizer for model "${this.options.model}", falling back to "gpt-5-nano". Error:`,
246
+ // );
125
247
  this.encoding = encoding_for_model("gpt-5-nano" as TiktokenModel);
126
248
  }
127
249
  }
@@ -136,11 +258,40 @@ export default class CompletionAdapterOpenAIResponses
136
258
  return this.encoding.encode(content).length;
137
259
  }
138
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
+
139
290
  complete = async (
140
- content: string,
291
+ requestOrContent: CompletionRequestInput | string,
141
292
  maxTokens = 50,
142
293
  outputSchema?: any,
143
- reasoningEffort: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" = "low",
294
+ reasoningEffort: ReasoningEffort = "low",
144
295
  toolsOrOnChunk?: CompletionTool[] | StreamChunkCallback,
145
296
  onChunk?: StreamChunkCallback,
146
297
  ): Promise<{
@@ -148,10 +299,29 @@ export default class CompletionAdapterOpenAIResponses
148
299
  finishReason?: string;
149
300
  error?: string;
150
301
  }> => {
302
+ const request =
303
+ typeof requestOrContent === "string"
304
+ ? {
305
+ content: requestOrContent,
306
+ maxTokens,
307
+ outputSchema,
308
+ reasoningEffort,
309
+ tools: Array.isArray(toolsOrOnChunk) ? toolsOrOnChunk : undefined,
310
+ onChunk:
311
+ typeof toolsOrOnChunk === "function"
312
+ ? toolsOrOnChunk
313
+ : onChunk,
314
+ }
315
+ : requestOrContent;
316
+ const {
317
+ content,
318
+ maxTokens: requestMaxTokens = 50,
319
+ outputSchema: requestOutputSchema,
320
+ reasoningEffort: requestReasoningEffort = "low",
321
+ tools,
322
+ onChunk: streamChunkCallback,
323
+ } = request;
151
324
  const model = this.options.model || "gpt-5-nano";
152
- const tools = Array.isArray(toolsOrOnChunk) ? toolsOrOnChunk : undefined;
153
- const streamChunkCallback =
154
- typeof toolsOrOnChunk === "function" ? toolsOrOnChunk : onChunk;
155
325
  const isStreaming = typeof streamChunkCallback === "function";
156
326
  const extra = this.options.extraRequestBodyParameters;
157
327
  let openAiTools: OpenAITool[] | undefined = undefined;
@@ -168,13 +338,13 @@ export default class CompletionAdapterOpenAIResponses
168
338
  const body = {
169
339
  model,
170
340
  input: content,
171
- max_output_tokens: maxTokens,
341
+ max_output_tokens: requestMaxTokens,
172
342
  stream: isStreaming,
173
- text: outputSchema
343
+ text: requestOutputSchema
174
344
  ? {
175
345
  format: {
176
346
  type: "json_schema",
177
- ...outputSchema,
347
+ ...requestOutputSchema,
178
348
  },
179
349
  }
180
350
  : {
@@ -183,7 +353,7 @@ export default class CompletionAdapterOpenAIResponses
183
353
  },
184
354
  },
185
355
  reasoning: {
186
- effort: reasoningEffort,
356
+ effort: requestReasoningEffort,
187
357
  summary: "auto",
188
358
  },
189
359
  tools: openAiTools,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/completion-adapter-openai-responses",
3
- "version": "2.0.21",
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
  },