agent-nuvira 1.76.0 → 1.78.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 (62) hide show
  1. package/README.md +3 -2
  2. package/dist/cli/bedrock.d.ts +27 -0
  3. package/dist/cli/bedrock.d.ts.map +1 -0
  4. package/dist/cli/bedrock.js +406 -0
  5. package/dist/cli/bedrock.js.map +1 -0
  6. package/dist/cli/gateway.d.ts +5 -0
  7. package/dist/cli/gateway.d.ts.map +1 -1
  8. package/dist/cli/gateway.js +154 -0
  9. package/dist/cli/gateway.js.map +1 -1
  10. package/dist/cli/models.d.ts.map +1 -1
  11. package/dist/cli/models.js +86 -0
  12. package/dist/cli/models.js.map +1 -1
  13. package/dist/cli/router.d.ts.map +1 -1
  14. package/dist/cli/router.js +4 -0
  15. package/dist/cli/router.js.map +1 -1
  16. package/dist/config/manager.d.ts.map +1 -1
  17. package/dist/config/manager.js +1 -0
  18. package/dist/config/manager.js.map +1 -1
  19. package/dist/gateway/channel-directory.d.ts +2 -0
  20. package/dist/gateway/channel-directory.d.ts.map +1 -1
  21. package/dist/gateway/channel-directory.js +18 -0
  22. package/dist/gateway/channel-directory.js.map +1 -1
  23. package/dist/gateway/contacts.d.ts +43 -6
  24. package/dist/gateway/contacts.d.ts.map +1 -1
  25. package/dist/gateway/contacts.js +95 -6
  26. package/dist/gateway/contacts.js.map +1 -1
  27. package/dist/gateway/registry.d.ts +3 -2
  28. package/dist/gateway/registry.d.ts.map +1 -1
  29. package/dist/gateway/registry.js +17 -9
  30. package/dist/gateway/registry.js.map +1 -1
  31. package/dist/inference/dynamic-catalog.d.ts +39 -0
  32. package/dist/inference/dynamic-catalog.d.ts.map +1 -0
  33. package/dist/inference/dynamic-catalog.js +174 -0
  34. package/dist/inference/dynamic-catalog.js.map +1 -0
  35. package/dist/inference/model-catalog.d.ts +0 -4
  36. package/dist/inference/model-catalog.d.ts.map +1 -1
  37. package/dist/inference/model-catalog.js +4 -42
  38. package/dist/inference/model-catalog.js.map +1 -1
  39. package/dist/inference/provider-catalog.d.ts +5 -1
  40. package/dist/inference/provider-catalog.d.ts.map +1 -1
  41. package/dist/inference/provider-catalog.js +22 -2
  42. package/dist/inference/provider-catalog.js.map +1 -1
  43. package/dist/learning/auto-router.d.ts.map +1 -1
  44. package/dist/learning/auto-router.js +19 -9
  45. package/dist/learning/auto-router.js.map +1 -1
  46. package/dist/learning/cost-tracker.js +3 -3
  47. package/dist/learning/cost-tracker.js.map +1 -1
  48. package/dist/tools/gateway-send.d.ts.map +1 -1
  49. package/dist/tools/gateway-send.js +30 -6
  50. package/dist/tools/gateway-send.js.map +1 -1
  51. package/dist/web-dashboard/server.d.ts.map +1 -1
  52. package/dist/web-dashboard/server.js +421 -0
  53. package/dist/web-dashboard/server.js.map +1 -1
  54. package/dist/workflow/templates.d.ts.map +1 -1
  55. package/dist/workflow/templates.js +11 -47
  56. package/dist/workflow/templates.js.map +1 -1
  57. package/package.json +1 -1
  58. package/src/web-dashboard/public/assets/{index-TbDN1H0u.js → index-D7wybkPX.js} +74 -74
  59. package/src/web-dashboard/public/assets/index-D7wybkPX.js.map +1 -0
  60. package/src/web-dashboard/public/assets/{index-BMXmCrQF.css → index-Dy2JGPXg.css} +1 -1
  61. package/src/web-dashboard/public/index.html +2 -2
  62. package/src/web-dashboard/public/assets/index-TbDN1H0u.js.map +0 -1
