Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ coverage:
target: 80% # 告诉裁判:整体覆盖率 80% 就给我亮绿灯
patch:
default:
target: 80% # 告诉裁判:这次 PR 新增的代码覆盖率达到 80% 也给我亮绿灯
target: 80% # 告诉裁判:这次 PR 新增的代码覆盖率达到 95% 才给我亮绿灯
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,27 @@ go run ./cmd/neocode --workdir /path/to/workspace
- 不提交明文密钥、个人配置或会话数据
- 不提交无关改动与临时文件

## 网关运维与安全(GW-06)

- 静默认证(Silent Auth):
- 启动 `neocode gateway` 时会自动读取 `~/.neocode/auth.json`。
- 若凭证不存在或损坏,会自动生成高强度 token 并写回该文件。
- `url-dispatch` 会自动读取同一 token 并先发送 `gateway.authenticate`,再发送业务请求。
- 认证与授权顺序:`Auth -> ACL -> Dispatch`。
- 未认证返回 `unauthorized`。
- 已认证但不允许的方法返回 `access_denied`。
- 运维端点:
- 免鉴权:`GET /healthz`、`GET /version`
- 需鉴权:`GET /metrics`、`GET /metrics.json`(`Authorization: Bearer <token>`)
- 关键默认治理参数(可通过 `config.yaml` 的 `gateway.*` 配置):
- `max_frame_bytes=1MiB`
- `ipc_max_connections=128`
- `http_max_request_bytes=1MiB`
- `http_max_stream_connections=128`
- `ipc_read/write_sec=30/30`
- `http_read/write/shutdown_sec=15/15/2`
- 详细设计文档:[`docs/gateway-detailed-design.md`](docs/gateway-detailed-design.md)

## License

MIT
204 changes: 204 additions & 0 deletions docs/gateway-detailed-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# Gateway 详细设计(EPIC-GW-06)

## 1. 目标与边界

Gateway 是 NeoCode 的协议与路由中枢,职责是:

- 生命周期管理(IPC + HTTP/WS/SSE 并行启动、优雅关闭)
- 协议归一化(外层 JSON-RPC 2.0,内层 `gateway.MessageFrame`)
- 鉴权与 ACL(`Auth -> ACL -> Dispatch`)
- 会话流式中继(session/run/channel 精准投递)

Gateway **不承载业务逻辑**,不会做模型推理、工具编排与 Provider 选择。业务执行仅由 Runtime 决定。

## 2. 架构图(含进程边界)

```mermaid
flowchart LR
subgraph ClientProcess["客户端进程边界"]
CLI["CLI / TUI"]
WEB["Web / Desktop UI"]
EXT["External Adapter\nURL Scheme / Clipboard"]
end

subgraph GatewayProcess["Gateway 进程边界"]
IPC["IPC Listener\nUDS / Named Pipe"]
NET["HTTP/WS/SSE Listener"]
AUTH["Auth + ACL"]
NORM["JSON-RPC -> MessageFrame\nNormalize"]
ROUTER["Dispatch + Stream Relay"]
OPS["Health / Version / Metrics"]
end

subgraph RuntimeProcess["Runtime 进程边界"]
RT["RuntimePort\n编排与事件流"]
TOOLS["Tools"]
PROVIDER["Provider Adapter"]
end

subgraph CloudBoundary["云端边界"]
CLOUD["Cloud LLM API"]
end

CLI --> IPC
WEB --> NET
EXT --> IPC
EXT --> NET

IPC --> AUTH
NET --> AUTH
AUTH --> NORM
NORM --> ROUTER
ROUTER --> RT
ROUTER --> OPS

RT --> TOOLS
RT --> PROVIDER
PROVIDER --> CLOUD
```

## 3. 核心时序图

### 3.1 本地控制面链路(Client -> Gateway -> Runtime -> Client)

```mermaid
sequenceDiagram
box rgb(238, 246, 255) 客户端进程
participant C as "Client (CLI/WS/SSE)"
end
box rgb(241, 255, 241) Gateway 进程
participant G as "Gateway Listener"
participant A as "Auth + ACL"
participant D as "Normalize + Dispatch"
participant R as "Stream Relay"
end
box rgb(255, 249, 238) Runtime 进程
participant RT as "RuntimePort"
end

C->>G: JSON-RPC request
G->>A: 校验 Token / ACL
A-->>G: allow
G->>D: Normalize(JSON-RPC -> MessageFrame)
D->>RT: RuntimePort 调用(无业务改写)
RT-->>D: 结果 / 事件
D->>R: MessageFrame(event/ack/error)
R-->>C: JSON-RPC response/notification
```

### 3.2 云端调用链路(Runtime -> Provider -> Cloud API)

```mermaid
sequenceDiagram
box rgb(238, 246, 255) 客户端进程
participant C as "Client"
end
box rgb(241, 255, 241) Gateway 进程
participant G as "Gateway"
end
box rgb(255, 249, 238) Runtime 进程
participant RT as "Runtime"
participant P as "Provider Adapter"
end
box rgb(255, 240, 245) 云端边界
participant LLM as "Cloud LLM API"
end

C->>G: gateway.run / wake.openUrl
G->>RT: 透传规范化请求
RT->>P: 选择并调用 Provider
P->>LLM: HTTP API
LLM-->>P: streaming/result
P-->>RT: 统一 Provider 结果
RT-->>G: runtime events
G-->>C: gateway.event / result
```

