@young1lin/dsh-gpt-sub 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +268 -0
- package/client.js +908 -0
- package/cordis.patch.yml +44 -0
- package/lib/index.d.ts +52 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +581 -0
- package/lib/index.js.map +1 -0
- package/lib/jwt.d.ts +15 -0
- package/lib/jwt.d.ts.map +1 -0
- package/lib/jwt.js +29 -0
- package/lib/jwt.js.map +1 -0
- package/lib/proxy-config.d.ts +91 -0
- package/lib/proxy-config.d.ts.map +1 -0
- package/lib/proxy-config.js +213 -0
- package/lib/proxy-config.js.map +1 -0
- package/lib/proxy-probe.d.ts +47 -0
- package/lib/proxy-probe.d.ts.map +1 -0
- package/lib/proxy-probe.js +51 -0
- package/lib/proxy-probe.js.map +1 -0
- package/lib/proxy-routing.d.ts +111 -0
- package/lib/proxy-routing.d.ts.map +1 -0
- package/lib/proxy-routing.js +171 -0
- package/lib/proxy-routing.js.map +1 -0
- package/lib/quota-route.d.ts +85 -0
- package/lib/quota-route.d.ts.map +1 -0
- package/lib/quota-route.js +105 -0
- package/lib/quota-route.js.map +1 -0
- package/lib/reset-credits.d.ts +64 -0
- package/lib/reset-credits.d.ts.map +1 -0
- package/lib/reset-credits.js +84 -0
- package/lib/reset-credits.js.map +1 -0
- package/lib/token-store.d.ts +107 -0
- package/lib/token-store.d.ts.map +1 -0
- package/lib/token-store.js +228 -0
- package/lib/token-store.js.map +1 -0
- package/lib/types.d.ts +18 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/lib/usage.d.ts +92 -0
- package/lib/usage.d.ts.map +1 -0
- package/lib/usage.js +106 -0
- package/lib/usage.js.map +1 -0
- package/package.json +82 -0
- package/src/index.ts +685 -0
- package/src/jwt.ts +26 -0
- package/src/proxy-config.ts +217 -0
- package/src/proxy-probe.ts +81 -0
- package/src/proxy-routing.ts +205 -0
- package/src/quota-route.ts +163 -0
- package/src/reset-credits.ts +128 -0
- package/src/token-store.ts +309 -0
- package/src/types.ts +17 -0
- package/src/usage.ts +154 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# 更新日志
|
|
2
|
+
|
|
3
|
+
记录用户可感知的变化;格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。
|
|
4
|
+
版本号见 `package.json`。
|
|
5
|
+
|
|
6
|
+
## [0.1.0] - 2026-09-20
|
|
7
|
+
|
|
8
|
+
### 新增
|
|
9
|
+
|
|
10
|
+
- 首个发布:ChatGPT/Codex 订阅直连 DeepSeek Harness(DSH),替代 CLIProxyAPI —— 无回环端口、无共享本地密钥、无外部代理二进制;`codex` CLI 仍持有登录态。
|
|
11
|
+
- 从 `~/.codex/auth.json` 读取 access token,到期前自动刷新(单次 refresh_token 随每次刷新轮换、原子写回,`codex` CLI 不受影响);refresh_token 被拒绝时给出重新 `codex` 登录的明确指引。
|
|
12
|
+
- 以 `CODEX_NATIVE_TOKEN` 凭据引用持续发布令牌(默认每 10 分钟同步一次),pi-ai 的 `openai-codex` 路由始终读到活令牌。
|
|
13
|
+
- 自带 `cordis.patch.yml` 双行补丁:插入 `gpt-sub` 插件行 + 编辑 dsh-base 的 `llm-pi-ai` 行播种 `openai-codex` 路由(随 pi-ai 目录走 `gpt-5.6-sol/terra/luna`),安装即用、零用户配置。
|
|
14
|
+
- 进程级 undici dispatcher 按主机名分流:`chatgpt.com` / `auth.openai.com` / `api.openai.com` 走配置代理(默认 `http://127.0.0.1:7890`,可空为直连),其余流量不受影响;仅对连接级失败重试(`bootstrapRetries`,默认 3),上游 HTTP 错误原样透传、绝不重放。
|
|
15
|
+
- 「Codex 配额」设置面板:5 小时 / 周窗口的剩余量进度条与精确重置倒计时(`4h22m后重置` 式)、按需用量重置次数徽章与消费入口(先确认后消费)、Auth 文件与 HTTP 代理在线切换(候选先验证再应用、持久化到 `stateFile`、测试连接探测、宿主只读目录选择器)。
|
|
16
|
+
- 宿主端点:`GET /gpt-sub/quota`、`GET /gpt-sub/status`、`GET /gpt-sub/reset-credits`、`POST /gpt-sub/reset-credits/consume`、`POST /gpt-sub/proxy`、`POST /gpt-sub/proxy/test`、`POST /gpt-sub/auth`、`GET /gpt-sub/auth/browse`。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 young1lin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# dsh-gpt-sub
|
|
2
|
+
|
|
3
|
+
Direct ChatGPT/Codex subscription access for DeepSeek Harness (DSH).
|
|
4
|
+
|
|
5
|
+
Published on npm as **`@young1lin/dsh-gpt-sub`** (public, MIT).
|
|
6
|
+
|
|
7
|
+
This plugin owns no transport. pi-ai already ships an `openai-codex` provider
|
|
8
|
+
that speaks the Codex wire format — it sets `store`, derives
|
|
9
|
+
`chatgpt-account-id` from the access token's own JWT claim, and speaks the
|
|
10
|
+
codex-responses protocol — but it authenticates only through OAuth, and
|
|
11
|
+
`dsh-llm-pi-ai` runs no login flow and holds no OAuth store. Naming a
|
|
12
|
+
credential on the route grafts an api-key method beside the provider's own,
|
|
13
|
+
and that credential is the seam this plugin fills: it keeps a live Codex
|
|
14
|
+
access token in the harness credential store, refreshing it from
|
|
15
|
+
`~/.codex/auth.json` before it expires.
|
|
16
|
+
|
|
17
|
+
The result needs no loopback port, no shared local key, and no external proxy
|
|
18
|
+
binary. The `codex` CLI still owns the login.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
~/.codex/auth.json ──(refresh + atomic write-back)──> gpt-sub ──> CODEX_NATIVE_TOKEN credential
|
|
22
|
+
│
|
|
23
|
+
DSH ──pi-ai openai-codex provider── Bearer <that token> ──> chatgpt.com/backend-api/codex/responses
|
|
24
|
+
through the process-global undici dispatcher, which
|
|
25
|
+
gpt-sub has scoped: chatgpt.com / auth.openai.com /
|
|
26
|
+
api.openai.com egress through the proxy with dropped-
|
|
27
|
+
connection retries; every other host is untouched
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Tokens come from `~/.codex/auth.json` — the same file the `codex` CLI uses. Refreshed
|
|
31
|
+
tokens are written back atomically (temp file + rename), so the CLI keeps working and never
|
|
32
|
+
observes a partial file.
|
|
33
|
+
|
|
34
|
+
## Setup
|
|
35
|
+
|
|
36
|
+
### 1. Mount the bundle
|
|
37
|
+
|
|
38
|
+
**Option A — install from npm (recommended).** `dsh plugin` installs the package
|
|
39
|
+
into the profile and mounts the bundled `cordis.patch.yml` automatically — no
|
|
40
|
+
manual yml editing:
|
|
41
|
+
|
|
42
|
+
```powershell
|
|
43
|
+
# Prereqs: Node ≥ 22.13 (the pnpm-managed toolchain needs 22.13+), dsh ≥ 0.1.1-rc.1 (ships the `dsh plugin` command),
|
|
44
|
+
# pnpm on PATH (corepack enable or npm i -g pnpm)
|
|
45
|
+
dsh plugin --profile web add @young1lin/dsh-gpt-sub
|
|
46
|
+
dsh web # restart the host (the host half is module code)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Remove it again with `dsh plugin --profile web remove @young1lin/dsh-gpt-sub`.
|
|
50
|
+
|
|
51
|
+
**Option B — link a checkout (development).** It is a profile bundle. Link it
|
|
52
|
+
into the profile and list it in `dsh.profile.bundles`:
|
|
53
|
+
|
|
54
|
+
```jsonc
|
|
55
|
+
// ~/.dsh/profiles/web/package.json
|
|
56
|
+
{
|
|
57
|
+
"dsh": {
|
|
58
|
+
"profile": {
|
|
59
|
+
"bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "@young1lin/dsh-gpt-sub"]
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@young1lin/dsh-gpt-sub": "link:<path-to-your-checkout>"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The bundle ships its own `cordis.patch.yml` with two rows:
|
|
69
|
+
|
|
70
|
+
- an **insert** for the `gpt-sub` plugin itself (token sync, proxy routing, quota panel), and
|
|
71
|
+
- an **edit** (no `insert:`) of the dormant `llm-pi-ai` row `@deepseek-ai/dsh-base` mounts,
|
|
72
|
+
seeding the provider route:
|
|
73
|
+
|
|
74
|
+
```yaml
|
|
75
|
+
- id: llm-pi-ai
|
|
76
|
+
name: '@deepseek-ai/dsh-llm-pi-ai'
|
|
77
|
+
config:
|
|
78
|
+
proxyUrl: http://127.0.0.1:7890
|
|
79
|
+
authFile: ~/.codex/auth.json
|
|
80
|
+
refreshMarginMinutes: 30
|
|
81
|
+
tokenRef: CODEX_NATIVE_TOKEN
|
|
82
|
+
syncIntervalMinutes: 10
|
|
83
|
+
bootstrapRetries: 3
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Naming `apiKeyEnv` on the route grafts an api-key auth method beside the catalog provider's
|
|
87
|
+
OAuth one — pi-ai's `openai-codex` would otherwise refuse an explicit key with
|
|
88
|
+
`Provider is not configured`. That credential reference is the token this plugin republishes,
|
|
89
|
+
so the two rows meet in the middle.
|
|
90
|
+
|
|
91
|
+
The edit row requires `@deepseek-ai/dsh-base` to come **earlier** in `dsh.profile.bundles`
|
|
92
|
+
(every standard profile does). A patch row for an id that does not exist is warned and
|
|
93
|
+
skipped, not an error.
|
|
94
|
+
|
|
95
|
+
The cordis layer applies at profile load, so a freshly installed bundle needs one restart of
|
|
96
|
+
`dsh web`; after that, user-settings changes hot-reload per request.
|
|
97
|
+
|
|
98
|
+
### 2. Sign in with the `codex` CLI
|
|
99
|
+
|
|
100
|
+
Run `codex` and complete the sign-in; that writes `~/.codex/auth.json`. This is the only
|
|
101
|
+
credential step — the plugin fails at start naming the path when the file is missing or
|
|
102
|
+
cannot serve tokens (`tokens.access_token`, `tokens.refresh_token`,
|
|
103
|
+
`tokens.account_id`), rather than leaving a route pointed at a credential nothing
|
|
104
|
+
maintains.
|
|
105
|
+
|
|
106
|
+
| Model | Context | Max output | Input | Thinking levels |
|
|
107
|
+
|---|---|---|---|---|
|
|
108
|
+
| `gpt-5.6-sol` | 272,000 | 128,000 | text, image | `minimal` (wire: `low`), `xhigh`, `max` |
|
|
109
|
+
| `gpt-5.6-terra` | 272,000 | 128,000 | text, image | `minimal` (wire: `low`), `xhigh`, `max` |
|
|
110
|
+
| `gpt-5.6-luna` | 272,000 | 128,000 | text, image | `minimal` (wire: `low`), `xhigh`, `max` |
|
|
111
|
+
|
|
112
|
+
The thinking level is per session, chosen in the model picker alongside the model.
|
|
113
|
+
|
|
114
|
+
To narrow or correct the catalog, write a user `llm-pi-ai:` settings section — it merges
|
|
115
|
+
per provider on top of the seeded route, so other providers declared there are additive:
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
# ~/.dsh/settings.yaml — keeps the credential, narrows the route to one model
|
|
119
|
+
llm-pi-ai:
|
|
120
|
+
providers:
|
|
121
|
+
gpt-sub:
|
|
122
|
+
displayName: CodexSubscription (direct)
|
|
123
|
+
api: openai-codex
|
|
124
|
+
apiKeyEnv: CODEX_NATIVE_TOKEN
|
|
125
|
+
models:
|
|
126
|
+
- id: gpt-5.6-sol
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`apiKeyEnv` **must equal the plugin's `tokenRef`** (default `CODEX_NATIVE_TOKEN`); both
|
|
130
|
+
sides name the same credential reference, so they always agree. You never store a value
|
|
131
|
+
under it yourself — this plugin is what keeps the reference populated, refreshing and
|
|
132
|
+
republishing the token on its own schedule. No `baseURL` is needed: the `openai-codex`
|
|
133
|
+
provider knows the Codex backend itself.
|
|
134
|
+
|
|
135
|
+
## Configuration reference
|
|
136
|
+
|
|
137
|
+
| Option | Default | Meaning |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| `proxyUrl` | `''` (direct) | Proxy for reaching the Codex hosts. The shipped `cordis.patch.yml` sets `http://127.0.0.1:7890`. Empty string means direct. |
|
|
140
|
+
| `authFile` | `~/.codex/auth.json` | Codex CLI credential file, shared with the `codex` command. |
|
|
141
|
+
| `tokenRef` | `CODEX_NATIVE_TOKEN` | Credential the seeded `openai-codex` route reads (`apiKeyEnv`). Must match the llm-pi-ai row. |
|
|
142
|
+
| `refreshMarginMinutes` | `30` | Refresh the access token when less than this many minutes remain. |
|
|
143
|
+
| `tokenRef` | `CODEX_NATIVE_TOKEN` | Credential the provider route names in `settings.yaml`; this plugin keeps it populated. |
|
|
144
|
+
| `syncIntervalMinutes` | `10` | How often to re-read auth.json and republish the token. |
|
|
145
|
+
| `bootstrapRetries` | `3` | Connection-level retries per request when the link to the proxy drops. |
|
|
146
|
+
| `stateFile` | `~/.dsh/gpt-sub.json` | Persisted panel overrides (`proxyUrl`, `authFile`); a field present in the file wins over the config's. |
|
|
147
|
+
|
|
148
|
+
## Why `bootstrapRetries` exists
|
|
149
|
+
|
|
150
|
+
This is not a generic retry knob, and it is not a nicety.
|
|
151
|
+
|
|
152
|
+
The machine this was written for reaches `chatgpt.com` only through a Clash proxy, and that
|
|
153
|
+
link drops connections at random. Measured direct to `chatgpt.com` through the same proxy,
|
|
154
|
+
bypassing any harness: 32KB failed twice, 64KB failed then succeeded, 96KB and 128KB
|
|
155
|
+
succeeded twice. Non-monotonic — so it is random TLS/connection failure, not a payload-size
|
|
156
|
+
threshold and not a protocol problem. DSH saw it as
|
|
157
|
+
`stream error: stream ID 1; PROTOCOL_ERROR; received from peer`, most visibly on a session's
|
|
158
|
+
first request.
|
|
159
|
+
|
|
160
|
+
The retry lives in undici's retry interceptor, composed onto the proxy agent and scoped to
|
|
161
|
+
the three Codex hosts, so no other provider's traffic changes behavior. It retries
|
|
162
|
+
connection-level failures only — `statusCodes` is deliberately empty, because an upstream
|
|
163
|
+
`429` or `500` carries a body DSH knows how to read and surface, and replaying it here
|
|
164
|
+
would swallow it. `POST` is listed even though undici omits it by default: a connection
|
|
165
|
+
error arrives before any response, and undici itself refuses to replay a request whose body
|
|
166
|
+
was already consumed, so a partially streamed response is never silently concatenated.
|
|
167
|
+
Model calls are POSTs, so without this the retry would never fire at all.
|
|
168
|
+
|
|
169
|
+
If your network is reliable, this setting costs nothing.
|
|
170
|
+
|
|
171
|
+
## Operations
|
|
172
|
+
|
|
173
|
+
**`refresh_token` rejected.** The refresh token is single-use and rotates on every refresh.
|
|
174
|
+
If it is rejected, the plugin fails with guidance to re-authenticate: run `codex` and sign in
|
|
175
|
+
again. That rewrites `~/.codex/auth.json`, which the plugin picks up.
|
|
176
|
+
|
|
177
|
+
**Upstream errors are never rewrapped.** An upstream `4xx`/`5xx` passes through with its
|
|
178
|
+
status and body unaltered — the routing layer retries no HTTP status, and nothing between
|
|
179
|
+
DSH and the backend rewrites an error. This is deliberate: a generic `500` hiding the real
|
|
180
|
+
cause once sent debugging down the wrong path for hours.
|
|
181
|
+
|
|
182
|
+
**Token publication.** The access token is republished to the credential on every sync tick
|
|
183
|
+
(default every 10 minutes) and immediately after a panel-side credential-file switch, so the
|
|
184
|
+
provider route never reads a stale token.
|
|
185
|
+
|
|
186
|
+
## Settings panel diagnostics
|
|
187
|
+
|
|
188
|
+
The `Codex 配额` settings section does more than render the usage bars. It shows
|
|
189
|
+
one bar per rate-limit window the account reports — the 5-hour rolling window
|
|
190
|
+
and the weekly window — each drawn Codex-style as the percent **remaining**: a
|
|
191
|
+
full bar at 100% that drains toward 0% as the allowance is spent, with its own
|
|
192
|
+
reset countdown. The countdown is exact, not rounded to one unit: `4h22m后重置`,
|
|
193
|
+
`10m22s后重置` under an hour (seconds only appear there, where they still change
|
|
194
|
+
fast enough to read), `2d5h后重置` past a day, and a zero component is dropped
|
|
195
|
+
rather than padded — `4h`, never `4h0m`. An account with no 5-hour limit (pro
|
|
196
|
+
accounts currently report only a weekly window) shows a note where that bar
|
|
197
|
+
would be, so the absence reads as a fact about the plan rather than a broken
|
|
198
|
+
panel. Beside the plan name the header carries the account's remaining
|
|
199
|
+
on-demand usage resets (upstream's `rate_limit_reset_credits.available_count`)
|
|
200
|
+
as a badge — `还有 2 次重置` — whenever the plan reports them; each reset clears
|
|
201
|
+
a capped window without waiting out its timer. Below the bars the panel shows:
|
|
202
|
+
|
|
203
|
+
- **Auth 来源** — the `authFile` the credentials come from, plus the access token's
|
|
204
|
+
remaining lifetime.
|
|
205
|
+
- **自动刷新** — when the next automatic refresh happens: the moment the token's
|
|
206
|
+
remaining life drops below `refreshMarginMinutes`, refreshed on the next read
|
|
207
|
+
(the `syncIntervalMinutes` timer or a model call, whichever comes first).
|
|
208
|
+
- **Auth 文件** — the credential file itself, editable inline. The host validates
|
|
209
|
+
a candidate before anything is persisted or applied (readable, parseable, and
|
|
210
|
+
carrying the three required token fields — the check spends no refresh);
|
|
211
|
+
**保存** then swaps the live token source immediately, republishes the access
|
|
212
|
+
token to the credential store, and persists the choice to `stateFile`. Saving
|
|
213
|
+
an empty value restores the cordis-configured default. **浏览** opens a small
|
|
214
|
+
folder picker backed by a read-only host listing — a browser's native dialog
|
|
215
|
+
cannot hand back absolute paths, so browsing runs through the host instead
|
|
216
|
+
(names and paths only, never file contents).
|
|
217
|
+
- **HTTP 代理** — the proxy currently routing `chatgpt.com` / `auth.openai.com` /
|
|
218
|
+
`api.openai.com`, editable inline. **测试连接** probes the candidate through a
|
|
219
|
+
one-off agent (one real usage read, 15s timeout) before you commit; **保存**
|
|
220
|
+
swaps the live routing immediately and persists the choice to `stateFile`, so
|
|
221
|
+
it survives restarts without touching cordis config layers. An empty value
|
|
222
|
+
means direct. The line under the input notes whether the current value comes
|
|
223
|
+
from cordis config or a page override.
|
|
224
|
+
|
|
225
|
+
Rate-limit reset credits (the codex CLI's `rate-limit-reset-credits` wham API): `GET /gpt-sub/reset-credits` lists the account's redeemable credits, `POST /gpt-sub/reset-credits/consume` (body `{ "creditId": "..." }`, optional) consumes one. The host mints a fresh `redeem_request_id` per click, and the panel asks for confirmation first because consuming is destructive.
|
|
226
|
+
|
|
227
|
+
Host endpoints behind the panel: `GET /gpt-sub/quota` (the bars), `GET /gpt-sub/status`,
|
|
228
|
+
`POST /gpt-sub/proxy` (`{ "proxyUrl": "..." }`), `POST /gpt-sub/proxy/test` (same body;
|
|
229
|
+
probes the value without applying it), `POST /gpt-sub/auth` (`{ "authFile": "..." }`; empty
|
|
230
|
+
clears the page override), and the read-only `GET /gpt-sub/auth/browse?path=...` directory
|
|
231
|
+
listing behind the file picker (empty path means the home directory).
|
|
232
|
+
|
|
233
|
+
## Known limitations
|
|
234
|
+
|
|
235
|
+
- **No model discovery.** Models are declared statically in `settings.yaml`; there is no
|
|
236
|
+
upstream catalog lookup in this plugin.
|
|
237
|
+
- **Single account.** No multi-account routing, usage/quota accounting, or management UI.
|
|
238
|
+
CLIProxyAPI has these; nothing here needs them.
|
|
239
|
+
- **The proxy routing is process-global.** pi-ai issues model calls through the global
|
|
240
|
+
`fetch`, so the plugin wraps the process-wide undici dispatcher — scoped by hostname, so
|
|
241
|
+
only the three Codex hosts change route. If the host or another plugin later installs its
|
|
242
|
+
own global dispatcher over this one, the routing here is silently bypassed: `chatgpt.com`
|
|
243
|
+
then goes direct and answers with a Cloudflare block page instead of an API error.
|
|
244
|
+
|
|
245
|
+
- **Composition order.** The seeded `llm-pi-ai` route edits dsh-base's row, so
|
|
246
|
+
`@deepseek-ai/dsh-base` must precede this bundle in `dsh.profile.bundles`.
|
|
247
|
+
|
|
248
|
+
## Development
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
pnpm build # tsc -> lib/ (src only)
|
|
252
|
+
pnpm typecheck # tsc -p tsconfig.test.json (src + tests, noEmit)
|
|
253
|
+
pnpm test # vitest
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`TokenStore`, `QuotaSource`, and `probeProxy` take injectable `fetchImpl` / `now` seams
|
|
257
|
+
and an injected dispatcher, so unit tests never touch the network; the plugin supplies the
|
|
258
|
+
real ones at startup.
|
|
259
|
+
|
|
260
|
+
## History
|
|
261
|
+
|
|
262
|
+
The first design ran an in-process loopback shim speaking `openai-responses`
|
|
263
|
+
(`docs/superpowers/specs/`); the credential-grafting design above replaced it, keeping
|
|
264
|
+
`TokenStore`'s refresh semantics unchanged. No user-visible configuration carried over
|
|
265
|
+
except the fields in the table above.
|
|
266
|
+
|
|
267
|
+
`tests/cordis-patch.spec.ts` holds the bundle patch and the plugin's `tokenRef`
|
|
268
|
+
default together, so the seeded route cannot drift from the credential it reads.
|