@weaccelerateinc/agent-sdk 0.0.0-stage → 0.10.3

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.
Files changed (52) hide show
  1. package/README.md +149 -2
  2. package/approved-extension-releases/0.0.0.1.json +39 -0
  3. package/approved-extension-releases/0.0.0.2.json +32 -0
  4. package/approved-extension.d.ts +24 -0
  5. package/approved-extension.js +90 -0
  6. package/browser-connector.d.ts +87 -0
  7. package/browser-connector.js +179 -0
  8. package/browser-operations.d.ts +56 -0
  9. package/browser-operations.js +144 -0
  10. package/browser-use-host.d.ts +43 -0
  11. package/browser-use-host.js +147 -0
  12. package/checkout-diagnostics.d.ts +23 -0
  13. package/checkout-diagnostics.js +67 -0
  14. package/cli.js +102 -0
  15. package/customer.env.example +6 -0
  16. package/host-adapter.d.ts +90 -0
  17. package/host-adapter.js +353 -0
  18. package/index.d.ts +209 -0
  19. package/index.js +441 -0
  20. package/linq-messaging.d.ts +45 -0
  21. package/linq-messaging.js +123 -0
  22. package/managed-mcp.d.ts +150 -0
  23. package/managed-mcp.js +382 -0
  24. package/package.json +114 -4
  25. package/partner-starter/.env.example +11 -0
  26. package/partner-starter/README.md +48 -0
  27. package/partner-starter/gitignore.template +5 -0
  28. package/partner-starter/package.json +21 -0
  29. package/partner-starter/runtime.mjs +32 -0
  30. package/partner-starter/server.mjs +13 -0
  31. package/partner-starter/smoke.mjs +56 -0
  32. package/partner-starter/synthetic.mjs +226 -0
  33. package/partner-starter/tests/starter.test.mjs +69 -0
  34. package/partner-starter/trusted-context.example.mjs +20 -0
  35. package/partner.d.ts +27 -0
  36. package/partner.js +49 -0
  37. package/postgres-host.d.ts +14 -0
  38. package/postgres-host.js +102 -0
  39. package/private-chat.d.ts +104 -0
  40. package/private-chat.js +309 -0
  41. package/private-ingress.d.ts +46 -0
  42. package/private-ingress.js +132 -0
  43. package/starter/.env.example +19 -0
  44. package/starter/README.md +88 -0
  45. package/starter/gitignore.template +5 -0
  46. package/starter/package.json +21 -0
  47. package/starter/runtime.mjs +61 -0
  48. package/starter/server.mjs +13 -0
  49. package/starter/smoke.mjs +55 -0
  50. package/starter/synthetic.mjs +163 -0
  51. package/starter/tests/starter.test.mjs +256 -0
  52. package/starter/trusted-context.example.mjs +49 -0
