@ahoo-wang/godex 0.0.15 → 0.0.17
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/README.md +213 -271
- package/README.zh-CN.md +218 -275
- package/bin/godex +1 -1
- package/package.json +10 -10
- package/scripts/install.cjs +3 -3
package/README.md
CHANGED
|
@@ -1,19 +1,104 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="design/assets/01-logo-system/png/godex-logo-horizontal-transparent-800x233.png" alt="GodeX" width="480" />
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Make every model a Codex engine.**
|
|
6
|
+
|
|
7
|
+
OpenAI-compatible Responses API gateway — translates `/v1/responses` into upstream Chat Completions API calls, connecting Codex, CLI, IDE, and automation tools with any model provider.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@ahoo-wang/godex)
|
|
10
|
+
[](https://codecov.io/gh/Ahoo-Wang/GodeX)
|
|
6
11
|
[](https://bun.sh)
|
|
7
12
|
[](https://www.typescriptlang.org/)
|
|
8
13
|
|
|
14
|
+
[Getting Started](https://godex.ahoo.me/01-getting-started/overview) · [Architecture](https://godex.ahoo.me/02-architecture/overview) · [Configuration](https://godex.ahoo.me/07-configuration/config-schema) · [Documentation](https://godex.ahoo.me)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
| | Feature | Description |
|
|
21
|
+
|---|---------|-------------|
|
|
22
|
+
| 🔄 | **Protocol Translation** | Bridges OpenAI Responses API and provider-specific Chat Completions APIs |
|
|
23
|
+
| 🔌 | **Provider-agnostic** | Plugin-based adapter system — add providers by implementing a small set of interfaces |
|
|
24
|
+
| ⚡ | **Streaming-first** | 4-stage `TransformStream` pipeline for low-latency SSE delivery |
|
|
25
|
+
| 💾 | **Session History** | Built-in `previous_response_id` chain resolution (SQLite / in-memory) |
|
|
26
|
+
| 🛡️ | **Structured Errors** | Domain-specific error hierarchy with structured codes and diagnostic context |
|
|
27
|
+
| 🔧 | **Built-in Tools** | `local_shell`, `shell`, `apply_patch` — Codex-compatible function tools |
|
|
28
|
+
| 📦 | **Standalone Binary** | Zero runtime dependencies, 6 platform builds via GitHub Actions |
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Install — no Bun required at runtime
|
|
34
|
+
npm install -g @ahoo-wang/godex
|
|
35
|
+
|
|
36
|
+
# Create config interactively
|
|
37
|
+
godex init
|
|
38
|
+
|
|
39
|
+
# Start the gateway
|
|
40
|
+
godex serve
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Point Codex CLI at your GodeX instance:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
export OPENAI_BASE_URL=http://localhost:5678/v1
|
|
47
|
+
export OPENAI_API_KEY=any-value # not validated by GodeX, must be set
|
|
48
|
+
codex
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or use the OpenAI SDK:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import OpenAI from "openai";
|
|
55
|
+
|
|
56
|
+
const client = new OpenAI({
|
|
57
|
+
baseURL: "http://localhost:5678/v1",
|
|
58
|
+
apiKey: "any-value",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const response = await client.responses.create({
|
|
62
|
+
model: "gpt-4o", // mapped to glm-4.7 via godex.yaml models table
|
|
63
|
+
input: "Hello!",
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## How It Works
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
Codex / CLI / IDE
|
|
71
|
+
│
|
|
72
|
+
▼ POST /v1/responses
|
|
73
|
+
┌─────────────────────────────────────────┐
|
|
74
|
+
│ GodeX Gateway │
|
|
75
|
+
│ │
|
|
76
|
+
│ Bun.serve → handleResponses() │
|
|
77
|
+
│ → ResponsesContext.create() │
|
|
78
|
+
│ → ModelResolver.resolve() │
|
|
79
|
+
│ → Registrar.resolve() │
|
|
80
|
+
│ → DefaultAdapter.stream/request() │
|
|
81
|
+
│ → ProviderMapper.map() │
|
|
82
|
+
│ → ChatClient.streamChat() │
|
|
83
|
+
│ → 4-stage TransformStream │
|
|
84
|
+
│ → Response (JSON or SSE) │
|
|
85
|
+
└──────────────┬──────────────────────────┘
|
|
86
|
+
│ Provider Adapter
|
|
87
|
+
▼
|
|
88
|
+
┌─────────────────────────────────────────┐
|
|
89
|
+
│ Chat Completions-compatible API │
|
|
90
|
+
│ (Zhipu, OpenAI, or custom) │
|
|
91
|
+
└─────────────────────────────────────────┘
|
|
92
|
+
```
|
|
93
|
+
|
|
9
94
|
## Architecture
|
|
10
95
|
|
|
11
96
|
```mermaid
|
|
12
97
|
C4Context
|
|
13
|
-
title
|
|
98
|
+
title GodeX — System Context
|
|
14
99
|
|
|
15
100
|
Person(user, "Developer / Codex CLI", "Sends Responses API requests<br/>via the OpenAI-compatible endpoint")
|
|
16
|
-
System(godex_svr, "
|
|
101
|
+
System(godex_svr, "GodeX Server", "Translates Responses API → Chat Completions API<br/>Bun.serve on configurable port")
|
|
17
102
|
SystemDb(sessions, "Session Store", "Stores response history for<br/>previous_response_id chain resolution<br/>SQLite (persistent) or In-Memory")
|
|
18
103
|
System_Ext(zhipu, "Zhipu (智谱)", "Chat Completions API provider")
|
|
19
104
|
System_Ext(openai, "OpenAI", "Chat Completions API provider")
|
|
@@ -26,72 +111,6 @@ C4Context
|
|
|
26
111
|
Rel(godex_svr, other, "POST /chat/completions", "HTTPS")
|
|
27
112
|
```
|
|
28
113
|
|
|
29
|
-
## Request Flow
|
|
30
|
-
|
|
31
|
-
```mermaid
|
|
32
|
-
sequenceDiagram
|
|
33
|
-
actor C as Client (Codex CLI)
|
|
34
|
-
participant R as Router
|
|
35
|
-
participant AC as ApplicationContext
|
|
36
|
-
participant RC as ResponsesContext
|
|
37
|
-
participant MR as ModelResolver
|
|
38
|
-
participant SS as SessionStore
|
|
39
|
-
participant REG as Registrar
|
|
40
|
-
participant A as Adapter (DefaultAdapter)
|
|
41
|
-
participant PM as ProviderMapper
|
|
42
|
-
participant CC as ChatClient
|
|
43
|
-
participant UP as Upstream API
|
|
44
|
-
|
|
45
|
-
C->>R: POST /v1/responses
|
|
46
|
-
R->>RC: ResponsesContext.create(app, body)
|
|
47
|
-
|
|
48
|
-
activate RC
|
|
49
|
-
RC->>MR: resolve(model)
|
|
50
|
-
MR-->>RC: { provider, model }
|
|
51
|
-
RC->>RC: validate provider config
|
|
52
|
-
|
|
53
|
-
opt previous_response_id
|
|
54
|
-
RC->>SS: resolveChain(id)
|
|
55
|
-
SS-->>RC: session snapshot
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
RC->>REG: resolve(provider)
|
|
59
|
-
REG-->>RC: Provider instance
|
|
60
|
-
deactivate RC
|
|
61
|
-
|
|
62
|
-
alt stream = true
|
|
63
|
-
R->>A: adapter.stream(ctx)
|
|
64
|
-
activate A
|
|
65
|
-
A->>PM: request.map(ctx)
|
|
66
|
-
PM-->>A: upstream request
|
|
67
|
-
A->>CC: streamChat(req)
|
|
68
|
-
CC->>UP: POST (SSE)
|
|
69
|
-
UP-->>CC: SSE chunks
|
|
70
|
-
CC-->>A: ReadableStream<SSE>
|
|
71
|
-
A->>A: pipeTransform → ProviderEventToResponseTransformer
|
|
72
|
-
A->>A: pipeTransform → ResponseSessionPersistenceTransformer
|
|
73
|
-
A-->>R: ReadableStream<ResponseStreamEvent>
|
|
74
|
-
deactivate A
|
|
75
|
-
R->>R: pipeTransform → ResponseSseEncodeTransformer
|
|
76
|
-
R-->>C: SSE byte stream
|
|
77
|
-
else stream = false
|
|
78
|
-
R->>A: adapter.request(ctx)
|
|
79
|
-
activate A
|
|
80
|
-
A->>PM: request.map(ctx)
|
|
81
|
-
PM-->>A: upstream request
|
|
82
|
-
A->>CC: chat(req)
|
|
83
|
-
CC->>UP: POST
|
|
84
|
-
UP-->>CC: JSON response
|
|
85
|
-
CC-->>A: upstream response
|
|
86
|
-
A->>PM: response.map(ctx, res)
|
|
87
|
-
PM-->>A: ResponseObject
|
|
88
|
-
A->>SS: save(session)
|
|
89
|
-
A-->>R: ResponseObject
|
|
90
|
-
deactivate A
|
|
91
|
-
R-->>C: JSON response
|
|
92
|
-
end
|
|
93
|
-
```
|
|
94
|
-
|
|
95
114
|
## Component Model
|
|
96
115
|
|
|
97
116
|
```mermaid
|
|
@@ -99,7 +118,7 @@ classDiagram
|
|
|
99
118
|
direction TB
|
|
100
119
|
|
|
101
120
|
class ApplicationContext {
|
|
102
|
-
+config:
|
|
121
|
+
+config: GodeXConfig
|
|
103
122
|
+logger: Logger
|
|
104
123
|
+resolver: ModelResolver
|
|
105
124
|
+registrar: Registrar
|
|
@@ -115,7 +134,7 @@ classDiagram
|
|
|
115
134
|
+provider: Provider
|
|
116
135
|
+responseId: string
|
|
117
136
|
+requestId: string
|
|
118
|
-
+
|
|
137
|
+
+attributes: Map
|
|
119
138
|
+create(app, body)$ Promise~ResponsesContext~
|
|
120
139
|
}
|
|
121
140
|
|
|
@@ -130,7 +149,6 @@ classDiagram
|
|
|
130
149
|
+registerFactory(name, factory)
|
|
131
150
|
+build(providers)
|
|
132
151
|
+resolve(name) Provider
|
|
133
|
-
+list() string[]
|
|
134
152
|
}
|
|
135
153
|
|
|
136
154
|
class Adapter {
|
|
@@ -165,22 +183,6 @@ classDiagram
|
|
|
165
183
|
+streamChat(req) Promise~ReadableStream~
|
|
166
184
|
}
|
|
167
185
|
|
|
168
|
-
class RequestMapper {
|
|
169
|
-
<<interface>>
|
|
170
|
-
+map(ctx) TReq
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
class ResponseMapper {
|
|
174
|
-
<<interface>>
|
|
175
|
-
+map(ctx, result) ResponseObject
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
class StreamMapper {
|
|
179
|
-
<<interface>>
|
|
180
|
-
+map(ctx, event) ResponseStreamEvent[]
|
|
181
|
-
+buildResponseObject(ctx, state) ResponseObject
|
|
182
|
-
}
|
|
183
|
-
|
|
184
186
|
class ResponseSessionStore {
|
|
185
187
|
<<interface>>
|
|
186
188
|
+get(id) StoredResponseSession
|
|
@@ -190,12 +192,6 @@ classDiagram
|
|
|
190
192
|
+close()
|
|
191
193
|
}
|
|
192
194
|
|
|
193
|
-
class Router {
|
|
194
|
-
-routes: Route[]
|
|
195
|
-
+register(route)
|
|
196
|
-
+dispatch(req) Promise~Response~
|
|
197
|
-
}
|
|
198
|
-
|
|
199
195
|
ApplicationContext --> ResponsesContext : creates
|
|
200
196
|
ApplicationContext --> ModelResolver
|
|
201
197
|
ApplicationContext --> Registrar
|
|
@@ -204,14 +200,65 @@ classDiagram
|
|
|
204
200
|
ResponsesContext --> Provider : uses
|
|
205
201
|
Provider --> ProviderMapper
|
|
206
202
|
Provider --> ChatClient
|
|
207
|
-
ProviderMapper --> RequestMapper
|
|
208
|
-
ProviderMapper --> ResponseMapper
|
|
209
|
-
ProviderMapper --> StreamMapper
|
|
210
203
|
Adapter <|.. DefaultAdapter
|
|
211
204
|
DefaultAdapter --> ProviderMapper : calls
|
|
212
205
|
DefaultAdapter --> ChatClient : calls
|
|
213
206
|
DefaultAdapter --> ResponseSessionStore : saves
|
|
214
|
-
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Request Flow
|
|
210
|
+
|
|
211
|
+
```mermaid
|
|
212
|
+
sequenceDiagram
|
|
213
|
+
actor C as Client (Codex CLI)
|
|
214
|
+
participant H as handleResponses
|
|
215
|
+
participant RC as ResponsesContext
|
|
216
|
+
participant MR as ModelResolver
|
|
217
|
+
participant SS as SessionStore
|
|
218
|
+
participant REG as Registrar
|
|
219
|
+
participant A as DefaultAdapter
|
|
220
|
+
participant PM as ProviderMapper
|
|
221
|
+
participant CC as ChatClient
|
|
222
|
+
participant UP as Upstream API
|
|
223
|
+
|
|
224
|
+
C->>H: POST /v1/responses
|
|
225
|
+
H->>RC: ResponsesContext.create(app, body)
|
|
226
|
+
activate RC
|
|
227
|
+
RC->>MR: resolve(model)
|
|
228
|
+
MR-->>RC: { provider, model }
|
|
229
|
+
opt previous_response_id
|
|
230
|
+
RC->>SS: resolveChain(id)
|
|
231
|
+
SS-->>RC: session snapshot
|
|
232
|
+
end
|
|
233
|
+
RC->>REG: resolve(provider)
|
|
234
|
+
REG-->>RC: Provider instance
|
|
235
|
+
deactivate RC
|
|
236
|
+
|
|
237
|
+
alt stream = true
|
|
238
|
+
H->>A: adapter.stream(ctx)
|
|
239
|
+
activate A
|
|
240
|
+
A->>PM: mapper.request.map(ctx)
|
|
241
|
+
A->>CC: chatClient.streamChat(req)
|
|
242
|
+
CC->>UP: POST (SSE)
|
|
243
|
+
UP-->>CC: SSE chunks
|
|
244
|
+
A->>A: pipeTransform → ProviderEventToResponse
|
|
245
|
+
A->>A: pipeTransform → ResponseLog
|
|
246
|
+
A->>A: pipeTransform → ResponseSessionPersistence
|
|
247
|
+
deactivate A
|
|
248
|
+
H->>H: pipeTransform → ResponseSseEncode
|
|
249
|
+
H-->>C: SSE byte stream
|
|
250
|
+
else stream = false
|
|
251
|
+
H->>A: adapter.request(ctx)
|
|
252
|
+
activate A
|
|
253
|
+
A->>PM: mapper.request.map(ctx)
|
|
254
|
+
A->>CC: chatClient.chat(req)
|
|
255
|
+
CC->>UP: POST
|
|
256
|
+
UP-->>A: upstream response
|
|
257
|
+
A->>PM: mapper.response.map(ctx, res)
|
|
258
|
+
A->>SS: save(session)
|
|
259
|
+
deactivate A
|
|
260
|
+
H-->>C: JSON response
|
|
261
|
+
end
|
|
215
262
|
```
|
|
216
263
|
|
|
217
264
|
## Stream Pipeline
|
|
@@ -219,120 +266,65 @@ classDiagram
|
|
|
219
266
|
```mermaid
|
|
220
267
|
flowchart LR
|
|
221
268
|
subgraph upstream["Upstream Provider"]
|
|
222
|
-
SSE["SSE Chunks
|
|
269
|
+
SSE["SSE Chunks"]
|
|
223
270
|
end
|
|
224
271
|
|
|
225
|
-
subgraph godex["
|
|
226
|
-
T1["
|
|
227
|
-
T2["
|
|
228
|
-
T3["
|
|
272
|
+
subgraph godex["GodeX Stream Pipeline"]
|
|
273
|
+
T1["① ProviderEventToResponse"]
|
|
274
|
+
T2["② ResponseLog"]
|
|
275
|
+
T3["③ SessionPersistence"]
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
subgraph server["HTTP Response"]
|
|
279
|
+
T4["④ SseEncode"]
|
|
229
280
|
end
|
|
230
281
|
|
|
231
282
|
subgraph client["Client"]
|
|
232
|
-
BYTES["SSE Bytes
|
|
283
|
+
BYTES["SSE Bytes"]
|
|
233
284
|
end
|
|
234
285
|
|
|
235
|
-
SSE -->|
|
|
236
|
-
T1 -->|
|
|
237
|
-
T2 -->|
|
|
238
|
-
T3 -->|
|
|
286
|
+
SSE -->|pipeThrough| T1
|
|
287
|
+
T1 -->|map per event| T2
|
|
288
|
+
T2 -->|log + pass through| T3
|
|
289
|
+
T3 -->|accumulate + save session| T4
|
|
290
|
+
T4 -->|serialize SSE wire format| BYTES
|
|
239
291
|
|
|
240
292
|
style upstream fill:#1a1a2e,stroke:#16213e,color:#e0e0e0
|
|
241
293
|
style godex fill:#0f3460,stroke:#16213e,color:#e0e0e0
|
|
294
|
+
style server fill:#1c2333,stroke:#16213e,color:#e0e0e0
|
|
242
295
|
style client fill:#1a1a2e,stroke:#16213e,color:#e0e0e0
|
|
243
296
|
```
|
|
244
297
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
|
248
|
-
|
|
249
|
-
|
|
|
250
|
-
|
|
|
251
|
-
| 3 | `ResponseSseEncodeTransformer` | `ResponseStreamEvent` | `Uint8Array` (SSE wire format) | Serializes to `event:` / `data:` lines |
|
|
252
|
-
|
|
253
|
-
## Error Hierarchy
|
|
254
|
-
|
|
255
|
-
```mermaid
|
|
256
|
-
classDiagram
|
|
257
|
-
direction TB
|
|
258
|
-
|
|
259
|
-
class GodexError {
|
|
260
|
-
+name: string
|
|
261
|
-
+code: string
|
|
262
|
-
+status: number
|
|
263
|
-
+context: object
|
|
264
|
-
+toLogEntry() object
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
class ServerError {
|
|
268
|
-
+status: 400-499
|
|
269
|
-
+context: object
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
class AdapterError {
|
|
273
|
-
+status: 400-499
|
|
274
|
-
+context: unsupported parameters / tools / input items
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
class ProviderError {
|
|
278
|
-
+status: 502
|
|
279
|
-
+context: upstream status / body / headers
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
class SessionError {
|
|
283
|
-
+status: 400-409
|
|
284
|
-
+context: chain metadata
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
GodexError <|-- ServerError
|
|
288
|
-
GodexError <|-- AdapterError
|
|
289
|
-
GodexError <|-- ProviderError
|
|
290
|
-
GodexError <|-- SessionError
|
|
291
|
-
|
|
292
|
-
note for GodexError "Base error with structured logging support.<br/>All errors carry domain codes (e.g. server.request.invalid_json)."
|
|
293
|
-
note for ProviderError "Wraps upstream HTTP failures:<br/>rate limits, timeouts, 5xx."
|
|
294
|
-
note for SessionError "Chain resolution failures:<br/>not found, cycles, depth exceeded."
|
|
295
|
-
```
|
|
298
|
+
| Stage | Transformer | Input | Output | Role |
|
|
299
|
+
|-------|------------|-------|--------|------|
|
|
300
|
+
| ① | `ProviderEventToResponseTransformer` | `JsonServerSentEvent` | `ResponseStreamEvent` | Maps upstream SSE chunks via `StreamMapper.map()` |
|
|
301
|
+
| ② | `ResponseLogTransformer` | `ResponseStreamEvent` | `ResponseStreamEvent` | Logs stream events for observability |
|
|
302
|
+
| ③ | `ResponseSessionPersistenceTransformer` | `ResponseStreamEvent` | `ResponseStreamEvent` | Accumulates `StreamState`, saves session on terminal event |
|
|
303
|
+
| ④ | `ResponseSseEncodeTransformer` | `ResponseStreamEvent` | `Uint8Array` | Serializes to `event:` / `data:` wire format |
|
|
296
304
|
|
|
297
305
|
## Project Structure
|
|
298
306
|
|
|
299
307
|
```
|
|
300
308
|
src/
|
|
301
|
-
├── cli/ Commander CLI (serve, config
|
|
309
|
+
├── cli/ Commander CLI (serve, config, init)
|
|
302
310
|
├── config/ godex.yaml schema, env interpolation, defaults
|
|
303
|
-
├── context/ ApplicationContext (DI
|
|
311
|
+
├── context/ ApplicationContext (DI), ResponsesContext (per-request)
|
|
304
312
|
├── adapter/ Adapter interface, DefaultAdapter, stream transformers
|
|
305
313
|
│ ├── mapper/ RequestMapper / ResponseMapper / StreamMapper contracts
|
|
306
|
-
│ └── transformers/
|
|
314
|
+
│ └── transformers/ 4-stage stream pipeline (map → log → persist → encode)
|
|
307
315
|
├── providers/ Provider registry + builtin factories
|
|
308
316
|
│ └── zhipu/ Reference provider: mapper, chat-client, tools, messages
|
|
309
317
|
├── resolver/ ModelResolver (model selector → provider + model)
|
|
310
|
-
├── server/ Bun
|
|
318
|
+
├── server/ Bun.serve, routes (/v1/responses, /health, /v1/models)
|
|
311
319
|
├── session/ ResponseSessionStore (Memory + SQLite), chain resolution
|
|
312
|
-
├── error/
|
|
320
|
+
├── error/ GodeXError hierarchy with domain codes
|
|
321
|
+
├── tools/ Built-in function tools (local_shell, shell, apply_patch)
|
|
313
322
|
├── protocol/openai/ OpenAI-compatible type definitions
|
|
314
323
|
├── logger/ Structured JSON logger
|
|
315
324
|
└── e2e/ End-to-end tests with mocked upstream
|
|
316
325
|
```
|
|
317
326
|
|
|
318
|
-
##
|
|
319
|
-
|
|
320
|
-
```bash
|
|
321
|
-
# Install dependencies
|
|
322
|
-
bun install
|
|
323
|
-
|
|
324
|
-
# Build standalone binary (current platform)
|
|
325
|
-
bun run build
|
|
326
|
-
|
|
327
|
-
# Create config interactively
|
|
328
|
-
bun run start -- init
|
|
329
|
-
|
|
330
|
-
# Start server (default port 5678)
|
|
331
|
-
bun run dev
|
|
332
|
-
|
|
333
|
-
# Or run the compiled binary directly
|
|
334
|
-
./platforms/darwin-arm64/bin/godex serve
|
|
335
|
-
```
|
|
327
|
+
## Configuration
|
|
336
328
|
|
|
337
329
|
### godex.yaml
|
|
338
330
|
|
|
@@ -359,115 +351,65 @@ logging:
|
|
|
359
351
|
level: info # trace | debug | info | warn | error
|
|
360
352
|
```
|
|
361
353
|
|
|
362
|
-
###
|
|
354
|
+
### Model Selection
|
|
363
355
|
|
|
364
|
-
Implement these interfaces in `src/providers/<name>/`:
|
|
365
|
-
|
|
366
|
-
| Interface | Purpose |
|
|
367
|
-
|-----------|---------|
|
|
368
|
-
| `Provider<TReq, TRes, TChunk>` | Bundles mapper + chatClient + capabilities |
|
|
369
|
-
| `ProviderMapper<TReq, TRes, TChunk>` | request / response / stream mapping functions |
|
|
370
|
-
| `ChatClient<TReq, TRes, TChunk>` | `chat()` and `streamChat()` HTTP calls |
|
|
371
|
-
|
|
372
|
-
Register the factory in `src/providers/builtin.ts`:
|
|
373
|
-
|
|
374
|
-
```ts
|
|
375
|
-
registrar.registerFactory("myprovider", (config) =>
|
|
376
|
-
createMyProvider(config) as Provider<unknown, unknown, unknown>
|
|
377
|
-
);
|
|
378
356
|
```
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
```bash
|
|
383
|
-
# Install — no Bun required at runtime
|
|
384
|
-
npm install -g @ahoo-wang/godex
|
|
385
|
-
|
|
386
|
-
# Create config interactively
|
|
387
|
-
godex init
|
|
388
|
-
|
|
389
|
-
# Start the gateway
|
|
390
|
-
godex serve
|
|
357
|
+
model: "gpt-4o" → resolved via default_provider model mapping
|
|
358
|
+
model: "zhipu/glm-4.7" → explicit provider/model selector
|
|
359
|
+
model: "openai/gpt-4o" → routes to configured openai provider
|
|
391
360
|
```
|
|
392
361
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
Godex exposes an **OpenAI-compatible Responses API** at `http://localhost:5678` (port is configurable). Point any tool that speaks the OpenAI protocol at this endpoint:
|
|
396
|
-
|
|
397
|
-
### With Codex CLI
|
|
362
|
+
### Health Check
|
|
398
363
|
|
|
399
364
|
```bash
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
codex
|
|
365
|
+
curl http://localhost:5678/health
|
|
366
|
+
# {"status":"ok","providers":["zhipu"],"unsupported_providers":[]}
|
|
403
367
|
```
|
|
404
368
|
|
|
405
|
-
###
|
|
406
|
-
|
|
407
|
-
```ts
|
|
408
|
-
import OpenAI from "openai";
|
|
369
|
+
### Adding a Provider
|
|
409
370
|
|
|
410
|
-
|
|
411
|
-
baseURL: "http://localhost:5678/v1",
|
|
412
|
-
apiKey: "any-value", // passed through, not validated
|
|
413
|
-
});
|
|
371
|
+
Implement three interfaces in `src/providers/<name>/`:
|
|
414
372
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
373
|
+
| Interface | Purpose |
|
|
374
|
+
|-----------|---------|
|
|
375
|
+
| `Provider` | Bundles mapper + chatClient + capabilities |
|
|
376
|
+
| `ProviderMapper` | request / response / stream mapping functions |
|
|
377
|
+
| `ChatClient` | `chat()` and `streamChat()` HTTP calls |
|
|
420
378
|
|
|
421
|
-
|
|
379
|
+
Register the factory in `src/providers/builtin.ts`:
|
|
422
380
|
|
|
381
|
+
```ts
|
|
382
|
+
registrar.registerFactory("myprovider", (config) =>
|
|
383
|
+
createMyProvider(config) as Provider<unknown, unknown, unknown>
|
|
384
|
+
);
|
|
423
385
|
```
|
|
424
|
-
model: "gpt-4o" → resolved via default_provider model mapping
|
|
425
|
-
model: "zhipu/glm-4.7" → explicit provider/model selector
|
|
426
|
-
model: "openai/gpt-4o" → routes to configured openai provider
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
The `models` map in `godex.yaml` lets you translate standard model names into provider-native ones — no code changes needed in the client.
|
|
430
386
|
|
|
431
|
-
|
|
387
|
+
## Development
|
|
432
388
|
|
|
433
389
|
```bash
|
|
434
|
-
|
|
435
|
-
#
|
|
390
|
+
bun install # Install dependencies
|
|
391
|
+
bun run dev # Dev server with hot reload (port 13145)
|
|
392
|
+
bun run test # Unit + integration tests
|
|
393
|
+
bun run test:e2e # E2E tests with mocked upstream
|
|
394
|
+
bun run build # Build standalone binary for current platform
|
|
395
|
+
bun run check # typecheck + lint + test
|
|
396
|
+
bun run ci # Full CI pipeline
|
|
436
397
|
```
|
|
437
398
|
|
|
438
399
|
## Publishing
|
|
439
400
|
|
|
440
|
-
|
|
401
|
+
`@ahoo-wang/godex` is a lightweight npm wrapper. Native binaries ship as platform-specific optional dependencies:
|
|
441
402
|
|
|
442
403
|
```
|
|
443
|
-
@ahoo-wang/godex
|
|
444
|
-
├──
|
|
445
|
-
├──
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
├── @ahoo-wang/godex-linux-arm64 ← Linux ARM64
|
|
451
|
-
├── @ahoo-wang/godex-win32-x64 ← Windows x86_64
|
|
452
|
-
└── @ahoo-wang/godex-win32-arm64 ← Windows ARM64
|
|
453
|
-
|
|
454
|
-
# Publishing flow:
|
|
455
|
-
# 1. Make the GitHub repository public, configure NPM_TOKEN, then push the release commit.
|
|
456
|
-
# 2. Create a GitHub Release tagged vX.Y.Z.
|
|
457
|
-
# 3. The Release workflow builds all platform binaries.
|
|
458
|
-
# 4. The Release workflow uploads binary archives and SHA256SUMS to Release Assets.
|
|
459
|
-
# 5. The Release workflow publishes platform packages first, then @ahoo-wang/godex.
|
|
404
|
+
@ahoo-wang/godex
|
|
405
|
+
├── @ahoo-wang/godex-darwin-arm64 ← macOS Apple Silicon
|
|
406
|
+
├── @ahoo-wang/godex-darwin-x64 ← macOS Intel
|
|
407
|
+
├── @ahoo-wang/godex-linux-x64 ← Linux x86_64
|
|
408
|
+
├── @ahoo-wang/godex-linux-arm64 ← Linux ARM64
|
|
409
|
+
├── @ahoo-wang/godex-win32-x64 ← Windows x86_64
|
|
410
|
+
└── @ahoo-wang/godex-win32-arm64 ← Windows ARM64
|
|
460
411
|
```
|
|
461
412
|
|
|
462
|
-
##
|
|
413
|
+
## License
|
|
463
414
|
|
|
464
|
-
|
|
465
|
-
bun run dev # Hot-reload dev server on port 13145
|
|
466
|
-
bun run build # Compile native binary for current platform
|
|
467
|
-
bun run compile:all # Cross-compile all 6 platforms locally
|
|
468
|
-
bun run test # Unit + integration tests
|
|
469
|
-
bun run test:e2e # E2E with mocked upstream
|
|
470
|
-
bun run typecheck # tsc --noEmit
|
|
471
|
-
bun run lint # Biome check
|
|
472
|
-
bun run ci # Full CI pipeline
|
|
473
|
-
```
|
|
415
|
+
[Apache License 2.0](LICENSE)
|
package/README.zh-CN.md
CHANGED
|
@@ -1,19 +1,105 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="design/assets/01-logo-system/png/godex-logo-horizontal-transparent-800x233.png" alt="GodeX" width="480" />
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**让每个模型都成为 Codex 引擎。**
|
|
6
|
+
|
|
7
|
+
OpenAI 兼容的 Responses API 网关 — 将 `/v1/responses` 请求转换为上游 Chat Completions API 调用,连接 Codex、CLI、IDE 和自动化开发工具与不同模型供应商。
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@ahoo-wang/godex)
|
|
10
|
+
[](https://codecov.io/gh/Ahoo-Wang/GodeX)
|
|
6
11
|
[](https://bun.sh)
|
|
7
12
|
[](https://www.typescriptlang.org/)
|
|
8
13
|
|
|
14
|
+
[快速入门](https://godex.ahoo.me/zh/01-getting-started/overview) · [架构](https://godex.ahoo.me/zh/02-architecture/overview) · [配置](https://godex.ahoo.me/zh/07-configuration/config-schema) · [文档](https://godex.ahoo.me/zh/)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## 功能特性
|
|
20
|
+
|
|
21
|
+
| | 特性 | 说明 |
|
|
22
|
+
|---|------|------|
|
|
23
|
+
| 🔄 | **协议转换** | 弥补 OpenAI Responses API 与提供商 Chat Completions API 之间的差距 |
|
|
24
|
+
| 🔌 | **提供商无关** | 基于插件的适配器系统 — 添加提供商只需实现少量接口 |
|
|
25
|
+
| ⚡ | **流式优先** | 4 阶段 `TransformStream` 管道,低延迟 SSE 传输 |
|
|
26
|
+
| 💾 | **会话历史** | 内置 `previous_response_id` 链式解析(SQLite / 内存) |
|
|
27
|
+
| 🛡️ | **结构化错误** | 域特定错误层次结构,带结构化编码和诊断上下文 |
|
|
28
|
+
| 🔧 | **内置工具** | `local_shell`、`shell`、`apply_patch` — Codex 兼容函数工具 |
|
|
29
|
+
| 📦 | **独立二进制** | 零运行时依赖,通过 GitHub Actions 构建 6 个平台 |
|
|
30
|
+
|
|
31
|
+
## 快速开始
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# 安装 — 运行时无需 Bun
|
|
35
|
+
npm install -g @ahoo-wang/godex
|
|
36
|
+
|
|
37
|
+
# 交互式创建配置
|
|
38
|
+
godex init
|
|
39
|
+
|
|
40
|
+
# 启动网关
|
|
41
|
+
godex serve
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
将 Codex CLI 指向你的 GodeX 实例:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
export OPENAI_BASE_URL=http://localhost:5678/v1
|
|
48
|
+
export OPENAI_API_KEY=any-value # GodeX 不验证此值,但必须设置
|
|
49
|
+
codex
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
或使用 OpenAI SDK:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import OpenAI from "openai";
|
|
56
|
+
|
|
57
|
+
const client = new OpenAI({
|
|
58
|
+
baseURL: "http://localhost:5678/v1",
|
|
59
|
+
apiKey: "any-value",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const response = await client.responses.create({
|
|
63
|
+
model: "gpt-4o", // 通过 godex.yaml 的 models 表映射为 glm-4.7
|
|
64
|
+
input: "Hello!",
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 工作原理
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
Codex / CLI / IDE
|
|
72
|
+
│
|
|
73
|
+
▼ POST /v1/responses
|
|
74
|
+
┌─────────────────────────────────────────┐
|
|
75
|
+
│ GodeX 网关 │
|
|
76
|
+
│ │
|
|
77
|
+
│ Bun.serve → handleResponses() │
|
|
78
|
+
│ → ResponsesContext.create() │
|
|
79
|
+
│ → ModelResolver.resolve() │
|
|
80
|
+
│ → Registrar.resolve() │
|
|
81
|
+
│ → DefaultAdapter.stream/request() │
|
|
82
|
+
│ → ProviderMapper.map() │
|
|
83
|
+
│ → ChatClient.streamChat() │
|
|
84
|
+
│ → 4 阶段 TransformStream │
|
|
85
|
+
│ → Response (JSON 或 SSE) │
|
|
86
|
+
└──────────────┬──────────────────────────┘
|
|
87
|
+
│ 提供商适配器
|
|
88
|
+
▼
|
|
89
|
+
┌─────────────────────────────────────────┐
|
|
90
|
+
│ Chat Completions 兼容 API │
|
|
91
|
+
│ (智谱、OpenAI 或自定义) │
|
|
92
|
+
└─────────────────────────────────────────┘
|
|
93
|
+
```
|
|
94
|
+
|
|
9
95
|
## 架构
|
|
10
96
|
|
|
11
97
|
```mermaid
|
|
12
98
|
C4Context
|
|
13
|
-
title
|
|
99
|
+
title GodeX — 系统上下文
|
|
14
100
|
|
|
15
101
|
Person(user, "开发者 / Codex CLI", "通过 OpenAI 兼容端点<br/>发送 Responses API 请求")
|
|
16
|
-
System(godex_svr, "
|
|
102
|
+
System(godex_svr, "GodeX 服务器", "转换 Responses API → Chat Completions API<br/>基于 Bun.serve,端口可配置")
|
|
17
103
|
SystemDb(sessions, "会话存储", "存储响应历史,用于<br/>previous_response_id 链式解析<br/>SQLite(持久化)或内存")
|
|
18
104
|
System_Ext(zhipu, "智谱 (Zhipu)", "Chat Completions API 提供商")
|
|
19
105
|
System_Ext(openai, "OpenAI", "Chat Completions API 提供商")
|
|
@@ -26,72 +112,6 @@ C4Context
|
|
|
26
112
|
Rel(godex_svr, other, "POST /chat/completions", "HTTPS")
|
|
27
113
|
```
|
|
28
114
|
|
|
29
|
-
## 请求流程
|
|
30
|
-
|
|
31
|
-
```mermaid
|
|
32
|
-
sequenceDiagram
|
|
33
|
-
actor C as 客户端 (Codex CLI)
|
|
34
|
-
participant R as Router 路由
|
|
35
|
-
participant AC as ApplicationContext 应用上下文
|
|
36
|
-
participant RC as ResponsesContext 响应上下文
|
|
37
|
-
participant MR as ModelResolver 模型解析器
|
|
38
|
-
participant SS as SessionStore 会话存储
|
|
39
|
-
participant REG as Registrar 注册器
|
|
40
|
-
participant A as Adapter 适配器 (DefaultAdapter)
|
|
41
|
-
participant PM as ProviderMapper 提供商映射器
|
|
42
|
-
participant CC as ChatClient 聊天客户端
|
|
43
|
-
participant UP as 上游 API
|
|
44
|
-
|
|
45
|
-
C->>R: POST /v1/responses
|
|
46
|
-
R->>RC: ResponsesContext.create(app, body)
|
|
47
|
-
|
|
48
|
-
activate RC
|
|
49
|
-
RC->>MR: resolve(model)
|
|
50
|
-
MR-->>RC: { provider, model }
|
|
51
|
-
RC->>RC: 验证提供商配置
|
|
52
|
-
|
|
53
|
-
opt previous_response_id
|
|
54
|
-
RC->>SS: resolveChain(id)
|
|
55
|
-
SS-->>RC: 会话快照
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
RC->>REG: resolve(provider)
|
|
59
|
-
REG-->>RC: Provider 实例
|
|
60
|
-
deactivate RC
|
|
61
|
-
|
|
62
|
-
alt stream = true
|
|
63
|
-
R->>A: adapter.stream(ctx)
|
|
64
|
-
activate A
|
|
65
|
-
A->>PM: request.map(ctx)
|
|
66
|
-
PM-->>A: 上游请求
|
|
67
|
-
A->>CC: streamChat(req)
|
|
68
|
-
CC->>UP: POST (SSE)
|
|
69
|
-
UP-->>CC: SSE 数据块
|
|
70
|
-
CC-->>A: ReadableStream<SSE>
|
|
71
|
-
A->>A: pipeTransform → ProviderEventToResponseTransformer
|
|
72
|
-
A->>A: pipeTransform → ResponseSessionPersistenceTransformer
|
|
73
|
-
A-->>R: ReadableStream<ResponseStreamEvent>
|
|
74
|
-
deactivate A
|
|
75
|
-
R->>R: pipeTransform → ResponseSseEncodeTransformer
|
|
76
|
-
R-->>C: SSE 字节流
|
|
77
|
-
else stream = false
|
|
78
|
-
R->>A: adapter.request(ctx)
|
|
79
|
-
activate A
|
|
80
|
-
A->>PM: request.map(ctx)
|
|
81
|
-
PM-->>A: 上游请求
|
|
82
|
-
A->>CC: chat(req)
|
|
83
|
-
CC->>UP: POST
|
|
84
|
-
UP-->>CC: JSON 响应
|
|
85
|
-
CC-->>A: 上游响应
|
|
86
|
-
A->>PM: response.map(ctx, res)
|
|
87
|
-
PM-->>A: ResponseObject
|
|
88
|
-
A->>SS: save(session)
|
|
89
|
-
A-->>R: ResponseObject
|
|
90
|
-
deactivate A
|
|
91
|
-
R-->>C: JSON 响应
|
|
92
|
-
end
|
|
93
|
-
```
|
|
94
|
-
|
|
95
115
|
## 组件模型
|
|
96
116
|
|
|
97
117
|
```mermaid
|
|
@@ -99,7 +119,7 @@ classDiagram
|
|
|
99
119
|
direction TB
|
|
100
120
|
|
|
101
121
|
class ApplicationContext {
|
|
102
|
-
+config:
|
|
122
|
+
+config: GodeXConfig
|
|
103
123
|
+logger: Logger
|
|
104
124
|
+resolver: ModelResolver
|
|
105
125
|
+registrar: Registrar
|
|
@@ -115,7 +135,7 @@ classDiagram
|
|
|
115
135
|
+provider: Provider
|
|
116
136
|
+responseId: string
|
|
117
137
|
+requestId: string
|
|
118
|
-
+
|
|
138
|
+
+attributes: Map
|
|
119
139
|
+create(app, body)$ Promise~ResponsesContext~
|
|
120
140
|
}
|
|
121
141
|
|
|
@@ -130,7 +150,6 @@ classDiagram
|
|
|
130
150
|
+registerFactory(name, factory)
|
|
131
151
|
+build(providers)
|
|
132
152
|
+resolve(name) Provider
|
|
133
|
-
+list() string[]
|
|
134
153
|
}
|
|
135
154
|
|
|
136
155
|
class Adapter {
|
|
@@ -165,22 +184,6 @@ classDiagram
|
|
|
165
184
|
+streamChat(req) Promise~ReadableStream~
|
|
166
185
|
}
|
|
167
186
|
|
|
168
|
-
class RequestMapper {
|
|
169
|
-
<<interface>>
|
|
170
|
-
+map(ctx) TReq
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
class ResponseMapper {
|
|
174
|
-
<<interface>>
|
|
175
|
-
+map(ctx, result) ResponseObject
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
class StreamMapper {
|
|
179
|
-
<<interface>>
|
|
180
|
-
+map(ctx, event) ResponseStreamEvent[]
|
|
181
|
-
+buildResponseObject(ctx, state) ResponseObject
|
|
182
|
-
}
|
|
183
|
-
|
|
184
187
|
class ResponseSessionStore {
|
|
185
188
|
<<interface>>
|
|
186
189
|
+get(id) StoredResponseSession
|
|
@@ -190,28 +193,73 @@ classDiagram
|
|
|
190
193
|
+close()
|
|
191
194
|
}
|
|
192
195
|
|
|
193
|
-
|
|
194
|
-
-routes: Route[]
|
|
195
|
-
+register(route)
|
|
196
|
-
+dispatch(req) Promise~Response~
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
ApplicationContext --> ResponsesContext : 创建
|
|
196
|
+
ApplicationContext --> ResponsesContext : creates
|
|
200
197
|
ApplicationContext --> ModelResolver
|
|
201
198
|
ApplicationContext --> Registrar
|
|
202
199
|
ApplicationContext --> Adapter
|
|
203
200
|
ApplicationContext --> ResponseSessionStore
|
|
204
|
-
ResponsesContext --> Provider :
|
|
201
|
+
ResponsesContext --> Provider : uses
|
|
205
202
|
Provider --> ProviderMapper
|
|
206
203
|
Provider --> ChatClient
|
|
207
|
-
ProviderMapper --> RequestMapper
|
|
208
|
-
ProviderMapper --> ResponseMapper
|
|
209
|
-
ProviderMapper --> StreamMapper
|
|
210
204
|
Adapter <|.. DefaultAdapter
|
|
211
|
-
DefaultAdapter --> ProviderMapper :
|
|
212
|
-
DefaultAdapter --> ChatClient :
|
|
213
|
-
DefaultAdapter --> ResponseSessionStore :
|
|
214
|
-
|
|
205
|
+
DefaultAdapter --> ProviderMapper : calls
|
|
206
|
+
DefaultAdapter --> ChatClient : calls
|
|
207
|
+
DefaultAdapter --> ResponseSessionStore : saves
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## 请求流程
|
|
211
|
+
|
|
212
|
+
```mermaid
|
|
213
|
+
sequenceDiagram
|
|
214
|
+
actor C as 客户端 (Codex CLI)
|
|
215
|
+
participant H as handleResponses
|
|
216
|
+
participant RC as ResponsesContext
|
|
217
|
+
participant MR as ModelResolver
|
|
218
|
+
participant SS as SessionStore
|
|
219
|
+
participant REG as Registrar
|
|
220
|
+
participant A as DefaultAdapter
|
|
221
|
+
participant PM as ProviderMapper
|
|
222
|
+
participant CC as ChatClient
|
|
223
|
+
participant UP as 上游 API
|
|
224
|
+
|
|
225
|
+
C->>H: POST /v1/responses
|
|
226
|
+
H->>RC: ResponsesContext.create(app, body)
|
|
227
|
+
activate RC
|
|
228
|
+
RC->>MR: resolve(model)
|
|
229
|
+
MR-->>RC: { provider, model }
|
|
230
|
+
opt previous_response_id
|
|
231
|
+
RC->>SS: resolveChain(id)
|
|
232
|
+
SS-->>RC: session snapshot
|
|
233
|
+
end
|
|
234
|
+
RC->>REG: resolve(provider)
|
|
235
|
+
REG-->>RC: Provider 实例
|
|
236
|
+
deactivate RC
|
|
237
|
+
|
|
238
|
+
alt stream = true
|
|
239
|
+
H->>A: adapter.stream(ctx)
|
|
240
|
+
activate A
|
|
241
|
+
A->>PM: mapper.request.map(ctx)
|
|
242
|
+
A->>CC: chatClient.streamChat(req)
|
|
243
|
+
CC->>UP: POST (SSE)
|
|
244
|
+
UP-->>CC: SSE 数据块
|
|
245
|
+
A->>A: pipeTransform → ProviderEventToResponse
|
|
246
|
+
A->>A: pipeTransform → ResponseLog
|
|
247
|
+
A->>A: pipeTransform → ResponseSessionPersistence
|
|
248
|
+
deactivate A
|
|
249
|
+
H->>H: pipeTransform → ResponseSseEncode
|
|
250
|
+
H-->>C: SSE 字节流
|
|
251
|
+
else stream = false
|
|
252
|
+
H->>A: adapter.request(ctx)
|
|
253
|
+
activate A
|
|
254
|
+
A->>PM: mapper.request.map(ctx)
|
|
255
|
+
A->>CC: chatClient.chat(req)
|
|
256
|
+
CC->>UP: POST
|
|
257
|
+
UP-->>A: 上游响应
|
|
258
|
+
A->>PM: mapper.response.map(ctx, res)
|
|
259
|
+
A->>SS: save(session)
|
|
260
|
+
deactivate A
|
|
261
|
+
H-->>C: JSON 响应
|
|
262
|
+
end
|
|
215
263
|
```
|
|
216
264
|
|
|
217
265
|
## 流式管道
|
|
@@ -219,80 +267,41 @@ classDiagram
|
|
|
219
267
|
```mermaid
|
|
220
268
|
flowchart LR
|
|
221
269
|
subgraph upstream["上游提供商"]
|
|
222
|
-
SSE["SSE
|
|
270
|
+
SSE["SSE 数据块"]
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
subgraph godex["GodeX 流式管道"]
|
|
274
|
+
T1["① ProviderEventToResponse"]
|
|
275
|
+
T2["② ResponseLog"]
|
|
276
|
+
T3["③ SessionPersistence"]
|
|
223
277
|
end
|
|
224
278
|
|
|
225
|
-
subgraph
|
|
226
|
-
|
|
227
|
-
T2["ResponseSession<br/>PersistenceTransformer"]
|
|
228
|
-
T3["ResponseSse<br/>EncodeTransformer"]
|
|
279
|
+
subgraph server["HTTP 响应"]
|
|
280
|
+
T4["④ SseEncode"]
|
|
229
281
|
end
|
|
230
282
|
|
|
231
283
|
subgraph client["客户端"]
|
|
232
|
-
BYTES["SSE
|
|
284
|
+
BYTES["SSE 字节流"]
|
|
233
285
|
end
|
|
234
286
|
|
|
235
|
-
SSE -->|
|
|
236
|
-
T1
|
|
237
|
-
T2
|
|
238
|
-
T3
|
|
287
|
+
SSE -->|pipeThrough| T1
|
|
288
|
+
T1 -->|逐事件映射| T2
|
|
289
|
+
T2 -->|日志透传| T3
|
|
290
|
+
T3 -->|累积状态并保存会话| T4
|
|
291
|
+
T4 -->|序列化 SSE 格式| BYTES
|
|
239
292
|
|
|
240
293
|
style upstream fill:#1a1a2e,stroke:#16213e,color:#e0e0e0
|
|
241
294
|
style godex fill:#0f3460,stroke:#16213e,color:#e0e0e0
|
|
295
|
+
style server fill:#1c2333,stroke:#16213e,color:#e0e0e0
|
|
242
296
|
style client fill:#1a1a2e,stroke:#16213e,color:#e0e0e0
|
|
243
297
|
```
|
|
244
298
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
|
248
|
-
|
|
249
|
-
|
|
|
250
|
-
|
|
|
251
|
-
| 3 | `ResponseSseEncodeTransformer` | `ResponseStreamEvent` | `Uint8Array`(SSE 传输格式) | 序列化为 `event:` / `data:` 行 |
|
|
252
|
-
|
|
253
|
-
## 错误体系
|
|
254
|
-
|
|
255
|
-
```mermaid
|
|
256
|
-
classDiagram
|
|
257
|
-
direction TB
|
|
258
|
-
|
|
259
|
-
class GodexError {
|
|
260
|
-
+name: string
|
|
261
|
-
+code: string
|
|
262
|
-
+status: number
|
|
263
|
-
+context: object
|
|
264
|
-
+toLogEntry() object
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
class ServerError {
|
|
268
|
-
+status: 400-499
|
|
269
|
-
+context: object
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
class AdapterError {
|
|
273
|
-
+status: 400-499
|
|
274
|
-
+context: 不支持的参数 / 工具 / 输入项
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
class ProviderError {
|
|
278
|
-
+status: 502
|
|
279
|
-
+context: 上游状态码 / 响应体 / 响应头
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
class SessionError {
|
|
283
|
-
+status: 400-409
|
|
284
|
-
+context: 链元数据
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
GodexError <|-- ServerError
|
|
288
|
-
GodexError <|-- AdapterError
|
|
289
|
-
GodexError <|-- ProviderError
|
|
290
|
-
GodexError <|-- SessionError
|
|
291
|
-
|
|
292
|
-
note for GodexError "基础错误,支持结构化日志。<br/>所有错误携带领域编码(如 server.request.invalid_json)。"
|
|
293
|
-
note for ProviderError "包装上游 HTTP 失败:<br/>速率限制、超时、5xx。"
|
|
294
|
-
note for SessionError "链式解析失败:<br/>未找到、循环、深度超限。"
|
|
295
|
-
```
|
|
299
|
+
| 阶段 | Transformer | 输入 | 输出 | 职责 |
|
|
300
|
+
|------|------------|------|------|------|
|
|
301
|
+
| ① | `ProviderEventToResponseTransformer` | `JsonServerSentEvent` | `ResponseStreamEvent` | 通过 `StreamMapper.map()` 映射上游 SSE 数据块 |
|
|
302
|
+
| ② | `ResponseLogTransformer` | `ResponseStreamEvent` | `ResponseStreamEvent` | 可观测性日志 |
|
|
303
|
+
| ③ | `ResponseSessionPersistenceTransformer` | `ResponseStreamEvent` | `ResponseStreamEvent` | 累积 `StreamState`,终止事件时保存会话 |
|
|
304
|
+
| ④ | `ResponseSseEncodeTransformer` | `ResponseStreamEvent` | `Uint8Array` | 序列化为 `event:` / `data:` 传输格式 |
|
|
296
305
|
|
|
297
306
|
## 项目结构
|
|
298
307
|
|
|
@@ -303,36 +312,20 @@ src/
|
|
|
303
312
|
├── context/ ApplicationContext(DI 容器)、ResponsesContext(每请求)
|
|
304
313
|
├── adapter/ Adapter 接口、DefaultAdapter、流式 Transformer
|
|
305
314
|
│ ├── mapper/ RequestMapper / ResponseMapper / StreamMapper 契约
|
|
306
|
-
│ └── transformers/
|
|
315
|
+
│ └── transformers/ 4 阶段流式管道(映射 → 日志 → 持久化 → 编码)
|
|
307
316
|
├── providers/ Provider 注册表 + 内置工厂
|
|
308
|
-
│ └── zhipu/
|
|
317
|
+
│ └── zhipu/ 参考提供商:映射器、聊天客户端、工具、消息
|
|
309
318
|
├── resolver/ ModelResolver(模型选择器 → 提供商 + 模型)
|
|
310
|
-
├── server/ Bun
|
|
319
|
+
├── server/ Bun.serve、路由(/v1/responses、/health、/v1/models)
|
|
311
320
|
├── session/ ResponseSessionStore(内存 + SQLite)、链式解析
|
|
312
|
-
├── error/
|
|
321
|
+
├── error/ GodeXError 错误体系及领域编码
|
|
322
|
+
├── tools/ 内置函数工具(local_shell、shell、apply_patch)
|
|
313
323
|
├── protocol/openai/ OpenAI 兼容类型定义
|
|
314
324
|
├── logger/ 结构化 JSON 日志
|
|
315
325
|
└── e2e/ 模拟上游的端到端测试
|
|
316
326
|
```
|
|
317
327
|
|
|
318
|
-
##
|
|
319
|
-
|
|
320
|
-
```bash
|
|
321
|
-
# 安装依赖
|
|
322
|
-
bun install
|
|
323
|
-
|
|
324
|
-
# 构建独立二进制文件(当前平台)
|
|
325
|
-
bun run build
|
|
326
|
-
|
|
327
|
-
# 交互式创建配置
|
|
328
|
-
bun run start -- init
|
|
329
|
-
|
|
330
|
-
# 启动服务器(默认端口 5678)
|
|
331
|
-
bun run dev
|
|
332
|
-
|
|
333
|
-
# 或直接运行编译后的二进制文件
|
|
334
|
-
./platforms/darwin-arm64/bin/godex serve
|
|
335
|
-
```
|
|
328
|
+
## 配置
|
|
336
329
|
|
|
337
330
|
### godex.yaml
|
|
338
331
|
|
|
@@ -359,115 +352,65 @@ logging:
|
|
|
359
352
|
level: info # trace | debug | info | warn | error
|
|
360
353
|
```
|
|
361
354
|
|
|
362
|
-
###
|
|
363
|
-
|
|
364
|
-
在 `src/providers/<name>/` 中实现以下接口:
|
|
365
|
-
|
|
366
|
-
| 接口 | 用途 |
|
|
367
|
-
|------|------|
|
|
368
|
-
| `Provider<TReq, TRes, TChunk>` | 组合 mapper + chatClient + capabilities |
|
|
369
|
-
| `ProviderMapper<TReq, TRes, TChunk>` | request / response / stream 映射函数 |
|
|
370
|
-
| `ChatClient<TReq, TRes, TChunk>` | `chat()` 和 `streamChat()` HTTP 调用 |
|
|
371
|
-
|
|
372
|
-
在 `src/providers/builtin.ts` 中注册工厂:
|
|
355
|
+
### 模型选择
|
|
373
356
|
|
|
374
|
-
```ts
|
|
375
|
-
registrar.registerFactory("myprovider", (config) =>
|
|
376
|
-
createMyProvider(config) as Provider<unknown, unknown, unknown>
|
|
377
|
-
);
|
|
378
357
|
```
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
```bash
|
|
383
|
-
# 安装 — 运行时无需 Bun
|
|
384
|
-
npm install -g @ahoo-wang/godex
|
|
385
|
-
|
|
386
|
-
# 交互式创建配置
|
|
387
|
-
godex init
|
|
388
|
-
|
|
389
|
-
# 启动网关
|
|
390
|
-
godex serve
|
|
358
|
+
model: "gpt-4o" → 通过 default_provider 的模型映射解析
|
|
359
|
+
model: "zhipu/glm-4.7" → 显式指定 provider/model 选择器
|
|
360
|
+
model: "openai/gpt-4o" → 路由到已配置的 openai 提供商
|
|
391
361
|
```
|
|
392
362
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
Godex 在 `http://localhost:5678` 暴露**与 OpenAI 兼容的 Responses API**(端口可配置)。将任何使用 OpenAI 协议的工具指向此端点即可:
|
|
396
|
-
|
|
397
|
-
### 搭配 Codex CLI
|
|
363
|
+
### 健康检查
|
|
398
364
|
|
|
399
365
|
```bash
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
codex
|
|
366
|
+
curl http://localhost:5678/health
|
|
367
|
+
# {"status":"ok","providers":["zhipu"],"unsupported_providers":[]}
|
|
403
368
|
```
|
|
404
369
|
|
|
405
|
-
###
|
|
370
|
+
### 添加提供商
|
|
406
371
|
|
|
407
|
-
|
|
408
|
-
import OpenAI from "openai";
|
|
372
|
+
在 `src/providers/<name>/` 中实现三个接口:
|
|
409
373
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const response = await client.responses.create({
|
|
416
|
-
model: "gpt-4o", // 通过 godex.yaml 的 models 表映射为 glm-4.7
|
|
417
|
-
input: "Hello!",
|
|
418
|
-
});
|
|
419
|
-
```
|
|
374
|
+
| 接口 | 用途 |
|
|
375
|
+
|------|------|
|
|
376
|
+
| `Provider` | 组合 mapper + chatClient + capabilities |
|
|
377
|
+
| `ProviderMapper` | request / response / stream 映射函数 |
|
|
378
|
+
| `ChatClient` | `chat()` 和 `streamChat()` HTTP 调用 |
|
|
420
379
|
|
|
421
|
-
|
|
380
|
+
在 `src/providers/builtin.ts` 中注册工厂:
|
|
422
381
|
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
382
|
+
```ts
|
|
383
|
+
registrar.registerFactory("myprovider", (config) =>
|
|
384
|
+
createMyProvider(config) as Provider<unknown, unknown, unknown>
|
|
385
|
+
);
|
|
427
386
|
```
|
|
428
387
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
### 健康检查
|
|
388
|
+
## 开发
|
|
432
389
|
|
|
433
390
|
```bash
|
|
434
|
-
|
|
435
|
-
#
|
|
391
|
+
bun install # 安装依赖
|
|
392
|
+
bun run dev # 热重载开发服务器(端口 13145)
|
|
393
|
+
bun run test # 单元 + 集成测试
|
|
394
|
+
bun run test:e2e # 模拟上游的端到端测试
|
|
395
|
+
bun run build # 为当前平台编译原生二进制
|
|
396
|
+
bun run check # typecheck + lint + test
|
|
397
|
+
bun run ci # 完整 CI 流水线
|
|
436
398
|
```
|
|
437
399
|
|
|
438
400
|
## 发布
|
|
439
401
|
|
|
440
|
-
|
|
402
|
+
`@ahoo-wang/godex` 是一个轻量 npm 外壳。原生二进制文件以平台特定的可选依赖发布:
|
|
441
403
|
|
|
442
404
|
```
|
|
443
|
-
@ahoo-wang/godex
|
|
444
|
-
├──
|
|
445
|
-
├──
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
├── @ahoo-wang/godex-linux-arm64 ← Linux ARM64
|
|
451
|
-
├── @ahoo-wang/godex-win32-x64 ← Windows x86_64
|
|
452
|
-
└── @ahoo-wang/godex-win32-arm64 ← Windows ARM64
|
|
453
|
-
|
|
454
|
-
# 发布流程:
|
|
455
|
-
# 1. 将 GitHub 仓库设为公开,配置 NPM_TOKEN,然后推送发布提交。
|
|
456
|
-
# 2. 创建标签为 vX.Y.Z 的 GitHub Release。
|
|
457
|
-
# 3. Release 工作流构建所有平台二进制文件。
|
|
458
|
-
# 4. Release 工作流上传二进制压缩包和 SHA256SUMS 到 Release Assets。
|
|
459
|
-
# 5. Release 工作流先发布平台包,再发布 @ahoo-wang/godex。
|
|
405
|
+
@ahoo-wang/godex
|
|
406
|
+
├── @ahoo-wang/godex-darwin-arm64 ← macOS Apple Silicon
|
|
407
|
+
├── @ahoo-wang/godex-darwin-x64 ← macOS Intel
|
|
408
|
+
├── @ahoo-wang/godex-linux-x64 ← Linux x86_64
|
|
409
|
+
├── @ahoo-wang/godex-linux-arm64 ← Linux ARM64
|
|
410
|
+
├── @ahoo-wang/godex-win32-x64 ← Windows x86_64
|
|
411
|
+
└── @ahoo-wang/godex-win32-arm64 ← Windows ARM64
|
|
460
412
|
```
|
|
461
413
|
|
|
462
|
-
##
|
|
414
|
+
## 许可证
|
|
463
415
|
|
|
464
|
-
|
|
465
|
-
bun run dev # 热重载开发服务器,端口 13145
|
|
466
|
-
bun run build # 为当前平台编译原生二进制
|
|
467
|
-
bun run compile:all # 本地交叉编译全部 6 个平台
|
|
468
|
-
bun run test # 单元 + 集成测试
|
|
469
|
-
bun run test:e2e # 模拟上游的端到端测试
|
|
470
|
-
bun run typecheck # tsc --noEmit
|
|
471
|
-
bun run lint # Biome 检查
|
|
472
|
-
bun run ci # 完整 CI 流水线
|
|
473
|
-
```
|
|
416
|
+
[Apache License 2.0](LICENSE)
|
package/bin/godex
CHANGED
|
@@ -59,7 +59,7 @@ function findBinary() {
|
|
|
59
59
|
const binary = findBinary();
|
|
60
60
|
if (!binary) {
|
|
61
61
|
console.error(
|
|
62
|
-
"
|
|
62
|
+
"GodeX: binary not found. Re-run npm install or build from source.",
|
|
63
63
|
);
|
|
64
64
|
process.exit(1);
|
|
65
65
|
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ahoo-wang/godex",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"description": "Make every model a Codex engine through an OpenAI-compatible Responses API gateway",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/Ahoo-Wang/
|
|
8
|
+
"url": "git+https://github.com/Ahoo-Wang/GodeX.git"
|
|
9
9
|
},
|
|
10
|
-
"homepage": "https://github.com/Ahoo-Wang/
|
|
10
|
+
"homepage": "https://github.com/Ahoo-Wang/GodeX#readme",
|
|
11
11
|
"bugs": {
|
|
12
|
-
"url": "https://github.com/Ahoo-Wang/
|
|
12
|
+
"url": "https://github.com/Ahoo-Wang/GodeX/issues"
|
|
13
13
|
},
|
|
14
14
|
"publishConfig": {
|
|
15
15
|
"access": "public"
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
"node": ">=18.0.0"
|
|
46
46
|
},
|
|
47
47
|
"optionalDependencies": {
|
|
48
|
-
"@ahoo-wang/godex-darwin-arm64": "0.0.
|
|
49
|
-
"@ahoo-wang/godex-darwin-x64": "0.0.
|
|
50
|
-
"@ahoo-wang/godex-linux-x64": "0.0.
|
|
51
|
-
"@ahoo-wang/godex-linux-arm64": "0.0.
|
|
52
|
-
"@ahoo-wang/godex-win32-x64": "0.0.
|
|
53
|
-
"@ahoo-wang/godex-win32-arm64": "0.0.
|
|
48
|
+
"@ahoo-wang/godex-darwin-arm64": "0.0.17",
|
|
49
|
+
"@ahoo-wang/godex-darwin-x64": "0.0.17",
|
|
50
|
+
"@ahoo-wang/godex-linux-x64": "0.0.17",
|
|
51
|
+
"@ahoo-wang/godex-linux-arm64": "0.0.17",
|
|
52
|
+
"@ahoo-wang/godex-win32-x64": "0.0.17",
|
|
53
|
+
"@ahoo-wang/godex-win32-arm64": "0.0.17"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@ahoo-wang/fetcher": "^3.16.8",
|
package/scripts/install.cjs
CHANGED
|
@@ -65,13 +65,13 @@ function link(src, dest) {
|
|
|
65
65
|
|
|
66
66
|
const platform = detectPlatform();
|
|
67
67
|
if (!platform) {
|
|
68
|
-
console.log("
|
|
68
|
+
console.log("GodeX: skipping binary setup — unsupported platform", process.platform, process.arch);
|
|
69
69
|
process.exit(0);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
const binary = findBinary("@ahoo-wang/godex-" + platform, platform);
|
|
73
73
|
if (!binary) {
|
|
74
|
-
console.log("
|
|
74
|
+
console.log("GodeX: no prebuilt binary for", platform, "— dev mode, skipping");
|
|
75
75
|
process.exit(0);
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -80,4 +80,4 @@ if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
|
|
|
80
80
|
const dest = path.join(destDir, "godex-binary");
|
|
81
81
|
|
|
82
82
|
link(binary, dest);
|
|
83
|
-
console.log("
|
|
83
|
+
console.log("GodeX: installed native binary for", platform);
|