Skip to content

Commit e8d7a5d

Browse files
committed
feat(tool): add OpenAI Responses API integration
Add toOpenAIResponses() method to BaseTool and Tools classes for converting tools to OpenAI's new Responses API format. - Add FunctionTool type import from openai/resources/responses/responses - Implement toOpenAIResponses() on BaseTool with strict mode (default: true) - Implement toOpenAIResponses() on Tools class for batch conversion - Add unit tests for both single tool and collection conversions - Add example file demonstrating Responses API usage The Responses API uses a flat structure ({ type, name, description, strict, parameters }) unlike Chat Completions which uses nested structure ({ type, function: { name, description, parameters } }).
1 parent 379d767 commit e8d7a5d

File tree

3 files changed

+179
-3
lines changed

3 files changed

+179
-3
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* This example shows how to use StackOne tools with OpenAI's Responses API.
3+
*/
4+
5+
import assert from 'node:assert';
6+
import process from 'node:process';
7+
import { StackOneToolSet } from '@stackone/ai';
8+
import OpenAI from 'openai';
9+
10+
const apiKey = process.env.STACKONE_API_KEY;
11+
if (!apiKey) {
12+
console.error('STACKONE_API_KEY environment variable is required');
13+
process.exit(1);
14+
}
15+
16+
// Replace with your actual account ID from StackOne dashboard
17+
const accountId = 'your-hris-account-id';
18+
19+
const openaiResponsesIntegration = async (): Promise<void> => {
20+
// Initialise StackOne
21+
const toolset = new StackOneToolSet({ accountId });
22+
23+
// Fetch HRIS tools via MCP
24+
const tools = await toolset.fetchTools({
25+
actions: ['hris_get_*'],
26+
});
27+
const openAIResponsesTools = tools.toOpenAIResponses();
28+
29+
// Initialise OpenAI client
30+
const openai = new OpenAI();
31+
32+
// Create a response with tool calls using the Responses API
33+
const response = await openai.responses.create({
34+
model: 'gpt-5.1',
35+
instructions: 'You are a helpful assistant that can access HRIS information.',
36+
input: 'What is the employee with id: c28xIQaWQ6MzM5MzczMDA2NzMzMzkwNzIwNA phone number?',
37+
tools: openAIResponsesTools,
38+
});
39+
40+
// Verify the response contains expected data
41+
assert(response.id, 'Expected response to have an ID');
42+
assert(response.model, 'Expected response to have a model');
43+
44+
// Check if the model made any tool calls
45+
const toolCalls = response.output.filter(
46+
(item): item is OpenAI.Responses.ResponseFunctionToolCall => item.type === 'function_call',
47+
);
48+
49+
assert(toolCalls.length > 0, 'Expected at least one tool call');
50+
51+
const toolCall = toolCalls[0];
52+
assert(toolCall.name === 'hris_get_employee', 'Expected tool call to be hris_get_employee');
53+
54+
// Parse the arguments to verify they contain the expected fields
55+
const args = JSON.parse(toolCall.arguments);
56+
assert(args.id === 'c28xIQaWQ6MzM5MzczMDA2NzMzMzkwNzIwNA', 'Expected id to match the query');
57+
};
58+
59+
// Run the example
60+
await openaiResponsesIntegration();

src/tool.test.ts

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe('StackOneTool', () => {
5555
});
5656
});
5757

