Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions examples/openai-responses-integration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* This example shows how to use StackOne tools with OpenAI's Responses API.
*/

import assert from 'node:assert';
import process from 'node:process';
import { StackOneToolSet } from '@stackone/ai';
import OpenAI from 'openai';

const apiKey = process.env.STACKONE_API_KEY;
if (!apiKey) {
console.error('STACKONE_API_KEY environment variable is required');
process.exit(1);
}

// Replace with your actual account ID from StackOne dashboard
const accountId = 'your-stackone-account-id';

const openaiResponsesIntegration = async (): Promise<void> => {
// Initialise StackOne
const toolset = new StackOneToolSet({ accountId });

// Fetch HRIS tools via MCP
const tools = await toolset.fetchTools({
actions: ['_list_*'],
});
const openAIResponsesTools = tools.toOpenAIResponses();

// Initialise OpenAI client
const openai = new OpenAI();

// Create a response with tool calls using the Responses API
const response = await openai.responses.create({
model: 'gpt-5.1',
instructions: 'You are a helpful assistant that can access various tools.',
input: 'What is the employee with id: c28xIQaWQ6MzM5MzczMDA2NzMzMzkwNzIwNA phone number?',
tools: openAIResponsesTools,
});

// Verify the response contains expected data
assert(response.id, 'Expected response to have an ID');
assert(response.model, 'Expected response to have a model');

// Check if the model made any tool calls
const toolCalls = response.output.filter(
(item): item is OpenAI.Responses.ResponseFunctionToolCall => item.type === 'function_call',
);

assert(toolCalls.length > 0, 'Expected at least one tool call');

const toolCall = toolCalls[0];
assert(toolCall.name === 'hris_get_employee', 'Expected tool call to be hris_get_employee');

// Parse the arguments to verify they contain the expected fields
const args = JSON.parse(toolCall.arguments);
assert(args.id === 'c28xIQaWQ6MzM5MzczMDA2NzMzMzkwNzIwNA', 'Expected id to match the query');
};

// Run the example
await openaiResponsesIntegration();
89 changes: 88 additions & 1 deletion src/tool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('StackOneTool', () => {
});
});

it('should convert to OpenAI tool format', () => {
it('should convert to OpenAI Chat Completions API tool format', () => {
const tool = createMockTool();
const openAIFormat = tool.toOpenAI();

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

it('should convert to OpenAI Responses API tool format', () => {
const tool = createMockTool();
const responsesFormat = tool.toOpenAIResponses();

expect(responsesFormat.type).toBe('function');
expect(responsesFormat.name).toBe('test_tool');
expect(responsesFormat.description).toBe('Test tool');
expect(responsesFormat.strict).toBe(true);
expect(responsesFormat.parameters?.type).toBe('object');
expect(
(
responsesFormat.parameters as {
properties: { id: { type: string } };
additionalProperties: boolean;
}
).properties.id.type,
).toBe('string');
expect(
(responsesFormat.parameters as { additionalProperties: boolean }).additionalProperties,
).toBe(false);
});

it('should convert to OpenAI Responses API tool format with strict disabled', () => {
const tool = createMockTool();
const responsesFormat = tool.toOpenAIResponses({ strict: false });

expect(responsesFormat.type).toBe('function');
expect(responsesFormat.name).toBe('test_tool');
expect(responsesFormat.strict).toBe(false);
expect(responsesFormat.parameters).toBeDefined();
expect(
(responsesFormat.parameters as { additionalProperties?: boolean }).additionalProperties,
).toBeUndefined();
});

it('should convert to AI SDK tool format', async () => {
const tool = createMockTool();

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

it('should convert all tools to OpenAI Responses API tools', () => {
const tool1 = new StackOneTool(
'tool1',
'Tool 1',
{
type: 'object',
properties: { id: { type: 'string' } },
},
{
kind: 'http',
method: 'GET',
url: 'https://api.example.com/test/{id}',
bodyType: 'json',
params: [],
},
);
const tool2 = new StackOneTool(
'tool2',
'Tool 2',
{
type: 'object',
properties: { name: { type: 'string' } },
},
{
kind: 'http',
method: 'POST',
url: 'https://api.example.com/test',
bodyType: 'json',
params: [],
},
);

const tools = new Tools([tool1, tool2]);
const responsesTools = tools.toOpenAIResponses();

expect(responsesTools).toBeInstanceOf(Array);
expect(responsesTools.length).toBe(2);
expect(responsesTools[0].type).toBe('function');
expect(responsesTools[0].name).toBe('tool1');
expect(responsesTools[0].strict).toBe(true);
expect(responsesTools[1].name).toBe('tool2');
expect(responsesTools[1].strict).toBe(true);
});

it('should convert all tools to OpenAI Responses API tools with strict disabled', () => {
const tool1 = createMockTool();
const tools = new Tools([tool1]);
const responsesTools = tools.toOpenAIResponses({ strict: false });

expect(responsesTools[0].strict).toBe(false);
});

it('should convert all tools to AI SDK tools', async () => {
const tool1 = createMockTool();
const tool2 = new StackOneTool(
Expand Down
33 changes: 31 additions & 2 deletions src/tool.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as orama from '@orama/orama';
import type { ChatCompletionFunctionTool } from 'openai/resources/chat/completions';
import type { FunctionTool as OpenAIResponsesFunctionTool } from 'openai/resources/responses/responses';
import { DEFAULT_HYBRID_ALPHA } from './consts';
import { RequestBuilder } from './requestBuilder';
import type {
Expand Down Expand Up @@ -164,7 +165,7 @@ export class BaseTool {
}

/**
* Convert the tool to OpenAI format
* Convert the tool to OpenAI Chat Completions API format
*/
toOpenAI(): ChatCompletionFunctionTool {
return {
Expand All @@ -181,6 +182,26 @@ export class BaseTool {
};
}

/**
* Convert the tool to OpenAI Responses API format
* @see https://platform.openai.com/docs/api-reference/responses
*/
toOpenAIResponses(options: { strict?: boolean } = {}): OpenAIResponsesFunctionTool {
const { strict = true } = options;
return {
type: 'function',
name: this.name,
description: this.description,
strict,
parameters: {
type: 'object',
properties: this.parameters.properties,
required: this.parameters.required,
...(strict ? { additionalProperties: false } : {}),
},
};
}

/**
* Convert the tool to AI SDK format
*/
Expand Down Expand Up @@ -352,12 +373,20 @@ export class Tools implements Iterable<BaseTool> {
}

/**
* Convert all tools to OpenAI format
* Convert all tools to OpenAI Chat Completions API format
*/
toOpenAI(): ChatCompletionFunctionTool[] {
return this.tools.map((tool) => tool.toOpenAI());
}

/**
* Convert all tools to OpenAI Responses API format
* @see https://platform.openai.com/docs/api-reference/responses
*/
toOpenAIResponses(options: { strict?: boolean } = {}): OpenAIResponsesFunctionTool[] {
return this.tools.map((tool) => tool.toOpenAIResponses(options));
}

/**
* Convert all tools to AI SDK format
*/
Expand Down
Loading