diff --git a/.claude/rules/file-operations.md b/.claude/rules/file-operations.md index bc594ea..986745d 100644 --- a/.claude/rules/file-operations.md +++ b/.claude/rules/file-operations.md @@ -1,8 +1,8 @@ --- description: Use when making HTTP requests. Covers native fetch API patterns and error handling. (project) alwaysApply: false -paths: "**/*.ts" -globs: "**/*.ts" +paths: '**/*.ts' +globs: '**/*.ts' --- # HTTP Request Standards @@ -17,17 +17,19 @@ Use the native fetch API for HTTP requests. Node.js now includes built-in fetch, ```typescript async function fetchData(url: string): Promise { - try { - const response = await fetch(url); - - if (!response.ok) { - throw new Error(`API error: ${response.status} for ${url}`); - } - - return await response.json(); - } catch (error) { - throw new Error(`Failed to fetch from ${url}: ${error instanceof Error ? error.message : String(error)}`); - } + try { + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`API error: ${response.status} for ${url}`); + } + + return await response.json(); + } catch (error) { + throw new Error( + `Failed to fetch from ${url}: ${error instanceof Error ? error.message : String(error)}`, + ); + } } ``` @@ -41,7 +43,7 @@ async function fetchData(url: string): Promise { ```typescript if (!response.ok) { - throw new Error(`API error: ${response.status} for ${url}`); + throw new Error(`API error: ${response.status} for ${url}`); } ``` @@ -65,17 +67,17 @@ async function fetchData(url: string): Promise { ```typescript async function getUser(userId: string): Promise { - try { - const response = await fetch(`https://api.example.com/users/${userId}`); + try { + const response = await fetch(`https://api.example.com/users/${userId}`); - if (!response.ok) { - throw new Error(`Failed to fetch user: ${response.status}`); - } + if (!response.ok) { + throw new Error(`Failed to fetch user: ${response.status}`); + } - return await response.json() as User; - } catch (error) { - throw new Error(`User fetch failed: ${error instanceof Error ? error.message : String(error)}`); - } + return (await response.json()) as User; + } catch (error) { + throw new Error(`User fetch failed: ${error instanceof Error ? error.message : String(error)}`); + } } ``` @@ -83,23 +85,25 @@ async function getUser(userId: string): Promise { ```typescript async function createUser(data: CreateUserInput): Promise { - try { - const response = await fetch('https://api.example.com/users', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }); - - if (!response.ok) { - throw new Error(`Failed to create user: ${response.status}`); - } - - return await response.json() as User; - } catch (error) { - throw new Error(`User creation failed: ${error instanceof Error ? error.message : String(error)}`); - } + try { + const response = await fetch('https://api.example.com/users', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error(`Failed to create user: ${response.status}`); + } + + return (await response.json()) as User; + } catch (error) { + throw new Error( + `User creation failed: ${error instanceof Error ? error.message : String(error)}`, + ); + } } ``` @@ -107,21 +111,23 @@ async function createUser(data: CreateUserInput): Promise { ```typescript async function getProtectedData(token: string): Promise { - try { - const response = await fetch('https://api.example.com/protected', { - headers: { - 'Authorization': `Bearer ${token}`, - }, - }); - - if (!response.ok) { - throw new Error(`Auth failed: ${response.status}`); - } - - return await response.json() as Data; - } catch (error) { - throw new Error(`Protected data fetch failed: ${error instanceof Error ? error.message : String(error)}`); - } + try { + const response = await fetch('https://api.example.com/protected', { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (!response.ok) { + throw new Error(`Auth failed: ${response.status}`); + } + + return (await response.json()) as Data; + } catch (error) { + throw new Error( + `Protected data fetch failed: ${error instanceof Error ? error.message : String(error)}`, + ); + } } ``` diff --git a/.claude/rules/typescript-patterns.md b/.claude/rules/typescript-patterns.md index 1fa395d..43ea9ce 100644 --- a/.claude/rules/typescript-patterns.md +++ b/.claude/rules/typescript-patterns.md @@ -1,8 +1,8 @@ --- description: Use when writing or reviewing TypeScript code. Covers type safety, exhaustiveness checks, avoiding any/non-null assertions, clean code practices. (project) alwaysApply: false -paths: "**/*.ts" -globs: "*.ts" +paths: '**/*.ts' +globs: '*.ts' --- # TypeScript Patterns and Best Practices @@ -17,19 +17,19 @@ When branching on string unions, use the `satisfies never` pattern to guarantee ```typescript switch (bodyType) { - case 'json': - // ... - break; - case 'form': - // ... - break; - case 'multipart-form': - // ... - break; - default: { - bodyType satisfies never; // Error if new variant added - throw new Error(`Unsupported HTTP body type: ${String(bodyType)}`); - } + case 'json': + // ... + break; + case 'form': + // ... + break; + case 'multipart-form': + // ... + break; + default: { + bodyType satisfies never; // Error if new variant added + throw new Error(`Unsupported HTTP body type: ${String(bodyType)}`); + } } ``` @@ -48,7 +48,7 @@ Always use specific types instead of `any` when possible. ```typescript function processData(data: any): any { - return data.value; + return data.value; } ``` @@ -56,15 +56,15 @@ function processData(data: any): any { ```typescript function processData(data: T): U { - return data.value; + return data.value; } // Or with type narrowing function processData(data: unknown): unknown { - if (typeof data === 'object' && data !== null && 'value' in data) { - return (data as Record).value; - } - throw new Error('Invalid data format'); + if (typeof data === 'object' && data !== null && 'value' in data) { + return (data as Record).value; + } + throw new Error('Invalid data format'); } ``` @@ -83,8 +83,8 @@ Never use non-null assertions. Instead, use proper null checking. ```typescript function getConfig(configMap: Map): Config { - const config = configMap.get('default'); - return config!; // Dangerous! + const config = configMap.get('default'); + return config!; // Dangerous! } ``` @@ -92,16 +92,16 @@ function getConfig(configMap: Map): Config { ```typescript function getConfig(configMap: Map): Config { - const config = configMap.get('default'); - if (!config) { - throw new Error('Default config not found'); - } - return config; + const config = configMap.get('default'); + if (!config) { + throw new Error('Default config not found'); + } + return config; } // Or with optional chaining and nullish coalescing function getConfig(configMap: Map): Config { - return configMap.get('default') ?? getDefaultConfig(); + return configMap.get('default') ?? getDefaultConfig(); } ``` @@ -113,8 +113,8 @@ Create new variables instead of reassigning function parameters. ```typescript function mergeOptions(options: Options, overrides?: Options): Options { - options = { ...options, ...overrides }; // Reassignment - return options; + options = { ...options, ...overrides }; // Reassignment + return options; } ``` @@ -122,8 +122,8 @@ function mergeOptions(options: Options, overrides?: Options): Options { ```typescript function mergeOptions(options: Options, overrides?: Options): Options { - const mergedOptions = { ...options, ...overrides }; - return mergedOptions; + const mergedOptions = { ...options, ...overrides }; + return mergedOptions; } ``` @@ -135,13 +135,13 @@ Use namespaces or simple exported functions instead. ```typescript export class Utils { - public static formatDate(date: Date): string { - return date.toISOString(); - } + public static formatDate(date: Date): string { + return date.toISOString(); + } - public static parseDate(dateStr: string): Date { - return new Date(dateStr); - } + public static parseDate(dateStr: string): Date { + return new Date(dateStr); + } } ``` @@ -149,13 +149,13 @@ export class Utils { ```typescript export namespace Utils { - export const formatDate = (date: Date): string => { - return date.toISOString(); - }; + export const formatDate = (date: Date): string => { + return date.toISOString(); + }; - export const parseDate = (dateStr: string): Date => { - return new Date(dateStr); - }; + export const parseDate = (dateStr: string): Date => { + return new Date(dateStr); + }; } ``` @@ -163,11 +163,11 @@ export namespace Utils { ```typescript export const formatDate = (date: Date): string => { - return date.toISOString(); + return date.toISOString(); }; export const parseDate = (dateStr: string): Date => { - return new Date(dateStr); + return new Date(dateStr); }; ``` @@ -179,7 +179,7 @@ Always specify return types for functions. ```typescript const calculateTotal = (items: Item[]) => { - return items.reduce((sum, item) => sum + item.price, 0); + return items.reduce((sum, item) => sum + item.price, 0); }; ``` @@ -187,12 +187,12 @@ const calculateTotal = (items: Item[]) => { ```typescript const calculateTotal = (items: Item[]): number => { - return items.reduce((sum, item) => sum + item.price, 0); + return items.reduce((sum, item) => sum + item.price, 0); }; // Or for function declarations function calculateTotal(items: Item[]): number { - return items.reduce((sum, item) => sum + item.price, 0); + return items.reduce((sum, item) => sum + item.price, 0); } ``` @@ -204,17 +204,19 @@ Use proper type-safe methods to remove properties instead of setting to `undefin ```typescript function removeProperty(obj: Record): void { - obj['propertyToRemove'] = undefined; // Type error! - delete obj['propertyToRemove']; // Linter warning + obj['propertyToRemove'] = undefined; // Type error! + delete obj['propertyToRemove']; // Linter warning } ``` **Good - Destructuring (Immutable)**: ```typescript -function removeProperty(obj: Record): Record { - const { propertyToRemove, ...rest } = obj; - return rest; +function removeProperty( + obj: Record, +): Record { + const { propertyToRemove, ...rest } = obj; + return rest; } ``` @@ -222,7 +224,7 @@ function removeProperty(obj: Record): Record): void { - delete obj['propertyToRemove']; + delete obj['propertyToRemove']; } ``` diff --git a/.claude/rules/typescript-testing.md b/.claude/rules/typescript-testing.md index b35ae99..37238b0 100644 --- a/.claude/rules/typescript-testing.md +++ b/.claude/rules/typescript-testing.md @@ -1,8 +1,8 @@ --- description: Use when writing or running tests. Covers Vitest commands, MSW HTTP mocking, fs-fixture for file system tests. (project) alwaysApply: false -paths: "**/*.test.ts" -globs: "*.test.ts" +paths: '**/*.test.ts' +globs: '*.test.ts' --- # TypeScript Testing with Vitest and MSW @@ -27,9 +27,9 @@ import { describe, it, expect, vi } from 'vitest'; // ✅ CORRECT - Use globals directly (no import needed) describe('MyTest', () => { - it('should work', () => { - expect(true).toBe(true); - }); + it('should work', () => { + expect(true).toBe(true); + }); }); ``` @@ -47,9 +47,9 @@ Add endpoints to `mocks/handlers.ts`: import { http, HttpResponse } from 'msw'; export const handlers = [ - http.get('https://api.example.com/endpoint', () => { - return HttpResponse.json({ data: 'mock response' }); - }), + http.get('https://api.example.com/endpoint', () => { + return HttpResponse.json({ data: 'mock response' }); + }), ]; ``` @@ -62,12 +62,12 @@ import { http, HttpResponse } from 'msw'; import { server } from '../../../mocks/node'; it('handles error responses', async () => { - server.use( - http.get('https://api.example.com/endpoint', () => { - return HttpResponse.json({ error: 'Not found' }, { status: 404 }); - }) - ); - // Test implementation + server.use( + http.get('https://api.example.com/endpoint', () => { + return HttpResponse.json({ error: 'Not found' }, { status: 404 }); + }), + ); + // Test implementation }); ``` @@ -79,18 +79,18 @@ Use MSW event listeners to verify requests were made: ```typescript it('makes the expected request', async () => { - const recordedRequests: Request[] = []; - const listener = ({ request }: { request: Request }) => { - recordedRequests.push(request); - }; - server.events.on('request:start', listener); + const recordedRequests: Request[] = []; + const listener = ({ request }: { request: Request }) => { + recordedRequests.push(request); + }; + server.events.on('request:start', listener); - await someFunction(); + await someFunction(); - expect(recordedRequests).toHaveLength(1); - expect(recordedRequests[0]?.url).toBe('https://api.example.com/endpoint'); + expect(recordedRequests).toHaveLength(1); + expect(recordedRequests[0]?.url).toBe('https://api.example.com/endpoint'); - server.events.removeListener('request:start', listener); + server.events.removeListener('request:start', listener); }); ``` @@ -108,9 +108,9 @@ For file system tests, use `fs-fixture` with `await using` for automatic cleanup import { createFixture } from 'fs-fixture'; it('should save file to disk', async () => { - await using fixture = await createFixture(); - await fixture.writeFile('data.json', JSON.stringify({ test: 'data' })); - expect(await fixture.exists('data.json')).toBe(true); + await using fixture = await createFixture(); + await fixture.writeFile('data.json', JSON.stringify({ test: 'data' })); + expect(await fixture.exists('data.json')).toBe(true); }); ``` diff --git a/.claude/skills/orama-integration/SKILL.md b/.claude/skills/orama-integration/SKILL.md index 519170e..8823231 100644 --- a/.claude/skills/orama-integration/SKILL.md +++ b/.claude/skills/orama-integration/SKILL.md @@ -1,7 +1,7 @@ --- name: orama-integration description: Use when integrating with Orama. Links to official docs for search, indexing, answer engine. (project) -globs: "" +globs: '' alwaysApply: false --- diff --git a/.gitleaks.toml b/.gitleaks.toml index 3e4bd33..05ba133 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -6,6 +6,4 @@ useDefault = true [allowlist] description = "Global allowlist" -paths = [ - '''pnpm-lock\.yaml$''', -] +paths = ['''pnpm-lock\.yaml$'''] diff --git a/README.md b/README.md index e9ddd7e..138d152 100644 --- a/README.md +++ b/README.md @@ -31,15 +31,15 @@ bun add @stackone/ai zod ## Usage ```typescript -import { StackOneToolSet } from "@stackone/ai"; +import { StackOneToolSet } from '@stackone/ai'; const toolset = new StackOneToolSet({ - baseUrl: "https://api.stackone.com", - accountId: "your-account-id", + baseUrl: 'https://api.stackone.com', + accountId: 'your-account-id', }); const tools = await toolset.fetchTools(); -const employeeTool = tools.getTool("bamboohr_list_employees"); +const employeeTool = tools.getTool('bamboohr_list_employees'); const employees = await employeeTool.execute(); ``` @@ -58,26 +58,26 @@ or load from a .env file using your preferred environment variable library. StackOne uses account IDs to identify different integrations. You can specify the account ID at different levels: ```typescript -import { StackOneToolSet } from "@stackone/ai"; +import { StackOneToolSet } from '@stackone/ai'; // Single account - simplest approach -const toolset = new StackOneToolSet({ accountId: "your-bamboohr-account" }); +const toolset = new StackOneToolSet({ accountId: 'your-bamboohr-account' }); const tools = await toolset.fetchTools(); // Multiple accounts - returns tools from both integrations const multiAccountToolset = new StackOneToolSet(); const allTools = await multiAccountToolset.fetchTools({ - accountIds: ["bamboohr-account-123", "workday-account-456"], + accountIds: ['bamboohr-account-123', 'workday-account-456'], }); // Filter to specific integration when using multiple accounts const bamboohrOnly = await multiAccountToolset.fetchTools({ - accountIds: ["bamboohr-account-123", "workday-account-456"], - actions: ["bamboohr_*"], // Only BambooHR tools + accountIds: ['bamboohr-account-123', 'workday-account-456'], + actions: ['bamboohr_*'], // Only BambooHR tools }); // Set directly on a tool instance -tools.setAccountId("direct-account-id"); +tools.setAccountId('direct-account-id'); const currentAccountId = tools.getAccountId(); // Get the current account ID ``` @@ -93,29 +93,29 @@ npm install @stackone/ai openai # or: yarn/pnpm/bun add ``` ```typescript -import { OpenAI } from "openai"; -import { StackOneToolSet } from "@stackone/ai"; +import { OpenAI } from 'openai'; +import { StackOneToolSet } from '@stackone/ai'; const toolset = new StackOneToolSet({ - baseUrl: "https://api.stackone.com", - accountId: "your-account-id", + baseUrl: 'https://api.stackone.com', + accountId: 'your-account-id', }); const tools = await toolset.fetchTools(); await openai.chat.completions.create({ - model: "gpt-5.1", - messages: [ - { - role: "system", - content: "You are a helpful HR assistant using BambooHR.", - }, - { - role: "user", - content: "Create a time-off request for employee id cxIQ5764hj2", - }, - ], - tools: tools.toOpenAI(), + model: 'gpt-5.1', + messages: [ + { + role: 'system', + content: 'You are a helpful HR assistant using BambooHR.', + }, + { + role: 'user', + content: 'Create a time-off request for employee id cxIQ5764hj2', + }, + ], + tools: tools.toOpenAI(), }); ``` @@ -131,12 +131,12 @@ npm install @stackone/ai openai # or: yarn/pnpm/bun add ``` ```typescript -import OpenAI from "openai"; -import { StackOneToolSet } from "@stackone/ai"; +import OpenAI from 'openai'; +import { StackOneToolSet } from '@stackone/ai'; const toolset = new StackOneToolSet({ - baseUrl: "https://api.stackone.com", - accountId: "your-account-id", + baseUrl: 'https://api.stackone.com', + accountId: 'your-account-id', }); const tools = await toolset.fetchTools(); @@ -144,10 +144,10 @@ const tools = await toolset.fetchTools(); const openai = new OpenAI(); await openai.responses.create({ - model: "gpt-5.1", - instructions: "You are a helpful HR assistant.", - input: "What is the phone number for employee c28xIQ?", - tools: tools.toOpenAIResponses(), + model: 'gpt-5.1', + instructions: 'You are a helpful HR assistant.', + input: 'What is the phone number for employee c28xIQ?', + tools: tools.toOpenAIResponses(), }); ``` @@ -163,12 +163,12 @@ npm install @stackone/ai @anthropic-ai/sdk # or: yarn/pnpm/bun add ``` ```typescript -import Anthropic from "@anthropic-ai/sdk"; -import { StackOneToolSet } from "@stackone/ai"; +import Anthropic from '@anthropic-ai/sdk'; +import { StackOneToolSet } from '@stackone/ai'; const toolset = new StackOneToolSet({ - baseUrl: "https://api.stackone.com", - accountId: "your-account-id", + baseUrl: 'https://api.stackone.com', + accountId: 'your-account-id', }); const tools = await toolset.fetchTools(); @@ -176,16 +176,16 @@ const tools = await toolset.fetchTools(); const anthropic = new Anthropic(); await anthropic.messages.create({ - model: "claude-haiku-4-5-20241022", - max_tokens: 1024, - system: "You are a helpful HR assistant.", - messages: [ - { - role: "user", - content: "What is the phone number for employee c28xIQ?", - }, - ], - tools: tools.toAnthropic(), + model: 'claude-haiku-4-5-20241022', + max_tokens: 1024, + system: 'You are a helpful HR assistant.', + messages: [ + { + role: 'user', + content: 'What is the phone number for employee c28xIQ?', + }, + ], + tools: tools.toAnthropic(), }); ``` @@ -201,21 +201,21 @@ npm install @stackone/ai ai @ai-sdk/openai # or: yarn/pnpm/bun add ``` ```typescript -import { openai } from "@ai-sdk/openai"; -import { generateText } from "ai"; -import { StackOneToolSet } from "@stackone/ai"; +import { openai } from '@ai-sdk/openai'; +import { generateText } from 'ai'; +import { StackOneToolSet } from '@stackone/ai'; const toolset = new StackOneToolSet({ - baseUrl: "https://api.stackone.com", - accountId: "your-account-id", + baseUrl: 'https://api.stackone.com', + accountId: 'your-account-id', }); const tools = await toolset.fetchTools(); await generateText({ - model: openai("gpt-5.1"), - tools: await tools.toAISDK(), - maxSteps: 3, + model: openai('gpt-5.1'), + tools: await tools.toAISDK(), + maxSteps: 3, }); ``` @@ -231,41 +231,41 @@ npm install @stackone/ai @tanstack/ai @tanstack/ai-openai zod # or: yarn/pnpm/b ``` ```typescript -import { chat } from "@tanstack/ai"; -import { openai } from "@tanstack/ai-openai"; -import { z } from "zod"; -import { StackOneToolSet } from "@stackone/ai"; +import { chat } from '@tanstack/ai'; +import { openai } from '@tanstack/ai-openai'; +import { z } from 'zod'; +import { StackOneToolSet } from '@stackone/ai'; const toolset = new StackOneToolSet({ - baseUrl: "https://api.stackone.com", - accountId: "your-account-id", + baseUrl: 'https://api.stackone.com', + accountId: 'your-account-id', }); const tools = await toolset.fetchTools(); -const employeeTool = tools.getTool("bamboohr_get_employee"); +const employeeTool = tools.getTool('bamboohr_get_employee'); // TanStack AI requires Zod schemas for tool input validation const getEmployeeTool = { - name: employeeTool.name, - description: employeeTool.description, - inputSchema: z.object({ - id: z.string().describe("The employee ID"), - }), - execute: async (args: { id: string }) => { - return employeeTool.execute(args); - }, + name: employeeTool.name, + description: employeeTool.description, + inputSchema: z.object({ + id: z.string().describe('The employee ID'), + }), + execute: async (args: { id: string }) => { + return employeeTool.execute(args); + }, }; const adapter = openai(); const stream = chat({ - adapter, - model: "gpt-5.1", - messages: [{ role: "user", content: "Get employee with id: abc123" }], - tools: [getEmployeeTool], + adapter, + model: 'gpt-5.1', + messages: [{ role: 'user', content: 'Get employee with id: abc123' }], + tools: [getEmployeeTool], }); for await (const chunk of stream) { - // Process streaming chunks + // Process streaming chunks } ``` @@ -281,12 +281,12 @@ npm install @stackone/ai @anthropic-ai/claude-agent-sdk zod # or: yarn/pnpm/bun ``` ```typescript -import { query } from "@anthropic-ai/claude-agent-sdk"; -import { StackOneToolSet } from "@stackone/ai"; +import { query } from '@anthropic-ai/claude-agent-sdk'; +import { StackOneToolSet } from '@stackone/ai'; const toolset = new StackOneToolSet({ - baseUrl: "https://api.stackone.com", - accountId: "your-account-id", + baseUrl: 'https://api.stackone.com', + accountId: 'your-account-id', }); // Fetch tools and convert to Claude Agent SDK format @@ -295,17 +295,17 @@ const mcpServer = await tools.toClaudeAgentSdk(); // Use with Claude Agent SDK query const result = query({ - prompt: "Get the employee with id: abc123", - options: { - model: "claude-sonnet-4-5-20250929", - mcpServers: { "stackone-tools": mcpServer }, - tools: [], // Disable built-in tools - maxTurns: 3, - }, + prompt: 'Get the employee with id: abc123', + options: { + model: 'claude-sonnet-4-5-20250929', + mcpServers: { 'stackone-tools': mcpServer }, + tools: [], // Disable built-in tools + maxTurns: 3, + }, }); for await (const message of result) { - // Process streaming messages + // Process streaming messages } ``` @@ -321,29 +321,29 @@ You can filter tools by account IDs, providers, and action patterns: ```typescript // Filter by account IDs -toolset.setAccounts(["account-123", "account-456"]); +toolset.setAccounts(['account-123', 'account-456']); const tools = await toolset.fetchTools(); // OR const tools = await toolset.fetchTools({ - accountIds: ["account-123", "account-456"], + accountIds: ['account-123', 'account-456'], }); // Filter by providers -const tools = await toolset.fetchTools({ providers: ["hibob", "bamboohr"] }); +const tools = await toolset.fetchTools({ providers: ['hibob', 'bamboohr'] }); // Filter by actions with exact match const tools = await toolset.fetchTools({ - actions: ["hibob_list_employees", "hibob_create_employees"], + actions: ['hibob_list_employees', 'hibob_create_employees'], }); // Filter by actions with glob patterns -const tools = await toolset.fetchTools({ actions: ["*_list_employees"] }); +const tools = await toolset.fetchTools({ actions: ['*_list_employees'] }); // Combine multiple filters const tools = await toolset.fetchTools({ - accountIds: ["account-123"], - providers: ["hibob"], - actions: ["*_list_*"], + accountIds: ['account-123'], + providers: ['hibob'], + actions: ['*_list_*'], }); ``` @@ -371,10 +371,10 @@ Meta tools provide two core capabilities: #### Basic Usage ```typescript -import { StackOneToolSet } from "@stackone/ai"; +import { StackOneToolSet } from '@stackone/ai'; const toolset = new StackOneToolSet({ - baseUrl: "https://api.stackone.com", + baseUrl: 'https://api.stackone.com', }); const tools = await toolset.fetchTools(); @@ -391,14 +391,14 @@ const aiSdkTools = await metaTools.toAISDK(); #### Example: Dynamic Tool Discovery with AI SDK ```typescript -import { generateText } from "ai"; -import { openai } from "@ai-sdk/openai"; +import { generateText } from 'ai'; +import { openai } from '@ai-sdk/openai'; const { text } = await generateText({ - model: openai("gpt-5.1"), - tools: aiSdkTools, - prompt: "Find tools for managing employees and create a time off request", - maxSteps: 3, // Allow multiple tool calls + model: openai('gpt-5.1'), + tools: aiSdkTools, + prompt: 'Find tools for managing employees and create a time off request', + maxSteps: 3, // Allow multiple tool calls }); ``` @@ -406,22 +406,22 @@ const { text } = await generateText({ ```typescript // Step 1: Discover relevant tools -const filterTool = metaTools.getTool("meta_search_tools"); +const filterTool = metaTools.getTool('meta_search_tools'); const searchResult = await filterTool.execute({ - query: "employee time off vacation", - limit: 5, - minScore: 0.3, // Minimum relevance score (0-1) + query: 'employee time off vacation', + limit: 5, + minScore: 0.3, // Minimum relevance score (0-1) }); // Step 2: Execute a discovered tool -const executeTool = metaTools.getTool("meta_execute_tool"); +const executeTool = metaTools.getTool('meta_execute_tool'); const result = await executeTool.execute({ - toolName: "bamboohr_create_time_off", - params: { - employeeId: "emp_123", - startDate: "2024-01-15", - endDate: "2024-01-19", - }, + toolName: 'bamboohr_create_time_off', + params: { + employeeId: 'emp_123', + startDate: '2024-01-15', + endDate: '2024-01-19', + }, }); ``` @@ -430,9 +430,9 @@ const result = await executeTool.execute({ ### Custom Base URL ```typescript -import { StackOneToolSet } from "@stackone/ai"; +import { StackOneToolSet } from '@stackone/ai'; -const toolset = new StackOneToolSet({ baseUrl: "https://api.example-dev.com" }); +const toolset = new StackOneToolSet({ baseUrl: 'https://api.example-dev.com' }); ``` ### Testing with dryRun @@ -440,21 +440,18 @@ const toolset = new StackOneToolSet({ baseUrl: "https://api.example-dev.com" }); You can use the `dryRun` option to return the api arguments from a tool call without making the actual api call: ```typescript -import { StackOneToolSet } from "@stackone/ai"; +import { StackOneToolSet } from '@stackone/ai'; // Initialize the toolset const toolset = new StackOneToolSet({ - baseUrl: "https://api.stackone.com", + baseUrl: 'https://api.stackone.com', }); const tools = await toolset.fetchTools(); -const employeeTool = tools.getTool("bamboohr_list_employees"); +const employeeTool = tools.getTool('bamboohr_list_employees'); // Use dryRun to see the request details -const dryRunResult = await employeeTool.execute( - { query: { limit: 5 } }, - { dryRun: true } -); +const dryRunResult = await employeeTool.execute({ query: { limit: 5 } }, { dryRun: true }); console.log(dryRunResult); // { @@ -493,15 +490,15 @@ The feedback tool: The feedback tool is automatically available when using `StackOneToolSet`: ```typescript -import { StackOneToolSet } from "@stackone/ai"; +import { StackOneToolSet } from '@stackone/ai'; const toolset = new StackOneToolSet({ - baseUrl: "https://api.stackone.com", + baseUrl: 'https://api.stackone.com', }); const tools = await toolset.fetchTools(); // The feedback tool is automatically included -const feedbackTool = tools.getTool("meta_collect_tool_feedback"); +const feedbackTool = tools.getTool('meta_collect_tool_feedback'); // Use with AI agents - they will ask for user consent first const openAITools = tools.toOpenAI(); @@ -515,13 +512,13 @@ You can also use the feedback tool directly: ```typescript // Get the feedback tool -const feedbackTool = tools.getTool("meta_collect_tool_feedback"); +const feedbackTool = tools.getTool('meta_collect_tool_feedback'); // Submit feedback (after getting user consent) const result = await feedbackTool.execute({ - feedback: "The tools worked great! Very easy to use.", - account_id: "acc_123456", - tool_names: ["bamboohr_list_employees", "bamboohr_create_time_off"], + feedback: 'The tools worked great! Very easy to use.', + account_id: 'acc_123456', + tool_names: ['bamboohr_list_employees', 'bamboohr_create_time_off'], }); ``` @@ -532,16 +529,16 @@ The feedback tool supports both single and multiple account IDs. When you provid ```typescript // Single account ID (string) await feedbackTool.execute({ - feedback: "The tools worked great! Very easy to use.", - account_id: "acc_123456", - tool_names: ["bamboohr_list_employees", "bamboohr_create_time_off"], + feedback: 'The tools worked great! Very easy to use.', + account_id: 'acc_123456', + tool_names: ['bamboohr_list_employees', 'bamboohr_create_time_off'], }); // Multiple account IDs (array) await feedbackTool.execute({ - feedback: "The tools worked great! Very easy to use.", - account_id: ["acc_123456", "acc_789012"], - tool_names: ["bamboohr_list_employees", "bamboohr_create_time_off"], + feedback: 'The tools worked great! Very easy to use.', + account_id: ['acc_123456', 'acc_789012'], + tool_names: ['bamboohr_list_employees', 'bamboohr_create_time_off'], }); ``` diff --git a/examples/README.md b/examples/README.md index 6edd50e..c859461 100644 --- a/examples/README.md +++ b/examples/README.md @@ -223,14 +223,14 @@ All production examples include proper error handling: ```typescript try { - const result = await tool.execute(params); - // Handle success + const result = await tool.execute(params); + // Handle success } catch (error) { - if (error instanceof StackOneAPIError) { - console.error("API Error:", error.statusCode, error.responseBody); - } else { - console.error("Unexpected error:", error.message); - } + if (error instanceof StackOneAPIError) { + console.error('API Error:', error.statusCode, error.responseBody); + } else { + console.error('Unexpected error:', error.message); + } } ``` @@ -240,13 +240,13 @@ Examples demonstrate different ways to provide account IDs: ```typescript // 1. At toolset initialization -const toolset = new StackOneToolSet({ accountId: "account_123" }); +const toolset = new StackOneToolSet({ accountId: 'account_123' }); // 2. When getting tools -const tools = toolset.getStackOneTools("hris_*", "account_123"); +const tools = toolset.getStackOneTools('hris_*', 'account_123'); // 3. Directly on individual tools -tool.setAccountId("account_123"); +tool.setAccountId('account_123'); ``` ## Testing Examples