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