@lantos1618/better-ui 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +217 -116
- package/dist/ThemeProvider-BYeqWMsn.d.mts +187 -0
- package/dist/ThemeProvider-BaVZaDBO.d.ts +187 -0
- package/dist/auth/index.d.mts +56 -0
- package/dist/auth/index.d.ts +56 -0
- package/dist/auth/index.js +104 -0
- package/dist/auth/index.mjs +67 -0
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/components/index.d.mts +258 -0
- package/dist/components/index.d.ts +258 -0
- package/dist/components/index.js +1977 -0
- package/dist/components/index.mjs +1922 -0
- package/dist/index.d.mts +57 -296
- package/dist/index.d.ts +57 -296
- package/dist/index.js +243 -178
- package/dist/index.mjs +241 -175
- package/dist/persistence/index.d.mts +11 -0
- package/dist/persistence/index.d.ts +11 -0
- package/dist/persistence/index.js +66 -0
- package/dist/persistence/index.mjs +41 -0
- package/dist/react/index.d.mts +91 -0
- package/dist/react/index.d.ts +91 -0
- package/dist/react/index.js +284 -0
- package/dist/react/index.mjs +257 -0
- package/dist/tool-Ca2x-VNK.d.mts +361 -0
- package/dist/tool-Ca2x-VNK.d.ts +361 -0
- package/dist/types-CAOfGUPH.d.mts +31 -0
- package/dist/types-CAOfGUPH.d.ts +31 -0
- package/package.json +42 -20
- package/src/theme.css +101 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "../chunk-Y6FXYEAI.mjs";
|
|
3
|
+
|
|
4
|
+
// src/react/useTool.ts
|
|
5
|
+
import { useState, useCallback, useEffect, useRef } from "react";
|
|
6
|
+
function useTool(tool, initialInput, options = {}) {
|
|
7
|
+
const [data, setData] = useState(null);
|
|
8
|
+
const [loading, setLoading] = useState(false);
|
|
9
|
+
const [error, setError] = useState(null);
|
|
10
|
+
const [executed, setExecuted] = useState(false);
|
|
11
|
+
const inputRef = useRef(initialInput);
|
|
12
|
+
const optionsRef = useRef(options);
|
|
13
|
+
optionsRef.current = options;
|
|
14
|
+
const executionIdRef = useRef(0);
|
|
15
|
+
const pendingCountRef = useRef(0);
|
|
16
|
+
const execute = useCallback(
|
|
17
|
+
async (input) => {
|
|
18
|
+
const finalInput = input ?? inputRef.current;
|
|
19
|
+
if (finalInput === void 0) {
|
|
20
|
+
const err = new Error("No input provided to tool");
|
|
21
|
+
setError(err);
|
|
22
|
+
optionsRef.current.onError?.(err);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const currentExecutionId = ++executionIdRef.current;
|
|
26
|
+
pendingCountRef.current++;
|
|
27
|
+
setLoading(true);
|
|
28
|
+
setError(null);
|
|
29
|
+
try {
|
|
30
|
+
const context = {
|
|
31
|
+
cache: /* @__PURE__ */ new Map(),
|
|
32
|
+
fetch: globalThis.fetch?.bind(globalThis),
|
|
33
|
+
isServer: false,
|
|
34
|
+
...optionsRef.current.context
|
|
35
|
+
};
|
|
36
|
+
const result = await tool.run(finalInput, context);
|
|
37
|
+
if (currentExecutionId === executionIdRef.current) {
|
|
38
|
+
setData(result);
|
|
39
|
+
setExecuted(true);
|
|
40
|
+
optionsRef.current.onSuccess?.(result);
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
} catch (err) {
|
|
44
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
45
|
+
if (currentExecutionId === executionIdRef.current) {
|
|
46
|
+
setError(error2);
|
|
47
|
+
optionsRef.current.onError?.(error2);
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
} finally {
|
|
51
|
+
pendingCountRef.current--;
|
|
52
|
+
if (pendingCountRef.current === 0) {
|
|
53
|
+
setLoading(false);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
[tool]
|
|
58
|
+
);
|
|
59
|
+
const reset = useCallback(() => {
|
|
60
|
+
setData(null);
|
|
61
|
+
setError(null);
|
|
62
|
+
setLoading(false);
|
|
63
|
+
setExecuted(false);
|
|
64
|
+
}, []);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (options.auto && initialInput !== void 0) {
|
|
67
|
+
inputRef.current = initialInput;
|
|
68
|
+
execute(initialInput);
|
|
69
|
+
}
|
|
70
|
+
}, [options.auto, initialInput, execute]);
|
|
71
|
+
return {
|
|
72
|
+
data,
|
|
73
|
+
loading,
|
|
74
|
+
error,
|
|
75
|
+
execute,
|
|
76
|
+
reset,
|
|
77
|
+
executed
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function useTools(tools, options = {}) {
|
|
81
|
+
const toolsRef = useRef(tools);
|
|
82
|
+
const optionsRef = useRef(options);
|
|
83
|
+
optionsRef.current = options;
|
|
84
|
+
if (process.env.NODE_ENV !== "production") {
|
|
85
|
+
const prevKeys = Object.keys(toolsRef.current);
|
|
86
|
+
const currKeys = Object.keys(tools);
|
|
87
|
+
if (prevKeys.length !== currKeys.length || !currKeys.every((k) => prevKeys.includes(k))) {
|
|
88
|
+
console.warn(
|
|
89
|
+
"useTools: The tools object keys changed between renders. This may cause unexpected behavior. Define tools outside the component or memoize with useMemo."
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
toolsRef.current = tools;
|
|
93
|
+
}
|
|
94
|
+
const [state, setState] = useState(() => {
|
|
95
|
+
const initial = {};
|
|
96
|
+
for (const name of Object.keys(tools)) {
|
|
97
|
+
initial[name] = {
|
|
98
|
+
data: null,
|
|
99
|
+
loading: false,
|
|
100
|
+
error: null,
|
|
101
|
+
executed: false
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return initial;
|
|
105
|
+
});
|
|
106
|
+
const createExecute = useCallback(
|
|
107
|
+
(toolName, tool) => {
|
|
108
|
+
return async (input) => {
|
|
109
|
+
if (input === void 0) {
|
|
110
|
+
const err = new Error("No input provided to tool");
|
|
111
|
+
setState((prev) => ({
|
|
112
|
+
...prev,
|
|
113
|
+
[toolName]: { ...prev[toolName], error: err }
|
|
114
|
+
}));
|
|
115
|
+
optionsRef.current.onError?.(err);
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
setState((prev) => ({
|
|
119
|
+
...prev,
|
|
120
|
+
[toolName]: { ...prev[toolName], loading: true, error: null }
|
|
121
|
+
}));
|
|
122
|
+
try {
|
|
123
|
+
const context = {
|
|
124
|
+
cache: /* @__PURE__ */ new Map(),
|
|
125
|
+
fetch: globalThis.fetch?.bind(globalThis),
|
|
126
|
+
isServer: false,
|
|
127
|
+
...optionsRef.current.context
|
|
128
|
+
};
|
|
129
|
+
const result = await tool.run(input, context);
|
|
130
|
+
setState((prev) => ({
|
|
131
|
+
...prev,
|
|
132
|
+
[toolName]: {
|
|
133
|
+
data: result,
|
|
134
|
+
loading: false,
|
|
135
|
+
error: null,
|
|
136
|
+
executed: true
|
|
137
|
+
}
|
|
138
|
+
}));
|
|
139
|
+
optionsRef.current.onSuccess?.(result);
|
|
140
|
+
return result;
|
|
141
|
+
} catch (err) {
|
|
142
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
143
|
+
setState((prev) => ({
|
|
144
|
+
...prev,
|
|
145
|
+
[toolName]: { ...prev[toolName], loading: false, error }
|
|
146
|
+
}));
|
|
147
|
+
optionsRef.current.onError?.(error);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
[]
|
|
153
|
+
);
|
|
154
|
+
const createReset = useCallback((toolName) => {
|
|
155
|
+
return () => {
|
|
156
|
+
setState((prev) => ({
|
|
157
|
+
...prev,
|
|
158
|
+
[toolName]: {
|
|
159
|
+
data: null,
|
|
160
|
+
loading: false,
|
|
161
|
+
error: null,
|
|
162
|
+
executed: false
|
|
163
|
+
}
|
|
164
|
+
}));
|
|
165
|
+
};
|
|
166
|
+
}, []);
|
|
167
|
+
const results = {};
|
|
168
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
169
|
+
const toolName = name;
|
|
170
|
+
const toolState = state[toolName];
|
|
171
|
+
results[toolName] = {
|
|
172
|
+
data: toolState?.data ?? null,
|
|
173
|
+
loading: toolState?.loading ?? false,
|
|
174
|
+
error: toolState?.error ?? null,
|
|
175
|
+
executed: toolState?.executed ?? false,
|
|
176
|
+
execute: createExecute(toolName, tool),
|
|
177
|
+
reset: createReset(toolName)
|
|
178
|
+
// Per-key type safety is enforced by the function's return type annotation.
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return results;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/react/useToolStream.ts
|
|
185
|
+
import { useState as useState2, useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
186
|
+
function useToolStream(tool, options = {}) {
|
|
187
|
+
const [data, setData] = useState2(null);
|
|
188
|
+
const [finalData, setFinalData] = useState2(null);
|
|
189
|
+
const [streaming, setStreaming] = useState2(false);
|
|
190
|
+
const [loading, setLoading] = useState2(false);
|
|
191
|
+
const [error, setError] = useState2(null);
|
|
192
|
+
const executionIdRef = useRef2(0);
|
|
193
|
+
const optionsRef = useRef2(options);
|
|
194
|
+
optionsRef.current = options;
|
|
195
|
+
const execute = useCallback2(
|
|
196
|
+
async (input) => {
|
|
197
|
+
const currentId = ++executionIdRef.current;
|
|
198
|
+
setLoading(true);
|
|
199
|
+
setStreaming(false);
|
|
200
|
+
setError(null);
|
|
201
|
+
setData(null);
|
|
202
|
+
setFinalData(null);
|
|
203
|
+
try {
|
|
204
|
+
let accumulated = {};
|
|
205
|
+
let result = null;
|
|
206
|
+
let firstChunkReceived = false;
|
|
207
|
+
for await (const chunk of tool.runStream(input, {
|
|
208
|
+
isServer: false,
|
|
209
|
+
...optionsRef.current.context
|
|
210
|
+
})) {
|
|
211
|
+
if (currentId !== executionIdRef.current) return null;
|
|
212
|
+
if (chunk.done) {
|
|
213
|
+
result = chunk.partial;
|
|
214
|
+
setFinalData(result);
|
|
215
|
+
setData(result);
|
|
216
|
+
setStreaming(false);
|
|
217
|
+
setLoading(false);
|
|
218
|
+
optionsRef.current.onSuccess?.(result);
|
|
219
|
+
} else {
|
|
220
|
+
if (!firstChunkReceived) {
|
|
221
|
+
firstChunkReceived = true;
|
|
222
|
+
setStreaming(true);
|
|
223
|
+
setLoading(false);
|
|
224
|
+
}
|
|
225
|
+
accumulated = { ...accumulated, ...chunk.partial };
|
|
226
|
+
setData({ ...accumulated });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
setLoading(false);
|
|
230
|
+
setStreaming(false);
|
|
231
|
+
return result;
|
|
232
|
+
} catch (err) {
|
|
233
|
+
if (currentId !== executionIdRef.current) return null;
|
|
234
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
235
|
+
setError(e);
|
|
236
|
+
setLoading(false);
|
|
237
|
+
setStreaming(false);
|
|
238
|
+
optionsRef.current.onError?.(e);
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
[tool]
|
|
243
|
+
);
|
|
244
|
+
const reset = useCallback2(() => {
|
|
245
|
+
setData(null);
|
|
246
|
+
setFinalData(null);
|
|
247
|
+
setStreaming(false);
|
|
248
|
+
setLoading(false);
|
|
249
|
+
setError(null);
|
|
250
|
+
}, []);
|
|
251
|
+
return { data, finalData, streaming, loading, error, execute, reset };
|
|
252
|
+
}
|
|
253
|
+
export {
|
|
254
|
+
useTool,
|
|
255
|
+
useToolStream,
|
|
256
|
+
useTools
|
|
257
|
+
};
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import React, { ReactElement } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Better UI v2 - Tool Definition
|
|
6
|
+
*
|
|
7
|
+
* Clean, type-safe tool definition inspired by TanStack AI,
|
|
8
|
+
* with Better UI's unique view integration.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Behavioral hints for tools */
|
|
12
|
+
interface ToolHints {
|
|
13
|
+
/** Tool performs destructive/irreversible actions (auto-implies requiresConfirmation) */
|
|
14
|
+
destructive?: boolean;
|
|
15
|
+
/** Tool only reads data, never modifies state */
|
|
16
|
+
readOnly?: boolean;
|
|
17
|
+
/** Tool can be safely retried without side effects */
|
|
18
|
+
idempotent?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** Entry stored in the tool context cache */
|
|
21
|
+
interface CacheEntry {
|
|
22
|
+
data: unknown;
|
|
23
|
+
expiry: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Context passed to tool handlers
|
|
27
|
+
*
|
|
28
|
+
* SECURITY NOTE:
|
|
29
|
+
* - Server-only fields (env, headers, cookies, user, session) are automatically
|
|
30
|
+
* stripped when running on the client to prevent accidental leakage.
|
|
31
|
+
* - Server handlers NEVER run on the client - they auto-fetch via API instead.
|
|
32
|
+
* - Never store secrets in tool output - it gets sent to the client.
|
|
33
|
+
*/
|
|
34
|
+
interface ToolContext {
|
|
35
|
+
/** Shared cache for deduplication */
|
|
36
|
+
cache: Map<string, CacheEntry>;
|
|
37
|
+
/** Fetch function (can be customized for auth headers, etc.) */
|
|
38
|
+
fetch: typeof fetch;
|
|
39
|
+
/** Whether running on server */
|
|
40
|
+
isServer: boolean;
|
|
41
|
+
/** Environment variables - NEVER available on client */
|
|
42
|
+
env?: Record<string, string>;
|
|
43
|
+
/** Request headers - NEVER available on client */
|
|
44
|
+
headers?: Headers;
|
|
45
|
+
/** Cookies - NEVER available on client */
|
|
46
|
+
cookies?: Record<string, string>;
|
|
47
|
+
/** Authenticated user - NEVER available on client */
|
|
48
|
+
user?: Record<string, unknown>;
|
|
49
|
+
/** Session data - NEVER available on client */
|
|
50
|
+
session?: Record<string, unknown>;
|
|
51
|
+
/** Optimistic update function */
|
|
52
|
+
optimistic?: <T>(data: Partial<T>) => void;
|
|
53
|
+
}
|
|
54
|
+
interface CacheConfig<TInput> {
|
|
55
|
+
ttl: number;
|
|
56
|
+
key?: (input: TInput) => string;
|
|
57
|
+
}
|
|
58
|
+
/** Configuration for auto-fetch behavior when no client handler is defined */
|
|
59
|
+
interface ClientFetchConfig {
|
|
60
|
+
/** API endpoint for tool execution (default: '/api/tools/execute') */
|
|
61
|
+
endpoint?: string;
|
|
62
|
+
}
|
|
63
|
+
interface ToolConfig<TInput, TOutput> {
|
|
64
|
+
name: string;
|
|
65
|
+
description?: string;
|
|
66
|
+
input: z.ZodType<TInput>;
|
|
67
|
+
output?: z.ZodType<TOutput>;
|
|
68
|
+
tags?: string[];
|
|
69
|
+
cache?: CacheConfig<TInput>;
|
|
70
|
+
/** Configure auto-fetch behavior for client-side execution */
|
|
71
|
+
clientFetch?: ClientFetchConfig;
|
|
72
|
+
/** If true or returns true, tool requires human confirmation before executing (HITL).
|
|
73
|
+
* When a function, it receives the input and can conditionally require confirmation. */
|
|
74
|
+
confirm?: boolean | ((input: TInput) => boolean);
|
|
75
|
+
/** Behavioral hints for the tool */
|
|
76
|
+
hints?: ToolHints;
|
|
77
|
+
/** Group related tool calls by a key derived from input.
|
|
78
|
+
* Calls with the same groupKey are collapsed in the thread — only the latest renders fully. */
|
|
79
|
+
groupKey?: (input: TInput) => string;
|
|
80
|
+
/** When true, a user UI action on this tool automatically sends the updated state
|
|
81
|
+
* to the AI so it can continue without the user typing a message. */
|
|
82
|
+
autoRespond?: boolean;
|
|
83
|
+
}
|
|
84
|
+
type ServerHandler<TInput, TOutput> = (input: TInput, ctx: ToolContext) => Promise<TOutput> | TOutput;
|
|
85
|
+
type ClientHandler<TInput, TOutput> = (input: TInput, ctx: ToolContext) => Promise<TOutput> | TOutput;
|
|
86
|
+
type StreamCallback<TOutput> = (partial: Partial<TOutput>) => void;
|
|
87
|
+
type StreamHandler<TInput, TOutput> = (input: TInput, ctx: ToolContext & {
|
|
88
|
+
/** Send a partial update to the view */
|
|
89
|
+
stream: StreamCallback<TOutput>;
|
|
90
|
+
}) => Promise<TOutput>;
|
|
91
|
+
type ViewState<TInput = unknown> = {
|
|
92
|
+
loading?: boolean;
|
|
93
|
+
/** True while receiving partial streaming updates */
|
|
94
|
+
streaming?: boolean;
|
|
95
|
+
error?: Error | null;
|
|
96
|
+
onAction?: (input: TInput) => void | Promise<void>;
|
|
97
|
+
};
|
|
98
|
+
type ViewComponent<TOutput, TInput = unknown> = (data: TOutput, state?: ViewState<TInput>) => ReactElement | null;
|
|
99
|
+
declare class Tool<TInput = any, TOutput = any> {
|
|
100
|
+
readonly name: string;
|
|
101
|
+
readonly description?: string;
|
|
102
|
+
readonly inputSchema: z.ZodType<TInput>;
|
|
103
|
+
readonly outputSchema?: z.ZodType<TOutput>;
|
|
104
|
+
readonly tags: string[];
|
|
105
|
+
readonly cacheConfig?: CacheConfig<TInput>;
|
|
106
|
+
readonly clientFetchConfig?: ClientFetchConfig;
|
|
107
|
+
readonly confirm: boolean | ((input: TInput) => boolean);
|
|
108
|
+
readonly hints: ToolHints;
|
|
109
|
+
readonly groupKey?: (input: TInput) => string;
|
|
110
|
+
readonly autoRespond: boolean;
|
|
111
|
+
private _server?;
|
|
112
|
+
private _client?;
|
|
113
|
+
private _view?;
|
|
114
|
+
private _stream?;
|
|
115
|
+
constructor(config: ToolConfig<TInput, TOutput>);
|
|
116
|
+
/**
|
|
117
|
+
* Define server-side implementation
|
|
118
|
+
* Runs on server (API routes, server components, etc.)
|
|
119
|
+
*/
|
|
120
|
+
server(handler: ServerHandler<TInput, TOutput>): this;
|
|
121
|
+
/**
|
|
122
|
+
* Define client-side implementation
|
|
123
|
+
* Runs in browser. If not specified, auto-fetches to /api/tools/{name}
|
|
124
|
+
*/
|
|
125
|
+
client(handler: ClientHandler<TInput, TOutput>): this;
|
|
126
|
+
/**
|
|
127
|
+
* Define view component for rendering results
|
|
128
|
+
* Our differentiator from TanStack AI
|
|
129
|
+
*/
|
|
130
|
+
view(component: ViewComponent<TOutput, TInput>): this;
|
|
131
|
+
/**
|
|
132
|
+
* Define streaming implementation
|
|
133
|
+
* The handler receives a `stream` callback to push partial updates.
|
|
134
|
+
*/
|
|
135
|
+
stream(handler: StreamHandler<TInput, TOutput>): this;
|
|
136
|
+
/**
|
|
137
|
+
* Execute the tool
|
|
138
|
+
* Automatically uses server or client handler based on environment
|
|
139
|
+
*
|
|
140
|
+
* SECURITY: Server handlers only run on server. Client automatically
|
|
141
|
+
* fetches from /api/tools/execute if no client handler is defined.
|
|
142
|
+
*/
|
|
143
|
+
run(input: TInput, ctx?: Partial<ToolContext>): Promise<TOutput>;
|
|
144
|
+
/**
|
|
145
|
+
* Make the tool callable directly: await weather({ city: 'London' })
|
|
146
|
+
*/
|
|
147
|
+
call(input: TInput, ctx?: Partial<ToolContext>): Promise<TOutput>;
|
|
148
|
+
/**
|
|
149
|
+
* Execute with streaming - returns async generator of partial results.
|
|
150
|
+
* Falls back to run() if no stream handler is defined.
|
|
151
|
+
*/
|
|
152
|
+
runStream(input: TInput, ctx?: Partial<ToolContext>): AsyncGenerator<{
|
|
153
|
+
partial: Partial<TOutput>;
|
|
154
|
+
done: boolean;
|
|
155
|
+
}>;
|
|
156
|
+
/**
|
|
157
|
+
* Default client fetch when no .client() is defined
|
|
158
|
+
*
|
|
159
|
+
* SECURITY: This ensures server handlers never run on the client.
|
|
160
|
+
* The server-side /api/tools/execute endpoint handles execution safely.
|
|
161
|
+
*/
|
|
162
|
+
private _defaultClientFetch;
|
|
163
|
+
/**
|
|
164
|
+
* Render the tool's view component
|
|
165
|
+
* Memoized to prevent unnecessary re-renders when parent state changes
|
|
166
|
+
*/
|
|
167
|
+
View: React.NamedExoticComponent<{
|
|
168
|
+
data: TOutput | null;
|
|
169
|
+
loading?: boolean;
|
|
170
|
+
streaming?: boolean;
|
|
171
|
+
error?: Error | null;
|
|
172
|
+
onAction?: (input: TInput) => void | Promise<void>;
|
|
173
|
+
}>;
|
|
174
|
+
private _initView;
|
|
175
|
+
/**
|
|
176
|
+
* Check if tool has a view
|
|
177
|
+
*/
|
|
178
|
+
get hasView(): boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Check if tool has server implementation
|
|
181
|
+
*/
|
|
182
|
+
get hasServer(): boolean;
|
|
183
|
+
/**
|
|
184
|
+
* Check if tool has custom client implementation
|
|
185
|
+
*/
|
|
186
|
+
get hasClient(): boolean;
|
|
187
|
+
/**
|
|
188
|
+
* Check if tool has a streaming implementation
|
|
189
|
+
*/
|
|
190
|
+
get hasStream(): boolean;
|
|
191
|
+
/**
|
|
192
|
+
* Check if tool requires human confirmation before executing (HITL)
|
|
193
|
+
* Returns true if `confirm` is truthy (boolean true or a function) OR `hints.destructive: true`
|
|
194
|
+
*/
|
|
195
|
+
get requiresConfirmation(): boolean;
|
|
196
|
+
/**
|
|
197
|
+
* Determine if a specific input should trigger user confirmation.
|
|
198
|
+
* - If `confirm` is a function, calls it with input
|
|
199
|
+
* - If `confirm` is boolean, returns it
|
|
200
|
+
* - If `hints.destructive`, returns true
|
|
201
|
+
*/
|
|
202
|
+
shouldConfirm(input: TInput): boolean;
|
|
203
|
+
/**
|
|
204
|
+
* Get the entity group key for a given input.
|
|
205
|
+
* Returns `"toolName:groupKey(input)"` if groupKey is defined, otherwise undefined.
|
|
206
|
+
*/
|
|
207
|
+
getGroupKey(input: TInput): string | undefined;
|
|
208
|
+
/**
|
|
209
|
+
* Convert to plain object (for serialization)
|
|
210
|
+
*
|
|
211
|
+
* SECURITY: This intentionally excludes handlers and schemas to prevent
|
|
212
|
+
* accidental exposure of server logic or validation details.
|
|
213
|
+
*/
|
|
214
|
+
toJSON(): {
|
|
215
|
+
name: string;
|
|
216
|
+
description: string | undefined;
|
|
217
|
+
tags: string[];
|
|
218
|
+
hasServer: boolean;
|
|
219
|
+
hasClient: boolean;
|
|
220
|
+
hasView: boolean;
|
|
221
|
+
hasStream: boolean;
|
|
222
|
+
hasCache: boolean;
|
|
223
|
+
confirm: boolean;
|
|
224
|
+
hints: ToolHints;
|
|
225
|
+
requiresConfirmation: boolean;
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* Convert to AI SDK format (Vercel AI SDK v5 compatible)
|
|
229
|
+
*
|
|
230
|
+
* If `confirm` is true, the execute function is omitted so the AI SDK
|
|
231
|
+
* leaves the tool call at `state: 'input-available'`, enabling HITL
|
|
232
|
+
* confirmation on the client before execution.
|
|
233
|
+
*/
|
|
234
|
+
toAITool(): {
|
|
235
|
+
description: string;
|
|
236
|
+
inputSchema: z.ZodType<TInput, z.ZodTypeDef, TInput>;
|
|
237
|
+
execute?: undefined;
|
|
238
|
+
} | {
|
|
239
|
+
description: string;
|
|
240
|
+
inputSchema: z.ZodType<TInput, z.ZodTypeDef, TInput>;
|
|
241
|
+
execute: (input: TInput) => Promise<TOutput>;
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Create a new tool with object config
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* const weather = tool({
|
|
249
|
+
* name: 'weather',
|
|
250
|
+
* description: 'Get weather for a city',
|
|
251
|
+
* input: z.object({ city: z.string() }),
|
|
252
|
+
* output: z.object({ temp: z.number() }),
|
|
253
|
+
* });
|
|
254
|
+
*
|
|
255
|
+
* weather.server(async ({ city }) => {
|
|
256
|
+
* return { temp: await getTemp(city) };
|
|
257
|
+
* });
|
|
258
|
+
*/
|
|
259
|
+
declare function tool<TInput, TOutput = any>(config: ToolConfig<TInput, TOutput>): Tool<TInput, TOutput>;
|
|
260
|
+
/**
|
|
261
|
+
* Create a new tool with fluent builder
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* const weather = tool('weather')
|
|
265
|
+
* .description('Get weather for a city')
|
|
266
|
+
* .input(z.object({ city: z.string() }))
|
|
267
|
+
* .output(z.object({ temp: z.number() }))
|
|
268
|
+
* .server(async ({ city }) => ({ temp: 72 }));
|
|
269
|
+
*/
|
|
270
|
+
declare function tool(name: string): ToolBuilder;
|
|
271
|
+
declare class ToolBuilder<TInput = any, TOutput = any> {
|
|
272
|
+
private _name;
|
|
273
|
+
private _description?;
|
|
274
|
+
private _input?;
|
|
275
|
+
private _output?;
|
|
276
|
+
private _tags;
|
|
277
|
+
private _cache?;
|
|
278
|
+
private _clientFetch?;
|
|
279
|
+
private _confirm?;
|
|
280
|
+
private _hints?;
|
|
281
|
+
private _groupKey?;
|
|
282
|
+
private _autoRespond?;
|
|
283
|
+
private _serverHandler?;
|
|
284
|
+
private _clientHandler?;
|
|
285
|
+
private _viewComponent?;
|
|
286
|
+
private _streamHandler?;
|
|
287
|
+
constructor(name: string);
|
|
288
|
+
description(desc: string): this;
|
|
289
|
+
/**
|
|
290
|
+
* Define input schema - enables type inference for handlers
|
|
291
|
+
*
|
|
292
|
+
* NOTE: Uses type assertion internally. This is safe because:
|
|
293
|
+
* 1. The schema is stored and used correctly at runtime
|
|
294
|
+
* 2. The return type correctly reflects the new generic parameter
|
|
295
|
+
* 3. TypeScript doesn't support "this type mutation" in fluent builders
|
|
296
|
+
*/
|
|
297
|
+
input<T>(schema: z.ZodType<T>): ToolBuilder<T, TOutput>;
|
|
298
|
+
/**
|
|
299
|
+
* Define output schema - enables type inference for results
|
|
300
|
+
*/
|
|
301
|
+
output<O>(schema: z.ZodType<O>): ToolBuilder<TInput, O>;
|
|
302
|
+
tags(...tags: string[]): this;
|
|
303
|
+
cache(config: CacheConfig<TInput>): this;
|
|
304
|
+
/** Configure auto-fetch endpoint for client-side execution */
|
|
305
|
+
clientFetch(config: ClientFetchConfig): this;
|
|
306
|
+
/** Require human confirmation before executing (HITL) */
|
|
307
|
+
requireConfirm(value?: boolean | ((input: TInput) => boolean)): this;
|
|
308
|
+
/** Set a groupKey function for collapsing related tool calls */
|
|
309
|
+
groupBy(fn: (input: TInput) => string): this;
|
|
310
|
+
/** Auto-send updated state to AI after user interacts with this tool's UI */
|
|
311
|
+
autoRespondAfterAction(value?: boolean): this;
|
|
312
|
+
/** Set behavioral hints for the tool */
|
|
313
|
+
hints(hints: ToolHints): this;
|
|
314
|
+
server(handler: ServerHandler<TInput, TOutput>): this;
|
|
315
|
+
client(handler: ClientHandler<TInput, TOutput>): this;
|
|
316
|
+
stream(handler: StreamHandler<TInput, TOutput>): this;
|
|
317
|
+
view(component: ViewComponent<TOutput, TInput>): this;
|
|
318
|
+
/**
|
|
319
|
+
* Build the final Tool instance
|
|
320
|
+
*/
|
|
321
|
+
build(): Tool<TInput, TOutput>;
|
|
322
|
+
/**
|
|
323
|
+
* Auto-build when accessing Tool methods
|
|
324
|
+
*/
|
|
325
|
+
run(input: TInput, ctx?: Partial<ToolContext>): Promise<TOutput>;
|
|
326
|
+
runStream(input: TInput, ctx?: Partial<ToolContext>): AsyncGenerator<{
|
|
327
|
+
partial: Partial<TOutput>;
|
|
328
|
+
done: boolean;
|
|
329
|
+
}>;
|
|
330
|
+
get View(): React.NamedExoticComponent<{
|
|
331
|
+
data: TOutput | null;
|
|
332
|
+
loading?: boolean;
|
|
333
|
+
streaming?: boolean;
|
|
334
|
+
error?: Error | null;
|
|
335
|
+
onAction?: ((input: TInput) => void | Promise<void>) | undefined;
|
|
336
|
+
}>;
|
|
337
|
+
toJSON(): {
|
|
338
|
+
name: string;
|
|
339
|
+
description: string | undefined;
|
|
340
|
+
tags: string[];
|
|
341
|
+
hasServer: boolean;
|
|
342
|
+
hasClient: boolean;
|
|
343
|
+
hasView: boolean;
|
|
344
|
+
hasStream: boolean;
|
|
345
|
+
hasCache: boolean;
|
|
346
|
+
confirm: boolean;
|
|
347
|
+
hints: ToolHints;
|
|
348
|
+
requiresConfirmation: boolean;
|
|
349
|
+
};
|
|
350
|
+
toAITool(): {
|
|
351
|
+
description: string;
|
|
352
|
+
inputSchema: z.ZodType<TInput, z.ZodTypeDef, TInput>;
|
|
353
|
+
execute?: undefined;
|
|
354
|
+
} | {
|
|
355
|
+
description: string;
|
|
356
|
+
inputSchema: z.ZodType<TInput, z.ZodTypeDef, TInput>;
|
|
357
|
+
execute: (input: TInput) => Promise<TOutput>;
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export { type ClientHandler as C, type ServerHandler as S, Tool as T, type ViewComponent as V, ToolBuilder as a, type ToolConfig as b, type ToolContext as c, type StreamCallback as d, type StreamHandler as e, type ViewState as f, type CacheConfig as g, type ClientFetchConfig as h, tool as t };
|