fix(open-sse): satisfy T11 explicit-any budget (regex counts word any)

- Reword comments that contained the token any; replace any types with typed shapes

- stream.ts: passthrough tool-call flag via local boolean (state is null in passthrough)

- Document T11 in zws_docs/ZWS_README_V8.md

Made-with: Cursor
This commit is contained in:
zhang-qiang
2026-03-24 17:42:52 +08:00
parent 3e9fdc777e
commit 8b57f88ca3
7 changed files with 50 additions and 19 deletions

View File

@@ -1353,7 +1353,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
{ id: "qwen/qwen3-32b", name: "Qwen3 32B (Puter)" },
{ id: "qwen/qwen3-coder", name: "Qwen3 Coder 480B (Puter)" },
],
passthroughModels: true, // 500+ models available — users can type any Puter model ID
passthroughModels: true, // 500+ models available — users can type arbitrary Puter model IDs
},
"cloudflare-ai": {

View File

@@ -1081,6 +1081,21 @@ async function handleComfyUIImageGeneration({ model, provider, providerConfig, b
}
}
type Imagen3ImageGenArgs = {
model: string;
provider: string;
providerConfig: { baseUrl: string };
body: { prompt?: string; size?: string; n?: number };
credentials: { apiKey?: string; accessToken?: string };
log?: { info?: (tag: string, msg: string) => void; error?: (tag: string, msg: string) => void } | null;
};
type Imagen3NormalizedImage = {
b64_json?: unknown;
url?: unknown;
revised_prompt?: string;
};
/**
* Handle Imagen 3 image generation
*/
@@ -1091,7 +1106,7 @@ async function handleImagen3ImageGeneration({
body,
credentials,
log,
}: any) {
}: Imagen3ImageGenArgs) {
const startTime = Date.now();
const token = credentials.apiKey || credentials.accessToken;
const aspectRatio = mapImageSize(body.size);
@@ -1142,11 +1157,11 @@ async function handleImagen3ImageGeneration({
const data = await response.json();
// Normalize response to OpenAI format
const images: any[] = [];
const images: Imagen3NormalizedImage[] = [];
if (Array.isArray(data.images)) {
images.push(
...data.images.map((img: any) => ({
b64_json: img.image || img.b64_json || img.url || img,
...data.images.map((img: Record<string, unknown>) => ({
b64_json: img.image ?? img.b64_json ?? img.url ?? img,
revised_prompt: body.prompt,
}))
);
@@ -1174,8 +1189,9 @@ async function handleImagen3ImageGeneration({
success: true,
data: { created: data.created || Math.floor(Date.now() / 1000), data: images },
};
} catch (err: any) {
if (log) log.error("IMAGE", `${provider} fetch error: ${err.message}`);
} catch (err: unknown) {
const errMsg = err instanceof Error ? err.message : String(err);
if (log) log.error("IMAGE", `${provider} fetch error: ${errMsg}`);
saveCallLog({
method: "POST",
@@ -1184,9 +1200,9 @@ async function handleImagen3ImageGeneration({
model: `${provider}/${model}`,
provider,
duration: Date.now() - startTime,
error: err.message,
error: errMsg,
}).catch(() => {});
return { success: false, status: 502, error: `Image provider error: ${err.message}` };
return { success: false, status: 502, error: `Image provider error: ${errMsg}` };
}
}

View File

@@ -23,12 +23,19 @@ export function parseSSEToOpenAIResponse(rawSSE, fallbackModel) {
const first = chunks[0];
const contentParts = [];
const reasoningParts = [];
const accumulatedToolCalls = new Map<string, any>();
type AccumulatedToolCall = {
id: string | null;
index: number;
type: string;
function: { name: string; arguments: string };
};
const accumulatedToolCalls = new Map<string, AccumulatedToolCall>();
let unknownToolCallSeq = 0;
let finishReason = "stop";
let usage = null;
const getToolCallKey = (toolCall: any) => {
const getToolCallKey = (toolCall: Record<string, unknown>) => {
if (toolCall?.id) return `id:${toolCall.id}`;
if (Number.isInteger(toolCall?.index)) return `idx:${toolCall.index}`;
unknownToolCallSeq += 1;

View File

@@ -29,7 +29,7 @@ type ClaudeTool = {
/**
* T02: Recursively strips empty text blocks from content arrays.
* Anthropic returns 400 "text content blocks must be non-empty" if any
* Anthropic returns 400 "text content blocks must be non-empty" when a
* text block has text: "". Must also recurse into nested tool_result.content.
* Ref: sub2api PR #1212
*/

View File

@@ -57,6 +57,8 @@ type TranslateState = ReturnType<typeof initState> & {
accumulatedContent?: string;
};
type UsageTokenRecord = Record<string, number>;
function getOpenAIIntermediateChunks(value: unknown): unknown[] {
if (!value || typeof value !== "object") return [];
const candidate = (value as JsonRecord)._openaiIntermediate;
@@ -108,7 +110,9 @@ export function createSSEStream(options: StreamOptions = {}) {
} = options;
let buffer = "";
let usage = null;
let usage: UsageTokenRecord | null = null;
/** Passthrough (OpenAI CC shape): saw tool_calls in stream before finish_reason */
let passthroughHasToolCalls = false;
// State for translate mode (accumulatedContent for call log response body)
const state: TranslateState | null =
@@ -223,9 +227,9 @@ export function createSSEStream(options: StreamOptions = {}) {
if (extracted) {
// Non-destructive merge: never overwrite a positive value with 0
// message_start carries input_tokens, message_delta carries output_tokens;
if (!usage) usage = {} as any;
const u = usage as Record<string, number>;
const eu = extracted as Record<string, number>;
if (!usage) usage = {};
const u = usage;
const eu = extracted as UsageTokenRecord;
if (eu.prompt_tokens > 0) u.prompt_tokens = eu.prompt_tokens;
if (eu.completion_tokens > 0) u.completion_tokens = eu.completion_tokens;
if (eu.total_tokens > 0) u.total_tokens = eu.total_tokens;
@@ -266,7 +270,7 @@ export function createSSEStream(options: StreamOptions = {}) {
// T18: Track if we saw tool calls
if (delta?.tool_calls && delta.tool_calls.length > 0) {
(state as any).passthroughHasToolCalls = true;
passthroughHasToolCalls = true;
}
const content = delta?.content || delta?.reasoning_content;
@@ -288,7 +292,7 @@ export function createSSEStream(options: StreamOptions = {}) {
// T18: Normalize finish_reason to 'tool_calls' if tool calls were used
if (
isFinishChunk &&
(state as any).passthroughHasToolCalls &&
passthroughHasToolCalls &&
parsed.choices[0].finish_reason !== "tool_calls"
) {
parsed.choices[0].finish_reason = "tool_calls";

View File

@@ -140,7 +140,7 @@ export function createDisconnectAwareStream(transformStream, streamController) {
const errorMsg = error instanceof Error ? error.message : "Upstream stream error";
const statusCode =
typeof error === "object" && error !== null && "statusCode" in error
? (error as any).statusCode
? Number((error as { statusCode?: unknown }).statusCode) || 500
: 500;
const errorEvent = {

View File

@@ -229,3 +229,7 @@ Zod 4 下全键 `record` 与「只提交部分协议」的 PATCH 语义冲突,
`src/lib/zed-oauth/keychain-reader.ts` 顶层 **`import keytar`** 会在 `next build` 收集路由数据时加载原生模块;无 `libsecret` 的 Linux runner 会失败。改为 **`await import("keytar")`** 动态加载,失败则 **跳过读钥匙串**(返回空列表 / null构建不再依赖本机 keytar。
> 若本文随上游合并,可删除文首「仅供 ZWS 作者自用」一句;**ZWS** 为贡献者笔名,可保留作变更索引。
### T11`check:any-budget:t11`
脚本 `scripts/check-t11-any-budget.mjs` 用正则统计文件中 **单词 `any`**(含注释里的英文 *any*)。失败原因通常是:注释误触(如 “type **any** model ID”、或真实 `: any` / `as any`。处理方式:改写注释用词、用 `unknown` / `Record<string, unknown>` / 显式接口替代 `any`。`open-sse/utils/stream.ts` 中 passthrough 分支原先对 `state`(在 passthrough 模式下为 `null`)做 `(state as any).passthroughHasToolCalls`,已改为闭包内独立布尔变量 `passthroughHasToolCalls`。