@nordsym/apiclaw 1.3.13 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/PRD-ANALYTICS-AGENTS-TEAMS.md +710 -0
  2. package/PRD-API-CHAINING.md +483 -0
  3. package/PRD-HARDEN-SHELL.md +18 -12
  4. package/PRD-LOGS-SUBAGENTS-V2.md +267 -0
  5. package/convex/_generated/api.d.ts +6 -0
  6. package/convex/agents.ts +188 -0
  7. package/convex/chains.ts +1248 -0
  8. package/convex/logs.ts +94 -0
  9. package/convex/schema.ts +139 -0
  10. package/convex/searchLogs.ts +141 -0
  11. package/convex/teams.ts +243 -0
  12. package/dist/chain-types.d.ts +187 -0
  13. package/dist/chain-types.d.ts.map +1 -0
  14. package/dist/chain-types.js +33 -0
  15. package/dist/chain-types.js.map +1 -0
  16. package/dist/chainExecutor.d.ts +122 -0
  17. package/dist/chainExecutor.d.ts.map +1 -0
  18. package/dist/chainExecutor.js +454 -0
  19. package/dist/chainExecutor.js.map +1 -0
  20. package/dist/chainResolver.d.ts +100 -0
  21. package/dist/chainResolver.d.ts.map +1 -0
  22. package/dist/chainResolver.js +519 -0
  23. package/dist/chainResolver.js.map +1 -0
  24. package/dist/chainResolver.test.d.ts +5 -0
  25. package/dist/chainResolver.test.d.ts.map +1 -0
  26. package/dist/chainResolver.test.js +201 -0
  27. package/dist/chainResolver.test.js.map +1 -0
  28. package/dist/execute.d.ts +4 -1
  29. package/dist/execute.d.ts.map +1 -1
  30. package/dist/execute.js +3 -0
  31. package/dist/execute.js.map +1 -1
  32. package/dist/index.js +478 -3
  33. package/dist/index.js.map +1 -1
  34. package/docs/SUBAGENT-NAMING.md +94 -0
  35. package/landing/public/logos/chattgpt.svg +1 -0
  36. package/landing/public/logos/claude.svg +1 -0
  37. package/landing/public/logos/gemini.svg +1 -0
  38. package/landing/public/logos/grok.svg +1 -0
  39. package/landing/src/app/page.tsx +12 -21
  40. package/landing/src/app/workspace/chains/page.tsx +520 -0
  41. package/landing/src/app/workspace/page.tsx +1903 -224
  42. package/landing/src/components/AITestimonials.tsx +15 -9
  43. package/landing/src/components/ChainStepDetail.tsx +310 -0
  44. package/landing/src/components/ChainTrace.tsx +261 -0
  45. package/landing/src/lib/stats.json +1 -1
  46. package/package.json +14 -2
  47. package/src/chainExecutor.ts +730 -0
  48. package/src/chainResolver.test.ts +246 -0
  49. package/src/chainResolver.ts +658 -0
  50. package/src/execute.ts +23 -0
  51. package/src/index.ts +524 -3
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ import { readSession, writeSession, clearSession, getMachineFingerprint } from '
26
26
  import { ConvexHttpClient } from 'convex/browser';
27
27
  import { getOrCreateCustomer, createMeteredCheckoutSession, getUsageSummary, METERED_BILLING } from './stripe.js';
28
28
  import { estimateCost } from './metered.js';
29
+ import { executeChain, getChainStatus, resumeChain } from './chainExecutor.js';
29
30
  // Default agent ID for MVP (in production, this would come from auth)
30
31
  const DEFAULT_AGENT_ID = 'agent_default';
31
32
  // Convex client for workspace management
@@ -178,6 +179,14 @@ const tools = [
178
179
  region: {
179
180
  type: 'string',
180
181
  description: 'Filter by region (e.g., "SE", "EU", "global")'
182
+ },
183
+ subagent_id: {
184
+ type: 'string',
185
+ description: 'Optional subagent identifier for multi-agent tracking'
186
+ },
187
+ ai_backend: {
188
+ type: 'string',
189
+ description: 'AI backend making this request (e.g., "claude-3-sonnet", "gpt-4"). Used for analytics.'
181
190
  }
182
191
  },
183
192
  required: ['query']
