Skip to content

Comments

fix:修复 Codex API 测试参数兼容性问题与增加支持流式测试#185

Merged
ding113 merged 7 commits intoding113:devfrom
Silentely:fix-provider-test
Nov 22, 2025
Merged

fix:修复 Codex API 测试参数兼容性问题与增加支持流式测试#185
ding113 merged 7 commits intoding113:devfrom
Silentely:fix-provider-test

Conversation

@Silentely
Copy link
Contributor

@Silentely Silentely commented Nov 22, 2025

概述 (Summary)

本 PR 修复了 Codex API 连接测试的兼容性问题,并新增流式响应测试支持,显著提升了供应商测试的可靠性和功能完整性。

主要改进:

  1. 🔧 修复 OpenAI Responses API 参数兼容性问题
  2. 🚀 新增流式响应测试支持 (SSE/NDJSON)
  3. 📊 优化响应内容预览长度 (100 → 500 字符)
  4. 🧩 改进流式数据块合并算法
  5. 🎨 增强测试结果 UI 显示

问题描述 (Problem)

1. 参数兼容性问题

在供应商管理页面测试 OpenAI Responses API 连接时,某些 Codex 中转服务返回 400 错误:

{
  "detail": "Unsupported parameter: max_output_tokens"
}

受影响的供应商:

  • IKunCode(Codex)
  • RightCode(Codex)
  • 其他不支持 max_output_tokens 参数的中转服务

2. 流式响应测试缺失

原测试系统仅支持非流式响应测试,无法验证:

  • SSE (Server-Sent Events) 格式响应
  • NDJSON (Newline-Delimited JSON) 格式响应
  • 流式数据的完整性和正确性

解决方案 (Solution)

1. 移除可选参数,提高兼容性

修复前:

body: (model) => ({
  model,
  max_output_tokens: API_TEST_CONFIG.TEST_MAX_TOKENS,  // ❌ 某些中转服务不支持
  input: "讲一个简短的故事",  // ❌ 格式不符合规范
})

修复后:

body: (model) => ({
  model,
  // ✅ 移除 max_output_tokens,使用最小化参数集
  input: [  // ✅ 修正为数组格式,符合 OpenAI Responses API 规范
    {
      role: "user",
      content: [
        {
          type: "input_text",
          text: API_TEST_CONFIG.TEST_PROMPT,
        },
      ],
    },
  ],
})

关键改进:

  • 移除可选的 max_output_tokens 参数,避免兼容性问题
  • 修正 input 格式为符合规范的数组结构
  • 使用最小化参数集,确保最大兼容性

2. 新增流式响应测试支持

自动检测响应类型

// 检查 Content-Type
const contentType = response.headers.get("content-type") || "";
const isStreamResponse =
  contentType.includes("text/event-stream") || 
  contentType.includes("application/x-ndjson");

// 检查响应内容(兜底检测)
const isLikelySSE = responseText.startsWith("event:") || 
                    responseText.startsWith("data:");

流式数据解析

SSE 格式解析:

async function parseStreamResponse(response: Response): Promise<StreamParseResult> {
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  const chunks: ProviderApiResponse[] = [];
  
  // 逐行解析 SSE 事件
  // 处理 data: {...} 格式
  // 合并所有 chunks 为完整响应
  
  return {
    data: mergedResponse,
    chunksReceived: chunks.length,
    format: "sse",
  };
}

文本格式兜底:

function parseSSEText(text: string): StreamParseResult {
  // 处理未正确设置 Content-Type 的流式响应
  // 支持 "data: {...}\n\n" 格式
  // 跳过 [DONE] 标记
}

智能数据块合并

支持多种 API 格式的流式数据合并:

  • Anthropic Messages API: 合并 content[].text
  • OpenAI Chat Completions API: 合并 choices[].delta.content
  • OpenAI Responses API: 合并 output[].content[].text
  • Gemini API: 合并 candidates[].content.parts[].text
function mergeStreamChunks(chunks: ProviderApiResponse[]): ProviderApiResponse {
  // 取最后一个 usage 信息
  // 合并所有文本内容
  // 重建完整响应对象
}

3. UI 增强

测试结果详情

流式响应详情显示

新增显示项:

  • 流式响应信息(数据块数量、格式类型)
  • 响应内容预览(500 字符,原 100 字符)
  • 流式格式标识(SSE/NDJSON)

Toast 通知

Toast 通知增强

增强的通知内容:

[
  `✅ ${testResult.message}`,
  `⏱️ 响应时间: ${testResult.details.responseTime}ms`,
  `🔢 Token 用量: ${usage}`,
  `📝 响应内容: ${content.slice(0, 500)}...`,
  `🌊 流式响应: 接收 N 个数据块 (SSE)`,  // 新增
].filter(Boolean)

技术细节 (Technical Details)

OpenAI Responses API 规范

必需参数:

{
  model: string;      // ✅ 必需
  input: InputItem[]; // ✅ 必需(数组格式)
}

可选参数:

  • max_output_tokens - 最大输出 token 数(某些中转服务不支持)
  • temperature - 温度参数
  • stream - 是否流式输出
  • instructions - 系统指令

流式响应格式

SSE (Server-Sent Events)

data: {"id":"msg_123","type":"message","content":[{"type":"text","text":"Hello"}]}

data: {"id":"msg_123","type":"message","content":[{"type":"text","text":" World"}]}

data: [DONE]

特征:

  • Content-Type: text/event-stream
  • 每个事件以 data: 开头
  • 事件之间用空行分隔
  • [DONE] 标记结束

NDJSON (Newline-Delimited JSON)

{"id":"msg_123","content":[{"text":"Hello"}]}
{"id":"msg_123","content":[{"text":" World"}]}

特征:

  • Content-Type: application/x-ndjson
  • 每行一个完整的 JSON 对象
  • 无特殊结束标记

错误处理策略

  1. 流式解析失败: 返回详细错误信息,不影响后续测试
  2. JSON 解析失败: 显示响应预览,帮助调试
  3. Content-Type 缺失: 自动检测 SSE 格式(兜底策略)
  4. 数据块为空: 抛出明确错误 "未能从流式响应中解析出有效数据"

