codex-copilot-dx 0.9.10 → 0.9.12
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/README.md +7 -3
- package/package.json +1 -1
- package/src/diagnostic-text.mjs +9 -2
- package/src/models.mjs +1 -1
- package/src/responses-handler.mjs +12 -2
- package/src/usage.mjs +46 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ The adapter handles response history, encrypted reasoning state, function/custom
|
|
|
20
20
|
|
|
21
21
|
Codex can select Standard or Fast without changing CCDX configuration. When the client requests a model with `service_tier: "priority"`, CCDX uses its exact `-fast` model only when the live Copilot catalog advertises that variant as enabled, selectable, OpenAI-owned, and Responses-only. Standard mode continues to use the base model; CCDX does not invent Fast availability when the upstream catalog does not provide it. This retains the established `gpt-5.6-sol` / `gpt-5.6-sol-fast` mapping and applies the same exact-match rule to future models.
|
|
22
22
|
|
|
23
|
-
Codex Auto-review uses the hidden `codex-auto-review` model ID. CCDX maps it to Copilot's `gpt-
|
|
23
|
+
Codex Auto-review uses the hidden `codex-auto-review` model ID. CCDX maps it to Copilot's `gpt-6.1-sol` Responses model by default, independently of the interactive Fast/Standard selection, and logs both model IDs when the mapping is used. An omitted Auto-review reasoning effort defaults to `low`; explicit efforts and other reasoning fields are preserved. The review target can be changed interactively without reinstalling or restarting CCDX.
|
|
24
24
|
|
|
25
25
|
### GPT-6 model catalog
|
|
26
26
|
|
|
@@ -117,7 +117,7 @@ ccdx auto-review-model
|
|
|
117
117
|
|
|
118
118
|
The selector first queries the running adapter, including its last-known-good model list when live refresh is unavailable, then falls back to a local model cache no older than seven days. It offers model IDs that advertise a Responses endpoint and are not explicitly hidden from the model picker. This selection does not itself verify policy or inference access.
|
|
119
119
|
|
|
120
|
-
The selection is saved in `~/.config/codex-copilot-dx/config.json`, or under `XDG_CONFIG_HOME` when set. Choosing `gpt-
|
|
120
|
+
The selection is saved in `~/.config/codex-copilot-dx/config.json`, or under `XDG_CONFIG_HOME` when set. Choosing `gpt-6.1-sol` clears the override and restores the package default. Existing saved models, including `gpt-5.5`, and environment overrides remain effective. A running adapter reads the setting on the next Auto-review request. The selector changes the model, not the reasoning effort; Codex Guardian currently sends `low`. GPT-6.1 Sol does not support explicit `none` or `minimal` efforts.
|
|
121
121
|
|
|
122
122
|
### Runtime caches
|
|
123
123
|
|
|
@@ -352,7 +352,7 @@ Recovery first tries compatible local credentials and starts Device Flow only wh
|
|
|
352
352
|
| `CCDX_STREAM_IDLE_TIMEOUT_MS` | `120000` | Maximum idle time between upstream streaming body chunks |
|
|
353
353
|
| `CCDX_UPSTREAM_RETRIES` | `2` | Retries for safe requests and clearly pre-connect POST failures; capped at `5` |
|
|
354
354
|
| `CCDX_UPSTREAM_RETRY_DELAY_MS` | `300` | Initial upstream retry backoff in milliseconds; capped at `5000` |
|
|
355
|
-
| `CCDX_AUTO_REVIEW_MODEL` | saved selection or `gpt-
|
|
355
|
+
| `CCDX_AUTO_REVIEW_MODEL` | saved selection or `gpt-6.1-sol` | Copilot Responses model used for Codex Auto-review; overrides the interactive selection |
|
|
356
356
|
| `CCDX_LOG_PATH` | unset | Mirror terminal logs to a file; set to `1` for `~/.local/share/codex-copilot-dx/debug.log` |
|
|
357
357
|
| `CCDX_LOG_LEVEL` | `info` | Set to `debug` for upstream attempts, status codes, retry causes, and timings |
|
|
358
358
|
| `CCDX_LOG_MAX_BYTES` | `16777216` | Rotate the debug log at this size and retain one `.1` backup; set to `0` to disable rotation |
|
|
@@ -394,6 +394,10 @@ The summary covers the current log and its single rotated backup, not an unbound
|
|
|
394
394
|
|
|
395
395
|
Usage writes are asynchronous and batched under the existing cross-process rotation lock. Pending and in-flight records share a 4096-record / 4 MiB ceiling. If either ceiling is reached, new excess records are dropped with a warning and a status counter; inference does not wait or retry. Normal records and per-record rotation boundaries are unchanged. The bounded shutdown flush remains 1.5 seconds; disk failures and shutdown deadlines can prevent persistence, so usage is diagnostic rather than a billing ledger.
|
|
396
396
|
|
|
397
|
+
Repeated plain usage summaries in the same process reuse one bounded cache entry when both retained log files are unchanged. File identity, size, modification/change timestamps and a two-second recent-write guard control reuse; concurrent settled-file queries share one scan. The serialized summary budget is 256 KiB and at most 128 model groups; larger results remain complete but uncached. Warnings, unstable reads, metadata errors and scans with changed files are not retained. Each caller receives an independent result. Analytics record callbacks and custom-warning reads keep the original streaming path. There is no persistent index, background polling, cross-process cache or change to usage writes; opening analytics still scans the retained records.
|
|
398
|
+
|
|
399
|
+
Diagnostic redaction removes Basic authorization payloads and URL userinfo, and consumes escaped quotes inside sensitive JSON fields. Ordinary Basic-related prose, safe host/path information and error codes are retained. These protections apply to existing failure/status/debug diagnostics, not forwarded requests or upstream response bodies. They do not make arbitrary upstream prose guaranteed safe to share.
|
|
400
|
+
|
|
397
401
|
## Debug logging
|
|
398
402
|
|
|
399
403
|
Set `CCDX_LOG_PATH=1` to mirror terminal logs to `~/.local/share/codex-copilot-dx/debug.log`, or provide a custom path. Add `CCDX_LOG_LEVEL=debug` for upstream attempts, retry causes, status codes, and timings. The adapter does not deliberately dump request/response bodies or authorization headers. File diagnostics redact common credential forms and labelled content echoes, but this cannot identify every secret in arbitrary external prose; review logs before sharing them. File writes are asynchronous and batched with a 4096-record / 2 MiB pending-and-in-flight ceiling. Overflow drops excess file records with one warning and a status counter; terminal output continues. CLI exit paths flush for at most 1.5 seconds. `ccdx doctor config` bypasses file logging entirely.
|
package/package.json
CHANGED
package/src/diagnostic-text.mjs
CHANGED
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
// but remove common credential forms and explicitly labelled content echoes.
|
|
3
3
|
export function redactDiagnosticText(value) {
|
|
4
4
|
return String(value ?? "")
|
|
5
|
-
.replace(
|
|
5
|
+
.replace(/(\b(?:proxy[-_]?)?authorization["']?\s*[:=]\s*)(?:Basic|Bearer)\s+[^\s,;"'<>\\]+/gi, "$1[redacted]")
|
|
6
|
+
.replace(/\b(Bearer|Basic)\s+([^\s,;"'<>\\]+)/gi, (match, scheme, value) => {
|
|
7
|
+
// Standalone ordinary prose such as "basic authentication" is not a credential.
|
|
8
|
+
if (scheme.toLowerCase() === "basic"
|
|
9
|
+
&& (!/^[a-z0-9+/_-]+={0,2}$/i.test(value) || !Buffer.from(value, "base64").includes(0x3a))) return match;
|
|
10
|
+
return `${scheme} [redacted]`;
|
|
11
|
+
})
|
|
12
|
+
.replace(/\b([a-z][a-z0-9+.-]{0,63}:\/\/)[^\s/?#"'<>]*@/gi, "$1[redacted]@")
|
|
6
13
|
.replace(/\b(?:github_pat_|gh[pousr]_|sk-|copilot_)[A-Za-z0-9._-]+/gi, "[redacted]")
|
|
7
|
-
.replace(/((?:api[_-]?key|access[_-]?token|refresh[_-]?token|authorization|cookie)["']?\s*[:=]\s*)(?:"[^"
|
|
14
|
+
.replace(/((?:api[_-]?key|access[_-]?token|refresh[_-]?token|authorization|cookie)["']?\s*[:=]\s*)(?:"(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*'|[^\s,;]+)/gi, "$1[redacted]")
|
|
8
15
|
.replace(/((?:prompt|input|completion|tool_arguments|arguments|content)["']?\s*[:=]\s*)[^\n]+/gi, "$1[redacted]")
|
|
9
16
|
.replace(/(encrypted(?: function output)? content\s+)[A-Za-z0-9+/_=-]{24,}/gi, "$1[redacted]")
|
|
10
17
|
.replace(/\b[A-Za-z0-9+/_=-]{96,}\b/g, "[redacted]");
|
package/src/models.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const CODEX_AUTO_REVIEW_MODEL = "codex-auto-review";
|
|
2
|
-
export const DEFAULT_CODEX_AUTO_REVIEW_MODEL = "gpt-
|
|
2
|
+
export const DEFAULT_CODEX_AUTO_REVIEW_MODEL = "gpt-6.1-sol";
|
|
3
3
|
export const CODEX_GPT6_MODEL = "gpt-6-astra";
|
|
4
4
|
|
|
5
5
|
function hasOpenAIEndpoint(model) {
|
|
@@ -272,7 +272,12 @@ export function createResponsesHandler(options) {
|
|
|
272
272
|
delete prepared.body.service_tier;
|
|
273
273
|
}
|
|
274
274
|
stripUnsupportedGpt6ServiceTier(prepared.body, requestedModel, upstreamModel, priorityTierModel);
|
|
275
|
-
if (requestedModel === CODEX_AUTO_REVIEW_MODEL)
|
|
275
|
+
if (requestedModel === CODEX_AUTO_REVIEW_MODEL) {
|
|
276
|
+
delete prepared.body.service_tier;
|
|
277
|
+
if (prepared.body.reasoning?.effort === undefined) {
|
|
278
|
+
prepared.body.reasoning = { ...prepared.body.reasoning, effort: "low" };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
276
281
|
if (upstreamModel !== requestedModel) prepared.body.model = upstreamModel;
|
|
277
282
|
markResponseModel(upstreamModel);
|
|
278
283
|
const upstreamLog = upstreamModel === requestedModel ? "" : ` upstream_model=${upstreamModel}`;
|
|
@@ -537,7 +542,12 @@ export function createResponsesCompactHandler(options) {
|
|
|
537
542
|
delete prepared.body.service_tier;
|
|
538
543
|
}
|
|
539
544
|
stripUnsupportedGpt6ServiceTier(prepared.body, requestedModel, upstreamModel, priorityTierModel);
|
|
540
|
-
if (requestedModel === CODEX_AUTO_REVIEW_MODEL)
|
|
545
|
+
if (requestedModel === CODEX_AUTO_REVIEW_MODEL) {
|
|
546
|
+
delete prepared.body.service_tier;
|
|
547
|
+
if (prepared.body.reasoning?.effort === undefined) {
|
|
548
|
+
prepared.body.reasoning = { ...prepared.body.reasoning, effort: "low" };
|
|
549
|
+
}
|
|
550
|
+
}
|
|
541
551
|
if (upstreamModel !== requestedModel) prepared.body.model = upstreamModel;
|
|
542
552
|
const upstreamLog = upstreamModel === requestedModel ? "" : ` upstream_model=${upstreamModel}`;
|
|
543
553
|
console.log(status("info", `responses compact model=${requestedModel}${upstreamLog} stream=false`));
|
package/src/usage.mjs
CHANGED
|
@@ -288,7 +288,7 @@ export function summarizeUsage(records) {
|
|
|
288
288
|
return summary;
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
|
|
291
|
+
async function summarizeUsageLogFiles(filePath, { warn = console.error, onRecord } = {}) {
|
|
292
292
|
const summary = { requests: 0, totals: {}, byModel: {} };
|
|
293
293
|
for (const candidate of [rotatedFilePath(filePath), filePath]) {
|
|
294
294
|
for await (const record of iterateUsageRecords(candidate, { warn })) {
|
|
@@ -305,6 +305,51 @@ export async function summarizeUsageLogs(filePath = usageLogPath(), { warn = con
|
|
|
305
305
|
return summary;
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
const SUMMARY_CACHE_MAX_BYTES = 256 * 1024;
|
|
309
|
+
const SUMMARY_CACHE_SETTLED_MS = 2000;
|
|
310
|
+
let cachedSummary = null;
|
|
311
|
+
|
|
312
|
+
async function usageSummaryVersion(filePath) {
|
|
313
|
+
try {
|
|
314
|
+
const files = await Promise.all([rotatedFilePath(filePath), filePath].map(async (file) => {
|
|
315
|
+
try {
|
|
316
|
+
const stat = await fs.promises.stat(file, { bigint: true });
|
|
317
|
+
if (!stat.isFile()) return null;
|
|
318
|
+
const recent = Math.max(Number(stat.mtimeMs), Number(stat.ctimeMs));
|
|
319
|
+
if (Date.now() - recent < SUMMARY_CACHE_SETTLED_MS) return null;
|
|
320
|
+
return [stat.dev, stat.ino, stat.size, stat.mtimeNs, stat.ctimeNs].join(":");
|
|
321
|
+
} catch (error) {
|
|
322
|
+
if (error?.code === "ENOENT") return "missing";
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
}));
|
|
326
|
+
return files.some((version) => version === null) ? null : `${path.resolve(filePath)}\0${files.join("|")}`;
|
|
327
|
+
} catch { return null; } // Uncacheable metadata never replaces the normal read/error path.
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export async function summarizeUsageLogs(filePath = usageLogPath(), { warn = console.error, onRecord } = {}) {
|
|
331
|
+
if (onRecord || warn !== console.error) return summarizeUsageLogFiles(filePath, { warn, onRecord });
|
|
332
|
+
const version = await usageSummaryVersion(filePath);
|
|
333
|
+
if (version === null) {
|
|
334
|
+
cachedSummary = null;
|
|
335
|
+
return summarizeUsageLogFiles(filePath, { warn });
|
|
336
|
+
}
|
|
337
|
+
if (cachedSummary?.version === version) return structuredClone(await cachedSummary.promise);
|
|
338
|
+
const entry = { version, promise: null };
|
|
339
|
+
cachedSummary = entry;
|
|
340
|
+
entry.promise = (async () => {
|
|
341
|
+
let warned = false;
|
|
342
|
+
const summary = await summarizeUsageLogFiles(filePath, { warn(message) { warned = true; warn(message); } });
|
|
343
|
+
const after = await usageSummaryVersion(filePath);
|
|
344
|
+
const retain = !warned && after === version && Object.keys(summary.byModel).length <= 128
|
|
345
|
+
&& Buffer.byteLength(JSON.stringify(summary)) <= SUMMARY_CACHE_MAX_BYTES;
|
|
346
|
+
if (!retain && cachedSummary === entry) cachedSummary = null;
|
|
347
|
+
return summary;
|
|
348
|
+
})();
|
|
349
|
+
try { return structuredClone(await entry.promise); }
|
|
350
|
+
catch (error) { if (cachedSummary === entry) cachedSummary = null; throw error; }
|
|
351
|
+
}
|
|
352
|
+
|
|
308
353
|
function fmt(n) {
|
|
309
354
|
return Number.isFinite(n) ? n.toLocaleString("en-US") : "0";
|
|
310
355
|
}
|