iris-chatbot 0.2.4

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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +49 -0
  3. package/bin/iris.mjs +267 -0
  4. package/package.json +61 -0
  5. package/template/LICENSE +21 -0
  6. package/template/README.md +49 -0
  7. package/template/eslint.config.mjs +18 -0
  8. package/template/next.config.ts +7 -0
  9. package/template/package-lock.json +9193 -0
  10. package/template/package.json +46 -0
  11. package/template/postcss.config.mjs +7 -0
  12. package/template/public/file.svg +1 -0
  13. package/template/public/globe.svg +1 -0
  14. package/template/public/next.svg +1 -0
  15. package/template/public/vercel.svg +1 -0
  16. package/template/public/window.svg +1 -0
  17. package/template/src/app/api/chat/route.ts +2445 -0
  18. package/template/src/app/api/connections/models/route.ts +255 -0
  19. package/template/src/app/api/connections/test/route.ts +124 -0
  20. package/template/src/app/api/local-sync/route.ts +74 -0
  21. package/template/src/app/api/tool-approval/route.ts +47 -0
  22. package/template/src/app/favicon.ico +0 -0
  23. package/template/src/app/globals.css +808 -0
  24. package/template/src/app/layout.tsx +74 -0
  25. package/template/src/app/page.tsx +444 -0
  26. package/template/src/components/ChatView.tsx +1537 -0
  27. package/template/src/components/Composer.tsx +160 -0
  28. package/template/src/components/MapView.tsx +244 -0
  29. package/template/src/components/MessageCard.tsx +955 -0
  30. package/template/src/components/SearchModal.tsx +72 -0
  31. package/template/src/components/SettingsModal.tsx +1257 -0
  32. package/template/src/components/Sidebar.tsx +153 -0
  33. package/template/src/components/TopBar.tsx +164 -0
  34. package/template/src/lib/connections.ts +275 -0
  35. package/template/src/lib/data.ts +324 -0
  36. package/template/src/lib/db.ts +49 -0
  37. package/template/src/lib/hooks.ts +76 -0
  38. package/template/src/lib/local-sync.ts +192 -0
  39. package/template/src/lib/memory.ts +695 -0
  40. package/template/src/lib/model-presets.ts +251 -0
  41. package/template/src/lib/store.ts +36 -0
  42. package/template/src/lib/tooling/approvals.ts +78 -0
  43. package/template/src/lib/tooling/providers/anthropic.ts +155 -0
  44. package/template/src/lib/tooling/providers/ollama.ts +73 -0
  45. package/template/src/lib/tooling/providers/openai.ts +267 -0
  46. package/template/src/lib/tooling/providers/openai_compatible.ts +16 -0
  47. package/template/src/lib/tooling/providers/types.ts +44 -0
  48. package/template/src/lib/tooling/registry.ts +103 -0
  49. package/template/src/lib/tooling/runtime.ts +189 -0
  50. package/template/src/lib/tooling/safety.ts +165 -0
  51. package/template/src/lib/tooling/tools/apps.ts +108 -0
  52. package/template/src/lib/tooling/tools/apps_plus.ts +153 -0
  53. package/template/src/lib/tooling/tools/communication.ts +883 -0
  54. package/template/src/lib/tooling/tools/files.ts +395 -0
  55. package/template/src/lib/tooling/tools/music.ts +988 -0
  56. package/template/src/lib/tooling/tools/notes.ts +461 -0
  57. package/template/src/lib/tooling/tools/notes_plus.ts +294 -0
  58. package/template/src/lib/tooling/tools/numbers.ts +175 -0
  59. package/template/src/lib/tooling/tools/schedule.ts +579 -0
  60. package/template/src/lib/tooling/tools/system.ts +142 -0
  61. package/template/src/lib/tooling/tools/web.ts +212 -0
  62. package/template/src/lib/tooling/tools/workflow.ts +218 -0
  63. package/template/src/lib/tooling/types.ts +27 -0
  64. package/template/src/lib/types.ts +309 -0
  65. package/template/src/lib/utils.ts +108 -0
  66. package/template/tsconfig.json +34 -0
