Release v1.4.3

This commit is contained in:
lutc5
2026-04-30 18:20:04 +08:00
parent a2f777a1a8
commit a02fd51c19
24 changed files with 1909 additions and 1176 deletions

View File

@@ -1,424 +1,316 @@
# lingma-ipc-proxy 架构文档
# lingma-ipc-proxy Architecture
本文档描述 lingma-ipc-proxy 的系统架构、工作原理和核心流程。
This document describes the current architecture of `lingma-ipc-proxy`, including both backend modes:
- `ipc`: bridge to the local Lingma IDE plugin transport
- `remote`: call Lingma remote HTTP APIs directly with detected credentials
---
## 1. 整体架构
## 1. System Overview
```
┌─────────────────────────────────────────────────────────────────────────┐
客户端层 │
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
Claude Code │ │ OpenAI │ │ Cline │ │ Continue │ │
(Anthropic) │ │ SDK │ │ (OpenAI) │ │ (OpenAI) │ │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
└─────────┼─────────────────┼─────────────────┼─────────────────┼─────────┘
│ │ │ │
└─────────────────┴────────┬────────┴─────────────────┘
│ HTTP
┌─────────────────────────────────────────────────────────────────────────┐
│ lingma-ipc-proxy │
┌─────────────────────────────────────────────────────────────────┐ │
│ │ internal/httpapi │ │
┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │ │
│ /v1/models │ │/v1/chat/comp│ │ /v1/messages │ │ │
│ │ (GET) │ │ (POST) │ │ (POST) │ │ │
└──────┬──────┘ └──────┬──────┘ └───────────┬─────────────┘ │ │
│ │ └─────────────────┴──────────┬──────────┘ │ │
│ │ │ normalizeRequest │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ internal/service │ │ │
│ │ │ ┌──────────┐ ┌──────────┐ ┌────────────────────────┐ │ │ │
│ │ │ │ Session │ │ Prompt │ │ Stream/Event │ │ │ │
│ │ │ │ Manager │ │ Builder │ │ Handler │ │ │ │
│ │ │ └────┬─────┘ └────┬─────┘ └───────────┬────────────┘ │ │ │
│ │ │ └─────────────┴──────────┬─────────┘ │ │ │
│ │ │ │ buildLingmaPrompt │ │ │
│ │ │ ▼ │ │ │
│ │ │ ┌─────────────────────────────────────────────────┐ │ │ │
│ │ │ │ internal/lingmaipc │ │ │ │
│ │ │ │ ┌──────────────┐ ┌──────────────────────────┐ │ │ │ │
│ │ │ │ │ WebSocket │ │ Named Pipe (Win) │ │ │ │ │
│ │ │ │ │ Transport │ │ Transport │ │ │ │ │
│ │ │ │ └──────┬───────┘ └───────────┬──────────────┘ │ │ │ │
│ │ │ └─────────┼──────────────────────┼────────────────┘ │ │ │
│ │ └────────────┼──────────────────────┼────────────────────┘ │ │
│ │ │ │ │ │
│ │ ┌────────────┼──────────────────────┼────────────────────┐ │ │
│ │ │ ▼ ▼ │ │ │
│ │ │ ┌─────────────────────────────────────────────────┐ │ │ │
│ │ │ │ internal/toolemulation │ │ │ │
│ │ │ │ ┌──────────────┐ ┌──────────────────────────┐ │ │ │ │
│ │ │ │ │InjectTooling │ │ ParseActionBlocks │ │ │ │ │
│ │ │ │ │ (Prompt) │ │ (Response) │ │ │ │ │
│ │ │ │ └──────────────┘ └──────────────────────────┘ │ │ │ │
│ │ │ └─────────────────────────────────────────────────┘ │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│ WebSocket / Named Pipe
┌─────────────────────────────────────────────────────────────────────────┐
│ Lingma 后端进程 │
│ (VS Code 插件的本地 IPC 服务) │
│ ws://127.0.0.1:8899/ws │
└─────────────────────────────────────────────────────────────────────────┘
│ HTTP API
┌─────────────────────────────────────────────────────────────────────────┐
│ 云端模型服务 │
│ (Kimi-K2.6 / Qwen3-Max / MiniMax-M2.7 等) │
└─────────────────────────────────────────────────────────────────────────┘
```mermaid
flowchart LR
A["Clients<br/>Claude Code / Hermes / Cline / Continue / OpenAI SDK / Anthropic SDK"]
B["internal/httpapi<br/>OpenAI + Anthropic compatible routes"]
C["internal/service<br/>request normalization / session policy / streaming / fallback"]
D["internal/toolemulation<br/>tool prompt injection + action block parsing"]
E["internal/lingmaipc<br/>WebSocket / Named Pipe"]
F["internal/remote<br/>credential detection / model list / chat / SSE"]
G["Lingma plugin local process"]
H["Lingma remote API"]
I["Desktop app<br/>Wails GUI / logs / token stats / persisted state"]
A --> B
I --> B
B --> C
C --> D
C --> E
C --> F
E --> G
F --> H
```
---
## 2. 模块职责
## 2. Runtime Modes
### 2.1 internal/httpapi
### 2.1 IPC mode
HTTP API 适配层,负责将外部请求转换为内部 `service.ChatRequest`
`backend=ipc`
| 端点 | 协议 | 功能 |
|------|------|------|
| `GET /v1/models` | OpenAI | 返回可用模型列表 |
| `POST /v1/chat/completions` | OpenAI | 聊天补全(流式/非流式) |
| `POST /v1/messages` | Anthropic | 消息接口(流式/非流式) |
- Reads local plugin transport information
- Connects through:
- WebSocket on macOS / Linux
- Named Pipe on Windows
- Reuses Lingma plugin session semantics
- Session/environment options in the desktop UI apply only here
**核心函数:**
- `handleOpenAIChatCompletions()` - 处理 OpenAI 格式请求
- `handleAnthropicMessages()` - 处理 Anthropic 格式请求
- `normalizeOpenAIRequest()` / `normalizeAnthropicRequest()` - 归一化请求
### 2.2 Remote API mode
**关键设计:**
- 支持 CORS 预检请求 (`OPTIONS`)
- 单请求并发控制 (`tryAcquire()` / `release()`)
- 流式响应通过 `http.Flusher` 实现 SSE
`backend=remote`
### 2.2 internal/service
业务逻辑层,负责会话管理和 Prompt 构建。
**核心结构:**
```go
type Service struct {
cfg Config
client *lingmaipc.Client
stickySessionID string
stickyModelID string
}
```
**核心函数:**
- `Generate()` - 非流式生成
- `GenerateStream()` - 流式生成(返回 `events` + `done` channel
- `buildLingmaPrompt()` - 构建 Lingma 原生 Prompt
- `runPromptLocked()` - 发送 `session/prompt` RPC 并监听 `session/update` 通知
**会话模式:**
| 模式 | 行为 |
|------|------|
| `reuse` | 复用 sticky session多轮对话保持上下文 |
| `fresh` | 每个请求新建临时 session完成后删除 |
| `auto` | 单轮请求复用;带 system/history 的请求用 fresh |
### 2.3 internal/lingmaipc
IPC 通信层,负责与 Lingma 后端进程建立连接。
**传输方式:**
| 平台 | 默认传输 | 说明 |
|------|----------|------|
| Windows | Named Pipe | `\\.\pipe\lingma-*` |
| macOS/Linux | WebSocket | `ws://127.0.0.1:{port}/ws` |
**连接发现:**
- 读取 VS Code 插件缓存:`~/.config/Lingma/SharedClientCache/.info.json`
- 获取 WebSocket 端口号
- 自动重连机制
**RPC 协议:**
- `session/new` - 创建会话
- `session/prompt` - 发送用户消息
- `session/update` - 接收流式响应通知
- `session/set_model` - 切换模型
- `chat/deleteSessionById` - 删除会话
### 2.4 internal/toolemulation
Tool 调用模拟层,将标准 `tools` 协议转换为 Prompt 层契约。
**核心流程:**
```
Client tools ──→ ExtractAnthropicTools() ──→ []Tool
InjectTooling() ──→ System Prompt + Tool 说明
模型输出 action block
ParseActionBlocks() ──→ []ToolCall
编码为 Anthropic tool_use / OpenAI tool_calls
```
**Prompt 契约格式:**
```
```json action
{"tool":"NAME","parameters":{"key":"value"}}
```
```
**支持格式:**
- `{"tool":"X","parameters":{}}` ✅ 标准格式
- `{"tool":"X","arguments":{}}` ✅ 兼容格式
- `{"tool":"X","input":{}}` ✅ 兼容格式
- `{"tool":"X","arg1":"val"}` ✅ 顶层参数(部分模型)
- Reads Lingma remote base URL
- Loads credentials from:
- explicit `remote_auth_file`
- or detected Lingma cache under `~/.lingma`
- Calls remote model list and chat endpoints directly
- Supports timeout / 429 / 5xx fallback across available remote models
- Does not use local plugin session environment knobs
---
## 3. 核心流程
## 3. Module Responsibilities
### 3.1 普通聊天请求流程
### 3.1 `cmd/lingma-ipc-proxy`
Entry point and config loading.
Responsibilities:
- parse CLI flags
- merge config file + environment + flags
- choose backend mode
- build `service.Config`
- start `internal/httpapi.Server`
Important config fields:
- `backend`
- `transport`
- `websocket_url`
- `pipe`
- `remote_base_url`
- `remote_auth_file`
- `remote_version`
- `remote_fallback_enabled`
- `remote_fallback_models`
### 3.2 `internal/httpapi`
Compatibility layer for OpenAI and Anthropic style APIs.
Primary routes:
- `GET /v1/models`
- `POST /v1/chat/completions`
- `POST /v1/messages`
- `GET /health`
- `GET /props`
Responsibilities:
- normalize OpenAI / Anthropic requests into `service.ChatRequest`
- convert service results back to OpenAI / Anthropic payloads
- stream SSE responses
- sanitize and record request / response payloads for debug UI
### 3.3 `internal/service`
Core orchestration layer.
Responsibilities:
- choose active backend
- warm up backend connection / credentials
- list models
- generate non-streaming responses
- generate streaming responses
- apply session reuse policy in IPC mode
- inject / parse tool emulation
- normalize image inputs
- apply remote fallback order
Important behavior split:
- IPC path uses `internal/lingmaipc`
- Remote path uses `internal/remote`
### 3.4 `internal/lingmaipc`
Local transport client for Lingma plugin IPC.
Responsibilities:
- detect WebSocket / pipe endpoint
- dial and reconnect
- send RPC messages such as:
- `session/new`
- `session/prompt`
- `session/set_model`
- `chat/deleteSessionById`
- consume `session/update` notifications
### 3.5 `internal/remote`
Remote HTTP client for Lingma cloud APIs.
Responsibilities:
- resolve base URL
- load and validate credentials
- derive machine / user identity for remote auth
- list remote models
- call remote chat endpoint
- handle remote SSE streaming
### 3.6 `internal/toolemulation`
Prompt-based tool bridge for models that do not expose native tool calling in Lingma transport.
Responsibilities:
- extract tool definitions from OpenAI / Anthropic requests
- append tool contract to prompt
- parse JSON action blocks from model output
- project tool calls back to:
- Anthropic `tool_use`
- OpenAI `tool_calls`
---
## 4. Request Flow
### 4.1 Shared ingress flow
```mermaid
sequenceDiagram
participant C as Client
participant H as HTTP API
participant S as Service
participant L as Lingma IPC
participant B as Lingma Backend
participant Client
participant HTTP as httpapi
participant Service as service
C->>H: POST /v1/messages
H->>H: normalizeAnthropicRequest()
H->>S: GenerateStream(req)
S->>S: ensureConnected()
S->>S: resolveSession()
S->>S: buildLingmaPrompt()
S->>L: Send("session/prompt", params)
L->>B: WebSocket RPC
B->>L: session/update (agent_message_chunk)
loop 流式响应
L->>S: notification (chunk)
S->>H: events <- StreamEvent{Delta}
H->>C: SSE: content_block_delta
end
B->>L: session/update (chat_finish)
L->>S: notification (finish)
S->>H: done <- StreamResult
H->>C: SSE: message_stop
Client->>HTTP: OpenAI/Anthropic request
HTTP->>HTTP: normalize request
HTTP->>Service: Generate / GenerateStream
```
### 3.2 Tool 调用流程
### 4.2 IPC backend flow
```mermaid
sequenceDiagram
participant C as Client
participant H as HTTP API
participant T as ToolEmulation
participant S as Service
participant L as Lingma IPC
participant Service
participant Tool as toolemulation
participant IPC as lingmaipc
participant Plugin as Lingma plugin
C->>H: POST /v1/messages (with tools)
H->>T: ExtractAnthropicTools()
H->>S: GenerateStream(req)
S->>T: InjectTooling(system, tools)
S->>L: session/prompt (with tool prompt)
L->>S: response (with action blocks)
S->>T: ParseActionBlocks(text)
T->>S: []ToolCall
S->>H: ChatResult{Text, ToolCalls}
H->>C: SSE: tool_use blocks
C->>H: POST /v1/messages (tool_result)
H->>T: ActionOutputPrompt(toolUseID, content)
H->>S: GenerateStream(req)
S->>L: session/prompt (with tool result)
L->>S: response
S->>H: ChatResult
H->>C: SSE: final response
Service->>Tool: inject tool contract if needed
Service->>IPC: ensure connected
Service->>IPC: create/reuse session
Service->>IPC: session/prompt
IPC->>Plugin: RPC message
Plugin-->>IPC: session/update chunks
IPC-->>Service: stream events
Service-->>Service: parse tool blocks / image references / stop reason
```
### 3.3 图片传输流程
### 4.3 Remote backend flow
```mermaid
sequenceDiagram
participant C as Client
participant H as HTTP API
participant S as Service
participant L as Lingma IPC
participant Service
participant Remote as remote client
participant API as Lingma remote API
C->>H: POST /v1/messages (with image)
H->>H: extractAnthropicImages()
H->>S: ChatRequest{Images: [...]}
S->>S: runPromptLocked()
Note over S: 1. 保存 base64 到 /tmp/lingma-img-*.ext
Note over S: 2. 构建 URI: lingma:///agent/file?path=...
S->>L: session/prompt
Note over L: prompt: [{type:"text"}, {type:"image", mimeType, uri, data}]
L->>S: response (model sees image)
S->>H: ChatResult
H->>C: SSE response
```
### 3.4 流式输出 SSE 事件序列
**Anthropic 格式(流式):**
```
event: message_start
data: {"type":"message_start","message":{...}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"你"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"好"}}
... (更多 delta)
event: content_block_stop
data: {"type":"content_block_stop","index":0}
[如有 tool_calls]
event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"...","name":"Bash","input":{"command":"ls /"}}}
event: content_block_stop
data: {"type":"content_block_stop","index":1}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":5}}
event: message_stop
data: {"type":"message_stop"}
Service->>Remote: load credentials / ensure client
Service->>Remote: list models if needed
Service->>Remote: chat request
Remote->>API: HTTPS request
API-->>Remote: JSON or SSE response
Remote-->>Service: normalized result
Service-->>Service: fallback to next model when allowed
```
---
## 4. 关键技术决策
## 5. Remote Fallback Strategy
### 4.1 为什么使用 Tool Emulation 而非原生 Tool Calling
Remote fallback is used only when all conditions are true:
Lingma 后端模型Kimi、Qwen 等)不原生支持 OpenAI/Anthropic 的 `tools` 协议。因此代理层需要将工具定义注入到 Prompt 中,通过结构化文本输出模拟工具调用。
- `backend=remote`
- `remote_fallback_enabled=true`
- request has not emitted stream output yet
- upstream error matches timeout / 429 / 5xx class
**优点:**
- 不依赖上游模型能力
- 兼容任何纯聊天模型
- 可精确控制 Prompt 格式
Current default order:
**缺点:**
- 模型需要学习特定格式
- 解析可能有容错问题
- 增加了 Prompt 长度
1. `kmodel`
2. `mmodel`
3. `dashscope_qwen3_coder`
4. `dashscope_qmodel`
5. `dashscope_qwen_max_latest`
6. `dashscope_qwen_plus_20250428_thinking`
### 4.2 为什么使用 WebSocket/Named Pipe 而非 HTTP
Lingma 插件使用本地 IPC 与后端通信,优势:
- 低延迟(本地通信)
- 双向实时通知session/update
- 认证信息由插件管理,代理无需处理
### 4.3 图片传输的双保险策略
```
Prompt 数组 (Lingma 原生格式):
[
{"type":"text","text":"..."},
{"type":"image","mimeType":"image/png","uri":"lingma:///agent/file?path=...","data":"base64..."}
]
```
- `uri`: Lingma 后端必须验证的本地文件路径
- `data`: base64 编码的图像数据(备用)
- `mimeType`: 图像类型标识
### 4.4 单请求并发控制
Lingma IPC 一次只能处理一个请求,因此代理使用 `tryAcquire()` 机制:
```go
if !s.tryAcquire() {
writeAnthropicError(w, 429, "rate_limit_error",
"Lingma IPC proxy handles one request at a time.")
return
}
defer s.release()
```
Before using that order, the service filters candidates against the actual `/v1/models` result from the remote backend so unavailable models are skipped.
---
## 5. 配置说明
## 6. Desktop App Architecture
### 5.1 配置文件结构
The Wails desktop app is a management UI around the local proxy process.
```json
{
"host": "127.0.0.1",
"port": 8095,
"transport": "websocket",
"mode": "agent",
"shell_type": "zsh",
"session_mode": "auto",
"timeout": 120,
"cwd": "/Users/tiancheng"
}
```
Responsibilities:
### 5.2 配置项说明
- start / stop / restart proxy
- show current backend and resolved endpoints
- persist:
- request history
- logs
- token statistics
- show detected IPC and remote credentials metadata
- edit config and restart proxy on save
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `host` | string | `127.0.0.1` | HTTP 监听地址 |
| `port` | int | `8095` | HTTP 监听端口 |
| `transport` | string | `auto` | IPC 传输方式:`auto`/`pipe`/`websocket` |
| `mode` | string | `chat` | 模式:`chat`/`agent` |
| `shell_type` | string | `powershell` | 终端类型 |
| `session_mode` | string | `auto` | 会话模式:`reuse`/`fresh`/`auto` |
| `timeout` | int | `120` | 请求超时(秒) |
| `cwd` | string | `""` | 工作目录(传给 Lingma 后端) |
Persisted local state:
- config: `~/.config/lingma-ipc-proxy/config.json`
- UI/runtime state: `~/.config/lingma-ipc-proxy/app-state.json`
Production packaging rules:
- packaged app should not auto-open inspector
- local development can opt in with `LINGMA_DESKTOP_DEBUG=1`
---
## 6. 扩展点
## 7. Key Design Decisions
### 6.1 添加新模型
### 7.1 Why keep both IPC and remote modes?
`service.go` 的模型映射中添加:
Because the two modes solve different problems:
```go
func (s *Service) resolveInternalModelID(model string) string {
switch strings.ToLower(strings.TrimSpace(model)) {
case "kimi-k2.6":
return "kimi2.6"
case "qwen3-max":
return "qwen3max"
// 添加新模型映射
default:
return ""
}
}
```
- IPC mode preserves plugin session semantics and local tool environment
- Remote mode avoids plugin runtime coupling and is usually better for third-party agent clients
### 6.2 添加新 Tool 格式支持
### 7.2 Why keep tool emulation even with remote mode?
`toolemulation.go``parseToolCallJSON()` 中扩展参数解析逻辑。
Because Lingma-exposed models are not guaranteed to speak OpenAI/Anthropic native tool protocol consistently across all routes. The proxy must keep a stable external contract even when the upstream model capability is uneven.
### 6.3 添加新 API 端点
### 7.3 Why persist requests and token stats in the desktop app?
`httpapi/server.go``NewServer()` 中注册新路由。
Because the GUI is used as an operational console, not a transient preview. Users need model usage, logs, and recent traffic to survive app restarts.
---
*文档版本: 2025-04-25*
*对应代码版本: 当前 master*
## 8. Known Boundaries
- IPC mode still has stronger environment coupling with the local Lingma plugin
- remote credential detection depends on local Lingma cache / auth file layout
- image payloads are sanitized in persisted request logs to avoid oversized local state
- request history may contain mixed models in remote mode when fallback is triggered or when different clients specify different models
---
## 9. Files to Read First
If you are extending the system, start here:
- `cmd/lingma-ipc-proxy/main.go`
- `internal/httpapi/server.go`
- `internal/service/service.go`
- `internal/lingmaipc/*`
- `internal/remote/*`
- `desktop/app.go`
- `desktop/main.go`
---
Document version: 2026-04-30