aicodeswitch 2.0.10 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### 2.1.1 (2026-02-03)
6
+
7
+ ### 2.0.11 (2026-02-03)
8
+
5
9
  ### 2.0.10 (2026-02-03)
6
10
 
7
11
  ### 2.0.9 (2026-02-02)
package/CLAUDE.md CHANGED
@@ -99,8 +99,27 @@ aicos version # Show current version information
99
99
  #### 3. Transformers - `server/transformers/`
100
100
  - **streaming.ts**: SSE parsing/serialization and event transformation
101
101
  - **claude-openai.ts**: Claude ↔ OpenAI Chat format conversion
102
+ - Image content block conversion (Claude ↔ OpenAI formats)
103
+ - Tool choice mapping (auto/any/tool ↔ auto/none/required)
104
+ - Stop reason mapping (including max_thinking_length)
105
+ - System prompt handling (string and array formats)
106
+ - Thinking/Reasoning content conversion
102
107
  - **chunk-collector.ts**: Collects streaming chunks for logging
103
108
 
109
+ **API 转换功能**:
110
+ 转换器实现了以下 API 格式之间的双向转换:
111
+ - **Claude Messages API** ↔ **OpenAI Chat Completions API**
112
+ - **Claude Messages API** ↔ **OpenAI Responses API**
113
+ - **DeepSeek Chat** ↔ 其他格式(支持 developer 角色映射)
114
+
115
+ **支持的转换内容**:
116
+ - 文本内容 (text)
117
+ - 图像内容 (image ↔ image_url)
118
+ - 工具调用 (tool_use ↔ tool_calls)
119
+ - 工具结果 (tool_result)
120
+ - 思考内容 (thinking ↔ reasoning/thinking)
121
+ - 系统提示词 (system - 支持字符串和数组格式)
122
+
104
123
  #### 4. Database - `server/database.ts`
105
124
  - SQLite3 database wrapper
106
125
  - Manages: Vendors, API Services, Routes, Rules, Logs
@@ -139,7 +158,7 @@ aicos version # Show current version information
139
158
  - `image-understanding`: Requests with image content
140
159
  - `thinking`: Requests with reasoning/thinking signals
141
160
  - `long-context`: Requests with large context (≥12000 chars or ≥8000 max tokens)
142
- - `background`: Background/priority requests
161
+ - `background`: Background/priority requests, including `/count_tokens` endpoint requests and token counting requests with `{"role": "user", "content": "count"}`
143
162
  - `default`: All other requests
144
163
 
145
164
  ### Request Transformation
@@ -163,7 +182,18 @@ aicos version # Show current version information
163
182
  ### Logging
164
183
  - Request logs: Detailed API call records with token usage
165
184
  - Access logs: System access records
166
- - Error logs: Error and exception records (includes upstream request information when available)
185
+ - Error logs: Error and exception records with comprehensive context
186
+ - **Error Log Details**:
187
+ - Basic error information: timestamp, method, path, status code, error message, error stack
188
+ - Request context: targetType (client type), requestModel (requested model)
189
+ - Routing context: ruleId (used rule), targetServiceId/Name (API service), targetModel (actual model)
190
+ - Vendor context: vendorId/Name (service provider)
191
+ - Request details: request headers, request body, response headers, response body
192
+ - **Upstream Request Information**: URL, headers, body, proxy usage
193
+ - Response time metrics
194
+ - **Data Sanitization**:
195
+ - Sensitive authentication fields (api_key, authorization, password, secret, etc.) are automatically masked in the UI
196
+ - Technical fields like `max_tokens`, `input_tokens`, `output_tokens` are NOT masked - they are legitimate API parameters
167
197
  - **Session Management**:
168
198
  - Tracks user sessions based on session ID (Claude Code: `metadata.user_id`, Codex: `headers.session_id`)
169
199
  - Auto-generates session title from first user message content:
package/TECH.md CHANGED
@@ -10,184 +10,306 @@ AI semantic search powered by Cloudflare AI
10
10
  Parameter Type Required Description
11
11
  q string ✓ AI search query
12
12
 
13
- Code Examples
13
+ ---
14
14
 
15
- const response = await fetch(
16
- 'https://skillsmp.com/api/v1/skills/ai-search?q=How+to+create+a+web+scraper',
17
- {
18
- headers: {
19
- 'Authorization': 'Bearer sk_live_skillsmp_SNqsutoSiH51g-7-E0zVFVuugcnXfQbxCqfDI786TI0'
20
- }
21
- }
22
- );
15
+ ## Thinking/Reasoning 功能实现
23
16
 
