digital-tools 2.1.3 → 2.3.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/CHANGELOG.md +9 -0
- package/README.md +2 -0
- package/dist/client.d.ts +109 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +69 -0
- package/dist/client.js.map +1 -0
- package/dist/define.d.ts +2 -2
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js +21 -11
- package/dist/define.js.map +1 -1
- package/dist/function-ref.d.ts +229 -0
- package/dist/function-ref.d.ts.map +1 -0
- package/dist/function-ref.js +28 -0
- package/dist/function-ref.js.map +1 -0
- package/dist/function-sugar.d.ts +57 -0
- package/dist/function-sugar.d.ts.map +1 -0
- package/dist/function-sugar.js +79 -0
- package/dist/function-sugar.js.map +1 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -4
- package/dist/index.js.map +1 -1
- package/dist/providers/analytics/mixpanel.d.ts.map +1 -1
- package/dist/providers/analytics/mixpanel.js +21 -18
- package/dist/providers/analytics/mixpanel.js.map +1 -1
- package/dist/providers/calendar/cal-com.d.ts.map +1 -1
- package/dist/providers/calendar/cal-com.js +10 -10
- package/dist/providers/calendar/cal-com.js.map +1 -1
- package/dist/providers/calendar/google-calendar.d.ts.map +1 -1
- package/dist/providers/calendar/google-calendar.js +4 -4
- package/dist/providers/calendar/google-calendar.js.map +1 -1
- package/dist/providers/crm/hubspot.d.ts.map +1 -1
- package/dist/providers/crm/hubspot.js +107 -85
- package/dist/providers/crm/hubspot.js.map +1 -1
- package/dist/providers/development/github.d.ts.map +1 -1
- package/dist/providers/development/github.js +40 -43
- package/dist/providers/development/github.js.map +1 -1
- package/dist/providers/ecommerce/shopify.d.ts.map +1 -1
- package/dist/providers/ecommerce/shopify.js +79 -62
- package/dist/providers/ecommerce/shopify.js.map +1 -1
- package/dist/providers/email/resend.d.ts.map +1 -1
- package/dist/providers/email/resend.js +20 -16
- package/dist/providers/email/resend.js.map +1 -1
- package/dist/providers/email/sendgrid.d.ts.map +1 -1
- package/dist/providers/email/sendgrid.js +12 -9
- package/dist/providers/email/sendgrid.js.map +1 -1
- package/dist/providers/finance/stripe.d.ts.map +1 -1
- package/dist/providers/finance/stripe.js +44 -42
- package/dist/providers/finance/stripe.js.map +1 -1
- package/dist/providers/forms/typeform.d.ts.map +1 -1
- package/dist/providers/forms/typeform.js +68 -58
- package/dist/providers/forms/typeform.js.map +1 -1
- package/dist/providers/knowledge/notion.d.ts.map +1 -1
- package/dist/providers/knowledge/notion.js +75 -41
- package/dist/providers/knowledge/notion.js.map +1 -1
- package/dist/providers/marketing/mailchimp.d.ts.map +1 -1
- package/dist/providers/marketing/mailchimp.js +74 -61
- package/dist/providers/marketing/mailchimp.js.map +1 -1
- package/dist/providers/media/cloudinary.d.ts.map +1 -1
- package/dist/providers/media/cloudinary.js +30 -28
- package/dist/providers/media/cloudinary.js.map +1 -1
- package/dist/providers/messaging/slack.d.ts.map +1 -1
- package/dist/providers/messaging/slack.js +75 -58
- package/dist/providers/messaging/slack.js.map +1 -1
- package/dist/providers/messaging/twilio-sms.d.ts.map +1 -1
- package/dist/providers/messaging/twilio-sms.js +33 -15
- package/dist/providers/messaging/twilio-sms.js.map +1 -1
- package/dist/providers/project-management/linear.d.ts.map +1 -1
- package/dist/providers/project-management/linear.js +31 -27
- package/dist/providers/project-management/linear.js.map +1 -1
- package/dist/providers/spreadsheet/google-sheets.d.ts.map +1 -1
- package/dist/providers/spreadsheet/google-sheets.js +21 -18
- package/dist/providers/spreadsheet/google-sheets.js.map +1 -1
- package/dist/providers/spreadsheet/xlsx.d.ts.map +1 -1
- package/dist/providers/spreadsheet/xlsx.js +4 -4
- package/dist/providers/spreadsheet/xlsx.js.map +1 -1
- package/dist/providers/storage/index.js +1 -0
- package/dist/providers/storage/index.js.map +1 -1
- package/dist/providers/storage/s3.d.ts.map +1 -1
- package/dist/providers/storage/s3.js +36 -27
- package/dist/providers/storage/s3.js.map +1 -1
- package/dist/providers/support/zendesk.d.ts.map +1 -1
- package/dist/providers/support/zendesk.js +24 -25
- package/dist/providers/support/zendesk.js.map +1 -1
- package/dist/providers/tasks/todoist.d.ts.map +1 -1
- package/dist/providers/tasks/todoist.js +18 -18
- package/dist/providers/tasks/todoist.js.map +1 -1
- package/dist/providers/video-conferencing/google-meet.d.ts.map +1 -1
- package/dist/providers/video-conferencing/google-meet.js +11 -11
- package/dist/providers/video-conferencing/google-meet.js.map +1 -1
- package/dist/providers/video-conferencing/jitsi.js +14 -14
- package/dist/providers/video-conferencing/jitsi.js.map +1 -1
- package/dist/providers/video-conferencing/teams.d.ts.map +1 -1
- package/dist/providers/video-conferencing/teams.js +9 -7
- package/dist/providers/video-conferencing/teams.js.map +1 -1
- package/dist/providers/video-conferencing/zoom.d.ts.map +1 -1
- package/dist/providers/video-conferencing/zoom.js +26 -24
- package/dist/providers/video-conferencing/zoom.js.map +1 -1
- package/dist/tools/data.d.ts.map +1 -1
- package/dist/tools/data.js +5 -12
- package/dist/tools/data.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/system.d.ts +289 -0
- package/dist/tools/system.d.ts.map +1 -0
- package/dist/tools/system.js +752 -0
- package/dist/tools/system.js.map +1 -0
- package/dist/tools/web.d.ts.map +1 -1
- package/dist/tools/web.js +22 -10
- package/dist/tools/web.js.map +1 -1
- package/dist/track-record.d.ts +101 -0
- package/dist/track-record.d.ts.map +1 -0
- package/dist/track-record.js +17 -0
- package/dist/track-record.js.map +1 -0
- package/dist/types.d.ts +210 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/verb-registration.d.ts +122 -0
- package/dist/verb-registration.d.ts.map +1 -0
- package/dist/verb-registration.js +176 -0
- package/dist/verb-registration.js.map +1 -0
- package/dist/worker.d.ts +93 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +315 -0
- package/dist/worker.js.map +1 -0
- package/dist/wrap.d.ts +89 -0
- package/dist/wrap.d.ts.map +1 -0
- package/dist/wrap.js +225 -0
- package/dist/wrap.js.map +1 -0
- package/package.json +31 -14
- package/src/client.ts +136 -0
- package/src/define.ts +30 -24
- package/src/function-ref.ts +264 -0
- package/src/function-sugar.ts +134 -0
- package/src/index.ts +132 -10
- package/src/providers/analytics/mixpanel.ts +19 -18
- package/src/providers/calendar/cal-com.ts +29 -18
- package/src/providers/calendar/google-calendar.ts +20 -14
- package/src/providers/crm/hubspot.ts +225 -99
- package/src/providers/development/github.ts +206 -135
- package/src/providers/ecommerce/shopify.ts +250 -89
- package/src/providers/email/resend.ts +101 -28
- package/src/providers/email/sendgrid.ts +12 -9
- package/src/providers/finance/stripe.ts +128 -49
- package/src/providers/forms/typeform.ts +74 -58
- package/src/providers/knowledge/notion.ts +340 -88
- package/src/providers/marketing/mailchimp.ts +86 -70
- package/src/providers/media/cloudinary.ts +99 -41
- package/src/providers/messaging/slack.ts +283 -85
- package/src/providers/messaging/twilio-sms.ts +35 -15
- package/src/providers/project-management/linear.ts +143 -55
- package/src/providers/spreadsheet/google-sheets.ts +222 -56
- package/src/providers/spreadsheet/xlsx.ts +47 -16
- package/src/providers/storage/s3.ts +119 -47
- package/src/providers/support/zendesk.ts +196 -46
- package/src/providers/tasks/todoist.ts +20 -26
- package/src/providers/video-conferencing/google-meet.ts +17 -20
- package/src/providers/video-conferencing/jitsi.ts +14 -14
- package/src/providers/video-conferencing/teams.ts +14 -13
- package/src/providers/video-conferencing/zoom.ts +54 -49
- package/src/tools/data.ts +6 -16
- package/src/tools/index.ts +1 -0
- package/src/tools/system.ts +887 -0
- package/src/tools/web.ts +22 -10
- package/src/track-record.ts +106 -0
- package/src/types.ts +241 -13
- package/src/verb-registration.ts +197 -0
- package/src/worker.ts +370 -0
- package/src/wrap.ts +260 -0
- package/test/client.test.ts +146 -0
- package/test/communication-tools-extended.test.ts +734 -0
- package/test/data-tools-extended.test.ts +743 -0
- package/test/define-extended.test.ts +819 -0
- package/test/define.test.ts +150 -41
- package/test/entities.test.ts +623 -0
- package/test/extended-entities.test.ts +1228 -0
- package/test/provider-implementations.test.ts +725 -0
- package/test/provider-registry-extended.test.ts +583 -0
- package/test/providers/google-sheets.test.ts +851 -0
- package/test/providers/helpers.ts +554 -0
- package/test/providers/hubspot.test.ts +576 -0
- package/test/providers/slack.test.ts +932 -0
- package/test/providers/stripe.test.ts +701 -0
- package/test/providers.test.ts +578 -0
- package/test/system-tools-extended.test.ts +632 -0
- package/test/system.test.ts +673 -0
- package/test/tools.test.ts +15 -11
- package/test/types.test.ts +402 -0
- package/test/verb-registration.test.ts +395 -0
- package/test/web-tools.test.ts +553 -0
- package/test/worker-extended.test.ts +699 -0
- package/test/worker.test.ts +576 -0
- package/test/wrap.test.ts +366 -0
- package/tsconfig.json +3 -13
- package/vitest.config.ts +37 -0
- package/wrangler.jsonc +9 -0
- package/.turbo/turbo-build.log +0 -4
- package/LICENSE +0 -21
- package/dist/providers/voice/vapi.d.ts +0 -27
- package/dist/providers/voice/vapi.d.ts.map +0 -1
- package/dist/providers/voice/vapi.js +0 -440
- package/dist/providers/voice/vapi.js.map +0 -1
- package/src/define.js +0 -259
- package/src/entities/advertising.js +0 -999
- package/src/entities/ai.js +0 -756
- package/src/entities/analytics.js +0 -1588
- package/src/entities/automation.js +0 -601
- package/src/entities/communication.js +0 -1150
- package/src/entities/crm.js +0 -1386
- package/src/entities/design.js +0 -546
- package/src/entities/development.js +0 -2212
- package/src/entities/document.js +0 -874
- package/src/entities/ecommerce.js +0 -1429
- package/src/entities/experiment.js +0 -1039
- package/src/entities/finance.js +0 -3478
- package/src/entities/forms.js +0 -1892
- package/src/entities/hr.js +0 -661
- package/src/entities/identity.js +0 -997
- package/src/entities/index.js +0 -282
- package/src/entities/infrastructure.js +0 -1153
- package/src/entities/knowledge.js +0 -1438
- package/src/entities/marketing.js +0 -1610
- package/src/entities/media.js +0 -1634
- package/src/entities/notification.js +0 -1199
- package/src/entities/presentation.js +0 -1274
- package/src/entities/productivity.js +0 -1317
- package/src/entities/project-management.js +0 -1136
- package/src/entities/recruiting.js +0 -736
- package/src/entities/shipping.js +0 -509
- package/src/entities/signature.js +0 -1102
- package/src/entities/site.js +0 -222
- package/src/entities/spreadsheet.js +0 -1341
- package/src/entities/storage.js +0 -1198
- package/src/entities/support.js +0 -1166
- package/src/entities/video-conferencing.js +0 -1750
- package/src/entities/video.js +0 -950
- package/src/entities.js +0 -1663
- package/src/index.js +0 -74
- package/src/providers/analytics/index.js +0 -17
- package/src/providers/analytics/mixpanel.js +0 -255
- package/src/providers/calendar/cal-com.js +0 -303
- package/src/providers/calendar/google-calendar.js +0 -335
- package/src/providers/calendar/index.js +0 -20
- package/src/providers/crm/hubspot.js +0 -566
- package/src/providers/crm/index.js +0 -17
- package/src/providers/development/github.js +0 -472
- package/src/providers/development/index.js +0 -17
- package/src/providers/ecommerce/index.js +0 -17
- package/src/providers/ecommerce/shopify.js +0 -378
- package/src/providers/email/index.js +0 -20
- package/src/providers/email/resend.js +0 -258
- package/src/providers/email/sendgrid.js +0 -161
- package/src/providers/finance/index.js +0 -17
- package/src/providers/finance/stripe.js +0 -549
- package/src/providers/forms/index.js +0 -17
- package/src/providers/forms/typeform.js +0 -500
- package/src/providers/index.js +0 -123
- package/src/providers/knowledge/index.js +0 -17
- package/src/providers/knowledge/notion.js +0 -389
- package/src/providers/marketing/index.js +0 -17
- package/src/providers/marketing/mailchimp.js +0 -443
- package/src/providers/media/cloudinary.js +0 -318
- package/src/providers/media/index.js +0 -17
- package/src/providers/messaging/index.js +0 -20
- package/src/providers/messaging/slack.js +0 -393
- package/src/providers/messaging/twilio-sms.js +0 -249
- package/src/providers/project-management/index.js +0 -17
- package/src/providers/project-management/linear.js +0 -575
- package/src/providers/registry.js +0 -86
- package/src/providers/spreadsheet/google-sheets.js +0 -375
- package/src/providers/spreadsheet/index.js +0 -20
- package/src/providers/spreadsheet/xlsx.js +0 -423
- package/src/providers/storage/index.js +0 -24
- package/src/providers/storage/s3.js +0 -419
- package/src/providers/support/index.js +0 -17
- package/src/providers/support/zendesk.js +0 -373
- package/src/providers/tasks/index.js +0 -17
- package/src/providers/tasks/todoist.js +0 -286
- package/src/providers/types.js +0 -9
- package/src/providers/video-conferencing/google-meet.js +0 -286
- package/src/providers/video-conferencing/index.js +0 -31
- package/src/providers/video-conferencing/jitsi.js +0 -254
- package/src/providers/video-conferencing/teams.js +0 -270
- package/src/providers/video-conferencing/zoom.js +0 -332
- package/src/registry.js +0 -128
- package/src/tools/communication.js +0 -184
- package/src/tools/data.js +0 -205
- package/src/tools/index.js +0 -11
- package/src/tools/web.js +0 -137
- package/src/types.js +0 -10
- package/test/define.test.js +0 -306
- package/test/registry.test.js +0 -357
- package/test/tools.test.js +0 -363
package/src/worker.ts
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Worker - provides tool service via RPC
|
|
3
|
+
*
|
|
4
|
+
* This worker can be deployed to Cloudflare Workers or run locally via Miniflare.
|
|
5
|
+
* It exposes tool registration, discovery, execution, and MCP conversion via Workers RPC.
|
|
6
|
+
*
|
|
7
|
+
* Uses Cloudflare Workers RPC (WorkerEntrypoint, RpcTarget) for communication.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { WorkerEntrypoint, RpcTarget } from 'cloudflare:workers'
|
|
11
|
+
import type { AnyTool, ToolCategory, ToolQuery, MCPTool, ToolSubcategory } from './types.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Core tool service - extends RpcTarget so it can be passed over RPC
|
|
15
|
+
*
|
|
16
|
+
* Contains all tool functionality: registration, discovery, execution, and MCP conversion
|
|
17
|
+
*/
|
|
18
|
+
export class ToolServiceCore extends RpcTarget {
|
|
19
|
+
private tools: Map<string, AnyTool> = new Map()
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
super()
|
|
23
|
+
// Register built-in tools
|
|
24
|
+
this.registerBuiltins()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Register built-in tools
|
|
29
|
+
*/
|
|
30
|
+
private registerBuiltins(): void {
|
|
31
|
+
// data.json.parse
|
|
32
|
+
this.register({
|
|
33
|
+
id: 'data.json.parse',
|
|
34
|
+
name: 'Parse JSON',
|
|
35
|
+
description: 'Parse a JSON string into an object',
|
|
36
|
+
category: 'data',
|
|
37
|
+
subcategory: 'transform',
|
|
38
|
+
parameters: [
|
|
39
|
+
{
|
|
40
|
+
name: 'text',
|
|
41
|
+
description: 'JSON string to parse',
|
|
42
|
+
schema: { type: 'string' },
|
|
43
|
+
required: true,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
handler: async (input: { text: string }) => {
|
|
47
|
+
try {
|
|
48
|
+
const data = JSON.parse(input.text)
|
|
49
|
+
return { data, valid: true }
|
|
50
|
+
} catch (e) {
|
|
51
|
+
return {
|
|
52
|
+
data: null,
|
|
53
|
+
valid: false,
|
|
54
|
+
error: e instanceof Error ? e.message : 'Invalid JSON',
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
tags: ['json', 'parse', 'transform'],
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// data.csv.parse
|
|
62
|
+
this.register({
|
|
63
|
+
id: 'data.csv.parse',
|
|
64
|
+
name: 'Parse CSV',
|
|
65
|
+
description: 'Parse CSV text into an array of objects',
|
|
66
|
+
category: 'data',
|
|
67
|
+
subcategory: 'transform',
|
|
68
|
+
parameters: [
|
|
69
|
+
{
|
|
70
|
+
name: 'text',
|
|
71
|
+
description: 'CSV text to parse',
|
|
72
|
+
schema: { type: 'string' },
|
|
73
|
+
required: true,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'delimiter',
|
|
77
|
+
description: 'Column delimiter (default: comma)',
|
|
78
|
+
schema: { type: 'string' },
|
|
79
|
+
required: false,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'hasHeaders',
|
|
83
|
+
description: 'First row is headers (default: true)',
|
|
84
|
+
schema: { type: 'boolean' },
|
|
85
|
+
required: false,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
handler: async (input: { text: string; delimiter?: string; hasHeaders?: boolean }) => {
|
|
89
|
+
const delimiter = input.delimiter || ','
|
|
90
|
+
const hasHeaders = input.hasHeaders !== false
|
|
91
|
+
const lines = input.text.split('\n').filter((line) => line.trim())
|
|
92
|
+
|
|
93
|
+
if (lines.length === 0) {
|
|
94
|
+
return { rows: [], headers: [], rowCount: 0 }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const firstLine = lines[0]!
|
|
98
|
+
const headers = hasHeaders
|
|
99
|
+
? firstLine.split(delimiter).map((h) => h.trim())
|
|
100
|
+
: firstLine.split(delimiter).map((_, i) => `column${i + 1}`)
|
|
101
|
+
|
|
102
|
+
const dataLines = hasHeaders ? lines.slice(1) : lines
|
|
103
|
+
|
|
104
|
+
const rows = dataLines.map((line) => {
|
|
105
|
+
const values = line.split(delimiter).map((v) => v.trim())
|
|
106
|
+
const row: Record<string, string> = {}
|
|
107
|
+
headers.forEach((header, i) => {
|
|
108
|
+
row[header] = values[i] || ''
|
|
109
|
+
})
|
|
110
|
+
return row
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
return { rows, headers, rowCount: rows.length }
|
|
114
|
+
},
|
|
115
|
+
tags: ['csv', 'parse', 'transform'],
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// data.transform
|
|
119
|
+
this.register({
|
|
120
|
+
id: 'data.transform',
|
|
121
|
+
name: 'Transform Data',
|
|
122
|
+
description: 'Transform data by mapping fields to new structure',
|
|
123
|
+
category: 'data',
|
|
124
|
+
subcategory: 'transform',
|
|
125
|
+
parameters: [
|
|
126
|
+
{
|
|
127
|
+
name: 'data',
|
|
128
|
+
description: 'Source data to transform',
|
|
129
|
+
schema: {},
|
|
130
|
+
required: true,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'transform',
|
|
134
|
+
description: 'Mapping of output fields to input paths',
|
|
135
|
+
schema: { type: 'object' },
|
|
136
|
+
required: true,
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
handler: async (input: { data: unknown; transform: Record<string, string> }) => {
|
|
140
|
+
const result: Record<string, unknown> = {}
|
|
141
|
+
|
|
142
|
+
for (const [outputKey, inputPath] of Object.entries(input.transform)) {
|
|
143
|
+
const pathParts = inputPath.split('.')
|
|
144
|
+
let value: unknown = input.data
|
|
145
|
+
|
|
146
|
+
for (const part of pathParts) {
|
|
147
|
+
if (value && typeof value === 'object' && part in (value as Record<string, unknown>)) {
|
|
148
|
+
value = (value as Record<string, unknown>)[part]
|
|
149
|
+
} else {
|
|
150
|
+
value = undefined
|
|
151
|
+
break
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
result[outputKey] = value
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { result }
|
|
159
|
+
},
|
|
160
|
+
tags: ['transform', 'map', 'extract'],
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Register a tool in the service registry
|
|
166
|
+
*/
|
|
167
|
+
register(tool: AnyTool): void {
|
|
168
|
+
this.tools.set(tool.id, tool)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Unregister a tool
|
|
173
|
+
*/
|
|
174
|
+
unregister(id: string): boolean {
|
|
175
|
+
return this.tools.delete(id)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get a tool by ID
|
|
180
|
+
*/
|
|
181
|
+
get(id: string): AnyTool | undefined {
|
|
182
|
+
return this.tools.get(id)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Check if a tool exists
|
|
187
|
+
*/
|
|
188
|
+
has(id: string): boolean {
|
|
189
|
+
return this.tools.has(id)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* List all tool IDs
|
|
194
|
+
*/
|
|
195
|
+
list(): string[] {
|
|
196
|
+
return Array.from(this.tools.keys())
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Query tools with filtering
|
|
201
|
+
*/
|
|
202
|
+
query(options: ToolQuery): AnyTool[] {
|
|
203
|
+
let results = Array.from(this.tools.values())
|
|
204
|
+
|
|
205
|
+
// Filter by category
|
|
206
|
+
if (options.category) {
|
|
207
|
+
results = results.filter((t) => t.category === options.category)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Filter by subcategory
|
|
211
|
+
if (options.subcategory) {
|
|
212
|
+
results = results.filter((t) => t.subcategory === options.subcategory)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Filter by tags
|
|
216
|
+
if (options.tags && options.tags.length > 0) {
|
|
217
|
+
results = results.filter((t) => t.tags && options.tags!.some((tag) => t.tags!.includes(tag)))
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Filter by audience
|
|
221
|
+
if (options.audience) {
|
|
222
|
+
results = results.filter(
|
|
223
|
+
(t) => t.audience === options.audience || t.audience === 'both' || !t.audience
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Text search
|
|
228
|
+
if (options.search) {
|
|
229
|
+
const search = options.search.toLowerCase()
|
|
230
|
+
results = results.filter(
|
|
231
|
+
(t) =>
|
|
232
|
+
t.name.toLowerCase().includes(search) ||
|
|
233
|
+
t.description.toLowerCase().includes(search) ||
|
|
234
|
+
t.id.toLowerCase().includes(search)
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Pagination
|
|
239
|
+
const offset = options.offset ?? 0
|
|
240
|
+
const limit = options.limit ?? results.length
|
|
241
|
+
results = results.slice(offset, offset + limit)
|
|
242
|
+
|
|
243
|
+
return results
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Get tools by category
|
|
248
|
+
*/
|
|
249
|
+
byCategory(category: ToolCategory): AnyTool[] {
|
|
250
|
+
return this.query({ category })
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Execute a tool by ID
|
|
255
|
+
*/
|
|
256
|
+
async executeTool<TInput = unknown, TOutput = unknown>(
|
|
257
|
+
id: string,
|
|
258
|
+
input: TInput
|
|
259
|
+
): Promise<TOutput> {
|
|
260
|
+
const tool = this.tools.get(id)
|
|
261
|
+
if (!tool) {
|
|
262
|
+
throw new Error(`Tool "${id}" not found`)
|
|
263
|
+
}
|
|
264
|
+
return tool.handler(input) as Promise<TOutput>
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Convert a Tool to MCP format
|
|
269
|
+
*/
|
|
270
|
+
toMCP(tool: AnyTool): MCPTool {
|
|
271
|
+
return {
|
|
272
|
+
name: tool.id,
|
|
273
|
+
description: tool.description,
|
|
274
|
+
inputSchema: {
|
|
275
|
+
type: 'object',
|
|
276
|
+
properties: Object.fromEntries(
|
|
277
|
+
tool.parameters.map((p) => [
|
|
278
|
+
p.name,
|
|
279
|
+
typeof p.schema === 'object' && 'type' in p.schema
|
|
280
|
+
? { ...p.schema, description: p.description }
|
|
281
|
+
: { description: p.description },
|
|
282
|
+
])
|
|
283
|
+
),
|
|
284
|
+
required: tool.parameters.filter((p) => p.required).map((p) => p.name),
|
|
285
|
+
},
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Convert all registered tools to MCP format
|
|
291
|
+
*/
|
|
292
|
+
listMCPTools(): MCPTool[] {
|
|
293
|
+
return this.list().map((id) => this.toMCP(this.get(id)!))
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Import a tool from MCP format
|
|
298
|
+
*/
|
|
299
|
+
fromMCP(
|
|
300
|
+
mcpTool: MCPTool,
|
|
301
|
+
handler: (input: unknown) => unknown | Promise<unknown>,
|
|
302
|
+
options?: {
|
|
303
|
+
category?: ToolCategory
|
|
304
|
+
subcategory?: ToolSubcategory
|
|
305
|
+
tags?: string[]
|
|
306
|
+
}
|
|
307
|
+
): AnyTool {
|
|
308
|
+
const inputSchema = mcpTool.inputSchema
|
|
309
|
+
const properties = inputSchema.properties || {}
|
|
310
|
+
const required = inputSchema.required || []
|
|
311
|
+
|
|
312
|
+
const parameters = Object.entries(properties).map(([name, schema]) => ({
|
|
313
|
+
name,
|
|
314
|
+
description: (schema as { description?: string }).description || `Parameter: ${name}`,
|
|
315
|
+
schema: schema as { type?: string },
|
|
316
|
+
required: required.includes(name),
|
|
317
|
+
}))
|
|
318
|
+
|
|
319
|
+
const tool: AnyTool = {
|
|
320
|
+
id: mcpTool.name,
|
|
321
|
+
name: mcpTool.name,
|
|
322
|
+
description: mcpTool.description,
|
|
323
|
+
category: options?.category || 'integration',
|
|
324
|
+
...(options?.subcategory !== undefined && { subcategory: options.subcategory }),
|
|
325
|
+
...(options?.tags !== undefined && { tags: options.tags }),
|
|
326
|
+
parameters,
|
|
327
|
+
handler,
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return tool
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Clear all tools (except built-ins if keepBuiltins is true)
|
|
335
|
+
*/
|
|
336
|
+
clear(keepBuiltins: boolean = false): void {
|
|
337
|
+
if (keepBuiltins) {
|
|
338
|
+
const builtinIds = ['data.json.parse', 'data.csv.parse', 'data.transform']
|
|
339
|
+
const builtins = builtinIds.map((id) => this.tools.get(id)).filter(Boolean) as AnyTool[]
|
|
340
|
+
this.tools.clear()
|
|
341
|
+
builtins.forEach((tool) => this.tools.set(tool.id, tool))
|
|
342
|
+
} else {
|
|
343
|
+
this.tools.clear()
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Main tool service exposed via RPC as WorkerEntrypoint
|
|
350
|
+
*
|
|
351
|
+
* Usage:
|
|
352
|
+
* const tools = await env.TOOLS.connect()
|
|
353
|
+
* tools.register({ id: 'my.tool', ... })
|
|
354
|
+
* tools.list()
|
|
355
|
+
* const result = await tools.executeTool('data.json.parse', { text: '{}' })
|
|
356
|
+
*/
|
|
357
|
+
export class ToolService extends WorkerEntrypoint {
|
|
358
|
+
/**
|
|
359
|
+
* Get a tool service instance - returns an RpcTarget that can be used directly
|
|
360
|
+
*/
|
|
361
|
+
connect(): ToolServiceCore {
|
|
362
|
+
return new ToolServiceCore()
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Export as default for WorkerEntrypoint pattern
|
|
367
|
+
export default ToolService
|
|
368
|
+
|
|
369
|
+
// Export aliases
|
|
370
|
+
export { ToolService as ToolWorker }
|
package/src/wrap.ts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `wrapTool()` — bridge from a `Tool` definition to an HTTP-shaped handler
|
|
3
|
+
* gated by `id.org.ai` brokers.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors id.org.ai's `wrap()` shape but is specific to digital-tools:
|
|
6
|
+
*
|
|
7
|
+
* 1. Calls `AuthBroker.gate(req, need)` to authenticate the caller.
|
|
8
|
+
* `need` is derived from the tool's `auth` declaration.
|
|
9
|
+
* On rejection, returns the broker's pre-baked `denialResponse(...)`.
|
|
10
|
+
*
|
|
11
|
+
* 2. When the tool declares `pricing`, calls
|
|
12
|
+
* `PaymentBroker.settle(req, identity, required)`. On rejection,
|
|
13
|
+
* returns the broker's pre-baked 402 (`PaymentRejection.response`).
|
|
14
|
+
* `required` is translated from our local MDXLD `PaymentRequired`
|
|
15
|
+
* shape into id.org.ai's discriminated `PaymentRequired` (`charge`
|
|
16
|
+
* intent — `session` intent is not yet supported in 0.3.0 and we
|
|
17
|
+
* throw a clear error if a tool requests it).
|
|
18
|
+
*
|
|
19
|
+
* 3. Reads `application/json` body, calls `tool.handler(input, ctx)`
|
|
20
|
+
* with `ctx.identity = Identity` and (when paid) `ctx.paymentReceipt
|
|
21
|
+
* = PaymentReceipt`. The receipt's `responseHeader` (e.g.
|
|
22
|
+
* `PAYMENT-RESPONSE` for x402 or `Payment-Receipt` for MPP) is
|
|
23
|
+
* stamped onto the success response.
|
|
24
|
+
*
|
|
25
|
+
* 4. Tools without `auth` and without `pricing` pass through directly;
|
|
26
|
+
* the handler is invoked with `ctx.identity` undefined-cast (we set
|
|
27
|
+
* it to a synthetic anonymous ref) so the handler signature stays
|
|
28
|
+
* consistent.
|
|
29
|
+
*
|
|
30
|
+
* Tests should pass structural fakes for `AuthBroker` and `PaymentBroker`
|
|
31
|
+
* — both are interfaces in id.org.ai. We intentionally do NOT depend on
|
|
32
|
+
* `AuthBrokerImpl` / `PaymentBrokerImpl` so this module is portable into
|
|
33
|
+
* Workers without pulling concrete adapters.
|
|
34
|
+
*
|
|
35
|
+
* @packageDocumentation
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import type {
|
|
39
|
+
AuthBroker,
|
|
40
|
+
AuthRequirement as IdAuthRequirement,
|
|
41
|
+
Identity,
|
|
42
|
+
PaymentBroker,
|
|
43
|
+
PaymentRequired as IdPaymentRequired,
|
|
44
|
+
PaymentReceipt,
|
|
45
|
+
RailQuote,
|
|
46
|
+
} from 'id.org.ai'
|
|
47
|
+
import { denialResponse } from 'id.org.ai'
|
|
48
|
+
import type { AuthRequirement, PaymentRequired, Tool, ToolHandlerContext } from './types.js'
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Translate the local MDXLD-shaped `AuthRequirement` (scopes + required
|
|
52
|
+
* mechanism) into id.org.ai's typed `AuthRequirement`. We always emit
|
|
53
|
+
* `minLevel: 1` for `oauth`/`apiKey` (a credential must be presented) and
|
|
54
|
+
* `minLevel: 0` for `none` (anonymous OK). Scopes flow through unchanged.
|
|
55
|
+
*/
|
|
56
|
+
function toIdAuthRequirement(req: AuthRequirement): IdAuthRequirement {
|
|
57
|
+
const minLevel = req.required === 'none' ? 0 : 1
|
|
58
|
+
return {
|
|
59
|
+
minLevel,
|
|
60
|
+
...(req.scopes.length > 0 && { scopes: req.scopes }),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Translate the local MDXLD-shaped `PaymentRequired` (single amount +
|
|
66
|
+
* accepted protocols) into id.org.ai's discriminated `PaymentRequired`
|
|
67
|
+
* (`charge` intent with explicit `RailQuote[]`). Each `accepts` entry
|
|
68
|
+
* fans out into `(method)` quotes — we use `exact` for x402 and `tempo`
|
|
69
|
+
* for MPP per phase-1 of `PaymentBrokerImpl`.
|
|
70
|
+
*
|
|
71
|
+
* Throws on `intent: 'session'` style requests — our local
|
|
72
|
+
* `PaymentRequired` doesn't currently model session intent, but if a
|
|
73
|
+
* caller mutates it to add one we surface the limitation early.
|
|
74
|
+
*/
|
|
75
|
+
function toIdPaymentRequired(pricing: PaymentRequired): IdPaymentRequired {
|
|
76
|
+
// Local `PaymentRequired` doesn't carry an `intent` discriminator;
|
|
77
|
+
// any future widening that does should fail fast here.
|
|
78
|
+
if ((pricing as unknown as { intent?: string }).intent === 'session') {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"wrapTool: 'session' intent pricing is not yet supported (id.org.ai PaymentBroker.session() is not implemented in 0.3.0). " +
|
|
81
|
+
"Use 'charge' intent or wait for the next id.org.ai release."
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const accepts: RailQuote[] = pricing.accepts.map((protocol): RailQuote => {
|
|
86
|
+
if (protocol === 'x402') {
|
|
87
|
+
return {
|
|
88
|
+
rail: { protocol: 'x402', method: 'exact', asset: pricing.currency },
|
|
89
|
+
amount: pricing.amount,
|
|
90
|
+
asset: pricing.currency,
|
|
91
|
+
payTo: pricing.recipient,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// protocol === 'mpp'
|
|
95
|
+
return {
|
|
96
|
+
rail: { protocol: 'mpp', method: 'tempo', asset: pricing.currency },
|
|
97
|
+
amount: pricing.amount,
|
|
98
|
+
asset: pricing.currency,
|
|
99
|
+
payTo: pricing.recipient,
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
intent: 'charge',
|
|
105
|
+
accepts,
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Read the request body as JSON. Returns `undefined` for empty bodies
|
|
111
|
+
* and non-JSON content types so tools with no input still work.
|
|
112
|
+
*/
|
|
113
|
+
async function readJsonBody(req: Request): Promise<unknown> {
|
|
114
|
+
const ct = req.headers.get('content-type') ?? ''
|
|
115
|
+
if (!ct.toLowerCase().includes('application/json')) return undefined
|
|
116
|
+
const text = await req.text()
|
|
117
|
+
if (!text) return undefined
|
|
118
|
+
try {
|
|
119
|
+
return JSON.parse(text)
|
|
120
|
+
} catch {
|
|
121
|
+
return undefined
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build a JSON `Response` for a successful tool invocation. Stamps the
|
|
127
|
+
* payment receipt's `responseHeader` (e.g. `PAYMENT-RESPONSE` for x402
|
|
128
|
+
* or `Payment-Receipt` for MPP) when present.
|
|
129
|
+
*/
|
|
130
|
+
function jsonOk(body: unknown, receipt?: PaymentReceipt): Response {
|
|
131
|
+
const headers: Record<string, string> = { 'content-type': 'application/json' }
|
|
132
|
+
if (receipt?.responseHeader) {
|
|
133
|
+
const [name, value] = receipt.responseHeader
|
|
134
|
+
headers[name] = value
|
|
135
|
+
}
|
|
136
|
+
return new Response(JSON.stringify(body), { status: 200, headers })
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Build a JSON error `Response`.
|
|
141
|
+
*/
|
|
142
|
+
function jsonError(status: number, code: string, message: string): Response {
|
|
143
|
+
return new Response(JSON.stringify({ error: code, error_description: message }), {
|
|
144
|
+
status,
|
|
145
|
+
headers: { 'content-type': 'application/json' },
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Wrap a Tool with broker-aware HTTP handling.
|
|
151
|
+
*
|
|
152
|
+
* @param broker AuthBroker (id.org.ai). Required when the tool has `auth`.
|
|
153
|
+
* @param paymentBroker PaymentBroker (id.org.ai). Required when the tool has `pricing`.
|
|
154
|
+
* @param tool The tool definition.
|
|
155
|
+
* @returns A function `(req: Request) => Promise<Response>` honoring the
|
|
156
|
+
* tool's `auth` and `pricing` declarations.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* const handler = wrapTool(authBroker, paymentBroker, sendEmailTool)
|
|
161
|
+
* // Use as a Worker fetch handler:
|
|
162
|
+
* export default { fetch: handler }
|
|
163
|
+
* ```
|
|
164
|
+
*
|
|
165
|
+
* Tools without `auth` / `pricing` pass through with no broker calls.
|
|
166
|
+
* In that case the broker arguments may still be provided but are unused.
|
|
167
|
+
*/
|
|
168
|
+
export function wrapTool<TInput, TOutput>(
|
|
169
|
+
broker: AuthBroker | undefined,
|
|
170
|
+
paymentBroker: PaymentBroker | undefined,
|
|
171
|
+
tool: Tool<TInput, TOutput>
|
|
172
|
+
): (req: Request) => Promise<Response> {
|
|
173
|
+
// Pre-translate auth/pricing once so per-request work stays minimal,
|
|
174
|
+
// and so a `session`-intent pricing fails at wrap-time rather than at
|
|
175
|
+
// first request — easier to debug.
|
|
176
|
+
const idAuth = tool.auth ? toIdAuthRequirement(tool.auth) : null
|
|
177
|
+
const idPricing = tool.pricing ? toIdPaymentRequired(tool.pricing) : null
|
|
178
|
+
|
|
179
|
+
return async function wrappedTool(req: Request): Promise<Response> {
|
|
180
|
+
let identity: Identity | undefined
|
|
181
|
+
let receipt: PaymentReceipt | undefined
|
|
182
|
+
|
|
183
|
+
// --- Auth gate ----------------------------------------------------
|
|
184
|
+
if (idAuth) {
|
|
185
|
+
if (!broker) {
|
|
186
|
+
return jsonError(
|
|
187
|
+
500,
|
|
188
|
+
'configuration_error',
|
|
189
|
+
`Tool "${tool.id}" declares auth but no AuthBroker was provided to wrapTool()`
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
const decision = await broker.gate(req, idAuth)
|
|
193
|
+
if (!decision.ok) {
|
|
194
|
+
return denialResponse(decision)
|
|
195
|
+
}
|
|
196
|
+
identity = decision.identity
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// --- Payment settle ----------------------------------------------
|
|
200
|
+
if (idPricing) {
|
|
201
|
+
if (!paymentBroker) {
|
|
202
|
+
return jsonError(
|
|
203
|
+
500,
|
|
204
|
+
'configuration_error',
|
|
205
|
+
`Tool "${tool.id}" declares pricing but no PaymentBroker was provided to wrapTool()`
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
// Auth is required for paid tools so we have an Identity to settle
|
|
209
|
+
// against. If a tool has pricing without auth we fall back to a
|
|
210
|
+
// synthetic anonymous identity — the broker still accepts it but
|
|
211
|
+
// rail negotiation will fail unless the request carries proof.
|
|
212
|
+
const settleIdentity: Identity = identity ?? {
|
|
213
|
+
id: 'anonymous',
|
|
214
|
+
type: 'agent',
|
|
215
|
+
name: 'anonymous',
|
|
216
|
+
verified: false,
|
|
217
|
+
level: 0,
|
|
218
|
+
claimStatus: 'unclaimed',
|
|
219
|
+
}
|
|
220
|
+
const outcome = await paymentBroker.settle(req, settleIdentity, idPricing)
|
|
221
|
+
if (!outcome.ok) {
|
|
222
|
+
return outcome.response
|
|
223
|
+
}
|
|
224
|
+
receipt = outcome
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// --- Invoke handler -----------------------------------------------
|
|
228
|
+
let input: TInput
|
|
229
|
+
try {
|
|
230
|
+
input = (await readJsonBody(req)) as TInput
|
|
231
|
+
} catch {
|
|
232
|
+
return jsonError(400, 'invalid_request', 'Could not parse request body as JSON')
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const ctx: ToolHandlerContext = {
|
|
236
|
+
identity: identity ?? 'anonymous',
|
|
237
|
+
...(receipt && { paymentReceipt: receipt }),
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const result = await tool.handler(input, ctx)
|
|
242
|
+
return jsonOk(result, receipt)
|
|
243
|
+
} catch (err) {
|
|
244
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
245
|
+
return jsonError(500, 'execution_error', message)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Internal: exported only for tests. Turn a local MDXLD `PaymentRequired`
|
|
252
|
+
* into id.org.ai's `PaymentRequired`. Throws on session intent.
|
|
253
|
+
*/
|
|
254
|
+
export const __toIdPaymentRequired = toIdPaymentRequired
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Internal: exported only for tests. Turn a local `AuthRequirement`
|
|
258
|
+
* into id.org.ai's `AuthRequirement`.
|
|
259
|
+
*/
|
|
260
|
+
export const __toIdAuthRequirement = toIdAuthRequirement
|