@@ -265,10 +274,28 @@ const tools = [
265
274
  },
266
275
  {
267
276
  name: 'call_api',
268
- description: 'Execute an API call through APIClaw. For actions that cost money (invoices, SMS), you will get a preview first and must confirm with the returned token. For free actions, executes immediately.',
277
+ description: `Execute an API call through APIClaw. Supports single calls AND multi-step chains.
278
+
279
+ SINGLE CALL: Provide provider + action + params
280
+ CHAIN: Provide chain array to execute multiple APIs in sequence/parallel with cross-step references.
281
+
282
+ Chain features:
283
+ - Sequential: Steps execute in order, each can reference previous results via $stepId.property
284
+ - Parallel: Use { parallel: [...steps] } to run concurrently
285
+ - Conditional: Use { if: "$step.success", then: {...}, else: {...} }
286
+ - Loops: Use { forEach: "$step.results", as: "item", do: {...} }
287
+ - Error handling: Per-step retry/fallback via onError
288
+ - Async: Set async: true to get chainId immediately, poll or use webhook
289
+
290
+ Example chain:
291
+ chain: [
292
+ { id: "search", provider: "brave_search", action: "search", params: { query: "AI agents" } },
293
+ { id: "summarize", provider: "openrouter", action: "chat", params: { message: "Summarize: $search.results" } }
294
+ ]`,
269
295
  inputSchema: {
270
296
  type: 'object',
271
297
  properties: {
298
+ // Single call params
272
299
  provider: {
273
300
  type: 'string',
274
301
  description: 'Provider ID (e.g., "46elks", "brave_search", "resend", "openrouter", "elevenlabs", "twilio", "coaccept", "frankfurter")'
@@ -292,9 +319,70 @@ const tools = [
292
319
  dry_run: {
293
320
  type: 'boolean',
294
321
  description: 'If true, shows what WOULD be sent without making actual API calls. Returns mock response and request details. Great for testing and debugging.'
322
+ },
323
+ // Chain execution params
324
+ chain: {
325
+ type: 'array',
326
+ description: 'Execute multiple API calls as a single chain. Each step can reference previous results via $stepId.property',
327
+ items: {
328
+ type: 'object',
329
+ properties: {
330
+ id: { type: 'string', description: 'Step identifier for cross-step references' },
331
+ provider: { type: 'string', description: 'API provider' },
332
+ action: { type: 'string', description: 'Action to execute' },
333
+ params: { type: 'object', description: 'Action parameters. Use $stepId.path for references.' },
334
+ parallel: { type: 'array', description: 'Steps to run in parallel' },
335
+ if: { type: 'string', description: 'Condition for conditional execution (e.g., "$step1.success")' },
336
+ then: { type: 'object', description: 'Step to execute if condition is true' },
337
+ else: { type: 'object', description: 'Step to execute if condition is false' },
338
+ forEach: { type: 'string', description: 'Array reference to iterate (e.g., "$search.results")' },
339
+ as: { type: 'string', description: 'Variable name for current item in loop' },
340
+ do: { type: 'object', description: 'Step to execute for each item' },
341
+ onError: {
342
+ type: 'object',
343
+ description: 'Error handling configuration',
344
+ properties: {
345
+ retry: {
346
+ type: 'object',
347
+ properties: {
348
+ attempts: { type: 'number', description: 'Max retry attempts' },
349
+ backoff: { type: 'string', description: '"exponential" or "linear" or array of ms delays' }
350
+ }
351
+ },
352
+ fallback: { type: 'object', description: 'Fallback step if this fails' },
353
+ abort: { type: 'boolean', description: 'Abort entire chain on failure' }
354
+ }
355
+ }
356
+ }
357
+ }
358
+ },
359
+ // Chain options
360
+ continueOnError: {
361
+ type: 'boolean',
362
+ description: 'Continue chain execution even if a step fails (default: false)'
363
+ },
364
+ timeout: {
365
+ type: 'number',
366
+ description: 'Maximum execution time for the entire chain in milliseconds'
367
+ },
368
+ async: {
369
+ type: 'boolean',
370
+ description: 'Return immediately with chainId. Use get_chain_status to poll or provide webhook.'
371
+ },
372
+ webhook: {
373
+ type: 'string',
374
+ description: 'URL to POST results when async chain completes'
375
+ },
376
+ subagent_id: {
377
+ type: 'string',
378
+ description: 'Optional subagent identifier for multi-agent tracking'
379
+ },
380
+ ai_backend: {
381
+ type: 'string',
382
+ description: 'AI backend making this request (e.g., "claude-3-sonnet", "gpt-4"). Used for analytics.'
295
383
  }
296
384
  },
297
- required: ['provider', 'action']
385
+ required: []
298
386
  }
299
387
  },
300
388
  {
@@ -332,6 +420,14 @@ const tools = [
332
420
  preferredProvider: { type: 'string', description: 'Hint to prefer a specific provider' },
333
421
  fallback: { type: 'boolean', description: 'Enable fallback to other providers (default: true)' }
334
422
  }
423
+ },
424
+ subagent_id: {
425
+ type: 'string',
426
+ description: 'Optional subagent identifier for multi-agent tracking'
427
+ },
428
+ ai_backend: {
429
+ type: 'string',
430
+ description: 'AI backend making this request (e.g., "claude-3-sonnet", "gpt-4"). Used for analytics.'
335
431
  }
336
432
  },
337
433
  required: ['capability', 'action', 'params']
@@ -430,6 +526,46 @@ const tools = [
430
526
  },
431
527
  required: ['call_count']
432
528
  }
