@khorsheed/dsh-local-agent-codex 0.2.0-rc.1 → 0.2.0-rc.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.en.md +32 -18
- package/README.i18n.yaml +2 -2
- package/README.md +32 -18
- package/lib/client.js +816 -322
- package/lib/index.js +501 -104
- package/lib/types/client/SettingsCard.d.ts +73 -24
- package/lib/types/client/SettingsCard.js +110 -140
- package/lib/types/client/index.d.ts +6 -26
- package/lib/types/client/index.js +136 -50
- package/lib/types/client/locales.d.ts +34 -24
- package/lib/types/client/locales.js +38 -28
- package/lib/types/codex-cli-provider.d.ts +21 -4
- package/lib/types/codex-cli-provider.js +60 -27
- package/lib/types/endpoint-auth.d.ts +194 -0
- package/lib/types/endpoint-auth.js +300 -0
- package/lib/types/index.d.ts +19 -3
- package/lib/types/index.js +127 -28
- package/lib/types/live-driver.d.ts +9 -0
- package/lib/types/live-driver.js +16 -2
- package/lib/types/live-switch.d.ts +7 -0
- package/lib/types/live-switch.js +1 -0
- package/lib/types/model-broker.d.ts +12 -2
- package/lib/types/model-broker.js +9 -5
- package/lib/types/model-catalog.d.ts +53 -9
- package/lib/types/model-catalog.js +95 -12
- package/lib/types/provision.d.ts +12 -27
- package/lib/types/provision.js +19 -40
- package/lib/types/records.d.ts +0 -7
- package/lib/types/records.js +1 -19
- package/locale/en.json +2 -2
- package/locale/zh.json +2 -2
- package/package.json +20 -21
- package/lib/invariant.js +0 -31
- package/lib/types/invariant.d.ts +0 -16
- package/lib/types/invariant.js +0 -33
|
@@ -16,10 +16,11 @@
|
|
|
16
16
|
import { randomUUID } from 'node:crypto';
|
|
17
17
|
import { createAssistantMessage, createToolResultMessage, createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
18
18
|
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
19
|
-
import { NO_START_CAPABILITIES, settleRunResult, subprocessRunHandle, } from '@deepseek-ai/dsh-subagent';
|
|
19
|
+
import { NO_START_CAPABILITIES, SUBAGENT_DESCRIPTOR_VERSION, settleRunResult, subprocessRunHandle, } from '@deepseek-ai/dsh-subagent';
|
|
20
20
|
import { assertResumeCwdUnchanged, assertResumeScopeUnchanged, resolveRoundModel, containerExecSpawn, containerScopedHome, delegationEnv, establishSubagentCatalogChild, persistChildSession, probeCliVersion, resolveChildCwd, subagentDelegationLabel, } from '@khorsheed/dsh-local-agent';
|
|
21
21
|
import { MEMBER_BRIDGE_SOCKET_ENV, MEMBER_BRIDGE_TOKEN_ENV } from '@khorsheed/dsh-local-agent/types';
|
|
22
22
|
import { LiveChannelUnavailableError } from './live-driver.js';
|
|
23
|
+
import { endpointAuthEnv, endpointConfigOverrides, hasEndpointCredential } from './endpoint-auth.js';
|
|
23
24
|
import { readCodexBaseUrl } from './provision.js';
|
|
24
25
|
import { codexRolloutRoundFacts, usageFromCodex } from './records.js';
|
|
25
26
|
/** Quote one TOML basic string for the `-c` config override. */
|
|
@@ -116,6 +117,7 @@ export class CodexCliProvider {
|
|
|
116
117
|
live;
|
|
117
118
|
model;
|
|
118
119
|
overrides;
|
|
120
|
+
endpoint;
|
|
119
121
|
name = 'codex-local';
|
|
120
122
|
capabilities = NO_START_CAPABILITIES;
|
|
121
123
|
inheritsParentContext = false;
|
|
@@ -126,22 +128,33 @@ export class CodexCliProvider {
|
|
|
126
128
|
* while a retiring generation still hosts it).
|
|
127
129
|
* @param model - resolver for the configured model, read PER ROUND so a
|
|
128
130
|
* settings-card write takes effect on the next delegation without a
|
|
129
|
-
* reload
|
|
130
|
-
*
|
|
131
|
-
*
|
|
131
|
+
* reload; it receives the round's scope (a named scope's curated list
|
|
132
|
+
* answers first). Undefined (the resolver absent, or returning
|
|
133
|
+
* undefined) leaves the argv exactly as it was before the key existed —
|
|
134
|
+
* the scoped config.toml's own `model` then decides, as it always did.
|
|
132
135
|
* @param overrides - resolver for the member's session-level model override
|
|
133
136
|
* (the composer picker, owned by the model broker). It outranks BOTH the
|
|
134
137
|
* delegation's recorded model and the configured one: on the exec path it
|
|
135
138
|
* re-points the next round's `-m`, and on the live path it is the member's
|
|
136
139
|
* start model — a resident runtime bound to a different model is retired
|
|
137
140
|
* so the round respawns onto the override.
|
|
141
|
+
* @param endpoint - resolver for the scope's endpoint credential, read PER
|
|
142
|
+
* ROUND so a settings-card write takes effect on the next delegation
|
|
143
|
+
* without a reload. A resolution carrying a key routes the round at the
|
|
144
|
+
* relay (a custom `model_providers` entry on the argv, the key in the
|
|
145
|
+
* env); with no key the spawn carries no credential at all.
|
|
138
146
|
*/
|
|
139
|
-
constructor(ctx, sandbox = 'workspace-write', live, model, overrides) {
|
|
147
|
+
constructor(ctx, sandbox = 'workspace-write', live, model, overrides, endpoint) {
|
|
140
148
|
this.ctx = ctx;
|
|
141
149
|
this.sandbox = sandbox;
|
|
142
150
|
this.live = live;
|
|
143
151
|
this.model = model;
|
|
144
152
|
this.overrides = overrides;
|
|
153
|
+
this.endpoint = endpoint;
|
|
154
|
+
}
|
|
155
|
+
/** The scope's endpoint credential for this round; absent resolver = never. */
|
|
156
|
+
resolveEndpoint(homeDir) {
|
|
157
|
+
return this.endpoint === undefined ? Promise.resolve(undefined) : this.endpoint(homeDir).catch(() => undefined);
|
|
145
158
|
}
|
|
146
159
|
/** Resolve the live driver for one round's member, if live is on for it. */
|
|
147
160
|
liveDriver(childSessionId) {
|
|
@@ -198,7 +211,7 @@ export class CodexCliProvider {
|
|
|
198
211
|
...record?.configurationLock === undefined ? {} : { configurationLock: record.configurationLock },
|
|
199
212
|
}, configuration => this.startCodexResume(request, intent, cwd, homeDir, exec, scope, record?.model, configuration), request.signal, intent?.onAdmitted);
|
|
200
213
|
}
|
|
201
|
-
const childSessionId = SessionId(intent?.preparedMemberId ?? randomUUID());
|
|
214
|
+
const childSessionId = SessionId(intent?.preparedMemberId ?? intent?.childSessionId ?? randomUUID());
|
|
202
215
|
if (typeof this.ctx.localAgent.withMemberConfigurationRound !== 'function') {
|
|
203
216
|
if (intent?.effort !== undefined)
|
|
204
217
|
throw new Error('Codex effort requires the configuration admission core');
|
|
@@ -231,14 +244,20 @@ export class CodexCliProvider {
|
|
|
231
244
|
},
|
|
232
245
|
});
|
|
233
246
|
const harness = this.ctx.localAgent.get('codex');
|
|
234
|
-
|
|
247
|
+
// Host 0.2.1 no longer resolves a descriptor onto the request (the
|
|
248
|
+
// runtime's one-shot snapshot is gone), so the provider stamps the same
|
|
249
|
+
// payload itself; the activation spec's required label arrives as the
|
|
250
|
+
// empty string when the caller named none.
|
|
251
|
+
const label = subagentDelegationLabel(harness?.displayName ?? 'codex', request.label || undefined);
|
|
235
252
|
childSession.append('subagent/descriptor', {
|
|
236
|
-
|
|
253
|
+
version: SUBAGENT_DESCRIPTOR_VERSION,
|
|
254
|
+
mode: 'one-shot',
|
|
255
|
+
provider: this.name,
|
|
237
256
|
label,
|
|
238
257
|
});
|
|
239
|
-
// The
|
|
240
|
-
//
|
|
241
|
-
//
|
|
258
|
+
// The 0.2.1 manager appends the parent-side subagent/catalog discovery
|
|
259
|
+
// row only for parent-delivered children; the family runs caller
|
|
260
|
+
// delivery, so the provider appends it here — once per child, where
|
|
242
261
|
// the descriptor lands. A failure degrades to the warn below; a missing
|
|
243
262
|
// row never blocks the delegation.
|
|
244
263
|
establishSubagentCatalogChild(request.parent.session, childSession.header, label);
|
|
@@ -251,11 +270,15 @@ export class CodexCliProvider {
|
|
|
251
270
|
catch (error) {
|
|
252
271
|
this.ctx.logger.warn(`subagent-codex: subagent session record failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
253
272
|
}
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
|
|
273
|
+
// The round's endpoint: a configured endpoint credential (the scope's
|
|
274
|
+
// endpoint-auth.json over the plugin config) routes the argv through a
|
|
275
|
+
// custom provider entry and its key rides the env. With no credential the
|
|
276
|
+
// scoped config.toml's own custom provider answers for the diagnostic
|
|
277
|
+
// exactly as before (a user-edited value is authoritative). Logged BEFORE
|
|
278
|
+
// the drive-mode branch so live rounds report their endpoint too.
|
|
279
|
+
const endpoint = await this.resolveEndpoint(homeDir);
|
|
280
|
+
const routed = hasEndpointCredential(endpoint);
|
|
281
|
+
const baseUrl = routed ? endpoint.baseUrl : await readCodexBaseUrl(homeDir).catch(() => undefined);
|
|
259
282
|
this.ctx.logger.info(`subagent-codex: delegating via ${baseUrl ?? 'codex default endpoint'}`);
|
|
260
283
|
// Live driver: the round goes to the resident app-server process (one per
|
|
261
284
|
// member). A channel that fails at spawn/handshake falls through to the
|
|
@@ -314,11 +337,12 @@ export class CodexCliProvider {
|
|
|
314
337
|
try {
|
|
315
338
|
const run = await startCodexCliRun(request, {
|
|
316
339
|
cwd,
|
|
317
|
-
env: delegationEnv({ CODEX_HOME: homeDir }),
|
|
340
|
+
env: delegationEnv({ CODEX_HOME: homeDir, ...endpointAuthEnv(endpoint) }),
|
|
318
341
|
...exec === undefined ? {} : { exec },
|
|
319
342
|
endpointLabel: baseUrl,
|
|
343
|
+
endpointArgv: endpointConfigOverrides(endpoint),
|
|
320
344
|
sandbox: this.sandbox,
|
|
321
|
-
...(configuration?.resolved ?? resolveRoundModel(this.overrides?.(runId) ?? requestedModel, this.model)),
|
|
345
|
+
...(configuration?.resolved ?? resolveRoundModel(this.overrides?.(runId) ?? requestedModel, () => this.model?.(scope))),
|
|
322
346
|
disposeGraceMs: DEFAULT_DISPOSE_GRACE_MS,
|
|
323
347
|
spawn: spec => this.ctx.subprocess.spawn(spec),
|
|
324
348
|
onError: (error, stopReason) => {
|
|
@@ -383,7 +407,11 @@ export class CodexCliProvider {
|
|
|
383
407
|
throw new Error(`subagent-codex: resume target child session ${intent.childSessionId} is not live — start a fresh delegation instead`);
|
|
384
408
|
}
|
|
385
409
|
const nextTurn = childSession.snapshotEvents().filter(event => event.type === 'turn/start').length + 1;
|
|
386
|
-
|
|
410
|
+
// Same endpoint precedence as the fresh path: a configured endpoint
|
|
411
|
+
// credential routes the round at the relay, else the scoped
|
|
412
|
+
// config.toml's own custom provider answers for the diagnostic.
|
|
413
|
+
const endpoint = await this.resolveEndpoint(homeDir);
|
|
414
|
+
const baseUrl = hasEndpointCredential(endpoint) ? endpoint.baseUrl : await readCodexBaseUrl(homeDir).catch(() => undefined);
|
|
387
415
|
// Logged BEFORE the drive-mode branch so live rounds report their endpoint too.
|
|
388
416
|
this.ctx.logger.info(`subagent-codex: resuming via ${baseUrl ?? 'codex default endpoint'}`);
|
|
389
417
|
// Live driver: continue the member's resident app-server thread. Channel
|
|
@@ -423,11 +451,12 @@ export class CodexCliProvider {
|
|
|
423
451
|
try {
|
|
424
452
|
run = await startCodexCliRun(request, {
|
|
425
453
|
cwd,
|
|
426
|
-
env: delegationEnv({ CODEX_HOME: homeDir }),
|
|
454
|
+
env: delegationEnv({ CODEX_HOME: homeDir, ...endpointAuthEnv(endpoint) }),
|
|
427
455
|
...exec === undefined ? {} : { exec },
|
|
428
456
|
endpointLabel: baseUrl,
|
|
457
|
+
endpointArgv: endpointConfigOverrides(endpoint),
|
|
429
458
|
sandbox: this.sandbox,
|
|
430
|
-
...(configuration?.resolved ?? resolveRoundModel(this.overrides?.(intent.childSessionId) ?? requestedModel, this.model)),
|
|
459
|
+
...(configuration?.resolved ?? resolveRoundModel(this.overrides?.(intent.childSessionId) ?? requestedModel, () => this.model?.(scope))),
|
|
431
460
|
disposeGraceMs: DEFAULT_DISPOSE_GRACE_MS,
|
|
432
461
|
spawn: spec => this.ctx.subprocess.spawn(spec),
|
|
433
462
|
onError: (error, stopReason) => {
|
|
@@ -697,7 +726,9 @@ export function startCodexCliRun(request, spec) {
|
|
|
697
726
|
const turn = spec.resume?.turn ?? 1;
|
|
698
727
|
// The member bridge rides a per-process `-c` override placed right after
|
|
699
728
|
// `exec` (the spike-verified position; `codex exec resume` accepts it too).
|
|
729
|
+
// The endpoint credential's overrides sit next to it — `-c` pairs commute.
|
|
700
730
|
const memberArgv = spec.member === undefined ? [] : ['-c', spec.member.configOverride];
|
|
731
|
+
const endpointArgv = spec.endpointArgv ?? [];
|
|
701
732
|
// `--skip-git-repo-check` rides every exec argv. Codex refuses to start
|
|
702
733
|
// outside a Git repository ("Not inside a trusted directory and
|
|
703
734
|
// --skip-git-repo-check was not specified") and exits before producing a
|
|
@@ -718,8 +749,8 @@ export function startCodexCliRun(request, spec) {
|
|
|
718
749
|
...spec.effort === undefined ? [] : ['-c', `model_reasoning_effort=${JSON.stringify(spec.effort)}`],
|
|
719
750
|
];
|
|
720
751
|
const argv = spec.resume === undefined
|
|
721
|
-
? ['codex', 'exec', ...memberArgv, ...modelArgv, '--sandbox', spec.sandbox, '--skip-git-repo-check', '--json', task]
|
|
722
|
-
: ['codex', 'exec', ...memberArgv, ...modelArgv, '--sandbox', spec.sandbox, '--skip-git-repo-check', '--json', 'resume', spec.resume.cliSessionId, task];
|
|
752
|
+
? ['codex', 'exec', ...memberArgv, ...endpointArgv, ...modelArgv, '--sandbox', spec.sandbox, '--skip-git-repo-check', '--json', task]
|
|
753
|
+
: ['codex', 'exec', ...memberArgv, ...endpointArgv, ...modelArgv, '--sandbox', spec.sandbox, '--skip-git-repo-check', '--json', 'resume', spec.resume.cliSessionId, task];
|
|
723
754
|
// Container target: the same argv, wrapped in `docker exec`. The host cwd
|
|
724
755
|
// still applies — it is the docker CLIENT's working directory now, while
|
|
725
756
|
// the CLI's own is the target's in-container workdir.
|
|
@@ -911,10 +942,12 @@ export function startCodexCliRun(request, spec) {
|
|
|
911
942
|
return mirrorCodexAfterExit(spec, task, turn, output, liveMirror, startedAtMs);
|
|
912
943
|
}, () => { });
|
|
913
944
|
return Promise.resolve(subprocessRunHandle({
|
|
914
|
-
//
|
|
915
|
-
//
|
|
916
|
-
//
|
|
917
|
-
id
|
|
945
|
+
// Host 0.2.1: the published run id is a fresh PER-EXECUTION identity —
|
|
946
|
+
// the activation manager rejects a run id naming a live Session (the
|
|
947
|
+
// mirrored child session is exactly that), and resume rounds reuse the
|
|
948
|
+
// child session id. The durable child identity the family tracks rides
|
|
949
|
+
// the staged intent instead (spec.childSession.id == its childSessionId).
|
|
950
|
+
id: SessionId(randomUUID()),
|
|
918
951
|
result,
|
|
919
952
|
signal: request.signal,
|
|
920
953
|
onAbort,
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Endpoint-key authentication — the codex harness's ONLY auth mode. There is
|
|
3
|
+
* no web login anywhere in this package: a delegation round reaches its
|
|
4
|
+
* endpoint with a custom `model_providers` entry on the argv pointing at the
|
|
5
|
+
* relay and the key traveling as `OPENAI_API_KEY` in the process environment
|
|
6
|
+
* (verified against codex-cli 0.163.0: a fresh scoped home with only these
|
|
7
|
+
* runs `codex exec` with no login file at all). With no credential the spawn
|
|
8
|
+
* carries no auth at all — it fails loudly at the endpoint, exactly the
|
|
9
|
+
* honest reading the status shows.
|
|
10
|
+
*
|
|
11
|
+
* Verified against codex-cli 0.163.0 (2026-10-20): a fresh scoped home with
|
|
12
|
+
* only `OPENAI_API_KEY` plus the two `-c` overrides below runs
|
|
13
|
+
* `codex exec -m <model>` end-to-end through an OpenAI-compatible relay
|
|
14
|
+
* (openproxy, `wire_api = "responses"`) — no login, no auth.json, no
|
|
15
|
+
* onboarding gate.
|
|
16
|
+
*
|
|
17
|
+
* Two layers, merged per field, file over config:
|
|
18
|
+
*
|
|
19
|
+
* - `<scopedHome>/endpoint-auth.json` (mode 0600) — the per-scope credential.
|
|
20
|
+
* Its name deliberately matches the eval contract hash's credential denylist
|
|
21
|
+
* (`auth`), so the key material never enters an evaluation's condition
|
|
22
|
+
* fingerprint; the file's mtime serves as the credential stamp, which makes
|
|
23
|
+
* "edit the file" the recovery gesture for a `rejected` grade.
|
|
24
|
+
* - The plugin config's `baseUrl` / `apiKey` — the deployment default every
|
|
25
|
+
* scope without a file shares. Read PER ROUND, so a profile edit applies to
|
|
26
|
+
* the next round (and a host restart clears the in-memory `rejected` mark).
|
|
27
|
+
*
|
|
28
|
+
* `wireApi` is a file-only escape hatch: the settings card never edits it,
|
|
29
|
+
* the default `responses` is codex's native wire (relay-verified), and a
|
|
30
|
+
* hand-edit to `"chat"` survives the card's whole-file writes because the
|
|
31
|
+
* read merges it back. The env layer rides the family's existing plumbing:
|
|
32
|
+
* `delegationEnv` on the host, `containerExecSpawn`'s name-only `-e`
|
|
33
|
+
* forwarding into a container — a containerized round authenticates with the
|
|
34
|
+
* same credential as the host, nothing is authorized inside the unit.
|
|
35
|
+
* @module @khorsheed/dsh-local-agent-codex/endpoint-auth
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* The scoped-home credential file's name. Chosen to match the eval contract
|
|
39
|
+
* hash's credential-shaped denylist (`/token|key|credential|secret|password|auth|\.env/i`),
|
|
40
|
+
* so the file is excluded from condition fingerprints by name.
|
|
41
|
+
*/
|
|
42
|
+
export declare const ENDPOINT_AUTH_FILE = "endpoint-auth.json";
|
|
43
|
+
/** The custom provider's TOML key in the `-c` overrides (bare-key safe). */
|
|
44
|
+
export declare const ENDPOINT_PROVIDER_NAME = "dsh-endpoint";
|
|
45
|
+
/** The env var the custom provider reads the key from (`env_key`). */
|
|
46
|
+
export declare const ENDPOINT_ENV_KEY = "OPENAI_API_KEY";
|
|
47
|
+
/**
|
|
48
|
+
* One scope's endpoint credential. `baseUrl` is optional (absent means the
|
|
49
|
+
* CLI's own default OpenAI endpoint — an official platform key needs no
|
|
50
|
+
* relay); a non-empty `apiKey` is what makes the scope endpoint-authenticated.
|
|
51
|
+
* `wireApi` selects the custom provider's wire protocol; absent defaults to
|
|
52
|
+
* `responses` (codex's native wire, relay-verified).
|
|
53
|
+
*/
|
|
54
|
+
export interface CodexEndpointAuth {
|
|
55
|
+
readonly baseUrl?: string;
|
|
56
|
+
/** Sent as the custom provider's `env_key` environment value (Bearer). */
|
|
57
|
+
readonly apiKey?: string;
|
|
58
|
+
/** `responses` (default) or `chat`; file-only, never card-edited. */
|
|
59
|
+
readonly wireApi?: string;
|
|
60
|
+
}
|
|
61
|
+
/** Whether a resolved endpoint carries a usable credential. */
|
|
62
|
+
export declare function hasEndpointCredential(auth: CodexEndpointAuth | undefined): auth is CodexEndpointAuth & {
|
|
63
|
+
apiKey: string;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Read the scope's endpoint-auth.json. Absent or unreadable yields undefined
|
|
67
|
+
* (the caller falls back to the plugin config); malformed JSON or a non-object
|
|
68
|
+
* payload also yields undefined — a torn file must not fail a status read.
|
|
69
|
+
* Non-string fields are dropped, never carried through.
|
|
70
|
+
* @param homeDir - the `codex` harness's scoped home.
|
|
71
|
+
* @returns the file's endpoint credential, or undefined.
|
|
72
|
+
*/
|
|
73
|
+
export declare function readEndpointAuthFile(homeDir: string): Promise<CodexEndpointAuth | undefined>;
|
|
74
|
+
/**
|
|
75
|
+
* Resolve one scope's effective endpoint credential: the scope's
|
|
76
|
+
* endpoint-auth.json merged FIELD BY FIELD over the plugin config's
|
|
77
|
+
* `baseUrl` / `apiKey` (a file that names only a key rotates the key while
|
|
78
|
+
* keeping the deployment's relay URL). The result may carry no credential at
|
|
79
|
+
* all — {@link hasEndpointCredential} is the judgment, kept separate so the
|
|
80
|
+
* caller can still route/log the resolved baseUrl.
|
|
81
|
+
* @param homeDir - the scope's scoped home.
|
|
82
|
+
* @param config - the plugin config's deployment-level endpoint pair.
|
|
83
|
+
* @returns the merged endpoint auth.
|
|
84
|
+
*/
|
|
85
|
+
export declare function resolveEndpointAuth(homeDir: string, config: CodexEndpointAuth): Promise<CodexEndpointAuth>;
|
|
86
|
+
/**
|
|
87
|
+
* The spawn-env layer for a resolved endpoint: the key under the custom
|
|
88
|
+
* provider's `env_key` name, nothing else. A credential-less resolution
|
|
89
|
+
* yields an empty layer — the spread then adds no keys at all, byte-identical
|
|
90
|
+
* to the pre-endpoint env. With no baseUrl the CLI's built-in OpenAI provider
|
|
91
|
+
* reads the same variable, so an official platform key needs no `-c`
|
|
92
|
+
* override either.
|
|
93
|
+
* @param auth - the resolved endpoint auth, or undefined.
|
|
94
|
+
* @returns the explicit env entries for the delegation spawn.
|
|
95
|
+
*/
|
|
96
|
+
export declare function endpointAuthEnv(auth: CodexEndpointAuth | undefined): NodeJS.ProcessEnv;
|
|
97
|
+
/**
|
|
98
|
+
* The `-c` config overrides routing the CLI at the resolved relay: a custom
|
|
99
|
+
* `model_providers` entry plus the `model_provider` selection, as a flat argv
|
|
100
|
+
* fragment (`-c <override>` pairs). Empty unless BOTH a key and a baseUrl are
|
|
101
|
+
* present — a key without a relay URL is served by the built-in provider
|
|
102
|
+
* through the env layer alone, and a relay URL without a key would route
|
|
103
|
+
* every round into a 401. Verified end-to-end against codex-cli 0.163.0.
|
|
104
|
+
* @param auth - the resolved endpoint auth, or undefined.
|
|
105
|
+
* @returns the argv fragment, possibly empty.
|
|
106
|
+
*/
|
|
107
|
+
export declare function endpointConfigOverrides(auth: CodexEndpointAuth | undefined): string[];
|
|
108
|
+
/**
|
|
109
|
+
* The credential marker's modification stamp (epoch ms): the endpoint file's
|
|
110
|
+
* mtime, or undefined when the scope has no file (a config-sourced credential
|
|
111
|
+
* has no marker — rotating it pairs with the profile edit and instance
|
|
112
|
+
* restart that clear the in-memory grade marks). A fresh file write is a
|
|
113
|
+
* fresh stamp, which is how an edited-in key recovers a `rejected` grade.
|
|
114
|
+
* @param homeDir - the scope's scoped home.
|
|
115
|
+
* @returns the stamp, or undefined.
|
|
116
|
+
*/
|
|
117
|
+
export declare function endpointAuthStamp(homeDir: string): Promise<number | undefined>;
|
|
118
|
+
/** Whether the scope carries its own endpoint file at all. */
|
|
119
|
+
export declare function endpointAuthFileExists(homeDir: string): Promise<boolean>;
|
|
120
|
+
/**
|
|
121
|
+
* Write the scope's endpoint-auth.json (mode 0600, mkdir -p first). The
|
|
122
|
+
* payload preserves the file-only `wireApi` across card writes: callers pass
|
|
123
|
+
* the merged object (current file + edits), never a bare card payload.
|
|
124
|
+
* @param homeDir - the scope's scoped home.
|
|
125
|
+
* @param auth - the endpoint credential to persist.
|
|
126
|
+
*/
|
|
127
|
+
export declare function writeEndpointAuth(homeDir: string, auth: CodexEndpointAuth): Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* Clear the scope's OWN endpoint credential (the endpoint-auth.json file).
|
|
130
|
+
* A deployment-level credential from the plugin config is untouched — the
|
|
131
|
+
* core's logout reply then names the profile row as the place to edit.
|
|
132
|
+
* @param homeDir - the scope's scoped home.
|
|
133
|
+
*/
|
|
134
|
+
export declare function clearEndpointAuth(homeDir: string): Promise<void>;
|
|
135
|
+
/** The official endpoint used when no baseUrl is configured anywhere. */
|
|
136
|
+
export declare const OFFICIAL_BASE_URL = "https://api.openai.com";
|
|
137
|
+
/** The probe's fetch shape (injected in tests). */
|
|
138
|
+
export type EndpointCheckFetch = (url: string, init: {
|
|
139
|
+
headers: Record<string, string>;
|
|
140
|
+
signal: AbortSignal;
|
|
141
|
+
}) => Promise<{
|
|
142
|
+
ok: boolean;
|
|
143
|
+
status: number;
|
|
144
|
+
text(): Promise<string>;
|
|
145
|
+
}>;
|
|
146
|
+
/**
|
|
147
|
+
* The outcome of one `GET <baseUrl>/v1/models`: the parsed id list, the HTTP
|
|
148
|
+
* status when the endpoint answered, or the transport error's message. No
|
|
149
|
+
* case throws.
|
|
150
|
+
*/
|
|
151
|
+
export type EndpointModelsResult = {
|
|
152
|
+
readonly kind: 'models';
|
|
153
|
+
readonly models: readonly string[];
|
|
154
|
+
} | {
|
|
155
|
+
readonly kind: 'http';
|
|
156
|
+
readonly status: number;
|
|
157
|
+
readonly body: string;
|
|
158
|
+
} | {
|
|
159
|
+
readonly kind: 'unreachable';
|
|
160
|
+
readonly message: string;
|
|
161
|
+
} | {
|
|
162
|
+
readonly kind: 'malformed';
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* `GET <baseUrl>/v1/models` with `Authorization: Bearer <key>` — the
|
|
166
|
+
* token-free endpoint probe shared by the `/codex check` verdict and any
|
|
167
|
+
* future catalog. OpenAI's own API serves this route with the platform key;
|
|
168
|
+
* relays (openproxy verified 2026-10-20, 139 models) proxy it and answer the
|
|
169
|
+
* same OpenAI-list shape. Never throws: every failure mode folds into the
|
|
170
|
+
* result.
|
|
171
|
+
* @param endpoint - the resolved endpoint auth with a decided baseUrl.
|
|
172
|
+
* @param fetchImpl - the HTTP fetch; default the host's global fetch.
|
|
173
|
+
* @param signal - abort for the whole probe (timeout bundled by the caller).
|
|
174
|
+
* @returns the folded result.
|
|
175
|
+
*/
|
|
176
|
+
export declare function fetchEndpointModels(endpoint: CodexEndpointAuth & {
|
|
177
|
+
readonly baseUrl: string;
|
|
178
|
+
}, fetchImpl?: EndpointCheckFetch, signal?: AbortSignal): Promise<EndpointModelsResult>;
|
|
179
|
+
/**
|
|
180
|
+
* Probe the resolved endpoint with a cheap, token-free round-trip: `GET
|
|
181
|
+
* <baseUrl>/v1/models` with the resolved credential's Bearer header. A 200
|
|
182
|
+
* proves both reachability and credential validity — the exact evidence a
|
|
183
|
+
* "verify" button needs — and the served model list rides the reply. One
|
|
184
|
+
* transport-level retry rides along: a local proxy restart (a Surge/clash
|
|
185
|
+
* reload) stalls the first attempt into the timeout and answers the retry
|
|
186
|
+
* immediately. Never throws: every failure mode folds into the reply text,
|
|
187
|
+
* since the reply IS the diagnostic.
|
|
188
|
+
* @param endpoint - the resolved endpoint auth (scope file over settings).
|
|
189
|
+
* @param fetchImpl - the HTTP fetch; default the host's global fetch.
|
|
190
|
+
* @param timeoutMs - one attempt's deadline (two attempts at most).
|
|
191
|
+
* @returns the human-readable verdict for the check reply.
|
|
192
|
+
*/
|
|
193
|
+
export declare function checkCodexEndpoint(endpoint: CodexEndpointAuth, fetchImpl?: EndpointCheckFetch, timeoutMs?: number): Promise<string>;
|
|
194
|
+
//# sourceMappingURL=endpoint-auth.d.ts.map
|