@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.
- package/PRD-ANALYTICS-AGENTS-TEAMS.md +710 -0
- package/PRD-API-CHAINING.md +483 -0
- package/PRD-HARDEN-SHELL.md +18 -12
- package/PRD-LOGS-SUBAGENTS-V2.md +267 -0
- package/convex/_generated/api.d.ts +6 -0
- package/convex/agents.ts +188 -0
- package/convex/chains.ts +1248 -0
- package/convex/logs.ts +94 -0
- package/convex/schema.ts +139 -0
- package/convex/searchLogs.ts +141 -0
- package/convex/teams.ts +243 -0
- package/dist/chain-types.d.ts +187 -0
- package/dist/chain-types.d.ts.map +1 -0
- package/dist/chain-types.js +33 -0
- package/dist/chain-types.js.map +1 -0
- package/dist/chainExecutor.d.ts +122 -0
- package/dist/chainExecutor.d.ts.map +1 -0
- package/dist/chainExecutor.js +454 -0
- package/dist/chainExecutor.js.map +1 -0
- package/dist/chainResolver.d.ts +100 -0
- package/dist/chainResolver.d.ts.map +1 -0
- package/dist/chainResolver.js +519 -0
- package/dist/chainResolver.js.map +1 -0
- package/dist/chainResolver.test.d.ts +5 -0
- package/dist/chainResolver.test.d.ts.map +1 -0
- package/dist/chainResolver.test.js +201 -0
- package/dist/chainResolver.test.js.map +1 -0
- package/dist/execute.d.ts +4 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +3 -0
- package/dist/execute.js.map +1 -1
- package/dist/index.js +478 -3
- package/dist/index.js.map +1 -1
- package/docs/SUBAGENT-NAMING.md +94 -0
- package/landing/public/logos/chattgpt.svg +1 -0
- package/landing/public/logos/claude.svg +1 -0
- package/landing/public/logos/gemini.svg +1 -0
- package/landing/public/logos/grok.svg +1 -0
- package/landing/src/app/page.tsx +12 -21
- package/landing/src/app/workspace/chains/page.tsx +520 -0
- package/landing/src/app/workspace/page.tsx +1903 -224
- package/landing/src/components/AITestimonials.tsx +15 -9
- package/landing/src/components/ChainStepDetail.tsx +310 -0
- package/landing/src/components/ChainTrace.tsx +261 -0
- package/landing/src/lib/stats.json +1 -1
- package/package.json +14 -2
- package/src/chainExecutor.ts +730 -0
- package/src/chainResolver.test.ts +246 -0
- package/src/chainResolver.ts +658 -0
- package/src/execute.ts +23 -0
- 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:
|
|
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: [
|
|
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
|
-
|
|
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: [
|