@apiosk/mcp 1.7.0 → 2.0.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.
Files changed (100) hide show
  1. package/README.md +83 -612
  2. package/assets/brand/apiosk-a-20260921.png +0 -0
  3. package/assets/brand/apple-touch-icon.png +0 -0
  4. package/assets/brand/favicon-dark.ico +0 -0
  5. package/assets/brand/favicon-light.ico +0 -0
  6. package/assets/brand/favicon.ico +0 -0
  7. package/assets/brand/icon-192.png +0 -0
  8. package/assets/brand/icon-512.png +0 -0
  9. package/assets/brand/icon-maskable-512.png +0 -0
  10. package/assets/brand/inter-OFL.txt +93 -0
  11. package/assets/brand/inter-latin-400-normal.woff2 +0 -0
  12. package/assets/brand/inter-latin-500-normal.woff2 +0 -0
  13. package/assets/brand/inter-latin-600-normal.woff2 +0 -0
  14. package/assets/brand/mark-20260905-transparent.svg +1 -0
  15. package/assets/brand/mark-20260918.svg +1 -0
  16. package/assets/brand/mark-black-20260918.svg +1 -0
  17. package/assets/brand/mark-dark-20260905-transparent.png +0 -0
  18. package/assets/brand/mark-dark-20260918.png +0 -0
  19. package/assets/brand/mark-dark-96.png +0 -0
  20. package/assets/brand/mark-light-20260905-transparent.png +0 -0
  21. package/assets/brand/mark-light-20260918.png +0 -0
  22. package/assets/brand/mark-light-96.png +0 -0
  23. package/assets/brand/wordmark-black-320.png +0 -0
  24. package/assets/brand/wordmark-white-320.png +0 -0
  25. package/docs/branding.md +11 -0
  26. package/docs/marketplace-submission-2026-04-08.md +4 -0
  27. package/docs/marketplace-submission-2026-08-20.md +157 -0
  28. package/docs/openai-plugin-submission-2026-09-05.md +143 -0
  29. package/dxt.json +26 -22
  30. package/index.mjs +3 -1
  31. package/logo-optimized-light.png +0 -0
  32. package/package.json +14 -14
  33. package/plugin/apiosk/.codex-plugin/plugin.json +45 -0
  34. package/plugin/apiosk/.mcp.json +8 -0
  35. package/plugin/apiosk/assets/icon-dark.png +0 -0
  36. package/plugin/apiosk/assets/icon.png +0 -0
  37. package/plugin/apiosk/assets/icon.svg +1 -0
  38. package/plugin/apiosk/assets/logo.png +0 -0
  39. package/plugin/apiosk/skills/apiosk/SKILL.md +33 -0
  40. package/plugin/apiosk/skills/apiosk/agents/openai.yaml +12 -0
  41. package/server.json +58 -24
  42. package/server.mjs +136 -151
  43. package/src/approval-feedback.mjs +21 -0
  44. package/src/brand-routes.mjs +28 -0
  45. package/src/create-server.mjs +160 -46
  46. package/src/display-money.mjs +20 -0
  47. package/src/display-text.mjs +67 -0
  48. package/src/gateway-client.mjs +42 -0
  49. package/src/gateway-v2-ask.mjs +50 -0
  50. package/src/gateway-v2-card-account.mjs +23 -0
  51. package/src/gateway-v2-card-actions.mjs +67 -0
  52. package/src/gateway-v2-card-answer-text.mjs +130 -0
  53. package/src/gateway-v2-card-answer.mjs +68 -0
  54. package/src/gateway-v2-card-blocks.mjs +166 -0
  55. package/src/gateway-v2-card-body.mjs +259 -0
  56. package/src/gateway-v2-card-budget.mjs +21 -0
  57. package/src/gateway-v2-card-cbs.mjs +56 -0
  58. package/src/gateway-v2-card-choices.mjs +4 -0
  59. package/src/gateway-v2-card-clarification.mjs +25 -0
  60. package/src/gateway-v2-card-compact.mjs +34 -0
  61. package/src/gateway-v2-card-events.mjs +41 -0
  62. package/src/gateway-v2-card-presentation.mjs +125 -0
  63. package/src/gateway-v2-card-research.mjs +63 -0
  64. package/src/gateway-v2-card-result.mjs +32 -0
  65. package/src/gateway-v2-card-search.mjs +39 -0
  66. package/src/gateway-v2-card-sources.mjs +32 -0
  67. package/src/gateway-v2-card-style.mjs +76 -0
  68. package/src/gateway-v2-card-verdict.mjs +66 -0
  69. package/src/gateway-v2-card.mjs +91 -0
  70. package/src/gateway-v2-contracts.json +66 -0
  71. package/src/gateway-v2-instructions.md +113 -0
  72. package/src/gateway-v2-recovery.mjs +18 -0
  73. package/src/gateway-v2-report-links.mjs +14 -0
  74. package/src/gateway-v2-workflows.mjs +17 -0
  75. package/src/gateway-v2.mjs +179 -0
  76. package/src/oauth.mjs +402 -1949
  77. package/src/observability.mjs +19 -3
  78. package/src/result-presentation.mjs +9 -0
  79. package/src/runtime.mjs +24 -3670
  80. package/src/source-groups.mjs +91 -0
  81. package/src/source-value-format.mjs +25 -0
  82. package/src/tool-result.mjs +20 -0
  83. package/src/ui-bridge.mjs +185 -0
  84. package/src/well-known-routes.mjs +132 -0
  85. package/src/assets/wallet-accounts.mjs +0 -20513
  86. package/src/assets/walletconnect-provider.mjs +0 -6319
  87. package/src/discovery.mjs +0 -929
  88. package/src/external-fetch.mjs +0 -203
  89. package/src/funding-options.mjs +0 -255
  90. package/src/gateway-management.mjs +0 -107
  91. package/src/hosted-payment.mjs +0 -552
  92. package/src/hosted-wallets.mjs +0 -530
  93. package/src/listing-metadata.mjs +0 -287
  94. package/src/local-config.mjs +0 -256
  95. package/src/payment-guidance.mjs +0 -298
  96. package/src/publisher.mjs +0 -1288
  97. package/src/result-canvas.mjs +0 -16
  98. package/src/source-registry.mjs +0 -215
  99. package/src/wallet-store.mjs +0 -476
  100. package/src/x402-inspect.mjs +0 -361
