twinclaw 1.4.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -297,6 +297,97 @@ export const STATIC_MODEL_CATALOG = [
|
|
|
297
297
|
pricing: 'Free tier available',
|
|
298
298
|
description: 'Fast inference with Groq'
|
|
299
299
|
},
|
|
300
|
+
// Alias entries for twinclaw.json model IDs that differ from catalog IDs
|
|
301
|
+
{
|
|
302
|
+
id: 'groq-qwen-qwen3-32b',
|
|
303
|
+
name: 'Qwen 3 32B (Groq)',
|
|
304
|
+
provider: 'groq',
|
|
305
|
+
model: 'qwen/qwen3-32b',
|
|
306
|
+
contextLength: 32768,
|
|
307
|
+
supportsStreaming: true,
|
|
308
|
+
pricing: 'Free tier available',
|
|
309
|
+
description: 'Fast inference with Groq'
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: 'groq-moonshotai-kimi-k2-instruct-0905',
|
|
313
|
+
name: 'Kimi K2 Instruct (Groq)',
|
|
314
|
+
provider: 'groq',
|
|
315
|
+
model: 'moonshotai/kimi-k2-instruct-0905',
|
|
316
|
+
contextLength: 32768,
|
|
317
|
+
supportsStreaming: true,
|
|
318
|
+
pricing: 'Free tier available',
|
|
319
|
+
description: 'Fast inference with Groq'
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: 'openrouter-z-ai-glm-4-5-air-free',
|
|
323
|
+
name: 'GLM-4.5 Air (Free)',
|
|
324
|
+
provider: 'openrouter',
|
|
325
|
+
model: 'z-ai/glm-4.5-air:free',
|
|
326
|
+
contextLength: 32000,
|
|
327
|
+
supportsStreaming: true,
|
|
328
|
+
pricing: 'Free',
|
|
329
|
+
description: 'Free model via OpenRouter'
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
id: 'openrouter-stepfun-step-3-5-flash-free',
|
|
333
|
+
name: 'StepFun Flash (Free)',
|
|
334
|
+
provider: 'openrouter',
|
|
335
|
+
model: 'stepfun/step-3.5-flash:free',
|
|
336
|
+
contextLength: 32000,
|
|
337
|
+
supportsStreaming: true,
|
|
338
|
+
pricing: 'Free',
|
|
339
|
+
description: 'Free model via OpenRouter'
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
id: 'openrouter-qwen-qwen3-coder-free',
|
|
343
|
+
name: 'Qwen3 Coder (Free)',
|
|
344
|
+
provider: 'openrouter',
|
|
345
|
+
model: 'qwen/qwen3-coder:free',
|
|
346
|
+
contextLength: 32000,
|
|
347
|
+
supportsStreaming: true,
|
|
348
|
+
pricing: 'Free',
|
|
349
|
+
description: 'Free model via OpenRouter'
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
id: 'openrouter-arcee-ai-trinity-mini-free',
|
|
353
|
+
name: 'Trinity Mini (Free)',
|
|
354
|
+
provider: 'openrouter',
|
|
355
|
+
model: 'arcee-ai/trinity-mini:free',
|
|
356
|
+
contextLength: 32000,
|
|
357
|
+
supportsStreaming: true,
|
|
358
|
+
pricing: 'Free',
|
|
359
|
+
description: 'Free model via OpenRouter'
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
id: 'openrouter-arcee-ai-trinity-large-preview-free',
|
|
363
|
+
name: 'Trinity Large Preview (Free)',
|
|
364
|
+
provider: 'openrouter',
|
|
365
|
+
model: 'arcee-ai/trinity-large-preview:free',
|
|
366
|
+
contextLength: 32000,
|
|
367
|
+
supportsStreaming: true,
|
|
368
|
+
pricing: 'Free',
|
|
369
|
+
description: 'Free model via OpenRouter'
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
id: 'copilot-gpt-4o',
|
|
373
|
+
name: 'GPT-4o (Copilot)',
|
|
374
|
+
provider: 'copilot',
|
|
375
|
+
model: 'gpt-4o',
|
|
376
|
+
contextLength: 128000,
|
|
377
|
+
supportsStreaming: true,
|
|
378
|
+
pricing: 'Included with Copilot',
|
|
379
|
+
description: 'GitHub Copilot model'
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
id: 'copilot-gemini-flash',
|
|
383
|
+
name: 'Gemini Flash (Copilot)',
|
|
384
|
+
provider: 'copilot',
|
|
385
|
+
model: 'gemini-2.0-flash',
|
|
386
|
+
contextLength: 1000000,
|
|
387
|
+
supportsStreaming: true,
|
|
388
|
+
pricing: 'Included with Copilot',
|
|
389
|
+
description: 'GitHub Copilot model'
|
|
390
|
+
},
|
|
300
391
|
// OpenRouter Free Models
|
|
301
392
|
{
|
|
302
393
|
id: 'openrouter-stepfun-flash',
|
|
@@ -69,10 +69,14 @@ class LearningSystem {
|
|
|
69
69
|
return entry;
|
|
70
70
|
}
|
|
71
71
|
#findSimilar(description) {
|
|
72
|
+
if (!description)
|
|
73
|
+
return undefined;
|
|
72
74
|
const searchTerms = description.toLowerCase().split(/\s+/);
|
|
73
75
|
let bestMatch;
|
|
74
76
|
let bestScore = 0;
|
|
75
77
|
for (const memory of this.#memories.values()) {
|
|
78
|
+
if (!memory.description)
|
|
79
|
+
continue;
|
|
76
80
|
const memTerms = memory.description.toLowerCase().split(/\s+/);
|
|
77
81
|
let score = 0;
|
|
78
82
|
for (const term of searchTerms) {
|
|
@@ -868,6 +868,12 @@ export class ModelRouter {
|
|
|
868
868
|
return 'copilot';
|
|
869
869
|
if (url.includes('api.github.com'))
|
|
870
870
|
return 'github';
|
|
871
|
+
if (url.includes('groq.com'))
|
|
872
|
+
return 'groq';
|
|
873
|
+
if (url.includes('api.openai.com'))
|
|
874
|
+
return 'openai';
|
|
875
|
+
if (url.includes('api.anthropic.com'))
|
|
876
|
+
return 'anthropic';
|
|
871
877
|
return 'unknown';
|
|
872
878
|
}
|
|
873
879
|
getModelCooldownState(modelId) {
|
|
@@ -1043,11 +1049,71 @@ export class ModelRouter {
|
|
|
1043
1049
|
return 0;
|
|
1044
1050
|
});
|
|
1045
1051
|
for (const def of sortedDefinitions) {
|
|
1052
|
+
// Resolve missing model/apiKeyEnvName from the static catalog and provider info
|
|
1053
|
+
let resolvedModel = def.model;
|
|
1054
|
+
let resolvedApiKeyEnvName = def.apiKeyEnvName;
|
|
1055
|
+
if (!resolvedModel || !resolvedApiKeyEnvName) {
|
|
1056
|
+
// Try to find the model in the static catalog by ID
|
|
1057
|
+
const catalogEntry = STATIC_MODEL_CATALOG.find(m => m.id === def.id);
|
|
1058
|
+
if (catalogEntry) {
|
|
1059
|
+
if (!resolvedModel)
|
|
1060
|
+
resolvedModel = catalogEntry.model;
|
|
1061
|
+
if (!resolvedApiKeyEnvName) {
|
|
1062
|
+
const providerInfo = PROVIDER_INFO[catalogEntry.provider];
|
|
1063
|
+
resolvedApiKeyEnvName = providerInfo?.apiKeyEnvName ?? '';
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
// If still missing, infer provider from baseURL and resolve apiKeyEnvName
|
|
1068
|
+
if (!resolvedApiKeyEnvName) {
|
|
1069
|
+
const url = def.baseURL.toLowerCase();
|
|
1070
|
+
if (url.includes('groq.com'))
|
|
1071
|
+
resolvedApiKeyEnvName = 'GROQ_API_KEY';
|
|
1072
|
+
else if (url.includes('openrouter.ai'))
|
|
1073
|
+
resolvedApiKeyEnvName = 'OPENROUTER_API_KEY';
|
|
1074
|
+
else if (url.includes('modal.direct'))
|
|
1075
|
+
resolvedApiKeyEnvName = 'MODAL_API_KEY';
|
|
1076
|
+
else if (url.includes('api.openai.com'))
|
|
1077
|
+
resolvedApiKeyEnvName = 'OPENAI_API_KEY';
|
|
1078
|
+
else if (url.includes('generativelanguage.googleapis.com'))
|
|
1079
|
+
resolvedApiKeyEnvName = 'GEMINI_API_KEY';
|
|
1080
|
+
else if (url.includes('githubcopilot.com'))
|
|
1081
|
+
resolvedApiKeyEnvName = 'GITHUB_TOKEN';
|
|
1082
|
+
else if (url.includes('api.anthropic.com'))
|
|
1083
|
+
resolvedApiKeyEnvName = 'ANTHROPIC_API_KEY';
|
|
1084
|
+
}
|
|
1085
|
+
// If model is still missing, try to derive from the ID (e.g., groq-qwen-qwen3-32b -> qwen/qwen3-32b)
|
|
1086
|
+
if (!resolvedModel) {
|
|
1087
|
+
// Try a fuzzy match against catalog entries by normalizing IDs
|
|
1088
|
+
const normalizedDefId = def.id.toLowerCase();
|
|
1089
|
+
const fuzzyMatch = STATIC_MODEL_CATALOG.find(m => {
|
|
1090
|
+
const normalizedCatalogId = m.id.toLowerCase();
|
|
1091
|
+
return normalizedDefId === normalizedCatalogId ||
|
|
1092
|
+
normalizedDefId.includes(normalizedCatalogId) ||
|
|
1093
|
+
normalizedCatalogId.includes(normalizedDefId);
|
|
1094
|
+
});
|
|
1095
|
+
if (fuzzyMatch) {
|
|
1096
|
+
resolvedModel = fuzzyMatch.model;
|
|
1097
|
+
}
|
|
1098
|
+
else {
|
|
1099
|
+
// Last resort: strip provider prefix and use the rest as model name
|
|
1100
|
+
const parts = def.id.split('-');
|
|
1101
|
+
if (parts.length > 1) {
|
|
1102
|
+
// For IDs like 'groq-qwen-qwen3-32b', try 'qwen/qwen3-32b'
|
|
1103
|
+
// For IDs like 'openrouter-z-ai-glm-4-5-air-free', try 'z-ai/glm-4-5-air:free'
|
|
1104
|
+
resolvedModel = def.id; // Use ID as-is — the API will reject if wrong, triggering fallback
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
if (!resolvedModel) {
|
|
1109
|
+
logThought(`[Router] Skipping definition '${def.id}': unable to resolve model name.`);
|
|
1110
|
+
continue;
|
|
1111
|
+
}
|
|
1046
1112
|
configModels.push({
|
|
1047
1113
|
id: def.id,
|
|
1048
|
-
model:
|
|
1114
|
+
model: resolvedModel,
|
|
1049
1115
|
baseURL: def.baseURL,
|
|
1050
|
-
apiKeyEnvName:
|
|
1116
|
+
apiKeyEnvName: resolvedApiKeyEnvName || '',
|
|
1051
1117
|
});
|
|
1052
1118
|
}
|
|
1053
1119
|
// Even with definitions, add fallback providers in case primary fails
|