@veryfront/ext-llm-openai 0.1.985
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/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +129 -0
- package/esm/index.d.ts +12 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +32 -0
- package/esm/openai-chat-request-builder.d.ts +67 -0
- package/esm/openai-chat-request-builder.d.ts.map +1 -0
- package/esm/openai-chat-request-builder.js +126 -0
- package/esm/openai-chat-stream.d.ts +2 -0
- package/esm/openai-chat-stream.d.ts.map +1 -0
- package/esm/openai-chat-stream.js +235 -0
- package/esm/openai-provider.d.ts +29 -0
- package/esm/openai-provider.d.ts.map +1 -0
- package/esm/openai-provider.js +436 -0
- package/esm/openai-responses-request-builder.d.ts +42 -0
- package/esm/openai-responses-request-builder.d.ts.map +1 -0
- package/esm/openai-responses-request-builder.js +220 -0
- package/esm/openai-responses-stream.d.ts +16 -0
- package/esm/openai-responses-stream.d.ts.map +1 -0
- package/esm/openai-responses-stream.js +235 -0
- package/esm/package.json +3 -0
- package/package.json +58 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { readProviderOptions, stringifyJsonValue, unwrapToolInputSchema, } from "veryfront/provider/shared";
|
|
2
|
+
function isOpenAIReasoningModel(modelId) {
|
|
3
|
+
return /^o[134](-|$)/.test(modelId);
|
|
4
|
+
}
|
|
5
|
+
function resolveOpenAIReasoningEffort(option) {
|
|
6
|
+
if (!option || option.enabled !== true) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
switch (option.effort) {
|
|
10
|
+
case "low":
|
|
11
|
+
return "low";
|
|
12
|
+
case "high":
|
|
13
|
+
case "max":
|
|
14
|
+
return "high";
|
|
15
|
+
case "medium":
|
|
16
|
+
default:
|
|
17
|
+
return "medium";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function toSnakeCaseRecord(record) {
|
|
21
|
+
return Object.fromEntries(Object.entries(record).map(([key, value]) => [
|
|
22
|
+
key.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`),
|
|
23
|
+
value,
|
|
24
|
+
]));
|
|
25
|
+
}
|
|
26
|
+
function toOpenAIResponsesInput(prompt) {
|
|
27
|
+
const instructionsParts = [];
|
|
28
|
+
const input = [];
|
|
29
|
+
for (const message of prompt) {
|
|
30
|
+
switch (message.role) {
|
|
31
|
+
case "system":
|
|
32
|
+
if (message.content.length > 0) {
|
|
33
|
+
instructionsParts.push(message.content);
|
|
34
|
+
}
|
|
35
|
+
break;
|
|
36
|
+
case "user":
|
|
37
|
+
input.push({
|
|
38
|
+
role: "user",
|
|
39
|
+
content: toOpenAIResponsesUserContent(message.content),
|
|
40
|
+
});
|
|
41
|
+
break;
|
|
42
|
+
case "assistant": {
|
|
43
|
+
const messageContent = [];
|
|
44
|
+
for (const part of message.content) {
|
|
45
|
+
if (part.type === "text") {
|
|
46
|
+
messageContent.push({ type: "output_text", text: part.text });
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (part.type === "reasoning") {
|
|
50
|
+
if (messageContent.length > 0) {
|
|
51
|
+
input.push({ role: "assistant", content: [...messageContent] });
|
|
52
|
+
messageContent.length = 0;
|
|
53
|
+
}
|
|
54
|
+
const summary = [];
|
|
55
|
+
if (typeof part.text === "string" && part.text.length > 0) {
|
|
56
|
+
summary.push({ type: "summary_text", text: part.text });
|
|
57
|
+
}
|
|
58
|
+
input.push({
|
|
59
|
+
type: "reasoning",
|
|
60
|
+
...(typeof part.signature === "string" ? { encrypted_content: part.signature } : {}),
|
|
61
|
+
summary,
|
|
62
|
+
});
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (messageContent.length > 0) {
|
|
66
|
+
input.push({ role: "assistant", content: [...messageContent] });
|
|
67
|
+
messageContent.length = 0;
|
|
68
|
+
}
|
|
69
|
+
input.push({
|
|
70
|
+
type: "function_call",
|
|
71
|
+
call_id: part.toolCallId,
|
|
72
|
+
name: part.toolName,
|
|
73
|
+
arguments: stringifyJsonValue(part.input),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (messageContent.length > 0) {
|
|
77
|
+
input.push({ role: "assistant", content: messageContent });
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case "tool":
|
|
82
|
+
for (const part of message.content) {
|
|
83
|
+
input.push({
|
|
84
|
+
type: "function_call_output",
|
|
85
|
+
call_id: part.toolCallId,
|
|
86
|
+
output: stringifyJsonValue(part.output.value),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
...(instructionsParts.length > 0 ? { instructions: instructionsParts.join("\n\n") } : {}),
|
|
94
|
+
input,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function toOpenAIResponsesUserContent(parts) {
|
|
98
|
+
const content = [];
|
|
99
|
+
for (const part of parts) {
|
|
100
|
+
if (part.type === "text") {
|
|
101
|
+
if (part.text.length > 0) {
|
|
102
|
+
content.push({ type: "input_text", text: part.text });
|
|
103
|
+
}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (part.type === "image" || part.mediaType.startsWith("image/")) {
|
|
107
|
+
content.push({ type: "input_image", image_url: part.url, detail: "auto" });
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
content.push({
|
|
111
|
+
type: "input_file",
|
|
112
|
+
file_url: part.url,
|
|
113
|
+
...(part.filename ? { filename: part.filename } : {}),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return content;
|
|
117
|
+
}
|
|
118
|
+
function toOpenAIResponsesTools(tools) {
|
|
119
|
+
if (!tools)
|
|
120
|
+
return undefined;
|
|
121
|
+
const normalized = [];
|
|
122
|
+
for (const tool of tools) {
|
|
123
|
+
if (tool.type === "function") {
|
|
124
|
+
normalized.push({
|
|
125
|
+
type: "function",
|
|
126
|
+
name: tool.name,
|
|
127
|
+
...(typeof tool.description === "string" ? { description: tool.description } : {}),
|
|
128
|
+
parameters: unwrapToolInputSchema(tool.inputSchema),
|
|
129
|
+
});
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (!tool.id.startsWith("openai."))
|
|
133
|
+
continue;
|
|
134
|
+
const providerType = tool.id.slice("openai.".length);
|
|
135
|
+
if (providerType.length === 0)
|
|
136
|
+
continue;
|
|
137
|
+
normalized.push({
|
|
138
|
+
type: providerType,
|
|
139
|
+
...toSnakeCaseRecord(tool.args),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
143
|
+
}
|
|
144
|
+
export function buildOpenAIResponsesRequest(modelId, providerName, options, stream, warnings) {
|
|
145
|
+
const isReasoningModel = isOpenAIReasoningModel(modelId);
|
|
146
|
+
const reasoningEffort = resolveOpenAIReasoningEffort(options.reasoning);
|
|
147
|
+
const reasoningEnabled = isReasoningModel || reasoningEffort !== undefined;
|
|
148
|
+
if (options.topK !== undefined) {
|
|
149
|
+
warnings.push({
|
|
150
|
+
type: "unsupported-setting",
|
|
151
|
+
provider: "openai",
|
|
152
|
+
setting: "topK",
|
|
153
|
+
details: "OpenAI Responses API does not expose top_k; the value was dropped.",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (reasoningEnabled) {
|
|
157
|
+
const dropped = [
|
|
158
|
+
["temperature", "temperature"],
|
|
159
|
+
["topP", "top_p"],
|
|
160
|
+
["presencePenalty", "presence_penalty"],
|
|
161
|
+
["frequencyPenalty", "frequency_penalty"],
|
|
162
|
+
];
|
|
163
|
+
for (const [key, openaiName] of dropped) {
|
|
164
|
+
if (options[key] !== undefined) {
|
|
165
|
+
warnings.push({
|
|
166
|
+
type: "unsupported-setting",
|
|
167
|
+
provider: "openai",
|
|
168
|
+
setting: key,
|
|
169
|
+
details: `Dropped because OpenAI reasoning models reject ${openaiName}. Reasoning was active for this request.`,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const { instructions, input } = toOpenAIResponsesInput(options.prompt);
|
|
175
|
+
const responsesTools = toOpenAIResponsesTools(options.tools);
|
|
176
|
+
const body = {
|
|
177
|
+
model: modelId,
|
|
178
|
+
input,
|
|
179
|
+
...(instructions !== undefined ? { instructions } : {}),
|
|
180
|
+
...(stream ? { stream: true } : {}),
|
|
181
|
+
...(options.maxOutputTokens !== undefined
|
|
182
|
+
? { max_output_tokens: options.maxOutputTokens }
|
|
183
|
+
: {}),
|
|
184
|
+
...(!reasoningEnabled && options.temperature !== undefined
|
|
185
|
+
? { temperature: options.temperature }
|
|
186
|
+
: {}),
|
|
187
|
+
...(!reasoningEnabled && options.topP !== undefined ? { top_p: options.topP } : {}),
|
|
188
|
+
...(responsesTools ? { tools: responsesTools } : {}),
|
|
189
|
+
...(options.toolChoice !== undefined ? { tool_choice: options.toolChoice } : {}),
|
|
190
|
+
...(reasoningEffort !== undefined
|
|
191
|
+
? { reasoning: { effort: reasoningEffort, summary: "auto" } }
|
|
192
|
+
: {}),
|
|
193
|
+
...(typeof options.userId === "string" && options.userId.length > 0
|
|
194
|
+
? { user: options.userId }
|
|
195
|
+
: {}),
|
|
196
|
+
...(options.serviceTier !== undefined ? { service_tier: options.serviceTier } : {}),
|
|
197
|
+
...(options.parallelToolCalls !== undefined
|
|
198
|
+
? { parallel_tool_calls: options.parallelToolCalls }
|
|
199
|
+
: {}),
|
|
200
|
+
...(options.responseFormat && options.responseFormat.type !== "text"
|
|
201
|
+
? {
|
|
202
|
+
text: {
|
|
203
|
+
format: options.responseFormat.type === "json" ? { type: "json_object" } : {
|
|
204
|
+
type: "json_schema",
|
|
205
|
+
name: options.responseFormat.name,
|
|
206
|
+
...(typeof options.responseFormat.description === "string"
|
|
207
|
+
? { description: options.responseFormat.description }
|
|
208
|
+
: {}),
|
|
209
|
+
schema: unwrapToolInputSchema(options.responseFormat.schema),
|
|
210
|
+
...(options.responseFormat.strict !== undefined
|
|
211
|
+
? { strict: options.responseFormat.strict }
|
|
212
|
+
: {}),
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
: {}),
|
|
217
|
+
};
|
|
218
|
+
Object.assign(body, readProviderOptions(options.providerOptions, "openai", providerName));
|
|
219
|
+
return body;
|
|
220
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RuntimeUsage } from "veryfront/provider/shared";
|
|
2
|
+
/**
|
|
3
|
+
* The Responses API uses `input_tokens` / `output_tokens` field names
|
|
4
|
+
* instead of Chat Completions' `prompt_tokens` / `completion_tokens`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function extractOpenAIResponsesUsage(payload: unknown): RuntimeUsage | undefined;
|
|
7
|
+
export declare function normalizeOpenAIResponsesFinishReason(raw: unknown): string | {
|
|
8
|
+
unified: string;
|
|
9
|
+
raw: string;
|
|
10
|
+
} | null;
|
|
11
|
+
/**
|
|
12
|
+
* Parse the Responses API streaming event grammar into the same UI part
|
|
13
|
+
* shapes the existing OpenAI / Anthropic / Google streams emit.
|
|
14
|
+
*/
|
|
15
|
+
export declare function streamOpenAIResponsesParts(stream: ReadableStream<Uint8Array>): AsyncIterable<unknown>;
|
|
16
|
+
//# sourceMappingURL=openai-responses-stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-responses-stream.d.ts","sourceRoot":"","sources":["../src/openai-responses-stream.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAc9D;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAqEtF;AAED,wBAAgB,oCAAoC,CAClD,GAAG,EAAE,OAAO,GACX,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAclD;AAED;;;GAGG;AACH,wBAAuB,0BAA0B,CAC/C,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GACjC,aAAa,CAAC,OAAO,CAAC,CAqJxB"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { parseSseChunk, readGatewayBillingMode, readRecord } from "veryfront/provider/shared";
|
|
2
|
+
/**
|
|
3
|
+
* The Responses API uses `input_tokens` / `output_tokens` field names
|
|
4
|
+
* instead of Chat Completions' `prompt_tokens` / `completion_tokens`.
|
|
5
|
+
*/
|
|
6
|
+
export function extractOpenAIResponsesUsage(payload) {
|
|
7
|
+
const record = readRecord(payload);
|
|
8
|
+
// Streaming usage lives on response.completed inside `response.usage`;
|
|
9
|
+
// non-streaming has it at the top level.
|
|
10
|
+
const responseRecord = readRecord(record?.response);
|
|
11
|
+
const usage = readRecord(responseRecord?.usage) ?? readRecord(record?.usage);
|
|
12
|
+
if (!usage)
|
|
13
|
+
return undefined;
|
|
14
|
+
const inputTokens = typeof usage.input_tokens === "number" ? usage.input_tokens : undefined;
|
|
15
|
+
const outputTokens = typeof usage.output_tokens === "number" ? usage.output_tokens : undefined;
|
|
16
|
+
const totalTokens = typeof usage.total_tokens === "number"
|
|
17
|
+
? usage.total_tokens
|
|
18
|
+
: (inputTokens !== undefined || outputTokens !== undefined
|
|
19
|
+
? (inputTokens ?? 0) + (outputTokens ?? 0)
|
|
20
|
+
: undefined);
|
|
21
|
+
const inputDetails = readRecord(usage.input_tokens_details);
|
|
22
|
+
const cachedTokens = inputDetails?.cached_tokens;
|
|
23
|
+
const outputDetails = readRecord(usage.output_tokens_details);
|
|
24
|
+
const reasoningTokens = outputDetails?.reasoning_tokens;
|
|
25
|
+
const veryfront = readRecord(usage.veryfront);
|
|
26
|
+
const costSource = veryfront?.cost_source;
|
|
27
|
+
const billingMode = readGatewayBillingMode(veryfront?.billing_mode);
|
|
28
|
+
const usageCaptureStatus = veryfront?.usage_capture_status;
|
|
29
|
+
return {
|
|
30
|
+
inputTokens,
|
|
31
|
+
outputTokens,
|
|
32
|
+
totalTokens,
|
|
33
|
+
...(typeof cachedTokens === "number" ? { cacheReadInputTokens: cachedTokens } : {}),
|
|
34
|
+
...(typeof reasoningTokens === "number" ? { reasoningTokens } : {}),
|
|
35
|
+
...(typeof veryfront?.billable_input_tokens === "number"
|
|
36
|
+
? { billableInputTokens: veryfront.billable_input_tokens }
|
|
37
|
+
: {}),
|
|
38
|
+
...(typeof veryfront?.billable_output_tokens === "number"
|
|
39
|
+
? { billableOutputTokens: veryfront.billable_output_tokens }
|
|
40
|
+
: {}),
|
|
41
|
+
...(typeof veryfront?.cost_usd === "number" ? { costUsd: veryfront.cost_usd } : {}),
|
|
42
|
+
...(typeof veryfront?.provider_input_cost_usd === "number"
|
|
43
|
+
? { providerInputCostUsd: veryfront.provider_input_cost_usd }
|
|
44
|
+
: {}),
|
|
45
|
+
...(typeof veryfront?.provider_output_cost_usd === "number"
|
|
46
|
+
? { providerOutputCostUsd: veryfront.provider_output_cost_usd }
|
|
47
|
+
: {}),
|
|
48
|
+
...(typeof veryfront?.provider_cost_usd === "number"
|
|
49
|
+
? { providerCostUsd: veryfront.provider_cost_usd }
|
|
50
|
+
: {}),
|
|
51
|
+
...(typeof veryfront?.veryfront_input_charge_usd === "number"
|
|
52
|
+
? { veryfrontInputChargeUsd: veryfront.veryfront_input_charge_usd }
|
|
53
|
+
: {}),
|
|
54
|
+
...(typeof veryfront?.veryfront_output_charge_usd === "number"
|
|
55
|
+
? { veryfrontOutputChargeUsd: veryfront.veryfront_output_charge_usd }
|
|
56
|
+
: {}),
|
|
57
|
+
...(typeof veryfront?.veryfront_charge_usd === "number"
|
|
58
|
+
? { veryfrontChargeUsd: veryfront.veryfront_charge_usd }
|
|
59
|
+
: {}),
|
|
60
|
+
...(typeof veryfront?.veryfront_billed_usd === "number"
|
|
61
|
+
? { veryfrontBilledUsd: veryfront.veryfront_billed_usd }
|
|
62
|
+
: {}),
|
|
63
|
+
...(typeof veryfront?.cost_credits === "number" ? { costCredits: veryfront.cost_credits } : {}),
|
|
64
|
+
...(costSource === "gateway" || costSource === "missing" || costSource === "partial"
|
|
65
|
+
? { costSource }
|
|
66
|
+
: {}),
|
|
67
|
+
...(billingMode !== undefined ? { billingMode } : {}),
|
|
68
|
+
...(usageCaptureStatus === "complete" ||
|
|
69
|
+
usageCaptureStatus === "missing" ||
|
|
70
|
+
usageCaptureStatus === "partial"
|
|
71
|
+
? { usageCaptureStatus }
|
|
72
|
+
: {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function normalizeOpenAIResponsesFinishReason(raw) {
|
|
76
|
+
if (typeof raw !== "string")
|
|
77
|
+
return null;
|
|
78
|
+
switch (raw) {
|
|
79
|
+
case "completed":
|
|
80
|
+
return { unified: "stop", raw };
|
|
81
|
+
case "incomplete":
|
|
82
|
+
return { unified: "length", raw };
|
|
83
|
+
case "failed":
|
|
84
|
+
return { unified: "error", raw };
|
|
85
|
+
case "in_progress":
|
|
86
|
+
return null;
|
|
87
|
+
default:
|
|
88
|
+
return raw;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Parse the Responses API streaming event grammar into the same UI part
|
|
93
|
+
* shapes the existing OpenAI / Anthropic / Google streams emit.
|
|
94
|
+
*/
|
|
95
|
+
export async function* streamOpenAIResponsesParts(stream) {
|
|
96
|
+
const decoder = new TextDecoder();
|
|
97
|
+
let buffer = "";
|
|
98
|
+
const reasoningBlocks = new Map();
|
|
99
|
+
const functionCalls = new Map();
|
|
100
|
+
const startedToolCalls = new Set();
|
|
101
|
+
let finishReason = null;
|
|
102
|
+
let usage;
|
|
103
|
+
let reasoningCounter = 0;
|
|
104
|
+
for await (const chunk of stream) {
|
|
105
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
106
|
+
const parsed = parseSseChunk(buffer);
|
|
107
|
+
buffer = parsed.remainder;
|
|
108
|
+
for (const event of parsed.events) {
|
|
109
|
+
if (event === "[DONE]")
|
|
110
|
+
continue;
|
|
111
|
+
const record = readRecord(event);
|
|
112
|
+
const type = typeof record?.type === "string" ? record.type : undefined;
|
|
113
|
+
if (!type)
|
|
114
|
+
continue;
|
|
115
|
+
// response.output_item.added: a new output item begins.
|
|
116
|
+
if (type === "response.output_item.added") {
|
|
117
|
+
const item = readRecord(record?.item);
|
|
118
|
+
const itemType = typeof item?.type === "string" ? item.type : undefined;
|
|
119
|
+
const itemId = typeof item?.id === "string" ? item.id : undefined;
|
|
120
|
+
if (itemType === "function_call" && itemId) {
|
|
121
|
+
const callId = typeof item?.call_id === "string" ? item.call_id : itemId;
|
|
122
|
+
const name = typeof item?.name === "string" ? item.name : "";
|
|
123
|
+
functionCalls.set(itemId, {
|
|
124
|
+
id: itemId,
|
|
125
|
+
toolCallId: callId,
|
|
126
|
+
name,
|
|
127
|
+
arguments: "",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (itemType === "reasoning" && itemId) {
|
|
131
|
+
reasoningBlocks.set(itemId, {
|
|
132
|
+
id: `reasoning-${reasoningCounter++}`,
|
|
133
|
+
emittedStart: false,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
// response.output_text.delta: text chunk for a message item.
|
|
139
|
+
if (type === "response.output_text.delta" && typeof record?.delta === "string") {
|
|
140
|
+
if (record.delta.length > 0) {
|
|
141
|
+
yield { type: "text-delta", delta: record.delta };
|
|
142
|
+
}
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
// response.reasoning_summary_text.delta: reasoning summary text chunk.
|
|
146
|
+
if (type === "response.reasoning_summary_text.delta" && typeof record?.delta === "string") {
|
|
147
|
+
const itemId = typeof record?.item_id === "string" ? record.item_id : undefined;
|
|
148
|
+
const state = itemId ? reasoningBlocks.get(itemId) : undefined;
|
|
149
|
+
if (state && record.delta.length > 0) {
|
|
150
|
+
if (!state.emittedStart) {
|
|
151
|
+
yield { type: "reasoning-start", id: state.id };
|
|
152
|
+
state.emittedStart = true;
|
|
153
|
+
}
|
|
154
|
+
yield { type: "reasoning-delta", id: state.id, delta: record.delta };
|
|
155
|
+
}
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
// response.function_call_arguments.delta: tool call argument chunk.
|
|
159
|
+
if (type === "response.function_call_arguments.delta" && typeof record?.delta === "string") {
|
|
160
|
+
const itemId = typeof record?.item_id === "string" ? record.item_id : undefined;
|
|
161
|
+
const state = itemId ? functionCalls.get(itemId) : undefined;
|
|
162
|
+
if (state && record.delta.length > 0) {
|
|
163
|
+
if (!startedToolCalls.has(state.id)) {
|
|
164
|
+
yield {
|
|
165
|
+
type: "tool-input-start",
|
|
166
|
+
id: state.toolCallId,
|
|
167
|
+
toolName: state.name,
|
|
168
|
+
};
|
|
169
|
+
startedToolCalls.add(state.id);
|
|
170
|
+
}
|
|
171
|
+
state.arguments += record.delta;
|
|
172
|
+
yield {
|
|
173
|
+
type: "tool-input-delta",
|
|
174
|
+
id: state.toolCallId,
|
|
175
|
+
delta: record.delta,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
// response.output_item.done: an item has finished emitting deltas.
|
|
181
|
+
if (type === "response.output_item.done") {
|
|
182
|
+
const item = readRecord(record?.item);
|
|
183
|
+
const itemType = typeof item?.type === "string" ? item.type : undefined;
|
|
184
|
+
const itemId = typeof item?.id === "string" ? item.id : undefined;
|
|
185
|
+
if (itemType === "reasoning" && itemId) {
|
|
186
|
+
const state = reasoningBlocks.get(itemId);
|
|
187
|
+
if (state?.emittedStart) {
|
|
188
|
+
yield { type: "reasoning-end", id: state.id };
|
|
189
|
+
}
|
|
190
|
+
reasoningBlocks.delete(itemId);
|
|
191
|
+
}
|
|
192
|
+
if (itemType === "function_call" && itemId) {
|
|
193
|
+
const state = functionCalls.get(itemId);
|
|
194
|
+
if (state) {
|
|
195
|
+
yield {
|
|
196
|
+
type: "tool-call",
|
|
197
|
+
toolCallId: state.toolCallId,
|
|
198
|
+
toolName: state.name,
|
|
199
|
+
input: state.arguments,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
functionCalls.delete(itemId);
|
|
203
|
+
}
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
// response.completed: terminal event with the final response object.
|
|
207
|
+
if (type === "response.completed") {
|
|
208
|
+
usage = extractOpenAIResponsesUsage(record) ?? usage;
|
|
209
|
+
const responseRecord = readRecord(record?.response);
|
|
210
|
+
finishReason = normalizeOpenAIResponsesFinishReason(responseRecord?.status);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (type === "response.failed" || type === "response.incomplete") {
|
|
214
|
+
const responseRecord = readRecord(record?.response);
|
|
215
|
+
finishReason = normalizeOpenAIResponsesFinishReason(responseRecord?.status) ??
|
|
216
|
+
(type === "response.failed"
|
|
217
|
+
? { unified: "error", raw: "failed" }
|
|
218
|
+
: { unified: "length", raw: "incomplete" });
|
|
219
|
+
usage = extractOpenAIResponsesUsage(record) ?? usage;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Close any reasoning streams still open at end-of-stream (defensive).
|
|
225
|
+
for (const state of reasoningBlocks.values()) {
|
|
226
|
+
if (state.emittedStart) {
|
|
227
|
+
yield { type: "reasoning-end", id: state.id };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
yield {
|
|
231
|
+
type: "finish",
|
|
232
|
+
finishReason,
|
|
233
|
+
...(usage ? { usage } : {}),
|
|
234
|
+
};
|
|
235
|
+
}
|
package/esm/package.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@veryfront/ext-llm-openai",
|
|
3
|
+
"version": "0.1.985",
|
|
4
|
+
"description": "Veryfront first-party extension package for ext-llm-openai",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"veryfront",
|
|
7
|
+
"extension",
|
|
8
|
+
"ext-llm-openai"
|
|
9
|
+
],
|
|
10
|
+
"author": "Veryfront",
|
|
11
|
+
"homepage": "https://github.com/veryfront/veryfront-code/tree/main/extensions/ext-llm-openai",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/veryfront/veryfront-code.git",
|
|
15
|
+
"directory": "extensions/ext-llm-openai"
|
|
16
|
+
},
|
|
17
|
+
"license": "Apache-2.0",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/veryfront/veryfront-code/issues"
|
|
20
|
+
},
|
|
21
|
+
"module": "./esm/index.js",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"import": "./esm/index.js",
|
|
25
|
+
"types": "./esm/index.d.ts"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"scripts": {},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.0.0"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"veryfront": {
|
|
36
|
+
"extension": true,
|
|
37
|
+
"contracts": {
|
|
38
|
+
"provides": [
|
|
39
|
+
"LLMProvider:openai"
|
|
40
|
+
],
|
|
41
|
+
"requires": [
|
|
42
|
+
"LLMProviderRegistry"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"capabilities": []
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"veryfront": "^0.1.985"
|
|
49
|
+
},
|
|
50
|
+
"type": "module",
|
|
51
|
+
"types": "./esm/index.d.ts",
|
|
52
|
+
"files": [
|
|
53
|
+
"esm",
|
|
54
|
+
"LICENSE",
|
|
55
|
+
"NOTICE",
|
|
56
|
+
"README.md"
|
|
57
|
+
]
|
|
58
|
+
}
|