dsh-codex-community 0.0.1 → 0.0.2
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 +48 -0
- package/README.en.md +12 -10
- package/README.md +12 -10
- package/THIRD_PARTY_NOTICES.md +2 -0
- package/dist/client/index.js +487 -49
- package/dist/host/index.mjs +3 -0
- package/dist/internal/authorization-bridge.mjs +12 -0
- package/dist/internal/codex-account-usage.mjs +509 -0
- package/dist/internal/codex-model-capabilities.mjs +90 -0
- package/dist/internal/codex-pi-provider.mjs +30 -3
- package/dist/internal/codex-provider-runtime.mjs +27 -1
- package/dist/internal/codex-route-adapter.mjs +36 -3
- package/dist/internal/session-preference-bridge.mjs +150 -0
- package/docs/architecture.en.md +20 -11
- package/docs/architecture.md +20 -11
- package/docs/compatibility.en.md +13 -12
- package/docs/compatibility.md +13 -12
- package/docs/configuration.en.md +23 -1
- package/docs/configuration.md +23 -1
- package/docs/releases/v0.0.1.md +0 -2
- package/docs/releases/v0.0.2.acceptance.json +160 -0
- package/docs/releases/v0.0.2.md +56 -0
- package/docs/releasing.en.md +1 -1
- package/docs/releasing.md +1 -1
- package/docs/troubleshooting.en.md +19 -3
- package/docs/troubleshooting.md +19 -3
- package/package.json +11 -1
- package/types/client.d.ts +59 -0
|
@@ -6,8 +6,14 @@ import { buildBaseOptions } from "@earendil-works/pi-ai/api/simple-options"
|
|
|
6
6
|
import { stream as streamCodexResponses } from "@earendil-works/pi-ai/api/openai-codex-responses"
|
|
7
7
|
import { openaiCodexProvider } from "@earendil-works/pi-ai/providers/openai-codex"
|
|
8
8
|
|
|
9
|
+
import {
|
|
10
|
+
piThinkingLevelMap,
|
|
11
|
+
supportsCodexFast,
|
|
12
|
+
} from "./codex-model-capabilities.mjs"
|
|
9
13
|
import { codexTransportSessionId } from "./codex-session-resources.mjs"
|
|
10
14
|
|
|
15
|
+
export { supportsCodexFast } from "./codex-model-capabilities.mjs"
|
|
16
|
+
|
|
11
17
|
const FACTORY_OPTION_KEYS = new Set([
|
|
12
18
|
"resolveSessionPreferences",
|
|
13
19
|
"resolveTransportSessionId",
|
|
@@ -72,7 +78,24 @@ function resolveSessionPreferenceOptions(resolver, sessionId) {
|
|
|
72
78
|
function reasoningOptions(model, reasoning) {
|
|
73
79
|
if (reasoning === undefined) return {}
|
|
74
80
|
const effort = clampThinkingLevel(model, reasoning)
|
|
75
|
-
return effort === "off" ?
|
|
81
|
+
return { reasoningEffort: effort === "off" ? "none" : effort }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function truthfulCodexModel(model) {
|
|
85
|
+
const thinkingLevelMap = piThinkingLevelMap(model.id)
|
|
86
|
+
if (thinkingLevelMap === undefined) {
|
|
87
|
+
return Object.freeze({
|
|
88
|
+
...model,
|
|
89
|
+
// A newly published model stays usable with the provider default, but
|
|
90
|
+
// gains no selector controls until its catalog capabilities are verified.
|
|
91
|
+
reasoning: false,
|
|
92
|
+
thinkingLevelMap: undefined,
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
return Object.freeze({
|
|
96
|
+
...model,
|
|
97
|
+
thinkingLevelMap,
|
|
98
|
+
})
|
|
76
99
|
}
|
|
77
100
|
|
|
78
101
|
/**
|
|
@@ -84,6 +107,7 @@ function reasoningOptions(model, reasoning) {
|
|
|
84
107
|
export function createCodexPiProvider(options = {}) {
|
|
85
108
|
validateFactoryOptions(options)
|
|
86
109
|
const source = openaiCodexProvider()
|
|
110
|
+
const models = source.getModels().map(truthfulCodexModel)
|
|
87
111
|
const resolveSessionPreferences = options.resolveSessionPreferences
|
|
88
112
|
const resolveTransportSessionId = options.resolveTransportSessionId
|
|
89
113
|
?? codexTransportSessionId
|
|
@@ -94,7 +118,7 @@ export function createCodexPiProvider(options = {}) {
|
|
|
94
118
|
baseUrl: source.baseUrl,
|
|
95
119
|
headers: source.headers,
|
|
96
120
|
auth: source.auth,
|
|
97
|
-
models
|
|
121
|
+
models,
|
|
98
122
|
...(source.filterModels === undefined
|
|
99
123
|
? {}
|
|
100
124
|
: {
|
|
@@ -109,11 +133,14 @@ export function createCodexPiProvider(options = {}) {
|
|
|
109
133
|
resolveSessionPreferences,
|
|
110
134
|
streamOptions.sessionId,
|
|
111
135
|
)
|
|
112
|
-
const
|
|
136
|
+
const requestedServiceTier = sessionPreferences === undefined
|
|
113
137
|
? options.serviceTier
|
|
114
138
|
: sessionPreferences.fast
|
|
115
139
|
? "priority"
|
|
116
140
|
: undefined
|
|
141
|
+
const serviceTier = requestedServiceTier !== undefined && supportsCodexFast(model.id)
|
|
142
|
+
? requestedServiceTier
|
|
143
|
+
: undefined
|
|
117
144
|
const transportSessionId = resolveTransportSessionId(streamOptions.sessionId)
|
|
118
145
|
if (
|
|
119
146
|
transportSessionId !== undefined
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai"
|
|
2
2
|
import { resolveRetryPolicy } from "@deepseek-ai/dsh-llm"
|
|
3
|
+
import { createModels } from "@earendil-works/pi-ai"
|
|
3
4
|
import {
|
|
4
5
|
installSettingsSection,
|
|
5
6
|
settingsNamespace,
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
import Schema from "@deepseek-ai/schemastery"
|
|
8
9
|
|
|
9
10
|
import { registerCodexAuthorizationFlow } from "./codex-authorization.mjs"
|
|
11
|
+
import { createCodexAccountUsageReader } from "./codex-account-usage.mjs"
|
|
10
12
|
import {
|
|
11
13
|
CODEX_PROVIDER_ID,
|
|
12
14
|
createCodexCredentialStore,
|
|
@@ -141,6 +143,27 @@ export function installCodexProviderRuntime(ctx, entryConfig = {}, options = {})
|
|
|
141
143
|
env: async () => undefined,
|
|
142
144
|
fileExists: async () => false,
|
|
143
145
|
})
|
|
146
|
+
const authModels = createModels({ credentials: credentialStore, authContext })
|
|
147
|
+
authModels.setProvider(provider)
|
|
148
|
+
const accountUsageReader = createCodexAccountUsageReader({
|
|
149
|
+
baseUrl: provider.baseUrl,
|
|
150
|
+
...(options.accountUsageFetch === undefined ? {} : { fetch: options.accountUsageFetch }),
|
|
151
|
+
...(options.accountUsageClock === undefined ? {} : { clock: options.accountUsageClock }),
|
|
152
|
+
resolveAuth: async ({ signal }) => {
|
|
153
|
+
if (signal.aborted) throw signal.reason
|
|
154
|
+
const resolved = await authModels.getAuth(provider.id)
|
|
155
|
+
if (signal.aborted) throw signal.reason
|
|
156
|
+
const credential = await credentialStore.read(provider.id)
|
|
157
|
+
if (
|
|
158
|
+
resolved?.auth?.apiKey === undefined
|
|
159
|
+
|| credential?.type !== "oauth"
|
|
160
|
+
|| resolved.auth.apiKey !== credential.access
|
|
161
|
+
) {
|
|
162
|
+
throw new Error("Codex OAuth credential is unavailable")
|
|
163
|
+
}
|
|
164
|
+
return { access: credential.access, accountId: credential.accountId }
|
|
165
|
+
},
|
|
166
|
+
})
|
|
144
167
|
|
|
145
168
|
let source = () => entry
|
|
146
169
|
let previousConfig
|
|
@@ -211,7 +234,10 @@ export function installCodexProviderRuntime(ctx, entryConfig = {}, options = {})
|
|
|
211
234
|
},
|
|
212
235
|
})
|
|
213
236
|
|
|
214
|
-
return Object.freeze({
|
|
237
|
+
return Object.freeze({
|
|
238
|
+
accountUsageReader,
|
|
239
|
+
getConfig: () => source(),
|
|
240
|
+
})
|
|
215
241
|
}
|
|
216
242
|
|
|
217
243
|
function configuredModels(configured, catalog) {
|
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
CODEX_PROVIDER_ID,
|
|
9
9
|
CODEX_ROUTE_ID,
|
|
10
10
|
} from "./codex-identifiers.mjs"
|
|
11
|
+
import {
|
|
12
|
+
codexModelCapability,
|
|
13
|
+
supportsCodexReasoningEffort,
|
|
14
|
+
} from "./codex-model-capabilities.mjs"
|
|
11
15
|
|
|
12
16
|
export { CODEX_ROUTE_ID } from "./codex-identifiers.mjs"
|
|
13
17
|
|
|
@@ -73,10 +77,10 @@ export class CodexRouteAdapter extends LlmAdapter {
|
|
|
73
77
|
|
|
74
78
|
async resolveModel(provider, model, signal) {
|
|
75
79
|
this.#assertRoute(provider)
|
|
76
|
-
return {
|
|
80
|
+
return externalModelInfo({
|
|
77
81
|
...await this.delegate.resolveModel(CODEX_PROVIDER_ID, model, signal),
|
|
78
82
|
provider: CODEX_ROUTE_ID,
|
|
79
|
-
}
|
|
83
|
+
})
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
async prepareCall(provider, model, signal) {
|
|
@@ -87,7 +91,10 @@ export class CodexRouteAdapter extends LlmAdapter {
|
|
|
87
91
|
signal,
|
|
88
92
|
)
|
|
89
93
|
return Object.freeze({
|
|
90
|
-
model: Object.freeze({
|
|
94
|
+
model: Object.freeze(externalModelInfo({
|
|
95
|
+
...prepared.model,
|
|
96
|
+
provider: CODEX_ROUTE_ID,
|
|
97
|
+
})),
|
|
91
98
|
stream: (options) => prepared.stream(this.#canonicalOptions(options)),
|
|
92
99
|
})
|
|
93
100
|
}
|
|
@@ -98,9 +105,20 @@ export class CodexRouteAdapter extends LlmAdapter {
|
|
|
98
105
|
|
|
99
106
|
#canonicalOptions(options) {
|
|
100
107
|
this.#assertRoute(options?.provider)
|
|
108
|
+
const reasoningEffort = options.reasoningEffort
|
|
109
|
+
if (
|
|
110
|
+
reasoningEffort !== undefined
|
|
111
|
+
&& !supportsCodexReasoningEffort(options.model, reasoningEffort)
|
|
112
|
+
) {
|
|
113
|
+
throw new LlmError(
|
|
114
|
+
`provider "${CODEX_ROUTE_ID}" model "${String(options.model)}" does not support reasoning effort "${String(reasoningEffort)}"`,
|
|
115
|
+
"UNSUPPORTED_REASONING_EFFORT",
|
|
116
|
+
)
|
|
117
|
+
}
|
|
101
118
|
return {
|
|
102
119
|
...options,
|
|
103
120
|
provider: CODEX_PROVIDER_ID,
|
|
121
|
+
...(reasoningEffort === undefined ? {} : { reasoningEffort }),
|
|
104
122
|
messages: options.messages.map(toCanonicalHistoryMessage),
|
|
105
123
|
}
|
|
106
124
|
}
|
|
@@ -115,6 +133,21 @@ export class CodexRouteAdapter extends LlmAdapter {
|
|
|
115
133
|
}
|
|
116
134
|
}
|
|
117
135
|
|
|
136
|
+
function externalModelInfo(model) {
|
|
137
|
+
const capability = codexModelCapability(model.id)
|
|
138
|
+
if (capability === undefined) {
|
|
139
|
+
const { reasoning: _unverifiedReasoning, ...withoutReasoning } = model
|
|
140
|
+
return withoutReasoning
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
...model,
|
|
144
|
+
reasoning: {
|
|
145
|
+
efforts: capability.reasoningEfforts,
|
|
146
|
+
defaultEffort: capability.defaultReasoningEffort,
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
118
151
|
function toCanonicalHistoryMessage(message) {
|
|
119
152
|
if (
|
|
120
153
|
message.role !== "assistant"
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Connection channels are one path segment; endpoints are appended by RPC.
|
|
2
|
+
export const SESSION_PREFERENCE_RPC_CHANNEL = "/dsh-codex-session"
|
|
3
|
+
|
|
4
|
+
const MAX_SESSION_ID_CHARS = 256
|
|
5
|
+
|
|
6
|
+
class RpcInputError extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message)
|
|
9
|
+
this.name = "RpcInputError"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Narrow browser bridge for the existing process-local session preferences.
|
|
15
|
+
* Transport choices and all store internals remain host-only; the client can
|
|
16
|
+
* read or change only Fast for one explicitly named session.
|
|
17
|
+
*/
|
|
18
|
+
export class CodexSessionPreferenceBridge {
|
|
19
|
+
#preferences
|
|
20
|
+
|
|
21
|
+
constructor(sessionPreferences) {
|
|
22
|
+
if (
|
|
23
|
+
sessionPreferences === null
|
|
24
|
+
|| typeof sessionPreferences !== "object"
|
|
25
|
+
|| typeof sessionPreferences.resolve !== "function"
|
|
26
|
+
|| typeof sessionPreferences.configure !== "function"
|
|
27
|
+
) {
|
|
28
|
+
throw new TypeError("sessionPreferences must provide resolve and configure")
|
|
29
|
+
}
|
|
30
|
+
this.#preferences = sessionPreferences
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get(payload, signal) {
|
|
34
|
+
throwIfCancelled(signal)
|
|
35
|
+
const input = objectInput(payload)
|
|
36
|
+
assertOnlyKeys(input, ["sessionId"])
|
|
37
|
+
const sessionId = requiredSessionId(input)
|
|
38
|
+
const result = publicFastSnapshot(this.#preferences.resolve(sessionId))
|
|
39
|
+
throwIfCancelled(signal)
|
|
40
|
+
return result
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setFast(payload, signal) {
|
|
44
|
+
throwIfCancelled(signal)
|
|
45
|
+
const input = objectInput(payload)
|
|
46
|
+
assertOnlyKeys(input, ["sessionId", "fast"])
|
|
47
|
+
const sessionId = requiredSessionId(input)
|
|
48
|
+
if (!Object.hasOwn(input, "fast") || typeof input.fast !== "boolean") {
|
|
49
|
+
throw new RpcInputError("fast must be a boolean")
|
|
50
|
+
}
|
|
51
|
+
const result = publicFastSnapshot(this.#preferences.configure(sessionId, { fast: input.fast }))
|
|
52
|
+
throwIfCancelled(signal)
|
|
53
|
+
return result
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
dispatch(endpoint, payload, signal) {
|
|
57
|
+
switch (endpoint) {
|
|
58
|
+
case "get": return this.get(payload, signal)
|
|
59
|
+
case "set-fast": return this.setFast(payload, signal)
|
|
60
|
+
default: throw new RpcInputError("Unknown session preference RPC endpoint")
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Register the session-only Fast surface on a dedicated loopback channel. */
|
|
66
|
+
export function registerSessionPreferenceRpc(ctx, sessionPreferences) {
|
|
67
|
+
const bridge = new CodexSessionPreferenceBridge(sessionPreferences)
|
|
68
|
+
ctx.connection.rpc.handle(
|
|
69
|
+
SESSION_PREFERENCE_RPC_CHANNEL,
|
|
70
|
+
createSessionPreferenceRpcHandler(bridge),
|
|
71
|
+
{ authority: "loopback" },
|
|
72
|
+
)
|
|
73
|
+
return bridge
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Convert bridge failures into the bounded browser RPC result envelope. */
|
|
77
|
+
export function createSessionPreferenceRpcHandler(bridge) {
|
|
78
|
+
if (bridge === null || typeof bridge !== "object" || typeof bridge.dispatch !== "function") {
|
|
79
|
+
throw new TypeError("bridge must provide dispatch")
|
|
80
|
+
}
|
|
81
|
+
return async (endpoint, payload, signal) => {
|
|
82
|
+
if (signal?.aborted === true) return cancelledResult()
|
|
83
|
+
try {
|
|
84
|
+
const value = await bridge.dispatch(endpoint, payload, signal)
|
|
85
|
+
if (signal?.aborted === true) return cancelledResult()
|
|
86
|
+
return { ok: true, value }
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (signal?.aborted === true) return cancelledResult()
|
|
89
|
+
if (error instanceof RpcInputError) {
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
error: { code: "bad-request", message: error.message, details: { issues: [] } },
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
ok: false,
|
|
97
|
+
error: { code: "internal", message: "Session preference request failed", details: {} },
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function publicFastSnapshot(value) {
|
|
104
|
+
if (value === null || typeof value !== "object" || typeof value.fast !== "boolean") {
|
|
105
|
+
throw new TypeError("session preference store returned an invalid snapshot")
|
|
106
|
+
}
|
|
107
|
+
return { fast: value.fast }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function objectInput(value) {
|
|
111
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
112
|
+
throw new RpcInputError("RPC payload must be an object")
|
|
113
|
+
}
|
|
114
|
+
const prototype = Object.getPrototypeOf(value)
|
|
115
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
116
|
+
throw new RpcInputError("RPC payload must be a plain object")
|
|
117
|
+
}
|
|
118
|
+
return value
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function assertOnlyKeys(value, allowed) {
|
|
122
|
+
const accepted = new Set(allowed)
|
|
123
|
+
if (Object.keys(value).some((key) => !accepted.has(key))) {
|
|
124
|
+
throw new RpcInputError("RPC payload contains an unknown field")
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function requiredSessionId(value) {
|
|
129
|
+
const sessionId = value.sessionId
|
|
130
|
+
if (
|
|
131
|
+
!Object.hasOwn(value, "sessionId")
|
|
132
|
+
|| typeof sessionId !== "string"
|
|
133
|
+
|| sessionId.length < 1
|
|
134
|
+
|| sessionId.length > MAX_SESSION_ID_CHARS
|
|
135
|
+
) {
|
|
136
|
+
throw new RpcInputError(`sessionId must contain 1 to ${MAX_SESSION_ID_CHARS} characters`)
|
|
137
|
+
}
|
|
138
|
+
return sessionId
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function throwIfCancelled(signal) {
|
|
142
|
+
if (signal?.aborted === true) throw new Error("request cancelled")
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function cancelledResult() {
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
error: { code: "cancelled", message: "Request cancelled", details: {} },
|
|
149
|
+
}
|
|
150
|
+
}
|
package/docs/architecture.en.md
CHANGED
|
@@ -8,16 +8,17 @@ This plugin registers the Codex route, settings namespace, OAuth flow, session p
|
|
|
8
8
|
|
|
9
9
|
```text
|
|
10
10
|
DSH Web settings ── loopback RPC ── AuthorizationBridge
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
│ │
|
|
12
|
+
│ page entry / manual refresh │
|
|
13
|
+
▼ ▼
|
|
14
|
+
AccountUsageReader ─────────────── CodexCredentialStore
|
|
15
|
+
│ │
|
|
16
|
+
▼ ▼
|
|
17
|
+
Codex Web usage endpoint ChatGPT OAuth grant
|
|
17
18
|
|
|
18
19
|
Harness Agent Loop
|
|
19
20
|
│
|
|
20
|
-
├── SessionPreferences ── Fast / transport
|
|
21
|
+
├── SessionPreferences ── lightning button, Fast / transport
|
|
21
22
|
│
|
|
22
23
|
▼
|
|
23
24
|
StreamResilience ── CodexRouteAdapter (`dsh-codex`)
|
|
@@ -48,15 +49,21 @@ The Host starts, cancels, and observes sign-in through the DSH authorization ser
|
|
|
48
49
|
|
|
49
50
|
`/codex-login status|cancel|logout` uses the same boundary and prevents command input from entering conversation history.
|
|
50
51
|
|
|
52
|
+
## AccountUsageReader
|
|
53
|
+
|
|
54
|
+
Whenever the settings page mounts or the user clicks Refresh, the Host-side `AccountUsageReader` uses the current OAuth grant to request the Web-backend usage compatibility endpoint used by the official Codex client. It strictly parses used percentages, window durations, and reset times, then converts the five-hour and weekly windows into a minimal credential-free snapshot. Access tokens, refresh tokens, account IDs, raw responses, and arbitrary response headers never cross the loopback RPC.
|
|
55
|
+
|
|
56
|
+
This endpoint is treated as a Web-backend compatibility boundary, not as a stable plugin public API. Requests have timeout, response-size, and numeric-range limits. A network failure, authentication failure, or schema change is never rendered as zero remaining usage and does not erase the latest verified reading. When no live reading can be retained, the page safely falls back to recent-request `QuotaObserver` state or “unknown” instead of inferring usage.
|
|
57
|
+
|
|
51
58
|
## SessionPreferences
|
|
52
59
|
|
|
53
|
-
|
|
60
|
+
The lightning button to the left of the model selector and `/codex` update the same current-session state:
|
|
54
61
|
|
|
55
|
-
- `fast on|off` controls whether the priority service tier is requested and defaults to off;
|
|
62
|
+
- the lightning button or `fast on|off` controls whether the Fast priority service tier is requested and defaults to off;
|
|
56
63
|
- `transport` accepts `auto`, `sse`, `websocket`, or `websocket-cached` and defaults to `auto`;
|
|
57
64
|
- `reset` restores that session's defaults.
|
|
58
65
|
|
|
59
|
-
Preferences live in a capacity-bounded in-memory table that returns immutable snapshots; they do not change global provider settings. A failed Fast request is not replayed automatically on another service tier, avoiding duplicate tool side effects.
|
|
66
|
+
Preferences live in a capacity-bounded in-memory table that returns immutable snapshots; they do not change global provider settings and return to their defaults after a process restart. Fast applies only to GPT-5.4, GPT-5.5, GPT-5.6 Luna, Sol, and Terra, using the official priority service tier to target 1.5× speed while consuming more usage. Other models never carry that tier. A toggle affects the next request, not one already in flight. A failed Fast request is not replayed automatically on another service tier, avoiding duplicate tool side effects.
|
|
60
67
|
|
|
61
68
|
The raw DSH session ID is used only for preference lookup and message/replay provenance. The transport/cache session ID passed to pi-ai is namespaced with `dsh-codex:`. `/codex reset`, `agent/disposed`, and runtime disposal use pi-ai's public exact-session APIs to clear only this plugin's WebSocket connection, fallback, and debug state. They never invoke no-argument global cleanup and do not affect sessions owned by another in-process pi-ai consumer. The namespace enters only pi-ai stream options; it does not rewrite history messages or replay envelopes.
|
|
62
69
|
|
|
@@ -66,6 +73,8 @@ This plugin's settings page uses `llm.discoverModels` to display requestable mod
|
|
|
66
73
|
|
|
67
74
|
The settings page requires at least one selected model. Selecting all removes the `models` override only when entries have no custom fields, allowing the directory to follow pi-ai version updates. Partial selections, extra fields, and custom parameters retain explicit configuration. Catalog filtering affects discovery only; exact hidden models remain resolvable, so older sessions are not invalidated merely because a model is hidden.
|
|
68
75
|
|
|
76
|
+
Model names, context windows, and input capabilities come from the installed provider catalog and are not presented as a dynamic account directory. `CodexRouteAdapter` projects reasoning controls from the verified subscription-Codex model catalog: it removes generic `Default`, `Off`, and `Minimal` entries, supplies each model's default, and exposes only Low through Max, which the Provider request layer can represent truthfully. Codex `Ultra` combines the highest plain reasoning level with proactive task delegation and is therefore an Agent orchestration mode. This plugin neither sends a fabricated `ultra` wire value nor silently degrades it to `Max`. Unknown models receive no inferred controls, and the route boundary rejects direct injection of an unverified effort.
|
|
77
|
+
|
|
69
78
|
## ImagePolicy
|
|
70
79
|
|
|
71
80
|
Optional settings resolve to one immutable policy with no optional numeric fields. The attachment contract receives only `{ maxPixels, maxBytes }`. Zero, negative, fractional, `NaN`, and unsafe integers fail at the configuration boundary.
|
|
@@ -80,7 +89,7 @@ Each plugin instance runs at most two remote-image jobs and queues at most 32; a
|
|
|
80
89
|
|
|
81
90
|
`QUOTA`, `QUOTA_OR_RATE_LIMIT`, and confirmed transport failures are rebuilt as minimal failures containing a fixed sanitized message, code, and optional valid HTTP status/safe-character request ID. Arbitrary provider fields and WebSocket close reasons are never reflected. `QUOTA_OR_RATE_LIMIT` is not written to `QuotaObserver`: it fails without retry when no partial output exists and only preserves already safe plain text when partial output exists.
|
|
82
91
|
|
|
83
|
-
`QuotaObserver` records only successful completion, `QUOTA`, and a reset timestamp accepted by strict format and bounded-horizon checks. Snapshots have three states: `unknown`, `recent-success`, and `exhausted`. It
|
|
92
|
+
`QuotaObserver` records only successful completion, `QUOTA`, and a reset timestamp accepted by strict format and bounded-horizon checks. Snapshots have three states: `unknown`, `recent-success`, and `exhausted`. It neither polls an account nor displays a balance or percentage. Instead, it is the request-observation fallback when `AccountUsageReader` has no usable live snapshot; the two evidence types never masquerade as each other.
|
|
84
93
|
|
|
85
94
|
## StreamResilience
|
|
86
95
|
|
package/docs/architecture.md
CHANGED
|
@@ -8,16 +8,17 @@
|
|
|
8
8
|
|
|
9
9
|
```text
|
|
10
10
|
DSH Web 设置 ── loopback RPC ── AuthorizationBridge
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
│ │
|
|
12
|
+
│ 进入页面 / 手动刷新 │
|
|
13
|
+
▼ ▼
|
|
14
|
+
AccountUsageReader ────────── CodexCredentialStore
|
|
15
|
+
│ │
|
|
16
|
+
▼ ▼
|
|
17
|
+
Codex Web usage endpoint ChatGPT OAuth grant
|
|
17
18
|
|
|
18
19
|
Harness Agent Loop
|
|
19
20
|
│
|
|
20
|
-
├── SessionPreferences ── Fast / transport
|
|
21
|
+
├── SessionPreferences ── 闪电按钮、Fast / transport
|
|
21
22
|
│
|
|
22
23
|
▼
|
|
23
24
|
StreamResilience ── CodexRouteAdapter (`dsh-codex`)
|
|
@@ -48,15 +49,21 @@ Host 端通过 DSH authorization service 启动、取消和观察登录流程,
|
|
|
48
49
|
|
|
49
50
|
`/codex-login status|cancel|logout` 使用同一边界,并禁止把命令输入写入会话记录。
|
|
50
51
|
|
|
52
|
+
## AccountUsageReader
|
|
53
|
+
|
|
54
|
+
设置页每次挂载以及用户点击“刷新”时,Host 端 `AccountUsageReader` 使用当前 OAuth grant 请求官方 Codex 客户端使用的 Web 后端 usage 兼容接口。它严格解析使用百分比、窗口时长和 reset 时间,并把五小时与每周窗口转换为不含凭据的最小快照;access token、refresh token、account ID、原始响应和任意响应头都不会跨过 loopback RPC。
|
|
55
|
+
|
|
56
|
+
该接口是 Web 后端兼容边界,不被当作稳定的插件公共 API。请求具有超时、响应大小和数值范围限制;网络失败、鉴权失败或结构变化不会被显示成零余额,也不会清除最近一次已验证读数。若没有可保留的实时读数,页面会安全降级为最近请求产生的 `QuotaObserver` 状态或“未知”,而不是推断额度。
|
|
57
|
+
|
|
51
58
|
## SessionPreferences
|
|
52
59
|
|
|
53
|
-
`/codex`
|
|
60
|
+
模型选择器左侧的闪电按钮和 `/codex` 修改同一份当前会话状态:
|
|
54
61
|
|
|
55
|
-
- `fast on|off` 控制是否发送 priority service tier,默认关闭;
|
|
62
|
+
- 闪电按钮或 `fast on|off` 控制是否发送 Fast priority service tier,默认关闭;
|
|
56
63
|
- `transport` 可选 `auto`、`sse`、`websocket` 或 `websocket-cached`,默认 `auto`;
|
|
57
64
|
- `reset` 恢复当前会话默认值。
|
|
58
65
|
|
|
59
|
-
偏好保存在有容量上限的内存表中,返回不可变快照,不写入全局 provider
|
|
66
|
+
偏好保存在有容量上限的内存表中,返回不可变快照,不写入全局 provider 设置,进程重启后恢复默认。Fast 只对 GPT-5.4、GPT-5.5、GPT-5.6 Luna、Sol 和 Terra 请求生效,使用官方 priority service tier,目标速度为 1.5 倍并消耗更多额度;其他模型不会携带该 tier。切换只影响下一次请求,已经开始的请求不变。Fast 请求失败后不会自动以另一 service tier 重放,避免重复工具副作用。
|
|
60
67
|
|
|
61
68
|
DSH 原始 session ID 只用于偏好查询和消息/replay provenance;传给 pi-ai 的 transport/cache session ID 带有 `dsh-codex:` 命名空间。`/codex reset`、`agent/disposed` 和 runtime dispose 只通过 pi-ai 的公开、精确 session API 清理本插件拥有的 WebSocket 连接、fallback 与 debug 状态,不调用无参全局清理,也不影响同进程其他 pi-ai consumer 的 session。该命名空间只进入 pi-ai stream options,不改写历史消息或 replay envelope。
|
|
62
69
|
|
|
@@ -66,6 +73,8 @@ DSH 原始 session ID 只用于偏好查询和消息/replay provenance;传给
|
|
|
66
73
|
|
|
67
74
|
设置页至少要求选中一个模型。全选且条目没有自定义字段时移除 `models` 覆盖,使目录随 pi-ai 版本更新;部分选择、额外字段和自定义参数保留显式配置。目录筛选只影响模型发现,精确指定的隐藏模型仍可解析,因此旧会话不会仅因模型被隐藏而失效。
|
|
68
75
|
|
|
76
|
+
模型名称、上下文窗口与输入能力来自当前安装的 provider catalog,不声称是账号动态目录。推理选择器由 `CodexRouteAdapter` 按已核验的订阅 Codex 模型目录重新投影:删除通用 `Default`、`Off` 与 `Minimal`,写入逐模型默认值,并只暴露可以由 Provider 请求层诚实表达的 Low 至 Max。Codex 的 `Ultra` 同时代表最高普通推理与主动任务委派,属于 Agent 编排模式;本插件不会只发送一个伪造的 `ultra` 值,也不会静默退化成 `Max`。未知模型不获得推断能力,route 边界也拒绝直接注入未核验档位。
|
|
77
|
+
|
|
69
78
|
## ImagePolicy
|
|
70
79
|
|
|
71
80
|
可选配置会解析为不可变、没有 optional 数字的完整策略。attachment contract 只收到 `{ maxPixels, maxBytes }`。零、负数、浮点、`NaN` 和超安全整数会在配置边界被拒绝。
|
|
@@ -80,7 +89,7 @@ DSH 原始 session ID 只用于偏好查询和消息/replay provenance;传给
|
|
|
80
89
|
|
|
81
90
|
`QUOTA`、`QUOTA_OR_RATE_LIMIT` 和已确认 transport 的输出均重新构造为最小 failure,只保留固定脱敏消息、code,以及可选的有效 HTTP status/受限字符集 request ID;不回显任意 provider 字段或 WebSocket close reason。`QUOTA_OR_RATE_LIMIT` 不写入 `QuotaObserver`:无 partial 输出时直接失败且不重试,已有安全纯文本时只保存 partial。
|
|
82
91
|
|
|
83
|
-
`QuotaObserver` 只记录成功终止、`QUOTA` 和通过严格格式及有限时距校验的 reset timestamp。快照只有 `unknown`、`recent-success` 和 `exhausted`
|
|
92
|
+
`QuotaObserver` 只记录成功终止、`QUOTA` 和通过严格格式及有限时距校验的 reset timestamp。快照只有 `unknown`、`recent-success` 和 `exhausted` 三态。它不轮询账户,也不展示余额或百分比;它作为 `AccountUsageReader` 无可用实时快照时的请求观测降级,两类证据不会互相伪装。
|
|
84
93
|
|
|
85
94
|
## StreamResilience
|
|
86
95
|
|
package/docs/compatibility.en.md
CHANGED
|
@@ -4,29 +4,30 @@
|
|
|
4
4
|
|
|
5
5
|
Last updated: 2026-08-29.
|
|
6
6
|
|
|
7
|
-
| Component or environment | `0.0.
|
|
7
|
+
| Component or environment | `0.0.2` evidence | Status |
|
|
8
8
|
| --- | --- | --- |
|
|
9
9
|
| DeepSeek Harness | `test/fixtures/dsh-runtime/pnpm-lock.yaml` locks the complete `@deepseek-ai/dsh@0.1.1-rc.2` runtime/peer graph and validates the `dsh-llm` schema and stream contract | Exact version verified |
|
|
10
|
-
| pi-ai | OAuth, catalog, Codex payload, and replay contract tests against `@earendil-works/pi-ai@0.82.1` | Public contract verified;
|
|
11
|
-
| Node.js | Complete local suite on `22.22.2`; [
|
|
12
|
-
| macOS
|
|
13
|
-
| Windows x64 | `windows-latest`
|
|
14
|
-
| Linux x64 | `ubuntu-latest`
|
|
10
|
+
| pi-ai | OAuth, catalog, Codex payload, and replay contract tests against `@earendil-works/pi-ai@0.82.1` | Public contract verified; 0.0.2 live-network acceptance is `0/13 pending` |
|
|
11
|
+
| Node.js | Complete local suite on `22.22.2`; the [0.0.2 three-platform candidate run](https://github.com/yoshino-xiao7/dsh-codex/actions/runs/33243698807) covers Node 22/24 | `>=22.19.0 <25`; candidate run passed |
|
|
12
|
+
| macOS | `macos-latest` Node 22/24 complete checks, frozen DSH installation, and Web/profile smoke | Platform gate passed |
|
|
13
|
+
| Windows x64 | `windows-latest` Node 22/24 complete checks, frozen DSH installation, and Web/profile smoke | Platform gate passed; real user environment pending |
|
|
14
|
+
| Linux x64 | `ubuntu-latest` Node 22/24 complete checks, frozen DSH installation, and Web/profile smoke | Platform gate passed |
|
|
15
15
|
| Real ChatGPT OAuth login | Automation does not read or modify a user's real grant | Controlled acceptance pending |
|
|
16
|
+
| Codex usage windows | Opening the settings page and refreshing manually both make the Host actively read and strictly parse the real five-hour and weekly limits; both windows were verified in the local DSH settings page | Verified; a failure retains the latest safe reading or falls back to request observation/an unknown state |
|
|
16
17
|
| Real Codex conversation | Automation does not consume user account quota | Controlled acceptance pending |
|
|
17
18
|
| Text / reasoning / usage / tools / replay | Public PiAiAdapter success, tool-call, and two-turn replay automation passes | Live reasoning, tool round trip, and conversation continuity are pending |
|
|
18
19
|
| Codex image input | Attachment-seam and budget-projection automation passes | A live request with `maxPixels=4194304` is pending |
|
|
19
20
|
| auto / SSE / WebSocket / cached | Transport mapping and session isolation pass in automation | One live request through each transport is pending |
|
|
20
21
|
| Fast / priority tier | Verified that only an explicit per-session choice changes `service_tier` | Account entitlement and live network pending |
|
|
21
|
-
| npm / GitHub Release | The strict workflow verifies the candidate, Registry readback, and Release assets
|
|
22
|
+
| npm / GitHub Release | The strict workflow verifies the candidate, Registry readback, provenance, signatures, and Release assets; artifacts are attached to [`v0.0.2`](https://github.com/yoshino-xiao7/dsh-codex/releases/tag/v0.0.2) | Formal publication is performed by the protected workflow |
|
|
22
23
|
|
|
23
|
-
The `0.0.x` line is a technical preview and declares compatibility only with the exact DSH release candidate above.
|
|
24
|
+
The `0.0.x` line is a technical preview and declares compatibility only with the exact DSH release candidate above. Linux, macOS, and Windows CI/profile smoke for `0.0.2` reached `3/3`; the accepted commit, exact Node versions, and job links are recorded in the [acceptance record](releases/v0.0.2.acceptance.json), with maintainer approval. Live OAuth, conversation, image, transport, and Fast network acceptance remains `0/13 pending` for post-release validation and will be completed individually after publication. These incomplete checks must remain visible and must never be presented as verified capabilities. Reading the usage windows was verified independently in the local settings page and is not one of these 13 checks that consume a model request or require a complete interaction round trip.
|
|
24
25
|
|
|
25
26
|
The root `pnpm-lock.yaml` locks plugin dependencies, while `test/fixtures/dsh-runtime/pnpm-lock.yaml` independently locks the complete DSH runtime and peer graph used by compatibility smoke. CI runs `pnpm --dir test/fixtures/dsh-runtime install --frozen-lockfile --ignore-scripts` instead of handing that peer graph to direct npm resolution, avoiding nondeterministic dependency results and uncontrolled memory use. This frozen layer verifies Web/profile integration; it does not claim coverage of native terminal or native-build capabilities in DSH dependencies that require lifecycle scripts. Dependency upgrades must use a pinned-version PR, update and review both lockfiles, repeat complete CI, profile smoke, and supply-chain verification before publication, and renew controlled live validation afterward. The scheduled compatibility workflow only verifies the current locked graph and reports Registry drift; neither a broad semver range nor one scheduled run proves cross-RC compatibility.
|
|
26
27
|
|
|
27
28
|
## Profile composition
|
|
28
29
|
|
|
29
|
-
The `0.0.
|
|
30
|
+
The `0.0.2` bundle does not modify the general `llm-pi-ai` Cordis row. It provides:
|
|
30
31
|
|
|
31
32
|
- the `dsh-codex` route;
|
|
32
33
|
- the `dsh-codex` settings namespace;
|
|
@@ -45,9 +46,9 @@ Fast and transport preferences set by `/codex` exist only for the current proces
|
|
|
45
46
|
## Credentials and capability boundaries
|
|
46
47
|
|
|
47
48
|
- The Codex route accepts ChatGPT OAuth only and does not read an OpenAI Platform API key.
|
|
48
|
-
- Cancel sign-in through the plugin settings page or `/codex-login cancel`: cancellation succeeds before write linearization, while an in-progress commit is reported as too late and remains observed through its final state; `logout` always deletes the credential. The current DSH public seam has no post-commit cancellation barrier, so third-party code that calls `ctx.authorization.cancel()` directly bypasses this coordination and is not a supported `0.0.
|
|
49
|
+
- Cancel sign-in through the plugin settings page or `/codex-login cancel`: cancellation succeeds before write linearization, while an in-progress commit is reported as too late and remains observed through its final state; `logout` always deletes the credential. The current DSH public seam has no post-commit cancellation barrier, so third-party code that calls `ctx.authorization.cancel()` directly bypasses this coordination and is not a supported `0.0.2` interaction path.
|
|
49
50
|
- When a plugin or hot-reload flow owner is disposed, sign-in is cancelled before commit selection, while a selected credential commit is allowed to reach its final write before disposal completes. An unsupported or malformed stored record is shown as `invalid`, requires signing in again or signing out to clear it, and is never presented as signed in.
|
|
50
|
-
- The
|
|
51
|
+
- The usage card actively reads the real five-hour and weekly limits when the settings page opens and when the user refreshes it manually. The Host returns only strictly parsed windows, percentages, and reset times to the Web page, never OAuth credentials or raw responses. If the endpoint is unavailable or malformed, the latest safe reading is retained and the card falls back to request observation or an unknown state rather than presenting the failure as zero remaining usage.
|
|
51
52
|
- DSH Web search uses its own provider and credentials; it does not reuse ChatGPT OAuth.
|
|
52
|
-
- `0.0.
|
|
53
|
+
- `0.0.2` does not provide image generation or editing.
|
|
53
54
|
- Session compaction continues to use DSH's built-in automatic compaction and `/compact`.
|
package/docs/compatibility.md
CHANGED
|
@@ -4,29 +4,30 @@
|
|
|
4
4
|
|
|
5
5
|
最近更新:2026-08-29。
|
|
6
6
|
|
|
7
|
-
| 组件或环境 | `0.0.
|
|
7
|
+
| 组件或环境 | `0.0.2` 证据 | 状态 |
|
|
8
8
|
| --- | --- | --- |
|
|
9
9
|
| DeepSeek Harness | `test/fixtures/dsh-runtime/pnpm-lock.yaml` 锁定完整 `@deepseek-ai/dsh@0.1.1-rc.2` runtime/peer 图,并验证 `dsh-llm` schema 与 stream contract | 精确版本已验证 |
|
|
10
|
-
| pi-ai | `@earendil-works/pi-ai@0.82.1` 的 OAuth、模型目录、Codex payload 与 replay 合同测试 |
|
|
11
|
-
| Node.js | 本地 `22.22.2` 完整测试;[
|
|
12
|
-
| macOS
|
|
13
|
-
| Windows x64 | `windows-latest`
|
|
14
|
-
| Linux x64 | `ubuntu-latest`
|
|
10
|
+
| pi-ai | `@earendil-works/pi-ai@0.82.1` 的 OAuth、模型目录、Codex payload 与 replay 合同测试 | 公开合同已验证;0.0.2 真实网络验收 `0/13 pending` |
|
|
11
|
+
| Node.js | 本地 `22.22.2` 完整测试;[0.0.2 三平台候选运行](https://github.com/yoshino-xiao7/dsh-codex/actions/runs/33243698807)覆盖 Node 22/24 | `>=22.19.0 <25`;候选运行通过 |
|
|
12
|
+
| macOS | `macos-latest` Node 22/24 完整检查、冻结 DSH 安装与 Web/profile smoke | 平台门禁通过 |
|
|
13
|
+
| Windows x64 | `windows-latest` Node 22/24 完整检查、冻结 DSH 安装与 Web/profile smoke | 平台门禁通过;真实用户环境待验收 |
|
|
14
|
+
| Linux x64 | `ubuntu-latest` Node 22/24 完整检查、冻结 DSH 安装与 Web/profile smoke | 平台门禁通过 |
|
|
15
15
|
| ChatGPT OAuth 真实登录 | 自动化不读取或修改用户真实 grant | 受控验收待完成 |
|
|
16
|
+
| Codex 额度窗口 | 进入设置页和手动刷新都会通过 Host 主动读取并严格解析真实五小时与每周额度;本机 DSH 设置页已验证两种额度窗口 | 已验证;读取失败时保留最近安全读数或降级为请求观测/未知状态 |
|
|
16
17
|
| Codex 真实网络对话 | 自动化不消耗用户账户配额 | 受控验收待完成 |
|
|
17
18
|
| 文本 / reasoning / usage / 工具 / replay | 公开 PiAiAdapter 成功流、工具调用和两轮 replay 自动化已通过 | 真实 reasoning、工具闭环与连续对话待验收 |
|
|
18
19
|
| Codex 图片输入 | attachment seam 与预算投影自动化已通过 | `maxPixels=4194304` 真实请求待验收 |
|
|
19
20
|
| auto / SSE / WebSocket / cached | transport 映射与会话隔离自动化已通过 | 四种真实请求待验收 |
|
|
20
21
|
| Fast / priority tier | 已验证仅在当前会话开启时改变 `service_tier` | 账号权限与真实网络待验收 |
|
|
21
|
-
| npm / GitHub Release | 严格工作流校验候选、Registry
|
|
22
|
+
| npm / GitHub Release | 严格工作流校验候选、Registry 回读、provenance、签名与 Release 资产;发布制品见 [`v0.0.2`](https://github.com/yoshino-xiao7/dsh-codex/releases/tag/v0.0.2) | 正式发布由受保护工作流完成 |
|
|
22
23
|
|
|
23
|
-
`0.0.x` 是技术预览,只对表中的精确 DSH 预发布版本声明兼容。`0.0.
|
|
24
|
+
`0.0.x` 是技术预览,只对表中的精确 DSH 预发布版本声明兼容。`0.0.2` 的 Linux、macOS 和 Windows CI/profile smoke 已达到 `3/3`,候选提交、精确 Node 版本与作业链接记录在[验收记录](releases/v0.0.2.acceptance.json)中,并已获得维护者批准。真实 OAuth、对话、图片、transport 和 Fast 网络验收为 `0/13 pending`,正式发布后再逐项补齐;这些未完成项必须如实展示,不能冒充已验证能力。额度窗口读取已独立完成本机设置页验证,不计入这 13 个会消耗模型请求或需要完整交互闭环的验收项。
|
|
24
25
|
|
|
25
26
|
根目录 `pnpm-lock.yaml` 锁定插件依赖,`test/fixtures/dsh-runtime/pnpm-lock.yaml` 独立锁定兼容性 smoke 的完整 DSH runtime 与 peer 图;CI 使用 `pnpm --dir test/fixtures/dsh-runtime install --frozen-lockfile --ignore-scripts`,不把该 peer 图交给直接 npm 解算,以避免不确定依赖结果和内存失控。该冻结层验证 Web/profile 集成,不声称覆盖 DSH 依赖中需要生命周期脚本的原生终端或本机构建能力。依赖升级必须通过固定版本变更 PR,同时更新并审查两套 lockfile,在发布前重跑完整 CI、profile smoke 和供应链校验,发布后重新记录受控真实验证。定时兼容工作流只验证当前锁定图并报告 Registry 漂移,不能从宽泛 semver 或一次定时运行推断跨 RC 兼容。
|
|
26
27
|
|
|
27
28
|
## Profile 组合
|
|
28
29
|
|
|
29
|
-
`0.0.
|
|
30
|
+
`0.0.2` 的 bundle 不修改通用 `llm-pi-ai` Cordis row。它提供:
|
|
30
31
|
|
|
31
32
|
- `dsh-codex` route;
|
|
32
33
|
- `dsh-codex` 设置命名空间;
|
|
@@ -45,9 +46,9 @@
|
|
|
45
46
|
## 凭据与功能边界
|
|
46
47
|
|
|
47
48
|
- Codex route 只接受 ChatGPT OAuth,不读取 OpenAI Platform API key;
|
|
48
|
-
- 请通过插件设置页或 `/codex-login cancel` 取消登录:写入线性化前会正常取消,提交已经开始时会明确提示无法取消并继续等待最终状态;`logout` 始终删除凭据。DSH 当前公共 seam 没有“提交后禁止取消”能力,第三方代码直接调用 `ctx.authorization.cancel()` 会绕过这一协调,不属于 `0.0.
|
|
49
|
+
- 请通过插件设置页或 `/codex-login cancel` 取消登录:写入线性化前会正常取消,提交已经开始时会明确提示无法取消并继续等待最终状态;`logout` 始终删除凭据。DSH 当前公共 seam 没有“提交后禁止取消”能力,第三方代码直接调用 `ctx.authorization.cancel()` 会绕过这一协调,不属于 `0.0.2` 支持的交互路径;
|
|
49
50
|
- 插件或热重载 flow owner 卸载时,提交选择前的登录会取消,已经进入凭据提交的登录会等待最终写入再完成卸载;不支持类型或结构损坏的已存记录显示为 `invalid`,需要重新登录或退出清除,不会显示为已登录;
|
|
50
|
-
-
|
|
51
|
+
- 额度卡会在进入设置页和手动刷新时主动读取真实五小时与每周额度;Host 只向 Web 页面返回严格解析后的窗口、百分比和重置时间,不返回 OAuth 凭据或原始响应。接口不可用或结构异常时保留最近一次安全读数,并降级为请求观测或未知状态,不把失败显示成零余额;
|
|
51
52
|
- DSH 的 Web search 使用它自己的 provider 和凭据,不复用 ChatGPT OAuth;
|
|
52
|
-
- `0.0.
|
|
53
|
+
- `0.0.2` 不提供图片生成或编辑;
|
|
53
54
|
- 会话压缩继续使用 DSH 自带的自动压缩和 `/compact`。
|