@peopl-health/nexus 2.2.8 → 2.2.9
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.
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
const { OpenAI } = require('openai');
|
|
2
2
|
const { retryWithBackoff } = require('../utils/retryHelper');
|
|
3
|
+
const {
|
|
4
|
+
handlePendingFunctionCalls: handlePendingFunctionCallsUtil,
|
|
5
|
+
handleRequiresAction: handleRequiresActionUtil,
|
|
6
|
+
transformToolsForResponsesAPI: transformToolsForResponsesAPIUtil
|
|
7
|
+
} = require('./OpenAIResponsesProviderTools');
|
|
3
8
|
|
|
4
9
|
const CONVERSATION_PREFIX = 'conv_';
|
|
5
10
|
const RESPONSE_PREFIX = 'resp_';
|
|
@@ -265,11 +270,29 @@ class OpenAIResponsesProvider {
|
|
|
265
270
|
truncationStrategy,
|
|
266
271
|
tools = [],
|
|
267
272
|
model,
|
|
273
|
+
assistant,
|
|
268
274
|
} = {}) {
|
|
269
275
|
try {
|
|
270
276
|
const id = this._ensurethreadId(threadId);
|
|
271
277
|
const messages = this._responseInput(additionalMessages) || [];
|
|
272
278
|
|
|
279
|
+
// Check for pending function calls in the conversation before creating a new response
|
|
280
|
+
if (assistant && toolOutputs.length === 0) {
|
|
281
|
+
try {
|
|
282
|
+
const conversationMessages = await this.listMessages({ threadId: id, order: 'desc', limit: 50 });
|
|
283
|
+
const items = conversationMessages?.data || [];
|
|
284
|
+
const pendingOutputs = await handlePendingFunctionCallsUtil(assistant, items);
|
|
285
|
+
if (pendingOutputs.length > 0) {
|
|
286
|
+
toolOutputs = pendingOutputs;
|
|
287
|
+
}
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.warn('[OpenAIResponsesProvider] Error checking for pending function calls:', error?.message);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Transform tools to Responses API format (name at top level, not nested in function)
|
|
294
|
+
const transformedTools = transformToolsForResponsesAPIUtil(this.variant, tools);
|
|
295
|
+
|
|
273
296
|
const payload = this._cleanObject({
|
|
274
297
|
conversation: id,
|
|
275
298
|
prompt: { id: assistantId },
|
|
@@ -281,7 +304,7 @@ class OpenAIResponsesProvider {
|
|
|
281
304
|
temperature,
|
|
282
305
|
max_output_tokens: maxOutputTokens,
|
|
283
306
|
truncation_strategy: truncationStrategy,
|
|
284
|
-
tools:
|
|
307
|
+
tools: transformedTools,
|
|
285
308
|
});
|
|
286
309
|
|
|
287
310
|
console.log('payload', payload);
|
|
@@ -390,34 +413,7 @@ class OpenAIResponsesProvider {
|
|
|
390
413
|
}
|
|
391
414
|
|
|
392
415
|
async handleRequiresAction(assistant, run, threadId) {
|
|
393
|
-
|
|
394
|
-
if (functionCalls.length === 0) {
|
|
395
|
-
return [];
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const outputs = [];
|
|
399
|
-
|
|
400
|
-
for (const call of functionCalls) {
|
|
401
|
-
try {
|
|
402
|
-
const name = call.name;
|
|
403
|
-
const args = call.arguments ? JSON.parse(call.arguments) : {};
|
|
404
|
-
const result = await assistant.executeTool(name, args);
|
|
405
|
-
outputs.push({
|
|
406
|
-
type: 'function_call_output',
|
|
407
|
-
call_id: call.call_id,
|
|
408
|
-
output: typeof result === 'string' ? result : JSON.stringify(result)
|
|
409
|
-
});
|
|
410
|
-
} catch (error) {
|
|
411
|
-
console.error('[OpenAIResponsesProvider] Tool execution failed', error);
|
|
412
|
-
outputs.push({
|
|
413
|
-
type: 'function_call_output',
|
|
414
|
-
call_id: call.call_id,
|
|
415
|
-
output: JSON.stringify({ success: false, error: error?.message || 'Tool execution failed' })
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return outputs;
|
|
416
|
+
return await handleRequiresActionUtil(assistant, run);
|
|
421
417
|
}
|
|
422
418
|
|
|
423
419
|
async checkRunStatus(assistant, thread_id, run_id, retryCount = 0, maxRetries = DEFAULT_MAX_RETRIES, actionHandled = false) {
|
|
@@ -425,33 +421,62 @@ class OpenAIResponsesProvider {
|
|
|
425
421
|
let run = await this.getRun({ threadId: thread_id, runId: run_id });
|
|
426
422
|
console.log(`Status: ${run.status} ${thread_id} ${run_id} (attempt ${retryCount + 1})`);
|
|
427
423
|
|
|
424
|
+
if (run.status === 'completed') {
|
|
425
|
+
return {run, completed: true};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (run.status === 'failed' || run.status === 'cancelled' || run.status === 'expired') {
|
|
429
|
+
return {run, completed: false};
|
|
430
|
+
}
|
|
431
|
+
|
|
428
432
|
const needsFunctionCall = run.output?.some(item => item.type === 'function_call');
|
|
429
433
|
if (needsFunctionCall && !actionHandled) {
|
|
430
434
|
if (retryCount >= maxRetries) {
|
|
435
|
+
console.warn('[OpenAIResponsesProvider] Max retries reached while handling function calls');
|
|
431
436
|
return {run, completed: false};
|
|
432
437
|
}
|
|
433
438
|
|
|
434
|
-
const outputs = await
|
|
439
|
+
const outputs = await handleRequiresActionUtil(assistant, run);
|
|
435
440
|
console.log('[OpenAIResponsesProvider] Function call outputs:', outputs);
|
|
436
441
|
|
|
437
442
|
if (outputs.length > 0) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
443
|
+
try {
|
|
444
|
+
await this.submitToolOutputs({
|
|
445
|
+
threadId: thread_id,
|
|
446
|
+
runId: run_id,
|
|
447
|
+
toolOutputs: outputs
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
451
|
+
|
|
452
|
+
return this.checkRunStatus(assistant, thread_id, run_id, retryCount + 1, maxRetries, true);
|
|
453
|
+
} catch (submitError) {
|
|
454
|
+
console.error('[OpenAIResponsesProvider] Error submitting tool outputs:', submitError);
|
|
455
|
+
if (retryCount < maxRetries) {
|
|
456
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
457
|
+
return this.checkRunStatus(assistant, thread_id, run_id, retryCount + 1, maxRetries, false);
|
|
458
|
+
}
|
|
459
|
+
return {run, completed: false};
|
|
460
|
+
}
|
|
461
|
+
} else {
|
|
462
|
+
console.warn('[OpenAIResponsesProvider] Function calls detected but no outputs generated');
|
|
463
|
+
return {run, completed: false};
|
|
445
464
|
}
|
|
446
465
|
}
|
|
447
466
|
|
|
448
|
-
|
|
467
|
+
if (retryCount < maxRetries) {
|
|
468
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
469
|
+
return this.checkRunStatus(assistant, thread_id, run_id, retryCount + 1, maxRetries, actionHandled);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return {run, completed: false};
|
|
449
473
|
} catch (error) {
|
|
450
|
-
console.error('Error checking run status:', error);
|
|
474
|
+
console.error('[OpenAIResponsesProvider] Error checking run status:', error);
|
|
451
475
|
return {run: null, completed: false};
|
|
452
476
|
}
|
|
453
477
|
}
|
|
454
478
|
|
|
479
|
+
|
|
455
480
|
/**
|
|
456
481
|
* Internal helpers
|
|
457
482
|
*/
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool and function call handling utilities for OpenAIResponsesProvider
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Execute a function call and return the output format
|
|
7
|
+
* @param {Object} assistant - The assistant instance with executeTool method
|
|
8
|
+
* @param {Object} call - The function call object with name, arguments, and call_id
|
|
9
|
+
* @returns {Promise<Object>} Function call output in Responses API format
|
|
10
|
+
*/
|
|
11
|
+
async function executeFunctionCall(assistant, call) {
|
|
12
|
+
try {
|
|
13
|
+
const name = call.name;
|
|
14
|
+
const args = call.arguments ? JSON.parse(call.arguments) : {};
|
|
15
|
+
const result = await assistant.executeTool(name, args);
|
|
16
|
+
return {
|
|
17
|
+
type: 'function_call_output',
|
|
18
|
+
call_id: call.call_id,
|
|
19
|
+
output: typeof result === 'string' ? result : JSON.stringify(result)
|
|
20
|
+
};
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('[OpenAIResponsesProvider] Tool execution failed', error);
|
|
23
|
+
return {
|
|
24
|
+
type: 'function_call_output',
|
|
25
|
+
call_id: call.call_id,
|
|
26
|
+
output: JSON.stringify({ success: false, error: error?.message || 'Tool execution failed' })
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Handle pending function calls from conversation items
|
|
33
|
+
* @param {Object} assistant - The assistant instance with executeTool method
|
|
34
|
+
* @param {Array} conversationItems - Array of conversation items
|
|
35
|
+
* @returns {Promise<Array>} Array of function call outputs
|
|
36
|
+
*/
|
|
37
|
+
async function handlePendingFunctionCalls(assistant, conversationItems) {
|
|
38
|
+
const pendingFunctionCalls = conversationItems.filter(item => item.type === 'function_call');
|
|
39
|
+
const functionOutputs = conversationItems.filter(item => item.type === 'function_call_output');
|
|
40
|
+
|
|
41
|
+
const orphanedCalls = pendingFunctionCalls.filter(call =>
|
|
42
|
+
!functionOutputs.some(output => output.call_id === call.call_id)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
if (orphanedCalls.length === 0) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(`[OpenAIResponsesProvider] Found ${orphanedCalls.length} pending function calls, handling them...`);
|
|
50
|
+
const outputs = [];
|
|
51
|
+
for (const call of orphanedCalls) {
|
|
52
|
+
outputs.push(await executeFunctionCall(assistant, call));
|
|
53
|
+
}
|
|
54
|
+
return outputs;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Handle function calls from a run output
|
|
59
|
+
* @param {Object} assistant - The assistant instance with executeTool method
|
|
60
|
+
* @param {Object} run - The run object with output array
|
|
61
|
+
* @returns {Promise<Array>} Array of function call outputs
|
|
62
|
+
*/
|
|
63
|
+
async function handleRequiresAction(assistant, run) {
|
|
64
|
+
const functionCalls = run.output?.filter(item => item.type === 'function_call') || [];
|
|
65
|
+
if (functionCalls.length === 0) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const outputs = [];
|
|
70
|
+
for (const call of functionCalls) {
|
|
71
|
+
outputs.push(await executeFunctionCall(assistant, call));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return outputs;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Transform tools from Assistants API format to Responses API format
|
|
79
|
+
* @param {string} variant - The API variant ('responses' or 'assistants')
|
|
80
|
+
* @param {Array} tools - Array of tool definitions
|
|
81
|
+
* @returns {Array|undefined} Transformed tools or undefined if empty
|
|
82
|
+
*/
|
|
83
|
+
function transformToolsForResponsesAPI(variant, tools) {
|
|
84
|
+
if (variant !== 'responses' || !Array.isArray(tools) || tools.length === 0) {
|
|
85
|
+
return Array.isArray(tools) && tools.length > 0 ? tools : undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return tools.map(tool => {
|
|
89
|
+
// If tool is in Assistants API format (type: 'function', function: {...})
|
|
90
|
+
if (tool.type === 'function' && tool.function) {
|
|
91
|
+
const transformed = {
|
|
92
|
+
name: tool.function.name,
|
|
93
|
+
type: 'function',
|
|
94
|
+
description: tool.function.description,
|
|
95
|
+
parameters: tool.function.parameters
|
|
96
|
+
};
|
|
97
|
+
if (tool.function.strict !== undefined) {
|
|
98
|
+
transformed.strict = tool.function.strict;
|
|
99
|
+
}
|
|
100
|
+
return transformed;
|
|
101
|
+
}
|
|
102
|
+
// If already in Responses API format, return as-is
|
|
103
|
+
return tool;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
executeFunctionCall,
|
|
109
|
+
handlePendingFunctionCalls,
|
|
110
|
+
handleRequiresAction,
|
|
111
|
+
transformToolsForResponsesAPI
|
|
112
|
+
};
|
|
113
|
+
|
|
@@ -41,12 +41,22 @@ const runAssistantAndWait = async ({
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const provider = createProvider({ variant: process.env.VARIANT || 'assistants' });
|
|
44
|
-
const { polling, ...conversationConfig } = runConfig || {};
|
|
44
|
+
const { polling, tools: configTools, ...conversationConfig } = runConfig || {};
|
|
45
|
+
const variant = provider.getVariant ? provider.getVariant() : (process.env.VARIANT || 'assistants');
|
|
46
|
+
|
|
47
|
+
// Get tool schemas from assistant if available, fallback to config tools
|
|
48
|
+
const tools = assistant.getToolSchemas ? assistant.getToolSchemas() : (configTools || []);
|
|
49
|
+
|
|
50
|
+
// Only pass assistant for Responses API (needed to handle pending function calls)
|
|
51
|
+
const runConfigWithAssistant = variant === 'responses'
|
|
52
|
+
? { ...conversationConfig, assistant }
|
|
53
|
+
: conversationConfig;
|
|
45
54
|
|
|
46
55
|
let run = await provider.runConversation({
|
|
47
56
|
threadId: thread.getConversationId(),
|
|
48
57
|
assistantId: thread.getAssistantId(),
|
|
49
|
-
|
|
58
|
+
tools: tools.length > 0 ? tools : undefined,
|
|
59
|
+
...runConfigWithAssistant,
|
|
50
60
|
});
|
|
51
61
|
|
|
52
62
|
const filter = thread.code ? { code: thread.code, active: true } : null;
|