{"record":{"id":"e89d75d32a6aeffe","repo":"janhq/jan","slug":"friendly-requesturlof-input","errorCode":null,"errorMessage":"${friendly} (${requestUrlOf(input)})","messagePattern":"\\$\\{friendly\\} \\(\\$\\{requestUrlOf\\(input\\)\\}\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web-app/src/lib/model-factory.ts","lineNumber":465,"sourceCode":"    let rawBody: Record<string, unknown> | null = null\n    if (init?.method === 'POST' || !init?.method) {\n      try {\n        rawBody = init?.body ? JSON.parse(init.body as string) : {}\n      } catch (e) {\n        throw new Error(\n          `Failed to parse request body as JSON: ${e instanceof Error ? e.message : String(e)}`\n        )\n      }\n      init = { ...init, body: JSON.stringify(buildBody(rawBody!, true)) }\n    }\n\n    let res: Response\n    try {\n      res = await baseFetch(input, init)\n    } catch (err) {\n      const friendly = describeTransportError(err)\n      if (!friendly) throw err\n      throw new Error(`${friendly} (${requestUrlOf(input)})`)\n    }\n    if (res.ok) {\n      // OpenAI-compatible servers may interleave custom named SSE events (e.g.\n      // tool-progress) with chat.completion.chunk data; the AI SDK validates\n      // every data line against the chunk schema, so strip non-default events.\n      // Opt-in only: Anthropic and the OpenAI Responses API use named SSE\n      // events as their protocol, so filtering there blanks the whole stream.\n      const contentType = res.headers.get('content-type') || ''\n      if (\n        filterNamedSseEvents &&\n        res.body &&\n        contentType.includes('text/event-stream')\n      ) {\n        return new Response(filterDefaultSseEvents(res.body), {\n          status: res.status,\n          statusText: res.statusText,\n          headers: res.headers,\n        })","sourceCodeStart":447,"sourceCodeEnd":483,"githubUrl":"https://github.com/janhq/jan/blob/fad3f12a147d138388a66f0d92a02b2675f65294/web-app/src/lib/model-factory.ts#L447-L483","documentation":"Thrown by the custom `fetch` wrapper in model-factory when `baseFetch(input, init)` rejects with a transport-class error (DNS, timeout, TLS, connection refused/reset). `describeTransportError` classifies the low-level message into a friendly string; if it returns non-null, the error is re-thrown as `${friendly} (${requestUrlOf(input)})`. Non-transport errors (e.g. programming errors) are re-thrown untouched.","triggerScenarios":"Any chat-completion / model request where the provider endpoint is unreachable: DNS resolution failure for the provider Base URL; connection refused (server down); TLS/certificate mismatch; request timeout; broken pipe mid-stream. The wrapper catches the fetch rejection and attaches the request URL for debuggability.","commonSituations":"Wrong/mistyped Base URL in provider settings; local llama.cpp server not running (connection refused); self-signed cert without trust store config; corporate proxy; provider outage; intermittent network drop mid-generation.","solutions":["Verify the provider Base URL is correct and the server is running (curl the endpoint).","For local servers: start llama.cpp/the engine before sending.","For TLS errors: install/trust the certificate or use the correct HTTPS endpoint.","For timeouts: increase the provider timeout or reduce model size for faster first-token.","Check DNS/proxy egress to the provider hostname."],"exampleFix":"// before\n} catch (err) {\n  const friendly = describeTransportError(err)\n  if (!friendly) throw err\n  throw new Error(`${friendly} (${requestUrlOf(input)})`)\n}\n\n// after\n} catch (err) {\n  const friendly = describeTransportError(err)\n  if (!friendly) throw err\n  console.error('[transport] request to', requestUrlOf(input), 'failed:', err)\n  throw new Error(`${friendly} (${requestUrlOf(input)})`)\n}","handlingStrategy":"try-catch","validationCode":"// Validate provider reachability before the first real request.\nasync function isProviderReachable(baseUrl: string): Promise<boolean> {\n  try {\n    const u = new URL(baseUrl)\n    return u.protocol === 'http:' || u.protocol === 'https:'\n  } catch {\n    return false\n  }\n}","typeGuard":"function isTransportError(err: unknown): boolean {\n  const raw = err instanceof Error ? err.message : String(err)\n  return /error sending request|failed to fetch|networkerror|connection (refused|reset|closed)|timed out|tls|certificate|dns/i.test(raw)\n}","tryCatchPattern":"try {\n  res = await baseFetch(input, init)\n} catch (err) {\n  const friendly = describeTransportError(err)\n  if (!friendly) throw err // non-transport — let it propagate\n  console.error('[transport]', requestUrlOf(input), err)\n  throw new Error(`${friendly} (${requestUrlOf(input)})`)\n}","preventionTips":["Verify the provider Base URL and that the server is running before sending.","For local servers, ensure the engine is started.","Trust self-signed certs in the app config or use valid HTTPS.","Increase provider timeout for slow first-token responses.","Check DNS/proxy egress to the provider hostname."],"tags":["transport","network","provider","fetch-wrapper","dns"],"backgroundTag":null,"analyzedSha":"fad3f12a147d138388a66f0d92a02b2675f65294","analyzedAt":"2026-08-12T20:33:47.516Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}