Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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