From 8b57f88ca3d0df9cdd12405378a1b9e67ffbe198 Mon Sep 17 00:00:00 2001 From: zhang-qiang Date: Tue, 24 Mar 2026 17:42:52 +0800 Subject: [PATCH] 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 --- open-sse/config/providerRegistry.ts | 2 +- open-sse/handlers/imageGeneration.ts | 32 ++++++++++++++----- open-sse/handlers/sseParser.ts | 11 +++++-- .../translator/request/openai-to-claude.ts | 2 +- open-sse/utils/stream.ts | 16 ++++++---- open-sse/utils/streamHandler.ts | 2 +- zws_docs/ZWS_README_V8.md | 4 +++ 7 files changed, 50 insertions(+), 19 deletions(-) diff --git a/open-sse/config/providerRegistry.ts b/open-sse/config/providerRegistry.ts index 756a588183..50d2d8fb4c 100644 --- a/open-sse/config/providerRegistry.ts +++ b/open-sse/config/providerRegistry.ts @@ -1353,7 +1353,7 @@ export const REGISTRY: Record = { { 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": { diff --git a/open-sse/handlers/imageGeneration.ts b/open-sse/handlers/imageGeneration.ts index 3aae2aa4fe..6a1ec17ec0 100644 --- a/open-sse/handlers/imageGeneration.ts +++ b/open-sse/handlers/imageGeneration.ts @@ -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) => ({ + 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}` }; } } diff --git a/open-sse/handlers/sseParser.ts b/open-sse/handlers/sseParser.ts index 577a13fbe6..f0b958383a 100644 --- a/open-sse/handlers/sseParser.ts +++ b/open-sse/handlers/sseParser.ts @@ -23,12 +23,19 @@ export function parseSSEToOpenAIResponse(rawSSE, fallbackModel) { const first = chunks[0]; const contentParts = []; const reasoningParts = []; - const accumulatedToolCalls = new Map(); + type AccumulatedToolCall = { + id: string | null; + index: number; + type: string; + function: { name: string; arguments: string }; + }; + + const accumulatedToolCalls = new Map(); let unknownToolCallSeq = 0; let finishReason = "stop"; let usage = null; - const getToolCallKey = (toolCall: any) => { + const getToolCallKey = (toolCall: Record) => { if (toolCall?.id) return `id:${toolCall.id}`; if (Number.isInteger(toolCall?.index)) return `idx:${toolCall.index}`; unknownToolCallSeq += 1; diff --git a/open-sse/translator/request/openai-to-claude.ts b/open-sse/translator/request/openai-to-claude.ts index 4b002e36da..14231ad544 100644 --- a/open-sse/translator/request/openai-to-claude.ts +++ b/open-sse/translator/request/openai-to-claude.ts @@ -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 */ diff --git a/open-sse/utils/stream.ts b/open-sse/utils/stream.ts index 74e1e84e48..987d309dbd 100644 --- a/open-sse/utils/stream.ts +++ b/open-sse/utils/stream.ts @@ -57,6 +57,8 @@ type TranslateState = ReturnType & { accumulatedContent?: string; }; +type UsageTokenRecord = Record; + 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; - const eu = extracted as Record; + 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"; diff --git a/open-sse/utils/streamHandler.ts b/open-sse/utils/streamHandler.ts index 40832c341a..825945fb2f 100644 --- a/open-sse/utils/streamHandler.ts +++ b/open-sse/utils/streamHandler.ts @@ -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 = { diff --git a/zws_docs/ZWS_README_V8.md b/zws_docs/ZWS_README_V8.md index 4a2326de13..e0218cfe1c 100644 --- a/zws_docs/ZWS_README_V8.md +++ b/zws_docs/ZWS_README_V8.md @@ -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` / 显式接口替代 `any`。`open-sse/utils/stream.ts` 中 passthrough 分支原先对 `state`(在 passthrough 模式下为 `null`)做 `(state as any).passthroughHasToolCalls`,已改为闭包内独立布尔变量 `passthroughHasToolCalls`。