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
2 changes: 1 addition & 1 deletion src/feedback.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/node';
import { StackOneError } from './utils/errors';
import { StackOneError } from './utils/error-stackone';
import { createFeedbackTool } from './feedback';

interface FeedbackResultItem {
Expand Down
2 changes: 1 addition & 1 deletion src/feedback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { z } from 'zod';
import { DEFAULT_BASE_URL } from './consts';
import { BaseTool } from './tool';
import type { ExecuteConfig, ExecuteOptions, JsonObject, JsonValue, ToolParameters } from './types';
import { StackOneError } from './utils/errors';
import { StackOneError } from './utils/error-stackone';

interface FeedbackToolOptions {
baseUrl?: string;
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

export { BaseTool, StackOneTool, Tools } from './tool';
export { createFeedbackTool } from './feedback';
export { StackOneAPIError, StackOneError } from './utils/errors';
export { StackOneError } from './utils/error-stackone';
export { StackOneAPIError } from './utils/error-stackone-api';

export {
StackOneToolSet,
Expand Down
2 changes: 1 addition & 1 deletion src/requestBuilder.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/node';
import { type HttpExecuteConfig, type JsonObject, ParameterLocation } from './types';
import { StackOneAPIError } from './utils/errors';
import { StackOneAPIError } from './utils/error-stackone-api';
import { RequestBuilder } from './requestBuilder';

describe('RequestBuilder', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/requestBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
type JsonObject,
ParameterLocation,
} from './types';
import { StackOneAPIError } from './utils/errors';
import { StackOneAPIError } from './utils/error-stackone-api';

interface SerializationOptions {
maxDepth?: number;
Expand Down
2 changes: 1 addition & 1 deletion src/rpc-client.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RpcClient } from './rpc-client';
import { stackOneHeadersSchema } from './headers';
import { StackOneAPIError } from './utils/errors';
import { StackOneAPIError } from './utils/error-stackone-api';

test('should successfully execute an RPC action', async () => {
const client = new RpcClient({
Expand Down
2 changes: 1 addition & 1 deletion src/rpc-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
rpcActionResponseSchema,
rpcClientConfigSchema,
} from './schema';
import { StackOneAPIError } from './utils/errors';
import { StackOneAPIError } from './utils/error-stackone-api';

// Re-export types for consumers and to make types portable
export type { RpcActionResponse } from './schema';
Expand Down
187 changes: 186 additions & 1 deletion src/tool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
ParameterLocation,
type ToolParameters,
} from './types';
import { StackOneAPIError } from './utils/errors';
import { StackOneAPIError } from './utils/error-stackone-api';

// Create a mock tool for testing
const createMockTool = (headers?: Record<string, string>): BaseTool => {
Expand Down Expand Up @@ -328,6 +328,157 @@ describe('StackOneTool', () => {
});
});

describe('BaseTool - additional coverage', () => {
it('should throw error when execute is called on non-HTTP tool', async () => {
const rpcTool = new BaseTool(
'rpc_tool',
'RPC tool',
{ type: 'object', properties: {} },
{
kind: 'rpc',
method: 'test_method',
url: 'https://api.example.com/rpc',
payloadKeys: { action: 'action', body: 'body' },
},
);

await expect(rpcTool.execute({})).rejects.toThrow(
'BaseTool.execute is only available for HTTP-backed tools',
);
});

it('should throw error for invalid parameter type', async () => {
const tool = createMockTool();

// @ts-expect-error - intentionally passing invalid type
await expect(tool.execute(12345)).rejects.toThrow('Invalid parameters type');
});

it('should create execution metadata for RPC config in toAISDK', async () => {
const rpcTool = new BaseTool(
'rpc_tool',
'RPC tool',
{ type: 'object', properties: {} },
{
kind: 'rpc',
method: 'test_method',
url: 'https://api.example.com/rpc',
payloadKeys: { action: 'action', body: 'body', headers: 'headers' },
},
);

const aiSdkTool = await rpcTool.toAISDK({ executable: false });
const execution = aiSdkTool.rpc_tool.execution;

expect(execution).toBeDefined();
expect(execution?.config.kind).toBe('rpc');
if (execution?.config.kind === 'rpc') {
expect(execution.config.method).toBe('test_method');
expect(execution.config.url).toBe('https://api.example.com/rpc');
expect(execution.config.payloadKeys).toEqual({
action: 'action',
body: 'body',
headers: 'headers',
});
}
});

it('should create execution metadata for local config in toAISDK', async () => {
const localTool = new BaseTool(
'local_tool',
'Local tool',
{ type: 'object', properties: {} },
{
kind: 'local',
identifier: 'local_test',
description: 'local://test',
},
);

const aiSdkTool = await localTool.toAISDK({ executable: false });
const execution = aiSdkTool.local_tool.execution;

expect(execution).toBeDefined();
expect(execution?.config.kind).toBe('local');
if (execution?.config.kind === 'local') {
expect(execution.config.identifier).toBe('local_test');
expect(execution.config.description).toBe('local://test');
}
});

it('should allow providing custom execution metadata in toAISDK', async () => {
const tool = createMockTool();
const customExecution = {
config: {
kind: 'http' as const,
method: 'POST' as const,
url: 'https://custom.example.com',
bodyType: 'json' as const,
params: [],
},
headers: { 'X-Custom': 'value' },
};

const aiSdkTool = await tool.toAISDK({ execution: customExecution });
const execution = aiSdkTool.test_tool.execution;

expect(execution).toBeDefined();
expect(execution?.config.kind).toBe('http');
if (execution?.config.kind === 'http') {
expect(execution.config.url).toBe('https://custom.example.com');
}
expect(execution?.headers).toEqual({ 'X-Custom': 'value' });
});

it('should return undefined execution when execution option is false', async () => {
const tool = createMockTool();

const aiSdkTool = await tool.toAISDK({ execution: false });
expect(aiSdkTool.test_tool.execution).toBeUndefined();
});

it('should return undefined execute when executable option is false', async () => {
const tool = createMockTool();

const aiSdkTool = await tool.toAISDK({ executable: false });
expect(aiSdkTool.test_tool.execute).toBeUndefined();
});

it('should get headers from tool without requestBuilder', () => {
const rpcTool = new BaseTool(
'rpc_tool',
'RPC tool',
{ type: 'object', properties: {} },
{
kind: 'rpc',
method: 'test_method',
url: 'https://api.example.com/rpc',
payloadKeys: { action: 'action', body: 'body' },
},
{ 'X-Custom': 'value' },
);

expect(rpcTool.getHeaders()).toEqual({ 'X-Custom': 'value' });
});

it('should set headers on tool without requestBuilder', () => {
const rpcTool = new BaseTool(
'rpc_tool',
'RPC tool',
{ type: 'object', properties: {} },
{
kind: 'rpc',
method: 'test_method',
url: 'https://api.example.com/rpc',
payloadKeys: { action: 'action', body: 'body' },
},
);

rpcTool.setHeaders({ 'X-New-Header': 'new-value' });
expect(rpcTool.getHeaders()).toEqual({ 'X-New-Header': 'new-value' });
});
});

describe('Tools', () => {
it('should get tool by name', () => {
const tool = createMockTool();
Expand Down Expand Up @@ -952,6 +1103,40 @@ describe('Meta Search Tools', () => {
});
});

describe('Error handling', () => {
it('should wrap non-StackOneError in meta_search_tools execute', async () => {
const filterTool = metaTools.getTool('meta_search_tools');
assert(filterTool, 'filterTool should be defined');

// Pass invalid params type to trigger JSON.parse error on non-JSON string
await expect(filterTool.execute('not valid json')).rejects.toThrow('Error executing tool:');
});

it('should wrap non-StackOneError in meta_execute_tool execute', async () => {
const executeTool = metaTools.getTool('meta_execute_tool');
assert(executeTool, 'executeTool should be defined');

// Pass invalid JSON string to trigger JSON.parse error
await expect(executeTool.execute('not valid json')).rejects.toThrow('Error executing tool:');
});

it('should throw StackOneError for invalid params type in meta_search_tools', async () => {
const filterTool = metaTools.getTool('meta_search_tools');
assert(filterTool, 'filterTool should be defined');

// @ts-expect-error - intentionally passing invalid type
await expect(filterTool.execute(123)).rejects.toThrow('Invalid parameters type');
});

it('should throw StackOneError for invalid params type in meta_execute_tool', async () => {
const executeTool = metaTools.getTool('meta_execute_tool');
assert(executeTool, 'executeTool should be defined');

// @ts-expect-error - intentionally passing invalid type
await expect(executeTool.execute(true)).rejects.toThrow('Invalid parameters type');
});
});

describe('Integration: meta tools workflow', () => {
it('should discover and execute tools in sequence', async () => {
const filterTool = metaTools.getTool('meta_search_tools');
Expand Down
2 changes: 1 addition & 1 deletion src/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import type {
ToolParameters,
} from './types';

import { StackOneError } from './utils/errors';
import { StackOneError } from './utils/error-stackone';
import { TfidfIndex } from './utils/tfidf-index';
import { tryImport } from './utils/try-import';

Expand Down
Loading
Loading