@adminforth/completion-adapter-open-ai-chat-gpt 2.0.11 → 2.0.14
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.js +174 -70
- package/index.ts +209 -91
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -8,48 +8,112 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { encoding_for_model } from "tiktoken";
|
|
11
|
+
function extractOutputText(data) {
|
|
12
|
+
var _a;
|
|
13
|
+
let text = "";
|
|
14
|
+
for (const item of (_a = data.output) !== null && _a !== void 0 ? _a : []) {
|
|
15
|
+
if (item.type !== "message" || !Array.isArray(item.content))
|
|
16
|
+
continue;
|
|
17
|
+
for (const part of item.content) {
|
|
18
|
+
if (part.type === "output_text" && typeof part.text === "string") {
|
|
19
|
+
text += part.text;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return text;
|
|
24
|
+
}
|
|
25
|
+
function extractReasoning(data) {
|
|
26
|
+
var _a, _b, _c;
|
|
27
|
+
let reasoning = "";
|
|
28
|
+
for (const item of (_a = data.output) !== null && _a !== void 0 ? _a : []) {
|
|
29
|
+
if (item.type !== "reasoning")
|
|
30
|
+
continue;
|
|
31
|
+
for (const part of (_b = item.summary) !== null && _b !== void 0 ? _b : []) {
|
|
32
|
+
if ((part === null || part === void 0 ? void 0 : part.type) === "summary_text" && typeof part.text === "string") {
|
|
33
|
+
reasoning += part.text;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (!reasoning) {
|
|
37
|
+
for (const part of (_c = item.content) !== null && _c !== void 0 ? _c : []) {
|
|
38
|
+
if ((part === null || part === void 0 ? void 0 : part.type) === "reasoning_text" && typeof part.text === "string") {
|
|
39
|
+
reasoning += part.text;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return reasoning || undefined;
|
|
45
|
+
}
|
|
46
|
+
function parseSseBlock(block) {
|
|
47
|
+
let event;
|
|
48
|
+
let data = "";
|
|
49
|
+
for (const rawLine of block.split("\n")) {
|
|
50
|
+
const line = rawLine.trimEnd();
|
|
51
|
+
if (!line)
|
|
52
|
+
continue;
|
|
53
|
+
if (line.startsWith("event:"))
|
|
54
|
+
event = line.slice(6).trim();
|
|
55
|
+
if (line.startsWith("data:"))
|
|
56
|
+
data += line.slice(5).trim();
|
|
57
|
+
}
|
|
58
|
+
return data ? { event, data } : null;
|
|
59
|
+
}
|
|
11
60
|
export default class CompletionAdapterOpenAIChatGPT {
|
|
12
61
|
constructor(options) {
|
|
13
62
|
//@ts-ignore
|
|
14
|
-
this.complete = (content_1, ...args_1) => __awaiter(this, [content_1, ...args_1], void 0, function* (content, maxTokens = 50, outputSchema, onChunk) {
|
|
15
|
-
var _a, _b, _c
|
|
16
|
-
// stop parameter is alredy not supported
|
|
17
|
-
// adapter users should explicitely ask model to stop at dot if needed (or "Complete only up to the end of sentence")
|
|
63
|
+
this.complete = (content_1, ...args_1) => __awaiter(this, [content_1, ...args_1], void 0, function* (content, maxTokens = 50, outputSchema, reasoningEffort = "low", onChunk) {
|
|
64
|
+
var _a, _b, _c;
|
|
18
65
|
const model = this.options.model || "gpt-5-nano";
|
|
19
66
|
const isStreaming = typeof onChunk === "function";
|
|
20
|
-
const
|
|
67
|
+
const body = {
|
|
68
|
+
model,
|
|
69
|
+
input: content,
|
|
70
|
+
max_output_tokens: maxTokens,
|
|
71
|
+
stream: isStreaming,
|
|
72
|
+
text: outputSchema
|
|
73
|
+
? {
|
|
74
|
+
format: Object.assign({ type: "json_schema" }, outputSchema),
|
|
75
|
+
}
|
|
76
|
+
: {
|
|
77
|
+
format: {
|
|
78
|
+
type: "text",
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
reasoning: {
|
|
82
|
+
effort: reasoningEffort,
|
|
83
|
+
summary: "auto",
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const resp = yield fetch("https://api.openai.com/v1/responses", {
|
|
21
87
|
method: "POST",
|
|
22
88
|
headers: {
|
|
23
89
|
"Content-Type": "application/json",
|
|
24
90
|
Authorization: `Bearer ${this.options.openAiApiKey}`,
|
|
25
91
|
},
|
|
26
|
-
body: JSON.stringify(
|
|
27
|
-
{
|
|
28
|
-
role: "user",
|
|
29
|
-
content,
|
|
30
|
-
},
|
|
31
|
-
], max_completion_tokens: maxTokens, response_format: outputSchema
|
|
32
|
-
? Object.assign({ type: "json_schema" }, outputSchema) : undefined, stream: isStreaming }, this.options.extraRequestBodyParameters)),
|
|
92
|
+
body: JSON.stringify(body),
|
|
33
93
|
});
|
|
34
94
|
if (!resp.ok) {
|
|
35
95
|
let errorMessage = `OpenAI request failed with status ${resp.status}`;
|
|
36
96
|
try {
|
|
37
|
-
const errorData = yield resp.json();
|
|
38
|
-
if ((_a = errorData
|
|
97
|
+
const errorData = (yield resp.json());
|
|
98
|
+
if ((_a = errorData.error) === null || _a === void 0 ? void 0 : _a.message)
|
|
39
99
|
errorMessage = errorData.error.message;
|
|
40
|
-
}
|
|
41
100
|
}
|
|
42
|
-
catch (
|
|
101
|
+
catch (_d) { }
|
|
43
102
|
return { error: errorMessage };
|
|
44
103
|
}
|
|
45
104
|
if (!isStreaming) {
|
|
46
|
-
const
|
|
105
|
+
const json = yield resp.json();
|
|
106
|
+
const data = json;
|
|
47
107
|
if (data.error) {
|
|
48
108
|
return { error: data.error.message };
|
|
49
109
|
}
|
|
110
|
+
const parsedContent = extractOutputText(data);
|
|
111
|
+
const reasoning = extractReasoning(data);
|
|
50
112
|
return {
|
|
51
|
-
content:
|
|
52
|
-
finishReason: (
|
|
113
|
+
content: parsedContent,
|
|
114
|
+
finishReason: ((_b = data.incomplete_details) === null || _b === void 0 ? void 0 : _b.reason)
|
|
115
|
+
? data.incomplete_details.reason
|
|
116
|
+
: undefined,
|
|
53
117
|
};
|
|
54
118
|
}
|
|
55
119
|
if (!resp.body) {
|
|
@@ -59,79 +123,121 @@ export default class CompletionAdapterOpenAIChatGPT {
|
|
|
59
123
|
const decoder = new TextDecoder("utf-8");
|
|
60
124
|
let buffer = "";
|
|
61
125
|
let fullContent = "";
|
|
126
|
+
let fullReasoning = "";
|
|
62
127
|
let finishReason;
|
|
128
|
+
const handleEvent = (event, eventType) => __awaiter(this, void 0, void 0, function* () {
|
|
129
|
+
var _a, _b, _c, _d;
|
|
130
|
+
const type = (event === null || event === void 0 ? void 0 : event.type) || eventType;
|
|
131
|
+
if (type === "response.output_text.delta") {
|
|
132
|
+
const delta = (event === null || event === void 0 ? void 0 : event.delta) || "";
|
|
133
|
+
if (!delta)
|
|
134
|
+
return;
|
|
135
|
+
fullContent += delta;
|
|
136
|
+
yield (onChunk === null || onChunk === void 0 ? void 0 : onChunk(delta, { type: "output", delta, text: fullContent }));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (type === "response.reasoning_summary_text.delta" ||
|
|
140
|
+
type === "response.reasoning_text.delta") {
|
|
141
|
+
const delta = (event === null || event === void 0 ? void 0 : event.delta) || "";
|
|
142
|
+
if (!delta)
|
|
143
|
+
return;
|
|
144
|
+
fullReasoning += delta;
|
|
145
|
+
yield (onChunk === null || onChunk === void 0 ? void 0 : onChunk(delta, {
|
|
146
|
+
type: "reasoning",
|
|
147
|
+
delta,
|
|
148
|
+
text: fullReasoning,
|
|
149
|
+
}));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (type === "response.completed" ||
|
|
153
|
+
type === "response.incomplete") {
|
|
154
|
+
const response = event === null || event === void 0 ? void 0 : event.response;
|
|
155
|
+
if (!response)
|
|
156
|
+
return;
|
|
157
|
+
const finalContent = extractOutputText(response);
|
|
158
|
+
if (finalContent.startsWith(fullContent)) {
|
|
159
|
+
const delta = finalContent.slice(fullContent.length);
|
|
160
|
+
if (delta) {
|
|
161
|
+
fullContent = finalContent;
|
|
162
|
+
yield (onChunk === null || onChunk === void 0 ? void 0 : onChunk(delta, {
|
|
163
|
+
type: "output",
|
|
164
|
+
delta,
|
|
165
|
+
text: fullContent,
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const finalReasoning = extractReasoning(response) || "";
|
|
170
|
+
if (finalReasoning.startsWith(fullReasoning)) {
|
|
171
|
+
const delta = finalReasoning.slice(fullReasoning.length);
|
|
172
|
+
if (delta) {
|
|
173
|
+
fullReasoning = finalReasoning;
|
|
174
|
+
yield (onChunk === null || onChunk === void 0 ? void 0 : onChunk(delta, {
|
|
175
|
+
type: "reasoning",
|
|
176
|
+
delta,
|
|
177
|
+
text: fullReasoning,
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
finishReason =
|
|
182
|
+
((_a = response.incomplete_details) === null || _a === void 0 ? void 0 : _a.reason) || response.status || finishReason;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (type === "response.failed") {
|
|
186
|
+
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) ||
|
|
187
|
+
((_d = event === null || event === void 0 ? void 0 : event.error) === null || _d === void 0 ? void 0 : _d.message) ||
|
|
188
|
+
"Response failed");
|
|
189
|
+
}
|
|
190
|
+
});
|
|
63
191
|
try {
|
|
64
192
|
while (true) {
|
|
65
193
|
const { value, done } = yield reader.read();
|
|
66
|
-
if (done)
|
|
194
|
+
if (done)
|
|
67
195
|
break;
|
|
68
|
-
}
|
|
69
196
|
buffer += decoder.decode(value, { stream: true });
|
|
70
|
-
const
|
|
71
|
-
buffer =
|
|
72
|
-
for (const
|
|
73
|
-
const
|
|
74
|
-
if (!
|
|
197
|
+
const blocks = buffer.split("\n\n");
|
|
198
|
+
buffer = blocks.pop() || "";
|
|
199
|
+
for (const block of blocks) {
|
|
200
|
+
const parsedBlock = parseSseBlock(block);
|
|
201
|
+
if (!(parsedBlock === null || parsedBlock === void 0 ? void 0 : parsedBlock.data) || parsedBlock.data === "[DONE]")
|
|
75
202
|
continue;
|
|
76
|
-
|
|
77
|
-
const dataStr = line.slice(5).trim();
|
|
78
|
-
if (dataStr === "[DONE]") {
|
|
79
|
-
return {
|
|
80
|
-
content: fullContent,
|
|
81
|
-
finishReason,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
let parsed;
|
|
203
|
+
let event;
|
|
85
204
|
try {
|
|
86
|
-
|
|
205
|
+
event = JSON.parse(parsedBlock.data);
|
|
87
206
|
}
|
|
88
|
-
catch (
|
|
207
|
+
catch (_e) {
|
|
89
208
|
continue;
|
|
90
209
|
}
|
|
91
|
-
if ((
|
|
92
|
-
return { error:
|
|
210
|
+
if ((_c = event === null || event === void 0 ? void 0 : event.error) === null || _c === void 0 ? void 0 : _c.message) {
|
|
211
|
+
return { error: event.error.message };
|
|
93
212
|
}
|
|
94
|
-
|
|
95
|
-
if (!choice) {
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
if (choice.finish_reason) {
|
|
99
|
-
finishReason = choice.finish_reason;
|
|
100
|
-
}
|
|
101
|
-
const chunk = (_k = (_j = choice.delta) === null || _j === void 0 ? void 0 : _j.content) !== null && _k !== void 0 ? _k : "";
|
|
102
|
-
if (!chunk) {
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
fullContent += chunk;
|
|
106
|
-
yield onChunk(chunk);
|
|
213
|
+
yield handleEvent(event, parsedBlock.event);
|
|
107
214
|
}
|
|
108
215
|
}
|
|
109
|
-
if (buffer.trim()
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
216
|
+
if (buffer.trim()) {
|
|
217
|
+
const parsedBlock = parseSseBlock(buffer.trim());
|
|
218
|
+
if ((parsedBlock === null || parsedBlock === void 0 ? void 0 : parsedBlock.data) && parsedBlock.data !== "[DONE]") {
|
|
112
219
|
try {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
finishReason = choice.finish_reason;
|
|
122
|
-
}
|
|
220
|
+
yield handleEvent(JSON.parse(parsedBlock.data), parsedBlock.event);
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
return {
|
|
224
|
+
error: (error === null || error === void 0 ? void 0 : error.message) || "Streaming failed",
|
|
225
|
+
content: fullContent || undefined,
|
|
226
|
+
finishReason,
|
|
227
|
+
};
|
|
123
228
|
}
|
|
124
|
-
catch (_r) { }
|
|
125
229
|
}
|
|
126
230
|
}
|
|
127
231
|
return {
|
|
128
|
-
content: fullContent,
|
|
232
|
+
content: fullContent || undefined,
|
|
129
233
|
finishReason,
|
|
130
234
|
};
|
|
131
235
|
}
|
|
132
236
|
catch (error) {
|
|
133
237
|
return {
|
|
134
238
|
error: (error === null || error === void 0 ? void 0 : error.message) || "Streaming failed",
|
|
239
|
+
content: fullContent || undefined,
|
|
240
|
+
finishReason,
|
|
135
241
|
};
|
|
136
242
|
}
|
|
137
243
|
finally {
|
|
@@ -147,8 +253,6 @@ export default class CompletionAdapterOpenAIChatGPT {
|
|
|
147
253
|
}
|
|
148
254
|
}
|
|
149
255
|
measureTokensCount(content) {
|
|
150
|
-
|
|
151
|
-
const tokens = this.encoding.encode(content);
|
|
152
|
-
return tokens.length;
|
|
256
|
+
return this.encoding.encode(content).length;
|
|
153
257
|
}
|
|
154
258
|
}
|
package/index.ts
CHANGED
|
@@ -1,8 +1,76 @@
|
|
|
1
1
|
import type { AdapterOptions } from "./types.js";
|
|
2
|
-
import type { CompletionAdapter } from "adminforth";
|
|
2
|
+
import type { CompletionAdapter, CompletionStreamEvent } from "adminforth";
|
|
3
3
|
import { encoding_for_model, type TiktokenModel } from "tiktoken";
|
|
4
|
+
import type OpenAI from "openai";
|
|
4
5
|
|
|
5
|
-
type StreamChunkCallback = (
|
|
6
|
+
type StreamChunkCallback = (
|
|
7
|
+
chunk: string,
|
|
8
|
+
event?: CompletionStreamEvent,
|
|
9
|
+
) => void | Promise<void>;
|
|
10
|
+
|
|
11
|
+
type ResponseCreateBody = OpenAI.Responses.ResponseCreateParams;
|
|
12
|
+
type OpenAIResponsesSuccess = OpenAI.Responses.Response;
|
|
13
|
+
type OpenAIErrorResponse = {
|
|
14
|
+
error?: {
|
|
15
|
+
message?: string;
|
|
16
|
+
type?: string;
|
|
17
|
+
param?: string | null;
|
|
18
|
+
code?: string | null;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function extractOutputText(data: OpenAIResponsesSuccess): string {
|
|
23
|
+
let text = "";
|
|
24
|
+
|
|
25
|
+
for (const item of data.output ?? []) {
|
|
26
|
+
if (item.type !== "message" || !Array.isArray(item.content)) continue;
|
|
27
|
+
for (const part of item.content) {
|
|
28
|
+
if (part.type === "output_text" && typeof part.text === "string") {
|
|
29
|
+
text += part.text;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return text;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function extractReasoning(data: OpenAIResponsesSuccess): string | undefined {
|
|
38
|
+
let reasoning = "";
|
|
39
|
+
|
|
40
|
+
for (const item of data.output ?? []) {
|
|
41
|
+
if (item.type !== "reasoning") continue;
|
|
42
|
+
|
|
43
|
+
for (const part of item.summary ?? []) {
|
|
44
|
+
if (part?.type === "summary_text" && typeof part.text === "string") {
|
|
45
|
+
reasoning += part.text;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!reasoning) {
|
|
50
|
+
for (const part of item.content ?? []) {
|
|
51
|
+
if (part?.type === "reasoning_text" && typeof part.text === "string") {
|
|
52
|
+
reasoning += part.text;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return reasoning || undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseSseBlock(block: string) {
|
|
62
|
+
let event: string | undefined;
|
|
63
|
+
let data = "";
|
|
64
|
+
|
|
65
|
+
for (const rawLine of block.split("\n")) {
|
|
66
|
+
const line = rawLine.trimEnd();
|
|
67
|
+
if (!line) continue;
|
|
68
|
+
if (line.startsWith("event:")) event = line.slice(6).trim();
|
|
69
|
+
if (line.startsWith("data:")) data += line.slice(5).trim();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return data ? { event, data } : null;
|
|
73
|
+
}
|
|
6
74
|
|
|
7
75
|
export default class CompletionAdapterOpenAIChatGPT
|
|
8
76
|
implements CompletionAdapter
|
|
@@ -13,7 +81,7 @@ export default class CompletionAdapterOpenAIChatGPT
|
|
|
13
81
|
constructor(options: AdapterOptions) {
|
|
14
82
|
this.options = options;
|
|
15
83
|
this.encoding = encoding_for_model(
|
|
16
|
-
(this.options.model || "gpt-5-nano") as TiktokenModel
|
|
84
|
+
(this.options.model || "gpt-5-nano") as TiktokenModel,
|
|
17
85
|
);
|
|
18
86
|
}
|
|
19
87
|
|
|
@@ -24,72 +92,78 @@ export default class CompletionAdapterOpenAIChatGPT
|
|
|
24
92
|
}
|
|
25
93
|
|
|
26
94
|
measureTokensCount(content: string): number {
|
|
27
|
-
|
|
28
|
-
const tokens = this.encoding.encode(content);
|
|
29
|
-
return tokens.length;
|
|
95
|
+
return this.encoding.encode(content).length;
|
|
30
96
|
}
|
|
31
97
|
//@ts-ignore
|
|
32
98
|
complete = async (
|
|
33
99
|
content: string,
|
|
34
|
-
maxTokens
|
|
100
|
+
maxTokens = 50,
|
|
35
101
|
outputSchema?: any,
|
|
102
|
+
reasoningEffort: 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' = "low",
|
|
36
103
|
onChunk?: StreamChunkCallback,
|
|
37
104
|
): Promise<{
|
|
38
105
|
content?: string;
|
|
39
106
|
finishReason?: string;
|
|
40
107
|
error?: string;
|
|
41
108
|
}> => {
|
|
42
|
-
// stop parameter is alredy not supported
|
|
43
|
-
// adapter users should explicitely ask model to stop at dot if needed (or "Complete only up to the end of sentence")
|
|
44
109
|
const model = this.options.model || "gpt-5-nano";
|
|
45
110
|
const isStreaming = typeof onChunk === "function";
|
|
111
|
+
const body = {
|
|
112
|
+
model,
|
|
113
|
+
input: content,
|
|
114
|
+
max_output_tokens: maxTokens,
|
|
115
|
+
stream: isStreaming,
|
|
116
|
+
text: outputSchema
|
|
117
|
+
? {
|
|
118
|
+
format: {
|
|
119
|
+
type: "json_schema",
|
|
120
|
+
...outputSchema,
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
: {
|
|
124
|
+
format: {
|
|
125
|
+
type: "text",
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
reasoning: {
|
|
129
|
+
effort: reasoningEffort,
|
|
130
|
+
summary: "auto",
|
|
131
|
+
}
|
|
132
|
+
} as ResponseCreateBody;
|
|
46
133
|
|
|
47
|
-
const resp = await fetch("https://api.openai.com/v1/
|
|
134
|
+
const resp = await fetch("https://api.openai.com/v1/responses", {
|
|
48
135
|
method: "POST",
|
|
49
136
|
headers: {
|
|
50
137
|
"Content-Type": "application/json",
|
|
51
138
|
Authorization: `Bearer ${this.options.openAiApiKey}`,
|
|
52
139
|
},
|
|
53
|
-
body: JSON.stringify(
|
|
54
|
-
model,
|
|
55
|
-
messages: [
|
|
56
|
-
{
|
|
57
|
-
role: "user",
|
|
58
|
-
content,
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
max_completion_tokens: maxTokens,
|
|
62
|
-
response_format: outputSchema
|
|
63
|
-
? {
|
|
64
|
-
type: "json_schema",
|
|
65
|
-
...outputSchema,
|
|
66
|
-
}
|
|
67
|
-
: undefined,
|
|
68
|
-
stream: isStreaming,
|
|
69
|
-
...this.options.extraRequestBodyParameters,
|
|
70
|
-
}),
|
|
140
|
+
body: JSON.stringify(body),
|
|
71
141
|
});
|
|
72
142
|
|
|
73
143
|
if (!resp.ok) {
|
|
74
144
|
let errorMessage = `OpenAI request failed with status ${resp.status}`;
|
|
75
145
|
try {
|
|
76
|
-
const errorData = await resp.json();
|
|
77
|
-
if (errorData
|
|
78
|
-
errorMessage = errorData.error.message;
|
|
79
|
-
}
|
|
146
|
+
const errorData = (await resp.json()) as OpenAIErrorResponse;
|
|
147
|
+
if (errorData.error?.message) errorMessage = errorData.error.message;
|
|
80
148
|
} catch {}
|
|
81
149
|
return { error: errorMessage };
|
|
82
150
|
}
|
|
83
151
|
|
|
84
152
|
if (!isStreaming) {
|
|
85
|
-
const
|
|
153
|
+
const json = await resp.json();
|
|
154
|
+
const data = json as OpenAIResponsesSuccess & OpenAIErrorResponse;
|
|
86
155
|
if (data.error) {
|
|
87
156
|
return { error: data.error.message };
|
|
88
157
|
}
|
|
89
158
|
|
|
159
|
+
const parsedContent = extractOutputText(data);
|
|
160
|
+
const reasoning = extractReasoning(data);
|
|
161
|
+
|
|
90
162
|
return {
|
|
91
|
-
content:
|
|
92
|
-
finishReason: data.
|
|
163
|
+
content: parsedContent,
|
|
164
|
+
finishReason: data.incomplete_details?.reason
|
|
165
|
+
? data.incomplete_details.reason
|
|
166
|
+
: undefined,
|
|
93
167
|
};
|
|
94
168
|
}
|
|
95
169
|
|
|
@@ -102,94 +176,138 @@ export default class CompletionAdapterOpenAIChatGPT
|
|
|
102
176
|
|
|
103
177
|
let buffer = "";
|
|
104
178
|
let fullContent = "";
|
|
179
|
+
let fullReasoning = "";
|
|
105
180
|
let finishReason: string | undefined;
|
|
106
181
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const { value, done } = await reader.read();
|
|
110
|
-
if (done) {
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
182
|
+
const handleEvent = async (event: any, eventType?: string) => {
|
|
183
|
+
const type = event?.type || eventType;
|
|
113
184
|
|
|
114
|
-
|
|
185
|
+
if (type === "response.output_text.delta") {
|
|
186
|
+
const delta = event?.delta || "";
|
|
187
|
+
if (!delta) return;
|
|
188
|
+
fullContent += delta;
|
|
189
|
+
await onChunk?.(delta, { type: "output", delta, text: fullContent });
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
115
192
|
|
|
116
|
-
|
|
117
|
-
|
|
193
|
+
if (
|
|
194
|
+
type === "response.reasoning_summary_text.delta" ||
|
|
195
|
+
type === "response.reasoning_text.delta"
|
|
196
|
+
) {
|
|
197
|
+
const delta = event?.delta || "";
|
|
198
|
+
if (!delta) return;
|
|
199
|
+
fullReasoning += delta;
|
|
200
|
+
await onChunk?.(delta, {
|
|
201
|
+
type: "reasoning",
|
|
202
|
+
delta,
|
|
203
|
+
text: fullReasoning,
|
|
204
|
+
});
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
118
207
|
|
|
119
|
-
|
|
120
|
-
|
|
208
|
+
if (
|
|
209
|
+
type === "response.completed" ||
|
|
210
|
+
type === "response.incomplete"
|
|
211
|
+
) {
|
|
212
|
+
const response = event?.response as OpenAIResponsesSuccess | undefined;
|
|
213
|
+
if (!response) return;
|
|
121
214
|
|
|
122
|
-
|
|
123
|
-
|
|
215
|
+
const finalContent = extractOutputText(response);
|
|
216
|
+
if (finalContent.startsWith(fullContent)) {
|
|
217
|
+
const delta = finalContent.slice(fullContent.length);
|
|
218
|
+
if (delta) {
|
|
219
|
+
fullContent = finalContent;
|
|
220
|
+
await onChunk?.(delta, {
|
|
221
|
+
type: "output",
|
|
222
|
+
delta,
|
|
223
|
+
text: fullContent,
|
|
224
|
+
});
|
|
124
225
|
}
|
|
226
|
+
}
|
|
125
227
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
228
|
+
const finalReasoning = extractReasoning(response) || "";
|
|
229
|
+
if (finalReasoning.startsWith(fullReasoning)) {
|
|
230
|
+
const delta = finalReasoning.slice(fullReasoning.length);
|
|
231
|
+
if (delta) {
|
|
232
|
+
fullReasoning = finalReasoning;
|
|
233
|
+
await onChunk?.(delta, {
|
|
234
|
+
type: "reasoning",
|
|
235
|
+
delta,
|
|
236
|
+
text: fullReasoning,
|
|
237
|
+
});
|
|
133
238
|
}
|
|
239
|
+
}
|
|
134
240
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
241
|
+
finishReason =
|
|
242
|
+
response.incomplete_details?.reason || response.status || finishReason;
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
141
245
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
246
|
+
if (type === "response.failed") {
|
|
247
|
+
throw new Error(
|
|
248
|
+
event?.response?.error?.message ||
|
|
249
|
+
event?.error?.message ||
|
|
250
|
+
"Response failed",
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
145
254
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
255
|
+
try {
|
|
256
|
+
while (true) {
|
|
257
|
+
const { value, done } = await reader.read();
|
|
258
|
+
if (done) break;
|
|
150
259
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
260
|
+
buffer += decoder.decode(value, { stream: true });
|
|
261
|
+
|
|
262
|
+
const blocks = buffer.split("\n\n");
|
|
263
|
+
buffer = blocks.pop() || "";
|
|
264
|
+
|
|
265
|
+
for (const block of blocks) {
|
|
266
|
+
const parsedBlock = parseSseBlock(block);
|
|
267
|
+
if (!parsedBlock?.data || parsedBlock.data === "[DONE]") continue;
|
|
154
268
|
|
|
155
|
-
|
|
156
|
-
|
|
269
|
+
let event: any;
|
|
270
|
+
try {
|
|
271
|
+
event = JSON.parse(parsedBlock.data);
|
|
272
|
+
} catch {
|
|
157
273
|
continue;
|
|
158
274
|
}
|
|
159
275
|
|
|
160
|
-
|
|
161
|
-
|
|
276
|
+
if (event?.error?.message) {
|
|
277
|
+
return { error: event.error.message };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
await handleEvent(event, parsedBlock.event);
|
|
162
281
|
}
|
|
163
282
|
}
|
|
164
283
|
|
|
165
|
-
if (buffer.trim()
|
|
166
|
-
const
|
|
167
|
-
if (
|
|
284
|
+
if (buffer.trim()) {
|
|
285
|
+
const parsedBlock = parseSseBlock(buffer.trim());
|
|
286
|
+
if (parsedBlock?.data && parsedBlock.data !== "[DONE]") {
|
|
168
287
|
try {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
fullContent
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
finishReason = choice.finish_reason;
|
|
178
|
-
}
|
|
179
|
-
} catch {}
|
|
288
|
+
await handleEvent(JSON.parse(parsedBlock.data), parsedBlock.event);
|
|
289
|
+
} catch (error: any) {
|
|
290
|
+
return {
|
|
291
|
+
error: error?.message || "Streaming failed",
|
|
292
|
+
content: fullContent || undefined,
|
|
293
|
+
finishReason,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
180
296
|
}
|
|
181
297
|
}
|
|
182
298
|
|
|
183
299
|
return {
|
|
184
|
-
content: fullContent,
|
|
300
|
+
content: fullContent || undefined,
|
|
185
301
|
finishReason,
|
|
186
302
|
};
|
|
187
303
|
} catch (error: any) {
|
|
188
304
|
return {
|
|
189
305
|
error: error?.message || "Streaming failed",
|
|
306
|
+
content: fullContent || undefined,
|
|
307
|
+
finishReason,
|
|
190
308
|
};
|
|
191
309
|
} finally {
|
|
192
310
|
reader.releaseLock();
|
|
193
311
|
}
|
|
194
312
|
};
|
|
195
|
-
}
|
|
313
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminforth/completion-adapter-open-ai-chat-gpt",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.14",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"typescript": "^5.9.3"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"openai": "^6.34.0",
|
|
19
20
|
"tiktoken": "^1.0.22"
|
|
20
21
|
},
|
|
21
22
|
"peerDependencies": {
|