@calliopelabs/cli 2.3.0 → 2.5.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 +17 -0
- package/dist/agents/agent-config-loader.js +1 -1
- package/dist/agents/agent-config-presets.js +13 -13
- package/dist/agents/agent-config-presets.js.map +1 -1
- package/dist/agents/agent-config-types.d.ts +1 -1
- package/dist/agents/agent-config-types.d.ts.map +1 -1
- package/dist/agents/dynamic-tools.d.ts.map +1 -1
- package/dist/agents/dynamic-tools.js +39 -10
- package/dist/agents/dynamic-tools.js.map +1 -1
- package/dist/agents/sdk-backend.js +1 -1
- package/dist/agents/sdk-backend.js.map +1 -1
- package/dist/api-server.d.ts +9 -0
- package/dist/api-server.d.ts.map +1 -1
- package/dist/api-server.js +74 -3
- package/dist/api-server.js.map +1 -1
- package/dist/auto-checkpoint.d.ts.map +1 -1
- package/dist/auto-checkpoint.js +50 -17
- package/dist/auto-checkpoint.js.map +1 -1
- package/dist/auto-compressor.d.ts.map +1 -1
- package/dist/auto-compressor.js +9 -5
- package/dist/auto-compressor.js.map +1 -1
- package/dist/bin.d.ts +8 -0
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +59 -4
- package/dist/bin.js.map +1 -1
- package/dist/branching.d.ts.map +1 -1
- package/dist/branching.js +14 -1
- package/dist/branching.js.map +1 -1
- package/dist/checkpoint.d.ts.map +1 -1
- package/dist/checkpoint.js +13 -1
- package/dist/checkpoint.js.map +1 -1
- package/dist/cli/agent.d.ts.map +1 -1
- package/dist/cli/agent.js +19 -3
- package/dist/cli/agent.js.map +1 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +99 -0
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +32 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/types.js +1 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +42 -4
- package/dist/diff.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +30 -3
- package/dist/errors.js.map +1 -1
- package/dist/headless.d.ts.map +1 -1
- package/dist/headless.js +56 -2
- package/dist/headless.js.map +1 -1
- package/dist/hooks.d.ts +8 -2
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +97 -11
- package/dist/hooks.js.map +1 -1
- package/dist/idle-eviction.d.ts.map +1 -1
- package/dist/idle-eviction.js +8 -1
- package/dist/idle-eviction.js.map +1 -1
- package/dist/markdown.d.ts.map +1 -1
- package/dist/markdown.js +32 -10
- package/dist/markdown.js.map +1 -1
- package/dist/mcp.d.ts +35 -5
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +186 -12
- package/dist/mcp.js.map +1 -1
- package/dist/model-detection.d.ts +14 -1
- package/dist/model-detection.d.ts.map +1 -1
- package/dist/model-detection.js +307 -114
- package/dist/model-detection.js.map +1 -1
- package/dist/model-router.js +7 -7
- package/dist/model-router.js.map +1 -1
- package/dist/parallel-tools.d.ts +9 -1
- package/dist/parallel-tools.d.ts.map +1 -1
- package/dist/parallel-tools.js +6 -5
- package/dist/parallel-tools.js.map +1 -1
- package/dist/plugins.d.ts +37 -0
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +87 -0
- package/dist/plugins.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +36 -2
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/bedrock.d.ts.map +1 -1
- package/dist/providers/bedrock.js +81 -17
- package/dist/providers/bedrock.js.map +1 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +2 -0
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js +19 -10
- package/dist/providers/types.js.map +1 -1
- package/dist/risk.d.ts.map +1 -1
- package/dist/risk.js +15 -5
- package/dist/risk.js.map +1 -1
- package/dist/sandbox-native.d.ts +1 -0
- package/dist/sandbox-native.d.ts.map +1 -1
- package/dist/sandbox-native.js +37 -5
- package/dist/sandbox-native.js.map +1 -1
- package/dist/scope.d.ts +10 -0
- package/dist/scope.d.ts.map +1 -1
- package/dist/scope.js +75 -15
- package/dist/scope.js.map +1 -1
- package/dist/scuttlebot/client.d.ts +83 -0
- package/dist/scuttlebot/client.d.ts.map +1 -0
- package/dist/scuttlebot/client.js +350 -0
- package/dist/scuttlebot/client.js.map +1 -0
- package/dist/scuttlebot/config.d.ts +28 -0
- package/dist/scuttlebot/config.d.ts.map +1 -0
- package/dist/scuttlebot/config.js +91 -0
- package/dist/scuttlebot/config.js.map +1 -0
- package/dist/scuttlebot/http-client.d.ts +63 -0
- package/dist/scuttlebot/http-client.d.ts.map +1 -0
- package/dist/scuttlebot/http-client.js +124 -0
- package/dist/scuttlebot/http-client.js.map +1 -0
- package/dist/scuttlebot/index.d.ts +13 -0
- package/dist/scuttlebot/index.d.ts.map +1 -0
- package/dist/scuttlebot/index.js +10 -0
- package/dist/scuttlebot/index.js.map +1 -0
- package/dist/scuttlebot/irc-client.d.ts +124 -0
- package/dist/scuttlebot/irc-client.d.ts.map +1 -0
- package/dist/scuttlebot/irc-client.js +599 -0
- package/dist/scuttlebot/irc-client.js.map +1 -0
- package/dist/skills.d.ts +19 -0
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +98 -10
- package/dist/skills.js.map +1 -1
- package/dist/smart-router.js +4 -4
- package/dist/smart-router.js.map +1 -1
- package/dist/storage.d.ts +0 -4
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +81 -5
- package/dist/storage.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +232 -38
- package/dist/tools.js.map +1 -1
- package/dist/trust.d.ts +16 -3
- package/dist/trust.d.ts.map +1 -1
- package/dist/trust.js +23 -4
- package/dist/trust.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +13 -4
- package/dist/types.js.map +1 -1
- package/dist/ui/agent.d.ts +1 -1
- package/dist/ui/agent.d.ts.map +1 -1
- package/dist/ui/agent.js +35 -44
- package/dist/ui/agent.js.map +1 -1
- package/dist/ui/chat-input.d.ts +3 -1
- package/dist/ui/chat-input.d.ts.map +1 -1
- package/dist/ui/chat-input.js +82 -17
- package/dist/ui/chat-input.js.map +1 -1
- package/dist/ui/commands.d.ts +2 -0
- package/dist/ui/commands.d.ts.map +1 -1
- package/dist/ui/commands.js +318 -10
- package/dist/ui/commands.js.map +1 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +236 -46
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/input-utils.d.ts +20 -0
- package/dist/ui/input-utils.d.ts.map +1 -0
- package/dist/ui/input-utils.js +35 -0
- package/dist/ui/input-utils.js.map +1 -0
- package/dist/ui/messages.d.ts +6 -2
- package/dist/ui/messages.d.ts.map +1 -1
- package/dist/ui/messages.js +42 -11
- package/dist/ui/messages.js.map +1 -1
- package/dist/ui/modals.d.ts +21 -1
- package/dist/ui/modals.d.ts.map +1 -1
- package/dist/ui/modals.js +67 -5
- package/dist/ui/modals.js.map +1 -1
- package/dist/ui/status-bar.d.ts +4 -1
- package/dist/ui/status-bar.d.ts.map +1 -1
- package/dist/ui/status-bar.js +12 -1
- package/dist/ui/status-bar.js.map +1 -1
- package/dist/ui/types.d.ts +3 -0
- package/dist/ui/types.d.ts.map +1 -1
- package/package.json +4 -7
- package/dist/completion.d.ts +0 -75
- package/dist/completion.d.ts.map +0 -1
- package/dist/completion.js +0 -234
- package/dist/completion.js.map +0 -1
- package/dist/keyboard.d.ts +0 -57
- package/dist/keyboard.d.ts.map +0 -1
- package/dist/keyboard.js +0 -265
- package/dist/keyboard.js.map +0 -1
package/dist/model-detection.js
CHANGED
|
@@ -6,6 +6,17 @@
|
|
|
6
6
|
import OpenAI from 'openai';
|
|
7
7
|
import { select } from '@inquirer/prompts';
|
|
8
8
|
import * as config from './config.js';
|
|
9
|
+
const DEBUG = process.env.CALLIOPE_DEBUG === '1';
|
|
10
|
+
function logModelDetectionWarning(message, error, options = {}) {
|
|
11
|
+
if (options.quiet || !DEBUG) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (error !== undefined) {
|
|
15
|
+
console.warn(message, error);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
console.warn(message);
|
|
19
|
+
}
|
|
9
20
|
// API base URLs for OpenAI-compatible providers
|
|
10
21
|
const PROVIDER_BASE_URLS = {
|
|
11
22
|
openrouter: 'https://openrouter.ai/api/v1',
|
|
@@ -172,7 +183,7 @@ function formatContextLength(tokens) {
|
|
|
172
183
|
/**
|
|
173
184
|
* Get available models for a provider
|
|
174
185
|
*/
|
|
175
|
-
export async function getAvailableModels(provider) {
|
|
186
|
+
export async function getAvailableModels(provider, options = {}) {
|
|
176
187
|
// Check cache first
|
|
177
188
|
const cached = modelCache.get(provider);
|
|
178
189
|
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
|
|
@@ -182,10 +193,10 @@ export async function getAvailableModels(provider) {
|
|
|
182
193
|
try {
|
|
183
194
|
switch (provider) {
|
|
184
195
|
case 'anthropic':
|
|
185
|
-
models = await getAnthropicModels();
|
|
196
|
+
models = await getAnthropicModels(options);
|
|
186
197
|
break;
|
|
187
198
|
case 'google':
|
|
188
|
-
models = await getGoogleModels();
|
|
199
|
+
models = await getGoogleModels(options);
|
|
189
200
|
break;
|
|
190
201
|
case 'openai':
|
|
191
202
|
models = await getOpenAIModels();
|
|
@@ -226,14 +237,16 @@ export async function getAvailableModels(provider) {
|
|
|
226
237
|
modelCache.set(provider, { models, timestamp: Date.now() });
|
|
227
238
|
}
|
|
228
239
|
catch (error) {
|
|
229
|
-
|
|
240
|
+
logModelDetectionWarning(`Failed to fetch models for ${provider}:`, error, options);
|
|
241
|
+
if (options.throwOnError)
|
|
242
|
+
throw error;
|
|
230
243
|
}
|
|
231
244
|
return models;
|
|
232
245
|
}
|
|
233
246
|
/**
|
|
234
247
|
* Get Anthropic models dynamically from API
|
|
235
248
|
*/
|
|
236
|
-
async function getAnthropicModels() {
|
|
249
|
+
async function getAnthropicModels(options = {}) {
|
|
237
250
|
const apiKey = config.getApiKey('anthropic');
|
|
238
251
|
if (!apiKey)
|
|
239
252
|
throw new Error('Anthropic API key not configured');
|
|
@@ -254,18 +267,20 @@ async function getAnthropicModels() {
|
|
|
254
267
|
id: model.id,
|
|
255
268
|
name: model.display_name || formatModelName(model.id),
|
|
256
269
|
description: getAnthropicModelDescription(model.id),
|
|
257
|
-
|
|
270
|
+
// The /v1/models list endpoint does not return the context window;
|
|
271
|
+
// derive it per model family rather than hardcoding a single value.
|
|
272
|
+
contextLength: getModelContextLimit('anthropic', model.id),
|
|
258
273
|
}))
|
|
259
274
|
.sort((a, b) => b.id.localeCompare(a.id)); // Newest first
|
|
260
275
|
}
|
|
261
276
|
catch (error) {
|
|
262
|
-
//
|
|
263
|
-
|
|
277
|
+
// Emergency fallback when the API is unreachable. Keep these as the current
|
|
278
|
+
// shipping models — discovery is the source of truth; this is the offline net.
|
|
279
|
+
logModelDetectionWarning('Failed to fetch Anthropic models, using fallback list', error, options);
|
|
264
280
|
return [
|
|
265
|
-
{ id: 'claude-opus-4-
|
|
266
|
-
{ id: 'claude-sonnet-4-
|
|
267
|
-
{ id: 'claude-
|
|
268
|
-
{ id: 'claude-3-5-haiku-20241022', name: 'Claude 3.5 Haiku', description: 'Fast and affordable', contextLength: 200000 },
|
|
281
|
+
{ id: 'claude-opus-4-8', name: 'Claude Opus 4.8', description: 'Most capable model', contextLength: 1000000 },
|
|
282
|
+
{ id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6', description: 'Balanced intelligence and speed', contextLength: 1000000 },
|
|
283
|
+
{ id: 'claude-haiku-4-5', name: 'Claude Haiku 4.5', description: 'Fast and affordable', contextLength: 200000 },
|
|
269
284
|
];
|
|
270
285
|
}
|
|
271
286
|
}
|
|
@@ -290,7 +305,7 @@ function getAnthropicModelDescription(modelId) {
|
|
|
290
305
|
/**
|
|
291
306
|
* Get Google models dynamically from API
|
|
292
307
|
*/
|
|
293
|
-
async function getGoogleModels() {
|
|
308
|
+
async function getGoogleModels(options = {}) {
|
|
294
309
|
const apiKey = config.getApiKey('google');
|
|
295
310
|
if (!apiKey)
|
|
296
311
|
throw new Error('Google API key not configured');
|
|
@@ -316,7 +331,7 @@ async function getGoogleModels() {
|
|
|
316
331
|
}
|
|
317
332
|
catch (error) {
|
|
318
333
|
// Fallback to known models if API fails
|
|
319
|
-
|
|
334
|
+
logModelDetectionWarning('Failed to fetch Google models, using fallback list', error, options);
|
|
320
335
|
return [
|
|
321
336
|
{ id: 'gemini-2.5-pro-preview-06-05', name: 'Gemini 2.5 Pro', description: 'Most capable', contextLength: 1048576 },
|
|
322
337
|
{ id: 'gemini-2.5-flash-preview-05-20', name: 'Gemini 2.5 Flash', description: 'Fast next-gen', contextLength: 1048576 },
|
|
@@ -594,45 +609,67 @@ async function getBedrockModels() {
|
|
|
594
609
|
const apiKey = config.getApiKey('bedrock');
|
|
595
610
|
// 1. Try gateway/proxy model listing (OpenAI-compatible)
|
|
596
611
|
if (baseUrl) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
602
|
-
}
|
|
603
|
-
const response = await fetch(modelsUrl, { headers });
|
|
604
|
-
if (response.ok) {
|
|
605
|
-
const data = await response.json();
|
|
606
|
-
return data.data
|
|
607
|
-
.filter(model => isCompatibleModel(model.id, 'bedrock'))
|
|
608
|
-
.map(model => ({
|
|
609
|
-
id: model.id,
|
|
610
|
-
name: model.id,
|
|
611
|
-
description: getBedrockModelDescription(model.id),
|
|
612
|
-
contextLength: getBedrockContextLength(model.id),
|
|
613
|
-
}));
|
|
614
|
-
}
|
|
612
|
+
const modelsUrl = baseUrl.endsWith('/v1') ? `${baseUrl}/models` : `${baseUrl}/v1/models`;
|
|
613
|
+
const headers = {};
|
|
614
|
+
if (apiKey) {
|
|
615
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
615
616
|
}
|
|
616
|
-
|
|
617
|
-
|
|
617
|
+
const response = await fetch(modelsUrl, { headers });
|
|
618
|
+
if (response.ok) {
|
|
619
|
+
const data = await response.json();
|
|
620
|
+
return data.data
|
|
621
|
+
.filter(model => isCompatibleModel(model.id, 'bedrock'))
|
|
622
|
+
.map(model => ({
|
|
623
|
+
id: model.id,
|
|
624
|
+
name: model.id,
|
|
625
|
+
description: getBedrockModelDescription(model.id),
|
|
626
|
+
contextLength: getBedrockContextLength(model.id),
|
|
627
|
+
}));
|
|
618
628
|
}
|
|
629
|
+
throw new Error(`Bedrock gateway ${baseUrl} returned ${response.status}. Check BEDROCK_BASE_URL / BEDROCK_API_KEY.`);
|
|
619
630
|
}
|
|
620
|
-
// 2.
|
|
631
|
+
// 2. Native AWS path — let errors bubble up so the user sees the real reason.
|
|
632
|
+
return discoverBedrockModelsNative();
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Resolve AWS credentials via the `aws` CLI. Handles SSO profiles,
|
|
636
|
+
* role-assumption profiles, and anything else `aws` knows about.
|
|
637
|
+
* Returns null if the CLI isn't installed or the profile resolution fails.
|
|
638
|
+
*/
|
|
639
|
+
async function resolveAwsCredentialsViaCli(profile) {
|
|
621
640
|
try {
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
641
|
+
const { execFileSync } = await import('child_process');
|
|
642
|
+
let output = '';
|
|
643
|
+
try {
|
|
644
|
+
output = execFileSync('aws', ['configure', 'export-credentials', '--profile', profile, '--format', 'env-no-export'], { encoding: 'utf-8', timeout: 10_000, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
645
|
+
}
|
|
646
|
+
catch {
|
|
647
|
+
output = execFileSync('aws', ['configure', 'export-credentials', '--profile', profile, '--format', 'env'], { encoding: 'utf-8', timeout: 10_000, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
629
648
|
}
|
|
649
|
+
const envs = {};
|
|
650
|
+
for (const rawLine of output.split(/\r?\n/)) {
|
|
651
|
+
const line = rawLine.trim();
|
|
652
|
+
const match = line.match(/^(?:export\s+)?([A-Z_]+)\s*=\s*(.+)$/);
|
|
653
|
+
if (!match)
|
|
654
|
+
continue;
|
|
655
|
+
let val = match[2].trim();
|
|
656
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
657
|
+
val = val.slice(1, -1);
|
|
658
|
+
}
|
|
659
|
+
envs[match[1]] = val;
|
|
660
|
+
}
|
|
661
|
+
if (envs.AWS_ACCESS_KEY_ID && envs.AWS_SECRET_ACCESS_KEY) {
|
|
662
|
+
return {
|
|
663
|
+
accessKeyId: envs.AWS_ACCESS_KEY_ID,
|
|
664
|
+
secretAccessKey: envs.AWS_SECRET_ACCESS_KEY,
|
|
665
|
+
sessionToken: envs.AWS_SESSION_TOKEN,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
return null;
|
|
630
669
|
}
|
|
631
670
|
catch {
|
|
632
|
-
|
|
671
|
+
return null;
|
|
633
672
|
}
|
|
634
|
-
// 3. No hardcoded fallback — the default model from types.ts is used when list is empty
|
|
635
|
-
return [];
|
|
636
673
|
}
|
|
637
674
|
/**
|
|
638
675
|
* Discover Bedrock models using the native AWS ListFoundationModels API.
|
|
@@ -647,85 +684,184 @@ async function discoverBedrockModelsNative() {
|
|
|
647
684
|
let accessKeyId = process.env.AWS_ACCESS_KEY_ID || '';
|
|
648
685
|
let secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY || '';
|
|
649
686
|
let sessionToken = process.env.AWS_SESSION_TOKEN;
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
}
|
|
669
|
-
const cred = sections[profile];
|
|
670
|
-
if (cred?.aws_access_key_id) {
|
|
671
|
-
accessKeyId = cred.aws_access_key_id;
|
|
672
|
-
secretAccessKey = cred.aws_secret_access_key || '';
|
|
673
|
-
sessionToken = cred.aws_session_token;
|
|
687
|
+
const profile = process.env.AWS_PROFILE || config.get('awsProfile') || 'default';
|
|
688
|
+
// Parse an INI-style AWS file. Handles both ~/.aws/credentials sections
|
|
689
|
+
// ([name]) and ~/.aws/config sections ([profile name]).
|
|
690
|
+
const readIni = (path) => {
|
|
691
|
+
if (!existsSync(path))
|
|
692
|
+
return {};
|
|
693
|
+
const content = readFileSync(path, 'utf-8');
|
|
694
|
+
const sections = {};
|
|
695
|
+
let section = '';
|
|
696
|
+
for (const line of content.split('\n')) {
|
|
697
|
+
const trimmed = line.trim();
|
|
698
|
+
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith(';'))
|
|
699
|
+
continue;
|
|
700
|
+
const secMatch = trimmed.match(/^\[(.+)\]$/);
|
|
701
|
+
if (secMatch) {
|
|
702
|
+
section = secMatch[1].replace(/^profile\s+/, '');
|
|
703
|
+
sections[section] = sections[section] || {};
|
|
704
|
+
continue;
|
|
674
705
|
}
|
|
706
|
+
const kvMatch = trimmed.match(/^([^=]+?)\s*=\s*(.+)$/);
|
|
707
|
+
if (kvMatch && section)
|
|
708
|
+
sections[section][kvMatch[1].trim()] = kvMatch[2].trim();
|
|
709
|
+
}
|
|
710
|
+
return sections;
|
|
711
|
+
};
|
|
712
|
+
if (!accessKeyId || !secretAccessKey) {
|
|
713
|
+
// Try ~/.aws/credentials (static keys) first, then ~/.aws/config (also
|
|
714
|
+
// used by some setups that put static keys alongside SSO config).
|
|
715
|
+
const credSections = readIni(join(homedir(), '.aws', 'credentials'));
|
|
716
|
+
const configSections = readIni(join(homedir(), '.aws', 'config'));
|
|
717
|
+
const cred = credSections[profile] || configSections[profile];
|
|
718
|
+
if (cred?.aws_access_key_id) {
|
|
719
|
+
accessKeyId = cred.aws_access_key_id;
|
|
720
|
+
secretAccessKey = cred.aws_secret_access_key || '';
|
|
721
|
+
sessionToken = cred.aws_session_token;
|
|
675
722
|
}
|
|
676
723
|
}
|
|
677
|
-
|
|
678
|
-
|
|
724
|
+
// Last resort: shell out to the AWS CLI. This resolves SSO / role-assumption
|
|
725
|
+
// profiles that can't be parsed from the INI files alone.
|
|
726
|
+
if (!accessKeyId || !secretAccessKey) {
|
|
727
|
+
const cliCreds = await resolveAwsCredentialsViaCli(profile);
|
|
728
|
+
if (cliCreds) {
|
|
729
|
+
accessKeyId = cliCreds.accessKeyId;
|
|
730
|
+
secretAccessKey = cliCreds.secretAccessKey;
|
|
731
|
+
sessionToken = cliCreds.sessionToken;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (!accessKeyId || !secretAccessKey) {
|
|
735
|
+
throw new Error(`No AWS credentials found for profile "${profile}". ` +
|
|
736
|
+
`Try: aws sso login --profile ${profile} (for SSO), or set AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY.`);
|
|
737
|
+
}
|
|
679
738
|
const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || config.get('awsRegion') || 'us-east-1';
|
|
680
739
|
const host = `bedrock.${region}.amazonaws.com`;
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
740
|
+
const signedGet = async (path, query) => {
|
|
741
|
+
const url = `https://${host}${path}${query ? '?' + query : ''}`;
|
|
742
|
+
const now = new Date();
|
|
743
|
+
const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, '');
|
|
744
|
+
const dateStamp = amzDate.slice(0, 8);
|
|
745
|
+
const sha256Fn = (d) => createHash('sha256').update(d).digest('hex');
|
|
746
|
+
const hmacFn = (k, d) => createHmac('sha256', k).update(d).digest();
|
|
747
|
+
const headers = { host, 'x-amz-date': amzDate };
|
|
748
|
+
if (sessionToken)
|
|
749
|
+
headers['x-amz-security-token'] = sessionToken;
|
|
750
|
+
const signedHeaderKeys = Object.keys(headers).map(k => k.toLowerCase()).sort();
|
|
751
|
+
const signedHeaders = signedHeaderKeys.join(';');
|
|
752
|
+
const canonicalHeaders = signedHeaderKeys.map(k => `${k}:${headers[k].trim()}`).join('\n') + '\n';
|
|
753
|
+
const payloadHash = sha256Fn('');
|
|
754
|
+
// AWS SigV4: non-S3 services require the canonical URI to be URI-encoded
|
|
755
|
+
// TWICE. Paths here don't currently contain special chars but we normalise
|
|
756
|
+
// for consistency with the chat signing path.
|
|
757
|
+
const canonicalPath = path.split('/').map(s => encodeURIComponent(s)).join('/');
|
|
758
|
+
const canonicalRequest = ['GET', canonicalPath, query, canonicalHeaders, signedHeaders, payloadHash].join('\n');
|
|
759
|
+
const credentialScope = `${dateStamp}/${region}/bedrock/aws4_request`;
|
|
760
|
+
const stringToSign = ['AWS4-HMAC-SHA256', amzDate, credentialScope, sha256Fn(canonicalRequest)].join('\n');
|
|
761
|
+
const kDate = hmacFn('AWS4' + secretAccessKey, dateStamp);
|
|
762
|
+
const kRegion = hmacFn(kDate, region);
|
|
763
|
+
const kService = hmacFn(kRegion, 'bedrock');
|
|
764
|
+
const signingKey = hmacFn(kService, 'aws4_request');
|
|
765
|
+
const signature = createHmac('sha256', signingKey).update(stringToSign).digest('hex');
|
|
766
|
+
headers['Authorization'] = `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
767
|
+
return fetch(url, { headers });
|
|
691
768
|
};
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
return [];
|
|
711
|
-
const data = await response.json();
|
|
712
|
-
if (!data.modelSummaries)
|
|
713
|
-
return [];
|
|
714
|
-
return data.modelSummaries
|
|
715
|
-
.filter(m => {
|
|
716
|
-
// Only text-in/text-out models that support streaming
|
|
717
|
-
if (!m.outputModalities?.includes('TEXT'))
|
|
718
|
-
return false;
|
|
719
|
-
if (!m.inputModalities?.includes('TEXT'))
|
|
720
|
-
return false;
|
|
721
|
-
return isCompatibleModel(m.modelId, 'bedrock');
|
|
722
|
-
})
|
|
769
|
+
// 1. ListFoundationModels (direct on-demand access).
|
|
770
|
+
// Dropped the byInferenceType=ON_DEMAND filter — newer Claude models are only
|
|
771
|
+
// accessible via cross-region inference profiles and don't have ON_DEMAND flag.
|
|
772
|
+
const foundationResp = await signedGet('/foundation-models', 'byOutputModality=TEXT');
|
|
773
|
+
if (!foundationResp.ok) {
|
|
774
|
+
let body = '';
|
|
775
|
+
try {
|
|
776
|
+
body = (await foundationResp.text()).slice(0, 400);
|
|
777
|
+
}
|
|
778
|
+
catch { /* ignore */ }
|
|
779
|
+
throw new Error(`AWS Bedrock ListFoundationModels returned ${foundationResp.status} in region ${region}. ` +
|
|
780
|
+
(body || 'Common causes: (1) no Bedrock access in this region — try us-east-1 or us-west-2; ' +
|
|
781
|
+
'(2) IAM role missing bedrock:ListFoundationModels; (3) SSO token expired — run `aws sso login`.'));
|
|
782
|
+
}
|
|
783
|
+
const foundationData = await foundationResp.json();
|
|
784
|
+
const foundationModels = (foundationData.modelSummaries || [])
|
|
785
|
+
.filter(m => m.inputModalities?.includes('TEXT') && m.outputModalities?.includes('TEXT'))
|
|
786
|
+
.filter(m => bedrockSupportsConverseTools(m.modelId))
|
|
723
787
|
.map(m => ({
|
|
724
788
|
id: m.modelId,
|
|
725
789
|
name: m.modelName || m.modelId,
|
|
726
790
|
description: `${m.providerName || 'Unknown'} — ${getBedrockModelDescription(m.modelId)}`,
|
|
727
791
|
contextLength: getBedrockContextLength(m.modelId),
|
|
728
792
|
}));
|
|
793
|
+
// 2. ListInferenceProfiles — cross-region profile IDs (e.g. us.anthropic.claude-sonnet-4-5-*).
|
|
794
|
+
// Many modern models are ONLY reachable via these, not direct foundation-model IDs.
|
|
795
|
+
// Failures here are non-fatal (older accounts / regions may not support it).
|
|
796
|
+
let profileModels = [];
|
|
797
|
+
try {
|
|
798
|
+
const profileResp = await signedGet('/inference-profiles', '');
|
|
799
|
+
if (profileResp.ok) {
|
|
800
|
+
const profileData = await profileResp.json();
|
|
801
|
+
profileModels = (profileData.inferenceProfileSummaries || [])
|
|
802
|
+
.filter(p => p.status !== 'INACTIVE')
|
|
803
|
+
.filter(p => bedrockSupportsConverseTools(p.inferenceProfileId))
|
|
804
|
+
.map(p => ({
|
|
805
|
+
id: p.inferenceProfileId,
|
|
806
|
+
name: p.inferenceProfileName || p.inferenceProfileId,
|
|
807
|
+
description: `Inference profile — ${getBedrockModelDescription(p.inferenceProfileId)}`,
|
|
808
|
+
contextLength: getBedrockContextLength(p.inferenceProfileId),
|
|
809
|
+
}));
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
catch {
|
|
813
|
+
// Non-fatal — foundation models alone is still useful.
|
|
814
|
+
}
|
|
815
|
+
// Merge. For every inference profile, strip the region prefix (e.g. `us.`,
|
|
816
|
+
// `eu.`, `apac.`, `jp.`) to get the base foundation-model ID it wraps, and
|
|
817
|
+
// drop that base from the foundation list — because newer Claude 4.x / Haiku
|
|
818
|
+
// 4.5 models can ONLY be invoked via their inference profile on on-demand
|
|
819
|
+
// throughput. Showing both would let users pick the invokable-broken raw ID.
|
|
820
|
+
const coveredBaseIds = new Set();
|
|
821
|
+
for (const p of profileModels) {
|
|
822
|
+
const base = p.id.replace(/^[a-z]{2,5}\./, '');
|
|
823
|
+
if (base !== p.id)
|
|
824
|
+
coveredBaseIds.add(base);
|
|
825
|
+
}
|
|
826
|
+
const filteredFoundation = foundationModels.filter(m => !coveredBaseIds.has(m.id));
|
|
827
|
+
const merged = new Map();
|
|
828
|
+
for (const m of filteredFoundation)
|
|
829
|
+
merged.set(m.id, m);
|
|
830
|
+
for (const m of profileModels)
|
|
831
|
+
merged.set(m.id, m);
|
|
832
|
+
return Array.from(merged.values()).sort((a, b) => a.id.localeCompare(b.id));
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Bedrock Converse API tool-calling support. Maintained as a local allowlist
|
|
836
|
+
* because AWS doesn't expose per-model tool capability via the list APIs.
|
|
837
|
+
* See: https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-supported-models-features.html
|
|
838
|
+
* Matches both raw foundation model IDs (e.g. anthropic.claude-3-5-sonnet-*)
|
|
839
|
+
* and cross-region inference profile IDs (e.g. us.anthropic.claude-sonnet-4-5-*).
|
|
840
|
+
*/
|
|
841
|
+
function bedrockSupportsConverseTools(modelId) {
|
|
842
|
+
const id = modelId.toLowerCase();
|
|
843
|
+
// Anthropic Claude 3, 3.5, 3.7, 4, 4.5 (all support tools). Excludes Claude 2.x / Instant.
|
|
844
|
+
if (/anthropic\.claude-(3|opus-4|sonnet-4|haiku-4|3-5|3-7)/.test(id))
|
|
845
|
+
return true;
|
|
846
|
+
// Amazon Nova (Pro / Lite / Micro support Converse tools; Nova Canvas/Reel are image models — excluded)
|
|
847
|
+
if (/amazon\.nova-(pro|lite|micro|premier)/.test(id))
|
|
848
|
+
return true;
|
|
849
|
+
// Cohere Command R / R+ support tools (older Command models do not)
|
|
850
|
+
if (/cohere\.command-r/.test(id))
|
|
851
|
+
return true;
|
|
852
|
+
// Mistral Large (2402, 2407), Pixtral Large, Mistral Small, Nemo
|
|
853
|
+
if (/mistral\.(mistral-large|pixtral|mistral-small|mistral-nemo)/.test(id))
|
|
854
|
+
return true;
|
|
855
|
+
// Meta Llama 3.1+ supports tools via Converse (3.0 and earlier do not)
|
|
856
|
+
if (/meta\.llama(3-1|3-2|3-3|4)/.test(id))
|
|
857
|
+
return true;
|
|
858
|
+
// AI21 Jamba 1.5 supports tools
|
|
859
|
+
if (/ai21\.jamba-1-5/.test(id))
|
|
860
|
+
return true;
|
|
861
|
+
// DeepSeek R1 supports tools
|
|
862
|
+
if (/deepseek\.r1/.test(id))
|
|
863
|
+
return true;
|
|
864
|
+
return false;
|
|
729
865
|
}
|
|
730
866
|
function getBedrockModelDescription(modelId) {
|
|
731
867
|
if (modelId.includes('claude') && modelId.includes('opus'))
|
|
@@ -865,7 +1001,7 @@ export function clearModelCache(provider) {
|
|
|
865
1001
|
export async function preWarmModelCache() {
|
|
866
1002
|
const configuredProviders = config.getConfiguredProviders();
|
|
867
1003
|
// Fetch models for all configured providers in parallel
|
|
868
|
-
await Promise.allSettled(configuredProviders.map(provider => getAvailableModels(provider)));
|
|
1004
|
+
await Promise.allSettled(configuredProviders.map(provider => getAvailableModels(provider, { quiet: true })));
|
|
869
1005
|
}
|
|
870
1006
|
/**
|
|
871
1007
|
* Get model info from cache by ID
|
|
@@ -874,12 +1010,29 @@ export function getModelInfo(provider, modelId) {
|
|
|
874
1010
|
const cached = modelCache.get(provider);
|
|
875
1011
|
if (!cached)
|
|
876
1012
|
return undefined;
|
|
877
|
-
|
|
1013
|
+
// Exact match first.
|
|
1014
|
+
const exact = cached.models.find(m => m.id === modelId);
|
|
1015
|
+
if (exact)
|
|
1016
|
+
return exact;
|
|
1017
|
+
// Otherwise only accept an UNAMBIGUOUS prefix relationship. Loose substring
|
|
1018
|
+
// matching wrongly resolved e.g. `gpt-4` -> `gpt-4o` or `claude-opus-4` ->
|
|
1019
|
+
// `claude-opus-4-8`, returning a different model's context/pricing.
|
|
1020
|
+
const related = cached.models.filter(m => m.id.startsWith(modelId) || modelId.startsWith(m.id));
|
|
1021
|
+
return related.length === 1 ? related[0] : undefined;
|
|
878
1022
|
}
|
|
879
1023
|
/**
|
|
880
1024
|
* Default context limits by model family (fallback when API doesn't provide it)
|
|
881
1025
|
*/
|
|
882
1026
|
const DEFAULT_CONTEXT_LIMITS = {
|
|
1027
|
+
// Anthropic — current 1M-context models matched first (longest key wins).
|
|
1028
|
+
// Everything else (Haiku 4.5, Claude 3.x, and the older -20250514 IDs) falls
|
|
1029
|
+
// through to the generic `claude` 200K entry below.
|
|
1030
|
+
'claude-fable-5': 1000000,
|
|
1031
|
+
'claude-opus-4-8': 1000000,
|
|
1032
|
+
'claude-opus-4-7': 1000000,
|
|
1033
|
+
'claude-opus-4-6': 1000000,
|
|
1034
|
+
'claude-sonnet-4-6': 1000000,
|
|
1035
|
+
'claude-haiku-4-5': 200000,
|
|
883
1036
|
'claude': 200000,
|
|
884
1037
|
'gpt-4o': 128000,
|
|
885
1038
|
'gpt-4-turbo': 128000,
|
|
@@ -941,4 +1094,44 @@ export function getModelContextLimit(provider, modelId) {
|
|
|
941
1094
|
// Ultimate fallback
|
|
942
1095
|
return 32000;
|
|
943
1096
|
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Default max OUTPUT tokens by model family (fallback when the API doesn't
|
|
1099
|
+
* report it). Replaces the old global 8192 cap so modern models can use their
|
|
1100
|
+
* real output ceiling. Unknown models fall through to a conservative 8192.
|
|
1101
|
+
*/
|
|
1102
|
+
const DEFAULT_MAX_OUTPUT = {
|
|
1103
|
+
'claude-fable-5': 128000,
|
|
1104
|
+
'claude-opus-4-8': 128000,
|
|
1105
|
+
'claude-opus-4-7': 128000,
|
|
1106
|
+
'claude-opus-4-6': 128000,
|
|
1107
|
+
'claude-sonnet-4-6': 64000,
|
|
1108
|
+
'claude-haiku-4-5': 64000,
|
|
1109
|
+
'claude': 8192,
|
|
1110
|
+
'gpt-5': 128000,
|
|
1111
|
+
'o1': 100000,
|
|
1112
|
+
'o3': 100000,
|
|
1113
|
+
'gpt-4o': 16384,
|
|
1114
|
+
'gpt-4': 8192,
|
|
1115
|
+
'gemini-2': 8192,
|
|
1116
|
+
'gemini-1.5': 8192,
|
|
1117
|
+
};
|
|
1118
|
+
/**
|
|
1119
|
+
* Get the max output-token ceiling for a model - cached API info first, then
|
|
1120
|
+
* family fallback. Conservative 8192 default keeps unknown/local models safe.
|
|
1121
|
+
*/
|
|
1122
|
+
export function getModelMaxOutput(provider, modelId) {
|
|
1123
|
+
const modelInfo = getModelInfo(provider, modelId);
|
|
1124
|
+
if (modelInfo?.maxOutputTokens) {
|
|
1125
|
+
return modelInfo.maxOutputTokens;
|
|
1126
|
+
}
|
|
1127
|
+
const lowerModel = modelId.toLowerCase();
|
|
1128
|
+
const sortedEntries = Object.entries(DEFAULT_MAX_OUTPUT)
|
|
1129
|
+
.sort((a, b) => b[0].length - a[0].length);
|
|
1130
|
+
for (const [key, limit] of sortedEntries) {
|
|
1131
|
+
if (lowerModel.includes(key.toLowerCase())) {
|
|
1132
|
+
return limit;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
return 8192;
|
|
1136
|
+
}
|
|
944
1137
|
//# sourceMappingURL=model-detection.js.map
|