opentool 0.5.0 → 0.6.1
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 +87 -22
- package/dist/ai/index.d.ts +237 -0
- package/dist/ai/index.js +759 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/cli/index.d.ts +38 -5
- package/dist/cli/index.js +2218 -67
- package/dist/cli/index.js.map +1 -1
- package/dist/index-D3DaM5Rs.d.ts +1693 -0
- package/dist/index.d.ts +33 -5
- package/dist/index.js +3258 -25
- package/dist/index.js.map +1 -1
- package/dist/payment/index.d.ts +2 -0
- package/dist/payment/index.js +969 -0
- package/dist/payment/index.js.map +1 -0
- package/dist/{types/metadata.d.ts → validate-DiIOFUU5.d.ts} +262 -415
- package/dist/wallets/index.d.ts +117 -0
- package/dist/wallets/index.js +337 -0
- package/dist/wallets/index.js.map +1 -0
- package/package.json +35 -4
- package/dist/cli/build.d.ts +0 -23
- package/dist/cli/build.d.ts.map +0 -1
- package/dist/cli/build.js +0 -223
- package/dist/cli/build.js.map +0 -1
- package/dist/cli/dev.d.ts +0 -6
- package/dist/cli/dev.d.ts.map +0 -1
- package/dist/cli/dev.js +0 -123
- package/dist/cli/dev.js.map +0 -1
- package/dist/cli/generate-metadata.d.ts +0 -15
- package/dist/cli/generate-metadata.d.ts.map +0 -1
- package/dist/cli/generate-metadata.js +0 -90
- package/dist/cli/generate-metadata.js.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/shared/metadata.d.ts +0 -19
- package/dist/cli/shared/metadata.d.ts.map +0 -1
- package/dist/cli/shared/metadata.js +0 -283
- package/dist/cli/shared/metadata.js.map +0 -1
- package/dist/cli/validate.d.ts +0 -12
- package/dist/cli/validate.d.ts.map +0 -1
- package/dist/cli/validate.js +0 -237
- package/dist/cli/validate.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/runtime/index.d.ts +0 -12
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/index.js +0 -241
- package/dist/runtime/index.js.map +0 -1
- package/dist/types/index.d.ts +0 -33
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -3
- package/dist/types/index.js.map +0 -1
- package/dist/types/metadata.d.ts.map +0 -1
- package/dist/types/metadata.js +0 -108
- package/dist/types/metadata.js.map +0 -1
- package/dist/utils/esbuild.d.ts +0 -13
- package/dist/utils/esbuild.d.ts.map +0 -1
- package/dist/utils/esbuild.js +0 -95
- package/dist/utils/esbuild.js.map +0 -1
- package/dist/utils/module-loader.d.ts +0 -3
- package/dist/utils/module-loader.d.ts.map +0 -1
- package/dist/utils/module-loader.js +0 -49
- package/dist/utils/module-loader.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,26 +1,3259 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import * as fs2 from 'fs';
|
|
5
|
+
import * as path5 from 'path';
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
7
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { zeroAddress, createPublicClient, http, createWalletClient } from 'viem';
|
|
10
|
+
import { baseSepolia, mainnet, base } from 'viem/chains';
|
|
11
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
12
|
+
import { Turnkey } from '@turnkey/sdk-server';
|
|
13
|
+
import { createAccount } from '@turnkey/viem';
|
|
14
|
+
import { tmpdir } from 'os';
|
|
15
|
+
import { build } from 'esbuild';
|
|
16
|
+
import { createRequire } from 'module';
|
|
17
|
+
|
|
18
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
19
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
20
|
+
}) : x)(function(x) {
|
|
21
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
22
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
23
|
+
});
|
|
24
|
+
var PAYMENT_SCHEMA_VERSION = 1;
|
|
25
|
+
var paymentSchemaVersionSchema = z.literal(PAYMENT_SCHEMA_VERSION);
|
|
26
|
+
var decimalStringSchema = z.string().regex(/^(?:0|[1-9]\d*)(?:\.\d+)?$/, "Value must be a positive decimal string");
|
|
27
|
+
var currencySchema = z.object({
|
|
28
|
+
code: z.string().min(2).max(12).transform((value) => value.toUpperCase()),
|
|
29
|
+
symbol: z.string().min(1).max(6).optional(),
|
|
30
|
+
decimals: z.number().int().min(0).max(36).optional(),
|
|
31
|
+
kind: z.enum(["fiat", "crypto"]).default("crypto").optional(),
|
|
32
|
+
description: z.string().optional()
|
|
33
|
+
});
|
|
34
|
+
var paymentAmountSchema = z.object({
|
|
35
|
+
value: decimalStringSchema,
|
|
36
|
+
currency: currencySchema,
|
|
37
|
+
display: z.string().optional()
|
|
38
|
+
});
|
|
39
|
+
var cryptoAssetSchema = z.object({
|
|
40
|
+
symbol: z.string().min(2).max(12),
|
|
41
|
+
network: z.string().min(1).optional(),
|
|
42
|
+
chainId: z.number().int().min(0).optional(),
|
|
43
|
+
address: z.string().min(1).optional(),
|
|
44
|
+
decimals: z.number().int().min(0).max(36).optional(),
|
|
45
|
+
standard: z.enum(["erc20", "spl", "custom"]).default("erc20").optional(),
|
|
46
|
+
description: z.string().optional()
|
|
47
|
+
});
|
|
48
|
+
var facilitatorConfigSchema = z.object({
|
|
49
|
+
url: z.string().url(),
|
|
50
|
+
vendor: z.string().optional(),
|
|
51
|
+
verifyPath: z.string().default("/verify").optional(),
|
|
52
|
+
settlePath: z.string().default("/settle").optional(),
|
|
53
|
+
apiKey: z.string().optional(),
|
|
54
|
+
apiKeyEnv: z.string().optional(),
|
|
55
|
+
apiKeyHeader: z.string().default("Authorization").optional(),
|
|
56
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
57
|
+
timeoutMs: z.number().int().positive().optional()
|
|
58
|
+
});
|
|
59
|
+
var settlementTermsSchema = z.object({
|
|
60
|
+
windowSeconds: z.number().int().positive().optional(),
|
|
61
|
+
targetConfirmations: z.number().int().positive().optional(),
|
|
62
|
+
finalityDescription: z.string().optional(),
|
|
63
|
+
slaDescription: z.string().optional()
|
|
64
|
+
});
|
|
65
|
+
var paymentFieldSchema = z.object({
|
|
66
|
+
key: z.string().min(1),
|
|
67
|
+
label: z.string().min(1),
|
|
68
|
+
required: z.boolean().default(true).optional(),
|
|
69
|
+
description: z.string().optional(),
|
|
70
|
+
example: z.string().optional()
|
|
71
|
+
});
|
|
72
|
+
var x402ProofSchema = z.object({
|
|
73
|
+
mode: z.literal("x402"),
|
|
74
|
+
scheme: z.string().min(1),
|
|
75
|
+
network: z.string().min(1),
|
|
76
|
+
version: z.number().int().min(1).optional(),
|
|
77
|
+
facilitator: facilitatorConfigSchema.optional(),
|
|
78
|
+
verifier: z.string().optional()
|
|
79
|
+
});
|
|
80
|
+
var directProofSchema = z.object({
|
|
81
|
+
mode: z.literal("direct"),
|
|
82
|
+
proofTypes: z.array(z.string().min(1)).nonempty(),
|
|
83
|
+
verifier: z.string().optional(),
|
|
84
|
+
instructions: z.string().optional(),
|
|
85
|
+
fields: z.array(paymentFieldSchema).optional(),
|
|
86
|
+
allowsManualReview: z.boolean().optional()
|
|
87
|
+
});
|
|
88
|
+
var paymentProofSchema = z.discriminatedUnion("mode", [
|
|
89
|
+
x402ProofSchema,
|
|
90
|
+
directProofSchema
|
|
91
|
+
]);
|
|
92
|
+
var paymentOptionSchema = z.object({
|
|
93
|
+
id: z.string().min(1),
|
|
94
|
+
title: z.string().min(1),
|
|
95
|
+
description: z.string().optional(),
|
|
96
|
+
amount: paymentAmountSchema,
|
|
97
|
+
asset: cryptoAssetSchema,
|
|
98
|
+
payTo: z.string().min(1),
|
|
99
|
+
resource: z.string().url().optional(),
|
|
100
|
+
proof: paymentProofSchema,
|
|
101
|
+
settlement: settlementTermsSchema.optional(),
|
|
102
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
103
|
+
});
|
|
104
|
+
var paymentRequirementsSchema = z.object({
|
|
105
|
+
schemaVersion: paymentSchemaVersionSchema,
|
|
106
|
+
message: z.string().optional(),
|
|
107
|
+
title: z.string().optional(),
|
|
108
|
+
resource: z.string().url().optional(),
|
|
109
|
+
accepts: z.array(paymentOptionSchema).nonempty(),
|
|
110
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
111
|
+
fallbackText: z.string().optional()
|
|
112
|
+
});
|
|
113
|
+
var x402PaymentHeaderSchema = z.object({
|
|
114
|
+
x402Version: z.number().int().min(1),
|
|
115
|
+
scheme: z.string().min(1),
|
|
116
|
+
network: z.string().min(1),
|
|
117
|
+
payload: z.unknown(),
|
|
118
|
+
correlationId: z.string().optional()
|
|
119
|
+
});
|
|
120
|
+
var directPaymentPayloadSchema = z.object({
|
|
121
|
+
schemaVersion: z.literal(1),
|
|
122
|
+
optionId: z.string().min(1),
|
|
123
|
+
proofType: z.string().min(1),
|
|
124
|
+
payload: z.unknown(),
|
|
125
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
126
|
+
});
|
|
127
|
+
var paymentSuccessMetadataSchema = z.object({
|
|
128
|
+
optionId: z.string().min(1),
|
|
129
|
+
verifier: z.string().optional(),
|
|
130
|
+
txHash: z.string().optional(),
|
|
131
|
+
networkId: z.string().optional(),
|
|
132
|
+
amount: paymentAmountSchema.optional(),
|
|
133
|
+
settledAt: z.string().datetime().optional(),
|
|
134
|
+
payload: z.unknown().optional()
|
|
135
|
+
});
|
|
136
|
+
var paymentFailureSchema = z.object({
|
|
137
|
+
reason: z.string().min(1),
|
|
138
|
+
code: z.enum([
|
|
139
|
+
"verifier_not_found",
|
|
140
|
+
"verification_failed",
|
|
141
|
+
"invalid_payload",
|
|
142
|
+
"unsupported_option",
|
|
143
|
+
"missing_header",
|
|
144
|
+
"unknown"
|
|
145
|
+
]).default("unknown").optional(),
|
|
146
|
+
retryable: z.boolean().optional(),
|
|
147
|
+
detail: z.unknown().optional()
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// src/helpers/payment.ts
|
|
151
|
+
var X402_VERSION_DEFAULT = 1;
|
|
152
|
+
var HEADER_X402 = "X-PAYMENT";
|
|
153
|
+
var HEADER_DIRECT = "X-PAYMENT-PROOF";
|
|
154
|
+
var HEADER_PAYMENT_RESPONSE = "X-PAYMENT-RESPONSE";
|
|
155
|
+
var x402RequirementSchema = z.object({
|
|
156
|
+
scheme: z.string().min(1),
|
|
157
|
+
network: z.string().min(1),
|
|
158
|
+
maxAmountRequired: z.string().min(1),
|
|
159
|
+
asset: z.string().min(1),
|
|
160
|
+
payTo: z.string().min(1),
|
|
161
|
+
resource: z.string().optional(),
|
|
162
|
+
description: z.string().optional(),
|
|
163
|
+
mimeType: z.string().optional(),
|
|
164
|
+
outputSchema: z.unknown().optional(),
|
|
165
|
+
maxTimeoutSeconds: z.number().int().positive().optional(),
|
|
166
|
+
extra: z.record(z.string(), z.unknown()).nullable().optional()
|
|
167
|
+
});
|
|
168
|
+
function createPaymentRequiredBody(definition) {
|
|
169
|
+
const parsed = paymentRequirementsSchema.parse(definition);
|
|
170
|
+
const x402Accepts = parsed.accepts.filter((option) => option.proof.mode === "x402").map(
|
|
171
|
+
(option) => toX402Requirement(option, parsed.resource, option.settlement)
|
|
172
|
+
).filter((value) => Boolean(value));
|
|
173
|
+
const x402Body = x402Accepts.length > 0 ? {
|
|
174
|
+
x402Version: resolveX402Version(parsed.accepts),
|
|
175
|
+
error: parsed.message ?? "Payment required",
|
|
176
|
+
accepts: x402Accepts
|
|
177
|
+
} : void 0;
|
|
178
|
+
if (x402Body) {
|
|
179
|
+
return {
|
|
180
|
+
...parsed,
|
|
181
|
+
x402: x402Body
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return parsed;
|
|
185
|
+
}
|
|
186
|
+
function paymentRequiredResponse(definition, init) {
|
|
187
|
+
const body = createPaymentRequiredBody(definition);
|
|
188
|
+
const headers = new Headers(init?.headers);
|
|
189
|
+
if (!headers.has("content-type")) {
|
|
190
|
+
headers.set("content-type", "application/json; charset=utf-8");
|
|
191
|
+
}
|
|
192
|
+
return new Response(JSON.stringify(body), {
|
|
193
|
+
...init,
|
|
194
|
+
status: init?.status ?? 402,
|
|
195
|
+
headers
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function extractPaymentAttempts(source) {
|
|
199
|
+
const attempts = [];
|
|
200
|
+
const failures = [];
|
|
201
|
+
const x402Header = source.headers.get(HEADER_X402);
|
|
202
|
+
if (x402Header) {
|
|
203
|
+
const { attempt, failure } = parseX402Header(x402Header);
|
|
204
|
+
if (attempt) {
|
|
205
|
+
attempts.push(attempt);
|
|
206
|
+
} else if (failure) {
|
|
207
|
+
failures.push(failure);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const directHeader = source.headers.get(HEADER_DIRECT);
|
|
211
|
+
if (directHeader) {
|
|
212
|
+
const { attempt, failure } = parseDirectHeader(directHeader);
|
|
213
|
+
if (attempt) {
|
|
214
|
+
attempts.push(attempt);
|
|
215
|
+
} else if (failure) {
|
|
216
|
+
failures.push(failure);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (attempts.length === 0 && failures.length === 0) {
|
|
220
|
+
failures.push(
|
|
221
|
+
paymentFailureSchema.parse({
|
|
222
|
+
reason: "No payment headers present",
|
|
223
|
+
code: "missing_header",
|
|
224
|
+
retryable: false
|
|
225
|
+
})
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
return { attempts, failures };
|
|
229
|
+
}
|
|
230
|
+
async function verifyPayment(options) {
|
|
231
|
+
const definition = paymentRequirementsSchema.parse(options.definition);
|
|
232
|
+
const attempts = options.attempts ? options.attempts : options.request ? extractPaymentAttempts(options.request).attempts : [];
|
|
233
|
+
if (attempts.length === 0) {
|
|
234
|
+
return {
|
|
235
|
+
success: false,
|
|
236
|
+
optionId: "",
|
|
237
|
+
attemptType: "direct",
|
|
238
|
+
failure: paymentFailureSchema.parse({
|
|
239
|
+
reason: "No payment attempt found",
|
|
240
|
+
code: "missing_header",
|
|
241
|
+
retryable: false
|
|
242
|
+
})
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
for (const attempt of attempts) {
|
|
246
|
+
const option = findMatchingOption(definition, attempt);
|
|
247
|
+
if (!option) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (attempt.type === "x402") {
|
|
251
|
+
const proof = option.proof;
|
|
252
|
+
const verifierId = proof.verifier ?? (proof.facilitator ? "x402:facilitator" : void 0);
|
|
253
|
+
if (verifierId === "x402:facilitator" && proof.facilitator) {
|
|
254
|
+
const context2 = {
|
|
255
|
+
attempt,
|
|
256
|
+
option,
|
|
257
|
+
definition
|
|
258
|
+
};
|
|
259
|
+
if (options.settle !== void 0) {
|
|
260
|
+
context2.settle = options.settle;
|
|
261
|
+
}
|
|
262
|
+
return runFacilitatorVerifier({
|
|
263
|
+
...context2,
|
|
264
|
+
fetchImpl: options.fetchImpl ?? fetch
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
const verifier = verifierId ? options.verifiers?.[verifierId] : void 0;
|
|
268
|
+
if (!verifier) {
|
|
269
|
+
return {
|
|
270
|
+
success: false,
|
|
271
|
+
optionId: option.id,
|
|
272
|
+
attemptType: attempt.type,
|
|
273
|
+
failure: paymentFailureSchema.parse({
|
|
274
|
+
reason: `No verifier registered for id: ${verifierId ?? "(missing)"}`,
|
|
275
|
+
code: "verifier_not_found",
|
|
276
|
+
retryable: false
|
|
277
|
+
})
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const context = {
|
|
281
|
+
attempt,
|
|
282
|
+
option,
|
|
283
|
+
definition
|
|
284
|
+
};
|
|
285
|
+
if (options.settle !== void 0) {
|
|
286
|
+
context.settle = options.settle;
|
|
287
|
+
}
|
|
288
|
+
return verifier(context);
|
|
289
|
+
}
|
|
290
|
+
if (attempt.type === "direct") {
|
|
291
|
+
const proof = option.proof;
|
|
292
|
+
const verifierId = proof.verifier ?? `direct:${attempt.payload.proofType}`;
|
|
293
|
+
const verifier = verifierId ? options.verifiers?.[verifierId] : void 0;
|
|
294
|
+
if (!verifier) {
|
|
295
|
+
return {
|
|
296
|
+
success: false,
|
|
297
|
+
optionId: option.id,
|
|
298
|
+
attemptType: attempt.type,
|
|
299
|
+
failure: paymentFailureSchema.parse({
|
|
300
|
+
reason: `No verifier registered for id: ${verifierId}`,
|
|
301
|
+
code: "verifier_not_found",
|
|
302
|
+
retryable: false
|
|
303
|
+
})
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
const context = {
|
|
307
|
+
attempt,
|
|
308
|
+
option,
|
|
309
|
+
definition
|
|
310
|
+
};
|
|
311
|
+
if (options.settle !== void 0) {
|
|
312
|
+
context.settle = options.settle;
|
|
313
|
+
}
|
|
314
|
+
return verifier(context);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
success: false,
|
|
319
|
+
optionId: "",
|
|
320
|
+
attemptType: attempts[0]?.type ?? "direct",
|
|
321
|
+
failure: paymentFailureSchema.parse({
|
|
322
|
+
reason: "No matching payment option for attempt",
|
|
323
|
+
code: "unsupported_option",
|
|
324
|
+
retryable: false
|
|
325
|
+
})
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function createPaymentResponseHeader(metadata) {
|
|
329
|
+
const parsed = paymentSuccessMetadataSchema.parse(metadata);
|
|
330
|
+
return encodeJson(parsed);
|
|
331
|
+
}
|
|
332
|
+
function parseX402Header(value) {
|
|
333
|
+
try {
|
|
334
|
+
const payload = decodeJson(value, x402PaymentHeaderSchema);
|
|
335
|
+
return {
|
|
336
|
+
attempt: {
|
|
337
|
+
type: "x402",
|
|
338
|
+
headerName: HEADER_X402,
|
|
339
|
+
raw: value,
|
|
340
|
+
payload
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
} catch (error) {
|
|
344
|
+
return {
|
|
345
|
+
failure: paymentFailureSchema.parse({
|
|
346
|
+
reason: `Invalid X-PAYMENT header: ${error.message}`,
|
|
347
|
+
code: "invalid_payload",
|
|
348
|
+
retryable: false
|
|
349
|
+
})
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function parseDirectHeader(value) {
|
|
354
|
+
try {
|
|
355
|
+
const payload = decodeJson(value, directPaymentPayloadSchema);
|
|
356
|
+
return {
|
|
357
|
+
attempt: {
|
|
358
|
+
type: "direct",
|
|
359
|
+
headerName: HEADER_DIRECT,
|
|
360
|
+
raw: value,
|
|
361
|
+
payload
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
} catch (error) {
|
|
365
|
+
return {
|
|
366
|
+
failure: paymentFailureSchema.parse({
|
|
367
|
+
reason: `Invalid X-PAYMENT-PROOF header: ${error.message}`,
|
|
368
|
+
code: "invalid_payload",
|
|
369
|
+
retryable: false
|
|
370
|
+
})
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function findMatchingOption(definition, attempt) {
|
|
375
|
+
return definition.accepts.find((candidate) => {
|
|
376
|
+
const option = paymentOptionSchema.parse(candidate);
|
|
377
|
+
if (attempt.type === "x402" && option.proof.mode === "x402") {
|
|
378
|
+
return option.proof.scheme === attempt.payload.scheme && option.proof.network === attempt.payload.network;
|
|
379
|
+
}
|
|
380
|
+
if (attempt.type === "direct" && option.proof.mode === "direct") {
|
|
381
|
+
return option.id === attempt.payload.optionId;
|
|
382
|
+
}
|
|
383
|
+
return false;
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
function resolveX402Version(options) {
|
|
387
|
+
const versions = [];
|
|
388
|
+
for (const option of options) {
|
|
389
|
+
if (option.proof.mode === "x402" && option.proof.version) {
|
|
390
|
+
versions.push(option.proof.version);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return versions.length > 0 ? Math.max(...versions) : X402_VERSION_DEFAULT;
|
|
394
|
+
}
|
|
395
|
+
function toX402Requirement(option, fallbackResource, settlement) {
|
|
396
|
+
if (option.proof.mode !== "x402") {
|
|
397
|
+
return void 0;
|
|
398
|
+
}
|
|
399
|
+
const decimals = resolveDecimals(option);
|
|
400
|
+
const assetAddress = option.asset.address;
|
|
401
|
+
if (!assetAddress) {
|
|
402
|
+
throw new Error(
|
|
403
|
+
`x402 payment option '${option.id}' is missing asset.address`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
const units = decimalToBaseUnits(option.amount.value, decimals);
|
|
407
|
+
return x402RequirementSchema.parse({
|
|
408
|
+
scheme: option.proof.scheme,
|
|
409
|
+
network: option.proof.network,
|
|
410
|
+
maxAmountRequired: units,
|
|
411
|
+
asset: assetAddress,
|
|
412
|
+
payTo: option.payTo,
|
|
413
|
+
resource: option.resource ?? fallbackResource,
|
|
414
|
+
description: option.description,
|
|
415
|
+
maxTimeoutSeconds: settlement?.windowSeconds,
|
|
416
|
+
extra: {
|
|
417
|
+
symbol: option.asset.symbol,
|
|
418
|
+
currencyCode: option.amount.currency.code,
|
|
419
|
+
decimals
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
function resolveDecimals(option) {
|
|
424
|
+
if (typeof option.asset.decimals === "number") {
|
|
425
|
+
return option.asset.decimals;
|
|
426
|
+
}
|
|
427
|
+
if (typeof option.amount.currency.decimals === "number") {
|
|
428
|
+
return option.amount.currency.decimals;
|
|
429
|
+
}
|
|
430
|
+
throw new Error(
|
|
431
|
+
`Payment option '${option.id}' must specify asset.decimals or currency.decimals`
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
function decimalToBaseUnits(value, decimals) {
|
|
435
|
+
const [whole, fraction = ""] = value.split(".");
|
|
436
|
+
const sanitizedFraction = fraction.slice(0, decimals);
|
|
437
|
+
const paddedFraction = sanitizedFraction.padEnd(decimals, "0");
|
|
438
|
+
const combined = `${whole}${paddedFraction}`.replace(/^0+/, "");
|
|
439
|
+
return combined.length > 0 ? combined : "0";
|
|
440
|
+
}
|
|
441
|
+
function decodeJson(value, schema) {
|
|
442
|
+
const base64 = normalizeBase64(value);
|
|
443
|
+
const json = Buffer.from(base64, "base64").toString("utf-8");
|
|
444
|
+
const parsed = JSON.parse(json);
|
|
445
|
+
return schema.parse(parsed);
|
|
446
|
+
}
|
|
447
|
+
function encodeJson(value) {
|
|
448
|
+
const json = JSON.stringify(value);
|
|
449
|
+
return Buffer.from(json, "utf-8").toString("base64");
|
|
450
|
+
}
|
|
451
|
+
function normalizeBase64(input) {
|
|
452
|
+
if (/^[A-Za-z0-9+/=]+$/.test(input)) {
|
|
453
|
+
return input;
|
|
454
|
+
}
|
|
455
|
+
const restored = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
456
|
+
const paddingNeeded = (4 - restored.length % 4) % 4;
|
|
457
|
+
return restored + "=".repeat(paddingNeeded);
|
|
458
|
+
}
|
|
459
|
+
async function runFacilitatorVerifier({
|
|
460
|
+
attempt,
|
|
461
|
+
option,
|
|
462
|
+
definition,
|
|
463
|
+
settle,
|
|
464
|
+
fetchImpl
|
|
465
|
+
}) {
|
|
466
|
+
if (option.proof.mode !== "x402" || attempt.type !== "x402" || !option.proof.facilitator) {
|
|
467
|
+
return {
|
|
468
|
+
success: false,
|
|
469
|
+
optionId: option.id,
|
|
470
|
+
attemptType: attempt.type,
|
|
471
|
+
failure: paymentFailureSchema.parse({
|
|
472
|
+
reason: "Facilitator verifier invoked for non-x402 option",
|
|
473
|
+
code: "verification_failed",
|
|
474
|
+
retryable: false
|
|
475
|
+
})
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
const facilitator = option.proof.facilitator;
|
|
479
|
+
const verifierUrl = new URL(
|
|
480
|
+
facilitator.verifyPath ?? "/verify",
|
|
481
|
+
ensureTrailingSlash(facilitator.url)
|
|
482
|
+
).toString();
|
|
483
|
+
const requirement = toX402Requirement(option, definition.resource, option.settlement);
|
|
484
|
+
if (!requirement) {
|
|
485
|
+
return {
|
|
486
|
+
success: false,
|
|
487
|
+
optionId: option.id,
|
|
488
|
+
attemptType: attempt.type,
|
|
489
|
+
failure: paymentFailureSchema.parse({
|
|
490
|
+
reason: "Unable to derive x402 requirement for facilitator",
|
|
491
|
+
code: "verification_failed",
|
|
492
|
+
retryable: false
|
|
493
|
+
})
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
const headers = buildFacilitatorHeaders(facilitator);
|
|
497
|
+
const controller = facilitator.timeoutMs ? new AbortController() : void 0;
|
|
498
|
+
const timeout = facilitator.timeoutMs ? setTimeout(() => controller?.abort(), facilitator.timeoutMs) : void 0;
|
|
499
|
+
try {
|
|
500
|
+
const verifyResponse = await fetchImpl(verifierUrl, {
|
|
501
|
+
method: "POST",
|
|
502
|
+
headers,
|
|
503
|
+
body: JSON.stringify({
|
|
504
|
+
x402Version: attempt.payload.x402Version,
|
|
505
|
+
paymentHeader: attempt.raw,
|
|
506
|
+
paymentRequirements: requirement
|
|
507
|
+
}),
|
|
508
|
+
signal: controller?.signal ?? null
|
|
509
|
+
});
|
|
510
|
+
if (!verifyResponse.ok) {
|
|
511
|
+
return {
|
|
512
|
+
success: false,
|
|
513
|
+
optionId: option.id,
|
|
514
|
+
attemptType: attempt.type,
|
|
515
|
+
failure: paymentFailureSchema.parse({
|
|
516
|
+
reason: `Facilitator verify request failed: ${verifyResponse.status}`,
|
|
517
|
+
code: "verification_failed",
|
|
518
|
+
retryable: verifyResponse.status >= 500
|
|
519
|
+
})
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
const verifyPayload = await verifyResponse.json();
|
|
523
|
+
if (!verifyPayload.isValid) {
|
|
524
|
+
return {
|
|
525
|
+
success: false,
|
|
526
|
+
optionId: option.id,
|
|
527
|
+
attemptType: attempt.type,
|
|
528
|
+
failure: paymentFailureSchema.parse({
|
|
529
|
+
reason: verifyPayload.invalidReason ?? "Facilitator verification failed",
|
|
530
|
+
code: "verification_failed",
|
|
531
|
+
retryable: false
|
|
532
|
+
})
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
if (!settle) {
|
|
536
|
+
return {
|
|
537
|
+
success: true,
|
|
538
|
+
optionId: option.id,
|
|
539
|
+
attemptType: attempt.type,
|
|
540
|
+
metadata: paymentSuccessMetadataSchema.parse({
|
|
541
|
+
optionId: option.id,
|
|
542
|
+
verifier: facilitator.vendor ?? "facilitator"
|
|
543
|
+
})
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
const settleUrl = new URL(
|
|
547
|
+
facilitator.settlePath ?? "/settle",
|
|
548
|
+
ensureTrailingSlash(facilitator.url)
|
|
549
|
+
).toString();
|
|
550
|
+
const settleResponse = await fetchImpl(settleUrl, {
|
|
551
|
+
method: "POST",
|
|
552
|
+
headers,
|
|
553
|
+
body: JSON.stringify({
|
|
554
|
+
x402Version: attempt.payload.x402Version,
|
|
555
|
+
paymentHeader: attempt.raw,
|
|
556
|
+
paymentRequirements: requirement
|
|
557
|
+
}),
|
|
558
|
+
signal: controller?.signal ?? null
|
|
559
|
+
});
|
|
560
|
+
if (!settleResponse.ok) {
|
|
561
|
+
return {
|
|
562
|
+
success: false,
|
|
563
|
+
optionId: option.id,
|
|
564
|
+
attemptType: attempt.type,
|
|
565
|
+
failure: paymentFailureSchema.parse({
|
|
566
|
+
reason: `Facilitator settle request failed: ${settleResponse.status}`,
|
|
567
|
+
code: "verification_failed",
|
|
568
|
+
retryable: settleResponse.status >= 500
|
|
569
|
+
})
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
const settlePayload = await settleResponse.json();
|
|
573
|
+
if (!settlePayload.success) {
|
|
574
|
+
return {
|
|
575
|
+
success: false,
|
|
576
|
+
optionId: option.id,
|
|
577
|
+
attemptType: attempt.type,
|
|
578
|
+
failure: paymentFailureSchema.parse({
|
|
579
|
+
reason: settlePayload.error ?? "Facilitator settlement failed",
|
|
580
|
+
code: "verification_failed",
|
|
581
|
+
retryable: false
|
|
582
|
+
})
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
const metadata = paymentSuccessMetadataSchema.parse({
|
|
586
|
+
optionId: option.id,
|
|
587
|
+
verifier: facilitator.vendor ?? "facilitator",
|
|
588
|
+
txHash: settlePayload.txHash ?? void 0,
|
|
589
|
+
networkId: settlePayload.networkId ?? void 0
|
|
590
|
+
});
|
|
591
|
+
return {
|
|
592
|
+
success: true,
|
|
593
|
+
optionId: option.id,
|
|
594
|
+
attemptType: attempt.type,
|
|
595
|
+
metadata,
|
|
596
|
+
responseHeaders: {
|
|
597
|
+
[HEADER_PAYMENT_RESPONSE]: createPaymentResponseHeader(metadata)
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
} catch (error) {
|
|
601
|
+
return {
|
|
602
|
+
success: false,
|
|
603
|
+
optionId: option.id,
|
|
604
|
+
attemptType: attempt.type,
|
|
605
|
+
failure: paymentFailureSchema.parse({
|
|
606
|
+
reason: `Facilitator request error: ${error.message}`,
|
|
607
|
+
code: "verification_failed",
|
|
608
|
+
retryable: false
|
|
609
|
+
})
|
|
610
|
+
};
|
|
611
|
+
} finally {
|
|
612
|
+
if (timeout) {
|
|
613
|
+
clearTimeout(timeout);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
function buildFacilitatorHeaders(config) {
|
|
618
|
+
const headers = {
|
|
619
|
+
"content-type": "application/json"
|
|
620
|
+
};
|
|
621
|
+
if (config?.headers) {
|
|
622
|
+
Object.assign(headers, config.headers);
|
|
623
|
+
}
|
|
624
|
+
const apiKey = resolveFacilitatorApiKey(config);
|
|
625
|
+
if (apiKey) {
|
|
626
|
+
const headerName = config?.apiKeyHeader ?? "Authorization";
|
|
627
|
+
headers[headerName] = apiKey;
|
|
628
|
+
}
|
|
629
|
+
return headers;
|
|
630
|
+
}
|
|
631
|
+
function resolveFacilitatorApiKey(config) {
|
|
632
|
+
if (!config) {
|
|
633
|
+
return void 0;
|
|
634
|
+
}
|
|
635
|
+
if (config.apiKey) {
|
|
636
|
+
return config.apiKey;
|
|
637
|
+
}
|
|
638
|
+
if (config.apiKeyEnv && typeof process !== "undefined") {
|
|
639
|
+
return process.env?.[config.apiKeyEnv];
|
|
640
|
+
}
|
|
641
|
+
return void 0;
|
|
642
|
+
}
|
|
643
|
+
function ensureTrailingSlash(value) {
|
|
644
|
+
return value.endsWith("/") ? value : `${value}/`;
|
|
645
|
+
}
|
|
646
|
+
var PAYMENT_HEADERS = {
|
|
647
|
+
x402: HEADER_X402,
|
|
648
|
+
direct: HEADER_DIRECT,
|
|
649
|
+
response: HEADER_PAYMENT_RESPONSE
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
// src/payment/index.ts
|
|
653
|
+
var DEFAULT_ID_X402 = "x402";
|
|
654
|
+
var DEFAULT_ID_402 = "402";
|
|
655
|
+
var SUPPORTED_CURRENCIES = {
|
|
656
|
+
USDC: {
|
|
657
|
+
decimals: 6,
|
|
658
|
+
symbol: "USDC",
|
|
659
|
+
x402: {
|
|
660
|
+
network: "base",
|
|
661
|
+
assetAddress: "0x833589fCD6eDb6E08f4c7C37b7b4c6e997E08A43"
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
var DEFAULT_FACILITATORS = {
|
|
666
|
+
opentool: "https://facilitator.opentool.dev/x402",
|
|
667
|
+
coinbase: "https://payments.coinbase.com/x402"
|
|
668
|
+
};
|
|
669
|
+
var PAYMENT_CONTEXT_SYMBOL = Symbol.for("opentool.payment.context");
|
|
670
|
+
var PaymentRequiredError = class extends Error {
|
|
671
|
+
constructor(response, verification) {
|
|
672
|
+
super("Payment required");
|
|
673
|
+
this.name = "PaymentRequiredError";
|
|
674
|
+
this.response = response;
|
|
675
|
+
this.verification = verification;
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
function setPaymentContext(request, context) {
|
|
679
|
+
try {
|
|
680
|
+
Object.defineProperty(request, PAYMENT_CONTEXT_SYMBOL, {
|
|
681
|
+
value: context,
|
|
682
|
+
configurable: true,
|
|
683
|
+
enumerable: false,
|
|
684
|
+
writable: true
|
|
685
|
+
});
|
|
686
|
+
} catch {
|
|
687
|
+
request[PAYMENT_CONTEXT_SYMBOL] = context;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
function getPaymentContext(request) {
|
|
691
|
+
return request[PAYMENT_CONTEXT_SYMBOL];
|
|
692
|
+
}
|
|
693
|
+
function applyPaymentHeaders(response, headers) {
|
|
694
|
+
const entries = Object.entries(headers ?? {});
|
|
695
|
+
if (entries.length === 0) {
|
|
696
|
+
return response;
|
|
697
|
+
}
|
|
698
|
+
let mutated = false;
|
|
699
|
+
const merged = new Headers(response.headers);
|
|
700
|
+
for (const [key, value] of entries) {
|
|
701
|
+
if (!merged.has(key)) {
|
|
702
|
+
merged.set(key, value);
|
|
703
|
+
mutated = true;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (!mutated) {
|
|
707
|
+
return response;
|
|
708
|
+
}
|
|
709
|
+
return new Response(response.body, {
|
|
710
|
+
status: response.status,
|
|
711
|
+
statusText: response.statusText,
|
|
712
|
+
headers: merged
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
function withPaymentRequirement(handler, payment, options = {}) {
|
|
716
|
+
return async (request) => {
|
|
717
|
+
const verification = await requirePayment(request, payment, options);
|
|
718
|
+
if (verification instanceof Response) {
|
|
719
|
+
return verification;
|
|
720
|
+
}
|
|
721
|
+
setPaymentContext(request, verification);
|
|
722
|
+
const response = await Promise.resolve(handler(request));
|
|
723
|
+
return applyPaymentHeaders(response, verification.headers);
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function definePayment(config) {
|
|
727
|
+
const verifiers = {
|
|
728
|
+
...config.verifiers ?? {}
|
|
729
|
+
};
|
|
730
|
+
const methods = config.acceptedMethods ?? ["402"];
|
|
731
|
+
const includeX402 = methods.includes("x402");
|
|
732
|
+
const includePlain402 = methods.includes("402");
|
|
733
|
+
if (!includeX402 && !includePlain402) {
|
|
734
|
+
throw new Error(
|
|
735
|
+
"definePayment requires at least one payment transport (x402 or 402)"
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
const currencyCode = normalizeCurrency(config.currency);
|
|
739
|
+
const currencySpec = SUPPORTED_CURRENCIES[currencyCode];
|
|
740
|
+
if (!currencySpec) {
|
|
741
|
+
throw new Error(`Unsupported currency for payments: ${currencyCode}`);
|
|
742
|
+
}
|
|
743
|
+
const decimals = currencySpec.decimals;
|
|
744
|
+
const symbol = currencySpec.symbol;
|
|
745
|
+
const value = toDecimalString(config.amount);
|
|
746
|
+
const accepts = [];
|
|
747
|
+
if (includeX402) {
|
|
748
|
+
const overrides = config.x402 ?? {};
|
|
749
|
+
const defaults = currencySpec.x402;
|
|
750
|
+
if (!defaults && (!overrides.network || !overrides.assetAddress)) {
|
|
751
|
+
throw new Error(
|
|
752
|
+
"x402 payments require a network and assetAddress; supply them or choose a supported currency."
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
const facilitator = resolveFacilitator(
|
|
756
|
+
config.facilitator ?? overrides.facilitator ?? "opentool"
|
|
757
|
+
);
|
|
758
|
+
accepts.push(
|
|
759
|
+
paymentOptionSchema.parse({
|
|
760
|
+
id: overrides.id ?? DEFAULT_ID_X402,
|
|
761
|
+
title: `Pay ${value} ${currencyCode}`,
|
|
762
|
+
amount: {
|
|
763
|
+
value,
|
|
764
|
+
currency: { code: currencyCode, symbol, decimals }
|
|
765
|
+
},
|
|
766
|
+
asset: {
|
|
767
|
+
symbol,
|
|
768
|
+
network: overrides.network ?? defaults?.network ?? "",
|
|
769
|
+
address: overrides.assetAddress ?? defaults?.assetAddress ?? "",
|
|
770
|
+
decimals,
|
|
771
|
+
standard: "erc20"
|
|
772
|
+
},
|
|
773
|
+
payTo: config.payTo,
|
|
774
|
+
proof: {
|
|
775
|
+
mode: "x402",
|
|
776
|
+
network: overrides.network ?? defaults?.network ?? "",
|
|
777
|
+
scheme: overrides.scheme ?? "exact",
|
|
778
|
+
version: overrides.version ?? 1,
|
|
779
|
+
facilitator,
|
|
780
|
+
verifier: facilitator ? "x402:facilitator" : void 0
|
|
781
|
+
},
|
|
782
|
+
settlement: overrides.settlement
|
|
783
|
+
})
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
if (includePlain402) {
|
|
787
|
+
const overrides = config.direct ?? {};
|
|
788
|
+
const id = overrides.id ?? DEFAULT_ID_402;
|
|
789
|
+
const verifierId = overrides.verifierId ?? `direct:${id}`;
|
|
790
|
+
const proofType = overrides.proofType ?? id;
|
|
791
|
+
const verifier = overrides.verify ?? buildDefaultDirectVerifier(overrides.token, verifierId, id);
|
|
792
|
+
verifiers[verifierId] = verifier;
|
|
793
|
+
accepts.push(
|
|
794
|
+
paymentOptionSchema.parse({
|
|
795
|
+
id,
|
|
796
|
+
title: `Pay ${value} ${currencyCode}`,
|
|
797
|
+
amount: {
|
|
798
|
+
value,
|
|
799
|
+
currency: { code: currencyCode, symbol, decimals }
|
|
800
|
+
},
|
|
801
|
+
asset: {
|
|
802
|
+
symbol,
|
|
803
|
+
decimals,
|
|
804
|
+
standard: "erc20"
|
|
805
|
+
},
|
|
806
|
+
payTo: config.payTo,
|
|
807
|
+
proof: {
|
|
808
|
+
mode: "direct",
|
|
809
|
+
proofTypes: [proofType],
|
|
810
|
+
verifier: verifierId,
|
|
811
|
+
instructions: overrides.instructions,
|
|
812
|
+
fields: overrides.fields,
|
|
813
|
+
allowsManualReview: overrides.allowsManualReview
|
|
814
|
+
},
|
|
815
|
+
settlement: overrides.settlement
|
|
816
|
+
})
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
const facilitatorLabel = includeX402 ? resolveFacilitatorLabel(config.facilitator ?? config.x402?.facilitator) : void 0;
|
|
820
|
+
const baseMetadata = {};
|
|
821
|
+
if (currencyCode === "USDC") {
|
|
822
|
+
baseMetadata.amountUSDC = Number(value);
|
|
823
|
+
}
|
|
824
|
+
baseMetadata.x402 = includeX402;
|
|
825
|
+
baseMetadata.plain402 = includePlain402;
|
|
826
|
+
baseMetadata.acceptedMethods = methods;
|
|
827
|
+
baseMetadata.acceptedCurrencies = config.acceptedCurrencies ?? [currencyCode];
|
|
828
|
+
if (config.chainIds) {
|
|
829
|
+
baseMetadata.chainIds = config.chainIds;
|
|
830
|
+
}
|
|
831
|
+
if (facilitatorLabel) {
|
|
832
|
+
baseMetadata.facilitator = facilitatorLabel;
|
|
833
|
+
}
|
|
834
|
+
const metadata = config.metadata ? { ...baseMetadata, ...config.metadata } : baseMetadata;
|
|
835
|
+
const definition = {
|
|
836
|
+
schemaVersion: PAYMENT_SCHEMA_VERSION,
|
|
837
|
+
accepts,
|
|
838
|
+
metadata
|
|
839
|
+
};
|
|
840
|
+
if (config.message !== void 0) {
|
|
841
|
+
definition.message = config.message;
|
|
842
|
+
}
|
|
843
|
+
if (config.resource !== void 0) {
|
|
844
|
+
definition.resource = config.resource;
|
|
845
|
+
}
|
|
846
|
+
const defined = {
|
|
847
|
+
definition,
|
|
848
|
+
verifiers,
|
|
849
|
+
metadata
|
|
850
|
+
};
|
|
851
|
+
if (config.message !== void 0) {
|
|
852
|
+
defined.message = config.message;
|
|
853
|
+
}
|
|
854
|
+
return defined;
|
|
855
|
+
}
|
|
856
|
+
async function requirePayment(request, payment, options = {}) {
|
|
857
|
+
const { definition, verifiers } = normalizePayment(payment);
|
|
858
|
+
const mergedVerifiers = {
|
|
859
|
+
...verifiers,
|
|
860
|
+
...options.verifiers ?? {}
|
|
861
|
+
};
|
|
862
|
+
const verifyOptions = {
|
|
863
|
+
definition,
|
|
864
|
+
request
|
|
865
|
+
};
|
|
866
|
+
if (Object.keys(mergedVerifiers).length > 0) {
|
|
867
|
+
verifyOptions.verifiers = mergedVerifiers;
|
|
868
|
+
}
|
|
869
|
+
if (options.settle !== void 0) {
|
|
870
|
+
verifyOptions.settle = options.settle;
|
|
871
|
+
}
|
|
872
|
+
if (options.fetchImpl) {
|
|
873
|
+
verifyOptions.fetchImpl = options.fetchImpl;
|
|
874
|
+
}
|
|
875
|
+
const verification = await verifyPayment(verifyOptions);
|
|
876
|
+
if (!verification.success || !verification.metadata) {
|
|
877
|
+
if (options.onFailure) {
|
|
878
|
+
return options.onFailure(verification);
|
|
879
|
+
}
|
|
880
|
+
const response = paymentRequiredResponse(definition);
|
|
881
|
+
throw new PaymentRequiredError(response, verification);
|
|
882
|
+
}
|
|
883
|
+
return {
|
|
884
|
+
payment: verification.metadata,
|
|
885
|
+
headers: verification.responseHeaders ?? {},
|
|
886
|
+
optionId: verification.optionId,
|
|
887
|
+
result: verification
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
function normalizePayment(payment) {
|
|
891
|
+
if (isDefinedPayment(payment)) {
|
|
892
|
+
return {
|
|
893
|
+
definition: payment.definition,
|
|
894
|
+
verifiers: payment.verifiers ?? {}
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
return {
|
|
898
|
+
definition: payment,
|
|
899
|
+
verifiers: {}
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
function isDefinedPayment(value) {
|
|
903
|
+
return !!value && typeof value === "object" && "definition" in value && value.definition !== void 0;
|
|
904
|
+
}
|
|
905
|
+
function resolveFacilitator(value) {
|
|
906
|
+
if (!value) {
|
|
907
|
+
return void 0;
|
|
908
|
+
}
|
|
909
|
+
if (typeof value === "string") {
|
|
910
|
+
if (value in DEFAULT_FACILITATORS) {
|
|
911
|
+
return {
|
|
912
|
+
url: DEFAULT_FACILITATORS[value]
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
return { url: value };
|
|
916
|
+
}
|
|
917
|
+
return value;
|
|
918
|
+
}
|
|
919
|
+
function resolveFacilitatorLabel(value) {
|
|
920
|
+
if (!value) {
|
|
921
|
+
return "opentool";
|
|
922
|
+
}
|
|
923
|
+
if (typeof value === "string") {
|
|
924
|
+
if (value === "opentool" || value === "coinbase") {
|
|
925
|
+
return value;
|
|
926
|
+
}
|
|
927
|
+
return "custom";
|
|
928
|
+
}
|
|
929
|
+
return "custom";
|
|
930
|
+
}
|
|
931
|
+
function normalizeCurrency(currency) {
|
|
932
|
+
return (currency ?? "USDC").toUpperCase();
|
|
933
|
+
}
|
|
934
|
+
function toDecimalString(value) {
|
|
935
|
+
return typeof value === "number" ? value.toString() : value;
|
|
936
|
+
}
|
|
937
|
+
function buildDefaultDirectVerifier(expectedToken, verifierId, optionId) {
|
|
938
|
+
return async ({ attempt, option }) => {
|
|
939
|
+
if (attempt.type !== "direct") {
|
|
940
|
+
return {
|
|
941
|
+
success: false,
|
|
942
|
+
optionId: option.id,
|
|
943
|
+
attemptType: attempt.type,
|
|
944
|
+
failure: {
|
|
945
|
+
reason: "Expected direct payment payload",
|
|
946
|
+
code: "invalid_payload"
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
const payload = attempt.payload.payload;
|
|
951
|
+
if (expectedToken) {
|
|
952
|
+
if (payload?.token !== expectedToken) {
|
|
953
|
+
return {
|
|
954
|
+
success: false,
|
|
955
|
+
optionId: option.id,
|
|
956
|
+
attemptType: attempt.type,
|
|
957
|
+
failure: {
|
|
958
|
+
reason: "Invalid or missing payment proof",
|
|
959
|
+
code: "verification_failed"
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
} else if (!payload) {
|
|
964
|
+
return {
|
|
965
|
+
success: false,
|
|
966
|
+
optionId: option.id,
|
|
967
|
+
attemptType: attempt.type,
|
|
968
|
+
failure: {
|
|
969
|
+
reason: "Payment proof is required",
|
|
970
|
+
code: "verification_failed"
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
return {
|
|
975
|
+
success: true,
|
|
976
|
+
optionId,
|
|
977
|
+
attemptType: attempt.type,
|
|
978
|
+
metadata: {
|
|
979
|
+
optionId,
|
|
980
|
+
verifier: verifierId,
|
|
981
|
+
payload
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// src/adapters/mcp.ts
|
|
988
|
+
var HTTP_METHODS = [
|
|
989
|
+
"GET",
|
|
990
|
+
"HEAD",
|
|
991
|
+
"POST",
|
|
992
|
+
"PUT",
|
|
993
|
+
"DELETE",
|
|
994
|
+
"PATCH",
|
|
995
|
+
"OPTIONS"
|
|
996
|
+
];
|
|
997
|
+
function createMcpAdapter(options) {
|
|
998
|
+
const normalizedSchema = ensureSchema(options.schema);
|
|
999
|
+
const defaultMethod = resolveDefaultMethod(options);
|
|
1000
|
+
const httpHandler = options.httpHandlers[defaultMethod];
|
|
1001
|
+
if (!httpHandler) {
|
|
1002
|
+
throw new Error(
|
|
1003
|
+
`Tool "${options.name}" does not export an HTTP handler for ${defaultMethod}`
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
return async function invoke(rawArguments) {
|
|
1007
|
+
const validated = normalizedSchema ? normalizedSchema.parse(rawArguments ?? {}) : rawArguments;
|
|
1008
|
+
const request = buildRequest(options.name, defaultMethod, validated);
|
|
1009
|
+
try {
|
|
1010
|
+
const response = await Promise.resolve(httpHandler(request));
|
|
1011
|
+
return await responseToToolResponse(response);
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
if (error instanceof PaymentRequiredError) {
|
|
1014
|
+
return await responseToToolResponse(error.response);
|
|
1015
|
+
}
|
|
1016
|
+
throw error;
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
function resolveDefaultMethod(options) {
|
|
1021
|
+
const explicit = options.defaultMethod?.toUpperCase();
|
|
1022
|
+
if (explicit && typeof options.httpHandlers[explicit] === "function") {
|
|
1023
|
+
return explicit;
|
|
1024
|
+
}
|
|
1025
|
+
const preferredOrder = ["POST", "PUT", "PATCH", "GET", "DELETE", "OPTIONS", "HEAD"];
|
|
1026
|
+
for (const method of preferredOrder) {
|
|
1027
|
+
if (typeof options.httpHandlers[method] === "function") {
|
|
1028
|
+
return method;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
const available = Object.keys(options.httpHandlers).filter(
|
|
1032
|
+
(method) => typeof options.httpHandlers[method] === "function"
|
|
1033
|
+
);
|
|
1034
|
+
if (available.length > 0) {
|
|
1035
|
+
return available[0];
|
|
1036
|
+
}
|
|
1037
|
+
throw new Error(`No HTTP handlers available for tool "${options.name}"`);
|
|
1038
|
+
}
|
|
1039
|
+
function ensureSchema(schema) {
|
|
1040
|
+
if (!schema) {
|
|
1041
|
+
return void 0;
|
|
1042
|
+
}
|
|
1043
|
+
if (schema instanceof z.ZodType) {
|
|
1044
|
+
return schema;
|
|
1045
|
+
}
|
|
1046
|
+
if (typeof schema?.parse === "function") {
|
|
1047
|
+
return schema;
|
|
1048
|
+
}
|
|
1049
|
+
throw new Error("MCP adapter requires a valid Zod schema to validate arguments");
|
|
1050
|
+
}
|
|
1051
|
+
function buildRequest(name, method, params) {
|
|
1052
|
+
const url = new URL(`https://opentool.local/${encodeURIComponent(name)}`);
|
|
1053
|
+
const headers = new Headers({
|
|
1054
|
+
"x-opentool-invocation": "mcp",
|
|
1055
|
+
"x-opentool-tool": name
|
|
1056
|
+
});
|
|
1057
|
+
if (method === "GET" || method === "HEAD") {
|
|
1058
|
+
if (params && typeof params === "object") {
|
|
1059
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
1060
|
+
if (value == null) {
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
url.searchParams.set(key, String(value));
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
return new Request(url, { method, headers });
|
|
1067
|
+
}
|
|
1068
|
+
headers.set("Content-Type", "application/json");
|
|
1069
|
+
const init = { method, headers };
|
|
1070
|
+
if (params != null) {
|
|
1071
|
+
init.body = JSON.stringify(params);
|
|
1072
|
+
}
|
|
1073
|
+
return new Request(url, init);
|
|
1074
|
+
}
|
|
1075
|
+
async function responseToToolResponse(response) {
|
|
1076
|
+
const statusIsError = response.status >= 400;
|
|
1077
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1078
|
+
const text = await response.text();
|
|
1079
|
+
if (contentType.includes("application/json")) {
|
|
1080
|
+
try {
|
|
1081
|
+
const payload = text ? JSON.parse(text) : {};
|
|
1082
|
+
if (payload && typeof payload === "object" && Array.isArray(payload.content)) {
|
|
1083
|
+
return {
|
|
1084
|
+
content: payload.content,
|
|
1085
|
+
isError: payload.isError ?? statusIsError
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
return {
|
|
1089
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
1090
|
+
isError: statusIsError
|
|
1091
|
+
};
|
|
1092
|
+
} catch {
|
|
1093
|
+
return {
|
|
1094
|
+
content: [{ type: "text", text }],
|
|
1095
|
+
isError: statusIsError
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
if (!text) {
|
|
1100
|
+
return {
|
|
1101
|
+
content: [],
|
|
1102
|
+
isError: statusIsError
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
return {
|
|
1106
|
+
content: [{ type: "text", text }],
|
|
1107
|
+
isError: statusIsError
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// src/runtime/index.ts
|
|
1112
|
+
function createDevServer(tools) {
|
|
1113
|
+
const metadata = loadMetadata();
|
|
1114
|
+
const metadataMap = buildMetadataMap(metadata);
|
|
1115
|
+
const adapters = buildAdapters(tools);
|
|
1116
|
+
const server = new Server(
|
|
1117
|
+
{
|
|
1118
|
+
name: "opentool-dev",
|
|
1119
|
+
version: "1.0.0"
|
|
1120
|
+
},
|
|
1121
|
+
{
|
|
1122
|
+
capabilities: {
|
|
1123
|
+
tools: {}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
);
|
|
1127
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
1128
|
+
tools: adapters.map(({ tool }) => serializeTool(tool, metadataMap))
|
|
1129
|
+
}));
|
|
1130
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1131
|
+
const entry = adapters.find(({ tool }) => {
|
|
1132
|
+
const toolName = tool.metadata?.name || tool.filename;
|
|
1133
|
+
return toolName === request.params.name;
|
|
1134
|
+
});
|
|
1135
|
+
if (!entry) {
|
|
1136
|
+
throw new Error(`Tool ${request.params.name} not found or not MCP-enabled`);
|
|
1137
|
+
}
|
|
1138
|
+
try {
|
|
1139
|
+
return await entry.invoke(request.params.arguments);
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
const message = error && error.message || String(error);
|
|
1142
|
+
return {
|
|
1143
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
1144
|
+
isError: true
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
return server;
|
|
1149
|
+
}
|
|
1150
|
+
async function createStdioServer(tools) {
|
|
1151
|
+
const metadata = loadMetadata();
|
|
1152
|
+
const metadataMap = buildMetadataMap(metadata);
|
|
1153
|
+
const toolDefinitions = tools || await loadToolsFromDirectory(metadataMap);
|
|
1154
|
+
const adapters = buildAdapters(toolDefinitions);
|
|
1155
|
+
const server = new Server(
|
|
1156
|
+
{
|
|
1157
|
+
name: "opentool-runtime",
|
|
1158
|
+
version: "1.0.0"
|
|
1159
|
+
},
|
|
1160
|
+
{
|
|
1161
|
+
capabilities: {
|
|
1162
|
+
tools: {}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
);
|
|
1166
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
1167
|
+
tools: adapters.map(({ tool }) => serializeTool(tool, metadataMap))
|
|
1168
|
+
}));
|
|
1169
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1170
|
+
const entry = adapters.find(({ tool }) => {
|
|
1171
|
+
const toolName = tool.metadata?.name || tool.filename;
|
|
1172
|
+
return toolName === request.params.name;
|
|
1173
|
+
});
|
|
1174
|
+
if (!entry) {
|
|
1175
|
+
throw new Error(`Tool ${request.params.name} not found or not MCP-enabled`);
|
|
1176
|
+
}
|
|
1177
|
+
try {
|
|
1178
|
+
return await entry.invoke(request.params.arguments);
|
|
1179
|
+
} catch (error) {
|
|
1180
|
+
const message = error && error.message || String(error);
|
|
1181
|
+
return {
|
|
1182
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
1183
|
+
isError: true
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
const transport = new StdioServerTransport();
|
|
1188
|
+
await server.connect(transport);
|
|
1189
|
+
console.error("MCP stdio server started");
|
|
1190
|
+
}
|
|
1191
|
+
function buildAdapters(tools) {
|
|
1192
|
+
return tools.filter((tool) => isMcpEnabled(tool)).map((tool) => {
|
|
1193
|
+
const httpHandlers = toHttpHandlerMap(tool.httpHandlers);
|
|
1194
|
+
const adapterOptions = {
|
|
1195
|
+
name: tool.metadata?.name || tool.filename,
|
|
1196
|
+
httpHandlers,
|
|
1197
|
+
...tool.schema ? { schema: tool.schema } : {},
|
|
1198
|
+
...tool.mcpConfig?.defaultMethod ? { defaultMethod: tool.mcpConfig.defaultMethod } : {}
|
|
1199
|
+
};
|
|
1200
|
+
const adapter = createMcpAdapter(adapterOptions);
|
|
1201
|
+
return {
|
|
1202
|
+
tool,
|
|
1203
|
+
invoke: adapter
|
|
1204
|
+
};
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
async function loadToolsFromDirectory(metadataMap) {
|
|
1208
|
+
const tools = [];
|
|
1209
|
+
const toolsDir = path5.join(process.cwd(), "tools");
|
|
1210
|
+
if (!fs2.existsSync(toolsDir)) {
|
|
1211
|
+
return tools;
|
|
1212
|
+
}
|
|
1213
|
+
const files = fs2.readdirSync(toolsDir);
|
|
1214
|
+
for (const file of files) {
|
|
1215
|
+
if (!isSupportedToolFile(file)) {
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
const toolPath = path5.join(toolsDir, file);
|
|
1219
|
+
try {
|
|
1220
|
+
const exportsObject = __require(toolPath);
|
|
1221
|
+
const candidate = resolveModuleCandidate(exportsObject);
|
|
1222
|
+
if (!candidate?.schema) {
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
const baseName = file.replace(/\.[^.]+$/, "");
|
|
1226
|
+
const name = candidate.metadata?.name || baseName;
|
|
1227
|
+
const meta = metadataMap.get(name);
|
|
1228
|
+
let inputSchema = meta?.inputSchema;
|
|
1229
|
+
if (!inputSchema) {
|
|
1230
|
+
try {
|
|
1231
|
+
inputSchema = zodToJsonSchema(candidate.schema, {
|
|
1232
|
+
name: `${name}Schema`,
|
|
1233
|
+
target: "jsonSchema7",
|
|
1234
|
+
$refStrategy: "none"
|
|
1235
|
+
});
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
inputSchema = { type: "object" };
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
inputSchema = normalizeInputSchema(inputSchema);
|
|
1241
|
+
const payment = candidate.payment ?? null;
|
|
1242
|
+
const httpHandlersRaw = collectHttpHandlers(candidate);
|
|
1243
|
+
const httpHandlers = [...httpHandlersRaw];
|
|
1244
|
+
if (httpHandlers.length === 0) {
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
if (payment) {
|
|
1248
|
+
for (let index = 0; index < httpHandlers.length; index += 1) {
|
|
1249
|
+
const entry = httpHandlers[index];
|
|
1250
|
+
httpHandlers[index] = {
|
|
1251
|
+
...entry,
|
|
1252
|
+
handler: withPaymentRequirement(entry.handler, payment)
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
const mcpConfig = normalizeRuntimeMcpConfig(candidate.mcp);
|
|
1257
|
+
const adapterOptions = {
|
|
1258
|
+
name,
|
|
1259
|
+
httpHandlers: toHttpHandlerMap(httpHandlers),
|
|
1260
|
+
...candidate.schema ? { schema: candidate.schema } : {},
|
|
1261
|
+
...typeof candidate.mcp?.defaultMethod === "string" ? { defaultMethod: candidate.mcp.defaultMethod } : {}
|
|
1262
|
+
};
|
|
1263
|
+
const adapter = createMcpAdapter(adapterOptions);
|
|
1264
|
+
const tool = {
|
|
1265
|
+
...candidate.schema ? { schema: candidate.schema } : {},
|
|
1266
|
+
inputSchema,
|
|
1267
|
+
metadata: candidate.metadata || meta || null,
|
|
1268
|
+
filename: baseName,
|
|
1269
|
+
httpHandlers,
|
|
1270
|
+
mcpConfig,
|
|
1271
|
+
handler: async (params) => adapter(params),
|
|
1272
|
+
payment
|
|
1273
|
+
};
|
|
1274
|
+
tools.push(tool);
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
console.warn(`Failed to load tool from ${file}: ${error}`);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
return tools;
|
|
1280
|
+
}
|
|
1281
|
+
function loadMetadata() {
|
|
1282
|
+
const metadataPath = path5.join(process.cwd(), "metadata.json");
|
|
1283
|
+
if (!fs2.existsSync(metadataPath)) {
|
|
1284
|
+
return null;
|
|
1285
|
+
}
|
|
1286
|
+
try {
|
|
1287
|
+
const contents = fs2.readFileSync(metadataPath, "utf8");
|
|
1288
|
+
return JSON.parse(contents);
|
|
1289
|
+
} catch (error) {
|
|
1290
|
+
console.warn(`Failed to parse metadata.json: ${error}`);
|
|
1291
|
+
return null;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
function buildMetadataMap(metadata) {
|
|
1295
|
+
const map = /* @__PURE__ */ new Map();
|
|
1296
|
+
if (!metadata?.tools) {
|
|
1297
|
+
return map;
|
|
1298
|
+
}
|
|
1299
|
+
metadata.tools.forEach((tool) => {
|
|
1300
|
+
map.set(tool.name, tool);
|
|
1301
|
+
});
|
|
1302
|
+
return map;
|
|
1303
|
+
}
|
|
1304
|
+
function serializeTool(tool, metadataMap) {
|
|
1305
|
+
const name = tool.metadata?.name || tool.filename;
|
|
1306
|
+
const meta = metadataMap.get(name);
|
|
1307
|
+
return {
|
|
1308
|
+
name,
|
|
1309
|
+
description: meta?.description || tool.metadata?.description || `${tool.filename} tool`,
|
|
1310
|
+
inputSchema: meta?.inputSchema || tool.inputSchema,
|
|
1311
|
+
annotations: meta?.annotations || tool.metadata?.annotations,
|
|
1312
|
+
payment: meta?.payment || tool.metadata?.payment,
|
|
1313
|
+
discovery: meta?.discovery || tool.metadata?.discovery
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
function isSupportedToolFile(file) {
|
|
1317
|
+
return /\.(cjs|mjs|js|ts)$/i.test(file);
|
|
1318
|
+
}
|
|
1319
|
+
function resolveModuleCandidate(exportsObject) {
|
|
1320
|
+
if (!exportsObject) {
|
|
1321
|
+
return null;
|
|
1322
|
+
}
|
|
1323
|
+
if (exportsObject.schema) {
|
|
1324
|
+
return exportsObject;
|
|
1325
|
+
}
|
|
1326
|
+
if (exportsObject.default && exportsObject.default.schema) {
|
|
1327
|
+
return exportsObject.default;
|
|
1328
|
+
}
|
|
1329
|
+
return exportsObject;
|
|
1330
|
+
}
|
|
1331
|
+
function collectHttpHandlers(module) {
|
|
1332
|
+
const handlers = [];
|
|
1333
|
+
HTTP_METHODS.forEach((method) => {
|
|
1334
|
+
const handler = module?.[method];
|
|
1335
|
+
if (typeof handler === "function") {
|
|
1336
|
+
handlers.push({
|
|
1337
|
+
method,
|
|
1338
|
+
handler: async (request) => handler.call(module, request)
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
});
|
|
1342
|
+
return handlers;
|
|
1343
|
+
}
|
|
1344
|
+
function toHttpHandlerMap(handlers) {
|
|
1345
|
+
return handlers.reduce((acc, handler) => {
|
|
1346
|
+
acc[handler.method.toUpperCase()] = handler.handler;
|
|
1347
|
+
return acc;
|
|
1348
|
+
}, {});
|
|
1349
|
+
}
|
|
1350
|
+
function normalizeInputSchema(schema) {
|
|
1351
|
+
if (!schema || typeof schema !== "object") {
|
|
1352
|
+
return schema;
|
|
1353
|
+
}
|
|
1354
|
+
const clone = JSON.parse(JSON.stringify(schema));
|
|
1355
|
+
if (typeof clone.$ref === "string" && clone.$ref.startsWith("#/definitions/")) {
|
|
1356
|
+
const refKey = clone.$ref.replace("#/definitions/", "");
|
|
1357
|
+
if (clone.definitions && typeof clone.definitions[refKey] === "object") {
|
|
1358
|
+
return normalizeInputSchema(clone.definitions[refKey]);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
delete clone.$ref;
|
|
1362
|
+
delete clone.definitions;
|
|
1363
|
+
if (!clone.type) {
|
|
1364
|
+
clone.type = "object";
|
|
1365
|
+
}
|
|
1366
|
+
return clone;
|
|
1367
|
+
}
|
|
1368
|
+
function normalizeRuntimeMcpConfig(rawConfig) {
|
|
1369
|
+
if (isPlainObject(rawConfig) && rawConfig.enabled === true) {
|
|
1370
|
+
let normalizedMode;
|
|
1371
|
+
if (typeof rawConfig.mode === "string") {
|
|
1372
|
+
const candidate = rawConfig.mode.toLowerCase();
|
|
1373
|
+
if (candidate === "stdio" || candidate === "lambda" || candidate === "dual") {
|
|
1374
|
+
normalizedMode = candidate;
|
|
1375
|
+
} else {
|
|
1376
|
+
throw new Error('mcp.mode must be one of "stdio", "lambda", or "dual"');
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
const metadataOverrides = isPlainObject(rawConfig.metadataOverrides) ? rawConfig.metadataOverrides : void 0;
|
|
1380
|
+
const config = { enabled: true };
|
|
1381
|
+
if (normalizedMode) {
|
|
1382
|
+
config.mode = normalizedMode;
|
|
1383
|
+
}
|
|
1384
|
+
if (typeof rawConfig.defaultMethod === "string") {
|
|
1385
|
+
config.defaultMethod = rawConfig.defaultMethod.toUpperCase();
|
|
1386
|
+
}
|
|
1387
|
+
if (metadataOverrides) {
|
|
1388
|
+
config.metadataOverrides = metadataOverrides;
|
|
1389
|
+
}
|
|
1390
|
+
return config;
|
|
1391
|
+
}
|
|
1392
|
+
return null;
|
|
1393
|
+
}
|
|
1394
|
+
function isPlainObject(value) {
|
|
1395
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1396
|
+
}
|
|
1397
|
+
function isMcpEnabled(tool) {
|
|
1398
|
+
return Boolean(tool.mcpConfig?.enabled);
|
|
1399
|
+
}
|
|
1400
|
+
function resolveRuntimePath(value) {
|
|
1401
|
+
if (value.startsWith("file://")) {
|
|
1402
|
+
return fileURLToPath(value);
|
|
1403
|
+
}
|
|
1404
|
+
return path5.resolve(value);
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
// src/types/index.ts
|
|
1408
|
+
var HTTP_METHODS2 = [
|
|
1409
|
+
"GET",
|
|
1410
|
+
"HEAD",
|
|
1411
|
+
"POST",
|
|
1412
|
+
"PUT",
|
|
1413
|
+
"DELETE",
|
|
1414
|
+
"PATCH",
|
|
1415
|
+
"OPTIONS"
|
|
1416
|
+
];
|
|
1417
|
+
var BASE_ALCHEMY_HOST = "https://base-mainnet.g.alchemy.com/v2/";
|
|
1418
|
+
var ETHEREUM_ALCHEMY_HOST = "https://eth-mainnet.g.alchemy.com/v2/";
|
|
1419
|
+
var BASE_SEPOLIA_ALCHEMY_HOST = "https://base-sepolia.g.alchemy.com/v2/";
|
|
1420
|
+
function buildRpcResolver(host, fallbackUrls) {
|
|
1421
|
+
return (options) => {
|
|
1422
|
+
if (options?.url) {
|
|
1423
|
+
return options.url;
|
|
1424
|
+
}
|
|
1425
|
+
if (options?.apiKey) {
|
|
1426
|
+
return `${host}${options.apiKey}`;
|
|
1427
|
+
}
|
|
1428
|
+
if (fallbackUrls.length > 0) {
|
|
1429
|
+
return fallbackUrls[0];
|
|
1430
|
+
}
|
|
1431
|
+
throw new Error(
|
|
1432
|
+
"No RPC URL available: supply a full url via options or an apiKey for the default host"
|
|
1433
|
+
);
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
var chains = {
|
|
1437
|
+
base: {
|
|
1438
|
+
id: base.id,
|
|
1439
|
+
slug: "base",
|
|
1440
|
+
name: "Base",
|
|
1441
|
+
chain: base,
|
|
1442
|
+
rpcUrl: buildRpcResolver(BASE_ALCHEMY_HOST, base.rpcUrls.default.http),
|
|
1443
|
+
publicRpcUrls: base.rpcUrls.default.http
|
|
1444
|
+
},
|
|
1445
|
+
ethereum: {
|
|
1446
|
+
id: mainnet.id,
|
|
1447
|
+
slug: "ethereum",
|
|
1448
|
+
name: "Ethereum",
|
|
1449
|
+
chain: mainnet,
|
|
1450
|
+
rpcUrl: buildRpcResolver(
|
|
1451
|
+
ETHEREUM_ALCHEMY_HOST,
|
|
1452
|
+
mainnet.rpcUrls.default.http
|
|
1453
|
+
),
|
|
1454
|
+
publicRpcUrls: mainnet.rpcUrls.default.http
|
|
1455
|
+
},
|
|
1456
|
+
baseSepolia: {
|
|
1457
|
+
id: baseSepolia.id,
|
|
1458
|
+
slug: "base-sepolia",
|
|
1459
|
+
name: "Base Sepolia",
|
|
1460
|
+
chain: baseSepolia,
|
|
1461
|
+
rpcUrl: buildRpcResolver(
|
|
1462
|
+
BASE_SEPOLIA_ALCHEMY_HOST,
|
|
1463
|
+
baseSepolia.rpcUrls.default.http
|
|
1464
|
+
)
|
|
1465
|
+
}
|
|
1466
|
+
};
|
|
1467
|
+
function createNativeToken(chainId, symbol, name) {
|
|
1468
|
+
return {
|
|
1469
|
+
[symbol]: {
|
|
1470
|
+
symbol,
|
|
1471
|
+
name,
|
|
1472
|
+
decimals: 18,
|
|
1473
|
+
address: zeroAddress,
|
|
1474
|
+
chainId,
|
|
1475
|
+
isNative: true
|
|
1476
|
+
}
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
function token(chainId, symbol, name, address, decimals) {
|
|
1480
|
+
return {
|
|
1481
|
+
symbol,
|
|
1482
|
+
name,
|
|
1483
|
+
decimals,
|
|
1484
|
+
address,
|
|
1485
|
+
chainId
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
var tokens = {
|
|
1489
|
+
base: {
|
|
1490
|
+
...createNativeToken(base.id, "ETH", "Ether"),
|
|
1491
|
+
USDC: token(
|
|
1492
|
+
base.id,
|
|
1493
|
+
"USDC",
|
|
1494
|
+
"USD Coin",
|
|
1495
|
+
"0x833589fCD6eDb6E08f4c7C31c9A8Ba32D74b86B2",
|
|
1496
|
+
6
|
|
1497
|
+
)
|
|
1498
|
+
},
|
|
1499
|
+
ethereum: {
|
|
1500
|
+
...createNativeToken(mainnet.id, "ETH", "Ether"),
|
|
1501
|
+
USDC: token(
|
|
1502
|
+
mainnet.id,
|
|
1503
|
+
"USDC",
|
|
1504
|
+
"USD Coin",
|
|
1505
|
+
"0xA0b86991c6218b36c1d19d4a2e9Eb0cE3606eB48",
|
|
1506
|
+
6
|
|
1507
|
+
)
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
var DEFAULT_CHAIN = chains.base;
|
|
1511
|
+
var DEFAULT_TOKENS = tokens.base;
|
|
1512
|
+
var registry = {
|
|
1513
|
+
chains,
|
|
1514
|
+
tokens
|
|
1515
|
+
};
|
|
1516
|
+
function normalizePrivateKey(raw) {
|
|
1517
|
+
const trimmed = raw.trim();
|
|
1518
|
+
const withPrefix = trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
|
|
1519
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(withPrefix)) {
|
|
1520
|
+
throw new Error("wallet() privateKey must be a 32-byte hex string");
|
|
1521
|
+
}
|
|
1522
|
+
return withPrefix;
|
|
1523
|
+
}
|
|
1524
|
+
function createPrivateKeyProvider(config) {
|
|
1525
|
+
const privateKey = normalizePrivateKey(config.privateKey);
|
|
1526
|
+
const account = privateKeyToAccount(privateKey);
|
|
1527
|
+
const transport = http(config.rpcUrl);
|
|
1528
|
+
const publicClient = createPublicClient({
|
|
1529
|
+
chain: config.chain.chain,
|
|
1530
|
+
transport
|
|
1531
|
+
});
|
|
1532
|
+
const walletClient = createWalletClient({
|
|
1533
|
+
account,
|
|
1534
|
+
chain: config.chain.chain,
|
|
1535
|
+
transport
|
|
1536
|
+
});
|
|
1537
|
+
async function sendTransaction(params) {
|
|
1538
|
+
const tx = {
|
|
1539
|
+
account
|
|
1540
|
+
};
|
|
1541
|
+
if (params.to) {
|
|
1542
|
+
tx.to = params.to;
|
|
1543
|
+
}
|
|
1544
|
+
if (params.value !== void 0) {
|
|
1545
|
+
tx.value = params.value;
|
|
1546
|
+
}
|
|
1547
|
+
if (params.data !== void 0) {
|
|
1548
|
+
tx.data = params.data;
|
|
1549
|
+
}
|
|
1550
|
+
return walletClient.sendTransaction(tx);
|
|
1551
|
+
}
|
|
1552
|
+
async function getNativeBalance() {
|
|
1553
|
+
return publicClient.getBalance({ address: account.address });
|
|
1554
|
+
}
|
|
1555
|
+
async function transfer(params) {
|
|
1556
|
+
return sendTransaction({
|
|
1557
|
+
to: params.to,
|
|
1558
|
+
value: params.amount,
|
|
1559
|
+
...params.data !== void 0 ? { data: params.data } : {}
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
return {
|
|
1563
|
+
address: account.address,
|
|
1564
|
+
account,
|
|
1565
|
+
walletClient,
|
|
1566
|
+
publicClient,
|
|
1567
|
+
sendTransaction,
|
|
1568
|
+
getNativeBalance,
|
|
1569
|
+
transfer
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
async function createTurnkeyProvider(config) {
|
|
1573
|
+
const turnkey = new Turnkey({
|
|
1574
|
+
apiBaseUrl: config.apiBaseUrl ?? "https://api.turnkey.com",
|
|
1575
|
+
// The delegated sub-organization the API key pair belongs to.
|
|
1576
|
+
defaultOrganizationId: config.organizationId,
|
|
1577
|
+
apiPublicKey: config.apiPublicKey,
|
|
1578
|
+
apiPrivateKey: config.apiPrivateKey
|
|
1579
|
+
});
|
|
1580
|
+
const account = await createAccount({
|
|
1581
|
+
client: turnkey.apiClient(),
|
|
1582
|
+
organizationId: config.organizationId,
|
|
1583
|
+
signWith: config.signWith
|
|
1584
|
+
});
|
|
1585
|
+
const transport = http(config.rpcUrl);
|
|
1586
|
+
const publicClient = createPublicClient({
|
|
1587
|
+
chain: config.chain.chain,
|
|
1588
|
+
transport
|
|
1589
|
+
});
|
|
1590
|
+
const walletClient = createWalletClient({
|
|
1591
|
+
account,
|
|
1592
|
+
chain: config.chain.chain,
|
|
1593
|
+
transport
|
|
1594
|
+
});
|
|
1595
|
+
async function sendTransaction(params) {
|
|
1596
|
+
const tx = {
|
|
1597
|
+
account
|
|
1598
|
+
};
|
|
1599
|
+
if (params.to) {
|
|
1600
|
+
tx.to = params.to;
|
|
1601
|
+
}
|
|
1602
|
+
if (params.value !== void 0) {
|
|
1603
|
+
tx.value = params.value;
|
|
1604
|
+
}
|
|
1605
|
+
if (params.data !== void 0) {
|
|
1606
|
+
tx.data = params.data;
|
|
1607
|
+
}
|
|
1608
|
+
return walletClient.sendTransaction(tx);
|
|
1609
|
+
}
|
|
1610
|
+
async function getNativeBalance() {
|
|
1611
|
+
return publicClient.getBalance({ address: account.address });
|
|
1612
|
+
}
|
|
1613
|
+
async function transfer(params) {
|
|
1614
|
+
return sendTransaction({
|
|
1615
|
+
to: params.to,
|
|
1616
|
+
value: params.amount,
|
|
1617
|
+
...params.data !== void 0 ? { data: params.data } : {}
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
return {
|
|
1621
|
+
address: account.address,
|
|
1622
|
+
account,
|
|
1623
|
+
walletClient,
|
|
1624
|
+
publicClient,
|
|
1625
|
+
sendTransaction,
|
|
1626
|
+
getNativeBalance,
|
|
1627
|
+
transfer
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
// src/wallets/index.ts
|
|
1632
|
+
function resolveChainSlug(reference) {
|
|
1633
|
+
if (reference === void 0) {
|
|
1634
|
+
return Object.entries(chains).find(([, meta]) => meta.id === DEFAULT_CHAIN.id)?.[0] || DEFAULT_CHAIN.slug;
|
|
1635
|
+
}
|
|
1636
|
+
if (typeof reference === "number") {
|
|
1637
|
+
const match = Object.entries(chains).find(([, meta]) => meta.id === reference);
|
|
1638
|
+
if (match) {
|
|
1639
|
+
return match[0];
|
|
1640
|
+
}
|
|
1641
|
+
} else if (typeof reference === "string") {
|
|
1642
|
+
const sanitize = (value) => value.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1643
|
+
if (reference in chains) {
|
|
1644
|
+
return reference;
|
|
1645
|
+
}
|
|
1646
|
+
const normalized = sanitize(reference);
|
|
1647
|
+
const keyMatch = Object.entries(chains).find(([key]) => sanitize(key) === normalized);
|
|
1648
|
+
if (keyMatch) {
|
|
1649
|
+
return keyMatch[0];
|
|
1650
|
+
}
|
|
1651
|
+
const slugMatch = Object.entries(chains).find(([, meta]) => {
|
|
1652
|
+
return meta.slug && sanitize(meta.slug) === normalized;
|
|
1653
|
+
});
|
|
1654
|
+
if (slugMatch) {
|
|
1655
|
+
return slugMatch[0];
|
|
1656
|
+
}
|
|
1657
|
+
const asNumber = Number.parseInt(normalized, 10);
|
|
1658
|
+
if (!Number.isNaN(asNumber)) {
|
|
1659
|
+
const match = Object.entries(chains).find(([, meta]) => meta.id === asNumber);
|
|
1660
|
+
if (match) {
|
|
1661
|
+
return match[0];
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
throw new Error(`Unknown chain reference: ${reference}`);
|
|
1666
|
+
}
|
|
1667
|
+
function getRpcUrl(chain, options) {
|
|
1668
|
+
const slug = resolveChainSlug(chain);
|
|
1669
|
+
const entry = chains[slug];
|
|
1670
|
+
return entry.rpcUrl(options);
|
|
1671
|
+
}
|
|
1672
|
+
async function wallet(options = {}) {
|
|
1673
|
+
if (options.privateKey && options.turnkey) {
|
|
1674
|
+
throw new Error("wallet() cannot be initialized with both privateKey and turnkey credentials");
|
|
1675
|
+
}
|
|
1676
|
+
const slug = resolveChainSlug(options.chain);
|
|
1677
|
+
const chain = chains[slug];
|
|
1678
|
+
const tokens2 = tokens[slug] ?? {};
|
|
1679
|
+
const overrides = {};
|
|
1680
|
+
if (options.rpcUrl) {
|
|
1681
|
+
overrides.url = options.rpcUrl;
|
|
1682
|
+
}
|
|
1683
|
+
if (options.apiKey) {
|
|
1684
|
+
overrides.apiKey = options.apiKey;
|
|
1685
|
+
}
|
|
1686
|
+
const rpcUrl = getRpcUrl(slug, overrides);
|
|
1687
|
+
let providerType = "readonly";
|
|
1688
|
+
let signerProvider;
|
|
1689
|
+
if (options.privateKey) {
|
|
1690
|
+
signerProvider = createPrivateKeyProvider({
|
|
1691
|
+
chain,
|
|
1692
|
+
rpcUrl,
|
|
1693
|
+
privateKey: options.privateKey
|
|
1694
|
+
});
|
|
1695
|
+
providerType = "privateKey";
|
|
1696
|
+
} else if (options.turnkey) {
|
|
1697
|
+
const turnkeyConfig = {
|
|
1698
|
+
chain,
|
|
1699
|
+
rpcUrl,
|
|
1700
|
+
organizationId: options.turnkey.organizationId,
|
|
1701
|
+
apiPublicKey: options.turnkey.apiPublicKey,
|
|
1702
|
+
apiPrivateKey: options.turnkey.apiPrivateKey,
|
|
1703
|
+
signWith: options.turnkey.signWith
|
|
1704
|
+
};
|
|
1705
|
+
if (options.turnkey.apiBaseUrl) {
|
|
1706
|
+
turnkeyConfig.apiBaseUrl = options.turnkey.apiBaseUrl;
|
|
1707
|
+
}
|
|
1708
|
+
signerProvider = await createTurnkeyProvider(turnkeyConfig);
|
|
1709
|
+
providerType = "turnkey";
|
|
1710
|
+
}
|
|
1711
|
+
const publicClient = signerProvider?.publicClient ?? createPublicClient({
|
|
1712
|
+
chain: chain.chain,
|
|
1713
|
+
transport: http(rpcUrl)
|
|
1714
|
+
});
|
|
1715
|
+
const baseContext = {
|
|
1716
|
+
chain,
|
|
1717
|
+
tokens: tokens2,
|
|
1718
|
+
rpcUrl,
|
|
1719
|
+
providerType,
|
|
1720
|
+
publicClient,
|
|
1721
|
+
getRpcUrl: (override) => getRpcUrl(slug, override)
|
|
1722
|
+
};
|
|
1723
|
+
if (signerProvider) {
|
|
1724
|
+
const { publicClient: _ignored, ...rest } = signerProvider;
|
|
1725
|
+
return {
|
|
1726
|
+
...baseContext,
|
|
1727
|
+
...rest
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
return baseContext;
|
|
1731
|
+
}
|
|
1732
|
+
var walletToolkit = {
|
|
1733
|
+
chains,
|
|
1734
|
+
tokens,
|
|
1735
|
+
registry,
|
|
1736
|
+
defaults: {
|
|
1737
|
+
chain: DEFAULT_CHAIN,
|
|
1738
|
+
tokens: DEFAULT_TOKENS
|
|
1739
|
+
},
|
|
1740
|
+
getRpcUrl,
|
|
1741
|
+
wallet
|
|
1742
|
+
};
|
|
1743
|
+
|
|
1744
|
+
// src/ai/errors.ts
|
|
1745
|
+
var AIError = class extends Error {
|
|
1746
|
+
constructor(message, options) {
|
|
1747
|
+
super(message);
|
|
1748
|
+
this.name = "AIError";
|
|
1749
|
+
if (options && "cause" in options) {
|
|
1750
|
+
this.cause = options.cause;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
var AIFetchError = class extends AIError {
|
|
1755
|
+
constructor(message, options) {
|
|
1756
|
+
super(message, options);
|
|
1757
|
+
this.name = "AIFetchError";
|
|
1758
|
+
}
|
|
1759
|
+
};
|
|
1760
|
+
var AIResponseError = class extends AIError {
|
|
1761
|
+
constructor(details, message) {
|
|
1762
|
+
super(message ?? `AI response error: ${details.status} ${details.statusText}`);
|
|
1763
|
+
this.name = "AIResponseError";
|
|
1764
|
+
this.status = details.status;
|
|
1765
|
+
this.statusText = details.statusText;
|
|
1766
|
+
this.body = details.body;
|
|
1767
|
+
this.headers = details.headers ?? {};
|
|
1768
|
+
}
|
|
1769
|
+
};
|
|
1770
|
+
var AIAbortError = class extends AIError {
|
|
1771
|
+
constructor(message = "AI request aborted") {
|
|
1772
|
+
super(message);
|
|
1773
|
+
this.name = "AIAbortError";
|
|
1774
|
+
}
|
|
1775
|
+
};
|
|
1776
|
+
|
|
1777
|
+
// src/ai/config.ts
|
|
1778
|
+
var DEFAULT_BASE_URL = "https://gateway.openpond.dev";
|
|
1779
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
1780
|
+
var DEFAULT_MODEL = "openai/gpt-5-mini";
|
|
1781
|
+
function assertFetchAvailable(fetchImplementation) {
|
|
1782
|
+
if (!fetchImplementation) {
|
|
1783
|
+
throw new Error(
|
|
1784
|
+
"No fetch implementation available. Provide one via AIClientConfig.fetchImplementation."
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
function resolveConfig(config = {}) {
|
|
1789
|
+
const fetchImplementation = config.fetchImplementation ?? globalThis.fetch;
|
|
1790
|
+
assertFetchAvailable(fetchImplementation);
|
|
1791
|
+
const resolved = {
|
|
1792
|
+
baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
|
|
1793
|
+
defaultModel: config.defaultModel ?? DEFAULT_MODEL,
|
|
1794
|
+
defaultHeaders: {
|
|
1795
|
+
"Content-Type": "application/json",
|
|
1796
|
+
...config.defaultHeaders
|
|
1797
|
+
},
|
|
1798
|
+
fetchImplementation,
|
|
1799
|
+
timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
1800
|
+
};
|
|
1801
|
+
if (config.apiKey !== void 0) {
|
|
1802
|
+
resolved.apiKey = config.apiKey;
|
|
1803
|
+
}
|
|
1804
|
+
return resolved;
|
|
1805
|
+
}
|
|
1806
|
+
function mergeHeaders(base2, overrides) {
|
|
1807
|
+
if (!overrides) {
|
|
1808
|
+
return { ...base2 };
|
|
1809
|
+
}
|
|
1810
|
+
const merged = { ...base2 };
|
|
1811
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
1812
|
+
if (value === void 0) {
|
|
1813
|
+
continue;
|
|
1814
|
+
}
|
|
1815
|
+
merged[key] = value;
|
|
1816
|
+
}
|
|
1817
|
+
return merged;
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
// src/ai/models.ts
|
|
1821
|
+
var MODEL_REGISTRY = [
|
|
1822
|
+
{
|
|
1823
|
+
name: "openai/gpt-5-mini",
|
|
1824
|
+
label: "OpenAI GPT-5 Mini",
|
|
1825
|
+
provider: "openai",
|
|
1826
|
+
supportsStreaming: true,
|
|
1827
|
+
supportsTools: true,
|
|
1828
|
+
reasoning: true,
|
|
1829
|
+
aliases: ["gpt-5-mini", "gpt5-mini", "gpt-5.0-mini"],
|
|
1830
|
+
default: true
|
|
1831
|
+
},
|
|
1832
|
+
{
|
|
1833
|
+
name: "anthropic/claude-4-sonnet-20250514",
|
|
1834
|
+
label: "Claude 4 Sonnet (20250514)",
|
|
1835
|
+
provider: "anthropic",
|
|
1836
|
+
supportsStreaming: true,
|
|
1837
|
+
supportsTools: true,
|
|
1838
|
+
aliases: ["claude-4-sonnet", "claude-sonnet"]
|
|
1839
|
+
},
|
|
1840
|
+
{
|
|
1841
|
+
name: "google/gemini-2.0-flash-001",
|
|
1842
|
+
label: "Gemini 2.0 Flash",
|
|
1843
|
+
provider: "google",
|
|
1844
|
+
supportsStreaming: true,
|
|
1845
|
+
supportsTools: true,
|
|
1846
|
+
aliases: ["gemini-2.0-flash", "gemini-flash"]
|
|
1847
|
+
},
|
|
1848
|
+
{
|
|
1849
|
+
name: "deepseek/deepseek-chat",
|
|
1850
|
+
label: "DeepSeek Chat",
|
|
1851
|
+
provider: "deepseek",
|
|
1852
|
+
supportsStreaming: true,
|
|
1853
|
+
supportsTools: true,
|
|
1854
|
+
aliases: ["deepseek-chat", "deepseek"]
|
|
1855
|
+
}
|
|
1856
|
+
];
|
|
1857
|
+
var ALIAS_LOOKUP = MODEL_REGISTRY.reduce(
|
|
1858
|
+
(accumulator, model) => {
|
|
1859
|
+
accumulator[model.name.toLowerCase()] = model.name;
|
|
1860
|
+
if (model.aliases) {
|
|
1861
|
+
for (const alias of model.aliases) {
|
|
1862
|
+
accumulator[alias.toLowerCase()] = model.name;
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
return accumulator;
|
|
1866
|
+
},
|
|
1867
|
+
{}
|
|
1868
|
+
);
|
|
1869
|
+
var DEFAULT_MODEL_NAME = MODEL_REGISTRY.find((model) => model.default)?.name ?? MODEL_REGISTRY[0].name;
|
|
1870
|
+
function listModels() {
|
|
1871
|
+
return [...MODEL_REGISTRY];
|
|
1872
|
+
}
|
|
1873
|
+
function getModelConfig(modelName) {
|
|
1874
|
+
if (!modelName) {
|
|
1875
|
+
return MODEL_REGISTRY.find((model) => model.default) ?? MODEL_REGISTRY[0];
|
|
1876
|
+
}
|
|
1877
|
+
const normalized = normalizeModelName(modelName);
|
|
1878
|
+
return MODEL_REGISTRY.find((model) => model.name === normalized);
|
|
1879
|
+
}
|
|
1880
|
+
function normalizeModelName(modelName) {
|
|
1881
|
+
if (!modelName) {
|
|
1882
|
+
return DEFAULT_MODEL_NAME;
|
|
1883
|
+
}
|
|
1884
|
+
const trimmed = modelName.trim();
|
|
1885
|
+
if (!trimmed) {
|
|
1886
|
+
return DEFAULT_MODEL_NAME;
|
|
1887
|
+
}
|
|
1888
|
+
const directMatch = ALIAS_LOOKUP[trimmed.toLowerCase()];
|
|
1889
|
+
if (directMatch) {
|
|
1890
|
+
return directMatch;
|
|
1891
|
+
}
|
|
1892
|
+
if (trimmed.includes("/")) {
|
|
1893
|
+
return trimmed;
|
|
1894
|
+
}
|
|
1895
|
+
return `openai/${trimmed}`;
|
|
1896
|
+
}
|
|
1897
|
+
function isStreamingSupported(modelName) {
|
|
1898
|
+
const config = getModelConfig(modelName);
|
|
1899
|
+
return config ? config.supportsStreaming : true;
|
|
1900
|
+
}
|
|
1901
|
+
function isToolCallingSupported(modelName) {
|
|
1902
|
+
const config = getModelConfig(modelName);
|
|
1903
|
+
return config ? config.supportsTools : true;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
// src/ai/tools.ts
|
|
1907
|
+
var WEBSEARCH_TOOL_NAME = "websearch";
|
|
1908
|
+
var WEBSEARCH_TOOL_DEFINITION = {
|
|
1909
|
+
type: "function",
|
|
1910
|
+
function: {
|
|
1911
|
+
name: WEBSEARCH_TOOL_NAME,
|
|
1912
|
+
description: "Search the web using the OpenPond search engine. Returns relevant results with titles, URLs, and text content.",
|
|
1913
|
+
parameters: {
|
|
1914
|
+
type: "object",
|
|
1915
|
+
properties: {
|
|
1916
|
+
query: {
|
|
1917
|
+
type: "string",
|
|
1918
|
+
description: "The search query"
|
|
1919
|
+
},
|
|
1920
|
+
limit: {
|
|
1921
|
+
type: "number",
|
|
1922
|
+
description: "Maximum number of results to return (default: 5)"
|
|
1923
|
+
}
|
|
1924
|
+
},
|
|
1925
|
+
required: ["query"]
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
};
|
|
1929
|
+
function resolveToolset(tools, policy) {
|
|
1930
|
+
if (!policy) {
|
|
1931
|
+
return tools;
|
|
1932
|
+
}
|
|
1933
|
+
const resolved = tools ? [...tools] : [];
|
|
1934
|
+
if (policy.webSearch) {
|
|
1935
|
+
const alreadyIncluded = resolved.some(
|
|
1936
|
+
(tool) => tool.type === "function" && tool.function?.name === WEBSEARCH_TOOL_NAME
|
|
1937
|
+
);
|
|
1938
|
+
if (!alreadyIncluded) {
|
|
1939
|
+
resolved.push(materializeWebSearchTool(policy.webSearch));
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
return resolved.length > 0 ? resolved : void 0;
|
|
1943
|
+
}
|
|
1944
|
+
function materializeWebSearchTool(options) {
|
|
1945
|
+
if (!options || Object.keys(options).length === 0) {
|
|
1946
|
+
return WEBSEARCH_TOOL_DEFINITION;
|
|
1947
|
+
}
|
|
1948
|
+
const baseParameters = WEBSEARCH_TOOL_DEFINITION.function.parameters ?? {};
|
|
1949
|
+
const baseProperties = baseParameters.properties ?? {};
|
|
1950
|
+
const properties = { ...baseProperties };
|
|
1951
|
+
if (options.limit !== void 0) {
|
|
1952
|
+
const existingLimit = baseProperties["limit"];
|
|
1953
|
+
const limitSchema = typeof existingLimit === "object" && existingLimit !== null ? { ...existingLimit } : {
|
|
1954
|
+
type: "number",
|
|
1955
|
+
description: "Maximum number of results to return (default: 5)"
|
|
1956
|
+
};
|
|
1957
|
+
limitSchema.default = options.limit;
|
|
1958
|
+
properties.limit = limitSchema;
|
|
1959
|
+
}
|
|
1960
|
+
if (options.includeImages) {
|
|
1961
|
+
properties.includeImages = {
|
|
1962
|
+
type: "boolean",
|
|
1963
|
+
description: "Whether to include representative images in results.",
|
|
1964
|
+
default: true
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
return {
|
|
1968
|
+
...WEBSEARCH_TOOL_DEFINITION,
|
|
1969
|
+
function: {
|
|
1970
|
+
...WEBSEARCH_TOOL_DEFINITION.function,
|
|
1971
|
+
parameters: {
|
|
1972
|
+
...WEBSEARCH_TOOL_DEFINITION.function.parameters,
|
|
1973
|
+
properties
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
// src/ai/messages.ts
|
|
1980
|
+
function flattenMessageContent(content, options = {}) {
|
|
1981
|
+
if (typeof content === "string") {
|
|
1982
|
+
return content;
|
|
1983
|
+
}
|
|
1984
|
+
if (!Array.isArray(content)) {
|
|
1985
|
+
return void 0;
|
|
1986
|
+
}
|
|
1987
|
+
const separator = options.separator ?? "";
|
|
1988
|
+
const collected = [];
|
|
1989
|
+
for (const part of content) {
|
|
1990
|
+
const text = extractTextPart(part, options);
|
|
1991
|
+
if (text) {
|
|
1992
|
+
collected.push(text);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
if (collected.length === 0) {
|
|
1996
|
+
return void 0;
|
|
1997
|
+
}
|
|
1998
|
+
return collected.join(separator);
|
|
1999
|
+
}
|
|
2000
|
+
function ensureTextContent(message, options) {
|
|
2001
|
+
const flattened = flattenMessageContent(message.content, options);
|
|
2002
|
+
if (flattened !== void 0) {
|
|
2003
|
+
return flattened;
|
|
2004
|
+
}
|
|
2005
|
+
throw new AIError(
|
|
2006
|
+
options?.errorMessage ?? "Assistant response did not contain textual content."
|
|
2007
|
+
);
|
|
2008
|
+
}
|
|
2009
|
+
function extractTextPart(part, options) {
|
|
2010
|
+
if (!part || typeof part !== "object") {
|
|
2011
|
+
return void 0;
|
|
2012
|
+
}
|
|
2013
|
+
if ("text" in part && typeof part.text === "string") {
|
|
2014
|
+
return part.text;
|
|
2015
|
+
}
|
|
2016
|
+
if (options.includeUnknown) {
|
|
2017
|
+
try {
|
|
2018
|
+
return JSON.stringify(part);
|
|
2019
|
+
} catch (error) {
|
|
2020
|
+
return `[unserializable_part: ${String(error)}]`;
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
return void 0;
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
// src/ai/client.ts
|
|
2027
|
+
var CHAT_COMPLETIONS_PATH = "/v1/chat/completions";
|
|
2028
|
+
function createAIClient(config = {}) {
|
|
2029
|
+
const resolved = resolveConfig(config);
|
|
2030
|
+
return {
|
|
2031
|
+
get config() {
|
|
2032
|
+
return resolved;
|
|
2033
|
+
},
|
|
2034
|
+
async generateText(options) {
|
|
2035
|
+
return generateText(options, config);
|
|
2036
|
+
},
|
|
2037
|
+
async streamText(options) {
|
|
2038
|
+
return streamText(options, config);
|
|
2039
|
+
},
|
|
2040
|
+
listModels
|
|
2041
|
+
};
|
|
2042
|
+
}
|
|
2043
|
+
async function generateText(options, clientConfig = {}) {
|
|
2044
|
+
const resolved = resolveConfig(clientConfig);
|
|
2045
|
+
const model = normalizeModelName(options.model ?? resolved.defaultModel);
|
|
2046
|
+
const payload = buildRequestPayload(options, model, {
|
|
2047
|
+
allowTools: isToolCallingSupported(model)
|
|
2048
|
+
});
|
|
2049
|
+
const headers = mergeHeaders(resolved.defaultHeaders, options.headers);
|
|
2050
|
+
if (resolved.apiKey) {
|
|
2051
|
+
headers.Authorization = `Bearer ${resolved.apiKey}`;
|
|
2052
|
+
}
|
|
2053
|
+
const endpoint = buildUrl(resolved.baseUrl, CHAT_COMPLETIONS_PATH);
|
|
2054
|
+
const abortBundle = createAbortBundle(
|
|
2055
|
+
options.abortSignal,
|
|
2056
|
+
options.timeoutMs ?? resolved.timeoutMs
|
|
2057
|
+
);
|
|
2058
|
+
let response;
|
|
2059
|
+
try {
|
|
2060
|
+
response = await resolved.fetchImplementation(endpoint, {
|
|
2061
|
+
method: "POST",
|
|
2062
|
+
headers,
|
|
2063
|
+
body: JSON.stringify(payload),
|
|
2064
|
+
signal: abortBundle.signal
|
|
2065
|
+
});
|
|
2066
|
+
} catch (error) {
|
|
2067
|
+
if (abortBundle.signal.aborted) {
|
|
2068
|
+
throw toAbortError(abortBundle.signal.reason ?? error);
|
|
2069
|
+
}
|
|
2070
|
+
throw new AIFetchError("Failed to reach AI gateway", { cause: error });
|
|
2071
|
+
} finally {
|
|
2072
|
+
abortBundle.cleanup();
|
|
2073
|
+
}
|
|
2074
|
+
if (!response.ok) {
|
|
2075
|
+
const errorBody = await safeParseJson(response);
|
|
2076
|
+
throw new AIResponseError({
|
|
2077
|
+
status: response.status,
|
|
2078
|
+
statusText: response.statusText,
|
|
2079
|
+
body: errorBody,
|
|
2080
|
+
headers: collectHeaders(response.headers)
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
const data = await response.json();
|
|
2084
|
+
const primaryChoice = data.choices.find(isPrimaryChoice);
|
|
2085
|
+
if (!primaryChoice) {
|
|
2086
|
+
throw new AIResponseError(
|
|
2087
|
+
{
|
|
2088
|
+
status: response.status,
|
|
2089
|
+
statusText: response.statusText,
|
|
2090
|
+
body: data
|
|
2091
|
+
},
|
|
2092
|
+
"Gateway response did not contain a valid choice"
|
|
2093
|
+
);
|
|
2094
|
+
}
|
|
2095
|
+
const result = {
|
|
2096
|
+
id: data.id,
|
|
2097
|
+
model: data.model,
|
|
2098
|
+
message: primaryChoice.message,
|
|
2099
|
+
raw: data
|
|
2100
|
+
};
|
|
2101
|
+
if (primaryChoice.finish_reason !== void 0) {
|
|
2102
|
+
result.finishReason = primaryChoice.finish_reason;
|
|
2103
|
+
}
|
|
2104
|
+
if (data.usage) {
|
|
2105
|
+
result.usage = data.usage;
|
|
2106
|
+
}
|
|
2107
|
+
return result;
|
|
2108
|
+
}
|
|
2109
|
+
async function streamText(options, clientConfig = {}) {
|
|
2110
|
+
const resolved = resolveConfig(clientConfig);
|
|
2111
|
+
const model = normalizeModelName(options.model ?? resolved.defaultModel);
|
|
2112
|
+
const streamExtras = buildStreamMetadataExtras(options);
|
|
2113
|
+
const payload = buildRequestPayload(
|
|
2114
|
+
options,
|
|
2115
|
+
model,
|
|
2116
|
+
{
|
|
2117
|
+
allowTools: isToolCallingSupported(model)
|
|
2118
|
+
},
|
|
2119
|
+
streamExtras
|
|
2120
|
+
);
|
|
2121
|
+
payload.stream = true;
|
|
2122
|
+
if (options.includeUsage) {
|
|
2123
|
+
payload.stream_options = { include_usage: true };
|
|
2124
|
+
}
|
|
2125
|
+
const headers = mergeHeaders(resolved.defaultHeaders, options.headers);
|
|
2126
|
+
if (resolved.apiKey) {
|
|
2127
|
+
headers.Authorization = `Bearer ${resolved.apiKey}`;
|
|
2128
|
+
}
|
|
2129
|
+
const endpoint = buildUrl(resolved.baseUrl, CHAT_COMPLETIONS_PATH);
|
|
2130
|
+
const abortBundle = createAbortBundle(
|
|
2131
|
+
options.abortSignal,
|
|
2132
|
+
options.timeoutMs ?? resolved.timeoutMs
|
|
2133
|
+
);
|
|
2134
|
+
let response;
|
|
2135
|
+
try {
|
|
2136
|
+
response = await resolved.fetchImplementation(endpoint, {
|
|
2137
|
+
method: "POST",
|
|
2138
|
+
headers,
|
|
2139
|
+
body: JSON.stringify(payload),
|
|
2140
|
+
signal: abortBundle.signal
|
|
2141
|
+
});
|
|
2142
|
+
} catch (error) {
|
|
2143
|
+
if (abortBundle.signal.aborted) {
|
|
2144
|
+
throw toAbortError(abortBundle.signal.reason ?? error);
|
|
2145
|
+
}
|
|
2146
|
+
throw new AIFetchError("Failed to reach AI gateway", { cause: error });
|
|
2147
|
+
}
|
|
2148
|
+
if (!response.ok) {
|
|
2149
|
+
const errorBody = await safeParseJson(response);
|
|
2150
|
+
abortBundle.cleanup();
|
|
2151
|
+
throw new AIResponseError({
|
|
2152
|
+
status: response.status,
|
|
2153
|
+
statusText: response.statusText,
|
|
2154
|
+
body: errorBody,
|
|
2155
|
+
headers: collectHeaders(response.headers)
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
if (!response.body) {
|
|
2159
|
+
abortBundle.cleanup();
|
|
2160
|
+
throw new AIFetchError("Streaming response did not include a readable body");
|
|
2161
|
+
}
|
|
2162
|
+
const reader = response.body.getReader();
|
|
2163
|
+
const decoder = new TextDecoder();
|
|
2164
|
+
const handlers = options.handlers ?? {};
|
|
2165
|
+
let finishedResolve;
|
|
2166
|
+
let finishedReject;
|
|
2167
|
+
const finished = new Promise((resolve4, reject) => {
|
|
2168
|
+
finishedResolve = resolve4;
|
|
2169
|
+
finishedReject = reject;
|
|
2170
|
+
});
|
|
2171
|
+
let settled = false;
|
|
2172
|
+
const resolveStream = () => {
|
|
2173
|
+
if (settled) {
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
settled = true;
|
|
2177
|
+
try {
|
|
2178
|
+
handlers.onDone?.();
|
|
2179
|
+
finishedResolve();
|
|
2180
|
+
} catch (error) {
|
|
2181
|
+
settled = false;
|
|
2182
|
+
rejectStream(error);
|
|
2183
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
const rejectStream = (reason) => {
|
|
2186
|
+
if (settled) {
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
settled = true;
|
|
2190
|
+
try {
|
|
2191
|
+
handlers.onError?.(reason);
|
|
2192
|
+
} catch (handlerError) {
|
|
2193
|
+
reason = handlerError;
|
|
2194
|
+
}
|
|
2195
|
+
finishedReject(reason);
|
|
2196
|
+
};
|
|
2197
|
+
const abort = () => abortBundle.abort();
|
|
2198
|
+
(async () => {
|
|
2199
|
+
let buffer = "";
|
|
2200
|
+
try {
|
|
2201
|
+
while (true) {
|
|
2202
|
+
const { done, value } = await reader.read();
|
|
2203
|
+
if (done) {
|
|
2204
|
+
buffer += decoder.decode();
|
|
2205
|
+
buffer = buffer.replace(/\r\n/g, "\n");
|
|
2206
|
+
if (buffer.trim().length > 0) {
|
|
2207
|
+
if (processStreamEventChunk(buffer, handlers)) {
|
|
2208
|
+
break;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
resolveStream();
|
|
2212
|
+
break;
|
|
2213
|
+
}
|
|
2214
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2215
|
+
buffer = buffer.replace(/\r\n/g, "\n");
|
|
2216
|
+
let boundaryIndex;
|
|
2217
|
+
while ((boundaryIndex = buffer.indexOf("\n\n")) !== -1) {
|
|
2218
|
+
const chunk = buffer.slice(0, boundaryIndex);
|
|
2219
|
+
buffer = buffer.slice(boundaryIndex + 2);
|
|
2220
|
+
if (!chunk) {
|
|
2221
|
+
continue;
|
|
2222
|
+
}
|
|
2223
|
+
if (processStreamEventChunk(chunk, handlers)) {
|
|
2224
|
+
await reader.cancel().catch(() => void 0);
|
|
2225
|
+
resolveStream();
|
|
2226
|
+
return;
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
} catch (error) {
|
|
2231
|
+
if (abortBundle.signal.aborted) {
|
|
2232
|
+
rejectStream(toAbortError(abortBundle.signal.reason ?? error));
|
|
2233
|
+
} else {
|
|
2234
|
+
rejectStream(error);
|
|
2235
|
+
}
|
|
2236
|
+
} finally {
|
|
2237
|
+
try {
|
|
2238
|
+
reader.releaseLock();
|
|
2239
|
+
} catch (error) {
|
|
2240
|
+
}
|
|
2241
|
+
abortBundle.cleanup();
|
|
2242
|
+
}
|
|
2243
|
+
})().catch((error) => {
|
|
2244
|
+
rejectStream(error);
|
|
2245
|
+
});
|
|
2246
|
+
return {
|
|
2247
|
+
abort,
|
|
2248
|
+
finished
|
|
2249
|
+
};
|
|
2250
|
+
function processStreamEventChunk(chunk, eventHandlers) {
|
|
2251
|
+
const dataString = extractSseData(chunk);
|
|
2252
|
+
if (dataString == null) {
|
|
2253
|
+
return false;
|
|
2254
|
+
}
|
|
2255
|
+
const trimmed = dataString.trim();
|
|
2256
|
+
if (trimmed === "[DONE]") {
|
|
2257
|
+
return true;
|
|
2258
|
+
}
|
|
2259
|
+
let payload2;
|
|
2260
|
+
try {
|
|
2261
|
+
payload2 = JSON.parse(dataString);
|
|
2262
|
+
} catch (error) {
|
|
2263
|
+
rejectStream(new AIError("Failed to parse streaming payload", { cause: error }));
|
|
2264
|
+
return true;
|
|
2265
|
+
}
|
|
2266
|
+
try {
|
|
2267
|
+
handleStreamPayload(payload2, eventHandlers);
|
|
2268
|
+
} catch (error) {
|
|
2269
|
+
rejectStream(error);
|
|
2270
|
+
return true;
|
|
2271
|
+
}
|
|
2272
|
+
return false;
|
|
2273
|
+
}
|
|
2274
|
+
function handleStreamPayload(payload2, eventHandlers) {
|
|
2275
|
+
if (!payload2 || typeof payload2 !== "object") {
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
if ("error" in payload2 && payload2.error) {
|
|
2279
|
+
const message = typeof payload2.error === "string" ? payload2.error : payload2.error.message;
|
|
2280
|
+
throw new AIError(message ?? "AI stream returned an error payload");
|
|
2281
|
+
}
|
|
2282
|
+
const structured = payload2;
|
|
2283
|
+
if (Array.isArray(structured.choices)) {
|
|
2284
|
+
for (const choice of structured.choices) {
|
|
2285
|
+
if (!choice || typeof choice !== "object") {
|
|
2286
|
+
continue;
|
|
2287
|
+
}
|
|
2288
|
+
const delta = choice.delta;
|
|
2289
|
+
if (!delta || typeof delta !== "object") {
|
|
2290
|
+
continue;
|
|
2291
|
+
}
|
|
2292
|
+
const deltaObject = delta;
|
|
2293
|
+
const textDelta = extractDeltaText(deltaObject.content);
|
|
2294
|
+
if (textDelta) {
|
|
2295
|
+
eventHandlers.onTextDelta?.(textDelta);
|
|
2296
|
+
}
|
|
2297
|
+
const reasoningDelta = extractDeltaText(deltaObject.reasoning);
|
|
2298
|
+
if (reasoningDelta) {
|
|
2299
|
+
eventHandlers.onReasoningDelta?.(reasoningDelta);
|
|
2300
|
+
}
|
|
2301
|
+
if (deltaObject.tool_calls !== void 0) {
|
|
2302
|
+
eventHandlers.onToolCallDelta?.(deltaObject.tool_calls);
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
if (structured.usage) {
|
|
2307
|
+
eventHandlers.onUsage?.(structured.usage);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
function extractDeltaText(value) {
|
|
2311
|
+
if (!value) {
|
|
2312
|
+
return void 0;
|
|
2313
|
+
}
|
|
2314
|
+
if (typeof value === "string") {
|
|
2315
|
+
return value;
|
|
2316
|
+
}
|
|
2317
|
+
if (Array.isArray(value)) {
|
|
2318
|
+
return flattenMessageContent(value);
|
|
2319
|
+
}
|
|
2320
|
+
if (typeof value === "object" && value !== null && "content" in value && Array.isArray(value.content)) {
|
|
2321
|
+
return flattenMessageContent(
|
|
2322
|
+
value.content ?? []
|
|
2323
|
+
);
|
|
2324
|
+
}
|
|
2325
|
+
return void 0;
|
|
2326
|
+
}
|
|
2327
|
+
function extractSseData(chunk) {
|
|
2328
|
+
const lines = chunk.split("\n");
|
|
2329
|
+
const dataLines = [];
|
|
2330
|
+
for (const rawLine of lines) {
|
|
2331
|
+
if (!rawLine) {
|
|
2332
|
+
continue;
|
|
2333
|
+
}
|
|
2334
|
+
const match = /^data:(.*)$/.exec(rawLine);
|
|
2335
|
+
if (!match) {
|
|
2336
|
+
continue;
|
|
2337
|
+
}
|
|
2338
|
+
const value = match[1];
|
|
2339
|
+
dataLines.push(value.startsWith(" ") ? value.slice(1) : value);
|
|
2340
|
+
}
|
|
2341
|
+
if (dataLines.length === 0) {
|
|
2342
|
+
return null;
|
|
2343
|
+
}
|
|
2344
|
+
return dataLines.join("\n");
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
function buildStreamMetadataExtras(options) {
|
|
2348
|
+
const streamConfig = {};
|
|
2349
|
+
if (options.sendReasoning !== void 0) {
|
|
2350
|
+
streamConfig.sendReasoning = options.sendReasoning;
|
|
2351
|
+
}
|
|
2352
|
+
if (options.includeUsage !== void 0) {
|
|
2353
|
+
streamConfig.includeUsage = options.includeUsage;
|
|
2354
|
+
}
|
|
2355
|
+
if (Object.keys(streamConfig).length === 0) {
|
|
2356
|
+
return void 0;
|
|
2357
|
+
}
|
|
2358
|
+
return {
|
|
2359
|
+
openpond: {
|
|
2360
|
+
stream: streamConfig
|
|
2361
|
+
}
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
function buildRequestPayload(options, model, capabilities, metadataExtras) {
|
|
2365
|
+
const payload = {
|
|
2366
|
+
model,
|
|
2367
|
+
messages: options.messages
|
|
2368
|
+
};
|
|
2369
|
+
const generation = options.generation ?? {};
|
|
2370
|
+
assignIfDefined(payload, "temperature", generation.temperature);
|
|
2371
|
+
assignIfDefined(payload, "top_p", generation.topP);
|
|
2372
|
+
assignIfDefined(payload, "max_tokens", generation.maxTokens);
|
|
2373
|
+
assignIfDefined(payload, "stop", generation.stop);
|
|
2374
|
+
assignIfDefined(
|
|
2375
|
+
payload,
|
|
2376
|
+
"frequency_penalty",
|
|
2377
|
+
generation.frequencyPenalty
|
|
2378
|
+
);
|
|
2379
|
+
assignIfDefined(payload, "presence_penalty", generation.presencePenalty);
|
|
2380
|
+
assignIfDefined(payload, "response_format", generation.responseFormat);
|
|
2381
|
+
const toolExecution = options.toolExecution;
|
|
2382
|
+
const enableTools = toolExecution?.enableTools ?? true;
|
|
2383
|
+
if (enableTools && capabilities.allowTools) {
|
|
2384
|
+
const resolvedTools = resolveToolset(options.tools, toolExecution);
|
|
2385
|
+
assignIfDefined(payload, "tools", resolvedTools);
|
|
2386
|
+
assignIfDefined(payload, "tool_choice", options.toolChoice);
|
|
2387
|
+
} else if (options.toolChoice && options.toolChoice !== "none") {
|
|
2388
|
+
payload.tool_choice = "none";
|
|
2389
|
+
}
|
|
2390
|
+
const metadataPayload = buildMetadataPayload(
|
|
2391
|
+
options.metadata,
|
|
2392
|
+
toolExecution,
|
|
2393
|
+
metadataExtras
|
|
2394
|
+
);
|
|
2395
|
+
if (metadataPayload) {
|
|
2396
|
+
payload.metadata = metadataPayload;
|
|
2397
|
+
}
|
|
2398
|
+
return payload;
|
|
2399
|
+
}
|
|
2400
|
+
function assignIfDefined(target, key, value) {
|
|
2401
|
+
if (value !== void 0) {
|
|
2402
|
+
target[key] = value;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
function buildUrl(baseUrl, path7) {
|
|
2406
|
+
const sanitizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
2407
|
+
return `${sanitizedBase}${path7}`;
|
|
2408
|
+
}
|
|
2409
|
+
function createAbortBundle(upstreamSignal, timeoutMs) {
|
|
2410
|
+
const controller = new AbortController();
|
|
2411
|
+
const cleanupCallbacks = [];
|
|
2412
|
+
if (upstreamSignal) {
|
|
2413
|
+
if (upstreamSignal.aborted) {
|
|
2414
|
+
controller.abort(upstreamSignal.reason);
|
|
2415
|
+
} else {
|
|
2416
|
+
const onAbort = () => controller.abort(upstreamSignal.reason);
|
|
2417
|
+
upstreamSignal.addEventListener("abort", onAbort, { once: true });
|
|
2418
|
+
cleanupCallbacks.push(
|
|
2419
|
+
() => upstreamSignal.removeEventListener("abort", onAbort)
|
|
2420
|
+
);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
if (timeoutMs && timeoutMs > 0) {
|
|
2424
|
+
const timeoutId = setTimeout(() => {
|
|
2425
|
+
controller.abort(new Error("AI request timed out"));
|
|
2426
|
+
}, timeoutMs);
|
|
2427
|
+
cleanupCallbacks.push(() => clearTimeout(timeoutId));
|
|
2428
|
+
}
|
|
2429
|
+
return {
|
|
2430
|
+
signal: controller.signal,
|
|
2431
|
+
abort: () => controller.abort(),
|
|
2432
|
+
cleanup: () => {
|
|
2433
|
+
cleanupCallbacks.forEach((fn) => fn());
|
|
2434
|
+
}
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
function collectHeaders(headers) {
|
|
2438
|
+
const result = {};
|
|
2439
|
+
headers.forEach((value, key) => {
|
|
2440
|
+
result[key] = value;
|
|
2441
|
+
});
|
|
2442
|
+
return result;
|
|
2443
|
+
}
|
|
2444
|
+
function buildMetadataPayload(base2, toolExecution, extras) {
|
|
2445
|
+
const metadata = base2 ? { ...base2 } : {};
|
|
2446
|
+
if (extras) {
|
|
2447
|
+
for (const [key, value] of Object.entries(extras)) {
|
|
2448
|
+
if (value === void 0) {
|
|
2449
|
+
continue;
|
|
2450
|
+
}
|
|
2451
|
+
if (key === "openpond" && typeof value === "object" && value !== null) {
|
|
2452
|
+
const existing = {
|
|
2453
|
+
...metadata.openpond ?? {}
|
|
2454
|
+
};
|
|
2455
|
+
metadata.openpond = {
|
|
2456
|
+
...existing,
|
|
2457
|
+
...value
|
|
2458
|
+
};
|
|
2459
|
+
} else {
|
|
2460
|
+
metadata[key] = value;
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
if (toolExecution) {
|
|
2465
|
+
const openpond = {
|
|
2466
|
+
...metadata.openpond ?? {},
|
|
2467
|
+
toolExecution
|
|
2468
|
+
};
|
|
2469
|
+
metadata.openpond = openpond;
|
|
2470
|
+
}
|
|
2471
|
+
return Object.keys(metadata).length > 0 ? metadata : void 0;
|
|
2472
|
+
}
|
|
2473
|
+
async function safeParseJson(response) {
|
|
2474
|
+
const contentType = response.headers.get("content-type");
|
|
2475
|
+
if (!contentType || !contentType.includes("application/json")) {
|
|
2476
|
+
return void 0;
|
|
2477
|
+
}
|
|
2478
|
+
try {
|
|
2479
|
+
return await response.json();
|
|
2480
|
+
} catch (error) {
|
|
2481
|
+
return { error: "Failed to parse error body", cause: String(error) };
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
function isPrimaryChoice(choice) {
|
|
2485
|
+
return choice.index === 0 || choice.message !== void 0;
|
|
2486
|
+
}
|
|
2487
|
+
function toAbortError(reason) {
|
|
2488
|
+
if (reason instanceof AIAbortError) {
|
|
2489
|
+
return reason;
|
|
2490
|
+
}
|
|
2491
|
+
if (reason instanceof Error) {
|
|
2492
|
+
if (reason.name === "AbortError") {
|
|
2493
|
+
return new AIAbortError(reason.message || "AI request aborted");
|
|
2494
|
+
}
|
|
2495
|
+
return new AIAbortError(reason.message);
|
|
2496
|
+
}
|
|
2497
|
+
return new AIAbortError(String(reason ?? "AI request aborted"));
|
|
2498
|
+
}
|
|
2499
|
+
var METADATA_SPEC_VERSION = "1.0.0";
|
|
2500
|
+
var McpAnnotationsSchema = z.object({
|
|
2501
|
+
title: z.string().optional(),
|
|
2502
|
+
readOnlyHint: z.boolean().optional(),
|
|
2503
|
+
destructiveHint: z.boolean().optional(),
|
|
2504
|
+
idempotentHint: z.boolean().optional(),
|
|
2505
|
+
openWorldHint: z.boolean().optional(),
|
|
2506
|
+
requiresPayment: z.boolean().optional()
|
|
2507
|
+
}).strict();
|
|
2508
|
+
var PaymentConfigSchema = z.object({
|
|
2509
|
+
amountUSDC: z.number().nonnegative().optional(),
|
|
2510
|
+
description: z.string().optional(),
|
|
2511
|
+
x402: z.boolean().optional(),
|
|
2512
|
+
plain402: z.boolean().optional(),
|
|
2513
|
+
acceptedMethods: z.array(z.union([z.literal("x402"), z.literal("402")])).optional(),
|
|
2514
|
+
acceptedCurrencies: z.array(z.string()).optional(),
|
|
2515
|
+
chainIds: z.array(z.number().int()).optional(),
|
|
2516
|
+
facilitator: z.string().optional()
|
|
2517
|
+
}).strict();
|
|
2518
|
+
var DiscoveryMetadataSchema = z.object({
|
|
2519
|
+
keywords: z.array(z.string()).optional(),
|
|
2520
|
+
category: z.string().optional(),
|
|
2521
|
+
useCases: z.array(z.string()).optional(),
|
|
2522
|
+
capabilities: z.array(z.string()).optional(),
|
|
2523
|
+
requirements: z.record(z.any()).optional(),
|
|
2524
|
+
pricing: z.record(z.any()).optional(),
|
|
2525
|
+
compatibility: z.record(z.any()).optional(),
|
|
2526
|
+
documentation: z.union([z.string(), z.array(z.string())]).optional()
|
|
2527
|
+
}).catchall(z.any());
|
|
2528
|
+
var ToolMetadataOverridesSchema = z.object({
|
|
2529
|
+
name: z.string().optional(),
|
|
2530
|
+
description: z.string().optional(),
|
|
2531
|
+
annotations: McpAnnotationsSchema.optional(),
|
|
2532
|
+
payment: PaymentConfigSchema.optional(),
|
|
2533
|
+
discovery: DiscoveryMetadataSchema.optional()
|
|
2534
|
+
}).catchall(z.any());
|
|
2535
|
+
var ToolSchema = z.object({
|
|
2536
|
+
name: z.string(),
|
|
2537
|
+
description: z.string(),
|
|
2538
|
+
inputSchema: z.any(),
|
|
2539
|
+
annotations: McpAnnotationsSchema.optional(),
|
|
2540
|
+
payment: PaymentConfigSchema.optional(),
|
|
2541
|
+
discovery: DiscoveryMetadataSchema.optional()
|
|
2542
|
+
}).strict();
|
|
2543
|
+
var AuthoredMetadataSchema = z.object({
|
|
2544
|
+
metadataSpecVersion: z.string().optional(),
|
|
2545
|
+
name: z.string().optional(),
|
|
2546
|
+
displayName: z.string().optional(),
|
|
2547
|
+
version: z.string().optional(),
|
|
2548
|
+
description: z.string().optional(),
|
|
2549
|
+
author: z.string().optional(),
|
|
2550
|
+
repository: z.string().optional(),
|
|
2551
|
+
website: z.string().optional(),
|
|
2552
|
+
category: z.string().optional(),
|
|
2553
|
+
categories: z.array(z.string()).optional(),
|
|
2554
|
+
termsOfService: z.string().optional(),
|
|
2555
|
+
mcpUrl: z.string().optional(),
|
|
2556
|
+
payment: PaymentConfigSchema.optional(),
|
|
2557
|
+
discovery: DiscoveryMetadataSchema.optional(),
|
|
2558
|
+
promptExamples: z.array(z.string()).optional(),
|
|
2559
|
+
iconPath: z.string().optional(),
|
|
2560
|
+
videoPath: z.string().optional(),
|
|
2561
|
+
image: z.string().optional(),
|
|
2562
|
+
animation_url: z.string().optional(),
|
|
2563
|
+
keywords: z.array(z.string()).optional(),
|
|
2564
|
+
useCases: z.array(z.string()).optional(),
|
|
2565
|
+
capabilities: z.array(z.string()).optional(),
|
|
2566
|
+
requirements: z.record(z.any()).optional(),
|
|
2567
|
+
pricing: z.record(z.any()).optional(),
|
|
2568
|
+
compatibility: z.record(z.any()).optional()
|
|
2569
|
+
}).catchall(z.any());
|
|
2570
|
+
var MetadataSchema = z.object({
|
|
2571
|
+
metadataSpecVersion: z.string().default(METADATA_SPEC_VERSION),
|
|
2572
|
+
name: z.string(),
|
|
2573
|
+
displayName: z.string(),
|
|
2574
|
+
version: z.string(),
|
|
2575
|
+
description: z.string().optional(),
|
|
2576
|
+
author: z.string().optional(),
|
|
2577
|
+
repository: z.string().optional(),
|
|
2578
|
+
website: z.string().optional(),
|
|
2579
|
+
category: z.string(),
|
|
2580
|
+
termsOfService: z.string().optional(),
|
|
2581
|
+
mcpUrl: z.string().optional(),
|
|
2582
|
+
payment: PaymentConfigSchema.optional(),
|
|
2583
|
+
tools: z.array(ToolSchema).min(1),
|
|
2584
|
+
discovery: DiscoveryMetadataSchema.optional(),
|
|
2585
|
+
promptExamples: z.array(z.string()).optional(),
|
|
2586
|
+
iconPath: z.string().optional(),
|
|
2587
|
+
videoPath: z.string().optional(),
|
|
2588
|
+
image: z.string().optional(),
|
|
2589
|
+
animation_url: z.string().optional()
|
|
2590
|
+
}).strict();
|
|
2591
|
+
function resolveTsconfig(projectRoot) {
|
|
2592
|
+
const candidate = path5.join(projectRoot, "tsconfig.json");
|
|
2593
|
+
if (fs2.existsSync(candidate)) {
|
|
2594
|
+
return candidate;
|
|
2595
|
+
}
|
|
2596
|
+
return void 0;
|
|
2597
|
+
}
|
|
2598
|
+
async function transpileWithEsbuild(options) {
|
|
2599
|
+
if (options.entryPoints.length === 0) {
|
|
2600
|
+
throw new Error("No entry points provided for esbuild transpilation");
|
|
2601
|
+
}
|
|
2602
|
+
const projectRoot = options.projectRoot;
|
|
2603
|
+
const tempBase = options.outDir ?? fs2.mkdtempSync(path5.join(tmpdir(), "opentool-"));
|
|
2604
|
+
if (!fs2.existsSync(tempBase)) {
|
|
2605
|
+
fs2.mkdirSync(tempBase, { recursive: true });
|
|
2606
|
+
}
|
|
2607
|
+
const tsconfig = resolveTsconfig(projectRoot);
|
|
2608
|
+
const buildOptions = {
|
|
2609
|
+
entryPoints: options.entryPoints,
|
|
2610
|
+
outdir: tempBase,
|
|
2611
|
+
bundle: false,
|
|
2612
|
+
format: options.format,
|
|
2613
|
+
platform: "node",
|
|
2614
|
+
target: "node20",
|
|
2615
|
+
logLevel: "warning",
|
|
2616
|
+
sourcesContent: false,
|
|
2617
|
+
sourcemap: false,
|
|
2618
|
+
packages: "external",
|
|
2619
|
+
loader: {
|
|
2620
|
+
".ts": "ts",
|
|
2621
|
+
".tsx": "tsx",
|
|
2622
|
+
".cts": "ts",
|
|
2623
|
+
".mts": "ts",
|
|
2624
|
+
".js": "js",
|
|
2625
|
+
".jsx": "jsx",
|
|
2626
|
+
".mjs": "js",
|
|
2627
|
+
".cjs": "js"
|
|
2628
|
+
},
|
|
2629
|
+
metafile: false,
|
|
2630
|
+
allowOverwrite: true,
|
|
2631
|
+
absWorkingDir: projectRoot
|
|
2632
|
+
};
|
|
2633
|
+
if (tsconfig) {
|
|
2634
|
+
buildOptions.tsconfig = tsconfig;
|
|
2635
|
+
}
|
|
2636
|
+
await build(buildOptions);
|
|
2637
|
+
if (options.format === "esm") {
|
|
2638
|
+
const packageJsonPath = path5.join(tempBase, "package.json");
|
|
2639
|
+
if (!fs2.existsSync(packageJsonPath)) {
|
|
2640
|
+
fs2.writeFileSync(packageJsonPath, JSON.stringify({ type: "module" }), "utf8");
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
const cleanup = () => {
|
|
2644
|
+
if (options.outDir) {
|
|
2645
|
+
return;
|
|
2646
|
+
}
|
|
2647
|
+
fs2.rmSync(tempBase, { recursive: true, force: true });
|
|
2648
|
+
};
|
|
2649
|
+
return { outDir: tempBase, cleanup };
|
|
2650
|
+
}
|
|
2651
|
+
createRequire(import.meta.url);
|
|
2652
|
+
function resolveCompiledPath(outDir, originalFile, extension = ".js") {
|
|
2653
|
+
const baseName = path5.basename(originalFile).replace(/\.[^.]+$/, "");
|
|
2654
|
+
return path5.join(outDir, `${baseName}${extension}`);
|
|
2655
|
+
}
|
|
2656
|
+
async function importFresh(modulePath) {
|
|
2657
|
+
const fileUrl = pathToFileURL(modulePath).href;
|
|
2658
|
+
const cacheBuster = `t=${Date.now()}-${Math.random()}`;
|
|
2659
|
+
const separator = fileUrl.includes("?") ? "&" : "?";
|
|
2660
|
+
return import(`${fileUrl}${separator}${cacheBuster}`);
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
// src/cli/shared/metadata.ts
|
|
2664
|
+
var METADATA_ENTRY = "metadata.ts";
|
|
2665
|
+
async function loadAuthoredMetadata(projectRoot) {
|
|
2666
|
+
const absPath = path5.join(projectRoot, METADATA_ENTRY);
|
|
2667
|
+
if (!fs2.existsSync(absPath)) {
|
|
2668
|
+
throw new Error(
|
|
2669
|
+
`metadata.ts not found in ${projectRoot}. Create metadata.ts to describe your agent.`
|
|
2670
|
+
);
|
|
2671
|
+
}
|
|
2672
|
+
const tempDir = path5.join(projectRoot, ".opentool-temp");
|
|
2673
|
+
if (fs2.existsSync(tempDir)) {
|
|
2674
|
+
fs2.rmSync(tempDir, { recursive: true, force: true });
|
|
2675
|
+
}
|
|
2676
|
+
const { outDir, cleanup } = await transpileWithEsbuild({
|
|
2677
|
+
entryPoints: [absPath],
|
|
2678
|
+
projectRoot,
|
|
2679
|
+
format: "esm",
|
|
2680
|
+
outDir: tempDir
|
|
2681
|
+
});
|
|
2682
|
+
try {
|
|
2683
|
+
const compiledPath = resolveCompiledPath(outDir, METADATA_ENTRY);
|
|
2684
|
+
const moduleExports = await importFresh(compiledPath);
|
|
2685
|
+
const metadataExport = extractMetadataExport(moduleExports);
|
|
2686
|
+
const parsed = AuthoredMetadataSchema.parse(metadataExport);
|
|
2687
|
+
return { metadata: parsed, sourcePath: absPath };
|
|
2688
|
+
} finally {
|
|
2689
|
+
cleanup();
|
|
2690
|
+
if (fs2.existsSync(tempDir)) {
|
|
2691
|
+
fs2.rmSync(tempDir, { recursive: true, force: true });
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
function extractMetadataExport(moduleExports) {
|
|
2696
|
+
if (!moduleExports || typeof moduleExports !== "object") {
|
|
2697
|
+
throw new Error("metadata.ts must export a metadata object");
|
|
2698
|
+
}
|
|
2699
|
+
const exportsObject = moduleExports;
|
|
2700
|
+
if (exportsObject.metadata) {
|
|
2701
|
+
return exportsObject.metadata;
|
|
2702
|
+
}
|
|
2703
|
+
if (exportsObject.default && typeof exportsObject.default === "object") {
|
|
2704
|
+
const defaultExport = exportsObject.default;
|
|
2705
|
+
if (defaultExport.metadata) {
|
|
2706
|
+
return defaultExport.metadata;
|
|
2707
|
+
}
|
|
2708
|
+
return defaultExport;
|
|
2709
|
+
}
|
|
2710
|
+
return moduleExports;
|
|
2711
|
+
}
|
|
2712
|
+
function readPackageJson(projectRoot) {
|
|
2713
|
+
const packagePath = path5.join(projectRoot, "package.json");
|
|
2714
|
+
if (!fs2.existsSync(packagePath)) {
|
|
2715
|
+
return {};
|
|
2716
|
+
}
|
|
2717
|
+
try {
|
|
2718
|
+
const content = fs2.readFileSync(packagePath, "utf8");
|
|
2719
|
+
return JSON.parse(content);
|
|
2720
|
+
} catch (error) {
|
|
2721
|
+
throw new Error(`Failed to read package.json: ${error}`);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
async function buildMetadataArtifact(options) {
|
|
2725
|
+
const projectRoot = options.projectRoot;
|
|
2726
|
+
const packageInfo = readPackageJson(projectRoot);
|
|
2727
|
+
const { metadata: authored, sourcePath } = await loadAuthoredMetadata(projectRoot);
|
|
2728
|
+
const defaultsApplied = [];
|
|
2729
|
+
const folderName = path5.basename(projectRoot);
|
|
2730
|
+
const name = resolveField(
|
|
2731
|
+
"name",
|
|
2732
|
+
authored.name,
|
|
2733
|
+
() => packageInfo.name ?? folderName,
|
|
2734
|
+
defaultsApplied,
|
|
2735
|
+
"package.json name"
|
|
2736
|
+
);
|
|
2737
|
+
const displayName = resolveField(
|
|
2738
|
+
"displayName",
|
|
2739
|
+
authored.displayName,
|
|
2740
|
+
() => {
|
|
2741
|
+
const source = packageInfo.name ?? folderName;
|
|
2742
|
+
return source.split(/[-_]/).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
|
|
2743
|
+
},
|
|
2744
|
+
defaultsApplied,
|
|
2745
|
+
"package.json name"
|
|
2746
|
+
);
|
|
2747
|
+
const versionRaw = resolveField(
|
|
2748
|
+
"version",
|
|
2749
|
+
authored.version,
|
|
2750
|
+
() => packageInfo.version ?? "0.1.0",
|
|
2751
|
+
defaultsApplied,
|
|
2752
|
+
"package.json version"
|
|
2753
|
+
);
|
|
2754
|
+
const version = typeof versionRaw === "number" ? String(versionRaw) : versionRaw;
|
|
2755
|
+
const category = determineCategory(authored, defaultsApplied);
|
|
2756
|
+
const description = authored.description ?? packageInfo.description;
|
|
2757
|
+
if (!authored.description && packageInfo.description) {
|
|
2758
|
+
defaultsApplied.push("description \u2192 package.json description");
|
|
2759
|
+
}
|
|
2760
|
+
const author = authored.author ?? packageInfo.author;
|
|
2761
|
+
if (!authored.author && packageInfo.author) {
|
|
2762
|
+
defaultsApplied.push("author \u2192 package.json author");
|
|
2763
|
+
}
|
|
2764
|
+
const repository = authored.repository ?? extractRepository(packageInfo.repository);
|
|
2765
|
+
if (!authored.repository && repository) {
|
|
2766
|
+
defaultsApplied.push("repository \u2192 package.json repository");
|
|
2767
|
+
}
|
|
2768
|
+
const website = authored.website ?? packageInfo.homepage;
|
|
2769
|
+
if (!authored.website && packageInfo.homepage) {
|
|
2770
|
+
defaultsApplied.push("website \u2192 package.json homepage");
|
|
2771
|
+
}
|
|
2772
|
+
const payment = resolvePayment(authored, defaultsApplied);
|
|
2773
|
+
const baseImage = authored.image ?? authored.iconPath;
|
|
2774
|
+
const animation = authored.animation_url ?? authored.videoPath;
|
|
2775
|
+
const discovery = buildDiscovery(authored);
|
|
2776
|
+
const metadataTools = options.tools.map((tool) => {
|
|
2777
|
+
const overrides = tool.metadata ? ToolMetadataOverridesSchema.parse(tool.metadata) : {};
|
|
2778
|
+
const toolName = overrides.name ?? tool.filename;
|
|
2779
|
+
const toolDescription = overrides.description ?? `${toolName} tool`;
|
|
2780
|
+
const toolPayment = overrides.payment ?? payment ?? void 0;
|
|
2781
|
+
if (!overrides.payment && toolPayment && payment && toolPayment === payment) {
|
|
2782
|
+
defaultsApplied.push(`tool ${toolName} payment \u2192 agent payment`);
|
|
2783
|
+
}
|
|
2784
|
+
const toolDiscovery = overrides.discovery ?? void 0;
|
|
2785
|
+
const toolDefinition = {
|
|
2786
|
+
name: toolName,
|
|
2787
|
+
description: toolDescription,
|
|
2788
|
+
inputSchema: tool.inputSchema
|
|
2789
|
+
};
|
|
2790
|
+
if (overrides.annotations) {
|
|
2791
|
+
toolDefinition.annotations = overrides.annotations;
|
|
2792
|
+
}
|
|
2793
|
+
if (toolPayment) {
|
|
2794
|
+
toolDefinition.payment = toolPayment;
|
|
2795
|
+
}
|
|
2796
|
+
if (toolDiscovery) {
|
|
2797
|
+
toolDefinition.discovery = toolDiscovery;
|
|
2798
|
+
}
|
|
2799
|
+
return toolDefinition;
|
|
2800
|
+
});
|
|
2801
|
+
const metadata = MetadataSchema.parse({
|
|
2802
|
+
metadataSpecVersion: authored.metadataSpecVersion ?? METADATA_SPEC_VERSION,
|
|
2803
|
+
name,
|
|
2804
|
+
displayName,
|
|
2805
|
+
version,
|
|
2806
|
+
description,
|
|
2807
|
+
author,
|
|
2808
|
+
repository,
|
|
2809
|
+
website,
|
|
2810
|
+
category,
|
|
2811
|
+
termsOfService: authored.termsOfService,
|
|
2812
|
+
mcpUrl: authored.mcpUrl,
|
|
2813
|
+
payment: payment ?? void 0,
|
|
2814
|
+
tools: metadataTools,
|
|
2815
|
+
discovery,
|
|
2816
|
+
promptExamples: authored.promptExamples,
|
|
2817
|
+
iconPath: authored.iconPath,
|
|
2818
|
+
videoPath: authored.videoPath,
|
|
2819
|
+
image: baseImage,
|
|
2820
|
+
animation_url: animation
|
|
2821
|
+
});
|
|
2822
|
+
return {
|
|
2823
|
+
metadata,
|
|
2824
|
+
defaultsApplied,
|
|
2825
|
+
sourceMetadataPath: sourcePath
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
function resolveField(field, value, fallback, defaultsApplied, fallbackLabel) {
|
|
2829
|
+
if (value !== void 0 && value !== null && value !== "") {
|
|
2830
|
+
return value;
|
|
2831
|
+
}
|
|
2832
|
+
const resolved = fallback();
|
|
2833
|
+
defaultsApplied.push(`${field} \u2192 ${fallbackLabel}`);
|
|
2834
|
+
return resolved;
|
|
2835
|
+
}
|
|
2836
|
+
function determineCategory(authored, defaultsApplied) {
|
|
2837
|
+
if (authored.category) {
|
|
2838
|
+
return authored.category;
|
|
2839
|
+
}
|
|
2840
|
+
if (Array.isArray(authored.categories) && authored.categories.length > 0) {
|
|
2841
|
+
defaultsApplied.push("category \u2192 metadata.categories[0]");
|
|
2842
|
+
return authored.categories[0];
|
|
2843
|
+
}
|
|
2844
|
+
defaultsApplied.push("category \u2192 default category");
|
|
2845
|
+
return "utility";
|
|
2846
|
+
}
|
|
2847
|
+
function extractRepository(repository) {
|
|
2848
|
+
if (!repository) {
|
|
2849
|
+
return void 0;
|
|
2850
|
+
}
|
|
2851
|
+
if (typeof repository === "string") {
|
|
2852
|
+
return repository;
|
|
2853
|
+
}
|
|
2854
|
+
return repository.url;
|
|
2855
|
+
}
|
|
2856
|
+
function resolvePayment(authored, defaults) {
|
|
2857
|
+
if (authored.payment) {
|
|
2858
|
+
return authored.payment;
|
|
2859
|
+
}
|
|
2860
|
+
const discoveryPricing = authored.discovery?.pricing;
|
|
2861
|
+
const legacyPricing = authored.pricing;
|
|
2862
|
+
const pricing = discoveryPricing ?? legacyPricing;
|
|
2863
|
+
if (!pricing) {
|
|
2864
|
+
return void 0;
|
|
2865
|
+
}
|
|
2866
|
+
const amount = typeof pricing.defaultAmount === "number" ? pricing.defaultAmount : 0;
|
|
2867
|
+
const sourceLabel = discoveryPricing ? "discovery.pricing.defaultAmount" : "pricing.defaultAmount";
|
|
2868
|
+
defaults.push(`payment \u2192 synthesized from ${sourceLabel}`);
|
|
2869
|
+
const acceptedMethodsRaw = Array.isArray(pricing.acceptedMethods) ? pricing.acceptedMethods : ["402"];
|
|
2870
|
+
const acceptedMethods = acceptedMethodsRaw.map(
|
|
2871
|
+
(method) => method === "x402" ? "x402" : "402"
|
|
2872
|
+
);
|
|
2873
|
+
return {
|
|
2874
|
+
amountUSDC: amount,
|
|
2875
|
+
description: typeof pricing.description === "string" ? pricing.description : void 0,
|
|
2876
|
+
x402: acceptedMethods.includes("x402"),
|
|
2877
|
+
plain402: acceptedMethods.includes("402"),
|
|
2878
|
+
acceptedMethods,
|
|
2879
|
+
acceptedCurrencies: Array.isArray(pricing.acceptedCurrencies) ? pricing.acceptedCurrencies : ["USDC"],
|
|
2880
|
+
chainIds: Array.isArray(pricing.chainIds) ? pricing.chainIds : [8453]
|
|
2881
|
+
};
|
|
2882
|
+
}
|
|
2883
|
+
function buildDiscovery(authored) {
|
|
2884
|
+
const legacyDiscovery = {};
|
|
2885
|
+
if (Array.isArray(authored.keywords) && authored.keywords.length > 0) {
|
|
2886
|
+
legacyDiscovery.keywords = authored.keywords;
|
|
2887
|
+
}
|
|
2888
|
+
if (Array.isArray(authored.useCases) && authored.useCases.length > 0) {
|
|
2889
|
+
legacyDiscovery.useCases = authored.useCases;
|
|
2890
|
+
}
|
|
2891
|
+
if (Array.isArray(authored.capabilities) && authored.capabilities.length > 0) {
|
|
2892
|
+
legacyDiscovery.capabilities = authored.capabilities;
|
|
2893
|
+
}
|
|
2894
|
+
if (authored.requirements) {
|
|
2895
|
+
legacyDiscovery.requirements = authored.requirements;
|
|
2896
|
+
}
|
|
2897
|
+
if (authored.compatibility) {
|
|
2898
|
+
legacyDiscovery.compatibility = authored.compatibility;
|
|
2899
|
+
}
|
|
2900
|
+
if (Array.isArray(authored.categories) && authored.categories.length > 0) {
|
|
2901
|
+
legacyDiscovery.category = authored.categories[0];
|
|
2902
|
+
}
|
|
2903
|
+
if (authored.pricing) {
|
|
2904
|
+
legacyDiscovery.pricing = authored.pricing;
|
|
2905
|
+
}
|
|
2906
|
+
const merged = {
|
|
2907
|
+
...legacyDiscovery,
|
|
2908
|
+
...authored.discovery ?? {}
|
|
2909
|
+
};
|
|
2910
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
2911
|
+
}
|
|
2912
|
+
var SUPPORTED_EXTENSIONS = [
|
|
2913
|
+
".ts",
|
|
2914
|
+
".tsx",
|
|
2915
|
+
".js",
|
|
2916
|
+
".jsx",
|
|
2917
|
+
".mjs",
|
|
2918
|
+
".cjs"
|
|
2919
|
+
];
|
|
2920
|
+
async function validateCommand(options) {
|
|
2921
|
+
console.log("\u{1F50D} Validating OpenTool metadata...");
|
|
2922
|
+
try {
|
|
2923
|
+
const toolsDir = path5.resolve(options.input);
|
|
2924
|
+
if (!fs2.existsSync(toolsDir)) {
|
|
2925
|
+
throw new Error(`Tools directory not found: ${toolsDir}`);
|
|
2926
|
+
}
|
|
2927
|
+
const projectRoot = path5.dirname(toolsDir);
|
|
2928
|
+
const tools = await loadAndValidateTools(toolsDir, { projectRoot });
|
|
2929
|
+
if (tools.length === 0) {
|
|
2930
|
+
throw new Error("No valid tools found - metadata validation aborted");
|
|
2931
|
+
}
|
|
2932
|
+
const { metadata, defaultsApplied, sourceMetadataPath } = await buildMetadataArtifact({
|
|
2933
|
+
projectRoot,
|
|
2934
|
+
tools
|
|
2935
|
+
});
|
|
2936
|
+
logMetadataSummary(metadata, defaultsApplied, sourceMetadataPath);
|
|
2937
|
+
console.log("\n\u2705 Metadata validation passed!\n");
|
|
2938
|
+
} catch (error) {
|
|
2939
|
+
console.error("\u274C Metadata validation failed:", error);
|
|
2940
|
+
process.exit(1);
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
async function loadAndValidateTools(toolsDir, options = {}) {
|
|
2944
|
+
const files = fs2.readdirSync(toolsDir).filter((file) => SUPPORTED_EXTENSIONS.includes(path5.extname(file)));
|
|
2945
|
+
if (files.length === 0) {
|
|
2946
|
+
return [];
|
|
2947
|
+
}
|
|
2948
|
+
const projectRoot = options.projectRoot ?? path5.dirname(toolsDir);
|
|
2949
|
+
const tempDir = path5.join(toolsDir, ".opentool-temp");
|
|
2950
|
+
if (fs2.existsSync(tempDir)) {
|
|
2951
|
+
fs2.rmSync(tempDir, { recursive: true, force: true });
|
|
2952
|
+
}
|
|
2953
|
+
const entryPoints = files.map((file) => path5.join(toolsDir, file));
|
|
2954
|
+
const { outDir, cleanup } = await transpileWithEsbuild({
|
|
2955
|
+
entryPoints,
|
|
2956
|
+
projectRoot,
|
|
2957
|
+
format: "esm",
|
|
2958
|
+
outDir: tempDir
|
|
2959
|
+
});
|
|
2960
|
+
const tools = [];
|
|
2961
|
+
try {
|
|
2962
|
+
for (const file of files) {
|
|
2963
|
+
const compiledPath = resolveCompiledPath(outDir, file);
|
|
2964
|
+
if (!fs2.existsSync(compiledPath)) {
|
|
2965
|
+
throw new Error(`Failed to compile ${file}`);
|
|
2966
|
+
}
|
|
2967
|
+
const moduleExports = await importFresh(compiledPath);
|
|
2968
|
+
const toolModule = extractToolModule(moduleExports, file);
|
|
2969
|
+
const schema = ensureZodSchema(toolModule.schema, file);
|
|
2970
|
+
const paymentExport = toolModule.payment;
|
|
2971
|
+
const toolName = toolModule.metadata?.name ?? toolModule.metadata?.title ?? toBaseName(file);
|
|
2972
|
+
const inputSchemaRaw = schema ? toJsonSchema(toolName, schema) : void 0;
|
|
2973
|
+
const inputSchema = normalizeInputSchema2(inputSchemaRaw);
|
|
2974
|
+
const httpHandlersRaw = collectHttpHandlers2(toolModule, file);
|
|
2975
|
+
const httpHandlers = [...httpHandlersRaw];
|
|
2976
|
+
if (httpHandlers.length === 0) {
|
|
2977
|
+
throw new Error(
|
|
2978
|
+
`${file} must export at least one HTTP handler (e.g. POST)`
|
|
2979
|
+
);
|
|
2980
|
+
}
|
|
2981
|
+
if (paymentExport) {
|
|
2982
|
+
for (let index = 0; index < httpHandlers.length; index += 1) {
|
|
2983
|
+
const entry = httpHandlers[index];
|
|
2984
|
+
httpHandlers[index] = {
|
|
2985
|
+
...entry,
|
|
2986
|
+
handler: withPaymentRequirement(entry.handler, paymentExport)
|
|
2987
|
+
};
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
const httpHandlerMap = toHttpHandlerMap2(httpHandlers);
|
|
2991
|
+
const defaultMethod = typeof toolModule.mcp?.defaultMethod === "string" ? toolModule.mcp.defaultMethod : void 0;
|
|
2992
|
+
const adapter = createMcpAdapter({
|
|
2993
|
+
name: toolName,
|
|
2994
|
+
httpHandlers: httpHandlerMap,
|
|
2995
|
+
...defaultMethod ? { defaultMethod } : {},
|
|
2996
|
+
...schema ? { schema } : {}
|
|
2997
|
+
});
|
|
2998
|
+
let metadataOverrides = toolModule.metadata ?? null;
|
|
2999
|
+
if (paymentExport?.metadata) {
|
|
3000
|
+
if (metadataOverrides) {
|
|
3001
|
+
metadataOverrides = {
|
|
3002
|
+
...metadataOverrides,
|
|
3003
|
+
payment: metadataOverrides.payment ?? paymentExport.metadata,
|
|
3004
|
+
annotations: {
|
|
3005
|
+
...metadataOverrides.annotations ?? {},
|
|
3006
|
+
requiresPayment: metadataOverrides.annotations?.requiresPayment ?? true
|
|
3007
|
+
}
|
|
3008
|
+
};
|
|
3009
|
+
} else {
|
|
3010
|
+
metadataOverrides = {
|
|
3011
|
+
payment: paymentExport.metadata,
|
|
3012
|
+
annotations: { requiresPayment: true }
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
const tool = {
|
|
3017
|
+
schema: schema ?? void 0,
|
|
3018
|
+
inputSchema,
|
|
3019
|
+
metadata: metadataOverrides,
|
|
3020
|
+
httpHandlers,
|
|
3021
|
+
mcpConfig: normalizeMcpConfig(toolModule.mcp, file),
|
|
3022
|
+
filename: toBaseName(file),
|
|
3023
|
+
sourcePath: path5.join(toolsDir, file),
|
|
3024
|
+
handler: async (params) => adapter(params),
|
|
3025
|
+
payment: paymentExport ?? null
|
|
3026
|
+
};
|
|
3027
|
+
tools.push(tool);
|
|
3028
|
+
}
|
|
3029
|
+
} finally {
|
|
3030
|
+
cleanup();
|
|
3031
|
+
if (fs2.existsSync(tempDir)) {
|
|
3032
|
+
fs2.rmSync(tempDir, { recursive: true, force: true });
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
return tools;
|
|
3036
|
+
}
|
|
3037
|
+
function extractToolModule(exportsObject, filename) {
|
|
3038
|
+
const candidates = [exportsObject, exportsObject?.default];
|
|
3039
|
+
for (const candidate of candidates) {
|
|
3040
|
+
if (candidate && typeof candidate === "object") {
|
|
3041
|
+
const hasSchema = candidate.schema && typeof candidate.schema === "object";
|
|
3042
|
+
const hasHttp = HTTP_METHODS2.some((method) => typeof candidate[method] === "function");
|
|
3043
|
+
if (hasSchema || hasHttp) {
|
|
3044
|
+
return candidate;
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
throw new Error(
|
|
3049
|
+
`${filename} must export a tool definition. Expected a Zod schema plus HTTP handlers (export async function POST).`
|
|
3050
|
+
);
|
|
3051
|
+
}
|
|
3052
|
+
function toJsonSchema(name, schema) {
|
|
3053
|
+
if (!schema) {
|
|
3054
|
+
return void 0;
|
|
3055
|
+
}
|
|
3056
|
+
try {
|
|
3057
|
+
return zodToJsonSchema(schema, {
|
|
3058
|
+
name: `${name}Schema`,
|
|
3059
|
+
target: "jsonSchema7",
|
|
3060
|
+
$refStrategy: "none"
|
|
3061
|
+
});
|
|
3062
|
+
} catch (error) {
|
|
3063
|
+
throw new Error(`Failed to convert Zod schema for ${name}: ${error}`);
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
function toBaseName(file) {
|
|
3067
|
+
return file.replace(/\.[^.]+$/, "");
|
|
3068
|
+
}
|
|
3069
|
+
function ensureZodSchema(schemaCandidate, filename) {
|
|
3070
|
+
if (schemaCandidate == null) {
|
|
3071
|
+
return void 0;
|
|
3072
|
+
}
|
|
3073
|
+
if (schemaCandidate instanceof z.ZodType) {
|
|
3074
|
+
return schemaCandidate;
|
|
3075
|
+
}
|
|
3076
|
+
const schema = schemaCandidate;
|
|
3077
|
+
if (typeof schema?.parse !== "function") {
|
|
3078
|
+
throw new Error(`${filename} schema export must be a Zod schema (missing parse method)`);
|
|
3079
|
+
}
|
|
3080
|
+
return schema;
|
|
3081
|
+
}
|
|
3082
|
+
function collectHttpHandlers2(module, filename) {
|
|
3083
|
+
const handlers = [];
|
|
3084
|
+
for (const method of HTTP_METHODS2) {
|
|
3085
|
+
const handler = module?.[method];
|
|
3086
|
+
if (typeof handler === "function") {
|
|
3087
|
+
handlers.push({
|
|
3088
|
+
method,
|
|
3089
|
+
handler: async (request) => handler.call(module, request)
|
|
3090
|
+
});
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
handlers.sort((a, b) => HTTP_METHODS2.indexOf(a.method) - HTTP_METHODS2.indexOf(b.method));
|
|
3094
|
+
const duplicates = findDuplicates(handlers.map((h) => h.method));
|
|
3095
|
+
if (duplicates.length > 0) {
|
|
3096
|
+
throw new Error(
|
|
3097
|
+
`${filename} exports multiple handlers for HTTP method(s): ${duplicates.join(", ")}`
|
|
3098
|
+
);
|
|
3099
|
+
}
|
|
3100
|
+
return handlers;
|
|
3101
|
+
}
|
|
3102
|
+
function toHttpHandlerMap2(handlers) {
|
|
3103
|
+
return handlers.reduce((acc, handler) => {
|
|
3104
|
+
acc[handler.method.toUpperCase()] = handler.handler;
|
|
3105
|
+
return acc;
|
|
3106
|
+
}, {});
|
|
3107
|
+
}
|
|
3108
|
+
function normalizeInputSchema2(schema) {
|
|
3109
|
+
if (!schema || typeof schema !== "object") {
|
|
3110
|
+
return schema;
|
|
3111
|
+
}
|
|
3112
|
+
const clone = JSON.parse(JSON.stringify(schema));
|
|
3113
|
+
if (typeof clone.$ref === "string" && clone.$ref.startsWith("#/definitions/")) {
|
|
3114
|
+
const refName = clone.$ref.replace("#/definitions/", "");
|
|
3115
|
+
const definitions = clone.definitions;
|
|
3116
|
+
if (definitions && typeof definitions[refName] === "object") {
|
|
3117
|
+
return normalizeInputSchema2(definitions[refName]);
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
delete clone.$ref;
|
|
3121
|
+
delete clone.definitions;
|
|
3122
|
+
if (!("type" in clone)) {
|
|
3123
|
+
clone.type = "object";
|
|
3124
|
+
}
|
|
3125
|
+
return clone;
|
|
3126
|
+
}
|
|
3127
|
+
function normalizeMcpConfig(rawConfig, filename) {
|
|
3128
|
+
if (rawConfig == null) {
|
|
3129
|
+
return null;
|
|
3130
|
+
}
|
|
3131
|
+
if (rawConfig === false) {
|
|
3132
|
+
return null;
|
|
3133
|
+
}
|
|
3134
|
+
if (rawConfig === true) {
|
|
3135
|
+
return { enabled: true };
|
|
3136
|
+
}
|
|
3137
|
+
if (!isPlainObject2(rawConfig)) {
|
|
3138
|
+
throw new Error(`${filename} export \\"mcp\\" must be an object with an enabled flag`);
|
|
3139
|
+
}
|
|
3140
|
+
const enabledRaw = rawConfig.enabled;
|
|
3141
|
+
if (enabledRaw === false) {
|
|
3142
|
+
return null;
|
|
3143
|
+
}
|
|
3144
|
+
if (enabledRaw !== true) {
|
|
3145
|
+
throw new Error(`${filename} mcp.enabled must be explicitly set to true to opt-in to MCP`);
|
|
3146
|
+
}
|
|
3147
|
+
const modeRaw = rawConfig.mode;
|
|
3148
|
+
let mode;
|
|
3149
|
+
if (typeof modeRaw === "string") {
|
|
3150
|
+
const normalized = modeRaw.toLowerCase();
|
|
3151
|
+
if (["stdio", "lambda", "dual"].includes(normalized)) {
|
|
3152
|
+
mode = normalized;
|
|
3153
|
+
} else {
|
|
3154
|
+
throw new Error(
|
|
3155
|
+
`${filename} mcp.mode must be one of "stdio", "lambda", or "dual" if specified`
|
|
3156
|
+
);
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
const defaultMethodRaw = rawConfig.defaultMethod;
|
|
3160
|
+
const defaultMethod = typeof defaultMethodRaw === "string" ? defaultMethodRaw.toUpperCase() : void 0;
|
|
3161
|
+
const overridesRaw = rawConfig.metadataOverrides;
|
|
3162
|
+
const metadataOverrides = isPlainObject2(overridesRaw) ? overridesRaw : void 0;
|
|
3163
|
+
const config = {
|
|
3164
|
+
enabled: true
|
|
3165
|
+
};
|
|
3166
|
+
if (mode) {
|
|
3167
|
+
config.mode = mode;
|
|
3168
|
+
}
|
|
3169
|
+
if (defaultMethod) {
|
|
3170
|
+
config.defaultMethod = defaultMethod;
|
|
3171
|
+
}
|
|
3172
|
+
if (metadataOverrides) {
|
|
3173
|
+
config.metadataOverrides = metadataOverrides;
|
|
3174
|
+
}
|
|
3175
|
+
return config;
|
|
3176
|
+
}
|
|
3177
|
+
function isPlainObject2(value) {
|
|
3178
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3179
|
+
}
|
|
3180
|
+
function findDuplicates(values) {
|
|
3181
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3182
|
+
const duplicates = /* @__PURE__ */ new Set();
|
|
3183
|
+
values.forEach((value) => {
|
|
3184
|
+
const count = seen.get(value) ?? 0;
|
|
3185
|
+
seen.set(value, count + 1);
|
|
3186
|
+
if (count >= 1) {
|
|
3187
|
+
duplicates.add(value);
|
|
3188
|
+
}
|
|
3189
|
+
});
|
|
3190
|
+
return Array.from(duplicates.values());
|
|
3191
|
+
}
|
|
3192
|
+
function logMetadataSummary(metadata, defaultsApplied, sourceMetadataPath) {
|
|
3193
|
+
console.log(`\u{1F4C4} metadata loaded from ${sourceMetadataPath}`);
|
|
3194
|
+
console.log("\n\u{1F4CA} Metadata Summary:");
|
|
3195
|
+
console.log(` \u2022 Name: ${metadata.name}`);
|
|
3196
|
+
console.log(` \u2022 Display Name: ${metadata.displayName}`);
|
|
3197
|
+
console.log(` \u2022 Version: ${metadata.version}`);
|
|
3198
|
+
console.log(` \u2022 Category: ${metadata.category}`);
|
|
3199
|
+
console.log(` \u2022 Tools: ${metadata.tools.length}`);
|
|
3200
|
+
console.log(` \u2022 Spec Version: ${metadata.metadataSpecVersion}`);
|
|
3201
|
+
if (metadata.payment) {
|
|
3202
|
+
console.log(` \u2022 Payment: $${metadata.payment.amountUSDC} USDC`);
|
|
3203
|
+
}
|
|
3204
|
+
if (defaultsApplied.length > 0) {
|
|
3205
|
+
console.log("\nDefaults applied during metadata synthesis:");
|
|
3206
|
+
defaultsApplied.forEach((entry) => console.log(` \u2022 ${entry}`));
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
// src/cli/generate-metadata.ts
|
|
3211
|
+
async function generateMetadataCommand(options) {
|
|
3212
|
+
const startTimestamp = timestamp();
|
|
3213
|
+
console.log(`[${startTimestamp}] Generating OpenTool metadata...`);
|
|
3214
|
+
try {
|
|
3215
|
+
const result = await generateMetadata(options);
|
|
3216
|
+
const endTimestamp = timestamp();
|
|
3217
|
+
console.log(`[${endTimestamp}] Metadata generation completed successfully!`);
|
|
3218
|
+
console.log(`Output file: ${result.outputPath}`);
|
|
3219
|
+
console.log(`Spec version: ${result.metadata.metadataSpecVersion}`);
|
|
3220
|
+
console.log(`Tools included: ${result.tools.length}`);
|
|
3221
|
+
if (result.defaultsApplied.length > 0) {
|
|
3222
|
+
console.log("Applied defaults:");
|
|
3223
|
+
for (const entry of result.defaultsApplied) {
|
|
3224
|
+
console.log(` \u2022 ${entry}`);
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
} catch (error) {
|
|
3228
|
+
const endTimestamp = timestamp();
|
|
3229
|
+
console.error(`[${endTimestamp}] Metadata generation failed:`, error);
|
|
3230
|
+
process.exit(1);
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
async function generateMetadata(options) {
|
|
3234
|
+
const toolsDir = path5.resolve(options.input);
|
|
3235
|
+
if (!fs2.existsSync(toolsDir)) {
|
|
3236
|
+
throw new Error(`Tools directory not found: ${toolsDir}`);
|
|
3237
|
+
}
|
|
3238
|
+
const projectRoot = path5.dirname(toolsDir);
|
|
3239
|
+
const tools = await loadAndValidateTools(toolsDir, { projectRoot });
|
|
3240
|
+
const { metadata, defaultsApplied } = await buildMetadataArtifact({
|
|
3241
|
+
projectRoot,
|
|
3242
|
+
tools
|
|
3243
|
+
});
|
|
3244
|
+
const outputPath = options.output ? path5.resolve(options.output) : path5.join(projectRoot, "metadata.json");
|
|
3245
|
+
fs2.writeFileSync(outputPath, JSON.stringify(metadata, null, 2));
|
|
3246
|
+
return {
|
|
3247
|
+
metadata,
|
|
3248
|
+
defaultsApplied,
|
|
3249
|
+
tools,
|
|
3250
|
+
outputPath
|
|
3251
|
+
};
|
|
3252
|
+
}
|
|
3253
|
+
function timestamp() {
|
|
3254
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
export { AIAbortError, AIError, AIFetchError, AIResponseError, DEFAULT_BASE_URL, DEFAULT_CHAIN, DEFAULT_MODEL, DEFAULT_TIMEOUT_MS, DEFAULT_TOKENS, HEADER_PAYMENT_RESPONSE, HTTP_METHODS2 as HTTP_METHODS, PAYMENT_HEADERS, PAYMENT_SCHEMA_VERSION, PaymentRequiredError, WEBSEARCH_TOOL_DEFINITION, WEBSEARCH_TOOL_NAME, chains, createAIClient, createDevServer, createMcpAdapter, createPaymentRequiredBody, createPaymentResponseHeader, createStdioServer, cryptoAssetSchema, currencySchema, decimalStringSchema, definePayment, directPaymentPayloadSchema, directProofSchema, ensureTextContent, extractPaymentAttempts, facilitatorConfigSchema, flattenMessageContent, generateMetadata, generateMetadataCommand, generateText, getModelConfig, getPaymentContext, getRpcUrl, isStreamingSupported, isToolCallingSupported, listModels, loadAndValidateTools, normalizeModelName, paymentAmountSchema, paymentFailureSchema, paymentFieldSchema, paymentOptionSchema, paymentProofSchema, paymentRequiredResponse, paymentRequirementsSchema, paymentSchemaVersionSchema, paymentSuccessMetadataSchema, registry, requirePayment, resolveConfig, resolveRuntimePath, resolveToolset, responseToToolResponse, settlementTermsSchema, streamText, tokens, validateCommand, verifyPayment, wallet, walletToolkit, withPaymentRequirement, x402PaymentHeaderSchema, x402ProofSchema };
|
|
3258
|
+
//# sourceMappingURL=index.js.map
|
|
26
3259
|
//# sourceMappingURL=index.js.map
|