@desplega.ai/agent-swarm 1.93.0 → 1.95.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/openapi.json +180 -1
- package/package.json +4 -3
- package/src/be/db.ts +74 -9
- package/src/be/migrations/090_model_tiers.sql +2 -0
- package/src/be/migrations/091_seed_swarm_operations_metrics.sql +12 -0
- package/src/be/migrations/092_metrics_dashboard_combobox_filters.sql +68 -0
- package/src/be/migrations/093_slack_message_tracking.sql +6 -0
- package/src/be/migrations/094_mcp_extra_authorize_params.sql +4 -0
- package/src/be/migrations/runner.ts +52 -0
- package/src/be/modelsdev-cache.json +2060 -198
- package/src/be/scripts/boot-reembed.ts +74 -0
- package/src/be/scripts/db.ts +19 -3
- package/src/be/seed/index.ts +1 -1
- package/src/be/seed/registry.ts +2 -2
- package/src/be/seed/runner.ts +5 -5
- package/src/be/seed/types.ts +6 -1
- package/src/be/seed-pricing.ts +1 -0
- package/src/be/seed-scripts/index.ts +3 -2
- package/src/be/skill-sync.ts +4 -4
- package/src/be/swarm-config-guard.ts +8 -0
- package/src/commands/provider-credentials.ts +14 -8
- package/src/commands/runner.ts +84 -13
- package/src/http/index.ts +13 -2
- package/src/http/mcp-oauth.ts +14 -0
- package/src/http/metrics.ts +55 -6
- package/src/http/schedules.ts +16 -15
- package/src/http/script-runs.ts +7 -1
- package/src/http/scripts.ts +147 -1
- package/src/http/tasks.ts +7 -0
- package/src/model-tiers.ts +140 -0
- package/src/oauth/mcp-wrapper.ts +14 -0
- package/src/providers/claude-managed-models.ts +9 -0
- package/src/providers/codex-skill-resolver.ts +22 -8
- package/src/providers/opencode-adapter.ts +21 -2
- package/src/providers/pi-mono-adapter.ts +143 -26
- package/src/providers/types.ts +12 -0
- package/src/scheduler/scheduler.ts +22 -34
- package/src/server-user.ts +8 -2
- package/src/slack/responses.ts +39 -11
- package/src/slack/watcher.ts +121 -8
- package/src/tests/agents-list-model-display.test.ts +13 -0
- package/src/tests/aws-error-classifier.test.ts +148 -0
- package/src/tests/claude-managed-adapter.test.ts +12 -0
- package/src/tests/context-window.test.ts +7 -0
- package/src/tests/credential-check.test.ts +185 -46
- package/src/tests/harness-provider-resolution.test.ts +23 -0
- package/src/tests/http-api-integration.test.ts +19 -0
- package/src/tests/mcp-oauth-queries.test.ts +71 -1
- package/src/tests/mcp-oauth-wrapper.test.ts +109 -0
- package/src/tests/metrics-http.test.ts +137 -3
- package/src/tests/migration-046-budgets.test.ts +33 -0
- package/src/tests/migration-runner-regressions.test.ts +69 -0
- package/src/tests/model-control.test.ts +162 -46
- package/src/tests/opencode-adapter.test.ts +38 -1
- package/src/tests/pi-mono-adapter.test.ts +319 -0
- package/src/tests/provider-command-format.test.ts +12 -0
- package/src/tests/providers/pi-cost.test.ts +9 -0
- package/src/tests/runner-fallback-output.test.ts +50 -0
- package/src/tests/scripts-boot-reembed.test.ts +163 -0
- package/src/tests/scripts-embeddings.test.ts +90 -0
- package/src/tests/seed.test.ts +26 -1
- package/src/tests/session-costs-model-key-normalize.test.ts +2 -0
- package/src/tests/skill-fs-writer.test.ts +7 -1
- package/src/tests/skill-sync.test.ts +15 -3
- package/src/tests/slack-watcher.test.ts +66 -0
- package/src/tests/workflow-agent-task.test.ts +5 -2
- package/src/tests/workflow-validation-port-routing.test.ts +181 -0
- package/src/tools/mcp-servers/mcp-server-create.ts +7 -0
- package/src/tools/mcp-servers/mcp-server-update.ts +8 -0
- package/src/tools/memory-get.ts +11 -0
- package/src/tools/memory-search.ts +18 -0
- package/src/tools/schedules/create-schedule.ts +71 -70
- package/src/tools/schedules/update-schedule.ts +43 -31
- package/src/tools/send-task.ts +16 -5
- package/src/tools/task-action.ts +11 -3
- package/src/types.ts +30 -0
- package/src/utils/aws-error-classifier.ts +97 -0
- package/src/utils/context-window.ts +2 -0
- package/src/utils/credentials.test.ts +68 -0
- package/src/utils/credentials.ts +44 -3
- package/src/utils/pretty-print.ts +25 -10
- package/src/utils/skill-fs-writer.ts +11 -3
- package/src/workflows/engine.ts +3 -2
- package/src/workflows/executors/agent-task.ts +3 -1
package/src/slack/watcher.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
getInProgressSlackTasks,
|
|
6
6
|
getTaskAttachments,
|
|
7
7
|
getTaskById,
|
|
8
|
+
setSlackMessageTracking,
|
|
8
9
|
} from "../be/db";
|
|
9
10
|
import type { AgentTask } from "../types";
|
|
10
11
|
import { getSlackApp } from "./app";
|
|
@@ -76,6 +77,15 @@ export function registerTreeMessage(
|
|
|
76
77
|
// Also register in legacy flat map so existing watcher processing still works
|
|
77
78
|
taskMessages.set(taskId, { channelId, threadTs, messageTs });
|
|
78
79
|
|
|
80
|
+
try {
|
|
81
|
+
setSlackMessageTracking(taskId, {
|
|
82
|
+
slackProgressMessageTs: messageTs,
|
|
83
|
+
slackTreeRootMessageTs: messageTs,
|
|
84
|
+
});
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error(`[Slack] Failed to persist message tracking for task ${taskId}:`, error);
|
|
87
|
+
}
|
|
88
|
+
|
|
79
89
|
console.log(`[Slack] Registered task ${taskId.slice(0, 8)} in tree message ${messageTs}`);
|
|
80
90
|
}
|
|
81
91
|
|
|
@@ -193,6 +203,12 @@ export function _getTreeMessages(): Map<string, TreeMessageState> {
|
|
|
193
203
|
export function _getTaskToTree(): Map<string, string> {
|
|
194
204
|
return taskToTree;
|
|
195
205
|
}
|
|
206
|
+
export function _getTaskMessages(): Map<
|
|
207
|
+
string,
|
|
208
|
+
{ channelId: string; threadTs: string; messageTs: string }
|
|
209
|
+
> {
|
|
210
|
+
return taskMessages;
|
|
211
|
+
}
|
|
196
212
|
export function _getLastRenderedTree(): Map<string, string> {
|
|
197
213
|
return lastRenderedTree;
|
|
198
214
|
}
|
|
@@ -306,13 +322,35 @@ export async function processTreeMessages(): Promise<void> {
|
|
|
306
322
|
: `Tasks in progress: ${rootNames}`;
|
|
307
323
|
|
|
308
324
|
// Update the Slack message
|
|
309
|
-
const
|
|
325
|
+
const result = await updateTreeMessage(tree.channelId, messageTs, blocks, fallbackText);
|
|
326
|
+
const success = result === "ok";
|
|
310
327
|
if (success) {
|
|
311
328
|
lastRenderedTree.set(messageTs, serialized);
|
|
312
329
|
treeLastUpdateTime.set(messageTs, now);
|
|
313
330
|
console.log(
|
|
314
331
|
`[Slack] Updated tree message ${messageTs} (${nodes.length} root(s), terminal=${fullyTerminal})`,
|
|
315
332
|
);
|
|
333
|
+
} else if (result === "not_found") {
|
|
334
|
+
const taskIds = Array.from(tree.rootTaskIds);
|
|
335
|
+
for (const taskId of taskIds) {
|
|
336
|
+
taskToTree.delete(taskId);
|
|
337
|
+
taskMessages.delete(taskId);
|
|
338
|
+
try {
|
|
339
|
+
setSlackMessageTracking(taskId, {
|
|
340
|
+
slackProgressMessageTs: null,
|
|
341
|
+
slackTreeRootMessageTs: null,
|
|
342
|
+
});
|
|
343
|
+
} catch (error) {
|
|
344
|
+
console.error(`[Slack] Failed to clear stale message tracking for ${taskId}:`, error);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
treeMessages.delete(messageTs);
|
|
348
|
+
lastRenderedTree.delete(messageTs);
|
|
349
|
+
treeLastUpdateTime.delete(messageTs);
|
|
350
|
+
console.warn(
|
|
351
|
+
`[Slack] Dropped stale tree ${messageTs} (${taskIds.length} task(s)); will repost on next tick`,
|
|
352
|
+
);
|
|
353
|
+
continue;
|
|
316
354
|
}
|
|
317
355
|
|
|
318
356
|
// DM channels: set assistant status in parallel for typing indicator UX
|
|
@@ -437,6 +475,50 @@ export function startTaskWatcher(intervalMs = 3000): void {
|
|
|
437
475
|
}
|
|
438
476
|
console.log(`[Slack] Initialized with ${existingCompleted.length} existing completed tasks`);
|
|
439
477
|
|
|
478
|
+
let hydratedTrees = 0;
|
|
479
|
+
let hydratedFlat = 0;
|
|
480
|
+
for (const task of getInProgressSlackTasks()) {
|
|
481
|
+
if (!task.slackChannelId || !task.slackThreadTs) continue;
|
|
482
|
+
|
|
483
|
+
const treeTs = task.slackTreeRootMessageTs;
|
|
484
|
+
const progressTs = task.slackProgressMessageTs;
|
|
485
|
+
|
|
486
|
+
if (treeTs) {
|
|
487
|
+
let tree = treeMessages.get(treeTs);
|
|
488
|
+
if (!tree) {
|
|
489
|
+
tree = {
|
|
490
|
+
channelId: task.slackChannelId,
|
|
491
|
+
threadTs: task.slackThreadTs,
|
|
492
|
+
messageTs: treeTs,
|
|
493
|
+
rootTaskIds: new Set(),
|
|
494
|
+
};
|
|
495
|
+
treeMessages.set(treeTs, tree);
|
|
496
|
+
}
|
|
497
|
+
tree.rootTaskIds.add(task.id);
|
|
498
|
+
taskToTree.set(task.id, treeTs);
|
|
499
|
+
taskMessages.set(task.id, {
|
|
500
|
+
channelId: task.slackChannelId,
|
|
501
|
+
threadTs: task.slackThreadTs,
|
|
502
|
+
messageTs: treeTs,
|
|
503
|
+
});
|
|
504
|
+
if (task.progress) sentProgress.set(task.id, task.progress);
|
|
505
|
+
hydratedTrees++;
|
|
506
|
+
} else if (progressTs) {
|
|
507
|
+
taskMessages.set(task.id, {
|
|
508
|
+
channelId: task.slackChannelId,
|
|
509
|
+
threadTs: task.slackThreadTs,
|
|
510
|
+
messageTs: progressTs,
|
|
511
|
+
});
|
|
512
|
+
if (task.progress) sentProgress.set(task.id, task.progress);
|
|
513
|
+
hydratedFlat++;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (hydratedTrees > 0 || hydratedFlat > 0) {
|
|
517
|
+
console.log(
|
|
518
|
+
`[Slack] Hydrated ${hydratedTrees} tree task(s) and ${hydratedFlat} flat task(s) from DB`,
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
|
|
440
522
|
watcherInterval = setInterval(async () => {
|
|
441
523
|
// Prevent overlapping processing cycles
|
|
442
524
|
if (isProcessing || !getSlackApp()) return;
|
|
@@ -515,8 +597,20 @@ export function startTaskWatcher(intervalMs = 3000): void {
|
|
|
515
597
|
sentProgress.set(task.id, "__in_progress__");
|
|
516
598
|
lastSendTime.set(progressKey, now);
|
|
517
599
|
try {
|
|
518
|
-
await updateProgressInPlace(task, "Starting...", tracked.messageTs);
|
|
519
|
-
|
|
600
|
+
const result = await updateProgressInPlace(task, "Starting...", tracked.messageTs);
|
|
601
|
+
if (result === "not_found") {
|
|
602
|
+
taskMessages.delete(task.id);
|
|
603
|
+
sentProgress.delete(task.id);
|
|
604
|
+
setSlackMessageTracking(task.id, {
|
|
605
|
+
slackProgressMessageTs: null,
|
|
606
|
+
slackTreeRootMessageTs: null,
|
|
607
|
+
});
|
|
608
|
+
} else if (result === "ok") {
|
|
609
|
+
console.log(`[Slack] Updated to in-progress for task ${task.id.slice(0, 8)}`);
|
|
610
|
+
} else {
|
|
611
|
+
sentProgress.delete(task.id);
|
|
612
|
+
lastSendTime.delete(progressKey);
|
|
613
|
+
}
|
|
520
614
|
} catch (error) {
|
|
521
615
|
sentProgress.delete(task.id);
|
|
522
616
|
lastSendTime.delete(progressKey);
|
|
@@ -535,23 +629,42 @@ export function startTaskWatcher(intervalMs = 3000): void {
|
|
|
535
629
|
sentProgress.set(task.id, task.progress);
|
|
536
630
|
lastSendTime.set(progressKey, now);
|
|
537
631
|
try {
|
|
632
|
+
let postedTs: string | undefined;
|
|
538
633
|
if (tracked) {
|
|
539
634
|
// Update the existing message in-place via chat.update
|
|
540
|
-
await updateProgressInPlace(task, task.progress, tracked.messageTs);
|
|
541
|
-
|
|
635
|
+
const result = await updateProgressInPlace(task, task.progress, tracked.messageTs);
|
|
636
|
+
if (result === "ok") {
|
|
637
|
+
console.log(`[Slack] Updated progress in-place for task ${task.id.slice(0, 8)}`);
|
|
638
|
+
} else if (result === "not_found") {
|
|
639
|
+
taskMessages.delete(task.id);
|
|
640
|
+
postedTs = await sendProgressUpdate(task, task.progress);
|
|
641
|
+
if (postedTs && task.slackChannelId && task.slackThreadTs) {
|
|
642
|
+
taskMessages.set(task.id, {
|
|
643
|
+
channelId: task.slackChannelId,
|
|
644
|
+
threadTs: task.slackThreadTs,
|
|
645
|
+
messageTs: postedTs,
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
} else {
|
|
649
|
+
sentProgress.delete(task.id);
|
|
650
|
+
lastSendTime.delete(progressKey);
|
|
651
|
+
}
|
|
542
652
|
} else {
|
|
543
653
|
// No tracked message (e.g., multi-task assignment or server restart)
|
|
544
654
|
// Post a new progress message and track its ts
|
|
545
|
-
|
|
546
|
-
if (
|
|
655
|
+
postedTs = await sendProgressUpdate(task, task.progress);
|
|
656
|
+
if (postedTs && task.slackChannelId && task.slackThreadTs) {
|
|
547
657
|
taskMessages.set(task.id, {
|
|
548
658
|
channelId: task.slackChannelId,
|
|
549
659
|
threadTs: task.slackThreadTs,
|
|
550
|
-
messageTs,
|
|
660
|
+
messageTs: postedTs,
|
|
551
661
|
});
|
|
552
662
|
}
|
|
553
663
|
console.log(`[Slack] Sent initial progress for task ${task.id.slice(0, 8)}`);
|
|
554
664
|
}
|
|
665
|
+
if (postedTs) {
|
|
666
|
+
setSlackMessageTracking(task.id, { slackProgressMessageTs: postedTs });
|
|
667
|
+
}
|
|
555
668
|
} catch (error) {
|
|
556
669
|
// If send fails, clear markers so we can retry
|
|
557
670
|
sentProgress.delete(task.id);
|
|
@@ -42,4 +42,17 @@ describe("agents list model display", () => {
|
|
|
42
42
|
providerId: "openrouter",
|
|
43
43
|
});
|
|
44
44
|
});
|
|
45
|
+
|
|
46
|
+
test("presents latest Anthropic direct model ids as readable labels", () => {
|
|
47
|
+
expect(getAgentModelPresentation("claude-fable-5")).toMatchObject({
|
|
48
|
+
label: "Claude Fable 5",
|
|
49
|
+
provider: "Anthropic",
|
|
50
|
+
providerId: "anthropic",
|
|
51
|
+
});
|
|
52
|
+
expect(getAgentModelPresentation("claude-mythos-5")).toMatchObject({
|
|
53
|
+
label: "Claude Mythos 5",
|
|
54
|
+
provider: "Anthropic",
|
|
55
|
+
providerId: "anthropic",
|
|
56
|
+
});
|
|
57
|
+
});
|
|
45
58
|
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for `classifyAwsSdkError` in `src/utils/aws-error-classifier.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Exercises all four error categories and the no-match path.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test } from "bun:test";
|
|
8
|
+
import { classifyAwsSdkError } from "../utils/aws-error-classifier";
|
|
9
|
+
|
|
10
|
+
describe("classifyAwsSdkError — aws-auth", () => {
|
|
11
|
+
test("ExpiredTokenException", () => {
|
|
12
|
+
const r = classifyAwsSdkError(
|
|
13
|
+
"ExpiredTokenException: The security token included in the request is expired",
|
|
14
|
+
);
|
|
15
|
+
expect(r).not.toBeNull();
|
|
16
|
+
expect(r!.category).toBe("aws-auth");
|
|
17
|
+
expect(r!.message).toContain("aws sso login");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("ExpiredToken (without Exception suffix)", () => {
|
|
21
|
+
const r = classifyAwsSdkError("ExpiredToken: token expired");
|
|
22
|
+
expect(r?.category).toBe("aws-auth");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("CredentialsProviderError", () => {
|
|
26
|
+
const r = classifyAwsSdkError("CredentialsProviderError: Could not load credentials");
|
|
27
|
+
expect(r?.category).toBe("aws-auth");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("Unable to locate credentials", () => {
|
|
31
|
+
const r = classifyAwsSdkError(
|
|
32
|
+
'Unable to locate credentials. You can configure credentials by running "aws configure".',
|
|
33
|
+
);
|
|
34
|
+
expect(r?.category).toBe("aws-auth");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("security token ... expired (lower-case)", () => {
|
|
38
|
+
const r = classifyAwsSdkError("The security token included in the request is expired");
|
|
39
|
+
expect(r?.category).toBe("aws-auth");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("InvalidSignatureException", () => {
|
|
43
|
+
const r = classifyAwsSdkError(
|
|
44
|
+
"InvalidSignatureException: The request signature we calculated does not match the signature you provided",
|
|
45
|
+
);
|
|
46
|
+
expect(r?.category).toBe("aws-auth");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("UnrecognizedClientException", () => {
|
|
50
|
+
const r = classifyAwsSdkError(
|
|
51
|
+
"UnrecognizedClientException: The security token included in the request is invalid",
|
|
52
|
+
);
|
|
53
|
+
expect(r?.category).toBe("aws-auth");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("classifyAwsSdkError — aws-throttle", () => {
|
|
58
|
+
test("ThrottlingException", () => {
|
|
59
|
+
const r = classifyAwsSdkError("ThrottlingException: Rate exceeded");
|
|
60
|
+
expect(r?.category).toBe("aws-throttle");
|
|
61
|
+
expect(r!.message).toContain("quota");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("TooManyRequestsException", () => {
|
|
65
|
+
const r = classifyAwsSdkError("TooManyRequestsException: Too many requests");
|
|
66
|
+
expect(r?.category).toBe("aws-throttle");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("ServiceQuotaExceededException", () => {
|
|
70
|
+
const r = classifyAwsSdkError(
|
|
71
|
+
"ServiceQuotaExceededException: You have exceeded your request quota for this service",
|
|
72
|
+
);
|
|
73
|
+
expect(r?.category).toBe("aws-throttle");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("Rate exceeded (standalone phrase)", () => {
|
|
77
|
+
const r = classifyAwsSdkError("Rate exceeded. Reduce your request rate.");
|
|
78
|
+
expect(r?.category).toBe("aws-throttle");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("classifyAwsSdkError — aws-access", () => {
|
|
83
|
+
test("AccessDeniedException with bedrock:InvokeModel", () => {
|
|
84
|
+
const r = classifyAwsSdkError(
|
|
85
|
+
"AccessDeniedException: User: arn:aws:iam::123:user/dev is not authorized to perform: bedrock:InvokeModel on resource: arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-v2",
|
|
86
|
+
);
|
|
87
|
+
expect(r?.category).toBe("aws-access");
|
|
88
|
+
expect(r!.message).toContain("bedrock:InvokeModel");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("not authorized to perform (phrase match)", () => {
|
|
92
|
+
const r = classifyAwsSdkError("User is not authorized to perform: bedrock:InvokeModel");
|
|
93
|
+
expect(r?.category).toBe("aws-access");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("classifyAwsSdkError — aws-model", () => {
|
|
98
|
+
test("ValidationException", () => {
|
|
99
|
+
const r = classifyAwsSdkError(
|
|
100
|
+
"ValidationException: Invocation of model ID anthropic.claude-v99 with on-demand throughput isn't supported",
|
|
101
|
+
);
|
|
102
|
+
expect(r?.category).toBe("aws-model");
|
|
103
|
+
expect(r!.message).toContain("MODEL_OVERRIDE");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("ResourceNotFoundException", () => {
|
|
107
|
+
const r = classifyAwsSdkError("ResourceNotFoundException: Could not find model");
|
|
108
|
+
expect(r?.category).toBe("aws-model");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("ModelTimeoutException", () => {
|
|
112
|
+
const r = classifyAwsSdkError(
|
|
113
|
+
"ModelTimeoutException: The model timed out processing your request",
|
|
114
|
+
);
|
|
115
|
+
expect(r?.category).toBe("aws-model");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("ModelNotReadyException", () => {
|
|
119
|
+
const r = classifyAwsSdkError("ModelNotReadyException: The model is not ready for inference");
|
|
120
|
+
expect(r?.category).toBe("aws-model");
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("classifyAwsSdkError — priority ordering", () => {
|
|
125
|
+
test("aws-auth wins over aws-model when both match (ExpiredToken + ValidationException)", () => {
|
|
126
|
+
// Should not happen in practice, but priority must be deterministic
|
|
127
|
+
const r = classifyAwsSdkError("ExpiredTokenException and also ValidationException");
|
|
128
|
+
expect(r?.category).toBe("aws-auth");
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("classifyAwsSdkError — no-match", () => {
|
|
133
|
+
test("returns null for empty string", () => {
|
|
134
|
+
expect(classifyAwsSdkError("")).toBeNull();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("returns null for unrelated error", () => {
|
|
138
|
+
expect(classifyAwsSdkError("TypeError: Cannot read property 'foo' of undefined")).toBeNull();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("returns null for generic network error", () => {
|
|
142
|
+
expect(classifyAwsSdkError("ECONNREFUSED 127.0.0.1:3013")).toBeNull();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("returns null for Claude API error (not AWS)", () => {
|
|
146
|
+
expect(classifyAwsSdkError("401 Unauthorized: Invalid API key")).toBeNull();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -794,6 +794,18 @@ describe("ClaudeManagedAdapter (Phase 4) — repo provisioning + cost data", ()
|
|
|
794
794
|
});
|
|
795
795
|
|
|
796
796
|
test("CLAUDE_MANAGED_MODEL_PRICING covers sonnet, opus, haiku at minimum", () => {
|
|
797
|
+
expect(CLAUDE_MANAGED_MODEL_PRICING["claude-fable-5"]).toEqual({
|
|
798
|
+
inputPerMillion: 10.0,
|
|
799
|
+
outputPerMillion: 50.0,
|
|
800
|
+
cacheReadPerMillion: 1.0,
|
|
801
|
+
cacheWritePerMillion: 12.5,
|
|
802
|
+
});
|
|
803
|
+
expect(CLAUDE_MANAGED_MODEL_PRICING["claude-mythos-5"]).toEqual({
|
|
804
|
+
inputPerMillion: 10.0,
|
|
805
|
+
outputPerMillion: 50.0,
|
|
806
|
+
cacheReadPerMillion: 1.0,
|
|
807
|
+
cacheWritePerMillion: 12.5,
|
|
808
|
+
});
|
|
797
809
|
expect(CLAUDE_MANAGED_MODEL_PRICING["claude-sonnet-4-6"]).toBeDefined();
|
|
798
810
|
expect(CLAUDE_MANAGED_MODEL_PRICING["claude-opus-4-7"]).toBeDefined();
|
|
799
811
|
expect(CLAUDE_MANAGED_MODEL_PRICING["claude-haiku-4-5"]).toBeDefined();
|
|
@@ -8,6 +8,13 @@ import {
|
|
|
8
8
|
} from "../utils/context-window";
|
|
9
9
|
|
|
10
10
|
describe("getContextWindowSize", () => {
|
|
11
|
+
test("returns 1M for fable and mythos models", () => {
|
|
12
|
+
expect(getContextWindowSize("claude-fable-5")).toBe(1_000_000);
|
|
13
|
+
expect(getContextWindowSize("claude-mythos-5")).toBe(1_000_000);
|
|
14
|
+
expect(getContextWindowSize("fable")).toBe(1_000_000);
|
|
15
|
+
expect(getContextWindowSize("mythos")).toBe(1_000_000);
|
|
16
|
+
});
|
|
17
|
+
|
|
11
18
|
test("returns 1M for opus models", () => {
|
|
12
19
|
expect(getContextWindowSize("claude-opus-4-8")).toBe(1_000_000);
|
|
13
20
|
expect(getContextWindowSize("claude-opus-4-7")).toBe(1_000_000);
|