@inductiv/node-red-openai-api
Version:
Enhance your Node-RED projects with advanced AI capabilities.
1,562 lines (1,430 loc) • 108 kB
JavaScript
"use strict";
// This file is the broad API surface sanity check.
// It proves our method wrappers, examples, and help text still line up with the OpenAI SDK contract.
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const test = require("node:test");
const adminNoa81EditorMethods = [
["getOrganizationUsageFileSearchCalls", "retrieve organization usage file search calls", "Retrieve Organization Usage File Search Calls"],
["getOrganizationUsageWebSearchCalls", "retrieve organization usage web search calls", "Retrieve Organization Usage Web Search Calls"],
["getOrganizationDataRetention", "retrieve organization data retention", "Retrieve Organization Data Retention"],
["modifyOrganizationDataRetention", "modify organization data retention", "Modify Organization Data Retention"],
["createOrganizationSpendAlert", "create organization spend alert", "Create Organization Spend Alert"],
["modifyOrganizationSpendAlert", "modify organization spend alert", "Modify Organization Spend Alert"],
["listOrganizationSpendAlerts", "list organization spend alerts", "List Organization Spend Alerts"],
["deleteOrganizationSpendAlert", "delete organization spend alert", "Delete Organization Spend Alert"],
["getProjectDataRetention", "retrieve project data retention", "Retrieve Project Data Retention"],
["modifyProjectDataRetention", "modify project data retention", "Modify Project Data Retention"],
["createProjectSpendAlert", "create project spend alert", "Create Project Spend Alert"],
["modifyProjectSpendAlert", "modify project spend alert", "Modify Project Spend Alert"],
["listProjectSpendAlerts", "list project spend alerts", "List Project Spend Alerts"],
["deleteProjectSpendAlert", "delete project spend alert", "Delete Project Spend Alert"],
["getProjectModelPermissions", "retrieve project model permissions", "Retrieve Project Model Permissions"],
["modifyProjectModelPermissions", "modify project model permissions", "Modify Project Model Permissions"],
["deleteProjectModelPermissions", "delete project model permissions", "Delete Project Model Permissions"],
["getProjectHostedToolPermissions", "retrieve project hosted tool permissions", "Retrieve Project Hosted Tool Permissions"],
["modifyProjectHostedToolPermissions", "modify project hosted tool permissions", "Modify Project Hosted Tool Permissions"],
["getOrganizationUserRole", "retrieve organization user role", "Retrieve Organization User Role"],
["getOrganizationGroup", "retrieve organization group", "Retrieve Organization Group"],
["getOrganizationGroupUser", "retrieve organization group user", "Retrieve Organization Group User"],
["getOrganizationGroupRole", "retrieve organization group role", "Retrieve Organization Group Role"],
["getOrganizationRole", "retrieve organization role", "Retrieve Organization Role"],
["modifyProjectServiceAccount", "modify project service account", "Modify Project Service Account"],
["getProjectUserRole", "retrieve project user role", "Retrieve Project User Role"],
["getProjectGroup", "retrieve project group", "Retrieve Project Group"],
["getProjectGroupRole", "retrieve project group role", "Retrieve Project Group Role"],
["getProjectRole", "retrieve project role", "Retrieve Project Role"],
];
const workloadIdentityAuditEventTypes = [
"workload_identity_provider.created",
"workload_identity_provider.updated",
"workload_identity_provider.deleted",
"workload_identity_provider_mapping.created",
"workload_identity_provider_mapping.updated",
"workload_identity_provider_mapping.deleted",
];
function withMockedOpenAI(FakeOpenAI, callback) {
const openaiModule = require("openai");
const originalDescriptor = Object.getOwnPropertyDescriptor(openaiModule, "OpenAI");
Object.defineProperty(openaiModule, "OpenAI", {
value: FakeOpenAI,
configurable: true,
enumerable: true,
writable: true,
});
const run = async () => {
try {
return await callback();
} finally {
if (originalDescriptor) {
Object.defineProperty(openaiModule, "OpenAI", originalDescriptor);
}
}
};
return run();
}
test("responses methods map parse/delete/cancel/compact/input-items/input-tokens to OpenAI SDK", async () => {
const calls = [];
class FakeOpenAI {
constructor(clientParams) {
calls.push({ method: "ctor", clientParams });
this.responses = {
parse: async (payload) => {
calls.push({ method: "responses.parse", payload });
return {
id: "resp_parsed",
output_parsed: { answer: "done" },
};
},
delete: async (responseId, options) => {
calls.push({ method: "responses.delete", responseId, options });
return { id: responseId, deleted: true };
},
cancel: async (responseId, options) => {
calls.push({ method: "responses.cancel", responseId, options });
return { id: responseId, status: "cancelled" };
},
compact: async (payload) => {
calls.push({ method: "responses.compact", payload });
return { id: "compaction_1", object: "response.compaction" };
},
inputItems: {
list: async (responseId, options) => {
calls.push({ method: "responses.inputItems.list", responseId, options });
return {
data: [{ id: "item_1" }, { id: "item_2" }],
};
},
},
inputTokens: {
count: async (payload) => {
calls.push({ method: "responses.inputTokens.count", payload });
return { object: "response.input_tokens", input_tokens: 42 };
},
},
};
}
}
await withMockedOpenAI(FakeOpenAI, async () => {
const modulePath = require.resolve("../src/responses/methods.js");
delete require.cache[modulePath];
const responsesMethods = require("../src/responses/methods.js");
const clientContext = { clientParams: { apiKey: "sk-test", baseURL: "https://api.example.com/v1" } };
const parsedResponse = await responsesMethods.parseModelResponse.call(clientContext, {
payload: {
model: "gpt-5.4-mini",
input: "Return structured output.",
text: {
format: {
type: "json_schema",
name: "result",
schema: {
type: "object",
properties: {
answer: { type: "string" },
},
required: ["answer"],
additionalProperties: false,
},
},
},
},
});
assert.deepEqual(parsedResponse, {
id: "resp_parsed",
output_parsed: { answer: "done" },
});
const deleteResponse = await responsesMethods.deleteModelResponse.call(clientContext, {
payload: {
response_id: "resp_123",
},
});
assert.deepEqual(deleteResponse, { id: "resp_123", deleted: true });
const cancelResponse = await responsesMethods.cancelModelResponse.call(clientContext, {
payload: {
response_id: "resp_123",
},
});
assert.deepEqual(cancelResponse, { id: "resp_123", status: "cancelled" });
const compactResponse = await responsesMethods.compactModelResponse.call(clientContext, {
payload: {
model: "gpt-5.2",
input: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
service_tier: "auto",
},
});
assert.deepEqual(compactResponse, {
id: "compaction_1",
object: "response.compaction",
});
const inputItems = await responsesMethods.listInputItems.call(clientContext, {
payload: {
response_id: "resp_123",
order: "desc",
include: ["message.input_image.image_url"],
},
});
assert.deepEqual(inputItems, [{ id: "item_1" }, { id: "item_2" }]);
const inputTokenCount = await responsesMethods.countInputTokens.call(clientContext, {
payload: {
model: "gpt-4.1-mini",
input: "hello",
personality: "friendly",
},
});
assert.deepEqual(inputTokenCount, {
object: "response.input_tokens",
input_tokens: 42,
});
delete require.cache[modulePath];
});
const responseCalls = calls.filter((entry) => entry.method !== "ctor");
assert.deepEqual(responseCalls, [
{
method: "responses.parse",
payload: {
model: "gpt-5.4-mini",
input: "Return structured output.",
text: {
format: {
type: "json_schema",
name: "result",
schema: {
type: "object",
properties: {
answer: { type: "string" },
},
required: ["answer"],
additionalProperties: false,
},
},
},
},
},
{
method: "responses.delete",
responseId: "resp_123",
options: {},
},
{
method: "responses.cancel",
responseId: "resp_123",
options: {},
},
{
method: "responses.compact",
payload: {
model: "gpt-5.2",
input: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
service_tier: "auto",
},
},
{
method: "responses.inputItems.list",
responseId: "resp_123",
options: {
order: "desc",
include: ["message.input_image.image_url"],
},
},
{
method: "responses.inputTokens.count",
payload: {
model: "gpt-4.1-mini",
input: "hello",
personality: "friendly",
},
},
]);
});
test("responses create forwards phase, prompt_cache_key, tool_search, defer_loading, computer, and gpt-5.4-mini payloads", async () => {
const calls = [];
class FakeOpenAI {
constructor(clientParams) {
calls.push({ method: "ctor", clientParams });
this.responses = {
create: async (payload) => {
calls.push({ method: "responses.create", payload });
return { id: "resp_123", status: "completed" };
},
};
}
}
const requestPayload = {
model: "gpt-5.4-mini",
prompt_cache_key: "responses-agentic-demo-v1",
input: [
{
type: "additional_tools",
role: "developer",
id: "item_tools_release_lookup",
tools: [
{
type: "function",
name: "lookup_release_ticket",
description: "Look up a release ticket by id.",
parameters: {
type: "object",
properties: {
ticket_id: { type: "string" },
},
required: ["ticket_id"],
additionalProperties: false,
},
strict: true,
},
],
},
{
type: "message",
role: "assistant",
phase: "commentary",
content: [{ type: "output_text", text: "Planning the response." }],
},
{
type: "message",
role: "user",
content: [{ type: "input_text", text: "Summarize the release work." }],
},
],
tools: [
{ type: "tool_search" },
{
type: "function",
name: "lookup_release_ticket",
description: "Look up a release ticket by id.",
parameters: {
type: "object",
properties: {
ticket_id: { type: "string" },
},
required: ["ticket_id"],
additionalProperties: false,
},
strict: true,
defer_loading: true,
},
{ type: "computer" },
],
};
await withMockedOpenAI(FakeOpenAI, async () => {
const modulePath = require.resolve("../src/responses/methods.js");
delete require.cache[modulePath];
const responsesMethods = require("../src/responses/methods.js");
const clientContext = { clientParams: { apiKey: "sk-test", baseURL: "https://api.example.com/v1" } };
const response = await responsesMethods.createModelResponse.call(clientContext, {
payload: requestPayload,
});
assert.deepEqual(response, { id: "resp_123", status: "completed" });
delete require.cache[modulePath];
});
const createCalls = calls.filter((entry) => entry.method === "responses.create");
assert.deepEqual(createCalls, [
{
method: "responses.create",
payload: requestPayload,
},
]);
});
test("responses create preserves computer keypress actions with keys arrays", async () => {
class FakeOpenAI {
constructor() {
this.responses = {
create: async () => ({
id: "resp_computer_1",
output: [
{
id: "item_1",
type: "computer_call",
action: {
type: "keypress",
keys: ["CTRL", "L"],
},
},
],
}),
};
}
}
await withMockedOpenAI(FakeOpenAI, async () => {
const modulePath = require.resolve("../src/responses/methods.js");
delete require.cache[modulePath];
const responsesMethods = require("../src/responses/methods.js");
const clientContext = {
clientParams: {
apiKey: "sk-test",
baseURL: "https://api.example.com/v1",
},
};
const response = await responsesMethods.createModelResponse.call(clientContext, {
payload: {
model: "gpt-5.4",
tools: [{ type: "computer" }],
input: "Open the address bar.",
},
});
assert.deepEqual(response, {
id: "resp_computer_1",
output: [
{
id: "item_1",
type: "computer_call",
action: {
type: "keypress",
keys: ["CTRL", "L"],
},
},
],
});
delete require.cache[modulePath];
});
});
test("responses example flows remain valid JSON and cover the documented agentic payload shapes", () => {
const phaseExamplePath = path.join(
__dirname,
"..",
"examples",
"responses",
"phase.json"
);
const toolSearchExamplePath = path.join(
__dirname,
"..",
"examples",
"responses",
"tool-search.json"
);
const computerUseExamplePath = path.join(
__dirname,
"..",
"examples",
"responses",
"computer-use.json"
);
const websocketExamplePath = path.join(
__dirname,
"..",
"examples",
"responses",
"websocket.json"
);
const phaseExample = JSON.parse(fs.readFileSync(phaseExamplePath, "utf8"));
const toolSearchExample = JSON.parse(
fs.readFileSync(toolSearchExamplePath, "utf8")
);
const computerUseExample = JSON.parse(
fs.readFileSync(computerUseExamplePath, "utf8")
);
const websocketExample = JSON.parse(
fs.readFileSync(websocketExamplePath, "utf8")
);
[phaseExample, toolSearchExample, computerUseExample].forEach((flow) => {
assert.ok(Array.isArray(flow));
const openaiNode = flow.find((entry) => entry.type === "OpenAI API");
const commentNodes = flow.filter((entry) => entry.type === "comment");
assert.ok(openaiNode);
assert.equal(openaiNode.method, "createModelResponse");
assert.ok(commentNodes.length >= 1);
});
const phaseInjectNode = phaseExample.find(
(entry) => entry.type === "inject" && entry.name === "Create Phased Response"
);
const toolSearchInjectNode = toolSearchExample.find(
(entry) =>
entry.type === "inject" && entry.name === "Create Tool Search Request"
);
const computerCreateInjectNode = computerUseExample.find(
(entry) => entry.type === "inject" && entry.name === "Create Computer Request"
);
const computerFollowupInjectNode = computerUseExample.find(
(entry) =>
entry.type === "inject" &&
entry.name === "Submit Computer Screenshot (edit placeholders)"
);
assert.ok(phaseInjectNode);
assert.ok(toolSearchInjectNode);
assert.ok(computerCreateInjectNode);
assert.ok(computerFollowupInjectNode);
const phaseMessage = JSON.parse(
phaseInjectNode.props.find((prop) => prop.p === "ai.input[0]").v
);
const toolSearchTool = JSON.parse(
toolSearchInjectNode.props.find((prop) => prop.p === "ai.tools[0]").v
);
const deferredMcpTool = JSON.parse(
toolSearchInjectNode.props.find((prop) => prop.p === "ai.tools[1]").v
);
const additionalToolsItem = JSON.parse(
toolSearchInjectNode.props.find((prop) => prop.p === "ai.input[0]").v
);
const computerTool = JSON.parse(
computerCreateInjectNode.props.find((prop) => prop.p === "ai.tools[0]").v
);
const computerCallOutput = JSON.parse(
computerFollowupInjectNode.props.find((prop) => prop.p === "ai.input[0]").v
);
assert.equal(
phaseInjectNode.props.find((prop) => prop.p === "ai.prompt_cache_key").v,
"responses-phase-example-v1"
);
assert.equal(phaseMessage.phase, "commentary");
assert.equal(toolSearchTool.type, "tool_search");
assert.equal(deferredMcpTool.defer_loading, true);
assert.equal(additionalToolsItem.type, "additional_tools");
assert.equal(additionalToolsItem.role, "developer");
assert.equal(additionalToolsItem.tools[0].name, "lookup_transport_notes");
assert.equal(computerTool.type, "computer");
assert.equal(computerCallOutput.type, "computer_call_output");
assert.equal(computerCallOutput.output.type, "computer_screenshot");
assert.ok(Array.isArray(websocketExample));
const websocketOpenaiNode = websocketExample.find(
(entry) => entry.type === "OpenAI API"
);
const websocketCommentNodes = websocketExample.filter(
(entry) => entry.type === "comment"
);
const connectInjectNode = websocketExample.find(
(entry) =>
entry.type === "inject" && entry.name === "Connect Responses WebSocket"
);
const sendInjectNode = websocketExample.find(
(entry) =>
entry.type === "inject" && entry.name === "Send response.create Event"
);
const closeInjectNode = websocketExample.find(
(entry) =>
entry.type === "inject" && entry.name === "Close Responses WebSocket"
);
assert.ok(websocketOpenaiNode);
assert.equal(websocketOpenaiNode.method, "manageModelResponseWebSocket");
assert.ok(websocketCommentNodes.length >= 2);
assert.ok(connectInjectNode);
assert.ok(sendInjectNode);
assert.ok(closeInjectNode);
assert.equal(
connectInjectNode.props.find((prop) => prop.p === "ai.action").v,
"connect"
);
assert.equal(
sendInjectNode.props.find((prop) => prop.p === "ai.action").v,
"send"
);
assert.equal(
closeInjectNode.props.find((prop) => prop.p === "ai.action").v,
"close"
);
assert.deepEqual(
JSON.parse(sendInjectNode.props.find((prop) => prop.p === "ai.event").v),
{
type: "response.create",
model: "gpt-5.4-nano-2026-03-17",
input: "Say hello from Responses websocket mode in one sentence.",
}
);
});
test("realtime example flow remains valid JSON and documents the nested session contract", () => {
const realtimeExamplePath = path.join(
__dirname,
"..",
"examples",
"realtime",
"client-secrets.json"
);
const realtimeExample = JSON.parse(fs.readFileSync(realtimeExamplePath, "utf8"));
assert.ok(Array.isArray(realtimeExample));
const openaiNode = realtimeExample.find((entry) => entry.type === "OpenAI API");
const realtimeInjectNode = realtimeExample.find(
(entry) =>
entry.type === "inject" &&
entry.name === "Create Realtime 2 Client Secret"
);
const audioInjectNode = realtimeExample.find(
(entry) =>
entry.type === "inject" &&
entry.name === "Create Audio 1.5 Client Secret"
);
const translationInjectNode = realtimeExample.find(
(entry) =>
entry.type === "inject" &&
entry.name === "Create Translation Client Secret"
);
assert.ok(openaiNode);
assert.equal(openaiNode.method, "createRealtimeClientSecret");
assert.ok(realtimeInjectNode);
assert.ok(audioInjectNode);
assert.ok(translationInjectNode);
assert.equal(
realtimeInjectNode.props.find((prop) => prop.p === "ai.session.type").v,
"realtime"
);
assert.equal(
realtimeInjectNode.props.find((prop) => prop.p === "ai.session.model").v,
"gpt-realtime-2"
);
assert.equal(
realtimeInjectNode.props.find((prop) => prop.p === "ai.session.reasoning.effort").v,
"low"
);
assert.equal(
realtimeInjectNode.props.find((prop) => prop.p === "ai.session.parallel_tool_calls").v,
"true"
);
assert.equal(
audioInjectNode.props.find((prop) => prop.p === "ai.session.type").v,
"realtime"
);
assert.equal(
audioInjectNode.props.find((prop) => prop.p === "ai.session.model").v,
"gpt-audio-1.5"
);
assert.equal(
realtimeInjectNode.props.find((prop) => prop.p === "ai.expires_after.seconds").v,
"600"
);
assert.equal(
translationInjectNode.props.find((prop) => prop.p === "ai.session.type").v,
"translation"
);
assert.equal(
translationInjectNode.props.find((prop) => prop.p === "ai.session.model").v,
"gpt-realtime-translate"
);
assert.equal(
translationInjectNode.props.find((prop) => prop.p === "ai.session.audio.input.noise_reduction.type").v,
"near_field"
);
assert.equal(
translationInjectNode.props.find((prop) => prop.p === "ai.session.audio.input.transcription.model").v,
"gpt-realtime-whisper"
);
assert.equal(
translationInjectNode.props.find((prop) => prop.p === "ai.session.audio.input.transcription.delay").v,
"low"
);
assert.equal(
translationInjectNode.props.find((prop) => prop.p === "ai.session.audio.input.turn_detection").v,
"null"
);
assert.equal(
translationInjectNode.props.find((prop) => prop.p === "ai.session.audio.output.language").v,
"es"
);
});
test("responses retrieve streams chunks when stream=true", async () => {
const calls = [];
async function* createFakeStream() {
yield { type: "response.in_progress", sequence_number: 1 };
yield { type: "response.completed", sequence_number: 2 };
}
class FakeOpenAI {
constructor(clientParams) {
calls.push({ method: "ctor", clientParams });
this.responses = {
retrieve: async (responseId, options) => {
calls.push({ method: "responses.retrieve", responseId, options });
if (options.stream) {
return createFakeStream();
}
return { id: responseId, status: "completed" };
},
};
}
}
await withMockedOpenAI(FakeOpenAI, async () => {
const modulePath = require.resolve("../src/responses/methods.js");
delete require.cache[modulePath];
const responsesMethods = require("../src/responses/methods.js");
const sentMessages = [];
const statuses = [];
const node = {
send: (msg) => sentMessages.push(msg),
status: (status) => statuses.push(status),
};
const clientContext = { clientParams: { apiKey: "sk-test" } };
const streamResult = await responsesMethods.getModelResponse.call(clientContext, {
_node: node,
msg: { topic: "t1" },
payload: {
response_id: "resp_stream",
stream: true,
},
});
assert.equal(streamResult, undefined);
assert.deepEqual(sentMessages, [
{
topic: "t1",
payload: { type: "response.in_progress", sequence_number: 1 },
},
{
topic: "t1",
payload: { type: "response.completed", sequence_number: 2 },
},
]);
assert.deepEqual(statuses, [
{
fill: "green",
shape: "dot",
text: "OpenaiApi.status.streaming",
},
{},
]);
const retrieveResponse = await responsesMethods.getModelResponse.call(clientContext, {
_node: node,
msg: { topic: "t1" },
payload: {
response_id: "resp_non_stream",
},
});
assert.deepEqual(retrieveResponse, { id: "resp_non_stream", status: "completed" });
delete require.cache[modulePath];
});
const retrieveCalls = calls.filter((entry) => entry.method === "responses.retrieve");
assert.deepEqual(retrieveCalls, [
{
method: "responses.retrieve",
responseId: "resp_stream",
options: { stream: true },
},
{
method: "responses.retrieve",
responseId: "resp_non_stream",
options: {},
},
]);
});
test("responses stream helper emits events and returns the final parsed response", async () => {
const calls = [];
function createFakeResponseStream() {
return {
async *[Symbol.asyncIterator]() {
yield { type: "response.in_progress", sequence_number: 1 };
yield { type: "response.output_text.delta", sequence_number: 2, delta: "Hello" };
yield { type: "response.completed", sequence_number: 3 };
},
async finalResponse() {
return {
id: "resp_stream_final",
output_parsed: { answer: "Hello" },
};
},
};
}
class FakeOpenAI {
constructor(clientParams) {
calls.push({ method: "ctor", clientParams });
this.responses = {
stream: (payload) => {
calls.push({ method: "responses.stream", payload });
return createFakeResponseStream();
},
};
}
}
await withMockedOpenAI(FakeOpenAI, async () => {
const modulePath = require.resolve("../src/responses/methods.js");
delete require.cache[modulePath];
const responsesMethods = require("../src/responses/methods.js");
const sentMessages = [];
const statuses = [];
const node = {
send: (msg) => sentMessages.push(msg),
status: (status) => statuses.push(status),
};
const clientContext = { clientParams: { apiKey: "sk-test" } };
const finalResponse = await responsesMethods.streamModelResponse.call(clientContext, {
_node: node,
msg: { topic: "stream-helper" },
payload: {
model: "gpt-5.4-mini",
input: "Say hello from the stream helper.",
},
});
assert.deepEqual(finalResponse, {
id: "resp_stream_final",
output_parsed: { answer: "Hello" },
});
assert.deepEqual(sentMessages, [
{
topic: "stream-helper",
payload: { type: "response.in_progress", sequence_number: 1 },
},
{
topic: "stream-helper",
payload: { type: "response.output_text.delta", sequence_number: 2, delta: "Hello" },
},
{
topic: "stream-helper",
payload: { type: "response.completed", sequence_number: 3 },
},
]);
assert.deepEqual(statuses, [
{
fill: "green",
shape: "dot",
text: "OpenaiApi.status.streaming",
},
{},
]);
delete require.cache[modulePath];
});
assert.deepEqual(calls.filter((entry) => entry.method !== "ctor"), [
{
method: "responses.stream",
payload: {
model: "gpt-5.4-mini",
input: "Say hello from the stream helper.",
},
},
]);
});
test("conversation methods map to OpenAI SDK conversations endpoints", async () => {
const calls = [];
class FakeOpenAI {
constructor(clientParams) {
calls.push({ method: "ctor", clientParams });
this.conversations = {
create: async (body) => {
calls.push({ method: "conversations.create", body });
return { id: "conv_new" };
},
retrieve: async (conversationId, options) => {
calls.push({ method: "conversations.retrieve", conversationId, options });
return { id: conversationId };
},
update: async (conversationId, body) => {
calls.push({ method: "conversations.update", conversationId, body });
return { id: conversationId, updated: true };
},
delete: async (conversationId, options) => {
calls.push({ method: "conversations.delete", conversationId, options });
return { id: conversationId, deleted: true };
},
items: {
create: async (conversationId, body) => {
calls.push({ method: "conversations.items.create", conversationId, body });
return { id: "item_new" };
},
retrieve: async (itemId, options) => {
calls.push({ method: "conversations.items.retrieve", itemId, options });
return { id: itemId };
},
list: async (conversationId, options) => {
calls.push({ method: "conversations.items.list", conversationId, options });
return { data: [{ id: "item_1" }, { id: "item_2" }] };
},
delete: async (itemId, options) => {
calls.push({ method: "conversations.items.delete", itemId, options });
return { id: itemId, deleted: true };
},
},
};
}
}
await withMockedOpenAI(FakeOpenAI, async () => {
const modulePath = require.resolve("../src/conversations/methods.js");
delete require.cache[modulePath];
const conversationMethods = require("../src/conversations/methods.js");
const clientContext = { clientParams: { apiKey: "sk-test" } };
const created = await conversationMethods.createConversation.call(clientContext, {
payload: { metadata: { app: "node-red" } },
});
assert.deepEqual(created, { id: "conv_new" });
const fetched = await conversationMethods.getConversation.call(clientContext, {
payload: { conversation_id: "conv_1", include: ["items"] },
});
assert.deepEqual(fetched, { id: "conv_1" });
const updated = await conversationMethods.modifyConversation.call(clientContext, {
payload: { conversation_id: "conv_1", metadata: { env: "dev" } },
});
assert.deepEqual(updated, { id: "conv_1", updated: true });
const deleted = await conversationMethods.deleteConversation.call(clientContext, {
payload: { conversation_id: "conv_1" },
});
assert.deepEqual(deleted, { id: "conv_1", deleted: true });
const createdItem = await conversationMethods.createConversationItem.call(clientContext, {
payload: {
conversation_id: "conv_1",
items: [
{
type: "additional_tools",
role: "developer",
id: "item_tools_shipping_lookup",
tools: [
{
type: "function",
name: "lookup_shipping_options",
description: "Look up available shipping options for an order.",
parameters: {
type: "object",
properties: {
order_id: { type: "string" },
},
required: ["order_id"],
additionalProperties: false,
},
strict: true,
},
],
},
{
type: "message",
role: "assistant",
phase: "commentary",
content: [{ type: "output_text", text: "hello" }],
},
],
},
});
assert.deepEqual(createdItem, { id: "item_new" });
const fetchedItem = await conversationMethods.getConversationItem.call(clientContext, {
payload: {
conversation_id: "conv_1",
item_id: "item_1",
},
});
assert.deepEqual(fetchedItem, { id: "item_1" });
const listedItems = await conversationMethods.listConversationItems.call(clientContext, {
payload: {
conversation_id: "conv_1",
limit: 2,
},
});
assert.deepEqual(listedItems, [{ id: "item_1" }, { id: "item_2" }]);
const deletedItem = await conversationMethods.deleteConversationItem.call(clientContext, {
payload: {
conversation_id: "conv_1",
item_id: "item_1",
},
});
assert.deepEqual(deletedItem, { id: "item_1", deleted: true });
delete require.cache[modulePath];
});
assert.equal(calls.some((entry) => entry.method === "conversations.create"), true);
assert.deepEqual(
calls.find((entry) => entry.method === "conversations.items.create"),
{
method: "conversations.items.create",
conversationId: "conv_1",
body: {
items: [
{
type: "additional_tools",
role: "developer",
id: "item_tools_shipping_lookup",
tools: [
{
type: "function",
name: "lookup_shipping_options",
description: "Look up available shipping options for an order.",
parameters: {
type: "object",
properties: {
order_id: { type: "string" },
},
required: ["order_id"],
additionalProperties: false,
},
strict: true,
},
],
},
{
type: "message",
role: "assistant",
phase: "commentary",
content: [{ type: "output_text", text: "hello" }],
},
],
},
}
);
assert.equal(calls.some((entry) => entry.method === "conversations.items.list"), true);
assert.equal(calls.some((entry) => entry.method === "conversations.items.delete"), true);
});
test("skills methods map to OpenAI SDK skills endpoints", async () => {
const calls = [];
class FakeOpenAI {
constructor(clientParams) {
calls.push({ method: "ctor", clientParams });
this.skills = {
create: async (body) => {
calls.push({ method: "skills.create", body });
return { id: "skill_new" };
},
retrieve: async (skillId, options) => {
calls.push({ method: "skills.retrieve", skillId, options });
return { id: skillId };
},
update: async (skillId, body) => {
calls.push({ method: "skills.update", skillId, body });
return { id: skillId, updated: true };
},
delete: async (skillId, options) => {
calls.push({ method: "skills.delete", skillId, options });
return { id: skillId, deleted: true };
},
list: async (options) => {
calls.push({ method: "skills.list", options });
return { data: [{ id: "skill_1" }, { id: "skill_2" }] };
},
content: {
retrieve: async (skillId, options) => {
calls.push({ method: "skills.content.retrieve", skillId, options });
return { id: skillId, object: "skill.content" };
},
},
versions: {
create: async (skillId, body) => {
calls.push({ method: "skills.versions.create", skillId, body });
return { id: "skill_version_new", skill_id: skillId };
},
retrieve: async (version, options) => {
calls.push({ method: "skills.versions.retrieve", version, options });
return { id: "skill_version_1", version };
},
list: async (skillId, options) => {
calls.push({ method: "skills.versions.list", skillId, options });
return { data: [{ id: "sv_1" }, { id: "sv_2" }] };
},
delete: async (version, options) => {
calls.push({ method: "skills.versions.delete", version, options });
return { deleted: true, version };
},
content: {
retrieve: async (version, options) => {
calls.push({ method: "skills.versions.content.retrieve", version, options });
return { object: "skill.version.content", version };
},
},
},
};
}
}
await withMockedOpenAI(FakeOpenAI, async () => {
const modulePath = require.resolve("../src/skills/methods.js");
delete require.cache[modulePath];
const skillMethods = require("../src/skills/methods.js");
const clientContext = { clientParams: { apiKey: "sk-test" } };
const listed = await skillMethods.listSkills.call(clientContext, {
payload: { order: "desc", limit: 2 },
});
assert.deepEqual(listed, [{ id: "skill_1" }, { id: "skill_2" }]);
const created = await skillMethods.createSkill.call(clientContext, {
payload: { files: ["./skill.zip"] },
});
assert.deepEqual(created, { id: "skill_new" });
const fetched = await skillMethods.getSkill.call(clientContext, {
payload: { skill_id: "skill_1" },
});
assert.deepEqual(fetched, { id: "skill_1" });
const updated = await skillMethods.modifySkill.call(clientContext, {
payload: { skill_id: "skill_1", default_version: "2" },
});
assert.deepEqual(updated, { id: "skill_1", updated: true });
const deleted = await skillMethods.deleteSkill.call(clientContext, {
payload: { skill_id: "skill_1" },
});
assert.deepEqual(deleted, { id: "skill_1", deleted: true });
const content = await skillMethods.getSkillContent.call(clientContext, {
payload: { skill_id: "skill_1" },
});
assert.deepEqual(content, { id: "skill_1", object: "skill.content" });
const listedVersions = await skillMethods.listSkillVersions.call(clientContext, {
payload: { skill_id: "skill_1", order: "asc" },
});
assert.deepEqual(listedVersions, [{ id: "sv_1" }, { id: "sv_2" }]);
const createdVersion = await skillMethods.createSkillVersion.call(clientContext, {
payload: { skill_id: "skill_1", default: true, files: ["./v2.zip"] },
});
assert.deepEqual(createdVersion, { id: "skill_version_new", skill_id: "skill_1" });
const fetchedVersion = await skillMethods.getSkillVersion.call(clientContext, {
payload: { skill_id: "skill_1", version: "2" },
});
assert.deepEqual(fetchedVersion, { id: "skill_version_1", version: "2" });
const deletedVersion = await skillMethods.deleteSkillVersion.call(clientContext, {
payload: { skill_id: "skill_1", version: "2" },
});
assert.deepEqual(deletedVersion, { deleted: true, version: "2" });
const versionContent = await skillMethods.getSkillVersionContent.call(clientContext, {
payload: { skill_id: "skill_1", version: "2" },
});
assert.deepEqual(versionContent, { object: "skill.version.content", version: "2" });
delete require.cache[modulePath];
});
const skillCalls = calls.filter((entry) => entry.method !== "ctor");
assert.deepEqual(skillCalls, [
{
method: "skills.list",
options: { order: "desc", limit: 2 },
},
{
method: "skills.create",
body: { files: ["./skill.zip"] },
},
{
method: "skills.retrieve",
skillId: "skill_1",
options: {},
},
{
method: "skills.update",
skillId: "skill_1",
body: { default_version: "2" },
},
{
method: "skills.delete",
skillId: "skill_1",
options: {},
},
{
method: "skills.content.retrieve",
skillId: "skill_1",
options: {},
},
{
method: "skills.versions.list",
skillId: "skill_1",
options: { order: "asc" },
},
{
method: "skills.versions.create",
skillId: "skill_1",
body: { default: true, files: ["./v2.zip"] },
},
{
method: "skills.versions.retrieve",
version: "2",
options: { skill_id: "skill_1" },
},
{
method: "skills.versions.delete",
version: "2",
options: { skill_id: "skill_1" },
},
{
method: "skills.versions.content.retrieve",
version: "2",
options: { skill_id: "skill_1" },
},
]);
});
test("chatkit methods map to OpenAI SDK beta chatkit endpoints", async () => {
const calls = [];
class FakeOpenAI {
constructor(clientParams) {
calls.push({ method: "ctor", clientParams });
this.beta = {
chatkit: {
sessions: {
create: async (body) => {
calls.push({ method: "beta.chatkit.sessions.create", body });
return {
id: "cksess_1",
object: "chatkit.session",
user: body.user,
};
},
cancel: async (sessionId, options) => {
calls.push({
method: "beta.chatkit.sessions.cancel",
sessionId,
options,
});
return {
id: sessionId,
object: "chatkit.session",
status: "cancelled",
};
},
},
threads: {
retrieve: async (threadId, options) => {
calls.push({
method: "beta.chatkit.threads.retrieve",
threadId,
options,
});
return { id: threadId, object: "chatkit.thread" };
},
list: async (options) => {
calls.push({ method: "beta.chatkit.threads.list", options });
return {
data: [{ id: "cthr_1" }, { id: "cthr_2" }],
};
},
delete: async (threadId, options) => {
calls.push({
method: "beta.chatkit.threads.delete",
threadId,
options,
});
return { id: threadId, deleted: true };
},
listItems: async (threadId, options) => {
calls.push({
method: "beta.chatkit.threads.listItems",
threadId,
options,
});
return {
data: [{ id: "item_1" }, { id: "item_2" }],
};
},
},
},
};
}
}
await withMockedOpenAI(FakeOpenAI, async () => {
const modulePath = require.resolve("../src/chatkit/methods.js");
delete require.cache[modulePath];
const chatkitMethods = require("../src/chatkit/methods.js");
const clientContext = { clientParams: { apiKey: "sk-test" } };
const createdSession = await chatkitMethods.createChatKitSession.call(
clientContext,
{
payload: {
user: "user_123",
workflow: {
id: "wf_123",
version: "3",
state_variables: {
region: "us",
retries: 2,
},
},
},
}
);
assert.deepEqual(createdSession, {
id: "cksess_1",
object: "chatkit.session",
user: "user_123",
});
const cancelledSession = await chatkitMethods.cancelChatKitSession.call(
clientContext,
{
payload: {
session_id: "cksess_1",
},
}
);
assert.deepEqual(cancelledSession, {
id: "cksess_1",
object: "chatkit.session",
status: "cancelled",
});
const listedThreads = await chatkitMethods.listChatKitThreads.call(
clientContext,
{
payload: {
user: "user_123",
limit: 2,
},
}
);
assert.deepEqual(listedThreads, [{ id: "cthr_1" }, { id: "cthr_2" }]);
const fetchedThread = await chatkitMethods.getChatKitThread.call(
clientContext,
{
payload: {
thread_id: "cthr_1",
},
}
);
assert.deepEqual(fetchedThread, { id: "cthr_1", object: "chatkit.thread" });
const deletedThread = await chatkitMethods.deleteChatKitThread.call(
clientContext,
{
payload: {
thread_id: "cthr_1",
},
}
);
assert.deepEqual(deletedThread, { id: "cthr_1", deleted: true });
const threadItems = await chatkitMethods.listChatKitThreadItems.call(
clientContext,
{
payload: {
thread_id: "cthr_1",
limit: 2,
order: "desc",
},
}
);
assert.deepEqual(threadItems, [{ id: "item_1" }, { id: "item_2" }]);
delete require.cache[modulePath];
});
const chatkitCalls = calls.filter((entry) => entry.method !== "ctor");
assert.deepEqual(chatkitCalls, [
{
method: "beta.chatkit.sessions.create",
body: {
user: "user_123",
workflow: {
id: "wf_123",
version: "3",
state_variables: {
region: "us",
retries: 2,
},
},
},
},
{
method: "beta.chatkit.sessions.cancel",
sessionId: "cksess_1",
options: {},
},
{
method: "beta.chatkit.threads.list",
options: {
user: "user_123",
limit: 2,
},
},
{
method: "beta.chatkit.threads.retrieve",
threadId: "cthr_1",
options: {},
},
{
method: "beta.chatkit.threads.delete",
threadId: "cthr_1",
options: {},
},
{
method: "beta.chatkit.threads.listItems",
threadId: "cthr_1",
options: {
limit: 2,
order: "desc",
},
},
]);
});
test("evals methods map to OpenAI SDK evals endpoints", async () => {
const calls = [];
class FakeOpenAI {
constructor(clientParams) {
calls.push({ method: "ctor", clientParams });
this.evals = {
create: async (body) => {
calls.push({ method: "evals.create", body });
return { id: "eval_new" };
},
retrieve: async (evalId, options) => {
calls.push({ method: "evals.retrieve", evalId, options });
return { id: evalId };
},
update: async (evalId, body) => {
calls.push({ method: "evals.update", evalId, body });
return { id: evalId, updated: true };
},
delete: async (evalId, options) => {
calls.push({ method: "evals.delete", evalId, options });
return { id: evalId, deleted: true };
},
list: async (options) => {
calls.push({ method: "evals.list", options });
return { data: [{ id: "eval_1" }, { id: "eval_2" }] };
},
runs: {
create: async (evalId, body) => {
calls.push({ method: "evals.runs.create", evalId, body });
return { id: "run_new", eval_id: evalId };
},
retrieve: async (runId, options) => {
calls.push({ method: "evals.runs.retrieve", runId, options });
return { id: runId };
},
list: async (evalId, options) => {
calls.push({ method: "evals.runs.list", evalId, options });
return { data: [{ id: "run_1" }, { id: "run_2" }] };
},
delete: async (runId, options) => {
calls.push({ method: "evals.runs.delete", runId, options });
return { id: runId, deleted: true };
},
cancel: async (runId, options) => {
calls.push({ method: "evals.runs.cancel", runId, options });
return { id: runId, status: "cancelled" };
},
outputItems: {
retrieve: async (outputItemId, options) => {
calls.push({
method: "evals.runs.outputItems.retrieve",
outputItemId,
options,
});
return { id: outputItemId };
},
list: async (runId, options) => {
calls.push({ method: "evals.runs.outputItems.list", runId, options });
return { data: [{ id: "out_1" }, { id: "out_2" }] };
},
},
},
};
}
}
await withMockedOpenAI(FakeOpenAI, async () => {
const modulePath = require.resolve("../src/evals/methods.js");
delete require.cache[modulePath];
const evalMethods = require("../src/evals/methods.js");
const clientContext = { clientParams: { apiKey: "sk-test" } };
const listedEvals = await evalMethods.listEvals.call(clientContext, {
payload: { order: "desc", limit: 2 },
});
assert.deepEqual(listedEvals, [{ id: "eval_1" }, { id: "eval_2" }]);
const createdEval = await evalMethods.createEval.call(clientContext, {
payload: {
name: "Support quality eval",
data_source_config: { type: "custom", schema: { type: "object" } },
testing_criteria: [],
},
});
assert.deepEqual(createdEval, { id: "eval_new" });
const fetchedEval = await evalMethods.getEval.call(clientContext, {
payload: { eval_id: "eval_1" },
});
assert.deepEqual(fetchedEval, { id: "eval_1" });
const updatedEval = await evalMethods.modifyEval.call(clientContext, {
payload: { eval_id: "eval_1", name: "Updated eval" },
});
assert.deepEqual(updatedEval, { id: "eval_1", updated: true });
const deletedEval = await evalMethods.deleteEval.call(clientContext, {
payload: { eval_id: "eval_1" },
});
assert.deepEqual(deletedEval, { id: "eval_1", deleted: true });
const listedRuns = await evalMethods.listEvalRuns.call(clientContext, {
payload: { eval_id: "eval_1", limit: 2 },
});
assert.deepEqual(listedRuns, [{ id: "run_1" }, { id: "run_2" }]);
const createdRun = await evalMethods.createEvalRun.call(clientContext, {
payload: {
eval_id: "eval_1",
data_source: {
type: "jsonl",
source: { type: "file_id", id: "file_1" },
},
},
});
assert.deepEqual(createdRun, { id: "run_new", eval_id: "eval_1" });
const fetchedRun = await evalMethods.getEvalRun.call(clientContext, {
payload: { eval_id: "eval_1", run_id: "run_1" },
});
assert.deepEqual(fetchedRun, { id: "run_1" });
const cancelledRun = await evalMethods.cancelEvalRun.call(clientContext, {
payload: { eval_id: "eval_1", run_id: "run_1" },
});
assert.deepEqual(cancelledRun, { id: "run_1", status: "cancelled" });
const deletedRun = await evalMethods.deleteEvalRun.call(clientContext, {
payload: { eval_id: "eval_1", run_id: "run_1" },
});
assert.deepEqual(deletedRun, { id: "run_1", deleted: true });
const listedOutputItems = await evalMethods.listEvalRunOutputItems.call(clientContext, {
payload: { eval_id: "eval_1", run_id: "run_1", limit: 1 },
});
assert.deepEqual(lis