## 4. 数据流向(本地端与云端区别)

- 本地控制面:
- 客户端只与 Gateway 通信(IPC/HTTP/WS/SSE)。
- Gateway 负责协议、连接、鉴权、路由与中继。
- 本地控制面不直接触达云端。
- 云端调用:
- 仅 Runtime 与 Provider 层触达 Cloud API。
- Gateway 不感知模型厂商细节,不拼接 Provider 私有字段。

## 5. 对外接口清单

### 5.1 面向客户端接口

| 接口 | 方向 | 认证 | 说明 |
|---|---|---|---|
| IPC (UDS / Named Pipe) | Client -> Gateway | `gateway.authenticate` 握手后复用 | 本地控制面主入口 |
| `POST /rpc` | Client -> Gateway | `Authorization: Bearer <token>` | 单次 JSON-RPC 请求 |
| `GET /ws` | Client <-> Gateway | `gateway.authenticate` 握手后复用 | 双向流式请求与通知 |
| `GET /sse` | Client <- Gateway | `?token=<token>` | 单向流式通知与心跳 |
| `GET /healthz` | Client -> Gateway | 无 | 健康检查 |
| `GET /version` | Client -> Gateway | 无 | 版本信息 |
| `GET /metrics` | Client -> Gateway | Bearer Token | Prometheus 指标 |
| `GET /metrics.json` | Client -> Gateway | Bearer Token | JSON 指标快照 |

### 5.2 JSON-RPC 方法

| Method | 方向 | 说明 |
|---|---|---|
| `gateway.authenticate` | request/response | 连接级鉴权,成功后复用认证态 |
| `gateway.ping` | request/response | 健康探针 |
| `gateway.bindStream` | request/response | 会话流绑定 |
| `wake.openUrl` | request/response | URL Scheme 唤醒入口 |
| `gateway.event` | notification | Gateway 推送运行时事件 |

### 5.3 面向 Runtime 接口(`RuntimePort`)

| 方法 | 说明 |
|---|---|
| `Run(ctx, input)` | 发起一次运行编排 |
| `Compact(ctx, input)` | 执行会话压缩 |
| `ResolvePermission(ctx, input)` | 回填权限审批结果 |
| `CancelActiveRun()` | 取消活动运行 |
| `Events()` | 订阅运行时事件流 |
| `ListSessions(ctx)` | 获取会话摘要 |
| `LoadSession(ctx, id)` | 加载会话详情 |

## 6. 安全与治理基线

### 6.1 Silent Auth

- Token 文件:`~/.neocode/auth.json`
- 启动网关时自动加载;缺失或损坏自动重建
- 文件结构:`version`, `token`, `created_at`, `updated_at`

### 6.2 ACL 与错误模型

- 执行顺序:`Auth -> ACL -> Dispatch`
- 错误返回统一:
- JSON-RPC:`error.code`
- Gateway 稳定码:`error.data.gateway_code`
- 关键稳定码:`unauthorized`, `access_denied`, `invalid_frame`, `unsupported_action`

### 6.3 默认治理参数

| 配置项 | 默认值 |
|---|---|
| `gateway.limits.max_frame_bytes` | `1048576` |
| `gateway.limits.ipc_max_connections` | `128` |
| `gateway.limits.http_max_request_bytes` | `1048576` |
| `gateway.limits.http_max_stream_connections` | `128` |
| `gateway.timeouts.ipc_read_sec` | `30` |
| `gateway.timeouts.ipc_write_sec` | `30` |
| `gateway.timeouts.http_read_sec` | `15` |
| `gateway.timeouts.http_write_sec` | `15` |
| `gateway.timeouts.http_shutdown_sec` | `2` |
| `gateway.observability.metrics_enabled` | `true` |

## 7. 配置优先级

- `flags > config.yaml > default constants`
- 当前支持通过 `~/.neocode/config.yaml` 的 `gateway.*` 段配置治理参数。

## 8. 非目标(本期)

- 不新增 Provider/Tools 业务能力
- 不引入外网公开监听与 TLS
- 不在 Gateway 内实现 Runtime 业务决策
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/x/ansi v0.11.6 // indirect
Expand All @@ -45,7 +47,12 @@ require (
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sahilm/fuzzy v0.1.1 // indirect
Expand All @@ -57,10 +64,12 @@ require (
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yuin/goldmark v1.7.13 // indirect
github.com/yuin/goldmark-emoji v1.0.6 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 // indirect
golang.org/x/image v0.28.0 // indirect
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f // indirect
golang.org/x/term v0.41.0 // indirect
golang.org/x/text v0.35.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
)
19 changes: 19 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3v
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
Expand Down Expand Up @@ -59,6 +63,7 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
Expand Down Expand Up @@ -90,10 +95,20 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
Expand Down Expand Up @@ -128,6 +143,8 @@ github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs=
github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.design/x/clipboard v0.7.1 h1:OEG3CmcYRBNnRwpDp7+uWLiZi3hrMRJpE9JkkkYtz2c=
Expand All @@ -150,6 +167,8 @@ golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
Loading
Loading