feat(providers): add Cline free-tier models and sync Freebuff catalog

- Cline: 6 free models, cline-cli product headers, API-key auth,
  {data} envelope unwrap, workos: prefix handling
- Freebuff: muse-spark 1.3 → 1.2 (upstream withdrawal 2026-09-07),
  DeepSeek V4.1 Flash rename
This commit is contained in:
MUH. IQRAM BAHRING committed 2026-09-11 12:21:35 +08:00
1 parent 8b34cfaa44
commit b0505067b2
16 files changed
+319 -64

No files matched your search

+4 -2
View File
@@ -468,9 +468,11 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
}
const errMsg = formatProviderError(error, provider, model, HTTP_STATUS.BAD_GATEWAY);
if (log?.errorLine) {
log.errorLine(reqTag, "✗", `ERROR 502 · ${provider}/${model} · ${Date.now() - requestStartTime}ms\n ${errMsg}`);
log.errorLine(reqTag, "✗", `ERROR ${error?.status || HTTP_STATUS.BAD_GATEWAY} · ${provider}/${model} · ${Date.now() - requestStartTime}ms\n ${errMsg}`);
}
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, errMsg, error?.resetsAtMs || undefined);
// Carry the executor's own status (429/409 quota gates) when it threw one;
// fall back to 502 for generic throws.
return createErrorResult(error?.status || HTTP_STATUS.BAD_GATEWAY, errMsg, error?.resetsAtMs || undefined);
}
// Handle 401/403 - try token refresh (skip for noAuth providers)
@@ -138,6 +138,21 @@ function openAICompletionToResponses(responseBody, customToolNames = null) {
};
}
/**
* Unwrap gateway envelopes around an OpenAI Chat Completions body.
* Some OpenAI-compatible gateways wrap the body in a `data` envelope
* (Cline: {data, success}) — without this, choices/usage don't resolve at
* top level downstream and surface as "no completion choices".
* Generic guard, no provider hardcode: only fires when the OpenAI body is
* nested under `data`.
*/
export function unwrapDataEnvelope(responseBody) {
if (responseBody && !responseBody.choices && responseBody.data?.choices) {
return responseBody.data;
}
return responseBody;
}
/**
* Translate non-streaming response body from provider format → OpenAI format.
*/
@@ -305,6 +320,9 @@ export async function handleNonStreamingResponse({ providerResponse, provider, m
}
reqLogger.logProviderResponse(providerResponse.status, providerResponse.statusText, providerResponse.headers, responseBody);
// Unwrap AFTER logging (raw envelope stays in the log for forensics) but
// BEFORE usage extraction/translation so choices/usage resolve downstream.
responseBody = unwrapDataEnvelope(responseBody);
if (onRequestSuccess) {
Promise.resolve()
.then(onRequestSuccess)