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
63 changes: 63 additions & 0 deletions examples/anthropic-integration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* This example shows how to use StackOne tools with Anthropic Claude.
*/

import assert from 'node:assert';
import process from 'node:process';
import Anthropic from '@anthropic-ai/sdk';
import { StackOneToolSet } from '@stackone/ai';

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-hris-account-id';

const anthropicIntegration = async (): Promise<void> => {
// Initialise StackOne
const toolset = new StackOneToolSet({
accountId,
baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com',
});

// Filter for any relevant tools
const tools = await toolset.fetchTools({
actions: ['*_list_*', '*_search_*'],
});
const anthropicTools = tools.toAnthropic();

// Initialise Anthropic client
const anthropic = new Anthropic();

// Create a message with tool calls
const response = await anthropic.messages.create({
model: 'claude-haiku-4-5-20241022',
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The model name claude-haiku-4-5-20241022 appears to be incorrect. Based on Anthropic's model naming conventions, this should likely be claude-3-5-haiku-20241022 (Claude 3.5 Haiku from October 2024). There is no publicly documented Claude 4 or Claude 4.5 model as of the knowledge cutoff.

Suggested change
model: 'claude-haiku-4-5-20241022',
model: 'claude-3-5-haiku-20241022',

Copilot uses AI. Check for mistakes.
max_tokens: 1024,
system: 'You are a helpful assistant that can access tools.',
messages: [
{
role: 'user',
content: 'What is the employee with id: c28xIQaWQ6MzM5MzczMDA2NzMzMzkwNzIwNA phone number?',
},
],
tools: anthropicTools,
});

// Verify the response contains tool use
assert(response.content.length > 0, 'Expected at least one content block in the response');

const toolUseBlock = response.content.find((block) => block.type === 'tool_use');
assert(toolUseBlock !== undefined, 'Expected a tool_use block in the response');
assert(toolUseBlock.type === 'tool_use', 'Expected block to be tool_use type');
assert(toolUseBlock.name === 'hris_get_employee', 'Expected tool call to be hris_get_employee');

// Verify the input contains the expected fields
const input = toolUseBlock.input as Record<string, unknown>;
assert(input.id === 'c28xIQaWQ6MzM5MzczMDA2NzMzMzkwNzIwNA', 'Expected id to match the query');
};

// Run the example
await anthropicIntegration();
1 change: 1 addition & 0 deletions examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"devDependencies": {
"@ai-sdk/openai": "catalog:dev",
"@anthropic-ai/sdk": "catalog:peer",
"@clack/prompts": "catalog:dev",
"@types/node": "catalog:dev",
"ai": "catalog:peer",
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,14 @@
"vitest": "catalog:dev"
},
"peerDependencies": {
"@anthropic-ai/sdk": "catalog:peer",
"ai": "catalog:peer",
"openai": "catalog:peer"
},
"peerDependenciesMeta": {
"@anthropic-ai/sdk": {
"optional": true
},
"ai": {
"optional": true
},
Expand Down
18 changes: 18 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ catalogs:
vitest: ^4.0.15
zod: ^4.1.13
peer:
'@anthropic-ai/sdk': ^0.52.0
ai: ^5.0.108
openai: ^6.2.0
prod:
Expand Down
69 changes: 69 additions & 0 deletions src/tool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ describe('StackOneTool', () => {
).toBe('string');
});

it('should convert to Anthropic tool format', () => {
const tool = createMockTool();
const anthropicFormat = tool.toAnthropic();

expect(anthropicFormat.name).toBe('test_tool');
expect(anthropicFormat.description).toBe('Test tool');
expect(anthropicFormat.input_schema.type).toBe('object');
const properties = anthropicFormat.input_schema.properties as Record<string, { type: string }>;
expect(properties.id).toBeDefined();
expect(properties.id.type).toBe('string');
});

it('should convert to OpenAI Responses API tool format', () => {
const tool = createMockTool();
const responsesFormat = tool.toOpenAIResponses();
Expand Down Expand Up @@ -352,6 +364,63 @@ describe('Tools', () => {
expect(openAITools[1].function.name).toBe('tool2');
});

it('should convert all tools to Anthropic format', () => {
const tool1 = new BaseTool(
'tool1',
'Tool 1',
{
type: 'object',
properties: { id: { type: 'string' } },
},
{
kind: 'http',
method: 'GET',
url: 'https://api.example.com/test/{id}',
bodyType: 'json',
params: [
{
name: 'id',
location: ParameterLocation.PATH,
type: 'string',
},
],
},
);

const tool2 = new BaseTool(
'tool2',
'Tool 2',
{
type: 'object',
properties: { name: { type: 'string' } },
},
{
kind: 'http',
method: 'POST',
url: 'https://api.example.com/test',
bodyType: 'json',
params: [
{
name: 'name',
location: ParameterLocation.BODY,
type: 'string',
},
],
},
);

const tools = new Tools([tool1, tool2]);
const anthropicTools = tools.toAnthropic();

expect(anthropicTools).toBeInstanceOf(Array);
expect(anthropicTools.length).toBe(2);
expect(anthropicTools[0].name).toBe('tool1');
expect(anthropicTools[0].description).toBe('Tool 1');
expect(anthropicTools[0].input_schema.type).toBe('object');
expect(anthropicTools[1].name).toBe('tool2');
expect(anthropicTools[1].description).toBe('Tool 2');
});

it('should convert all tools to OpenAI Responses API tools', () => {
const tool1 = new StackOneTool(
'tool1',
Expand Down
25 changes: 25 additions & 0 deletions src/tool.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Tool as AnthropicTool } from '@anthropic-ai/sdk/resources';
import * as orama from '@orama/orama';
import type { ChatCompletionFunctionTool } from 'openai/resources/chat/completions';
import type { FunctionTool as OpenAIResponsesFunctionTool } from 'openai/resources/responses/responses';
Expand Down Expand Up @@ -182,6 +183,22 @@ export class BaseTool {
};
}

/**
* Convert the tool to Anthropic format
* @see https://docs.anthropic.com/en/docs/build-with-claude/tool-use
*/
toAnthropic(): AnthropicTool {
return {
name: this.name,
description: this.description,
input_schema: {
type: 'object',
properties: this.parameters.properties,
required: this.parameters.required,
},
};
}

/**
* Convert the tool to OpenAI Responses API format
* @see https://platform.openai.com/docs/api-reference/responses
Expand Down Expand Up @@ -379,6 +396,14 @@ export class Tools implements Iterable<BaseTool> {
return this.tools.map((tool) => tool.toOpenAI());
}

/**
* Convert all tools to Anthropic format
* @see https://docs.anthropic.com/en/docs/build-with-claude/tool-use
*/
toAnthropic(): AnthropicTool[] {
return this.tools.map((tool) => tool.toAnthropic());
}

/**
* Convert all tools to OpenAI Responses API format
* @see https://platform.openai.com/docs/api-reference/responses
Expand Down
Loading