529
+ },
530
+ // ============================================
531
+ // CHAIN MANAGEMENT TOOLS
532
+ // ============================================
533
+ {
534
+ name: 'get_chain_status',
535
+ description: 'Check the status of an async chain execution. Use the chainId returned from call_api with async: true.',
536
+ inputSchema: {
537
+ type: 'object',
538
+ properties: {
539
+ chain_id: {
540
+ type: 'string',
541
+ description: 'Chain ID returned from async chain execution'
542
+ }
543
+ },
544
+ required: ['chain_id']
545
+ }
546
+ },
547
+ {
548
+ name: 'resume_chain',
549
+ description: 'Resume a failed chain from the point of failure. Use the resumeToken from the error response. Requires the original chain definition.',
550
+ inputSchema: {
551
+ type: 'object',
552
+ properties: {
553
+ resume_token: {
554
+ type: 'string',
555
+ description: 'Resume token from a failed chain (e.g., "chain_xyz_step_2")'
556
+ },
557
+ original_chain: {
558
+ type: 'array',
559
+ description: 'The original chain definition that failed. Required to resume execution.',
560
+ items: { type: 'object' }
561
+ },
562
+ overrides: {
563
+ type: 'object',
564
+ description: 'Optional parameter overrides for specific steps. Format: { "stepId": { ...newParams } }'
565
+ }
566
+ },
567
+ required: ['resume_token', 'original_chain']
568
+ }
433
569
  }
434
570
  ];
435
571
  // Create server
@@ -494,9 +630,46 @@ Docs: https://apiclaw.nordsym.com
494
630
  const category = args?.category;
495
631
  const maxResults = args?.max_results || 5;
496
632
  const region = args?.region;
633
+ const subagentId = args?.subagent_id;
634
+ const aiBackend = args?.ai_backend;
497
635
  const startTime = Date.now();
498
636
  const results = discoverAPIs(query, { category, maxResults, region });
