@inductiv/node-red-openai-api 1.103.0 → 6.22.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.
Files changed (61) hide show
  1. package/README.md +165 -95
  2. package/examples/responses/mcp.json +1 -1
  3. package/lib.js +7035 -13298
  4. package/locales/en-US/node.json +49 -1
  5. package/node.html +1526 -981
  6. package/node.js +194 -54
  7. package/package.json +8 -7
  8. package/src/assistants/help.html +1 -77
  9. package/src/audio/help.html +1 -37
  10. package/src/batch/help.html +3 -17
  11. package/src/chat/help.html +11 -89
  12. package/src/container-files/help.html +1 -27
  13. package/src/containers/help.html +8 -18
  14. package/src/conversations/help.html +135 -0
  15. package/src/conversations/methods.js +73 -0
  16. package/src/conversations/template.html +10 -0
  17. package/src/embeddings/help.html +1 -11
  18. package/src/evals/help.html +249 -0
  19. package/src/evals/methods.js +114 -0
  20. package/src/evals/template.html +14 -0
  21. package/src/files/help.html +4 -17
  22. package/src/fine-tuning/help.html +1 -35
  23. package/src/images/help.html +1 -45
  24. package/src/lib.js +53 -1
  25. package/src/messages/help.html +19 -39
  26. package/src/messages/methods.js +13 -0
  27. package/src/messages/template.html +7 -18
  28. package/src/models/help.html +1 -5
  29. package/src/moderations/help.html +1 -5
  30. package/src/node.html +126 -37
  31. package/src/realtime/help.html +129 -0
  32. package/src/realtime/methods.js +45 -0
  33. package/src/realtime/template.html +7 -0
  34. package/src/responses/help.html +203 -61
  35. package/src/responses/methods.js +49 -16
  36. package/src/responses/template.html +16 -1
  37. package/src/runs/help.html +1 -123
  38. package/src/skills/help.html +183 -0
  39. package/src/skills/methods.js +99 -0
  40. package/src/skills/template.html +13 -0
  41. package/src/threads/help.html +1 -15
  42. package/src/uploads/help.html +1 -21
  43. package/src/vector-store-file-batches/help.html +1 -27
  44. package/src/vector-store-file-batches/methods.js +5 -5
  45. package/src/vector-store-files/help.html +1 -25
  46. package/src/vector-store-files/methods.js +4 -7
  47. package/src/vector-stores/help.html +2 -31
  48. package/src/vector-stores/methods.js +5 -11
  49. package/src/vector-stores/template.html +7 -22
  50. package/src/videos/help.html +113 -0
  51. package/src/videos/methods.js +50 -0
  52. package/src/videos/template.html +8 -0
  53. package/src/webhooks/help.html +61 -0
  54. package/src/webhooks/methods.js +40 -0
  55. package/src/webhooks/template.html +4 -0
  56. package/test/openai-methods-mapping.test.js +1220 -0
  57. package/test/openai-node-auth-routing.test.js +203 -0
  58. package/test/service-host-editor-template.test.js +53 -0
  59. package/test/service-host-node.test.js +182 -0
  60. package/test/services.test.js +147 -0
  61. package/test/utils.test.js +75 -0
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+
3
+ const assert = require("node:assert/strict");
4
+ const EventEmitter = require("node:events");
5
+ const test = require("node:test");
6
+
7
+ const OpenaiApi = require("../lib.js");
8
+ const nodeModule = require("../node.js");
9
+
10
+ function createEvaluateNodeProperty() {
11
+ return (value, type, node, msg, callback) => {
12
+ let resolvedValue = value;
13
+
14
+ if (type === "env" || type === "msg" || type === "flow" || type === "global") {
15
+ resolvedValue = `resolved:${type}:${value}`;
16
+ }
17
+
18
+ if (typeof callback === "function") {
19
+ callback(null, resolvedValue);
20
+ return undefined;
21
+ }
22
+
23
+ return resolvedValue;
24
+ };
25
+ }
26
+
27
+ function createNodeHarness() {
28
+ const registeredTypes = {};
29
+ const configNodes = new Map();
30
+
31
+ const RED = {
32
+ nodes: {
33
+ createNode: (node, config) => {
34
+ const emitter = new EventEmitter();
35
+ node.on = emitter.on.bind(emitter);
36
+ node.emit = emitter.emit.bind(emitter);
37
+
38
+ node.sentMessages = [];
39
+ node.errorMessages = [];
40
+ node.send = (msg) => {
41
+ node.sentMessages.push(msg);
42
+ };
43
+ node.error = (error, msg) => {
44
+ node.errorMessages.push({ error, msg });
45
+ };
46
+ node.status = () => { };
47
+ node.context = () => ({
48
+ flow: { get: () => undefined },
49
+ global: { get: () => undefined },
50
+ });
51
+
52
+ node.credentials = config.credentials || {};
53
+ node.id = config.id;
54
+ },
55
+ registerType: (name, ctor) => {
56
+ registeredTypes[name] = ctor;
57
+ },
58
+ getNode: (id) => configNodes.get(id),
59
+ },
60
+ util: {
61
+ evaluateNodeProperty: createEvaluateNodeProperty(),
62
+ getMessageProperty: (msg, path) => {
63
+ if (path === "payload") {
64
+ return msg.payload;
65
+ }
66
+
67
+ return path.split(".").reduce((value, key) => {
68
+ if (value === undefined || value === null) {
69
+ return undefined;
70
+ }
71
+ return value[key];
72
+ }, msg);
73
+ },
74
+ },
75
+ };
76
+
77
+ nodeModule(RED);
78
+
79
+ return {
80
+ OpenaiApiNode: registeredTypes["OpenAI API"],
81
+ ServiceHostNode: registeredTypes["Service Host"],
82
+ configNodes,
83
+ };
84
+ }
85
+
86
+ async function runAuthRoutingCase(serviceConfig) {
87
+ const harness = createNodeHarness();
88
+ assert.ok(harness.OpenaiApiNode, "OpenAI API node should register");
89
+ assert.ok(harness.ServiceHostNode, "Service Host node should register");
90
+
91
+ const serviceNode = new harness.ServiceHostNode({
92
+ id: "service-1",
93
+ apiBase: "https://api.example.com/v1",
94
+ apiBaseType: "str",
95
+ secureApiKeyHeaderOrQueryName: "Authorization",
96
+ secureApiKeyHeaderOrQueryNameType: "str",
97
+ organizationId: "OPENAI_ORG_ID",
98
+ organizationIdType: "env",
99
+ secureApiKeyIsQuery: false,
100
+ secureApiKeyValueType: "cred",
101
+ credentials: {
102
+ secureApiKeyValue: "sk-test",
103
+ },
104
+ ...serviceConfig,
105
+ });
106
+ harness.configNodes.set("service-1", serviceNode);
107
+
108
+ const apiNode = new harness.OpenaiApiNode({
109
+ id: "openai-1",
110
+ service: "service-1",
111
+ method: "createModelResponse",
112
+ property: "payload",
113
+ propertyType: "msg",
114
+ });
115
+
116
+ apiNode.emit("input", { payload: { model: "gpt-5-nano" } });
117
+
118
+ // Allow Promise-based input handler to resolve and send.
119
+ await new Promise((resolve) => setImmediate(resolve));
120
+ await new Promise((resolve) => setImmediate(resolve));
121
+
122
+ assert.equal(apiNode.errorMessages.length, 0);
123
+ assert.equal(apiNode.sentMessages.length, 1);
124
+ }
125
+
126
+ test("applies custom auth header to OpenAI client params", async () => {
127
+ const originalCreateModelResponse = OpenaiApi.prototype.createModelResponse;
128
+ const capturedClientParams = [];
129
+
130
+ OpenaiApi.prototype.createModelResponse = async function () {
131
+ capturedClientParams.push({ ...this.clientParams });
132
+ return { ok: true };
133
+ };
134
+
135
+ try {
136
+ await runAuthRoutingCase({
137
+ secureApiKeyHeaderOrQueryName: "X-API-Key",
138
+ secureApiKeyHeaderOrQueryNameType: "str",
139
+ secureApiKeyIsQuery: false,
140
+ });
141
+ } finally {
142
+ OpenaiApi.prototype.createModelResponse = originalCreateModelResponse;
143
+ }
144
+
145
+ assert.equal(capturedClientParams.length, 1);
146
+ assert.deepEqual(capturedClientParams[0].defaultHeaders, {
147
+ Authorization: null,
148
+ "X-API-Key": "sk-test",
149
+ });
150
+ assert.equal(capturedClientParams[0].defaultQuery, undefined);
151
+ });
152
+
153
+ test("applies query-string auth mode with typed header name", async () => {
154
+ const originalCreateModelResponse = OpenaiApi.prototype.createModelResponse;
155
+ const capturedClientParams = [];
156
+
157
+ OpenaiApi.prototype.createModelResponse = async function () {
158
+ capturedClientParams.push({ ...this.clientParams });
159
+ return { ok: true };
160
+ };
161
+
162
+ try {
163
+ await runAuthRoutingCase({
164
+ secureApiKeyHeaderOrQueryName: "OPENAI_AUTH_QUERY_PARAM",
165
+ secureApiKeyHeaderOrQueryNameType: "env",
166
+ secureApiKeyIsQuery: "true",
167
+ });
168
+ } finally {
169
+ OpenaiApi.prototype.createModelResponse = originalCreateModelResponse;
170
+ }
171
+
172
+ assert.equal(capturedClientParams.length, 1);
173
+ assert.deepEqual(capturedClientParams[0].defaultHeaders, {
174
+ Authorization: null,
175
+ });
176
+ assert.deepEqual(capturedClientParams[0].defaultQuery, {
177
+ "resolved:env:OPENAI_AUTH_QUERY_PARAM": "sk-test",
178
+ });
179
+ });
180
+
181
+ test("keeps OpenAI SDK default Authorization behavior when header stays Authorization", async () => {
182
+ const originalCreateModelResponse = OpenaiApi.prototype.createModelResponse;
183
+ const capturedClientParams = [];
184
+
185
+ OpenaiApi.prototype.createModelResponse = async function () {
186
+ capturedClientParams.push({ ...this.clientParams });
187
+ return { ok: true };
188
+ };
189
+
190
+ try {
191
+ await runAuthRoutingCase({
192
+ secureApiKeyHeaderOrQueryName: "Authorization",
193
+ secureApiKeyHeaderOrQueryNameType: "str",
194
+ secureApiKeyIsQuery: false,
195
+ });
196
+ } finally {
197
+ OpenaiApi.prototype.createModelResponse = originalCreateModelResponse;
198
+ }
199
+
200
+ assert.equal(capturedClientParams.length, 1);
201
+ assert.equal(capturedClientParams[0].defaultHeaders, undefined);
202
+ assert.equal(capturedClientParams[0].defaultQuery, undefined);
203
+ });
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+
3
+ const assert = require("node:assert/strict");
4
+ const fs = require("node:fs");
5
+ const path = require("node:path");
6
+ const test = require("node:test");
7
+
8
+ const templatePath = path.join(__dirname, "..", "src", "node.html");
9
+ const template = fs.readFileSync(templatePath, "utf8");
10
+
11
+ test("service host API key editor uses native typedInput cred handling", () => {
12
+ assert.match(
13
+ template,
14
+ /types: \["cred", "env", "msg", "flow", "global"\],/
15
+ );
16
+ assert.match(
17
+ template,
18
+ /<input type="text" id="node-config-input-secureApiKeyValue" placeholder="" \/>/
19
+ );
20
+ assert.match(
21
+ template,
22
+ /<input\s+type="text"\s+id="node-config-input-secureApiKeyHeaderOrQueryName"\s+placeholder="Authorization"\s+\/>/
23
+ );
24
+ assert.match(
25
+ template,
26
+ /if \(inputValue && inputValue !== "__PWRD__"\) \{/
27
+ );
28
+ assert.match(
29
+ template,
30
+ /const syncApiKeyInputValue = function \(\) \{/
31
+ );
32
+ assert.match(
33
+ template,
34
+ /if \(selectedType !== "cred" && currentValue === "__PWRD__"\) \{/
35
+ );
36
+ assert.match(
37
+ template,
38
+ /apiKeyInput\.typedInput\("value", apiKeyRef\.val\(\) \|\| ""\);/
39
+ );
40
+ assert.match(
41
+ template,
42
+ /if \(selectedType === "cred" && previousApiKeyType !== "cred"\) \{/
43
+ );
44
+ assert.match(
45
+ template,
46
+ /const inputValue = apiKeyInput\.typedInput\("value"\);/
47
+ );
48
+ assert.match(
49
+ template,
50
+ /const existingCredValue = apiKeyInput\.val\(\);/
51
+ );
52
+ assert.ok(!/apiKeyInput\.prop\("type"/.test(template));
53
+ });
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+
3
+ const assert = require("node:assert/strict");
4
+ const test = require("node:test");
5
+
6
+ const nodeModule = require("../node.js");
7
+
8
+ function createServiceHostNode(evaluateNodeProperty) {
9
+ let ServiceHostNode;
10
+
11
+ const RED = {
12
+ nodes: {
13
+ createNode: (node, config) => {
14
+ node.credentials = config.credentials || {};
15
+ },
16
+ registerType: (name, ctor) => {
17
+ if (name === "Service Host") {
18
+ ServiceHostNode = ctor;
19
+ }
20
+ },
21
+ getNode: () => undefined,
22
+ },
23
+ util: {
24
+ evaluateNodeProperty,
25
+ },
26
+ };
27
+
28
+ nodeModule(RED);
29
+ assert.ok(ServiceHostNode, "Service Host constructor should register");
30
+ return ServiceHostNode;
31
+ }
32
+
33
+ function createEvaluateNodeProperty(calls, options = {}) {
34
+ return (value, type, node, msg, callback) => {
35
+ calls.push({ value, type, node, msg, hasCallback: typeof callback === "function" });
36
+
37
+ if (type === "flow" || type === "global") {
38
+ const resolvedValue = `resolved:${type}:${value}`;
39
+ if (typeof callback === "function") {
40
+ const deliver = options.asyncFlowGlobal ? setImmediate : (fn) => fn();
41
+ deliver(() => callback(null, resolvedValue));
42
+ return undefined;
43
+ }
44
+ return resolvedValue;
45
+ }
46
+
47
+ if (type === "str" || type === "cred") {
48
+ if (typeof callback === "function") {
49
+ callback(null, value);
50
+ return undefined;
51
+ }
52
+ return value;
53
+ }
54
+
55
+ const resolvedValue = `resolved:${type}:${value}`;
56
+ if (typeof callback === "function") {
57
+ callback(null, resolvedValue);
58
+ return undefined;
59
+ }
60
+ return resolvedValue;
61
+ };
62
+ }
63
+
64
+ test("uses credential value when API key type is cred", () => {
65
+ const calls = [];
66
+ const ServiceHostNode = createServiceHostNode(createEvaluateNodeProperty(calls));
67
+ const node = new ServiceHostNode({
68
+ secureApiKeyValueType: "cred",
69
+ secureApiKeyValueRef: "",
70
+ credentials: { secureApiKeyValue: "sk-test" },
71
+ });
72
+
73
+ const result = node.evaluateTyped("secureApiKeyValue", {}, node);
74
+
75
+ assert.equal(result, "sk-test");
76
+ assert.equal(calls.length, 1);
77
+ assert.equal(calls[0].value, "sk-test");
78
+ assert.equal(calls[0].type, "cred");
79
+ });
80
+
81
+ test("uses explicit reference value when API key type is env", () => {
82
+ const calls = [];
83
+ const ServiceHostNode = createServiceHostNode(createEvaluateNodeProperty(calls));
84
+ const node = new ServiceHostNode({
85
+ secureApiKeyValueType: "env",
86
+ secureApiKeyValueRef: "OPENAI_API_KEY",
87
+ credentials: { secureApiKeyValue: "sk-ignored" },
88
+ });
89
+
90
+ const result = node.evaluateTyped("secureApiKeyValue", {}, node);
91
+
92
+ assert.equal(result, "resolved:env:OPENAI_API_KEY");
93
+ assert.equal(calls.length, 1);
94
+ assert.equal(calls[0].value, "OPENAI_API_KEY");
95
+ assert.equal(calls[0].type, "env");
96
+ });
97
+
98
+ test("supports legacy non-cred nodes that stored env reference in credentials", () => {
99
+ const calls = [];
100
+ const ServiceHostNode = createServiceHostNode(createEvaluateNodeProperty(calls));
101
+ const node = new ServiceHostNode({
102
+ secureApiKeyValueType: "env",
103
+ credentials: { secureApiKeyValue: "OPENAI_API_KEY" },
104
+ });
105
+
106
+ const result = node.evaluateTyped("secureApiKeyValue", {}, node);
107
+
108
+ assert.equal(result, "resolved:env:OPENAI_API_KEY");
109
+ assert.equal(calls.length, 1);
110
+ assert.equal(calls[0].value, "OPENAI_API_KEY");
111
+ assert.equal(calls[0].type, "env");
112
+ });
113
+
114
+ test("falls back to credential value when non-cred reference is empty", () => {
115
+ const calls = [];
116
+ const ServiceHostNode = createServiceHostNode(createEvaluateNodeProperty(calls));
117
+ const node = new ServiceHostNode({
118
+ secureApiKeyValueType: "env",
119
+ secureApiKeyValueRef: "",
120
+ credentials: { secureApiKeyValue: "OPENAI_API_KEY" },
121
+ });
122
+
123
+ const result = node.evaluateTyped("secureApiKeyValue", {}, node);
124
+
125
+ assert.equal(result, "resolved:env:OPENAI_API_KEY");
126
+ assert.equal(calls.length, 1);
127
+ assert.equal(calls[0].value, "OPENAI_API_KEY");
128
+ assert.equal(calls[0].type, "env");
129
+ });
130
+
131
+ test("supports legacy typed value stored only in credentials type field", () => {
132
+ const calls = [];
133
+ const ServiceHostNode = createServiceHostNode(createEvaluateNodeProperty(calls));
134
+ const node = new ServiceHostNode({
135
+ credentials: {
136
+ secureApiKeyValue: "OPENAI_API_KEY",
137
+ secureApiKeyValueType: "env",
138
+ },
139
+ });
140
+
141
+ const result = node.evaluateTyped("secureApiKeyValue", {}, node);
142
+
143
+ assert.equal(result, "resolved:env:OPENAI_API_KEY");
144
+ assert.equal(calls.length, 1);
145
+ assert.equal(calls[0].value, "OPENAI_API_KEY");
146
+ assert.equal(calls[0].type, "env");
147
+ });
148
+
149
+ test("supports legacy string API key values without reference field", () => {
150
+ const calls = [];
151
+ const ServiceHostNode = createServiceHostNode(createEvaluateNodeProperty(calls));
152
+ const node = new ServiceHostNode({
153
+ secureApiKeyValueType: "str",
154
+ credentials: { secureApiKeyValue: "sk-legacy" },
155
+ });
156
+
157
+ const result = node.evaluateTyped("secureApiKeyValue", {}, node);
158
+
159
+ assert.equal(result, "sk-legacy");
160
+ assert.equal(calls.length, 1);
161
+ assert.equal(calls[0].value, "sk-legacy");
162
+ assert.equal(calls[0].type, "str");
163
+ });
164
+
165
+ test("evaluateTypedAsync resolves flow/global types using callback path", async () => {
166
+ const calls = [];
167
+ const ServiceHostNode = createServiceHostNode(
168
+ createEvaluateNodeProperty(calls, { asyncFlowGlobal: true })
169
+ );
170
+ const node = new ServiceHostNode({
171
+ secureApiKeyValueType: "flow",
172
+ secureApiKeyValueRef: "openai.key",
173
+ });
174
+
175
+ const result = await node.evaluateTypedAsync("secureApiKeyValue", {}, node);
176
+
177
+ assert.equal(result, "resolved:flow:openai.key");
178
+ assert.equal(calls.length, 1);
179
+ assert.equal(calls[0].value, "openai.key");
180
+ assert.equal(calls[0].type, "flow");
181
+ assert.equal(calls[0].hasCallback, true);
182
+ });
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+
3
+ const assert = require("assert");
4
+ const { createClient } = require("../src/client");
5
+ const { createApi } = require("../src/index");
6
+
7
+ // Mock the OpenAI API client
8
+ jest.mock("../src/client", () => {
9
+ return {
10
+ createClient: jest.fn().mockReturnValue({
11
+ chat: {
12
+ completions: {
13
+ create: jest.fn().mockResolvedValue({ id: "chat-123", choices: [] }),
14
+ },
15
+ },
16
+ beta: {
17
+ threads: {
18
+ create: jest.fn().mockResolvedValue({ id: "thread-123" }),
19
+ messages: {
20
+ create: jest.fn().mockResolvedValue({ id: "message-123" }),
21
+ list: jest
22
+ .fn()
23
+ .mockResolvedValue({ data: [{ id: "message-123" }] }),
24
+ retrieve: jest.fn().mockResolvedValue({ id: "message-123" }),
25
+ update: jest
26
+ .fn()
27
+ .mockResolvedValue({ id: "message-123", updated: true }),
28
+ },
29
+ },
30
+ },
31
+ batches: {
32
+ create: jest.fn().mockResolvedValue({ id: "batch-123" }),
33
+ retrieve: jest
34
+ .fn()
35
+ .mockResolvedValue({ id: "batch-123", status: "pending" }),
36
+ list: jest.fn().mockResolvedValue({ data: [{ id: "batch-123" }] }),
37
+ cancel: jest
38
+ .fn()
39
+ .mockResolvedValue({ id: "batch-123", cancelled: true }),
40
+ },
41
+ }),
42
+ };
43
+ });
44
+
45
+ describe("API Services", () => {
46
+ beforeEach(() => {
47
+ jest.clearAllMocks();
48
+ });
49
+
50
+ describe("ChatService", () => {
51
+ it("should create chat completion", async () => {
52
+ const api = createApi({ apiKey: "test-key" });
53
+ const result = await api.chat.createChatCompletion({
54
+ payload: { messages: [{ role: "user", content: "Hello" }] },
55
+ });
56
+
57
+ assert.strictEqual(result.id, "chat-123");
58
+ expect(createClient).toHaveBeenCalledWith({ apiKey: "test-key" });
59
+ });
60
+ });
61
+
62
+ describe("MessagesService", () => {
63
+ it("should create a message", async () => {
64
+ const api = createApi({ apiKey: "test-key" });
65
+ const result = await api.messages.createMessage({
66
+ payload: { thread_id: "thread-123", content: "Hello" },
67
+ });
68
+
69
+ assert.strictEqual(result.id, "message-123");
70
+ });
71
+
72
+ it("should list messages", async () => {
73
+ const api = createApi({ apiKey: "test-key" });
74
+ const result = await api.messages.listMessages({
75
+ payload: { thread_id: "thread-123" },
76
+ });
77
+
78
+ assert.strictEqual(result.length, 1);
79
+ assert.strictEqual(result[0].id, "message-123");
80
+ });
81
+
82
+ it("should get a message", async () => {
83
+ const api = createApi({ apiKey: "test-key" });
84
+ const result = await api.messages.getMessage({
85
+ payload: { thread_id: "thread-123", message_id: "message-123" },
86
+ });
87
+
88
+ assert.strictEqual(result.id, "message-123");
89
+ });
90
+
91
+ it("should modify a message", async () => {
92
+ const api = createApi({ apiKey: "test-key" });
93
+ const result = await api.messages.modifyMessage({
94
+ payload: {
95
+ thread_id: "thread-123",
96
+ message_id: "message-123",
97
+ metadata: { updated: true },
98
+ },
99
+ });
100
+
101
+ assert.strictEqual(result.updated, true);
102
+ });
103
+ });
104
+
105
+ describe("BatchesService", () => {
106
+ it("should create a batch", async () => {
107
+ const api = createApi({ apiKey: "test-key" });
108
+ const result = await api.batches.createBatch({
109
+ payload: {
110
+ requests: [
111
+ { model: "gpt-4", messages: [{ role: "user", content: "Hello" }] },
112
+ ],
113
+ },
114
+ });
115
+
116
+ assert.strictEqual(result.id, "batch-123");
117
+ });
118
+
119
+ it("should retrieve a batch", async () => {
120
+ const api = createApi({ apiKey: "test-key" });
121
+ const result = await api.batches.retrieveBatch({
122
+ payload: { batch_id: "batch-123" },
123
+ });
124
+
125
+ assert.strictEqual(result.status, "pending");
126
+ });
127
+
128
+ it("should list batches", async () => {
129
+ const api = createApi({ apiKey: "test-key" });
130
+ const result = await api.batches.listBatches({
131
+ payload: {},
132
+ });
133
+
134
+ assert.strictEqual(result.length, 1);
135
+ assert.strictEqual(result[0].id, "batch-123");
136
+ });
137
+
138
+ it("should cancel a batch", async () => {
139
+ const api = createApi({ apiKey: "test-key" });
140
+ const result = await api.batches.cancelBatch({
141
+ payload: { batch_id: "batch-123" },
142
+ });
143
+
144
+ assert.strictEqual(result.cancelled, true);
145
+ });
146
+ });
147
+ });
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+
3
+ const assert = require("assert");
4
+ const { validation, streaming, fileHelpers } = require("../src/index").utils;
5
+
6
+ describe("Utility Functions", () => {
7
+ describe("Validation", () => {
8
+ it("should validate required parameters", () => {
9
+ const schema = {
10
+ required: ["name"],
11
+ properties: {
12
+ name: { type: "string" },
13
+ age: { type: "number" },
14
+ },
15
+ };
16
+
17
+ // Valid case
18
+ expect(() =>
19
+ validation.validateParams({ name: "Test" }, schema)
20
+ ).not.toThrow();
21
+
22
+ // Invalid case - missing required field
23
+ expect(() => validation.validateParams({}, schema)).toThrow();
24
+
25
+ // Invalid case - wrong type
26
+ expect(() => validation.validateParams({ name: 123 }, schema)).toThrow();
27
+ });
28
+ });
29
+
30
+ describe("Streaming", () => {
31
+ it("should create a streaming handler", () => {
32
+ const onData = jest.fn();
33
+ const onError = jest.fn();
34
+ const onFinish = jest.fn();
35
+
36
+ const handler = streaming.createStreamHandler({
37
+ onData,
38
+ onError,
39
+ onFinish,
40
+ });
41
+
42
+ expect(handler).toHaveProperty("controller");
43
+ expect(handler).toHaveProperty("stream");
44
+
45
+ // Mock data event
46
+ const mockChunk = { choices: [{ delta: { content: "Hello" } }] };
47
+ handler.onChunk(mockChunk);
48
+ expect(onData).toHaveBeenCalledWith(mockChunk);
49
+
50
+ // Mock done event
51
+ handler.onDone();
52
+ expect(onFinish).toHaveBeenCalled();
53
+ });
54
+ });
55
+
56
+ describe("File Helpers", () => {
57
+ it("should determine file type from path", () => {
58
+ expect(fileHelpers.getFileTypeFromPath("image.png")).toBe("image");
59
+ expect(fileHelpers.getFileTypeFromPath("audio.mp3")).toBe("audio");
60
+ expect(fileHelpers.getFileTypeFromPath("document.pdf")).toBe("document");
61
+ });
62
+
63
+ it("should validate file size", () => {
64
+ const mockFile = { size: 1024 * 1024 }; // 1MB
65
+
66
+ // File under limit
67
+ expect(fileHelpers.validateFileSize(mockFile, 2 * 1024 * 1024)).toBe(
68
+ true
69
+ );
70
+
71
+ // File exceeds limit
72
+ expect(fileHelpers.validateFileSize(mockFile, 512 * 1024)).toBe(false);
73
+ });
74
+ });
75
+ });