mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-19 13:42:09 +03:00
fix(cline): force upstream streaming for Cline/ClinePass (streaming-only API)
Cline's API (api.cline.bot) only implements streaming (streamText). A non-streaming request returns HTTP 500 "generateText is not implemented" (Claude models) or HTTP 502 "empty response" (others). Live-verified on the VPS: stream:true → works (STREAM_OK), stream:false → fails. This is why testing a Cline model in the dashboard (the test button sends stream:false) failed. Fix (reuses the existing isClaudeCodeCompatible mechanism, no new handler): - Flag `cline` and `clinepass` registry entries with `forceStream: true`. - In chatCore, OR `providerRequiresStreaming` into `upstreamStream` (line 1591) so the upstream request always streams for these providers, while the client's original `stream` intent still drives the response format. The existing non-streaming branch (parseNonStreamingResponseBody) already accumulates the upstream SSE and converts it back to JSON for stream:false clients — the same path Claude-Code-compatible providers already use. Tests (Rule #18): tests/unit/cline-force-stream.test.ts pins the registry flags + resolveStreamFlag forcing behavior. Live VPS before/after recorded on the PR.
This commit is contained in:
@@ -5,6 +5,11 @@ export const clineProvider: RegistryEntry = {
|
||||
alias: "cl",
|
||||
format: "openai",
|
||||
executor: "openai",
|
||||
// Cline's API only implements streaming (streamText). A non-streaming request
|
||||
// returns "generateText is not implemented" / an empty body, so force upstream
|
||||
// streaming and let chatCore convert the SSE back to JSON for stream:false
|
||||
// clients (e.g. the model-test button, non-streaming API callers).
|
||||
forceStream: true,
|
||||
baseUrl: "https://api.cline.bot/api/v1/chat/completions",
|
||||
authType: "oauth",
|
||||
authHeader: "Authorization",
|
||||
|
||||
@@ -9,6 +9,11 @@ export const clinepassProvider: RegistryEntry = {
|
||||
alias: "clinepass",
|
||||
format: "openai",
|
||||
executor: "default",
|
||||
// ClinePass shares Cline's streaming-only API — a non-streaming request returns
|
||||
// "generateText is not implemented" / an empty body. Force upstream streaming;
|
||||
// chatCore accumulates the SSE and converts it back to JSON for stream:false
|
||||
// clients. (Same as the sibling `cline` provider.)
|
||||
forceStream: true,
|
||||
baseUrl: "https://api.cline.bot/api/v1/chat/completions",
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
|
||||
@@ -1588,7 +1588,13 @@ export async function handleChatCore({
|
||||
headers: clientRawRequest?.headers,
|
||||
userAgent,
|
||||
});
|
||||
const upstreamStream = stream || isClaudeCodeCompatible;
|
||||
// `forceStream` providers (e.g. Cline / ClinePass) only implement upstream
|
||||
// streaming — a non-streaming request returns "generateText is not implemented"
|
||||
// / an empty body. Force the upstream request to stream even when the client
|
||||
// wants JSON; the non-streaming branch below accumulates the SSE and converts
|
||||
// it back to JSON (same mechanism already used for Claude-Code-compatible
|
||||
// providers via isClaudeCodeCompatible).
|
||||
const upstreamStream = stream || isClaudeCodeCompatible || providerRequiresStreaming;
|
||||
let ccSessionId: string | null = null;
|
||||
const stripTypes = getStripTypesForProviderModel(provider || "", model || "");
|
||||
|
||||
|
||||
37
tests/unit/cline-force-stream.test.ts
Normal file
37
tests/unit/cline-force-stream.test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { REGISTRY } from "@omniroute/open-sse/config/providers/index.ts";
|
||||
import { resolveStreamFlag } from "@omniroute/open-sse/utils/aiSdkCompat.ts";
|
||||
|
||||
// Cline / ClinePass only implement upstream streaming — a non-streaming request
|
||||
// returns "generateText is not implemented" / an empty body. They must carry
|
||||
// `forceStream: true` so chatCore forces upstream streaming (upstreamStream) even
|
||||
// when the client wants JSON, then converts the SSE back to JSON. Regression guard
|
||||
// for the "cline model test → generateText is not implemented / empty response"
|
||||
// bug (live-verified on the VPS: stream:true works, stream:false failed).
|
||||
|
||||
test("cline provider is flagged forceStream (streaming-only upstream)", () => {
|
||||
assert.equal(REGISTRY.cline?.forceStream, true);
|
||||
});
|
||||
|
||||
test("clinepass provider is flagged forceStream (streaming-only upstream)", () => {
|
||||
assert.equal(REGISTRY.clinepass?.forceStream, true);
|
||||
});
|
||||
|
||||
test("resolveStreamFlag forces streaming for a forceStream provider even when the client sent stream:false", () => {
|
||||
// providerRequiresStreaming derives from REGISTRY[provider].forceStream === true
|
||||
const providerRequiresStreaming = REGISTRY.cline?.forceStream === true;
|
||||
assert.equal(
|
||||
resolveStreamFlag(false, "application/json", "openai", { providerRequiresStreaming }),
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test("resolveStreamFlag still honors stream:false for a normal (non-forceStream) provider", () => {
|
||||
const providerRequiresStreaming = REGISTRY.openai?.forceStream === true; // false
|
||||
assert.equal(
|
||||
resolveStreamFlag(false, "application/json", "openai", { providerRequiresStreaming }),
|
||||
false
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user