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.
- package/README.md +3 -2
- package/dist/cli/bedrock.d.ts +27 -0
- package/dist/cli/bedrock.d.ts.map +1 -0
- package/dist/cli/bedrock.js +406 -0
- package/dist/cli/bedrock.js.map +1 -0
- package/dist/cli/gateway.d.ts +5 -0
- package/dist/cli/gateway.d.ts.map +1 -1
- package/dist/cli/gateway.js +154 -0
- package/dist/cli/gateway.js.map +1 -1
- package/dist/cli/models.d.ts.map +1 -1
- package/dist/cli/models.js +86 -0
- package/dist/cli/models.js.map +1 -1
- package/dist/cli/router.d.ts.map +1 -1
- package/dist/cli/router.js +4 -0
- package/dist/cli/router.js.map +1 -1
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +1 -0
- package/dist/config/manager.js.map +1 -1
- package/dist/gateway/channel-directory.d.ts +2 -0
- package/dist/gateway/channel-directory.d.ts.map +1 -1
- package/dist/gateway/channel-directory.js +18 -0
- package/dist/gateway/channel-directory.js.map +1 -1
- package/dist/gateway/contacts.d.ts +43 -6
- package/dist/gateway/contacts.d.ts.map +1 -1
- package/dist/gateway/contacts.js +95 -6
- package/dist/gateway/contacts.js.map +1 -1
- package/dist/gateway/registry.d.ts +3 -2
- package/dist/gateway/registry.d.ts.map +1 -1
- package/dist/gateway/registry.js +17 -9
- package/dist/gateway/registry.js.map +1 -1
- package/dist/inference/dynamic-catalog.d.ts +39 -0
- package/dist/inference/dynamic-catalog.d.ts.map +1 -0
- package/dist/inference/dynamic-catalog.js +174 -0
- package/dist/inference/dynamic-catalog.js.map +1 -0
- package/dist/inference/model-catalog.d.ts +0 -4
- package/dist/inference/model-catalog.d.ts.map +1 -1
- package/dist/inference/model-catalog.js +4 -42
- package/dist/inference/model-catalog.js.map +1 -1
- package/dist/inference/provider-catalog.d.ts +5 -1
- package/dist/inference/provider-catalog.d.ts.map +1 -1
- package/dist/inference/provider-catalog.js +22 -2
- package/dist/inference/provider-catalog.js.map +1 -1
- package/dist/learning/auto-router.d.ts.map +1 -1
- package/dist/learning/auto-router.js +19 -9
- package/dist/learning/auto-router.js.map +1 -1
- package/dist/learning/cost-tracker.js +3 -3
- package/dist/learning/cost-tracker.js.map +1 -1
- package/dist/tools/gateway-send.d.ts.map +1 -1
- package/dist/tools/gateway-send.js +30 -6
- package/dist/tools/gateway-send.js.map +1 -1
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +421 -0
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/workflow/templates.d.ts.map +1 -1
- package/dist/workflow/templates.js +11 -47
- package/dist/workflow/templates.js.map +1 -1
- package/package.json +1 -1
- package/src/web-dashboard/public/assets/{index-TbDN1H0u.js → index-D7wybkPX.js} +74 -74
- package/src/web-dashboard/public/assets/index-D7wybkPX.js.map +1 -0
- package/src/web-dashboard/public/assets/{index-BMXmCrQF.css → index-Dy2JGPXg.css} +1 -1
- package/src/web-dashboard/public/index.html +2 -2
- 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
|