@pure01fx/dsh-openai-codex-auth 0.5.0 → 0.6.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 +42 -0
- package/README.md +33 -5
- package/client.js +186 -16
- package/cordis.patch.yml +2 -6
- package/lib/catalog.d.ts +77 -0
- package/lib/catalog.js +383 -0
- package/lib/endpoint.d.ts +2 -0
- package/lib/endpoint.js +38 -0
- package/lib/index.d.ts +63 -17
- package/lib/index.js +535 -130
- package/lib/native-adapter.d.ts +43 -0
- package/lib/native-adapter.js +246 -0
- package/lib/native-http.d.ts +61 -0
- package/lib/native-http.js +602 -0
- package/lib/native-websocket-session.d.ts +14 -0
- package/lib/native-websocket-session.js +109 -0
- package/lib/native-websocket-socket.d.ts +28 -0
- package/lib/native-websocket-socket.js +215 -0
- package/lib/native-websocket.d.ts +45 -0
- package/lib/native-websocket.js +615 -0
- package/lib/rate-limits.d.ts +35 -0
- package/lib/rate-limits.js +189 -0
- package/lib/replay.d.ts +49 -0
- package/lib/replay.js +240 -0
- package/lib/response-usage.d.ts +14 -0
- package/lib/response-usage.js +35 -0
- package/lib/responses.d.ts +126 -0
- package/lib/responses.js +572 -0
- package/lib/sse.d.ts +13 -0
- package/lib/sse.js +81 -0
- package/lib/upstream.d.ts +6 -0
- package/lib/upstream.js +6 -0
- package/lib/usage.d.ts +29 -0
- package/lib/usage.js +160 -0
- package/package.json +47 -11
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.6.0
|
|
6
|
+
|
|
7
|
+
### Native Codex transport
|
|
8
|
+
|
|
9
|
+
- Adds package-owned ChatGPT Codex Responses HTTP/SSE and WebSocket v2 transports for DSH 0.1.0-rc.6.
|
|
10
|
+
- Supports text, tools, images, reasoning effort, usage, cancellation, bounded retries, replay state, stable routing identity, prewarm, incremental suffixes, reconnect, and safe pre-output HTTP fallback.
|
|
11
|
+
- Exposes capability-gated `<base>-fast` aliases while sending the base wire model with `service_tier: priority`; unsupported Fast requests fail without downgrade.
|
|
12
|
+
- Centralizes the tracked OpenAI Codex repository revision, release, and Catalog client version in one source; updates Catalog requests to the tracked release.
|
|
13
|
+
- Matches current Codex reasoning wire semantics by mapping Persistent to `disabled` and Ultra through model-aware fallbacks.
|
|
14
|
+
|
|
15
|
+
### Reliability and replay
|
|
16
|
+
|
|
17
|
+
- Keeps DSH subagent settlement histories usable by dropping relayed non-assistant reasoning and child-owned tool calls at the native adapter boundary while preserving surrounding text, strict validation for ordinary user messages, and assistant native replay.
|
|
18
|
+
- Waits through pure HTTP/WebSocket connection outages without consuming the five stream retries, using cancellable 5-second exponential delays capped at 60 seconds until the network recovers.
|
|
19
|
+
- Matches Codex's default five dropped-stream retries: WebSocket reconnects use bounded exponential backoff and provider delays, while retryable failures after visible output recover at DSH's durable failed-step boundary without promoting failed chunks into assistant history or duplicating the pre-output retry budget.
|
|
20
|
+
|
|
21
|
+
### Route migration
|
|
22
|
+
|
|
23
|
+
- Makes the native adapter the default owner of `openai-codex`; auth-only integrations may explicitly set `nativeAdapter: false`, while the legacy `openai-codex-native` route remains opt-in.
|
|
24
|
+
- Keeps profile-specific provider removal and migration out of this package; integration bundles must release any existing `openai-codex` route before mounting it.
|
|
25
|
+
- Existing rc.6 pi-ai replay degrades to durable visible history when a production session first continues on the native adapter.
|
|
26
|
+
- Reverse rollback to pi-ai does not guarantee continuation of sessions that already contain native replay; start a new session or continue with a native-enabled integration.
|
|
27
|
+
- Shows the Host-observed `openai-codex` route state in the settings UI, including Native transport mode, external ownership, compatibility-route activity, and unregistered failures without guessing an external adapter identity.
|
|
28
|
+
|
|
29
|
+
### Usage and Web UI
|
|
30
|
+
|
|
31
|
+
- Preserves exact `response.completed.usage_metadata.amount` strings across HTTP/SSE and WebSocket and exposes the latest account-scoped observation in status.
|
|
32
|
+
- Parses bounded subscription quota updates directly from WebSocket `codex.rate_limits` events and HTTP/wrapped-WebSocket `x-codex-*` headers, updates the settings card immediately, and skips the redundant turn-end `wham/usage` request while retaining explicit and missing-update fallback refreshes.
|
|
33
|
+
- Hides incomplete weekly quota placeholders when they report 0% usage without a reset timestamp, in both the conversation quota panel and settings card.
|
|
34
|
+
- Shows the conversation quota panel on hover or keyboard focus and reserves clicking the quota ring for an explicit refresh.
|
|
35
|
+
- Adds browser-local Codex model visibility checkboxes that immediately filter the model selector without changing Host routing or existing sessions.
|
|
36
|
+
|
|
37
|
+
### Security and packaging
|
|
38
|
+
|
|
39
|
+
- Rejects redirects for credential-bearing OAuth, Catalog, Responses, and subscription-usage requests.
|
|
40
|
+
- Keeps OAuth credentials and account authority host-side, validates credential-bearing endpoint authorities, bounds request/response/replay/WebSocket state, and refuses managed/external credential mismatches.
|
|
41
|
+
- Pins Host/runtime contracts to DSH rc.6 and includes synchronized JavaScript/declaration artifacts plus the complete native transport modules.
|
|
42
|
+
- Keeps the package name `@pure01fx/dsh-openai-codex-auth`; a rename is intentionally deferred.
|
package/README.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<strong>把 ChatGPT
|
|
7
|
-
设备码优先、浏览器 OAuth
|
|
6
|
+
<strong>把 ChatGPT 订阅的 Native Codex Provider 接入 DeepSeek Harness。</strong><br>
|
|
7
|
+
设备码优先、浏览器 OAuth 备用;默认注册 <code>openai-codex</code>,在 DSH 设置页完成登录、查看用量并直接运行 Codex Responses。
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
10
|
<p align="center">
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
dsh plugin --profile web add @pure01fx/dsh-openai-codex-auth
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
Native Adapter 默认持有 `openai-codex`。如果现有整合包仍把该 route 配给 `llm-pi-ai` 或其他 Adapter,应由整合包先释放该 route;不要同时挂载两个 owner。只需要本插件的 OAuth/UI 时可显式设置 `nativeAdapter: false`。
|
|
27
|
+
|
|
26
28
|
开发 checkout 也可直接使用本地路径:
|
|
27
29
|
|
|
28
30
|
```sh
|
|
@@ -78,8 +80,10 @@ http://localhost:1455/auth/callback
|
|
|
78
80
|
| --- | --- |
|
|
79
81
|
| 设备码登录 | 无本机 callback,支持远程/headless 使用 |
|
|
80
82
|
| 浏览器 OAuth fallback | PKCE + state、端到端 1455 探测、手动 code 回填和临时 callback listener |
|
|
81
|
-
| Codex 用量面板 |
|
|
82
|
-
|
|
|
83
|
+
| Codex 用量面板 | 展示短周期与周用量、剩余额度和重置时间;Native 模式直接接收 Codex 返回的套餐额度,`wham/usage` 仅作初始读取和 fallback |
|
|
84
|
+
| 当前路由状态 | 设置页显示 `openai-codex` 当前由本插件 Native Adapter、外部 Adapter 或无人持有;Native 模式同时显示 WebSocket v2 或 HTTP/SSE |
|
|
85
|
+
| 输入框额度圈 | 登录后显示在输入框右下角;悬停或键盘聚焦显示用量,点击圆圈强制刷新,不做常驻额度轮询 |
|
|
86
|
+
| 模型列表隐藏 | 在 Codex 设置页勾选不想看到的模型;只保存在当前浏览器并立即过滤模型选择器,不影响已有会话或 Host 路由 |
|
|
83
87
|
| 自动凭据续期 | 在令牌接近过期时刷新,并更新 DSH credentials |
|
|
84
88
|
| DSH 模型接入 | 将有效令牌提供给 `openai-codex` 模型提供方 |
|
|
85
89
|
| 登录生命周期管理 | 支持取消、15 分钟设备码超时、10 分钟浏览器超时和安全退出 |
|
|
@@ -96,7 +100,7 @@ http://localhost:1455/auth/callback
|
|
|
96
100
|
1. 凭据以 owner-only 权限原子写入 `$DSH_HOME/openai-codex-auth.json`。
|
|
97
101
|
2. access token 通过 DSH credentials 注入 `DSH_OPENAI_CODEX_TOKEN`。
|
|
98
102
|
3. `openai-codex` 模型提供方在请求时读取该凭据。
|
|
99
|
-
4. 输入框右下角显示 Codex
|
|
103
|
+
4. 输入框右下角显示 Codex 额度圈;悬停或键盘聚焦显示当前用量,点击才强制刷新。Native transport 优先消费 WebSocket `codex.rate_limits` 事件或 HTTP `x-codex-*` headers,未收到直接更新时才在 turn 结束后读取 `wham/usage`。
|
|
100
104
|
5. 设置页与额度圈只读取登录状态、账号 ID、过期时间和用量摘要,不接触 token。
|
|
101
105
|
|
|
102
106
|
## 同源路由
|
|
@@ -173,6 +177,29 @@ $DSH_HOME/openai-codex-auth.json
|
|
|
173
177
|
|
|
174
178
|
`path` 的优先级高于 `dshHome`。
|
|
175
179
|
|
|
180
|
+
### 原生 Codex transport 与组合边界
|
|
181
|
+
|
|
182
|
+
本包跟踪的上游 Codex repository、commit、release 与 Catalog client version 只在 `src/upstream.ts` 维护;升级上游时先更新该文件,再刷新 fixtures、生成产物和兼容测试,避免多个散落 hash 漂移。
|
|
183
|
+
|
|
184
|
+
从 0.6 开始,Native Adapter 是插件默认 Provider,并持有生产 route `openai-codex`。旧预览会话确实需要时,可显式设置 `nativeCompatibilityRoute: true`,让同一个 adapter 同时注册 `openai-codex-native` 兼容 route;启用兼容 route 时两者仍原子注册,任一 route 已被占用都会整体失败。默认只暴露生产 route。
|
|
185
|
+
|
|
186
|
+
本包只定义 Codex Provider,不发布或改写任何具体 Profile。整合包必须在挂载本插件前释放已有的 `openai-codex` route,例如从自己的 `llm-pi-ai` provider map 中移除该项;本包不会清空未知的 sibling providers。只想复用 OAuth/UI 并继续使用外部 Provider 的组合可以显式设置 `nativeAdapter: false`。从 pi-ai 切到 Native 时,旧 foreign replay 会降级为可见持久历史;反向切回 pi-ai 后,已产生 Native replay 的进行中 session 不保证可续跑,应新建 session。
|
|
187
|
+
|
|
188
|
+
原生 route 默认使用 Responses WebSocket v2,在会话首个请求上执行 `generate: false` prewarm,并仅在请求历史严格延伸时发送 `previous_response_id` 与增量后缀。连接重建会清除增量链;纯 WebSocket/HTTP 连接建立失败不消耗普通 stream retry 预算,而是按 Codex 的网络恢复策略从 5 秒指数退避到 60 秒并持续等待成功或取消,因此可跨越半分钟断网。连接已经建立后的首个 DSH chunk 前错误仍使用默认 5 次 stream retry 与 200ms 起步的有界指数退避,安全重试耗尽或握手返回 HTTP 426 后,该 DSH 会话会确定性地回退到 HTTP/SSE。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 中。
|
|
189
|
+
|
|
190
|
+
`<base>-fast` 只是公开选择别名:wire model 仍是 `<base>`,请求携带 `service_tier: priority`。若账号目录不声明 priority 能力,或请求前账号 authority 已改变,Fast 会直接失败,不会静默降级。Reasoning 选择在 adapter 边界按上游规则转换:`persistent` 在线上请求中写为 `disabled`;`ultra` 优先使用 Catalog 的 `multi_agent_reasoning_effort`,否则回退到 `max`、最高非 Ultra 档或 `medium`。
|
|
191
|
+
|
|
192
|
+
```yaml
|
|
193
|
+
- insert:
|
|
194
|
+
- id: openai-codex-auth
|
|
195
|
+
name: '@pure01fx/dsh-openai-codex-auth'
|
|
196
|
+
config:
|
|
197
|
+
nativeAdapter: true # 默认值;只使用外部 Provider 时显式设为 false
|
|
198
|
+
nativeCompatibilityRoute: false # 默认值;仅续跑旧预览 session 时设为 true
|
|
199
|
+
nativeWebSocket: true # 默认值;设为 false 可强制使用 HTTP/SSE
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
|
|
176
203
|
## 凭据处理边界
|
|
177
204
|
|
|
178
205
|
以下内容说明插件如何处理本地凭据与管理接口,不代表或承诺任何 OpenAI 账号风控结果。
|
|
@@ -181,6 +208,7 @@ $DSH_HOME/openai-codex-auth.json
|
|
|
181
208
|
- 设备码由 Host 轮询;Web 页面只看到用户码、验证网址和过期时间。
|
|
182
209
|
- 凭据目录与文件分别以 owner-only 权限创建,并通过文件锁和原子写入更新。
|
|
183
210
|
- access token 与 refresh token 只保存在 Host 侧;Web 页面不会读取或保存它们。
|
|
211
|
+
- OAuth、Catalog、Responses 与用量请求均拒绝 HTTP redirect,避免 credential-bearing 请求进入重定向链。
|
|
184
212
|
- 管理路由复用 DSH `trustedHosts`,并检查 Host、Origin、Fetch Metadata 和 CSRF。
|
|
185
213
|
- 临时 callback Server 只绑定 IPv4/IPv6 loopback 的 1455,并只接受注册 URI 对应的 `Host: localhost:1455`、`/auth/callback` 路径和匹配的 state。
|
|
186
214
|
- 浏览器连通性探测使用 flow 专属随机 URL,只向 HTTP `127.0.0.1`/`localhost` Origin 返回 CORS 结果;手动 code 提交受同源管理检查和 CSRF 保护,粘贴完整 URL 时还会校验 state。
|
package/client.js
CHANGED
|
@@ -10,9 +10,45 @@ window.__ModuleLoader__.load({
|
|
|
10
10
|
const BASE = '/openai-codex'
|
|
11
11
|
const PLUGIN_ID = '@pure01fx/dsh-openai-codex-auth'
|
|
12
12
|
const statusSubscribers = new Set()
|
|
13
|
+
const HIDDEN_MODELS_KEY = 'dsh.openai-codex.hidden-models.v1'
|
|
14
|
+
const AVAILABLE_MODELS_KEY = 'dsh.openai-codex.available-models.v1'
|
|
15
|
+
const MODEL_VISIBILITY_EVENT = 'dsh:openai-codex-model-visibility'
|
|
16
|
+
const MODEL_CATALOG_EVENT = 'dsh:openai-codex-model-catalog'
|
|
13
17
|
let sharedStatus = null
|
|
14
18
|
let statusRequestGeneration = 0
|
|
15
19
|
|
|
20
|
+
function storedJson(key, fallback) {
|
|
21
|
+
try {
|
|
22
|
+
const value = JSON.parse(window.localStorage.getItem(key) || 'null')
|
|
23
|
+
return value === null ? fallback : value
|
|
24
|
+
} catch {
|
|
25
|
+
return fallback
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readModelVisibility() {
|
|
30
|
+
const availableValue = storedJson(AVAILABLE_MODELS_KEY, [])
|
|
31
|
+
const hiddenValue = storedJson(HIDDEN_MODELS_KEY, [])
|
|
32
|
+
const available = Array.isArray(availableValue)
|
|
33
|
+
? availableValue.slice(0, 512).filter((model) => model && typeof model.id === 'string'
|
|
34
|
+
&& model.id.length > 0 && typeof model.name === 'string' && model.name.length > 0)
|
|
35
|
+
: []
|
|
36
|
+
const hidden = Array.isArray(hiddenValue)
|
|
37
|
+
? [...new Set(hiddenValue.slice(0, 512).filter((id) => typeof id === 'string' && id.length > 0))]
|
|
38
|
+
: []
|
|
39
|
+
return { available, hidden }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function writeHiddenModels(hidden) {
|
|
43
|
+
try {
|
|
44
|
+
window.localStorage.setItem(HIDDEN_MODELS_KEY, JSON.stringify(hidden))
|
|
45
|
+
window.dispatchEvent(new CustomEvent(MODEL_VISIBILITY_EVENT, { detail: hidden }))
|
|
46
|
+
return true
|
|
47
|
+
} catch {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
16
52
|
const css = `
|
|
17
53
|
.codexSection{box-sizing:border-box;max-width:720px;color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;gap:12px}
|
|
18
54
|
.codexSection *{box-sizing:border-box}.codexTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}
|
|
@@ -35,6 +71,8 @@ window.__ModuleLoader__.load({
|
|
|
35
71
|
.codexBar{height:6px;margin:10px 0 8px;overflow:hidden;border-radius:999px;background:var(--dsw-alias-bg-layer-1)}.codexBarFill{height:100%;border-radius:inherit;background:var(--dsw-alias-state-success-primary);transition:width .25s ease}.codexBarFill.high{background:var(--dsw-alias-state-warn-label)}
|
|
36
72
|
.codexReset{font-size:12px;color:var(--dsw-alias-label-tertiary)}
|
|
37
73
|
.codexPlan{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:2px}.codexPlan strong{font-size:14px;font-weight:500}.codexPlan span{font-size:12px;color:var(--dsw-alias-label-tertiary)}
|
|
74
|
+
.codexRoute{margin-bottom:14px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:11px 12px;background:var(--dsw-alias-bg-layer-2)}.codexRouteHead{display:flex;align-items:center;justify-content:space-between;gap:12px}.codexRouteHead strong{font-size:13px;font-weight:500}.codexRouteValue{margin-top:5px;color:var(--dsw-alias-label-primary);font:12px/18px ui-monospace,SFMono-Regular,Consolas,monospace;overflow-wrap:anywhere}.codexRouteDetail{margin:3px 0 0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
|
75
|
+
.codexModelVisibility{margin-top:14px;border-top:1px solid var(--dsw-alias-border-l2);padding-top:14px}.codexModelVisibilityHead{display:flex;align-items:center;justify-content:space-between;gap:12px}.codexModelVisibilityHead strong{font-size:13px;font-weight:500}.codexModelVisibilityHint{margin:4px 0 8px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.codexModelVisibilityList{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;max-height:220px;overflow:auto}.codexModelVisibilityOption{display:flex;align-items:flex-start;gap:8px;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 8px;background:var(--dsw-alias-bg-layer-2);font-size:12px;line-height:18px;cursor:pointer}.codexModelVisibilityOption input{margin:2px 0 0;flex:none}.codexModelVisibilityOption span{min-width:0;overflow-wrap:anywhere}.codexModelVisibilityReset{border:0;background:transparent;color:var(--dsw-alias-label-secondary);padding:0;font:12px/18px inherit;cursor:pointer}.codexModelVisibilityReset:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}
|
|
38
76
|
.codexNotice,.codexWarning,.codexError{margin:14px 0 0;border-radius:8px;padding:8px 10px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
|
|
39
77
|
.codexWarning{color:var(--dsw-alias-state-warn-label)}.codexError{color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere}
|
|
40
78
|
.codexDevice{margin-top:14px;border:1px solid var(--dsw-alias-brand-primary);border-radius:10px;padding:14px;background:var(--dsw-alias-interactive-bg-hover)}
|
|
@@ -47,10 +85,10 @@ window.__ModuleLoader__.load({
|
|
|
47
85
|
.codexField{display:block;margin-top:14px}.codexFieldLabel{display:block;margin-bottom:6px;color:var(--dsw-alias-label-secondary);font-size:12px}.codexInput{display:block;width:100%;min-height:36px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 10px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font:13px/20px ui-monospace,SFMono-Regular,Consolas,monospace}.codexInput:focus{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.codexInput::placeholder{color:var(--dsw-alias-label-dimmed)}
|
|
48
86
|
.codexEmpty{padding:4px 0;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}.codexSkeleton{height:8px;margin:10px 0;border-radius:99px;background:linear-gradient(90deg,var(--dsw-alias-bg-layer-2),var(--dsw-alias-interactive-bg-hover-solid),var(--dsw-alias-bg-layer-2));background-size:200% 100%;animation:codexPulse 1.2s infinite}
|
|
49
87
|
.codexQuotaRoot{display:inline-flex;position:relative}.codexQuotaTrigger{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:transparent;border:none;border-radius:999px;flex:none;place-items:center;display:grid}.codexQuotaTrigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.codexQuotaTrigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
|
|
50
|
-
.codexQuotaTrack{fill:none;stroke:var(--dsw-alias-border-l3);stroke-width:2px}.codexQuotaAvailable{fill:none;stroke:var(--dsw-alias-state-success-primary);stroke-width:2px;transition:stroke .25s ease}.codexQuotaAvailable.high{stroke:var(--dsw-alias-state-warn-label)}.codexQuotaAvailable.critical{stroke:var(--dsw-alias-state-error-primary)}.
|
|
88
|
+
.codexQuotaTrack{fill:none;stroke:var(--dsw-alias-border-l3);stroke-width:2px}.codexQuotaAvailable{fill:none;stroke:var(--dsw-alias-state-success-primary);stroke-width:2px;stroke-linecap:round;transition:stroke .25s ease,stroke-dasharray .25s ease,stroke-dashoffset .25s ease}.codexQuotaAvailable.high{stroke:var(--dsw-alias-state-warn-label)}.codexQuotaAvailable.critical{stroke:var(--dsw-alias-state-error-primary)}.codexQuotaLoading{animation:codexQuotaPulse .8s ease-in-out infinite alternate}
|
|
51
89
|
.codexQuotaPanel{z-index:100;box-sizing:border-box;width:272px;border:1px solid var(--dsw-alias-border-inverted);border-radius:12px;padding:12px;background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-secondary);cursor:default;font-size:12px;line-height:20px;position:absolute;right:0;bottom:calc(100% + 8px)}.codexQuotaPanelHead{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:8px}.codexQuotaPanelHead strong{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500}.codexQuotaPanelHead span{color:var(--dsw-alias-label-tertiary)}
|
|
52
90
|
.codexQuotaWindow{padding:7px 0}.codexQuotaWindow+.codexQuotaWindow{border-top:1px solid var(--dsw-alias-border-l2)}.codexQuotaWindowHead{display:flex;align-items:center;justify-content:space-between;gap:10px}.codexQuotaWindowHead strong{font-weight:500;color:var(--dsw-alias-label-secondary)}.codexQuotaWindowHead span{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary)}.codexQuotaBar{height:4px;margin:6px 0;border-radius:999px;overflow:hidden;background:var(--dsw-alias-interactive-bg-hover)}.codexQuotaBarFill{height:100%;border-radius:inherit;background:var(--dsw-alias-state-success-primary)}.codexQuotaBarFill.high{background:var(--dsw-alias-state-warn-label)}.codexQuotaBarFill.critical{background:var(--dsw-alias-state-error-primary)}.codexQuotaReset{color:var(--dsw-alias-label-tertiary)}.codexQuotaMessage{margin:4px 0;color:var(--dsw-alias-label-tertiary)}.codexQuotaMessage.error{color:var(--dsw-alias-state-error-primary)}
|
|
53
|
-
@keyframes codexPulse{to{background-position:-200% 0}}@keyframes codexQuotaPulse{to{opacity:.35}}@media(prefers-reduced-motion:reduce){.codexSkeleton,.codexQuotaLoading{animation:none}}@media(max-width:620px){.codexHero{padding:14px;flex-direction:column}.codexBody{padding:14px}.codexGrid{grid-template-columns:1fr}.codexCode{font-size:20px}}
|
|
91
|
+
@keyframes codexPulse{to{background-position:-200% 0}}@keyframes codexQuotaPulse{to{opacity:.35}}@media(prefers-reduced-motion:reduce){.codexSkeleton,.codexQuotaLoading{animation:none}}@media(max-width:620px){.codexHero{padding:14px;flex-direction:column}.codexBody{padding:14px}.codexGrid,.codexModelVisibilityList{grid-template-columns:1fr}.codexCode{font-size:20px}}
|
|
54
92
|
`
|
|
55
93
|
if (typeof document !== 'undefined' && document.querySelector('style[data-plugin="' + PLUGIN_ID + '"]') === null) {
|
|
56
94
|
const style = document.createElement('style')
|
|
@@ -102,6 +140,11 @@ window.__ModuleLoader__.load({
|
|
|
102
140
|
return relative + ' · ' + date.toLocaleString([], { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' })
|
|
103
141
|
}
|
|
104
142
|
|
|
143
|
+
function displayQuotaWindow(window) {
|
|
144
|
+
if (!window) return false
|
|
145
|
+
return !(Number(window.usedPercent) === 0 && !Number.isFinite(window.resetAt))
|
|
146
|
+
}
|
|
147
|
+
|
|
105
148
|
function formatCountdown(expiresAt, now) {
|
|
106
149
|
const seconds = Math.max(0, Math.ceil((Number(expiresAt) - now) / 1000))
|
|
107
150
|
return Math.floor(seconds / 60) + ':' + String(seconds % 60).padStart(2, '0')
|
|
@@ -119,6 +162,41 @@ window.__ModuleLoader__.load({
|
|
|
119
162
|
}
|
|
120
163
|
}
|
|
121
164
|
|
|
165
|
+
function routePresentation(route) {
|
|
166
|
+
if (!route) return null
|
|
167
|
+
const provider = route.provider || 'openai-codex'
|
|
168
|
+
const registeredName = route.registeredName && route.registeredName !== provider ? ' · ' + route.registeredName : ''
|
|
169
|
+
if (route.owner === 'native') {
|
|
170
|
+
const transport = route.transport === 'http-sse' ? 'HTTP/SSE' : 'WebSocket v2'
|
|
171
|
+
const compatibility = route.compatibilityRoute && route.compatibilityRoute.active ? ';兼容路由已启用' : ''
|
|
172
|
+
return {
|
|
173
|
+
title: '原生 Codex Adapter',
|
|
174
|
+
value: provider + ' · ' + transport,
|
|
175
|
+
detail: route.active
|
|
176
|
+
? '当前由本插件直接处理模型请求' + compatibility + '。'
|
|
177
|
+
: '已配置 Native Adapter,但生产路由尚未注册。',
|
|
178
|
+
tone: route.active ? 'connected' : 'pending',
|
|
179
|
+
state: route.active ? '已激活' : '未激活',
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (route.owner === 'external') {
|
|
183
|
+
return {
|
|
184
|
+
title: '外部 Adapter',
|
|
185
|
+
value: provider + registeredName,
|
|
186
|
+
detail: '本插件当前只提供登录与凭据;模型请求由其他 Adapter 处理。DSH rc.6 不公开其具体 owner 身份。',
|
|
187
|
+
tone: 'connected',
|
|
188
|
+
state: '已激活',
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
title: '未注册模型路由',
|
|
193
|
+
value: provider,
|
|
194
|
+
detail: '当前没有 Adapter 持有生产 Codex 路由,模型请求将不可用。',
|
|
195
|
+
tone: 'pending',
|
|
196
|
+
state: '不可用',
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
122
200
|
async function copyText(text) {
|
|
123
201
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
124
202
|
await navigator.clipboard.writeText(text)
|
|
@@ -222,17 +300,25 @@ window.__ModuleLoader__.load({
|
|
|
222
300
|
if (!status || !status.loggedIn) return null
|
|
223
301
|
const usage = status.usage
|
|
224
302
|
const primary = usage && usage.primary
|
|
225
|
-
const secondary = usage && usage.secondary
|
|
303
|
+
const secondary = usage && displayQuotaWindow(usage.secondary) ? usage.secondary : null
|
|
226
304
|
const meter = primary || secondary
|
|
227
305
|
const used = meter ? Math.max(0, Math.min(100, Number(meter.usedPercent) || 0)) : 0
|
|
228
|
-
const
|
|
229
|
-
const
|
|
306
|
+
const remaining = Math.max(0, 100 - used)
|
|
307
|
+
const reading = meter ? Math.round(remaining) + '%' : '待更新'
|
|
308
|
+
const title = loading ? '正在刷新 Codex 额度' : 'Codex 额度剩余 ' + reading + ';点击刷新'
|
|
230
309
|
const circumference = 2 * Math.PI * 5.5
|
|
310
|
+
const usedLength = circumference * used / 100
|
|
311
|
+
const remainingLength = circumference - usedLength
|
|
231
312
|
const windows = []
|
|
232
313
|
if (primary) windows.push({ name: quotaWindowName(primary, false), window: primary })
|
|
233
314
|
if (secondary) windows.push({ name: quotaWindowName(secondary, true), window: secondary })
|
|
234
315
|
|
|
235
|
-
return h('span', {
|
|
316
|
+
return h('span', {
|
|
317
|
+
ref: rootRef,
|
|
318
|
+
className: 'codexQuotaRoot',
|
|
319
|
+
onMouseEnter: () => { setOpen(true) },
|
|
320
|
+
onMouseLeave: () => { setOpen(false) },
|
|
321
|
+
},
|
|
236
322
|
h('button', {
|
|
237
323
|
type: 'button',
|
|
238
324
|
className: 'codexQuotaTrigger' + (loading ? ' codexQuotaLoading' : ''),
|
|
@@ -240,21 +326,22 @@ window.__ModuleLoader__.load({
|
|
|
240
326
|
'aria-label': title,
|
|
241
327
|
'aria-haspopup': 'dialog',
|
|
242
328
|
'aria-expanded': open,
|
|
329
|
+
onFocus: () => { setOpen(true) },
|
|
330
|
+
onBlur: (event) => {
|
|
331
|
+
if (!rootRef.current || !rootRef.current.contains(event.relatedTarget)) setOpen(false)
|
|
332
|
+
},
|
|
243
333
|
onClick: () => {
|
|
244
|
-
setOpen(
|
|
334
|
+
setOpen(true)
|
|
245
335
|
void load(true)
|
|
246
336
|
},
|
|
247
337
|
},
|
|
248
338
|
h('svg', { viewBox: '0 0 14 14', width: '14', height: '14', 'aria-hidden': true },
|
|
249
339
|
h('circle', { className: 'codexQuotaTrack', cx: '7', cy: '7', r: '5.5' }),
|
|
250
|
-
meter ? h('circle', {
|
|
340
|
+
meter && remaining > 0 ? h('circle', {
|
|
251
341
|
className: 'codexQuotaAvailable' + quotaTone(used),
|
|
252
342
|
cx: '7', cy: '7', r: '5.5',
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
className: 'codexQuotaConsumed',
|
|
256
|
-
cx: '7', cy: '7', r: '5.5',
|
|
257
|
-
strokeDasharray: (circumference * used / 100) + ' ' + circumference,
|
|
343
|
+
strokeDasharray: remainingLength + ' ' + usedLength,
|
|
344
|
+
strokeDashoffset: -usedLength,
|
|
258
345
|
transform: 'rotate(-90 7 7)',
|
|
259
346
|
}) : null,
|
|
260
347
|
)),
|
|
@@ -265,7 +352,7 @@ window.__ModuleLoader__.load({
|
|
|
265
352
|
),
|
|
266
353
|
windows.length > 0
|
|
267
354
|
? windows.map((row) => h(QuotaWindow, { key: row.name, name: row.name, window: row.window }))
|
|
268
|
-
: h('p', { className: 'codexQuotaMessage' }, '
|
|
355
|
+
: h('p', { className: 'codexQuotaMessage' }, '点击圆圈刷新最新额度。'),
|
|
269
356
|
status.usageError ? h('p', { className: 'codexQuotaMessage error', role: 'status' }, '额度读取失败:' + status.usageError) : null,
|
|
270
357
|
error ? h('p', { className: 'codexQuotaMessage error', role: 'status' }, '无法连接额度接口:' + error) : null,
|
|
271
358
|
) : null,
|
|
@@ -281,8 +368,46 @@ window.__ModuleLoader__.load({
|
|
|
281
368
|
const [probe, setProbe] = useState({ state: 'idle', message: '' })
|
|
282
369
|
const [manualInput, setManualInput] = useState('')
|
|
283
370
|
const [now, setNow] = useState(Date.now())
|
|
371
|
+
const [modelVisibility, setModelVisibility] = useState(readModelVisibility)
|
|
284
372
|
const probeGeneration = useRef(0)
|
|
285
373
|
|
|
374
|
+
useEffect(() => {
|
|
375
|
+
const reload = () => { setModelVisibility(readModelVisibility()) }
|
|
376
|
+
const storage = (event) => {
|
|
377
|
+
if (event.key === HIDDEN_MODELS_KEY || event.key === AVAILABLE_MODELS_KEY) reload()
|
|
378
|
+
}
|
|
379
|
+
window.addEventListener(MODEL_CATALOG_EVENT, reload)
|
|
380
|
+
window.addEventListener('storage', storage)
|
|
381
|
+
return () => {
|
|
382
|
+
window.removeEventListener(MODEL_CATALOG_EVENT, reload)
|
|
383
|
+
window.removeEventListener('storage', storage)
|
|
384
|
+
}
|
|
385
|
+
}, [])
|
|
386
|
+
|
|
387
|
+
const updateHiddenModel = useCallback((modelId, hidden) => {
|
|
388
|
+
setModelVisibility((current) => {
|
|
389
|
+
const ids = new Set(current.hidden)
|
|
390
|
+
if (hidden) ids.add(modelId)
|
|
391
|
+
else ids.delete(modelId)
|
|
392
|
+
const next = [...ids]
|
|
393
|
+
if (!writeHiddenModels(next)) {
|
|
394
|
+
setError('浏览器无法保存模型隐藏设置。')
|
|
395
|
+
return current
|
|
396
|
+
}
|
|
397
|
+
setError('')
|
|
398
|
+
return { ...current, hidden: next }
|
|
399
|
+
})
|
|
400
|
+
}, [])
|
|
401
|
+
|
|
402
|
+
const resetHiddenModels = useCallback(() => {
|
|
403
|
+
if (!writeHiddenModels([])) {
|
|
404
|
+
setError('浏览器无法保存模型隐藏设置。')
|
|
405
|
+
return
|
|
406
|
+
}
|
|
407
|
+
setError('')
|
|
408
|
+
setModelVisibility((current) => ({ ...current, hidden: [] }))
|
|
409
|
+
}, [])
|
|
410
|
+
|
|
286
411
|
const load = useCallback(async (refresh) => {
|
|
287
412
|
try {
|
|
288
413
|
const value = await requestStatus(refresh)
|
|
@@ -466,11 +591,19 @@ window.__ModuleLoader__.load({
|
|
|
466
591
|
const browserPending = Boolean(status && status.loginMethod === 'browser' && status.browser)
|
|
467
592
|
const browserAvailable = Boolean(status && browserLoginAvailable(status.browserCallbackUrl))
|
|
468
593
|
const plan = usage && usage.planType ? String(usage.planType).toUpperCase() : 'ChatGPT 订阅'
|
|
594
|
+
const usageSource = usage && usage.source === 'response' ? 'Codex 返回' : '额度接口'
|
|
595
|
+
const creditNotice = usage && usage.credits
|
|
596
|
+
? usage.credits.unlimited ? 'Codex Credits:无限'
|
|
597
|
+
: usage.credits.hasCredits
|
|
598
|
+
? 'Codex Credits' + (usage.credits.balance ? ' 余额:' + usage.credits.balance : ':可用')
|
|
599
|
+
: 'Codex Credits:不可用'
|
|
600
|
+
: null
|
|
601
|
+
const route = routePresentation(status && status.route)
|
|
469
602
|
const windows = useMemo(() => {
|
|
470
603
|
if (!usage) return []
|
|
471
604
|
const rows = []
|
|
472
605
|
if (usage.primary) rows.push({ name: usage.primary.windowSeconds === 18000 ? '5 小时额度' : '短周期额度', window: usage.primary })
|
|
473
|
-
if (usage.secondary) rows.push({ name: '周额度', window: usage.secondary })
|
|
606
|
+
if (displayQuotaWindow(usage.secondary)) rows.push({ name: '周额度', window: usage.secondary })
|
|
474
607
|
return rows
|
|
475
608
|
}, [usage])
|
|
476
609
|
|
|
@@ -491,15 +624,52 @@ window.__ModuleLoader__.load({
|
|
|
491
624
|
),
|
|
492
625
|
),
|
|
493
626
|
h('div', { className: 'codexBody' },
|
|
627
|
+
route
|
|
628
|
+
? h('div', { className: 'codexRoute' },
|
|
629
|
+
h('div', { className: 'codexRouteHead' },
|
|
630
|
+
h('strong', null, '当前模型路由'),
|
|
631
|
+
h('span', { className: 'codexBadge ' + route.tone },
|
|
632
|
+
h('span', { className: 'codexDot', 'aria-hidden': true }), route.state,
|
|
633
|
+
),
|
|
634
|
+
),
|
|
635
|
+
h('div', { className: 'codexRouteValue' }, route.title + ' · ' + route.value),
|
|
636
|
+
h('p', { className: 'codexRouteDetail' }, route.detail),
|
|
637
|
+
)
|
|
638
|
+
: null,
|
|
494
639
|
loading
|
|
495
640
|
? h('div', { 'aria-label': '加载中' }, h('div', { className: 'codexSkeleton' }), h('div', { className: 'codexSkeleton', style: { width: '72%' } }))
|
|
496
641
|
: connected
|
|
497
642
|
? h(React.Fragment, null,
|
|
498
|
-
h('div', { className: 'codexPlan' }, h('strong', null, plan), h('span', null, usage && usage.fetchedAt ? '更新于 ' + new Date(usage.fetchedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '额度信息待更新')),
|
|
643
|
+
h('div', { className: 'codexPlan' }, h('strong', null, plan), h('span', null, usage && usage.fetchedAt ? '更新于 ' + new Date(usage.fetchedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) + ' · ' + usageSource : '额度信息待更新')),
|
|
499
644
|
windows.length > 0
|
|
500
645
|
? h('div', { className: 'codexGrid' }, windows.map((row) => h(UsageCard, { key: row.name, name: row.name, window: row.window })))
|
|
501
646
|
: h('p', { className: 'codexEmpty' }, '账号已连接,暂时没有返回可展示的额度窗口。'),
|
|
502
647
|
usage && Number.isFinite(usage.resetCredits) ? h('p', { className: 'codexNotice' }, '可用额度重置次数:' + usage.resetCredits) : null,
|
|
648
|
+
creditNotice ? h('p', { className: 'codexNotice' }, creditNotice) : null,
|
|
649
|
+
h('div', { className: 'codexModelVisibility' },
|
|
650
|
+
h('div', { className: 'codexModelVisibilityHead' },
|
|
651
|
+
h('strong', null, '模型列表显示'),
|
|
652
|
+
h('button', {
|
|
653
|
+
type: 'button',
|
|
654
|
+
className: 'codexModelVisibilityReset',
|
|
655
|
+
disabled: modelVisibility.hidden.length === 0,
|
|
656
|
+
onClick: resetHiddenModels,
|
|
657
|
+
}, '全部显示'),
|
|
658
|
+
),
|
|
659
|
+
h('p', { className: 'codexModelVisibilityHint' }, '勾选要隐藏的模型;只影响当前浏览器中的模型选择列表,不会禁用模型或改变已有会话。'),
|
|
660
|
+
modelVisibility.available.length > 0
|
|
661
|
+
? h('div', { className: 'codexModelVisibilityList' }, modelVisibility.available.map((model) =>
|
|
662
|
+
h('label', { className: 'codexModelVisibilityOption', key: model.id },
|
|
663
|
+
h('input', {
|
|
664
|
+
type: 'checkbox',
|
|
665
|
+
checked: modelVisibility.hidden.includes(model.id),
|
|
666
|
+
onChange: (event) => { updateHiddenModel(model.id, event.target.checked) },
|
|
667
|
+
}),
|
|
668
|
+
h('span', null, model.name === model.id ? model.id : model.name + ' · ' + model.id),
|
|
669
|
+
),
|
|
670
|
+
))
|
|
671
|
+
: h('p', { className: 'codexEmpty' }, '打开一个会话的模型列表后,这里会显示可隐藏的 Codex 模型。'),
|
|
672
|
+
),
|
|
503
673
|
status.usageError ? h('p', { className: 'codexError', role: 'status' }, '额度读取失败:' + status.usageError) : null,
|
|
504
674
|
)
|
|
505
675
|
: h('p', { className: 'codexEmpty' }, '设备码登录可在任意设备完成授权,令牌只由 Host 保存和刷新,Web 页面不会读取令牌。'),
|
package/cordis.patch.yml
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
providers:
|
|
4
|
-
openai-codex:
|
|
5
|
-
apiKeyEnv: DSH_OPENAI_CODEX_TOKEN
|
|
6
|
-
|
|
1
|
+
# Native Codex is the package default. Integration bundles own any removal or
|
|
2
|
+
# replacement of an existing openai-codex route before mounting this plugin.
|
|
7
3
|
- insert:
|
|
8
4
|
- id: openai-codex-auth
|
|
9
5
|
name: '@pure01fx/dsh-openai-codex-auth'
|
package/lib/catalog.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export { CODEX_CLIENT_VERSION } from './upstream.js';
|
|
2
|
+
export declare const CODEX_MODELS_URL = "https://chatgpt.com/backend-api/codex/models";
|
|
3
|
+
export declare const CODEX_CATALOG_CACHE_TTL_MS: number;
|
|
4
|
+
/** One request-scoped credential for native Codex backend calls. */
|
|
5
|
+
export interface NativeCodexCredential {
|
|
6
|
+
accessToken: string;
|
|
7
|
+
accountId: string;
|
|
8
|
+
}
|
|
9
|
+
/** Resolve current native Codex authority without retaining it across operations. */
|
|
10
|
+
export type NativeCodexCredentialResolver = (signal?: AbortSignal) => Promise<NativeCodexCredential>;
|
|
11
|
+
export interface NativeCodexReasoningLevel {
|
|
12
|
+
effort: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface NativeCodexServiceTier {
|
|
16
|
+
id: string;
|
|
17
|
+
name?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Validated subset of codex-rs ModelInfo used by DSH metadata and later transport milestones. */
|
|
21
|
+
export interface NativeCodexModel {
|
|
22
|
+
slug: string;
|
|
23
|
+
displayName: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
defaultReasoningLevel?: string;
|
|
26
|
+
supportedReasoningLevels: readonly NativeCodexReasoningLevel[];
|
|
27
|
+
multiAgentReasoningEffort?: string;
|
|
28
|
+
visibility: string;
|
|
29
|
+
supportedInApi: boolean;
|
|
30
|
+
priority: number;
|
|
31
|
+
additionalSpeedTiers: readonly string[];
|
|
32
|
+
serviceTiers: readonly NativeCodexServiceTier[];
|
|
33
|
+
defaultServiceTier?: string;
|
|
34
|
+
contextWindow?: number;
|
|
35
|
+
inputModalities: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
export interface NativeCodexCatalogView {
|
|
38
|
+
models: readonly NativeCodexModel[];
|
|
39
|
+
authorityHash?: string;
|
|
40
|
+
}
|
|
41
|
+
/** Narrow catalog seam consumed by the adapter and injectable in tests. */
|
|
42
|
+
export interface NativeCodexModelCatalog {
|
|
43
|
+
list(signal?: AbortSignal): Promise<readonly NativeCodexModel[]>;
|
|
44
|
+
listWithAuthority?(signal?: AbortSignal): Promise<NativeCodexCatalogView>;
|
|
45
|
+
etag(): string | undefined;
|
|
46
|
+
}
|
|
47
|
+
export interface NativeCodexCatalogOptions {
|
|
48
|
+
resolveCredential: NativeCodexCredentialResolver;
|
|
49
|
+
endpoint?: string;
|
|
50
|
+
clientVersion?: string;
|
|
51
|
+
cacheTtlMs?: number;
|
|
52
|
+
maxStaleMs?: number;
|
|
53
|
+
timeoutMs?: number;
|
|
54
|
+
fetch?: typeof fetch;
|
|
55
|
+
now?: () => number;
|
|
56
|
+
warn?: (message: string) => void;
|
|
57
|
+
}
|
|
58
|
+
export declare function nativeCodexAuthorityHash(accountId: string): string;
|
|
59
|
+
/** Codex-compatible live catalog with bounded stale fallback and no credential retention. */
|
|
60
|
+
export declare class NativeCodexCatalog implements NativeCodexModelCatalog {
|
|
61
|
+
private readonly options;
|
|
62
|
+
private readonly endpoint;
|
|
63
|
+
private readonly clientVersion;
|
|
64
|
+
private readonly cacheTtlMs;
|
|
65
|
+
private readonly maxStaleMs;
|
|
66
|
+
private readonly timeoutMs;
|
|
67
|
+
private readonly fetchImpl;
|
|
68
|
+
private readonly now;
|
|
69
|
+
private snapshot;
|
|
70
|
+
private readonly refreshes;
|
|
71
|
+
private currentEtag;
|
|
72
|
+
constructor(options: NativeCodexCatalogOptions);
|
|
73
|
+
etag(): string | undefined;
|
|
74
|
+
list(signal?: AbortSignal): Promise<readonly NativeCodexModel[]>;
|
|
75
|
+
listWithAuthority(signal?: AbortSignal): Promise<NativeCodexCatalogView>;
|
|
76
|
+
private fetchCatalog;
|
|
77
|
+
}
|