Skip to content

Commit 63901db

Browse files
authored
Merge pull request #127 from dafthunk-com:feature/agent
feat: add tool calling
2 parents 0c9c7fd + 79faadc commit 63901db

95 files changed

Lines changed: 3105 additions & 59 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/api/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"devDependencies": {
2525
"@cloudflare/puppeteer": "^1.0.2",
2626
"@cloudflare/vitest-pool-workers": "^0.8.56",
27-
"@cloudflare/workers-types": "^4.20250724.0",
27+
"@cloudflare/workers-types": "^4.20250726.0",
2828
"@eslint/js": "^9.26.0",
2929
"@types/mailparser": "^3.4.6",
3030
"@types/node": "^22.15.3",
@@ -44,6 +44,7 @@
4444
"@aws-sdk/client-ses": "^3.812.0",
4545
"@cf-wasm/photon": "^0.1.31",
4646
"@cf-wasm/quickjs": "^0.0.6",
47+
"@cloudflare/ai-utils": "^1.0.1",
4748
"@dafthunk/types": "workspace:*",
4849
"@hono-rate-limiter/cloudflare": "^0.2.2",
4950
"@hono/oauth-providers": "^0.7.1",
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import {
2+
ToolDefinition,
3+
ToolProvider,
4+
ToolProviderConstructor,
5+
ToolReference,
6+
ToolResult,
7+
} from "./tool-types";
8+
9+
/**
10+
* Tool call record for tracking function calls made during execution
11+
*/
12+
export interface ToolCall {
13+
name: string;
14+
arguments: any;
15+
result: any;
16+
timestamp: number;
17+
}
18+
19+
/**
20+
* Tool call tracker for a specific execution context
21+
*/
22+
export class ToolCallTracker {
23+
private toolCalls: ToolCall[] = [];
24+
25+
/**
26+
* Wrap tool definitions to track calls when executed
27+
*/
28+
public wrapToolDefinitions(
29+
toolDefinitions: ToolDefinition[]
30+
): ToolDefinition[] {
31+
return toolDefinitions.map((toolDef) => ({
32+
...toolDef,
33+
function: this.wrapToolFunction(toolDef),
34+
}));
35+
}
36+
37+
/**
38+
* Get all tool calls made during this execution
39+
*/
40+
public getToolCalls(): ToolCall[] {
41+
return [...this.toolCalls];
42+
}
43+
44+
/**
45+
* Clear all tracked tool calls
46+
*/
47+
public clearToolCalls(): void {
48+
this.toolCalls = [];
49+
}
50+
51+
/**
52+
* Wrap a tool function to track when it's called
53+
*/
54+
private wrapToolFunction(
55+
toolDef: ToolDefinition
56+
): (args: any) => Promise<string> {
57+
return async (args: any): Promise<string> => {
58+
const startTime = Date.now();
59+
60+
try {
61+
const result = await toolDef.function(args);
62+
63+
// Record the tool call
64+
this.toolCalls.push({
65+
name: toolDef.name,
66+
arguments: args,
67+
result: result,
68+
timestamp: startTime,
69+
});
70+
71+
return result;
72+
} catch (error) {
73+
// Record failed tool calls too
74+
this.toolCalls.push({
75+
name: toolDef.name,
76+
arguments: args,
77+
result: {
78+
error: error instanceof Error ? error.message : "Unknown error",
79+
},
80+
timestamp: startTime,
81+
});
82+
83+
throw error;
84+
}
85+
};
86+
}
87+
}
88+
89+
/**
90+
* Base tool registry that manages different types of tool providers
91+
* and provides a unified interface for tool resolution and execution
92+
*/
93+
export abstract class BaseToolRegistry {
94+
protected providers: Map<string, ToolProvider> = new Map();
95+
96+
/**
97+
* Register a tool provider for a specific tool type
98+
*/
99+
public registerProvider(type: string, provider: ToolProvider): void {
100+
this.providers.set(type, provider);
101+
}
102+
103+
/**
104+
* Register a tool provider constructor that will be instantiated
105+
*/
106+
public registerProviderConstructor(
107+
type: string,
108+
ProviderConstructor: ToolProviderConstructor,
109+
...args: any[]
110+
): void {
111+
const provider = new ProviderConstructor(...args);
112+
this.registerProvider(type, provider);
113+
}
114+
115+
/**
116+
* Get tool definition for a tool reference
117+
*/
118+
public async getToolDefinition(
119+
toolRef: ToolReference
120+
): Promise<ToolDefinition> {
121+
const provider = this.providers.get(toolRef.type);
122+
if (!provider) {
123+
throw new Error(`No provider registered for tool type: ${toolRef.type}`);
124+
}
125+
return provider.getToolDefinition(toolRef.identifier);
126+
}
127+
128+
/**
129+
* Execute a tool with given parameters
130+
*/
131+
public async executeTool(
132+
toolRef: ToolReference,
133+
parameters: any
134+
): Promise<ToolResult> {
135+
const provider = this.providers.get(toolRef.type);
136+
if (!provider) {
137+
throw new Error(`No provider registered for tool type: ${toolRef.type}`);
138+
}
139+
return provider.executeTool(toolRef.identifier, parameters);
140+
}
141+
142+
/**
143+
* Get all tool definitions for multiple tool references
144+
*/
145+
public async getToolDefinitions(
146+
toolRefs: ToolReference[]
147+
): Promise<ToolDefinition[]> {
148+
return Promise.all(
149+
toolRefs.map((toolRef) => this.getToolDefinition(toolRef))
150+
);
151+
}
152+
153+
/**
154+
* Execute multiple tools with their respective parameters
155+
*/
156+
public async executeTools(
157+
executions: Array<{ toolRef: ToolReference; parameters: any }>
158+
): Promise<ToolResult[]> {
159+
return Promise.all(
160+
executions.map(({ toolRef, parameters }) =>
161+
this.executeTool(toolRef, parameters)
162+
)
163+
);
164+
}
165+
166+
/**
167+
* Check if a tool type is supported
168+
*/
169+
public hasProvider(type: string): boolean {
170+
return this.providers.has(type);
171+
}
172+
173+
/**
174+
* Get all registered tool types
175+
*/
176+
public getRegisteredTypes(): string[] {
177+
return Array.from(this.providers.keys());
178+
}
179+
180+
/**
181+
* Get all available tools from all providers (if they support listing)
182+
*/
183+
public async getAllAvailableTools(): Promise<
184+
Array<{ type: string; tools: ToolDefinition[] }>
185+
> {
186+
const results: Array<{ type: string; tools: ToolDefinition[] }> = [];
187+
188+
for (const [type, provider] of this.providers.entries()) {
189+
if (provider.listTools) {
190+
try {
191+
const tools = await provider.listTools();
192+
results.push({ type, tools });
193+
} catch (error) {
194+
console.warn(`Failed to list tools for provider ${type}:`, error);
195+
}
196+
}
197+
}
198+
199+
return results;
200+
}
201+
202+
/**
203+
* Abstract method for subclasses to initialize their providers
204+
*/
205+
protected abstract initializeProviders(): void;
206+
}

apps/api/src/nodes/browser/cloudflare-browser-content-node.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export class CloudflareBrowserContentNode extends ExecutableNode {
1717
tags: ["Browser"],
1818
icon: "globe",
1919
computeCost: 10,
20+
asTool: true,
2021
inputs: [
2122
{
2223
name: "url",

apps/api/src/nodes/browser/cloudflare-browser-json-node.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export class CloudflareBrowserJsonNode extends ExecutableNode {
1717
tags: ["Browser"],
1818
icon: "braces",
1919
computeCost: 10,
20+
asTool: true,
2021
inputs: [
2122
{
2223
name: "url",

apps/api/src/nodes/browser/cloudflare-browser-links-node.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export class CloudflareBrowserLinksNode extends ExecutableNode {
1717
tags: ["Browser"],
1818
icon: "link",
1919
computeCost: 10,
20+
asTool: true,
2021
inputs: [
2122
{
2223
name: "url",

apps/api/src/nodes/browser/cloudflare-browser-markdown-node.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export class CloudflareBrowserMarkdownNode extends ExecutableNode {
1717
tags: ["Browser"],
1818
icon: "markdown",
1919
computeCost: 10,
20+
asTool: true,
2021
inputs: [
2122
{
2223
name: "url",

apps/api/src/nodes/browser/cloudflare-browser-scrape-node.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export class CloudflareBrowserScrapeNode extends ExecutableNode {
1717
tags: ["Browser"],
1818
icon: "search",
1919
computeCost: 10,
20+
asTool: true,
2021
inputs: [
2122
{
2223
name: "url",

apps/api/src/nodes/cloudflare-node-registry.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ import { SectorNode } from "./geo/sector-node";
102102
import { ShortestPathNode } from "./geo/shortest-path-node";
103103
import { SimplifyNode } from "./geo/simplify-node";
104104
import { SquareNode } from "./geo/square-node";
105+
import { ToGeoJsonNode } from "./geo/to-geojson-node";
105106
import { TransformRotateNode } from "./geo/transform-rotate-node";
106107
import { TransformScaleNode } from "./geo/transform-scale-node";
107108
import { TransformTranslateNode } from "./geo/transform-translate-node";
@@ -186,10 +187,13 @@ import { BartLargeCnnNode } from "./text/bart-large-cnn-node";
186187
import { BgeRerankerBaseNode } from "./text/bge-reranker-base-node";
187188
import { DeepseekR1DistillQwen32BNode } from "./text/deepseek-r1-distill-qwen-32b-node";
188189
import { DistilbertSst2Int8Node } from "./text/distilbert-sst-2-int8-node";
190+
import { Hermes2ProMistral7BNode } from "./text/hermes-2-pro-mistral-7b-node";
189191
import { InputTextNode } from "./text/input-text-node";
190192
import { Llama318BInstructFastNode } from "./text/llama-3-1-8b-instruct-fast-node";
191193
import { Llama3370BInstructFastNode } from "./text/llama-3-3-70b-instruct-fp8-fast-node";
194+
import { Llama4Scout17B16EInstructNode } from "./text/llama-4-scout-17b-16e-instruct-node";
192195
import { M2m10012bNode } from "./text/m2m100-1-2b-node";
196+
import { MistralSmall31_24BInstructNode } from "./text/mistral-small-3-1-24b-instruct-node";
193197
import { MultiVariableStringTemplateNode } from "./text/multi-variable-string-template-node";
194198
import { RegexExtractNode } from "./text/regex-extract-node";
195199
import { RegexMatchNode } from "./text/regex-match-node";
@@ -206,6 +210,7 @@ import { StringToLowerCaseNode } from "./text/string-to-lower-case-node";
206210
import { StringToUpperCaseNode } from "./text/string-to-upper-case-node";
207211
import { StringTrimNode } from "./text/string-trim-node";
208212
import { TextAreaNode } from "./text/text-area-node";
213+
import { ToJsonNode } from "./text/to-json-node";
209214
import { ToStringNode } from "./text/to-string-node";
210215
import { TwilioSmsNode } from "./text/twilio-sms-node";
211216

@@ -261,6 +266,12 @@ export class CloudflareNodeRegistry extends BaseNodeRegistry {
261266
this.registerImplementation(Llama318BInstructFastNode);
262267
this.registerImplementation(Llama3370BInstructFastNode);
263268
this.registerImplementation(DeepseekR1DistillQwen32BNode);
269+
this.registerImplementation(Hermes2ProMistral7BNode);
270+
this.registerImplementation(Llama4Scout17B16EInstructNode);
271+
this.registerImplementation(MistralSmall31_24BInstructNode);
272+
this.registerImplementation(Hermes2ProMistral7BNode);
273+
this.registerImplementation(Llama4Scout17B16EInstructNode);
274+
this.registerImplementation(MistralSmall31_24BInstructNode);
264275
this.registerImplementation(WhisperNode);
265276
this.registerImplementation(WhisperLargeV3TurboNode);
266277
this.registerImplementation(WhisperTinyEnNode);
@@ -308,6 +319,7 @@ export class CloudflareNodeRegistry extends BaseNodeRegistry {
308319
this.registerImplementation(RegexExtractNode);
309320
this.registerImplementation(RegexReplaceNode);
310321
this.registerImplementation(RegexSplitNode);
322+
this.registerImplementation(ToJsonNode);
311323
this.registerImplementation(ConditionalForkNode);
312324
this.registerImplementation(ConditionalJoinNode);
313325

@@ -463,6 +475,7 @@ export class CloudflareNodeRegistry extends BaseNodeRegistry {
463475
this.registerImplementation(ShortestPathNode);
464476
this.registerImplementation(SimplifyNode);
465477
this.registerImplementation(SquareNode);
478+
this.registerImplementation(ToGeoJsonNode);
466479
this.registerImplementation(TransformRotateNode);
467480
this.registerImplementation(TransformScaleNode);
468481
this.registerImplementation(TransformTranslateNode);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { BaseNodeRegistry } from "./base-node-registry";
2+
import { BaseToolRegistry } from "./base-tool-registry";
3+
import { NodeToolProvider } from "./node-tool-provider";
4+
import { NodeContext } from "./types";
5+
6+
/**
7+
* Cloudflare-specific tool registry implementation
8+
*/
9+
export class CloudflareToolRegistry extends BaseToolRegistry {
10+
constructor(
11+
private nodeRegistry: BaseNodeRegistry,
12+
private createNodeContextFn: (
13+
nodeId: string,
14+
inputs: Record<string, any>
15+
) => NodeContext
16+
) {
17+
super();
18+
this.initializeProviders();
19+
}
20+
21+
protected initializeProviders(): void {
22+
// Register the node tool provider for "node" type tools
23+
const nodeToolProvider = new NodeToolProvider(
24+
this.nodeRegistry,
25+
this.createNodeContextFn
26+
);
27+
28+
this.registerProvider("node", nodeToolProvider);
29+
}
30+
}

apps/api/src/nodes/email/parse-email-node.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export class ParseEmailNode extends ExecutableNode {
3131
"Parses raw email content and extracts key fields like subject, body, sender, recipients, and attachments.",
3232
tags: ["Email"],
3333
icon: "mail",
34+
asTool: true,
3435
compatibility: ["email_message"],
3536
inlinable: true,
3637
inputs: [

0 commit comments

Comments
 (0)