499
- trackSearch(query, results.length, Date.now() - startTime);
637
+ const responseTimeMs = Date.now() - startTime;
638
+ trackSearch(query, results.length, responseTimeMs);
639
+ // Log search to Convex for analytics
640
+ if (workspaceContext?.sessionToken) {
641
+ const searchLogPayload = {
642
+ path: 'searchLogs:log',
643
+ args: {
644
+ sessionToken: workspaceContext.sessionToken,
645
+ subagentId: subagentId || undefined,
646
+ query,
647
+ resultCount: results.length,
648
+ matchedProviders: results.slice(0, 10).map(r => r.provider.id),
649
+ responseTimeMs,
650
+ },
651
+ };
652
+ fetch('https://agile-crane-840.convex.cloud/api/mutation', {
653
+ method: 'POST',
654
+ headers: { 'Content-Type': 'application/json' },
655
+ body: JSON.stringify(searchLogPayload),
656
+ }).catch(() => { }); // Fire and forget
657
+ }
658
+ // Update AI backend tracking if provided
659
+ if (aiBackend && workspaceContext?.sessionToken) {
660
+ fetch('https://agile-crane-840.convex.cloud/api/mutation', {
661
+ method: 'POST',
662
+ headers: { 'Content-Type': 'application/json' },
663
+ body: JSON.stringify({
664
+ path: 'agents:updateAIBackend',
665
+ args: {
666
+ token: workspaceContext.sessionToken,
667
+ subagentId: subagentId || undefined,
668
+ aiBackend,
669
+ },
670
+ }),
671
+ }).catch(() => { }); // Fire and forget
672
+ }
500
673
  if (results.length === 0) {
501
674
  return {
502
675
  content: [
@@ -691,6 +864,135 @@ Docs: https://apiclaw.nordsym.com
691
864
  const params = args?.params || {};
692
865
  const confirmToken = args?.confirm_token;
693
866
  const dryRun = args?.dry_run;
867
+ const chain = args?.chain;
868
+ const subagentId = args?.subagent_id;
869
+ const aiBackend = args?.ai_backend;
870
+ // Track AI backend if provided
871
+ if (aiBackend && workspaceContext?.sessionToken) {
872
+ fetch('https://agile-crane-840.convex.cloud/api/mutation', {
873
+ method: 'POST',
874
+ headers: { 'Content-Type': 'application/json' },
875
+ body: JSON.stringify({
876
+ path: 'agents:updateAIBackend',
877
+ args: {
878
+ token: workspaceContext.sessionToken,
879
+ subagentId: subagentId || undefined,
880
+ aiBackend,
881
+ },
882
+ }),
883
+ }).catch(() => { }); // Fire and forget
884
+ }
885
+ // ============================================
886
+ // CHAIN EXECUTION MODE
887
+ // ============================================
888
+ if (chain && Array.isArray(chain) && chain.length > 0) {
889
+ // Check workspace access for chains
890
+ const access = checkWorkspaceAccess();
891
+ if (!access.allowed) {
892
+ return {
893
+ content: [{
894
+ type: 'text',
895
+ text: JSON.stringify({
896
+ status: 'error',
897
+ error: access.error,
898
+ hint: 'Use register_owner to authenticate your workspace.',
899
+ }, null, 2)
900
+ }],
901
+ isError: true
902
+ };
903
+ }
904
+ try {
905
+ // Construct ChainDefinition from the input
906
+ const chainDefinition = {
907
+ steps: chain,
908
+ timeout: args?.timeout,
909
+ errorPolicy: args?.continueOnError
910
+ ? { mode: 'best-effort' }
911
+ : { mode: 'fail-fast' },
912
+ };
913
+ const chainCredentials = {
914
+ userId: DEFAULT_AGENT_ID,
915
+ customerKeys: {},
916
+ };
917
+ // Add customer key if provided
918
+ const customerKey = args?.customer_key;
919
+ if (customerKey) {
920
+ // Apply to all providers (or could be provider-specific)
921
+ chainCredentials.customerKeys = { default: customerKey };
922
+ }
923
+ const chainOptions = {
924
+ verbose: false,
925
+ };
926
+ // Execute the chain
927
+ const chainResult = await executeChain(chainDefinition, chainCredentials, {}, // inputs
928
+ chainOptions);
929
+ // Track usage for chain (count completed steps)
930
+ if (chainResult.success && workspaceContext) {
931
+ const completedCount = chainResult.completedSteps.length;
932
+ for (let i = 0; i < completedCount; i++) {
933
+ try {
934
+ await convex.mutation("workspaces:incrementUsage", {
935
+ workspaceId: workspaceContext.workspaceId,
936
+ });
937
+ }
938
+ catch (e) {
939
+ console.error('[APIClaw] Failed to track chain usage:', e);
940
+ }
941
+ }
942
+ }
943
+ // Format response to match expected chain response format
944
+ return {
945
+ content: [{
946
+ type: 'text',
947
+ text: JSON.stringify({
948
+ status: chainResult.success ? 'success' : 'error',
949
+ mode: 'chain',
950
+ chainId: chainResult.chainId,
951
+ steps: chainResult.trace.map(t => ({
952
+ id: t.stepId,
953
+ status: t.success ? 'completed' : 'failed',
954
+ result: t.output,
955
+ error: t.error,
956
+ latencyMs: t.latencyMs,
957
+ cost: t.cost,
958
+ })),
959
+ finalResult: chainResult.finalResult,
960
+ totalLatencyMs: chainResult.totalLatencyMs,
961
+ totalCost: chainResult.totalCost,
962
+ tokensSaved: (chain.length - 1) * 500, // Estimate tokens saved
963
+ ...(chainResult.error ? {
964
+ completedSteps: chainResult.completedSteps,
965
+ failedStep: chainResult.failedStep ? {
966
+ id: chainResult.failedStep.stepId,
967
+ error: chainResult.failedStep.error,
968
+ code: chainResult.failedStep.errorCode,
969
+ } : undefined,
970
+ partialResults: chainResult.results,
971
+ canResume: chainResult.canResume,
972
+ resumeToken: chainResult.resumeToken,
973
+ } : {}),
974
+ }, null, 2)
975
+ }],
976
+ isError: !chainResult.success
977
+ };
978
+ }
979
+ catch (error) {
980
+ return {
981
+ content: [{
982
+ type: 'text',
983
+ text: JSON.stringify({
984
+ status: 'error',
985
+ mode: 'chain',
986
+ error: error instanceof Error ? error.message : String(error),
987
+ }, null, 2)
988
+ }],
989
+ isError: true
990
+ };
991
+ }
992
+ }
993
+ // ============================================
994
+ // SINGLE CALL MODE (existing logic)
995
+ // ============================================
694
996
  // Handle dry-run mode - no actual API calls, just show what would happen
695
997
  if (dryRun) {
696
998
  const { generateDryRun } = await import('./execute.js');
@@ -905,6 +1207,23 @@ Docs: https://apiclaw.nordsym.com
905
1207
  const action = args?.action;
906
1208
  const params = args?.params || {};
907
1209
  const preferences = args?.preferences || {};
1210
+ const subagentId = args?.subagent_id;
1211
+ const aiBackend = args?.ai_backend;
1212
+ // Track AI backend if provided
1213
+ if (aiBackend && workspaceContext?.sessionToken) {
1214
+ fetch('https://agile-crane-840.convex.cloud/api/mutation', {
1215
+ method: 'POST',
1216
+ headers: { 'Content-Type': 'application/json' },
1217
+ body: JSON.stringify({
1218
+ path: 'agents:updateAIBackend',
1219
+ args: {
1220
+ token: workspaceContext.sessionToken,
1221
+ subagentId: subagentId || undefined,
1222
+ aiBackend,
1223
+ },
1224
+ }),
1225
+ }).catch(() => { }); // Fire and forget
1226
+ }
908
1227
  // Check if capability exists
909
1228
  const exists = await hasCapability(capabilityId);
910
1229
  if (!exists) {
@@ -1323,6 +1642,162 @@ Docs: https://apiclaw.nordsym.com
1323
1642
  }]
1324
1643
  };
1325
1644
  }
1645
+ // ============================================
1646
+ // CHAIN MANAGEMENT TOOLS
1647
+ // ============================================
1648
+ case 'get_chain_status': {
1649
+ const chainId = args?.chain_id;
1650
+ if (!chainId) {
1651
+ return {
1652
+ content: [{
1653
+ type: 'text',
1654
+ text: JSON.stringify({
1655
+ status: 'error',
1656
+ error: 'chain_id is required'
1657
+ }, null, 2)
1658
+ }],
1659
+ isError: true
1660
+ };
1661
+ }
1662
+ const chainStatus = await getChainStatus(chainId);
1663
+ if (chainStatus.status === 'not_found') {
1664
+ return {
1665
+ content: [{
1666
+ type: 'text',
1667
+ text: JSON.stringify({
1668
+ status: 'error',
1669
+ error: `Chain not found: ${chainId}`,
1670
+ hint: 'Chain states expire after 1 hour. The chain may have completed or expired.'
1671
+ }, null, 2)
1672
+ }],
1673
+ isError: true
1674
+ };
1675
+ }
1676
+ return {
1677
+ content: [{
1678
+ type: 'text',
1679
+ text: JSON.stringify({
1680
+ status: 'success',
1681
+ chain: {
1682
+ chainId: chainStatus.chainId,
1683
+ executionStatus: chainStatus.status,
1684
+ ...(chainStatus.result ? {
1685
+ result: {
1686
+ success: chainStatus.result.success,
1687
+ completedSteps: chainStatus.result.completedSteps,
1688
+ totalLatencyMs: chainStatus.result.totalLatencyMs,
1689
+ totalCost: chainStatus.result.totalCost,
1690
+ finalResult: chainStatus.result.finalResult,
1691
+ error: chainStatus.result.error,
1692
+ canResume: chainStatus.result.canResume,
1693
+ resumeToken: chainStatus.result.resumeToken,
1694
+ }
1695
+ } : {})
1696
+ }
1697
+ }, null, 2)
1698
+ }]
1699
+ };
1700
+ }
1701
+ case 'resume_chain': {
1702
+ const resumeToken = args?.resume_token;
1703
+ const overrides = args?.overrides;
1704
+ const originalChain = args?.original_chain;
1705
+ if (!resumeToken) {
1706
+ return {
1707
+ content: [{
1708
+ type: 'text',
1709
+ text: JSON.stringify({
1710
+ status: 'error',
1711
+ error: 'resume_token is required'
1712
+ }, null, 2)
1713
+ }],
1714
+ isError: true
1715
+ };
1716
+ }
1717
+ // Check workspace access
1718
+ const access = checkWorkspaceAccess();
1719
+ if (!access.allowed) {
1720
+ return {
1721
+ content: [{
1722
+ type: 'text',
1723
+ text: JSON.stringify({
1724
+ status: 'error',
1725
+ error: access.error,
1726
+ hint: 'Use register_owner to authenticate your workspace.',
1727
+ }, null, 2)
1728
+ }],
1729
+ isError: true
1730
+ };
1731
+ }
1732
+ try {
1733
+ // Note: The resume_chain function requires the original chain definition
1734
+ // In practice, you'd store this or require the caller to provide it
1735
+ if (!originalChain) {
1736
+ return {
1737
+ content: [{
1738
+ type: 'text',
1739
+ text: JSON.stringify({
1740
+ status: 'error',
1741
+ error: 'original_chain is required to resume. Please provide the original chain definition.',
1742
+ hint: 'Pass original_chain: [...] with the same chain array used in the failed execution.'
1743
+ }, null, 2)
1744
+ }],
1745
+ isError: true
1746
+ };
1747
+ }
1748
+ const chainDefinition = {
1749
+ steps: originalChain,
1750
+ };
1751
+ const chainCredentials = {
1752
+ userId: DEFAULT_AGENT_ID,
1753
+ customerKeys: {},
1754
+ };
1755
+ const customerKey = args?.customer_key;
1756
+ if (customerKey) {
1757
+ chainCredentials.customerKeys = { default: customerKey };
1758
+ }
1759
+ const result = await resumeChain(resumeToken, chainDefinition, chainCredentials, {}, // inputs
1760
+ overrides, { verbose: false });
1761
+ return {
1762
+ content: [{
1763
+ type: 'text',
1764
+ text: JSON.stringify({
1765
+ status: result.success ? 'success' : 'error',
1766
+ mode: 'chain_resumed',
1767
+ chainId: result.chainId,
1768
+ steps: result.trace.map(t => ({
1769
+ id: t.stepId,
1770
+ status: t.success ? 'completed' : 'failed',
1771
+ result: t.output,
1772
+ error: t.error,
1773
+ latencyMs: t.latencyMs,
1774
+ })),
1775
+ finalResult: result.finalResult,
1776
+ totalLatencyMs: result.totalLatencyMs,
1777
+ totalCost: result.totalCost,
1778
+ ...(result.error ? {
1779
+ error: result.error,
1780
+ canResume: result.canResume,
1781
+ resumeToken: result.resumeToken,
1782
+ } : {}),
1783
+ }, null, 2)
1784
+ }],
1785
+ isError: !result.success
1786
+ };
1787
+ }
1788
+ catch (error) {
1789
+ return {
1790
+ content: [{
1791
+ type: 'text',
1792
+ text: JSON.stringify({
1793
+ status: 'error',
1794
+ error: error instanceof Error ? error.message : String(error),
1795
+ }, null, 2)
1796
+ }],
1797
+ isError: true
1798
+ };
1799
+ }
1800
+ }
1326
1801
  default:
1327
1802
  return {
1328
1803
  content: [