@galaxy-yearn/codex-deepseek-gateway 0.1.4 → 0.1.5
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 +86 -199
- package/bin/codex-deepseek-gateway.js +12 -1
- package/config/gateway.example.json +1 -17
- package/package.json +2 -2
- package/src/codex-launch.js +9 -1
- package/src/codex-sessions.js +56 -12
- package/src/config.js +1 -1
- package/src/firecrawl.js +2 -2
- package/src/protocol.js +331 -86
- package/src/server.js +125 -15
- package/src/tavily.js +3 -3
- package/src/web-search-emulator.js +104 -23
package/README.md
CHANGED
|
@@ -1,59 +1,49 @@
|
|
|
1
1
|
# Codex DeepSeek Gateway
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A lightweight local gateway for using DeepSeek models in Codex.
|
|
4
4
|
|
|
5
|
-
Codex keeps
|
|
5
|
+
Codex keeps using the OpenAI `Responses API` wire format. The gateway translates requests to DeepSeek-compatible `Chat Completions`, calls DeepSeek, then translates the result back to Responses JSON or `response.*` SSE events.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
The runtime uses only the Node.js standard library. It installs under `~/.codex/deepseek-gateway`, runs as a detached background process, and does not edit your existing Codex config.
|
|
10
|
-
|
|
11
|
-
Use this when you want Codex to stay on its normal Responses API path while the actual model is DeepSeek, including DeepSeek thinking output, function calls, local session resume help, and optional web search emulation through Tavily and Firecrawl.
|
|
7
|
+
Package: [@galaxy-yearn/codex-deepseek-gateway](https://www.npmjs.com/package/@galaxy-yearn/codex-deepseek-gateway)
|
|
12
8
|
|
|
13
9
|
## Requirements
|
|
14
10
|
|
|
15
|
-
- Node.js
|
|
11
|
+
- Node.js 22 or newer
|
|
16
12
|
- A DeepSeek API key
|
|
17
|
-
- Codex
|
|
13
|
+
- Codex CLI
|
|
18
14
|
|
|
19
15
|
## Install
|
|
20
16
|
|
|
21
|
-
Run:
|
|
22
|
-
|
|
23
17
|
```sh
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
This copies the runtime to:
|
|
28
|
-
|
|
29
|
-
```text
|
|
30
|
-
~/.codex/deepseek-gateway
|
|
18
|
+
npm install -g @galaxy-yearn/codex-deepseek-gateway
|
|
19
|
+
codex-deepseek-gateway --version
|
|
20
|
+
codex-deepseek-gateway install
|
|
31
21
|
```
|
|
32
22
|
|
|
33
|
-
|
|
23
|
+
The runtime is copied to `~/.codex/deepseek-gateway`. Put your DeepSeek API key in:
|
|
34
24
|
|
|
35
25
|
```text
|
|
36
26
|
~/.codex/deepseek-gateway/config/gateway.local.json
|
|
37
|
-
~/.codex/deepseek-gateway/config/model-aliases.json
|
|
38
27
|
```
|
|
39
28
|
|
|
40
|
-
Put your DeepSeek API key in `gateway.local.json`:
|
|
41
|
-
|
|
42
29
|
```json
|
|
43
|
-
|
|
30
|
+
{
|
|
31
|
+
"upstreamApiKey": "sk-..."
|
|
32
|
+
}
|
|
44
33
|
```
|
|
45
34
|
|
|
46
|
-
`
|
|
47
|
-
|
|
48
|
-
If the key is already configured, `install` also starts the gateway. If this is your first install, add the key and then run:
|
|
35
|
+
`install` preserves an existing `gateway.local.json`. If this is your first install, add the key and start the gateway:
|
|
49
36
|
|
|
50
37
|
```sh
|
|
51
|
-
|
|
38
|
+
codex-deepseek-gateway start
|
|
39
|
+
codex-deepseek-gateway status
|
|
52
40
|
```
|
|
53
41
|
|
|
54
|
-
|
|
42
|
+
`status` should show `"reachable": true`.
|
|
43
|
+
|
|
44
|
+
## Codex Config
|
|
55
45
|
|
|
56
|
-
|
|
46
|
+
Add this provider to `~/.codex/config.toml`:
|
|
57
47
|
|
|
58
48
|
```toml
|
|
59
49
|
model_provider = "deepseek-gateway"
|
|
@@ -68,225 +58,122 @@ base_url = "http://127.0.0.1:3000/v1"
|
|
|
68
58
|
wire_api = "responses"
|
|
69
59
|
```
|
|
70
60
|
|
|
71
|
-
Use `deepseek-v4-pro`
|
|
72
|
-
|
|
73
|
-
Restart Codex after changing `config.toml`.
|
|
61
|
+
Use `deepseek-v4-pro` if you want the pro model. Restart Codex after editing `config.toml`.
|
|
74
62
|
|
|
75
|
-
##
|
|
63
|
+
## Usage
|
|
76
64
|
|
|
77
|
-
Start
|
|
65
|
+
Start a new Codex conversation with gateway overrides:
|
|
78
66
|
|
|
79
67
|
```sh
|
|
80
|
-
|
|
81
|
-
npx @galaxy-yearn/codex-deepseek-gateway stop
|
|
82
|
-
npx @galaxy-yearn/codex-deepseek-gateway status
|
|
68
|
+
codex-deepseek-gateway new
|
|
83
69
|
```
|
|
84
70
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Run `doctor` to check the active Codex config and the DeepSeek request shape the gateway will send:
|
|
71
|
+
Resume a Codex session from the current project:
|
|
88
72
|
|
|
89
73
|
```sh
|
|
90
|
-
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Important fields:
|
|
94
|
-
|
|
95
|
-
- `codexConfigUsingGateway` should be `true`
|
|
96
|
-
- `codexModel` should be your gateway model, for example `deepseek-v4-pro`
|
|
97
|
-
- `codexReasoningEffort` should match `model_reasoning_effort`
|
|
98
|
-
- `deepseekThinking` shows the DeepSeek `thinking` payload
|
|
99
|
-
- `deepseekReasoningEffort` shows the DeepSeek effort sent upstream
|
|
100
|
-
- `reasoningDisplayMode` shows whether reasoning is shown as `summary`, `hidden`, or `disabled`
|
|
101
|
-
- `tavilyWebSearchReady` and `firecrawlWebFetchReady` show whether optional web search backends are usable
|
|
102
|
-
|
|
103
|
-
Avoid running Codex through a proxy while using the local gateway. Some proxy clients intercept `http://127.0.0.1:3000` and can break local gateway requests.
|
|
104
|
-
|
|
105
|
-
## Models
|
|
106
|
-
|
|
107
|
-
The installed `config/model-aliases.json` starts with:
|
|
108
|
-
|
|
109
|
-
```json
|
|
110
|
-
{
|
|
111
|
-
"deepseek-v4-flash": {
|
|
112
|
-
"model": "deepseek-v4-flash",
|
|
113
|
-
"thinking": "auto"
|
|
114
|
-
},
|
|
115
|
-
"deepseek-v4-pro": {
|
|
116
|
-
"model": "deepseek-v4-pro",
|
|
117
|
-
"thinking": "auto"
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
The gateway serves these aliases directly on `GET /v1/models`. If you also want to merge DeepSeek's upstream `/models` list, set this in `gateway.local.json`:
|
|
123
|
-
|
|
124
|
-
```json
|
|
125
|
-
"fetchUpstreamModels": true
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
Whether Codex TUI `/model` shows custom provider models depends on the Codex client build. `config.toml`, `new`, and `sessions` remain the reliable ways to choose a model.
|
|
129
|
-
|
|
130
|
-
## Reasoning
|
|
131
|
-
|
|
132
|
-
Codex reasoning effort maps to DeepSeek V4 thinking mode:
|
|
133
|
-
|
|
134
|
-
| Codex `model_reasoning_effort` | DeepSeek request |
|
|
135
|
-
| --- | --- |
|
|
136
|
-
| `low` | `thinking.type = disabled` |
|
|
137
|
-
| `medium` | `thinking.type = enabled`, `reasoning_effort = high` |
|
|
138
|
-
| `high` | `thinking.type = enabled`, `reasoning_effort = high` |
|
|
139
|
-
| `xhigh` | `thinking.type = enabled`, `reasoning_effort = max` |
|
|
140
|
-
|
|
141
|
-
`model_supports_reasoning_summaries = true` and `model_reasoning_summary = "auto"` tell Codex to use its normal thinking UI path. When DeepSeek returns `reasoning_content`, the gateway keeps the raw text as `reasoning_text` and mirrors it into `summary_text` with a `Reasoning` heading for Codex TUI compatibility.
|
|
142
|
-
|
|
143
|
-
When thinking is enabled, the gateway intentionally buffers visible assistant text and tool-call output until the upstream response completes, then emits reasoning first. This improves thinking order in Codex at the cost of higher first-token latency.
|
|
144
|
-
|
|
145
|
-
## Web Search
|
|
146
|
-
|
|
147
|
-
Web search is off by default. To let Codex's native `web_search` or `web_search_preview` tool work while DeepSeek is active, configure the optional backends in `gateway.local.json`.
|
|
148
|
-
|
|
149
|
-
Tavily provides search results:
|
|
150
|
-
|
|
151
|
-
```json
|
|
152
|
-
"tavilyApiKey": "tvly-...",
|
|
153
|
-
"tavilyWebSearchEnabled": true
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
Firecrawl provides opened-page reading and focused page lookup:
|
|
157
|
-
|
|
158
|
-
```json
|
|
159
|
-
"firecrawlApiKey": "fc-...",
|
|
160
|
-
"firecrawlWebFetchEnabled": true,
|
|
161
|
-
"firecrawlAutoScrapeTopResults": 3
|
|
74
|
+
codex-deepseek-gateway sessions
|
|
162
75
|
```
|
|
163
76
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
- `tavily_search` searches the web and returns citation-ready snippets.
|
|
167
|
-
- `firecrawl_open_page` reads a specific public page.
|
|
168
|
-
- `firecrawl_find_in_page` reads a page with a focused query.
|
|
169
|
-
|
|
170
|
-
DeepSeek receives compact text context containing the search query, sources, snippets, and optional opened-page excerpts. Codex receives Responses-compatible `web_search_call` items, final assistant messages, and URL citation annotations when the final text cites a matching source marker.
|
|
171
|
-
|
|
172
|
-
The web payload is intentionally text-focused. Tavily is called with `include_raw_content: false`; Firecrawl defaults to main content, removes base64 images, rejects local/private URLs, and truncates page text before it reaches the model.
|
|
173
|
-
|
|
174
|
-
## New Conversations
|
|
175
|
-
|
|
176
|
-
Start a new Codex conversation with a temporary gateway model override:
|
|
77
|
+
Useful non-interactive forms:
|
|
177
78
|
|
|
178
79
|
```sh
|
|
179
|
-
|
|
80
|
+
codex-deepseek-gateway new --model deepseek-v4-flash --reasoning-effort low
|
|
81
|
+
codex-deepseek-gateway new --print
|
|
82
|
+
codex-deepseek-gateway sessions --print
|
|
83
|
+
codex-deepseek-gateway sessions --all
|
|
84
|
+
codex-deepseek-gateway sessions --exec <id-or-row>
|
|
180
85
|
```
|
|
181
86
|
|
|
182
|
-
|
|
87
|
+
`new` chooses a model, then Codex reasoning effort. `sessions` chooses a session first, then model and reasoning effort. Both launch Codex with:
|
|
183
88
|
|
|
184
89
|
```sh
|
|
185
|
-
codex -c model_provider=deepseek-gateway -c model=<model> -c model_reasoning_effort=<effort>
|
|
90
|
+
codex -c model_provider=deepseek-gateway -c model=<model> -c model_reasoning_effort=<effort> -c model_supports_reasoning_summaries=true -c model_reasoning_summary=auto
|
|
186
91
|
```
|
|
187
92
|
|
|
188
|
-
|
|
93
|
+
Model aliases are read from:
|
|
189
94
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
```sh
|
|
193
|
-
npx @galaxy-yearn/codex-deepseek-gateway new --model deepseek-v4-flash --reasoning-effort low
|
|
95
|
+
```text
|
|
96
|
+
~/.codex/deepseek-gateway/config/model-aliases.json
|
|
194
97
|
```
|
|
195
98
|
|
|
196
|
-
|
|
99
|
+
## Commands
|
|
197
100
|
|
|
198
101
|
```sh
|
|
199
|
-
|
|
102
|
+
codex-deepseek-gateway install
|
|
103
|
+
codex-deepseek-gateway start
|
|
104
|
+
codex-deepseek-gateway stop
|
|
105
|
+
codex-deepseek-gateway status
|
|
106
|
+
codex-deepseek-gateway doctor
|
|
107
|
+
codex-deepseek-gateway new
|
|
108
|
+
codex-deepseek-gateway sessions
|
|
109
|
+
codex-deepseek-gateway uninstall
|
|
200
110
|
```
|
|
201
111
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
Open a cross-provider session picker from a project:
|
|
112
|
+
`doctor` checks the active Codex config, DeepSeek request shape, reasoning mode, and optional web-search backend readiness.
|
|
205
113
|
|
|
206
|
-
|
|
207
|
-
npx @galaxy-yearn/codex-deepseek-gateway sessions
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
The picker is read-only. It scans Codex local transcript files, lists sessions for the current project across providers, then runs:
|
|
114
|
+
To remove the local runtime and then uninstall the global package:
|
|
211
115
|
|
|
212
116
|
```sh
|
|
213
|
-
codex
|
|
117
|
+
codex-deepseek-gateway uninstall
|
|
118
|
+
npm uninstall -g @galaxy-yearn/codex-deepseek-gateway
|
|
214
119
|
```
|
|
215
120
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
- choose a model from `~/.codex/deepseek-gateway/config/model-aliases.json`
|
|
219
|
-
- choose Codex reasoning effort
|
|
220
|
-
- choose `[New conversation]` or the session to resume
|
|
221
|
-
- use `↑/↓` to select, `Enter` to confirm, `←` to go back, and `Esc` to quit
|
|
222
|
-
|
|
223
|
-
Print copyable resume commands instead of opening the picker:
|
|
224
|
-
|
|
225
|
-
```sh
|
|
226
|
-
npx @galaxy-yearn/codex-deepseek-gateway sessions --print
|
|
227
|
-
```
|
|
121
|
+
## Reasoning
|
|
228
122
|
|
|
229
|
-
|
|
123
|
+
Codex effort maps to DeepSeek V4 thinking mode:
|
|
230
124
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
125
|
+
| Codex effort | DeepSeek request |
|
|
126
|
+
| --- | --- |
|
|
127
|
+
| `low` | `thinking.type = disabled` |
|
|
128
|
+
| `medium` | `thinking.type = enabled`, `reasoning_effort = high` |
|
|
129
|
+
| `high` | `thinking.type = enabled`, `reasoning_effort = high` |
|
|
130
|
+
| `xhigh` | `thinking.type = enabled`, `reasoning_effort = max` |
|
|
234
131
|
|
|
235
|
-
|
|
132
|
+
When DeepSeek returns `reasoning_content`, the gateway preserves the raw text for DeepSeek history and sends a display-only Markdown-cleaned copy through Codex's reasoning summary UI. The raw reasoning is not duplicated into visible message content.
|
|
236
133
|
|
|
237
|
-
|
|
238
|
-
npx @galaxy-yearn/codex-deepseek-gateway sessions --exec <id-or-row>
|
|
239
|
-
```
|
|
134
|
+
## Web Search
|
|
240
135
|
|
|
241
|
-
|
|
136
|
+
Web search is optional and off by default. Configure Tavily for search:
|
|
242
137
|
|
|
243
|
-
```
|
|
244
|
-
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"tavilyApiKey": "tvly-...",
|
|
141
|
+
"tavilyWebSearchEnabled": true
|
|
142
|
+
}
|
|
245
143
|
```
|
|
246
144
|
|
|
247
|
-
|
|
145
|
+
Configure Firecrawl if you also want opened-page reading:
|
|
248
146
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
npx @galaxy-yearn/codex-deepseek-gateway stop
|
|
255
|
-
npx @galaxy-yearn/codex-deepseek-gateway status
|
|
256
|
-
npx @galaxy-yearn/codex-deepseek-gateway doctor
|
|
257
|
-
npx @galaxy-yearn/codex-deepseek-gateway new
|
|
258
|
-
npx @galaxy-yearn/codex-deepseek-gateway sessions
|
|
259
|
-
npx @galaxy-yearn/codex-deepseek-gateway uninstall
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"firecrawlApiKey": "fc-...",
|
|
150
|
+
"firecrawlWebFetchEnabled": true
|
|
151
|
+
}
|
|
260
152
|
```
|
|
261
153
|
|
|
262
|
-
`
|
|
154
|
+
Codex can keep requesting `web_search` / `web_search_preview`. The gateway exposes compact internal web tools to DeepSeek, executes Tavily/Firecrawl calls itself, feeds tool results back to the model, and returns Codex-compatible `web_search_call` items. `TAVILY_MAX_SEARCH_ROUNDS` defaults to `10` and is only a runaway/cost guardrail; when reached, the gateway disables web tools for one final-answer turn.
|
|
263
155
|
|
|
264
|
-
|
|
156
|
+
Final answers should include useful source titles and URLs directly.
|
|
265
157
|
|
|
266
|
-
##
|
|
158
|
+
## 0.1.5 Updates
|
|
267
159
|
|
|
268
|
-
-
|
|
269
|
-
-
|
|
270
|
-
-
|
|
271
|
-
-
|
|
272
|
-
-
|
|
273
|
-
-
|
|
274
|
-
-
|
|
275
|
-
- `GET /v1/models` with local model aliases and optional upstream discovery
|
|
276
|
-
- optional Tavily/Firecrawl-backed `web_search` emulation
|
|
277
|
-
- new-conversation launcher with per-run Codex config overrides
|
|
278
|
-
- read-only cross-provider session picker
|
|
160
|
+
- Global CLI usage: install once with `npm install -g`, then run `codex-deepseek-gateway ...` from any directory.
|
|
161
|
+
- `install` preserves `gateway.local.json`; runtime defaults stay in code.
|
|
162
|
+
- `new` and `sessions` launch Codex with reasoning-summary config overrides.
|
|
163
|
+
- `sessions` first chooses the session, hides subagent transcripts, and sorts by the latest user-message date.
|
|
164
|
+
- DeepSeek-facing tool descriptions are compact; namespaced Codex local tools, including multi-agent tools, pass back to Codex with namespace preserved.
|
|
165
|
+
- Web search uses a model-driven loop: internal Tavily/Firecrawl tools run in the gateway, local Codex tools pass through to Codex, and final-answer turns no longer end as empty completions.
|
|
166
|
+
- Reasoning display now uses Codex summary events only, with display-only Markdown cleanup to avoid duplicate or truncated thinking text.
|
|
279
167
|
|
|
280
168
|
## Limits
|
|
281
169
|
|
|
282
|
-
Chat Completions is not a full Responses API replacement.
|
|
170
|
+
Chat Completions is not a full Responses API replacement.
|
|
283
171
|
|
|
284
|
-
- Hosted tools
|
|
285
|
-
- Tavily
|
|
286
|
-
- URL citations are returned in the Responses metadata path. Whether they appear as clickable links in the terminal depends on the Codex client build and how it renders custom-provider citation annotations.
|
|
172
|
+
- Hosted tools without a local Codex executor are represented as function shims. Web search is the only hosted tool the gateway emulates directly.
|
|
173
|
+
- Tavily/Firecrawl web emulation is text-focused; it does not provide browser control, screenshots, raw HTML, cookies, crawl jobs, or private-network access.
|
|
287
174
|
- OpenAI `file_id` values are passed through; the gateway cannot fetch private OpenAI-hosted files.
|
|
288
|
-
- In-memory conversation history is lost when the gateway restarts.
|
|
289
|
-
- The gateway exposes model aliases on `/v1/models
|
|
175
|
+
- In-memory `previous_response_id` / `conversation` history is lost when the gateway process restarts.
|
|
176
|
+
- The gateway exposes model aliases on `/v1/models`; whether Codex TUI `/model` shows them depends on the Codex build. `config.toml`, `new`, and `sessions` are the reliable model-selection paths.
|
|
290
177
|
|
|
291
178
|
## License
|
|
292
179
|
|
|
@@ -39,6 +39,7 @@ Usage:
|
|
|
39
39
|
codex-deepseek-gateway uninstall
|
|
40
40
|
|
|
41
41
|
Options:
|
|
42
|
+
-v, --version Print package version
|
|
42
43
|
--dir <path> Install directory, defaults to ~/.codex/deepseek-gateway
|
|
43
44
|
--no-edit Do not open the local config file after install
|
|
44
45
|
--all With sessions, include sessions outside the current project
|
|
@@ -58,7 +59,9 @@ function parseArgs(argv) {
|
|
|
58
59
|
const rest = [];
|
|
59
60
|
for (let index = 0; index < argv.length; index += 1) {
|
|
60
61
|
const arg = argv[index];
|
|
61
|
-
if (arg === '--
|
|
62
|
+
if (arg === '--version' || arg === '-v') {
|
|
63
|
+
options.version = true;
|
|
64
|
+
} else if (arg === '--dir' || arg === '-d' || arg === '-InstallDir') {
|
|
62
65
|
const value = argv[index + 1];
|
|
63
66
|
if (!value) throw new Error(`${arg} requires a path`);
|
|
64
67
|
options.dir = resolve(value);
|
|
@@ -253,6 +256,10 @@ async function install(options) {
|
|
|
253
256
|
}
|
|
254
257
|
|
|
255
258
|
async function start(options) {
|
|
259
|
+
if (!existsSync(options.dir)) {
|
|
260
|
+
throw new Error(`Missing runtime at ${options.dir}. Run install first.`);
|
|
261
|
+
}
|
|
262
|
+
copyRuntime(options.dir);
|
|
256
263
|
if (!existsSync(serverPath(options.dir))) {
|
|
257
264
|
throw new Error(`Missing runtime at ${options.dir}. Run install first.`);
|
|
258
265
|
}
|
|
@@ -400,6 +407,10 @@ async function uninstall(options) {
|
|
|
400
407
|
|
|
401
408
|
async function main() {
|
|
402
409
|
const { command, options } = parseArgs(process.argv.slice(2));
|
|
410
|
+
if (options.version) {
|
|
411
|
+
print(`${packageJson.version}\n`);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
403
414
|
if (!command || command === '-h' || command === '--help' || command === 'help') {
|
|
404
415
|
usage();
|
|
405
416
|
return;
|
|
@@ -1,23 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"port": 3000,
|
|
3
|
-
"host": "127.0.0.1",
|
|
4
|
-
"upstreamProvider": "deepseek",
|
|
5
|
-
"upstreamBaseUrl": "https://api.deepseek.com",
|
|
6
2
|
"upstreamApiKey": "sk-REPLACE_ME",
|
|
7
|
-
"upstreamTimeoutMs": 120000,
|
|
8
3
|
"tavilyApiKey": "",
|
|
9
4
|
"tavilyWebSearchEnabled": false,
|
|
10
|
-
"tavilySearchDepth": "basic",
|
|
11
|
-
"tavilyMaxResults": 5,
|
|
12
|
-
"tavilyMaxSearchRounds": 2,
|
|
13
|
-
"tavilyTimeoutMs": 15000,
|
|
14
5
|
"firecrawlApiKey": "",
|
|
15
|
-
"firecrawlWebFetchEnabled": false
|
|
16
|
-
"firecrawlAutoScrapeTopResults": 3,
|
|
17
|
-
"firecrawlPageMaxChars": 5000,
|
|
18
|
-
"firecrawlTimeoutMs": 30000,
|
|
19
|
-
"fetchUpstreamModels": false,
|
|
20
|
-
"modelsTimeoutMs": 5000,
|
|
21
|
-
"modelsCacheMs": 60000,
|
|
22
|
-
"debugPayload": false
|
|
6
|
+
"firecrawlWebFetchEnabled": false
|
|
23
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@galaxy-yearn/codex-deepseek-gateway",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Local OpenAI Codex Responses API gateway for DeepSeek Chat Completions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/server.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"test": "node --test"
|
|
20
20
|
},
|
|
21
21
|
"engines": {
|
|
22
|
-
"node": ">=
|
|
22
|
+
"node": ">=22"
|
|
23
23
|
},
|
|
24
24
|
"keywords": [
|
|
25
25
|
"codex",
|
package/src/codex-launch.js
CHANGED
|
@@ -66,6 +66,10 @@ function configOverrideArgs(context) {
|
|
|
66
66
|
`model=${JSON.stringify(context.model)}`,
|
|
67
67
|
'-c',
|
|
68
68
|
`model_reasoning_effort=${JSON.stringify(context.reasoningEffort)}`,
|
|
69
|
+
'-c',
|
|
70
|
+
'model_supports_reasoning_summaries=true',
|
|
71
|
+
'-c',
|
|
72
|
+
'model_reasoning_summary="auto"',
|
|
69
73
|
];
|
|
70
74
|
}
|
|
71
75
|
|
|
@@ -77,6 +81,10 @@ function configOverrideCommandParts(context) {
|
|
|
77
81
|
`model=${context.model}`,
|
|
78
82
|
'-c',
|
|
79
83
|
`model_reasoning_effort=${context.reasoningEffort}`,
|
|
84
|
+
'-c',
|
|
85
|
+
'model_supports_reasoning_summaries=true',
|
|
86
|
+
'-c',
|
|
87
|
+
'model_reasoning_summary=auto',
|
|
80
88
|
];
|
|
81
89
|
}
|
|
82
90
|
|
|
@@ -127,7 +135,7 @@ function renderPicker(state) {
|
|
|
127
135
|
const row = `${index === selected ? '>' : ' '} ${rows[index]}`;
|
|
128
136
|
output += `${index === selected ? `${SELECTED_ROW}${row}${RESET_STYLE}` : row}\n`;
|
|
129
137
|
}
|
|
130
|
-
output += '\
|
|
138
|
+
output += '\nUp/Down select Enter confirm Left back Esc quit\n';
|
|
131
139
|
print(output);
|
|
132
140
|
}
|
|
133
141
|
|
package/src/codex-sessions.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
readFileSync,
|
|
6
6
|
readdirSync,
|
|
7
7
|
readSync,
|
|
8
|
+
statSync,
|
|
8
9
|
} from 'node:fs';
|
|
9
10
|
import { join, resolve } from 'node:path';
|
|
10
11
|
import {
|
|
@@ -60,6 +61,48 @@ function parseJsonLine(line) {
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
function isSubagentSession(meta) {
|
|
65
|
+
return meta?.thread_source === 'subagent' || Boolean(meta?.parent_thread_id);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function timestampFromUserMessage(row) {
|
|
69
|
+
const payload = row?.payload;
|
|
70
|
+
if (row?.type === 'event_msg' && payload?.type === 'user_message') return row.timestamp || '';
|
|
71
|
+
if (row?.type === 'response_item' && payload?.type === 'message' && payload.role === 'user') return row.timestamp || '';
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function readLastUserMessageTimestamp(file, chunkSize = 256 * 1024) {
|
|
76
|
+
const handle = openSync(file, 'r');
|
|
77
|
+
try {
|
|
78
|
+
let position = statSync(file).size;
|
|
79
|
+
let suffix = Buffer.alloc(0);
|
|
80
|
+
while (position > 0) {
|
|
81
|
+
const length = Math.min(chunkSize, position);
|
|
82
|
+
position -= length;
|
|
83
|
+
const buffer = Buffer.alloc(length);
|
|
84
|
+
const read = readSync(handle, buffer, 0, length, position);
|
|
85
|
+
let data = buffer.subarray(0, read);
|
|
86
|
+
if (suffix.length) data = Buffer.concat([data, suffix]);
|
|
87
|
+
|
|
88
|
+
let lineEnd = data.length;
|
|
89
|
+
for (let index = data.length - 1; index >= 0; index -= 1) {
|
|
90
|
+
if (data[index] !== 10) continue;
|
|
91
|
+
const row = parseJsonLine(data.subarray(index + 1, lineEnd).toString('utf8').trim());
|
|
92
|
+
const timestamp = timestampFromUserMessage(row);
|
|
93
|
+
if (timestamp) return timestamp;
|
|
94
|
+
lineEnd = index;
|
|
95
|
+
}
|
|
96
|
+
suffix = data.subarray(0, lineEnd);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const row = parseJsonLine(suffix.toString('utf8').trim());
|
|
100
|
+
return timestampFromUserMessage(row);
|
|
101
|
+
} finally {
|
|
102
|
+
closeSync(handle);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
63
106
|
function readSessionIndex(codexHome) {
|
|
64
107
|
const file = join(codexHome, 'session_index.jsonl');
|
|
65
108
|
const byId = new Map();
|
|
@@ -118,16 +161,17 @@ function isInsideProject(cwd, projectRoot) {
|
|
|
118
161
|
return sessionCwd === root || sessionCwd.startsWith(`${root}${process.platform === 'win32' ? '\\' : '/'}`);
|
|
119
162
|
}
|
|
120
163
|
|
|
121
|
-
function readSession(file, indexById) {
|
|
164
|
+
export function readSession(file, indexById) {
|
|
122
165
|
const lines = readStart(file).split(/\r?\n/).filter(Boolean);
|
|
123
166
|
const meta = parseJsonLine(lines[0])?.payload;
|
|
124
167
|
if (!meta?.id) return null;
|
|
168
|
+
if (isSubagentSession(meta)) return null;
|
|
125
169
|
const index = indexById.get(meta.id) || {};
|
|
126
170
|
return {
|
|
127
171
|
id: meta.id,
|
|
128
172
|
provider: meta.model_provider || '',
|
|
129
173
|
cwd: meta.cwd || '',
|
|
130
|
-
updatedAt: index.updated_at || meta.timestamp || '',
|
|
174
|
+
updatedAt: readLastUserMessageTimestamp(file) || index.updated_at || meta.timestamp || '',
|
|
131
175
|
title: index.thread_name || firstUserPreview(lines) || '(untitled)',
|
|
132
176
|
};
|
|
133
177
|
}
|
|
@@ -180,9 +224,16 @@ async function chooseSessionFlow(allSessions, context, limit) {
|
|
|
180
224
|
const listed = allSessions.slice(0, limit);
|
|
181
225
|
const header = sessionHeader();
|
|
182
226
|
return await withPickerScreen(async () => {
|
|
183
|
-
let step = '
|
|
227
|
+
let step = 'session';
|
|
184
228
|
while (true) {
|
|
185
|
-
if (step === '
|
|
229
|
+
if (step === 'session') {
|
|
230
|
+
const rows = [NEW_SESSION_ROW, ...sessionRows(listed)];
|
|
231
|
+
const result = await pick(`Choose Codex session`, rows, header);
|
|
232
|
+
if (result.action === 'cancel') return null;
|
|
233
|
+
if (result.action === 'back') return null;
|
|
234
|
+
context.selectedSession = result.index === 0 ? { newConversation: true } : listed[result.index - 1];
|
|
235
|
+
step = 'model';
|
|
236
|
+
} else if (step === 'model') {
|
|
186
237
|
const action = await pickModel(context);
|
|
187
238
|
if (action !== 'select') return null;
|
|
188
239
|
step = 'reasoning';
|
|
@@ -190,14 +241,7 @@ async function chooseSessionFlow(allSessions, context, limit) {
|
|
|
190
241
|
const action = await pickReasoning(context);
|
|
191
242
|
if (action === 'cancel') return null;
|
|
192
243
|
if (action === 'back') step = 'model';
|
|
193
|
-
else
|
|
194
|
-
} else {
|
|
195
|
-
const rows = [NEW_SESSION_ROW, ...sessionRows(listed)];
|
|
196
|
-
const result = await pick(`Choose Codex session for ${context.provider} / ${context.model} / ${context.reasoningEffort}`, rows, header);
|
|
197
|
-
if (result.action === 'cancel') return null;
|
|
198
|
-
if (result.action === 'back') step = 'reasoning';
|
|
199
|
-
else if (result.index === 0) return { newConversation: true };
|
|
200
|
-
else return listed[result.index - 1];
|
|
244
|
+
else return context.selectedSession;
|
|
201
245
|
}
|
|
202
246
|
}
|
|
203
247
|
});
|
package/src/config.js
CHANGED
|
@@ -25,7 +25,7 @@ export function loadConfig(env = process.env) {
|
|
|
25
25
|
tavilyBaseUrl: mergedEnv.TAVILY_BASE_URL || 'https://api.tavily.com',
|
|
26
26
|
tavilySearchDepth: mergedEnv.TAVILY_SEARCH_DEPTH || 'basic',
|
|
27
27
|
tavilyMaxResults: Number(mergedEnv.TAVILY_MAX_RESULTS || 5),
|
|
28
|
-
tavilyMaxSearchRounds: Number(mergedEnv.TAVILY_MAX_SEARCH_ROUNDS ||
|
|
28
|
+
tavilyMaxSearchRounds: Number(mergedEnv.TAVILY_MAX_SEARCH_ROUNDS || 10),
|
|
29
29
|
tavilyTimeoutMs: Number(mergedEnv.TAVILY_TIMEOUT_MS || 15000),
|
|
30
30
|
tavilySnippetChars: Number(mergedEnv.TAVILY_SNIPPET_CHARS || 650),
|
|
31
31
|
tavilyResultMaxChars: Number(mergedEnv.TAVILY_RESULT_MAX_CHARS || 6000),
|
package/src/firecrawl.js
CHANGED
|
@@ -304,12 +304,12 @@ export function formatFirecrawlScrapeResult({ url = '', title = '', summary = ''
|
|
|
304
304
|
if (links?.length) {
|
|
305
305
|
lines.push('Page links:');
|
|
306
306
|
for (const [index, link] of links.entries()) {
|
|
307
|
-
lines.push(`
|
|
307
|
+
lines.push(`Link ${index + 1}: ${link.title || link.url}`);
|
|
308
308
|
lines.push(`URL: ${link.url}`);
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
311
|
}
|
|
312
|
-
lines.push('Use this opened page only as source text.
|
|
312
|
+
lines.push('Use this opened page only as source text. If it supports the answer, include the page title and URL.');
|
|
313
313
|
const text = lines.filter(Boolean).join('\n');
|
|
314
314
|
const maxChars = Number(config.firecrawlResultMaxChars) || DEFAULT_TOTAL_CHARS;
|
|
315
315
|
if (text.length <= maxChars) return text;
|