变更文件 (Changes)

核心逻辑 (src/actions/providers.ts)

新增函数:

  • parseStreamResponse() - 从 Response 对象解析流式数据(323 行)
  • parseSSEText() - 从文本解析 SSE 格式(70 行)
  • mergeStreamChunks() - 智能合并数据块(120 行)

修改配置:

  • MAX_RESPONSE_PREVIEW_LENGTH: 100 → 500
  • testProviderOpenAIResponses() 参数修正

UI 组件 (src/app/[locale]/settings/providers/_components/forms/api-test-button.tsx)

新增显示:

  • 流式响应信息面板(数据块数量、格式类型)
  • Toast 通知中的流式信息

类型扩展:

details: {
  streamInfo?: {
    chunksReceived: number;
    format: "sse" | "ndjson";
  };
}

国际化 (messages/zh-CN/settings.json)

文案优化:

- "注意:测试将向供应商发送真实请求(非流式)"
+ "注意:测试将向供应商发送真实请求"

测试验证 (Testing)

1. 参数兼容性测试

IKunCode/RightCode (Codex):

  • ✅ 之前:400 "Unsupported parameter: max_output_tokens"
  • ✅ 修复后:测试成功,返回 Token 用量

2. 流式响应测试

测试场景:

  • ✅ SSE 格式 + 正确 Content-Type
  • ✅ SSE 格式 + 缺失 Content-Type(兜底检测)
  • ✅ NDJSON 格式
  • ✅ 非流式响应(向后兼容)

测试步骤:

# 1. 重启应用
docker compose restart app

# 2. 访问供应商管理页面
open http://localhost:13500/settings/providers

# 3. 选择 Codex 供应商,选择 "OpenAI Responses API" 测试
# 4. 点击"测试连接"
# 5. 验证测试成功,显示流式响应信息

3. 数据块合并测试

验证点:

  • ✅ 文本完整性(无缺失、无乱序)
  • ✅ Token 用量准确(取最后一个 chunk 的 usage)
  • ✅ 支持多种 API 格式(Anthropic/OpenAI/Gemini)

相关资源 (References)


Checklist

  • 修复参数兼容性问题
  • 新增流式响应测试支持
  • 添加 SSE/NDJSON 格式解析
  • 实现智能数据块合并
  • 优化 UI 显示和提示
  • 更新国际化文案
  • 手动测试通过(IKunCode/RightCode)
  • 向后兼容非流式响应

新增对流式响应(如 SSE 和 NDJSON)的识别逻辑,在检测到流式响应时返回明确提示信息。
同时优化 JSON 响应解析流程,增加对非法 JSON 格式的捕获与日志记录。
此外,调整 OpenAI Responses API 测试输入格式,确保符合其接口规范。
某些中转服务不支持 max_output_tokens 参数,导致测试失败。根据注释说明,
该参数在当前上下文中并非必需,因此将其移除以确保与所有服务兼容。
新增对 SSE 和 NDJSON 格式流式响应的解析能力,包括:
- 添加 `streamInfo` 字段以记录接收到的数据块数量及格式
- 实现 `parseSSEText` 和 `parseStreamResponse` 函数用于解析流式数据
- 引入 `mergeStreamChunks` 方法将多个响应块合并为完整响应
- 更新 API 测试逻辑以正确处理并展示流式响应结果
- 增强错误处理机制以应对流式响应解析失败的情况
- 调整 API 测试配置中响应内容预览最大长度从 100 到 500 字符
- 在测试结果中新增 streamInfo 字段用于记录流式响应数据
- UI 上增加流式响应信息的展示区域,包括接收到的数据块数量和流格式
- 支持在结果详情和错误信息区域显示流式响应相关统计
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Silentely, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求旨在增强供应商 API 连接测试的健壮性和功能性。它通过解决 Codex API 测试中因参数不兼容导致的错误,并引入对流式响应的测试支持,确保了更广泛的 API 能够被准确有效地验证。这些改进提升了系统与各种第三方服务集成的可靠性。

Highlights

  • 修复 Codex API 测试参数兼容性问题: 解决了某些 Codex 中转服务因不支持可选的 max_output_tokens 参数而导致 API 测试失败(400 错误)的问题。通过移除测试请求中的此参数,提高了兼容性。
  • 增加对流式 API 响应测试的支持: 引入了对流式 API 响应(如 SSE 和 NDJSON 格式)的解析和测试功能,即使 Content-Type 未正确设置也能进行识别和处理,从而更全面地验证供应商 API 的连通性。
  • 优化 API 响应预览和显示: 将 API 响应预览的最大长度从 100 增加到 500 字符,并在测试结果中增加了流式响应的详细信息(如接收到的数据块数量和格式),提升了用户体验和调试能力。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@ding113 ding113 added the size/M Medium PR (< 500 lines) label Nov 22, 2025
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

这个 PR 的核心目标是修复 Codex API 测试的兼容性问题,并增加了对流式响应的测试支持,完成度很高。主要改进包括:

  1. 移除了 testProviderOpenAIResponses 中不兼容的 max_output_tokens 参数,并修正了 input 的格式,解决了核心的兼容性问题。
  2. executeProviderApiTest 中增加了对流式响应(SSE)的处理逻辑,包括基于 Content-Type 的判断和对内容格式的启发式检测,这大大增强了测试的健壮性。
  3. 新增了 parseStreamResponseparseSSETextmergeStreamChunks 等函数来解析和合并流式数据块,并能适配多种 API 的响应格式。
  4. 前端相应地更新了 UI,可以展示流式测试的结果,例如接收到的数据块数量。

代码整体质量不错,但新增加的流式解析函数中存在一些可以优化的地方,主要集中在代码重复和错误处理方面。我在具体的代码行上提出了一些建议,希望能帮助提升代码的可维护性。

Copy link
Owner

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Code Review - In Progress

I'm conducting a detailed code review focusing on correctness, security, and reliability issues. Review comments will be submitted shortly.

Initial Analysis: Found several critical and high-severity issues related to:

  • Resource management in stream parsing
  • Type safety concerns
  • Error handling edge cases