@@ -0,0 +1,142 @@
1
+ import { ensureMacOS } from "../safety";
2
+ import { runCommandSafe } from "../runtime";
3
+ import type { ToolDefinition, ToolExecutionContext } from "../types";
4
+
5
+ type SetVolumeInput = {
6
+ level?: number;
7
+ };
8
+
9
+ type OpenUrlInput = {
10
+ url?: string;
11
+ };
12
+
13
+ const APPLESCRIPT_TIMEOUT_MS = 20_000;
14
+
15
+ function asObject(input: unknown): Record<string, unknown> {
16
+ if (!input || typeof input !== "object") {
17
+ throw new Error("Tool input must be an object.");
18
+ }
19
+ return input as Record<string, unknown>;
20
+ }
21
+
22
+ function asNumber(input: unknown, field: string): number {
23
+ if (typeof input !== "number" || !Number.isFinite(input)) {
24
+ throw new Error(`Missing required numeric field: ${field}`);
25
+ }
26
+ return input;
27
+ }
28
+
29
+ function asString(input: unknown, field: string): string {
30
+ if (typeof input !== "string" || !input.trim()) {
31
+ throw new Error(`Missing required string field: ${field}`);
32
+ }
33
+ return input.trim();
34
+ }
35
+
36
+ function assertHttpUrl(input: string): string {
37
+ const url = new URL(input);
38
+ if (!["http:", "https:"].includes(url.protocol)) {
39
+ throw new Error("Only http/https URLs are supported.");
40
+ }
41
+ return url.toString();
42
+ }
43
+
44
+ async function runAppleScript(script: string, args: string[] = [], signal?: AbortSignal) {
45
+ const { stdout } = await runCommandSafe({
46
+ command: "osascript",
47
+ args: ["-e", script, ...args],
48
+ signal,
49
+ timeoutMs: APPLESCRIPT_TIMEOUT_MS,
50
+ });
51
+ return stdout;
52
+ }
53
+
54
+ async function runSystemSetVolume(input: unknown, context: ToolExecutionContext) {
55
+ ensureMacOS("System automation");
56
+ const payload = asObject(input) as SetVolumeInput;
57
+ const level = Math.max(0, Math.min(100, Math.round(asNumber(payload.level, "level"))));
58
+ if (context.localTools.dryRun) {
59
+ return { dryRun: true, action: "system_set_volume", level };
60
+ }
61
+ await runAppleScript(`set volume output volume ${level}`, [], context.signal);
62
+ return { level };
63
+ }
64
+
65
+ async function runSystemGetStatus(_: unknown, context: ToolExecutionContext) {
66
+ ensureMacOS("System automation");
67
+ const volumeText = await runAppleScript("output volume of (get volume settings)", [], context.signal);
68
+ const app = await runAppleScript('tell application "System Events" to get name of first process whose frontmost is true', [], context.signal);
69
+ const timeText = await runAppleScript("return (current date) as text", [], context.signal);
70
+
71
+ let battery: string | null = null;
72
+ try {
73
+ const { stdout } = await runCommandSafe({
74
+ command: "pmset",
75
+ args: ["-g", "batt"],
76
+ signal: context.signal,
77
+ timeoutMs: 2_000,
78
+ });
79
+ battery = stdout;
80
+ } catch {
81
+ battery = null;
82
+ }
83
+
84
+ return {
85
+ volume: Number(volumeText) || null,
86
+ frontmostApp: app || null,
87
+ time: timeText || null,
88
+ battery,
89
+ };
90
+ }
91
+
92
+ async function runSystemOpenUrl(input: unknown, context: ToolExecutionContext) {
93
+ const payload = asObject(input) as OpenUrlInput;
94
+ const url = assertHttpUrl(asString(payload.url, "url"));
95
+ if (context.localTools.dryRun) {
96
+ return { dryRun: true, action: "system_open_url", url };
97
+ }
98
+ await runCommandSafe({
99
+ command: "open",
100
+ args: [url],
101
+ signal: context.signal,
102
+ });
103
+ return { opened: true, url };
104
+ }
105
+
106
+ export const systemTools: ToolDefinition[] = [
107
+ {
108
+ name: "system_set_volume",
109
+ description: "Set macOS system output volume level.",
110
+ inputSchema: {
111
+ type: "object",
112
+ required: ["level"],
113
+ properties: {
114
+ level: { type: "number" },
115
+ },
116
+ additionalProperties: false,
117
+ },
118
+ risk: "system",
119
+ execute: runSystemSetVolume,
120
+ },
121
+ {
122
+ name: "system_get_status",
123
+ description: "Get basic system status: volume, active app, time, battery.",
124
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
125
+ risk: "read",
126
+ execute: runSystemGetStatus,
127
+ },
128
+ {
129
+ name: "system_open_url",
130
+ description: "Open an HTTP/HTTPS URL in the default browser.",
131
+ inputSchema: {
132
+ type: "object",
133
+ required: ["url"],
134
+ properties: {
135
+ url: { type: "string" },
136
+ },
137
+ additionalProperties: false,
138
+ },
139
+ risk: "system",
140
+ execute: runSystemOpenUrl,
141
+ },
142
+ ];
@@ -0,0 +1,212 @@
1
+ import { runCommandSafe, withRetry } from "../runtime";
2
+ import type { ToolDefinition, ToolExecutionContext } from "../types";
3
+
4
+ type WebSearchInput = {
5
+ query?: string;
6
+ maxResults?: number;
7
+ recency?: "day" | "week" | "month" | "any";
8
+ };
9
+
10
+ type WebOpenInput = {
11
+ url?: string;
12
+ mode?: "browser" | "extract";
13
+ maxChars?: number;
14
+ };
15
+
16
+ const SEARCH_CACHE_TTL_MS = 60_000;
17
+ const searchCache = new Map<
18
+ string,
19
+ {
20
+ createdAt: number;
21
+ results: Array<{ title: string; url: string; snippet: string; source: string; rank: number }>;
22
+ }
23
+ >();
24
+
25
+ function asObject(input: unknown): Record<string, unknown> {
26
+ if (!input || typeof input !== "object") {
27
+ throw new Error("Tool input must be an object.");
28
+ }
29
+ return input as Record<string, unknown>;
30
+ }
31
+
32
+ function asString(input: unknown, field: string): string {
33
+ if (typeof input !== "string" || !input.trim()) {
34
+ throw new Error(`Missing required string field: ${field}`);
35
+ }
36
+ return input.trim();
37
+ }
38
+
39
+ function asWebUrl(raw: string): string {
40
+ let url: URL;
41
+ try {
42
+ url = new URL(raw);
43
+ } catch {
44
+ throw new Error("Invalid URL.");
45
+ }
46
+ if (!["http:", "https:"].includes(url.protocol)) {
47
+ throw new Error("Only http/https URLs are supported.");
48
+ }
49
+ return url.toString();
50
+ }
51
+
52
+ function stripHtml(value: string): string {
53
+ return value
54
+ .replace(/<script[\s\S]*?<\/script>/gi, " ")
55
+ .replace(/<style[\s\S]*?<\/style>/gi, " ")
56
+ .replace(/<[^>]+>/g, " ")
57
+ .replace(/&nbsp;/gi, " ")
58
+ .replace(/&amp;/gi, "&")
59
+ .replace(/\s+/g, " ")
60
+ .trim();
61
+ }
62
+
63
+ function parseDuckDuckGoResults(html: string, maxResults: number) {
64
+ const results: Array<{ title: string; url: string; snippet: string; source: string; rank: number }> = [];
65
+ const regex =
66
+ /<a[^>]*class="[^"]*result__a[^"]*"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a[^>]*class="[^"]*result__snippet[^"]*"[^>]*>([\s\S]*?)<\/a>/gi;
67
+
68
+ let match: RegExpExecArray | null;
69
+ while ((match = regex.exec(html)) && results.length < maxResults) {
70
+ const href = match[1]?.trim();
71
+ const title = stripHtml(match[2] ?? "");
72
+ const snippet = stripHtml(match[3] ?? "");
73
+ if (!href || !title) {
74
+ continue;
75
+ }
76
+ results.push({
77
+ title,
78
+ url: href,
79
+ snippet,
80
+ source: "duckduckgo",
81
+ rank: results.length + 1,
82
+ });
83
+ }
84
+ return results;
85
+ }
86
+
87
+ async function runWebSearch(input: unknown, context: ToolExecutionContext) {
88
+ const payload = asObject(input) as WebSearchInput;
89
+ const query = asString(payload.query, "query");
90
+ const maxResults =
91
+ typeof payload.maxResults === "number" && Number.isFinite(payload.maxResults)
92
+ ? Math.max(1, Math.min(10, Math.floor(payload.maxResults)))
93
+ : 5;
94
+ const recency = payload.recency ?? "any";
95
+ const cacheKey = `${query}::${recency}::${maxResults}`;
96
+ const cached = searchCache.get(cacheKey);
97
+ if (cached && Date.now() - cached.createdAt <= SEARCH_CACHE_TTL_MS) {
98
+ return {
99
+ query,
100
+ recency,
101
+ count: cached.results.length,
102
+ results: cached.results,
103
+ cached: true,
104
+ };
105
+ }
106
+
107
+ const q = recency === "any" ? query : `${query} ${recency}`;
108
+ const target = `https://duckduckgo.com/html/?q=${encodeURIComponent(q)}`;
109
+
110
+ const html = await withRetry({
111
+ operation: async () => {
112
+ const response = await fetch(target, { signal: context.signal });
113
+ if (!response.ok) {
114
+ throw new Error(`Search request failed with ${response.status}`);
115
+ }
116
+ return response.text();
117
+ },
118
+ });
119
+
120
+ const results = parseDuckDuckGoResults(await html, maxResults);
121
+ if (results.length === 0) {
122
+ return {
123
+ query,
124
+ recency,
125
+ count: 0,
126
+ results: [],
127
+ message: "No results parsed from current backend.",
128
+ };
129
+ }
130
+ searchCache.set(cacheKey, {
131
+ createdAt: Date.now(),
132
+ results,
133
+ });
134
+ return {
135
+ query,
136
+ recency,
137
+ count: results.length,
138
+ results,
139
+ };
140
+ }
141
+
142
+ async function runWebOpen(input: unknown, context: ToolExecutionContext) {
143
+ const payload = asObject(input) as WebOpenInput;
144
+ const url = asWebUrl(asString(payload.url, "url"));
145
+ const mode = payload.mode === "browser" ? "browser" : "extract";
146
+ const maxChars =
147
+ typeof payload.maxChars === "number" && Number.isFinite(payload.maxChars)
148
+ ? Math.max(200, Math.min(20_000, Math.floor(payload.maxChars)))
149
+ : 6_000;
150
+
151
+ if (mode === "browser") {
152
+ await runCommandSafe({
153
+ command: "open",
154
+ args: [url],
155
+ signal: context.signal,
156
+ });
157
+ return { opened: true, mode, url };
158
+ }
159
+
160
+ const html = await withRetry({
161
+ operation: async () => {
162
+ const response = await fetch(url, { signal: context.signal });
163
+ if (!response.ok) {
164
+ throw new Error(`Open request failed with ${response.status}`);
165
+ }
166
+ return response.text();
167
+ },
168
+ });
169
+
170
+ const text = stripHtml(await html).slice(0, maxChars);
171
+ return {
172
+ mode,
173
+ url,
174
+ extractedChars: text.length,
175
+ text,
176
+ };
177
+ }
178
+
179
+ export const webTools: ToolDefinition[] = [
180
+ {
181
+ name: "web_search",
182
+ description: "Search the web for a query and return top results.",
183
+ inputSchema: {
184
+ type: "object",
185
+ required: ["query"],
186
+ properties: {
187
+ query: { type: "string" },
188
+ maxResults: { type: "number" },
189
+ recency: { type: "string", enum: ["day", "week", "month", "any"] },
190
+ },
191
+ additionalProperties: false,
192
+ },
193
+ risk: "external",
194
+ execute: runWebSearch,
195
+ },
196
+ {
197
+ name: "web_open",
198
+ description: "Open a URL in browser or fetch/extract readable text.",
199
+ inputSchema: {
200
+ type: "object",
201
+ required: ["url"],
202
+ properties: {
203
+ url: { type: "string" },
204
+ mode: { type: "string", enum: ["browser", "extract"] },
205
+ maxChars: { type: "number" },
206
+ },
207
+ additionalProperties: false,
208
+ },
209
+ risk: "external",
210
+ execute: runWebOpen,
211
+ },
212
+ ];
@@ -0,0 +1,218 @@
1
+ import { normalizeToolError, withRetry } from "../runtime";
2
+ import type { ToolDefinition, ToolExecutionContext } from "../types";
3
+
4
+ type WorkflowStep = {
5
+ id?: string;
6
+ tool?: string;
7
+ input?: Record<string, unknown>;
8
+ dependsOn?: string[];
9
+ continueOnError?: boolean;
10
+ };
11
+
12
+ type WorkflowInput = {
13
+ steps?: WorkflowStep[];
14
+ maxParallel?: number;
15
+ };
16
+
17
+ function asObject(input: unknown): Record<string, unknown> {
18
+ if (!input || typeof input !== "object") {
19
+ throw new Error("Tool input must be an object.");
20
+ }
21
+ return input as Record<string, unknown>;
22
+ }
23
+
24
+ function validateSteps(steps: WorkflowStep[]): Required<WorkflowStep>[] {
25
+ if (!Array.isArray(steps) || steps.length === 0) {
26
+ throw new Error("Missing required array field: steps");
27
+ }
28
+
29
+ const ids = new Set<string>();
30
+ return steps.map((step, index) => {
31
+ const id = typeof step.id === "string" && step.id.trim() ? step.id.trim() : `step_${index + 1}`;
32
+ const tool = typeof step.tool === "string" && step.tool.trim() ? step.tool.trim() : "";
33
+ if (!tool) {
34
+ throw new Error(`Missing required string field: steps[${index}].tool`);
35
+ }
36
+ if (ids.has(id)) {
37
+ throw new Error(`Duplicate workflow step id: ${id}`);
38
+ }
39
+ ids.add(id);
40
+
41
+ return {
42
+ id,
43
+ tool,
44
+ input: step.input ?? {},
45
+ dependsOn: Array.isArray(step.dependsOn) ? step.dependsOn : [],
46
+ continueOnError: step.continueOnError === true,
47
+ };
48
+ });
49
+ }
50
+
51
+ export function createWorkflowTool(getTools: () => ToolDefinition[]): ToolDefinition {
52
+ return {
53
+ name: "workflow_run",
54
+ description: "Run a multi-step tool workflow with dependencies.",
55
+ inputSchema: {
56
+ type: "object",
57
+ required: ["steps"],
58
+ properties: {
59
+ steps: {
60
+ type: "array",
61
+ items: {
62
+ type: "object",
63
+ required: ["id", "tool"],
64
+ properties: {
65
+ id: { type: "string" },
66
+ tool: { type: "string" },
67
+ input: { type: "object" },
68
+ dependsOn: { type: "array", items: { type: "string" } },
69
+ continueOnError: { type: "boolean" },
70
+ },
71
+ additionalProperties: false,
72
+ },
73
+ },
74
+ maxParallel: { type: "number" },
75
+ },
76
+ additionalProperties: false,
77
+ },
78
+ risk: "write",
79
+ async execute(input: unknown, context: ToolExecutionContext) {
80
+ const payload = asObject(input) as WorkflowInput;
81
+ const steps = validateSteps(payload.steps ?? []);
82
+ const maxParallel =
83
+ typeof payload.maxParallel === "number" && Number.isFinite(payload.maxParallel)
84
+ ? Math.max(1, Math.min(5, Math.floor(payload.maxParallel)))
85
+ : 3;
86
+
87
+ const tools = getTools();
88
+ const byId = new Map(steps.map((step) => [step.id, step]));
89
+ for (const step of steps) {
90
+ for (const depId of step.dependsOn) {
91
+ if (!byId.has(depId)) {
92
+ throw new Error(`Unknown dependency ${depId} for step ${step.id}`);
93
+ }
94
+ if (depId === step.id) {
95
+ throw new Error(`Step ${step.id} cannot depend on itself.`);
96
+ }
97
+ }
98
+ }
99
+
100
+ const results: Array<{
101
+ id: string;
102
+ tool: string;
103
+ ok: boolean;
104
+ output?: unknown;
105
+ error?: string;
106
+ retryable?: boolean;
107
+ durationMs: number;
108
+ }> = [];
109
+ const done = new Set<string>();
110
+ const failed = new Set<string>();
111
+ const running = new Set<string>();
112
+
113
+ const canRun = (step: Required<WorkflowStep>) =>
114
+ step.dependsOn.every((dep) => done.has(dep));
115
+
116
+ while (done.size + failed.size < steps.length) {
117
+ const runnable = steps.filter(
118
+ (step) =>
119
+ !done.has(step.id) &&
120
+ !failed.has(step.id) &&
121
+ !running.has(step.id) &&
122
+ canRun(step),
123
+ );
124
+
125
+ if (runnable.length === 0 && running.size === 0) {
126
+ const blocked = steps
127
+ .filter((step) => !done.has(step.id) && !failed.has(step.id))
128
+ .map((step) => step.id);
129
+ for (const stepId of blocked) {
130
+ failed.add(stepId);
131
+ results.push({
132
+ id: stepId,
133
+ tool: byId.get(stepId)?.tool ?? "unknown",
134
+ ok: false,
135
+ error: "Blocked by failed dependency.",
136
+ durationMs: 0,
137
+ });
138
+ }
139
+ break;
140
+ }
141
+
142
+ const batch = runnable.slice(0, Math.max(0, maxParallel - running.size));
143
+ await Promise.all(
144
+ batch.map(async (step) => {
145
+ running.add(step.id);
146
+ const startedAt = Date.now();
147
+ const tool = tools.find((candidate) => candidate.name === step.tool);
148
+ if (!tool || tool.name === "workflow_run") {
149
+ failed.add(step.id);
150
+ running.delete(step.id);
151
+ results.push({
152
+ id: step.id,
153
+ tool: step.tool,
154
+ ok: false,
155
+ error: tool ? "Nested workflow_run is not allowed." : `Unknown tool: ${step.tool}`,
156
+ durationMs: Date.now() - startedAt,
157
+ });
158
+ return;
159
+ }
160
+
161
+ try {
162
+ const output = await withRetry({
163
+ operation: async () => tool.execute(step.input, context),
164
+ maxRetries: 2,
165
+ });
166
+ done.add(step.id);
167
+ results.push({
168
+ id: step.id,
169
+ tool: step.tool,
170
+ ok: true,
171
+ output,
172
+ durationMs: Date.now() - startedAt,
173
+ });
174
+ } catch (error) {
175
+ const normalized = normalizeToolError(error);
176
+ failed.add(step.id);
177
+ results.push({
178
+ id: step.id,
179
+ tool: step.tool,
180
+ ok: false,
181
+ error: normalized.message,
182
+ retryable: normalized.retryable,
183
+ durationMs: Date.now() - startedAt,
184
+ });
185
+ if (!step.continueOnError) {
186
+ for (const downstream of steps.filter((candidate) =>
187
+ candidate.dependsOn.includes(step.id),
188
+ )) {
189
+ if (!done.has(downstream.id) && !failed.has(downstream.id)) {
190
+ failed.add(downstream.id);
191
+ results.push({
192
+ id: downstream.id,
193
+ tool: downstream.tool,
194
+ ok: false,
195
+ error: `Skipped because dependency ${step.id} failed.`,
196
+ durationMs: 0,
197
+ });
198
+ }
199
+ }
200
+ }
201
+ } finally {
202
+ running.delete(step.id);
203
+ }
204
+ }),
205
+ );
206
+ }
207
+
208
+ const successCount = results.filter((result) => result.ok).length;
209
+ const failCount = results.length - successCount;
210
+ return {
211
+ steps: results,
212
+ summary: failCount === 0
213
+ ? `Workflow completed: ${successCount}/${results.length} steps succeeded.`
214
+ : `Workflow finished with partial failures: ${successCount} succeeded, ${failCount} failed.`,
215
+ };
216
+ },
217
+ };
218
+ }
@@ -0,0 +1,27 @@
1
+ import type { LocalToolsSettings } from "../types";
2
+
3
+ export type ToolRisk =
4
+ | "read"
5
+ | "write"
6
+ | "destructive"
7
+ | "app"
8
+ | "external"
9
+ | "system";
10
+
11
+ export type ToolDefinition = {
12
+ name: string;
13
+ description: string;
14
+ inputSchema: Record<string, unknown>;
15
+ risk: ToolRisk;
16
+ execute: (input: unknown, context: ToolExecutionContext) => Promise<unknown>;
17
+ };
18
+
19
+ export type ToolExecutionContext = {
20
+ localTools: LocalToolsSettings;
21
+ signal?: AbortSignal;
22
+ };
23
+
24
+ export type ToolExecutionResult = {
25
+ ok: boolean;
26
+ output: unknown;
27
+ };