Skip to content

Commit 839f58b

Browse files
authored
Merge branch 'main' into fix/zod-catalog-prod
2 parents 905e535 + 407fe98 commit 839f58b

16 files changed

Lines changed: 186 additions & 202 deletions

examples/meta-tools.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import process from 'node:process';
1010
import { openai } from '@ai-sdk/openai';
11-
import { StackOneToolSet, Tools } from '@stackone/ai';
11+
import { type JsonObject, StackOneToolSet, Tools } from '@stackone/ai';
1212
import { generateText, stepCountIs } from 'ai';
1313

1414
const apiKey = process.env.STACKONE_API_KEY;
@@ -160,7 +160,7 @@ const directMetaToolUsage = async (): Promise<void> => {
160160

161161
try {
162162
// Prepare parameters based on the tool's schema
163-
let params: Record<string, unknown> = {};
163+
let params = {} satisfies JsonObject;
164164
if (firstTool.name === 'bamboohr_list_employees') {
165165
params = { limit: 5 };
166166
} else if (firstTool.name === 'bamboohr_create_employee') {
@@ -173,7 +173,7 @@ const directMetaToolUsage = async (): Promise<void> => {
173173

174174
const result = await executeTool.execute({
175175
toolName: firstTool.name,
176-
params: params,
176+
params,
177177
});
178178

179179
console.log('Execution result:', JSON.stringify(result, null, 2));
@@ -209,7 +209,7 @@ const dynamicToolRouter = async (): Promise<void> => {
209209
const metaTools = await combinedTools.metaTools();
210210

211211
// Create a router function that finds and executes tools based on intent
212-
const routeAndExecute = async (intent: string, params: Record<string, unknown> = {}) => {
212+
const routeAndExecute = async (intent: string, params: JsonObject = {}) => {
213213
const filterTool = metaTools.getTool('meta_search_tools');
214214
const executeTool = metaTools.getTool('meta_execute_tool');
215215
if (!filterTool || !executeTool) throw new Error('Meta tools not found');
@@ -221,18 +221,18 @@ const dynamicToolRouter = async (): Promise<void> => {
221221
minScore: 0.5,
222222
});
223223

224-
const tools = searchResult.tools as Array<{ name: string; score: number }>;
225-
if (tools.length === 0) {
224+
const tools = searchResult.tools;
225+
if (!Array.isArray(tools) || tools.length === 0) {
226226
return { error: 'No relevant tools found for the given intent' };
227227
}
228228

229-
const selectedTool = tools[0];
229+
const selectedTool = tools[0] as { name: string; score: number };
230230
console.log(`Routing to: ${selectedTool.name} (score: ${selectedTool.score.toFixed(2)})`);
231231

232232
// Execute the selected tool
233233
return await executeTool.execute({
234234
toolName: selectedTool.name,
235-
params: params,
235+
params,
236236
});
237237
};
238238

@@ -244,7 +244,7 @@ const dynamicToolRouter = async (): Promise<void> => {
244244
params: { name: 'Jane Smith', email: 'jane@example.com' },
245245
},
246246
{ intent: 'Find recruitment candidates', params: { status: 'active' } },
247-
];
247+
] as const satisfies { intent: string; params: JsonObject }[];
248248

249249
for (const { intent, params } of intents) {
250250
console.log(`\nIntent: "${intent}"`);

examples/tanstack-ai-integration.test.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,24 +33,19 @@ describe('tanstack-ai-integration example e2e', () => {
3333

3434
// Get a specific tool
3535
const employeeTool = tools.getTool('bamboohr_get_employee');
36-
expect(employeeTool).toBeDefined();
36+
assert(employeeTool, 'Expected bamboohr_get_employee tool to exist');
3737

3838
// Create TanStack AI compatible tool wrapper
3939
// Use toJsonSchema() to get the parameter schema in JSON Schema format
4040
const getEmployeeTool = {
41-
name: employeeTool!.name,
42-
description: employeeTool!.description,
43-
inputSchema: employeeTool!.toJsonSchema(),
44-
execute: async (args: Record<string, unknown>) => {
45-
return employeeTool!.execute(args);
46-
},
41+
name: employeeTool.name,
42+
description: employeeTool.description,
43+
inputSchema: employeeTool.toJsonSchema(),
44+
execute: employeeTool.execute.bind(employeeTool),
4745
};
4846

4947
expect(getEmployeeTool.name).toBe('bamboohr_get_employee');
50-
expect(getEmployeeTool.description).toContain('employee');
51-
expect(getEmployeeTool.inputSchema).toBeDefined();
5248
expect(getEmployeeTool.inputSchema.type).toBe('object');
53-
expect(typeof getEmployeeTool.execute).toBe('function');
5449
});
5550

5651
it('should execute tool directly', async () => {
@@ -68,9 +63,7 @@ describe('tanstack-ai-integration example e2e', () => {
6863
name: employeeTool.name,
6964
description: employeeTool.description,
7065
inputSchema: employeeTool.toJsonSchema(),
71-
execute: async (args: Record<string, unknown>) => {
72-
return employeeTool.execute(args);
73-
},
66+
execute: employeeTool.execute.bind(employeeTool),
7467
};
7568

7669
// Execute the tool directly to verify it works

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"publint": "catalog:dev",
6060
"tsdown": "catalog:dev",
6161
"type-fest": "catalog:dev",
62+
"typescript": "catalog:dev",
6263
"unplugin-unused": "catalog:dev",
6364
"vitest": "catalog:dev"
6465
},

pnpm-lock.yaml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ catalogs:
2424
publint: ^0.3.12
2525
tsdown: ^0.17.2
2626
type-fest: ^4.41.0
27+
typescript: ^5.8.3
2728
unplugin-unused: ^0.5.4
2829
vitest: ^4.0.15
2930
examples:

src/feedback.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { z } from 'zod';
22
import { DEFAULT_BASE_URL } from './consts';
33
import { BaseTool } from './tool';
4-
import type { ExecuteConfig, ExecuteOptions, JsonDict, ToolParameters } from './types';
4+
import type { ExecuteConfig, ExecuteOptions, JsonObject, JsonValue, ToolParameters } from './types';
55
import { StackOneError } from './utils/errors';
66

77
interface FeedbackToolOptions {
@@ -107,9 +107,9 @@ export function createFeedbackTool(
107107

108108
tool.execute = async function (
109109
this: BaseTool,
110-
inputParams?: JsonDict | string,
110+
inputParams?: JsonObject | string,
111111
executeOptions?: ExecuteOptions,
112-
): Promise<JsonDict> {
112+
): Promise<JsonObject> {
113113
try {
114114
const rawParams =
115115
typeof inputParams === 'string' ? JSON.parse(inputParams) : inputParams || {};
@@ -137,12 +137,12 @@ export function createFeedbackTool(
137137
return {
138138
multiple_requests: dryRunResults,
139139
total_accounts: parsedParams.account_id.length,
140-
} satisfies JsonDict;
140+
} satisfies JsonObject;
141141
}
142142

143143
// Send feedback to each account individually
144-
const results = [];
145-
const errors = [];
144+
const results: Array<{ account_id: string; status: number; response: JsonValue }> = [];
145+
const errors: Array<{ account_id: string; status?: number; error: string }> = [];
146146

147147
for (const accountId of parsedParams.account_id) {
148148
try {
@@ -159,9 +159,9 @@ export function createFeedbackTool(
159159
});
160160

161161
const text = await response.text();
162-
let parsed: unknown;
162+
let parsed: JsonValue;
163163
try {
164-
parsed = text ? JSON.parse(text) : {};
164+
parsed = text ? (JSON.parse(text) satisfies JsonValue) : {};
165165
} catch {
166166
parsed = { raw: text };
167167
}
@@ -191,7 +191,7 @@ export function createFeedbackTool(
191191
}
192192

193193
// Return summary of all submissions in Python SDK format
194-
const response: JsonDict = {
194+
const response = {
195195
message: `Feedback sent to ${parsedParams.account_id.length} account(s)`,
196196
total_accounts: parsedParams.account_id.length,
197197
successful: results.length,
@@ -208,7 +208,7 @@ export function createFeedbackTool(
208208
error: e.error,
209209
})),
210210
],
211-
};
211+
} satisfies JsonObject;
212212

213213
// If all submissions failed, throw an error
214214
if (errors.length > 0 && results.length === 0) {

src/headers.test.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { describe, expect, it } from 'vitest';
21
import { normaliseHeaders } from './headers';
32

43
describe('normaliseHeaders', () => {
@@ -43,12 +42,6 @@ describe('normaliseHeaders', () => {
4342
});
4443
});
4544

46-
it('skips undefined values', () => {
47-
expect(normaliseHeaders({ foo: 'bar', baz: undefined })).toEqual({
48-
foo: 'bar',
49-
});
50-
});
51-
5245
it('skips null values', () => {
5346
expect(normaliseHeaders({ foo: 'bar', baz: null })).toEqual({
5447
foo: 'bar',
@@ -64,7 +57,6 @@ describe('normaliseHeaders', () => {
6457
object: { nested: 'value' },
6558
array: [1, 2, 3],
6659
nullValue: null,
67-
undefinedValue: undefined,
6860
}),
6961
).toEqual({
7062
string: 'text',

src/headers.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { z } from 'zod/mini';
2-
import type { JsonDict } from './types';
2+
import type { JsonObject } from './types';
33

44
/**
55
* Known StackOne API header keys that are forwarded as HTTP headers
@@ -18,13 +18,13 @@ export const stackOneHeadersSchema = z.record(z.string(), z.string()).brand<'Sta
1818
export type StackOneHeaders = z.infer<typeof stackOneHeadersSchema>;
1919

2020
/**
21-
* Normalises header values from JsonDict to StackOneHeaders (branded type)
21+
* Normalises header values from JsonObject to StackOneHeaders (branded type)
2222
* Converts numbers and booleans to strings, and serialises objects to JSON
2323
*
24-
* @param headers - Headers object with unknown value types
24+
* @param headers - Headers object with JSON value types
2525
* @returns Normalised headers with string values only (branded type)
2626
*/
27-
export function normaliseHeaders(headers: JsonDict | undefined): StackOneHeaders {
27+
export function normaliseHeaders(headers: JsonObject | undefined): StackOneHeaders {
2828
if (!headers) return stackOneHeadersSchema.parse({});
2929
const result: Record<string, string> = {};
3030
for (const [key, value] of Object.entries(headers)) {

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export type {
2121
AISDKToolResult,
2222
ExecuteConfig,
2323
ExecuteOptions,
24-
JsonDict,
24+
JsonObject,
25+
JsonValue,
2526
ParameterLocation,
2627
ToolDefinition,
2728
} from './types';

0 commit comments

Comments
 (0)