@llumiverse/drivers 0.23.0 → 0.24.0
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/README.md +141 -218
- package/lib/cjs/azure/azure_foundry.js +46 -2
- package/lib/cjs/azure/azure_foundry.js.map +1 -1
- package/lib/cjs/bedrock/index.js +140 -15
- package/lib/cjs/bedrock/index.js.map +1 -1
- package/lib/cjs/groq/index.js +115 -85
- package/lib/cjs/groq/index.js.map +1 -1
- package/lib/cjs/index.js +1 -0
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/openai/index.js +310 -114
- package/lib/cjs/openai/index.js.map +1 -1
- package/lib/cjs/openai/openai_compatible.js +62 -0
- package/lib/cjs/openai/openai_compatible.js.map +1 -0
- package/lib/cjs/openai/openai_format.js +32 -39
- package/lib/cjs/openai/openai_format.js.map +1 -1
- package/lib/cjs/vertexai/index.js +147 -0
- package/lib/cjs/vertexai/index.js.map +1 -1
- package/lib/cjs/vertexai/models/claude.js +88 -2
- package/lib/cjs/vertexai/models/claude.js.map +1 -1
- package/lib/cjs/vertexai/models/gemini.js +59 -20
- package/lib/cjs/vertexai/models/gemini.js.map +1 -1
- package/lib/cjs/xai/index.js +10 -16
- package/lib/cjs/xai/index.js.map +1 -1
- package/lib/esm/azure/azure_foundry.js +46 -2
- package/lib/esm/azure/azure_foundry.js.map +1 -1
- package/lib/esm/bedrock/index.js +141 -16
- package/lib/esm/bedrock/index.js.map +1 -1
- package/lib/esm/groq/index.js +115 -85
- package/lib/esm/groq/index.js.map +1 -1
- package/lib/esm/index.js +1 -0
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/openai/index.js +311 -115
- package/lib/esm/openai/index.js.map +1 -1
- package/lib/esm/openai/openai_compatible.js +55 -0
- package/lib/esm/openai/openai_compatible.js.map +1 -0
- package/lib/esm/openai/openai_format.js +32 -39
- package/lib/esm/openai/openai_format.js.map +1 -1
- package/lib/esm/vertexai/index.js +148 -1
- package/lib/esm/vertexai/index.js.map +1 -1
- package/lib/esm/vertexai/models/claude.js +87 -2
- package/lib/esm/vertexai/models/claude.js.map +1 -1
- package/lib/esm/vertexai/models/gemini.js +60 -21
- package/lib/esm/vertexai/models/gemini.js.map +1 -1
- package/lib/esm/xai/index.js +10 -16
- package/lib/esm/xai/index.js.map +1 -1
- package/lib/types/azure/azure_foundry.d.ts +7 -5
- package/lib/types/azure/azure_foundry.d.ts.map +1 -1
- package/lib/types/bedrock/index.d.ts +5 -0
- package/lib/types/bedrock/index.d.ts.map +1 -1
- package/lib/types/groq/index.d.ts.map +1 -1
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/openai/index.d.ts +13 -7
- package/lib/types/openai/index.d.ts.map +1 -1
- package/lib/types/openai/openai_compatible.d.ts +26 -0
- package/lib/types/openai/openai_compatible.d.ts.map +1 -0
- package/lib/types/openai/openai_format.d.ts +4 -2
- package/lib/types/openai/openai_format.d.ts.map +1 -1
- package/lib/types/vertexai/index.d.ts +11 -0
- package/lib/types/vertexai/index.d.ts.map +1 -1
- package/lib/types/vertexai/models/claude.d.ts +8 -0
- package/lib/types/vertexai/models/claude.d.ts.map +1 -1
- package/lib/types/vertexai/models/gemini.d.ts +1 -1
- package/lib/types/vertexai/models/gemini.d.ts.map +1 -1
- package/lib/types/xai/index.d.ts +2 -3
- package/lib/types/xai/index.d.ts.map +1 -1
- package/package.json +12 -12
- package/src/azure/azure_foundry.ts +56 -7
- package/src/bedrock/index.ts +188 -24
- package/src/groq/index.ts +120 -94
- package/src/index.ts +1 -0
- package/src/openai/index.ts +363 -136
- package/src/openai/openai_compatible.ts +74 -0
- package/src/openai/openai_format.ts +44 -54
- package/src/vertexai/index.ts +186 -0
- package/src/vertexai/models/claude.ts +97 -2
- package/src/vertexai/models/gemini.ts +78 -27
- package/src/xai/index.ts +10 -17
package/lib/esm/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./huggingface_ie.js";
|
|
|
5
5
|
export * from "./mistral/index.js";
|
|
6
6
|
export * from "./openai/azure_openai.js";
|
|
7
7
|
export * from "./openai/openai.js";
|
|
8
|
+
export * from "./openai/openai_compatible.js";
|
|
8
9
|
export * from "./replicate.js";
|
|
9
10
|
export * from "./test-driver/index.js";
|
|
10
11
|
export * from "./togetherai/index.js";
|
package/lib/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC"}
|
package/lib/esm/openai/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { AbstractDriver, ModelType, TrainingJobStatus, getModelCapabilities, modelModalitiesToArray, supportsToolUse, } from "@llumiverse/core";
|
|
2
|
-
import { asyncMap } from "@llumiverse/core/async";
|
|
1
|
+
import { AbstractDriver, ModelType, TrainingJobStatus, getConversationMeta, getModelCapabilities, incrementConversationTurn, modelModalitiesToArray, stripBase64ImagesFromConversation, supportsToolUse, truncateLargeTextInConversation, unwrapConversationArray, } from "@llumiverse/core";
|
|
3
2
|
import { formatOpenAILikeMultimodalPrompt } from "./openai_format.js";
|
|
4
3
|
// Helper function to convert string to CompletionResult[]
|
|
5
4
|
function textToCompletionResult(text) {
|
|
@@ -17,17 +16,11 @@ export class BaseOpenAIDriver extends AbstractDriver {
|
|
|
17
16
|
constructor(opts) {
|
|
18
17
|
super(opts);
|
|
19
18
|
this.formatPrompt = formatOpenAILikeMultimodalPrompt;
|
|
20
|
-
//TODO: better type, we send back OpenAI.Chat.Completions.ChatCompletionMessageParam[] but just not compatible with Function call that we don't use here
|
|
21
19
|
}
|
|
22
20
|
extractDataFromResponse(_options, result) {
|
|
23
|
-
const tokenInfo =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
total: result.usage?.total_tokens,
|
|
27
|
-
};
|
|
28
|
-
const choice = result.choices[0];
|
|
29
|
-
const tools = collectTools(choice.message.tool_calls);
|
|
30
|
-
const data = choice.message.content ?? undefined;
|
|
21
|
+
const tokenInfo = mapUsage(result.usage);
|
|
22
|
+
const tools = collectTools(result.output);
|
|
23
|
+
const data = extractTextFromResponse(result);
|
|
31
24
|
if (!data && !tools) {
|
|
32
25
|
this.logger.error({ result }, "[OpenAI] Response is not valid");
|
|
33
26
|
throw new Error("Response is not valid: no data");
|
|
@@ -35,7 +28,7 @@ export class BaseOpenAIDriver extends AbstractDriver {
|
|
|
35
28
|
return {
|
|
36
29
|
result: textToCompletionResult(data || ''),
|
|
37
30
|
token_usage: tokenInfo,
|
|
38
|
-
finish_reason:
|
|
31
|
+
finish_reason: responseFinishReason(result, tools),
|
|
39
32
|
tool_use: tools,
|
|
40
33
|
};
|
|
41
34
|
}
|
|
@@ -43,26 +36,10 @@ export class BaseOpenAIDriver extends AbstractDriver {
|
|
|
43
36
|
if (options.model_options?._option_id !== "openai-text" && options.model_options?._option_id !== "openai-thinking") {
|
|
44
37
|
this.logger.warn({ options: options.model_options }, "Invalid model options");
|
|
45
38
|
}
|
|
39
|
+
// Include conversation history (same as non-streaming)
|
|
40
|
+
const conversation = updateConversation(options.conversation, prompt);
|
|
46
41
|
const toolDefs = getToolDefinitions(options.tools);
|
|
47
|
-
const useTools = toolDefs ? supportsToolUse(options.model,
|
|
48
|
-
const mapFn = (chunk) => {
|
|
49
|
-
let result = undefined;
|
|
50
|
-
if (useTools && this.provider !== "xai" && options.result_schema) {
|
|
51
|
-
result = chunk.choices[0]?.delta?.tool_calls?.[0].function?.arguments ?? "";
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
result = chunk.choices[0]?.delta.content ?? "";
|
|
55
|
-
}
|
|
56
|
-
return {
|
|
57
|
-
result: textToCompletionResult(result),
|
|
58
|
-
finish_reason: openAiFinishReason(chunk.choices[0]?.finish_reason ?? undefined), //Uses expected "stop" , "length" format
|
|
59
|
-
token_usage: {
|
|
60
|
-
prompt: chunk.usage?.prompt_tokens,
|
|
61
|
-
result: chunk.usage?.completion_tokens,
|
|
62
|
-
total: (chunk.usage?.prompt_tokens ?? 0) + (chunk.usage?.completion_tokens ?? 0),
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
};
|
|
42
|
+
const useTools = toolDefs ? supportsToolUse(options.model, this.provider, true) : false;
|
|
66
43
|
convertRoles(prompt, options.model);
|
|
67
44
|
const model_options = options.model_options;
|
|
68
45
|
insert_image_detail(prompt, model_options?.image_detail ?? "auto");
|
|
@@ -78,30 +55,26 @@ export class BaseOpenAIDriver extends AbstractDriver {
|
|
|
78
55
|
strictMode = false;
|
|
79
56
|
}
|
|
80
57
|
}
|
|
81
|
-
const
|
|
58
|
+
const reasoning = model_options?.reasoning_effort ? { effort: model_options.reasoning_effort } : undefined;
|
|
59
|
+
const stream = await this.service.responses.create({
|
|
82
60
|
stream: true,
|
|
83
|
-
stream_options: { include_usage: true },
|
|
84
61
|
model: options.model,
|
|
85
|
-
|
|
86
|
-
|
|
62
|
+
input: conversation,
|
|
63
|
+
reasoning,
|
|
87
64
|
temperature: model_options?.temperature,
|
|
88
65
|
top_p: model_options?.top_p,
|
|
89
|
-
|
|
90
|
-
frequency_penalty: model_options?.frequency_penalty,
|
|
91
|
-
n: 1,
|
|
92
|
-
max_completion_tokens: model_options?.max_tokens, //TODO: use max_tokens for older models, currently relying on OpenAI to handle it
|
|
66
|
+
max_output_tokens: model_options?.max_tokens,
|
|
93
67
|
tools: useTools ? toolDefs : undefined,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
json_schema: {
|
|
68
|
+
text: parsedSchema ? {
|
|
69
|
+
format: {
|
|
70
|
+
type: "json_schema",
|
|
98
71
|
name: "format_output",
|
|
99
72
|
schema: parsedSchema,
|
|
100
73
|
strict: strictMode,
|
|
101
74
|
}
|
|
102
75
|
} : undefined,
|
|
103
76
|
});
|
|
104
|
-
return
|
|
77
|
+
return mapResponseStream(stream);
|
|
105
78
|
}
|
|
106
79
|
async requestTextCompletion(prompt, options) {
|
|
107
80
|
if (options.model_options?._option_id !== "openai-text" && options.model_options?._option_id !== "openai-thinking") {
|
|
@@ -111,7 +84,7 @@ export class BaseOpenAIDriver extends AbstractDriver {
|
|
|
111
84
|
const model_options = options.model_options;
|
|
112
85
|
insert_image_detail(prompt, model_options?.image_detail ?? "auto");
|
|
113
86
|
const toolDefs = getToolDefinitions(options.tools);
|
|
114
|
-
const useTools = toolDefs ? supportsToolUse(options.model,
|
|
87
|
+
const useTools = toolDefs ? supportsToolUse(options.model, this.provider) : false;
|
|
115
88
|
let conversation = updateConversation(options.conversation, prompt);
|
|
116
89
|
let parsedSchema = undefined;
|
|
117
90
|
let strictMode = false;
|
|
@@ -125,22 +98,19 @@ export class BaseOpenAIDriver extends AbstractDriver {
|
|
|
125
98
|
strictMode = false;
|
|
126
99
|
}
|
|
127
100
|
}
|
|
128
|
-
const
|
|
101
|
+
const reasoning = model_options?.reasoning_effort ? { effort: model_options.reasoning_effort } : undefined;
|
|
102
|
+
const res = await this.service.responses.create({
|
|
129
103
|
stream: false,
|
|
130
104
|
model: options.model,
|
|
131
|
-
|
|
132
|
-
|
|
105
|
+
input: conversation,
|
|
106
|
+
reasoning,
|
|
133
107
|
temperature: model_options?.temperature,
|
|
134
108
|
top_p: model_options?.top_p,
|
|
135
|
-
|
|
136
|
-
frequency_penalty: model_options?.frequency_penalty,
|
|
137
|
-
n: 1,
|
|
138
|
-
max_completion_tokens: model_options?.max_tokens, //TODO: use max_tokens for older models, currently relying on OpenAI to handle it
|
|
109
|
+
max_output_tokens: model_options?.max_tokens, //TODO: use max_tokens for older models, currently relying on OpenAI to handle it
|
|
139
110
|
tools: useTools ? toolDefs : undefined,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
json_schema: {
|
|
111
|
+
text: parsedSchema ? {
|
|
112
|
+
format: {
|
|
113
|
+
type: "json_schema",
|
|
144
114
|
name: "format_output",
|
|
145
115
|
schema: parsedSchema,
|
|
146
116
|
strict: strictMode,
|
|
@@ -151,8 +121,20 @@ export class BaseOpenAIDriver extends AbstractDriver {
|
|
|
151
121
|
if (options.include_original_response) {
|
|
152
122
|
completion.original_response = res;
|
|
153
123
|
}
|
|
154
|
-
conversation = updateConversation(conversation,
|
|
155
|
-
|
|
124
|
+
conversation = updateConversation(conversation, createAssistantMessageFromCompletion(completion));
|
|
125
|
+
// Increment turn counter for deferred stripping
|
|
126
|
+
conversation = incrementConversationTurn(conversation);
|
|
127
|
+
// Strip large base64 image data based on options.stripImagesAfterTurns
|
|
128
|
+
const currentTurn = getConversationMeta(conversation).turnNumber;
|
|
129
|
+
const stripOptions = {
|
|
130
|
+
keepForTurns: options.stripImagesAfterTurns ?? Infinity,
|
|
131
|
+
currentTurn,
|
|
132
|
+
textMaxTokens: options.stripTextMaxTokens
|
|
133
|
+
};
|
|
134
|
+
let processedConversation = stripBase64ImagesFromConversation(conversation, stripOptions);
|
|
135
|
+
// Truncate large text content if configured
|
|
136
|
+
processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
|
|
137
|
+
completion.conversation = processedConversation;
|
|
156
138
|
return completion;
|
|
157
139
|
}
|
|
158
140
|
canStream(_options) {
|
|
@@ -164,6 +146,47 @@ export class BaseOpenAIDriver extends AbstractDriver {
|
|
|
164
146
|
}
|
|
165
147
|
return Promise.resolve(true);
|
|
166
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Build conversation context after streaming completion.
|
|
151
|
+
* Reconstructs the assistant message from accumulated results and applies stripping.
|
|
152
|
+
*/
|
|
153
|
+
buildStreamingConversation(prompt, result, toolUse, options) {
|
|
154
|
+
// Build assistant message from accumulated CompletionResult[]
|
|
155
|
+
const completionResults = result;
|
|
156
|
+
const textContent = completionResultsToText(completionResults);
|
|
157
|
+
// Start with the conversation from options or the prompt
|
|
158
|
+
let conversation = updateConversation(options.conversation, prompt);
|
|
159
|
+
// Add assistant message as EasyInputMessage
|
|
160
|
+
if (textContent) {
|
|
161
|
+
const assistantMessage = {
|
|
162
|
+
role: 'assistant',
|
|
163
|
+
content: textContent,
|
|
164
|
+
};
|
|
165
|
+
conversation = updateConversation(conversation, [assistantMessage]);
|
|
166
|
+
}
|
|
167
|
+
// Add function calls as separate items (Response API format)
|
|
168
|
+
if (toolUse && toolUse.length > 0) {
|
|
169
|
+
const functionCalls = toolUse.map(t => ({
|
|
170
|
+
type: 'function_call',
|
|
171
|
+
call_id: t.id,
|
|
172
|
+
name: t.tool_name,
|
|
173
|
+
arguments: typeof t.tool_input === 'string' ? t.tool_input : JSON.stringify(t.tool_input ?? {}),
|
|
174
|
+
}));
|
|
175
|
+
conversation = updateConversation(conversation, functionCalls);
|
|
176
|
+
}
|
|
177
|
+
// Increment turn counter
|
|
178
|
+
conversation = incrementConversationTurn(conversation);
|
|
179
|
+
// Apply stripping based on options
|
|
180
|
+
const currentTurn = getConversationMeta(conversation).turnNumber;
|
|
181
|
+
const stripOptions = {
|
|
182
|
+
keepForTurns: options.stripImagesAfterTurns ?? Infinity,
|
|
183
|
+
currentTurn,
|
|
184
|
+
textMaxTokens: options.stripTextMaxTokens
|
|
185
|
+
};
|
|
186
|
+
let processedConversation = stripBase64ImagesFromConversation(conversation, stripOptions);
|
|
187
|
+
processedConversation = truncateLargeTextInConversation(processedConversation, stripOptions);
|
|
188
|
+
return processedConversation;
|
|
189
|
+
}
|
|
167
190
|
createTrainingPrompt(options) {
|
|
168
191
|
if (options.model.includes("gpt")) {
|
|
169
192
|
return super.createTrainingPrompt(options);
|
|
@@ -286,44 +309,169 @@ function jobInfo(job) {
|
|
|
286
309
|
details
|
|
287
310
|
};
|
|
288
311
|
}
|
|
289
|
-
function
|
|
290
|
-
if (
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
312
|
+
function mapUsage(usage) {
|
|
313
|
+
if (!usage) {
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
prompt: usage.input_tokens,
|
|
318
|
+
result: usage.output_tokens,
|
|
319
|
+
total: usage.total_tokens,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function completionResultsToText(completionResults) {
|
|
323
|
+
if (!completionResults) {
|
|
324
|
+
return '';
|
|
325
|
+
}
|
|
326
|
+
return completionResults
|
|
327
|
+
.map(r => {
|
|
328
|
+
switch (r.type) {
|
|
329
|
+
case 'text':
|
|
330
|
+
return r.value;
|
|
331
|
+
case 'json':
|
|
332
|
+
return typeof r.value === 'string' ? r.value : JSON.stringify(r.value);
|
|
333
|
+
case 'image':
|
|
334
|
+
// Skip images in conversation - they're in the result
|
|
335
|
+
return '';
|
|
336
|
+
default:
|
|
337
|
+
return String(r.value || '');
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
.join('');
|
|
341
|
+
}
|
|
342
|
+
function createAssistantMessageFromCompletion(completion) {
|
|
343
|
+
const textContent = completionResultsToText(completion.result);
|
|
344
|
+
const result = [];
|
|
345
|
+
// Add assistant text message if present
|
|
346
|
+
if (textContent) {
|
|
347
|
+
const assistantMessage = {
|
|
348
|
+
role: 'assistant',
|
|
349
|
+
content: textContent,
|
|
350
|
+
};
|
|
351
|
+
result.push(assistantMessage);
|
|
352
|
+
}
|
|
353
|
+
// Add function calls as separate items (Response API format)
|
|
354
|
+
if (completion.tool_use && completion.tool_use.length > 0) {
|
|
355
|
+
for (const t of completion.tool_use) {
|
|
356
|
+
const functionCall = {
|
|
357
|
+
type: 'function_call',
|
|
358
|
+
call_id: t.id,
|
|
359
|
+
name: t.tool_name,
|
|
360
|
+
arguments: typeof t.tool_input === 'string'
|
|
361
|
+
? t.tool_input
|
|
362
|
+
: JSON.stringify(t.tool_input ?? {}),
|
|
363
|
+
};
|
|
364
|
+
result.push(functionCall);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return result;
|
|
368
|
+
}
|
|
369
|
+
function mapResponseStream(stream) {
|
|
370
|
+
const toolCallMetadata = new Map();
|
|
371
|
+
return {
|
|
372
|
+
async *[Symbol.asyncIterator]() {
|
|
373
|
+
for await (const event of stream) {
|
|
374
|
+
if (event.type === 'response.output_item.added' && event.item.type === 'function_call') {
|
|
375
|
+
const syntheticId = `tool_${event.output_index}`;
|
|
376
|
+
const actualId = event.item.id ?? event.item.call_id;
|
|
377
|
+
if (actualId) {
|
|
378
|
+
toolCallMetadata.set(actualId, { syntheticId, name: event.item.name });
|
|
296
379
|
}
|
|
297
|
-
|
|
298
|
-
|
|
380
|
+
const toolUse = {
|
|
381
|
+
id: syntheticId,
|
|
382
|
+
_actual_id: actualId,
|
|
383
|
+
tool_name: event.item.name,
|
|
384
|
+
tool_input: '',
|
|
385
|
+
};
|
|
386
|
+
yield {
|
|
387
|
+
result: [],
|
|
388
|
+
tool_use: [toolUse],
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
else if (event.type === 'response.function_call_arguments.delta') {
|
|
392
|
+
const metadata = toolCallMetadata.get(event.item_id);
|
|
393
|
+
const syntheticId = metadata?.syntheticId ?? `tool_${event.output_index}`;
|
|
394
|
+
const toolUse = {
|
|
395
|
+
id: syntheticId,
|
|
396
|
+
_actual_id: event.item_id,
|
|
397
|
+
tool_name: metadata?.name ?? '',
|
|
398
|
+
tool_input: event.delta,
|
|
399
|
+
};
|
|
400
|
+
yield {
|
|
401
|
+
result: [],
|
|
402
|
+
tool_use: [toolUse],
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
// Note: We don't emit response.function_call_arguments.done because the arguments were already
|
|
406
|
+
// streamed via delta events. Emitting it again would duplicate the tool_input content.
|
|
407
|
+
// We only update the metadata to ensure the tool name is captured.
|
|
408
|
+
else if (event.type === 'response.function_call_arguments.done') {
|
|
409
|
+
// Just update metadata, don't yield (arguments already accumulated from delta events)
|
|
410
|
+
const metadata = toolCallMetadata.get(event.item_id);
|
|
411
|
+
const syntheticId = metadata?.syntheticId ?? `tool_${event.output_index}`;
|
|
412
|
+
const tool_name = metadata?.name ?? event.name ?? '';
|
|
413
|
+
if (event.item_id) {
|
|
414
|
+
toolCallMetadata.set(event.item_id, { syntheticId, name: tool_name });
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
else if (event.type === 'response.output_text.delta') {
|
|
418
|
+
yield {
|
|
419
|
+
result: textToCompletionResult(event.delta),
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
// Note: We don't emit response.output_text.done because the text was already
|
|
423
|
+
// streamed via delta events. Emitting it again would duplicate the content.
|
|
424
|
+
else if (event.type === 'response.completed' || event.type === 'response.incomplete' || event.type === 'response.failed') {
|
|
425
|
+
const finalTools = collectTools(event.response.output);
|
|
426
|
+
yield {
|
|
427
|
+
result: [],
|
|
428
|
+
finish_reason: responseFinishReason(event.response, finalTools),
|
|
429
|
+
token_usage: mapUsage(event.response.usage),
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
function insert_image_detail(items, detail_level) {
|
|
437
|
+
if (detail_level === "auto" || detail_level === "low" || detail_level === "high") {
|
|
438
|
+
for (const item of items) {
|
|
439
|
+
// Check if it's an EasyInputMessage or Message with content array
|
|
440
|
+
if ('role' in item && 'content' in item && item.role !== 'assistant') {
|
|
441
|
+
const content = item.content;
|
|
442
|
+
if (Array.isArray(content)) {
|
|
443
|
+
for (const part of content) {
|
|
444
|
+
if (typeof part === 'object' && part.type === 'input_image') {
|
|
445
|
+
part.detail = detail_level;
|
|
446
|
+
}
|
|
299
447
|
}
|
|
300
448
|
}
|
|
301
449
|
}
|
|
302
450
|
}
|
|
303
451
|
}
|
|
304
|
-
return
|
|
452
|
+
return items;
|
|
305
453
|
}
|
|
306
|
-
function convertRoles(
|
|
454
|
+
function convertRoles(items, model) {
|
|
307
455
|
//New openai models use developer role instead of system
|
|
308
456
|
if (model.includes("o1") || model.includes("o3")) {
|
|
309
457
|
if (model.includes("o1-mini") || model.includes("o1-preview")) {
|
|
310
458
|
//o1-mini and o1-preview support neither system nor developer
|
|
311
|
-
for (const
|
|
312
|
-
if (
|
|
313
|
-
|
|
459
|
+
for (const item of items) {
|
|
460
|
+
if ('role' in item && item.role === 'system') {
|
|
461
|
+
item.role = 'user';
|
|
314
462
|
}
|
|
315
463
|
}
|
|
316
464
|
}
|
|
317
465
|
else {
|
|
318
466
|
//Models newer than o1 use developer role
|
|
319
|
-
for (const
|
|
320
|
-
if (
|
|
321
|
-
|
|
467
|
+
for (const item of items) {
|
|
468
|
+
if ('role' in item && item.role === 'system') {
|
|
469
|
+
item.role = 'developer';
|
|
322
470
|
}
|
|
323
471
|
}
|
|
324
472
|
}
|
|
325
473
|
}
|
|
326
|
-
return
|
|
474
|
+
return items;
|
|
327
475
|
}
|
|
328
476
|
//Structured output support is typically aligned with tool use support
|
|
329
477
|
//Not true for realtime models, which do not support structured output, but do support tool use.
|
|
@@ -352,62 +500,63 @@ function getToolDefinition(toolDef) {
|
|
|
352
500
|
}
|
|
353
501
|
return {
|
|
354
502
|
type: "function",
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
strict: strictMode,
|
|
360
|
-
},
|
|
503
|
+
name: toolDef.name,
|
|
504
|
+
description: toolDef.description,
|
|
505
|
+
parameters: parsedSchema ?? null,
|
|
506
|
+
strict: strictMode,
|
|
361
507
|
};
|
|
362
508
|
}
|
|
363
|
-
function
|
|
364
|
-
if (
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
function updateConversation(conversation, message) {
|
|
370
|
-
if (!message) {
|
|
371
|
-
return conversation;
|
|
509
|
+
function updateConversation(conversation, items) {
|
|
510
|
+
if (!items) {
|
|
511
|
+
// Unwrap array if wrapped, otherwise treat as array
|
|
512
|
+
const unwrapped = unwrapConversationArray(conversation);
|
|
513
|
+
return unwrapped ?? (conversation || []);
|
|
372
514
|
}
|
|
373
515
|
if (!conversation) {
|
|
374
|
-
return
|
|
516
|
+
return items;
|
|
375
517
|
}
|
|
376
|
-
|
|
518
|
+
// Unwrap array if wrapped, otherwise treat as array
|
|
519
|
+
const unwrapped = unwrapConversationArray(conversation);
|
|
520
|
+
const convArray = unwrapped ?? conversation;
|
|
521
|
+
return [...convArray, ...items];
|
|
377
522
|
}
|
|
378
|
-
export function collectTools(
|
|
379
|
-
if (!
|
|
523
|
+
export function collectTools(output) {
|
|
524
|
+
if (!output) {
|
|
380
525
|
return undefined;
|
|
381
526
|
}
|
|
382
527
|
const tools = [];
|
|
383
|
-
for (const
|
|
384
|
-
|
|
385
|
-
id
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
528
|
+
for (const item of output) {
|
|
529
|
+
if (item.type === 'function_call') {
|
|
530
|
+
const id = item.call_id || item.id;
|
|
531
|
+
if (!id) {
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
tools.push({
|
|
535
|
+
id,
|
|
536
|
+
tool_name: item.name ?? '',
|
|
537
|
+
tool_input: safeJsonParse(item.arguments),
|
|
538
|
+
});
|
|
539
|
+
}
|
|
389
540
|
}
|
|
390
541
|
return tools.length > 0 ? tools : undefined;
|
|
391
542
|
}
|
|
392
|
-
function createPromptFromResponse(response) {
|
|
393
|
-
const messages = [];
|
|
394
|
-
if (response) {
|
|
395
|
-
messages.push({
|
|
396
|
-
role: response.role,
|
|
397
|
-
content: [{
|
|
398
|
-
type: "text",
|
|
399
|
-
text: response.content ?? ""
|
|
400
|
-
}],
|
|
401
|
-
tool_calls: response.tool_calls,
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
return messages;
|
|
405
|
-
}
|
|
406
543
|
//For strict mode false
|
|
407
544
|
function limitedSchemaFormat(schema) {
|
|
408
545
|
const formattedSchema = { ...schema };
|
|
409
546
|
// Defaults not supported
|
|
410
547
|
delete formattedSchema.default;
|
|
548
|
+
// OpenAI requires type field even in non-strict mode
|
|
549
|
+
// If no type is specified, default to 'object' for properties with format/editor hints,
|
|
550
|
+
// otherwise 'string' as a safe fallback
|
|
551
|
+
if (!formattedSchema.type && formattedSchema.description) {
|
|
552
|
+
// Properties with format: "document" or editor hints are typically objects
|
|
553
|
+
if (formattedSchema.format === 'document' || formattedSchema.editor) {
|
|
554
|
+
formattedSchema.type = 'object';
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
formattedSchema.type = 'string';
|
|
558
|
+
}
|
|
559
|
+
}
|
|
411
560
|
if (formattedSchema?.properties) {
|
|
412
561
|
// Process each property recursively
|
|
413
562
|
for (const propName of Object.keys(formattedSchema.properties)) {
|
|
@@ -443,6 +592,10 @@ function openAISchemaFormat(schema, nesting = 0) {
|
|
|
443
592
|
// Process each property recursively
|
|
444
593
|
for (const propName of Object.keys(formattedSchema.properties)) {
|
|
445
594
|
const property = formattedSchema.properties[propName];
|
|
595
|
+
// OpenAI strict mode requires all properties to have a type
|
|
596
|
+
if (!property?.type) {
|
|
597
|
+
throw new Error(`Property '${propName}' is missing required 'type' field for OpenAI strict mode`);
|
|
598
|
+
}
|
|
446
599
|
// Recursively process properties
|
|
447
600
|
formattedSchema.properties[propName] = openAISchemaFormat(property, nesting + 1);
|
|
448
601
|
// Process arrays with items of type object
|
|
@@ -461,4 +614,47 @@ function openAISchemaFormat(schema, nesting = 0) {
|
|
|
461
614
|
}
|
|
462
615
|
return formattedSchema;
|
|
463
616
|
}
|
|
617
|
+
function extractTextFromResponse(response) {
|
|
618
|
+
if (response.output_text) {
|
|
619
|
+
return response.output_text;
|
|
620
|
+
}
|
|
621
|
+
const collected = [];
|
|
622
|
+
for (const item of response.output ?? []) {
|
|
623
|
+
if (item.type === 'message') {
|
|
624
|
+
const text = item.content
|
|
625
|
+
.map(part => part.type === 'output_text' ? part.text : '')
|
|
626
|
+
.join('');
|
|
627
|
+
if (text) {
|
|
628
|
+
collected.push(text);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return collected.join("\n");
|
|
633
|
+
}
|
|
634
|
+
function responseFinishReason(response, tools) {
|
|
635
|
+
if (tools && tools.length > 0) {
|
|
636
|
+
return "tool_use";
|
|
637
|
+
}
|
|
638
|
+
if (response.status === 'incomplete') {
|
|
639
|
+
if (response.incomplete_details?.reason === 'max_output_tokens') {
|
|
640
|
+
return 'length';
|
|
641
|
+
}
|
|
642
|
+
return response.incomplete_details?.reason ?? 'incomplete';
|
|
643
|
+
}
|
|
644
|
+
if (response.status && response.status !== 'completed') {
|
|
645
|
+
return response.status;
|
|
646
|
+
}
|
|
647
|
+
return 'stop';
|
|
648
|
+
}
|
|
649
|
+
function safeJsonParse(value) {
|
|
650
|
+
if (typeof value !== 'string') {
|
|
651
|
+
return value;
|
|
652
|
+
}
|
|
653
|
+
try {
|
|
654
|
+
return JSON.parse(value);
|
|
655
|
+
}
|
|
656
|
+
catch {
|
|
657
|
+
return value;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
464
660
|
//# sourceMappingURL=index.js.map
|