@@ -0,0 +1,150 @@
1
+ import type { AccelerateClient, AccelerateNextAction, Checkout, CheckoutId, CustomerBrowserBinding } from './index.js';
2
+ import type { BrowserResult } from './browser-operations.js';
3
+ import type { BrowserConnector } from './browser-connector.js';
4
+
5
+ /** Resolve from authenticated host state. MCP arguments and _meta alone are not identity proof. */
6
+ export interface ManagedCheckoutContext {
7
+ customer_id: string;
8
+ workspace_id: string;
9
+ conversation_id: string;
10
+ phone_number: string;
11
+ }
12
+ /** Minimal private storage projection; never returned through a model tool. */
13
+ export interface ManagedCheckoutBinding extends ManagedCheckoutContext { checkout_id: CheckoutId }
14
+ export interface ManagedCheckoutCreation {
15
+ /** Server-derived key for this exact creation attempt, protected by the host's durable fence. */
16
+ idempotency_key: string;
17
+ /** Optional existing browser from the host's owned workflow, never MCP input.
18
+ * Only permitted with browserMode:'customer_backend_trusted'. */
19
+ browser?: CustomerBrowserBinding;
20
+ /** Resolve only after durable storage. Retain an uncertainty fence if acknowledgement is lost. */
21
+ persistCreated(binding: Readonly<ManagedCheckoutBinding>): Promise<void>;
22
+ }
23
+ export interface ManagedMcpError {
24
+ code: string;
25
+ message: string;
26
+ status: number;
27
+ next_action: AccelerateNextAction;
28
+ retry_safe: false;
29
+ payment_confirmed: false;
30
+ support_reference?: `acs_${string}` | `arq_${string}`;
31
+ }
32
+ export type ManagedMcpOutput = { ok: true; checkout: Checkout } | { ok: true; browser: BrowserResult } |
33
+ { ok: false; error: ManagedMcpError; checkout?: Checkout };
34
+ export interface ManagedCheckoutEvent {
35
+ version: 'v1';
36
+ kind: 'created' | 'payment_requested' | 'approval_requested' | 'review_ready';
37
+ /** Stable hash-only key; deduplicate delivery durably. Never retry the underlying SDK operation. */
38
+ idempotency_key: `accelerate-checkout-event:${string}`;
39
+ checkout: Checkout;
40
+ }
41
+ export interface ManagedMcpToolResult {
42
+ [key: string]: unknown;
43
+ content: Array<{ type: 'text'; text: string }>;
44
+ structuredContent: ManagedMcpOutput;
45
+ isError?: true;
46
+ }
47
+ export interface ManagedMcpStandardSchema {
48
+ readonly '~standard': {
49
+ readonly version: 1;
50
+ readonly vendor: string;
51
+ readonly validate: (value: unknown) => { value: unknown } | { issues: Array<{ message: string }> };
52
+ readonly jsonSchema: {
53
+ readonly input: (options?: unknown) => Record<string, unknown>;
54
+ readonly output: (options?: unknown) => Record<string, unknown>;
55
+ };
56
+ };
57
+ }
58
+ export interface ManagedMcpRegisteredDefinition {
59
+ title: string;
60
+ description: string;
61
+ inputSchema: ManagedMcpStandardSchema;
62
+ outputSchema: ManagedMcpStandardSchema;
63
+ annotations: { readOnlyHint: boolean; idempotentHint: boolean; destructiveHint: boolean; openWorldHint: true };
64
+ }
65
+ /** Structural MCP2 registration surface; no protocol or schema package is imported at runtime. */
66
+ export interface ManagedMcpServer<Extra = unknown> {
67
+ registerTool(name: string, definition: ManagedMcpRegisteredDefinition,
68
+ handler: (args: unknown, extra: Extra) => Promise<ManagedMcpToolResult>): unknown;
69
+ }
70
+ export type ManagedCheckoutProfileId = 'managed_review_v2' | 'managed_review_v1' | 'legacy_before_fill_v1';
71
+ /** Immutable flow metadata. Does not qualify a merchant layout or grant submit
72
+ * authority. The private sender must verify the actual five-minute window. */
73
+ export const managedCheckoutProfiles: Readonly<{
74
+ managed_review_v2: Readonly<{ id: 'managed_review_v2'; verificationTiming: 'on_payment';
75
+ approvalPolicy: 'after_fill'; approvalWindowMs: 300000; fillAfterVerification: true }>;
76
+ managed_review_v1: Readonly<{ id: 'managed_review_v1'; verificationTiming: 'on_payment';
77
+ approvalPolicy: 'after_fill'; approvalWindowMs: 300000 }>;
78
+ legacy_before_fill_v1: Readonly<{ id: 'legacy_before_fill_v1'; verificationTiming: 'on_start';
79
+ approvalPolicy: 'before_fill' }>;
80
+ }>;
81
+ export interface ManagedCheckoutToolsOptions<Extra = unknown> {
82
+ /** Defaults to AccelerateClient.fromEnv(). Instantiate only on the authenticated application server. */
83
+ client?: AccelerateClient;
84
+ /** Called on EVERY tool invocation; resolve verified host context, not untrusted model metadata. */
85
+ resolveContext(extra: Extra): Promise<ManagedCheckoutContext> | ManagedCheckoutContext;
86
+ /**
87
+ * Hold your existing durable owner lock and uncertainty fence, authorize ALL
88
+ * binding fields, invoke work exactly once with a minimal saved binding, and
89
+ * return its result. Never retry an operation or clear an uncertain purchase.
90
+ */
91
+ withOwnedCheckout<T>(context: Readonly<ManagedCheckoutContext>, checkoutId: CheckoutId,
92
+ work: (binding: Readonly<ManagedCheckoutBinding>) => Promise<T>,
93
+ metadata?: Readonly<{ operation: ManagedCheckoutToolName; revision?: number }>): Promise<T>;
94
+ /**
95
+ * Hold the same durable owner lock/fence until work resolves. Supply a stable
96
+ * server-derived creation key and persistCreated acknowledgement. If creation
97
+ * or persistence is uncertain, preserve that attempt and reconcile it; never
98
+ * create a replacement or automatically compensate with another payment call.
99
+ */
100
+ withCheckoutCreation<T>(context: Readonly<ManagedCheckoutContext>,
101
+ work: (creation: ManagedCheckoutCreation) => Promise<T>, metadata?: Readonly<{ start_url: string }>): Promise<T>;
102
+ /**
103
+ * Durably accept/deduplicate this SAFE event under its key before privately
104
+ * starting/reading chat state and rendering/delivering prompts. Use an outbox
105
+ * to recover lost acknowledgements with the SAME event key, never a new
106
+ * checkout/payment action. The private client/replies remain outside MCP.
107
+ * On unconfirmed acceptance the tool returns a fixed error and known safe
108
+ * checkout ID/revision; the existing owner record and uncertainty fence remain.
109
+ */
110
+ dispatchCheckoutEvent(context: Readonly<ManagedCheckoutContext>, event: Readonly<ManagedCheckoutEvent>): Promise<void>;
111
+ /** Immutable for newly created checkouts. Default sandbox_fill_only. */
112
+ purpose?: 'sandbox_fill_only' | 'purchase';
113
+ /** Default false. Registers final-submit tool only when true and purpose is purchase. */
114
+ enableSubmit?: boolean;
115
+ /** Default managed_review_v1: verification at payment and approval after fill.
116
+ * Legacy before-fill behavior requires this explicit profile or approvalPolicy:'before_fill'. */
117
+ profile?: ManagedCheckoutProfileId;
118
+ /** Must be on_payment for managed_review_v1/v2. Legacy allows either timing and defaults to on_start. */
119
+ verificationTiming?: 'on_start' | 'on_payment';
120
+ /** Must match the selected profile. An explicit before_fill without profile
121
+ * deliberately selects legacy behavior; requested after-fill never downgrades. */
122
+ approvalPolicy?: 'before_fill' | 'after_fill';
123
+ /** Explicitly selects trusted customer-backend existing-browser attachment. The
124
+ * default remains Accelerate-created private browser custody. */
125
+ browserMode?: 'accelerate_managed' | 'customer_backend_trusted';
126
+ /** Optional strict existing-browser lifecycle. Requires customer_backend_trusted
127
+ * and managed_review_v1. The legacy server-provided scope.browser path remains explicit. */
128
+ browserConnector?: BrowserConnector;
129
+ }
130
+ export type ManagedCheckoutToolName = 'accelerate_checkout_start' | 'accelerate_checkout_status' | 'accelerate_checkout_browser' |
131
+ 'accelerate_checkout_request_payment' | 'accelerate_checkout_apply_details' | 'accelerate_checkout_request_approval' |
132
+ 'accelerate_checkout_fill' | 'accelerate_checkout_cancel' | 'accelerate_checkout_submit';
133
+ export interface ManagedCheckoutToolDefinition {
134
+ name: string;
135
+ title: string;
136
+ description: string;
137
+ inputSchema: Readonly<Record<string, unknown>>;
138
+ outputSchema: Readonly<Record<string, unknown>>;
139
+ annotations: Readonly<ManagedMcpRegisteredDefinition['annotations']>;
140
+ readOnly?: true;
141
+ destructive?: true;
142
+ submitOnly?: true;
143
+ /** Registered only when verificationTiming is on_payment. */
144
+ paymentOnly?: true;
145
+ }
146
+ /** Frozen canonical JSON Schemas and descriptions for discovery/documentation. */
147
+ export const managedCheckoutToolDefinitions: readonly Readonly<ManagedCheckoutToolDefinition>[];
148
+ /** Register on the customer's authenticated MCP server, with private ingress configured separately. */
149
+ export function registerManagedCheckoutTools<Extra = unknown>(server: ManagedMcpServer<Extra>,
150
+ options: ManagedCheckoutToolsOptions<Extra>): readonly unknown[];
package/managed-mcp.js ADDED
@@ -0,0 +1,382 @@
1
+ import { AccelerateClient, AccelerateError } from './index.js';
2
+ import { browserOperationJsonSchema, parseBrowserOperation, normalizeBrowserOperation } from './browser-operations.js';
3
+ import { checkoutDiagnosticsJsonSchema, validateCheckoutDiagnostics } from './checkout-diagnostics.js';
4
+ import { createHash } from 'node:crypto';
5
+
6
+ const ID = /^mcs_[A-Za-z0-9_-]{43}$/;
7
+ const OWNER = /^[A-Za-z0-9_.:-]{1,500}$/;
8
+ const PHONE = /^\+[1-9]\d{6,14}$/;
9
+ const CONTEXT_KEYS = ['customer_id', 'workspace_id', 'conversation_id', 'phone_number'];
10
+ const STATUSES = ['wallet_required', 'selection_ready', 'details_ready', 'approval_required', 'approved',
11
+ 'ready_for_submit', 'cancelled', 'expired', 'failed', 'submit_attempted', 'outcome_unknown'];
12
+ const NEXT_ACTIONS = ['check_configuration', 'check_request', 'check_status', 'complete_wallet',
13
+ 'complete_verification', 'refresh_quote', 'refresh_revision', 'reconcile_order', 'stop'];
14
+ const object = value => value !== null && typeof value === 'object' && !Array.isArray(value) &&
15
+ [Object.prototype, null].includes(Object.getPrototypeOf(value));
16
+ const check = (value, code = 'invalid_input') => { if (!value) throw new AccelerateError(code); };
17
+ function shape(value, required, optional = [], code = 'invalid_input') {
18
+ check(object(value) && required.every(key => Object.hasOwn(value, key)) &&
19
+ Object.keys(value).every(key => required.includes(key) || optional.includes(key)), code);
20
+ }
21
+ function freeze(value) {
22
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
23
+ for (const child of Object.values(value)) freeze(child);
24
+ Object.freeze(value);
25
+ }
26
+ return value;
27
+ }
28
+ /** Versioned server-selected flow policies. Merchant/layout qualification and
29
+ * the actual delivered five-minute window are verified by the managed service
30
+ * and private review sender; these constants grant no payment capability. */
31
+ export const managedCheckoutProfiles = freeze({
32
+ managed_review_v2: { id: 'managed_review_v2', verificationTiming: 'on_payment',
33
+ approvalPolicy: 'after_fill', approvalWindowMs: 300000, fillAfterVerification: true },
34
+ managed_review_v1: { id: 'managed_review_v1', verificationTiming: 'on_payment',
35
+ approvalPolicy: 'after_fill', approvalWindowMs: 300000 },
36
+ legacy_before_fill_v1: { id: 'legacy_before_fill_v1', verificationTiming: 'on_start',
37
+ approvalPolicy: 'before_fill' },
38
+ });
39
+ function flowProfile({ profile, verificationTiming, approvalPolicy }) {
40
+ const id = profile === undefined ? approvalPolicy === 'before_fill' ? 'legacy_before_fill_v1' : 'managed_review_v1' : profile;
41
+ check(typeof id === 'string' && Object.hasOwn(managedCheckoutProfiles, id));
42
+ const selected = managedCheckoutProfiles[id];
43
+ const timing = verificationTiming === undefined ? selected.verificationTiming : verificationTiming;
44
+ const approval = approvalPolicy === undefined ? selected.approvalPolicy : approvalPolicy;
45
+ check(approval === selected.approvalPolicy && ['on_start', 'on_payment'].includes(timing) &&
46
+ (id === 'legacy_before_fill_v1' || timing === selected.verificationTiming));
47
+ return freeze({ ...selected, verificationTiming: timing });
48
+ }
49
+ const schemaObject = (properties, required = Object.keys(properties)) => ({ type: 'object', properties,
50
+ required, additionalProperties: false });
51
+ const stringSchema = maxLength => ({ type: 'string', minLength: 1, maxLength });
52
+ const idSchema = { type: 'string', pattern: ID.source };
53
+ const revisionSchema = { type: 'integer', minimum: 1, maximum: 2147483647 };
54
+ const urlSchema = { ...stringSchema(2048), format: 'uri', description: 'Actual public HTTPS merchant checkout URL. No credentials or private browser URLs.' };
55
+ const timestampSchema = { type: 'string', format: 'date-time' };
56
+ const nullableText = max => ({ anyOf: [stringSchema(max), { type: 'null' }] });
57
+ const checkoutSchema = schemaObject({
58
+ id: idSchema, status: { type: 'string', enum: STATUSES }, revision: { type: 'integer', minimum: 0, maximum: 2147483647 },
59
+ expires_at: timestampSchema, purpose: { type: 'string', enum: ['purchase', 'sandbox_source_comparison', 'sandbox_fill_only'] },
60
+ approval_policy: { const: 'after_fill' }, purchase_approved: { type: 'boolean' },
61
+ fill_after_verification: { const: true },
62
+ submit_allowed: { type: 'boolean' }, retry_safe: { const: false }, payment_confirmed: { const: false },
63
+ card: schemaObject({ last4: { anyOf: [{ type: 'string', pattern: '^\\d{4}$' }, { type: 'null' }] }, brand: nullableText(40) }),
64
+ address: schemaObject({ label: nullableText(400), city: nullableText(128), state: nullableText(128), postal_code_mask: nullableText(128) }, ['label']),
65
+ quote: schemaObject({ checkout_origin: urlSchema, amount_cents: revisionSchema, currency: { const: 'USD' } }),
66
+ submit_expires_at: timestampSchema, diagnostics: checkoutDiagnosticsJsonSchema,
67
+ }, ['id', 'status', 'revision', 'expires_at', 'retry_safe', 'payment_confirmed']);
68
+ const browserSchema = schemaObject({ content: { type: 'array', minItems: 1, maxItems: 20,
69
+ items: schemaObject({ type: { const: 'text' }, text: { type: 'string', maxLength: 24000 } }) } });
70
+ const errorSchema = schemaObject({ code: { type: 'string', pattern: '^[a-z][a-z0-9_]{0,63}$' }, message: stringSchema(500),
71
+ status: { type: 'integer', minimum: 0, maximum: 599 }, next_action: { type: 'string', enum: NEXT_ACTIONS },
72
+ retry_safe: { const: false }, payment_confirmed: { const: false },
73
+ support_reference: { type: 'string', pattern: '^(?:acs_|arq_)[a-f0-9]{32}$' },
74
+ }, ['code', 'message', 'status', 'next_action', 'retry_safe', 'payment_confirmed']);
75
+ const failureSchema = schemaObject({ ok: { const: false }, error: errorSchema, checkout: checkoutSchema }, ['ok', 'error']);
76
+ const outputSchema = kind => ({ type: 'object', oneOf: [schemaObject({ ok: { const: true }, [kind]: kind === 'browser' ? browserSchema : checkoutSchema }), failureSchema] });
77
+ const idInput = schemaObject({ checkout_id: idSchema });
78
+ const revisionInput = schemaObject({ checkout_id: idSchema, revision: revisionSchema });
79
+ const browserInput = { ...browserOperationJsonSchema, oneOf: browserOperationJsonSchema.oneOf.map(branch => ({ ...branch,
80
+ properties: { checkout_id: idSchema, ...branch.properties }, required: ['checkout_id', ...branch.required] })) };
81
+
82
+ /** One model-visible contract. Private authentication and reply APIs are deliberately absent. */
83
+ export const managedCheckoutToolDefinitions = freeze([
84
+ { name: 'accelerate_checkout_start', title: 'Start Accelerate checkout',
85
+ description: 'Start one owned chat checkout in Accelerate’s managed private browser. Identity, phone, immutable test/purchase policy and creation idempotency come from your authenticated server. The default managed_review_v1 profile defers verification until payment: prepare the purchase before calling accelerate_checkout_request_payment. Legacy verification at creation requires deliberate server configuration. Shopper verification stays private in chat. Does not submit payment. Never restart an uncertain purchase.',
86
+ inputSchema: schemaObject({ start_url: urlSchema }), outputSchema: outputSchema('checkout') },
87
+ { name: 'accelerate_checkout_status', title: 'Read Accelerate checkout status',
88
+ description: 'Read safe status, current revision and optional fixed recovery guidance for this owned checkout. No card credentials, private wallet tokens or browser connection are returned. Status and guidance do not confirm an order or authorize retries.',
89
+ inputSchema: idInput, outputSchema: outputSchema('checkout'), readOnly: true },
90
+ { name: 'accelerate_checkout_browser', title: 'Prepare merchant checkout',
91
+ description: 'Prepare this same managed checkout browser with restricted text observations, public navigation and safe actions. In deferred verification mode, shop and reach payment before requesting wallet verification; wallet_required does not prevent preparation. Each result replaces ALL earlier element refs; use only the latest refs and batch at most eight steps. No scripts, screenshots, card entry or final purchase. Generic inspection is fenced after private handover.',
92
+ inputSchema: browserInput, outputSchema: outputSchema('browser') },
93
+ { name: 'accelerate_checkout_request_payment', title: 'Start private payment verification',
94
+ description: 'At the merchant payment step, request private OTP and masked wallet selection for this existing owned browser. Available only with deferred verification and while wallet_required. Request this only when the observed merchant page requires payment information, never merely because status is wallet_required or diagnostics suggest private chat. Fresh CVV is requested privately after the final quote is verified; with after_fill policy the customer reviews and approves only after private filling. Does not approve, fill or submit. Private dispatch is deduplicated for this checkout; check this existing checkout after uncertain delivery instead of creating a replacement.',
95
+ inputSchema: idInput, outputSchema: outputSchema('checkout'), paymentOnly: true },
96
+ { name: 'accelerate_checkout_apply_details', title: 'Apply selected wallet address',
97
+ description: 'Privately apply the shopper’s selected saved address to the current merchant checkout before final shipping, tax and total. The agent supplies only the checkout URL; address values stay private. Reinspect the actual final total afterward.',
98
+ inputSchema: schemaObject({ checkout_id: idSchema, checkout_url: urlSchema }), outputSchema: outputSchema('checkout') },
99
+ { name: 'accelerate_checkout_request_approval', title: 'Request exact checkout approval',
100
+ description: 'Freeze the actual final merchant URL, USD cents, total control and final-submit control. Accelerate independently verifies the page total. With after_fill policy this requests private CVV for filling, then the customer reviews a masked screenshot and approves the final amount afterward. With before_fill policy it requests approval first. This call never fills or submits.',
101
+ inputSchema: schemaObject({ checkout_id: idSchema, checkout_url: urlSchema, amount_cents: revisionSchema, currency: { const: 'USD' },
102
+ checkout_total_selector: stringSchema(500), submit_selector: stringSchema(200) }), outputSchema: outputSchema('checkout') },
103
+ { name: 'accelerate_checkout_fill', title: 'Fill approved checkout privately',
104
+ description: 'Fill this exact CVV-verified revision once through Accelerate’s private extension. No automatic submission. The default managed_review_v1 profile requires private ingress to deliver the masked filled review and final total, then wait for explicit customer approval. Its five-minute decision window starts at confirmed private message delivery; the private sender verifies the actual window. purchase_approved must be true before submit. Observe the returned submit deadline and cancel fill-only tests after review. Never replay a failed or uncertain fill.',
105
+ inputSchema: revisionInput, outputSchema: outputSchema('checkout'), destructive: true },
106
+ { name: 'accelerate_checkout_cancel', title: 'Cancel owned checkout',
107
+ description: 'Cancel this exact owned checkout and request private browser cleanup. Cancellation does not reverse an attempted order or prove that no purchase occurred. Keep uncertain-payment fences until independently reconciled.',
108
+ inputSchema: idInput, outputSchema: outputSchema('checkout'), destructive: true },
109
+ { name: 'accelerate_checkout_submit', title: 'Explicitly submit approved purchase',
110
+ description: 'Explicit final purchase decision for this exact owned revision while its returned ready claim remains valid. Calls the one-use private final-submit operation once. Never automatically invoke after fill or retry after uncertainty. The default review profile requires the customer’s private post-fill approval first. submit_attempted is not order or payment confirmation. Available only when the server explicitly enables purchase submission.',
111
+ inputSchema: revisionInput, outputSchema: outputSchema('checkout'), destructive: true, submitOnly: true },
112
+ ].map(value => ({ ...value, annotations: { readOnlyHint: value.readOnly === true, idempotentHint: value.readOnly === true,
113
+ destructiveHint: value.destructive === true, openWorldHint: true } })));
114
+
115
+ function safeText(value, max = 1000, multiline = false) {
116
+ check(typeof value === 'string' && value.length > 0 && value.length <= max &&
117
+ !(multiline ? /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u : /[\u0000-\u001f\u007f]/u).test(value), 'invalid_response');
118
+ const flat = value.replace(/[\r\n\t]/g, ' ');
119
+ check(!/\b(?:session_token|handoff_token|submit_token|id_token|authorizationToken|api_key|hmac_secret)\b/iu.test(flat), 'invalid_response');
120
+ for (let start = 0; start < flat.length; start += 1000) {
121
+ try { normalizeBrowserOperation('browser_act', { steps: [{ type: 'type', text: flat.slice(start, start + 2000) }] }); }
122
+ catch { throw new AccelerateError('invalid_response'); }
123
+ }
124
+ return value;
125
+ }
126
+ function timestamp(value) { check(typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T[\d:.]+Z$/u.test(value) && Number.isFinite(Date.parse(value)), 'invalid_response'); return value; }
127
+ function projectCheckout(value) {
128
+ shape(value, ['id', 'status', 'revision', 'expires_at', 'retry_safe', 'payment_confirmed'],
129
+ ['purpose', 'submit_allowed', 'approval_policy', 'purchase_approved', 'fill_after_verification', 'card', 'address', 'quote', 'submit_expires_at', 'diagnostics'], 'invalid_response');
130
+ check(typeof value.id === 'string' && ID.test(value.id) && STATUSES.includes(value.status) && Number.isSafeInteger(value.revision) &&
131
+ value.revision >= 0 && value.revision <= 2147483647 && value.retry_safe === false && value.payment_confirmed === false, 'invalid_response');
132
+ const result = { id: value.id, status: value.status, revision: value.revision, expires_at: timestamp(value.expires_at), retry_safe: false, payment_confirmed: false };
133
+ if (value.purpose !== undefined || value.submit_allowed !== undefined) {
134
+ check(['purchase', 'sandbox_source_comparison', 'sandbox_fill_only'].includes(value.purpose) && value.submit_allowed === (value.purpose === 'purchase'), 'invalid_response');
135
+ result.purpose = value.purpose; result.submit_allowed = value.submit_allowed;
136
+ }
137
+ if (value.approval_policy !== undefined || value.purchase_approved !== undefined) {
138
+ check(value.approval_policy === 'after_fill' && typeof value.purchase_approved === 'boolean' && (!value.purchase_approved || value.status === 'ready_for_submit' && value.purpose === 'purchase'), 'invalid_response');
139
+ result.approval_policy = value.approval_policy; result.purchase_approved = value.purchase_approved;
140
+ }
141
+ if (value.fill_after_verification !== undefined) {
142
+ check(value.fill_after_verification === true && value.approval_policy === 'after_fill' &&
143
+ ['purchase', 'sandbox_fill_only'].includes(value.purpose), 'invalid_response');
144
+ result.fill_after_verification = true;
145
+ }
146
+ if (value.card !== undefined) {
147
+ shape(value.card, ['last4', 'brand'], [], 'invalid_response');
148
+ check(value.card.last4 === null || typeof value.card.last4 === 'string' && /^\d{4}$/u.test(value.card.last4), 'invalid_response');
149
+ result.card = { last4: value.card.last4, brand: value.card.brand === null ? null : safeText(value.card.brand, 40) };
150
+ }
151
+ if (value.address !== undefined) {
152
+ shape(value.address, ['label'], ['city', 'state', 'postal_code_mask'], 'invalid_response');
153
+ result.address = { label: value.address.label === null ? null : safeText(value.address.label, 400) };
154
+ for (const key of ['city', 'state', 'postal_code_mask']) if (value.address[key] !== undefined) result.address[key] =
155
+ value.address[key] === null ? null : safeText(value.address[key], 128);
156
+ check(result.address.postal_code_mask == null || /[*•]/u.test(result.address.postal_code_mask), 'invalid_response');
157
+ }
158
+ if (value.quote !== undefined) {
159
+ shape(value.quote, ['checkout_origin', 'amount_cents', 'currency'], [], 'invalid_response');
160
+ const url = new URL(value.quote.checkout_origin);
161
+ check(url.origin === value.quote.checkout_origin && Number.isSafeInteger(value.quote.amount_cents) && value.quote.amount_cents > 0 &&
162
+ value.quote.amount_cents <= 2147483647 && value.quote.currency === 'USD', 'invalid_response');
163
+ try { normalizeBrowserOperation('navigate', { url: value.quote.checkout_origin }); } catch { throw new AccelerateError('invalid_response'); }
164
+ result.quote = { checkout_origin: value.quote.checkout_origin, amount_cents: value.quote.amount_cents, currency: 'USD' };
165
+ }
166
+ if (value.submit_expires_at !== undefined) result.submit_expires_at = timestamp(value.submit_expires_at);
167
+ if (value.diagnostics !== undefined) result.diagnostics = JSON.parse(JSON.stringify(validateCheckoutDiagnostics(value.diagnostics, result)));
168
+ return result;
169
+ }
170
+ function projectBrowser(value) {
171
+ shape(value, ['content'], [], 'invalid_response'); check(Array.isArray(value.content) && value.content.length >= 1 && value.content.length <= 20, 'invalid_response');
172
+ let total = 0;
173
+ const content = value.content.map(part => {
174
+ shape(part, ['type', 'text'], [], 'invalid_response'); check(part.type === 'text', 'invalid_response');
175
+ // An empty text block is allowed by the ordinary browser result contract.
176
+ const text = part.text === '' ? '' : safeText(part.text, 24000, true); total += Buffer.byteLength(text, 'utf8');
177
+ return { type: 'text', text };
178
+ });
179
+ check(total <= 24000, 'invalid_response'); return { content };
180
+ }
181
+ function projectError(error) {
182
+ let safe;
183
+ try { safe = error instanceof AccelerateError ? new AccelerateError(error.code, error.status, error.supportReference) : new AccelerateError('request_failed'); }
184
+ catch { safe = new AccelerateError('request_failed'); }
185
+ return { code: safe.code, message: safe.message, status: safe.status, next_action: safe.nextAction,
186
+ retry_safe: false, payment_confirmed: false, ...(typeof safe.supportReference === 'string' && /^(?:acs_|arq_)[a-f0-9]{32}$/u.test(safe.supportReference)
187
+ ? { support_reference: safe.supportReference } : {}) };
188
+ }
189
+ function parseOutput(value, kind) {
190
+ if (value?.ok === false) {
191
+ shape(value, ['ok', 'error'], ['checkout'], 'invalid_response');
192
+ shape(value.error, ['code', 'message', 'status', 'next_action', 'retry_safe', 'payment_confirmed'], ['support_reference'], 'invalid_response');
193
+ const expected = projectError(new AccelerateError(value.error.code, value.error.status, value.error.support_reference));
194
+ check(Object.keys(expected).length === Object.keys(value.error).length && Object.keys(expected).every(key => expected[key] === value.error[key]), 'invalid_response');
195
+ if (value.checkout !== undefined) check(value.error.code === 'checkout_event_unconfirmed', 'invalid_response');
196
+ return { ok: false, error: expected, ...(value.checkout !== undefined ? { checkout: projectCheckout(value.checkout) } : {}) };
197
+ }
198
+ shape(value, ['ok', kind], [], 'invalid_response'); check(value.ok === true, 'invalid_response');
199
+ return { ok: true, [kind]: kind === 'browser' ? projectBrowser(value.browser) : projectCheckout(value.checkout) };
200
+ }
201
+ function parseInput(name, value) {
202
+ const base = name === 'accelerate_checkout_start' ? ['start_url'] : name === 'accelerate_checkout_browser'
203
+ ? ['checkout_id', 'operation', 'arguments'] : name === 'accelerate_checkout_apply_details' ? ['checkout_id', 'checkout_url']
204
+ : name === 'accelerate_checkout_request_approval' ? ['checkout_id', 'checkout_url', 'amount_cents', 'currency', 'checkout_total_selector', 'submit_selector']
205
+ : ['accelerate_checkout_fill', 'accelerate_checkout_submit'].includes(name) ? ['checkout_id', 'revision'] : ['checkout_id'];
206
+ shape(value, base);
207
+ if (value.checkout_id !== undefined) check(typeof value.checkout_id === 'string' && ID.test(value.checkout_id));
208
+ if (name === 'accelerate_checkout_browser') return { checkout_id: value.checkout_id, ...parseBrowserOperation({ operation: value.operation, arguments: value.arguments }) };
209
+ for (const key of ['start_url', 'checkout_url']) if (value[key] !== undefined) normalizeBrowserOperation('navigate', { url: value[key] });
210
+ if (value.revision !== undefined) check(Number.isSafeInteger(value.revision) && value.revision >= 1 && value.revision <= 2147483647);
211
+ if (name === 'accelerate_checkout_request_approval') {
212
+ check(Number.isSafeInteger(value.amount_cents) && value.amount_cents > 0 && value.amount_cents <= 2147483647 && value.currency === 'USD');
213
+ for (const [key, max] of [['checkout_total_selector', 500], ['submit_selector', 200]]) {
214
+ check(typeof value[key] === 'string' && value[key].length > 0 && value[key].length <= max && !/[\u0000-\u001f\u007f]/u.test(value[key]));
215
+ normalizeBrowserOperation('browser_act', { steps: [{ type: 'type', text: value[key] }] });
216
+ }
217
+ }
218
+ return JSON.parse(JSON.stringify(value));
219
+ }
220
+ function standardSchema(json, parse) {
221
+ return freeze({ '~standard': { version: 1, vendor: 'accelerate-managed-checkout',
222
+ validate(value) { try { return { value: parse(value) }; } catch { return { issues: [{ message: 'Invalid Accelerate managed checkout tool value.' }] }; } },
223
+ jsonSchema: { input: () => json, output: () => json } } });
224
+ }
225
+ function contextInput(value) {
226
+ shape(value, CONTEXT_KEYS, [], 'permission_denied');
227
+ check(CONTEXT_KEYS.slice(0, 3).every(key => typeof value[key] === 'string' && OWNER.test(value[key])) &&
228
+ typeof value.phone_number === 'string' && PHONE.test(value.phone_number), 'permission_denied');
229
+ return Object.freeze(Object.fromEntries(CONTEXT_KEYS.map(key => [key, value[key]])));
230
+ }
231
+ function requestOptions(extra) { return extra?.signal instanceof AbortSignal ? { signal: extra.signal } : {}; }
232
+
233
+ /**
234
+ * Register on an authenticated CUSTOMER server. The host's hooks resolve verified
235
+ * identity and hold the durable ownership/uncertainty fence; MCP arguments and
236
+ * extra/_meta are never authentication proof by themselves. Hooks invoke work
237
+ * exactly once, return its result and never retry it. A failed creation/persistence
238
+ * acknowledgement must keep a durable uncertainty fence, not create a replacement.
239
+ */
240
+ export function registerManagedCheckoutTools(server, { client = AccelerateClient.fromEnv(), resolveContext,
241
+ withOwnedCheckout, withCheckoutCreation, dispatchCheckoutEvent, purpose = 'sandbox_fill_only', enableSubmit = false,
242
+ profile, verificationTiming: requestedTiming, approvalPolicy: requestedApproval, browserMode = 'accelerate_managed', browserConnector } = {}) {
243
+ const { verificationTiming, approvalPolicy, fillAfterVerification = false } = flowProfile({ profile, verificationTiming: requestedTiming, approvalPolicy: requestedApproval });
244
+ check(server && typeof server.registerTool === 'function' && client?.checkouts &&
245
+ ['create', 'get', 'browser', 'applyDetails', 'requestApproval', 'fill', 'submit', 'cancel'].every(key => typeof client.checkouts[key] === 'function') &&
246
+ [resolveContext, withOwnedCheckout, withCheckoutCreation, dispatchCheckoutEvent].every(value => typeof value === 'function') &&
247
+ ['purchase', 'sandbox_fill_only'].includes(purpose) && typeof enableSubmit === 'boolean' && (!enableSubmit || purpose === 'purchase') &&
248
+ ['on_start', 'on_payment'].includes(verificationTiming) && ['before_fill', 'after_fill'].includes(approvalPolicy) &&
249
+ ['accelerate_managed', 'customer_backend_trusted'].includes(browserMode) &&
250
+ (browserMode !== 'customer_backend_trusted' || verificationTiming === 'on_payment' && approvalPolicy === 'after_fill') &&
251
+ (browserConnector === undefined || browserMode === 'customer_backend_trusted' && browserConnector?.contract === 'accelerate_existing_browser_v1' &&
252
+ ['prepareAttachment', 'commitAttachment', 'assertPaymentReady', 'confirmCleanup'].every(key => typeof browserConnector[key] === 'function')));
253
+ const requirePolicy = checkout => {
254
+ check(approvalPolicy === 'after_fill' ? checkout.approval_policy === 'after_fill' && typeof checkout.purchase_approved === 'boolean'
255
+ : checkout.approval_policy === undefined && checkout.purchase_approved === undefined, 'invalid_response');
256
+ return checkout;
257
+ };
258
+ const registered = [];
259
+ for (const definition of managedCheckoutToolDefinitions) {
260
+ if (fillAfterVerification && definition.name === 'accelerate_checkout_fill') continue;
261
+ if (definition.submitOnly && !enableSubmit) continue;
262
+ if (definition.paymentOnly && verificationTiming !== 'on_payment') continue;
263
+ const kind = definition.name === 'accelerate_checkout_browser' ? 'browser' : 'checkout';
264
+ const customerDescriptions = {
265
+ accelerate_checkout_start: 'At the merchant payment step, attach the exact existing HTTPS browser document selected by the authenticated backend. Finish shopping, cart changes, all required non-secret contact/shipping fields, any required cardholder-name (cc-name) field and navigation before attachment; use ordinary customer details or ask for missing non-secret details. Leave card number/PAN, expiry and CVV empty; never obtain them through ordinary browser tools. It never creates a replacement browser. A configured connector checks ownership, extension readiness, recording policy, lifetime and automation fencing before private verification. The executor must admit actual unambiguous card fields, supported frames and safe masked review in that document before OTP or CVV. Compatible forms are detected across merchants; unsupported or ambiguous layouts stop before private collection. Identity, phone, browser connection and immutable policy are never model inputs. Continue with accelerate_checkout_request_payment. Does not fill or submit. Never restart an uncertain checkout.',
266
+ accelerate_checkout_browser: 'Read this exact attached checkout with snapshot, text, find or bounded wait before quote handover. Cart changes, required non-secret contact/shipping and cardholder-name fields, and navigation must finish in the platform browser before attachment. Card number/PAN, expiry and CVV remain private and must never be entered through ordinary browser tools. Generic browser actions, scripts, screenshots and credential inspection are forbidden. Each result replaces all previous element refs. Once the quote is frozen, ordinary inspection is fenced; use safe checkout status only. The private masked review is delivered outside model tools.',
267
+ };
268
+ const description = fillAfterVerification && definition.name === 'accelerate_checkout_request_approval'
269
+ ? 'Verify the actual final merchant quote and explicitly arm one private card fill for this exact revision, card, address and existing browser. Successful private CVV verification immediately continues the armed fill in Accelerate’s backend, without a model roundtrip. The private ingress sends the masked filled review and final total. Do not call a manual fill or submit automatically; final purchase approval and the explicit agent submit remain separate.'
270
+ : browserMode === 'customer_backend_trusted' ? customerDescriptions[definition.name] ?? definition.description : definition.description;
271
+ registered.push(server.registerTool(definition.name, { title: definition.title, description,
272
+ inputSchema: standardSchema(definition.inputSchema, value => parseInput(definition.name, value)),
273
+ outputSchema: standardSchema(definition.outputSchema, value => parseOutput(value, kind)), annotations: definition.annotations,
274
+ }, async (args, extra) => {
275
+ let creationStarted = false, creationAcknowledged = false;
276
+ try {
277
+ // Re-resolve for EVERY invocation; never cache a shopper across calls.
278
+ const context = contextInput(await resolveContext(extra));
279
+ let input;
280
+ try { input = parseInput(definition.name, args); } catch { throw new AccelerateError('invalid_input'); }
281
+ const options = requestOptions(extra);
282
+ let admitting = true;
283
+ const requireAdmission = () => check(admitting, 'operation_conflict');
284
+ const dispatch = async (kind, checkout) => {
285
+ requireAdmission();
286
+ const key = createHash('sha256').update(JSON.stringify(['v1', kind, checkout.id, ['approval_requested', 'review_ready'].includes(kind) ? checkout.revision : 0])).digest('hex');
287
+ const event = freeze({ version: 'v1', kind, idempotency_key: 'accelerate-checkout-event:' + key, checkout: projectCheckout(checkout) });
288
+ try { await dispatchCheckoutEvent(context, event); requireAdmission(); return { ok: true, checkout }; }
289
+ catch { requireAdmission(); return { ok: false, error: projectError(new AccelerateError('checkout_event_unconfirmed')), checkout }; }
290
+ };
291
+ let called = false, completed = false, repeated = false, expected;
292
+ const once = work => value => {
293
+ const pending = (async () => {
294
+ if (!admitting || called) { repeated = true; throw new AccelerateError('operation_conflict'); }
295
+ called = true;
296
+ const output = await work(value); requireAdmission(); completed = true; expected = output; return output;
297
+ })();
298
+ // A broken host must not turn a deliberately rejected late callback
299
+ // into an unhandled process rejection. The returned promise still rejects.
300
+ void pending.catch(() => {}); return pending;
301
+ };
302
+ let result;
303
+ try {
304
+ if (definition.name === 'accelerate_checkout_start') {
305
+ result = await withCheckoutCreation(context, once(async scope => {
306
+ shape(scope, ['idempotency_key', 'persistCreated'], browserMode === 'customer_backend_trusted' && !browserConnector ? ['browser', 'browser_resource'] : []);
307
+ check(typeof scope.idempotency_key === 'string' && scope.idempotency_key.length >= 1 && scope.idempotency_key.length <= 200 &&
308
+ !/[\u0000-\u001f\u007f]/u.test(scope.idempotency_key) && typeof scope.persistCreated === 'function');
309
+ const attachment = browserConnector ? await browserConnector.prepareAttachment(context, {
310
+ attempt_key: scope.idempotency_key, checkout_url: input.start_url,
311
+ }, options) : undefined;
312
+ requireAdmission();
313
+ const browser = attachment ? attachment.browser : scope.browser;
314
+ if (browserMode === 'customer_backend_trusted') {
315
+ shape(browser, ['provider', 'browser_id', 'custody_mode'], browser?.provider === 'connector' ? ['connector_id'] : [], 'permission_denied');
316
+ const valid = browser.provider === 'browser_use'
317
+ ? typeof browser.browser_id === 'string' && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/u.test(browser.browser_id)
318
+ : browser.provider === 'connector' && typeof browser.connector_id === 'string' && /^[a-z][a-z0-9_-]{0,63}$/u.test(browser.connector_id) &&
319
+ typeof browser.browser_id === 'string' && /^[A-Za-z0-9_.:-]{1,200}$/u.test(browser.browser_id);
320
+ check(valid && browser.custody_mode === browserMode, 'permission_denied');
321
+ }
322
+ creationStarted = true;
323
+ const created = await client.checkouts.create({ ...context, start_url: input.start_url,
324
+ idempotency_key: scope.idempotency_key, interaction_mode: 'chat', purpose,
325
+ ...(browserMode === 'customer_backend_trusted' ? { browser: { ...browser } } : {}),
326
+ ...(attachment?.browser_resource ?? scope.browser_resource ? { browser_resource: attachment?.browser_resource ?? scope.browser_resource } : {}),
327
+ ...(approvalPolicy === 'after_fill' ? { approval_policy: approvalPolicy } : {}),
328
+ ...(fillAfterVerification ? { fill_after_verification: true } : {}) }, options);
329
+ requireAdmission(); const checkout = requirePolicy(projectCheckout(created));
330
+ check(approvalPolicy !== 'after_fill' || checkout.purchase_approved === false, 'invalid_response');
331
+ check(!fillAfterVerification || checkout.fill_after_verification === true, 'invalid_response');
332
+ check(checkout.purpose === purpose && checkout.submit_allowed === (purpose === 'purchase'), 'invalid_response');
333
+ await scope.persistCreated(Object.freeze({ checkout_id: checkout.id, ...context })); requireAdmission();
334
+ if (browserConnector) { await browserConnector.commitAttachment(context, { checkout_id: checkout.id, attachment }, options); requireAdmission(); }
335
+ creationAcknowledged = true;
336
+ return verificationTiming === 'on_payment' ? { ok: true, checkout } : dispatch('created', checkout);
337
+ }), Object.freeze({ start_url: input.start_url }));
338
+ } else {
339
+ result = await withOwnedCheckout(context, input.checkout_id, once(async owned => {
340
+ shape(owned, ['checkout_id', ...CONTEXT_KEYS], [], 'permission_denied');
341
+ check(owned.checkout_id === input.checkout_id && CONTEXT_KEYS.every(key => owned[key] === context[key]), 'permission_denied');
342
+ const id = input.checkout_id;
343
+ let value;
344
+ if (['accelerate_checkout_status', 'accelerate_checkout_request_payment'].includes(definition.name)) value = await client.checkouts.get(id, options);
345
+ else if (definition.name === 'accelerate_checkout_browser') value = await client.checkouts.browser(id, { operation: input.operation, arguments: input.arguments }, options);
346
+ else if (definition.name === 'accelerate_checkout_apply_details') value = await client.checkouts.applyDetails(id, { checkout_url: input.checkout_url }, options);
347
+ else if (definition.name === 'accelerate_checkout_request_approval') {
348
+ if (browserConnector) { await browserConnector.assertPaymentReady(context, { checkout_id: id, checkout_url: input.checkout_url }, options); requireAdmission(); }
349
+ const { checkout_id: _id, ...approval } = input;
350
+ value = await client.checkouts.requestApproval(id, { ...approval, ...(fillAfterVerification ? { fill_after_verification: true } : {}) }, options);
351
+ check(!fillAfterVerification || value.fill_after_verification === true, 'invalid_response');
352
+ } else if (definition.name === 'accelerate_checkout_fill') value = await client.checkouts.fill(id, { revision: input.revision }, options);
353
+ else if (definition.name === 'accelerate_checkout_submit') { check(enableSubmit && purpose === 'purchase', 'permission_denied'); value = await client.checkouts.submit(id, { revision: input.revision }, options); }
354
+ else value = await client.checkouts.cancel(id, options);
355
+ requireAdmission();
356
+ const projected = kind === 'browser' ? projectBrowser(value) : requirePolicy(projectCheckout(value));
357
+ if (kind === 'checkout') check(projected.id === id, 'invalid_response');
358
+ if (definition.name === 'accelerate_checkout_request_payment') {
359
+ check(projected.status === 'wallet_required', 'checkout_frozen');
360
+ if (browserConnector) { await browserConnector.assertPaymentReady(context, { checkout_id: id }, options); requireAdmission(); }
361
+ return dispatch('payment_requested', projected);
362
+ }
363
+ if (browserConnector && ['accelerate_checkout_cancel', 'accelerate_checkout_status'].includes(definition.name) &&
364
+ projected.diagnostics?.cleanup_status === 'confirmed') {
365
+ await browserConnector.confirmCleanup(context, { checkout_id: id }, options); requireAdmission();
366
+ }
367
+ if (definition.name === 'accelerate_checkout_fill' && approvalPolicy === 'after_fill') return dispatch('review_ready', projected);
368
+ return definition.name === 'accelerate_checkout_request_approval' ? dispatch('approval_requested', projected) : { ok: true, [kind]: projected };
369
+ }), Object.freeze({ operation: definition.name, ...(input.revision === undefined ? {} : { revision: input.revision }) }));
370
+ }
371
+ } finally { admitting = false; }
372
+ check(called && completed && !repeated && result === expected, 'operation_conflict');
373
+ const payload = parseOutput(result, kind);
374
+ return { ...(payload.ok === false ? { isError: true } : {}), content: [{ type: 'text', text: JSON.stringify(payload) }], structuredContent: payload };
375
+ } catch (error) {
376
+ const payload = { ok: false, error: projectError(creationStarted && !creationAcknowledged ? new AccelerateError('outcome_unknown') : error) };
377
+ return { isError: true, content: [{ type: 'text', text: JSON.stringify(payload) }], structuredContent: payload };
378
+ }
379
+ }));
380
+ }
381
+ return Object.freeze(registered);
382
+ }