@@ -540,6 +540,7 @@ async function readModelsHealth() {
540
540
  checkLMStudioProvider(),
541
541
  checkAnyscaleProvider(),
542
542
  checkVLLMProvider(),
543
+ checkBedrockProvider(),
543
544
  ]);
544
545
  const providers = results.filter(Boolean);
545
546
  const totalModels = providers.reduce((sum, p) => sum + p.models.length, 0);
@@ -2701,6 +2702,78 @@ function handleRequest(req, res) {
2701
2702
  })();
2702
2703
  return;
2703
2704
  }
2705
+ // ── Contacts management (name-centric contact store for outbound messaging) ──
2706
+ // GET /api/admin/contacts — list all contacts (with name, platform, id, phone, status)
2707
+ // PUT /api/admin/contacts — update a contact (approve/reject/edit/delete)
2708
+ if (pathname === '/api/admin/contacts') {
2709
+ void (async () => {
2710
+ const session = adminSessions.validate(bearerToken(req));
2711
+ if (!session) {
2712
+ writeJson(res, 401, { ok: false, error: 'Not authenticated — log in first.' });
2713
+ return;
2714
+ }
2715
+ if (!roleCan(session.role, 'routing.operate')) {
2716
+ writeJson(res, 403, { ok: false, error: `Access denied — role '${session.role}' cannot manage contacts (requires admin or operator).` });
2717
+ return;
2718
+ }
2719
+ if (req.method === 'GET') {
2720
+ const { readGatewayContacts } = await import('../gateway/contacts.js');
2721
+ const contacts = readGatewayContacts();
2722
+ writeJson(res, 200, { ok: true, contacts });
2723
+ return;
2724
+ }
2725
+ if (req.method === 'PUT') {
2726
+ const body = await readJsonBody(req);
2727
+ const action = body?.action;
2728
+ const { readGatewayContacts, upsertGatewayContact, setContactStatus, removeGatewayContact, removeContactByNameOrId } = await import('../gateway/contacts.js');
2729
+ if (action === 'approve') {
2730
+ const { platform, nameOrId } = body;
2731
+ if (!platform || !nameOrId) {
2732
+ writeJson(res, 400, { ok: false, error: 'Missing platform or nameOrId.' });
2733
+ return;
2734
+ }
2735
+ const { setContactStatus: setStatus } = await import('../gateway/contacts.js');
2736
+ const ok = setStatus(platform, nameOrId, 'approved');
2737
+ writeJson(res, ok ? 200 : 404, { ok, error: ok ? undefined : 'Contact not found.' });
2738
+ return;
2739
+ }
2740
+ if (action === 'reject') {
2741
+ const { platform, nameOrId } = body;
2742
+ if (!platform || !nameOrId) {
2743
+ writeJson(res, 400, { ok: false, error: 'Missing platform or nameOrId.' });
2744
+ return;
2745
+ }
2746
+ const ok = setContactStatus(platform, nameOrId, 'rejected');
2747
+ writeJson(res, ok ? 200 : 404, { ok, error: ok ? undefined : 'Contact not found.' });
2748
+ return;
2749
+ }
2750
+ if (action === 'delete') {
2751
+ const { platform, nameOrId } = body;
2752
+ if (!platform || !nameOrId) {
2753
+ writeJson(res, 400, { ok: false, error: 'Missing platform or nameOrId.' });
2754
+ return;
2755
+ }
2756
+ const ok = removeGatewayContact(platform, nameOrId);
2757
+ writeJson(res, ok ? 200 : 404, { ok, error: ok ? undefined : 'Contact not found.' });
2758
+ return;
2759
+ }
2760
+ if (action === 'update') {
2761
+ const { name, platform, id, phone, status } = body;
2762
+ if (!platform || !id || !name) {
2763
+ writeJson(res, 400, { ok: false, error: 'Missing platform, id, or name.' });
2764
+ return;
2765
+ }
2766
+ upsertGatewayContact({ name, platform: platform, id, phone, status: status ?? 'approved', registeredAt: Date.now() });
2767
+ writeJson(res, 200, { ok: true });
2768
+ return;
2769
+ }
2770
+ writeJson(res, 400, { ok: false, error: 'Unknown action. Use: approve, reject, delete, update.' });
2771
+ return;
2772
+ }
2773
+ writeJson(res, 405, { ok: false, error: 'Method not allowed — use GET or PUT.' });
2774
+ })();
2775
+ return;
2776
+ }
2704
2777
  // ── User management (Session 19 — RBAC on the dashboard control layer) ──
