@clawcash/forge 0.1.8 → 0.2.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 +39 -44
- package/dist/index.cjs +329 -562
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -171
- package/dist/index.d.ts +75 -171
- package/dist/index.js +314 -549
- package/dist/index.js.map +1 -1
- package/package.json +2 -12
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,33 +17,33 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
21
31
|
var index_exports = {};
|
|
22
32
|
__export(index_exports, {
|
|
23
33
|
FORGE_GATEWAY_USER_ID: () => FORGE_GATEWAY_USER_ID,
|
|
24
|
-
PublicBaseUrlResolver: () => PublicBaseUrlResolver,
|
|
25
34
|
buildForgeSurfaceStatus: () => buildForgeSurfaceStatus,
|
|
26
35
|
buildWellKnownDocument: () => buildWellKnownDocument,
|
|
36
|
+
checkType: () => checkType,
|
|
27
37
|
createContext: () => createContext,
|
|
28
|
-
|
|
38
|
+
createForgeHandler: () => createForgeHandler,
|
|
39
|
+
createForgeRequestMatcher: () => createForgeRequestMatcher,
|
|
29
40
|
defineAgentService: () => defineAgentService,
|
|
30
41
|
forgeGatewayLoopbackHeaders: () => forgeGatewayLoopbackHeaders,
|
|
31
42
|
forgeSdkVersion: () => forgeSdkVersion,
|
|
32
|
-
|
|
43
|
+
forgeWorker: () => forgeWorker,
|
|
33
44
|
getForgeApiKey: () => getForgeApiKey,
|
|
34
|
-
getForgeGatewaySecret: () => getForgeGatewaySecret,
|
|
35
|
-
getLastHeartbeat: () => getLastHeartbeat,
|
|
36
|
-
heartbeatGateway: () => heartbeatGateway,
|
|
37
|
-
isForgeGatewayRequest: () => isForgeGatewayRequest,
|
|
38
|
-
isZeroAddress: () => isZeroAddress,
|
|
39
|
-
mountAgentServices: () => mountAgentServices,
|
|
40
|
-
recordHeartbeatResult: () => recordHeartbeatResult,
|
|
41
|
-
requireForgeGateway: () => requireForgeGateway,
|
|
42
|
-
resolveBaseUrlFromEnv: () => resolveBaseUrlFromEnv,
|
|
43
|
-
resolvePayTo: () => resolvePayTo,
|
|
44
45
|
toKebabCase: () => toKebabCase,
|
|
46
|
+
validateInput: () => validateInput,
|
|
45
47
|
waitUntil: () => waitUntil
|
|
46
48
|
});
|
|
47
49
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -84,136 +86,8 @@ function defineAgentService(config) {
|
|
|
84
86
|
};
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
// src/
|
|
88
|
-
var
|
|
89
|
-
var import_express2 = require("@x402/express");
|
|
90
|
-
var import_server = require("@x402/core/server");
|
|
91
|
-
var import_server2 = require("@x402/evm/exact/server");
|
|
92
|
-
|
|
93
|
-
// src/base-url.ts
|
|
94
|
-
var import_node_crypto = require("crypto");
|
|
95
|
-
function resolveBaseUrlFromEnv(explicit) {
|
|
96
|
-
const fromExplicit = explicit?.trim();
|
|
97
|
-
if (fromExplicit) return stripSlash(fromExplicit);
|
|
98
|
-
const fromEnv = process.env.PUBLIC_BASE_URL?.trim();
|
|
99
|
-
if (fromEnv) return stripSlash(fromEnv);
|
|
100
|
-
const railway = process.env.RAILWAY_PUBLIC_DOMAIN?.trim();
|
|
101
|
-
if (railway) return `https://${stripSlash(railway)}`;
|
|
102
|
-
const render = process.env.RENDER_EXTERNAL_URL?.trim();
|
|
103
|
-
if (render) return stripSlash(render);
|
|
104
|
-
const vercel = process.env.VERCEL_PROJECT_PRODUCTION_URL?.trim() || process.env.VERCEL_URL?.trim();
|
|
105
|
-
if (vercel) return `https://${stripSlash(vercel)}`;
|
|
106
|
-
const fly = process.env.FLY_APP_NAME?.trim();
|
|
107
|
-
if (fly) return `https://${fly}.fly.dev`;
|
|
108
|
-
const heroku = process.env.HEROKU_APP_NAME?.trim();
|
|
109
|
-
if (heroku) return `https://${heroku}.herokuapp.com`;
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
function stripSlash(url) {
|
|
113
|
-
return url.replace(/\/$/, "");
|
|
114
|
-
}
|
|
115
|
-
var MAX_VERIFY_ATTEMPTS = 5;
|
|
116
|
-
var VERIFY_TIMEOUT_MS = 3e3;
|
|
117
|
-
var PublicBaseUrlResolver = class {
|
|
118
|
-
/** Per-boot nonce, echoed by /forge/health for self-verification. */
|
|
119
|
-
instanceNonce;
|
|
120
|
-
resolved;
|
|
121
|
-
verifying = false;
|
|
122
|
-
rejected = /* @__PURE__ */ new Set();
|
|
123
|
-
attempts = 0;
|
|
124
|
-
onResolved;
|
|
125
|
-
constructor(opts) {
|
|
126
|
-
this.instanceNonce = (0, import_node_crypto.randomBytes)(16).toString("hex");
|
|
127
|
-
this.resolved = opts.initial;
|
|
128
|
-
this.onResolved = opts.onResolved ?? null;
|
|
129
|
-
}
|
|
130
|
-
get() {
|
|
131
|
-
return this.resolved;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Feed an inbound Forge-surface request. No-op once resolved. Kicks off
|
|
135
|
-
* async self-verification of the candidate URL derived from the request.
|
|
136
|
-
*/
|
|
137
|
-
considerRequest(req) {
|
|
138
|
-
if (this.resolved || this.verifying) return;
|
|
139
|
-
if (this.attempts >= MAX_VERIFY_ATTEMPTS) return;
|
|
140
|
-
const candidate = candidateFromRequest(req);
|
|
141
|
-
if (!candidate || this.rejected.has(candidate)) return;
|
|
142
|
-
this.verifying = true;
|
|
143
|
-
this.attempts += 1;
|
|
144
|
-
void this.verify(candidate).then((ok) => {
|
|
145
|
-
if (ok) {
|
|
146
|
-
this.resolved = candidate;
|
|
147
|
-
console.log(`[forge] public base URL inferred and verified: ${candidate}`);
|
|
148
|
-
this.onResolved?.(candidate);
|
|
149
|
-
} else {
|
|
150
|
-
this.rejected.add(candidate);
|
|
151
|
-
console.warn(
|
|
152
|
-
`[forge] candidate base URL failed self-verification, ignoring: ${candidate}`
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
}).finally(() => {
|
|
156
|
-
this.verifying = false;
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
/** Fetch our own /forge/health at the candidate and match the boot nonce. */
|
|
160
|
-
async verify(candidate) {
|
|
161
|
-
try {
|
|
162
|
-
const controller = new AbortController();
|
|
163
|
-
const timer = setTimeout(() => controller.abort(), VERIFY_TIMEOUT_MS);
|
|
164
|
-
const res = await fetch(`${candidate}/forge/health`, {
|
|
165
|
-
signal: controller.signal,
|
|
166
|
-
headers: { accept: "application/json" },
|
|
167
|
-
redirect: "manual"
|
|
168
|
-
});
|
|
169
|
-
clearTimeout(timer);
|
|
170
|
-
if (!res.ok) return false;
|
|
171
|
-
const json = await res.json();
|
|
172
|
-
return json.instance === this.instanceNonce;
|
|
173
|
-
} catch {
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
function candidateFromRequest(req) {
|
|
179
|
-
const host = firstHeaderValue(req.headers["x-forwarded-host"]) ?? req.headers.host;
|
|
180
|
-
if (!host || !isValidHost(host)) return null;
|
|
181
|
-
const proto = firstHeaderValue(req.headers["x-forwarded-proto"]) ?? (req.protocol || "http");
|
|
182
|
-
if (proto !== "https" && proto !== "http") return null;
|
|
183
|
-
const isLocal = /^(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/i.test(host);
|
|
184
|
-
if (proto === "http" && !isLocal) return null;
|
|
185
|
-
return `${proto}://${host}`;
|
|
186
|
-
}
|
|
187
|
-
function firstHeaderValue(value) {
|
|
188
|
-
const raw = Array.isArray(value) ? value[0] : value;
|
|
189
|
-
const first = raw?.split(",")[0]?.trim();
|
|
190
|
-
return first || null;
|
|
191
|
-
}
|
|
192
|
-
function isValidHost(host) {
|
|
193
|
-
return /^[a-z0-9.-]+(:\d{1,5})?$/i.test(host) || /^\[[0-9a-f:]+\](:\d{1,5})?$/i.test(host);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// src/gateway.ts
|
|
197
|
-
async function heartbeatGateway(opts) {
|
|
198
|
-
const gateway = opts.gatewayUrl.replace(/\/$/, "");
|
|
199
|
-
const res = await fetch(`${gateway}/register/heartbeat`, {
|
|
200
|
-
method: "POST",
|
|
201
|
-
headers: {
|
|
202
|
-
"content-type": "application/json",
|
|
203
|
-
authorization: `Bearer ${opts.registerSecret}`
|
|
204
|
-
},
|
|
205
|
-
body: JSON.stringify({
|
|
206
|
-
handle: opts.handle.trim().toLowerCase(),
|
|
207
|
-
baseUrl: opts.baseUrl.replace(/\/$/, "")
|
|
208
|
-
})
|
|
209
|
-
});
|
|
210
|
-
if (!res.ok) {
|
|
211
|
-
const text = await res.text();
|
|
212
|
-
throw new Error(
|
|
213
|
-
`gateway heartbeat failed (${res.status}): ${text.slice(0, 240)}`
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
89
|
+
// src/worker.ts
|
|
90
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
217
91
|
|
|
218
92
|
// src/gateway-auth.ts
|
|
219
93
|
function getForgeApiKey(explicit) {
|
|
@@ -221,29 +95,7 @@ function getForgeApiKey(explicit) {
|
|
|
221
95
|
if (!value || value.length < 16) return null;
|
|
222
96
|
return value;
|
|
223
97
|
}
|
|
224
|
-
function getForgeGatewaySecret(explicit) {
|
|
225
|
-
return getForgeApiKey(explicit);
|
|
226
|
-
}
|
|
227
|
-
function isForgeGatewayRequest(req, secret) {
|
|
228
|
-
const expected = getForgeApiKey(secret);
|
|
229
|
-
if (!expected) return false;
|
|
230
|
-
const header = req.header("authorization") ?? "";
|
|
231
|
-
const bearer = header.toLowerCase().startsWith("bearer ") ? header.slice(7).trim() : "";
|
|
232
|
-
const alt = req.header("x-forge-api-key")?.trim() || req.header("x-clawcash-gateway")?.trim() || "";
|
|
233
|
-
return bearer === expected || alt === expected;
|
|
234
|
-
}
|
|
235
98
|
var FORGE_GATEWAY_USER_ID = "forge_gateway_agent";
|
|
236
|
-
function requireForgeGateway(secret) {
|
|
237
|
-
return (req, res, next) => {
|
|
238
|
-
if (!isForgeGatewayRequest(req, secret)) {
|
|
239
|
-
res.status(401).json({
|
|
240
|
-
error: "Unauthorized \u2014 Forge API key required (FORGE_API_KEY from Forge dashboard)"
|
|
241
|
-
});
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
next();
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
99
|
function forgeGatewayLoopbackHeaders(secret) {
|
|
248
100
|
const expected = getForgeApiKey(secret);
|
|
249
101
|
if (!expected) return {};
|
|
@@ -253,139 +105,7 @@ function forgeGatewayLoopbackHeaders(secret) {
|
|
|
253
105
|
};
|
|
254
106
|
}
|
|
255
107
|
|
|
256
|
-
// src/
|
|
257
|
-
var ZERO = /^0x0{40}$/i;
|
|
258
|
-
function isZeroAddress(address) {
|
|
259
|
-
if (!address) return true;
|
|
260
|
-
return ZERO.test(address.trim());
|
|
261
|
-
}
|
|
262
|
-
function normalizeBaseUrl(url) {
|
|
263
|
-
return url.replace(/\/$/, "");
|
|
264
|
-
}
|
|
265
|
-
function pickForgeBaseUrl(source) {
|
|
266
|
-
const fromEnv = process.env.FORGE_URL?.trim();
|
|
267
|
-
if (fromEnv) return normalizeBaseUrl(fromEnv);
|
|
268
|
-
const fromOpts = source?.baseUrl?.trim();
|
|
269
|
-
if (!fromOpts) return null;
|
|
270
|
-
const normalized = normalizeBaseUrl(fromOpts);
|
|
271
|
-
if (/localhost|127\.0\.0\.1/i.test(normalized) && process.env.NODE_ENV === "production") {
|
|
272
|
-
return null;
|
|
273
|
-
}
|
|
274
|
-
return normalized;
|
|
275
|
-
}
|
|
276
|
-
async function resolvePayTo(opts) {
|
|
277
|
-
const envOverride = process.env.PAY_TO?.trim();
|
|
278
|
-
if (envOverride && !isZeroAddress(envOverride)) {
|
|
279
|
-
return envOverride;
|
|
280
|
-
}
|
|
281
|
-
const explicit = opts.payTo?.trim();
|
|
282
|
-
if (explicit && !isZeroAddress(explicit) && !opts.forge) {
|
|
283
|
-
return explicit;
|
|
284
|
-
}
|
|
285
|
-
const baseUrl = pickForgeBaseUrl(opts.forge);
|
|
286
|
-
if (baseUrl && opts.forge) {
|
|
287
|
-
const url = `${baseUrl}/api/public/pay-to/${encodeURIComponent(opts.forge.owner)}/${encodeURIComponent(opts.forge.repo)}`;
|
|
288
|
-
try {
|
|
289
|
-
const controller = new AbortController();
|
|
290
|
-
const timer = setTimeout(
|
|
291
|
-
() => controller.abort(),
|
|
292
|
-
opts.timeoutMs ?? 2500
|
|
293
|
-
);
|
|
294
|
-
const res = await fetch(url, {
|
|
295
|
-
signal: controller.signal,
|
|
296
|
-
headers: { accept: "application/json" }
|
|
297
|
-
});
|
|
298
|
-
clearTimeout(timer);
|
|
299
|
-
if (res.ok) {
|
|
300
|
-
const json = await res.json();
|
|
301
|
-
if (json.address && !isZeroAddress(json.address)) {
|
|
302
|
-
return json.address.trim();
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
} catch (err) {
|
|
306
|
-
console.warn("[forge] live payTo fetch failed, using fallback:", err);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
if (explicit && !isZeroAddress(explicit)) return explicit;
|
|
310
|
-
if (opts.fallback && !isZeroAddress(opts.fallback)) return opts.fallback;
|
|
311
|
-
throw new Error(
|
|
312
|
-
"resolvePayTo: no usable address (set Forge wallet / PAY_TO, or re-run Forge PR)"
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// src/status.ts
|
|
317
|
-
var PACKAGE_VERSION = "0.1.8";
|
|
318
|
-
var lastHeartbeat = {
|
|
319
|
-
attempted: false,
|
|
320
|
-
ok: null,
|
|
321
|
-
at: null,
|
|
322
|
-
error: null
|
|
323
|
-
};
|
|
324
|
-
function recordHeartbeatResult(result) {
|
|
325
|
-
lastHeartbeat = {
|
|
326
|
-
attempted: true,
|
|
327
|
-
ok: result.ok,
|
|
328
|
-
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
329
|
-
error: result.ok ? null : result.error
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
function getLastHeartbeat() {
|
|
333
|
-
return { ...lastHeartbeat };
|
|
334
|
-
}
|
|
335
|
-
function buildForgeSurfaceStatus(opts) {
|
|
336
|
-
const fulfillPath = opts.fulfillPath.replace(/\/$/, "") || "/forge/fulfill";
|
|
337
|
-
const agentBasePath = opts.agentBasePath.replace(/\/$/, "") || "/agent";
|
|
338
|
-
const ready = opts.fulfillMounted && Boolean(opts.publicBaseUrl) && opts.actions.length > 0;
|
|
339
|
-
return {
|
|
340
|
-
ok: true,
|
|
341
|
-
forge: true,
|
|
342
|
-
version: PACKAGE_VERSION,
|
|
343
|
-
handle: opts.handle,
|
|
344
|
-
publicBaseUrl: opts.publicBaseUrl,
|
|
345
|
-
gatewayUrl: opts.gatewayUrl,
|
|
346
|
-
fulfillMounted: opts.fulfillMounted,
|
|
347
|
-
fulfillPath,
|
|
348
|
-
agentBasePath,
|
|
349
|
-
skipPayment: opts.skipPayment,
|
|
350
|
-
actions: opts.actions,
|
|
351
|
-
ready,
|
|
352
|
-
heartbeat: getLastHeartbeat(),
|
|
353
|
-
endpoints: {
|
|
354
|
-
health: "/forge/health",
|
|
355
|
-
status: "/forge/status",
|
|
356
|
-
wellKnown: "/.well-known/clawcash.json",
|
|
357
|
-
fulfill: fulfillPath,
|
|
358
|
-
agent: agentBasePath
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
function buildWellKnownDocument(status) {
|
|
363
|
-
return {
|
|
364
|
-
forge: true,
|
|
365
|
-
version: status.version,
|
|
366
|
-
health: status.endpoints.health,
|
|
367
|
-
status: status.endpoints.status,
|
|
368
|
-
handle: status.handle,
|
|
369
|
-
ready: status.ready
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
function forgeSdkVersion() {
|
|
373
|
-
return PACKAGE_VERSION;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// src/mount.ts
|
|
377
|
-
var DEFAULT_NETWORK = "eip155:8453";
|
|
378
|
-
var DEFAULT_FACILITATOR_URL = "https://facilitator.xpay.sh";
|
|
379
|
-
var DEFAULT_GATEWAY_URL = "https://gateway.clawca.sh";
|
|
380
|
-
function asNetwork(value) {
|
|
381
|
-
return value ?? DEFAULT_NETWORK;
|
|
382
|
-
}
|
|
383
|
-
function createFacilitatorClient(options) {
|
|
384
|
-
return new import_server.HTTPFacilitatorClient({
|
|
385
|
-
url: options.facilitatorUrl ?? DEFAULT_FACILITATOR_URL,
|
|
386
|
-
createAuthHeaders: options.createAuthHeaders
|
|
387
|
-
});
|
|
388
|
-
}
|
|
108
|
+
// src/validate.ts
|
|
389
109
|
function validateInput(body, schema) {
|
|
390
110
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
391
111
|
return { ok: false, error: "Request body must be a JSON object" };
|
|
@@ -431,306 +151,353 @@ function checkType(key, value, type) {
|
|
|
431
151
|
return null;
|
|
432
152
|
}
|
|
433
153
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
154
|
+
|
|
155
|
+
// src/worker.ts
|
|
156
|
+
var DEFAULT_GATEWAY_URL = "https://gateway.clawca.sh";
|
|
157
|
+
var DEFAULT_WAIT_MS = 2e4;
|
|
158
|
+
var BACKOFF_STEPS_MS = [1e3, 2e3, 5e3, 1e4, 3e4];
|
|
159
|
+
function findService(services, action) {
|
|
160
|
+
const key = toKebabCase(action.trim());
|
|
161
|
+
return services.find((s) => s.path === key || toKebabCase(s.name) === key) ?? null;
|
|
162
|
+
}
|
|
163
|
+
async function postJson(url, apiKey, body, signal) {
|
|
164
|
+
const res = await fetch(url, {
|
|
165
|
+
method: "POST",
|
|
166
|
+
headers: {
|
|
167
|
+
"content-type": "application/json",
|
|
168
|
+
accept: "application/json",
|
|
169
|
+
authorization: `Bearer ${apiKey}`
|
|
170
|
+
},
|
|
171
|
+
body: JSON.stringify(body),
|
|
172
|
+
signal
|
|
173
|
+
});
|
|
174
|
+
const text = await res.text();
|
|
175
|
+
let json2 = {};
|
|
176
|
+
try {
|
|
177
|
+
json2 = text ? JSON.parse(text) : {};
|
|
178
|
+
} catch {
|
|
179
|
+
json2 = { raw: text };
|
|
180
|
+
}
|
|
181
|
+
return { status: res.status, json: json2 };
|
|
437
182
|
}
|
|
438
|
-
function
|
|
439
|
-
const
|
|
440
|
-
if (!
|
|
441
|
-
|
|
442
|
-
"
|
|
183
|
+
function forgeWorker(options) {
|
|
184
|
+
const handle = (options.handle ?? process.env.FORGE_HANDLE ?? "").trim().toLowerCase();
|
|
185
|
+
if (!handle) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
"forgeWorker: handle is required (option or FORGE_HANDLE env)"
|
|
443
188
|
);
|
|
444
|
-
return;
|
|
445
189
|
}
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
190
|
+
const apiKey = getForgeApiKey(options.apiKey);
|
|
191
|
+
if (!apiKey) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
"forgeWorker: FORGE_API_KEY is required (per-project key from the Forge dashboard, min 16 chars)"
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
if (!options.services?.length) {
|
|
197
|
+
throw new Error("forgeWorker: services must be a non-empty array");
|
|
198
|
+
}
|
|
199
|
+
const gatewayUrl = (options.gatewayUrl?.trim() || process.env.GATEWAY_URL?.trim() || DEFAULT_GATEWAY_URL).replace(/\/$/, "");
|
|
200
|
+
const workerId = options.workerId?.trim() || `${import_node_os.default.hostname()}-${process.pid}`;
|
|
201
|
+
const waitMs = options.waitMs ?? DEFAULT_WAIT_MS;
|
|
202
|
+
let running = true;
|
|
203
|
+
const controller = new AbortController();
|
|
204
|
+
let wakeSleeper = null;
|
|
205
|
+
const reportError = (err) => {
|
|
206
|
+
if (options.onError) options.onError(err);
|
|
207
|
+
else console.warn(`[forge-worker] ${err.message}`);
|
|
208
|
+
};
|
|
209
|
+
const sleep = (ms) => new Promise((resolve) => {
|
|
210
|
+
const timer = setTimeout(() => {
|
|
211
|
+
wakeSleeper = null;
|
|
212
|
+
resolve();
|
|
213
|
+
}, ms);
|
|
214
|
+
wakeSleeper = () => {
|
|
215
|
+
clearTimeout(timer);
|
|
216
|
+
wakeSleeper = null;
|
|
217
|
+
resolve();
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
async function runJob(job) {
|
|
221
|
+
const resultUrl = `${gatewayUrl}/jobs/${encodeURIComponent(job.id)}/result`;
|
|
222
|
+
const service = findService(options.services, job.action);
|
|
223
|
+
if (!service) {
|
|
224
|
+
await postJson(
|
|
225
|
+
resultUrl,
|
|
226
|
+
apiKey,
|
|
227
|
+
{ ok: false, error: `no service registered for action ${job.action}` },
|
|
228
|
+
controller.signal
|
|
229
|
+
);
|
|
230
|
+
reportError(new Error(`job ${job.id}: unknown action ${job.action}`));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
const validated = validateInput(job.input, service.input);
|
|
235
|
+
if (!validated.ok) {
|
|
236
|
+
await postJson(
|
|
237
|
+
resultUrl,
|
|
238
|
+
apiKey,
|
|
239
|
+
{ ok: false, error: validated.error },
|
|
240
|
+
controller.signal
|
|
241
|
+
);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const output = await service.execute(validated.value, createContext());
|
|
245
|
+
const res = await postJson(
|
|
246
|
+
resultUrl,
|
|
247
|
+
apiKey,
|
|
248
|
+
{ ok: true, output },
|
|
249
|
+
controller.signal
|
|
250
|
+
);
|
|
251
|
+
if (res.status >= 400) {
|
|
252
|
+
reportError(
|
|
253
|
+
new Error(
|
|
254
|
+
`job ${job.id}: result rejected (${res.status}): ${JSON.stringify(res.json).slice(0, 300)}`
|
|
255
|
+
)
|
|
256
|
+
);
|
|
257
|
+
} else {
|
|
258
|
+
console.log(`[forge-worker] completed job ${job.id} (${job.action})`);
|
|
468
259
|
}
|
|
260
|
+
} catch (err) {
|
|
261
|
+
const message = err instanceof Error ? err.message : "execute failed";
|
|
262
|
+
try {
|
|
263
|
+
await postJson(
|
|
264
|
+
resultUrl,
|
|
265
|
+
apiKey,
|
|
266
|
+
{ ok: false, error: message },
|
|
267
|
+
controller.signal
|
|
268
|
+
);
|
|
269
|
+
} catch {
|
|
270
|
+
}
|
|
271
|
+
reportError(new Error(`job ${job.id} failed: ${message}`));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async function loop() {
|
|
275
|
+
console.log(
|
|
276
|
+
`[forge-worker] ${handle} polling ${gatewayUrl}/jobs/claim as ${workerId} (${options.services.length} action(s))`
|
|
469
277
|
);
|
|
278
|
+
let backoffIndex = 0;
|
|
279
|
+
while (running) {
|
|
280
|
+
try {
|
|
281
|
+
const { status, json: json2 } = await postJson(
|
|
282
|
+
`${gatewayUrl}/jobs/claim`,
|
|
283
|
+
apiKey,
|
|
284
|
+
{ handle, workerId, waitMs },
|
|
285
|
+
controller.signal
|
|
286
|
+
);
|
|
287
|
+
if (status === 401) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
`claim rejected (401): ${JSON.stringify(json2).slice(0, 300)} \u2014 check FORGE_API_KEY matches the Forge dashboard key`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
if (status === 404 || status === 409) {
|
|
293
|
+
const why = status === 404 ? `handle "${handle}" is not registered yet \u2014 press Go live on the Forge dashboard` : `merchant is not in worker mode yet \u2014 press Go live again`;
|
|
294
|
+
console.warn(
|
|
295
|
+
`[forge-worker] waiting: ${why} (${JSON.stringify(json2).slice(0, 160)})`
|
|
296
|
+
);
|
|
297
|
+
throw Object.assign(new Error(`claim waiting (${status})`), {
|
|
298
|
+
transient: true
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
if (status >= 400) {
|
|
302
|
+
throw Object.assign(
|
|
303
|
+
new Error(`claim failed (${status})`),
|
|
304
|
+
{ transient: true }
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
backoffIndex = 0;
|
|
308
|
+
const job = json2.job;
|
|
309
|
+
if (job) {
|
|
310
|
+
await runJob(job);
|
|
311
|
+
}
|
|
312
|
+
} catch (err) {
|
|
313
|
+
if (!running) break;
|
|
314
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
315
|
+
if (error.name === "AbortError") break;
|
|
316
|
+
reportError(error);
|
|
317
|
+
const transient = error.transient === true || !/claim rejected \(401\)/.test(error.message);
|
|
318
|
+
if (!transient) {
|
|
319
|
+
console.error(
|
|
320
|
+
"[forge-worker] fatal configuration error \u2014 stopping worker"
|
|
321
|
+
);
|
|
322
|
+
running = false;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
const delay = BACKOFF_STEPS_MS[Math.min(backoffIndex, BACKOFF_STEPS_MS.length - 1)];
|
|
326
|
+
backoffIndex += 1;
|
|
327
|
+
await sleep(delay);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
console.log("[forge-worker] stopped");
|
|
470
331
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
332
|
+
void loop();
|
|
333
|
+
return {
|
|
334
|
+
stop() {
|
|
335
|
+
running = false;
|
|
336
|
+
controller.abort();
|
|
337
|
+
wakeSleeper?.();
|
|
338
|
+
},
|
|
339
|
+
get running() {
|
|
340
|
+
return running;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
475
343
|
}
|
|
476
|
-
|
|
477
|
-
|
|
344
|
+
|
|
345
|
+
// src/status.ts
|
|
346
|
+
var PACKAGE_VERSION = "0.2.0";
|
|
347
|
+
function buildForgeSurfaceStatus(opts) {
|
|
348
|
+
const fulfillPath = opts.fulfillPath.replace(/\/$/, "") || "/forge/fulfill";
|
|
349
|
+
const ready = opts.fulfillMounted && opts.actions.length > 0;
|
|
350
|
+
return {
|
|
351
|
+
ok: true,
|
|
352
|
+
forge: true,
|
|
353
|
+
version: PACKAGE_VERSION,
|
|
478
354
|
handle: opts.handle,
|
|
479
|
-
publicBaseUrl: opts.
|
|
355
|
+
publicBaseUrl: opts.publicBaseUrl,
|
|
480
356
|
gatewayUrl: opts.gatewayUrl,
|
|
481
357
|
fulfillMounted: opts.fulfillMounted,
|
|
482
|
-
fulfillPath
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
res.json(buildWellKnownDocument(getStatus()));
|
|
358
|
+
fulfillPath,
|
|
359
|
+
actions: opts.actions,
|
|
360
|
+
ready,
|
|
361
|
+
endpoints: {
|
|
362
|
+
health: "/forge/health",
|
|
363
|
+
status: "/forge/status",
|
|
364
|
+
wellKnown: "/.well-known/clawcash.json",
|
|
365
|
+
fulfill: fulfillPath
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function buildWellKnownDocument(status) {
|
|
370
|
+
return {
|
|
371
|
+
forge: true,
|
|
372
|
+
version: status.version,
|
|
373
|
+
health: status.endpoints.health,
|
|
374
|
+
status: status.endpoints.status,
|
|
375
|
+
handle: status.handle,
|
|
376
|
+
ready: status.ready
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function forgeSdkVersion() {
|
|
380
|
+
return PACKAGE_VERSION;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/handler.ts
|
|
384
|
+
function json(body, status = 200) {
|
|
385
|
+
return new Response(JSON.stringify(body), {
|
|
386
|
+
status,
|
|
387
|
+
headers: { "content-type": "application/json" }
|
|
513
388
|
});
|
|
514
|
-
console.log(
|
|
515
|
-
"[forge] liveliness: GET /forge/health, GET /forge/status, GET /.well-known/clawcash.json"
|
|
516
|
-
);
|
|
517
389
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
throw new Error(
|
|
529
|
-
"mountAgentServices: resolved payTo is the zero address. Re-run Forge to provision a wallet."
|
|
530
|
-
);
|
|
531
|
-
}
|
|
390
|
+
function isAuthorized(request, apiKey) {
|
|
391
|
+
if (!apiKey) return false;
|
|
392
|
+
const header = request.headers.get("authorization") ?? "";
|
|
393
|
+
const bearer = header.toLowerCase().startsWith("bearer ") ? header.slice(7).trim() : "";
|
|
394
|
+
const alt = request.headers.get("x-forge-api-key")?.trim() || request.headers.get("x-clawcash-gateway")?.trim() || "";
|
|
395
|
+
return bearer === apiKey || alt === apiKey;
|
|
396
|
+
}
|
|
397
|
+
function createForgeRequestMatcher(services, options = {}) {
|
|
398
|
+
if (!services?.length) {
|
|
399
|
+
throw new Error("createForgeHandler: services must be a non-empty array");
|
|
532
400
|
}
|
|
533
|
-
const basePath = (options.basePath ?? "/agent").replace(/\/$/, "") || "/agent";
|
|
534
401
|
const fulfillPath = (options.fulfillPath ?? "/forge/fulfill").replace(/\/$/, "") || "/forge/fulfill";
|
|
535
|
-
const
|
|
402
|
+
const handle = options.handle?.trim().toLowerCase() || process.env.FORGE_HANDLE?.trim().toLowerCase() || null;
|
|
403
|
+
const gatewayUrl = options.gatewayUrl?.trim() || process.env.GATEWAY_URL?.trim() || "https://gateway.clawca.sh";
|
|
536
404
|
const context = createContext();
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
405
|
+
const getStatus = () => {
|
|
406
|
+
const apiKey = getForgeApiKey(options.apiKey);
|
|
407
|
+
return buildForgeSurfaceStatus({
|
|
408
|
+
handle,
|
|
409
|
+
publicBaseUrl: options.publicBaseUrl ?? null,
|
|
410
|
+
gatewayUrl,
|
|
411
|
+
fulfillMounted: Boolean(apiKey),
|
|
412
|
+
fulfillPath,
|
|
413
|
+
actions: services.map((s) => ({
|
|
414
|
+
name: s.name,
|
|
415
|
+
path: s.path,
|
|
416
|
+
price: s.payment.amount
|
|
417
|
+
}))
|
|
418
|
+
});
|
|
419
|
+
};
|
|
420
|
+
return async (request) => {
|
|
421
|
+
const url = new URL(request.url);
|
|
422
|
+
const pathname = url.pathname.replace(/\/$/, "") || "/";
|
|
423
|
+
if (request.method === "GET") {
|
|
424
|
+
if (pathname === "/forge/health") {
|
|
425
|
+
const status = getStatus();
|
|
426
|
+
return json({
|
|
427
|
+
ok: true,
|
|
428
|
+
forge: true,
|
|
429
|
+
ready: status.ready,
|
|
430
|
+
handle: status.handle,
|
|
431
|
+
version: status.version,
|
|
432
|
+
actions: status.actions.map((a) => a.name)
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
if (pathname === "/forge/status") {
|
|
436
|
+
const status = getStatus();
|
|
437
|
+
return json(status, status.ready ? 200 : 503);
|
|
438
|
+
}
|
|
439
|
+
if (pathname === "/.well-known/clawcash.json") {
|
|
440
|
+
return json(buildWellKnownDocument(getStatus()));
|
|
441
|
+
}
|
|
442
|
+
return null;
|
|
553
443
|
}
|
|
554
|
-
|
|
444
|
+
if (request.method === "POST" && pathname.startsWith(`${fulfillPath}/`)) {
|
|
445
|
+
const actionPath = pathname.slice(fulfillPath.length + 1);
|
|
446
|
+
const service = services.find((s) => s.path === actionPath) ?? null;
|
|
447
|
+
if (!service) {
|
|
448
|
+
return json({ error: `Unknown action ${actionPath}` }, 404);
|
|
449
|
+
}
|
|
450
|
+
const apiKey = getForgeApiKey(options.apiKey);
|
|
451
|
+
if (!isAuthorized(request, apiKey)) {
|
|
452
|
+
return json(
|
|
453
|
+
{
|
|
454
|
+
error: "Unauthorized \u2014 Forge API key required (FORGE_API_KEY from Forge dashboard)"
|
|
455
|
+
},
|
|
456
|
+
401
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
let body;
|
|
460
|
+
try {
|
|
461
|
+
body = await request.json();
|
|
462
|
+
} catch {
|
|
463
|
+
return json({ error: "Request body must be JSON" }, 400);
|
|
464
|
+
}
|
|
465
|
+
const validated = validateInput(body, service.input);
|
|
466
|
+
if (!validated.ok) {
|
|
467
|
+
return json({ error: validated.error }, 400);
|
|
468
|
+
}
|
|
555
469
|
try {
|
|
556
|
-
const validated = validateInput(req.body, service.input);
|
|
557
|
-
if (!validated.ok) {
|
|
558
|
-
res.status(400).json({ error: validated.error });
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
470
|
const output = await service.execute(validated.value, context);
|
|
562
|
-
|
|
471
|
+
return json(output);
|
|
563
472
|
} catch (err) {
|
|
564
473
|
const message = err instanceof Error ? err.message : "Internal error";
|
|
565
|
-
console.error(`[forge] ${service.name} failed:`, err);
|
|
566
|
-
|
|
474
|
+
console.error(`[forge] fulfill ${service.name} failed:`, err);
|
|
475
|
+
return json({ error: message }, 500);
|
|
567
476
|
}
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
if (!options.skipPayment && Object.keys(routes).length > 0) {
|
|
571
|
-
const facilitator = createFacilitatorClient(options);
|
|
572
|
-
const networks = new Set(
|
|
573
|
-
services.map((s) => asNetwork(s.payment.network))
|
|
574
|
-
);
|
|
575
|
-
const resourceServer = new import_express2.x402ResourceServer(facilitator);
|
|
576
|
-
for (const network of networks) {
|
|
577
|
-
resourceServer.register(network, new import_server2.ExactEvmScheme());
|
|
578
477
|
}
|
|
579
|
-
|
|
580
|
-
}
|
|
581
|
-
app.use(basePath, router);
|
|
582
|
-
const fulfillMounted = Boolean(
|
|
583
|
-
getForgeApiKey(options.gateway?.registerSecret)
|
|
584
|
-
);
|
|
585
|
-
mountFulfillmentRouter(app, services, options);
|
|
586
|
-
const gatewayUrl = options.gateway?.url?.trim() || process.env.GATEWAY_URL?.trim() || DEFAULT_GATEWAY_URL;
|
|
587
|
-
const handle = options.gateway?.handle?.trim() || process.env.FORGE_HANDLE?.trim() || null;
|
|
588
|
-
const registerSecret = options.gateway?.registerSecret?.trim() || process.env.FORGE_API_KEY?.trim() || process.env.GATEWAY_REGISTER_SECRET?.trim();
|
|
589
|
-
const sendHeartbeat = (baseUrl) => {
|
|
590
|
-
if (!handle || !registerSecret) return;
|
|
591
|
-
void heartbeatGateway({
|
|
592
|
-
gatewayUrl,
|
|
593
|
-
handle,
|
|
594
|
-
baseUrl,
|
|
595
|
-
registerSecret
|
|
596
|
-
}).then(() => {
|
|
597
|
-
recordHeartbeatResult({ ok: true });
|
|
598
|
-
}).catch((err) => {
|
|
599
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
600
|
-
recordHeartbeatResult({ ok: false, error: message });
|
|
601
|
-
console.warn("[forge] gateway heartbeat skipped:", err);
|
|
602
|
-
});
|
|
478
|
+
return null;
|
|
603
479
|
};
|
|
604
|
-
const resolver = new PublicBaseUrlResolver({
|
|
605
|
-
initial: resolveBaseUrlFromEnv(options.gateway?.publicBaseUrl),
|
|
606
|
-
// Base URL learned late (inferred from the first probe): heartbeat now.
|
|
607
|
-
onResolved: sendHeartbeat
|
|
608
|
-
});
|
|
609
|
-
mountForgeStatusRoutes(app, services, options, {
|
|
610
|
-
fulfillMounted,
|
|
611
|
-
fulfillPath,
|
|
612
|
-
agentBasePath: basePath,
|
|
613
|
-
handle,
|
|
614
|
-
resolver,
|
|
615
|
-
gatewayUrl
|
|
616
|
-
});
|
|
617
|
-
const bootBaseUrl = resolver.get();
|
|
618
|
-
if (bootBaseUrl) {
|
|
619
|
-
sendHeartbeat(bootBaseUrl);
|
|
620
|
-
}
|
|
621
|
-
return router;
|
|
622
|
-
}
|
|
623
|
-
function createForgeRouter(services, options) {
|
|
624
|
-
return (app) => mountAgentServices(app, services, options);
|
|
625
480
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
const base = meta.baseUrl.replace(/\/$/, "");
|
|
630
|
-
const lines = [
|
|
631
|
-
`---`,
|
|
632
|
-
`name: ${meta.name}`,
|
|
633
|
-
`description: ${meta.description}`,
|
|
634
|
-
`---`,
|
|
635
|
-
``,
|
|
636
|
-
`# ${meta.name}`,
|
|
637
|
-
``,
|
|
638
|
-
meta.description,
|
|
639
|
-
``
|
|
640
|
-
];
|
|
641
|
-
if (meta.homepage) {
|
|
642
|
-
lines.push(`Homepage: ${meta.homepage}`, ``);
|
|
643
|
-
}
|
|
644
|
-
lines.push(
|
|
645
|
-
`## How to use`,
|
|
646
|
-
``,
|
|
647
|
-
`1. Read this skill.`,
|
|
648
|
-
`2. Call the agent endpoint with a JSON body matching the input schema.`,
|
|
649
|
-
`3. If you receive HTTP 402, pay via x402 (USDC) and retry with the payment header.`,
|
|
650
|
-
`4. Use the returned output fields.`,
|
|
651
|
-
``,
|
|
652
|
-
`## Services`,
|
|
653
|
-
``
|
|
654
|
-
);
|
|
655
|
-
for (const service of services) {
|
|
656
|
-
const path = `${base}/agent/${service.path}`;
|
|
657
|
-
const price = service.payment.amount.startsWith("$") ? service.payment.amount : `$${service.payment.amount}`;
|
|
658
|
-
lines.push(
|
|
659
|
-
`### \`${service.name}\``,
|
|
660
|
-
``,
|
|
661
|
-
service.description,
|
|
662
|
-
``,
|
|
663
|
-
`- **Method:** \`POST\``,
|
|
664
|
-
`- **URL:** \`${path}\``,
|
|
665
|
-
`- **Price:** ${price} ${service.payment.currency} via ${service.payment.protocols.join(", ")}`,
|
|
666
|
-
``,
|
|
667
|
-
`**Input**`,
|
|
668
|
-
``,
|
|
669
|
-
"```json",
|
|
670
|
-
JSON.stringify(
|
|
671
|
-
Object.fromEntries(
|
|
672
|
-
Object.entries(service.input).map(([k, t]) => [k, `<${t}>`])
|
|
673
|
-
),
|
|
674
|
-
null,
|
|
675
|
-
2
|
|
676
|
-
),
|
|
677
|
-
"```",
|
|
678
|
-
``,
|
|
679
|
-
`**Output**`,
|
|
680
|
-
``,
|
|
681
|
-
"```json",
|
|
682
|
-
JSON.stringify(
|
|
683
|
-
Object.fromEntries(
|
|
684
|
-
Object.entries(service.output).map(([k, t]) => [k, `<${t}>`])
|
|
685
|
-
),
|
|
686
|
-
null,
|
|
687
|
-
2
|
|
688
|
-
),
|
|
689
|
-
"```",
|
|
690
|
-
``,
|
|
691
|
-
`**Example**`,
|
|
692
|
-
``,
|
|
693
|
-
"```bash",
|
|
694
|
-
`curl -X POST ${path} \\`,
|
|
695
|
-
` -H "Content-Type: application/json" \\`,
|
|
696
|
-
` -d '${JSON.stringify(
|
|
697
|
-
Object.fromEntries(
|
|
698
|
-
Object.entries(service.input).map(([k, t]) => [
|
|
699
|
-
k,
|
|
700
|
-
t === "url" ? "https://example.com/image.png" : `example-${k}`
|
|
701
|
-
])
|
|
702
|
-
)
|
|
703
|
-
)}'`,
|
|
704
|
-
"```",
|
|
705
|
-
``
|
|
706
|
-
);
|
|
707
|
-
}
|
|
708
|
-
return lines.join("\n");
|
|
481
|
+
function createForgeHandler(services, options = {}) {
|
|
482
|
+
const match = createForgeRequestMatcher(services, options);
|
|
483
|
+
return async (request) => await match(request) ?? json({ error: "Not found" }, 404);
|
|
709
484
|
}
|
|
710
485
|
// Annotate the CommonJS export names for ESM import in node:
|
|
711
486
|
0 && (module.exports = {
|
|
712
487
|
FORGE_GATEWAY_USER_ID,
|
|
713
|
-
PublicBaseUrlResolver,
|
|
714
488
|
buildForgeSurfaceStatus,
|
|
715
489
|
buildWellKnownDocument,
|
|
490
|
+
checkType,
|
|
716
491
|
createContext,
|
|
717
|
-
|
|
492
|
+
createForgeHandler,
|
|
493
|
+
createForgeRequestMatcher,
|
|
718
494
|
defineAgentService,
|
|
719
495
|
forgeGatewayLoopbackHeaders,
|
|
720
496
|
forgeSdkVersion,
|
|
721
|
-
|
|
497
|
+
forgeWorker,
|
|
722
498
|
getForgeApiKey,
|
|
723
|
-
getForgeGatewaySecret,
|
|
724
|
-
getLastHeartbeat,
|
|
725
|
-
heartbeatGateway,
|
|
726
|
-
isForgeGatewayRequest,
|
|
727
|
-
isZeroAddress,
|
|
728
|
-
mountAgentServices,
|
|
729
|
-
recordHeartbeatResult,
|
|
730
|
-
requireForgeGateway,
|
|
731
|
-
resolveBaseUrlFromEnv,
|
|
732
|
-
resolvePayTo,
|
|
733
499
|
toKebabCase,
|
|
500
|
+
validateInput,
|
|
734
501
|
waitUntil
|
|
735
502
|
});
|
|
736
503
|
//# sourceMappingURL=index.cjs.map
|