24
- const data = await response.json();
25
- console.log(data.data.skills);
17
+ ### 概述
26
18
 
27
- Responses example:
19
+ 实现了 Claude 与 OpenAI 系列 API(Chat Completions、Responses、DeepSeek)之间的 thinking/reasoning 功能的双向转换。
20
+
21
+ ### 设计思路
22
+
23
+ 1. **统一抽象**:Claude 的 `thinking` 配置与 OpenAI 的 `reasoning` 配置虽有语义差异,但核心概念相似
24
+ 2. **智能映射**:根据不同 API 规范进行自适应转换
25
+ 3. **流式支持**:完整支持流式响应中的 thinking 内容转换
26
+
27
+ ### 关键实现
28
+
29
+ #### 1. 请求配置转换
30
+
31
+ | 源格式 | 目标 API | 转换逻辑 |
32
+ |--------|----------|----------|
33
+ | `thinking: { type: "enabled" }` | OpenAI Chat | `thinking: { type: "enabled" }` |
34
+ | `thinking: { type: "enabled" }` | OpenAI Responses | `thinking: { type: "enabled" }, reasoning: { effort: "medium" }` |
35
+ | `thinking: { type: "disabled" }` | OpenAI Responses | `thinking: { type: "disabled" }, reasoning: { effort: "minimal" }` |
36
+ | `thinking: { type: "auto" }` | OpenAI Responses | `thinking: { type: "auto" }, reasoning: { effort: "low" }` |
37
+ | `reasoning_effort: "high"` | OpenAI Responses | `reasoning: { effort: "high" }` |
38
+
39
+ #### 2. Content Block 处理
40
+
41
+ - **Claude → OpenAI**:thinking content block 转换为特殊标记文本 `<thinking>...</thinking>`
42
+ - **OpenAI → Claude**:识别多种格式(`message.thinking`、`reasoning.summary`、`output[].content`)并转换为 thinking block
43
+
44
+ #### 3. 流式事件转换
45
+
46
+ **OpenAI → Claude**:
47
+ - `delta.thinking.content` → `thinking_delta` 事件
48
+ - `response.reasoning_text.delta` → `thinking_delta` 事件
49
+ - `response.reasoning_summary_text.delta` → `thinking_delta` 事件
50
+ - `response.output_text.delta` → `text_delta` 事件
51
+
52
+ **Claude → OpenAI**:
53
+ - `thinking_delta` → `delta.thinking.content`
54
+
55
+ ### 文件位置
56
+
57
+ - `src/server/transformers/claude-openai.ts` - 请求/响应转换
58
+ - `src/server/transformers/streaming.ts` - 流式事件转换
59
+
60
+ ### 注意事项
61
+
62
+ 1. **类型安全**:使用 `(any)` 类型断言避免 TypeScript 类型错误
63
+ 2. **向后兼容**:保持对旧格式响应的支持
64
+ 3. **错误处理**:流式转换器中捕获异常避免中断整个流
65
+
66
+ ---
67
+
68
+ ## API 转换架构
69
+
70
+ ### 概述
71
+
72
+ AI Code Switch 实现了多种 AI API 格式之间的双向转换,支持 Claude、OpenAI Chat Completions、OpenAI Responses、DeepSeek 等主流 API。
73
+
74
+ ### 支持的 API 格式
75
+
76
+ | API 类型 | 端点 | 主要用途 | 状态 |
77
+ |---------|------|----------|------|
78
+ | **Claude Messages API** | `/v1/messages` | Anthropic 官方 API | ✅ 完整支持 |
79
+ | **OpenAI Chat Completions** | `/v1/chat/completions` | OpenAI 对话 API | ✅ 完整支持 |
80
+ | **OpenAI Responses API** | `/v1/responses` | OpenAI 新一代响应接口 | ✅ 完整支持 |
81
+ | **DeepSeek Chat** | `/v1/chat/completions` | DeepSeek 对话 API | ✅ 完整支持(developer 角色) |
82
+
83
+ ### API 转换矩阵
84
+
85
+ | 源 API | 目标 API | 转换函数 | 支持状态 |
86
+ |--------|----------|----------|----------|
87
+ | Claude Messages | OpenAI Chat | [`transformClaudeRequestToOpenAIChat()`](src/server/transformers/claude-openai.ts#L278) | ✅ 完整 |
88
+ | Claude Messages | OpenAI Responses | `transformClaudeRequestToOpenAIChat()` + reasoning 映射 | ✅ 完整 |
89
+ | Claude Messages | DeepSeek Chat | `transformClaudeRequestToOpenAIChat()` + developer 角色映射 | ✅ 完整 |
90
+ | OpenAI Chat | Claude Messages | [`transformOpenAIChatResponseToClaude()`](src/server/transformers/claude-openai.ts#L363) | ✅ 完整 |
91
+ | OpenAI Responses | Claude Messages | 流式事件转换 + [`transformResponsesToChatCompletions()`](src/server/transformers/claude-openai.ts#L584) | ✅ 完整 |
92
+ | DeepSeek Chat | Claude Messages | `transformOpenAIChatResponseToClaude()` | ✅ 完整 |
93
+ | OpenAI Chat | OpenAI Responses | [`transformChatCompletionsToResponses()`](src/server/transformers/claude-openai.ts#L511) | ✅ 新增 |
94
+ | OpenAI Responses | OpenAI Chat | [`transformResponsesToChatCompletions()`](src/server/transformers/claude-openai.ts#L584) | ✅ 新增 |
95
+
96
+ ### 内容块转换
97
+
98
+ #### 文本内容 (Text)
99
+ ```typescript
100
+ // Claude
101
+ { type: "text", text: "Hello" }
102
+
103
+ // OpenAI
104
+ { type: "text", text: "Hello" } // 或直接 "Hello"
105
+ ```
106
+
107
+ #### 图像内容 (Image)
108
+ ```typescript
109
+ // Claude
28
110
  {
29
- "success": true,
30
- "data": {
31
- "object": "vector_store.search_results.page",
32
- "search_query": "How to create a web scraper",
33
- "data": [
34
- {
35
- "file_id": "b941f4a570315d69f10272c602473c47f04bdd75fb714311ed36ea5b7029b1e5",
36
- "filename": "skills/mattb543-asheville-event-feed-claude-event-scraper-skill-md.md",
37
- "score": 0.58138084,
38
- "skill": {
39
- "id": "mattb543-asheville-event-feed-claude-event-scraper-skill-md",
40
- "name": "event-scraper",
41
- "author": "MattB543",
42
- "description": "Create new event scraping scripts for websites. Use when adding a new event source to the Asheville Event Feed. ALWAYS start by detecting the CMS/platform and trying known API endpoints first. Browser scraping is NOT supported (Vercel limitation). Handles API-based, HTML/JSON-LD, and hybrid patterns with comprehensive testing workflows.",
43
- "githubUrl": "https://github.com/MattB543/asheville-event-feed/tree/main/claude/event-scraper",
44
- "skillUrl": "https://skillsmp.com/skills/mattb543-asheville-event-feed-claude-event-scraper-skill-md",
45
- "stars": 5,
46
- "updatedAt": 1768598524
47
- }
48
- },
49
- {
50
- "file_id": "34e6c58d525232653f2adfdf8bc5abf9b50eb9128b5abe68a630d656ca9801c9",
51
- "filename": "skills/dvorkinguy-claude-skills-agents-skills-apify-scraper-builder-skill-md.md",
52
- "score": 0.5716883
53
- },
54
- {
55
- "file_id": "7dcf28e65a11cb6fb0e205bc9b3ad1ce9007724f809632ccd81f42c3167bd688",
56
- "filename": "skills/honeyspoon-nix-config-config-opencode-skill-web-scraper-skill-md.md",
57
- "score": 0.5999056,
58
- "skill": {
59
- "id": "honeyspoon-nix-config-config-opencode-skill-web-scraper-skill-md",
60
- "name": "web-scraper",
61
- "author": "honeyspoon",
62
- "description": "This skill should be used when users need to scrape content from websites, extract text from web pages, crawl and follow links, or download documentation from online sources. It features concurrent URL processing, automatic deduplication, content filtering, domain restrictions, and proper directory hierarchy based on URL structure. Use for documentation gathering, content extraction, web archival, or research data collection.",
63
- "githubUrl": "https://github.com/honeyspoon/nix_config/tree/main/config/opencode/skill/web-scraper",
64
- "skillUrl": "https://skillsmp.com/skills/honeyspoon-nix-config-config-opencode-skill-web-scraper-skill-md",
65
- "stars": 0,
66
- "updatedAt": 1769614394
67
- }
68
- },
69
- {
70
- "file_id": "1f2f1810d2d63396a79130bbb59849ad776fdcc6242e57120528d626d7da4adc",
71
- "filename": "skills/igosuki-claude-skills-web-scraper-skill-md.md",
72
- "score": 0.5999056,
73
- "skill": {
74
- "id": "igosuki-claude-skills-web-scraper-skill-md",
75
- "name": "web-scraper",
76
- "author": "Igosuki",
77
- "description": "This skill should be used when users need to scrape content from websites, extract text from web pages, crawl and follow links, or download documentation from online sources. It features concurrent URL processing, automatic deduplication, content filtering, domain restrictions, and proper directory hierarchy based on URL structure. Use for documentation gathering, content extraction, web archival, or research data collection.",
78
- "githubUrl": "https://github.com/Igosuki/claude-skills/tree/main/web-scraper",
79
- "skillUrl": "https://skillsmp.com/skills/igosuki-claude-skills-web-scraper-skill-md",
80
- "stars": 1,
81
- "updatedAt": 1761052646
82
- }
83
- },
84
- {
85
- "file_id": "8bc3072e2ca3c703aae8ec4cb48be2a3c0826d96b4f22585788d34f1ddd9cbda",
86
- "filename": "skills/breverdbidder-life-os-skills-website-to-vite-scraper-skill-md.md",
87
- "score": 0.5806182,
88
- "skill": {
89
- "id": "breverdbidder-life-os-skills-website-to-vite-scraper-skill-md",
90
- "name": "website-to-vite-scraper",
91
- "author": "breverdbidder",
92
- "description": "Multi-provider website scraper that converts any website (including CSR/SPA) to deployable static sites. Uses Playwright, Apify RAG Browser, Crawl4AI, and Firecrawl for comprehensive scraping. Triggers on requests to clone, reverse-engineer, or convert websites.",
93
- "githubUrl": "https://github.com/breverdbidder/life-os/tree/main/skills/website-to-vite-scraper",
94
- "skillUrl": "https://skillsmp.com/skills/breverdbidder-life-os-skills-website-to-vite-scraper-skill-md",
95
- "stars": 2,
96
- "updatedAt": 1769767370
97
- }
98
- },
99
- {
100
- "file_id": "8ed6e980ab048ac58d8c7d3bd3238fdc4cda3d25f17bc97b4dc4cfb2cf34cd35",
101
- "filename": "skills/leobrival-serum-plugins-official-plugins-crawler-skills-website-crawler-skill-md.md",
102
- "score": 0.5535727,
103
- "skill": {
104
- "id": "leobrival-serum-plugins-official-plugins-crawler-skills-website-crawler-skill-md",
105
- "name": "website-crawler",
106
- "author": "leobrival",
107
- "description": "High-performance web crawler for discovering and mapping website structure. Use when users ask to crawl a website, map site structure, discover pages, find all URLs on a site, analyze link relationships, or generate site reports. Supports sitemap discovery, checkpoint/resume, rate limiting, and HTML report generation.",
108
- "githubUrl": "https://github.com/leobrival/serum-plugins-official/tree/main/plugins/crawler/skills/website-crawler",
109
- "skillUrl": "https://skillsmp.com/skills/leobrival-serum-plugins-official-plugins-crawler-skills-website-crawler-skill-md",
110
- "stars": 1,
111
- "updatedAt": 1769437425
112
- }
113
- },
114
- {
115
- "file_id": "efa5af3ef929da6b1db4f194da6d639600db620612b508e425099947215f480a",
116
- "filename": "skills/hokupod-sitepanda-assets-skill-md.md",
117
- "score": 0.57184756,
118
- "skill": {
119
- "id": "hokupod-sitepanda-assets-skill-md",
120
- "name": "sitepanda",
121
- "author": "hokupod",
122
- "description": "Scrape websites with a headless browser and extract main readable content as Markdown. Use this skill when the user asks to retrieve, analyze, or summarize content from a URL or website.",
123
- "githubUrl": "https://github.com/hokupod/sitepanda/tree/main/assets",
124
- "skillUrl": "https://skillsmp.com/skills/hokupod-sitepanda-assets-skill-md",
125
- "stars": 10,
126
- "updatedAt": 1768397847
127
- }
128
- },
129
- {
130
- "file_id": "e74a45c1d4c2646144b1019bb8a4e52aa4352c52a1b35566234a68bf057c666a",
131
- "filename": "skills/vanman2024-ai-dev-marketplace-plugins-rag-pipeline-skills-web-scraping-tools-skill-md.md",
132
- "score": 0.55511314
133
- },
134
- {
135
- "file_id": "9b11ec7c719303b2999eaf4fa535a26ffcf718ea773f579e5e6b5b8d046cce12",
136
- "filename": "skills/nathanvale-side-quest-marketplace-plugins-scraper-toolkit-skills-playwright-scraper-skill-md.md",
137
- "score": 0.54990166,
138
- "skill": {
139
- "id": "nathanvale-side-quest-marketplace-plugins-scraper-toolkit-skills-playwright-scraper-skill-md",
140
- "name": "playwright-scraper",
141
- "author": "nathanvale",
142
- "description": "Production-proven Playwright web scraping patterns with selector-first approach and robust error handling.\nUse when users need to build web scrapers, extract data from websites, automate browser interactions,\nor ask about Playwright selectors, text extraction (innerText vs textContent), regex patterns for HTML,\nfallback hierarchies, or scraping best practices.",
143
- "githubUrl": "https://github.com/nathanvale/side-quest-marketplace/tree/main/plugins/scraper-toolkit/skills/playwright-scraper",
144
- "skillUrl": "https://skillsmp.com/skills/nathanvale-side-quest-marketplace-plugins-scraper-toolkit-skills-playwright-scraper-skill-md",
145
- "stars": 2,
146
- "updatedAt": 1769733906
147
- }
148
- },
149
- {
150
- "file_id": "42348180a6ffcff196f8aa2b23797a0868a891174655e0c4df3245b4bfc530a0",
151
- "filename": "skills/salberg87-authenticated-scrape-skill-md.md",
152
- "score": 0.5437498,
153
- "skill": {
154
- "id": "salberg87-authenticated-scrape-skill-md",
155
- "name": "authenticated-scrape",
156
- "author": "Salberg87",
157
- "description": "Scrape data from authenticated websites by capturing network requests with auth headers automatically. Use when the user wants to extract data from logged-in pages, private dashboards, or authenticated APIs.",
158
- "githubUrl": "https://github.com/Salberg87/authenticated-scrape",
159
- "skillUrl": "https://skillsmp.com/skills/salberg87-authenticated-scrape-skill-md",
160
- "stars": 0,
161
- "updatedAt": 1767684913
162
- }
163
- }
164
- ],
165
- "has_more": false,
166
- "next_page": null
167
- },
168
- "meta": {
169
- "requestId": "f841d8bc-3d77-4a00-9899-4df8b4e52c86",
170
- "responseTimeMs": 3327
171
- }
111
+ type: "image",
112
+ source: { type: "base64", media_type: "image/jpeg", data: "..." }
172
113
  }
173
114
 
174
- Error Handling
175
- The API uses standard HTTP status codes and returns error details in JSON format.
115
+ // OpenAI
116
+ {
117
+ type: "image_url",
118
+ image_url: { url: "data:image/jpeg;base64,...", detail: "auto" }
119
+ }
120
+ ```
121
+ **转换函数**: [`convertClaudeImageToOpenAI()`](src/server/transformers/claude-openai.ts#L35), [`convertOpenAIImageToClaude()`](src/server/transformers/claude-openai.ts#L79)
122
+
123
+ #### 思考内容 (Thinking)
124
+ ```typescript
125
+ // Claude
126
+ { type: "thinking", thinking: "Let me analyze..." }
176
127
 
177
- Error Code HTTP Description
178
- MISSING_API_KEY 401 API key not provided
179
- INVALID_API_KEY 401 Invalid API key
180
- MISSING_QUERY 400 Missing required query parameter
181
- INTERNAL_ERROR 500 Internal server error
182
- Error Response Example:
128
+ // OpenAI Chat
129
+ { thinking: { content: "Let me analyze..." } } // 或 delta.thinking.content
183
130
 
184
- json
131
+ // OpenAI Responses
132
+ { type: "thinking", text: "Let me analyze..." } // 在 output[].content 中
133
+ ```
185
134
 
186
- Copy
135
+ #### 工具调用 (Tool Calls)
136
+ ```typescript
137
+ // Claude
138
+ { type: "tool_use", id: "call_123", name: "get_weather", input: {...} }
139
+
140
+ // OpenAI
187
141
  {
188
- "success": false,
189
- "error": {
190
- "code": "INVALID_API_KEY",
191
- "message": "The provided API key is invalid"
192
- }
142
+ tool_calls: [{
143
+ id: "call_123",
144
+ type: "function",
145
+ function: { name: "get_weather", arguments: "{...}" }
146
+ }]
193
147
  }
148
+ ```
149
+
150
+ #### 工具结果 (Tool Results)
151
+ ```typescript
152
+ // Claude
153
+ { type: "tool_result", tool_use_id: "call_123", content: "..." }
154
+
155
+ // OpenAI
156
+ { role: "tool", tool_call_id: "call_123", content: "..." }
157
+ ```
158
+
159
+ ### 参数转换映射
160
+
161
+ #### 温度参数 (Temperature)
162
+ - 所有 API: `0.0 - 2.0` (OpenAI) / `0.0 - 1.0` (Claude)
163
+ - 直接传递,需注意范围差异
164
+
165
+ #### 停止序列 (Stop Sequences)
166
+ ```typescript
167
+ // Claude
168
+ { stop_sequences: ["END", "STOP"] }
169
+
170
+ // OpenAI
171
+ { stop: ["END", "STOP"] } // 或 "END"
172
+ ```
173
+
174
+ #### Token 限制
175
+ ```typescript
176
+ // Claude
177
+ { max_tokens: 4096 }
178
+
179
+ // OpenAI Chat
180
+ { max_tokens: 4096 }
181
+
182
+ // OpenAI Responses
183
+ { max_output_tokens: 4096 }
184
+ ```
185
+
186
+ #### 工具选择 (Tool Choice)
187
+ ```typescript
188
+ // Claude
189
+ { tool_choice: "any" } // 或 { type: "tool", name: "func_name" }
190
+
191
+ // OpenAI
192
+ { tool_choice: "required" } // 或 { type: "function", function: { name: "func_name" } }
193
+ ```
194
+ **转换函数**: [`mapClaudeToolChoiceToOpenAI()`](src/server/transformers/claude-openai.ts#L136)
195
+
196
+ ### Stop Reason 映射
197
+
198
+ #### OpenAI → Claude
199
+ | OpenAI finish_reason | Claude stop_reason |
200
+ |---------------------|-------------------|
201
+ | `stop` | `end_turn` |
202
+ | `length` | `max_tokens` |
203
+ | `tool_calls` | `tool_use` |
204
+ | `content_filter` | `content_filter` |
205
+
206
+ **转换函数**: [`mapStopReason()`](src/server/transformers/claude-openai.ts#L187)
207
+
208
+ #### Claude → OpenAI
209
+ | Claude stop_reason | OpenAI finish_reason |
210
+ |-------------------|---------------------|
211
+ | `end_turn` | `stop` |
212
+ | `max_tokens` | `length` |
213
+ | `max_thinking_length` | `length` |
214
+ | `tool_use` | `tool_calls` |
215
+ | `stop_sequence` | `stop` |
216
+ | `content_filter` | `content_filter` |
217
+
218
+ **转换函数**: [`mapClaudeStopReasonToOpenAI()`](src/server/transformers/claude-openai.ts#L207)
219
+
220
+ ### 流式事件转换
221
+
222
+ #### OpenAI Chat → Claude
223
+ | OpenAI Chat Event | Claude Event | 转换位置 |
224
+ |------------------|--------------|----------|
225
+ | `data.choices[0].delta.content` | `content_block_delta` (text_delta) | [`OpenAIToClaudeEventTransform`](src/server/transformers/streaming.ts#L175) |
226
+ | `data.choices[0].delta.thinking.content` | `content_block_delta` (thinking_delta) | [`OpenAIToClaudeEventTransform`](src/server/transformers/streaming.ts#L334) |
227
+ | `data.choices[0].delta.tool_calls` | `content_block_delta` (input_json_delta) | [`OpenAIToClaudeEventTransform`](src/server/transformers/streaming.ts#L354) |
228
+ | `data.choices[0].finish_reason` | `message_delta` (stop_reason) | [`OpenAIToClaudeEventTransform`](src/server/transformers/streaming.ts#L504) |
229
+
230
+ #### OpenAI Responses → Claude
231
+ | Responses Event | Claude Event | 转换位置 |
232
+ |----------------|--------------|----------|
233
+ | `response.reasoning_text.delta` | `content_block_delta` (thinking_delta) | [`handleResponsesAPIEvent()`](src/server/transformers/streaming.ts#L408) |
234
+ | `response.reasoning_summary_text.delta` | `content_block_delta` (thinking_delta) | [`handleResponsesAPIEvent()`](src/server/transformers/streaming.ts#L429) |
235
+ | `response.output_text.delta` | `content_block_delta` (text_delta) | [`handleResponsesAPIEvent()`](src/server/transformers/streaming.ts#L450) |
236
+ | `response.refusal.delta` | `content_block_delta` (text_delta) | [`handleResponsesAPIEvent()`](src/server/transformers/streaming.ts#L471) |
237
+ | `response.completed/failed/incomplete` | `message_stop` | [`handleResponsesAPIEvent()`](src/server/transformers/streaming.ts#L500) |
238
+
239
+ #### Claude → OpenAI Chat
240
+ | Claude Event | OpenAI Chat Event | 转换位置 |
241
+ |--------------|------------------|----------|
242
+ | `content_block_delta` (text_delta) | `data.choices[0].delta.content` | [`ClaudeToOpenAIChatEventTransform`](src/server/transformers/streaming.ts#L622) |
243
+ | `content_block_delta` (thinking_delta) | `data.choices[0].delta.thinking.content` | [`ClaudeToOpenAIChatEventTransform`](src/server/transformers/streaming.ts#L628) |
244
+ | `content_block_delta` (input_json_delta) | `data.choices[0].delta.tool_calls` | [`ClaudeToOpenAIChatEventTransform`](src/server/transformers/streaming.ts#L594) |
245
+ | `message_delta` (stop_reason) | `data.choices[0].finish_reason` | [`ClaudeToOpenAIChatEventTransform`](src/server/transformers/streaming.ts#L632) |
246
+
247
+ ### Token Usage 转换
248
+
249
+ ```typescript
250
+ // OpenAI 格式
251
+ {
252
+ prompt_tokens: 100,
253
+ completion_tokens: 50,
254
+ total_tokens: 150,
255
+ prompt_tokens_details: { cached_tokens: 20 }
256
+ }
257
+
258
+ // Claude 格式
259
+ {
260
+ input_tokens: 80, // prompt - cached
261
+ output_tokens: 50,
262
+ cache_read_input_tokens: 20 // cached
263
+ }
264
+ ```
265
+ **转换函数**: [`convertOpenAIUsageToClaude()`](src/server/transformers/claude-openai.ts#L173)
266
+
267
+ ### 特殊处理
268
+
269
+ #### DeepSeek Developer 角色
270
+ 某些 OpenAI 兼容 API(如 DeepSeek)不支持 `system` 角色,需要使用 `developer` 角色。
271
+
272
+ **检测函数**: [`shouldUseDeveloperRole()`](src/server/transformers/claude-openai.ts#L229)
273
+
274
+ #### System 提示词数组
275
+ Claude 支持数组格式的 system 提示词(包含缓存控制),转换时会提取文本内容。
276
+
277
+ **处理位置**: [`transformClaudeRequestToOpenAIChat()`](src/server/transformers/claude-openai.ts#L169)
278
+
279
+ ### 文件结构
280
+
281
+ ```
282
+ src/server/transformers/
283
+ ├── claude-openai.ts # 请求/响应转换
284
+ │ ├── transformClaudeRequestToOpenAIChat()
285
+ │ ├── transformOpenAIChatResponseToClaude()
286
+ │ ├── transformClaudeResponseToOpenAIChat()
287
+ │ ├── transformChatCompletionsToResponses()
288
+ │ ├── transformResponsesToChatCompletions()
289
+ │ ├── convertClaudeImageToOpenAI()
290
+ │ ├── convertOpenAIImageToClaude()
291
+ │ ├── mapClaudeToolChoiceToOpenAI()
292
+ │ ├── mapStopReason()
293
+ │ └── mapClaudeStopReasonToOpenAI()
294
+ └── streaming.ts # 流式事件转换
295
+ ├── SSEParserTransform # SSE 解析
296
+ ├── SSESerializerTransform # SSE 序列化
297
+ ├── OpenAIToClaudeEventTransform # OpenAI → Claude 流式
298
+ ├── ClaudeToOpenAIChatEventTransform # Claude → OpenAI 流式
299
+ └── handleResponsesAPIEvent() # Responses API 事件处理
300
+ ```
301
+
302
+ ### 开发注意事项
303
+
304
+ 1. **类型安全**:转换函数使用 `any` 类型处理动态 API 格式
305
+ 2. **向后兼容**:保持对旧版本 API 响应的支持
306
+ 3. **错误恢复**:流式转换器捕获异常避免中断整个流
307
+ 4. **性能优化**:使用数组格式构建消息(当包含图像时)
308
+ 5. **完整测试**:覆盖所有转换路径和边界情况
309
+
310
+ ### 相关文档
311
+
312
+ - [Claude API Schema](schemes/claude.schema.md) - Claude 官方 API 规范
313
+ - [OpenAI API Schema](schemes/openai.schema.md) - OpenAI 官方 API 规范
314
+ - [CLAUDE.md](CLAUDE.md) - 项目架构文档
315
+
@@ -25,6 +25,9 @@ const os_1 = __importDefault(require("os"));
25
25
  const auth_1 = require("./auth");
26
26
  const version_check_1 = require("./version-check");
27
27
  const utils_1 = require("./utils");
28
+ const tools_service_1 = require("./tools-service");
29
+ const websocket_service_1 = require("./websocket-service");
30
+ const rules_status_service_1 = require("./rules-status-service");
28
31
  const config_metadata_1 = require("./config-metadata");
29
32
  const config_1 = require("./config");
30
33
  const appDir = path_1.default.join(os_1.default.homedir(), '.aicodeswitch');
@@ -1356,6 +1359,19 @@ ${instruction}
1356
1359
  res.json({ success: false });
1357
1360
  }
1358
1361
  })));
1362
+ // 工具安装检测相关路由
1363
+ app.get('/api/tools/status', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
1364
+ console.log('[API] GET /api/tools/status - 获取工具安装状态');
1365
+ try {
1366
+ const status = yield (0, tools_service_1.getToolsInstallationStatus)();
1367
+ console.log('[API] 工具安装状态:', status);
1368
+ res.json(status);
1369
+ }
1370
+ catch (error) {
1371
+ console.error('[API] 获取工具状态失败:', error);
1372
+ res.status(500).json({ error: '获取工具状态失败' });
1373
+ }
1374
+ })));
1359
1375
  };
1360
1376
  const start = () => __awaiter(void 0, void 0, void 0, function* () {
1361
1377
  fs_1.default.mkdirSync(dataDir, { recursive: true });
@@ -1377,6 +1393,28 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
1377
1393
  const server = app.listen(port, host, () => {
1378
1394
  console.log(`Admin server running on http://${host}:${port}`);
1379
1395
  });
1396
+ // 创建 WebSocket 服务器用于工具安装
1397
+ const toolInstallWss = (0, websocket_service_1.createToolInstallationWSServer)();
1398
+ // 创建 WebSocket 服务器用于规则状态
1399
+ const rulesStatusWss = (0, rules_status_service_1.createRulesStatusWSServer)();
1400
+ // 将 WebSocket 服务器附加到 HTTP 服务器
1401
+ server.on('upgrade', (request, socket, head) => {
1402
+ if (request.url === '/api/tools/install') {
1403
+ toolInstallWss.handleUpgrade(request, socket, head, (ws) => {
1404
+ toolInstallWss.emit('connection', ws, request);
1405
+ });
1406
+ }
1407
+ else if (request.url === '/api/rules/status') {
1408
+ rulesStatusWss.handleUpgrade(request, socket, head, (ws) => {
1409
+ rulesStatusWss.emit('connection', ws, request);
1410
+ });
1411
+ }
1412
+ else {
1413
+ socket.destroy();
1414
+ }
1415
+ });
1416
+ console.log(`WebSocket server for tool installation attached to ws://${host}:${port}/api/tools/install`);
1417
+ console.log(`WebSocket server for rules status attached to ws://${host}:${port}/api/rules/status`);
1380
1418
  const shutdown = () => __awaiter(void 0, void 0, void 0, function* () {
1381
1419
  console.log('Shutting down server...');
1382
1420
  dbManager.close();