2705
2778
  // role.manage (admin) gates who may add/remove dashboard admin users.
2706
2779
  // rbac.json assignments override a credential's stored role (CLI parity),
@@ -2962,6 +3035,56 @@ function handleRequest(req, res) {
2962
3035
  res.end(JSON.stringify(readModelRegistryData()));
2963
3036
  return;
2964
3037
  }
3038
+ // Model Discovery Timeline endpoint
3039
+ if (pathname === '/api/model-timeline') {
3040
+ try {
3041
+ const registryData = readJSON(join(MEMORY_DIR, 'model-registry.json'));
3042
+ const now = Date.now();
3043
+ const entries = Object.values(registryData?.entries ?? {});
3044
+ const STALE_DAYS = 7;
3045
+ const REMOVED_DAYS = 30;
3046
+ let freshCount = 0;
3047
+ let staleCount = 0;
3048
+ let removedCount = 0;
3049
+ const timelineEntries = entries.map((e) => {
3050
+ const daysSinceProbe = e.lastProbedAt ? (now - e.lastProbedAt) / (24 * 60 * 60 * 1000) : Infinity;
3051
+ const isStale = daysSinceProbe > STALE_DAYS;
3052
+ const isRemoved = daysSinceProbe > REMOVED_DAYS && e.errorRate > 0.5;
3053
+ if (isRemoved)
3054
+ removedCount++;
3055
+ else if (isStale)
3056
+ staleCount++;
3057
+ else
3058
+ freshCount++;
3059
+ return {
3060
+ provider: e.provider,
3061
+ model: e.model,
3062
+ status: e.status,
3063
+ lastVerifiedAt: e.lastVerifiedAt || 0,
3064
+ lastProbedAt: e.lastProbedAt || 0,
3065
+ lastUsedAt: e.lastUsedAt || 0,
3066
+ errorRate: e.errorRate || 0,
3067
+ latencyMs: e.latencyMs,
3068
+ contextWindowTokens: e.contextWindowTokens,
3069
+ firstSeenAt: e.firstSeenAt || e.lastProbedAt || 0,
3070
+ };
3071
+ });
3072
+ res.writeHead(200, { 'Content-Type': 'application/json' });
3073
+ res.end(JSON.stringify({
3074
+ entries: timelineEntries,
3075
+ lastUpdated: now,
3076
+ totalModels: entries.length,
3077
+ freshCount,
3078
+ staleCount,
3079
+ removedCount,
3080
+ }));
3081
+ }
3082
+ catch (err) {
3083
+ res.writeHead(500, { 'Content-Type': 'application/json' });
3084
+ res.end(JSON.stringify({ error: 'Failed to load timeline data' }));
3085
+ }
3086
+ return;
3087
+ }
2965
3088
  if (pathname === '/api/requests') {
2966
3089
  res.writeHead(200, { 'Content-Type': 'application/json' });
2967
3090
  res.end(JSON.stringify(readRequestsData()));
@@ -3495,6 +3618,117 @@ function handleRequest(req, res) {
3495
3618
  writeJson(res, 200, { ok: true, removed: keys, status: platformConfigStatus(platform) });
3496
3619
  return;
3497
3620
  }
3621
+ // ── Bedrock onboarding endpoints ────────────────────────────────────────
3622
+ // GET /api/bedrock/status — current Bedrock config status
3623
+ if (pathname === '/api/bedrock/status' && req.method === 'GET') {
3624
+ const session = adminSessions.validate(bearerToken(req));
3625
+ if (!session) {
3626
+ writeJson(res, 401, { ok: false, error: 'Not authenticated.' });
3627
+ return;
3628
+ }
3629
+ const region = process.env.BEDROCK_REGION || 'us-east-1';
3630
+ const apiKey = process.env.AWS_BEARER_TOKEN;
3631
+ const accessKey = process.env.AWS_ACCESS_KEY_ID;
3632
+ writeJson(res, 200, {
3633
+ configured: !!(apiKey || accessKey),
3634
+ region,
3635
+ authMethod: apiKey ? 'bearer' : accessKey ? 'iam' : 'none',
3636
+ apiKeySet: !!apiKey,
3637
+ iamKeySet: !!accessKey,
3638
+ });
3639
+ return;
3640
+ }
3641
+ // POST /api/bedrock/setup — save Bedrock env vars to ~/.buff/.env
3642
+ if (pathname === '/api/bedrock/setup' && req.method === 'POST') {
3643
+ void (async () => {
3644
+ const session = adminSessions.validate(bearerToken(req));
3645
+ if (!session) {
3646
+ writeJson(res, 401, { ok: false, error: 'Not authenticated.' });
3647
+ return;
3648
+ }
3649
+ if (!roleCan(session.role, 'routing.operate')) {
3650
+ writeJson(res, 403, { ok: false, error: 'Access denied — admin or operator role required.' });
3651
+ return;
3652
+ }
3653
+ const body = await readJsonBody(req);
3654
+ const envVars = body?.envVars;
3655
+ if (!envVars || typeof envVars !== 'object' || Array.isArray(envVars)) {
3656
+ writeJson(res, 400, { ok: false, error: 'Missing envVars object in request body.' });
3657
+ return;
3658
+ }
3659
+ // Validate env var keys
3660
+ const allowedKeys = ['BEDROCK_REGION', 'AWS_BEARER_TOKEN', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN'];
3661
+ const clean = {};
3662
+ for (const [k, v] of Object.entries(envVars)) {
3663
+ if (allowedKeys.includes(k) && typeof v === 'string') {
3664
+ clean[k] = v;
3665
+ }
3666
+ }
3667
+ const { wrote } = writeEnvFile(clean);
3668
+ applyEnvToProcess(clean);
3669
+ writeJson(res, 200, { ok: true, envVarsWritten: wrote });
3670
+ })();
3671
+ return;
3672
+ }
3673
+ // POST /api/bedrock/probe — probe Bedrock models in a region
3674
+ if (pathname === '/api/bedrock/probe' && req.method === 'POST') {
3675
+ void (async () => {
3676
+ const session = adminSessions.validate(bearerToken(req));
3677
+ if (!session) {
3678
+ writeJson(res, 401, { ok: false, error: 'Not authenticated.' });
3679
+ return;
3680
+ }
3681
+ const body = await readJsonBody(req);
3682
+ const region = (typeof body?.region === 'string' && body.region) || process.env.BEDROCK_REGION || 'us-east-1';
3683
+ const apiKey = process.env.AWS_BEARER_TOKEN;
3684
+ if (!apiKey) {
3685
+ writeJson(res, 400, { ok: false, error: 'AWS_BEARER_TOKEN not configured. Run Bedrock setup first.' });
3686
+ return;
3687
+ }
3688
+ const runtimeBase = `https://bedrock-runtime.${region}.amazonaws.com`;
3689
+ const probeModels = [
3690
+ 'anthropic.claude-haiku-4-5-20251001-v1:0',
3691
+ 'anthropic.claude-sonnet-4-6',
3692
+ 'anthropic.claude-fable-5',
3693
+ 'meta.llama3-1-8b-instruct-v1:0',
3694
+ 'meta.llama3-3-70b-instruct-v1:0',
3695
+ 'meta.llama4-scout-17b-instruct-v1:0',
3696
+ 'deepseek.v3.2',
3697
+ 'mistral.mistral-large-3-675b-instruct',
3698
+ 'amazon.nova-pro-v1:0',
3699
+ 'openai.gpt-5.6-terra',
3700
+ 'qwen.qwen3-coder-next',
3701
+ 'xai.grok-4.6',
3702
+ 'google.gemma-3-12b-it',
3703
+ ];
3704
+ const models = await Promise.all(probeModels.map(async (modelId) => {
3705
+ try {
3706
+ const controller = new AbortController();
3707
+ const timeout = setTimeout(() => controller.abort(), 5000);
3708
+ const probe = await fetch(`${runtimeBase}/openai/v1/chat/completions`, {
3709
+ method: 'POST',
3710
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
3711
+ body: JSON.stringify({ model: modelId, messages: [{ role: 'user', content: 'hi' }], max_tokens: 1 }),
3712
+ signal: controller.signal,
3713
+ });
3714
+ clearTimeout(timeout);
3715
+ return {
3716
+ modelId,
3717
+ status: probe.ok ? 'accessible'
3718
+ : probe.status === 403 ? 'permission-denied'
3719
+ : probe.status === 404 ? 'not-found'
3720
+ : 'error',
3721
+ httpStatus: probe.status || undefined,
3722
+ };
3723
+ }
3724
+ catch {
3725
+ return { modelId, status: 'error' };
3726
+ }
3727
+ }));
3728
+ writeJson(res, 200, { ok: true, region, models });
3729
+ })();
3730
+ return;
3731
+ }
3498
3732
  // ── P3 Chat console (GUI parity with `buff chat "<prompt>"`) ──────────
3499
3733
  // One tool-loop turn per message, history threaded per session. Running the
3500
3734
  // agent executes tools, so it rides the same admin + routing.operate gate
@@ -4636,5 +4870,192 @@ async function checkVLLMProvider() {
4636
4870
  }
4637
4871
  return result;
4638
4872
  }
4873
+ /** Well-known Bedrock model IDs to probe when the control-plane listing fails.
4874
+ * Organized by provider; covers the most commonly enabled models.
4875
+ */
4876
+ const BEDROCK_PROBE_MODELS = [
4877
+ // Anthropic Claude (current catalog)
4878
+ 'anthropic.claude-haiku-4-5-20251001-v1:0',
4879
+ 'anthropic.claude-sonnet-4-6',
4880
+ 'anthropic.claude-fable-5',
4881
+ 'anthropic.claude-opus-4-6-v1',
4882
+ 'anthropic.claude-sonnet-4-5-20250929-v1:0',
4883
+ // Meta Llama (current catalog)
4884
+ 'meta.llama3-1-8b-instruct-v1:0',
4885
+ 'meta.llama3-1-70b-instruct-v1:0',
4886
+ 'meta.llama3-3-70b-instruct-v1:0',
4887
+ 'meta.llama4-scout-17b-instruct-v1:0',
4888
+ 'meta.llama4-maverick-17b-instruct-v1:0',
4889
+ // Mistral (current catalog)
4890
+ 'mistral.mistral-large-3-675b-instruct',
4891
+ 'mistral.devstral-2-123b',
4892
+ 'mistral.ministral-3-14b-instruct',
4893
+ // DeepSeek
4894
+ 'deepseek.v3.2',
4895
+ 'deepseek.r1-v1:0',
4896
+ // Amazon Nova
4897
+ 'amazon.nova-pro-v1:0',
4898
+ 'amazon.nova-lite-v1:0',
4899
+ // OpenAI on Bedrock
4900
+ 'openai.gpt-5.6-terra',
4901
+ 'openai.gpt-oss-120b-1:0',
4902
+ // Qwen
4903
+ 'qwen.qwen3-coder-next',
4904
+ 'qwen.qwen3-32b-v1:0',
4905
+ // Google Gemma
4906
+ 'google.gemma-3-12b-it',
4907
+ // xAI
4908
+ 'xai.grok-4.6',
4909
+ ];
4910
+ /** Resolve the Bedrock region from env (BEDROCK_REGION) or default to us-east-1.
4911
+ * Do NOT default to eu-north-1 — models are not widely available there. */
4912
+ function getBedrockRegion() {
4913
+ return process.env.BEDROCK_REGION || 'us-east-1';
4914
+ }
4915
+ /** Check Amazon Bedrock provider — uses Bearer token auth + runtime endpoint for discovery.
4916
+ *
4917
+ * Strategy:
4918
+ * 1. Try the control-plane `foundation-models` listing (needs bedrock:ListFoundationModels).
4919
+ * 2. If that returns 0 models or fails, fall back to probing well-known model IDs
4920
+ * through the `bedrock-runtime` OpenAI-compatible endpoint.
4921
+ * 3. Log detailed diagnostics for common failure modes (region, permissions, model access).
4922
+ */
4923
+ async function checkBedrockProvider() {
4924
+ const apiKey = process.env.AWS_BEARER_TOKEN;
4925
+ const region = getBedrockRegion();
4926
+ const runtimeBase = `https://bedrock-runtime.${region}.amazonaws.com`;
4927
+ const controlBase = `https://bedrock.${region}.amazonaws.com`;
4928
+ const result = {
4929
+ provider: 'bedrock', providerLabel: 'Amazon Bedrock', icon: '🟠',
4930
+ apiConfigured: !!apiKey, apiAccessible: false, canGenerate: false,
4931
+ overallStatus: 'unavailable', models: [],
4932
+ notes: `Amazon Bedrock (${region}) — Claude, Llama, Mistral, DeepSeek, Qwen, etc.`,
4933
+ freeTierInfo: 'Pay-per-use. Requires bedrock:InvokeModel + bedrock:ListFoundationModels.',
4934
+ };
4935
+ if (!apiKey) {
4936
+ result.models = [{ id: '(no key)', name: 'AWS_BEARER_TOKEN not set', status: 'unavailable', statusReason: 'Set AWS_BEARER_TOKEN in ~/.buff/.env' }];
4937
+ return result;
4938
+ }
4939
+ // ── Step 1: Try the control-plane foundation-models listing ──────────────
4940
+ // This endpoint requires IAM/SigV4 auth. Bearer tokens may not work here;
4941
+ // if they do, great — if not, we fall back to runtime probing (Step 2).
4942
+ const check = await fetchWithTimeout(`${controlBase}/foundation-models`, { headers: { 'Authorization': `Bearer ${apiKey}` } });
4943
+ if (check.ok && check.data?.modelSummaries && check.data.modelSummaries.length > 0) {
4944
+ result.apiAccessible = true;
4945
+ // Filter to text chat models that are active
4946
+ const chatModels = check.data.modelSummaries.filter((m) => m.outputModalities?.includes('TEXT') &&
4947
+ m.modelLifecycle?.status === 'ACTIVE' &&
4948
+ (m.inferenceAPIsSupported?.converse?.sync || m.inferenceAPIsSupported?.openAiChatCompletions));
4949
+ // Test inference with the first model that supports OpenAI chat completions
4950
+ const openaiModels = chatModels.filter((m) => m.inferenceAPIsSupported?.openAiChatCompletions);
4951
+ const testModelId = openaiModels[0]?.modelId || chatModels[0]?.modelId;
4952
+ if (testModelId) {
4953
+ const inferCheck = await fetchWithTimeout(`${runtimeBase}/openai/v1/chat/completions`, {
4954
+ method: 'POST',
4955
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
4956
+ body: JSON.stringify({ model: testModelId, messages: [{ role: 'user', content: 'Say hi' }], max_tokens: 10 }),
4957
+ });
4958
+ if (inferCheck.ok && inferCheck.data?.choices) {
4959
+ result.canGenerate = true;
4960
+ result.overallStatus = 'available';
4961
+ }
4962
+ }
4963
+ if (!result.canGenerate) {
4964
+ result.overallStatus = 'limited';
4965
+ }
4966
+ result.models = chatModels.slice(0, 30).map((m) => ({
4967
+ id: m.modelId, name: `${m.modelName} (${m.providerName})`,
4968
+ status: (result.canGenerate ? 'available' : 'limited'),
4969
+ statusReason: result.canGenerate ? 'Listed + inference verified' : `Listed (${m.modelLifecycle.status}) — add bedrock:InvokeModel for generation`,
4970
+ }));
4971
+ return result;
4972
+ }
4973
+ // ── Step 2: Control-plane listing returned 0 models or failed — probe via runtime ──
4974
+ // The bedrock-runtime OpenAI-compatible endpoint accepts Bearer token auth.
4975
+ // We probe well-known model IDs to discover which ones are enabled + accessible.
4976
+ console.log(`[Bedrock] Control-plane listing returned 0 models (HTTP ${check.status}). Probing runtime endpoint…`);
4977
+ const discoveredModels = [];
4978
+ const probePromises = BEDROCK_PROBE_MODELS.map(async (modelId) => {
4979
+ try {
4980
+ const probe = await fetchWithTimeout(`${runtimeBase}/openai/v1/chat/completions`, {
4981
+ method: 'POST',
4982
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
4983
+ body: JSON.stringify({ model: modelId, messages: [{ role: 'user', content: 'hi' }], max_tokens: 1 }),
4984
+ });
4985
+ if (probe.ok) {
4986
+ // Extract provider from model ID (e.g., "anthropic.claude-…" → "Anthropic")
4987
+ const prefix = modelId.split('.')[0];
4988
+ const providerLabels = {
4989
+ anthropic: 'Anthropic', meta: 'Meta', mistral: 'Mistral',
4990
+ cohere: 'Cohere', deepseek: 'DeepSeek', amazon: 'Amazon', ai21: 'AI21 Labs',
4991
+ };
4992
+ discoveredModels.push({
4993
+ id: modelId,
4994
+ name: `${modelId} (${providerLabels[prefix] || prefix})`,
4995
+ provider: providerLabels[prefix] || prefix,
4996
+ });
4997
+ }
4998
+ else if (probe.status === 403) {
4999
+ // Model exists in region but not enabled — helpful diagnostic
5000
+ console.log(`[Bedrock] Model ${modelId}: 403 Forbidden — model exists in ${region} but access not approved. Request access at https://console.aws.amazon.com/bedrock/home?region=${region}#/modelaccess`);
5001
+ }
5002
+ else if (probe.status === 404) {
5003
+ // Model doesn't exist in this region
5004
+ // Silent — expected for most probe IDs in any given region
5005
+ }
5006
+ else {
5007
+ console.log(`[Bedrock] Model ${modelId}: HTTP ${probe.status} — ${probe.statusText}`);
5008
+ }
5009
+ }
5010
+ catch {
5011
+ // Connection failure — likely region or network issue
5012
+ }
5013
+ });
5014
+ await Promise.all(probePromises);
5015
+ if (discoveredModels.length > 0) {
5016
+ result.apiAccessible = true;
5017
+ result.canGenerate = true;
5018
+ result.overallStatus = 'available';
5019
+ result.models = discoveredModels.slice(0, 30).map((m) => ({
5020
+ id: m.id, name: m.name,
5021
+ status: 'available',
5022
+ statusReason: 'Verified via runtime probe — model enabled and accessible',
5023
+ }));
5024
+ console.log(`[Bedrock] ✓ Discovered ${discoveredModels.length} accessible model(s) in ${region} via runtime probing.`);
5025
+ }
5026
+ else {
5027
+ // ── Step 3: Diagnose why 0 models were found ────────────────────────
5028
+ result.models = [];
5029
+ let diagnosis = '';
5030
+ // Diagnose: check if it's a permissions error from the control-plane call
5031
+ if (check.status === 401 || check.status === 403) {
5032
+ diagnosis = `Permissions error (HTTP ${check.status}). Ensure your API key / IAM role has: ` +
5033
+ `bedrock:ListFoundationModels + bedrock:InvokeModel. ` +
5034
+ `For Bearer token auth, ensure the key is from Bedrock → Settings → API keys. ` +
5035
+ `For IAM auth, set AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY.`;
5036
+ }
5037
+ else if (check.status === 0) {
5038
+ diagnosis = `Cannot reach Bedrock in region ${region}. ` +
5039
+ `Verify the region is correct (set BEDROCK_REGION env var). ` +
5040
+ `Supported regions: us-east-1, us-west-2, eu-west-1, ap-southeast-1, etc. ` +
5041
+ `Note: do NOT use eu-north-1 unless models are explicitly enabled there.`;
5042
+ }
5043
+ else {
5044
+ diagnosis = `Control-plane listing returned HTTP ${check.status} with no models. ` +
5045
+ `Most likely cause: no model access approved in ${region}. ` +
5046
+ `→ Open https://console.aws.amazon.com/bedrock/home?region=${region}#/modelaccess ` +
5047
+ `→ Click "Manage model access" → Select Claude, Llama, Mistral, DeepSeek, etc. → Submit. ` +
5048
+ `Approval is usually instant for Anthropic/Meta models.`;
5049
+ }
5050
+ console.log(`[Bedrock] ✗ No accessible models in ${region}. Diagnosis: ${diagnosis}`);
5051
+ result.models = [{
5052
+ id: '(no-models)',
5053
+ name: `No models accessible in ${region}`,
5054
+ status: 'unavailable',
5055
+ statusReason: diagnosis,
5056
+ }];
5057
+ }
5058
+ return result;
5059
+ }
4639
5060
  export const DASHBOARD_DEFAULTS = { PORT, HOST };
4640
5061
  //# sourceMappingURL=server.js.map