58-
it('should convert to OpenAI tool format', () => {
58+
it('should convert to OpenAI Chat Completions API tool format', () => {
5959
const tool = createMockTool();
6060
const openAIFormat = tool.toOpenAI();
6161

@@ -72,6 +72,41 @@ describe('StackOneTool', () => {
7272
).toBe('string');
7373
});
7474

75+
it('should convert to OpenAI Responses API tool format', () => {
76+
const tool = createMockTool();
77+
const responsesFormat = tool.toOpenAIResponses();
78+
79+
expect(responsesFormat.type).toBe('function');
80+
expect(responsesFormat.name).toBe('test_tool');
81+
expect(responsesFormat.description).toBe('Test tool');
82+
expect(responsesFormat.strict).toBe(true);
83+
expect(responsesFormat.parameters?.type).toBe('object');
84+
expect(
85+
(
86+
responsesFormat.parameters as {
87+
properties: { id: { type: string } };
88+
additionalProperties: boolean;
89+
}
90+
).properties.id.type,
91+
).toBe('string');
92+
expect(
93+
(responsesFormat.parameters as { additionalProperties: boolean }).additionalProperties,
94+
).toBe(false);
95+
});
96+
97+
it('should convert to OpenAI Responses API tool format with strict disabled', () => {
98+
const tool = createMockTool();
99+
const responsesFormat = tool.toOpenAIResponses({ strict: false });
100+
101+
expect(responsesFormat.type).toBe('function');
102+
expect(responsesFormat.name).toBe('test_tool');
103+
expect(responsesFormat.strict).toBe(false);
104+
expect(responsesFormat.parameters).toBeDefined();
105+
expect(
106+
(responsesFormat.parameters as { additionalProperties?: boolean }).additionalProperties,
107+
).toBeUndefined();
108+
});
109+
75110
it('should convert to AI SDK tool format', async () => {
76111
const tool = createMockTool();
77112

@@ -317,6 +352,58 @@ describe('Tools', () => {
317352
expect(openAITools[1].function.name).toBe('tool2');
318353
});
319354

355+
it('should convert all tools to OpenAI Responses API tools', () => {
356+
const tool1 = new StackOneTool(
357+
'tool1',
358+
'Tool 1',
359+
{
360+
type: 'object',
361+
properties: { id: { type: 'string' } },
362+
},
363+
{
364+
kind: 'http',
365+
method: 'GET',
366+
url: 'https://api.example.com/test/{id}',
367+
bodyType: 'json',
368+
params: [],
369+
},
370+
);
371+
const tool2 = new StackOneTool(
372+
'tool2',
373+
'Tool 2',
374+
{
375+
type: 'object',
376+
properties: { name: { type: 'string' } },
377+
},
378+
{
379+
kind: 'http',
380+
method: 'POST',
381+
url: 'https://api.example.com/test',
382+
bodyType: 'json',
383+
params: [],
384+
},
385+
);
386+
387+
const tools = new Tools([tool1, tool2]);
388+
const responsesTools = tools.toOpenAIResponses();
389+
390+
expect(responsesTools).toBeInstanceOf(Array);
391+
expect(responsesTools.length).toBe(2);
392+
expect(responsesTools[0].type).toBe('function');
393+
expect(responsesTools[0].name).toBe('tool1');
394+
expect(responsesTools[0].strict).toBe(true);
395+
expect(responsesTools[1].name).toBe('tool2');
396+
expect(responsesTools[1].strict).toBe(true);
397+
});
398+
399+
it('should convert all tools to OpenAI Responses API tools with strict disabled', () => {
400+
const tool1 = createMockTool();
401+
const tools = new Tools([tool1]);
402+
const responsesTools = tools.toOpenAIResponses({ strict: false });
403+
404+
expect(responsesTools[0].strict).toBe(false);
405+
});
406+
320407
it('should convert all tools to AI SDK tools', async () => {
321408
const tool1 = createMockTool();
322409
const tool2 = new StackOneTool(

src/tool.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as orama from '@orama/orama';
22
import type { ChatCompletionFunctionTool } from 'openai/resources/chat/completions';
3+
import type { FunctionTool as OpenAIResponsesFunctionTool } from 'openai/resources/responses/responses';
34
import { DEFAULT_HYBRID_ALPHA } from './consts';
45
import { RequestBuilder } from './requestBuilder';
56
import type {
@@ -164,7 +165,7 @@ export class BaseTool {
164165
}
165166

166167
/**
167-
* Convert the tool to OpenAI format
168+
* Convert the tool to OpenAI Chat Completions API format
168169
*/
169170
toOpenAI(): ChatCompletionFunctionTool {
170171
return {
@@ -181,6 +182,26 @@ export class BaseTool {
181182
};
182183
}
183184

185+
/**
186+
* Convert the tool to OpenAI Responses API format
187+
* @see https://platform.openai.com/docs/api-reference/responses
188+
*/
189+
toOpenAIResponses(options: { strict?: boolean } = {}): OpenAIResponsesFunctionTool {
190+
const { strict = true } = options;
191+
return {
192+
type: 'function',
193+
name: this.name,
194+
description: this.description,
195+
strict,
196+
parameters: {
197+
type: 'object',
198+
properties: this.parameters.properties,
199+
required: this.parameters.required,
200+
...(strict ? { additionalProperties: false } : {}),
201+
},
202+
};
203+
}
204+
184205
/**
185206
* Convert the tool to AI SDK format
186207
*/
@@ -352,12 +373,20 @@ export class Tools implements Iterable<BaseTool> {
352373
}
353374

354375
/**
355-
* Convert all tools to OpenAI format
376+
* Convert all tools to OpenAI Chat Completions API format
356377
*/
357378
toOpenAI(): ChatCompletionFunctionTool[] {
358379
return this.tools.map((tool) => tool.toOpenAI());
359380
}
360381

382+
/**
383+
* Convert all tools to OpenAI Responses API format
384+
* @see https://platform.openai.com/docs/api-reference/responses
385+
*/
386+
toOpenAIResponses(options: { strict?: boolean } = {}): OpenAIResponsesFunctionTool[] {
387+
return this.tools.map((tool) => tool.toOpenAIResponses(options));
388+
}
389+
361390
/**
362391
* Convert all tools to AI SDK format
363392
*/

0 commit comments

Comments
 (0)