Detailed inline comments coming next...

@ding113 ding113 added bug Something isn't working enhancement New feature or request labels Nov 22, 2025
Copy link
Owner

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review with critical and high severity issues found

Copy link
Owner

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional critical issues


// 跳过 [DONE] 标记
if (dataContent === "[DONE]") {
continue;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High: Potential Type Safety Violation in Stream Merging

Why this is a problem: In mergeStreamChunks (line 1048+), when merging text content back into response objects, the code performs unsafe type assertions without validating the response structure. For example:

(base as OpenAIResponsesResponse).output = [...]

If base does not actually match OpenAIResponsesResponse structure (e.g., it's an Anthropic response but we're in the OpenAI Responses branch), this creates invalid response objects that will fail extraction.

Suggested fix:

// OpenAI Responses API - only update if this is actually a Responses API response
if ("output" in base && Array.isArray(base.output)) {
  const firstOutput = base.output[0];
  (base as OpenAIResponsesResponse).output = [
    {
      type: "message",
      id: firstOutput?.id || "msg_" + Date.now(),
      status: firstOutput?.status || "completed",
      role: "assistant",
      content: [{ type: "output_text", text: mergedText }],
    },
  ];
}

Copy link
Owner

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security and correctness issues

Copy link
Owner

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security Scan Results - CRITICAL VULNERABILITIES FOUND

🚨 Vulnerabilities Found

  • Critical (🔴): 1 vulnerability
  • High (🟠): 1 vulnerability
  • Medium (🟡): 0 weaknesses
  • Low (🟢): 0 informational findings

⚡ IMMEDIATE ACTIONS REQUIRED

🔴 CRITICAL: Uncontrolled Resource Consumption (DoS) via Unbounded Stream Parsing

Location: src/actions/providers.ts lines 1055-1150

Security Impact:
An attacker with admin access can exhaust server memory by causing the server to parse an unbounded streaming response, leading to:

  • Out-of-memory (OOM) crash
  • Service unavailability for all users
  • Potential for bypassing rate limits if memory exhaustion occurs before limit checks

Attack Scenario:

  1. Attacker gains admin access (or compromises admin account)
  2. Attacker configures a malicious provider URL pointing to their controlled server
  3. Malicious server responds with infinite SSE stream: data: {...}\n\ndata: {...}\n\n... (never sends [DONE])
  4. parseStreamResponse() continuously accumulates chunks in memory with no size limit:
    • chunks array grows unbounded
    • buffer string grows unbounded
    • while (true) loop never terminates
  5. Server runs out of memory and crashes (DoS for all users)

Vulnerable Code:

async function parseStreamResponse(response: Response): Promise<StreamParseResult> {
  const chunks: ProviderApiResponse[] = []; // ⚠️ No size limit
  let buffer = "";
  
  while (true) { // ⚠️ No maximum iteration count
    const { done, value } = await reader.read();
    if (done) break; // ⚠️ Attacker controls when 'done' is true
    
    buffer += decoder.decode(value, { stream: true }); // ⚠️ Buffer grows unbounded
    // ...
    chunks.push(parsed); // ⚠️ Chunks array grows unbounded
  }
}

Same issue in parseSSEText() at lines 998-1051.

Remediation:

// Add to API_TEST_CONFIG
const API_TEST_CONFIG = {
  TIMEOUT_MS: 10000,
  MAX_RESPONSE_PREVIEW_LENGTH: 500,
  TEST_MAX_TOKENS: 100,
  TEST_PROMPT: "Hello",
  // Add these limits:
  MAX_STREAM_CHUNKS: 1000,        // Max chunks to parse
  MAX_STREAM_BUFFER_SIZE: 10 * 1024 * 1024, // 10MB max buffer
  MAX_STREAM_ITERATIONS: 10000,   // Prevent infinite loops
} as const;

async function parseStreamResponse(response: Response): Promise<StreamParseResult> {
  if (\!response.body) {
    throw new Error("响应体为空");
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  const chunks: ProviderApiResponse[] = [];
  
  let buffer = "";
  let currentData = "";
  let iterations = 0;

  try {
    while (true) {
      // Prevent infinite loops
      if (++iterations > API_TEST_CONFIG.MAX_STREAM_ITERATIONS) {
        throw new Error(`流式响应超过最大迭代次数 (${API_TEST_CONFIG.MAX_STREAM_ITERATIONS})`);
      }
      
      // Prevent excessive chunks
      if (chunks.length >= API_TEST_CONFIG.MAX_STREAM_CHUNKS) {
        throw new Error(`流式响应超过最大数据块数量 (${API_TEST_CONFIG.MAX_STREAM_CHUNKS})`);
      }
      
      // Prevent buffer overflow
      if (buffer.length > API_TEST_CONFIG.MAX_STREAM_BUFFER_SIZE) {
        throw new Error(`流式响应缓冲区超过最大大小 (${API_TEST_CONFIG.MAX_STREAM_BUFFER_SIZE} 字节)`);
      }
      
      const { done, value } = await reader.read();
      if (done) break;
      
      buffer += decoder.decode(value, { stream: true });
      // ... rest of parsing logic
    }
  } finally {
    reader.releaseLock();
  }
  
  // ... rest of function
}

// Apply same limits to parseSSEText
function parseSSEText(text: string): StreamParseResult {
  // Validate input size
  if (text.length > API_TEST_CONFIG.MAX_STREAM_BUFFER_SIZE) {
    throw new Error(`SSE 文本超过最大大小 (${API_TEST_CONFIG.MAX_STREAM_BUFFER_SIZE} 字节)`);
  }
  
  const lines = text.split("\n");
  
  // Prevent excessive lines
  if (lines.length > API_TEST_CONFIG.MAX_STREAM_ITERATIONS) {
    throw new Error(`SSE 超过最大行数 (${API_TEST_CONFIG.MAX_STREAM_ITERATIONS})`);
  }
  
  const chunks: ProviderApiResponse[] = [];
  let currentData = "";
  
  for (const line of lines) {
    // Prevent excessive chunks
    if (chunks.length >= API_TEST_CONFIG.MAX_STREAM_CHUNKS) {
      throw new Error(`SSE 超过最大数据块数量 (${API_TEST_CONFIG.MAX_STREAM_CHUNKS})`);
    }
    
    // ... rest of parsing logic
  }
  
  // ... rest of function
}

References:


🟠 HIGH: Insecure Deserialization with Insufficient Validation

Location: src/actions/providers.ts lines 1017-1283 (multiple occurrences)

Security Impact:
While JSON.parse() itself is safe in JavaScript/TypeScript, the code parses untrusted streaming data from external providers without adequate validation, which could lead to:

  • Injection of malicious data structures into the application
  • Type confusion attacks if malformed data bypasses TypeScript type assertions
  • Potential prototype pollution (though mitigated by modern JS engines)
  • Resource exhaustion via deeply nested objects

Attack Scenario:

  1. Attacker controls a malicious provider endpoint
  2. Provider returns malicious SSE stream with crafted payloads:
data: {"__proto__": {"isAdmin": true}}
data: {"content": [{"text": "<script>alert('xss')</script>"}]}
data: {"usage": {"a": {"b": {"c": { ... 10000 levels deep ... }}}}}
  1. JSON.parse(currentData) as ProviderApiResponse blindly trusts the parsed object
  2. Type assertion (as ProviderApiResponse) provides no runtime validation
  3. Malicious data propagates through mergeStreamChunks() and into application state

Vulnerable Code:

// parseSSEText (line 1017)
try {
  const parsed = JSON.parse(currentData) as ProviderApiResponse; // ⚠️ No validation
  chunks.push(parsed);
} catch {
  // Silently ignores - could hide attacks
}

// parseStreamResponse (line 1100)
try {
  const parsed = JSON.parse(currentData) as ProviderApiResponse; // ⚠️ No validation
  chunks.push(parsed);
} catch {
  // Silently ignores
}

// executeProviderApiTest (line 1601)
try {
  result = JSON.parse(responseText) as ProviderApiResponse; // ⚠️ No validation
} catch (jsonError) {
  // Only logs, doesn't validate structure
}

Remediation:

// Add Zod schema for runtime validation
import { z } from 'zod';

// Define schemas for each API format
const AnthropicUsageSchema = z.object({
  input_tokens: z.number().int().nonnegative(),
  output_tokens: z.number().int().nonnegative(),
  cache_creation_input_tokens: z.number().int().nonnegative().optional(),
  cache_read_input_tokens: z.number().int().nonnegative().optional(),
});

const AnthropicResponseSchema = z.object({
  id: z.string(),
  type: z.literal('message'),
  role: z.literal('assistant'),
  content: z.array(z.object({
    type: z.string(),
    text: z.string().optional(),
  })).max(100), // Prevent array DoS
  model: z.string(),
  usage: AnthropicUsageSchema.optional(),
}).passthrough(); // Allow other fields but validate critical ones

// ... similar schemas for OpenAI, Gemini, etc.

const ProviderResponseSchema = z.union([
  AnthropicResponseSchema,
  OpenAIChatResponseSchema,
  OpenAIResponsesResponseSchema,
  GeminiResponseSchema,
]);

// Safe parsing function with depth limit
function safeParseJSON(jsonString: string, maxDepth = 10): unknown {
  const parsed = JSON.parse(jsonString);
  
  // Check nesting depth to prevent stack overflow
  function checkDepth(obj: unknown, depth = 0): void {
    if (depth > maxDepth) {
      throw new Error(`对象嵌套深度超过限制 (${maxDepth})`);
    }
    if (typeof obj === 'object' && obj \!== null) {
      for (const value of Object.values(obj)) {
        checkDepth(value, depth + 1);
      }
    }
  }
  
  checkDepth(parsed);
  return parsed;
}

// Use in parseSSEText
try {
  const parsed = safeParseJSON(currentData);
  const validated = ProviderResponseSchema.parse(parsed); // Runtime validation
  chunks.push(validated as ProviderApiResponse);
} catch (error) {
  logger.warn('Invalid SSE chunk structure', { 
    error: error instanceof Error ? error.message : 'Unknown',
    chunkPreview: clipText(currentData, 100),
  });
  // Don't silently ignore - log for security monitoring
  continue; // Skip invalid chunk
}

Alternative (Lightweight):
If Zod adds too much overhead, implement basic structural validation:

function validateProviderResponse(data: unknown): data is ProviderApiResponse {
  if (typeof data \!== 'object' || data === null) return false;
  
  // Check it has expected structure (at least one known field)
  const obj = data as Record<string, unknown>;
  const hasValidStructure = 
    'content' in obj ||  // Anthropic
    'choices' in obj ||   // OpenAI Chat
    'output' in obj ||    // OpenAI Responses
    'candidates' in obj;  // Gemini
  
  if (\!hasValidStructure) {
    logger.warn('Provider response missing expected fields', { 
      keys: Object.keys(obj).slice(0, 10) 
    });
    return false;
  }
  
  return true;
}

// Use in parsing
const parsed = safeParseJSON(currentData);
if (\!validateProviderResponse(parsed)) {
  logger.warn('Invalid provider response structure');
  continue;
}
chunks.push(parsed);

References:


📋 OWASP Top 10 Coverage

  • ✅ A01: Injection - Clean (no SQL/Command/XXE vulnerabilities)
  • ✅ A02: Broken Authentication - Clean (admin-only access control enforced)
  • ✅ A03: Sensitive Data Exposure - Clean (API keys properly redacted in logs)
  • ❌ A04: XML External Entities - N/A (no XML parsing)
  • ✅ A05: Broken Access Control - Clean (proper admin role checks)
  • ✅ A06: Security Misconfiguration - Clean
  • ✅ A07: XSS - Clean (React auto-escaping, no dangerouslySetInnerHTML)
  • 🔴 A08: Insecure Deserialization - 1 HIGH severity finding (insufficient validation)
  • ❌ A09: Known Vulnerabilities - N/A (code changes only)
  • ✅ A10: Logging & Monitoring - Clean (proper logging with sensitive data redaction)
  • 🔴 Additional: Uncontrolled Resource Consumption (DoS) - 1 CRITICAL finding

🛡️ Security Posture

Assessment: CRITICAL CONCERNS - DO NOT MERGE

This PR introduces serious security vulnerabilities that could lead to:

  1. Complete service outage via memory exhaustion (DoS)
  2. Data integrity issues via unvalidated deserialization

Recommendation:

  • BLOCK MERGE until both vulnerabilities are fixed
  • Implement resource limits (MAX_CHUNKS, MAX_BUFFER_SIZE, MAX_ITERATIONS)
  • Add runtime validation for parsed JSON data (Zod schemas or manual validation)
  • Add integration tests with malicious payloads to verify fixes

Positive Security Controls Observed:

  • ✅ Admin-only access control properly enforced
  • ✅ SSRF protection via URL validation (blocks localhost, internal IPs, dangerous ports)
  • ✅ API key redaction in logs
  • ✅ Proper error handling with try-catch blocks
  • ✅ Timeout protection (10 second timeout)

🤖 Automated security scan by Claude AI - OWASP Top 10 & CWE coverage
Scanned commit: d1bee4c

Copy link
Owner

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling and edge case issues

format: "sse",
};
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High: Silent Chunk Parsing Failures

Why this is a problem: In both parseSSEText and parseStreamResponse, chunk parsing errors are silently ignored:

try {
  const parsed = JSON.parse(currentData) as ProviderApiResponse;
  chunks.push(parsed);
} catch {
  // 忽略解析失败的 chunk
}

This leads to:

  • Data loss: Valid chunks after the malformed one are silently dropped
  • No visibility: Developers cannot debug why stream parsing fails
  • Incorrect results: Partial responses appear as "successful" even when most chunks failed

Suggested fix:

try {
  const parsed = JSON.parse(currentData) as ProviderApiResponse;
  chunks.push(parsed);
} catch (parseError) {
  logger.warn("Failed to parse SSE chunk", {
    chunkData: currentData.slice(0, 100),
    error: parseError instanceof Error ? parseError.message : String(parseError),
  });
  // Continue parsing other chunks
}

Copy link
Owner

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance and code quality issues

// 保留最后一行(可能不完整)
buffer = lines.pop() || "";

for (const line of lines) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium: Inefficient Text Merging with String Concatenation

Why this is a problem: In mergeStreamChunks, text is merged using repeated string concatenation (mergedText += ...) across potentially hundreds of chunks. This creates intermediate string objects, causing O(n²) time complexity.

Performance impact: For large responses (>10KB), this can cause noticeable slowdowns in the UI.

Suggested fix:

// Use array join for O(n) performance
const textChunks: string[] = [];

for (const chunk of chunks) {
  if ("content" in chunk && Array.isArray(chunk.content)) {
    for (const content of chunk.content) {
      if (content.type === "text" && "text" in content) {
        textChunks.push(content.text);
      }
    }
  }
  // ... other cases
}

const mergedText = textChunks.join("");

Copy link
Owner

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium severity - Maintainability

if ("usageMetadata" in chunk && chunk.usageMetadata) {
(base as GeminiResponse).usageMetadata = chunk.usageMetadata;
break;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium: Hardcoded Test Prompt Position

Why this is a problem: The test prompt structure uses hardcoded array position [0] to access the first (and only) input item:

input: [
  {
    role: "user",
    content: [
      {
        type: "input_text",
        text: API_TEST_CONFIG.TEST_PROMPT,
      },
    ],
  },
],

While correct for now, this pattern makes the code harder to extend. If someone wants to add system instructions or multi-turn conversations for testing, they would need to update both the array structure AND all code that assumes output[0] exists.

Suggested fix: Add a comment explaining this is a minimal test payload:

// Minimal test payload - only required fields
// Note: Production usage may include additional fields like instructions, etc.
input: [
  {
    role: "user",
    content: [{ type: "input_text", text: API_TEST_CONFIG.TEST_PROMPT }],
  },
],

Copy link
Owner

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Configuration issue

@@ -33,7 +33,7 @@ import { GeminiAuth } from "@/app/v1/_lib/gemini/auth";
// API 测试配置常量
const API_TEST_CONFIG = {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low: Magic Number for Preview Length

Why this is a problem: The preview length was increased from 100 to 500 characters without justification in code:

MAX_RESPONSE_PREVIEW_LENGTH: 500, // 响应内容预览最大长度(增加到 500 字符以显示更多内容)

While this improves UX, the value is arbitrary. For streaming responses with many chunks, this could still truncate important information.

Recommendation: Consider making this configurable or context-aware:

const API_TEST_CONFIG = {
  TIMEOUT_MS: 10000,
  MAX_RESPONSE_PREVIEW_LENGTH: 500, 
  // For streaming responses, show more content since we've already
  // paid the cost of parsing the full stream
  MAX_STREAMING_PREVIEW_LENGTH: 1000,
  TEST_MAX_TOKENS: 100,
  TEST_PROMPT: "Hello",
} as const;

Copy link
Owner

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Code Review Summary

This PR adds streaming response support for API testing, which is a valuable feature. However, the implementation has several critical and high-severity issues that must be addressed before merging.

🔍 Issues Found

  • Critical (🔴): 1 issue
  • High (🟠): 3 issues
  • Medium (🟡): 2 issues
  • Low (🟢): 1 issue

🎯 Priority Actions

  1. Fix stream reader resource leak (Critical): Add await reader.cancel() in error path to prevent memory leaks and socket exhaustion

  2. Fix type safety in stream merging (High): Add proper type guards before casting response objects to avoid creating invalid responses

  3. Improve SSE detection logic (High): Replace string matching with regex to prevent false positives/negatives

  4. Add logging for chunk parse failures (High): Silent failures make debugging impossible - add warn-level logging

  5. Optimize text concatenation (Medium): Use array join instead of repeated string concatenation for O(n) performance

💡 General Observations

Positive aspects:

  • Good separation of concerns with dedicated parsing functions
  • Comprehensive support for multiple API formats (Anthropic, OpenAI, Gemini)
  • Proper error handling at the top level

Areas needing improvement:

  • Resource management: Streams must be explicitly cancelled on errors
  • Type safety: Heavy use of type assertions without runtime validation
  • Observability: Silent error swallowing makes debugging difficult
  • Performance: String concatenation in loops creates unnecessary overhead

Testing recommendations:

  • Test with malformed SSE responses (invalid JSON chunks)
  • Test with very large responses (>100 chunks) to verify performance
  • Test error scenarios (connection drops mid-stream)
  • Verify no resource leaks with multiple consecutive tests

🤖 Automated review by Claude AI - focused on identifying issues for improvement

@Silentely Silentely changed the title 修复 Codex API 测试参数兼容性问题与增加支持流式测试 fix:修复 Codex API 测试参数兼容性问题与增加支持流式测试 Nov 22, 2025
应用 Prettier 格式化规则到 providers.ts,修复 Code Quality Check 失败问题。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@ding113
Copy link
Owner

ding113 commented Nov 22, 2025

🔒 Security Scan Results

No security vulnerabilities detected

This PR has been scanned against OWASP Top 10, CWE Top 25, and common security anti-patterns. No security issues were identified in the code changes.

🛡️ Security Improvements Found

This PR actually improves security posture by adding DoS protections:

  1. Stream Buffer Size Limits (MAX_STREAM_BUFFER_SIZE = 10MB)

    • Prevents memory exhaustion attacks
    • Enforced in parseSSEText() at line 999
  2. Stream Chunk Limits (MAX_STREAM_CHUNKS = 1000)

    • Prevents infinite stream attacks
    • Enforced in parseSSEText() at line 1018
  3. Iteration Limits (MAX_STREAM_ITERATIONS = 10000)

    • Prevents infinite loop DoS
    • Enforced in parseSSEText() at line 1006

📋 OWASP Top 10 Coverage

  • A01: Injection - Clean

    • All user inputs properly validated
    • No SQL/NoSQL/Command injection risks
    • JSON parsing uses safe JSON.parse() with error handling
  • A02: Broken Authentication - Clean

    • Admin-only access enforced (session.user.role \!== "admin")
    • API keys properly sanitized in logs (credential masking)
  • A03: Sensitive Data Exposure - Clean

    • API keys redacted in logs using regex replacement
    • No sensitive data in error messages
  • A04: XML External Entities (XXE) - N/A (no XML processing)

  • A05: Broken Access Control - Clean

    • Authorization check present at line 1085-1086
    • Admin-only operations properly gated
  • A06: Security Misconfiguration - Clean

    • Proper timeout configured (10s)
    • SSRF protections already in place (URL validation)
  • A07: Cross-Site Scripting (XSS) - Clean

    • React auto-escaping used in UI components
    • No dangerouslySetInnerHTML usage
  • A08: Insecure Deserialization - Clean

    • Safe JSON parsing with try-catch blocks
    • No eval() or unsafe deserialization
  • A09: Using Components with Known Vulnerabilities - Clean

    • No new dependencies added
  • A10: Insufficient Logging & Monitoring - Clean

    • Comprehensive logging added for stream parsing
    • Error tracking with structured logging

🔍 Additional Security Checks

  • SSRF Protection - Already implemented (existing validateProviderUrlForConnectivity)
  • Path Traversal - N/A (no file operations)
  • Race Conditions - Safe (no shared mutable state)
  • Cryptographic Issues - N/A
  • Input Validation - Comprehensive size and iteration limits added
  • DoS Protection - IMPROVED with new buffer/chunk/iteration limits
  • Information Disclosure - API keys properly sanitized in logs

📊 Code Quality Observations

Positive Security Practices:

  • Fail-safe error handling (stream parsing errors don't crash the app)
  • Resource limits to prevent DoS attacks
  • Credential sanitization in logs
  • Authorization checks enforced
  • Structured logging for security monitoring

No Security Concerns Found

🎯 Recommendation

APPROVED from security perspective

This PR enhances security by adding DoS protections for streaming responses. No vulnerabilities were identified.


🤖 Automated security scan by Claude AI - OWASP Top 10 & CWE coverage
🔍 Scanned Files: 3 | Lines Analyzed: 734 | Vulnerabilities: 0
⏱️ *Scan completed at: *

* 解析流式响应(从 Response 对象读取)
*/
async function parseStreamResponse(response: Response): Promise<StreamParseResult> {
if (!response.body) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical: 资源耗尽攻击风险 - parseStreamResponse 缺少资源限制

Why this is a problem:
parseStreamResponse() 函数在解析流式响应时缺少关键的资源限制检查,可能导致:

  1. 无限循环while (true) 循环没有迭代次数限制
  2. 内存耗尽buffer 变量可以无限增长,没有大小限制
  3. DoS 攻击:恶意供应商可以发送无限流数据,导致服务器崩溃

虽然代码定义了 MAX_STREAM_CHUNKSMAX_STREAM_BUFFER_SIZEMAX_STREAM_ITERATIONS 常量,但在 parseStreamResponse() 函数中完全没有使用这些限制。相比之下,parseSSEText() 函数正确实现了所有这些检查。

Suggested fix:

async function parseStreamResponse(response: Response): Promise<StreamParseResult> {
  if (!response.body) {
    throw new Error("响应体为空");
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  const chunks: ProviderApiResponse[] = [];

  let buffer = "";
  let currentData = "";
  let iterations = 0; // 添加迭代计数器

  try {
    while (true) {
      // 防止无限循环(防止 DoS)
      if (iterations++ > API_TEST_CONFIG.MAX_STREAM_ITERATIONS) {
        logger.warn("Stream 解析达到最大迭代次数", {
          maxIterations: API_TEST_CONFIG.MAX_STREAM_ITERATIONS,
        });
        break;
      }

      const { done, value } = await reader.read();
      if (done) break;

      buffer += decoder.decode(value, { stream: true });

      // 防止缓冲区过大(防止内存耗尽)
      if (buffer.length > API_TEST_CONFIG.MAX_STREAM_BUFFER_SIZE) {
        throw new Error(`Stream 缓冲区超过最大大小 (${API_TEST_CONFIG.MAX_STREAM_BUFFER_SIZE} 字节)`);
      }

      const lines = buffer.split("\n");
      buffer = lines.pop() || "";

      for (const line of lines) {
        const trimmed = line.trim();

        if (trimmed.startsWith("data:")) {
          const dataContent = trimmed.slice(5).trim();
          if (dataContent === "[DONE]") {
            continue;
          }
          if (dataContent) {
            currentData = dataContent;
          }
        } else if (trimmed === "" && currentData) {
          // 防止过多数据块(防止 DoS)
          if (chunks.length >= API_TEST_CONFIG.MAX_STREAM_CHUNKS) {
            logger.warn("Stream 解析达到最大数据块限制", {
              maxChunks: API_TEST_CONFIG.MAX_STREAM_CHUNKS,
            });
            break;
          }

          try {
            const parsed = JSON.parse(currentData) as ProviderApiResponse;
            chunks.push(parsed);
            currentData = "";
          } catch {
            // 忽略解析失败的 chunk
          }
        }
      }
    }

    // ... rest of the function
  } finally {
    reader.releaseLock();
  }

  if (chunks.length === 0) {
    throw new Error("未能从流式响应中解析出有效数据");
  }

  const mergedResponse = mergeStreamChunks(chunks);

  return {
    data: mergedResponse,
    chunksReceived: chunks.length,
    format: "sse",
  };
}


for (const chunk of chunks) {
// Anthropic Messages API
if ("content" in chunk && Array.isArray(chunk.content)) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High: mergeStreamChunks 中的类型断言逻辑错误

Why this is a problem:
mergeStreamChunks() 函数中,OpenAI Chat Completions API 的流式响应合并逻辑存在错误。代码检查 delta 字段后,仍然尝试访问不存在的 message.content

// 流式响应使用 delta
if (firstChoice && "delta" in firstChoice) {
  const delta = firstChoice.delta as { content?: string };
  if (delta.content) {
    mergedText += delta.content;
  }
}
// 非流式响应使用 message
else if (firstChoice?.message?.content) {
  mergedText += firstChoice.message.content;
}

问题:在流式响应中,firstChoicedelta 字段但没有 message 字段。如果第一个 chunk 有 delta 但后续 chunk 有 message,会导致数据丢失。

更严重的是,在最后写回响应对象时,代码假设所有流式响应都会转换为 message 格式:

// OpenAI Chat Completions API
if ("choices" in base && Array.isArray(base.choices)) {
  base.choices = [
    {
      ...base.choices[0],
      message: { role: "assistant", content: mergedText },  // ❌ 丢失 delta 字段
      finish_reason: "stop",
    },
  ];
}

这会导致原始流式响应结构被破坏。

Suggested fix:

// 在循环中收集文本
for (const chunk of chunks) {
  if ("choices" in chunk && Array.isArray(chunk.choices)) {
    const firstChoice = chunk.choices[0];
    if (firstChoice) {
      // 流式响应:delta 字段
      if ("delta" in firstChoice && firstChoice.delta) {
        const delta = firstChoice.delta as { content?: string };
        if (delta.content) {
          mergedText += delta.content;
        }
      }
      // 非流式响应:message 字段
      else if ("message" in firstChoice && firstChoice.message?.content) {
        mergedText += firstChoice.message.content;
      }
    }
  }
}

// 写回响应对象时,保留原始结构
if (mergedText) {
  if ("choices" in base && Array.isArray(base.choices)) {
    const firstChoice = base.choices[0];
    if (firstChoice && "delta" in firstChoice) {
      // 流式响应:使用 delta
      base.choices = [
        {
          ...firstChoice,
          delta: { role: "assistant", content: mergedText },
          finish_reason: "stop",
        },
      ];
    } else {
      // 非流式响应:使用 message
      base.choices = [
        {
          ...base.choices[0],
          message: { role: "assistant", content: mergedText },
          finish_reason: "stop",
        },
      ];
    }
  }
}

Copy link
Owner

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Code Review Summary

本 PR 为 Codex API 测试功能新增了流式响应支持,修复了参数兼容性问题。整体方向正确,但在流式响应解析的安全性和正确性方面存在需要立即修复的关键问题。

🔍 Issues Found

  • Critical (🔴): 1 issue
  • High (🟠): 1 issue
  • Medium (🟡): 0 issues
  • Low (🟢): 0 issues

🎯 Priority Actions

  1. [Critical]parseStreamResponse() 中添加资源限制检查(迭代次数、缓冲区大小、数据块数量),防止 DoS 攻击和内存耗尽
  2. [High] 修复 mergeStreamChunks() 中 OpenAI Chat Completions API 的流式响应处理逻辑,避免 delta/message 字段混淆导致的数据丢失

💡 General Observations

优点:

  • 新增的 DoS 防护常量(MAX_STREAM_CHUNKSMAX_STREAM_BUFFER_SIZEMAX_STREAM_ITERATIONS)设计合理
  • parseSSEText() 函数正确实现了所有资源限制检查,可作为参考
  • 移除 max_output_tokens 参数提高了兼容性
  • UI 展示流式响应信息的设计清晰实用

需要改进:

  • parseStreamResponse() 虽然定义了防护常量,但未实际使用,存在严重安全风险
  • 流式响应的数据结构转换逻辑需要更加严谨,确保不同 API 格式的兼容性

🤖 Automated review by Claude AI - focused on identifying issues for improvement

根据 PR ding113#185 的代码审查建议,修复以下问题:

**Critical 修复**:
- 🔴 修复 Stream reader 资源泄漏
  - 在错误路径中添加 `await reader.cancel()` 防止内存泄漏
  - 确保在异常情况下正确释放资源

**High 修复**:
- 🟠 添加日志记录
  - 为 chunk 解析失败添加 warn 级别日志
  - 记录跳过的 chunk 数量和错误详情
  - 添加流式响应解析完成的 info 日志

- 🟠 改进 SSE 检测逻辑
  - 使用正则表达式替代字符串匹配
  - 提高检测的健壮性,避免误判

- 🟠 增强类型安全
  - 在类型转换前添加运行时类型守卫
  - 为空数组情况添加默认值处理
  - 确保不会创建无效的响应对象

**改进点**:
- 统一错误处理模式
- 提高代码可维护性
- 增强调试能力

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@ding113 ding113 added the size/L Large PR (< 1000 lines) label Nov 22, 2025
while (true) {
const { done, value } = await reader.read();
if (done) break;

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High: Missing chunk count limit in parseStreamResponse can cause memory exhaustion

Why this is a problem: The parseSSEText function checks chunks.length >= API_TEST_CONFIG.MAX_STREAM_CHUNKS before adding chunks, but parseStreamResponse has no such check. A malicious stream could send unlimited small chunks, exhausting memory.

Suggested fix:

        } else if (trimmed === "" && currentData) {
          // 防止过多数据块(防止 DoS)
          if (chunks.length >= API_TEST_CONFIG.MAX_STREAM_CHUNKS) {
            logger.warn("流式响应解析达到最大数据块限制", {
              maxChunks: API_TEST_CONFIG.MAX_STREAM_CHUNKS,
              skipped: skippedChunks,
            });
            break;
          }

          // 空行表示一个完整的 SSE 事件结束
          try {
            const parsed = JSON.parse(currentData) as ProviderApiResponse;
            chunks.push(parsed);
            currentData = "";
          } catch (parseError) {

}
}
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium: Using Date.now() for message ID generation may cause collisions

Why this is a problem: Date.now() returns milliseconds, and multiple chunks processed in the same millisecond could generate the same ID. This could cause issues if these IDs are used for deduplication or tracking downstream.

Suggested fix:

    if (
      "id" in base &&
      typeof base.id === "string" &&
      "type" in base &&
      base.type === "response"
    ) {
      (base as OpenAIResponsesResponse).output = [
        {
          type: "message",
          id: firstOutput?.id || `msg_${crypto.randomUUID()}`,
          status: firstOutput?.status || "completed",
          role: "assistant",
          content: [{ type: "output_text", text: mergedText }],
        },
      ];
    }

@ding113
Copy link
Owner

ding113 commented Nov 22, 2025

📊 PR Size Analysis

This PR is size/L with 595 lines changed across 3 files.

While the PR is well-structured and addresses related concerns, large PRs are harder to review and more likely to introduce bugs. The current implementation combines two distinct changes that could be reviewed independently.

🔀 Suggested Split:

Based on the changes, this PR could be split into 2 sequential PRs:

PR 1: Fix OpenAI Responses API Parameter Compatibility

Files:

  • src/actions/providers.ts (config changes)
  • messages/zh-CN/settings.json (documentation update)

Changes:

  • Remove max_output_tokens parameter to fix compatibility
  • Fix input format to match OpenAI Responses API spec (array structure)
  • Update test prompt configuration
  • Update UI text to remove "non-streaming" mention

Estimated size: ~50 lines (size/XS)

PR 2: Add Streaming Response Testing Support

Files:

  • src/actions/providers.ts (new functions)
  • src/app/[locale]/settings/providers/_components/forms/api-test-button.tsx (UI updates)

Changes:

  • Add parseStreamResponse() - 323 lines
  • Add parseSSEText() - 70 lines
  • Add mergeStreamChunks() - 120 lines
  • Update UI to display stream info (chunks count, format type)
  • Enhance response preview length (100 → 500 chars)
  • Add streaming info to Toast notifications

Estimated size: ~540 lines (size/L)

Why Split?

  1. PR 1 is a critical bug fix that should be merged ASAP

    • Fixes 400 errors for IKunCode/RightCode providers
    • Simple parameter adjustment, easy to review
    • Low risk of regression
  2. PR 2 is a feature enhancement that can be reviewed more carefully

    • Adds complex streaming parsing logic
    • Requires thorough testing of SSE/NDJSON formats
    • Benefits from isolated testing and review
  3. Better git history

    • Clear separation between "fix" and "feat" commits
    • Easier to revert feature if issues arise without losing the bug fix
    • Clearer changelog for end users
  4. Faster CI feedback on the critical bug fix

Alternative: Keep as Single PR

If you prefer to keep this as a single PR (which is also reasonable given the changes are logically related to API testing), I recommend:

  • Adding more inline code comments in the streaming parsing functions
  • Breaking down the parseStreamResponse() function into smaller helper functions
  • Adding unit tests for mergeStreamChunks() to validate chunk merging logic

🤖 Automated analysis by Claude AI

@ding113 ding113 merged commit 47537a7 into ding113:dev Nov 22, 2025
15 of 16 checks passed
Copy link
Owner

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Code Review Summary

This PR adds streaming response support and fixes Codex API compatibility. The implementation is solid overall, but there are some resource protection inconsistencies between the two parsing functions that should be addressed before merge.

🔍 Issues Found

  • Critical (🔴): 0 issues
  • High (🟠): 2 issues
  • Medium (🟡): 3 issues
  • Low (🟢): 0 issues

🎯 Priority Actions

  1. Add buffer size check in parseStreamResponse - The streaming parser lacks the DoS protection present in parseSSEText. A malicious stream could cause memory exhaustion.

  2. Add chunk count limit in parseStreamResponse - Missing MAX_STREAM_CHUNKS check allows unlimited chunk accumulation.

  3. Fix SSE detection regex - Current pattern may have false positives with JSON starting with "data:".

  4. Preserve original finish_reason - Currently hardcoded to "stop", but should preserve the actual value from the last chunk.

  5. Use crypto.randomUUID() for message IDs - Date.now() can cause ID collisions.

💡 General Observations

The DoS protections in parseSSEText (buffer size limit, chunk count limit, iteration limit) are comprehensive, but parseStreamResponse is missing equivalent protections. Consider extracting these limits into a shared validation approach to ensure consistency.


🤖 Automated review by Claude AI - focused on identifying issues for improvement

This was referenced Nov 24, 2025
@Silentely Silentely deleted the fix-provider-test branch November 26, 2025 12:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request size/L Large PR (< 1000 lines) size/M Medium PR (< 500 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants