From 332bc5f8250f02a8bb466223ca99b9321c1a0b88 Mon Sep 17 00:00:00 2001 From: Shashi-Stackone Date: Thu, 2 Apr 2026 14:05:22 +0100 Subject: [PATCH 1/8] include available connectors in search/execute tool descriptions --- src/toolsets.ts | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/toolsets.ts b/src/toolsets.ts index 5093b39..52c0bf7 100644 --- a/src/toolsets.ts +++ b/src/toolsets.ts @@ -332,10 +332,15 @@ const localConfig = (id: string): LocalExecuteConfig => ({ }); /** @internal */ -export function createSearchTool(toolset: StackOneToolSet, accountIds?: string[]): BaseTool { +export function createSearchTool( + toolset: StackOneToolSet, + accountIds?: string[], + connectors?: string, +): BaseTool { + const connectorLine = connectors ? ` Available connectors: ${connectors}.` : ''; const tool = new BaseTool( 'tool_search', - 'Search for available tools by describing what you need. Returns matching tool names, descriptions, and parameter schemas. Use the returned parameter schemas to know exactly what to pass when calling tool_execute.', + `Search for available tools by describing what you need. Returns matching tool names, descriptions, and parameter schemas. Use the returned parameter schemas to know exactly what to pass when calling tool_execute.${connectorLine}`, searchParameters, localConfig('search'), ); @@ -380,12 +385,17 @@ export function createSearchTool(toolset: StackOneToolSet, accountIds?: string[] } /** @internal */ -export function createExecuteTool(toolset: StackOneToolSet, accountIds?: string[]): BaseTool { +export function createExecuteTool( + toolset: StackOneToolSet, + accountIds?: string[], + connectors?: string, +): BaseTool { let cachedTools: Awaited> | null = null; + const connectorLine = connectors ? ` Available connectors: ${connectors}.` : ''; const tool = new BaseTool( 'tool_execute', - 'Execute a tool by name with the given parameters. Use tool_search first to find available tools. The parameters field must match the parameter schema returned by tool_search. Pass parameters as a nested object matching the schema structure.', + `Execute a tool by name with the given parameters. Use tool_search first to find available tools. The parameters field must match the parameter schema returned by tool_search. Pass parameters as a nested object matching the schema structure.${connectorLine}`, executeParameters, localConfig('execute'), ); @@ -632,22 +642,34 @@ export class StackOneToolSet { * @param options - Options to scope tool discovery * @returns Tools collection containing tool_search and tool_execute */ - getTools(options?: { accountIds?: string[] }): Tools { + async getTools(options?: { accountIds?: string[] }): Promise { return this.buildTools(options?.accountIds); } /** * Build tool_search + tool_execute tools scoped to this toolset. */ - private buildTools(accountIds?: string[]): Tools { + private async buildTools(accountIds?: string[]): Promise { if (this.searchConfig === null) { throw new ToolSetConfigError( 'Search is disabled. Initialize StackOneToolSet with a search config to enable.', ); } - const searchTool = createSearchTool(this, accountIds); - const executeTool = createExecuteTool(this, accountIds); + // Discover available connectors for dynamic descriptions + let connectors = ''; + try { + const allTools = await this.fetchTools({ accountIds }); + const connectorSet = allTools.getConnectors(); + if (connectorSet.size > 0) { + connectors = Array.from(connectorSet).sort().join(', '); + } + } catch { + // Best-effort: if discovery fails, use generic descriptions + } + + const searchTool = createSearchTool(this, accountIds, connectors); + const executeTool = createExecuteTool(this, accountIds, connectors); return new Tools([searchTool, executeTool]); } @@ -681,7 +703,7 @@ export class StackOneToolSet { const effectiveAccountIds = options?.accountIds ?? this.executeConfig?.accountIds; if (options?.mode === 'search_and_execute') { - return this.buildTools(effectiveAccountIds).toOpenAI(); + return (await this.buildTools(effectiveAccountIds)).toOpenAI(); } const tools = await this.fetchTools({ accountIds: effectiveAccountIds }); From 56500267188fb3c5ee9eabd0674788d2daf5acd7 Mon Sep 17 00:00:00 2001 From: Shashi-Stackone Date: Thu, 2 Apr 2026 14:16:35 +0100 Subject: [PATCH 2/8] No breaking change --- src/toolsets.ts | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/toolsets.ts b/src/toolsets.ts index 52c0bf7..cae0dbf 100644 --- a/src/toolsets.ts +++ b/src/toolsets.ts @@ -642,32 +642,20 @@ export class StackOneToolSet { * @param options - Options to scope tool discovery * @returns Tools collection containing tool_search and tool_execute */ - async getTools(options?: { accountIds?: string[] }): Promise { + getTools(options?: { accountIds?: string[] }): Tools { return this.buildTools(options?.accountIds); } /** * Build tool_search + tool_execute tools scoped to this toolset. */ - private async buildTools(accountIds?: string[]): Promise { + private buildTools(accountIds?: string[], connectors?: string): Tools { if (this.searchConfig === null) { throw new ToolSetConfigError( 'Search is disabled. Initialize StackOneToolSet with a search config to enable.', ); } - // Discover available connectors for dynamic descriptions - let connectors = ''; - try { - const allTools = await this.fetchTools({ accountIds }); - const connectorSet = allTools.getConnectors(); - if (connectorSet.size > 0) { - connectors = Array.from(connectorSet).sort().join(', '); - } - } catch { - // Best-effort: if discovery fails, use generic descriptions - } - const searchTool = createSearchTool(this, accountIds, connectors); const executeTool = createExecuteTool(this, accountIds, connectors); return new Tools([searchTool, executeTool]); @@ -703,7 +691,18 @@ export class StackOneToolSet { const effectiveAccountIds = options?.accountIds ?? this.executeConfig?.accountIds; if (options?.mode === 'search_and_execute') { - return (await this.buildTools(effectiveAccountIds)).toOpenAI(); + // Discover available connectors for dynamic descriptions + let connectors: string | undefined; + try { + const allTools = await this.fetchTools({ accountIds: effectiveAccountIds }); + const connectorSet = allTools.getConnectors(); + if (connectorSet.size > 0) { + connectors = Array.from(connectorSet).sort().join(', '); + } + } catch { + // Best-effort: if discovery fails, use generic descriptions + } + return this.buildTools(effectiveAccountIds, connectors).toOpenAI(); } const tools = await this.fetchTools({ accountIds: effectiveAccountIds }); From 74d07326a0cce9eaf25a7a1facc7005cc8cd5d4a Mon Sep 17 00:00:00 2001 From: Shashi-Stackone Date: Tue, 7 Apr 2026 10:11:41 +0100 Subject: [PATCH 3/8] Fix th timeout and account ID issue in the SDK for the toolset.execute path --- src/rpc-client.ts | 33 +++++++++++++++++++++++++++------ src/schema.ts | 1 + src/toolsets.ts | 13 ++++++++++++- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/rpc-client.ts b/src/rpc-client.ts index ceb90dc..bc2f88a 100644 --- a/src/rpc-client.ts +++ b/src/rpc-client.ts @@ -23,6 +23,7 @@ export type { RpcActionResponse } from './schema'; export class RpcClient { private readonly baseUrl: string; private readonly authHeader: string; + private readonly timeout: number; constructor(config: RpcClientConfig) { const validatedConfig = rpcClientConfigSchema.parse(config); @@ -30,6 +31,7 @@ export class RpcClient { const username = validatedConfig.security.username; const password = validatedConfig.security.password || ''; this.authHeader = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`; + this.timeout = validatedConfig.timeout ?? 60_000; } /** @@ -73,13 +75,32 @@ export class RpcClient { ...forwardedHeaders, } satisfies Record; - const response = await fetch(url, { - method: 'POST', - headers: httpHeaders, - body: JSON.stringify(requestBody), - }); + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), this.timeout); - const responseBody: unknown = await response.json(); + let response: Response; + let responseBody: unknown; + try { + response = await fetch(url, { + method: 'POST', + headers: httpHeaders, + body: JSON.stringify(requestBody), + signal: controller.signal, + }); + responseBody = await response.json(); + } catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + throw new StackOneAPIError( + `Request timed out after ${this.timeout}ms for ${url}`, + 0, + null, + requestBody, + ); + } + throw error; + } finally { + clearTimeout(timeoutId); + } if (!response.ok) { throw new StackOneAPIError( diff --git a/src/schema.ts b/src/schema.ts index 17f7f80..4e9ccac 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -56,6 +56,7 @@ export const rpcClientConfigSchema = z.object({ username: z.string(), password: z.optional(z.string()), }), + timeout: z.optional(z.number()), }); /** diff --git a/src/toolsets.ts b/src/toolsets.ts index cae0dbf..3642a5e 100644 --- a/src/toolsets.ts +++ b/src/toolsets.ts @@ -97,6 +97,8 @@ export interface BaseToolSetConfig { authentication?: AuthenticationConfig; headers?: Record; rpcClient?: RpcClient; + /** Request timeout in milliseconds. Default: 60000 (60s). */ + timeout?: number; } /** @@ -134,6 +136,8 @@ type AccountConfig = SimplifyDeep; private rpcClient?: RpcClient; + private readonly timeout: number; private readonly searchConfig: SearchConfig | null; private readonly executeConfig: ExecuteToolsConfig | undefined; @@ -507,6 +512,7 @@ export class StackOneToolSet { this.authentication = authentication; this.headers = configHeaders; this.rpcClient = config?.rpcClient; + this.timeout = config?.timeout ?? config?.execute?.timeout ?? 60_000; this.accountId = accountId; this.accountIds = config?.accountIds ?? []; @@ -643,7 +649,11 @@ export class StackOneToolSet { * @returns Tools collection containing tool_search and tool_execute */ getTools(options?: { accountIds?: string[] }): Tools { - return this.buildTools(options?.accountIds); + const accountIds = + options?.accountIds ?? + this.executeConfig?.accountIds ?? + (this.accountIds.length > 0 ? this.accountIds : undefined); + return this.buildTools(accountIds); } /** @@ -1155,6 +1165,7 @@ export class StackOneToolSet { username: apiKey, password, }, + timeout: this.timeout, }); return this.rpcClient; From 862557e3ecb709dcb8d177eeba0707df406736f0 Mon Sep 17 00:00:00 2001 From: Shashi-Stackone Date: Tue, 7 Apr 2026 12:08:55 +0100 Subject: [PATCH 4/8] Trim the search tool example to use only missiing path --- examples/search-tools.ts | 179 ++++++--------------------------------- 1 file changed, 28 insertions(+), 151 deletions(-) diff --git a/examples/search-tools.ts b/examples/search-tools.ts index 501222f..6e790d0 100644 --- a/examples/search-tools.ts +++ b/examples/search-tools.ts @@ -1,168 +1,45 @@ /** - * This example demonstrates how to use semantic search for dynamic tool discovery. - * Semantic search allows AI agents to find relevant tools based on natural language queries - * using StackOne's search API with local BM25+TF-IDF fallback. + * Search tool patterns: callable wrapper and config overrides. * - * Search config can be set at the constructor level via `{ search: SearchConfig }` and - * overridden per-call on `searchTools()`. Pass `{ search: null }` to disable search. - * SearchConfig: { method?: 'auto' | 'semantic' | 'local', topK?: number, minSimilarity?: number } + * For full agent execution, see agent-tool-search.ts. * - * @example - * ```bash - * # Run with required environment variables: - * STACKONE_API_KEY=your-key OPENAI_API_KEY=your-key npx tsx examples/search-tools.ts - * ``` + * Run with: + * STACKONE_API_KEY=xxx STACKONE_ACCOUNT_ID=xxx npx tsx examples/search-tools.ts */ import process from 'node:process'; -import { openai } from '@ai-sdk/openai'; import { StackOneToolSet } from '@stackone/ai'; -import { generateText, stepCountIs } from 'ai'; -const apiKey = process.env.STACKONE_API_KEY; -if (!apiKey) { - console.error('STACKONE_API_KEY environment variable is required'); - process.exit(1); -} - -/** - * Example 1: Search for tools with semantic search and use with AI SDK - */ -const searchToolsWithAISDK = async (): Promise => { - console.log('Example 1: Semantic tool search with AI SDK\n'); - - // Configure search at the constructor level — applies to all searchTools() calls - const toolset = new StackOneToolSet({ search: { method: 'semantic', topK: 5 } }); - - // searchTools() inherits the constructor's search config - const tools = await toolset.searchTools('manage employee records and time off'); - - console.log(`Found ${tools.length} relevant tools`); - - // Convert to AI SDK format and use with generateText - const aiSdkTools = await tools.toAISDK(); - - const { text, toolCalls } = await generateText({ - model: openai('gpt-5.1'), - tools: aiSdkTools, - prompt: `List the first 5 employees from the HR system.`, - stopWhen: stepCountIs(3), - }); - - console.log('AI Response:', text); - console.log('\nTool calls made:', toolCalls?.map((call) => call.toolName).join(', ')); -}; - -/** - * Example 2: Using SearchTool for agent loops - */ -const searchToolWithAgentLoop = async (): Promise => { - console.log('\nExample 2: SearchTool for agent loops\n'); - - // Enable search with default method: 'auto' - const toolset = new StackOneToolSet({ search: {} }); - - // Per-call options override constructor defaults when needed - const searchTool = toolset.getSearchTool({ search: 'auto' }); - - // In an agent loop, search for tools as needed - const queries = ['create a new employee', 'list job candidates', 'send a message to a channel']; +const accountId = process.env.STACKONE_ACCOUNT_ID; - for (const query of queries) { - const tools = await searchTool.search(query, { topK: 3 }); - const toolNames = tools.toArray().map((t) => t.name); - console.log(`Query: "${query}" -> Found: ${toolNames.join(', ') || '(none)'}`); - } -}; +// --- Example 1: getSearchTool() callable --- +console.log('=== getSearchTool() callable ===\n'); -/** - * Example 3: Lightweight action name search - */ -const searchActionNames = async (): Promise => { - console.log('\nExample 3: Lightweight action name search\n'); - - const toolset = new StackOneToolSet({ search: {} }); - - // Search for action names without fetching full tool definitions - const results = await toolset.searchActionNames('manage employees', { - topK: 5, - }); - - console.log('Search results:'); - for (const result of results) { - console.log(` - ${result.id}: score=${result.similarityScore.toFixed(2)}`); - } - - // Then fetch specific tools based on the results - if (results.length > 0) { - const topActions = results.filter((r) => r.similarityScore > 0.7).map((r) => r.id); - console.log(`\nFetching tools for top actions: ${topActions.join(', ')}`); - - const tools = await toolset.fetchTools({ actions: topActions }); - console.log(`Fetched ${tools.length} tools`); - } -}; - -/** - * Example 4: Local-only search (no API call) - */ -const localSearchOnly = async (): Promise => { - console.log('\nExample 4: Local-only BM25+TF-IDF search\n'); - - // Set search method at constructor level — all searchTools() calls use local search - const toolset = new StackOneToolSet({ search: { method: 'local', topK: 3 } }); - - // searchTools() inherits local search config from the constructor - const tools = await toolset.searchTools('create time off request'); +const toolset = new StackOneToolSet({ search: {} }); +const searchTool = toolset.getSearchTool(); - console.log(`Found ${tools.length} tools using local search:`); - for (const tool of tools) { - console.log(` - ${tool.name}: ${tool.description}`); - } -}; - -/** - * Example 5: Constructor-level topK vs per-call override - */ -const topKConfig = async (): Promise => { - console.log('\nExample 5: topK at constructor vs per-call\n'); +const queries = ['cancel an event', 'list employees', 'send a message']; +for (const query of queries) { + const tools = await searchTool.search(query, { topK: 3, accountIds: accountId ? [accountId] : undefined }); + const names = tools.toArray().map((t) => t.name); + console.log(` "${query}" -> ${names.join(', ') || '(none)'}`); +} - // Constructor-level topK — all calls default to returning 3 results - const toolset = new StackOneToolSet({ search: { topK: 3 } }); +// --- Example 2: Constructor topK vs per-call override --- +console.log('\n=== Constructor topK vs per-call override ===\n'); - const query = 'manage employee records'; - console.log(`Constructor topK=3: searching for "${query}"`); - const toolsDefault = await toolset.searchTools(query); - console.log(` Got ${toolsDefault.length} tools (constructor default)`); - for (const tool of toolsDefault) { - console.log(` - ${tool.name}`); - } +const toolset3 = new StackOneToolSet({ search: { topK: 3 } }); +const toolset10 = new StackOneToolSet({ search: { topK: 10 } }); - // Per-call override — this single call returns up to 10 results - console.log('\nPer-call topK=10: overriding constructor default'); - const toolsOverride = await toolset.searchTools(query, { topK: 10 }); - console.log(` Got ${toolsOverride.length} tools (per-call override)`); - for (const tool of toolsOverride) { - console.log(` - ${tool.name}`); - } -}; +const query = 'manage employee records'; +const opts = accountId ? { accountIds: [accountId] } : {}; -// Main execution -const main = async (): Promise => { - try { - if (process.env.OPENAI_API_KEY) { - await searchToolsWithAISDK(); - } else { - console.log('OPENAI_API_KEY not found, skipping AI SDK example\n'); - } +const tools3 = await toolset3.searchTools(query, opts); +console.log(`Constructor topK=3: got ${tools3.length} tools`); - await searchToolWithAgentLoop(); - await searchActionNames(); - await localSearchOnly(); - await topKConfig(); - } catch (error) { - console.error('Error running examples:', error); - } -}; +const tools10 = await toolset10.searchTools(query, opts); +console.log(`Constructor topK=10: got ${tools10.length} tools`); -await main(); +// Per-call override: constructor says 3 but this call says 10 +const toolsOverride = await toolset3.searchTools(query, { ...opts, topK: 10 }); +console.log(`Per-call topK=10 (overrides constructor 3): got ${toolsOverride.length} tools`); From 69c8a1ae0c9f3142e34a49f9297cbec2a6b4fe2c Mon Sep 17 00:00:00 2001 From: Shashi-Stackone Date: Tue, 7 Apr 2026 12:46:04 +0100 Subject: [PATCH 5/8] Add working Workday Example --- examples/workday-integration.ts | 69 +++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 examples/workday-integration.ts diff --git a/examples/workday-integration.ts b/examples/workday-integration.ts new file mode 100644 index 0000000..b9888b9 --- /dev/null +++ b/examples/workday-integration.ts @@ -0,0 +1,69 @@ +/** + * Workday integration: timeout and account scoping for slow providers. + * + * Workday can take 10-15s to respond. This example shows how to configure + * timeout and accountIds through the execute config. + * + * Run with: + * STACKONE_API_KEY=xxx OPENAI_API_KEY=xxx STACKONE_ACCOUNT_ID=xxx npx tsx examples/workday-integration.ts + */ + +import process from 'node:process'; +import { StackOneToolSet } from '@stackone/ai'; +import OpenAI from 'openai'; + +const accountId = process.env.STACKONE_ACCOUNT_ID ?? ''; + +// Timeout and accountIds both live in the execute config +const toolset = new StackOneToolSet({ + search: { method: 'semantic', topK: 5 }, + accountId, + execute: { timeout: 120_000 }, +}); + +const client = new OpenAI(); + +async function runAgent( + messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[], + tools: OpenAI.Chat.Completions.ChatCompletionTool[], + maxSteps = 10, +): Promise { + for (let i = 0; i < maxSteps; i++) { + const response = await client.chat.completions.create({ model: 'gpt-5.4', messages, tools }); + const choice = response.choices[0]; + + if (!choice.message.tool_calls?.length) { + console.log(`Answer: ${choice.message.content}`); + return; + } + + messages.push(choice.message); + for (const tc of choice.message.tool_calls) { + if (tc.type !== 'function') continue; + console.log(` -> ${tc.function.name}(${tc.function.arguments.slice(0, 80)})`); + const tool = toolset.getTools({ accountIds: [accountId] }).getTool(tc.function.name); + const result = tool ? await tool.execute(tc.function.arguments) : { error: 'Unknown tool' }; + messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(result) }); + } + } +} + +// --- Example 1: Search and execute mode --- +console.log('=== Search and execute mode ===\n'); +const searchTools = toolset.getTools({ accountIds: [accountId] }).toOpenAI(); +await runAgent( + [ + { role: 'system', content: 'Use tool_search to find tools, then tool_execute to run them.' }, + { role: 'user', content: 'List the first 5 employees.' }, + ], + searchTools, +); + +// --- Example 2: Normal mode --- +console.log('\n=== Normal mode ===\n'); +const tools = await toolset.fetchTools({ actions: ['workday_*_employee*'] }); +if (tools.length === 0) { + console.log('No Workday tools found for this account.'); +} else { + await runAgent([{ role: 'user', content: 'List the first 5 employees.' }], tools.toOpenAI()); +} From 2cdcce6f26b7b30bdd4794b6f2489ebf6581938a Mon Sep 17 00:00:00 2001 From: Shashi-Stackone Date: Tue, 7 Apr 2026 12:55:34 +0100 Subject: [PATCH 6/8] Fix CI --- examples/search-tools.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/search-tools.ts b/examples/search-tools.ts index 6e790d0..547d452 100644 --- a/examples/search-tools.ts +++ b/examples/search-tools.ts @@ -20,7 +20,10 @@ const searchTool = toolset.getSearchTool(); const queries = ['cancel an event', 'list employees', 'send a message']; for (const query of queries) { - const tools = await searchTool.search(query, { topK: 3, accountIds: accountId ? [accountId] : undefined }); + const tools = await searchTool.search(query, { + topK: 3, + accountIds: accountId ? [accountId] : undefined, + }); const names = tools.toArray().map((t) => t.name); console.log(` "${query}" -> ${names.join(', ') || '(none)'}`); } From 09f4d3a892b23ade3d9277666c8c638dea47defd Mon Sep 17 00:00:00 2001 From: Shashi-Stackone Date: Tue, 7 Apr 2026 14:50:47 +0100 Subject: [PATCH 7/8] Document the Stackone API in the example for search tols and workday --- examples/search-tools.ts | 29 +++++++++++++++-------------- examples/workday-integration.ts | 33 +++++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/examples/search-tools.ts b/examples/search-tools.ts index 547d452..bb6846a 100644 --- a/examples/search-tools.ts +++ b/examples/search-tools.ts @@ -10,20 +10,27 @@ import process from 'node:process'; import { StackOneToolSet } from '@stackone/ai'; +const apiKey = process.env.STACKONE_API_KEY; const accountId = process.env.STACKONE_ACCOUNT_ID; +if (!apiKey) { + console.error('Set STACKONE_API_KEY to run this example.'); + process.exit(1); +} +if (!accountId) { + console.error('Set STACKONE_ACCOUNT_ID to run this example.'); + process.exit(1); +} + // --- Example 1: getSearchTool() callable --- console.log('=== getSearchTool() callable ===\n'); -const toolset = new StackOneToolSet({ search: {} }); +const toolset = new StackOneToolSet({ apiKey, accountId, search: {} }); const searchTool = toolset.getSearchTool(); const queries = ['cancel an event', 'list employees', 'send a message']; for (const query of queries) { - const tools = await searchTool.search(query, { - topK: 3, - accountIds: accountId ? [accountId] : undefined, - }); + const tools = await searchTool.search(query, { topK: 3 }); const names = tools.toArray().map((t) => t.name); console.log(` "${query}" -> ${names.join(', ') || '(none)'}`); } @@ -31,18 +38,12 @@ for (const query of queries) { // --- Example 2: Constructor topK vs per-call override --- console.log('\n=== Constructor topK vs per-call override ===\n'); -const toolset3 = new StackOneToolSet({ search: { topK: 3 } }); -const toolset10 = new StackOneToolSet({ search: { topK: 10 } }); +const toolset3 = new StackOneToolSet({ apiKey, accountId, search: { topK: 3 } }); const query = 'manage employee records'; -const opts = accountId ? { accountIds: [accountId] } : {}; -const tools3 = await toolset3.searchTools(query, opts); +const tools3 = await toolset3.searchTools(query); console.log(`Constructor topK=3: got ${tools3.length} tools`); -const tools10 = await toolset10.searchTools(query, opts); -console.log(`Constructor topK=10: got ${tools10.length} tools`); - -// Per-call override: constructor says 3 but this call says 10 -const toolsOverride = await toolset3.searchTools(query, { ...opts, topK: 10 }); +const toolsOverride = await toolset3.searchTools(query, { topK: 10 }); console.log(`Per-call topK=10 (overrides constructor 3): got ${toolsOverride.length} tools`); diff --git a/examples/workday-integration.ts b/examples/workday-integration.ts index b9888b9..8a89efe 100644 --- a/examples/workday-integration.ts +++ b/examples/workday-integration.ts @@ -2,7 +2,12 @@ * Workday integration: timeout and account scoping for slow providers. * * Workday can take 10-15s to respond. This example shows how to configure - * timeout and accountIds through the execute config. + * timeout for slow providers. + * + * Prerequisites: + * - STACKONE_API_KEY + * - STACKONE_ACCOUNT_ID (a Workday-connected account) + * - OPENAI_API_KEY * * Run with: * STACKONE_API_KEY=xxx OPENAI_API_KEY=xxx STACKONE_ACCOUNT_ID=xxx npx tsx examples/workday-integration.ts @@ -12,13 +17,28 @@ import process from 'node:process'; import { StackOneToolSet } from '@stackone/ai'; import OpenAI from 'openai'; -const accountId = process.env.STACKONE_ACCOUNT_ID ?? ''; +const apiKey = process.env.STACKONE_API_KEY; +const accountId = process.env.STACKONE_ACCOUNT_ID; + +if (!apiKey) { + console.error('Set STACKONE_API_KEY to run this example.'); + process.exit(1); +} +if (!accountId) { + console.error('Set STACKONE_ACCOUNT_ID to run this example.'); + process.exit(1); +} +if (!process.env.OPENAI_API_KEY) { + console.error('Set OPENAI_API_KEY to run this example.'); + process.exit(1); +} -// Timeout and accountIds both live in the execute config +// Timeout for slow providers (Workday can take 10-15s) const toolset = new StackOneToolSet({ - search: { method: 'semantic', topK: 5 }, + apiKey, accountId, - execute: { timeout: 120_000 }, + search: { method: 'auto', topK: 5 }, + timeout: 120_000, }); const client = new OpenAI(); @@ -41,7 +61,8 @@ async function runAgent( for (const tc of choice.message.tool_calls) { if (tc.type !== 'function') continue; console.log(` -> ${tc.function.name}(${tc.function.arguments.slice(0, 80)})`); - const tool = toolset.getTools({ accountIds: [accountId] }).getTool(tc.function.name); + const searchTools = toolset.getTools({ accountIds: [accountId] }); + const tool = searchTools.getTool(tc.function.name); const result = tool ? await tool.execute(tc.function.arguments) : { error: 'Unknown tool' }; messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(result) }); } From 84a6993af6788edaaaff58b959d909afa23866c7 Mon Sep 17 00:00:00 2001 From: Shashi-Stackone Date: Tue, 7 Apr 2026 15:51:04 +0100 Subject: [PATCH 8/8] Fix CI --- examples/workday-integration.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/workday-integration.ts b/examples/workday-integration.ts index 8a89efe..9648825 100644 --- a/examples/workday-integration.ts +++ b/examples/workday-integration.ts @@ -17,8 +17,8 @@ import process from 'node:process'; import { StackOneToolSet } from '@stackone/ai'; import OpenAI from 'openai'; -const apiKey = process.env.STACKONE_API_KEY; -const accountId = process.env.STACKONE_ACCOUNT_ID; +const apiKey = process.env.STACKONE_API_KEY ?? ''; +const accountId = process.env.STACKONE_ACCOUNT_ID ?? ''; if (!apiKey) { console.error('Set STACKONE_API_KEY to run this example.');