@pure01fx/dsh-openai-codex-auth 0.7.2 → 0.7.3
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 +11 -0
- package/README.md +3 -3
- package/client.js +119 -1
- package/lib/native-websocket-socket.d.ts +2 -0
- package/lib/native-websocket-socket.js +12 -0
- package/lib/native-websocket.js +7 -3
- package/package.json +10 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.7.3
|
|
6
|
+
|
|
7
|
+
### Client model visibility
|
|
8
|
+
|
|
9
|
+
- Connects the settings-panel Codex visibility controls to DSH's shared model directory: opening either model selector now caches the loaded Codex catalog, updates an already-open settings section, and filters hidden models in the current browser.
|
|
10
|
+
|
|
11
|
+
### WebSocket reliability
|
|
12
|
+
|
|
13
|
+
- Keeps credential-backed WebSocket connection failures inside the bounded reconnect budget and falls back to HTTP/SSE, preventing turns from remaining on “Deep diving...” forever when a server can reach Codex over HTTPS but not WebSocket.
|
|
14
|
+
- Routes WebSocket v2 through the same opt-in `NODE_USE_ENV_PROXY=1`, `HTTPS_PROXY` / `HTTP_PROXY`, and `NO_PROXY` contract as Node fetch, using an HTTP CONNECT tunnel for WSS endpoints.
|
|
15
|
+
|
|
5
16
|
## 0.7.2
|
|
6
17
|
|
|
7
18
|
### Auxiliary-call compatibility
|
package/README.md
CHANGED
|
@@ -189,7 +189,7 @@ $DSH_HOME/openai-codex-auth.json
|
|
|
189
189
|
|
|
190
190
|
上游 Native Codex Responses 请求没有输出 token 上限字段,因此普通会话若显式设置 `maxTokens` 仍会在 Provider I/O 前失败。DSH 的压缩与会话标题辅助调用会固定携带 `maxTokens`,并分别通过 `purpose: compaction` 与 `purpose: session-title` 标识;本适配器允许这两类 hint 通过,但不会把它序列化为 `max_output_tokens`,实际输出长度仍由 Native Codex 决定。
|
|
191
191
|
|
|
192
|
-
原生 route 默认使用 Responses WebSocket v2,在会话首个请求上执行 `generate: false` prewarm,并仅在请求历史严格延伸时发送 `previous_response_id`
|
|
192
|
+
原生 route 默认使用 Responses WebSocket v2,在会话首个请求上执行 `generate: false` prewarm,并仅在请求历史严格延伸时发送 `previous_response_id` 与增量后缀。连接重建会清除增量链;凭据解析完成前的网络失败不消耗普通 stream retry 预算,而是按 Codex 的网络恢复策略从 5 秒指数退避到 60 秒并持续等待成功或取消。凭据可用后,WebSocket 连接建立失败与连接已经建立后的首个 DSH chunk 前错误都使用默认 5 次 stream retry 与 200ms 起步的有界指数退避;安全重试耗尽或握手返回 HTTP 426 后,该 DSH 会话会确定性地回退到 HTTP/SSE,因此仅 WebSocket 不可达不会让界面无限停在 Deep diving。transport 层在任何 DSH chunk 已输出后仍不会直接重放原始 stream;它会把瞬态中断重新分类为只允许 durable failed-step 恢复的错误,标准 Agent Profile 中的 `dsh-llm-retry` 再按默认 5 次策略重建请求。该外层策略不会再次匹配 transport 已在首个 chunk 前耗尽的有限 stream 错误,避免两层预算相乘;失败 attempt 的原始事件可以留作非 surface 诊断,但不会组装成模型可见的持久 assistant message。未加载该恢复插件的直接 `ctx.llm.stream()` 调用在首个 chunk 后仍保持 single-attempt,避免重复输出。WebSocket `codex.rate_limits` 事件、握手/错误 metadata 以及 HTTP/SSE response headers 中的 `x-codex-*` quota 会被边界校验后直接写入 Host 用量缓存;额外 metered limit 不会覆盖默认 `codex` 套餐卡片。`response.completed.usage_metadata.amount` 会按原始高精度字符串保留,并作为当前账号的最近响应观测出现在 status JSON 中。
|
|
193
193
|
|
|
194
194
|
`<base>-fast` 只是公开选择别名:wire model 仍是 `<base>`,请求携带 `service_tier: priority`。若账号目录不声明 priority 能力,或请求前账号 authority 已改变,Fast 会直接失败,不会静默降级。Reasoning 选择在 adapter 边界按上游规则转换:`persistent` 在线上请求中写为 `disabled`;`ultra` 优先使用 Catalog 的 `multi_agent_reasoning_effort`,否则回退到 `max`、最高非 Ultra 档或 `medium`。
|
|
195
195
|
|
|
@@ -221,7 +221,7 @@ $DSH_HOME/openai-codex-auth.json
|
|
|
221
221
|
|
|
222
222
|
## 代理
|
|
223
223
|
|
|
224
|
-
|
|
224
|
+
插件的 HTTP/SSE 请求使用 Node.js 原生 `fetch`,WebSocket v2 使用 `ws`。设置 `NODE_USE_ENV_PROXY=1` 后,两条链路都会按 `HTTPS_PROXY` / `HTTP_PROXY` 选择 HTTP CONNECT 代理并遵守 `NO_PROXY`;若访问 OpenAI 需要代理,请在启动 DSH 前设置:
|
|
225
225
|
|
|
226
226
|
```sh
|
|
227
227
|
NODE_USE_ENV_PROXY=1 \
|
|
@@ -230,7 +230,7 @@ HTTPS_PROXY=http://127.0.0.1:7890 \
|
|
|
230
230
|
dsh web
|
|
231
231
|
```
|
|
232
232
|
|
|
233
|
-
可按环境补充 `NO_PROXY=localhost,127.0.0.1,::1
|
|
233
|
+
可按环境补充 `NO_PROXY=localhost,127.0.0.1,::1`。对于 `https://` / `wss://` Codex 端点应设置 `HTTPS_PROXY`;代理地址本身可以是 `http://`,WebSocket 会通过该代理向 `chatgpt.com:443` 建立 CONNECT 隧道。
|
|
234
234
|
|
|
235
235
|
## 常见问题
|
|
236
236
|
|
package/client.js
CHANGED
|
@@ -49,6 +49,121 @@ window.__ModuleLoader__.load({
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function writeAvailableModels(models) {
|
|
53
|
+
try {
|
|
54
|
+
const next = JSON.stringify(models)
|
|
55
|
+
if (window.localStorage.getItem(AVAILABLE_MODELS_KEY) === next) return
|
|
56
|
+
window.localStorage.setItem(AVAILABLE_MODELS_KEY, next)
|
|
57
|
+
window.dispatchEvent(new CustomEvent(MODEL_CATALOG_EVENT, { detail: models }))
|
|
58
|
+
} catch {
|
|
59
|
+
// Browser-local catalog persistence is advisory and must never break model loading.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function codexModels(groups) {
|
|
64
|
+
const models = []
|
|
65
|
+
const seen = new Set()
|
|
66
|
+
for (const group of Array.isArray(groups) ? groups : []) {
|
|
67
|
+
if (!group || (group.id !== 'openai-codex' && group.id !== 'openai-codex-native')) continue
|
|
68
|
+
for (const model of Array.isArray(group.models) ? group.models : []) {
|
|
69
|
+
if (!model || typeof model.id !== 'string' || model.id.length === 0
|
|
70
|
+
|| typeof model.name !== 'string' || model.name.length === 0 || seen.has(model.id)) continue
|
|
71
|
+
seen.add(model.id)
|
|
72
|
+
models.push({ id: model.id, name: model.name })
|
|
73
|
+
if (models.length >= 512) return models
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return models
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function visibleModelGroups(groups) {
|
|
80
|
+
if (!Array.isArray(groups)) return []
|
|
81
|
+
const hidden = new Set(readModelVisibility().hidden)
|
|
82
|
+
if (hidden.size === 0) return groups
|
|
83
|
+
return groups.flatMap((group) => {
|
|
84
|
+
if (!group || (group.id !== 'openai-codex' && group.id !== 'openai-codex-native')) return [group]
|
|
85
|
+
const models = Array.isArray(group.models)
|
|
86
|
+
? group.models.filter((model) => model && !hidden.has(model.id)) : []
|
|
87
|
+
return [{ ...group, models }]
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function installModelVisibilityBridge(ctx) {
|
|
92
|
+
const resolver = ctx.modelDirectories
|
|
93
|
+
if (!resolver || typeof resolver.directoryFor !== 'function') return () => {}
|
|
94
|
+
const originalDirectoryFor = resolver.directoryFor
|
|
95
|
+
const tracked = new Map()
|
|
96
|
+
|
|
97
|
+
const render = (directory, rawGroups) => {
|
|
98
|
+
const available = codexModels(rawGroups)
|
|
99
|
+
if (available.length > 0) writeAvailableModels(available)
|
|
100
|
+
const groups = visibleModelGroups(rawGroups)
|
|
101
|
+
if (directory.store && typeof directory.store.update === 'function') {
|
|
102
|
+
directory.store.update((state) => { state.groups = groups })
|
|
103
|
+
}
|
|
104
|
+
return groups
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const track = (directory) => {
|
|
108
|
+
if (!directory || tracked.has(directory)
|
|
109
|
+
|| typeof directory.load !== 'function' || typeof directory.dispose !== 'function') return directory
|
|
110
|
+
const record = {
|
|
111
|
+
load: directory.load,
|
|
112
|
+
dispose: directory.dispose,
|
|
113
|
+
wrappedLoad: undefined,
|
|
114
|
+
wrappedDispose: undefined,
|
|
115
|
+
generation: 0,
|
|
116
|
+
rawGroups: directory.store && typeof directory.store.getSnapshot === 'function'
|
|
117
|
+
? directory.store.getSnapshot().groups : [],
|
|
118
|
+
}
|
|
119
|
+
record.wrappedLoad = async function (...args) {
|
|
120
|
+
const generation = ++record.generation
|
|
121
|
+
const value = await record.load.apply(this, args)
|
|
122
|
+
const rawGroups = Array.isArray(value && value.groups) ? value.groups : []
|
|
123
|
+
if (generation !== record.generation) {
|
|
124
|
+
const groups = visibleModelGroups(rawGroups)
|
|
125
|
+
return value && typeof value === 'object' ? { ...value, groups } : value
|
|
126
|
+
}
|
|
127
|
+
record.rawGroups = rawGroups
|
|
128
|
+
const groups = render(directory, record.rawGroups)
|
|
129
|
+
return value && typeof value === 'object' ? { ...value, groups } : value
|
|
130
|
+
}
|
|
131
|
+
record.wrappedDispose = function (...args) {
|
|
132
|
+
++record.generation
|
|
133
|
+
tracked.delete(directory)
|
|
134
|
+
return record.dispose.apply(this, args)
|
|
135
|
+
}
|
|
136
|
+
directory.load = record.wrappedLoad
|
|
137
|
+
directory.dispose = record.wrappedDispose
|
|
138
|
+
tracked.set(directory, record)
|
|
139
|
+
if (record.rawGroups.length > 0) render(directory, record.rawGroups)
|
|
140
|
+
return directory
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const bridgedDirectoryFor = function (...args) {
|
|
144
|
+
return track(originalDirectoryFor.apply(this, args))
|
|
145
|
+
}
|
|
146
|
+
resolver.directoryFor = bridgedDirectoryFor
|
|
147
|
+
if (resolver.live && resolver.live.directories && typeof resolver.live.directories.values === 'function') {
|
|
148
|
+
for (const directory of resolver.live.directories.values()) track(directory)
|
|
149
|
+
}
|
|
150
|
+
const refresh = () => {
|
|
151
|
+
for (const [directory, record] of tracked) render(directory, record.rawGroups)
|
|
152
|
+
}
|
|
153
|
+
window.addEventListener(MODEL_VISIBILITY_EVENT, refresh)
|
|
154
|
+
|
|
155
|
+
return () => {
|
|
156
|
+
window.removeEventListener(MODEL_VISIBILITY_EVENT, refresh)
|
|
157
|
+
if (resolver.directoryFor === bridgedDirectoryFor) resolver.directoryFor = originalDirectoryFor
|
|
158
|
+
for (const [directory, record] of tracked) {
|
|
159
|
+
++record.generation
|
|
160
|
+
if (directory.load === record.wrappedLoad) directory.load = record.load
|
|
161
|
+
if (directory.dispose === record.wrappedDispose) directory.dispose = record.dispose
|
|
162
|
+
}
|
|
163
|
+
tracked.clear()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
52
167
|
const css = `
|
|
53
168
|
.codexSection{box-sizing:border-box;max-width:720px;color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;gap:12px}
|
|
54
169
|
.codexSection *{box-sizing:border-box}.codexTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}
|
|
@@ -748,8 +863,11 @@ window.__ModuleLoader__.load({
|
|
|
748
863
|
)
|
|
749
864
|
}
|
|
750
865
|
|
|
751
|
-
const inject = ['slots']
|
|
866
|
+
const inject = ['slots', 'modelDirectories']
|
|
752
867
|
function apply(ctx) {
|
|
868
|
+
ctx.inject(['modelDirectories'], (scope) => {
|
|
869
|
+
scope.effect(() => installModelVisibilityBridge(scope), 'openai-codex-auth: model visibility bridge')
|
|
870
|
+
})
|
|
753
871
|
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
754
872
|
name: 'settings.section',
|
|
755
873
|
id: 'openai-codex',
|
|
@@ -22,6 +22,8 @@ export interface NativeCodexWebSocketConnectOptions {
|
|
|
22
22
|
export interface NativeCodexWebSocketFactory {
|
|
23
23
|
connect(options: NativeCodexWebSocketConnectOptions): Promise<NativeCodexWebSocket>;
|
|
24
24
|
}
|
|
25
|
+
/** Resolve the same opt-in environment proxy contract used by Node fetch. */
|
|
26
|
+
export declare function nativeCodexWebSocketProxy(endpoint: string): string | undefined;
|
|
25
27
|
export declare function nativeCodexWebSocketUrl(endpoint: string): string;
|
|
26
28
|
export declare class NodeNativeCodexWebSocketFactory implements NativeCodexWebSocketFactory {
|
|
27
29
|
connect(options: NativeCodexWebSocketConnectOptions): Promise<NativeCodexWebSocket>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Bounded Node WebSocket client seam with injectable deterministic factories. */
|
|
2
2
|
import { LlmError, ProviderRequestId } from '@deepseek-ai/dsh-llm';
|
|
3
|
+
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
4
|
+
import { getProxyForUrl } from 'proxy-from-env';
|
|
3
5
|
import { nativeCodexEndpoint } from './endpoint.js';
|
|
4
6
|
import { NATIVE_CODEX_CONNECTION_FAILED_CODE, isNativeCodexConnectionFailure, } from './native-adapter.js';
|
|
5
7
|
import WebSocket from 'ws';
|
|
@@ -34,6 +36,13 @@ function positive(value, fallback, label) {
|
|
|
34
36
|
throw failure(`native Codex ${label} is invalid`, 'INVALID_ARGS');
|
|
35
37
|
return value;
|
|
36
38
|
}
|
|
39
|
+
/** Resolve the same opt-in environment proxy contract used by Node fetch. */
|
|
40
|
+
export function nativeCodexWebSocketProxy(endpoint) {
|
|
41
|
+
if (process.env.NODE_USE_ENV_PROXY !== '1')
|
|
42
|
+
return undefined;
|
|
43
|
+
const proxy = getProxyForUrl(nativeCodexEndpoint(endpoint, true).toString());
|
|
44
|
+
return proxy.length === 0 ? undefined : proxy;
|
|
45
|
+
}
|
|
37
46
|
export function nativeCodexWebSocketUrl(endpoint) {
|
|
38
47
|
const url = nativeCodexEndpoint(endpoint, true);
|
|
39
48
|
if (url.protocol === 'https:')
|
|
@@ -165,8 +174,11 @@ export class NodeNativeCodexWebSocketFactory {
|
|
|
165
174
|
throw abortFailure(options.signal);
|
|
166
175
|
return new Promise((resolve, reject) => {
|
|
167
176
|
const responseHeaders = {};
|
|
177
|
+
const proxy = nativeCodexWebSocketProxy(options.url);
|
|
178
|
+
const agent = proxy === undefined ? undefined : new HttpsProxyAgent(proxy);
|
|
168
179
|
const socket = new WebSocket(nativeCodexWebSocketUrl(options.url), {
|
|
169
180
|
headers: options.headers,
|
|
181
|
+
...(agent === undefined ? {} : { agent }),
|
|
170
182
|
handshakeTimeout: timeout,
|
|
171
183
|
maxPayload: maximum,
|
|
172
184
|
perMessageDeflate: true,
|
package/lib/native-websocket.js
CHANGED
|
@@ -29,6 +29,7 @@ function reconnectable(code) {
|
|
|
29
29
|
return [
|
|
30
30
|
'WS_RETRYABLE', 'WS_RETRYABLE_RESET', 'WS_PROTOCOL_ERROR',
|
|
31
31
|
'WS_FRAME_TOO_LARGE', 'WS_RESPONSE_TOO_LARGE', 'TIMEOUT',
|
|
32
|
+
NATIVE_CODEX_CONNECTION_FAILED_CODE,
|
|
32
33
|
].includes(code);
|
|
33
34
|
}
|
|
34
35
|
function failedStepRetryable(code) {
|
|
@@ -538,9 +539,12 @@ export class NativeCodexWebSocketTransport {
|
|
|
538
539
|
throw failedStepRetry(failureValue);
|
|
539
540
|
throw failureValue;
|
|
540
541
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
542
|
+
// Credential resolution may need the network itself, so wait until it recovers.
|
|
543
|
+
// Once a credential is available, however, a WebSocket-only outage must consume
|
|
544
|
+
// the bounded reconnect budget and then fall back to HTTP/SSE instead of leaving
|
|
545
|
+
// the DSH turn in an invisible infinite reconnect loop.
|
|
546
|
+
if (attemptedCredential === undefined
|
|
547
|
+
&& isNativeCodexConnectionFailure(failureValue)) {
|
|
544
548
|
await this.waitForConnection(connectionRetryDelayMs, signal);
|
|
545
549
|
connectionRetryDelayMs = Math.min(connectionRetryDelayMs * 2, MAX_CONNECTION_RETRY_DELAY_MS);
|
|
546
550
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pure01fx/dsh-openai-codex-auth",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "Native ChatGPT Codex provider, device-code-first login, and same-origin Web integration for DeepSeek Harness",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -69,11 +69,6 @@
|
|
|
69
69
|
"CHANGELOG.md",
|
|
70
70
|
"LICENSE"
|
|
71
71
|
],
|
|
72
|
-
"scripts": {
|
|
73
|
-
"build": "tsc",
|
|
74
|
-
"test": "vitest run",
|
|
75
|
-
"prepack": "pnpm build"
|
|
76
|
-
},
|
|
77
72
|
"dsh": {
|
|
78
73
|
"engines": {
|
|
79
74
|
"dsh": "0.1.1-rc.2"
|
|
@@ -84,6 +79,7 @@
|
|
|
84
79
|
"client": {
|
|
85
80
|
"inject": [
|
|
86
81
|
"@deepseek-ai/dsh-client-runtime",
|
|
82
|
+
"@deepseek-ai/dsh-client-ui-model-selection",
|
|
87
83
|
"@deepseek-ai/dsh-client-ui-conversation",
|
|
88
84
|
"@deepseek-ai/dsh-client-ui-settings"
|
|
89
85
|
],
|
|
@@ -100,6 +96,8 @@
|
|
|
100
96
|
"@deepseek-ai/dsh-atomic-write": "0.1.1-rc.2",
|
|
101
97
|
"@deepseek-ai/dsh-home-paths": "0.1.1-rc.2",
|
|
102
98
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
99
|
+
"https-proxy-agent": "7.0.6",
|
|
100
|
+
"proxy-from-env": "1.1.0",
|
|
103
101
|
"ws": "8.21.3"
|
|
104
102
|
},
|
|
105
103
|
"devDependencies": {
|
|
@@ -110,7 +108,12 @@
|
|
|
110
108
|
"@deepseek-ai/dsh-llm": "0.1.1-rc.2",
|
|
111
109
|
"@deepseek-ai/dsh-session": "0.1.1-rc.2",
|
|
112
110
|
"@types/node": "^22.20.0",
|
|
111
|
+
"@types/proxy-from-env": "1.0.4",
|
|
113
112
|
"typescript": "^6.0.3",
|
|
114
113
|
"vitest": "^4.1.8"
|
|
114
|
+
},
|
|
115
|
+
"scripts": {
|
|
116
|
+
"build": "tsc",
|
|
117
|
+
"test": "vitest run"
|
|
115
118
|
}
|
|
116
|
-
}
|
|
119
|
+
}
|