twinclaw 1.2.3 → 1.2.5

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.
@@ -526,17 +526,7 @@ export class ModelRouter {
526
526
  this.recordEvent('attempt', input.config, `Attempting ${input.config.model} (profile=${input.directive.profile}, severity=${input.directive.severity}).`);
527
527
  const startedAt = this.nowFn();
528
528
  const timeoutMs = 60000; // 60 second timeout
529
- // For providers that don't support tools well, strip them from the request
530
- // This includes fallback models that use OpenRouter/StepFun which have tool issues
531
- const providerId = this.resolveProviderId(input.config);
532
- const noToolProviders = ['stepfun', 'openrouter', 'unknown'];
533
- const isFallbackModel = input.config.id === MODEL_SLOT_IDS.FALLBACK_1 || input.config.id === MODEL_SLOT_IDS.FALLBACK_2;
534
529
  let payload = input.payload;
535
- if ((noToolProviders.includes(providerId) || isFallbackModel) && input.payload.tools) {
536
- payload = { ...input.payload };
537
- delete payload.tools;
538
- delete payload.tool_choice;
539
- }
540
530
  try {
541
531
  const controller = new AbortController();
542
532
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
@@ -670,16 +660,7 @@ export class ModelRouter {
670
660
  const responseContentParts = [];
671
661
  const toolCalls = [];
672
662
  // For providers that don't support tools well, strip them from the request
673
- // This includes fallback models that use OpenRouter/StepFun which have tool issues
674
- const providerId = this.resolveProviderId(input.config);
675
- const noToolProviders = ['stepfun', 'openrouter', 'unknown'];
676
- const isFallbackModel = input.config.id === MODEL_SLOT_IDS.FALLBACK_1 || input.config.id === MODEL_SLOT_IDS.FALLBACK_2;
677
663
  let payload = input.payload;
678
- if ((noToolProviders.includes(providerId) || isFallbackModel) && input.payload.tools) {
679
- payload = { ...input.payload };
680
- delete payload.tools;
681
- delete payload.tool_choice;
682
- }
683
664
  try {
684
665
  const controller = new AbortController();
685
666
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
@@ -1094,11 +1075,33 @@ export class ModelRouter {
1094
1075
  apiKeyEnvName: 'MODAL_API_KEY',
1095
1076
  });
1096
1077
  }
1097
- if (openRouterApiKey && !configModels.find((m) => m.id === MODEL_SLOT_IDS.FALLBACK_1)) {
1078
+ // Use Groq as fallback (supports tools, fast, cheap)
1079
+ const groqApiKey = getConfigValue('GROQ_API_KEY');
1080
+ if (groqApiKey && !configModels.find((m) => m.id === MODEL_SLOT_IDS.FALLBACK_1)) {
1081
+ const groqInfo = PROVIDER_INFO.groq;
1082
+ configModels.push({
1083
+ id: MODEL_SLOT_IDS.FALLBACK_1,
1084
+ model: 'qwen/qwen3-32b',
1085
+ baseURL: groqInfo?.baseURL || 'https://api.groq.com/openai/v1/chat/completions',
1086
+ apiKeyEnvName: 'GROQ_API_KEY',
1087
+ });
1088
+ }
1089
+ else if (openRouterApiKey && !configModels.find((m) => m.id === MODEL_SLOT_IDS.FALLBACK_1)) {
1090
+ // Use OpenRouter free models that support tools
1098
1091
  const orInfo = PROVIDER_INFO.openrouter;
1099
1092
  configModels.push({
1100
1093
  id: MODEL_SLOT_IDS.FALLBACK_1,
1101
- model: 'stepfun/step-3.5-flash:free',
1094
+ model: 'arcee-ai/trinity-large-preview:free',
1095
+ baseURL: orInfo?.baseURL ? (orInfo.baseURL.endsWith('/chat/completions') ? orInfo.baseURL : `${orInfo.baseURL}/chat/completions`) : 'https://openrouter.ai/api/v1/chat/completions',
1096
+ apiKeyEnvName: 'OPENROUTER_API_KEY',
1097
+ });
1098
+ }
1099
+ // Add OpenRouter free model as FALLBACK_2 if available
1100
+ if (openRouterApiKey && !configModels.find((m) => m.id === MODEL_SLOT_IDS.FALLBACK_2)) {
1101
+ const orInfo = PROVIDER_INFO.openrouter;
1102
+ configModels.push({
1103
+ id: MODEL_SLOT_IDS.FALLBACK_2,
1104
+ model: 'arcee-ai/trinity-mini:free',
1102
1105
  baseURL: orInfo?.baseURL ? (orInfo.baseURL.endsWith('/chat/completions') ? orInfo.baseURL : `${orInfo.baseURL}/chat/completions`) : 'https://openrouter.ai/api/v1/chat/completions',
1103
1106
  apiKeyEnvName: 'OPENROUTER_API_KEY',
1104
1107
  });
@@ -1,6 +1,6 @@
1
1
  import Groq from 'groq-sdk';
2
2
  import { createReadStream } from 'node:fs';
3
- const DEFAULT_MODEL = 'whisper-large-v3-turbo';
3
+ const DEFAULT_MODEL = 'whisper-large-v3';
4
4
  /**
5
5
  * Speech-to-Text service backed by Groq's hosted Whisper API (free tier).
6
6
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twinclaw",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "Eagle-eyed agentic AI gateway with multi-modal hooks and proactive memory.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {