Claw Web commited on
Commit
a51697c
·
1 Parent(s): 3705a49

fix: protect API config from corrupted settings — hardcoded fallbacks + sanitization

Browse files

- resolveApiConfig: FALLBACK_URL and FALLBACK_MODEL hardcoded
- ENV.forgeApiUrl fallback to https://router.huggingface.co/v1
- ENV.forgeApiKey fallback to process.env.BUILT_IN_FORGE_API_KEY
- Custom provider path: fallback to FALLBACK_URL when no provider match
- chat-endpoint.ts: sanitize all settings before passing to agent
- Empty strings, 'default' provider, non-http URLs all treated as null
- temperature/topP use ?? instead of || to preserve 0 values
- Debug logging in resolveApiConfig for troubleshooting

server/runtime/agent.ts CHANGED
@@ -210,48 +210,56 @@ const RETRY_BASE_DELAY_MS = 2000;
210
  * Resolve the API URL and key based on provider config
211
  */
212
  function resolveApiConfig(config: AgentConfig) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  // Treat empty, null, masked, or built-in providers as "use server default"
214
  const hasCustomKey = config.apiKey && config.apiKey.length > 4 && !config.apiKey.startsWith("••••");
215
  if (config.apiProvider === "claw" || config.apiProvider === "default" || config.apiProvider === "huggingface" || !hasCustomKey) {
216
- // Resolve model aliases
217
- const aliasMap: Record<string, string> = {
218
- // Xiaomi MiMo (default)
219
- mimo: "XiaomiMiMo/MiMo-V2-Flash",
220
- "mimo-flash": "XiaomiMiMo/MiMo-V2-Flash",
221
- "mimo-v2": "XiaomiMiMo/MiMo-V2-Flash",
222
- // Qwen models
223
- "qwen-coder": "Qwen/Qwen3-Coder-Next",
224
- "qwen3-8b": "Qwen/Qwen3-8B",
225
- "qwen3-coder": "Qwen/Qwen3-Coder-30B-A3B-Instruct",
226
- // Llama
227
- llama: "meta-llama/Llama-3.3-70B-Instruct",
228
- "llama-70b": "meta-llama/Llama-3.3-70B-Instruct",
229
- // DeepSeek
230
- deepseek: "deepseek-ai/DeepSeek-V3.2",
231
- "deepseek-r1": "deepseek-ai/DeepSeek-R1",
232
- // OpenAI GPT-5.x family (if user has OpenAI key)
233
- "gpt5": "gpt-5.4",
234
- "gpt-5": "gpt-5.4",
235
- "gpt54": "gpt-5.4",
236
- // Anthropic aliases (for compatibility)
237
- opus: "claude-opus-4-6",
238
- sonnet: "claude-sonnet-4-6",
239
- haiku: "claude-haiku-4-5-20251213",
240
- // xAI
241
- grok: "grok-3",
242
- "grok-3": "grok-3",
243
- };
244
- const defaultModel = process.env.DEFAULT_MODEL || "XiaomiMiMo/MiMo-V2-Flash";
245
  const resolvedModel = aliasMap[config.model] || config.model || defaultModel;
246
  // Use BUILT_IN_FORGE_API_URL from env — HuggingFace router or OpenAI
247
- const baseUrl = ENV.forgeApiUrl.replace(/\/$/, "");
 
 
248
  return {
249
  url: `${baseUrl}/chat/completions`,
250
- key: ENV.forgeApiKey,
251
- model: resolvedModel,
252
  };
253
  }
254
 
 
255
  let baseUrl = config.apiBaseUrl || "";
256
  if (!baseUrl) {
257
  const providers: Record<string, string> = {
@@ -262,13 +270,15 @@ function resolveApiConfig(config: AgentConfig) {
262
  anthropic: "https://api.anthropic.com/v1",
263
  ollama: "http://localhost:11434/v1",
264
  };
265
- baseUrl = providers[config.apiProvider] || "";
266
  }
267
 
 
 
268
  return {
269
  url: `${baseUrl.replace(/\/$/, "")}/chat/completions`,
270
  key: config.apiKey,
271
- model: config.model,
272
  };
273
  }
274
 
 
210
  * Resolve the API URL and key based on provider config
211
  */
212
  function resolveApiConfig(config: AgentConfig) {
213
+ // ─── HARDCODED FALLBACK — always works even if settings are corrupted ───
214
+ const FALLBACK_URL = "https://router.huggingface.co/v1";
215
+ const FALLBACK_MODEL = "XiaomiMiMo/MiMo-V2-Flash";
216
+
217
+ // Resolve model aliases (used for both default and custom paths)
218
+ const aliasMap: Record<string, string> = {
219
+ // Xiaomi MiMo (default)
220
+ mimo: "XiaomiMiMo/MiMo-V2-Flash",
221
+ "mimo-flash": "XiaomiMiMo/MiMo-V2-Flash",
222
+ "mimo-v2": "XiaomiMiMo/MiMo-V2-Flash",
223
+ // Qwen models
224
+ "qwen-coder": "Qwen/Qwen3-Coder-Next",
225
+ "qwen3-8b": "Qwen/Qwen3-8B",
226
+ "qwen3-coder": "Qwen/Qwen3-Coder-30B-A3B-Instruct",
227
+ // Llama
228
+ llama: "meta-llama/Llama-3.3-70B-Instruct",
229
+ "llama-70b": "meta-llama/Llama-3.3-70B-Instruct",
230
+ // DeepSeek
231
+ deepseek: "deepseek-ai/DeepSeek-V3.2",
232
+ "deepseek-r1": "deepseek-ai/DeepSeek-R1",
233
+ // OpenAI GPT-5.x family (if user has OpenAI key)
234
+ "gpt5": "gpt-5.4",
235
+ "gpt-5": "gpt-5.4",
236
+ "gpt54": "gpt-5.4",
237
+ // Anthropic aliases (for compatibility)
238
+ opus: "claude-opus-4-6",
239
+ sonnet: "claude-sonnet-4-6",
240
+ haiku: "claude-haiku-4-5-20251213",
241
+ // xAI
242
+ grok: "grok-3",
243
+ "grok-3": "grok-3",
244
+ };
245
+
246
  // Treat empty, null, masked, or built-in providers as "use server default"
247
  const hasCustomKey = config.apiKey && config.apiKey.length > 4 && !config.apiKey.startsWith("••••");
248
  if (config.apiProvider === "claw" || config.apiProvider === "default" || config.apiProvider === "huggingface" || !hasCustomKey) {
249
+ const defaultModel = process.env.DEFAULT_MODEL || FALLBACK_MODEL;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  const resolvedModel = aliasMap[config.model] || config.model || defaultModel;
251
  // Use BUILT_IN_FORGE_API_URL from env — HuggingFace router or OpenAI
252
+ const baseUrl = (ENV.forgeApiUrl || FALLBACK_URL).replace(/\/$/, "");
253
+ const apiKey = ENV.forgeApiKey || process.env.BUILT_IN_FORGE_API_KEY || "";
254
+ console.log(`[agent] resolveApiConfig: using server default. URL=${baseUrl}, model=${resolvedModel}, hasKey=${!!apiKey}`);
255
  return {
256
  url: `${baseUrl}/chat/completions`,
257
+ key: apiKey,
258
+ model: resolvedModel || FALLBACK_MODEL,
259
  };
260
  }
261
 
262
+ // Custom provider path — user has their own API key
263
  let baseUrl = config.apiBaseUrl || "";
264
  if (!baseUrl) {
265
  const providers: Record<string, string> = {
 
270
  anthropic: "https://api.anthropic.com/v1",
271
  ollama: "http://localhost:11434/v1",
272
  };
273
+ baseUrl = providers[config.apiProvider] || FALLBACK_URL;
274
  }
275
 
276
+ const resolvedModel = aliasMap[config.model] || config.model || FALLBACK_MODEL;
277
+ console.log(`[agent] resolveApiConfig: custom provider. URL=${baseUrl}, model=${resolvedModel}`);
278
  return {
279
  url: `${baseUrl.replace(/\/$/, "")}/chat/completions`,
280
  key: config.apiKey,
281
+ model: resolvedModel,
282
  };
283
  }
284
 
server/runtime/chat-endpoint.ts CHANGED
@@ -105,14 +105,20 @@ export async function handleChatStream(req: Request, res: Response) {
105
  const effortLevel = getEffortLevel(sessionId);
106
 
107
  // Build config from settings
 
 
 
 
 
 
108
  const config = {
109
- model: session.model || settings?.model || process.env.DEFAULT_MODEL || "XiaomiMiMo/MiMo-V2-Flash",
110
- apiProvider: session.provider || settings?.apiProvider || "huggingface",
111
- apiKey: (settings?.apiKey && settings.apiKey.length > 4 && !settings.apiKey.startsWith("••••")) ? settings.apiKey : null,
112
- apiBaseUrl: settings?.apiBaseUrl || null,
113
  maxTokens: settings?.maxTokens || 16384,
114
- temperature: settings?.temperature || 0.7,
115
- topP: settings?.topP || 1,
116
  systemPrompt: buildSystemPrompt({
117
  memory: settings?.memoryContent,
118
  effortLevel,
 
105
  const effortLevel = getEffortLevel(sessionId);
106
 
107
  // Build config from settings
108
+ // Sanitize settings — treat empty strings, "default", and masked values as null
109
+ const sanitizedModel = session.model || (settings?.model && settings.model.trim()) || process.env.DEFAULT_MODEL || "XiaomiMiMo/MiMo-V2-Flash";
110
+ const sanitizedProvider = session.provider || (settings?.apiProvider && settings.apiProvider !== "default" && settings.apiProvider.trim()) || "huggingface";
111
+ const sanitizedKey = (settings?.apiKey && settings.apiKey.length > 4 && !settings.apiKey.startsWith("••••")) ? settings.apiKey : null;
112
+ const sanitizedBaseUrl = (settings?.apiBaseUrl && settings.apiBaseUrl.trim() && settings.apiBaseUrl.startsWith("http")) ? settings.apiBaseUrl : null;
113
+
114
  const config = {
115
+ model: sanitizedModel,
116
+ apiProvider: sanitizedProvider,
117
+ apiKey: sanitizedKey,
118
+ apiBaseUrl: sanitizedBaseUrl,
119
  maxTokens: settings?.maxTokens || 16384,
120
+ temperature: settings?.temperature ?? 0.7,
121
+ topP: settings?.topP ?? 1,
122
  systemPrompt: buildSystemPrompt({
123
  memory: settings?.memoryContent,
124
  effortLevel,