@@ -0,0 +1,179 @@
1
+ import { addDossierDiscovery } from './gateway-v2-workflows.mjs';
2
+ import { attachReportLinks } from './gateway-v2-report-links.mjs';
3
+ import { planningRetryId, gatewayFailure, CLARIFICATION_GUIDANCE } from "./gateway-v2-recovery.mjs";
4
+ import { formatDisplayMoney } from "./display-money.mjs";
5
+ import { randomUUID } from "node:crypto";
6
+ import { readFileSync } from "node:fs";
7
+ import { AjvJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/ajv-provider.js";
8
+ import schemas from "./gateway-v2-contracts.json" with { type: "json" };
9
+ import { resolveConnectToken } from "./gateway-client.mjs";
10
+ import { content } from "./tool-result.mjs";
11
+ import { APIO_V2_CARD_URI, APIO_V2_CHATGPT_CARD_URI } from "./gateway-v2-card.mjs";
12
+ import { V2_RESULT_PRESENTATION, V2_RESULT_TOOL_DESCRIPTION, V2_SOURCES_PRESENTATION } from "./result-presentation.mjs";
13
+ import { GROUPED_SOURCES_TOOL_TEXT, presentSources, sourcesOutputSchema } from "./source-groups.mjs";
14
+ import { ASK_TOOLS, askDefinitions, askRequest, presentSearch } from "./gateway-v2-ask.mjs";
15
+
16
+ export const V2_INSTRUCTIONS = readFileSync(new URL('./gateway-v2-instructions.md', import.meta.url), 'utf8');
17
+ export const V2_DESCRIPTION = "Ask a data question, review one plan and total price ceiling, approve in the chat card within your connected account's spending limits, and receive source-backed results. Resume without buying the same work twice.";
18
+ export const V2_RESOURCE = { uri: "apiosk://v2/host-contract", name: "Apiosk v2 chatbot instructions", mimeType: "text/markdown" };
19
+ const failure = value => ({ ...content(value), isError: true });
20
+ const schemes = [{ type: "oauth2", scopes: ["mcp:tools"] }];
21
+ const displayCurrency = currency => !currency || currency === "USDC" ? "USD" : currency;
22
+ const errorFields = {
23
+ error_code: { type: "string" }, message: { type: "string" },
24
+ request_id: { type: "string", format: "uuid" }, idempotency_key: { type: "string", format: "uuid" },
25
+ recover_task_ref: { type: "string", format: "uuid" },
26
+ };
27
+ const sourcesOutput = sourcesOutputSchema(errorFields);
28
+ const actionOutput = {
29
+ type: "object", additionalProperties: false, required: ["action_id", "kind", "label", "requires_authorization", "input_schema"],
30
+ properties: { action_id: { type: "string", format: "uuid" }, kind: { type: "string" }, label: { type: "string" }, requires_authorization: { type: "boolean" }, input_schema: { type: "object" } },
31
+ };
32
+ const proposalOutput = {
33
+ type: "object", additionalProperties: false, required: ["label", "quote_ref", "price_status", "currency", "max_total_atomic", "expires_at", "approval_url", "steps", "step_details"],
34
+ properties: {
35
+ label: { type: "string" }, quote_ref: { type: "string", format: "uuid" }, price_status: { type: "string" }, currency: { type: "string" },
36
+ max_total_atomic: { type: "string", pattern: "^[0-9]+$" }, expires_at: { type: "string", format: "date-time" }, approval_url: { type: "string", format: "uri" },
37
+ steps: { type: "array", items: { type: "string" } }, step_details: { type: "array", items: { type: "object", additionalProperties: true } },
38
+ },
39
+ };
40
+ const taskOutput = {
41
+ type: "object", additionalProperties: false,
42
+ properties: {
43
+ protocol_version: { type: "string", const: "2" }, request_id: { type: "string", format: "uuid" },
44
+ status: { type: "string", enum: ["ready", "needs_input", "needs_selection", "requires_approval", "running", "cancelled", "succeeded", "partial", "unsupported", "state_conflict", "failed"] },
45
+ intent_ref: { type: ["string", "null"], format: "uuid" }, context_view: { type: "object", additionalProperties: true },
46
+ proposal: { anyOf: [proposalOutput, { type: "null" }] }, result: {}, billing: {},
47
+ next_actions: { type: "array", items: actionOutput }, state: { anyOf: [schemas.state, { type: "null" }] },
48
+ errors: { type: "array", items: { type: "object", additionalProperties: true } }, retry_after_ms: { type: "integer", minimum: 0 }, ...errorFields,
49
+ },
50
+ anyOf: [{ required: ["protocol_version", "request_id", "status", "context_view", "proposal", "result", "next_actions", "state", "errors"] }, { required: ["error_code", "message"] }],
51
+ };
52
+
53
+ export function createV2Runtime(options = {}) {
54
+ const env = options.env || process.env;
55
+ const base = new URL(env.APIOSK_GATEWAY_V2_URL);
56
+ if (base.username || base.password || base.search || base.hash || base.pathname !== '/' || !(base.protocol === "https:" || (base.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(base.hostname)))) {
57
+ throw new Error("APIOSK_GATEWAY_V2_URL requires an HTTPS origin or loopback HTTP without credentials, path or query.");
58
+ }
59
+ const publicBase = env.APIOSK_MCP_PUBLIC_BASE_URL || `http://localhost:${env.PORT || 3000}`;
60
+ const metadata = new URL('/.well-known/oauth-protected-resource/mcp', publicBase).href;
61
+ const authFailure = () => ({ ...failure({ error_code: 'unauthorized', message: 'Reconnect your Apiosk account, then recover the existing task.' }),
62
+ _meta: { 'mcp/www_authenticate': [`Bearer resource_metadata="${metadata}", error="invalid_token", error_description="Connect your Apiosk account to continue", scope="mcp:tools"`] } });
63
+ const discover = structuredClone(schemas.discover);
64
+ // Optional means omit it. Advertising null makes some chatbot models eagerly
65
+ // send nulls for every unused field, which weakens the wire contract.
66
+ discover.properties.state = schemas.state;
67
+ addDossierDiscovery(discover);
68
+ const execute = structuredClone(schemas.execute);
69
+ execute.properties.state = schemas.state;
70
+ const definitions = [
71
+ { name: "apiosk_sources", title: "Browse Apiosk sources", description: `Find published data sources by name, category, sector, tag or capability. Browsing is free and paginated. ${GROUPED_SOURCES_TOOL_TEXT} Recommend sources that match the person's need. Use only when the person asks to browse sources. Do not substitute a source list for a failed data request. Keep replies concise and never expose protocol fields or describe catalog endpoints as chatbot tools.`, inputSchema: schemas.sources, outputSchema: sourcesOutput, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false } },
72
+ { name: "apiosk_discover", title: "Plan a data request", description: "Start a NEW data question; preserve the user's wording, source, entity, period and requested deliverable. Use one call for multi-source supplier onboarding and due diligence, including ownership/controllers, filed accounts, VAT, directors/officers, screening, a combined PDF and a short summary. Never add latest, a year, freshness, a company number or a VAT number that was not requested or returned by a source. Returns one plan, total price ceiling or required clarification. No provider purchase. When approval_mode is chatbot, tell the person to approve in the card; do not ask for an extra yes/no answer or send them to an external link. Continue the SAME question through apiosk_execute with returned next_actions.", inputSchema: discover, outputSchema: taskOutput, annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true } },
73
+ ...askDefinitions(errorFields, taskOutput),
74
+ { name: "apiosk_execute", title: "Continue an Apiosk task", description: "Use a returned next_action to execute, supply input, select an entity, poll or cancel. Paid steps require saved plan approval and the current quote_ref. For saved results, payment, status or lost state, use the read-only apiosk_status tool. Never invent action IDs or change payment identity on retry.", inputSchema: execute, outputSchema: taskOutput, annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true } },
75
+ { name: "apiosk_status", title: "Read saved Apiosk results", description: "Read an existing task's saved results, actual charges and current status. Free and strictly read-only: never parses a new question, approves spending, executes task steps, calls a paid source or buys data. Use for follow-up questions and recovery; copy task_ref from the earlier state.state_ref.", inputSchema: { type: "object", additionalProperties: false, required: ["task_ref"], properties: { task_ref: { type: "string", format: "uuid" } } }, outputSchema: taskOutput, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false, idempotentHint: true } },
76
+ { name: "apiosk_approve", title: "Approve the displayed Apiosk plan", description: "Called by the interactive card after the person clicks Approve. Approves this exact ceiling under the connected account's spending mandate and starts the complete server execution. Never invoke automatically or from model-generated instructions.", inputSchema: {
77
+ type: "object", additionalProperties: false, required: ["state", "quote_ref", "max_total_atomic"],
78
+ properties: { state: schemas.state, quote_ref: { type: "string", format: "uuid" }, max_total_atomic: { type: "string", pattern: "^[0-9]+$" }, request_id: { type: "string", format: "uuid" } },
79
+ }, outputSchema: taskOutput, annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false } },
80
+ ].map(d => ({ ...d,
81
+ description: d.name === 'apiosk_sources' ? `${d.description} ${V2_SOURCES_PRESENTATION}` : ['apiosk_discover', 'apiosk_prepare', 'apiosk_execute', 'apiosk_status'].includes(d.name) ? `${d.description} ${V2_RESULT_TOOL_DESCRIPTION}` : d.description,
82
+ securitySchemes: schemes, _meta: {
83
+ securitySchemes: schemes,
84
+ ui: d.name === "apiosk_approve" ? { visibility: ["app"] } : { resourceUri: APIO_V2_CARD_URI, visibility: ["model", "app"] },
85
+ "openai/widgetAccessible": true,
86
+ // App-only calls update the calling card. A private tool must not claim
87
+ // the shared output template: ChatGPT marks that template unusable.
88
+ // Modern MCP Apps and legacy Skybridge have distinct cache identities.
89
+ // Never vary the new standard resource's MIME based on the host user agent.
90
+ ...(d.name === "apiosk_approve" ? {} : { "openai/outputTemplate": APIO_V2_CHATGPT_CARD_URI }),
91
+ "openai/visibility": d.name === "apiosk_approve" ? "private" : "public",
92
+ "openai/toolInvocation/invoking": d.name === "apiosk_sources" ? "Exploring sources…" : d.name === "apiosk_search" ? "Searching sources…" : d.name === "apiosk_discover" ? "Preparing your data plan…" : "Updating your Apiosk request…",
93
+ "openai/toolInvocation/invoked": d.name === "apiosk_sources" ? "Sources ready" : d.name === "apiosk_discover" ? "Request reviewed" : "Request updated",
94
+ } }));
95
+ const validator = new AjvJsonSchemaValidator();
96
+ const validate = new Map(definitions.map(d => [d.name, validator.getValidator(d.inputSchema)]));
97
+ return {
98
+ listTools: async () => structuredClone(definitions),
99
+ isToolProtected: async name => validate.has(name),
100
+ async callTool(name, args = {}, authInfo = null) {
101
+ if (!validate.has(name)) return failure({ error_code: "tool.unknown", message: "Use the advertised Apiosk tools." });
102
+ // Hosted sessions must never fall back to a machine-wide buyer credential.
103
+ const token = resolveConnectToken(authInfo, options.hostedAuthEnabled ? {} : env);
104
+ if (!token) return authFailure();
105
+ const cleanArgs = Object.fromEntries(Object.entries(args).filter(([, value]) => value !== null && value !== undefined));
106
+ if (!validate.get(name)(cleanArgs).valid) return failure({ error_code: 'invalid_arguments', message: 'Use the tool schema and copy the latest gateway-issued state and action. Recovery takes only recover_task_ref.' });
107
+ const recover = name === "apiosk_status" ? cleanArgs.task_ref : name === "apiosk_execute" && cleanArgs.recover_task_ref;
108
+ if (recover && name === "apiosk_execute" && Object.keys(cleanArgs).some(k => !['recover_task_ref', 'request_id'].includes(k))) return failure({ error_code: 'invalid_recovery', message: 'Recover using only recover_task_ref and an optional request_id.' });
109
+ const workflow = name === "apiosk_discover" && cleanArgs.workflow;
110
+ const ask = ASK_TOOLS.includes(name) && askRequest(name, cleanArgs), searching = name === "apiosk_search";
111
+ const body = ask ? ask.body : { ...(workflow ? { input: workflow.input } : cleanArgs), request_id: cleanArgs.request_id || randomUUID() };
112
+ if (name === "apiosk_execute" && !recover) body.idempotency_key ||= args.action_id;
113
+ const browsing = name === "apiosk_sources";
114
+ const path = ask ? ask.path : browsing ? "/v2/sources" : recover ? `/v2/tasks/${recover}` : workflow ? `/v2/workflows/${encodeURIComponent(workflow.slug)}/start` : name === "apiosk_discover" ? "/v2/discover" : name === "apiosk_approve" ? "/v2/approve" : "/v2/execute";
115
+ try {
116
+ const url = new URL(path, base);
117
+ if (browsing) for (const [key, value] of Object.entries(cleanArgs)) url.searchParams.set(key, String(value));
118
+ const request = async () => {
119
+ const response = await (options.fetchImpl || fetch)(url, {
120
+ method: recover || browsing ? "GET" : "POST", redirect: "error", headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
121
+ body: recover || browsing ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(80_000),
122
+ });
123
+ if (response.status === 401) { await response.body?.cancel(); return authFailure(); }
124
+ const reader = response.body?.getReader();
125
+ if (!reader) throw new Error('No response');
126
+ // A source page is trimmed after reading (presentSources); task views are read whole.
127
+ let bytes = 0; const chunks = []; const limit = (browsing ? 4096 : 1024) * 1024;
128
+ for (;;) { const { done, value } = await reader.read(); if (done) break; bytes += value.byteLength; if (bytes > limit) { await reader.cancel(); throw new Error('Response limit'); } chunks.push(value); }
129
+ const result = JSON.parse(Buffer.concat(chunks).toString("utf8"));
130
+ return { response, result };
131
+ };
132
+ let received = await request();
133
+ if (received?.isError) return received;
134
+ // Only an explicit idempotency conflict proves this input was not run.
135
+ // Never retry a timeout, an approval or a paid execution here.
136
+ if (name === "apiosk_discover" && cleanArgs.request_id && received.result?.errors?.some(e=>e.code==='request_conflict')) {
137
+ body.request_id = planningRetryId(body);
138
+ received = await request();
139
+ if (received?.isError) return received;
140
+ }
141
+ const {response} = received;
142
+ let {result} = received;
143
+ if (!response.ok) return failure(gatewayFailure(result, recover || args.state?.state_ref));
144
+ if (searching) result = presentSearch(result, cleanArgs.parsed_request);
145
+ else if (result?.protocol_version !== '2' || (browsing ? !Array.isArray(result.sources) : !Array.isArray(result.next_actions) || !Array.isArray(result.errors))) throw new Error('Unexpected protocol');
146
+ if (browsing) result = presentSources(result);
147
+ if (!browsing) {
148
+ result = { ...result,
149
+ ...(result.proposal && { proposal: { ...result.proposal, label: "Data request", currency: displayCurrency(result.proposal.currency) } }),
150
+ ...(result.billing && { billing: { ...result.billing, currency: displayCurrency(result.billing.currency) } }),
151
+ ...(result.result && typeof result.result === 'object' && !Array.isArray(result.result) && result.result.currency === 'USDC' && { result: { ...result.result, currency: 'USD' } }),
152
+ };
153
+ }
154
+ const eventsPath = result.context_view?.events_path;
155
+ if (typeof eventsPath === 'string' && eventsPath.startsWith(`/v2/tasks/${result.state?.state_ref}/events?`)) result.context_view.events_url = new URL(eventsPath, base).href;
156
+ attachReportLinks(result, base);
157
+ const reply = content(result);
158
+ // Include the presentation contract on every response: existing hosts
159
+ // may still have an older initialize/tool-description snapshot cached.
160
+ if (browsing) reply.content.push({ type: 'text', text: V2_SOURCES_PRESENTATION });
161
+ if (!browsing) {
162
+ const maximum = formatDisplayMoney(result.proposal?.max_total_atomic, result.proposal?.currency, result.context_view?.money_display, true);
163
+ const charged = formatDisplayMoney(result.billing?.total_charged, result.billing?.currency, result.context_view?.money_display);
164
+ const prices = [maximum && `Maximum total price: ${maximum}.`, charged && `Actual charge so far: ${charged}.`].filter(Boolean).join(' ');
165
+ if (prices) reply.content.unshift({ type: 'text', text: prices + (result.context_view?.money_display?.fallback_reason ? ' Display currency conversion is unavailable; amounts are shown in USD.' : '') });
166
+ }
167
+ if (result.status === 'needs_input') reply.content.push({type:'text',text:CLARIFICATION_GUIDANCE});
168
+ if (!browsing && result.state?.state_ref) reply.content.push({ type: "text", text: `This is a snapshot. The interactive card can approve and execute this task after this response. Before answering ANY later follow-up about its results, payment or status, recover current evidence by calling apiosk_status with ONLY {"task_ref":"${result.state.state_ref}"}. This read is free and never buys or approves. Never conclude that nothing was bought or saved from this earlier snapshot. Preserve source values exactly. Only report a currency or unit when the source explicitly supplies it; otherwise say it was not specified. The Apiosk billing currency does not establish the currency of the source data. ${V2_RESULT_PRESENTATION}` });
169
+ return reply;
170
+ } catch (error) {
171
+ // Transport errors may contain credential-bearing URLs or upstream text:
172
+ // log only a fixed reason code.
173
+ const reason = error?.message === 'Response limit' ? 'size' : error?.message === 'Unexpected protocol' ? 'protocol' : error instanceof SyntaxError ? 'json' : error?.name === 'TimeoutError' ? 'timeout' : 'transport';
174
+ console.error(JSON.stringify({ event: "gateway_response_rejected", tool: name, reason }));
175
+ return failure({ error_code: "gateway.unavailable", message: browsing ? "The source catalog is temporarily unavailable. Retry browsing shortly." : "The gateway response could not be confirmed. Recover the saved task before continuing.", request_id: body.request_id, idempotency_key: body.idempotency_key, recover_task_ref: recover || args.state?.state_ref || undefined });
176
+ }
177
+ },
178
+ };
179
+ }