feat: add SSE passthrough stream action
This commit is contained in:
@@ -4,6 +4,7 @@ type PageKey = "overview" | "requests" | "profiles" | "rules" | "logs";
|
||||
type Tone = "" | "success" | "error";
|
||||
type ReasoningMatchMode = "formula_518n_minus_2" | "manual";
|
||||
type AuthMode = "passthrough" | "fixed_bearer" | "manual_bearer" | "auth_json";
|
||||
type StreamAction = "strict_502" | "disconnect" | "passthrough";
|
||||
|
||||
type GatewayConfig = {
|
||||
profile_name?: string;
|
||||
@@ -30,6 +31,7 @@ type GatewayConfig = {
|
||||
upstream_fetch_retry_attempts?: number;
|
||||
upstream_fetch_retry_backoff_ms?: number;
|
||||
non_stream_status_code?: number;
|
||||
stream_action?: StreamAction;
|
||||
log_match?: boolean;
|
||||
};
|
||||
|
||||
@@ -190,6 +192,7 @@ type ProfileFormModel = {
|
||||
retryable_error_messages?: string[];
|
||||
upstream_fetch_retry_attempts?: string;
|
||||
upstream_fetch_retry_backoff_ms?: string;
|
||||
stream_action?: StreamAction;
|
||||
endpoints?: string[];
|
||||
};
|
||||
|
||||
@@ -207,6 +210,7 @@ type Profile = {
|
||||
model_remap?: string;
|
||||
reasoning_match_mode?: ReasoningMatchMode;
|
||||
reasoning_equals?: string;
|
||||
stream_action?: StreamAction;
|
||||
};
|
||||
form?: ProfileFormModel;
|
||||
};
|
||||
@@ -321,6 +325,7 @@ type ProfileFormState = {
|
||||
retryable_error_messages: string;
|
||||
upstream_fetch_retry_attempts: string;
|
||||
upstream_fetch_retry_backoff_ms: string;
|
||||
stream_action: StreamAction;
|
||||
endpoints: string;
|
||||
};
|
||||
|
||||
@@ -346,6 +351,7 @@ type RuleFormState = {
|
||||
upstream_fetch_retry_backoff_ms: string;
|
||||
endpoints: string;
|
||||
non_stream_status_code: string;
|
||||
stream_action: StreamAction;
|
||||
log_match: boolean;
|
||||
};
|
||||
|
||||
@@ -454,6 +460,7 @@ const defaultProfileForm: ProfileFormState = {
|
||||
retryable_error_messages: "Selected model is at capacity. Please try a different model.\nstream disconnected before completion: Concurrency limit exceeded for account, please retry later",
|
||||
upstream_fetch_retry_attempts: "5",
|
||||
upstream_fetch_retry_backoff_ms: "350",
|
||||
stream_action: "strict_502",
|
||||
endpoints: "/responses\n/chat/completions\n/v1/responses\n/v1/chat/completions",
|
||||
};
|
||||
|
||||
@@ -694,6 +701,24 @@ function normalizeReasoningMode(value: unknown): ReasoningMatchMode {
|
||||
return value === "manual" ? "manual" : "formula_518n_minus_2";
|
||||
}
|
||||
|
||||
function normalizeStreamAction(value: unknown): StreamAction {
|
||||
if (value === "disconnect" || value === "passthrough") {
|
||||
return value;
|
||||
}
|
||||
return "strict_502";
|
||||
}
|
||||
|
||||
function formatStreamAction(value: unknown) {
|
||||
const action = normalizeStreamAction(value);
|
||||
if (action === "passthrough") {
|
||||
return "直通";
|
||||
}
|
||||
if (action === "disconnect") {
|
||||
return "透传后断开";
|
||||
}
|
||||
return "严格检查";
|
||||
}
|
||||
|
||||
function formatReasoningMode(mode: ReasoningMatchMode) {
|
||||
return mode === "manual" ? "manual" : "518n-2";
|
||||
}
|
||||
@@ -782,6 +807,7 @@ function profileFormFromStatus(status: StatusPayload | null): ProfileFormState {
|
||||
upstream_fetch_retry_backoff_ms: String(
|
||||
config.upstream_fetch_retry_backoff_ms ?? defaultProfileForm.upstream_fetch_retry_backoff_ms,
|
||||
),
|
||||
stream_action: normalizeStreamAction(config.stream_action),
|
||||
endpoints: Array.isArray(config.endpoints) ? config.endpoints.join("\n") : defaultProfileForm.endpoints,
|
||||
};
|
||||
}
|
||||
@@ -814,6 +840,7 @@ function profileFormFromProfile(profile: Profile): ProfileFormState {
|
||||
form.upstream_fetch_retry_attempts || defaultProfileForm.upstream_fetch_retry_attempts,
|
||||
upstream_fetch_retry_backoff_ms:
|
||||
form.upstream_fetch_retry_backoff_ms || defaultProfileForm.upstream_fetch_retry_backoff_ms,
|
||||
stream_action: normalizeStreamAction(form.stream_action),
|
||||
endpoints: Array.isArray(form.endpoints) ? form.endpoints.join("\n") : "",
|
||||
};
|
||||
}
|
||||
@@ -866,6 +893,7 @@ function ruleFormFromStatus(status: StatusPayload | null): RuleFormState {
|
||||
),
|
||||
endpoints: Array.isArray(config.endpoints) ? config.endpoints.join("\n") : "",
|
||||
non_stream_status_code: String(config.non_stream_status_code || 502),
|
||||
stream_action: normalizeStreamAction(config.stream_action),
|
||||
log_match: Boolean(config.log_match),
|
||||
};
|
||||
}
|
||||
@@ -1168,6 +1196,7 @@ export default function App() {
|
||||
upstream_fetch_retry_backoff_ms: Number.parseInt(ruleForm.upstream_fetch_retry_backoff_ms, 10),
|
||||
endpoints: splitLines(ruleForm.endpoints),
|
||||
non_stream_status_code: Number.parseInt(ruleForm.non_stream_status_code, 10),
|
||||
stream_action: ruleForm.stream_action,
|
||||
log_match: ruleForm.log_match,
|
||||
}),
|
||||
});
|
||||
@@ -1276,6 +1305,7 @@ export default function App() {
|
||||
retryable_error_messages: splitLines(profileForm.retryable_error_messages),
|
||||
upstream_fetch_retry_attempts: Number.parseInt(profileForm.upstream_fetch_retry_attempts, 10),
|
||||
upstream_fetch_retry_backoff_ms: Number.parseInt(profileForm.upstream_fetch_retry_backoff_ms, 10),
|
||||
stream_action: profileForm.stream_action,
|
||||
endpoints: splitLines(profileForm.endpoints),
|
||||
}),
|
||||
});
|
||||
@@ -2202,6 +2232,7 @@ export default function App() {
|
||||
label="Rule Mode"
|
||||
value={formatReasoningMode(normalizeReasoningMode(profile.summary?.reasoning_match_mode))}
|
||||
/>
|
||||
<MiniStat label="SSE" value={formatStreamAction(profile.summary?.stream_action)} />
|
||||
</div>
|
||||
</article>
|
||||
))
|
||||
@@ -2388,6 +2419,16 @@ export default function App() {
|
||||
<input type="number" min={0} step={50} value={profileForm.upstream_fetch_retry_backoff_ms} onChange={(event) => setProfileForm({ ...profileForm, upstream_fetch_retry_backoff_ms: event.target.value })} />
|
||||
</Field>
|
||||
</div>
|
||||
<Field label="stream_action" hint="passthrough 对 stream:true 请求关闭 reasoning retry;若上游返回 text/event-stream,会把原始 chunk 直接转发,不解析、缓存或规范化。">
|
||||
<select
|
||||
value={profileForm.stream_action}
|
||||
onChange={(event) => setProfileForm({ ...profileForm, stream_action: normalizeStreamAction(event.target.value) })}
|
||||
>
|
||||
<option value="strict_502">strict_502:完整检查后回放</option>
|
||||
<option value="disconnect">disconnect:边转发边检查,命中后断开</option>
|
||||
<option value="passthrough">passthrough:SSE 原样直通</option>
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="endpoints">
|
||||
<textarea value={profileForm.endpoints} onChange={(event) => setProfileForm({ ...profileForm, endpoints: event.target.value })} />
|
||||
</Field>
|
||||
@@ -2717,6 +2758,16 @@ export default function App() {
|
||||
命中时写日志
|
||||
</label>
|
||||
</div>
|
||||
<Field label="stream_action" hint="passthrough 会立即使当前实例的 SSE 原样直通;若要让重启或切换 profile 后仍保持,请在 Profiles 页保存该字段。">
|
||||
<select
|
||||
value={ruleForm.stream_action}
|
||||
onChange={(event) => setRuleForm({ ...ruleForm, stream_action: normalizeStreamAction(event.target.value) })}
|
||||
>
|
||||
<option value="strict_502">strict_502:完整检查后回放</option>
|
||||
<option value="disconnect">disconnect:边转发边检查,命中后断开</option>
|
||||
<option value="passthrough">passthrough:SSE 原样直通</option>
|
||||
</select>
|
||||
</Field>
|
||||
<div className="toolbar">
|
||||
<button className="primary" type="submit">
|
||||
保存并立即生效
|
||||
|
||||
Reference in New Issue
Block a user