@haven_ai/signer 0.5.0-alpha.1 → 0.7.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -6
- package/dist/cli.cjs +481 -22
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +482 -23
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +482 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -4
- package/dist/index.d.ts +39 -4
- package/dist/index.js +483 -24
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { randomUUID, createHash } from 'crypto';
|
|
3
3
|
import { readFile, mkdir, writeFile, stat, lstat, rename, appendFile, chmod } from 'fs/promises';
|
|
4
4
|
import { resolve, dirname, join } from 'path';
|
|
5
|
-
import { createNextStepBuilder, havenClientIdentity, isSupportedNodeVersion, unsupportedNodeVersionMessage, addressFromKey, HavenSigningError, buildSweepTypedData, selectStandardPaymentOption, HavenApiError, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, isSettlementChildTypedData, chainIdForNetwork, assertOwnSettlementChild, isPackedUserOperationTypedData, assertUserOpTypedDataBinding, assertBoundDirectPaymentUserOp as assertBoundDirectPaymentUserOp$1, assertFundingLegPaysDelegate, HavenTypedDataRefusedError,
|
|
5
|
+
import { createNextStepBuilder, havenClientIdentity, isSupportedNodeVersion, unsupportedNodeVersionMessage, addressFromKey, HavenSigningError, buildSweepTypedData, selectStandardPaymentOption, HavenApiError, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, isSettlementChildTypedData, chainIdForNetwork, assertOwnSettlementChild, isPackedUserOperationTypedData, assertUserOpTypedDataBinding, assertBoundDirectPaymentUserOp as assertBoundDirectPaymentUserOp$1, assertFundingLegPaysDelegate, HavenTypedDataRefusedError, connectorUpgradeCommand, buildSweepAuthorizationMessage, verifySignature, x402AuthorizationAmount, SignerRefusalCode, SIGNER_UPDATE_FALLBACK, HavenUnsupportedSignerVersionError, buildX402ExpectedMessage, TASK_SIGN_CONTEXT_VERSION, DIRECT_SIGN_CONTEXT_VERSION, HavenError, AgentPaymentFailureCode, AgentPaymentNextAction, HavenUserOpBindingError, connectorRerunCommand, assertOwnTaskChild, assertOwnTaskBudgetCloseUserOp, assertOwnSubBudgetChild, assertOwnSubBudgetCloseUserOp, readClientUpdate, HAVEN_CLIENT_HEADER } from '@haven_ai/sdk/edge';
|
|
6
6
|
import { recoverTypedDataAddress, hashTypedData, hashMessage } from 'viem';
|
|
7
7
|
import { z as z$1 } from 'zod/v3';
|
|
8
8
|
import { z } from 'zod';
|
|
@@ -212,7 +212,7 @@ var HavenSignContextError = class extends HavenSigningError {
|
|
|
212
212
|
/**
|
|
213
213
|
* #3303: on a 426 `client_outdated` refusal, the backend's update hint —
|
|
214
214
|
* this signer is below the minimum the deployment set, and
|
|
215
|
-
* `client_update.upgrade_command`
|
|
215
|
+
* `client_update.upgrade_command` starts the update (#3412: for the signer, the doctor, then its repair line).
|
|
216
216
|
*/
|
|
217
217
|
client_update;
|
|
218
218
|
constructor(message, code, refusal, paymentId, flow = "x402") {
|
|
@@ -229,7 +229,7 @@ var HavenSignContextError = class extends HavenSigningError {
|
|
|
229
229
|
step = signerRefusalStep({
|
|
230
230
|
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
231
231
|
nextTool: null,
|
|
232
|
-
nextToolOmittedReason: `this signer is below the minimum version Haven accepts \u2014 tell the user to run ${refusal.clientUpdate?.upgrade_command ??
|
|
232
|
+
nextToolOmittedReason: `this signer is below the minimum version Haven accepts \u2014 tell the user to run ${refusal.clientUpdate?.upgrade_command ?? connectorUpgradeCommand()} and the repair line it prints, restart the agent runtime, then retry the same haven_sign / haven_sign_x402 call`
|
|
233
233
|
});
|
|
234
234
|
} else if (flow === "direct" && refusal?.httpStatus === 404) {
|
|
235
235
|
this.fallback = "typed_data_b64";
|
|
@@ -254,6 +254,13 @@ var HavenSignContextError = class extends HavenSigningError {
|
|
|
254
254
|
nextTool: null,
|
|
255
255
|
nextToolOmittedReason: "re-run the hosted quote tool you called with the same idempotency_key; which one depends on the flow"
|
|
256
256
|
});
|
|
257
|
+
} else if (flow === "task") {
|
|
258
|
+
this.next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
259
|
+
step = signerRefusalStep({
|
|
260
|
+
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
261
|
+
nextTool: null,
|
|
262
|
+
nextToolOmittedReason: `re-check this task budget's status (id ${paymentId}) with the hosted Haven server before retrying`
|
|
263
|
+
});
|
|
257
264
|
} else {
|
|
258
265
|
this.next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
259
266
|
step = signerRefusalStep({
|
|
@@ -262,6 +269,13 @@ var HavenSignContextError = class extends HavenSigningError {
|
|
|
262
269
|
nextArguments: { payment_id: paymentId }
|
|
263
270
|
});
|
|
264
271
|
}
|
|
272
|
+
} else if (flow === "task") {
|
|
273
|
+
this.next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
274
|
+
step = signerRefusalStep({
|
|
275
|
+
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
276
|
+
nextTool: null,
|
|
277
|
+
nextToolOmittedReason: "retry the same haven_sign call with the same task_budget_id"
|
|
278
|
+
});
|
|
265
279
|
} else {
|
|
266
280
|
this.fallback = "typed_data_b64";
|
|
267
281
|
this.next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
@@ -430,8 +444,234 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
430
444
|
clientUpdate: readClientUpdate(body.client_update)
|
|
431
445
|
};
|
|
432
446
|
}
|
|
447
|
+
var SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS = [TASK_SIGN_CONTEXT_VERSION];
|
|
448
|
+
var SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS = [1];
|
|
449
|
+
async function fetchSubBudgetSignContext(identity, subBudgetId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
|
|
450
|
+
let response;
|
|
451
|
+
try {
|
|
452
|
+
response = await fetchImpl(
|
|
453
|
+
`${identity.apiUrl}/sub-budgets/${encodeURIComponent(subBudgetId)}/sign-context`,
|
|
454
|
+
{
|
|
455
|
+
headers: signContextHeaders(identity, clientIdentity),
|
|
456
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
457
|
+
}
|
|
458
|
+
);
|
|
459
|
+
} catch (err) {
|
|
460
|
+
const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
|
|
461
|
+
throw new HavenSignContextError(
|
|
462
|
+
timedOut ? `Haven did not answer the sub-budget signing-context fetch for ${subBudgetId} within ${timeoutMs} ms. Retry.` : `Could not reach Haven to fetch the sub-budget signing context for ${subBudgetId}: ${err instanceof Error ? err.message : String(err)}.`,
|
|
463
|
+
timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE",
|
|
464
|
+
void 0,
|
|
465
|
+
subBudgetId,
|
|
466
|
+
"task"
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
let body;
|
|
470
|
+
try {
|
|
471
|
+
body = await response.json();
|
|
472
|
+
} catch (err) {
|
|
473
|
+
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
474
|
+
throw new HavenSignContextError(
|
|
475
|
+
`Haven did not finish sending the sub-budget signing context for ${subBudgetId} within ${timeoutMs} ms. Retry.`,
|
|
476
|
+
"SIGN_CONTEXT_TIMEOUT",
|
|
477
|
+
void 0,
|
|
478
|
+
subBudgetId,
|
|
479
|
+
"task"
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
body = {};
|
|
483
|
+
}
|
|
484
|
+
if (!response.ok) {
|
|
485
|
+
const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
|
|
486
|
+
throw new HavenSignContextError(
|
|
487
|
+
`Haven refused the sub-budget signing-context fetch for ${subBudgetId}: ${detail}`,
|
|
488
|
+
"SIGN_CONTEXT_REFUSED",
|
|
489
|
+
refusalOf(response.status, body),
|
|
490
|
+
subBudgetId,
|
|
491
|
+
"task"
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
const purpose = body.purpose;
|
|
495
|
+
const typedData = body.typed_data;
|
|
496
|
+
const version = body.sub_budget_sign_context_version;
|
|
497
|
+
const expected = body.expected;
|
|
498
|
+
const malformed = (detail) => {
|
|
499
|
+
throw new HavenSignContextError(
|
|
500
|
+
`The Haven sub-budget sign-context response for ${subBudgetId} is malformed: ${detail}.`,
|
|
501
|
+
"SIGN_CONTEXT_MALFORMED",
|
|
502
|
+
void 0,
|
|
503
|
+
subBudgetId,
|
|
504
|
+
"task"
|
|
505
|
+
);
|
|
506
|
+
};
|
|
507
|
+
if (typeof version !== "number" || !SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS.includes(version)) {
|
|
508
|
+
throw new HavenSignContextError(
|
|
509
|
+
`The Haven sub-budget sign-context response is version ${String(version)}, which this signer does not support (supported: ${SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS.join(", ")}). Update @haven_ai/signer.`,
|
|
510
|
+
"SIGN_CONTEXT_MALFORMED",
|
|
511
|
+
void 0,
|
|
512
|
+
subBudgetId,
|
|
513
|
+
"task"
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
if (!typedData || typeof typedData !== "object") return malformed("missing typed_data");
|
|
517
|
+
if (!expected || typeof expected !== "object") return malformed("missing expected");
|
|
518
|
+
const e = expected;
|
|
519
|
+
if (purpose === "open") {
|
|
520
|
+
if (typeof e.delegate_account !== "string" || typeof e.chain_id !== "number" || typeof e.token_address !== "string" || typeof e.period_amount_atomic !== "string" || typeof e.period_duration_seconds !== "number" || typeof e.start_date !== "number" || e.recipient_address !== null && typeof e.recipient_address !== "string" || typeof e.expires_at !== "number" || typeof e.parent_delegation_hash !== "string" || typeof e.child_delegate_account !== "string") {
|
|
521
|
+
return malformed("expected is missing a required field for purpose open");
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
purpose: "open",
|
|
525
|
+
subBudgetId: String(body.sub_budget_id ?? subBudgetId),
|
|
526
|
+
typedData,
|
|
527
|
+
expected: {
|
|
528
|
+
delegateAccount: e.delegate_account,
|
|
529
|
+
chainId: e.chain_id,
|
|
530
|
+
tokenAddress: e.token_address,
|
|
531
|
+
periodAmountAtomic: e.period_amount_atomic,
|
|
532
|
+
periodDurationSeconds: e.period_duration_seconds,
|
|
533
|
+
startDate: e.start_date,
|
|
534
|
+
recipientAddress: e.recipient_address ?? null,
|
|
535
|
+
expiresAt: e.expires_at,
|
|
536
|
+
parentDelegationHash: e.parent_delegation_hash,
|
|
537
|
+
childDelegateAccount: e.child_delegate_account
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
if (purpose === "close") {
|
|
542
|
+
const userOpHash = body.user_op_hash;
|
|
543
|
+
if (typeof userOpHash !== "string") return malformed("missing user_op_hash for purpose close");
|
|
544
|
+
if (typeof e.delegate_account !== "string" || typeof e.chain_id !== "number" || typeof e.delegation_hash !== "string") {
|
|
545
|
+
return malformed("expected is missing a required field for purpose close");
|
|
546
|
+
}
|
|
547
|
+
return {
|
|
548
|
+
purpose: "close",
|
|
549
|
+
subBudgetId: String(body.sub_budget_id ?? subBudgetId),
|
|
550
|
+
typedData,
|
|
551
|
+
userOpHash,
|
|
552
|
+
expected: {
|
|
553
|
+
delegateAccount: e.delegate_account,
|
|
554
|
+
chainId: e.chain_id,
|
|
555
|
+
delegationHash: e.delegation_hash
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
return malformed(`unknown purpose '${String(purpose)}'`);
|
|
560
|
+
}
|
|
561
|
+
async function fetchTaskBudgetSignContext(identity, taskBudgetId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
|
|
562
|
+
let response;
|
|
563
|
+
try {
|
|
564
|
+
response = await fetchImpl(
|
|
565
|
+
`${identity.apiUrl}/task-budgets/${encodeURIComponent(taskBudgetId)}/sign-context`,
|
|
566
|
+
{
|
|
567
|
+
headers: signContextHeaders(identity, clientIdentity),
|
|
568
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
569
|
+
}
|
|
570
|
+
);
|
|
571
|
+
} catch (err) {
|
|
572
|
+
const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
|
|
573
|
+
throw new HavenSignContextError(
|
|
574
|
+
timedOut ? `Haven did not answer the task-budget signing-context fetch for ${taskBudgetId} within ${timeoutMs} ms. Retry.` : `Could not reach Haven to fetch the task-budget signing context for ${taskBudgetId}: ${err instanceof Error ? err.message : String(err)}.`,
|
|
575
|
+
timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE",
|
|
576
|
+
void 0,
|
|
577
|
+
taskBudgetId,
|
|
578
|
+
"task"
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
let body;
|
|
582
|
+
try {
|
|
583
|
+
body = await response.json();
|
|
584
|
+
} catch (err) {
|
|
585
|
+
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
586
|
+
throw new HavenSignContextError(
|
|
587
|
+
`Haven did not finish sending the task-budget signing context for ${taskBudgetId} within ${timeoutMs} ms. Retry.`,
|
|
588
|
+
"SIGN_CONTEXT_TIMEOUT",
|
|
589
|
+
void 0,
|
|
590
|
+
taskBudgetId,
|
|
591
|
+
"task"
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
body = {};
|
|
595
|
+
}
|
|
596
|
+
if (!response.ok) {
|
|
597
|
+
const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
|
|
598
|
+
throw new HavenSignContextError(
|
|
599
|
+
`Haven refused the task-budget signing-context fetch for ${taskBudgetId}: ${detail}`,
|
|
600
|
+
"SIGN_CONTEXT_REFUSED",
|
|
601
|
+
refusalOf(response.status, body),
|
|
602
|
+
taskBudgetId,
|
|
603
|
+
"task"
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
const purpose = body.purpose;
|
|
607
|
+
const typedData = body.typed_data;
|
|
608
|
+
const version = body.task_sign_context_version;
|
|
609
|
+
const expected = body.expected;
|
|
610
|
+
const malformed = (detail) => {
|
|
611
|
+
throw new HavenSignContextError(
|
|
612
|
+
`The Haven task-budget sign-context response for ${taskBudgetId} is malformed: ${detail}.`,
|
|
613
|
+
"SIGN_CONTEXT_MALFORMED",
|
|
614
|
+
void 0,
|
|
615
|
+
taskBudgetId,
|
|
616
|
+
"task"
|
|
617
|
+
);
|
|
618
|
+
};
|
|
619
|
+
if (typeof version !== "number" || !SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS.includes(version)) {
|
|
620
|
+
throw new HavenSignContextError(
|
|
621
|
+
`The Haven task-budget sign-context response is version ${String(version)}, which this signer does not support (supported: ${SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS.join(", ")}). Update @haven_ai/signer.`,
|
|
622
|
+
"SIGN_CONTEXT_MALFORMED",
|
|
623
|
+
void 0,
|
|
624
|
+
taskBudgetId,
|
|
625
|
+
"task"
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
if (!typedData || typeof typedData !== "object") return malformed("missing typed_data");
|
|
629
|
+
if (!expected || typeof expected !== "object") return malformed("missing expected");
|
|
630
|
+
const e = expected;
|
|
631
|
+
if (purpose === "open") {
|
|
632
|
+
if (typeof e.delegate_account !== "string" || typeof e.chain_id !== "number" || typeof e.token_address !== "string" || typeof e.max_amount_atomic !== "string" || e.recipient_address !== null && typeof e.recipient_address !== "string" || typeof e.expires_at !== "number" || typeof e.parent_delegation_hash !== "string") {
|
|
633
|
+
return malformed("expected is missing a required field for purpose open");
|
|
634
|
+
}
|
|
635
|
+
return {
|
|
636
|
+
purpose: "open",
|
|
637
|
+
taskBudgetId: String(body.task_budget_id ?? taskBudgetId),
|
|
638
|
+
typedData,
|
|
639
|
+
expected: {
|
|
640
|
+
delegateAccount: e.delegate_account,
|
|
641
|
+
chainId: e.chain_id,
|
|
642
|
+
tokenAddress: e.token_address,
|
|
643
|
+
maxAmountAtomic: e.max_amount_atomic,
|
|
644
|
+
recipientAddress: e.recipient_address ?? null,
|
|
645
|
+
expiresAt: e.expires_at,
|
|
646
|
+
parentDelegationHash: e.parent_delegation_hash
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
if (purpose === "close") {
|
|
651
|
+
const userOpHash = body.user_op_hash;
|
|
652
|
+
if (typeof userOpHash !== "string") return malformed("missing user_op_hash for purpose close");
|
|
653
|
+
if (typeof e.delegate_account !== "string" || typeof e.chain_id !== "number" || typeof e.delegation_hash !== "string") {
|
|
654
|
+
return malformed("expected is missing a required field for purpose close");
|
|
655
|
+
}
|
|
656
|
+
return {
|
|
657
|
+
purpose: "close",
|
|
658
|
+
taskBudgetId: String(body.task_budget_id ?? taskBudgetId),
|
|
659
|
+
typedData,
|
|
660
|
+
userOpHash,
|
|
661
|
+
expected: {
|
|
662
|
+
delegateAccount: e.delegate_account,
|
|
663
|
+
chainId: e.chain_id,
|
|
664
|
+
delegationHash: e.delegation_hash
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
return malformed(`unknown purpose '${String(purpose)}'`);
|
|
669
|
+
}
|
|
433
670
|
|
|
434
671
|
// src/tools.ts
|
|
672
|
+
var SIGNER_NAME = "@haven_ai/signer";
|
|
673
|
+
var SIGNER_VERSION = "0.7.0-alpha.0";
|
|
674
|
+
var UNSUPPORTED_ARGUMENT_MARKER = "HavenSignerUnsupportedArgument:";
|
|
435
675
|
var USEROP_BINDING_MISMATCH = "USEROP_BINDING_MISMATCH";
|
|
436
676
|
var HavenUserOpBindingRefusedError = class extends HavenSigningError {
|
|
437
677
|
next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
@@ -552,7 +792,20 @@ var toolSchemas = {
|
|
|
552
792
|
// Bounded: a realistic redemption payload is ~10KB encoded; 256KB is
|
|
553
793
|
// generous headroom while keeping the offline signer from materializing
|
|
554
794
|
// arbitrarily large caller input.
|
|
555
|
-
typed_data_b64: z$1.string().min(1).max(262144).optional()
|
|
795
|
+
typed_data_b64: z$1.string().min(1).max(262144).optional(),
|
|
796
|
+
// #3329: sign a task budget's own child delegation (purpose `open`) or
|
|
797
|
+
// its early-close UserOp (purpose `close`) — the signer fetches the
|
|
798
|
+
// exact typed data and expectation itself, exactly like payment_id.
|
|
799
|
+
// Mutually exclusive with payment_id / payload_hash (checked in the
|
|
800
|
+
// handler, since Zod's object-level refine on a raw shape used by
|
|
801
|
+
// several call sites is not the shape this schema is composed as).
|
|
802
|
+
task_budget_id: z$1.string().min(1).optional(),
|
|
803
|
+
// #3330: sign a sub-budget's own child delegation or its early-close
|
|
804
|
+
// UserOp — same fetch-verify-sign channel as task_budget_id, against
|
|
805
|
+
// `GET /sub-budgets/:id/sign-context`. Mutually exclusive with
|
|
806
|
+
// payment_id / payload_hash AND with task_budget_id (checked in the
|
|
807
|
+
// handler).
|
|
808
|
+
sub_budget_id: z$1.string().min(1).optional()
|
|
556
809
|
},
|
|
557
810
|
haven_x402_sign_header: {
|
|
558
811
|
// The parsed HTTP 402 PaymentRequired from the merchant. Typed as an object
|
|
@@ -602,7 +855,11 @@ var SIGN_DESCRIPTION = [
|
|
|
602
855
|
"intent against a Haven-signed context \u2014 never an arbitrary typed_data payload, however it is shaped.",
|
|
603
856
|
"Next: call mcp__haven__haven_submit with signature, then pass x402_binding",
|
|
604
857
|
"to mcp__haven-signer__haven_x402_sign_header. A bare payload_hash with no payment_id, typed_data",
|
|
605
|
-
"or x402_expected is REFUSED (BARE_HASH_REFUSED): a hash carries nothing this signer can verify."
|
|
858
|
+
"or x402_expected is REFUSED (BARE_HASH_REFUSED): a hash carries nothing this signer can verify.",
|
|
859
|
+
"TASK BUDGETS (#3329): pass task_budget_id ALONE \u2014 mutually exclusive with payment_id and",
|
|
860
|
+
"payload_hash \u2014 to sign a task budget\u2019s own child delegation (opening it) or its early-close",
|
|
861
|
+
"UserOp; this signer fetches the exact typed data itself. Result then carries next_tool_name",
|
|
862
|
+
"(haven_submit) and next_arguments { task_budget_id, signature } instead of x402_binding."
|
|
606
863
|
].join(" ");
|
|
607
864
|
var X402_SIGN_HEADER_DESCRIPTION = [
|
|
608
865
|
"Build and sign the EIP-3009 merchant payment header for the merchant leg of an x402 payment.",
|
|
@@ -766,9 +1023,172 @@ function createToolHandlers(signer, options = {}) {
|
|
|
766
1023
|
throw err;
|
|
767
1024
|
}
|
|
768
1025
|
}
|
|
1026
|
+
async function signTaskBudget(taskBudgetId) {
|
|
1027
|
+
const identity = await options.signContext?.loadIdentity() ?? null;
|
|
1028
|
+
if (!identity) {
|
|
1029
|
+
throw new HavenSigningError(
|
|
1030
|
+
`task_budget_id signing needs the agent identity (identity.json next to the signer credentials), which this signer could not load. Re-run \`${connectorRerunCommand()}\` to restore it.`
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
const ctx = await fetchTaskBudgetSignContext(
|
|
1034
|
+
identity,
|
|
1035
|
+
taskBudgetId,
|
|
1036
|
+
options.signContext?.fetchImpl,
|
|
1037
|
+
void 0,
|
|
1038
|
+
options.signContext?.clientIdentity
|
|
1039
|
+
);
|
|
1040
|
+
let signature;
|
|
1041
|
+
if (ctx.purpose === "open") {
|
|
1042
|
+
try {
|
|
1043
|
+
assertOwnTaskChild(
|
|
1044
|
+
ctx.typedData,
|
|
1045
|
+
{
|
|
1046
|
+
chainId: ctx.expected.chainId,
|
|
1047
|
+
tokenAddress: ctx.expected.tokenAddress,
|
|
1048
|
+
maxAmountAtomic: ctx.expected.maxAmountAtomic,
|
|
1049
|
+
recipientAddress: ctx.expected.recipientAddress,
|
|
1050
|
+
expiresAt: ctx.expected.expiresAt,
|
|
1051
|
+
parentDelegationHash: ctx.expected.parentDelegationHash
|
|
1052
|
+
},
|
|
1053
|
+
signer.delegateAddress
|
|
1054
|
+
);
|
|
1055
|
+
} catch (err) {
|
|
1056
|
+
if (err instanceof HavenTypedDataRefusedError) throw err;
|
|
1057
|
+
throw new HavenTypedDataRefusedError(err instanceof Error ? err.message : String(err));
|
|
1058
|
+
}
|
|
1059
|
+
signature = await signer.signDelegationTypedData(ctx.typedData);
|
|
1060
|
+
} else {
|
|
1061
|
+
try {
|
|
1062
|
+
assertUserOpTypedDataBinding(ctx.typedData, ctx.userOpHash);
|
|
1063
|
+
} catch (err) {
|
|
1064
|
+
if (err instanceof HavenUserOpBindingError) throw new HavenUserOpBindingRefusedError(err.message);
|
|
1065
|
+
throw err;
|
|
1066
|
+
}
|
|
1067
|
+
try {
|
|
1068
|
+
assertOwnTaskBudgetCloseUserOp(
|
|
1069
|
+
ctx.typedData,
|
|
1070
|
+
{ delegationHash: ctx.expected.delegationHash },
|
|
1071
|
+
signer.delegateAddress
|
|
1072
|
+
);
|
|
1073
|
+
} catch (err) {
|
|
1074
|
+
if (err instanceof HavenTypedDataRefusedError) throw err;
|
|
1075
|
+
throw new HavenTypedDataRefusedError(err instanceof Error ? err.message : String(err));
|
|
1076
|
+
}
|
|
1077
|
+
signature = await signer.signDelegationTypedData(ctx.typedData);
|
|
1078
|
+
}
|
|
1079
|
+
await auditSigning("haven_sign", hashTypedData(ctx.typedData));
|
|
1080
|
+
return {
|
|
1081
|
+
signature,
|
|
1082
|
+
task_budget_id: ctx.taskBudgetId,
|
|
1083
|
+
purpose: ctx.purpose,
|
|
1084
|
+
next_tool_name: "haven_submit",
|
|
1085
|
+
next_tool_server_role: "hosted",
|
|
1086
|
+
next_arguments: { task_budget_id: ctx.taskBudgetId, signature }
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
async function signSubBudget(subBudgetId) {
|
|
1090
|
+
const identity = await options.signContext?.loadIdentity() ?? null;
|
|
1091
|
+
if (!identity) {
|
|
1092
|
+
throw new HavenSigningError(
|
|
1093
|
+
`sub_budget_id signing needs the agent identity (identity.json next to the signer credentials), which this signer could not load. Re-run \`${connectorRerunCommand()}\` to restore it.`
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
const ctx = await fetchSubBudgetSignContext(
|
|
1097
|
+
identity,
|
|
1098
|
+
subBudgetId,
|
|
1099
|
+
options.signContext?.fetchImpl,
|
|
1100
|
+
void 0,
|
|
1101
|
+
options.signContext?.clientIdentity
|
|
1102
|
+
);
|
|
1103
|
+
let signature;
|
|
1104
|
+
if (ctx.purpose === "open") {
|
|
1105
|
+
try {
|
|
1106
|
+
assertOwnSubBudgetChild(
|
|
1107
|
+
ctx.typedData,
|
|
1108
|
+
{
|
|
1109
|
+
chainId: ctx.expected.chainId,
|
|
1110
|
+
tokenAddress: ctx.expected.tokenAddress,
|
|
1111
|
+
periodAmountAtomic: ctx.expected.periodAmountAtomic,
|
|
1112
|
+
periodDurationSeconds: ctx.expected.periodDurationSeconds,
|
|
1113
|
+
startDate: ctx.expected.startDate,
|
|
1114
|
+
recipientAddress: ctx.expected.recipientAddress,
|
|
1115
|
+
expiresAt: ctx.expected.expiresAt,
|
|
1116
|
+
parentDelegationHash: ctx.expected.parentDelegationHash,
|
|
1117
|
+
delegateAccount: ctx.expected.delegateAccount,
|
|
1118
|
+
childDelegateAccount: ctx.expected.childDelegateAccount
|
|
1119
|
+
},
|
|
1120
|
+
signer.delegateAddress
|
|
1121
|
+
);
|
|
1122
|
+
} catch (err) {
|
|
1123
|
+
if (err instanceof HavenTypedDataRefusedError) throw err;
|
|
1124
|
+
throw new HavenTypedDataRefusedError(err instanceof Error ? err.message : String(err));
|
|
1125
|
+
}
|
|
1126
|
+
signature = await signer.signDelegationTypedData(ctx.typedData);
|
|
1127
|
+
} else {
|
|
1128
|
+
try {
|
|
1129
|
+
assertUserOpTypedDataBinding(ctx.typedData, ctx.userOpHash);
|
|
1130
|
+
} catch (err) {
|
|
1131
|
+
if (err instanceof HavenUserOpBindingError) throw new HavenUserOpBindingRefusedError(err.message);
|
|
1132
|
+
throw err;
|
|
1133
|
+
}
|
|
1134
|
+
try {
|
|
1135
|
+
assertOwnSubBudgetCloseUserOp(
|
|
1136
|
+
ctx.typedData,
|
|
1137
|
+
{ delegationHash: ctx.expected.delegationHash },
|
|
1138
|
+
signer.delegateAddress
|
|
1139
|
+
);
|
|
1140
|
+
} catch (err) {
|
|
1141
|
+
if (err instanceof HavenTypedDataRefusedError) throw err;
|
|
1142
|
+
throw new HavenTypedDataRefusedError(err instanceof Error ? err.message : String(err));
|
|
1143
|
+
}
|
|
1144
|
+
signature = await signer.signDelegationTypedData(ctx.typedData);
|
|
1145
|
+
}
|
|
1146
|
+
await auditSigning("haven_sign", hashTypedData(ctx.typedData));
|
|
1147
|
+
return {
|
|
1148
|
+
signature,
|
|
1149
|
+
sub_budget_id: ctx.subBudgetId,
|
|
1150
|
+
purpose: ctx.purpose,
|
|
1151
|
+
next_tool_name: "haven_submit",
|
|
1152
|
+
next_tool_server_role: "hosted",
|
|
1153
|
+
next_arguments: { sub_budget_id: ctx.subBudgetId, signature }
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
769
1156
|
return {
|
|
770
1157
|
haven_sign: async (input) => runTool(async () => {
|
|
771
|
-
const args =
|
|
1158
|
+
const args = parseStrictFor("haven_sign", coerceX402Expected(input));
|
|
1159
|
+
if (args.task_budget_id) {
|
|
1160
|
+
if (args.payment_id || args.payload_hash) {
|
|
1161
|
+
throw new z$1.ZodError([
|
|
1162
|
+
{
|
|
1163
|
+
code: z$1.ZodIssueCode.custom,
|
|
1164
|
+
path: ["task_budget_id"],
|
|
1165
|
+
message: "task_budget_id is mutually exclusive with payment_id / payload_hash \u2014 pass exactly one."
|
|
1166
|
+
}
|
|
1167
|
+
]);
|
|
1168
|
+
}
|
|
1169
|
+
if (args.sub_budget_id) {
|
|
1170
|
+
throw new z$1.ZodError([
|
|
1171
|
+
{
|
|
1172
|
+
code: z$1.ZodIssueCode.custom,
|
|
1173
|
+
path: ["sub_budget_id"],
|
|
1174
|
+
message: "task_budget_id is mutually exclusive with sub_budget_id \u2014 pass exactly one."
|
|
1175
|
+
}
|
|
1176
|
+
]);
|
|
1177
|
+
}
|
|
1178
|
+
return signTaskBudget(args.task_budget_id);
|
|
1179
|
+
}
|
|
1180
|
+
if (args.sub_budget_id) {
|
|
1181
|
+
if (args.payment_id || args.payload_hash) {
|
|
1182
|
+
throw new z$1.ZodError([
|
|
1183
|
+
{
|
|
1184
|
+
code: z$1.ZodIssueCode.custom,
|
|
1185
|
+
path: ["sub_budget_id"],
|
|
1186
|
+
message: "sub_budget_id is mutually exclusive with payment_id / payload_hash \u2014 pass exactly one."
|
|
1187
|
+
}
|
|
1188
|
+
]);
|
|
1189
|
+
}
|
|
1190
|
+
return signSubBudget(args.sub_budget_id);
|
|
1191
|
+
}
|
|
772
1192
|
const resolved = await resolveSignContext(args, { allowDirectFallback: true });
|
|
773
1193
|
const typedData = resolved?.ctx.typedData ?? resolveTypedData(args);
|
|
774
1194
|
const payloadHash = resolved?.ctx.payloadHash ?? args.payload_hash;
|
|
@@ -811,7 +1231,7 @@ function createToolHandlers(signer, options = {}) {
|
|
|
811
1231
|
return { signature: result.signature, x402_binding: result.x402Binding, ...clientUpdateField(resolved) };
|
|
812
1232
|
}),
|
|
813
1233
|
haven_x402_sign_header: async (input) => runTool(async () => {
|
|
814
|
-
const args =
|
|
1234
|
+
const args = parseStrictFor("haven_x402_sign_header", coercePaymentRequired(input));
|
|
815
1235
|
const result = await signer.buildX402PaymentHeader(
|
|
816
1236
|
args.payment_required,
|
|
817
1237
|
args.x402_binding
|
|
@@ -823,7 +1243,7 @@ function createToolHandlers(signer, options = {}) {
|
|
|
823
1243
|
return { payment_header: result.paymentHeader, accepted: result.accepted };
|
|
824
1244
|
}),
|
|
825
1245
|
haven_sign_x402: async (input) => runTool(async () => {
|
|
826
|
-
const args =
|
|
1246
|
+
const args = parseStrictFor("haven_sign_x402", coerceX402Expected(coercePaymentRequired(input)));
|
|
827
1247
|
const resolved = await resolveSignContext(args, { allowDirectFallback: false });
|
|
828
1248
|
const fetched = resolved?.kind === "x402" ? resolved.ctx : void 0;
|
|
829
1249
|
const payloadHash = fetched?.payloadHash ?? args.payload_hash;
|
|
@@ -859,7 +1279,7 @@ function createToolHandlers(signer, options = {}) {
|
|
|
859
1279
|
};
|
|
860
1280
|
}),
|
|
861
1281
|
haven_sign_sweep_delegate: async (input) => runTool(async () => {
|
|
862
|
-
const args =
|
|
1282
|
+
const args = parseStrictFor("haven_sign_sweep_delegate", input);
|
|
863
1283
|
const result = await signer.signSweepAuthorization({
|
|
864
1284
|
authorization: args.authorization,
|
|
865
1285
|
expectedAuth: args.expected_auth,
|
|
@@ -892,8 +1312,18 @@ function createToolHandlers(signer, options = {}) {
|
|
|
892
1312
|
}
|
|
893
1313
|
}
|
|
894
1314
|
}
|
|
895
|
-
function
|
|
896
|
-
|
|
1315
|
+
function parseStrictFor(name, input) {
|
|
1316
|
+
const result = z$1.object(toolSchemas[name]).strict().safeParse(input ?? {});
|
|
1317
|
+
if (result.success) return result.data;
|
|
1318
|
+
const topLevelUnknown = result.error.issues.flatMap(
|
|
1319
|
+
(issue) => issue.code === "unrecognized_keys" && issue.path.length === 0 ? issue.keys : []
|
|
1320
|
+
);
|
|
1321
|
+
if (topLevelUnknown.length === 0) throw result.error;
|
|
1322
|
+
const unknown = [...new Set(topLevelUnknown)];
|
|
1323
|
+
const err = new Error(
|
|
1324
|
+
`${UNSUPPORTED_ARGUMENT_MARKER} ${name} does not accept ${unknown.map((k) => `"${k}"`).join(", ")} \u2014 this signer predates that argument form. Nothing was signed or fetched.`
|
|
1325
|
+
);
|
|
1326
|
+
throw err;
|
|
897
1327
|
}
|
|
898
1328
|
function coerceX402Expected(input) {
|
|
899
1329
|
if (!input || typeof input !== "object") return input;
|
|
@@ -932,6 +1362,25 @@ async function runTool(fn) {
|
|
|
932
1362
|
}
|
|
933
1363
|
}
|
|
934
1364
|
function normalizeError(err) {
|
|
1365
|
+
if (err instanceof Error && err.message.startsWith(UNSUPPORTED_ARGUMENT_MARKER)) {
|
|
1366
|
+
const unknown = [...err.message.matchAll(/"([^"]+)"/g)].map((m) => m[1]);
|
|
1367
|
+
return {
|
|
1368
|
+
success: false,
|
|
1369
|
+
code: "UNSUPPORTED_ARGUMENT",
|
|
1370
|
+
message: err.message.replace(`${UNSUPPORTED_ARGUMENT_MARKER} `, ""),
|
|
1371
|
+
unknown_arguments: unknown,
|
|
1372
|
+
signer_version: SIGNER_VERSION,
|
|
1373
|
+
fallback: connectorUpgradeCommand(),
|
|
1374
|
+
next_action: AgentPaymentNextAction.StopAndTellUser,
|
|
1375
|
+
...nextStepWireFields(
|
|
1376
|
+
signerRefusalStep({
|
|
1377
|
+
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
1378
|
+
nextTool: null,
|
|
1379
|
+
nextToolOmittedReason: `update @haven_ai/signer: run ${connectorUpgradeCommand()} and the repair line it prints, then repeat the same call`
|
|
1380
|
+
})
|
|
1381
|
+
)
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
935
1384
|
if (err instanceof z$1.ZodError) {
|
|
936
1385
|
return {
|
|
937
1386
|
success: false,
|
|
@@ -953,7 +1402,9 @@ function normalizeError(err) {
|
|
|
953
1402
|
...nextStepWireFields(signerRefusalStep({
|
|
954
1403
|
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
955
1404
|
nextTool: null,
|
|
956
|
-
|
|
1405
|
+
// #3412: the upgrade command, not "re-run the connector" — a bare re-run
|
|
1406
|
+
// stops at "Missing --setup" on an existing install.
|
|
1407
|
+
nextToolOmittedReason: `update @haven_ai/signer: run ${connectorUpgradeCommand()} and the repair line it prints, then repeat the same call`
|
|
957
1408
|
}))
|
|
958
1409
|
};
|
|
959
1410
|
}
|
|
@@ -1039,7 +1490,7 @@ function normalizeError(err) {
|
|
|
1039
1490
|
|
|
1040
1491
|
// src/consent.ts
|
|
1041
1492
|
var toolSummaries = {
|
|
1042
|
-
haven_sign: "signs
|
|
1493
|
+
haven_sign: "signs a Haven-prepared payment, task-budget open, or task-budget close (a UserOp from your own account whose only call redeems a delegation made to it, directly or through a self-delegated task-budget child, or an x402 funding intent against a Haven-signed context)",
|
|
1043
1494
|
haven_x402_sign_header: "builds and signs the EIP-3009 merchant payment header for an x402 payment",
|
|
1044
1495
|
haven_sign_x402: "the two above in one call: funding payload + merchant header",
|
|
1045
1496
|
haven_sign_sweep_delegate: "signs a Haven-prepared gasless sweep of stranded funds back to your own account"
|
|
@@ -1470,7 +1921,7 @@ function assertSupportedBindingVersion(received, supported, context) {
|
|
|
1470
1921
|
const highest = Math.max(...supported);
|
|
1471
1922
|
const outOfDate = received > highest;
|
|
1472
1923
|
const code = context === "x402 expected context" ? SignerRefusalCode.UnsupportedExpectedContextVersion : SignerRefusalCode.UnsupportedSweepBindingVersion;
|
|
1473
|
-
const ceiling = outOfDate ? `This signer is out of date: it supports ${context} versions up to ${highest}, and Haven sent version ${received}. Update @haven_ai/signer \u2014
|
|
1924
|
+
const ceiling = outOfDate ? `This signer is out of date: it supports ${context} versions up to ${highest}, and Haven sent version ${received}. Update @haven_ai/signer \u2014 run \`${connectorUpgradeCommand()}\`, then the repair line it prints, which reinstalls the pinned MCP runtime.` : `Unsupported ${context} version ${received}: this signer supports ${supported.join(", ")}.`;
|
|
1474
1925
|
const fallback = outOfDate ? SIGNER_UPDATE_FALLBACK : `This ${context} version (${received}) is older than what this signer enforces (${supported.join(", ")}) \u2014 updating @haven_ai/signer will not restore it. Nothing was signed or spent; stop and tell the user rather than retrying.`;
|
|
1475
1926
|
throw new HavenUnsupportedSignerVersionError(
|
|
1476
1927
|
`${ceiling} Nothing was signed. Do not rewrite the version field to a supported value: it is part of the Haven-signed binding message, so changing it invalidates the signature and would misrepresent what Haven declared.`,
|
|
@@ -1571,7 +2022,8 @@ function signerCompatibility() {
|
|
|
1571
2022
|
return {
|
|
1572
2023
|
x402_expected_context_versions: [...SUPPORTED_X402_EXPECTED_VERSIONS],
|
|
1573
2024
|
sweep_binding_versions: [...SUPPORTED_SWEEP_BINDING_VERSIONS],
|
|
1574
|
-
direct_sign_context_versions: [...SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS]
|
|
2025
|
+
direct_sign_context_versions: [...SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS],
|
|
2026
|
+
task_sign_context_versions: [...SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS]
|
|
1575
2027
|
};
|
|
1576
2028
|
}
|
|
1577
2029
|
function signerCapabilityAdvertisement() {
|
|
@@ -1590,12 +2042,13 @@ function signerInstructions() {
|
|
|
1590
2042
|
`- x402 expected-context versions supported: ${compatibility.x402_expected_context_versions.join(", ")}`,
|
|
1591
2043
|
`- sweep authorization binding versions supported: ${compatibility.sweep_binding_versions.join(", ")}`,
|
|
1592
2044
|
`- direct-payment (haven_send / haven_pay) sign-context versions supported: ${compatibility.direct_sign_context_versions.join(", ")} \u2014 pass payment_id alone to haven_sign; this signer fetches the exact bytes`,
|
|
2045
|
+
`- task-budget sign-context versions supported: ${compatibility.task_sign_context_versions.join(", ")} \u2014 pass task_budget_id alone to haven_sign; this signer fetches the exact bytes`,
|
|
1593
2046
|
"",
|
|
1594
2047
|
"Haven quote and prepare results report the expected-context version they will emit",
|
|
1595
2048
|
"(signer_compatibility.x402_expected_context_version). If that version is not in the list",
|
|
1596
2049
|
"above, this signer is out of date: STOP before signing, and tell the user to update",
|
|
1597
|
-
`@haven_ai/signer
|
|
1598
|
-
"MCP runtime. Do not edit the version field to a supported value \u2014 it is part of the",
|
|
2050
|
+
`@haven_ai/signer: run \`${connectorUpgradeCommand()}\`, then the repair line it prints,`,
|
|
2051
|
+
"which reinstalls the pinned MCP runtime. Do not edit the version field to a supported value \u2014 it is part of the",
|
|
1599
2052
|
"Haven-signed binding message, so changing it invalidates the signature.",
|
|
1600
2053
|
"",
|
|
1601
2054
|
"A version-mismatch refusal from haven_sign / haven_sign_x402 / haven_sign_sweep_delegate is",
|
|
@@ -1603,7 +2056,13 @@ function signerInstructions() {
|
|
|
1603
2056
|
"fallback fields alongside the message, so you can branch on it directly. Every signer",
|
|
1604
2057
|
"refusal also carries the next-step family: next_tool_name + next_tool_server_role when a",
|
|
1605
2058
|
"hosted tool follows (resolve the role against your own server names), else",
|
|
1606
|
-
"next_tool_omitted_reason saying why not."
|
|
2059
|
+
"next_tool_omitted_reason saying why not.",
|
|
2060
|
+
"",
|
|
2061
|
+
"An undeclared top-level argument is refused, not stripped: haven_sign answers",
|
|
2062
|
+
"UNSUPPORTED_ARGUMENT with unknown_arguments, signer_version and fallback (the update",
|
|
2063
|
+
"command) instead of dropping the key and answering the generic signing error \u2014 if you see",
|
|
2064
|
+
"that refusal, this signer predates the argument form you sent. Update the signer; nothing",
|
|
2065
|
+
"was signed, fetched or audited."
|
|
1607
2066
|
].join("\n");
|
|
1608
2067
|
}
|
|
1609
2068
|
async function loadSignerCredentials(path = process.env.HAVEN_CREDENTIALS) {
|
|
@@ -1706,8 +2165,6 @@ async function warnIfCredentialFilePermissive(path, log = (message) => process.s
|
|
|
1706
2165
|
}
|
|
1707
2166
|
|
|
1708
2167
|
// src/server.ts
|
|
1709
|
-
var SIGNER_NAME = "@haven_ai/signer";
|
|
1710
|
-
var SIGNER_VERSION = "0.5.0-alpha.1";
|
|
1711
2168
|
async function resolveSignerRuntime(options = {}) {
|
|
1712
2169
|
assertSupportedNodeVersion(options.nodeVersion);
|
|
1713
2170
|
if (options.delegateKey) {
|
|
@@ -1758,12 +2215,14 @@ function buildSignerMcpServer(signer, options = {}) {
|
|
|
1758
2215
|
clientIdentity: havenClientIdentity(SIGNER_NAME, SIGNER_VERSION)
|
|
1759
2216
|
}
|
|
1760
2217
|
});
|
|
1761
|
-
const registerTool = server.
|
|
2218
|
+
const registerTool = server.registerTool.bind(server);
|
|
1762
2219
|
for (const name of Object.keys(toolSchemas)) {
|
|
1763
2220
|
registerTool(
|
|
1764
2221
|
name,
|
|
1765
|
-
|
|
1766
|
-
|
|
2222
|
+
{
|
|
2223
|
+
description: toolDescriptions[name],
|
|
2224
|
+
inputSchema: z.object(toolSchemas[name]).passthrough()
|
|
2225
|
+
},
|
|
1767
2226
|
async (args) => toMcpResult(await handlers[name](args))
|
|
1768
2227
|
);
|
|
1769
2228
|
}
|