diff --git a/.github/actions/setup-project-bun/action.yml b/.github/actions/setup-project-bun/action.yml index 1ab22dd1dc9..cf28f6f9d43 100644 --- a/.github/actions/setup-project-bun/action.yml +++ b/.github/actions/setup-project-bun/action.yml @@ -8,8 +8,8 @@ description: >- # (#6713 run 37631558263: Linux 1/4, macOS 2/2, Windows 9/9). It moved from 1.4.0 to # 1.4.3 for oven-sh/bun#44581: 1.4.0's Bun.spawnSync could lose a child's exit after # finalizers ran inside an earlier call and spin past its own timeout (#4956). -# Rejoin only after the upstream runner crash is fixed and a green lane=all -# dispatch proves the unified version; then delete testRunnerBun and this role input. +# Both roles now default to the package's 1.4.4 security transition, retaining role validation. +# Promotion requires a green full cross-platform lane=all run on the exact candidate head. inputs: role: description: Bun role to install (runtime or test-runner). diff --git a/.github/scripts/enforce-pr-target.test.cjs b/.github/scripts/enforce-pr-target.test.cjs index 8973f07a63b..3fc9c675daa 100644 --- a/.github/scripts/enforce-pr-target.test.cjs +++ b/.github/scripts/enforce-pr-target.test.cjs @@ -94,7 +94,9 @@ describe("enforce-pr-target workflow", () => { it("writes exactly one consolidated comment via a single upsert helper", () => { assert.match(workflow, /GATE_MARKER,/); - assert.match(workflow, /comment\.body\?\.includes\(GATE_MARKER\)/); + // The gate comment is the bot comment that starts with GATE_MARKER; a + // marker echoed inside another comment does not classify it. + assert.match(workflow, /comments\.find\(isGateComment\)/); assert.match(workflow, /upsertGateComment/); assert.match(workflow, /buildGateCommentBody/); // No legacy two-comment write path remains. diff --git a/.github/scripts/pr-carry-attribution.cjs b/.github/scripts/pr-carry-attribution.cjs index fa4894330d8..401b3b50ba2 100644 --- a/.github/scripts/pr-carry-attribution.cjs +++ b/.github/scripts/pr-carry-attribution.cjs @@ -49,7 +49,16 @@ const SENTENCE_END_RE = /[.!?](?:\s|$)|\n/; function carryWindow(text, from) { const slice = text.slice(from, from + 80); const end = slice.search(SENTENCE_END_RE); - return end === -1 ? slice : slice.slice(0, end); + if (end !== -1) return slice.slice(0, end); + + // Finish only the number already started in this window (including a bare + // hash at the cap). The bounded suffix check and forward digit walk keep + // untrusted text linear and leave owner/repo qualifiers intact for REF_RE. + let to = from + slice.length; + if (/#\d*$/.test(slice)) { + while (to < text.length && text[to] >= "0" && text[to] <= "9") to++; + } + return text.slice(from, to); } const TRAILER_RE = /^[ \t]*co-authored-by:[ \t]*(.+)$/gim; diff --git a/.github/scripts/pr-carry-attribution.test.cjs b/.github/scripts/pr-carry-attribution.test.cjs index 8d94fc707ba..0f4781dfe67 100644 --- a/.github/scripts/pr-carry-attribution.test.cjs +++ b/.github/scripts/pr-carry-attribution.test.cjs @@ -4,6 +4,7 @@ const { describe, it } = require("node:test"); const assert = require("node:assert/strict"); const { assessCarryAttribution, + carryWindow, referencedCarryNumbers, } = require("./pr-carry-attribution.cjs"); @@ -291,6 +292,38 @@ describe("assessCarryAttribution", () => { assert.deepEqual(failures[0].paths, ["#3193"]); }); + it("keeps the complete references from the #7223 subject", () => { + const subject = "carry Combo visibility, workspace panes, Windows picker and Gentle Shell (#7195, #6767"; + assert.deepEqual([...referencedCarryNumbers(subject)], [7195, 6767]); + }); + + it("finishes a number or hash at the cap without admitting the next reference", () => { + for (const reference of ["#6767", "other/repo#6767"]) { + for (let included = 1; included <= 5; included++) { + const padding = " ".repeat(80 - reference.length + 5 - included); + const text = padding + reference + " #3192"; + assert.equal(carryWindow(text, 0), padding + reference); + assert.deepEqual( + [...referencedCarryNumbers("carry" + text)], + reference.startsWith("#") ? [6767] : [], + ); + } + } + }); + + it("excludes references starting at or beyond the cap and after a newline", () => { + for (const padding of [80, 81]) { + assert.deepEqual([...referencedCarryNumbers("carry" + " ".repeat(padding) + "#3192")], []); + } + assert.deepEqual([...referencedCarryNumbers("carry #3193\n#3192")], [3193]); + }); + + it("completes a long numeric suffix without scanning the following text", () => { + const digits = "7".repeat(100_000); + const text = " ".repeat(79) + "#" + digits; + assert.equal(carryWindow(text + " #3192", 0), text); + }); + it("reads a trailer that only exists on a branch commit", () => { assert.deepEqual( assessCarryAttribution( diff --git a/.github/scripts/pr-hygiene.cjs b/.github/scripts/pr-hygiene.cjs index e0be6ae2c20..82586d4dea4 100644 --- a/.github/scripts/pr-hygiene.cjs +++ b/.github/scripts/pr-hygiene.cjs @@ -292,6 +292,109 @@ const HYGIENE_GATE_LABELS = [ "attribution-approved", ]; +/** + * Exception approvals vouch for one revision of the change. Each counts only + * while the head the hygiene workflow last recorded is still the head being + * evaluated and nothing has rewritten the branch since. Sponsorship + * (`maintainer-sponsored`) is about which surfaces change, not about one + * revision, so it is not head-bound. + */ +const HEAD_BOUND_APPROVAL_LABELS = [ + "test-exception-approved", + "suppression-approved", + "generated-change-approved", + "dependency-change-approved", + "attribution-approved", +]; + +/** Issue events after which the head can point at an earlier commit again. */ +const HEAD_REWRITE_EVENTS = ["head_ref_force_pushed", "head_ref_restored"]; + +const APPROVAL_RECORD_TOKEN = "pr-hygiene-approvals"; +const APPROVAL_RECORD_LINE = /^$/; +const FULL_SHA = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/; + +function isRewriteCount(value) { + return Number.isSafeInteger(value) && value >= 0; +} + +/** + * Read the head binding the hygiene workflow wrote as the last line of its + * own section. The token must occur exactly once in `source` and sit on that + * last line, so echoed text (a changed path can contain newlines) can only + * make the record unreadable, never stand in for it. + * @returns {{ head: string, headRewrites: number | null } | null} + */ +function parseApprovalRecord(source) { + if (typeof source !== "string") return null; + if (source.split(APPROVAL_RECORD_TOKEN).length !== 2) return null; + const lines = source.split("\n").map((line) => line.trim()).filter(Boolean); + const match = lines.at(-1)?.match(APPROVAL_RECORD_LINE); + if (!match) return null; + let value; + try { + value = JSON.parse(match[1]); + } catch { + return null; + } + if (value === null || typeof value !== "object" || Array.isArray(value)) return null; + if (Object.keys(value).sort().join(",") !== "head,headRewrites") return null; + if (typeof value.head !== "string" || !FULL_SHA.test(value.head)) return null; + if (value.headRewrites !== null && !isRewriteCount(value.headRewrites)) return null; + return { head: value.head, headRewrites: value.headRewrites }; +} + +/** `headRewrites` is null when the history read failed: nothing is confirmed. */ +function formatApprovalRecord({ head, headRewrites }) { + const value = { head, headRewrites: isRewriteCount(headRewrites) ? headRewrites : null }; + return ""; +} + +function countHeadRewrites(events) { + return (events ?? []).filter((entry) => HEAD_REWRITE_EVENTS.includes(entry?.event)).length; +} + +function presentHeadBoundApprovals(labels) { + return HEAD_BOUND_APPROVAL_LABELS.filter((label) => labels.includes(label)); +} + +/** True when a current rewrite count could still confirm an approval. */ +function needsHeadRewriteCount({ labels = [], headSha, record = null, eventAction }) { + return ( + eventAction !== "synchronize" && + record !== null && + record.head === headSha && + isRewriteCount(record.headRewrites) && + presentHeadBoundApprovals(labels).length > 0 + ); +} + +/** + * Split the head-bound approvals on the pull request into the ones that still + * describe the evaluated head and the ones that do not. They are kept only + * when the last recorded evaluation saw this same head and the rewrite count, + * read before that evaluation's snapshot and again after this one's, has not + * changed. Without a rewrite, fast-forward pushes cannot bring the recorded + * head back, so the head has not moved since that evaluation, which removed + * every approval it could not confirm. A synchronize event, a missing record, + * or an unknown count confirms nothing. + * @returns {{ kept: string[], stale: string[] }} + */ +function reconcileHeadBoundApprovals({ + labels = [], + headSha, + record = null, + eventAction, + headRewriteCount = null, +}) { + const present = presentHeadBoundApprovals(labels); + const current = + needsHeadRewriteCount({ labels, headSha, record, eventAction }) && + isRewriteCount(headRewriteCount) && + headRewriteCount === record.headRewrites; + return current ? { kept: present, stale: [] } : { kept: [], stale: present }; +} + /** * Combine patch-hygiene and sponsored-surface failures into one list so the * hygiene workflow and the PR quality gate cannot disagree about Ready. @@ -340,6 +443,10 @@ module.exports = { addedLines, assessHygiene, collectDeterministicHygieneFailures, + countHeadRewrites, + formatApprovalRecord, + HEAD_BOUND_APPROVAL_LABELS, + HEAD_REWRITE_EVENTS, HYGIENE_FAILURE_HINTS, HYGIENE_GATE_LABELS, hasEmptyCatch, @@ -347,6 +454,9 @@ module.exports = { isBehaviorPath, isGeneratedPath, isTestPath, + needsHeadRewriteCount, + parseApprovalRecord, + reconcileHeadBoundApprovals, resultLines, resultLinesByHunk, }; diff --git a/.github/scripts/pr-hygiene.test.cjs b/.github/scripts/pr-hygiene.test.cjs index 3ab5a8b2ca1..64548f0b011 100644 --- a/.github/scripts/pr-hygiene.test.cjs +++ b/.github/scripts/pr-hygiene.test.cjs @@ -6,9 +6,15 @@ const { addedLines, assessHygiene, collectDeterministicHygieneFailures, + countHeadRewrites, + formatApprovalRecord, + HEAD_BOUND_APPROVAL_LABELS, HYGIENE_FAILURE_HINTS, HYGIENE_GATE_LABELS, hasEmptyCatch, + needsHeadRewriteCount, + parseApprovalRecord, + reconcileHeadBoundApprovals, resultLines, resultLinesByHunk, } = require("./pr-hygiene.cjs"); @@ -391,3 +397,100 @@ describe("pr-hygiene workflow trust boundary", () => { ); }); }); + +describe("head-bound approvals", () => { + const HEAD = "c".repeat(40); + const OTHER = "d".repeat(40); + const record = (head, headRewrites) => + ""; + + it("covers exactly the exception approvals, not sponsorship", () => { + assert.deepEqual( + [...HEAD_BOUND_APPROVAL_LABELS].sort(), + HYGIENE_GATE_LABELS.filter( + (label) => label !== "intake: hygiene-blocked" && label !== "maintainer-sponsored", + ).sort(), + ); + }); + + it("round-trips a record, including an unknown rewrite count", () => { + const line = formatApprovalRecord({ head: HEAD, headRewrites: 2 }); + assert.equal(line, record(HEAD, 2)); + assert.deepEqual(parseApprovalRecord("status\n\n" + line), { head: HEAD, headRewrites: 2 }); + assert.deepEqual(parseApprovalRecord(formatApprovalRecord({ head: HEAD, headRewrites: undefined })), { + head: HEAD, + headRewrites: null, + }); + assert.deepEqual(parseApprovalRecord(record("e".repeat(64), 0)), { head: "e".repeat(64), headRewrites: 0 }); + }); + + it("rejects anything but one well-formed record on the last line", () => { + for (const source of [ + null, + "", + record(HEAD, 0) + "\nlater line", + record(HEAD, 0) + "\n" + record(HEAD, 0), + "mentions pr-hygiene-approvals elsewhere\n" + record(HEAD, 0), + "", + "", + "", + "", + record("abc", 0), + record(HEAD.toUpperCase(), 0), + record(HEAD, -1), + record(HEAD, 1.5), + record(HEAD, "1"), + ]) { + assert.equal(parseApprovalRecord(source), null, String(source)); + } + }); + + it("counts only head rewrite events", () => { + assert.equal( + countHeadRewrites([ + { event: "head_ref_force_pushed" }, + { event: "labeled" }, + { event: "head_ref_restored" }, + null, + ]), + 2, + ); + assert.equal(countHeadRewrites(undefined), 0); + }); + + it("keeps approvals only under an unchanged head and rewrite count", () => { + const labels = ["test-exception-approved", "maintainer-sponsored"]; + const base = { labels, headSha: HEAD, record: { head: HEAD, headRewrites: 1 }, eventAction: "edited" }; + const kept = { kept: ["test-exception-approved"], stale: [] }; + const stale = { kept: [], stale: ["test-exception-approved"] }; + assert.deepEqual(reconcileHeadBoundApprovals({ ...base, headRewriteCount: 1 }), kept); + assert.deepEqual(reconcileHeadBoundApprovals({ ...base, headRewriteCount: 2 }), stale); + assert.deepEqual(reconcileHeadBoundApprovals({ ...base, headRewriteCount: null }), stale); + assert.deepEqual(reconcileHeadBoundApprovals({ ...base, headRewriteCount: 1, eventAction: "synchronize" }), stale); + assert.deepEqual(reconcileHeadBoundApprovals({ ...base, headRewriteCount: 1, headSha: OTHER }), stale); + assert.deepEqual(reconcileHeadBoundApprovals({ ...base, headRewriteCount: 1, record: null }), stale); + assert.deepEqual( + reconcileHeadBoundApprovals({ ...base, headRewriteCount: 1, record: { head: HEAD, headRewrites: null } }), + stale, + ); + assert.deepEqual( + reconcileHeadBoundApprovals({ ...base, labels: ["maintainer-sponsored"], headRewriteCount: 1 }), + { kept: [], stale: [] }, + ); + }); + + it("asks for a rewrite count only when one could confirm an approval", () => { + const base = { + labels: ["suppression-approved"], + headSha: HEAD, + record: { head: HEAD, headRewrites: 0 }, + eventAction: "labeled", + }; + assert.equal(needsHeadRewriteCount(base), true); + assert.equal(needsHeadRewriteCount({ ...base, labels: [] }), false); + assert.equal(needsHeadRewriteCount({ ...base, eventAction: "synchronize" }), false); + assert.equal(needsHeadRewriteCount({ ...base, headSha: OTHER }), false); + assert.equal(needsHeadRewriteCount({ ...base, record: null }), false); + assert.equal(needsHeadRewriteCount({ ...base, record: { head: HEAD, headRewrites: null } }), false); + }); +}); diff --git a/.github/scripts/pr-quality-messages.cjs b/.github/scripts/pr-quality-messages.cjs index e9b642b574b..56233036ebc 100644 --- a/.github/scripts/pr-quality-messages.cjs +++ b/.github/scripts/pr-quality-messages.cjs @@ -30,6 +30,18 @@ const HYGIENE_BLOCK_RE = new RegExp( `^[ \\t]*${HYGIENE_BLOCK_START}[ \\t]*\\n([\\s\\S]*?)\\n[ \\t]*${HYGIENE_BLOCK_END}[ \\t]*$`, "m" ); +/** + * Whole-line delimiter matchers, the same shape HYGIENE_BLOCK_RE anchors on. + * A block is only read when each delimiter occurs on exactly one line: a + * changed filename can contain newlines, so an echoed path can add delimiter + * lines, and an ambiguous block must never be cut at the first one. + */ +const HYGIENE_BLOCK_START_LINE_RE = new RegExp(`^[ \\t]*${HYGIENE_BLOCK_START}[ \\t]*$`, "gm"); +const HYGIENE_BLOCK_END_LINE_RE = new RegExp(`^[ \\t]*${HYGIENE_BLOCK_END}[ \\t]*$`, "gm"); + +function countDelimiterLines(text, pattern) { + return (text.match(pattern) ?? []).length; +} function inlineCode(value) { const text = String(value); @@ -41,6 +53,37 @@ function inlineCode(value) { return `${delimiter}${text}${delimiter}`; } +/** + * Render contributor-supplied text (a changed path) on one line: control + * characters, including newlines, become `\uXXXX` escapes. Without this an + * echoed path could add whole lines, such as delimiter or record lines, to a + * bot comment. + */ +function escapeControlCharacters(value) { + return String(value).replace( + /[\u0000-\u001f\u007f\u2028\u2029]/g, + character => "\\u" + character.charCodeAt(0).toString(16).padStart(4, "0") + ); +} + +/** The consolidated gate comment: written by the workflow bot, marker first. */ +function isGateComment(comment) { + return ( + comment?.user?.login === "github-actions[bot]" && + typeof comment.body === "string" && + comment.body.startsWith(GATE_MARKER) + ); +} + +/** The standalone hygiene comment posted before a gate comment exists. */ +function isStandaloneHygieneComment(comment) { + return ( + comment?.user?.login === "github-actions[bot]" && + typeof comment.body === "string" && + comment.body.startsWith(HYGIENE_MARKER) + ); +} + function readinessChecklistLines(readiness) { return REVIEW_READINESS_ITEMS.map( (item, index) => @@ -121,9 +164,17 @@ function buildGateCommentBody(state, opts) { * `null` when the comment has none. The gate rebuilds its body from scratch * every run, so without this round-trip a hygiene update from the separate * hygiene workflow would be silently dropped on the next gate write. + * An ambiguous block (a delimiter on more than one line) reads as `null`, so + * the gate drops it on rebuild instead of re-wrapping a truncated section. */ function extractHygieneSection(body) { if (typeof body !== "string") return null; + if ( + countDelimiterLines(body, HYGIENE_BLOCK_START_LINE_RE) !== 1 || + countDelimiterLines(body, HYGIENE_BLOCK_END_LINE_RE) !== 1 + ) { + return null; + } const match = body.match(HYGIENE_BLOCK_RE); if (!match) return null; return match[1] @@ -150,13 +201,40 @@ function withHygieneSection(body, hygieneLines) { ].join("\n"); if (HYGIENE_BLOCK_RE.test(base)) { - return base.replace(HYGIENE_BLOCK_RE, block); + // A callback, not a replacement string: echoed paths can contain $&, $1 + // and similar tokens that a template would expand into extra lines. + return base.replace(HYGIENE_BLOCK_RE, () => block); } // No existing block: append one at the end. return `${base.replace(/\s+$/, "")}\n\n## Hygiene\n\n${block}\n`; } +/** + * The text the hygiene workflow last wrote for this pull request: the hygiene + * block of the first bot comment that starts with GATE_MARKER, or the first + * bot comment that starts with HYGIENE_MARKER while that gate comment has no + * block yet (the gate never absorbs it). A marker elsewhere in a comment does + * not classify it. An ambiguous gate block yields `null`; later comments are + * never searched for a more convenient candidate. + */ +function authoritativeHygieneSection(comments) { + const list = comments ?? []; + const gate = list.find(isGateComment); + if (gate) { + const section = extractHygieneSection(gate.body); + if (section !== null) return section; + if ( + countDelimiterLines(gate.body, HYGIENE_BLOCK_START_LINE_RE) > 0 || + countDelimiterLines(gate.body, HYGIENE_BLOCK_END_LINE_RE) > 0 + ) { + return null; + } + } + const standalone = list.find(isStandaloneHygieneComment); + return standalone ? standalone.body : null; +} + function descriptionFailureLines(reason) { switch (reason) { case "empty": @@ -323,10 +401,14 @@ module.exports = { HYGIENE_BLOCK_START, HYGIENE_BLOCK_END, inlineCode, + escapeControlCharacters, + isGateComment, + isStandaloneHygieneComment, readinessChecklistLines, buildGateCommentBody, extractHygieneSection, withHygieneSection, + authoritativeHygieneSection, descriptionFailureLines, buildFailureSections, failureSummary, diff --git a/.github/scripts/pr-quality-messages.test.cjs b/.github/scripts/pr-quality-messages.test.cjs index c344ce1bc06..f4665ae0d6a 100644 --- a/.github/scripts/pr-quality-messages.test.cjs +++ b/.github/scripts/pr-quality-messages.test.cjs @@ -15,6 +15,10 @@ const { buildGateCommentBody, extractHygieneSection, withHygieneSection, + authoritativeHygieneSection, + escapeControlCharacters, + isGateComment, + isStandaloneHygieneComment, descriptionFailureLines, buildFailureSections, failureSummary, @@ -421,3 +425,75 @@ describe("hygiene section round-trip", () => { assert.equal(afterGateSecond.split(HYGIENE_BLOCK_END).length - 1, 1); }); }); + +describe("authoritative hygiene section", () => { + const bot = { login: "github-actions[bot]" }; + const block = (lines) => + `${HYGIENE_BLOCK_START}\n${HYGIENE_MARKER}\n\n${lines.join("\n")}\n\n${HYGIENE_BLOCK_END}`; + + it("reads nothing from a block whose delimiters repeat on whole lines", () => { + const body = [GATE_MARKER, HYGIENE_BLOCK_START, "a", HYGIENE_BLOCK_END, "b", HYGIENE_BLOCK_END].join("\n"); + assert.equal(extractHygieneSection(body), null); + const twoStarts = [GATE_MARKER, HYGIENE_BLOCK_START, HYGIENE_BLOCK_START, "a", HYGIENE_BLOCK_END].join("\n"); + assert.equal(extractHygieneSection(twoStarts), null); + }); + + it("prefers the gate comment's block over a standalone hygiene comment", () => { + const comments = [ + { user: bot, body: `${HYGIENE_MARKER}\n\nstandalone` }, + { user: bot, body: `${GATE_MARKER}\n\n${block(["from gate"])}` }, + ]; + assert.equal(authoritativeHygieneSection(comments), "from gate"); + }); + + it("falls back to the standalone comment only while the gate comment has no block", () => { + const standalone = { user: bot, body: `${HYGIENE_MARKER}\n\nstandalone` }; + assert.equal(authoritativeHygieneSection([{ user: bot, body: GATE_MARKER }, standalone]), standalone.body); + assert.equal(authoritativeHygieneSection([standalone]), standalone.body); + const ambiguous = { user: bot, body: [GATE_MARKER, HYGIENE_BLOCK_START, "a", HYGIENE_BLOCK_END, HYGIENE_BLOCK_END].join("\n") }; + assert.equal(authoritativeHygieneSection([ambiguous, standalone]), null); + }); + + it("ignores comments not written by the workflow bot", () => { + const human = { user: { login: "contributor" }, body: `${GATE_MARKER}\n\n${block(["forged"])}` }; + assert.equal(authoritativeHygieneSection([human]), null); + assert.equal(authoritativeHygieneSection(undefined), null); + }); +}); + +describe("echoed text and comment classification", () => { + const bot = { login: "github-actions[bot]" }; + + it("escapes control characters so echoed text stays on one line", () => { + assert.equal(escapeControlCharacters("src/a.ts"), "src/a.ts"); + assert.equal(escapeControlCharacters("a\nb\r\tc\u2028"), "a\\u000ab\\u000d\\u0009c\\u2028"); + assert.ok(!escapeControlCharacters("x\n" + HYGIENE_BLOCK_END).includes("\n")); + }); + + it("classifies a comment only by the marker its body starts with", () => { + assert.equal(isGateComment({ user: bot, body: GATE_MARKER + "\n\nbody" }), true); + assert.equal(isGateComment({ user: bot, body: HYGIENE_MARKER + "\n- x " + GATE_MARKER }), false); + assert.equal(isGateComment({ user: { login: "someone" }, body: GATE_MARKER }), false); + assert.equal(isStandaloneHygieneComment({ user: bot, body: HYGIENE_MARKER + "\n\nok" }), true); + assert.equal(isStandaloneHygieneComment({ user: bot, body: GATE_MARKER + "\n" + HYGIENE_MARKER }), false); + }); + + it("does not read a standalone hygiene comment that echoes the gate marker as the gate block", () => { + const standalone = { + user: bot, + body: [HYGIENE_MARKER, "- x " + GATE_MARKER, HYGIENE_BLOCK_START, "echoed", HYGIENE_BLOCK_END].join("\n"), + }; + assert.equal(authoritativeHygieneSection([standalone]), standalone.body); + }); +}); + +describe("hygiene block replacement", () => { + it("writes replacement tokens in hygiene lines literally", () => { + const base = withHygieneSection(GATE_MARKER, ["first"]); + for (const token of ["$&", "$1", "$`", "$'", "$$"]) { + const updated = withHygieneSection(base, ["Paths: src/" + token + ".ts"]); + assert.ok(updated.includes("Paths: src/" + token + ".ts"), token); + assert.equal(extractHygieneSection(updated), "Paths: src/" + token + ".ts", token); + } + }); +}); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2df7ddab928..b6d7e7ad467 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ # ============================================================================ # CI POLICY (maintainer decision — do not change in a contributor PR) -# * Pull requests run ONLY the fast Ubuntu subset. +# * Pull requests run ONLY the fast Ubuntu subset (default: gates + `test changed`). # * Full cross-platform (macOS/Windows) CI runs ONLY via manual # workflow_dispatch and on push to main/preview (release lines). # * NEVER add a dev push trigger or pull_request runs for macOS/Windows jobs. @@ -25,11 +25,9 @@ on: # carry contributor prefixes (`fix/`, `feat/`, `agent/`) as readily as # `codex/`, and contributor stacks need CI most. The `changes` job below is # the real scope gate, using the same allowlist as the push trigger. Safe to - # widen here - # because this workflow is `pull_request` (not `pull_request_target`), + # widen here because this workflow is `pull_request` (not `pull_request_target`), # declares `contents: read`, and reads no secrets. # - # # Pull requests run only the fast Ubuntu subset. Every macOS and Windows # job (and the macOS/Windows legs of the keyring, npm-global, setup-action # and remote-helper matrices) is skipped on `pull_request` and runs after @@ -94,7 +92,6 @@ on: permissions: contents: read -# Retrigger CI after dir-fsync / oauth deadline follow-ups (tip 34a1ac46). concurrency: # `push` and `pull_request` want supersession: a newer head on the same ref # makes the older one irrelevant, and cancelling it saves an hour of runners @@ -216,6 +213,7 @@ jobs: # of silently making every expensive job skip. ci: ${{ steps.scope.outputs.ci }} desktop: ${{ steps.scope.outputs.desktop }} + fast: ${{ steps.lane.outputs.fast }} # true: `test changed` replaces the four shards native: ${{ steps.matrices.outputs.native }} # Matrix include lists for keyring-smoke and npm-global-smoke, built and # shape-checked by the same validation step as `native`. @@ -233,6 +231,10 @@ jobs: # skipping the check it selects. setup_action: ${{ steps.narrow.outputs.setup_action }} remote_helper: ${{ steps.narrow.outputs.remote_helper }} + # Pull-request scope for the two smoke jobs that used to ride the whole ci filter. + # Push, dispatch and schedule run both regardless; see the docker/keyring filters. + docker: ${{ steps.narrow.outputs.docker }} + keyring: ${{ steps.narrow.outputs.keyring }} # Whether this pull request needs the nine Windows shards: the windows path # filter below OR an added src/tests line naming a Windows marker. Computed # by scripts/ci/windows-sensitive-diff.sh, which validates its input and @@ -388,6 +390,44 @@ jobs: # real only on macOS and Windows, so its job lints and tests the crate on all three. remote_helper: - 'native/remote-workspace-helper/**' + # docker-smoke and keyring-smoke on a pull request. Both used to run on every PR the + # ci filter matched, which is nearly all of them, while each proves something narrow: + # the image builds, starts and survives recreation with its volume; the OS keyring + # binding creates, reads and deletes a secret. A pull request now runs each only when + # its own inputs move. Push to main/preview, workflow_dispatch and the nightly still + # request both unconditionally, so the release gate's push run always carries them. + # ci.yml is listed so an edit to either job verifies itself. The setup action is left to + # its own setup-action job, as the docs and structure filters leave it. + docker: + - 'Dockerfile' + - 'compose.yaml' + - '.dockerignore' + - 'docker/**' + - 'scripts/ci/docker-smoke.ts' + - 'scripts/generate-compatibility-version.ts' + - 'scripts/model-metadata.source.json' + # The image copies and runs src/, then builds the GUI with tsc and Vite. + # Include their inputs without selecting GUI tests or documentation alone. + - 'src/**' + - 'package.json' + - 'bun.lock' + - 'tsconfig.json' + - 'gui/package.json' + - 'gui/bun.lock' + - 'gui/src/**' + - 'gui/public/**' + - 'gui/index.html' + - 'gui/vite.config.ts' + - 'gui/tsconfig*.json' + - '.github/workflows/ci.yml' + # scripts/keyring-smoke.ts imports only src/lib/keyring-native.ts; the native binding + # itself arrives through package.json and bun.lock. + keyring: + - 'src/lib/keyring-native.ts' + - 'scripts/keyring-smoke.ts' + - 'package.json' + - 'bun.lock' + - '.github/workflows/ci.yml' # Paths whose change selects the nine Windows suite shards on a pull # request. These carry the Windows service, update, shim, intercept, # ACL and process code, and the harness every Windows shard runs. @@ -532,9 +572,11 @@ jobs: env: SETUP_ACTION: ${{ github.event_name == 'schedule' && 'true' || steps.filter.outputs.setup_action }} REMOTE_HELPER: ${{ github.event_name == 'schedule' && 'true' || steps.filter.outputs.remote_helper }} + DOCKER_SCOPE: ${{ github.event_name == 'schedule' && 'true' || steps.filter.outputs.docker }} + KEYRING_SCOPE: ${{ github.event_name == 'schedule' && 'true' || steps.filter.outputs.keyring }} run: | set -euo pipefail - for pair in "setup_action=$SETUP_ACTION" "remote_helper=$REMOTE_HELPER"; do + for pair in "setup_action=$SETUP_ACTION" "remote_helper=$REMOTE_HELPER" "docker=$DOCKER_SCOPE" "keyring=$KEYRING_SCOPE"; do case "${pair#*=}" in true|false) printf '%s\n' "$pair" >> "$GITHUB_OUTPUT" @@ -546,6 +588,15 @@ jobs: esac done + - name: Choose the pull request test lane + id: lane + shell: bash + env: + EVENT_NAME: ${{ github.event_name }} + CI_SCOPE: ${{ steps.scope.outputs.ci }} + FULL_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'ci:full') }} + run: bash scripts/ci/select-test-lane.sh + - name: Decide whether the Windows shards run id: windows shell: bash @@ -578,12 +629,10 @@ jobs: printf 'Scheduled run: workflow from %s, testing dev at %s\n' "$GITHUB_SHA" "$sha" >> "$GITHUB_STEP_SUMMARY" printf 'scheduled run tests dev at %s (workflow %s)\n' "$sha" "$GITHUB_SHA" - # Bun roles (#6713): runtime 1.4.2 fixes #6684's Windows Bun.fetch streaming; - # the test runner skipped 1.4.2's multi-file --isolate preload segfault (#4821 - # reverted #4064; run 37631558263: Linux 1/4, macOS 2/2, Windows 9/9) and runs - # 1.4.3, whose oven-sh/bun#44581 stops Bun.spawnSync losing a child's exit (#4956). - # Rejoin after the upstream runner crash is fixed and a green lane=all dispatch - # proves the unified version, then delete testRunnerBun and the action's role input. + # Bun runtime and test-runner roles both read their 1.4.4 defaults from package.json. + # Keep role validation after the historical isolate preload and spawnSync failures + # (#6713, #4956). Promotion requires a green full cross-platform lane=all run on + # the exact candidate head; focused local results do not replace that proof. # The suite, split by file across four Linux runners. # # `scripts/ci/run-bun-test-batches.sh` assigns files to shards by the per-file @@ -605,7 +654,7 @@ jobs: test: name: test ${{ matrix.shard }}/4 needs: changes - if: github.event_name != 'pull_request' || needs.changes.outputs.ci == 'true' + if: github.event_name != 'pull_request' || (needs.changes.outputs.ci == 'true' && needs.changes.outputs.fast != 'true') runs-on: ubuntu-latest # A quarter of the suite. A shard that needs longer than this is wedged, not # slow — the old 30-minute ceiling was margin for the Windows leg, which no @@ -668,6 +717,27 @@ jobs: TEST_SHARD: ${{ matrix.shard }}/4 run: bash scripts/ci/run-bun-test-batches.sh "$TEST_SHARD" + # Fast lane: import-graph tests for the diff; the batched dev dispatch is the backstop. + test-changed: + name: test changed + needs: changes + if: github.event_name == 'pull_request' && needs.changes.outputs.fast == 'true' + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false + ref: ${{ needs.changes.outputs.checkout_ref }} + fetch-depth: 2 # merge commit + base tip, which the selection diffs against + - uses: ./.github/actions/setup-project-bun + with: + role: test-runner # See the Bun roles contract above test. + - name: Install dependencies and build the GUI the tests serve + run: bun install --frozen-lockfile && cd gui && bun install --frozen-lockfile && bun run build + - name: Test the files this pull request reaches + run: bash scripts/ci/run-changed-tests.sh + # Bun 1.3.14 has shown a Linux isolate/epoll race around the storage-policy # harness. Keep the entire six-file family in one fresh process so a runtime # failure is bounded to this job instead of poisoning a general test shard. @@ -997,12 +1067,15 @@ jobs: # now means Linux + macOS + the gates; Windows re-enters the gate when the # tracked failures are fixed, not before. # - # Dispatch-only proved too late. #6639, #6556, #6605 and #6606 each landed with - # green pull-request CI while breaking Windows, and the breakage surfaced days - # later on a manual dispatch. So a pull request whose changes job reports - # windows=true (a Windows path, or an added src/tests line naming a Windows - # marker; see scripts/ci/windows-sensitive-diff.sh) now runs the nine shards, - # and the daily schedule catches whatever that filter misses. + # Pull requests never run these shards (AGENTS.md "CI policy", a maintainer + # decision): Windows runs on the nightly schedule and on a lane=all dispatch. + # The changes job still computes windows=true for a Windows-sensitive pull + # request (a Windows path, or an added src/tests line naming a Windows marker; + # see scripts/ci/windows-sensitive-diff.sh). That verdict does not schedule + # these jobs; it posts a non-blocking notice, and a maintainer dispatches + # lane=all on the branch before merging. #6639, #6556, #6605, #6606 and #7468 + # each landed with green pull-request CI while breaking Windows, which is why + # that dispatch is part of merging a Windows-sensitive pull request. platform-windows: name: windows ${{ matrix.shard }}/9 needs: [changes, select-windows-runner] @@ -1145,7 +1218,7 @@ jobs: keyring-smoke: name: keyring ${{ matrix.name }} needs: changes - if: github.event_name != 'pull_request' || needs.changes.outputs.ci == 'true' + if: github.event_name != 'pull_request' || (needs.changes.outputs.ci == 'true' && needs.changes.outputs.keyring == 'true') runs-on: ${{ matrix.runner }} timeout-minutes: 8 strategy: @@ -1208,7 +1281,7 @@ jobs: docker-smoke: name: docker smoke needs: changes - if: github.event_name != 'pull_request' || needs.changes.outputs.ci == 'true' + if: github.event_name != 'pull_request' || (needs.changes.outputs.ci == 'true' && needs.changes.outputs.docker == 'true') runs-on: ubuntu-latest timeout-minutes: 20 steps: @@ -1705,7 +1778,7 @@ jobs: # direct dependencies only, so a failing `select-windows-runner` would # otherwise reach this gate as nothing at all while its dependents report # `skipped`, which is the shape the step below is written to catch. - needs: [changes, select-windows-runner, test, storage-policy, api-usage, gates, platform-macos, macos-control, platform-windows, keyring-smoke, docker-smoke, docs-site-build, structure-gate, privacy-gate, npm-global-smoke, widget, desktop-shell, setup-action, remote-helper] + needs: [changes, select-windows-runner, test, test-changed, storage-policy, api-usage, gates, platform-macos, macos-control, platform-windows, keyring-smoke, docker-smoke, docs-site-build, structure-gate, privacy-gate, npm-global-smoke, widget, desktop-shell, setup-action, remote-helper] runs-on: ubuntu-latest timeout-minutes: 5 permissions: @@ -1721,11 +1794,14 @@ jobs: EVENT_NAME: ${{ github.event_name }} LANE: ${{ github.event.inputs.lane }} CHANGES_CI: ${{ needs.changes.outputs.ci }} + CHANGES_FAST: ${{ needs.changes.outputs.fast }} CHANGES_PACKAGING: ${{ needs.changes.outputs.packaging }} CHANGES_DOCS: ${{ needs.changes.outputs.docs }} CHANGES_STRUCTURE: ${{ needs.changes.outputs.structure }} CHANGES_SETUP_ACTION: ${{ needs.changes.outputs.setup_action }} CHANGES_REMOTE_HELPER: ${{ needs.changes.outputs.remote_helper }} + CHANGES_DOCKER: ${{ needs.changes.outputs.docker }} + CHANGES_KEYRING: ${{ needs.changes.outputs.keyring }} CHANGES_NATIVE: ${{ needs.changes.outputs.native }} CHANGES_DESKTOP: ${{ needs.changes.outputs.desktop }} CHANGES_WINDOWS: ${{ needs.changes.outputs.windows }} @@ -1749,6 +1825,12 @@ jobs: if [ "$EVENT_NAME" = "pull_request" ] && [ "$CHANGES_CI" != "true" ]; then scoped=not-requested fi + full_tests=$scoped # the fast lane swaps the shards for test changed; unset = full + changed_tests=not-requested + if [ "$EVENT_NAME" = "pull_request" ] && [ "$scoped" = requested ] && [ "${CHANGES_FAST:-false}" = "true" ]; then + full_tests=not-requested + changed_tests=requested + fi # platform-macos and widget never run on a pull request (macOS runs after merge). # desktop-shell (ubuntu) carries the ordinary scope gate AND the native or # package-E2E path filters. This mirrors those expressions exactly; where it @@ -1782,6 +1864,16 @@ jobs: if [ "$CHANGES_REMOTE_HELPER" = "true" ]; then remote_helper=requested fi + # docker-smoke and keyring-smoke follow the ci scope, narrowed on a pull request to + # their own filters. Mirrors their `if:` exactly, like desktop_shell above. + docker=$scoped + if [ "$EVENT_NAME" = "pull_request" ] && [ "$CHANGES_DOCKER" != "true" ]; then + docker=not-requested + fi + keyring=$scoped + if [ "$EVENT_NAME" = "pull_request" ] && [ "$CHANGES_KEYRING" != "true" ]; then + keyring=not-requested + fi # privacy-gate runs exactly where `gates` (scoped) does not, on every # pull request the ci filter declines. Deriving it from `scoped` keeps # the two scans complementary here as they are in the jobs' own @@ -1809,7 +1901,7 @@ jobs: # One line per job, mirroring that job's own `if:`. Adding a job to this workflow # without adding it here fails the gate by name rather than passing unnoticed. - GATED_JOBS="changes select-windows-runner test storage-policy api-usage gates" + GATED_JOBS="changes select-windows-runner test test-changed storage-policy api-usage gates" GATED_JOBS="$GATED_JOBS platform-macos keyring-smoke docker-smoke npm-global-smoke" GATED_JOBS="$GATED_JOBS macos-control platform-windows docs-site-build" GATED_JOBS="$GATED_JOBS structure-gate widget" @@ -1820,8 +1912,12 @@ jobs: expected_for() { case "$1" in changes|select-windows-runner) echo requested ;; - test|storage-policy|api-usage|gates|keyring-smoke|docker-smoke) + test) echo "$full_tests" ;; + test-changed) echo "$changed_tests" ;; + storage-policy|api-usage|gates) echo "$scoped" ;; + docker-smoke) echo "$docker" ;; + keyring-smoke) echo "$keyring" ;; platform-macos|widget) echo "$native" ;; desktop-shell) echo "$desktop_shell" ;; diff --git a/.github/workflows/enforce-pr-target.yml b/.github/workflows/enforce-pr-target.yml index 35f6ea7b38c..dfcb50cc809 100644 --- a/.github/workflows/enforce-pr-target.yml +++ b/.github/workflows/enforce-pr-target.yml @@ -44,7 +44,8 @@ jobs: github.event.label.name == 'test-exception-approved' || github.event.label.name == 'suppression-approved' || github.event.label.name == 'generated-change-approved' || - github.event.label.name == 'dependency-change-approved')) + github.event.label.name == 'dependency-change-approved' || + github.event.label.name == 'attribution-approved')) runs-on: ubuntu-latest permissions: contents: read @@ -229,7 +230,11 @@ jobs: ); const { collectDeterministicHygieneFailures, + countHeadRewrites, HYGIENE_FAILURE_HINTS, + needsHeadRewriteCount, + parseApprovalRecord, + reconcileHeadBoundApprovals, } = require( path.join(process.cwd(), ".github", "scripts", "pr-hygiene.cjs"), ); @@ -260,12 +265,13 @@ jobs: const { GATE_MARKER, READINESS_MARKER, - HYGIENE_MARKER, - HYGIENE_BLOCK_START, - HYGIENE_BLOCK_END, inlineCode, buildGateCommentBody, extractHygieneSection, + authoritativeHygieneSection, + withHygieneSection, + escapeControlCharacters, + isGateComment, buildFailureSections, failureSummary, buildStaleNotice, @@ -337,13 +343,10 @@ jobs: ); // One consolidated comment. The gate finds its own comment by the - // single GATE_MARKER; the legacy enforcer/readiness markers are - // matched only to migrate pre-consolidation PRs. - let gateComment = comments.find( - comment => - comment.user?.login === "github-actions[bot]" && - comment.body?.includes(GATE_MARKER) - ); + // single GATE_MARKER its body starts with (a marker echoed inside + // another bot comment does not count); the legacy enforcer/readiness + // markers are matched only to migrate pre-consolidation PRs. + let gateComment = comments.find(isGateComment); let gateCommentId = gateComment?.id ?? null; const storedGateState = parseGateState( gateComment?.body, @@ -437,13 +440,15 @@ jobs: async function upsertGateComment(state, opts) { let body = buildGateCommentBody(state, opts).join("\n"); // The hygiene workflow writes its status into this same comment. - // Preserve whatever it left so a gate rebuild does not drop it. + // Preserve whatever it left so a gate rebuild does not drop it; + // it carries the approval record the next runs read. Appended + // unconditionally (a rebuilt body may not end in whitespace) and + // never through a replacement template. const existingHygiene = extractHygieneSection(gateComment?.body); - if (existingHygiene && !body.includes("pr-hygiene-block")) { - body = body.replace( - /\s+$/, - `\n\n## Hygiene\n\n${HYGIENE_BLOCK_START}\n${HYGIENE_MARKER}\n\n${existingHygiene}\n\n${HYGIENE_BLOCK_END}` - ); + // Delimiter-aware: an echoed path that merely contains the marker + // text must not suppress the preservation. + if (existingHygiene && extractHygieneSection(body) === null) { + body = withHygieneSection(body, existingHygiene.split("\n")); } if (gateCommentId) { // Skip the write when the rebuilt body matches what is already @@ -747,6 +752,10 @@ jobs: const changedFiles = []; const changedFilePaths = []; let filesTruncated = true; + // The head the changed-file list was verified against; null when the + // head moved during the snapshot. Head-bound approvals are judged + // against the same head the files belong to. + let changedFilesHeadSha = null; for (let attempt = 0; attempt < 2; attempt += 1) { const { data: fileSnapshot } = await github.rest.pulls.get({ owner, @@ -774,6 +783,8 @@ jobs: core.warning( "PR head moved during changed-file snapshot; treating file list as truncated." ); + } else { + changedFilesHeadSha = headShaForFiles; } changedFiles.length = 0; changedFiles.push(...listedFiles); @@ -812,7 +823,37 @@ jobs: // the Hygiene comment section, but Ready / review-ready must not // clear while those checks fail. Re-assess here from the same // trusted scripts so the gate cannot race ahead of hygiene. - const labelNames = (pr.labels ?? []).map(label => label.name); + const liveLabelNames = (pr.labels ?? []).map(label => label.name); + // Head-bound exception approvals count only under the head binding + // the hygiene workflow recorded. This gate reads that record and + // never removes labels or writes it. + const approvalInput = { + labels: liveLabelNames, + // Files listed at another head than this run's PR snapshot + // confirm no approval. + headSha: + changedFilesHeadSha !== null && changedFilesHeadSha === pr.head?.sha + ? changedFilesHeadSha + : null, + record: parseApprovalRecord(authoritativeHygieneSection(comments)), + eventAction: context.payload.action, + }; + let headRewriteCount = null; + if (needsHeadRewriteCount(approvalInput)) { + try { + headRewriteCount = countHeadRewrites(await github.paginate( + github.rest.issues.listEvents, + { owner, repo, issue_number: pull_number, per_page: 100 } + )); + } catch (error) { + core.warning(`Could not read head rewrite history: ${error.message}`); + } + } + const { stale: staleApprovals } = reconcileHeadBoundApprovals({ + ...approvalInput, + headRewriteCount, + }); + const labelNames = liveLabelNames.filter(name => !staleApprovals.includes(name)); // Same inputs the hygiene workflow feeds the carry-attribution // assessor. Both gates must reach the same verdict or Ready can // clear while hygiene is still red. @@ -1241,7 +1282,7 @@ jobs: const hint = HYGIENE_FAILURE_HINTS[failure.code]; if (!hint) continue; const paths = failure.paths?.length - ? ` Paths: ${failure.paths.map(p => inlineCode(p)).join(", ")}.` + ? ` Paths: ${failure.paths.map(p => inlineCode(escapeControlCharacters(p))).join(", ")}.` : ""; actions.push(`Fix **${failure.code}** — ${hint}${paths}`); } diff --git a/.github/workflows/pr-hygiene.yml b/.github/workflows/pr-hygiene.yml index b60943b93a4..74be06069f9 100644 --- a/.github/workflows/pr-hygiene.yml +++ b/.github/workflows/pr-hygiene.yml @@ -57,7 +57,12 @@ jobs: const path = require("node:path"); const { collectDeterministicHygieneFailures, + countHeadRewrites, + formatApprovalRecord, HYGIENE_FAILURE_HINTS, + needsHeadRewriteCount, + parseApprovalRecord, + reconcileHeadBoundApprovals, } = require( path.join(process.cwd(), ".github", "scripts", "pr-hygiene.cjs"), ); @@ -68,8 +73,11 @@ jobs: path.join(process.cwd(), ".github", "scripts", "pr-referenced-authors.cjs"), ); const { - GATE_MARKER, HYGIENE_MARKER, + authoritativeHygieneSection, + escapeControlCharacters, + isGateComment, + isStandaloneHygieneComment, withHygieneSection } = require( path.join(process.cwd(), ".github", "scripts", "pr-quality-messages.cjs"), @@ -104,33 +112,79 @@ jobs: } for (const name of Object.keys(labelDefinitions)) await ensureLabel(name); + // Counts head rewrites (force pushes, restored branches); null when + // the history cannot be read, which confirms no approval. + async function readHeadRewrites() { + try { + return countHeadRewrites( + await github.paginate(github.rest.issues.listEvents, { + owner, repo, issue_number: pull_number, per_page: 100, + }), + ); + } catch (error) { + core.warning(`Could not read head rewrite history: ${error.message}`); + return null; + } + } + // Read before the snapshot so the published record never counts a + // rewrite that happened after the head it names. + const headRewritesBefore = await readHeadRewrites(); const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number }); const files = await github.paginate(github.rest.pulls.listFiles, { owner, repo, pull_number, per_page: 100, }); + // The file list must belong to the head the approvals are judged + // against; if the head moved while listing, none is confirmed. + const { data: prAfterFiles } = await github.rest.pulls.get({ owner, repo, pull_number }); + const filesAtHead = prAfterFiles.head?.sha === pr.head.sha; const labels = new Set(pr.labels.map((label) => label.name)); + const comments = await github.paginate(github.rest.issues.listComments, { + owner, repo, issue_number: pull_number, per_page: 100, + }); // Exception approvals are head-specific: a new commit invalidates // them, so a contributor cannot obtain one narrow exception and - // then push unreviewed violations under the same label. - if (context.payload.action === "synchronize") { - for (const name of [ - "test-exception-approved", - "suppression-approved", - "generated-change-approved", - "dependency-change-approved", - "attribution-approved", - ]) { - if (labels.has(name)) { - await github.rest.issues.removeLabel({ - owner, repo, issue_number: pull_number, name, - }); - labels.delete(name); - } + // then push unreviewed violations under the same label. This is + // decided on every run from the head this workflow last recorded, + // not from the triggering action: a queued synchronize run can be + // replaced by a later event before it executes. + const approvalInput = { + labels: [...labels], + headSha: filesAtHead ? pr.head.sha : null, + record: parseApprovalRecord(authoritativeHygieneSection(comments)), + eventAction: context.payload.action, + }; + const headRewriteCount = needsHeadRewriteCount(approvalInput) + ? await readHeadRewrites() + : null; + const { stale: staleApprovals } = reconcileHeadBoundApprovals({ + ...approvalInput, + headRewriteCount, + }); + for (const name of staleApprovals) { + try { + await github.rest.issues.removeLabel({ + owner, repo, issue_number: pull_number, name, + }); + } catch (error) { + if (error.status !== 404) throw error; } + labels.delete(name); } + // Written last in the hygiene section on every outcome; the next + // run of this workflow and the PR gate both read it. + const approvalLines = [ + ...(staleApprovals.length === 0 + ? [] + : [ + "", + `ℹ️ Removed ${staleApprovals.map((name) => "\x60" + name + "\x60").join(", ")}: these approvals could not be confirmed for this head (\x60${pr.head.sha.slice(0, 7)}\x60). A maintainer can re-apply them after reviewing it.`, + ]), + "", + formatApprovalRecord({ head: pr.head.sha, headRewrites: headRewritesBefore }), + ]; // Sponsorship is head-independent: it is about which surfaces the // change touches, not about the state of a particular revision, so - // it is NOT cleared by the synchronize sweep above. + // it is not head-bound. // author_association is not enough: a read/triage collaborator can // be COLLABORATOR without write access. Match the PR quality gate. let authorPermission = null; @@ -188,17 +242,18 @@ jobs: } async function upsert(body) { - const comments = await github.paginate(github.rest.issues.listComments, { - owner, repo, issue_number: pull_number, per_page: 100, - }); // The single consolidated bot comment is the one the PR gate // owns (GATE_MARKER). Write the hygiene status into that same // comment so there is one editable message, not two. Fall back // to a standalone hygiene comment only when the gate has not - // posted yet (the next gate run absorbs it). - const gateComment = comments.find( - (comment) => comment.user?.login === "github-actions[bot]" && comment.body?.includes(GATE_MARKER), - ); + // posted yet; the next hygiene run writes into the gate comment. + // The comment list was read once above, before the approval + // check; both workflows share one concurrency group, so no gate + // write lands in between. + // Classified by the marker each body starts with, the same rule + // the approval-record reader uses: echoed paths can contain a + // marker string but never start the comment. + const gateComment = comments.find(isGateComment); if (gateComment) { // The hygiene block already carries the marker; strip the // standalone body's own marker line before merging. @@ -210,9 +265,7 @@ jobs: await github.rest.issues.updateComment({ owner, repo, comment_id: gateComment.id, body: merged }); return; } - const existingHygiene = comments.find( - (comment) => comment.user?.login === "github-actions[bot]" && comment.body?.includes(marker), - ); + const existingHygiene = comments.find(isStandaloneHygieneComment); if (existingHygiene) { await github.rest.issues.updateComment({ owner, repo, comment_id: existingHygiene.id, body }); } else { @@ -222,17 +275,17 @@ jobs: if (failures.length === 0) { await setBlocked(false); - await upsert(`${marker}\n\n✅ **Deterministic PR hygiene checks passed.**`); + await upsert([marker, "", "✅ **Deterministic PR hygiene checks passed.**", ...approvalLines].join("\n")); return; } const lines = failures.map((failure) => { const paths = failure.paths?.length - ? ` Paths: ${failure.paths.map((p) => `\`${p}\``).join(", ")}.` + ? ` Paths: ${failure.paths.map((p) => `\`${escapeControlCharacters(p)}\``).join(", ")}.` : ""; return `- **${failure.code}** — ${HYGIENE_FAILURE_HINTS[failure.code] ?? failure.code}${paths}`; }); await setBlocked(true); - await upsert([marker, "", "⚠️ **Deterministic hygiene checks failed.**", "", ...lines].join("\n")); + await upsert([marker, "", "⚠️ **Deterministic hygiene checks failed.**", "", ...lines, ...approvalLines].join("\n")); core.setFailed(`PR hygiene failed: ${failures.map((f) => f.code).join(", ")}`); diff --git a/.github/workflows/react-doctor.yml b/.github/workflows/react-doctor.yml index d8ac13ab366..93d8a38e3df 100644 --- a/.github/workflows/react-doctor.yml +++ b/.github/workflows/react-doctor.yml @@ -14,8 +14,15 @@ name: React Doctor on: # Scans the PR's changed files relative to the merge base of the target branch. + # The scan reads only gui/ (`directory: gui` below), so a pull request that + # changes nothing there has nothing to scan: it used to start a full-history + # checkout and an npm install on every PR for an empty changed-file list. No + # branch rule requires this check, so a PR it skips is not left waiting on it. pull_request: types: [opened, synchronize, reopened, ready_for_review] + paths: + - "gui/**" + - ".github/workflows/react-doctor.yml" # Scans `main` on every push to track the health-score trend. push: branches: ["main"] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 764eb0622a6..152d4a8adaa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -150,8 +150,11 @@ jobs: dependency_cpu: arm64 smoke: false runs-on: ${{ matrix.os }} - timeout-minutes: 25 + # The Windows leg can wait up to an hour for a release-signing approval in SignPath. + timeout-minutes: ${{ matrix.target == 'bun-windows-x64' && 90 || 25 }} permissions: + # actions: read lets the SignPath action read this job and download the unsigned artifact. + actions: read contents: read steps: - name: Checkout @@ -162,6 +165,23 @@ jobs: - name: Setup project Bun uses: ./.github/actions/setup-project-bun + # Windows Authenticode signing through SignPath Foundation (#7207). Decided before the build, so + # a broken SignPath configuration fails in seconds instead of after the build. The rules + # (no token: skip; partial configuration: fail; test-signing: dry runs only) live in + # scripts/ci/windows-signing-plan.sh, where tests/ci-workflows/release-windows-signing.test.ts + # exercises them. + - name: Decide Windows code signing + id: windows-signing + if: runner.os == 'Windows' + shell: bash + env: + SIGNPATH_CONFIGURED: ${{ secrets.SIGNPATH_API_TOKEN != '' }} + SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }} + SIGNPATH_SIGNING_POLICY: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }} + DRY_RUN: ${{ inputs.dry-run }} + run: bash scripts/ci/windows-signing-plan.sh + - name: Install dependencies run: bun install --frozen-lockfile --os=${{ matrix.dependency_os }} --cpu=${{ matrix.dependency_cpu }} @@ -201,6 +221,58 @@ jobs: Invoke-WebRequest -UseBasicParsing http://127.0.0.1:10177/ | Out-Null } finally { Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue } + # Signed after the smoke test and before the archive, so the zip and its checksum are built + # from the signed binary. The artifact holds ocx.exe alone at its root, which is the shape the + # windows-standalone-zip artifact configuration expects. The keyring addons beside it are + # third-party builds that the SignPath Foundation policy forbids signing with its certificate. + - name: Upload the unsigned Windows binary for signing + id: unsigned-ocx + if: steps.windows-signing.outputs.mode == 'sign' + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: signpath-unsigned-${{ matrix.target }} + path: dist/standalone/${{ matrix.target }}/ocx.exe + if-no-files-found: error + retention-days: 1 + + # continue-on-error on every signing step: a SignPath outage, timeout or rejection must never + # fail a release. The apply step reads this outcome and keeps the unsigned binary with a + # warning. Under release-signing every request waits for a maintainer's manual approval in + # app.signpath.io, hence the hour. + - name: Sign the Windows binary through SignPath + id: sign-ocx + if: steps.windows-signing.outputs.mode == 'sign' && steps.unsigned-ocx.outcome == 'success' + continue-on-error: true + uses: signpath/github-action-submit-signing-request@f6d04783b4569d051e0c80105fe66e82819d0092 # v3.0 + timeout-minutes: 65 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }} + signing-policy-slug: ${{ steps.windows-signing.outputs.policy }} + artifact-configuration-slug: windows-standalone-zip + github-artifact-id: ${{ steps.unsigned-ocx.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: 3600 + output-artifact-directory: ${{ runner.temp }}/signpath-signed-ocx + + - name: Swap in the verified signed Windows binary + if: steps.windows-signing.outputs.mode == 'sign' + continue-on-error: true + shell: pwsh + env: + STANDALONE_TARGET: ${{ matrix.target }} + SIGNPATH_OUTCOME: ${{ steps.sign-ocx.outcome }} + SIGNING_TRUST: ${{ steps.windows-signing.outputs.trust }} + run: | + ./scripts/ci/apply-signed-windows-artifact.ps1 ` + -Target "dist/standalone/$env:STANDALONE_TARGET/ocx.exe" ` + -SignedDirectory (Join-Path $env:RUNNER_TEMP "signpath-signed-ocx") ` + -SubmitOutcome "$env:SIGNPATH_OUTCOME" ` + -Trust $env:SIGNING_TRUST + + - name: Archive standalone release shell: bash env: @@ -262,8 +334,11 @@ jobs: sidecar-targets: x86_64-unknown-linux-gnu artifact-suffixes: linux-x86_64.AppImage,linux-amd64.deb runs-on: ${{ matrix.os }} - timeout-minutes: 45 + # The Windows leg can wait up to an hour for a release-signing approval in SignPath. + timeout-minutes: ${{ matrix.target == 'x86_64-pc-windows-msvc' && 105 || 45 }} permissions: + # actions: read lets the SignPath action read this job and download the unsigned artifact. + actions: read contents: read env: # Whether this run holds the Developer ID material at all. A run without it still builds @@ -278,6 +353,23 @@ jobs: - name: Setup project Bun uses: ./.github/actions/setup-project-bun + # Windows Authenticode signing through SignPath Foundation (#7207). Decided before the build, so + # a broken SignPath configuration fails in seconds instead of after the build. The rules + # (no token: skip; partial configuration: fail; test-signing: dry runs only) live in + # scripts/ci/windows-signing-plan.sh, where tests/ci-workflows/release-windows-signing.test.ts + # exercises them. + - name: Decide Windows code signing + id: windows-signing + if: runner.os == 'Windows' + shell: bash + env: + SIGNPATH_CONFIGURED: ${{ secrets.SIGNPATH_API_TOKEN != '' }} + SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }} + SIGNPATH_SIGNING_POLICY: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }} + DRY_RUN: ${{ inputs.dry-run }} + run: bash scripts/ci/windows-signing-plan.sh + # The desktop build takes its version from tauri.conf.json and Cargo.toml (the widget # plist inherits it), not from package.json or this input, while the updater manifest is # derived from the input. A mismatch ships an app that reports the previous version under a @@ -485,6 +577,80 @@ jobs: # diagnostics on the first attempt; Apple signing commands stay non-verbose. run: bunx tauri ${{ runner.os == 'Linux' && '--verbose' || '' }} build --ci --target ${{ matrix.target }} --bundles ${{ matrix.bundles }} --config "${{ runner.os == 'Windows' && format('{0}/opencodex-msi.json', runner.temp) || '{}' }}" + # The Windows installer goes to SignPath after the build and before the asset rename, so the + # renamed asset, its checksum and its updater signature all describe the signed MSI. The + # windows-msi-zip artifact configuration signs every .exe inside the installer (the app and + # the bundled ocx.exe sidecar) and then the MSI itself. + - name: Locate the Windows installer for signing + id: windows-msi + if: steps.windows-signing.outputs.mode == 'sign' + continue-on-error: true + shell: bash + env: + DESKTOP_TARGET: ${{ matrix.target }} + run: | + set -euo pipefail + shopt -s nullglob + installers=("desktop/src-tauri/target/$DESKTOP_TARGET/release/bundle/msi/"*.msi) + if [ "${#installers[@]}" -ne 1 ]; then + echo "::warning::Expected one MSI to sign, found ${#installers[@]}; the installer ships unsigned." + exit 1 + fi + echo "path=${installers[0]}" >> "$GITHUB_OUTPUT" + + - name: Upload the unsigned Windows installer for signing + id: unsigned-msi + if: steps.windows-signing.outputs.mode == 'sign' && steps.windows-msi.outcome == 'success' + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: signpath-unsigned-${{ matrix.target }} + path: ${{ steps.windows-msi.outputs.path }} + if-no-files-found: error + retention-days: 1 + + # continue-on-error, as for the standalone binary: an outage, timeout or rejection keeps the + # unsigned installer and its original updater signature. + - name: Sign the Windows installer through SignPath + id: sign-msi + if: steps.windows-signing.outputs.mode == 'sign' && steps.unsigned-msi.outcome == 'success' + continue-on-error: true + uses: signpath/github-action-submit-signing-request@f6d04783b4569d051e0c80105fe66e82819d0092 # v3.0 + timeout-minutes: 65 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }} + signing-policy-slug: ${{ steps.windows-signing.outputs.policy }} + artifact-configuration-slug: windows-msi-zip + github-artifact-id: ${{ steps.unsigned-msi.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: 3600 + output-artifact-directory: ${{ runner.temp }}/signpath-signed-msi + + # Authenticode rewrites the MSI, so the Tauri updater signature made during the build no + # longer matches it. The apply script regenerates .sig over the signed bytes with the + # same key before swapping both in; verify-release then checks that signature against the + # final MSI and writes latest.json from it. + - name: Swap in the verified signed Windows installer + if: steps.windows-signing.outputs.mode == 'sign' && steps.windows-msi.outcome == 'success' + continue-on-error: true + shell: pwsh + env: + MSI_PATH: ${{ steps.windows-msi.outputs.path }} + SIGNPATH_OUTCOME: ${{ steps.sign-msi.outcome }} + SIGNING_TRUST: ${{ steps.windows-signing.outputs.trust }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + run: | + ./scripts/ci/apply-signed-windows-artifact.ps1 ` + -Target "$env:MSI_PATH" ` + -SignedDirectory (Join-Path $env:RUNNER_TEMP "signpath-signed-msi") ` + -SubmitOutcome "$env:SIGNPATH_OUTCOME" ` + -Trust $env:SIGNING_TRUST ` + -Installer ` + -UpdaterSignature + - name: Verify the packaged universal macOS runtime if: runner.os == 'macOS' run: | diff --git a/.github/workflows/windows-signing-test.yml b/.github/workflows/windows-signing-test.yml new file mode 100644 index 00000000000..06aefeba8a1 --- /dev/null +++ b/.github/workflows/windows-signing-test.yml @@ -0,0 +1,251 @@ +name: Windows signing test + +# A non-publishing, end-to-end exercise of Windows Authenticode signing through SignPath (#7207). +# It builds the standalone ocx.exe and the desktop MSI on GitHub-hosted Windows runners exactly as +# the release workflow does, submits both with the test-signing policy, and verifies the returned +# signatures. Nothing is tagged, published or attached to a release: the test-signed files are kept +# only as short-lived workflow artifacts, so the run can be shown to SignPath for review. +# +# The policy is fixed to test-signing here on purpose. release-signing requests need a +# maintainer's manual approval and belong to real releases (release.yml); this workflow must never +# spend one. Unlike the release workflow, every failure here is a red run, because a green run is +# the evidence this workflow exists to produce. +on: + workflow_dispatch: + +permissions: {} + +concurrency: + group: windows-signing-test + cancel-in-progress: false + +env: + SIGNPATH_TEST_POLICY: test-signing + +jobs: + standalone: + name: sign ocx.exe (test-signing) + runs-on: windows-latest + timeout-minutes: 40 + permissions: + # actions: read lets the SignPath action read this job and download the unsigned artifact. + actions: read + contents: read + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false + + - name: Setup project Bun + uses: ./.github/actions/setup-project-bun + + - name: Require the SignPath configuration + shell: bash + env: + SIGNPATH_CONFIGURED: ${{ secrets.SIGNPATH_API_TOKEN != '' }} + SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }} + run: | + set -euo pipefail + missing="" + [ "$SIGNPATH_CONFIGURED" = "true" ] || missing="$missing SIGNPATH_API_TOKEN" + [ -n "$SIGNPATH_ORGANIZATION_ID" ] || missing="$missing SIGNPATH_ORGANIZATION_ID" + [ -n "$SIGNPATH_PROJECT_SLUG" ] || missing="$missing SIGNPATH_PROJECT_SLUG" + if [ -n "$missing" ]; then + echo "::error::This signing test needs the SignPath configuration. Missing:$missing" + exit 1 + fi + + - name: Install dependencies + run: bun install --frozen-lockfile --os=win32 --cpu=x64 + + - name: Build dashboard + run: bun run build:gui + + - name: Build standalone binary + run: bun run build:standalone --target bun-windows-x64 + + - name: Smoke test the unsigned binary + shell: pwsh + run: | + & dist/standalone/bun-windows-x64/ocx.exe --version + if ($LASTEXITCODE -ne 0) { throw "ocx.exe --version failed (exit code $LASTEXITCODE)" } + + - name: Upload the unsigned Windows binary for signing + id: unsigned-ocx + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: signpath-unsigned-bun-windows-x64 + path: dist/standalone/bun-windows-x64/ocx.exe + if-no-files-found: error + retention-days: 1 + + - name: Sign the Windows binary through SignPath + id: sign-ocx + uses: signpath/github-action-submit-signing-request@f6d04783b4569d051e0c80105fe66e82819d0092 # v3.0 + timeout-minutes: 20 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }} + signing-policy-slug: ${{ env.SIGNPATH_TEST_POLICY }} + artifact-configuration-slug: windows-standalone-zip + github-artifact-id: ${{ steps.unsigned-ocx.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: 900 + output-artifact-directory: ${{ runner.temp }}/signpath-signed-ocx + + - name: Link the SignPath signing request + if: always() && steps.sign-ocx.outputs.signing-request-web-url != '' + shell: bash + env: + SIGNING_REQUEST_URL: ${{ steps.sign-ocx.outputs.signing-request-web-url }} + run: echo "- SignPath signing request $SIGNING_REQUEST_URL" >> "$GITHUB_STEP_SUMMARY" + + - name: Swap in and verify the signed Windows binary + shell: pwsh + run: | + ./scripts/ci/apply-signed-windows-artifact.ps1 ` + -Target "dist/standalone/bun-windows-x64/ocx.exe" ` + -SignedDirectory (Join-Path $env:RUNNER_TEMP "signpath-signed-ocx") ` + -Trust test ` + -FailOnError + + - name: Keep the test-signed binary for review + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: test-signed-ocx-bun-windows-x64 + path: dist/standalone/bun-windows-x64/ocx.exe + if-no-files-found: error + retention-days: 7 + + installer: + name: sign the MSI (test-signing) + runs-on: windows-latest + timeout-minutes: 75 + permissions: + actions: read + contents: read + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false + + - name: Setup project Bun + uses: ./.github/actions/setup-project-bun + + - name: Require the SignPath configuration + shell: bash + env: + SIGNPATH_CONFIGURED: ${{ secrets.SIGNPATH_API_TOKEN != '' }} + SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }} + run: | + set -euo pipefail + missing="" + [ "$SIGNPATH_CONFIGURED" = "true" ] || missing="$missing SIGNPATH_API_TOKEN" + [ -n "$SIGNPATH_ORGANIZATION_ID" ] || missing="$missing SIGNPATH_ORGANIZATION_ID" + [ -n "$SIGNPATH_PROJECT_SLUG" ] || missing="$missing SIGNPATH_PROJECT_SLUG" + if [ -n "$missing" ]; then + echo "::error::This signing test needs the SignPath configuration. Missing:$missing" + exit 1 + fi + + - name: Install project dependencies + run: bun install --frozen-lockfile --os=win32 --cpu=x64 + + - name: Build dashboard + run: bun run build:gui + + - name: Setup Rust + uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master + with: + toolchain: stable + + - name: Install desktop dependencies + working-directory: desktop + run: bun install --frozen-lockfile + + - name: Prepare sidecar + run: bun desktop/scripts/prepare-sidecar.ts --target x86_64-pc-windows-msvc + + # The MSI version comes from the desktop configuration, as in a release. Updater artifacts + # are switched off: this run holds no updater key and publishes no manifest, and the release + # workflow is where the updater signature over the signed MSI is regenerated. + - name: Prepare Windows installer configuration + shell: bash + run: | + set -euo pipefail + version="$(bun -e 'console.log(JSON.parse(await Bun.file("desktop/src-tauri/tauri.conf.json").text()).version)')" + bun desktop/scripts/windows-installer-config.ts "$version" "$RUNNER_TEMP/opencodex-msi-base.json" + bun -e 'const dir = process.env.RUNNER_TEMP; const config = JSON.parse(await Bun.file(dir + "/opencodex-msi-base.json").text()); config.bundle.createUpdaterArtifacts = false; await Bun.write(dir + "/opencodex-msi.json", JSON.stringify(config));' + + - name: Build the Windows installer + working-directory: desktop + run: bunx tauri build --ci --target x86_64-pc-windows-msvc --bundles msi --config "${{ runner.temp }}/opencodex-msi.json" + + - name: Locate the Windows installer + id: windows-msi + shell: bash + run: | + set -euo pipefail + shopt -s nullglob + installers=(desktop/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/msi/*.msi) + if [ "${#installers[@]}" -ne 1 ]; then + echo "::error::Expected one MSI, found ${#installers[@]}" + exit 1 + fi + echo "path=${installers[0]}" >> "$GITHUB_OUTPUT" + + - name: Upload the unsigned Windows installer for signing + id: unsigned-msi + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: signpath-unsigned-x86_64-pc-windows-msvc + path: ${{ steps.windows-msi.outputs.path }} + if-no-files-found: error + retention-days: 1 + + - name: Sign the Windows installer through SignPath + id: sign-msi + uses: signpath/github-action-submit-signing-request@f6d04783b4569d051e0c80105fe66e82819d0092 # v3.0 + timeout-minutes: 20 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }} + signing-policy-slug: ${{ env.SIGNPATH_TEST_POLICY }} + artifact-configuration-slug: windows-msi-zip + github-artifact-id: ${{ steps.unsigned-msi.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: 900 + output-artifact-directory: ${{ runner.temp }}/signpath-signed-msi + + - name: Link the SignPath signing request + if: always() && steps.sign-msi.outputs.signing-request-web-url != '' + shell: bash + env: + SIGNING_REQUEST_URL: ${{ steps.sign-msi.outputs.signing-request-web-url }} + run: echo "- SignPath signing request $SIGNING_REQUEST_URL" >> "$GITHUB_STEP_SUMMARY" + + - name: Swap in and verify the signed Windows installer + shell: pwsh + env: + MSI_PATH: ${{ steps.windows-msi.outputs.path }} + run: | + ./scripts/ci/apply-signed-windows-artifact.ps1 ` + -Target "$env:MSI_PATH" ` + -SignedDirectory (Join-Path $env:RUNNER_TEMP "signpath-signed-msi") ` + -Trust test ` + -Installer ` + -FailOnError + + - name: Keep the test-signed installer for review + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: test-signed-msi-x86_64-pc-windows-msvc + path: ${{ steps.windows-msi.outputs.path }} + if-no-files-found: error + retention-days: 7 diff --git a/AGENTS.md b/AGENTS.md index 2ba03009389..2e1c13e6dca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -367,7 +367,12 @@ than nudged. ## CI policy (maintainer decision — do not change) -- Pull requests run **only the fast Ubuntu subset**. +- Pull requests run **only the fast Ubuntu subset**. By default that is `gates` plus + `test changed` (import-graph tests for the diff); the four full Ubuntu shards run on a + pull request only with the `ci:full` label or a CI/harness/toolchain/dependency change. + Merged pull requests are verified in batches by a manual `dev` dispatch + (`MAINTAINERS.md`, "Batched merges"). Because `test changed` follows the module graph, + add `ci:full` for changes it cannot see (subprocesses, files read as data, generated output). - Full cross-platform (macOS/Windows) CI runs **only** via manual `workflow_dispatch` and on push to `main`/`preview` (release lines). - **Never** add a `dev` push trigger, or `pull_request` runs for macOS/Windows diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9ae2a4555e7..bbcd248f8f8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,9 +69,11 @@ A ready-for-review PR is the author's claim that the change is complete, underst ## Local validation and hooks -The suite uses Bun 1.4.3 (`testRunnerBun`) while the shipped runtime is 1.4.2. -`scripts/test.ts` finds a 1.4.3 binary on PATH or in `~/.bun/bin`, or uses +The suite uses Bun 1.4.4 (`testRunnerBun`) while the shipped runtime is 1.4.4. +`scripts/test.ts` finds a 1.4.4 binary on PATH or in `~/.bun/bin`, or uses `OCX_TEST_RUNNER_BUN`; install that version locally if it is missing. +The roles remain independently checked even when their versions match. Promotion requires +a successful full cross-platform `lane=all` run on the candidate head. Run `bun run test` before review readiness. If the full local suite is too costly for the task or available resources, run at least focused regression tests for diff --git a/Dockerfile b/Dockerfile index 7e36f2063a0..54e0153e0f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,14 @@ # syntax=docker/dockerfile:1 # Keep the runtime aligned with package.json and pin the multi-platform image index. -# Runtime 1.4.2 fixes #6684's Windows Bun.fetch streaming; CI tests run on 1.4.3 -# because 1.4.2 segfaults reloading preloads in multi-file --isolate (#4821; -# #6713 run 37631558263: Linux 1/4, macOS 2/2, Windows 9/9) and 1.4.0's spawnSync -# can lose a child's exit (#4956, oven-sh/bun#44581). Rejoin after the -# upstream crash fix passes a unified-version lane=all dispatch, then delete -# package.json's testRunnerBun pin and setup-project-bun's role input. -ARG BUN_IMAGE=oven/bun:1.4.2@sha256:9114c058aeae42162ee16dd5084b95fe9473970bb6bcb5b232ab1630f0546895 +# Runtime 1.4.4 keeps 1.4.2's #6684 Windows Bun.fetch streaming fix and ships NSS +# 3.131.1 roots without the compromised eMudhra CAs. Runtime and test-runner +# defaults are both 1.4.4; keep package.json's testRunnerBun pin and +# setup-project-bun's role input independently validated. Promotion requires +# an exact-head cross-platform lane=all dispatch, including multi-file --isolate: +# historical 1.4.2 preload crashes (#4821; #6713 run 37631558263) and 1.4.0 +# spawnSync exit loss (#4956, oven-sh/bun#44581) remain regression risks. +ARG BUN_IMAGE=oven/bun:1.4.4@sha256:1d22548f177f4ff38e0c15baa3c2c46c6c2060ba2d1bc573bb4720b407548f1d FROM ${BUN_IMAGE} AS manifest WORKDIR /home/bun/app diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 18741234818..db2798905e0 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -62,6 +62,16 @@ when a maintainer steps down. the ruleset does not check ancestry (see the note under the change log). - Pull requests require successful required CI checks before merge. Contributor pull requests normally require approval from at least one maintainer other than the author. +- **Batched merges.** A pull request's own CI is the fast lane by default: `gates` plus + `test changed` (the tests Bun's import graph reaches from the diff), not the four full + Ubuntu shards. The shards still run on a pull request that carries the `ci:full` label at + push time or touches the CI/test harness, fixtures, toolchain or dependency set + (`scripts/ci/select-test-lane.sh`). Add `ci:full` before pushing when a change reaches shared + contracts in ways an import graph cannot see (subprocesses, files read as data, generated + output). Merge green pull requests one at a time; after roughly every five merges, run + `gh workflow run ci.yml --ref dev` (the default `lane=all`). If that run is red, stop merging, + fix `dev` from the batch it covers, and resume once it is green. Always run it before a + promotion to `main`. - A current maintainer with GitHub `maintain` or `admin` access may explicitly integrate a pull request into `dev` without another maintainer's approval, including their own pull request. Record that choice and the exact-head verification in the pull-request description or comment. diff --git a/README.md b/README.md index e8223c7eac8..5fba47f1840 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ platform from the [latest release](https://github.com/lidge-jun/opencodex/releas | Platform | File | Notes | |---|---|---| | macOS 13+ (Apple Silicon and Intel) | `OpenCodex--macos.dmg` | Universal build, signed with a Developer ID and notarized | -| Windows (x64) | `OpenCodex--windows-x64.msi` | Not code-signed yet: SmartScreen asks once, choose **More info → Run anyway** | +| Windows (x64) | `OpenCodex--windows-x64.msi` | Code signing via SignPath Foundation is rolling out ([code signing policy](https://opencodex.me/reference/code-signing/)); until a release is signed, SmartScreen asks once: choose **More info → Run anyway** | | Linux (x86_64) | `OpenCodex--linux-x86_64.AppImage` or `-linux-amd64.deb` | The tray needs an AppIndicator-capable desktop | Every file has a `.sha256` next to it on the release page. On macOS 14+ the app also ships a @@ -471,6 +471,16 @@ Contributor work that landed through a maintainer carry or reimplementation, where the commit does not name its original author, is recorded in **[CREDITS.md](./CREDITS.md)**. +## Code signing policy + +Windows binaries are moving to Authenticode signing ([#7207](https://github.com/lidge-jun/opencodex/issues/7207)). The [Windows Code Signing guide](https://opencodex.me/reference/code-signing/) covers the rollout, how to verify a signature, and the full policy. + +Free code signing provided by [SignPath.io](https://about.signpath.io/), certificate by [SignPath Foundation](https://signpath.org/). + +- Committers and reviewers: [lidge-jun](https://github.com/lidge-jun) +- Approvers: [lidge-jun](https://github.com/lidge-jun) +- Privacy: This program will not transfer any information to other networked systems unless specifically requested by the user or the person installing or operating it. + ## Disclaimer opencodex is an independent, community-maintained project and is **not affiliated with or endorsed by OpenAI, Anthropic, or any other provider**. diff --git a/app/Sources/MenuBarCore/ProxyModels.swift b/app/Sources/MenuBarCore/ProxyModels.swift index 0a1267303b3..bf9d0881ac2 100644 --- a/app/Sources/MenuBarCore/ProxyModels.swift +++ b/app/Sources/MenuBarCore/ProxyModels.swift @@ -163,6 +163,7 @@ public struct UsageAccountRow: Decodable, Equatable, Sendable { } public struct QuotaWindow: Decodable, Equatable, Sendable { + public let modelId: String? public let label: String? public let percent: Double? public let resetAt: Double? @@ -188,6 +189,7 @@ public struct QuotaReport: Decodable, Equatable, Sendable { /// A provider-agnostic view of quota, since the window key differs per provider. public struct NormalizedQuota: Equatable, Sendable { + public let modelId: String? public let provider: String public let providerLabel: String public let percent: Double? @@ -219,10 +221,10 @@ public extension QuotaReport { let name = label ?? provider var windows: [NormalizedQuota] = [] - func append(_ percent: Double?, _ windowLabel: String, _ resetAt: Double?) { + func append(_ percent: Double?, _ windowLabel: String, _ resetAt: Double?, _ modelId: String? = nil) { guard percent != nil || resetAt != nil else { return } windows.append(NormalizedQuota( - provider: provider, providerLabel: name, percent: percent, + modelId: modelId, provider: provider, providerLabel: name, percent: percent, windowLabel: windowLabel, resetAt: Self.date(from: resetAt) )) } @@ -232,7 +234,7 @@ public extension QuotaReport { append(quota?.monthlyPercent, "month", quota?.monthlyResetAt) for window in quota?.customWindows ?? [] { - append(window.percent, window.label ?? "window", window.resetAt) + append(window.percent, window.label ?? "window", window.resetAt, window.modelId) } return windows @@ -240,7 +242,8 @@ public extension QuotaReport { /// The single window that best represents current pressure, for the compact row. /// - /// Selection is **highest reported usage**, not longest horizon. Every window can + /// Diagnostic model rows are retained in details and excluded here. Selection is + /// **highest reported usage**, not longest horizon. Every aggregate window can /// stop work: a provider at 99% of a five-hour limit and 10% of its monthly limit is /// blocked right now, and showing the monthly 10% would paint that row green while /// the user cannot make a request. Ties break toward the longer horizon, since that @@ -250,7 +253,7 @@ public extension QuotaReport { /// em dash rather than a misleading zero. func normalized() -> NormalizedQuota { let name = label ?? provider - let windows = normalizedWindows() + let windows = normalizedWindows().filter { $0.modelId == nil } // Longer horizons rank higher only as a tie-breaker. func horizonRank(_ label: String) -> Int { @@ -271,7 +274,7 @@ public extension QuotaReport { } ?? windows.first return preferred ?? NormalizedQuota( - provider: provider, providerLabel: name, percent: nil, + modelId: nil, provider: provider, providerLabel: name, percent: nil, windowLabel: "—", resetAt: nil ) } diff --git a/app/Sources/MenuBarCoreTests/ModelDecodingSuite.swift b/app/Sources/MenuBarCoreTests/ModelDecodingSuite.swift index 0ec0012e621..f79580bbc49 100644 --- a/app/Sources/MenuBarCoreTests/ModelDecodingSuite.swift +++ b/app/Sources/MenuBarCoreTests/ModelDecodingSuite.swift @@ -230,6 +230,23 @@ enum ModelDecodingSuite { t.equal(report.normalized().percent, 12) } + t.test("quotas: model diagnostics stay detailed and cannot become compact pressure") { + let json = """ + {"provider":"google-antigravity","quota":{"customWindows":[ + {"label":"aux","modelId":"aux","percent":100}, + {"label":"Gem","percent":20}]}} + """ + let report = try decode(QuotaReport.self, json) + t.equal(report.normalized().percent, 20) + t.equal(report.normalized().windowLabel, "Gem") + t.equal(report.normalizedWindows().count, 2) + t.equal(report.quota?.customWindows?.first?.modelId, "aux") + t.equal(report.normalizedWindows().first?.modelId, "aux") + let onlyModel = try decode(QuotaReport.self, + #"{"provider":"p","quota":{"customWindows":[{"label":"aux","modelId":"aux","percent":100}]}}"#) + t.isNil(onlyModel.normalized().percent, "model-only compact percent") + } + t.test("quotas: an absent quota normalizes to a nil percent") { let normalized = try decode(QuotaReport.self, #"{"provider":"p","label":"P"}"#).normalized() t.isNil(normalized.percent, "percent") diff --git a/bin/ocx.mjs b/bin/ocx.mjs index 6baf5168b5f..22c391d4b42 100755 --- a/bin/ocx.mjs +++ b/bin/ocx.mjs @@ -37,8 +37,10 @@ import { homedir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { isRealBunBinary } from "../src/lib/bun-binary-validator.mjs"; +import { scopeTranspilerCacheToRuntime } from "../src/lib/bun-transpiler-cache-scope.mjs"; import { findDesktopCli, findPathBun } from "../src/lib/bun-path-runtime.mjs"; import { npmInvocation } from "../src/update/npm-invocation.mjs"; +import { runNpmInstall } from "../src/update/npm-install.mjs"; import { pnpmInvocationForPath, resolvePnpmCommands } from "../src/update/pnpm-invocation.mjs"; import { detectInstallOwnershipFromPath } from "../src/update/install-detection.mjs"; import { @@ -55,10 +57,13 @@ import { runNpmCachePreflight, } from "../src/update/npm-cache-preflight.mjs"; import { handoffWindowsTrayForUpdate, planWindowsTrayUpdate } from "../src/update/tray-update-plan.mjs"; -import { bootRestoreProbe, launcherUsableAfterNpmUpdate, transactionalNpmUpdate } from "../src/update/transactional-install.mjs"; +import { bootRestoreProbe, launcherUsableAfterNpmUpdate, transactionalNpmUpdate as transactionalNpmUpdateSync, transactionalNpmUpdateAsync } from "../src/update/transactional-install.mjs"; import { manualUpdateFailureGuidance, npmUpdateFailureGuidance } from "../src/update/update-failure-guidance.mjs"; import { + captureCodexCliTransportEnv, + CODEX_CLI_TRANSPORT_ENV_KEYS, CODEX_CLI_VERSION_MANAGER_ROOT_ENV_SLOTS, + codexCliInspectionLookupSeeds, isCodexCliUpdateInspectionArgv, } from "../src/update/codex-cli-update-launch-policy.mjs"; import { @@ -209,7 +214,7 @@ function runningPnpmShimPath() { return resolve(invoked); } -function runPackageManagerSelfUpdate(manager) { +async function runPackageManagerSelfUpdate(manager) { const current = currentPackageVersion(); const tag = updateTag(current); let owner; @@ -846,7 +851,8 @@ function runPackageManagerSelfUpdate(manager) { try { if (manager === "npm") { const packageDir = resolve(here, ".."); - const tx = transactionalNpmUpdate({ + const transactionalNpmUpdate = process.platform === "win32" ? transactionalNpmUpdateAsync : transactionalNpmUpdateSync; + const tx = await transactionalNpmUpdate({ packageDir, pkgName: PKG, targetVersion: latest || undefined, @@ -854,6 +860,9 @@ function runPackageManagerSelfUpdate(manager) { cachePath: npmCachePath, runNpm: (args) => { const invocation = npmInvocation(args); + if (process.platform === "win32") return runNpmInstall(invocation, { + env: unprivilegedOwnershipMutationEnvironment(invocation?.options?.env ?? process.env), + }); if (!invocation) return { status: 1 }; return spawnSync(invocation.file, invocation.args, { ...invocation.options, @@ -1003,6 +1012,15 @@ function findBunBinary(bunDir) { return null; } +function bundledBunVersion(bunDir) { + try { + const version = JSON.parse(readFileSync(join(bunDir, "package.json"), "utf8")).version; + return typeof version === "string" ? version : null; + } catch { + return null; + } +} + function fail(msg) { const reinstall = installMethod === "pnpm" ? "pnpm add -g --allow-build=bun @bitkyc08/opencodex" @@ -1039,7 +1057,7 @@ function resolveBun({ allowInstall = true } = {}) { } catch { /* Missing dependency can still fall back to a validated PATH Bun. */ } let bin = bunDir ? findBunBinary(bunDir) : null; - if (bin) return { path: bin, source: "bundled" }; + if (bin) return { path: bin, source: "bundled", version: bundledBunVersion(bunDir) }; // Lazy fallback: --ignore-scripts (or a failed postinstall) leaves the // ~450-byte placeholder stub. Run the bun package's own installer once. @@ -1048,7 +1066,7 @@ function resolveBun({ allowInstall = true } = {}) { const r = spawnSync(process.execPath, [installJs], { stdio: "inherit" }); if (r.status === 0) bin = findBunBinary(bunDir); } - if (bin) return { path: bin, source: "bundled" }; + if (bin) return { path: bin, source: "bundled", version: bundledBunVersion(bunDir) }; // Reached only when the bundled runtime is unusable. The two probes start the candidate Bun // cold, and on Windows a first run of a copied bun.exe is often held by an on-access scan for @@ -1056,7 +1074,7 @@ function resolveBun({ allowInstall = true } = {}) { const pathBun = findPathBun({ pinnedVersion: pinnedBunVersion(), deadlineMs: PATH_BUN_PROBE_BUDGET_MS }); if (pathBun) { console.error(`opencodex: using PATH Bun ${pathBun.version}.`); - return { path: pathBun.path, source: "process" }; + return { path: pathBun.path, source: "process", version: pathBun.version }; } fail(bunDir ? "Bun binary missing after install attempt." : "the `bun` dependency is not installed."); } @@ -1122,8 +1140,8 @@ if (process.argv[2] === "update" && installMethod === "mise") { } if (process.argv[2] === "update" && isNodeModulesInstall() && !isBunGlobalInstall()) { - if (installMethod === "npm") runNpmSelfUpdate(); - if (installMethod === "pnpm") runPnpmSelfUpdate(); + if (installMethod === "npm") await runNpmSelfUpdate(); + if (installMethod === "pnpm") await runPnpmSelfUpdate(); } // #1849 boot probe: a prior update that lost power (or double-faulted) mid-swap leaves a @@ -1182,14 +1200,25 @@ function createNodeLaunchContext() { : configuredCodexCliPath; const preBunPath = typeof process.env.PATH === "string" ? process.env.PATH : null; const preBunPathExt = typeof process.env.PATHEXT === "string" ? process.env.PATHEXT : null; + // HOME and NODE_PATH seed the candidate Node module lookup the update + // inspection models, so they are proof-bound here for the same reason as + // PATH: the live environment could have been rewritten after selection. They + // stay in the inherited environment regardless — the child may need them for + // its own work; only the snapshot copy is optional. + const preBunHome = typeof process.env.HOME === "string" ? process.env.HOME : null; + const preBunNodePath = typeof process.env.NODE_PATH === "string" ? process.env.NODE_PATH : null; const preBunCodexCliManagerRoots = Object.fromEntries( CODEX_CLI_VERSION_MANAGER_ROOT_ENV_SLOTS.flatMap(name => { const value = process.env[name]; return typeof value === "string" && value !== "" ? [[name, value]] : []; }), ); + // Proxy/CA settings are captured once, here, before Bun can add project dotenv + // values. They are part of the required selection snapshot, so they count + // against the context budget below before any optional lookup seed does. + const preBunTransportEnv = captureCodexCliTransportEnv(process.env); const launchProof = randomBytes(32).toString("base64url"); - const launchContext = JSON.stringify({ + const serializeContext = lookupSeeds => JSON.stringify({ version: 1, proof: launchProof, anthropicEnvSlots: preBunAnthropicSlots, @@ -1197,11 +1226,30 @@ function createNodeLaunchContext() { codexCliPath: preBunCodexCliPath, path: preBunPath, pathExt: preBunPathExt, + home: lookupSeeds.home, + nodePath: lookupSeeds.nodePath, managerRoots: preBunCodexCliManagerRoots, + transportEnv: preBunTransportEnv, + // The published Codex entrypoint selects its native backing by the arch of + // the Node that runs it; a Bun override or PATH fallback can run another + // arch under Rosetta, so this Node launcher's arch is the bound value. + arch: process.arch, configDir: configDir(), } : null, }); - // The inspection snapshot above already carries PATH, PATHEXT, and the manager-root slots as + let launchContext = serializeContext({ home: preBunHome, nodePath: preBunNodePath }); + // The lookup seeds are optional: a Windows inspection never models the global + // Node lookup, and an oversized NODE_PATH must not push the serialized + // context past the reader's limit — that would discard the whole selection + // snapshot, while dropped seeds merely leave the fallback lane unverifiable + // and fail only the content observation closed. + const lookupSeeds = codexCliInspectionLookupSeeds( + process.platform, preBunHome, preBunNodePath, launchContext.length, + ); + if (lookupSeeds.home !== preBunHome || lookupSeeds.nodePath !== preBunNodePath) { + launchContext = serializeContext(lookupSeeds); + } + // The inspection snapshot above already carries PATH, PATHEXT, manager roots and proxy/CA settings as // proof-bound values, and `inspectCodexCliInstall` reads them from that snapshot rather than // from the live environment. Inheriting them again would spend the 32,767-character Windows // environment block twice, so a large-but-valid shell environment could stop the Bun child @@ -1214,7 +1262,8 @@ function createNodeLaunchContext() { const inheritedEnv = { ...process.env }; if (codexCliUpdateInspection) { const snapshotted = new Set( - ["PATH", "PATHEXT", ...CODEX_CLI_VERSION_MANAGER_ROOT_ENV_SLOTS].map(name => name.toLowerCase()), + ["PATH", "PATHEXT", ...CODEX_CLI_VERSION_MANAGER_ROOT_ENV_SLOTS, + ...CODEX_CLI_TRANSPORT_ENV_KEYS].map(name => name.toLowerCase()), ); for (const name of Object.keys(inheritedEnv)) { if (snapshotted.has(name.toLowerCase())) delete inheritedEnv[name]; @@ -1230,7 +1279,8 @@ const child = spawn(bun, [cliPath, `${NODE_LAUNCH_PROOF_PREFIX}${launchProof}`, // the long-running Bun child, and closing that window kills the proxy (#1236). windowsHide: true, env: { - ...inheritedEnv, + // A cache shared with another Bun version (the CI test runner's) is evicted on every start. + ...scopeTranspilerCacheToRuntime(inheritedEnv, bunRuntime.version, join), [NODE_LAUNCH_CONTEXT_ENV]: launchContext, [BUN_RUNTIME_SOURCE_ENV]: bunRuntime.source, [BUN_RUNTIME_PATH_ENV]: bunRuntime.path, diff --git a/bun.lock b/bun.lock index 063207baa8b..fd75af84840 100644 --- a/bun.lock +++ b/bun.lock @@ -6,10 +6,10 @@ "name": "@bitkyc08/opencodex", "dependencies": { "@bufbuild/protobuf": "^2.14.0", - "@modelcontextprotocol/sdk": "^1.30.0", + "@modelcontextprotocol/sdk": "^1.32.1", "@napi-rs/keyring": "1.3.0", - "bun": "1.4.2", - "zod": "4.4.3", + "bun": "1.4.4", + "zod": "4.6.6", }, "devDependencies": { "@types/bun": "1.4.0", @@ -25,17 +25,18 @@ ], "overrides": { "@hono/node-server": "2.1.0", + "body-parser": "2.4.0", "fast-uri": "^3.1.7", - "hono": "4.13.8", - "ip-address": "^10.4.0", - "qs": "^6.16.0", + "hono": "4.13.13", + "ip-address": "^10.7.4", + "qs": "^6.16.1", }, "packages": { "@bufbuild/protobuf": ["@bufbuild/protobuf@2.14.0", "", {}, "sha512-C3UGsiCwSprE2NKIIFA3hCDlpXTMCAXRZuEVp88L1GY36Y41+rYL5fryE+nOFhp4p4JPQvdV8PQ4DWgHgeTE+w=="], "@hono/node-server": ["@hono/node-server@2.1.0", "", { "peerDependencies": { "hono": "^4" } }, "sha512-XovyyCCnBzW+zKu+z/zq8hwNs4KOR5rEMAOxo2f40Q5xoOI37IMm6MIg2COOUtUApo0i6850MTBKH2u4QLGIqg=="], - "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.31.0", "", { "dependencies": { "@hono/node-server": "^1.19.9 || ^2.0.5", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-UvTMgnNlnIBO/22ob2RcVGDlcvOslQs8T59+FTGdA0L27a39fdGF/EDETNtDVK4DZGpwomlsYpRdA8UXcVL/pw=="], + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.32.1", "", { "dependencies": { "@hono/node-server": "^1.19.9 || ^2.0.5", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-2DdE+SJDtzLEEWzY1ZjY7Q+VcPhcV1KisD3zI4u0XZyktsjHum1mwbMI+JaulUBi2OZk+KJAi2uPXzxichPkdw=="], "@napi-rs/keyring": ["@napi-rs/keyring@1.3.0", "", { "optionalDependencies": { "@napi-rs/keyring-darwin-arm64": "1.3.0", "@napi-rs/keyring-darwin-x64": "1.3.0", "@napi-rs/keyring-freebsd-x64": "1.3.0", "@napi-rs/keyring-linux-arm-gnueabihf": "1.3.0", "@napi-rs/keyring-linux-arm64-gnu": "1.3.0", "@napi-rs/keyring-linux-arm64-musl": "1.3.0", "@napi-rs/keyring-linux-riscv64-gnu": "1.3.0", "@napi-rs/keyring-linux-x64-gnu": "1.3.0", "@napi-rs/keyring-linux-x64-musl": "1.3.0", "@napi-rs/keyring-win32-arm64-msvc": "1.3.0", "@napi-rs/keyring-win32-ia32-msvc": "1.3.0", "@napi-rs/keyring-win32-x64-msvc": "1.3.0" } }, "sha512-WrOw/bcXm0f9qHkumlT1QlArXSTWqaY9sunsDpOk+yCCorCKMxvWT/a3xko4EYHVdeZoh00yI2TydXn6eyICDA=="], @@ -63,29 +64,29 @@ "@napi-rs/keyring-win32-x64-msvc": ["@napi-rs/keyring-win32-x64-msvc@1.3.0", "", { "os": "win32", "cpu": "x64" }, "sha512-4DnCWXwDc0HRKwyRlG5y0VhKZW2tNRQfKKfyj6IX/KWfDNyq9hn4n+GL1auyDcOO/v8PwnhmYo2+rOOqCkvvOg=="], - "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.4.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MXdZkP1featqxZ+/VTXWG1BVjM4OGBehVY2Q88EeUj/7L0UMeCGItmyPYTN+wxvlGJ6F66JEtzsw+GvQWewnag=="], + "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.4.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zW/hxwc8sJAILGwB5eTC4i48ouGXf0V4lrxmEOk3JIE6JkYVW1TZh1E2pIempqsezgW1sckTIIDudFON8bJ2Mw=="], - "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.4.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-gZTxZuLjkUhAWjTETu3tw0WhsEdNkJ64daj60ybhPf835a2yollV3yTkK9JozvzKPx4TRFzLSl8C+U525pxVbw=="], + "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.4.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-oKGcxLsvh4oHjAo28nPm8c/xN0o/OmGyj1IHYc94vprkWsW2hNg2keEZt5f40cI5Hvu4jDlSRTR0UNpv1UIAxw=="], - "@oven/bun-freebsd-aarch64": ["@oven/bun-freebsd-aarch64@1.4.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-SMNItMw1Z8QeeQVKnw8jA7xQNkeXdP+OPgin4Wi/QTx/B8RHHLnuZfqmFy7NtVeT2NF0kKYppW4WWd2CCYZjhQ=="], + "@oven/bun-freebsd-aarch64": ["@oven/bun-freebsd-aarch64@1.4.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-klvA9FlwLxZj3JYIOPQA4bbnHIC3uas8kBE4GaGGkeVgAQP/k8hAzYkOqkSaqHBDbxLrTyFEQArEAevJq6NJzg=="], - "@oven/bun-freebsd-x64": ["@oven/bun-freebsd-x64@1.4.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-THbPKXhO54N0DpFRKZNDZpQ7dpbX0bWASuARckAUS9wRtFIHsiY+uULXJvxJGo2YD1YewvXQ4G8Fj7XT5oBCiw=="], + "@oven/bun-freebsd-x64": ["@oven/bun-freebsd-x64@1.4.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-sF1xS/scbyN94P2d0eTiarO6JHxrIvhZNT9hnWjQXW5nwKRtfuP+UYnIMNyvW1BQPcZFP/cvfLBNUIe1ea4zPA=="], - "@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.4.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-3BBP9ovJ2RGHFH6Ae1CAtxNtG1+YY6GD6rmYbsUosoAk9+OEl6zeDQ/k4fBkc6dYOJCtWnx8hUxzNzQATSmvYQ=="], + "@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZTVtpf9zmHSBVPX1sBwe5agGb/rN34yPGg/Hby8e2Ax2uCUA2URZKlZH7qLntK7qaFJCMXh9v0GWNYN2zINz1w=="], - "@oven/bun-linux-aarch64-android": ["@oven/bun-linux-aarch64-android@1.4.2", "", { "os": "android", "cpu": "arm64" }, "sha512-3mZKO2rhsNgbAUtAHC1UKUlF2zTxFraDZT/Elv8wzyH0fJL9h+Iv3TgB9lO63w89PRn3eFe+NRA1bhVgikKNPQ=="], + "@oven/bun-linux-aarch64-android": ["@oven/bun-linux-aarch64-android@1.4.4", "", { "os": "android", "cpu": "arm64" }, "sha512-5lvn3at5vRC0GxvmiqEiV2/e23tnRi0q5pzR40yfDvDA3OW7PSkt4chF1nA9vnAJhro4/l+ol1FmNP6D3cf/oA=="], - "@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.4.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-+Sm6y+lSiSFBOtXmnekp5Q6n1tUKlyv71FCPWBc61Cgb14T5eBs8SN/nh4MUCOKzONkI3O+as3MGUgikS4aCBQ=="], + "@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-QFd0P+mHJxbZFbUla4MgiE5OwxysHaUIYxL2pPNkpWtaI7v4ShOpr8OhBmsuyB0xn5rv/ZJnPsj2y8+bB9+g/A=="], - "@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.4.2", "", { "os": "linux", "cpu": "x64" }, "sha512-9/E/UXOTpSo3YsV5g+FhtTd/qTpiWoKuxS12cqtuYA1ssu9fRAoPQnipFgGyck3tWO63iUdxBiygq+kELFawng=="], + "@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-CHbbkmHVM28T1GFq+6GL4Ao4ujwwYwj1CxFFwEFzLanPfNsId9AhbIP0Q4reWuWBkGimUkoQygrK7Rgjtj3vog=="], - "@oven/bun-linux-x64-android": ["@oven/bun-linux-x64-android@1.4.2", "", { "os": "android", "cpu": "x64" }, "sha512-6HC5tzcC79113n2IHCTJMWv+HsQImv4ZFEK2XpYLxY6HbT8tM4cUM2Zv1bHZBQsS3jv/zYBamDJ1UX7If0d5tw=="], + "@oven/bun-linux-x64-android": ["@oven/bun-linux-x64-android@1.4.4", "", { "os": "android", "cpu": "x64" }, "sha512-3dMicj9C1DhaIfDkwt/9GtpU0W+q+GazbG9OIlEyL3Q1BBEz2e6ZRz/JiO8D/ECC0caXAqvnpFudehcLlgIwWQ=="], - "@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.4.2", "", { "os": "linux", "cpu": "x64" }, "sha512-vVTKUg1bnPhRP/Hp73jIVoFh2vPFNYEqYX0ERKfZBOQEEHitNAeukZzzuUDZS0SoDCIpuWUGSpd/CDMbjdR+Uw=="], + "@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lEEjNOJJrbGZu2NVcPI4uXdILcpspezWkA8R/Jr0RCGhM6zQd0Y7JBIIKNAmbX59EeWpeEz7Q78iLbpcAUypUA=="], - "@oven/bun-windows-aarch64": ["@oven/bun-windows-aarch64@1.4.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-8EJ1ST7339WJE3poPW5nBgVW/lWf9HBz4W27ZUNhburKmcBLOByPyE6DP9fHD8FQGm5c+ilUN2hX1mrW0jxq9Q=="], + "@oven/bun-windows-aarch64": ["@oven/bun-windows-aarch64@1.4.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-n0ep5ZRPCZY1+dqFJpnnRUZB1wy4GYU6At8RF+pyzySCLjkN9R4rFh2PtBHAgF/lg1vOl3/CeER3pDM+p40ATA=="], - "@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.4.2", "", { "os": "win32", "cpu": "x64" }, "sha512-+bN6OuVld/9diT/RLSXSW7JE6CvNE3gL9XsAEjULi1nUsXd6DNO6GuA9jNdNb3r8PdJFnYHr5aypNV1Oj3Rd9g=="], + "@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.4.4", "", { "os": "win32", "cpu": "x64" }, "sha512-a3LNctBVO8PjePsFZfwDleF2JXYf2BfQ1u/PLRnBDLnC2Riml+4+LgJpKj4xRGhB92KweGyQI5PXaTkP9qypMQ=="], "@types/bun": ["@types/bun@1.4.0", "", { "dependencies": { "bun-types": "1.4.0" } }, "sha512-K+lZULY23vRgK/CfTjFIV+tyifaNdSMlPh9j+6mQ/cLfpOznLyAuzgV/JQysyECpkBQLVMSyvjlr2fBUSA9wFQ=="], @@ -137,9 +138,9 @@ "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], - "body-parser": ["body-parser@2.3.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^2.0.0", "debug": "^4.4.3", "http-errors": "^2.0.1", "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", "qs": "^6.15.2", "raw-body": "^3.0.2", "type-is": "^2.1.0" } }, "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw=="], + "body-parser": ["body-parser@2.4.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^2.1.0", "debug": "^4.4.3", "http-errors": "^2.0.1", "iconv-lite": "^0.7.3", "on-finished": "^2.4.1", "qs": "^6.16.1", "raw-body": "^3.0.2", "type-is": "^2.1.0" } }, "sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ=="], - "bun": ["bun@1.4.2", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.4.2", "@oven/bun-darwin-x64": "1.4.2", "@oven/bun-freebsd-aarch64": "1.4.2", "@oven/bun-freebsd-x64": "1.4.2", "@oven/bun-linux-aarch64": "1.4.2", "@oven/bun-linux-aarch64-android": "1.4.2", "@oven/bun-linux-aarch64-musl": "1.4.2", "@oven/bun-linux-x64": "1.4.2", "@oven/bun-linux-x64-android": "1.4.2", "@oven/bun-linux-x64-musl": "1.4.2", "@oven/bun-windows-aarch64": "1.4.2", "@oven/bun-windows-x64": "1.4.2" }, "os": [ "!aix", "!sunos", "!openbsd", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-TrSXo6HJfIEaczpb3kjX82I2pL47vK1QUNmHRCUdz9IzaOwa9lzOXSWwu2l18YHE3sNfGRapVLd4nNm+22vVVA=="], + "bun": ["bun@1.4.4", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.4.4", "@oven/bun-darwin-x64": "1.4.4", "@oven/bun-freebsd-aarch64": "1.4.4", "@oven/bun-freebsd-x64": "1.4.4", "@oven/bun-linux-aarch64": "1.4.4", "@oven/bun-linux-aarch64-android": "1.4.4", "@oven/bun-linux-aarch64-musl": "1.4.4", "@oven/bun-linux-x64": "1.4.4", "@oven/bun-linux-x64-android": "1.4.4", "@oven/bun-linux-x64-musl": "1.4.4", "@oven/bun-windows-aarch64": "1.4.4", "@oven/bun-windows-x64": "1.4.4" }, "os": [ "!aix", "!sunos", "!openbsd", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-hj0ASdG6PNjLZGFAXktWb3Uaj/dcv/1wCsIttoEXADNIQBsGjTr+5uLKYbrDkuc7NT0Tkj4yPIqJI2jDxXdvVg=="], "bun-types": ["bun-types@1.4.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-iIKw23BspnQQYd3prITOBxeUsxBHnwzX6YJfGMuNOZzeNcMmVqzIIVGRm1l69ogaPQmb4wB6BN8mA5bE9YuC5Q=="], @@ -211,7 +212,7 @@ "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], - "hono": ["hono@4.13.8", "", {}, "sha512-/Gng7NfoykZl2pjukW5Z6+8Yxm3BPRf86GTbQnt0SbySkvax4fyL4H3HhY1cCpBGmiW9XDRFzRV+CXK2W8QudQ=="], + "hono": ["hono@4.13.13", "", {}, "sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA=="], "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], @@ -219,7 +220,7 @@ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - "ip-address": ["ip-address@10.7.1", "", {}, "sha512-4OUAqU9Z1i3vCnS05hzGiFnEMDpQ+62pAD/MVQOp83fYyNC8GleCqaS0QikQBmcWCrKFiUs/B8ztRRiYOAXuCA=="], + "ip-address": ["ip-address@10.7.4", "", {}, "sha512-DWBMKyeM25D70nrIiTkgQth59/G+naKOTep6huF9j+GmeK3DMD60Zvxg5d5qQnz+38AHgou6ean2LECX+lrKDg=="], "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], @@ -265,7 +266,7 @@ "proxy-addr": ["proxy-addr@2.0.8", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ=="], - "qs": ["qs@6.16.0", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA=="], + "qs": ["qs@6.16.1", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg=="], "range-parser": ["range-parser@1.3.0", "", {}, "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw=="], @@ -315,11 +316,13 @@ "wreq-js": ["wreq-js@2.3.1", "", { "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ] }, "sha512-vaKasaKeskrDKEuuO5Q5uamEG9a6FrF5ZSicH7TCvYS4RxF7/gzaU/vYqwJzcs+uydyJPVWY1KCvfVCgp0tiGA=="], - "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + "zod": ["zod@4.6.6", "", {}, "sha512-4UmzQUF5fxvfcYHlbjBmejU+w+hbDnWFx4aS8kBx7nPBHcO2SqPbpAG8snbdg+Xse4pRefR98hgjFgIMCI/UhA=="], "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], - "body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], + "body-parser/content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], + + "body-parser/iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], "type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], } diff --git a/bunfig.toml b/bunfig.toml index 1a6ced2c5dc..7376005a5c5 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -16,8 +16,10 @@ root = "tests" # # That qualifier is load-bearing. Bun resolves bunfig.toml from the CURRENT WORKING # DIRECTORY, so `cd /tmp && bun test /tests/x.test.ts` loads no preload at all: nothing -# is sandboxed, the guard stays disarmed, and every `getConfigDir()` in that process returns -# the developer's real `~/.opencodex`. On 2026-09-15 such a run deleted one. A test that needs -# a config directory pins its own OPENCODEX_HOME instead of trusting this line, and -# `tests/ci-workflows/test-home-guard.test.ts` enforces that for the destructive case. +# is sandboxed, and every `getConfigDir()` in that process returns the developer's real +# `~/.opencodex`. On 2026-09-15 such a run deleted one, and in #7407 another wrote auth.json. +# The write guard now arms itself from the test-file entry in that case (src/lib/test-home-guard.ts), +# so such writes throw instead of landing, but a test that needs a config directory still pins its +# own OPENCODEX_HOME instead of trusting this line, and `tests/ci-workflows/test-home-guard.test.ts` +# enforces that for the destructive case. preload = ["./tests/preload.ts"] diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock index b994d029dc7..63f898f81ec 100644 --- a/desktop/src-tauri/Cargo.lock +++ b/desktop/src-tauri/Cargo.lock @@ -2645,7 +2645,7 @@ dependencies = [ [[package]] name = "opencodex-desktop" -version = "2.86.0" +version = "2.93.0-preview.20261027" dependencies = [ "base64 0.22.1", "dbus", diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index a65970d63d9..28072923678 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "opencodex-desktop" -version = "2.86.0" +version = "2.93.0-preview.20261027" description = "OpenCodex desktop shell" authors = ["OpenCodex contributors"] license = "MIT" diff --git a/desktop/src-tauri/src/claim.rs b/desktop/src-tauri/src/claim.rs index 22d0cfcbc24..d7e2d53ef51 100644 --- a/desktop/src-tauri/src/claim.rs +++ b/desktop/src-tauri/src/claim.rs @@ -163,7 +163,12 @@ pub async fn run(app: &AppHandle, argv: Vec, deadline: Instant) -> Claim return ClaimResult::Failed(format!("the bundled CLI could not be started ({error})")) } }; - match timeout_at(deadline, command.output()).await { + match timeout_at( + deadline, + crate::process_spawn::with_spawn_async(command.output()), + ) + .await + { Ok(Ok(output)) => read(output.status.code(), &output.stdout, &output.stderr), Ok(Err(error)) => { ClaimResult::Failed(format!("the bundled CLI could not be run ({error})")) diff --git a/desktop/src-tauri/src/cli_command.rs b/desktop/src-tauri/src/cli_command.rs index a89361e9fd7..7e05e54f85e 100644 --- a/desktop/src-tauri/src/cli_command.rs +++ b/desktop/src-tauri/src/cli_command.rs @@ -454,18 +454,20 @@ mod tests { } fn selected_observation_case(case: &str) { let t = Temp::new(); - let result = std::process::Command::new(std::env::current_exe().unwrap()) - .args([ - "--exact", - "cli_command::tests::selected_observation_child", - "--nocapture", - ]) - .env("OCX_CLI_OBSERVATION_TEST", case) - .env("HOME", &t.0) - .env("ZDOTDIR", &t.0) - .env("XDG_CONFIG_HOME", t.0.join(".config")) - .output() - .unwrap(); + let result = crate::process_spawn::with_spawn(|| { + std::process::Command::new(std::env::current_exe().unwrap()) + .args([ + "--exact", + "cli_command::tests::selected_observation_child", + "--nocapture", + ]) + .env("OCX_CLI_OBSERVATION_TEST", case) + .env("HOME", &t.0) + .env("ZDOTDIR", &t.0) + .env("XDG_CONFIG_HOME", t.0.join(".config")) + .output() + }) + .unwrap(); assert!(result.status.success(), "selected-observation-failed"); assert!( String::from_utf8_lossy(&result.stdout).contains("1 passed"), @@ -578,16 +580,18 @@ mod tests { } } fn disabled_selector_case(action: &str) { - let result = std::process::Command::new(std::env::current_exe().unwrap()) - .args([ - "--exact", - "cli_command::tests::disabled_selector_child", - "--nocapture", - ]) - .env("OCX_CLI_SELECTOR_TEST", action) - .env("ZDOTDIR", "/invalid:zdotdir") - .output() - .unwrap(); + let result = crate::process_spawn::with_spawn(|| { + std::process::Command::new(std::env::current_exe().unwrap()) + .args([ + "--exact", + "cli_command::tests::disabled_selector_child", + "--nocapture", + ]) + .env("OCX_CLI_SELECTOR_TEST", action) + .env("ZDOTDIR", "/invalid:zdotdir") + .output() + }) + .unwrap(); assert!(result.status.success(), "disabled-selector-failed"); assert!( String::from_utf8_lossy(&result.stdout).contains("1 passed"), diff --git a/desktop/src-tauri/src/cli_command_posix.rs b/desktop/src-tauri/src/cli_command_posix.rs index 8ab0fcf8295..b3016bd9ff4 100644 --- a/desktop/src-tauri/src/cli_command_posix.rs +++ b/desktop/src-tauri/src/cli_command_posix.rs @@ -673,6 +673,7 @@ pub fn remove_plan(store: &Store, current: &Record) -> Result<(Record, Vec Temp { @@ -701,13 +702,13 @@ mod tests { command.arg("-N"); } assert!( - command + with_spawn(|| command .arg(path) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) - .status() - .unwrap() - .success(), + .status()) + .unwrap() + .success(), "acl-command-failed" ); } @@ -969,7 +970,7 @@ mod tests { if let Some(path) = path { command.env("PATH", path); } - let output = command.output().unwrap(); + let output = with_spawn(|| command.output()).unwrap(); assert!(output.status.success()); assert_eq!(String::from_utf8(output.stdout).unwrap(), expected); } @@ -988,14 +989,16 @@ mod tests { 0o700, ) .unwrap(); - let out = std::process::Command::new(&shim) - .env_clear() - .env("ANTHROPIC_AUTH_TOKEN", "dummy") - .env("ANTHROPIC_API_KEY", "") - .env("OCX_NODE_LAUNCH_CONTEXT", "untrusted") - .arg("status") - .output() - .unwrap(); + let out = with_spawn(|| { + std::process::Command::new(&shim) + .env_clear() + .env("ANTHROPIC_AUTH_TOKEN", "dummy") + .env("ANTHROPIC_API_KEY", "") + .env("OCX_NODE_LAUNCH_CONTEXT", "untrusted") + .arg("status") + .output() + }) + .unwrap(); assert!(out.status.success()); let text = String::from_utf8(out.stdout).unwrap(); let lines: Vec<_> = text.lines().collect(); @@ -1011,9 +1014,7 @@ mod tests { assert!(!text.contains("dummy")); fs::remove_file(cli).unwrap(); assert_eq!( - std::process::Command::new(&shim) - .env_clear() - .output() + with_spawn(|| std::process::Command::new(&shim).env_clear().output()) .unwrap() .status .code(), @@ -1035,13 +1036,18 @@ mod tests { .replace("-N32", "-N1"), ] { record::atomic(&t.0.join("cli"), None, inspect, 0o700).unwrap(); - fs::write(&shim, rendered).unwrap(); - let out = std::process::Command::new(&shim) - .env_clear() - .env("OCX_NODE_LAUNCH_CONTEXT", "inherited") - .arg("status") - .output() - .unwrap(); + { + let _writer = crate::process_spawn::executable_write(); + fs::write(&shim, rendered).unwrap(); + } + let out = with_spawn(|| { + std::process::Command::new(&shim) + .env_clear() + .env("OCX_NODE_LAUNCH_CONTEXT", "inherited") + .arg("status") + .output() + }) + .unwrap(); assert!(out.status.success()); assert_eq!(out.stdout, b"\nstatus\n"); fs::remove_file(t.0.join("cli")).unwrap(); @@ -1197,15 +1203,17 @@ mod tests { // System rc files may print to stdout (Ubuntu's /etc/bash.bashrc sudo hint), so only // the marked result lines count; a failed lookup or CLI run still fails the shell, and // the end marker makes any extra CLI output line fail the comparison. - let output = command - .args([ - "-i", - "-c", - "p=$(command -v ocx) || exit 11; o=$(ocx hello) || exit 12; \ + let output = with_spawn(|| { + command + .args([ + "-i", + "-c", + "p=$(command -v ocx) || exit 11; o=$(ocx hello) || exit 12; \ printf 'OCX-PATH:%s\\nOCX-OUT:%s:OCX-END\\n' \"$p\" \"$o\"", - ]) - .output() - .expect("cannot start real shell"); + ]) + .output() + }) + .expect("cannot start real shell"); assert!(output.status.success(), "{shell}: command failed"); let stdout = String::from_utf8(output.stdout).expect("shell stdout is not UTF-8"); let marked = |tag: &str| { diff --git a/desktop/src-tauri/src/cli_command_record.rs b/desktop/src-tauri/src/cli_command_record.rs index 294c0d86346..950993a3b4f 100644 --- a/desktop/src-tauri/src/cli_command_record.rs +++ b/desktop/src-tauri/src/cli_command_record.rs @@ -548,6 +548,8 @@ fn replace(from: &Path, to: &Path) -> Result<()> { io(fs::rename(from, to)) } pub fn atomic(path: &Path, before: Option<&[u8]>, after: &[u8], mode: u32) -> Result<()> { + #[cfg(unix)] + let _executable_write = (mode & 0o111 != 0).then(crate::process_spawn::executable_write); let parent = path.parent().ok_or("unsafe-file")?; check(parent, true)?; rc_acl_check(path)?; @@ -593,6 +595,8 @@ pub fn atomic(path: &Path, before: Option<&[u8]>, after: &[u8], mode: u32) -> Re if read_bytes(path)?.as_deref() != before { return Err("concurrent-edit".into()); } + // Publish only after releasing our writer; a terminal may execute the new shim immediately. + drop(f); replace(&tmp, path)?; #[cfg(unix)] io(fs::File::open(parent).and_then(|f| f.sync_all()))?; @@ -1145,6 +1149,40 @@ impl Store { #[cfg(test)] pub(crate) mod tests { use super::*; + #[cfg(unix)] + use crate::process_spawn::with_spawn; + #[cfg(unix)] + #[test] + fn executable_writes_wait_for_process_spawns_but_record_writes_do_not() { + use std::{sync::mpsc, thread, time::Duration}; + let t = Temp::new(); + let path = t.0.join("shim"); + let (started_tx, started_rx) = mpsc::channel(); + let (done_tx, done_rx) = mpsc::channel(); + let (writer, early_completion) = with_spawn(|| { + // Non-executable record writes must still overlap the process-spawn stress test. + atomic(&t.0.join("record"), None, b"record", 0o600).unwrap(); + let path = path.clone(); + let writer = thread::spawn(move || { + started_tx.send(()).unwrap(); + let result = atomic(&path, None, b"#!/bin/sh\nexit 0\n", 0o700); + done_tx.send(()).unwrap(); + result + }); + started_rx.recv_timeout(Duration::from_secs(5)).unwrap(); + let early_completion = done_rx.recv_timeout(Duration::from_millis(100)); + (writer, early_completion) + }); + if early_completion.is_err() { + done_rx.recv_timeout(Duration::from_secs(5)).unwrap(); + } + writer.join().unwrap().unwrap(); + assert!(matches!( + early_completion, + Err(mpsc::RecvTimeoutError::Timeout) + )); + assert_eq!(fs::read(path).unwrap(), b"#!/bin/sh\nexit 0\n"); + } pub(crate) struct Temp(pub PathBuf); impl Temp { pub(crate) fn new() -> Self { @@ -1554,13 +1592,13 @@ pub(crate) mod tests { command.arg("-N"); } assert!( - command + with_spawn(|| command .arg(path) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) - .status() - .unwrap() - .success(), + .status()) + .unwrap() + .success(), "acl-command-failed" ); } @@ -1631,12 +1669,14 @@ pub(crate) mod tests { fn linux_acl_inheritance_clear_and_existing_child_refusal() { let t = Temp::new(); let set = |path: &Path, spec: &str| { - std::process::Command::new("setfacl") - .args(["-m", spec]) - .arg(path) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .status() + with_spawn(|| { + std::process::Command::new("setfacl") + .args(["-m", spec]) + .arg(path) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + }) }; let result = set(&t.0, "d:u:65534:rwx"); if result @@ -1906,7 +1946,9 @@ pub(crate) mod tests { ready.wait(); let mut spawns = 0; while !stop.load(Ordering::Relaxed) { - assert!(Command::new(TRUE).status().unwrap().success()); + assert!(with_spawn(|| Command::new(TRUE).status()) + .unwrap() + .success()); spawns += 1; } spawns diff --git a/desktop/src-tauri/src/exit.rs b/desktop/src-tauri/src/exit.rs index 8950c338638..9c3d0040881 100644 --- a/desktop/src-tauri/src/exit.rs +++ b/desktop/src-tauri/src/exit.rs @@ -535,7 +535,7 @@ async fn finish_and_exit_after(app: &AppHandle, reason: ExitReason) { match (reason, verdict) { (ExitReason::UserQuit, _) => app.exit(0), (ExitReason::CoordinatedRestart, DrainVerdict::Drained) => { - app.restart(); + crate::process_spawn::with_spawn(|| app.restart()); } (ExitReason::CoordinatedRestart, _) => { crate::logging::log_once("the update restart was refused", verdict.describe()); @@ -574,7 +574,7 @@ pub async fn prepare_restart(app: &AppHandle) -> RestartReadiness { /// Come back, once the installer has finished and returned. pub fn complete_restart(app: &AppHandle) -> ! { - app.restart() + crate::process_spawn::with_spawn(|| app.restart()) } /// Stop the runtime this app owns and confirm it is gone. diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index d4e3dec7e32..255954ed767 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -46,6 +46,7 @@ mod tray_login; #[allow(dead_code)] #[path = "popup.rs"] mod popup_portable_test; +mod process_spawn; mod proxy; mod resolve; mod runtime_stop; @@ -373,6 +374,9 @@ pub fn run() { .inner_size(1100.0, 720.0) .visible(false) .user_agent(&window::webview_user_agent()) + // Keep dashboard HTML drag/drop (including chat panes) in the webview; + // Tauri's native drop handler intercepts it on Windows. + .disable_drag_drop_handler() // Cmd on macOS, Ctrl elsewhere, with + / - / 0. WebView2 zooms natively; on // macOS and Linux Tauri injects a keydown polyfill whose one IPC call is granted // to the loopback dashboard by `capabilities/dashboard-zoom.json`. diff --git a/desktop/src-tauri/src/login_registration.rs b/desktop/src-tauri/src/login_registration.rs index 43ea2ae71e2..4adf3f78550 100644 --- a/desktop/src-tauri/src/login_registration.rs +++ b/desktop/src-tauri/src/login_registration.rs @@ -527,13 +527,16 @@ mod command { pub(super) fn run(program: &str, args: &[&str], timeout: Duration) -> Result { let slot = Slot::take().ok_or(RunError::Failed)?; let deadline = Instant::now() + timeout; - let mut child = Command::new(program) - .args(args) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .map_err(|_| RunError::Failed)?; + let spawn = || { + Command::new(program) + .args(args) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + }; + let child = crate::process_spawn::with_spawn(spawn); + let mut child = child.map_err(|_| RunError::Failed)?; let readers = [drain(child.stdout.take()), drain(child.stderr.take())]; let status = loop { match child.try_wait() { @@ -579,6 +582,29 @@ mod command { #[test] fn a_command_past_its_deadline_holds_the_only_slot_until_it_ends() { + // Measure command deadlines in a fresh process: the parent's executable-fixture + // writers may hold the test spawn lock longer than this test's timing budget. + const CHILD: &str = "OCX_LOGIN_COMMAND_DEADLINE_CHILD"; + if std::env::var_os(CHILD).is_none() { + let output = crate::process_spawn::with_spawn(|| { + Command::new(std::env::current_exe().unwrap()) + .args([ + "--exact", + "login_registration::command::tests::a_command_past_its_deadline_holds_the_only_slot_until_it_ends", + "--nocapture", + ]) + .env(CHILD, "1") + .output() + }) + .unwrap(); + assert!( + output.status.success(), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + assert!(String::from_utf8_lossy(&output.stdout).contains("1 passed")); + return; + } let started = Instant::now(); assert_eq!( run("/bin/sleep", &["1"], Duration::from_millis(50)), diff --git a/desktop/src-tauri/src/process_spawn.rs b/desktop/src-tauri/src/process_spawn.rs new file mode 100644 index 00000000000..51cdcd9936e --- /dev/null +++ b/desktop/src-tauri/src/process_spawn.rs @@ -0,0 +1,203 @@ +//! Keep Unix forks from inheriting a writable descriptor for a soon-to-be-published executable. +//! CLOEXEC only closes it at exec; closing the parent's copy does not close a child's copy. + +// A queued writer must not block another spawner: a plugin launcher can wait for the main +// thread, where a second launcher may run. std::RwLock may prefer the queued writer and deadlock +// those two readers. Use reader preference; only an active writer excludes process creation. +#[cfg(unix)] +static EXECUTABLE_IO: Gate = Gate::new(); + +#[cfg(unix)] +struct Gate { + access: std::sync::Mutex, + changed: std::sync::Condvar, +} + +#[cfg(unix)] +struct Access { + spawners: usize, + writing: bool, +} + +#[cfg(unix)] +struct Guard<'a> { + gate: &'a Gate, + writing: bool, +} + +#[cfg(unix)] +impl Gate { + const fn new() -> Self { + Self { + access: std::sync::Mutex::new(Access { + spawners: 0, + writing: false, + }), + changed: std::sync::Condvar::new(), + } + } + + fn access(&self) -> std::sync::MutexGuard<'_, Access> { + self.access + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + } + + fn acquire(&self, writing: bool) -> Guard<'_> { + let mut access = self + .changed + .wait_while(self.access(), |s| s.writing || (writing && s.spawners != 0)) + .unwrap_or_else(std::sync::PoisonError::into_inner); + if writing { + access.writing = true; + } else { + access.spawners += 1; + } + Guard { + gate: self, + writing, + } + } +} + +#[cfg(unix)] +impl Drop for Guard<'_> { + fn drop(&mut self) { + let mut access = self.gate.access(); + if self.writing { + access.writing = false; + } else { + access.spawners -= 1; + } + self.gate.changed.notify_all(); + } +} + +#[cfg(unix)] +pub(crate) fn executable_write() -> impl Drop { + EXECUTABLE_IO.acquire(true) +} + +/// Cover process creation through exec, not the subsequent wait for the child's exit. +/// Never request an executable writer from a spawner, or call this from a writer/pre_exec hook. +pub(crate) fn with_spawn(spawn: impl FnOnce() -> T) -> T { + #[cfg(unix)] + let _guard = EXECUTABLE_IO.acquire(false); + spawn() +} + +/// The pinned shell plugin's output future spawns synchronously when polled. Guard each poll +/// so creation is covered even for a lazy future; release before Pending, never across await. +pub(crate) async fn with_spawn_async(future: F) -> F::Output { + let mut future = std::pin::pin!(future); + std::future::poll_fn(|cx| with_spawn(|| future.as_mut().poll(cx))).await +} + +#[cfg(all(test, unix))] +mod tests { + use super::*; + use std::{ + future::{poll_fn, Future}, + sync::{mpsc, Arc}, + task::{Context, Poll, Waker}, + time::Duration, + }; + + #[test] + fn a_spawn_waits_for_the_writer_and_releases_before_child_exit() { + let writer = executable_write(); + let (started_tx, started_rx) = mpsc::channel(); + let (spawned_tx, spawned_rx) = mpsc::channel(); + let spawner = std::thread::spawn(move || { + started_tx.send(()).unwrap(); + let child = with_spawn(|| { + std::process::Command::new("/bin/cat") + .stdin(std::process::Stdio::piped()) + .spawn() + }); + spawned_tx.send(child).unwrap(); + }); + started_rx.recv_timeout(Duration::from_secs(5)).unwrap(); + let early = spawned_rx.recv_timeout(Duration::from_millis(100)); + drop(writer); + let mut child = match early { + Err(mpsc::RecvTimeoutError::Timeout) => spawned_rx + .recv_timeout(Duration::from_secs(5)) + .unwrap() + .unwrap(), + Ok(Ok(mut child)) => { + drop(child.stdin.take()); + let _ = child.wait(); + panic!("spawn overlapped an executable writer"); + } + _ => panic!("spawn failed before the writer was released"), + }; + spawner.join().unwrap(); + // cat is still waiting for EOF, but executable publication must already be unblocked. + let writer = executable_write(); + assert!(child.try_wait().unwrap().is_none()); + drop(writer); + drop(child.stdin.take()); + assert!(child.wait().unwrap().success()); + } + + #[test] + fn queued_writer_does_not_block_a_launchers_main_thread_callback() { + // An independent instance of the production gate keeps unrelated fixture/spawn stress + // from consuming this scheduling test's deadline. + let gate = Arc::new(Gate::new()); + let launcher = gate.acquire(false); + let writer_gate = gate.clone(); + let (started_tx, started_rx) = mpsc::channel(); + let (done_tx, done_rx) = mpsc::channel(); + let writer = std::thread::spawn(move || { + started_tx.send(()).unwrap(); + let _writer = writer_gate.acquire(true); + done_tx.send(()).unwrap(); + }); + started_rx.recv_timeout(Duration::from_secs(5)).unwrap(); + assert!(matches!( + done_rx.recv_timeout(Duration::from_millis(100)), + Err(mpsc::RecvTimeoutError::Timeout) + )); + // Model an install helper waiting for a main-thread callback that launches a URL. + let callback = gate.acquire(false); + assert_eq!(gate.access().spawners, 2); + drop(callback); + drop(launcher); + done_rx.recv_timeout(Duration::from_secs(5)).unwrap(); + writer.join().unwrap(); + } + + #[test] + fn async_spawn_is_guarded_on_poll_without_holding_a_lock_across_pending() { + let mut polled = false; + let future = with_spawn_async(poll_fn(|_| { + assert!( + EXECUTABLE_IO.access().spawners > 0, + "async spawn poll must hold shared access" + ); + if polled { + Poll::Ready(42) + } else { + polled = true; + Poll::Pending + } + })); + fn assert_send(_: &impl Send) {} + assert_send(&future); + let mut future = std::pin::pin!(future); + let mut cx = Context::from_waker(Waker::noop()); + assert_eq!(future.as_mut().poll(&mut cx), Poll::Pending); + // A paused output reader cannot stop a reconcile from writing its shim. + drop(executable_write()); + assert_eq!(future.as_mut().poll(&mut cx), Poll::Ready(42)); + } + + #[test] + fn failed_spawns_release_the_guard() { + let result = with_spawn(|| std::process::Command::new("/ocx-missing-executable").spawn()); + assert!(result.is_err()); + drop(executable_write()); + } +} diff --git a/desktop/src-tauri/src/provider_icons.rs b/desktop/src-tauri/src/provider_icons.rs index 2799453c795..515d9b9f643 100644 --- a/desktop/src-tauri/src/provider_icons.rs +++ b/desktop/src-tauri/src/provider_icons.rs @@ -37,6 +37,8 @@ const ALIASES: &[(&str, &str)] = &[ ("commandcode", "commandcode-color.svg"), ("cursor", "cursor-color.svg"), ("deepseek", "deepseek-color.svg"), + ("dsh-account", "deepseek-color.svg"), + ("workbuddy-ai", "workbuddy-color.svg"), ("devin", "devin.svg"), ("firepass", "firepass-color.svg"), ("fireworks", "fireworks-color.svg"), @@ -162,6 +164,7 @@ fn svg(file: &str) -> Option<&'static str> { "cursor-color.svg" => svg!("cursor-color.svg"), "deepinfra.svg" => svg!("deepinfra.svg"), "deepseek-color.svg" => svg!("deepseek-color.svg"), + "workbuddy-color.svg" => svg!("workbuddy-color.svg"), "devin.svg" => svg!("devin.svg"), "digitalocean.svg" => svg!("digitalocean.svg"), "featherless.svg" => svg!("featherless.svg"), diff --git a/desktop/src-tauri/src/resolve.rs b/desktop/src-tauri/src/resolve.rs index 1f9ddcfdda0..7e590e53189 100644 --- a/desktop/src-tauri/src/resolve.rs +++ b/desktop/src-tauri/src/resolve.rs @@ -304,7 +304,12 @@ pub async fn run(app: &AppHandle, deadline: Instant) -> Resolution { return Resolution::Unknown(format!("the bundled CLI could not be started ({error})")) } }; - match timeout_at(deadline, command.output()).await { + match timeout_at( + deadline, + crate::process_spawn::with_spawn_async(command.output()), + ) + .await + { Ok(Ok(output)) => read(output.status.code(), &output.stdout, &output.stderr), Ok(Err(error)) => { Resolution::Unknown(format!("the bundled CLI could not be run ({error})")) diff --git a/desktop/src-tauri/src/runtime_stop.rs b/desktop/src-tauri/src/runtime_stop.rs index c92528a5f4b..664da927067 100644 --- a/desktop/src-tauri/src/runtime_stop.rs +++ b/desktop/src-tauri/src/runtime_stop.rs @@ -270,7 +270,12 @@ async fn run_with_args(app: &AppHandle, deadline: Instant, argv: Vec) -> return StopResult::Failed(format!("the bundled CLI could not be started ({error})")) } }; - match timeout_at(deadline, command.output()).await { + match timeout_at( + deadline, + crate::process_spawn::with_spawn_async(command.output()), + ) + .await + { Ok(Ok(output)) => read(output.status.code(), &output.stdout, &output.stderr), Ok(Err(error)) => StopResult::Failed(format!("the bundled CLI could not be run ({error})")), Err(_) => { diff --git a/desktop/src-tauri/src/sidecar.rs b/desktop/src-tauri/src/sidecar.rs index 71f16db8f0a..e601498fe0f 100644 --- a/desktop/src-tauri/src/sidecar.rs +++ b/desktop/src-tauri/src/sidecar.rs @@ -205,7 +205,8 @@ pub fn start( .args(["start", "--port", &endpoint.port.to_string()]) .env("OPENCODEX_GUI_DIST", gui_dist) .env(SUPERVISED_ENV, "1"); - let (events, child) = command.spawn().map_err(|error| error.to_string())?; + let (events, child) = + crate::process_spawn::with_spawn(|| command.spawn()).map_err(|error| error.to_string())?; let watch = watch.for_child(child.pid()); watch.follow(events); Ok(child) diff --git a/desktop/src-tauri/src/tray.rs b/desktop/src-tauri/src/tray.rs index c702bc7c240..ec76efdf4a7 100644 --- a/desktop/src-tauri/src/tray.rs +++ b/desktop/src-tauri/src/tray.rs @@ -224,9 +224,10 @@ pub fn install(app: &AppHandle) -> tauri::Result<()> { else { return; }; - let _ = app - .opener() - .open_url(format!("{}#/usage", endpoint.url("/")), None::); + let _ = crate::process_spawn::with_spawn(|| { + app.opener() + .open_url(format!("{}#/usage", endpoint.url("/")), None::) + }); } "start-at-login" => { #[cfg(target_os = "macos")] diff --git a/desktop/src-tauri/src/tray_login.rs b/desktop/src-tauri/src/tray_login.rs index 5126c1f44e7..3a1948ed855 100644 --- a/desktop/src-tauri/src/tray_login.rs +++ b/desktop/src-tauri/src/tray_login.rs @@ -151,10 +151,11 @@ fn perform(app: &AppHandle, action: ClickAction) { .map_err(|error| error.to_string()), ClickAction::Enable => crate::login_autostart::enable(app), // launchd keeps its override whatever the plist says; the person turns it back on. - ClickAction::OpenLoginItems => app - .opener() - .open_url(login_registration::LOGIN_ITEMS_SETTINGS, None::) - .map_err(|error| error.to_string()), + ClickAction::OpenLoginItems => crate::process_spawn::with_spawn(|| { + app.opener() + .open_url(login_registration::LOGIN_ITEMS_SETTINGS, None::) + }) + .map_err(|error| error.to_string()), ClickAction::Nothing => Ok(()), }; if let Err(error) = result { diff --git a/desktop/src-tauri/src/updater.rs b/desktop/src-tauri/src/updater.rs index bcaf3349533..cc02ae9e9fc 100644 --- a/desktop/src-tauri/src/updater.rs +++ b/desktop/src-tauri/src/updater.rs @@ -410,7 +410,7 @@ pub async fn install(app: &AppHandle, update: Update) -> Result<(), String> { )); } - if let Err(error) = update.install(package) { + if let Err(error) = crate::process_spawn::with_spawn(|| update.install(package)) { // The installer returned a failure. The runtime was stopped for an install that did not // happen, so the app brings one back instead of sitting drained. recover_after_failed_install(app); diff --git a/desktop/src-tauri/src/window.rs b/desktop/src-tauri/src/window.rs index 5c69ae17fe7..7b71614c64a 100644 --- a/desktop/src-tauri/src/window.rs +++ b/desktop/src-tauri/src/window.rs @@ -67,7 +67,9 @@ fn opens_in_default_browser(url: &Url) -> bool { pub fn open_in_default_browser(url: &Url) { if opens_in_default_browser(url) { - let _ = tauri_plugin_opener::open_url(url.as_str(), None::<&str>); + let _ = crate::process_spawn::with_spawn(|| { + tauri_plugin_opener::open_url(url.as_str(), None::<&str>) + }); } } diff --git a/desktop/src-tauri/tauri.conf.json b/desktop/src-tauri/tauri.conf.json index c1bb2b933d3..1a08a75500d 100644 --- a/desktop/src-tauri/tauri.conf.json +++ b/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "OpenCodex", - "version": "2.86.0", + "version": "2.93.0-preview.20261027", "identifier": "com.opencodex.desktop", "build": { "frontendDist": "../ui", diff --git a/devlog/_fin/261015_argon_thought_boundary/000_plan.md b/devlog/_fin/261015_argon_thought_boundary/000_plan.md new file mode 100644 index 00000000000..7497e8bb94d --- /dev/null +++ b/devlog/_fin/261015_argon_thought_boundary/000_plan.md @@ -0,0 +1,97 @@ +# Gemini Argon output separation + +A report of reasoning-looking assistant output prompted comparison of the Google response +classifier with CLIProxyAPI and cc-switch. The inspected classifier already separates marked +thoughts. This unit verifies that separation through every client protocol using synthetic +markers, and documents the boundary without claiming the original observation is reproduced. + +## Loop specification + +- Archetype: satisfy-spec; one C3 verification/documentation work phase. +- Trigger/goal: investigate Argon output separation and deliver an evidence-backed PR to dev. +- In scope: synthetic Argon integration tests, their layout registration, and current Google docs. +- Non-goals: heuristic text censorship, runtime changes without a demonstrated defect, private + session transcripts, user configuration changes, deployment, other worktrees, native PR stacks. +- Verifier: new protocol test plus adjacent Google/direct-encoder tests; typecheck, test layout, + file-size, privacy and structure gates; docs build; required CI at the PR head. +- Stop: coverage/docs merged with passing relevant checks, or a concrete blocker recorded. +- Memory artifact: this unit and local `.tmp/argon-protocol/` evidence; no memory-store writes. +- Outcomes: DONE for the verification PR; runtime NOOP if marked thoughts remain separated. + Do not claim the reported live symptom fixed merely because synthetic tests pass. +- Escalation: a demonstrated defect changes the plan before patching; unavailable live evidence + remains an explicit limitation. Existing user authority includes push/PR/merge, no release. +- Bounds: none stated; host limits apply. Tools: local Bun, read-only public Aside/GitHub research, + repository-scoped GitHub publication. No production credentials are needed for tests. + +## Evidence and hypotheses + +- H1: the adapter converts marked thoughts to answer text. Falsifier: mixed marked/unmarked + synthetic parts preserve separate channels in streaming and buffered responses. +- H2: a client converter collapses channels. Falsifier: actual Responses, Chat and Messages + handlers keep thought markers out of ordinary answer fields with direct routing on and off. +- H3: upstream ordinary text or client rendering explains the observation. Static tests cannot + establish this; never reclassify prose by its wording or infer a root cause from test success. + +Source anchors: `src/adapters/google.ts:716`, `:989`, `:1378`, `:1654`; +`src/protocols/encoders/chat.ts:133`; `src/protocols/encoders/messages.ts:135`. +CLIProxyAPI `5fc580b1d72d87a7bcfe5cfb6f479c1aea5625b3` maps `thought` to +`reasoning_content`; cc-switch `76f99d41a66e24a5ed2e8982bc494399f306bfe6` filters +marked thoughts from visible text. Public Aside reads succeeded with complete=true, +truncated=false, contentVerified=true for Google thinking docs and CLIProxyAPI #6607. +Those docs distinguish thought signatures from ordinary text visibility; #6607 is a buffering +decision, not evidence of text leakage. Argon upstream behavior is not independently certified. + +Baseline: `bun test tests/adapters/google/google-hardening.test.ts +tests/adapters/google/google-adapter.test.ts tests/adapters/google/gemini-4-argon-antigravity.test.ts +tests/responses/responses-show-thinking-summary.test.ts`: exit 0, 158 pass at 7640a942b3. +Work then refreshed to dev 0b13396af4; adjacent checks will run on the final patch. +Typecheck's `include: ["src"]` does not validate new tests; Bun execution does. + +## Consultation + +Architect proposal and reflection are retained in the local coordinator evidence. +- ARGON-SEP-01/02 accepted: keep one classifier and existing output channels. +- ARGON-SEP-03 accepted: provenance is not proof of observed upstream behavior. +- ARGON-SEP-04 amended: add handler-level coverage and current contract clarification. + Preserve historical ADR-0055 per `structure/AGENTS.md`; do not rewrite the old decision. +- Same-architect reflection: ALIGNED against `010_verification.md`; require assertions on both + deltas and final accumulated payloads, and correct tool signature attachment where exposed. + Independent plan audit: PASS. Refreshed-base adjacent direct/encoder baseline: 131 pass, 0 fail. + +No new runtime fields or enums. No new runtime enforcement: tests are regression checks only; +they can be bypassed by skipping tests, and cannot certify external client rendering. + +## Verification result + +The investigation's runtime outcome is NOOP: the tested marked thoughts stay out of ordinary +answer fields. New coverage adds 75 cases. Deliberately changing the classifier to emit marked +thoughts as text fails all 75; byte-for-byte restoration returns all 75 to green. No classifier +mutation is part of the patch. The live observation's cause remains unresolved. + +Final focused validation on the repository-pinned Bun 1.4.3: 364 pass / 0 fail across the new +matrix and eight adjacent Google/direct-protocol files; layout/tooling/size: 36 pass / 0 fail. +`bun run test:changed` selects the new file only and passes all 75 cases. `bun run typecheck`, +`bun run structure:check`, and `bun run privacy:scan` pass. `cd docs-site && bun run build` +builds 577 pages and checks 81,202 links. Raw logs stay in local `.tmp/argon-protocol/`. +An initial layout run under the shell's Bun 1.4.0 failed the pinned-runner check; using the +already-installed Bun 1.4.3 resolved it without source changes. + +Initial fixture expectations incorrectly treated a disabled provider display default as a +client opt-out and omitted the Messages thinking opt-in. The current tests explicitly request +the protocol's display state, matching `core-normalize.ts` and `claude/inbound.ts`; they still +force upstream marked thoughts when hidden to test output suppression, not just request flags. + +The full 2,330-file suite is not run locally: this tests/docs-only change has bounded integration +coverage and shares the host with concurrent coding workloads. Broader coverage remains with +the PR's required CI. Neither this exception nor synthetic tests certify external harness UI. + +Fresh independent final review: PASS, no actionable defects. The fresh reader identified the +result as synthetic regression coverage, with the live symptom explicitly unverified. No prose +change was requested. The investigation is closed with that limitation; PR integration is +tracked by the coordinator. Runtime source is unchanged. + +Hosted review follow-up: restore all fixture globals in finally if spend-lease release throws; +add independent client-only opt-outs with provider opt-in still true for all three protocols. +A Chat opt-out mutation fails 18 cases; a Messages display-omitted mutation fails 6. All +production files were restored byte-for-byte. These probes confirm the added cases detect +the independent client preference, rather than passing only because the provider default is off. diff --git a/devlog/_fin/261015_argon_thought_boundary/010_verification.md b/devlog/_fin/261015_argon_thought_boundary/010_verification.md new file mode 100644 index 00000000000..8f328c5c6e7 --- /dev/null +++ b/devlog/_fin/261015_argon_thought_boundary/010_verification.md @@ -0,0 +1,33 @@ +# One phase: verify the Argon output boundary + +Depends on current Google adapter and existing client handlers; no runtime design changes. + +1. NEW `tests/responses/protocol-argon-thought-boundary.test.ts`: isolated homes and synthetic + credentials, mock Google transport only, invoke real Responses/Chat/Messages handlers. + Generate `thought: true` marker parts, ordinary text (absent and false markers), signature-only + parts, an unmarked signed text part, and an optional tool call with synthetic signature. + Exercise buffered and SSE, coalesced and split SSE frames, and direct/bridge Chat/Messages. + Assert exact ordinary answer text in both deltas and accumulated final payloads, + thought marker only in reasoning/thinking fields, no + signature-only text, preserved tool name/args and call-bound signature where the client wire + exposes it, successful terminal. For independent client-only opt-outs (Responses/Chat summary none and Messages display omitted), + force upstream thought parts anyway and require suppression while answer and tool survive. + Inspect protocol trace so the direct/bridge cases prove the intended lane executed. +2. MODIFY `scripts/test-layout/layout.json` and `tests/fixtures/test-layout-expected.json`: + register the new test in responses, using existing shard conventions. +3. MODIFY `structure/providers/google.md`: describe signature-independent text classification + and reference the new test; preserve the historical decision record. +4. MODIFY `docs-site/src/content/docs/guides/providers.md`: clarify Gemini thought summaries, + client field separation, and that unmarked ordinary text is not censored by wording. +5. Run new tests and adjacent Google/direct-lane/encoder tests. Validate the new oracle by a + temporary local classifier mutation (marked thoughts become text), require failure, restore + bytes, then require green. No mutation may be committed. +6. Run typecheck, test-layout/tooling, file-size, privacy, structure and docs build. Full suite + default applies; if concurrent workload makes it disproportionate for tests/docs only, record + exact focused scope and leave remaining platform/integration coverage to required CI. +7. Fresh independent review, fix valid findings, create ordinary PR to dev using template. + Inspect exact-head required CI and maintainer-integration policy before the authorized merge. + +Acceptance is protocol correctness for the synthetic fixtures and prevention of regression. +The original live symptom and other harnesses' rendering remain unverified; the PR and final +report must say so. A newly demonstrated runtime defect requires a plan amendment and audit. diff --git a/devlog/_plan/260928_remote_thread_provider_policy/000_plan.md b/devlog/_plan/260928_remote_thread_provider_policy/000_plan.md new file mode 100644 index 00000000000..530fa2bd582 --- /dev/null +++ b/devlog/_plan/260928_remote_thread_provider_policy/000_plan.md @@ -0,0 +1,32 @@ +# 5848: native remote-list provider policy before a backend relay + +Status: **proposal and executable specifications only; no production fix**. +Date: 2026-09-28. + +Related: [#5848](https://github.com/lidge-jun/opencodex/issues/5848), +[duplicate #5906](https://github.com/lidge-jun/opencodex/issues/5906), +[upstream #48358](https://github.com/openai/codex/issues/48358). + +## Proposed decision + +Prefer a narrowly scoped, operator-opt-in policy in **native Codex app-server** +for remote `thread/list` requests that do not supply a provider array. Keep the +normal default-provider behavior, all existing authorization, and history intact. +The mobile client's explicit all-provider request remains the simplest upstream +client correction. Compare both with the previously tested local backend relay; +do not turn that experimental relay into an installed feature in this PR. + +The current OpenCodex runtime and [ADR-5848](../../../structure/decisions/ADR-5848-provider-table-remote-history-visibility.md) +remain unchanged. This proposal does **not** close #5848. + +## Work remaining + +- [x] Identify the native remote-connection boundary and existing provider predicate. +- [x] Specify precedence and test the native-policy proposal offline. +- [x] Retain and rerun the isolated loopback relay alternative. +- [ ] Obtain upstream agreement on the native configuration/API contract. +- [ ] Implement config/schema, trusted-origin plumbing, and Rust regressions upstream. +- [ ] Verify native pagination, managed policy, account changes, and actual mobile resume. +- [ ] Establish released-version/capability detection before adding any OpenCodex toggle. + +See [design](010_design.md) and [verification](020_verification.md). diff --git a/devlog/_plan/260928_remote_thread_provider_policy/010_design.md b/devlog/_plan/260928_remote_thread_provider_policy/010_design.md new file mode 100644 index 00000000000..a0f8ca22474 --- /dev/null +++ b/devlog/_plan/260928_remote_thread_provider_policy/010_design.md @@ -0,0 +1,134 @@ +# Design comparison and proposed native contract + +This is an RFC, not an accepted architecture change or a released setting. +`probes/native_policy.py` is an executable specification, **not native Rust code**. +No probe is imported by OpenCodex or registered in its runtime/CLI/test suite. + +## Evidence and scope + +The source baseline is OpenAI Codex +`1cc7e2361237ce7244430ee1d581c77f95c57ac8` and OpenCodex `dev` +`eb7f0f0970c2298f8b2d66d170c4d4be869f301b`. + +- [Native list predicate](https://github.com/openai/codex/blob/1cc7e2361237ce7244430ee1d581c77f95c57ac8/codex-rs/app-server/src/request_processors/thread_processor.rs#L5456-L5530): explicit nonempty arrays filter to those ids; `[]` removes the provider predicate; omission defaults to the configured provider, except for related-thread queries. +- [Remote connection origin](https://github.com/openai/codex/blob/1cc7e2361237ce7244430ee1d581c77f95c57ac8/codex-rs/app-server-transport/src/transport/remote_control/client_tracker.rs): native remote sessions open with `ConnectionOrigin::RemoteControl`; messages become native transport events. +- [Original rationale, #5658](https://github.com/openai/codex/pull/5658): provider annotations/filtering address cross-provider resume/decryption failures. Visibility is not proof that a conversation is safe to resume under a different provider. +- [OpenCodex mitigation, #6007](https://github.com/lidge-jun/opencodex/pull/6007): warnings and documentation without rewriting native history or intercepting native RPC. + +## Alternatives + +| Approach | Can leave official mobile app unchanged? | Additional ownership | Recommendation | +| --- | --- | --- | --- | +| Mobile explicitly sends `modelProviders: []` (or a chosen list) | No | Client's list behavior and resume UX | Simplest client correction; continue upstream tracking. | +| Native, opt-in, remote-only default list policy | Yes, once a compatible native build ships | Native config and request policy, no extra service | **Preferred implementation proposal** when the host needs explicit control. | +| Local relay selected through `chatgpt_base_url` | Potentially; live service not verified | Shared backend traffic, token forwarding, connection lifecycle | Research fallback, not a default or currently supported remedy. | +| Globally change omitted filter to all providers | Yes | Changes behavior for every caller | Do not use: loses the existing default isolation behavior. | +| Rewrite `openai` history tags to `opencodex` | Yes in some observations | Native SQLite/rollout state | Do not use: violates the retained history-writer boundary. | + +The native proposal minimizes new connection and credential handling; this is an +engineering recommendation, not evidence of upstream acceptance or deployment. +Neither upstream route can be delivered by modifying OpenCodex's `/v1` inference +proxy alone. This PR publishes the comparison rather than disguising a probe as a fix. + +## Proposed native setting (name subject to upstream review) + +**Illustrative only; do not add this to current Codex/OpenCodex configuration.** + +```toml +[remote_control] +thread_list_model_providers = ["openai", "opencodex"] +``` + +Absent setting means opt-out. An explicitly empty array means all providers; +a nonempty array means exactly those ids. Do not automatically infer equivalence +from provider names, model names, or a shared URL. The setting changes listing, +not authentication, permission checks, routing, compaction, or resume semantics. +An operator who only needs two provider ids need not opt into every provider. + +### Precedence + +1. Existing authentication and managed remote-control policy still run first. +2. A request's explicit provider **array**, including `[]`, always wins. +3. With no array, parent/ancestor queries retain their current no-default-filter behavior. +4. Only a server-identified remote connection may use the operator's configured policy. +5. With no policy, and for every non-remote connection, preserve the existing default. + +Typed Rust `Option>` treats omission and JSON `null` alike. This +proposal deliberately applies its default to both. The raw relay probe preserves +all present JSON keys, including null; that difference is documented and must not +be mistaken for identical behavior between the two alternatives. + +Use the native `ConnectionOrigin` attached to the connection. Never infer remote +origin from clientInfo.name, a user-agent, JSON fields, or a caller-supplied header. +The Python enum tests only model this trusted input; they do not prove authentication. + +### Native implementation boundary + +The upstream change must add and validate the config contract, snapshot it with +the native app-server's effective configuration, carry the trusted connection +origin to the `thread/list` handler, and resolve the provider predicate before +calling the existing store pagination. A new RPC layer is unnecessary. + +Conceptual selection, **not a drop-in patch**: + +```text +if request supplies provider array: use that array +else if parent/ancestor query: use existing related-thread behavior +else if trusted origin is RemoteControl and operator policy is configured: + use the configured array +else: use existing configured-default behavior +``` + +Do not add a global exception to `list_threads_common` for all callers. Do not +change `thread/start`, `thread/resume`, `turn/start`, stored provider metadata, +or request error handling. Keep source/cwd/archive/project filters and ordering. +The new configuration's trust/precedence should prevent project content from +silently opting an operator into a broader default; upstream must choose and test +the appropriate configuration layers and any managed restrictions. + +Hold one effective policy throughout a listing/pagination sequence. A changed +policy requires a fresh listing cursor; do not combine pages obtained under +incompatible filters. Native tests must pin behavior with actual cursor semantics. +The fixture tests here use integer pagination, not native opaque cursors. + +## Local relay alternative: what was and was not shown + +[Native startup](https://github.com/openai/codex/blob/1cc7e2361237ce7244430ee1d581c77f95c57ac8/codex-rs/app-server/src/lib.rs) +uses `config.chatgpt_base_url` for remote control. The +[URL protocol](https://github.com/openai/codex/blob/1cc7e2361237ce7244430ee1d581c77f95c57ac8/codex-rs/app-server-transport/src/transport/remote_control/protocol.rs) +accepts loopback URLs. A host-side relay is therefore a concrete design alternative: + +```text +unchanged mobile <-> existing ChatGPT backend <-> local relay <-> native app-server +``` + +Connection establishment starts on the host. Enroll/pair/refresh can be forwarded +rather than reimplemented. However, `chatgpt_base_url` is shared with +[authentication configuration](https://github.com/openai/codex/blob/1cc7e2361237ce7244430ee1d581c77f95c57ac8/codex-rs/core/src/config/auth_keyring.rs) +and other backend consumers. A WebSocket-only implementation is insufficient. +[Enrollment persistence](https://github.com/openai/codex/blob/1cc7e2361237ce7244430ee1d581c77f95c57ac8/codex-rs/app-server-transport/src/transport/remote_control/enroll.rs) +also keys state by URL/account/client, so changing the URL can affect enrollment +selection; whether live re-pairing is required remains unverified. + +The retained relay fixture accepts only literal `127.0.0.1` HTTP upstreams and +synthetic credentials. It is not a production service and cannot be configured +for ChatGPT. It handles regular and single-chunk frames; multi-chunk messages are +passed unchanged. Actual protocol-v3 segmentation, native reconnect/ACK/cursors, +account changes, managed network policy, and full backend compatibility remain +release gates, not claims supported by these tests. See native +[WebSocket handling](https://github.com/openai/codex/blob/1cc7e2361237ce7244430ee1d581c77f95c57ac8/codex-rs/app-server-transport/src/transport/remote_control/websocket.rs). + +No global base-URL mutation, token-pool integration, production listener, +configuration migration, history write, or authentication bypass is proposed here. + +## Upstream and downstream follow-through + +An upstream implementation needs Rust config/schema, origin-scoping, explicit/null/ +related-query, pagination, and managed-policy tests. Keep existing defaults intact. +Document the omitted/default behavior and regenerate affected schema/TS fixtures. + +After an accepted implementation is released, OpenCodex may consider an opt-in +integration with positive capability/version evidence and precise configuration +ownership/restoration. Merely writing an unknown TOML key is not a feature test. +Until then, retain #6007's warnings and keep #5848 open. Do not suppress the warning +because an experimental setting was written or a fixture passed. diff --git a/devlog/_plan/260928_remote_thread_provider_policy/020_verification.md b/devlog/_plan/260928_remote_thread_provider_policy/020_verification.md new file mode 100644 index 00000000000..520f602b369 --- /dev/null +++ b/devlog/_plan/260928_remote_thread_provider_policy/020_verification.md @@ -0,0 +1,65 @@ +# Verification + +Status: local evidence for an RFC. The probes are executable specifications that +run on demand; no CI workflow runs them, and nothing in the OpenCodex build, test, +or runtime path imports them. All inputs, accounts, tokens, thread ids, and +database rows are synthetic. No native Codex process, user home, installed +config, or real service is accessed. + +## How to run + +From the repository root: + +```sh +cd devlog/_plan/260928_remote_thread_provider_policy/probes +python3 -m unittest -v test_native_policy test_probe +``` + +The socket suite needs aiohttp. Install it into a scratch directory, not the +repository or a system site-packages: + +```sh +python3 -m pip install --target "$SCRATCH_DIR" aiohttp +PYTHONPATH="$SCRATCH_DIR" python3 -m unittest -v test_native_policy test_probe test_loopback_bridge +``` + +## Inventory + +63 tests across three suites: + +- 23 tests (`test_native_policy`) specify the proposed native policy: opt-out, + trusted-origin scoping, explicit arrays, null semantics, parent/ancestor + exceptions, immutable policy, exact ids, UUID thread-id validation, and + synthetic paginated fixture reads. +- 29 raw-frame tests (`test_probe`) cover the relay alternative's + ordinary/single-chunk rewrites, byte-identical pass-through, limits, and + unsupported inputs. +- 11 localhost HTTP/WebSocket tests (`test_loopback_bridge`) use a mock host and + mock backend, checking two-way traffic, fixture auth/headers, explicit filters, + enrollment, unrelated HTTP, fixture endpoint restrictions, and both directions + of close-code forwarding. + +The first 52 use the Python standard library only. + +The synthetic database has 5,200 `openai` rows, one `opencodex` row, and one +`other` row. Default filtering yields one; the two-id opt-in yields 5,201; all +providers yields 5,202. Page sizes 1, 37, and 1,000 are exercised. The fixture +dump hash stays unchanged after reads. This demonstrates the specification, not +native SQLite schema compatibility or actual mobile results. + +## Recorded runs + +| Date | Environment | Suites | Result | +| --- | --- | --- | --- | +| 2026-09-28 | author, local | native policy + raw frame | 52 passed | +| 2026-10-13 | Linux, Python 3.12.14, aiohttp 3.14.4 | all three | 63 passed | +| 2026-10-15 | macOS, Python 3.9.6, aiohttp 3.13.5 | all three | 63 passed | + +Each run reflects the probe source at that time; a later edit to a probe needs a +fresh local run. + +## Not executed / not established + +- Native Rust implementation, compilation, config/schema generation, or native tests. +- Actual ChatGPT mobile pairing, full pagination, resume, token renewal, or reconnect. +- Production multi-segment relay support or management/security review of a relay. diff --git a/devlog/_plan/260928_remote_thread_provider_policy/probes/native_policy.py b/devlog/_plan/260928_remote_thread_provider_policy/probes/native_policy.py new file mode 100644 index 00000000000..80859ac8395 --- /dev/null +++ b/devlog/_plan/260928_remote_thread_provider_policy/probes/native_policy.py @@ -0,0 +1,103 @@ +"""Executable specification for a PROPOSED native Codex policy, not an integration. + +No socket, authentication, config, history, or rollout access. The real Rust +implementation must obtain origin from its authenticated server-side connection, +not from JSON, clientInfo.name, or an HTTP header. This enum only models that input. +None as the result means no provider predicate, not no results. +""" +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from uuid import UUID +import re + +# Rust's Uuid::parse_str accepts exactly four textual shapes - hyphenated, simple, +# braced-hyphenated and the urn:uuid prefix. Python's UUID() is looser: it tolerates +# arbitrary hyphen placement inside a 32-hex payload, so wrapper/hyphen variants +# must be rejected by shape before parsing instead of leaning on the constructor. +_UUID_ACCEPTED_SHAPES = re.compile( + r"(?:" + r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" + r"|[0-9a-fA-F]{32}" + r"|\{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\}" + r"|urn:uuid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" + r")\Z" +) + + +class ConnectionOrigin(Enum): + STDIO = "stdio" + WEBSOCKET = "websocket" + IN_PROCESS = "in_process" + REMOTE_CONTROL = "remote_control" + + +def _validate_ids(value: tuple[str, ...], label: str) -> None: + if not isinstance(value, tuple): + raise ValueError(f"{label} must be a tuple") + if any(not isinstance(item, str) or not item.strip() for item in value): + raise ValueError(f"{label} must contain non-empty strings") + if len(set(value)) != len(value): + raise ValueError(f"{label} must not contain duplicate identifiers") + + +def _validate_thread_id(value: str, label: str) -> None: + """Upstream parses ThreadId as a UUID; a non-UUID string must not widen the list.""" + if not isinstance(value, str) or not _UUID_ACCEPTED_SHAPES.match(value): + raise ValueError(f"{label} must be a UUID thread id") + try: + UUID(value) + except (ValueError, AttributeError, TypeError): + raise ValueError(f"{label} must be a UUID thread id") + + +@dataclass(frozen=True) +class RemoteListPolicy: + # None: opt-out; (): explicitly all; non-empty: exactly these provider ids. + providers: tuple[str, ...] | None = None + + def __post_init__(self) -> None: + if self.providers is not None: + _validate_ids(self.providers, "providers") + + +def resolve_provider_filter( + *, + origin: ConnectionOrigin, + default_provider: str, + requested: tuple[str, ...] | None = None, + parent_thread_id: str | None = None, + ancestor_thread_id: str | None = None, + policy: RemoteListPolicy = RemoteListPolicy(), +) -> tuple[str, ...] | None: + """Model precedence after typed request decoding and existing authorization. + + Rust Option> decodes both omission and JSON null as None. They + intentionally have the same semantics in this native-policy proposal. The + separate raw-frame relay probe instead preserves every present JSON key. + Empty requested tuple is an explicit client request for all providers. + The policy applies only to thread/list; callers must not reuse it for resume. + """ + if not isinstance(origin, ConnectionOrigin): + raise ValueError("origin must be supplied by the trusted connection context") + _validate_ids((default_provider,), "default_provider") + for label, tid in ( + ("parent_thread_id", parent_thread_id), + ("ancestor_thread_id", ancestor_thread_id), + ): + if tid is not None: + _validate_thread_id(tid, label) + if parent_thread_id is not None and ancestor_thread_id is not None: + raise ValueError("parent_thread_id and ancestor_thread_id are mutually exclusive") + if requested is not None: + # Preserve every typed client array, including duplicate/empty ids. + # Native Vec accepts them; this proposal must not add rejection. + if not isinstance(requested, tuple) or any(not isinstance(item, str) for item in requested): + raise ValueError("requested must model a typed string array") + return requested or None + if parent_thread_id is not None or ancestor_thread_id is not None: + return None + if origin is ConnectionOrigin.REMOTE_CONTROL and policy.providers is not None: + return policy.providers or None + return (default_provider,) diff --git a/devlog/_plan/260928_remote_thread_provider_policy/probes/remote_list_probe.py b/devlog/_plan/260928_remote_thread_provider_policy/probes/remote_list_probe.py new file mode 100644 index 00000000000..a2622cdf18e --- /dev/null +++ b/devlog/_plan/260928_remote_thread_provider_policy/probes/remote_list_probe.py @@ -0,0 +1,160 @@ +"""Offline research probe; NOT an installed OpenCodex feature or production proxy. + +Based on openai/codex commit 1cc7e2361237ce7244430ee1d581c77f95c57ac8. +Only changes omitted thread/list.modelProviders in explicit opt-in mode. +No network, authentication, filesystem, database, or rollout writes occur here. +Complete multi-segment messages need a separate bounded streaming reassembler; +this probe deliberately passes those messages through unchanged. +""" +from __future__ import annotations + +import base64 +import binascii +import json +from dataclasses import dataclass +from typing import Any + + +@dataclass(frozen=True) +class Policy: + enabled: bool = False + # An empty tuple explicitly requests all providers, matching modelProviders: []. + providers: tuple[str, ...] = ("openai", "opencodex") + max_wire_bytes: int = 150 * 1024 + + def __post_init__(self) -> None: + if not isinstance(self.providers, tuple) or any( + not isinstance(p, str) or not p.strip() for p in self.providers + ): + raise ValueError("providers must be a tuple of non-empty strings") + if len(set(self.providers)) != len(self.providers): + raise ValueError("duplicate provider identifiers") + if self.max_wire_bytes < 64: + raise ValueError("max_wire_bytes must be at least 64") + + +@dataclass(frozen=True) +class Outcome: + text: str + changed: bool + reason: str + + +def _unique_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]: + result: dict[str, Any] = {} + for key, value in pairs: + if key in result: + raise ValueError("duplicate JSON member") + result[key] = value + return result + + +def _reject_constant(value: str) -> None: + raise ValueError("non-standard JSON constant") + + +def decode(text: str | bytes) -> Any: + return json.loads(text, object_pairs_hook=_unique_object, + parse_constant=_reject_constant) + + +def encode(value: Any) -> str: + return json.dumps(value, ensure_ascii=False, separators=(",", ":"), + allow_nan=False) + + +def _patch_message(message: Any, policy: Policy) -> tuple[bool, str]: + if not isinstance(message, dict) or message.get("method") != "thread/list": + return False, "not_thread_list" + request_id = message.get("id") + if isinstance(request_id, bool) or not isinstance(request_id, (int, str)): + return False, "not_request" + if "result" in message or "error" in message: + return False, "ambiguous_request" + params = message.get("params", {}) + if not isinstance(params, dict): + return False, "non_object_params" + # Preserve explicit client choices, including an explicit null. + if "modelProviders" in params: + return False, "explicit_filter_preserved" + # Native related-thread queries deliberately bypass the omitted-provider default. + if params.get("parentThreadId") is not None or params.get("ancestorThreadId") is not None: + return False, "relation_query_preserved" + params["modelProviders"] = list(policy.providers) + message["params"] = params + return True, "omitted_filter_patched" + + +def rewrite_backend_frame(text: str, policy: Policy) -> Outcome: + """Process ONE backend-to-host text frame. No authentication decision is made. + + The caller must already enforce the authorized connection and user opt-in. + Unmodified and unsupported messages are returned byte-for-byte unchanged. + """ + if not isinstance(text, str): + raise TypeError("text frame must be str") + if not policy.enabled: + return Outcome(text, False, "disabled") + try: + if len(text.encode("utf-8")) > policy.max_wire_bytes: + return Outcome(text, False, "input_size_limit") + envelope = decode(text) + if not isinstance(envelope, dict): + return Outcome(text, False, "not_envelope") + if not isinstance(envelope.get("client_id"), str) or not envelope["client_id"]: + return Outcome(text, False, "missing_client_id") + kind = envelope.get("type") + if kind == "client_message": + changed, reason = _patch_message(envelope.get("message"), policy) + elif kind == "client_message_chunk": + if type(envelope.get("segment_count")) is not int or type(envelope.get("segment_id")) is not int: + return Outcome(text, False, "invalid_segment_index") + if envelope["segment_count"] != 1 or envelope["segment_id"] != 0: + return Outcome(text, False, "multi_segment_not_implemented") + size = envelope.get("message_size_bytes") + if type(size) is not int or size < 0 or size > policy.max_wire_bytes: + return Outcome(text, False, "invalid_segment_size") + payload_b64 = envelope.get("message_chunk_base64") + if not isinstance(payload_b64, str): + return Outcome(text, False, "invalid_segment_payload") + payload = base64.b64decode(payload_b64, validate=True) + if len(payload) != size: + return Outcome(text, False, "segment_length_mismatch") + message = decode(payload) + changed, reason = _patch_message(message, policy) + if changed: + encoded_payload = encode(message).encode("utf-8") + envelope["message_size_bytes"] = len(encoded_payload) + envelope["message_chunk_base64"] = base64.b64encode(encoded_payload).decode("ascii") + else: + return Outcome(text, False, "unrelated_envelope") + if not changed: + return Outcome(text, False, reason) + output = encode(envelope) + if len(output.encode("utf-8")) > policy.max_wire_bytes: + return Outcome(text, False, "output_size_limit") + return Outcome(output, True, reason) + except (ValueError, TypeError, UnicodeError, binascii.Error, RecursionError): + return Outcome(text, False, "invalid_frame_preserved") + + +def make_envelope(message: dict[str, Any], *, chunk: bool = False) -> dict[str, Any]: + """Synthetic fixture builder; all identifiers below are test-only.""" + envelope: dict[str, Any] = { + "client_id": "mock-client", "stream_id": "mock-stream", + "seq_id": 7, "cursor": "mock-backend-cursor", + } + if chunk: + payload = encode(message).encode("utf-8") + envelope.update(type="client_message_chunk", segment_id=0, segment_count=1, + message_size_bytes=len(payload), + message_chunk_base64=base64.b64encode(payload).decode("ascii")) + else: + envelope.update(type="client_message", message=message) + return envelope + + +def extract_message(envelope: dict[str, Any]) -> dict[str, Any]: + if envelope["type"] == "client_message": + return envelope["message"] + return decode(base64.b64decode(envelope["message_chunk_base64"], validate=True)) diff --git a/devlog/_plan/260928_remote_thread_provider_policy/probes/test_loopback_bridge.py b/devlog/_plan/260928_remote_thread_provider_policy/probes/test_loopback_bridge.py new file mode 100644 index 00000000000..2438a4334a6 --- /dev/null +++ b/devlog/_plan/260928_remote_thread_provider_policy/probes/test_loopback_bridge.py @@ -0,0 +1,259 @@ +"""Loopback-only mock transport proof. NEVER connects to ChatGPT/OpenAI. + +This is a test fixture, not a production proxy. Native Codex is not executed. +The mock backend validates a hard-coded, non-secret fixture token. +""" +from __future__ import annotations +import asyncio +import contextlib +import unittest +from urllib.parse import urlsplit + +import aiohttp +from aiohttp import web +from remote_list_probe import Policy, decode, encode, make_envelope, extract_message, rewrite_backend_frame + +BASE = "/backend-api" +WS_PATH = BASE + "/wham/remote/control/server" +MOCK_AUTH = "Bearer NOT-A-REAL-TOKEN-MOCK-ONLY" + + +def assert_loopback_only(base: str) -> None: + url = urlsplit(base) + if url.scheme != "http" or url.hostname != "127.0.0.1" or not url.port or url.username or url.password: + raise ValueError("this research fixture permits only HTTP at literal 127.0.0.1") + if url.query or url.fragment or url.path: + raise ValueError("mock base must contain only origin") + + +def selected_headers(headers) -> dict[str, str]: + return {key: value for key, value in headers.items() + if key.lower() in ("authorization", "chatgpt-account-id", "content-type") + or key.lower().startswith("x-codex-")} + + +class LoopbackFixtureRelay: + def __init__(self, mock_backend: str, session: aiohttp.ClientSession, policy: Policy): + assert_loopback_only(mock_backend) + self.backend = mock_backend + self.session = session + self.policy = policy + + async def handle(self, request: web.Request) -> web.StreamResponse: + # Test-only origin/host guard. No remote bind and no arbitrary upstream selection. + if request.headers.get("Origin"): + return web.Response(status=403, text="browser-origin-blocked") + if not request.host.startswith("127.0.0.1:"): + return web.Response(status=403, text="unexpected-host") + if request.path == WS_PATH and request.headers.get("Upgrade", "").lower() == "websocket": + return await self.handle_websocket(request) + target = self.backend + request.raw_path + async with self.session.request(request.method, target, + data=await request.read(), + headers=selected_headers(request.headers), + allow_redirects=False) as response: + body = await response.read() + headers = {key: value for key, value in response.headers.items() + if key.lower() in ("content-type", "x-request-id", "retry-after")} + return web.Response(status=response.status, body=body, headers=headers) + + async def handle_websocket(self, request: web.Request) -> web.StreamResponse: + target = self.backend + request.raw_path + try: + upstream = await self.session.ws_connect(target, + headers=selected_headers(request.headers), autoping=False) + except aiohttp.WSServerHandshakeError as exc: + return web.Response(status=exc.status, text="mock-upstream-rejected") + downstream = web.WebSocketResponse(autoping=False) + await downstream.prepare(request) + + async def pump(source, destination, transform: bool): + async for message in source: + if message.type == aiohttp.WSMsgType.TEXT: + text = rewrite_backend_frame(message.data, self.policy).text if transform else message.data + await destination.send_str(text) + elif message.type == aiohttp.WSMsgType.BINARY: + await destination.send_bytes(message.data) + elif message.type == aiohttp.WSMsgType.PING: + await destination.ping(message.data) + elif message.type == aiohttp.WSMsgType.PONG: + await destination.pong(message.data) + else: + break + # Forward the peer's close so the other side sees the same code + # instead of hanging on a connection that already ended. + close_code = getattr(source, "close_code", None) or 1000 + if not destination.closed: + await destination.close(code=close_code) + + tasks = [asyncio.create_task(pump(upstream, downstream, True)), + asyncio.create_task(pump(downstream, upstream, False))] + try: + done, _ = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) + for task in done: + task.result() + finally: + for task in tasks: + task.cancel() + await asyncio.gather(*tasks, return_exceptions=True) + await upstream.close() + await downstream.close() + return downstream + + +async def start_loopback_app(app: web.Application): + runner = web.AppRunner(app, access_log=None) + await runner.setup() + site = web.TCPSite(runner, "127.0.0.1", 0) + await site.start() + port = runner.addresses[0][1] + return runner, f"http://127.0.0.1:{port}" + + +class LoopbackBridgeTests(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self): + self.frames = [] + self.received = asyncio.Queue() + self.handshake_headers = {} + self.http_received = [] + self.backend_closed = asyncio.Event() + self.backend_close_code = None + self.backend_close_on_connect = None + self.backend_app = web.Application() + self.backend_app.router.add_route("*", "/backend-api/{tail:.*}", self.mock_backend) + self.backend_runner, self.backend_base = await start_loopback_app(self.backend_app) + self.relay_session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=5)) + self.relay = LoopbackFixtureRelay(self.backend_base, self.relay_session, Policy(enabled=True)) + relay_app = web.Application() + relay_app.router.add_route("*", "/backend-api/{tail:.*}", self.relay.handle) + self.relay_runner, self.relay_base = await start_loopback_app(relay_app) + self.host = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=5)) + + async def asyncTearDown(self): + await self.host.close() + await self.relay_runner.cleanup() + await self.relay_session.close() + await self.backend_runner.cleanup() + + async def mock_backend(self, request: web.Request): + if request.headers.get("Authorization") != MOCK_AUTH: + return web.Response(status=401, text="mock-auth-required") + if request.path == WS_PATH and request.headers.get("Upgrade", "").lower() == "websocket": + self.handshake_headers = dict(request.headers) + websocket = web.WebSocketResponse(autoping=False) + await websocket.prepare(request) + for frame in self.frames: + await websocket.send_str(frame) + if self.backend_close_on_connect is not None: + await websocket.close(code=self.backend_close_on_connect) + return websocket + async for message in websocket: + if message.type == aiohttp.WSMsgType.TEXT: + await self.received.put(message.data) + elif message.type == aiohttp.WSMsgType.PING: + await websocket.pong(message.data) + self.backend_close_code = websocket.close_code + self.backend_closed.set() + return websocket + body = await request.read() + self.http_received.append((request.path, body, selected_headers(request.headers))) + if request.path.endswith("/enroll"): + return web.json_response({"server_id": "mock-server", "environment_id": "mock-env", + "remote_control_token": "MOCK-ONLY", "expires_at": "2099-01-01T00:00:00Z"}, + status=201, headers={"x-request-id": "mock-http-request"}) + return web.Response(status=200, body=b"mock-unrelated-backend-endpoint") + + def add_request(self, params=None, *, chunk=False): + request = {"id": 42, "method": "thread/list", "params": params or {"limit": 20}} + self.frames.append(encode(make_envelope(request, chunk=chunk))) + + async def receive_host_frame(self, headers=None): + async with self.host.ws_connect(self.relay_base + WS_PATH, + headers=headers or {"Authorization": MOCK_AUTH}) as host: + message = await asyncio.wait_for(host.receive(), 2) + self.assertEqual(message.type, aiohttp.WSMsgType.TEXT) + return message.data + + async def test_backend_to_host_rewrite_and_host_reply_passthrough(self): + self.add_request() + async with self.host.ws_connect(self.relay_base + WS_PATH, + headers={"Authorization": MOCK_AUTH}) as host: + incoming = await asyncio.wait_for(host.receive(), 2) + envelope = decode(incoming.data) + self.assertEqual(extract_message(envelope)["params"]["modelProviders"], ["openai", "opencodex"]) + response = '{ "type":"server_message", "client_id":"mock-client", "stream_id":"mock-stream", "seq_id":9, "message":{"id":42,"result":{"data":[]}} }' + await host.send_str(response) + self.assertEqual(await asyncio.wait_for(self.received.get(), 2), response) + + async def test_auth_and_protocol_handshake_headers_preserved(self): + self.add_request() + headers = {"Authorization": MOCK_AUTH, "chatgpt-account-id": "mock-account", + "x-codex-protocol-version": "3", "x-codex-server-id": "mock-server", + "x-codex-installation-id": "mock-installation", "x-codex-subscribe-cursor": "mock-resume-cursor"} + await self.receive_host_frame(headers) + actual = {k.lower(): v for k, v in self.handshake_headers.items()} + for name, value in headers.items(): + self.assertEqual(actual[name.lower()], value) + + async def test_invalid_auth_stays_denied(self): + with self.assertRaises(aiohttp.WSServerHandshakeError) as ctx: + await self.host.ws_connect(self.relay_base + WS_PATH, + headers={"Authorization": "Bearer WRONG-MOCK"}) + self.assertEqual(ctx.exception.status, 401) + + async def test_explicit_filter_frame_is_byte_identical(self): + self.add_request({"modelProviders": ["other"], "limit": 20}) + self.assertEqual(await self.receive_host_frame(), self.frames[0]) + + async def test_single_chunk_transport(self): + self.add_request(chunk=True) + incoming = decode(await self.receive_host_frame()) + self.assertEqual(extract_message(incoming)["params"]["modelProviders"], ["openai", "opencodex"]) + self.assertEqual(incoming["seq_id"], 7) + self.assertEqual(incoming["cursor"], "mock-backend-cursor") + + async def test_host_close_code_reaches_backend(self): + async with self.host.ws_connect(self.relay_base + WS_PATH, + headers={"Authorization": MOCK_AUTH}) as host: + await host.close(code=1001) + await asyncio.wait_for(self.backend_closed.wait(), 5) + self.assertEqual(self.backend_close_code, 1001) + + async def test_backend_close_code_reaches_host(self): + self.backend_close_on_connect = 1011 + async with self.host.ws_connect(self.relay_base + WS_PATH, + headers={"Authorization": MOCK_AUTH}) as host: + async for message in host: + if message.type in (aiohttp.WSMsgType.CLOSE, + aiohttp.WSMsgType.CLOSED, + aiohttp.WSMsgType.CLOSING): + break + self.assertEqual(host.close_code, 1011) + + async def test_enrollment_http_forwarding_with_fake_credentials(self): + body = b'{"name":"mock-host","installation_id":"mock-installation"}' + async with self.host.post(self.relay_base + WS_PATH + "/enroll", data=body, + headers={"Authorization": MOCK_AUTH, "chatgpt-account-id": "mock-account"}) as response: + self.assertEqual(response.status, 201) + self.assertEqual(response.headers["x-request-id"], "mock-http-request") + self.assertEqual((await response.json())["server_id"], "mock-server") + self.assertEqual(self.http_received[0][1], body) + + async def test_unrelated_http_backend_endpoint_forwarded(self): + async with self.host.get(self.relay_base + BASE + "/mock/account", headers={"Authorization": MOCK_AUTH}) as response: + self.assertEqual(response.status, 200) + self.assertEqual(await response.read(), b"mock-unrelated-backend-endpoint") + + async def test_cross_origin_browser_request_denied(self): + async with self.host.get(self.relay_base + BASE + "/mock/account", headers={"Authorization": MOCK_AUTH, "Origin": "https://example.invalid"}) as response: + self.assertEqual(response.status, 403) + self.assertFalse(self.http_received) + + async def test_fixture_rejects_real_service_upstream(self): + for base in ("https://chatgpt.com", "http://example.invalid", "http://0.0.0.0:1234"): + with self.subTest(base=base): + with self.assertRaises(ValueError): + LoopbackFixtureRelay(base, self.relay_session, Policy(enabled=True)) + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/devlog/_plan/260928_remote_thread_provider_policy/probes/test_native_policy.py b/devlog/_plan/260928_remote_thread_provider_policy/probes/test_native_policy.py new file mode 100644 index 00000000000..58d07be97f2 --- /dev/null +++ b/devlog/_plan/260928_remote_thread_provider_policy/probes/test_native_policy.py @@ -0,0 +1,198 @@ +"""Offline contract tests. This does NOT run Rust, native Codex, or a mobile app.""" +from __future__ import annotations + +import hashlib +import json +import sqlite3 +import unittest +from dataclasses import FrozenInstanceError +from native_policy import ConnectionOrigin as Origin, RemoteListPolicy, resolve_provider_filter + + +class NativePolicyTests(unittest.TestCase): + def resolve(self, **kwargs): + defaults = {"origin": Origin.REMOTE_CONTROL, "default_provider": "opencodex"} + defaults.update(kwargs) + return resolve_provider_filter(**defaults) + + def test_opt_out_keeps_existing_default_for_every_origin(self): + for origin in Origin: + with self.subTest(origin=origin): + self.assertEqual(self.resolve(origin=origin), ("opencodex",)) + + def test_only_native_remote_origin_uses_opt_in(self): + policy = RemoteListPolicy(("openai", "opencodex")) + for origin in Origin: + with self.subTest(origin=origin): + expected = policy.providers if origin is Origin.REMOTE_CONTROL else ("opencodex",) + self.assertEqual(self.resolve(origin=origin, policy=policy), expected) + + def test_explicit_nonempty_filter_wins_over_every_policy(self): + for policy in (RemoteListPolicy(), RemoteListPolicy(()), RemoteListPolicy(("openai",))): + for origin in Origin: + with self.subTest(policy=policy, origin=origin): + for requested in (("other",), ("other", "other"), ("",), (" other ",)): + self.assertEqual(self.resolve(origin=origin, requested=requested, policy=policy), requested) + + def test_explicit_empty_filter_is_all_for_every_origin(self): + for origin in Origin: + with self.subTest(origin=origin): + self.assertIsNone(self.resolve(origin=origin, requested=(), policy=RemoteListPolicy(("openai",)))) + + def test_opt_in_all_is_not_the_default(self): + self.assertIsNone(self.resolve(policy=RemoteListPolicy(()))) + self.assertEqual(self.resolve(), ("opencodex",)) + + def test_parent_query_keeps_native_no_default_filter(self): + for policy in (RemoteListPolicy(), RemoteListPolicy(("opencodex",))): + self.assertIsNone(self.resolve(parent_thread_id="00000000-0000-4000-8000-0000000000aa", policy=policy)) + + def test_ancestor_query_keeps_native_no_default_filter(self): + for policy in (RemoteListPolicy(), RemoteListPolicy(("opencodex",))): + self.assertIsNone(self.resolve(ancestor_thread_id="00000000-0000-4000-8000-0000000000bb", policy=policy)) + + def test_related_thread_ids_follow_native_validation(self): + # Upstream thread_list_response_inner rejects malformed ids and rejects + # parent+ancestor together; a malformed id must not silently widen the list. + for relation in ( + {"parent_thread_id": ""}, + {"ancestor_thread_id": " "}, + {"parent_thread_id": "fixture-parent"}, + {"ancestor_thread_id": "fixture-ancestor"}, + {"parent_thread_id": "p"}, + ): + with self.subTest(relation=relation), self.assertRaises(ValueError): + self.resolve(**relation) + # Python's UUID() tolerates hyphen shapes Rust Uuid::parse_str rejects: + # shifted hyphens, a bare 32-hex blob inside braces, an uppercased URN, and + # a braced simple string all stay invalid no matter the provider shape. + malformed = ( + "000-00000-0000-4000-8000-0000000000aa", + "{000000000000400080000000000000aa}", + "URN:UUID:00000000-0000-4000-8000-0000000000aa", + "{000000000000-4000-8000-0000000000aa}", + ) + for key in ("parent_thread_id", "ancestor_thread_id"): + for bad in malformed: + for requested in (None, ("openai",)): + with self.subTest(key=key, bad=bad, requested=requested), self.assertRaises(ValueError): + self.resolve(**{key: bad}, requested=requested) + # The four shapes upstream accepts stay valid for both relations. + for key in ("parent_thread_id", "ancestor_thread_id"): + for good in ( + "00000000-0000-4000-8000-0000000000aa", + "000000000000400080000000000000aa", + "{00000000-0000-4000-8000-0000000000aa}", + "urn:uuid:00000000-0000-4000-8000-0000000000aa", + ): + with self.subTest(key=key, good=good): + self.assertIsNone(self.resolve(**{key: good})) + with self.assertRaises(ValueError): + self.resolve( + parent_thread_id="00000000-0000-4000-8000-0000000000aa", + ancestor_thread_id="00000000-0000-4000-8000-0000000000bb", + ) + + def test_explicit_filter_still_wins_for_related_queries(self): + for relation in ( + {"parent_thread_id": "00000000-0000-4000-8000-0000000000aa"}, + {"ancestor_thread_id": "00000000-0000-4000-8000-0000000000bb"}, + ): + self.assertEqual(self.resolve(requested=("other",), policy=RemoteListPolicy(()), **relation), ("other",)) + + def test_null_and_omitted_match_typed_native_option_semantics(self): + for wire in ('{}', '{"modelProviders": null}'): + requested = json.loads(wire).get("modelProviders") + self.assertIsNone(requested) + self.assertEqual(self.resolve(requested=requested, policy=RemoteListPolicy(("openai",))), ("openai",)) + + def test_provider_names_are_not_hardcoded(self): + self.assertEqual(self.resolve(default_provider="custom"), ("custom",)) + self.assertEqual(self.resolve(policy=RemoteListPolicy(("azure-team", "local"))), ("azure-team", "local")) + + def test_provider_ids_are_exact_not_aliases(self): + self.assertEqual(self.resolve(policy=RemoteListPolicy(("OpenAI", " openai "))), ("OpenAI", " openai ")) + + def test_policy_cannot_be_mutated_during_pagination(self): + policy = RemoteListPolicy(("openai",)) + with self.assertRaises(FrozenInstanceError): + policy.providers = () + + def test_rejects_invalid_policy(self): + for value in (["openai"], "openai", ("",), (" ",), (1,), ("openai", "openai")): + with self.subTest(value=value), self.assertRaises(ValueError): + RemoteListPolicy(value) + + def test_rejects_untrusted_origin_string(self): + with self.assertRaises(ValueError): + self.resolve(origin="remote_control") + + def test_rejects_invalid_default_provider(self): + for value in ("", " ", None, 1): + with self.subTest(value=value), self.assertRaises(ValueError): + self.resolve(default_provider=value) + + def test_rejects_invalid_requested_filter(self): + for value in (["other"], "other", (1,), (None,)): + with self.subTest(value=value), self.assertRaises(ValueError): + self.resolve(requested=value) + + +class SyntheticPaginationTests(unittest.TestCase): + """Fixture SQL illustrates the contract, not the native Codex store schema.""" + def setUp(self): + self.db = sqlite3.connect(":memory:") + self.addCleanup(self.db.close) + self.db.execute("CREATE TABLE threads (id INTEGER PRIMARY KEY, provider TEXT NOT NULL)") + self.db.executemany("INSERT INTO threads VALUES (?, ?)", + [(i, "openai") for i in range(1, 5201)] + [(5201, "opencodex"), (5202, "other")]) + self.before = self.digest() + + def digest(self): + return hashlib.sha256("\n".join(self.db.iterdump()).encode()).hexdigest() + + def list_all(self, policy, limit=37, requested=None): + providers = resolve_provider_filter(origin=Origin.REMOTE_CONTROL, + default_provider="opencodex", requested=requested, policy=policy) + rows, last_id = [], 0 + while True: + values = [last_id] + sql = "SELECT id, provider FROM threads WHERE id > ?" + if providers is not None: + sql += " AND provider IN (" + ",".join("?" for _ in providers) + ")" + values.extend(providers) + sql += " ORDER BY id LIMIT ?" + values.append(limit) + page = self.db.execute(sql, values).fetchall() + if not page: + break + rows.extend(page) + last_id = page[-1][0] + self.assertEqual(self.digest(), self.before, "read must not retag fixture rows") + self.assertEqual(len({row[0] for row in rows}), len(rows)) + return rows + + def test_default_remains_one_thread(self): + self.assertEqual(self.list_all(RemoteListPolicy()), [(5201, "opencodex")]) + + def test_opt_in_two_provider_list_returns_5201_rows(self): + rows = self.list_all(RemoteListPolicy(("openai", "opencodex"))) + self.assertEqual(len(rows), 5201) + self.assertNotIn("other", {row[1] for row in rows}) + + def test_explicit_all_returns_5202_rows(self): + self.assertEqual(len(self.list_all(RemoteListPolicy(()))), 5202) + + def test_explicit_client_filter_not_broadened(self): + self.assertEqual(self.list_all(RemoteListPolicy(()), requested=("other",)), [(5202, "other")]) + + def test_unlisted_provider_returns_empty_without_fallback(self): + self.assertEqual(self.list_all(RemoteListPolicy(("not-in-fixture",))), []) + + def test_every_page_uses_same_policy_without_duplicates(self): + policy = RemoteListPolicy(("openai", "opencodex")) + self.assertEqual(self.list_all(policy, limit=1), self.list_all(policy, limit=1000)) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/devlog/_plan/260928_remote_thread_provider_policy/probes/test_probe.py b/devlog/_plan/260928_remote_thread_provider_policy/probes/test_probe.py new file mode 100644 index 00000000000..ffdb0c6e24d --- /dev/null +++ b/devlog/_plan/260928_remote_thread_provider_policy/probes/test_probe.py @@ -0,0 +1,196 @@ +from __future__ import annotations +import base64 +import hashlib +import json +import sqlite3 +import unittest +from copy import deepcopy +from remote_list_probe import Policy, decode, encode, make_envelope, extract_message, rewrite_backend_frame + + +class RewriteTests(unittest.TestCase): + def setUp(self): + self.policy = Policy(enabled=True) + self.request = {"id": 27, "method": "thread/list", "params": {"limit": 20, "cursor": "opaque"}} + + def frame(self, request=None, chunk=False): + return encode(make_envelope(deepcopy(self.request if request is None else request), chunk=chunk)) + + def result(self, request=None, chunk=False, policy=None): + return rewrite_backend_frame(self.frame(request, chunk), policy or self.policy) + + def assert_preserved(self, raw): + outcome = rewrite_backend_frame(raw, self.policy) + self.assertFalse(outcome.changed) + self.assertEqual(raw, outcome.text) + + def test_opt_in_required(self): + raw = self.frame() + self.assertEqual(rewrite_backend_frame(raw, Policy()).text, raw) + self.assertFalse(rewrite_backend_frame(raw, Policy()).changed) + + def test_plain_omitted_filter(self): + out = self.result() + self.assertTrue(out.changed) + self.assertEqual(extract_message(decode(out.text))["params"]["modelProviders"], ["openai", "opencodex"]) + + def test_explicit_all_provider_mode(self): + out = self.result(policy=Policy(enabled=True, providers=())) + self.assertEqual(extract_message(decode(out.text))["params"]["modelProviders"], []) + + def test_preserves_envelope_metadata(self): + before = decode(self.frame()) + after = decode(self.result().text) + for key in ("client_id", "stream_id", "seq_id", "cursor"): + self.assertEqual(before[key], after[key]) + + def test_preserves_all_other_parameters(self): + self.request["params"].update(archived=True, cwd="C:/mock", sourceKinds=["cli"], + sortDirection="asc", searchTerm="mock", isPinned=False) + after = extract_message(decode(self.result().text)) + del after["params"]["modelProviders"] + self.assertEqual(after, self.request) + + def test_explicit_filter_preserved(self): + self.request["params"]["modelProviders"] = ["other"] + self.assert_preserved(self.frame()) + + def test_explicit_empty_filter_preserved(self): + self.request["params"]["modelProviders"] = [] + self.assert_preserved(self.frame()) + + def test_explicit_null_filter_preserved(self): + self.request["params"]["modelProviders"] = None + self.assert_preserved(self.frame()) + + def test_missing_params_created(self): + del self.request["params"] + self.assertTrue(self.result().changed) + + def test_non_object_params_preserved(self): + for value in (None, [], "not-an-object", 1): + with self.subTest(value=value): + self.request["params"] = value + self.assert_preserved(self.frame()) + + def test_resume_and_other_methods_unchanged(self): + for method in ("thread/resume", "thread/start", "turn/start", "turn/interrupt", "initialize", "thread/search"): + with self.subTest(method=method): + self.request["method"] = method + self.assert_preserved(self.frame()) + + def test_notification_without_id_unchanged(self): + del self.request["id"] + self.assert_preserved(self.frame()) + + def test_request_id_preserved_exactly(self): + for request_id in ("request-string", 9007199254740993, 0, -1): + with self.subTest(request_id=request_id): + self.request["id"] = request_id + self.assertEqual(extract_message(decode(self.result().text))["id"], request_id) + + def test_response_like_object_unchanged(self): + self.request["result"] = {"data": []} + self.assert_preserved(self.frame()) + + def test_relation_query_bypass_preserved(self): + for key in ("parentThreadId", "ancestorThreadId"): + with self.subTest(key=key): + self.request["params"] = {key: "00000000-0000-4000-8000-0000000000aa"} + self.assert_preserved(self.frame()) + + def test_null_relation_does_not_block(self): + self.request["params"]["parentThreadId"] = None + self.assertTrue(self.result().changed) + + def test_ping_ack_close_unchanged(self): + for kind in ("ping", "ack", "client_closed", "unknown_future_event"): + with self.subTest(kind=kind): + self.assert_preserved(encode({"type": kind, "client_id": "mock-client", "seq_id": 3})) + + def test_invalid_json_preserved(self): + for raw in ("{broken", "[]", "null", '{"type":"client_message","client_id":"x","message":NaN}'): + with self.subTest(raw=raw): + self.assert_preserved(raw) + + def test_duplicate_members_preserved(self): + raw = self.frame().replace('"method":"thread/list"', '"method":"thread/list","method":"thread/resume"') + self.assert_preserved(raw) + + def test_unknown_envelope_fields_preserved(self): + envelope = decode(self.frame()) + envelope["future_field"] = {"tag": [1, 2, 3]} + out = rewrite_backend_frame(encode(envelope), self.policy) + self.assertEqual(decode(out.text)["future_field"], envelope["future_field"]) + + def test_single_chunk_transformed_with_correct_byte_size(self): + self.request["params"]["searchTerm"] = "한글🙂" + out = self.result(chunk=True) + self.assertTrue(out.changed) + after = decode(out.text) + payload = base64.b64decode(after["message_chunk_base64"], validate=True) + self.assertEqual(len(payload), after["message_size_bytes"]) + self.assertEqual(decode(payload)["params"]["modelProviders"], ["openai", "opencodex"]) + self.assertEqual(after["seq_id"], 7) + + def test_single_chunk_existing_filter_unchanged(self): + self.request["params"]["modelProviders"] = ["other"] + self.assert_preserved(self.frame(chunk=True)) + + def test_multi_segment_deliberately_not_implemented(self): + envelope = decode(self.frame(chunk=True)) + envelope["segment_count"] = 2 + out = rewrite_backend_frame(encode(envelope), self.policy) + self.assertEqual(out.reason, "multi_segment_not_implemented") + self.assertFalse(out.changed) + + def test_bad_chunk_length_unchanged(self): + envelope = decode(self.frame(chunk=True)) + envelope["message_size_bytes"] += 1 + self.assert_preserved(encode(envelope)) + + def test_bad_chunk_base64_unchanged(self): + envelope = decode(self.frame(chunk=True)) + envelope["message_chunk_base64"] = "not@@base64" + self.assert_preserved(encode(envelope)) + + def test_oversized_input_unchanged(self): + raw = self.frame() + out = rewrite_backend_frame(raw, Policy(enabled=True, max_wire_bytes=64)) + self.assertFalse(out.changed) + self.assertEqual(out.text, raw) + + def test_oversized_output_unchanged(self): + raw = self.frame() + out = rewrite_backend_frame(raw, Policy(enabled=True, max_wire_bytes=len(raw.encode()))) + self.assertFalse(out.changed) + self.assertEqual(out.reason, "output_size_limit") + + def test_invalid_policy_rejected(self): + for providers in (("",), ("a", "a"), (1,), ["a"]): + with self.subTest(providers=providers): + with self.assertRaises(ValueError): + Policy(enabled=True, providers=providers) + + def test_synthetic_sqlite_visibility_and_no_mutation(self): + # Synthetic fixture, NOT the user's database and NOT native Codex. + db = sqlite3.connect(":memory:") + try: + db.execute("CREATE TABLE threads (id INTEGER, model_provider TEXT)") + db.executemany("INSERT INTO threads VALUES (?, ?)", [(i, "openai") for i in range(5200)]) + db.execute("INSERT INTO threads VALUES (5200, 'opencodex')") + db.execute("INSERT INTO threads VALUES (5201, 'unrelated')") + before = hashlib.sha256("\n".join(db.iterdump()).encode()).hexdigest() + self.assertEqual(db.execute("SELECT count(*) FROM threads WHERE model_provider = 'opencodex'").fetchone()[0], 1) + providers = extract_message(decode(self.result().text))["params"]["modelProviders"] + placeholders = ",".join("?" for _ in providers) + scoped = db.execute(f"SELECT count(*) FROM threads WHERE model_provider IN ({placeholders})", providers).fetchone()[0] + self.assertEqual(scoped, 5201) + self.assertEqual(db.execute("SELECT count(*) FROM threads").fetchone()[0], 5202) + after = hashlib.sha256("\n".join(db.iterdump()).encode()).hexdigest() + self.assertEqual(before, after) + finally: + db.close() + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/devlog/_plan/261004_codex_remote_messaging/000_scope_and_boundary.md b/devlog/_plan/261004_codex_remote_messaging/000_scope_and_boundary.md new file mode 100644 index 00000000000..10a9697d3dc --- /dev/null +++ b/devlog/_plan/261004_codex_remote_messaging/000_scope_and_boundary.md @@ -0,0 +1,63 @@ +# 000 — remote Codex messaging: second contribution + +Status: implemented locally; bounded contribution qualification recorded. No publication, +deployment or maintainer acceptance of the remote boundary is implied. + +## Baseline and direction + +The contribution branch is rebuilt on upstream `dev` +`0dd7451976e40f1e80e023e3c604e38d1966a68e`, containing integrated local messaging +[PR #6544](https://github.com/lidge-jun/opencodex/pull/6544). +It does not carry the superseded local PR commits. +The broader deployed experiment remains separate. + +[Proposal #6478](https://github.com/lidge-jun/opencodex/issues/6478) and +[scoped lifecycle feedback](https://github.com/lidge-jun/opencodex/issues/6478#issuecomment-5975718099) +require a bounded optional owner, aggregate cross-peer limits, idempotent owned +cleanup and no replay. Foreground ownership is a revisable proposal, not accepted +remote architecture. Enrollment/authentication/SSH require independent review. + +## Included scope + +- CLI-only Codex-to-Codex delivery to exact loaded recipients on individually + enrolled SSH peers, selected by exact alias or machine UUID. +- Explicit enable, fingerprint probe/confirmation, enrollment, list, removal, + status and foreground `ocx message serve`. +- Stable per-configuration-home machine identity and protected directional + capabilities, with versioned SSH-stdio enrollment and retained transactions + for unknown enrollment outcomes. +- IPv4-loopback gateway: mutual capability-derived proofs on the actual data + connection before attaching to the existing native Unix daemon. No capability + in URLs, argv, upgrade headers, ordinary receipts or logs. +- Initialization, loaded discovery, projected metadata and direct experimental + `thread/queue/add`; no generic RPC, lifecycle, turns or approval/config writes. +- An owned -L/-R pair and bounded return lease support replies without a second + SSH login. Live owner proof, not persisted ports, establishes route readiness. +- Initiating enrollment grants outbound delivery and correlated replies only. + Broader local inbound delivery requires explicit per-peer consent; return transport + and capability possession do not substitute for that consent. +- Discovery, final recipient revalidation and queue submission share one + authenticated connection; exact receipt correlation, queued/not_sent/unknown, + and no automatic resend. +- Shared owner-wide capacities, cancellation, shutdown, focused regressions, + isolated real-SSH and stock-native qualification, docs and ownership records. + +## Deliberate exclusions + +No mesh/coordinator, dashboard/API, caches, isolation policy, Claude, managed +skills, prompt/delegation policy, idle triggers or session creation/resume. +No proxy startup hook, daemon start/restart/install, remote installation or +automatic tunnel recovery. Unrelated local extensions are not carried. + +The gateway is a protected OpenCodex wrapper protocol, not a native WebSocket +endpoint: raw `codex queue --remote` does not perform its mutual proof handshake. +Native daemon auth/configuration is unchanged. SSH provides network encryption; +capability proof is endpoint identity/admission, not transport encryption or a +same-UID isolation boundary. + +## Publication boundary + +Prepare a local PR description and exact-head evidence. Do not push or open the +next PR without user direction. Reference #6478 rather than closing the broader +proposal. Linux qualification does not establish macOS interoperability; required +current-head hosted CI and maintainer/security acceptance remain separate gates. diff --git a/devlog/_plan/261004_codex_remote_messaging/010_extraction_and_ownership.md b/devlog/_plan/261004_codex_remote_messaging/010_extraction_and_ownership.md new file mode 100644 index 00000000000..96429a4bb7f --- /dev/null +++ b/devlog/_plan/261004_codex_remote_messaging/010_extraction_and_ownership.md @@ -0,0 +1,44 @@ +# 010 — extraction and source ownership + +## Source identities + +Destination: upstream `dev` `0dd7451976e40f1e80e023e3c604e38d1966a68e`. +Source reference: local `feat/claude-messaging`, including completed direct-RPC +hardening at `a665128c3`. This is responsibility-based extraction and narrow +rewriting, not a whole-commit cherry-pick or a copy of the deployed subsystem. + +## Extraction decisions + +| Responsibility | Contribution | +| --- | --- | +| Enrollment | Strict JSON state and SSH stdio control; explicit enable on both nodes, directional capabilities, recoverable transaction and local-first revocation. No mesh or auto-enable. | +| SSH | Reuse unchanged Remote Link argv/fingerprint/environment policy; messaging owns separate known_hosts and -L/-R children. | +| Bridge | Codex-only mutual-proof gateway, small RPC whitelist, projected metadata, bounded aggregate admission. No cross-harness/runtime feature imports. | +| Runtime | New explicit foreground owner with shared cancellation and accounting; no supervisor, proxy startup activation or automatic retries. | +| Send | Extend integrated same-connection direct queue RPC through a narrow authenticated socket attachment seam. Preserve the Unix-only local factory. | +| Envelope | Optional machine metadata and exact remote reply host; descriptive sender identity does not grant authority. | +| Fixtures | Standalone isolated native/SSH fixtures, not the broader experiment's fixtures. | + +## Current ownership and paths + +- `structure/remote-messaging.md` owns remote modules and CLI lifecycle/contracts. +- `structure/local-messaging.md` preserves local-only default and queue semantics. +- `structure/cli-management.md` accounts for explicit remote command dispatch. +- `structure/decisions/ADR-6480-remote-messaging-owner.md` records provisional + foreground ownership. The manifest and generated structure index register it. +- `src/link/ssh-argv.ts`, `fingerprint.ts` and SSH environment policy remain + unchanged and shared; messaging does not enroll Remote Link or reuse provider keys. +- `src/messaging/remote-process.ts` adds cancellation/join behavior the existing + link runner does not expose. It does not silently change Remote Link behavior. +- `src/messaging/rpc.ts` accepts a caller-authenticated connected socket only + through `attach`. Its local `connect` still refuses network URLs. +- `src/cli/message-command.ts` imports remote runtime only after pure syntax + validation. Local commands without a host do not read remote state. +- English public CLI reference, registry, capabilities, help and generated + operating surface describe implemented commands, not proposed HTTP routes. +- New tests are registered in both layout manifests. The contained test-temp + directory now explicitly uses 0700 to honor private socket/store fixtures. + +No source dependency/process graph or exhaustive downstream audit is claimed. +Callbacks, subprocesses, source-oracle guards and native contracts require explicit +regression coverage rather than import-graph coverage alone. diff --git a/devlog/_plan/261004_codex_remote_messaging/020_contract_and_test_matrix.md b/devlog/_plan/261004_codex_remote_messaging/020_contract_and_test_matrix.md new file mode 100644 index 00000000000..525a067c72e --- /dev/null +++ b/devlog/_plan/261004_codex_remote_messaging/020_contract_and_test_matrix.md @@ -0,0 +1,79 @@ +# 020 — contracts and qualification ledger + +Status: implemented contracts with focused evidence; final qualification is tracked +in 030 and the local PR verification packet, not inferred from historical help. + +## Native contract + +The gateway uses the integrated experimental `thread/queue/add` RPC, with one +connection for discovery, revalidation and submission. Validated native IDs and +text echo establish queued; a correlated rejection establishes not_sent. +Post-write cancellation, malformed/lost receipts and route loss remain unknown. +Nothing retries a possibly submitted message. + +Stock `codex-cli 0.160.1` was exercised in a separate credential-free home with +a synthetic local provider. Executable SHA-256: +`f34a4d2301892ae96c90097786bfe5dc269f187b6f69faf42a7b357b8c081e35`. +The real daemon accepted the gateway request, completed a synthetic turn and sent +a return response through the authenticated route. This is Linux evidence, +not macOS or production-node qualification. + +Separately, a disposable loopback OpenSSH server with generated keys proved +fingerprint confirmation, private enrollment, separate -L/-R routes, request and +response and complete owned cleanup. Native endpoints in that SSH fixture are +strict offline Unix fixtures. These two receipts are complementary, not a claim +that the combined real-SSH/real-native matrix ran. + +## Fixed aggregate contracts + +One foreground owner covers at most four peers, 16 admitted connections, 32 active +requests, ten helpers (eight persistent tunnel children plus two transient control +or inspection children) and 2 MiB accounted input/output. Connections admit at +most four pending requests, frames at most 1 MiB, state at most 128 KiB and helper +stdout/stderr at most 64 KiB each. There is no waiting queue. Full capacity refuses +before allocation, and release is idempotent. + +Setup uses one 30-second budget across peers and stages. Authentication is bounded +to five seconds, route refresh to ten seconds, leases to 25 seconds. Connections +last at most 30 seconds. Owned cleanup has a three-second ceiling with TERM then +KILL; concurrent shutdown joins the same flight. Escaped process groups are not +claimed to be terminated; retained pipes are canceled. Logical helper operations +settle after bounded cleanup; unresolved physical children retain their reservation. +Withdrawn published routes remain part of the owner's final cleanup join. + +## Regression mapping + +| File in tests/codex-integration | Evidence covered | +| --- | --- | +| messaging-remote-contract.test.ts | Direction/nonce/identity proofs; exact RPC admission; metadata projection; no native attachment before auth; replacement listener gets no client proof/body; upgrade and capacity refusal; revocation. | +| messaging-remote-store.test.ts | Absent reads stay absent, strict bounded schema, private ownership/mode/symlink/file guards, locking, secret omission. | +| messaging-remote-enrollment.test.ts | Explicit receiver enable, immutable transaction, fingerprint mismatch, lost-reply reconciliation, generation-bound journal ownership, concurrent completion, removal and journal-failure ordering, full registry refusal. | +| messaging-remote-send.test.ts | Same connection, machine-aware replies, return-only receiver, strict receipts, unload, source admission revalidation after awaits, lost/malformed receipts, no replay. | +| messaging-remote-lifecycle.test.ts | Inert imports and pure parser, owned cancellation, shared cleanup/output limits; published and unpublished tunnel cleanup joins both helpers, including failure. | +| messaging-remote-process.test.ts | Bounded logical settlement for unresolved exit and failed stdin, retained physical reservations, persistent/transient caps and injected Darwin listener inspection. | +| messaging-remote-interop.test.ts | Opt-in generated-key loopback SSH enrollment, duplex delivery and zero owned reservations after cleanup. | +| messaging-remote-native.test.ts | Opt-in explicit stock binary, real queue/turn contract in isolated home, synthetic provider and return delivery. | + +Existing local messaging and shared SSH policy regressions are part of integrated +validation. Import-graph selection alone cannot cover source-oracle, subprocess or +golden-file guards; run those explicitly. + +## Remaining gates + +Full-suite qualification remains incomplete: the existing runner stopped its +parallel lane after 900 seconds, exit 124; serial lanes were not reached. The +local PR packet records the resource exception, observed failures, focused +coverage and commands instead of claiming a passing full suite. No ratchet, +timeout or required check was relaxed. + +A later bounded `test:changed` attempt selected 979 of 2,222 files and exceeded its +explicit 180-second runner ceiling, exit 124, without a final count receipt. It is +also incomplete, not passing; final focused regressions cover subsequent changes. + +Required exact-head hosted CI once publication is authorized and macOS +interoperability remain outstanding. Local checks and independent source review +are recorded separately from maintainer acceptance and operational qualification. +No receipt from an unrun or skipped tier is described as passing. + +Unreleased security assessment material belongs in ignored scratch or managed +scan artifacts, never in this public planning ledger. diff --git a/devlog/_plan/261004_codex_remote_messaging/030_implementation_sequence.md b/devlog/_plan/261004_codex_remote_messaging/030_implementation_sequence.md new file mode 100644 index 00000000000..759b67c4ad1 --- /dev/null +++ b/devlog/_plan/261004_codex_remote_messaging/030_implementation_sequence.md @@ -0,0 +1,56 @@ +# 030 — implementation and handover + +Status: the agreed remote slice is implemented locally on +`feat/codex-remote-contribution`; bounded qualification and source review are +recorded in the local PR packet. Publication is user-authorized; deployment and +maintainer acceptance remain separate. + +## Completed implementation sequence + +1. Reconstructed the contribution on current upstream dev containing merged + #6544, preserving the old prep worktree and deployed experiment. +2. Pinned narrow gateway/native contracts, mutual connection proofs, strict + receipt semantics and numeric aggregate capacities. +3. Added explicit private state/enrollment and recoverable private-stdio + transactions; no implicit remote enable or lifecycle changes. +4. Reused shared SSH policy with messaging-owned -L/-R children and live return + leases. Added cancellation/join behavior without changing Remote Link. +5. Added an explicit foreground owner and dynamically selected remote CLI path. + No listener, helper, timer or state creation occurs from imports/default startup. +6. Added one-connection remote sends and machine-aware return replies, preserving + local-only Unix trust and queued/not_sent/unknown/no-replay behavior. +7. Added registered isolated regressions, generated surfaces, public CLI docs, + source ownership and a foreground decision record. +8. Qualified disposable loopback SSH and stock Codex 0.160.1 separately. Joined + unpublished tunnel cleanup has dedicated regressions and a narrow independent + review. The rebase carried the implementation unchanged, verified by range-diff. +9. Recorded the full-suite 900-second timeout as incomplete evidence with a + proportionate resource exception. Hosted CI, macOS qualification and maintainer + acceptance remain outstanding; no unrun tier is reported as passing. +10. Refined transaction ownership, source admission and published cleanup joins; + qualified bounded helper settlement and persistent/transient helper slots. + Final focused validation passed 283 tests with six skips and no failures; + broader changed-test selection remains incomplete at its bounded ceiling. +11. Rebased onto upstream `dev` `0dd7451976e40f1e80e023e3c604e38d1966a68e` + before publication. All five contribution patches carried unchanged, verified + by range-diff. Rebased qualification is recorded separately in the PR packet. + +## Maintainer adjustment points + +Foreground lifetime is provisional. The individual-peer schema is intentionally +independent of any future mesh. The protected gateway protocol is not a native +WebSocket listener and does not claim raw queue CLI compatibility. Linux evidence +must not be generalized to macOS. If the owner requests smaller contributions, +split along enrollment/transport/delivery while retaining the tested contracts. + +## Safe handover + +Keep source and exact-head receipts together. Preserve the deployed local branch; +do not install, restart, enroll production hosts or migrate skills from this PR +preparation. Do not push or raise a PR without the user's go. Reference the broader +proposal rather than auto-closing it. + +The local PR draft must distinguish implementation, tests actually run, remaining +platform/CI/maintainer gates and security review. Any unrun suite or skipped opt-in +fixture stays explicitly unqualified. Never include secrets, personal host paths, +internal product names or unpublished security notes in public commits. diff --git a/devlog/_plan/261021_fallback_off/000_design.md b/devlog/_plan/261021_fallback_off/000_design.md new file mode 100644 index 00000000000..6357bc48de1 --- /dev/null +++ b/devlog/_plan/261021_fallback_off/000_design.md @@ -0,0 +1,145 @@ +# OCX-11 fallback off — design + +> Written 2026-10-21, revised 2026-10-22 after the supervisor decisions (§9) · implemented in PR #7482. +> Consumer: GOAT hub design, goat-si/goat PR #684 (ADR-0021 §1.6, design doc §1.6 and DH3). +> The present-tense contract is [structure/fallback-policy.md](../../../structure/fallback-policy.md). + +## 1. Goal + +Give the operator one clear way to say "do not fall back; fail with a clear error instead", for two families: + +- **Hub down.** A Remote Link Child or Hub client whose Home is unreachable never silently serves from + its own local providers, and the operator can see and set the per-device local fallback permission. +- **Fallback chains.** Model and provider fallback (combos, subagent fallback, the main-account + hard-lock fallback, routing profiles) can be refused per scope: global, per provider, per model or + route, and per device. + +Defaults stay exactly as they are today (§7). + +## 2. Terms adopted from GOAT + +ADR-0021 §1.6 and its design doc define the hub-down policy. This note reuses these terms verbatim: + +- "a Child never silently serves its local providers when the Home is unreachable, and model, account and + provider fallback chains can be turned off per scope" (design §1.6, describing this package). The + account part is settled differently: see §9 C3. +- "**local fallback** opt-in per device" (DH3: "pause plus decision item; local fallback opt-in per device"). + GOAT's switch is `hub.devices[alias].localFallback`; the OpenCodex key is + `fallback.devices..localFallback`, so the leaf reads the same on both sides. +- "goal work on that device **pauses** (health condition `HubUnreachable`), people's own threads see + OpenCodex's 503 as today". OpenCodex keeps that 503 and adds `code: "home_unreachable"` (§6). +- "GOAT reads that state; it never changes OpenCodex fallback settings", shown as "**on, off, unknown** + when the Home predates OCX-11". The Home advertises `fallback-policy/1` in `/readyz` `features`; + GOAT reads its absence as unknown. +- "**GOAT fallback**" (ADR-0018 chains) is GOAT's word for moving GOAT work to another provider instance. + OpenCodex never uses that phrase; its own hops are "model fallback", "provider fallback" and "local + fallback". + +## 3. Inventory + +A fallback is a hop that serves a request from a **different model** or a **different provider** than +the one chosen or requested. + +| # | Fallback | Kind | Where | Governed | +|---|---|---|---|---| +| F1 | Combo failover | provider or model | `src/server/responses/core-combo.ts` `executeComboResponses`; decisions in `src/combos/failover.ts`, picker in `src/combos/resolve.ts` | yes: reactive hop at the dispatch loop, proactive for a cooling first `failover` target | +| F2 | Subagent model fallback | model or provider | `src/codex/subagent-model-fallback.ts` `applySubagentModelFallback` | yes, proactive | +| F3 | Main-account hard-lock fallback | provider or model | `src/server/responses/main-hard-lock-routing.ts` `applyMainHardLockFallbackOverride` | yes, proactive | +| F4 | Routing-profile fallback | provider or model | `src/server/responses/policy-fallback.ts` `handleResponsesWithPolicyFallback` | yes, reactive | +| F5 | Claude Code classifier fallbacks | — | `src/claude/inbound-model-options.ts` | not a hop: the first configured entry always wins, with no availability check | +| F6–F9 | Codex pool account switch (including pool → signed-in main), Anthropic OAuth, generic OAuth and API-key pool rotation | — | `src/codex/routing/selection.ts`, `src/oauth/anthropic-routing.ts`, `src/oauth/generic-account-failover.ts`, `src/providers/key-failover.ts` | not a fallback: intra-pool rotation (§9 C3) | +| F10 | Remote Link Child, Home unreachable | local | `src/client/link-relay.ts` | no local path exists; 503 gains `home_unreachable` | +| F11 | Hub client, Home unreachable | local | Codex on a Hub client talks to the Hub URL directly; the local listener carries only the dashboard relay (`src/client/hub-relay.ts`, 502) | no local path exists; 502 gains `home_unreachable` | + +Neither client kind can serve from local providers: both run the relay-only client runtime +(`src/client/runtime.ts`), `ocx start` refuses an invalid client state instead of starting standalone +(`src/cli/index.ts`), and the standalone recycle runs only when the link itself ends, never on an outage. +`tests/clients/client-home-unreachable.test.ts` pins this with a load-time import-graph check. + +Out of scope because the target does not change: same-target retries (`src/lib/upstream-retry.ts`), +the Antigravity direct lane's bridge fallback (`src/protocols/settings.ts`), transport fallbacks. + +### Fallback versus intra-pool rotation + +A **fallback** changes *what* answers: another model or another provider, with different capabilities, +pricing, privacy terms or quality. An operator can reasonably refuse that. + +**Intra-pool rotation** changes only *which credential* answers for the same provider and model: the +next account of an OAuth pool or the next key of an API-key pool when one is rate-limited, or the Codex +pool's move away from an account at its usage threshold. The request, model and provider are unchanged. +The maintainer rule recorded in `src/types/config.ts` says rotating away from an account upstream has +just rate-limited "only ever runs after a refusal, so stranding it while a second logged-in account sits +idle is a defect rather than a configuration choice". OCX-11 keeps that rule: rotation is resilience +inside one pool and these controls do not govern it. An operator who wants one account only configures +one account, or pauses the others, with the existing pool controls. + +## 4. Configuration + +```jsonc +"fallback": { + "model": true, // absent = true + "provider": true, + "providers": { "anthropic": { "model": false } }, + "routes": { "combo/coding": { "provider": false } }, + "devices": { "lnk_0123456789abcdef": { "localFallback": false, "model": false } } +} +``` + +A hop's kind is decided by what changes: provider differs → `provider`; same provider, different model → +`model`. A malformed leaf reads as absent and diagnostics name the section. Device keys are a link id +or a Hub data key id; the resolver maps a request's admission key id to its link through the link store. + +CLI: `ocx fallback status|off|on|clear [--kind model|provider|all] [--provider|--route|--device]` and +`ocx fallback local on|off --device `. Management: `GET|PUT /api/fallback`. + +## 5. Resolver + +`src/routing/fallback-policy.ts`: `resolveFallbackPolicy`, `fallbackHopKind`, `guardFallbackHop` (records +and refuses) and a 20-entry event ring. Nothing else reads `config.fallback` on the request path. +`src/routing/fallback-state.ts` builds the `ocx.fallback/1` view and applies the scoped write. + +## 6. Error contract + +A refused hop answers with the status of the failure that triggered it (with its `Retry-After`), or 429 +for proactive quota/cooldown/hard-lock refusals and 503 otherwise, and: + +```json +{ "error": { "type": "fallback_refused", "code": "fallback_refused", "message": "… To allow it: ocx fallback on --kind provider --route combo/coding", + "fallback": { "kind": "provider", "scope": "route", "scopeKey": "combo/coding", "from": "a/m", "to": "b/n", "trigger": "rate_limited" } } } +``` + +plus `x-opencodex-fallback: refused`. A taken hop is recorded in status; no header is added to the +answering response, because the combo and policy responses are built deep in child dispatch. + +Hub down keeps status and retry semantics: `{ "error": "link tunnel unavailable", "code": "home_unreachable", "message": "…" }` +on a Child (503) and `{ "error": "hub relay unavailable", "code": "home_unreachable", … }` on a Hub +client's dashboard relay (502). + +## 7. Defaults and migration + +No default changes and no migration: absent `fallback` keeps every chain as it was, and an absent +`localFallback` reads false, which is what a Child already does. + +## 8. Visibility + +`ocx status --json` `fallback` (live, the Home's over the link on a Child, or the local policy), `GET +/api/fallback` (also over the link relay, aliases omitted), `/readyz` `fallback-policy/1`, and the +dashboard Fallback panel on the Dashboard overview and on Remote Link. Events carry time, site, route, kind, from, to, +trigger, outcome, scope and device id; never bodies, keys or account identities. + +## 9. Decisions (supervisor, 2026-10-22) + +- **C1 precedence: accepted.** Route > provider > global, most specific first; a device set to off always refuses. +- **C2 `localFallback: true`: accepted as permission only.** OpenCodex keeps answering 503 when the Home is + unreachable; GOAT reads the flag and moves the work itself (ADR-0021 §1.6, "pause + decision item"). +- **C3 account rotation: overridden.** Account-pool 429 rotation stays not switchable; the maintainer rule + in `src/types/config.ts` stands. Rotation is intra-pool resilience, not fallback, so the `account` kind + is out of OCX-11 (§3, "Fallback versus intra-pool rotation"). + +## 10. Tests + +`tests/routing/fallback-policy.test.ts` (81-case precedence matrix, degrade, device identity, contract, +privacy), `tests/responses/responses-combo-fallback-off.test.ts`, `tests/routing/routing-policy-fallback-off.test.ts`, +`tests/routing/subagent-model-fallback-off.test.ts`, `tests/codex-integration/main-account-hard-lock-fallback-off.test.ts`, +`tests/server/fallback-routes.test.ts`, `tests/clients/client-home-unreachable.test.ts`, `tests/cli/cli-fallback.test.ts`, +`tests/server/readyz-features.test.ts` and `gui/tests/fallback-panel.test.tsx`. diff --git a/devlog/_plan/261021_link_apply_parity/000_plan.md b/devlog/_plan/261021_link_apply_parity/000_plan.md new file mode 100644 index 00000000000..ea02ac32019 --- /dev/null +++ b/devlog/_plan/261021_link_apply_parity/000_plan.md @@ -0,0 +1,220 @@ +# Remote Link parity on apply (OCX-6, #6957) + +Status: open. Branch `feat/link-apply-parity`, PR target `dev` (merges after 2.90.0). + +## Problem + +The hub design (goat-si/goat ADR-0021, devlog 261021_goatclaw_hub §1.4) checks two things on a +Remote Link Child that OpenCodex cannot answer today: + +- **P5 runtime freshness**: the Child runtime must have started after the last apply of the Home's + settings. Nothing records when the Home's settings were applied, and `ocx sync` on a linked Child + re-applies them without restarting the runtime. `ocx restart` cannot restart a linked Child at + all: the client listener's `/healthz` answers no attestation challenge and advertises no + `restartCapability`, so `requestBoundSystemRestart` refuses it (`restart_attestation_failed`). + Step 2 of the hub repair ladder (`ocx restart` on the device) therefore cannot work. +- **P6 Codex settings**: there is no stable digest of the applied settings to compare between + Home and Child. + +## Contract + +- `codexSettingsDigest(settings)`: `sha256:<64 hex>` of the canonical JSON (recursively sorted keys, + no whitespace) of the `HomeCodexSettings` object. Home name and version are excluded, so a Home + and its Child agree. Never the content. +- Child record `/link/home-codex-settings.json` gains `appliedAt`. A re-apply of the + same digest for the same link keeps the earlier `appliedAt` (idempotent); a new digest or a + first apply stamps now. Older records without it read `appliedAt = fetchedAt`. +- The client runtime publishes `startedAt` (ISO, its process start) on `/healthz`. +- `ocx status --json` gains top-level `codexSettings`: + `{ source: "published" | "home" | "local", digest, appliedAt, runtimeStartedAt }`. + Home/standalone: `published` with the digest of what it serves Children. Link Child: `home` with + the record's digest and `appliedAt`, plus the live client runtime's `startedAt`. Hub client or a + Child with no record: `local`, nulls. +- Home `GET /api/link/status` (and `ocx link status --json`) gains per link + `parity: null | { appliedAt, runtimeStartedAt, codexSettingsDigest, reportedAt }`, from the + Child's report. The CLI validator accepts the key absent (older runtime) and normalizes to null. + +## Restart (P5) + +Reuse the existing bound restart (`ocx restart` -> attested `/healthz` -> capability-signed +`POST /api/system/restart`). The client listener gains: + +- `/healthz`: `restartCapability: "v1"`, `startedAt`, and the attestation proof for a challenge, + keyed by the same secret the runtime already writes to `runtime-port.json`. +- `POST /api/system/restart`: admitted only with the HMAC restart capability bound to this pid and + port (no GUI session, no admin token). 202 with the standalone body shape; idempotent while one + restart is in flight (`alreadyDraining: true`). + +Client restart executor (`src/client/runtime-restart.ts`), same supervisor rules as the standalone +restart and the client recycle: + +1. Desktop-supervised: exit 75, the app starts the replacement. +2. `OCX_SERVICE=1` with a viable service: exit 1, the failure-only supervisor relaunches. +3. Unsupervised: stop the tunnel supervisor, stop the listener gracefully (bounded by the 60 s + drain), spawn `ocx start` with `replacementStartEnvironment(true)` and `waitForHealth: true` + (`spawnReplacementStart`). Success: exit 0. Failure: rebind the listener on the same port, + restart the supervisor, keep serving, and log the failure code. The CLI sees the same pid at its + deadline and reports the restart as failed (existing fail-closed path); the old runtime stays up. + +`ocx sync` on a linked Child: after a successful apply, if a client runtime is live and its +`startedAt` precedes the record's `appliedAt`, run the same `handleProxyRestart` as `ocx restart` +(no start when nothing is running). Unchanged digest + fresh runtime = no restart (idempotent). A +failed restart exits non-zero with a message naming the kept runtime; re-running `ocx sync` or +`ocx restart` retries. + +Enrollment (`POST /api/link/apply`) already runs `connect --link` (which records the settings) +then `ocx restart` once, so it gains `appliedAt` with no flow change. + +## Child -> Home report + +`POST /v1/link-management/child-parity` on the Home's hub-link ingress only, after link-key +admission. Body exactly `{ linkId, appliedAt, runtimeStartedAt, codexSettingsDigest }` (<= 1 KiB, +strict ISO timestamps or null, digest pattern or null, linkId must belong to the admitting key). +Stored in memory per link id (lost on Home restart until the next report), display only, never +used for admission or routing. 204 on accept, 400 on a malformed body, 404 for a key/link mismatch. + +The client runtime (link mode) sends it through the connection-bound relay +(`fetchBoundLinkRelay`, same as the settings read): first after start with backoff +(2 s, 5 s, 15 s, 30 s, 60 s), then every 5 minutes; a 404 from an older Home stops it. Unref'd +timers, stopped on shutdown/restart. + +## Files + +- src/link/codex-settings.ts: digest. +- src/link/child-parity.ts (new): report parse + Home in-memory store. +- src/link/management-routes.ts: report path in the route table. +- src/server/index/link-management.ts: report handler (needs the admitting key id). +- src/server/index/serve-options.ts: pass the admission key id. +- src/link/status-projection.ts, src/server/management/link-routes.ts: `parity` per link. +- src/cli/link.ts: validator accepts `parity`. +- src/client/link-home.ts: `appliedAt`, report send. +- src/client/connect.ts: enrollment write goes through the appliedAt helper. +- src/client/machine-listener.ts: healthz attestation/capability/startedAt, restart route. +- src/client/runtime.ts, src/client/runtime-restart.ts (new), src/client/link-parity-reporter.ts (new). +- src/cli/dispatch.ts, src/cli/link-apply-restart.ts (new): sync -> restart. +- src/cli/status.ts: `codexSettings` block. +- structure/remote-link.md, docs-site CLI reference (status, link, sync, restart). + +## Tests (isolated homes only) + +- digest: key order / whitespace independence, change detection, Home projection == Child parse. +- record: first apply stamps, same digest keeps appliedAt, new digest restamps, legacy record. +- client listener: healthz proof + capability; restart route refuses without/with wrong capability, + accepts a valid one once, idempotent second accept. +- restart executor: unsupervised success exits 0; replacement failure rebinds and keeps serving + (old runtime kept, failure reported); desktop -> 75; service -> 1. +- sync: stale runtime -> restart called; fresh runtime -> not called; restart failure -> exit 1. +- report: Home route accepts a valid report, rejects malformed / foreign link; status shows it. +- JSON shape: `ocx status --json` codexSettings (Home and Child) and `ocx link status --json` parity. + +## Verification + +typecheck, focused files, test:changed, privacy:scan, structure:check, full suite under the lock. + + +## Audit round 1 (gpt-6.1-sol, FAIL) and the revision + +1. **Not-ready replacement counted as success.** Folded. `spawnReplacementStart` gains + `notReady: "handoff" | "terminate"` (default `handoff`, standalone unchanged). The client restart + passes `terminate`: a replacement still unready at the deadline is killed and the call rejects + with `not_ready`, so rollback starts only once no replacement survives. Test: a replacement that + stays alive without answering is killed and the old runtime serves again. +2. **Rollback cannot restart the stopped tunnel supervisor; shutdown captured locals.** Folded. + `startClientRuntime` keeps its resources in module state (`activeServer`, `activeSupervisor`) + and the signal handlers read that state. Rollback is one transaction in `runtime.ts`: bind a + fresh listener (port wait capped at 10 s, inside the CLI's observation budget), create a fresh + supervisor with the same key source, republish pid + runtime record with the same attestation + secret, and clear the restart latch. +3. **Supervised handoff exits before validating the replacement.** Rebutted, kept as a stated + residual. Under the desktop app (exit 75) or a viable service (exit 1) the supervisor owns the + process identity and the port; the old process cannot hand the port back, and both supervisors + retry the relaunch (desktop on 75, launchd KeepAlive on unsuccessful exit, systemd + `Restart=on-failure`). This is exactly the standalone `ocx restart` semantics today. Refusing + instead would leave P5 unrepairable on service-installed Children, which is the common fleet + shape. A non-viable service falls through to the unsupervised path with rollback, as the + standalone restart does. Documented in structure/remote-link.md. +4. **Receipt best-effort and independently committed.** Partly folded. The record write now + returns the committed receipt or throws; `ocx sync` reports a failed receipt write and exits + non-zero, and enrollment's `connect --link` fails the same way instead of swallowing it. The write + runs under `withClientLifecycleSync` after `assertClientConnectionUnchanged`. Holding L across + the injection is not possible: the documented lock order is N -> L -> C and injection takes N + itself. Residual: two concurrent `ocx sync` runs on one Child that read two different Home + documents can leave a receipt naming the earlier one; the next sync corrects it, and P6 then + reports the mismatch rather than hiding it. + +Notes folded: the restart route requires the capability principal explicitly (GUI session, admin +token and link key are refused, with tests); it is outside `linkRouteAllowed`. The report POST is +handled before the passive-read dispatch without widening the GET/HEAD table, bound to the stored +link id and its admitted `apiKeyId`, read with a 1 KiB streaming cap and a 5 s deadline; reports for +links no longer in the store are pruned on every write and never projected. `ocx sync` passes a +start callback that never starts a proxy and rechecks connection ownership before restarting; a +live runtime without `startedAt` (older build) counts as stale. Wire names stay camelCase +(`appliedAt`, `runtimeStartedAt`) like every other `ocx ... --json` field; the docs map them to the +brief's `applied_at` / `runtime_started_at`. + + +Sync restart callback: when discovery first reads absent, start nothing (`skipped`); when an accepted restart left no runtime (`recoveringLiveRestart`), delegate to the ordinary recovery start (`handleRestartStartWhenStopped(true)`) so a vanished Child is brought back. + +## Audit round 2 (FAIL) and the revision + +1. **Supervised handoff.** Folded by refusal. Under the desktop app or a service supervisor the + client runtime answers the bound restart with `409 restart_supervised` and keeps serving; the + CLI reports the restart as not performed and names the supervisor's own restart + (`ocx service restart` / the desktop app). No supervised exit path exists in the client restart, + so no code path can leave a supervised Child down. A non-viable service (`OCX_SERVICE=1` without + viable assets) is treated as unsupervised and uses the rollback path. Consequence: P5 on a + supervised Child is repaired by the supervisor's restart, not by `ocx sync`; `ocx sync` says so. + Reported upstream as the open design question (supervisor-coordinated handoff with old-runtime + fallback would be a separate change in the desktop app and service assets). +2. **Rollback vs CLI recovery.** Folded with one budget. The client restart owns a single deadline + of 90 s from acceptance: listener drain at most 20 s, replacement readiness until 70 s, then + termination (SIGTERM, wait for the exit event up to 3 s, SIGKILL, wait up to 2 s; the exit event is + the confirmation), rebind with a 10 s port wait, publication. The CLI's replacement observation + runs to about 135 s, so the old pid is back before recovery can become eligible, and the CLI + then reads the same pid and fails closed. `spawnReplacementStart` takes the shared deadline via + `io.deadlineAt` and `notReady: "terminate"` returns only after the child's exit is observed. + Rollback keeps the original `startedAt`. +3. **Concurrent-sync receipt.** Folded by serialization. A link apply (Child `ocx sync` and the + link branch of `ocx connect`) holds a dedicated apply lock, outermost (before N), from the Home + read through injection to the receipt write. It reuses the lifecycle lock primitive + (`withClientLifecycle` with its own `lockPath` under `/link/`), `busy_timeout = 0`, so + a second concurrent apply fails fast with "another apply is in progress" and changes nothing. +4. **Enrollment receipt failure.** Folded as a post-commit warning. `connect --link` keeps the + committed connection; a failed receipt write is printed as a warning (`Codex settings applied; + apply record not written; run ocx sync`) and `ocx status` then reports `source: "local"` until a + sync records it, which P6 shows as missing rather than as parity. + + +## Audit round 3 (FAIL, two blockers) and the revision + +1. **Late acceptance can still overlap rollback with CLI recovery.** Folded with a pre-POST guard. + `CLIENT_RESTART_BUDGET_MS = 90_000` lives in `src/lib/system-restart-contract.ts`. The client + `/healthz` advertises `role: "client"`; `requestBoundSystemRestart` refuses to POST to a client + target (`restart_deadline_expired`, no request sent, runtime untouched) unless + `deadlineAt - RESTART_REOBSERVE_RESERVE_MS - now >= CLIENT_RESTART_BUDGET_MS + 5_000`. With the + 145 s deadline and 10 s reserve, a client restart is only accepted within the first 40 s, so the + rollback always finishes before replacement observation ends. The client enforces its own 90 s + from acceptance. Test: delayed acceptance is refused before POST; an accepted restart with a + failed replacement is back on the old pid inside the observation window. +2. **409 loses the guidance.** Folded. The client answers + `409 { error: { code: "restart_supervised", supervisor: "service" | "desktop" } }`. + `requestBoundSystemRestart` reads that bounded body and returns the validated code + `restart_supervised_service` / `restart_supervised_desktop`; `src/cli/restart-failure.ts` prints + "Restart not performed; the running Child runtime was kept. Restart it with `ocx service restart` + (or from the OpenCodex desktop app)." Any other 409 keeps the existing generic report. + + +## Contract change from the manager (GOAT H2, goat-si/goat PR #696) + +Supersedes the field names and formats above where they differ: + +- **OCX-6a**: every link row of the Home's `ocx link status --json` carries `lastApplyAt`, epoch + **milliseconds** of the Child's last successful settings apply (with `runtimeStartedAt`, + `appliedCodexSettingsDigest` and `reportedAt` beside it). The Child report uses epoch ms too. +- **OCX-6b**: `ocx status --json` on any device carries top-level `appliedCodexSettingsDigest` = + lowercase hex SHA-256 (no prefix) of the compact UTF-8 JSON + `{"version":1,"settings":{"fastMode":…,"multiAgentMode":…,"webSearchSidecarEnabled":…,"subagentDefaults":null|{"model":…,"reasoningEffort":…}}}` + in exactly that key order, from the values actually applied. Serialized by name, not by a sorting + canonicalizer. Golden digests for a null and a set `subagentDefaults` are pinned in + `tests/clients/link-codex-settings-digest.test.ts`. The Child's `lastApplyAt` and + `runtimeStartedAt` sit beside it as epoch ms. diff --git a/devlog/_plan/261021_readyz_features/000_plan.md b/devlog/_plan/261021_readyz_features/000_plan.md new file mode 100644 index 00000000000..745de5e35aa --- /dev/null +++ b/devlog/_plan/261021_readyz_features/000_plan.md @@ -0,0 +1,50 @@ +# OCX-3: `/readyz` advertises peer features + +Producer side of the GOAT hub design (goat-si/goat#684, ADR-0021, `261021_goatclaw_hub/000_design.md` §5 and +brief OCX-3). Peers decide compatibility from `features` instead of parsing versions. + +## Current state + +- `GET /readyz` (`src/server/index/serve-options.ts`) spreads `readyProtocolMetadata(config, req)` from + `src/remote/protocol.ts` into its body. `RemoteReadyMetadata.features?: string[]` already exists and + `parseRemoteReadyMetadata` validates it (at most 64 unique non-empty strings, each at most 80 chars, no control + characters), but no producer fills it. +- Relay auth version: `LINK_RELAY_AUTH_VERSION = "2"` in `src/link/relay-auth.ts`. +- Management routes are declared in `MANAGEMENT_ROUTES` (`src/server/management/route-registry.ts`), pure data + reconciled against dispatch source by `tests/server/management-route-registry.test.ts`. +- The Remote Link management relay read table is `LINK_MANAGEMENT_READ_PATHS` (`src/link/management-routes.ts`). +- No `x-opencodex-served-by` header is emitted yet (OCX-2). + +## Change + +1. New `src/remote/features.ts`: one ordered registry `PEER_FEATURES`, each entry an id plus a predicate over a + `PeerFeatureSources` snapshot read from the owning modules. `servedPeerFeatures(sources?)` returns the ids + whose predicate holds, in registry order. + - `quota-pool/1`: `MANAGEMENT_ROUTES` declares `GET /api/quota-pool` (OCX-1 turns it on by declaring the route, + which the registry reconciliation test already forces). + - `link-management-relay`: `LINK_MANAGEMENT_READ_PATHS` is non-empty. + - `served-by-header`: `SERVED_BY_HEADER_EMITTED`, the emitter's descriptor in a new + `src/remote/served-by.ts` (`false` until OCX-2, which flips it in the change that emits the header). + A live test on an isolated server keeps it honest: `x-opencodex-served-by` is present on the + `/readyz`, `/healthz` and unknown-`/v1/*` responses exactly when `/readyz` advertises the feature. + - `relay-auth/` (currently `relay-auth/2`): always. +2. `readyProtocolMetadata` adds `features: servedPeerFeatures()` (computed once; the sources are build constants). +3. Tests in a new `tests/server/readyz-features.test.ts` (registered in layout + expected fixture): registry + presence/absence follows each source, ids are unique and survive `parseRemoteReadyMetadata`, the live + `/readyz` body carries exactly the registry output and nothing account-shaped. Update the exact-shape + assertions line-neutrally in `tests/server/proxy-liveness.test.ts` (two `readyProtocolMetadata` `toEqual`s) + and `tests/server/server-live.test.ts:1442` (sorted key allowlist gains `features`; allowlist kept). +4. Docs: `structure/runtime.md` (owns `src/remote/`), `structure/remote-link.md` pointer for relay-auth, + `docs-site` lifecycle `ocx ready` field list. + +## Out of scope / open question + +The Child machine listener (`src/client/machine-listener.ts`) answers its own `/readyz` with a different fixed +shape (`protocolVersion: 1`, no `features`). Whether a Child should advertise features, and which (it serves no +`/api/quota-pool` of its own), is a product decision reported back, not chosen here. + +## Verification + +typecheck; focused files (new test, proxy-liveness, management-route-registry, core-link-boundary, +core-lab-boundary, client-machine-listener, server-live); `bun run test:changed`; `bun run structure:check`; +`bun run privacy:scan`; full `bun run test` under `/usr/bin/lockf -t 3600 /tmp/ocx-heavy-test.lock`. diff --git a/devlog/_plan/261021_service_status_json/000_plan.md b/devlog/_plan/261021_service_status_json/000_plan.md new file mode 100644 index 00000000000..5ce225f7b75 --- /dev/null +++ b/devlog/_plan/261021_service_status_json/000_plan.md @@ -0,0 +1,30 @@ +# 000 — OCX-10: passive service status JSON and install-method verdict + +Producer side of the GOAT hub inventory (goat-si/goat#684, design §5 detection steps 3–4). +GOAT reads an adopted OpenCodex install over SSH and must not change it. + +## Scope + +1. `ocx service status --json`: one stable document built from the same reads the text + status already performs — `diagnoseService()` (launchd / systemd user unit / Task Scheduler + / WinSW) and, when a registration exists, the loopback serving probe. It is answered before + `platformOps()` resolves, so no install, start, stop, repair or migration code is reachable. + The text output is unchanged. +2. `ocx status --json` gains an additive `install` field mapped from the existing + `detectInstallOwnership()` (`src/update/install-detection.mjs`), with the evidence basis and + no new paths. + +## Shape decisions + +- `running` is the service manager's verdict (the value `startup.serviceRunning` already + carries); `serving` says separately whether a proxy answers on the installed port. +- `backend` names the manager holding a registration: `launchd`, `systemd`, `schtasks`, + `winsw`, or `none` when nothing is registered or the platform is unsupported. +- `install.method` adds `standalone` to the requested set because the existing detector already + distinguishes the compiled desktop sidecar; `unknown` is reserved for a detector failure. + +## Verification + +Platform shape snapshots with mocked managers, a passive guard over every spawn entry point on +darwin/linux/win32, an install-method matrix, typecheck, test:changed, skill surface, structure, +privacy and the full suite. diff --git a/devlog/_plan/261023_sweep/010_wave_plan.md b/devlog/_plan/261023_sweep/010_wave_plan.md new file mode 100644 index 00000000000..970c0f1b827 --- /dev/null +++ b/devlog/_plan/261023_sweep/010_wave_plan.md @@ -0,0 +1,127 @@ +# 2.92.0 sweep — wave plan v1 + +> Written 2026-10-23 after the 2.91.0 release (main `83ec4f75a4`, npm/tag/GitHub release/updater verified). +> Baseline: `dev` at `299e8b68b2` (after #7511, #7510 and #7458). Planner only: no lane here merges; the +> sweep manager owns every merge. UI/UX changes are excluded from the sweep and get issues only. + +## 1. Safety gates (unchanged) + +- Combined-tree simulation on current `dev` before each merge. +- Rule C: a Windows-sensitive change (path filter plus markers `spawnSync`, `SIGTERM`, `SIGKILL`, + `process.kill`, PowerShell, `.cmd`) needs a green `lane=all` run on the exact head. +- No unresolved blocking review threads; dev-red stop rule. +- Auth, credentials, dependency, GitHub Actions or release tooling changes carry a security summary in the + PR description; this applies to every lane below. + +## 2. Host placement + +- Remote hosts (mac-studio, macmini-ts) are mid GOAT update, and remote launches from the coordinating + MacBook are refused until the manager deploys the dev build there. **Start remote lanes only after that + deploy.** +- Until then, run **at most two light lanes on the coordinating Mac** (its GOAT pressure is elevated): + W1-B and W1-C first, then W1-E when one finishes. +- After the deploy: heavy and Windows-sensitive lanes on mac-studio (16 cores), light lanes on + macmini-ts (10 cores). mini (win32) and lidge-ts have no opencodex checkout. +- Model: `anthropic/claude-opus-5-5` for auth and Windows-sensitive lanes; + `anthropic2/claude-opus-5-5` for plain carries and test-only lanes. + +## 3. Wave 1 (no file overlap between lanes) + +| Lane | Scope | Size | Windows | Host | Branch | +|---|---|---|---|---|---| +| B | #7505 honor upstream no-retry verdict | S | – | this Mac | `sweep/w1-retry-noretry` | +| C | #7392 finish with `length` on Responses incomplete length; register test in both layout maps | S | – | this Mac | `sweep/w1-chat-length` | +| E | #7497 + #7498 SSE carry performance | S | – | this Mac, after B or C | `sweep/w1-sse-carry` | +| A | #7521 (#7517) → #7519 (#7512) → #7391 (#7390), sequential; #7519 and #7391 share `src/clients/config-export/qoder.ts`; #7391 must refuse an unowned legacy collision (review P2) | M | – | mac-studio | `sweep/w1-qoder` | +| D | #7457 (#7403); #7404 finite client-connect deadline; loopback-listener `freePort`→bind race (macOS EADDRINUSE); tests only | M | – | mac-studio | `sweep/w1-flaky-tests` | +| F | PowerShell effective-account SID lookup timeout at `src/codex/user-identity.ts:254` during service install | S | yes | mac-studio | `sweep/w1-win-sid-timeout` | +| G | Upstream catalog refresh (section 6) | M | – | mac-studio | `sweep/w1-upstream-oct` | + +#7521 touches `structure/providers-and-adapters.md` and `docs-site/.../guides/providers.md`; #7471, #7444 and +#7445 touch the same files, so they wait for wave 2. + +## 4. Wave 2 (after wave 1 merges) + +| Lane | Scope | Notes | +|---|---|---| +| H | #7471 command-code credit refusal rotation | blocker withdrawn by reviewer | +| I | #7444 (#7440) Business Premium capacity weight | S | +| J | #7445 deprecate mimo-free → #7439 (#7438) Alibaba Token Plan | sequential: `entries-extended.ts`, `derive.ts`; endpoint change needs primary-source review | +| K | #7463 (#7456) fail unmet required tool completions | conflicting; `openai-chat.ts` after lane E | +| L | #7375 message phase inference | review P2 on terminal snapshot phases | +| M | #7450 + #7499 | share `src/codex/auth-api/account-list.ts` | +| N | #7459 Hermes conversation id | unblocked by #7511; can run beside wave 1 | +| O | #7425 `ocx sync` overwrites user edits in `config.toml` | no PR; implement | +| P | #7442 (#7441) Antigravity family quota to JEV | open CHANGES_REQUESTED blocker; `core-combo.ts` | +| Q | Anthropic API version `2026-10-21` (sampling parameters removed) | `src/adapters/anthropic.ts` | +| – | #7523 replay state isolation by caller | after P (`core-combo.ts`) | +| R | #7516 Qoder CN image input, allowed only for qoderclicn >= 1.1.70 (decision §9) | after lane A: shares `src/adapters/qoder/adapter.ts` with #7521; version probe must fail closed | +| S | #7500 route an Antigravity model only to accounts whose discovered roster lists it; clear error when none is eligible (decision §9) | `src/oauth/generic-account-failover.ts`, `src/providers/antigravity-account-roster.ts`; `anthropic/` | +| T | #7525 opt-in JEV per-session pin, narrower than #6665 (decision §9) | after P: `src/combos/jev-dispatch.ts`, `src/cli/combo.ts`; default off, reuse JEV dispatch/decision owners | +| U | #7366 document how `spawn_agent` fills slots when fewer than five models are featured (decision §9) | docs only (`guides/sub-agent-surface.md`); the dashboard hint is UI and stays on #7366 for the UI track | + +## 5. Wave 3 + +W3: contributor PR review queue, managed separately. + +## 6. Upstream provider changes (lane G / Q input) + +- Anthropic Claude Haiku 5.5 (`claude-haiku-5-5`, released 2026-10-07): 1M context, 128k output, + adaptive thinking and `effort`; sampling parameters must be omitted. +- Anthropic API version `2026-10-21`: breaking removal of sampling parameters from the Messages API. +- Gemini: `gemini-omni-flash-preview` shut down 2026-10-22 (replacement `gemini-omni-1.1-flash`); + `deep-research-pro-preview-12-2025` on 2026-10-23; Vertex retired `gemini-2.5-pro` on 2026-10-20, + with no Developer API date yet, so change Vertex presets only. +- Codex CLI 0.163.0 stable (2026-10-15), 0.164.0-alpha.9 prerelease: app-server and catalog smoke. +- xAI, DeepSeek, Kimi and Qwen were not checked; lane G verifies them against official docs. + +## 7. Lane brief template + +Remote lanes cannot be steered after launch (`goat_thread_send` to a remote thread is refused by the +permission ceiling), so a remote brief must be self-contained: every decision pre-made, a stop rule for +ambiguity (decide, record the decision in the PR description, continue), and a final report of PR URL, head +SHA, verification commands with totals, and anything `NOT RUN`. + +> `$codexclaw:cxc-loop` (HOTL, scoped) + `$codexclaw:cxc-dev`. Lane : . Base: latest +> `origin/dev`, branch . Carrying a contributor PR keeps a `Co-authored-by` trailer. Resolve every +> CHANGES_REQUESTED, CodeRabbit and Codex thread, recording reasons for rejected findings. Register new tests +> in both `layout.json` (or its shard) and a `test-layout-expected*.json` shard; never grow a file at its +> size cap. Verify with focused tests, `bun run test:changed`, `typecheck`, `privacy:scan` and +> `structure:check`. Windows-sensitive changes need exact-head `lane=all` green. Auth, credentials, +> dependency, GitHub Actions or release tooling changes carry a security summary. Open a templated PR against `dev`, do not merge, and report +> the head SHA and check state to the manager. File issues for UI changes instead of making them. + +## 8. Close proposals + +Done by the manager: #7448 (fixed by #7449), #7436 (duplicate of #7433), #7437 (duplicate of #7434), +#7533 (superseded by the opt-in #7534). + +## 9. Needs a product decision + +Approved by the supervisor as recommended on 2026-10-23; a later user decision overrides. + +| Item | Decision | +|---|---| +| #7533 vs #7534 Flex tier | keep the opt-in variant (#7534) | +| #7516 Qoder CN image input | allow, gated on qoderclicn >= 1.1.70 | +| #7500 Antigravity paid vs trial Pro | route by discovered model roster; clear error when no account is eligible | +| #7525 JEV per-session pin | opt-in, narrower than #6665 | +| #7366 `spawn_agent` slot fill | document and hint first; policy change later | + +## 10. Deferred and excluded + +- Waiting for reporter data: #7443, #7382. +- Upstream tracking only: #7524 (desktop capability gate; no repository change between 2.86.0 and 2.90.0). +- UI/UX, excluded: #4649, #7529, #7318, #7275, #7292, #7347, #7526, #7368, #7406. +- Large drafts parked for this sweep: #7531, #7485, #7344, #7308, #7270, #7267, #7311, #7428, #7376, + #7234, #7325, #7374, #7370. +- Already in flight: #7527, #7528. + +## 11. State at plan revision (2026-10-23) + +- Merged: #7511 `8d6e124dd8`, #7510 `943f63ffc1`, #7458 `299e8b68b2`; #7520 and #7509 on exact-head `lane=all`. +- Running on the coordinating Mac: W1-B, W1-C. +- W1-A was started on mac-studio, then interrupted to honor the remote-after-deploy rule: branch + `sweep/w1-qoder` holds the #7521 and #7519 cherry-picks, unpushed, stopped mid-conflict on #7391. + Resume in the same worktree after the deploy. + diff --git a/devlog/_plan/261025_command_code_plan_denial/000_plan.md b/devlog/_plan/261025_command_code_plan_denial/000_plan.md new file mode 100644 index 00000000000..d66592cab6a --- /dev/null +++ b/devlog/_plan/261025_command_code_plan_denial/000_plan.md @@ -0,0 +1,120 @@ +# 261025 Command Code MODEL_NOT_IN_PLAN refusal (#7684) + +Command Code answers a model the account's plan excludes with HTTP 403 +`{"success":false,"error":{"code":"FORBIDDEN","status":403,"message":"MODEL_NOT_IN_PLAN: "}}`. +OpenCodex forwards that 403, and Codex treats any 403 as a retryable `UnexpectedStatus`: a scratch +probe against Codex CLI 0.163.0 counted 6 sends (`Reconnecting... 1/5` to `5/5`) for the 403 and +exactly 1 send for an HTTP 400 (`.tmp/m5/codex-retry-probe.ts`, not committed). Inside a combo the +same 403 cools the whole Command Code provider row (`comboFailureCooldownScope` returns +`provider` for 401/402/403), so every other Command Code model in that combo is skipped. This unit +makes the refusal a single, non-retryable, model-scoped answer. + +## Loop spec + +- Archetype: satisfy-spec, one work-phase (wp1). Trigger: lane M5 brief for issue #7684. +- Goal: one upstream send per turn for a plan-excluded model, a clear client error naming the + model and the plan, combos hop to the next target and cool only the refused target. +- Non-goals: catalog/discovery removal of plan-excluded models (unverifiable without an + authenticated account), sibling-account rotation on a plan refusal, a per-account denial cache + (generic OAuth has none; `src/codex/observed-model-denials.ts` is Codex-pool only and none is + added), the Command Code API-key `openai-chat` preset, the shared error formatter allowlist, + docs-site (guides/providers.md is owned by lane M4), the known failure at + `tests/claude-integration/claude-desktop-remote-hub.test.ts:249`. +- Verifier: `bun test tests/providers/command-code-plan-denial.test.ts` red before / green after; + credit refusal suites stay green; typecheck, test:changed, privacy:scan, structure:check exit 0; + Codex probe through the real proxy route counts 1 send. +- Stop: PR open on dev with independent review PASS at the exact head and CI state reported. +- Memory artifact: this file plus the goalplan under `.codexclaw/goalplans/`. +- Terminal outcomes: DONE as above; BLOCKED if push/PR/review surfaces fail repeatedly; UNSAFE if + the fix needs credential handling changes; NEEDS_HUMAN if review demands excluded scope. +- Escalation: a reviewer asking for catalog removal or account rotation goes to the origin. +- Resource bounds: none stated; host limits apply. + +## Diff-level plan + +1. `src/adapters/command-code.ts`: generalize the credit normalizer into + `normalizeCommandCodeRefusal(response, oauth, modelId, signal)`. Status 400 keeps the existing + OAuth-only credit rewrite byte-for-byte. Status 403, for every auth mode, reads at most 8 KiB + (fatal UTF-8, display-safe) and matches only `success === false`, `error.code === "FORBIDDEN"` + and a message starting with `MODEL_NOT_IN_PLAN` followed by `:` or the end. It returns HTTP 400 with + `{"error":{"type":"invalid_request_error","code":"model_not_in_plan","message":...}}`, headers + `x-opencodex-upstream-status: 403` and `x-opencodex-refusal: model-not-in-plan` (both diagnostic). + `commandCodePlanRefusalMessage` is fixed copy naming the canonical model; the upstream detail is + never quoted. The adapter marks the Response it builds in memory + (`markVerifiedModelNotInPlanResponse`); that object identity is the only provenance. Any other 403, a malformed or + oversized body, or malformed UTF-8 is returned untouched. The reasoning-effort retry send uses the + same normalizer. No account, key or provider cooldown is recorded; nothing is logged. +2. `src/combos/failover.ts`: `comboFailureDecision` hops on `model_not_in_plan` only when the caller + passes `verifiedPlanRefusal` (combo loop from `consumeComboFailure`, policy fallback from the + envelope mark); a bare, passthrough, streamed or run-turn code is read like any unknown code. + `comboFailureCooldownScope` then falls through to `target` for the 400, so only the refused + provider/model target cools. +3. `tests/providers/command-code-plan-denial.test.ts` (registered in + `scripts/test-layout/layout-adapters.json` and on an existing line of + `tests/fixtures/test-layout-expected.json`): OAuth two-account dispatch (one send, 400, active + account unchanged, the same account then serves another model), key row (one send, 400), an + unrelated FORBIDDEN 403 stays 403, combo `[plan-refused model, sibling model]` on the same + provider completes on the sibling, and the failover predicates for the code. +4. `structure/providers-and-adapters.md`: extend the existing command-code row in place (net zero + lines; the doc is at 600/600). + +## Risks + +- A 400 is a status change for this one refusal; clients that branched on 403 for it now see + `invalid_request_error`. The upstream status is kept in `x-opencodex-upstream-status`. +- The client body keeps the dispatch wrapper (`Provider error 400: {...}`); the formatter + keeps `model_not_in_plan` as the top-level code so clients and policy fallback can read it. The + wire code is not provenance; only the boundary that verified the refusal marks the envelope. + +## Audit r1 amendment (GOAT task m5-plan-audit-r1, codex/gpt-6.1-sol high: GO-WITH-FIXES) + +1. Accepted (P2 policy fallback): `policy-fallback.ts` hops on the final envelope's `error.code`, + which `adapter-dispatch.ts` never forwards. The constant `MODEL_NOT_IN_PLAN_CODE` lives in + `src/lib/errors.ts`; `formatErrorResponse` keeps it when a caller passes it (same rule as + `undeclared_tool_call`). `adapter-dispatch.ts` passes it only when `isModelNotInPlanRefusal` + accepts the response: a 400 with the canonical body code whose Response object the adapter + marked in memory. The header is diagnostic only and is never read as provenance. +2. Accepted (P2 misleading detail): the message is fixed wording built from the request's + canonical model id (accepted only if it matches a conservative id pattern). Upstream detail is + never quoted, so effort-repair, opaque-recovery and subagent quota classifiers cannot fire on it. + The adapter also skips its effort-repair branch for a normalized plan refusal. +3. Accepted (P1 logging): no upstream text reaches the client body or the combo warning. A test + puts synthetic account/key canaries plus effort and quota phrases in the upstream detail and + asserts their absence from the client body and captured console output. +4. Accepted (P2 matrix): adapter-level negatives for malformed JSON, invalid UTF-8, oversized body, + wrong `success`/`code`, a `MODEL_NOT_IN_PLAN_OTHER` prefix lookalike, abort propagation, + key/forward/OAuth modes, normalization after a genuine effort-repair send; dispatch tests on the + Responses, Chat and Messages inbound paths; combo hop plus a second request that skips only the + cooled target; a policy-route hop. +5. Noted: attribution moves from `credential-rejected` to `payload-rejected` by the status table; + documented in the PR, not pinned by a test that would restate the table. + +## Review rounds (PR #7696) + +Independent review via GOAT task (codex/gpt-6.1-sol, high), each round at the exact head: + +- r1 MUSTFIX: the combo trust gate matched only the lowercase code while failover folds case, + space and hyphens. Fixed with `namesModelNotInPlanCode` (`6d92c0657`). +- r2 MUSTFIX: policy fallback trusted a passthrough envelope's code. Fixed with the explicit + `verifiedPlanRefusal` option on `comboFailureDecision` (`5d41c59f5`). +- r3 MUSTFIX: status, code and marker header are all upstream-forgeable. Fixed by making + provenance in-memory object identity (`bbc39eab6`). r4 PASS at `bbc39eab6`. +- CodeRabbit: `formatErrorResponse` minted the mark from a caller-supplied code, which run-turn + combo children take from an adapter event. Fixed by marking only at the verifying boundaries + (`adapter-dispatch.ts`, `consumeComboFailure`); this plan text was also corrected. +- r5 MUSTFIX: the web-search and image sidecar loops rebuilt a failed first iteration as their own + envelope, dropping the fixed copy, code and mark, so a sidecar combo or policy stopped instead of + moving on. Fixed with `verifiedPlanRefusalMessage` in both loops and mark propagation across the + `sidecar-execution.ts` stream wrappers. +- r6 MUSTFIX: a refusal on a later sidecar iteration lands inside an already committed 200 stream, + where `model_not_in_plan` made Codex CLI 0.163.0 reconnect (6 sends). It now ends as `invalid_prompt` + with `retryable: false` and the same copy (1 send), via `sidecarLoopTerminalEvent`. +- CodeRabbit (tests): whole-body assertion and positive streamed combo/policy fallbacks. +- r7 MUSTFIX: on Chat inbound the later-iteration `invalid_prompt` arrived without a numeric status, + and both the streamed projection and the buffered collector inferred 502 `server_error` from the + wording. `src/chat/outbound.ts` now treats every `isTerminalRefusalCode` code as a 400-class + verdict that keeps its code (the existing cyber-policy rule, generalized). +- CodeRabbit (tests): the sidecar route test asserts the injected `web_search` function tool. +- Manager Linux verification: `adapter-dispatch.ts` grew to 2,007 lines, failing the file-size ratchet + and the responses-core module cap. The plan-refusal branch moved into `formatUpstreamFailureResponse` + (`src/bridge/errors.ts`); `adapter-dispatch.ts` is back to 1,999 lines with a one-call change. diff --git a/devlog/_plan/261025_hermes_profiles/000_plan.md b/devlog/_plan/261025_hermes_profiles/000_plan.md new file mode 100644 index 00000000000..8cf5afbd0a5 --- /dev/null +++ b/devlog/_plan/261025_hermes_profiles/000_plan.md @@ -0,0 +1,50 @@ +# 000 — hermes_profiles: Plan + +## Objective + +Issue #7674: the Hermes integration writes only the default profile. Hermes keeps one +home per profile: the default home is `HERMES_HOME` or the platform default +(`%LOCALAPPDATA%\hermes` on Windows, `~/.hermes` elsewhere), and each named profile is +`/profiles//` with its own `config.yaml`. OpenCodex resolves exactly one home +(`src/clients/config-export.ts:266,276`, registry `src/integrations/registry.ts:320`), so +named profiles get no provider block and no catalog. + +Evidence base (Hermes upstream, NousResearch/hermes-agent@4dcef215): + +- `hermes_constants.py` `PROFILE_ID_RE = ^[a-z0-9][a-z0-9_-]{0,63}$`, + `_PROFILE_IDENTITY_MARKERS = (config.yaml, .env, SOUL.md, profile.yaml, auth.json, state.db)`, + tombstone `profiles/.deleted/`, `_hermes_root_of` (HERMES_HOME under the native home + -> native home; `/profiles/` -> ``; otherwise HERMES_HOME itself), + `_platform_default_hermes_home` (Windows `%LOCALAPPDATA%\hermes`). +- `hermes_cli/profiles.py` `_get_profiles_root` = root/profiles, `_iter_named_profile_dirs`. +- Docs: https://hermes-agent.nousresearch.com/docs/user-guide/profiles , + https://hermes-agent.nousresearch.com/docs/getting-started/installation (Windows home). + +## Loop-spec + +- Loop archetype: verifier-defined (route/uninstall regression tests, gates). +- Write scope: new `src/integrations/hermes-profiles.ts`, new + `src/server/management/hermes-profile-routes.ts`, scoped store/path plumbing in + `src/server/management/integration-routes.ts`, `src/cli/uninstall-integrations.ts`, + GUI Hermes profile list + scope plumbing, docs-site integrations guide (EN), + `structure/clients/hermes.md` (new leaf; integrations.md net <= +1 line), tests. +- Out of scope: CLI `ocx integrations` profile flags, Hermes catalog auto-refresh (Hermes is + not in any refresh list today, default included), "apply to all" batch, translated docs, + `structure/runtime.md`. +- Budget: one work-phase; one PR. + +## Work-phase map + +| WP | Doc | Slice | Depends on | +|----|-----|-------|------------| +| wp1 | 010_phase1.md | discovery + scoped API + uninstall + GUI + docs + delivery | — | + +## Accept criteria + +- Named Hermes profiles are discovered and selectable; with no selection (or `profile=default`) + behavior is identical to dev. +- Each named profile has its own ownership store `integrations/hermes-profiles//` + (records, journal, snapshots); apply/disable/overwrite/restore/delete-history are scoped. +- Uninstall disables every owned Hermes profile before removing state. +- Regression test fails on dev, passes after the fix; acceptance commands green. +- PR to dev, exact-head CI dispatched, independent review PASS. diff --git a/devlog/_plan/261025_hermes_profiles/010_phase1.md b/devlog/_plan/261025_hermes_profiles/010_phase1.md new file mode 100644 index 00000000000..e0f8a3cdb99 --- /dev/null +++ b/devlog/_plan/261025_hermes_profiles/010_phase1.md @@ -0,0 +1,101 @@ +# 010 — Phase 1 (hermes_profiles) + +## MODIFY / NEW / DELETE map + +### NEW src/integrations/hermes-profiles.ts + +- `HERMES_PROFILE_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/` (Hermes PROFILE_ID_RE), + `HERMES_DEFAULT_PROFILE = "default"`, identity markers as Hermes. +- `hermesRootDir(env, home)`: Hermes `_hermes_root_of` over OpenCodex's existing native home: + HERMES_HOME unset -> native; HERMES_HOME inside native -> native; parent named `profiles` + -> grandparent; else HERMES_HOME. +- `listHermesProfiles(env, home)`: `{ name: "default", home: hermesHomeDir(env, home) }` followed by + named profiles under `/profiles`: real directory (lstat, never a symbolic link or + junction), name matches the id grammar and is not `default`, carries an identity marker, no + tombstone, and does not resolve to the default home (HERMES_HOME pointing at a profile already + makes it the default target). Sorted by name. Missing root -> default only. +- `resolveHermesProfile(name, env, home)`: `HermesProfileError` 400 for an invalid name, + 404 when it is not a listed profile. +- `hermesProfileStore(root, name)` at `/hermes-profiles/`; + `listHermesProfileStores(root)` enumerates persisted child stores (uninstall), refusing names + outside the grammar. +- `hermesProfileEnv(profile, env)`: `{ ...env, HERMES_HOME: profile.home }` — how Hermes itself + selects a profile; registry/writer/state then resolve that profile's config.yaml. + +### NEW src/server/management/hermes-profile-routes.ts + +- `GET /api/client-integrations/hermes/profiles` -> `{ profiles: [IntegrationStatus & { hermesProfile }] }`. +- `scopeHermesProfileRequest(ctx, ...)`: when `profile` is present and the request is + `/api/client-integrations/hermes` or a history path with `client=hermes` (`journal`, `preview`, + `restore`, `restore/preview`), validate and resolve the profile, register `{ profile, store, env }` + per Request, and return a ctx whose URL drops `profile`/`client` so the Aside handlers never + interpret a Hermes name. `profile=default` leaves the root scope. Errors -> 400/404 JSON. + +### MODIFY src/server/management/integration-routes.ts + +- `integrationStore(ctx?)` and `pathOverrides(ctx?)` consult the request scope; generic call sites + pass `ctx`. Aside options keep the root store. +- Call the profile list route and the scope resolver at the top of `handleIntegrationRoutes`. +- Scoped preview requires body `clientId: "hermes"`; scoped GET echoes `hermesProfile`. + +### MODIFY src/cli/uninstall-integrations.ts + +- Read every Hermes child store strictly before mutation; a child naming another client + refuses. `resolvedPaths` from the record (must be `.../profiles//config.yaml`), + disable through the same coordinated writer; final emptiness check includes Hermes children. + +### GUI + +- `integration-api.ts`: `hermesProfile?: string` scope on state, journal, preview, restore + preview, toggle, restore, delete; `loadHermesProfiles`. +- NEW `HermesProfilesPage.tsx`: profile list with state badges; selecting one renders + `FileIntegrationPage` scoped to it (switch, overwrite, history, undo). +- `FileIntegrationPage.tsx`, `RestoreDialog.tsx`: pass `hermesProfile` through. +- `Integrations.tsx`: Hermes tab renders `HermesProfilesPage`. i18n keys in every locale. + +### Docs + +- `docs-site/src/content/docs/guides/integrations.md`: Hermes profiles paragraph. +- NEW `structure/clients/hermes.md` registered in `structure/manifest.json`; one pointer line in + `structure/clients/integrations.md`. + +## TESTS + +- NEW `tests/clients/hermes-profiles.test.ts`: discovery (markers, tombstone, invalid names, + symlink refusal, HERMES_HOME-at-profile exclusion, LOCALAPPDATA root), root derivation. +- NEW `tests/clients/hermes-profile-routes.test.ts`: the regression — apply with `?profile=coder` + writes `profiles/coder/config.yaml`, leaves default untouched, records ownership in + `hermes-profiles/coder`; journal scoping, restore (undo) and disable per profile; invalid or + missing profile 400/404; default selection unchanged; uninstall disables profile ownership. + +## Verification (C) + +- `bun test tests/clients/hermes-profiles.test.ts tests/clients/hermes-profile-routes.test.ts` (red on dev, then green) +- `bun test tests/server/management-integration-routes.test.ts tests/cli/uninstall.test.ts tests/test-layout.test.ts tests/test-layout-tooling.test.ts` +- `bun run typecheck`, `bun run test:changed`, `bun run privacy:scan`, `bun run structure:check`, + `bun run lint:gui`, GUI tests, docs-site build. Exit 0 each. + +## A-round 1 (FAIL, 4 blockers) — resolutions + +1. Flight join (accepted): `scopedFlightKey` prefixes named-profile flight keys with + `hermes-profile::`; default keeps `apply`/`disable`/`restore:...`. Named/named and + named/default concurrent writes return `integration_mutation_busy`. Test: "a second profile is busy...". +2. Aside fallback (accepted): only `profile` is stripped; `client=hermes` stays. Every scoped request, + `profile=default` included, registers a scope, and the generic routes skip + `asideJournalResponse`, `asideRestoreResponse` and `asideJournalDeleteResponse` for it; + `outsideHermesScope` 404s any non-Hermes operation in restore, restore preview and delete, and a + scoped preview must name `clientId: "hermes"`. Test: foreign pi and Aside op ids, both scopes. +3. Root move (accepted): `hermesProfileBoundary` refuses (`hermes_profile_owner_mismatch`, 409) when the + child store's record names another config.yaml — at scope creation and again in the writer's + synchronous `guard` (restore: last step of `revalidate`). The list reports such a profile `unsafe`. + Test: two roots, same profile name, apply/overwrite/disable all refused, original record retained. +4. Filesystem boundary (accepted, proportionate): the boundary pins `profiles/` and the profile home by + `dev:ino` and refuses links/junctions, a tombstone appearing, and a linked store root or store + parent, immediately before the write. Discovery never lists links/junctions. Uninstall reads + records strictly and checks the `profiles//config.yaml` shape. Test: junctioned profile + not offered; profile swapped for a junction inside the flight is refused with no write. +5. Unscoped history (contract, not aggregated): the unscoped journal lists the root store only; + named-profile history is read/undone from that profile's page. Documented in structure/clients/hermes.md. +6. GUI keys (accepted): `hermesProfile` is part of the remount key, resource/session cache keys, callback + deps, and the response identity check (`status.hermesProfile === hermesProfile`). +7. Gates (accepted): layout shard + expected fixture registered; structure INDEX regenerated. diff --git a/devlog/_plan/261025_moonshot_k31_seed/000_plan.md b/devlog/_plan/261025_moonshot_k31_seed/000_plan.md new file mode 100644 index 00000000000..324f56eaca0 --- /dev/null +++ b/devlog/_plan/261025_moonshot_k31_seed/000_plan.md @@ -0,0 +1,53 @@ +# 000 — moonshot_k31_seed: Plan + +Issue #7653 (lane M4). Single-cycle C3 change; docs-first roadmap cycle skipped (one work-phase). + +## Objective + +The `moonshot` preset (Kimi API, `https://api.moonshot.ai/v1`) seeds `kimi-k3` plus legacy K2 ids +(`src/providers/registry/model-seeds.ts:772-773`) but not Kimi K3.1 Pro, so the picker never offers +`kimi-k3.1-pro` / `kimi-k3.1-pro-highspeed`, and a routed request gets no 1M window or effort ladder. +#7657 (46f23cab5e) already added the price rows for both ids (`src/usage/expected-prices.ts:467-468`) +but deliberately left the seeds out. + +Evidence base (official Moonshot platform docs only, fetched 2026-10-25): + +- https://platform.kimi.ai/docs/models.md — both `kimi-k3.1-pro` and `kimi-k3.1-pro-highspeed` listed + as current multi-modal models ("native visual understanding and a 1M-token context window"; + highspeed = "same intelligence, about 2-3x the speed"). +- https://platform.kimi.ai/docs/pricing/chat.md — "All K3 series models support a 1,048,576-token + context window"; price rows $2/$10/$0.20/$2(5m) and $4/$20/$0.20/$4(5m) (already in tree, unchanged). +- https://platform.kimi.ai/docs/guide/kimi-k3-1-pro-quickstart.md — always thinking; top-level + `reasoning_effort` low/high/max, default max; temperature/top_p/n/penalties fixed; vision via base64 + or ms:// file ids; return the complete assistant message (reasoning_content) on multi-turn. +- https://platform.kimi.ai/docs/api/models-overview.md — "kimi-k3.1-pro and kimi-k3.1-pro-highspeed + also have the same parameter constraints as kimi-k3"; `tool_choice` auto/none/required supported. +- https://platform.kimi.ai/docs/guide/use-reasoning-effort.md — low/high/max, default max. + +## Loop-spec + +- Loop archetype: verifier-defined (parity + wire tests, typecheck, gates). +- Write scope: model-seeds.ts KIMI_API_* constants; entries-extended.ts moonshot entry wiring; + tests/providers/provider-registry-parity.test.ts; tests/codex-integration/reasoning-effort.test.ts; + docs-site/src/content/docs/guides/providers.md (English canonical); structure/providers-and-adapters.md + (in-place, file stays at 600 lines); this devlog unit. +- Out of scope (owner decision, noted in PR): K2 retirement, moonshot defaultModel (kimi-k2.7-code, + entries-extended.ts:475), rename migrations, kimi-k3's own max-only ladder (docs now list low/high/max + for kimi-k3 too — follow-up), coding presets, prices, GUI, translated docs (English-only addition + does not contradict them). +- Budget / bounds: none stated; host limits apply. Tool scope: local repo, gh (push/PR authorized by + lane brief), GOAT task.create for review. + +## Work-phase map (one phase = one full PABCD cycle) + +| WP | Doc | Slice | Depends on | +|----|-----|-------|------------| +| wp1 | 010_phase1.md | seed + tests + docs + gates + PR + review | — | + +## Accept criteria + +- c-1 moonshot seeds both ids with 1M window, text+image, low/high/max default max, K3 effort map, + fixed sampling, preserve reasoning, not auto-tool-choice-only; kimi-k3 / K2 / default unchanged. +- c-2 regression tests red on base (0aeaf0cb6b) and green after the fix. +- c-3 typecheck, test:changed, privacy:scan, structure:check, docs-site build green on final head. +- c-4 PR open on dev, GOAT-linked, independent review PASS at exact head, CI state reported. diff --git a/devlog/_plan/261025_moonshot_k31_seed/010_phase1.md b/devlog/_plan/261025_moonshot_k31_seed/010_phase1.md new file mode 100644 index 00000000000..6f36afc9371 --- /dev/null +++ b/devlog/_plan/261025_moonshot_k31_seed/010_phase1.md @@ -0,0 +1,91 @@ +# 010 — Phase 1 (moonshot_k31_seed) — consolidated after audit rounds 1-2 and manager scope update + +## MODIFY / NEW / DELETE map + +### MODIFY src/providers/registry/model-seeds.ts (KIMI_API_* block, ~lines 772-809) + +```ts +export const KIMI_LEGACY_API_MODELS = [...unchanged]; +// 261025 Kimi K3.1 Pro on the Moonshot API (#7653): evidence comment (models.md, pricing/chat.md, +// kimi-k3-1-pro-quickstart.md, api/models-overview.md; China platform.kimi.com/docs/models.md), verified 2026-10-25. +export const KIMI_API_K31_MODELS = ["kimi-k3.1-pro", "kimi-k3.1-pro-highspeed"]; +export const KIMI_API_K3_SERIES_MODELS = ["kimi-k3", ...KIMI_API_K31_MODELS]; +export const KIMI_API_MODELS = [...KIMI_API_K3_SERIES_MODELS, ...KIMI_LEGACY_API_MODELS]; +// Frozen previous seed for the saved-roster repair (literal, not derived). +export const KIMI_API_MODELS_BEFORE_K31 = ["kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"]; +export const KIMI_API_NO_REASONING_MODELS = KIMI_API_MODELS.filter(id => !KIMI_API_K3_SERIES_MODELS.includes(id)); +// none -> omit: thinking cannot be disabled; value mirrors REASONING_EFFORT_OMIT_SENTINEL (src/reasoning-effort.ts:30) +export const KIMI_API_K31_REASONING_EFFORT_MAP = { ...KIMI_CODING_K3_REASONING_EFFORT_MAP, none: "__omit__" }; +export const KIMI_API_REASONING_EFFORTS = KIMI_API_MODELS -> K3.1: ["low","high","max"]; kimi-k3: ["max"] (unchanged); K2: []. +export const KIMI_API_DEFAULT_REASONING_EFFORTS = { "kimi-k3.1-pro": "max", "kimi-k3.1-pro-highspeed": "max" }; +export const KIMI_API_REASONING_EFFORT_MAPS = { both K3.1 ids: KIMI_API_K31_REASONING_EFFORT_MAP }; +export const KIMI_API_MODEL_CONTEXT_WINDOWS = K3 series 1_048_576, K2 262_144; +export const KIMI_API_MODEL_INPUT_MODALITIES = K3 series ["text","image"]; +``` + +(The omit literal is used because model-seeds.ts imports nothing from src/reasoning-effort.ts and the +registry must not depend on the request-path module; a test asserts the literal equals the exported +constant.) + +### MODIFY src/providers/registry/entries-extended.ts (moonshot entry) + +Add `modelDefaultReasoningEfforts: KIMI_API_DEFAULT_REASONING_EFFORTS` and +`modelReasoningEffortMap: KIMI_API_REASONING_EFFORT_MAPS` (+ imports). defaultModel stays kimi-k2.7-code. + +### MODIFY src/providers/stale-model-roster-migration.ts (STALE_MODEL_ROSTERS) + +```ts +{ + provider: "moonshot", from: KIMI_API_MODELS_BEFORE_K31, to: KIMI_API_MODELS, + contextWindows: KIMI_API_MODEL_CONTEXT_WINDOWS, defaultReasoningEfforts: KIMI_API_DEFAULT_REASONING_EFFORTS, + destinationBaseUrls: [MOONSHOT_INTL_BASE_URL, MOONSHOT_CN_BASE_URL], +} +``` + +### MODIFY docs-site/src/content/docs/guides/providers.md (English canonical, after the Kimi Coding price paragraph) + +One paragraph: moonshot seeds kimi-k3.1-pro / -highspeed next to kimi-k3 and the K2 ids; 1M context, +text+image, always-on thinking, low/high/max (default max; medium->high, xhigh->max, none omits the field); +sampling params omitted; untouched saved model lists on the International/China URLs gain both ids at +startup; default stays kimi-k2.7-code; link the official model list (K2 closes to new accounts 2026-10-31, +retirement planned end of December 2026). + +### Structure docs: no edit + +providers-and-adapters.md is owned by lane M5 (manager). Its line 87 (Kimi Coding seeds) and line 394 +(generic stale-roster contract) remain accurate: this change adds a data row to an existing mechanism and +seeds; no contract changes. chat-compat.md (600/600) describes wire compat, not seeds. Per +structure/AGENTS.md ("a reviewed document whose content remains accurate does not need copied unchanged +prose") no structure edit is required. Recorded in the PR. + +## TESTS (all in tests/providers/) + +- provider-registry-parity.test.ts, moonshot section: exact models, defaultModel, exact context windows, + per-K3.1 metadata (no-reasoning absent, ladder, default max, modalities, fixed-sampling + preserve lists, + not auto-tool-choice-only, map == K3 map with none "__omit__"), kimi-k3 unchanged (["max"], no default/map), + omit literal === REASONING_EFFORT_OMIT_SENTINEL; catalog entry default_reasoning_level "max". + New test: persisted OLD moonshot row (old models list, old modelContextWindows/modelReasoningEfforts) routed + via routeModel to moonshot/kimi-k3.1-pro[-highspeed]: configuredReasoningEfforts low/high/max; wire none -> no + reasoning_effort, low->low, medium->high, high->high, xhigh->max, max->max, ultra->max; temperature/top_p/penalties stripped. +- model-roster-seed-repair.test.ts: untouched moonshot seed on intl URL, cn URL, and no URL -> refreshed, existing + containers gain 1M windows + max defaults; custom URL and hand-edited list unchanged. + +Red run first on base 0aeaf0cb6b with test edits only. + +## Verification (C) + +| Command | Expected | +|---|---| +| bun test tests/providers/provider-registry-parity.test.ts tests/providers/model-roster-seed-repair.test.ts tests/usage/usage-cost.test.ts tests/codex-integration/reasoning-effort.test.ts tests/codex-integration/codex-catalog.test.ts | exit 0 | +| bun run typecheck | exit 0 | +| OCX_TEST_RUNNER_BUN= bun run test:changed | exit 0 except known claude-desktop-remote-hub.test.ts:249 | +| bun run privacy:scan | exit 0 | +| bun run structure:check | exit 0 | +| cd docs-site && bun install --frozen-lockfile && bun run build | exit 0 | + +## Audit history + +- Round 1 FAIL: (1) none sent on wire for always-thinking K3.1 -> own map with omit; (2) saved rows keep old + roster -> STALE_MODEL_ROSTERS entry. Both folded. +- Round 2 NEAR-PASS: chat-compat.md is 600/600 -> resolved by making no structure edit (rationale above); + stale superseded sections -> this consolidated rewrite. diff --git a/devlog/_plan/261025_sync_expect_link/010_plan.md b/devlog/_plan/261025_sync_expect_link/010_plan.md new file mode 100644 index 00000000000..9290d2127fc --- /dev/null +++ b/devlog/_plan/261025_sync_expect_link/010_plan.md @@ -0,0 +1,121 @@ +# 10 — `ocx sync --expect-link` / `--expect-hub-key` (issue #7694) + +Class C3 (CLI contract + client lifecycle). Single work-phase `wp1`. + +## Problem + +`ocx sync` (`src/cli/dispatch.ts`, `sync` runner) reads `readClientConnectionState()` without a +lock and picks a path from it: connected -> `syncConnectedClient`; disconnected -> local +`syncModelsToCodex`. GOAT runs `ocx connect status --json` and then `ocx sync`; if the Child was +disconnected or relinked in between, sync quietly applies local providers or another Home. + +## Contract + +- `ocx sync --expect-link `: proceed only when, under the lock that selects the path, the + machine is a connected Remote Link Child (`transport: "link"`) whose saved `link.linkId` equals + ``, with no pending key rotation and no pending/unsafe disconnect receipt. +- `ocx sync --expect-hub-key `: same for a hub-mode client (transport absent or `hub`) + whose `apiKeyId` equals the value. +- Match: exactly today's connected sync (catalog, Home Codex settings, apply record, runtime restart). +- Mismatch: nothing written, no local proxy discovery, no `syncModelsToCodex`; exit **5** (the + documented "state is not what you expected; inspect first" code); stderr carries one human line + and a final JSON line `{"phase":"child_link_mismatch"|"hub_client_mismatch","reason":R}` with + R in `disconnected | invalid_state | disconnect_pending | pending_operation | hub_client | + link_client | other_link | other_key`. Identifiers are not echoed. +- Usage: missing value, malformed link id (`^lnk_[0-9a-f]{16}$`), empty/oversized key id + (> 256 chars), a repeated flag, or both flags -> exit 2 before any state read. +- Without either flag, behavior is unchanged. + +## Diff-level plan + +1. `src/client/sync-expectation.ts` (new): `ClientSyncExpectation`, `ClientSyncMismatchReason`, + `ClientSyncExpectationError` (`phase`, `reason`), pure `classifyClientSyncExpectation(state, + receipt, expectation)`, and `assertClientSyncExpectation(expectation)` which reads the disconnect + receipt and connection state. Read-only; called with L/C held. +2. `src/client/connect.ts`: `syncConnectedClient(options.expect?)`; inside the first + `withClientLifecycleSync(withConfigMutationLockSync(...))` block (already inside `withLinkApply`) + call `assertClientSyncExpectation` before the existing `assertNoClientDisconnectPending`. Later + writes are already CAS-bound to that connection by `assertClientConnectionUnchanged`. Return the + connection the sync ran under (`connection`) so the CLI stops relying on its unlocked read. +3. `src/cli/sync-expectation.ts` (new): `parseSyncExpectation(args)` and + `reportSyncExpectationMismatch(error, log)` (returns 5 or null). +4. `src/cli/dispatch.ts` `sync`: parse first (exit 2 on usage); with an expectation, skip the + unlocked branch decision and always call `syncConnectedClient({ restartCodex, expect })`; map + `ClientSyncExpectationError` to exit 5; derive `link` and the restart owner from + `result.connection`. +5. CLI surface: `src/cli/registry.ts` usage/details, `src/cli/help.ts` line, sync capability flags + in `src/cli/capabilities-base.ts`; `bun run skill:surface`; prose in + `skills/ocx/references/04_failure_semantics.md` and `05_remote_hub.md`. +6. Docs: `docs-site/src/content/docs/reference/cli/lifecycle.md` (English) and + `structure/cli-management.md` (manager scope: `structure/runtime.md` is at its 600-line cap + and stays untouched). +7. Test `tests/cli/cli-sync-expect-link.test.ts` (registered in the `layout-clients.json` shard + and `tests/fixtures/test-layout-expected.json`): in-process, `OPENCODEX_HOME` temp dir, config + fixtures written directly, the client lifecycle lock moved into the test directory; spies on + `syncModelsToCodex` and `findLiveProxy` prove no fallback; a match uses a Child selecting only + `claude` (the schema requires one client) and a stubbed Home catalog fetch, so the real + connected path runs end to end. +8. `src/client/machine-api.ts`: `/api/machine/sync` spreads the sync result into its response, + so it drops the new `connection` field (token fingerprint, prior catalog) before replying. + +## Audit round 1 (codex/gpt-6.1-sol, NEAR-PASS) — decisions + +1. Record deletions after the Home settings await are not re-checked against a concurrent + disconnect. **Rebutted for this unit**: pre-existing in every connected sync, unchanged by the + expectation, and the record is one disconnect removes anyway. Left as a residual. +2. Pending connect marker not inspected. **Accepted**: on a disconnected machine a valid + `client-connect-pending` marker is `pending_operation`, an unreadable one `invalid_state`. A + connect only starts from a disconnected machine, so a marker beside a connection is ignored. +3. Test coverage. **Accepted in part**: added both expectation kinds end to end, invalid config, + pending connect (valid and malformed), pending/unsafe receipts in the classifier, a relink + during the catalog read, isolated lock path, restored `process.exitCode`. Selected-Codex + injection is unchanged code after the gate and stays covered by its existing suites. +4–6. Notes adopted: lock files are coordination artifacts, not state; the JSON `phase` is the + discriminator, exit 5 the code; `result.connection` is `next`. + +Test hazard found while recording the red run: on an unfixed tree an argv containing +`--restart-codex` is a real restart request, which ended the Codex app hosting the agent. The +test carries no restart flag and stubs the restart helper. + +## PR review round 1 (codex/gpt-6.1-sol, FAIL) — decisions + +1. Blocker, **accepted**: `runCli` ran `maybeAutoRestoreCodexShim` before dispatch, so a pinned + sync could repair the shim on a mismatched machine, and a malformed one read shim state before + exiting 2. Fix: `src/cli/root.ts` validates the flags before any preflight (exit 2); + `skipsCodexShimAutoRestore` exempts a pinned sync (the `stop --expect-*` precedent); the sync + runner calls `runCodexShimAutoRestore` once the locked check passed, so a match still repairs + like an unpinned sync. Root-level tests added; without the fix 4 of 25 cases fail. +2. Non-blocking, **accepted**: trailing blank lines at EOF removed (`git diff --check` clean). + +## Manager review at 7bfbd75a41 (MUST-FIX) — decisions + +1. P2, **accepted**: a link connect holds the apply lock across its network reads, so a pinned + sync failed acquiring it before its check and exited 1 without a phase. `withLinkApply` now + throws `LinkApplyBusyError` (`code: "link_apply_busy"`, same message); `syncConnectedClient` + maps that or `client_lifecycle_busy` raised before its check passed to + `ClientSyncExpectationError(..., "pending_operation")`. Tests hold a real link connect at its + readiness fetch (deferred promise) and a real lifecycle lock; without the fix both fail. +2. P3, **accepted**: PR body regains a standalone `## Verification` heading and `Closes #7694`. + +## PR review round 4 (codex/gpt-6.1-sol at 21166e1be, FAIL) — decisions + +1. Blocker, **accepted**: the check also needs the config mutation lock; a concurrent config writer + made acquisition throw `CONFIG_MUTATION_LOCK_UNAVAILABLE` (cause `SQLITE_BUSY`), which the + mapper ignored (exit 1, no phase). `isClientSyncLockBusy` now accepts that code when its cause + is busy/locked, with the classification `src/config/mutation-lock.ts` already uses; other + acquisition or storage failures stay themselves. Test: a real SQLite `BEGIN IMMEDIATE` holder + for both expectation kinds, an unpinned control (exit 1), and a holder taken after the check + (exit 1, "Config mutation already in progress"). Without the fix the case fails. + +## Verification + +Red run of the new test on the unfixed tree, then: focused test files, `bun run typecheck`, +`bun run test:changed`, `bun run privacy:scan`, `bun run structure:check`, +`bun run skill:surface:check`, docs-site build. Full `bun run test` is optional (shared host). + +## Risks + +- Older `ocx` ignores unknown sync flags, so a caller must version-gate the flag (GOAT keeps its + status check for older versions, per the issue). +- A connection that changes after the locked check but before a write still fails as today + (`client_connection_changed`, exit 1), not as a mismatch: the expectation held when checked. diff --git a/docs-site/astro.config.mjs b/docs-site/astro.config.mjs index 8476dae8623..89ccd0728d6 100644 --- a/docs-site/astro.config.mjs +++ b/docs-site/astro.config.mjs @@ -103,6 +103,7 @@ export default defineConfig({ { label: "Desktop App", translations: { fr: "Application de bureau", ko: "데스크톱 앱", "zh-CN": "桌面应用", "zh-TW": "桌面 App", ru: "Настольное приложение", ja: "デスクトップアプリ", tr: "Masaüstü Uygulaması" }, slug: "guides/desktop-app" }, { label: "Model Ordering", translations: { fr: "Ordre des modèles", ko: "모델 정렬에 관하여", "zh-CN": "模型排序", "zh-TW": "模型排序", ru: "Сортировка моделей", ja: "モデルの並び順", tr: "Model Sıralaması" }, slug: "guides/model-ordering" }, { label: "Combos", translations: { fr: "Combinaisons", ko: "콤보", "zh-CN": "组合", "zh-TW": "組合", ru: "Комбо", ja: "コンボ", tr: "Kombolar" }, slug: "guides/combos" }, + { label: "Fallback off", translations: { fr: "Désactiver le repli", ko: "폴백 끄기", "zh-CN": "关闭回退", "zh-TW": "關閉備援", ru: "Отключение резерва", ja: "フォールバックをオフにする", tr: "Yedeği kapatma" }, slug: "guides/fallback-off" }, { label: "Protocol Paths", translations: { fr: "Chemins de protocole", ko: "프로토콜 경로", "zh-CN": "协议路径", "zh-TW": "協定路徑", ru: "Пути протоколов", ja: "プロトコル経路", tr: "Protokol Yolları" }, slug: "guides/protocol-paths" }, { label: "Claude Code", translations: { fr: "Claude Code", ko: "Claude Code", "zh-CN": "Claude Code", "zh-TW": "Claude Code", ru: "Claude Code", ja: "Claude Code", tr: "Claude Code" }, slug: "guides/claude-code" }, { label: "Grok Build", translations: { fr: "Grok Build", ko: "Grok Build", "zh-CN": "Grok Build", "zh-TW": "Grok Build", ru: "Grok Build", ja: "Grok Build", tr: "Grok Build" }, slug: "guides/grok-build" }, @@ -162,6 +163,7 @@ export default defineConfig({ { label: "Adapters", translations: { fr: "Adaptateurs", ko: "어댑터", "zh-CN": "适配器", "zh-TW": "適配器", ru: "Адаптеры", ja: "アダプター", tr: "Adaptörler" }, slug: "reference/adapters" }, { label: "Architecture", translations: { fr: "Architecture", ko: "아키텍처", "zh-CN": "架构", "zh-TW": "架構", ru: "Архитектура", ja: "アーキテクチャ", tr: "Mimari" }, slug: "reference/architecture" }, { label: "Platform Support", translations: { fr: "Prise en charge des plateformes", ko: "플랫폼 지원", "zh-CN": "平台支持", "zh-TW": "平台支援", ru: "Поддержка платформ", ja: "プラットフォーム対応", tr: "Platform Desteği" }, link: `${SITE_URL}/reference/platform-support` }, + { label: "Windows Code Signing", translations: { fr: "Signature du code Windows", ko: "Windows 코드 서명", "zh-CN": "Windows 代码签名", "zh-TW": "Windows 程式碼簽署", ru: "Подпись кода Windows", ja: "Windows コード署名", tr: "Windows Kod İmzalama" }, slug: "reference/code-signing" }, { label: "Proxy API Formats", translations: { fr: "Formats de l’API proxy", ko: "프록시 API 형식", "zh-CN": "代理 API 格式", "zh-TW": "代理 API 格式", ru: "Форматы API прокси", ja: "プロキシAPI形式", tr: "Proxy API Formatları" }, slug: "reference/proxy-formats" }, { label: "Management API", translations: { fr: "API de gestion", ko: "관리 API", "zh-CN": "管理 API", "zh-TW": "管理 API", ru: "API управления", ja: "管理API", tr: "Yönetim API'si" }, slug: "reference/management-api" }, ], diff --git a/docs-site/src/components/Landing.astro b/docs-site/src/components/Landing.astro index 4048ae628be..8944272605e 100644 --- a/docs-site/src/components/Landing.astro +++ b/docs-site/src/components/Landing.astro @@ -111,6 +111,7 @@ const fr = { 'Installation guide': 'Guide d’installation', 'All releases and checksums': 'Toutes les versions et sommes de contrôle', 'Desktop app guide': 'Guide de l’app de bureau', + 'Code signing policy': 'Politique de signature du code', 'Desktop App': 'App de bureau', 'Latest release': 'Dernière version', 'Star on GitHub': 'Ajouter une étoile sur GitHub', @@ -344,6 +345,12 @@ const docsMap = [ {t('All releases and checksums', '전체 릴리스와 체크섬', '所有版本与校验和', 'Все релизы и контрольные суммы', 'すべてのリリースとチェックサム', '所有版本與校驗碼', 'Tüm sürümler ve sağlama toplamları')} {t('Desktop app guide', '데스크톱 앱 가이드', '桌面应用指南', 'Руководство по приложению', 'デスクトップアプリガイド', '桌面應用程式指南', 'Masaüstü uygulaması kılavuzu')}

+ +

+ Free code signing provided by SignPath.io, certificate by SignPath Foundation. + {t('Code signing policy', '코드 서명 정책', '代码签名策略', 'Политика подписи кода', 'コード署名ポリシー', '程式碼簽署政策', 'Kod imzalama politikası')} +

diff --git a/docs-site/src/content/docs/contributing.md b/docs-site/src/content/docs/contributing.md index aa8668df8c5..d0021b7f12a 100644 --- a/docs-site/src/content/docs/contributing.md +++ b/docs-site/src/content/docs/contributing.md @@ -45,9 +45,11 @@ bun run privacy:scan # credential/privacy scan used by CI bun run prepare:package # refresh package launchers/assets ``` -The suite uses Bun 1.4.3 (`package.json`'s `testRunnerBun`) while the shipped runtime is 1.4.2. -`scripts/test.ts` finds a 1.4.3 binary on PATH or in `~/.bun/bin`, or uses `OCX_TEST_RUNNER_BUN`. +The suite uses Bun 1.4.4 (`package.json`'s `testRunnerBun`) while the shipped runtime is 1.4.4. +`scripts/test.ts` finds a 1.4.4 binary on PATH or in `~/.bun/bin`, or uses `OCX_TEST_RUNNER_BUN`. Install that version locally if it is missing; the resolver never downloads it. +The roles remain independently checked even when their versions match. Promotion requires +a successful full cross-platform `lane=all` run on the candidate head. Run `bun run test` by default. If a full run is disproportionately expensive for the task size, available machine resources, or concurrent worktrees, you must at least run focused regression tests @@ -263,6 +265,11 @@ Use one of the versioned dispositions: `passthrough`, `translated`, `degraded`, Every non-passthrough claim must state its limitation, and every fixture-backed claim must name the exact assertion ids that prove it. Add the secret-free request vector under `tests/fixtures/compatibility/` and execute it against the production adapter in a focused test. +A `passthrough` claim may only cite `equals` assertions whose expected value is the caller's own +value at the same place, on a selector bound to that feature in the manifest test, so a removed, +rewritten, or unrelated field cannot be labeled lossless. Keep related +aspects as separate claims when a route can preserve one and lose the other, such as a custom tool +and its grammar. Compatibility manifests are passive data: the ordinary router, Responses handler, and server startup path must not import the manifest catalog or activate Compatibility Lab. diff --git a/docs-site/src/content/docs/fr/getting-started/quickstart.md b/docs-site/src/content/docs/fr/getting-started/quickstart.md index a3eb2f9fea0..fa96c775b52 100644 --- a/docs-site/src/content/docs/fr/getting-started/quickstart.md +++ b/docs-site/src/content/docs/fr/getting-started/quickstart.md @@ -13,7 +13,7 @@ ocx init `ocx init` vous accompagne dans les étapes suivantes : -1. **Choix d’un fournisseur** — sélectionnez l’un des 104 préréglages intégrés au registre, ou `custom` pour saisir une +1. **Choix d’un fournisseur** — sélectionnez l’un des 106 préréglages intégrés au registre, ou `custom` pour saisir une URL de base et un adaptateur. 2. **Clé API** — collez une clé ou référencez une variable d’environnement telle que `${ANTHROPIC_API_KEY}`. 3. **Modèle par défaut** — pour les fournisseurs clés, locaux et personnalisés, acceptez le préréglage ou saisissez un identifiant de modèle. diff --git a/docs-site/src/content/docs/fr/guides/claude-code.md b/docs-site/src/content/docs/fr/guides/claude-code.md index 49cd7d6113e..0405b2807d9 100644 --- a/docs-site/src/content/docs/fr/guides/claude-code.md +++ b/docs-site/src/content/docs/fr/guides/claude-code.md @@ -169,24 +169,24 @@ unknown signifie qu’opencodex ne peut pas déterminer si les réglages pointen ### Mode picker : modèles opencodex dans le sélecteur Code first-party -Le mode picker fait partie du mode first-party. Sur macOS, il est activé par défaut lorsque first-party +Le mode picker fait partie du mode first-party. Sur macOS et Windows, il est activé par défaut lorsque first-party est sélectionné, sauf si `claudeCode.intercept.picker: false` est défini. Il modifie le sélecteur de modèles de l'onglet Code de Desktop first-party pour y afficher les modèles opencodex disponibles par leur nom. Lors de la première activation, macOS peut demander l'autorisation d'une autorité de certification -locale dans le trousseau de connexion. Cette autorité est limitée à `claude.ai` et à ses sous-domaines. +locale dans le trousseau de connexion, et Windows dans le magasin racine de l'utilisateur actuel. Cette autorité est limitée à `claude.ai` et à ses sous-domaines. Son identité de signature exportable est protégée par le magasin d'identifiants du système et réutilisée lors des redémarrages ordinaires. Aucune clé de signature picker en clair n'est enregistrée dans le répertoire de configuration OpenCodex. La validation complète de l'autorité contrainte et la vérification -de la confiance du système restent obligatoires. Avec la même identité approuvée et un magasin accessible, -un redémarrage n'ajoute ni ne supprime de réglages de confiance des certificats. La restauration au démarrage -n'installe jamais la confiance : si elle manque, est révoquée ou inconnue, le picker reste en attente. -Exécutez explicitement `ocx claude desktop picker on` ou `ocx claude desktop picker trust` pour l'accorder. +de la confiance du système restent obligatoires, avec sur Windows la correspondance exacte de l'empreinte et une feuille vérifiée. Avec la même identité approuvée et un magasin accessible, +un redémarrage n'ajoute ni ne supprime de réglages de confiance des certificats macOS. La restauration au démarrage +ou la vérification d'état n'installe jamais la confiance : si elle manque, est révoquée ou inconnue, le picker reste en attente. +Exécutez explicitement `ocx claude desktop picker on` ou `ocx claude desktop picker trust` pour l'accorder, y compris la confirmation Windows si nécessaire. Une migration unique depuis une ancienne identité peut demander votre consentement pour supprimer sa confiance. Si ce nettoyage échoue, le picker reste indisponible et le profil appliqué utilise un relais sans déchiffrement jusqu'à sa réussite. macOS peut aussi demander de déverrouiller le trousseau ou -d'autoriser l'accès d'une application aux identifiants, y compris après un redémarrage ou une mise à jour. -Le picker reste non pris en charge sous Windows et Linux : aucun travail de CA, de magasin d'identifiants +d'autoriser l'accès d'une application aux identifiants, y compris après un redémarrage ou une mise à jour. Sur Windows, `on` ou `trust` explicite peut afficher une confirmation d'ajout de la racine. +Le picker reste non pris en charge sous Linux : aucun travail de CA, de magasin d'identifiants ou de proxy picker n'y démarre. L'interception Claude principale reste disponible ; ses fichiers CA locaux sont protégés par des vérifications du propriétaire, des liens symboliques, des permissions et des ACL Windows. diff --git a/docs-site/src/content/docs/fr/guides/codex-app-models.md b/docs-site/src/content/docs/fr/guides/codex-app-models.md index 0e69068a714..8e420079af1 100644 --- a/docs-site/src/content/docs/fr/guides/codex-app-models.md +++ b/docs-site/src/content/docs/fr/guides/codex-app-models.md @@ -138,11 +138,12 @@ approximation fondée sur un ancien modèle d'entrée. | Route | Identifiants du sélecteur et métadonnées du catalogue | | --- | --- | -| Connexion Codex (lignes qualifiées par compte désactivées) | Identifiants natifs non qualifiés comme `gpt-5.6-sol`, `gpt-5.6-terra` et `gpt-5.6-luna` ; Pool ou Direct est choisi avec `codexAccountMode`. Les lignes GPT-5.6 utilisent une fenêtre de catalogue de 372 000 jetons. | +| Connexion Codex (lignes qualifiées par compte désactivées) | Identifiants natifs non qualifiés comme `gpt-5.6-sol`, `gpt-5.6-terra` et `gpt-5.6-luna` ; Pool ou Direct est choisi avec `codexAccountMode`. Les lignes GPT-5.6 utilisent une fenêtre de catalogue de 372 000 jetons. `gpt-6.1-terra` : contexte par défaut 500,000 / étendu 872,000. | | Connexion Codex (lignes qualifiées par compte activées avec des sélecteurs admissibles) | Une ligne `/` par sélecteur admissible et modèle natif pris en charge ; chaque ligne utilise exclusivement le compte associé, et les lignes natives non qualifiées sont masquées dans le sélecteur. Les métadonnées natives et les fenêtres de contexte sont préservées. | | Connexion Codex (ligne Daybreak transférée explicitement) | `openai/gpt-daybreak-blue-latest` uniquement lorsque l'entrée `customModels` exacte est configurée sur le fournisseur canonique `openai`. Elle conserve l'identifiant Daybreak transmis et utilise l'instantané de capacités Sol épinglé (contexte de 372 000 jetons ; compactage automatique à 334 800 jetons). | -| OpenAI (clé API) | Exactement dix lignes avec espace de noms : `gpt-5.5`, `gpt-5.6`, Sol/Terra/Luna, les trois identifiants virtuels `*-pro` et les deux alias Daybreak (contexte de 1 050 000 jetons ; entrée maximale de 922 000 jetons pour les dix) | +| OpenAI (clé API) | Les modèles avec espace de noms comprennent `gpt-5.5`, `gpt-5.6`, Sol/Terra/Luna, les identifiants virtuels `*-pro` et les alias Daybreak (contexte de 1,050,000 ; entrée maximale de 922,000). `openai-apikey/gpt-6.1-terra` ajoute un contexte de 1,050,000 et une sortie maximale de 128,000. | | OpenRouter | `openrouter/openai/gpt-5.6-sol`, `openrouter/openai/gpt-5.6-terra`, `openrouter/openai/gpt-5.6-luna` (1 050 000) | +| OpenRouter | `openrouter/openai/gpt-6.1-terra` (1,050,000) | | Cursor | Le repli statique comprend `cursor/gpt-5.6-sol`, `cursor/gpt-5.6-terra` et `cursor/gpt-5.6-luna` (1 000 000), ainsi que des lignes ordinaires/rapides pour Grok 4.5, 4.6 et 4.7 (500 000) ; 4.6 et 4.7 ajoutent `xhigh`, et la découverte dynamique propre au compte détermine quelles lignes restent visibles. | | xAI | La découverte dynamique fait autorité. Le catalogue de secours comprend `xai/grok-4.6` et `xai/grok-4.7` et utilise `xai/grok-4.5` par défaut ; les trois ont une fenêtre de 500 000 jetons. Grok 4.6 et 4.7 proposent `low` / `medium` / `high` / `xhigh` (valeur amont par défaut : `high`), tandis que Grok 4.5 s’arrête à `high`. | diff --git a/docs-site/src/content/docs/fr/guides/combos.md b/docs-site/src/content/docs/fr/guides/combos.md index 4cb051fc727..8a9398988f6 100644 --- a/docs-site/src/content/docs/fr/guides/combos.md +++ b/docs-site/src/content/docs/fr/guides/combos.md @@ -201,7 +201,7 @@ Les échecs d’un combo se répartissent entre ceux qui entraînent un **bascul | HTTP 401, 403, 404, 408, 429, ou n'importe quel 5xx | Refroidissez la cible et passez à la prochaine cible éligible. | | Erreur classée comme erreur d’authentification, d’abonnement, de quota, de limitation de débit, de surcharge ou de serveur en amont | Place la cible en période de refroidissement et bascule, même si le statut seul ne suffit pas. | | Annulation client (499), `origin_rejected`, refus de cyber-politique, débordement de contexte ou autre demande invalide | Arrêtez et renvoyez l'erreur ; une autre cible ne rendrait pas la demande valide. | -| Rejet structuré de `user`, valeur non prise en charge pour `reasoning.effort`/`reasoning_effort`, ou rejet d'entrée d'image propre à un modèle (`param: input`) | Bascule vers la cible admissible suivante avant le début de la sortie, sans délai de refroidissement ; voir Compatibilité des paramètres facultatifs ci-dessous. | +| Rejet structuré de `user`, valeur non prise en charge pour `reasoning.effort`/`reasoning_effort`, rejet d'entrée d'image propre à un modèle (`param: input`), ou refus d'un média d'entrée que la route ne peut pas lire (`unsupported_input_modality`) | Bascule vers la cible admissible suivante avant le début de la sortie, sans délai de refroidissement ; voir Compatibilité des paramètres facultatifs ci-dessous. | | Premier appel d'outil d'un tour Responses exécuté par un adaptateur interne (`runTurn`) que la requête courante n'a pas déclaré, avant toute sortie et tout effet de bord non rejouable | Met la cible en refroidissement et bascule avec le même catalogue d'outils. Après une sortie visible ou un effet de bord non rejouable, le refus est définitif. Les requêtes Chat Completions et Anthropic Messages ne changent pas. | | Toute autre erreur non classifiée | Arrêtez et renvoyez l'erreur. | @@ -277,7 +277,7 @@ flux de travail des tâches. ### Tableau de bord Ouvrez le tableau de bord local et choisissez **Modèles → Combos**. L'espace de travail crée, modifie, renomme et supprime -combos, et son sélecteur de cible exclut les modèles désactivés et les combos imbriqués. +les combos. Les sélecteurs de modèles incluent les modèles masqués : la visibilité ne concerne que les listes externes, pas la sélection interne des combos. Sélectionner un modèle masqué ne le rend pas visible. Le sélecteur de cible exclut toujours les fournisseurs désactivés, les combos imbriqués et les fournisseurs JEV réservés au service de décision. Chaque cible affiche aussi un badge de quota en direct : **Disponible**, **Quota épuisé** ou **Quota inconnu**. L’éditeur bloque Enregistrer et Créer pour une raison de quota uniquement lorsque chaque cible utilisable dispose d’une confirmation serveur encore valide indiquant que la limite d’inférence liée à ses identifiants configurés est épuisée. Les quotas de compte, de modèle, de recherche et de MCP fournis uniquement à titre d’affichage, ainsi que les informations de routage absentes ou expirées, ne déclenchent pas ce blocage. Le blocage expire à la réinitialisation applicable ou à l’expiration de la validité des données et fait l’objet d’une nouvelle vérification lorsque la page devient active ou visible ; Actualiser recharge à la fois les données des combos et les quotas. @@ -294,8 +294,8 @@ ocx combo remove --yes ``` `set` accepte également `--strategy`, `--sticky`, `--effort`, `--alias`, `--native-alias`, -`--display-name` et `--rename-from`. Utilisez `-` comme valeur de `--effort`, `--alias` ou -`--display-name` pour effacer ce champ. `--native-alias` exige un alias de modèle natif non qualifié actuellement +`--display-name`, `--description` et `--rename-from`. Utilisez `-` comme valeur de `--effort`, `--alias`, +`--display-name` ou `--description` pour effacer ce champ. `--native-alias` exige un alias de modèle natif non qualifié actuellement pris en charge et un nom d’affichage non vide. `create` et `update` sont des alias pour `set` ; `delete` est un alias pour `remove` ; et les mêmes sous-commandes sont disponibles sous `ocx route combo`. @@ -341,6 +341,7 @@ Les combos sont stockés dans l'objet `combos` de niveau supérieur, saisi par l | `alias` | Non | aucun | Identifiant de modèle public tronqué facultatif ; utilisez les règles d'alias ci-dessus. Une valeur vide est stockée sans alias. | | `nativeAlias` | Non | `false` | Autoriser explicitement un `alias` natif nu actuellement pris en charge à avoir la priorité sur le routage et le catalogue. Jamais déduit de l'alias. | | `displayName` | Non | aucun | Étiquette de catalogue délimitée en affichage uniquement. Obligatoire et non vide lorsque `nativeAlias` est vrai. | +| `description` | Non | aucun | Description de ligne de catalogue en affichage uniquement, avec les mêmes limites que `displayName`. Sans valeur, le texte généré `Routed via opencodex → combo (combo).` est conservé. | ## Dépannage @@ -370,6 +371,6 @@ gérer un refus de politique ou corriger l’origine de la demande rejetée. Les ## Compatibilité des paramètres facultatifs -Exception aux erreurs 400 terminales : un rejet structuré de `user`, une valeur non prise en charge pour `reasoning.effort`/`reasoning_effort`, ou un rejet d’entrée d’image propre à un modèle (`param: input`) peut faire passer le combo à la cible admissible suivante avant le début de la sortie, sans délai de refroidissement. Le refus de sécurité, l’annulation et une sortie déjà commencée restent non rejouables. +Exception aux erreurs 400 terminales : un rejet structuré de `user`, une valeur non prise en charge pour `reasoning.effort`/`reasoning_effort`, un rejet d’entrée d’image propre à un modèle (`param: input`), ou le refus d’un média d’entrée que la route ne peut pas lire (`unsupported_input_modality`) peut faire passer le combo à la cible admissible suivante avant le début de la sortie, sans délai de refroidissement. Le refus de sécurité, l’annulation et une sortie déjà commencée restent non rejouables. [Canonical compatibility details](/guides/combos/#request-local-target-compatibility). diff --git a/docs-site/src/content/docs/fr/guides/providers.md b/docs-site/src/content/docs/fr/guides/providers.md index f820ce88b5a..91cc03aaabe 100644 --- a/docs-site/src/content/docs/fr/guides/providers.md +++ b/docs-site/src/content/docs/fr/guides/providers.md @@ -67,8 +67,9 @@ La relance d'une requête 429 avec la même clé, configurée par [`retryOn429`](/fr/reference/configuration/), s'applique uniquement aux fournisseurs à clé API (`authMode: "key"`). Les préréglages OAuth, de transfert et locaux sont exclus : leurs identifiants ne doivent jamais être réutilisés sur le même jeton, et les environnements locaux ne possèdent aucune clé distante à -préserver. Cette fonction est facultative : elle est désactivée lorsque l'option est absente ; la présence de -l'objet l'active, sauf si `enabled: false`. +préserver. Cette fonction est facultative, sauf pour les valeurs par défaut documentées propres à certaines destinations. Le Chat Mistral canonique utilise une politique de relance patiente (6 relances, intervalle de repli de 10 secondes), dans la limite de l’allocation d’envois existante pour l’ensemble de la requête. Les réglages explicites de `retryOn429`, y compris `enabled: false`, sont prioritaires. Le préréglage Mistral transmet aussi par défaut les clés de cache de prompt fournies ; `promptCacheKey: false` désactive cette transmission. L’affinité de cache augmente les chances de réutilisation, mais ne garantit pas un succès de cache et ne lève pas les limites de débit de jetons. + +Mistral utilise un seul fournisseur à clé API pour les clés Studio et Vibe. Les types de clés et les allocations des offres sont décrits dans la [documentation des clés API de Mistral](https://docs.mistral.ai/admin/identity-access/api-keys) ; le proxy ne déduit pas le niveau d’abonnement à partir de la clé. Pour tous les modèles, les échéances `Retry-After` explicites renvoyées en amont sont partagées entre les appels qui utilisent les mêmes identifiants et le même modèle. Un 429 sans délai de relance peut déclencher une attente de récupération prudente de 60 secondes lorsque des compteurs par minute valides indiquent une capacité épuisée, ou lorsque le coût mesuré de la requête refusée dépasse les jetons restants tout en tenant dans l’allocation complète annoncée. Il s’agit d’une politique de récupération locale, pas d’une prédiction exacte de la réinitialisation côté serveur. Le trafic sain ne prédit jamais le cache ni le coût de la requête suivante. Après l’attente, une seule sonde de récupération est envoyée ; si elle réussit, le trafic ordinaire reprend. Des indications absentes, mal formées, sans droit d’usage, mensuelles épuisées ou supérieures à l’allocation complète n’inventent ni réinitialisation ni compactage de l’historique. `requestPacing: { "enabled": false }` ou `retryOn429: { "respectRetryAfter": false }` désactive le cadencement partagé. Pour une relance avec la même clé, si le délai de relance de Mistral dépasse `retryOn429.maxIntervalMs`, le proxy renvoie le 429 et l’indication de relance au lieu de relancer trop tôt. Lorsque la sélection des clés en rotation est activée, les requêtes en attente revérifient le refroidissement des clés et acquièrent la capacité de cadencement d’une clé de remplacement, et un rejet de clé avant toute sortie peut être récupéré avec une clé saine dans la limite d’envois existante de la requête. Les fenêtres de contexte et les préférences de compactage par modèle restent indépendantes. ## 1. Connexion ChatGPT (transfert direct) @@ -112,6 +113,7 @@ ocx login google-antigravity ocx login cursor # standalone Cursor PKCE login ocx login command-code # Command Code browser OAuth (or import ~/.commandcode/auth.json) ocx login devin # Cognition/Devin : import de l'identifiant du Devin CLI, sinon connexion navigateur Auth0 +ocx login dsh-account # DeepSeek Harness Desktop (import de l'autorisation ~/.dsh/.credentials.yaml) ocx login github-copilot # GitHub device flow → Copilot token (Copilot Pro/Business) ocx login codex # pool de comptes Codex (alias : chatgpt, openai ; nécessite un proxy en cours d'exécution) ocx logout @@ -128,6 +130,7 @@ ocx logout | `google-antigravity` | `google` | `https://daily-cloudcode-pa.googleapis.com` | Google OAuth avec le protocole Cloud Code Assist. La découverte en direct utilise le point de terminaison CCA authentifié `v1internal:fetchAvailableModels` et publie les modèles d'agent accessibles au compte connecté ; le catalogue maintenu reste la solution de repli. | | `cursor` | `cursor` | `https://api2.cursor.sh` | Connexion PKCE expérimentale, transport HTTP/2 en direct et découverte de modèles filtrés par compte. | | `devin` | `devin` | `https://server.codeium.com` | Passerelle Cognition/Devin non officielle et expérimentale. La connexion importe d'abord l'identifiant que le Devin CLI installé détient déjà (`devin auth login` écrit un `devin-session-token` dans son propre `credentials.toml`) ; à défaut, elle ouvre l'authentification Auth0 dans le navigateur puis échange le jeton collé via `RegisterUser` contre une clé d'API durable. `ocx login devin-cli` reste accepté comme alias déprécié. Les modèles sont découverts par compte avec `GetCascadeModelConfigs` ; le streaming passe uniquement par `runTurn` sur Connect-RPC. Absente du préréglage du tableau de bord par défaut. | +| `dsh-account` | `dsh-account` | `https://api.deepseek.com/anthropic` | Fournisseur de compte expérimental à importation prioritaire réutilisant un compte DeepSeek Harness (DSH) Desktop installé et connecté. Lit les identifiants en lecture seule stricte depuis `~/.dsh/.credentials.yaml` lors de l'importation explicite. Indépendant de la facturation par clé d'API DeepSeek. | | `github-copilot` | `openai-chat` | `https://api.githubcopilot.com` | Expérimental. Flux d'appareil GitHub et échange `copilot_internal` (client OAuth de VS Code). Nécessite un abonnement Copilot actif ; il ne s'agit pas d'une API tierce officielle. | Les vérifications de quota Google Antigravity utilisent des points de terminaison Google fixes, y compris le repli vers la liste des modèles. Elles prennent en charge le DNS Fake-IP transparent pour ces destinations en conservant la vérification TLS, le refus des redirections et les contrôles des adresses privées. Une URL de base personnalisée ne modifie que les requêtes de modèles ; `NO_PROXY` conserve la politique de connexion directe. @@ -293,9 +296,20 @@ reconnue. Réparez ou supprimez la base illisible dans le chemin de données nor sélecteurs d'importation, puis réessayez. La connexion depuis une machine dépourvue de session `kiro-cli` existante n'est pas concernée. +### DeepSeek Account (DSH) + +`dsh-account` est un fournisseur de compte expérimental à importation prioritaire, conçu pour réutiliser les identifiants de compte authentifiés d'une application DeepSeek Harness (DSH) Desktop installée. + +- **Indépendant de la clé d'API DeepSeek :** `dsh-account` utilise le quota de session DeepSeek authentifiée de l'utilisateur (portefeuilles standard et bonus, dépenses totales), tandis que le fournisseur `deepseek` facture sur le solde de la clé d'API de la plateforme. Les deux domaines d'identifiants et de routage sont strictement distincts et ne basculent jamais l'un vers l'autre. +- **Importation explicite uniquement :** OpenCodeX détecte si DeepSeek Harness est installé et connecté (`~/.dsh/.credentials.yaml`), mais ne copiera, n'importera ni n'utilisera jamais automatiquement le jeton sans une action explicite de l'utilisateur (`ocx login dsh-account` ou clic sur Importer dans le tableau de bord web). +- **Magasin d'identifiants DSH en lecture seule :** OpenCodeX traite `~/.dsh/.credentials.yaml` comme strictement en lecture seule. OpenCodeX n'écrit, ne modifie, ne supprime ni ne change jamais les permissions des fichiers appartenant à DeepSeek Harness. +- **Copie d'identifiant et déconnexion isolées :** Lors de l'importation, OpenCodeX valide le jeton auprès du point de terminaison d'identité officiel de DeepSeek (`/auth-api/v0/users/current`) et enregistre sa propre copie dans le magasin d'authentification OpenCodeX. Se déconnecter ou supprimer le compte dans OpenCodeX supprime uniquement la copie locale d'OpenCodeX ; cela ne déclenche jamais de déconnexion de session distante et ne vous déconnecte pas de DeepSeek Harness Desktop. +- **Pas de rotation automatique en arrière-plan et réimportation en cas de 401 :** Les autorisations de session DSH ne disposent pas d'un point de terminaison d'actualisation automatique en arrière-plan. Si le serveur distant renvoie HTTP 401 (`ACCOUNT_TOKEN_INVALID`), OpenCodeX marque le compte comme `needsReauth` et interrompt le routage. Pour rétablir l'accès, l'utilisateur doit réimporter explicitement via `ocx login dsh-account`. +- **Avertissement relatif aux tiers :** OpenCodeX ne revendique aucune approbation ni autorisation officielle de DeepSeek pour la réutilisation des autorisations de session par des clients tiers. + ## 3. Catalogue des clés API -opencodex fournit 104 préréglages intégrés : 85 à clé, 15 OAuth, trois locaux et un préréglage par défaut de +opencodex fournit 106 préréglages intégrés : 85 à clé, 17 OAuth, trois locaux et un préréglage par défaut de transfert ChatGPT. Dans le tableau de bord, le sélecteur **Ajouter un fournisseur** ouvre le tableau de bord du fournisseur à clé, valide la clé et l'enregistre ; la validation dépend du fournisseur. Parmi les entrées notables : @@ -351,7 +365,7 @@ promotionnels de Cline ne sont accessibles que dans l'IDE ou la CLI Cline, pas p | Z.AI (GLM Coding) | `https://api.z.ai/api/coding/paas/v4` | | Zhipu AI (BigModel) | `https://open.bigmodel.cn/api/paas/v4` | | [BigModel Coding Plan — Responses (liste statique)](/guides/providers/#bigmodel-coding-plan-over-responses) | `https://open.bigmodel.cn/api/v1` | -| Qwen Cloud | Forfait à jetons (par défaut) : `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` · Facturation à l'usage : `https://dashscope.aliyuncs.com/compatible-mode/v1` · ou personnalisé | +| Qwen Cloud | Forfait à jetons (par défaut) : `https://token-plan.maas.qwencloudapi.com/compatible-mode/v1` · Facturation à l'usage : `https://dashscope.aliyuncs.com/compatible-mode/v1` · ou personnalisé | | Tencent Cloud Coding Plan | `https://api.lkeap.cloud.tencent.com/coding/v3` | | SiliconFlow | `https://api.siliconflow.cn/v1` | | Volcengine Ark · Coding Plan · Agent Plan | `https://ark.cn-beijing.volces.com/api/v3` · `https://ark.cn-beijing.volces.com/api/coding/v3` · `https://ark.cn-beijing.volces.com/api/plan/v3` | @@ -371,32 +385,20 @@ par Sionic, `deepseek/deepseek-v4.1-flash-ultrafast` et `z-ai/glm-5.3-flash-ultr sont affichés en premier. Créez une clé dans le [tableau de bord OpenGateway](https://opengateway.ai/api-keys), puis lancez `ocx provider add opengateway` ou sélectionnez **OpenGateway** dans le tableau de bord. Les requêtes chat utilisent la clé Bearer configurée ; la liste publique ne valide pas cette clé. +Sur le protocole Responses, OpenGateway refuse les appels d’outils personnalisés natifs rejoués ; le préréglage convertit donc les outils personnalisés Codex, comme `exec` en mode code, en outils de fonction avant l’envoi. +Les tableaux de contenu des résultats d’outils sont envoyés sous forme de texte, et les images sont conservées après le groupe de résultats d’outils associé. Les parties de résultat non prises en charge, y compris les refus et les contenus opaques, sont rejetées explicitement par une erreur d’entrée client avant l’envoi. +Le niveau de détail d’image `original` est converti en `high`, que la passerelle accepte ; les octets de l’image restent inchangés. +La route Responses native vérifie aussi les complétions vides en texte seul avant de les présenter comme un succès. +Un budget de sortie épuisé produit `incomplete` avec `max_output_tokens` ; le proxy n’augmente pas le budget demandé. Une complétion vide qui peut être relancée sans risque et qui dispose encore de budget peut être retentée une seule fois, dans la limite d’envois existante. `emptyCompletionRetry: false` explicite ou `OCX_EMPTY_COMPLETION_RETRY=0` désactive cette nouvelle tentative. Le texte, les refus, l’activité d’outils et les événements de flux inconnus empêchent la relance et conservent le flux d’origine. Cette protection ne modifie ni les autres fournisseurs ni leurs valeurs par défaut de nouvelle tentative. +La relance exige `store: false` explicite, une entrée complète et uniquement des outils exécutés par le client ; un outil hébergé comme `web_search` empêche la relance automatique même si aucun événement d’outil n’a été observé. + +**OpenCode Zen** (`opencode-zen`) utilise `https://opencode.ai/zen/v1` avec une clé API obtenue sur [opencode.ai/auth](https://opencode.ai/auth). Les modèles gratuits peuvent subir une limite courte d’environ 15–20 requêtes/minute (mesure communautaire ; OpenCode ne publie pas de RPM). Si une réponse 429 de limitation omet `Retry-After`, opencodex ajoute des indications et un `Retry-After` synthétique ; les en-têtes valides en amont restent prioritaires. Les tentatives avec la même clé restent facultatives via [`retryOn429`](/fr/reference/configuration/). + +Sur les routes officielles `https://opencode.ai/zen/v1` et `https://opencode.ai/inference/v1` du fournisseur canonique `opencode-zen` avec authentification par clé API, Gemini utilise automatiquement l’API native Google en mode `googleMode: ai-studio` (par défaut). Les identifiants Zen sont conservés, y compris Flash sans suffixe `-tiered`. Claude utilise automatiquement Anthropic Messages, quel que soit `googleMode`. Au chargement des configurations enregistrées, seules les anciennes surcharges Chat/Responses admissibles de ces modèles natifs sont ignorées, ainsi que les préférences admissibles de l’outil hébergé `image_generation` propres à Responses. Les fournisseurs personnalisés, les routes sans authentification par clé et les autres préférences restent inchangés. + +**La prise en charge de `opencode-free` a été supprimée** : tous les modèles renvoient désormais `403 permission_denied` à cause du contrôle du niveau gratuit en amont chez OpenCode ([#7255](https://github.com/lidge-jun/opencodex/issues/7255)). Le chargement d’une configuration existante désactive en mémoire les entrées concernées et leurs références de modèles, avertit une fois par processus et ne modifie pas le fichier. Si ce fournisseur était le défaut, la route OpenAI OAuth standard devient le défaut. Utilisez `opencode-go`, `opencode-zen` avec clé ou un autre fournisseur. [Conditions OpenCode Zen](https://opencode.ai/docs/zen/). -**OpenCode Zen** (`opencode-zen`) et le préréglage sans clé **OpenCode Free** utilisent tous deux -`https://opencode.ai/zen/v1`. Sur cette passerelle, les modèles gratuits atteignent souvent une limite de -rafale sur une courte fenêtre, d'environ 15 à 20 requêtes par minute (mesure de la communauté ; OpenCode ne publie -pas de valeur RPM). Zen peut renvoyer des erreurs génériques 429 de limitation de débit sans en-têtes -`Retry-After` / `X-RateLimit-*`. Cette limite est distincte du quota sans clé pour application de bureau -annoncé par OpenCode (environ 200 requêtes Big Pickle ou vers des modèles gratuits toutes les 5 heures sur -`opencode-free`). Lorsque Zen omet `Retry-After` sur une telle réponse 429, opencodex ajoute à l'erreur client -des indications propres au fournisseur ainsi qu'un `Retry-After` synthétique ; un `Retry-After` reçu en amont -reste prioritaire. L'attente et la nouvelle tentative avec la même clé restent facultatives et s'activent avec -[`retryOn429`](/fr/reference/configuration/). - -**Le niveau sans clé `opencode-free` est actuellement fermé aux clients tiers.** Zen refuse toute requête -qui arrive sans en-tête `x-opencode-session` et répond avec le type d'erreur `MissingSessionID` et le message -« OpenCode's free tier can only be used in OpenCode ». Le contrôle porte uniquement sur la présence de -l'en-tête : un proxy pourrait donc le franchir en inventant une valeur, ce que opencodex ne fait pas. -Fabriquer un identifiant de session et un User-Agent versionné `opencode/` revient à se déclarer -client OpenCode, alors qu'OpenCode ne publie aucun contrat d'intégration tierce pour ce niveau sans clé ; -une réponse HTTP 200 obtenue ainsi est un contrôle d'admission contourné, pas une autorisation. opencodex -signale donc la restriction au lieu de la contourner : une requête vers `opencode-free` renvoie une erreur -qui explique le blocage en amont. - -La voie prise en charge vers les mêmes modèles est le fournisseur **`opencode-zen`** avec une clé d'API -OpenCode Zen obtenue sur [opencode.ai/auth](https://opencode.ai/auth). Si OpenCode publie plus tard un accès -tiers pour le niveau sans clé, opencodex pourra le suivre ; d'ici là, le préréglage sert à documenter la -restriction. Conditions en amont : [opencode.ai/docs/zen](https://opencode.ai/docs/zen/). +Les affectations et valeurs par défaut valides de Claude Desktop sont conservées. Les règles dont la liste de modèles sources autorisés devient vide sont désactivées, ainsi que la synchronisation native des sous-agents si son modèle d’injection est supprimé. Les sauvegardes suivantes ne rétablissent pas les références supprimées et préservent les modifications simultanées valides. La plupart utilisent l'adaptateur `openai-chat` avec une clé Bearer ; les préréglages compatibles Anthropic, comme **Xiaomi MiMo** (`xiaomi`), utilisent l'adaptateur `anthropic` (`x-api-key`). Xiaomi propose @@ -622,6 +624,9 @@ maintenir leur visibilité même lorsque les catalogues en direct ne sont pas en | Connexion Codex (Pool ou Direct) | `gpt-5.6-*` | 372 000 | | OpenAI (clé API) | `openai-apikey/gpt-5.6-*` plus `*-pro` | 1 050 000 (entrée maximale de 922 000) | | OpenRouter | `openrouter/openai/gpt-5.6-sol`, `openrouter/openai/gpt-5.6-terra`, `openrouter/openai/gpt-5.6-luna` | 1 050 000 | +| Codex | `gpt-6.1-terra` | 500,000 / 872,000 | +| OpenAI API | `openai-apikey/gpt-6.1-terra` | 1,050,000 (128,000 max output) | +| OpenRouter | `openrouter/openai/gpt-6.1-terra` | 1,050,000 | | Cursor | `cursor/gpt-5.6-sol`, `cursor/gpt-5.6-terra`, `cursor/gpt-5.6-luna` | 1 000 000 | Les entrées natives GPT-5.6 conservent les niveaux de raisonnement fixés en amont — Luna propose par exemple @@ -642,7 +647,7 @@ reste une passerelle à clé ou jeton d'abonnement sur son point de terminaison **Cloudflare AI Gateway** exige que les identifiants de votre compte et de votre passerelle figurent dans l'URL. Copilot présente un catalogue qui utilise plusieurs protocoles : ces modèles (`gpt-5.3-codex`, `gpt-5.4`, -`gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`) rejettent +`gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6.1-sol`, `gpt-6.1-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`) rejettent `/chat/completions` pour le trafic d'agent. opencodex route donc ces modèles sur l'API Responses par défaut, tandis que tous les autres modèles Copilot restent sur Chat Completions. L'ordre de priorité est le suivant : verrouillage explicite du protocole → entrée [`modelAdapters`](/fr/reference/configuration/providers/) définie @@ -721,4 +726,4 @@ au fournisseur. Les fournisseurs qui disposent d'une sonde en direct sont OpenAI/Codex, Anthropic, xAI, Cursor, Kimi, Google Antigravity, OpenRouter, DeepSeek, ClinePass, Z.AI, MiniMax, Moonshot, Venice, Synthetic, DeepInfra, -Neuralwatt, Command Code, ainsi que tout fournisseur personnalisé reposant sur a6api. +Neuralwatt, Command Code, ainsi que tout fournisseur personnalisé reposant sur a6api ou Abliteration. diff --git a/docs-site/src/content/docs/fr/guides/remote-link.md b/docs-site/src/content/docs/fr/guides/remote-link.md index 9503b02ad37..82d715db467 100644 --- a/docs-site/src/content/docs/fr/guides/remote-link.md +++ b/docs-site/src/content/docs/fr/guides/remote-link.md @@ -55,7 +55,7 @@ Le rôle **Child** exige également qu’OpenCodex fonctionne en mode autonome s - **Connected** signifie que le tunnel SSH est prêt et que Child peut utiliser la liaison Home. - **Reconnecting** signifie que le tunnel est réessayé. Les requêtes peuvent temporairement renvoyer `503` avec `Retry-After`. Sur un Child connecté depuis son propre tableau de bord, une requête attend d’abord jusqu’à 15 secondes le retour du tunnel. -- **Failed** signifie que la liaison nécessite une intervention. Vérifiez l’authentification SSH, la clé d’hôte confirmée, la redirection ou le délai indiqué. Le côté qui ouvre le tunnel SSH réessaie de lui-même après une mise en veille, une panne ou un redémarrage (Home pour un Child qu’il a ajouté, le Child pour une liaison qu’il a rejointe depuis son propre tableau de bord) : environ une fois par minute après un délai dépassé ou une erreur de redirection, et toutes les cinq minutes après une erreur d’authentification. Une clé d’hôte modifiée n’est jamais réessayée. Pour réessayer tout de suite, choisissez **Retry** dans le tableau de bord du Home ou exécutez `ocx link reconnect --link-id ` sur le Home. Le tunnel de cette liaison redémarre sans redémarrer OpenCodex. +- **Failed** signifie que la liaison nécessite une intervention. Vérifiez l’authentification SSH, la clé d’hôte confirmée, la redirection ou le délai indiqué. Le côté qui ouvre le tunnel SSH réessaie de lui-même après une mise en veille, une panne ou un redémarrage (Home pour un Child qu’il a ajouté, le Child pour une liaison qu’il a rejointe depuis son propre tableau de bord) : une minute après un délai dépassé ou une erreur de redirection, puis avec une attente qui double après chaque nouvel échec, jusqu’à cinq minutes, et toutes les cinq minutes après une erreur d’authentification. Une clé d’hôte modifiée n’est jamais réessayée. Pour réessayer tout de suite, choisissez **Retry** dans le tableau de bord du Home ou exécutez `ocx link reconnect --link-id ` sur le Home. Le tunnel de cette liaison redémarre sans redémarrer OpenCodex. Une liaison en échec ne bascule pas silencieusement vers un fournisseur local. diff --git a/docs-site/src/content/docs/fr/guides/sidecars.md b/docs-site/src/content/docs/fr/guides/sidecars.md index 1fcc91cfec6..ee37d9ef37b 100644 --- a/docs-site/src/content/docs/fr/guides/sidecars.md +++ b/docs-site/src/content/docs/fr/guides/sidecars.md @@ -88,9 +88,12 @@ les échecs de génération postérieurs à l'envoi des en-têtes sont transmis Lorsqu'un modèle routé figure dans le `noVisionModels` de son fournisseur — ou est déclaré texte seul pour ce modèle via `modelInputModalities` — et qu'une requête porte une image, opencodex décrit chaque image **avant** l'appel principal et la remplace par du texte, à condition qu'un plan de sidecar vision soit disponible. Sans plan disponible, l'image brute -est supprimée au lieu d'être transmise à un backend texte seul. Le catalogue de modèles annonce l'entrée image pour chaque -modèle couvert par le sidecar. Les combos annoncent l'entrée image seulement lorsque chaque membre accepte les images, -nativement ou via un sidecar, et que le paramètre `imageInput` du combo n'est pas désactivé, afin que des clients comme +est supprimée au lieu d'être transmise à un backend texte seul. Avec `visionSidecar.enabled: false`, le catalogue de modèles +n'ajoute pas d'entrée image fournie par le sidecar aux modèles texte seuls, y compris les entrées personnalisées et les +membres des combos. La prise en charge native des images et les modalités image explicitement configurées sont conservées. +Si `enabled` est omis, l'annonce des capacités du sidecar reste activée ; la présence dans le catalogue ne garantit pas +que les identifiants ou la capacité du sidecar sont disponibles. Les combos annoncent l'entrée image seulement lorsque +chaque membre accepte les images, nativement ou via un sidecar activé, et que le paramètre `imageInput` du combo n'est pas désactivé, afin que des clients comme l'application Codex autorisent les pièces jointes au lieu de les bloquer avant l'exécution du sidecar. Lorsque `visionSidecar.model` est absent ou vide, le chemin d'exécution OpenAI, le tableau de bord et l'API de gestion utilisent le modèle de repli `gpt-5.6-luna`. Au démarrage, une ancienne valeur `gpt-5.6-luna` explicitement enregistrée @@ -164,6 +167,8 @@ le délai d'attente et la limite précédemment choisis. les clés omises inchangées. `timeoutMs` utilise les limites entières de l'environnement d'exécution (1–2147483647 ms). +Un changement de l'état d'activation effectif de Vision via `PUT /api/sidecar-settings` actualise le catalogue et le cache gérés après l'enregistrement ; `catalogRefreshPending=true` signale que cette actualisation n'a pas abouti et ne constitue pas un accusé de réception du client. + La carte du service auxiliaire de recherche web reprend la même forme de contrôle : la première ligne du sélecteur de modèle est **Désactivé (Off)**. La désactivation arrête l'interception de `web_search` par OpenCodex et l'intégration Codex écrit `web_search = "disabled"` dans diff --git a/docs-site/src/content/docs/fr/guides/sub-agent-surface.md b/docs-site/src/content/docs/fr/guides/sub-agent-surface.md index a5b172a4e48..be3e9d21f82 100644 --- a/docs-site/src/content/docs/fr/guides/sub-agent-surface.md +++ b/docs-site/src/content/docs/fr/guides/sub-agent-surface.md @@ -46,6 +46,42 @@ Pour une liste v2, l'éligibilité a trois états : une entrée estampillée `" sans champ `multi_agent_version` est admissible. Une valeur explicite `"v1"` est exclue, car elle indique que le modèle appartient à l’autre surface de collaboration. +## Modèles annoncés par `spawn_agent` + +Codex liste au plus cinq modèles dans la description de l'outil `spawn_agent`, et il les choisit lui-même : il trie les lignes du catalogue visibles dans le sélecteur par `priority` croissante et garde les cinq premières. La liste de la page **Subagents** (`subagentModels`) n'agit sur cette sélection qu'en donnant à ses modèles les priorités les plus basses, dans l'ordre de la liste (`0` à `4` sans sélecteurs de compte). Elle ne limite pas la sélection à ces modèles. + +Si vous mettez en avant un à quatre modèles, Codex complète les emplacements restants avec les lignes suivantes par priorité. Vous n'avez pas choisi ces lignes, et le tableau de bord n'indique pas lesquelles sont retenues. Elles dépendent du catalogue : + +| Catalogue | Lignes qui complètent les emplacements restants | +| --- | --- | +| Sans sélecteurs de compte Codex | Les autres lignes routées `provider/model`. Elles partagent toutes la priorité `5` et apparaissent donc par nom de fournisseur, puis par identifiant de modèle, dans l'ordre alphabétique. Les lignes GPT natives non mises en avant ont une priorité d'au moins `100` plus le nombre d'identifiants mis en avant ; elles viennent donc après. | +| Avec sélecteurs de compte Codex | Les lignes natives qualifiées par compte (`/`), placées juste après les lignes mises en avant. Les lignes routées non mises en avant passent à la priorité `1005` et viennent après elles. | + +Par exemple, sans sélecteurs de compte et avec cette liste : + +```json +{ "subagentModels": ["xai/grok-4.5", "anthropic/claude-sonnet-5"] } +``` + +| Emplacement | Modèle | Priorité | Raison | +| ---: | --- | ---: | --- | +| 1 | `xai/grok-4.5` | `0` | Première entrée de la liste | +| 2 | `anthropic/claude-sonnet-5` | `1` | Deuxième entrée de la liste | +| 3 | `deepseek/deepseek-v4-flash` | `5` | Première ligne routée restante dans l'ordre alphabétique | +| 4 | `google/gemini-3-pro` | `5` | Ligne routée suivante | +| 5 | `moonshot/kimi-k3` | `5` | Ligne routée suivante | +| 6+ | `zai/glm-5.2`, puis les lignes GPT natives | `5`, puis `102` | Hors des cinq | + +Avec une liste vide, opencodex ne fixe aucune priorité et les cinq suivent les priorités normales du catalogue. + +Les emplacements complétés sont des annonces ordinaires : l'agent principal peut choisir l'un de ces modèles. La liste n'est pas non plus une liste d'autorisation : Codex accepte un modèle du catalogue hors des cinq lorsque l'agent principal le nomme exactement. Pour contrôler ce que Codex annonce : + +- Mettez cinq modèles en avant. Une liste complète ne laisse aucun emplacement à compléter. +- Désactivez un modèle que vous ne voulez jamais voir proposé, ou retirez-le des `selectedModels` de son fournisseur. Un modèle désactivé ou non sélectionné quitte le sélecteur et ne peut pas compléter un emplacement. +- `modelPickerOrder` modifie les priorités que Codex utilise pour trier. Une liste uniquement routée déplace les lignes listées non mises en avant à `1000` et au-delà ; les lignes routées non listées complètent donc les emplacements en premier. Une liste qui contient un identifiant natif nu ordonne tout le sélecteur, lignes mises en avant comprises : les lignes listées passent en premier et occupent les emplacements annoncés avant toute ligne non listée. Voir [Model ordering](/guides/model-ordering/). + +En v2, Codex écarte les lignes épinglées `"disabled"` avant de prendre les cinq ; le reste fonctionne de la même façon. La liste des consignes v2 d'opencodex (`{{roster}}`) ne nomme que les modèles configurés présents parmi les cinq : les modèles de complément apparaissent dans la description de l'outil Codex, jamais dans les consignes d'opencodex. Codex lit le catalogue au démarrage d'une session ; une modification de la liste s'applique donc aux nouvelles sessions. + ## Modèle et efforts de délégation La **délégation de sous-agent** du tableau de bord contrôle trois paramètres associés : @@ -239,6 +275,10 @@ un décompte partiel positif avant de passer un dépassement de modèle ou d'eff Il peut être masqué par le sélecteur, en dehors de la limite d'affichage de cinq modèles, absent du catalogue ou épinglé à la v1. Une valeur de surface `"v2"`, `null` ou absente est admissible ; une valeur explicite `"v1"` ne l’est pas. +### Pourquoi `spawn_agent` liste-t-il des modèles que je n'ai pas mis en avant ? + +Codex remplit jusqu'à cinq emplacements annoncés à partir du catalogue, par priorité. Avec moins de cinq modèles mis en avant, les lignes suivantes occupent les emplacements restants ; voir la section « Modèles annoncés par `spawn_agent` » ci-dessus. + ### Les changements de mode affectent-ils les sessions en cours ? Non. Démarrez une nouvelle session Codex après avoir changé de mode. Si un hôte App de longue durée affiche toujours des informations obsolètes diff --git a/docs-site/src/content/docs/fr/guides/subagent-v1-default.md b/docs-site/src/content/docs/fr/guides/subagent-v1-default.md index cafcd8b6a32..d93aa422837 100644 --- a/docs-site/src/content/docs/fr/guides/subagent-v1-default.md +++ b/docs-site/src/content/docs/fr/guides/subagent-v1-default.md @@ -148,3 +148,14 @@ ocx v2 mode v1 La CLI ne demande pas de confirmation. C'est le même paramètre : choisissez-le en tenant compte de ce que décrit cette page. + +## Limite de sous-agents simultanés + +Dans **Models → Model settings**, le réglage de limite des sous-agents définit le nombre maximal +de sous-agents simultanés, sans compter l'agent principal. Il reste disponible en v1, base et v2, +y compris en mode hybride lorsque le drapeau v2 global est désactivé. Le choix du mode est +indépendant de cette limite. + +Augmenter la limite autorise davantage de sous-agents, sans les créer automatiquement ni garantir +une exécution plus rapide. Lancez une nouvelle session Codex après modification. Si un hôte App +de longue durée utilise encore l'ancienne limite, redémarrez cet hôte. diff --git a/docs-site/src/content/docs/fr/reference/cli.md b/docs-site/src/content/docs/fr/reference/cli.md index 26211aae04d..2563cdc0de9 100644 --- a/docs-site/src/content/docs/fr/reference/cli.md +++ b/docs-site/src/content/docs/fr/reference/cli.md @@ -17,7 +17,7 @@ Exécutez `ocx help` (ou `ocx --help` / `ocx -h`) pour afficher l’aide génér Les commandes de gestion communiquent avec l’API de gestion du proxy actif. Elles s’appuient sur le port d’exécution enregistré et sur des contrôles d’identité, plutôt que sur un second chemin de configuration. Un proxy arrêté ou inaccessible est représenté par une réponse HTTP 503 et entraîne un code de sortie CLI non nul. Les commandes explicitement documentées comme des opérations de configuration hors ligne peuvent, quant à elles, valider et modifier le fichier de configuration sans proxy actif. -`ocx system codex-cli-update check` ne nécessite aucun proxy actif et n’interroge aucun registre de paquets. La commande inspecte, dans des limites strictes, les métadonnées de provenance du candidat d’installation configuré, notamment l’emplacement expurgé de l’exécutable et les preuves de propriété. Le contexte de confiance du lanceur publié authentifie uniquement cet instantané du candidat, et non l’exécution réussie de Codex. Comme cette commande ponctuelle n’exécute jamais Codex, les candidats issus de l’environnement ou de l’état persistant restent purement informatifs (`managed: false`, normalement `selection_unattested`) et `selectionAttested` reste `false`. La sortie JSON contient `candidateAvailable`, `candidateVersion`, `candidateSource` et `selectionAttested: false`. Une exécution directe via Bun ou depuis les sources ne fournit pas la preuve du lanceur, ignore les candidats issus de l’environnement ou de l’état persistant et peut signaler `candidate_unavailable` sous POSIX ou `windows_inspection_deferred` sous Windows. Sous Windows, cette première étape n’effectue aucune E/S de système de fichiers sur les chemins du candidat ou de configuration. Seul un candidat d’environnement absolu capturé par le lanceur de confiance peut recevoir une étiquette lexicale de bundle d’application ou de gestionnaire de versions ; tous les autres candidats Windows échouent de manière fermée. La commande n’installe ni ne répare de logiciel, n’exécute ni Codex ni npm, ne contrôle aucun processus actif et n’écrit aucun état de configuration ou de cache. +`check` ne consulte aucun registre et n’exécute pas Codex. Sur POSIX, `selectionAttested` peut être `true` si le résolveur choisit le même chemin canonique que le candidat d’environnement capturé par le lanceur publié. Un manifeste npm-global lié peut alors établir `managed: true` s’il prouve la propriété et, lorsqu’une version candidate est signalée, si elle correspond à celle du manifeste ; les candidats persistants restent non attestés. Windows diffère l’inspection sans lire les fichiers du candidat ni la configuration. Une exécution directe via Bun ou les sources n’a pas de preuve du lanceur. `plan` consulte le registre officiel avec une configuration, un cache et des journaux npm temporaires isolés, et refuse les sessions actives ou une observation incomplète des processus. Il n’installe rien et n’envoie aucun signal. Voir [les commandes et motifs de refus complets (en anglais)](/reference/cli/agents/#read-only-codex-cli-update-plan). Pour observer une installation Windows x64, consultez [`attest`](/fr/reference/cli/agents/). Sans chemins explicites, la commande observe le candidat sélectionné identifié par l’instantané du lanceur lié à la preuve ; aucune autorité de mise à jour ni sélection du runtime n’est attestée. diff --git a/docs-site/src/content/docs/fr/reference/cli/agents.md b/docs-site/src/content/docs/fr/reference/cli/agents.md index 03019ca987d..1337a76d0ce 100644 --- a/docs-site/src/content/docs/fr/reference/cli/agents.md +++ b/docs-site/src/content/docs/fr/reference/cli/agents.md @@ -273,7 +273,9 @@ le CLI, l’API, et le GUI utilisent les mêmes octets. Gérez les paramètres d'exécution sans tête, le démarrage, la synchronisation, les diagnostics et les mises à jour. -`ocx system codex-restart --yes` redémarre les serveurs d'application Codex et quitte puis relance entièrement l'application Codex Desktop, via le même module que `ocx sync --restart-codex`. Lorsque le proxy lui-même s'exécute dans l'application Codex, la commande refuse avec un message actionnable au lieu de promettre un transfert qu'elle ne peut pas mener à bien. +`ocx system codex-restart --yes` redémarre les serveurs d'application Codex via le même module que `ocx sync --restart-codex`. Elle quitte et relance l'application Codex Desktop si celle-ci a démarré avant l'écriture du catalogue de modèles actuel ou si cette requête a modifié sa liaison `model_catalog_json`, car le sélecteur de modèles est construit au lancement. Sinon l'application reste ouverte, relance son propre app-server et conserve les brouillons non envoyés et les approbations en attente. Si l'état de l'application ou du catalogue est illisible, elle effectue le redémarrage complet. Lorsque le proxy lui-même s'exécute dans l'application Codex, le redémarrage Desktop refuse avec un message actionnable au lieu de promettre un transfert qu'il ne peut pas mener à bien. + +Une liaison `model_catalog_json` modifiée par cette requête impose également le redémarrage Desktop, même si le processus a démarré après l’écriture du fichier du catalogue. ```bash ocx system settings --stream-mode eager-relay @@ -285,7 +287,7 @@ ocx system settings --stream-mode eager-relay ocx system codex-cli-update check --json ``` -`check` n’interroge aucun registre de paquets et inspecte, dans des limites strictes, les éléments de provenance du candidat d’installation configuré, notamment l’emplacement expurgé de l’exécutable et les preuves de propriété. Le contexte de confiance du lanceur publié authentifie uniquement cet instantané du candidat, et non l’exécution réussie de Codex. Comme cette commande ponctuelle n’exécute jamais Codex, les candidats issus de l’environnement ou de l’état persistant restent purement informatifs (`managed: false`, normalement `selection_unattested`) et `selectionAttested` reste `false`. La sortie JSON contient `candidateAvailable`, `candidateVersion`, `candidateSource` et `selectionAttested: false`. Une exécution directe via Bun ou depuis les sources ne fournit pas la preuve du lanceur, ignore les candidats issus de l’environnement ou de l’état persistant et peut signaler `candidate_unavailable` sur les systèmes POSIX. Sous Windows, cette première étape n’effectue aucune E/S de système de fichiers sur les chemins du candidat ou de configuration. Seul un candidat d’environnement absolu capturé par le lanceur de confiance peut recevoir une étiquette lexicale de bundle d’application ou de gestionnaire de versions ; tous les autres candidats Windows échouent de manière fermée. Comme cette étape ne consulte jamais l’état persistant, une exécution Windows pour laquelle aucun candidat d’environnement n’a été capturé signale `windows_inspection_deferred` plutôt que `candidate_unavailable` : la commande ne peut pas observer si une CLI Codex est installée, elle signale donc le report de l’inspection au lieu d’affirmer qu’aucun candidat n’existe. La commande n’exécute ni Codex ni aucun gestionnaire de paquets, ne répare aucun shim, n’écrit ni dans la configuration ni dans le cache, n’arrête aucun processus et n’installe rien. Les candidats intégrés à une application, issus d’un gestionnaire de versions reconnu, autonomes mais non vérifiés, ou associés à un état de shim ambigu sont signalés comme non gérés ou inconnus et ne sont jamais classés comme gérés. +`check` ne consulte aucun registre et n’exécute pas Codex. Sur POSIX, `selectionAttested` peut être `true` si le résolveur choisit le même chemin canonique que le candidat d’environnement capturé par le lanceur publié. Un manifeste npm-global lié peut alors établir `managed: true` s’il prouve la propriété et, lorsqu’une version candidate est signalée, si elle correspond à celle du manifeste ; les candidats persistants restent non attestés. Windows diffère l’inspection sans lire les fichiers du candidat ni la configuration. Une exécution directe via Bun ou les sources n’a pas de preuve du lanceur. `plan` consulte le registre officiel avec une configuration, un cache et des journaux npm temporaires isolés, et refuse les sessions actives ou une observation incomplète des processus. Il n’installe rien et n’envoie aucun signal. Voir [les commandes et motifs de refus complets (en anglais)](/reference/cli/agents/#read-only-codex-cli-update-plan). Sous Windows, une commande simple capturée comme `CODEX_CLI_PATH=codex`, un chemin distant ou un chemin de périphérique produit plutôt `candidate_path_unavailable`. Le candidat a été capturé, mais son chemin ne convient pas à cette inspection. diff --git a/docs-site/src/content/docs/fr/reference/cli/lifecycle.md b/docs-site/src/content/docs/fr/reference/cli/lifecycle.md index bf590b687a5..d5e247ccad0 100644 --- a/docs-site/src/content/docs/fr/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/fr/reference/cli/lifecycle.md @@ -68,7 +68,7 @@ Il s'agit d'un réétiquetage large et destructif : chaque fil contenant un mess ### `ocx recover-history --ocx-compaction --yes` -Réparez l'historique d'une tâche compactée par un fournisseur routé avant de la reprendre avec Codex natif. La commande sélectionne exactement une tâche par UUID, enregistre d'abord une sauvegarde privée octet par octet, puis convertit uniquement l'état de compaction `ocx1:` propre à OpenCodeX en résumé ordinaire relisible par Codex natif. Le contenu chiffré natif et les autres tâches restent inchangés. Fermez la tâche sélectionnée avant d'exécuter la commande ; toute modification simultanée du rollout interrompt la récupération sans remplacer le fichier. +Réparez l'historique d'une tâche compactée par un fournisseur routé avant de la reprendre avec Codex natif. La commande sélectionne exactement une tâche par UUID, enregistre d'abord une sauvegarde privée octet par octet, puis convertit uniquement l'état de compaction `ocx1:`/`ocx2:` propre à OpenCodeX en résumé ordinaire relisible par Codex natif. Le contenu chiffré natif et les autres tâches restent inchangés. Fermez la tâche sélectionnée avant d'exécuter la commande ; toute modification simultanée du rollout interrompt la récupération sans remplacer le fichier. ### `ocx uninstall` · `ocx remove` @@ -141,7 +141,7 @@ Vérifie l’identité du proxy actif. La sortie destinée aux utilisateurs indi ### `ocx ready [--json] [--wait [--timeout ]]` -Vérifie l’état de préparation après synchronisation au moyen du point de terminaison non authentifié `GET /readyz`. Il renvoie `200` lorsque le service est prêt, ou `503` avec `Retry-After: 1` pour les états `pending` et terminal `failed`. Son identité HTTP expurgée est `{service, version, uptime, pid, port, status, protocol, minimumClientProtocol, managementUrl}`. `protocol` est la version courante du protocole distant du hub, `minimumClientProtocol` la plus ancienne version cliente compatible et `managementUrl` l’origine canonique de gestion visible par le navigateur. Les anciens proxys dépourvus de `/readyz` échouent de manière sûre avec l’état `unreachable` ; `/healthz` mesure la disponibilité du processus, et non son état de préparation. +Vérifie l’état de préparation après synchronisation au moyen du point de terminaison non authentifié `GET /readyz`. Il renvoie `200` lorsque le service est prêt, ou `503` avec `Retry-After: 1` pour les états `pending` et terminal `failed`. Son identité HTTP expurgée est `{service, version, uptime, pid, port, status, protocol, minimumClientProtocol, managementUrl, features}`. `protocol` est la version courante du protocole distant du hub, `minimumClientProtocol` la plus ancienne version cliente compatible et `managementUrl` l’origine canonique de gestion visible par le navigateur. Les anciens proxys dépourvus de `/readyz` échouent de manière sûre avec l’état `unreachable` ; `/healthz` mesure la disponibilité du processus, et non son état de préparation. Par défaut, la commande effectue une seule sonde. Avec `--wait`, elle interroge le service jusqu’à ce qu’il soit prêt ou jusqu’à l’expiration du délai, mais s’arrête immédiatement si elle observe l’état terminal `failed`. Le délai par défaut est de 45 secondes. `--timeout ` exige `--wait` et accepte un entier positif compris entre 1 et 300. La sortie JSON de la CLI est `{ready, status, pid, port}`, où `status` vaut `ready`, `pending`, `failed` ou `unreachable`. Les codes de sortie sont 0 si le service est prêt ; 1 s’il n’est pas prêt, reste en attente, échoue, dépasse le délai ou est inaccessible ; et 64 si les arguments sont invalides. @@ -155,6 +155,10 @@ La section **OAuth reliability** indique si le stockage des identifiants est acc ### `ocx sync [--restart-codex] [--restart-app-server-only]` +> **Enfant Remote Link :** `ocx sync` applique aussi les réglages Codex du Home et, si le runtime client a démarré avant cette application, le redémarre même sans option de redémarrage. Si le redémarrage échoue, l'ancien runtime continue de servir et la commande se termine avec un code non nul. Les champs correspondants de `ocx status --json` et `ocx link status --json` sont décrits dans [Remote Link apply parity](/reference/cli/lifecycle/#remote-link-apply-parity) (en anglais). + +Un redémarrage explicitement demandé a également lieu après une modification de `config.toml` validée et enregistrée, même si les octets du catalogue et du cache restent identiques. Une opération sans changement de configuration, un refus, une validation préalable ou une écriture annulée ne le déclenche pas. Sans option de redémarrage, sync ne redémarre jamais Codex automatiquement. + Récupère la liste active des modèles de chaque fournisseur configuré et réinjecte le catalogue fusionné dans Codex. Exécutez cette commande après l’ajout d’un fournisseur ou pour actualiser les modèles disponibles. Avant la découverte des fournisseurs ou le remplacement du catalogue et du cache, `ocx sync` vérifie que la configuration Codex gérée peut recevoir l’injection. Si cette validation refuse la configuration, la commande renvoie un code non nul, affiche la cause précise sur stderr et laisse le catalogue ainsi que le cache existants inchangés. `ocx restore back` effectue la même vérification préalable sans écriture avant de réactiver le routage. @@ -167,6 +171,8 @@ Si des processus Codex `app-server` de longue durée sont encore actifs, `ocx sy Lorsque la commande s’exécute depuis l’application Codex, le redémarrage est confié à un assistant détaché et cette session se termine avec l’application. +**Synchronisation épinglée.** `ocx sync --expect-link ` (enfant Remote Link) et `ocx sync --expect-hub-key ` (client hub) ne synchronisent que si cette machine est toujours connectée à ce lien ou avec cette clé. Sinon, rien n'est modifié, les fournisseurs locaux ne sont pas synchronisés, la commande quitte avec le code 5 et la dernière ligne de stderr est `{"phase":"child_link_mismatch"|"hub_client_mismatch","reason":...}`. Un drapeau mal formé ou répété quitte avec le code 2. Détails : [référence anglaise](/reference/cli/lifecycle/#sync-only-from-the-expected-home---expect-link-and---expect-hub-key). + ### `ocx sync-cache [--restart-codex] [--restart-app-server-only]` Invalide le cache local du sélecteur de modèles de Codex afin qu’il soit reconstruit à partir du catalogue opencodex actif. Le même avertissement concernant un `app-server` obsolète et les mêmes options de redémarrage que pour `ocx sync` s’appliquent. diff --git a/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md b/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md index 89d1e6773f6..25e82d8de21 100644 --- a/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md @@ -164,6 +164,15 @@ et la colonne plan/label recouvre le plan, l'e-mail masqué, l'étiquette et la ### `ocx account list [provider] [--json] [--all]` +Les comptes ChatGPT enregistrés dans le pool restent consultables sur les hôtes sans répertoire +Codex natif, y compris dans le tableau de bord et lors de l'actualisation des comptes. Cette consultation +ne crée pas de répertoire Codex et ne déconnecte pas le compte principal natif. Désactiver l'intégration +du client Codex ne désactive pas l'observation du quota d'une connexion native existante. Un chemin +`CODEX_HOME` explicite invalide continue de produire une erreur de configuration. Un stockage +`OPENCODEX_HOME` non sûr produit une erreur même si le répertoire Codex natif est absent. La résolution +du chemin Codex par défaut renvoie également une erreur s'il désigne un fichier, un lien symbolique +dont la cible n'existe pas, ou si son répertoire parent ne peut pas être traversé. + Sans fournisseur, répertorie le groupe de comptes Codex, les comptes OAuth et les groupes de clés API configurés. Les fournisseurs vides sont ignorés à moins que `--all` soit présent. Avec un fournisseur, répertorie uniquement cette famille d’informations d’identification. La sortie destinée aux utilisateurs utilise `PROVIDER TYPE ID PLAN/LABEL PRIORITY STATUS` ; une ligne Codex sélectionnée manuellement porte la mention diff --git a/docs-site/src/content/docs/fr/reference/configuration/advisor.md b/docs-site/src/content/docs/fr/reference/configuration/advisor.md index baf92fbb1db..9c48ca2b7fc 100644 --- a/docs-site/src/content/docs/fr/reference/configuration/advisor.md +++ b/docs-site/src/content/docs/fr/reference/configuration/advisor.md @@ -1,6 +1,6 @@ --- title: Conseiller -description: Le sidecar de consultation experte d'OpenCodex — un modèle expert configuré conseille les workers routés, avec les politiques manual et preflight. +description: Consultation indépendante avec les politiques manual, preflight et adaptive fondée sur des preuves. --- Le conseiller est un modèle expert indépendant qui examine la tâche du worker et renvoie des @@ -16,6 +16,8 @@ sidecar côté proxy invisible du client — même un worker qui ne spawn jamais ## Configuration +Sur la page **Advisor**, choisissez le modèle dans le catalogue. Le sélecteur conserve exactement les identifiants natifs, de fournisseur, de compte, personnalisés et de combo. Les modèles désactivés ou en attente de sélection initiale ne sont pas proposés. Un identifiant enregistré absent du catalogue reste visible avec un avertissement, jusqu’à son remplacement ou sa suppression. Un échec de chargement conserve la sélection et propose de réessayer. Choisissez un modèle capable de fournir des conseils textuels : le catalogue ne garantit pas la compatibilité des consultations. La CLI et l’API acceptent toujours les chaînes de modèle prises en charge par le routeur. + ```json { "advisor": { @@ -33,12 +35,13 @@ sidecar côté proxy invisible du client — même un worker qui ne spawn jamais | `enabled?` | `boolean` | `false` | Interrupteur principal. Désactivé : aucun comportement conseiller sur le chemin de requête. | | `model?` | `string` | — | Le modèle expert. Toute chaîne de modèle acceptée par le routeur : modèle natif seul (`gpt-6-astra`), `provider/model` explicite (`anthropic/claude-sonnet-4-6`, `xai/grok-...`) ou modèle natif qualifié par compte. Inter-fournisseurs entièrement pris en charge. | | `effort?` | `string` | `"max"` | Intensité de raisonnement de l'appel conseiller (`low`–`ultra`). | -| `policy?` | `"manual" \| "preflight"` | `"manual"` | Quand consulter le conseiller. | +| `policy?` | `"manual" \| "preflight" \| "adaptive"` | `"manual"` | Quand consulter le conseiller. | +| `maxConsultationsPerTask?` | `integer` | `3` | Budget adaptatif par tâche (1 à 3 tentatives, appels manuels compris). | | `timeoutMs?` | `number` | `120000` | Délai de la consultation en boucle locale. | | `contextSharingConsent?` | `"v1"` | absent | Consentement de l'opérateur pour envoyer le contexte de la tâche au fournisseur conseiller configuré. Seul `"v1"` est courant. Une valeur absente, périmée ou autre n'autorise aucun envoi. `enabled: true` n'est pas ce consentement. | Gérez-le via la page **Advisor** du tableau de bord ou -`ocx advisor status|on|off|set --model --effort --policy `. +`ocx advisor status|on|off|set --model --effort --policy `. Sans consentement courant, `ocx advisor on` n'active pas le partage inter-fournisseurs : il affiche cette divulgation et s'arrête. `ocx advisor on --ack-context-sharing` et `ocx advisor consent` enregistrent `v1`. `ocx advisor consent --revoke` retire le consentement et arrête immédiatement l'envoi. `ocx advisor set` n'accorde pas le consentement. La case du tableau de bord n'est pas précochée. @@ -105,7 +108,14 @@ codage, et une consultation ne change jamais le modèle principal de la session. - Les tours natifs OpenAI en passthrough (workers du pool ChatGPT) ne reçoivent pas l'outil synthétique ; le conseiller couvre les fournisseurs routés (traduits). La consultation preflight s'applique aux adaptateurs run-turn ; l'outil non. -- Pas de déclencheur adaptatif : pas de détection de blocage, d'analyse d'échecs répétés, de - niveaux d'escalade, de conseillers multiples ni de vote. `manual` et `preflight` seulement. +- Pas de détection sémantique de blocage, de revue finale, de niveaux d’escalade, de conseillers multiples ou de vote. - Le registre de déduplication preflight vit dans le processus ; après un redémarrage du proxy, une tâche en cours peut recevoir une tentative preflight de plus. + +## Consultation adaptative et activité + +`adaptive` ajoute à la consultation initiale une consultation lorsque la même vérification, dans le même répertoire, échoue, qu’une modification de fichier ultérieure réussit, puis qu’une nouvelle exécution échoue encore. Une réussite efface l’échec ; une vérification démarrée avant la modification ne compte pas comme une nouvelle tentative après réparation. Les étapes initiale et réparation ont des revendications distinctes. Une identité de conversation stable est requise pour l’automatisation. Les appels/résultats doivent être appariés avec un code de sortie explicite. Scripts complexes, lots, modifications shell et sorties ambiguës ne déclenchent rien. + +Configurez `ocx advisor set --policy adaptive --max-consultations 2`. Les tentatives manuelles et automatiques partagent un budget de 1 à 3 par tâche, réservé avant envoi dans `OPENCODEX_HOME/advisor-budget.sqlite`. Un redémarrage ou un changement de worker ne le remet pas à zéro. Seuls les condensats, compteurs et expirations sont conservés, pendant 24 heures après la première tentative (512 tâches au maximum). Si le budget est inaccessible ou épuisé, le worker continue. Les appels envoyés puis échoués ou annulés restent réservés car ils peuvent coûter. Sans identité stable, seuls les appels manuels limités par requête restent disponibles. + +La page Advisor et `ocx advisor activity --json` (`GET /api/advisor/activity`) montrent jusqu’à 100 entrées de métadonnées du processus sur 24 heures ; la page visible s’actualise toutes les cinq secondes. Elles incluent motif, état, modèles, durée, tokens d’entrée/sortie signalés et efforts demandé et traduit par l’adaptateur. Aucun texte, commande, conseil ou identifiant de conversation n’est conservé ; un redémarrage efface ces entrées. La traduction décrit les paramètres envoyés et ne garantit pas le raisonnement interne. Les données absentes restent inconnues ; adoption et coût exact ne sont pas déduits. diff --git a/docs-site/src/content/docs/fr/reference/configuration/providers.md b/docs-site/src/content/docs/fr/reference/configuration/providers.md index 442c597f0db..a46a4f89add 100644 --- a/docs-site/src/content/docs/fr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/fr/reference/configuration/providers.md @@ -12,6 +12,16 @@ En l’absence de fenêtre déclarée ou configurée, OpenCodex utilise les mét Cette estimation est désactivée par défaut : activez-la par fournisseur avec `inferContextWindow: true`. Sans ce réglage, une fenêtre absente utilise le Context cap activé, puis 128k. +## GPT-6.1 Terra + +[GPT-6.1 Terra](https://openai.com/index/introducing-gpt-6-1-terra/), sorti le 26 octobre 2026, succède à GPT-5.6 Terra. `gpt-6.1-terra` reste disponible aux côtés de `gpt-5.6-terra`, sans changement de modèle par défaut ni de liste de sous-agents. GitHub Copilot utilise Responses ; OpenRouter propose `openai/gpt-6.1-terra` avec un contexte de 1,050,000 tokens et des tarifs de $1.00 en entrée / $5.00 en sortie par million de tokens. + +Codex natif : contexte par défaut de 500,000 tokens, plafond étendu de 872,000, effort par défaut `low` et niveaux `low`, `medium`, `high`, `xhigh`, `max`, `ultra`. Le niveau natif `ultra` est envoyé sous la forme `max` sur le réseau. La ligne native propose uniquement Fast. + +[API OpenAI](https://developers.openai.com/api/docs/models/gpt-6.1-terra) : contexte de 1,050,000 tokens, sortie maximale de 128,000 ; entrée texte/image, sortie texte ; connaissances arrêtées au 2026-06-01. Efforts : `none`, `low`, `medium` (par défaut), `high`, `xhigh`, `max`. Points de terminaison : `v1/responses` et `v1/chat/completions` ; les appels d’outils nécessitent Responses. + +Par million de tokens : $1.00 en entrée, $0.05 en lecture du cache, $1.25 en écriture du cache (30 minutes), $5.00 en sortie. Au-delà de 272,000 tokens d’entrée, toute la requête est facturée à 2× pour l’entrée et le cache, et 1.5× pour la sortie : respectivement $2.00 / $0.10 / $2.50 / $7.50. Le mode Fast coûte 2×. + ## Sélection des modèles à l’inscription Une nouvelle connexion sans OAuth attend une liste de modèles fiable avant de les exposer. Si l’onglet Models contient au moins 20 lignes distinctes, tous les interrupteurs de modèles sont initialement OFF ; le fournisseur reste ACTIVE. Les connexions utilisant effectivement OAuth ou la connexion ChatGPT conservent leurs valeurs par défaut. @@ -55,7 +65,7 @@ Après une inscription ou une connexion OAuth dans l’interface, une boîte de | `codexWsReuseAcrossTurns?` | `boolean` | `false` | Réutilisation optionnelle du WebSocket Codex entre les tours d'un même compte et fil. Désactivé par défaut. Activé, un socket occupé est attendu au plus 750 ms et jusqu'à deux sockets restent lors d'un changement de modèle ou de palier. | | `upstreamHostCircuitThreshold?` | `number` | `0` | Seuil facultatif du coupe-circuit pour les échecs DNS/TCP avérés avant connexion sur les requêtes Responses OpenAI natives en mode transfert et les envois compacts. `0` le désactive ; `1`–`20` ouvre, après ce nombre de requêtes logiques arrivées à leur terme, une temporisation de 30 secondes propre à l'origine du fournisseur. Tant que le circuit est ouvert, les requêtes reçoivent `503` avec `Retry-After` avant la sélection du compte ou l'envoi en amont ; après la temporisation, une requête est admise en état semi-ouvert. Les délais d'attente et les réponses HTTP ne sont jamais comptabilisés, et toute réponse HTTP ferme le circuit. Ce mécanisme s'applique uniquement au routage du pool Codex sans compte épinglé ; il reste inactif pour `codexAccountMode: "direct"` et les sélecteurs qualifiés par compte. | | `modelCacheTtlMs?` | `number` | `300000` | Fenêtre de fraîcheur pour le cache `/models` par fournisseur. | -| `cacheRetention?` | `"none" \| "short" \| "long"` | `"short"` | Politique Anthropic de mise en cache des invites : désactivée, éphémère pendant 5 minutes ou étendue à 1 heure. | +| `cacheRetention?` | `"none" \| "short" \| "long"` | Anthropic direct : `"long"` ; autres points de terminaison compatibles Anthropic : `"short"` | Politique Anthropic de mise en cache des invites : désactivée, éphémère pendant 5 minutes ou étendue à 1 heure. Sans valeur, les requêtes vers `api.anthropic.com` (clé API ou pools de comptes OAuth) utilisent un TTL d’une heure ; les passerelles et points de terminaison compatibles gardent 5 minutes. Une valeur explicite l’emporte toujours. | | `tokenGuardian?` | `OcxTokenGuardianConfig` | désactivé | Politique facultative d'actualisation proactive OAuth et de préchauffage des comptes Codex. | Pour ces stratégies, le seuil effectif de chaque compte est son entrée `codexAccountAutoSwitchThresholds` si elle existe, sinon le seuil global `autoSwitchThreshold`. Une valeur de zéro désactive uniquement le basculement proactif selon l'utilisation ; la liaison au démarrage, le verrouillage strict, le cooldown, les droits d'accès au modèle et la reprise après échec restent appliqués. @@ -95,7 +105,9 @@ sauvegarde dont le contenu diffère, puis réécrit en identifiants sans préfix | --- | --- | --- | | `adapter` | `string` | L'un des `openai-chat`, `openai-responses`, `anthropic`, `google`, `kiro`, `cursor`, `ollama-native`, `azure-openai` (ou alias `azure`). | | `baseUrl` | `string` | URL de base de l'API en amont. La plupart des points de terminaison fixes intégrés ignorent une valeur incompatible ; les préréglages de clés protégés contre les collisions préservent une ancienne destination personnalisée portant le même nom. | -| `requestPacing?` | `{ enabled, requestsPerMinute?, minIntervalMs?, maxConcurrentRequests?, models? }` | Cadencement facultatif du démarrage des requêtes sortantes côté client, distinct de l’utilisation, de la facturation et des indicateurs de limitation en amont. `maxConcurrentRequests` est une limite entière positive des requêtes en cours ; une règle fournisseur ou modèle peut utiliser uniquement cette limite. Les limites du fournisseur s’appliquent à tous ses modèles, tandis que les entrées `models` ciblent les identifiants exacts des modèles en amont et peuvent ajouter du délai ou réduire la concurrence. L’attente dans la file ne consomme pas le délai d’expiration des en-têtes de réponse en amont. Les requêtes HTTP et les distributions explicites `fetchResponse`/`runTurn` des adaptateurs sont couvertes. Avec une limite de concurrence, un tour Responses WebSocket canonique utilise HTTP/SSE afin de libérer la capacité à la fin, en cas d’erreur ou d’annulation du corps de réponse. Pour les adaptateurs `runTurn`, dont Cursor, la limite compte les tours actifs plutôt que les envois physiques : RunSSE et BidiAppend peuvent se chevaucher dans un même tour, tandis qu’un autre tour attend. Les envois suivants respectent toujours les intervalles de démarrage. | +| `requestPacing?` | `{ enabled, requestsPerMinute?, minIntervalMs?, maxConcurrentRequests?, models?, keySelection?, keys? }` | Cadencement facultatif du démarrage des requêtes sortantes côté client, distinct de l’utilisation, de la facturation et des indicateurs de limitation en amont. `maxConcurrentRequests` est une limite entière positive des requêtes en cours ; une règle fournisseur ou modèle peut utiliser uniquement cette limite. Les limites du fournisseur s’appliquent à tous ses modèles, tandis que les entrées `models` ciblent les identifiants exacts des modèles en amont et peuvent ajouter du délai ou réduire la concurrence. L’attente dans la file ne consomme pas le délai d’expiration des en-têtes de réponse en amont. Les requêtes HTTP et les distributions explicites `fetchResponse`/`runTurn` des adaptateurs sont couvertes. Avec une limite de concurrence, un tour Responses WebSocket canonique utilise HTTP/SSE afin de libérer la capacité à la fin, en cas d’erreur ou d’annulation du corps de réponse. Pour les adaptateurs `runTurn`, dont Cursor, la limite compte les tours actifs plutôt que les envois physiques : RunSSE et BidiAppend peuvent se chevaucher dans un même tour, tandis qu’un autre tour attend. Les envois suivants respectent toujours les intervalles de démarrage. | +| `requestPacing.keySelection?` | `"active"`, `"round-robin"` | Sélection des clés pour les requêtes normales : `active` par défaut conserve la clé enregistrée et la rotation sur erreur ; `round-robin` répartit les allocations entre les clés disponibles sans modifier la sélection enregistrée. Cela équilibre les allocations, pas les jetons ni les coûts. | +| `requestPacing.keys?` | `{ requestsPerMinute?, minIntervalMs?, maxConcurrentRequests? }` | Limites uniformes par référence de clé : RPM, intervalle minimal et concurrence, partagées entre modèles et cumulées avec les plafonds fournisseur et modèle. Les références identiques partagent leur capacité ; plusieurs références du même projet ne garantissent pas des quotas indépendants. TPM et quotas quotidiens ne sont pas gérés. | | `responsesPath?` | `string` | Chemin de ressource relatif pour les requêtes d'authentification par clé `openai-responses`. Il doit commencer par `/` et ne contenir aucun schéma, requête ou fragment. | | `chatCompletionsPath?` | `string` | Chemin de ressource relatif pour les requêtes `openai-chat`, miroir de `responsesPath` et soumis aux mêmes règles de forme. Nécessaire lorsqu'un même service en amont sert Chat Completions et Responses sous des préfixes différents : un override wire par modèle change l'adaptateur sans toucher `baseUrl`, donc sans ce réglage une requête Chat activée serait envoyée vers la base Responses. L'exemple fourni est Z.AI. | | `upstreamWebsocket?` | `boolean` | Active le transport Responses WebSocket en amont pour les requêtes `openai-responses` (désactivé par défaut). N'est honoré que pour l'amont first-party `https://api.openai.com/v1` ; les points de terminaison des fournisseurs personnalisés utilisent toujours HTTP/SSE borné, car Bun ne peut pas appliquer de limite de taille aux messages WebSocket entrants avant d'avoir alloué le message complet. Pour le fournisseur canonique ChatGPT `openai`, l'omettre conserve le WebSocket en amont sur les tours éligibles, `false` envoie les tours en streaming via HTTP/SSE, et `true` est refusé ; avec `false`, le pilotage et l'injection natifs en cours de tour sont indisponibles. Ce champ est indépendant du réglage `websockets` côté client et ne change ni le point de terminaison ni les identifiants. Une base HTTP reste en SSE ; les chemins qui ne sont pas Responses et les requêtes `openai-chat` restent en HTTP. | @@ -119,7 +131,7 @@ sauvegarde dont le contenu diffère, puis réécrit en identifiants sans préfix | `modelAutoCompactTokenLimits?` | `Record` | Budgets souples de compactage automatique par modèle, sous forme d'entiers sûrs positifs. Ils peuvent uniquement abaisser l'enveloppe effective de 90 % du contexte ou de l'entrée maximale et sont omis lorsqu'aucune fenêtre de contexte effective n'est déterminée. Pour le fournisseur canonique `openai`, les clés doivent être les identifiants exacts de modèles natifs pris en charge, sans préfixe de fournisseur ni de sélecteur de compte. PATCH fusionne les entrées ; `null` supprime une clé, tandis que `null` pour le champ entier efface la table. Ces marqueurs `null` sont réservés à PATCH. | | `defaultMaxOutputTokens?` | `number` | Solution de secours `openai-chat` à l’échelle du fournisseur lorsque le client omet `max_output_tokens`. | | `modelMaxOutputTokens?` | `Record` | Budgets de repli `openai-chat` positifs par modèle ; les correspondances exactes ou par motif priment sur la valeur par défaut du fournisseur. | -| `modelCosts?` | `Record` | Prix affichés par modèle (USD par 1M de jetons), indexés par l'identifiant exact du modèle en amont de ce fournisseur — et non par un identifiant de fournisseur ni par une étiquette routée `provider/model`, par exemple `{ "deepseek-v4-flash": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 } }`. Tout identifiant de modèle constitue une clé valide : les fournisseurs personnalisés peuvent cibler n'importe quel point de terminaison compatible avec OpenAI au moyen de l'adaptateur `openai-chat`, et les identifiants de fournisseur locaux ou internes fonctionnent même s'ils sont absents des catalogues intégrés. Les prix configurés par l'utilisateur priment sur les catalogues intégrés dans les estimations des pages Journaux (`~$`) et Utilisation. Les entrées historiques sont recalculées à partir de la surcharge actuelle ; modifier un prix peut donc changer les totaux antérieurs. L'ordre de repli est le suivant : `modelCosts` défini par l'utilisateur → catalogue jawcode → surcharge des prix attendus → repli propre au fournisseur au niveau du modèle. Une surcharge utilisateur explicitement définie à zéro produit une estimation nulle connue ; supprimez cette entrée pour rétablir la tarification automatique. Les prix de catalogue entièrement nuls restent soumis au repli. Chaque tarif doit être un nombre fini positif ou nul, inférieur ou égal à 1 000 000 (USD par 1M de jetons) ; les lignes hors plage sont rejetées par l'interface de gestion et ignorées au chargement. Ces valeurs servent uniquement à l'estimation lors de l'affichage : les surcharges n'affectent jamais le routage, la sélection des comptes, les quotas ni la facturation. | +| `modelCosts?` | `Record` | Prix affichés par modèle (USD par 1M de jetons), indexés par l'identifiant exact du modèle en amont de ce fournisseur — et non par un identifiant de fournisseur ni par une étiquette routée `provider/model`, par exemple `{ "deepseek-v4-flash": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 } }`. Tout identifiant de modèle constitue une clé valide : les fournisseurs personnalisés peuvent cibler n'importe quel point de terminaison compatible avec OpenAI au moyen de l'adaptateur `openai-chat`, et les identifiants de fournisseur locaux ou internes fonctionnent même s'ils sont absents des catalogues intégrés. Les prix configurés par l'utilisateur priment sur les catalogues intégrés dans les estimations des pages Journaux (`~$`) et Utilisation. Les entrées historiques sont recalculées à partir de la surcharge actuelle ; modifier un prix peut donc changer les totaux antérieurs. L'ordre de repli est le suivant : `modelCosts` défini par l'utilisateur → catalogue jawcode → surcharge des prix attendus → repli propre au fournisseur au niveau du modèle. Une surcharge utilisateur explicitement définie à zéro produit une estimation nulle connue ; supprimez cette entrée pour rétablir la tarification automatique. Les prix de catalogue entièrement nuls restent soumis au repli. Chaque tarif doit être un nombre fini positif ou nul, inférieur ou égal à 1 000 000 (USD par 1M de jetons) ; les lignes hors plage sont rejetées par l'interface de gestion et ignorées au chargement. Ces valeurs servent uniquement à l'estimation lors de l'affichage : les surcharges n'affectent jamais le routage, la sélection des comptes, les quotas ni la facturation. | | `headers?` | `Record` | En-têtes supplémentaires en amont. L'autorisation, les cookies, les en-têtes de clé API, les nouvelles lignes intégrées et les noms invalides sont rejetés. | | `openRouterRouting?` | `OpenRouterProviderRouting` | Préférences OpenRouter `order`, `only` et `allowFallbacks` par défaut ; valable uniquement pour les OpenRouter canoniques avec `openai-chat`. | | `modelOpenRouterRouting?` | `Record` | Remplacements exacts de l'ID de modèle qui remplacent la préférence OpenRouter à l'échelle du fournisseur. | @@ -131,7 +143,7 @@ sauvegarde dont le contenu diffère, puis réécrit en identifiants sans préfix | `modelReasoningEfforts?` | `Record` | Libellés propres à chaque modèle. Une liste vide masque le contrôle de l'effort. Comme pour `reasoningEfforts`, chaque échelle configurée avec l'adaptateur `google` déclare la capacité `thinkingLevel` ; les requêtes directes et Vertex sans image utilisent le chemin Gemini à plat, tandis que Cloud Code Assist l'envoie dans son enveloppe de requête. | | `modelSupportsReasoningSummaries?` | `Record` | Définissez un modèle sur `false` pour arrêter la publicité des résumés et supprimer les champs de livraison du résumé. | | `modelReasoningSummaryDelivery?` | `Record` | Énumération de livraison des réponses par modèle ; réécrit un champ de livraison existant. | -| `modelAdapters?` | `Record` | Remplacement du protocole `openai-chat` ou `openai-responses` par modèle pour les passerelles multiprotocoles. Les entrées explicites priment sur les valeurs par défaut du registre. Le préréglage OpenCode Go sélectionne Responses pour `gpt-5.6-luna` tout en laissant les modèles apparentés sur leurs protocoles documentés ; DeepSeek peut sélectionner Responses natif pour `deepseek-v4-flash` ; GitHub Copilot déclare des valeurs par défaut limitées à Responses pour ces modèles (`gpt-5.3-codex`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`), car ces modèles rejettent `/chat/completions` pour le trafic des agents. Les modèles sans valeur intégrée par défaut, comme `gpt-5.4-nano`, peuvent être activés ici. Les services en amont à protocole unique et le transfert canonique ChatGPT rejettent ces remplacements. | +| `modelAdapters?` | `Record` | Remplacement du protocole `openai-chat` ou `openai-responses` par modèle pour les passerelles multiprotocoles. Les entrées explicites priment sur les valeurs par défaut du registre. Le préréglage OpenCode Go sélectionne Responses pour `gpt-5.6-luna` tout en laissant les modèles apparentés sur leurs protocoles documentés ; DeepSeek peut sélectionner Responses natif pour `deepseek-v4-flash` ; GitHub Copilot déclare des valeurs par défaut limitées à Responses pour ces modèles (`gpt-5.3-codex`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6.1-sol`, `gpt-6.1-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`), car ces modèles rejettent `/chat/completions` pour le trafic des agents. Les modèles sans valeur intégrée par défaut, comme `gpt-5.4-nano`, peuvent être activés ici. Les services en amont à protocole unique et le transfert canonique ChatGPT rejettent ces remplacements. | | Activation Responses xAI (tableau de bord) | interrupteur | Pour `xai` uniquement, définit ou efface atomiquement les entrées `modelAdapters` de `grok-4.5` et `grok-4.6`. Une seule entrée apparaît comme un état mixte jusqu’à la prochaine écriture. Les autres remplacements et le comportement des tiers restent inchangés. Grok 4.7 utilise Responses par défaut sur OAuth via le registre et peut passer à Chat avec une entrée explicite `modelAdapters["grok-4.7"] = "openai-chat"`. | | `xaiResponsesXSearch?` | `boolean` | Désactivé par défaut. Sur une destination xAI Responses, ajoute la déclaration `x_search` hébergée par le fournisseur uniquement lorsqu’un outil `web_search` actif subsiste après la normalisation finale de la requête. Les déclarations existantes ne sont pas dupliquées, les sélecteurs `tool_choice`/`allowed_tools` de l’appelant ne sont jamais élargis, et cette option est distincte des options `search.xSearch` du service auxiliaire de recherche web. | | `modelPreferHostedTools?` | `Record` | Activation explicite par modèle exact pour les passerelles Responses hors transfert qui réservent un espace de noms aux outils hébergés. Seul `["image_generation"]` est actuellement accepté ; le modèle correspondant doit utiliser le protocole `openai-responses` et prendre en charge cet outil hébergé. Le proxy supprime les déclarations clientes `image_gen` en conflit et réécrit leurs sélecteurs afin de préserver le choix d'outil de l'appelant. Pour les modèles virtuels `-pro` de l'API OpenAI, l'identifiant public sélectionné est comparé en premier et l'identifiant résolu du modèle de base sur le protocole sert de repli. `modelAdapters` résout d'abord l'identifiant public, puis celui de base ; la seconde résolution détermine le protocole final. Les autres modèles conservent le comportement normal des alias. | @@ -145,13 +157,14 @@ sauvegarde dont le contenu diffère, puis réécrit en identifiants sans préfix | `noStopModels?` | `string[]` | Modèles qui rejettent `stop` spécifié par l’appelant. L’adaptateur `openai-chat`, le passthrough Chat et le passthrough Responses omettent ce champ pour ces identifiants. Le préréglage `xai` intégré y liste ses modèles de raisonnement (`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`), que xAI documente comme le refusant ; `grok-4.20-0309-non-reasoning`, `grok-composer-2.5-fast` conservent le `stop` de l’appelant. | | `noPenaltyModels?` | `string[]` | Modèles qui rejettent les pénalités presence/frequency. Le préréglage `xai` intégré y liste ses modèles de raisonnement (`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`), que xAI documente comme les refusant ; les identifiants sans raisonnement conservent les pénalités de l’appelant. | | `noStructuredOutputModels?` | `string[]` | ID de modèle exact dont le point final `openai-chat` rejette `response_format`. Seule une correspondance exacte du modèle demandé omet le champ ; la traduction à sortie structurée reste activée pour tous les autres modèles `openai-chat`. | -| `noJsonSchemaModels?` | `string[]` | ID de modèle exact dont le point final `openai-chat` rejette un `response_format` `json_schema` mais accepte encore `json_object`. Une telle requête est rétrogradée vers `json_object` au lieu d’être supprimée, donc un appelant qui demande du JSON en reçoit toujours. `noStructuredOutputModels` l’emporte quand un modèle figure dans les deux listes. Les préréglages `opencode go`, `opencode zen` et `opencode free` l’embarquent pour leurs routes DeepSeek. | +| `noJsonSchemaModels?` | `string[]` | ID de modèle exact dont le point final `openai-chat` rejette un `response_format` `json_schema` mais accepte encore `json_object`. Une telle requête est rétrogradée vers `json_object` au lieu d’être supprimée, donc un appelant qui demande du JSON en reçoit toujours. `noStructuredOutputModels` l’emporte quand un modèle figure dans les deux listes. Les préréglages `opencode go` et `opencode zen` l’embarquent pour leurs routes DeepSeek. | | `foldDeveloperRoleToSystem?` | `boolean` | Indique si une destination `openai-chat` accepte le rôle `developer`. `foldDeveloperRoleToSystem` non défini envoie `system`, `true` envoie `system` et `false` envoie `developer`. Non défini signifie que rien n'a été enregistré pour cette destination ; `true` enregistre un service en amont qui refuse le rôle ; `false` en enregistre un qui l'accepte. Dans tous les cas le message conserve sa position dans la conversation ; seul le rôle change. Une destination qui refuse le rôle répond `400 role 'developer' is not allowed` et le tour ne démarre pas, d'où l'état non enregistré replié par défaut. | | `parallelToolCalls?` | `boolean` | Contrôler les appels d’outils parallèles. Pour `openai-chat`, ils sont activés par défaut ; `false` envoie explicitement `parallel_tool_calls: false`. Les autres adaptateurs ne les annoncent que lorsque la valeur vaut explicitement `true`. | | `terminalContinuationGuard?` | `boolean` | Active, pour un fournisseur `openai-chat`, une relance interne bornée lorsqu’un tour exploitable annonce une action puis s’arrête proprement sans appel d’outil. La valeur par défaut est `false`, et une valeur explicite `false` équivaut à l’absence du champ. Les tentatives de combinaison et les tours de compactage routés sont exclus ; les autres adaptateurs ignorent cette option. | | `responsesItemIdRepair?` | `{ message?: string[]; reasoning?: string[]; repairMissingTerminalIds?: boolean; repairInvalidIds?: boolean }` | Réparation SSE en aval désactivée par défaut pour les identifiants d'espace réservé exacts, les identifiants de terminal manquants et (avec `repairInvalidIds`) les identifiants message/reasoning manquant du préfixe canonique `msg_`/`rs_`. Les identifiants d’appel de fonction ne sont jamais réécrits. Le DeepSeek intégré active les deux derniers par défaut. | | `responsesSnapshotRepair?` | `boolean` | Réparation côté client désactivée par défaut pour les instantanés du cycle de vie des réponses clairsemés dans SSE et JSON. Remplit les métadonnées d'état canonique, de sortie et d'outil manquantes tandis que l'inspection brute et la persistance restent inchangées. | -| `retryOn429?` | `{ enabled?: boolean; attempts?: number; intervalMs?: number; maxIntervalMs?: number; respectRetryAfter?: boolean }` | Fournisseurs à clé API uniquement (`authMode: "key"`). Nouvelle tentative facultative sur la même cible après un 429 : lorsque `retryOn429` est absent, la fonctionnalité est désactivée ; la présence d'un objet l'active, sauf avec `enabled: false`. Après un 429, le proxy attend selon `Retry-After` reçu en amont ou selon l'intervalle fixe, puis relit la requête à l'identique avec la même clé avant tout basculement de clé. Ce comportement couvre la boucle principale de récupération d'un tour textuel, le protocole de transfert Responses, le pont d'images et de vidéos, le service auxiliaire de recherche Web et les continuations du terminal. Seules les réponses HTTP 429 reçues avant le début de la diffusion peuvent être relues ; les transports `runTurn` personnalisés ne font pas partie de la boucle de nouvelle tentative HTTP. `attempts` compte les relectures avec la même clé après le premier 429, soit `attempts` + 1 envois au total, et constitue un budget commun à toute la requête, partagé entre la boucle principale de récupération, la continuation de la garde du terminal et les nouvelles tentatives du pont. L'épuisement de `attempts` arrête uniquement les relectures supplémentaires avec la même clé : le basculement normal de clé ou la gestion de l'erreur finale s'applique ensuite selon les cibles disponibles. Sur le protocole de transfert authentifié par clé, aucun basculement n'est possible ; le 429 final est donc renvoyé sans modification. Codex ne retente jamais lui-même une requête après un 429 : cette option constitue ainsi la seule protection pour les fournisseurs à clé unique. Valeurs par défaut : `enabled: true`, `attempts: 3`, `intervalMs: 5000`, `maxIntervalMs: 60000` (chaque attente est plafonnée à `maxIntervalMs`, lui-même plafonné à 600000), `respectRetryAfter: true`. | +| `promptCacheKey?` | `boolean` | Active pour tout le fournisseur `openai-chat` la transmission de `prompt_cache_key`. L’adaptateur transmet la clé reçue sans en inventer ; la traduction Claude Messages peut toutefois la dériver de `metadata.user_id`, ou d’un groupe modèle/système/outils sans métadonnées. Désactivé par défaut sauf dans un préréglage ; Mistral l’active selon son [contrat de cache](https://docs.mistral.ai/studio/conversations/advanced/prompt-caching). `false` explicite le désactive. N’activer que si l’amont documente ce champ, car une passerelle stricte peut répondre HTTP 400. | +| `retryOn429?` | `{ enabled?: boolean; attempts?: number; intervalMs?: number; maxIntervalMs?: number; respectRetryAfter?: boolean }` | Réservé aux fournisseurs à clé API (`authMode: "key"`). Facultatif : absent, désactivé, sauf pour OpenCode Go et Command Code sur leurs destinations canoniques et Mistral canonique avec `openai-chat` : 6 relances, intervalle de 10 s, plafond de 60 s. Un objet active la fonction ; `enabled: false` désactive aussi ces valeurs de repli. Après un HTTP 429 avant le flux, attend `Retry-After` ou l’intervalle fixe, puis renvoie la même requête avec la même clé avant le basculement. Couvre la récupération du tour texte, Responses passthrough, les ponts image/vidéo, le service web-search et les continuations terminales ; les transports `runTurn` personnalisés sont exclus. `attempts` compte les relances après le premier 429 (envois totaux = `attempts` + 1), dans un budget partagé à l’échelle de la requête. Son épuisement arrête les relances avec la même clé, puis laisse agir le basculement ou l’erreur finale ; le passthrough à clé n’a pas de basculement. Pour Mistral Chat canonique, un délai explicite supérieur à l’attente autorisée arrête la relance avec la même clé et conserve le `Retry-After` original ; une autre clé du pool peut encore servir. Les autres fournisseurs conservent le délai plafonné. Mistral partage les échéances par identifiant/modèle, sauf avec `requestPacing.enabled: false` ou `retryOn429.respectRetryAfter: false`. Un 429 sans échéance avec une pression validée sur le budget par minute déclenche une attente prudente de 60 s, puis une seule sonde ; son succès rétablit le trafic ordinaire. Le trafic sain ne prédit pas le coût suivant. La politique utilise les allocations annoncées, exclut les allocations mensuelles connues comme épuisées, ne prédit pas une réinitialisation exacte et ne déclenche pas de compactage. Codex ne relance pas lui-même les 429. Valeurs par défaut de l’objet : `enabled: true`, `attempts: 3`, `intervalMs: 5000`, `maxIntervalMs: 60000` (attente plafonnée ; valeur maximale 600000), `respectRetryAfter: true`. | | `transientRetryOn5xx?` | `{ enabled?: boolean; attempts?: number }` | Fournisseurs `openai-chat` et `openai-responses` authentifiés par clé uniquement. Les fournisseurs `authMode: "forward"` (le pool de comptes ChatGPT) ne lisent jamais cette option et conservent l'échelle par défaut. Nouvelle tentative facultative pour les états transitoires reçus en amont avant le début de la diffusion (500, 502, 503, 504, 520, 521, 522) : l'absence de l'option la désactive ; la présence d'un objet l'active, sauf avec `enabled: false`. Ce comportement couvre la requête Responses initiale, la continuation de la garde du terminal, le point de terminaison natif `/v1/chat/completions` et les réémissions liées à la récupération après un 429 ou à la récupération de compte. `attempts` représente le nombre TOTAL d'envois en amont autorisés pour une requête, premier envoi compris (de 1 à 10, valeur par défaut : 3). Il constitue un budget commun à la requête, partagé avec la récupération après une réinitialisation de connexion ; ainsi, `3` signifie qu'au plus trois requêtes réelles atteignent le fournisseur. Les attentes utilisent une temporisation exponentielle à base fixe de 400 ms, plafonnée à 5 s, et respectent `Retry-After`. Cette option est distincte de `retryOn429`, qui traite la limitation de débit ; les échecs en cours de diffusion ne sont jamais relus. Chaque cible Combo compte son premier envoi prépayé dans son propre total configuré : `attempts: 1` autorise un envoi, sans rembourser la réservation ni élargir le plafond partagé. Un refus local ou une annulation avant l'envoi libère la réservation inutilisée. | | `retryOnReset?` | `{ enabled?: boolean; replacements?: number }` | Fournisseurs `openai-responses` natifs, `authMode: "forward"` compris, ainsi que les requêtes Responses traduites envoyées par la voie générique avant les en-têtes reçus en amont. Remplacement facultatif d'un envoi qui a échoué alors que l'appelant n'avait rien observé : l'absence de l'option la désactive ; la présence d'un objet l'active, sauf avec `enabled: false`. Couvre les deux étapes ambiguës — une connexion rompue avant tout en-tête de réponse, et un corps SSE rompu après l'en-tête alors qu'il ne portait que des événements de contrôle. Une WebSocket canonique ChatGPT en amont qui s'est fermée ou a échoué après l'envoi de sa trame de création, avant tout événement Responses, est couverte de la même façon ; son remplacement est envoyé en HTTP. Seule une requête autonome est remplacée : `store: false`, `input` complet, ni `previous_response_id`, ni `conversation`, ni `stream_id`, et uniquement des outils exécutés par le client. `replacements` est le nombre d'envois de remplacement qu'UNE requête logique peut effectuer, toutes étapes et tous enfants de combo confondus (de 1 à 2, valeur par défaut : 1). Ce n'est ni un nombre de tentatives par étape ni un budget d'envoi : un remplacement doit toujours tenir dans l'allocation d'envois dont l'étape disposait déjà. Une requête qui a déjà émis une sortie ou un appel d'outil n'est jamais remplacée, quelle que soit cette valeur. L'inférence de remplacement peut tout de même être facturée si l'origine avait déjà démarré la première, d'où la désactivation par défaut. La voie générique de traduction prend en charge le remplacement après une rupture avant les en-têtes pour les envois initiaux et reconstruits, avec la même autorisation et le même budget d’envois. Les transports gérés par les adaptateurs et les erreurs des flux traduits après les en-têtes reçus en amont sont exclus. | | `autoToolChoiceOnlyModels?` | `string[]` | Modèles dont `tool_choice` accepte uniquement `auto` ou `none` ; les choix forcés sont dévalorisés. | @@ -165,8 +178,8 @@ sauvegarde dont le contenu diffère, puis réécrit en identifiants sans préfix | `anthropicEofTolerance?` | `boolean` | Laissez une passerelle compatible Anthropic compléter un flux qui se termine avant `message_stop`, uniquement lorsque du texte visible ou une entrée complète d'outil d'objet JSON a été reçue. Désactivé par défaut. | | `googleMode?` | `"ai-studio" \| "vertex" \| "cloud-code-assist"` | Mode Google transport/auth. Par défaut `ai-studio`. | | `googleToolSchemaPolicy?` | `"compatible" \| "reject-lossy"` | Google uniquement. Une valeur absente ou `compatible` conserve le schéma compatible et la réparation 400 existante hors mode direct. `reject-lossy` refuse une perte initiale ou une comparaison bornée indéterminée avant l’envoi et bloque une réparation Vertex ou Cloud Code Assist qui ouvrirait des contraintes. AI Studio direct n’effectue jamais cette réparation. | -| `project?` | `string` | ID du projet Vertex ou Antigravity Cloud Code Assist. | -| `location?` | `string` | Région Vertex ; la valeur de repli de l'environnement est `GOOGLE_CLOUD_LOCATION`. | +| `project?` | `string` | ID du projet Vertex ou Antigravity Cloud Code Assist. Lorsqu'il est défini, les clés API Vertex utilisent le point de terminaison du projet ; sinon elles utilisent le mode express, même si `GOOGLE_CLOUD_PROJECT` est défini. ADC se replie sur `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT`. Avec `"auto"`, chaque clé API Vertex résout son propre projet (une résolution réussie est mise en cache par clé ; une clé dont la résolution échoue est de nouveau sondée lors d'une requête ultérieure), de sorte qu'un pool de clés peut couvrir plusieurs projets ; ADC refuse `"auto"`. | +| `location?` | `string` | Région Vertex. Les points de terminaison de projet avec clé API utilisent `global` par défaut et ignorent `GOOGLE_CLOUD_LOCATION` ; ADC exige ce champ ou le repli `GOOGLE_CLOUD_LOCATION`. | | `mcpServers?` | `Record` | Cursor uniquement : serveurs MCP sur entrée-sortie standard ou HTTP diffusé en continu. | | `desktopExecutor?` | `DesktopExecutorConfig` | Cursor uniquement : commandes externes d'utilisation d'un ordinateur et d'enregistrement de l'écran. | | `unsafeAllowNativeLocalExec?` | `boolean` | Ancien booléen de Cursor, équivalent à `nativeLocalExec: "on"` uniquement lorsque le champ plus récent n'est pas défini. | @@ -631,3 +644,8 @@ le nom actuel avant d'effectuer une autre modification. ### `anthropicAccountPool.routes` Les routes `anthropicAccountPool.routes` associent un modèle à des identifiants de comptes OAuth Anthropic enregistrés. La première règle correspondante (`match`, glob sensible à la casse) limite la sélection initiale et les reprises 429 lorsque le pool est activé. `fallback: true` élargit la sélection seulement si aucun compte de la règle n’est éligible. + + +### `promptLengthPricing` + +Le champ facultatif `promptLengthPricing` complète les quatre tarifs de base. `mode: "automatic"` (ou champ absent) conserve les règles de contexte du catalogue ; `mode: "flat"` les désactive. Avec `mode: "custom"`, `thresholdTokens` est un entier positif sûr, `comparison` vaut `"gt"` (>) ou `"gte"` (≥), et `rates` contient les quatre tarifs alternatifs absolus. Le seuil compte tous les jetons du prompt, y compris les lectures et écritures du cache, sans la sortie. Les tarifs sélectionnés couvrent toute la requête, sortie comprise, et remplacent la règle de contexte du catalogue des deux côtés du seuil. Les ajustements Fast/Priority s'appliquent une seule fois après la sélection des tarifs et suivent la relation Priority publiée dans le catalogue pour ce modèle ; l'estimation n'est indiquée comme borne inférieure que lorsque le catalogue déclare la relation `lower-bound` (actuellement xAI Grok) ; un modèle sans règle de catalogue applique le multiplicateur Priority une seule fois. Configurez ces options dans **Models → Price**. **Reset to automatic** supprime tous les tarifs et la règle personnalisés. Les anciennes configurations restent compatibles ; ces paramètres concernent les estimations affichées. diff --git a/docs-site/src/content/docs/fr/reference/management-api.md b/docs-site/src/content/docs/fr/reference/management-api.md index fb6a70a30aa..25f81a99073 100644 --- a/docs-site/src/content/docs/fr/reference/management-api.md +++ b/docs-site/src/content/docs/fr/reference/management-api.md @@ -353,7 +353,7 @@ lui-même s'il souhaite ajouter une étoile au dépôt. | `POST /api/system/restart` | Amorcer un redémarrage du processus qui attend l'évacuation des requêtes, sans retirer l'injection du client | Renvoie 202 ; les appels répétés signalent l'évacuation déjà en cours | | `POST /api/stop` | Arrêter le service, restaurer Codex en mode natif, retirer l'injection Grok gérée et évacuer les requêtes du proxy | 409 conflit de propriété du service; 409 `respawnable_service` lorsqu'un wrapper du Planificateur de tâches Windows pourrait relancer le proxy et que l'appelant n'est pas `ocx stop` (rien n'est modifié) ; 409 lorsque le gestionnaire installé refuse de s'arrêter ; 409 `service_state_unknown` lorsque l'état du Planificateur de tâches ne peut pas être lu (rien n'est modifié ; réparez la requête puis réessayez) | | `GET /api/system/codex-app-server` | Indiquer si les serveurs d'application Codex en cours d'exécution sont antérieurs au catalogue de modèles actuel | — | -| `POST /api/system/codex-restart` | Actualiser le catalogue, puis redémarrer les serveurs d'application Codex obsolètes et quitter puis relancer entièrement l'application Codex Desktop afin que le sélecteur de modèles se recharge. Lorsque le proxy lui-même s'exécute dans l'application Codex, le redémarrage Desktop est refusé plutôt que transféré. | Renvoie 200 avec `code: partially_stopped` lorsqu'une cible ne s'arrête pas | +| `POST /api/system/codex-restart` | Actualiser le catalogue, puis redémarrer les serveurs d'application Codex. Une application Codex Desktop en cours reçoit une tentative de redémarrage afin que le sélecteur de modèles puisse se recharger ; les champs `desktopApp` de la réponse signalent un refus, des cibles survivantes ou un échec de relance. Un processus plus récent que le catalogue est tout de même signalé avec `desktopApp.pickerState: "unknown"`, car son heure de démarrage ne prouve pas quelle liaison `model_catalog_json` il a chargée ; une synchronisation antérieure peut avoir relié un catalogue déjà existant. Un processus qui n'est pas plus récent que le catalogue, ou dont cette requête a changé la liaison, est `stale`. Une application fermée renvoie `desktopApp.reason: "no_targets"` ; son `pickerState` peut être `not_running`, ou `stale` si cette requête a changé la liaison au catalogue. Lorsque le proxy lui-même s'exécute dans l'application Codex, le redémarrage Desktop est refusé plutôt que transféré. | Renvoie 200 avec `code: partially_stopped` lorsqu'une cible ne s'arrête pas | ### Délégation de l'authentification Codex diff --git a/docs-site/src/content/docs/fr/reference/proxy-formats.md b/docs-site/src/content/docs/fr/reference/proxy-formats.md index 5535c90ade6..ae904510a0e 100644 --- a/docs-site/src/content/docs/fr/reference/proxy-formats.md +++ b/docs-site/src/content/docs/fr/reference/proxy-formats.md @@ -41,6 +41,8 @@ Les points de terminaison Chat Completions et Messages honorent les catalogues d Par ailleurs, sur chaque voie entrante, un appel dans l’espace de noms `default.` d’un fournisseur est normalisé vers le nom d’outil nu déclaré par la requête (`default.Read` → `Read`). +Messages natif reconnaît aussi les définitions d’outils en ligne dans les messages système avec `tools: []`. Ces noms restent appelables jusqu’à leur retrait permanent, sans autoriser d’autres outils clients. Les appels MCP hébergés et les flux d’arguments des outils serveur conservent leur format natif. + ## Présentation du point de terminaison | Espace client | Point de terminaison | Résultat non-stream réussi | Résultat de flux ou de socket réussi | diff --git a/docs-site/src/content/docs/getting-started/installation.md b/docs-site/src/content/docs/getting-started/installation.md index 020b0c2bc4a..3ffc2f3effd 100644 --- a/docs-site/src/content/docs/getting-started/installation.md +++ b/docs-site/src/content/docs/getting-started/installation.md @@ -38,8 +38,8 @@ Recent npm versions may block bun's postinstall script (`npm warn install-scripts ... blocked because they are not covered by allowScripts`), which leaves the bundled Bun runtime unprepared. After trying permitted bundled-runtime recovery, the launcher can use an executable Bun from an absolute PATH directory if its -stable version has the pinned major and a minor at least the pinned minor. With the current -1.4.2 dependency, that means 1.4.0 or newer within major 1. Bounded version-policy and identity +stable version has the pinned major and a version at least the pinned version. With the current +1.4.4 dependency, that means 1.4.4 or newer within major 1. Bounded version-policy and identity checks require `bun --version` to match `bun -e`'s `Bun.version`. On POSIX, the launcher checks the whole candidate path, including ancestor directories and components reached through symlinks. Each traversed directory and symlink, and the resolved executable, must be owned by @@ -99,6 +99,9 @@ are not required. Download the archive for your platform, extract it, and run: The extracted `gui/dist` and `keyring` directories must stay beside the binary. The first serves the dashboard; the second carries the platform-native OS credential-store binding. +On Windows, [Windows Code Signing](/reference/code-signing/) explains how `ocx.exe` is signed and +how to verify it. + ### Release channels The stable `latest` channel already includes GPT-5.6 Sol/Terra/Luna catalog support for ChatGPT, diff --git a/docs-site/src/content/docs/getting-started/quickstart.md b/docs-site/src/content/docs/getting-started/quickstart.md index 99ff1a1d5d7..ed39e7005e5 100644 --- a/docs-site/src/content/docs/getting-started/quickstart.md +++ b/docs-site/src/content/docs/getting-started/quickstart.md @@ -18,7 +18,7 @@ ocx init `ocx init` walks you through: -1. **Pick a provider** — choose one of the 104 built-in registry presets or `custom` to type a base +1. **Pick a provider** — choose one of the 106 built-in registry presets or `custom` to type a base URL and adapter. 2. **API key** — paste a key, or reference an environment variable like `${ANTHROPIC_API_KEY}`. 3. **Default model** — for key, local, and custom providers, accept the preset or enter a model id. @@ -146,6 +146,18 @@ ocx restore # restore native Codex without stopping (alias: ocx eject) ocx restore back # route Codex through the still-running proxy again ``` +## Startup ownership could not be verified + +If startup reports `Shared-client owner could not be verified` and `refusing startup`, +OpenCodex could not safely decide whether another proxy owns the shared client settings. +It stops before publishing a new runtime or changing those settings. Retry startup after +local connectivity recovers; an installed background service retries through its service manager. +A timeout does not permanently turn the new process into a secondary instance. + +IPv6 does not need to be enabled on loopback. Startup checks whether each loopback +address can be bound locally and skips an unavailable family. Timeouts on an available +family still stop startup until ownership can be verified. + ## Next - [How It Works](/getting-started/how-it-works/) — what happens to each request. diff --git a/docs-site/src/content/docs/guides/chatgpt-desktop.md b/docs-site/src/content/docs/guides/chatgpt-desktop.md index 887493f1935..458acdfbedf 100644 --- a/docs-site/src/content/docs/guides/chatgpt-desktop.md +++ b/docs-site/src/content/docs/guides/chatgpt-desktop.md @@ -126,6 +126,17 @@ app-server until you run one of them. A single output line longer than 8 MiB is passed through unparsed rather than buffered. This standalone shim does not rewrite conversation metadata or route model calls. -Other app gates or upstream refusals can still prevent sending. Evidence reported -on an exhausted Plus account also used an intercept, so it does not establish -that this shim alone resolves every desktop send lock. +Other app gates or upstream refusals can still prevent sending. An attached launcher +or a passing filter self-test does not prove that the app's gate data crosses this pipe. + +Reporter observations in [#6196](https://github.com/lidge-jun/opencodex/issues/6196) +on October 2 and 13, 2026 describe an attached shim with Send still disabled at +natural quota exhaustion. On Desktop 26.1007.30912 with OpenCodex 2.84.0, the +reporter observed no eligible rate-limit RPC messages while the renderer's gate +used a separate usage transport. This shim cannot rewrite data that never reaches +its stdout filter. These are build-specific field reports, not a new live +verification or proof that every Desktop build uses the same transport. + +The remaining signed-in composer behavior stays tracked in #6196. Earlier success +with a combined shim/intercept configuration does not establish shim-only +sufficiency, and the closed, unmerged #6365 intercept is not a shipped remedy. diff --git a/docs-site/src/content/docs/guides/claude-code.md b/docs-site/src/content/docs/guides/claude-code.md index f6a457ebcec..4a815b3c6d1 100644 --- a/docs-site/src/content/docs/guides/claude-code.md +++ b/docs-site/src/content/docs/guides/claude-code.md @@ -254,6 +254,12 @@ Claude Desktop can use OpenCodex in one of two mutually exclusive modes. Pick it **Connect → Claude Desktop → Connection mode** in the dashboard or with `ocx claude desktop apply --first-party|--gateway`. +> Desktop library profiles and `_meta.json` must be regular files of at most +> 4 MiB. OpenCodex also refuses generated profile or metadata output above that +> limit before replacing files. Back up and reduce oversized configuration +> manually; a size refusal does not truncate the existing data. Connection-owned +> stores retain their existing tighter limits. + ### Gateway (default) New installs use **gateway** by default. Its third-party profile, described below, switches the @@ -271,6 +277,15 @@ not advertise an authoritative per-provider ladder, and the selected provider's normal effort normalization still applies. This remains a client-version-specific compatibility mechanism. +Gateway apply refuses unreadable or non-regular configuration files, including dangling metadata +links, before creating a new profile. If the metadata update fails, apply restores the profile's +previous contents, or removes a profile it had just created, but only while the profile still holds +what apply wrote; if another program changed it in the meantime, apply keeps that version and +reports "profile rollback skipped". Apply rechecks the profile before +backup and replacement; if it detects a concurrent edit, it refuses with `desktop_profile_changed`. +Review the edit and reapply. If restoring the profile also fails after a metadata error, the +reported reason includes both failures; reconcile the profile and metadata before retrying. + ### First-party (opt-in) :::caution[Account risk] @@ -306,10 +321,15 @@ entries, is kept. Claude Desktop first-party routes its Code tab and subagents through OpenCodex. The standalone Claude Code CLI has a separate first-party switch. Both clients read the same `~/.claude/settings.json` proxy and CA settings: if only one switch is on, the other client still transits the local proxy, where TLS terminates, but its Messages requests relay to Anthropic unchanged. Other Anthropic paths relay unchanged and unrelated hosts remain blind tunnels. With the CLI switch on, the standalone `claude` also lists routed models in `/model`; see [Model picker in the CLI](#model-picker-in-the-cli). :::note[Windows system proxy (Clash, v2rayN, corporate proxies)] -When a Windows system proxy is on, Claude Desktop hands it to the Code tab as `HTTPS_PROXY`, and +When picker intent is off at OpenCodex startup, its Windows relay stays unbound. Turning picker +mode on later starts it within the running service; OpenCodex does not need a restart. +For picker-off installs or pending installs with no applied picker profile, a Windows system proxy +is handed to the Code tab as `HTTPS_PROXY`, and that value takes precedence over the OpenCodex proxy in `~/.claude/settings.json`. The Code tab then goes around OpenCodex and routed models fail there, while the standalone CLI keeps working. -Add `api.anthropic.com` to your proxy client's system-proxy bypass list (in Clash Verge, +An applied selected picker profile uses Desktop's fixed egress proxy while it is available, even when +missing trust leaves the picker pending and `claude.ai` relays blindly. System-proxy bypass advice +applies when there is no selected available picker profile. Add `api.anthropic.com` to your proxy client's system-proxy bypass list (in Clash Verge, `system_proxy_bypass`), then fully quit and reopen Claude Desktop. `ocx doctor` reports this under "Claude Desktop first-party vs Windows system proxy". It cannot evaluate a PAC script or automatic proxy detection (WPAD, "Automatically detect settings"), so it reports those as unknown; @@ -340,17 +360,17 @@ hub, so `ocx claude desktop apply` there uses the gateway profile. ### Picker mode: opencodex models in the first-party Code-tab picker -Picker mode is part of first-party mode. On macOS it is on by default when first-party is selected, +Picker mode is part of first-party mode. On macOS and Windows it is on by default when first-party is selected, unless `claudeCode.intercept.picker: false` is set. It changes the first-party Desktop Code-tab picker so it lists available opencodex models by name. The first time it is enabled, macOS may ask you to -trust a local certificate authority in the login keychain. That authority is constrained to `claude.ai` +trust a local certificate authority in the login keychain, and Windows may ask you to trust it in the current-user Root store. That authority is constrained to `claude.ai` and its subdomains. OpenCodex protects its exportable signing identity in the OS credential store and reuses the same validated certificate and key across normal restarts. No plaintext picker signing key is stored in the OpenCodex config directory. The full constrained CA validation and OS trust -verification still apply. With the same approved identity and an available credential store, restarting -OpenCodex does not add or remove Certificate Trust Settings. Startup restore never installs trust: +verification still apply, including the exact fingerprint with a verified leaf on Windows. With the same approved identity and an available credential store, restarting +OpenCodex does not add or remove macOS Certificate Trust Settings. Restart and status never install trust: if trust is missing, revoked or unknown, the picker stays pending. Run `ocx claude desktop picker on` -or `ocx claude desktop picker trust` explicitly to grant trust. +or `ocx claude desktop picker trust` explicitly to grant trust, including the Windows confirmation when needed. Startup also attempts to remove a legacy on-disk picker signing key before checking whether interception is enabled. Cleanup is best-effort and does not enable interception or block startup. @@ -358,9 +378,9 @@ One-time migration from an older picker identity may require consent to remove i If cleanup cannot finish, the picker stays unavailable and the applied profile uses a blind relay until cleanup succeeds; `ocx claude desktop picker status` reports that state. macOS may separately ask you to unlock the keychain or approve an application's access to stored credentials. These native -access prompts can still occur on restart or upgrade. +access prompts can still occur on restart or upgrade. On Windows, explicit `on` or `trust` may show a confirmation to add the root. -Picker mode remains unsupported on Windows and Linux: OpenCodex starts no picker CA, credential-store +Picker mode remains unsupported on Linux: OpenCodex starts no picker CA, credential-store or proxy work there. The main Claude intercept remains available with ownership, symlink, file-permission and Windows ACL checks protecting its local CA files. @@ -418,7 +438,7 @@ next request; Desktop does not need a restart. Turn on the CLI switch in Connect → Claude, or run `ocx claude config set --first-party on`; use `off` to disable it. The switch is immediate and refuses `{enabled:false, cliFirstParty:true}` before any field is saved; it may also refuse to turn on if the local intercept is unavailable, the CA cannot be prepared, settings cannot be read, or a foreign proxy setting owns the keys. Off persists even when the intercept is unavailable; disabling Claude routing alone leaves an owned settings env untouched. For fully native terminal traffic with only Desktop first-party on, set `NO_PROXY='*'` in the shell. This still carries the first-party account risk stated above. Disabling Claude routing leaves the owned settings env untouched. While the bound listener still runs, every Messages request relays unchanged; after it stops, plain `claude` cannot connect until OpenCodex runs or Desktop/CLI first-party is turned off. Native `ocx claude` sets `NO_PROXY=*` only for an owned env without a foreign inherited `HTTPS_PROXY`/`https_proxy`. With a foreign proxy it preserves that value and warns that the settings-owned intercept still applies; turn Desktop/CLI first-party off or unset the setting. -The UI distinguishes uncertainty about whether settings still point at its proxy (unknown), a token-bearing opencodex proxy with a foreign CA (foreign: fix HTTPS_PROXY / NODE_EXTRA_CA_CERTS manually), and a tokenless loopback proxy beside a foreign CA (local: ownership is unconfirmed; remove HTTPS_PROXY if unused). With matching applied settings and a bound listener but Claude routing off, disabled means requests relay unchanged while it is bound; turn first-party off to remove settings. An owned URL with no listener is stopped; a bound listener with an owned CA but mismatched port or token is broken even when routing is off. With an intent on, stopped or broken plus ineligible interception displays routingOff: Claude routing or the intercept is off, or this machine is a client of another opencodex hub; enable interception on this machine or turn first-party off to remove the settings. When interception is stopped, use **Start interception** or `ocx claude intercept start` to retry within the running OpenCodex service. Saving first-party settings also starts it automatically. A refusal reports disabled routing, client role, an ephemeral public port, an occupied port, or a startup failure. For an occupied port, free it or set `claudeCode.intercept.port`. If a pair is already bound on a different port, the response names both ports; use the bound port or restore the configured value. CLI intent with no proxy is not applied; one intent with a live proxy gets the shared-relay notice; any remaining proxy with neither intent is residual, unless unknown, foreign, or local takes precedence. +The UI distinguishes uncertainty about whether settings still point at its proxy (unknown), a token-bearing opencodex proxy with a foreign CA (foreign: fix HTTPS_PROXY / NODE_EXTRA_CA_CERTS manually), and a tokenless loopback proxy beside a foreign CA (local: ownership is unconfirmed; remove HTTPS_PROXY if unused). With matching applied settings and a bound listener but Claude routing off, disabled means requests relay unchanged while it is bound; turn first-party off to remove settings. An owned URL with no listener is stopped; a bound listener with an owned CA but mismatched port or token is broken even when routing is off. With an intent on, stopped or broken plus ineligible interception displays routingOff: Claude routing or the intercept is off, or this machine is a client of another opencodex hub; enable interception on this machine or turn first-party off to remove the settings. When interception is stopped, use **Start interception** or `ocx claude intercept start` to retry within the running OpenCodex service. Saving first-party settings also starts it automatically. A refusal reports disabled routing, client role, an ephemeral public port, an occupied port, or a startup failure. For an occupied port, free it or set `claudeCode.intercept.port`. Startup refreshes an existing managed Claude proxy environment only after the intercept proxy binds successfully, using its bound port; an occupied port leaves Claude settings unchanged. If a pair is already bound on a different port, the response names both ports; use the bound port or restore the configured value. CLI intent with no proxy is not applied; one intent with a live proxy gets the shared-relay notice; any remaining proxy with neither intent is residual, unless unknown, foreign, or local takes precedence. - Gateway discovery (`/model` → "From gateway") is not used, because Claude Code only queries `GET /v1/models` on a configured gateway. Routed models reach the picker another way; see @@ -948,7 +968,7 @@ The proxy translates every Anthropic Messages API request into the Codex Respons | User `tool_result` | `function_call_output` (`is_error` → `[tool error]` prefix) | | `tool_reference` in a tool result | Text `Tool loaded: ` in the paired result; preserves loaded-tool names without adding declarations or enabling translated server-side deferral | | `thinking` / `redacted_thinking` replay | `reasoning` items with bounded `ocxr1` envelopes for signatures and redacted payloads | -| Function tools | `{type: "function"}` (`web_search*` → `{type: "web_search"}`) | +| Function tools | `{type: "function"}` (`web_search*` → `{type: "web_search"}`). On a route to `api.anthropic.com`, the typed `web_search_*` and `web_fetch_*` declarations are also sent to Anthropic as declared and run server-side, unless the web-search sidecar supplies `web_search` itself. A search or fetch with a matching call and complete input comes back to the client as its `server_tool_use` block and its `web_search_tool_result` or `web_fetch_tool_result`, verbatim. Orphan results are dropped without synthesizing a call, while the surrounding text is preserved. When the client sends a complete pair back in its history, the next `api.anthropic.com` request carries it in place only if the following block uniquely identifies its original turn. Repeated anchors, unmatched or duplicate call/result ids, malformed call input, or a result appearing before its call drop the exchange. Call input must be an object with a nonempty search query or fetch URL string; other destinations never see the exchange | | `tool_choice` | `auto`→`auto`, `none`→`none`, `any`→`required`, named function→`{type:"function",name}`, hosted WebSearch/web_search→`{type:"web_search"}` | | `max_tokens` | `max_output_tokens` | | `stop_sequences` | `stop` | diff --git a/docs-site/src/content/docs/guides/codex-app-models.md b/docs-site/src/content/docs/guides/codex-app-models.md index cd39beb0418..4da93918113 100644 --- a/docs-site/src/content/docs/guides/codex-app-models.md +++ b/docs-site/src/content/docs/guides/codex-app-models.md @@ -221,11 +221,12 @@ metadata instead of an older-template approximation. | Route | Picker ids and catalog metadata | | --- | --- | -| Codex login (account-qualified rows disabled) | Bare native ids such as `gpt-5.6-sol`, `gpt-5.6-terra`, and `gpt-5.6-luna`; Pool or Direct is selected through `codexAccountMode`. GPT-5.6 rows use a 922,000-token catalog window. | +| Codex login (account-qualified rows disabled) | Bare native ids such as `gpt-5.6-sol`, `gpt-5.6-terra`, and `gpt-5.6-luna`; Pool or Direct is selected through `codexAccountMode`. GPT-5.6 rows use a 922,000-token catalog window. `gpt-6.1-terra`: 500,000 default / 872,000 long context. | | Codex login (account-qualified rows enabled with eligible selectors) | One `/` row per eligible selector and supported native model; each row uses only its mapped account, and bare native rows are hidden from the picker. Native metadata and context windows are preserved. | | Codex login (explicit Daybreak forward row) | `openai/gpt-daybreak-blue-latest` only when the exact `customModels` row is configured on the canonical `openai` provider. It keeps the Daybreak wire id and uses the pinned Sol capability snapshot (922,000 context; 829,800 automatic compaction). | -| OpenAI (API key) | Exactly ten namespaced rows: `gpt-5.5`, `gpt-5.6`, Sol/Terra/Luna, the three `*-pro` virtual ids, and the two Daybreak aliases (1,050,000 context; 922,000 max input for all ten) | +| OpenAI (API key) | Namespaced models include `gpt-5.5`, `gpt-5.6`, Sol/Terra/Luna, the `*-pro` virtual ids and the Daybreak aliases (1,050,000 context; 922,000 max input). `openai-apikey/gpt-6.1-terra` adds 1,050,000 context and 128,000 max output. | | OpenRouter | `openrouter/openai/gpt-5.6-sol`, `openrouter/openai/gpt-5.6-terra`, `openrouter/openai/gpt-5.6-luna` (922,000) | +| OpenRouter | `openrouter/openai/gpt-6.1-terra` (1,050,000) | | Cursor | Static fallback includes `cursor/gpt-5.6-sol`, `cursor/gpt-5.6-terra`, and `cursor/gpt-5.6-luna` (1,000,000), plus regular/Fast rows for Grok 4.5, 4.6, and 4.7 (500,000); 4.6 and 4.7 add `xhigh`, and live account discovery decides which rows remain visible. | | xAI | Live discovery is authoritative. The fallback catalog includes `xai/grok-4.6` and `xai/grok-4.7` and defaults to `xai/grok-4.5`; all three have 500,000-token windows. Grok 4.6 and 4.7 expose `low` / `medium` / `high` / `xhigh` (upstream default: `high`), while Grok 4.5 stops at `high`. | @@ -278,6 +279,12 @@ preserved, so Luna has `max` but no `ultra`. On the wire, routed adapters map or clamp unsupported tiers. For older native models whose real ladder stops at `xhigh`, `nativeEffortClamp` maps a direct `max` or an `ultra` selection to `xhigh` (for example, GPT-5.5). Sol, Terra, and Luna have a real `max` rung. +The ChatGPT backend itself accepts `none` through `max`, so a native ChatGPT request never forwards `ultra` as-is: +native models with a real `max` rung receive `max`, including Astra, GPT-6.1 Sol and GPT-6.1 Terra whose ladders +list `ultra`, while older models such as GPT-5.5 receive `xhigh` as above. This applies to +`/v1/responses` and `/v1/chat/completions` alike. The native clamp does not apply to routed model IDs: a routed +OpenAI Chat passthrough can copy a declared `reasoning_effort: "ultra"` to its provider unchanged, +while the ordinary translated adapter maps `ultra` to `max`. Catalog advertisement of the two top tiers is unconditional: `ocx sync` no longer removes `max` or `ultra` when the installed Codex binary is too old to offer them — Codex versions without those @@ -314,6 +321,45 @@ advertised where it cannot be honored. An `openai-chat` destination can authoriz eligible model with `chatServiceTier: true`, or authorize only exact models with `modelSupportsServiceTier`; Responses routes do not need that extra Chat wire authorization. +### Selecting Flex in Codex + +For a routed provider and model verified to accept `service_tier: "flex"`, explicitly +declare Flex support alongside the existing Fast wire. Merge these fields into the +**existing** `providers.nanogpt` entry (do not replace its URL or credentials): + +```json +{ + "modelSupportsServiceTier": { "openai/gpt-6-luna": true }, + "fastWire": { + "kind": "service-tier", + "canonicalToWire": { "priority": "priority", "flex": "flex" }, + "foreignCallerTiers": "verbatim" + } +} +``` + +With an eligible `openai-responses` route, the Luna row advertises Standard, +Fast and Flex in Codex's speed selector. For an `openai-chat` route, also set +`chatServiceTier: true` **only if** the upstream Chat Completions endpoint supports +caller-provided service tiers. The `fastWire` mapping is **provider-wide**: +other service-tier-eligible routed models in the same `providers.nanogpt` +entry can also advertise Flex when the catalog publishes them as ordinary routed +rows, even when they are not listed in `modelSupportsServiceTier`. That map is not +an allowlist excluding other eligible models. Combo-namespace and native-alias +rows are **not** published with Flex. Verify Flex support for **every** eligible +ordinary routed model; +if only Luna supports Flex, configure it in a separate provider entry with +no other Fast-eligible models. Do not infer Flex support from Fast alone. + +Keep OpenCodex's global Fast mode on **Auto** (`fastMode` unset), as forcing +Fast or Default overrides the caller's Flex choice. After `ocx sync`, refresh the +Codex model list. Select **Flex** from the speed selector, or use +`/model --speed=flex` in Codex CLI. This is a **selectable tier**, +not a per-model forced default: switching models does not force Flex automatically. +The tier is forwarded as `service_tier: "flex"` and can still be declined by +the upstream; availability, cost and latency depend on the provider. Neither +the menu nor a request alone proves which tier was billed. + ### Selecting Ultra Fast in Codex Desktop For a provider that supports `service_tier: "ultrafast"`, declare that wire mapping once. @@ -341,10 +387,18 @@ Desktop if it still has the old list, select the routed model, then choose **Ult in its speed menu. Standard remains the default. Only an eligible `service-tier` wire with an explicit `ultrafast → ultrafast` mapping adds -this choice. Ordinary Fast support, a GPT-like name, native/account rows and combo aliases -do not enable it. Removing the mapping or disabling that model's service-tier support -removes the generated choice from both the catalog and cache on the next successful sync. -The separate `ultraFastTier` setting still only retains hand-supplied catalog tiers. +this choice to routed rows; ordinary Fast support or a GPT-like name is insufficient. +That routed mapping does not apply to native/account rows or combo aliases. Removing the +mapping or disabling that model's service-tier support removes the generated routed choice +from both the catalog and cache on the next successful sync. +The active declaration also controls the Ultra Fast choice when Codex loads its model list over HTTP. + +For routed rows, `ultraFastTier` retains independently supplied Ultra Fast tiers from the +same exact persisted row; generated routed tiers are not treated as operator-supplied. +For bare native `gpt-6-astra` / `gpt-6.1-sol` rows and their trusted account-bound clones, +enabling `ultraFastTier` also generates a missing Ultrafast choice without changing the +native default tier. Disabling it withdraws only native tier metadata tracked as generated; +independently supplied or edited native tier descriptors are preserved. [OpenAI's API guide](https://developers.openai.com/api/docs/guides/ultrafast-mode) documents Ultrafast for GPT-6 Astra and GPT-6.1 Sol. API access and diff --git a/docs-site/src/content/docs/guides/codex-integration.md b/docs-site/src/content/docs/guides/codex-integration.md index acf3da827d7..eec26ef3049 100644 --- a/docs-site/src/content/docs/guides/codex-integration.md +++ b/docs-site/src/content/docs/guides/codex-integration.md @@ -31,7 +31,24 @@ selected stored account is cooling down and no eligible stored alternative or re available. This also covers a new request blocked before sending, following the same caller validation used after an upstream rejection. Existing model-permission and main-account policy checks still apply. The fallback preserves the stored account's cooldown and does not persist the -caller credential as the Pool selection. An exact account binding remains bound to that account. +caller credential as the Pool selection. An exact account binding remains bound to that account. A manual Pool +pin is a preference, not an exact account binding: it can still use this fallback. Use an +account-qualified model when a request must remain on one account. Pausing the observed native +main blocks fallback for its matching caller credential; it does not identify or pause unrelated +caller-owned logins. + +Adding or reauthenticating a pool account rejects a duplicate of the native main login with +`Account is already configured as the native Codex main account.` The check compares ChatGPT +account ID, personal/workspace subscription bucket, and email; a missing email does not prove +a different identity. Different workspaces, distinct workspace members, and separate personal +and workspace subscriptions remain supported. A failed usage lookup does not discard the +subscription plan carried by the login token. + +Existing duplicates remain stored but cannot be selected as a second Pool account. The account +list exposes `selectionExcludedReason: "duplicate_main"` and an actionable `selectionExcludedAction`. Use native main explicitly, +or sign native Codex into a different account before reusing the stored entry. Detection follows +the latest owned native-login observation, including a later native relogin; passive account +listing and caller-owned requests do not open native credentials to refresh that observation. ## Config injection @@ -125,6 +142,17 @@ to restore the default upstream WebSocket selection. This setting does not chang client-facing `websockets` switch or the ChatGPT account used for the request. Native mid-turn steering and injection need upstream WebSocket and are unavailable while it is off. +**Reasoning usage metadata (2.90.0 and later).** For HTTP/SSE clients on the canonical +ChatGPT route, the upstream WebSocket relay preserves an actual `x-reasoning-included` +connection-response header when the Bun runtime exposes it, including on a reused connection. +This tells Codex that the server's reported usage already includes earlier reasoning. Metadata +sent before the first Responses event takes precedence; later metadata remains in the stream +but cannot change headers already committed. The relay does not invent this signal or alter +usage numbers. A missing upstream signal and Codex's own accounting lifecycle remain separate +issues, so this change alone does not establish that early compaction is fixed. Client-facing +WebSocket connections have already completed their upgrade before the upstream response and +cannot receive the signal retroactively as an upgrade header. + Local provider pacing can also hold a request before it is dispatched at all. So a slow first output has several possible contributors, and upstream queueing is only one of them. `ocx doctor` classifies configuration and measures none of these: compare actual transport, pacing, network, @@ -379,6 +407,10 @@ Root keys such as `openai_base_url`, `model_provider`, and `model_catalog_json` first `[table]` header. The injector guarantees that placement, removes its own stale/duplicate copies, and never overwrites a user-owned root `openai_base_url`; if one exists, sync updates the catalog but reports that routing was not injected. + +Sync and restore also leave a root `model_context_window` in place, since OpenCodex never writes +that key. Codex uses it instead of each model's catalog window, capped at that model's maximum +window, so delete it yourself if you want each model to report its own window. ::: ## Shared model catalog @@ -427,6 +459,14 @@ provider advertises `supports_websockets = true` only when `"websockets": true`; built-in provider may try WebSocket first, and a disabled proxy returns `426` so Codex falls back to HTTP/SSE. +The proxy admits up to 128 client-facing Responses WebSocket connections. When a new connection +finds all 128 slots occupied, it closes the least recently used idle connection to make room. Connections +with an active response, pending client tool or approval results, a native steering/injection +session, or buffered output are protected. If no idle connection can be released, the new +connection receives the existing `503` capacity response. A reclaimed client can reconnect for +later requests using the existing continuation handling. This happens only under connection +pressure, needs no setting, and does not change voice connections or upstream connection reuse. + If a canonical ChatGPT forward continuation references expired or missing local replay state, opencodex returns `previous_response_not_found` before sending anything upstream. Codex's WebSocket client recognizes this error and can reconnect with its full retained context, @@ -464,10 +504,10 @@ other providers keep their defaults. Authenticated loopback routing normally keeps Codex on its built-in `openai` provider identity. That preserves native thread identity, but it also makes Codex request native remote compaction. When a routed provider cannot return a native compaction blob, OpenCodeX stores the summary in its -own `ocx1:` envelope. Native ChatGPT cannot verify that envelope if OpenCodeX is later removed from +own `ocx1:`/`ocx2:` envelope. Native ChatGPT cannot verify that envelope if OpenCodeX is later removed from the request path. -On an authenticated loopback route, enable client-side compaction to keep V2 sub-agent routing while preventing new `ocx1:` compaction summaries. Non-loopback and API-key routes retain their existing provider and authentication behavior: +On an authenticated loopback route, enable client-side compaction to keep V2 sub-agent routing while preventing new `ocx1:`/`ocx2:` compaction summaries. Non-loopback and API-key routes retain their existing provider and authentication behavior: ```bash ocx system settings --client-compaction on # or "codexClientCompaction": true in config.json @@ -495,7 +535,7 @@ selected provider. V2 sub-agent requests keep their existing provider selection accounting. Client-side compaction does not change plaintext delivery, encrypted task passthrough through `allowEncryptedV2AgentTasks`, or configured recovery and fallback behavior. -This preference affects future compactions only, and it rewrites no existing `ocx1:` payload in +This preference affects future compactions only, and it rewrites no existing `ocx1:`/`ocx2:` payload in any configuration, so use the explicit history recovery workflow for a thread that needs one. Whether resume-history metadata is re-tagged depends on which form the injection takes. On its @@ -976,6 +1016,22 @@ has reached Codex the turn is never sent again. If every attempt is declined, Co the same error it would have received without the retries. Native compact requests move to another Pool account on a server error in the same way. +### Runtime agent instances (Codex 0.164+) + +Codex 0.164 can sign each request with a runtime agent assertion when +`features.runtime_agent_instances` is enabled (it is off by default). opencodex supports one of +its two modes: + +- `mode = "dual_header"` keeps the normal `Authorization: Bearer` credential and adds an + `x-openai-agent-assertion` header. opencodex treats the request exactly as before. The assertion + is forwarded only when the turn goes to ChatGPT Direct with your own credential. It is removed + when a Pool account, a stored main credential, a routed provider, or a sidecar serves the request, + because it belongs to the credential Codex sent. +- `mode = "assertion_only"` replaces the bearer with `Authorization: AgentAssertion `. + opencodex refuses these requests with HTTP 400 and error code `agent_assertion_unsupported`. + It never substitutes a Pool or provider credential for the assertion, and it never forwards or + logs the assertion. Use `dual_header`, or turn the feature off, when Codex runs through opencodex. + ### Codex autostart shim Run `ocx codex-shim install` to install the optional launcher wrapper. It runs @@ -1088,6 +1144,13 @@ an app updater can omit the managed connection argument. Save drafts, close Code After a Codex update, an unassessed app version refuses correction until its compatibility is reviewed; reinstalling the certificate does not make an unassessed build supported. +The panel can also report the last completed build check as diagnostic metadata: the package +version this build was assessed against, the package version observed at that check, whether +they matched, and when the check finished. It is a past observation, not a live reading of the +installed package. After the service stops, the app updates, or a check is still running or was +interrupted, it can lag the actual installation; only a completed probe refreshes it, and a +`verified` result describes the package observed at that check, not whatever is installed now. + On Windows, an explicit OpenCodex full-app restart preserves an already active loopback compatibility PAC argument only when the current OpenCodex process owns the serving compatibility runtime. A stale or foreign endpoint is refused before stopping Codex; replacement of the runtime @@ -1139,7 +1202,8 @@ Pool quota responses can authorize a pause only with valid usage and a captured that is still current. A response without that proof may refresh displayed usage without pausing an account. Check this server’s authenticated `GET /api/codex-auth/low-quota-events` history for `logged` alerts or save failures. Manual resume remains in force for the current quota episode. The -default alert reaches only the log and API; it does not produce a desktop or OS notification. +default alert reaches only the log and API. Add `actions.desktop: true` to also show a macOS or +Linux notification on the machine running the proxy; Windows stays log-only. When the ChatGPT 5-hour quota is exhausted, Codex may offer a reserve fallback model (`gpt-reserve` / Luna Reserve). While that state is active, the Codex model picker can make @@ -1214,6 +1278,8 @@ An allowed response with primary and secondary windows can omit the optional ter This recovery uses your included plan allowance and does not require enabling **Use credits**. Partial or invalid usage data retains the previous block until recovery is confirmed. +Stored accounts with **Use credits after limit** enabled renew their credit evidence on the existing background cycle once an included usage window is exhausted and the credit observation is at least three minutes old. In **Direct** mode this also covers stored accounts explicitly bound in `codexAccountNamespaces`, such as a sub-account selector. You can keep Direct mode and separate main/sub identities; this does not enable Pool routing or refresh unbound accounts. Paused and reauthentication-required accounts are skipped. Metadata reads do not validate pending accounts or send model requests. Failed reads retain backoff and requests still need fresh spendable credits; ordinary response usage headers do not renew the credit balance timestamp. + ### Cancelling main-account device reauthentication When cancelling main-account device reauthentication, a temporary DELETE or network failure, or a response with an unknown or nonterminal status, keeps the active flow and the cancellation-failure indication so cancellation can be retried. Status polling normally continues so a login that completes can still be detected. If a retryable cancellation failure overlaps a non-2xx GET status response while the flow is `pending` or `committing`, either response order preserves cancellation retry on that same flow, with its last server-provided device code, verification URL, and phase. The GET HTTP failure still stops polling, but cancellation can be retried without starting a second login POST. A terminal `failed` response releases the flow and displays the normalized failure reason; only `succeeded` reports login success. A confirmed `cancelled` response releases the flow so a new device login can be started. A definitive HTTP 404 response with code `unknown_flow` also releases the expired flow ID so a new device login can be started, but does not report a successful login or confirmed cancellation. Late POST, GET, or DELETE responses from an earlier flow cannot change the new flow or report login success for it. @@ -1614,3 +1680,50 @@ The shared SSE decoder accepts LF, CRLF and standalone CR line endings, even whe Choosing an account-qualified model does not enable credit spending. Stored accounts obey **Use credits after limit** during authentication, when credentials are prepared, and before Responses HTTP or WebSocket dispatch after pacing or retry waits. A held request reports the credit policy, not an authentication failure; wait for the reset, choose another account, or explicitly enable that account’s credit spending. Stored-account vision and web-search helpers also recheck this policy before sending, including retries. If consent changes or a limit is reached after helper selection, the helper reports the policy refusal without sending that attempt. The standalone search relay returns a reset-bound 429. Caller-owned Direct credentials retain their existing behavior. + +## Opt in a custom provider to managed local routing + +A custom provider such as `chatgpt-http` may use `supports_websockets = false` +to keep HTTP transport. OpenCodex leaves custom providers untouched by default. +To delegate only that provider's URL to OpenCodex, run: + +```sh +ocx codex-provider allow chatgpt-http +ocx start +``` + +The first command explains the scope and asks for confirmation, defaulting to No. +For an explicitly approved scripted operation, use `allow chatgpt-http --yes`. +Consent applies to this provider ID in the selected `CODEX_HOME`; it does not +approve other providers or other homes. Start and sync update only its `base_url` +to the local proxy. Its identity, WebSocket setting, other TOML fields and history +remain unchanged. Keep the proxy running while using this route. +Turning the Codex integration OFF also prevents approved-provider URL updates on +start or sync; the saved provider consent remains available when you turn it back on. + +This mode supports explicit Responses provider tables with `requires_openai_auth = true` +and no custom credentials or headers. Non-loopback targets, authless Desktop mode +and client compaction require the existing dedicated integration instead. + +`ocx stop` and `ocx restore` restore recorded URLs while keeping consent for a later +start. Run `ocx codex-provider status` to list approvals, or +`ocx codex-provider revoke chatgpt-http` to restore its managed URL and remove consent. +A URL changed outside OpenCodex is preserved and requires revoke followed by a new +approval; OpenCodex does not silently adopt the replacement as a new baseline. +The original URL assignment is retained in private recovery metadata under +`OPENCODEX_HOME`, separately from the native config journal. + +Startup safety recognizes an approved provider's recorded managed URL as OpenCodex routing. +This does not install restart protection: a manually started proxy still needs a viable +background service to recover automatically after a restart. + +## Native Desktop relay CONNECT validation + +The Desktop relay's existing HTTP/HTTPS CONNECT transport now rejects malformed +proxy response headers and ambiguous HTTP Basic usernames containing colons. +Decoded credential pairs are limited to 4096 UTF-8 bytes. If your selected proxy +is rejected, correct its configuration or response; the relay does not silently +switch to a direct connection. + +Proxy selection, SOCKS5/SOCKS5h support, certificate verification, and connection +lifecycle are unchanged. Both SOCKS spellings retain remote target DNS resolution. diff --git a/docs-site/src/content/docs/guides/combos.md b/docs-site/src/content/docs/guides/combos.md index 60e3db08e6b..d32a8dcf122 100644 --- a/docs-site/src/content/docs/guides/combos.md +++ b/docs-site/src/content/docs/guides/combos.md @@ -15,6 +15,17 @@ This is useful when you want either: Combos sit in front of normal provider routing. Read [Model Routing](/guides/model-routing/) first if `provider/model` selectors are new to you. +## Metered fallback consent + +Mark a pay-per-use combo target with `"metered": true` when it follows subscription targets. Omitted or false consent denies metered dispatch. Choose consent for your deployment: + +- **Local Claude Code or Codex:** on a loopback-only proxy, set `"allowMeteredTargets": true` on the combo, or run `ocx combo set --allow-metered-targets on`. This explicitly permits local callers to use its marked targets. The default is false; `off` clears consent. Loopback authentication does not read API keys. +- **Network or linked callers:** the caller's configured `apiKeys[]` entry must have `"allowMeteredComboTargets": true`. A proxy with a non-loopback listener ignores the combo flag, including on its secondary loopback listener. Linked callers also retain their configured-key consent requirement when the primary listener is loopback. The environment token never qualifies. + +Request headers cannot grant consent. Model/provider scopes still apply. This covers initial selection, failover and recovery across combo strategies, compaction, Messages, Chat Completions and WebSocket requests, and a subagent model fallback naming a combo alias. A collapsed fallback tries a usable unmetered target before refusing. Unmarked targets retain existing behavior; metering is declared by the operator rather than detected automatically. + +For example, a failover chain can target Anthropic OAuth, then Cursor, then `{ "provider": "anthropic-apikey", "model": "claude-opus-5-5", "metered": true }`. Without consent, exhaustion returns the last unmetered error, with one exception: an earlier 429 or 402 quota refusal takes precedence over a later 400, 401 or 403. If the subscription target returns 429 and Cursor then refuses with 401 or 403, the caller receives the subscription's 429, because the spent subscription explains the failure and Cursor's credential refusal does not. Dashboard saves retain omitted target metering and combo consent; explicitly setting either to false clears it. These consent fields are configured through the config file or API; the combo flag is also available through the CLI, while the dashboard does not expose it. + ## 60-second quickstart This example creates `combo/main` with Anthropic first and OpenAI second. Both providers must @@ -207,6 +218,115 @@ order. Weights and `stickyLimit` do not affect this strategy. This ranking and provider exclusion before dispatch require fresh model-inference limits that apply to the current single API key as a whole. OAuth/current-account summaries, caller-forward routes, multiple keys, and snapshots with changed credentials or destinations are display-only for this early decision. The same applies when `Authorization`, `x-api-key`, or `x-goog-api-key` headers override credentials; search-only and MCP-only windows are excluded. If no eligible target has an applicable reset, configuration order wins. Account selection and retries still enforce their normal limits. +### Advisory quota signals for JEV + +Quota evidence is separately opt-in **per JEV Combo**, for all three decision methods: + +```bash +ocx combo set coding --decision-quota-signals on --json +ocx combo set coding --decision-quota-tiers '{"moderate":40}' --json +``` + +Only enable this on an existing `strategy: "jev"` Combo. `decisionQuotaSignals: true` adds +subscription quota tiers to the decision request, not a hard eligibility rule. The decision +backend is asked to prefer healthier quota among adequate options; it still chooses only from +configured eligible targets and effort allowlists. Unknown is not exhausted, and ordinary +fallback, cooldown, account selection and provider limits remain unchanged. + +Defaults are `limited: 70` and `nearlyExhausted: 90`, inclusive percentages used. There is no +moderate tier unless you set it explicitly (for example, `moderate: 40`). Partial overrides merge +with those defaults, then all thresholds must be finite, within 0–100 and strictly ascending. + +Evidence comes only from memory snapshots published by existing quota collectors, accepted +response headers and credential/account owners, and only while at least one JEV Combo has this +switch on: until then opencodex collects nothing for it, so evidence starts accumulating (and reads +as unknown) from the moment you enable it. Reading advisory quota evidence never resolves +credentials, loads auth stores or quota files, reads a keychain, probes upstream, or starts a +refresher. Normal decision-backend credential resolution is unchanged. Cold, missing, stale (over +30 minutes), future, invalid or reset-expired evidence is unknown. Each account uses its worst valid +relevant five-hour, weekly, monthly or structurally model-scoped window. An enabled usable +Anthropic/Codex pool uses its healthiest account tier; unknown ties healthy. A pool is nearly +exhausted only if every usable account has known nearly-exhausted evidence. Reserve models +without separately attributable bars remain unknown. Active-account summaries, multiple-key +display reports, credits and API/Spark/USD meters are not quota evidence for this decision. +The separate Anthropic · Pool 2 (`anthropic2`) account pool is not a source of this evidence, so its targets stay unknown. + +Antigravity contributes Gem/Cla five-hour and weekly buckets only after a fresh authenticated probe +for its sole active, usable OAuth account, and only to the `google-antigravity` provider that signs in +with that account. A renamed provider row reads unknown and never reuses that account's quota. Quota +follows the resolved native `gemini-*` or `claude-*` model, not its display alias. Account, credential, project or destination +changes withdraw the old observation. A display-only refresh can retain matching family counters +only with their original timestamps; per-model and Argon rows never become decision evidence. + +:::caution[Additional decision data] +Opting in sends quota tiers, and available used percentage/window/reset delay, to your selected +decision backend, including hosted TypeSafe. No account ids, tokens or credential fingerprints +are sent or logged by this feature. Local decision records retain only bounded tier counts and +the selected tier. If quota would exceed the 64 KiB request cap, it is removed before retrying +serialization; an ordinary oversized decision still fails open. +::: + +`--decision-quota-signals off` disables evidence while retaining configured tiers; `-` clears +the switch. `--decision-quota-tiers -` clears overrides, restoring defaults. API `null` clears; +API `false` disables the switch. Omitted fields survive JEV updates, target replacement and +rename, including saves through the existing editor. Switching away from JEV removes both +quota settings. No new editor controls or background quota work are enabled. + +### Session pin for JEV + +A JEV Combo decides a target for every request, so consecutive turns of one conversation can move +between providers, and each move starts with a cold provider prompt cache. `sessionPin` is an +opt-in setting per JEV Combo, off by default, that keeps a client session on the target JEV chose +for it while the session stays active: + +In the dashboard, open the JEV Combo editor's **Decision method** section and enable +**Keep a session on its decided target**, then set **Idle window (ms)** and save. The range is +60000–86400000 ms (1 minute–24 hours); newly enabled pins start at 60000 ms. Disable the checkbox +and save to turn pinning off. Invalid or empty enabled windows block saving; failed saves keep +your edits for retry. Older responses without this setting leave it omitted until you change it. + +```bash +ocx combo set jev-auto --session-pin 20m --json +``` + +```json +{ "strategy": "jev", "sessionPin": { "ttlMs": 1200000 } } +``` + +The first request of a session is decided normally. Once the target JEV applied for it completes a +response, later requests of the same session reuse that target and effort without another decision +call. `ttlMs` (1 minute to 24 hours) is an idle window: every completed turn on the pinned target, +tool-call continuations included, starts it again, and a session that stays quiet longer is decided +afresh. + +A session is identified by the client's own session headers (`thread-id`, `session_id` or +`session-id`, `x-codex-parent-thread-id`, or a promoted `x-session-id`) together with the API key +that admitted the request and the Combo. Requests without a session header are never pinned, +`prompt_cache_key` is never used as a session identity, and two API keys never share a pin. + +JEV decides again, and the new decision replaces the pin, when: + +- the pinned target is not offered for this request: disabled, removed from the Combo, cooling + down after a 429 or another failure, out of cached quota, metered, or otherwise ineligible; +- the pinned effort is no longer offered for that target; +- `decisionQuotaSignals` is on and the target's quota evidence reads nearly exhausted; +- any send of a pinned session fails (that request still fails over as usual); +- the configuration has changed since the pin was written. + +A cancelled request keeps the pin. A fail-open decision (timeout, missing key, invalid answer) +never creates one. Pins live in the proxy's memory, at most 512 sessions, and do not survive a +restart. + +Usage records mark a reused turn with `jevDecision.pinned: true` and `pinReason: "session"`. A +decision that replaced a released pin records why in `pinReason` (`expired`, `config_changed`, +`target_unavailable`, `effort_unavailable` or `quota_nearly_exhausted`). `ocx combo stats` +counts pinned requests apart from decisions; they still count toward successful requests, model +fallbacks and per-model tokens. + +A pin makes provider cache reuse possible; whether the provider actually serves cached tokens still +depends on the provider, the account and a stable prompt prefix. `--session-pin -` (API `null`) +turns it off, switching away from JEV removes it, and saves from the dashboard editor keep it. + ### Decision method `strategy: "jev"` asks a decision backend to choose the first eligible target and a compatible @@ -335,10 +455,21 @@ Combo's `decisionProvider`: - Self-hosted services receive each target/effort option as a plain description string (for example `Target openai/gpt-5.6-sol (provider openai, model gpt-5.6-sol) with low reasoning effort.`), because Ollama accepts only string or `null` option descriptions. TypeSafe keeps - receiving the structured option objects. -- OpenCodex offers 2–26 options to a System One-compatible row. Each target contributes one option - per offered reasoning effort; use per-target `reasoningEfforts` to trim them. Fewer than 2 or - more than 26 options fail open without a request. + receiving the structured option objects. When targeting Vercel AI Gateway (`ai-gateway.vercel.sh`), + route options include both `value` and detailed `description` fields. Other self-hosted endpoints, + including a custom `/decisions` path, keep the System One request format. +- OpenCodex offers 2–26 options to a System One-compatible row (`SELF_HOSTED_MIN_OPTIONS = 2`, `SELF_HOSTED_MAX_OPTIONS = 26`). + Each target contributes one option per offered reasoning effort; use per-target `reasoningEfforts` to trim them. + Fewer than 2 or more than 26 options fail open without a request. For Vercel AI Gateway, whose + [OpenAI-compatible Decisions reference](https://vercel.com/docs/ai-gateway/sdks-and-apis/openai-decisions) + supports 1..255 choices, the upper bound expands to 255 options (`DECISIONS_MAX_OPTIONS = 255`); OpenCodex + enforces the 2-choice minimum as local routing policy to select among candidate targets. +- Vercel AI Gateway endpoints (`https://ai-gateway.vercel.sh/v1`) automatically normalize to `/decisions`. + OpenCodex projects structured conversation context into a formatted plain-text `input` string + (`Task:`, `Signals:`, `Recent Assistant:`, `Recent Tool Call:`, and `Tool Output:`). Route answers are accepted + from object (`answers.route`) or choice-array formats, resolving from either `choice` or `value`. Probability + distributions are accepted as key-value maps or probability records; duplicate choice keys or invalid distributions + strictly fail open to the first eligible target. - A cold model load can take tens of seconds, and an aborted decision request makes Ollama abandon the load. Pre-warm the model and keep it resident (`OLLAMA_KEEP_ALIVE=-1`, or `keep_alive`), and raise `decisionTimeoutMs` (1000–120000 ms, default 4000) when the service is slower than four seconds. @@ -481,8 +612,9 @@ are ignored. Operator notes are evidence for the decision, not commands, and can target allowlist or reasoning-effort limits. Only put information there that may be disclosed to the selected decision backend. -Each logical model call is decided on its own; there is no per-conversation pin. Consecutive turns of -one session can therefore land on different targets, and every switch starts a cold provider prompt +By default, each logical model call is decided on its own. Unless you enable the opt-in +[session pin](#session-pin-for-jev), consecutive turns of one session can therefore land on +different targets, and every switch starts a cold provider prompt cache, so a mix of very different targets can cost more input tokens than it saves. Keep the allowlist to targets you are content to alternate between. Targets marked `lastResort` are withheld from JEV under `cooldownWaitPolicy: "before-last-resort"` while any normal target is offered, and @@ -532,6 +664,8 @@ rows show decision count, applied count, and average latency for `typesafe`, `sy ## What happens when a target fails Combo failures are divided into **hop** failures and **terminal** failures. +To make a combo fail with a clear `fallback_refused` error instead of hopping to another model or +provider, see [Fallback off](/guides/fallback-off/). | Result | Behavior | | --- | --- | @@ -540,7 +674,7 @@ Combo failures are divided into **hop** failures and **terminal** failures. | Classified authentication, subscription, quota, rate-limit, overload, or upstream-server error | Cool the target and hop, even when the status alone is not sufficient. | | Client cancellation (499), `origin_rejected`, cyber-policy refusal, context overflow, or other invalid request | Stop and return the error; another target would not make the request valid. | | Exact HTTP 400 saying a model is not supported when using Codex with a ChatGPT account | Try the next declared target before output commitment. The message must be the whole refusal in `detail`, `error.message`, or bare text; competing `detail` and `error` envelopes stay terminal. This does not quarantine the account or cool every model on the provider. | -| Structured HTTP 400 rejecting optional `user`, an unsupported reasoning effort, or model-scoped image input | Hop before output commitment without cooling; see request-local target compatibility below. | +| Structured HTTP 400 rejecting optional `user`, an unsupported reasoning effort, model-scoped image input, or input media the route cannot read (`unsupported_input_modality`) | Hop before output commitment without cooling; see request-local target compatibility below. | | First tool call of a Responses turn run by an in-process adapter (`runTurn`) that the current request did not declare, before any output or replay-unsafe side effect | Cool the target and hop with the same tool catalog. After visible output or a replay-unsafe side effect the refusal is final. Chat Completions and Anthropic Messages requests are unchanged. | | Any other unclassified error | Stop and return the error. | @@ -640,7 +774,11 @@ classify the original HTTP 403 as a hop. For streaming requests, the upstream HTTP status is not the final decision. OpenCodex buffers a bounded pre-output prefix of the selected child's Responses SSE. If the stream reports a retryable `response.failed` terminal before any text, reasoning, tool call, or other output event, the child -is recorded as failed and the combo may try its next eligible target. Once any output event begins, +is recorded as failed and the combo may try its next eligible target. A zero-output +`response.incomplete` follows the same path when its reason is missing or is `adapter_eof`, +`missing_terminal_event`, or `upstream_stall_timeout`. Explicit semantic reasons such as +`max_output_tokens` and `content_filter` remain terminal, as does an explicit null reason. +A reasonless final snapshot that already contains output items is also kept. Once any output event begins, the target is committed: a later stream failure is returned to the client and is never replayed on another provider, which prevents duplicate text and tool execution. If the pre-output buffer reaches its safety cap without a terminal or output boundary, OpenCodex also commits the current target @@ -650,7 +788,23 @@ instead of growing memory without a bound. When routing Claude Code to the canonical ChatGPT Codex backend, OpenCodex removes the unsupported top-level `user` metadata field without changing the session/cache key, input messages, tool schemas, or safety identifiers. Public Responses API and noncanonical forward gateways keep that field. -A combo can also advance after an intact HTTP 400 `invalid_request_error` that specifically rejects `user`, reports `unsupported_value` for `reasoning.effort`/`reasoning_effort`, or reports `param: input` with an exact model-scoped `does not support image inputs` rejection. This is a mismatch for that request, not evidence that the target is unhealthy, so it records no cooldown. This compatibility recovery does not silently change `none` into a different effort or broaden this exception to arbitrary invalid requests. Policy refusals, cancellation and already-committed output remain non-replayable. A single-target request still returns an unresolved upstream rejection. +A combo can also advance after an intact HTTP 400 `invalid_request_error` that specifically rejects `user`, reports `unsupported_value` for `reasoning.effort`/`reasoning_effort`, or reports `param: input` with an exact model-scoped `does not support image inputs` rejection. A route that refuses the request's media with code `unsupported_input_modality`, such as image input sent to a CLI provider route without a verified multimodal contract, advances the same way. This is a mismatch for that request, not evidence that the target is unhealthy, so it records no cooldown. This compatibility recovery does not silently change `none` into a different effort or broaden this exception to arbitrary invalid requests. Policy refusals, cancellation and already-committed output remain non-replayable. A single-target request still returns an unresolved upstream rejection. + +## Tool calls stay on one target + +When a model asks your client to run a tool, the request that returns the tool result finishes the +same assistant turn. OpenCodex sends that request to the target that issued the tool call, as long +as the target is still enabled, still in the combo, and out of cooldown. This holds for every +strategy, including JEV, which skips its decision for that request. The next user message chooses a +target normally. + +Tracking uses the client's thread or session id, so a client that sends neither gets a normal pick. +If the original target is unavailable or fails, the combo fails over as usual, and the new target +receives the earlier tool calls in a form it accepts. DeepSeek thinking models get a one-space +reasoning placeholder for a tool call that another model issued without reasoning. Gemini gets +Google's documented signature placeholder. Anthropic gets a plain tool call and result. Earlier +versions could send DeepSeek a bare tool call here and fail with `The reasoning_text in the +thinking mode must be passed back to the API`. ## Default reasoning effort @@ -702,6 +856,25 @@ even when every target supports images — the catalog drops `image` from `input image-bearing requests are rejected with HTTP 400 before any target is called. `"auto"` (or omitting the field) keeps the automatic intersection. +In the dashboard, the **Image / multimodal** switch can be enabled whenever every target has +known image or text input capabilities. Native image members stay unchanged; text-only members are +automatically declared text-only on save (`modelCapabilities[model].inputModalities = ["text"]`) +so the [Vision Sidecar](/guides/sidecars/) describes their images. The switch's hint names the +members that will be enrolled. A member whose modalities are unknown or have no text input (for +example an audio-only model) cannot be covered by the sidecar; it keeps the switch unavailable and +is named in the hint. If the [Vision Sidecar](/guides/sidecars/) is disabled globally, enabling the +switch shows a warning linking to the dashboard: enrollment still saves, but images are replaced +with an omission note and requests continue without image descriptions until the sidecar is +re-enabled. Turning the switch off disables image input for the combo +but keeps those provider declarations, and `PUT /api/combos` accepts the same enrollment as an +optional top-level `visionSidecarTargets` array of exact `{ provider, model }` targets +(request-only; it is never stored on the combo and is rejected while `imageInput` is +`"disabled"` when the enrollment list is non-empty). Entries that already accept image input or +are declared without text are rejected with HTTP 400 rather than overwritten, entries the sidecar +already covers are skipped, and a save refused before publication restores the combo and every declaration together. +Removing a member from the combo does not remove its text-only declaration; clear it in the +provider editor if you no longer want it. + ## Encrypted v2 sub-agent tasks There is one important limitation for Codex v2 sub-agents ([issue #92](https://github.com/lidge-jun/opencodex/issues/92)). @@ -739,8 +912,9 @@ task workflow. ### Dashboard Open the local dashboard and choose **Models → Combos**. The workspace creates, edits, renames, and removes -combos, and its target picker excludes disabled models, nested combos, and the credential-only JEV -provider. **Create JEV Auto** opens the same Combo editor with an editable decision target template; +combos. Its model selectors include hidden models: catalog visibility only controls external model +lists, not internal Combo selection, and selecting a hidden model does not make it visible externally. +The target picker still excludes disabled providers, nested combos, and the credential-only JEV provider. **Create JEV Auto** opens the same Combo editor with an editable decision target template; an existing `jev-auto` id or alias is reported instead of creating a duplicate. A JEV Combo also shows **Decision service** (TypeSafe JEV or a configured `jev-decision` provider) and **Decision timeout (ms)**, and the Combos overview lists each JEV Combo's decision service, endpoint, and timeout. @@ -761,9 +935,10 @@ ocx combo remove --yes ``` `set` also accepts `--strategy`, `--sticky`, `--effort`, `--alias`, `--native-alias`, -`--display-name`, `--decision-provider`, `--decision-timeout`, and `--rename-from`. Use `-` as the -value of `--effort`, `--alias`, `--display-name`, `--decision-provider`, or `--decision-timeout` to -clear that field. The two decision flags apply only to `--strategy jev`. `--native-alias` requires a currently supported bare native +`--allow-metered-targets on|off`, `--display-name`, `--description`, `--decision-provider`, `--decision-timeout`, and `--rename-from`. Use `-` as the +value of `--effort`, `--alias`, `--display-name`, `--description`, `--decision-provider`, or `--decision-timeout` to +clear that field. The two decision flags apply only when the effective strategy is JEV: `--strategy jev`, or a stored JEV combo when `--strategy` is omitted. On an existing combo, `--targets` replaces only +the target list: without `--strategy` the stored strategy and its JEV settings stay as they are. `--native-alias` requires a currently supported bare native model alias and a non-empty display name. `create` and `update` are aliases for `set`; `delete` is an alias for `remove`; and the same subcommands are available under `ocx route combo`. @@ -828,6 +1003,7 @@ Combos are stored in the top-level `combos` object, keyed by combo id: | `alias` | No | none | Optional trimmed public model id; use the alias rules above. An empty value is stored as no alias. | | `nativeAlias` | No | `false` | Explicitly permit a currently supported bare native `alias` to take routing and catalog precedence. Never inferred from the alias. | | `displayName` | No | none | Bounded display-only catalog label. Required and non-empty when `nativeAlias` is true. | +| `description` | No | none | Display-only catalog row description, bounded like `displayName` (at most 128 characters, no control characters). Unset keeps the generated `Routed via opencodex → combo (combo).` text. Management API writes that omit it keep the saved value; an empty string clears it. | | `decisionProvider` | No | `"jev"` | JEV only. Provider id of the decision service: `"jev"` (TypeSafe, valid without a provider row; the same as omission) or a configured `adapter: "jev-decision"` row with a full HTTPS decision URL or a local HTTP `/systemone` URL, such as a self-hosted Ollama `tev1`. | | `decisionTimeoutMs` | No | `4000` | JEV only. Integer from 1000 to 120000: the decision deadline before failing open to the first eligible target. | @@ -843,7 +1019,10 @@ running opencodex instance that receives model requests. Every target is currently ineligible: for example, its provider is disabled, it is cooling down, it has already been attempted for this request, or an encrypted v2 task excludes it. Check target -provider state and recent upstream errors. For cooldowns, follow an observed `Retry-After` value first; +provider state and recent upstream errors. When no target was attempted, the error message lists +why each target was skipped, for example +`No available targets for combo: fast (openai/gpt-6: quota exhausted; anthropic/claude-opus-5-5: cooldown 42s)`. +For cooldowns, follow an observed `Retry-After` value first; Codex reset headers also take precedence over `cooldownMs`. If neither upstream signal is usable, the configured `cooldownMs` applies, or the upstream fallback applies when it is unset (5 seconds for request-rate codes `1302`/`1305`, 10 minutes for a spent usage @@ -861,3 +1040,31 @@ validation message. The error was terminal rather than target-specific. Fix invalid input, reduce an oversized context, handle a policy refusal, or correct the rejected request origin. Combos do not hop for those cases. + +## Retry all-cooled targets (opt-in) + +Combo cooldown recovery is opt-in and disabled by default. To allow one new +failover pass when every otherwise eligible target has a saved combo cooldown: + +```sh +ocx combo set review --cooldown-reprobe on --cooldown-reprobe-interval 300000 +ocx combo set review --cooldown-reprobe off +``` + +The corresponding per-combo configuration is: + +```json +{ "cooldownReprobe": { "enabled": true, "intervalMs": 300000 } } +``` + +The default interval is five minutes; accepted values are integer milliseconds +from 1000 to 86400000. Setting the interval alone does not enable recovery. +Recovery is demand-driven: the first eligible request can reserve a pass immediately, +and later passes are separated by at least the configured interval. There is no timer +or background traffic. Each target can be attempted once in the request's existing +failover budget; failures record fresh cooldowns. Account quota checks, disabled +providers, request compatibility, metered-spend permissions and cancellation remain +enforced. Explicit provider Retry-After values may therefore be retried early only +when the operator enables this option. Preview/list operations never clear cooldowns. +The interval is tracked per combo in memory and resets on proxy restart or combo +selection-state reset. Existing cooldown-wait behavior is unchanged when disabled. diff --git a/docs-site/src/content/docs/guides/desktop-app.md b/docs-site/src/content/docs/guides/desktop-app.md index 4ecad314138..73e9367061f 100644 --- a/docs-site/src/content/docs/guides/desktop-app.md +++ b/docs-site/src/content/docs/guides/desktop-app.md @@ -23,9 +23,10 @@ still blocks it, use **System Settings → Privacy & Security → Open Anyway**. ### Windows -Download `OpenCodex--windows-x64.msi` and run the installer. Windows SmartScreen may -warn because the installer is not yet code-signed; choose **More info → Run anyway** after -confirming that you downloaded it from the release page. +Download `OpenCodex--windows-x64.msi` and run the installer. Code signing through +SignPath Foundation is rolling out ([Windows Code Signing](/reference/code-signing/)). Until a +release carries the signature, Windows SmartScreen may warn about an unsigned installer; choose +**More info → Run anyway** after confirming that you downloaded it from the release page. ### Linux @@ -116,8 +117,12 @@ means the registration could not be read. While the app runs the proxy, the Star background service installer disabled; to use it instead, quit OpenCodex and run `ocx service install` in a terminal. The Codex launcher shim can still be installed there. When a Codex launch finds no proxy, the shim waits for OpenCodex to restart it, opens -OpenCodex on macOS if it quit, and otherwise reports an error instead of starting a second -proxy. If the shim reads "activation not confirmed", add +OpenCodex on macOS if it quit and its recorded ownership matches a valid **Start at Login** +registration, and otherwise reports an error instead of starting a second proxy. Automatic +reopening selects that registration's app bundle path and checks that its declared main +executable matches the registered Desktop executable. If registration is missing, stale or +inconsistent, open OpenCodex manually; another copy with the same bundle identifier is not +selected as a fallback. If the shim reads "activation not confirmed", add `. ~/.opencodex/codex-shell-env.sh` to your shell startup file after other PATH setup. The bundled CLI is `/Applications/OpenCodex.app/Contents/MacOS/ocx` on macOS and diff --git a/docs-site/src/content/docs/guides/fallback-off.md b/docs-site/src/content/docs/guides/fallback-off.md new file mode 100644 index 00000000000..424b158e01f --- /dev/null +++ b/docs-site/src/content/docs/guides/fallback-off.md @@ -0,0 +1,132 @@ +--- +title: "Fallback off" +description: Turn model and provider fallback off per scope, and see what happens on a Remote Link Child when its Home is unreachable. +--- + +OpenCodex can serve a request from a different model or provider when the one you chose fails: a +[combo](/guides/combos/) moves to its next target, a [routing profile](/guides/routing-profile-editor/) +tries its next candidate, a spawned subagent moves down its model fallback chain, and the main-account +hard lock can reroute to its configured fallback model. All of these stay on by default. + +Turn a kind of fallback **off** when you would rather get a clear error than an answer from somewhere +you did not pick. + +## What counts as a fallback + +A fallback is a hop to a **different model** or a **different provider**: + +| Kind | Example | Switch | +|---|---|---| +| Model fallback | `anthropic/claude-opus` fails, `anthropic/claude-sonnet` would answer | `model` | +| Provider fallback | `anthropic/claude-opus` fails, `openai/gpt-6-sol` would answer | `provider` | + +Rotation among the accounts or API keys of **one provider pool** for the same model is not a fallback. +It is how a pool absorbs a rate-limited account, and it keeps working whatever you set here. Retrying +the same target, and transport fallbacks that keep the same target, are not fallbacks either. + +When a main-account hard lock routes to a combo or profile, it refuses early only when every declared +destination is known to be policy-denied. Otherwise normal selection proceeds without filtering, and +its actual selected destination still follows these switches. Allowing provider fallback does not allow +a different model on the same provider when model fallback is off, even if another candidate is allowed. + +## Turn fallback off + +```bash +ocx fallback off # model and provider fallback, everywhere +ocx fallback off --kind provider # only cross-provider hops +ocx fallback off --route combo/coding # one combo, routing profile or model selector +ocx fallback off --provider anthropic # hops that leave one provider +ocx fallback on --route combo/coding # an exception under a global off +ocx fallback clear --route combo/coding # remove an override +ocx fallback status # current settings and the last fallback +``` + +The same switches are on the dashboard: the **Fallback** card at the bottom of the **Dashboard** holds the +global switches, the overrides and the last fallback taken or refused. + +### Precedence + +1. A **device** set to off always refuses (see [per device](#per-device-settings)). A device setting can + only refuse; turning it on never reopens a hop another scope refuses. +2. Otherwise the most specific setting wins: **route**, then **provider**, then **global**. +3. Nothing set means allowed, which is today's behavior. + +The settings live in `config.json` under `fallback`: + +```json +{ + "fallback": { + "provider": false, + "routes": { "combo/coding": { "provider": true } }, + "providers": { "anthropic": { "model": false } }, + "devices": { "lnk_0123456789abcdef": { "localFallback": false, "model": false } } + } +} +``` + +A malformed leaf is ignored, so policy resolution uses the next applicable scope, and `ocx config` +diagnostics name it. A whole-config write that contains one is refused instead. + +## The error you get + +A refused fallback answers with the **status of the failure that triggered it**, so a refused 429 is +still a 429 with its `Retry-After`, and with this body: + +```json +{ + "error": { + "type": "fallback_refused", + "code": "fallback_refused", + "message": "combo/coding: anthropic/claude-opus failed (rate_limited). Provider fallback to openai/gpt-6-sol is off for route combo/coding, so OpenCodex did not send the request there. To allow it: ocx fallback on --kind provider --route combo/coding", + "fallback": { "kind": "provider", "scope": "route", "scopeKey": "combo/coding", + "from": "anthropic/claude-opus", "to": "openai/gpt-6-sol", "trigger": "rate_limited" } + } +} +``` + +The response also carries `x-opencodex-fallback: refused`. When the first target is already known to be +unavailable, the refusal happens before anything is sent, and the fallback target is never contacted. +The status depends on why the first target was skipped: + +- A combo's first target cooling down, or with its quota spent: 429. +- A combo's first target disabled or not usable for the request: 503. +- A blocked main account: 429. +- An unavailable subagent model: 503. + +After a real upstream failure, the refusal keeps that failure's status. + +## Remote Link Children and Hub clients + +A Remote Link Child and a Hub client never serve requests from their own local providers. When the +Home is unreachable, a Child answers a retryable `503` (and a Hub client's dashboard relay a `502`) with: + +```json +{ "error": "link tunnel unavailable", "code": "home_unreachable", "message": "The Home is unreachable, and OpenCodex did not serve this request from this device's local providers. …" } +``` + +Nothing is sent to a local provider, and the link key and the request body never leave the device. + +### Per-device settings + +On the Home, each linked device has two switches, on **Remote Link** or with the CLI: + +```bash +ocx fallback local on --device mac-studio # permission for local fallback while the Home is down +ocx fallback off --device mac-studio # refuse model and provider fallback for this device's requests +``` + +`--device` takes a link alias or id from `ocx link status`, or a Hub data key id. + +**Local fallback** is a permission that GOAT reads: with it on, GOAT may move that device's work to the +device's own sign-ins while the Home is unreachable. OpenCodex itself still answers `home_unreachable` +and never serves the device locally. It is off unless you turn it on. + +## Status + +`ocx status --json` includes a `fallback` object (on a Child it is the Home's, read over the link), and +`GET /api/fallback` returns the same `ocx.fallback/1` document: the effective global switches, the +overrides, every device with its local fallback permission, and the last 20 fallbacks taken or refused +with time, route, from, to, trigger and the scope that decided. The list is kept in memory and resets +when the proxy restarts. It never contains request bodies, keys or account identities. + +A Home that supports this advertises `fallback-policy/1` in `GET /readyz` `features`. diff --git a/docs-site/src/content/docs/guides/integrations.md b/docs-site/src/content/docs/guides/integrations.md index 5bd17d8072e..fdb813c12b6 100644 --- a/docs-site/src/content/docs/guides/integrations.md +++ b/docs-site/src/content/docs/guides/integrations.md @@ -1,15 +1,16 @@ --- title: Integrations -description: Connect opencodex to OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, gjc, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Aside, Raycast, omo, Cline CLI, Kilo, Command Code and Factory Droid from the dashboard — one switch per client, with a backup taken before every write. +description: Connect opencodex to OpenCode, Pi, Gentle Shell, OMP, Hermes, OpenClaw, Kimi Code, gjc, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Aside, Raycast, omo, Cline CLI, Kilo, Command Code and Factory Droid from the dashboard — one switch per client, with a backup taken before every write. --- The **Connect** page writes opencodex's provider block into a client's own config -file, and removes it again. Nineteen clients work this way, each with a switch: +file, and removes it again. Each client below has its own switch: | Client | Config file | Format | When the change takes effect | Credential | |---|---|---|---|---| | OpenCode | `~/.config/opencode/opencode.json` | JSON | next direct launch | `OPENCODEX_OPENCODE_API_KEY` | | Pi | `~/.pi/agent/models.json` | JSON | new sessions | loopback placeholder | +| Gentle Shell | `~/.gentle-shell/agent/models.json` (persistent selected home) | JSON | after restarting Gentle Shell | loopback placeholder | | OMP | `~/.omp/agent/models.yml` | YAML | after restarting OMP | `opencodex-loopback` placeholder | | Hermes | `~/.hermes/config.yaml` | YAML | new sessions | `OPENCODEX_HERMES_API_KEY` | | OpenClaw | `~/.openclaw/openclaw.json` | JSON5 | immediately, on a running gateway | `OPENCODEX_OPENCLAW_API_KEY` | @@ -23,17 +24,84 @@ file, and removes it again. Nineteen clients work this way, each with a switch: | Raycast | `~/.config/raycast/ai/providers.yaml` | YAML | immediately on save — Raycast watches the file | none — loopback only | | omo (Pi / senpi) | `~/.omo/agent/models.json` | JSON | new sessions | loopback placeholder | | Qoder | `settings.json` in `$QODER_CONFIG_DIR`, default `~/.qoder` | JSON | new sessions | loopback placeholder | +| Qoder CN | `settings.json` in `$QODERCN_CONFIG_DIR`, default `~/.qoder-cn` | JSON | new sessions | loopback placeholder | +| WorkBuddy (CN) | `~/.workbuddy/models.json` | JSON | auto reload / new sessions | loopback placeholder | +| WorkBuddy AI | `~/.workbuddy-ai/models.json` | JSON | auto reload / new sessions | loopback placeholder | | Cline CLI | `~/.cline/data/settings/providers.json` and sibling `models.json` | JSON pair | after stopping and restarting Cline | loopback placeholder | | Kilo | first existing `kilo.jsonc`, `kilo.json`, `opencode.jsonc`, `opencode.json`, or `config.json` under `~/.config/kilo` | JSONC | new sessions | `OPENCODEX_KILO_API_KEY` | | Command Code | `~/.commandcode/providers.json` (on Windows with the default home: use `HOME ?? USERPROFILE` only if nonblank and Windows-absolute, otherwise `homedir()`; a separately supplied home is preserved; append `.commandcode\providers.json`) | JSON | next launch of Command Code | none — keyless loopback (`apiKey: false`) | | Factory Droid | `~/.factory/settings.json` (`%USERPROFILE%\.factory\settings.json` on Windows) | JSON | immediately via file watching | none — keyless loopback | +## Gentle Shell + +Choose **Connect → Gentle Shell**, or use the same managed-integration commands: + +```bash +ocx export --client gentle-shell --json +ocx integration client enable --client gentle-shell +ocx integration client disable --client gentle-shell +``` + +Gentle Shell has a separate destination and rollback history from Pi. Only +`providers.opencodex` is managed; other providers and preferences stay intact. +Catalog changes refresh an already connected, unchanged block. An existing manually +copied block is a conflict, not permission to overwrite it: inspect the preview and +use the explicit overwrite confirmation only if you intend to replace it. + +The saved `home` in `~/.gentle-shell/config.json` selects the destination. A saved +custom path wins; isolated/default mode uses `GENTLE_SHELL_HOME` or +`~/.gentle-shell/agent`. `GENTLE_SHELL_CONFIG` selects an alternate launcher config. +These environment variables must be set on the **OpenCodex process**, not just in a +later shell. Selectors must be absolute paths; literal `~` and relative paths refuse. +One-off `gentle-shell --home` flags cannot be discovered by a background proxy. + +Link mode or a home aliasing Pi refuses with a Pi-integration remedy; use **Connect → Pi** +for that shared file instead. OpenCodex does not change launcher preferences. An +unreadable/unsafe launcher config refuses; malformed JSON falls back to isolated, +matching the launcher. If you move a connected home, disable it using its original +selectors before reconnecting the new one. Restart Gentle Shell after a catalog update. +This integration retains Pi’s loopback-only export policy and stores no real API key. + "omo" names three products that share the `~/.omo` folder. The **omo** tab manages Pi-based omo (the senpi engine) through `~/.omo/agent/models.json`, as in the table above. Codex-based omo (LazyCodex) gets its own section on the same tab, shown only when LazyCodex is detected, described in [omo (Codex / LazyCodex) role models](#omo-codex--lazycodex-role-models). OpenCode-based omo (oh-my-opencode) keeps its own config under OpenCode; opencodex does not read or write it. +### Qoder CN and WorkBuddy editions + +In **Connect**, enable **Qoder CN**, **WorkBuddy (CN)** or **WorkBuddy AI**, then choose +an OpenCodex model in the client's custom-model picker. The proxy must stay running. +The first enable saves a snapshot; disable removes only the generated contribution, +and Undo restores the snapshot. Existing models and unrelated settings remain intact. +These are separate connections from Global Qoder and from each other. + +The same operations are available through the CLI (replace `workbuddy` with +`workbuddy-ai` or `qoder-cn` for the other editions): + +```bash +ocx integration client enable --client workbuddy +ocx export --client workbuddy --json +ocx integration client disable --client workbuddy +``` + +Qoder CN honors `QODERCN_CONFIG_DIR`. Both WorkBuddy editions honor their native +`WORKBUDDY_CONFIG_DIR`, then `CODEBUDDY_CONFIG_DIR`; unset overrides keep the regional +roots in the table. Overrides must be absolute, `~`, or start with `~/` or `~\`. WorkBuddy's legacy +array configuration and its object-form `models` list are both supported. If an existing +`availableModels` restriction excludes OpenCodex models, remove that restriction in +WorkBuddy before enabling. Project-level settings can override user-level models. + +Connections require an unauthenticated loopback listener; no upstream credentials are +copied into these clients. WorkBuddy receives the full Chat Completions URL and the +capabilities declared by the OpenCodex catalog. Restart the client or start a new session +if its model picker has not reloaded yet. Configuration/lifecycle tests do not establish +end-to-end compatibility with every upstream model or every client release. + +Doubao Desktop and Devin currently have no verified arbitrary-provider configuration +contract in this integration. They have no enable switch; adding a local JSON file alone +would not make their model pickers consume it. + Qoder IDE / qodercli supports export and managed configuration through the Dashboard and CLI: ```bash @@ -43,15 +111,22 @@ ocx integration client disable --client qoder ``` Qoder uses `settings.json` in `QODER_CONFIG_DIR` when that variable is set, and -`~/.qoder/settings.json` otherwise; a relative `QODER_CONFIG_DIR` is refused. Enable merges `providers.opencodex` and -OpenCodex entries in `modelConfigs.customModels`, preserving other providers, custom models, -and settings. Disable removes the recorded OpenCodex entries; restore uses the existing +`~/.qoder/settings.json` otherwise; a relative `QODER_CONFIG_DIR` is refused. Enable merges only `providers.opencodex`, preserving other providers, custom models, +and settings. Models appear once in the provider catalog; duplicating them in +`modelConfigs.customModels` causes Qoder to reject the provider. Refreshing an older managed +integration removes its recorded duplicate rows; user-edited rows remain conflicts. Disable removes the recorded OpenCodex entries; restore uses the existing snapshot workflow. The provider's default model is the first model in the sorted export. -Custom models use their catalog context window, falling back to 128000 input tokens. +If `modelConfigs.customModels` already holds an `opencodex` row for an exported model that +OpenCodex did not write, enable, refresh, overwrite and disable report a conflict and leave the +file unchanged; remove or rename that row in Qoder's settings, then try again. Rows for other +providers or for models outside the export are left alone. +Provider models use their catalog `contextWindow`, falling back to 128000 input tokens. +Restart Qoder after refreshing an existing integration to reload the provider registry. The generated API key is a non-secret placeholder: Qoder integration requires loopback access and refuses an authenticated non-loopback bind. When the dedicated unauthenticated loopback listener is enabled, exports point Qoder at that listener. `ocx sync` and catalog changes refresh an already enabled Qoder integration like the other managed clients. +Model IDs containing `,` or `]` (for example `kimi/k3[1m]`) are exported like any other model. Generated catalogs include only enabled models from each provider selection. This applies to both downloads and managed integrations, including Pi and Aside. The management model list still shows @@ -268,7 +343,12 @@ parse, or one whose structure we cannot reason about, still refuses. ## Hermes session affinity The generated `providers.opencodex` block includes `session_affinity_header: session-id` for all -models. This names a header; Hermes supplies its dynamic conversation identifier. OpenCodex does +models. Retain this generated default unless you deliberately choose log-only correlation: +changing `session_affinity_header` from `session-id` to `x-hermes-conversation-id` replaces the +header sent by Hermes. Because OpenCodex treats `x-hermes-conversation-id` strictly as a request-log +correlation fallback and not as a native routing or turn-termination identity, switching to +`x-hermes-conversation-id` removes native session affinity and lane assignment while correlating +agent loops in request logs. This names a header; Hermes supplies its dynamic conversation identifier. OpenCodex does not write a shared static identifier or change `api_mode` to enable affinity. Use a Hermes version supporting [per-provider request options](https://hermes-agent.nousresearch.com/docs/user-guide/configuring-models#per-provider-request-options). @@ -287,6 +367,28 @@ exception to the conflict rule above: other edits, a different header name, or a matching ownership record still require conflict resolution. Unrelated YAML settings and comments remain untouched, and the existing snapshot and Restore workflow applies to the upgrade. +## Hermes profiles + +Hermes keeps a separate home for each bot profile. The default profile uses +`~/.hermes/config.yaml` (`%LOCALAPPDATA%\hermes\config.yaml` on Windows, or `$HERMES_HOME/config.yaml`), +and each named profile created with `hermes profile create ` uses +`/profiles//config.yaml`. See Hermes' +[profiles guide](https://hermes-agent.nousresearch.com/docs/user-guide/profiles). + +When named profiles exist, **Connect → Hermes** lists every profile with its own status. Select a +profile to apply, disable, replace, or restore OpenCodex for that profile alone; each profile keeps its +own backups and rollback history, and the other profiles stay unchanged. Every profile keeps its own +`model.default` and other providers: OpenCodex adds only `providers.opencodex`. With no named profiles, +the page works as before and changes the default profile. + +The management API selects a profile with `profile=` on `/api/client-integrations/hermes` and with +`client=hermes&profile=` on the preview, journal, and restore endpoints; +`GET /api/client-integrations/hermes/profiles` lists the profiles. A directory under `profiles/` counts +only when Hermes would list it: a valid name, a Hermes identity file such as `config.yaml` or `SOUL.md`, +and no deletion marker. A profile reached through a symbolic link or junction is not offered. If +`HERMES_HOME` later points at a different Hermes home, a profile with the same name there is refused +until you disconnect the original one, so OpenCodex never loses track of a file it changed. + ## Preview and confirm changes In the dashboard, Apply, Replace, Disable, and Restore begin with a preview. The dialog shows exactly which @@ -307,6 +409,8 @@ profiles** remains a separate bulk action and is not bound to one combined previ ## What to expect, honestly +If a generated managed path cannot be represented safely, that client reports an unsafe status and its preview refuses the operation. Attempts to apply, overwrite or disable return HTTP 409 without changing the configuration. Other clients remain available. + **Managed configuration files are limited to 4 MiB (4,194,304 UTF-8 bytes).** The shared integration reader refuses larger files as unsafe to read, and the writer refuses a merged configuration that exceeds the same limit with `write_failed`, leaving @@ -715,7 +819,7 @@ every Codex agent role found in `$CODEX_HOME/agents/*.toml`, with the model each to. Codex runs a role on that pin no matter which model the parent asks for, so this is where a role's model is actually decided. LazyCodex counts as installed when the `omo@sisyphuslabs` Codex plugin is enabled in `$CODEX_HOME/config.toml` and an installed copy under -`$CODEX_HOME/plugins/cache/sisyphuslabs/omo/` carries its `lazycodex-install.json`. A `~/.omo` +`$CODEX_HOME/plugins/cache/sisyphuslabs/omo/` carries its plugin manifest or `lazycodex-install.json`. A `~/.omo` folder on its own does not count, because Pi-based omo creates it too. Without LazyCodex the section is hidden and the command line reports it as not installed. Pick a model and a reasoning effort on a row and press Save. A row whose role has no model pin can save an effort on its own; diff --git a/docs-site/src/content/docs/guides/minimax.md b/docs-site/src/content/docs/guides/minimax.md index db01d782d7a..121bf88c9c1 100644 --- a/docs-site/src/content/docs/guides/minimax.md +++ b/docs-site/src/content/docs/guides/minimax.md @@ -13,7 +13,7 @@ the protocol boundary it actually exposes: ## MiniMax Code In the OpenCodex Models page, both MiniMax Coding Plan regions advertise image input for -`MiniMax-M3` and `MiniMax-M3.1-Flash-Preview`. A Combo's **Image / multimodal** switch is +`MiniMax-M3` and `MiniMax-M3.1-Flash`. A Combo's **Image / multimodal** switch is available only when every selected model supports image input. The switch controls image attachments; it does not enable video uploads. diff --git a/docs-site/src/content/docs/guides/model-routing.md b/docs-site/src/content/docs/guides/model-routing.md index 4ccfde4b97a..772a078eb74 100644 --- a/docs-site/src/content/docs/guides/model-routing.md +++ b/docs-site/src/content/docs/guides/model-routing.md @@ -117,6 +117,8 @@ Routing and catalog visibility are separate controls: It raises the advertised window only when that exact model has a known per-model window in `modelContextWindows` (for example, `1000000`); an unknown model keeps its live window. An explicit per-model window wins, then the existing provider cap applies. + Saved windows and tier selections survive startup and OAuth re-login. OAuth presets fill + missing per-model defaults while keeping your configured entries, including on xAI. The `"default"` tier retains normal live context metadata. The dashboard control is planned separately. diff --git a/docs-site/src/content/docs/guides/pi.md b/docs-site/src/content/docs/guides/pi.md index 03b1886a8e0..741b56d4298 100644 --- a/docs-site/src/content/docs/guides/pi.md +++ b/docs-site/src/content/docs/guides/pi.md @@ -147,12 +147,14 @@ An explicit reasoning effort of `none` survives Chat conversion. Output limits a controls are preserved for generic API-key Responses targets; the canonical ChatGPT target still applies its own restrictions. This does not make all providers' controls equivalent. -**Audio and most file attachments need a native input wire that supports them.** A document -that carries its own base64 bytes in a user message is the exception: it survives translation -and reaches the Anthropic, OpenAI Chat and Google wires as a native document, file part and -inline data part. Everything else still returns an explicit error rather than succeeding -without the attachment — audio, a file-ID or remote reference the proxy cannot dereference, an -attachment in a tool output or a system message, and a document routed to a wire with no byte +Inline WAV/MP3 audio in a user message or tool result survives translation to OpenAI Chat as an +`input_audio` content part; tool-result audio is promoted into a follow-up user message after +the paired tool results because Chat tool messages are text-only. A document that carries its +own base64 bytes in a user message is also carried: it reaches the Anthropic, OpenAI Chat and +Google wires as a native document, file part and inline data part. Everything else still +returns an explicit error rather than succeeding without the attachment — remote or +unsupported audio, a file-ID or remote reference the proxy cannot dereference, an attachment +in a system message, a document in a tool result, and a document routed to a wire with no byte carrier. File-ID-only images have the same restriction because translated adapters cannot resolve those IDs. Convert the attachment to text first, or use a native wire and model that support it. Native Chat and raw Responses (including Azure) retain their existing behavior; diff --git a/docs-site/src/content/docs/guides/protocol-paths.md b/docs-site/src/content/docs/guides/protocol-paths.md index 0c0f5c93fb9..2e5ac6de19c 100644 --- a/docs-site/src/content/docs/guides/protocol-paths.md +++ b/docs-site/src/content/docs/guides/protocol-paths.md @@ -102,7 +102,7 @@ These switches stage the new paths. Each defaults to off except `antigravityDire | `managedMessagesNative` | A Messages request whose route is a direct, key-authenticated Anthropic provider is sent as Messages instead of through the bridge. Routes that need bridge-only behaviour (a pinned effort, blocked-skill elision, the web-search sidecar, vision preprocessing, synthetic rows) stay on the bridge. | | `managedMessagesNativeOAuth` | Native Messages for the unpooled `anthropic` OAuth provider on `api.anthropic.com`. Effective only together with `managedMessagesNative`; a pooled Anthropic OAuth account set stays on the bridge. | | `directEncoders` | For a non-Responses upstream, the answer to a Chat or Messages client is encoded directly from the adapter's events instead of through the internal Responses stream. The request side is unchanged. | -| `antigravityDirect` | **On by default**; set it to `false` to turn it off. A Chat or Messages request whose route is one Google Antigravity provider skips the internal Responses body in both directions: the conversation is decoded from the client body straight into what the Google adapter compiles from, and the answer is encoded directly as with `directEncoders`. The path reads `chat -> ir -> other` or `messages -> ir -> other`. Request settings (model, effort, limits, tool choice) still pass through the usual policies, so account rotation, effort rules and usage are unchanged. Combos, policies, a hosted web-search tool, images for a model marked text-only, a model the shadow-call intercept could rewrite, and text the proxy rewrites (a skills catalog, a generated-image link) stay on the bridge, and the log row names the reason. | +| `antigravityDirect` | **On by default**; set it to `false` to turn it off. A Chat or Messages request whose route is one Google Antigravity provider skips the internal Responses body in both directions: the conversation is decoded from the client body straight into what the Google adapter compiles from, and the answer is encoded directly as with `directEncoders`. The path reads `chat -> ir -> other` or `messages -> ir -> other`. Request settings (model, effort, limits, tool choice) still pass through the usual policies, so account rotation, effort rules and usage are unchanged. Combos, policies, a hosted web-search tool, images for a model marked text-only, a model the shadow-call intercept could rewrite, and text the proxy rewrites (a skills catalog, a generated-image link) stay on the bridge, and the log row names the reason. On a Chat request the decoded conversation counts toward the shared translation budget, so a request whose retained IR exceeds it is refused with HTTP 413 and `translation_buffer_limit` before anything goes upstream. | | `shadowPlan` | At the end of each Chat or Messages request, the plan a preview would have predicted is compared with the path the request took; a disagreement adds `planMismatch: true` to the log row's path record. No second request is sent. | Change them with the CLI or by editing `protocols` in `config.json` @@ -160,5 +160,11 @@ These paths still use the internal Responses bridge or are not covered: top-level fields outside its allowlist without a feature effect, and does not apply `claudeCode.stabilizePromptCache`. A recognized Claude Code client keeps `context-management-2025-06-27` together with its - `context_management` body field. Dropped beta names appear once in the proxy log as - `anthropic-beta-dropped=`. + `context_management` body field. For first-party Anthropic key and OAuth requests, any native + Messages client can send `thinking-binding-controls-2026-08-01` with `thinking.block_binding` + on adaptive or enabled thinking. The proxy strips that nested field when the beta is absent or + the destination does not support it, preserving the rest of the thinking configuration. Cloud + vendor endpoints and third-party compatible providers do not inherit first-party beta support. + Pairing uses the final header, including operator-configured betas; a beta alone is valid for + [thinking transformation diagnostics](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking). + Dropped beta names appear once in the proxy log as `anthropic-beta-dropped=`. diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index 520161b511b..f2d5f813a86 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -52,6 +52,8 @@ reset is expected to recover as `+N% pool capacity`. **Incomplete coverage** mea accounts could not safely contribute to the estimate, for example because their plan or quota is unknown, their reading is stale, or the account is paused or needs reauthentication. +Business Premium seats reported as `self_serve_business_prolite` count as five Standard-seat units in this estimate. This display weight does not select accounts or create a five-hour window when the upstream reports only weekly usage. The calibration follows the [official Business usage limits](https://help.openai.com/en/articles/12003714-chatgpt-business-models-and-limits), checked 2026-10-20; unknown plans continue to count at the baseline with the unknown-plan count disclosed. + A **partial window coverage** warning means some included accounts reported one quota window but not another. The overview keeps those windows separate and marks each affected window incomplete instead of treating the missing reading as usage for that window. @@ -64,6 +66,17 @@ Shipped v1 configs migrate automatically to marker 2 and one option-aware row. T is retained once at `~/.opencodex/config.json.pre-openai-tiers-v2.bak`; restore it with `cp ~/.opencodex/config.json.pre-openai-tiers-v2.bak ~/.opencodex/config.json`. +## Claude Opus 5.5 progress updates + +When the `anthropic` adapter sends Claude Opus 5.5 to `api.anthropic.com` (OAuth or API key), +OpenCodex requests Anthropic's [progress-update display mode](https://platform.claude.com/docs/en/build-with-claude/thinking#progress-updates). +Progress updates and mid-turn acknowledgements appear as visible assistant commentary in Codex, +even when thinking summaries are hidden. Reasoning summaries are omitted in this mode because +Anthropic cannot distinguish progress updates from reasoning under `display: "summarized"`. +The original signed thinking blocks are preserved for tool continuation; no signature is decoded +or rewritten. Updates appear once their signed block completes. Other models and compatible +third-party endpoints retain their existing thinking display behavior. + ## Anthropic image input The built-in Claude model seeds advertise text and image input for both `anthropic` (OAuth) and @@ -76,6 +89,26 @@ modalities are declared; Kimi omits `capabilities` for unknown or text-only mode a supported capability field keep their existing configuration shape. After updating opencodex, regenerate or refresh the client configuration managed by opencodex to receive the updated metadata. +## Replayed Anthropic thinking signatures + +On the Responses endpoint, an Anthropic-compatible upstream can reject a replayed thinking +signature because it belongs to a different conversation prefix. When the error explicitly +identifies this signature-binding problem, opencodex can remove replayed signed/redacted thinking +and retry once within the request's send budget. This covers HTTP 400 refusals and initial stream +errors before output or tool activity. Hosted web search and image/video bridges share one recovery +limit across their model iterations: only the rejected inference is resent, while completed sidecar +calls and tool results are retained. An empty-completion retry shares its request's allowance; +ordinary continuations after output cannot use it. A failed replacement is +returned without another signature retry. Other errors keep their existing handling. + +Sidecar recovery is disabled when enforced spend accounting cannot establish receipts for earlier +ordinary sidecar sends. It never increases a caller or Combo send allowance. + +Only a successful, nonempty completion remembers the rejection for that conversation and serving +identity for five minutes. Only the refused opaque payloads are suppressed when they can be +identified; newly issued thinking remains replayable. Plaintext history remains available; a failed or cancelled retry does +not disable thinking replay on later requests. + ## Auth modes Provider configs accept three `authMode` values (`key` is the default). The built-in registry also @@ -90,8 +123,29 @@ labels local presets separately; those normally omit both `authMode` and `apiKey The [`retryOn429`](/reference/configuration/) same-key 429 replay applies only to API-key providers (`authMode: "key"`). OAuth, forward, and local presets are excluded — their credentials must never be replayed on the same token, and local runtimes have no remote key to -preserve. It is opt-in: when the option is absent the feature is off; object presence enables -it unless `enabled: false`. +preserve. It is opt-in except for documented destination-specific defaults. Canonical Mistral +Chat uses a patient retry policy (6 replays, 10-second fallback interval), within the +existing request-wide send allowance. Explicit `retryOn429` settings, including +`enabled: false`, take precedence. The Mistral preset also forwards supplied prompt-cache +keys by default; `promptCacheKey: false` disables forwarding. Cache affinity improves the +chance of reuse, but does not guarantee a cache hit or remove token-throughput limits. + +Mistral uses one API-key provider for both Studio and Vibe keys. Key types and plan allowances +are described in [Mistral's API-key documentation](https://docs.mistral.ai/admin/identity-access/api-keys); +the proxy does not infer a subscription tier from the key. For every model, explicit upstream +`Retry-After` deadlines are shared between calls on the same credential and model. A 429 +without a retry time can activate a conservative 60-second recovery wait when valid minute +counters show exhausted capacity, or the refused query's measured cost exceeds the remaining +tokens while fitting the full advertised allowance. This is a local recovery policy, not an +exact server-reset prediction. Healthy traffic never predicts the next query's cache or cost. +After the wait, one recovery probe runs; success restores ordinary traffic. Missing, malformed, +zero-entitlement, exhausted-monthly, or above-full-allowance evidence does not invent a reset or compact history. +`requestPacing: { "enabled": false }` or `retryOn429: { "respectRetryAfter": false }` +disables shared pacing. If Mistral's +retry time exceeds `retryOn429.maxIntervalMs`, the proxy returns the 429 and retry hint rather +than replaying early. Context windows and per-model compaction preferences remain independent. + +When round-robin key selection is enabled, queued requests recheck key cooldowns before sending and acquire pacing capacity for any replacement key. This applies to native Chat, Messages, Responses and compact. Waiting for replacement-key capacity does not consume the upstream response-header timeout. Per-key pacing and round-robin use guarded HTTP/SSE even when upstream WebSockets are enabled; WebSocket-only steering and injection require a route without those key policies. This does not change the saved active key. An established upstream turn keeps its credential; later sends are refused if that key cools. Pre-output key rejection can recover to a healthy key within the existing request limit. Direct Images and compact requests record genuine 401/429 cooldowns without replaying that paid POST. ### Anthropic API-key credit exhaustion @@ -145,6 +199,9 @@ Cursor, Kiro, Zed Hosted AI and Nous Portal are login-only and have no API-key e login-only too: `ocx login google-antigravity` signs in with your Google account over the Cloud Code Assist wire, and the `google` preset beside it is the AI Studio Gemini API — a different product reached with its own key, not a key mode for the same login. +The Antigravity Accounts tab shows an observed membership tier as a badge beside the account +label, using the upstream tier ID (for example `free-tier` or `gemini-pro`). No badge appears +when the plan is unknown; it does not affect routing or quota. Gemini 4 Argon (`gemini-4-argon`) is listed on the `google` preset with low, medium (the default), and high thinking levels. Google offers it on the paid Gemini API tier only, not the free @@ -154,6 +211,28 @@ eligible Enterprise accounts. Cloud Code Assist serves Argon only to the Antigra OpenCodex sends Argon requests and model discovery with the CLI User-Agent (`antigravity/cli/1.3.4`) and keeps the IDE User-Agent for every other model. +On Vertex AI, `gemini-4-argon` receives images returned by tools, such as screenshots, inside +the matching function response. This avoids the upstream 400 error "Requests ending with a +model turn are not supported" after an image-producing tool call while preserving the images. +Images from duplicate results or results without a matching call are preserved in a separate +message after the tool responses, with a marker explaining that they could not be paired. +No configuration change is needed; this handling is specific to Vertex Argon. + +For Gemini on Antigravity, including Argon, provider-marked thought summaries stay separate +from the answer: Responses uses reasoning summary items, Chat Completions uses +`reasoning_content`, and Messages uses `thinking` blocks. Clients decide how to display those +fields. A Responses client can request `reasoning.summary: "none"` to suppress summaries; +this does not turn off the model's internal reasoning. A thought signature is continuation +metadata and can accompany ordinary text, so its presence alone does not hide that text. +OpenCodex preserves unmarked answer text even when its wording sounds like planning or analysis. + +Gemini models, Argon in particular, sometimes skip thinking on a tool-calling turn and write their +planning into the visible progress text, for example a progress line followed by "Let's read +google.ts next." Once that text is sent back as history, the model tends to repeat the pattern +on every later turn. To stop it from spreading, OpenCodex sends Gemini only the first sentence of +the text that came before a tool call in earlier turns. Final answers are sent back in full, and +what your client displays is not changed. + The built-in `google-antigravity` model list is Gemini 3.8 Flash (`gemini-3.8-flash`), Gemini 4 Argon (`gemini-4-argon`), Claude Opus 5.5 (`claude-opus-5-5`), and Claude Sonnet 5.5 (`claude-sonnet-5-5`), each with low, medium, and high efforts. What you actually see is your @@ -161,9 +240,25 @@ account's own model discovery, because Google enables models per account: some a Claude Sonnet 4.6 and Claude Opus 4.6 (Thinking), others only Claude Opus and Sonnet 5.5 (directly billed, non-trial Pro or Ultra plans), and Gemini 4 Argon only on Ultra and Enterprise. Both Claude generations map: 4.6 takes its effort as a thinking level, 5.5 as its `-low`, `-medium`, or -`-high` model. With several Antigravity accounts and `oauthAccountFailover.enabled`, a request -prefers an account whose discovery lists the requested model. Saved selections of models that left -the list keep routing as before. +`-high` model. + +Antigravity requests go only to accounts that may serve the requested model. OpenCodex remembers +each account's model list from that account's own discovery, in memory, for six hours. An account +whose list includes the model is preferred; an account with no current list (not yet discovered, +expired, or forgotten by a restart) is still used, because nothing proves it lacks the model. An +account whose current list omits the model is never sent the request, including on 429 and +authentication failover. If the selected account omits it and `oauthAccountFailover.enabled` is on, +the request moves to another account that is not paused or awaiting reauthentication. Otherwise +OpenCodex refuses the request locally with HTTP 403 `permission_denied` and sends nothing upstream: + +- `The selected Google Antigravity account does not list the model "" in its model discovery. Select an account that lists it, or enable oauthAccountFailover so OpenCodex can choose one.` + Another usable account may serve the model, but failover is off. +- `The selected Google Antigravity account does not list the model "" in its model discovery, and no account that may serve it could be used for this request. Retry the request.` + Failover is on and chose another account, but that account could not be used for this request. +- `No usable Google Antigravity account lists the model "" in its model discovery. Choose a model your accounts list, or add an account that has access to it.` + Every usable account's current list omits the model. + +A saved selection of a model that left the list follows the same rule. To check which mode a provider is actually using, open it on the Providers page: the **Connection** block's **Authentication** row reads `OAuth`, `API key`, `ChatGPT passthrough`, `Local`, or @@ -213,6 +308,7 @@ ocx login zed # Zed native-app callback login (experimental) ocx login command-code # Command Code browser OAuth (or import ~/.commandcode/auth.json) ocx login orcarouter-oauth # OrcaRouter browser consent + PKCE ocx login devin # Cognition/Devin: import Devin CLI credential, else Auth0 browser sign-in +ocx login dsh-account # DeepSeek Harness Desktop (import ~/.dsh/.credentials.yaml grant) ocx login github-copilot # GitHub device flow → Copilot token (Copilot account) ocx login codex # Codex account pool (aliases: chatgpt, openai; needs a running proxy) ocx logout @@ -222,7 +318,7 @@ ocx logout | --- | --- | --- | --- | | `xai` | `openai-chat` | `https://cli-chat-proxy.grok.com/v1` | OAuth uses the separate Grok CLI subscription gateway. The API-key override uses `https://api.x.ai/v1` and may inject Priority Processing. Live-first Grok catalog; `grok-4.5` is the fallback default. | | `anthropic` | `anthropic` | `https://api.anthropic.com` | Claude models; live model list fetched from `/v1/models`. | -| `kimi` | `openai-chat` | `https://api.kimi.com/coding/v1` | Kimi Code Plan coding models. Defaults to the stable `kimi-for-coding` alias (currently K2.8 Preview): 1M-token context window, adjustable `low`/`high`/`max` thinking (default `max`), text + image input. Retired `kimi-k2.x` selections are migrated to the alias on upgrade. | +| `kimi` | `openai-chat` | `https://api.kimi.com/coding/v1` | Kimi Code Plan coding models. Defaults to the stable `kimi-for-coding` alias (currently K2.8 Preview): 1M-token context window, adjustable `low`/`high`/`max` thinking (default `max`), text + image input. `k3.1-pro` and `k3.1-pro-highspeed` (K3.1 Pro) are also listed: 1M-token context, thinking-only on the same ladder (default `max`). Retired `kimi-k2.x` selections are migrated to the alias on upgrade. | | `kimi-responses` | `openai-responses` | `https://api.kimi.com/coding/v1` | Same Kimi account login (reuses the `kimi` OAuth credential) over the OpenAI Responses wire. Same model roster and capabilities as `kimi`; thinking content stays encrypted server-side, tool calls and results stay visible. | | `nous` | `openai-chat` | `https://inference-api.nousresearch.com/v1` | Nous Research subscription gateway (same backend Hermes Agent uses). Device-grant login against `portal.nousresearch.com`; the access token is the per-request inference JWT. Mixed paid + `:free` model catalog (`tencent/hy3:free`, `stepfun/step-3.7-flash:free`, ...) discovered live from the signed-in account. Refresh tokens are single-use and rotated on every refresh. | | `kiro` | `kiro` | `https://runtime.us-east-1.kiro.dev` | The dashboard Login and Add account buttons offer Builder ID, Google, GitHub device login, or Kiro CLI. Native device login adds an account without signing `kiro-cli` out. The Kiro CLI choice imports or starts a CLI session; its Add account workflow temporarily switches the CLI account and restores it on cancellation or failure. | @@ -231,6 +327,7 @@ ocx logout | `zed` | `zed` | `https://cloud.zed.dev` | Experimental Zed Hosted AI bridge. Uses Zed's native-app RSA callback, exchanges the account token for a short-lived hosted-inference token, and discovers the account's live roster. | | `orcarouter-oauth` | `openai-chat` | `https://api.orcarouter.ai/v1` | Browser consent and key exchange use `https://www.orcarouter.ai` with S256 PKCE. The returned user-owned `sk-orca-…` API key is stored in the existing credential store and reused until revoked. | | `devin` | `devin` | `https://server.codeium.com` | Experimental unofficial Cognition/Devin bridge. Login first imports the credential the installed Devin CLI already holds (`devin auth login` writes a `devin-session-token` to its own `credentials.toml`); when none is present it opens Auth0 browser sign-in and exchanges the pasted token via Cognition's `RegisterUser` for a long-lived API key. `ocx login devin-cli` remains as a deprecated alias. Models are discovered per account with `GetCascadeModelConfigs`. Account quota comes from `GetUserStatus` under one eight-second request and body deadline: dated daily and weekly windows, plus the monthly prompt and flex credit pool for a credit-billed plan or an unknown strategy with both reset dates absent when a balance field is present. Negative used credits omit the monthly window; valid zero available credits mark it exhausted. A timed-out probe keeps the last good quota. Not shown in the dashboard preset by default. Chat and usage reporting are verified against a live account across three models. | +| `dsh-account` | `dsh-account` | `https://api.deepseek.com/anthropic` | Experimental import-first account provider reusing an installed and signed-in DeepSeek Harness (DSH) Desktop account. Reads credentials strictly read-only from `~/.dsh/.credentials.yaml` upon explicit import. Independent from DeepSeek API key billing. | | `github-copilot` | `openai-chat` | `https://api.githubcopilot.com` | Experimental. GitHub device flow + `copilot_internal` exchange (VS Code OAuth client). Requires an active Copilot subscription; not an official third-party API. | Google Antigravity groups a model's complete `low` / `medium` / `high` variants into one entry with an effort selector. This includes Claude Opus and Sonnet 5.5 and future versions discovered during automatic refresh. Partial sets remain separate. Existing enabled/disabled choices carry into the grouped entry; genuinely new models still follow your New model policy. Saved suffix IDs continue to request their original tier. Grouped effort routing survives a proxy restart when model discovery is temporarily unavailable. A running Codex session may need to reload its model list after catalog refresh. Usage groups known Claude Sonnet and Opus 5.5 tier IDs under their base model, including historical tier records. Their displayed costs are derived Anthropic reference estimates, not Antigravity subscription charges. @@ -278,6 +375,8 @@ provided, and the bridge infers the hosted backend family from the live row or t model discovery is not an allowlist. The Zed access token has no refresh endpoint, so when Zed revokes it, run `ocx login zed` again. +For Zed-hosted OpenAI models, OpenCodex translates `developer` instructions to `system` messages and removes OpenAI-private fields and unsupported built-in tools such as `web_search`. Function and custom tools remain available through the delegate; a tool choice with no supported tools left becomes `none`. + For the canonical Kimi Coding Plan presets (`kimi` account login and `kimi-code` API key), opencodex forwards only a caller-supplied stable `prompt_cache_key` to the Chat Completions request (the Responses wire accepts the same field, but opencodex does not send it there today); @@ -291,15 +390,28 @@ to avoid its `assistant must not be empty` rejection. Nonempty text, whitespace, reasoning and tool calls/results are retained; stored client history is not rewritten. An empty message referenced by an `item_reference` in the same input is retained rather than leaving a dangling reference. -Kimi Coding usage prices for `k3`, `k3[1m]`, and `k3-256k` are **API-reference estimates** across -`kimi`, `kimi-code`, and `kimi-responses`, using the published default 5-minute cache-write rate. -They do not reproduce Code Plan billing or quota: K3's 1M variant consumes about twice the quota -of `k3-256k`. `kimi-for-coding` now points to K2.8 Preview, so its former K2.7 price is no longer +Kimi Coding usage prices for `k3`, `k3[1m]`, `k3-256k`, `k3.1-pro`, and `k3.1-pro-highspeed` are +**API-reference estimates** across `kimi`, `kimi-code`, and `kimi-responses`, using the published +default 5-minute cache-write rate (the `moonshot` API preset prices the same models as `kimi-k3`, +`kimi-k3.1-pro`, and `kimi-k3.1-pro-highspeed`). They do not reproduce Code Plan billing or quota: +K3's 1M variant consumes about twice the quota of `k3-256k`, and K3.1 Pro Highspeed about twice +that of `k3.1-pro`. `kimi-for-coding` now points to K2.8 Preview, so its former K2.7 price is no longer used; its estimate remains unavailable unless the operator supplies `modelCosts`. Unknown-price routing policies and cost caps that exclude unknown estimates can therefore exclude this alias. See [Kimi's model configuration](https://www.kimi.com/code/docs/en/kimi-code/models.html) and [API pricing](https://platform.kimi.ai/docs/pricing/chat). +The `moonshot` (Kimi API) preset lists `kimi-k3.1-pro` and `kimi-k3.1-pro-highspeed` next to +`kimi-k3` and the K2 ids. Both have a 1M-token context window and text + image input, and always +think: the picker offers `low` / `high` / `max` (default `max`), Codex `medium` is sent as `high` +and `xhigh` as `max`, and an explicit `none` leaves `reasoning_effort` out so the upstream default +applies. Fixed sampling parameters (`temperature`, `top_p`, penalties) are not sent. A saved +`moonshot` model list that is still the previous built-in list gains both ids at startup when it +points at the International or China endpoint; an edited list or a Custom URL is left as it is. +The preset default stays `kimi-k2.7-code`. Moonshot closes the K2 series to new accounts on +2026-10-31 and plans to retire it by the end of December 2026; see the +[Kimi API model list](https://platform.kimi.ai/docs/models). + A custom `openai-chat` provider can opt in when its upstream documents support for `prompt_cache_key`: @@ -370,6 +482,29 @@ Two cases behave differently, and it is worth knowing which you are in: Device-code providers never open a browser from the proxy in either case: they show a code and a verification URL to open wherever you are signed in. +### Headless Claude login (code-display mode) + +For Anthropic there is a cleaner path than pasting a dead redirect: the **code-display** login. +Anthropic redirects to its own page and shows a `code#state` string instead of calling back to the +proxy, so nothing needs to reach `127.0.0.1` on the proxy's host. + +```bash +ocx login anthropic --headless +``` + +Over the management API, start with `POST /api/oauth/login` and `{"provider":"anthropic","mode":"code"}`; +the response carries a `flowId` and an `expiresAt` (10 minutes). Submit the pasted string to +`POST /api/oauth/login/code` with that `flowId`. The answer is the result of the token exchange — +`{"ok":true,"account":…}` or `{"ok":false,"error":…}` — so a hosted caller with a single paste box +knows whether the login actually worked. A paste with the wrong state, or with no code in it, is +refused before any token request and leaves the flow open for another try; a paste that reaches the +token exchange uses the flow up, so start a new login if that exchange fails. + +Only `anthropic` and `anthropic2` support this mode; other providers answer `code_mode_unsupported`. Several code +logins can be in flight at once, each pinned to its `flowId`, but they all write the same account +store: the most recent successful one becomes the active Claude account. This is meant for one +operator on a headless host, not for isolating unrelated users on a shared instance. + ### Multiple OAuth accounts OAuth providers whose credentials include a stable account id or email can keep more than one @@ -393,6 +528,8 @@ cat accounts.json | ocx account import google-antigravity --format cockpit-tools Inline JSON and extra positional arguments are rejected. Keep exported files private and delete or store them securely after import. +Each record may carry an optional `project_id` for an account whose Cloud Code Assist project is not discovered automatically. It must be a Google Cloud project id (6–30 lowercase letters, digits or hyphens). OpenCodex uses it only when discovery finds no project, and only after a quota check with that account's own token succeeds for it; otherwise the record fails with `missing_project` and nothing is saved. A discovered project always takes precedence. + ### OAuth reliability opencodex coordinates token refresh and Codex pool routing so concurrent requests do not race the @@ -538,9 +675,26 @@ selectors, then retry. Signing in from a machine with no existing `kiro-cli` ses The native dashboard choices are add-only and do not sign out `kiro-cli`. A device dialog shows the code and verification destination. Only recognized Kiro or Builder ID hosts are opened as links; an unexpected destination is shown as copyable text for review. Closing the dialog sends cancellation and leaves a bounded background status check to reconcile a commit already in progress. A stalled status response is retried; exhausting the flow deadline produces the neutral ended outcome rather than claiming success. Google and GitHub approval completes when Kiro reports `authorized` (or the legacy `approved` status) with valid tokens and a profile ARN. The account list marks Kiro accounts excluded from automatic selection with a reason, when available. +### DeepSeek API + +The `deepseek` API-key provider includes `deepseek-flash`, its `deepseek-v4-flash` alias, and `deepseek-v4-pro`. DeepSeek [withdrew the Pro retirement plan](https://api-docs.deepseek.com/updates/) and continues serving it; opencodex retains Pro in the catalog with a 1,048,576-token context window, text-only input, and `low`/`high`/`max` reasoning effort. Responses requests use its native Responses endpoint; Chat Completions requests retain the Chat wire. Flash remains the default. Third-party providers have independent model availability. + +On upgrade, startup adds Pro to a saved model list only when it exactly matches the previous built-in DeepSeek seed and still uses the official endpoint and adapter. Customized lists, including empty lists, remain unchanged; live discovery need not be enabled for this repair. + +### DeepSeek Account (DSH) + +`dsh-account` is an experimental, import-first account provider designed to reuse the authenticated account credentials of an installed DeepSeek Harness (DSH) Desktop application. + +- **Independent from DeepSeek API Key:** `dsh-account` runs on the DeepSeek account signed in to DeepSeek Harness, whereas the `deepseek` provider bills against your platform API key balance. The two credential domains and routes are strictly separate and never fall through into one another. +- **Explicit import only:** opencodex reads `~/.dsh/.credentials.yaml` (or `$DSH_HOME/.credentials.yaml`) only when you run `ocx login dsh-account` or select **Log in** on the DeepSeek Account (DSH) row of the dashboard's **Add provider** picker. It never copies, imports, or uses the token on its own. +- **Read-only DSH credential store:** OpenCodeX treats `~/.dsh/.credentials.yaml` as strictly read-only. OpenCodeX never writes, modifies, removes, or alters the permissions of any files belonging to DeepSeek Harness. +- **Isolated credential copy & logout:** Upon import, OpenCodeX validates the token against DeepSeek's authoritative identity endpoint (`/auth-api/v0/users/current`) and saves its own copy to the OpenCodeX auth store. Logging out or removing the account in OpenCodeX only deletes the OpenCodeX local copy; it never triggers upstream remote session logout and will not sign you out of DeepSeek Harness Desktop. +- **No background token rotation & 401 re-import:** DSH session grants do not feature an automatic background refresh endpoint. If upstream returns HTTP 401 (`ACCOUNT_TOKEN_INVALID`), OpenCodeX marks the account `needsReauth` and halts routing. To restore access, the user must explicitly re-import via `ocx login dsh-account`. +- **Third-party disclaimer:** OpenCodeX makes no claim of official endorsement or authorization by DeepSeek for third-party clients reusing session grants. + ## 3. API-key catalog -opencodex ships 104 built-in presets: 85 key-based, 15 OAuth, three local, and one default +opencodex ships 106 built-in presets: 85 key-based, 17 OAuth, three local, and one default ChatGPT-forward preset. The dashboard's **Add provider** picker opens a key provider's dashboard, validates the key, and stores it; validation is provider-specific. Notable entries: @@ -622,7 +776,7 @@ region-pinned EU routes, is at [opper.ai/models](https://opper.ai/models). Opper | Z.AI (GLM Coding) | `https://api.z.ai` — Responses at `/api/v1/responses` by default; Chat Completions at `/api/coding/paas/v4/chat/completions` per model through `modelAdapters` | | Zhipu AI (BigModel) | `https://open.bigmodel.cn/api/paas/v4` | | BigModel Coding Plan (Responses, static roster) | `https://open.bigmodel.cn/api/v1` | -| Qwen Cloud | Token plan (default): `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` · Pay as you go: `https://dashscope.aliyuncs.com/compatible-mode/v1` · or Custom | +| Qwen Cloud | Token plan (default): `https://token-plan.maas.qwencloudapi.com/compatible-mode/v1` · Pay as you go: `https://dashscope.aliyuncs.com/compatible-mode/v1` · or Custom | | Tencent Cloud Coding Plan | `https://api.lkeap.cloud.tencent.com/coding/v3` | | SiliconFlow | `https://api.siliconflow.cn/v1` | | Volcengine Ark · Coding Plan · Agent Plan | `https://ark.cn-beijing.volces.com/api/v3` · `https://ark.cn-beijing.volces.com/api/coding/v3` · `https://ark.cn-beijing.volces.com/api/plan/v3` | @@ -632,11 +786,33 @@ region-pinned EU routes, is at [opper.ai/models](https://opper.ai/models). Opper | Opper | `https://api.opper.ai/v3/compat` | | TokenLab | `https://api.tokenlab.sh/v1` | | Modelflare | `https://modelflare.dev/v1` | +| AI Stupid Level | `https://aistupidlevel.info/v1` | | OpenGateway | `https://apis.opengateway.ai/v1` | | GitLab Duo | `https://cloud.gitlab.com/ai/v1/proxy/openai/v1` | | Cloudflare AI Gateway | `https://gateway.ai.cloudflare.com/v1/{account-id}/{gateway}/anthropic` | | …and more | opencode zen, Vercel AI Gateway, Venice, NanoGPT, Synthetic, Qianfan, Alibaba, Parallel, ZenMux, LiteLLM | +**Alibaba Token Plan** provides Beijing Personal Edition and International Team Edition +presets. Both regions offer both subscription tiers. The presets keep static chat-model +lists because upstream discovery also includes non-chat models. Existing selections and +explicit International endpoint overrides are preserved. + +Both presets include `deepseek-v4.1-flash-1017` with text/image input, text output, +1,000,000 context and `low`/`high`/`max` reasoning (default `high`). Its 393,216 output +budget follows the existing published-budget normalization, not a measured boundary; +the lab lists 384,000. Strict schemas reject and `json_object` is unverified for this +exact ID, so Chat requests omit `response_format`. This is best-effort text, not +guaranteed JSON. The bare model's verified JSON downgrade and existing Responses +pins are unchanged. Beijing also includes now-listed `deepseek-v4-pro-0813`; +International omits retired `deepseek-v3.2` and bare `deepseek-v4-flash` but retains +`deepseek-v4-flash-0731`. Both presets keep the hosts their official quick starts +document: Beijing `https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1` and +International `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1`. +The Qwen Cloud preset uses the separate Token Plan host that Qwen Cloud documents above. +See the [1017 model card](https://www.qianwenai.com/models/deepseek-v4.1-flash-1017), +[Chat reference](https://docs.qwencloud.com/api-reference/chat/openai-chat) and +[Personal overview](https://help.aliyun.com/en/model-studio/token-plan-personal-overview). + **OpenGateway** is an OpenAI-compatible gateway operated by Sionic AI at `https://apis.opengateway.ai/v1`. Its public catalog contains about 80 active models (as verified on 2026-10-02); the preset automatically refreshes the live model list from @@ -647,6 +823,16 @@ Create a key in the [OpenGateway dashboard](https://opengateway.ai/api-keys), th use the configured Bearer key; the public model list does not validate that key. On the Responses wire OpenGateway rejects replayed native custom tool calls, so the preset lowers Codex custom tools such as code-mode `exec` to function tools before sending them. +Tool-result content arrays are sent as text, with images preserved after the paired tool-result group. Unsupported result parts, including refusals and opaque content, are rejected explicitly with a client input error before sending. +Image detail `original` maps to `high`, which the gateway accepts; image bytes remain unchanged. +The native Responses route also checks text-only empty completions before exposing them as success. +Exhausted output budgets produce `incomplete` with `max_output_tokens`; the proxy does not increase +the requested budget. A replay-safe empty completion with budget remaining may retry once, within +the existing send limit. Explicit `emptyCompletionRetry: false` or `OCX_EMPTY_COMPLETION_RETRY=0` +disables that retry. Text, refusals, tool activity, and unknown stream events prevent replay and +retain the original stream. The protection does not change other providers or their retry defaults. +Replay requires explicit `store: false`, complete input, and client-executed tools only; a hosted +tool such as `web_search` prevents automatic replay even if no tool event was observed. **TokenLab** ([sponsor](https://github.com/lidge-jun/opencodex/blob/main/SPONSORS.md)) is an OpenAI-compatible API gateway at [tokenlab.sh](https://tokenlab.sh/r/OPENCODEX), @@ -664,7 +850,7 @@ Each model then uses the request format TokenLab declares for it | Models | Codex (Responses clients) | Chat clients | Claude Code (Anthropic clients) | | --- | --- | --- | --- | -| `gpt-6-astra`, `gpt-6.1-sol`, `gpt-6-sol`, `gpt-6-luna`, `grok-4.7`, `deepseek-v4.1-flash`, `deepseek-v4-pro`, `kimi-k3`, `glm-5.3` | [Responses](https://tokenlab.sh/docs/en/api-reference/responses/create-response) | Chat Completions | Chat Completions | +| `gpt-6-astra`, `gpt-6.1-sol`, `gpt-6.1-terra`, `gpt-6-sol`, `gpt-6-luna`, `grok-4.7`, `deepseek-v4.1-flash`, `deepseek-v4-pro`, `kimi-k3`, `glm-5.3` | [Responses](https://tokenlab.sh/docs/en/api-reference/responses/create-response) | Chat Completions | Chat Completions | | `claude-*` | [Messages](https://tokenlab.sh/docs/en/api-reference/messages/create-message) | Messages | Messages | | Every other model, including `gemini-3.8-flash` | Chat Completions | Chat Completions | Chat Completions | @@ -685,9 +871,11 @@ picker and marks it as a sponsor, and nothing else about routing or defaults cha **Modelflare** is an OpenAI-compatible relay at `https://modelflare.dev/v1`. Select **Modelflare** in `ocx init` or the dashboard's **Add provider** picker, or run `ocx provider add modelflare`, and give it your key directly or as the reference `${MODELFLARE_API_KEY}` so the key stays in your -environment. The preset uses Chat Completions and seeds four models that +environment. The preset uses Chat Completions. The following existing models were verified with chat, streaming and tool calls: `glm-5.3-flash` (the default), -`claude-sonnet-5-5`, `gpt-6.1-sol` and `deepseek-v4-flash`. You can add other Chat-compatible +`claude-sonnet-5-5`, `gpt-6.1-sol` and `deepseek-v4-flash`. +`gpt-6.1-terra` is a preemptive seed mirroring Sol; it has not been inference-verified here. +You can add other Chat-compatible Modelflare models as custom models. Image and video models use separate Modelflare endpoints and do not work through this Chat preset. An ordinary Modelflare key is bound to one routing group and can call only that group's models, so use a Smart key for the full seeded set, or pass @@ -695,6 +883,31 @@ can call only that group's models, so use a Smart key for the full seeded set, o `deepseek-v4.1-flash` is listed upstream but currently answers 503, so the preset leaves it out. Upstream latency varies from a few seconds to about 30 seconds, with occasional transient 503s. +**AI Stupid Level** is a Smart Router at `https://aistupidlevel.info/v1`, operated by S.C. Studio +Platforms S.R.L. Create a router key at [aistupidlevel.info/router/keys](https://aistupidlevel.info/router/keys) +and add your own OpenAI, Anthropic, Google, DeepSeek, Kimi or GLM keys to the same account. Then select +**AI Stupid Level** in `ocx init` or the dashboard's **Add provider** picker, or run +`ocx provider add aistupidlevel`, and give it the router key directly or as the reference +`${AISTUPIDLEVEL_API_KEY}`. The router calls each model with your own provider key, so the provider +bills you directly and AI Stupid Level adds no markup ([terms](https://aistupidlevel.info/terms#router)). +The preset uses Chat Completions for every client, Claude Code included, and discovers models from the +authenticated `GET /v1/models`. Its seeded models are the +[routing modes](https://aistupidlevel.info/router/docs#strategies): `auto-coding` (the default) sends +each request to the model with the best measured score on AI Stupid Level's coding benchmark, +`auto-tooling` and `auto-reasoning` do the same for tool use and reasoning, and `auto-value-coding` or +`auto-fastest-quality` pick the best-value or fastest model within 5 points of the best. A real model id +from the live list, such as `aistupidlevel/claude-opus-5-5`, pins one model, and the `X-AISM-Model` +response header names the model that answered. Routing modes declare a 262,144-token window and 64,000 +output tokens, which hold for any model the router picks. The router accepts text only, so for a +routing mode or pinned model at the preset destination the proxy hands images to the vision sidecar instead of sending them. Its Anthropic +Messages endpoint is a passthrough to Anthropic without routing, so the preset does not use it. + +The provider editor also accepts an optional `inputModalities` fallback: a nonempty array containing +`text`, `image`, `audio`, or `video`. Exact per-model declarations take precedence. Invalid values +are rejected on save; a malformed value in a hand-edited config loads as `["text"]` with a warning. +The preset's text-only fallback is applied at runtime, including when saved under another provider +name at the same URL and Chat adapter. It does not follow a custom URL or another adapter. + The MiniMax and MiniMax (CN) provider cards can also show Coding Plan quota when the configured key has an active plan. The dashboard reads the plan's 5-hour window and, when present, weekly window; these are display observations and do not change model routing. @@ -706,19 +919,32 @@ attempt fails, while transient failures keep it. An unreadable or oversized body further attempts. Dollar credits show the included allowance only, excluding purchased credits, so an exhausted allowance stops routing to Ollama Cloud only when no purchased credit remains. -`MiniMax-M3.1-Flash-Preview` (1M context) is listed on both MiniMax presets. MiniMax serves it -only to Token Plan subscription keys and MiniMax Code for now, so a pay-as-you-go API key gets an -error for it. Thinking is always on: the effort picker offers `low` through `max` and defaults to -`max`, and there is no way to turn thinking off. MiniMax has not published a per-token price -for the preview; usage is drawn from your -[Token Plan quota](https://platform.minimax.io/docs/guides/pricing-token-plan), so OpenCodex -shows no estimated cost for it. MiniMax's `/models` endpoint does not list -the preview yet, so OpenCodex keeps it in the catalog from the preset. An install whose saved -MiniMax model list is still the previous default receives it on the next start; a list you edited -is left as it is; register the preview by hand with -`ocx models add minimax MiniMax-M3.1-Flash-Preview --context-window 1000000`. - -**OpenCode Go** requires a stable session identifier for routing. OpenCodex sends +`MiniMax-M3.1-Flash` (1M context, up to 524,288 output tokens) is the default model on both MiniMax +presets. MiniMax released it on 2026-10-19 and retired the `MiniMax-M3.1-Flash-Preview` id, so a +saved configuration that still names the preview is renamed to `MiniMax-M3.1-Flash` on the next +start. Thinking is always on: the effort picker offers `low` through `max` and defaults to `max`, +and there is no way to turn thinking off. MiniMax's `/models` endpoint does not list Flash, so +OpenCodex keeps it in the catalog from the preset. An install whose saved MiniMax model list is +still the previous default receives it on the next start; a list you edited is left as it is; +register it by hand with +`ocx models add minimax MiniMax-M3.1-Flash --context-window 1000000`. + +**OpenCode Go** routes `gemini-4-argon-contributor` through its Gemini API, including +streaming and tool-result continuations. The existing `opencode-go` preset keeps its +historical URL pinning: saved custom URLs, including `https://opencode.ai/inference/go/v1`, +route through the canonical `https://opencode.ai/zen/go/v1` endpoint and inherit Go defaults. +Gemini then uses Go's native model endpoint with key authentication. A differently named +custom provider keeps its configured URL and does not receive this Gemini pin. +Go selects protocols by exact model ID: GPT 6 Luna, GPT 5.6 Luna, Grok 4.6/4.7 and +Muse Spark Contributor use Responses; Claude Haiku 5.5, MiniMax M2.7/M3 and +Qwen3.7 Plus/3.8 Max/3.8 Flash use Anthropic Messages. Legacy MiniMax M2.5 and +Union Alpha also retain Messages. Models without a listed wire retain Chat. +New native pins apply to the pinned Go destination with key authentication. +Responses models still accept an explicit `modelAdapters` override. +When reading an older saved configuration, OpenCodex ignores obsolete Chat/Responses overrides for the newly pinned Gemini and Qwen models, along with their formerly valid Responses-only hosted-tool preferences. Other providers, accounts and routing remain available. Reads leave the file unchanged; a later explicit configuration edit saves the upgraded values. New overrides for these native models are still rejected. +Gemini uses `low`, `medium` and `high` thinking levels; higher requested efforts clamp to `high`. + +OpenCode Go requires a stable session identifier for routing. OpenCodex sends `claude-haiku-5-5`, like the MiniMax models, over the Anthropic Messages wire, matching [OpenCode Go's endpoint table](https://opencode.ai/docs/go/#endpoints). OpenCodex derives its Go session header from Codex thread/session headers, or from a client's @@ -746,46 +972,37 @@ when a stable upstream session is required. **MiMo tool-call echoes.** On OpenCode Go and other Chat Completions routes, a bare `` block is hidden when it duplicates one structured call to the same tool -with the same effective input. If the input differs or several calls could explain +with the same effective input. This includes MiMo wrappers such as +`` with matching closing IDs, including echoes appended to ordinary prose. +Code examples remain visible. If the input differs or several calls could explain the block, the markup remains visible. For `mimo-v2` and dotted MiMo V2 model IDs such as `mimo-v2.6-pro`, if the gateway instead sends exactly one empty `{}` call for a declared -freeform tool and puts its input in a standalone bare block, opencodex restores that +freeform tool and puts its input in a standalone bare or identified block, opencodex restores that input to the call and hides the block. Hyphenated IDs such as `mimo-v2-pro` and `mimo-v2-omni` are outside this recovery rule. Prose, quoted examples, ordinary functions, and ambiguous responses remain unchanged. This also handles MiMo's malformed `` User-Agent is a claim to -*be* the OpenCode client, and OpenCode publishes no third-party integration contract for this -keyless tier; an HTTP 200 obtained that way is a bypassed admission check rather than -permission. opencodex therefore reports the restriction instead of working around it: a request -to `opencode-free` returns an error explaining the upstream gate and pointing here. - -The supported route to the same models is the keyed **`opencode-zen`** provider with an OpenCode -Zen API key from [opencode.ai/auth](https://opencode.ai/auth). If OpenCode later publishes a -supported third-party path for the keyless tier, opencodex can follow it; until then the preset -stays as documentation of the restriction. Upstream terms: -[opencode.ai/docs/zen](https://opencode.ai/docs/zen/). +**OpenCode Zen** (`opencode-zen`) uses `https://opencode.ai/zen/v1` with an API key from [opencode.ai/auth](https://opencode.ai/auth). Free-model traffic can hit short-window rate limits around 15–20 requests/minute (community-measured; OpenCode does not publish RPM). When a rate-limit 429 omits `Retry-After`, opencodex adds guidance and a synthetic `Retry-After`; valid upstream headers take precedence. Same-key retries remain opt-in via [`retryOn429`](/reference/configuration/). + +Gemini models on `opencode-zen` use Google's native API automatically when the provider uses an API key, the default `googleMode` (`ai-studio`), and either `https://opencode.ai/zen/v1` or `https://opencode.ai/inference/v1`. Use the model IDs listed by Zen; OpenCodex preserves them, including Flash IDs without a `-tiered` suffix. Claude models on these API-key routes use Anthropic Messages automatically, regardless of `googleMode`. When loading saved configurations, OpenCodex ignores eligible older Gemini and Claude Chat/Responses overrides only for native models in the canonical keyed `opencode-zen` provider on these official Zen routes. It also ignores eligible Responses-only `image_generation` hosted-tool preferences; custom providers, non-key routes, and other preferences remain unchanged. + +**`opencode-free` has been removed** because every model now returns `403 permission_denied` under OpenCode's upstream free-tier gate ([#7255](https://github.com/lidge-jun/opencodex/issues/7255)). Loading an existing configuration disables its entries and dependent model references in memory, warns once per process, and leaves the file unchanged. If it was the default provider, the standard OpenAI OAuth route becomes the default. Use `opencode-go`, keyed `opencode-zen`, or another provider. [OpenCode Zen terms](https://opencode.ai/docs/zen/). + +Healthy Claude Desktop assignments and defaults are preserved. Rules whose source-model allowlist becomes empty are disabled, along with native subagent sync when its injection model is removed. Later config saves keep removed references out of the file while preserving healthy concurrent edits. Most use the `openai-chat` adapter with a bearer key; Anthropic-compatible presets such as **Xiaomi MiMo** (`xiaomi`) use the `anthropic` adapter (`x-api-key`). Xiaomi also has an OpenAI Chat preset, `xiaomi-mimo`, and a token-plan preset, `mimo`. All three default to MiMo V2.6 (`mimo-v2.6-pro`, or `mimo-v2.6-flash` on `xiaomi-mimo`). Xiaomi retires `mimo-v2.5` and `mimo-v2.5-pro` on 2026-10-21 with no redirect, so switch a saved V2.5 default before then; opencodex does not rewrite it for you. + +**Reasoning capabilities.** Model metadata describes three independent capabilities: `supportsReasoning`, `supportsThinkingToggle`, and `supportsReasoningEffort`. An omitted value is unknown; `false` is an explicit declaration. `defaultThinkingEnabled` records the default switch state, while `reasoningEfforts` lists actual strength choices. Per-model declarations live in the provider's `modelCapabilities` map; reasoning axes merge over registry defaults using case-insensitive model IDs, with operator values taking precedence. `/v1/models` exposes the corresponding `supports_reasoning`, `supports_thinking_toggle`, `supports_reasoning_effort`, and `default_thinking_enabled` fields under `capabilities`, without inventing an effort list for fixed-strength models. + +Legacy configurations with an explicit empty effort list and no reasoning capability declarations retain `capabilities.supports_reasoning: false` in `/v1/models` discovery. An explicit `supportsReasoning: true` or `supportsThinkingToggle: true` still advertises thinking when the effort list is empty. + +Public MiMo V2.6 is one instance: its three models support reasoning and an on/off switch, but no adjustable intensity; thinking defaults to enabled. The wire adapter sends `thinking.type: enabled/disabled`. OCX's Responses ingress also accepts `reasoning.enabled` independently of `reasoning.effort`. For clients that only offer effort enums, Codex catalogs and OpenCode/Kilo exports represent this switch as `none/high`; those values are client compatibility aliases, not MiMo strength levels. Client-owned labels may still say “High.” Fixed reasoning models without a switch get no fabricated control. Xiaomi's [deep-thinking guide](https://mimo.mi.com/docs/en-US/quick-start/usage-guide/other/deep-thinking) describes the public MiMo switch; free, token-plan, third-party routes and older MiMo models keep their existing declarations. + Volcengine Coding Plan and Agent Plan use their native Responses endpoints through `openai-responses`. During validated Ark Coding Plan tool continuations, replaying the returned Responses `reasoning` item answered `400 InvalidParameter`, so the Coding Plan preset drops replayed reasoning items before @@ -940,6 +1157,10 @@ ladder, and `meta-model/muse-wonder-1.0` (Muse Wonder, preview) with `minimal` t Muse Wonder is Standard mode only on the Model API: it has no Contributor tier and costs $4 per 1M input and $20 per 1M output tokens. Muse Wonder Full is available only inside Muse Code subscriptions, so it is not seeded. +On startup, an untouched saved two-Spark model list gains Muse Wonder when `meta-model` uses +`https://api.meta.ai/v1` or has no saved endpoint override. Custom gateway destinations and +hand-edited model lists stay unchanged. Existing per-model context-window overrides and output +limits are preserved; Wonder's context window is filled only into an existing window map. Discovery stays off until an authenticated roster is verified, because Meta serves image and voice models on the same host. @@ -956,7 +1177,10 @@ stored by `muse login`. With no local credential, or on another platform, it sta browser device-approval flow. Add-account and reauthentication skip local import to avoid reusing the account being replaced. OpenCodex never launches the Muse CLI. If device login fails without cancellation, an available manual-input surface can accept a pasted key; -that key faces the same format and Model API validation as an imported key. See +that key faces the same format and Model API validation as an imported key. On macOS, +approve the Keychain prompt on the proxy host within 120 seconds. Import refusals +show the failure reason and offer an existing-key paste field without starting another +login. Add account on an already configured provider bypasses local import. See [Platform support](/reference/platform-support/) for the full per-platform picture. Starting any Meta Muse login through the management API requires a dashboard session, @@ -1064,6 +1288,35 @@ Create a key in [Novita's key manager](https://novita.ai/settings/key-management > hosts and schemas and are not routed by this preset. > Live discovery for this preset is capped at a 1 MiB response and 256 raw model rows. +### WorkBuddy AI subscription + +`workbuddy-ai` connects your international WorkBuddy AI subscription as a model provider. +Choose **WorkBuddy AI** in Providers and log in, or run: + +```bash +ocx login workbuddy-ai +``` + +Complete the browser sign-in; OpenCodex polls for authorization for up to five minutes. +Tokens are stored in OpenCodex and refreshed on demand. Adding accounts uses the existing +OAuth account list; team identity stays attached to its own token. No WorkBuddy desktop +installation or credential-file import is required. + +Models and capabilities come from your account's live product catalog. Select a +`workbuddy-ai/` model after login; `workbuddy-ai/default-model` is the fallback +selector when discovery is unavailable. Streaming, tool calls and buffered responses use +WorkBuddy's Chat endpoint. The Overview and Accounts tabs show remaining credits, used/total credits, base and gift +balances, and the next expiring non-daily credits when available. Refresh reads the signed-in +account's billing summary; an unavailable read is not a zero balance. These credits are +informational and do not disable free models when paid credits run out. + +This uses an unofficial subscription protocol based on +[Magpie's WorkBuddy plugin](https://github.com/magpie-community/plugins/tree/e7f65641df820dfd715c0d2241f99f2092e4efb1/packages/workbuddy). +Upstream client-channel or account restrictions are returned to the caller. OpenCodex does +not rewrite agent identity prompts to bypass them. Availability depends on your account, +region, model entitlement and WorkBuddy's current API. This is separate from the CodeBuddy +CLI API-key providers and does not connect the WorkBuddy desktop client to OpenCodex. + ### Official CodeBuddy Code CLI (Global & CN) OpenCodex provides official adapter support for Tencent Cloud's CodeBuddy Code CLI via the `codebuddy` (Global) and `codebuddy-cn` (China) presets. @@ -1129,8 +1382,11 @@ OpenCodex provides official adapter support for Qoder through the `qoder` (Globa - **Authentication:** Create a PAT in the account integrations page ([Global](https://qoder.com/account/integrations), [CN](https://qoder.cn/account/integrations)) and paste it as the provider's API key. The stored key reaches the CLI only as `QODER_PERSONAL_ACCESS_TOKEN` (Global) or `QODERCN_PERSONAL_ACCESS_TOKEN` (CN) in a scoped child environment. - **Region Isolation:** Each preset accepts only its canonical destination (`https://qoder.com` or `https://qoder.cn`) and resolves its own executable. Credentials, model cache, usage, and health are independent; neither region falls back to the other. An older custom provider named `qoder` with a different destination keeps its existing adapter and URL. +- **Windows executables:** Each preset launches its CLI's native executable (`qoder.exe` or `qodercli.exe` for Global, `qodercn.exe` or `qoderclicn.exe` for CN) when one is on PATH. Within one PATH folder the `.exe` wins over a same-name `.cmd`, and a later candidate name such as `qoderclicn.exe` anywhere on PATH wins over `qodercn.cmd`; an earlier PATH folder that holds only `qodercn.cmd` still shadows a later folder's `qodercn.exe`. Qoder turns pass empty `--tools` and `--setting-sources` values to keep the CLI's built-in tools and local settings out, and a dispatcher that forwards through PowerShell drops those empty values. When only such a dispatcher is found, the turn fails before the CLI starts with `cli_dispatcher_drops_empty_args`; add the folder holding the native executable to PATH (the official CN installer places it in `%USERPROFILE%\.qoder-cn\bin\qoderclicn`) and retry. A dispatcher that passes arguments straight through, such as an npm shim, keeps working. - **Model Discovery:** `qoder --list-models` is the authoritative entitlement roster for the current PAT. The cache is bound to an irreversible fingerprint of the token, so switching accounts never reuses another account's roster. If discovery fails, the provider degrades to a stale cache and then the documented static seed. +- **Token usage:** Qoder CLI reports zero token counts unless its usage switch is on, so each preset turns it on in its scoped CLI child environment: `QODER_EXPOSE_TOKEN_USAGE=true` for Global and `QODERCN_EXPOSE_TOKEN_USAGE=true` for CN, which reads only its own prefix. - **Tool Ownership:** The CLI runs single-turn `stream-json` with `--tools ""`, `--strict-mcp-config`, setting sources disabled, and session persistence disabled, so Codex keeps exclusive tool ownership. Client-declared tools are exposed through an isolated request-scoped MCP server. +- **Output guard:** If the CLI echoes OpenCodex tool-call/result replay envelopes into assistant text, the turn fails with `vendor_scaffold_detected` (502) instead of returning the envelope as a successful answer, and neither reasoning nor tool calls following the envelope are forwarded. Reasoning starts a fresh replay-text block, so earlier prose or code fences cannot hide an envelope in later text. If the CLI's turn already ended with its own error, that error is reported instead, still without the envelope. The proxy does not retry that failure automatically. Ordinary prose and closed fenced examples follow the shared envelope recognizer; an unverified overflowing fenced example is refused. Assistant text preserves Unicode characters split across CLI frames; unpaired surrogates are replaced with U+FFFD. - **Image Input:** Qoder Global accepts image input through the Qoder CLI. OpenCodex forwards supported image inputs that reach the Global adapter to the CLI without maintaining a per-model vision capability allowlist there. Image compatibility and any model-specific preprocessing or routing are handled by Qoder. OpenCodex does not use the static discovery fallback roster to classify per-model vision capability. Qoder CN keeps its existing sidecar classification, and any residual raw-image request that reaches the CN adapter is rejected as `unsupported_input_modality`. OpenCodex serializes history before the current turn, but Qoder does not persist sessions, so prior images are replayed on later turns. In observed multi-turn image conversations, replayed images can become unreliably associated or effectively ordered relative to the historical text they originally accompanied. - **PAT recovery:** For both Qoder presets, a multi-key API-key pool can switch to another PAT on a supported authentication rejection or account-level quota exhaustion, but only before response content or an event that makes replay unsafe. Recovery requires Qoder-specific classification and respects credential-hop and send budgets; an HTTP 401/429 or quota message alone is not permission to replay. Single-key configurations do not rotate. Sharing the CLI runner does not enable this API-key recovery for CodeBuddy or self-authenticated Claude CLI. - **Quota:** No public quota API is used, so totals and reset times are unavailable. Codes 110 (account daily limit) and 118 (personal Credits exhaustion) report `insufficient_quota` and permit consideration of PAT recovery. Other supported quota codes also report `insufficient_quota` but do not authorize it. Numeric error codes take precedence: an unrecognized numeric code does not fall back to quota or authentication text. Without a numeric code, only the supported account-daily message beginning “You've reached your daily usage limit for Chat”, with no team/model/shared qualifier, grants quota recovery; generic credit-limit text does not. Only quota failures eligible for PAT recovery enter key cooldown handling. When such a failure has no reset time, the failed key uses the existing default cooldown; nonrecoverable quota classifications do not create a cooldown through this path, and no next-day reset is inferred. @@ -1183,15 +1439,28 @@ CLI headlessly (`claude -p`, `stream-json`) once per turn: variable (a `claude` already pointed at this proxy therefore cannot loop back into it), telemetry, feedback and the auto-updater disabled, and `--tools ""`, `--strict-mcp-config` plus `--setting-sources ""`. The harness loads no CLAUDE.md, skill, hook, plugin or MCP server from the - machine and can neither read, write, exec nor browse. No session is persisted between turns. + machine and can neither read, write, exec nor browse. No session is persisted between turns. The + official CLI is found on `PATH`, or at the native install location (`~/.local/bin/claude`) when + a service starts OpenCodex with an older `PATH`. - **System prompt:** the caller's system and developer prompts replace the Claude Code preset (`--system-prompt-file`), so the turn answers the client's contract rather than the harness persona. The folded prompt is staged in a private per-turn file (mode `0600`) and passed by path, because process arguments are world-readable through process listing; a request that carries - neither a system nor a developer prompt gets an empty file, which replaces the preset with nothing. -- **Tool ownership:** v1 is text and reasoning only, exactly like the CodeBuddy and Qoder presets: - with no tool channel, approval, sandboxing and execution stay with the client. The shared - capture-only tool bridge is the documented follow-up. + neither a system nor a developer prompt gets only the fixed conversation-replay instructions. +- **Conversation caching:** each message is replayed as a separate stable text block, with the + current message formatted identically to its later history representation. Long history is cut + only at whole-message checkpoints. Tool catalogs serialize deterministically. Claude Code + controls cache breakpoints, so this makes prompt-cache reuse possible without guaranteeing it. +- **Tool ownership:** when the request carries tools, an isolated capture-only MCP catalog exposes + only the client's selected tools (the same bridge CodeBuddy uses). The CLI captures the call and + the turn ends there; approval, sandboxing, execution and the returned result stay with Codex, + Pi, or another client. Built-in tools and every other MCP server remain disabled. +- **Subscription limits:** before each launch OpenCodex asks Anthropic's usage endpoint, with the + CLI's own sign-in read in memory, whether the plan's 5-hour, weekly or model-specific window is + full. An exhausted plan is refused locally with HTTP 429 `claude_subscription_cooldown` and a + `Retry-After` for the reset, instead of starting a CLI that would fail. The answer is cached for + a minute in `claude-usage-admission.json` under the OpenCodex home (percentages, reset times and + a hashed account key only). If the check cannot run, the turn proceeds. - **Destination:** the canonical row names `https://api.anthropic.com` because that is where the subscription's traffic lands. OpenCodex never sends that request itself, and overriding the base URL fails closed rather than handing the turn to another environment. @@ -1317,6 +1586,9 @@ visible even while live catalogs lag: | Codex login (Pool or Direct) | `gpt-5.6-*` | 922,000 | | OpenAI (API key) | `openai-apikey/gpt-5.6-*` plus `*-pro` | 922,000 (922,000 max input) | | OpenRouter | `openrouter/openai/gpt-5.6-sol`, `openrouter/openai/gpt-5.6-terra`, `openrouter/openai/gpt-5.6-luna` | 922,000 | +| Codex | `gpt-6.1-terra` | 500,000 / 872,000 | +| OpenAI API | `openai-apikey/gpt-6.1-terra` | 1,050,000 (128,000 max output) | +| OpenRouter | `openrouter/openai/gpt-6.1-terra` | 1,050,000 | | Cursor | `cursor/gpt-5.6-sol`, `cursor/gpt-5.6-terra`, `cursor/gpt-5.6-luna` | 1,000,000 | The native GPT-5.6 entries preserve the pinned upstream reasoning ladders (for example, Luna has @@ -1363,7 +1635,7 @@ seconds per call, including pacing and body reads. These are OpenCodex safeguard GitHub limits; oversized catalogs, slow negotiation or a truncated intent excerpt can affect compatibility. For named selections, Copilot fronts a mixed-wire catalog: the following models (`gpt-5.3-codex`, `gpt-5.4`, -`gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`) reject +`gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6.1-sol`, `gpt-6.1-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`) reject `/chat/completions` for agent traffic, so opencodex routes those models over the Responses API by built-in default while every other Copilot model stays on chat completions. The precedence is: hard wire pin → your explicit @@ -1439,8 +1711,17 @@ dashboard or `custom` in `ocx init` and enter the base URL. See the Codex asks a second model to review approval requests, and takes that reviewer from `auto_review_model_override` on the catalog row of the current turn's model. The root -`auto_review_model` in `$CODEX_HOME/config.toml` applies one reviewer to every row. To give a -routed provider its own — usually cheaper — reviewer, set the selector on that provider row in +`auto_review_model` in `$CODEX_HOME/config.toml` is Codex's fallback. The dashboard's global +**Auto-review override** in Model settings applies one reviewer to all catalog rows that do not +have a provider-scoped reviewer. It stores `autoReviewOverride` in `~/.opencodex/config.json`; the +setting must be enabled and name a model. An enabled setting with no model stays unapplied and +leaves the Codex fallback in effect. A catalog refresh is a separate state: after a save it can +stay pending, and the Models page reports it so you can retry with `ocx sync`. From the CLI, read +the setting with `ocx models auto-review status` or +change it with `ocx models auto-review set [--enabled ]`; use `-` to clear the +model. CLI output recommends `ocx sync` when a refresh can be retried. If a refresh fails, the +setting remains saved and the command exits 1. Resolve the failure before syncing. To give a routed +provider its own — usually cheaper — reviewer, set the selector on that provider row in `~/.opencodex/config.json`: ```json @@ -1472,9 +1753,10 @@ no per-model override, leaving a valid provider-wide target as fallback. Whateve keep the root selector, or upstream behavior when that is unset. Removing the root selector leaves provider stamps alone, and removing a provider selector clears only that provider's stamps. -These fields are available through configuration, `PATCH /api/providers?name=`, and -the dashboard raw JSON provider editor; dedicated form controls are not present. The canonical `openai` provider -rejects them. Field-by-field rules live in the +Provider fields are available through configuration, `PATCH /api/providers?name=`, and +the dashboard raw JSON provider editor; dedicated provider form controls are not present. The +global override is available in Model settings. The canonical `openai` provider rejects provider +reviewer fields. Field-by-field rules live in the [provider configuration reference](/reference/configuration/providers/#auto-review-approval-model-selection). ## Rate limits in the providers overview @@ -1487,7 +1769,14 @@ provider-specific) is already consumed. Providers with a live probe: OpenAI/Codex, Anthropic, xAI, Cursor, Kimi, Google Antigravity, OpenCode Go, OpenRouter, DeepSeek, ClinePass, Z.AI, MiniMax, Moonshot, Venice, Synthetic, DeepInfra, Neuralwatt, Command Code, and any a6api-backed -custom provider. +or Abliteration custom provider. + +**Abliteration credits.** A provider of any name whose `baseUrl` is +`https://api.abliteration.ai` or `https://api.abliteration.ai/v1` reads +`GET https://api.abliteration.ai/v1/credits` with its active key as a Bearer token and does not +follow redirects. `total_credits` minus `total_usage` is the remaining USD balance, shown as a +credits bar of consumed spend. An account with no credits shows no bar. A rejected key clears the +row; `503 credit_balance_unavailable`, 408, and 429 keep the last reading. **OpenCode Go quota.** The canonical `opencode-go` preset reads `GET https://opencode.ai/zen/go/v1/usage` with the configured key as a Bearer token and diff --git a/docs-site/src/content/docs/guides/remote-hub.md b/docs-site/src/content/docs/guides/remote-hub.md index ab4e06cd75e..8dfd7f0723a 100644 --- a/docs-site/src/content/docs/guides/remote-hub.md +++ b/docs-site/src/content/docs/guides/remote-hub.md @@ -562,7 +562,7 @@ input checks. opencodex does not publish an official container image. The repository does maintain a source-build [`Dockerfile`](https://github.com/lidge-jun/opencodex/blob/main/Dockerfile), [`compose.yaml`](https://github.com/lidge-jun/opencodex/blob/main/compose.yaml), and a narrow -`.dockerignore`. The build pins the multi-platform Bun 1.4.2 image index by digest, runs the proxy as +`.dockerignore`. The build pins the multi-platform Bun 1.4.4 image index by digest, runs the proxy as the non-root `bun` user, keeps the root filesystem read-only, drops Linux capabilities, and publishes only the data listener on the host's `127.0.0.1:10100` by default. The foreground process uses `OCX_SERVICE=1`, so stopping or recreating the container preserves routed Codex state instead diff --git a/docs-site/src/content/docs/guides/remote-link.md b/docs-site/src/content/docs/guides/remote-link.md index e3dbbf673e3..5fd0d404030 100644 --- a/docs-site/src/content/docs/guides/remote-link.md +++ b/docs-site/src/content/docs/guides/remote-link.md @@ -27,6 +27,8 @@ The dashboard does not ask you to enter a token. It probes the host first, and i The Add Child sheet explains that the Child uses this Home's providers over SSH and lists the prerequisites above. Host aliases come from `~/.ssh/config` on the Home running OpenCodex, not necessarily the computer displaying the browser. If discovery succeeds with no hosts, add a `Host` entry like this, then choose **Rescan hosts**. You can also enter an existing alias manually; selecting or entering an alias enables **Test connection**. The sheet links to this guide. +Host discovery also follows top-level `Include` directives. Parentheses and braces in an included filename stay literal when combined with wildcards: for example, `Include conf.d/*(1).conf` finds `backup(1).conf`, and `Include conf.d/shard-{1..3}-*.conf` looks for filenames containing `{1..3}`. An `Include` inside a `Host` or `Match` block is conditional and is not used for discovery. + ```sshconfig Host devbox HostName devbox.example.com @@ -49,6 +51,8 @@ Connecting restarts OpenCodex on this computer. Codex turns that are already run The Child waits for its configured port while the old process releases it. If a CLI-managed restart still fails, run `ocx start` on the Child and check `~/.opencodex/restart-handoff.log`. In the desktop app, the app starts and supervises the replacement automatically. +`ocx stop` on a Child stops its proxy and leaves Codex routing, the catalog and the Grok config in place, so `ocx start` (or `ocx service start`) brings the Child back unchanged. If `ocx status` on the Child reports that Codex routing is native, run `ocx sync --restart-codex`; `ocx start` does not rewrite a Child's Codex routing. + If **Child** says to pair this machine first, open this computer's configured literal-loopback HTTP dashboard, for example `http://127.0.0.1:`. The local pairing form appears only when pairing is missing and the dashboard and API use the same loopback origin. Copy the form's `ocx gui pair --origin "http://127.0.0.1:"` command, run it in a terminal on this computer, and paste the one-use code into the form. Use the exact origin shown in the form; a provider API key or admin token is not a pairing code. Missing pairing is separate from a configured-port mismatch. On a Hub dashboard opened through a non-loopback address, the browser may have no loopback session or may have a shared session that cannot manage machine links. When Remote Link reports that session or permission problem, choose **Connect this dashboard to the hub** in the Remote Link page to open the one-time pairing form. Run the displayed command on the Hub and enter its code. The page validates the new session's Remote Link access before it closes the form. @@ -59,12 +63,16 @@ The **Child** role also requires a standalone OpenCodex runtime running on its c - **Connected** means the SSH tunnel is ready and the Child can use the Home link. - **Reconnecting** means the tunnel is being retried. Requests can temporarily return `503` with `Retry-After` while the retry is in progress. On a Child that connected from its own dashboard, a request first waits up to 15 seconds for the tunnel to come back. -- **Failed** means the link needs attention. Check SSH authentication, the confirmed host key, forwarding, or the timeout reason shown in the dashboard. The side that runs the SSH tunnel keeps retrying by itself, after sleep, an outage or a restart: Home for a Child it added, and the Child for a link it joined from its own dashboard. It retries about once a minute after a timeout or forwarding error, and every five minutes after an authentication error. A changed host key is never retried. To try again at once, select **Retry** in the Home's dashboard or run `ocx link reconnect --link-id ` on the Home. It restarts that link's tunnel without restarting OpenCodex. +- **Failed** means the link needs attention. Check SSH authentication, the confirmed host key, forwarding, or the timeout reason shown in the dashboard. The side that runs the SSH tunnel keeps retrying by itself, after sleep, an outage or a restart: Home for a Child it added, and the Child for a link it joined from its own dashboard. After a timeout or forwarding error it retries after one minute and doubles the wait after each retry that fails again, up to five minutes; after an authentication error it retries every five minutes. A changed host key is never retried. To try again at once, select **Retry** in the Home's dashboard or run `ocx link reconnect --link-id ` on the Home. It restarts that link's tunnel without restarting OpenCodex. A failed link does not silently switch to a local provider. The Child returns a retryable `503` without forwarding the link key or request when its tunnel is failed, stopped, or not supervised. It also keeps the key local when another process owns the tunnel port; free that port so the Child's SSH tunnel can bind it. +On a Child, `GET /healthz` only says that OpenCodex is running. `GET /readyz` (and `ocx ready`) also says whether the link can carry a request right now: while it cannot, it answers `503` with `Retry-After: 1`, `"status": "pending"` and a `link` object such as `{ "state": "unavailable", "reason": "home_unreachable" }`, and while it can, `200` with `"status": "ready"` and `{ "state": "available" }`. The reasons are `tunnel_connecting`, `tunnel_failed`, `tunnel_idle`, `tunnel_unavailable`, `sidecar_invalid`, `home_unreachable`, `home_unauthorized`, `link_auth_failed`, `link_credential_unavailable` and `link_unverified`. A Child that joined from its own dashboard answers from its tunnel. A Child that its Home added has no tunnel of its own to look at, so it asks the Home through the tunnel with the same credential-free check every relayed request starts with; it repeats that check at most once every five seconds, and answers `link_unverified` when the Home has not replied within half a second. + +To confirm which Home is serving a Child, compare the `x-opencodex-served-by` header on the Child's `GET /v1/models` with the `instanceId` in the Home's `GET /healthz`. The Child passes the Home's header through unchanged, so the two match while the link works. The value is an opaque id, never a hostname; see [Home identity and the served-by header](/reference/management-api/#home-identity-and-the-served-by-header). + ## What a Child shows and manages A Child runs on the Home's providers, accounts and settings, so the Home is where you change them. @@ -95,6 +103,20 @@ When a step fails, the dashboard shows the reason and, when SSH reported one, a - **ocx was not found on the remote computer**: OpenCodex looks for `ocx` on the PATH of a non-interactive SSH session first, then in `~/.bun/bin`, `~/.local/bin`, `/opt/homebrew/bin` and `/usr/local/bin`. If it is installed elsewhere, add that directory to PATH in a file the remote shell reads for non-interactive sessions, such as `~/.zshenv` for zsh. - **OpenCodex on the remote computer is too old**: run `ocx update` on that computer. Remote Link needs 2.66.0 or later. - **The remote computer did not report an OpenCodex version**: `ocx --version` on that computer printed something else, for example the usage text of an unsupported Windows install. +- **Requests fail with `home_unreachable` while the Home is down**: a Child never serves requests from its own providers. When the tunnel is down before a request is forwarded, the Child answers a retryable 503 and sends nothing to the Home. If the tunnel drops after forwarding started, the answer is also `home_unreachable`, but the Home may already have received the request. A failed link relay authentication answers `link_relay_auth_failed` instead: upgrade the Home and Child or re-link. Whether GOAT may move that device's work to its own sign-ins is the per-device local fallback permission on the Home's **Remote Link** page, or `ocx fallback local on --device `; see [Fallback off](/guides/fallback-off/). +- **A Home link stays Failed with a forwarding error after the Home slept or changed networks**: run `ocx link status --json` on the Home. Its `tunnel` field shows the attempts since the link was last connected, `nextRetryAt`, and the last failure. A `lastFailure.cause` of `remote_forward_conflict` means the Child's SSH server still holds the tunnel port for the session that died with the Home's old connection, so every new tunnel is refused until it lets go. OpenCodex keeps retrying and connects on its own once the port is free; it never stops processes on the Child. The Child's SSH server notices a dead session only when it probes it. Without `ClientAliveInterval` in the Child's `sshd_config`, that can take as long as the operating system's TCP keepalive (two hours by default on macOS). Setting `ClientAliveInterval 15` and `ClientAliveCountMax 3` there makes it drop such a session within about a minute. + +### Retryable 503 diagnostics + +A relay-generated `503` includes a server-generated `x-opencodex-link-request-id`. Use it to correlate a failure with local evidence under the Child's OpenCodex configuration home: `link/diagnostics.jsonl` and one rotated backup, `link/diagnostics.jsonl.1`. Each file is capped at 256 KiB, with private permissions (directory 0700 and files 0600 on POSIX; private ACLs on Windows). Records contain only fixed event/outcome/state categories, generated correlation IDs, numeric timing/generation/attempt metadata and timestamps, never secrets, request bodies, headers, URLs, account identifiers or raw errors. Each event/outcome pair is limited to one record per second; the next record includes a count of suppressed events, so an absent record does not prove an absent failure. A backward wall-clock adjustment starts a new rate-limit window, including when the writer queue is full, and preserves the suppression count. An asynchronous writer keeps at most 56 pending records, counts overflow as suppression, and yields while Windows ACL work runs. Logging is best effort and cannot change whether a request is admitted. Graceful signal shutdown and standalone recycling wait up to one second for queued diagnostics before exiting; stalled storage, forced termination and power loss can still lose records. + +`owner_lookup` records refused ownership checks; routine successful checks are silent. `owner_lookup.elapsedMs` measures the owner lookup itself. For `auth_timeout`, `relay_retry` and `relay_failure` report the actual transport-attempt duration in `elapsedMs` (normally about five seconds). A `relay_recovered` event with outcome `recovered` is emitted only after a retry receives an upstream response with `upstream.ok` (HTTP 2xx); it does not prove the streamed body completed. Rate limiting makes request-ID correlation sampled evidence, not a guaranteed trace of every failed request. + +The Home writes the same kind of file under its own configuration home for the tunnels it runs. Each change of a Home-initiated tunnel's state is a `tunnel_transition` record with `previousState`, `state`, the cause as `outcome` (`auth`, `hostkey`, `forward`, `remote_forward_conflict`, `timeout`, `network`, `unknown`, `spawn_failed` or `listener_unavailable`; `connected`, `spawned` or `stopped` when nothing failed), the `attempt` count since the link was last connected, the link's `tunnelPort` and, when a retry is scheduled, `retryAfterMs`. A retry of a failed link that fails again is recorded too, so an outage leaves one record per attempt. The same rate limit applies, across all links. + +Before sending data, OpenCodex allows one shared preflight retry for either an owner-lookup deadline or a specifically identified five-second authentication deadline. The retry obtains fresh owner proof where required and a fresh physical socket, then authenticates that socket before sending the link credential or body. It does not reuse cached owner approval, bypass security checks, restart SSH or OpenCodex, or replay a dispatched request body. The two-second owner-lookup and five-second authentication deadlines are unchanged for each attempt; other failures do not qualify for this preflight retry. + +This can recover these transient deadline paths and helps diagnose future failures. It does not establish the historical cause of [#7297](https://github.com/lidge-jun/opencodex/issues/7297). ## Security @@ -123,6 +145,25 @@ Update both the Home and Child when upgrading to connection-bound relay authenti Removing the final Home link drains pending authenticated relay requests before releasing its listener. Stopping the process still cancels active connections. This does not change which caller credentials are stripped or which routes can be relayed, and it does not replace SSH's host-key verification. +### Home and Child versions + +On the Home, `ocx link status --json` reports the Home's own `local` version, relay authentication version and features, and gives every link a `peer` block. Nothing is read over SSH: the Home learns about a Child from the relay authentication check the Child sends before each relayed request. From 2.91.0 that check also carries the Child's version and feature list. The Home keeps what it learned in memory, so after a Home restart a link reads `unknown` until its Child relays a request again. + +| Part of the link | First release | +|---|---| +| `relay-auth/2`, connection-bound relay authentication | 2.69.0 | +| `link-management-relay`, read-only Home management on the Child | 2.84.0 | +| `link-peer-report/1`, the Child reports its version and features | 2.91.0 | + +`peer.compatibility` is one of: + +- `compatible`: the relay authentication versions match, and the Home serves every feature the Child reports. A Child older than 2.91.0 reports no version; `peer.version` is then `null` and `peer.versionRange` gives the range its relay authentication implies, for example `{ "min": "2.69.0", "below": "2.91.0" }`. +- `degraded`: the Child reports a feature this Home does not serve. Upgrade the Home. +- `incompatible`: the Child asked for a relay authentication version this Home does not speak, so its relayed requests are refused. `peer.reasons` names the side to upgrade. +- `unknown`: the Child has not relayed a request since the Home started, or it sent a version or feature list the Home could not read, so its features cannot be checked. + +The version a Child reports is its own claim, bound to its link key. OpenCodex uses it for display and never for admission. Older `ocx` installations keep receiving the previous status document; only a 2.91.0 or newer CLI asks for these fields. + ### Standalone pairing authorization @@ -142,6 +183,12 @@ On the Home, run `ocx link probe child-a --json` to inspect the offered SSH fing Home-initiated connections now restart the Child after saving its connection, including capable older Child installations. The Child must already serve standalone OpenCodex on its configured port, expose bound restart support, and have curl installed. Update or disconnect a Child that fails this preflight. Desktop and system OpenCodex installations take precedence over generic PATH shims; broken candidates are skipped. Windows Children remain unsupported. +### Windows computers + +`ocx link add` and the dashboard refuse a Windows computer during the probe or host confirmation, before any link key is issued or tunnel started. The refusal covers Windows OpenSSH with Git Bash, MSYS2 or Cygwin as its shell (`uname -s` reports MINGW, MSYS or CYGWIN) and with cmd or PowerShell as its shell. Connect a Windows computer in Hub mode instead: run `ocx hub invite` on a Hub and run the printed `ocx connect` command on the Windows computer. + +A Windows Child linked by an older release keeps its link. Run `ocx link cleanup` on the Home to find such links; add `--link-id ` to check one, or `--json` for a machine-readable report. It asks each Home-initiated Child for its platform over the link's pinned SSH trust and changes nothing. For each Windows Child it prints the recovery order: run `ocx disconnect` and `ocx restart` on the Windows computer, then `ocx link revoke --link-id --force --yes` on the Home, then connect it in Hub mode. A plain revoke fails on these Children because its remote disconnect runs a POSIX command that Windows refuses. + Restart waits for running turns and may take several minutes. Success requires the restarted Child to serve the client role and relay a fresh complete catalog to this Home. SSH setup has a bounded allowance derived from measured latency; relay verification starts after restart with its own 15-second window. Errors name the failing phase. If an error reports residual Child configuration, run `ocx disconnect` and `ocx restart` on that Child before retrying. Home cleanup alone does not restore Child configuration. A CLI timeout reports an uncertain outcome; inspect Home `ocx link status` and Child `ocx connect status` first. On a Home-initiated Child, `ocx link status` reports role `child`, link identity and tunnel port from its saved connection even though its Home-side link store is empty. Locally unobserved tunnel state is `idle`. `ocx status` calls the peer a Remote Link Home. When Home runs in Hub mode, status reads its supported projection through the local Child relay. A standalone Home reports that it does not publish Hub state. Child-initiated tunnel identity comes from its valid sidecar; unreadable evidence does not claim a direction. diff --git a/docs-site/src/content/docs/guides/remote-workspace.md b/docs-site/src/content/docs/guides/remote-workspace.md index 939ea624de9..ade9bc5ed6b 100644 --- a/docs-site/src/content/docs/guides/remote-workspace.md +++ b/docs-site/src/content/docs/guides/remote-workspace.md @@ -167,10 +167,35 @@ to another computer or folder silently. If the Executor disconnects, the session Prompt submission acknowledges acceptance immediately; the dashboard polls the session for progress and completion. If the acknowledgement is lost, the draft remains visible with an unknown-submission notice. Check session progress before sending it again; the dashboard never retries a prompt automatically. +If the Pi process exits or its extension fails while a turn is starting, the session reports the failure even if Pi has not acknowledged that turn yet. + **Stop** remains available while a prompt is running. It interrupts the Hub coding-agent turn, cancels an active Executor command, and prevents a late response from reopening the stopped session. +## Work with sessions side by side + +The session list on the left opens a session in the focused pane. Drag a session to the left, +right, top, or bottom edge of another pane to split the conversation area. You can show up to +four different sessions at once. An already visible session moves between panes instead of +opening a duplicate composer. + +Use **Split right** or **Split below** next to a session for the keyboard-accessible alternative. +Drag a divider to resize the panes, or focus the divider and use its arrow keys. **Home** and +**End** set the divider to its minimum and maximum size. Each session +keeps its own draft, submission state, and Stop action, so a pending turn in one pane does not +block a different session. **Close pane** changes the layout only; it does not stop the session. + +The pane layout is restored for this Hub in the same browser. Only session identifiers and pane +positions and divider sizes are saved; draft text and model output are not written to browser storage. Reloading +the page clears unsent drafts. On screens up to 760px wide, panes stack vertically without +resize dividers; the saved split positions and sizes return when the screen is wider. + +This view is shared by the browser dashboard and the OpenCodex desktop dashboard. CLI users +can open the dashboard with `ocx gui` and select **Remote Workspace** after its normal Hub setup. +It does not change the terminal interface of `codex` or the conversation layout inside the +separate official ChatGPT/Codex desktop app. + ## Restart and reconnect behavior The Hub persists bounded session metadata and a small recent event snapshot. After a Hub restart, diff --git a/docs-site/src/content/docs/guides/sidecars.md b/docs-site/src/content/docs/guides/sidecars.md index 3d35989872b..e25d49dbfe7 100644 --- a/docs-site/src/content/docs/guides/sidecars.md +++ b/docs-site/src/content/docs/guides/sidecars.md @@ -18,6 +18,11 @@ closed. Explicit `xai` requires a usable stored Grok OAuth account and does not requires both ChatGPT login auth and an enabled `forward` provider. ::: +On file load, a retired OpenCode Free model target in global or `claudeCode` overrides of +`visionSidecar` or `webSearchSidecar` is removed and that sidecar is explicitly disabled, retaining +its other settings. Select a supported target before re-enabling it. Healthy targets and the defaults +for absent sidecar sections are unchanged. + ### Additional web-search backends (explicit-only) Three more web-search backends exist beyond the ChatGPT and Claude paths. Each is @@ -138,16 +143,39 @@ failures after response headers have started are delivered as `response.failed` ## Vision sidecar +Mistral's `zai-glm-5-3` is enrolled by the provider preset as text-only. With the +vision sidecar enabled and available, OpenCodex describes attached images and +sends their text descriptions to GLM. The Codex catalog advertises image +attachments for this route. After an update, run `ocx sync`, then fully quit and +relaunch the Codex desktop app or reload the VS Code window. Sync without a +restart flag does not restart an existing Codex app-server, which can retain +the old model list. Alternatively, use `ocx sync --restart-codex` to sync and +restart matching Codex processes and the desktop app. Finish active work first: +this restart ends live conversations. See the [sync and restart reference](/reference/cli/lifecycle/#ocx-sync---restart-codex---restart-app-server-only). +After relaunch, select `mistral/zai-glm-5-3` and verify that image attachments +are available. No model modality override is needed. Mistral documents native input in its +[GLM 5.3 reference](https://docs.mistral.ai/models/zai-glm-5-3). + Image routing is capability-aware. Before an image-bearing upstream send, opencodex resolves the selected model's effective input modalities from runtime provider evidence, explicit operator declarations, backend/registry metadata, and generated vendor metadata. A target positively known to be text-only goes through the Vision Sidecar first; the image is described **before** the main call and replaced inline with text. A target positively known to support images receives the image directly. Unknown custom models keep the existing compatibility behavior rather than being guessed text-only. For the canonical ChatGPT Codex route, opencodex uses the `openai-codex` metadata bundle rather than public OpenAI API metadata, so backend-specific modality differences are respected. The native Chat fast path uses the same gate and cannot bypass a known text-only verdict. Without an available sidecar plan, raw images are stripped before a proven text-only backend. -Combos advertise image input only when every member accepts images, either natively or through a +With `visionSidecar.enabled: false`, the model catalog does not add sidecar-backed image input to +text-only models, including custom rows and combo members. Native image support and explicitly +configured image modalities are preserved. Omitting `enabled` keeps sidecar advertisement on; +catalog visibility does not establish that sidecar credentials or capacity are available. +Combos advertise image input only when every member accepts images, either natively or through an enabled sidecar, and the combo's `imageInput` setting is not disabled, so clients such as the Codex app allow attachments instead of blocking them before the sidecar runs. When `visionSidecar.model` is absent or blank, the OpenAI execution path, Dashboard, and management API use the `gpt-5.6-luna` fallback. Startup still migrates an explicitly persisted legacy `gpt-5.4-mini` value to `gpt-5.6-luna`; that migration applies to a stored value, not to an absent model field. +Saving a multimodal combo from the dashboard enrolls its non-image members automatically: the +Combos page sends them as `visionSidecarTargets` on `PUT /api/combos`, which writes the exact +text-only declaration on the member's provider, so no manual config edit and reload is needed. +Enrollment never overwrites an existing image-capable declaration, and members whose modalities are +unknown or have no text input are not enrolled — the dashboard names them and keeps the combo's +image switch unavailable. The first-party DeepSeek `deepseek-flash` model is native multimodal (`text` and `image`) and does not use this sidecar by default. Explicit `noVisionModels` or text-only declarations remain authoritative. First-party `deepseek-chat`, `deepseek-reasoner`, and `deepseek-v4-flash` remain @@ -222,6 +250,10 @@ timeout, and limit. omitted keys unchanged. `timeoutMs` uses the runtime integer bounds (1–2147483647 ms). +When the effective Vision switch changes, saving also refreshes the managed Codex catalog and +model cache. The response reports `catalogRefreshPending: true` if that refresh did not complete; +the saved switch is retained, and catalog publication does not confirm that a running client has reloaded it. + The web-search sidecar card carries the same control shape: the model picker's first row is **Off**. Off does two things, and the second one is the reason the row exists. OpenCodex stops intercepting `web_search`, and the Codex integration writes Codex's own diff --git a/docs-site/src/content/docs/guides/sub-agent-surface.md b/docs-site/src/content/docs/guides/sub-agent-surface.md index 67ec9f1a8ad..d0d52fee5ba 100644 --- a/docs-site/src/content/docs/guides/sub-agent-surface.md +++ b/docs-site/src/content/docs/guides/sub-agent-surface.md @@ -79,6 +79,60 @@ For a v2 roster, eligibility has three states: an entry stamped `"v2"`, explicit with no `multi_agent_version` field is eligible. A genuine `"v1"` pin is excluded because it states that the model belongs to the other collaboration surface. +## Which models `spawn_agent` advertises + +Codex lists at most five models in the `spawn_agent` tool description, and it chooses them +itself: it sorts the picker-visible catalog rows by `priority`, lowest first, and takes the first +five. The roster on the **Subagents** page (`subagentModels`) shapes that list only by giving its +models the lowest priorities in roster order (`0` through `4` without account selectors). It +does not limit the list to the roster. + +When you feature one to four models, Codex fills the remaining slots with the next rows by +priority. You did not choose those rows, and the dashboard does not show which ones they are. They +depend on the catalog: + +| Catalog | Rows that fill the remaining slots | +| --- | --- | +| No Codex account selectors | Other routed `provider/model` rows. They all share priority `5`, so they appear by provider name and then model id, alphabetically. Unfeatured native GPT rows sit at priority `100` plus the number of featured ids or higher, so they come later. | +| Codex account selectors | Account-qualified native rows (`/`), which directly follow the featured rows. Unfeatured routed rows move to priority `1005` and come after them. | + +For example, with no account selectors and this roster: + +```json +{ "subagentModels": ["xai/grok-4.5", "anthropic/claude-sonnet-5"] } +``` + +| Slot | Model | Priority | Why | +| ---: | --- | ---: | --- | +| 1 | `xai/grok-4.5` | `0` | First roster entry | +| 2 | `anthropic/claude-sonnet-5` | `1` | Second roster entry | +| 3 | `deepseek/deepseek-v4-flash` | `5` | First remaining routed row alphabetically | +| 4 | `google/gemini-3-pro` | `5` | Next routed row | +| 5 | `moonshot/kimi-k3` | `5` | Next routed row | +| 6+ | `zai/glm-5.2`, then native GPT rows | `5`, then `102` | Outside the five | + +With an empty roster, opencodex pins nothing and the five follow the catalog's normal +priorities. + +The filled slots are ordinary advertisements, so the main agent may pick one of those models. +The list is also not an allowlist: Codex accepts a catalog model outside the five when the main +agent names it exactly. To control what Codex advertises: + +- Feature five models. With a full roster, no slot is left to fill. +- Disable a model you never want offered, or leave it out of its provider's `selectedModels`. + A disabled or unselected model leaves the picker and cannot fill a slot. +- `modelPickerOrder` changes the priorities Codex sorts by. A routed-only list moves the listed + unfeatured rows to `1000` and up, so the unlisted routed rows fill the slots first. A list that + contains a bare native id orders the complete picker, featured rows included: listed rows come + first and take the advertised slots before any unlisted row. See + [Model ordering](/guides/model-ordering/). + +On v2, Codex skips rows pinned `"disabled"` before it takes the five; otherwise the window works +the same way. The opencodex v2 guidance roster (`{{roster}}`) names only configured roster models +inside the five, so filler models appear in Codex's tool description and never in opencodex +guidance. Codex reads the catalog when a session starts, so a roster change applies to new +sessions. + ## Delegation model and effort The dashboard's **Sub-agent delegation** controls three related settings: @@ -137,9 +191,12 @@ opencodex no sign that v1 is available, so the guidance is not added there. The default-off `syncCodexSubagentDefaults` option is separate from guidance. When opencodex owns active Codex routing, sync or restart can write the selected values as marker-owned `[agents] default_subagent_model` and `default_subagent_reasoning_effort` entries in Codex TOML. -opencodex updates or removes only fields bearing its markers. If either target field is user-owned, -the pair is left unchanged rather than partially written; ambiguous TOML is rejected without a -write. External provider managers and user-owned root routing also remain authoritative. +opencodex updates or removes only fields bearing its markers. If you edit a managed value in place, +the next sync keeps your value, drops the marker, and treats the field as yours from then on. +Restore also keeps those edits when they recreate previously injected file contents. If +either target field is user-owned, the pair is left unchanged rather than partially written; +ambiguous TOML is rejected without a write. External provider managers and user-owned root +routing also remain authoritative. ## Fallback chains @@ -306,6 +363,12 @@ a positive partial count before passing a model or effort override. It may be picker-hidden, outside the five-model display limit, missing from the catalog, or pinned to v1. A `"v2"`, `null`, or absent surface value is eligible; a real `"v1"` pin is not. +### Why does `spawn_agent` list models I did not feature? + +Codex fills up to five advertised slots from the catalog by priority. With fewer than five +featured models, the next rows take the remaining slots; see +[Which models `spawn_agent` advertises](#which-models-spawn_agent-advertises). + ### Do mode changes affect running sessions? No. Start a new Codex session after changing the mode. If a long-running App host still shows stale diff --git a/docs-site/src/content/docs/guides/subagent-v1-default.md b/docs-site/src/content/docs/guides/subagent-v1-default.md index 5f4fd79b58f..ab8c6654c6f 100644 --- a/docs-site/src/content/docs/guides/subagent-v1-default.md +++ b/docs-site/src/content/docs/guides/subagent-v1-default.md @@ -130,3 +130,13 @@ ocx v2 mode v1 ``` The CLI does not prompt. It is the same setting, so choose it knowing what this page describes. + +## Concurrent child limit + +In **Models → Model settings**, the subagent limit control sets the maximum number of concurrent +child agents, excluding the primary agent. It remains available in v1, base and v2, including +hybrid mode with the global v2 flag disabled. Choose the surface mode separately from this limit. + +Raising the cap permits more children; it does not create them automatically or guarantee faster +execution. Start a new Codex session after changing the limit. If a long-running App host still +uses the previous limit, restart that host. diff --git a/docs-site/src/content/docs/guides/web-dashboard.md b/docs-site/src/content/docs/guides/web-dashboard.md index d808779cd34..0792854aa24 100644 --- a/docs-site/src/content/docs/guides/web-dashboard.md +++ b/docs-site/src/content/docs/guides/web-dashboard.md @@ -71,7 +71,7 @@ host and port over a LAN IP or an alias. ## Dashboard layout -The sidebar has eight rows, in order: **Dashboard**, **Connect**, **Codex**, **Providers**, **Models**, **Subagents**, **Usage & Logs**, and **Remote Link**. **Connect** opens the page headed **Connect**, with its tab strip ordered **Codex**, **Claude**, **Claude Desktop**, **Grok Build**, then the remaining integrations. Claude has no sub-tabs; it shows the Claude Code settings directly. Claude Desktop is a separate Connect tab at `#claude/desktop`. Connect has no section switcher. **Usage & Logs** opens Usage and groups Usage, Logs & Debug, and Storage with a pill-shaped section switcher. The last row, **Remote Link**, groups Remote Link (`#remote`) and Remote Workspace (`#remote-workspace`, when available) with the same switcher. **Codex** is the renamed **Codex Set** row; the preserved URLs include `#codex-set`, `#claude`, `#claude/code`, and `#claude/desktop`; the former `#claude/settings` bookmark opens Code. The former `#claude/account` bookmark redirects to `#providers?provider=anthropic&tab=accounts`. Startup (`#startup`) stays outside the sidebar. +The sidebar has eight rows, in order: **Dashboard**, **Connect**, **Codex**, **Providers**, **Models**, **Subagents**, **Usage & Logs**, and **Remote Link**. **Connect** opens the page headed **Connect**, with its tab strip ordered **Codex**, **Claude**, **Claude Desktop**, **Grok Build**, then the remaining integrations. Claude has no sub-tabs; it shows the Claude Code settings directly. Claude Desktop is a separate Connect tab at `#claude/desktop`. Connect has no section switcher. **Subagents** opens Subagents and groups Subagents and Advisor (`#advisor`) with a pill-shaped section switcher. **Usage & Logs** opens Usage and groups Usage, Logs & Debug, and Storage with a pill-shaped section switcher. The last row, **Remote Link**, groups Remote Link (`#remote`) and Remote Workspace (`#remote-workspace`, when available) with the same switcher. **Codex** is the renamed **Codex Set** row; the preserved URLs include `#codex-set`, `#claude`, `#claude/code`, and `#claude/desktop`; the former `#claude/settings` bookmark opens Code. The former `#claude/account` bookmark redirects to `#providers?provider=anthropic&tab=accounts`. Startup (`#startup`) stays outside the sidebar. Responses first-output timing includes streamed function arguments and custom-tool input, as well as text and reasoning. A tool-only turn can therefore have a first-output time even without prose. @@ -111,9 +111,10 @@ is visible) and never forces an upstream refresh. | Area | What it does | | --- | --- | | **Dashboard summary** | Multi-agent mode, online state, version, uptime, provider count, 30-day token total, active providers, and available native/routed models. | +| **Native reasoning retention** | Independently opt into reasoning ciphertext forwarding on native model or account changes. Both default off; account retention is experimental. See [the configuration scope](/reference/configuration/server/#native-reasoning-retention). | | **Sub-agent delegation** | Choose a native or routed model and optional reasoning effort shared by OpenCodex delegation guidance and the separate native-default opt-in. This is not a proxy-side per-spawn router; see below. | | **Sidecars** | Choose the web-search model and effort plus the vision-description model. Changes apply on the next request. | -| **Maintenance** | Resync the Codex model catalog, inspect project-local config bypass warnings, check the latest or preview release, and run an update with optional proxy restart. In the desktop shell, its update entry opens the native app update page instead of running the package updater. | +| **Maintenance** | Resync the Codex model catalog, inspect project-local config bypass warnings, check the latest or preview release, and run an update with optional proxy restart. With restart on, Codex routing and its model catalog stay in place while the package is replaced, so a Codex app that loads them during the update still sees your routed models; the restarted proxy takes them over, and `ocx stop` restores native Codex if it never comes back. In the desktop shell, its update entry opens the native app update page instead of running the package updater. | | **Startup safety** | Show whether injected Codex routing survives a restart, with separate service and launcher-shim health plus exact repair commands. | | **Windows tray** | Install a per-user login tray for one-click proxy start, stop, restart, dashboard access, and status. The tray is a controller, not a proxy restart service. | | **Codex autostart** | Allow an already-installed Codex launcher shim to run `ocx ensure`. This toggle does not install a shim or background service. | @@ -148,6 +149,9 @@ its request and attempt failure cause is `transport-ambiguous`, with resend perm `refused-ambiguous`: the upstream may already have processed the turn. A genuine provider 429 remains `rate-limit` with resend permission `permitted`. +A Codex move to another account after HTTP 402 appears as **Account quota exhausted (402)** +in the attempt's recovery details and counts as a quota recovery in exported metrics. + Logs filters combine surface, intercepted requests, provider, exact model, status, time, speed, and conversation ID over the currently loaded request ring. Provider and model choices also include fallback attempts; model matching ignores case and surrounding spaces @@ -203,7 +207,13 @@ The **Usage** summary and Models/Providers tables show end-to-end output through The **Usage** Models and Providers tables show the estimated priced portion for each row. Requests without a matching price or usable usage are counted as excluded beside that amount when the proxy -reports pricing coverage fields. A row with only excluded requests shows an em dash with that count +reports pricing coverage fields. Usage that was only partly measured (`unreported` or `unsupported`) +is not priced, and one excluded part excludes the whole request from that row's priced count. A +leg that reached no upstream, or that upstream refused with an HTTP error before any output, spent +nothing and does not; a client cancellation (499), or a leg whose response began or ended without +a status but reported no usage, may have been billed, so it does. A refusal followed by a measured +successful send contributes that send's cost to the summary even when the aggregate usage status +is `unreported`. A row with only excluded requests shows an em dash with that count instead of a zero-dollar estimate, and a displayed zero-dollar value is a confirmed priced estimate. An older proxy that reports no coverage fields keeps a bare em dash with no count, so missing coverage stays distinct from an all-excluded row and from a confirmed zero estimate. @@ -470,3 +480,30 @@ them. Both switches apply immediately; **Save** never changes them. When interce Claude/Anthropic accounts are managed only on **Providers > Anthropic > Accounts**: login and the browser option, the account roster with switch, pause, remove, and reauthentication, the paste-code field, account pool settings, and quota. Claude no longer has an Account sub-tab. Provider-level controls such as the default provider, removal, and the enabled switch stay on Providers. The bookmarks `#claude` and `#claude/code` open this page, and the former `#claude/settings` redirects to `#claude/code`; `#claude/desktop` selects the separate Claude Desktop tab. The old `#claude/account` bookmark redirects to `#providers?provider=anthropic&tab=accounts`. Legacy `#integrations/claude` and `#integrations/claude/desktop` bookmarks still redirect to `#claude/code` and `#claude/desktop`, respectively. Arrow keys move between tabs; Home and End select the first and last tab. + + +## Test one provider model + +In **Providers**, **Test connection** checks discovery connectivity. Use the +separate **Test completion** control to check whether a selected model answers. +Choose a model and accept the confirmation that the request may incur provider +charges. Cancelling the confirmation sends nothing. Changing the provider or +model, or leaving the page, cancels the test and discards a stale result. + +The completion test sends `Reply exactly OK.` once with a 32-token output cap, +a 20-second deadline and a 64 KiB stream limit. It reports TTFT at the first +non-empty text delta, total latency, the upstream-reported model and a match, +mismatch or unknown comparison against the wire model id. Missing or conflicting +metadata is unknown; upstream metadata does not verify model identity. The panel +shows the applicability reason when authentication, adapter or transport settings +cannot be tested. No generated text is displayed, and the diagnostic does not +update routing health, request history or the provider catalog. + +The authenticated management endpoint is +`POST /api/providers/test-completion?name=` with exactly +`{"model":"","confirm":true}`. Dashboard sessions require their usual CSRF +token. Invalid input returns 400, unknown providers return 404 and a concurrent +test for the same provider returns 409. Completed diagnostics and applicability +results return 200, including a structured error when inference fails. Destination +policy and provider egress settings apply; redirects, retries and failover are disabled. +For the CLI workflow, see [Providers and accounts](/reference/cli/providers-accounts/). diff --git a/docs-site/src/content/docs/ja/contributing.md b/docs-site/src/content/docs/ja/contributing.md index 5a82e0e6073..9ad672176ec 100644 --- a/docs-site/src/content/docs/ja/contributing.md +++ b/docs-site/src/content/docs/ja/contributing.md @@ -175,6 +175,10 @@ versioned disposition は `passthrough`、`translated`、`degraded`、`unsupport `passthrough` 以外のクレームには具体的な制限を記載し、fixture に基づくクレームでは根拠となる正確な assertion id を指定してください。秘密情報を含まない request vector を `tests/fixtures/compatibility/` に 追加し、production adapter に対して実行する集中テストを用意します。 +`passthrough` claim が引用できるのは、manifest test でその feature に紐付けられた selector 上の +`equals` assertion で、期待値が同じ位置の呼び出し元の値そのものであるものだけです。これにより、削除・ +書き換え・無関係なフィールドを lossless と表示できません。custom tool とその grammar のように、片方だけ +保たれうる側面は別々の claim にしてください。 compatibility manifest は受動的なデータです。通常の router、Responses handler、server startup path から manifest catalog を import したり、Compatibility Lab を有効化したりしてはいけません。 diff --git a/docs-site/src/content/docs/ja/getting-started/quickstart.md b/docs-site/src/content/docs/ja/getting-started/quickstart.md index 2294fc1d541..216e3891dac 100644 --- a/docs-site/src/content/docs/ja/getting-started/quickstart.md +++ b/docs-site/src/content/docs/ja/getting-started/quickstart.md @@ -18,7 +18,7 @@ ocx init `ocx init` では次の手順を説明します。 -1. **プロバイダーを選択してください** — 104 個の組み込みレジストリプリセットのいずれか、または `custom` を選択してベース URL とアダプターを入力します。 +1. **プロバイダーを選択してください** — 106 個の組み込みレジストリプリセットのいずれか、または `custom` を選択してベース URL とアダプターを入力します。 2. **API キー** — キーを貼り付けるか、`${ANTHROPIC_API_KEY}` のような環境変数を参照します。 3. **デフォルト モデル** — キー、ローカル、カスタム プロバイダーの場合は、プリセットを受け入れるか、モデル ID を入力します。 4. **プロキシ ポート** — デフォルトは `10100` です。 diff --git a/docs-site/src/content/docs/ja/guides/claude-code.md b/docs-site/src/content/docs/ja/guides/claude-code.md index 4d1cd5938b4..8476e7eb932 100644 --- a/docs-site/src/content/docs/ja/guides/claude-code.md +++ b/docs-site/src/content/docs/ja/guides/claude-code.md @@ -166,21 +166,21 @@ unknown は設定がまだ opencodex のプロキシを指すか判断できな ### Picker モード: 1P の Code タブで opencodex モデルを表示する -Picker モードは 1P モードの一部です。macOS で 1P を選ぶとデフォルトで有効になりますが、 +Picker モードは 1P モードの一部です。macOS と Windows で 1P を選ぶとデフォルトで有効になりますが、 `claudeCode.intercept.picker: false` を設定した場合は無効です。1P の Desktop の Code タブにある モデルピッカーを書き換え、利用できる opencodex モデルを名前付きで表示します。初回の有効化時は、 -macOS がログインキーチェーン内のローカル証明書認証局を信頼するよう求めることがあります。この認証局の +macOS がログインキーチェーン内、Windows が現在のユーザーのルートストアでローカル証明書認証局を信頼するよう求めることがあります。この認証局の 制約は `claude.ai` とそのサブドメインに限られます。エクスポート可能な署名用 ID は OS の資格情報ストアで 保護され、通常の再起動でも同じ証明書と鍵を再利用します。OpenCodex の設定ディレクトリには Picker の -署名鍵を平文で保存しません。制約付き CA の完全な検証と OS の信頼確認は引き続き必要です。 -承認済みの同じ ID と利用可能な資格情報ストアがあれば、再起動で証明書の信頼設定を追加・削除しません。 -起動時の復元は信頼をインストールしません。信頼が未設定、取り消し済み、不明の場合は保留となり、 -`ocx claude desktop picker on` または `ocx claude desktop picker trust` を明示的に実行して信頼を付与します。 +署名鍵を平文で保存しません。制約付き CA の完全な検証と OS の信頼確認は引き続き必要です(Windows では正確なフィンガープリントと検証済みリーフで確認します)。 +承認済みの同じ ID と利用可能な資格情報ストアがあれば、再起動で macOS の証明書の信頼設定を追加・削除しません。 +起動時の復元や状態確認は信頼をインストールしません。信頼が未設定、取り消し済み、不明の場合は保留となり、 +`ocx claude desktop picker on` または `ocx claude desktop picker trust` を明示的に実行して信頼を付与します(Windows では確認が表示されることがあります)。 旧 ID からの一度限りの移行では、以前の信頼を削除するために同意が必要になる場合があります。 削除が完了するまでは Picker は利用できず、適用済みプロファイルは復号しない中継を使います。 キーチェーンのロック解除やアプリの資格情報へのアクセス許可は macOS 独自の動作で、再起動や更新時にも -表示される場合があります。Windows と Linux は Picker 非対応で、Picker の CA、資格情報ストア、 +表示される場合があります。Windows では `on` や `trust` の明示的な実行時にルート追加の確認が表示されることがあります。Linux は Picker 非対応で、Picker の CA、資格情報ストア、 プロキシの処理を開始しません。通常の Claude インターセプトは利用でき、ローカル CA ファイルには 所有者、シンボリックリンク、ファイル権限、Windows ACL の検査が適用されます。 @@ -535,7 +535,7 @@ Messages → Responses に変換されるリクエストでは、Claude Code の | Assistant `tool_use` | `function_call`(`input` → JSON 文字列に変換した `arguments`) | | ユーザー `tool_result` | `function_call_output`(`is_error` → `[tool error]` 接頭辞) | | `thinking` / `redacted_thinking` 再生 | シグネチャと秘匿ペイロードを境界付き `ocxr1` エンベロープに保持した `reasoning` 項目 | -| Function ツール | `{type: "function"}`(`web_search*` → `{type: "web_search"}`) | +| Function ツール | `{type: "function"}`(`web_search*` → `{type: "web_search"}`). `api.anthropic.com` への翻訳ルートでは、型付き `web_search_*` と `web_fetch_*` 宣言も Anthropic に送信され、サーバー側で実行されます。ただし Web 検索サイドカーが `web_search` を提供する場合はそちらが優先されます。検索と取得の結果は `server_tool_use` と対応する `web_search_tool_result` / `web_fetch_tool_result` としてクライアントに返されます | | `tool_choice` | `auto`→`auto`、`none`→`none`、`any`→`required`、名前指定関数→`{type:"function",name}`、ホスト型 WebSearch/web_search→`{type:"web_search"}` | | `max_tokens` | `max_output_tokens` | | `stop_sequences` | `stop` | diff --git a/docs-site/src/content/docs/ja/guides/codex-app-models.md b/docs-site/src/content/docs/ja/guides/codex-app-models.md index 63d62a27364..ef99c820f3a 100644 --- a/docs-site/src/content/docs/ja/guides/codex-app-models.md +++ b/docs-site/src/content/docs/ja/guides/codex-app-models.md @@ -52,10 +52,11 @@ visibility = "list" |ルート |ピッカー ID とカタログのメタデータ | | --- | --- | -| Codex ログイン (account-qualified 行が無効) | `gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-5.6-luna` などの bare native id を表示し、`codexAccountMode` に従って Pool または Direct を使用します。GPT-5.6 行のカタログ ウィンドウは 922,000 トークンです。 | +| Codex ログイン (account-qualified 行が無効) | `gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-5.6-luna` などの bare native id を表示し、`codexAccountMode` に従って Pool または Direct を使用します。GPT-5.6 行のカタログ ウィンドウは 922,000 トークンです。 `gpt-6.1-terra`:既定 500,000 / 長文 872,000。 | | Codex ログイン (account-qualified 行が有効で、有効な selector あり) | 有効な selector とサポート対象 native model の各組み合わせに `/` 行を表示します。各行は対応付けられたアカウントだけを使用し、bare native 行はピッカーで非表示になります。Native metadata と context window は保持されます。 | -| OpenAI (API キー) |正確に 8 つの名前空間行: `gpt-5.5`、`gpt-5.6`、Sol/Terra/Luna、および 3 つの `*-pro` 仮想 ID (コンテキスト 922,000、8 つすべての最大入力 922,000) | +| OpenAI (API キー) | 名前空間付きモデルには `gpt-5.5`、`gpt-5.6`、Sol/Terra/Luna、`*-pro` 仮想 ID、Daybreak エイリアスが含まれます(コンテキスト 1,050,000、最大入力 922,000)。`openai-apikey/gpt-6.1-terra` はコンテキスト 1,050,000、最大出力 128,000 です。 | |オープンルーター | `openrouter/openai/gpt-5.6-sol`、`openrouter/openai/gpt-5.6-terra`、`openrouter/openai/gpt-5.6-luna` (922,000) | +| OpenRouter | `openrouter/openai/gpt-6.1-terra` (1,050,000) | | Cursor | 静的フォールバックには `cursor/gpt-5.6-sol`、`cursor/gpt-5.6-terra`、`cursor/gpt-5.6-luna` (1,000,000) と、Grok 4.5 / 4.6 / 4.7 の通常・Fast 行 (500,000) が含まれます。4.6 と 4.7 は `xhigh` も公開し、ライブアカウントの検出によって表示される行が決まります。 | | xAI | xAI のライブ検出が優先されます。フォールバックには `xai/grok-4.6` と `xai/grok-4.7` が含まれ、デフォルトは `xai/grok-4.5` です。3 モデルともコンテキストは 500,000 トークンです。Grok 4.6 と 4.7 は `low` / `medium` / `high` / `xhigh`(上流のデフォルトは `high`)を提供し、Grok 4.5 は `high` までです。 | diff --git a/docs-site/src/content/docs/ja/guides/combos.md b/docs-site/src/content/docs/ja/guides/combos.md index 1a03b3e6a07..cb31f349e60 100644 --- a/docs-site/src/content/docs/ja/guides/combos.md +++ b/docs-site/src/content/docs/ja/guides/combos.md @@ -126,7 +126,7 @@ ocx combo set balanced \ |機密認証、サブスクリプション、クォータ、レート制限、過負荷、またはアップストリーム サーバー エラー |ステータスだけでは物足りない場合でもターゲットを冷やしてホップさせましょう。 | |クライアントのキャンセル (499)、`origin_rejected`、サイバー ポリシーの拒否、コンテキスト オーバーフロー、またはその他の無効なリクエスト |停止してエラーを返します。別のターゲットではリクエストは有効になりません。 | | ChatGPT アカウントで Codex を使う際に、そのモデルをサポートしないことを示す正確な HTTP 400 の拒否 | 出力を確定する前に、次の設定済みターゲットを試します。拒否文全体が `detail`、`error.message`、または本文そのものにある場合に限ります。`detail` と `error` が競合する応答は終了扱いです。この拒否だけでアカウントを再認証待ちにしたり、プロバイダーの全モデルをクールダウンしたりしません。 | -|`user` の明示的な拒否、`reasoning.effort`/`reasoning_effort` の非対応値、またはモデル固有の画像入力拒否(`param: input`)を示す構造化 HTTP 400 |出力開始前に次の適格なターゲットへ進み、クールダウンを記録しません。任意パラメーターの互換性を参照してください。 | +|`user` の明示的な拒否、`reasoning.effort`/`reasoning_effort` の非対応値、モデル固有の画像入力拒否(`param: input`)、またはルートが読み取れない入力メディアの拒否(`unsupported_input_modality`)を示す構造化 HTTP 400 |出力開始前に次の適格なターゲットへ進み、クールダウンを記録しません。任意パラメーターの互換性を参照してください。 | |インプロセスアダプター(`runTurn`)が実行する Responses ターンで、現在のリクエストが宣言していない最初のツール呼び出し(出力やリプレイ不可の副作用より前) |ターゲットをクールダウンし、同じツールカタログで次へ進みます。可視出力やリプレイ不可の副作用の後は拒否が確定します。Chat Completions と Anthropic Messages のリクエストは変わりません。 | |その他の未分類のエラー |停止してエラーを返します。 | @@ -184,7 +184,7 @@ v1/base/v2 モードと完全な暗号化タスクのワークフローについ ### ダッシュボード -ローカル ダッシュボードを開き、**Models → コンボ**を選択します。ワークスペースはコンボを作成、編集、名前変更、削除し、そのターゲット ピッカーは無効なモデルとネストされたコンボを除外します。 +ローカル ダッシュボードを開き、**Models → コンボ**を選択します。ワークスペースではコンボの作成、編集、名前変更、削除ができます。モデル選択には非表示モデルも含まれます。表示設定は外部のモデル一覧にのみ適用され、コンボ内部の選択には影響しません。非表示モデルを選択しても外部には表示されません。ターゲット選択では、無効なプロバイダー、ネストされたコンボ、決定サービス専用の JEV プロバイダーは引き続き除外されます。 各ターゲットには **利用可能**、**クォータを使い切りました**、**クォータ不明** のライブバッジも表示されます。 エディターがクォータを理由に保存と作成をブロックするのは、使用可能なすべてのターゲットについて、設定された認証情報の推論上限に達したことを示す、サーバーによる確認が現在も有効な場合だけです。表示専用のアカウント・モデル・検索・MCP クォータや、ルーティングの根拠情報の欠落・期限切れによって、このブロックが発生することはありません。ブロックは該当するリセット時刻またはデータの有効期限に解除され、ページがアクティブになるか表示状態になると再確認されます。「更新」はコンボデータとクォータの両方を再読み込みします。ダッシュボードのエディターではまだ `cooldownMs` と `waitForCooldownMs` を設定できません。後続の UI 作業が完了するまでは、構成ファイルまたは管理 API を使用してください。 @@ -201,9 +201,9 @@ ocx combo remove --yes ``` `set` は、`--strategy`、`--sticky`、`--effort`、`--alias`、`--native-alias`、 -`--display-name`、および `--rename-from` も受け入れます。`--native-alias` には現在 +`--display-name`、`--description`、および `--rename-from` も受け入れます。`--native-alias` には現在 サポートされている bare native alias と空でない display name が必要です。`--effort`、`--alias`、 -`--display-name` に `-` を指定すると、その値をクリアできます。`create` および `update` は `set` の +`--display-name`、`--description` に `-` を指定すると、その値をクリアできます。`create` および `update` は `set` の エイリアス、`delete` は `remove` のエイリアスです。すべてのコンボ サブコマンドは `ocx route combo ...` からも利用できます。 ### 管理 API @@ -248,6 +248,7 @@ ocx combo remove --yes | `alias` |いいえ |なし |オプションのトリミングされたパブリック モデル ID。上記のエイリアス ルールを使用します。空の値はエイリアスなしで保存されます。 | | `nativeAlias` |いいえ | `false` | 現在サポートされている bare native alias に routing/catalog の優先権を明示的に与えます。 | | `displayName` |いいえ |なし | catalog 表示専用ラベル。`nativeAlias` が true の場合は必須です。 | +| `description` |いいえ |なし | catalog 行の表示専用の説明。制限は `displayName` と同じです。未設定の場合は生成された `Routed via opencodex → combo (combo).` が使われます。 | ## トラブルシューティング @@ -269,6 +270,6 @@ ocx combo remove --yes ## 任意パラメーターの互換性 -通常の 400 エラーは終了扱いですが、`user` の明示的な拒否、`reasoning.effort`/`reasoning_effort` の非対応値、またはモデル固有の画像入力拒否(`param: input`)を示す構造化エラーでは、出力開始前に次の適格なターゲットへ進めます。この不一致ではクールダウンを記録しません。安全ポリシーによる拒否、キャンセル、出力開始後の再実行は禁止のままです。 +通常の 400 エラーは終了扱いですが、`user` の明示的な拒否、`reasoning.effort`/`reasoning_effort` の非対応値、モデル固有の画像入力拒否(`param: input`)、またはルートが読み取れない入力メディアの拒否(`unsupported_input_modality`)を示す構造化エラーでは、出力開始前に次の適格なターゲットへ進めます。この不一致ではクールダウンを記録しません。安全ポリシーによる拒否、キャンセル、出力開始後の再実行は禁止のままです。 [Canonical compatibility details](/guides/combos/#request-local-target-compatibility). diff --git a/docs-site/src/content/docs/ja/guides/integrations.md b/docs-site/src/content/docs/ja/guides/integrations.md index 1cecbcb394f..3a952318d4e 100644 --- a/docs-site/src/content/docs/ja/guides/integrations.md +++ b/docs-site/src/content/docs/ja/guides/integrations.md @@ -3,7 +3,7 @@ title: クライアント統合 description: ダッシュボードから opencodex を OpenCode、Pi、OMP、Hermes、OpenClaw、Kimi Code、gjc、DeepSeek Harness、MiniMax Code、ZCode、Prime Agent、Aside、Raycast、omo、Cline CLI、Kilo、Command Code、Factory Droid に接続します。クライアントごとにスイッチがあり、書き込み前には必ずバックアップを取ります。 --- -**接続** タブは、各クライアントの設定ファイルに opencodex のプロバイダーブロックを書き込み、必要に応じて削除します。次の 18 クライアントは、それぞれのスイッチで管理できます。 +**接続** タブは、各クライアントの設定ファイルに opencodex のプロバイダーブロックを書き込み、必要に応じて削除します。以下のクライアントは、それぞれのスイッチで管理できます。 | クライアント | 設定ファイル | 形式 | 変更が反映される時点 | 認証情報 | |---|---|---|---|---| diff --git a/docs-site/src/content/docs/ja/guides/providers.md b/docs-site/src/content/docs/ja/guides/providers.md index 114fa62773f..2c5920a8c38 100644 --- a/docs-site/src/content/docs/ja/guides/providers.md +++ b/docs-site/src/content/docs/ja/guides/providers.md @@ -57,7 +57,9 @@ Codex login を Pool モードで使うと、Providers の概要には任意の [`retryOn429`](/ja/reference/configuration/)(同一キーでの 429 リトライ)は API キー プロバイダー (`authMode: "key"`)のみに適用されます。OAuth・forward・ローカル プリセットは除外されます — 同じトークンを再送すべきではなく、ローカルランタイムには保存すべきリモートキーがありません。 -オプトインです: オプションが無ければ無効、オブジェクトがあれば `enabled: false` でない限り有効です。 +ドキュメントに記載された宛先固有のデフォルトを除き、オプトインです。正規の Mistral Chat は、既存のリクエスト全体の送信枠の範囲内で、粘り強いリトライポリシー(再送 6 回、フォールバック間隔 10 秒)を使います。`enabled: false` を含む明示的な `retryOn429` 設定が優先されます。Mistral プリセットは、渡されたプロンプトキャッシュキーもデフォルトで転送します。`promptCacheKey: false` で転送を無効にできます。キャッシュアフィニティは再利用の可能性を高めますが、キャッシュヒットを保証せず、トークンスループットの制限もなくしません。 + +Mistral は Studio キーと Vibe キーの両方に 1 つの API キー プロバイダーを使います。キーの種類とプランの許容量は [Mistral の API キーのドキュメント](https://docs.mistral.ai/admin/identity-access/api-keys)に記載されています。プロキシはキーからサブスクリプションのティアを推測しません。すべてのモデルで、上流が明示した `Retry-After` の期限は、同じ認証情報とモデルでの呼び出し間で共有されます。リトライ時刻のない 429 は、有効な分単位カウンターが容量の枯渇を示す場合、または拒否されたクエリの実測コストが残りトークンを超えつつ公表された全許容量には収まる場合に、控えめな 60 秒の回復待機を開始することがあります。これはローカルの回復ポリシーであり、サーバーのリセット時刻を正確に予測するものではありません。正常なトラフィックでは、次のクエリのキャッシュやコストを予測しません。待機後に回復プローブを 1 回だけ送り、成功すれば通常のトラフィックに戻ります。欠落、不正な形式、利用権ゼロ、月間枠の枯渇、全許容量超過といった根拠からは、リセットを作り出したり履歴をコンパクトしたりしません。`requestPacing: { "enabled": false }` または `retryOn429: { "respectRetryAfter": false }` で共有ペーシングを無効にします。同じキーでの再送について、Mistral のリトライ時刻が `retryOn429.maxIntervalMs` を超える場合、プロキシは早すぎる再送をせず、429 とリトライのヒントを返します。ラウンドロビンのキー選択が有効な場合、待機中のリクエストはキーのクールダウンを再確認して代替キーのペーシング容量を確保し、出力前のキー拒否はリクエストの既存の送信上限内で正常なキーに切り替えて回復できます。コンテキストウィンドウとモデルごとのコンパクション設定は独立したままです。 ## 1. ChatGPT ログイン(forward / パススルー) @@ -101,6 +103,7 @@ ocx login google-antigravity ocx login cursor # Cursor 専用 PKCE ログイン ocx login command-code # Command Code のブラウザ OAuth (または ~/.commandcode/auth.json を取り込み) ocx login devin # Cognition/Devin: Devin CLI の資格情報を優先インポート、なければ Auth0 ブラウザサインイン +ocx login dsh-account # DeepSeek Harness Desktop (~/.dsh/.credentials.yaml の許可を取り込み) ocx login github-copilot # GitHub デバイスフロー → Copilot トークン (Copilot Pro/Business) ocx login codex # Codex アカウントプール (別名: chatgpt, openai / プロキシの起動が必要) ocx logout @@ -117,6 +120,7 @@ ocx logout | `google-antigravity` | `google` | `https://daily-cloudcode-pa.googleapis.com` | Google OAuth を Cloud Code Assist wire で使用。ライブ探索は認証済みの CCA `v1internal:fetchAvailableModels` エンドポイントを使用し、ログイン中のアカウントで利用可能な agent モデルのみを公開します。管理されたカタログはフォールバックとして残ります。 | | `cursor` | `cursor` | `https://api2.cursor.sh` | 実験的 PKCE ログイン、HTTP/2 トランスポート、アカウント別モデル探索をサポート。 | | `devin` | `devin` | `https://server.codeium.com` | 実験的な非公式 Cognition/Devin ブリッジ。ログインはまず、インストール済み Devin CLI が保持する認証情報を取り込みます(`devin auth login` が `devin-session-token` を自身の `credentials.toml` に書き込みます)。なければ Auth0 のブラウザサインインを開き、貼り付けたトークンを `RegisterUser` で長期 API キーに交換します。`ocx login devin-cli` は非推奨エイリアスとして引き続き使えます。モデル一覧は `GetCascadeModelConfigs` でアカウントごとに取得し、ストリーミングは Connect-RPC 上の `runTurn` 経路のみを使います。ダッシュボードのプリセットには既定で含まれません。 | +| `dsh-account` | `dsh-account` | `https://api.deepseek.com/anthropic` | 実験的なインポート優先アカウントプロバイダー。インストールおよびサインイン済みの DeepSeek Harness (DSH) Desktop アカウントを再利用します。明示的なインポート時に `~/.dsh/.credentials.yaml` から厳格に読み取り専用で資格情報を読み込みます。DeepSeek API キー課金とは完全に独立しています。 | | `github-copilot` | `openai-chat` | `https://api.githubcopilot.com` | 実験的。GitHub デバイスフロー + `copilot_internal` 交換(VS Code OAuth クライアント)。有効な Copilot サブスクリプションが必要で、公式のサードパーティ API ではありません。 | Google Antigravity のアカウント・プロバイダーのクォータ確認は、モデル一覧へのフォールバックも含め、固定の Google エンドポイントを使用します。その宛先では透過 Fake-IP DNS に対応し、TLS 検証、リダイレクト拒否、プライベートアドレス検査を維持します。カスタム base URL はモデル要求にのみ適用されます。`NO_PROXY` は直接接続のポリシーを維持します。 @@ -191,9 +195,20 @@ Kiro のログインには Kiro CLI が必要です。Unix では `curl -fsSL ht ロールバックはスナップショットがある場合にのみ可能なため、セッションストアが存在するのに取得できない場合(ファイルが読めない、スキーマの不一致、トークン選択があいまい)、`KIROCLI_DB_PATH` / `KIRO_CLI_DB_FILE` が実際の CLI ストアと異なるインポート先を指す場合、またはプライマリ CLI データベースに認識できるトークン行がない場合、**アカウントを追加**は `kiro-cli` のログアウトを拒否します。通常の `kiro-cli` データパス上の壊れたデータベースを修復または削除し、インポート専用セレクタが設定されていれば解除してから再試行してください。既存の `kiro-cli` セッションがまったくない環境には影響しません。 +### DeepSeek Account (DSH) + +`dsh-account` は、インストールおよびサインイン済みの DeepSeek Harness (DSH) Desktop アプリケーションの認証済みアカウント資格情報を再利用する、実験的なインポート優先アカウントプロバイダーです。 + +- **DeepSeek API キーから独立:** `dsh-account` はユーザーの認証済み DeepSeek セッションクォータ(通常/ボーナスウォレット残高および総支出)を使用しますが、`deepseek` プロバイダーはプラットフォーム API キー残高から課金されます。この2つの資格情報ドメインおよびルーティングは厳格に分離されており、相互にフォールバックすることはありません。 +- **明示的インポートのみ:** OpenCodeX は DeepSeek Harness がインストールされサインインされているか(`~/.dsh/.credentials.yaml`)を検出しますが、ユーザーの明示的な操作(`ocx login dsh-account` または Web ダッシュボードでのインポートクリック)なしにトークンを自動的にコピー、インポート、または使用することはありません。 +- **DSH 資格情報ストアへの読み取り専用:** OpenCodeX は `~/.dsh/.credentials.yaml` を厳格に読み取り専用として扱います。OpenCodeX が DeepSeek Harness に属するファイルの書き込み、変更、削除、またはアクセス権の変更を行うことは決してありません。 +- **隔離された資格情報の複製とログアウト:** インポート時、OpenCodeX は DeepSeek の信頼できる認証エンドポイント(`/auth-api/v0/users/current`)に対してトークンを検証し、OpenCodeX 認証ストアに独自の複製を保存します。OpenCodeX でアカウントをログアウトまたは削除しても、OpenCodeX のローカル複製が削除されるだけであり、上流のリモートセッションログアウトがトリガーされたり、DeepSeek Harness Desktop からサインアウトされたりすることはありません。 +- **バックグラウンドトークン自動ローテーションなし・401 時の再インポート:** DSH セッショングラントには自動バックグラウンド更新エンドポイントがありません。上流から HTTP 401(`ACCOUNT_TOKEN_INVALID`)が返された場合、OpenCodeX はアカウントを `needsReauth` としてマークし、ルーティングを停止します。アクセスを再開するには、ユーザーが `ocx login dsh-account` で明示的に再インポートする必要があります。 +- **サードパーティ免責事項:** OpenCodeX は、サードパーティクライアントによるセッショングラントの再利用について、DeepSeek 公式からの承認や保証を主張するものではありません。 + ## 3. API キーカタログ -opencodex には組み込みプリセットが 104 個含まれています。キー方式 85、OAuth 15、ローカル 3、 +opencodex には組み込みプリセットが 106 個含まれています。キー方式 85、OAuth 17、ローカル 3、 デフォルト ChatGPT 転送プリセット 1 です。ダッシュボードの **Add provider** ピッカーはキー発行ページを開き、 入力したキーを検証した後保存します(検証はプロバイダー固有です)。主な項目は以下のとおりです: @@ -247,7 +262,7 @@ Cline IDE/CLI のみで API からは使えません。`minimax/minimax-m2.5` | Z.AI (GLM Coding) | `https://api.z.ai/api/coding/paas/v4` | | Zhipu AI (BigModel) | `https://open.bigmodel.cn/api/paas/v4` | | [BigModel Coding Plan — Responses (静的モデル一覧)](/guides/providers/#bigmodel-coding-plan-over-responses) | `https://open.bigmodel.cn/api/v1` | -| Qwen Cloud | トークンプラン(デフォルト): `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` · 従量課金: `https://dashscope.aliyuncs.com/compatible-mode/v1` · またはカスタム | +| Qwen Cloud | トークンプラン(デフォルト): `https://token-plan.maas.qwencloudapi.com/compatible-mode/v1` · 従量課金: `https://dashscope.aliyuncs.com/compatible-mode/v1` · またはカスタム | | Tencent Cloud Coding Plan | `https://api.lkeap.cloud.tencent.com/coding/v3` | | SiliconFlow | `https://api.siliconflow.cn/v1` | | Volcengine Ark · Coding Plan · Agent Plan | `https://ark.cn-beijing.volces.com/api/v3` · `https://ark.cn-beijing.volces.com/api/coding/v3` · `https://ark.cn-beijing.volces.com/api/plan/v3` | @@ -267,13 +282,20 @@ Chat Completions モデル(および Responses 専用で Responses にルー [OpenGateway ダッシュボード](https://opengateway.ai/api-keys)でキーを作成し、 `ocx provider add opengateway` を実行するか、ダッシュボードで **OpenGateway** を選択してください。 Chat リクエストは設定済みの Bearer キーを使います。公開一覧はキーの有効性を検証しません。 +Responses では OpenGateway がネイティブのカスタムツール呼び出しの再送を拒否するため、プリセットはコードモードの `exec` など Codex のカスタムツールを送信前に関数ツールへ変換します。 +ツール結果のコンテンツ配列はテキストとして送り、画像は対応するツール結果グループの後に保持します。refusal や不透明なコンテンツなど、サポートされない結果パートは送信前に明示的なクライアント入力エラーとして拒否します。 +画像の詳細度 `original` はゲートウェイが受け付ける `high` に変換し、画像のバイトはそのまま保ちます。 +ネイティブ Responses ルートは、テキストのみの空の完了を成功として返す前に検査します。 +出力予算を使い切った場合は `max_output_tokens` を伴う `incomplete` になり、プロキシは要求された予算を増やしません。予算が残っていて安全に再送できる空の完了は、既存の送信上限の範囲内で 1 回だけ再試行することがあります。明示的な `emptyCompletionRetry: false` または `OCX_EMPTY_COMPLETION_RETRY=0` でこの再試行を無効にします。テキスト、refusal、ツールの動作、未知のストリームイベントがあると再送せず、元のストリームを保持します。この保護は他のプロバイダーやそのリトライのデフォルトを変更しません。 +再送には明示的な `store: false`、完全な入力、クライアント実行のツールのみが必要です。`web_search` などのホスト型ツールがあると、ツールイベントが観測されていなくても自動再送しません。 + +**OpenCode Zen**(`opencode-zen`)は [opencode.ai/auth](https://opencode.ai/auth) の API キーで `https://opencode.ai/zen/v1` を使用します。無料モデルは毎分約 15–20 リクエストの短時間レート制限に当たることがあります(コミュニティ計測。OpenCode は RPM を公表していません)。レート制限 429 に `Retry-After` がない場合、opencodex は案内と合成 `Retry-After` を追加し、有効な上流ヘッダーを優先します。同一キーの再試行は [`retryOn429`](/ja/reference/configuration/) で有効にします。 -**OpenCode Zen**(`opencode-zen`)とキー不要の **OpenCode Free** プリセットは -`https://opencode.ai/zen/v1` を共有します。このゲートウェイ上の無料モデルは、しばしばおおよそ毎分 15–20 リクエストの短時間レート制限に当たります(コミュニティ計測。OpenCode は RPM を公表しません)。Zen は `Retry-After` / `X-RateLimit-*` ヘッダーなしの汎用 429 を返すことがあります。これはキー不要デスクトップ枠(`opencode-free` で Big Pickle/無料モデル約 200 回 / 5 時間)とは別です。Zen がそのような 429 で `Retry-After` を省略した場合、opencodex はクライアント向けエラーに案内を足し、合成 `Retry-After` を付けます(上流の `Retry-After` があればそれが優先されます)。同一キーの待機再試行は [`retryOn429`](/ja/reference/configuration/) でオプトインします。 +正規の `opencode-zen` プロバイダーで API キー認証と公式ルート `https://opencode.ai/zen/v1` または `https://opencode.ai/inference/v1` を使用すると、Gemini は既定の `googleMode: ai-studio` で Google ネイティブ API を自動的に使用します。Zen のモデル ID は、`-tiered` 接尾辞のない Flash ID もそのまま保持します。Claude は `googleMode` に関係なく Anthropic Messages を使用します。保存済み設定の読み込み時、これらのネイティブモデルの適格な旧 Chat/Responses オーバーライドと、適格な Responses 専用 `image_generation` ホストツール設定のみを無視します。カスタムプロバイダー、キー認証以外のルート、その他の設定は変更しません。 -**キー不要の `opencode-free` 枠は、現在サードパーティのクライアントに閉じられています。** Zen は `x-opencode-session` ヘッダーを伴わないリクエストをすべて拒否し、エラータイプ `MissingSessionID` と "OpenCode's free tier can only be used in OpenCode" というメッセージを返します。関門はヘッダーの有無だけを見るため、プロキシは値をでっち上げれば通過できますが、opencodex はそうしません。セッション識別子とバージョン付きの `opencode/` User-Agent を作って送ることは、自分が OpenCode クライアントであると主張することであり、OpenCode はこのキー不要の枠についてサードパーティ連携の契約を公開していません。その方法で得た HTTP 200 は許可ではなく、突破された関門にすぎません。そこで opencodex は回避せずに制限を報告します。`opencode-free` へのリクエストは、上流の関門を説明するエラーを返します。 +**`opencode-free` のサポートは削除されました。** OpenCode の上流無料枠の制限により、すべてのモデルが `403 permission_denied` を返すためです([#7255](https://github.com/lidge-jun/opencodex/issues/7255))。既存設定の読み込み時は該当項目と依存するモデル参照をメモリ上で無効にし、プロセスごとに一度警告します。ファイルは変更しません。既定のプロバイダーだった場合、標準の OpenAI OAuth 経路を既定にします。`opencode-go`、API キー付きの `opencode-zen`、または別のプロバイダーを使用してください。[OpenCode Zen 規約](https://opencode.ai/docs/zen/)。 -同じモデルに至るサポートされた経路は、[opencode.ai/auth](https://opencode.ai/auth) で発行した OpenCode Zen API キーを使う **`opencode-zen`** プリセットです。OpenCode が後にキー不要の枠へのサードパーティ経路を公開すれば、opencodex もそれに従えます。それまでこのプリセットは制限を記録する役割を担います。上流の規約: [opencode.ai/docs/zen](https://opencode.ai/docs/zen/)。 +正常な Claude Desktop の割り当てと既定値は保持されます。ソースモデルの許可リストが空になるルールは無効になり、注入モデルが削除された場合はネイティブサブエージェント同期も無効になります。その後の設定保存では、削除済み参照を復元せず、正常な同時編集を保持します。 大半は bearer キーと共に `openai-chat` アダプターを使い、**Xiaomi MiMo**(`xiaomi`)などの Anthropic 互換プリセットは `anthropic` アダプター(`x-api-key`)を使います。Xiaomi には @@ -443,6 +465,9 @@ Sol/Terra/Luna をフォールバックリストに入れています。 | Codex ログイン(Pool または Direct) | `gpt-5.6-*` | 922,000 | | OpenAI (API キー) | `openai-apikey/gpt-5.6-*` と `*-pro` | 922,000 (max input 922,000) | | OpenRouter | `openrouter/openai/gpt-5.6-sol`、`openrouter/openai/gpt-5.6-terra`、`openrouter/openai/gpt-5.6-luna` | 922,000 | +| Codex | `gpt-6.1-terra` | 500,000 / 872,000 | +| OpenAI API | `openai-apikey/gpt-6.1-terra` | 1,050,000 (128,000 max output) | +| OpenRouter | `openrouter/openai/gpt-6.1-terra` | 1,050,000 | | Cursor | `cursor/gpt-5.6-sol`、`cursor/gpt-5.6-terra`、`cursor/gpt-5.6-luna` | 1,000,000 | ネイティブ GPT-5.6 項目は固定の上流推論段階に従います。例えば Luna には @@ -462,7 +487,7 @@ Amazon Bedrock ネイティブ API のような、これらの実装のいずれ Gateway** は URL にアカウント + ゲートウェイ ID を埋める必要があります。 Copilot は混在 wire カタログを提供します。モデル(`gpt-5.3-codex`、`gpt-5.4`、 -`gpt-5.4-mini`、`gpt-5.5`、`gpt-5.6-luna`、`gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`)はエージェント +`gpt-5.4-mini`、`gpt-5.5`、`gpt-5.6-luna`、`gpt-5.6-sol`、`gpt-5.6-terra`、 `gpt-6.1-sol`、 `gpt-6.1-terra`、 `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`)はエージェント 通信の `/chat/completions` を拒否するため、opencodex はこれらのモデルを組み込みデフォルトで Responses API 経由にルーティングし、他の Copilot モデルはすべて chat completions のままです。 優先順位は次のとおりです: ハード wire ピン → 明示的な diff --git a/docs-site/src/content/docs/ja/guides/remote-link.md b/docs-site/src/content/docs/ja/guides/remote-link.md index 4ae8b634195..905cfb3db49 100644 --- a/docs-site/src/content/docs/ja/guides/remote-link.md +++ b/docs-site/src/content/docs/ja/guides/remote-link.md @@ -55,7 +55,7 @@ Home のプロバイダーを使うコンピューターで次の操作を行い - **Connected** は SSH トンネルが準備でき、Child が Home のリンクを使える状態です。 - **Reconnecting** はトンネルを再試行している状態です。再試行中はリクエストが `Retry-After` 付きの `503` を一時的に返すことがあります。自分のダッシュボードから接続した Child では、リクエストはまずトンネルの復帰を最大 15 秒待ちます。 -- **Failed** は対応が必要な状態です。SSH 認証、確認済みのホストキー、転送、タイムアウトの理由を確認してください。SSH トンネルを張る側が、スリープ、障害、再起動の後も自動で再試行します。Home が追加した Child では Home が、自分のダッシュボードから接続した Child では Child が再試行します。タイムアウトや転送エラーの後は約 1 分ごと、認証エラーの後は 5 分ごとです。ホストキーが変わった場合は再試行しません。すぐに再接続するには、Home のダッシュボードで **Retry** を選ぶか、Home で `ocx link reconnect --link-id ` を実行します。OpenCodex を再起動せずに、そのリンクのトンネルだけを張り直します。 +- **Failed** は対応が必要な状態です。SSH 認証、確認済みのホストキー、転送、タイムアウトの理由を確認してください。SSH トンネルを張る側が、スリープ、障害、再起動の後も自動で再試行します。Home が追加した Child では Home が、自分のダッシュボードから接続した Child では Child が再試行します。タイムアウトや転送エラーの後は 1 分後に再試行し、失敗が続くたびに間隔を倍にして最大 5 分ごとになります。認証エラーの後は 5 分ごとです。ホストキーが変わった場合は再試行しません。すぐに再接続するには、Home のダッシュボードで **Retry** を選ぶか、Home で `ocx link reconnect --link-id ` を実行します。OpenCodex を再起動せずに、そのリンクのトンネルだけを張り直します。 リンクが失敗しても、ローカルプロバイダーへ自動的に切り替わることはありません。 diff --git a/docs-site/src/content/docs/ja/guides/sidecars.md b/docs-site/src/content/docs/ja/guides/sidecars.md index 4d623877c98..0ffc670f9a8 100644 --- a/docs-site/src/content/docs/ja/guides/sidecars.md +++ b/docs-site/src/content/docs/ja/guides/sidecars.md @@ -72,8 +72,11 @@ stall は全体生成 timeout ではありません。SSE 開始前の失敗は ルーティングモデルが該当プロバイダーの `noVisionModels` にある、またはそのモデルが `modelInputModalities` でテキスト専用と宣言され、リクエストに画像が来る場合、opencodex は利用可能な ビジョンサイドカー計画があるときに限り、メイン呼び出し**前に**各画像を説明したテキストに差し替えます。 -計画が利用できない場合は、生の画像をテキスト専用バックエンドへ転送せず削除します。モデルカタログは -サイドカー対象の各モデルに画像入力を広告します。コンボは、すべてのメンバーがネイティブまたはサイドカーを +計画が利用できない場合は、生の画像をテキスト専用バックエンドへ転送せず削除します。`visionSidecar.enabled: false` の場合、 +モデルカタログはカスタムの項目やコンボのメンバーを含むテキスト専用モデルに、サイドカーによる画像入力を追加しません。 +ネイティブの画像対応と明示的に設定された画像モダリティは維持されます。`enabled` を省略した場合、サイドカーによる +画像入力の広告は引き続き有効です。カタログに表示されることは、サイドカーの認証情報や処理容量が利用可能であることを +保証しません。コンボは、すべてのメンバーがネイティブまたは有効化されたサイドカーを 通じて画像を受け入れ、かつコンボの `imageInput` 設定が無効でない場合にのみ画像入力を広告します。これにより Codex アプリなどのクライアントは、サイドカー実行前に添付をブロックせず許可できます。`visionSidecar.model` が未設定または空の場合、 OpenAI 実行経路、ダッシュボード、管理 API は `gpt-5.6-luna` をフォールバックとして使います。起動時には @@ -137,6 +140,8 @@ OpenAI 実行経路、ダッシュボード、管理 API は `gpt-5.6-luna` を `PUT /api/sidecar-settings` は同じフィールドを受け付けます。部分更新では省略したキーをそのまま残します。`timeoutMs` はランタイムの整数範囲(1–2147483647 ms)を使います。 +`PUT /api/sidecar-settings` によりビジョンの実効的なオン/オフ状態が変わると、保存後に管理対象のカタログとキャッシュが更新されますが、`catalogRefreshPending=true` は更新が完了しなかったことを示し、クライアントによる受領確認を意味しません。 + Web 検索サイドカーのカードも同じ構成です。モデルピッカーの先頭行が **オフ (Off)** です。オフにすると OpenCodex は `web_search` への介入をやめ、Codex 統合は `~/.codex/config.toml` に `web_search = "disabled"` を書き込みます。Codex は自身のモードがそうなるまでネイティブの diff --git a/docs-site/src/content/docs/ja/guides/sub-agent-surface.md b/docs-site/src/content/docs/ja/guides/sub-agent-surface.md index c8d4e06b544..af233ba13d0 100644 --- a/docs-site/src/content/docs/ja/guides/sub-agent-surface.md +++ b/docs-site/src/content/docs/ja/guides/sub-agent-surface.md @@ -33,6 +33,42 @@ opencodex は、これを最終パスとしてライブ `/v1/models` カタロ v2 ロスターの場合、適格性には 3 つの状態があります。`"v2"` スタンプが付いているエントリー、明示的に `null` に設定されているエントリー、または `multi_agent_version` フィールドのないエントリーが適格です。純正の `"v1"` ピンは、モデルが他のコラボレーション サーフェスに属していると記載されているため、除外されます。 +## `spawn_agent` が提示するモデル + +Codex は `spawn_agent` ツールの説明に最大 5 つのモデルを列挙し、その選択は Codex 自身が行います。ピッカーに表示されるカタログ行を `priority` の昇順に並べ、先頭の 5 つを取ります。**Subagents** ページのロスター (`subagentModels`) がこのリストに影響するのは、ロスター順に最も低い priority (アカウントセレクターがない場合は `0`〜`4`) を割り当てることだけです。リストをロスターに限定するわけではありません。 + +モデルを 1〜4 個だけ指定すると、Codex は残りの枠を priority 順に次の行で埋めます。これらの行はユーザーが選んだものではなく、どのモデルが入ったかはダッシュボードにも表示されません。どの行が入るかはカタログによって決まります。 + +| カタログ | 残りの枠を埋める行 | +| --- | --- | +| Codex アカウントセレクターなし | その他のルーティング済み `provider/model` 行。すべて priority `5` を共有するため、プロバイダー名、次にモデル ID のアルファベット順に並びます。指定されていないネイティブ GPT 行の priority は `100` に指定した ID の数を足した値以上なので、その後に来ます。 | +| Codex アカウントセレクターあり | アカウント指定のネイティブ行 (`/`)。指定した行の直後に来ます。指定されていないルーティング済み行は priority `1005` に移り、その後に来ます。 | + +たとえば、アカウントセレクターがなく、ロスターが次のとおりの場合: + +```json +{ "subagentModels": ["xai/grok-4.5", "anthropic/claude-sonnet-5"] } +``` + +| 枠 | モデル | Priority | 理由 | +| ---: | --- | ---: | --- | +| 1 | `xai/grok-4.5` | `0` | ロスターの 1 番目 | +| 2 | `anthropic/claude-sonnet-5` | `1` | ロスターの 2 番目 | +| 3 | `deepseek/deepseek-v4-flash` | `5` | アルファベット順で最初の残りのルーティング済み行 | +| 4 | `google/gemini-3-pro` | `5` | 次のルーティング済み行 | +| 5 | `moonshot/kimi-k3` | `5` | 次のルーティング済み行 | +| 6+ | `zai/glm-5.2`、続いてネイティブ GPT 行 | `5`、続いて `102` | 5 つの枠外 | + +ロスターが空の場合、opencodex は何も固定せず、5 つの枠はカタログ本来の priority に従います。 + +埋められた枠も通常の提示と同じなので、メインエージェントがそのモデルを選ぶことがあります。また、このリストは許可リストではありません。メインエージェントが正確な名前を指定すれば、Codex は 5 つの枠外のカタログモデルも受け付けます。Codex が提示するリストを制御するには: + +- モデルを 5 つ指定します。ロスターが埋まっていれば、補充される枠は残りません。 +- 提示させたくないモデルは無効にするか、そのプロバイダーの `selectedModels` から外します。無効または未選択のモデルはピッカーから消えるため、枠を埋めることはありません。 +- `modelPickerOrder` は Codex が並べ替えに使う priority を変更します。ルーティング済み行だけのリストでは、列挙された未指定の行が `1000` 以上に移るため、列挙されていないルーティング済み行が先に枠を埋めます。ベアのネイティブ ID を含むリストは、指定した行も含めてピッカー全体を並べ替えます。列挙された行が先頭に来るため、列挙されていないどの行よりも先に提示枠を占めます。[Model ordering](/guides/model-ordering/) を参照してください。 + +v2 では、Codex は 5 つを取る前に `"disabled"` に固定された行を除外します。それ以外は同じ仕組みです。opencodex の v2 ガイダンスのロスター (`{{roster}}`) は、5 つの枠内にある設定済みのロスターモデルだけを挙げます。そのため、補充されたモデルは Codex のツール説明にだけ現れ、opencodex のガイダンスには現れません。Codex はセッション開始時にカタログを読むため、ロスターの変更は新しいセッションから反映されます。 + ## 委任モデルと推論負荷 ダッシュボードの **サブエージェント委任** は、次の 3 つの関連設定を制御します。 @@ -163,6 +199,10 @@ curl -X PUT http://localhost:10100/api/injection-model \ ピッカーで非表示になっているか、5 つのモデルの表示制限を超えているか、カタログから欠落しているか、v1 に固定されている可能性があります。 `"v2"`、`null`、または表面値が存在しない場合は対象となります。実際の `"v1"` ピンはそうではありません。 +### 指定していないモデルが `spawn_agent` に表示されるのはなぜですか? + +Codex は最大 5 つの提示枠を、カタログから priority 順に埋めます。指定したモデルが 5 つ未満の場合、残りの枠には次の行が入ります。上の「`spawn_agent` が提示するモデル」の節を参照してください。 + ### モードの変更は実行中のセッションに影響しますか? いいえ。モードを変更した後、新しい Codex セッションを開始します。長時間実行されているアプリ ホストで依然として古いカタログ状態が表示される場合は、`ocx sync` を実行して、その Codex サーフェスを再起動します。 diff --git a/docs-site/src/content/docs/ja/guides/subagent-v1-default.md b/docs-site/src/content/docs/ja/guides/subagent-v1-default.md index 7cdae1cc726..ab3814d4e71 100644 --- a/docs-site/src/content/docs/ja/guides/subagent-v1-default.md +++ b/docs-site/src/content/docs/ja/guides/subagent-v1-default.md @@ -94,3 +94,9 @@ ocx v2 mode v1 ``` CLI は確認を求めません。同じ設定を変更するため、このページの内容を理解したうえで選んでください。 + +## 子エージェントの同時実行上限 + +**Models → Model settings** のサブエージェント上限設定では、主エージェントを除く子エージェントの同時実行数の上限を指定します。v1、base、v2 のほか、グローバル v2 フラグが無効なハイブリッドモードでも利用できます。サーフェスのモードは、この上限とは別に選択します。 + +上限を増やすとより多くの子を許可できますが、自動生成や高速化を保証するものではありません。変更後は新しい Codex セッションを開始してください。長時間動作している App ホストで以前の上限が使われ続ける場合は、そのホストを再起動してください。 diff --git a/docs-site/src/content/docs/ja/reference/cli.md b/docs-site/src/content/docs/ja/reference/cli.md index deead3c02df..c766a1cdfaf 100644 --- a/docs-site/src/content/docs/ja/reference/cli.md +++ b/docs-site/src/content/docs/ja/reference/cli.md @@ -20,7 +20,7 @@ opencodex CLI は `ocx` です。最初のコマンド名でディスパッチ 管理コマンドは、2 番目の構成パスを維持するのではなく、記録されたランタイム ポートと ID チェックを使用して、稼働中のプロキシの管理 API をラウンドトリップします。停止したプロキシまたは到達不能なプロキシは HTTP 503 として表され、ゼロ以外の CLI 終了が生成されます。オフライン構成操作として明示的に文書化されているコマンドは、代わりに、稼働中のプロキシを使用せずに設定ファイルを検証および編集できます。 -`ocx system codex-cli-update check` は稼働中のプロキシを必要とせず、パッケージレジストリにも問い合わせません。設定済みのインストール候補について、秘匿化された実行ファイルの場所や所有権を示す根拠を含む来歴メタデータを、範囲を限定して検査します。公開ランチャー由来の信頼済みコンテキストが真正性を裏付けるのは候補のスナップショットだけであり、Codex が正常に実行されたことではありません。この単発コマンドは Codex を一切実行しないため、環境または永続化された状態から得た候補は報告対象にとどまります(`managed: false`、通常は `selection_unattested`)。`selectionAttested` は常に `false` です。JSON 出力には `candidateAvailable`、`candidateVersion`、`candidateSource`、`selectionAttested: false` が含まれます。Bun またはソースから直接起動するとランチャーの証明がないため、環境由来および永続化された候補を無視し、POSIX では `candidate_unavailable`、Windows では `windows_inspection_deferred` を報告することがあります。Windows では、この最初のスライスは候補や構成のパスに対するファイルシステム I/O を一切行いません。信頼済みランチャーが取り込んだ絶対パスの環境候補だけを、アプリ同梱またはバージョンマネージャーとして字句的に報告でき、それ以外の Windows 候補はすべて失敗時閉鎖になります。このコマンドはソフトウェアのインストールや修復、Codex または npm の実行、稼働中プロセスの制御、設定やキャッシュ状態への書き込みを行いません。 +`check` はレジストリへの問い合わせも Codex の実行も行いません。POSIX では、公開ランチャーが記録した環境候補とランタイムリゾルバーが選ぶ正規パスが一致すると `selectionAttested: true` になり、リンクされた npm-global パッケージのマニフェストで所有権を確認でき、候補バージョンが報告されている場合はそれがマニフェストのバージョンと一致するときに `managed: true` になります。永続化された候補は認証されません。Windows は候補や設定ファイルを読まずに検査を延期します。Bun やソースからの直接実行にはランチャーの証明がありません。`plan` は分離した一時 npm 設定・キャッシュ・ログで公式レジストリに問い合わせ、実行中のセッションや不完全なプロセス観測を拒否します。インストールやプロセスへのシグナル送信は行いません。[コマンドと拒否理由の詳細(英語)](/reference/cli/agents/#read-only-codex-cli-update-plan)を参照してください。 Windows x64 インストールの観測は [`attest` コマンド](/ja/reference/cli/agents/)を参照してください。明示パスを省略すると、証明にバインドされたランチャースナップショットが特定した選択候補を観測します。更新権限やランタイム選択は証明しません。 diff --git a/docs-site/src/content/docs/ja/reference/cli/agents.md b/docs-site/src/content/docs/ja/reference/cli/agents.md index 9c891b8b913..ebe43edfffc 100644 --- a/docs-site/src/content/docs/ja/reference/cli/agents.md +++ b/docs-site/src/content/docs/ja/reference/cli/agents.md @@ -204,7 +204,9 @@ opencode は `{env:OPENCODEX_OPENCODE_API_KEY}` を補間します。opencodex ヘッドレス ランタイムの設定、起動、同期、診断、更新を管理します。 -`ocx system codex-restart --yes` は `ocx sync --restart-codex` と同じモジュールで Codex app-server を再起動し、デスクトップ アプリも完全に終了して再起動します。プロキシ自体が Codex アプリ内で動いている場合、完了できない引き渡しを約束せず、実行可能な案内とともに拒否します。 +`ocx system codex-restart --yes` は `ocx sync --restart-codex` と同じモジュールで Codex app-server を再起動します。モデル ピッカーは起動時に構築されるため、デスクトップ アプリを完全に終了して再起動するのは、現在のモデル カタログが書き込まれる前にアプリが起動していた場合、またはこのリクエストで `model_catalog_json` の参照先が変わった場合です。それ以外の場合、アプリは開いたまま自身の app-server を再起動し、未送信の下書きや保留中の承認を保持します。アプリまたはカタログの状態を読み取れない場合は完全な再起動を行います。プロキシ自体が Codex アプリ内で動いている場合、デスクトップの再起動は完了できない引き渡しを約束せず、実行可能な案内とともに拒否します。 + +このリクエストで `model_catalog_json` の参照先が変わった場合も、シェルがカタログファイルの書き込み後に起動していてもデスクトップを再起動します。 ```bash ocx system settings --stream-mode eager-relay @@ -216,7 +218,7 @@ ocx system settings --stream-mode eager-relay ocx system codex-cli-update check --json ``` -`check` はパッケージレジストリに問い合わせず、設定済みのインストール候補について、秘匿化された実行ファイルの場所や所有権を示す根拠を含む来歴情報を、範囲を限定して検査します。公開ランチャー由来の信頼済みコンテキストが真正性を裏付けるのは候補のスナップショットだけであり、Codex が正常に実行されたことではありません。この単発コマンドは Codex を一切実行しないため、環境または永続化された状態から得た候補は報告対象にとどまります(`managed: false`、通常は `selection_unattested`)。`selectionAttested` は常に `false` です。JSON 出力には `candidateAvailable`、`candidateVersion`、`candidateSource`、`selectionAttested: false` が含まれます。Bun またはソースから直接起動するとランチャーの証明がないため、環境由来および永続化された候補を無視し、POSIX では `candidate_unavailable` を報告することがあります。Windows では、この最初のスライスは候補や構成のパスに対するファイルシステム I/O を一切行いません。信頼済みランチャーが取り込んだ絶対パスの環境候補だけを、アプリ同梱またはバージョンマネージャーとして字句的に報告でき、それ以外の Windows 候補はすべて失敗時閉鎖になります。このスライスは永続化された選択状態を一切読み取らないため、環境候補がキャプチャされていない Windows 実行では `candidate_unavailable` ではなく `windows_inspection_deferred` を報告します。コマンドは Codex CLI が導入されているかどうかを観測できないので、候補が存在しないと断定せず、検査が延期されたことを報告します。このコマンドは Codex やパッケージマネージャーの実行、shim の修復、設定やキャッシュ状態への書き込み、プロセスの停止、インストールを行いません。アプリ同梱、認識済みのバージョンマネージャー、未検証のスタンドアロン、曖昧な shim の各候補は管理対象外または不明として報告され、管理対象と判定されることはありません。 +`check` はレジストリへの問い合わせも Codex の実行も行いません。POSIX では、公開ランチャーが記録した環境候補とランタイムリゾルバーが選ぶ正規パスが一致すると `selectionAttested: true` になり、リンクされた npm-global パッケージのマニフェストで所有権を確認でき、候補バージョンが報告されている場合はそれがマニフェストのバージョンと一致するときに `managed: true` になります。永続化された候補は認証されません。Windows は候補や設定ファイルを読まずに検査を延期します。Bun やソースからの直接実行にはランチャーの証明がありません。`plan` は分離した一時 npm 設定・キャッシュ・ログで公式レジストリに問い合わせ、実行中のセッションや不完全なプロセス観測を拒否します。インストールやプロセスへのシグナル送信は行いません。[コマンドと拒否理由の詳細(英語)](/reference/cli/agents/#read-only-codex-cli-update-plan)を参照してください。 Windows で `CODEX_CLI_PATH=codex` のような単純なコマンド名、リモートパス、デバイスパスが候補としてキャプチャされた場合は、`candidate_path_unavailable` を報告します。候補は取得されていますが、そのパスはこの検査の対象になりません。 diff --git a/docs-site/src/content/docs/ja/reference/cli/lifecycle.md b/docs-site/src/content/docs/ja/reference/cli/lifecycle.md index 84acd32d7f1..17b27dc3f4f 100644 --- a/docs-site/src/content/docs/ja/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/ja/reference/cli/lifecycle.md @@ -28,7 +28,13 @@ ocx start --socks5-off ### `ocx stop` -実行中のプロキシを (PID によって) 停止し、PID ファイルを削除して、ネイティブ Codex を復元します。マネージド バックグラウンド サービスがインストールされている場合、`ocx stop` はそれを最初に停止するため、プロキシを再起動できません。Web ダッシュボードの **停止** ボタンは同じ処理 (`POST /api/stop`) を実行しますが、Windows タスク スケジューラだけは例外です。タスク終了後もラッパーがプロキシを再起動しうるため、ダッシュボードは `respawnable_service` で拒否し、何も変更せずに `ocx stop` の実行を促します。 +実行中のプロキシを (PID によって) 停止し、PID ファイルを削除します。スタンドアロンの所有者ではネイティブ Codex を復元します。マネージド バックグラウンド サービスがインストールされている場合、`ocx stop` はそれを最初に停止するため、プロキシを再起動できません。Web ダッシュボードの **停止** ボタンは同じ処理 (`POST /api/stop`) を実行しますが、Windows タスク スケジューラだけは例外です。タスク終了後もラッパーがプロキシを再起動しうるため、ダッシュボードは `respawnable_service` で拒否し、何も変更せずに `ocx stop` の実行を促します。 + +接続済みの hub クライアントまたは Remote Link Child では、`ocx stop`(成功した API 停止を含む)は接続が所有する Codex のルーティング、モデルカタログ、Grok 設定を保持します。`ocx start` はルーティングを再注入せず、その設定で再び処理します。Codex を自身のアカウントへ戻すには `ocx disconnect`、既にネイティブルーティングへ戻した後の再適用には `ocx sync --restart-codex` を使います。 + +互換性を確認できない古い通常プロキシは、サービスやプロセスを停止する前に拒否されます。監視元からランタイムを更新・再起動して再試行してください。 + +既に停止済みのクライアントでは、サービス管理側が非アクティブで、既知のローカル接続先にランタイムが存在しないことを確認してから、通常の未実行結果を返します。 ### `ocx restart` @@ -63,7 +69,7 @@ ocx eject back ### `ocx recover-history --ocx-compaction --yes` -ルーティングされたプロバイダーで圧縮されたタスクをネイティブ Codex で再開する前に、その履歴を修復します。このコマンドは UUID で 1 つのタスクだけを選択し、非公開のバイト単位バックアップを保存してから、OpenCodeX 所有の `ocx1:` 圧縮状態だけをネイティブ Codex が再生できる通常の要約に変換します。ネイティブの暗号化コンテンツと他のタスクは変更しません。実行前に対象タスクを閉じてください。処理中に rollout が変更された場合、ファイルを置き換えずに修復を中止します。 +ルーティングされたプロバイダーで圧縮されたタスクをネイティブ Codex で再開する前に、その履歴を修復します。このコマンドは UUID で 1 つのタスクだけを選択し、非公開のバイト単位バックアップを保存してから、OpenCodeX 所有の `ocx1:`/`ocx2:` 圧縮状態だけをネイティブ Codex が再生できる通常の要約に変換します。ネイティブの暗号化コンテンツと他のタスクは変更しません。実行前に対象タスクを閉じてください。処理中に rollout が変更された場合、ファイルを置き換えずに修復を中止します。 ### `ocx uninstall`・`ocx remove` @@ -137,7 +143,7 @@ ocx status --json 認証不要の `GET /readyz` エンドポイントで同期後の準備状態を確認します。準備完了時は `200`、 `pending` または終端状態の `failed` では `Retry-After: 1` とともに `503` を返します。HTTP の -サニタイズ済み識別フィールドは `{service, version, uptime, pid, port, status, protocol, minimumClientProtocol, managementUrl}` です。`protocol` は hub の現在の remote protocol、`minimumClientProtocol` は互換性のある最小 client protocol、`managementUrl` は browser から見える canonical management origin です。`/readyz` がない +サニタイズ済み識別フィールドは `{service, version, uptime, pid, port, status, protocol, minimumClientProtocol, managementUrl, features}` です。`protocol` は hub の現在の remote protocol、`minimumClientProtocol` は互換性のある最小 client protocol、`managementUrl` は browser から見える canonical management origin です。`/readyz` がない 旧プロキシは `unreachable` として fail-closed し、`/healthz` は readiness ではなく別の liveness 確認です。 デフォルトでは 1 回だけ probe します。`--wait` は準備完了または timeout まで polling しますが、 終端 `failed` を確認すると即座に終了します。デフォルト timeout は 45 秒で、`--timeout ` には @@ -156,6 +162,10 @@ ocx status --json ### `ocx sync [--restart-codex] [--restart-app-server-only]` +> **Remote Link Child:** `ocx sync` は Home の Codex 設定も適用し、クライアントランタイムがその適用より前に起動していた場合は、再起動フラグなしでもランタイムを再起動します。再起動に失敗した場合は以前のランタイムが動作し続け、コマンドは 0 以外で終了します。`ocx status --json` と `ocx link status --json` の関連フィールドは [Remote Link apply parity](/reference/cli/lifecycle/#remote-link-apply-parity)(英語)を参照してください。 + +再起動フラグを明示した場合、カタログとキャッシュのバイト列が変わらなくても、`config.toml` の変更が正常にコミットされれば再起動します。設定だけを扱う操作が変更なし、拒否、事前検証、ロールバックとなった場合は再起動しません。再起動フラグがなければ、sync が Codex を自動的に再起動することはありません。 + 構成されているすべてのプロバイダーからライブ モデル リストを取得し、マージされたカタログを Codex に再挿入します。プロバイダーを追加した後、または利用可能なモデルを更新するために実行します。 存続期間の長い Codex `app-server` プロセスがまだ実行されている場合、`ocx sync` は、`opencodex-catalog.json` / `models_cache.json` が更新されても、以前のメモリ内モデル リストを提供し続ける可能性があることを警告します。`--restart-codex` を渡すと、一致する `codex … app-server` および `codex-code-mode-host` プロセスを再起動し、さらに macOS、Linux、Windows で Codex デスクトップ アプリを完全に終了して再起動します。モデル ピッカーがカタログを読み直すためです。進行中の会話は終了します。広範な `pkill -f codex` 一致は意図的に回避されます。 @@ -166,6 +176,8 @@ ocx status --json コマンドを Codex アプリ内から実行すると、再起動は切り離されたヘルパーに引き渡され、このセッションはアプリとともに終了します。 +**接続先を固定した同期。** `ocx sync --expect-link `(Remote Link Child)と `ocx sync --expect-hub-key `(hub クライアント)は、このマシンがまだそのリンク、またはそのキーで接続している場合にのみ同期します。そうでなければ何も変更せず、ローカルのプロバイダーも同期せず、終了コード 5 で終了し、stderr の最後の行は `{"phase":"child_link_mismatch"|"hub_client_mismatch","reason":...}` になります。不正な形式または重複したフラグは終了コード 2 で終了します。詳細は[英語版リファレンス](/reference/cli/lifecycle/#sync-only-from-the-expected-home---expect-link-and---expect-hub-key)を参照してください。 + ### `ocx sync-cache [--restart-codex] [--restart-app-server-only]` Codex のローカル モデル ピッカー キャッシュを無効にし、アクティブな opencodex カタログから再構築されるようにします。 `ocx sync` と同じ、古い `app-server` 警告とオプションの再起動フラグが適用されます。 diff --git a/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md index f920a33c459..7e2a2c06150 100644 --- a/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md @@ -131,6 +131,14 @@ Codex pool selection applies to the next request after clearing existing affinit ### `ocx account list [provider] [--json] [--all] [--quota [--refresh]]` +保存済みの ChatGPT プールアカウントは、ネイティブ Codex ホームがないホストでも、 +ダッシュボードやアカウント更新時を含めて一覧表示できます。一覧表示によって Codex ホームが +作成されたり、ネイティブのメインアカウントからサインアウトしたりすることはありません。 +Codex クライアント連携を無効にしても、既存のネイティブログインのクォータ観測は無効になりません。 +明示的に指定した `CODEX_HOME` のパスが無効な場合は、引き続き設定エラーを返します。 +`OPENCODEX_HOME` のストレージが安全でない場合は、ネイティブ Codex ホームがなくてもエラーを返します。 +既定の Codex ホームのパス解決では、ファイル、リンク先が存在しないシンボリックリンク、辿れない親ディレクトリもエラーになります。 + プロバイダーを使用しない場合、Codex プール、OAuth アカウント、および設定された API キー プールが一覧表示されます。 `--all` が存在しない限り、空のプロバイダーはスキップされます。プロバイダーを使用すると、その資格情報ファミリーのみがリストされます。人間の出力では `PROVIDER TYPE ID PLAN/LABEL PRIORITY STATUS` を使用します。手動で選択した Codex 行には `selected` というマークが付けられます。利用可能な Kiro アカウントが 2 つ以上保存されている場合、既定では 429 を受けると別のアカウントへ自動的に切り替え、既知の残り利用枠が最も多いアカウントを優先します。この切り替えはアカウントの存在によって有効になり、無効にはできません。`oauthAccountFailover.enabled: false` が断るのは 429 復旧ではなく、送信前にアカウントを選ぶ動作だけです。`ocx account login kiro` はアカウントを 1 件ずつプールへ追加します。結果が空であっても成功です。 `--json` は次を返します: ```text diff --git a/docs-site/src/content/docs/ja/reference/configuration/advisor.md b/docs-site/src/content/docs/ja/reference/configuration/advisor.md index c80ac5da93b..d78a179f231 100644 --- a/docs-site/src/content/docs/ja/reference/configuration/advisor.md +++ b/docs-site/src/content/docs/ja/reference/configuration/advisor.md @@ -1,6 +1,6 @@ --- title: アドバイザー -description: OpenCodex 自身のエキスパート相談サイドカー — 設定されたエキスパートモデルがルーティングされたワーカーに助言を返します。manual と preflight の 2 つのポリシーを提供します。 +description: manual、preflight、証拠に基づく adaptive ポリシーによる独立した専門家への相談。 --- アドバイザーはワーカーのタスクをレビューし助言を返す独立したエキスパートモデルです。相談は OpenCodex がエンドツーエンドで所有します。プロキシはワーカーのターンに合成 `advisor` ツールを注入し、通常のルーティング権威を通じて相談を自ら実行し、助言を再注入して元のワーカーを継続させます。ワーカーは委譲も spawn もせず、プロバイダー資格情報も持ちません。 @@ -9,6 +9,8 @@ description: OpenCodex 自身のエキスパート相談サイドカー — 設 ## 設定 +ダッシュボードの **Advisor** ページでカタログからモデルを選択します。ネイティブ、プロバイダー、アカウント指定、カスタム、コンボの識別子をそのまま保存します。無効なモデルと初期選択待ちのモデルは新規選択に表示されません。選択可能な一覧にない保存済み識別子は警告付きで保持され、別のモデルを選ぶか解除するまで変更されません。読み込みに失敗しても選択は保持され、再試行できます。テキストの助言を返せるモデルを選んでください。カタログは相談の互換性を保証しません。CLI と API は引き続きルーター対応のモデル文字列を受け付けます。 + ```json { "advisor": { @@ -26,11 +28,12 @@ description: OpenCodex 自身のエキスパート相談サイドカー — 設 | `enabled?` | `boolean` | `false` | マスタースイッチ。無効ならリクエストパスにアドバイザーの動作は一切ありません。 | | `model?` | `string` | — | エキスパートモデル。ルーターが受け付ける任意のモデル文字列:ネイティブモデル(`gpt-6-astra`)、明示的な `provider/model`(`anthropic/claude-sonnet-4-6`、`xai/grok-...`)、アカウント修飾ネイティブモデル。クロスプロバイダーを完全にサポートします。 | | `effort?` | `string` | `"max"` | アドバイザー呼び出しの推論強度(`low`〜`ultra`)。 | -| `policy?` | `"manual" \| "preflight"` | `"manual"` | 相談のタイミング。 | +| `policy?` | `"manual" \| "preflight" \| "adaptive"` | `"manual"` | 相談のタイミング。 | +| `maxConsultationsPerTask?` | `integer` | `3` | 適応型タスクの相談上限(1〜3回、手動呼び出しを含む)。 | | `timeoutMs?` | `number` | `120000` | ループバック相談のタイムアウト。 | | `contextSharingConsent?` | `"v1"` | なし | 設定されたアドバイザーのプロバイダーへタスク文脈を送ることへの操作者の同意。現在有効な値は `"v1"` だけです。欠落、古い値、その他の値ではタスク内容は送られません。`enabled: true` だけでは同意になりません。 | -ダッシュボードの **Advisor** ページまたは `ocx advisor status|on|off|set --model --effort --policy ` で管理します。 +ダッシュボードの **Advisor** ページまたは `ocx advisor status|on|off|set --model --effort --policy ` で管理します。 現在の同意がないとき、`ocx advisor on` はプロバイダー間送信を有効にしません。開示を表示して停止します。`ocx advisor on --ack-context-sharing` と `ocx advisor consent` が `v1` を記録します。`ocx advisor consent --revoke` は同意を消し、送信を直ちに止めます。`ocx advisor set` は同意を与えません。ダッシュボードの同意チェックは初期状態でオフです。 @@ -78,5 +81,13 @@ OpenCodex はプロバイダー API キー、Authorization ヘッダー、OAuth ## PR1 の制限 - ネイティブ OpenAI パススルーのターン(ChatGPT プールのワーカー)には合成ツールが注入されません。アドバイザーはルーティング(翻訳)プロバイダーを対象とします。preflight 相談は run-turn アダプターに適用されますが、ツールは適用されません。 -- 適応トリガーはありません:詰み検出、繰り返し失敗の分析、エスカレーション階層、複数アドバイザー、投票はありません。`manual` と `preflight` のみです。 +- 意味的な停滞検出、完了前レビュー、段階的エスカレーション、複数専門家、投票は行いません。 - preflight の重複排除台帳はプロセス内です。プロキシ再起動後、進行中のタスクはもう一度 preflight 相談を受けることがあります。 + +## 証拠に基づく適応型相談と活動記録 + +`adaptive` は初回相談に加え、同じ作業ディレクトリの同じ検証が失敗し、その後のファイル編集が成功し、新たな検証実行も失敗した場合に相談します。成功した検証は失敗状態を解除します。編集前に開始した検証は修正後の再試行に数えません。初回と修正失敗は別々に重複を防ぎ、自動相談には安定した会話 ID が必要です。対応するツール呼び出しと結果、および明示的な終了コードが必要です。複雑なスクリプト、バッチ、shell による編集、曖昧な出力では発火しません。 + +`ocx advisor set --policy adaptive --max-consultations 2` で設定します。手動と自動の試行はタスクごとに1〜3回の予算を共有し、送信前に `OPENCODEX_HOME/advisor-budget.sqlite` へ予約します。プロキシ再起動や Worker 変更でリセットされません。タスクのダイジェスト、回数、有効期限のみ保存し、最初の試行から24時間で期限切れになります(最大512タスク)。予算が使えない場合も Worker は継続します。送信済みの失敗・キャンセルには費用が発生する可能性があるため返却しません。会話 ID がなければリクエスト単位の手動相談のみです。 + +Advisor ページと `ocx advisor activity --json`(`GET /api/advisor/activity`)は、このプロセスの24時間以内の最新100件のメタデータを表示します。画面が表示されている間は5秒ごとに更新します。理由、状態、モデル、所要時間、報告された入出力トークン、要求値とアダプターの推論変換値を表示します。本文、コマンド、助言、会話 ID は保存せず、再起動時に消えます。変換値は送信パラメーターの証拠であり、モデル内部の推論を保証しません。未報告は不明のままとし、採用状況や正確な費用は推測しません。 diff --git a/docs-site/src/content/docs/ja/reference/configuration/providers.md b/docs-site/src/content/docs/ja/reference/configuration/providers.md index 23e2efd7611..fc82f872333 100644 --- a/docs-site/src/content/docs/ja/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ja/reference/configuration/providers.md @@ -11,6 +11,16 @@ description: プロバイダー エントリ、認証、エンドポイント、 この推定は既定で無効です。プロバイダーごとに `inferContextWindow: true` で有効にします。未設定の場合、ウィンドウがないモデルは有効な Context cap、次に 128k を使います。 +## GPT-6.1 Terra + +[GPT-6.1 Terra](https://openai.com/index/introducing-gpt-6-1-terra/) は GPT-5.6 Terra の後継として 2026年10月26日に公開されました。`gpt-6.1-terra` は `gpt-5.6-terra` と並行して利用でき、既定モデルやサブエージェントのモデル一覧は変更しません。GitHub Copilot では Responses を使用します。OpenRouter の ID は `openai/gpt-6.1-terra` で、コンテキストは 1,050,000、100万トークンあたり入力 $1.00、出力 $5.00 です。 + +ネイティブ Codex:既定コンテキスト 500,000、長文コンテキスト上限 872,000、既定 effort は `low`。段階は `low`、`medium`、`high`、`xhigh`、`max`、`ultra` です。ネイティブの `ultra` は通信時に `max` として送信します。ネイティブ行の高速モードは Fast のみです。 + +[OpenAI API](https://developers.openai.com/api/docs/models/gpt-6.1-terra):コンテキスト 1,050,000、最大出力 128,000。入力はテキストと画像、出力はテキスト。知識カットオフは 2026-06-01 です。effort は `none`、`low`、`medium`(既定)、`high`、`xhigh`、`max`。エンドポイントは `v1/responses` と `v1/chat/completions` ですが、ツール呼び出しには Responses が必要です。 + +100万トークンあたり入力 $1.00、キャッシュ入力 $0.05、キャッシュ書き込み $1.25(30分)、出力 $5.00。入力が 272,000 トークンを超える場合、リクエスト全体の入力・キャッシュは 2×、出力は 1.5× となり、それぞれ $2.00 / $0.10 / $2.50 / $7.50 です。Fast モードは 2× です。 + ## 初回登録時のモデル選択 新しい非 OAuth 接続では、信頼できるモデル一覧の取得が完了するまでモデルの公開を保留します。Models タブの重複しないモデル行が20個以上なら、モデルのスイッチをすべて OFF にします。プロバイダー自体は ACTIVE のままです。実際の認証方式が OAuth または ChatGPT ログインなら既定値を維持します。 @@ -53,7 +63,7 @@ GUI で登録または OAuth ログインが完了すると、Models ページ | `codexWsReuseAcrossTurns?` | `boolean` | `false` | 同一アカウントとスレッドで Codex WebSocket をターンをまたいで再利用します。既定はオフ。オンにすると忙しいソケットを最大750ms待ち、モデルやティアの変更では最大2本を残します。 | | `upstreamHostCircuitThreshold?` | `number` | `0` | ネイティブOpenAI forwardのResponses送信とcompact送信で、実証済みの接続前DNS/TCP障害に適用するオプトインのサーキットしきい値です。`0`で無効、`1`〜`20`ではその回数の終端論理リクエストが失敗するとprovider-originを30秒間遮断します。遮断中はアカウント選択やupstream送信の前に`Retry-After`付き`503`を返し、時間経過後はhalf-openリクエストを1件だけ許可します。タイムアウトとHTTP応答は数えず、HTTP応答が1件でもあれば回路を閉じます。 Codex Pool ルーティングでアカウントが固定されていない場合にのみ適用され、`codexAccountMode: "direct"` とアカウント修飾セレクターでは動作しません。 | | `modelCacheTtlMs?` | `number` | `300000` |プロバイダーごとの `/models` キャッシュの鮮度ウィンドウ。 | -| `cacheRetention?` | `"none" \| "short" \| "long"` | `"short"` | Anthropic プロンプト キャッシュ ポリシー: 無効、5 分間の一時的、または 1 時間の延長。 | +| `cacheRetention?` | `"none" \| "short" \| "long"` | Anthropic ファーストパーティ: `"long"`、その他の Anthropic 互換エンドポイント: `"short"` | Anthropic プロンプト キャッシュ ポリシー: 無効、5 分間の一時的、または 1 時間の延長。未設定の場合、`api.anthropic.com`(API キーまたは OAuth アカウント プール)へのリクエストは 1 時間の TTL を使い、互換ゲートウェイやベンダーのエンドポイントは 5 分の TTL のままです。明示した値が常に優先されます。 | | `tokenGuardian?` | `OcxTokenGuardianConfig` |オフ |オプションのプロアクティブな OAuth 更新および Codex アカウントのウォームアップ ポリシー。 | 各戦略で使う実効しきい値は、アカウントの `codexAccountAutoSwitchThresholds` があればその値、なければグローバルの `autoSwitchThreshold` です。0 は使用量による先行切り替えだけを無効にし、起動時のバインド、ハードロック、クールダウン、モデル利用資格の確認、障害復旧は引き続き適用されます。 @@ -89,7 +99,9 @@ account を削除しても mapping は保持され、同じ id を再追加す | --- | --- | --- | | `adapter` | `string` | `openai-chat`、`openai-responses`、`anthropic`、`google`、`kiro`、`cursor`、`ollama-native`、`azure-openai` (または別名 `azure`) のいずれか。 | | `baseUrl` | `string` |アップストリーム API のベース URL。ほとんどの組み込み固定エンドポイントは不一致を無視します。衝突安全キー プリセットは、古い同じ名前のカスタム宛先を保持します。 | -| `requestPacing?` | `{ enabled, requestsPerMinute?, minIntervalMs?, maxConcurrentRequests?, models? }` | 上流の使用量、請求、レート制限表示とは別の、クライアント側の送信開始間隔調整です。`maxConcurrentRequests` は実行中リクエスト数を制限する正の整数で、プロバイダー規則またはモデル規則だけに設定できます。プロバイダー制限は全モデルに適用され、`models` は上流の正確なモデル ID に一致し、遅延を増やすか同時実行数を減らします。キュー待機は応答ヘッダーのタイムアウトを消費しません。HTTP と明示的なアダプターの `fetchResponse`/`runTurn` 送信を対象にします。同時実行数の上限がある場合、標準の Responses WebSocket ターンは応答本文の完了・エラー・キャンセル時に枠を解放できるよう HTTP/SSE を使います。 Cursor を含む `runTurn` アダプターでは、上限は物理送信数ではなく進行中のターン数を数えます。同じターンの RunSSE と BidiAppend は重なり得ますが、別のターンは待機します。後続送信にも開始間隔は適用されます。 | +| `requestPacing?` | `{ enabled, requestsPerMinute?, minIntervalMs?, maxConcurrentRequests?, models?, keySelection?, keys? }` | 上流の使用量、請求、レート制限表示とは別の、クライアント側の送信開始間隔調整です。`maxConcurrentRequests` は実行中リクエスト数を制限する正の整数で、プロバイダー規則またはモデル規則だけに設定できます。プロバイダー制限は全モデルに適用され、`models` は上流の正確なモデル ID に一致し、遅延を増やすか同時実行数を減らします。キュー待機は応答ヘッダーのタイムアウトを消費しません。HTTP と明示的なアダプターの `fetchResponse`/`runTurn` 送信を対象にします。同時実行数の上限がある場合、標準の Responses WebSocket ターンは応答本文の完了・エラー・キャンセル時に枠を解放できるよう HTTP/SSE を使います。 Cursor を含む `runTurn` アダプターでは、上限は物理送信数ではなく進行中のターン数を数えます。同じターンの RunSSE と BidiAppend は重なり得ますが、別のターンは待機します。後続送信にも開始間隔は適用されます。 | +| `requestPacing.keySelection?` | `"active"`, `"round-robin"` | 通常リクエストのキー選択です。既定の `active` は保存済みキーと既存の障害時切替を維持します。`round-robin` は保存済み選択を変えず、利用可能なプールのキーを順に割り当てます。トークンや料金ではなく割当回数を分散します。 | +| `requestPacing.keys?` | `{ requestsPerMinute?, minIntervalMs?, maxConcurrentRequests? }` | キー参照ごとの共通 RPM・開始間隔・同時実行上限です。全モデルで共有し、プロバイダーとモデルの上限も同時に適用します。同一参照は枠を共有しますが、同じ上流プロジェクトの別参照は独立クォータを意味しません。TPM と日次上限は管理しません。 | | `responsesPath?` | `string` |キー認証 `openai-responses` リクエストの相対リソース パス。 `/` で始まり、スキーム、クエリ、またはフラグメントが含まれていない必要があります。 | | `chatCompletionsPath?` | `string` | `openai-chat` リクエストの相対リソース パス。 `responsesPath` の対となる設定で、同じ形式ルールが適用されます。1つのアップストリームが Chat Completions と Responses を異なるプレフィックスで提供する場合に必要です。モデルごとの wire override はアダプターのみを変更し `baseUrl` は変更しないため、この設定がないと有効化された Chat リクエストが Responses ベースへ送信されます。同梱例は Z.AI です。 | | `upstreamWebsocket?` | `boolean` | `openai-responses` リクエストで使用するアップストリーム Responses WebSocket トランスポート(既定値は無効)。ファーストパーティの `https://api.openai.com/v1` アップストリームでのみ有効です。カスタムプロバイダーのエンドポイントは常に制限付き HTTP/SSE を使用します。Bun はメッセージ全体を確保する前に受信 WebSocket メッセージのサイズ上限を適用できないためです。正規の ChatGPT `openai` プロバイダーでは、省略すると対象となるターンでアップストリーム WebSocket を使用し、`false` はストリーミングのターンを HTTP/SSE で送信し、`true` は拒否されます。`false` の間はネイティブのターン途中のステアリングとインジェクションを利用できません。このフィールドはクライアント側の `websockets` 設定とは独立しており、エンドポイントと認証情報のどちらも変更しません。HTTP のベース URL は SSE のままとなり、Responses 以外のパスと `openai-chat` リクエストは HTTP を使用します。 | @@ -112,7 +124,7 @@ account を削除しても mapping は保持され、同じ id を再追加す | `modelAutoCompactTokenLimits?` | `Record` | モデルごとの正の安全な整数によるソフト自動圧縮予算。実効値であるコンテキストまたは最大入力の 90% の上限を下げることだけができ、有効なコンテキストウィンドウを解決できない場合は出力されません。canonical `openai` では、キーは provider や account-selector の接頭辞を含まない、サポート対象の正確なネイティブモデル ID でなければなりません。provider PATCH はエントリをマージし、キーを `null` にするとそのキーを削除し、フィールド全体を `null` にするとマップを消去します。これらの `null` tombstone は PATCH 専用です。 | | `defaultMaxOutputTokens?` | `number` |クライアントが `max_output_tokens` を省略した場合の、プロバイダー全体の `openai-chat` フォールバック。 | | `modelMaxOutputTokens?` | `Record` |モデルごとの `openai-chat` フォールバック バジェットがプラスになります。正確な/パターン一致はプロバイダーのデフォルトを上回ります。 | -| `modelCosts?` | `Record` | モデルごとの表示価格(100万トークンあたりの米ドル)。そのプロバイダーの正確なアップストリーム モデル ID をキーにします(プロバイダー識別子やルーティングされた `provider/model` ラベルではありません)。値は `input`, `output`, `cacheRead`, `cacheWrite` の 4 フィールドです(例: `{ "deepseek-v4-flash": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 } }`)。組み込みカタログにないモデル ID も、任意の OpenAI 互換エンドポイントを対象とするカスタムプロバイダーや、ローカル・内部プロバイダーで有効です。ユーザー設定の価格は Logs の `~$` と Usage の見積もりで組み込みカタログより優先されます。過去のエントリも現在のオーバーレイで再計算されるため、価格を編集すると過去の合計が変わることがあります(フォールバック順: ユーザー設定 → jawcode カタログ → expected-price オーバーレイ → モデル別ベンダー価格)。ユーザーが明示的に全レートを 0 にした場合は、既知のゼロ料金として見積もります。自動料金に戻すにはそのモデルの設定を削除してください。カタログの全ゼロ料金は引き続きフォールバックします。各レートは 0 以上の有限数で、最大 1,000,000(100万トークンあたりの米ドル)です。範囲外の行は管理境界で拒否され、読み込み時に破棄されます。表示専用の見積もりであり、ルーティング・アカウント選択・クォータ・請求には影響しません。 | +| `modelCosts?` | `Record` | モデルごとの表示価格(100万トークンあたりの米ドル)。そのプロバイダーの正確なアップストリーム モデル ID をキーにします(プロバイダー識別子やルーティングされた `provider/model` ラベルではありません)。値は `input`, `output`, `cacheRead`, `cacheWrite` の 4 フィールドです(例: `{ "deepseek-v4-flash": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 } }`)。組み込みカタログにないモデル ID も、任意の OpenAI 互換エンドポイントを対象とするカスタムプロバイダーや、ローカル・内部プロバイダーで有効です。ユーザー設定の価格は Logs の `~$` と Usage の見積もりで組み込みカタログより優先されます。過去のエントリも現在のオーバーレイで再計算されるため、価格を編集すると過去の合計が変わることがあります(フォールバック順: ユーザー設定 → jawcode カタログ → expected-price オーバーレイ → モデル別ベンダー価格)。ユーザーが明示的に全レートを 0 にした場合は、既知のゼロ料金として見積もります。自動料金に戻すにはそのモデルの設定を削除してください。カタログの全ゼロ料金は引き続きフォールバックします。各レートは 0 以上の有限数で、最大 1,000,000(100万トークンあたりの米ドル)です。範囲外の行は管理境界で拒否され、読み込み時に破棄されます。表示専用の見積もりであり、ルーティング・アカウント選択・クォータ・請求には影響しません。 | | `headers?` | `Record` |追加の上流ヘッダー。認証、Cookie、API キー ヘッダー、埋め込まれた改行、および無効な名前は拒否されます。 | | `openRouterRouting?` | `OpenRouterProviderRouting` |デフォルトの OpenRouter `order`、`only`、および `allowFallbacks` 設定。 `openai-chat` を持つ正規 OpenRouter に対してのみ有効です。 | | `modelOpenRouterRouting?` | `Record` |プロバイダー全体の OpenRouter 設定を置き換える正確なモデル ID のオーバーライド。 | @@ -124,7 +136,7 @@ account を削除しても mapping は保持され、同じ id を再追加す | `modelReasoningEfforts?` | `Record` |モデルごとのラベル。空のリストは努力制御を非表示にします。 | | `modelSupportsReasoningSummaries?` | `Record` |モデルを `false` に設定して、概要の広告を停止し、概要配信フィールドを削除します。 | | `modelReasoningSummaryDelivery?` | `Record` |モデルごとの応答配信列挙型。既存の配信フィールドを書き換えます。 | -| `modelAdapters?` | `Record` | 混合配線ゲートウェイのモデルごとの `openai-chat` または `openai-responses` 配線オーバーライド。明示的なエントリはレジストリのデフォルトを破ります。DeepSeek のプリセットは `deepseek-v4-flash` のネイティブ Responses を選択でき、GitHub Copilot は モデル (`gpt-5.3-codex`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`) を Responses 専用デフォルトとして宣言します。これらのモデルはエージェント トラフィックで `/chat/completions` を拒否するためです。`gpt-5.4-nano` のようなビルトイン デフォルトのないモデルはここでオプトインできます。単線アップストリーム ピンと正規の ChatGPT 転送はオーバーライドを拒否します。 | +| `modelAdapters?` | `Record` | 混合配線ゲートウェイのモデルごとの `openai-chat` または `openai-responses` 配線オーバーライド。明示的なエントリはレジストリのデフォルトを破ります。DeepSeek のプリセットは `deepseek-v4-flash` のネイティブ Responses を選択でき、GitHub Copilot は モデル (`gpt-5.3-codex`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6.1-sol`, `gpt-6.1-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`) を Responses 専用デフォルトとして宣言します。これらのモデルはエージェント トラフィックで `/chat/completions` を拒否するためです。`gpt-5.4-nano` のようなビルトイン デフォルトのないモデルはここでオプトインできます。単線アップストリーム ピンと正規の ChatGPT 転送はオーバーライドを拒否します。 | | xAI Responses オプトイン(ダッシュボード) | スイッチ | `xai` のみで、`grok-4.5` と `grok-4.6` の `modelAdapters` エントリを原子的に設定または削除します。片方だけの場合は、次のスイッチ操作で両方が正規化されるまで混合状態を表示します。他のオーバーライドと tier 動作は変わりません。 Grok 4.7 は OAuth でレジストリの既定値により Responses を使用し、明示的な `modelAdapters["grok-4.7"] = "openai-chat"` で Chat に切り替えられます。 | | `xaiResponsesXSearch?` | `boolean` | デフォルトでは無効です。xAI Responses の宛先では、最終的なリクエスト正規化後もライブの `web_search` ツールが残っている場合にのみ、プロバイダーがホストする `x_search` 宣言を追加します。既存の宣言は重複させず、呼び出し元の `tool_choice` / `allowed_tools` セレクターの範囲を拡張することもありません。また、これは `search.xSearch` オプションを持つウェブ検索サイドカーとは別です。 | | `modelPreferHostedTools?` | `Record` | hosted tool namespace を予約する非 forward Responses gateway 向けの完全一致モデル opt-in。現在は `["image_generation"]` のみを受け付けます。一致したモデルは `openai-responses` wire を使い、その hosted tool をサポートする必要があります。競合するクライアント `image_gen` 宣言を除去し、呼び出し元の tool choice を維持するため selector も書き換えます。OpenAI API の仮想 `-pro` モデルでは、まず選択した公開 ID に一致させ、解決後のベース wire-model ID をフォールバックとして使用します。`modelAdapters` は公開 ID、次にベース ID の順に解決し、後者の結果が最終 wire を決めます。未設定のモデルは通常の alias 動作を維持します。 | @@ -138,12 +150,13 @@ account を削除しても mapping は保持され、同じ id を再追加す | `noStopModels?` | `string[]` |発信者指定の`stop`を拒否するモデル。`openai-chat`アダプター、Chatパススルー、Responsesパススルーはこれらのモデルに対してこのフィールドを送りません。組み込みの`xai`プリセットは、xAIがこれを拒否すると文書化している推論モデル(`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`)をここに登録します。`grok-4.20-0309-non-reasoning`, `grok-composer-2.5-fast`は呼び出し元の`stop`をそのまま受け取ります。 | | `noPenaltyModels?` | `string[]` |存在/周波数ペナルティを拒否するモデル。 組み込みの`xai`プリセットは、xAIがこれらを拒否すると文書化している推論モデル(`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`)をここに登録します。非推論モデルは呼び出し元のペナルティをそのまま受け取ります。 | | `noStructuredOutputModels?` | `string[]` | `openai-chat` エンドポイントが `response_format` を拒否する正確なモデル ID。要求モデルが項目と完全一致する場合だけフィールドを省略し、その他の `openai-chat` モデルでは structured-output 変換を維持します。 | -| `noJsonSchemaModels?` | `string[]` | `openai-chat` エンドポイントが `json_schema` 形式は拒否しつつ `json_object` は受け入れる正確なモデル ID。この要求はフィールドを削除せず `json_object` に降格して送るため、JSON を求めた呼び出し側は散文ではなく JSON を受け取れます。両方の一覧に載るモデルでは `noStructuredOutputModels` が優先します。`opencode go` / `opencode zen` / `opencode free` プリセットが DeepSeek 経路に既定で載せます。 | +| `noJsonSchemaModels?` | `string[]` | `openai-chat` エンドポイントが `json_schema` 形式は拒否しつつ `json_object` は受け入れる正確なモデル ID。この要求はフィールドを削除せず `json_object` に降格して送るため、JSON を求めた呼び出し側は散文ではなく JSON を受け取れます。両方の一覧に載るモデルでは `noStructuredOutputModels` が優先します。`opencode go` / `opencode zen` プリセットが DeepSeek 経路に既定で載せます。 | | `foldDeveloperRoleToSystem?` | `boolean` | `openai-chat` の宛先が `developer` ロールを受け付けるかを記録します。`foldDeveloperRoleToSystem` が未設定なら `system`、`true` なら `system`、`false` なら `developer` として送ります。未設定はこの宛先について何も記録されていないことを意味し、`true` は上流がロールを拒否する記録、`false` は受け付ける記録です。いずれの場合もメッセージは会話内の位置を保ち、変わるのはロールだけです。ロールを拒否する宛先は `400 role 'developer' is not allowed` を返してターンが始まらないため、未記録の既定は畳む側にしてあります。 | | `parallelToolCalls?` | `boolean` |並列ツール呼び出しを切り替えます。 OpenAI Chat はデフォルトでオンになっています。非チャット アダプターは明示的な `true` でのみアドバタイズします。 | | `responsesItemIdRepair?` | `{ message?: string[]; reasoning?: string[]; repairMissingTerminalIds?: boolean; repairInvalidIds?: boolean }` |正確なプレースホルダー ID、欠落している端末 ID、および(`repairInvalidIds` で)正規の `msg_`/`rs_` 接頭辞を欠く message/reasoning ID に対するダウンストリーム SSE 修復はデフォルトで無効になっています。関数呼び出し ID は決して書き換えられません。組み込み DeepSeek は最後の 2 つをデフォルトで有効にします。 | | `responsesSnapshotRepair?` | `boolean` | デフォルトで無効のクライアント向け修復です。SSE と JSON の Responses ライフサイクルで欠落した status、output、ツールメタデータを補完し、raw 検査と永続化は変更しません。 | -| `retryOn429?` | `{ enabled?: boolean; attempts?: number; intervalMs?: number; maxIntervalMs?: number; respectRetryAfter?: boolean }` | API-key プロバイダーのみ(`authMode: "key"`)。オプトインの同一ターゲット 429 リトライ: `retryOn429` が無ければ無効で、オブジェクトがあれば `enabled: false` でない限り有効になります。429 時に待機(上流の `Retry-After` または固定間隔)してから、キー フェイルオーバーの前に同一キーで同一リクエストを再送します — メインのテキストターン回復ループ、Responses passthrough、画像/動画ブリッジ、web-search サイドカー、ターミナル継続要求をすべてカバーします。再送の対象はプリストリームの HTTP 429 応答のみで、カスタム `runTurn` トランスポートは HTTP リトライループの対象外です。`attempts` は最初の 429 以降の同一キー再送回数(合計送信数 = `attempts` + 1)で、メインの回復ループ・ターミナルガード継続・ブリッジ再試行で共有されるリクエスト単位の予算です。`attempts` を使い切っても同一キーでの再送が止まるだけで、通常のキー フェイルオーバーまたは最終エラー処理が利用可能なターゲットに応じて続きます — キー認証の passthrough ワイヤにはフェイルオーバーがないため、使い切った 429 はそのまま返ります。Codex 自体は 429 をリトライしないため、単一キーのプロバイダーでは唯一の防御です。デフォルト: `enabled: true`、`attempts: 3`、`intervalMs: 5000`、`maxIntervalMs: 60000`(1回の待機は `maxIntervalMs` で上限、その上限は 600000)、`respectRetryAfter: true`。 | +| `promptCacheKey?` | `boolean` | `openai-chat` のプロバイダー全体で `prompt_cache_key` の転送を有効にします。アダプターは受け取ったキーを転送し生成しませんが、Claude Messages 変換では `metadata.user_id`、またはメタデータがなければモデル・システム・ツールの組み合わせから導出します。プリセットが指定しない限り既定は無効です。Mistral は[プロンプトキャッシュ契約](https://docs.mistral.ai/studio/conversations/advanced/prompt-caching)に従い有効で、明示的な `false` で無効になります。上流が対応を明記する場合のみ有効にしてください。厳格なゲートウェイは未知のフィールドを HTTP 400 で拒否する場合があります。 | +| `retryOn429?` | `{ enabled?: boolean; attempts?: number; intervalMs?: number; maxIntervalMs?: number; respectRetryAfter?: boolean }` | API キーのプロバイダーのみ(`authMode: "key"`)。未設定なら無効ですが、正規の OpenCode Go と Command Code、および正規の Mistral の `openai-chat` は例外で、再送 6 回、間隔 10 秒、上限 60 秒のポリシーを使います。オブジェクトを指定すると有効になり、`enabled: false` はこの既定ポリシーも無効にします。ストリーム開始前の HTTP 429 で `Retry-After` または固定間隔を待ち、キーの切り替え前に同じキーで同じリクエストを再送します。テキストターンの回復、Responses passthrough、画像・動画ブリッジ、web-search サイドカー、ターミナル継続が対象で、独自の `runTurn` は対象外です。`attempts` は最初の 429 後の同一キー再送回数(総送信数 = `attempts` + 1)で、リクエスト全体で共有します。使い切ると同一キーの再送だけが止まり、通常のキー切り替えか最終エラー処理に進みます。キー認証の passthrough では切り替えがなく、そのまま 429 を返します。正規の Mistral Chat で明示的な上流の待機時間が許容上限を超えると、同一キー再送を止めて元の `Retry-After` を返しますが、別のプールキーが処理できる場合があります。他のプロバイダーでは待機時間を上限で切り詰めます。Mistral は認証情報とモデルごとに期限を共有します。`requestPacing.enabled: false` または `retryOn429.respectRetryAfter: false` で無効になります。再試行時刻のない 429 で分単位の予算不足が検証されると、控えめな 60 秒の回復待機後、プローブを 1 回実行し、成功すれば通常のトラフィックを再開します。正常なトラフィックから次のトークン消費は予測しません。公表された許容量を使い、既知の月間枠の枯渇は除外し、正確なリセット時刻を保証せず、履歴の圧縮も起動しません。Codex 自体は 429 を再試行しません。オブジェクトの既定値:`enabled: true`、`attempts: 3`、`intervalMs: 5000`、`maxIntervalMs: 60000`(待機の上限、設定の最大値は 600000)、`respectRetryAfter: true`。 | | `transientRetryOn5xx?` | `{ enabled?: boolean; attempts?: number }` | キー認証の `openai-chat` および `openai-responses` プロバイダーのみ。`authMode: "forward"` のプロバイダー(ChatGPT アカウントプール)はこのオプションを読まず、既定の再試行段数を維持します。ストリーム開始前に上流から返される一時的なステータス(500、502、503、504、520、521、522)に対するオプトインの再試行です。設定がなければ無効で、オブジェクトを指定すると `enabled: false` でない限り有効になります。最初の Responses リクエスト、ターミナルガード継続、ネイティブの `/v1/chat/completions`、および 429/アカウント回復時の再取得が対象です。`attempts` は最初の送信を含め、1 回のリクエストで許可される上流への送信総数です(1~10、デフォルトは 3)。接続リセット回復と共有するリクエスト単位の単一予算であるため、`3` を指定した場合、プロバイダーに到達する実リクエストは最大 3 回です。待機には 400 ms を基準とする固定式の指数バックオフを使用し、上限は 5 秒で、`Retry-After` に従います。レート制限を扱う `retryOn429` とは別の機能であり、ストリーム開始後の失敗は再送されません。 各 Combo ターゲットでは、事前予約した初回送信もそのターゲットの設定総数に含まれます。`attempts: 1` は 1 回だけ送信し、予約を返却して余裕を作ったり、共有上限を広げたりしません。送信前のローカル拒否やキャンセルでは未使用の予約を解放します。 | | `retryOnReset?` | `{ enabled?: boolean; replacements?: number }` | ネイティブ `openai-responses` プロバイダー(`authMode: "forward"` を含む)と、上流ヘッダー受信前の汎用 Responses 変換経路に適用されます。呼び出し側が何も観測しないまま失敗した送信を、オプトインで置き換えます。設定がなければ無効で、オブジェクトを指定すると `enabled: false` でない限り有効になります。レスポンスヘッダーが届く前に接続が切れた場合と、ヘッダー後に SSE 本文が制御イベントだけを運んだまま切れた場合の両方が対象です。canonical ChatGPT upstream WebSocket で create フレームの送信後、Responses イベントが届く前にソケットが閉じたかエラーになった場合も同じく対象で、その置き換えは HTTP で送信します。置き換えるのは自己完結したリクエストだけで、`store: false`、完全な `input`、`previous_response_id` / `conversation` / `stream_id` がないこと、クライアントが実行するツールのみ、が条件です。`replacements` は、すべてのレッグとすべてのコンボ子リクエストを合わせて 1 つの論理リクエストが行える置き換え送信の回数です(1..2、デフォルトは 1)。レッグ単位の再試行回数でも送信予算でもないため、置き換え送信もそのレッグがすでに持つ送信許容量に収まる必要があります。すでに出力やツール呼び出しを送ったリクエストは、この値に関わらず置き換えません。元の送信がすでに開始されていた場合は置き換えた推論も課金される可能性があるため、既定では無効です。 汎用変換経路では、初回送信と再構築後の送信のどちらも、同じ許可と送信予算でヘッダー受信前の接続リセットを置き換えます。アダプター独自のトランスポートと、上流ヘッダー受信後の変換ストリーム障害は対象外です。 | | `autoToolChoiceOnlyModels?` | `string[]` | `tool_choice` が `auto` または `none` のみを受け入れるモデル。強制的な選択は格下げされます。 | @@ -157,8 +170,8 @@ account を削除しても mapping は保持され、同じ id を再追加す | `anthropicEofTolerance?` | `boolean` | `message_stop` 前にストリームが終了しても、可視テキストまたは完全な JSON オブジェクトのツール入力が受信済みの場合に限り完了を許可します(Anthropic 互換ゲートウェイ向け)。デフォルトはオフ。 | | `googleMode?` | `"ai-studio" \| "vertex" \| "cloud-code-assist"` | Google トランスポート/認証モード。デフォルトは`ai-studio`です。 | | `googleToolSchemaPolicy?` | `"compatible" \| "reject-lossy"` | Google 専用です。省略時または `compatible` は互換スキーマと既存の非直接 400 修復を維持します。`reject-lossy` は初期損失または判定不能な上限付き比較を送信前に拒否し、制約を開く Vertex/Cloud Code Assist 修復を保留します。直接 AI Studio はこの修復を行いません。 | -| `project?` | `string` | Vertex または Antigravity Cloud Code Assist プロジェクト ID。 | -| `location?` | `string` |頂点の位置。環境フォールバックは `GOOGLE_CLOUD_LOCATION` です。 | +| `project?` | `string` | Vertex または Antigravity Cloud Code Assist プロジェクト ID。Vertex の API キーは、これが設定されているとプロジェクトエンドポイントを使い、未設定なら `GOOGLE_CLOUD_PROJECT` があっても express モードを使います。ADC は `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT` にフォールバックします。`"auto"` を指定すると、Vertex の各 API キーが自身のプロジェクトを解決します(解決に成功した結果はキーごとにキャッシュし、解決に失敗したキーは後続のリクエストで再度プローブします)。1 つのキープールで複数プロジェクトを扱えます。ADC は `"auto"` を拒否します。 | +| `location?` | `string` | Vertex のロケーション。API キーのプロジェクトエンドポイントは既定で `global` を使い、`GOOGLE_CLOUD_LOCATION` を無視します。ADC にはこの値か `GOOGLE_CLOUD_LOCATION` フォールバックが必要です。 | | `mcpServers?` | `Record` |カーソルのみ: 標準入出力またはストリーミング可能な HTTP MCP サーバー。 | | `desktopExecutor?` | `DesktopExecutorConfig` |カーソルのみ: 外部コンピュータ使用および画面録画コマンド。 | | `unsafeAllowNativeLocalExec?` | `boolean` |カーソルのレガシー ブール値。新しいフィールドが設定されていない場合のみ、`nativeLocalExec: "on"` と同等です。 | @@ -562,3 +575,8 @@ Vercel AI Gateway は、1 つのモデルを複数の基盤となる推論プロ ### `anthropicAccountPool.routes` `anthropicAccountPool.routes` は Anthropic OAuth の保存済みアカウント ID をモデルに割り当てます。有効なプールでは、大文字小文字を区別する `match` グロブの最初の一致が初回選択と 429 再試行を制限します。`fallback: true` はルート内に適格なアカウントがない場合だけ通常のプールを使います。 + + +### `promptLengthPricing` + +省略可能な `promptLengthPricing` は4つの基本料金に追加します。`mode: "automatic"`(または省略)はカタログのコンテキスト料金規則を維持し、`mode: "flat"` はその規則を無効にします。`mode: "custom"` では、`thresholdTokens` に正の安全な整数、`comparison` に `"gt"`(>)または `"gte"`(≥)、`rates` に4つの絶対額の代替料金を指定します。判定にはキャッシュの読み書きを含む全プロンプトトークンを使い、出力は含めません。選択された料金は出力を含むリクエスト全体に適用され、しきい値の上下どちらでもカタログの規則を置き換えます。Fast/Priority 調整は料金の選択後に1回だけ適用され、そのモデルについてカタログが公開している Priority との関係に従います。推定値が下限として表示されるのは、カタログがその関係を `lower-bound` と宣言している場合(現在は xAI Grok)だけです。カタログに規則がないモデルでは、Priority 倍率は1回だけ適用されます。**Models → Price** で設定できます。**Reset to automatic** は基本料金と規則を含む上書き全体を削除します。既存設定との互換性は維持され、表示される推定料金だけに関する設定です。 diff --git a/docs-site/src/content/docs/ja/reference/proxy-formats.md b/docs-site/src/content/docs/ja/reference/proxy-formats.md index aa05232953d..50ddb2fe2ca 100644 --- a/docs-site/src/content/docs/ja/reference/proxy-formats.md +++ b/docs-site/src/content/docs/ja/reference/proxy-formats.md @@ -35,6 +35,8 @@ Chat Completions と Messages の各エンドポイントは遅延ツールカ これとは別に、すべての受信ワイヤで、プロバイダーの `default.` 名前空間の呼び出しはリクエストが宣言した素のツール名に正規化されます(`default.Read` → `Read`)。 +ネイティブ Messages では、`tools: []` と併用されたシステムメッセージ内のインラインツール定義も認識します。その名前は恒久的に削除されるまで呼び出せますが、無関係なクライアントツールは許可されません。ホスト型 MCP 呼び出しとサーバーツールの引数ストリームはネイティブ形式を維持します。 + ## エンドポイントの概要 |クライアントサーフェス |エンドポイント |非ストリームの結果が成功 |成功したストリームまたはソケットの結果 | diff --git a/docs-site/src/content/docs/ko/contributing.md b/docs-site/src/content/docs/ko/contributing.md index 3508277d71b..cb8ba43379e 100644 --- a/docs-site/src/content/docs/ko/contributing.md +++ b/docs-site/src/content/docs/ko/contributing.md @@ -173,6 +173,10 @@ wire format을 쓴다는 이유만으로 다른 프로바이더 또는 목적지 `passthrough`가 아닌 주장에는 구체적인 제한을 적고, fixture 기반 주장에는 이를 입증하는 정확한 assertion id를 명시하세요. 비밀정보가 없는 request vector를 `tests/fixtures/compatibility/`에 추가하고 production adapter를 대상으로 실행하는 집중 테스트를 작성합니다. +`passthrough` claim은 manifest test에서 그 feature에 묶인 selector 위의 `equals` assertion 가운데, +기대값이 같은 위치의 호출자 값 그대로인 것만 인용할 수 있습니다. 그래서 제거되거나 바뀌었거나 관계없는 +필드를 무손실로 표시할 수 없습니다. custom tool과 그 grammar처럼 한쪽만 보존될 수 있는 측면은 별도 +claim으로 나누세요. 호환성 매니페스트는 수동적인 데이터입니다. 일반 router, Responses handler, server startup path가 manifest catalog를 import하거나 Compatibility Lab을 활성화해서는 안 됩니다. diff --git a/docs-site/src/content/docs/ko/getting-started/installation.md b/docs-site/src/content/docs/ko/getting-started/installation.md index df36ad351a9..4e2ec7dbdbe 100644 --- a/docs-site/src/content/docs/ko/getting-started/installation.md +++ b/docs-site/src/content/docs/ko/getting-started/installation.md @@ -26,8 +26,8 @@ npm install -g @bitkyc08/opencodex install-scripts ... blocked because they are not covered by allowScripts`). 이 경우 번들 Bun 런타임이 준비되지 않을 수 있습니다. 런처는 허용된 번들 복구를 시도한 다음, 절대 경로 PATH 디렉터리의 실행 가능한 Bun을 검증합니다. 정식 버전의 -major가 패키지 pin과 같고 minor가 pin 이상이면 사용할 수 있습니다. 현재 의존성 -1.4.2에서는 1.4.0 이상, 2.0.0 미만입니다. 제한 시간 안에 버전 정책과 identity를 확인하며, +major가 패키지 pin과 같고 버전이 pin 이상이면 사용할 수 있습니다. 현재 의존성 +1.4.4에서는 1.4.4 이상, 2.0.0 미만입니다. 제한 시간 안에 버전 정책과 identity를 확인하며, `bun --version` 출력이 `bun -e`로 확인한 `Bun.version`과 같아야 합니다. POSIX에서는 런처가 상위 디렉터리와 심볼릭 링크를 따라 도달하는 구성 요소를 포함한 후보 경로 전체를 검사합니다. 검사하는 각 디렉터리와 심볼릭 링크, 최종 실행 파일은 root 또는 런처의 유효 사용자 소유여야 합니다. diff --git a/docs-site/src/content/docs/ko/getting-started/quickstart.md b/docs-site/src/content/docs/ko/getting-started/quickstart.md index 2c06b538f7f..77cc5a35d1e 100644 --- a/docs-site/src/content/docs/ko/getting-started/quickstart.md +++ b/docs-site/src/content/docs/ko/getting-started/quickstart.md @@ -18,7 +18,7 @@ ocx init `ocx init`은 다음 과정을 안내합니다: -1. **프로바이더 선택** — 내장 레지스트리 프리셋 104개 중 하나를 고르거나 `custom`을 선택해 base URL과 adapter를 직접 입력합니다. +1. **프로바이더 선택** — 내장 레지스트리 프리셋 106개 중 하나를 고르거나 `custom`을 선택해 base URL과 adapter를 직접 입력합니다. 2. **API 키** — 키를 붙여넣거나 `${ANTHROPIC_API_KEY}` 같은 환경 변수를 참조합니다. 3. **기본 모델** — 키, 로컬, custom 프로바이더에서는 프리셋을 그대로 쓰거나 모델 ID를 직접 입력합니다. 4. **프록시 포트** — 기본값은 `10100`입니다. diff --git a/docs-site/src/content/docs/ko/guides/claude-code.md b/docs-site/src/content/docs/ko/guides/claude-code.md index f4a6f3354ec..14ebff76cba 100644 --- a/docs-site/src/content/docs/ko/guides/claude-code.md +++ b/docs-site/src/content/docs/ko/guides/claude-code.md @@ -192,21 +192,21 @@ unknown은 설정이 아직 opencodex 프록시를 가리키는지 판단할 수 ### Picker 모드: 1P Code 탭에 opencodex 모델 표시하기 -Picker 모드는 1P 모드의 일부예요. macOS에서 1P를 선택하면 기본으로 켜지지만, +Picker 모드는 1P 모드의 일부예요. macOS와 Windows에서 1P를 선택하면 기본으로 켜지지만, `claudeCode.intercept.picker: false`를 설정하면 꺼져요. 1P Desktop의 Code 탭 모델 선택기를 바꿔서 -사용 가능한 opencodex 모델을 이름으로 보여줘요. 처음 켤 때 macOS 로그인 키체인에서 로컬 인증 기관을 +사용 가능한 opencodex 모델을 이름으로 보여줘요. 처음 켤 때 macOS 로그인 키체인이나 Windows 현재 사용자 Root 저장소에서 로컬 인증 기관을 신뢰하라는 메시지가 표시될 수 있어요. 이 인증 기관은 `claude.ai`와 그 하위 도메인으로 제한돼요. 내보낼 수 있는 서명용 인증서와 키는 OS 자격 증명 저장소로 보호하며, 일반적인 재시작에서는 같은 것을 재사용해요. OpenCodex 설정 디렉터리에 Picker 서명 키를 평문으로 저장하지 않아요. 제한된 CA의 전체 검증과 OS 신뢰 확인은 -계속 적용돼요. 승인한 인증서와 키가 같고 자격 증명 저장소를 사용할 수 있다면 재시작 시 인증서 신뢰 설정을 -추가하거나 삭제하지 않아요. 시작 시 복원은 신뢰를 설치하지 않아요. 신뢰가 없거나 철회됐거나 확인할 수 없다면 +계속 적용돼요(Windows에서는 정확한 지문과 확인된 리프로 검증해요). 승인한 인증서와 키가 같고 자격 증명 저장소를 사용할 수 있다면 재시작 시 macOS 인증서 신뢰 설정을 +추가하거나 삭제하지 않아요. 시작 시 복원이나 상태 확인은 신뢰를 설치하지 않아요. 신뢰가 없거나 철회됐거나 확인할 수 없다면 Picker는 대기 상태로 남아요. `ocx claude desktop picker on` 또는 `ocx claude desktop picker trust`를 직접 실행해 -신뢰를 부여하면 돼요. +신뢰를 부여하면 돼요(Windows에서는 확인 창이 나올 수 있어요). 이전 인증서에서 한 번 마이그레이션할 때는 기존 신뢰를 제거하기 위한 동의가 필요할 수 있어요. 정리가 끝나지 않으면 Picker는 사용할 수 없고 적용된 프로필은 복호화하지 않는 중계로 동작해요. 키체인 잠금 해제나 앱의 -자격 증명 접근 허용은 별도의 macOS 동작이며, 재시작이나 업데이트 때도 메시지가 나올 수 있어요. -Windows와 Linux에서는 Picker를 지원하지 않으며 Picker CA, 자격 증명 저장소, 프록시 작업을 시작하지 않아요. +자격 증명 접근 허용은 별도의 macOS 동작이며, 재시작이나 업데이트 때도 메시지가 나올 수 있어요. Windows에서는 `on`이나 `trust`를 직접 실행할 때 루트 추가 확인이 나올 수 있어요. +Linux에서는 Picker를 지원하지 않으며 Picker CA, 자격 증명 저장소, 프록시 작업을 시작하지 않아요. 기본 Claude 인터셉트는 계속 사용할 수 있고, 로컬 CA 파일에 소유자, 심볼릭 링크, 파일 권한, Windows ACL 검사를 적용해요. Picker 모드가 켜져 있는 동안 Claude Desktop의 네트워크는 OpenCodex를 거쳐요. OpenCodex가 중단되면 @@ -575,7 +575,7 @@ Messages → Responses로 변환되는 요청에서는 Claude Code의 `/effort` | 사용자 `tool_result` | `function_call_output`(`is_error` → `[tool error]` 접두사) | | 도구 결과의 `tool_reference` | 짝지어진 결과에 `Tool loaded: ` 텍스트로 보존합니다. 도구 선언을 추가하거나 번역 경로의 서버 측 지연 로딩을 활성화하지는 않습니다. | | `thinking` / `redacted_thinking` 재생 | 서명과 비공개 페이로드를 제한된 `ocxr1` 봉투에 담은 `reasoning` 항목 | -| Function 도구 | `{type: "function"}`(`web_search*` → `{type: "web_search"}`) | +| Function 도구 | `{type: "function"}`(`web_search*` → `{type: "web_search"}`). `api.anthropic.com`으로 번역하여 보내는 경로에서는 타입이 지정된 `web_search_*`와 `web_fetch_*` 선언도 Anthropic에 전달되어 서버에서 실행됩니다. 웹 검색 사이드카가 `web_search`를 제공하면 사이드카가 우선합니다. 검색과 가져오기 결과는 `server_tool_use` 및 대응하는 `web_search_tool_result` / `web_fetch_tool_result` 블록으로 클라이언트에 반환됩니다 | | `tool_choice` | `auto`→`auto`, `none`→`none`, `any`→`required`, 이름 지정 함수→`{type:"function",name}`, 호스팅 WebSearch/web_search→`{type:"web_search"}` | | `max_tokens` | `max_output_tokens` | | `stop_sequences` | `stop` | diff --git a/docs-site/src/content/docs/ko/guides/codex-app-models.md b/docs-site/src/content/docs/ko/guides/codex-app-models.md index 22a2035c24f..40c716687da 100644 --- a/docs-site/src/content/docs/ko/guides/codex-app-models.md +++ b/docs-site/src/content/docs/ko/guides/codex-app-models.md @@ -104,11 +104,12 @@ GPT-5.6에만 사용합니다. 오래된 템플릿으로 근사하지 않고 모 | 경로 | 선택기 id와 카탈로그 메타데이터 | | --- | --- | -| Codex 로그인(계정 한정 선택기 행 비활성) | `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna` 같은 bare native id를 표시하고 `codexAccountMode`에 따라 Pool 또는 Direct를 사용합니다. GPT-5.6 행의 카탈로그 창은 922,000토큰입니다. | +| Codex 로그인(계정 한정 선택기 행 비활성) | `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna` 같은 bare native id를 표시하고 `codexAccountMode`에 따라 Pool 또는 Direct를 사용합니다. GPT-5.6 행의 카탈로그 창은 922,000토큰입니다. `gpt-6.1-terra`: 기본 500,000 / 장문 872,000. | | Codex 로그인(계정 한정 선택기 행 활성, 유효한 selector 있음) | 유효한 selector와 지원되는 native model의 각 조합마다 `/` 행을 표시합니다. 각 행은 매핑된 계정만 사용하며 bare native 행은 선택기에서 숨깁니다. Native metadata와 context window는 보존됩니다. | | Codex 로그인(명시적 Daybreak forward 행) | canonical `openai` provider에 정확한 `customModels` 항목이 있을 때만 `openai/gpt-daybreak-blue-latest`를 표시합니다. Daybreak wire id를 유지하고 고정된 Sol capability snapshot(컨텍스트 922,000; 자동 압축점 922,000)을 사용합니다. | -| OpenAI(API key) | 정확히 열 개의 네임스페이스 행: `gpt-5.5`, `gpt-5.6`, Sol/Terra/Luna, 세 개의 `*-pro` 가상 id, 두 Daybreak 별칭 (모두 컨텍스트 922,000; 최대 입력 922,000) | +| OpenAI(API key) | 네임스페이스 모델에는 `gpt-5.5`, `gpt-5.6`, Sol/Terra/Luna, `*-pro` 가상 id, Daybreak 별칭이 포함됩니다(컨텍스트 1,050,000; 최대 입력 922,000). `openai-apikey/gpt-6.1-terra`는 컨텍스트 1,050,000, 최대 출력 128,000입니다. | | OpenRouter | `openrouter/openai/gpt-5.6-sol`, `openrouter/openai/gpt-5.6-terra`, `openrouter/openai/gpt-5.6-luna` (922,000) | +| OpenRouter | `openrouter/openai/gpt-6.1-terra` (1,050,000) | | Cursor | 정적 폴백에는 `cursor/gpt-5.6-sol`, `cursor/gpt-5.6-terra`, `cursor/gpt-5.6-luna` (1,000,000)와 Grok 4.5/4.6/4.7의 일반·Fast 항목(500,000)이 들어갑니다. 4.6과 4.7은 `xhigh`도 노출하며, 실시간 계정 탐색이 어떤 항목을 계속 보일지 정합니다. | | xAI | 실시간 탐색이 기준입니다. 폴백 카탈로그에는 `xai/grok-4.6`과 `xai/grok-4.7`이 포함되며 기본값은 `xai/grok-4.5`입니다. 세 모델 모두 컨텍스트 창은 500,000입니다. Grok 4.6과 4.7은 `low` / `medium` / `high` / `xhigh`(업스트림 기본값: `high`)를 제공하고, Grok 4.5는 `high`까지만 제공합니다. | @@ -172,6 +173,46 @@ fast_mode = true `service_tier`가 제거됩니다(레지스트리가 정식 OpenAI를 `true`, DeepSeek과 Volcengine Ark를 `false`로 분류). 미분류 커스텀 게이트웨이는 호출자가 준 값을 그대로 보존하고 주입도 받지 않습니다. 따라서 처리 불가능한 곳에 fast 옵션이 노출되지 않으며, 커스텀 게이트웨이는 `true`로 명시적으로 옵트인할 수 있습니다. +### Codex에서 Flex 선택 + +`service_tier: "flex"` 지원이 확인된 라우팅 프로바이더와 모델에 한해 Flex를 +명시적으로 선언할 수 있습니다. 아래 필드는 **기존** `providers.nanogpt` 설정에 +병합해야 하며 URL과 API 키를 덮어쓰면 안 됩니다. + +```json +{ + "modelSupportsServiceTier": { "openai/gpt-6-luna": true }, + "fastWire": { + "kind": "service-tier", + "canonicalToWire": { "priority": "priority", "flex": "flex" }, + "foreignCallerTiers": "verbatim" + } +} +``` + +적격한 `openai-responses` 라우트에서는 Luna의 Codex 속도 선택기에 Standard, +Fast, Flex가 나타납니다. `openai-chat` 라우트는 Chat Completions API가 +호출자의 서비스 등급을 지원하는 것이 확인된 경우에만 `chatServiceTier: true`도 +설정해야 합니다. `fastWire` 매핑은 **프로바이더 전체에 적용**되므로 같은 +`providers.nanogpt` 항목에서 서비스 등급 사용이 이미 허용된 다른 모델에도 +Flex 선택지가 표시될 수 있습니다. `modelSupportsServiceTier`에 Luna만 적어도 +이미 적격인 다른 모델을 제외하는 허용 목록이 되는 것은 아닙니다. 다만 Flex 선택지는 +카탈로그에 게시되는 **일반 라우팅 모델 행**에만 표시되며, Combo 네임스페이스와 +Native Alias 행에는 표시되지 않습니다. 따라서 해당 항목에서 서비스 등급 사용이 +허용된 **모든 일반 라우팅 모델**의 Flex 지원 여부를 확인하세요. +Luna만 Flex를 지원한다면 Fast 지원 모델이 다른 모델과 섞이지 않도록 +**별도 프로바이더 항목**으로 분리해야 합니다. Fast 지원만으로 Flex 지원을 +추정해서는 안 됩니다. + +OpenCodex의 전역 Fast 설정은 **Auto**(`fastMode` 생략)로 유지하세요. +Fast 또는 Default 강제 모드는 Flex 선택을 덮어씁니다. `ocx sync` 후 Codex의 +모델 목록을 갱신하고 속도 선택기에서 **Flex**를 선택하거나, Codex CLI에서 +`/model --speed=flex`를 사용하면 됩니다. 이는 **선택 가능한 등급**이지 +모델별 강제 기본값은 아닙니다. 다른 모델로 바꾸더라도 Flex 사용이 자동 강제되지는 +않습니다. 상위 API로 `service_tier: "flex"`를 전달하지만, 실제 처리 가능 여부, +요금과 지연 시간은 공급자에 따라 다릅니다. 메뉴나 요청 로그만으로 Flex 과금이 +확정되지는 않습니다. + ### Codex 데스크톱에서 Ultra Fast 선택 `service_tier: "ultrafast"`를 지원하는 프로바이더에는 전송 매핑을 한 번 설정하면 됩니다. @@ -198,11 +239,19 @@ opencodex의 Fast 모드를 **Auto**로 유지하세요(`fastMode` 생략). Fast 목록이 남아 있다면 Codex 데스크톱을 다시 시작한 뒤 라우팅 모델의 속도 메뉴에서 **Ultra Fast**를 선택하세요. 기본 속도는 Standard로 유지됩니다. -사용 가능한 `service-tier` 전송 경로와 명시적인 `ultrafast → ultrafast` 매핑이 있어야 합니다. -일반 Fast 지원, GPT와 비슷한 이름, 네이티브·계정별 항목, combo 별칭만으로는 추가되지 않습니다. -매핑을 제거하거나 모델의 service-tier 지원을 끄면 다음 동기화 성공 시 카탈로그와 캐시에서 -자동 생성된 선택지도 제거됩니다. 별도의 `ultraFastTier` 설정은 기존처럼 사용자가 직접 넣은 -카탈로그 tier를 보존하는 용도입니다. +이 선택지를 라우팅 항목에 자동 생성하려면 사용 가능한 `service-tier` 전송 경로와 명시적인 +`ultrafast → ultrafast` 매핑이 있어야 합니다. 일반 Fast 지원이나 GPT와 비슷한 이름만으로는 +추가되지 않습니다. 이 라우팅 매핑은 네이티브·계정별 네이티브 항목이나 combo 별칭에는 +적용되지 않습니다. 매핑을 제거하거나 모델의 service-tier 지원을 끄면 다음 동기화 성공 시 +카탈로그와 캐시에서 자동 생성된 라우팅 선택지도 제거됩니다. +Codex가 HTTP로 모델 목록을 불러올 때도 현재 적용 중인 설정의 동일한 선언이 Ultra Fast 선택지를 결정합니다. + +라우팅 항목에서는 `ultraFastTier`가 `provider/model` 식별자가 정확히 일치하는 저장된 항목에 사용자가 +별도로 넣은 Ultra Fast tier를 보존합니다. 자동 생성된 라우팅 tier는 사용자 제공 tier로 +취급하지 않습니다. bare native `gpt-6-astra` / `gpt-6.1-sol` 항목과 신뢰된 계정별 복제 +항목에서는 `ultraFastTier`를 켜면 네이티브 기본 tier를 바꾸지 않고 누락된 Ultrafast 선택지도 +생성합니다. 끄면 자동 생성한 것으로 추적되는 네이티브 tier 메타데이터만 제거하며, +사용자가 별도로 넣거나 수정한 네이티브 tier 항목은 보존합니다. [OpenAI API 문서](https://developers.openai.com/api/docs/guides/ultrafast-mode)는 GPT-6 Astra와 GPT-6.1 Sol의 Ultrafast 지원을 안내합니다. API 사용 권한과 diff --git a/docs-site/src/content/docs/ko/guides/codex-integration.md b/docs-site/src/content/docs/ko/guides/codex-integration.md index 33ab38aac98..ad7c94bb7d5 100644 --- a/docs-site/src/content/docs/ko/guides/codex-integration.md +++ b/docs-site/src/content/docs/ko/guides/codex-integration.md @@ -13,6 +13,18 @@ Pool 모드에서는 선택된 저장 계정이 쿨다운 중이고 사용 가 권한과 main 계정 정책 검사는 유지됩니다. 이 fallback은 저장 계정의 쿨다운을 해제하거나 호출자 인증을 Pool 선택으로 저장하지 않습니다. 특정 계정에 정확히 고정된 요청은 그 계정에 계속 묶입니다. +수동 Pool pin은 우선순위 선호이므로 위 fallback을 허용합니다. 특정 계정만 사용해야 하면 +계정이 지정된 모델을 선택하세요. 관측된 native main을 일시 정지하면 일치하는 호출자 자격 증명의 +Pool fallback도 거부하지만, 별도 호출자 로그인을 main으로 간주하지는 않습니다. + +계정 ID·개인/워크스페이스 구독 구분·이메일이 native main과 중복되는 Pool 등록 및 재인증은 +거부됩니다. 이메일이 없으면 다른 구성원이라는 근거로 취급하지 않습니다. 서로 다른 워크스페이스, +이메일로 구분되는 구성원, 개인/워크스페이스 구독은 계속 사용할 수 있습니다. 사용량 조회 실패 시에도 +로그인 토큰의 구독 정보는 유지합니다. 기존 중복 계정은 삭제하지 않고 Pool 선택에서 제외하며 계정 +목록의 `selectionExcludedReason: "duplicate_main"`과 `selectionExcludedAction`으로 이유를 알립니다. native main을 사용하거나 +native Codex를 다른 계정으로 로그인하면 됩니다. 재로그인 감지는 권한을 확보한 다음 native 로그인 +관측에서 반영하며, 수동 목록 조회나 호출자 소유 요청은 이를 위해 native 자격 증명을 읽지 않습니다. + ## 설정 주입 `ocx init`, `ocx start`, `ocx sync`는 모두 인젝터를 호출합니다. 기본 loopback 바인드에서는 Codex의 빌트인 `openai` 프로바이더 id를 그대로 유지한 채, 그 프로바이더가 opencodex를 바라보게 합니다. @@ -207,6 +219,17 @@ Windows에서 Orca shell은 `CODEX_HOME`과 `ORCA_CODEX_HOME`을 Orca의 번들 전용 provider 모드의 `requires_openai_auth = true`는 Codex App/TUI의 계정 게이트 화면을 네이티브 Codex와 같은 조건으로 맞춥니다. opencodex는 `/v1/responses`도 WebSocket으로 제공합니다. 전용 provider는 `"websockets": true`일 때만 `supports_websockets = true`를 광고합니다. loopback에서는 Codex의 빌트인 provider가 먼저 WebSocket을 시도할 수 있으며, 비활성화된 proxy는 `426`을 반환해서 Codex가 HTTP/SSE로 fallback합니다. +**추론 사용량 표시 보존(2.90.0 이상).** 정식 ChatGPT 경로를 HTTP/SSE로 받는 +클라이언트에는, Bun 런타임이 제공하는 상류 WebSocket 연결 응답의 실제 +`x-reasoning-included` 헤더를 전달합니다. 같은 연결을 재사용하는 요청에서도 유지합니다. +이 표시는 서버가 보고한 사용량에 이전 추론량이 이미 포함됐음을 Codex에 알립니다. +첫 Responses 이벤트 전에 받은 메타데이터가 우선하며, 이후 메타데이터는 본문에 남지만 +이미 확정한 응답 헤더를 바꾸지는 못합니다. 없는 표시를 만들어 붙이거나 사용량 숫자를 +수정하지 않습니다. 상류가 표시를 보내지 않는 경우와 Codex 자체의 사용량 상태 관리는 +별도 문제이므로, 이 변경만으로 조기 압축 해결을 보장하지 않습니다. 클라이언트도 +WebSocket으로 연결하면 상류 응답 전에 연결 승인이 끝나므로, 그 승인 헤더에 표시를 +나중에 추가할 수는 없습니다. + 네이티브 ChatGPT forward 요청의 로컬 재생 상태가 만료되었거나 없으면 opencodex는 upstream 요청 전에 `previous_response_not_found`를 반환합니다. Codex WebSocket 클라이언트는 일반 스트림 재시도 한도 안에서 다시 연결하고, 완료된 도구 호출과 결과를 포함한 현재 보유 @@ -367,6 +390,8 @@ ChatGPT 계정을 추가하거나 재인증할 때 OpenCodex는 일반적으로 **Codex Set → Multi-auth**에서 **Codex Auth** 제목 줄의 **Codex 크레딧** 스위치를 켜면 메인 계정과 풀 계정의 최근 크레딧 잔액이 Week 바로 아래에 표시됩니다. 기본값은 꺼짐이며 `showCodexCredits`로 저장됩니다. 잔액은 로케일에 맞는 숫자로 표시하고, 응답에 따라 무제한 또는 초과 사용 한도 도달 안내를 표시합니다. 전체 크레딧 한도가 제공되지 않으므로 막대는 백분율이 아닌 사용 가능 상태를 나타냅니다. 스위치는 표시만 제어하며 새 로그인에는 해당 계정의 조회 결과가 필요합니다. +**Use credits after limit**을 허용한 저장 계정은 포함 사용량 한도가 소진되고 크레딧 관측이 3분 이상 지나면 기존 백그라운드 주기에서 잔액을 다시 조회합니다. **Direct** 모드에서도 `codexAccountNamespaces`로 명시적으로 연결한 서브 계정 등이 대상입니다. Direct와 메인/서브 계정 분리를 유지하며, Pool 라우팅을 켜거나 연결되지 않은 계정을 조회하지 않습니다. 일시 정지·재인증 필요 계정은 제외합니다. 메타데이터 조회는 검증 대기 계정을 검증하거나 모델 요청을 보내지 않습니다. 조회 실패 시 재시도 대기를 유지하며 요청에는 여전히 최신의 사용 가능한 크레딧 증거가 필요합니다. 일반 응답의 사용량 헤더는 크레딧 잔액 관측 시각을 갱신하지 않습니다. + 별도의 백그라운드 재검증은 기본적으로 꺼져 있습니다. Token Guardian, `openai`의 `proactive` 갱신 정책, `tokenGuardian.codexWarmupEnabled`가 필요하며 등록 검증 대기 계정은 제외합니다. ### 계정이 요청을 처리하지 못하게 된 이유 diff --git a/docs-site/src/content/docs/ko/guides/combos.md b/docs-site/src/content/docs/ko/guides/combos.md index c9ccf3757b3..e5d8c844516 100644 --- a/docs-site/src/content/docs/ko/guides/combos.md +++ b/docs-site/src/content/docs/ko/guides/combos.md @@ -132,7 +132,7 @@ ocx combo set balanced \ | 인증, 구독, 쿼터, 속도 제한, 과부하, 또는 상위 서버 오류로 분류됨 | 상태 코드만으로는 충분하지 않더라도 대상을 쿨다운으로 보내고 넘어갑니다. | | 클라이언트 취소(499), `origin_rejected`, cyber-policy refusal, context overflow, 또는 기타 invalid request | 멈추고 오류를 반환합니다. 다른 대상을 써도 요청이 유효해지지 않기 때문입니다. | | ChatGPT 계정으로 Codex를 사용할 때 해당 모델을 지원하지 않는다는 정확한 HTTP 400 거부 | 출력이 시작되기 전에 다음 설정 대상으로 넘어갑니다. 거부 문장 전체가 `detail`, `error.message` 또는 일반 텍스트에 있어야 하며, `detail`과 `error`가 충돌하면 중단합니다. 이 사유만으로 계정을 재인증 상태로 바꾸거나 공급자의 모든 모델에 쿨다운을 걸지는 않습니다. | -| `user`를 명시적으로 거부하거나, `reasoning.effort`/`reasoning_effort`의 지원되지 않는 값 또는 모델별 이미지 입력 거부(`param: input`)를 나타내는 구조화된 HTTP 400 | 출력 시작 전에 쿨다운 기록 없이 다음 적격 대상으로 넘어갑니다. 선택적 매개변수 호환성을 참조하세요. | +| `user`를 명시적으로 거부하거나, `reasoning.effort`/`reasoning_effort`의 지원되지 않는 값, 모델별 이미지 입력 거부(`param: input`) 또는 경로가 읽을 수 없는 입력 미디어 거부(`unsupported_input_modality`)를 나타내는 구조화된 HTTP 400 | 출력 시작 전에 쿨다운 기록 없이 다음 적격 대상으로 넘어갑니다. 선택적 매개변수 호환성을 참조하세요. | | 인프로세스 어댑터(`runTurn`)가 실행하는 Responses 턴에서 현재 요청이 선언하지 않은 첫 도구 호출(출력이나 재전송 불가 부작용 이전) | 대상을 쿨다운하고 같은 도구 카탈로그로 다음 대상으로 넘어갑니다. 출력이 보였거나 재전송 불가 부작용이 생긴 뒤에는 거부가 그대로 확정됩니다. Chat Completions와 Anthropic Messages 요청은 바뀌지 않습니다. | | 그 밖의 분류되지 않은 오류 | 멈추고 오류를 반환합니다. | @@ -194,7 +194,7 @@ v1/base/v2 모드와 암호화된 작업의 전체 흐름은 [Sub-agent Surface] ### 대시보드 -로컬 대시보드를 열고 **Models → Combos**를 선택합니다. 워크스페이스는 콤보를 만들고, 편집하고, 이름을 바꾸고, 제거할 수 있으며, 대상 선택기에서는 비활성 모델과 중첩 콤보를 제외합니다. +로컬 대시보드를 열고 **Models → Combos**를 선택합니다. 워크스페이스에서 콤보를 만들고, 편집하고, 이름을 바꾸고, 제거할 수 있습니다. 모델 선택기에는 숨겨진 모델도 포함됩니다. 표시 설정은 외부 모델 목록에만 적용되며 콤보 내부 선택에는 영향을 주지 않습니다. 숨겨진 모델을 선택해도 외부에 표시되지 않습니다. 대상 선택기에서는 비활성 공급자, 중첩 콤보, 결정 서비스 전용 JEV 공급자를 계속 제외합니다. 각 대상에는 **사용 가능**, **할당량 소진**, **할당량 알 수 없음** 실시간 배지도 표시됩니다. 편집기는 사용 가능한 모든 대상에 대해 설정된 인증 정보의 추론 한도가 소진되었다는 서버 확인이 현재 유효할 때만 할당량을 이유로 저장과 만들기를 차단합니다. 표시 전용 계정·모델·검색·MCP 할당량이나 누락되거나 만료된 라우팅 근거 정보는 이 차단을 일으키지 않습니다. 차단은 해당 한도의 초기화 시점이나 데이터 유효기간이 끝나면 해제되며 페이지가 활성화되거나 표시될 때 다시 확인됩니다. 새로 고침은 콤보 데이터와 할당량을 모두 다시 불러옵니다. 대시보드 편집기에서는 아직 `cooldownMs`나 `waitForCooldownMs`를 설정할 수 없습니다. 후속 UI 작업이 완료될 때까지 구성 파일이나 관리 API를 사용하세요. @@ -209,9 +209,9 @@ ocx combo set --targets provider/model[:weight],... ocx combo remove --yes ``` -`set`은 `--strategy`, `--sticky`, `--effort`, `--alias`, `--native-alias`, `--display-name`, +`set`은 `--strategy`, `--sticky`, `--effort`, `--alias`, `--native-alias`, `--display-name`, `--description`, `--rename-from`도 받습니다. `--native-alias`에는 현재 지원되는 bare native alias와 비어 있지 않은 display name이 필요합니다. `--effort`, `--alias`, -`--display-name`에 `-`를 지정하면 해당 값을 지울 수 있습니다. `create`와 `update`는 `set`의 +`--display-name`, `--description`에 `-`를 지정하면 해당 값을 지울 수 있습니다. `create`와 `update`는 `set`의 별칭이고 `delete`는 `remove`의 별칭입니다. 모든 combo 하위 명령은 `ocx route combo ...`에서도 사용할 수 있습니다. ### Management API @@ -256,6 +256,7 @@ ocx combo remove --yes | `alias` | 아니요 | 없음 | 선택적으로 앞뒤 공백을 제거한 공개 모델 ID입니다. 위의 alias 규칙을 따릅니다. 빈 값은 alias 없음으로 저장됩니다. | | `nativeAlias` | 아니요 | `false` | 현재 지원되는 bare native alias가 routing/catalog 우선권을 갖도록 명시적으로 허용합니다. | | `displayName` | 아니요 | 없음 | catalog 표시 전용 label입니다. `nativeAlias`가 true이면 필수입니다. | +| `description` | 아니요 | 없음 | catalog 행에 표시되는 설명입니다. `displayName`과 같은 제한이 적용됩니다. 설정하지 않으면 생성된 `Routed via opencodex → combo (combo).` 문구가 유지됩니다. | ## 문제 해결 @@ -285,6 +286,6 @@ opencodex 인스턴스에 기록했는지 확인하세요. ## 선택적 매개변수 호환성 -일반적인 400 오류는 종료되지만, `user`를 명시적으로 거부하거나 `reasoning.effort`/`reasoning_effort`의 지원되지 않는 값 또는 모델별 이미지 입력 거부(`param: input`)를 나타내는 구조화된 오류는 출력 시작 전에 다음 적격 대상으로 넘어갈 수 있습니다. 이 경우 쿨다운을 기록하지 않습니다. 보안 정책 거부, 취소 및 이미 시작된 출력은 재실행하지 않습니다. +일반적인 400 오류는 종료되지만, `user`를 명시적으로 거부하거나 `reasoning.effort`/`reasoning_effort`의 지원되지 않는 값, 모델별 이미지 입력 거부(`param: input`) 또는 경로가 읽을 수 없는 입력 미디어 거부(`unsupported_input_modality`)를 나타내는 구조화된 오류는 출력 시작 전에 다음 적격 대상으로 넘어갈 수 있습니다. 이 경우 쿨다운을 기록하지 않습니다. 보안 정책 거부, 취소 및 이미 시작된 출력은 재실행하지 않습니다. [Canonical compatibility details](/guides/combos/#request-local-target-compatibility). diff --git a/docs-site/src/content/docs/ko/guides/desktop-app.md b/docs-site/src/content/docs/ko/guides/desktop-app.md index 446a6e20c64..37c38585ff1 100644 --- a/docs-site/src/content/docs/ko/guides/desktop-app.md +++ b/docs-site/src/content/docs/ko/guides/desktop-app.md @@ -91,8 +91,12 @@ macOS에서 메뉴의 **Start at Login** 항목은 시작 안전 페이지도 **status unknown**은 등록을 읽지 못했다는 뜻입니다. 앱이 프록시를 실행하는 동안 시작 안전 페이지는 백그라운드 서비스 설치를 막아 둡니다. 서비스를 대신 쓰려면 OpenCodex를 종료하고 터미널에서 `ocx service install`을 실행하세요. Codex 런처 shim은 이 페이지에서 설치할 수 있습니다. -Codex를 실행할 때 프록시가 없으면 shim은 OpenCodex가 프록시를 다시 띄우기를 기다리고, macOS에서 -앱이 종료돼 있으면 앱을 열며, 그 밖의 경우에는 두 번째 프록시를 띄우지 않고 오류를 알립니다. +Codex를 실행할 때 프록시가 없으면 shim은 OpenCodex가 프록시를 다시 띄우기를 기다립니다. macOS에서 +앱이 종료돼 있고 기록된 소유 정보가 유효한 **Start at Login** 등록과 일치하면 앱을 열고, 그 밖의 +경우에는 두 번째 프록시를 띄우지 않고 오류를 알립니다. 자동으로 다시 열 때는 그 등록에 기록된 앱 +번들 경로를 사용하고, 번들이 선언한 주 실행 파일이 등록된 Desktop 실행 파일과 같은지 확인합니다. +등록이 없거나 오래됐거나 서로 맞지 않으면 OpenCodex를 직접 여세요. 번들 식별자가 같은 다른 사본을 +대신 고르지 않습니다. shim이 "활성화 미확인"으로 보이면 셸 시작 파일의 다른 PATH 설정 뒤에 `. ~/.opencodex/codex-shell-env.sh`를 추가하세요. diff --git a/docs-site/src/content/docs/ko/guides/integrations.md b/docs-site/src/content/docs/ko/guides/integrations.md index c5a058d89be..aa51c5a3bf6 100644 --- a/docs-site/src/content/docs/ko/guides/integrations.md +++ b/docs-site/src/content/docs/ko/guides/integrations.md @@ -3,7 +3,7 @@ title: 연동 description: 대시보드에서 OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, gjc, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Aside, Raycast, omo, Cline CLI, Kilo, Command Code와 Factory Droid를 opencodex에 연결합니다. 클라이언트마다 스위치가 하나씩 있으며 기록 전마다 백업합니다. --- -**연결** 페이지는 클라이언트의 설정 파일에 opencodex 프로바이더 블록을 쓰고 다시 제거합니다. 다음 18개 클라이언트는 각각 스위치로 관리합니다. +**연결** 페이지는 클라이언트의 설정 파일에 opencodex 프로바이더 블록을 쓰고 다시 제거합니다. 다음 클라이언트는 각각 스위치로 관리합니다. | 클라이언트 | 설정 파일 | 형식 | 변경 적용 시점 | 자격 증명 | |---|---|---|---|---| diff --git a/docs-site/src/content/docs/ko/guides/providers.md b/docs-site/src/content/docs/ko/guides/providers.md index f9c973b95d4..e7fe1c3c387 100644 --- a/docs-site/src/content/docs/ko/guides/providers.md +++ b/docs-site/src/content/docs/ko/guides/providers.md @@ -55,8 +55,9 @@ shipped v1 config는 marker 2의 단일 옵션 행으로 자동 이관됩니다. [`retryOn429`](/ko/reference/configuration/)(동일 키 429 재시도)는 API 키 프로바이더 (`authMode: "key"`)에만 적용됩니다. OAuth·forward·로컬 프리셋은 제외됩니다 — 같은 토큰을 -재전송해서는 안 되며, 로컬 런타임에는 보존할 원격 키가 없습니다. 옵트인입니다: 옵션이 없으면 -꺼져 있고, 객체가 있으면 `enabled: false`가 아닌 한 활성화됩니다. +재전송해서는 안 되며, 로컬 런타임에는 보존할 원격 키가 없습니다. 문서화된 대상별 기본값을 제외하면 옵트인입니다. 정식 Mistral Chat은 기존 요청 단위 전송 한도 안에서 끈질긴 재시도 정책(재전송 6회, 대체 간격 10초)을 사용합니다. `enabled: false`를 포함한 명시적인 `retryOn429` 설정이 우선합니다. Mistral 프리셋은 전달받은 프롬프트 캐시 키도 기본으로 전달하며, `promptCacheKey: false`로 전달을 끌 수 있습니다. 캐시 친화성은 재사용 가능성을 높이지만 캐시 적중을 보장하거나 토큰 처리량 제한을 없애지는 않습니다. + +Mistral은 Studio 키와 Vibe 키 모두에 하나의 API 키 프로바이더를 사용합니다. 키 종류와 요금제 허용량은 [Mistral API 키 문서](https://docs.mistral.ai/admin/identity-access/api-keys)에 설명되어 있으며, 프록시는 키에서 구독 등급을 추론하지 않습니다. 모든 모델에서 업스트림이 명시한 `Retry-After` 기한은 같은 자격 증명과 모델의 호출 사이에서 공유됩니다. 재시도 시각이 없는 429는 유효한 분 단위 카운터가 용량 소진을 보여 주거나, 거절된 쿼리의 측정 비용이 남은 토큰을 넘지만 공시된 전체 허용량 안에 들어올 때 보수적인 60초 복구 대기를 시작할 수 있습니다. 이는 로컬 복구 정책이며 서버 재설정 시각을 정확히 예측하는 것이 아닙니다. 정상 트래픽에서는 다음 쿼리의 캐시나 비용을 예측하지 않습니다. 대기 후 복구 프로브를 한 번 보내고, 성공하면 일반 트래픽으로 돌아갑니다. 근거가 없거나, 형식이 잘못됐거나, 이용 권한이 0이거나, 월간 한도가 소진됐거나, 전체 허용량을 넘는 경우에는 재설정을 지어내거나 기록을 압축하지 않습니다. `requestPacing: { "enabled": false }` 또는 `retryOn429: { "respectRetryAfter": false }`로 공유 페이싱을 끕니다. 같은 키로 재전송할 때 Mistral의 재시도 시각이 `retryOn429.maxIntervalMs`를 넘으면 프록시는 일찍 재전송하지 않고 429와 재시도 힌트를 반환합니다. 라운드 로빈 키 선택이 켜져 있으면 대기 중인 요청은 키 쿨다운을 다시 확인하고 대체 키의 페이싱 용량을 확보하며, 출력 전의 키 거절은 요청의 기존 전송 한도 안에서 정상 키로 복구될 수 있습니다. 컨텍스트 창과 모델별 압축 설정은 서로 독립적으로 유지됩니다. ## 1. ChatGPT 로그인 (forward / 패스스루) @@ -99,6 +100,7 @@ ocx login google-antigravity ocx login cursor # Cursor 전용 PKCE 로그인 ocx login command-code # Command Code 브라우저 OAuth (또는 ~/.commandcode/auth.json 가져오기) ocx login devin # Cognition/Devin: Devin CLI 자격 임포트 우선, 없으면 Auth0 브라우저 로그인 +ocx login dsh-account # DeepSeek Harness Desktop (~/.dsh/.credentials.yaml 권한 가져오기) ocx login github-copilot # GitHub 디바이스 플로우 → Copilot 토큰 (Copilot Pro/Business) ocx login codex # Codex 계정 풀 (별칭: chatgpt, openai / 프록시가 실행 중이어야 함) ocx logout @@ -115,10 +117,18 @@ ocx logout | `google-antigravity` | `google` | `https://daily-cloudcode-pa.googleapis.com` | Google OAuth를 Cloud Code Assist wire로 사용합니다. 실시간 탐색은 인증된 CCA `v1internal:fetchAvailableModels` 엔드포인트를 사용하며 로그인한 계정에서 사용할 수 있는 agent 모델만 게시합니다. 유지 관리되는 카탈로그는 폴백으로 남습니다. | | `cursor` | `cursor` | `https://api2.cursor.sh` | 실험적 PKCE 로그인, HTTP/2 전송, 계정별 모델 탐색을 지원합니다. | | `devin` | `devin` | `https://server.codeium.com` | 실험적인 비공식 Cognition/Devin 브리지. 로그인은 먼저 설치된 Devin CLI가 이미 보유한 자격을 가져옵니다(`devin auth login`이 `devin-session-token`을 자기 `credentials.toml`에 씁니다). 없으면 Auth0 브라우저 사인인을 열어 붙여넣은 토큰을 `RegisterUser`로 장기 API 키에 교환합니다. `ocx login devin-cli`는 deprecated alias로 계속 동작합니다. 모델 목록은 `GetCascadeModelConfigs`로 계정마다 조회하며, 스트리밍은 Connect-RPC 위에서 `runTurn` 경로만 씁니다. 대시보드 프리셋에는 기본으로 없으니 직접 추가하세요. | +| `dsh-account` | `dsh-account` | `https://api.deepseek.com/anthropic` | 설치 및 로그인된 DeepSeek Harness (DSH) Desktop 계정을 재사용하는 실험적 자격 임포트 우선 계정 프로바이더입니다. 명시적 임포트 시 `~/.dsh/.credentials.yaml`에서 엄격히 읽기 전용으로 자격을 읽습니다. DeepSeek API 키 요금 청구와 완전히 독립적입니다. | | `github-copilot` | `openai-chat` | `https://api.githubcopilot.com` | 실험적. GitHub 디바이스 플로우 + `copilot_internal` 교환(VS Code OAuth 클라이언트). 활성 Copilot 구독 필요; 공식 서드파티 API가 아닙니다. | Google Antigravity 계정·제공자 할당량 확인은 모델 목록 폴백을 포함해 고정된 Google 회계 엔드포인트를 사용합니다. 해당 목적지의 투명 Fake-IP DNS를 지원하며 TLS 검증, 리다이렉트 거부, 사설 주소 검사는 유지합니다. 사용자 지정 base URL은 모델 요청에만 적용되며 할당량 목적지는 바꾸지 않습니다. `NO_PROXY`는 기존 직접 연결 정책을 유지합니다. +Vertex AI의 `gemini-4-argon`에는 스크린샷 같은 도구 결과 이미지를 해당 함수 응답 안에 담아 +전송합니다. 이미지가 있는 도구 호출 뒤에 발생하는 400 오류 +"Requests ending with a model turn are not supported"를 방지하며 이미지를 보존합니다. +중복된 결과나 일치하는 호출이 없는 결과의 이미지는 도구 응답 뒤의 별도 메시지로 보존하고, +호출과 연결할 수 없었다는 표시를 남깁니다. +별도의 설정 변경은 필요하지 않으며, 이 처리는 Vertex Argon에만 적용됩니다. + ### Google 도구 스키마 손실 진단 Google 도구 선언은 선택된 엔드포인트 클래스에 맞춰 컴파일됩니다. `ocx debug provider on`, @@ -188,9 +198,20 @@ Kiro 로그인에는 Kiro CLI가 필요합니다. Unix에서는 `curl -fsSL http 롤백은 스냅샷이 있을 때만 가능하므로, 세션 저장소가 존재하지만 캡처할 수 없는 경우(파일을 읽을 수 없음, 스키마 불일치, 토큰 선택 모호), `KIROCLI_DB_PATH` / `KIRO_CLI_DB_FILE`이 실제 CLI 저장소와 다른 가져오기 경로를 가리키는 경우, 또는 기본 CLI 데이터베이스에 인식 가능한 토큰 행이 없는 경우 **계정 추가**는 `kiro-cli` 로그아웃을 거부합니다. 일반 `kiro-cli` 데이터 경로의 손상된 데이터베이스를 수리하거나 제거하고, 가져오기 전용 선택자가 설정돼 있으면 해제한 뒤 다시 시도하세요. 기존 `kiro-cli` 세션이 아예 없는 환경에서는 영향이 없습니다. +### DeepSeek Account (DSH) + +`dsh-account`는 설치 및 로그인된 DeepSeek Harness (DSH) Desktop 애플리케이션의 인증된 계정 자격 증명을 재사용하는 실험적 자격 임포트 우선 계정 프로바이더입니다. + +- **DeepSeek API 키와 독립:** `dsh-account`는 사용자의 인증된 DeepSeek 세션 할당량(일반/보너스 지갑 잔액 및 총 지출)을 사용하며, `deepseek` 프로바이더는 플랫폼 API 키 잔액에서 청구됩니다. 두 자격 증명 도메인 및 라우팅은 엄격히 분리되어 상호 폴백되지 않습니다. +- **명시적 가져오기 전용:** OpenCodeX는 DeepSeek Harness의 설치 및 로그인 여부(`~/.dsh/.credentials.yaml`)를 감지하지만, 사용자의 명시적 명령(`ocx login dsh-account` 또는 웹 대시보드에서 가져오기 클릭) 없이 토큰을 자동 복사, 가져오기 또는 사용하지 않습니다. +- **DSH 자격 증명 저장소 읽기 전용:** OpenCodeX는 `~/.dsh/.credentials.yaml`을 엄격히 읽기 전용으로 취급합니다. OpenCodeX는 DeepSeek Harness에 속한 어떠한 파일도 쓰기, 수정, 삭제하거나 권한을 변경하지 않습니다. +- **격리된 자격 증명 복사본 및 로그아웃:** 가져오기 시 OpenCodeX는 DeepSeek의 신뢰할 수 있는 ID 엔드포인트(`/auth-api/v0/users/current`)에서 토큰을 검증하고 자체 복사본을 OpenCodeX 인증 저장소에 저장합니다. OpenCodeX에서 로그아웃하거나 계정을 삭제해도 OpenCodeX 로컬 복사본만 삭제되며, 업스트림 원격 세션 로그아웃을 트리거하거나 DeepSeek Harness Desktop에서 로그아웃되지 않습니다. +- **백그라운드 토큰 자동 갱신 없음 및 401 재가져오기:** DSH 세션 부여에는 자동 백그라운드 갱신 엔드포인트가 없습니다. 업스트림에서 HTTP 401(`ACCOUNT_TOKEN_INVALID`)을 반환하면 OpenCodeX는 계정을 `needsReauth`로 표시하고 라우팅을 중단합니다. 액세스를 복원하려면 사용자가 `ocx login dsh-account`로 명시적 재가져오기를 수행해야 합니다. +- **서드파티 면책 조항:** OpenCodeX는 서드파티 클라이언트의 세션 부여 재사용에 대한 DeepSeek의 공식 보증이나 승인을 주장하지 않습니다. + ## 3. API 키 카탈로그 -opencodex에는 빌트인 프리셋이 104개 들어 있습니다. 키 방식 85개, OAuth 15개, 로컬 3개, +opencodex에는 빌트인 프리셋이 106개 들어 있습니다. 키 방식 85개, OAuth 17개, 로컬 3개, 기본 ChatGPT 포워드 프리셋 1개입니다. 대시보드의 **Add provider** 선택기는 키 발급 페이지를 열고, 입력한 키를 검증한 뒤 저장합니다(검증은 프로바이더별로 다릅니다). 주요 항목은 다음과 같습니다: @@ -245,7 +266,7 @@ Cline IDE/CLI에서만 제공되며 API로는 사용할 수 없습니다. `minim | Z.AI (GLM Coding) | `https://api.z.ai/api/coding/paas/v4` | | Zhipu AI (BigModel) | `https://open.bigmodel.cn/api/paas/v4` | | [BigModel Coding Plan — Responses (정적 모델 목록)](/guides/providers/#bigmodel-coding-plan-over-responses) | `https://open.bigmodel.cn/api/v1` | -| Qwen Cloud | Token plan(기본): `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` · 종량제: `https://dashscope.aliyuncs.com/compatible-mode/v1` · 또는 사용자 지정 | +| Qwen Cloud | Token plan(기본): `https://token-plan.maas.qwencloudapi.com/compatible-mode/v1` · 종량제: `https://dashscope.aliyuncs.com/compatible-mode/v1` · 또는 사용자 지정 | | Tencent Cloud Coding Plan | `https://api.lkeap.cloud.tencent.com/coding/v3` | | SiliconFlow | `https://api.siliconflow.cn/v1` | | Volcengine Ark · Coding Plan · Agent Plan | `https://ark.cn-beijing.volces.com/api/v3` · `https://ark.cn-beijing.volces.com/api/coding/v3` · `https://ark.cn-beijing.volces.com/api/plan/v3` | @@ -265,13 +286,25 @@ Chat Completions 모델(그리고 Responses 전용이라 Responses로 보내는 [OpenGateway 대시보드](https://opengateway.ai/api-keys)에서 키를 발급한 뒤 `ocx provider add opengateway`를 실행하거나 대시보드에서 **OpenGateway**를 선택하세요. Chat 요청은 설정한 Bearer 키를 사용하며 공개 모델 목록은 키의 유효성을 검증하지 않습니다. +OpenGateway는 Responses에서 네이티브 커스텀 도구 호출의 재전송을 거절하므로 프리셋은 코드 모드의 `exec` 등 Codex 커스텀 도구를 전송 전에 함수 도구로 변환합니다. +Responses 도구 결과 배열은 텍스트로 변환하고 이미지는 짝이 맞는 도구 결과 묶음 뒤의 메시지에 보존합니다. refusal이나 불투명한 내용 등 지원하지 않는 결과 part는 전송 전에 명시적인 클라이언트 입력 오류로 거절합니다. +이미지의 `original` 상세도는 게이트웨이가 지원하는 `high`로 바꾸며 이미지 바이트는 그대로 유지합니다. +네이티브 Responses 경로는 텍스트가 없는 완료 응답도 검사합니다. 출력 예산이 소진됐으면 +`incomplete / max_output_tokens`로 알리며 요청한 예산을 임의로 늘리지 않습니다. +예산이 남고 안전하게 재전송할 수 있는 빈 완료는 기존 호출 한도 안에서 한 번만 재시도합니다. +`emptyCompletionRetry: false` 또는 `OCX_EMPTY_COMPLETION_RETRY=0`이면 이 재시도를 끕니다. +텍스트·거절·도구 동작·알 수 없는 스트림 이벤트가 관측되면 재전송하지 않고 원본을 유지합니다. +다른 제공자의 동작과 재시도 기본값에는 적용하지 않습니다. +재전송은 명시적인 `store: false`, 완전한 입력, 클라이언트 실행 도구만 있는 요청에 한정합니다. +`web_search` 같은 서버 실행 도구가 선언돼 있으면 도구 이벤트가 없어도 자동 재전송하지 않습니다. + +**OpenCode Zen**(`opencode-zen`)은 [opencode.ai/auth](https://opencode.ai/auth)에서 발급한 API 키로 `https://opencode.ai/zen/v1`을 사용합니다. 무료 모델은 분당 약 15–20회 요청의 짧은 창 속도 제한에 걸릴 수 있습니다(커뮤니티 측정; OpenCode는 RPM을 공개하지 않음). 속도 제한 429에 `Retry-After`가 없으면 opencodex가 안내와 합성 `Retry-After`를 추가하며, 유효한 업스트림 헤더가 우선합니다. 동일 키 재시도는 [`retryOn429`](/ko/reference/configuration/)로 선택합니다. -**OpenCode Zen**(`opencode-zen`)과 키 없는 **OpenCode Free** 프리셋은 -`https://opencode.ai/zen/v1`을 공유합니다. 그 게이트웨이의 무료 모델은 종종 분당 약 15–20회 요청의 짧은 창 속도 제한에 걸립니다(커뮤니티 측정; OpenCode는 RPM을 공개하지 않음). Zen은 `Retry-After` / `X-RateLimit-*` 헤더 없는 일반 429를 반환할 수 있습니다. 이는 키 없는 데스크톱 할당량(`opencode-free`에서 약 5시간당 Big Pickle/무료 모델 200회)과 별개입니다. Zen이 그런 429에서 `Retry-After`를 생략하면 opencodex는 클라이언트 오류에 안내를 더하고 합성 `Retry-After`를 붙입니다(업스트림 `Retry-After`가 있으면 그것이 우선). 동일 키 대기 재시도는 [`retryOn429`](/ko/reference/configuration/)로 선택합니다. +정식 `opencode-zen` 제공자에서 API 키 인증과 공식 경로 `https://opencode.ai/zen/v1` 또는 `https://opencode.ai/inference/v1`을 사용하면 Gemini는 기본 `googleMode: ai-studio`에서 Google 네이티브 API를 자동으로 사용합니다. Zen 모델 ID는 `-tiered` 접미사가 없는 Flash ID도 그대로 유지합니다. Claude는 `googleMode`와 관계없이 Anthropic Messages를 사용합니다. 저장된 설정을 읽을 때 이 네이티브 모델의 조건에 맞는 기존 Chat/Responses 재정의와 조건에 맞는 Responses 전용 `image_generation` 호스팅 도구 선호 설정만 무시합니다. 사용자 지정 제공자, 키 인증이 아닌 경로, 그 밖의 선호 설정은 그대로 유지합니다. -**키 없는 `opencode-free` 등급은 현재 서드파티 클라이언트에 닫혀 있습니다.** Zen은 `x-opencode-session` 헤더가 없는 요청을 모두 거부하고, 오류 타입 `MissingSessionID`와 "OpenCode's free tier can only be used in OpenCode" 메시지를 돌려줍니다. 관문은 헤더가 있는지만 보기 때문에 프록시가 값을 지어내 통과할 수도 있지만, opencodex는 그렇게 하지 않습니다. 세션 식별자와 버전이 붙은 `opencode/` User-Agent를 만들어 보내는 것은 자신이 OpenCode 클라이언트라고 주장하는 일이고, OpenCode는 이 키 없는 등급에 서드파티 연동 계약을 공개한 적이 없습니다. 그렇게 받아낸 HTTP 200은 허가가 아니라 뚫린 관문일 뿐입니다. 그래서 opencodex는 우회하지 않고 제한을 그대로 알립니다. `opencode-free`로 보낸 요청은 업스트림 관문을 설명하는 오류를 돌려받습니다. +**`opencode-free` 지원이 제거되었습니다.** OpenCode의 업스트림 무료 등급 제한으로 모든 모델이 `403 permission_denied`를 반환하기 때문입니다([#7255](https://github.com/lidge-jun/opencodex/issues/7255)). 기존 설정을 불러오면 해당 항목과 종속 모델 참조를 메모리에서 비활성화하고 프로세스당 한 번 경고하며 파일은 변경하지 않습니다. 기본 공급자였다면 표준 OpenAI OAuth 경로가 기본값이 됩니다. `opencode-go`, API 키를 사용하는 `opencode-zen` 또는 다른 공급자를 사용하세요. [OpenCode Zen 약관](https://opencode.ai/docs/zen/). -같은 모델로 가는 지원 경로는 [opencode.ai/auth](https://opencode.ai/auth)에서 발급받은 OpenCode Zen API 키를 쓰는 **`opencode-zen`** 프리셋입니다. 나중에 OpenCode가 키 없는 등급의 서드파티 경로를 공개하면 opencodex도 따라갈 수 있고, 그때까지 이 프리셋은 제한을 기록해 두는 역할을 합니다. 업스트림 약관: [opencode.ai/docs/zen](https://opencode.ai/docs/zen/). +정상 Claude Desktop 할당과 기본값은 유지됩니다. 소스 모델 허용 목록이 비게 되는 규칙은 비활성화되며, 주입 모델이 제거되면 네이티브 하위 에이전트 동기화도 비활성화됩니다. 이후 설정을 저장하면 제거된 참조는 다시 기록되지 않고 정상적인 동시 편집은 유지됩니다. 대부분은 bearer 키와 함께 `openai-chat` 어댑터를 사용하며, **Xiaomi MiMo**(`xiaomi`) 같은 Anthropic 호환 프리셋은 `anthropic` 어댑터(`x-api-key`)를 사용합니다. Xiaomi에는 OpenAI Chat @@ -438,6 +471,9 @@ Sol/Terra/Luna를 폴백 목록에 넣어 둡니다. | Codex 로그인(Pool 또는 Direct) | `gpt-5.6-*` | 922,000 | | OpenAI (API key) | `openai-apikey/gpt-5.6-*`와 `*-pro` | 922,000 (max input 922,000) | | OpenRouter | `openrouter/openai/gpt-5.6-sol`, `openrouter/openai/gpt-5.6-terra`, `openrouter/openai/gpt-5.6-luna` | 922,000 | +| Codex | `gpt-6.1-terra` | 500,000 / 872,000 | +| OpenAI API | `openai-apikey/gpt-6.1-terra` | 1,050,000 (128,000 max output) | +| OpenRouter | `openrouter/openai/gpt-6.1-terra` | 1,050,000 | | Cursor | `cursor/gpt-5.6-sol`, `cursor/gpt-5.6-terra`, `cursor/gpt-5.6-luna` | 1,000,000 | 네이티브 GPT-5.6 항목은 고정된 업스트림 reasoning 단계를 그대로 따릅니다. 예를 들어 Luna에는 @@ -458,7 +494,7 @@ Amazon Bedrock 네이티브 API처럼 이 구현 중 어느 것과도 맞지 않 Gateway**는 URL에 계정 + 게이트웨이 id를 채워야 합니다. Copilot은 혼합 wire 카탈로그를 제공합니다. 모델(`gpt-5.3-codex`, `gpt-5.4`, -`gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`)은 에이전트 +`gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6.1-sol`, `gpt-6.1-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`)은 에이전트 트래픽에 대해 `/chat/completions`를 거부하므로 opencodex는 이 모델들을 내장 기본값으로 Responses API를 통해 라우팅하고, 다른 Copilot 모델은 모두 chat completions를 유지합니다. 우선순위는 하드 wire 핀 → 명시적 [`modelAdapters`](/ko/reference/configuration/providers/) diff --git a/docs-site/src/content/docs/ko/guides/remote-link.md b/docs-site/src/content/docs/ko/guides/remote-link.md index ea3733f92e8..5ad0f0ea302 100644 --- a/docs-site/src/content/docs/ko/guides/remote-link.md +++ b/docs-site/src/content/docs/ko/guides/remote-link.md @@ -55,7 +55,7 @@ Home의 프로바이더를 사용할 컴퓨터에서 다음을 진행합니다. - **Connected**는 SSH 터널이 준비되어 Child가 Home 링크를 사용할 수 있다는 뜻입니다. - **Reconnecting**은 터널을 다시 연결하는 중이라는 뜻입니다. 재시도 중에는 요청이 일시적으로 `Retry-After`와 함께 `503`을 반환할 수 있습니다. 자기 대시보드에서 연결한 Child에서는 요청이 먼저 터널이 돌아오기를 최대 15초 기다립니다. -- **Failed**는 조치가 필요하다는 뜻입니다. SSH 인증, 확인한 호스트 키, 포워딩 또는 타임아웃 사유를 확인하세요. SSH 터널을 띄운 쪽이 절전, 장애, 재시작 뒤에도 스스로 다시 시도합니다. Home이 추가한 Child라면 Home이, 자기 대시보드에서 연결한 Child라면 Child가 시도합니다. 타임아웃이나 포워딩 오류 뒤에는 약 1분마다, 인증 오류 뒤에는 5분마다 시도합니다. 호스트 키가 바뀐 경우에는 다시 시도하지 않습니다. 바로 다시 연결하려면 Home 대시보드에서 **Retry**를 누르거나 Home에서 `ocx link reconnect --link-id `를 실행하세요. OpenCodex를 재시작하지 않고 그 링크의 터널만 다시 띄웁니다. +- **Failed**는 조치가 필요하다는 뜻입니다. SSH 인증, 확인한 호스트 키, 포워딩 또는 타임아웃 사유를 확인하세요. SSH 터널을 띄운 쪽이 절전, 장애, 재시작 뒤에도 스스로 다시 시도합니다. Home이 추가한 Child라면 Home이, 자기 대시보드에서 연결한 Child라면 Child가 시도합니다. 타임아웃이나 포워딩 오류 뒤에는 1분 뒤에 다시 시도하고, 다시 실패할 때마다 간격을 두 배로 늘려 최대 5분마다 시도합니다. 인증 오류 뒤에는 5분마다 시도합니다. 호스트 키가 바뀐 경우에는 다시 시도하지 않습니다. 바로 다시 연결하려면 Home 대시보드에서 **Retry**를 누르거나 Home에서 `ocx link reconnect --link-id `를 실행하세요. OpenCodex를 재시작하지 않고 그 링크의 터널만 다시 띄웁니다. 링크가 실패해도 로컬 프로바이더로 조용히 전환하지 않습니다. diff --git a/docs-site/src/content/docs/ko/guides/remote-workspace.md b/docs-site/src/content/docs/ko/guides/remote-workspace.md index ef5b6490aec..1dcdf4564ad 100644 --- a/docs-site/src/content/docs/ko/guides/remote-workspace.md +++ b/docs-site/src/content/docs/ko/guides/remote-workspace.md @@ -93,8 +93,32 @@ ocx remote-workspace status 프롬프트를 보내면 수락 여부를 즉시 응답하고, 대시보드는 진행과 완료 상태를 폴링합니다. 수락 응답을 잃으면 초안은 제출 여부를 알 수 없다는 알림과 함께 남습니다. 다시 보내기 전에 세션 진행 상태를 확인하세요. 대시보드는 프롬프트를 자동 재시도하지 않습니다. +턴을 시작하는 동안 Pi 프로세스가 종료되거나 확장 기능에서 오류가 발생하면, Pi가 아직 해당 턴의 수락을 응답하지 않았더라도 세션에 실패가 표시됩니다. + 프롬프트 실행 중에도 **Stop**을 사용할 수 있습니다. Hub 코딩 에이전트의 현재 턴을 중단하고 활성 Executor 명령을 취소하며, 늦게 온 응답이 중지된 세션을 다시 열지 못하게 합니다. +## 여러 세션을 분할해서 보기 + +왼쪽 세션 목록에서 세션을 클릭하면 현재 선택한 창에서 열립니다. 세션을 다른 창의 +왼쪽·오른쪽·위·아래 가장자리로 끌어다 놓으면 화면이 분할됩니다. 최대 네 개의 서로 다른 +세션을 동시에 표시할 수 있고, 이미 표시 중인 세션은 중복 창을 만드는 대신 이동합니다. + +세션 옆 **오른쪽으로 분할** 또는 **아래로 분할** 버튼도 사용할 수 있습니다. 경계선을 +드래그하거나 경계선에 키보드 초점을 놓고 방향키를 눌러 크기를 조절하세요. **Home**과 +**End** 키는 최소·최대 크기로 조절합니다. 각 세션의 +입력 중인 글, 전송 상태, 중지 동작은 독립적입니다. **창 닫기**는 배치만 변경하며 세션을 +중지하지 않습니다. + +같은 브라우저에서 이 Hub를 다시 열면 창 배치가 복원됩니다. 브라우저 저장소에는 세션 +식별자, 분할 배치와 경계선 크기만 저장하며, 작성 중인 글이나 모델 응답은 저장하지 않습니다. +새로고침하면 아직 보내지 않은 글은 지워집니다. 화면 너비가 760px 이하이면 경계선 없이 +창을 세로로 쌓아 표시하고, 화면이 넓어지면 저장된 분할 배치와 크기로 돌아갑니다. + +이 화면은 OpenCodex 브라우저 대시보드와 데스크톱 앱에서 공통으로 사용합니다. CLI에서는 +`ocx gui`로 대시보드를 열고 정상적인 Hub 설정 후 **원격 워크스페이스**를 선택하세요. +이 기능은 `codex` 터미널 화면이나 별도 공식 ChatGPT/Codex 앱 내부의 대화 배치를 변경하지 +않습니다. + ## 재시작과 재연결 동작 Hub는 제한된 세션 메타데이터와 최근 이벤트의 작은 스냅샷을 저장합니다. Hub 재시작 뒤 완료되지 않은 세션은 원래 Executor를 기다립니다. 해당 기기가 다시 연결되면 다음 프롬프트가 원래 Codex 작업, Claude Code 세션 또는 Pi 세션 ID를 이어갑니다. diff --git a/docs-site/src/content/docs/ko/guides/sidecars.md b/docs-site/src/content/docs/ko/guides/sidecars.md index b1aca7ee1b1..9716fc27937 100644 --- a/docs-site/src/content/docs/ko/guides/sidecars.md +++ b/docs-site/src/content/docs/ko/guides/sidecars.md @@ -74,8 +74,11 @@ stall은 전체 생성 timeout이 아닙니다. SSE가 시작되기 전 실패 라우팅 모델이 해당 프로바이더의 `noVisionModels`에 있거나 해당 모델이 `modelInputModalities`에서 텍스트 전용으로 선언되어 있고 요청에 이미지가 들어오면, opencodex는 사용 가능한 비전 사이드카 계획이 있을 때에만 메인 호출 **전에** 각 이미지를 설명한 텍스트로 바꿉니다. 사용 가능한 계획이 없으면 원본 이미지는 텍스트 전용 -백엔드로 전달되지 않고 제거됩니다. 모델 카탈로그는 사이드카로 처리되는 모든 모델에 image input을 알립니다. -콤보는 모든 멤버가 네이티브로 또는 사이드카를 통해 이미지를 수용하고 콤보의 `imageInput` 설정이 비활성화되지 않은 +백엔드로 전달되지 않고 제거됩니다. `visionSidecar.enabled: false`이면 모델 카탈로그는 사용자 정의 모델과 +콤보 멤버를 포함한 텍스트 전용 모델에 사이드카 기반 이미지 입력을 추가하지 않습니다. 네이티브 이미지 지원과 +명시적으로 설정한 이미지 입력은 유지됩니다. `enabled`를 생략하면 사이드카 기반 표시는 켜진 상태이며, +카탈로그 표시는 사이드카의 인증 정보나 사용 가능 용량을 보장하지 않습니다. +콤보는 모든 멤버가 네이티브로 또는 활성화된 사이드카를 통해 이미지를 수용하고 콤보의 `imageInput` 설정이 비활성화되지 않은 경우에만 image input을 알립니다. 따라서 Codex 앱 같은 클라이언트는 사이드카가 실행되기 전에 첨부를 차단하지 않고 허용합니다. `visionSidecar.model`이 없거나 빈 값이면 OpenAI 실행 경로, Dashboard, 관리 API는 `gpt-5.6-luna`를 폴백으로 사용합니다. 시작 시 명시적으로 @@ -139,6 +142,10 @@ OpenAI 실행 경로, Dashboard, 관리 API는 `gpt-5.6-luna`를 폴백으로 `PUT /api/sidecar-settings`는 같은 필드를 받습니다. 부분 업데이트는 보내지 않은 키를 유지합니다. `timeoutMs`는 런타임 정수 범위(1–2147483647 ms)를 사용합니다. +비전의 실제 활성화 상태가 바뀌면 저장 후 관리되는 Codex 카탈로그와 모델 캐시도 갱신합니다. +갱신을 완료하지 못하면 응답의 `catalogRefreshPending`이 `true`가 되며 저장된 설정은 유지됩니다. +카탈로그 갱신 완료가 실행 중인 클라이언트의 다시 읽기를 확인하는 것은 아닙니다. + 웹 검색 사이드카 카드도 같은 구성입니다. 모델 선택기의 첫 행은 **끔 (Off)** 행입니다. 끄면 OpenCodex가 `web_search` 가로채기를 멈추고 Codex 통합이 `~/.codex/config.toml`에 `web_search = "disabled"`를 씁니다. Codex는 자체 모드가 그렇게 될 때까지 네이티브 호스팅 diff --git a/docs-site/src/content/docs/ko/guides/sub-agent-surface.md b/docs-site/src/content/docs/ko/guides/sub-agent-surface.md index c0273412ece..3b1fb5ab0c5 100644 --- a/docs-site/src/content/docs/ko/guides/sub-agent-surface.md +++ b/docs-site/src/content/docs/ko/guides/sub-agent-surface.md @@ -33,6 +33,42 @@ opencodex는 이 값을 Codex가 읽는 실시간 `/v1/models` 카탈로그와 v2 로스터의 경우 적합성은 세 가지 상태로 나뉩니다. `"v2"`로 표시된 항목, 명시적으로 `null`로 설정된 항목, 또는 `multi_agent_version` 필드가 없는 항목은 사용 가능합니다. 진짜 `"v1"` 핀은 해당 모델이 다른 협업 서피스에 속한다고 명시하므로 제외됩니다. +## `spawn_agent`가 광고하는 모델 + +Codex는 `spawn_agent` 도구 설명에 모델을 최대 5개까지 나열하며, 그 목록은 Codex가 직접 고릅니다. 선택기에 보이는 카탈로그 행을 `priority` 오름차순으로 정렬한 뒤 앞의 5개를 가져옵니다. **Subagents** 페이지의 로스터(`subagentModels`)는 로스터 순서대로 소속 모델에 가장 낮은 priority(계정 셀렉터가 없으면 `0`~`4`)를 주는 방식으로만 이 목록에 영향을 줍니다. 목록을 로스터로 제한하지는 않습니다. + +모델을 1~4개만 지정하면 Codex는 남은 칸을 priority 순으로 다음 행들로 채웁니다. 이 행들은 사용자가 고른 것이 아니며, 대시보드도 어떤 모델이 들어갔는지 보여 주지 않습니다. 어떤 행이 들어가는지는 카탈로그에 따라 다릅니다. + +| 카탈로그 | 남은 칸을 채우는 행 | +| --- | --- | +| Codex 계정 셀렉터 없음 | 나머지 라우팅 `provider/model` 행. 모두 priority `5`를 공유하므로 provider 이름, 그다음 모델 id의 알파벳 순서로 들어갑니다. 지정하지 않은 네이티브 GPT 행은 priority가 `100`에 지정한 id 수를 더한 값 이상이라 그 뒤에 옵니다. | +| Codex 계정 셀렉터 있음 | 계정 지정 네이티브 행(`/`). 지정한 행 바로 뒤에 옵니다. 지정하지 않은 라우팅 행은 priority `1005`로 옮겨져 그 뒤에 옵니다. | + +예를 들어 계정 셀렉터가 없고 로스터가 다음과 같으면: + +```json +{ "subagentModels": ["xai/grok-4.5", "anthropic/claude-sonnet-5"] } +``` + +| 칸 | 모델 | Priority | 이유 | +| ---: | --- | ---: | --- | +| 1 | `xai/grok-4.5` | `0` | 로스터 첫 번째 항목 | +| 2 | `anthropic/claude-sonnet-5` | `1` | 로스터 두 번째 항목 | +| 3 | `deepseek/deepseek-v4-flash` | `5` | 알파벳 순으로 첫 번째인 남은 라우팅 행 | +| 4 | `google/gemini-3-pro` | `5` | 다음 라우팅 행 | +| 5 | `moonshot/kimi-k3` | `5` | 다음 라우팅 행 | +| 6+ | `zai/glm-5.2`, 그다음 네이티브 GPT 행 | `5`, 그다음 `102` | 5개 밖 | + +로스터가 비어 있으면 opencodex는 아무것도 고정하지 않고, 5개는 카탈로그의 원래 priority를 따릅니다. + +채워진 칸도 일반 광고 항목이므로 메인 에이전트가 그 모델을 고를 수 있습니다. 이 목록은 허용 목록도 아닙니다. 메인 에이전트가 정확한 이름을 대면 Codex는 5개 밖의 카탈로그 모델도 받아들입니다. Codex가 광고하는 목록을 제어하려면: + +- 모델 5개를 지정하세요. 로스터가 가득 차면 채울 칸이 남지 않습니다. +- 절대 제안되지 않았으면 하는 모델은 비활성화하거나 해당 provider의 `selectedModels`에서 빼세요. 비활성화되었거나 선택되지 않은 모델은 선택기에서 빠지므로 칸을 채울 수 없습니다. +- `modelPickerOrder`는 Codex가 정렬에 쓰는 priority를 바꿉니다. 라우팅 행만 담은 목록은 나열된 미지정 행을 `1000` 이상으로 옮기므로, 나열되지 않은 라우팅 행이 먼저 칸을 채웁니다. bare 네이티브 id가 들어 있는 목록은 지정한 행까지 포함해 선택기 전체를 정렬합니다. 나열된 행이 앞에 오므로 나열되지 않은 어떤 행보다 먼저 광고 칸을 차지합니다. [Model ordering](/guides/model-ordering/)을 참고하세요. + +v2에서는 Codex가 5개를 고르기 전에 `"disabled"`로 고정된 행을 건너뜁니다. 그 밖에는 똑같이 동작합니다. opencodex v2 가이던스 로스터(`{{roster}}`)는 5개 안에 든, 설정된 로스터 모델만 나열합니다. 그래서 채워진 모델은 Codex 도구 설명에만 나타나고 opencodex 가이던스에는 나타나지 않습니다. Codex는 세션을 시작할 때 카탈로그를 읽으므로 로스터 변경은 새 세션부터 적용됩니다. + ## 위임 모델과 추론 강도 대시보드의 **서브에이전트 위임** 설정은 다음 세 가지 값을 제어합니다. @@ -161,6 +197,10 @@ curl -X PUT http://localhost:10100/api/injection-model \ 선택기에서 숨겨져 있거나, 다섯 모델 표시 한도를 넘었거나, 카탈로그에 없거나, v1에 고정되어 있을 수 있습니다. `v2`, `null`, 또는 값이 없는 서피스 값은 사용할 수 있지만, 실제 `"v1"` 핀은 사용할 수 없습니다. +### 지정하지 않은 모델이 왜 `spawn_agent` 목록에 보이나요? + +Codex는 광고하는 칸을 최대 5개까지 카탈로그에서 priority 순으로 채웁니다. 지정한 모델이 5개보다 적으면 다음 행들이 남은 칸을 차지합니다. 위의 "`spawn_agent`가 광고하는 모델" 절을 참고하세요. + ### 모드를 바꾸면 실행 중인 세션에도 반영되나요? 아닙니다. 모드를 바꾼 뒤에는 새 Codex 세션을 시작하세요. 오래 실행 중인 App 호스트에 오래된 카탈로그 상태가 남아 있으면 `ocx sync`를 실행한 뒤 해당 Codex 서피스를 다시 시작하세요. diff --git a/docs-site/src/content/docs/ko/guides/subagent-v1-default.md b/docs-site/src/content/docs/ko/guides/subagent-v1-default.md index 9bb8994418d..dbf8fee9103 100644 --- a/docs-site/src/content/docs/ko/guides/subagent-v1-default.md +++ b/docs-site/src/content/docs/ko/guides/subagent-v1-default.md @@ -94,3 +94,9 @@ ocx v2 mode v1 ``` CLI는 확인 메시지를 띄우지 않습니다. 같은 설정이므로 이 문서의 동작을 이해한 뒤 선택하세요. + +## 자식 에이전트 동시 실행 상한 + +**Models → Model settings**의 서브에이전트 상한 설정은 주 에이전트를 제외한 자식 에이전트의 최대 동시 실행 수를 정합니다. v1, base, v2와 전역 v2 플래그가 꺼진 하이브리드 모드에서도 사용할 수 있습니다. 서피스 모드는 이 상한과 별도로 선택합니다. + +상한을 높이면 더 많은 자식을 허용하지만, 자식을 자동으로 만들거나 실행 속도를 보장하지는 않습니다. 변경 후 새 Codex 세션을 시작하세요. 오래 실행된 App 호스트가 이전 상한을 계속 사용한다면 해당 호스트를 다시 시작하세요. diff --git a/docs-site/src/content/docs/ko/reference/architecture.md b/docs-site/src/content/docs/ko/reference/architecture.md index 68c30f9871e..59dc83a038c 100644 --- a/docs-site/src/content/docs/ko/reference/architecture.md +++ b/docs-site/src/content/docs/ko/reference/architecture.md @@ -69,7 +69,8 @@ HTTP 경계는 `server/index/serve-options.ts`가 맡고, Responses 데이터 건드리지 않은 채 종료 상태, 지연 시간, 프로바이더/모델, 최선 추정 토큰 사용량을 기록합니다. 요청 전 입력량 추정은 라우팅된 어댑터가 실제로 보내는 내용을 따릅니다. `openai-chat` 모델이 -`preserveReasoningContentModels`에 없으면 어댑터가 이전 assistant thinking을 보내지 않으므로 +명시적 `preserveReasoningContentModels` 목록 또는 미설정 시 DeepSeek Flash/Pro 감지에 따른 +유효 thinking 보존 정책에서 제외되면 어댑터가 이전 assistant thinking을 보내지 않으므로 추정에서도 뺍니다. 그래서 보내지도 않는 기록 때문에 로컬 컨텍스트 한도에서 잘못 거부되는 일이 없습니다. reasoning을 보존하는 모델과 다른 어댑터는 이전 thinking을 실제로 보내므로 계속 계산에 넣습니다. diff --git a/docs-site/src/content/docs/ko/reference/cli.md b/docs-site/src/content/docs/ko/reference/cli.md index 21a88093a16..3abe8fbd115 100644 --- a/docs-site/src/content/docs/ko/reference/cli.md +++ b/docs-site/src/content/docs/ko/reference/cli.md @@ -17,7 +17,7 @@ opencodex CLI는 `ocx`입니다. 첫 번째 명령 이름으로 분기하며, `s 관리 명령은 기록된 런타임 포트와 신원 검사를 사용해 살아 있는 프록시의 management API와 왕복 통신하며, 두 번째 설정 경로를 따로 두지 않습니다. 멈췄거나 닿을 수 없는 프록시는 HTTP 503으로 표시되며 CLI는 0이 아닌 종료 코드를 반환합니다. 명시적으로 오프라인 설정 작업으로 문서화된 명령은 라이브 프록시 없이 설정 파일을 검증하고 수정할 수 있습니다. -`ocx system codex-cli-update check`는 실행 중인 프록시가 없어도 되며 패키지 레지스트리를 조회하지 않습니다. 설정된 설치 후보에 대해 전체 경로를 숨긴 실행 파일 위치와 소유권 근거를 포함한 provenance 메타데이터를 제한된 범위에서 검사합니다. 신뢰할 수 있는 배포 런처 컨텍스트가 인증하는 것은 후보 스냅샷뿐이며, Codex가 성공적으로 실행되었다는 사실은 인증하지 않습니다. 이 단발성 명령은 Codex를 전혀 실행하지 않으므로 환경 또는 저장된 상태에서 얻은 후보는 보고 전용입니다(`managed: false`, 일반적으로 `selection_unattested`). `selectionAttested`는 항상 `false`입니다. JSON 출력에는 `candidateAvailable`, `candidateVersion`, `candidateSource`, `selectionAttested: false`가 포함됩니다. Bun이나 소스에서 직접 실행하면 런처 증거가 없으므로 환경 및 저장된 후보를 무시하고 POSIX에서는 `candidate_unavailable`, Windows에서는 `windows_inspection_deferred`을 보고할 수 있습니다. Windows에서는 이 첫 조각이 후보 또는 설정 경로의 파일시스템을 전혀 읽지 않습니다. 배포 런처가 증명한 절대 환경 후보에 한해서 앱 번들 또는 버전 관리자라는 어휘적 표지만 보고하며, 그 밖의 Windows 후보는 모두 실패 닫힘 처리합니다. 이 명령은 소프트웨어를 설치하거나 복구하지 않고, Codex나 npm을 실행하지 않으며, 실행 중인 프로세스를 제어하거나 설정 또는 캐시 상태를 쓰지 않습니다. +`check`는 레지스트리를 조회하거나 Codex를 실행하지 않습니다. POSIX에서는 배포 런처가 캡처한 환경 후보와 런타임 리졸버가 선택한 정규 경로가 같으면 `selectionAttested: true`가 될 수 있습니다. 연결된 npm-global 패키지 매니페스트가 소유권을 증명하고, 후보 버전이 보고된 경우 그 버전이 매니페스트 버전과 일치하면 `managed: true`가 될 수 있으며 저장된 후보는 인증되지 않습니다. Windows는 후보나 설정 파일을 읽지 않고 검사를 연기합니다. 직접 Bun/소스 실행은 런처 증거가 없어 후보를 신뢰하지 않습니다. `plan`은 격리된 임시 npm 설정·캐시·로그로 공식 레지스트리를 조회하고 활성 세션이나 불완전한 프로세스 관측을 거절합니다. 설치하거나 프로세스에 신호를 보내지 않습니다. [명령과 거절 사유의 전체 설명(영문)](/reference/cli/agents/#read-only-codex-cli-update-plan)을 참고하세요. Windows x64 설치 관측은 [`attest` 명령](/ko/reference/cli/agents/)을 참조하세요. 명시적 경로 없이 증명에 바인딩된 런처 스냅샷이 식별한 선택 후보를 관측하며, 업데이트 권한이나 런타임 선택을 증명하지 않습니다. diff --git a/docs-site/src/content/docs/ko/reference/cli/agents.md b/docs-site/src/content/docs/ko/reference/cli/agents.md index 545e1e4a790..c8bb7e53731 100644 --- a/docs-site/src/content/docs/ko/reference/cli/agents.md +++ b/docs-site/src/content/docs/ko/reference/cli/agents.md @@ -88,6 +88,8 @@ ocx route combo set reliable --targets ark/model-a:2,openai/gpt-5.5 ## 관측성과 디버그 +`ocx usage --top 25` (`ocx observe usage --top 25`도 동일)는 사람이 읽는 모델 표의 행 수를 1~1000 정수로 지정합니다. 기본값은 10이며 `--search` 사용 시 100입니다. 검색 필터링 후 로컬 표시에만 적용되며 합계, 제공자/계정 행, API 매개변수는 바뀌지 않습니다. `--json`은 유효한 `--top`을 무시하고 기존 전체 보고서 또는 100행 검색 뷰를 유지합니다. 잘못된 값은 요청 전에 종료 코드 2를 반환합니다. + `ocx logs filter --json`은 최근 로그 한 구간을 읽고 조건에 맞는 행을 고릅니다. `--scan-limit`은 읽을 행 수, `--limit`은 출력할 행 수입니다. 결과가 비어 있어도 전체 기록에 일치 항목이 없다는 뜻은 아닙니다. `ocx usage --search `는 모델 행만 검색하고 보고서의 총계와 조회 권한 범위는 유지합니다. `ocx companion usage --json`은 저장된 모델·공급자 표시 설정으로 오늘과 30일 사용량을 읽습니다. 한 구간이 실패하면 다른 구간은 남기고 `partial: true`와 종료 코드 1을 반환합니다. 설정 기본값을 사용했다면 `settingsFallback`, 손상된 설정의 기본값이라면 `settingsCorrupt`도 확인하세요. 자세한 옵션과 응답은 [영문 CLI 기준 문서](/reference/cli/agents/#filter-a-bounded-log-snapshot)를 참고하세요. @@ -99,7 +101,7 @@ ocx route combo set reliable --targets ark/model-a:2,openai/gpt-5.5 | 별칭 | 대응 리소스 | | --- | --- | | `ocx logs [filters] [--follow] [--json|--jsonl]` | `ocx observe logs` | -| `ocx usage [--range ] [--surface ] [--provider ] [--model ] [--json]` | `ocx observe usage` | +| `ocx usage [--range ] [--surface ] [--provider ] [--model ] [--search ] [--top <1-1000>] [--json]` | `ocx observe usage` | | `ocx storage [--json]` | `ocx observe storage` | | `ocx memory [--json]` | `ocx observe memory` | @@ -235,9 +237,14 @@ opencode는 `{env:OPENCODEX_OPENCODE_API_KEY}`를 보간합니다. opencodex가 헤드리스 런타임 설정, 시작, 동기화, 진단, 업데이트를 관리합니다. `ocx system codex-restart --yes`는 `ocx sync --restart-codex`와 같은 모듈로 Codex -app-server를 재시작하고 데스크톱 앱도 완전히 종료한 뒤 다시 띄웁니다. 프록시 자체가 -Codex 앱 안에서 실행 중이면 넘길 수 없는 handoff를 약속하지 않고, 대신 실행 가능한 -안내와 함께 거절합니다. +app-server를 재시작합니다. 모델 선택기는 앱 실행 시점에 만들어지므로, 데스크톱 앱은 현재 +모델 카탈로그가 기록되기 전에 실행된 경우나 이 요청으로 `model_catalog_json` 경로가 바뀐 경우 완전히 종료한 뒤 다시 띄웁니다. 그렇지 않으면 +앱은 열린 채로 자체 app-server를 다시 띄우고, 보내지 않은 초안과 대기 중인 승인을 유지합니다. +앱이나 카탈로그 상태를 읽을 수 없으면 전체 재시작을 수행합니다. 프록시 자체가 Codex 앱 안에서 +실행 중이면 데스크톱 재시작은 넘길 수 없는 handoff를 약속하지 않고, 대신 실행 가능한 안내와 +함께 거절합니다. + +이 요청으로 `model_catalog_json` 경로가 바뀌면 셸이 카탈로그 파일 작성 후 시작되었어도 데스크톱을 재시작합니다. ```bash ocx system settings --stream-mode eager-relay @@ -249,7 +256,7 @@ ocx system settings --stream-mode eager-relay ocx system codex-cli-update check --json ``` -`check`는 패키지 레지스트리를 조회하지 않고, 설정된 설치 후보에 대해 전체 경로를 숨긴 실행 파일 위치와 소유권 근거를 포함한 provenance 정보를 제한된 범위에서 검사합니다. 신뢰할 수 있는 배포 런처 컨텍스트가 인증하는 것은 후보 스냅샷뿐이며, Codex가 성공적으로 실행되었다는 사실은 인증하지 않습니다. 이 단발성 명령은 Codex를 전혀 실행하지 않으므로 환경 또는 저장된 상태에서 얻은 후보는 보고 전용입니다(`managed: false`, 일반적으로 `selection_unattested`). `selectionAttested`는 항상 `false`입니다. JSON 출력에는 `candidateAvailable`, `candidateVersion`, `candidateSource`, `selectionAttested: false`가 포함됩니다. Bun이나 소스에서 직접 실행하면 런처 증거가 없으므로 환경 및 저장된 후보를 무시하고 POSIX에서는 `candidate_unavailable`을 보고할 수 있습니다. Windows에서는 이 첫 조각이 후보 또는 설정 경로의 파일시스템을 전혀 읽지 않습니다. 배포 런처가 증명한 절대 환경 후보에 한해서 앱 번들 또는 버전 관리자라는 어휘적 표지만 보고하며, 그 밖의 Windows 후보는 모두 실패 닫힘 처리합니다. 이 조각은 저장된 선택 상태를 전혀 읽지 않으므로, 환경 후보가 캡처되지 않은 Windows 실행은 `candidate_unavailable`이 아니라 `windows_inspection_deferred`를 보고합니다. 명령이 Codex CLI 설치 여부를 관측할 수 없으므로, 후보가 없다고 단정하는 대신 검사가 연기되었음을 보고합니다. 이 명령은 Codex나 패키지 관리자를 실행하거나 shim을 복구하지 않고, 설정 또는 캐시 상태를 쓰거나 프로세스를 중지하거나 어떤 것도 설치하지 않습니다. 앱에 포함된 후보, 인식된 버전 관리자의 후보, 검증되지 않은 독립 실행형 후보, shim 상태가 모호한 후보는 관리 대상이 아니거나 알 수 없는 것으로 보고되며, 관리 대상으로 분류되지 않습니다. +`check`는 레지스트리를 조회하거나 Codex를 실행하지 않습니다. POSIX에서는 배포 런처가 캡처한 환경 후보와 런타임 리졸버가 선택한 정규 경로가 같으면 `selectionAttested: true`가 될 수 있습니다. 연결된 npm-global 패키지 매니페스트가 소유권을 증명하고, 후보 버전이 보고된 경우 그 버전이 매니페스트 버전과 일치하면 `managed: true`가 될 수 있으며 저장된 후보는 인증되지 않습니다. Windows는 후보나 설정 파일을 읽지 않고 검사를 연기합니다. 직접 Bun/소스 실행은 런처 증거가 없어 후보를 신뢰하지 않습니다. `plan`은 격리된 임시 npm 설정·캐시·로그로 공식 레지스트리를 조회하고 활성 세션이나 불완전한 프로세스 관측을 거절합니다. 설치하거나 프로세스에 신호를 보내지 않습니다. [명령과 거절 사유의 전체 설명(영문)](/reference/cli/agents/#read-only-codex-cli-update-plan)을 참고하세요. Windows에서 `CODEX_CLI_PATH=codex` 같은 단순 명령 이름이나 원격 경로·장치 경로가 후보로 캡처되면 `candidate_path_unavailable`을 보고합니다. 후보는 캡처됐지만 해당 경로가 이 검사 대상에 적합하지 않은 경우입니다. diff --git a/docs-site/src/content/docs/ko/reference/cli/lifecycle.md b/docs-site/src/content/docs/ko/reference/cli/lifecycle.md index aa2e4a8db43..23105712070 100644 --- a/docs-site/src/content/docs/ko/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/ko/reference/cli/lifecycle.md @@ -44,7 +44,7 @@ ocx start --socks5-off ### `ocx stop` -실행 중인 프록시를 PID 기준으로 중지하고, PID 파일을 삭제한 뒤 기본 Codex를 복원합니다. 관리형 +실행 중인 프록시를 PID 기준으로 중지하고 PID 파일을 삭제합니다. 독립 실행 소유자라면 기본 Codex를 복원합니다. 관리형 백그라운드 서비스가 설치되어 있으면 `ocx stop`이 먼저 그 서비스를 중지하므로 프록시가 다시 올라올 수 없습니다. 웹 대시보드의 **Stop** 버튼도 같은 동작(`POST /api/stop`)을 하지만, Windows 작업 스케줄러는 예외입니다. 작업이 끝나도 래퍼가 프록시를 다시 띄울 수 있어서, 대시보드는 `respawnable_service`로 거절하고 아무것도 바꾸지 않은 채 `ocx stop` 실행을 안내합니다. @@ -54,6 +54,12 @@ ocx start --socks5-off 넘어가지 않습니다. 영수증에 근거한 지연 복원도 최종 복원과 영수증 정리를 부모가 담당하며, 부모의 공유 설정 복원이 실패하면 종료 실패로 남기고 미완료 영수증을 보존합니다. +연결된 hub 클라이언트나 Remote Link Child에서는 성공한 API 종료를 포함해 `ocx stop`이 연결 소유의 Codex 라우팅, 모델 카탈로그와 Grok 설정을 보존합니다. `ocx start`는 라우팅을 다시 주입하지 않고 보존한 설정으로 서비스를 재개합니다. Codex를 자체 계정으로 돌리려면 `ocx disconnect`를, 이미 기본 라우팅으로 복원했다면 다시 연결하려고 `ocx sync --restart-codex`를 실행하세요. + +보존 기능을 확인할 수 없는 오래된 일반 프록시는 서비스나 프로세스를 중지하기 전에 거절합니다. 런타임을 관리하는 앱이나 서비스에서 업데이트·재시작한 뒤 다시 시도하세요. + +이미 중지된 클라이언트는 서비스 관리자가 비활성이고 알려진 로컬 주소에 런타임이 없음을 확인한 뒤 정상적인 미실행 결과를 반환합니다. + ### `ocx restart` 프록시가 실행 중이면 확인된 정확한 PID와 포트에 in-place 재시작을 요청하고, 정상 드레인을 @@ -106,7 +112,7 @@ dedicated-provider history도 포함됩니다. 상태를 백업하고 이 전체 ### `ocx recover-history --ocx-compaction --yes` -라우팅된 provider를 통해 압축된 작업을 native Codex에서 다시 열기 전에 해당 기록을 복구합니다. 이 명령은 UUID로 정확히 하나의 작업을 선택하고 비공개 바이트 단위 백업을 저장한 뒤, OpenCodeX가 소유한 `ocx1:` 압축 상태만 native Codex가 재생할 수 있는 일반 요약으로 변환합니다. native 암호화 콘텐츠와 다른 작업은 변경하지 않습니다. 실행 전에 선택한 작업을 닫으십시오. 처리 중 rollout이 변경되면 파일을 교체하지 않고 복구를 중단합니다. +라우팅된 provider를 통해 압축된 작업을 native Codex에서 다시 열기 전에 해당 기록을 복구합니다. 이 명령은 UUID로 정확히 하나의 작업을 선택하고 비공개 바이트 단위 백업을 저장한 뒤, OpenCodeX가 소유한 `ocx1:`/`ocx2:` 압축 상태만 native Codex가 재생할 수 있는 일반 요약으로 변환합니다. native 암호화 콘텐츠와 다른 작업은 변경하지 않습니다. 실행 전에 선택한 작업을 닫으십시오. 처리 중 rollout이 변경되면 파일을 교체하지 않고 복구를 중단합니다. ### `ocx uninstall` · `ocx remove` @@ -209,7 +215,7 @@ ocx status --json 인증이 필요 없는 `GET /readyz` 엔드포인트로 동기화 후 준비 상태를 확인합니다. 준비되면 `200`, `pending` 또는 종단 상태인 `failed`이면 `Retry-After: 1`과 함께 `503`을 반환합니다. HTTP의 정제된 -식별 필드는 `{service, version, uptime, pid, port, status, protocol, minimumClientProtocol, managementUrl}`입니다. `protocol`은 허브의 현재 원격 프로토콜, `minimumClientProtocol`은 호환되는 최소 클라이언트 프로토콜, `managementUrl`은 브라우저에서 보이는 표준 관리 origin입니다. `/readyz`가 없는 이전 프록시는 +식별 필드는 `{service, version, uptime, pid, port, status, protocol, minimumClientProtocol, managementUrl, features}`입니다. `protocol`은 허브의 현재 원격 프로토콜, `minimumClientProtocol`은 호환되는 최소 클라이언트 프로토콜, `managementUrl`은 브라우저에서 보이는 표준 관리 origin입니다. `/readyz`가 없는 이전 프록시는 `unreachable`로 fail-closed하며, `/healthz`는 준비 상태가 아닌 별도의 liveness 확인입니다. 기본값은 한 번의 probe이며, `--wait`는 준비 또는 timeout까지 polling하지만 종단 `failed`를 확인하면 즉시 종료합니다. 기본 timeout은 45초이며, `--timeout `는 `--wait`와 함께 써야 하고 양의 정수인 1~300초 범위를 받습니다. CLI JSON은 @@ -238,6 +244,10 @@ single-flight/lock 파일을 만들 수 있는지, 건강하지 않은 OAuth 또 ### `ocx sync [--restart-codex] [--restart-app-server-only]` +> **Remote Link Child:** `ocx sync`는 Home의 Codex 설정도 적용하며, 클라이언트 런타임이 그 적용보다 먼저 시작되었으면 재시작 플래그 없이도 런타임을 재시작합니다. 재시작이 실패하면 이전 런타임이 계속 서비스하고 명령은 0이 아닌 코드로 끝납니다. `ocx status --json`과 `ocx link status --json`의 관련 필드는 [Remote Link apply parity](/reference/cli/lifecycle/#remote-link-apply-parity)(영문)를 참고하세요. + +재시작 플래그를 명시하면 카탈로그와 캐시 바이트가 같아도 `config.toml` 변경이 성공적으로 커밋된 뒤 재시작합니다. 설정만 다루는 작업이 변경 없음, 거부, 사전 검증 또는 롤백으로 끝나면 재시작하지 않습니다. 재시작 플래그가 없으면 sync는 Codex를 자동으로 재시작하지 않습니다. + 설정된 모든 공급자에서 라이브 모델 목록을 가져와 병합된 카탈로그를 Codex에 다시 주입합니다. 공급자를 추가한 뒤나 사용 가능한 모델을 새로 고칠 때 실행합니다. @@ -260,6 +270,8 @@ app-server / code-mode-host 프로세스에만 `SIGTERM`을 보내고 데스크 명령을 Codex 앱 안에서 실행하면 재시작은 분리된 helper에 넘기고, 이 세션은 앱과 함께 종료됩니다. +**대상을 고정한 동기화.** `ocx sync --expect-link `(Remote Link Child)와 `ocx sync --expect-hub-key `(hub 클라이언트)는 이 머신이 여전히 그 링크나 그 키로 연결되어 있을 때만 동기화합니다. 그렇지 않으면 아무것도 바꾸지 않고 로컬 공급자도 동기화하지 않으며, 종료 코드 5로 끝나고 stderr 마지막 줄에 `{"phase":"child_link_mismatch"|"hub_client_mismatch","reason":...}`를 출력합니다. 형식이 잘못되었거나 반복된 플래그는 종료 코드 2로 끝납니다. 자세한 내용은 [영어 레퍼런스](/reference/cli/lifecycle/#sync-only-from-the-expected-home---expect-link-and---expect-hub-key)를 참고하세요. + ### `ocx sync-cache [--restart-codex] [--restart-app-server-only]` Codex의 로컬 모델 선택기 캐시를 무효화하여, 활성 opencodex 카탈로그에서 다시 빌드되게 합니다. diff --git a/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md index a60a7f21685..a0dae108083 100644 --- a/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md @@ -241,12 +241,28 @@ Codex pool selection applies to the next request after clearing existing affinit ### `ocx account list [provider] [--json] [--all] [--quota [--refresh]]` +저장된 ChatGPT 풀 계정은 네이티브 Codex 홈이 없는 호스트에서도 대시보드와 계정 새로 고침을 +포함해 계속 나열할 수 있습니다. 목록 조회는 Codex 홈을 만들거나 네이티브 메인 계정에서 +로그아웃하지 않습니다. Codex 클라이언트 통합을 비활성화해도 기존 네이티브 로그인의 할당량 +관측은 비활성화되지 않습니다. 명시적으로 지정한 `CODEX_HOME` 경로가 유효하지 않으면 계속 +설정 오류를 반환합니다. `OPENCODEX_HOME` 저장소가 안전하지 않으면 네이티브 Codex 홈이 +없어도 오류를 반환합니다. +기본 Codex 홈의 경로를 확인할 때도 파일, 대상이 없는 심볼릭 링크, 탐색할 수 없는 상위 디렉터리는 오류를 발생시킵니다. + 제공자를 지정하지 않으면 Codex 풀, OAuth 계정, 설정된 API 키 풀을 나열합니다. `--all`이 없으면 비어 있는 제공자는 건너뜁니다. 제공자를 지정하면 해당 자격 증명 계열만 나열합니다. 사람이 보는 출력은 `PROVIDER TYPE ID PLAN/LABEL PRIORITY STATUS` 형식을 사용하며, 수동으로 선택한 Codex 행에는 `selected`가 표시됩니다. 사용 가능한 Kiro 계정이 두 개 이상 저장되어 있으면 기본적으로 429 응답 시 다른 계정으로 자동 전환하며, 알려진 잔여 할당량이 가장 많은 계정을 우선합니다. 이 전환은 계정 존재만으로 활성화되며 끌 수 없습니다. `oauthAccountFailover.enabled: false`는 429 복구가 아니라 요청을 보내기 전 계정을 고르는 동작만 거부합니다. `ocx account login kiro`는 계정을 한 번에 하나씩 풀에 추가합니다. 빈 결과도 성공입니다. `--json`은 다음을 반환합니다: ```text { accounts: AccountRow[], notes: string[] } ``` +Codex JSON 행에는 등록된 `id`와 같은 `slotId`, 그리고 `native-main` 또는 `pool`인 +`slotKind`가 포함됩니다. 프록시가 상위 계정과 사용자 식별값을 모두 확인하면 +`uai1_` 뒤에 소문자 16진수 64자리가 붙는 `upstreamAccountKey`도 제공합니다. +같은 `openai` 제공자에서 키가 같으면 한 상위 사용자의 슬롯이며, 각 슬롯과 한도 상태는 +그대로 유지해야 합니다. 키는 이 설치 환경에 한정되고 관리자 토큰이 바뀌면 달라질 수 +있습니다. 키가 없을 때는 가려진 이메일이나 한도가 비슷하다는 이유로 묶지 않습니다. +상위 계정 ID·사용자 식별값·인증 정보 원문은 목록에 포함되지 않습니다. + ### `ocx account current [--json]` 활성 계정이나 키를 보여줍니다. 수동 고정이 없는 Codex 풀은 우선순위를 고려한 자동 선택을 보고합니다. 가장 우선순위가 높은 적격 tier를 고르고, 그 tier 안에서 할당량 라우팅 기준으로 가장 적게 사용한 항목을 선택합니다. 활성 자격 증명이 없는 다른 계열은 그 상태를 보고하고도 종료 코드 0으로 끝납니다. `--json`은 다음을 반환합니다: diff --git a/docs-site/src/content/docs/ko/reference/configuration/advisor.md b/docs-site/src/content/docs/ko/reference/configuration/advisor.md index d0539ae96e1..0e1b4d940fe 100644 --- a/docs-site/src/content/docs/ko/reference/configuration/advisor.md +++ b/docs-site/src/content/docs/ko/reference/configuration/advisor.md @@ -1,6 +1,6 @@ --- title: 어드바이저 -description: OpenCodex가 소유한 전문가 상담 사이드카 — 구성된 전문가 모델이 라우팅된 워커에게 조언을 반환하며, manual과 preflight 정책을 제공합니다. +description: manual, preflight 및 증거 기반 adaptive 정책을 제공하는 독립 전문가 상담. --- 어드바이저는 워커의 작업을 검토하고 조언을 반환하는 독립 전문가 모델입니다. 상담은 OpenCodex가 엔드투엔드로 소유합니다. 프록시가 워커 턴에 합성 `advisor` 도구를 주입하고, 정상적인 라우팅 권위를 통해 상담을 직접 실행하며, 조언을 재주입해 원래 워커가 계속 진행하게 합니다. 워커는 위임하지 않고, 아무것도 spawn하지 않으며, 제공자 자격 증명도 가지지 않습니다. @@ -9,6 +9,8 @@ description: OpenCodex가 소유한 전문가 상담 사이드카 — 구성된 ## 구성 +대시보드 **어드바이저** 페이지에서 카탈로그 선택기로 전문가 모델을 선택하세요. 네이티브, 공급자, 계정 지정, 사용자 정의 및 콤보 선택자의 정확한 카탈로그 식별자를 저장합니다. 비활성 모델과 초기 선택 대기 모델은 새 선택에 표시하지 않습니다. 선택 가능한 목록에 없는 저장된 식별자는 경고와 함께 유지되며, 다른 모델을 선택하거나 지우기 전까지 변경되지 않습니다. 카탈로그 로드 실패 시 현재 선택을 보존하고 재시도를 제공합니다. 텍스트 조언을 반환할 수 있는 모델을 선택하세요. 카탈로그가 상담 호환성을 보장하지는 않습니다. CLI와 API는 라우터가 지원하는 모델 문자열을 계속 허용합니다. + ```json { "advisor": { @@ -26,11 +28,12 @@ description: OpenCodex가 소유한 전문가 상담 사이드카 — 구성된 | `enabled?` | `boolean` | `false` | 마스터 스위치. 비활성화 시 요청 경로에 어드바이저 동작이 전혀 없습니다. | | `model?` | `string` | — | 전문가 모델. 라우터가 허용하는 모든 모델 문자열: 네이티브 모델(`gpt-6-astra`), 명시적 `provider/model`(`anthropic/claude-sonnet-4-6`, `xai/grok-...`), 계정 한정 네이티브 모델. 크로스 프로바이더를 완전히 지원합니다. | | `effort?` | `string` | `"max"` | 어드바이저 호출의 추론 강도(`low`~`ultra`). | -| `policy?` | `"manual" \| "preflight"` | `"manual"` | 상담 시점. | +| `policy?` | `"manual" \| "preflight" \| "adaptive"` | `"manual"` | 상담 시점. | +| `maxConsultationsPerTask?` | `integer` | `3` | 적응형 작업 상담 한도(1–3회 시도, 수동 호출 포함). | | `timeoutMs?` | `number` | `120000` | 루프백 상담 타임아웃. | | `contextSharingConsent?` | `"v1"` | 없음 | 설정된 어드바이저 프로바이더로 작업 맥락을 보내는 운영자 동의. 현재 값은 `"v1"`뿐입니다. 없거나, 오래되었거나, 다른 값이면 작업 내용을 보내지 않습니다. `enabled: true`만으로는 동의가 아닙니다. | -대시보드 **Advisor** 페이지 또는 `ocx advisor status|on|off|set --model --effort --policy `로 관리합니다. +대시보드 **Advisor** 페이지 또는 `ocx advisor status|on|off|set --model --effort --policy `로 관리합니다. 현재 동의가 없으면 `ocx advisor on`은 프로바이더 간 전송을 켜지 않습니다. 고지를 출력하고 멈춥니다. `ocx advisor on --ack-context-sharing`와 `ocx advisor consent`가 `v1`을 기록합니다. `ocx advisor consent --revoke`는 동의를 지우고 전송을 즉시 멈춥니다. `ocx advisor set`은 동의를 부여하지 않습니다. 대시보드의 동의 확인란은 미리 선택되지 않습니다. @@ -78,5 +81,13 @@ OpenCodex는 프로바이더 API 키, Authorization 헤더, OAuth 토큰, 백엔 ## PR1 제한 - 네이티브 OpenAI 패스스루 턴(ChatGPT 풀 워커)에는 합성 도구가 주입되지 않습니다. 어드바이저는 라우팅(번역) 제공자를 대상으로 합니다. preflight 상담은 run-turn 어댑터에 적용되지만 도구는 적용되지 않습니다. -- 적응형 트리거가 없습니다: 막힘 감지, 반복 실패 분석, 에스컬레이션 계층, 다중 어드바이저, 투표가 없습니다. `manual`과 `preflight`만 있습니다. +- 의미 기반 막힘 감지, 완료 검토, 단계별 에스컬레이션, 다중 전문가 또는 투표는 제공하지 않습니다. - preflight 중복 제거 원장은 프로세스 내에 있습니다. 프록시 재시작 후 진행 중인 작업은 preflight 상담을 한 번 더 받을 수 있습니다. + +## 증거 기반 적응형 상담과 활동 기록 + +`adaptive`는 초기 상담 외에 같은 작업 디렉터리의 같은 검증이 실패하고, 이후 파일 편집이 성공한 뒤 새 검증 실행도 실패했을 때 상담합니다. 검증 성공은 실패 상태를 지웁니다. 편집 전에 시작한 검증은 수정 후 재시도가 아닙니다. 초기 상담과 수정 실패 상담은 별도로 중복을 방지하며 자동 상담에는 안정적인 대화 식별자가 필요합니다. 짝지어진 도구 호출/결과와 명시적 종료 코드가 필요합니다. 복잡한 스크립트, 일괄 호출, shell 편집 및 모호한 출력은 트리거하지 않습니다. + +`ocx advisor set --policy adaptive --max-consultations 2`로 설정합니다. 수동 및 자동 시도는 작업별 1–3회 예산을 공유하며 전송 전에 `OPENCODEX_HOME/advisor-budget.sqlite`에 예약합니다. 프록시 재시작이나 Worker 변경으로 초기화되지 않습니다. 작업 다이제스트, 횟수, 만료 시간만 보관하며 첫 시도 후 24시간에 만료됩니다(최대 512개 작업). 예산을 사용할 수 없어도 Worker는 계속합니다. 전송된 실패·취소 호출도 비용이 발생할 수 있으므로 예약을 돌려주지 않습니다. 안정적인 식별자가 없으면 요청별 수동 상담만 가능합니다. + +Advisor 페이지와 `ocx advisor activity --json`(`GET /api/advisor/activity`)은 이 프로세스의 최근 24시간 상담 메타데이터를 최대 100개 표시합니다. 화면이 보일 때 5초마다 갱신합니다. 이유, 상태, 모델, 시간, 보고된 입력/출력 토큰, 요청 및 어댑터 매핑 추론값을 포함합니다. 작업 본문, 명령, 조언, 대화 식별자는 저장하지 않으며 재시작 시 사라집니다. 매핑은 전송 파라미터의 증거일 뿐 내부 추론을 보장하지 않습니다. 미보고 값은 알 수 없음으로 유지하며 정확한 비용이나 조언 채택을 추정하지 않습니다. diff --git a/docs-site/src/content/docs/ko/reference/configuration/providers.md b/docs-site/src/content/docs/ko/reference/configuration/providers.md index 6ece10e3718..6a0a43ef73b 100644 --- a/docs-site/src/content/docs/ko/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ko/reference/configuration/providers.md @@ -11,6 +11,16 @@ description: 공급자 항목, 인증, 엔드포인트, 모델 카탈로그, 할 이 추정은 기본적으로 꺼져 있습니다. 공급자별로 `inferContextWindow: true`를 설정해야 켜집니다. 설정하지 않으면 창이 없는 모델은 활성 Context cap, 그다음 128k를 사용합니다. +## GPT-6.1 Terra + +[GPT-6.1 Terra](https://openai.com/index/introducing-gpt-6-1-terra/)는 GPT-5.6 Terra의 후속 모델로 2026년 10월 26일 출시되었습니다. `gpt-6.1-terra`는 `gpt-5.6-terra`와 함께 제공하며 기본 모델이나 하위 에이전트 모델 목록은 바꾸지 않습니다. GitHub Copilot에서는 Responses를 사용합니다. OpenRouter ID는 `openai/gpt-6.1-terra`이며 컨텍스트는 1,050,000, 100만 토큰당 입력 $1.00 / 출력 $5.00입니다. + +네이티브 Codex: 기본 컨텍스트 500,000, 장문 상한 872,000, 기본 effort `low`, 단계는 `low`, `medium`, `high`, `xhigh`, `max`, `ultra`입니다. 네이티브 `ultra`는 전송 시 `max`로 변환합니다. 네이티브 행의 고속 모드는 Fast만 제공합니다. + +[OpenAI API](https://developers.openai.com/api/docs/models/gpt-6.1-terra): 컨텍스트 1,050,000, 최대 출력 128,000, 텍스트·이미지 입력과 텍스트 출력, 지식 기준일 2026-06-01입니다. effort는 `none`, `low`, `medium`(기본), `high`, `xhigh`, `max`입니다. 엔드포인트는 `v1/responses`와 `v1/chat/completions`이며 도구 호출에는 Responses가 필요합니다. + +100만 토큰당 입력 $1.00, 캐시 입력 $0.05, 캐시 쓰기 $1.25(30분), 출력 $5.00입니다. 입력이 272,000 토큰을 초과하면 요청 전체에 입력·캐시 2×, 출력 1.5×를 적용하여 각각 $2.00 / $0.10 / $2.50 / $7.50입니다. Fast 모드는 2×입니다. + ## 처음 등록할 때의 모델 선택 신규 비-OAuth 연결은 신뢰할 수 있는 모델 목록을 확보할 때까지 모델 노출을 보류합니다. Models 탭의 중복 없는 모델 행이 20개 이상이면 모델 스위치를 모두 OFF로 설정합니다. 프로바이더는 활성 상태를 유지합니다. 실제 인증 방식이 OAuth나 ChatGPT 로그인인 연결은 기존 기본값을 유지합니다. @@ -53,7 +63,7 @@ GUI에서 등록이나 OAuth 로그인을 마치면 Models 페이지로 이동 | `codexWsReuseAcrossTurns?` | `boolean` | `false` | 같은 계정과 스레드의 Codex WebSocket을 턴 사이에 재사용합니다. 기본값은 꺼짐입니다. 켜면 바쁜 소켓을 최대 750ms 기다리고, 모델이나 티어가 바뀌어도 소켓을 최대 2개 유지합니다. | | `upstreamHostCircuitThreshold?` | `number` | `0` | 네이티브 OpenAI forward Responses와 compact 전송에서 입증된 연결 전 DNS/TCP 실패에 적용하는 선택적 회로 차단 임계값입니다. `0`은 비활성화하며, `1`~`20`은 이 횟수만큼 최종 논리 요청이 실패하면 provider-origin을 30초 동안 차단합니다. 차단 중에는 계정 선택이나 업스트림 전송 전에 `Retry-After`가 포함된 `503`을 반환하고, 시간이 지나면 반개방 요청 하나만 허용합니다. 타임아웃과 HTTP 응답은 집계하지 않으며, HTTP 응답이 하나라도 오면 회로를 닫습니다. Codex Pool 라우팅에서 계정이 고정되지 않은 경우에만 적용되며, `codexAccountMode: "direct"` 및 계정 한정 선택자에서는 동작하지 않습니다. | | `modelCacheTtlMs?` | `number` | `300000` | 공급자별 `/models` 캐시의 최신성 창입니다. | -| `cacheRetention?` | `"none" \| "short" \| "long"` | `"short"` | Anthropic 프롬프트 캐시 정책입니다. 비활성, 5분짜리 임시, 1시간짜리 확장 중 하나입니다. | +| `cacheRetention?` | `"none" \| "short" \| "long"` | Anthropic 퍼스트파티: `"long"`, 그 외 Anthropic 호환 엔드포인트: `"short"` | Anthropic 프롬프트 캐시 정책입니다. 비활성, 5분짜리 임시, 1시간짜리 확장 중 하나입니다. 설정하지 않으면 `api.anthropic.com`(API 키 또는 OAuth 계정 풀) 요청은 1시간 TTL을 쓰고, 호환 게이트웨이와 벤더 엔드포인트는 5분 TTL을 유지합니다. 명시한 값이 항상 우선합니다. | | `tokenGuardian?` | `OcxTokenGuardianConfig` | 꺼짐 | 선택적 선제 OAuth 갱신과 Codex 계정 워밍업 정책입니다. | 각 전략의 계정별 유효 임계값은 `codexAccountAutoSwitchThresholds` 항목이 있으면 그 값이며, 없으면 전역 `autoSwitchThreshold`입니다. 0은 사용량 기반 사전 전환만 비활성화합니다. 시작 바인딩, 하드 잠금, 쿨다운, 모델 사용 권한 검사와 장애 복구는 계속 적용됩니다. @@ -89,7 +99,9 @@ managed map을 활성화하면 privacy-safe selector를 만들고, 이후 계정 | --- | --- | --- | | `adapter` | `string` | `openai-chat`, `openai-responses`, `anthropic`, `google`, `kiro`, `cursor`, `ollama-native`, `azure-openai` 중 하나이며, `azure`는 별칭입니다. | | `baseUrl` | `string` | 상위 API 기본 URL입니다. 대부분의 내장 고정 엔드포인트는 불일치를 무시합니다. 충돌 안전 키 프리셋은 같은 이름의 이전 사용자 지정 목적지를 보존합니다. | -| `requestPacing?` | `{ enabled, requestsPerMinute?, minIntervalMs?, maxConcurrentRequests?, models? }` | 업스트림 사용량, 과금, rate-limit 지표와 별개인 선택적 클라이언트 측 아웃바운드 요청 시작 속도 조절입니다. `maxConcurrentRequests`는 진행 중 요청 수를 제한하는 양의 정수이며 provider 또는 model 규칙에서 단독으로 사용할 수 있습니다. Provider 제한은 모든 모델에 적용되고 `models` 항목은 정확한 업스트림 모델 ID와 일치하며 지연을 늘리거나 동시성을 줄일 수 있습니다. 큐 대기는 응답 헤더 타임아웃을 소모하지 않습니다. HTTP 및 명시적 어댑터 `fetchResponse`/`runTurn` 전송을 포함합니다. 동시성 제한이 있으면 표준 Responses WebSocket 턴은 응답 본문의 완료·오류·취소 때 점유 슬롯을 해제할 수 있도록 HTTP/SSE를 사용합니다. Cursor를 포함한 `runTurn` 어댑터에서는 물리적 전송 수가 아니라 진행 중인 턴 수를 제한합니다. 같은 턴의 RunSSE와 BidiAppend는 겹칠 수 있지만 다른 턴은 대기합니다. 후속 전송에도 시작 간격은 적용됩니다. | +| `requestPacing?` | `{ enabled, requestsPerMinute?, minIntervalMs?, maxConcurrentRequests?, models?, keySelection?, keys? }` | 업스트림 사용량, 과금, rate-limit 지표와 별개인 선택적 클라이언트 측 아웃바운드 요청 시작 속도 조절입니다. `maxConcurrentRequests`는 진행 중 요청 수를 제한하는 양의 정수이며 provider 또는 model 규칙에서 단독으로 사용할 수 있습니다. Provider 제한은 모든 모델에 적용되고 `models` 항목은 정확한 업스트림 모델 ID와 일치하며 지연을 늘리거나 동시성을 줄일 수 있습니다. 큐 대기는 응답 헤더 타임아웃을 소모하지 않습니다. HTTP 및 명시적 어댑터 `fetchResponse`/`runTurn` 전송을 포함합니다. 동시성 제한이 있으면 표준 Responses WebSocket 턴은 응답 본문의 완료·오류·취소 때 점유 슬롯을 해제할 수 있도록 HTTP/SSE를 사용합니다. Cursor를 포함한 `runTurn` 어댑터에서는 물리적 전송 수가 아니라 진행 중인 턴 수를 제한합니다. 같은 턴의 RunSSE와 BidiAppend는 겹칠 수 있지만 다른 턴은 대기합니다. 후속 전송에도 시작 간격은 적용됩니다. | +| `requestPacing.keySelection?` | `"active"`, `"round-robin"` | 정상 요청의 키 선택 방식입니다. 기본 `active`는 저장된 키와 기존 오류 전환을 유지하며, `round-robin`은 저장된 활성 키를 변경하지 않고 사용 가능한 풀 키를 요청별 순서대로 배정합니다. 토큰이나 비용이 아니라 배정 횟수를 분산합니다. | +| `requestPacing.keys?` | `{ requestsPerMinute?, minIntervalMs?, maxConcurrentRequests? }` | 키 참조별 동일한 RPM·최소 시작 간격·동시 요청 제한입니다. 모든 모델에서 공유하며 프로바이더·정확한 모델 상한과 함께 적용합니다. 중복 참조는 한 슬롯을 공유하지만 같은 업스트림 프로젝트의 다른 참조가 독립 할당량을 뜻하지는 않습니다. TPM·일일 한도는 처리하지 않습니다. | | `responsesPath?` | `string` | 키 인증 `openai-responses` 요청의 상대 리소스 경로입니다. 반드시 `/`로 시작해야 하며 스킴, query, fragment를 포함하면 안 됩니다. | | `chatCompletionsPath?` | `string` | `openai-chat` 요청의 상대 리소스 경로로, `responsesPath`와 동일한 형식 규칙이 적용되는 대응 항목입니다. 하나의 업스트림이 Chat Completions와 Responses를 서로 다른 접두사로 제공할 때 필요합니다. 모델별 wire override는 어댑터만 바꾸고 `baseUrl`은 그대로 두므로, 이 설정이 없으면 옵트인된 Chat 요청이 Responses base로 전송됩니다. Z.AI가 제공되는 예시입니다. | | `upstreamWebsocket?` | `boolean` | `openai-responses` 요청에 대한 업스트림 Responses WebSocket 전송을 선택적으로 활성화합니다(기본값 `false`). 퍼스트파티 `https://api.openai.com/v1` 업스트림에서만 적용되며, 사용자 지정 공급자 엔드포인트는 항상 제한된 HTTP/SSE를 사용합니다. Bun은 전체 메시지를 할당하기 전에는 수신 WebSocket 메시지 크기 제한을 적용할 수 없기 때문입니다. 정식 ChatGPT `openai` 공급자에서는 생략하면 대상 턴에서 업스트림 WebSocket을 사용하고, `false`는 스트리밍 턴을 HTTP/SSE로 전송하며, `true`는 거부됩니다. `false`이면 네이티브 턴 중 스티어링과 주입을 사용할 수 없습니다. 이 필드는 클라이언트 측 `websockets` 설정과 독립적이며 엔드포인트와 자격 증명을 변경하지 않습니다. HTTP 기본 URL은 SSE를 유지하고, Responses가 아닌 경로와 `openai-chat` 요청은 HTTP를 사용합니다. | @@ -112,7 +124,7 @@ managed map을 활성화하면 privacy-safe selector를 만들고, 이후 계정 | `modelAutoCompactTokenLimits?` | `Record` | 모델별 양의 안전 정수형 소프트 자동 압축 예산입니다. 유효한 컨텍스트 또는 최대 입력의 90% 한도를 낮출 수만 있으며, 유효 컨텍스트 창을 결정할 수 없으면 내보내지 않습니다. canonical `openai`에서는 키가 공급자나 계정 선택자 접두사가 없는 정확한 지원 네이티브 모델 ID여야 합니다. 공급자 PATCH는 항목을 병합하며, 키를 `null`로 지정하면 해당 키를 삭제하고 필드 전체를 `null`로 지정하면 맵을 지웁니다. 이 `null` tombstone은 PATCH에서만 사용할 수 있습니다. | | `defaultMaxOutputTokens?` | `number` | 클라이언트가 `max_output_tokens`를 생략했을 때 쓰는 공급자 전반의 `openai-chat` 폴백입니다. | | `modelMaxOutputTokens?` | `Record` | 양수 모델별 `openai-chat` 폴백 예산입니다. 정확한 일치와 패턴 일치가 공급자 기본값보다 우선합니다. | -| `modelCosts?` | `Record` | 모델별 표시 가격(100만 토큰당 USD). 해당 공급자의 정확한 업스트림 모델 ID를 키로 사용하며(공급자 식별자나 라우팅된 `provider/model` 레이블이 아님) 값은 `input`, `output`, `cacheRead`, `cacheWrite` 네 필드입니다(예: `{ "deepseek-v4-flash": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 } }`). 커스텀 공급자는 `openai-chat` 어댑터로 임의의 OpenAI 호환 엔드포인트를 대상으로 할 수 있으며, 내장 카탈로그에 없는 로컬·내부 공급자 ID도 유효합니다. 사용자 구성 가격은 Logs `~$` 및 Usage 추정에서 내장 카탈로그보다 우선합니다. 기존 항목도 현재 오버레이로 다시 계산되므로 가격을 편집하면 과거 합계가 바뀔 수 있습니다(폴백 순서: 사용자 설정 → jawcode 카탈로그 → expected-price 오버레이 → 모델별 벤더 가격). 사용자가 모든 요율을 명시적으로 0으로 설정하면 비용을 0으로 추정합니다. 자동 가격으로 되돌리려면 해당 모델 항목을 삭제하세요. 카탈로그의 전부 0인 요율은 계속 다음 소스로 폴백합니다. 각 요율은 0 이상의 유한한 숫자이며 최대 1,000,000(100만 토큰당 USD)입니다. 범위를 벗어난 행은 관리 경계에서 거부되고 로드 시 삭제됩니다. 표시 전용 추정이며 라우팅·계정 선택·할당량·청구에는 영향을 주지 않습니다. | +| `modelCosts?` | `Record` | 모델별 표시 가격(100만 토큰당 USD). 해당 공급자의 정확한 업스트림 모델 ID를 키로 사용하며(공급자 식별자나 라우팅된 `provider/model` 레이블이 아님) 값은 `input`, `output`, `cacheRead`, `cacheWrite` 네 필드입니다(예: `{ "deepseek-v4-flash": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 } }`). 커스텀 공급자는 `openai-chat` 어댑터로 임의의 OpenAI 호환 엔드포인트를 대상으로 할 수 있으며, 내장 카탈로그에 없는 로컬·내부 공급자 ID도 유효합니다. 사용자 구성 가격은 Logs `~$` 및 Usage 추정에서 내장 카탈로그보다 우선합니다. 기존 항목도 현재 오버레이로 다시 계산되므로 가격을 편집하면 과거 합계가 바뀔 수 있습니다(폴백 순서: 사용자 설정 → jawcode 카탈로그 → expected-price 오버레이 → 모델별 벤더 가격). 사용자가 모든 요율을 명시적으로 0으로 설정하면 비용을 0으로 추정합니다. 자동 가격으로 되돌리려면 해당 모델 항목을 삭제하세요. 카탈로그의 전부 0인 요율은 계속 다음 소스로 폴백합니다. 각 요율은 0 이상의 유한한 숫자이며 최대 1,000,000(100만 토큰당 USD)입니다. 범위를 벗어난 행은 관리 경계에서 거부되고 로드 시 삭제됩니다. 표시 전용 추정이며 라우팅·계정 선택·할당량·청구에는 영향을 주지 않습니다. | | `headers?` | `Record` | 추가 상위 헤더입니다. Authorization, cookies, API-key 헤더, 내장 개행, 잘못된 이름은 허용하지 않습니다. | | `openRouterRouting?` | `OpenRouterProviderRouting` | 기본 OpenRouter `order`, `only`, `allowFallbacks` 선호도입니다. 정식 OpenRouter와 `openai-chat`에서만 유효합니다. | | `modelOpenRouterRouting?` | `Record` | 공급자 전반의 OpenRouter 선호도를 덮어쓰는 정확한 모델 id별 재정의입니다. | @@ -124,7 +136,7 @@ managed map을 활성화하면 privacy-safe selector를 만들고, 이후 계정 | `modelReasoningEfforts?` | `Record` | 모델별 레이블입니다. 빈 목록이면 effort 제어를 숨깁니다. | | `modelSupportsReasoningSummaries?` | `Record` | 모델을 `false`로 두면 summary 광고를 멈추고 summary 전달 필드를 제거합니다. | | `modelReasoningSummaryDelivery?` | `Record` | 모델별 Responses 전달 enum입니다. 기존 delivery 필드를 다시 씁니다. | -| `modelAdapters?` | `Record` | 혼합 와이어 게이트웨이를 위한 모델별 `openai-chat` 또는 `openai-responses` 와이어 재정의입니다. 명시적 항목이 레지스트리 기본값보다 우선합니다. DeepSeek 프리셋은 `deepseek-v4-flash`에 네이티브 Responses를 선택할 수 있고, GitHub Copilot은 모델(`gpt-5.3-codex`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`)을 Responses 전용 기본값으로 선언합니다. 이 모델들은 에이전트 트래픽에서 `/chat/completions`를 거부하기 때문입니다. `gpt-5.4-nano`처럼 기본값이 없는 모델은 여기서 직접 옵트인할 수 있습니다. 단일 와이어 상위 항목과 정식 ChatGPT forward는 재정의를 거부합니다. | +| `modelAdapters?` | `Record` | 혼합 와이어 게이트웨이를 위한 모델별 `openai-chat` 또는 `openai-responses` 와이어 재정의입니다. 명시적 항목이 레지스트리 기본값보다 우선합니다. DeepSeek 프리셋은 `deepseek-v4-flash`에 네이티브 Responses를 선택할 수 있고, GitHub Copilot은 모델(`gpt-5.3-codex`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6.1-sol`, `gpt-6.1-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`)을 Responses 전용 기본값으로 선언합니다. 이 모델들은 에이전트 트래픽에서 `/chat/completions`를 거부하기 때문입니다. `gpt-5.4-nano`처럼 기본값이 없는 모델은 여기서 직접 옵트인할 수 있습니다. 단일 와이어 상위 항목과 정식 ChatGPT forward는 재정의를 거부합니다. | | xAI Responses 옵트인(대시보드) | 스위치 | `xai`에서만 `grok-4.5`와 `grok-4.6`의 `modelAdapters` 항목을 원자적으로 설정하거나 지웁니다. 한 항목만 있으면 다음 스위치 쓰기가 둘을 정규화할 때까지 혼합 상태로 표시됩니다. 다른 재정의와 티어 동작은 바뀌지 않습니다. Grok 4.7은 OAuth에서 레지스트리 와이어 기본값으로 Responses를 사용하며, 명시적 `modelAdapters["grok-4.7"] = "openai-chat"` 항목으로 Chat으로 전환할 수 있습니다. | | `xaiResponsesXSearch?` | `boolean` | 기본적으로 비활성화됩니다. xAI Responses 대상에서는 최종 요청 정규화 후에도 실제 `web_search` 도구가 남아 있을 때만 공급자가 호스팅하는 `x_search` 선언을 추가합니다. 기존 선언은 중복하지 않고, 호출자의 `tool_choice`/`allowed_tools` 선택기 범위를 확장하지 않으며, 웹 검색 사이드카의 `search.xSearch` 옵션과는 별개입니다. | | `modelPreferHostedTools?` | `Record` | hosted tool namespace를 예약하는 non-forward Responses gateway용 정확한 모델 ID opt-in입니다. 현재 `["image_generation"]`만 허용하며, 일치하는 모델은 `openai-responses` wire를 사용하고 해당 hosted tool을 지원해야 합니다. 충돌하는 클라이언트 `image_gen` 선언을 제거하고 호출자의 tool choice를 유지하도록 selector도 다시 씁니다. OpenAI API 가상 `-pro` 모델은 선택한 공개 ID를 먼저 일치시키고, 해석된 기본 wire-model ID를 대체값으로 사용합니다. `modelAdapters`는 공개 ID를 먼저, 그 다음 기본 ID를 해석하며, 두 번째 결과가 최종 wire를 결정합니다. 설정하지 않은 모델은 일반 alias 동작을 유지합니다. | @@ -138,12 +150,13 @@ managed map을 활성화하면 privacy-safe selector를 만들고, 이후 계정 | `noStopModels?` | `string[]` | 호출자가 지정한 `stop`을 거부하는 모델입니다. `openai-chat` 어댑터, Chat 패스스루, Responses 패스스루가 이 모델에는 해당 필드를 보내지 않습니다. 기본 `xai` 프리셋은 xAI 문서가 이 값을 거부한다고 밝힌 추론 모델(`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`)을 여기에 넣습니다. `grok-4.20-0309-non-reasoning`, `grok-composer-2.5-fast`은 호출자가 보낸 `stop`을 그대로 받습니다. | | `noPenaltyModels?` | `string[]` | presence/frequency penalty를 허용하지 않는 모델입니다. 기본 `xai` 프리셋은 xAI 문서가 이 값을 거부한다고 밝힌 추론 모델(`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`)을 여기에 넣습니다. 추론이 없는 모델은 호출자가 보낸 penalty를 그대로 받습니다. | | `noStructuredOutputModels?` | `string[]` | `openai-chat` 엔드포인트가 `response_format`을 거부하는 정확한 모델 ID입니다. 요청 모델이 항목과 정확히 일치할 때만 필드를 생략하며, 그 외 `openai-chat` 모델에서는 structured-output 변환을 유지합니다. | -| `noJsonSchemaModels?` | `string[]` | `openai-chat` 엔드포인트가 `json_schema` 형식은 거부하지만 `json_object`는 받는 정확한 모델 ID입니다. 이런 요청은 필드를 지우는 대신 `json_object`로 낮춰 보내므로, JSON을 요청한 클라이언트가 산문 대신 JSON을 받습니다. 한 모델이 두 목록에 모두 있으면 `noStructuredOutputModels`가 우선합니다. `opencode go`, `opencode zen`, `opencode free` 프리셋이 DeepSeek 경로에 기본으로 싣습니다. | +| `noJsonSchemaModels?` | `string[]` | `openai-chat` 엔드포인트가 `json_schema` 형식은 거부하지만 `json_object`는 받는 정확한 모델 ID입니다. 이런 요청은 필드를 지우는 대신 `json_object`로 낮춰 보내므로, JSON을 요청한 클라이언트가 산문 대신 JSON을 받습니다. 한 모델이 두 목록에 모두 있으면 `noStructuredOutputModels`가 우선합니다. `opencode go`, `opencode zen` 프리셋이 DeepSeek 경로에 기본으로 싣습니다. | | `foldDeveloperRoleToSystem?` | `boolean` | `openai-chat` 목적지가 `developer` 역할을 받는지 기록합니다. `foldDeveloperRoleToSystem`이 없으면 `system`, `true`이면 `system`, `false`이면 `developer`로 보냅니다. 값이 없다는 것은 이 목적지에 대해 기록된 것이 없다는 뜻이고, `true`는 상위 서비스가 역할을 거부한다는 기록, `false`는 받아들인다는 기록입니다. 어느 경우에도 메시지는 대화 안의 원래 위치를 유지하며 역할만 바뀝니다. 역할을 거부하는 목적지는 `400 role 'developer' is not allowed`로 응답해 턴이 시작조차 못 하므로, 기록이 없는 상태의 기본값을 접는 쪽으로 둡니다. | | `parallelToolCalls?` | `boolean` | 병렬 도구 호출을 켜거나 끕니다. OpenAI Chat은 기본으로 켜져 있고, 비-chat 어댑터는 명시적으로 `true`일 때만 이를 노출합니다. | | `responsesItemIdRepair?` | `{ message?: string[]; reasoning?: string[]; repairMissingTerminalIds?: boolean; repairInvalidIds?: boolean }` | 기본값이 꺼진 downstream SSE 복구입니다. 정확한 자리표시자 id, 누락된 종료 id, 그리고(`repairInvalidIds`) 정규 `msg_`/`rs_` 접두사가 없는 message/reasoning id를 복구합니다. function-call id는 다시 쓰지 않습니다. 내장 DeepSeek은 마지막 두 가지를 기본으로 켭니다. | | `responsesSnapshotRepair?` | `boolean` | 기본값이 꺼진 클라이언트용 복구입니다. SSE와 JSON의 Responses 수명 주기에서 누락된 status, output, 도구 메타데이터를 채우며 raw 검사와 영속화는 변경하지 않습니다. | -| `retryOn429?` | `{ enabled?: boolean; attempts?: number; intervalMs?: number; maxIntervalMs?: number; respectRetryAfter?: boolean }` | API-key 프로바이더 전용(`authMode: "key"`). 동일 대상 429 재시도: `retryOn429`가 없으면 기능이 꺼져 있고, 객체가 있으면 `enabled: false`가 아닌 한 활성화됩니다. 429 시 대기(업스트림 `Retry-After` 또는 고정 간격) 후 키 장애 조치 전에 동일 키로 동일 요청을 재전송합니다 — 일반 텍스트 턴 복구 루프, Responses passthrough, 이미지/비디오 브리지, web-search 사이드카, 터미널 연속 요청을 모두 포함합니다. 재전송 대상은 프리스트림 HTTP 429 응답뿐이며, 커스텀 `runTurn` 전송은 HTTP 재시도 루프에서 제외됩니다. `attempts`는 첫 429 이후의 동일 키 재전송 횟수(총 전송 = `attempts` + 1)이며, 메인 복구 루프·터미널 가드 연속 요청·브리지 재시도가 공유하는 요청 단위 예산입니다. `attempts`를 모두 소진해도 동일 키 재전송만 중단되며, 이후에는 일반 키 장애 조치 또는 최종 오류 처리가 사용 가능한 대상에 따라 진행됩니다 — 키 인증 passthrough 와이어에는 장애 조치가 없으므로 소진된 429가 그대로 반환됩니다. Codex 자체는 429를 재시도하지 않으므로 단일 키 프로바이더의 유일한 방어선입니다. 기본값: `enabled: true`, `attempts: 3`, `intervalMs: 5000`, `maxIntervalMs: 60000`(단일 대기는 `maxIntervalMs`로 상한, 그 자체는 600000으로 상한), `respectRetryAfter: true`. | +| `promptCacheKey?` | `boolean` | `openai-chat` 프로바이더 전체에서 `prompt_cache_key` 전달을 켭니다. 어댑터는 받은 키만 전달하지만 Claude Messages 변환은 `metadata.user_id` 또는 메타데이터가 없을 때 모델/시스템/도구 조합에서 키를 유도할 수 있습니다. 프리셋이 선언하지 않으면 기본적으로 꺼집니다. Mistral은 [프롬프트 캐시 계약](https://docs.mistral.ai/studio/conversations/advanced/prompt-caching)에 따라 켜며 명시적 `false`로 끕니다. 엄격한 게이트웨이는 알 수 없는 필드를 HTTP 400으로 거절할 수 있으므로 업스트림이 지원을 명시할 때만 켜세요. | +| `retryOn429?` | `{ enabled?: boolean; attempts?: number; intervalMs?: number; maxIntervalMs?: number; respectRetryAfter?: boolean }` | API 키 프로바이더 전용(`authMode: "key"`). 미설정 시 꺼지지만 정식 엔드포인트의 OpenCode Go와 Command Code, 정식 Mistral `openai-chat`은 예외로 재전송 6회, 간격 10초, 상한 60초 정책을 사용합니다. 객체를 지정하면 켜지며 `enabled: false`는 이 기본 정책도 끕니다. 스트림 시작 전 HTTP 429에서 `Retry-After` 또는 고정 간격을 기다린 후 키 장애 조치 전에 동일 키로 동일 요청을 재전송합니다. 텍스트 턴 복구, Responses passthrough, 이미지/비디오 브리지, web-search 사이드카, 터미널 연속 요청을 포함하며 커스텀 `runTurn`은 제외됩니다. `attempts`는 첫 429 이후 동일 키 재전송 횟수(총 전송 = `attempts` + 1)로 요청 전체에서 공유됩니다. 소진 시 동일 키 재전송만 중단되고 일반 키 장애 조치나 최종 오류 처리가 이어집니다. 키 인증 passthrough에는 장애 조치가 없어 원래 429를 반환합니다. 정식 Mistral Chat의 명시적 대기 시간이 허용 상한을 넘으면 동일 키 재전송을 중단하고 원래 `Retry-After`를 반환하지만 풀의 다른 키가 처리할 수 있습니다. 다른 프로바이더는 대기 시간을 상한으로 제한합니다. Mistral은 자격 증명/모델별 재시도 기한을 공유하며 `requestPacing.enabled: false` 또는 `retryOn429.respectRetryAfter: false`로 끕니다. 재시도 시각이 없는 429에서 분당 예산 부족이 검증되면 보수적인 60초 복구 대기 후 한 번 탐색하고, 성공하면 일반 트래픽으로 돌아갑니다. 정상 트래픽에서는 다음 토큰 비용을 예측하지 않습니다. 공시된 허용량을 사용하고 소진된 월간 허용량은 제외하며 정확한 초기화 시각을 보장하거나 기록 압축을 유발하지 않습니다. Codex 자체는 429를 재시도하지 않습니다. 객체 기본값: `enabled: true`, `attempts: 3`, `intervalMs: 5000`, `maxIntervalMs: 60000`(대기 상한, 설정 최대 600000), `respectRetryAfter: true`. | | `transientRetryOn5xx?` | `{ enabled?: boolean; attempts?: number }` | 키 인증 `openai-chat` 및 `openai-responses` 프로바이더 전용입니다. `authMode: "forward"` 프로바이더(ChatGPT 계정 풀)는 이 옵션을 읽지 않고 기본 재시도 단계를 유지합니다. 스트림 시작 전의 일시적인 업스트림 상태(500, 502, 503, 504, 520, 521, 522)를 선택적으로 재시도합니다. 이 옵션이 없으면 꺼져 있고, 객체가 있으면 `enabled: false`가 아닌 한 활성화됩니다. 최초 Responses 요청, 터미널 가드 연속 요청, 네이티브 `/v1/chat/completions`, 429/계정 복구 재조회를 포함합니다. `attempts`는 최초 전송을 포함하여 요청 하나에 허용되는 업스트림 전송의 총횟수(1..10, 기본값 3)입니다. 연결 재설정 복구와 요청 단위 예산 하나를 공유하므로 `3`이면 실제로 프로바이더에 도달하는 요청은 최대 세 번입니다. 대기에는 400ms로 고정된 지수 백오프를 사용하고 상한은 5초이며 `Retry-After`를 따릅니다. 속도 제한을 처리하는 `retryOn429`와는 별개이며, 스트림 도중의 실패는 절대 재전송하지 않습니다. 각 Combo 대상은 선예약한 첫 전송을 해당 대상의 설정 총횟수에 포함합니다. `attempts: 1`은 한 번만 전송하며, 예약을 환불해 여유를 만들거나 공유 상한을 늘리지 않습니다. 전송 전 로컬 거부나 취소는 미사용 예약을 해제합니다. | | `retryOnReset?` | `{ enabled?: boolean; replacements?: number }` | 네이티브 `openai-responses` 프로바이더(`authMode: "forward"` 포함)와 업스트림 헤더 수신 전의 일반 Responses 변환 전송 경로에 적용됩니다. 호출자가 아무것도 관측하지 못한 채 실패한 전송을 선택적으로 대체합니다. 이 옵션이 없으면 꺼져 있고, 객체가 있으면 `enabled: false`가 아닌 한 활성화됩니다. 응답 헤더가 오기 전에 연결이 끊어진 경우와, 헤더 이후 SSE 본문이 제어 이벤트만 실은 채 끊어진 경우를 모두 다룹니다. canonical ChatGPT 업스트림 WebSocket에서 create 프레임을 보낸 뒤 Responses 이벤트가 오기 전에 소켓이 닫히거나 오류가 난 경우도 같은 방식으로 다루며, 이때 대체 전송은 HTTP로 보냅니다. 자체 완결된 요청만 대체합니다. `store: false`, 완전한 `input`, `previous_response_id`·`conversation`·`stream_id` 없음, 클라이언트가 실행하는 도구만 해당합니다. `replacements`는 모든 구간과 모든 콤보 자식을 합쳐 논리 요청 하나가 만들 수 있는 대체 전송 횟수입니다(1..2, 기본값 1). 구간별 재시도 횟수도 전송 예산도 아니므로, 대체 전송도 해당 구간이 이미 가진 전송 허용량 안에 들어가야 합니다. 이미 출력이나 도구 호출을 내보낸 요청은 이 값과 무관하게 대체하지 않습니다. 원본 전송이 이미 시작됐다면 대체한 추론도 과금될 수 있어서 기본값은 꺼짐입니다. 일반 변환 전송 경로에서는 최초 전송과 재구축한 전송 모두 같은 대체 권한과 전송 예산으로 헤더 수신 전 연결 리셋을 대체합니다. 어댑터가 자체 관리하는 전송 경로와 업스트림 헤더 수신 후 변환 스트림 장애는 제외됩니다. | | `autoToolChoiceOnlyModels?` | `string[]` | `tool_choice`가 `auto` 또는 `none`만 받는 모델입니다. 강제 선택은 낮은 수준으로 바뀝니다. | @@ -157,8 +170,8 @@ managed map을 활성화하면 privacy-safe selector를 만들고, 이후 계정 | `anthropicEofTolerance?` | `boolean` | `message_stop` 전에 스트림이 끝나도 표시 텍스트 또는 완전한 JSON 객체 툴 입력을 받은 경우에만 완료를 허용합니다(Anthropic 호환 게이트웨이용). 기본값은 꺼짐. | | `googleMode?` | `"ai-studio" \| "vertex" \| "cloud-code-assist"` | Google 전송/인증 모드입니다. 기본값은 `ai-studio`입니다. | | `googleToolSchemaPolicy?` | `"compatible" \| "reject-lossy"` | Google 전용입니다. 생략하거나 `compatible`이면 호환 스키마와 기존 비직접 400 복구를 유지합니다. `reject-lossy`는 초기 손실 또는 판정 불가능한 상한 비교를 전송 전에 거부하고 제약을 개방하는 Vertex 또는 Cloud Code Assist 복구를 보류합니다. 직접 AI Studio는 이 복구를 수행하지 않습니다. | -| `project?` | `string` | Vertex 또는 Antigravity Cloud Code Assist 프로젝트 id입니다. | -| `location?` | `string` | Vertex 위치입니다. 환경 변수 폴백은 `GOOGLE_CLOUD_LOCATION`입니다. | +| `project?` | `string` | Vertex 또는 Antigravity Cloud Code Assist 프로젝트 id입니다. Vertex API 키는 이 값이 있으면 프로젝트 엔드포인트를 쓰고, 없으면 `GOOGLE_CLOUD_PROJECT`가 설정돼 있어도 express 모드를 씁니다. ADC는 `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT`로 폴백합니다.`"auto"`로 설정하면 각 Vertex API 키가 자신의 프로젝트를 확인합니다(확인에 성공한 결과는 키마다 캐시하고, 확인에 실패한 키는 이후 요청에서 다시 탐색합니다). 하나의 키 풀이 여러 프로젝트에 걸칠 수 있으며, ADC는 `"auto"`를 거부합니다. | +| `location?` | `string` | Vertex 위치입니다. API 키 프로젝트 엔드포인트는 기본값이 `global`이고 `GOOGLE_CLOUD_LOCATION`을 무시합니다. ADC는 이 값이나 `GOOGLE_CLOUD_LOCATION` 폴백이 필요합니다. | | `mcpServers?` | `Record` | Cursor 전용입니다. stdio 또는 Streamable HTTP MCP 서버입니다. | | `desktopExecutor?` | `DesktopExecutorConfig` | Cursor 전용입니다. 외부 computer-use 및 record-screen 명령입니다. | | `unsafeAllowNativeLocalExec?` | `boolean` | Cursor 레거시 불리언입니다. 더 새로운 필드가 설정되지 않았을 때만 `nativeLocalExec: "on"`과 같습니다. | @@ -186,6 +199,8 @@ API 키 공급자는 리터럴 키나 환경 참조를 둘 수 있습니다. OAu 대시보드 연결 테스트와 라이브 모델 발견은 범위가 제한된 GET 전용 전송을 사용합니다. 아웃바운드 프록시가 없으면 opencodex는 호스트 이름을 한 번만 확인하고, 검증된 주소로만 연결합니다. HTTPS는 원래 Host, SNI, 인증서 검증을 유지하며, 공급자 설정으로 인증서 검사를 끌 수는 없습니다. +직접 응답 다운로드는 로컬 처리가 다음 데이터를 받을 준비가 될 때까지 잠시 멈춥니다. gzip과 deflate 응답에도 적용됩니다. 이 대기 시간은 공급자의 무응답 시간으로 계산하지 않으며, 요청 전체의 제한 시간과 취소는 계속 적용됩니다. + 이 작업들은 [서버에 설정된 아웃바운드 fetch](/ko/reference/configuration/server/)를 사용합니다. `config.proxy`로 설정했거나 SOCKS5 `ALL_PROXY`에서 상속된 서버 SOCKS5 프록시는 대상이 `NO_PROXY`에 걸리지 않을 때 OpenCodex의 내장 터널을 사용합니다. `HTTP_PROXY`와 `HTTPS_PROXY`는 Bun의 네이티브 HTTP(S) 처리를 그대로 사용하지만, SOCKS가 아닌 `ALL_PROXY`는 네이티브 HTTP fetch 경로가 아닙니다. URL과 리터럴 주소 검사는 계속 실행되지만, 선택된 프록시가 최종 경로, DNS 응답, 피어를 고르므로 opencodex는 그 피어를 고정하거나 검증할 수 없습니다. 이는 명시적인 보안 한계입니다. 사설/로컬 목적지는 `allowPrivateNetwork: true`가 필요하며, 아웃바운드 프록시가 활성화된 경우에는 일치하는 `NO_PROXY` 항목도 필요합니다. loopback은 자동으로 추가됩니다. CIDR 항목은 해석하지 않으므로 각 LAN 호스트는 따로 적어야 합니다. matcher는 정확한 호스트, 도메인 접미사, 선택적 포트, 괄호로 감싼 IPv6, `*`를 지원합니다. 예를 들면 `192.168.1.50`은 따로 적어야 합니다. 메타데이터와 link-local 목적지는 계속 차단됩니다. 진단 요청은 리디렉션을 거부하고, 자격 증명이 제거된 대상만 보고합니다. 일반적인 공급자 요청의 리디렉션 검토는 이 진단 가드와 별도로 유지됩니다. @@ -391,6 +406,12 @@ Cursor 서버 주도 로컬 도구는 기본값으로 비활성화됩니다. Cod 필드는 최상위가 아니라 `providers.cursor`에 설정하십시오. 대시보드에서는 **Providers → Cursor → Edit JSON**을 사용해 저장한 뒤 다시 시작합니다. 레거시 `unsafeAllowNativeLocalExec: true`는 `nativeLocalExec`이 설정되지 않았을 때만 `nativeLocalExec: "on"`과 같습니다. MCP, 화면 녹화, computer use는 `mcpServers`와 `desktopExecutor`가 따로 제어합니다. +네이티브 `grep`이 허용된 디렉터리 검색에서는 `glob`으로 파일을 고릅니다. `*.ts`는 모든 하위 폴더의 +파일명에 적용되고, `src/**/*.ts`는 선택한 검색 폴더를 기준으로 한 상대 경로에 적용됩니다(`./`로 시작해도 됩니다). 모든 운영체제에서 +패턴의 경로 구분자는 `/`를 사용합니다. 문자 범위와 중괄호 선택 패턴을 지원하며, Bun 1.4.4에서는 +`**/!(*.test).ts` 같은 제외 패턴과 `part-{1..3}.ts` 같은 숫자 범위도 지원합니다. +필터는 검색 폴더를 바꾸거나 네이티브 실행을 활성화하지 않습니다. + 각 `mcpServers.`는 `command`(stdio) 또는 `url`(Streamable HTTP) 중 하나를 받습니다. stdio는 `args`, `env`, `cwd`도 받습니다. HTTP는 `headers`를 받습니다. 둘 다 `enabled`(기본값 true)와 `toolPrefix`를 지원합니다. `desktopExecutor`는 `computerUseCommand`, `recordScreenCommand`, `cwd`, `env`, `timeoutMs`(기본값 `30000`)를 받습니다. 명령은 `sh -c`를 거치며 stdin에서 JSON 요청 하나를 읽고, stdout에 JSON 결과 하나를 써야 합니다. :::caution[보안] @@ -578,3 +599,8 @@ source 재정의가 0이면 꺼지고, 전역 0이어도 source에 양수 재정 ### `anthropicAccountPool.routes` `anthropicAccountPool.routes`는 모델을 저장된 Anthropic OAuth 계정 ID에 연결합니다. 풀이 활성화되면 대소문자를 구분하는 `match` 글롭의 첫 일치가 최초 선택과 429 재시도를 제한합니다. `fallback: true`는 해당 경로에 적격 계정이 없을 때만 일반 풀로 확장합니다. + + +### `promptLengthPricing` + +선택 항목 `promptLengthPricing`은 네 기본 요율을 확장합니다. `mode: "automatic"` 또는 생략은 카탈로그의 컨텍스트 규칙을 유지하고, `mode: "flat"`은 이를 비활성화합니다. `mode: "custom"`은 양의 안전한 정수 `thresholdTokens`, `"gt"` (>) 또는 `"gte"` (≥)인 `comparison`, 네 절대 대체 요율을 담은 `rates`를 사용합니다. 임계값에는 캐시 읽기와 쓰기를 포함한 모든 프롬프트 토큰이 포함되며 출력은 제외됩니다. 선택한 요율은 출력을 포함한 전체 요청에 적용되고 임계값 양쪽에서 카탈로그 규칙을 대체합니다. Fast/Priority 조정은 요율을 선택한 뒤 한 번만 적용되며, 해당 모델에 대해 카탈로그가 공개한 Priority 관계를 따릅니다. 추정치는 카탈로그가 그 관계를 `lower-bound`로 선언한 경우(현재 xAI Grok)에만 하한으로 표시됩니다. 카탈로그 규칙이 없는 모델은 Priority 배수를 한 번만 적용합니다. **Models → Price**에서 설정합니다. **Reset to automatic**은 기본 요율과 규칙을 포함한 전체 재정의를 삭제합니다. 기존 설정은 호환되며 이 설정은 표시되는 추정 비용에만 해당합니다. diff --git a/docs-site/src/content/docs/ko/reference/proxy-formats.md b/docs-site/src/content/docs/ko/reference/proxy-formats.md index 9cad2c81f9a..bf854d93543 100644 --- a/docs-site/src/content/docs/ko/reference/proxy-formats.md +++ b/docs-site/src/content/docs/ko/reference/proxy-formats.md @@ -40,6 +40,8 @@ Chat Completions와 Messages 엔드포인트는 지연된 도구 카탈로그를 별도로, 모든 인바운드 와이어에서 공급자의 `default.` 네임스페이스 호출은 요청이 선언한 원래 도구 이름으로 정규화됩니다(`default.Read` → `Read`). +네이티브 Messages는 `tools: []`와 함께 제공된 시스템 메시지의 인라인 도구 정의도 인식합니다. 해당 이름은 영구적으로 제거될 때까지 호출할 수 있으며, 다른 클라이언트 도구를 허용하지는 않습니다. 호스팅된 MCP 호출과 서버 도구의 인수 스트림은 네이티브 형식을 유지합니다. + ## 엔드포인트 개요 | 클라이언트 표면 | 엔드포인트 | 성공한 비스트리밍 결과 | 성공한 스트리밍 또는 소켓 결과 | @@ -292,12 +294,17 @@ call creation 이후 클라이언트는 다음의 지원되는 모든 inbound 프록시는 업스트림 join URL을 정규화한 뒤, 양방향 텍스트 및 바이너리 프레임을 투명하게 릴레이합니다. 업스트림 인증은 프록시가 소유한 상태로 유지되며, 클라이언트 프로토콜 헤더는 보존됩니다. +sideband 연결을 종료할 때 프록시는 업스트림의 정상 종료를 1초 동안 기다린 뒤, 응답하지 않는 +전송 연결을 강제로 닫습니다. 클라이언트 업그레이드가 실패한 경우에도 실제 업스트림 연결이 +닫힌 것을 확인한 후에만 해당 계정의 활성 통화 용량을 반환합니다. + call creation과 sideband join은 같은 OpenAI 계정으로 이루어져야 하며, 그렇지 않으면 업스트림이 join을 거부합니다(`404`). 두 요청 모두 Codex의 `session-id`와 `thread-id` 헤더를 실어 보냅니다. Pool 모드는 계정 선택을 그 쌍에 묶어 두므로(프로세스 로컬) 프록시에 도착한 join은 통화를 만든 계정을 그대로 쓰고, Direct 모드는 두 요청 모두 호출자의 현재 bearer를 전달합니다. 릴레이되는 클라이언트 헤더는 정확히 `openai-alpha`, `x-session-id`, `session-id`, `thread-id`, `originator`, `x-oai-attestation`, -`x-codex-turn-metadata`(`src/server/live.ts`의 `LIVE_CLIENT_PROTOCOL_HEADERS`)이며, 각 헤더는 +`x-codex-turn-metadata`, `voice-session-id`(실시간 `x-session-id`와 별개인 클라이언트 음성 세션 id) +(`src/server/live.ts`의 `LIVE_CLIENT_PROTOCOL_HEADERS`)이며, 각 헤더는 호출자가 보낸 경우에만 전달되고 프록시가 만들어 내지 않습니다. `Authorization`과 ChatGPT 계정 id는 ChatGPT 경로에서 프록시가 소유합니다(Pool은 저장된 계정으로 교체, Direct는 검증된 호출자 bearer를 전달). API 키 프로바이더는 자체 bearer를 씁니다. Codex가 join을 프록시로 보내는 것은 diff --git a/docs-site/src/content/docs/reference/adapters.md b/docs-site/src/content/docs/reference/adapters.md index 70cf9514f98..ef88ecb76b5 100644 --- a/docs-site/src/content/docs/reference/adapters.md +++ b/docs-site/src/content/docs/reference/adapters.md @@ -164,6 +164,17 @@ body and response, with narrow compatibility rewrites for routed gateways. `forward` uses configured static headers without relaying caller authorization; `key` uses the configured provider key. +When history from Google is replayed to canonical ChatGPT Codex or the official OpenAI Responses +API, the adapter removes item-level `extra_content` from the outgoing HTTP request or WebSocket +create. Native `/responses/compact` applies the same projection. The original history keeps the Google +thought signature for later Google turns. Other Responses gateways retain their extensions. + +OpenAI-operated destinations receive replayed `url_citation` annotations with missing or invalid +titles and indices repaired. This applies to ordinary HTTP/WebSocket requests and native +`/responses/compact`. Custom gateways keep the original annotations even when +`supportsNativeCompactEndpoint: true` enables native compaction. Citation repair leaves the +client's stored history unchanged. + The adapter preserves the incoming client's `User-Agent` as a fallback in both auth modes because some Responses-compatible providers use the Codex client fingerprint for compatibility behavior. An explicitly configured provider `User-Agent` remains authoritative regardless of header casing; @@ -172,6 +183,46 @@ with `forwardClientHeaders`; provider `headers` win for this option, and credent names are refused. Canonical ChatGPT forward auth retains its separate fixed header allowlist. Only `originator`, `x-client-request-id`, `x-codex-app-version`, and `user-agent` are supported by `forwardClientHeaders`; arbitrary names are rejected on load/write and ignored at runtime. +### Native compaction on custom gateways + +Responses compatibility alone is not evidence of native compaction support. Custom gateways +continue using portable text summaries unless the operator declares the relevant capability: + +- `supportsNativeCompactionTrigger: true` preserves v2 `compaction_trigger` requests and native output. +- `supportsNativeCompactEndpoint: true` opts key/local/forward routes into the separate v1 endpoint. + OAuth retains routed compaction and its existing credential lifecycle. V2 support does not imply v1 support. +- `decodesNativeCompactionBlobs: true` preserves native blobs on subsequent turns when there is + no known serving-provider mismatch. Neither generation flag implies this replay capability. + +Custom native v1 also honors `preserveResponsesReasoningContent: true` for plaintext reasoning +input, just like ordinary custom Responses requests. Absent or `false` keeps content blank; +OpenAI-operated compact destinations keep blanking even if the flag is set. Internal `ocxr1:` +envelopes are always removed. Plaintext preservation is independent of the generation flags, +native blob replay capability, and serving-identity checks. + +If the custom provider also requires adjacent tool results (`requiresAdjacentResponsesToolResults: true`), +both ordinary Responses and native v1 remove opaque reasoning ciphertext and require plaintext replay. +Missing `reasoning_text` is filled from the item's `summary_text`, or a minimal placeholder if no +usable summary exists. Existing plaintext is preserved. OpenAI-operated compact keeps its own policy. + +For a gateway whose **v2 generation and replay** have both been verified, merge these fields into +its existing provider configuration, retaining its real URL and configured authentication: + +```json +{ + "adapter": "openai-responses", + "supportsNativeCompactionTrigger": true, + "decodesNativeCompactionBlobs": true +} +``` + +These flags do not make the gateway an official OpenAI destination, forward caller credentials, +change account selection, or bypass normal compatibility sanitization. Validate an actual compact +response and continued conversation, not just HTTP 200. Keep blobs on the backend that minted them; +cross-provider compaction routing overrides still use portable summaries. V1 fallback summaries +remain portable even when the destination separately accepts v2 triggers. Native-capable routes +are excluded from portable emergency compaction recovery for their respective protocol. + Adapter selection does not select the upstream transport. Eligible requests can use the [upstream WebSocket proxy route](/reference/proxy-formats/#json-and-sse-output); invalid or unsupported WebSocket proxy settings fall back to HTTP/SSE. HTTP fetch-based Responses handling uses the @@ -278,6 +329,14 @@ MiMo model Command Code serves. **Auth:** `key` (`x-api-key` by default, or `Authorization: Bearer` with `apiKeyTransport: "bearer"`) or `oauth` (Bearer + `anthropic-beta`, for Claude Pro/Max). - Converts messages to Anthropic content blocks (text, base64 image, `tool_use`, `thinking`). +- Routed Codex compaction keeps the client tool catalog, system prompt and tool-selection + options to preserve their prompt-cache prefix. Summary requests disable search/media + sidecars; upstream-hosted tools retain their declarations with `tool_choice: none`, as does a + mandatory tool choice, while the advertised catalog stays the same. A request that advertises a + sidecar web-search tool (for example Exa) still changes its tool prefix on compaction. + A summary that attempts a tool call fails without publishing a function call or replacing + history. A successful summary returns exactly one compaction item. Image normalization + and the existing one-shot tighter-image retry after HTTP 413 remain unchanged. - Translated Anthropic Messages reasoning replay shares the request translation budget, including encoding/decoding copy overhead. Requests exceeding it return HTTP 413 with `translation_buffer_limit`; signatures and opaque reasoning data are never truncated to fit. @@ -446,7 +505,9 @@ MiMo model Command Code serves. The plan's precise credit balance is retained with that identity-fenced quota reading. Separately, Kiro `meteringEvent` credit values are measured request spend: the last value within a physical response is used, and credits from separately billed sends are added. - Token usage remains estimated; credits are never inferred from tokens. + Token usage remains estimated; credits are never inferred from tokens. Requests with hosted + search or image tools retain the adapter's full prompt estimate in Usage and daily activity, + including replayed history, even when the model does not invoke those tools. - After an admitted request, reads that account's available models from the regional management service without delaying the request. Cached results add model IDs to the shipped catalog. Empty or unrecognised replies retain the shipped list and any last good account list. Model @@ -454,7 +515,8 @@ MiMo model Command Code serves. limits inform context windows, conservatively combined with shipped limits when evidence is partial. Accounts that have not served have no list evidence yet. - Participates in multi-account rotation. With two stored accounts, a request-rate refusal - cools the refused account briefly; an exact monthly-quota refusal on HTTP 400 or 429 + cools the refused account briefly; an exact `MONTHLY_REQUEST_COUNT` or + `OVERAGE_REQUEST_LIMIT_EXCEEDED` refusal on HTTP 400 or 429 excludes it until the observed reset or evidence expiry. A confirmed HTTP 403 suspension quarantines that account in process, while an ordinary 403 does not rotate. Reactive rotation remains available when `oauthAccountFailover.enabled` is false. Refusal-aware diff --git a/docs-site/src/content/docs/reference/architecture.md b/docs-site/src/content/docs/reference/architecture.md index d9a323ae3c5..02885f50ef5 100644 --- a/docs-site/src/content/docs/reference/architecture.md +++ b/docs-site/src/content/docs/reference/architecture.md @@ -73,7 +73,8 @@ the `server/responses.ts` facade and its `server/responses/*.ts` modules: status, latency, provider/model labels, and best-effort token usage without changing the response. The pre-dispatch input estimate follows what the routed adapter actually sends. For `openai-chat` -models outside `preserveReasoningContentModels`, the adapter drops replayed assistant thinking, so +models excluded by the effective Chat thinking replay policy (an explicit +`preserveReasoningContentModels` list, or DeepSeek Flash/Pro detection when unset), the adapter drops replayed assistant thinking, so the estimate excludes it too; history that never reaches the provider therefore cannot cause a local context-limit refusal. Models that preserve reasoning, and other adapters, send that thinking and still count it. diff --git a/docs-site/src/content/docs/reference/cli.md b/docs-site/src/content/docs/reference/cli.md index 32368c3265f..d6ba1d76c35 100644 --- a/docs-site/src/content/docs/reference/cli.md +++ b/docs-site/src/content/docs/reference/cli.md @@ -146,8 +146,9 @@ proxy. `ocx system codex-cli-update check` needs no live proxy and makes no package-registry request. It inspects bounded provenance metadata for the configured install candidate, including its redacted executable location and ownership evidence. Trusted published-launcher context authenticates that candidate snapshot, -not a successful Codex execution. Because this one-shot command never executes Codex, environment and persisted candidates -remain report-only (`managed: false`, normally `selection_unattested`) and `selectionAttested` remains `false`. +not a successful Codex execution. On POSIX, `selectionAttested` can be `true` when the runtime resolver +selects the same canonical environment candidate without executing it. A linked npm-global package +manifest can then establish `managed: true` when it proves ownership and, if a candidate version is reported, that version matches the manifest; persisted candidates remain unattested. The JSON report exposes `candidateAvailable`, `candidateVersion`, `candidateSource`, and `selectionAttested`. Inspecting the configured candidate requires a trusted published-launcher context; a direct Bun/source launch has no such proof, ignores ambient and persisted candidate state, and may report @@ -156,6 +157,13 @@ only a proof-captured absolute environment candidate can receive lexical app-bun every other Windows candidate fails closed. The command does not install or repair software, execute Codex or npm, control a running process, or write configuration/cache state. +`ocx system codex-cli-update plan --json` adds a read-only update decision for an attested POSIX +npm-global installation. It resolves `latest` through the official npm registry, validates the +exact version and SHA-512 integrity, and refuses active sessions or incomplete process observation. +Registry queries use isolated temporary npm configuration, cache and logs with best-effort cleanup. +No software is installed and no process is signalled. Windows returns `windows_inspection_deferred`. +See [planning details](/reference/cli/agents/#read-only-codex-cli-update-plan). + For Windows x64 installation observation, see [the `attest` command](/reference/cli/agents/#explicit-installation-observation-on-windows-x64). Without explicit paths it observes the selected candidate identified from the proof-bound launcher snapshot; it does not grant update authority or attest runtime selection. List or status is the default where unambiguous. Use `--json` only on leaves that support it, and @@ -256,6 +264,8 @@ local configuration and local Lab inspection do not require startup. `ocx status --json` includes `cliCommand`: configured Desktop intent, the expected executable, observed PATH candidates, `pathFirst`, `desktopFirstOnPath`, issue codes, and `shellResolution: "unobserved"`. The human status report prints one command-selection line. `ocx doctor` adds an “ocx command selection” section: invalid, unsafe (`record-unsafe`) or enabled-pending records and missing/unusable Desktop targets fail the check, while disabled cleanup-pending records, PATH ordering conflicts and incomplete scans warn. `OCX_NO_DESKTOP_HANDOFF=1` suppresses package-launcher handoff for one invocation; it leaves the Desktop shim and PATH configuration in place. These read-only observations do not execute candidates, resolve parent-shell aliases/functions, or identify the proxy’s runtime owner. On Windows, package-launcher handoff is disabled; user `Path` order selects the Desktop `ocx.exe`. Status and doctor still read the record and report the first PATH candidate. A possible cmd current-directory candidate is reported separately from PATH order. +`ocx status --json` also includes `install`: how this `ocx` was installed, using the same detector `ocx update` acts on. `install.method` is `bun`, `npm`, `pnpm`, `mise`, `source`, `standalone` (the compiled desktop binary) or `unknown` when detection failed. `install.evidence.basis` names what decided it (`package-layout`, `mise-backend-record`, `standalone-binary` or `unavailable`); a mise install adds the mise tool alias and backend, and a failure adds an `error` code. No path is added. + Output flags are per command. `doctor` rejects `--json` with exit 2; [`v2` (family reference)](/reference/cli/agents/) supports `--json` for local and `--live` targets. Even for JSON-capable management commands, API failures diff --git a/docs-site/src/content/docs/reference/cli/agents.md b/docs-site/src/content/docs/reference/cli/agents.md index 882c7ef59db..501f13e7bfb 100644 --- a/docs-site/src/content/docs/reference/cli/agents.md +++ b/docs-site/src/content/docs/reference/cli/agents.md @@ -257,13 +257,29 @@ unqualified bare OpenAI model id. Bare `gpt-5.6-*` native aliases use Codex Pool Account-qualified OpenAI routes remain distinct, while provider-qualified routes such as `openai-apikey/gpt-5.6-*` use their configured API key and never fall through to the native alias. Read the safety and visibility contract in the guide before enabling the compatibility pair. -With `--strategy jev` only, the decision method is chosen by one of two mutually exclusive flags. +Only when the effective strategy is JEV (`--strategy jev`, or a stored JEV Combo when `--strategy` is omitted), the decision method is chosen by one of two mutually exclusive flags. `--decision-provider ` names a configured `jev-decision` row (for example a self-hosted Ollama `tev1`) as a System One-compatible server. `--decision-model ` names an ordinary opencodex route (for example `ollama/qwen3:4b`) that answers the same choice as JSON; it cannot be -this combo or any JEV combo. Omitting both uses TypeSafe. `--decision-timeout ` sets the +this combo or any JEV combo. A new JEV Combo that omits both uses TypeSafe; an existing JEV Combo keeps +its stored decision method until one of the flags changes it. `--decision-timeout ` sets the decision deadline (1000–120000, default 4000); `-` clears any of the three. +`ocx combo set --decision-quota-signals on|off|-` controls per-Combo advisory evidence. +`--decision-quota-tiers '{"limited":80}'` sets partial threshold overrides; `-` clears them. +Both are JEV-only. Omitted policy survives `--targets-file` or `--targets` replacement and rename. Without +`--strategy`, replacing the targets of an existing Combo keeps its stored strategy, so a JEV Combo stays JEV with +its decision method, quota settings and session pin; a new Combo defaults to failover. If the kept JEV +policy rejects the new targets, the command fails, names that policy, and saves nothing. Off retains thresholds; +switching away from JEV removes both settings. This adds quota data to the selected decision +backend, not probes, new account selection or hard exclusions. See +[quota defaults, freshness and privacy](/guides/combos/#advisory-quota-signals-for-jev). + +`ocx combo set --session-pin ` (JEV only) keeps each client session on the target +JEV chose for it while the session stays active, so the provider prompt cache stays warm. The +duration is the idle window: `90s`, `20m`, `1h` or milliseconds, from 1 minute to 24 hours; `-` +turns it off. See [Session pin for JEV](/guides/combos/#session-pin-for-jev). + `ocx combo test [--combo ] [--decision-provider | --decision-model ] [--decision-timeout ]` sends one synthetic decision probe through a saved combo's method or an unsaved selection and reports the gate, backend, and latency; it may spend one decision call. @@ -302,6 +318,7 @@ explicit `lastResort: false`. `--image-input auto|disabled` and `--reasoning-effort-mode strict|adaptive` preserve omission, while explicit `auto` and `strict` override saved disabled/adaptive values. Reasoning-effort mode is separate from `--effort-mode fallback|force`, which governs the default effort. +`--allow-metered-targets on|off` grants or clears operator consent for marked combo targets on a loopback-only proxy; public and linked callers still need configured-key consent. `--native-alias on|off` preserves explicit false; the legacy bare flag means true. Turning it off may require the intended alias clear, `--native-alias off --alias -`, to avoid retaining an incompatible native alias. Set/rename is an upsert without @@ -326,6 +343,25 @@ There is no monetary cost or comparative savings field in this report. See [Combos](/guides/combos/) for routing behavior and configuration guidance. +## Fallback off + +### `ocx fallback ...` + +```bash +ocx fallback status --json +ocx fallback off --kind provider +ocx fallback on --route combo/coding +ocx fallback clear --provider anthropic --kind model +ocx fallback local on --device mac-studio +``` + +`off`, `on` and `clear` take `--kind model|provider|all` (default `all`) and at most one scope: +`--provider `, `--route ` or `--device `; with none they set the global switch. +A device set to off always refuses; otherwise route beats provider beats global. `local` records whether a +device may use its own providers while the Home is unreachable, a permission GOAT reads; OpenCodex itself +still answers `home_unreachable` there. The commands talk to the live proxy; on a Remote Link Child, +`status` reads the Home and writes are refused. See [Fallback off](/guides/fallback-off/). + ## Routing profiles Existing profiles support inspection and evaluation through the live API: @@ -415,7 +451,8 @@ Inspect proxy requests, usage, storage, memory, and debug data. The direct alias | Alias | Equivalent resource | | --- | --- | | `ocx logs [filters] [--follow] [--json|--jsonl|--events]` | `ocx observe logs` | -| `ocx usage [--range ] [--since --until ] [--surface ] [--provider ] [--model ] [--api-key-id ] [--json]` | `ocx observe usage` | +| `ocx usage [--range ] [--since --until ] [--surface ] [--provider ] [--model ] [--api-key-id ] [--search ] [--top <1-1000>] [--json]` | `ocx observe usage` | +| `ocx usage compare [--range ] [--json]` | `ocx observe usage compare` | | `ocx storage [--json]` | `ocx observe storage` | | `ocx memory [--json]` | `ocx observe memory` | @@ -450,6 +487,43 @@ Any displayed totals reflect readable records only. If a filter has no readable the warning and guidance instead of total lines; skipped records may contain matches. `--json` preserves the response-level `usageIncomplete` diagnostic and reason. +### Compare a range with the previous equal window + +```bash +ocx usage compare --range today --json +ocx usage compare --range 7d --provider xai +``` + +`ocx observe usage compare` is the equivalent family form. The command reads `GET /api/usage` +twice: the named range ending now, then the same range evaluated one equal window earlier +(one local day for `today`/`1d`, seven for `7d`, thirty for `30d`). Default range is `30d`. +`--range all` is refused because open history has no previous equal window. `--since` and +`--until` are not accepted; the windows are derived. Surface, provider, model, and API-key +filters apply to both reads. Human output prints current, previous, signed delta, and percent +change. `--json` is `{schemaVersion,range,current,previous,delta,change,incomplete}`. The two +reads are sequential, not an atomic snapshot. Custom `since`/`until` on each response must +match the requested window. Both reads use the same connection and credential. If the +connection, account, or credential changes during the command, it reports an owner +mismatch and asks you to retry instead of printing a comparison. + +### Page indexed request history + +```bash +ocx logs history --provider xai --status 200 --limit 50 --json +ocx logs history --cursor '' --json +ocx logs get --json +``` + +`ocx observe logs history` and `ocx observe logs get` are the equivalent family +forms. These read the derived request-history index, not the recent `ocx logs` +ring. `--limit` is 1–100 (default 50). `--from` and `--to` accept epoch +milliseconds or full ISO datetimes with a timezone and are inclusive. `--json` +returns `{entries,hasMore,nextCursor,index}`. `--jsonl` emits rows only; when +more pages exist, `nextCursor=…` is printed on stderr so a caller can resume +without mixing the token into the row stream. Empty matches succeed. `get` +returns one canonical row by request ID; `ocx logs explain` remains the +route-decision view for that same ID. + ### Filter a bounded log snapshot ```bash @@ -489,7 +563,7 @@ ocx usage --range 7d --search 'model-a' --json `--search` matches a trimmed case-insensitive substring in model, provider or resolved model after reading the report. It sorts model rows by descending total -tokens with stable ties and keeps up to 100. JSON adds +tokens with stable ties and keeps up to 100 by default. JSON adds `modelView:{query,matchedModelCount,returnedModelCount,limit:100,truncated}`; human output labels the view and shows its selected model rows. Report totals, provider/day/account rows, exact filters and incomplete/window metadata remain @@ -500,6 +574,8 @@ preserves the existing output. Exact `--provider`/`--model` still scope the underlying report. Search does not broaden selected-key or connected-client self scope and is never sent as a new Hub API query parameter. +`ocx usage --top 25` (also `ocx observe usage --top 25`) sets the human model table limit to an integer from 1 to 1000. The default is 10, or 100 with `--search`; the limit applies after search filtering. It changes local display only: report totals, provider/account rows and API parameters stay unchanged. With `--json`, a valid `--top` is ignored; JSON retains its existing full report or 100-row search view. Invalid limits return exit 2 before fetching. + ### Saved companion usage totals ```bash @@ -1020,10 +1096,15 @@ the CLI, the API, and the GUI use the same bytes. Manage headless runtime settings, startup, sync, diagnostics, and updates. -`ocx system codex-restart --yes` restarts Codex app-servers and fully quits and relaunches the -Codex desktop app, through the same module as `ocx sync --restart-codex`. When the proxy itself -is running inside the Codex app, the command refuses with an actionable message instead of -promising a handoff it cannot complete. +`ocx system codex-restart --yes` restarts Codex app-servers through the same module as +`ocx sync --restart-codex`. It quits and relaunches the Codex desktop app when the app launched before the current model catalog was written or this request changed +its `model_catalog_json` binding, because the model picker is built at +launch. Otherwise the app stays open, respawns its own app-server, and keeps unsaved drafts and +pending approvals. If the app or catalog state cannot be read, it takes the full restart. When +the proxy itself is running inside the Codex app, the desktop restart refuses with an actionable +message instead of promising a handoff it cannot complete. + +A `model_catalog_json` binding changed by this request also forces the desktop restart, even when the shell started after the catalog file was written. ```bash ocx system settings --stream-mode eager-relay @@ -1077,9 +1158,9 @@ ocx system codex-cli-update check --json `check` makes no package-registry request and inspects bounded configured-candidate provenance evidence, including a redacted executable location and ownership evidence. Trusted published-launcher context authenticates -the candidate snapshot, not successful Codex execution. Because this one-shot command never executes Codex, -environment and persisted candidates remain report-only (`managed: false`, normally `selection_unattested`); -`selectionAttested` remains `false`. The JSON report exposes `candidateAvailable`, `candidateVersion`, `candidateSource`, +the candidate snapshot, not successful Codex execution. On POSIX, selection is attested only when the runtime +resolver selects the same canonical environment candidate without executing it. A linked npm-global manifest +can then establish `managed: true` when it proves ownership and, if a candidate version is reported, that version matches the manifest; persisted candidates remain unattested. The JSON report exposes `candidateAvailable`, `candidateVersion`, `candidateSource`, and `selectionAttested`. Inspecting the configured candidate requires a trusted published-launcher context; a direct Bun/source launch has no such proof, ignores ambient and persisted candidate state, and may report `candidate_unavailable` on POSIX. On Windows this first slice performs no candidate or configuration filesystem I/O: @@ -1094,6 +1175,26 @@ and are never classified as managed. On Windows, a captured bare command such as `CODEX_CLI_PATH=codex`, a remote path, or a device path reports `candidate_path_unavailable` instead. Those cases have a captured candidate; its path is not eligible for this inspection. +#### Read-only Codex CLI update plan + +```bash +ocx system codex-cli-update plan --channel latest --json +``` + +Run through the published Node launcher with the intended `CODEX_CLI_PATH` in the shell environment. +The plan requires an attested POSIX npm-global installation and a linked package manifest version. +It queries the official npm registry for an exact version, SHA-512 integrity and same-origin tarball, +using the launcher's captured PATH and isolated temporary npm configuration, cache and logs. Proxy +settings and `NODE_EXTRA_CA_CERTS` remain supported; cleanup is best-effort. No Codex command runs. + +An applicable plan reports `applicable: true` and a deterministic `planId` binding platform, +installation-root digest, installed version, channel and target version/integrity. The plan is never +persisted. It refuses downgrades, already-current versions, active Codex sessions and incomplete +process observation. Windows returns `windows_inspection_deferred` before registry/process queries. +A refusal exits 0: inspect `applicable` and `refusal`. The printed install argv is indicative; this +command installs nothing, changes no application state and never signals a session. No apply command +is exposed, and a plan ID is not installation authorization. + #### Explicit installation observation on Windows x64 ```text diff --git a/docs-site/src/content/docs/reference/cli/lifecycle.md b/docs-site/src/content/docs/reference/cli/lifecycle.md index 1a8f7a509d1..cabb3012544 100644 --- a/docs-site/src/content/docs/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/reference/cli/lifecycle.md @@ -82,8 +82,19 @@ ocx start --socks5-off ### `ocx stop [--json]` -Stop the running proxy (by PID), remove the PID file, and restore native Codex. If a managed +Stop the running proxy (by PID), remove the PID file, and restore native Codex for standalone owners. If a managed background service is installed, `ocx stop` also stops it first so it cannot respawn the proxy. +On a connected client (a Remote Link Child or a hub client), `ocx stop` leaves Codex routing, its +model catalog and the Grok config as they are, including after a successful API stop: the connection +owns them, and `ocx start` serves them again without re-injecting routing. Use `ocx disconnect` to hand Codex back to its own account. If Codex on a connected +client already reads native routing, `ocx status` names `ocx sync --restart-codex` to point it back. +A connected stop verifies the local runtime before changing its service: a client-role listener +with the matching PID is supported, while an ordinary proxy must explicitly advertise preservation +across API, signal and service shutdown. Older or unverified ordinary runtimes are refused before +stop actions. Update/restart that runtime through its supervisor and retry; use `ocx disconnect` +only when you intend to release the connection-owned integrations. +Repeated stops of an already-stopped client confirm service-manager inactivity and absence at the +known local endpoints before returning the normal not-running result. While the OpenCodex desktop app is running, `ocx stop` does not keep the proxy down: the app treats it as an unexpected exit and starts a proxy again, within seconds for one it started and after about a minute for one it had only attached to. Use the app's tray **Stop proxy** (for a proxy the app @@ -113,8 +124,11 @@ failure to restore shared client configuration keeps the stop failed and its rec carries the outcome class (`stopped`, `not-running`, `history-incomplete`, `history-deferred`, or `failed`), the service and proxy classifications, whether the runtime is down, and a stable one-line message. Exit codes are identical with and without `--json`: 0 on success, 1 on failure, -79 when only Codex history cleanup did not complete, and 80 when the shared teardown was deferred -and is still owed. +79 when only Codex history cleanup did not complete, 80 when the shared teardown was deferred +and is still owed, and 81 when a dashboard update that will restart the proxy handed Codex routing +and its catalog to the restarted proxy instead of restoring native Codex. With 81 the proxy is +down, and the restarted proxy releases the kept teardown after its first sync; if it never starts, +the next `ocx stop` restores native Codex. For a desktop-approved takeover, `ocx stop --json` also accepts the complete set of `--expect-*` values supplied by `ocx resolve --json`. On Windows, this guarded stop can @@ -153,6 +167,14 @@ Eligibility refusals name the reason and next action: use the owning installatio owning service/desktop app on Windows. Unknown ancestry or a changed target calls for `ocx status` before any restart. +On a connected client runtime (a Remote Link Child or a hub client), `ocx restart` asks the runtime +to start its own replacement on the same port. If the replacement does not answer within the +restart budget it is stopped, and the old runtime binds the port again and keeps serving; the +command then reports the restart as not completed. A client runtime run by a background service or +the desktop app refuses the request and keeps serving ("Restart not performed; the running client +runtime was kept"); restart it with `ocx service restart` or from the app. See +[Remote Link apply parity](#remote-link-apply-parity). + If package files or the Bun runtime remain incomplete after the installer exited or failed, run `ocx status`, stop any running proxy through its owner (`ocx stop`, or its service or desktop app), then reinstall with the same package manager and start it again. See @@ -245,7 +267,7 @@ intended. Repair one thread that was compacted through a routed provider before resuming it through native Codex. The command reads the exact thread selected by UUID, saves a private byte-for-byte backup, -then converts only OpenCodeX-owned `ocx1:` compaction state into a plain summary that native Codex +then converts only OpenCodeX-owned `ocx1:`/`ocx2:` compaction state into a plain summary that native Codex can replay. Native encrypted content and other threads are left unchanged. Close the selected thread before running the command; a concurrent rollout change makes recovery stop without replacing the file. @@ -279,6 +301,9 @@ only in build metadata; neither side is called older. Versions are not trimmed a is not normalized. JSON exposes the same advice in `versionSkew`, whose fields remain `cliVersion`, `proxyVersion`, `skewed`, and `warning`. +On a Remote Link Child and its Home, `--json` also reports `appliedCodexSettingsDigest`, +`lastApplyAt` and `runtimeStartedAt`; see [Remote Link apply parity](#remote-link-apply-parity). + Print a read-only diagnostic summary: proxy PID, `/healthz` reachability, dashboard URL, config path, default provider, Codex autostart setting, service state, shim state, and the redacted effective Codex home. Only the explicit, high-confidence Windows Orca runtime-home signature adds an actionable App-home @@ -394,12 +419,19 @@ not traffic missed between polls; injection seq cannot exclude restart/eviction Check post-sync readiness through the unauthenticated `GET /readyz` endpoint. It returns `200` when ready, or `503` with `Retry-After: 1` for `pending` and terminal `failed`. Its sanitized HTTP identity is `{service, version, uptime, pid, port, status}` plus the remote-hub protocol fields -`{protocol, minimumClientProtocol, managementUrl}`. `protocol` is the hub protocol this proxy +`{protocol, minimumClientProtocol, managementUrl, features}`. `protocol` is the hub protocol this proxy speaks and `minimumClientProtocol` the oldest client it still accepts, so a client can refuse an incompatible pairing before sending anything else. `managementUrl` is the origin a client should use for the management plane: the configured `hub.managementPublicOrigin` when `runtimeRole` is `hub`, and otherwise the origin the request itself arrived on. A readiness request with no -HTTP(S) origin is rejected rather than answered with a guess. Old proxies without `/readyz` fail +HTTP(S) origin is rejected rather than answered with a guess. +`features` lists the peer features this build serves, so a peer can check a capability without +comparing versions: `relay-auth/` names the Remote Link relay authentication version, +`link-management-relay` the read-only Remote Link management relay, `link-peer-report/` that +a Remote Link Home reports each Child's version and compatibility in `ocx link status`, `quota-pool/1` the +`GET /api/quota-pool` management read, and `served-by-header` the `x-opencodex-served-by` response +header. A feature this build does not serve is absent. The list holds fixed names only, never +account or configuration data. Old proxies without `/readyz` fail closed as `unreachable`; `/healthz` is separate liveness, not readiness. The command performs one probe by default; `--wait` polls until ready or timeout, but exits immediately when it observes the terminal `failed` state. The default timeout is 45 seconds; `--timeout ` requires `--wait` and accepts positive integer seconds from 1–300. @@ -408,6 +440,12 @@ The CLI's own `--json` output is deliberately narrower than the HTTP body: it em `unreachable`. Exit codes are 0 for ready; 1 for not-ready, pending, failed, timeout, or unreachable; and 64 for invalid arguments. +On a Remote Link Child, readiness also covers the link. While the link cannot carry a request, +`/readyz` answers `503` with `status: "pending"` and `link: {state: "unavailable", reason}`, so +`ocx ready --wait` keeps waiting until the link is back; a working link adds +`link: {state: "available"}` to the `200` answer. `/healthz` on a Child keeps meaning only that the +process runs. The reasons are listed in [Remote Link](/guides/remote-link/#link-status). + ### `ocx resolve [--json]` Resolve the runtime facts a shell needs without re-implementing them: the config home, the @@ -493,6 +531,11 @@ upstream status but never consumes or resumes a grant. ### `ocx sync [--restart-codex] [--restart-app-server-only]` +An explicitly requested restart also runs after a successful committed `config.toml` change, even if the catalog and cache bytes are unchanged. A configuration-only no-op, refusal, preflight, or rolled-back write does not trigger it. Without a restart flag, sync never automatically restarts Codex. + +On a Remote Link Child, `ocx sync` also applies the Home's Codex settings and, when the client runtime +started before that apply, restarts it; see [Remote Link apply parity](#remote-link-apply-parity). + Fetch the live model list from every configured provider and re-inject the merged catalog into Codex. Run it after adding a provider or to refresh available models. @@ -520,6 +563,46 @@ and a stale picker is not. When the command runs from inside the Codex app, the restart is handed off to a detached helper and this session ends with the app. +#### Sync only from the expected Home: `--expect-link` and `--expect-hub-key` + +```bash +ocx sync --expect-link [--restart-codex] +ocx sync --expect-hub-key [--restart-codex] +``` + +A script that reads `ocx connect status --json` and then runs `ocx sync` cannot tell whether the +machine was disconnected or relinked to another Home in between; a plain `ocx sync` would then +sync the local providers or the other Home's catalog. Passing the identity you read closes that +gap. `--expect-link` takes the `link.linkId` of a Remote Link Child and `--expect-hub-key` the +`apiKeyId` of a hub client. The check runs under the same lock that chooses the sync path, and +every later write is bound to the connection that passed it. + +When the identity holds, the command is exactly the connected sync above. Otherwise it changes +nothing (not even the automatic Codex shim repair other commands run first), never syncs the local +providers, exits **5**, and prints a human line followed by one JSON line as the last line on stderr: + +```json +{"phase":"child_link_mismatch","reason":"other_link"} +``` + +`phase` is `child_link_mismatch` for `--expect-link` and `hub_client_mismatch` for +`--expect-hub-key`. + +| `reason` | Machine state | +| --- | --- | +| `disconnected` | Not connected to any Home or hub | +| `invalid_state` | The client configuration or a connect marker cannot be read | +| `disconnect_pending` | A disconnect has started and not finished | +| `pending_operation` | A key rotation, a connect, or another sync is in progress (including one that holds the locks this check needs) | +| `hub_client` | `--expect-link` on a hub client | +| `link_client` | `--expect-hub-key` on a Remote Link Child | +| `other_link` / `other_key` | Connected, but to a different link or with a different key | + +A missing or malformed value, a repeated flag, or both flags together exit 2 before any state is +read. A connection that changes after the check, while the catalog is being fetched, still fails +as a plain connected sync does (exit 1, nothing written). Versions without these flags ignore +them, so check `ocx --version` first. + ### `ocx sync-cache [--restart-codex] [--restart-app-server-only]` Invalidate Codex's local model picker cache so it is rebuilt from the active opencodex catalog. The @@ -679,7 +762,7 @@ If the proxy is newer instead, check the CLI installation and `PATH` as describe | `restart` | The same refresh and a guaranteed restart on every platform. On macOS an unchanged, already-loaded job is kickstarted in place. Not an alias of `repair`. | | `start` | Start an installed service. | | `stop` | Stop the service and restore native Codex. | -| `status` | Report service and proxy diagnostics plus log paths. | +| `status` | Report service and proxy diagnostics plus log paths. `--json` prints the passive document described below. | | `uninstall` | Remove the service and restore native Codex. | | `remove` | Alias of `uninstall`. | @@ -687,6 +770,8 @@ On Windows, a bare `ocx service` runs the install path only after both Task Sche proven absent. If either status query is inconclusive, it refuses to register anything and asks you to run `ocx service status`; use explicit `ocx service install` only after confirming absence. +`ocx service status --json` prints one passive document for scripts and peers such as GOAT's device inventory. It runs the same reads as the text report and never installs, starts, stops or repairs anything; `--json` on any other `service` verb is rejected. Fields: `schemaVersion` (1), `platform`, `supported`, `backend` (`launchd`, `systemd`, `schtasks`, `winsw`, or `none` when nothing is registered), `label` (the job, unit, task or WinSW service name), `installed`, `enabled`, `running` (the service manager's verdict), `viable`, `stale`, `conflict`, `serving` (`probed`, `ok`, `port`; probed only when a registration exists), `summary`, `logPath`, `ownershipLease` and `desktopLogin`. An unsupported platform or a Docker container still gets a document, with `supported: false`. + ### Runtime ownership The OpenCodex desktop app can take the background proxy over from a CLI installation. When it does, @@ -1095,6 +1180,54 @@ target, mode, schedule and optional last/next-run facts. `job.status` describes existing idle/running state, not a cleanup started by this command. Read back on an unknown outcome rather than replaying the write. +## Remote Link apply parity + +A Remote Link Child applies its Home's OpenCodex-owned Codex settings when it is linked +(`ocx link add` on the Home) and on every `ocx sync`. Three facts let a peer check that the +Child really runs under the settings it applied, without reading either machine's configuration. + +`ocx status --json`, on any device, carries: + +| Field | Meaning | +| --- | --- | +| `appliedCodexSettingsDigest` | Lowercase hex SHA-256 of the applied settings (below). On a Child, the values its last recorded apply wrote. On a Home or standalone install, the settings document it serves its Children right now, computed from its current configuration. `null` on a hub client, on a Child with no recorded apply, and on a Child whose apply of changed settings is in progress or failed (the previous digest is withdrawn first, so it is never reported for values Codex no longer has). | +| `lastApplyAt` | Child only: epoch milliseconds of the last successful apply that changed the applied settings. Re-applying the same values is not a new apply and keeps it, so an unchanged `ocx sync` never forces a restart. `null` elsewhere. | +| `runtimeStartedAt` | Child only: epoch milliseconds the running client runtime started. `null` when no client runtime answers. | + +The digest is computed over this compact UTF-8 JSON, with the keys in exactly this order and no +whitespace, from the values actually applied: + +```json +{"version":1,"settings":{"fastMode":true,"multiAgentMode":"v1","webSearchSidecarEnabled":false,"subagentDefaults":null}} +``` + +`subagentDefaults` is `null` when unset, otherwise `{"model":…,"reasoningEffort":…}` in that order. +The example above hashes to `57c751eff89844f93db71a6ee4acdac03afbc29fec52734ade80a40193e826cd`. The digest +never contains a setting value, and the Home's name and version are not part of it, so a Home and +a Child that applied the same values report the same digest. + +On the Home, every row of `ocx link status --json` carries what that Child last reported: +`lastApplyAt`, `runtimeStartedAt`, `appliedCodexSettingsDigest` (same meanings as above) and +`reportedAt` (epoch milliseconds, the Home's clock). A linked Child reports shortly after its runtime +starts and then once a minute; the Home keeps reports in memory only, so after a Home restart the +fields read `null` until the next report. A Child whose runtime started before `lastApplyAt` is +running an older runtime than its last apply. + +For a link the Home added, each row also carries `tunnel`: `attempt` (ssh attempts since the link +was last connected), `nextRetryAt` (ISO time of the next scheduled retry, `null` while one runs or +none is due), `lastTransition` (`{from, to, at, cause}`) and `lastFailure` (`{cause, at}`). A cause +is one of `auth`, `hostkey`, `forward`, `remote_forward_conflict`, `timeout`, `network`, +`unknown`, `spawn_failed` and `listener_unavailable`. The Home keeps it in memory, so it starts +empty after a Home restart. `GET /api/link/status` includes the field only when asked with +`?diagnostics=1`, because an older `ocx link status` rejects fields it does not know. + +After `ocx sync` applies the Home's settings on a Child, it restarts the Child's client runtime when +that runtime started before `lastApplyAt`, using the same restart as `ocx restart`. A runtime that +already started after the apply is left alone, nothing is started when no client runtime is +running, and a failed restart exits nonzero while the previous runtime keeps serving; run +`ocx sync` or `ocx restart` again to retry. Two applies on one Child never interleave: a second +`ocx sync` while one is applying fails fast and changes nothing. + ## Forced link revoke and remote recovery Only when skipping remote cleanup is explicitly intended, use the actual selected @@ -1139,3 +1272,13 @@ Use `ocx connect --pairing-code-stdin`, `ocx connect status`, `ocx sync`, On Linux, `ocx service install` checks `loginctl show-user -p Linger` before printing boot advice. Enabled linger is reported as already enabled. Disabled linger gets the `loginctl enable-linger $USER` hint; missing or unreadable diagnostics state the uncertainty and do not fail installation. On macOS, `ocx service status` distinguishes a loaded Desktop login item from a valid enabled registration awaiting the next login. `ocx status` credits the latter in restart safety when the same Desktop app currently supervises the proxy. An incorrect plist label or command, a disabled entry, an unverified load probe, or changed process identity still cannot earn protection. + +## Custom Codex provider consent + +`ocx codex-provider status` lists the provider IDs approved for the selected Codex home. +`ocx codex-provider allow ` asks before allowing URL-only management on start/sync; +`--yes` is explicit noninteractive consent for that named provider. +`ocx codex-provider revoke ` restores an unchanged managed URL and revokes permission. +These commands are local and do not require a running proxy. See +[custom provider routing](/guides/codex-integration/#opt-in-a-custom-provider-to-managed-local-routing) +for eligibility, restore behavior and transport preservation. diff --git a/docs-site/src/content/docs/reference/cli/messaging.md b/docs-site/src/content/docs/reference/cli/messaging.md index 65df17bd87f..b64b25e7255 100644 --- a/docs-site/src/content/docs/reference/cli/messaging.md +++ b/docs-site/src/content/docs/reference/cli/messaging.md @@ -1,9 +1,9 @@ --- -title: Local Codex Messaging -description: Discover loaded local Codex sessions and submit one correlated queued message. +title: Codex Messaging +description: Queue correlated messages to loaded Codex sessions locally or through explicit SSH peers. --- -`ocx message` is an opt-in, command-local surface for existing Codex sessions. +`ocx message` is an opt-in surface for existing Codex sessions. It starts no proxy or app-server, resumes no thread and installs no skill. Linux/macOS Unix sockets are supported; Windows fails explicitly. The existing Codex daemon must support experimental `thread/queue/add`. No CLI version pin or @@ -85,5 +85,182 @@ The final loaded-target check and queue submission are not atomic: if the target unloads afterwards, an explicit rejection is `not_sent` with `queue_rejected`; a lost or invalid reply is `unknown`. OpenCodex does not resume it or retry. -No remote hosts, bearer management, SSH, Claude messaging, -dashboard, isolation or idle-notification controls are part of this surface. +## Explicit remote peers + +Remote messaging is independent of Remote Link and normal proxy startup. It does +not redirect providers, share provider credentials or start a native daemon. +Both nodes need a compatible OpenCodex CLI and an existing queue-capable daemon. +Linux has isolated interoperability evidence; macOS requires its own qualification. +Windows remote transport is unsupported. + +On each node, explicitly enable messaging. On the receiving node, keep the +foreground owner running: + +```bash +ocx message enable --port 39176 --json +ocx message serve --json +``` + +On the initiating node, use your existing OpenSSH destination/alias. Check the +offered fingerprint against a trusted source before confirming it: + +```bash +ocx message hosts probe --ssh worker --json +ocx message hosts add worker --ssh worker --fingerprint 'SHA256:' --json +ocx message hosts list --json +ocx message serve --host worker --json +``` + +Private probe records keep their alias-prefixed format even when your normal SSH +configuration enables `HashKnownHosts`; your normal known_hosts file is not changed. +Each enrollment attempt uses its own transaction-qualified pinned-key scratch file, +removed after its SSH helper settles, including failures, without depending on another +mutation owner's lock. If cleanup cannot be confirmed, the command reports an unknown +storage outcome, not success. The recovery journal is separate. + +`hosts add worker` authorizes outbound messages to `worker`, **not arbitrary messages +back into this initiating node**. The SSH control exchange authorizes that outbound +direction at the receiving node using your existing SSH login. The enrollment receipt +and `hosts list` show each node's local inbound policy. On the initiating node the +default is `responses-only`: the peer can see and reply only to original local sender +sessions awaiting a correlated response to a request this node sent. A response must +match the request UUID, enrollment and original local destination; it cannot target +another session or become an unsolicited request/notification. + +To grant broader access on either node, explicitly run on that node: + +```bash +ocx message hosts allow-inbound --json +# Withdraw the broad grant without removing enrollment: +ocx message hosts deny-inbound --json +``` + +`allow-inbound` permits all three message kinds into **any loaded local Codex session** +and exposes their directory metadata to that peer. It is not a responses-only grant. +Consent changes apply to existing connections before subsequent reads/submissions. +Older records without a local inbound policy default to `responses-only`, not broad access. +Reply routes are bounded to 256 outstanding requests per foreground owner and expire +after 24 hours or owner retirement/restart. No sender context means no reply route. +A queued or uncertain response consumes its route; a definite native rejection does not. +An expired/lost route requires explicit operator/sender reconciliation, never automatic replay. + +Each `serve` is foreground-owned: keep it running and use a separate terminal +for messaging. At most four distinct `--host` selections may be supplied. Nothing +installs a service or starts an owner automatically. SSH uses confirmed pinned +host keys and your configured keys/agent; password prompts are disabled. The +wrapper owns loopback-only forward and return routes, so the receiving node can send +correlated replies without an SSH login back to the initiator, subject to the policy above. + +```bash +ocx message status --json +ocx message sessions --host worker --json +printf '%s\n' 'Please review and reply using the supplied route.' | + ocx message send --host worker --name reviewer --stdin --json +``` + +`--host` selects an exact enrolled alias or machine UUID. Reply commands include +the validated source machine UUID and thread UUID; source identity is resolved on +the source daemon, not guessed from a remote directory. Without sender context, +there is no invented reply command. Local commands without `--host` retain their +no-state/no-SSH/no-listener path. +The receiver validates the complete canonical envelope against the authenticated +machine and native message UUID, including reply host/command and standard peer guidance. +A peer cannot label the wrapper as another machine or omit its non-user-message warning. +Session names and thread IDs remain descriptive claims by that machine, not per-session authentication. + +Receiver-issued directional capabilities live in private messaging state under +`OPENCODEX_HOME` (or the default OpenCodex configuration home). They never appear in +normal command output, helper arguments, URLs or message envelopes. Mutual proofs +authenticate the actual WebSocket connection before native/body frames. A separate +health probe is not sufficient identity evidence. This is an OpenCodex gateway +protocol: raw `codex queue --remote` does not implement its authentication handshake. +The gateway uses the same native experimental `thread/queue/add` contract; it does +not alter Codex's own `--ws-auth`, configuration or native bearer-token listeners. +It exposes only loaded metadata and text queueing, not general app-server control. + +Status distinguishes enabled configuration, a running owner and initiated/leased +routes. Return leases expire after 25 seconds without refresh; they are not delivery +or processing receipts. Losing a route or changing owner configuration requires +explicit owner restart. Transport recovery never resends an uncertain message. + +```bash +ocx message hosts remove worker --json +ocx message disable --json +``` + +Removal revokes local admission first; an unconfirmed remote cleanup exits 3 and +requires removal on that peer too. Each outgoing removal uses an independent pinned-key +scratch file; a failure preparing that file still reports local removal and unconfirmed +remote cleanup. Scratch cleanup is attempted on both success and failure. Generated cleanup guidance uses +`hosts remove --transaction --json`; the transaction +guard refuses an old receipt rather than removing a newer enrollment. Ordinary removal +without this optional guard still removes the currently selected peer. +Disable retires the current owner generation; +dispatch refuses new work immediately, and the foreground owner notices changed +configuration within its ten-second maintenance interval. The sender checks local +authority again immediately before dispatch; revocation after dispatch cannot recall +an in-flight message or already accepted native work. A lost enrollment response retains one private +transaction: repeat the exact same add command to reconcile, rather than enrolling +under another name within the same enabled generation. Disable/re-enable or port +changes invalidate that recovery generation. Completed or revoked transactions cannot +restore a removed peer or erase another enrollment's recovery record. +If the saved local journal has malformed JSON, record shapes or field values, +`hosts add`, `hosts list`, `status` and `hosts abandon` refuse with +`invalid_enrollment_journal`. Its bytes are preserved for explicit operator recovery; +OpenCodex does not repair, replace or delete it automatically. `hosts add` sends no +enrollment control request in this case, although its preliminary host-key probe may run. +Unsafe storage or a busy mutation lock retains its separate error classification. +If a valid journal can no longer be reconciled, inspect `ocx message hosts list --json` or +`ocx message status --json` for `pendingEnrollment` (alias, transaction UUID and whether +its generation is stale), then explicitly abandon that exact local journal: + +```bash +ocx message hosts abandon --transaction --json +``` + +Abandonment works while disabled too. It leaves existing peers unchanged, does not retry +enrollment and exits 3 because remote cleanup is unconfirmed. Follow its cleanup guidance +on the remote node if an orphan enrollment remains. Stale journals are not silently deleted. +A full receiver returns `peer_capacity` without enrolling; free a slot and repeat the same +transaction or explicitly abandon it. Malformed or lost control replies remain uncertain. +Existing trusted 0755 configuration homes are accepted without chmod; messaging's own +subdirectory remains private. An immediate owner restart can replace a stale return lease +after the new endpoint authenticates and the old endpoint no longer authenticates within a +one-second check. A still-authenticating old endpoint retains its lease until expiry; +temporary unreachability can permit takeover, not replay. Unexpected tunnel loss visibly +retires the foreground owner; deliberate peer removal leaves unrelated routes running. +Crash mutation locks fail closed and need operator recovery; +the wrapper never guesses that an old lock is abandoned. +State and recovery-journal publication sync the file and containing directory before dependent +remote work; journal removal also syncs its directory. `remote_storage_unknown` (exit 3) means +the storage change's crash durability could not be confirmed. On macOS, an ACL refusal or probe +failure after file replacement also reports this code: the new state is already visible, but its +ACL privacy and crash durability are unconfirmed. Inspect state and permissions before explicit +recovery rather than automatically retrying or rolling back. A safely readable journal with malformed +JSON or schema does not block peer removal, but is retained for operator recovery; unsafe storage and +failure to invalidate a matching journal still refuse removal. + +The foreground owner shares finite connection/request/helper/buffer limits across +all peers and rejects overload without an unbounded queue: enrolled peers together may +hold twelve authenticated connections, four more stay reserved for the owner's own +control, outgoing probes and renewal, and at most eight tunnel +children and two transient control/inspection helpers, ten total. Setup is bounded by +30 seconds across the whole selected peer batch. A final lease renewal precedes startup +success; final and periodic renewal batches have five-second deadlines, within the setup +deadline where applicable, and periodic maintenance runs every ten seconds. +Cancellation stops only owned +SSH helpers/listeners; it leaves Codex daemons, threads and unrelated proxies alone. +Cleanup joins withdrawn tunnel routes too. Direct-child exit and natural pipe EOF must +both settle within the three-second cleanup ceiling; otherwise cleanup is incomplete and +the helper reservation remains charged. Cancelling retained pipes does not prove descendants +exited. Detached descendants or descendants that close inherited pipes are not tracked. +Helper stdout capture is limited to 64 KiB; stderr is discarded without being printed or stored. +After capture overflow, the helper is stopped and discarded output is drained to natural EOF. +Successful cleanup reports the original output error; genuinely incomplete cleanup remains +`cleanup_incomplete` and keeps its helper reservation charged. +Same-uid processes can read private enrollment state; machine authentication does +not establish non-forgeable per-agent identity. Native traffic bypassing this gateway +is not controlled by it. + +Mesh, dashboard/cache controls, isolation, Claude messaging, managed skills and +idle notifications remain outside this contribution. diff --git a/docs-site/src/content/docs/reference/cli/providers-accounts.md b/docs-site/src/content/docs/reference/cli/providers-accounts.md index 27387b52951..8c0c237f941 100644 --- a/docs-site/src/content/docs/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/reference/cli/providers-accounts.md @@ -24,7 +24,7 @@ both `--adapter` and `--base-url`. | `list` | `--json`, `--jsonl` | List configured providers and the remaining registry entries; `--jsonl` emits one configured provider object per line. | | `add ` | `--adapter `, `--base-url `, `--api-key `, `--responses-path `, `--auth-mode `, `--default-model `, `--set-default`, `--force`, `--json`, `--sync`, `--live` | Save locally by default; `--sync` attempts client sync in either output mode. `--live` writes through the running proxy and cannot combine with `--sync`. `--force` permits overwrite. | | `edit ` | provider field flags, `--headers `, `--model-context-tier `, `--upstream-http-version `, `--fast `, `--context-window `, `--json` | Edit validated live provider fields without replacing key pools. Repeat `--model-context-tier` for multiple Copilot models. `--headers` merges custom request headers; pass `{}` or `-` to clear them. | -| `test ` | `--json` | Probe the real upstream model endpoint. | +| `test ` | `--model `, `--yes`, `--json` | Check discovery; with `--model`, request one confirmed completion test. | | `show ` | `--json` | Show config with API keys masked. | | `remove ` | `--json`, `--live`, `--yes` | Local removal refuses the default and last provider. Live removal requires `--yes` and uses server dependency checks, default reassignment and account/custom-model cleanup. | | `set-default ` | `--json`, `--live` | Select a local default, or update the running proxy with `--live`. | @@ -54,6 +54,13 @@ ocx models live --provider ark --json Use it for scripts that process one configured provider object per line. `--json` and `--jsonl` cannot be combined. +Each configured row also has `active`. It is `false` when this device is linked to a Home +(a Remote Link Child or a Hub-mode client): the row is local configuration that the Home's +providers shadow, so it does not serve requests while the link is connected. It is `true` when the +row actually serves, including when the link state cannot be read. Rows never carry credential +values: `baseUrl` drops embedded userinfo and masks credential-shaped query values such as `key` +or `token`, while keeping ordinary parameters such as `api-version`. + :::caution[Custom headers are not a credential channel] `--headers` is for non-secret request metadata — routing hints, tenant or project selectors, tracing ids. It is **not** a place to put authentication @@ -120,10 +127,37 @@ notices. Null, absent or `not-requested` refresh can exit 0 without a client syn Read back before recovery; do not repeat a persisted write to repair convergence. Errors use safe prose on stderr, including in JSON mode, rather than a JSON error envelope. -`provider test` is separate: it checks upstream model discovery connectivity. A failed connection exits 1; success and a static catalog with no applicable discovery endpoint exit 0. +`provider test ` without `--model` checks upstream model discovery connectivity. A failed connection exits 1; success and a static catalog with no applicable discovery endpoint exit 0. `applicable: false` is an expected static-catalog result, not successful inference or a failed connection. +To send one completion request for a chosen model, use: + +```bash +ocx provider test my-relay --model my-model +ocx provider test my-relay --model my-model --yes --json +``` + +This test may incur provider charges. An interactive terminal asks for confirmation; +`--yes` accepts the charge warning. Non-interactive use and `--json` require +`--yes`. Using `--yes` without `--model` is a usage error. Declining confirmation +sends no request and exits 1. The command uses the running proxy's management API. + +The completion test sends the fixed prompt `Reply exactly OK.`, requests a 32-token output cap, stops after 20 seconds or 64 KiB, and never retries or fails over. +It supports key-auth or local OpenAI Chat and Responses providers, including +per-model wire overrides. Disabled, OAuth, forwarded-auth and unsupported adapter +or transport configurations return an applicability reason without sending a completion. +TLS profiles, HTTP version pins, enabled request pacing and custom executors with +a provider proxy override are currently unsupported. + +Results include time to first text delta (TTFT), total latency, the requested and +wire model ids, sanitized upstream model metadata and a distinct match, mismatch +or unknown comparison. Missing or conflicting metadata is unknown. The comparison +uses the upstream id before masking, against the id actually sent after alias and +suffix handling. Provider-reported metadata cannot verify model identity: +`identityVerified` is always `false`. Generated text is not returned. A failed +completion exits 1; success and a not-applicable result exit 0. + ### Transport settings and request pacing `provider edit` is already live. Use `--upstream-http-version http1.1` to pin that @@ -218,6 +252,13 @@ as OrcaRouter are reused until the provider revokes them); API-key login provide key, validate it when possible, and save the resulting provider config. The command prints the currently accepted OAuth and API-key provider ids when the name is missing or unknown. +Failed OAuth logins exit with status 1 and print a fixed diagnostic: the HTTP status and standard +OAuth error code (such as `invalid_grant`) when the provider returned one, `network_error` or +`timeout` when the provider could not be reached, `configuration_error` with the reason when a +local setting such as a Codex account namespace blocks the login, and `oauth_authentication_failed` +otherwise. Pool 2 (`anthropic2`) also reports `configuration_error` before prompting when a custom +provider owns that name or its config file cannot be read or parsed. + Use the same command to **reauthenticate** after `ocx status` / `ocx doctor` reports reauthentication required or a terminal refresh failure (or use Reauthenticate in the dashboard). Codex pool accounts are not one of those OAuth or API-key providers, but `ocx login codex` reaches @@ -238,8 +279,18 @@ ocx login xai ocx login anthropic ocx login orcarouter-oauth # browser consent + S256 PKCE ocx login orcarouter # paste an existing API key +ocx login anthropic --headless # no browser, no local port ``` +`--headless` runs the **code-display** login for hosts whose browser cannot reach the proxy's loopback +callback (containers, remote servers). The command prints an authorization URL; approve it in any +browser, and the provider shows a `code#state` string to paste back at the prompt. No callback port +is bound and no browser is launched. Only `anthropic` and `anthropic2` support it today; any other provider exits +with the list of providers that do. For Codex accounts use the device-code login instead, +`ocx account login codex --device` — `ocx login codex --headless` is refused rather than passed on. +The paste window is 10 minutes, and each login accepts one code: if the exchange fails, start a new login. A successful login becomes the provider's active account, exactly +like a browser login. + OAuth reauthentication preserves operator settings such as model selections, pricing overrides, and account failover preferences. Login-owned transport/authentication fields and registry-owned catalog metadata are refreshed. A live-discovery provider keeps its selected default model; a @@ -302,7 +353,7 @@ Only fields named by `supported` may be written; null is not false, and an inert policy is not active pooling. Threshold zero and `enabled:false` are independent. Use `--enabled on|off`, `--threshold 0..100`, `--strategy`, `--sticky 1..100`, and `--quota-window` only where supported. OpenAI does not support pool enabled/window -writes; `reset-first` is OpenAI-only, `least-loaded` Kiro-only, and Anthropic windows +writes; `reset-first` is for OpenAI and generic OAuth pools (never Anthropic), `least-loaded` Kiro-only, and Anthropic windows are `five-hour`, `weekly`, `max-utilization`. Omitted fields stay unchanged. Read DTO fields such as routes, per-account concurrency or Anthropic `nativeMessages` do not imply a setter in this command; use only the listed CLI options. A save that @@ -351,6 +402,32 @@ grant counts, nullable dates, cooldown, pending operation and journal availabili Empty grants are not the same as unavailable status. It never consumes a grant or resumes a pending spend. Consumption remains a human GUI operation. +### Use remaining included quota + +To keep using a selected account past a proactive switching threshold or the main-account +protection threshold, explicitly enable its remaining-quota preference: + +```bash +ocx account use-remaining openai main on +ocx account use openai main +ocx account use-remaining openai main status --json +ocx account use-remaining openai main off +``` + +Replace `main` with a unique pool-account ID or alias to target an added account. +Enabling this disables proactive usage switching for that account. For the main login, it also +moves both effective hard-lock thresholds to 100%, so a 95% reading can still admit a request. +The preference persists across restarts until turned off; original threshold settings remain +stored and resume applying when it is disabled. It does not select an account itself or override +round-robin scheduling, manual/automatic pause, entitlement checks, reauthentication, or upstream +cooldowns. Paid-credit permission remains a separate setting; enabling this never opts an account +into credits or redeems a reset grant. The default-on main hard lock still refuses at 100%. + +`GET /api/codex-auth/accounts/use-remaining?id=` reads the live policy. +`PUT /api/codex-auth/accounts/use-remaining` accepts `{ id, useRemainingQuota: boolean }`. +Both require the existing management authentication. This control is currently exposed through +the CLI and API. + ### Main-account quota protection In **Codex settings → Multi-auth → Advanced settings**, **Block main account** @@ -374,6 +451,22 @@ percentages, for example `{ "short": 90, "long": 98 }`. Both must be integers fr and `short` must not exceed `long`; omitted fields use the defaults. The settings API accepts the same object and returns the effective values in `mainAccountHardLock.thresholds`. +**While the lock blocks, requests can fall back to another model instead of failing.** The +**Fallback while blocked** row sits beside the protection switch and works like the shadow-call +row: picking a model — for example `deepseek/deepseek-chat` — turns the reroute on immediately, +the optional thinking effort applies to it, and the switch pauses the reroute without forgetting +the model. While the block is in force and no Codex account can serve a request for a native Codex +model, that request is routed to the fallback model (with the configured effort) instead of being +refused. A usable Pool account still serves the request first; provider-qualified selectors, +combos, account-qualified rows and Luna Reserve keep their own destinations; and a fallback that +itself names a native Codex model is ignored, because rerouting gpt-* to gpt-* would only re-enter +the same refusal. The lock itself is unchanged: it releases on its own as before, and the reroute +ends with it. Configure `codexMainAccountHardLockFallback` in `config.json` as +`{ "model": "deepseek/deepseek-chat", "reasoningEffort": "low" }`; `enabled: false` pauses the +reroute while keeping the model. An absent section, an omitted model, or a malformed section means +requests are refused as usual; `reasoningEffort` must be a declared effort. The settings API +accepts the same object, rejects invalid writes before mutation, and `null` clears the section. + The dashboard and `ocx status` show **possible usage outside opencodex** when fresh readings rise by at least one point in the same reset episode without recent main-account activity through this proxy. This is an advisory observation, not proof: a long-running turn can also cause it. The lock @@ -384,9 +477,10 @@ reset changes and expires after six hours or at the observed reset, whichever co An unreadable 5h reading cannot hide a weekly block. Unknown usage does not fabricate a zero, and a missing reading does not erase an already measured blocking tuple. A predicted reset time alone does not unlock it. -While blocked, the minute sweep waits for the latest known blocking reset, then checks owned usage. -If no future reset is known or a check remains blocked, recovery uses a capped 5/10/20/40/60-minute -schedule; a longer `Retry-After` also delays profile and token preparation. Only fresh valid usage or authoritative window-absence evidence +While blocked, recovery checks owned usage metadata on a capped 5/10/20/40/60-minute schedule, so a reset +redeemed outside opencodex is noticed before the predicted reset; the first check waits five minutes, checks +stay at least five minutes apart, and the next check is pulled in to the latest known blocking reset. A paused +or reauthentication-required main receives no checks before its predicted reset. A longer `Retry-After` also delays profile and token preparation. Only fresh valid usage or authoritative window-absence evidence can lift the block. In Pool mode, a quota `--refresh` bypasses cache freshness but still honors failed-read pacing; a deferred read makes no new diagnostic attempt. Other pause, reauthentication, and upstream limits remain independent. @@ -580,6 +674,13 @@ changes Orca files, or changes the active pool account. ### `ocx account list [provider] [--json] [--all] [--quota [--refresh]]` +Stored ChatGPT pool accounts remain listable on hosts without a native Codex home, including in the +dashboard and on account refresh. Listing does not create a Codex home or sign out the native main +account. Disabling the Codex client integration does not disable an existing native login's quota +observation; invalid explicit `CODEX_HOME` paths still report a configuration error. Unsafe +`OPENCODEX_HOME` storage reports an error even when the native Codex home is missing. +Default-home resolution also reports errors for files, dangling links, and parent directories that cannot be traversed. + Without a provider, lists the Codex pool, OAuth accounts, and configured API-key pools. Empty providers are skipped unless `--all` is present. With a provider, lists only that credential family. Human output uses `PROVIDER TYPE ID PLAN/LABEL PRIORITY STATUS`; a manually chosen Codex row is marked @@ -597,6 +698,14 @@ returns: { accounts: AccountRow[], notes: string[] } ``` +Codex JSON rows include `slotId` (the registered `id`) and `slotKind` (`native-main` or +`pool`). When the proxy can verify both the upstream account and user identity, it also +includes `upstreamAccountKey` (`uai1_` followed by 64 lowercase hexadecimal characters). +Equal keys within the `openai` provider identify slots for the same upstream user; keep +each slot and its quota state separately. The key is local to this installation and can +change when its administrator token rotates. If it is absent, do not infer a match from +masked email or quota values. Raw upstream IDs, user claims and credentials are not listed. + An empty provider listing names the next command in human output and JSON `notes`: `ocx account login ` for OAuth/Codex, or `ocx account add-key ` with a human-controlled piped stdin source for API keys. An empty global listing @@ -638,7 +747,9 @@ from that account's own credential and Cloud Code Assist project id. The per-acc talks to Google's Cloud Code Assist host through the pinned outbound transport, regardless of a configured `baseUrl`: a custom base URL is a routing choice for requests, not a second source of Google's accounting for a stored credential. An account without a project id, or one whose probe -is redirected or fails, shows `unavailable`. +is redirected or fails, shows `unavailable`. When fallback model discovery supplies quota readings, +detailed displays retain up to 256 model diagnostics with distinct bounded labels. These rows do not +affect provider summary headlines, severity, account ranking, routing, or reset notifications. Devin rows come from Cognition's `GetUserStatus` for that account's own key, sent to its allowlisted api-server host. If an older credential has no host, the probe uses the configured @@ -676,6 +787,8 @@ separately billed sends add to the request spend. Credits are never estimated fr With two or more Kiro accounts logged in, request-rate, confirmed monthly-quota, and confirmed suspension refusals can rotate before output. Monthly exhaustion excludes only that login until reset or evidence expiry; completed service clears an older verdict. +Quota refusals include the exact `MONTHLY_REQUEST_COUNT` and `OVERAGE_REQUEST_LIMIT_EXCEEDED` +reasons on HTTP 400 or 429. Plans allowing overage can keep serving after the monthly gauge reaches 100%. Reactive rotation remains available when proactive account preference is off. Accounts are added one at a time — `ocx account login kiro` hands off to the Kiro CLI and appends the new account to the pool. In the dashboard, Login and Add account also offer Builder ID, Google, and GitHub device login alongside the Kiro CLI choice; native device login adds an account without signing the CLI out. Users of one organization (IAM Identity Center) profile share its profile ARN, so a Kiro CLI account is identified by that profile together with the email `kiro-cli whoami` reports for the session. Two users of the same organization therefore stay separate accounts, and signing the same user in again updates that user's account. When `kiro-cli whoami` reports no user, the profile alone identifies the account as before. If that profile already has an account with a known user, such a login only refreshes the account whose stored Kiro CLI session it continues; any other login is refused instead of replacing an account, and an Add account login switches the Kiro CLI back to its previous session. Run `kiro-cli whoami` to confirm the signed-in user, then retry. Reauthenticating an account saved before its user was known records the user that signs in, unless that user already has another account of the same profile. @@ -734,7 +847,9 @@ Clear the manual Codex account selection without resolving an account id, so it ### `ocx account pause|resume [--json]` -Pause or resume one account in the Codex, Anthropic, or generic OAuth provider pool, including +Accounts paused by the opt-in [per-provider auto-pause policy](/reference/configuration/providers/#auto-pause-permanently-refused-accounts) use the same commands. Listing shows the fixed pause reason; resume clears it. + +Pause or resume one account in the Codex, Anthropic, generic OAuth, or API-key provider pool, including `google-antigravity`. For the Codex pool, `main` identifies only the built-in Codex account; OAuth accounts must be identified by id or a unique alias. A paused OAuth account is excluded from request selection, 429 failover, and proactive token refresh, and cannot be @@ -815,12 +930,50 @@ openai: { provider, autoSwitchThreshold: number, enabled: boolean } generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } ``` +### `ocx account preserve-signed-in openai [on|off|status] [--apply-to-bound] [--json]` + +Opt in to **use other eligible accounts first and keep the signed-in ChatGPT/Codex Desktop +identity as fallback**. A bare invocation or `status` reads the running proxy's policy, affected +entry IDs, manual pin and next unbound choice. `on` persists `codexPreserveSignedInAccount: true`; +`off` restores your existing custom account order without rewriting any priorities. + +The reserved group includes native `__main__` and ordinary pool entries with the same selected +ChatGPT account/workspace **and** normalized member email. Sharing a workspace alone is insufficient. +The group follows the latest physical login observed by the proxy's native-login lifecycle; +no incoming request credential can define it. If member/workspace evidence is unavailable, +`identityKnown: false` reports that only main can be identified. Status obtains a fresh owned +observation; a busy or unreadable native profile returns 503 without applying the change. + +All four Codex strategies prefer other identities with quota headroom, preserving custom order +within each group. Reserved entries remain fallback when preferred tiers drain. **Fallback can +consume the signed-in quota**, subject to existing model access, pause, cooldown, reauthentication, +hard-lock and paid-credit consent checks. This policy does not grant paid-credit consent. + +Healthy existing bindings stay in place, and manual pins and exact account-qualified model routes +override the preference. To apply it to ongoing work, run: + +```bash +ocx account preserve-signed-in openai on --apply-to-bound +``` + +This explicitly releases only affected bindings and any affected manual pin for the **next request**; +in-flight requests keep their captured account. Unrelated bindings and pins remain. Moving work may +lose its warm prompt cache, and account-scoped uploads or continuation state can prevent a move. +`codexAccountPriorityFailback` remains a separate opt-in, with its existing quota-strategy, +positive-threshold and fresh-observation conditions; it can also move work toward a preferred group. + +For **never use this identity through the pool**, use `ocx account pause openai main` instead: +linked pause excludes main and matching pool entries without changing the desktop login file. +Only requests that reach OpenCodex are controlled. This does not unlock an already-disabled +desktop composer or promise to fix client-side quota gating. + ### `ocx account priority [<-100..100|first|earlier|normal|later|last|reset>] [--json]` Reads or sets one Codex pool account's selection order: **higher is used earlier**, the default is `0`, and the range is `-100` through `100`. Only the `openai` Codex pool is ordered, so other providers exit 1. `main` targets the Codex Desktop login, which is ordered like any other pool -account — `ocx account priority openai main last` is how you keep it as the reserve. +account. `ocx account priority openai main last` lowers only that entry; use +`preserve-signed-in` above to reserve its matching ordinary pool entries too. Preset words stand in for small integers: `first` is `+2`, `earlier` is `+1`, `normal` is `0`, `later` is `-1`, and `last` is `-2`. `reset` returns the account to the default and drops its stored @@ -1041,6 +1194,12 @@ verified official-price fallbacks. Unknown models return `null`; no price is inv Automatic defaults are derived on read and do not populate `modelCosts` in your config, so catalog updates remain effective. Use `set-price` to save provider-specific rates. +`price --json` also preserves a saved `promptLengthPricing` policy in `cost`; the four +`effectiveCost` rates are the base rates, before evaluating a request's prompt length or tier. +Use **Models → Price** or the management API to edit a prompt-length policy. Existing `set-price` +commands replace the four base rates and keep the model's saved Flat or Custom threshold policy; +`--auto` removes both the rates and any policy. + Discover command syntax with `ocx models --help` before live work. `list-custom` reads local configuration; `add` and `remove` save locally unless given `--live`. Local custom writes opportunistically synchronize when a proxy exists. The remaining diff --git a/docs-site/src/content/docs/reference/code-signing.md b/docs-site/src/content/docs/reference/code-signing.md new file mode 100644 index 00000000000..a85a1b381cf --- /dev/null +++ b/docs-site/src/content/docs/reference/code-signing.md @@ -0,0 +1,101 @@ +--- +title: Windows Code Signing +description: How OpenCodex signs its Windows binaries through SignPath Foundation, how to verify a signature, and the project's code signing policy. +--- + +OpenCodex is moving its Windows downloads, the standalone `ocx.exe` and the desktop installer +`OpenCodex--windows-x64.msi`, to Authenticode code signing through +[SignPath Foundation](https://signpath.org). A signed file tells Windows, SmartScreen and antivirus +products who published it and that nobody changed it after the build, which is what stops +unsigned-binary false positives like the one reported in +[#7207](https://github.com/lidge-jun/opencodex/issues/7207). + +Signing is rolling out. The release workflow is wired, and SignPath is validating it with a test +certificate. Until the production certificate is issued, Windows release files stay unsigned, and +SmartScreen may ask once (**More info → Run anyway**). Releases published after that switch carry +a signature from **SignPath Foundation**. + +## Code signing policy + +Free code signing provided by [SignPath.io](https://about.signpath.io/), certificate by +[SignPath Foundation](https://signpath.org/). + +- Committers and reviewers: [lidge-jun](https://github.com/lidge-jun) +- Approvers: [lidge-jun](https://github.com/lidge-jun) + +Every signing request for a release is approved by hand in SignPath before the certificate is +used. Only binaries built from this repository's source by GitHub Actions are signed. Third-party +files bundled with a release, such as the prebuilt keyring addons, keep their upstream form; the +SignPath Foundation policy forbids signing them with the project certificate. + +### Privacy + +This program will not transfer any information to other networked systems unless specifically +requested by the user or the person installing or operating it. In practice, OpenCodex forwards +requests to the providers you configure. To look for new versions it also reads release +metadata from GitHub and the npm registry, and those checks carry none of your prompts, +credentials or usage data. + +## Verifying a download + +In PowerShell, check the file you downloaded: + +```powershell +Get-AuthenticodeSignature .\OpenCodex--windows-x64.msi | Format-List Status, SignerCertificate +Get-AuthenticodeSignature .\ocx.exe | Format-List Status, SignerCertificate +``` + +A signed release reports `Status : Valid` and a signer certificate whose subject names +`SignPath Foundation`. In Explorer, the same information is under **Properties → Digital +Signatures**. Every release file also has a `.sha256` next to it on the release page, which you can +compare with `Get-FileHash -Algorithm SHA256`. + +## What gets signed + +| File | Signed contents | +|---|---| +| `ocx--bun-windows-x64.zip` | `ocx.exe` inside the archive. The zip and its `.sha256` are built from the signed binary. | +| `OpenCodex--windows-x64.msi` | The installer, plus every `.exe` it installs (the desktop app and its bundled `ocx.exe`). | + +The desktop updater signature (`.msi.sig`) and `latest.json` are produced from the signed +installer, so in-app updates install the same bytes the release page offers. + +## For maintainers + +The release workflow (`.github/workflows/release.yml`) signs through the official +`signpath/github-action-submit-signing-request` action. It reads one secret and three repository +variables: + +| Name | Kind | Value | +|---|---|---| +| `SIGNPATH_API_TOKEN` | secret | API token of the SignPath CI user | +| `SIGNPATH_ORGANIZATION_ID` | variable | SignPath organization ID | +| `SIGNPATH_PROJECT_SLUG` | variable | `opencodex` | +| `SIGNPATH_SIGNING_POLICY_SLUG` | variable | `test-signing` today; `release-signing` once the certificate is issued | + +SignPath artifact configurations: `windows-standalone-zip` (one `ocx.exe` at the zip root) and +`windows-msi-zip` (one `.msi` at the zip root, deep-signed). + +Signing never blocks a release. What a run does depends on the policy: + +| Policy | Dry run | Real release | +|---|---|---| +| No token, or an incomplete configuration | Unsigned | Unsigned, with a notice or warning | +| `test-signing` | Signed with the test certificate, kept only as a workflow artifact | Unsigned, with a notice; a test certificate never ships | +| `release-signing` | Signed and verified as trusted | Signed and verified as trusted | + +If a signing request fails, times out or is rejected, or if verification fails, the job logs a +warning and ships the unsigned file together with its original updater signature. + +**Switching to the production certificate.** Once SignPath issues it, set the repository variable +`SIGNPATH_SIGNING_POLICY_SLUG` to `release-signing` (Settings → Secrets and variables → Actions → +Variables). No workflow change is needed. From then on, every release, including a dry run, opens +two signing requests (the standalone binary and the installer). An approver must approve both in +[app.signpath.io](https://app.signpath.io) while the run waits. The workflow waits up to an hour +per request before it gives up and ships unsigned. + +**Testing without publishing.** The **Windows signing test** workflow +(`.github/workflows/windows-signing-test.yml`, manual dispatch only, no inputs) builds `ocx.exe` +and the MSI on GitHub-hosted Windows runners, signs both with `test-signing`, verifies the +signatures, and keeps the test-signed files as short-lived workflow artifacts. It publishes +nothing and never uses the release policy. diff --git a/docs-site/src/content/docs/reference/codex-retry-diagnostics.md b/docs-site/src/content/docs/reference/codex-retry-diagnostics.md index d2e2ee8c2e3..0a7112cab84 100644 --- a/docs-site/src/content/docs/reference/codex-retry-diagnostics.md +++ b/docs-site/src/content/docs/reference/codex-retry-diagnostics.md @@ -48,6 +48,60 @@ that the account pool routed to the main account, the earliest known time anothe pool account recovers, whichever comes first (never sooner than one minute). Resume the Goal after that time; routing then picks an account from fresh state. +## Stored-account credit metadata retries + +An explicitly selected stored account can reach its included usage limit while its last +credit balance observation is older than five minutes. If credit spending is enabled for +that account and the evidence is stale, missing or incomplete, its local 429 advises retrying +after the 300-second base metadata cadence (or at the included reset if sooner). A later known upstream +cooldown for the selected quota scope, including account-wide holds, still takes precedence; when +both apply, the later deadline wins. Unrelated +model-group holds are not imported. The original included-window reset remains attached to the +internal refusal as `resetAt`; the shorter hint is a metadata recheck opportunity, not a new quota reset. + +The request remains refused until fresh spendable credit evidence is available. This hint +does not perform a refresh, bypass metadata-query pacing, guarantee recovery, change account +selection, or enable credit spending. Direct account bindings retain their identity. Explicit +credit opt-out, an older cached null without provenance, a known spending veto or a finite empty +balance without unlimited entitlement retain the included-reset advice. A newly observed missing +or malformed credit summary retracts spending authority but permits the short metadata retry; +an explicit spending veto still wins. Unlimited credits with a zero balance require fresh evidence +just like other credits. Ordinary upstream 429 delays keep the existing parser ceiling of 24 hours +for numeric and HTTP-date Retry-After values. + +## Triage: `idle timeout waiting for SSE` + +Codex ends a stream that has sent no event for its `stream_idle_timeout_ms` +(300 seconds by default) and retries the turn. The report alone does not say +why the stream went quiet. On the native ChatGPT upstream WebSocket, one +possible cause is a path that failed silently after the response started: a VPN +or tunnel restart, a Wi-Fi rejoin or an expired NAT mapping can drop it without +a FIN or RST reaching the proxy. + +On the canonical ChatGPT Codex endpoint +(`https://chatgpt.com/backend-api/codex/responses`, the exchange with the +metadata channel), the proxy pings the upstream for the whole exchange. Once the +response has started, a peer that has answered at least one ping on that +exchange and then delivers no frame and no pong for two ping intervals ends the +stream with a retryable failure. Codex then reconnects 30 to 45 seconds after +the last frame or pong instead of after 300 seconds, and the reconnect row names +the cause: + +```text +Reconnecting... 1/5 (stream disconnected before completion: Upstream stream terminated unexpectedly: codex websocket stopped answering after the response started [cause=after-response-started ...]) +``` + +When a ping reaches a path that a restarted tunnel resets, the same row reports +`codex websocket closed before a Responses terminal event (close 1006)` +instead, within about one ping interval. An `idle timeout waiting for SSE` that +still arrives after the full timeout comes from a peer that kept answering pings +while sending no events, a peer that never answered a ping, or an exchange +without the metadata channel, such as the OpenAI API Responses WebSocket, which +sends no pings. All three keep the client's idle deadline on purpose. +`ocx logs get --json` shows the attempt's `codexWsStage` with its +`pings` and `pongs` counts, which tell the three apart. Raising +`stream_idle_timeout_ms` only makes them wait longer. + ## Why the first reconnect row can be absent The public Codex `rust-v0.158.0-alpha.2.1` source gates the ordinary stream-retry diff --git a/docs-site/src/content/docs/reference/configuration.md b/docs-site/src/content/docs/reference/configuration.md index 06cf68b87c6..baba267dadf 100644 --- a/docs-site/src/content/docs/reference/configuration.md +++ b/docs-site/src/content/docs/reference/configuration.md @@ -48,6 +48,10 @@ Aliases are optional short request names. They never change the native model id Aliases match case-insensitively. A model alias works as `or/opus` or, when globally unique, bare `opus`; an ambiguous bare alias reports its qualified candidates. Codex model pickers show the qualified alias while preserving the canonical `provider/model` routing id. A provider's `defaultAliases` value overrides `defaultModelAliases`. Built-ins are skipped when multiple models in one provider match the same pattern. +### Native reasoning retention + +Native reasoning ciphertext retention on model and account changes has two opt-in settings, both off by default. Configure them on the dashboard overview or see [Native reasoning retention](/reference/configuration/server/#native-reasoning-retention) for the exact scope and experimental account behavior. + ### Cursor effort rows `cursorEffortRows` is an optional boolean and defaults to `false`. When enabled, the raw OpenAI-style diff --git a/docs-site/src/content/docs/reference/configuration/advisor.md b/docs-site/src/content/docs/reference/configuration/advisor.md index 139256cb206..036b322d589 100644 --- a/docs-site/src/content/docs/reference/configuration/advisor.md +++ b/docs-site/src/content/docs/reference/configuration/advisor.md @@ -1,6 +1,6 @@ --- title: Advisor -description: The OpenCodex-owned expert consultation sidecar — a configured expert model advises routed workers, with manual and preflight policies. +description: Independent expert consultation with manual, preflight and evidence-based adaptive policies. --- The advisor is an independent expert model that reviews the worker's task and returns advice. @@ -16,6 +16,8 @@ never sees — even a worker that never spawns anything can be advised. ## Configuration +On the dashboard **Advisor** page (under the **Subagents** sidebar row), choose the expert model from the catalog selector. It saves the exact catalog identifier, including native, provider, account-qualified, custom and combo selectors. Disabled models and models awaiting initial selection are not offered as new choices. A saved identifier missing from the selectable catalog remains visible with a warning and is preserved until you choose another model or clear it. Catalog loading failures preserve the selection and provide Retry. Choose a model that can return text advice: the catalog does not guarantee consultation compatibility. The CLI and API continue to accept router-supported model strings. + ```json { "advisor": { @@ -33,14 +35,15 @@ never sees — even a worker that never spawns anything can be advised. | `enabled?` | `boolean` | `false` | Master switch. Disabled means zero advisor behavior on the request path. Enabling does not record consent. | | `model?` | `string` | — | The expert model. Any model string the router accepts: a bare native model (`gpt-6-astra`), an explicit `provider/model` (`anthropic/claude-sonnet-4-6`, `xai/grok-...`), or an account-qualified native model. Cross-provider is fully supported: the worker and the advisor do not need to share a provider. | | `effort?` | `string` | `"max"` | Reasoning effort for the advisor call (`low` through `ultra`). | -| `policy?` | `"manual" \| "preflight"` | `"manual"` | When the advisor is consulted. | +| `policy?` | `"manual" \| "preflight" \| "adaptive"` | `"manual"` | When the advisor is consulted. | +| `maxConsultationsPerTask?` | `integer` | `3` | Adaptive task allowance (1–3 attempts), including manual calls. | | `timeoutMs?` | `number` | `120000` | Loopback consultation timeout. | | `contextSharingConsent?` | `"v1"` | absent | Operator consent to send task context to the configured Advisor provider. Only `"v1"` is current. Absent, stale, or any other value means no task context is sent. | Manage it from the dashboard **Advisor** page (the context-sharing checkbox starts unchecked) or with `ocx advisor status`, `ocx advisor on --ack-context-sharing`, `ocx advisor consent`, `ocx advisor consent --revoke`, `ocx advisor off`, and -`ocx advisor set --model --effort --policy `. +`ocx advisor set --model --effort --policy `. `ocx advisor on` without `--ack-context-sharing` does not enable cross-provider sharing when consent is missing: it prints this disclosure and stops. `ocx advisor set` does not grant consent. @@ -56,6 +59,42 @@ consent is missing: it prints this disclosure and stops. `ocx advisor set` does attempted consultation that FAILS is not silently treated as advice: the task retries after the failure's ledger entry expires, so a temporary advisor outage does not permanently silence the policy. +- **`adaptive`** — adds failed-repair consultations to the initial preflight attempt. The proxy + must observe a completed failing validation, a subsequent successful file-edit tool result, + and a new execution of the **same command in the same working directory** failing again. + A passing validation clears its failure state. A check started before the edit cannot count + as a retry after the edit. The initial and failed-repair stages have separate dedup claims. + Only clients with a stable conversation identity receive automatic adaptive consultations. + +### Adaptive budget and evidence + +```sh +ocx advisor set --policy adaptive --max-consultations 2 +ocx advisor activity --json +``` + +The adaptive task budget counts manual and automatic **attempts**, across requests and worker +model changes. The server reserves an attempt before dispatch in a private +`advisor-budget.sqlite` file in `OPENCODEX_HOME`; proxy restarts do not reset it. A task is +identified by a digest of conversation identity and the latest user-task boundary. Reservations +expire 24 hours after the first attempt, so this is a bounded task-lifetime approximation, +not a permanent session or daily monetary budget. Only digests, counts and expiry times are +stored. There is room for 512 live tasks; a full, unavailable or locked budget skips the expert +call while the worker continues. Failed or disconnected dispatched calls retain their reservation +because they may have incurred usage. Revoked consent before dispatch spends no allowance. +Clients without a stable identity keep manual, request-bounded access but receive no automatic +adaptive consultation. Legacy `manual` and `preflight` retain their previous task-dedup behavior. + +The collector recognizes paired shell calls with explicit numeric exit status, including +`exec_command`, `shell_command`, `shell`, `Bash`, and terminal `write_stdin` polls. Supported +commands include common Bun/npm/pnpm/yarn test, typecheck, lint, check and build commands, +`pytest`, `python -m pytest`, `tsc`, `eslint`, `cargo test/check/clippy/build` and `go test/vet`. +File edits use successful `apply_patch`, `Write`, `Edit`, `write_file` or `edit_file` results. +Single Code Mode helpers with literal JSON arguments (bare keys accepted) are recognized; +arbitrary JavaScript, batches, shell pipelines/substitutions, shell-based edits, ambiguous +output and error prose alone provide no trigger. At most the latest 512 messages since the +latest user task are examined. Failed-repair context includes the validation, bounded first +and repeated failure excerpts, and source message references before the general transcript. ## Consent @@ -98,7 +137,7 @@ prevents structural breakout and forged fields; it cannot guarantee prompt-injec A dedicated consultation-result protocol could distinguish advice from ordinary user input more strongly. -Each request allows at most three consultations and four Advisor-owned worker continuations. +Each request allows at most three consultations, including automatic attempts, and four Advisor-owned worker continuations. After consultation exhaustion the `advisor` tool is removed. A repeated call receives one final paired limit result; if the worker calls it again, typed 502 `advisor_continuation_limit` ends the request without another hidden worker call. The bound is shared with empty-completion retries. @@ -113,6 +152,17 @@ model** — never merged into the worker's token counts — and each consultatio `[advisor]` log line with trigger, duration, status, and usage, so an advisor call is always provable from the logs. +The **Recent advisor activity** section and `GET /api/advisor/activity` show the latest 100 +consultation metadata records from this process, retained for up to 24 hours. The dashboard +refreshes them every five seconds while visible. Records include trigger, status, worker and +expert models, duration, reported input/output tokens, requested effort and adapter-mapped +effort. A budget refusal is shown as blocked. Records contain no task text, commands, advice, +thread identifiers or provider error bodies and are cleared on proxy restart. + +Mapped effort describes what the adapter serialized, not a guarantee about the provider's +hidden reasoning. Unreported effort or usage stays unknown; it is never presented as zero +usage or confirmed reasoning support. No advice-adoption score or exact monetary cost is inferred. + ## Failure behavior The advisor fails open. A DISPATCHED consultation that fails (unavailable model, misconfigured @@ -125,12 +175,12 @@ A manual `advisor()` call without current consent returns a consent-required too sends nothing. An advisor failure never fails the coding request, and a consultation never switches the session's main model. -## PR1 limitations +## Limitations - Native OpenAI passthrough turns (ChatGPT-pool workers) do not get the synthetic tool; advisor support covers routed (translated) providers. Preflight consultation applies to run-turn adapters; the tool does not. -- No adaptive trigger: no stuck detection, repeated-failure analysis, escalation tiers, multiple - advisors, or advisor voting. `manual` and `preflight` are the only policies. +- No semantic stuck detection, completion review, escalation tiers, multiple advisors or voting. + Adaptive consultation requires the structured validation/edit/retry evidence described above. - The preflight dedup ledger is process-local; after a proxy restart, a task in progress may receive one more preflight attempt. diff --git a/docs-site/src/content/docs/reference/configuration/agents.md b/docs-site/src/content/docs/reference/configuration/agents.md index ed106c598e0..1b6791280cd 100644 --- a/docs-site/src/content/docs/reference/configuration/agents.md +++ b/docs-site/src/content/docs/reference/configuration/agents.md @@ -68,6 +68,12 @@ Mode changes apply to new sessions. `maxConcurrentThreadsPerSession` is a `PUT / `config.json` key; `ocx v2 threads ` writes `max_concurrent_threads_per_session` under `[features.multi_agent_v2]` in Codex's `$CODEX_HOME/config.toml` after v2 is enabled. +The Models page's sub-agent limit counts children. A stored V2 total of 1 is root-only +and displays 0; setting one child saves a V2 total of 2. New child limits must be at least +1, and both the child count and converted API value must be safe integers (at most +9,007,199,254,740,991). With global V2 disabled, including hybrid mode, no root slot is added. +The API rejects unsafe limits with 400 before changing the feature flag or configuration. + **Always proactive delegation** in Subagents → Advanced (formerly **Ultra mode**) changes the delegation trigger without changing reasoning effort. Its preset preserves user instructions, authority boundaries, task scope, and tool rules. The dashboard toggle, `PUT /api/v2` field @@ -378,6 +384,9 @@ Synthetic-row effort and combo defaults are preserved as the effective input bef Each selected destination resolves its own pin, then applicable caps and wire normalization. Compaction requests are exempt. `none` means effort omission and provider-default behavior, not guaranteed reasoning disablement. +For canonical OpenAI forwarding, the final model's supported effort list takes precedence: +`gpt-6.1-sol` and `gpt-6.1-terra` start at `low`, so an incoming `none` or `minimal` is sent as `low`, including +after a compaction model switch or combo failover. `GET /api/effort-caps` includes the map. `PUT /api/effort-caps` accepts `modelPinnedEfforts` alongside the existing caps: omitted fields stay unchanged, `null` clears the map, and a map diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index 50c3520b0df..808d56d2c23 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -3,9 +3,46 @@ title: Provider Configuration description: Provider entries, authentication, endpoints, model catalogs, quotas, context caps, and provider-specific options. --- + + A provider tells opencodex where a model lives, which wire adapter it speaks, and how requests are authenticated. +## DeepSeek: tokens versus image bytes + +DeepSeek documents a separate [48 MiB request-body limit](https://api-docs.deepseek.com/guides/files_api/). +A screenshot-heavy session can reach that limit before its token context fills; increasing +`model_auto_compact_token_limit` cannot prevent it. + +Uploads are **off by default**. Set `modelCapabilities: { "deepseek-flash": { deepseekImageFiles: true } }` +on your provider if you accept separately persisted images on DeepSeek's servers for one hour. +PATCH `null` on the axis or model entry clears the opt-in. + +For opted-in models on first-party Responses URLs, bodies above 40 MiB replace older inline images +with [Files API references](https://api-docs.deepseek.com/guides/files_api/), aiming for 32 MiB. +Image bytes, history and token usage stay unchanged. Files request one-hour expiry; successful +partial uploads remain until expiry even if generation fails. The bounded reference cache is +credential-isolated and lasts at most 50 minutes (less if the provider reports earlier expiry). +A retry can reuse successful uploads; another credential uploads its own copy. + +Only `user` and `developer` message images and tool-output images are uploaded; DeepSeek rejects +images in other roles, so those are never persisted. +Foreign gateways and Chat Completions are unchanged. Explicit `detail: low` stays inline because +file references ignore it. Failures stop without discarding images or retrying generation. Repairs +have a 60-second upload deadline, a 128 MiB input cap and at most two simultaneous preparations; +provider pacing/concurrency still applies. Oversized text or unconvertible images still need +compaction; this transport repair does not extend the model's context window. + +## GPT-6.1 Terra + +[GPT-6.1 Terra](https://openai.com/index/introducing-gpt-6-1-terra/) was released on October 26, 2026 as the successor to GPT-5.6 Terra. `gpt-6.1-terra` is available alongside `gpt-5.6-terra`; no default model or sub-agent roster changes. GitHub Copilot uses Responses; OpenRouter lists `openai/gpt-6.1-terra` with 1,050,000 context and $1.00 input / $5.00 output per 1M tokens. + +Native Codex: default context 500,000, long-context ceiling 872,000, default effort `low`, ladder `low`, `medium`, `high`, `xhigh`, `max`, `ultra`. Native `ultra` is sent as `max` on the wire. The native row offers Fast only. + +[OpenAI API](https://developers.openai.com/api/docs/models/gpt-6.1-terra): context 1,050,000, maximum output 128,000; text/image input and text output; knowledge cutoff 2026-06-01. Efforts: `none`, `low`, `medium` (default), `high`, `xhigh`, `max`. Endpoints: `v1/responses` and `v1/chat/completions`; tool calling requires Responses. + +Per 1M tokens: $1.00 input, $0.05 cached input, $1.25 cache write (30 minutes), $5.00 output. Above 272,000 input tokens, the whole request uses 2× input/cache and 1.5× output: $2.00 / $0.10 / $2.50 / $7.50 respectively. Fast mode costs 2×. + ## Context windows on custom providers When a custom provider omits a model's context window, OpenCodex can infer it from the bundled @@ -15,6 +52,13 @@ compatibility fallback. With it, the order is: discovered/configured limits (the Azure metadata, model-ID metadata, enabled Context cap as the window, then 128k. An enabled Context cap still limits the result. Built-in providers keep their existing behavior and ignore the flag. +`GET /api/providers` includes `inferContextWindow` only when configured, preserving both `true` and +`false`. To change it, `PATCH /api/providers?name=` accepts `true`, `false`, or `null` to +clear the setting and restore the default-off behavior; other types return `400`. Omitting it keeps +the current setting. A same-name `POST /api/providers` also keeps an omitted setting, including +`false` and any change committed while the save was validating. A submitted boolean wins, including +`false`, even if the stored value is `true`. + ```json { "providers": { "my-relay": { "adapter": "anthropic", "baseUrl": "https://relay.example/v1", "inferContextWindow": true } } } ``` @@ -69,6 +113,7 @@ separate. Full request URLs such as `/api/v1/responses` are not provider base UR | `contextCapValue?` | `number` | `350000` | Default used on first enable. A later enable restores the selected provider value. Updating the global value with `setAll: true` changes enabled caps only; `setAll: true` without a value enables all configured providers at the current global value. | | `codexAccounts?` | `CodexAccount[]` | `[]` | ChatGPT/Codex pool account metadata managed by Codex Auth. Secrets live separately in `codex-accounts.json`. | | `pausedCodexAccountIds?` | `string[]` | `[]` | Accounts excluded from Pool selection until resumed, including the main `__main__` account when paused. Manual pause and resume also update existing main and pool entries for the same account and workspace. | +| `codexUseRemainingQuotaAccountIds?` | `string[]` | `[]` | Accounts opted into using their remaining included quota. Disables proactive usage switching for listed accounts; for `__main__`, effective short/long hard-lock thresholds become 100%. Original thresholds remain stored and apply again after disabling. Pause, authentication, eligibility, cooldown and paid-credit permission still apply. Managed by `ocx account use-remaining openai on`; this persistent preference does not select the account. | | `creditCodexAccountIds?` | `string[]` | `[]` | Accounts allowed to keep serving from ChatGPT credits after a usage limit, including the main `__main__` account. Upstream does not refuse an account that holds credits at 100%; it serves the request and draws the balance. Spending is opt-in: an account not listed here is skipped by selection while one of its usage windows (weekly or monthly, only monthly on 30-day plans, or the 5-hour window) reads 100% with its reset still ahead, and returns once that reset passes. A weekly or monthly reading without a reset time does not hold the account; a 5-hour reading at 100% without a reset holds it only while that reading is still fresh. When no other account is available, automatic selection finds none rather than spending credits. An unlisted `__main__` is also refused, like a hard-lock refusal, when a request names it or carries its credential. Listing `__main__` does not lift the main-account hard lock (on by default at 98%), which still stops the main login first; turn the lock off to let the main account spend credits (a lock at 100% still stops it at 100%). New accounts start unlisted. Managed by the **Use credits** switch in the Codex Auth header and the **Use credits after limit** switch in each account card's **⋯** menu. | | `codexQuotaAutoRefresh?` | `Record` | `{}` | Per-Codex-login-account opt-in for automatic `fiveHour` and `weekly` window activation in Pool mode; Direct mode does not run this worker. In Providers/Codex Auth **Advanced settings**, one control enables or disables both supported windows across all current main and added accounts. New accounts are not opted in automatically. Enable skips windows absent from live WHAM data; disable also clears stale enabled windows. The UI reuses granular `/api/settings` writes, reconciles partial failures, and retries the original ON/OFF intent without replacing unrelated settings or completed reset markers. The API still rejects enabling an unavailable window with HTTP 409. At a reported reset time, opencodex sends one minimal non-stored Codex message using that account's quota and persists the activated timestamp. This does not apply to API-key providers. | | `codexAccountNamespaces?` | `Record` | — | Optional map from an arbitrary public model selector to a stored Codex account target. When account-qualified picker rows are enabled, each selector whose target is present adds separate `/` rows to the Codex picker; each row uses only that account. With any selector active, bare native rows are hidden in the picker, but their ids remain routable and listed by raw `/v1/models` unless explicitly disabled. | @@ -76,6 +121,7 @@ separate. Full request URLs such as `/api/v1/responses` are not provider base UR | `activeCodexAccountId?` | `string` | — | Manually selected Pool account for the next request. Selection clears thread affinity; in-flight requests keep captured credentials. | | `codexAccountPriorities?` | `Record` | — | Per-account selection order for the Codex pool: account id → integer from `-100` to `100`, **higher is used earlier**, absent means `0`. This is an ordering boundary, not an eligibility one: selection narrows the already-eligible accounts to the highest tier that still has quota headroom, and `accountPoolStrategy` then picks within that tier. A tier is skipped only when every member is at or above its own nonzero effective usage threshold (account override or global default), cooling down, soft-avoided, paused, or needs reauthentication — unknown quota never drains a tier. Ordering never makes an ineligible account selectable and by default, never re-binds a thread that already has an account. The main `__main__` account participates on equal terms, which is how the Codex Desktop login can be set to drain last. With no entries the pool behaves exactly as before. A malformed map is ignored with a console warning (ordering off, no config repair). Managed by `ocx account priority` and the Codex Auth page. | | `activeCodexAccountPinned?` | `string` | — | Account id the operator last selected by hand. While set, a higher `codexAccountPriorities` tier cannot preempt it until the pin is released by drain, exclusion, deletion, or an explicit failover/promotion away. Ordinary round-robin movement inside the capped tier does not release it. Writing any `codexAccountPriorities` entry also releases the pin, so a pin made before an order existed cannot outrank one set afterward. `GET /api/codex-auth/active` reports both whether the effective account is pinned (`pinned`) and the account carrying the ceiling (`pinnedAccountId`). | +| `codexPreserveSignedInAccount?` | `boolean` | `false` | Prefer other eligible Codex Pool identities before native main and ordinary entries matching its selected workspace and member email. Existing custom priorities are preserved inside each group. Healthy bindings and manual pins remain; exact account routes and Direct mode are unchanged. Fallback may consume signed-in quota under all existing guards. Use [account preserve-signed-in](/reference/cli/providers-accounts/#ocx-account-preserve-signed-in-openai-onoffstatus---apply-to-bound---json) for status or an explicit next-request binding transition. | | `codexAccountPriorityFailback?` | `boolean` | `false` | Opt-in return of ongoing Codex Pool tasks to a usable higher-priority account under the `quota` strategy. Requests trigger a background quota refresh at most once every five minutes, including failed refresh attempts. When recent quota observations show headroom, the next request may rebind even if its current account is below the threshold. This explicit preference can discard a warm prompt cache; it does not change default cache affinity. No requests means no polling. Manual pins, model access, cooldowns, pauses and reauthentication exclusions still apply; in-flight requests keep their captured account. Requires the current source account's effective threshold to be positive (an override of 0 disables it; a positive override works with global 0); exact account routes and Direct mode are unchanged. | | `autoSwitchThreshold?` | `number` | `80` | Usage threshold for placing new/unbound work. `quota` can re-evaluate unbound tasks on their next request once usage crosses this threshold. Bound tasks keep their account past the threshold by default (`pool.cacheAffinity`); they leave only when that account is exhausted or otherwise cannot serve, and then only for an account with genuine quota headroom and strictly lower usage. Set `pool.cacheAffinity: false` to re-evaluate bound tasks at this threshold, still only onto such a destination. `fill-first` uses it only as the drain point for unbound assignment; normal `round-robin` selection does not use it. The score uses the hottest known 5h, weekly, or 30d quota window. `0` disables usage-based proactive switching only, not unbound assignment or failure recovery. | | `codexAccountAutoSwitchThresholds?` | `Record` | — | Per-account overrides for `autoSwitchThreshold`: account id → integer from `0` to `100`. An absent entry inherits the global value; `0` disables usage-driven switching only when that account is the source. `quota` uses the current source account's effective threshold for bound-task re-evaluation and unbound switching. Fill-first drain and selection-order tier headroom are evaluated against each account's own effective threshold; main-account pins and subagent fallback also use the relevant account's effective threshold. Supports the main `__main__` account. A malformed map is ignored with a warning. Managed by each account card on the Codex Auth page; disabling an override removes its entry. Enabling the override copies the current global threshold into a fixed account value. The override, including `0`, takes precedence over later global changes. Disabling it sends `threshold: null`, removes the entry, and restores inheritance of the current global threshold and future global changes. | @@ -90,10 +136,14 @@ separate. Full request URLs such as `/api/v1/responses` are not provider base UR | `upstreamHostCircuitThreshold?` | `number` | `0` | Opt-in circuit threshold for proven pre-connection DNS/TCP failures on native OpenAI forward Responses and compact sends. `0` disables it; `1`–`20` opens a 30-second provider-origin cooldown after that many terminal logical requests. While open, requests receive `503` with `Retry-After` before account selection or upstream send; after cooldown, one half-open request is admitted. Timeouts and HTTP responses never count, and any HTTP response closes the circuit. Applies only to Codex Pool routing with no pinned account; it is inert for `codexAccountMode: "direct"` and account-qualified selectors. | | `maxUpstreamBodyBytes?` | `number` | `0` | Opt-in ceiling, in bytes, on a serialized native Responses **passthrough** body. `0` or omitted disables it — no limit is inferred for any destination. When set, a built body above the ceiling is refused locally before the send: streaming turns receive a terminal `response.failed` / `context_length_exceeded` so the client compacts instead of resending, and non-streaming turns receive a `413` naming the size, the number of embedded `input_image` items, and roughly how many megabytes of image data they represent. Checked at every build and rebuild point, including OAuth-refresh replay and alternate-account retry. Translated adapter paths are not covered. There is deliberately no default: the only measured ceiling here belongs to the WebSocket transport, which already falls back to HTTP for oversized turns, so a default would refuse requests that currently succeed. Set it when your gateway has a known request-size limit and you would rather see an actionable local error than an opaque upstream failure. | | `maxInboundBodyBytes?` | `number` | `0` | Opt-in ceiling, in bytes, on a decompressed **inbound** data-plane request body — the mirror of `maxUpstreamBodyBytes` above. `0` or omitted keeps the built-in 256 MiB default. Raise it when a large-context session can no longer compact: Codex replays the whole history to the compaction model, so on the 922k-token opt-in window the compaction request is itself the one that crosses the limit, and the session is stuck at the only operation that would have shrunk it. Clamped to 1 MiB–512 MiB. The ceiling is not negotiable: the reader materializes the body several times over (wire bytes, decoded bytes, the decoded string, and the parsed object graph), so peak memory is a multiple of whatever is admitted, and an unbounded value would be a memory exhaustion lever. Applies to `/v1/responses`, `/v1/responses/compact`, `/v1/chat/completions`, and `/v1/messages`. The listener's accept size is fixed when the proxy binds, so a change takes effect on restart. A body above the limit is refused locally with HTTP 413 and `code: "inbound_body_too_large"`, which is deliberately distinct from the `context_length_exceeded` 413 a provider size refusal produces. | +| `upstreamRequestCompression?` | `"off" \| "gzip" \| "zstd"` | `"off"` | Opt-in compression of native ChatGPT Responses HTTP request bodies. Codex re-sends the whole conversation on every turn, and when that turn goes over HTTP OpenCodex otherwise forwards it to `chatgpt.com` uncompressed; on a metered or long-distance egress that upload is the dominant cost. When set, string bodies from 1 KiB to 16 MiB sent to `https://chatgpt.com/backend-api/codex/responses` and `/compact` are compressed at the final send and labelled with `Content-Encoding`. WebSocket turns, every other destination, bodies that already declare a `Content-Encoding`, and bodies outside that size window are sent unchanged, and a codec failure falls back to the uncompressed send. `zstd` is the encoding Codex CLI itself uses with this backend and is the faster choice for image-heavy turns; `gzip` (level 1) is also available. In a local measurement, code-heavy history shrank about 2–4×, while embedded images barely shrink. Compression runs synchronously on the proxy's event loop, so each eligible send briefly delays other in-flight requests — on the order of 2–3 ms per MiB for text on a recent laptop CPU, more for incompressible data with `gzip`. Byte limits such as `maxUpstreamBodyBytes` still measure the uncompressed body. Set it with `ocx config set upstreamRequestCompression zstd` (an unknown value is refused) or by editing `config.json`, where an unknown value is ignored, then restart the proxy to apply. | | `modelCacheTtlMs?` | `number` | `300000` | Freshness window for the per-provider `/models` cache. | -| `cacheRetention?` | `"none" \| "short" \| "long"` | `"short"` | Anthropic prompt-cache policy: disabled, 5-minute ephemeral, or 1-hour extended. | +| `cacheRetention?` | `"none" \| "short" \| "long"` | first-party Anthropic: `"long"`; other Anthropic-compatible endpoints: `"short"` | Anthropic prompt-cache policy: disabled, 5-minute ephemeral, or 1-hour extended. When unset, requests to `api.anthropic.com` (API key or the OAuth account pools) use the 1-hour TTL, because agentic turns often pause longer than five minutes and a 1-hour write (2x base input instead of 1.25x) pays for itself on the first reuse; Anthropic-compatible gateways and vendor endpoints keep the 5-minute TTL because their 1-hour support is undocumented. On native `/v1/messages` passthrough, caller breakpoints without a `ttl` get `"1h"` unless the request pins any breakpoint to `"5m"`, in which case it is forwarded unchanged. On a translated `/v1/messages` route, caller breakpoints that all ask for `ttl: "1h"` mean `"long"` and any explicit `"5m"` means `"short"`. An explicit value always wins. Changing the TTL makes each open conversation write its cache once more, since 5-minute and 1-hour entries are separate. Cost estimates price writes reported as 1-hour at the 1-hour rate. | | `tokenGuardian?` | `OcxTokenGuardianConfig` | off | Optional proactive OAuth refresh and Codex-account warmup policy. | +Opted-in pool accounts with a full included usage window refresh their credit evidence through the existing background sweep from three minutes of age. Ordinary response headers do not refresh credit balances. A failed metadata read never extends the five-minute spending-evidence lifetime. + + Across these strategies, each account's effective threshold is its `codexAccountAutoSwitchThresholds` entry when present, otherwise global `autoSwitchThreshold`. A zero override disables only proactive usage switching; startup binding, hard-lock, cooldown, entitlement checks, and failure recovery remain enforced. Setting an account's switching threshold to `0` does not disable main-account hard-lock, startup @@ -144,6 +194,15 @@ and `modelAutoCompactTokenLimits` can narrow its window and soft compaction budg `modelAutoCompactTokenLimits: { "gpt-6-astra": 700000 }` lowers the long-window default of 784,800. An explicit smaller provider cap or target limit still wins, including native-alias combos. +Without a configured hard limit, the native catalog keeps the advertised `max_context_window` +separate from the default `context_window` (for Astra, 872,000 versus 272,000). Compaction still +uses the effective default window. To give one model 500,000 tokens and the others 300,000, set +`providers.openai.contextWindow` to `300000` and +`providers.openai.modelContextWindows` to `{ "gpt-6.1-sol": 500000 }`. +The per-model value overrides the provider window; `providerContextCaps.openai`, when enabled, +remains a ceiling for every native model and must be at least `500000` for this example. +Each model's own capacity still bounds the result. + The API row has 1,050,000 context, 922,000 maximum input, 128,000 maximum output, text/image input, and API reasoning efforts through `max`. OpenCodex's routed synthetic Ultra control retains its existing wire-effort mapping; it is not an additional API effort. There is no Astra `-pro` alias. @@ -245,23 +304,26 @@ Providers can expose a built-in shorthand, such as `agy` for `google-antigravity | `baseUrl` | `string` | Upstream API base URL. Most built-in fixed endpoints ignore a mismatch; collision-safe key presets preserve an older same-named custom destination. | | `proxy?` | `string \| null` | Per-provider egress route. Omit it to inherit the global proxy decision; use `"direct"` or `null` to force direct egress; or provide an absolute `http://`, `https://`, `socks5://`, or `socks5h://` proxy URL. An empty string is rejected. | | `noProxy?` | `string \| string[]` | Destinations this provider reaches directly, using `NO_PROXY` host-pattern syntax. A match bypasses both this provider's own proxy and an inherited global proxy. | -| `requestPacing?` | `{ enabled, requestsPerMinute?, minIntervalMs?, maxConcurrentRequests?, models? }` | Optional client-side outbound request-start pacing, separate from upstream usage, billing, and rate-limit indicators. RPM is converted to an even interval; `minIntervalMs` may impose a longer interval. `maxConcurrentRequests` is a positive integer cap on in-flight requests. A provider or model rule may use the concurrency cap alone; provider limits apply across all models, while `models` entries use exact upstream model IDs (for example `nvidia/llama-3.1-nemotron-ultra-253b-v1`) and can only add delay or narrow concurrency. Queue waits do not consume the upstream response-header timeout. HTTP and explicit adapter `fetchResponse`/`runTurn` dispatches are covered. A concurrency-capped canonical Responses WebSocket turn uses HTTP/SSE so its lease can be released when the response body completes, errors, or is cancelled. For `runTurn` adapters, including Cursor, the cap counts active turns rather than physical sends: RunSSE and BidiAppend may overlap within one turn, while another turn waits. Follow-up sends still obey start intervals. | +| `requestPacing?` | `{ enabled, requestsPerMinute?, minIntervalMs?, maxConcurrentRequests?, models?, keySelection?, keys? }` | Optional client-side outbound request-start pacing, separate from upstream usage, billing, and rate-limit indicators. RPM is converted to an even interval; `minIntervalMs` may impose a longer interval. `maxConcurrentRequests` is a positive integer cap on in-flight requests. A provider or model rule may use the concurrency cap alone; provider limits apply across all models, while `models` entries use exact upstream model IDs (for example `nvidia/llama-3.1-nemotron-ultra-253b-v1`) and can only add delay or narrow concurrency. Queue waits do not consume the upstream response-header timeout. HTTP and explicit adapter `fetchResponse`/`runTurn` dispatches are covered. A concurrency-capped canonical Responses WebSocket turn uses HTTP/SSE so its lease can be released when the response body completes, errors, or is cancelled. For `runTurn` adapters, including Cursor, the cap counts active turns rather than physical sends: RunSSE and BidiAppend may overlap within one turn, while another turn waits. Follow-up sends still obey start intervals. | +| `requestPacing.keySelection?` | `"active"`, `"round-robin"` | Normal-request key selection. Default `active` preserves the saved key and existing failure rotation; `round-robin` distributes request-local selections across eligible API-key pool references without changing the saved active key. It balances allocations, not tokens or cost. | +| `requestPacing.keys?` | `{ requestsPerMinute?, minIntervalMs?, maxConcurrentRequests? }` | Uniform per-key-reference RPM, minimum start interval and concurrency limits, shared across models. These compose with provider and exact-model upper bounds. Duplicate references share a bucket; separate references in one upstream project do not imply independent quota. TPM and daily quotas are not enforced. | | `upstreamHttpVersion?` | `"auto" \| "http1.1" \| "h1" \| "http2" \| "h2"` | Pin the HTTP version used for upstream requests to this provider. Defaults to `auto`, which lets Bun negotiate. An explicit pin requires an HTTPS target and fails locally when it cannot be honored. Set `http1.1` when a provider's HTTP/2 SSE stream stalls instead of delivering events — the symptom is a long-running streaming request that produces nothing and eventually times out. For Cursor, `http1.1`/`h1` selects its `RunSSE` + `BidiAppend` compatibility transport for inference and also pins live model discovery. Management `POST`/`PATCH` accept `null` to clear it back to `auto`. | | `tlsProfile?` | `"antigravity-browser"` | **Use at your own risk.** Opt-in browser-like TLS handshake (through the optional `wreq-js` dependency) for the canonical `google-antigravity` OAuth provider. It changes only how the connection looks on the wire; it is not an official Google client, and it does not change what Google's terms allow. Google can still detect, rate-limit, suspend, or ban the account you signed in with, and you alone carry that risk. It is off by default and accepted only with the Google adapter, Cloud Code Assist mode, and Google's canonical HTTPS Antigravity hosts. Redirects stay manual. The provider's own `proxy`/`noProxy` route is carried by the TLS transport; a route it cannot keep fails the request instead of leaving by another path, which includes any direct route while `HTTP_PROXY`, `HTTPS_PROXY`, or `ALL_PROXY` is set. `GET /api/providers` reports the profile state as `pending`, `active`, or `failed`. Omitting the field keeps the normal Bun transport and never loads the dependency. | | `responsesPath?` | `string` | Relative resource path for key-auth `openai-responses` requests. It must start with `/` and contain no scheme, query, or fragment. | | `chatCompletionsPath?` | `string` | Relative resource path for `openai-chat` requests, the mirror of `responsesPath` and subject to the same shape rules. Needed when one upstream serves Chat Completions and Responses under different prefixes: a per-model wire override changes the adapter and leaves `baseUrl` alone, so without this an opted-in Chat request would be sent to the Responses base. Z.AI is the shipped example. | | `allowEncryptedV2AgentTasks?` | `boolean` | Disabled by default. Trust a direct key-auth `openai-responses` provider to consume or relay opaque encrypted V2 sub-agent tasks unchanged. Eligible routes skip `agentTaskRecovery`; all other routes keep the existing recovery or fail-closed behavior. OpenCodex does not decrypt, translate, or recover tasks sent through this opt-in. | +| `projectResponsesLiteHostedSearchHistory?` | `boolean` | Default off. Set the actual boolean `true` to opt this provider into lossy projection of unreferenced hosted-search history for ordinary turns and native v2 `compaction_trigger` requests to exact canonical ChatGPT Codex `/responses` with explicit Responses Lite intent. Omission or `false` retains structural history; strings and numbers are invalid. It does not enable Lite, change transport, or affect native v1 `/responses/compact`, public OpenAI API/other destinations, or existing portable/routed compaction projection. See [Lite hosted-search history](/reference/proxy-formats/#codex-native-ordinary-lite-hosted-search-history). | | `upstreamWebsocket?` | `boolean` | Opt-in upstream Responses WebSocket transport for `openai-responses` requests (default false). Honored only for the first-party `https://api.openai.com/v1` upstream; custom-provider endpoints always use bounded HTTP/SSE because Bun cannot enforce an inbound WebSocket message limit before allocating the complete message. For the canonical ChatGPT `openai` provider, omitting it keeps the upstream WebSocket on eligible turns, an explicit `false` sends streaming turns over HTTP/SSE, and provider management rejects `true`; with `false`, native mid-turn steering and injection are unavailable. The field is independent of the client-facing `websockets` setting and changes neither the endpoint nor the credential. Plain HTTP remains on SSE; non-Responses paths and `openai-chat` requests stay on HTTP. | | `supportsServiceTier?` | `boolean` | Tri-state canonical Fast capability fallback. `true` publishes Fast in the catalog, satisfies service-tier routing requirements, contributes a supported fingerprint, and lets fast mode inject the provider's canonical wire value on a compatible final adapter. `false` strips the field and never injects, and exact model declarations cannot reopen it. Absent leaves the provider unclassified: fast mode does not inject or normalize a canonical caller value, and caller values obey the final wire's forwarding permission (`chatServiceTier` on Chat; passthrough on Responses). The registry classifies canonical OpenAI (`true`), DeepSeek, and Volcengine Ark (`false`); set it explicitly only for custom gateways that genuinely support tiers. | | `responseTierAuthoritative?` | `boolean` | Whether the response tier can confirm or deny Fast. Set `false` explicitly for routes with non-authoritative response metadata; omission preserves existing behavior. This does not enable Fast or change outbound parameters. See [Response service-tier authority](#response-service-tier-authority). | | `fastEnabled?` | `boolean` | Operator switch for the provider's Fast lane. `false` turns Fast off (no Fast toggle, no `--fast` row, no fast wire field) and overrides `supportsServiceTier`. `true` enables a lane the registry marks opt-in. Absent keeps the registry default: off for `anthropic` and `anthropic-apikey`, whose fast mode spends usage credits at 2x price, and unchanged for every other provider. The dashboard Models page shows an Off/On row for opt-in providers. | | `modelSupportsServiceTier?` | `Record` | Exact upstream model capability overrides. Exact `true` enables canonical Fast for that model; exact `false` narrows provider defaults. An explicit provider-level `supportsServiceTier: false` remains fail-closed and cannot be reopened. Exact `true` does not authorize foreign caller-tier forwarding on Chat. Undeclared models fall back to provider-wide behavior. Management `PATCH /api/providers` merges entries and accepts `null` to clear one. | | `chatServiceTier?` | `boolean` | Provider-wide Chat-wire opt-in for forwarding caller `service_tier` values. On a classified route it governs foreign values such as `flex`, not proxy-owned canonical Fast after capability validation; on an unclassified route it governs every caller value because no Fast capability has been validated. Exact model capability does not authorize foreign forwarding. Responses routes retain their capability-based caller forwarding behavior. | -| `promptCacheKey?` | `boolean` | Provider-wide `openai-chat` opt-in for forwarding a `prompt_cache_key`. The adapter forwards the key it is given and never invents one, but the key is not always the caller's: Claude Messages translation derives one from `metadata.user_id`, or from a model/system/tools cohort when no metadata is sent. Default off. Enable only when the upstream documents support, because strict gateways may reject the unknown field with HTTP 400. | -| `preserveResponsesReasoningContent?` | `boolean` | Default off for custom providers. In **Providers → Settings**, enable **Replay plaintext reasoning** for a custom Responses endpoint only if it accepts this history. This sends prior plaintext reasoning to that endpoint and may increase token use. Keep plaintext reasoning content on replayed Responses reasoning items instead of blanking it (blanking is the ChatGPT backend's rule). Enable for upstreams whose contract accepts reasoning replay, such as DeepSeek or a self-hosted Kimi on vLLM. Proxy-minted `ocxr1` envelopes are always stripped. A hand-configured `openai-responses` provider without it logs one warning per host the first time plaintext reasoning is dropped. `preserveReasoningContentModels` does not affect this wire. | +| `promptCacheKey?` | `boolean` | Provider-wide `openai-chat` opt-in for forwarding a `prompt_cache_key`. The adapter forwards the key it is given and never invents one, but the key is not always the caller's: Claude Messages translation derives one from `metadata.user_id`, or from a model/system/tools cohort when no metadata is sent. Default off unless declared by the provider preset; Mistral enables it per its [prompt-caching contract](https://docs.mistral.ai/studio/conversations/advanced/prompt-caching), and explicit `false` disables it. Enable only when the upstream documents support, because strict gateways may reject the unknown field with HTTP 400. | +| `preserveResponsesReasoningContent?` | `boolean` | Default off for custom providers. In **Providers → Settings**, enable **Replay plaintext reasoning** for a custom Responses endpoint only if it accepts this history. This sends prior plaintext reasoning to that endpoint and may increase token use. Keep plaintext reasoning content on replayed Responses reasoning items instead of blanking it, including custom native v1 compact input (blanking is the ChatGPT backend's rule). Enable for upstreams whose contract accepts reasoning replay, such as DeepSeek or a self-hosted Kimi on vLLM. Proxy-minted `ocxr1` envelopes are always stripped. A hand-configured `openai-responses` provider without it logs one warning per host the first time plaintext reasoning is dropped. `preserveReasoningContentModels` does not affect this wire. | | `preserveResponsesInputItemIds?` | `boolean` | Default off. Preserve valid `input[].id` when `store: false` for explicitly trusted launcher relays that require turn identity. Ordinary Responses providers keep stripping IDs; xAI custom-call ID repair remains active. | -| `preserveResponsesMessageMetadata?` | `boolean` | Default off. Preserve private `internal_chat_message_metadata_passthrough` on input items for explicitly trusted launcher relays. This forwards private turn/session metadata to that destination; do not enable for ordinary gateways. | -| `dropResponsesReasoningItems?` | `boolean` | Remove replayed Responses `reasoning` items from continuation input entirely, instead of forwarding them with blanked content. This is lossy: summaries, item ids, and `encrypted_content` go with them, so no reasoning item from the previous turn remains in the forwarded input. It does not remove `previous_response_id`, so an upstream holding its own turn state may still reach it. Enable only for an upstream that rejects reasoning replay outright — the built-in Volcengine Ark Coding Plan preset sets it because validated tool continuations answered `400 InvalidParameter` otherwise. Set it to `false` on that provider to forward reasoning items again. | +| `preserveResponsesMessageMetadata?` | `boolean` | Default off. Preserve private `internal_chat_message_metadata_passthrough` on input items for explicitly trusted launcher relays, including custom native v1 compact requests. This forwards private turn/session metadata to that destination; do not enable for ordinary gateways. | +| `dropResponsesReasoningItems?` | `boolean` | Remove replayed Responses `reasoning` items from continuation input entirely, including custom native v1 compact requests, instead of forwarding them with blanked content. This is lossy: summaries, item ids, and `encrypted_content` go with them, so no reasoning item from the previous turn remains in the forwarded input. It does not remove `previous_response_id`, so an upstream holding its own turn state may still reach it. Enable only for an upstream that rejects reasoning replay outright — the built-in Volcengine Ark Coding Plan preset sets it because validated tool continuations answered `400 InvalidParameter` otherwise. Set it to `false` on that provider to forward reasoning items again. | | `disabled?` | `boolean` | Keep the provider on disk but exclude it from routing and model/catalog listings. | | `apiKey?` | `string` | API key, an `${ENV_VAR}` / `$ENV_VAR` reference, or a `keychain:` reference written by `ocx provider keychain store`. References resolve at request time. See [Storing keys in the OS keychain](#storing-keys-in-the-os-keychain). | | `apiKeyTransport?` | `"x-api-key" \| "bearer"` | Anthropic key header style. Defaults to native `x-api-key`; valid only for key-auth `anthropic` providers. | @@ -283,9 +345,12 @@ Providers can expose a built-in shorthand, such as `agy` for `google-antigravity | `minMaxOutputTokens?` | `number` | Optional positive integer floor for explicit positive `max_output_tokens` budgets on `azure-openai`/`azure` Responses routes. Unset preserves the caller's cap. Setting it authorizes raising a smaller cap, which can increase output usage. Omitted or invalid request budgets are unchanged. | | `defaultMaxOutputTokens?` | `number` | Provider-wide `openai-chat` fallback when the client omits `max_output_tokens`. | | `modelMaxOutputTokens?` | `Record` | Positive per-model `openai-chat` fallback budgets; exact/pattern matches beat the provider default. | -| `modelCosts?` | `Record` | Per-model display prices (USD per 1M tokens), keyed by that provider's exact upstream model id — not a provider identifier or a routed `provider/model` label, e.g. `{ "deepseek-v4-flash": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 } }`. Any model id is a valid key — custom providers may target any OpenAI-compatible endpoint through the `openai-chat` adapter, and local or internal provider ids work even when they are absent from the built-in catalogs. User-configured prices win over the built-in catalogs in the Logs `~$` and Usage estimates; historical entries are repriced from the current overlay, so editing a price can move past totals. The fallback order is user `modelCosts` → exact official correction → jawcode catalog → expected-price overlay → model-level vendor fallback, and an explicit all-zero user entry means a known-zero estimate; delete that model entry to restore automatic pricing. All-zero catalog metadata still falls through. Each rate must be a non-negative finite number at most 1,000,000 (USD per 1M tokens); out-of-range rows are rejected by the management boundary and dropped on load. Display-time estimation only: overlays never affect routing, account selection, quotas, or billing. | +| `modelCosts?` | `Record` | Per-model display prices (USD per 1M tokens), keyed by that provider's exact upstream model id — not a provider identifier or a routed `provider/model` label, e.g. `{ "deepseek-v4-flash": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 } }`. Any model id is a valid key — custom providers may target any OpenAI-compatible endpoint through the `openai-chat` adapter, and local or internal provider ids work even when they are absent from the built-in catalogs. User-configured prices win over the built-in catalogs in the Logs `~$` and Usage estimates; historical entries are repriced from the current overlay, so editing a price can move past totals. The fallback order is user `modelCosts` → exact official correction → jawcode catalog → expected-price overlay → model-level vendor fallback, and an explicit all-zero user entry means a known-zero estimate; delete that model entry to restore automatic pricing. All-zero catalog metadata still falls through. Each rate must be a non-negative finite number at most 1,000,000 (USD per 1M tokens); out-of-range rows are rejected by the management boundary and dropped on load. Display-time estimation only: overlays never affect routing, account selection, quotas, or billing. | | `headers?` | `Record` | Extra upstream headers. Authorization, cookies, API-key headers, embedded newlines, and invalid names are rejected. Requests that ride the provider outbound wrapper — model discovery, connection tests, and other proxy-originated diagnostics such as the Ollama show probe — send `User-Agent: opencodex` unless a User-Agent is set here or by the provider preset. | | `forwardClientHeaders?` | `string[]` | `openai-responses` only: opt in to copying named inbound client metadata headers onto inference requests when the provider has not already set that header. Credential and transport-owned names such as `Authorization`, cookies, API-key headers, `Content-Type`, `Content-Length`, `Host`, and `x-oai-attestation` are rejected and also filtered at runtime. Header names are case-insensitive and may not repeat. `headers` remains authoritative; the existing caller `User-Agent` fallback still applies when neither surface sets one. Supported names: `originator`, `x-client-request-id`, `x-codex-app-version`, `user-agent`; all other names are refused. | +| `supportsNativeCompactionTrigger?` | `boolean` | `openai-responses` only: declare support for native v2 `compaction_trigger` input items. Defaults off for noncanonical gateways; preserves native compaction rather than requesting a portable text summary. Independent of the v1 endpoint and blob replay capabilities. | +| `supportsNativeCompactEndpoint?` | `boolean` | `openai-responses` only: declare support for native v1 `POST /responses/compact`. Defaults off for custom gateways. Key/local/forward routes use the provider's Responses URL plus `/compact` and its normal transport authentication; OAuth keeps routed compaction and its existing credential lifecycle. Does not enable v2 triggers or grant official OpenAI identity. | +| `decodesNativeCompactionBlobs?` | `boolean` | Declare that this destination can replay native, non-`ocx1:` compaction blobs. Independent of either generation capability; a known serving-provider mismatch still degrades foreign ciphertext. Enable only after testing compact and a subsequent turn on the same backend. | | `openRouterRouting?` | `OpenRouterProviderRouting` | Default OpenRouter `order`, `only`, and `allowFallbacks` preferences; valid only for canonical OpenRouter with `openai-chat`. | | `modelOpenRouterRouting?` | `Record` | Exact model-id overrides that replace the provider-wide OpenRouter preference. | | `vercelGatewayRouting?` | `VercelGatewayRouting` | Default Vercel AI Gateway `order`, `only`, and `sort` (`"cost"` \| `"ttft"` \| `"tps"`) preferences; valid only for canonical Vercel AI Gateway with `openai-chat`. | @@ -299,7 +364,7 @@ Providers can expose a built-in shorthand, such as `agy` for `google-antigravity | `modelSuppressSyntheticMax?` | `Record` | Catalog-only per-model switch. `true` prevents OpenCodex from adding a missing synthetic `max` rung while retaining a provider-declared `max` and continuing to add `ultra` to reasoning-capable ladders. A preserved row that already contains `max` keeps it until healthy discovery rebuilds that row, because the catalog does not persist whether an older `max` was synthetic. If a configured default names a suppressed missing `max`, the catalog falls back to the highest real rung below it. Codex uses the same ladder membership for the picker and explicit `spawn_agent` effort validation, so an explicit `max` spawn can fail client-side before proxy clamping; `ultra` remains the supported harness path. | | `modelSupportsReasoningSummaries?` | `Record` | Set a model to `false` to stop advertising summaries and strip summary-delivery fields. | | `modelReasoningSummaryDelivery?` | `Record` | Per-model Responses delivery enum; rewrites an existing delivery field. | -| `modelAdapters?` | `Record` | Per-model `openai-chat` or `openai-responses` wire override for mixed-wire gateways. Explicit entries beat registry defaults. The OpenCode Go preset selects Responses for `gpt-5.6-luna` while leaving sibling models on their documented wires; DeepSeek can select native Responses for `deepseek-v4-flash`; Alibaba Token Plan (Beijing) pins `qwen3.8-flash`, `qwen3.7-plus`, and `glm-5.3` to its native Responses API for Responses inbound only (live-verified on that gateway, including plaintext reasoning replay; Chat and Anthropic inbound stay on Chat, and the rest of the family can be opted in here); and GitHub Copilot declares Responses-only defaults for the following models (`gpt-5.3-codex`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`) because those models reject `/chat/completions` for agent traffic. Models without a built-in default (for example `gpt-5.4-nano`) can be opted in here. Single-wire upstream pins and canonical ChatGPT forward reject overrides. | +| `modelAdapters?` | `Record` | Per-model `openai-chat` or `openai-responses` wire override for mixed-wire gateways. Explicit entries beat registry defaults. The OpenCode Go preset selects Responses for `gpt-5.6-luna` while leaving sibling models on their documented wires; DeepSeek can select native Responses for `deepseek-v4-flash`; Alibaba Token Plan (Beijing) pins `qwen3.8-flash`, `qwen3.7-plus`, and `glm-5.3` to its native Responses API for Responses inbound only (live-verified on that gateway, including plaintext reasoning replay; Chat and Anthropic inbound stay on Chat, and the rest of the family can be opted in here); and GitHub Copilot declares Responses-only defaults for the following models (`gpt-5.3-codex`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6.1-sol`, `gpt-6.1-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`) because those models reject `/chat/completions` for agent traffic. Models without a built-in default (for example `gpt-5.4-nano`) can be opted in here. Single-wire upstream pins and canonical ChatGPT forward reject overrides. | | xAI Chat Completions (dashboard / CLI) | switch | Grok 4.5/4.6 OAuth Responses requests default to Responses. Existing Chat overrides are migrated once on upgrade; later Chat choices are preserved. Turn on to select Chat for both models, off to select Responses. CLI: `ocx provider edit xai --xai-chat on` or `--xai-chat off` (running proxy required). Mixed means only one model currently uses Chat. Other overrides and tier policy stay unchanged. API-key and translated Chat/Anthropic defaults are unchanged. Grok 4.7 defaults to Responses on OAuth through its registry wire default and can use Chat through an explicit `modelAdapters["grok-4.7"] = "openai-chat"` override. | | `xaiResponsesXSearch?` | `boolean` | Disabled by default. On an xAI Responses destination, append the provider-hosted `x_search` declaration only when a live `web_search` tool survives final request normalization. Existing declarations are not duplicated, caller `tool_choice`/`allowed_tools` selectors are never widened, and this is separate from the web-search sidecar's `search.xSearch` options. | | `modelPreferHostedTools?` | `Record` | Exact-model opt-in for non-forward Responses gateways that reserve a hosted-tool namespace. Currently accepts only `["image_generation"]`; a matching model must use the `openai-responses` wire and support that hosted tool. It removes colliding client `image_gen` declarations and rewrites their selectors to preserve caller tool choice. For OpenAI API virtual `-pro` models, the selected public ID is matched first and the resolved base wire-model ID is a fallback. `modelAdapters` resolves the public ID first, then the base ID; the second resolution determines the final wire. Other models retain normal alias behavior. | @@ -307,22 +372,23 @@ Providers can expose a built-in shorthand, such as `agy` for `google-antigravity | `unsupportedHostedTools?` | `string[]` | Hosted tool declarations this Responses destination rejects, so they are stripped from `tools`, from client-loaded `additional_tools`, and from `tool_choice` instead of being forwarded and rejected upstream. Use it for an OpenAI-compatible gateway with a narrower capability set — one that accepts plain Responses requests and `function` tools but returns HTTP 400 for hosted `web_search` — so a text-only prompt is not failed by a capability it never needed. Accepts only hosted tool type names (`web_search`, `web_search_preview`, `file_search`, `computer_use_preview`, `computer_use`, `code_interpreter`, `image_generation`, `image_gen`, `mcp`, `tool_search`, `local_shell`, `x_search`); an unrecognized name is rejected rather than silently ignored. Spelling variants of one capability are aliased, so `["web_search"]` also denies `web_search_preview`. A provider cannot both deny a hosted tool here and prefer it in `modelPreferHostedTools`. Xiaomi MiMo Responses destinations (`xiaomimimo.com` and its subdomains, including `api.xiaomimimo.com` and `token-plan-cn.xiaomimimo.com`) automatically strip `web_search` and `web_search_preview` while preserving function tools; no declaration is needed for these hosts. This is independent of `supportsResponsesCustomTools`; set both for a gateway that also rejects native custom tools. `PATCH /api/providers?name=` accepts an array or `null` to clear it. | | `reasoningEffortMap?` | `Record` | Provider-wide wire aliases for reasoning labels. Map a label to `"__omit__"` to drop the reasoning field from the upstream request entirely: `reasoning_effort` on an OpenAI-compatible wire, and Ollama's native `think` field on the Ollama native adapter (#2356). | | `modelReasoningEffortMap?` | `Record>` | Per-model wire aliases for reasoning labels. Map a label to `"__omit__"` to drop the reasoning field from the upstream request entirely. | -| `reasoningWireFormat?` | `"gateway-object"` | For OpenAI-compatible gateways that accept `reasoning: { enabled, effort }` instead of `reasoning_effort`. The ClinePass preset sets this automatically. A provider save that keeps the destination keeps it; see [What a provider save keeps](#what-a-provider-save-keeps). `PATCH` accepts `"gateway-object"` or `null` to clear it. | +| `reasoningWireFormat?` | `"gateway-object"` | For OpenAI-compatible gateways that accept `reasoning: { enabled, effort }` instead of `reasoning_effort`. For custom Responses destinations, this also explicitly permits the OCX `reasoning.enabled` extension; other Responses destinations strip that internal field. Model thinking capability alone does not opt into this wire format. The ClinePass preset sets this automatically. A provider save that keeps the destination keeps it; see [What a provider save keeps](#what-a-provider-save-keeps). `PATCH` accepts `"gateway-object"` or `null` to clear it. | | `noReasoningModels?` | `string[]` | Models that reject reasoning/thinking parameters. | | `noTemperatureModels?` | `string[]` | Models that reject caller-specified `temperature`. | | `noTopPModels?` | `string[]` | Models that reject caller-specified `top_p`. | | `noStopModels?` | `string[]` | Models that reject caller-specified `stop`. The `openai-chat` adapter, the Chat passthrough and the Responses passthrough omit the field for those ids. The built-in `xai` preset lists its reasoning models (`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`), which xAI documents as rejecting it; `grok-4.20-0309-non-reasoning`, `grok-composer-2.5-fast` keep caller `stop`. | | `noPenaltyModels?` | `string[]` | Models that reject presence/frequency penalties. The built-in `xai` preset lists its reasoning models (`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`), which xAI documents as rejecting them; non-reasoning ids keep caller penalties. | | `noStructuredOutputModels?` | `string[]` | Exact model IDs whose `openai-chat` endpoint rejects `response_format`. Only an exact requested-model match omits the field; structured-output translation stays enabled for every other `openai-chat` model. | -| `noJsonSchemaModels?` | `string[]` | Exact model IDs whose `openai-chat` endpoint rejects a `json_schema` `response_format` but still accepts `json_object`. Such a request is downgraded to `json_object` instead of being dropped, so a caller asking for JSON still gets JSON. `noStructuredOutputModels` wins when a model is on both lists. The `opencode go`, `opencode zen`, and `opencode free` presets ship this for their DeepSeek routes. | +| `noJsonSchemaModels?` | `string[]` | Exact model IDs whose `openai-chat` endpoint rejects a `json_schema` `response_format` but still accepts `json_object`. Such a request is downgraded to `json_object` instead of being dropped, so a caller asking for JSON still gets JSON. `noStructuredOutputModels` wins when a model is on both lists. The `opencode go` and `opencode zen` presets ship this for their DeepSeek routes. | | `foldDeveloperRoleToSystem?` | `boolean` | Whether an `openai-chat` destination accepts the `developer` role. `foldDeveloperRoleToSystem` unset sends `system`, `true` sends `system`, and `false` sends `developer`. Unset means nothing has been recorded about this destination; `true` records an upstream that rejects the role; `false` records one that accepts it. The message keeps its position in the conversation in every case — only the role changes. A destination that rejects the role answers `400 role 'developer' is not allowed` and the turn never starts, which is why the unrecorded state is the folded one. A provider save that keeps the destination keeps it; see [What a provider save keeps](#what-a-provider-save-keeps). `PATCH` accepts a boolean or `null` to clear it. | | `omitReasoningEffortWithToolsModels?` | `string[]` | Exact `openai-chat` model IDs that accept a reasoning-effort field on an ordinary turn but reject it once function tools are present. The model keeps its advertised effort ladder; OpenCodex omits the wire field for tool-bearing requests only and the upstream default applies. Narrower than `noReasoningModels`, which strips reasoning from every request and costs the model its picker entirely. A provider save that keeps the destination keeps it; see [What a provider save keeps](#what-a-provider-save-keeps). | | `parallelToolCalls?` | `boolean` | Toggle parallel tool calls. OpenAI Chat defaults on; non-chat adapters advertise only on explicit `true`. | | `terminalContinuationGuard?` | `boolean` | Opt in an `openai-chat` provider to one bounded internal re-ask when an actionable turn announces work, then cleanly stops without a tool call. Defaults to `false`; explicit `false` behaves like omission. Combo attempts and routed compaction turns are excluded, and non-`openai-chat` adapters ignore this option. | | `responsesItemIdRepair?` | `{ message?: string[]; reasoning?: string[]; repairMissingTerminalIds?: boolean; repairInvalidIds?: boolean }` | Disabled-by-default downstream SSE repair for exact placeholder ids, missing terminal ids, and (with `repairInvalidIds`) message/reasoning ids missing the canonical `msg_`/`rs_` prefix. Function-call ids are never rewritten. Built-in DeepSeek enables the last two by default. | | `responsesSnapshotRepair?` | `boolean` | Disabled-by-default client-facing repair for sparse Responses lifecycle snapshots in SSE and JSON. Fills missing canonical status, output, and tool metadata while raw inspection and persistence remain unchanged. | +| `messagePhaseInference?` | `boolean` | Off by default. For Responses passthrough streams, infer missing message phases at completion: a subsequent protocol event yields `commentary`; a terminal event or `[DONE]` yields `final_answer`. Explicit commentary/final_answer phases are preserved (null or unknown values are inferred like missing ones), keepalives do not decide phase, and completed/incomplete snapshots reuse per-item decisions when their phase is absent. Live text keeps streaming while message completion waits for classification. Retained state uses the request's translator budget. Also applies to JSON reconstructed from upstream SSE; ordinary upstream JSON is unchanged. | | `webSearchBridge?` | `{ enabled?: boolean; backend?: "ollama" \| "openai" \| "anthropic" \| "xai" \| "gemini" \| "exa"; maxSearches?: number; timeoutMs?: number; endpoint?: string }` | Key-auth `openai-responses` passthrough providers only. Off by default. Codex always declares the hosted `web_search` tool, and the passthrough relays it on the assumption the destination executes it. A gateway that does not run hosted search answers with a `function_call` named `web_search` that nothing runs, and the undeclared-tool guard ends the turn. With `enabled: true` and an explicit `backend` OpenCodex intercepts that call, runs the search itself, feeds the result back to the same upstream, and shows Codex a hosted `web_search_call` cell. Never armed for `authMode: "forward"` (ChatGPT already searches) or for a provider that executes hosted search upstream. `backend` is required; there is no implicit default and a missing credential for the named backend leaves the bridge disarmed rather than falling through to another paid search. `ollama` reuses this provider's own API key on `POST /api/web_search`, so the origin must be `https://ollama.com` unless the operator names `endpoint` explicitly. `openai` / `anthropic` / `xai` / `gemini` / `exa` reuse the matching sidecar executor and that executor's own credential (`webSearchSidecar.exaApiKey` for Exa). The search model comes from `webSearchSidecar.model` only when `webSearchSidecar.backend` resolves to the same backend this bridge names; otherwise the bridge runs that backend's own default, because a model chosen for one vendor is rejected by another. An unset `webSearchSidecar.backend` resolves to `openai`, so an unset-backend model reaches an `openai` bridge and no other. There is no per-provider bridge model override. Streaming turns only. A turn that mixes `web_search` with another client tool call still fails closed rather than dropping the client's call. Assistant text such as XML-like `` prose is not executed. Defaults: `maxSearches: 3` (1..10), `timeoutMs: 60000` (1000..600000). | -| `retryOn429?` | `{ enabled?: boolean; attempts?: number; intervalMs?: number; maxIntervalMs?: number; respectRetryAfter?: boolean }` | API-key providers only (`authMode: "key"`). Opt-in same-target 429 retry: when `retryOn429` is absent the feature is off, except that key-auth OpenCode Go and Command Code (canonical endpoints) fall back to a patient policy (6 replays, 10 s interval, 60 s cap); object presence enables it unless `enabled: false`, which also turns that fallback off. On 429 the proxy waits (upstream `Retry-After` or the fixed interval) and replays the identical request on the same key before any key failover — across the main text-turn recovery loop, the Responses passthrough wire, the image/video bridge, the web-search sidecar, and terminal continuations. Only pre-stream HTTP 429 responses are eligible for replay; custom `runTurn` transports are outside the HTTP retry loop. `attempts` counts same-key replays after the first 429 (total sends = `attempts` + 1) and is one request-wide budget shared by the main recovery loop, the terminal-guard continuation, and bridge retries. Exhausting `attempts` only stops further same-key replays: normal key failover or final-error handling then applies per the available targets — on the key-auth passthrough wire there is no failover, so the exhausted 429 surfaces as-is. Codex itself never retries 429, so this is the only defense for single-key providers. Defaults: `enabled: true`, `attempts: 3`, `intervalMs: 5000`, `maxIntervalMs: 60000` (any single wait is capped at `maxIntervalMs`, itself capped at 600000), `respectRetryAfter: true`. | +| `retryOn429?` | `{ enabled?: boolean; attempts?: number; intervalMs?: number; maxIntervalMs?: number; respectRetryAfter?: boolean }` | API-key providers only (`authMode: "key"`). Opt-in same-target 429 retry: when `retryOn429` is absent the feature is off, except that key-auth OpenCode Go and Command Code (canonical endpoints) fall back to a patient policy (6 replays, 10 s interval, 60 s cap), and canonical Mistral `openai-chat` falls back to 6 replays with a 10 s interval and 60 s cap; object presence enables it unless `enabled: false`, which also turns that fallback off. On 429 the proxy waits (upstream `Retry-After` or the fixed interval) and replays the identical request on the same key before any key failover — across the main text-turn recovery loop, the Responses passthrough wire, the image/video bridge, the web-search sidecar, and terminal continuations. Only pre-stream HTTP 429 responses are eligible for replay; custom `runTurn` transports are outside the HTTP retry loop. `attempts` counts same-key replays after the first 429 (total sends = `attempts` + 1) and is one request-wide budget shared by the main recovery loop, the terminal-guard continuation, and bridge retries. Exhausting `attempts` only stops further same-key replays: normal key failover or final-error handling then applies per the available targets — on the key-auth passthrough wire there is no failover, so the exhausted 429 surfaces as-is. For canonical Mistral Chat, an explicit upstream delay above the allowed wait stops same-key replay (another pooled key may still serve) and returns the original `Retry-After`; other providers retain the capped-delay behavior. Mistral shares retry deadlines per credential/model unless `requestPacing.enabled: false` or `retryOn429.respectRetryAfter: false`. A 429 with validated minute-budget pressure and no retry time activates a conservative 60 s recovery wait, followed by one probe; success restores ordinary traffic. Healthy traffic never predicts the next token charge. This policy uses advertised allowances, excludes known exhausted monthly allowances, does not claim an exact reset time, and never triggers compaction. Codex itself never retries 429, so this is the only defense for single-key providers. Defaults: `enabled: true`, `attempts: 3`, `intervalMs: 5000`, `maxIntervalMs: 60000` (any single wait is capped at `maxIntervalMs`, itself capped at 600000), `respectRetryAfter: true`. | | `transientRetryOn5xx?` | `{ enabled?: boolean; attempts?: number }` | Key-auth `openai-chat` and `openai-responses` providers only. `authMode: "forward"` providers (the ChatGPT account pool) never read this option and keep the default ladder. Opt-in retry for pre-stream transient upstream statuses (500, 502, 503, 504, 520, 521, 522): absent means off, object presence enables it unless `enabled: false`. Covers the initial Responses request, the Responses passthrough lane and each of its recovery legs (OAuth-401 replay, same-target 429 replay, validated rebuild), the terminal-guard continuation, and native `/v1/chat/completions`. `attempts` is the TOTAL number of upstream sends allowed for one request including the first (1..10, default 3) — it is one budget shared with connection-reset recovery, so `3` means at most three real requests reach the provider. On the Responses passthrough lane the configured value is additionally intersected with the request-wide send allowance, so a value below that allowance narrows the ladder exactly while a value above it does not raise the bound. Each Combo target includes its prepaid first send in that target's configured total; `attempts: 1` sends once, without refunding the reservation or widening the shared Combo ceiling. A local refusal or cancellation before dispatch releases an unsent booking. Waits use a fixed 400 ms exponential backoff capped at 5 s and honor `Retry-After`. Separate from `retryOn429`, which handles rate limiting; mid-stream failures are never replayed. | | `retryOnReset?` | `{ enabled?: boolean; replacements?: number }` | Native `openai-responses` providers, including `authMode: "forward"`, plus generic translated Responses dispatch before upstream headers. Opt-in replacement of a send that failed while the caller had observed nothing: absent means off, object presence enables it unless `enabled: false`. Covers both ambiguous stages — a connection that died before any response header, and an SSE body that died after the header while carrying only control events. A canonical ChatGPT upstream WebSocket that closed or errored after its create frame left, before any Responses event, is covered the same way, and its replacement is sent over HTTP. Only a self-contained request is ever replaced: `store: false`, complete `input`, no `previous_response_id`, `conversation` or `stream_id`, and only client-executed tools. `replacements` is the number of replacement sends ONE logical request may make across every leg and every combo child (1..2, default 1) — not a per-leg retry count and not a send budget, so a replacement still has to fit inside the send allowance the leg already had. A request that already emitted output or a tool call is never replaced, whatever this is set to. The replacement inference may still be billed if the origin had already started the first one, which is why this is off by default. Generic translated dispatch supports pre-header reset replacement for initial and rebuilt sends under the same grant and send budget. Adapter-owned transports and translated stream failures after upstream headers are excluded. | | `autoToolChoiceOnlyModels?` | `string[]` | Models whose `tool_choice` accepts only `auto` or `none`; forced choices are downgraded. | @@ -330,7 +396,7 @@ Providers can expose a built-in shorthand, such as `agy` for `google-antigravity | `inlineThinkTagModels?` | `string[]` | Opt-in recovery for `openai-chat` gateways without a server-side reasoning parser. A leading `` / `` / `` block (optionally after whitespace) activates splitting in streamed and buffered replies. All answer whitespace is preserved. Subsequent tags are delimiters anywhere, including same-line interleaving and code fences; this mode does not interpret Markdown. Ordinary text or a code fence before the first tag keeps the whole reply untouched. Off by default; prefer structured upstream reasoning or `reasoningSplitModels` where supported. | | `reasoningDetailsModels?` | `string[]` | Models whose endpoint returns thinking as a structured `reasoning_details` array (MiniMax M-series with `reasoning_split`); stream deltas are cumulative snapshots that are prefix-diffed, and preserved reasoning replays as a `reasoning_details` array instead of a `reasoning_content` string. | | `requiresReasoningPlaceholderModels?` | `string[]` | Models whose upstream rejects a tool_call continuation missing `reasoning_content` (DeepSeek thinking mode); a minimal placeholder is injected when the replay cache misses. Defaults to `preserveReasoningContentModels`; set `[]` to opt out. A provider save that keeps the destination keeps the stored list, including `[]`; see [What a provider save keeps](#what-a-provider-save-keeps). `PATCH /api/providers?name=` accepts an array or `null` to clear it. | -| `showThinkingSummary?` | `boolean` | Display provider-authored summaries when a Responses client omits `reasoning.summary`. Explicit wire `"none"` wins; a client that serializes its preference as omission cannot be distinguished. Raw reasoning remains content and is never relabeled as a summary. The `google-antigravity` preset defaults to `true`; explicit `false` disables that default. CCA Gemini requests also opt into `generationConfig.thinkingConfig.includeThoughts` when display is enabled; image, Claude and gpt-oss requests do not. This does not change client configuration or global catalog summary defaults. | +| `showThinkingSummary?` | `boolean` | Display provider-authored summaries when a Responses client omits `reasoning.summary`. Explicit wire `"none"` wins; a client that serializes its preference as omission cannot be distinguished. Raw reasoning remains content and is never relabeled as a summary. The `google-antigravity` preset defaults to `true`; explicit `false` disables that default. Eligible CCA, Vertex and direct Gemini requests also opt into `generationConfig.thinkingConfig.includeThoughts` when display is enabled; image, Claude, gpt-oss and unladdered Vertex requests do not. This does not change client configuration or global catalog summary defaults. | | `hideRawReasoning?` | `boolean` | Keep a routed provider's raw chain of thought off the client-facing content channel (openai-chat `reasoning_content`, kiro tags, `response.reasoning_text.*`) while provider-authored summaries still stream on the summary channel. Explicit wire `reasoning.summary: "none"` keeps hiding both. This controls what a client displays; it does not keep the text from the client. A Responses bridge route still sends it inside the `ocxr1` replay envelope in the reasoning item's `encrypted_content`, which is base64 JSON rather than encryption, so the client can echo it back on the next turn. The direct Chat and Messages encoders have no envelope on their wire, so those clients receive no copy and replay comes from the server-side reasoning replay cache. `preserveReasoningContentModels` replay and DeepSeek-style continuations are unaffected either way. Off by default; a native passthrough route relays the upstream's own frames and ignores this option. A save of the same provider name keeps an omitted value even after a destination move; `PATCH` accepts a boolean or `null` to clear it. | | `thinkingToggleModels?` | `string[]` | Chat models using `thinking.enabled` rather than an effort ladder. | | `thinkingBudgetModels?` | `string[]` | Chat models using integer `thinking_budget`; effort maps to a budget fraction. | @@ -339,11 +405,12 @@ Providers can expose a built-in shorthand, such as `agy` for `google-antigravity | `anthropicEofTolerance?` | `boolean` | Let an Anthropic-compatible gateway complete a stream that ends before `message_stop`, only when visible text or a complete JSON-object tool input was received. Off by default. | | `googleMode?` | `"ai-studio" \| "vertex" \| "cloud-code-assist"` | Google transport/auth mode. Default `ai-studio`. | | `googleToolSchemaPolicy?` | `"compatible" \| "reject-lossy"` | Google only. Omitted/`compatible` preserves the compatible tool schema and existing non-direct 400 repair. `reject-lossy` rejects initial schema loss or an indeterminate bounded comparison before dispatch and withholds a Vertex or Cloud Code Assist repair that would open constraints. Direct AI Studio never performs that repair. | -| `directGeminiWireRenames?` | `boolean` | Google only. Applies only to direct AI Studio requests. Omitted or `true` keeps the `-tiered` wire rename for Gemini Flash ids (`gemini-3.7-flash` -> `gemini-3.7-flash-tiered`); `false` sends the requested bare ids to the wire unchanged. Vertex preserves the requested model ID, and Cloud Code Assist routing is unchanged. Set `false` when the configured upstream still serves the bare ids. | -| `project?` | `string` | Vertex or Antigravity Cloud Code Assist project id. | +| `directGeminiWireRenames?` | `boolean` | Google only. Applies in AI Studio mode, including custom Google-compatible URLs. Omitted or `true` keeps the `-tiered` wire rename for Gemini Flash ids (`gemini-3.7-flash` -> `gemini-3.7-flash-tiered`); `false` sends the requested bare ids to the wire unchanged. Vertex and canonical keyed OpenCode Zen routes preserve the requested model ID regardless of this setting; Cloud Code Assist routing is unchanged. Set `false` when the configured upstream still serves the bare ids. | +| `project?` | `string` | Vertex or Antigravity Cloud Code Assist project id. Vertex API keys use this project endpoint when set; without it they use express mode, even if `GOOGLE_CLOUD_PROJECT` is set. ADC falls back to `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT`. `"auto"` makes each Vertex API key resolve its own project (a successful lookup is cached per key; a key whose lookup fails is probed again on a later request), so one key pool can span projects; ADC rejects `"auto"`. | | — | — | Antigravity account quota probes (`retrieveUserQuota` and `retrieveUserQuotaSummary`) always go to Google's own Cloud Code host through the pinned outbound transport, regardless of a configured `baseUrl`; the account bearer is never sent to an operator-configured endpoint and a redirect aborts the probe. Only the model-list fallback still honors `baseUrl`. | | — | — | If Antigravity quota summary returns 403 for a valid OAuth account, OpenCodex retries that endpoint once with the legacy `antigravity/1.0` User-Agent and the same token and project. A 401 is not retried. Inference and model discovery retain the IDE User-Agent. | -| `location?` | `string` | Vertex location; environment fallback is `GOOGLE_CLOUD_LOCATION`. | +| — | — | Antigravity quota shows Gemini 4 Argon as separate `Argon` / `Argon (Weekly)` windows next to Gem and Cla. They are display-only: account ranking, quota exhaustion, reset ordering and reset notifications keep using the Gemini and Claude windows, and Argon requests are still judged by the Gemini window. | +| `location?` | `string` | Vertex location. API-key project endpoints default to `global` and ignore `GOOGLE_CLOUD_LOCATION`; ADC requires this field or the `GOOGLE_CLOUD_LOCATION` fallback. Gemini 4 Argon supports `global`, `us`, and `eu`. | | `mcpServers?` | `Record` | Cursor only: stdio or Streamable HTTP MCP servers. | | `desktopExecutor?` | `DesktopExecutorConfig` | Cursor only: external computer-use and record-screen commands. | | `unsafeAllowNativeLocalExec?` | `boolean` | Cursor legacy boolean, equivalent to `nativeLocalExec: "on"` only when the newer field is unset. | @@ -377,6 +444,23 @@ For example, this applies a provider-wide concurrency cap and a stricter cap to The concurrency slot stays occupied until the upstream request finishes, including the final streamed response bytes. Requests above the applicable provider and model limits wait in the pacing queue; when a slot is released, the next eligible request is admitted. Queue waiting does not consume the upstream response-header timeout. +For multiple API-key pool entries, opt into normal-request distribution separately from the existing failure-selection `apiKeyPoolStrategy`: + +```json +{ + "requestPacing": { + "enabled": true, + "keySelection": "round-robin", + "maxConcurrentRequests": 10, + "keys": { "requestsPerMinute": 15, "maxConcurrentRequests": 1 } + } +} +``` + +This admits at most ten active requests across the provider and one per credential reference, with starts on each reference at least four seconds apart. These are local upper bounds, not a promise of upstream capacity. A provider-wide RPM setting, when present, remains an aggregate limit and does not multiply with key count. Key limits span models; model rules remain additional restrictions. Projects sharing quota need an appropriate aggregate limit; TPM and daily quotas are not tracked here. + +Round-robin selects keys per request without saving a new active key. Known 401/429 cooldowns are skipped; when none remains eligible, new requests receive a retryable 429 instead of repeatedly sending. Already queued work normally retains its selected key and may wait behind that key even when another is idle. If that selection becomes cooled or invalid, the request selects an eligible key again and reacquires pacing admission for it; the bounded queue expires rather than waiting forever. Selection and diagnostics are process-local, reset on restart, and do not equal balanced token usage or billing. Starts count pacing admissions, including work cancelled before dispatch. OAuth and forwarded credentials keep their existing selection. The dashboard shows key handles, starts, in-flight work, queued work and known cooldowns, never secret key values. These limits cover the inference pacing executor, not the standalone Images relay's direct fetch. + ### Azure OpenAI model metadata discovery For a base URL whose hostname ends in `.openai.azure.com`, discovery fills missing image/text @@ -413,6 +497,10 @@ The destination is the adapter, the base URL (scheme and host compared without r `hideRawReasoning` is an operator display policy, so a save of the same provider name keeps its stored value whenever the request omits it, including after a destination move. A value sent in the request wins, including `false`. +The optional `inferContextWindow` setting also survives same-name saves that omit it, including +destination moves. Its read, update, and clear behavior is described under +[Context windows on custom providers](#context-windows-on-custom-providers). + `PATCH /api/providers?name=` changes only the fields it names and keeps every other stored field, whatever the destination. It accepts all eight settings; `null` clears one. It also accepts a boolean `hideRawReasoning` or `null` to clear it. For the two reasoning lists an empty array is stored as an explicit opt-out rather than removed. With `webSearchBridge` enabled, a search continuation stays bound to the API-key selection that @@ -536,9 +624,17 @@ optional pin in a hand-edited file is ignored on load without discarding the res Codex reads `auto_review_model_override` from the catalog row of the current turn's model to choose the model that reviews approval requests. The root `auto_review_model` setting in -`$CODEX_HOME/config.toml` applies one reviewer to task catalog rows; the provider-scoped fields -below override it per provider. The [provider guide](/guides/providers/#approval-reviewer-per-provider) -has the operator workflow and a worked example. +`$CODEX_HOME/config.toml` supplies Codex's fallback. The dashboard's global **Auto-review override** +in Model settings stores `autoReviewOverride` in `~/.opencodex/config.json` and takes precedence +over that fallback; the provider-scoped fields below still win on their own rows. The global setting +must be enabled and name a model. An enabled setting with no model stays unapplied and leaves the +Codex fallback in effect. A catalog refresh is a separate state: after a save it can stay pending, +and the Models page reports it so you can retry with `ocx sync`. CLI users can read the setting with +`ocx models auto-review status` or change it with +`ocx models auto-review set [--enabled ]`; use `-` to clear the model. CLI output +recommends `ocx sync` when a refresh can be retried. If a refresh fails, the setting remains saved +and the command exits 1. Resolve the failure before syncing. The [provider +guide](/guides/providers/#approval-reviewer-per-provider) has the operator workflow and a worked example. When native OpenAI rows are included and the final catalog has an ordinary bare native row other than Reserve (hidden rows count), OpenCodex keeps the hidden `codex-auto-review` row so @@ -619,7 +715,9 @@ The effective label order is operator `modelDisplayNames`, then provider catalog normal `provider/model` fallback. The routed selector remains `xai/grok-4.6`, while the upstream wire model remains `grok-4.6`. Labels are display only. They do not change authentication, adapter behavior, routing, billing, or upstream request construction. Removing a map entry resets only its -label. A management client can set or reset one label with +label. For built-in OpenAI API models, use +`providers.openai-apikey.modelDisplayNames`; the label survives catalog refreshes. +A management client can set or reset one label with `PUT /api/providers/:provider/model-display-names` and a body of `{ "modelId": "grok-4.6", "displayName": "Grok 4.6" }`; send `displayName: null` to reset it. Provider `PATCH` does not edit this map. Use this dedicated `PUT` endpoint to change or remove labels. @@ -828,6 +926,7 @@ OpenAI-backed model slugs: - `openai/gpt-5.6-sol` - `openai/gpt-5.6-terra` +- `openai/gpt-6.1-terra` - `openai/gpt-5.6-luna` `anthropic/claude-sonnet-5` and undeclared OpenRouter models remain unclassified. A provider-level @@ -874,6 +973,10 @@ outbound proxy, opencodex resolves the hostname once and connects only to that v HTTPS retains the original Host, SNI, and certificate verification; provider config cannot disable certificate checks. +Direct response downloads pause while local processing is not ready for more data, including +gzip and deflate responses. This pause does not count as provider inactivity; the request's +overall deadline and cancellation still apply. + These operations use the [server's configured outbound fetch](/reference/configuration/server/). A server SOCKS5 proxy — set with `config.proxy` or inherited from a SOCKS5 `ALL_PROXY` — uses OpenCodex's built-in tunnel when @@ -1137,7 +1240,8 @@ second account. | --- | --- | --- | --- | | `oauthAccountFailover.enabled?` | `boolean` | presence-driven | Global override for the **pre-dispatch account preference** only. `false` stops a healthy request being steered toward the account with more known headroom. It does **not** disable 429 rotation. | | `providers..oauthAccountFailover.enabled?` | `boolean` | inherits | Per-provider override for the same preference; beats the global setting in either direction. `false` declines the preference for this provider even when the global setting is `true`, and `true` opts this provider in even when the global setting is `false`. Reactive 429 rotation is unaffected either way. | -| `providers..oauthAccountFailover.strategy?` | `"quota" \| "round-robin" \| "fill-first" \| "least-loaded"` (Kiro only) | — | Kiro's `least-loaded` picks the eligible account with the fewest in-flight requests, using account order for ties. It requires `pool.kernel` and proactive preference; other strategies retain their existing rules. | +| `providers..oauthAccountFailover.sessionAffinity?` | `"conversation" \| "none"` | `"none"` | Opt-in conversation pinning for generic OAuth pools. Requires proactive `enabled: true` (provider or inherited global value). Existing sessions keep their account ahead of the new-session strategy until it becomes unavailable. | +| `providers..oauthAccountFailover.strategy?` | `"quota" \| "round-robin" \| "fill-first" \| "reset-first" \| "least-loaded"` (Kiro only) | — | Kiro's `least-loaded` picks the eligible account with the fewest in-flight requests, using account order for ties. It requires `pool.kernel` and proactive preference; other strategies retain their existing rules. `reset-first` also requires `pool.kernel`: among eligible accounts that are not exhausted, the first attempt (while the proactive preference is on) and a 429 rotation both prefer the account whose quota resets soonest (the weekly window when one is reported, otherwise the 5-hour window), with more headroom breaking ties. Exhausted accounts go last. Without quota evidence, or when a passive provider has evidence for only some accounts, it falls back to headroom order. The Anthropic pool does not accept it. | | `providers.kiro.oauthAccountFailover.maxConcurrentPerAccount?` | integer 1–100 | unlimited | A process-local per-account queue holds a slot through response completion or cancellation. A full selected account waits at most 250 ms, then returns 503 `account_capacity` with `Retry-After: 1`. The cap does not move the request to another account. | | `providers..oauthAccountFailover.autoSwitchThreshold?` | `number` | `80` | 0–100 usage percent at which `fill-first` advances off the active account (#695). Set with `ocx account auto-switch threshold `. Read only under `pool.kernel` with `strategy: "fill-first"`; an account with no measured usage counts as under the threshold. | | `providers..oauthAccountFailover.stickyLimit?` | `number` | `1` | Successful dispatches retained on one `round-robin` selection, 1–100 (#695). Read only under `pool.kernel` with `strategy: "round-robin"`. | @@ -1166,9 +1270,26 @@ and the `ocx account strategy` / `ocx account auto-switch` / `ocx account sticky preference. `quotaWindow` is not part of the generic contract. Codex (`/api/codex-auth`) and Anthropic (`anthropicAccountPool`) keep their own contracts unchanged. -Deliberately narrower than `anthropicAccountPool`: no session affinity, no quota-ranked -selection, no probe leases. It answers one question — the account that just returned 429 is -cooled, is there another one available. +Set `providers..oauthAccountFailover.sessionAffinity` to `"conversation"` together with +`enabled: true` to keep a conversation on its admitted account. Session identity uses the client +thread marker, session headers, thread header, then `prompt_cache_key`; a shared Desktop cache +cohort is excluded. Bindings are isolated by provider, configuration home and authenticated caller +principal. Unauthenticated local callers share the local scope. No session marker means no binding. + +New sessions use the existing strategy (`pool.kernel` still gates round-robin/fill-first). +Existing sessions stay pinned even when another account has more quota. A pause, reauthentication +requirement, family cooldown or incompatible model roster releases the binding; existing bounded +failure recovery binds the admitted replacement. Kiro's concurrency cap still waits or refuses +rather than moving a healthy bound conversation merely to balance load. + +In this mode automatic choices, including unkeyed requests and failovers, stay request-local: +the dashboard's selected account remains the operator's selection. Request history still attributes +the serving account. Manual selection, same-account reselection and roster/policy revision changes +invalidate provider bindings, including writes from another process. An ordinary refresh keeps the +same account. Fill-first retains its current account in memory without changing the saved selection. +Bindings expire after 24 hours without admission, are bounded to 2,000 entries process-wide +(including fill-first cursors), and are lost on restart. Set `sessionAffinity: "none"` or disable +proactive preference to release them. Codex and Anthropic retain their own affinity contracts. The Codex pool and the Anthropic pool are excluded and keep their own rotation; enabling this changes neither. A provider with a single stored account is a strict no-op, and no cooldown is @@ -1224,7 +1345,7 @@ provider in question. ### Managed record shapes -`apiKeys[]` entries contain `id`, `name`, generated `key`, and ISO `createdAt` strings. +`apiKeys[]` entries contain `id`, `name`, generated `key`, and ISO `createdAt` strings. Optional `allowMeteredComboTargets: true` explicitly permits targets marked `metered: true`; omitted or false consent denies them. The environment token never qualifies. Local loopback-only callers can instead use per-combo `allowMeteredTargets: true`; configured-key and linked callers still need key consent. See [Metered fallback consent](/guides/combos/#metered-fallback-consent). `codexAccounts[]` entries require `id`, `email`, and `isMain`, with optional `plan`, `chatgptAccountId`, and privacy-safe `logLabel`. These records are normally dashboard-managed. @@ -1345,6 +1466,12 @@ Set `nativeLocalExec` on `providers.cursor`, not at the top level. In the dashbo `nativeLocalExec: "on"` only when `nativeLocalExec` is unset. MCP, screen recording, and computer use are controlled separately by `mcpServers` and `desktopExecutor`. +For an enabled native `grep` directory search, `glob` filters file names: `*.ts` matches at any +depth, while `src/**/*.ts` matches paths relative to the selected search directory (`./` is optional). Use `/` for +pattern separators on every platform. Character classes and brace alternatives are supported; +Bun 1.4.4 also supports extglobs such as `**/!(*.test).ts` and ranges such as `part-{1..3}.ts`. +These filters do not change the selected directory or enable native execution. + Foreground `shellArgs` and `shellStreamArgs` requests are rejected before process creation on Windows, macOS, and Linux, including with `nativeLocalExec: "on"`. They require a kernel-backed descendant owner that this implementation does not provide; use the client's shell tool instead. @@ -1462,6 +1589,32 @@ model key. ## Storing keys in the OS keychain +For Vertex, the dedicated API-key pool input (`POST /api/providers/keys`) accepts complete GCP +`service_account` or `authorized_user` credential JSON after the provider has been created. +The provider create/edit form and `ocx provider add --api-key` refuse credential JSON before +saving: create the provider without a key, then add the JSON through its key pool. Paste the +JSON contents, not a Windows, Unix, or relative file path. Multiple +JSON objects separated by commas or whitespace are stored together as separate pool entries; +the first becomes active. Configuration contains only `gcp-sa:` references, never the +credential JSON. Mixed JSON/literal-key bulk pastes are rejected as a whole; add literal keys +separately. Storage failures leave the configuration unchanged and report a fixed error without +echoing credential material. Empty marker accounts are invalid and do not fall back to host credentials. +Paste one credential object or a sequence of objects, without an enclosing array or quoted string. +Malformed credential text is rejected. If a selected stored credential is removed or becomes unreadable, +Vertex refuses the request even when it previously cached an access token for that credential. +Removing a key or provider, or replacing a provider with `ocx provider add --force`, deletes its unreferenced GCP credentials after the configuration is +saved; a credential referenced by another provider is retained. If OS-store cleanup fails, a +warning requests manual inspection without undoing the committed configuration removal. +Ordinary keychain store/restore actions leave GCP markers and JSON in the OS store. Restoring +credential JSON through a `keychain:` alias is refused, and cleanup preserves accounts still +referenced through either marker spelling. +Using a `keychain:` alias containing GCP JSON as an API key also refuses the request; select its +`gcp-sa:` reference for Vertex authentication instead. +Literal API keys must be plain token text: JSON objects, arrays and quoted JSON strings are refused +in literal-key resolution and restore, including wrappers containing escaped credential text. +Credential writes and marker persistence share the cleanup transaction lock, so concurrent +removal cannot delete the stored JSON between read-back and the marker commit. + By default a provider's `apiKey` and `apiKeyPool` sit in `config.json` (mode 0600, atomic writes). If you would rather keep the key material out of the file, move it into the OS credential store: @@ -1682,7 +1835,7 @@ contracts. ### Per-model capability declarations -`modelCapabilities` stores explicit declarations keyed by exact upstream model ID. IDs preserve case and must not contain surrounding whitespace. Each entry may contain `inputModalities` (`text`, `image`, `audio`, `video`), `contextTier` (`default`, `long_context`) and `video.processing` (`static`, `agentic`). These are operator declarations, not proof of provider support. Context-tier and video fields currently record intent only and do not activate upstream behavior or increase catalog windows. +`modelCapabilities` stores explicit declarations keyed by exact upstream model ID. IDs preserve case and must not contain surrounding whitespace. Each entry may contain `inputModalities` (`text`, `image`, `audio`, `video`), `contextTier` (`default`, `long_context`), `video.processing` (`static`, `agentic`) and `deepseekImageFiles` (`boolean`; see [DeepSeek image bytes](#deepseek-tokens-versus-image-bytes)). These are operator declarations, not proof of provider support. Context-tier and video fields currently record intent only and do not activate upstream behavior or increase catalog windows. The raw provider editor and provider API expose this map. POST/PUT replace an explicitly supplied map and reject null entries. PATCH merges individual axes; null clears a map, model, axis or video processing value, while `{}` makes no change. Omitted provider overwrites preserve the existing map. Malformed hand-edited files retain valid independent axes and treat malformed explicit input modalities as text-only, with a diagnostic. @@ -1702,6 +1855,57 @@ image is absent from the list. A row declaring only `audio` or `video` therefore image-incapable, rather than being treated as a text model by one predicate and an image target by the other. +### Prompt-length pricing + +In **Models → Price**, keep four base rates and choose a prompt-length mode: + +- **Automatic** keeps the catalog context rules. It is also the behavior of existing four-rate overrides without a policy. +- **Flat rate** uses the base rates at every prompt length, disabling catalog context adjustments. +- **Custom threshold** selects four absolute alternative rates using `>` or `≥` and one token threshold. The custom policy replaces the catalog context rule on both sides of the threshold. + +All prompt tokens count, including cache reads and writes, before billable-input subtraction. +Output does not count toward the threshold. Once crossed, the alternative rates apply to the +entire request, including output, not only the excess tokens. Custom rates are standard-speed +rates: an applicable Fast/Priority premium applies once after the rates are selected and follows +the catalog's relation between Priority and long-context pricing for that model. A confirmed +Priority request shows the custom estimate as a lower bound only when the catalog declares that +relation as `lower-bound`, as for xAI Grok, which publishes no combined rate. A model with no +catalog context rule applies any Priority multiplier once. These settings control estimates; they +do not change provider billing. +**Reset to automatic** deletes the entire override and restores catalog prices and rules. Saving or +editing the config updates live estimates and historical totals. + +For example, a Haiku 5.5 override with alternative rates strictly above 100,000 prompt tokens: + +```json +{ + "modelCosts": { + "claude-haiku-5-5": { + "input": 0.10, + "output": 0.50, + "cacheRead": 0.01, + "cacheWrite": 0.125, + "promptLengthPricing": { + "mode": "custom", + "thresholdTokens": 100000, + "comparison": "gt", + "rates": { "input": 0.50, "output": 2.50, "cacheRead": 0.05, "cacheWrite": 0.625 } + } + } + } +} +``` + +This is an example of user-defined prices, not an official Haiku price list. +Any positive safe-integer threshold, including `272000`, is supported; use `"gte"` for inclusive +comparison. All four alternative rates are required and follow the base-rate limits, including zero. +The other policy is `{ "mode": "flat" }`. Omit `promptLengthPricing` to retain automatic context +rules with your base prices; an explicit `{ "mode": "automatic" }` is accepted and means the same, +and saves from the dashboard or API store it as an absent field. + +Legacy `openai-multi` model price overrides retain valid prompt-length policies when startup +migrates them to `openai`, including overrides keyed by `openai-multi/`. + ### Renamed API-key presets A provider saved under another name, such as `CommandCode`, inherits missing reasoning-effort metadata when its adapter and fixed API-key endpoint match a registry preset. Your explicit per-model lists, including `[]`, remain authoritative. An omitted provider-wide list inherits the preset default; an explicit list remains unchanged. This does not match OAuth, unrelated endpoints, or templated/custom endpoint presets. @@ -1730,3 +1934,62 @@ raised output allowance would exceed that ceiling. } } ``` + +### Native Chat thinking replay + +These Chat reasoning lists also apply to native `/v1/chat/completions` tool-call continuation. +The proxy preserves supplied `reasoning_content` and restores omitted reasoning from upstream +JSON or SSE responses using tool-call ids. Original reasoning replay requires a stable +`thread-id`, `session_id`, or `session-id` header on each request and the same provider, endpoint, +model and credential. The cache is process-local and bounded; a restart, expiry, eviction, or +missing conversation header uses the configured minimal placeholder instead. A placeholder +cannot reconstruct lost reasoning. Clients should retain upstream `reasoning_content` in their +assistant history for reliable resumed conversations. + +When `preserveReasoningContentModels` is unset, DeepSeek Flash/Pro model names are recognized +across versions, including `deepseek-v4.1-flash`, namespaced ids and gateway suffixes. Explicit +lists remain authoritative: `preserveReasoningContentModels: []` disables automatic repair, +while `requiresReasoningPlaceholderModels: []` disables placeholders and still allows cached +original reasoning. Other model families require the preserve declaration. +Tool-enabled DeepSeek requests also require reasoning on earlier assistant answers without tool +calls. The configured placeholder fills these omissions; no original reasoning can be recovered +from the tool-call cache for those turns. Preserve their supplied reasoning for full fidelity. + +## Auto-pause permanently refused accounts + +`providers..accountAutoPause` is opt-in and defaults to off. It applies to that +provider's OAuth accounts, Codex pool accounts (including the main account in pool mode), +and configured API-key pool entries. For example: + +```sh +ocx config set providers.anthropic.accountAutoPause.enabled true +ocx config set providers.anthropic.accountAutoPause.consecutiveFailures 2 +``` + +`consecutiveFailures` is an integer from 1 to 100; omitting it uses 2. Only exact, +account-scoped HTTP 401/403 refusals qualify: an organization forbidding OAuth, an inactive +or expired subscription, an account denied API/Claude Code access, or a downgrade to a free +plan with no credits. A model permission error, expired access token, quota window, 429, +5xx, malformed response or network failure never pauses an account. + +The counter is process-local, serialized per account, and resets on a successful HTTP +response, explicit resume or a new login. Token refresh alone does not reset an OAuth +account's streak. Transient errors leave the counter unchanged. Restarting the proxy clears +counters; completed pauses, their fixed reason codes and timestamps persist. Independent +proxy processes maintain independent counters. Changing, disabling or re-enabling the policy, disabling the provider, +or changing its endpoint discards in-flight refusal observations, so a refusal from a request +sent under the earlier policy can never pause an account afterwards. A refusal counts only for the credential that actually +reached the upstream: when a local plugin rewrites the credential or destination of a send, that +send never counts toward a pause. For Codex pool accounts this includes the workspace +(`chatgpt-account-id`) the request was sent for. + +At the threshold, OpenCodex excludes the account using its existing pause mechanism and +attempts an eligible sibling for the current request within the existing retry/send budget. +Fixed Codex account selectors and account-bound uploaded files retain their restrictions. +A pause never cancels already dispatched work or changes credentials. It emits one generic +log line without account identifiers or upstream error text. + +`ocx account list ` shows the pause reason. The dashboard's Paused badge exposes +the reason and time. Use `ocx account resume ` to clear the +reason and allow selection again. On an API-key row, clicking Resume also activates the key. +This policy never resumes an account automatically. diff --git a/docs-site/src/content/docs/reference/configuration/routing.md b/docs-site/src/content/docs/reference/configuration/routing.md index ff299a2b440..e518b009789 100644 --- a/docs-site/src/content/docs/reference/configuration/routing.md +++ b/docs-site/src/content/docs/reference/configuration/routing.md @@ -13,13 +13,23 @@ Routing turns the model id sent by a client into one concrete provider and upstr | `combos?` | `Record` | `{}` | Virtual `combo/` models built from ordered provider/model targets. | | `routingProfiles?` | `Record` | `{}` | Virtual `policy/` models that select among an explicit candidate allowlist using hard capability requirements and deterministic scoring. | +### Model alias discovery + +Set `preferModelAliasIds: true` to publish configured or enabled built-in model aliases +as the `id` in `GET /v1/models`. The default is `false`. Promoted rows retain their +previous qualified ID in `native_id` and keep `alias_of`; both IDs remain routable. +Models without aliases keep their existing IDs. If a short alias is ambiguous across +providers or is shadowed by another route, the row keeps its qualified ID so selecting +it still reaches the intended model. The qualified ID also stays when a built-in short +alias would produce an unparseable synthetic Fast selector. Codex's own model catalog is unchanged. + ### Codex Pool low-quota protection `codexPool.lowQuotaProtection` applies only to stored Codex Pool accounts. The Desktop/main account keeps its separate 98% hard lock. The optional shape is: ```json -{"codexPool":{"lowQuotaProtection":{"enabled":true,"threshold":80,"actions":{"pause":true,"notify":true},"windows":{"short":true,"weekly":true}}}} +{"codexPool":{"lowQuotaProtection":{"enabled":true,"threshold":80,"actions":{"pause":true,"notify":true,"desktop":true},"windows":{"short":true,"weekly":true}}}} ``` Absence or `enabled: false` disables it. `threshold` is a finite inclusive percentage from 1 @@ -42,9 +52,20 @@ With `notify`, the server writes a local log line with window and percentage but and records a bounded event. Authenticated `GET /api/codex-auth/low-quota-events` exposes only that server’s events, including account id, window, usage percentage, known reset time, timestamp, and status. The default status is `logged`: the alert reached the log and event history only. -There is no desktop or OS notification. Each account/window logs once per server-local episode. -If an injected notification sink fails, its failure is recorded and a later eligible observation -can retry it; only a successful sink is marked `delivered`. +Each account/window logs once per server-local episode. + +Optional `actions.desktop: true` (it requires `notify: true`) also hands each notice to the +operating-system notifier of the machine running the proxy: `/usr/bin/osascript` on macOS, or +`notify-send` from `/usr/bin`, `/usr/local/bin` or `/bin` on Linux. The notification names the account +only by its pseudonymous log label (for example `p1a2b3c`), never its email, alias or id, and +includes the window, usage, threshold and known reset time. Windows and hosts without a supported +notifier keep the alert `logged`. `delivered` means the OS notifier accepted the notice; it cannot +see whether the system's notification settings then hide it. A failed or timed-out notifier is +recorded as `failed`; an eligible observation at least five minutes later can retry it, without +repeating the log line. Turning `desktop` on or off +applies to the next observation without a restart. A notice still in flight is cancelled at shutdown, and +when the next quota observation detects that its episode ended (reset boundary, recovery, +account removal or policy change); until then the notifier is bounded by a five-second timeout. ## Model resolution order @@ -138,7 +159,8 @@ namespace, and cannot use reserved bare native families such as `gpt-*`, `o1-*`, | Key | Type | Default | Meaning | | --- | --- | --- | --- | -| `targets` | `{ provider: string; model: string; weight?: number; lastResort?: boolean }[]` | required | Ordered concrete routes. `weight` is 1–10000 and defaults to `1`. `lastResort` marks an emergency-only target; see `cooldownWaitPolicy`. | +| `targets` | `{ provider: string; model: string; weight?: number; lastResort?: boolean; metered?: boolean }[]` | required | Ordered concrete routes. `weight` is 1–10000 and defaults to `1`. `lastResort` marks an emergency-only target; see `cooldownWaitPolicy`. `metered: true` requires key consent or loopback-only combo consent; see [consent](/guides/combos/#metered-fallback-consent). | +| `allowMeteredTargets?` | `boolean` | `false` | Operator consent for local callers on a loopback-only proxy. A non-loopback listener disables this flag; configured-key and linked callers still need key consent. | | `strategy?` | `"failover" \| "round-robin" \| "random" \| "least-used" \| "reset-window" \| "jev"` | `"failover"` | Selection strategy. Target order is failover priority; weights shape round-robin and random draws; least-used follows recorded successes; reset-window follows the soonest quota reset; JEV makes one bounded decision for the initial eligible target and effort, then uses ordinary ordered fallback. | | `stickyLimit?` | `number` | `1` | Successful requests retained in one round-robin batch. Range 1–100. Applies only to round-robin. | | `cooldownMs?` | `number` | unset → upstream fallback (5 s for request-rate 429 codes `1302`/`1305`, otherwise 60 s) | Range 1–600000. When set, applies whenever no usable upstream `Retry-After` or Codex reset signal exists, including request-rate 429s; when unset, uses the upstream fallback. Upstream signals take precedence. An explicit upstream `Retry-After` is capped at 24 hours; reset-derived, configured, and fallback cooldowns are capped at 10 minutes. | @@ -154,6 +176,14 @@ namespace, and cannot use reserved bare native families such as `gpt-*`, `o1-*`, | `decisionProvider?` | `string` | `"jev"` | `strategy: "jev"` only. `"jev"` (the same as omitting it, and stored as omission) is the TypeSafe decision service, valid without a provider row; any other value must name a configured provider with `adapter: "jev-decision"` and a full HTTPS decision `baseUrl` (any path) or a local HTTP `/systemone` endpoint. Userinfo, query strings, and fragments are refused. | | `decisionModel?` | `string` | unset | `strategy: "jev"` only, mutually exclusive with `decisionProvider`. An ordinary opencodex route (for example `ollama/qwen3:4b`) asked to pick one offered option as JSON. It runs with the selected provider's stored credentials, never the caller's, and cannot resolve to this combo, any JEV combo, or a `jev-decision` row. | | `decisionTimeoutMs?` | `number` | `4000` | `strategy: "jev"` only. Decision deadline before failing open, 1000–120000 ms. | +| `decisionQuotaSignals?` | `boolean` | `false` | JEV only. Explicit `true` sends loaded advisory quota evidence to the selected backend; never changes hard eligibility or account selection. | +| `decisionQuotaTiers?` | `{ moderate?: number; limited?: number; nearlyExhausted?: number }` | `{ limited: 70, nearlyExhausted: 90 }` | JEV only. Partial overrides merge with defaults; finite 0–100 values must be strictly ascending. `moderate` is absent unless explicit. Does not enable signals by itself. | +| `sessionPin?` | `{ ttlMs: number }` | off | JEV only. Keeps an explicit client session on the target JEV chose for it while the session stays active; `ttlMs` is the idle window, 60000–86400000. `null` clears. See [Session pin for JEV](/guides/combos/#session-pin-for-jev). | + +Quota settings, privacy and unknown-pool semantics are described in +[Advisory quota signals for JEV](/guides/combos/#advisory-quota-signals-for-jev). +Omitted settings survive JEV API updates/rename; `null` clears, `false` disables signals without +clearing tiers, and switching to another strategy removes both settings. ```json { @@ -367,7 +397,8 @@ Returned history and route-decision payloads expose only masked request metadata (for example opaque `apiKeyId` labels). They do not include credentials, raw prompt bodies, or provider secrets. -CLI: `ocx logs explain `, `ocx logs rebuild-index`, +CLI: `ocx logs history`, `ocx logs get `, +`ocx logs explain `, `ocx logs rebuild-index`, `ocx logs index-status`, `ocx route policy list | show | dry-run | evaluate`. ## Migration diff --git a/docs-site/src/content/docs/reference/configuration/server.md b/docs-site/src/content/docs/reference/configuration/server.md index 45da7168de8..ae044e3faa9 100644 --- a/docs-site/src/content/docs/reference/configuration/server.md +++ b/docs-site/src/content/docs/reference/configuration/server.md @@ -23,6 +23,7 @@ runs helper features around provider requests. | `websockets?` | `boolean` | `false` | Advertise and admit the client-facing Responses WebSocket path. False keeps clients on HTTP/SSE; it does not disable an eligible canonical ChatGPT upstream WS optimization. Complete-input requests may reuse an upstream connection within the same selected credential, account, thread and turn; changed handshake policy or missing identity keeps requests on separate connections. This does not trim HTTP input or create previous-response IDs. | | `codexNativeSteering?` | `boolean` | `false` | Experimental, native-only mid-turn steering on the Responses WebSocket endpoint. Requires `websockets: true`, a compatible upstream/client, and a pinned account/model/tool surface. Validated generation settings can change in explicit saved-result continuations. Does not enable translated models or HTTP fallback. See [native steering](/guides/codex-integration/#experimental-native-mid-turn-steering). | | `codexNativeInjection?` | `boolean` | `false` | Experimental saved function-result injection on compatible native multi-agent WebSocket turns. Requires `websockets: true`, explicit `multi_agent.enabled`, and an eligible provider. Separate from steering; no automatic tool rerun or recovery create. See [native injection](/guides/codex-integration/#experimental-native-function-result-injection). | +| `nativeReasoningRetention?` | `{ modelSwitch?: boolean; accountSwitch?: boolean }` | both `false` | Permit reasoning ciphertext forwarding across a known model or account change on the same canonical ChatGPT backend. Account retention is experimental. See [Native reasoning retention](#native-reasoning-retention). | | `corsAllowOrigins?` | `string[]` | `[]` | Additional exact origins allowed by CORS. Loopback origins are always allowed. Authority-based browser extension origins such as `chrome-extension://` are supported; `*` is not a wildcard. Firefox and Safari regenerate the extension UUID (per install / per browser launch), so update the entry when the origin changes. | | `apiKeys?` | `OcxApiKey[]` | `[]` | Generated `ocx_…` data-plane admission credentials on non-loopback binds. They do not authorize management APIs; management access uses the separate credential documented in the [management reference](/reference/management-api/). Dashboard-managed. | | `storageCleanupPolicy?` | `StorageCleanupPolicy` | disabled | Opt-in archived-session cleanup policy. Never enabled implicitly. | @@ -41,6 +42,7 @@ runs helper features around provider requests. | `codexProviderDisplayName?` | `string` | `"OpenCodex Proxy"` | Label Codex shows for the injected `opencodex` provider, written as its `name` field in `config.toml` and the reference profile. Presentation only: routing resolves through the provider id `opencodex`, so a rename never moves `model_provider = "opencodex"` or the `[model_providers.opencodex]` header and cannot orphan threads already tagged with that id. Codex refuses to load a provider with no name, so there is no way to omit the field — choose a neutral label instead. A blank, over-128-character, or control-character value is ignored and the default label is written. | | `codexMainAccountHardLock?` | `boolean` | `true` | Block new identity-matched main-account requests at their per-window threshold. Only explicit `false` opts out. See [main-account protection](/reference/cli/providers-accounts/#main-account-quota-protection). | | `codexMainAccountHardLockThresholds?` | `{ short?: number; long?: number }` | `{ short: 90, long: 98 }` | Independent 5h and weekly/monthly thresholds. Integers 80–100, with `short <= long`. Invalid persisted fields fall back to defaults; the settings API rejects invalid writes before mutation. | +| `codexMainAccountHardLockFallback?` | `{ enabled?: boolean; model?: string; reasoningEffort?: string }` | off | Extended hard-lock settings: while the lock blocks and no Codex account can serve the request, qualifying native-Codex-model requests reroute to `model` with `reasoningEffort` instead of failing. `enabled: false` pauses the reroute and keeps the model. An absent section or model keeps the refusal behavior; a usable Pool account still serves first. `reasoningEffort` must be a declared effort; invalid persisted sections degrade to off and the settings API rejects invalid writes before mutation. See [main-account protection](/reference/cli/providers-accounts/#main-account-quota-protection). | | `nativeMainRefresh?` | `"codex" \| "opencodex"` | auto | Who refreshes the native main ChatGPT login in `CODEX_HOME`. `"codex"` asks the Codex binary (app-server `getAuthStatus`, Codex 0.130.0 or newer, found through the same runtime resolution as `ocx sync`, including the ChatGPT app bundle) and never writes `auth.json`; this also works when Codex stores its login in the OS keyring. `"opencodex"` keeps OpenCodex's guarded `auth.json` writer. Unset picks `"codex"` when a supported binary is found; without one, both unset and `"codex"` fall back to the guarded writer. `ocx doctor` shows the active mode under Native main refresh. | | `resetCreditAutoRedeem?` | `{ enabled?: boolean; leadTimeMinutes?: number }` | off | Opt-in: redeem the main Codex account's soonest-expiring reset credit `leadTimeMinutes` (1–60, default 10) before it expires. Every attempt re-reads the upstream credit list first and skips when the credit is gone (for example, redeemed by hand); the `redeem_request_id` is journaled in `$OPENCODEX_HOME/reset-credit-auto-redeem.json` before the call so a crash replays the same idempotent request instead of spending a second credit. Servers sharing this configuration directory coordinate reservations and settlements so one process does not replace another's request record. Logs carry a hashed account key only. | | `syncResumeHistory?` | `boolean` | `true` | Reversible Codex App history compatibility. Original metadata is backed up and restored by `ocx stop` / `ocx restore`. | @@ -51,6 +53,32 @@ runs helper features around provider requests. | `visionSidecar?` | `OcxVisionSidecarConfig` | on when usable | Image-description sidecar options. | | `images?` | `OcxImagesConfig` | automatic OpenAI selection | Standalone Images relay options for Codex `image_gen`. | +On Linux x64/arm64 with pidfd support, service health recovery binds termination to the proxy's +kernel process identity, so PID reuse cannot redirect it. The sentinel's watching log ends in +`(pidfd)`. Unsupported kernels or architectures retain the existing live-parent PID check and log +that fallback; other binding failures disable recovery for that proxy. macOS retains the live-parent +check, and Windows retains its process handle binding. +## Native reasoning retention + +The dashboard overview exposes two independent opt-ins: retain reasoning when switching models, and retain it when switching accounts. Both default off. The same policy can be saved in `config.json`: + +```jsonc +{ + "nativeReasoningRetention": { + "modelSwitch": true, + "accountSwitch": false + } +} +``` + +This example retains reasoning `encrypted_content` on a known model switch within the same native ChatGPT destination and credential. Set `accountSwitch` to `true` only to opt into experimental cross-account forwarding. When both model and account change, both switches must be enabled. The policy applies to each actual native Combo target and pool retry; it does not grant retention to third-party or API-key destinations. + +Retention means that OpenCodex may forward the carried ciphertext. It cannot confirm that the upstream accepts or reuses that reasoning. A detected decrypt rejection still enters the existing bounded cleanup recovery, and the rejection memo takes precedence over either switch. Response and conversation references, foreign reasoning item ids, uploaded-file account restrictions and cache isolation continue using their own ownership rules. Native compaction blocks keep their existing cleanup behavior; direct native `/v1/responses/compact` sanitization is unchanged. + +Use `GET /api/native-reasoning-retention` to read the resolved booleans and `PUT` with a partial boolean object to update them. `PUT` with JSON `null` restores both defaults. Invalid fields or unknown keys are rejected; a malformed file block disables retention with a warning. Dashboard/API changes apply to subsequent requests. Local `ocx config set/unset` edits require a restart to converge the running proxy. + +## Upstream transport recovery + While the canonical ChatGPT upstream WebSocket waits for the first Responses event after sending the create frame, it watches for liveness rather than a fixed deadline. When the socket supports protocol pings, the proxy pings it every 15 seconds. Any inbound frame — quota, @@ -672,6 +700,21 @@ Auto auth selects subscription when stored Claude auth is found, proxy when none subscription with a warning when detection is inconclusive. See [Claude Code auth mode](/guides/claude-code/#auth-mode). +## Retained requests after compaction + +For translated providers and routed Responses gateways, OpenCodex automatically frames +plain-text user requests before the latest readable Codex checkpoint as historical context. +Requests the checkpoint reports as completed should not be repeated; retained text remains +available for unfinished work within its existing authorization. Later user input takes priority, +including pauses, cancellations and changes of scope. A checkpoint grants no new permission. + +This is enabled by default and needs no setting. Original request text and canonical thread/replay +history are preserved. Native OpenAI Responses passthrough, summarizer turns, context wrappers, +images/files and assistant/tool items are unchanged. The framing applies to plaintext checkpoints +beginning with Codex's summary prefix; decoded OpenCodex checkpoints retain their existing +continuation guidance. Client-side summarizer turns using Codex's standard compaction prompt +also bypass framing. Framing is a prompt instruction, so model behavior can vary. + ## Compaction routing In **Dashboard → Overview → Compaction routing**, choose a model, which triggers it applies to, @@ -757,6 +800,84 @@ without tool declarations. This changes only the summary request, not stored his compaction; a failed summary does not replace the history. Restart the proxy after editing `config.json` by hand. Dashboard saves apply immediately. +### Reasoning retention during compaction + +Readable reasoning retention is an advanced, explicit opt-in and is **off by default**. +Only `reasoningRetention.enabled: true` enables it; an omitted block, `enabled: false`, +or legacy limits without `enabled: true` leave ordinary compaction behavior unchanged. +When enabled for routed v1 `POST /v1/responses/compact` and routed v2 `compaction_trigger` requests, readable reasoning stays local while the selected +model summarizes other context. After a readable summary succeeds, exact retained text returns +alongside it in framed historical context messages, not as new assistant reasoning or instructions. +Repeated compaction withholds those messages again. Ordinary subsequent turns replay them as history. + +```json +{ + "reasoningRetention": { + "enabled": true, + "maxContextPercent": 20, + "maxTokens": 100000 + } +} +``` + +Both limit fields are optional and, **when enabled**, default at runtime to `20` percent and `100000` estimated tokens. +The effective limit is the smaller of the percentage of the compacting model's context window +and the absolute token cap; when the context window is unknown, only the absolute cap applies. +The estimate includes each retained message's historical-context frame, not just its reasoning text. +Above the limit, readable text is written verbatim under the resolved OpenCodex home's +`reasoning-archive/` (normally `~/.opencodex/reasoning-archive/`). The summarizer receives a +path note instead of the text, and the finished readable summary includes a notice naming that file. +New archives are private to the user on POSIX. An archive is provisional until successful replacement history +returns its path, either in a readable summary or in a separate notice alongside a native result +after routed v1 Combo failover. Failures, cancellation, and invalid summaries remove +the current attempt's file. Internal retries reuse it; previously successful archives are preserved. +Failed compaction returns no replacement history. + +The percentage/token limits bound reasoning returned in context, **not cumulative archive storage**. +Each archive contains only readable reasoning admitted in that request; request admission is bounded +by `maxInboundBodyBytes` (256 MiB by default, configurable up to 512 MiB), plus small block separators. +The archive directory has a separate compiled-in ceiling of **512 MiB and 256 files**, counting +committed archives, provisional remnants, and other regular files in that directory. A bounded +streaming scan stops once refusal is certain; nonregular or uninspectable entries refuse closed. +A sibling SQLite transaction serializes the scan, exclusive creation, write, and close across +cooperating writers. The capacity check runs **before a new archive payload is written**; +acquiring the lock and recording local ownership can still write lock or metadata files. +This ceiling is not a physical free-space guarantee and does not enforce writes by older builds +or external tools. There is **no automatic expiry or background archive cleanup**. + +If the archive is full, routed compaction refuses the new payload with HTTP 503 +`reasoning_archive_quota_exceeded`: review and manually remove older archives before retrying +the unchanged request. HTTP 503 `reasoning_archive_busy` means another writer holds the lock; +wait for it to finish and retry. HTTP 503 `reasoning_archive_unavailable` requires checking +directory permissions and storage health. These responses do not expose filesystem paths or +raw causes and do not invent a Retry-After delay. A refused archive does not truncate reasoning +or delete earlier files, and failed compaction returns no replacement history. + +Successful archives remain until you manually delete their files, including after disabling retention +or resetting the setting. They are plaintext, contain potentially sensitive conversation reasoning, +and share the resolved OpenCodex home of that proxy instance. Deleting an archive makes any existing +path notice unable to recover its contents; save anything you need first. A process crash or failed +filesystem write/cleanup can also leave a provisional file requiring manual cleanup. + +**Dashboard → Overview → Advanced settings → Reasoning retention** provides the explicit switch +and both limits; **Reset and turn off** removes the override and switches retention off. GET/PUT `/api/settings` exposes `reasoningRetention`: null clears the block, +and omission preserves it. `enabled` must be a boolean when present. Percentages must be greater than 0 and at most 100; token caps must +be positive integers, and unknown fields are rejected. Invalid hand edits disable only this +block with a warning, keeping unrelated providers and settings. + +Private provider ciphertext and signatures are not decrypted or copied into portable retained +messages. An upstream native opaque compaction response retains its exact output item and +ciphertext. If routed v1 Combo failover reaches a native result after withholding readable reasoning, +replacement history also includes the retained historical messages or a notice naming their archive. +Native compact endpoints keep their existing behavior. + +Routed v2 still returns exactly one compaction item. Its proxy-owned `ocx2:` envelope stores +the summary and retained historical reasoning separately as JSON/Base64; this is encoding, +not provider encryption. Subsequent requests restore separate historical messages, and +repeated compaction withholds the reasoning again. Old `ocx1:` summaries remain supported. +Opt-in emergency compaction recovery also preserves this separation for JSON and SSE. +Explicit native-history recovery restores separate summary and historical reasoning messages too. + ## Memory routing In **Dashboard → Overview → Memory routing**, choose a model and an optional reasoning effort for diff --git a/docs-site/src/content/docs/reference/management-api.md b/docs-site/src/content/docs/reference/management-api.md index 19c55968fa8..6c810c21dac 100644 --- a/docs-site/src/content/docs/reference/management-api.md +++ b/docs-site/src/content/docs/reference/management-api.md @@ -243,7 +243,7 @@ completes. | Method and path | Purpose | Notable errors | | --- | --- | --- | | `GET /api/combos` | List normalized combos and their public model ids | Catalog work can return `catalog_busy` | -| `PUT /api/combos` | Create, replace, or rename one combo | 400 invalid id, target, config, rename, or ordinary collision; 409 Codex-account namespace collision | +| `PUT /api/combos` | Create, replace, or rename one combo | 400 invalid id, target, config, rename, or ordinary collision (a combo validation 400 also names the first invalid top-level field in `field`, for example `targets` or `decisionProvider`); 409 Codex-account namespace collision | | `DELETE /api/combos?id=...` | Delete one combo and clear its selection/cooldown state | 400 missing id; 404 unknown combo | See [Combos](/guides/combos/) for target strategies, cooldowns, aliases, and routing failures. @@ -270,6 +270,7 @@ See [Codex prompt layers](/guides/codex-prompt/) for the layer model and the key | `GET /api/config` | Return the redacted, management-safe configuration DTO | — | | `PUT /api/config` | Disabled full-config replacement guard | 405; use focused endpoints instead | | `GET, PUT /api/settings` | Read runtime/startup settings or update auto-start, stream mode, app-owned memory budget, and `codexAccountPickerEnabled` | 400 invalid, non-object, or empty update | +| `GET, PUT /api/native-reasoning-retention` | Read resolved model/account reasoning-retention booleans; partial PUT preserves omitted fields, JSON `null` restores defaults. See [native reasoning retention](/reference/configuration/server/#native-reasoning-retention). | 400 unknown field, invalid type or non-object/non-null body | | `GET /api/startup-health` | Read cached service/shim startup health | — | | `POST /api/startup-action` | Install or repair the service or Codex shim | 400 invalid action; 500 action failure | | `GET, POST /api/windows-tray` | Read Windows tray state or install/start/stop/uninstall it | 400 unsupported platform/action; 500 operation failure | @@ -290,10 +291,11 @@ upstream model evidence remains absent rather than being inferred from the reque Failed request rows can include bounded `upstreamErrorType`, `upstreamErrorCode`, and `upstreamRequestId` diagnostics. On the Codex upstream WebSocket path, a bare error followed by -EOF retains recognized rate-limit or overload codes in the synthesized failed terminal. -Request logs record these as 429 for `rate_limit_exceeded` / `rate_limit_error`, or 503 for -`server_is_overloaded` / `overloaded_error`. A recognized type without a code uses the same -status. On the streaming path, an explicit unknown code records the 5xx status the Codex +EOF retains recognized rate-limit, overload, or client-refusal codes in the synthesized failed terminal. +Request logs record these as 429 for `rate_limit_exceeded` / `rate_limit_error`, 503 for +`server_is_overloaded` / `overloaded_error`, and 400/401/403/404 for `invalid_request_error` / +`authentication_error` / `permission_error` / `not_found_error`. A recognized type without a code +uses the same status. On the streaming path, an explicit unknown code records the 5xx status the Codex WebSocket wrapper states as a top-level integer `status` or `status_code` (one of the two, not both); otherwise, and for buffered JSON callers, it remains a generic 502 failure. The client terminal keeps `upstream_server_error` for unknown codes either way. Bare-error status is provisional: @@ -471,12 +473,29 @@ Explicit all-zero user rates mean a known-zero estimate; **Reset to automatic** override and restores the usual catalog fallback. These remain display estimates, not bills. `GET /api/providers/{provider}/model-costs` returns `{ provider, modelCosts }`, with sanitized -four-rate entries keyed by exact upstream model ID. `PUT` on the same route accepts -`{ modelId, cost }`, where `cost` is `{ input, output, cacheRead, cacheWrite }` or `null` to reset. +four-rate entries and their optional `promptLengthPricing` policy, keyed by exact upstream model ID. +`PUT` on the same route accepts `{ modelId, cost }`, where `cost` contains +`{ input, output, cacheRead, cacheWrite }` and optionally `promptLengthPricing`, or is `null` to reset. All four rates must be finite numbers from 0 through 1,000,000, in USD per 1M tokens. Unknown fields and malformed rates are rejected. A write preserves other models' overrides and returns `{ ok: true, provider, modelId, cost }`; reset returns `cost: null`. +The **Model price** dialog offers **Automatic**, **Flat rate**, and **Custom threshold** +prompt-length pricing. Automatic (also the default when the policy is absent) retains catalog +context rules with the saved base rates. Flat disables context adjustments. Custom selects four +absolute alternative rates using one positive safe-integer `thresholdTokens` and `comparison` +(`"gt"` for `>`, `"gte"` for `≥`). All prompt tokens count, including cache reads and writes; +output does not. The selected rates apply to the whole request, including output, not just the +excess tokens. Custom replaces the catalog context rule both below and above its threshold. +Custom rates are standard-speed rates: an applicable Fast/Priority adjustment applies once after +rate selection and follows the catalog's Priority relation for that model. Only a model whose +catalog relation is `lower-bound` reports `priorityLowerBound: true`; a model with no catalog rule +applies any Priority multiplier once. +`{ "mode": "automatic" }` is accepted on write and stored as an absent policy, so GET and PUT +receipts never contain it. +**Reset to automatic** removes the entire override, including base rates and the policy. +See the [configuration example](../configuration/providers/#prompt-length-pricing). + ```bash ocx models price ollama/custom-model --json ocx models set-price ollama/custom-model --input 0.50 --output 1.50 @@ -588,9 +607,9 @@ Labels are read on login/import or refresh, never inferred from quota percentage | --- | --- | --- | | `GET /api/oauth/providers` | List providers with public OAuth login flows | — | | `GET /api/key-providers` | List providers configured through API-key login | — | -| `POST /api/oauth/login` | Start an OAuth login or account-add flow | 400 unknown/invalid provider; `oauth_mutation_busy` | -| `POST /api/oauth/login/code` | Submit a manual callback URL or authorization code | 400 invalid provider/code; `oauth_mutation_busy` | -| `POST /api/oauth/login/cancel` | Cancel a public in-progress OAuth flow | 400 unknown provider | +| `POST /api/oauth/login` | Start an OAuth login or account-add flow. `mode: "code"` starts a code-display login (Anthropic only) and returns `mode`, `flowId`, `expiresAt` | 400 unknown/invalid provider, invalid `mode`, `code_mode_unsupported`; `oauth_mutation_busy` | +| `POST /api/oauth/login/code` | Submit a manual callback URL or authorization code. For a code-display flow (with `flowId`) returns the token-exchange outcome `{ok, account}` / `{ok:false, error}` | 400 invalid provider/code, `state_mismatch`, `invalid_or_expired_code`, `provider_unreachable`, `malformed_input`; 409 `no_pending_login`; `oauth_mutation_busy` | +| `POST /api/oauth/login/cancel` | Cancel a public in-progress OAuth flow or dismiss its settled failure; an optional `flowId` names one code-display flow (a flow of another provider is refused) | 400 unknown provider | | `GET /api/oauth/status` | Poll one provider's OAuth flow | 400 unknown provider | | `POST /api/oauth/logout` | Remove the selected provider credential | 400 unknown provider; `oauth_mutation_busy` | | `GET /api/oauth/accounts` | List masked accounts; Anthropic and generic OAuth account rows include their `paused` state. Kiro rows include `autoSelectable` and a closed `skipReason` when excluded from automatic selection; an active singleton may still send. Quota remains opt-in. | 400 invalid provider | @@ -606,6 +625,9 @@ Labels are read on login/import or refresh, never inferred from quota percentage | `PUT /api/providers/keys/alias` | Set or clear a provider-key alias | 400 invalid input; 404 provider/key missing | | `GET, POST, PATCH, DELETE /api/keys` | List, create, edit, or delete data-plane admission keys | 400 invalid body/id; 404 key missing | +`POST /api/oauth/login/cancel` can also dismiss the selected settled failed login attempt. It leaves stored credentials and successful login results intact; status then reports the remaining flow or idle. Cancelling an in-progress attempt still records its cancelled outcome. + + For Anthropic, `routes` is an ordered array of `{name, match, accounts, fallback?}` rules on both settings endpoints. GET and write echoes include `routes` (`null` when absent); unified DTOs expose `routes: null` for other kinds. A supplied `routes` on another kind is rejected. Omission preserves rules, `[]` matches nothing, and `null` clears them. The `accounts` values are stored IDs; removed IDs remain valid in a rule so re-adding an account can restore routing. Management responses retain valid configured names; request logs identify a match only as `route:#` (1-based list position). If a hand-edited stored rule is invalid, both settings GETs return `routes: null` and a `routesError` diagnostic instead of presenting that rule as valid; the stored value remains available for correction. Valid or absent rules omit `routesError`. Credential list responses are deliberately masked. OAuth access tokens and complete provider API @@ -636,6 +658,23 @@ API page. The CLI drives these routes with `ocx api protocols`, `ocx api explain [API surfaces](/reference/configuration/server/#api-surfaces-apisurfaces) for how the Messages setting interacts with `claudeCode.enabled` across upgrades and downgrades. +### Home identity and the served-by header + +`GET /healthz` includes `instanceId`, this install's opaque Home id: `home_` followed by 32 hex +characters, generated at random once per install. It is the same value as `producer.homeId` in +`GET /api/quota-pool`, it stays the same across restarts, and it is never a hostname or derived from +one. Every response to `GET /v1/models` carries the same value in the `x-opencodex-served-by` +header, so a client can tell which Home answered without reading logs. A Remote Link Child relays the +Home's header unchanged, and a Hub-mode client's Codex reads the Hub's response directly, so on any +device the header names the Home that served the model list. `GET /readyz` lists +`served-by-header` in `features` when the build sends it. + +Right after a fresh install starts, before its identity file exists, or when that file is +unreadable, both the `instanceId` field and the header are left out; an empty value is never sent. +Deleting `quota-pool-identity.json` from the OpenCodex home rotates the id together with every +quota-pool account reference when the proxy next starts; a running proxy keeps reporting the id it +already read. + ### Providers | Method and path | Purpose | Notable errors | @@ -646,10 +685,29 @@ setting interacts with `claudeCode.enabled` across upgrades and downgrades. | `DELETE /api/providers?name=...` | Delete a provider, reassigning the default when possible | 404 unknown provider; 409 `last_provider`; 409 `provider_has_dependent_combos` | | `POST /api/providers/test?name=...` | Perform a bounded live provider connectivity/model-discovery probe | 404 unknown provider; failures are normally returned as `ok: false` evidence | | `GET /api/provider-quotas` | Read provider quota reports; `refresh=1` forces refresh | — | +| `GET /api/quota-pool` | Read the passive `ocx.quota-pool/1` account quota pool (per-account and pooled windows, opaque account references) from cached evidence only; never probes or refreshes, and ignores `refresh`. Also readable from a Remote Link Child or Hub client at `/api/machine/hub-relay/api/quota-pool` | — | | `GET /api/quota-resets` | List recently detected quota-window resets and whether detection is enabled; `limit=` caps the count | 400 invalid `limit` | +| `GET /api/fallback` | Read the `ocx.fallback/1` fallback state: effective global switches, provider and route overrides, devices with local fallback permission, and the last 20 fallbacks taken or refused. Also readable from a Remote Link Child at `/api/machine/hub-relay/api/fallback` (link aliases omitted) | — | +| `PUT /api/fallback` | Set or clear switches: one write `{ scope: "global"\|"provider"\|"route"\|"device", key?, setting: "model"\|"provider"\|"localFallback", value: true\|false\|null }`, or `{ writes: [ … ] }` with up to 8 applied in one config mutation; `localFallback` is per device; a device key may be a link alias. Returns the new state | 400 invalid body; 404 `unknown_device` (nothing written); 409 `config_unavailable` | | `GET, PUT /api/provider-context-caps` | Read or update global, all-provider, or one-provider context caps | 400 invalid request; 404 unknown provider | | `GET /api/provider-presets` | Return GUI provider presets derived from the runtime registry | — | +Provider reads include the optional boolean `inferContextWindow` when configured. Provider PATCH +accepts only a boolean or `null` to clear this setting; omission preserves it. A same-name POST +also preserves an omitted setting, including the latest stored `false`; an explicit submitted +boolean wins. See [Context windows on custom providers](/reference/configuration/providers/#context-windows-on-custom-providers) +for its default-off, custom-provider-only behavior. + +When Antigravity's quota-summary endpoint has no usable response, quota reports may also include +per-model Cloud Code Assist usage windows when the model-discovery response provides them. These +measurements are diagnostic; an absent model window does not establish that the model is unavailable. + +With `quota=1`, an Anthropic or Antigravity account row whose probe returned no quota carries +`quotaUnavailable: true` and, when the cause is known, a closed `quotaFailure` code: +`account_unavailable` (sign the account in again), `access_denied`, `rate_limited`, `timeout`, +`transport_error`, `upstream_error` or `response_unusable` (retry later), among others. The code +never includes upstream text, and a quota failure never changes the row's `health`. + The provider context-cap response includes `caps` (active limits) and `values` (last selected values, retained while disabled). Enabling a provider without `value` restores its selection, or uses the global `contextCapValue` on first enable. This also applies to OpenAI: the switch @@ -715,7 +773,7 @@ the 503 without reporting model capacity; provider overload errors retain their | `POST /api/system/restart` | Begin a drain-aware process restart without removing client injection; optional JSON `drainGraceMs` explicitly shortens the default 60s grace | Returns 202; repeated calls report the existing grace; 400 invalid body or grace | | `POST /api/stop` | Stop the service, restore native Codex, remove managed Grok injection, and drain the proxy | 409 service ownership conflict; 409 `respawnable_service` when a Windows Task Scheduler wrapper could respawn the proxy and the caller is not `ocx stop` (nothing is changed); 409 `self_unload_service` when this proxy is running as the installed launchd/systemd service, because stopping the manager from inside it would end the process before native Codex is restored — run `ocx stop` instead (nothing is changed); 409 when the installed manager refuses to stop; 409 `service_state_unknown` when the Task Scheduler state cannot be read (nothing is changed; repair the query and retry) | | `GET /api/system/codex-app-server` | Report whether running Codex app-servers predate the current model catalog | — | -| `POST /api/system/codex-restart` | Refresh the catalog, then restart stale Codex app-servers and fully quit and relaunch the Codex desktop app so the model picker reloads. When the proxy itself is running inside the Codex app, the desktop restart is refused rather than handed off. | Returns 200 with `code: partially_stopped` when a target survives | +| `POST /api/system/codex-restart` | Refresh the catalog, then restart Codex app-servers. A running Codex desktop app is given a restart attempt so the model picker can reload; the `desktopApp` response fields report a refusal, surviving targets, or a failed relaunch. A shell newer than the catalog is still reported as `desktopApp.pickerState: "unknown"`, because its start time cannot prove which `model_catalog_json` binding it loaded; an earlier sync may have rebound an already-existing catalog. A shell no newer than the catalog, or one whose binding this request changed, is `stale`. A closed desktop returns `desktopApp.reason: "no_targets"`; its `pickerState` may be `not_running`, or `stale` when this request changed the catalog binding. When the proxy itself is running inside the Codex app, the desktop restart is refused rather than handed off. | Returns 200 with `code: partially_stopped` when a target survives | ### Codex authentication delegation @@ -742,10 +800,12 @@ manager. Its routes are: | `POST /api/codex-auth/accounts/clear-cooldown` | Clear runtime cooldown for one account or all accounts | 400 invalid id | | `GET, PUT /api/codex-auth/active` | Read or select the active account | 400 invalid or missing account; 409 paused/legacy-row conflict or `account_selection_unavailable` | | `PUT /api/codex-auth/auto-switch` | Set the global quota threshold with `{ threshold }`, or an account override with `{ id, threshold }`; `null` restores global inheritance, and `__main__` selects the Desktop login | 400 invalid id/threshold; 404 missing account | +| `GET /api/codex-auth/preserve-signed-in` | Read policy, current `affectedAccountIds`, `identityKnown`, `nextAccountId`, `nextUsesSignedInQuota` (actual next choice, even when disabled), `pinnedAccountId`, `bindings`, `priorityFailbackEnabled`, and `fallbackConsumesSignedInQuota` | 503 native profile busy/unreadable | +| `PUT /api/codex-auth/preserve-signed-in` | Strict `{ enabled: boolean, applyToBound?: boolean }`; explicit `applyToBound: true` requires enabled, releases affected bindings/pin for next request; reports `releasedPin` | 400 invalid body; 503 native profile busy/unreadable | | `PUT, PATCH /api/codex-auth/pool-strategy` | Update Codex account-pool selection strategy | 400 invalid strategy/config | | `PUT /api/codex-auth/failover` | Set the account failover threshold | 400 invalid threshold | | `GET /api/codex-auth/quota` | Read cached quota state by account | — | -| `GET /api/codex-auth/low-quota-events?limit=20` | Read only this server’s last 0–100 low-quota log/notice and pause-save events (default 20); includes account id and status (`logged` for the default log-only alert; `delivered` for a successful injected notice sink; `succeeded` for a completed pause save) | 400 invalid limit; management authentication required | +| `GET /api/codex-auth/low-quota-events?limit=20` | Read only this server’s last 0–100 low-quota log/notice and pause-save events (default 20); includes account id and status (`logged` for the default log-only alert or a host without a supported notifier; `delivered` when the opted-in OS notifier accepted the notice; `failed` when it failed or timed out; `cancelled` when its episode ended first; `succeeded` for a completed pause save) | 400 invalid limit; management authentication required | | `GET /api/codex-auth/reset-credits` | Inspect reset-credit eligibility for an account | 400 missing account id; upstream status passthrough; 500 lookup failure | | `POST /api/codex-auth/reset-credits/consume` | Consume an eligible reset credit. Optional `operationId` (UUIDv4) makes the redemption idempotent: the same id replays one durable outcome instead of spending a second credit. | 400 missing account id or invalid `operationId`; 409 `identity_mismatch` when the id belongs to another account; upstream status passthrough; 503 `server_busy`, `capacity`, or `unavailable`; 500 consume failure | | `POST /api/codex-auth/login` | Start Codex login or reauthentication | 400 invalid request; conflict/busy login states | @@ -854,6 +914,17 @@ ports or overwrite the existing connection record. Restart returns to Observe, n process's bound OpenCodex listener. Runtime status may expose `contextFailure` for this condition or `build_unverified`. These states disable response correction without claiming an app repair. +A status response can also carry `buildAssessment`, the public metadata of the last completed +build check: `assessedVersion` (the exact package this runtime was assessed against), +`observedVersion`, `verified` and `completedAt`. It records a past observation, not a live +install reading: a stopped runtime, an app update or a pending or interrupted check can leave it +older than the installed package, and only a completed check replaces it. `observedVersion` is +null unless discovery matched the assessed package family and entry point; an identified newer +build keeps its own version with `verified` false, and `verified` always describes the package +observed at `completedAt`, never whatever is installed later. The field is absent until the +service creates its probe during start and `null` until that probe finishes a check; reading +status never starts one. + Apply requires a recently observed eligible exhaustion snapshot and lasts at most three minutes at the response layer. It changes two account-UI gate flags, not usage percentages, credits, spending restrictions or server limits. The usage response does not identify the selected model: diff --git a/docs-site/src/content/docs/reference/platform-support.md b/docs-site/src/content/docs/reference/platform-support.md index 5309bf7badc..a564c8d0826 100644 --- a/docs-site/src/content/docs/reference/platform-support.md +++ b/docs-site/src/content/docs/reference/platform-support.md @@ -59,6 +59,25 @@ offers an input surface. Pasted keys use the same format and Model API validatio as imported ones. Management login requires a dashboard session before either credential-acquisition path; see the [provider guide](/guides/providers/). +The macOS Keychain prompt appears on the machine running the proxy, which may be +separate from your dashboard browser. OpenCodex allows up to 120 seconds to approve +it. Cancel the dashboard login to stop the pending read. Missing entries, denied +access, unavailable Keychain interaction, timeout, malformed credentials, and +Model API validation failures have separate recovery messages. If import is +blocked, the dashboard offers a paste field for an existing Muse Code API key; it +does not automatically start another device grant. To use browser sign-in instead, +choose **Add account** on an already configured provider. OpenCodex does not change +Keychain access controls. + +If login still fails, report the exact displayed Muse error (including any HTTP +status), OpenCodex version, `muse --version`, whether the dashboard is remote from +the proxy, and how long the prompt was visible. In browser developer tools → +Network, copy only the `error` field from `/api/oauth/login` or +`/api/oauth/status?provider=meta-muse`. Do not share a HAR, request headers, +auth files, Keychain output, account details, or the rest of the status response. +These safe diagnostics distinguish local import from Meta validation failures; +no credential dump is needed. + For Responses requests to Meta, omitted or `auto` tool selection is supported. Explicit `none` sends `tools: []` and removes `additional_tools` items from the input. Forced, named, and `allowed_tools` selections return HTTP 400 before the @@ -67,6 +86,10 @@ destinations keep their existing tool-selection behavior. ## Windows notes +The Node launcher's npm self-update requires Windows 10 or later with Job List +process-creation attributes. If atomic process supervision is unavailable, it +reports `EINSTALLJOIN` and does not start npm; update Windows before retrying. + The Windows service can run under Task Scheduler or as a native WinSW service, and those are mutually exclusive. `ocx service repair` refuses to proceed when it finds state for both, because guessing which one you meant is how a machine diff --git a/docs-site/src/content/docs/reference/proxy-formats.md b/docs-site/src/content/docs/reference/proxy-formats.md index 0168480b8e6..61eb7839155 100644 --- a/docs-site/src/content/docs/reference/proxy-formats.md +++ b/docs-site/src/content/docs/reference/proxy-formats.md @@ -20,6 +20,42 @@ response safety still happen at the proxy boundary. Configure the listener and a [Configuration](/reference/configuration/); use [Combos](/guides/combos/) when one public model id should select among several targets. +## Required tools on buffered native combo routes + +A combo checks buffered native `openai-responses` JSON results when `tool_choice` is `required` +or selects a named function/custom tool. A completed result without the required call becomes +HTTP 502 with `error.code: "required_tool_contract_unsatisfied"`. The combo may try another eligible +target before delivering the response, subject to its existing fallback policy and send budget. +If no eligible target succeeds, the client receives the typed error instead of a false completion. +A hosted call for a declared capability can satisfy `required`; a mismatched hosted call returns +HTTP 400 with the same error code and `x-should-retry: false`, since the hosted action may already +have executed. No fallback occurs in that case. Usage remains recorded. +Auto/none choices, incomplete results and unknown tool contracts keep their existing behavior. +This guard covers buffered native combo children. Streaming required-tool enforcement and direct +native routes are separate scopes; visible output is never replayed. Translated Chat routes have +their own guard, described next. + +## Required tools on translated Chat routes + +A translated OpenAI Chat response must contain a complete selected client tool call when +`tool_choice` is required, names a tool, or uses a required allowed-tool selection. If the upstream +finishes normally without one, the turn fails with `required_tool_not_called` and preserves usage. +Already streamed text stays visible; the proxy does not replay the request or switch targets after +that output. A streamed Responses reply ends with `response.failed`; an admitted buffered Responses +envelope has `status: "failed"` even when its HTTP status is 200. Check the envelope status. +Auto/none choices and truncated outcomes keep their existing behavior. This guard covers translated +Chat client-tool catalogs; catalogs containing proxy-executed sidecars, native passthrough and other +adapters retain their existing completion rules. + +## MiMo bare tool-call recovery + +On translated OpenAI Chat routes, `mimo-v2` and dotted `mimo-v2.*` model IDs can recover a +complete bare `` block as a call to an exact declared freeform tool when the response +contains no structured tool calls. Prose around the block is preserved; fenced and inline-code +examples, nested headers, undeclared tools and identified wrappers remain literal. Recovery is +bounded to 128 calls, 4 MiB total and 2 MiB per candidate. Long streamed prose may release the +original markup before terminal recovery; errors never dispatch recovered calls. + ## Upstream redirects Credential-bearing model, image, video, and search requests do not automatically follow HTTP redirects, including same-origin redirects. Configure the final upstream API URL instead of a redirecting alias. A redirect does not cause the server to resend credentials or the request body to its destination. The response owner retains its existing error or relay behavior; native Responses and compact routes can return the original 3xx and `Location` to the client. Client redirect behavior is separate from this server transport policy. @@ -32,6 +68,16 @@ the same serialized request and records the recovery in Logs. Other 400 errors, custom destinations, cancellations and repeated upload rejections remain failures. This does not retry filtered model responses or interrupted streams. +## Codex-native ordinary Lite hosted-search history + +When the final outbound request declares no hosted search tool (no `web_search` or `web_search_preview` entry in `tools` or in a Lite `additional_tools` wrapper, including after the provider's unsupported hosted tools were removed), every Responses destination receives unreferenced replayed `web_search_call` history as the same labeled assistant reference notes described below, with or without the opt-in. Native Sol answered such history with a generic `server_error` (#7250), so a conversation that searched on another model can continue on a route whose current request has no search tool. A function tool named `web_search` is not a hosted declaration. No search tool is added, no search is rerun, and no result is invented; same-input `item_reference` cells stay structural. A request that does declare hosted search keeps structural history unless the opt-in below applies. + +Ordinary requests that declare hosted search retain structural hosted-search history by default, including Responses Lite requests. To opt in to lossy reference notes, set `providers.openai.projectResponsesLiteHostedSearchHistory: true` in `config.json` (an actual boolean, not the string `"true"`). The per-provider setting defaults to off; omission or `false` keeps structural history. Even when enabled, projection applies to ordinary turns and native v2 `compaction_trigger` requests whose final destination is exact canonical ChatGPT Codex `/responses` and whose effective request intent explicitly selects Responses Lite. It converts unreferenced remaining `web_search_call` history into labeled assistant reference notes. The final `x-openai-internal-codex-responses-lite` header selects Lite only when its value is exactly `true`; an explicit `false` overrides frame metadata. Without a recognized header value, a valid string-map `client_metadata.ws_request_header_x_openai_internal_codex_responses_lite` value of `true` is used, matching the existing WS metadata handling. Non-Lite or unrecognized effective intent retains structural history; malformed metadata does not override an explicit HTTP `true`. Model names, subscription authentication, missing hosted declarations and `additional_tools` alone do not select this policy; no header or metadata is rewritten to make it apply. Once enabled, it applies to new threads/forks, cross-model continuations and cache misses/restarts, without diagnostic settings. Only a valid same-scope bridge memo proves an original function call/result that can be restored first; an ID prefix, model name or missing memo cannot determine the issuer. A hosted cell exactly referenced by a same-input `item_reference.id` is retained. `store:false` removes references earlier, and its remaining hosted history can be projected. Tool declarations/Lite `additional_tools`, choices, client call/result pairs, reasoning/native ciphertext, non-hosted order and caller raw history retain their existing contracts. + +Within that opted-in Lite boundary, this is an explicit **lossy history policy**, not a claim that hosted calls are invalid schema or that all upstream errors are fixed. Notes carry untrusted status/action metadata and allowlisted URL/title/type from both action and top-level sources, never instructions or fetched page bodies: 2,048 UTF-16 units per string, 20 entries per list, 65,536 serialized bytes for all projected notes, then an omission note. IDs and unknown fields are dropped from converted cells. HTTP/SSE and WS share the final ordinary builder. + +Apart from the missing-declaration rule above, public OpenAI API and other destinations do not gain ordinary or native-compact projection. Native `/responses/compact` remains its existing endpoint/account-scrub/official opaque-output path and retains structural hosted history. Portable/routed summaries reuse their existing projection trigger and default reference behavior; the shared renderer additionally retains allowlisted top-level sources there. No new retry, source classification or compaction summarizer is introduced. + ## Empty search answers After hosted search, a clean but empty forced-answer pass receives one additional answer @@ -71,6 +117,10 @@ returns 504, and client cancellation returns 499. Bun does not expose the exact cannot currently be forwarded precisely. A successful connection preserves the initial session frames in order. This handshake policy is separate from the Responses WebSocket transport. +When a sideband connection ends, the proxy allows one second for a graceful upstream +close before terminating an unresponsive transport. It releases the account's active-call +capacity only after that transport has closed, including when the client upgrade fails. + ## Undeclared tool calls On the Responses endpoint, when a request declares a tool catalog, a native passthrough reply @@ -85,7 +135,11 @@ declare a partial catalog and discover the rest at runtime through their own too which — not the translated catalog snapshot — owns the full declaration boundary. A call outside the declared list is relayed for the client to validate; a call invented against an explicitly empty `tools: []` is still refused, because no deferred registry exists under an -empty declaration. Those refusals keep the `undeclared_tool_call` code on every wire: +empty declaration. This applies to managed native routes and translated responses, including +client tool calls in final hosted-search, image or video responses. Only the current turn's declaration applies; +omitted, partial, and hosted-only catalogs keep their existing behavior. Caller-forward routes +retain their exemption, and internal hosted operations are unaffected. Those refusals keep the +`undeclared_tool_call` code on every wire: - **Chat Completions:** a `data:` frame containing an OpenAI-style `error` object carrying the code, then the stream closes without a success completion or `[DONE]`; buffered JSON returns @@ -96,6 +150,10 @@ empty declaration. Those refusals keep the `undeclared_tool_call` code on every with the code. A deterministic catalog violation is never reported as a retryable `overloaded_error` (HTTP 529). +Native Messages also recognizes inline system-message tool definitions alongside `tools: []`. +Those names remain callable until permanently removed; an inline declaration does not authorize +unrelated client tools. Hosted MCP calls and server-tool argument streams retain their native shape. + Separately, on every inbound wire, a provider's `default.`-namespaced call is normalized back to the bare tool name the request declared (`default.Read` → `Read`). @@ -294,7 +352,26 @@ returns an error instead of partial JSON with HTTP 200. Streaming callers are un When a provider filters or truncates a response, an unfinished tool call remains `incomplete` in both JSON and SSE. Partial output is preserved, and the bridge does not emit an argument completion event for that open call. Calls already completed keep their status. This preserves -the provider outcome; client retry behavior for incomplete responses is unchanged. +the provider outcome. + +On the Responses streaming wire, an explicit non-retryable content-filter stop from +Anthropic (`refusal` / `content_filter`) or Kiro (`CONTENT_FILTERED` / +`GUARDRAIL_INTERVENED`) ends with `response.failed`, `error.code: "invalid_prompt"`, +and `retryable: false`. This prevents clients that retry incomplete streams from +resending the refused context. Usage and partial output are preserved. Web-search +and image sidecars stop without executing calls from that refused iteration or +asking the model again, on every inbound wire. + +Buffered Responses JSON retains `status: "incomplete"`; Chat Completions retains +`finish_reason: "content_filter"`, and Messages retains `stop_reason: "refusal"`. +A content filter without an explicit non-retryable verdict, token limits, stalls, +and EOFs keep their existing incomplete behavior. + +Explicit incomplete reasons use the same vocabulary in adapter-backed responses and native +Responses JSON, SSE, and WebSocket delivery: +output-limit aliases such as `length` and `max_tokens` become `max_output_tokens`, and refusal/filter +aliases such as `refusal` become `content_filter`. Other reasons remain unchanged, along with the +provider's message, retryability, and usage. Direct Chat and Messages encoders apply the same mapping. On the pending `dev` implementation for #4112, a final upstream HTTP 413 on this surface is classified as `invalid_request_error` / `context_length_exceeded`. Non-streaming callers @@ -402,6 +479,13 @@ it is on the wire. Responses rejected at authentication or origin admission never reach this wrapper and carry no id. +Logged `POST /v1/chat/completions` replies also carry `x-opencodex-request-id`, exposed through +CORS, for JSON, streaming, handler errors and logged pre-handler refusals. This is the gateway +history id, independent of the response body's completion id. Caller and upstream values cannot +replace it. Rejections without a request-log owner, including authentication, origin, drain and +active-turn refusals, carry no id. Streaming headers identify the eventual history row before +final usage is saved. + Logged `POST /v1/messages` replies carry the same OCX request-history id in **both** `request-id` and `x-opencodex-request-id`. This applies to native and translated replies, streaming and JSON, and logged refusals. Claude Code can use its persisted `requestId` @@ -493,6 +577,18 @@ effort default to `reasoning.summary: "auto"` so thinking streams back as `reasoning.summary: "none"`. An explicit `reasoning.summary` of `auto`, `concise`, `detailed`, or `none` wins over `include_reasoning`. +When the model is Claude, the response also carries `reasoning_details` in the same shape +OpenRouter uses: `{"type": "reasoning.text", "text", "signature", "format": "anthropic-claude-v1"}` +for each signed thinking block and `{"type": "reasoning.encrypted", "data", ...}` for each redacted +one, on `message` or as `delta.reasoning_details` (each entry arrives complete in one chunk). Send +the array back unchanged on the assistant message, next to its `tool_calls`, and Claude keeps its +reasoning across tool steps. The signature is opaque and is the same value `/v1/messages` +clients receive. OpenCodex replays it only when the model routes to Anthropic's own API +(`api.anthropic.com`) as a single route; on any other destination, or when an entry is +malformed (an unknown type or format, or a missing signature), the turn falls back to the plain +`reasoning_content` text. An entry whose text or signature was edited is forwarded as is, and +Anthropic rejects it, so return the array exactly as received. + Structured output is part of that translation: `response_format` with `json_object` or `json_schema` is forwarded to routed `openai-chat` models. On `POST /v1/responses` the equivalent request field is `text.format`: native Responses routes preserve it in the raw @@ -688,7 +784,8 @@ upstream (`404`). Both legs carry Codex's `session-id` and `thread-id` headers; account choice is bound to that pair (process-local), so a join that reaches the proxy reuses the account that created the call, while Direct mode forwards the caller's current bearer on both legs. The relayed client headers are exactly `openai-alpha`, `x-session-id`, `session-id`, `thread-id`, -`originator`, `x-oai-attestation`, and `x-codex-turn-metadata` +`originator`, `x-oai-attestation`, `x-codex-turn-metadata`, and `voice-session-id` (the client voice +session id, separate from the realtime `x-session-id`) (`LIVE_CLIENT_PROTOCOL_HEADERS` in `src/server/live.ts`); each is relayed only when the caller sent it, and none is invented. `Authorization` and the ChatGPT account id are proxy-owned on ChatGPT-backed routes (Pool replaces @@ -705,8 +802,14 @@ conversation. | Route type | Behavior | | --- | --- | | Canonical ChatGPT or official OpenAI route | Tries the native `/responses/compact` endpoint with the resolved account and model authentication; HTTP 404 falls back to a regular Responses compaction turn | +| Explicitly opted-in custom `openai-responses` gateway | `supportsNativeCompactEndpoint: true` tries the native v1 endpoint for key/local/forward auth using the provider's normal Responses transport; HTTP 404 uses portable fallback; OAuth retains routed compaction and credential resolution | | Other routed model | Runs an internal, non-streaming, no-tools compaction turn with a `compaction_trigger`; requires exactly one synthetic `compaction` item whose `encrypted_content` is an `ocx1:` envelope; decodes that summary into v1 replacement history | +The custom v1 endpoint capability is independent of v2 `supportsNativeCompactionTrigger` +and of `decodesNativeCompactionBlobs` for replay. None grants canonical OpenAI identity or +caller credential forwarding. Cross-provider overrides remain portable; v2 success alone +does not establish support for `/responses/compact`. See [provider configuration](/reference/configuration/providers/). + If the native compact endpoint returns HTTP 404, OpenCodex retries compaction through a regular Responses turn with the same model selector and session headers. Canonical ChatGPT fallback turns use upstream SSE; the compact caller still receives JSON. A completed native opaque @@ -851,3 +954,10 @@ An ordinary 400 and a 429 are never retried this way, and neither is a 5xx, with exception: a 502 whose body is the exact encrypted tool-output decrypt rejection, sent for a request that carries encrypted tool output, gets the same single resend. A second rejection reaches the client unchanged. If that happens, start a new conversation on the destination provider. + +### Malformed tool-call history + +Routed requests tolerate malformed serialized function arguments as an empty object. +Two bounded history repairs preserve usable arguments: a missing JSON object prefix, +and MiMo's trailing `` envelope with a missing closing quote or brace. +A repair must decode to a JSON object. Valid JSON values containing those tags are preserved. diff --git a/docs-site/src/content/docs/ru/contributing.md b/docs-site/src/content/docs/ru/contributing.md index 7d4c7622ecd..84da5ac93e5 100644 --- a/docs-site/src/content/docs/ru/contributing.md +++ b/docs-site/src/content/docs/ru/contributing.md @@ -179,6 +179,11 @@ Pull request'ы с ребейзом приветствуются: ребейз заявления на основе fixture укажите точные assertion id, которые его доказывают. Добавьте request vector без секретов в `tests/fixtures/compatibility/` и сфокусированный тест, выполняющий его через production adapter. +Claim `passthrough` может ссылаться только на assertion `equals` по selector, привязанному к этой +feature в manifest test, чей ожидаемый результат совпадает с собственным значением вызывающей стороны в +том же месте. Поэтому удалённое, переписанное или не относящееся к делу поле нельзя пометить как +lossless. Аспекты, которые маршрут может сохранить по отдельности, например custom tool и его grammar, +оформляйте отдельными claim. Манифесты совместимости являются пассивными данными. Обычные router, Responses handler и server startup path не должны импортировать каталог манифестов или активировать Compatibility Lab. diff --git a/docs-site/src/content/docs/ru/getting-started/quickstart.md b/docs-site/src/content/docs/ru/getting-started/quickstart.md index 67ff8415a92..99d6cdb2a1b 100644 --- a/docs-site/src/content/docs/ru/getting-started/quickstart.md +++ b/docs-site/src/content/docs/ru/getting-started/quickstart.md @@ -18,7 +18,7 @@ ocx init `ocx init` проведёт вас по следующим шагам: -1. **Выбор провайдера** — выберите один из 104 встроенных пресетов реестра или `custom`, чтобы +1. **Выбор провайдера** — выберите один из 106 встроенных пресетов реестра или `custom`, чтобы ввести базовый URL и адаптер вручную. 2. **API-ключ** — вставьте ключ или сошлитесь на переменную окружения вида `${ANTHROPIC_API_KEY}`. 3. **Модель по умолчанию** — для провайдеров с ключом, локальных и `custom` примите значение из diff --git a/docs-site/src/content/docs/ru/guides/claude-code.md b/docs-site/src/content/docs/ru/guides/claude-code.md index 13c5632fb0f..84bf72f2142 100644 --- a/docs-site/src/content/docs/ru/guides/claude-code.md +++ b/docs-site/src/content/docs/ru/guides/claude-code.md @@ -180,22 +180,22 @@ unknown означает, что opencodex не может определить, ### Режим picker: модели opencodex в селекторе Code first-party -Режим picker входит в режим first-party. В macOS он включён по умолчанию при выборе first-party, +Режим picker входит в режим first-party. В macOS и Windows он включён по умолчанию при выборе first-party, если не задано `claudeCode.intercept.picker: false`. Он изменяет селектор моделей во вкладке Code first-party Desktop и показывает доступные модели opencodex по именам. При первом включении macOS может -попросить доверить локальному центру сертификации в связке ключей для входа. Этот центр ограничен +попросить доверить локальному центру сертификации в связке ключей для входа, а Windows — в хранилище корней текущего пользователя. Этот центр ограничен `claude.ai` и его поддоменами. Экспортируемая идентичность подписи защищена хранилищем учётных данных ОС и повторно используется при обычных перезапусках. Ключ подписи picker не хранится открытым текстом в -каталоге конфигурации OpenCodex. Полная проверка ограниченного CA и доверия ОС сохраняется. Если одобренная +каталоге конфигурации OpenCodex. Полная проверка ограниченного CA и доверия ОС сохраняется, в Windows — по точному отпечатку с проверенным листом. Если одобренная идентичность не изменилась и хранилище доступно, перезапуск не добавляет и не удаляет настройки доверия -сертификатам. Восстановление при запуске никогда не устанавливает доверие: если оно отсутствует, отозвано +сертификатам macOS. Восстановление при запуске и проверка состояния никогда не устанавливают доверие: если оно отсутствует, отозвано или неизвестно, picker остаётся в ожидании. Для предоставления доверия явно выполните -`ocx claude desktop picker on` или `ocx claude desktop picker trust`. +`ocx claude desktop picker on` или `ocx claude desktop picker trust`, включая подтверждение Windows при необходимости. При однократной миграции со старой идентичности может потребоваться согласие на удаление прежнего доверия. Пока очистка не завершена, picker недоступен, а применённый профиль использует туннель без расшифровки. macOS отдельно может попросить разблокировать связку ключей или разрешить приложению доступ к учётным -данным, в том числе после перезапуска или обновления. Windows и Linux по-прежнему не поддерживают picker: +данным, в том числе после перезапуска или обновления. В Windows явные `on` или `trust` могут запросить подтверждение добавления корня. Linux по-прежнему не поддерживает picker: работа с его CA, хранилищем и прокси не запускается. Основной перехват Claude остаётся доступен; для его локальных файлов CA проверяются владелец, символические ссылки, права доступа и ACL Windows. @@ -571,7 +571,7 @@ Claude Code — это лишь учётные данные для доступ | `tool_use` ассистента | `function_call` (`input` → `arguments` в виде JSON-строки) | | `tool_result` пользователя | `function_call_output` (`is_error` → префикс `[tool error]`) | | Повтор `thinking` / `redacted_thinking` | Элементы `reasoning` с ограниченными конвертами `ocxr1` для подписей и скрытых данных | -| Function-инструменты | `{type: "function"}` (`web_search*` → `{type: "web_search"}`) | +| Function-инструменты | `{type: "function"}` (`web_search*` → `{type: "web_search"}`). На переведённом маршруте к `api.anthropic.com` типизированные объявления `web_search_*` и `web_fetch_*` также передаются Anthropic для выполнения на сервере, если только сайдкар веб-поиска сам не предоставляет `web_search`. Поиск и загрузка возвращаются клиенту как `server_tool_use` и соответствующий `web_search_tool_result` / `web_fetch_tool_result` | | `tool_choice` | `auto`→`auto`, `none`→`none`, `any`→`required`, именованная функция→`{type:"function",name}`, размещённый WebSearch/web_search→`{type:"web_search"}` | | `max_tokens` | `max_output_tokens` | | `stop_sequences` | `stop` | diff --git a/docs-site/src/content/docs/ru/guides/codex-app-models.md b/docs-site/src/content/docs/ru/guides/codex-app-models.md index 366b3b44046..04a4381f8ee 100644 --- a/docs-site/src/content/docs/ru/guides/codex-app-models.md +++ b/docs-site/src/content/docs/ru/guides/codex-app-models.md @@ -84,10 +84,11 @@ per-model identity и метаданные вместо приближения | Маршрут | Id в селекторе и метаданные каталога | | --- | --- | -| Вход Codex (строки с указанием аккаунта выключены) | Bare native-id, например `gpt-5.6-sol`, `gpt-5.6-terra` и `gpt-5.6-luna`; Pool или Direct выбирается через `codexAccountMode`. У строк GPT-5.6 окно каталога 922 000 токенов. | +| Вход Codex (строки с указанием аккаунта выключены) | Bare native-id, например `gpt-5.6-sol`, `gpt-5.6-terra` и `gpt-5.6-luna`; Pool или Direct выбирается через `codexAccountMode`. У строк GPT-5.6 окно каталога 922 000 токенов. `gpt-6.1-terra`: контекст 500,000 по умолчанию / 872,000 расширенный. | | Вход Codex (строки с указанием аккаунта включены и есть подходящие селекторы) | По одной строке `/` для каждой пары подходящего селектора и поддерживаемой нативной модели; каждая строка использует только сопоставленный аккаунт, а bare native-строки скрыты из picker'а. Нативные метаданные и окна контекста сохраняются. | -| OpenAI (API key) | Ровно восемь namespaced-строк: `gpt-5.5`, `gpt-5.6`, Sol/Terra/Luna и три виртуальных id `*-pro` (контекст 922,000; максимум входа 922,000 у всех восьми) | +| OpenAI (API key) | Модели с пространством имён включают `gpt-5.5`, `gpt-5.6`, Sol/Terra/Luna, виртуальные id `*-pro` и псевдонимы Daybreak (контекст 1,050,000; максимум входа 922,000). `openai-apikey/gpt-6.1-terra` добавляет контекст 1,050,000 и максимум выхода 128,000. | | OpenRouter | `openrouter/openai/gpt-5.6-sol`, `openrouter/openai/gpt-5.6-terra`, `openrouter/openai/gpt-5.6-luna` (922,000) | +| OpenRouter | `openrouter/openai/gpt-6.1-terra` (1,050,000) | | Cursor | Статический fallback включает `cursor/gpt-5.6-sol`, `cursor/gpt-5.6-terra` и `cursor/gpt-5.6-luna` (1,000,000), а также обычные/Fast-строки Grok 4.5, 4.6 и 4.7 (500,000). Для 4.6 и 4.7 доступен ещё `xhigh`; какие строки останутся видимыми, решает live-discovery аккаунта. | | xAI | Live-discovery имеет приоритет. Fallback-каталог включает `xai/grok-4.6` и `xai/grok-4.7`, а моделью по умолчанию остаётся `xai/grok-4.5`; у всех трёх окно 500,000 токенов. Grok 4.6 и 4.7 поддерживают `low` / `medium` / `high` / `xhigh` (upstream-default: `high`), а Grok 4.5 — только до `high`. | diff --git a/docs-site/src/content/docs/ru/guides/combos.md b/docs-site/src/content/docs/ru/guides/combos.md index fe966d0cbf9..31b760bfd3d 100644 --- a/docs-site/src/content/docs/ru/guides/combos.md +++ b/docs-site/src/content/docs/ru/guides/combos.md @@ -163,7 +163,7 @@ ocx combo set balanced \ | Классифицированная ошибка аутентификации, подписки, квоты, rate-limit, перегрузки или upstream-server | Перевести цель в cooldown и переключиться, даже если одного статуса недостаточно. | | Отмена клиентом (499), `origin_rejected`, отказ из-за cyber-policy, переполнение контекста или иной некорректный запрос | Остановиться и вернуть ошибку; другая цель не сделает такой запрос корректным. | | Точный отказ HTTP 400 о том, что модель не поддерживается в Codex с аккаунтом ChatGPT | До начала вывода пробует следующую настроенную цель. Полная фраза отказа должна находиться в `detail`, `error.message` или обычном тексте; смешанные объекты `detail` и `error` завершают запрос. Сам этот отказ не переводит аккаунт в состояние повторного входа и не включает период ожидания для всех моделей провайдера. | -| Структурированный HTTP 400, отклоняющий необязательный `user`, неподдерживаемое значение `reasoning.effort`/`reasoning_effort` или специфичный для модели отказ входного изображения (`param: input`) | До начала вывода переходит к следующей допустимой цели без охлаждения; см. «Совместимость необязательных параметров» ниже. | +| Структурированный HTTP 400, отклоняющий необязательный `user`, неподдерживаемое значение `reasoning.effort`/`reasoning_effort`, специфичный для модели отказ входного изображения (`param: input`) или отказ маршрута, который не может прочитать входные медиаданные (`unsupported_input_modality`) | До начала вывода переходит к следующей допустимой цели без охлаждения; см. «Совместимость необязательных параметров» ниже. | | Первый вызов инструмента в Responses-ходе внутрипроцессного адаптера (`runTurn`), который текущий запрос не объявлял, до любого вывода и до побочного эффекта, небезопасного для повтора | Охлаждает цель и переходит к следующей с тем же каталогом инструментов. После видимого вывода или небезопасного для повтора побочного эффекта отказ окончателен. Запросы Chat Completions и Anthropic Messages не меняются. | | Любая другая неклассифицированная ошибка | Остановиться и вернуть ошибку. | @@ -234,7 +234,7 @@ Failover намеренно ограничен. Он помогает при п ### Дашборд Откройте локальный дашборд и выберите **Models → Combos**. Рабочая область умеет создавать, редактировать, -переименовывать и удалять combo, а селектор целей исключает отключённые модели и вложенные combo. +переименовывать и удалять combo. Селекторы моделей включают скрытые модели: видимость влияет только на внешние списки, а не на внутренний выбор в combo. Выбор скрытой модели не делает её видимой снаружи. Селектор целей по-прежнему исключает отключённых провайдеров, вложенные combo и провайдеров JEV, предназначенных только для сервиса принятия решений. У каждой цели также отображается актуальный значок квоты: **Доступно**, **Квота исчерпана** или **Квота неизвестна**. Редактор блокирует сохранение и создание из-за квоты только тогда, когда для каждой пригодной к использованию цели есть действующее подтверждение сервера об исчерпании лимита инференса для настроенных учётных данных. Квоты аккаунта, модели, поиска и MCP, предназначенные только для отображения, а также отсутствующие или просроченные данные для принятия решения о маршрутизации не вызывают эту блокировку. Блокировка истекает при соответствующем сбросе квоты или окончании срока актуальности данных и проверяется повторно, когда страница становится активной или видимой; «Обновить» повторно загружает и данные combo, и квоты. Редактор дашборда пока не предоставляет `cooldownMs` и `waitForCooldownMs`; до появления соответствующего UI используйте файл конфигурации или Management API. @@ -251,7 +251,7 @@ ocx combo remove --yes ``` `set` также принимает `--strategy`, `--sticky`, `--effort`, `--alias`, `--native-alias`, -`--display-name` и `--rename-from`. Значение `-` у `--effort`, `--alias` или `--display-name` +`--display-name`, `--description` и `--rename-from`. Значение `-` у `--effort`, `--alias`, `--display-name` или `--description` очищает соответствующее поле. Для `--native-alias` нужны поддерживаемый сейчас bare native alias и непустой display name. `create` и `update` — alias для `set`, а `delete` — alias для `remove`; те же подкоманды доступны через `ocx route combo`. @@ -301,6 +301,7 @@ Combo хранятся в объекте верхнего уровня `combos`, | `alias` | No | none | Необязательный обрезанный публичный id модели; используйте правила alias выше. Пустое значение хранится как отсутствие alias. | | `nativeAlias` | No | `false` | Явно разрешает поддерживаемому сейчас bare native alias перехватить приоритет routing/catalog только для неквалифицированного id. Bare `gpt-5.6-*` использует учётные данные Codex Pool/Direct; маршруты с квалификатором аккаунта сохраняют свою идентичность, а provider-qualified `openai-apikey/gpt-5.6-*` использует API-ключ и никогда не переходит на native alias. | | `displayName` | No | none | Метка только для отображения в catalog; обязательна при `nativeAlias: true`. | +| `description` | No | none | Описание строки catalog только для отображения, с теми же ограничениями, что и `displayName`. Без него остаётся сгенерированный текст `Routed via opencodex → combo (combo).`. | ## Устранение неполадок @@ -331,6 +332,6 @@ CLI и дашборд показывают точное сообщение ва ## Совместимость необязательных параметров -Исключение для завершающих ошибок 400: структурированный отказ от `user`, неподдерживаемое значение `reasoning.effort`/`reasoning_effort` или специфичный для модели отказ входного изображения (`param: input`) позволяет до начала вывода перейти к следующей допустимой цели без периода охлаждения. Отказ политики безопасности, отмена и уже начавшийся вывод по-прежнему запрещают повторное выполнение. +Исключение для завершающих ошибок 400: структурированный отказ от `user`, неподдерживаемое значение `reasoning.effort`/`reasoning_effort`, специфичный для модели отказ входного изображения (`param: input`) или отказ маршрута, который не может прочитать входные медиаданные (`unsupported_input_modality`) позволяет до начала вывода перейти к следующей допустимой цели без периода охлаждения. Отказ политики безопасности, отмена и уже начавшийся вывод по-прежнему запрещают повторное выполнение. [Canonical compatibility details](/guides/combos/#request-local-target-compatibility). diff --git a/docs-site/src/content/docs/ru/guides/integrations.md b/docs-site/src/content/docs/ru/guides/integrations.md index b5e4929dcb1..0aebfa48c8a 100644 --- a/docs-site/src/content/docs/ru/guides/integrations.md +++ b/docs-site/src/content/docs/ru/guides/integrations.md @@ -4,8 +4,8 @@ description: Подключайте opencodex к OpenCode, Pi, OMP, Hermes, Open --- Вкладка **Подключение** записывает блок провайдера opencodex в собственный файл -конфигурации клиента и при необходимости удаляет его. Так работают восемнадцать -клиентов, у каждого свой переключатель: +конфигурации клиента и при необходимости удаляет его. Так работают следующие +клиенты, у каждого свой переключатель: | Клиент | Файл конфигурации | Формат | Когда изменение начинает действовать | Учётные данные | |---|---|---|---|---| diff --git a/docs-site/src/content/docs/ru/guides/providers.md b/docs-site/src/content/docs/ru/guides/providers.md index 1449dfbb822..49ff52970f9 100644 --- a/docs-site/src/content/docs/ru/guides/providers.md +++ b/docs-site/src/content/docs/ru/guides/providers.md @@ -65,8 +65,9 @@ description: Все способы, которыми opencodex аутентиф Повтор при 429 на том же ключе ([`retryOn429`](/ru/reference/configuration/)) применим только к провайдерам с API-ключом (`authMode: "key"`). Пресеты OAuth, forward и local исключены — их учётные данные нельзя повторно отправлять по тому же токену, а у локальных сред выполнения нет -удалённого ключа. Это opt-in: при отсутствии опции функция выключена; наличие объекта включает -её, если только `enabled: false`. +удалённого ключа. Это opt-in, за исключением задокументированных значений по умолчанию для отдельных назначений. Канонический Mistral Chat использует терпеливую политику повторов (6 повторов, резервный интервал 10 секунд) в пределах существующего лимита отправок на весь запрос. Явные настройки `retryOn429`, включая `enabled: false`, имеют приоритет. Пресет Mistral также по умолчанию пересылает переданные ключи кэша промптов; `promptCacheKey: false` отключает пересылку. Привязка к кэшу повышает шанс повторного использования, но не гарантирует попадание в кэш и не снимает лимиты пропускной способности по токенам. + +Mistral использует одного провайдера с API-ключом и для ключей Studio, и для ключей Vibe. Типы ключей и лимиты тарифов описаны в [документации Mistral по API-ключам](https://docs.mistral.ai/admin/identity-access/api-keys); прокси не определяет уровень подписки по ключу. Для всех моделей явные сроки `Retry-After` от апстрима общие для вызовов с одними и теми же учётными данными и моделью. 429 без времени повтора может запустить консервативное 60-секундное ожидание восстановления, если корректные поминутные счётчики показывают исчерпанную ёмкость или измеренная стоимость отклонённого запроса превышает оставшиеся токены, но укладывается в полный заявленный лимит. Это локальная политика восстановления, а не точный прогноз сброса на сервере. При нормальном трафике кэш и стоимость следующего запроса никогда не прогнозируются. После ожидания выполняется одна пробная попытка восстановления; при успехе обычный трафик возобновляется. Отсутствующие, некорректные, с нулевым правом использования, с исчерпанным месячным лимитом или превышающие полный лимит данные не приводят к выдуманному сбросу или сжатию истории. `requestPacing: { "enabled": false }` или `retryOn429: { "respectRetryAfter": false }` отключает общий темп запросов. Для повтора с тем же ключом: если время повтора от Mistral превышает `retryOn429.maxIntervalMs`, прокси возвращает 429 и подсказку о повторе, а не повторяет запрос раньше времени. Если включён выбор ключей по кругу (round-robin), ожидающие запросы перепроверяют периоды охлаждения ключей и получают ёмкость темпа для запасного ключа, а отказ ключа до начала вывода может быть восстановлен на исправном ключе в пределах существующего лимита отправок запроса. Окна контекста и настройки компактизации для отдельных моделей остаются независимыми. ## 1. Вход через ChatGPT (forward / passthrough) @@ -110,6 +111,7 @@ ocx login google-antigravity ocx login cursor # отдельный PKCE-вход Cursor ocx login command-code # браузерный OAuth Command Code (или импорт ~/.commandcode/auth.json) ocx login devin # Cognition/Devin: сначала импорт данных Devin CLI, иначе вход через браузер Auth0 +ocx login dsh-account # DeepSeek Harness Desktop (импорт разрешения ~/.dsh/.credentials.yaml) ocx login github-copilot # device flow GitHub → токен Copilot (Copilot Pro/Business) ocx login codex # пул аккаунтов Codex (псевдонимы: chatgpt, openai; нужен запущенный прокси) ocx logout @@ -126,6 +128,7 @@ ocx logout | `google-antigravity` | `google` | `https://daily-cloudcode-pa.googleapis.com` | Google OAuth поверх протокола Cloud Code Assist. Живое обнаружение использует аутентифицированный CCA-эндпоинт `v1internal:fetchAvailableModels` и публикует только agent-модели, доступные текущему аккаунту; поддерживаемый каталог остаётся резервным вариантом. | | `cursor` | `cursor` | `https://api2.cursor.sh` | Экспериментальный PKCE-вход, живой транспорт HTTP/2 и обнаружение моделей с фильтрацией по аккаунту. | | `devin` | `devin` | `https://server.codeium.com` | Экспериментальный неофициальный мост к Cognition/Devin. Вход сначала импортирует учётные данные, которые уже хранит установленный Devin CLI (`devin auth login` записывает `devin-session-token` в его `credentials.toml`); если их нет — открывает страницу Auth0 в браузере и обменивает вставленный токен через `RegisterUser` на долгоживущий API-ключ. `ocx login devin-cli` продолжает работать как устаревший алиас. Список моделей запрашивается для каждой учётной записи через `GetCascadeModelConfigs`; потоковая передача идёт только по пути `runTurn` поверх Connect-RPC. В пресете панели по умолчанию отсутствует. | +| `dsh-account` | `dsh-account` | `https://api.deepseek.com/anthropic` | Экспериментальный провайдер с приоритетом импорта, повторно использующий аккаунт установленного и авторизованного DeepSeek Harness (DSH) Desktop. Считывает учётные данные строго в режиме только для чтения из `~/.dsh/.credentials.yaml` при явном импорте. Полностью независим от тарификации API-ключей DeepSeek. | | `github-copilot` | `openai-chat` | `https://api.githubcopilot.com` | Экспериментально. Device flow GitHub + обмен `copilot_internal` (OAuth-клиент VS Code). Требуется активная подписка Copilot; это не официальный сторонний API. | Проверки квот аккаунтов и провайдера Google Antigravity используют фиксированные адреса Google, включая резервный запрос списка моделей. Для этих адресов поддерживается прозрачный Fake-IP DNS с сохранением проверки TLS, запрета перенаправлений и проверки частных адресов. Пользовательский base URL меняет только запросы моделей; `NO_PROXY` сохраняет политику прямого подключения. @@ -204,9 +207,20 @@ Inline JSON и лишние позиционные аргументы откло Поскольку откат возможен только при наличии снимка, **Добавить аккаунт** откажется выходить из `kiro-cli`, если хранилище сессии существует, но его нельзя захватить (файл не читается, несовпадение схемы, неоднозначный выбор токена), если `KIROCLI_DB_PATH` / `KIRO_CLI_DB_FILE` направляют импорт не на активное хранилище CLI, или если в основной базе CLI нет распознаваемой строки токена. Исправьте или удалите повреждённую базу по обычному пути данных `kiro-cli`, снимите селекторы только для импорта и повторите попытку. На машины без существующей сессии `kiro-cli` это не влияет. +### DeepSeek Account (DSH) + +`dsh-account` — экспериментальный провайдер учётных записей с приоритетом импорта, предназначенный для повторного использования аутентифицированных учётных данных установленного приложения DeepSeek Harness (DSH) Desktop. + +- **Независимость от API-ключа DeepSeek:** `dsh-account` использует квоту авторизованной сессии DeepSeek пользователя (баланс обычного/бонусного кошельков и общие расходы), тогда как провайдер `deepseek` списывает средства с баланса API-ключа платформы. Эти два домена учётных данных и маршрутизации строго разделены и никогда не переключаются друг на друга. +- **Только явный импорт:** OpenCodeX определяет наличие установленного и авторизованного DeepSeek Harness (`~/.dsh/.credentials.yaml`), но никогда не копирует, не импортирует и не использует токен автоматически без явного действия пользователя (`ocx login dsh-account` или нажатия кнопки «Импорт» в веб-дашборде). +- **Хранилище учётных данных DSH только для чтения:** OpenCodeX обращается с `~/.dsh/.credentials.yaml` исключительно в режиме только для чтения. OpenCodeX никогда не записывает, не изменяет, не удаляет файлы и не меняет права доступа к файлам DeepSeek Harness. +- **Изолированная копия учётных данных и выход:** При импорте OpenCodeX проверяет токен через официальный эндпоинт идентификации DeepSeek (`/auth-api/v0/users/current`) и сохраняет собственную копию в хранилище аутентификации OpenCodeX. Выход из системы или удаление аккаунта в OpenCodeX удаляет только локальную копию OpenCodeX; это никогда не вызывает завершение удалённой сессии и не приводит к выходу из DeepSeek Harness Desktop. +- **Без фонового обновления токена и повторный импорт при 401:** Разрешения сессий DSH не поддерживают эндпоинт автоматического фонового обновления. Если апстрим возвращает HTTP 401 (`ACCOUNT_TOKEN_INVALID`), OpenCodeX помечает аккаунт как `needsReauth` и прекращает маршрутизацию. Для восстановления доступа пользователь должен выполнить явный повторный импорт через `ocx login dsh-account`. +- **Отказ от ответственности третьих сторон:** OpenCodeX не заявляет об официальном одобрении или авторизации со стороны DeepSeek для сторонних клиентов, повторно использующих сессионные разрешения. + ## 3. Каталог API-ключей -opencodex поставляется с 104 встроенными пресетами: 85 на основе ключей, 15 OAuth, три локальных и +opencodex поставляется с 106 встроенными пресетами: 85 на основе ключей, 17 OAuth, три локальных и один пресет ChatGPT-форварда по умолчанию. Селектор **Add provider** в дашборде открывает страницу выдачи ключей провайдера, проверяет ключ и сохраняет его; проверка зависит от провайдера. Наиболее заметные записи: @@ -262,7 +276,7 @@ opencodex поставляется с 104 встроенными пресета | Z.AI (GLM Coding) | `https://api.z.ai/api/coding/paas/v4` | | Zhipu AI (BigModel) | `https://open.bigmodel.cn/api/paas/v4` | | [BigModel Coding Plan — Responses (статический список)](/guides/providers/#bigmodel-coding-plan-over-responses) | `https://open.bigmodel.cn/api/v1` | -| Qwen Cloud | Token plan (по умолчанию): `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` · Pay as you go: `https://dashscope.aliyuncs.com/compatible-mode/v1` · или Custom | +| Qwen Cloud | Token plan (по умолчанию): `https://token-plan.maas.qwencloudapi.com/compatible-mode/v1` · Pay as you go: `https://dashscope.aliyuncs.com/compatible-mode/v1` · или Custom | | Tencent Cloud Coding Plan | `https://api.lkeap.cloud.tencent.com/coding/v3` | | SiliconFlow | `https://api.siliconflow.cn/v1` | | Volcengine Ark · Coding Plan · Agent Plan | `https://ark.cn-beijing.volces.com/api/v3` · `https://ark.cn-beijing.volces.com/api/coding/v3` · `https://ark.cn-beijing.volces.com/api/plan/v3` | @@ -282,29 +296,20 @@ opencodex поставляется с 104 встроенными пресета Создайте ключ в [панели OpenGateway](https://opengateway.ai/api-keys), затем выполните `ocx provider add opengateway` или выберите **OpenGateway** в панели. Chat-запросы используют настроенный Bearer-ключ; публичный список не подтверждает действительность ключа. +В Responses OpenGateway отклоняет повторные нативные вызовы пользовательских инструментов, поэтому пресет перед отправкой преобразует инструменты Codex, например `exec` в режиме кода, в функциональные инструменты. +Массивы содержимого результатов инструментов отправляются как текст, а изображения сохраняются после соответствующей группы результатов инструментов. Неподдерживаемые части результатов, включая отказы и непрозрачное содержимое, явно отклоняются с ошибкой клиентского ввода до отправки. +Детализация изображения `original` преобразуется в `high`, которую принимает шлюз; байты изображения не меняются. +Нативный маршрут Responses также проверяет пустые текстовые завершения, прежде чем выдать их как успешные. +Исчерпанный бюджет вывода даёт `incomplete` с `max_output_tokens`; прокси не увеличивает запрошенный бюджет. Пустое завершение, которое безопасно повторить и у которого остался бюджет, может быть повторено один раз в пределах существующего лимита отправок. Явный `emptyCompletionRetry: false` или `OCX_EMPTY_COMPLETION_RETRY=0` отключает этот повтор. Текст, отказы, активность инструментов и неизвестные события потока исключают повтор, и исходный поток сохраняется. Эта защита не меняет других провайдеров и их значения повторов по умолчанию. +Для повтора нужны явный `store: false`, полный ввод и только инструменты, выполняемые клиентом; размещённый инструмент, например `web_search`, исключает автоматический повтор, даже если событий инструмента не было. + +**OpenCode Zen** (`opencode-zen`) использует `https://opencode.ai/zen/v1` с API-ключом из [opencode.ai/auth](https://opencode.ai/auth). Бесплатные модели могут упираться в короткий лимит около 15–20 запросов в минуту (оценка сообщества; OpenCode не публикует RPM). Если ответ 429 о лимите частоты не содержит `Retry-After`, opencodex добавляет пояснение и синтетический `Retry-After`; корректные заголовки сервера имеют приоритет. Повторы с тем же ключом включаются через [`retryOn429`](/ru/reference/configuration/). + +На официальных маршрутах `https://opencode.ai/zen/v1` и `https://opencode.ai/inference/v1` канонического провайдера `opencode-zen` с аутентификацией API-ключом Gemini автоматически использует нативный API Google в стандартном режиме `googleMode: ai-studio`. Идентификаторы моделей Zen сохраняются, включая Flash без суффикса `-tiered`. Claude использует Anthropic Messages независимо от `googleMode`. При загрузке сохранённой конфигурации игнорируются только допустимые старые переопределения Chat/Responses для этих нативных моделей и допустимые настройки размещённого инструмента `image_generation`, предназначенные только для Responses. Пользовательские провайдеры, маршруты без аутентификации ключом и остальные настройки не меняются. + +**Поддержка `opencode-free` удалена**: теперь все модели возвращают `403 permission_denied` из-за ограничения бесплатного уровня OpenCode ([#7255](https://github.com/lidge-jun/opencodex/issues/7255)). При загрузке существующей конфигурации соответствующие записи и ссылки на модели отключаются в памяти, предупреждение выводится один раз на процесс, файл не изменяется. Если это был провайдер по умолчанию, используется стандартный маршрут OpenAI OAuth. Выберите `opencode-go`, `opencode-zen` с ключом или другого провайдера. [Условия OpenCode Zen](https://opencode.ai/docs/zen/). -**OpenCode Zen** (`opencode-zen`) и бесключевой пресет **OpenCode Free** используют один -`https://opencode.ai/zen/v1`. Бесплатные модели на этом шлюзе часто упираются в короткое окно -примерно 15–20 запросов в минуту (оценка сообщества; OpenCode не публикует RPM). -Zen может отвечать общими 429 без заголовков `Retry-After` / `X-RateLimit-*`. Это отдельно от -бесключевой десктопной квоты (~200 запросов Big Pickle/бесплатных моделей за 5 часов на -`opencode-free`). Когда Zen опускает `Retry-After` на таком 429, opencodex добавляет пояснение -в ошибку клиента и синтетический `Retry-After`; при наличии upstream `Retry-After` он имеет -приоритет. Повтор с тем же ключом по-прежнему включается через [`retryOn429`](/ru/reference/configuration/). - -**Бесключевой уровень `opencode-free` сейчас закрыт для сторонних клиентов.** Zen отклоняет любой -запрос без заголовка `x-opencode-session`, возвращая тип ошибки `MissingSessionID` и сообщение -«OpenCode's free tier can only be used in OpenCode». Проверяется только наличие заголовка, поэтому -прокси мог бы пройти её, выдумав значение, — opencodex так не делает. Подделать идентификатор сессии -и версионный User-Agent `opencode/` значит объявить себя клиентом OpenCode, а OpenCode не -публикует договор о стороннем подключении к этому бесключевому уровню; полученный так HTTP 200 — -это обойдённая проверка допуска, а не разрешение. Поэтому opencodex сообщает об ограничении вместо -обхода: запрос к `opencode-free` возвращает ошибку с объяснением. - -Поддерживаемый путь к тем же моделям — провайдер **`opencode-zen`** с ключом OpenCode Zen API, -полученным на [opencode.ai/auth](https://opencode.ai/auth). Если OpenCode позже опубликует сторонний -путь для бесключевого уровня, opencodex сможет его использовать; до тех пор пресет документирует -ограничение. Условия вышестоящего сервиса: [opencode.ai/docs/zen](https://opencode.ai/docs/zen/). +Рабочие назначения и значения по умолчанию Claude Desktop сохраняются. Правила с опустевшим списком разрешённых исходных моделей отключаются; при удалении модели внедрения отключается и синхронизация нативных субагентов. Последующие сохранения не восстанавливают удалённые ссылки и сохраняют корректные параллельные изменения. Большинство провайдеров использует адаптер `openai-chat` с bearer-ключом; пресеты с поддержкой протокола Anthropic, такие как **Xiaomi MiMo** (`xiaomi`), используют адаптер `anthropic` (`x-api-key`). @@ -485,6 +490,9 @@ GPT-5.6 Sol/Terra/Luna заранее внесены в резервные сп | Вход Codex (Pool или Direct) | `gpt-5.6-*` | 922,000 | | OpenAI (API key) | `openai-apikey/gpt-5.6-*` плюс `*-pro` | 922,000 (макс. вход 922,000) | | OpenRouter | `openrouter/openai/gpt-5.6-sol`, `openrouter/openai/gpt-5.6-terra`, `openrouter/openai/gpt-5.6-luna` | 922,000 | +| Codex | `gpt-6.1-terra` | 500,000 / 872,000 | +| OpenAI API | `openai-apikey/gpt-6.1-terra` | 1,050,000 (128,000 max output) | +| OpenRouter | `openrouter/openai/gpt-6.1-terra` | 1,050,000 | | Cursor | `cursor/gpt-5.6-sol`, `cursor/gpt-5.6-terra`, `cursor/gpt-5.6-luna` | 1,000,000 | Нативные записи GPT-5.6 сохраняют закреплённые вышестоящие шкалы уровней рассуждений (например, у @@ -505,7 +513,7 @@ Assist), `azure` / `azure-openai`, `kiro` и `cursor`. Проприетарны точке. **Cloudflare AI Gateway** требует подставить в URL id аккаунта и шлюза. Copilot предоставляет каталог со смешанными проводами: модели (`gpt-5.3-codex`, -`gpt-5.4`, `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`) +`gpt-5.4`, `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6.1-sol`, `gpt-6.1-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`) отклоняют `/chat/completions` для агентного трафика, поэтому opencodex по умолчанию маршрутизирует эти модели через Responses API, а все остальные модели Copilot остаются на chat completions. Приоритет: жёсткий wire-пин → явная запись diff --git a/docs-site/src/content/docs/ru/guides/remote-link.md b/docs-site/src/content/docs/ru/guides/remote-link.md index 9ade1c07cc8..229918c6f7c 100644 --- a/docs-site/src/content/docs/ru/guides/remote-link.md +++ b/docs-site/src/content/docs/ru/guides/remote-link.md @@ -55,7 +55,7 @@ Host devbox - **Connected** означает, что SSH-туннель готов и Child может использовать связь Home. - **Reconnecting** означает, что туннель переподключается. Во время повторных попыток запросы могут временно получать `503` с `Retry-After`. На Child, подключённом из собственной панели, запрос сначала до 15 секунд ждёт восстановления туннеля. -- **Failed** означает, что связь требует действий. Проверьте SSH-аутентификацию, подтверждённый ключ хоста, перенаправление или причину тайм-аута. Сторона, которая держит SSH-туннель, сама повторяет попытки после сна, сбоя или перезапуска (Home — для добавленного им Child, Child — для связи, к которой он подключился из собственной панели): примерно раз в минуту после тайм-аута или ошибки перенаправления и каждые пять минут после ошибки аутентификации. Изменённый ключ хоста никогда не повторяется. Чтобы переподключиться сразу, нажмите **Retry** в панели Home или выполните `ocx link reconnect --link-id ` на Home. Туннель этой связи перезапустится без перезапуска OpenCodex. +- **Failed** означает, что связь требует действий. Проверьте SSH-аутентификацию, подтверждённый ключ хоста, перенаправление или причину тайм-аута. Сторона, которая держит SSH-туннель, сама повторяет попытки после сна, сбоя или перезапуска (Home — для добавленного им Child, Child — для связи, к которой он подключился из собственной панели): после тайм-аута или ошибки перенаправления через минуту, а затем с интервалом, который удваивается после каждой новой неудачи, но не превышает пяти минут; после ошибки аутентификации — каждые пять минут. Изменённый ключ хоста никогда не повторяется. Чтобы переподключиться сразу, нажмите **Retry** в панели Home или выполните `ocx link reconnect --link-id ` на Home. Туннель этой связи перезапустится без перезапуска OpenCodex. При сбое связи система молча не переключается на локального провайдера. diff --git a/docs-site/src/content/docs/ru/guides/sidecars.md b/docs-site/src/content/docs/ru/guides/sidecars.md index 337c69062cc..57db4fe1022 100644 --- a/docs-site/src/content/docs/ru/guides/sidecars.md +++ b/docs-site/src/content/docs/ru/guides/sidecars.md @@ -84,9 +84,13 @@ SSE-событие `response.failed`. `modelInputModalities` явно указана только текстовая модальность — и запрос содержит изображение, opencodex описывает каждое изображение **до** основного вызова и заменяет его текстом, если доступен план vision-сайдкара. Без доступного плана исходное изображение удаляется, а не передаётся текстовому -бэкенду. Каталог моделей объявляет вход изображений для каждой модели, покрытой сайдкаром. Комбо -объявляют вход изображений только если каждый участник принимает изображения нативно или через -сайдкар и параметр комбо `imageInput` не отключён; поэтому такие клиенты, как приложение Codex, +бэкенду. При `visionSidecar.enabled: false` каталог моделей не добавляет поддержку ввода изображений +через сайдкар текстовым моделям, включая пользовательские записи и участников комбо. Нативная +поддержка изображений и явно настроенные модальности изображений сохраняются. Если `enabled` +не указан, объявление поддержки через сайдкар остаётся включённым; наличие в каталоге не подтверждает +доступность учётных данных или ресурсов сайдкара. Комбо объявляют вход изображений только если +каждый участник принимает изображения нативно или через включённый сайдкар и параметр комбо +`imageInput` не отключён; поэтому такие клиенты, как приложение Codex, разрешают вложения вместо их блокировки до запуска сайдкара. Если `visionSidecar.model` отсутствует или пуст, путь выполнения OpenAI, дашборд и API управления используют фолбэк `gpt-5.6-luna`. При запуске явно сохранённое устаревшее значение `gpt-5.4-mini` по-прежнему мигрирует на `gpt-5.6-luna`; миграция применяется только к сохранённому @@ -155,6 +159,8 @@ opencodex описывает каждое изображение **до** осн `PUT /api/sidecar-settings` принимает те же поля. Частичное обновление оставляет непереданные ключи без изменений. `timeoutMs` использует целочисленные границы рантайма (1–2147483647 мс). +Если через `PUT /api/sidecar-settings` меняется действующее состояние переключателя Vision, после сохранения обновляются управляемые каталог и кэш; `catalogRefreshPending=true` сообщает, что обновление не завершилось, и не служит подтверждением от клиента. + Карточка сайдкара web-search устроена так же: первая строка выбора модели — **Выкл. (Off)**. Выключение останавливает перехват `web_search` со стороны OpenCodex, а интеграция Codex записывает `web_search = "disabled"` в `~/.codex/config.toml`: до этого Codex diff --git a/docs-site/src/content/docs/ru/guides/sub-agent-surface.md b/docs-site/src/content/docs/ru/guides/sub-agent-surface.md index edfb1fe75cb..a854342ccaf 100644 --- a/docs-site/src/content/docs/ru/guides/sub-agent-surface.md +++ b/docs-site/src/content/docs/ru/guides/sub-agent-surface.md @@ -46,6 +46,42 @@ TUI-сессии. `multi_agent_version`. Настоящий pin `"v1"` исключает запись, потому что он прямо указывает, что эта модель относится к другой collaboration surface. +## Какие модели предлагает `spawn_agent` + +Codex перечисляет в описании инструмента `spawn_agent` не больше пяти моделей и выбирает их сам: сортирует видимые в picker строки каталога по `priority` по возрастанию и берёт первые пять. Ростер на странице **Subagents** (`subagentModels`) влияет на этот список только тем, что даёт своим моделям самые низкие приоритеты в порядке ростера (`0`–`4` без селекторов аккаунтов). Список им не ограничивается. + +Если выбрать от одной до четырёх моделей, Codex заполнит оставшиеся места следующими строками по приоритету. Вы их не выбирали, и дашборд не показывает, какие модели туда попали. Это зависит от каталога: + +| Каталог | Строки, которые заполняют оставшиеся места | +| --- | --- | +| Без селекторов аккаунтов Codex | Другие маршрутизируемые строки `provider/model`. У всех приоритет `5`, поэтому они идут по имени провайдера, затем по id модели, в алфавитном порядке. Невыбранные нативные строки GPT имеют приоритет не ниже `100` плюс число выбранных id и поэтому идут позже. | +| С селекторами аккаунтов Codex | Нативные строки с привязкой к аккаунту (`/`), сразу после выбранных строк. Невыбранные маршрутизируемые строки получают приоритет `1005` и идут после них. | + +Например, без селекторов аккаунтов и с таким ростером: + +```json +{ "subagentModels": ["xai/grok-4.5", "anthropic/claude-sonnet-5"] } +``` + +| Место | Модель | Priority | Почему | +| ---: | --- | ---: | --- | +| 1 | `xai/grok-4.5` | `0` | Первая запись ростера | +| 2 | `anthropic/claude-sonnet-5` | `1` | Вторая запись ростера | +| 3 | `deepseek/deepseek-v4-flash` | `5` | Первая оставшаяся маршрутизируемая строка по алфавиту | +| 4 | `google/gemini-3-pro` | `5` | Следующая маршрутизируемая строка | +| 5 | `moonshot/kimi-k3` | `5` | Следующая маршрутизируемая строка | +| 6+ | `zai/glm-5.2`, затем нативные строки GPT | `5`, затем `102` | За пределами пяти | + +С пустым ростером opencodex ничего не закрепляет, и пять мест определяются обычными приоритетами каталога. + +Заполненные места — обычные предложения, и главный агент может выбрать одну из этих моделей. Этот список также не является списком разрешённых моделей: если главный агент указывает точное имя, Codex принимает и модель каталога за пределами пяти. Чтобы управлять тем, что предлагает Codex: + +- Выберите пять моделей. При полном ростере заполнять нечего. +- Отключите модель, которую никогда не хотите видеть в предложениях, или не включайте её в `selectedModels` её провайдера. Отключённая или невыбранная модель исчезает из picker и не может занять место. +- `modelPickerOrder` меняет приоритеты, по которым сортирует Codex. Список только из маршрутизируемых строк переносит перечисленные невыбранные строки на `1000` и выше, поэтому места первыми заполняют неперечисленные маршрутизируемые строки. Список с голым нативным id упорядочивает весь picker, включая выбранные строки: перечисленные строки идут первыми и занимают предлагаемые места раньше любой неперечисленной строки. См. [Model ordering](/guides/model-ordering/). + +В v2 Codex пропускает строки, закреплённые как `"disabled"`, прежде чем взять пять; в остальном всё работает так же. Ростер в v2-инструкциях opencodex (`{{roster}}`) называет только настроенные модели ростера, попавшие в пятёрку, поэтому модели-заполнители появляются в описании инструмента Codex и никогда — в инструкциях opencodex. Codex читает каталог при старте сессии, поэтому изменение ростера действует для новых сессий. + ## Модель делегирования и effort Раздел **Sub-agent delegation** в дашборде управляет тремя связанными настройками: @@ -220,6 +256,10 @@ model- или effort-override. иметь pin на v1. Значения `"v2"`, `null` или отсутствие surface-поля допускаются; настоящий `"v1"` pin — нет. +### Почему `spawn_agent` показывает модели, которые я не выбирал? + +Codex заполняет до пяти предлагаемых мест из каталога по приоритету. Если выбрано меньше пяти моделей, оставшиеся места занимают следующие строки; см. раздел «Какие модели предлагает `spawn_agent`» выше. + ### Смена режима влияет на уже запущенные сессии? Нет. После смены режима начните новую сессию Codex. Если долго живущий App host всё ещё показывает diff --git a/docs-site/src/content/docs/ru/guides/subagent-v1-default.md b/docs-site/src/content/docs/ru/guides/subagent-v1-default.md index 022b974c431..8cbc5f3a39a 100644 --- a/docs-site/src/content/docs/ru/guides/subagent-v1-default.md +++ b/docs-site/src/content/docs/ru/guides/subagent-v1-default.md @@ -146,3 +146,13 @@ ocx v2 mode v1 CLI подтверждение не запрашивает. Настройка та же, поэтому выбирайте её, учитывая изложенные здесь ограничения. + +## Лимит одновременно работающих подагентов + +В **Models → Model settings** настройка лимита подагентов задаёт максимальное число одновременно +работающих подагентов без учёта основного агента. Она доступна в v1, base и v2, включая гибридный +режим с отключённым глобальным флагом v2. Выбор режима не зависит от этого лимита. + +Повышение лимита разрешает больше подагентов, но не создаёт их автоматически и не гарантирует +ускорения. После изменения начните новую сессию Codex. Если длительно работающий хост App +продолжает использовать прежний лимит, перезапустите этот хост. diff --git a/docs-site/src/content/docs/ru/reference/cli.md b/docs-site/src/content/docs/ru/reference/cli.md index c47b3a1d397..da9ff0c9096 100644 --- a/docs-site/src/content/docs/ru/reference/cli.md +++ b/docs-site/src/content/docs/ru/reference/cli.md @@ -31,7 +31,7 @@ runtime port и проверку identity, а не поддерживая вто явно документированные как offline-операции с конфигурацией, вместо этого могут валидировать и редактировать файл конфигурации без живого прокси. -`ocx system codex-cli-update check` не требует работающего прокси и не обращается к реестру пакетов. Команда в строго ограниченном объёме проверяет метаданные происхождения настроенного кандидата, включая замаскированный путь к исполняемому файлу и подтверждения его принадлежности. Доверенный контекст опубликованного средства запуска подтверждает только подлинность снимка данных о кандидате, но не факт успешного запуска Codex. Поскольку команда выполняет только такую проверку и никогда не запускает Codex, кандидаты из окружения и сохранённых данных отображаются только в отчёте (`managed: false`, обычно `selection_unattested`). В выводе JSON присутствуют `candidateAvailable`, `candidateVersion`, `candidateSource` и `selectionAttested`, причём значение `selectionAttested` всегда равно `false`. Для проверки настроенного кандидата нужен доверенный контекст опубликованного средства запуска. При прямом запуске через Bun или из исходного кода такого подтверждения нет; в этом случае команда игнорирует кандидатов из окружения и сохранённых данных и может вернуть `candidate_unavailable` в POSIX или `windows_inspection_deferred` в Windows. В Windows этот первый этап вообще не выполняет файловый ввод-вывод по путям кандидата или конфигурации. Только абсолютный кандидат из окружения, зафиксированный доверенным средством запуска, может получить лексическую метку комплекта приложения или менеджера версий; все остальные кандидаты Windows отклоняются по принципу fail-closed. Команда не устанавливает и не восстанавливает ПО, не запускает Codex или npm, не управляет работающими процессами и ничего не записывает в конфигурацию или кеш. +`check` не обращается к реестру и не запускает Codex. В POSIX `selectionAttested` может быть `true`, если разрешитель выбирает тот же канонический путь, что и кандидат окружения, зафиксированный опубликованным средством запуска. Связанный манифест npm-global может подтвердить `managed: true`, если он доказывает владение и, когда версия кандидата сообщается, она совпадает с версией манифеста; сохранённые кандидаты остаются неаттестованными. Windows откладывает проверку, не читая файлы кандидата и конфигурацию. Прямой запуск через Bun или исходный код не даёт доказательства средства запуска. `plan` обращается к официальному реестру с изолированными временными настройками, кешем и журналами npm, отказывая при активных сессиях или неполном наблюдении процессов. Он ничего не устанавливает и не посылает сигналы. См. [полное описание команд и причин отказа (англ.)](/reference/cli/agents/#read-only-codex-cli-update-plan). Для наблюдения установки Windows x64 см. [`attest`](/ru/reference/cli/agents/). Без явных путей команда наблюдает выбранного кандидата, определённого привязанным к доказательству снимком средства запуска; полномочия на обновление и выбор runtime не подтверждаются. diff --git a/docs-site/src/content/docs/ru/reference/cli/agents.md b/docs-site/src/content/docs/ru/reference/cli/agents.md index 8ebf9402e23..1c24581fb08 100644 --- a/docs-site/src/content/docs/ru/reference/cli/agents.md +++ b/docs-site/src/content/docs/ru/reference/cli/agents.md @@ -256,7 +256,9 @@ env-reference, либо несекретную loopback-заглушку. Адр Управляйте headless runtime-setting'ами, startup, sync, diagnostics и update. -`ocx system codex-restart --yes` перезапускает Codex app-server'ы и полностью закрывает и заново запускает Desktop-приложение Codex тем же модулем, что и `ocx sync --restart-codex`. Если сам proxy запущен внутри приложения Codex, команда отказывается с actionable-сообщением вместо handoff, который она не может завершить. +`ocx system codex-restart --yes` перезапускает Codex app-server'ы тем же модулем, что и `ocx sync --restart-codex`. Desktop-приложение Codex полностью закрывается и запускается заново, если оно было запущено до записи текущего каталога моделей или этот запрос изменил привязку `model_catalog_json`, потому что выбор модели строится при запуске. Иначе приложение остаётся открытым, само перезапускает свой app-server и сохраняет неотправленные черновики и ожидающие подтверждения. Если состояние приложения или каталога прочитать нельзя, выполняется полный перезапуск. Если сам proxy запущен внутри приложения Codex, перезапуск Desktop отказывается с actionable-сообщением вместо handoff, который он не может завершить. + +Изменение привязки `model_catalog_json` этим запросом также вызывает перезапуск Desktop, даже если процесс оболочки запущен после записи файла каталога. ```bash ocx system settings --stream-mode eager-relay @@ -268,7 +270,7 @@ ocx system settings --stream-mode eager-relay ocx system codex-cli-update check --json ``` -`check` не обращается к реестру пакетов и в строго ограниченном объёме проверяет данные о происхождении настроенного кандидата, включая замаскированный путь к исполняемому файлу и подтверждения его принадлежности. Доверенный контекст опубликованного средства запуска подтверждает только подлинность снимка данных о кандидате, но не факт успешного запуска Codex. Поскольку команда выполняет только такую проверку и никогда не запускает Codex, кандидаты из окружения и сохранённых данных отображаются только в отчёте (`managed: false`, обычно `selection_unattested`). В выводе JSON присутствуют `candidateAvailable`, `candidateVersion`, `candidateSource` и `selectionAttested`, причём значение `selectionAttested` всегда равно `false`. Для проверки настроенного кандидата нужен доверенный контекст опубликованного средства запуска. При прямом запуске через Bun или из исходного кода такого подтверждения нет; в этом случае команда игнорирует кандидатов из окружения и сохранённых данных и может вернуть `candidate_unavailable` в POSIX-системах. В Windows этот первый этап вообще не выполняет файловый ввод-вывод по путям кандидата или конфигурации. Только абсолютный кандидат из окружения, зафиксированный доверенным средством запуска, может получить лексическую метку комплекта приложения или менеджера версий; все остальные кандидаты Windows отклоняются по принципу fail-closed. Поскольку этот этап вообще не читает сохранённое состояние выбора, запуск в Windows без захваченного кандидата из окружения возвращает `windows_inspection_deferred`, а не `candidate_unavailable`: команда не может определить, установлен ли Codex CLI, поэтому сообщает об отложенной проверке, а не утверждает, что кандидата нет. Команда не запускает Codex или менеджер пакетов, не восстанавливает shim, ничего не записывает в конфигурацию или кеш, не останавливает процессы и ничего не устанавливает. Кандидаты, входящие в комплект приложения, найденные в распознанных путях менеджеров версий, являющиеся непроверенными автономными установками или имеющие неоднозначное состояние shim, отображаются как `unmanaged` или `unknown` и никогда не классифицируются как `managed`. +`check` не обращается к реестру и не запускает Codex. В POSIX `selectionAttested` может быть `true`, если разрешитель выбирает тот же канонический путь, что и кандидат окружения, зафиксированный опубликованным средством запуска. Связанный манифест npm-global может подтвердить `managed: true`, если он доказывает владение и, когда версия кандидата сообщается, она совпадает с версией манифеста; сохранённые кандидаты остаются неаттестованными. Windows откладывает проверку, не читая файлы кандидата и конфигурацию. Прямой запуск через Bun или исходный код не даёт доказательства средства запуска. `plan` обращается к официальному реестру с изолированными временными настройками, кешем и журналами npm, отказывая при активных сессиях или неполном наблюдении процессов. Он ничего не устанавливает и не посылает сигналы. См. [полное описание команд и причин отказа (англ.)](/reference/cli/agents/#read-only-codex-cli-update-plan). В Windows захваченная команда без полного пути, например `CODEX_CLI_PATH=codex`, удалённый путь или путь устройства возвращает `candidate_path_unavailable`. Кандидат захвачен, но его путь не подходит для этой проверки. diff --git a/docs-site/src/content/docs/ru/reference/cli/lifecycle.md b/docs-site/src/content/docs/ru/reference/cli/lifecycle.md index 8ed62d8c7d7..f2e946ac417 100644 --- a/docs-site/src/content/docs/ru/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/ru/reference/cli/lifecycle.md @@ -50,10 +50,16 @@ ocx start --socks5-off ### `ocx stop` -Остановить работающий прокси (по PID), удалить PID-file и восстановить native Codex. Если +Остановить работающий прокси (по PID) и удалить PID-file; для автономного владельца также восстановить native Codex. Если установлена managed background service, `ocx stop` сначала останавливает и её, чтобы она не перезапустила прокси обратно. Кнопка **Stop** в веб-дашборде выполняет то же действие (`POST /api/stop`) на всех бэкендах, кроме планировщика заданий Windows: там обёртка может перезапустить прокси после завершения задачи, поэтому дашборд отказывает с `respawnable_service`, ничего не меняет и просит выполнить `ocx stop`. +На подключённом клиенте hub или Remote Link Child команда `ocx stop`, включая успешную остановку через API, сохраняет принадлежащие соединению маршрутизацию Codex, каталог моделей и конфигурацию Grok. `ocx start` снова обслуживает их, не внедряя маршрутизацию заново. Чтобы вернуть Codex к собственному аккаунту, выполните `ocx disconnect`; для повторного применения уже восстановленной нативной маршрутизации используйте `ocx sync --restart-codex`. + +Старая обычная версия прокси без подтверждённой поддержки сохранения отклоняется до остановки службы или процесса. Обновите и перезапустите её через управляющий супервизор, затем повторите команду. + +Для уже остановленного клиента команда подтверждает неактивность службы и отсутствие рантайма на известных локальных адресах, затем возвращает обычный результат «не запущен». + ### `ocx restart` Если прокси уже работает, команда запрашивает перезапуск на месте у точно аттестованных PID и @@ -101,7 +107,7 @@ ocx eject back ### `ocx recover-history --ocx-compaction --yes` -Исправьте историю одной задачи, сжатой через маршрутизируемого провайдера, перед её возобновлением в нативном Codex. Команда выбирает ровно одну задачу по UUID, сначала сохраняет приватную побайтовую резервную копию, а затем преобразует только принадлежащее OpenCodeX состояние сжатия `ocx1:` в обычную сводку, которую может воспроизвести нативный Codex. Нативное зашифрованное содержимое и другие задачи не изменяются. Перед запуском закройте выбранную задачу; если rollout изменится во время обработки, восстановление остановится без замены файла. +Исправьте историю одной задачи, сжатой через маршрутизируемого провайдера, перед её возобновлением в нативном Codex. Команда выбирает ровно одну задачу по UUID, сначала сохраняет приватную побайтовую резервную копию, а затем преобразует только принадлежащее OpenCodeX состояние сжатия `ocx1:`/`ocx2:` в обычную сводку, которую может воспроизвести нативный Codex. Нативное зашифрованное содержимое и другие задачи не изменяются. Перед запуском закройте выбранную задачу; если rollout изменится во время обработки, восстановление остановится без замены файла. ### `ocx uninstall` · `ocx remove` @@ -206,7 +212,7 @@ Identity-check живого прокси. Текстовый вывод сооб Проверяет готовность после синхронизации через не требующий аутентификации `GET /readyz`. При готовности возвращается `200`; для `pending` и терминального `failed` возвращается `503` с -`Retry-After: 1`. Санитизированные поля HTTP-ответа: `{service, version, uptime, pid, port, status, protocol, minimumClientProtocol, managementUrl}`. `protocol` — текущая версия удалённого протокола hub, `minimumClientProtocol` — минимальная совместимая версия клиента, а `managementUrl` — канонический origin управления для браузера. +`Retry-After: 1`. Санитизированные поля HTTP-ответа: `{service, version, uptime, pid, port, status, protocol, minimumClientProtocol, managementUrl, features}`. `protocol` — текущая версия удалённого протокола hub, `minimumClientProtocol` — минимальная совместимая версия клиента, а `managementUrl` — канонический origin управления для браузера. Старые прокси без `/readyz` fail-closed как `unreachable`; `/healthz` — отдельная проверка liveness, а не готовности. По умолчанию команда выполняет одну пробу. `--wait` опрашивает до готовности или тайм-аута, но при терминальном `failed` завершается немедленно. Тайм-аут по умолчанию — 45 секунд; @@ -234,6 +240,10 @@ credential'ы и не выполняет repair. ### `ocx sync [--restart-codex] [--restart-app-server-only]` +> **Дочерняя машина Remote Link:** `ocx sync` также применяет настройки Codex с Home и, если клиентский рантайм запущен раньше этого применения, перезапускает его даже без флага перезапуска. Если перезапуск не удался, прежний рантайм продолжает работать, а команда завершается с ненулевым кодом. Соответствующие поля `ocx status --json` и `ocx link status --json` описаны в разделе [Remote Link apply parity](/reference/cli/lifecycle/#remote-link-apply-parity) (на английском). + +Явно запрошенный перезапуск также выполняется после успешного сохранения изменения `config.toml`, даже если байты каталога и кэша не изменились. Операция только с конфигурацией без изменений, отказ, предварительная проверка или отменённая запись не вызывают перезапуск. Без флага перезапуска sync никогда не перезапускает Codex автоматически. + Получить живой список моделей от каждого настроенного провайдера и заново внедрить объединённый каталог в Codex. Запускайте после добавления провайдера или когда нужно обновить доступные модели. @@ -258,6 +268,8 @@ conversation'ов невосстановима, а устаревший picker Если команда запущена изнутри приложения Codex, restart передаётся detached helper'у, и эта сессия завершается вместе с приложением. +**Синхронизация с закреплённым источником.** `ocx sync --expect-link ` (Remote Link Child) и `ocx sync --expect-hub-key ` (клиент hub) выполняют синхронизацию, только если эта машина всё ещё подключена к этой связи или с этим ключом. Иначе ничего не меняется, локальные провайдеры не синхронизируются, команда завершается с кодом 5, а последняя строка stderr — `{"phase":"child_link_mismatch"|"hub_client_mismatch","reason":...}`. Неверный или повторённый флаг завершает команду с кодом 2. Подробнее: [английская справка](/reference/cli/lifecycle/#sync-only-from-the-expected-home---expect-link-and---expect-hub-key). + ### `ocx sync-cache [--restart-codex] [--restart-app-server-only]` Инвалидировать локальный кэш model picker'а Codex, чтобы он пересобрался из активного каталога diff --git a/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md index 87a58230f1d..c1a53a5103f 100644 --- a/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md @@ -154,6 +154,14 @@ label и masked key. ### `ocx account list [provider] [--json] [--all]` +Сохранённые аккаунты пула ChatGPT доступны для просмотра даже на хостах без нативного каталога +Codex, в том числе в dashboard и при обновлении аккаунтов. Просмотр списка не создаёт каталог +Codex и не выполняет выход из нативного основного аккаунта. Отключение интеграции клиента Codex +не отключает наблюдение за квотой существующего нативного входа. Некорректный явно заданный +путь `CODEX_HOME` по-прежнему вызывает ошибку конфигурации. Небезопасное хранилище +`OPENCODEX_HOME` вызывает ошибку даже при отсутствии нативного каталога Codex. +При разрешении пути к каталогу Codex по умолчанию также возвращается ошибка, если он является файлом, ссылкой на отсутствующий объект или его родительский каталог не позволяет пройти по пути. + Без провайдера команда показывает пул Codex, OAuth-аккаунты и настроенные API-key pool'ы. Пустые провайдеры пропускаются, если не задан `--all`. С провайдером выводится только это семейство credential'ов. Human-output использует формат diff --git a/docs-site/src/content/docs/ru/reference/configuration/advisor.md b/docs-site/src/content/docs/ru/reference/configuration/advisor.md index 2cd0e396e13..7619ab6724c 100644 --- a/docs-site/src/content/docs/ru/reference/configuration/advisor.md +++ b/docs-site/src/content/docs/ru/reference/configuration/advisor.md @@ -1,6 +1,6 @@ --- title: Консультант -description: Принадлежащий OpenCodex sidecar экспертных консультаций — настроенная экспертная модель консультирует маршрутизируемых воркеров; политики manual и preflight. +description: Независимые консультации с политиками manual, preflight и adaptive на основе наблюдаемых данных. --- Консультант — независимая экспертная модель, которая анализирует задачу воркера и возвращает рекомендации. Консультацией владеет OpenCodex от начала до конца: прокси внедряет синтетический инструмент `advisor` в ход воркера, сам выполняет консультацию через штатный маршрутизирующий механизм и возвращает рекомендацию, чтобы исходный воркер продолжил работу. Воркеру не нужно делегировать, spawn-ить что-либо или держать провайдерские учётные данные. @@ -9,6 +9,8 @@ description: Принадлежащий OpenCodex sidecar экспертных ## Конфигурация +На странице **Advisor** выберите модель из каталога. Селектор сохраняет точные идентификаторы нативных моделей, провайдеров, аккаунтов, пользовательских моделей и combo. Отключённые модели и модели, ожидающие начального выбора, не предлагаются. Сохранённый идентификатор вне доступного списка остаётся видимым с предупреждением до замены или очистки. Ошибка загрузки сохраняет выбор и позволяет повторить попытку. Выберите модель для текстовых советов: каталог не гарантирует совместимость консультаций. CLI и API по-прежнему принимают строки моделей, поддерживаемые маршрутизатором. + ```json { "advisor": { @@ -26,11 +28,12 @@ description: Принадлежащий OpenCodex sidecar экспертных | `enabled?` | `boolean` | `false` | Главный выключатель. Выключено — ноль поведения консультанта на пути запроса. | | `model?` | `string` | — | Экспертная модель. Любая строка модели, которую принимает роутер: «голая» нативная модель (`gpt-6-astra`), явный `provider/model` (`anthropic/claude-sonnet-4-6`, `xai/grok-...`) или модель с квалификацией аккаунта. Полная поддержка межпровайдерных сценариев. | | `effort?` | `string` | `"max"` | Интенсивность рассуждений вызова консультанта (`low`–`ultra`). | -| `policy?` | `"manual" \| "preflight"` | `"manual"` | Когда консультировать. | +| `policy?` | `"manual" \| "preflight" \| "adaptive"` | `"manual"` | Когда консультировать. | +| `maxConsultationsPerTask?` | `integer` | `3` | Бюджет адаптивной задачи: 1–3 попытки, включая ручные вызовы. | | `timeoutMs?` | `number` | `120000` | Тайм-аут loopback-консультации. | | `contextSharingConsent?` | `"v1"` | нет | Согласие оператора отправлять контекст задачи настроенному провайдеру консультанта. Текущая версия только `"v1"`. Отсутствие, устаревшее или иное значение означает, что содержимое задачи не отправляется. `enabled: true` само по себе не согласие. | -Управляйте через страницу **Advisor** на дашборде или `ocx advisor status|on|off|set --model --effort --policy `. +Управляйте через страницу **Advisor** на дашборде или `ocx advisor status|on|off|set --model --effort --policy `. Без текущего согласия `ocx advisor on` не включает межпровайдерную отправку: команда печатает раскрытие и останавливается. `ocx advisor on --ack-context-sharing` и `ocx advisor consent` записывают `v1`. `ocx advisor consent --revoke` снимает согласие и сразу прекращает отправку. `ocx advisor set` согласие не выдаёт. Флажок на дашборде изначально снят. @@ -78,5 +81,13 @@ OpenCodex не вставляет в этот запрос ключи API про ## Ограничения PR1 - Нативные passthrough-ходы OpenAI (воркеры пула ChatGPT) не получают синтетический инструмент; поддержка консультанта покрывает маршрутизируемых (переведённых) провайдеров. Preflight-консультация применяется к run-turn-адаптерам; инструмент — нет. -- Нет адаптивного триггера: нет детекции застревания, анализа повторных сбоев, уровней эскалации, нескольких консультантов или голосования. Только `manual` и `preflight`. +- Нет семантической детекции застревания, итоговой проверки, уровней эскалации, нескольких консультантов или голосования. - Учётная книга дедупликации preflight живёт в процессе; после перезапуска прокси задача в работе может получить ещё одну preflight-консультацию. + +## Адаптивные консультации и журнал + +`adaptive` добавляет к начальной консультации обращение после неудачного исправления: та же проверка в том же каталоге не проходит, последующее редактирование файла успешно завершается, а новая проверка снова не проходит. Успех снимает состояние сбоя; проверка, начатая до редактирования, не считается повтором после исправления. Начальный этап и этап исправления дедуплицируются отдельно. Для автоматики нужен стабильный идентификатор беседы. Нужны парные вызовы/результаты инструментов и явный код выхода. Сложные скрипты, пакеты вызовов, правки через shell и неоднозначный вывод не запускают консультацию. + +Настройка: `ocx advisor set --policy adaptive --max-consultations 2`. Ручные и автоматические попытки делят бюджет 1–3 на задачу. До отправки сервер резервирует попытку в `OPENCODEX_HOME/advisor-budget.sqlite`; перезапуск и смена worker не сбрасывают счётчик. Хранятся только дайджесты задач, счётчики и сроки: 24 часа от первой попытки, максимум 512 задач. Если бюджет недоступен или исчерпан, worker продолжает. Уже отправленные ошибочные или отменённые вызовы не возвращают резерв, поскольку могли стоить денег. Без стабильного идентификатора остаются ручные вызовы с лимитом на запрос. + +Страница Advisor и `ocx advisor activity --json` (`GET /api/advisor/activity`) показывают до 100 записей метаданных процесса за 24 часа. Видимая страница обновляется каждые пять секунд. Записи включают причину, статус, модели, время, сообщённые токены ввода/вывода, запрошенный и переданный адаптером уровни рассуждения. Текст задачи, команды, советы и идентификаторы беседы не хранятся; перезапуск очищает журнал. Значение адаптера описывает отправленный параметр, а не внутренние рассуждения провайдера. Отсутствующие данные остаются неизвестными; принятие советов и точная стоимость не выводятся. diff --git a/docs-site/src/content/docs/ru/reference/configuration/providers.md b/docs-site/src/content/docs/ru/reference/configuration/providers.md index 70e46f22113..bba82ea2c7b 100644 --- a/docs-site/src/content/docs/ru/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ru/reference/configuration/providers.md @@ -12,6 +12,16 @@ description: Записи провайдеров, аутентификация, Эта оценка по умолчанию выключена: включите её для провайдера параметром `inferContextWindow: true`. Без него модель без окна использует включённый Context cap, затем 128k. +## GPT-6.1 Terra + +[GPT-6.1 Terra](https://openai.com/index/introducing-gpt-6-1-terra/) вышла 26 октября 2026 года как преемница GPT-5.6 Terra. `gpt-6.1-terra` доступна наряду с `gpt-5.6-terra`; модель по умолчанию и список подагентов не меняются. GitHub Copilot использует Responses. OpenRouter предлагает `openai/gpt-6.1-terra` с контекстом 1,050,000 и ценой $1.00 за вход / $5.00 за выход на миллион токенов. + +Нативный Codex: контекст по умолчанию 500,000, расширенный предел 872,000, effort по умолчанию `low`; уровни `low`, `medium`, `high`, `xhigh`, `max`, `ultra`. Нативный `ultra` передаётся по сети как `max`. Нативная строка предлагает только Fast. + +[OpenAI API](https://developers.openai.com/api/docs/models/gpt-6.1-terra): контекст 1,050,000, максимальный выход 128,000; текст и изображения на входе, текст на выходе; срез знаний 2026-06-01. Effort: `none`, `low`, `medium` (по умолчанию), `high`, `xhigh`, `max`. Эндпоинты: `v1/responses` и `v1/chat/completions`; вызов инструментов требует Responses. + +За миллион токенов: $1.00 вход, $0.05 чтение кеша, $1.25 запись кеша (30 минут), $5.00 выход. При входе свыше 272,000 токенов весь запрос тарифицируется с множителем 2× для входа и кеша и 1.5× для выхода: соответственно $2.00 / $0.10 / $2.50 / $7.50. Режим Fast стоит 2×. + ## Выбор моделей при первой регистрации Новое подключение без OAuth не публикует модели до получения достоверного списка. Если в Models не менее 20 уникальных строк моделей, все переключатели моделей изначально OFF, но сам провайдер остаётся ACTIVE. Подключения, фактически использующие OAuth или вход ChatGPT, сохраняют настройки по умолчанию. @@ -54,7 +64,7 @@ ocx models provider openrouter on | `codexWsReuseAcrossTurns?` | `boolean` | `false` | Необязательное повторное использование Codex WebSocket между ходами одного аккаунта и потока. По умолчанию выключено. Во включённом режиме занятый сокет ждут не дольше 750 мс, а при смене модели или уровня сохраняются до двух сокетов. | | `upstreamHostCircuitThreshold?` | `number` | `0` | Опциональный порог circuit breaker для доказанных DNS/TCP-сбоев до соединения в нативных OpenAI forward Responses- и compact-отправках. `0` отключает его; `1`–`20` открывает 30-секундный cooldown для provider-origin после такого числа завершившихся логических запросов. Пока circuit открыт, до выбора аккаунта и upstream-отправки возвращается `503` с `Retry-After`; после cooldown допускается один half-open запрос. Таймауты и HTTP-ответы не учитываются, а любой HTTP-ответ закрывает circuit. Применяется только к маршрутизации Codex Pool без закреплённого аккаунта; при `codexAccountMode: "direct"` и для селекторов с указанием аккаунта схема не активна. | | `modelCacheTtlMs?` | `number` | `300000` | Окно свежести для кэша `/models` на уровне провайдера. | -| `cacheRetention?` | `"none" \| "short" \| "long"` | `"short"` | Политика prompt-cache Anthropic: отключено, 5-минутный ephemeral или 1-часовой extended. | +| `cacheRetention?` | `"none" \| "short" \| "long"` | Anthropic напрямую: `"long"`; другие Anthropic-совместимые эндпоинты: `"short"` | Политика prompt-cache Anthropic: отключено, 5-минутный ephemeral или 1-часовой extended. Если значение не задано, запросы к `api.anthropic.com` (API-ключ или пулы OAuth-аккаунтов) используют TTL 1 час; совместимые шлюзы и эндпоинты вендоров остаются на 5 минутах. Явно заданное значение всегда имеет приоритет. | | `tokenGuardian?` | `OcxTokenGuardianConfig` | off | Необязательная политика proactive OAuth refresh и warmup'а аккаунтов Codex. | Во всех этих стратегиях эффективный порог аккаунта берётся из `codexAccountAutoSwitchThresholds`, если запись есть, иначе из глобального `autoSwitchThreshold`. Нулевое переопределение отключает только проактивное переключение по использованию; привязка при запуске, hard-lock, cooldown, проверка доступа к модели и восстановление после сбоев остаются обязательными. @@ -102,7 +112,9 @@ cross-route credential fallback не существует. Строки API GPT- | --- | --- | --- | | `adapter` | `string` | Один из `openai-chat`, `openai-responses`, `anthropic`, `google`, `kiro`, `cursor`, `ollama-native`, `azure-openai` (или alias `azure`). | | `baseUrl` | `string` | Базовый URL API upstream'а. Большинство built-in fixed-endpoint'ов игнорируют несовпадение; collision-safe key-preset'ы сохраняют старый custom destination с тем же именем. | -| `requestPacing?` | `{ enabled, requestsPerMinute?, minIntervalMs?, maxConcurrentRequests?, models? }` | Опциональное клиентское выравнивание начала исходящих запросов, отдельное от учёта использования, биллинга и индикаторов rate limit апстрима. `maxConcurrentRequests` — положительное целое, ограничивающее число запросов в работе; правило провайдера или модели может содержать только этот параметр. Лимит провайдера действует на все модели, а `models` сопоставляется с точными ID моделей апстрима и может увеличить задержку или уменьшить параллелизм. Ожидание очереди не расходует таймаут заголовков ответа. Поддерживаются HTTP и явные вызовы адаптеров `fetchResponse`/`runTurn`. При ограничении параллелизма канонический запрос Responses WebSocket использует HTTP/SSE, чтобы освободить слот после завершения, ошибки или отмены тела ответа. Для адаптеров `runTurn`, включая Cursor, лимит считает активные ходы, а не физические отправки: RunSSE и BidiAppend могут пересекаться внутри одного хода, пока следующий ход ждёт. Последующие отправки по-прежнему соблюдают интервалы запуска. | +| `requestPacing?` | `{ enabled, requestsPerMinute?, minIntervalMs?, maxConcurrentRequests?, models?, keySelection?, keys? }` | Опциональное клиентское выравнивание начала исходящих запросов, отдельное от учёта использования, биллинга и индикаторов rate limit апстрима. `maxConcurrentRequests` — положительное целое, ограничивающее число запросов в работе; правило провайдера или модели может содержать только этот параметр. Лимит провайдера действует на все модели, а `models` сопоставляется с точными ID моделей апстрима и может увеличить задержку или уменьшить параллелизм. Ожидание очереди не расходует таймаут заголовков ответа. Поддерживаются HTTP и явные вызовы адаптеров `fetchResponse`/`runTurn`. При ограничении параллелизма канонический запрос Responses WebSocket использует HTTP/SSE, чтобы освободить слот после завершения, ошибки или отмены тела ответа. Для адаптеров `runTurn`, включая Cursor, лимит считает активные ходы, а не физические отправки: RunSSE и BidiAppend могут пересекаться внутри одного хода, пока следующий ход ждёт. Последующие отправки по-прежнему соблюдают интервалы запуска. | +| `requestPacing.keySelection?` | `"active"`, `"round-robin"` | Выбор ключа для обычных запросов: `active` по умолчанию сохраняет выбранный ключ и переключение при ошибке; `round-robin` последовательно распределяет запросы между доступными ключами без изменения сохранённого выбора. Балансируются назначения, не токены или стоимость. | +| `requestPacing.keys?` | `{ requestsPerMinute?, minIntervalMs?, maxConcurrentRequests? }` | Общие для всех моделей ограничения RPM, интервала и параллелизма на ссылку ключа, вместе с лимитами провайдера и модели. Одинаковые ссылки делят ёмкость; разные ссылки одного проекта не означают независимые квоты. TPM и дневные квоты не контролируются. | | `responsesPath?` | `string` | Relative resource path для key-auth запросов `openai-responses`. Должен начинаться с `/` и не может содержать scheme, query или fragment. | | `chatCompletionsPath?` | `string` | Relative resource path для запросов `openai-chat`, зеркало `responsesPath` с теми же правилами формы. Нужен, когда один upstream обслуживает Chat Completions и Responses под разными префиксами: per-model wire override меняет адаптер и не трогает `baseUrl`, поэтому без него включённый Chat-запрос ушёл бы в Responses base. Поставляемый пример — Z.AI. | | `upstreamWebsocket?` | `boolean` | Необязательный upstream Responses WebSocket для запросов `openai-responses` (по умолчанию `false`). Учитывается только для first-party upstream `https://api.openai.com/v1`; конечные точки пользовательских провайдеров всегда используют ограниченный HTTP/SSE, поскольку Bun не может применить ограничение размера входящего сообщения WebSocket до выделения памяти под всё сообщение. У канонического провайдера ChatGPT `openai` пропуск сохраняет upstream WebSocket для подходящих ходов, `false` отправляет потоковые ходы по HTTP/SSE, а `true` отклоняется; при `false` нативное управление и внедрение в середине хода недоступны. Это поле не зависит от клиентской настройки `websockets` и не меняет ни конечную точку, ни учётные данные. Для HTTP остаётся SSE; пути, не относящиеся к Responses, и запросы `openai-chat` остаются на HTTP. | @@ -125,7 +137,7 @@ cross-route credential fallback не существует. Строки API GPT- | `modelAutoCompactTokenLimits?` | `Record` | Мягкие бюджеты автосжатия по моделям в виде положительных безопасных целых чисел. Они могут только уменьшать эффективную границу в 90 % контекста или максимального ввода и не выдаются, если эффективное окно контекста не определено. Для канонического `openai` ключами могут быть только точные поддерживаемые ID нативных моделей без префиксов провайдера или селектора аккаунта. PATCH провайдера объединяет записи: `null` для ключа удаляет его, а `null` для всего поля очищает карту. Такие маркеры `null` допустимы только в PATCH. | | `defaultMaxOutputTokens?` | `number` | Provider-wide fallback для `openai-chat`, когда клиент не передал `max_output_tokens`. | | `modelMaxOutputTokens?` | `Record` | Положительные fallback-budget'ы `openai-chat` по моделям; exact/pattern-match имеет приоритет над provider-default. | -| `modelCosts?` | `Record` | Отображаемые цены по моделям (USD за 1M токенов), ключ — точный upstream id модели этого провайдера (не идентификатор провайдера и не маршрутизируемая метка `provider/model`), значение — четыре поля: `input`, `output`, `cacheRead`, `cacheWrite` (пример: `{ "deepseek-v4-flash": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 } }`). Любой id допустим — кастомный провайдер может указывать на любой OpenAI-совместимый endpoint через адаптер `openai-chat`, а локальные и внутренние провайдеры работают даже без строки во встроенных каталогах. Пользовательские цены имеют приоритет над встроенными каталогами в оценках `~$` в Logs и Usage; исторические записи пересчитываются по текущему оверлею, поэтому изменение цены может сдвинуть прошлые суммы (порядок: пользователь → каталог jawcode → expected-price overlay → вендорская цена модели); явно заданный пользователем набор нулевых ставок означает известную нулевую оценку; удалите запись модели, чтобы восстановить автоматическую цену. Нулевые цены каталога по-прежнему переходят к следующему источнику. Каждая ставка должна быть неотрицательным конечным числом не более 1 000 000 (USD за 1M токенов); строки вне диапазона отклоняются на управляющей границе и отбрасываются при загрузке. Только оценка для отображения: оверлеи не влияют на маршрутизацию, выбор аккаунта, квоты или биллинг. | +| `modelCosts?` | `Record` | Отображаемые цены по моделям (USD за 1M токенов), ключ — точный upstream id модели этого провайдера (не идентификатор провайдера и не маршрутизируемая метка `provider/model`), значение — четыре поля: `input`, `output`, `cacheRead`, `cacheWrite` (пример: `{ "deepseek-v4-flash": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 } }`). Любой id допустим — кастомный провайдер может указывать на любой OpenAI-совместимый endpoint через адаптер `openai-chat`, а локальные и внутренние провайдеры работают даже без строки во встроенных каталогах. Пользовательские цены имеют приоритет над встроенными каталогами в оценках `~$` в Logs и Usage; исторические записи пересчитываются по текущему оверлею, поэтому изменение цены может сдвинуть прошлые суммы (порядок: пользователь → каталог jawcode → expected-price overlay → вендорская цена модели); явно заданный пользователем набор нулевых ставок означает известную нулевую оценку; удалите запись модели, чтобы восстановить автоматическую цену. Нулевые цены каталога по-прежнему переходят к следующему источнику. Каждая ставка должна быть неотрицательным конечным числом не более 1 000 000 (USD за 1M токенов); строки вне диапазона отклоняются на управляющей границе и отбрасываются при загрузке. Только оценка для отображения: оверлеи не влияют на маршрутизацию, выбор аккаунта, квоты или биллинг. | | `headers?` | `Record` | Дополнительные upstream-header'ы. Заголовки авторизации, cookie, API-key-header'ы, встроенные переводы строк и невалидные имена отклоняются. | | `openRouterRouting?` | `OpenRouterProviderRouting` | Предпочтения по умолчанию для OpenRouter (`order`, `only`, `allowFallbacks`); валидно только для канонического OpenRouter с `openai-chat`. | | `modelOpenRouterRouting?` | `Record` | Exact override по model id, которые полностью заменяют provider-wide preference для OpenRouter. | @@ -137,7 +149,7 @@ cross-route credential fallback не существует. Строки API GPT- | `modelReasoningEfforts?` | `Record` | Label'ы по отдельным моделям. Пустой список скрывает управление effort. | | `modelSupportsReasoningSummaries?` | `Record` | Установите `false` для модели, чтобы перестать рекламировать summary и вырезать поля доставки summary. | | `modelReasoningSummaryDelivery?` | `Record` | Responses delivery enum по моделям; переписывает уже существующее поле delivery. | -| `modelAdapters?` | `Record` | Wire-override по модели для `openai-chat` или `openai-responses` в gateway с несколькими wire-форматами. Явные записи имеют приоритет над default'ами registry; preset DeepSeek может выбирать native Responses для `deepseek-v4-flash`, а GitHub Copilot объявляет Responses-only default'ы для моделей (`gpt-5.3-codex`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`), потому что эти модели отклоняют `/chat/completions` для агентного трафика. Модели без встроенного default'а (например, `gpt-5.4-nano`) можно включить здесь. Single-wire upstream pin'ы и canonical ChatGPT forward override не принимают. | +| `modelAdapters?` | `Record` | Wire-override по модели для `openai-chat` или `openai-responses` в gateway с несколькими wire-форматами. Явные записи имеют приоритет над default'ами registry; preset DeepSeek может выбирать native Responses для `deepseek-v4-flash`, а GitHub Copilot объявляет Responses-only default'ы для моделей (`gpt-5.3-codex`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6.1-sol`, `gpt-6.1-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`), потому что эти модели отклоняют `/chat/completions` для агентного трафика. Модели без встроенного default'а (например, `gpt-5.4-nano`) можно включить здесь. Single-wire upstream pin'ы и canonical ChatGPT forward override не принимают. | | Opt-in xAI Responses (панель) | переключатель | Только для `xai`: атомарно задаёт или удаляет записи `modelAdapters` для `grok-4.5` и `grok-4.6`. Одна запись отображается как смешанное состояние до следующего переключения. Остальные override и поведение tier не меняются. Grok 4.7 по умолчанию использует Responses на OAuth через настройку реестра и может перейти на Chat через явную запись `modelAdapters["grok-4.7"] = "openai-chat"`. | | `xaiResponsesXSearch?` | `boolean` | По умолчанию отключено. Для назначения xAI Responses декларация `x_search`, размещённая у провайдера, добавляется только тогда, когда действующий инструмент `web_search` сохраняется после окончательной нормализации запроса. Существующие декларации не дублируются, селекторы вызывающей стороны `tool_choice`/`allowed_tools` никогда не расширяются, и эта настройка не связана с параметрами `search.xSearch` сайдкара веб-поиска. | | `modelPreferHostedTools?` | `Record` | Opt-in для точного model ID в non-forward Responses gateway, который резервирует namespace hosted tool. Сейчас допускается только `["image_generation"]`; совпавшая модель должна использовать wire `openai-responses` и поддерживать этот hosted tool. Прокси удаляет конфликтующие клиентские объявления `image_gen` и переписывает их selectors, сохраняя caller tool choice. Для виртуальных моделей OpenAI API `-pro` сначала сопоставляется выбранный публичный ID, а затем в качестве fallback используется ID базовой wire-модели. `modelAdapters` сначала разрешается по публичному ID, затем по базовому ID; второй результат определяет итоговый wire. Остальные модели сохраняют обычное alias-поведение. | @@ -151,12 +163,13 @@ cross-route credential fallback не существует. Строки API GPT- | `noStopModels?` | `string[]` | Модели, отвергающие переданный вызывающей стороной `stop`. Адаптер `openai-chat`, Chat passthrough и Responses passthrough не передают это поле для таких моделей. Встроенный пресет `xai` перечисляет здесь свои модели рассуждения (`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`), которые, по документации xAI, отвергают этот параметр; `grok-4.20-0309-non-reasoning`, `grok-composer-2.5-fast` сохраняют `stop` вызывающей стороны. | | `noPenaltyModels?` | `string[]` | Модели, отвергающие penalty presence/frequency. Встроенный пресет `xai` перечисляет здесь свои модели рассуждения (`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`), которые, по документации xAI, отвергают эти параметры; модели без рассуждения сохраняют penalty вызывающей стороны. | | `noStructuredOutputModels?` | `string[]` | Точные идентификаторы моделей, чей endpoint `openai-chat` отклоняет `response_format`. Поле опускается только при точном совпадении запрошенной модели; для остальных моделей `openai-chat` преобразование structured output остаётся включённым. | -| `noJsonSchemaModels?` | `string[]` | Точные идентификаторы моделей, чей endpoint `openai-chat` отклоняет `response_format` типа `json_schema`, но принимает `json_object`. Такой запрос понижается до `json_object`, а не отбрасывается, поэтому вызывающая сторона всё равно получает JSON. Если модель есть в обоих списках, побеждает `noStructuredOutputModels`. Пресеты `opencode go`, `opencode zen` и `opencode free` включают это для своих маршрутов DeepSeek. | +| `noJsonSchemaModels?` | `string[]` | Точные идентификаторы моделей, чей endpoint `openai-chat` отклоняет `response_format` типа `json_schema`, но принимает `json_object`. Такой запрос понижается до `json_object`, а не отбрасывается, поэтому вызывающая сторона всё равно получает JSON. Если модель есть в обоих списках, побеждает `noStructuredOutputModels`. Пресеты `opencode go` и `opencode zen` включают это для своих маршрутов DeepSeek. | | `foldDeveloperRoleToSystem?` | `boolean` | Принимает ли назначение `openai-chat` роль `developer`. Если `foldDeveloperRoleToSystem` не задан, сообщение уходит как `system`; при `true` — как `system`; при `false` — как `developer`. Не задано означает, что об этом назначении ничего не записано; `true` фиксирует вышестоящий сервис, который роль отклоняет; `false` — тот, который её принимает. В любом случае сообщение сохраняет свою позицию в разговоре, меняется только роль. Назначение, отклоняющее роль, отвечает `400 role 'developer' is not allowed`, и ход не начинается — поэтому незаписанное состояние по умолчанию свёрнуто. | | `parallelToolCalls?` | `boolean` | Переключатель parallel tool call'ов. Для OpenAI Chat по умолчанию включено; не-chat adapter'ы рекламируют это только при явном `true`. | | `responsesItemIdRepair?` | `{ message?: string[]; reasoning?: string[]; repairMissingTerminalIds?: boolean; repairInvalidIds?: boolean }` | По умолчанию выключенная downstream SSE-repair для exact placeholder-id, отсутствующих terminal-id и (с `repairInvalidIds`) message/reasoning id без канонического префикса `msg_`/`rs_`. Function-call id никогда не переписываются. Встроенный DeepSeek включает последние два по умолчанию. | | `responsesSnapshotRepair?` | `boolean` | По умолчанию выключенная клиентская repair для неполных lifecycle snapshot'ов Responses в SSE и JSON. Добавляет отсутствующие status, output и tool metadata, не меняя raw inspection и persistence. | -| `retryOn429?` | `{ enabled?: boolean; attempts?: number; intervalMs?: number; maxIntervalMs?: number; respectRetryAfter?: boolean }` | Только для провайдеров с API-ключом (`authMode: "key"`). Опциональный повтор при 429 на том же таргете: если `retryOn429` отсутствует, функция выключена; наличие объекта включает её, если только `enabled: false`. При 429: ожидание (`Retry-After` апстрима или фиксированный интервал) и повтор идентичного запроса на том же ключе до любого фейловера ключей — покрывает основной цикл восстановления текстовых ходов, passthrough-канал Responses, мост изображений/видео, sidecar web-search и терминальные продолжения. Повтор допустим только для HTTP 429, полученных до начала потока; пользовательские транспорты `runTurn` не входят в цикл HTTP-повторов. `attempts` — это число повторов на том же ключе после первого 429 (всего отправок = `attempts` + 1) и единый бюджет на запрос, общий для основного цикла восстановления, терминального продолжения и повторов моста. Исчерпание `attempts` лишь останавливает дальнейшие повторы на том же ключе; далее применяется обычный фейловер ключей или финальная обработка ошибки в зависимости от доступных таргетов — на passthrough-канале с ключевой аутентификацией фейловера нет, поэтому исчерпанный 429 возвращается как есть. Codex сам никогда не повторяет 429, поэтому это единственная защита для провайдеров с одним ключом. По умолчанию: `enabled: true`, `attempts: 3`, `intervalMs: 5000`, `maxIntervalMs: 60000` (любое ожидание ограничено `maxIntervalMs`, который сам ограничен 600000), `respectRetryAfter: true`. | +| `promptCacheKey?` | `boolean` | Включает передачу `prompt_cache_key` для всего провайдера `openai-chat`. Адаптер передаёт полученный ключ, не создавая свой, но перевод Claude Messages может вывести его из `metadata.user_id` либо из сочетания модели/системы/инструментов без метаданных. По умолчанию выключено, если пресет не указывает иное; Mistral включает по своему [контракту кэширования](https://docs.mistral.ai/studio/conversations/advanced/prompt-caching), явное `false` отключает. Включайте только при документированной поддержке апстримом: строгий шлюз может отклонить неизвестное поле с HTTP 400. | +| `retryOn429?` | `{ enabled?: boolean; attempts?: number; intervalMs?: number; maxIntervalMs?: number; respectRetryAfter?: boolean }` | Только API-ключи (`authMode: "key"`). Без настройки выключено, кроме канонических OpenCode Go и Command Code и канонического Mistral с `openai-chat`: 6 повторов, интервал 10 с, предел 60 с. Объект включает повторы; `enabled: false` отключает и эти значения по умолчанию. При HTTP 429 до начала потока прокси ждёт `Retry-After` или фиксированный интервал и повторяет тот же запрос с тем же ключом до переключения ключей. Охватывает восстановление текстового хода, Responses passthrough, мосты изображений/видео, web-search и терминальные продолжения; пользовательский `runTurn` исключён. `attempts` — число повторов после первого 429 (всего отправок = `attempts` + 1), общий бюджет всего запроса. Исчерпание прекращает только повторы с тем же ключом, затем применяется обычное переключение или окончательная ошибка; passthrough с ключом не переключается и возвращает 429 как есть. Для канонического Mistral Chat явная задержка сверх разрешённого ожидания прекращает повтор с тем же ключом и сохраняет исходный `Retry-After`; другой ключ пула ещё может обслужить запрос. У остальных провайдеров задержка ограничивается пределом. Mistral разделяет сроки между вызовами с одинаковыми учётными данными и моделью, кроме `requestPacing.enabled: false` или `retryOn429.respectRetryAfter: false`. 429 без времени повтора при подтверждённом дефиците минутного бюджета запускает консервативное ожидание 60 с и одну пробную попытку; успех возвращает обычный трафик. Здоровый трафик не предсказывает следующую стоимость токенов. Политика использует заявленные лимиты, исключает известное исчерпание месячных лимитов, не обещает точное время сброса и не запускает сжатие истории. Сам Codex не повторяет 429. Значения объекта по умолчанию: `enabled: true`, `attempts: 3`, `intervalMs: 5000`, `maxIntervalMs: 60000` (предел ожидания, максимум настройки 600000), `respectRetryAfter: true`. | | `transientRetryOn5xx?` | `{ enabled?: boolean; attempts?: number }` | Только для провайдеров `openai-chat` и `openai-responses` с аутентификацией по ключу. Провайдеры с `authMode: "forward"` (пул аккаунтов ChatGPT) никогда не читают эту настройку и сохраняют число повторов по умолчанию. Опциональный повтор при временных статусах апстрима до начала потока (500, 502, 503, 504, 520, 521, 522): если параметр отсутствует, функция выключена; наличие объекта включает её, если только `enabled: false`. Покрывает исходный запрос `Responses`, продолжение терминального предохранителя, нативный `/v1/chat/completions`, а также повторные запросы при восстановлении после 429 или ошибки учётной записи. `attempts` — ОБЩЕЕ число разрешённых отправок в апстрим для одного запроса, включая первую (1..10, по умолчанию 3). Это единый бюджет на запрос, общий с восстановлением после сброса соединения, поэтому `3` означает, что до провайдера дойдут не более трёх реальных запросов. Ожидание использует экспоненциальную задержку с фиксированной начальной величиной 400 мс, ограниченную 5 с, и учитывает `Retry-After`. Параметр не связан с `retryOn429`, который обрабатывает ограничение частоты запросов; сбои после начала потока никогда не воспроизводятся. Для каждой цели Combo первая заранее зарезервированная отправка входит в настроенный итог этой цели: `attempts: 1` допускает одну отправку, без возврата резерва ради дополнительного лимита и без расширения общего потолка. Локальный отказ или отмена до отправки освобождает неиспользованный резерв. | | `retryOnReset?` | `{ enabled?: boolean; replacements?: number }` | Для нативных провайдеров `openai-responses`, включая `authMode: "forward"`, а также для общего пути отправки переведённых запросов Responses до получения заголовков от upstream. Необязательная замена отправки, которая завершилась неудачей, когда вызывающая сторона ещё ничего не наблюдала: если параметр отсутствует, функция выключена; наличие объекта включает её, если только `enabled: false`. Покрывает обе неоднозначные стадии — обрыв соединения до любого заголовка ответа и обрыв тела SSE после заголовка, когда оно несло только управляющие события. Так же покрывается upstream WebSocket canonical ChatGPT, который закрылся или завершился ошибкой после отправки кадра create и до любого события Responses; замена в этом случае отправляется по HTTP. Заменяется только самодостаточный запрос: `store: false`, полный `input`, отсутствие `previous_response_id`, `conversation` и `stream_id`, и только инструменты, исполняемые клиентом. `replacements` — число замещающих отправок, которые ОДИН логический запрос может сделать по всем участкам и всем дочерним запросам комбо (1..2, по умолчанию 1). Это не число повторов на участок и не бюджет отправок, поэтому замена всё равно должна поместиться в уже имеющийся у участка лимит отправок. Запрос, который уже выдал вывод или вызов инструмента, не заменяется никогда, каким бы ни было это значение. Замещающий вывод модели всё равно может быть оплачен, если источник уже начал первый, поэтому параметр выключен по умолчанию. Общий путь перевода поддерживает замену при сбросе соединения до заголовков для первоначальных и заново сформированных отправок, используя то же разрешение и бюджет отправок. Транспорты, управляемые самими адаптерами, и сбои переведённого потока после получения заголовков от upstream исключены. | | `autoToolChoiceOnlyModels?` | `string[]` | Модели, у которых `tool_choice` принимает только `auto` или `none`; forced choice понижается. | @@ -170,8 +183,8 @@ cross-route credential fallback не существует. Строки API GPT- | `anthropicEofTolerance?` | `boolean` | Позволяет Anthropic-совместимому шлюзу завершить поток до `message_stop`, только если получен видимый текст или полный JSON-объект аргументов инструмента. По умолчанию выключено. | | `googleMode?` | `"ai-studio" \| "vertex" \| "cloud-code-assist"` | Режим транспорта/аутентификации Google. По умолчанию `ai-studio`. | | `googleToolSchemaPolicy?` | `"compatible" \| "reject-lossy"` | Только Google. Отсутствие поля или `compatible` сохраняет совместимую схему и существующее недиректное восстановление после 400. `reject-lossy` отклоняет начальную потерю или неопределённый результат ограниченного сравнения до отправки и блокирует восстановление Vertex или Cloud Code Assist, которое сняло бы ограничения. Прямой AI Studio такое восстановление не выполняет. | -| `project?` | `string` | Идентификатор проекта Vertex или Antigravity Cloud Code Assist. | -| `location?` | `string` | Локация Vertex; fallback через окружение — `GOOGLE_CLOUD_LOCATION`. | +| `project?` | `string` | Идентификатор проекта Vertex или Antigravity Cloud Code Assist. Если он задан, API-ключи Vertex используют endpoint проекта; без него — режим express, даже если задан `GOOGLE_CLOUD_PROJECT`. ADC использует fallback `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT`. Значение `"auto"` заставляет каждый API-ключ Vertex определять собственный проект (успешный результат кешируется для каждого ключа; ключ, для которого определение не удалось, проверяется снова при следующем запросе), поэтому один пул ключей может охватывать несколько проектов; ADC отклоняет `"auto"`. | +| `location?` | `string` | Локация Vertex. Endpoint проекта для API-ключа по умолчанию использует `global` и игнорирует `GOOGLE_CLOUD_LOCATION`; ADC требует это поле или fallback `GOOGLE_CLOUD_LOCATION`. | | `mcpServers?` | `Record` | Только Cursor: MCP-серверы в режимах stdio или Streamable HTTP. | | `desktopExecutor?` | `DesktopExecutorConfig` | Только Cursor: команды внешнего computer-use и record-screen. | | `unsafeAllowNativeLocalExec?` | `boolean` | Legacy boolean Cursor, эквивалентен `nativeLocalExec: "on"` только если новое поле не задано. | @@ -658,3 +671,8 @@ base- и Pro-id с context `1050000` и max input `922000`; OpenRouter зара ### `anthropicAccountPool.routes` `anthropicAccountPool.routes` привязывает модель к сохранённым ID учётных записей Anthropic OAuth. При включённом пуле первое совпадение полного шаблона `match` с учётом регистра ограничивает начальный выбор и повтор после 429. `fallback: true` расширяет выбор только при отсутствии доступных учётных записей маршрута. + + +### `promptLengthPricing` + +Необязательное поле `promptLengthPricing` дополняет четыре базовых тарифа. `mode: "automatic"` (или отсутствие поля) сохраняет контекстные правила каталога; `mode: "flat"` отключает их. Для `mode: "custom"` задайте положительное безопасное целое `thresholdTokens`, `comparison`: `"gt"` (>) или `"gte"` (≥), и четыре абсолютных альтернативных тарифа в `rates`. Порог учитывает все токены промпта, включая чтение и запись кэша, но исключает вывод. Выбранные тарифы применяются ко всему запросу, включая вывод, и заменяют правило каталога по обе стороны порога. Корректировки Fast/Priority применяются один раз после выбора тарифов и следуют опубликованному в каталоге соотношению Priority для этой модели; оценка помечается как нижняя граница, только если каталог объявляет соотношение `lower-bound` (сейчас это xAI Grok); для модели без правила в каталоге множитель Priority применяется один раз. Настройка доступна в **Models → Price**. **Reset to automatic** удаляет всё переопределение, включая базовые тарифы и правило. Старые конфигурации совместимы; настройки относятся к отображаемым оценкам. diff --git a/docs-site/src/content/docs/ru/reference/proxy-formats.md b/docs-site/src/content/docs/ru/reference/proxy-formats.md index ad8d9d3f3b8..3f1bcae5e90 100644 --- a/docs-site/src/content/docs/ru/reference/proxy-formats.md +++ b/docs-site/src/content/docs/ru/reference/proxy-formats.md @@ -42,6 +42,8 @@ Endpoint'ы Chat Completions и Messages поддерживают отложен Отдельно: на каждом входящем канале вызов провайдера в пространстве имён `default.` нормализуется обратно к «голому» имени инструмента из запроса (`default.Read` → `Read`). +Нативный Messages также учитывает встроенные определения инструментов в системных сообщениях при `tools: []`. Эти имена можно вызывать до постоянного удаления; другие клиентские инструменты не разрешаются. Вызовы размещённых MCP и потоки аргументов серверных инструментов сохраняют нативный формат. + ## Обзор endpoint'ов | Клиентская поверхность | Endpoint | Успешный non-stream результат | Успешный результат потока или сокета | diff --git a/docs-site/src/content/docs/tr/getting-started/quickstart.md b/docs-site/src/content/docs/tr/getting-started/quickstart.md index baa5172c95b..27e9f1d95a7 100644 --- a/docs-site/src/content/docs/tr/getting-started/quickstart.md +++ b/docs-site/src/content/docs/tr/getting-started/quickstart.md @@ -14,7 +14,7 @@ ocx init `ocx init` adım adım size rehberlik eder: -1. **Bir sağlayıcı seçin** — yerleşik kayıt defterindeki 104 önayardan birini +1. **Bir sağlayıcı seçin** — yerleşik kayıt defterindeki 106 önayardan birini veya bir temel URL ile adaptör yazmak için `custom` seçeneğini belirleyin. 2. **API anahtarı** — bir anahtar yapıştırın veya `${ANTHROPIC_API_KEY}` gibi bir ortam değişkenine başvurun. diff --git a/docs-site/src/content/docs/tr/guides/chatgpt-desktop.md b/docs-site/src/content/docs/tr/guides/chatgpt-desktop.md new file mode 100644 index 00000000000..847b14d27f0 --- /dev/null +++ b/docs-site/src/content/docs/tr/guides/chatgpt-desktop.md @@ -0,0 +1,143 @@ +--- +title: ChatGPT Desktop app-server shim (deneysel) +description: Paketlenmiş app-server stdout akışındaki normal kota geçidi alanlarını yeniden yazan, isteğe bağlı macOS deneyi. +--- + +Bu deney **yalnızca macOS içindir ve varsayılan olarak kapalıdır**. Paketlenmiş +ChatGPT app-server'ın JSON-RPC stdout akışını filtreleyerek bilinen normal kota +geçitlerini açar. Hesabın kotasını artırmaz; üst servisin reddettiği isteği kabul +etmesini sağlamaz. + +OpenCodex `config.json` dosyanızda etkinleştirin: + +```json +{ + "chatgptDesktop": { "appServerShim": true } +} +``` + +Ardından çalıştırın: + +```bash +ocx chatgpt launch +ocx chatgpt status +``` + +`launch`, OpenCodex yapılandırma dizininde çalıştırılabilir bir başlatıcı oluşturur, +ChatGPT açıksa kapatır ve `open -a --env CODEX_CLI_PATH=` ile +yeniden açar. Uygulama `com.openai.codex` paket kimliğiyle bulunur; `~/Applications` +veya başka diskteki kurulumlar da çalışır, “ChatGPT” adını paylaşan başka bir +uygulama kapatılmaz veya açılmaz. Önce devam eden işinizi kaydedin: uygulama +yeniden başlatılır. Çalışan OpenCodex proxy'si gerekmez. + +Başlatıcıyı kaldırıp geçersiz kılma olmadan yeniden açmak için: + +```bash +ocx chatgpt restore +``` + +Geri yükleme yapılandırma bayrağını değiştirmez. Sonraki açık shim başlatmalarını +kapatmak için `chatgptDesktop.appServerShim` değerini `false` yapın veya kaldırın. +Dock/Spotlight üzerinden normal başlatmalar shim'i otomatik uygulamaz. + +ChatGPT kurulu değilse (`com.openai.codex` paketi bulunamazsa) `restore` yalnızca +başlatıcıyı kaldırır; yeniden açabileceği uygulama olmadığından hatayla çıkar. + +## Yeniden yazma sınırı + +Yalnızca `account/rateLimits/updated` bildirimleri ve üst düzey sonucunda +`rateLimits`, `rateLimitsByLimitId` veya `ordinaryUsageAllowed` bulunan yanıtlar +uygundur. Normal `rate_limit_reached` işaretleri temizlenir. Bilinen kota bayrakları +(`allowed`, `limit_reached` / `limitReached`, `ordinaryUsageAllowed`) yalnızca normal +kota kanıtıyla açılır: temizlenmiş normal sınır türü veya %100 dolmuş pencere. +Yükün açıklamadığı bir nedenle kapalı olan bayrak kapalı kalır. Çalışma alanı, +kredi, bilinmeyen sınır türü ve harcama denetimi kısıtlamaları geçidi kapalı tutar. + +Gösterilen kullanım değişmez: yüzdeler, sıfırlanma zamanları, pencere süreleri, +plan bilgileri ve diğer gösterim alanları alındığı gibi kalır. İlgisiz JSON-RPC +iletileri, iç içe araç çıktıları, konuşma gönderim engeli meta verileri ve bozuk +satırlar aynen geçer. Yalnızca değişen satırlar yeniden serileştirilir; diğer +baytların kodlaması ve satır sonları korunur. Stdin, stderr ve gerçek dosyanın +çıkış durumu uygulamaya doğrudan bağlı kalır. + +## Çalıştırılabilir dosya ve ortam güvenliği + +Oluşturulan başlatıcının izin modu `0755` olur; mevcut OpenCodex çalıştırılabilir +dosyasını ve kaynak kurulumlarında CLI giriş yolunu içerir. `CODEX_CLI_PATH`, +ChatGPT'nin paketlenmiş dosyayı doğrudan çalıştırmak yerine başlatıcıyı kullanmasını +sağlar. Başlatıcıyı, yapılandırma dizinini ve OpenCodex kurulumunu kontrolünüzde +tutun: bu yolları değiştirmek uygulamanın çalıştırdığı kodu değiştirir. Başlatıcı +yine bulunan paketin dosyasını `exec` ile çalıştırır. Pakette app-server dosyası +yoksa `launch` başlatıcı yazmak yerine reddeder. + +Yazmadan önce `launch`, paketin ve app-server dosyasının size veya root'a ait +olduğunu, grup/diğer kullanıcılar tarafından yazılamadığını ve OpenAI takım +kimliği (`2DC432GLL2`) altında katı kod imzası doğrulamasından geçtiğini kontrol +eder. Başka hesaba ait paketler dahil kontrollerden geçemeyen paket reddedilir. +Başlatıcı önce geçici dosyaya yazılıp yeniden adlandırılır; hedefteki sembolik +bağlantı izlenmez, değiştirilir. + +`restore`, uygulamayı kapatmadan/açmadan önce pakete ve ana uygulama dosyasına +aynı sahiplik, izin ve imza kontrollerini uygular. Deney bayrağı kapalıyken ve +paket içinde app-server yokken de çalışır. Güven doğrulaması veya yeniden açma +başarısızsa mevcut başlatıcı kurtarma için korunur. + +İki komut da paketin üst klasörlerini dosya sistemi köküne kadar denetler. Başka +hesaba ait klasörler, sembolik bağlantılar ve sıradan grup/herkes yazılabilir +üst klasörler reddedilir. Root'a ait yönetici grubu kurulum klasörleri ve güvenilir +sticky klasörler normal izin davranışlarını korur. Bunlar sahiplik, POSIX izni ve +imza kontrolleridir; yerel ACL ve disk sahiplik ilkesi davranışı doğrulanmamıştır. + +Entegrasyon sertifika, ağ dinleyicisi, PAC veya arka plan izleyicisi kurmaz. +Uygulama iletilerini/ortamını kaydetmez. Durum çıktısı, çalışan ChatGPT paket +işleminin beklenen başlatıcı geçersiz kılmasını taşıyıp taşımadığını bildirir. + +Uygulama keşfi veya işlem incelemesi başarısızsa `status`, `not running` yerine +`unknown` bildirir. `launch` ve `restore`, çıktığı doğrulanamayan uygulamayı +yeniden açmayı reddeder; restore başlatıcıyı korur. İnceleme hatasını giderip +yeniden deneyin. Launch yazmadan önce kontrol etse de atomik güncelleme sonrasında +başka bir inceleme başarısız olabilir; komutlar bütünüyle başarılı ya da bütünüyle +etkisiz bir yeniden başlatma işlemi garanti etmez. + +## Hata davranışı ve bilinen sınırlar + +Platform macOS değilse, OpenCodex çalışma zamanı eksikse veya filtre öz sınaması +başarısızsa başlatıcı özgün dosyayı stdout'u değiştirmeden çalıştırır. Paketlenmiş +app-server dosyasının eksikliği istisnadır: geri dönülecek dosya olmadığı için +başlatıcı hatayla çıkar. + +Öz sınamayı geçen filtre oturum ortasında çökerse boru kapanır. Paketlenmiş +app-server'ın bundan sonra ne yaptığı doğrulanmamıştır; SIGPIPE/yazma hatası +alabilir ve Desktop tarafından aynı başlatıcıyla yeniden başlatılabilir. Filtrenin +doğrudan geçiş modu bunu çıkış/çökme durumuyla sınırlar: yeniden yazma istisnası +satırı aynen geçirir, beklenmedik yeniden yazma mekanizması hatasıysa kalan akışı +ham baytlara geçirir. + +Deney; paketlenmiş dosya yoluna, uygulamanın `CODEX_CLI_PATH` değerini kullanmasına +ve güncel RPC alan biçimlerine bağlıdır. Güncellemeler bunları değiştirebilir. +OpenCodex taşınır veya kaldırılırsa başlatıcının ön kontrolü başarısız olur ve +özgün dosya çalışır. Kurulumu taşıdıktan sonra `ocx chatgpt launch` komutunu yeniden çalıştırın. + +Uygulama güncellemesi paketlenmiş app-server dosyasını taşır veya kaldırırsa +başlatıcı onu çalıştıramaz: stderr üzerinde `ocx chatgpt launch` ve +`ocx chatgpt restore` komutlarını belirten iletiyle çıkar. Bunlardan birini +çalıştırana kadar Desktop app-server'ını başlatamaz. 8 MiB'den uzun tek çıktı +satırı tamponlanmak yerine ayrıştırılmadan geçirilir. + +Bu bağımsız shim konuşma meta verilerini yeniden yazmaz veya model çağrılarını +yönlendirmez. Diğer uygulama geçitleri veya üst servis retleri gönderimi hâlâ +engelleyebilir. Başlatıcının bağlı olması veya öz sınamanın geçmesi, uygulamanın +geçit verilerinin bu borudan geçtiğini kanıtlamaz. + +[#6196](https://github.com/lidge-jun/opencodex/issues/6196) üzerindeki 2 ve 13 Ekim +2026 tarihli bildirimler, doğal kota tükenmesinde shim bağlıyken Gönder düğmesinin +hâlâ kapalı kaldığını anlatır. Desktop 26.1007.30912 ve OpenCodex 2.84.0 kullanan +bildirici, arayüzün ayrı kullanım aktarımı kullanması nedeniyle uygun hız sınırı +RPC iletisi gözlemlememiştir. Shim stdout filtresine ulaşmayan veriyi yeniden +yazamaz. Bunlar derlemeye özgü saha bildirimleridir; yeni bir canlı doğrulama veya +her Desktop derlemesinin aynı aktarımı kullandığının kanıtı değildir. + +Oturum açıkken mesaj düzenleyicinin kalan davranışı #6196 altında izlenir. Birleşik +shim/yakalama yapılandırmasının önceki başarısı, shim'in tek başına yeterli +olduğunu göstermez. Kapatılmış ve birleştirilmemiş #6365 yakalama çalışması +yayımlanmış bir çözüm değildir. diff --git a/docs-site/src/content/docs/tr/guides/claude-code.md b/docs-site/src/content/docs/tr/guides/claude-code.md index 82340db7c25..3b6b953d344 100644 --- a/docs-site/src/content/docs/tr/guides/claude-code.md +++ b/docs-site/src/content/docs/tr/guides/claude-code.md @@ -191,22 +191,22 @@ unknown, ayarların hâlâ opencodex vekiline işaret edip etmediğinin belirlen ### Picker modu: first-party Code sekmesinde opencodex modelleri -Picker modu first-party modunun bir parçasıdır. macOS'ta first-party seçildiğinde varsayılan olarak +Picker modu first-party modunun bir parçasıdır. macOS ve Windows'ta first-party seçildiğinde varsayılan olarak açıktır; `claudeCode.intercept.picker: false` ayarlanırsa kapalı kalır. First-party Desktop'ın Code sekmesindeki model seçiciyi değiştirerek kullanılabilir opencodex modellerini adlarıyla listeler. -İlk etkinleştirmede macOS, giriş anahtar zincirinde yerel bir sertifika yetkilisine güvenmenizi isteyebilir. +İlk etkinleştirmede macOS, giriş anahtar zincirinde, Windows ise geçerli kullanıcının kök deposunda yerel bir sertifika yetkilisine güvenmenizi isteyebilir. Bu yetkili `claude.ai` ve alt alan adlarıyla sınırlıdır. Dışa aktarılabilir imzalama kimliği işletim sisteminin kimlik bilgisi deposunda korunur ve normal yeniden başlatmalarda aynı sertifika ve anahtar yeniden kullanılır. OpenCodex yapılandırma dizininde düz metin picker imza anahtarı saklanmaz. Kısıtlı CA'nın tam doğrulaması ve -sistemdeki güven denetimi devam eder. Onaylanmış kimlik aynıysa ve depo erişilebilirse yeniden başlatma, -sertifika güven ayarlarını eklemez veya kaldırmaz. Başlangıçta geri yükleme hiçbir zaman güven yüklemez: +sistemdeki güven denetimi devam eder (Windows'ta tam parmak izi ve doğrulanmış yaprak ile). Onaylanmış kimlik aynıysa ve depo erişilebilirse yeniden başlatma, +macOS sertifika güven ayarlarını eklemez veya kaldırmaz. Başlangıçta geri yükleme ve durum denetimi hiçbir zaman güven yüklemez: güven eksik, iptal edilmiş veya bilinmiyorsa picker beklemede kalır. Güven vermek için açıkça -`ocx claude desktop picker on` veya `ocx claude desktop picker trust` komutunu çalıştırın. +`ocx claude desktop picker on` veya `ocx claude desktop picker trust` komutunu çalıştırın, gerekiyorsa Windows onayıyla birlikte. Eski bir kimlikten bir defalık geçişte önceki güveni kaldırmak için onay gerekebilir. Temizlik tamamlanana kadar picker kullanılamaz ve uygulanmış profil şifre çözmeyen bir aktarma kullanır. macOS ayrıca anahtar zincirinin kilidini açmanızı veya uygulamanın kimlik bilgilerine erişimini onaylamanızı isteyebilir; -bu istemler yeniden başlatma veya güncelleme sonrasında da çıkabilir. Windows ve Linux'ta picker desteklenmez: +bu istemler yeniden başlatma veya güncelleme sonrasında da çıkabilir. Windows'ta açık `on` veya `trust` kök ekleme onayını gösterebilir. Linux'ta picker desteklenmez: picker CA, kimlik bilgisi deposu veya proxy işlemleri başlatılmaz. Ana Claude yakalama işlevi kullanılabilir; yerel CA dosyaları için sahiplik, sembolik bağlantı, dosya izinleri ve Windows ACL denetimleri uygulanır. diff --git a/docs-site/src/content/docs/tr/guides/codex-app-models.md b/docs-site/src/content/docs/tr/guides/codex-app-models.md index e270fea3574..6cf1ba16587 100644 --- a/docs-site/src/content/docs/tr/guides/codex-app-models.md +++ b/docs-site/src/content/docs/tr/guides/codex-app-models.md @@ -152,11 +152,12 @@ meta verileri sağladığı GPT-5.6 için kullanılır. | Rota | Seçici kimlikleri ve katalog meta verileri | | --- | --- | -| Codex girişi (hesap nitelikli satırlar devre dışı) | `gpt-5.6-sol`, `gpt-5.6-terra` ve `gpt-5.6-luna` gibi yalın yerel kimlikler; Pool veya Direct `codexAccountMode` aracılığıyla seçilir. GPT-5.6 satırları 922.000 tokenlik bir katalog penceresi kullanır. | +| Codex girişi (hesap nitelikli satırlar devre dışı) | `gpt-5.6-sol`, `gpt-5.6-terra` ve `gpt-5.6-luna` gibi yalın yerel kimlikler; Pool veya Direct `codexAccountMode` aracılığıyla seçilir. GPT-5.6 satırları 922.000 tokenlik bir katalog penceresi kullanır. `gpt-6.1-terra`: varsayılan bağlam 500,000 / uzun bağlam 872,000. | | Codex girişi (uygun seçicilerle hesap nitelikli satırlar etkin) | Uygun seçici ve desteklenen yerel model başına bir `/` satırı; her satır yalnızca eşlenen hesabını kullanır ve yalın yerel satırlar seçiciden gizlenir. Yerel meta veriler ve bağlam pencereleri korunur. | | Codex girişi (açık Daybreak iletme satırı) | Yalnızca tam `customModels` satırı kurallı `openai` sağlayıcısında yapılandırıldığında `openai/gpt-daybreak-blue-latest`. Daybreak hat kimliğini korur ve sabitlenmiş Sol yetenek anlık görüntüsünü kullanır (922.000 bağlam; 829.800 otomatik sıkıştırma). | -| OpenAI (API anahtarı) | Tam olarak on ad alanlı satır: `gpt-5.5`, `gpt-5.6`, Sol/Terra/Luna, üç `*-pro` sanal kimliği ve iki Daybreak takma adı (onunun tümü için 922.000 bağlam; 922.000 maksimum girdi) | +| OpenAI (API anahtarı) | Ad alanlı modeller arasında `gpt-5.5`, `gpt-5.6`, Sol/Terra/Luna, `*-pro` sanal kimlikleri ve Daybreak takma adları bulunur (1,050,000 bağlam; 922,000 maksimum girdi). `openai-apikey/gpt-6.1-terra` için bağlam 1,050,000, maksimum çıktı 128,000’dır. | | OpenRouter | `openrouter/openai/gpt-5.6-sol`, `openrouter/openai/gpt-5.6-terra`, `openrouter/openai/gpt-5.6-luna` (922.000) | +| OpenRouter | `openrouter/openai/gpt-6.1-terra` (1,050,000) | | Cursor | Statik geri dönüş `cursor/gpt-5.6-sol`, `cursor/gpt-5.6-terra` ve `cursor/gpt-5.6-luna` (1.000.000) ile Grok 4.5, 4.6 ve 4.7 için normal/Fast satırları (500.000) içerir; 4.6 ve 4.7 ayrıca `xhigh` sunar. Canlı hesap keşfi hangi satırların görünür kalacağını belirler. | | xAI | Canlı keşif yetkilidir. Geri dönüş kataloğu `xai/grok-4.6` ve `xai/grok-4.7` içerir; varsayılan model `xai/grok-4.5` olarak kalır. Üçünün de bağlam penceresi 500.000 tokendir. Grok 4.6 ve 4.7 `low` / `medium` / `high` / `xhigh` (yukarı akış varsayılanı: `high`) sunarken, Grok 4.5 `high` ile durur. | diff --git a/docs-site/src/content/docs/tr/guides/combos.md b/docs-site/src/content/docs/tr/guides/combos.md index d2c3be63d49..e2063712afc 100644 --- a/docs-site/src/content/docs/tr/guides/combos.md +++ b/docs-site/src/content/docs/tr/guides/combos.md @@ -230,7 +230,7 @@ ikiye ayrılır. | HTTP 401, 403, 404, 408, 429 veya herhangi bir 5xx | Hedefi soğutun ve bir sonraki uygun hedefe atlayın. | | Sınıflandırılmış kimlik doğrulama, abonelik, kota, hız sınırı, aşırı yük veya yukarı akış sunucu hatası | Yalnızca durum yeterli olmadığında bile hedefi soğutun ve atlayın. | | İstemci iptali (499), `origin_rejected`, siber politika reddi, bağlam taşması veya diğer geçersiz istek | Durun ve hatayı döndürün; başka bir hedef isteği geçerli kılmaz. | -| `user` alanını açıkça reddeden, `reasoning.effort`/`reasoning_effort` için desteklenmeyen değer bildiren veya modele özgü görüntü girdisini reddeden (`param: input`) yapılandırılmış HTTP 400 | Çıktı başlamadan önce bekleme süresi kaydetmeden sonraki uygun hedefe atlar; aşağıdaki isteğe bağlı parametre uyumluluğuna bakın. | +| `user` alanını açıkça reddeden, `reasoning.effort`/`reasoning_effort` için desteklenmeyen değer bildiren, modele özgü görüntü girdisini reddeden (`param: input`) veya rotanın okuyamadığı girdi medyasını reddeden (`unsupported_input_modality`) yapılandırılmış HTTP 400 | Çıktı başlamadan önce bekleme süresi kaydetmeden sonraki uygun hedefe atlar; aşağıdaki isteğe bağlı parametre uyumluluğuna bakın. | | Süreç içi bir bağdaştırıcının (`runTurn`) yürüttüğü Responses turunda, geçerli isteğin bildirmediği ilk araç çağrısı (herhangi bir çıktıdan ve yeniden oynatılamaz yan etkiden önce) | Hedefi bekleme süresine alır ve aynı araç kataloğuyla sonraki hedefe atlar. Görünür çıktıdan veya yeniden oynatılamaz bir yan etkiden sonra ret kesindir. Chat Completions ve Anthropic Messages istekleri değişmez. | | Diğer sınıflandırılmamış hatalar | Durun ve hatayı döndürün. | @@ -305,8 +305,9 @@ Arayüzü](/tr/guides/sub-agent-surface/) sayfasına bakın. Yerel kontrol panelini açın ve **Modeller → Kombolar** seçeneğini belirleyin. Çalışma alanı komboları oluşturur, düzenler, yeniden adlandırır ve kaldırır; -hedef seçicisi ise devre dışı bırakılmış modelleri ve iç içe geçmiş komboları -hariç tutar. +model seçicileri gizli modelleri de içerir. Görünürlük yalnızca dış model listelerini etkiler, +kombo içindeki seçimi etkilemez; gizli bir modeli seçmek onu dışarıda görünür yapmaz. +Hedef seçicisi devre dışı sağlayıcıları, iç içe komboları ve yalnızca karar hizmeti sunan JEV sağlayıcılarını hariç tutar. Her hedef ayrıca canlı bir kota rozeti gösterir: **Kullanılabilir**, **Kota tükendi** veya **Kota bilinmiyor**. Düzenleyici, kota nedeniyle Kaydet ve Oluştur işlemlerini yalnızca kullanılabilir hedeflerin tümü için yapılandırılmış kimlik bilgisine ait çıkarım sınırının tükendiğini doğrulayan geçerli sunucu bilgisi varsa engeller. Yalnızca görüntüleme amaçlı hesap, model, arama ve MCP kotaları ya da eksik veya süresi dolmuş yönlendirme kanıtları bu engellemeye neden olmaz. Engelleme, ilgili sıfırlama zamanında veya verinin güncellik süresi dolduğunda sona erer ve sayfa etkin ya da görünür olduğunda yeniden kontrol edilir; Yenile, hem kombo verilerini hem de kotaları yeniden yükler. @@ -323,8 +324,8 @@ ocx combo remove --yes ``` `set` ayrıca `--strategy`, `--sticky`, `--effort`, `--alias`, `--native-alias`, -`--display-name` ve `--rename-from` kabul eder. Bu alanı temizlemek için -`--effort`, `--alias` veya `--display-name` değeri olarak `-` kullanın. +`--display-name`, `--description` ve `--rename-from` kabul eder. Bu alanı temizlemek için +`--effort`, `--alias`, `--display-name` veya `--description` değeri olarak `-` kullanın. `--native-alias`, şu anda desteklenen yalın bir yerel model takma adı ve boş olmayan bir görüntüleme adı gerektirir. `create` ve `update`, `set`'in takma adlarıdır; `delete`, `remove`'un takma adıdır; ve aynı alt komutlar `ocx route @@ -374,6 +375,7 @@ saklanır: | `alias` | Hayır | yok | İsteğe bağlı kırpılmış genel model kimliği; yukarıdaki takma ad kurallarını kullanın. Boş bir değer takma ad yok olarak saklanır. | | `nativeAlias` | Hayır | `false` | Şu anda desteklenen yalın bir yerel `alias`'ın yönlendirme ve katalog önceliği almasına açıkça izin verin. Asla takma addan çıkarılmaz. | | `displayName` | Hayır | yok | Sınırlı salt görüntüleme katalog etiketi. `nativeAlias` true olduğunda gerekli ve boş değildir. | +| `description` | Hayır | yok | Salt görüntüleme katalog satırı açıklaması; `displayName` ile aynı sınırlar geçerlidir. Ayarlanmazsa üretilen `Routed via opencodex → combo (combo).` metni korunur. | ## Sorun Giderme @@ -409,6 +411,6 @@ reddedilen istek kaynağını düzeltin. Kombolar bu durumlar için atlama yapma ## İsteğe bağlı parametre uyumluluğu -Sonlandırıcı 400 hatalarının dar bir istisnası vardır: `user` alanını açıkça reddeden, `reasoning.effort`/`reasoning_effort` için desteklenmeyen değer bildiren veya modele özgü görüntü girdisini reddeden (`param: input`) yapılandırılmış hata, çıktı başlamadan önce sonraki uygun hedefe geçebilir. Bu uyumsuzluk için bekleme süresi kaydedilmez. Güvenlik politikası reddi, iptal ve başlamış çıktı yeniden yürütülmez. +Sonlandırıcı 400 hatalarının dar bir istisnası vardır: `user` alanını açıkça reddeden, `reasoning.effort`/`reasoning_effort` için desteklenmeyen değer bildiren, modele özgü görüntü girdisini reddeden (`param: input`) veya rotanın okuyamadığı girdi medyasını reddeden (`unsupported_input_modality`) yapılandırılmış hata, çıktı başlamadan önce sonraki uygun hedefe geçebilir. Bu uyumsuzluk için bekleme süresi kaydedilmez. Güvenlik politikası reddi, iptal ve başlamış çıktı yeniden yürütülmez. [Canonical compatibility details](/guides/combos/#request-local-target-compatibility). diff --git a/docs-site/src/content/docs/tr/guides/composer-usage-gate-fallback.md b/docs-site/src/content/docs/tr/guides/composer-usage-gate-fallback.md new file mode 100644 index 00000000000..d3baf3de172 --- /dev/null +++ b/docs-site/src/content/docs/tr/guides/composer-usage-gate-fallback.md @@ -0,0 +1,218 @@ +--- +title: Mesaj alanı kullanım engeli için kuyruk seçeneği +description: OpenCodex yönlendirmesini ve masaüstü kimlik doğrulamasını değiştirmeden, sunucu kotalarını koruyarak mevcut Codex konuşmasına isteğe bağlı metin ekleme. +--- + +Masaüstünde **yalnızca mesaj yazma alanındaki** kullanım engeli, konuşmanın +OpenCodex rotasında kapasite olsa bile yeni girişi önleyebilir. Uyumlu kurulumda +`codex queue`, bu alanı kullanmadan Codex'in yerel app-server kuyruğuna gönderir. +Uygulamayı yamaz, TLS trafiğini yakalamaz, sertifika kurmaz, kimlik doğrulamayı değiştirmez. + +Bu, **her kullanım sınırı durumunun çözümü değildir**. Tükenmiş kotayı geri +getirmez, model seçiciyi açmaz. `gpt-reserve` kullanan konuşma o modeli kullanmayı +sürdürür; metni kuyruğa almak başka sağlayıcıya geçirmez. Sunucu yetkilendirmesi, +sağlayıcı kotaları, onaylar ve konuşmanın çalıştırma ayarları geçerlidir. + +## Engellenen katmanı belirleyin + +Gönderimden önce mesaj alanının kapalı olması, gönderim sonrası gerçek sağlayıcı +reddinden farklıdır. Özel model etiketi bağımsız sağlayıcıyı veya kapasiteyi +kanıtlamaz. Bu seçeneği, rotası kullanılabilir olan ve bilerek seçtiğiniz konuşmada +kullanın; bekleyen onayları, etkin turları ve app-server bağlantısını ayrı kontrol edin. + +Ana hesap kesin kilidi yetkili kalır. Bu, tükenme öncesi kabul korumasıdır; +tükenmiş hesabı kurtarmaz. Yardımcı korumayı kapatmaz veya konuşmanın sağlayıcısını +değiştirmez. Kuyruktaki yerel hesap isteği çalışsın diye kota yanıtlarını +değiştirmeyin veya korumayı kapatmayın. + +## OpenCodex entegrasyonu ve sorumluluk + +Mevcut [Codex entegrasyonu](/tr/guides/codex-integration) ve +[model yönlendirme](/tr/guides/model-routing) ayarlarını koruyun. Yol: + +```text +yardımcı -> yerel Codex kuyruğu -> seçilen konuşmanın app-server'ı + -> konuşmanın yapılandırılmış sağlayıcısı -> zaten oraya yönlendirilmişse OpenCodex +``` + +Yardımcılar yeni sağlayıcı yönlendiricisi uygulamaz. Yalnızca hedef ve metni +gönderir; model, hesap, hizmet katmanı, onay, sandbox veya temel URL geçersiz +kılması göndermez. OpenCodex'e yönlendirilmiş yerleşik `openai` ve özel `opencodex` +sağlayıcısı mevcut yapılandırmayı korur. Pool/Direct seçimi, hesap bağları, +sağlayıcı kimlik bilgileri ve kabul kontrolleri OpenCodex ile hizmet veren daemon'a +aittir. Kuyruk kabulü tek başına sağlayıcı çağrısını kanıtlamaz. + +Masaüstü kurulumuyla **aynı `CODEX_HOME` ve uyumlu CLI/app-server** kullanın. +`CODEX_HOME` varsayılanı `~/.codex`'tir; başka ana dizin başka daemon/konuşma +deposu bulabilir. `OPENCODEX_HOME` proxy'nin dizinidir, `CODEX_HOME` yerine geçmez. +Yardımcılar iki değişkeni ve iki uygulamanın yapılandırmasını değiştirmez. +PowerShell'de yerel alt işlemler, `CODEX_HOME` göreli olsa da kabuğun dosya sistemi +konumunu devralır. + +Proxy zaten çalışmalı ve konuşma amaçlanan rotayı zaten kullanmalıdır. Yardımcı +OpenCodex'i başlatmaz/eşitlemez/yeniden yapılandırmaz, kapalı entegrasyonu açmaz, +doğrudan OpenAI rotasını sessizce değiştirmez. `codexDesktopAuthless` değişimi +gerekmez. Uzak OpenCodex sağlayıcı URL'si uzak Codex app-server değildir; +yardımcılar başka bilgisayara otomatik konuşma yönlendirmesi değil, yerel daemon keşfi kullanır. + +Bunlar kurulu `ocx queue` komutu veya pano denetimi değil, **isteğe bağlı depo +yardımcılarıdır**. npm paketinin dosya izin listesi `scripts/` içermez; eşleşen +depo kopyasını veya yerel `codex queue` komutunu kullanın. Bu dar kapsam, ikinci +aktarım, kalıcı dinleyici, hesap önbelleği veya kullanım sınırı izleyicisi eklemez. + +## Açma/kapatma ve normal kullanım + +**Kalıcı açma/kapatma anahtarı yoktur**; yardımcıyı çalıştırmak tek gönderime +katılmaktır. OpenCodex'i kurmak/güncellemek veya betikleri diskte tutmak onları çalıştırmaz. + +| Durum | Davranış | +| --- | --- | +| Çağrılmadı; kota var veya tükenmiş | İşlem, zamanlayıcı, sorgulama, dinleyici veya yardımcı kaynaklı istek yoktur. | +| Kota varken normal çağrıldı | Sıradan tek mesajı kuyruğa alır. Masaüstü alanı çalışıyor diye atlamaz; gönderilince normal kullanım/ücret uygulanabilir. | +| Yalnızca mesaj alanı engelliyken çağrıldı | Aynı yerel kuyruk gönderimini dener; rota yetkili ve kullanılabilir olmalıdır. | +| Gerçek sağlayıcıda kapasite/yetki yok | Kısıtı aşmaz, sağlayıcı/hesap değiştirmez; kuyruk kabulünden sonra çalıştırma başarısız olabilir. | +| `-DryRun` / `--dry-run` | Hedef seçip CLI yardımını sorgular; metin göndermez, kota/daemon sağlığı/sağlayıcı erişimini kontrol etmez. | +| Kullanım engeli kalktı | Normal mesaj alanına dönün; geri alınacak yardımcı ayarı yoktur. | + +Kullanmayı bırakmak için çağırmayı bırakın. Sırf yardımcıyı kapatmak için +`ocx restore` çalıştırmayın veya Codex entegrasyonunu kapatmayın; normal +yönlendirmeyi de değiştirir. Haricen zamanladıysanız o zamanlamayı kapatın. +Kabul edilmiş kuyruk öğeleri Codex'e aittir ve yine çalışabilir: betiği silmek, +işlemini kapatmak veya kotanın düzelmesi bunları iptal etmez. Aynı konuşmadaki +normal kuyruk denetimleriyle inceleyip kaldırın. Aynı istemi hem yardımcıdan hem +mesaj alanından göndermeyin. + +## Göndermeden önce uyumluluk + +Uygulamayla paketlenen CLI'yı tercih edin; PATH'teki ayrı `codex` eski olabilir. +`codex queue --help` içinde `--thread`, `--message` seçeneklerini kontrol edin. +Yardımcılar CLI yeteneğini sorgular; daemon'ın `thread/queue/add` desteğini ancak +gerçek istek doğrular. Yöntem desteklenmiyorsa eşleşen kurulumu güncellemeden veya +yeniden başlatmadan önce işi kaydedin. Yardımcılar daemon'ı yeniden başlatmaz veya +başka sunucuyla denemez. Kuyruk komutuna `--no-daemon` eklemeyin. Güncellemeler +uyumluluğu değiştirebilir. + +Önce hedef konuşmayı açıp proje, model ve sağlayıcıyı doğrulayın. **Açık UUID** +tercih edin; yerel CLI tam oturum adını da kabul eder: + +```powershell +codex queue --thread 'my-project-review' --message 'continue with the next step' +``` + +Windows yardımcısı depo kopyasından paketlenmiş yerel `codex.exe` dosyasını bulabilir: + +```powershell +.\scripts\codex-queue.ps1 -Thread 'my-project-review' -Message 'continue with the next step' +``` + +macOS/Linux'ta Bash kullanın (macOS Bash 3.2 dahil): + +```bash +bash scripts/codex-queue.sh --thread 'my-project-review' --message 'continue with the next step' +``` + +Eşleşen güvenilir dosyayı `-CodexExe 'C:\path\to\codex.exe'`, +`--codex '/path/to/codex'` veya `CODEX_EXE` ile sabitleyin. Açık seçim geçersizse +başka dosya sessizce seçilmez, hata verilir. Windows'ta mesaj tırnaklamasını +`cmd.exe` dışında tutmak için npm `.cmd`/PowerShell shim yerine yerel `.exe` +kullanın. Kuyruk destekli uygulama paketleri ve bağımsız yerleşimler PATH'ten önce +denenir. Otomatik PATH keşfi, mevcut projeden farkında olmadan CLI çalıştırmamak +için boş/göreli girdileri yok sayar. Güvenilir yerel dosya yine +`--codex './codex'` veya `-CodexExe '.\codex.exe'` ile açıkça seçilebilir. +Kurulum kökleri ve mutlak PATH girdileri işletmeci tarafından güvenilir olmalıdır; +yardımcı imza doğrulamaz. Keşif, uygulama sürümünün eşleştiğini kanıtlamaz. + +### Özel tanılama ve isteğe bağlı son konuşma keşfi + +Deneme çalıştırması varsayılan olarak dosya yollarını, konuşma UUID/adını ve mesaj +gövdesini gizler: + +```powershell +.\scripts\codex-queue.ps1 -Thread 'my-project-review' -DryRun +``` + +Yalnızca CLI yeteneğini/hedef seçimini bildirir; sunucu doğrulamasını değil. +Eski rollout depolarında `-Latest` / `--latest`, **en son dosya sezgisine** açıkça +katılır. Seçimi görmek için yönlendirilmemiş özel terminal kullanın: + +```powershell +.\scripts\codex-queue.ps1 -Latest -DryRun -ShowTarget +``` + +```bash +bash scripts/codex-queue.sh --latest --dry-run --show-target +``` + +`-ShowTarget` / `--show-target`, deneme çalıştırması ve yerel terminal çıktısı +gerektirir. Yalnızca bu açık istekle seçilen yol ve UUID/adı gösterir, kontrol +karakterlerini kaçırır, CI yakalaması gibi yönlendirilmiş çıktıyı reddeder. +Kayıtlı/paylaşılan terminalde kullanmayın veya çıktıyı yayımlamayın. Mesajı asla +yazdırmaz. Gerçek gönderimde normal Codex çıktısı geçirilir ve kimlik içerebilir; +günlük paylaşmadan önce inceleyin. + +Latest keşfi `CODEX_HOME/sessions` altında değiştirilme zamanını kullanır; +eşitlikte dosya adı sırası belirler. Bu **mevcut masaüstü konuşması değildir**; +başka proje veya alt ajan seçilebilir. Yerel önizlemeyi doğrulayıp açık UUID ile +gönderin. Mesaj verilirse Latest de gönderebilir; yine bu sezgiye bilinçli katılımdır. + +Tanınan `rollout-*.jsonl` adları konuşma UUID'si, bazen ardından `_` +içerir; yardımcı ilk UUID'yi alır. Bozuk adlar atlanır. Eksik/okunamayan depo başka +ana dizin seçilmeden hata verir. Taşınmış, yalnızca sayfalı veya yalnızca uzak +konuşmaların yerel rollout'u olmayabilir; açık UUID/ad kullanın. + +Mesajın tamamını tek argümanda tutun. Bash `--message '- start with this'` veya +`-- '- start with this'`; PowerShell `-Message '- start with this'` kabul eder. +Kabuk geçmişi ve yerel işlem listeleri komut metnini açığa çıkarabilir; istemlere +kimlik bilgisi koymayın. + +## Kuyruğa alınmak, çalıştırılmak değildir + +`Queued message ... for thread ...` yalnızca **kuyruk kabulünü** doğrular; model +çalışmasını veya tamamlanmasını değil. Meşgul konuşma mevcut turu/onayı bekleyebilir. +İncelenen üst kaynak uygulamasında yüklü olmayan kayıtlı konuşma, başka istemci +sürdürünceye kadar mesajı tutabilir. + +Kuyruğu ve etkinliği **aynı konuşmada** kontrol edin. Yüklü değilse uygulamada +açın veya istemi tekrar eklemeden `codex resume ` kullanın. Bekleyen +onayları ve kuyruğu inceleyin. Her çağrı yeni öğe yaratabilir; belirsiz hatadan +sonra yeniden denemeden önce inceleyin. Yardımcılar CLI çıkış durumunu korur, +otomatik yeniden göndermez. + +Yerel CLI'nın açık `--remote` seçenekleri vardır (`codex queue --help`); bu, +masaüstü uzaktan denetim bağlantısının kullanıldığını varsaymaktan farklıdır. +Kimlik doğrulama ayarını korumak ilgisiz ağ/kimlik hatalarını onarmaz veya uzaktan +denetim sürekliliğini garanti etmez. + +## Mesaj alanı olmadan iş başlatma veya sürdürme + +`codex exec ''`, açık masaüstü konuşmasına mesaj değil, etkileşimsiz yeni +iş başlatır. Çalışma dizinini, sağlayıcıyı, izinleri ve ayarları kontrol edin. +`codex resume ` açık oturumu sürdürür. `codex resume --last` normalde +mevcut çalışma dizinine göre süzer; `--all` bunu kaldırır, diğer uygunluk +filtreleri kalabilir. Genel seçim görünür veya en yeni dosya sistemi oturumu +olmayabilir. Süren masaüstü işi için açık kimlik tercih edin. + +## Kapsam ve doğrulama + +Bu seçenek hesap haklarını ve uygulama dosyalarını değiştirmez. Sağlayıcıyı +tanıyan mesaj alanı/model seçici onarımı, otomatik kota kurtarma veya eski +deneylerin ilgisiz proxy/sertifika değişikliklerini temizleme özelliği değildir. + +Özgün Windows denemesi (Desktop `26.917.9434.0`), canlı konuşma için kuyruk kabulü +ve var olmayan konuşma için beklenen hata bildirdi. Bu; uçtan uca model çalışması, +yüklü olmayan konuşma, uzaktan denetim veya platformlar arası garanti değildir. +Üst kaynak `7dae8c53d97e61cd774e4d6bcca5243c29ca615c` sürümünde incelendi: + +- [CLI kuyruk seçenekleri](https://github.com/openai/codex/blob/7dae8c53d97e61cd774e4d6bcca5243c29ca615c/codex-rs/cli/src/queue_cmd.rs) + ve [app-server gönderimi](https://github.com/openai/codex/blob/7dae8c53d97e61cd774e4d6bcca5243c29ca615c/codex-rs/tui/src/session_queue_commands.rs). +- [Yüklü konuşmaya kuyruk teslimi](https://github.com/openai/codex/blob/7dae8c53d97e61cd774e4d6bcca5243c29ca615c/codex-rs/ext/queue/src/service.rs) + ve [sürdürme seçim seçenekleri](https://github.com/openai/codex/blob/7dae8c53d97e61cd774e4d6bcca5243c29ca615c/codex-rs/cli/src/main.rs). + +Çevrimdışı regresyonları `node --test scripts/codex-queue.test.mjs` ile çalıştırın +(Node 20+). Sahte yerel CLI ve geçici dizinler kullanılır; gerçek hesap/model +isteği yoktur. **Codex queue helpers** iş akışı Windows PowerShell 5.1/7, macOS +sistem Bash ve Linux Bash üzerinde çalışır; gerekli kabuk eksikse sessiz atlamak +yerine hata verir. Yapılandırma koruma örnekleri yerleşik/özel sağlayıcı ayarlarını +ve iki kullanım durumunu kapsar, canlı OpenCodex yönlendirmesini değil. Gerçek +Desktop gönderimini, sağlayıcı başarısını ve uzaktan denetim sürekliliğini +desteklenen kurulumda ayrı doğrulayın. diff --git a/docs-site/src/content/docs/tr/guides/fallback-off.md b/docs-site/src/content/docs/tr/guides/fallback-off.md new file mode 100644 index 00000000000..878e4e23d41 --- /dev/null +++ b/docs-site/src/content/docs/tr/guides/fallback-off.md @@ -0,0 +1,134 @@ +--- +title: Yedek geçişini kapatma +description: Model ve sağlayıcı yedek geçişini kapsama göre kapatın; Home'a ulaşılamadığında Remote Link Child davranışını öğrenin. +--- + +Seçtiğiniz hedef başarısız olduğunda OpenCodex isteği başka bir model veya +sağlayıcıyla karşılayabilir: [combo](/tr/guides/combos/) sonraki hedefe, +[yönlendirme profili](/tr/guides/routing-profile-editor/) sonraki adaya geçer; +başlatılan alt ajan model yedek zincirinde ilerler, ana hesap kesin kilidi ise +yapılandırılan yedek modele yönlendirebilir. Bunların tümü varsayılan olarak açıktır. + +Seçmediğiniz bir yerden yanıt almak yerine açık bir hata almak istiyorsanız +ilgili yedek geçişini **kapatın**. + +## Yedek geçişi sayılan durumlar + +Yedek geçişi, **farklı modele** veya **farklı sağlayıcıya** geçiştir: + +| Tür | Örnek | Anahtar | +| --- | --- | --- | +| Model yedeği | `anthropic/claude-opus` başarısız; `anthropic/claude-sonnet` yanıtlayacak | `model` | +| Sağlayıcı yedeği | `anthropic/claude-opus` başarısız; `openai/gpt-6-sol` yanıtlayacak | `provider` | + +Aynı model için **tek sağlayıcı havuzundaki** hesaplar veya API anahtarları arasında +dönüşüm yedek geçişi değildir. Havuz, hız sınırına takılan hesabı bu şekilde +telafi eder ve buradaki ayarlar bunu durdurmaz. Aynı hedefi yeniden denemek veya +hedefi koruyan aktarım yedekleri de bu kapsama girmez. + +## Yedek geçişini kapatma + +```bash +ocx fallback off # model ve sağlayıcı yedeğini her yerde kapat +ocx fallback off --kind provider # yalnızca sağlayıcılar arası geçişler +ocx fallback off --route combo/coding # tek combo, yönlendirme profili veya model seçicisi +ocx fallback off --provider anthropic # bu sağlayıcıdan ayrılan geçişler +ocx fallback on --route combo/coding # genel kapatma altında bir istisna +ocx fallback clear --route combo/coding # geçersiz kılmayı kaldır +ocx fallback status # güncel ayarlar ve son yedek geçişi +``` + +Aynı anahtarlar panoda da bulunur. **Dashboard** sayfasının altındaki **Fallback** +kartı genel anahtarları, geçersiz kılmaları ve yapılan ya da reddedilen son geçişi gösterir. + +### Öncelik + +1. Kapalı ayarlanmış **cihaz** her zaman reddeder ([cihaz ayarları](#cihaz-ayarları)). + Cihaz ayarı yalnızca reddedebilir; açılması başka kapsamın reddettiği geçişe izin vermez. +2. Diğer durumlarda en özel ayar kazanır: **rota**, ardından **sağlayıcı**, ardından **genel**. +3. Ayar yoksa izin verilir; mevcut davranış budur. + +Ayarlar `config.json` içindeki `fallback` bölümünde saklanır: + +```json +{ + "fallback": { + "provider": false, + "routes": { "combo/coding": { "provider": true } }, + "providers": { "anthropic": { "model": false } }, + "devices": { "lnk_0123456789abcdef": { "localFallback": false, "model": false } } + } +} +``` + +Bozuk bir yaprak değer yok sayılır; ilke çözümlemesi sonraki uygun kapsamı kullanır +ve `ocx config` tanılaması bunu belirtir. Böyle bir değer içeren tüm yapılandırmayı +yazma işlemi ise reddedilir. + +## Alınan hata + +Reddedilen geçiş, **onu tetikleyen hatanın durum koduyla** yanıtlanır. Örneğin +429, `Retry-After` başlığıyla birlikte 429 olarak kalır: + +```json +{ + "error": { + "type": "fallback_refused", + "code": "fallback_refused", + "message": "combo/coding: anthropic/claude-opus failed (rate_limited). Provider fallback to openai/gpt-6-sol is off for route combo/coding, so OpenCodex did not send the request there. To allow it: ocx fallback on --kind provider --route combo/coding", + "fallback": { "kind": "provider", "scope": "route", "scopeKey": "combo/coding", + "from": "anthropic/claude-opus", "to": "openai/gpt-6-sol", "trigger": "rate_limited" } + } +} +``` + +Yanıt `x-opencodex-fallback: refused` başlığını da taşır. İlk hedefin kullanılamadığı +önceden biliniyorsa hiçbir şey gönderilmeden ret verilir; yedek hedefle hiç iletişim kurulmaz. +Durum kodu ilk hedefin atlanma nedenine bağlıdır: + +- Combo'nun ilk hedefi bekleme süresindeyse veya kotası bittiyse: 429. +- Combo'nun ilk hedefi devre dışıysa veya isteğe uygun değilse: 503. +- Ana hesap engelliyse: 429. +- Alt ajan modeli kullanılamıyorsa: 503. + +Gerçek bir üst sağlayıcı hatasından sonra ret, o hatanın durum kodunu korur. + +## Remote Link Child ve Hub istemcileri + +Remote Link Child ve Hub istemcisi kendi yerel sağlayıcılarından istek karşılamaz. +Home'a ulaşılamadığında Child yeniden denenebilir `503`, Hub istemcisinin pano +aktarıcısı ise `502` döndürür: + +```json +{ "error": "link tunnel unavailable", "code": "home_unreachable", "message": "The Home is unreachable, and OpenCodex did not serve this request from this device's local providers. …" } +``` + +Yerel sağlayıcıya hiçbir şey gönderilmez; bağlantı anahtarı ve istek gövdesi cihazdan çıkmaz. + +### Cihaz ayarları + +Home üzerinde her bağlı cihazın **Remote Link** sayfasında veya CLI'da iki anahtarı vardır: + +```bash +ocx fallback local on --device mac-studio # Home kapalıyken yerel yedek izni +ocx fallback off --device mac-studio # bu cihazın isteklerinde model ve sağlayıcı yedeğini reddet +``` + +`--device`, `ocx link status` çıktısındaki bağlantı takma adını/kimliğini veya +Hub veri anahtarı kimliğini alır. + +**Yerel yedek**, GOAT'ın okuduğu bir izindir. Açıksa GOAT, Home'a ulaşılamadığında +cihazın işini cihazın kendi oturumlarına taşıyabilir. OpenCodex'in kendisi yine +`home_unreachable` döndürür ve cihazda yerel hizmet vermez. Siz açmadıkça kapalıdır. + +## Durum + +`ocx status --json` bir `fallback` nesnesi içerir (Child üzerinde bağlantıdan okunan +Home nesnesidir). `GET /api/fallback` aynı `ocx.fallback/1` belgesini döndürür: +etkin genel anahtarlar, geçersiz kılmalar, her cihazın yerel yedek izni ve yapılan +ya da reddedilen son 20 geçiş. Kayıtlarda zaman, rota, kaynak, hedef, tetikleyici +ve kararı veren kapsam bulunur. Liste bellektedir, proxy yeniden başlayınca +sıfırlanır. İstek gövdesi, anahtar veya hesap kimliği içermez. + +Bu özelliği destekleyen Home, `GET /readyz` yanıtının `features` alanında +`fallback-policy/1` bildirir. diff --git a/docs-site/src/content/docs/tr/guides/local-plugins.md b/docs-site/src/content/docs/tr/guides/local-plugins.md new file mode 100644 index 00000000000..289d4ff9df7 --- /dev/null +++ b/docs-site/src/content/docs/tr/guides/local-plugins.md @@ -0,0 +1,114 @@ +--- +title: Yerel eklentiler +description: Sağlayıcı gönderimlerini değiştirmek ve örneğin sağlayıcıların önüne yerel sıkıştırma proxy'si koymak için başlangıçta kendi kodunuzu yükleyin. +--- + +Yerel eklenti, `ocx start` komutunun proxy hizmet vermeye başlamadan yüklediği +TypeScript/JavaScript dosyasıdır. Her sağlayıcı gönderimini işlemden çıkmadan +hemen önce görebilir, yönlendirebilir veya başlık ekleyebilir. Böylece opencodex'i +değiştirmeden sağlayıcıların önüne yerel yardımcı servis (sıkıştırma proxy'si veya +kayıt aracı gibi) yerleştirebilirsiniz. + +Eklentiler tek kuruluma özeldir. opencodex bunları indirmez, güncellemez veya imzalamaz. + +## Eklentilerin konumu + +Dosyaları opencodex ana dizinindeki `plugins/` içine koyun: `~/.opencodex/plugins/` +veya değişken ayarlıysa `$OPENCODEX_HOME/plugins/`. + +```text +~/.opencodex/plugins/ + my-sidecar.ts +``` + +- `.ts`, `.js`, `.mjs` dosyaları ad sırasıyla yüklenir. +- `.` veya `_` ile başlayan adlar ve `*.d.ts` yok sayılır. Kapatmak için dosyayı + `_my-sidecar.ts` olarak yeniden adlandırın. +- Dizin isteğe bağlıdır; yoksa hiçbir şey yüklenmez. +- Eklenti proxy içinde sizin kimlik bilgilerinizle çalışır. Bu yüzden dosya veya + `plugins/` dizini başka kullanıcıya aitse, grup/diğer kullanıcılarca yazılabiliyorsa + ya da sembolik bağlantıysa reddedilir. `/` köküne kadar tüm üst dizinler size + veya root'a ait olmalı ve `/tmp` gibi sticky değillerse grup/diğer kullanıcılarca + yazılamamalıdır. İzinler için `chmod go-w ~/.opencodex/plugins ~/.opencodex/plugins/*` + kullanın; varsayılan umask `002` olan sistemlerde üst dizinleri de kontrol edin. + macOS'ta başka kullanıcıya/gruba yazma, silme, izin değiştirme veya yol girdisi + ekleme/silme yetkisi veren ACL, mod `0600` olsa bile yüklemeyi engeller. + Yolun kendisini `/bin/ls -lebd -- ` ile inceleyin. Salt okunur, ret, + yalnızca devralma ve yalnızca yol sahibine/çalışan kullanıcıya verilen izinler + engellemez. `root` veya `0` gibi ACL görünen adları sayısal UID kanıtı sayılmaz. + Linux'ta `getfacl` kuruluysa genişletilmiş ACL denetlenir; yoksa yalnızca sahiplik + ve mod bitleri doğrulanır. +- Windows'ta ACL güven kontrolü sağlanana kadar otomatik eklenti yükleme kapalıdır. + +Ekleme, değiştirme veya kaldırma sonrasında proxy'yi yeniden başlatın +(`ocx service restart` veya `ocx start` işlemini durdurup başlatın). Yüklenen her +eklenti başlangıçta `Plugin loaded: ` yazar; atlanan eklenti sınırlı bir +neden kategorisi bildirir. Ham eklenti istisna metni otomatik yazdırılmaz. + +Bir kez eklentisiz başlatmak için `OCX_PLUGINS=0` ayarlayın. + +## Eklenti yazma + +Eklenti, isteğe bağlı `name` ve bir `setup` işlevi içeren nesneyi varsayılan dışa +aktarım olarak sunar. `setup` bir bağlam alır. Asenkron `setup` beş saniyede +tamamlanmalıdır. Eklentiler proxy'nin kendi iş parçacığında çalıştığından senkron +bloklayan `setup` kesilemez; dönene kadar başlangıcı geciktirir. Zaman aşımındaki +`setup` da durdurulmaz: başlattığı sunucu/zamanlayıcılar çalışmaya devam eder. +Uzun ömürlü kaynakları yalnızca başarısız olabilecek işlerden sonra başlatın. + +```ts +interface UpstreamTarget { + url: string; // mutlak üst sunucu URL'si; yönlendirmek için değiştirin + headers: Headers; // kimlik bilgileri dahil giden başlıklar; asla kaydetmeyin + readonly transport: "http" | "websocket"; +} + +export default { + name: "my-sidecar", + setup(ctx: { + log(message: string): void; + registerUpstreamRewriter(rewrite: (target: UpstreamTarget) => void): void; + onShutdown(teardown: () => void): void; + }) { + ctx.registerUpstreamRewriter(target => { + const upstream = new URL(target.url); + if (!upstream.pathname.endsWith("/chat/completions")) return; + target.url = `http://127.0.0.1:9000${upstream.pathname}${upstream.search}`; + target.headers.set("x-original-origin", upstream.origin); + }); + }, +}; +``` + +Bağlam ayrıca `name`, `configDir` (opencodex ana dizini) ve `pluginDir` taşır. + +Eklentiler opencodex modüllerini içe aktaramaz; paketlenmiş çalıştırılabilir +dosyada bunlar diskte bulunmaz. Gerekli küçük arayüzleri örnekteki gibi yerel tanımlayın. + +## Yeniden yazmanın davranışı + +- Yeniden yazıcı, opencodex aktarımı seçtikten sonra her HTTP sağlayıcı gönderiminde + ve Codex WebSocket bağlantısında senkron çalışır. Hızlı tutun; sağlık sorguları + gibi ağ kontrollerini arka planda yapıp önbellekteki sonucu okuyun. +- Geri döngü adresine (`127.0.0.1`, `::1`, `localhost`) yönlendirilen gönderim HTTP + ve Codex WebSocket üzerinde doğrudan bağlanır; sağlayıcı proxy'lerini ve + `HTTP_PROXY` değerini yok sayar. Başka yerdeki proxy bu makinenin geri döngüsüne + ulaşamaz. Diğer hedefler iki aktarımda da yeniden yazılan URL'ye göre normal + çıkış ayarlarını (`NO_PROXY` dahil) kullanır. +- Codex WebSocket yeniden yazıcısı, boş havuz soketi yeniden kullanılmadan önce + her turda çalışır. Soket yalnızca hedef ve yeniden yazılan başlıklar aynıysa + kullanılır; tur başına `x-codex-turn-state` ve `x-codex-turn-metadata` istisnadır. + Bunlar istek çerçevesinde taşınır, aynı sokette değişebilir ve yeniden yazıcının + bunlara yaptığı değişiklikler atılır. Eklentinin yönlendirmeyi başlatması veya + bırakması sonraki turda etkili olur. +- Sağlayıcı, hesap ve rota seçildikten sonra çalışır; yönlendirmeyi, hesap seçimini, + yeniden denemeleri veya istek günlüklerini değiştirmez. +- Gönderim seçtiğiniz sunucuya gider. O sunucu, kimlik bilgileri dahil sağlayıcının + göreceği isteği görür. +- Yeniden yazıcı hata fırlatırsa opencodex o yazıcının değişikliklerini geri alıp + işlem sonuna kadar onu kapatır. Önceki yeniden yazıcıların değişiklikleri korunur; + tek eklentiyse gönderim değişmeden çıkar. `setup` hata verir veya zaman aşımına + uğrarsa eklenti atlanır, kaydettikleri kaldırılır, sonraki kayıt girişimleri yok + sayılır. Diğer eklentiler ve proxy normal başlar. +- Mevcut ama okunamayan eklenti dizini (örneğin yanlış izinler) boş sayılmaz; + başlangıçta bildirilir. diff --git a/docs-site/src/content/docs/tr/guides/protocol-paths.md b/docs-site/src/content/docs/tr/guides/protocol-paths.md new file mode 100644 index 00000000000..4c5c7a18149 --- /dev/null +++ b/docs-site/src/content/docs/tr/guides/protocol-paths.md @@ -0,0 +1,173 @@ +--- +title: Protokol yolları +description: Responses, Chat Completions veya Messages isteğinin sağlayıcıya ulaşma yolu, önizleme ve izleme araçları ile bu yolu değiştiren aşamalı anahtarlar. +--- + +opencodex üç istemci API'si sunar: **Responses** (`/v1/responses`), **Chat Completions** +(`/v1/chat/completions`) ve **Messages** (`/v1/messages`). Her sağlayıcı, +bağdaştırıcısının belirlediği tek üst sunucu protokolünü alır. İstemci API'si ile +üst sunucu protokolü farklıysa istek gidişte, yanıt dönüşte dönüştürülür. Bu sayfa +yolun adlandırılmasını, istek öncesi/sonrası görülmesini ve onu değiştiren ayarları açıklar. + +Bu sayfadaki ayarlar varsayılan olarak kapalıdır; aşağıda açıklanan +`antigravityDirect` varsayılanı istisnadır. Kapalı anahtarlar mevcut yolu değiştirmez. + +## Teslim modları + +Yol, istemci API'sinden üst sunucu protokolüne uzanan adımlar listesidir. Adımlar +üç API adı, `ir` (opencodex'in bağdaştırıcıdan bağımsız istek/olay biçimi) ve +`responses-internal` (yalnızca iç köprüde üretilen, istemciye gösterilmeyen Responses JSON/SSE) olabilir. + +| Mod | Anlamı | Örnek istek yolu | +| --- | --- | --- | +| `native` | İki uçta aynı API; sağlayıcı istemcinin gövdesini alır | `chat > chat` | +| `translated` | Hedef protokolün kodlayıcısı veya IR üzerinden tek dönüşüm | `chat > responses`, `responses > ir > messages` | +| `legacy-bridge` | Önce iç Responses biçimi üzerinden dönüşüm | `chat > responses-internal > ir > messages` | +| `blocked` | Gönderimden önce ret | Yok | + +`native`, isteğin taşınma biçimidir; uyumluluk kararı değildir. Yerel yol üzerinde +de sağlayıcı bir alanı reddedebilir. Compatibility Lab doğrulama sonuçları ayrıdır. + +Tüm anahtarlar kapalıyken uygun tek sağlayıcı rotası şu yolları izler: + +| İstemci API'si → üst sunucu | Yol | +| --- | --- | +| Responses → Responses | Yerel | +| Responses → Chat veya Messages | IR üzerinden dönüştürülür | +| Chat → Chat | Yerel; istemci `stream: false` gönderirse JSON | +| Chat → Responses, Messages → Responses | Responses kodlayıcısı üzerinden dönüştürülür | +| Chat → Messages, Messages → Chat | Eski köprü | +| Messages → Messages | Yalnızca çağıran kendi Anthropic kimlik bilgisini iletiyorsa yerel; opencodex'in yönettiği anahtarda eski köprü | + +Aşağıdaki anahtarlardan biri farklı davranış belirtmedikçe combo'lar, yönlendirme +ilkeleri, sentetik çaba veya hızlı model satırları Chat/Messages girişinde eski köprüyü kullanır. + +## Özellik etkileri + +Bazı istek özellikleri her dönüşümde korunamaz. Örneğin Chat alanları `n`, +`logprobs`, `logit_bias`, `seed`, `audio`, `prediction` Responses gövdesinde yer +bulamaz; Messages `top_k` alanı da Responses'a giderken düşürülür. opencodex her +etkiyi `passthrough`, `translated`, `degraded`, `unsupported` olarak kaydeder +(sonuncusu panoda *Dropped* gösterilir). Bunlar dönüştürücü kodundan çıkarılan +beyanlardır; ölçülmüş sonuçlar değildir. + +## Yolu önizleme + +Önizleme yalnızca yapılandırmanızdan hesaplanır. Sağlayıcıya istek göndermez, +combo dönüş sırasını ilerletmez ve günlüğe yazılmaz. + +- Pano: **API** sayfasındaki **Request path preview**. +- CLI: + + ```bash + ocx api explain --model combo/main --inbound chat --feature request.seed + ocx api explain --model claude-sonnet-4-5 --inbound messages --json + ``` + +Her rota adayı için istek yolu, teslim modu, özellik etkileri ve aşağıdaki +`reject` ilkesinin reddedip reddetmeyeceği listelenir. `ocx api protocols --json` +kabul edilen özellik adlarını gösterir. Çağıranın kendi Anthropic kimlik bilgisini +ilettiği Messages isteği varsayılmaz; önizlemede çağıran bulunmadığından +`caller-credential-required` gösterilir. + +## İsteği izleme + +Sağlayıcıya ulaşan veya gönderilmeden reddedilen her isteğin günlük satırına gerçek +yolu kaydedilir: son mod ve yollar, nedenler, özellik etkileri ve her fiziksel +deneme için bir yol. Panodaki **Logs**, yol rozeti, **Protocol path** filtresi ve +istek ayrıntısında aynı adlı bölüm sunar. Eski satırlarda yol verisinin olmadığı +belirtilir; tahmin yapılmaz. + +## Temsil edilemeyen özellikler + +`protocols.unrepresentable`, yolun düşüreceği bir özellik taşındığında davranışı belirler: + +- `legacy` (varsayılan): İstek eskisi gibi gönderilir; kayıp yalnızca izin özellik etkilerinde görünür. +- `reject`: Hiç gönderilmeden HTTP 400 ile reddedilir; yalnızca özellik anahtarları + adlandırılır. Chat, `unsupported_feature` kodlu `invalid_request_error`; + Messages, `invalid_request_error` döndürür. İz `blocked` olur. + +`reject` altında doğrudan rota, sağlayıcı ve protokol belirlendiğinde girişte +değerlendirilir. Combo/ilkeler girişte değerlendirilmez. `nativeChatCombos` +açıksa Chat combo her adayı ayrı değerlendirip isteği taşıyamayanı atlar. + +## Aşamalı geçiş anahtarları + +Yeni yollar bu anahtarlarla aşamalı açılır. `antigravityDirect` dışında tümü +varsayılan olarak kapalıdır; kapalı anahtar değişiklik yapmaz. + +| Anahtar | Açıkken | +| --- | --- | +| `nativeChatCombos` | Combo içindeki uygun Chat adayı, istemci gövdesinin kendine ait kopyasından yerel gönderilir; diğer adaylar köprüde kalır. | +| `managedMessagesNative` | Doğrudan, anahtarla doğrulanan Anthropic sağlayıcısına giden Messages isteği köprü yerine Messages olarak gönderilir. Sabitlenmiş çaba, engelli beceri çıkarma, web arama yardımcısı, görsel ön işleme veya sentetik satır gibi yalnızca köprüde çalışan davranışlar köprüde kalır. | +| `managedMessagesNativeOAuth` | `api.anthropic.com` üzerindeki havuzsuz `anthropic` OAuth sağlayıcısı için yerel Messages. Yalnızca `managedMessagesNative` ile birlikte etkilidir; Anthropic OAuth hesap havuzu köprüde kalır. | +| `directEncoders` | Responses dışı üst sunucuda Chat/Messages yanıtı, iç Responses akışı yerine doğrudan bağdaştırıcı olaylarından kodlanır. İstek tarafı değişmez. | +| `antigravityDirect` | **Varsayılan olarak açık**; kapatmak için `false` yapın. Tek Google Antigravity sağlayıcısına giden Chat/Messages, iç Responses gövdesini iki yönde de atlar: konuşma doğrudan Google bağdaştırıcısının derlediği biçime çözülür, yanıt `directEncoders` gibi kodlanır. Yol `chat -> ir -> other` veya `messages -> ir -> other` olur. Model, çaba, sınırlar ve araç seçimi normal ilkelerden geçtiği için hesap dönüşümü, çaba kuralları ve kullanım değişmez. Combo'lar, ilkeler, barındırılan web araması aracı, yalnızca metin modeline gelen görseller, gölge çağrı yakalamasının değiştirebileceği modeller ve proxy'nin yeniden yazdığı metinler (beceri kataloğu, üretilmiş görsel bağlantısı) köprüde kalır; neden günlükte belirtilir. | +| `shadowPlan` | Her Chat/Messages isteğinin sonunda önizlemenin öngöreceği plan gerçek yolla karşılaştırılır. Uyuşmazsa yol kaydına `planMismatch: true` eklenir. İkinci istek gönderilmez. | + +CLI ile veya `config.json` içindeki `protocols` bölümünü düzenleyerek değiştirin +([başvuru](/tr/reference/configuration/server/)): + +```bash +ocx api policy # geçerli ilkeyi göster; değişiklik yapmaz +ocx api policy --rollout shadowPlan=on # tek anahtarı değiştir +ocx api policy --unrepresentable reject +``` + +`ocx api policy` yalnızca ayar bayrağı verdiğinizde ve komutu çalıştırdığınızda yazar. + +### Gölge plan + +`shadowPlan` güvenli ilk adımdır: hiçbir isteği değiştirmez. Gönderdiğiniz gerçek +trafikte önizlemenin gerçekle uyuşmasını kontrol eder. Sonuçlanan rotayı (combo'da +yanıt veren hedefi) kullanarak teslim modunu, üst sunucu protokolünü ve istek +yolunu karşılaştırır. `directEncoders` yanıt yolunu değiştirdiği ve önizleme bu +anahtarı modellemediği için yanıt yolu karşılaştırılmaz. Çağıran kimlik bilgili +Messages, Claude uyumluluk retleri ve Responses istekleri karşılaştırılmaz. + +`planMismatch`, `GET /api/logs` satırının `protocolTrace` alanında görünür. +Panoda henüz gösterilmez. + +## Henüz taşınmayan yollar + +Aşağıdakiler iç Responses köprüsünü kullanmaya devam eder veya kapsam dışıdır: + +- Dönüştürülen yollar dahil Chat/Messages, IR öncesinde hâlâ Responses biçimli + gövdeye çözülür. Tek Google Antigravity rotası istisnadır; ancak + `antigravityDirect: false` ise o da köprü kullanır. +- Yönlendirme ilkesi adayları, çaba satırından ulaşılan combo'lar, + `nativeChatCombos` kapalıyken combo adayları ve Messages combo'ları. +- `directEncoders`, yalnızca somut Responses dışı rotalarda akışlı teslimi kapsar. + Combo/ilke alt istekleri, yönlendirilmiş sıkıştırma, tur çalıştıran bağdaştırıcılar + (Cursor, Devin, kod ajanı CLI'ları, CodeBuddy) ve yardımcı servis turları köprüde kalır. +- Çıktı üretmeden akış içinde başarısız olan yerel Chat combo adayı sonraki adaya + geçmez; köprülenen aday geçerdi. +- Web arama, görsel işleme ve görsel üretme yardımcıları yalnızca Responses hattında çalışır. +- `previous_response_id`, `store`, `background`, sıkıştırma Responses özellikleri olarak kalır. +- Protokolü üç API'den biri olmayan bağdaştırıcılar (Gemini, Kiro, Cursor ve diğerleri) + özellik iddiası olmadan IR üzerinden dönüştürülür. +- Birinci taraf Anthropic'e yönetilen yerel Messages yolu; token yenileme ve hesap + seçimi boyunca gözlenen tutarlı Claude Code CLI user-agent, oturum UUID'si ve + izin listeli SDK başlıklarını korur. Kimlik bilgisi yerleşimini proxy yönetir; + beta izin listesi geçerlidir. Kimlik eksik/geçersizse mevcut uyumluluk başlıkları + kullanılır; uyumlu sunucular ve üretilen Responses mevcut davranışı korur. + Başlıklar tek başına istek yetkisi veya resmi destek sağlamaz. TypeScript Agent + SDK'nın `sdk-ts` user-agent giriş noktası da aynı zorunlu kimlik başlıklarıyla + tanınır; birinci taraf Anthropic isteklerinde izin listeli yerel betalarını korur. +- OAuth üzerinden yerel Chat planlanmıyor. Anthropic OAuth ile yerel Messages + yalnızca havuzsuz hesabı kapsar; hesap havuzu köprüde kalır. +- Yönetilen yerel Messages, çağıranın `anthropic-beta` değerlerinden yalnızca kısa + izin listesindekileri ve yalnızca `api.anthropic.com` hedefine iletir. İzin + listesi dışı üst düzey alanları özellik etkisi kaydetmeden düşürür ve + `claudeCode.stabilizePromptCache` uygulamaz. Tanınan Claude Code istemcisi, + `context-management-2025-06-27` ile `context_management` gövde alanını birlikte + korur. Birinci taraf Anthropic anahtar/OAuth isteklerinde herhangi bir yerel + Messages istemcisi, adaptive veya enabled thinking üzerinde + `thinking-binding-controls-2026-08-01` ile `thinking.block_binding` gönderebilir. + Beta yoksa veya hedef desteklemiyorsa proxy iç alanı çıkarır, diğer düşünme + ayarlarını korur. Bulut sağlayıcı uç noktaları ve üçüncü taraf uyumlu sağlayıcılar + birinci taraf beta desteğini devralmaz. Eşleştirme, işletmecinin ayarladığı + betalar dahil son başlığı kullanır. Beta tek başına + [düşünme dönüşümü tanılaması](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking) + için geçerlidir. Düşürülen beta adları proxy günlüğünde bir kez + `anthropic-beta-dropped=` olarak görünür. diff --git a/docs-site/src/content/docs/tr/guides/providers.md b/docs-site/src/content/docs/tr/guides/providers.md index d3758aa66b2..58f0f8ee2f1 100644 --- a/docs-site/src/content/docs/tr/guides/providers.md +++ b/docs-site/src/content/docs/tr/guides/providers.md @@ -77,8 +77,9 @@ etiketler; bunlar normalde hem `authMode` hem de `apiKey`'i atlar. özelliği yalnızca API anahtarı sağlayıcıları için geçerlidir (`authMode: "key"`). OAuth, iletme ve yerel önayarlar hariç tutulur — kimlik bilgileri asla aynı belirteç üzerinde yeniden oynatılmamalıdır ve yerel çalışma zamanlarının -korunacak uzak bir anahtarı yoktur. İsteğe bağlıdır: seçenek olmadığında özellik -kapalıdır; nesnenin varlığı `enabled: false` olmadığı sürece etkinleştirir. +korunacak uzak bir anahtarı yoktur. Belgelenmiş hedefe özgü varsayılanlar dışında isteğe bağlıdır. Standart Mistral Chat, mevcut istek genelindeki gönderim hakkı içinde sabırlı bir yeniden deneme politikası (6 yeniden oynatma, 10 saniyelik yedek aralık) kullanır. `enabled: false` dahil açık `retryOn429` ayarları önceliklidir. Mistral önayarı, sağlanan istem önbelleği anahtarlarını da varsayılan olarak iletir; `promptCacheKey: false` iletmeyi kapatır. Önbellek yakınlığı yeniden kullanım olasılığını artırır, ancak önbellek isabetini garanti etmez ve belirteç verimi sınırlarını kaldırmaz. + +Mistral, hem Studio hem Vibe anahtarları için tek bir API anahtarı sağlayıcısı kullanır. Anahtar türleri ve plan hakları [Mistral'ın API anahtarı belgelerinde](https://docs.mistral.ai/admin/identity-access/api-keys) açıklanır; proxy anahtardan abonelik katmanı çıkarmaz. Tüm modellerde, yukarı akışın açıkça bildirdiği `Retry-After` süreleri aynı kimlik bilgisi ve model üzerindeki çağrılar arasında paylaşılır. Yeniden deneme zamanı içermeyen bir 429, geçerli dakikalık sayaçlar kapasitenin tükendiğini gösterdiğinde veya reddedilen sorgunun ölçülen maliyeti kalan belirteçleri aşıp ilan edilen tam hakka sığdığında temkinli bir 60 saniyelik kurtarma beklemesi başlatabilir. Bu, sunucu sıfırlamasının kesin bir tahmini değil, yerel bir kurtarma politikasıdır. Sağlıklı trafik, bir sonraki sorgunun önbelleğini veya maliyetini asla tahmin etmez. Beklemeden sonra tek bir kurtarma yoklaması çalışır; başarılı olursa normal trafik geri gelir. Eksik, hatalı biçimli, sıfır haklı, aylık hakkı tükenmiş veya tam hakkın üzerindeki kanıtlar bir sıfırlama uydurmaz ve geçmişi sıkıştırmaz. `requestPacing: { "enabled": false }` veya `retryOn429: { "respectRetryAfter": false }` paylaşılan hız ayarlamasını kapatır. Aynı anahtarla yeniden oynatmada, Mistral'ın yeniden deneme süresi `retryOn429.maxIntervalMs` değerini aşarsa proxy erken yeniden oynatmak yerine 429'u ve yeniden deneme ipucunu döndürür. Döngüsel (round-robin) anahtar seçimi etkinse bekleyen istekler anahtar soğuma sürelerini yeniden denetler ve yedek anahtar için hız kapasitesi alır; çıktıdan önceki bir anahtar reddi, isteğin mevcut gönderim sınırı içinde sağlıklı bir anahtarla kurtarılabilir. Bağlam pencereleri ve model başına sıkıştırma tercihleri bağımsız kalır. ## 1. ChatGPT girişi (iletme / doğrudan geçiş) @@ -125,6 +126,7 @@ ocx login google-antigravity ocx login cursor # bağımsız Cursor PKCE girişi ocx login command-code # Command Code tarayıcı OAuth (veya ~/.commandcode/auth.json içe aktarma) ocx login devin # Cognition/Devin: önce Devin CLI kimliği içe aktarılır, yoksa Auth0 tarayıcı girişi +ocx login dsh-account # DeepSeek Harness Desktop (~/.dsh/.credentials.yaml yetkisi içe aktarılır) ocx login github-copilot # GitHub cihaz akışı → Copilot belirteci (Copilot Pro/Business) ocx login codex # Codex hesap havuzu (takma adlar: chatgpt, openai; çalışan bir proxy gerekir) ocx logout @@ -141,6 +143,7 @@ ocx logout | `google-antigravity` | `google` | `https://daily-cloudcode-pa.googleapis.com` | Cloud Code Assist hattı üzerinden Google OAuth. Canlı keşif CCA'nın kimlik doğrulamalı `v1internal:fetchAvailableModels` uç noktasını kullanır ve oturum açmış hesap için kullanılabilir olan ajan modellerini yayınlar; sürdürülen katalog geri dönüş olarak kalır. | | `cursor` | `cursor` | `https://api2.cursor.sh` | Deneysel PKCE girişi, canlı HTTP/2 aktarımı ve hesap filtreli model keşfi. | | `devin` | `devin` | `https://server.codeium.com` | Deneysel, resmi olmayan Cognition/Devin köprüsü. Giriş önce kurulu Devin CLI'nin zaten tuttuğu kimlik bilgisini içe aktarır (`devin auth login`, `devin-session-token`'ı kendi `credentials.toml` dosyasına yazar); yoksa tarayıcıda Auth0 oturumunu açar ve yapıştırılan belirteci `RegisterUser` ile uzun ömürlü bir API anahtarına dönüştürür. `ocx login devin-cli` kullanımdan kaldırılmış bir takma ad olarak çalışmaya devam eder. Modeller hesaba göre `GetCascadeModelConfigs` ile keşfedilir; akış yalnızca Connect-RPC üzerindeki `runTurn` yolunu kullanır. Panel ön ayarında varsayılan olarak yer almaz. | +| `dsh-account` | `dsh-account` | `https://api.deepseek.com/anthropic` | Kurulu ve oturum açılmış DeepSeek Harness (DSH) Desktop hesabını yeniden kullanan deneysel, içe aktarma öncelikli hesap sağlayıcısı. Açık içe aktarma sırasında `~/.dsh/.credentials.yaml` dosyasından kesinlikle salt okunur kimlik bilgilerini okur. DeepSeek API anahtarı faturalandırmasından bağımsızdır. | | `github-copilot` | `openai-chat` | `https://api.githubcopilot.com` | Deneysel. GitHub cihaz akışı + `copilot_internal` değişimi (VS Code OAuth istemcisi). Aktif bir Copilot aboneliği gerektirir; resmi bir üçüncü taraf API değildir. | Google Antigravity hesap ve sağlayıcı kota sorguları, model listesine geri dönüş dahil sabit Google uç noktalarını kullanır. Bu hedefler için şeffaf Fake-IP DNS desteklenirken TLS doğrulaması, yönlendirme reddi ve özel adres kontrolleri korunur. Özel base URL yalnızca model isteklerini değiştirir; `NO_PROXY` doğrudan bağlantı politikasını korur. @@ -325,9 +328,20 @@ altındaki okunamayan veritabanını onarın veya kaldırın, bu içe aktarma seçicilerini kaldırın ve ardından yeniden deneyin. Mevcut bir `kiro-cli` oturumu olmayan bir makineden oturum açmak bundan etkilenmez. +### DeepSeek Account (DSH) + +`dsh-account`, kurulu bir DeepSeek Harness (DSH) Desktop uygulamasının kimliği doğrulanmış hesap bilgilerini yeniden kullanmak üzere tasarlanmış deneysel, içe aktarma öncelikli bir hesap sağlayıcısıdır. + +- **DeepSeek API Anahtarından Bağımsız:** `dsh-account`, kullanıcının kimliği doğrulanmış DeepSeek oturum kotasını (normal/bonus cüzdan bakiyeleri ve toplam harcama) kullanırken, `deepseek` sağlayıcısı platform API anahtarı bakiyesinden ücretlendirilir. Bu iki kimlik bilgisi alanı ve yönlendirmesi kesinlikle ayrıdır ve birbirine asla geri dönüş yapmaz. +- **Yalnızca Açık İçe Aktarma:** OpenCodeX, DeepSeek Harness'in kurulu ve oturum açılmış olup olmadığını (`~/.dsh/.credentials.yaml`) algılar, ancak kullanıcının açık bir komutu (`ocx login dsh-account` veya Web Panelinde İçe Aktar'a tıklama) olmadan belirteci asla otomatik olarak kopyalamaz, içe aktarmaz veya kullanmaz. +- **Salt Okunur DSH Kimlik Deposu:** OpenCodeX, `~/.dsh/.credentials.yaml` dosyasına kesinlikle salt okunur olarak yaklaşır. OpenCodeX, DeepSeek Harness'e ait hiçbir dosyayı asla yazmaz, değiştirmez, silmez veya dosya izinlerini değiştirmez. +- **Yalıtılmış Kimlik Bilgisi Kopyası ve Oturumu Kapatma:** İçe aktarma sırasında OpenCodeX, belirteci DeepSeek'in yetkili kimlik uç noktasında (`/auth-api/v0/users/current`) doğrular ve kendi kopyasını OpenCodeX kimlik deposuna kaydeder. OpenCodeX'te oturumu kapatmak veya hesabı kaldırmak yalnızca OpenCodeX'in yerel kopyasını siler; yukarı akış uzak oturum kapatmayı asla tetiklemez ve DeepSeek Harness Desktop oturumunuzu kapatmaz. +- **Arka Planda Belirteç Döndürme Yok ve 401 Durumunda Yeniden İçe Aktarma:** DSH oturum yetkileri otomatik bir arka plan yenileme uç noktasına sahip değildir. Yukarı akış HTTP 401 (`ACCOUNT_TOKEN_INVALID`) döndürürse OpenCodeX hesabı `needsReauth` olarak işaretler ve yönlendirmeyi durdurur. Erişimi geri yüklemek için kullanıcı `ocx login dsh-account` ile açıkça yeniden içe aktarmalıdır. +- **Üçüncü Taraf Sorumluluk Reddi:** OpenCodeX, oturum yetkilerini yeniden kullanan üçüncü taraf istemciler için DeepSeek tarafından herhangi bir resmi onay veya yetkilendirme iddiasında bulunmaz. + ## 3. API anahtarı kataloğu -opencodex 104 yerleşik önayar ile birlikte gelir: 85 anahtar tabanlı, 15 +opencodex 106 yerleşik önayar ile birlikte gelir: 85 anahtar tabanlı, 17 OAuth, üç yerel ve bir varsayılan ChatGPT iletme önayarı. Kontrol panelinin **Sağlayıcı ekle** seçicisi bir anahtar sağlayıcısının kontrol panelini açar, anahtarı doğrular ve saklar; doğrulama sağlayıcıya özgüdür. Dikkate değer @@ -389,7 +403,7 @@ yalnızca Cline IDE/CLI içinde mevcuttur; `minimax/minimax-m2.5` belgelenmiş A | Z.AI (GLM Kodlama) | `https://api.z.ai/api/coding/paas/v4` | | Zhipu AI (BigModel) | `https://open.bigmodel.cn/api/paas/v4` | | [BigModel Coding Plan — Responses (statik model listesi)](/guides/providers/#bigmodel-coding-plan-over-responses) | `https://open.bigmodel.cn/api/v1` | -| Qwen Cloud | Token planı (varsayılan): `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` · Kullandıkça öde: `https://dashscope.aliyuncs.com/compatible-mode/v1` · veya Özel | +| Qwen Cloud | Token planı (varsayılan): `https://token-plan.maas.qwencloudapi.com/compatible-mode/v1` · Kullandıkça öde: `https://dashscope.aliyuncs.com/compatible-mode/v1` · veya Özel | | Tencent Cloud Coding Plan | `https://api.lkeap.cloud.tencent.com/coding/v3` | | SiliconFlow | `https://api.siliconflow.cn/v1` | | Volcengine Ark · Coding Plan · Agent Plan | `https://ark.cn-beijing.volces.com/api/v3` · `https://ark.cn-beijing.volces.com/api/coding/v3` · `https://ark.cn-beijing.volces.com/api/plan/v3` | @@ -409,36 +423,20 @@ yeniler ve etkin Chat Completions modellerini (ve yalnızca Responses ile sunulu [OpenGateway panelinde](https://opengateway.ai/api-keys) bir anahtar oluşturun, ardından `ocx provider add opengateway` çalıştırın veya panelde **OpenGateway** seçin. Chat istekleri yapılandırılmış Bearer anahtarını kullanır; genel model listesi anahtarı doğrulamaz. +Responses protokolünde OpenGateway, yeniden oynatılan yerel özel araç çağrılarını reddeder; bu nedenle ön ayar, kod modundaki `exec` gibi Codex özel araçlarını göndermeden önce işlev araçlarına dönüştürür. +Araç sonucu içerik dizileri metin olarak gönderilir; görseller eşleşen araç sonucu grubundan sonra korunur. Ret yanıtları ve opak içerik dahil desteklenmeyen sonuç parçaları, gönderimden önce açık bir istemci girdi hatasıyla reddedilir. +`original` görsel ayrıntısı, ağ geçidinin kabul ettiği `high` değerine eşlenir; görsel baytları değişmez. +Yerel Responses yolu, yalnızca metin içeren boş tamamlamaları başarı olarak sunmadan önce de denetler. +Tükenen çıktı bütçeleri `max_output_tokens` ile `incomplete` üretir; proxy istenen bütçeyi artırmaz. Bütçesi kalan ve güvenle yeniden oynatılabilen boş bir tamamlama, mevcut gönderim sınırı içinde bir kez yeniden denenebilir. Açık `emptyCompletionRetry: false` veya `OCX_EMPTY_COMPLETION_RETRY=0` bu yeniden denemeyi kapatır. Metin, ret yanıtları, araç etkinliği ve bilinmeyen akış olayları yeniden oynatmayı engeller ve özgün akışı korur. Bu koruma diğer sağlayıcıları ve onların yeniden deneme varsayılanlarını değiştirmez. +Yeniden oynatma açık `store: false`, eksiksiz girdi ve yalnızca istemcide çalışan araçlar gerektirir; `web_search` gibi barındırılan bir araç, hiçbir araç olayı gözlenmemiş olsa bile otomatik yeniden oynatmayı engeller. + +**OpenCode Zen** (`opencode-zen`), [opencode.ai/auth](https://opencode.ai/auth) üzerinden alınan API anahtarıyla `https://opencode.ai/zen/v1` kullanır. Ücretsiz modeller yaklaşık 15–20 istek/dakika kısa pencere sınırına takılabilir (topluluk ölçümü; OpenCode RPM yayımlamaz). Hız sınırı 429 yanıtında `Retry-After` yoksa opencodex açıklama ve sentetik `Retry-After` ekler; geçerli üst kaynak başlıkları önceliklidir. Aynı anahtarla yeniden deneme [`retryOn429`](/tr/reference/configuration/) ile isteğe bağlıdır. + +Standart `opencode-zen` sağlayıcısının API anahtarıyla kimlik doğrulanan resmi `https://opencode.ai/zen/v1` ve `https://opencode.ai/inference/v1` yollarında Gemini, varsayılan `googleMode: ai-studio` modunda Google'ın yerel API'sini otomatik kullanır. Zen model kimlikleri, `-tiered` eki olmayan Flash kimlikleri dahil korunur. Claude, `googleMode` değerinden bağımsız olarak Anthropic Messages kullanır. Kaydedilmiş yapılandırmalar yüklenirken yalnızca bu yerel modellerin uygun eski Chat/Responses geçersiz kılmaları ve uygun, yalnızca Responses'a özgü `image_generation` barındırılan araç tercihleri yok sayılır. Özel sağlayıcılar, anahtar kimlik doğrulaması kullanmayan yollar ve diğer tercihler değişmez. + +**`opencode-free` desteği kaldırıldı**: OpenCode’un üst kaynak ücretsiz katman kapısı nedeniyle tüm modeller artık `403 permission_denied` döndürüyor ([#7255](https://github.com/lidge-jun/opencodex/issues/7255)). Mevcut yapılandırma yüklenirken ilgili kayıtlar ve bağımlı model referansları bellekte devre dışı bırakılır, işlem başına bir kez uyarılır ve dosya değiştirilmez. Varsayılan sağlayıcı buysa standart OpenAI OAuth yolu varsayılan olur. `opencode-go`, anahtarlı `opencode-zen` veya başka bir sağlayıcı kullanın. [OpenCode Zen koşulları](https://opencode.ai/docs/zen/). -**OpenCode Zen** (`opencode-zen`) ve anahtarsız **OpenCode Free** önayarı -`https://opencode.ai/zen/v1` adresini paylaşır. Bu ağ geçidindeki ücretsiz -modeller genellikle yaklaşık 15–20 istek/dakika civarında kısa pencereli bir -patlama sınırına ulaşır (topluluk tarafından ölçülmüştür; OpenCode RPM -yayınlamaz). Zen, `Retry-After` / `X-RateLimit-*` başlıkları olmadan genel hız -sınırı 429 yanıtları döndürebilir. Bu, OpenCode'un tanıttığı anahtarsız masaüstü -kotasından ayrıdır (`opencode-free` üzerinde 5 saatte yaklaşık 200 Big -Pickle/ücretsiz model isteği). Zen böyle bir 429'da `Retry-After`'ı atladığında -opencodex istemci hatasına sağlayıcı rehberliği ve sentetik bir `Retry-After` -ekler; bir yukarı akış `Retry-After`'ı yine de önceliklidir. Aynı anahtarla -bekle ve yeniden dene özelliği [`retryOn429`](/tr/reference/configuration/) -aracılığıyla isteğe bağlı kalır. - -**Anahtarsız `opencode-free` katmanı şu anda üçüncü taraf istemcilere kapalıdır.** Zen, -`x-opencode-session` başlığı olmadan gelen her isteği reddeder ve `MissingSessionID` hata -tipiyle "OpenCode's free tier can only be used in OpenCode" mesajını döndürür. Kapıda -yalnızca başlığın varlığı denetlenir; yani bir proxy uydurma bir değerle geçebilirdi, -opencodex bunu yapmaz. Bir oturum kimliği ile sürüm taşıyan `opencode/` -User-Agent üretmek, kendini OpenCode istemcisi ilan etmek demektir ve OpenCode bu -anahtarsız katman için üçüncü taraf entegrasyon sözleşmesi yayımlamamıştır; böyle elde -edilen bir HTTP 200, izin değil atlatılmış bir kabul denetimidir. Bu yüzden opencodex -kısıtlamayı aşmak yerine bildirir: `opencode-free` sağlayıcısına giden bir istek, yukarı -akıştaki kapıyı açıklayan bir hata döndürür. - -Aynı modellere giden desteklenen yol, [opencode.ai/auth](https://opencode.ai/auth) -üzerinden alınan bir OpenCode Zen API anahtarıyla kullanılan anahtarlı -**`opencode-zen`** sağlayıcısıdır. OpenCode ileride anahtarsız katman için desteklenen -bir üçüncü taraf yolu yayımlarsa opencodex bunu izleyebilir; o zamana kadar önayar -kısıtlamayı belgeler. Yukarı akış koşulları: [opencode.ai/docs/zen](https://opencode.ai/docs/zen/). +Geçerli Claude Desktop atamaları ve varsayılanları korunur. Kaynak model izin listesi boşalan kurallar devre dışı bırakılır; enjeksiyon modeli kaldırılırsa yerel alt ajan eşitlemesi de kapatılır. Sonraki yapılandırma kayıtları budanan referansları geri getirmez ve geçerli eşzamanlı düzenlemeleri korur. Çoğu, taşıyıcı anahtarla `openai-chat` adaptörünü kullanır; **Xiaomi MiMo** (`xiaomi`) gibi Anthropic uyumlu önayarlar `anthropic` adaptörünü (`x-api-key`) kullanır. @@ -667,6 +665,9 @@ tohumlanmıştır: | Codex girişi (Pool veya Direct) | `gpt-5.6-*` | 922.000 | | OpenAI (API anahtarı) | `openai-apikey/gpt-5.6-*` artı `*-pro` | 922.000 (922.000 maksimum girdi) | | OpenRouter | `openrouter/openai/gpt-5.6-sol`, `openrouter/openai/gpt-5.6-terra`, `openrouter/openai/gpt-5.6-luna` | 922.000 | +| Codex | `gpt-6.1-terra` | 500,000 / 872,000 | +| OpenAI API | `openai-apikey/gpt-6.1-terra` | 1,050,000 (128,000 max output) | +| OpenRouter | `openrouter/openai/gpt-6.1-terra` | 1,050,000 | | Cursor | `cursor/gpt-5.6-sol`, `cursor/gpt-5.6-terra`, `cursor/gpt-5.6-luna` | 1.000.000 | Yerel GPT-5.6 girdileri sabitlenmiş yukarı akış akıl yürütme merdivenlerini @@ -690,7 +691,7 @@ anahtar/abonelik belirteci ağ geçidi olarak kalır. **Cloudflare AI Gateway**, URL'ye doldurulan hesap + ağ geçidi kimliklerinize ihtiyaç duyar. Copilot karma hatlı bir katalog sunar: modeller (`gpt-5.3-codex`, `gpt-5.4`, -`gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`) ajan +`gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6.1-sol`, `gpt-6.1-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`) ajan trafiği için `/chat/completions`'ı reddeder, bu nedenle opencodex yerleşik varsayılan olarak bu modelleri Responses API üzerinden yönlendirirken diğer tüm Copilot modelleri sohbet tamamlamalarında kalır. Öncelik sırası: sabit hat @@ -779,5 +780,5 @@ gösterir. Canlı araştırmaya sahip sağlayıcılar: OpenAI/Codex, Anthropic, xAI, Cursor, Kimi, Google Antigravity, OpenRouter, DeepSeek, ClinePass, Z.AI, MiniMax, -Moonshot, Venice, Synthetic, DeepInfra, Neuralwatt ve a6api destekli herhangi -bir özel sağlayıcı. +Moonshot, Venice, Synthetic, DeepInfra, Neuralwatt ve a6api ya da Abliteration +destekli herhangi bir özel sağlayıcı. diff --git a/docs-site/src/content/docs/tr/guides/remote-link.md b/docs-site/src/content/docs/tr/guides/remote-link.md index da56c5c72fa..ef4af2ee5ec 100644 --- a/docs-site/src/content/docs/tr/guides/remote-link.md +++ b/docs-site/src/content/docs/tr/guides/remote-link.md @@ -55,7 +55,7 @@ Bağlanmak bu bilgisayardaki OpenCodex'i yeniden başlatır. Zaten çalışan Co - **Connected**, SSH tünelinin hazır ve Child'ın Home bağlantısını kullanabilir olduğu anlamına gelir. - **Reconnecting**, tünelin yeniden denendiği anlamına gelir. Yeniden deneme sırasında istekler geçici olarak `Retry-After` ile birlikte `503` döndürebilir. Kendi panosundan bağlanan bir Child üzerinde istek önce tünelin geri gelmesi için en fazla 15 saniye bekler. -- **Failed**, bağlantının ilgilenilmesi gerektiği anlamına gelir. SSH kimlik doğrulamasını, onaylanan ana bilgisayar anahtarını, yönlendirmeyi veya zaman aşımı nedenini kontrol edin. SSH tünelini açan taraf (eklediği Child için Home, kendi panosundan katıldığı bağlantı için Child); uyku, kesinti veya yeniden başlatmadan sonra kendiliğinden yeniden dener: zaman aşımı veya yönlendirme hatasından sonra yaklaşık dakikada bir, kimlik doğrulama hatasından sonra beş dakikada bir. Değişmiş bir ana bilgisayar anahtarı asla yeniden denenmez. Hemen yeniden denemek için Home panosunda **Retry** düğmesini seçin veya Home üzerinde `ocx link reconnect --link-id ` komutunu çalıştırın. Bu, OpenCodex'i yeniden başlatmadan yalnızca o bağlantının tünelini yeniden başlatır. +- **Failed**, bağlantının ilgilenilmesi gerektiği anlamına gelir. SSH kimlik doğrulamasını, onaylanan ana bilgisayar anahtarını, yönlendirmeyi veya zaman aşımı nedenini kontrol edin. SSH tünelini açan taraf (eklediği Child için Home, kendi panosundan katıldığı bağlantı için Child); uyku, kesinti veya yeniden başlatmadan sonra kendiliğinden yeniden dener: zaman aşımı veya yönlendirme hatasından sonra bir dakika sonra, ardından her yeni başarısızlıkta bekleme ikiye katlanarak en fazla beş dakikada bir; kimlik doğrulama hatasından sonra beş dakikada bir. Değişmiş bir ana bilgisayar anahtarı asla yeniden denenmez. Hemen yeniden denemek için Home panosunda **Retry** düğmesini seçin veya Home üzerinde `ocx link reconnect --link-id ` komutunu çalıştırın. Bu, OpenCodex'i yeniden başlatmadan yalnızca o bağlantının tünelini yeniden başlatır. Bağlantı başarısız olduğunda sistem sessizce yerel bir sağlayıcıya geçmez. diff --git a/docs-site/src/content/docs/tr/guides/sidecars.md b/docs-site/src/content/docs/tr/guides/sidecars.md index fdd53a327ac..a52dab0127f 100644 --- a/docs-site/src/content/docs/tr/guides/sidecars.md +++ b/docs-site/src/content/docs/tr/guides/sidecars.md @@ -107,9 +107,13 @@ Yönlendirilen model sağlayıcısının `noVisionModels` listesinde yer aldığ bu model için `modelInputModalities` ile salt metin olarak bildirildiğinde ve bir istek görsel taşıdığında, opencodex kullanılabilir bir vision sidecar planı varsa ana çağrıdan **önce** her görseli açıklar ve onu metinle değiştirir. Kullanılabilir -bir plan yoksa ham görsel salt metin arka ucuna iletilmek yerine kaldırılır. Model -kataloğu sidecar kapsamında olan her model için görsel girdisini bildirir. Kombolar, -her üye görselleri yerel olarak veya bir sidecar üzerinden kabul ettiğinde ve kombonun +bir plan yoksa ham görsel salt metin arka ucuna iletilmek yerine kaldırılır. +`visionSidecar.enabled: false` olduğunda model kataloğu, özel kayıtlar ve kombo üyeleri +dahil salt metin modellere sidecar destekli görsel girdisi eklemez. Yerel görsel desteği +ve açıkça yapılandırılmış görsel modaliteleri korunur. `enabled` belirtilmezse sidecar +destekli görsel girdisinin bildirilmesi açık kalır; katalogda görünmesi, sidecar kimlik +bilgilerinin veya kapasitesinin kullanılabilir olduğunu doğrulamaz. Kombolar, +her üye görselleri yerel olarak veya etkinleştirilmiş bir sidecar üzerinden kabul ettiğinde ve kombonun `imageInput` ayarı devre dışı olmadığında görsel girdisini bildirir; böylece Codex uygulaması gibi istemciler, sidecar çalışmadan önce ekleri engellemek yerine kabul eder. `visionSidecar.model` olmadığında veya boş @@ -196,6 +200,8 @@ akıl yürütmeyi, zaman aşımını ve sınırı korur. atlanan anahtarları değiştirmeden bırakır. `timeoutMs` çalışma zamanı tamsayı sınırlarını kullanır (1–2147483647 ms). +Vizyon'un geçerli açma/kapatma durumunu `PUT /api/sidecar-settings` üzerinden değiştirmek, kayıttan sonra yönetilen kataloğu ve önbelleği yeniler; `catalogRefreshPending=true`, yenilemenin tamamlanmadığını bildirir ve istemci onayı anlamına gelmez. + Web arama sidecar kartı aynı denetim yapısını taşır: model seçicisinin ilk satırı **Kapalı (Off)**'dır. Kapatmak OpenCodex'in `web_search` yakalamasını durdurur ve Codex entegrasyonu `~/.codex/config.toml` dosyasına `web_search = "disabled"` yazar; çünkü Codex kendi diff --git a/docs-site/src/content/docs/tr/guides/sub-agent-surface.md b/docs-site/src/content/docs/tr/guides/sub-agent-surface.md index 4b2559f0a04..fcca69f6d4e 100644 --- a/docs-site/src/content/docs/tr/guides/sub-agent-surface.md +++ b/docs-site/src/content/docs/tr/guides/sub-agent-surface.md @@ -47,6 +47,42 @@ olarak ayarlanmış veya `multi_agent_version` alanı olmayan bir girdi uygundur Gerçek bir `"v1"` sabitlemesi hariç tutulur çünkü modelin diğer işbirliği yüzeyine ait olduğunu belirtir. +## `spawn_agent` hangi modelleri sunar? + +Codex, `spawn_agent` araç açıklamasında en fazla beş model listeler ve bunları kendisi seçer: seçicide görünen katalog satırlarını `priority` değerine göre küçükten büyüğe sıralar ve ilk beşini alır. **Subagents** sayfasındaki kadro (`subagentModels`) bu listeyi yalnızca kendi modellerine kadro sırasıyla en düşük öncelikleri vererek etkiler (hesap seçicisi yoksa `0` ile `4` arası). Listeyi kadroyla sınırlamaz. + +Bir ile dört arasında model öne çıkarırsanız Codex kalan yuvaları önceliğe göre sonraki satırlarla doldurur. Bu satırları siz seçmediniz ve pano hangilerinin girdiğini göstermez. Hangi satırların gireceği kataloğa bağlıdır: + +| Katalog | Kalan yuvaları dolduran satırlar | +| --- | --- | +| Codex hesap seçicisi yok | Diğer yönlendirilmiş `provider/model` satırları. Hepsi `5` önceliğini paylaştığı için sağlayıcı adına, ardından model kimliğine göre alfabetik sırayla gelir. Öne çıkarılmamış yerel GPT satırlarının önceliği en az `100` artı öne çıkarılan kimlik sayısıdır; bu yüzden daha sonra gelirler. | +| Codex hesap seçicisi var | Hesaba bağlı yerel satırlar (`/`); öne çıkarılan satırların hemen ardından gelir. Öne çıkarılmamış yönlendirilmiş satırlar `1005` önceliğine taşınır ve onlardan sonra gelir. | + +Örneğin hesap seçicisi yokken ve kadro şöyleyken: + +```json +{ "subagentModels": ["xai/grok-4.5", "anthropic/claude-sonnet-5"] } +``` + +| Yuva | Model | Öncelik | Neden | +| ---: | --- | ---: | --- | +| 1 | `xai/grok-4.5` | `0` | Kadronun ilk girdisi | +| 2 | `anthropic/claude-sonnet-5` | `1` | Kadronun ikinci girdisi | +| 3 | `deepseek/deepseek-v4-flash` | `5` | Alfabetik olarak kalan ilk yönlendirilmiş satır | +| 4 | `google/gemini-3-pro` | `5` | Sonraki yönlendirilmiş satır | +| 5 | `moonshot/kimi-k3` | `5` | Sonraki yönlendirilmiş satır | +| 6+ | `zai/glm-5.2`, ardından yerel GPT satırları | `5`, ardından `102` | Beşin dışında | + +Kadro boşsa opencodex hiçbir satırı sabitlemez ve beş yuva kataloğun normal önceliklerine göre belirlenir. + +Doldurulan yuvalar sıradan sunumlardır; ana ajan bu modellerden birini seçebilir. Liste bir izin listesi de değildir: ana ajan tam adını verirse Codex beşin dışındaki bir katalog modelini de kabul eder. Codex'in sunduğu listeyi denetlemek için: + +- Beş model öne çıkarın. Kadro doluysa doldurulacak yuva kalmaz. +- Hiç sunulmasını istemediğiniz bir modeli devre dışı bırakın ya da sağlayıcısının `selectedModels` listesine eklemeyin. Devre dışı ya da seçilmemiş bir model seçiciden çıkar ve yuva dolduramaz. +- `modelPickerOrder`, Codex'in sıralamada kullandığı öncelikleri değiştirir. Yalnızca yönlendirilmiş satırlardan oluşan bir liste, listelenen öne çıkarılmamış satırları `1000` ve üzerine taşır; bu yüzden listelenmemiş yönlendirilmiş satırlar yuvaları önce doldurur. Yalın bir yerel kimlik içeren liste, öne çıkarılan satırlar dahil tüm seçiciyi sıralar: listelenen satırlar önce gelir ve listelenmemiş her satırdan önce sunulan yuvaları alır. Bkz. [Model ordering](/guides/model-ordering/). + +v2'de Codex beşi almadan önce `"disabled"` olarak sabitlenmiş satırları atlar; geri kalanı aynı şekilde çalışır. opencodex v2 yönlendirmesindeki kadro (`{{roster}}`) yalnızca beşin içindeki yapılandırılmış kadro modellerini adlandırır; dolgu modelleri Codex'in araç açıklamasında görünür, opencodex yönlendirmesinde hiç görünmez. Codex kataloğu oturum başlarken okur; bu yüzden kadro değişikliği yeni oturumlarda geçerli olur. + ## Yetkilendirme modeli ve çaba Kontrol panelinin **Alt ajan yetkilendirmesi** üç ilgili ayarı denetler: @@ -266,6 +302,10 @@ Seçicide gizlenmiş olabilir, beş modelli görüntüleme sınırının dışı katalogda eksik olabilir veya v1'e sabitlenmiş olabilir. Bir `"v2"`, `null` veya bulunmayan arayüz değeri uygundur; gerçek bir `"v1"` sabitlemesi uygun değildir. +### `spawn_agent` neden öne çıkarmadığım modelleri listeliyor? + +Codex sunduğu en fazla beş yuvayı katalogdan önceliğe göre doldurur. Beşten az model öne çıkardığınızda kalan yuvaları sonraki satırlar alır; yukarıdaki "`spawn_agent` hangi modelleri sunar?" bölümüne bakın. + ### Mod değişiklikleri çalışan oturumları etkiler mi? Hayır. Modu değiştirdikten sonra yeni bir Codex oturumu başlatın. Uzun süredir diff --git a/docs-site/src/content/docs/tr/guides/subagent-v1-default.md b/docs-site/src/content/docs/tr/guides/subagent-v1-default.md index 3249a294bd5..11ebaff1e56 100644 --- a/docs-site/src/content/docs/tr/guides/subagent-v1-default.md +++ b/docs-site/src/content/docs/tr/guides/subagent-v1-default.md @@ -130,3 +130,13 @@ ocx v2 mode v1 ``` CLI onay istemez. Aynı ayardır; bu sayfanın anlattıklarını bilerek seçin. + +## Eşzamanlı alt ajan sınırı + +**Models → Model settings** içindeki alt ajan sınırı, ana ajan hariç eşzamanlı alt ajanların +azami sayısını belirler. Genel v2 bayrağı kapalı olan hibrit mod dahil v1, base ve v2'de +kullanılabilir. Arayüz modu bu sınırdan bağımsız olarak seçilir. + +Sınırı artırmak daha fazla alt ajana izin verir; onları otomatik oluşturmaz veya daha hızlı +çalışmayı garanti etmez. Değişiklikten sonra yeni bir Codex oturumu başlatın. Uzun süredir çalışan +App ana bilgisayarı eski sınırı kullanmaya devam ediyorsa o ana bilgisayarı yeniden başlatın. diff --git a/docs-site/src/content/docs/tr/reference/cli.md b/docs-site/src/content/docs/tr/reference/cli.md index 4a24b1bc2f0..e8bcea6d8fc 100644 --- a/docs-site/src/content/docs/tr/reference/cli.md +++ b/docs-site/src/content/docs/tr/reference/cli.md @@ -36,7 +36,7 @@ yönetim API'sine gidiş-dönüş yapar. Durdurulmuş veya erişilemeyen bir pro yapılandırma işlemleri olarak açıkça belgelenen komutlar, bunun yerine canlı bir proxy olmadan yapılandırma dosyasını doğrulayabilir ve düzenleyebilir. -`ocx system codex-cli-update check` canlı proxy gerektirmez ve paket kayıt defterine istek göndermez. Yapılandırmada belirtilen kurulum adayına ilişkin provenance meta verilerini, maskelenmiş yürütülebilir dosya konumu ve sahiplik kanıtı dâhil, sınırlı biçimde inceler. Yayımlanmış başlatıcıdan gelen güvenilir bağlam aday anlık görüntüsünü doğrular; Codex'in başarıyla çalıştırıldığını doğrulamaz. Bu tek seferlik denetim Codex'i hiçbir zaman çalıştırmadığından, ortamdan ve kalıcı kayıtlardan gelen adaylar yalnızca raporlanır (`managed: false`, genellikle `selection_unattested`). JSON çıktısında `candidateAvailable`, `candidateVersion` ve `candidateSource` alanları bulunur; `selectionAttested` değeri ise `false` kalır. Yapılandırmada belirtilen kurulum adayını incelemek için yayımlanmış başlatıcıdan gelen güvenilir bağlam gerekir; Bun ile veya kaynak koddan doğrudan başlatıldığında bu kanıt bulunmadığından ortamdaki ve kalıcı kayıtlardaki aday durumu yok sayılır ve POSIX'te `candidate_unavailable`, Windows'ta ise `windows_inspection_deferred` bildirilebilir. Windows'ta bu ilk parça, aday veya yapılandırma yollarında hiçbir dosya sistemi G/Ç işlemi yapmaz. Yalnızca güvenilir başlatıcının yakaladığı mutlak bir ortam adayı sözcüksel olarak uygulama paketi ya da sürüm yöneticisi etiketi alabilir; diğer tüm Windows adayları kapalı başarısızlıkla reddedilir. Komut yazılım kurmaz veya onarmaz, Codex ya da npm çalıştırmaz, çalışan bir sürece müdahale etmez ve yapılandırmaya ya da önbellek durumuna yazmaz. +`check` kayıt defterini sorgulamaz ve Codex’i çalıştırmaz. POSIX üzerinde çözümleyicinin seçtiği kanonik yol, yayımlanmış başlatıcının yakaladığı ortam adayıyla aynıysa `selectionAttested: true` olabilir. Bağlantılı npm-global paket bildirimi sahipliği doğrularsa ve aday sürüm bildirildiyse bu sürüm bildirimdeki sürümle eşleşirse `managed: true` olabilir; kalıcı adaylar doğrulanmaz. Windows, aday veya yapılandırma dosyalarını okumadan incelemeyi erteler. Doğrudan Bun/kaynak çalıştırması başlatıcı kanıtı sağlamaz. `plan`, yalıtılmış geçici npm yapılandırması, önbelleği ve günlükleriyle resmî kayıt defterini sorgular; etkin oturumları veya eksik süreç gözlemini reddeder. Yazılım kurmaz ve süreçlere sinyal göndermez. [Komutların ve ret nedenlerinin tam açıklamasına (İngilizce)](/reference/cli/agents/#read-only-codex-cli-update-plan) bakın. Windows x64 kurulum gözlemi için [`attest`](/tr/reference/cli/agents/) komutuna bakın. Açık yollar verilmezse komut, kanıta bağlı başlatıcı anlık görüntüsünün belirlediği seçili adayı gözlemler; güncelleme yetkisi veya çalışma zamanı seçimi doğrulanmaz. diff --git a/docs-site/src/content/docs/tr/reference/cli/agents.md b/docs-site/src/content/docs/tr/reference/cli/agents.md index e328f84b62c..132a02705d3 100644 --- a/docs-site/src/content/docs/tr/reference/cli/agents.md +++ b/docs-site/src/content/docs/tr/reference/cli/agents.md @@ -302,10 +302,15 @@ Başsız çalışma zamanı ayarlarını, başlatmayı, senkronizasyonu, tanıla güncellemeleri yönetin. `ocx system codex-restart --yes`, `ocx sync --restart-codex` ile aynı modül -üzerinden Codex app-server'larını yeniden başlatır ve Codex masaüstü -uygulamasını tamamen kapatıp yeniden başlatır. Proxy'nin kendisi Codex -uygulamasının içinde çalışıyorsa, tamamlayamayacağı bir devri vaat etmek -yerine eyleme geçirilebilir bir iletiyle reddeder. +üzerinden Codex app-server'larını yeniden başlatır. Model seçici uygulama +açılırken oluşturulduğu için Codex masaüstü uygulamasını güncel model kataloğu yazılmadan önce başlatılmışsa veya bu istek `model_catalog_json` bağını değiştirdiyse tamamen kapatıp yeniden başlatır. Aksi +halde uygulama açık kalır, kendi app-server'ını yeniden başlatır ve +gönderilmemiş taslakları ve bekleyen onayları korur. Uygulama veya katalog +durumu okunamazsa tam yeniden başlatma yapılır. Proxy'nin kendisi Codex +uygulamasının içinde çalışıyorsa masaüstü yeniden başlatması, tamamlayamayacağı +bir devri vaat etmek yerine eyleme geçirilebilir bir iletiyle reddeder. + +Bu isteğin `model_catalog_json` bağını değiştirmesi de kabuk katalog dosyası yazıldıktan sonra başlamış olsa bile masaüstünün yeniden başlatılmasını gerektirir. ```bash ocx system settings --stream-mode eager-relay @@ -317,7 +322,7 @@ ocx system settings --stream-mode eager-relay ocx system codex-cli-update check --json ``` -`check` paket kayıt defterine istek göndermez ve yapılandırmada belirtilen kurulum adayına ilişkin provenance kanıtını, maskelenmiş yürütülebilir dosya konumu ve sahiplik kanıtı dâhil, sınırlı biçimde inceler. Yayımlanmış başlatıcıdan gelen güvenilir bağlam aday anlık görüntüsünü doğrular; Codex'in başarıyla çalıştırıldığını doğrulamaz. Bu tek seferlik komut Codex'i hiçbir zaman çalıştırmadığından, ortamdan ve kalıcı kayıtlardan gelen adaylar yalnızca raporlanır (`managed: false`, genellikle `selection_unattested`). JSON çıktısında `candidateAvailable`, `candidateVersion` ve `candidateSource` alanları bulunur; `selectionAttested` değeri ise `false` kalır. Yapılandırmada belirtilen kurulum adayını incelemek için yayımlanmış başlatıcıdan gelen güvenilir bağlam gerekir; Bun ile veya kaynak koddan doğrudan başlatıldığında bu kanıt bulunmadığından ortamdaki ve kalıcı kayıtlardaki aday durumu yok sayılır ve POSIX sistemlerinde `candidate_unavailable` bildirilebilir. Windows'ta bu ilk parça, aday veya yapılandırma yollarında hiçbir dosya sistemi G/Ç işlemi yapmaz. Yalnızca güvenilir başlatıcının yakaladığı mutlak bir ortam adayı sözcüksel olarak uygulama paketi ya da sürüm yöneticisi etiketi alabilir; diğer tüm Windows adayları kapalı başarısızlıkla reddedilir. Bu parça kalıcı seçim durumunu hiç okumadığından, ortam adayı yakalanmamış olan Windows çalıştırmaları `candidate_unavailable` yerine `windows_inspection_deferred` bildirir: komut bir Codex CLI'nin kurulu olup olmadığını gözlemleyemez, bu yüzden aday bulunmadığını iddia etmek yerine incelemenin ertelendiğini bildirir. Komut Codex veya bir paket yöneticisi çalıştırmaz, shim'i onarmaz, yapılandırmaya ya da önbellek durumuna yazmaz, hiçbir süreci durdurmaz ve hiçbir şey kurmaz. Uygulamayla birlikte paketlenmiş adaylar, tanınan sürüm yöneticisi yollarında bulunan adaylar, doğrulanmamış bağımsız adaylar ve belirsiz shim durumları `unmanaged` veya `unknown` olarak raporlanır; hiçbir zaman `managed` olarak sınıflandırılmaz. +`check` kayıt defterini sorgulamaz ve Codex’i çalıştırmaz. POSIX üzerinde çözümleyicinin seçtiği kanonik yol, yayımlanmış başlatıcının yakaladığı ortam adayıyla aynıysa `selectionAttested: true` olabilir. Bağlantılı npm-global paket bildirimi sahipliği doğrularsa ve aday sürüm bildirildiyse bu sürüm bildirimdeki sürümle eşleşirse `managed: true` olabilir; kalıcı adaylar doğrulanmaz. Windows, aday veya yapılandırma dosyalarını okumadan incelemeyi erteler. Doğrudan Bun/kaynak çalıştırması başlatıcı kanıtı sağlamaz. `plan`, yalıtılmış geçici npm yapılandırması, önbelleği ve günlükleriyle resmî kayıt defterini sorgular; etkin oturumları veya eksik süreç gözlemini reddeder. Yazılım kurmaz ve süreçlere sinyal göndermez. [Komutların ve ret nedenlerinin tam açıklamasına (İngilizce)](/reference/cli/agents/#read-only-codex-cli-update-plan) bakın. Windows üzerinde `CODEX_CLI_PATH=codex` gibi yalın bir komut, uzak yol veya aygıt yolu aday olarak yakalanırsa `candidate_path_unavailable` bildirilir. Aday yakalanmıştır; ancak yolu bu inceleme için uygun değildir. diff --git a/docs-site/src/content/docs/tr/reference/cli/lifecycle.md b/docs-site/src/content/docs/tr/reference/cli/lifecycle.md index f5791a12523..86366c641e1 100644 --- a/docs-site/src/content/docs/tr/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/tr/reference/cli/lifecycle.md @@ -105,7 +105,7 @@ dahildir. Durumu yedekleyin ve yalnızca bu kapsamın tamamını istiyorsanız ### `ocx recover-history --ocx-compaction --yes` -Yönlendirilmiş bir sağlayıcı üzerinden sıkıştırılmış bir görevi yerel Codex ile sürdürmeden önce geçmişini onarın. Komut UUID ile yalnızca bir görevi seçer, önce özel ve bayt bayt bir yedek kaydeder, ardından yalnızca OpenCodeX'e ait `ocx1:` sıkıştırma durumunu yerel Codex'in yeniden oynatabileceği düz bir özete dönüştürür. Yerel şifreli içerik ve diğer görevler değişmeden kalır. Komutu çalıştırmadan önce seçili görevi kapatın; işlem sırasında rollout değişirse kurtarma dosyayı değiştirmeden durur. +Yönlendirilmiş bir sağlayıcı üzerinden sıkıştırılmış bir görevi yerel Codex ile sürdürmeden önce geçmişini onarın. Komut UUID ile yalnızca bir görevi seçer, önce özel ve bayt bayt bir yedek kaydeder, ardından yalnızca OpenCodeX'e ait `ocx1:`/`ocx2:` sıkıştırma durumunu yerel Codex'in yeniden oynatabileceği düz bir özete dönüştürür. Yerel şifreli içerik ve diğer görevler değişmeden kalır. Komutu çalıştırmadan önce seçili görevi kapatın; işlem sırasında rollout değişirse kurtarma dosyayı değiştirmeden durur. ### `ocx uninstall` · `ocx remove` @@ -206,7 +206,7 @@ takdirde 1 ile çıkar, bu da onu servis probları için uygun hale getirir. Kimliği doğrulanmamış `GET /readyz` uç noktası aracılığıyla senkronizasyon sonrası hazırlığı kontrol edin. Hazır olduğunda `200` veya `pending` ve terminal `failed` için `Retry-After: 1` ile `503` döndürür. Temizlenmiş HTTP kimliği -`{service, version, uptime, pid, port, status, protocol, minimumClientProtocol, managementUrl}` şeklindedir. `protocol` hub'ın güncel uzak protokolünü, `minimumClientProtocol` uyumlu en düşük istemci protokolünü ve `managementUrl` tarayıcıya görünen kanonik yönetim origin'ini belirtir. `/readyz` içermeyen +`{service, version, uptime, pid, port, status, protocol, minimumClientProtocol, managementUrl, features}` şeklindedir. `protocol` hub'ın güncel uzak protokolünü, `minimumClientProtocol` uyumlu en düşük istemci protokolünü ve `managementUrl` tarayıcıya görünen kanonik yönetim origin'ini belirtir. `/readyz` içermeyen eski proxy'ler `unreachable` olarak kapalı başarısız olur; `/healthz` hazırlık değil, ayrı bir canlılıktır. Komut varsayılan olarak bir prob gerçekleştirir; `--wait`, hazır olana veya zaman aşımına kadar yoklar, ancak terminal `failed` @@ -238,6 +238,10 @@ Doctor asla kimlik bilgilerini değiştirmez veya onarımlar uygulamaz. ### `ocx sync [--restart-codex] [--restart-app-server-only]` +> **Remote Link Child:** `ocx sync` Home'un Codex ayarlarını da uygular ve istemci çalışma zamanı bu uygulamadan önce başlamışsa, yeniden başlatma bayrağı olmadan da onu yeniden başlatır. Yeniden başlatma başarısız olursa önceki çalışma zamanı hizmet vermeye devam eder ve komut sıfırdan farklı bir kodla çıkar. `ocx status --json` ve `ocx link status --json` içindeki ilgili alanlar için [Remote Link apply parity](/reference/cli/lifecycle/#remote-link-apply-parity) (İngilizce) bölümüne bakın. + +Açıkça istenen yeniden başlatma, katalog ve önbellek baytları değişmese bile `config.toml` değişikliği başarıyla kaydedildikten sonra da çalışır. Yalnızca yapılandırmayı işleyen bir işlem değişiklik yapmadığında, reddedildiğinde, ön kontrolde kaldığında veya yazma geri alındığında yeniden başlatma tetiklenmez. Yeniden başlatma bayrağı olmadan sync, Codex’i otomatik olarak yeniden başlatmaz. + Yapılandırılmış her sağlayıcıdan canlı model listesini alın ve birleştirilmiş kataloğu Codex'e yeniden enjekte edin. Bir sağlayıcı ekledikten sonra veya kullanılabilir modelleri yenilemek için çalıştırın. @@ -272,6 +276,8 @@ alınamaz, eski bir seçici ise alınabilir. Komut Codex uygulamasının içinden çalıştırıldığında yeniden başlatma ayrılmış bir yardımcıya devredilir ve bu oturum uygulamayla birlikte sona erer. +**Sabitlenmiş eşitleme.** `ocx sync --expect-link ` (Remote Link Child) ve `ocx sync --expect-hub-key ` (hub istemcisi), yalnızca bu makine hâlâ o bağlantıya veya o anahtarla bağlıysa eşitler. Aksi halde hiçbir şey değişmez, yerel sağlayıcılar eşitlenmez, komut 5 koduyla çıkar ve stderr'in son satırı `{"phase":"child_link_mismatch"|"hub_client_mismatch","reason":...}` olur. Hatalı veya tekrarlanan bir bayrak 2 koduyla çıkar. Ayrıntılar: [İngilizce başvuru](/reference/cli/lifecycle/#sync-only-from-the-expected-home---expect-link-and---expect-hub-key). + ### `ocx sync-cache [--restart-codex] [--restart-app-server-only]` Codex'in yerel model seçici önbelleğini geçersiz kılın, böylece aktif opencodex diff --git a/docs-site/src/content/docs/tr/reference/cli/messaging.md b/docs-site/src/content/docs/tr/reference/cli/messaging.md new file mode 100644 index 00000000000..41a0ede30fa --- /dev/null +++ b/docs-site/src/content/docs/tr/reference/cli/messaging.md @@ -0,0 +1,274 @@ +--- +title: Codex mesajlaşma +description: Yüklü Codex oturumlarına yerel olarak veya açıkça tanımlanmış SSH eşleri üzerinden ilişkili mesajlar gönderin. +--- + +`ocx message`, mevcut Codex oturumları için isteğe bağlı arayüzdür. Proxy veya +app-server başlatmaz, konuşma sürdürmez, beceri kurmaz. Linux/macOS Unix soketleri +desteklenir; Windows açık hatayla durur. Mevcut Codex daemon'ı deneysel +`thread/queue/add` desteklemelidir. CLI sürümü sabitlemek veya `CODEX_CLI_PATH` +seçmek gerekmez. + +## Keşfetme ve gönderme + +```bash +ocx message sessions --json +printf '%s\n' 'Please review the current change and send your findings back.' | + ocx message send --name 'reviewer' --stdin --json +``` + +Keşif yalnızca yüklü konuşma kimliklerini, adlarını ve çalışma durumlarını döndürür; +geçmiş, önizleme veya çalışma dizini döndürmez. Tam UUID (`--thread`) veya benzersiz +tam ad (`--name`) kullanın. Eksik, yinelenen, tamamlanmamış veya yüklü olmayan +hedefler reddedilir; yaklaşık eşleştirme ya da kayıtlı oturumlara geri dönüş yoktur. + +Mevcut denetim soketi, diğer Codex entegrasyon komutlarıyla aynı ana dizin ilkesi +kullanılarak etkin `CODEX_HOME` altında çözümlenir. Bağlanmadan önce çözümlenen +yolun sizin UID'nize ait soket olması gerekir. Kök dahil tüm gerçek üst dizinler +size veya root'a ait olmalı ve root'a ait `/tmp` gibi sticky dizinler dışında +grup/diğer kullanıcılarca yazılamamalıdır. Çözümlenen yol kontrolü geçiyorsa +sembolik bağlantılı ana dizinler kabul edilir. Güvenilmeyen yollar, çözümlenmiş +yol ayrıntıları verilmeden `untrusted_socket` ile reddedilir. + +Keşif, son yüklü hedef kontrolü ve kuyruk gönderimi aynı soket bağlantısını +kullanır. Yerel CLI yardımcısı, kurulum, onarım veya yeniden bağlantı yapılmaz. +Yerel kuyruk desteği olmayan daemon `unsupported_queue` döndürür. + +Yalnızca UTF-8 stdin kabul edilir: NUL içermeyen, boş olmayan en fazla 16 KiB metin. +Girdi, keşif, yeniden doğrulama ve gönderimin toplam süresi 30 saniyedir. Her RPC +en fazla 10 saniye sürer. Tam zarf 32 KiB ile sınırlıdır. Mesaj Unix RPC bağlantısı +üzerinden iletilir; başlatılan işlemlerin argümanlarına konmaz. + +## Gönderen ve yanıtlar + +Sarmalayıcı mesaj UUID'si üretir; yüklü `CODEX_THREAD_ID` ve daemon meta verisinden +gönderen kimliği/adı ekler. Bu bağlam **kimliği doğrulanmış eş yetkisi değildir**; +kullanıcı onayı veya yetki yükseltme sağlamaz. Geçersiz/yüklü olmayan gönderen +iddiası reddedilir; eksik bağlamda gönderen bilinmeyen kalır, yanıt rotası tahmin edilmez. + +Eş mesajları izin onayı veya ayar geçersiz kılması olarak değil, metin olarak +kuyruğa alınır. Alıcı ajanın çalıştırma ortamı izinleri uygular; zarf yönergeleri +teknik yetkilendirme sınırı veya model davranışı garantisi değildir. + +Varsayılan tür `request`'tir. Zarftaki üretilmiş `replyCommand` ile yanıtlayın veya +yanıtı açıkça ilişkilendirin: + +```bash +printf '%s\n' 'Review findings: ...' | + ocx message send --thread --kind response \ + --in-reply-to --stdin --json +``` + +`response`, `--in-reply-to` gerektirir; diğer türler bunu reddeder. Bilgilendirme +için `--kind notification` kullanın. Yanıt/bildirimler alındı onayı istemez. +Ajandan gelen normal son yanıt eşe gönderilmez; eksik rotalar tahmin edilmemelidir. +Genel istem değişikliği veya onay döngüsü eklenmez. + +## Alındılar ve çıkış kodları + +`--json`, `ocx-message/1` sürümlü tek alındı üretir: mesaj UUID'si, türü, yanıt +ilişkisi, gönderen/hedef meta verisi ve gönderim durumu. Gövde metni veya ham +daemon çıktısı içermez. Alındı oluşmadan önceki girdi/kurulum hataları, +`not_sent` ile `ocx-message-error/1` kullanır; keşif hataları aynı şemayı gönderim +durumu olmadan kullanır. Geçersiz kullanım, I/O öncesinde stderr ile 64 koduyla çıkar. + +| Durum | Çıkış | Anlamı | +| --- | --- | --- | +| `not_sent` | 1 | Doğrulama/keşif/yeniden doğrulama başarısız, kuyruk çerçevesi yazılmadı veya daemon açıkça reddetti. | +| `queued` | 0 | İlişkili daemon yanıtı gönderimi onayladı; alıcının **işlemesini değil**. | +| `unknown` | 3 | Gönderim olmuş olabilir; onay kayıp, bozuk, eşleşmiyor veya iptal edilmiş. **Yeniden göndermeyin.** | + +Uygulama mesaj UUID'si kuyruk isteğinin `clientUserMessageId` değeridir. Daemon +gönderim kimliği ayrıdır; hiçbiri alıcı işlemeyi kanıtlamaz. Kuyruğa alma, etkin +turu yönlendirme değildir; meşgul turun bitmesini bekleyebilir. OpenCodex otomatik +yeniden gönderim, takip sorgusu, kalıcı saklama veya teslim izleme yapmaz. +Son yüklü hedef kontrolü ile kuyruk gönderimi atomik değildir. Hedef sonra +kaldırılırsa açık ret `queue_rejected` ile `not_sent`; kayıp/geçersiz yanıt +`unknown` olur. OpenCodex hedefi sürdürmez veya yeniden denemez. + +## Açıkça tanımlanmış uzak eşler + +Uzak mesajlaşma, Remote Link ve normal proxy başlangıcından bağımsızdır. Sağlayıcı +yönlendirmez, sağlayıcı kimlik bilgisi paylaşmaz, yerel daemon başlatmaz. İki +düğümde de uyumlu OpenCodex CLI ve kuyruk destekli mevcut daemon gerekir. Linux +için yalıtılmış birlikte çalışabilirlik kanıtı vardır; macOS ayrı doğrulama +gerektirir. Windows uzak aktarımı desteklenmez. + +Her düğümde mesajlaşmayı açıkça etkinleştirin. Alıcıda ön plan yöneticisini çalışır tutun: + +```bash +ocx message enable --port 39176 --json +ocx message serve --json +``` + +Başlatan düğümde mevcut OpenSSH hedefini/takma adını kullanın. Onaylamadan önce +sunulan parmak izini güvenilir kaynaktan doğrulayın: + +```bash +ocx message hosts probe --ssh worker --json +ocx message hosts add worker --ssh worker --fingerprint 'SHA256:' --json +ocx message hosts list --json +ocx message serve --host worker --json +``` + +Normal SSH ayarlarınızda `HashKnownHosts` açık olsa bile özel sorgu kayıtları +takma ad önekli biçimi korur; normal known_hosts dosyanız değişmez. Her kayıt +girişimi kendine ait, işleme özgü sabitlenmiş anahtar geçici dosyası kullanır. +SSH yardımcısı sonuçlanınca başarısızlık dahil temizlenir; başka değişiklik +yöneticisinin kilidine bağlı değildir. Temizlik doğrulanamazsa başarı değil, +bilinmeyen depolama sonucu bildirilir. Kurtarma günlüğü ayrıdır. + +`hosts add worker`, `worker` hedefine giden mesajlara izin verir; **başlatan +düğüme gelişigüzel geri mesajlara değil**. SSH denetim alışverişi mevcut SSH +girişinizle alıcıda giden yönü yetkilendirir. Kayıt alındısı ve `hosts list`, +her düğümün yerel gelen mesaj ilkesini gösterir. Başlatan düğümde varsayılan +`responses-only`'dir: eş yalnızca bu düğümün gönderdiği isteğe ilişkili yanıt +bekleyen özgün yerel gönderen oturumlarını görebilir ve yanıtlayabilir. Yanıt; +istek UUID'si, kayıt ve özgün yerel hedefle eşleşmelidir. Başka oturumu +hedefleyemez, istenmeyen istek/bildirime dönüşemez. + +Bir düğümde daha geniş erişim vermek için o düğümde açıkça çalıştırın: + +```bash +ocx message hosts allow-inbound --json +# Kaydı kaldırmadan geniş izni geri alın: +ocx message hosts deny-inbound --json +``` + +`allow-inbound`, üç mesaj türünü **yüklü tüm yerel Codex oturumlarına** kabul eder +ve dizin meta verilerini eşe açar. Yalnızca yanıt izni değildir. Onay değişiklikleri +mevcut bağlantılarda sonraki okuma/gönderimden önce uygulanır. Yerel gelen ilke +içermeyen eski kayıtlar geniş erişim değil, `responses-only` varsayar. +Yanıt rotaları ön plan yöneticisi başına 256 bekleyen istekle sınırlıdır; 24 saat +sonra veya yönetici sonlandırılınca/yeniden başlayınca sona erer. Gönderen bağlamı +yoksa yanıt rotası yoktur. Kuyruğa alınan veya belirsiz yanıt rotayı tüketir; +kesin yerel ret tüketmez. Kayıp/süresi dolmuş rota otomatik yeniden gönderim +değil, işletmecinin/gönderenin açık uzlaştırmasını gerektirir. + +Her `serve` ön planda yönetilir; çalışır bırakıp mesajlar için ayrı terminal +kullanın. En fazla dört farklı `--host` seçimi verilebilir. Servis kurulmaz veya +yönetici otomatik başlatılmaz. SSH doğrulanmış sabit sunucu anahtarlarını ve +yapılandırılmış anahtarlarınızı/ajanınızı kullanır; parola istemleri kapalıdır. +Sarmalayıcı yalnızca geri döngüde ileri/dönüş rotalarını yönetir. Böylece alıcı, +yukarıdaki ilkeye bağlı olarak başlatana SSH girişi yapmadan ilişkili yanıt gönderebilir. + +```bash +ocx message status --json +ocx message sessions --host worker --json +printf '%s\n' 'Please review and reply using the supplied route.' | + ocx message send --host worker --name reviewer --stdin --json +``` + +`--host` tam kayıtlı takma ad veya makine UUID'si seçer. Yanıt komutları doğrulanmış +kaynak makine ve konuşma UUID'sini içerir; kimlik uzak dizinden tahmin edilmez, +kaynak daemon'da çözülür. Gönderen bağlamı yoksa yanıt komutu uydurulmaz. +`--host` olmayan yerel komutlar durum/SSH/dinleyici kullanmayan yolu korur. +Alıcı; yanıt sunucusu/komutu ve standart eş yönergeleri dahil tüm kanonik zarfı, +kimliği doğrulanmış makine ve yerel mesaj UUID'sine göre doğrular. Eş, +sarmalayıcıyı başka makine olarak etiketleyemez veya kullanıcı mesajı olmadığına +dair uyarıyı çıkaramaz. Oturum adları ve konuşma kimlikleri makinenin açıklayıcı +iddialarıdır; oturum başına kimlik doğrulaması değildir. + +Alıcının verdiği yönlü yetkiler `OPENCODEX_HOME` (veya varsayılan yapılandırma +dizini) altındaki özel mesajlaşma durumunda tutulur. Normal komut çıktısında, +yardımcı argümanlarında, URL'lerde veya mesaj zarflarında görünmez. Karşılıklı +kanıtlar gerçek WebSocket bağlantısını yerel/gövde çerçevelerinden önce doğrular; +ayrı sağlık sorgusu kimlik kanıtı için yetmez. Bu bir OpenCodex ağ geçidi +protokolüdür; yalın `codex queue --remote` onun kimlik el sıkışmasını uygulamaz. +Ağ geçidi aynı deneysel `thread/queue/add` sözleşmesini kullanır; Codex'in +`--ws-auth`, yapılandırma veya yerel bearer-token dinleyicilerini değiştirmez. +Genel app-server denetimi değil, yalnızca yüklü meta veri ve metin kuyruğu sunar. + +Durum; etkin yapılandırmayı, çalışan yöneticiyi ve başlatılan/kiralanan rotaları +ayırır. Dönüş kiraları yenilenmezse 25 saniyede sona erer; teslim/işleme alındısı +değildir. Rota kaybı veya yönetici yapılandırma değişikliği açık yönetici yeniden +başlatması gerektirir. Aktarım kurtarması belirsiz mesajı yeniden göndermez. + +```bash +ocx message hosts remove worker --json +ocx message disable --json +``` + +Kaldırma önce yerel kabulü iptal eder. Doğrulanmamış uzak temizlik çıkış 3 verir; +eş üzerinde de kaldırma gerekir. Her giden kaldırma bağımsız sabit anahtar geçici +dosyası kullanır; bu dosya hazırlanamazsa da yerel kaldırma ve doğrulanmamış uzak +temizlik bildirilir. Başarı/hata durumunda temizlik denenir. Üretilen rehber +`hosts remove --transaction --json` kullanır; +işlem koruması eski alındıyla yeni kaydın kaldırılmasını reddeder. İsteğe bağlı +koruma olmadan normal kaldırma, seçili mevcut eşi kaldırır. + +Kapatma mevcut yönetici neslini sonlandırır; yeni iş hemen reddedilir. Ön plan +yöneticisi değişen ayarı on saniyelik bakım aralığında fark eder. Gönderen +gönderimden hemen önce yerel yetkiyi yeniden kontrol eder; sonradan iptal, yoldaki +mesajı veya kabul edilmiş yerel işi geri çekemez. Kayıp kayıt yanıtı bir özel işlem +tutar: aynı etkin nesil içinde başka adla kayıt yapmak yerine aynı add komutunu +tekrarlayarak uzlaştırın. Kapatıp açmak veya port değişimi kurtarma neslini +geçersiz kılar. Tamamlanan/iptal edilen işlemler kaldırılmış eşi geri getiremez +veya başka kaydın kurtarma kaydını silemez. + +Yerel günlük JSON'u, kayıt biçimleri veya alan değerleri bozuksa `hosts add`, +`hosts list`, `status`, `hosts abandon`, `invalid_enrollment_journal` ile reddeder. +Baytlar işletmecinin açık kurtarması için korunur; OpenCodex otomatik onarmaz, +değiştirmez veya silmez. Bu durumda `hosts add` kayıt denetim isteği göndermez; +ancak ön sunucu anahtarı sorgusu çalışabilir. Güvensiz depolama ve meşgul değişiklik +kilidi ayrı hata sınıfını korur. Geçerli günlük artık uzlaştırılamıyorsa +`ocx message hosts list --json` veya `ocx message status --json` içindeki +`pendingEnrollment` alanını (takma ad, işlem UUID'si, neslin eskiliği) inceleyin. +Sonra tam o yerel günlüğü açıkça bırakın: + +```bash +ocx message hosts abandon --transaction --json +``` + +Bırakma kapalıyken de çalışır; mevcut eşleri değiştirmez, kaydı yeniden denemez. +Uzak temizlik doğrulanmadığı için 3 ile çıkar. Yetim kayıt kalırsa uzak düğümde +verilen temizlik yönergesini uygulayın. Eski günlükler sessiz silinmez. Dolu +alıcı kayıt yapmadan `peer_capacity` döndürür; yer açıp aynı işlemi tekrarlayın +veya açıkça bırakın. Bozuk/kayıp denetim yanıtları belirsiz kalır. + +Mevcut güvenilir `0755` yapılandırma dizinleri chmod yapılmadan kabul edilir; +mesajlaşmanın alt dizini özel kalır. Yeni uç kimlik doğrular ve eskisi bir +saniyelik kontrolde doğrulayamazsa hemen yeniden başlatılan yönetici eski dönüş +kirasını değiştirebilir. Hâlâ doğrulanan eski uç kirasını süresi dolana kadar +korur. Geçici erişimsizlik devralmaya izin verebilir, yeniden göndermeye değil. +Beklenmeyen tünel kaybı ön plan yöneticisini görünür biçimde sonlandırır; +bilinçli eş kaldırma ilgisiz rotaları durdurmaz. Çökmeden kalan değişiklik +kilitleri kapalı güvenli davranır ve işletmeci kurtarması ister; sarmalayıcı eski +kilidin sahipsiz olduğunu tahmin etmez. + +Durum ve kurtarma günlüğü, bağlı uzak işten önce dosyayı ve üst dizini diske +eşitler; günlük kaldırma da dizini eşitler. `remote_storage_unknown` (çıkış 3), +depolama değişikliğinin çökmeye karşı kalıcılığının doğrulanamadığını belirtir. +macOS'ta dosya değişiminden sonra ACL reddi/sorgu hatası da bu kodu üretir: +yeni durum görünür, fakat ACL gizliliği ve kalıcılığı doğrulanmamıştır. Otomatik +tekrar/geri alma yerine durumu ve izinleri inceleyip açık kurtarma yapın. +Güvenle okunabilir ama bozuk JSON/şemalı günlük, eş kaldırmayı engellemez; +kurtarma için korunur. Güvensiz depolama veya eşleşen günlüğü geçersiz kılamama +ise kaldırmayı reddeder. + +Ön plan yöneticisi tüm eşler arasında sonlu bağlantı/istek/yardımcı/tampon sınırları +paylaşır; sınırsız kuyruk kurmadan aşırı yükü reddeder. Kayıtlı eşler birlikte +12 doğrulanmış bağlantı tutabilir. Dört bağlantı yöneticinin denetimi, giden +sorguları ve yenilemesi için ayrılır. En fazla sekiz tünel alt işlemi ve iki +geçici denetim/inceleme yardımcısı, toplam on işlem vardır. Seçilen tüm eşlerin +kurulumu 30 saniyeyle sınırlıdır. Başarıdan önce son kira yenilemesi yapılır; +son ve dönemsel yenileme gruplarının süresi beş saniyedir (uygunsa genel kurulum +süresi içinde). Dönemsel bakım her on saniyede çalışır. + +İptal yalnızca kendisine ait SSH yardımcılarını/dinleyicileri durdurur; +Codex daemon'larına, konuşmalara ve ilgisiz proxy'lere dokunmaz. Temizlik geri +çekilmiş tünel rotalarını da bekler. Doğrudan alt işlemin çıkışı ile doğal boru +EOF'u üç saniyelik temizlik sınırında sonuçlanmalıdır; aksi halde temizlik eksik +kalır ve yardımcı kapasitesi ayrılmış kalır. Boruları iptal etmek alt nesil +işlemlerin çıktığını kanıtlamaz. Ayrılmış veya devraldığı boruları kapatan alt +nesil işlemler izlenmez. Yardımcı stdout yakalaması 64 KiB ile sınırlıdır; +stderr yazdırılmadan/saklanmadan atılır. Yakalama taşınca yardımcı durdurulur, +atılan çıktı doğal EOF'a kadar boşaltılır. Başarılı temizlik özgün çıktı hatasını +bildirir; gerçekten eksik temizlik `cleanup_incomplete` kalır ve kapasiteyi tutar. + +Aynı UID ile çalışan işlemler özel kayıt durumunu okuyabilir; makine kimlik +doğrulaması taklit edilemez ajan kimliği sağlamaz. Bu ağ geçidini atlayan yerel +trafik onun denetiminde değildir. + +Mesh, pano/önbellek denetimleri, yalıtım, Claude mesajlaşması, yönetilen beceriler +ve boşta bildirimleri bu katkının kapsamı dışındadır. diff --git a/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md b/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md index acb18d2c2a1..184e193ca23 100644 --- a/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md @@ -175,6 +175,14 @@ kullanılamadığında atlanır): ### `ocx account list [provider] [--json] [--all]` +Kayıtlı ChatGPT havuz hesapları, yerel Codex ana dizini olmayan makinelerde de listelenebilir; +buna pano ve hesap yenileme işlemleri dahildir. Listeleme, Codex ana dizini oluşturmaz ve yerel ana +hesabın oturumunu kapatmaz. Codex istemci entegrasyonunu devre dışı bırakmak, mevcut bir yerel oturumun +kota gözlemini devre dışı bırakmaz. Açıkça belirtilmiş geçersiz `CODEX_HOME` yolları yine yapılandırma +hatası döndürür. Güvenli olmayan `OPENCODEX_HOME` depolaması, yerel Codex ana dizini eksik olsa bile +hata döndürür. Varsayılan Codex ana dizini yolu çözümlenirken dosyalar, hedefi bulunmayan sembolik +bağlantılar ve geçiş yapılamayan üst dizinler de hataya neden olur. + Bir sağlayıcı olmadan Codex havuzunu, OAuth hesaplarını ve yapılandırılmış API anahtarı havuzlarını listeler. `--all` bulunmadıkça boş sağlayıcılar atlanır. Bir sağlayıcı ile yalnızca bu kimlik bilgisi ailesini listeler. İnsan çıktısı diff --git a/docs-site/src/content/docs/tr/reference/code-signing.md b/docs-site/src/content/docs/tr/reference/code-signing.md new file mode 100644 index 00000000000..d88d0866aed --- /dev/null +++ b/docs-site/src/content/docs/tr/reference/code-signing.md @@ -0,0 +1,103 @@ +--- +title: Windows kod imzalama +description: OpenCodex Windows dosyalarının SignPath Foundation ile imzalanması, imzaların doğrulanması ve projenin kod imzalama ilkesi. +--- + +OpenCodex, bağımsız `ocx.exe` ve `OpenCodex--windows-x64.msi` masaüstü +yükleyicisi için [SignPath Foundation](https://signpath.org) üzerinden Authenticode +kod imzalamaya geçiyor. İmza, Windows'a, SmartScreen'e ve antivirüs ürünlerine +dosyayı kimin yayımladığını ve derlemeden sonra değiştirilmediğini bildirir. +Bu, [#7207](https://github.com/lidge-jun/opencodex/issues/7207) gibi imzasız +dosyalardan kaynaklanan yanlış alarmları önlemeye yardımcı olur. + +Geçiş sürüyor. Yayın iş akışı hazır; SignPath bunu test sertifikasıyla doğruluyor. +Üretim sertifikası verilene kadar Windows yayın dosyaları imzasız kalır ve +SmartScreen bir kez **Ek bilgi → Yine de çalıştır** seçimini isteyebilir. +Geçişten sonra yayımlanan sürümler **SignPath Foundation** imzası taşır. + +## Kod imzalama ilkesi + +Ücretsiz kod imzalama [SignPath.io](https://about.signpath.io/), sertifika ise +[SignPath Foundation](https://signpath.org/) tarafından sağlanır. + +- Kod katkısı yapanlar ve inceleyenler: [lidge-jun](https://github.com/lidge-jun) +- Onaylayanlar: [lidge-jun](https://github.com/lidge-jun) + +Sertifika kullanılmadan önce her yayın imzalama isteği SignPath'te elle onaylanır. +Yalnızca bu deponun kaynaklarından GitHub Actions ile derlenen dosyalar imzalanır. +Önceden derlenmiş keyring eklentileri gibi sürüme eklenen üçüncü taraf dosyaları +özgün hallerini korur; SignPath Foundation ilkesi bunların proje sertifikasıyla +imzalanmasını yasaklar. + +### Gizlilik + +Bu program; kullanıcı, kuran kişi veya çalıştıran kişi açıkça istemedikçe başka +ağ sistemlerine bilgi aktarmaz. OpenCodex, istekleri yapılandırdığınız sağlayıcılara +iletir. Yeni sürümler için GitHub ve npm kayıt deposundan yayın meta verilerini +de okur; bu kontroller istemlerinizi, kimlik bilgilerinizi veya kullanım verilerinizi +taşımaz. + +## İndirmeyi doğrulama + +PowerShell'de indirdiğiniz dosyayı kontrol edin: + +```powershell +Get-AuthenticodeSignature .\OpenCodex--windows-x64.msi | Format-List Status, SignerCertificate +Get-AuthenticodeSignature .\ocx.exe | Format-List Status, SignerCertificate +``` + +İmzalı sürüm `Status : Valid` ve konusunda `SignPath Foundation` bulunan bir +imzalayan sertifikası gösterir. Aynı bilgi Dosya Gezgini'nde **Özellikler → Dijital +İmzalar** altındadır. Yayın sayfasında her dosyanın yanında bir `.sha256` dosyası +da bulunur; bunu `Get-FileHash -Algorithm SHA256` çıktısıyla karşılaştırın. + +## İmzalanan dosyalar + +| Dosya | İmzalanan içerik | +| --- | --- | +| `ocx--bun-windows-x64.zip` | Arşivdeki `ocx.exe`. ZIP ve `.sha256`, imzalı dosyadan oluşturulur. | +| `OpenCodex--windows-x64.msi` | Yükleyici ve kurduğu tüm `.exe` dosyaları: masaüstü uygulaması ile paketlenmiş `ocx.exe`. | + +Masaüstü güncelleyici imzası (`.msi.sig`) ve `latest.json`, imzalı yükleyiciden +üretilir; uygulama içi güncellemeler yayın sayfasındaki aynı baytları kurar. + +## Proje bakımını yapanlar için + +`.github/workflows/release.yml`, resmi +`signpath/github-action-submit-signing-request` eylemini kullanır. Bir gizli değer +ve üç depo değişkeni okur: + +| Ad | Tür | Değer | +| --- | --- | --- | +| `SIGNPATH_API_TOKEN` | Gizli değer | SignPath CI kullanıcısının API tokenı | +| `SIGNPATH_ORGANIZATION_ID` | Değişken | SignPath kuruluş kimliği | +| `SIGNPATH_PROJECT_SLUG` | Değişken | `opencodex` | +| `SIGNPATH_SIGNING_POLICY_SLUG` | Değişken | Şimdilik `test-signing`; sertifika verilince `release-signing` | + +SignPath çıktı yapılandırmaları: `windows-standalone-zip` (ZIP kökünde tek +`ocx.exe`) ve `windows-msi-zip` (ZIP kökünde tek `.msi`, iç dosyaları da imzalanır). + +İmzalama, yayını engellemez. İşleyiş seçilen ilkeye bağlıdır: + +| İlke | Deneme çalıştırması | Gerçek yayın | +| --- | --- | --- | +| Token yok veya yapılandırma eksik | İmzasız | Bildirim veya uyarıyla imzasız | +| `test-signing` | Test sertifikasıyla imzalanır, yalnızca iş akışı çıktısı olarak tutulur | Bildirimle imzasız; test sertifikası hiçbir zaman yayımlanmaz | +| `release-signing` | İmzalanır ve güvenilirliği doğrulanır | İmzalanır ve güvenilirliği doğrulanır | + +İstek başarısız olur, zaman aşımına uğrar veya reddedilirse ya da doğrulama +başarısız olursa iş uyarı kaydeder; imzasız dosyayı özgün güncelleyici imzasıyla yayımlar. + +**Üretim sertifikasına geçiş:** SignPath sertifikayı verince depo değişkeni +`SIGNPATH_SIGNING_POLICY_SLUG` değerini `release-signing` yapın +(Settings → Secrets and variables → Actions → Variables). İş akışını değiştirmek +gerekmez. Bundan sonra deneme çalıştırmaları dahil her yayın iki imzalama isteği +açar: bağımsız dosya ve yükleyici. Çalıştırma beklerken onaylayan kişi ikisini de +[app.signpath.io](https://app.signpath.io) üzerinde onaylamalıdır. İş akışı her +istek için en fazla bir saat bekler, sonra imzasız yayımlar. + +**Yayımlamadan test etme:** **Windows signing test** iş akışı +(`.github/workflows/windows-signing-test.yml`, yalnızca elle başlatılır, girdi +almaz) GitHub Windows çalıştırıcılarında `ocx.exe` ve MSI derler. İkisini de +`test-signing` ile imzalar, imzaları doğrular ve test imzalı dosyaları kısa süreli +iş akışı çıktıları olarak saklar. Hiçbir şey yayımlamaz, yayın ilkesini kullanmaz. diff --git a/docs-site/src/content/docs/tr/reference/codex-retry-diagnostics.md b/docs-site/src/content/docs/tr/reference/codex-retry-diagnostics.md new file mode 100644 index 00000000000..2fcf4c4b1e3 --- /dev/null +++ b/docs-site/src/content/docs/tr/reference/codex-retry-diagnostics.md @@ -0,0 +1,119 @@ +--- +title: Codex yeniden deneme tanılaması +description: Hız sınırı önerisini, otomatik yeniden gönderimi, sağlayıcının düzelmesini ve Desktop bildirimlerini birbirinden ayırın. +--- + +OpenCodex'in [Devin bağdaştırıcısı](/tr/reference/adapters/#devin), Codex'in anladığı +hata sözleşmesinde kullanılabilir yeniden deneme ipucunu korur. Bu, Desktop'ın +yeniden bağlanma satırı göstereceğini veya sağlayıcının sonraki isteği kabul +edeceğini garanti etmez. + +## Beklemeyi kim yönetir? + +`OPENCODEX_DEVIN_STATED_RESET_WAIT_MS` ayarlanmamışsa veya `0` ise Devin +bağdaştırıcısı reddedilen isteği belirtilen bekleme süresi boyunca tutmaz. Son +hata Codex'e iletilir; sınırlı yeniden deneme ilkesini Codex yönetir. Pozitif değer, +proxy'nin mevcut bekleme davranışını açar; son hatanın teslimini geciktirir ve +istemci yeniden denemeleriyle birleşebilir. Bu uyumluluk eşlemesi ne ayarı ne de +Codex'in yeniden deneme sayısını değiştirir. Yalnızca işleme özel değişken yeniden +başlatmada korunmayabilir. + +`rate_limit_exceeded` ve `Please try again in Ns.` bir yeniden deneme önerisidir; +geri sayım olayı değildir. Codex'in yeniden deneme, iptal ve yaşam döngüsü +kuralları geçerlidir. Beklemeyi göstermek için yapay akıl yürütme, araç, asistan +veya başarı öğeleri eklemeyin: geçmişi bozabilir veya iş yapılmış izlenimi +verebilirler. Sağlayıcı gecikmesini kısaltmayın; istemci sayacını ilerletmek için +fazladan istek üretmeyin. + +## Tanılama: `exceeded retry limit, last status: 429 Too Many Requests` + +Bu ileti istemcinin yeniden deneme bütçesinin özetidir; tanı değildir. İstemcinin +yeniden göndermediği tek ret de tamamen tükenmiş bütçeyle aynı sözlerle turu +bitirebilir. Bu katmanda aynı görünen üç durum ayrı kontrol edilir: + +| Kontrol | Ayırt edici kanıt | Sonuç doğrulanırsa | +| --- | --- | --- | +| Ana hesap kesin kilidi (yerel kabul ilkesi) | Pano Ayarlar veya `GET /api/settings`, `mainAccountHardLock.state` için `blocked`, `ready` ya da `off`; ayrıca `thresholds` ve isteğe bağlı `resetAt` bildirir. Akışsız istek, yalın üst sunucu 429'u yerine açık ana ilke gövdesi de taşıyabilir. | `ocx account refresh openai` ile kullanımı yenileyin. Tüm engelleme eşiklerinin altında güncel kullanım veya pencerenin bulunmadığının doğrulanması kilidi kaldırır. Engel sürerse daha düşük güncel kullanımı bekleyin veya Ayarlar'da `codexMainAccountHardLock` değerini açıkça kapatın. [#6287](https://github.com/lidge-jun/opencodex/pull/6287) öncesi derlemeler, yeni gözlem eskisinin yerini almadığında eski kısa pencere engelini yeniden başlatmalar arasında koruyabiliyordu. Ayrıntılar [#6244 kapanış notunda](https://github.com/lidge-jun/opencodex/issues/6244). | +| Üst sunucudaki ChatGPT plan penceresi | Kilit `ready` veya `off` iken hesap kota bölümü 5 saatlik/haftalık pencereyi sınıra yakın veya dolu gösterir. | Sıfırlanmayı bekleyin, ChatGPT'nin sunduğu sıfırlama seçeneklerini inceleyin ([biriktirilmiş sıfırlama kuralları](https://help.openai.com/en/articles/20001498-how-banked-codex-resets-work), [plan pencereleri](https://help.openai.com/en/articles/11369540-using-codex-with-your-chatgpt-plan)) veya `ocx account use openai ` ile başka hesaba geçin. Sınır sonrası açıkça izin verilmiş ücretli kredi kullanımı için [hesap kredi ilkesine](/tr/reference/cli/providers-accounts/) bakın. [#6845](https://github.com/lidge-jun/opencodex/issues/6845) kredi bakiyesi yeniden keşif hatası [#6886](https://github.com/lidge-jun/opencodex/pull/6886) ile düzeltildi. | +| Sağlayıcı olayı veya aktarım hatası | Kota düşük ve kilit açık kalırken `ocx doctor` aktarım/ChatGPT erişim sorunu bildirir. | Olay düzeldikten sonra yeniden deneyin veya mevcut combo üzerinden yönlendirin. Kilit/eşik ilkesini değiştirmek aktarım hatasını çözmez. | + +Kilitli ana kimlik bilgisi istek kabulünü kapatır; kalan kotayı ayırmaz. Tahmini +sıfırlanma zamanı tek başına kilidi kaldırmaz. Eşikler (varsayılan `short` 90, +`long` 98) durum yükünde görünür ve `codexMainAccountHardLockThresholds` ile ayarlanır. + +Kesin kilit reddi `usage_limit_reached` olarak bildirilir. Böylece Codex günlerce +yeniden denemede beklemek yerine turu bitirir ve etkin Goal'ı kullanım sınırlı +işaretler. Önerilen zaman, ana hesap sıfırlanmasıdır; havuzun ana hesaba +yönlendirdiği isteklerde başka havuz hesabının bilinen en erken düzelme zamanı +daha erkense o kullanılır. Süre hiçbir zaman bir dakikadan kısa değildir. +Goal'ı o zamandan sonra sürdürün; yönlendirme güncel durumdan hesap seçer. + +## İlk yeniden bağlanma satırı neden görünmeyebilir? + +Herkese açık Codex `rust-v0.158.0-alpha.2.1` kaynaklarında normal akış yeniden +deneme bildirimi +[`handle_response_stream_error`](https://github.com/openai/codex/blob/rust-v0.158.0-alpha.2.1/codex-rs/core/src/responses_retry.rs) +içinde şu koşula bağlıdır: + +```rust +let report_error = retry_count > 1 + || cfg!(debug_assertions) + || !sess.services.model_client.responses_websocket_enabled(); +``` + +Yayın derlemesinde iç WebSocket koşulu doğruysa bu döngünün ilk yeniden denemesi +`Reconnecting...` üretmeden bekleyebilir. Seçilen gecikme bildirim koşulunun parçası +değildir; uyku `if report_error` dışındadır. Sunucunun önerdiği uzun bekleme de +sessiz olabilir. Bu, belirli sürümün kaynak gözlemidir; bir Desktop isteğinin o +dalı kullandığını kanıtlamaz. İç koşulu model adından, HTTP durumundan veya tek +proxy adımında görülen aktarımdan çıkarmayın. + +Etkin bildirim aboneliği ve satırı çizebilen arayüz yalnızca alıcı yolunun +varlığını gösterir; motorun ilgili turda olay ürettiğini kanıtlamaz. Daraltılmış +sağlayıcı ayrıntıları eksik ayrıntı metnini açıklayabilir; tek başına eksik yeniden +bağlanma satırını açıklamaz. + +Karşılık gelen app-server +[`ErrorNotification`](https://github.com/openai/codex/blob/rust-v0.158.0-alpha.2.1/codex-rs/app-server-protocol/schema/typescript/v2/ErrorNotification.ts) +olayı `willRetry`, `threadId`, `turnId` içerir. Üretimi, doğru tura teslimi ve +gösterimi ayrı gözlemleyin. Proxy'nin `response.failed` çerçevesi bu bildirim +değildir. İlk bildirim ilkesinin değişmesi Codex değişikliği gerektirir; OpenCodex +hata metnini değiştirerek olay üretmeyen daldan olay geçiremez. + +## Salt okunur doğrulama + +Kanıt toplarken uygulamayı, proxy'yi, ayarları ve test turunu değiştirmeyin. +Yeniden başlatmak, yeni tur göndermek veya aktarımı değiştirmek deneyi değiştirir. +Ayrı bir takip testinde aynı kontrollü hatayı iki iç WebSocket durumunda karşılaştırın; +ilk ve sonraki yeniden denemeleri inceleyin. Arayüz gözlenmediyse bunu gerçek +Desktop gösterim testi olarak adlandırmayın. + +İlgili konuşma ve etkin tur için şu sonuçları ayırın: + +| Soru | Gerekli kanıt | +| --- | --- | +| Kullanılabilir öneri ulaştı mı? | Son hatanın kodu, normalleştirilmiş gecikme ve yanıt bitiş zamanı. | +| Otomatik yeniden gönderim oldu mu? | Elle gönderim olmadan, hatadan sonra onunla ilişkili yeni istek. Süreyi ilk istek başlangıcından değil, hata yanıtının bitişinden sonraki isteğin başlangıcına ölçün. | +| Sağlayıcı düzeldi mi? | Başarılı yanıt ve ilgili işin devamı; yalnızca yeni istek veya ilgisiz başarılı tur yeterli değildir. | +| Yeniden deneme bildirimi üretildi ve ulaştı mı? | Yalnızca abonelik değil, eşleşen app-server hata olayı ve yeniden deneme bayrağı. | +| Bildirim göründü mü? | İlgili Desktop turunun gözlemi; protokol/kaynak kontrolü tek başına yetmez. | + +Yaklaşık sağlayıcı önerisi ve zamanlama yükü küçük farklar yaratabilir. Yeniden +gönderim soket kapanma hatasına ulaşıyor, daha sonraki deneme yeni hız sınırı +alıyorsa yeniden gönderimi çalışıyor, sağlayıcı düzelmesini eksik kaydedin. Yeni +ret gecikmesi yeni gözlemdir; önceki retten devralınmış zamanlayıcı değildir. +`inProgress` turu tek başına düzelmeyi veya doğru gösterimi kanıtlamaz. Deneme +tükenmesi, iptal ve uygulama yeniden başlatma kurtarmasını ayrı değerlendirin. + +Yalnızca doğrulama kapsamını, toplu süreleri ve sonuçları yayımlayın. Ham günlükleri, +istek gövdelerini, kimlik bilgilerini, hesap verilerini, özel yolları ve +tur/konuşma/istek/iz kimliklerini herkese açık PR ve raporlara koymayın. + +## Regresyon testinin kapsamı + +`tests/server/retry-delay-hardening.test.ts`; 120, 900, 1.800, 2.460 ve 3.600 +saniyelik önerilerin biçimlendirmede korunduğunu, kısa öneri de varken uzunun önde +kaldığını ve aradaki süresiz bağlantı kesilmesinin sonraki rete eski gecikmeyi +taşımadığını kontrol eder. Bunlar saf ayrıştırıcı/biçimlendirici testleridir. +Bir saat beklemez, Codex bildirim koşulunu çalıştırmaz, uygulama yeniden +başlatmasını doğrulamaz ve canlı sağlayıcı hatasından düzelmeyi kanıtlamaz. diff --git a/docs-site/src/content/docs/tr/reference/configuration/advisor.md b/docs-site/src/content/docs/tr/reference/configuration/advisor.md index a2b7de6a13a..0a44487f45e 100644 --- a/docs-site/src/content/docs/tr/reference/configuration/advisor.md +++ b/docs-site/src/content/docs/tr/reference/configuration/advisor.md @@ -1,6 +1,6 @@ --- title: Danışman -description: OpenCodex'in sahibi olduğu uzman danışma sidecar'ı — yapılandırılan uzman model yönlendirilen worker'lara tavsiye döndürür; manual ve preflight politikaları. +description: manual, preflight ve kanıta dayalı adaptive politikalarıyla bağımsız uzman danışması. --- Danışman, worker'ın görevini inceleyen ve tavsiye döndüren bağımsız bir uzman modeldir. Danışmayı uçtan uca OpenCodex sahiplenir: proxy, worker'ın turuna sentetik `advisor` aracını enjekte eder, danışmayı normal yönlendirme otoritesi aracılığıyla kendisi yürütür ve tavsiyeyi geri enjekte ederek özgün worker'ın devam etmesini sağlar. Worker'ın bir şey devretmesine, spawn etmesine veya sağlayıcı kimlik bilgisi taşımasına gerek yoktur. @@ -9,6 +9,8 @@ Bu, alt ajan yüzeyinden farklıdır (bkz. [Ajan yapılandırması](/tr/referenc ## Yapılandırma +Kontrol panelindeki **Advisor** sayfasında katalogdan model seçin. Yerel, sağlayıcı, hesap, özel model ve combo tanımlayıcıları aynen kaydedilir. Devre dışı veya ilk seçimi bekleyen modeller yeni seçenek olarak sunulmaz. Listede bulunmayan kayıtlı tanımlayıcı, değiştirilene veya temizlenene kadar uyarıyla korunur. Yükleme hatası seçimi korur ve yeniden deneme sunar. Metin önerisi verebilen bir model seçin; katalog danışma uyumluluğunu garanti etmez. CLI ve API, yönlendiricinin desteklediği model dizelerini kabul etmeye devam eder. + ```json { "advisor": { @@ -26,11 +28,12 @@ Bu, alt ajan yüzeyinden farklıdır (bkz. [Ajan yapılandırması](/tr/referenc | `enabled?` | `boolean` | `false` | Ana anahtar. Kapalıyken istek yolunda hiçbir danışman davranışı olmaz. | | `model?` | `string` | — | Uzman model. Yönlendiricinin kabul ettiği herhangi bir model dizisi: çıplak yerel model (`gpt-6-astra`), açık `provider/model` (`anthropic/claude-sonnet-4-6`, `xai/grok-...`) veya hesap nitelemeli yerel model. Sağlayıcılar arası tam desteklenir. | | `effort?` | `string` | `"max"` | Danışman çağrısının muhakeme düzeyi (`low`–`ultra`). | -| `policy?` | `"manual" \| "preflight"` | `"manual"` | Ne zaman danışılır. | +| `policy?` | `"manual" \| "preflight" \| "adaptive"` | `"manual"` | Ne zaman danışılır. | +| `maxConsultationsPerTask?` | `integer` | `3` | Uyarlanabilir görev bütçesi: manuel çağrılar dahil 1–3 deneme. | | `timeoutMs?` | `number` | `120000` | Loopback danışma zaman aşımı. | | `contextSharingConsent?` | `"v1"` | yok | Yapılandırılmış danışman sağlayıcısına görev bağlamını gönderme onayı. Güncel değer yalnızca `"v1"`. Yok, eskimiş veya başka bir değer görev içeriğinin gönderilmemesi demektir. `enabled: true` bu onay değildir. | -Panodaki **Advisor** sayfası veya `ocx advisor status|on|off|set --model --effort --policy ` ile yönetin. +Panodaki **Advisor** sayfası veya `ocx advisor status|on|off|set --model --effort --policy ` ile yönetin. Güncel onay yokken `ocx advisor on` sağlayıcılar arası gönderimi açmaz: açıklamayı basar ve durur. `ocx advisor on --ack-context-sharing` ile `ocx advisor consent` `v1` kaydeder. `ocx advisor consent --revoke` onayı kaldırır ve gönderimi hemen durdurur. `ocx advisor set` onay vermez. Panodaki onay kutusu önceden işaretli değildir. @@ -78,5 +81,13 @@ Danışman fail-open davranır: gönderilmiş bir danışma başarısız olursa ## PR1 sınırlamaları - Yerel OpenAI passthrough turları (ChatGPT havuzu worker'ları) sentetik aracı almaz; danışman desteği yönlendirilen (çevrilen) sağlayıcıları kapsar. Preflight danışması run-turn bağdaştırıcılarına uygulanır; araç uygulanmaz. -- Uyarlanabilir tetikleyici yok: takılma algılama, tekrarlayan başarısızlık analizi, yükseltme katmanları, çoklu danışman veya oylama yok. Yalnızca `manual` ve `preflight`. +- Anlamsal takılma algılama, tamamlanma incelemesi, yükseltme katmanları, çoklu danışman veya oylama yoktur. - Preflight tekilleştirme defteri süreç içindedir; proxy yeniden başlatıldıktan sonra devam eden bir görev bir preflight danışması daha alabilir. + +## Kanıta dayalı uyarlanabilir danışma ve etkinlik + +`adaptive`, ilk danışmaya ek olarak aynı çalışma dizinindeki aynı doğrulama başarısız olduğunda, sonraki dosya düzenlemesi tamamlandığında ve yeni doğrulama tekrar başarısız olduğunda danışır. Başarı hata durumunu temizler; düzenleme öncesinde başlayan kontrol onarım sonrası deneme sayılmaz. İlk danışma ve başarısız onarım ayrı ayrı tekilleştirilir. Otomasyon sabit bir konuşma kimliği gerektirir. Eşleşen araç çağrıları/sonuçları ve açık çıkış kodu gerekir. Karmaşık betikler, toplu çağrılar, shell düzenlemeleri ve belirsiz çıktılar tetiklemez. + +`ocx advisor set --policy adaptive --max-consultations 2` ile ayarlayın. Manuel ve otomatik denemeler görev başına 1–3 bütçesini paylaşır. Sunucu göndermeden önce `OPENCODEX_HOME/advisor-budget.sqlite` dosyasına rezervasyon yazar; yeniden başlatma veya worker değişimi sayacı sıfırlamaz. Yalnızca görev özetleri, sayaçlar ve süreler tutulur: ilk denemeden itibaren 24 saat, en fazla 512 görev. Bütçe kullanılamıyorsa worker devam eder. Gönderilen başarısız veya iptal edilen çağrılar ücretlenmiş olabileceğinden rezervasyon iade edilmez. Sabit kimlik yoksa yalnızca istek düzeyinde sınırlı manuel çağrılar kullanılabilir. + +Advisor sayfası ve `ocx advisor activity --json` (`GET /api/advisor/activity`), bu işlemin son 24 saatinden en fazla 100 meta veri kaydını gösterir. Görünür sayfa beş saniyede bir yenilenir. Neden, durum, modeller, süre, bildirilen girdi/çıktı tokenları, istenen ve adaptörün eşlediği akıl yürütme düzeyi gösterilir. Görev metni, komutlar, tavsiye veya konuşma kimliği tutulmaz; yeniden başlatmada kayıtlar silinir. Eşleme gönderilen parametreyi gösterir, sağlayıcının iç akıl yürütmesini garanti etmez. Eksik veriler bilinmiyor olarak kalır; tavsiye benimsenmesi ve kesin maliyet çıkarılmaz. diff --git a/docs-site/src/content/docs/tr/reference/configuration/providers.md b/docs-site/src/content/docs/tr/reference/configuration/providers.md index 5f3309a0f31..0242a1b4754 100644 --- a/docs-site/src/content/docs/tr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/tr/reference/configuration/providers.md @@ -12,6 +12,16 @@ Model penceresi bildirilmemiş veya yapılandırılmamışsa sırasıyla Azure m Bu tahmin varsayılan olarak kapalıdır; sağlayıcı başına `inferContextWindow: true` ile açılır. Ayarlanmazsa penceresi olmayan model etkin Context cap'i, ardından 128k'yı kullanır. +## GPT-6.1 Terra + +[GPT-6.1 Terra](https://openai.com/index/introducing-gpt-6-1-terra/), GPT-5.6 Terra’nın halefi olarak 26 Ekim 2026’da yayımlandı. `gpt-6.1-terra`, `gpt-5.6-terra` ile birlikte kullanılabilir; varsayılan model ve alt ajan listesi değişmez. GitHub Copilot, Responses kullanır. OpenRouter kimliği `openai/gpt-6.1-terra`, bağlamı 1,050,000 ve milyon token başına giriş / çıkış fiyatı $1.00 / $5.00’dır. + +Yerel Codex: varsayılan bağlam 500,000, uzun bağlam tavanı 872,000, varsayılan effort `low`; düzeyler `low`, `medium`, `high`, `xhigh`, `max`, `ultra`. Yerel `ultra`, aktarımda `max` olarak gönderilir. Yerel satır yalnızca Fast sunar. + +[OpenAI API](https://developers.openai.com/api/docs/models/gpt-6.1-terra): bağlam 1,050,000, en fazla çıktı 128,000; metin/görsel girişi ve metin çıktısı; bilgi kesim tarihi 2026-06-01. Effort düzeyleri: `none`, `low`, `medium` (varsayılan), `high`, `xhigh`, `max`. Uç noktalar: `v1/responses` ve `v1/chat/completions`; araç çağrıları Responses gerektirir. + +Milyon token başına: giriş $1.00, önbellek okuma $0.05, önbellek yazma $1.25 (30 dakika), çıktı $5.00. Giriş 272,000 tokenı aşarsa tüm istekte giriş/önbellek 2×, çıktı 1.5× ücretlendirilir: sırasıyla $2.00 / $0.10 / $2.50 / $7.50. Fast modu 2× ücretlidir. + ## İlk kayıtta model seçimi Yeni OAuth dışı bağlantılar, modelleri göstermeden önce güvenilir bir model listesini bekler. Models sekmesinde en az 20 benzersiz model satırı varsa tüm model anahtarları başlangıçta OFF olur; sağlayıcının kendisi ACTIVE kalır. Gerçekte OAuth veya ChatGPT girişi kullanan bağlantılar varsayılanlarını korur. @@ -55,7 +65,7 @@ Arayüzde kayıt veya OAuth girişi tamamlanınca Models sayfasını açan bir b | `codexWsReuseAcrossTurns?` | `boolean` | `false` | Aynı hesap ve iş parçacığında Codex WebSocket'ini turlar arasında yeniden kullanır. Varsayılan kapalıdır. Açıkken meşgul soket en fazla 750 ms beklenir; model veya katman değişince en fazla 2 soket tutulur. | | `upstreamHostCircuitThreshold?` | `number` | `0` | Yerel OpenAI iletme Responses ve sıkıştırma gönderimlerinde kanıtlanmış bağlantı öncesi DNS/TCP arızaları için isteğe bağlı devre eşiği. `0` devre dışı bırakır; `1`–`20`, bu kadar terminal mantıksal istekten sonra 30 saniyelik bir sağlayıcı-kaynak soğuma süresi açar. Açıkken istekler, hesap seçiminden veya yukarı akış gönderiminden önce `Retry-After` ile `503` alır; soğuma süresinden sonra bir yarı açık isteğe izin verilir. Zaman aşımları ve HTTP yanıtları asla sayılmaz ve herhangi bir HTTP yanıtı devreyi kapatır. Yalnızca sabitlenmiş hesabı olmayan Codex Havuz yönlendirmesi için geçerlidir; `codexAccountMode: "direct"` ve hesap nitelikli seçiciler için etkisizdir. | | `modelCacheTtlMs?` | `number` | `300000` | Sağlayıcı başına `/models` önbelleği için tazelik penceresi. | -| `cacheRetention?` | `"none" \| "short" \| "long"` | `"short"` | Anthropic istem önbelleği politikası: devre dışı, 5 dakikalık kısa ömürlü veya 1 saatlik uzatılmış. | +| `cacheRetention?` | `"none" \| "short" \| "long"` | Birinci taraf Anthropic: `"long"`; diğer Anthropic uyumlu uç noktalar: `"short"` | Anthropic istem önbelleği politikası: devre dışı, 5 dakikalık kısa ömürlü veya 1 saatlik uzatılmış. Ayarlanmadığında `api.anthropic.com` (API anahtarı veya OAuth hesap havuzları) istekleri 1 saatlik TTL kullanır; uyumlu ağ geçitleri ve sağlayıcı uç noktaları 5 dakikada kalır. Açıkça verilen değer her zaman önceliklidir. | | `tokenGuardian?` | `OcxTokenGuardianConfig` | kapalı | İsteğe bağlı proaktif OAuth yenileme ve Codex hesabı ısınma politikası. | Bu stratejilerde her hesabın etkin eşiği, varsa `codexAccountAutoSwitchThresholds` girdisi, yoksa genel `autoSwitchThreshold` değeridir. Sıfır yalnızca kullanıma dayalı proaktif geçişi kapatır; başlangıç bağlaması, kesin kilit, bekleme süresi, model erişim denetimleri ve hata kurtarma uygulanmaya devam eder. @@ -126,7 +136,7 @@ alanlı seçilmiş kimlikleri yalın kimliklere yeniden yazar. | `modelAutoCompactTokenLimits?` | `Record` | Model başına pozitif güvenli tamsayı biçiminde yumuşak otomatik sıkıştırma bütçeleri. Değerler yalnızca bağlamın veya maksimum girdinin etkin %90 zarfını düşürebilir ve etkin bir bağlam penceresi çözümlenemiyorsa yayımlanmaz. Canonical `openai` için anahtarlar, sağlayıcı veya hesap seçici öneki olmadan desteklenen tam yerel model kimlikleri olmalıdır. Sağlayıcı PATCH girdileri birleştirir; bir anahtarı `null` yapmak o anahtarı siler, alanın tamamını `null` yapmak haritayı temizler. Bu `null` silme işaretleri yalnızca PATCH içindir. | | `defaultMaxOutputTokens?` | `number` | İstemci `max_output_tokens` değerini atladığında sağlayıcı genelinde `openai-chat` geri dönüşü. | | `modelMaxOutputTokens?` | `Record` | Pozitif model başına `openai-chat` geri dönüş bütçeleri; tam/kalıp eşleşmeleri sağlayıcı varsayılanını yener. | -| `modelCosts?` | `Record` | Sağlayıcının tam yukarı akış model kimliğine göre anahtarlanan model başına görüntüleme fiyatları (1M token başına USD) — bir sağlayıcı tanımlayıcısı veya yönlendirilen `provider/model` etiketi değil, örn. `{ "deepseek-v4-flash": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 } }`. Herhangi bir model kimliği geçerli bir anahtardır — özel sağlayıcılar `openai-chat` adaptörü aracılığıyla herhangi bir OpenAI uyumlu uç noktayı hedefleyebilir ve yerel veya dahili sağlayıcı kimlikleri yerleşik kataloglarda bulunmasalar bile çalışır. Kullanıcı tarafından yapılandırılan fiyatlar Günlükler `~$` ve Kullanım tahminlerinde yerleşik katalogları yener; geçmiş girdiler geçerli katmandan yeniden fiyatlandırılır, bu nedenle bir fiyatı düzenlemek geçmiş toplamları değiştirebilir. Geri dönüş sırası: kullanıcı `modelCosts` → jawcode kataloğu → beklenen fiyat katmanı → model düzeyinde satıcı geri dönüşü ve kullanıcının açıkça sıfır olarak belirlediği oranlar bilinen sıfır maliyetli bir tahmin üretir; otomatik fiyatlandırmaya dönmek için model girdisini silin. Tamamen sıfır katalog fiyatları bir sonraki kaynağa geçmeye devam eder. Her oran en fazla 1.000.000 (1M token başına USD) olan negatif olmayan sonlu bir sayı olmalıdır; aralık dışı satırlar yönetim sınırı tarafından reddedilir ve yükleme sırasında bırakılır. Yalnızca görüntüleme zamanı tahmini: katmanlar yönlendirmeyi, hesap seçimini, kotaları veya faturalandırmayı asla etkilemez. | +| `modelCosts?` | `Record` | Sağlayıcının tam yukarı akış model kimliğine göre anahtarlanan model başına görüntüleme fiyatları (1M token başına USD) — bir sağlayıcı tanımlayıcısı veya yönlendirilen `provider/model` etiketi değil, örn. `{ "deepseek-v4-flash": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 } }`. Herhangi bir model kimliği geçerli bir anahtardır — özel sağlayıcılar `openai-chat` adaptörü aracılığıyla herhangi bir OpenAI uyumlu uç noktayı hedefleyebilir ve yerel veya dahili sağlayıcı kimlikleri yerleşik kataloglarda bulunmasalar bile çalışır. Kullanıcı tarafından yapılandırılan fiyatlar Günlükler `~$` ve Kullanım tahminlerinde yerleşik katalogları yener; geçmiş girdiler geçerli katmandan yeniden fiyatlandırılır, bu nedenle bir fiyatı düzenlemek geçmiş toplamları değiştirebilir. Geri dönüş sırası: kullanıcı `modelCosts` → jawcode kataloğu → beklenen fiyat katmanı → model düzeyinde satıcı geri dönüşü ve kullanıcının açıkça sıfır olarak belirlediği oranlar bilinen sıfır maliyetli bir tahmin üretir; otomatik fiyatlandırmaya dönmek için model girdisini silin. Tamamen sıfır katalog fiyatları bir sonraki kaynağa geçmeye devam eder. Her oran en fazla 1.000.000 (1M token başına USD) olan negatif olmayan sonlu bir sayı olmalıdır; aralık dışı satırlar yönetim sınırı tarafından reddedilir ve yükleme sırasında bırakılır. Yalnızca görüntüleme zamanı tahmini: katmanlar yönlendirmeyi, hesap seçimini, kotaları veya faturalandırmayı asla etkilemez. | | `headers?` | `Record` | Ek yukarı akış başlıkları. Yetkilendirme, çerezler, API anahtarı başlıkları, gömülü yeni satırlar ve geçersiz adlar reddedilir. | | `openRouterRouting?` | `OpenRouterProviderRouting` | Varsayılan OpenRouter `order`, `only` ve `allowFallbacks` tercihleri; yalnızca `openai-chat` ile kurallı OpenRouter için geçerlidir. | | `modelOpenRouterRouting?` | `Record` | Sağlayıcı genelindeki OpenRouter tercihinin yerini alan tam model kimliği geçersiz kılmaları. | @@ -138,7 +148,7 @@ alanlı seçilmiş kimlikleri yalın kimliklere yeniden yazar. | `modelReasoningEfforts?` | `Record` | Model başına etiketler. Boş bir liste çaba denetimini gizler. `reasoningEfforts`'ta olduğu gibi, yapılandırılmış her `google` adaptör merdiveni `thinkingLevel` yeteneğini iddia eder; doğrudan ve Vertex görsel olmayan istekleri düz Gemini yolunu kullanırken, Cloud Code Assist bunu istek zarfı altında gönderir. | | `modelSupportsReasoningSummaries?` | `Record` | Özetlerin bildirilmesini durdurmak ve özet teslim alanlarını kaldırmak için bir modeli `false` olarak ayarlayın. | | `modelReasoningSummaryDelivery?` | `Record` | Model başına Responses teslim enum'ı; mevcut bir teslim alanını yeniden yazar. | -| `modelAdapters?` | `Record` | Karışık hatlı ağ geçitleri için model başına `openai-chat` veya `openai-responses` hat geçersiz kılma. Açık girdiler kayıt defteri varsayılanlarını yener. OpenCode Go önayarı, kardeş modelleri belgelenmiş hatlarında bırakırken `gpt-5.6-luna` için Responses'ı seçer; DeepSeek, `deepseek-v4-flash` için yerel Responses seçebilir; ve GitHub Copilot, modeller (`gpt-5.3-codex`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`) için yalnızca Responses varsayılanlarını bildirir çünkü bu modeller ajan trafiği için `/chat/completions`'ı reddeder. Yerleşik varsayılanı olmayan modeller (örneğin `gpt-5.4-nano`) burada dahil edilebilir. Tek hatlı yukarı akış pinleri ve kurallı ChatGPT iletme geçersiz kılmaları reddeder. | +| `modelAdapters?` | `Record` | Karışık hatlı ağ geçitleri için model başına `openai-chat` veya `openai-responses` hat geçersiz kılma. Açık girdiler kayıt defteri varsayılanlarını yener. OpenCode Go önayarı, kardeş modelleri belgelenmiş hatlarında bırakırken `gpt-5.6-luna` için Responses'ı seçer; DeepSeek, `deepseek-v4-flash` için yerel Responses seçebilir; ve GitHub Copilot, modeller (`gpt-5.3-codex`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6.1-sol`, `gpt-6.1-terra`, `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`) için yalnızca Responses varsayılanlarını bildirir çünkü bu modeller ajan trafiği için `/chat/completions`'ı reddeder. Yerleşik varsayılanı olmayan modeller (örneğin `gpt-5.4-nano`) burada dahil edilebilir. Tek hatlı yukarı akış pinleri ve kurallı ChatGPT iletme geçersiz kılmaları reddeder. | | xAI Responses katılımı (panel) | anahtar | Yalnızca `xai` için `grok-4.5` ve `grok-4.6` `modelAdapters` girdilerini atomik olarak ayarlar veya temizler. Tek girdi, sonraki anahtar yazımı ikisini eşitleyene kadar karma durum olarak görünür. Diğer geçersiz kılmalar ve katman davranışı değişmez. Grok 4.7, OAuth üzerinde kayıt defteri hat varsayılanıyla Responses kullanır ve açık `modelAdapters["grok-4.7"] = "openai-chat"` girdisiyle Chat’e geçirilebilir. | | `xaiResponsesXSearch?` | `boolean` | Varsayılan olarak devre dışıdır. Bir xAI Responses hedefinde, yalnızca canlı bir `web_search` aracı son istek normalleştirmesinden sağ çıktığında sağlayıcı tarafından barındırılan `x_search` bildirimini ekler. Mevcut bildirimler yinelenmez, çağıranın `tool_choice`/`allowed_tools` seçicileri hiçbir zaman genişletilmez ve bu, web araması yardımcı hizmetinin `search.xSearch` seçeneklerinden ayrıdır. | | `modelPreferHostedTools?` | `Record` | Barındırılan bir araç ad alanı ayıran iletme harici Responses ağ geçitleri için tam model dahil etme. Şu anda yalnızca `["image_generation"]` kabul eder; eşleşen bir model `openai-responses` hattını kullanmalı ve bu barındırılan aracı desteklemelidir. Çakışan istemci `image_gen` bildirimlerini kaldırır ve arayan araç seçimini korumak için seçicilerini yeniden yazar. OpenAI API sanal `-pro` modelleri için önce seçilen genel kimlik eşleştirilir ve çözümlenen temel hat model kimliği bir geri dönüştür. `modelAdapters` önce genel kimliği, ardından temel kimliği çözer; ikinci çözümleme son hattı belirler. Diğer modeller normal takma ad davranışını korur. | @@ -152,12 +162,13 @@ alanlı seçilmiş kimlikleri yalın kimliklere yeniden yazar. | `noStopModels?` | `string[]` | Arayan tarafından belirtilen `stop` değerini reddeden modeller. `openai-chat` bağdaştırıcısı, Chat geçişi ve Responses geçişi bu kimlikler için alanı göndermez. Yerleşik `xai` ön ayarı, xAI belgelerine göre bunu reddeden akıl yürütme modellerini (`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`) burada listeler; `grok-4.20-0309-non-reasoning`, `grok-composer-2.5-fast` çağıranın `stop` değerini korur. | | `noPenaltyModels?` | `string[]` | Varlık/frekans cezalarını reddeden modeller. Yerleşik `xai` ön ayarı, xAI belgelerine göre bu değerleri reddeden akıl yürütme modellerini (`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`) burada listeler; akıl yürütmesiz modeller çağıranın cezalarını korur. | | `noStructuredOutputModels?` | `string[]` | `openai-chat` uç noktası `response_format`'ı reddeden tam model kimlikleri. Yalnızca tam bir istenen model eşleşmesi alanı atlar; yapılandırılmış çıktı çevirisi diğer her `openai-chat` modeli için etkin kalır. | -| `noJsonSchemaModels?` | `string[]` | `openai-chat` uç noktası `json_schema` biçimini reddeden ama `json_object` kabul eden tam model kimlikleri. Böyle bir istek atılmak yerine `json_object` seviyesine düşürülür, böylece JSON isteyen çağıran yine JSON alır. Bir model her iki listede de varsa `noStructuredOutputModels` kazanır. `opencode go`, `opencode zen` ve `opencode free` hazır ayarları bunu DeepSeek rotaları için getirir. | +| `noJsonSchemaModels?` | `string[]` | `openai-chat` uç noktası `json_schema` biçimini reddeden ama `json_object` kabul eden tam model kimlikleri. Böyle bir istek atılmak yerine `json_object` seviyesine düşürülür, böylece JSON isteyen çağıran yine JSON alır. Bir model her iki listede de varsa `noStructuredOutputModels` kazanır. `opencode go` ve `opencode zen` hazır ayarları bunu DeepSeek rotaları için getirir. | | `foldDeveloperRoleToSystem?` | `boolean` | Bir `openai-chat` hedefinin `developer` rolünü kabul edip etmediğini kaydeder. `foldDeveloperRoleToSystem` ayarlanmamışsa `system`, `true` ise `system`, `false` ise `developer` olarak gönderilir. Ayarlanmamış olması bu hedef için hiçbir şey kaydedilmediği anlamına gelir; `true` rolü reddeden bir üst hizmeti, `false` ise kabul edeni kaydeder. Her durumda mesaj konuşmadaki konumunu korur; yalnızca rol değişir. Rolü reddeden bir hedef `400 role 'developer' is not allowed` yanıtı verir ve tur hiç başlamaz; kaydedilmemiş durumun katlanmış olmasının nedeni budur. | | `parallelToolCalls?` | `boolean` | Paralel araç çağrılarını açıp kapatın. OpenAI Chat varsayılan olarak açıktır; sohbet harici adaptörler yalnızca açık `true` durumunda bildirir. | | `responsesItemIdRepair?` | `{ message?: string[]; reasoning?: string[]; repairMissingTerminalIds?: boolean; repairInvalidIds?: boolean }` | Tam yer tutucu kimlikleri, eksik terminal kimlikleri ve (`repairInvalidIds` ile) kurallı `msg_`/`rs_` öneki eksik olan mesaj/akıl yürütme kimlikleri için varsayılan olarak devre dışı bırakılmış aşağı akış SSE onarımı. Fonksiyon çağrısı kimlikleri asla yeniden yazılmaz. Yerleşik DeepSeek son ikisini varsayılan olarak etkinleştirir. | | `responsesSnapshotRepair?` | `boolean` | SSE ve JSON'daki seyrek Responses yaşam döngüsü anlık görüntüleri için varsayılan olarak devre dışı bırakılmış istemciye yönelik onarım. Ham inceleme ve kalıcılık değişmeden kalırken eksik kurallı durumu, çıktıyı ve araç meta verilerini doldurur. | -| `retryOn429?` | `{ enabled?: boolean; attempts?: number; intervalMs?: number; maxIntervalMs?: number; respectRetryAfter?: boolean }` | Yalnızca API anahtarı sağlayıcıları (`authMode: "key"`). İsteğe bağlı aynı hedef 429 yeniden denemesi: `retryOn429` olmadığında özellik kapalıdır; nesnenin varlığı `enabled: false` olmadığı sürece özelliği etkinleştirir. 429'da proxy bekler (yukarı akış `Retry-After` veya sabit aralık) ve herhangi bir anahtar yük devretmesinden önce aynı istek üzerinde aynı anahtarla aynı isteği yeniden oynatır — ana metin turu kurtarma döngüsü, Responses doğrudan geçiş hattı, görsel/video köprüsü, web araması sidecar'ı ve terminal devamları genelinde. Yalnızca akış öncesi HTTP 429 yanıtları yeniden oynatma için uygundur; özel `runTurn` aktarımları HTTP yeniden deneme döngüsünün dışındadır. `attempts`, ilk 429'dan sonraki aynı anahtar yeniden oynatmalarını sayar (toplam gönderim = `attempts` + 1) ve ana kurtarma döngüsü, terminal koruma devamı ve köprü yeniden denemeleri tarafından paylaşılan tek bir istek genelinde bütçedir. `attempts`'ı tüketmek yalnızca daha fazla aynı anahtar yeniden oynatmasını durdurur: normal anahtar yük devretmesi veya nihai hata işleme daha sonra kullanılabilir hedeflere göre geçerli olur — anahtar kimlik doğrulamalı doğrudan geçiş hattında yük devretme yoktur, bu nedenle tükenen 429 olduğu gibi görünür. Codex'in kendisi 429'u asla yeniden denemez, bu nedenle tek anahtarlı sağlayıcılar için tek savunma budur. Varsayılanlar: `enabled: true`, `attempts: 3`, `intervalMs: 5000`, `maxIntervalMs: 60000` (tek bir bekleme `maxIntervalMs` ile sınırlandırılır, kendisi de 600000 ile sınırlandırılır), `respectRetryAfter: true`. | +| `promptCacheKey?` | `boolean` | `openai-chat` sağlayıcısı genelinde `prompt_cache_key` iletimini etkinleştirir. Adaptör aldığı anahtarı iletir, üretmez; ancak Claude Messages çevirisi `metadata.user_id` veya metadata yoksa model/sistem/araç grubundan anahtar türetebilir. Ön ayar belirtmedikçe varsayılan kapalıdır; Mistral [istem önbelleği sözleşmesine](https://docs.mistral.ai/studio/conversations/advanced/prompt-caching) göre açar, açık `false` kapatır. Yalnızca üst akış desteği belgeliyse açın; katı ağ geçitleri bilinmeyen alanı HTTP 400 ile reddedebilir. | +| `retryOn429?` | `{ enabled?: boolean; attempts?: number; intervalMs?: number; maxIntervalMs?: number; respectRetryAfter?: boolean }` | Yalnızca API anahtarlı sağlayıcılar (`authMode: "key"`). Ayar yoksa kapalıdır; kanonik OpenCode Go ve Command Code ile kanonik Mistral `openai-chat` istisnadır: 6 tekrar, 10 sn aralık, 60 sn tavan. Nesne verilmesi etkinleştirir; `enabled: false` bu varsayılanları da kapatır. Akış öncesi HTTP 429 durumunda `Retry-After` veya sabit aralık beklenir ve anahtar değişiminden önce aynı istek aynı anahtarla yeniden gönderilir. Metin turu kurtarma, Responses passthrough, görüntü/video köprüsü, web-search yan hizmeti ve terminal devamlarını kapsar; özel `runTurn` taşıması kapsam dışıdır. `attempts`, ilk 429 sonrasındaki aynı anahtar tekrarlarıdır (toplam gönderim = `attempts` + 1) ve istek genelinde paylaşılır. Tükenmesi yalnızca aynı anahtar tekrarını durdurur; normal anahtar değişimi veya son hata işleme sürer. Anahtarlı passthrough yolunda geçiş yoktur, 429 aynen döner. Kanonik Mistral Chat için açık üst akış gecikmesi izin verilen beklemeyi aşarsa aynı anahtar tekrarı durur ve özgün `Retry-After` korunur; havuzdaki başka anahtar yine hizmet verebilir. Diğer sağlayıcılarda gecikme tavana kırpılır. Mistral, kimlik bilgisi/model başına son tarihleri paylaşır; `requestPacing.enabled: false` veya `retryOn429.respectRetryAfter: false` bunu kapatır. Tekrar zamanı olmayan 429 ve doğrulanmış dakika bütçesi baskısı, temkinli 60 sn bekleme ve ardından tek kurtarma denemesi başlatır; başarı olağan trafiği geri getirir. Sağlıklı trafik sonraki token maliyetini tahmin etmez. Bildirilen kotalar kullanılır; tükendiği bilinen aylık kotalar hariçtir, kesin sıfırlama zamanı vaat edilmez ve geçmiş sıkıştırılmaz. Codex 429’u kendisi yeniden denemez. Nesne varsayılanları: `enabled: true`, `attempts: 3`, `intervalMs: 5000`, `maxIntervalMs: 60000` (bekleme tavanı, ayar en fazla 600000), `respectRetryAfter: true`. | | `transientRetryOn5xx?` | `{ enabled?: boolean; attempts?: number }` | Yalnızca anahtarla kimlik doğrulanan `openai-chat` ve `openai-responses` sağlayıcıları. `authMode: "forward"` sağlayıcıları (ChatGPT hesap havuzu) bu seçeneği hiç okumaz ve varsayılan merdiveni korur. Akış öncesi geçici yukarı akış durumları (500, 502, 503, 504, 520, 521, 522) için isteğe bağlı yeniden deneme: seçenek belirtilmezse kapalıdır; nesnenin varlığı, `enabled: false` olmadığı sürece özelliği etkinleştirir. İlk Responses isteğini, terminal koruma devamını, yerel `/v1/chat/completions` isteklerini ve 429/hesap kurtarma yeniden getirmelerini kapsar. `attempts`, bir istek için ilk gönderim dahil izin verilen yukarı akış gönderimlerinin TOPLAM sayısıdır (1..10, varsayılan 3) — bağlantı sıfırlama kurtarmasıyla paylaşılan, istek kapsamlı tek bütçedir; dolayısıyla `3`, sağlayıcıya en fazla üç gerçek isteğin ulaşması anlamına gelir. Beklemelerde 400 ms'lik sabit üstel geri çekilme uygulanır, süre 5 sn ile sınırlandırılır ve `Retry-After` dikkate alınır. Hız sınırlamasını işleyen `retryOn429` seçeneğinden ayrıdır; akış ortası hataları hiçbir zaman yeniden oynatılmaz. Her Combo hedefinde önceden ayrılmış ilk gönderim, o hedefin yapılandırılmış toplamına dahildir: `attempts: 1` tek gönderim yapar; rezervasyonu geri vererek ek hak yaratmaz ve ortak tavanı artırmaz. Gönderim öncesi yerel ret veya iptal, kullanılmamış rezervasyonu serbest bırakır. | | `retryOnReset?` | `{ enabled?: boolean; replacements?: number }` | Yerel `openai-responses` sağlayıcıları (`authMode: "forward"` dahil) ve upstream başlıkları gelmeden önceki genel Responses çeviri gönderim yolu için geçerlidir. Çağıranın hiçbir şey gözlemlemediği bir anda başarısız olan gönderimin isteğe bağlı olarak değiştirilmesi: seçenek belirtilmezse kapalıdır; nesnenin varlığı, `enabled: false` olmadığı sürece özelliği etkinleştirir. İki belirsiz aşamayı da kapsar: yanıt başlığı gelmeden kopan bağlantı ve başlıktan sonra yalnızca denetim olayları taşırken kopan SSE gövdesi. Kanonik ChatGPT upstream WebSocket'i create çerçevesi gönderildikten sonra, herhangi bir Responses olayından önce kapanır veya hata verirse aynı şekilde kapsanır ve değiştirme gönderimi HTTP üzerinden yapılır. Yalnızca kendi kendine yeten bir istek değiştirilir: `store: false`, eksiksiz `input`, `previous_response_id`, `conversation` veya `stream_id` bulunmaması ve yalnızca istemcinin yürüttüğü araçlar. `replacements`, BİR mantıksal isteğin tüm bacaklar ve tüm combo alt istekleri boyunca yapabileceği değiştirme gönderimi sayısıdır (1..2, varsayılan 1). Bacak başına yeniden deneme sayısı da gönderim bütçesi de değildir; bu yüzden bir değiştirme gönderimi, ilgili bacağın hâlihazırda sahip olduğu gönderim payına sığmak zorundadır. Halihazırda çıktı veya araç çağrısı üretmiş bir istek, bu değer ne olursa olsun asla değiştirilmez. Kaynak ilk çıkarımı zaten başlatmışsa değiştirilen çıkarım yine ücretlendirilebilir; bu nedenle varsayılan olarak kapalıdır. Genel çeviri gönderim yolu, ilk ve yeniden oluşturulan gönderimlerde başlık öncesi bağlantı sıfırlamasından sonra aynı izin ve gönderim bütçesiyle değiştirme destekler. Adaptörlerin kendi yönettiği taşıma yolları ve upstream başlıklarından sonraki çevrilmiş akış hataları kapsam dışıdır. | | `autoToolChoiceOnlyModels?` | `string[]` | `tool_choice`'u yalnızca `auto` veya `none` kabul eden modeller; zorunlu seçimlerin derecesi düşürülür. | @@ -171,8 +182,8 @@ alanlı seçilmiş kimlikleri yalın kimliklere yeniden yazar. | `anthropicEofTolerance?` | `boolean` | Yalnızca görünür metin veya eksiksiz bir JSON nesnesi araç girdisi alındığında, Anthropic uyumlu bir ağ geçidinin `message_stop` öncesinde biten bir akışı tamamlamasına izin verin. Varsayılan olarak kapalıdır. | | `googleMode?` | `"ai-studio" \| "vertex" \| "cloud-code-assist"` | Google aktarım/kimlik doğrulama modu. Varsayılan `ai-studio`. | | `googleToolSchemaPolicy?` | `"compatible" \| "reject-lossy"` | Yalnızca Google. Alanın atlanması veya `compatible`, uyumlu şemayı ve mevcut doğrudan olmayan 400 onarımını korur. `reject-lossy`, ilk kaybı veya sonucu belirsiz sınırlı bir karşılaştırmayı gönderimden önce reddeder ve kısıtları açacak Vertex ya da Cloud Code Assist onarımını engeller. Doğrudan AI Studio bu onarımı yapmaz. | -| `project?` | `string` | Vertex veya Antigravity Cloud Code Assist proje kimliği. | -| `location?` | `string` | Vertex konumu; ortam geri dönüşü `GOOGLE_CLOUD_LOCATION`'dır. | +| `project?` | `string` | Vertex veya Antigravity Cloud Code Assist proje kimliği. Ayarlandığında Vertex API anahtarları proje uç noktasını kullanır; ayarlanmadığında `GOOGLE_CLOUD_PROJECT` tanımlı olsa bile express modunu kullanır. ADC, `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT` değerine geri döner. `"auto"` değeri her Vertex API anahtarının kendi projesini çözmesini sağlar (başarılı bir çözümleme anahtar başına önbelleğe alınır; çözümlemesi başarısız olan anahtar sonraki bir istekte yeniden yoklanır); böylece tek bir anahtar havuzu birden çok projeyi kapsayabilir. ADC `"auto"` değerini reddeder. | +| `location?` | `string` | Vertex konumu. API anahtarlı proje uç noktaları varsayılan olarak `global` kullanır ve `GOOGLE_CLOUD_LOCATION` değerini yok sayar; ADC bu alanı veya `GOOGLE_CLOUD_LOCATION` geri dönüşünü gerektirir. | | `mcpServers?` | `Record` | Yalnızca Cursor: stdio veya Akışlanabilir HTTP MCP sunucuları. | | `desktopExecutor?` | `DesktopExecutorConfig` | Yalnızca Cursor: harici bilgisayar kullanımı ve ekran kaydetme komutları. | | `unsafeAllowNativeLocalExec?` | `boolean` | Cursor eski boolean değeri, yalnızca daha yeni alan ayarlanmadığında `nativeLocalExec: "on"` değerine eşdeğerdir. | @@ -679,3 +690,8 @@ geçerli adı kontrol edin. ### `anthropicAccountPool.routes` `anthropicAccountPool.routes`, modeli kayıtlı Anthropic OAuth hesap kimliklerine bağlar. Havuz açıkken büyük/küçük harfe duyarlı `match` kalıbıyla ilk eşleşen kural ilk seçimi ve 429 yeniden denemesini sınırlar. `fallback: true` yalnızca kuralda uygun hesap kalmadığında normal havuza döner. + + +### `promptLengthPricing` + +İsteğe bağlı `promptLengthPricing` alanı dört temel ücreti genişletir. `mode: "automatic"` (veya alanın olmaması) katalog bağlam kurallarını korur; `mode: "flat"` bunları devre dışı bırakır. `mode: "custom"` için pozitif güvenli tamsayı `thresholdTokens`, `"gt"` (>) veya `"gte"` (≥) değerli `comparison` ve dört mutlak alternatif ücret içeren `rates` kullanılır. Eşik, önbellek okuma ve yazma dahil tüm istem tokenlarını sayar; çıktıyı saymaz. Seçilen ücretler çıktı dahil isteğin tamamına uygulanır ve eşiğin her iki tarafında katalog kuralının yerini alır. Fast/Priority ayarlamaları ücretler seçildikten sonra bir kez uygulanır ve katalogda bu model için yayımlanan Priority ilişkisini izler; tahmin yalnızca katalog bu ilişkiyi `lower-bound` olarak bildirdiğinde (şu anda xAI Grok) alt sınır olarak işaretlenir; katalog kuralı olmayan bir modelde Priority çarpanı bir kez uygulanır. **Models → Price** üzerinden yapılandırın. **Reset to automatic**, temel ücretler ve kural dahil tüm geçersiz kılmayı siler. Eski yapılandırmalar uyumludur; bu ayarlar gösterilen tahminleri ilgilendirir. diff --git a/docs-site/src/content/docs/tr/reference/proxy-formats.md b/docs-site/src/content/docs/tr/reference/proxy-formats.md index 6ca40e9fd11..5d14cc1288b 100644 --- a/docs-site/src/content/docs/tr/reference/proxy-formats.md +++ b/docs-site/src/content/docs/tr/reference/proxy-formats.md @@ -44,6 +44,8 @@ Chat Completions ve Messages uç noktaları ertelenmiş araç kataloglarını de Ayrıca, her gelen bağlantıda sağlayıcının `default.` ad alanlı çağrısı, isteğin bildirdiği yalın araç adına normalleştirilir (`default.Read` → `Read`). +Native Messages, `tools: []` ile birlikte sistem mesajlarındaki satır içi araç tanımlarını da tanır. Bu adlar kalıcı olarak kaldırılana kadar çağrılabilir; ilgisiz istemci araçlarına izin vermez. Barındırılan MCP çağrıları ve sunucu araçlarının argüman akışları native biçimlerini korur. + ## Uç nokta genel bakışı | İstemci yüzeyi | Uç nokta | Başarılı akışsız sonuç | Başarılı akış veya soket sonucu | diff --git a/docs-site/src/content/docs/tr/troubleshooting/spend-ledger-synced-folder.md b/docs-site/src/content/docs/tr/troubleshooting/spend-ledger-synced-folder.md new file mode 100644 index 00000000000..ccaa188441b --- /dev/null +++ b/docs-site/src/content/docs/tr/troubleshooting/spend-ledger-synced-folder.md @@ -0,0 +1,75 @@ +--- +title: Eşitlenen klasörde harcama kaydı reddi +description: Durum dizini iCloud Drive veya başka bir eşitlenen klasördeyken görülen harcama kaydı hatasının nedeni ve çözümü. +--- + +Harcama kaydı depolama reddi, `server_error` türü ve `spend_ledger_storage_unavailable` +kodu ile HTTP 503 döndürür. Bu yerel bir depolama sorunudur; sağlayıcı veya hesap +sağlığını olumsuz etkilemez. İstek günlüklerinde `local-refusal` olarak kaydedilir. +Akışı başlamış yanıtlarda aynı kod son hata olayında yer alır. Dosyanın durumu +geçiciyse aynı oturumdaki diğer istekler başarılı olabilir. + +Eski sürümler yanıltıcı bir “Provider unreachable” önekiyle HTTP 502 döndürüyordu. +Güncel sürümler hangi dosya rolünün hangi kontrolden geçemediğini belirtir: + +```text +Spend-ledger storage could not be opened safely (journal: extra-hard-link). +``` + +## Kontrolün amacı + +opencodex, durum dizininde (varsayılan `~/.opencodex` veya `OPENCODEX_HOME`) +`spend-ledger.jsonl` günlük dosyasını ve `spend-ledger.salt` tuz dosyasını tutar. +Her yazmadan önce dosyaların size ait normal dosyalar olduğunu, sembolik bağlantı +olmadığını ve tam olarak bir dizin girdisine sahip olduğunu doğrular. İkinci bir +sabit bağlantı, aynı diskte başka bir adın aynı baytları okuyabilmesi veya +değiştirebilmesi demektir. Bu yüzden opencodex yazmayı reddeder. Bu kontrol +bilerek katı tutulur. + +| İletideki koşul | Anlamı | +| --- | --- | +| `extra-hard-link` | Aynı dosyaya başka bir dizin girdisi işaret ediyor. | +| `symbolic-link` | Kayıt dosyası sembolik bağlantı. | +| `not-regular-file` | Kayıt yolunda normal dosya dışında bir nesne var. | +| `foreign-owner` | Dosya başka bir kullanıcıya ait. | +| `invalid-salt` | Tuz dosyası mevcut ancak içeriği geçerli değil. | + +İletideki rol `journal`, `journal-compaction` (günlük sıkıştırılırken yazılan geçici +dosya) veya `salt` olabilir. + +## Eşitlenen klasörler neden bu hatayı tetikler? + +“Masaüstü ve Belgeler Klasörleri” açık olan iCloud Drive ve OneDrive, Dropbox, +Google Drive gibi File Provider istemcileri, bir değişikliği hazırlarken veya +yüklerken dosyaya geçici olarak ikinci bağlantı tutabilir. Durum dizini böyle bir +klasördeyse sıradan bir yazmadan sonra günlüğün kısa süreliğine iki bağlantısı +olabilir. O anda gelen istek reddedilirken bir sonraki başarılı olabilir. +Eşitleme tamamlanınca dosya yeniden tek bağlantılı olur; sonradan incelemek +sorunu göstermeyebilir. + +Başlangıçta opencodex; durum dizini iCloud Drive (`~/Library/Mobile Documents`), +bir File Provider klasörü (`~/Library/CloudStorage`), iCloud Masaüstü ve Belgeler +eşitlemesi açık görünürken Masaüstü/Belgeler veya Google Drive for desktop +kullanılıyor görünürken Masaüstü/Belgeler içindeyse uyarır. Google Drive ipucu, +`~/Library/CloudStorage/GoogleDrive-*` ya da +`~/Library/Application Support/Google/DriveFS` varlığıdır. Hangi klasörlerin +eşitleneceğini uygulamadan okumaz; Belgeler seçili olmasa bile Drive kurulumu +uyarıya yol açabilir. Bu yalnızca bilgilendirmedir. Klasör düzenine dayalı +algılama hem gereksiz uyarı verebilir hem de bir durumu kaçırabilir. + +## Çözüm + +Durum dizinini eşitlenen klasörlerin dışında tutun. Varsayılan `~/.opencodex` +eşitlenmez. + +1. opencodex'i durdurun. +2. Durum dizinini `~/.opencodex-trial` gibi eşitlenmeyen bir konuma taşıyın veya kopyalayın. +3. `OPENCODEX_HOME` değişkenini o konuma ayarlayın ya da varsayılanı kullanmak için kaldırın; opencodex'i yeniden başlatın. + +Hatayı gidermek için günlüğü silmeyin, izinlerini gevşetmeyin veya kontrolü +kaldırmayın. Günlük harcama geçmişinizi tutar; kontrol de beklenmedik bir bağlantı +üzerinden yazılmasını önler. + +İleti `extra-hard-link` dışında bir koşul belirtiyorsa veya durum dizini +eşitlenen bir klasörde değilse tam ret iletisiyle bir sorun kaydı açın. Bu ileti +dosya yolu, hesap bilgisi veya istek içeriği içermez. diff --git a/docs-site/src/content/docs/tr/troubleshooting/update-failed.md b/docs-site/src/content/docs/tr/troubleshooting/update-failed.md new file mode 100644 index 00000000000..c7930988479 --- /dev/null +++ b/docs-site/src/content/docs/tr/troubleshooting/update-failed.md @@ -0,0 +1,161 @@ +--- +title: Windows'ta güncelleme başarısız +description: npm kurulum adımında başarısız olan ocx update sonrasında kurulumun durumu, güncellemeyi güvenle tamamlama ve kalan klasörleri temizleme. +--- + +Bu sayfa, çoğunlukla Windows'ta `ocx update` veya pano güncellemesinin +[#5624](https://github.com/lidge-jun/opencodex/issues/5624) örneğindeki gibi yarıda +kalması içindir: + +```text +update command failed (1) +exit 1 · code: ENOTDIR · syscall: mkdir +``` + +Ardından paketin yanında, `bunx.exe` kullanımda olduğu için silinemeyen (`EPERM`) +bir klasör kalabilir. + +## Kurulumun durumu + +Güncelleyici proxy'yi durdurur, yeni sürümü ayrı bir hazırlık klasörüne kurar, +kontrol eder ve ancak sonra eski sürümün yerine geçirir. Kurulum adımı başarısızsa +değiştirme yapılmaz: önceki sürüm kurulu ve çalıştırılabilir kalır. Güncelleyici +çıkmadan önce eski arka plan servisini veya proxy'yi yeniden başlatır. Çalışan +proxy'ye dönmek için yeniden kurmanız gerekmez. + +Kontrol edin: + +```bash +ocx --version +ocx status +``` + +Kurulum çıktısı yerel yollar içerebildiği için pano işi yalnızca çıkış durumunu +kaydeder. Terminalde `ocx update` çalıştırmak başarısız adımı ve sonraki işlemi gösterir. + +## Güncellemeyi tamamlama + +1. OpenCodex dosyalarını çalıştırabilecek uygulamaları kapatın: Codex uygulaması, + `ocx` veya `bunx` başlatan terminaller ve OpenCodex sistem tepsisi uygulaması. + Çalışan `bun.exe` veya `bunx.exe`, Windows'ta dosyalarını kilitli tutar. +2. Terminalden yeniden `ocx update` çalıştırın. +3. Aynı hata sürerse `ocx status` çalıştırıp devam eden kurtarmanın bitmesini bekleyin. + Güncelleyici proxy'yi yeniden başlattıysa onu yöneten uygulama veya servis üzerinden + (`ocx stop`, servisi veya masaüstü uygulaması) durdurup durduğunu doğrulayın. + Kurulumu yöneten aynı paket yöneticisiyle yeniden kurun. OpenCodex servisinin + yönettiği npm kurulumu için: + + ```bash + ocx stop + npm install -g --allow-scripts=bun @bitkyc08/opencodex@latest + ocx service restart + ``` + + Kurulumdan sonra yöneten servis veya masaüstü uygulamasıyla başlatın; bağımsız + çalıştırıyorsanız `ocx start` kullanın. Önizleme kanalındaysanız `latest` yerine `preview` yazın. + +## Kalan klasörler + +Bunlar npm'in genel klasöründe paketin yanında bulunur; Windows'ta genellikle +`%APPDATA%\npm\node_modules\@bitkyc08\` konumundadır. + +| Klasör | Oluşturan | Yapılacak işlem | +| --- | --- | --- | +| `.opencodex-` | Doğrudan `npm install -g` sırasında paketi değiştiren npm | İçinden OpenCodex işlemi çalışmadığında silin. | +| `.ocx-staging-` | OpenCodex güncelleyicisinin hazırlık kurulumu | İşaretli ve yarım saatten eski klasör bildirilir ama silinmez. İçinden OpenCodex işlemi çalışmadığında elle silin. `.ocx-update-owner.json` işareti yoksa kökeni doğrulanmamıştır: yarım kalan güncelleme de aynı öneki kullanan başka bir şey de oluşturmuş olabilir. Silmeden önce OpenCodex'e ait olduğunu doğrulayın. | +| `.ocx-backup-` | Güncelleyicinin önceki sürüm kopyası | Bırakın; yeni sürüm sağlıklı başlayınca otomatik silinir, başlamazsa geri yüklenir. | + +Her deneme yeni klasör kullandığı için bu adlar sonraki güncellemeyi doğrudan +engellemez. Ancak artıklar disk tüketir ve birikirse sonraki hazırlık `ENOSPC` +ile başarısız olabilir. Güncelleyici sonraki güncellemelerde de bunları yerinde +bırakır; sahiplik işaretini kontrol edip yarım saatten eski işaretli klasörleri +bildirir. Yalnızca içinden OpenCodex işlemi çalışmadığını doğruladıktan sonra silin. + +Bir klasör silinemiyorsa içinden çalışan işlem kalmış olabilir. Windows oturumunu +kapatmak veya yeniden başlatmak kilidi serbest bırakır. + +## Güncelleme UNSUPERVISED proxy bildiriyorsa + +Güncellemeden sonra arka plan servisi yenilenemediğinde güncelleyici önce mevcut +servisi launchd (macOS) veya systemd kullanıcı yöneticisi (Linux) üzerinden yeniden +başlatmayı ister. Servisin yönettiği proxy dönmezse hizmet kesilmesin diye proxy'yi +doğrudan başlatır, `UNSUPERVISED` uyarısı verir ve sıfır dışında kodla çıkar. +Proxy çalışır; ancak çökerse yeniden başlatılmaz ve sonraki servis başlangıcıyla +aynı bağlantı noktası için yarışabilir. `ocx service repair` ile yönetimi servise +devredin, ardından `ocx status` kontrolü yapın. + +Panodan başlayan güncelleme de aynı sırayı izler. Yalnızca doğrudan başlatma +başarılıysa yeni sürüm yanıtlıyor olsa bile pano aynı uyarıyla başarısızlık +gösterir. Aynı onarım komutu bunu giderir. + +## Elle kurulumdan sonra proxy 503 döndürüyorsa + +Çalışan proxy'nin üzerine kurulum yapmak, yeniden başlayana kadar +`package_tree_changed` koduyla `503` döndürmesine yol açar. 2.64.0 sonrasındaki +sürümler, paket ağacı ve Bun çalışma zamanı tamamlanınca birkaç saniye içinde +kendini yeniden başlatabilir. Eksik veya başarısız Bun postinstall adımı bu +kurtarmayı engeller. `ocx restart` veya `ocx service restart` bu durumdaki proxy'yi +bulabilir; yine de eksiksiz çalışma zamanı ve paket ağacı gerekir. 2.64.0 ve +öncesinde proxy bu durumda kalır; eski işlem bağlantı noktasını tutarken +`ocx restart` çalışan proxy yok diyebilir. `ocx service restart` kullanın veya +`/healthz` yanıtındaki `pid` işlemine son verip `ocx start` çalıştırın. + +Yükleyici çıktıysa veya başarısız olduysa hatasını inceleyin ve `ocx status` +çalıştırın. Kurtarmanın bitmesini bekleyin; çalışan proxy'yi yöneticisi üzerinden +durdurun ve durduğunu doğrulayın. Aynı paket yöneticisiyle yeniden kurup Bun +postinstall adımının tamamlanmasını sağlayın. Sonra yöneten servis/uygulama +üzerinden; bağımsızsa `ocx start` ile başlatın. + +## Güncelleme npm önbellek kontrolünde duruyorsa + +`mkdir` işleminde `ENOTDIR`, npm'in bir bileşeni dosya olan veya hedefi kayıp bir +bağlantı olan yolda klasör oluşturmaya çalıştığını gösterir. +[#6288](https://github.com/lidge-jun/opencodex/issues/6288) örneğinde, +`%LOCALAPPDATA%\npm-cache` başka sürücüye işaret eden bir junction idi; hedef +klasör silinmiş veya sürücü kaldırılmıştı. + +Güncelleyici proxy'yi durdurmadan önce önbellek klasörünü kontrol eder. npm hazırlık +kurulumu da `npm config get cache` tarafından bildirilen aynı klasörü kullanır. +Klasör kullanılamıyorsa güncelleme `cache_root_dangling_link` veya +`cache_root_not_directory` ile durur ve proxy çalışmaya devam eder: + +- `cache_root_dangling_link`: Önbellek bağlantısının/junction'ının hedefi kayıp. + Hedefi yeniden oluşturun veya npm normal klasör oluşturabilsin diye bağlantıyı kaldırın. +- `cache_root_not_directory`: Önbellek ya da üst klasörlerinden biri dosya veya + kullanılamayan sürücüde. Dosyayı başka yere alın ya da + `npm config set cache ` ile başka önbellek seçin. + +Sonra `ocx update` komutunu yeniden çalıştırın. Kontrol geçmesine rağmen `ENOTDIR` +sürerse tam `ocx update` terminal çıktısı, `npm config get prefix` ve +`npm config get cache` çıktılarıyla sorun kaydı açın. + +## Masaüstü güncellemesi “Installing update…” aşamasında takılıyorsa + +OpenCodex masaüstü uygulamasında **Install update**, uygulamayı değiştirmeden ve +yeniden başlatmadan önce yükleyiciyi GitHub Releases'ten indirir. Bilinen sürümde +indirme/kurulum başarısızsa elle indirme ipucu ve o sürümün yayın bağlantısı gösterilir. + +“Installing update…” takılır veya zaman aşımına uğrarsa ağınız GitHub yayın +indirmelerini yavaşlatıyor veya engelliyor olabilir. Hatadaki bağlantıyı kullanın +ya da [yayınlar sayfasında](https://github.com/lidge-jun/opencodex/releases) aynı +sürümü bulup işletim sisteminize ve mimarinize uygun yükleyiciyi indirin. +Önizleme sürümlerinin ayrı sayfaları vardır; uygulamanın önerdiği sürümü seçin. + +Yükleyiciyi çalıştırmadan önce proxy'yi onu yöneten uygulama veya servis üzerinden +durdurun ve durduğunu doğrulayın: + +1. Uygulamanın yönettiği proxy için OpenCodex masaüstü uygulamasından tepsi menüsüyle çıkın. +2. Servisin yönettiği proxy'yi servis denetimleriyle durdurun (OpenCodex servisi için + `ocx stop`). Bağımsız terminal proxy'sini o terminalden durdurun. +3. Windows'ta kalan çalışma zamanlarını belirlemeye yardımcı olması için: + + ```powershell + Get-Process -Name ocx, opencodex-desktop, bun, bunx -ErrorAction SilentlyContinue + ``` + + Bu liste işlemi hangi uygulamanın yönettiğini kanıtlamaz. OpenCodex çalışma + zamanını belirleyip yöneten uygulama/servisle durdurun; `bun` başka uygulamaya + ait olabilir. Kurulumdan önce uygulamanın ve proxy'nin çıkmasını bekleyin. + +İndirdiğiniz yükleyiciyi çalıştırın, ardından masaüstü uygulamasını açın veya +yöneten servisi yeniden başlatın. diff --git a/docs-site/src/content/docs/troubleshooting/codex-cannot-sign-in.md b/docs-site/src/content/docs/troubleshooting/codex-cannot-sign-in.md index 9bff3814fdb..e1f84d88b7c 100644 --- a/docs-site/src/content/docs/troubleshooting/codex-cannot-sign-in.md +++ b/docs-site/src/content/docs/troubleshooting/codex-cannot-sign-in.md @@ -114,3 +114,17 @@ ocx account login openai --device See [Codex Integration](/guides/codex-integration/) for what the injection writes and how routing is chosen. + +## OAuth refresh capacity reached + +If a Responses request reports `OAuth token refresh capacity reached`, wait at +least one second before retrying. The proxy limits pending OAuth refresh work +to 256 consumers per process, including requests waiting on the same refresh. +A disconnected request keeps its refresh reservation until that work settles; +requests using fresh cached credentials can still proceed. + +Before a response starts, this temporary refusal returns HTTP 503 with +`Retry-After: 1`. A stream that has already started instead ends with a server +error. Capacity refusal does not mark the account as needing reauthentication, +so signing in again is unnecessary for this error. A refresh that never settles +can continue occupying capacity; the limit does not impose a refresh deadline. diff --git a/docs-site/src/content/docs/troubleshooting/update-failed.md b/docs-site/src/content/docs/troubleshooting/update-failed.md index df5b916612c..678eb5b03d7 100644 --- a/docs-site/src/content/docs/troubleshooting/update-failed.md +++ b/docs-site/src/content/docs/troubleshooting/update-failed.md @@ -34,6 +34,25 @@ paths. Running `ocx update` in a terminal shows the step that failed and the nex ## Finish the update +### If npm staging times out + +On Windows, the npm-backed `ocx update` staging install has a three-minute budget. A timeout reports +`ETIMEDOUT` with that budget. On Windows, the updater terminates the installer's entire +process tree and confirms its exit before removing the stage and recovering the previous +service. It does not continue extracting into a deleted stage after recovery starts. + +If installer exit cannot be confirmed, the update fails and keeps the staging folder and +its ownership marker. Confirm that no installer or OpenCodex process runs from that folder +before removing it. A retained folder does not prevent another update attempt. + +Check your registry connection if the install repeatedly exceeds the budget, then retry +`ocx update`. Increasing the budget is not needed to clean up a timed-out install safely. + +This process-tree exit guarantee applies to Windows npm updates. POSIX retains its existing +timeout handling; complete-tree timeout cleanup remains open there. + +### Retry or reinstall + 1. Close anything that may still run OpenCodex files: the Codex app, terminals that started `ocx` or `bunx`, and the OpenCodex tray. A running `bun.exe` or `bunx.exe` keeps its files locked on Windows. diff --git a/docs-site/src/content/docs/zh-cn/contributing.md b/docs-site/src/content/docs/zh-cn/contributing.md index 412a3d66bef..8aa8bb4cef5 100644 --- a/docs-site/src/content/docs/zh-cn/contributing.md +++ b/docs-site/src/content/docs/zh-cn/contributing.md @@ -162,6 +162,9 @@ package API,还要从 `src/index.ts` export。 `passthrough` 声明都必须说明具体限制;基于 fixture 的声明必须列出证明它的准确 assertion id。 在 `tests/fixtures/compatibility/` 中添加不含 secret 的 request vector,并编写通过 production adapter 执行该向量的聚焦测试。 +`passthrough` claim 只能引用 manifest test 中绑定到该 feature 的 selector 上的 `equals` assertion, +且其期望值必须正是调用方在同一位置的原值。因此,被删除、被改写或无关的字段都不能标为 lossless。 +对于路由可能只保留其一的方面,例如 custom tool 及其 grammar,请拆成独立的 claim。 兼容性 manifest 是被动数据。普通 router、Responses handler 和 server startup path 不得导入 manifest 目录或激活 Compatibility Lab。 diff --git a/docs-site/src/content/docs/zh-cn/getting-started/quickstart.md b/docs-site/src/content/docs/zh-cn/getting-started/quickstart.md index 9d0c51ebf7b..76d866052f8 100644 --- a/docs-site/src/content/docs/zh-cn/getting-started/quickstart.md +++ b/docs-site/src/content/docs/zh-cn/getting-started/quickstart.md @@ -18,7 +18,7 @@ ocx init `ocx init` 会引导你完成: -1. **选择 provider** — 从内置 registry 的 104 个预设中选择一个,或选择 `custom` 手动输入 base URL 和 adapter。 +1. **选择 provider** — 从内置 registry 的 106 个预设中选择一个,或选择 `custom` 手动输入 base URL 和 adapter。 2. **API key** — 粘贴一个 key,或引用一个环境变量,例如 `${ANTHROPIC_API_KEY}`。 3. **默认模型** — 对于 key、本地和 custom provider,接受预设值或输入模型 id。 4. **代理端口** — 默认为 `10100`。 diff --git a/docs-site/src/content/docs/zh-cn/guides/claude-code.md b/docs-site/src/content/docs/zh-cn/guides/claude-code.md index 5a28a1ac65b..67e176bebb9 100644 --- a/docs-site/src/content/docs/zh-cn/guides/claude-code.md +++ b/docs-site/src/content/docs/zh-cn/guides/claude-code.md @@ -158,18 +158,18 @@ unknown 表示 opencodex 无法确定设置是否仍指向自己的代理。外 ### Picker 模式:在第一方 Code 标签页中显示 opencodex 模型 -Picker 模式是第一方模式的一部分。在 macOS 上选择第一方时默认开启;设置 +Picker 模式是第一方模式的一部分。在 macOS 和 Windows 上选择第一方时默认开启;设置 `claudeCode.intercept.picker: false` 后会保持关闭。它会修改第一方 Desktop 的 Code 标签页模型选择器, -按名称列出可用的 opencodex 模型。首次开启时,macOS 可能会要求你在登录钥匙串中信任本地证书颁发机构。 +按名称列出可用的 opencodex 模型。首次开启时,macOS 可能会要求你在登录钥匙串中、Windows 可能会要求你在当前用户根存储中信任本地证书颁发机构。 该颁发机构限制为 `claude.ai` 及其子域名。可导出的签名身份由 OS 凭据存储保护,普通重启会复用同一证书和密钥。 -OpenCodex 配置目录中不保存明文 Picker 签名密钥。受限 CA 的完整验证和 OS 信任检查仍然适用。 -已批准的身份不变且凭据存储可用时,重启不会添加或删除证书信任设置。启动恢复绝不会安装信任: +OpenCodex 配置目录中不保存明文 Picker 签名密钥。受限 CA 的完整验证和 OS 信任检查仍然适用(Windows 按精确指纹与已验证叶子验证)。 +已批准的身份不变且凭据存储可用时,重启不会添加或删除 macOS 证书信任设置。启动恢复与状态检查绝不会安装信任: 若信任缺失、被撤销或无法确认,Picker 会保持待处理状态。请明确运行 `ocx claude desktop picker on` -或 `ocx claude desktop picker trust` 来授予信任。 +或 `ocx claude desktop picker trust` 来授予信任(Windows 需要时含确认)。 从旧身份进行一次性迁移时,清除原有信任可能需要同意。清理未完成时,Picker 不可用,已应用的配置档案 -会使用不解密的中继。macOS 还可能单独要求解锁钥匙串或批准应用访问凭据,重启或升级时也可能出现这些提示。 -Windows 和 Linux 仍不支持 Picker,不会启动 Picker CA、凭据存储或代理工作。主要 Claude 拦截功能仍可用, +会使用不解密的中继。macOS 还可能单独要求解锁钥匙串或批准应用访问凭据,重启或升级时也可能出现这些提示。Windows 上显式 `on` 或 `trust` 可能显示添加根的确认。 +Linux 仍不支持 Picker,不会启动 Picker CA、凭据存储或代理工作。主要 Claude 拦截功能仍可用, 其本地 CA 文件受到所有者、符号链接、文件权限及 Windows ACL 检查保护。 Picker 模式开启期间,Claude Desktop 通过 OpenCodex 访问网络。如果 OpenCodex 停止,Desktop 会处于离线状态, @@ -497,7 +497,7 @@ Search 和图像描述沿用仓库已有的 Claude Code OAuth fingerprint 先例 | Assistant `tool_use` | `function_call`(`input` → JSON 字符串化的 `arguments`) | | 用户 `tool_result` | `function_call_output`(`is_error` → `[tool error]` 前缀) | | 重放 `thinking` / `redacted_thinking` | `reasoning` 项;签名和脱敏载荷保存在有界 `ocxr1` 信封中 | -| Function 工具 | `{type: "function"}`(`web_search*` → `{type: "web_search"}`) | +| Function 工具 | `{type: "function"}`(`web_search*` → `{type: "web_search"}`). 在到 `api.anthropic.com` 的转换路由上,带类型的 `web_search_*` 和 `web_fetch_*` 声明也会发送到 Anthropic 并在服务端执行;如果网页搜索边车提供了 `web_search`,则优先使用边车。搜索和抓取结果以 `server_tool_use` 及对应的 `web_search_tool_result` / `web_fetch_tool_result` 块返回客户端 | | `tool_choice` | `auto`→`auto`,`none`→`none`,`any`→`required`,指定函数→`{type:"function",name}`,托管 WebSearch/web_search→`{type:"web_search"}` | | `max_tokens` | `max_output_tokens` | | `stop_sequences` | `stop` | diff --git a/docs-site/src/content/docs/zh-cn/guides/codex-app-models.md b/docs-site/src/content/docs/zh-cn/guides/codex-app-models.md index 5214e768367..3d2f9b0b3ff 100644 --- a/docs-site/src/content/docs/zh-cn/guides/codex-app-models.md +++ b/docs-site/src/content/docs/zh-cn/guides/codex-app-models.md @@ -65,10 +65,11 @@ visibility = "list" | 路由 | 选择器 id 与目录元数据 | | --- | --- | -| Codex 登录(账户限定的选择器行未启用) | 显示 `gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-5.6-luna` 等裸原生 id,并按 `codexAccountMode` 使用 Pool 或 Direct。GPT-5.6 行使用 922,000-token 目录窗口。 | +| Codex 登录(账户限定的选择器行未启用) | 显示 `gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-5.6-luna` 等裸原生 id,并按 `codexAccountMode` 使用 Pool 或 Direct。GPT-5.6 行使用 922,000-token 目录窗口。 `gpt-6.1-terra`:默认上下文 500,000 / 长上下文 872,000。 | | Codex 登录(账户限定的选择器行已启用且存在有效 selector) | 为每个有效 selector 与受支持原生模型的组合显示 `/` 行。每行只使用映射账户,裸原生行会从选择器中隐藏。原生 metadata 与 context window 会保留。 | -| OpenAI(API key) | 恰好八个命名空间行:`gpt-5.5`、`gpt-5.6`、Sol/Terra/Luna,以及三个 `*-pro` 虚拟 id(八个条目均为 1,050,000 context / 922,000 max input) | +| OpenAI(API key) | 命名空间模型包括 `gpt-5.5`、`gpt-5.6`、Sol/Terra/Luna、`*-pro` 虚拟 id 和 Daybreak 别名(上下文 1,050,000;最大输入 922,000)。`openai-apikey/gpt-6.1-terra` 的上下文为 1,050,000,最大输出为 128,000。 | | OpenRouter | `openrouter/openai/gpt-5.6-sol`、`openrouter/openai/gpt-5.6-terra`、`openrouter/openai/gpt-5.6-luna`(922,000) | +| OpenRouter | `openrouter/openai/gpt-6.1-terra` (1,050,000) | | Cursor | 静态回退包含 `cursor/gpt-5.6-sol`、`cursor/gpt-5.6-terra`、`cursor/gpt-5.6-luna`(1,000,000),以及 Grok 4.5/4.6/4.7 的普通和 Fast 条目(500,000)。4.6 和 4.7 还提供 `xhigh`;实时账户发现会决定最终哪些条目仍然可见。 | | xAI | 实时发现具有权威性。回退目录包含 `xai/grok-4.6` 和 `xai/grok-4.7`,默认模型仍为 `xai/grok-4.5`;三者的上下文窗口均为 500,000。Grok 4.6 和 4.7 提供 `low` / `medium` / `high` / `xhigh`(上游默认值为 `high`),Grok 4.5 最高为 `high`。 | diff --git a/docs-site/src/content/docs/zh-cn/guides/combos.md b/docs-site/src/content/docs/zh-cn/guides/combos.md index d0513216b58..efb8ddc85a5 100644 --- a/docs-site/src/content/docs/zh-cn/guides/combos.md +++ b/docs-site/src/content/docs/zh-cn/guides/combos.md @@ -152,7 +152,7 @@ combo 失败分为 **跳转** 失败和 **终止** 失败。 | 被分类为认证、订阅、配额、速率限制、过载或上游服务器错误 | 即使仅凭状态码不足以判断,也会使该目标进入冷却并跳转。 | | 客户端取消(499)、`origin_rejected`、cyber-policy 拒绝、上下文溢出,或其他无效请求 | 停止并返回错误;换其他目标也无法让请求变得有效。 | | HTTP 400 明确表示使用 ChatGPT 账户运行 Codex 时不支持该模型 | 在输出提交前尝试下一个已配置目标。完整拒绝句必须位于 `detail`、`error.message` 或纯文本中;同时包含 `detail` 与 `error` 的混合封装仍会终止请求。此原因本身不会将账户标为需要重新登录,也不会让供应商的所有模型进入冷却。 | -| 结构化 HTTP 400,明确拒绝 `user`、对 `reasoning.effort`/`reasoning_effort` 返回不支持值,或返回模型特定图像输入拒绝(`param: input`) | 在输出开始前跳转到下一个符合条件的目标,且不记录冷却时间;参见下方可选参数兼容性。 | +| 结构化 HTTP 400,明确拒绝 `user`、对 `reasoning.effort`/`reasoning_effort` 返回不支持值、返回模型特定图像输入拒绝(`param: input`),或拒绝该路由无法读取的输入媒体(`unsupported_input_modality`) | 在输出开始前跳转到下一个符合条件的目标,且不记录冷却时间;参见下方可选参数兼容性。 | | 由进程内适配器(`runTurn`)执行的 Responses 回合中,当前请求未声明的第一个工具调用(在任何输出和不可重放的副作用之前) | 让该目标进入冷却,并以相同的工具目录跳转到下一个目标。出现可见输出或不可重放的副作用之后,拒绝即为最终结果。Chat Completions 和 Anthropic Messages 请求不受影响。 | | 任何其他未分类错误 | 停止并返回错误。 | @@ -214,7 +214,7 @@ combo 失败分为 **跳转** 失败和 **终止** 失败。 ### Dashboard -打开本地 dashboard 并选择 **Models → Combos**。该工作区可以创建、编辑、重命名和删除 combo,其目标选择器会排除已禁用的模型和嵌套 combo。 +打开本地 dashboard 并选择 **Models → Combos**。该工作区可以创建、编辑、重命名和删除 combo。模型选择器使用包含隐藏模型的完整目录:显示/隐藏设置只影响对外模型列表,不影响 Combo 内部选择,选中隐藏模型也不会将其对外显示。目标选择器仍排除已禁用的提供方、嵌套 combo 和仅用于决策服务的 JEV 提供方。 每个目标还会显示实时额度徽章:**可用**、**额度已用尽**或**额度未知**。只有当每个可用目标均有当前有效的服务器确认,表明其所配置凭据的推理限额已耗尽时,编辑器才会因额度而禁止保存和创建。仅供显示的账户、模型、搜索和 MCP 额度,以及缺失或已过期的路由依据,都不会触发此限制。此限制会在适用的重置时间或数据有效期结束时解除,并在页面变为活动或可见状态时重新检查;刷新会同时重新加载 Combo 数据和额度。dashboard 编辑器目前还不能设置 `cooldownMs` 或 `waitForCooldownMs`;在后续 UI 完成前,请使用配置文件或管理 API。 @@ -230,7 +230,7 @@ ocx combo remove --yes ``` `set` 也接受 `--strategy`、`--sticky`、`--effort`、`--alias`、`--native-alias`、 -`--display-name` 和 `--rename-from`。将 `--effort`、`--alias` 或 `--display-name` 的值设为 +`--display-name`、`--description` 和 `--rename-from`。将 `--effort`、`--alias`、`--display-name` 或 `--description` 的值设为 `-` 可清除该字段。`--native-alias` 必须配合当前受支持的裸原生 alias 和非空显示名称使用。 `create` 和 `update` 是 `set` 的别名;`delete` 是 `remove` 的别名;同样的子命令也可通过 `ocx route combo` 使用。 @@ -278,6 +278,7 @@ combo 会存储在顶层的 `combos` 对象中,并以 combo id 作为键: | `alias` | 否 | 无 | 可选的、已修剪的公开模型 id;使用上面的别名规则。空值会以“无别名”形式存储。 | | `nativeAlias` | 否 | `false` | 显式允许当前受支持的裸原生 alias 接管路由和 catalog 优先级;绝不会根据 alias 自动推断。 | | `displayName` | 否 | 无 | 仅用于 catalog 展示的有界标签;`nativeAlias` 为 true 时必须非空。 | +| `description` | 否 | 无 | 仅用于 catalog 展示的行描述,限制与 `displayName` 相同。未设置时保留生成的 `Routed via opencodex → combo (combo).` 文本。 | ## 故障排查 @@ -299,6 +300,6 @@ combo id 不存在。响应是 HTTP 404,类型为 `invalid_request_error`。 ## 可选参数兼容性 -一般 400 错误仍会终止请求,但明确拒绝 `user`、对 `reasoning.effort`/`reasoning_effort` 返回不支持值,或返回模型特定图像输入拒绝(`param: input`)的结构化错误,可让 combo 在输出开始前尝试下一个符合条件的目标,而不记录冷却时间。安全策略拒绝、取消以及已经开始的输出仍不可重放。 +一般 400 错误仍会终止请求,但明确拒绝 `user`、对 `reasoning.effort`/`reasoning_effort` 返回不支持值、返回模型特定图像输入拒绝(`param: input`),或拒绝该路由无法读取的输入媒体(`unsupported_input_modality`)的结构化错误,可让 combo 在输出开始前尝试下一个符合条件的目标,而不记录冷却时间。安全策略拒绝、取消以及已经开始的输出仍不可重放。 [Canonical compatibility details](/guides/combos/#request-local-target-compatibility). diff --git a/docs-site/src/content/docs/zh-cn/guides/integrations.md b/docs-site/src/content/docs/zh-cn/guides/integrations.md index 4dab03cdc9b..65ff2c1a994 100644 --- a/docs-site/src/content/docs/zh-cn/guides/integrations.md +++ b/docs-site/src/content/docs/zh-cn/guides/integrations.md @@ -3,7 +3,7 @@ title: 集成 description: 从仪表盘将 opencodex 连接到 OpenCode、Pi、OMP、Hermes、OpenClaw、Kimi Code、gjc、DeepSeek Harness、MiniMax Code、ZCode、Prime Agent、Aside、Raycast、omo、Cline CLI、Kilo、Command Code 和 Factory Droid;每个客户端都有独立开关,且每次写入前都会备份。 --- -**连接** 标签页可将 opencodex 的提供商配置块写入客户端自己的配置文件,也可再次移除。以下 18 个客户端都采用这种方式,各有独立开关: +**连接** 标签页可将 opencodex 的提供商配置块写入客户端自己的配置文件,也可再次移除。以下客户端都采用这种方式,各有独立开关: | 客户端 | 配置文件 | 格式 | 变更生效时间 | 凭据 | |---|---|---|---|---| diff --git a/docs-site/src/content/docs/zh-cn/guides/minimax.md b/docs-site/src/content/docs/zh-cn/guides/minimax.md index d7baff5ceeb..ca0b375156e 100644 --- a/docs-site/src/content/docs/zh-cn/guides/minimax.md +++ b/docs-site/src/content/docs/zh-cn/guides/minimax.md @@ -11,7 +11,7 @@ MiniMax 发布两种不同的命令行产品。OpenCodex 在它们实际提供 ## MiniMax Code 在 OpenCodex 的模型管理页面,MiniMax Coding Plan 两个区域的 `MiniMax-M3` 和 -`MiniMax-M3.1-Flash-Preview` 均声明支持图片输入。只有所有选定模型均支持图片输入时, +`MiniMax-M3.1-Flash` 均声明支持图片输入。只有所有选定模型均支持图片输入时, Combo 的“图片 / 多模态”开关才可用。该开关控制图片附件,不提供视频上传能力。 先按照 MiniMax 的说明安装并登录 MiniMax Code,然后启动 OpenCodex 并连接可逆的文件集成: diff --git a/docs-site/src/content/docs/zh-cn/guides/providers.md b/docs-site/src/content/docs/zh-cn/guides/providers.md index 4d0e03d4f35..55dbf90e35e 100644 --- a/docs-site/src/content/docs/zh-cn/guides/providers.md +++ b/docs-site/src/content/docs/zh-cn/guides/providers.md @@ -52,7 +52,9 @@ shipped v1 配置自动迁移到 marker 2 的单一选项行。原配置只保 [`retryOn429`](/zh-cn/reference/configuration/)(同 key 的 429 重试)仅适用于 API-key 提供商 (`authMode: "key"`)。OAuth、forward 与本地预设均被排除——同一 token 绝不可重放,本地运行时 -也没有需要保留的远程 key。仅在配置后启用,默认关闭;配置了对象即启用,除非 `enabled: false`。 +也没有需要保留的远程 key。除文档中说明的特定目标默认值外,此功能需手动启用。规范的 Mistral Chat 在现有的按请求发送额度内使用耐心重试策略(6 次重放,10 秒回退间隔)。显式的 `retryOn429` 设置(包括 `enabled: false`)优先。Mistral 预设还会默认转发传入的 prompt 缓存 key;`promptCacheKey: false` 可关闭转发。缓存亲和性会提高复用几率,但不保证命中缓存,也不会消除 token 吞吐量限制。 + +Mistral 的 Studio 和 Vibe 密钥共用同一个 API-key 提供商。密钥类型和套餐额度见 [Mistral 的 API 密钥文档](https://docs.mistral.ai/admin/identity-access/api-keys);代理不会根据密钥推断订阅等级。对所有模型,上游显式给出的 `Retry-After` 截止时间会在同一凭据与模型的调用之间共享。没有重试时间的 429,在有效的每分钟计数器显示容量已耗尽,或被拒查询的实测成本超过剩余 token 但仍在公布的完整额度内时,可能触发保守的 60 秒恢复等待。这是本地恢复策略,并非对服务端重置时间的精确预测。正常流量下从不预测下一次查询的缓存或成本。等待结束后发送一次恢复探测;成功即恢复正常流量。缺失、格式错误、零授权、月度额度已耗尽或超过完整额度的依据,不会凭空推断重置,也不会压缩历史。`requestPacing: { "enabled": false }` 或 `retryOn429: { "respectRetryAfter": false }` 可关闭共享节流。对于同一密钥的重放,若 Mistral 的重试时间超过 `retryOn429.maxIntervalMs`,代理会返回 429 及重试提示,而不会提前重放。启用轮询(round-robin)密钥选择时,排队的请求会重新检查密钥冷却并为替代密钥获取节流容量;输出前的密钥拒绝可以在该请求现有的发送上限内切换到健康的密钥恢复。上下文窗口和按模型的压缩偏好保持独立。 ## 1. ChatGPT 登录(forward / 透传) @@ -92,6 +94,7 @@ ocx login cursor # 独立的 Cursor PKCE 登录 ocx login command-code # Command Code 浏览器 OAuth(或导入 ~/.commandcode/auth.json) ocx login orcarouter-oauth # OrcaRouter 浏览器授权 + PKCE ocx login devin # Cognition/Devin:优先导入 Devin CLI 凭据,否则走 Auth0 浏览器登录 +ocx login dsh-account # DeepSeek Harness Desktop(导入 ~/.dsh/.credentials.yaml 授权) ocx login github-copilot # GitHub 设备流 → Copilot 令牌(Copilot 账户) ocx login codex # Codex 账号池(别名:chatgpt、openai;需要代理正在运行) ocx logout @@ -109,6 +112,7 @@ ocx logout | `cursor` | `cursor` | `https://api2.cursor.sh` | 实验性 PKCE 登录、带可选 HTTP/1.1 兼容路径的 HTTP/2 传输,以及按账号筛选的模型发现。 | | `orcarouter-oauth` | `openai-chat` | `https://api.orcarouter.ai/v1` | 浏览器授权与密钥交换走 `https://www.orcarouter.ai` + S256 PKCE。交换结果是用户自己的普通 `sk-orca-…` API key,保存在现有凭据库中并持续复用,直到被撤销。 | | `devin` | `devin` | `https://server.codeium.com` | 实验性的非官方 Cognition/Devin 桥接。登录会先导入已安装 Devin CLI 已持有的凭据(`devin auth login` 会把 `devin-session-token` 写入它自己的 `credentials.toml`);没有则打开 Auth0 浏览器页面,再用 `RegisterUser` 把粘贴的令牌换成长期 API 密钥。`ocx login devin-cli` 仍作为已弃用别名可用。模型列表按账号通过 `GetCascadeModelConfigs` 实时获取,流式仅走 Connect-RPC 上的 `runTurn` 路径。默认不在仪表盘预设中,需要手动启用。 | +| `dsh-account` | `dsh-account` | `https://api.deepseek.com/anthropic` | 实验性优先导入账户提供商,复用已安装并登录的 DeepSeek Harness (DSH) Desktop 账户。在用户显式导入时严格以只读方式读取 `~/.dsh/.credentials.yaml` 中的凭据。独立于 DeepSeek API Key 计费。 | | `github-copilot` | `openai-chat` | `https://api.githubcopilot.com` | 实验性。GitHub 设备流 + `copilot_internal` 交换(VS Code OAuth 客户端)。需要有效的 Copilot 订阅;不是官方第三方 API。 | Google Antigravity 账户和提供方的配额查询(包括模型列表回退)使用固定的 Google 计量端点。这些目标支持透明 Fake-IP DNS,同时保留 TLS 验证、重定向拒绝和私有地址检查。自定义 base URL 仅改变模型请求,不改变配额目标;`NO_PROXY` 仍使用直连策略。 @@ -184,9 +188,37 @@ Kiro 登录需要 Kiro CLI:Unix 使用 `curl -fsSL https://cli.kiro.dev/instal 由于回滚依赖快照,当会话存储已存在但无法捕获时(文件不可读、架构不匹配、令牌选择有歧义),当 `KIROCLI_DB_PATH` / `KIRO_CLI_DB_FILE` 将导入路径指向与活动 CLI 存储不同的位置时,或当主 CLI 数据库没有可识别的令牌行时,**添加账户**会拒绝将 `kiro-cli` 登出。请修复或删除常规 `kiro-cli` 数据路径下的损坏数据库,并取消仅用于导入的选择器后重试。对于完全没有现有 `kiro-cli` 会话的机器,不受影响。 +### WorkBuddy AI 订阅 + +`workbuddy-ai` 将国际版 WorkBuddy AI 订阅作为 OpenCodex 的模型来源。在 Providers 中选择 +**WorkBuddy AI** 并登录,或运行 `ocx login workbuddy-ai`,然后完成浏览器授权。 +授权最多等待五分钟;令牌保存在 OpenCodex 中并按需刷新。无需安装 WorkBuddy 客户端, +也不会读取或修改客户端凭据。多个账户及其团队身份由现有 OAuth 账户管理流程隔离。 + +登录后从账号实时模型目录选择 `workbuddy-ai/`。模型发现不可用时可使用 +`workbuddy-ai/default-model`。支持流式输出、工具调用和非流式客户端;概览和账户页显示剩余积分、已用/总量、基础及赠送积分, +并在可查询时显示下一批非每日积分的到期时间。刷新读取当前账户的账单汇总;查询失败不会 +显示为零。积分仅用于展示,不会因付费积分耗尽而禁用仍可用的免费模型。 + +这是参考 [Magpie WorkBuddy 插件](https://github.com/magpie-community/plugins/tree/e7f65641df820dfd715c0d2241f99f2092e4efb1/packages/workbuddy) +实现的非官方订阅协议。上游的客户端通道或账号限制会原样拒绝请求,不会改写代理身份提示词 +绕过限制。实际可用性取决于账号、地区、模型权限和上游 API。本功能独立于 CodeBuddy CLI +API Key 提供商,也不负责让 WorkBuddy 桌面客户端使用 OpenCodex 模型。 + +### DeepSeek Account (DSH) + +`dsh-account` 是一个实验性的、优先导入已有凭据的账户提供商,用于复用已安装并登录的 DeepSeek Harness (DSH) Desktop 应用程序的认证凭据。 + +- **独立于 DeepSeek API Key**:`dsh-account` 使用用户的 DeepSeek 认证会话配额(普通/赠送钱包余额与总消费),而 `deepseek` 提供商按平台的 API Key 余额扣费。这两个凭据体系和路由严格隔离,互不回退。 +- **仅显式导入**:OpenCodeX 会检测是否已安装 DeepSeek Harness 且已登录(`~/.dsh/.credentials.yaml`),但绝不会在没有用户明确操作(`ocx login dsh-account` 或在 Web 仪表盘点击导入)的情况下自动复制、导入或使用该令牌。 +- **对 DSH 凭据库只读**:OpenCodeX 将 `~/.dsh/.credentials.yaml` 视为严格只读。OpenCodeX 绝不写入、修改、删除或更改任何属于 DeepSeek Harness 的文件权限。 +- **隔离的凭据副本与登出**:导入后,OpenCodeX 会通过 DeepSeek 权威身份端点(`/auth-api/v0/users/current`)校验令牌,并在 OpenCodeX 认证库中保存独立副本。在 OpenCodeX 中登出或删除该账户仅会删除 OpenCodeX 本地副本,绝不会触发上游远程会话登出,也不会退出 DeepSeek Harness Desktop。 +- **无后台令牌自动刷新与 401 重新导入**:DSH 会话授权不支持自动后台刷新端点。若上游返回 HTTP 401(`ACCOUNT_TOKEN_INVALID`),OpenCodeX 将该账户标记为 `needsReauth` 并停止路由。要恢复访问,用户必须通过 `ocx login dsh-account` 显式重新导入。 +- **第三方免责声明**:OpenCodeX 不主张 DeepSeek 官方对于第三方客户端复用会话授权提供背书或支持。 + ## 3. API 密钥目录 -opencodex 内置 104 个预设:85 个密钥预设、15 个 OAuth 预设、3 个本地预设,以及 1 个默认的 +opencodex 内置 106 个预设:85 个密钥预设、17 个 OAuth 预设、3 个本地预设,以及 1 个默认的 ChatGPT 转发预设。仪表盘的 **Add provider** 选择器会打开密钥提供商的控制台,验证并保存密钥。 验证因提供商而异。主要条目包括: @@ -241,7 +273,7 @@ Cline IDE/CLI 中提供,不能通过 API 使用;`minimax/minimax-m2.5` 是 | Z.AI (GLM Coding) | `https://api.z.ai/api/coding/paas/v4` | | 智谱 AI (BigModel) | `https://open.bigmodel.cn/api/paas/v4` | | [BigModel Coding Plan — Responses (静态模型列表)](/guides/providers/#bigmodel-coding-plan-over-responses) | `https://open.bigmodel.cn/api/v1` | -| Qwen Cloud | Token plan(默认): `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` · 按量付费: `https://dashscope.aliyuncs.com/compatible-mode/v1` · 或自定义 | +| Qwen Cloud | Token plan(默认): `https://token-plan.maas.qwencloudapi.com/compatible-mode/v1` · 按量付费: `https://dashscope.aliyuncs.com/compatible-mode/v1` · 或自定义 | | 腾讯云 Coding Plan | `https://api.lkeap.cloud.tencent.com/coding/v3` | | SiliconFlow | `https://api.siliconflow.cn/v1` | | 火山方舟 · Coding Plan · Agent Plan | `https://ark.cn-beijing.volces.com/api/v3` · `https://ark.cn-beijing.volces.com/api/coding/v3` · `https://ark.cn-beijing.volces.com/api/plan/v3` | @@ -261,16 +293,23 @@ Sionic 提供的 `deepseek/deepseek-v4.1-flash-ultrafast` 和 [OpenGateway 控制台](https://opengateway.ai/api-keys)创建密钥,再运行 `ocx provider add opengateway` 或在控制台选择 **OpenGateway**。Chat 请求使用配置的 Bearer 密钥;公开模型列表不能验证密钥有效性。 +在 Responses 协议中,OpenGateway 拒绝重放原生自定义工具调用,因此预设会在发送前将代码模式的 `exec` 等 Codex 自定义工具转换为函数工具。 +工具结果内容数组以文本发送,图像保留在配对的工具结果组之后。不支持的结果部分(包括拒绝和不透明内容)会在发送前以明确的客户端输入错误拒绝。 +图像细节 `original` 映射为网关接受的 `high`;图像字节保持不变。 +原生 Responses 路由还会在将纯文本空补全作为成功返回之前进行检查。 +输出预算耗尽时返回带 `max_output_tokens` 的 `incomplete`;代理不会增加请求的预算。仍有预算且可安全重放的空补全,可在现有发送上限内重试一次。显式设置 `emptyCompletionRetry: false` 或 `OCX_EMPTY_COMPLETION_RETRY=0` 可关闭该重试。出现文本、拒绝、工具活动或未知流事件时不会重放,并保留原始流。此保护不改变其他提供商及其重试默认值。 +重放要求显式 `store: false`、完整输入且仅含客户端执行的工具;声明了 `web_search` 等托管工具时,即使未观察到工具事件也不会自动重放。 MiniMax 和 MiniMax (CN) 的提供商卡片也会在配置的密钥有有效 Coding Plan 时显示用量。 仪表盘读取 5 小时窗口,以及套餐提供时的每周窗口;这些仅用于展示,不会改变模型路由。 -**OpenCode Zen**(`opencode-zen`)与免密钥的 **OpenCode Free** 预设共用 -`https://opencode.ai/zen/v1`。该网关上的免费模型常会触发约每分钟 15–20 次请求的短窗口限流(社区观测;OpenCode 未公布 RPM)。Zen 可能返回不带 `Retry-After` / `X-RateLimit-*` 的通用 429。这与免密钥桌面配额(`opencode-free` 上约每 5 小时 200 次 Big Pickle/免费模型请求)是分开的。当这类 429 省略 `Retry-After` 时,opencodex 会在客户端错误中补充说明并附带合成的 `Retry-After`;若上游已提供 `Retry-After`,则仍以它为准。同密钥等待重试仍可通过 [`retryOn429`](/zh-cn/reference/configuration/) 选择开启。 +**OpenCode Zen**(`opencode-zen`)使用 [opencode.ai/auth](https://opencode.ai/auth) 签发的 API 密钥访问 `https://opencode.ai/zen/v1`。免费模型可能触发每分钟约 15–20 次请求的短窗口限流(社区观测;OpenCode 未公布 RPM)。限流 429 缺少 `Retry-After` 时,opencodex 会添加说明和合成的 `Retry-After`;有效的上游头优先。同密钥重试仍须通过 [`retryOn429`](/zh-cn/reference/configuration/) 启用。 + +在规范 `opencode-zen` 提供商中,使用 API 密钥认证和官方路由 `https://opencode.ai/zen/v1` 或 `https://opencode.ai/inference/v1` 时,Gemini 会在默认 `googleMode: ai-studio` 下自动使用 Google 原生 API。Zen 模型 ID 保持不变,包括没有 `-tiered` 后缀的 Flash ID。Claude 使用 Anthropic Messages,不受 `googleMode` 影响。加载已保存的配置时,仅忽略这些原生模型符合条件的旧 Chat/Responses 覆盖设置,以及符合条件的 Responses 专用 `image_generation` 托管工具偏好。自定义提供商、非密钥认证路由及其他偏好保持不变。 -**免密钥的 `opencode-free` 层级目前对第三方客户端关闭。** Zen 会拒绝任何不带 `x-opencode-session` 头的请求,返回错误类型 `MissingSessionID` 和消息 "OpenCode's free tier can only be used in OpenCode"。这道关卡只检查该头是否存在,因此代理完全可以编一个值蒙混过去,但 opencodex 不这么做。伪造会话标识并附上带版本号的 `opencode/` User-Agent,等于声称自己就是 OpenCode 客户端,而 OpenCode 并未公布这一免密钥层级的第三方集成约定;用这种方式换来的 HTTP 200 是绕过了准入检查,而不是获得了许可。因此 opencodex 选择如实报告限制:发往 `opencode-free` 的请求会返回一条解释上游关卡的错误。 +**已移除 `opencode-free` 支持。** OpenCode 的上游免费层级门槛导致所有模型均返回 `403 permission_denied`([#7255](https://github.com/lidge-jun/opencodex/issues/7255))。加载现有配置时,会在内存中禁用相关条目和依赖的模型引用,每个进程仅警告一次,不修改文件。如果它原来是默认提供商,将改用标准 OpenAI OAuth 路径作为默认值。请使用 `opencode-go`、需要密钥的 `opencode-zen` 或其他提供商。[OpenCode Zen 条款](https://opencode.ai/docs/zen/)。 -通往同一批模型的受支持路径,是使用 [opencode.ai/auth](https://opencode.ai/auth) 获取的 OpenCode Zen API 密钥、走带密钥的 **`opencode-zen`** 预设。若 OpenCode 之后公布了免密钥层级的第三方接入方式,opencodex 可以跟进;在此之前,这个预设的作用是记录该限制。上游条款:[opencode.ai/docs/zen](https://opencode.ai/docs/zen/)。 +保留正常的 Claude Desktop 分配和默认值。源模型允许列表变空的规则会被禁用;注入模型被移除时,原生子代理同步也会被禁用。后续保存配置时不会恢复已移除的引用,并会保留正常的并发编辑。 大多数使用带 bearer 密钥的 `openai-chat` adapter;**Xiaomi MiMo**(`xiaomi`)等 Anthropic 兼容预设 使用 `anthropic` adapter(`x-api-key`)。小米还提供 OpenAI Chat 预设 `xiaomi-mimo` 和 token plan 预设 @@ -464,6 +503,9 @@ GPT-5.6 Sol/Terra/Luna 会预置在提供商的回退列表中,因此即使实 | Codex 登录(Pool 或 Direct) | `gpt-5.6-*` | 922,000 | | OpenAI (API key) | `openai-apikey/gpt-5.6-*` 和 `*-pro` | 922,000(max input 922,000) | | OpenRouter | `openrouter/openai/gpt-5.6-sol`、`openrouter/openai/gpt-5.6-terra`、`openrouter/openai/gpt-5.6-luna` | 922,000 | +| Codex | `gpt-6.1-terra` | 500,000 / 872,000 | +| OpenAI API | `openai-apikey/gpt-6.1-terra` | 1,050,000 (128,000 max output) | +| OpenRouter | `openrouter/openai/gpt-6.1-terra` | 1,050,000 | | Cursor | `cursor/gpt-5.6-sol`、`cursor/gpt-5.6-terra`、`cursor/gpt-5.6-luna` | 1,000,000 | 原生 GPT-5.6 条目保留固定的上游 reasoning 档位,例如 Luna 有 `max`,但没有 `ultra`。路由条目 @@ -496,7 +538,7 @@ GitHub 可能改变协议或账户权限。Auto 每次请求使用选中模型 不是已公布的 GitHub 限制;过大的目录、缓慢的协商或意图文本截断都可能影响兼容性。 具名选择时,Copilot 提供混合 wire 目录:其模型(`gpt-5.3-codex`、`gpt-5.4`、 -`gpt-5.4-mini`、`gpt-5.5`、`gpt-5.6-luna`、`gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`)会拒绝面向 +`gpt-5.4-mini`、`gpt-5.5`、`gpt-5.6-luna`、`gpt-5.6-sol`、`gpt-5.6-terra`、 `gpt-6.1-sol`、 `gpt-6.1-terra`、 `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`)会拒绝面向 agent 流量的 `/chat/completions`,因此 opencodex 默认将这些模型路由到 Responses API,而其他 Copilot 模型仍走 chat completions。优先级为:硬 wire 固定 → 显式 [`modelAdapters`](/zh-cn/reference/configuration/providers/) 条目 → 注册表默认值 → 提供商级 diff --git a/docs-site/src/content/docs/zh-cn/guides/remote-link.md b/docs-site/src/content/docs/zh-cn/guides/remote-link.md index dffb61b85e2..37c89eef023 100644 --- a/docs-site/src/content/docs/zh-cn/guides/remote-link.md +++ b/docs-site/src/content/docs/zh-cn/guides/remote-link.md @@ -55,7 +55,7 @@ Host devbox - **Connected** 表示 SSH 隧道已就绪,子机可以使用主机链接。 - **Reconnecting** 表示正在重试隧道。重试期间请求可能暂时返回带有 `Retry-After` 的 `503`。在从自己的仪表板连接的 Child 上,请求会先最多等待 15 秒让隧道恢复。 -- **Failed** 表示链接需要处理。请检查 SSH 身份验证、已确认的主机密钥、转发或超时原因。建立 SSH 隧道的一方会在睡眠、故障或重启后自动重试(Home 添加的 Child 由 Home 重试,从自己的仪表板连接的 Child 由 Child 重试):超时或转发错误后大约每分钟一次,身份验证错误后每五分钟一次。主机密钥变更时不会重试。要立即重连,请在 Home 的仪表板中选择 **Retry**,或在 Home 上运行 `ocx link reconnect --link-id `。这只会重启该链接的隧道,不会重启 OpenCodex。 +- **Failed** 表示链接需要处理。请检查 SSH 身份验证、已确认的主机密钥、转发或超时原因。建立 SSH 隧道的一方会在睡眠、故障或重启后自动重试(Home 添加的 Child 由 Home 重试,从自己的仪表板连接的 Child 由 Child 重试):超时或转发错误后先在一分钟后重试,之后每次重试失败等待时间翻倍,最长五分钟一次;身份验证错误后每五分钟一次。主机密钥变更时不会重试。要立即重连,请在 Home 的仪表板中选择 **Retry**,或在 Home 上运行 `ocx link reconnect --link-id `。这只会重启该链接的隧道,不会重启 OpenCodex。 链接失败时不会静默切换到本地提供商。 diff --git a/docs-site/src/content/docs/zh-cn/guides/sidecars.md b/docs-site/src/content/docs/zh-cn/guides/sidecars.md index 15605d2dcf9..b959040db09 100644 --- a/docs-site/src/content/docs/zh-cn/guides/sidecars.md +++ b/docs-site/src/content/docs/zh-cn/guides/sidecars.md @@ -66,8 +66,10 @@ OAuth 账户时使用 `anthropic`,否则使用 `openai`。显式选择 `anthro 当路由模型列在其 provider 的 `noVisionModels` 中,或该模型在 `modelInputModalities` 中被声明为仅文本, 并且请求包含图像时,只要有可用的 vision sidecar plan,opencodex 就会在主调用**之前**描述每张图像并用文字替换图像。 -如果没有可用 plan,原始图像会被移除,而不会继续转发给纯文本后端。模型目录会为每个由 sidecar 覆盖的模型声明图像输入。 -只有当每个 combo 成员都能原生或通过 sidecar 接受图像、且 combo 的 `imageInput` 设置未禁用时,combo 才会声明图像输入; +如果没有可用 plan,原始图像会被移除,而不会继续转发给纯文本后端。当 `visionSidecar.enabled: false` 时, +模型目录不会为纯文本模型添加由 sidecar 提供的图像输入,包括自定义模型和 combo 成员。原生图像支持和显式配置的 +图像输入会保留。省略 `enabled` 时,默认仍会声明 sidecar 图像支持;目录中的声明不保证 sidecar 凭据或容量可用。 +只有当每个 combo 成员都能原生或通过已启用的 sidecar 接受图像、且 combo 的 `imageInput` 设置未禁用时,combo 才会声明图像输入; 这样 Codex 应用等客户端会允许附件,而不会在 sidecar 运行前阻止它们。当 `visionSidecar.model` 缺失或为空时,OpenAI 执行路径、 Dashboard 和管理 API 都使用 `gpt-5.6-luna` 作为回退。启动时仍会把明确保存的旧 `gpt-5.4-mini` 值迁移到 `gpt-5.6-luna`;该迁移只作用于已保存值,不适用于缺失的 model 字段。 @@ -126,6 +128,10 @@ Dashboard 和管理 API 都使用 `gpt-5.6-luna` 作为回退。启动时仍会 `PUT /api/sidecar-settings` 接受相同字段。部分更新会保留未提交的键。`timeoutMs` 使用运行时整数边界(1–2147483647 毫秒)。 +Vision 的实际启用状态改变时,保存后也会刷新受管理的 Codex 目录和模型缓存。 +若刷新未完成,响应会返回 `catalogRefreshPending: true`,已保存的开关设置仍然保留。 +目录发布成功并不表示运行中的客户端已重新加载目录。 + Web 搜索 sidecar 卡片采用相同的控件形态:模型选择器的第一行是 **关闭 (Off)**。关闭会停止 OpenCodex 对 `web_search` 的拦截,同时 Codex 集成会把 `web_search = "disabled"` 写入 `~/.codex/config.toml`;因为 Codex 在自身模式如此声明前会一直声明其原生托管的 diff --git a/docs-site/src/content/docs/zh-cn/guides/sub-agent-surface.md b/docs-site/src/content/docs/zh-cn/guides/sub-agent-surface.md index 390ad72d4a9..41634687f33 100644 --- a/docs-site/src/content/docs/zh-cn/guides/sub-agent-surface.md +++ b/docs-site/src/content/docs/zh-cn/guides/sub-agent-surface.md @@ -33,6 +33,42 @@ opencodex 会把这一点作为最后一步同时应用到实时的 `/v1/models` 对于 v2 roster,资格有三种状态:标记为 `"v2"` 的条目、显式设为 `null` 的条目,或者没有 `multi_agent_version` 字段的条目。真正的 `"v1"` 固定值会被排除,因为它说明该模型属于另一种协作界面。 +## `spawn_agent` 列出哪些模型 + +Codex 在 `spawn_agent` 工具描述中最多列出五个模型,并由 Codex 自己挑选:它把选择器中可见的目录行按 `priority` 升序排序,取前五个。**Subagents** 页面上的 roster(`subagentModels`)只能通过按 roster 顺序为其中的模型分配最低的 priority(没有账号选择器时为 `0` 到 `4`)来影响这份列表,不会把列表限制在 roster 之内。 + +如果只设置一到四个模型,Codex 会按 priority 用接下来的行填满剩余位置。这些行不是你选的,仪表板也不会显示是哪些模型。具体是哪些行取决于目录: + +| 目录 | 填补剩余位置的行 | +| --- | --- | +| 没有 Codex 账号选择器 | 其他路由的 `provider/model` 行。它们的 priority 都是 `5`,因此按 provider 名称、再按模型 id 的字母顺序排列。未设置的原生 GPT 行的 priority 不低于 `100` 加上已设置 id 的数量,因此排在后面。 | +| 有 Codex 账号选择器 | 账号限定的原生行(`/`),紧跟在已设置的行之后。未设置的路由行移到 priority `1005`,排在它们之后。 | + +例如,没有账号选择器且 roster 如下时: + +```json +{ "subagentModels": ["xai/grok-4.5", "anthropic/claude-sonnet-5"] } +``` + +| 位置 | 模型 | Priority | 原因 | +| ---: | --- | ---: | --- | +| 1 | `xai/grok-4.5` | `0` | roster 第一项 | +| 2 | `anthropic/claude-sonnet-5` | `1` | roster 第二项 | +| 3 | `deepseek/deepseek-v4-flash` | `5` | 按字母顺序第一个剩余的路由行 | +| 4 | `google/gemini-3-pro` | `5` | 下一个路由行 | +| 5 | `moonshot/kimi-k3` | `5` | 下一个路由行 | +| 6+ | `zai/glm-5.2`,然后是原生 GPT 行 | `5`,然后是 `102` | 不在前五之内 | + +roster 为空时,opencodex 不固定任何行,前五个按目录原有的 priority 决定。 + +被填补的位置和普通列出的模型一样,主代理可能会选用它们。这份列表也不是允许列表:主代理写出确切名称时,Codex 也接受前五之外的目录模型。要控制 Codex 列出的模型: + +- 设置五个模型。roster 满了就没有可填补的位置。 +- 对于永远不想被提供的模型,将其禁用,或不放进其 provider 的 `selectedModels`。被禁用或未选中的模型会离开选择器,不会填补位置。 +- `modelPickerOrder` 会改变 Codex 排序所用的 priority。只含路由行的列表会把列出的未设置行移到 `1000` 及以上,因此未列出的路由行会先填补位置。包含裸原生 id 的列表会对整个选择器排序(包括已设置的行):列出的行排在前面,先于任何未列出的行占据列出位置。参见 [Model ordering](/guides/model-ordering/)。 + +在 v2 上,Codex 会在取前五个之前跳过固定为 `"disabled"` 的行;其余机制相同。opencodex 的 v2 指引 roster(`{{roster}}`)只列出位于前五之内的已配置 roster 模型,因此填补的模型只出现在 Codex 的工具描述中,不会出现在 opencodex 指引里。Codex 在会话开始时读取目录,因此 roster 的更改从新会话开始生效。 + ## 委派模型与推理强度 Dashboard 上的 **Sub-agent delegation** 控件管理三个相关设置: @@ -161,6 +197,10 @@ curl -X PUT http://localhost:10100/api/injection-model \ 它可能在 picker 中被隐藏、超出了五个模型的显示上限、从目录中缺失,或者被固定到 v1。`"v2"`、`null` 或缺失的界面值都可以;真正的 `"v1"` 固定值不可以。 +### 为什么 `spawn_agent` 列出了我没有设置的模型? + +Codex 按 priority 从目录中填充最多五个列出位置。设置的模型少于五个时,接下来的行会占据剩余位置。参见上文“`spawn_agent` 列出哪些模型”一节。 + ### 模式更改会影响正在运行的会话吗? 不会。更改模式后请启动一个新的 Codex 会话。如果长时间运行的 App host 仍然显示旧的目录状态,请运行 `ocx sync` 并重启那个 Codex 界面。 diff --git a/docs-site/src/content/docs/zh-cn/guides/subagent-v1-default.md b/docs-site/src/content/docs/zh-cn/guides/subagent-v1-default.md index bbe261970d2..58fa0890419 100644 --- a/docs-site/src/content/docs/zh-cn/guides/subagent-v1-default.md +++ b/docs-site/src/content/docs/zh-cn/guides/subagent-v1-default.md @@ -94,3 +94,9 @@ ocx v2 mode v1 ``` CLI 不会提示。这是同一设置,因此请了解本页描述的行为后再选择。 + +## 子代理并发上限 + +在 **Models → Model settings** 中,子代理上限控件可设置同时运行的子代理数量上限,不包括主代理。v1、base 和 v2 均可使用,包括全局 v2 标志关闭的混合模式。界面模式与此上限分别设置。 + +提高上限可允许更多子代理,但不会自动创建子代理,也不保证执行更快。更改后请启动新的 Codex 会话。如果长时间运行的 App 主机仍使用旧上限,请重新启动该主机。 diff --git a/docs-site/src/content/docs/zh-cn/guides/web-dashboard.md b/docs-site/src/content/docs/zh-cn/guides/web-dashboard.md index 044fd71f388..81909ec088b 100644 --- a/docs-site/src/content/docs/zh-cn/guides/web-dashboard.md +++ b/docs-site/src/content/docs/zh-cn/guides/web-dashboard.md @@ -43,6 +43,7 @@ bun run dev:gui | 区域 | 作用 | | --- | --- | | **Dashboard 摘要** | 显示 multi-agent 模式、在线状态、版本、运行时间、provider 数量、30 天 token 总量、活动 provider 和可用的原生/路由模型。 | +| **原生推理密文保留** | 独立开启原生模型/账号切换时的推理密文转发;默认均关闭,跨账号保留属于实验性选项。见 [配置范围](/zh-cn/reference/configuration/#原生推理密文保留)。 | | **Sub-agent delegation** | 选择供 OpenCodex 委派指引与可选的 Codex 原生子代理默认值共用的原生/路由模型和可选 reasoning 强度。它不是逐次生成的路由器,详见下文。 | | **Sidecar** | 选择 web-search 模型及强度,以及图像描述模型;更改从下一次请求开始生效。 | | **Maintenance** | 重新同步 Codex 模型目录,查看项目级配置绕过警告,检查 latest/preview 版本,并可在更新后重启代理。 在桌面 shell 中,该更新入口会打开原生应用更新页面,而不是运行包更新器。 | diff --git a/docs-site/src/content/docs/zh-cn/reference/cli.md b/docs-site/src/content/docs/zh-cn/reference/cli.md index 87731f641e7..c6e89a3b4fa 100644 --- a/docs-site/src/content/docs/zh-cn/reference/cli.md +++ b/docs-site/src/content/docs/zh-cn/reference/cli.md @@ -17,7 +17,7 @@ opencodex 的 CLI 是 `ocx`。它会根据第一个命令名进行分发;文 管理命令会通过实时代理的管理 API 往返调用,使用记录下来的运行时端口和身份检查,而不是维护第二条配置路径。已停止或不可达的代理会被表示为 HTTP 503,并导致 CLI 以非零状态退出。明确标注为离线配置操作的命令,则可以在没有实时代理的情况下验证并编辑配置文件。 -`ocx system codex-cli-update check` 不需要实时代理,也不会向软件包注册表发起请求。它只会在限定范围内检查已配置候选项的来源元数据,包括经过脱敏的可执行文件位置和所有权证据。受信任的已发布启动器上下文只能验证该候选项快照,并不证明 Codex 已成功运行。由于这条一次性检查命令绝不会运行 Codex,来自环境变量和持久化记录的候选项仅用于报告(`managed: false`,通常为 `selection_unattested`);JSON 输出包含 `candidateAvailable`、`candidateVersion` 和 `candidateSource`,且 `selectionAttested` 始终为 `false`。检查已配置候选项需要受信任的已发布启动器上下文;直接使用 Bun 启动或从源码运行时没有这项证明,因此会忽略环境变量和持久化记录中的候选项状态,并可能报告 POSIX 下的 `candidate_unavailable` 或 Windows 下的 `windows_inspection_deferred`。在 Windows 上,这个首个切片不会对候选路径或配置路径执行任何文件系统 I/O。只有由受信任启动器捕获的绝对环境候选项可以获得应用捆绑或版本管理器的纯词法标签;其他所有 Windows 候选项都会以失败关闭方式处理。该命令不会安装或修复软件,不会运行 Codex 或 npm,不会控制正在运行的进程,也不会写入配置或缓存状态。 +`check` 不查询注册表,也不运行 Codex。在 POSIX 上,当已发布启动器捕获的环境候选项与运行时解析器选择的规范路径一致时,`selectionAttested` 可以为 `true`;如果关联的 npm-global 软件包清单证明了所有权,`managed` 可以为 `true`。持久化候选项仍不获认证。Windows 不读取候选项或配置文件,而是推迟检查。直接通过 Bun 或源码运行时没有启动器证明。`plan` 使用隔离的临时 npm 配置、缓存和日志查询官方注册表,并拒绝活动会话或不完整的进程观测。它不安装软件,也不向进程发送信号。请参阅[完整命令和拒绝原因(英文)](/reference/cli/agents/#read-only-codex-cli-update-plan)。 有关 Windows x64 安装观察,请参阅 [`attest` 命令](/zh-cn/reference/cli/agents/)。未提供显式路径时,该命令观察由受证明约束的启动器快照识别出的已选候选项;它不授予更新权限,也不证明运行时选择。 diff --git a/docs-site/src/content/docs/zh-cn/reference/cli/agents.md b/docs-site/src/content/docs/zh-cn/reference/cli/agents.md index d2c231de196..f3b95ff0f58 100644 --- a/docs-site/src/content/docs/zh-cn/reference/cli/agents.md +++ b/docs-site/src/content/docs/zh-cn/reference/cli/agents.md @@ -208,7 +208,9 @@ opencode 会插值 `{env:OPENCODEX_OPENCODE_API_KEY}`。opencodex 生成的 Pi 管理无头运行时设置、启动、同步、诊断和更新。 -`ocx system codex-restart --yes` 通过与 `ocx sync --restart-codex` 相同的模块重启 Codex app-server,并完全退出再重新启动 Codex 桌面应用。若代理本身运行在 Codex 应用内部,该命令会给出可执行提示并拒绝,而不是承诺无法完成的移交。 +`ocx system codex-restart --yes` 通过与 `ocx sync --restart-codex` 相同的模块重启 Codex app-server。模型选择器在应用启动时构建,因此当桌面应用在当前模型目录写入之前启动,或本次请求更改了 `model_catalog_json` 绑定时,会完全退出再重新启动它。否则应用保持打开,自行重启其 app-server,并保留未发送的草稿和待处理的审批。若无法读取应用或目录状态,则执行完整重启。若代理本身运行在 Codex 应用内部,桌面重启会给出可执行提示并拒绝,而不是承诺无法完成的移交。 + +如果本次请求更改了 `model_catalog_json` 绑定,即使桌面进程启动于目录文件写入之后,也会触发桌面重启。 ```bash ocx system settings --stream-mode eager-relay @@ -220,7 +222,7 @@ ocx system settings --stream-mode eager-relay ocx system codex-cli-update check --json ``` -`check` 不会向软件包注册表发起请求,只会在限定范围内检查已配置候选项的来源证据,包括经过脱敏的可执行文件位置和所有权证据。受信任的已发布启动器上下文只能验证该候选项快照,并不证明 Codex 已成功运行。由于这条一次性命令绝不会运行 Codex,来自环境变量和持久化记录的候选项仅用于报告(`managed: false`,通常为 `selection_unattested`);JSON 输出包含 `candidateAvailable`、`candidateVersion` 和 `candidateSource`,且 `selectionAttested` 始终为 `false`。检查已配置候选项需要受信任的已发布启动器上下文;直接使用 Bun 启动或从源码运行时没有这项证明,因此会忽略环境变量和持久化记录中的候选项状态,并可能在 POSIX 系统上报告 `candidate_unavailable`。在 Windows 上,这个首个切片不会对候选路径或配置路径执行任何文件系统 I/O。只有由受信任启动器捕获的绝对环境候选项可以获得应用捆绑或版本管理器的纯词法标签;其他所有 Windows 候选项都会以失败关闭方式处理。由于这个切片完全不读取持久化的选择状态,在未捕获任何环境候选项的 Windows 运行中会报告 `windows_inspection_deferred` 而非 `candidate_unavailable`:该命令无法观测 Codex CLI 是否已安装,因此报告检查被推迟,而不是断言不存在候选项。该命令不会运行 Codex 或软件包管理器,不会修复 shim,不会写入配置或缓存,不会停止进程,也不会安装任何内容。随应用捆绑的候选项、位于已识别版本管理器路径中的候选项、未经验证的独立候选项以及 shim 状态不明确的候选项,都会报告为 `unmanaged` 或 `unknown`,绝不会归类为 `managed`。 +`check` 不查询注册表,也不运行 Codex。在 POSIX 上,当已发布启动器捕获的环境候选项与运行时解析器选择的规范路径一致时,`selectionAttested` 可以为 `true`;如果关联的 npm-global 软件包清单证明了所有权,`managed` 可以为 `true`。持久化候选项仍不获认证。Windows 不读取候选项或配置文件,而是推迟检查。直接通过 Bun 或源码运行时没有启动器证明。`plan` 使用隔离的临时 npm 配置、缓存和日志查询官方注册表,并拒绝活动会话或不完整的进程观测。它不安装软件,也不向进程发送信号。请参阅[完整命令和拒绝原因(英文)](/reference/cli/agents/#read-only-codex-cli-update-plan)。 在 Windows 上,如果捕获到 `CODEX_CLI_PATH=codex` 这样的裸命令、远程路径或设备路径,则报告 `candidate_path_unavailable`。这些情况下候选项已被捕获,但其路径不适用于此检查。 diff --git a/docs-site/src/content/docs/zh-cn/reference/cli/lifecycle.md b/docs-site/src/content/docs/zh-cn/reference/cli/lifecycle.md index c13b8aa9797..9f790a0f0d4 100644 --- a/docs-site/src/content/docs/zh-cn/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/zh-cn/reference/cli/lifecycle.md @@ -28,7 +28,13 @@ ocx start --socks5-off ### `ocx stop` -停止正在运行的代理(按 PID),移除 PID 文件,并恢复原生 Codex。如果安装了受管后台服务,`ocx stop` 还会先停止该服务,这样它就无法重新拉起代理。Web 仪表盘的 **Stop** 按钮在多数后端执行同样的操作(`POST /api/stop`),但 Windows 任务计划程序除外:任务结束后包装器仍可能重新拉起代理,只有运行在代理之外的 stop 才能在恢复客户端配置前确认这个重启窗口,因此仪表盘会以 `respawnable_service` 拒绝、不做任何更改,并提示改用 `ocx stop`。 +停止正在运行的代理(按 PID),移除 PID 文件;独立运行的所有者还会恢复原生 Codex。如果安装了受管后台服务,`ocx stop` 还会先停止该服务,这样它就无法重新拉起代理。Web 仪表盘的 **Stop** 按钮在多数后端执行同样的操作(`POST /api/stop`),但 Windows 任务计划程序除外:任务结束后包装器仍可能重新拉起代理,只有运行在代理之外的 stop 才能在恢复客户端配置前确认这个重启窗口,因此仪表盘会以 `respawnable_service` 拒绝、不做任何更改,并提示改用 `ocx stop`。 + +对于已连接的 hub 客户端或 Remote Link Child,`ocx stop`(包括成功的 API 停止)会保留连接所拥有的 Codex 路由、模型目录和 Grok 配置。`ocx start` 使用这些保留的配置恢复服务,不会重新注入路由。要让 Codex 回到自己的账户,请运行 `ocx disconnect`;若已恢复为原生路由,要重新应用连接路由,请运行 `ocx sync --restart-codex`。 + +若旧版普通代理无法确认支持保留配置,命令会在停止服务或进程前拒绝操作。请通过管理该运行时的应用或服务更新并重启它,然后重试。 + +对于已停止的客户端,命令会确认服务管理器未活动且已知本地端点没有运行时,然后返回正常的“未运行”结果。 ### `ocx restart` @@ -62,7 +68,7 @@ ocx eject back ### `ocx recover-history --ocx-compaction --yes` -在通过原生 Codex 恢复某个曾由路由提供方压缩的任务前,修复该任务的历史记录。此命令按 UUID 精确选择一个任务,先保存私有的逐字节备份,然后仅将 OpenCodeX 自有的 `ocx1:` 压缩状态转换为原生 Codex 可重放的普通摘要。原生加密内容和其他任务保持不变。运行前请关闭所选任务;如果 rollout 在处理期间发生变化,恢复会停止且不会替换原文件。 +在通过原生 Codex 恢复某个曾由路由提供方压缩的任务前,修复该任务的历史记录。此命令按 UUID 精确选择一个任务,先保存私有的逐字节备份,然后仅将 OpenCodeX 自有的 `ocx1:`/`ocx2:` 压缩状态转换为原生 Codex 可重放的普通摘要。原生加密内容和其他任务保持不变。运行前请关闭所选任务;如果 rollout 在处理期间发生变化,恢复会停止且不会替换原文件。 ### `ocx uninstall` · `ocx remove` @@ -136,7 +142,7 @@ ocx status --json 通过无需认证的 `GET /readyz` 端点检查同步后的就绪状态。就绪时返回 `200`;状态为 `pending` 或 终态 `failed` 时返回 `503`,并带有 `Retry-After: 1`。HTTP 仅返回经脱敏的身份字段 -`{service, version, uptime, pid, port, status, protocol, minimumClientProtocol, managementUrl}`。`protocol` 是 Hub 当前的远程协议版本,`minimumClientProtocol` 是兼容的最低客户端协议版本,`managementUrl` 是浏览器可见的规范管理 origin。不支持 `/readyz` 的旧代理会按 `unreachable` 失败关闭; +`{service, version, uptime, pid, port, status, protocol, minimumClientProtocol, managementUrl, features}`。`protocol` 是 Hub 当前的远程协议版本,`minimumClientProtocol` 是兼容的最低客户端协议版本,`managementUrl` 是浏览器可见的规范管理 origin。不支持 `/readyz` 的旧代理会按 `unreachable` 失败关闭; `/healthz` 是独立的存活检查,不是就绪检查。默认只探测一次;`--wait` 会轮询到就绪或超时,但遇到终态 `failed` 会立即退出。默认超时为 45 秒;`--timeout ` 必须与 `--wait` 一起使用,取值范围为 1–300 秒的正整数。CLI JSON 输出 `{ready, status, pid, port}`,其中 `status` 为 `ready`、`pending`、`failed` 或 @@ -152,6 +158,10 @@ ocx status --json ### `ocx sync [--restart-codex] [--restart-app-server-only]` +> **Remote Link Child:** `ocx sync` 还会应用 Home 的 Codex 设置;如果客户端运行时在该次应用之前启动,即使没有重启参数也会重启运行时。重启失败时,原有运行时继续提供服务,命令以非零状态退出。`ocx status --json` 和 `ocx link status --json` 中的相关字段见 [Remote Link apply parity](/reference/cli/lifecycle/#remote-link-apply-parity)(英文)。 + +显式指定重启标志后,即使目录和缓存的字节未变,成功提交 `config.toml` 更改也会触发重启。仅处理配置的操作若没有更改、被拒绝、仅作预检或写入已回滚,则不会触发重启。未指定重启标志时,sync 不会自动重启 Codex。 + 从每个已配置的提供方获取实时模型列表,并将合并后的目录重新注入 Codex。在添加提供方后运行,或用于刷新可用模型。 如果仍有长期运行的 Codex `app-server` 进程,`ocx sync` 会警告它们可能继续提供旧的内存模型列表,即使 `opencodex-catalog.json` / `models_cache.json` 已更新。传入 `--restart-codex` 会重启匹配的 `codex … app-server` 和 `codex-code-mode-host` 进程,并在 macOS、Linux 和 Windows 上完全退出再重新启动 Codex 桌面应用,让模型选择器重新读取目录。进行中的对话会结束。故意避免使用宽泛的 `pkill -f codex` 匹配。 @@ -162,6 +172,8 @@ ocx status --json 当命令在 Codex 应用内部运行时,重启会交给分离的 helper,当前会话会随应用一起结束。 +**指定来源的同步。** `ocx sync --expect-link `(Remote Link Child)和 `ocx sync --expect-hub-key `(hub 客户端)只有在本机仍连接到该链接或使用该密钥时才会同步。否则不做任何更改,也不同步本地提供方,命令以退出码 5 结束,stderr 最后一行为 `{"phase":"child_link_mismatch"|"hub_client_mismatch","reason":...}`。格式错误或重复的标志以退出码 2 结束。详情见[英文参考](/reference/cli/lifecycle/#sync-only-from-the-expected-home---expect-link-and---expect-hub-key)。 + ### `ocx sync-cache [--restart-codex] [--restart-app-server-only]` 使 Codex 的本地模型选择器缓存失效,让它根据当前激活的 opencodex 目录重新生成。与 `ocx sync` 相同的陈旧 `app-server` 警告和可选重启标志同样适用。 diff --git a/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md b/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md index 684040f94e8..d30ce4d9ea7 100644 --- a/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md @@ -5,6 +5,13 @@ description: 提供方配置、凭据、配额,以及模型目录命令。 这些命令用于配置上游提供方、认证账号、管理凭据池,并控制暴露给 Codex 的模型目录。 +使用剩余套餐额度可通过 `ocx account use-remaining openai main on` 开启,再用 +`ocx account use openai main` 选择主账号;`status --json` 查看状态,`off` 关闭。 +将 `main` 替换为唯一的池账号 ID 或别名即可控制该账号。开启后关闭该账号按用量提前切换; +主账号的短、长窗口硬锁阈值延后到 100%。开关跨重启保留,关闭后恢复原有阈值。 +暂停、重新认证、上游冷却和付费 credits 权限仍然生效;此开关不会开启付费额度。 +详见[英文说明](/reference/cli/providers-accounts/#use-remaining-included-quota)。 + ## 提供方 ### `ocx provider ` @@ -142,6 +149,12 @@ OAuth 账号会显示为 `Account N`,而 plan/label 列会在 plan、屏蔽后 ### `ocx account list [provider] [--json] [--all] [--quota [--refresh]]` +即使主机上没有原生 Codex 主目录,保存的 ChatGPT 池账号仍可列出,包括在仪表盘中和刷新账号时。 +查询列表不会创建 Codex 主目录,也不会退出原生主账号。禁用 Codex 客户端集成不会停止 +观测已有原生登录的配额;显式设置的 `CODEX_HOME` 路径无效时,仍会报告配置错误。 +如果 `OPENCODEX_HOME` 存储不安全,即使没有原生 Codex 主目录,也会报告错误。 +解析默认 Codex 主目录路径时,如果该路径是文件、目标不存在的符号链接,或父目录无法遍历,也会报告错误。 + 不指定提供方时,会列出 Codex 池、OAuth 账号和已配置的 API 密钥池。除非提供 `--all`,否则会跳过空的提供方。指定提供方时,只列出该凭据家族。人类可读输出 使用 `PROVIDER TYPE ID PLAN/LABEL PRIORITY STATUS`;手动选中的 Codex 行会标记为 `selected`。 diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration.md b/docs-site/src/content/docs/zh-cn/reference/configuration.md index 66327ddc41c..c8538ee8168 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration.md @@ -34,6 +34,25 @@ opencodex 会把持久化配置存放在 `$OPENCODEX_HOME/config.json`,通常 路由有自己独立的顺序化解析规则;见 [Routing](/reference/configuration/routing/)。 +## 原生推理密文保留 + +仪表盘概览提供“切换模型时保留”和“切换账号时保留”两个独立开关,默认均关闭。也可以在 `config.json` 中配置: + +```jsonc +{ + "nativeReasoningRetention": { + "modelSwitch": true, + "accountSwitch": false + } +} +``` + +上面的例子允许在同一原生 ChatGPT 目标和凭据下,发生已知模型切换时转发推理 `encrypted_content`。跨账号保留属于实验性选项;模型和账号同时变化时必须同时开启两个开关。策略按实际选中的原生 Combo 成员或账号池重试目标判断,不适用于第三方或 API-key provider。 + +允许转发不代表上游一定接受或复用推理。已检测到的解密拒绝仍执行现有的有限清理恢复,拒绝缓存优先于开关。旧响应和会话引用、外部推理项 ID、上传文件的账号限制及缓存隔离保持各自的归属规则。原生压缩块继续沿用现有清理行为,原生 `/v1/responses/compact` 的直接转发清理也不改变。 + +仪表盘和 `PUT /api/native-reasoning-retention` 的修改对后续请求生效。PUT 接受部分布尔字段,JSON `null` 恢复默认值;未知字段或类型错误会被拒绝。使用本地 `ocx config set/unset` 修改后需重启代理。完整说明见 [Native reasoning retention](/reference/configuration/server/#native-reasoning-retention)。 + ## 配置域 - [Providers](/reference/configuration/providers/) — provider 条目、认证、端点、目录、allowlist、上下文限制、配额和 provider 特定选项。 diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration/advisor.md b/docs-site/src/content/docs/zh-cn/reference/configuration/advisor.md index 3ef528c6135..33d2ef714ab 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration/advisor.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration/advisor.md @@ -1,6 +1,6 @@ --- title: 顾问 -description: OpenCodex 自有的专家咨询 sidecar — 配置的专家模型为路由 Worker 提供建议,支持 manual 与 preflight 两种策略。 +description: 独立专家咨询,支持 manual、preflight 与基于证据的 adaptive 策略。 --- 顾问是一个独立的专家模型,审阅 Worker 的任务并返回建议。OpenCodex 端到端地拥有整个咨询过程:代理向 Worker 的回合注入合成的 `advisor` 工具,自己通过正常路由权威执行咨询,并回注建议使原 Worker 继续。Worker 无需委托、无需 spawn 任何东西、也不携带 provider 凭据。 @@ -9,6 +9,8 @@ description: OpenCodex 自有的专家咨询 sidecar — 配置的专家模型 ## 配置 +在仪表板 **Advisor** 页面从目录选择专家模型。选择器原样保存原生、提供商、指定账户、自定义和组合模型的标识符。已禁用和等待初始选择的模型不会作为新选项。已保存但不在可选目录中的标识符会保留并显示警告,直到您选择其他模型或清除。目录加载失败时保留当前选择并提供重试。请选择能返回文本建议的模型;目录不保证咨询兼容性。CLI 和 API 仍接受路由器支持的模型字符串。 + ```json { "advisor": { @@ -26,11 +28,12 @@ description: OpenCodex 自有的专家咨询 sidecar — 配置的专家模型 | `enabled?` | `boolean` | `false` | 总开关。关闭时请求路径上没有任何 advisor 行为。 | | `model?` | `string` | — | 专家模型。任何路由权威接受的模型字符串:裸原生模型(`gpt-6-astra`)、显式 `provider/model`(`anthropic/claude-sonnet-4-6`、`xai/grok-...`)或账户限定的原生模型。完整支持跨 provider:Worker 与 Advisor 无需同属一个 provider。 | | `effort?` | `string` | `"max"` | Advisor 调用的推理强度(`low` 至 `ultra`)。 | -| `policy?` | `"manual" \| "preflight"` | `"manual"` | 何时咨询顾问。 | +| `policy?` | `"manual" \| "preflight" \| "adaptive"` | `"manual"` | 何时咨询顾问。 | +| `maxConsultationsPerTask?` | `integer` | `3` | 自适应任务咨询上限(1–3 次尝试),包含手动调用。 | | `timeoutMs?` | `number` | `120000` | 回环咨询超时。 | | `contextSharingConsent?` | `"v1"` | 缺省 | 操作者同意把任务上下文发给所配置的顾问 provider。只有 `"v1"` 是当前版本。缺省、过期或其他值都表示不发送任务内容。`enabled: true` 本身不是同意。 | -通过仪表盘的 **Advisor** 页面或 `ocx advisor status|on|off|set --model --effort --policy ` 管理。 +通过仪表盘的 **Advisor** 页面或 `ocx advisor status|on|off|set --model --effort --policy ` 管理。 `ocx advisor on` 在没有当前同意时不会开启跨 provider 发送:它会打印披露并停止。`ocx advisor on --ack-context-sharing` 与 `ocx advisor consent` 记录 `v1`。`ocx advisor consent --revoke` 会移除同意并立即停止发送。`ocx advisor set` 不授予同意。仪表盘上的同意复选框默认不勾选。 @@ -78,5 +81,13 @@ Advisor 失败是 fail-open 的:已经发出的咨询若失败(模型不可 ## PR1 限制 - 原生 OpenAI passthrough 回合(ChatGPT 池 Worker)不会获得合成工具;advisor 支持覆盖路由(translated)provider。preflight 咨询适用于 run-turn 适配器;工具不适用。 -- 无自适应触发:没有卡住检测、重复失败分析、升级分层、多 Advisor 或投票。`manual` 与 `preflight` 是仅有的策略。 +- 不提供语义卡住检测、任务完成复核、升级分层、多专家或投票;自适应触发只使用已观察到的工具证据。 - preflight 去重账本是进程内的;代理重启后,进行中的任务可能再收到一次 preflight 咨询。 + +## 基于证据的自适应咨询与活动记录 + +`adaptive` 在初始咨询外,只有在同一工作目录中的同一验证命令失败、后续文件编辑成功、重新执行验证仍失败时才咨询。成功验证会清除对应失败;编辑前已启动的验证不算修复后的重试。初始阶段与失败阶段分别去重,自动咨询需要稳定的会话标识。工具须提供明确退出码和配对的调用/结果;支持常见测试、类型检查、lint、构建命令及已完成的文件编辑,轮询命令和单一静态 Code Mode helper 也可识别。复杂脚本、批量调用、shell 修改及模糊输出不触发。 + +用 `ocx advisor set --policy adaptive --max-consultations 2` 设置预算。自适应模式中,手动与自动尝试共用每任务 1–3 次预算,服务端在发出调用前写入 `OPENCODEX_HOME/advisor-budget.sqlite`,重启或切换 Worker 不会清零。仅保存任务摘要、次数和过期时间,首次调用后 24 小时过期;最多保留 512 项任务。预算不可用或耗尽时 Worker 继续执行。已发出的失败或取消调用仍可能计费,因此不退回额度。没有稳定会话标识时仅保留请求级的手动调用。 + +Advisor 页面和 `ocx advisor activity --json`(`GET /api/advisor/activity`)显示本进程最近 24 小时内最多 100 条调用元数据。页面可见时每 5 秒更新。包括原因、状态、模型、耗时、已报告的输入/输出 Token、请求档位与适配器映射档位;不存任务正文、命令、建议或会话标识,重启即清空。映射是适配器发出的参数,不代表已验证模型内部推理;未报告保持未知,不推测精确费用或建议采纳情况。 diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md index 4b738b35b26..bbe40d89bbc 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md @@ -11,6 +11,16 @@ description: 提供者条目、身份验证、端点、模型目录、配额、 该推断默认关闭,需要按渠道设置 `inferContextWindow: true` 开启。未开启时,缺少窗口的模型使用已启用的 Context cap,否则回退到 128k。 +## GPT-6.1 Terra + +[GPT-6.1 Terra](https://openai.com/index/introducing-gpt-6-1-terra/) 于 2026年10月26日发布,是 GPT-5.6 Terra 的后继型号。`gpt-6.1-terra` 与 `gpt-5.6-terra` 并列提供,不修改默认模型或子代理列表。GitHub Copilot 使用 Responses;OpenRouter ID 为 `openai/gpt-6.1-terra`,上下文为 1,050,000,每百万 token 输入 $1.00、输出 $5.00。 + +原生 Codex:默认上下文 500,000,长上下文上限 872,000,默认 effort 为 `low`,级别为 `low`、`medium`、`high`、`xhigh`、`max`、`ultra`。原生 `ultra` 在传输时映射为 `max`。原生条目仅提供 Fast 高速模式。 + +[OpenAI API](https://developers.openai.com/api/docs/models/gpt-6.1-terra):上下文 1,050,000,最大输出 128,000;支持文本和图像输入、文本输出;知识截止日期为 2026-06-01。effort 为 `none`、`low`、`medium`(默认)、`high`、`xhigh`、`max`。端点为 `v1/responses` 和 `v1/chat/completions`;工具调用必须使用 Responses。 + +每百万 token:输入 $1.00、缓存输入 $0.05、缓存写入 $1.25(30分钟)、输出 $5.00。输入超过 272,000 token 时,整个请求按输入和缓存 2×、输出 1.5×计费,依次为 $2.00 / $0.10 / $2.50 / $7.50。Fast 模式价格为 2×。 + ## 首次注册时的模型选择 新的非 OAuth 连接会等待可靠的模型列表,再公开模型。如果 Models 标签页中去重后的模型行达到20个,所有模型开关初始为 OFF,但提供者本身保持 ACTIVE。实际认证方式为 OAuth 或 ChatGPT 登录的连接保留默认设置。 @@ -53,7 +63,7 @@ ocx models provider openrouter on | `codexWsReuseAcrossTurns?` | `boolean` | `false` | 可选:同一账号和线程的 Codex WebSocket 跨回合复用。开启后忙套接字最多等待 750ms,模型或服务层级变化时最多保留 2 个套接字,回合状态头只出现在每条消息上。 | | `upstreamHostCircuitThreshold?` | `number` | `0` | 原生 OpenAI forward Responses 与 compact 发送的可选断路器阈值,仅统计已证明的连接前 DNS/TCP 故障。`0` 表示禁用;`1`–`20` 表示在这么多个终止逻辑请求失败后,对 provider-origin 冷却 30 秒。断路期间会在账户选择和上游发送之前返回带 `Retry-After` 的 `503`;冷却结束后只允许一个半开请求。超时和 HTTP 响应不计数,任意 HTTP 响应都会关闭断路器。 仅适用于未固定账户的 Codex Pool 路由;在 `codexAccountMode: "direct"` 或使用账户限定选择器时不会启用。 | | `modelCacheTtlMs?` | `number` | `300000` | 每个提供者 `/models` 缓存的新鲜度窗口。 | -| `cacheRetention?` | `"none" \| "short" \| "long"` | `"short"` | Anthropic 提示缓存策略:禁用、5 分钟临时缓存,或 1 小时扩展缓存。 | +| `cacheRetention?` | `"none" \| "short" \| "long"` | Anthropic 官方端点:`"long"`;其他 Anthropic 兼容端点:`"short"` | Anthropic 提示缓存策略:禁用、5 分钟临时缓存,或 1 小时扩展缓存。未设置时,发往 `api.anthropic.com`(API 密钥或 OAuth 账号池)的请求使用 1 小时 TTL,兼容网关和厂商端点保持 5 分钟 TTL。显式设置的值始终优先。 | | `tokenGuardian?` | `OcxTokenGuardianConfig` | 关闭 | 可选的主动 OAuth 刷新与 Codex 账户预热策略。 | 这些策略均使用各账户的有效阈值:存在 `codexAccountAutoSwitchThresholds` 条目时使用该值,否则继承全局 `autoSwitchThreshold`。覆盖值为 0 仅关闭基于用量的主动切换;启动绑定、硬锁、冷却、模型使用资格检查和故障恢复仍然生效。 @@ -89,9 +99,12 @@ selector,而不是分配一个新名称。 | --- | --- | --- | | `adapter` | `string` | `openai-chat`、`openai-responses`、`anthropic`、`google`、`kiro`、`cursor`、`ollama-native`、`azure-openai`(或别名 `azure`)之一。 | | `baseUrl` | `string` | 上游 API 基础 URL。大多数内置固定端点会忽略不匹配的值;具备冲突安全键的预设会保留一个更早、同名的自定义目标。 | -| `requestPacing?` | `{ enabled, requestsPerMinute?, minIntervalMs?, maxConcurrentRequests?, models? }` | 可选的客户端出站请求启动节流,与上游用量、计费和限流指标相互独立。`maxConcurrentRequests` 是限制进行中请求数的正整数,提供商或模型规则都可以只配置此项。提供商限制适用于所有模型,`models` 按上游模型精确 ID 匹配,并可增加延迟或收紧并发限制。排队等待不计入响应头超时。覆盖 HTTP 以及显式适配器 `fetchResponse`/`runTurn` 调用。配置并发上限时,标准 Responses WebSocket 请求改用 HTTP/SSE,以便在响应体完成、出错或取消时释放并发名额。 对包括 Cursor 在内的 `runTurn` 适配器,并发上限计算进行中的回合数,而非物理发送数:同一回合内的 RunSSE 和 BidiAppend 可以重叠,其他回合仍须等待。后续发送仍遵守启动间隔。 | +| `requestPacing?` | `{ enabled, requestsPerMinute?, minIntervalMs?, maxConcurrentRequests?, models?, keySelection?, keys? }` | 可选的客户端出站请求启动节流,与上游用量、计费和限流指标相互独立。`maxConcurrentRequests` 是限制进行中请求数的正整数,提供商或模型规则都可以只配置此项。提供商限制适用于所有模型,`models` 按上游模型精确 ID 匹配,并可增加延迟或收紧并发限制。排队等待不计入响应头超时。覆盖 HTTP 以及显式适配器 `fetchResponse`/`runTurn` 调用。配置并发上限时,标准 Responses WebSocket 请求改用 HTTP/SSE,以便在响应体完成、出错或取消时释放并发名额。 对包括 Cursor 在内的 `runTurn` 适配器,并发上限计算进行中的回合数,而非物理发送数:同一回合内的 RunSSE 和 BidiAppend 可以重叠,其他回合仍须等待。后续发送仍遵守启动间隔。 | +| `requestPacing.keySelection?` | `"active"`, `"round-robin"` | 正常请求的密钥选择:默认 `active` 保留已保存密钥和现有错误切换;`round-robin` 按顺序分配可用池密钥,不修改已保存的选择。均衡的是分配次数,不是令牌或费用。 | +| `requestPacing.keys?` | `{ requestsPerMinute?, minIntervalMs?, maxConcurrentRequests? }` | 按密钥引用设置统一 RPM、最小启动间隔和并发上限,跨模型共享,并同时受提供商和模型上限约束。相同引用共享容量;同一上游项目的不同引用不代表独立配额。不管理 TPM 或每日配额。 | | `responsesPath?` | `string` | 用于 key-auth `openai-responses` 请求的相对资源路径。必须以 `/` 开头,且不能包含 scheme、query 或 fragment。 | | `chatCompletionsPath?` | `string` | 用于 `openai-chat` 请求的相对资源路径,是 `responsesPath` 的对应项,适用相同的路径规则。当同一上游以不同前缀提供 Chat Completions 和 Responses 时需要此配置:按模型的 wire override 只更换适配器而不改动 `baseUrl`,否则已启用的 Chat 请求会被发送到 Responses base。随附示例为 Z.AI。 | +| `projectResponsesLiteHostedSearchHistory?` | `boolean` | 默认关闭。仅实际布尔值 `true` 可为此 provider 显式启用有损 hosted-search 历史投影,且只作用于最终目的地精确为标准 ChatGPT Codex `/responses`、明确选择 Responses Lite 的普通请求及原生 v2 `compaction_trigger` 压缩请求。省略或设为 `false` 时保留结构化历史;字符串和数字无效。不会启用 Lite、改变传输,也不影响原生 v1 `/responses/compact`、公开 OpenAI API/其他目的地或既有 portable/routed compaction 投影。详见[Lite 搜索历史](/zh-cn/reference/proxy-formats/#codex-原生普通-lite-请求的搜索历史)。 | | `upstreamWebsocket?` | `boolean` | 为 `openai-responses` 请求选择性启用上游 Responses WebSocket 传输(默认 `false`)。仅对第一方 `https://api.openai.com/v1` 上游生效;自定义提供者端点始终使用有界 HTTP/SSE,因为 Bun 无法在分配完整消息之前对入站 WebSocket 消息实施大小限制。对于规范 ChatGPT `openai` 提供商,省略该字段会在符合条件的轮次使用上游 WebSocket,`false` 通过 HTTP/SSE 发送流式轮次,`true` 会被拒绝;设为 `false` 时,原生轮次中操控与注入不可用。该字段独立于客户端侧的 `websockets` 设置,且不改变端点或凭据。普通 HTTP 仍使用 SSE;非 Responses 路径和 `openai-chat` 请求仍使用 HTTP。 | | `supportsServiceTier?` | `boolean` | `service_tier` 能力的三态。`true`:fast 模式可以注入,调用方提供的值也会被保留。`false`:剥离该字段且绝不注入(已明确不支持的上游不会收到它)。未设置:未分类——调用方提供的值原样保留,fast 模式绝不注入。注册表已对官方 OpenAI(`true`)、DeepSeek 和 Volcengine Ark(`false`)分类;仅对真正支持分层的自定义网关显式设置。 | | `responseTierAuthoritative?` | `boolean` | 响应等级是否足以确认或否定 Fast。对响应元数据不具权威性的链路显式设为 `false`;未设置时保持原有行为。不会启用 Fast 或修改出站参数。详见[响应服务等级的可信度](#响应服务等级的可信度)。 | @@ -113,7 +126,7 @@ selector,而不是分配一个新名称。 | `modelAutoCompactTokenLimits?` | `Record` | 按模型设置的正安全整数软自动压缩预算。该值只能降低“上下文或最大输入的 90%”这一有效上限;无法解析出有效上下文窗口时不会输出。对于规范 `openai`,键必须是受支持的精确原生模型 ID,且不得包含提供者或账户选择器前缀。提供者 PATCH 会合并条目;将某个键设为 `null` 会删除该键,将整个字段设为 `null` 会清空映射。这些 `null` 删除标记仅适用于 PATCH。 | | `defaultMaxOutputTokens?` | `number` | 当客户端省略 `max_output_tokens` 时,`openai-chat` 的提供者级回退值。 | | `modelMaxOutputTokens?` | `Record` | 正数型、按模型设置的 `openai-chat` 回退预算;精确/模式匹配优先于提供者默认值。 | -| `modelCosts?` | `Record` | 按模型设置的显示价格(每 100 万 token 的美元数),以该提供者的精确上游模型 ID 为键(不是提供者标识符或路由后的 `provider/model` 标签),值为四个字段:`input`、`output`、`cacheRead`、`cacheWrite`(示例:`{ "deepseek-v4-flash": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 } }`)。任何模型 ID 都是有效键——自定义提供者可以通过 `openai-chat` 适配器指向任意 OpenAI 兼容端点,即使不存在于内置目录中,本地 OpenAI 兼容和内部提供者的 ID 同样有效。用户配置的价格在 Logs 的 `~$` 和 Usage 估算中优先于内置目录;历史条目也会按当前覆盖项重新计价,因此修改价格可能改变过去的总额(回退顺序:用户配置 → jawcode 目录 → expected-price 覆盖 → 模型级厂商价格);用户明确将所有费率设为零时,会得到已知的零费用估算;删除该模型的覆盖项即可恢复自动定价。目录中的全零价格仍会回退到下一个来源。每个费率必须是大于等于 0 的有限数字,且不超过 1,000,000(每 100 万 token 的美元数);超出范围的条目会在管理边界被拒绝,并在加载时被丢弃。仅用于显示的估算:覆盖项不影响路由、账户选择、配额或计费。 | +| `modelCosts?` | `Record` | 按模型设置的显示价格(每 100 万 token 的美元数),以该提供者的精确上游模型 ID 为键(不是提供者标识符或路由后的 `provider/model` 标签),值为四个字段:`input`、`output`、`cacheRead`、`cacheWrite`(示例:`{ "deepseek-v4-flash": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 } }`)。任何模型 ID 都是有效键——自定义提供者可以通过 `openai-chat` 适配器指向任意 OpenAI 兼容端点,即使不存在于内置目录中,本地 OpenAI 兼容和内部提供者的 ID 同样有效。用户配置的价格在 Logs 的 `~$` 和 Usage 估算中优先于内置目录;历史条目也会按当前覆盖项重新计价,因此修改价格可能改变过去的总额(回退顺序:用户配置 → jawcode 目录 → expected-price 覆盖 → 模型级厂商价格);用户明确将所有费率设为零时,会得到已知的零费用估算;删除该模型的覆盖项即可恢复自动定价。目录中的全零价格仍会回退到下一个来源。每个费率必须是大于等于 0 的有限数字,且不超过 1,000,000(每 100 万 token 的美元数);超出范围的条目会在管理边界被拒绝,并在加载时被丢弃。仅用于显示的估算:覆盖项不影响路由、账户选择、配额或计费。 | | `headers?` | `Record` | 额外的上游请求头。会拒绝 Authorization、cookie、API key 头、嵌入换行符以及无效名称。 | | `openRouterRouting?` | `OpenRouterProviderRouting` | 默认的 OpenRouter `order`、`only` 和 `allowFallbacks` 偏好;仅对使用 `openai-chat` 的规范 OpenRouter 有效。 | | `modelOpenRouterRouting?` | `Record` | 精确模型 id 级别的覆盖项,会替换提供者级 OpenRouter 偏好。 | @@ -125,7 +138,7 @@ selector,而不是分配一个新名称。 | `modelReasoningEfforts?` | `Record` | 按模型设置的标签。空列表会隐藏 effort 控件。 | | `modelSupportsReasoningSummaries?` | `Record` | 将某个模型设为 `false`,即可停止暴露摘要并移除摘要交付字段。 | | `modelReasoningSummaryDelivery?` | `Record` | 按模型设置的 Responses 交付枚举;会重写现有的 delivery 字段。 | -| `modelAdapters?` | `Record` | 按模型设置的 `openai-chat` 或 `openai-responses` 线协议覆盖项,用于混合线协议网关。显式条目优先于注册表默认值;DeepSeek 预设可以为 `deepseek-v4-flash` 选择原生 Responses,GitHub Copilot 则为 模型(`gpt-5.3-codex`、`gpt-5.4`、`gpt-5.4-mini`、`gpt-5.5`、`gpt-5.6-luna`、`gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`)声明了 Responses 专用默认值,因为这些模型在代理流量下会拒绝 `/chat/completions`。没有内置默认值的模型(例如 `gpt-5.4-nano`)可以在此手动启用。单一线协议上游固定项和规范 ChatGPT forward 会拒绝覆盖。 | +| `modelAdapters?` | `Record` | 按模型设置的 `openai-chat` 或 `openai-responses` 线协议覆盖项,用于混合线协议网关。显式条目优先于注册表默认值;DeepSeek 预设可以为 `deepseek-v4-flash` 选择原生 Responses,GitHub Copilot 则为 模型(`gpt-5.3-codex`、`gpt-5.4`、`gpt-5.4-mini`、`gpt-5.5`、`gpt-5.6-luna`、`gpt-5.6-sol`、`gpt-5.6-terra`, `gpt-6.1-sol`, `gpt-6.1-terra`、`gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`)声明了 Responses 专用默认值,因为这些模型在代理流量下会拒绝 `/chat/completions`。没有内置默认值的模型(例如 `gpt-5.4-nano`)可以在此手动启用。单一线协议上游固定项和规范 ChatGPT forward 会拒绝覆盖。 | | xAI Responses 启用项(仪表板) | 开关 | 仅用于 `xai`,以原子方式设置或清除 `grok-4.5` 和 `grok-4.6` 的 `modelAdapters` 条目。若只存在一个条目,则显示混合状态,直到下次开关写入将两者统一。其他覆盖项和层级行为不变。 Grok 4.7 在 OAuth 上通过注册表线协议默认使用 Responses,也可通过显式的 `modelAdapters["grok-4.7"] = "openai-chat"` 条目切换到 Chat。 | | `xaiResponsesXSearch?` | `boolean` | 默认禁用。在 xAI Responses 目标上,仅当有效的 `web_search` 工具在最终请求规范化后仍保留时,才附加由提供方托管的 `x_search` 声明。不会重复已有声明,绝不会扩大调用方的 `tool_choice`/`allowed_tools` 选择范围,并且此项独立于网络搜索辅助服务的 `search.xSearch` 选项。 | | `modelPreferHostedTools?` | `Record` | 非 forward Responses gateway 的精确模型 ID opt-in,用于上游预留 hosted tool namespace 的情况。目前只支持 `["image_generation"]`;匹配模型必须使用 `openai-responses` wire 且支持该 hosted 工具。它会移除冲突的客户端 `image_gen` 声明,并改写其 selector 以保持调用方的 tool choice。对于 OpenAI API 的虚拟 `-pro` 模型,先匹配所选公开 ID,未命中时才使用解析出的基础 wire-model ID 作为回退。`modelAdapters` 会先按公开 ID、再按基础 ID 解析;后一次结果决定最终 wire。未配置模型保持普通 alias 行为。 | @@ -139,12 +152,13 @@ selector,而不是分配一个新名称。 | `noStopModels?` | `string[]` | 会拒绝调用方指定 `stop` 的模型。`openai-chat` 适配器、Chat 直通和 Responses 直通会为这些模型省略该字段。内置 `xai` 预设在此列出 xAI 文档说明会拒绝该参数的推理模型(`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`);`grok-4.20-0309-non-reasoning`, `grok-composer-2.5-fast` 保留调用方的 `stop`。 | | `noPenaltyModels?` | `string[]` | 会拒绝 presence/frequency penalty 的模型。 内置 `xai` 预设在此列出 xAI 文档说明会拒绝这些参数的推理模型(`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`);非推理模型保留调用方的 penalty。 | | `noStructuredOutputModels?` | `string[]` | `openai-chat` 端点拒绝 `response_format` 的精确模型 ID。仅当请求模型与条目完全匹配时才省略该字段;其他 `openai-chat` 模型仍启用 structured-output 转换。 | -| `noJsonSchemaModels?` | `string[]` | `openai-chat` 端点拒绝 `json_schema` 形式但仍接受 `json_object` 的精确模型 ID。这类请求会降级为 `json_object` 而不是被丢弃,因此请求 JSON 的调用方仍能拿到 JSON。同一模型同时出现在两个列表时,以 `noStructuredOutputModels` 为准。`opencode go`、`opencode zen`、`opencode free` 预设已为其 DeepSeek 路由内置该项。 | +| `noJsonSchemaModels?` | `string[]` | `openai-chat` 端点拒绝 `json_schema` 形式但仍接受 `json_object` 的精确模型 ID。这类请求会降级为 `json_object` 而不是被丢弃,因此请求 JSON 的调用方仍能拿到 JSON。同一模型同时出现在两个列表时,以 `noStructuredOutputModels` 为准。`opencode go`、`opencode zen` 预设已为其 DeepSeek 路由内置该项。 | | `foldDeveloperRoleToSystem?` | `boolean` | 记录某个 `openai-chat` 目的地是否接受 `developer` 角色。`foldDeveloperRoleToSystem` 未设置时按 `system` 发送,`true` 时按 `system` 发送,`false` 时按 `developer` 发送。未设置表示尚未记录该目的地的情况;`true` 记录上游拒绝该角色;`false` 记录其接受该角色。无论哪种情况,消息都保留在对话中的原有位置,只有角色改变。拒绝该角色的目的地会返回 `400 role 'developer' is not allowed`,这一轮根本无法开始,这就是未记录状态默认折叠的原因。 | | `parallelToolCalls?` | `boolean` | 切换并行工具调用。OpenAI Chat 默认开启;非 chat 适配器只有显式 `true` 时才会声明支持。 | | `responsesItemIdRepair?` | `{ message?: string[]; reasoning?: string[]; repairMissingTerminalIds?: boolean; repairInvalidIds?: boolean }` | 默认关闭的下游 SSE 修复,用于精确占位 id、缺失的终止 id,以及(`repairInvalidIds`)缺少规范 `msg_`/`rs_` 前缀的 message/reasoning id。function-call id 永远不会被重写。内置 DeepSeek 默认启用后两项。 | | `responsesSnapshotRepair?` | `boolean` | 默认关闭的客户端修复,用于补全 SSE 与 JSON 中稀疏 Responses 生命周期快照缺失的 status、output 和工具元数据;原始检查与持久化保持不变。 | -| `retryOn429?` | `{ enabled?: boolean; attempts?: number; intervalMs?: number; maxIntervalMs?: number; respectRetryAfter?: boolean }` | 仅限 API-key 提供商(`authMode: "key"`)。可选的同目标 429 重试:未配置 `retryOn429` 时功能关闭;对象存在即启用,除非 `enabled: false`。收到 429 时等待(上游 `Retry-After` 或固定间隔)后在相同 key 上重放完全相同请求,再进入任何 key 故障转移——覆盖主文本恢复循环、Responses passthrough、图像/视频桥、web-search 侧车与终结续接。重放仅适用于流开始前的 HTTP 429 响应;自定义 `runTurn` 传输不在 HTTP 重试循环范围内。`attempts` 是首个 429 之后的同 key 重放次数(总发送次数 = `attempts` + 1),是主恢复循环、终结守卫续接与桥接重试共享的按请求统一预算;`attempts` 耗尽只会停止进一步的同 key 重放:随后按可用目标进行正常的 key 故障转移或最终错误处理——key 认证的 passthrough 线路上没有故障转移,因此耗尽的 429 会原样透出。Codex 自身从不重试 429,因此这是单 key 提供商唯一的防线。默认值:`enabled: true`、`attempts: 3`、`intervalMs: 5000`、`maxIntervalMs: 60000`(单次等待以 `maxIntervalMs` 为上限,其本身上限 600000)、`respectRetryAfter: true`。 | +| `promptCacheKey?` | `boolean` | 为整个 `openai-chat` 提供商启用 `prompt_cache_key` 转发。适配器只转发收到的密钥,不自行生成,但 Claude Messages 转换会从 `metadata.user_id`,或在没有元数据时从模型/系统/工具组合派生密钥。除非预设声明,否则默认关闭;Mistral 按其[提示缓存契约](https://docs.mistral.ai/studio/conversations/advanced/prompt-caching)启用,显式 `false` 可关闭。仅在上游明确支持时启用,严格网关可能以 HTTP 400 拒绝未知字段。 | +| `retryOn429?` | `{ enabled?: boolean; attempts?: number; intervalMs?: number; maxIntervalMs?: number; respectRetryAfter?: boolean }` | 仅限 API-key 提供商(`authMode: "key"`)。未配置时关闭,但规范端点的 OpenCode Go 和 Command Code,以及规范 Mistral 的 `openai-chat` 例外:默认重放 6 次、间隔 10 秒、上限 60 秒。配置对象即启用;`enabled: false` 也会关闭该默认策略。流开始前的 HTTP 429 按 `Retry-After` 或固定间隔等待,然后在密钥故障转移之前用同一密钥重放同一请求。覆盖文本轮次恢复、Responses passthrough、图像/视频桥、web-search 侧车和终结续接;自定义 `runTurn` 不在范围内。`attempts` 统计首个 429 后的同密钥重放次数(总发送次数 = `attempts` + 1),预算由整个请求共享。耗尽时仅停止同密钥重放,随后按可用目标执行正常密钥故障转移或最终错误处理;密钥认证 passthrough 无故障转移,会原样返回 429。规范 Mistral Chat 的显式上游延迟超过允许等待时间时,会停止同密钥重放并保留原始 `Retry-After`;池中另一密钥仍可能处理请求。其他提供商仍将等待截断至上限。Mistral 按凭据/模型共享重试截止时间;`requestPacing.enabled: false` 或 `retryOn429.respectRetryAfter: false` 可关闭。无重试时间的 429 在确认每分钟预算不足时触发保守的 60 秒恢复等待,随后仅探测一次,成功即恢复正常流量。正常流量不预测下一次 token 成本。策略使用公布的额度,排除已知耗尽的月度额度,不承诺精确重置时间,也不触发历史压缩。Codex 本身不会重试 429。对象默认值:`enabled: true`、`attempts: 3`、`intervalMs: 5000`、`maxIntervalMs: 60000`(单次等待上限,配置最大 600000)、`respectRetryAfter: true`。 | | `transientRetryOn5xx?` | `{ enabled?: boolean; attempts?: number }` | 仅限使用 key 认证的 `openai-chat` 与 `openai-responses` 提供商。`authMode: "forward"` 的提供商(ChatGPT 账号池)从不读取此选项,保持默认重试次数。可选的流开始前上游瞬态状态码(500、502、503、504、520、521、522)重试:未配置时关闭;对象存在即启用,除非 `enabled: false`。覆盖初始 Responses 请求、终结守卫续接、原生 `/v1/chat/completions`,以及 429/账户恢复重新获取。`attempts` 是单个请求允许向上游发送的总次数,包含首次发送(1..10,默认 3);它是与连接重置恢复共享的按请求预算,因此 `3` 表示最多只有三个实际请求到达提供商。等待采用固定 400 毫秒的指数退避,上限为 5 秒,并遵循 `Retry-After`。此选项独立于处理速率限制的 `retryOn429`;流开始后的故障绝不会重放。 每个 Combo 目标都把预先预约的首次发送计入该目标配置的总次数;`attempts: 1` 只发送一次,不会退还预约来增加额度,也不会提高共享上限。发送前的本地拒绝或取消会释放未使用的预约。 | | `retryOnReset?` | `{ enabled?: boolean; replacements?: number }` | 适用于原生 `openai-responses` 提供商(包含 `authMode: "forward"`),以及收到上游响应头之前的通用 Responses 转换发送路径。可选地替换一次在调用方尚未观察到任何内容时就失败的发送:未配置时关闭;对象存在即启用,除非 `enabled: false`。涵盖两个不确定阶段——响应头到达前连接断开,以及响应头之后 SSE 正文只承载控制事件时断开。canonical ChatGPT 上游 WebSocket 在 create 帧发出之后、任何 Responses 事件到达之前关闭或出错时,也按同样方式处理,其替换发送走 HTTP。只有自包含的请求才会被替换:`store: false`、完整的 `input`、没有 `previous_response_id`/`conversation`/`stream_id`,且只使用由客户端执行的工具。`replacements` 是单个逻辑请求在所有环节和所有组合子请求中可以进行的替换发送次数(1..2,默认 1);它既不是按环节的重试次数,也不是发送预算,因此替换发送仍必须落在该环节已有的发送额度之内。已经产生输出或工具调用的请求,无论此值为何都不会被替换。如果上游已经开始了第一次推理,被替换的推理仍可能计费,因此该选项默认关闭。 通用转换发送路径的首次发送和重建后的发送都支持响应头之前连接重置后的替换,使用同一授权和发送预算。适配器自行管理的传输,以及收到上游响应头之后的转换流故障,均不在此选项范围内。 | | `autoToolChoiceOnlyModels?` | `string[]` | `tool_choice` 只接受 `auto` 或 `none` 的模型;强制选择会被降级。 | @@ -158,8 +172,8 @@ selector,而不是分配一个新名称。 | `anthropicEofTolerance?` | `boolean` | 允许 Anthropic 兼容网关在 `message_stop` 前结束流,仅当已收到可见文本或完整的 JSON 对象工具输入时。默认关闭。 | | `googleMode?` | `"ai-studio" \| "vertex" \| "cloud-code-assist"` | Google 传输/身份验证模式。默认 `ai-studio`。 | | `googleToolSchemaPolicy?` | `"compatible" \| "reject-lossy"` | 仅限 Google。省略或设为 `compatible` 时保留兼容架构和现有的非直连 400 修复。`reject-lossy` 会在发送前拒绝初始损失或结果不确定的有界比较,并阻止会放宽约束的 Vertex 或 Cloud Code Assist 修复。直连 AI Studio 从不执行该修复。 | -| `project?` | `string` | Vertex 或 Antigravity Cloud Code Assist 项目 id。 | -| `location?` | `string` | Vertex 位置;环境变量回退为 `GOOGLE_CLOUD_LOCATION`。 | +| `project?` | `string` | Vertex 或 Antigravity Cloud Code Assist 项目 id。设置后,Vertex API 密钥使用项目端点;未设置时,即使存在 `GOOGLE_CLOUD_PROJECT` 也使用 express 模式。ADC 回退到 `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT`。设为 `"auto"` 时,每个 Vertex API 密钥各自解析所属项目(解析成功的结果按密钥缓存;解析失败的密钥会在后续请求中再次探测),同一个密钥池可跨多个项目;ADC 不接受 `"auto"`。 | +| `location?` | `string` | Vertex 位置。API 密钥的项目端点默认使用 `global`,并忽略 `GOOGLE_CLOUD_LOCATION`;ADC 需要此字段或 `GOOGLE_CLOUD_LOCATION` 回退。 | | `mcpServers?` | `Record` | 仅 Cursor:stdio 或 Streamable HTTP MCP 服务器。 | | `desktopExecutor?` | `DesktopExecutorConfig` | 仅 Cursor:外部 computer-use 和录屏命令。 | | `unsafeAllowNativeLocalExec?` | `boolean` | Cursor 旧布尔值;仅当更新字段未设置时,等同于 `nativeLocalExec: "on"`。 | @@ -587,3 +601,8 @@ Vercel AI Gateway 可以在多个底层推理提供者之间路由一个模型 ### `anthropicAccountPool.routes` `anthropicAccountPool.routes` 将模型绑定到已保存的 Anthropic OAuth 账户 ID。启用账户池后,区分大小写的 `match` 通配模式按顺序取第一个匹配规则,限制首次选择和 429 重试。仅当该规则没有可用账户时,`fallback: true` 才回退到普通账户池。 + + +### `promptLengthPricing` + +可选字段 `promptLengthPricing` 扩展四项基础费率。`mode: "automatic"`(或省略该字段)保留目录的上下文计价规则;`mode: "flat"` 禁用这些规则。`mode: "custom"` 使用正安全整数 `thresholdTokens`、值为 `"gt"`(>)或 `"gte"`(≥)的 `comparison`,以及包含四项绝对替代费率的 `rates`。阈值统计所有提示词 token,包括缓存读取和写入,不包括输出。选中的费率适用于包括输出在内的整个请求,并在阈值两侧都取代目录上下文规则。Fast/Priority 调整在选定费率后只应用一次,并遵循目录为该模型公布的 Priority 关系;仅当目录将该关系声明为 `lower-bound`(目前为 xAI Grok)时,估算才会标记为下限;没有目录规则的模型只应用一次 Priority 倍率。可在 **Models → Price** 中配置。**Reset to automatic** 删除包括基础费率和规则在内的整个覆盖配置。旧配置保持兼容;这些设置只涉及显示的费用估算。 diff --git a/docs-site/src/content/docs/zh-cn/reference/management-api.md b/docs-site/src/content/docs/zh-cn/reference/management-api.md index d28d3e573d1..3679c8c5390 100644 --- a/docs-site/src/content/docs/zh-cn/reference/management-api.md +++ b/docs-site/src/content/docs/zh-cn/reference/management-api.md @@ -164,6 +164,7 @@ Aside 配置档的变更在这种情况下仍会保存一件事:确认之后 | `GET /api/config` | 返回已脱敏、对管理安全的配置 DTO | — | | `PUT /api/config` | 禁用的完整配置替换保护 | 405;请改用聚焦端点 | | `GET, PUT /api/settings` | 读取运行时/启动设置,或更新自动启动、流模式、应用拥有的内存预算和 `codexAccountPickerEnabled` | 400 无效、非对象或空更新 | +| `GET, PUT /api/native-reasoning-retention` | 读取模型/账号推理保留开关;部分 PUT 保留未提交字段,JSON `null` 恢复默认值。见 [原生推理密文保留](/zh-cn/reference/configuration/#原生推理密文保留)。 | 400 未知字段、无效类型或非对象/非 null 输入 | | `GET /api/startup-health` | 读取缓存的服务/shim 启动健康状态 | — | | `POST /api/startup-action` | 安装或修复服务或 Codex shim | 400 无效动作;500 动作失败 | | `GET, POST /api/windows-tray` | 读取 Windows 托盘状态,或安装、启动、停止、卸载它 | 400 不支持的平台/动作;500 操作失败 | diff --git a/docs-site/src/content/docs/zh-cn/reference/proxy-formats.md b/docs-site/src/content/docs/zh-cn/reference/proxy-formats.md index 2b8778764f2..ed4f77711fc 100644 --- a/docs-site/src/content/docs/zh-cn/reference/proxy-formats.md +++ b/docs-site/src/content/docs/zh-cn/reference/proxy-formats.md @@ -40,6 +40,8 @@ Chat Completions 和 Messages 端点支持延迟提供的工具目录:客户 此外,在所有入站协议中,提供方返回的 `default.` 命名空间调用都会规范化为客户端声明的裸名称,即使延迟目录规则关闭了上述拒绝检查。 +原生 Messages 也会识别与 `tools: []` 一同提供的系统消息内联工具定义。这些名称在被永久移除前仍可调用,但不会授权其他客户端工具。托管 MCP 调用和服务器工具的参数流保留原生格式。 + ## 端点总览 | 客户端表面 | 端点 | 成功的非流式结果 | 成功的流式或套接字结果 | @@ -260,8 +262,14 @@ Compaction 会为需要缩短长 Responses 会话的客户端返回替换历史 | 路由类型 | 行为 | | --- | --- | | Canonical ChatGPT 或官方 OpenAI 路由 | 将请求转发到原生 `/responses/compact` 端点,并使用已解析的账号和模型认证 | +| Explicitly opted-in custom `openai-responses` gateway | `supportsNativeCompactEndpoint: true` tries the native v1 endpoint for key/local/forward auth using the provider's normal Responses transport; HTTP 404 uses portable fallback; OAuth retains routed compaction and credential resolution | | 其他路由模型 | 运行一次内部的、非流式、无工具的 compaction 轮次,并带 `compaction_trigger`;要求且只允许一个 synthetic `compaction` 项,其 `encrypted_content` 是一个 `ocx1:` 封装;把该摘要解码为 v1 替换历史 | +The custom v1 capability is independent of v2 `supportsNativeCompactionTrigger` and +`decodesNativeCompactionBlobs` for replay. None grants canonical identity or caller credential +forwarding; cross-provider overrides remain portable. See the canonical +[provider configuration](/reference/configuration/providers/) before opting in. + 原生 compact 响应会被缓冲,最大 32 MiB,即使其声明的 `Content-Length` 已经超过该限制也是如此。compact 专用失败包括: | 状态 | 类型或 code | 含义 | @@ -326,3 +334,13 @@ Anthropic 来源的失败会以 Anthropic 的错误封装呈现,因此该方 重放的推理项携带的 `encrypted_content` 只有生成它的 provider 和凭据才能读取。如果 opencodex 知道该对话上一次由另一个 provider 处理,它会在发送前移除这个 blob,并保留该项的摘要。如果那个 provider 还使用了不同的 endpoint 或凭据,该项的 `rs_…` id 也会被移除,因为它指向新目标无法查到的项。如果 opencodex 无从得知,例如代理重启之后,新目标会拒绝这个 blob:OpenAI 和 Azure OpenAI 返回 `400 invalid_encrypted_content`。此时 opencodex 会去掉上一个 provider 的推理状态(blob 和 `rs_…` id)后只重发一次请求;保留 id 会导致 `Item with id 'rs_…' not found`。 这项恢复适用于所有使用 Responses 协议的 adapter,因此 `openai-responses` 和 `azure-openai` 的行为相同。恢复成功后,该对话在同一目标上的后续轮次会在接下来五分钟内于首次发送前移除这些状态。重发计入请求的常规发送预算。普通的 400 和 429 不会以这种方式重发,5xx 也不会,只有一个狭窄的例外:对于携带加密工具输出的请求,响应体恰好是该解密拒绝的 502 会得到同样的一次重发。第二次拒绝会原样返回给客户端。遇到这种情况,请在目标 provider 上开始新的对话。 + +## Codex 原生普通 Lite 请求的搜索历史 + +当最终发出的请求未声明任何 hosted 搜索工具时(`tools` 或 Lite `additional_tools` 中都没有 `web_search` 或 `web_search_preview`,包括移除 provider 不支持的 hosted 工具之后),所有 Responses 目的地都会把未被同输入引用的回放 `web_search_call` 历史转为下文所述的带标签 assistant 参考说明,与是否启用该选项无关。原生 Sol 对这类历史会返回通用 `server_error`(#7250),因此在其他模型上搜索过的对话可以在当前请求没有搜索工具的路由上继续。名为 `web_search` 的 function 工具不算 hosted 声明。不会添加搜索工具、重新搜索或编造结果;被同输入 `item_reference` 引用的项保持结构化。已声明 hosted 搜索的请求保留结构化历史,除非下述选项生效。 + +已声明 hosted 搜索的普通请求默认保留结构化 hosted-search 历史,Responses Lite 请求也一样。若要选择有损参考说明,请在 `config.json` 中设置 `providers.openai.projectResponsesLiteHostedSearchHistory: true`(必须是布尔值,不能是字符串 `"true"`)。该设置按 provider 配置且默认关闭;省略或设为 `false` 时保留结构化历史。即使已启用,也只有最终目的地精确为标准 ChatGPT Codex `/responses` 且当前有效意图明确选择 Responses Lite 的普通请求及原生 v2 `compaction_trigger` 压缩请求,才会把未被同输入引用的剩余 `web_search_call` 历史转为带标签的 assistant 参考说明。最终 `x-openai-internal-codex-responses-lite` header 值精确为 `true` 时选择 Lite;明确的 `false` 优先于 frame metadata。没有可识别 header 值时,采用有效字符串 map 中 `client_metadata.ws_request_header_x_openai_internal_codex_responses_lite` 的 `true` 值,沿用既有 WS metadata 语义。最终有效意图为非 Lite 或无法识别时保留结构化历史;格式无效的 metadata 不会覆盖明确的 HTTP `true`。模型名、订阅鉴权、hosted 声明缺失或仅有 `additional_tools` 都不能选择这项策略;不会为触发转换而改写 header 或 metadata。启用后适用于新 thread、fork、跨模型续聊、缓存未命中或重启,不依赖诊断设置。只有有效同 scope bridge memo 能证明并先恢复原函数调用及结果;ID 前缀、模型名或 memo 缺失不能判断来源。若同输入的 `item_reference.id` 精确指向某 hosted cell,该 cell 保留;`store:false` 会先移除引用,剩余 hosted 历史可投影。工具声明、Lite `additional_tools`、选择、call/result 配对、推理/原生密文、非 hosted 顺序及调用方 raw 历史保持原契约。 + +这是仅在上述显式启用的 Lite 边界内采用的**有损历史策略**,不是 hosted schema 非法或所有上游错误已修复的断言。说明标为不可信参考数据,复制 status/action 及 action 或顶层 sources 中允许的 URL/title/type,不是指令或网页正文;每字符串最多 2,048 个 UTF-16 单元、每列表 20 项、全部已投影说明共 65,536 字节,超出的 action 用省略说明替代,转换项不复制 ID 或未知字段。HTTP/SSE 与 WS 共用普通请求的最终 builder。 + +公开 OpenAI API 及其他目的地不新增普通或 native compact 投影。原生 `/responses/compact` 保持既有端点、账户清理及官方 opaque 输出路径,保留结构化 hosted 历史。portable/routed 摘要仍沿用原有触发条件与默认引用行为;共享 renderer 在这些既有路径中也补充顶层 sources 白名单保留。本次不增加重试、来源分类或新的 compaction summarizer。 diff --git a/docs-site/src/content/docs/zh-tw/getting-started/quickstart.md b/docs-site/src/content/docs/zh-tw/getting-started/quickstart.md index 8a28107b8bc..ea72d3cfcae 100644 --- a/docs-site/src/content/docs/zh-tw/getting-started/quickstart.md +++ b/docs-site/src/content/docs/zh-tw/getting-started/quickstart.md @@ -13,7 +13,7 @@ ocx init `ocx init` 會引導你完成: -1. **選擇 provider** —— 從內建 registry 的 104 個預設中選擇一個,或選擇 `custom` 手動輸入 +1. **選擇 provider** —— 從內建 registry 的 106 個預設中選擇一個,或選擇 `custom` 手動輸入 base URL 和 adapter。 2. **API key** —— 貼上一個 key,或引用一個環境變數,例如 `${ANTHROPIC_API_KEY}`。 3. **預設模型** —— 對於 API key、本機和 custom provider,可接受預設值或輸入模型 id。 diff --git a/docs-site/src/content/docs/zh-tw/guides/claude-code.md b/docs-site/src/content/docs/zh-tw/guides/claude-code.md index c91637c60ae..dee9b6374a1 100644 --- a/docs-site/src/content/docs/zh-tw/guides/claude-code.md +++ b/docs-site/src/content/docs/zh-tw/guides/claude-code.md @@ -146,18 +146,18 @@ unknown 表示 opencodex 無法確定設定是否仍指向自己的代理。外 ### Picker 模式:在第一方 Code 分頁顯示 opencodex 模型 -Picker 模式是第一方模式的一部分。在 macOS 上選擇第一方時預設開啟;設定 +Picker 模式是第一方模式的一部分。在 macOS 和 Windows 上選擇第一方時預設開啟;設定 `claudeCode.intercept.picker: false` 後會保持關閉。它會修改第一方 Desktop 的 Code 分頁模型選擇器, -依名稱列出可用的 opencodex 模型。首次開啟時,macOS 可能會要求你在登入鑰匙圈中信任本機憑證授權單位。 +依名稱列出可用的 opencodex 模型。首次開啟時,macOS 可能會要求你在登入鑰匙圈中、Windows 可能會要求你在目前使用者根存放區中信任本機憑證授權單位。 該授權單位限制為 `claude.ai` 及其子網域。可匯出的簽章身分由 OS 認證資料儲存區保護,一般重新啟動會重用相同的憑證與金鑰。 -OpenCodex 設定目錄不會儲存明文 Picker 簽章金鑰。受限 CA 的完整驗證與 OS 信任檢查仍然適用。 -已核准的身分不變且認證資料儲存區可用時,重新啟動不會新增或移除憑證信任設定。啟動復原絕不會安裝信任: +OpenCodex 設定目錄不會儲存明文 Picker 簽章金鑰。受限 CA 的完整驗證與 OS 信任檢查仍然適用(Windows 按精確指紋與已驗證葉片驗證)。 +已核准的身分不變且認證資料儲存區可用時,重新啟動不會新增或移除 macOS 憑證信任設定。啟動復原與狀態檢查絕不會安裝信任: 若信任缺失、遭撤銷或無法確認,Picker 會維持待處理狀態。請明確執行 `ocx claude desktop picker on` -或 `ocx claude desktop picker trust` 來授予信任。 +或 `ocx claude desktop picker trust` 來授予信任(Windows 需要時含確認)。 從舊身分進行一次性移轉時,清除原有信任可能需要同意。清理未完成時,Picker 無法使用,已套用的設定檔 -會使用不解密的中繼。macOS 也可能另外要求解鎖鑰匙圈或核准應用程式存取憑證,重新啟動或升級時也可能出現這些提示。 -Windows 與 Linux 仍不支援 Picker,不會啟動 Picker CA、認證資料儲存區或代理作業。主要 Claude 攔截功能仍可用, +會使用不解密的中繼。macOS 也可能另外要求解鎖鑰匙圈或核准應用程式存取憑證,重新啟動或升級時也可能出現這些提示。Windows 上明確執行 `on` 或 `trust` 可能顯示新增根的確認。 +Linux 仍不支援 Picker,不會啟動 Picker CA、認證資料儲存區或代理作業。主要 Claude 攔截功能仍可用, 其本機 CA 檔案受到擁有者、符號連結、檔案權限及 Windows ACL 檢查保護。 Picker 模式開啟期間,Claude Desktop 會透過 OpenCodex 存取網路。如果 OpenCodex 停止,Desktop 會離線, diff --git a/docs-site/src/content/docs/zh-tw/guides/codex-app-models.md b/docs-site/src/content/docs/zh-tw/guides/codex-app-models.md index 176a68a8bfd..2b9eb7c20aa 100644 --- a/docs-site/src/content/docs/zh-tw/guides/codex-app-models.md +++ b/docs-site/src/content/docs/zh-tw/guides/codex-app-models.md @@ -86,10 +86,11 @@ GPT-5.6,以便提供每個模型真實的身份和後設資料,而不是套 | 路由 | 選擇器 id 與目錄後設資料 | | --- | --- | -| Codex 登入(停用帳號限定列) | 裸原生 id,例如 `gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-5.6-luna`;透過 `codexAccountMode` 選擇 Pool 或 Direct。GPT-5.6 列使用 922,000 token 的目錄視窗。 | +| Codex 登入(停用帳號限定列) | 裸原生 id,例如 `gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-5.6-luna`;透過 `codexAccountMode` 選擇 Pool 或 Direct。GPT-5.6 列使用 922,000 token 的目錄視窗。 `gpt-6.1-terra`:預設上下文 500,000 / 長上下文 872,000。 | | Codex 登入(啟用帳號限定列且有合格選擇器) | 每個合格選擇器與受支援的原生模型各有一列 `/`;每列只使用其對應帳號,且裸原生列會從選擇器中隱藏。原生後設資料與 context 視窗保持不變。 | -| OpenAI(API key) | 恰好八個帶名稱空間的列:`gpt-5.5`、`gpt-5.6`、Sol/Terra/Luna 與三個 `*-pro` 虛擬 id(全部八個都是 1,050,000 context;922,000 max input) | +| OpenAI(API key) | 帶命名空間的模型包括 `gpt-5.5`、`gpt-5.6`、Sol/Terra/Luna、`*-pro` 虛擬 id 與 Daybreak 別名(上下文 1,050,000;最大輸入 922,000)。`openai-apikey/gpt-6.1-terra` 的上下文為 1,050,000,最大輸出為 128,000。 | | OpenRouter | `openrouter/openai/gpt-5.6-sol`、`openrouter/openai/gpt-5.6-terra`、`openrouter/openai/gpt-5.6-luna`(922,000) | +| OpenRouter | `openrouter/openai/gpt-6.1-terra` (1,050,000) | | Cursor | 靜態回退目錄包含 `cursor/gpt-5.6-sol`、`cursor/gpt-5.6-terra`、`cursor/gpt-5.6-luna`(1,000,000),以及 Grok 4.5/4.6/4.7 的一般與 Fast 項目(500,000)。4.6 和 4.7 還提供 `xhigh`;帳號的即時發現結果決定最終顯示哪些模型。 | | xAI | 以即時發現結果為準。回退目錄包含 `xai/grok-4.6` 與 `xai/grok-4.7`,預設模型仍為 `xai/grok-4.5`;三者的 context window 均為 500,000。Grok 4.6 與 4.7 提供 `low` / `medium` / `high` / `xhigh`(上游預設值為 `high`),Grok 4.5 最高為 `high`。 | diff --git a/docs-site/src/content/docs/zh-tw/guides/combos.md b/docs-site/src/content/docs/zh-tw/guides/combos.md index 3fdb413a417..86d26e0d0b9 100644 --- a/docs-site/src/content/docs/zh-tw/guides/combos.md +++ b/docs-site/src/content/docs/zh-tw/guides/combos.md @@ -165,7 +165,7 @@ Combo 失敗分為**跳轉**失敗與**終端**失敗。 | HTTP 401、403、404、408、429 或任何 5xx | 冷卻目標並跳到下一個合格目標。 | | 分類為認證、訂閱、配額、限流、過載或上游伺服器錯誤 | 冷卻目標並跳轉,即使單靠狀態碼不足。 | | 客戶端取消(499)、`origin_rejected`、cyber-policy 拒絕、上下文溢出或其他無效請求 | 停止並回傳錯誤;另一個目標不會讓請求變為有效。 | -| 結構化 HTTP 400,明確拒絕 `user`、對 `reasoning.effort`/`reasoning_effort` 回傳不支援值,或回傳模型特定影像輸入拒絕(`param: input`) | 在輸出開始前跳轉到下一個符合條件的目標,且不記錄冷卻時間;參見下方選用參數相容性。 | +| 結構化 HTTP 400,明確拒絕 `user`、對 `reasoning.effort`/`reasoning_effort` 回傳不支援值、回傳模型特定影像輸入拒絕(`param: input`),或拒絕該路由無法讀取的輸入媒體(`unsupported_input_modality`) | 在輸出開始前跳轉到下一個符合條件的目標,且不記錄冷卻時間;參見下方選用參數相容性。 | | 由行程內轉接器(`runTurn`)執行的 Responses 回合中,目前請求未宣告的第一個工具呼叫(在任何輸出與不可重播的副作用之前) | 讓該目標進入冷卻,並以相同的工具目錄跳轉到下一個目標。出現可見輸出或不可重播的副作用之後,拒絕即為最終結果。Chat Completions 與 Anthropic Messages 請求不受影響。 | | 任何其他未分類錯誤 | 停止並回傳錯誤。 | @@ -221,7 +221,7 @@ Codex v2 子代理有一個重要限制([issue #92](https://github.com/lidge-j ### 儀表板 -開啟本機儀表板並選擇 **Combos**。該工作區可建立、編輯、重新命名與移除 combo,且其目標 picker 會排除已停用的模型與巢狀 combo。 +開啟本機儀表板並選擇 **Combos**。該工作區可建立、編輯、重新命名與移除 combo。模型選擇器包含隱藏模型;顯示/隱藏設定僅影響對外模型清單,不影響 Combo 內部選擇,選取隱藏模型也不會將其對外顯示。目標選擇器仍排除已停用的供應商、巢狀 combo 與僅提供決策服務的 JEV 供應商。 每個目標也會顯示即時額度徽章:**可用**、**額度已用盡**或**額度未知**。只有當每個可用目標均有目前有效的伺服器確認,顯示其所設定憑證的推論限額已耗盡時,編輯器才會因配額而停用儲存與建立。僅供顯示的帳戶、模型、搜尋與 MCP 配額,以及缺失或已過期的路由依據,都不會觸發此限制。此限制會在適用的重設時間或資料有效期限結束時解除,並在頁面變為作用中或可見狀態時重新檢查;重新整理會同時重新載入 Combo 資料與配額。 @@ -236,7 +236,7 @@ ocx combo set --targets provider/model[:weight],... ocx combo remove --yes ``` -`set` 也接受 `--strategy`、`--sticky`、`--effort`、`--alias` 與 `--rename-from`。用 `-` 作為 `--effort` 或 `--alias` 的值可清除該欄位。`create` 與 `update` 為 `set` 的別名;`delete` 為 `remove` 的別名;且相同子指令在 `ocx route combo` 下也可用。 +`set` 也接受 `--strategy`、`--sticky`、`--effort`、`--alias`、`--display-name`、`--description` 與 `--rename-from`。用 `-` 作為 `--effort`、`--alias`、`--display-name` 或 `--description` 的值可清除該欄位。`create` 與 `update` 為 `set` 的別名;`delete` 為 `remove` 的別名;且相同子指令在 `ocx route combo` 下也可用。 ### 管理 API @@ -275,6 +275,7 @@ Combo 儲存於頂層 `combos` 物件中,以 combo id 為 key: | `defaultEffort` | 否 | `null` | `low`、`medium`、`high`、`xhigh`、`max` 或 `ultra`;僅在呼叫者省略 effort 且目標宣告支援時套用。 | | `reasoningEffortMode` | 否 | `"strict"` | `strict` 或 `adaptive`;選擇混合能力交集及目標層級控制正規化。 | | `alias` | 否 | 無 | 可選的修剪後公開模型 id;使用上述別名規則。空值儲存為無別名。 | +| `description` | 否 | 無 | 僅供 catalog 顯示的列描述,最多 128 個字元且不含控制字元。未設定時保留產生的 `Routed via opencodex → combo (combo).` 文字。 | ## 疑難排解 @@ -296,6 +297,6 @@ Combo id 未知。回應為 HTTP 404 並帶 type `invalid_request_error`。執 ## 選用參數相容性 -一般 400 錯誤仍會終止請求,但明確拒絕 `user`、對 `reasoning.effort`/`reasoning_effort` 回傳不支援值,或回傳模型特定影像輸入拒絕(`param: input`)的結構化錯誤,可讓 combo 在輸出開始前嘗試下一個符合條件的目標,而不記錄冷卻時間。安全政策拒絕、取消及已開始的輸出仍不可重播。 +一般 400 錯誤仍會終止請求,但明確拒絕 `user`、對 `reasoning.effort`/`reasoning_effort` 回傳不支援值、回傳模型特定影像輸入拒絕(`param: input`),或拒絕該路由無法讀取的輸入媒體(`unsupported_input_modality`)的結構化錯誤,可讓 combo 在輸出開始前嘗試下一個符合條件的目標,而不記錄冷卻時間。安全政策拒絕、取消及已開始的輸出仍不可重播。 [Canonical compatibility details](/guides/combos/#request-local-target-compatibility). diff --git a/docs-site/src/content/docs/zh-tw/guides/providers.md b/docs-site/src/content/docs/zh-tw/guides/providers.md index 38a7b1a8060..9683b3925be 100644 --- a/docs-site/src/content/docs/zh-tw/guides/providers.md +++ b/docs-site/src/content/docs/zh-tw/guides/providers.md @@ -56,8 +56,9 @@ preset 通常同時省略 `authMode` 與 `apiKey`。 [`retryOn429`](/zh-tw/reference/configuration/) 的 same-key 429 replay 只適用於 API-key provider (`authMode: "key"`)。OAuth、forward 與 local preset 都被排除:它們的 credential 絕不能在同一 token -上重播,而 local runtime 也沒有 remote key 可保留。此功能為 opt-in;未設定時關閉,物件存在時預設 -啟用,除非明確設為 `enabled: false`。 +上重播,而 local runtime 也沒有 remote key 可保留。除了文件記載的特定目的地預設值之外,此功能為 opt-in。標準 Mistral Chat 會在既有的整個請求傳送額度內使用耐心的重試策略(6 次 replay,10 秒備援間隔)。明確的 `retryOn429` 設定(包括 `enabled: false`)優先。Mistral preset 也會預設轉送提供的 prompt cache key;`promptCacheKey: false` 可停用轉送。快取親和性能提高重用機會,但不保證快取命中,也不會移除 token 吞吐量限制。 + +Mistral 的 Studio 與 Vibe key 共用同一個 API-key provider。key 類型與方案額度請見 [Mistral 的 API key 文件](https://docs.mistral.ai/admin/identity-access/api-keys);proxy 不會從 key 推斷訂閱等級。對所有模型,上游明確給出的 `Retry-After` 期限會在同一 credential 與模型的呼叫之間共用。沒有重試時間的 429,在有效的每分鐘計數器顯示容量已耗盡,或被拒查詢的實測成本超過剩餘 token 但仍在公告的完整額度內時,可能啟動保守的 60 秒復原等待。這是本地復原策略,並非對伺服器重設時間的精確預測。正常流量從不預測下一次查詢的快取或成本。等待後會執行一次復原探測;成功即恢復一般流量。缺少、格式錯誤、零授權、月額度已耗盡或超過完整額度的依據,不會憑空推斷重設,也不會壓縮歷史。`requestPacing: { "enabled": false }` 或 `retryOn429: { "respectRetryAfter": false }` 可停用共用節流。對於同一 key 的 replay,若 Mistral 的重試時間超過 `retryOn429.maxIntervalMs`,proxy 會回傳 429 與重試提示,而不會提早 replay。啟用 round-robin key 選擇時,排隊中的請求會重新檢查 key 冷卻並為替代 key 取得節流容量;輸出前的 key 拒絕可在該請求既有的送出上限內改用健康的 key 復原。context window 與各模型的壓縮偏好維持獨立。 ## 1. ChatGPT 登入(forward / passthrough) @@ -98,6 +99,7 @@ ocx login google-antigravity ocx login cursor # 獨立 Cursor PKCE 登入 ocx login command-code # Command Code browser OAuth(或匯入 ~/.commandcode/auth.json) ocx login devin # Cognition/Devin:優先匯入 Devin CLI 憑證,否則走 Auth0 瀏覽器登入 +ocx login dsh-account # DeepSeek Harness Desktop(匯入 ~/.dsh/.credentials.yaml 授權) ocx login github-copilot # GitHub device flow → Copilot token(Copilot Pro/Business) ocx login codex # Codex 帳號池(別名:chatgpt、openai;需要 proxy 正在執行) ocx logout @@ -114,6 +116,7 @@ ocx logout | `google-antigravity` | `google` | `https://daily-cloudcode-pa.googleapis.com` | 透過 Cloud Code Assist wire 使用 Google OAuth。即時探索使用 CCA 經認證的 `v1internal:fetchAvailableModels` 端點,發布目前登入帳號可用的 agent 模型;維護中的 catalog 作為 fallback。 | | `cursor` | `cursor` | `https://api2.cursor.sh` | 實驗性 PKCE 登入、即時 HTTP/2 transport 與按帳號篩選的模型探索。 | | `devin` | `devin` | `https://server.codeium.com` | 實驗性的非官方 Cognition/Devin 橋接。登入會先匯入已安裝 Devin CLI 已持有的憑證(`devin auth login` 會把 `devin-session-token` 寫入它自己的 `credentials.toml`);沒有則開啟 Auth0 瀏覽器頁面,再以 `RegisterUser` 將貼上的權杖換成長期 API 金鑰。`ocx login devin-cli` 仍作為已棄用別名可用。模型清單依帳號透過 `GetCascadeModelConfigs` 即時取得,串流僅走 Connect-RPC 上的 `runTurn` 路徑。預設不在儀表板預設集內,需手動啟用。 | +| `dsh-account` | `dsh-account` | `https://api.deepseek.com/anthropic` | 實驗性優先匯入帳號提供者,複用已安裝並登入的 DeepSeek Harness (DSH) Desktop 帳號。在使用者明確匯入時嚴格以唯讀方式讀取 `~/.dsh/.credentials.yaml` 憑據。獨立於 DeepSeek API Key 計費。 | | `github-copilot` | `openai-chat` | `https://api.githubcopilot.com` | 實驗性。GitHub device flow + `copilot_internal` exchange(VS Code OAuth client)。需要有效 Copilot 訂閱;不是官方第三方 API。 | Google Antigravity 帳戶與供應商的配額查詢(包括模型清單備援)使用固定的 Google 計量端點。這些目標支援透明 Fake-IP DNS,同時保留 TLS 驗證、重新導向拒絕與私有位址檢查。自訂 base URL 只改變模型請求,不改變配額目標;`NO_PROXY` 仍使用直連政策。 @@ -247,9 +250,20 @@ database 並移除目前的 WAL、SHM 與 journal sidecar,再發布先前的 s `kiro-cli` data path 修復或移除不可讀 database、取消這些 import selector 後重試。沒有既有 `kiro-cli` session 的新機器登入不受影響。 +### DeepSeek Account (DSH) + +`dsh-account` 是一個實驗性的、優先匯入既有憑據的帳號提供者,用於複用已安裝且登入的 DeepSeek Harness (DSH) Desktop 應用程式之認證憑據。 + +- **獨立於 DeepSeek API Key**:`dsh-account` 使用使用者的 DeepSeek 認證工作階段額度(一般/贈送錢包餘額與總支出),而 `deepseek` 提供者則依平台 API Key 餘額扣款。這兩個憑據體系與路由嚴格隔離,互不 fallback。 +- **僅明確匯入**:OpenCodeX 會偵測是否已安裝 DeepSeek Harness 且已登入(`~/.dsh/.credentials.yaml`),但絕不會在未經使用者明確操作(`ocx login dsh-account` 或在 Web 儀表板點選匯入)的情況下自動複製、匯入或使用該權杖。 +- **對 DSH 憑據存放區唯讀**:OpenCodeX 將 `~/.dsh/.credentials.yaml` 視為嚴格唯讀。OpenCodeX 絕不寫入、修改、刪除或變更任何屬於 DeepSeek Harness 的檔案權限。 +- **隔離的憑據複本與登出**:匯入後,OpenCodeX 會透過 DeepSeek 權威身分端點(`/auth-api/v0/users/current`)驗證權杖,並在 OpenCodeX 認證存放區中保存獨立複本。在 OpenCodeX 中登出或移除該帳號僅會刪除 OpenCodeX 本地複本,絕不觸發上游遠端工作階段登出,也不會使 DeepSeek Harness Desktop 登出。 +- **無背景權杖自動輪替與 401 重新匯入**:DSH 工作階段授權不提供自動背景重新整理端點。若上游傳回 HTTP 401(`ACCOUNT_TOKEN_INVALID`),OpenCodeX 會將該帳號標記為 `needsReauth` 並停止路由。若要恢復存取,使用者必須透過 `ocx login dsh-account` 明確重新匯入。 +- **第三方免責聲明**:OpenCodeX 不主張 DeepSeek 官方對第三方客戶端複用工作階段授權提供背書或支援。 + ## 3. API 金鑰目錄 -opencodex 內建 104 個 preset:85 個 key-based、15 個 OAuth、3 個 local,以及 1 個預設 ChatGPT-forward +opencodex 內建 106 個 preset:85 個 key-based、17 個 OAuth、3 個 local,以及 1 個預設 ChatGPT-forward preset。儀表板的 **Add provider** picker 會開啟 key provider 的 dashboard、驗證金鑰並儲存;驗證方式 依 provider 而異。主要條目如下。 @@ -303,7 +317,7 @@ IDE/CLI,不透過 API;`minimax/minimax-m2.5` 是文件列出的 API 免費 | Z.AI (GLM Coding) | `https://api.z.ai/api/coding/paas/v4` | | Zhipu AI (BigModel) | `https://open.bigmodel.cn/api/paas/v4` | | [BigModel Coding Plan — Responses (靜態模型清單)](/guides/providers/#bigmodel-coding-plan-over-responses) | `https://open.bigmodel.cn/api/v1` | -| Qwen Cloud | Token plan(預設):`https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` · pay as you go:`https://dashscope.aliyuncs.com/compatible-mode/v1` · 或 Custom | +| Qwen Cloud | Token plan(預設):`https://token-plan.maas.qwencloudapi.com/compatible-mode/v1` · pay as you go:`https://dashscope.aliyuncs.com/compatible-mode/v1` · 或 Custom | | Tencent Cloud Coding Plan | `https://api.lkeap.cloud.tencent.com/coding/v3` | | SiliconFlow | `https://api.siliconflow.cn/v1` | | Volcengine Ark · Coding Plan · Agent Plan | `https://ark.cn-beijing.volces.com/api/v3` · `https://ark.cn-beijing.volces.com/api/coding/v3` · `https://ark.cn-beijing.volces.com/api/plan/v3` | @@ -323,26 +337,20 @@ Sionic 提供的 `deepseek/deepseek-v4.1-flash-ultrafast` 與 [OpenGateway 控制台](https://opengateway.ai/api-keys)建立金鑰,再執行 `ocx provider add opengateway` 或在控制台選擇 **OpenGateway**。Chat 請求使用設定的 Bearer 金鑰;公開模型清單無法驗證金鑰有效性。 +在 Responses 協定中,OpenGateway 拒絕 replay 原生自訂工具呼叫,因此 preset 會在傳送前將程式碼模式的 `exec` 等 Codex 自訂工具轉換為函式工具。 +工具結果內容陣列以文字傳送,圖片保留在配對的工具結果群組之後。不支援的結果部分(包括拒絕與不透明內容)會在傳送前以明確的用戶端輸入錯誤拒絕。 +圖片細節 `original` 對應為閘道接受的 `high`;圖片位元組維持不變。 +原生 Responses 路由也會在將純文字空完成以成功回傳之前先檢查。 +輸出預算耗盡時回傳帶 `max_output_tokens` 的 `incomplete`;proxy 不會提高請求的預算。仍有預算且可安全 replay 的空完成,可在既有傳送上限內重試一次。明確設定 `emptyCompletionRetry: false` 或 `OCX_EMPTY_COMPLETION_RETRY=0` 可停用此重試。出現文字、拒絕、工具活動或未知串流事件時不會 replay,並保留原始串流。此保護不會改變其他 provider 及其重試預設值。 +Replay 需要明確的 `store: false`、完整輸入且僅含用戶端執行的工具;宣告了 `web_search` 等託管工具時,即使未觀察到工具事件也不會自動 replay。 + +**OpenCode Zen**(`opencode-zen`)使用 [opencode.ai/auth](https://opencode.ai/auth) 核發的 API key 存取 `https://opencode.ai/zen/v1`。免費模型可能遇到每分鐘約 15–20 次請求的短時間速率限制(社群測量;OpenCode 未公布 RPM)。速率限制 429 缺少 `Retry-After` 時,opencodex 會加入說明與合成的 `Retry-After`;有效的上游 header 優先。同 key 重試仍須透過 [`retryOn429`](/zh-tw/reference/configuration/) 啟用。 + +在標準 `opencode-zen` 提供者中,使用 API 金鑰驗證及官方路由 `https://opencode.ai/zen/v1` 或 `https://opencode.ai/inference/v1` 時,Gemini 會在預設 `googleMode: ai-studio` 下自動使用 Google 原生 API。Zen 模型 ID 維持不變,包括沒有 `-tiered` 後綴的 Flash ID。Claude 使用 Anthropic Messages,不受 `googleMode` 影響。載入已儲存的設定時,僅忽略這些原生模型符合條件的舊 Chat/Responses 覆寫設定,以及符合條件的 Responses 專用 `image_generation` 託管工具偏好。自訂提供者、非金鑰驗證路由及其他偏好維持不變。 + +**已移除 `opencode-free` 支援。** OpenCode 上游免費層級的限制使所有模型都回傳 `403 permission_denied`([#7255](https://github.com/lidge-jun/opencodex/issues/7255))。載入既有設定時,會在記憶體中停用相關項目與相依的模型參照,每個程序只警告一次,不修改檔案。如果它原為預設供應商,將以標準 OpenAI OAuth 路徑作為預設。請使用 `opencode-go`、需要 key 的 `opencode-zen` 或其他供應商。[OpenCode Zen 條款](https://opencode.ai/docs/zen/)。 -**OpenCode Zen**(`opencode-zen`)與無 key 的 **OpenCode Free** preset 共用 -`https://opencode.ai/zen/v1`。該 gateway 的免費模型常遇到短時間 burst limit,約 15–20 requests/minute -(社群實測;OpenCode 未公布 RPM)。Zen 可能回傳 generic rate-limit 429,而沒有 `Retry-After`/ -`X-RateLimit-*` header。這與 OpenCode 宣告的 keyless desktop quota 是不同限制:`opencode-free` 約每 5 -小時 200 次 Big Pickle/free-model request。Zen 在這類 429 省略 `Retry-After` 時,opencodex 會在 client -error 加入 provider guidance 與 synthetic `Retry-After`;若上游有 `Retry-After`,仍以上游值為準。 -same-key wait-and-retry 仍需透過 [`retryOn429`](/zh-tw/reference/configuration/) 明確 opt-in。 - -**無 key 的 `opencode-free` tier 目前對第三方 client 關閉。** Zen 會拒絕任何沒有帶 `x-opencode-session` -header 的 request,回傳 error type `MissingSessionID` 與訊息 "OpenCode's free tier can only be used in -OpenCode"。這道 gate 只檢查該 header 是否存在,因此 proxy 大可捏造一個值通過,但 opencodex 不這麼做。 -偽造 session identifier 並附上帶版本號的 `opencode/` User-Agent,等於宣稱自己就是 OpenCode -client,而 OpenCode 並未公布這個 keyless tier 的第三方整合合約;用這種方式取得的 HTTP 200 是繞過 -admission check,而不是取得授權。因此 opencodex 選擇如實回報限制:送往 `opencode-free` 的 request 會 -收到一則說明上游 gate 的 error。 - -通往同一批模型的受支援路徑,是使用 [opencode.ai/auth](https://opencode.ai/auth) 取得的 OpenCode Zen API -key,走帶 key 的 **`opencode-zen`** preset。若 OpenCode 日後公布 keyless tier 的第三方路徑,opencodex 可以 -跟進;在此之前,這個 preset 的作用是記錄該限制。上游條款:[opencode.ai/docs/zen](https://opencode.ai/docs/zen/)。 +保留正常的 Claude Desktop 指派與預設值。來源模型允許清單變空的規則會停用;注入模型遭移除時,也會停用原生子代理同步。後續儲存設定時不會還原已移除的參照,並會保留正常的同時編輯。 大多數 provider 使用帶 bearer key 的 `openai-chat` adapter;**Xiaomi MiMo**(`xiaomi`)等 相容 Anthropic 的 preset 則使用 `anthropic` adapter(`x-api-key`)。Xiaomi 另有 OpenAI Chat @@ -525,6 +533,9 @@ GPT-5.6 Sol/Terra/Luna 會預置在 provider fallback list 中,因此即使即 | Codex 登入(Pool 或 Direct) | `gpt-5.6-*` | 922,000 | | OpenAI (API key) | `openai-apikey/gpt-5.6-*` 加 `*-pro` | 922,000(922,000 max input) | | OpenRouter | `openrouter/openai/gpt-5.6-sol`、`openrouter/openai/gpt-5.6-terra`、`openrouter/openai/gpt-5.6-luna` | 922,000 | +| Codex | `gpt-6.1-terra` | 500,000 / 872,000 | +| OpenAI API | `openai-apikey/gpt-6.1-terra` | 1,050,000 (128,000 max output) | +| OpenRouter | `openrouter/openai/gpt-6.1-terra` | 1,050,000 | | Cursor | `cursor/gpt-5.6-sol`、`cursor/gpt-5.6-terra`、`cursor/gpt-5.6-luna` | 1,000,000 | 原生 GPT-5.6 條目保留固定的上游 reasoning ladder,例如 Luna 有 `max` 但沒有 `ultra`。路由條目使用各 @@ -542,7 +553,7 @@ Antigravity/Cloud Code Assist 模式)、`azure` / `azure-openai`、`kiro`、 key/subscription-token gateway。**Cloudflare AI Gateway** 需要在 URL 填入 account 與 gateway id。 Copilot 的 catalog 混合多種 wire:模型(`gpt-5.3-codex`、`gpt-5.4`、`gpt-5.4-mini`、 -`gpt-5.5`、`gpt-5.6-luna`、`gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`)會拒絕 agent traffic 的 +`gpt-5.5`、`gpt-5.6-luna`、`gpt-5.6-sol`、`gpt-5.6-terra`、 `gpt-6.1-sol`、 `gpt-6.1-terra`、 `gpt-6-astra`, `grok-4.5`, `grok-4.6`, `mai-code-1.1-flash`, `mai-code-1-flash-picker`)會拒絕 agent traffic 的 `/chat/completions`,因此 opencodex 會依內建預設把這些模型路由到 Responses API;其他 Copilot 模型 仍使用 chat completions。優先順序為:hard wire pin → 你明確設定的 [`modelAdapters`](/zh-tw/reference/configuration/providers/) → registry default → provider-wide adapter。 @@ -608,4 +619,4 @@ refresh 的即時 utilization bar。bar 代表特定 window(5 小時、weekly 具有 live probe 的 provider:OpenAI/Codex、Anthropic、xAI、Cursor、Kimi、Google Antigravity、 OpenRouter、DeepSeek、ClinePass、Z.AI、MiniMax、Moonshot、Venice、Synthetic、DeepInfra、Neuralwatt, -以及任何由 a6api 支援的 custom provider。 +以及任何由 a6api 或 Abliteration 支援的 custom provider。 diff --git a/docs-site/src/content/docs/zh-tw/guides/remote-link.md b/docs-site/src/content/docs/zh-tw/guides/remote-link.md index cde622a3fed..d91b65acb3a 100644 --- a/docs-site/src/content/docs/zh-tw/guides/remote-link.md +++ b/docs-site/src/content/docs/zh-tw/guides/remote-link.md @@ -55,7 +55,7 @@ Host devbox - **Connected** 表示 SSH 通道已準備好,Child 可以使用 Home 連結。 - **Reconnecting** 表示正在重試通道。重試期間請求可能暫時回傳帶有 `Retry-After` 的 `503`。在從自己的儀表板連線的 Child 上,請求會先最多等待 15 秒讓通道恢復。 -- **Failed** 表示連結需要處理。請檢查 SSH 驗證、已確認的主機金鑰、轉送或逾時原因。建立 SSH 通道的一方會在睡眠、故障或重新啟動後自動重試(Home 新增的 Child 由 Home 重試,從自己的儀表板連線的 Child 由 Child 重試):逾時或轉送錯誤後大約每分鐘一次,驗證錯誤後每五分鐘一次。主機金鑰變更時不會重試。要立即重新連線,請在 Home 的儀表板中選擇 **Retry**,或在 Home 上執行 `ocx link reconnect --link-id `。這只會重新啟動該連結的通道,不會重新啟動 OpenCodex。 +- **Failed** 表示連結需要處理。請檢查 SSH 驗證、已確認的主機金鑰、轉送或逾時原因。建立 SSH 通道的一方會在睡眠、故障或重新啟動後自動重試(Home 新增的 Child 由 Home 重試,從自己的儀表板連線的 Child 由 Child 重試):逾時或轉送錯誤後先在一分鐘後重試,之後每次重試失敗等待時間加倍,最長五分鐘一次;驗證錯誤後每五分鐘一次。主機金鑰變更時不會重試。要立即重新連線,請在 Home 的儀表板中選擇 **Retry**,或在 Home 上執行 `ocx link reconnect --link-id `。這只會重新啟動該連結的通道,不會重新啟動 OpenCodex。 連結失敗時不會靜默切換到本機供應商。 diff --git a/docs-site/src/content/docs/zh-tw/guides/sidecars.md b/docs-site/src/content/docs/zh-tw/guides/sidecars.md index fae3319f226..bc200704d04 100644 --- a/docs-site/src/content/docs/zh-tw/guides/sidecars.md +++ b/docs-site/src/content/docs/zh-tw/guides/sidecars.md @@ -66,8 +66,10 @@ OAuth 帳號時使用 `anthropic`,否則使用 `openai`。明確選擇 `anthro 當路由模型列在其 provider 的 `noVisionModels` 中,或該模型在 `modelInputModalities` 中被宣告為僅文字, 且請求包含圖像時,只要有可用的 vision sidecar plan,opencodex 就會在主呼叫**之前**描述每張圖像並用文字替換圖像。 -若沒有可用 plan,原始圖像會被移除,不會繼續轉送給純文字後端。模型目錄會為每個由 sidecar 處理的模型宣告圖像輸入。 -只有當每個 combo 成員都能原生或透過 sidecar 接受圖像,且 combo 的 `imageInput` 設定未停用時,combo 才會宣告圖像輸入; +若沒有可用 plan,原始圖像會被移除,不會繼續轉送給純文字後端。當 `visionSidecar.enabled: false` 時, +模型目錄不會為純文字模型新增由 sidecar 提供的圖像輸入,包括自訂項目與 combo 成員。原生圖像支援及明確設定的圖像模態會保留。 +省略 `enabled` 時,仍會宣告 sidecar 提供的圖像輸入;目錄中顯示這項能力不代表已確認 sidecar 的憑證或處理容量可用。 +只有當每個 combo 成員都能原生或透過已啟用的 sidecar 接受圖像,且 combo 的 `imageInput` 設定未停用時,combo 才會宣告圖像輸入; 如此 Codex 應用程式等用戶端會允許附件,而不會在 sidecar 執行前阻擋它們。Dashboard 和管理 API 目前顯示的預設值是 `gpt-5.6-luna`,啟動時也會把明確儲存的舊 `gpt-5.4-mini` 值遷移到 Luna。只有在 `visionSidecar.model` 欄位不存在或為空字串時,vision 執行路徑才會使用程式碼中的 `gpt-5.6-luna` 回退值。 @@ -121,6 +123,8 @@ OAuth 帳號時使用 `anthropic`,否則使用 `openai`。明確選擇 `anthro `PUT /api/sidecar-settings` 接受相同欄位。部分更新會保留未提交的鍵。`timeoutMs` 使用執行時整數邊界(1–2147483647 毫秒)。 +透過 `PUT /api/sidecar-settings` 變更 Vision 開關的實際生效狀態後,儲存完成便會重新整理受管理的目錄與快取;`catalogRefreshPending=true` 表示重新整理未完成,並非用戶端的確認回覆。 + Web 搜尋 sidecar 卡片採用相同的控制項形態:模型選擇器的第一列是 **關閉 (Off)**。關閉會停止 OpenCodex 對 `web_search` 的攔截,同時 Codex 整合會把 `web_search = "disabled"` 寫入 `~/.codex/config.toml`;因為 Codex 在自身模式如此宣告前會一直宣告其原生託管的 diff --git a/docs-site/src/content/docs/zh-tw/guides/sub-agent-surface.md b/docs-site/src/content/docs/zh-tw/guides/sub-agent-surface.md index b18e2d34c1c..6f9d6cdf2f8 100644 --- a/docs-site/src/content/docs/zh-tw/guides/sub-agent-surface.md +++ b/docs-site/src/content/docs/zh-tw/guides/sub-agent-surface.md @@ -20,7 +20,7 @@ opencodex 允許你為目錄中的所有模型選擇多代理協作介面。儀 | 模式 | 介面 | 行為 | | --- | --- | --- | | **v1** | `multi_agent_v1` | 使用經典的名稱空間代理工具,以及 `send_input` / `close_agent` / `resume_agent`。`spawn_agent` 的模型覆蓋可以在其他模型上生成子代理。 | -| **base**(預設) | 上游固定值 | 恢復上游模型的固定值:gpt-5.6-sol 和 gpt-5.6-terra 使用 v2,gpt-5.6-luna 使用 v1;未固定的模型遵循 Codex 的 `multi_agent_v2` 功能開關。生成行為取決於該模型最終使用的介面。 | +| **base**(預設) | 上游固定值 | 恢復上游模型的固定值:gpt-5.6-sol、gpt-5.6-terra 和 gpt-6.1-terra 使用 v2,gpt-5.6-luna 使用 v1;未固定的模型遵循 Codex 的 `multi_agent_v2` 功能開關。生成行為取決於該模型最終使用的介面。 | | **v2** | `multi_agent_v2` | 使用扁平的 `spawn_agent` 工具、併發會話,以及 `send_message` / `followup_task` / `wait_agent` / `interrupt_agent`。全量歷史 fork 時子代理繼承父模型;`fork_turns: "none"`(或部分 fork)時接受 `model` / `reasoning_effort` 覆蓋。如果原生→路由子代理只收到後端加密的任務內容,未明確信任的外部路由會回傳 `unreadable_encrypted_agent_task`;明確信任的直接金鑰驗證 Responses 路由可以原樣接收,而混合組合仍優先選擇可解密的原生目標([#92](https://github.com/lidge-jun/opencodex/issues/92))。 | ## 運作原理 @@ -33,6 +33,42 @@ opencodex 允許你為目錄中的所有模型選擇多代理協作介面。儀 無論是即時 `/v1/models` 目錄回應,還是磁碟目錄同步,這項覆蓋都會作為最後一步執行。因此,無論條目原本如何生成,新會話都會使用一致的模式。 +## `spawn_agent` 列出哪些模型 + +Codex 在 `spawn_agent` 工具說明中最多列出五個模型,並由 Codex 自己挑選:它將選擇器中可見的目錄列依 `priority` 由小到大排序,取前五個。**Subagents** 頁面上的名冊(`subagentModels`)只能透過依名冊順序為其中的模型指定最低的 priority(沒有帳號選擇器時為 `0` 到 `4`)來影響這份清單,不會將清單限制在名冊之內。 + +如果只設定一到四個模型,Codex 會依 priority 以接下來的列填滿其餘位置。這些列不是你選的,儀表板也不會顯示是哪些模型。實際是哪些列取決於目錄: + +| 目錄 | 填補其餘位置的列 | +| --- | --- | +| 沒有 Codex 帳號選擇器 | 其他路由的 `provider/model` 列。它們的 priority 都是 `5`,因此依 provider 名稱、再依模型 id 的字母順序排列。未設定的原生 GPT 列的 priority 不低於 `100` 加上已設定 id 的數量,因此排在後面。 | +| 有 Codex 帳號選擇器 | 帳號限定的原生列(`/`),緊接在已設定的列之後。未設定的路由列移到 priority `1005`,排在它們之後。 | + +例如,沒有帳號選擇器且名冊如下時: + +```json +{ "subagentModels": ["xai/grok-4.5", "anthropic/claude-sonnet-5"] } +``` + +| 位置 | 模型 | Priority | 原因 | +| ---: | --- | ---: | --- | +| 1 | `xai/grok-4.5` | `0` | 名冊第一項 | +| 2 | `anthropic/claude-sonnet-5` | `1` | 名冊第二項 | +| 3 | `deepseek/deepseek-v4-flash` | `5` | 依字母順序第一個剩餘的路由列 | +| 4 | `google/gemini-3-pro` | `5` | 下一個路由列 | +| 5 | `moonshot/kimi-k3` | `5` | 下一個路由列 | +| 6+ | `zai/glm-5.2`,接著是原生 GPT 列 | `5`,接著是 `102` | 不在前五之內 | + +名冊為空時,opencodex 不固定任何列,前五個依目錄原有的 priority 決定。 + +被填補的位置與一般列出的模型相同,主代理可能會選用它們。這份清單也不是允許清單:主代理寫出確切名稱時,Codex 也接受前五之外的目錄模型。若要控制 Codex 列出的模型: + +- 設定五個模型。名冊滿了就沒有可填補的位置。 +- 對於永遠不想被提供的模型,將其停用,或不放進其 provider 的 `selectedModels`。已停用或未選取的模型會離開選擇器,不會填補位置。 +- `modelPickerOrder` 會改變 Codex 排序所用的 priority。只含路由列的清單會將列出的未設定列移到 `1000` 以上,因此未列出的路由列會先填補位置。包含裸原生 id 的清單會為整個選擇器排序(包括已設定的列):列出的列排在前面,先於任何未列出的列佔用列出位置。參見 [Model ordering](/guides/model-ordering/)。 + +在 v2 上,Codex 會在取前五個之前略過固定為 `"disabled"` 的列;其餘機制相同。opencodex 的 v2 指引名冊(`{{roster}}`)只列出位於前五之內的已設定名冊模型,因此填補的模型只會出現在 Codex 的工具說明中,不會出現在 opencodex 指引裡。Codex 在工作階段開始時讀取目錄,因此名冊的變更從新的工作階段開始生效。 + ## 委託模型與推理強度 儀表板的 **子代理委託** 控制三個相關設定: @@ -185,6 +221,10 @@ curl -X PUT http://localhost:10100/api/injection-model \ 它可能是 picker 隱藏、超出五個模型的顯示上限、不在目錄中,或固定為 v1。值為 `"v2"`、`null` 或缺 少介面值的項目合格;真正的 `"v1"` 固定值不合格。 +### 為什麼 `spawn_agent` 列出了我沒有設定的模型? + +Codex 依 priority 從目錄填入最多五個列出位置。設定的模型少於五個時,接下來的列會佔用其餘位置。參見上文「`spawn_agent` 列出哪些模型」一節。 + ### 模式變更會影響執行中的工作階段嗎? 不會。變更模式後請開啟新的 Codex 工作階段。若長時間執行的 App host 仍顯示過時的目錄狀態,請執行 @@ -216,7 +256,7 @@ opencodex 會將磁碟上的模型目錄與目前使用者擁有的每個 Codex | 模型 | 線路上的 `max` | 選擇 `ultra` 後的線路值 | | --- | --- | --- | | gpt-5.5 | xhigh | xhigh(先轉為 max,再經 `nativeEffortClamp`) | -| gpt-5.6-sol、gpt-5.6-terra | max | max | +| gpt-5.6-sol、gpt-5.6-terra、gpt-6.1-terra | max | max | | gpt-5.6-luna | max | 其精確上游階梯不提供該選項 | | 路由模型 | 由適配器對映或限制 | 先轉為 max,再由適配器對映或限制 | diff --git a/docs-site/src/content/docs/zh-tw/guides/subagent-v1-default.md b/docs-site/src/content/docs/zh-tw/guides/subagent-v1-default.md index 4215a418350..bdab9f109f1 100644 --- a/docs-site/src/content/docs/zh-tw/guides/subagent-v1-default.md +++ b/docs-site/src/content/docs/zh-tw/guides/subagent-v1-default.md @@ -94,3 +94,9 @@ ocx v2 mode v1 ``` CLI 不會提示。這是同一項設定;選擇前請理解本頁所述的影響。 + +## 子代理並行上限 + +在 **Models → Model settings** 中,子代理上限控制項可設定同時執行的子代理數量上限,不包括主代理。v1、base 和 v2 均可使用,包括全域 v2 旗標關閉的混合模式。介面模式與此上限分別設定。 + +提高上限可允許更多子代理,但不會自動建立子代理,也不保證執行更快。變更後請啟動新的 Codex 工作階段。如果長時間執行的 App 主機仍使用舊上限,請重新啟動該主機。 diff --git a/docs-site/src/content/docs/zh-tw/reference/cli.md b/docs-site/src/content/docs/zh-tw/reference/cli.md index 37f9f01d563..acfdece2371 100644 --- a/docs-site/src/content/docs/zh-tw/reference/cli.md +++ b/docs-site/src/content/docs/zh-tw/reference/cli.md @@ -28,7 +28,7 @@ opencodex 的命令列工具是 `ocx`。它依第一個命令名稱分派,有 設定路徑。停止或無法連線的代理以 HTTP 503 呈現,並產生非零的 CLI 離開碼。明確記載為 離線設定操作的命令,可以在沒有執行中代理的情況下驗證與編輯設定檔。 -`ocx system codex-cli-update check` 不需要執行中的代理,也不會向套件 registry 發出請求。它只會在限定範圍內檢查設定中的安裝候選項來源中繼資料,包括經過遮罩的可執行檔位置與所有權證據。正式發布的 launcher 所提供的可信內容只會驗證該候選項快照,並不證明 Codex 已成功執行。由於這個單次檢查命令絕不會執行 Codex,來自環境變數與持久化記錄的候選項只供報告(`managed: false`,通常為 `selection_unattested`);JSON 輸出包含 `candidateAvailable`、`candidateVersion` 與 `candidateSource`,而 `selectionAttested` 維持 `false`。檢查設定中的安裝候選項時,必須有正式發布的 launcher 所提供的可信內容;直接使用 Bun 啟動或從原始碼執行時不具備這項證明,因此會忽略來自環境與持久化記錄的候選項狀態,並可能報告 POSIX 下的 `candidate_unavailable` 或 Windows 下的 `windows_inspection_deferred`。在 Windows 上,這個首個切片不會對候選路徑或設定路徑執行任何檔案系統 I/O。只有由可信 launcher 擷取的絕對環境候選項可以取得應用程式封裝或版本管理工具的純詞彙標籤;其他所有 Windows 候選項都會以失敗關閉方式處理。此命令不會安裝或修復軟體、不會執行 Codex 或 npm、不會控制執行中的程序,也不會寫入設定或快取狀態。 +`check` 不查詢註冊表,也不執行 Codex。在 POSIX 上,當已發布啟動器擷取的環境候選項與執行階段解析器選擇的正規路徑相同時,`selectionAttested` 可以為 `true`;若關聯的 npm-global 套件資訊清單證明了所有權,`managed` 可以為 `true`。持久化候選項仍不獲認證。Windows 不讀取候選項或設定檔,而是延後檢查。直接透過 Bun 或原始碼執行時沒有啟動器證明。`plan` 使用隔離的暫存 npm 設定、快取和日誌查詢官方註冊表,並拒絕活動工作階段或不完整的程序觀測。它不安裝軟體,也不向程序傳送訊號。請參閱[完整命令與拒絕原因(英文)](/reference/cli/agents/#read-only-codex-cli-update-plan)。 觀測 Windows x64 安裝的方式請參閱 [`attest` 命令](/zh-tw/reference/cli/agents/)。未提供明確路徑時,該命令會觀測由受證明約束的啟動器快照識別出的已選候選項;它不授予更新權限,也不證明執行階段選擇。 diff --git a/docs-site/src/content/docs/zh-tw/reference/cli/agents.md b/docs-site/src/content/docs/zh-tw/reference/cli/agents.md index 92bbc9fde20..4728a5f131b 100644 --- a/docs-site/src/content/docs/zh-tw/reference/cli/agents.md +++ b/docs-site/src/content/docs/zh-tw/reference/cli/agents.md @@ -209,7 +209,9 @@ opencode 會插值 `{env:OPENCODEX_OPENCODE_API_KEY}`。Pi 與 OMP 的匯出不 管理無頭執行階段設定、啟動、同步、診斷與更新。 -`ocx system codex-restart --yes` 透過與 `ocx sync --restart-codex` 相同的模組重啟 Codex app-server,並完全結束再重新啟動 Codex 桌面應用程式。若代理本身在 Codex 應用程式內部執行,此命令會給出可執行提示並拒絕,而不是承諾無法完成的移交。 +`ocx system codex-restart --yes` 透過與 `ocx sync --restart-codex` 相同的模組重啟 Codex app-server。模型選擇器在應用程式啟動時建立,因此當桌面應用程式於目前模型目錄寫入之前啟動,或本次請求變更了 `model_catalog_json` 綁定時,會完全結束再重新啟動它。否則應用程式保持開啟,自行重啟其 app-server,並保留未送出的草稿與待處理的核准。若無法讀取應用程式或目錄狀態,則執行完整重啟。若代理本身在 Codex 應用程式內部執行,桌面重啟會給出可執行提示並拒絕,而不是承諾無法完成的移交。 + +如果本次請求變更了 `model_catalog_json` 綁定,即使桌面程序在目錄檔案寫入後才啟動,也會觸發桌面重新啟動。 ```bash ocx system settings --stream-mode eager-relay @@ -221,7 +223,7 @@ ocx system settings --stream-mode eager-relay ocx system codex-cli-update check --json ``` -`check` 不會向套件 registry 發出請求,只會在限定範圍內檢查設定中的安裝候選項來源證據,包括經過遮罩的可執行檔位置與所有權證據。正式發布的 launcher 所提供的可信內容只會驗證該候選項快照,並不證明 Codex 已成功執行。由於這個單次命令絕不會執行 Codex,來自環境變數與持久化記錄的候選項只供報告(`managed: false`,通常為 `selection_unattested`);JSON 輸出包含 `candidateAvailable`、`candidateVersion` 與 `candidateSource`,而 `selectionAttested` 維持 `false`。檢查設定中的安裝候選項時,必須有正式發布的 launcher 所提供的可信內容;直接使用 Bun 啟動或從原始碼執行時不具備這項證明,因此會忽略來自環境與持久化記錄的候選項狀態,並可能在 POSIX 系統上報告 `candidate_unavailable`。在 Windows 上,這個首個切片不會對候選路徑或設定路徑執行任何檔案系統 I/O。只有由可信 launcher 擷取的絕對環境候選項可以取得應用程式封裝或版本管理工具的純詞彙標籤;其他所有 Windows 候選項都會以失敗關閉方式處理。由於這個切片完全不會讀取持久化的選擇狀態,在未擷取任何環境候選項的 Windows 執行中會報告 `windows_inspection_deferred` 而非 `candidate_unavailable`:該命令無法觀測 Codex CLI 是否已安裝,因此會報告檢查被延後,而不是斷言候選項不存在。此命令不會執行 Codex 或套件管理工具、不會修復 shim、不會寫入設定或快取、不會停止程序,也不會安裝任何內容。隨應用程式封裝的候選項、位於已識別版本管理工具路徑中的候選項、未經驗證的獨立候選項,以及 shim 狀態不明確的候選項,都會報告為 `unmanaged` 或 `unknown`,絕不會歸類為 `managed`。 +`check` 不查詢註冊表,也不執行 Codex。在 POSIX 上,當已發布啟動器擷取的環境候選項與執行階段解析器選擇的正規路徑相同時,`selectionAttested` 可以為 `true`;若關聯的 npm-global 套件資訊清單證明了所有權,`managed` 可以為 `true`。持久化候選項仍不獲認證。Windows 不讀取候選項或設定檔,而是延後檢查。直接透過 Bun 或原始碼執行時沒有啟動器證明。`plan` 使用隔離的暫存 npm 設定、快取和日誌查詢官方註冊表,並拒絕活動工作階段或不完整的程序觀測。它不安裝軟體,也不向程序傳送訊號。請參閱[完整命令與拒絕原因(英文)](/reference/cli/agents/#read-only-codex-cli-update-plan)。 在 Windows 上,如果擷取到 `CODEX_CLI_PATH=codex` 這類單純命令名稱、遠端路徑或裝置路徑,則回報 `candidate_path_unavailable`。這些情況已有擷取的候選項,但其路徑不適用於此檢查。 diff --git a/docs-site/src/content/docs/zh-tw/reference/cli/lifecycle.md b/docs-site/src/content/docs/zh-tw/reference/cli/lifecycle.md index c67785a0f30..1e3639ead4d 100644 --- a/docs-site/src/content/docs/zh-tw/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/zh-tw/reference/cli/lifecycle.md @@ -61,7 +61,7 @@ ocx eject back ### `ocx recover-history --ocx-compaction --yes` -在透過原生 Codex 恢復曾由路由提供方壓縮的工作前,修復該工作的歷史記錄。此命令依 UUID 精確選取一個工作,先儲存私有的逐位元組備份,然後只把 OpenCodeX 自有的 `ocx1:` 壓縮狀態轉換成原生 Codex 可重播的普通摘要。原生加密內容與其他工作不會變更。執行前請關閉所選工作;若 rollout 在處理期間發生變化,復原會停止且不會取代原始檔案。 +在透過原生 Codex 恢復曾由路由提供方壓縮的工作前,修復該工作的歷史記錄。此命令依 UUID 精確選取一個工作,先儲存私有的逐位元組備份,然後只把 OpenCodeX 自有的 `ocx1:`/`ocx2:` 壓縮狀態轉換成原生 Codex 可重播的普通摘要。原生加密內容與其他工作不會變更。執行前請關閉所選工作;若 rollout 在處理期間發生變化,復原會停止且不會取代原始檔案。 ### `ocx uninstall` · `ocx remove` @@ -134,7 +134,7 @@ ocx status --json ### `ocx ready [--json] [--wait [--timeout ]]` -透過免認證的 `GET /readyz` 端點檢查同步後的就緒狀態。就緒時回傳 `200`,或 `pending` 與終端 `failed` 時回傳附帶 `Retry-After: 1` 的 `503`。其淨化的 HTTP 身分為 `{service, version, uptime, pid, port, status, protocol, minimumClientProtocol, managementUrl}`。`protocol` 是 Hub 目前的遠端協定版本,`minimumClientProtocol` 是相容的最低用戶端協定版本,`managementUrl` 是瀏覽器可見的標準管理 origin。沒有 `/readyz` 的舊代理會以 `unreachable` 方式 fail closed;`/healthz` 是分開的存活檢查,而非就緒檢查。此指令預設執行一次探測;`--wait` 輪詢直到就緒或逾時,但在觀察到終端 `failed` 狀態時立即退出。預設逾時為 45 秒;`--timeout ` 需要 `--wait`,接受 1–300 的正整數秒。CLI JSON 輸出 `{ready, status, pid, port}`,其中 `status` 為 `ready`、`pending`、`failed` 或 `unreachable`。離開碼為:就緒 0;未就緒、pending、failed、逾時或 unreachable 1;無效引數 64。 +透過免認證的 `GET /readyz` 端點檢查同步後的就緒狀態。就緒時回傳 `200`,或 `pending` 與終端 `failed` 時回傳附帶 `Retry-After: 1` 的 `503`。其淨化的 HTTP 身分為 `{service, version, uptime, pid, port, status, protocol, minimumClientProtocol, managementUrl, features}`。`protocol` 是 Hub 目前的遠端協定版本,`minimumClientProtocol` 是相容的最低用戶端協定版本,`managementUrl` 是瀏覽器可見的標準管理 origin。沒有 `/readyz` 的舊代理會以 `unreachable` 方式 fail closed;`/healthz` 是分開的存活檢查,而非就緒檢查。此指令預設執行一次探測;`--wait` 輪詢直到就緒或逾時,但在觀察到終端 `failed` 狀態時立即退出。預設逾時為 45 秒;`--timeout ` 需要 `--wait`,接受 1–300 的正整數秒。CLI JSON 輸出 `{ready, status, pid, port}`,其中 `status` 為 `ready`、`pending`、`failed` 或 `unreachable`。離開碼為:就緒 0;未就緒、pending、failed、逾時或 unreachable 1;無效引數 64。 ### `ocx doctor` @@ -146,6 +146,10 @@ ocx status --json ### `ocx sync [--restart-codex] [--restart-app-server-only]` +> **Remote Link Child:** `ocx sync` 也會套用 Home 的 Codex 設定;如果用戶端執行階段在該次套用之前啟動,即使沒有重新啟動旗標也會重新啟動執行階段。重新啟動失敗時,原有執行階段會繼續提供服務,指令以非零狀態結束。`ocx status --json` 與 `ocx link status --json` 的相關欄位請見 [Remote Link apply parity](/reference/cli/lifecycle/#remote-link-apply-parity)(英文)。 + +明確指定重新啟動旗標後,即使目錄和快取的位元組未變,成功提交 `config.toml` 變更也會觸發重新啟動。僅處理設定的操作若沒有變更、遭拒、只做預先檢查或寫入已回復,則不會觸發重新啟動。未指定重新啟動旗標時,sync 不會自動重新啟動 Codex。 + 從每個已設定的供應商擷取即時模型清單,並將合併後的目錄重新注入 Codex。在新增供應商後或要重新整理可用模型時執行它。 若長壽的 Codex `app-server` 仍在執行,`ocx sync` 會警告它們可能繼續提供先前的記憶體內模型清單,即使 `opencodex-catalog.json` / `models_cache.json` 已更新。傳入 `--restart-codex` 會重啟相符的 `codex … app-server` 與 `codex-code-mode-host` 進程,並在 macOS、Linux 與 Windows 上完全結束再重新啟動 Codex 桌面應用程式,讓模型選擇器重新讀取目錄。進行中的對話會結束。刻意避免廣泛的 `pkill -f codex` 比對。 @@ -156,6 +160,8 @@ ocx status --json 當命令在 Codex 應用程式內部執行時,重啟會交給分離的 helper,此工作階段會隨應用程式一起結束。 +**指定來源的同步。** `ocx sync --expect-link `(Remote Link Child)與 `ocx sync --expect-hub-key `(hub 用戶端)只有在本機仍連線到該連結或使用該金鑰時才會同步。否則不會變更任何內容,也不會同步本機供應商,指令以結束碼 5 結束,stderr 最後一行為 `{"phase":"child_link_mismatch"|"hub_client_mismatch","reason":...}`。格式錯誤或重複的旗標以結束碼 2 結束。詳情請見[英文參考](/reference/cli/lifecycle/#sync-only-from-the-expected-home---expect-link-and---expect-hub-key)。 + ### `ocx sync-cache [--restart-codex] [--restart-app-server-only]` 使 Codex 的本機模型選擇器快取失效,使其從現用的 opencodex 目錄重建。與 `ocx sync` 相同的過時 `app-server` 警告與可選重啟旗標適用。 diff --git a/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md b/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md index 1fcf6399416..e5e3c6dd68f 100644 --- a/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md @@ -113,6 +113,11 @@ Codex 池選擇套用於清除既有親和性後的下一個請求;進行中 ### `ocx account list [provider] [--json] [--all] [--quota [--refresh]]` +即使主機沒有原生 Codex 主目錄,仍可列出已儲存的 ChatGPT 池帳號,包括在儀表板中及重新整理帳號時。 +列出帳號不會建立 Codex 主目錄,也不會將原生主帳號登出。停用 Codex 用戶端整合不會停用對既有原生登入的額度觀測; +明確設定但無效的 `CODEX_HOME` 路徑仍會回報設定錯誤。若 `OPENCODEX_HOME` 儲存空間不安全,即使原生 Codex 主目錄不存在,也會回報錯誤。 +解析預設 Codex 主目錄路徑時,若該路徑是檔案、目標不存在的符號連結,或父目錄無法遍歷,也會回報錯誤。 + 未指定供應商時,列出 Codex 池、OAuth 帳號與已設定的 API-key 池。除非存在 `--all`,否則空的供應商會被跳過。指定供應商時,僅列出該憑證家族。人類輸出使用 `PROVIDER TYPE ID PLAN/LABEL STATUS`;手動選擇的 Codex 列標記為 `selected`。當儲存了兩個以上符合資格的 Kiro 帳號時,預設情況下 429 會自動輪換至另一個帳號,並優先選擇已知剩餘額度最多的帳號;輪換由帳號存在與否驅動,且無法關閉;`oauthAccountFailover.enabled: false` 拒絕的是送出前的帳號優選,而非 429 復原。`ocx account login kiro` 每次將一個帳號加入池中。空結果仍為成功。`--json` 回傳: ```text diff --git a/docs-site/src/content/docs/zh-tw/reference/configuration/advisor.md b/docs-site/src/content/docs/zh-tw/reference/configuration/advisor.md index 655328acc95..05a2936e8f5 100644 --- a/docs-site/src/content/docs/zh-tw/reference/configuration/advisor.md +++ b/docs-site/src/content/docs/zh-tw/reference/configuration/advisor.md @@ -1,6 +1,6 @@ --- title: 顧問 -description: OpenCodex 自有的專家諮詢 sidecar — 設定的專家模型為路由 Worker 提供建議,支援 manual 與 preflight 兩種策略。 +description: 獨立專家諮詢,支援 manual、preflight 與依據證據的 adaptive 策略。 --- 顧問是一個獨立的專家模型,審閱 Worker 的任務並返回建議。OpenCodex 端到端地擁有整個諮詢過程:代理向 Worker 的回合注入合成的 `advisor` 工具,自己透過正常路由權威執行諮詢,並回注建議使原 Worker 繼續。Worker 無需委託、無需 spawn 任何東西、也不攜帶 provider 憑證。 @@ -9,6 +9,8 @@ description: OpenCodex 自有的專家諮詢 sidecar — 設定的專家模型 ## 設定 +在儀表板 **Advisor** 頁面從目錄選擇專家模型。選擇器原樣儲存原生、供應商、指定帳戶、自訂和組合模型的識別碼。已停用和等待初始選擇的模型不會作為新選項。已儲存但不在可選目錄中的識別碼會保留並顯示警告,直到您選擇其他模型或清除。目錄載入失敗時保留目前選擇並提供重試。請選擇能回傳文字建議的模型;目錄不保證諮詢相容性。CLI 和 API 仍接受路由器支援的模型字串。 + ```json { "advisor": { @@ -26,11 +28,12 @@ description: OpenCodex 自有的專家諮詢 sidecar — 設定的專家模型 | `enabled?` | `boolean` | `false` | 總開關。關閉時請求路徑上沒有任何 advisor 行為。 | | `model?` | `string` | — | 專家模型。任何路由權威接受的模型字串:裸原生模型(`gpt-6-astra`)、明確 `provider/model`(`anthropic/claude-sonnet-4-6`、`xai/grok-...`)或帳戶限定的原生模型。完整支援跨 provider:Worker 與 Advisor 無需同屬一個 provider。 | | `effort?` | `string` | `"max"` | Advisor 呼叫的推理強度(`low` 至 `ultra`)。 | -| `policy?` | `"manual" \| "preflight"` | `"manual"` | 何時諮詢顧問。 | +| `policy?` | `"manual" \| "preflight" \| "adaptive"` | `"manual"` | 何時諮詢顧問。 | +| `maxConsultationsPerTask?` | `integer` | `3` | 自適應任務諮詢上限(1–3 次嘗試),包含手動呼叫。 | | `timeoutMs?` | `number` | `120000` | 回環諮詢逾時。 | | `contextSharingConsent?` | `"v1"` | 缺省 | 操作者同意把任務上下文傳送給所設定的顧問 provider。只有 `"v1"` 是目前版本。缺省、過期或其他值都表示不傳送任務內容。`enabled: true` 本身不是同意。 | -透過儀表板的 **Advisor** 頁面或 `ocx advisor status|on|off|set --model --effort --policy ` 管理。 +透過儀表板的 **Advisor** 頁面或 `ocx advisor status|on|off|set --model --effort --policy ` 管理。 沒有目前同意時,`ocx advisor on` 不會開啟跨 provider 傳送:它會印出揭露並停止。`ocx advisor on --ack-context-sharing` 與 `ocx advisor consent` 記錄 `v1`。`ocx advisor consent --revoke` 會移除同意並立即停止傳送。`ocx advisor set` 不授予同意。儀表板上的同意核取方塊預設不勾選。 @@ -78,5 +81,13 @@ Advisor 失敗是 fail-open 的:已經發出的諮詢若失敗(模型不可 ## PR1 限制 - 原生 OpenAI passthrough 回合(ChatGPT 池 Worker)不會獲得合成工具;advisor 支援覆蓋路由(translated)provider。preflight 諮詢適用於 run-turn 介面卡;工具不適用。 -- 無自適應觸發:沒有卡住偵測、重複失敗分析、升級分層、多 Advisor 或投票。`manual` 與 `preflight` 是僅有的策略。 +- 不提供語義卡住偵測、任務完成複核、升級分層、多專家或投票;自適應觸發只使用已觀察到的工具證據。 - preflight 去重帳本是行程內的;代理重啟後,進行中的任務可能再收到一次 preflight 諮詢。 + +## 依據證據的自適應諮詢與活動記錄 + +`adaptive` 在初始諮詢之外,只有同一工作目錄中的同一驗證命令失敗、後續檔案編輯成功、重新執行驗證仍失敗時才諮詢。成功驗證會清除對應失敗;編輯前已啟動的驗證不算修復後重試。初始與失敗階段分別去重,自動諮詢需要穩定的對話識別碼。工具須提供明確退出碼及配對的呼叫/結果;複雜腳本、批次呼叫、shell 修改及模糊輸出不觸發。 + +以 `ocx advisor set --policy adaptive --max-consultations 2` 設定預算。自適應模式中,手動與自動嘗試共用每項任務 1–3 次預算,服務端在發出呼叫前寫入 `OPENCODEX_HOME/advisor-budget.sqlite`,重啟或切換 Worker 不會歸零。僅儲存任務摘要、次數及到期時間,首次呼叫後 24 小時到期;最多保留 512 項任務。預算無法使用或耗盡時 Worker 繼續執行。已發出的失敗或取消呼叫仍可能計費,因此不退還額度。缺少穩定識別碼時僅保留請求級手動呼叫。 + +Advisor 頁面與 `ocx advisor activity --json`(`GET /api/advisor/activity`)顯示目前程序最近 24 小時內最多 100 筆呼叫中繼資料,可見時每 5 秒更新。包括原因、狀態、模型、耗時、已回報的輸入/輸出 Token、要求及介接器映射的推理等級;不存任務內文、命令、建議或對話識別碼,重啟即清空。映射僅代表介接器發出的參數;未回報保持未知,不推測精確費用或建議採納情況。 diff --git a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md index 4e805cf5d69..042eeefef15 100644 --- a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md @@ -11,6 +11,16 @@ description: 供應商項目、認證、端點、模型目錄、配額、context 此推斷預設關閉,需按供應商設定 `inferContextWindow: true` 開啟。未開啟時,缺少視窗的模型使用已啟用的 Context cap,否則回退至 128k。 +## GPT-6.1 Terra + +[GPT-6.1 Terra](https://openai.com/index/introducing-gpt-6-1-terra/) 於 2026年10月26日發布,是 GPT-5.6 Terra 的後繼型號。`gpt-6.1-terra` 與 `gpt-5.6-terra` 並列提供,不修改預設模型或子代理清單。GitHub Copilot 使用 Responses;OpenRouter ID 為 `openai/gpt-6.1-terra`,上下文為 1,050,000,每百萬 token 輸入 $1.00、輸出 $5.00。 + +原生 Codex:預設上下文 500,000,長上下文上限 872,000,預設 effort 為 `low`,級別為 `low`、`medium`、`high`、`xhigh`、`max`、`ultra`。原生 `ultra` 在傳輸時對應至 `max`。原生項目僅提供 Fast 高速模式。 + +[OpenAI API](https://developers.openai.com/api/docs/models/gpt-6.1-terra):上下文 1,050,000,最大輸出 128,000;支援文字與影像輸入、文字輸出;知識截止日期為 2026-06-01。effort 為 `none`、`low`、`medium`(預設)、`high`、`xhigh`、`max`。端點為 `v1/responses` 和 `v1/chat/completions`;工具呼叫必須使用 Responses。 + +每百萬 token:輸入 $1.00、快取輸入 $0.05、快取寫入 $1.25(30分鐘)、輸出 $5.00。輸入超過 272,000 token 時,整個請求按輸入與快取 2×、輸出 1.5×計費,依序為 $2.00 / $0.10 / $2.50 / $7.50。Fast 模式價格為 2×。 + ## 首次註冊時的模型選擇 新的非 OAuth 連線會先等待可靠的模型清單,再公開模型。如果 Models 分頁中去重後的模型列達到20個,所有模型開關初始為 OFF,但供應商本身保持 ACTIVE。實際驗證方式為 OAuth 或 ChatGPT 登入的連線保留預設值。 @@ -50,7 +60,7 @@ ocx models provider openrouter on | `codexPinnedTransientPolicy?` | `"hold" \| "detour-new-threads"` | `"hold"` | 手動釘選帳號降級時,`hold` 繼續使用並記錄警告;`detour-new-threads` 只把新執行緒放到其他帳號。不會改派進行中的請求。 | | `codexWsReuseAcrossTurns?` | `boolean` | `false` | 選用:同一帳號與執行緒的 Codex WebSocket 跨回合重用。開啟後忙碌 socket 最多等待 750ms,模型或層級變更時最多保留 2 個 socket。 | | `modelCacheTtlMs?` | `number` | `300000` | Per-供應商 `/models` 快取的新鮮度視窗。 | -| `cacheRetention?` | `"none" \| "short" \| "long"` | `"short"` | Anthropic prompt-cache 政策:停用、5 分鐘臨時或 1 小時延長。 | +| `cacheRetention?` | `"none" \| "short" \| "long"` | Anthropic 官方端點:`"long"`;其他 Anthropic 相容端點:`"short"` | Anthropic prompt-cache 政策:停用、5 分鐘臨時或 1 小時延長。未設定時,送往 `api.anthropic.com`(API 金鑰或 OAuth 帳號池)的請求使用 1 小時 TTL,相容閘道與廠商端點維持 5 分鐘 TTL。明確設定的值一律優先。 | | `tokenGuardian?` | `OcxTokenGuardianConfig` | off | 可選的主動 OAuth refresh 與 Codex 帳號暖機政策。 | 這些策略均使用各帳戶的有效閾值:存在 `codexAccountAutoSwitchThresholds` 項目時使用該值,否則繼承全域 `autoSwitchThreshold`。覆寫值為 0 僅關閉基於用量的主動切換;啟動綁定、硬鎖、冷卻、模型使用資格檢查和故障復原仍然生效。 @@ -69,9 +79,12 @@ ocx models provider openrouter on | --- | --- | --- | | `adapter` | `string` | `openai-chat`、`openai-responses`、`anthropic`、`google`、`kiro`、`cursor`、`ollama-native`、`azure-openai`(或別名 `azure`)之一。 | | `baseUrl` | `string` | 上游 API base URL。多數內建固定端點忽略不符;碰撞安全的金鑰預設保留較舊的同名自訂目的地。 | -| `requestPacing?` | `{ enabled, requestsPerMinute?, minIntervalMs?, maxConcurrentRequests?, models? }` | 選用的用戶端出站請求啟動節流,與上游用量、計費及限流指標彼此獨立。`maxConcurrentRequests` 是限制進行中請求數的正整數,供應商或模型規則都可單獨設定此項。供應商限制適用於所有模型,`models` 依上游模型精確 ID 比對,並可增加延遲或收緊並發限制。排隊等待不計入回應標頭逾時。涵蓋 HTTP 及明確的適配器 `fetchResponse`/`runTurn` 呼叫。設定並行上限時,標準 Responses WebSocket 請求改用 HTTP/SSE,以便在回應本文完成、出錯或取消時釋放並行名額。 對包含 Cursor 的 `runTurn` 適配器,並行上限計算進行中的回合數,而非實體傳送數:同一回合內的 RunSSE 與 BidiAppend 可重疊,其他回合仍須等待。後續傳送仍遵守啟動間隔。 | +| `requestPacing?` | `{ enabled, requestsPerMinute?, minIntervalMs?, maxConcurrentRequests?, models?, keySelection?, keys? }` | 選用的用戶端出站請求啟動節流,與上游用量、計費及限流指標彼此獨立。`maxConcurrentRequests` 是限制進行中請求數的正整數,供應商或模型規則都可單獨設定此項。供應商限制適用於所有模型,`models` 依上游模型精確 ID 比對,並可增加延遲或收緊並發限制。排隊等待不計入回應標頭逾時。涵蓋 HTTP 及明確的適配器 `fetchResponse`/`runTurn` 呼叫。設定並行上限時,標準 Responses WebSocket 請求改用 HTTP/SSE,以便在回應本文完成、出錯或取消時釋放並行名額。 對包含 Cursor 的 `runTurn` 適配器,並行上限計算進行中的回合數,而非實體傳送數:同一回合內的 RunSSE 與 BidiAppend 可重疊,其他回合仍須等待。後續傳送仍遵守啟動間隔。 | +| `requestPacing.keySelection?` | `"active"`, `"round-robin"` | 正常請求的金鑰選擇:預設 `active` 保留已儲存金鑰及既有錯誤切換;`round-robin` 依序分配可用池金鑰,不修改已儲存選擇。均衡的是分配次數,而非權杖或費用。 | +| `requestPacing.keys?` | `{ requestsPerMinute?, minIntervalMs?, maxConcurrentRequests? }` | 依金鑰參照設定統一 RPM、最小啟動間隔及並行上限,跨模型共享,並同時受供應商及模型上限約束。相同參照共享容量;同一上游專案的不同參照不代表獨立配額。不管理 TPM 或每日配額。 | | `responsesPath?` | `string` | Key-auth `openai-responses` 請求的相對資源路徑。必須以 `/` 開頭且不含 scheme、query 或 fragment。 | | `chatCompletionsPath?` | `string` | `openai-chat` 請求的相對資源路徑,為 `responsesPath` 的對應項,適用相同的路徑規則。當同一上游以不同前綴提供 Chat Completions 與 Responses 時需要此設定:按模型的 wire override 只更換適配器而不改動 `baseUrl`,否則已啟用的 Chat 請求會送往 Responses base。隨附範例為 Z.AI。 | +| `projectResponsesLiteHostedSearchHistory?` | `boolean` | 預設關閉。僅實際布林值 `true` 可為此供應商明確啟用有損 hosted-search 歷史投影,且只作用於最終目的地精確為標準 ChatGPT Codex `/responses`、明確選擇 Responses Lite 的一般請求及原生 v2 `compaction_trigger` 壓縮請求。省略或設為 `false` 時保留結構化歷史;字串和數字無效。不會啟用 Lite、變更傳輸,也不影響原生 v1 `/responses/compact`、公開 OpenAI API/其他目的地或既有 portable/routed compaction 投影。詳見[Lite 搜尋歷史](/zh-tw/reference/proxy-formats/#codex-原生一般-lite-請求的搜尋歷史)。 | | `upstreamWebsocket?` | `boolean` | 為 `openai-responses` 請求選用上游 Responses WebSocket 傳輸(預設 `false`)。僅對第一方 `https://api.openai.com/v1` 上游生效;自訂供應商端點一律使用有界 HTTP/SSE,因為 Bun 無法在配置完整訊息之前對傳入 WebSocket 訊息套用大小限制。對於規範 ChatGPT `openai` 供應商,省略此欄位會在符合條件的回合使用上游 WebSocket,`false` 會以 HTTP/SSE 傳送串流回合,`true` 會被拒絕;設為 `false` 時,原生回合中操控與注入無法使用。此欄位獨立於用戶端 `websockets` 設定,且不會變更端點或認證資料。一般 HTTP 仍使用 SSE;非 Responses 路徑與 `openai-chat` 請求仍使用 HTTP。 | | `disabled?` | `boolean` | 將供應商保留在磁碟上但排除於路由與模型/目錄清單。 | | `apiKey?` | `string` | API 金鑰,或在請求時解析的 `${ENV_VAR}` / `$ENV_VAR` 參考。 | @@ -100,7 +113,7 @@ ocx models provider openrouter on | `modelReasoningEfforts?` | `Record` | Per-model 標籤。空清單隱藏 effort 控制。 | | `modelSupportsReasoningSummaries?` | `Record` | 將模型設為 `false` 以停止廣告摘要並剝離 summary-delivery 欄位。 | | `modelReasoningSummaryDelivery?` | `Record` | Per-model Responses delivery 列舉;重寫既有的 delivery 欄位。 | -| `modelAdapters?` | `Record` | 混合 wire 閘道的 Per-model `openai-chat` 或 `openai-responses` wire 覆寫。明確項目勝過 registry 預設;DeepSeek 的預設可為 `deepseek-v4-flash` 選擇原生 Responses。單一 wire 上游 pin 與規範 ChatGPT forward 拒絕覆寫。 | +| `modelAdapters?` | `Record` | 混合 wire 閘道的 Per-model `openai-chat` 或 `openai-responses` wire 覆寫。明確項目勝過 registry 預設;DeepSeek 的預設可為 `deepseek-v4-flash` 選擇原生 Responses。單一 wire 上游 pin 與規範 ChatGPT forward 拒絕覆寫。 GitHub Copilot 為 `gpt-6.1-sol` 與 `gpt-6.1-terra` 使用 Responses。 | | xAI Responses 選用(儀表板) | 開關 | 僅用於 `xai`,以原子方式設定或清除 `grok-4.5` 與 `grok-4.6` 的 `modelAdapters` 項目。若只有一個項目,會顯示混合狀態,直到下次開關寫入統一兩者。其他覆寫與層級行為不變。 Grok 4.7 在 OAuth 上透過登錄檔 wire 預設使用 Responses,也可透過明確的 `modelAdapters["grok-4.7"] = "openai-chat"` 項目切換至 Chat。 | | `annotateEmptyToolOutputs?` | `boolean` | 在工具結果送達模型前,將已存在但為空的結果替換成簡短標記,使空白結果不會被解讀為遺漏的結果。適用於空白字串及僅含文字部分的陣列;影像、檔案及加密部分絕不會被更動。DeepSeek 透過內建登錄檔預設為 `true`,其他情況則不設定。設為 `false` 可讓供應商停用此功能;後續編輯即使省略此欄位,也會保留明確設定的 `false`。`PATCH /api/providers?name=` 接受 `true`、`false` 或 `null`;`null` 會清除覆寫並恢復使用登錄檔的預設行為。 | | `xaiResponsesXSearch?` | `boolean` | 預設停用。在 xAI Responses 目的地上,僅當即時 `web_search` 工具通過最終請求正規化後仍保留時,才附加由供應商託管的 `x_search` 宣告。既有宣告不會重複,呼叫端的 `tool_choice`/`allowed_tools` 選擇器絕不會擴大,且此設定與網頁搜尋輔助服務的 `search.xSearch` 選項分開。 | @@ -112,10 +125,12 @@ ocx models provider openrouter on | `noStopModels?` | `string[]` | 拒絕呼叫者指定 `stop` 的模型。`openai-chat` 轉接器、Chat 直通與 Responses 直通會為這些模型省略該欄位。內建 `xai` 預設在此列出 xAI 文件說明會拒絕該參數的推理模型(`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`);`grok-4.20-0309-non-reasoning`, `grok-composer-2.5-fast` 保留呼叫者的 `stop`。 | | `noPenaltyModels?` | `string[]` | 拒絕 presence/frequency penalty 的模型。 內建 `xai` 預設在此列出 xAI 文件說明會拒絕這些參數的推理模型(`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`);非推理模型保留呼叫者的 penalty。 | | `noStructuredOutputModels?` | `string[]` | 其 `openai-chat` 端點拒絕 `response_format` 的精確模型 ID。僅精確符合的請求模型會省略該欄位;structured-output 轉譯對其他每個 `openai-chat` 模型保持啟用。 | -| `noJsonSchemaModels?` | `string[]` | 其 `openai-chat` 端點拒絕 `json_schema` 形式但仍接受 `json_object` 的精確模型 ID。這類請求會降級為 `json_object` 而非被丟棄,因此要求 JSON 的呼叫端仍會拿到 JSON。同一模型同時列在兩份清單時,以 `noStructuredOutputModels` 為準。`opencode go`、`opencode zen`、`opencode free` 預設已為其 DeepSeek 路由內建。 | +| `noJsonSchemaModels?` | `string[]` | 其 `openai-chat` 端點拒絕 `json_schema` 形式但仍接受 `json_object` 的精確模型 ID。這類請求會降級為 `json_object` 而非被丟棄,因此要求 JSON 的呼叫端仍會拿到 JSON。同一模型同時列在兩份清單時,以 `noStructuredOutputModels` 為準。`opencode go`、`opencode zen` 預設已為其 DeepSeek 路由內建。 | | `foldDeveloperRoleToSystem?` | `boolean` | 記錄某個 `openai-chat` 目的地是否接受 `developer` 角色。`foldDeveloperRoleToSystem` 未設定時以 `system` 傳送,`true` 時以 `system` 傳送,`false` 時以 `developer` 傳送。未設定表示尚未記錄該目的地的情況;`true` 記錄上游拒絕該角色;`false` 記錄其接受該角色。無論何者,訊息都保留在對話中的原有位置,只有角色改變。拒絕該角色的目的地會回應 `400 role 'developer' is not allowed`,該回合根本無法開始,這就是未記錄狀態預設摺疊的原因。 | | `parallelToolCalls?` | `boolean` | 切換平行工具呼叫。OpenAI Chat 預設開啟;非 chat adapter 僅在明確 `true` 時廣告。 | | `responsesItemIdRepair?` | `{ message?: string[]; reasoning?: string[]; repairMissingTerminalIds?: boolean }` | 預設停用的下游 SSE 修復,用於精確佔位 id 與缺失的終端 id。Function-call id 永不被重寫。 | +| `promptCacheKey?` | `boolean` | 為整個 `openai-chat` provider 啟用 `prompt_cache_key` 轉送。adapter 只轉送收到的 key,不自行產生,但 Claude Messages 轉換會從 `metadata.user_id`,或在沒有 metadata 時從模型/系統/工具組合衍生 key。除非 preset 宣告,否則預設關閉;Mistral 依其[提示快取契約](https://docs.mistral.ai/studio/conversations/advanced/prompt-caching)啟用,明確的 `false` 可停用。僅在上游明確支援時啟用,嚴格閘道可能以 HTTP 400 拒絕未知欄位。 | +| `retryOn429?` | `{ enabled?: boolean; attempts?: number; intervalMs?: number; maxIntervalMs?: number; respectRetryAfter?: boolean }` | 僅限 API-key provider(`authMode: "key"`)。未設定時關閉,但標準端點的 OpenCode Go 與 Command Code,以及標準 Mistral 的 `openai-chat` 例外:預設 replay 6 次、間隔 10 秒、上限 60 秒。設定物件即啟用;`enabled: false` 也會停用該預設策略。串流開始前的 HTTP 429 會依 `Retry-After` 或固定間隔等待,然後在 key 容錯移轉前以同一 key 重播相同請求。涵蓋文字回合復原、Responses passthrough、圖片/影片橋接、web-search 輔助服務及終端續接;自訂 `runTurn` 不在範圍內。`attempts` 計算第一個 429 後的同 key replay 次數(總傳送次數 = `attempts` + 1),預算由整個請求共用。耗盡時僅停止同 key replay,接著依可用目的地執行一般 key 容錯移轉或最終錯誤處理;金鑰認證 passthrough 沒有容錯移轉,會原樣回傳 429。標準 Mistral Chat 的明確上游延遲超過允許等待時間時,停止同 key replay 並保留原始 `Retry-After`;pool 中另一個 key 仍可能處理請求。其他 provider 仍將等待截斷至上限。Mistral 依 credential/模型共用重試期限;`requestPacing.enabled: false` 或 `retryOn429.respectRetryAfter: false` 可停用。沒有重試時間的 429 在確認每分鐘預算不足時觸發保守的 60 秒復原等待,隨後只探測一次,成功即恢復一般流量。正常流量不預測下一次 token 成本。策略使用公告額度,排除已知耗盡的月額度,不承諾精確重設時間,也不觸發歷史壓縮。Codex 本身不重試 429。物件預設值:`enabled: true`、`attempts: 3`、`intervalMs: 5000`、`maxIntervalMs: 60000`(單次等待上限,設定最大 600000)、`respectRetryAfter: true`。 | | `transientRetryOn5xx?` | `{ enabled?: boolean; attempts?: number }` | 僅限使用金鑰認證的 `openai-chat` 與 `openai-responses` 供應商。`authMode: "forward"` 的供應商(ChatGPT 帳號池)從不讀取此選項,維持預設重試次數。選擇性重試串流開始前的暫時性上游狀態(500、502、503、504、520、521、522):未設定時停用;只要有此物件即啟用,除非 `enabled: false`。涵蓋初始 `Responses` 請求、終止防護續接、原生 `/v1/chat/completions`,以及 429/帳號復原的重新擷取。`attempts` 是單一請求允許傳送至上游的總次數,包含第一次(1..10,預設 3);這是與連線重設復原共用的單一請求範圍預算,因此 `3` 表示最多只有三個實際請求會送達供應商。等待採固定 400 毫秒、上限 5 秒的指數退避,並遵循 `Retry-After`。此機制獨立於處理速率限制的 `retryOn429`;串流中的失敗絕不重播。 每個 Combo 目標都把預先預約的首次傳送計入該目標設定的總次數;`attempts: 1` 只傳送一次,不會退還預約來增加額度,也不會提高共用上限。傳送前的本機拒絕或取消會釋放未使用的預約。 | | `retryOnReset?` | `{ enabled?: boolean; replacements?: number }` | 適用於原生 `openai-responses` 供應商(包含 `authMode: "forward"`),以及收到上游回應標頭之前的通用 Responses 轉換傳送路徑。可選擇性地替換一次在呼叫端尚未觀察到任何內容時就失敗的傳送:未設定時停用;只要有此物件即啟用,除非 `enabled: false`。涵蓋兩個不確定階段——回應標頭抵達前連線中斷,以及標頭之後 SSE 內文只載有控制事件時中斷。canonical ChatGPT upstream WebSocket 在 create 訊框送出之後、任何 Responses 事件抵達之前關閉或出錯時,也以相同方式處理,其替換傳送改走 HTTP。只有自我完備的請求才會被替換:`store: false`、完整的 `input`、沒有 `previous_response_id`/`conversation`/`stream_id`,且僅使用由用戶端執行的工具。`replacements` 是單一邏輯請求在所有環節與所有組合子請求中可進行的替換傳送次數(1..2,預設 1);它既不是各環節的重試次數,也不是傳送預算,因此替換傳送仍必須落在該環節既有的傳送額度之內。已經產生輸出或工具呼叫的請求,無論此值為何都不會被替換。若上游已經開始第一次推論,被替換的推論仍可能計費,因此此選項預設停用。 通用轉換傳送路徑的首次傳送和重建後的傳送都支援標頭之前連線重設後的替換,使用同一授權和傳送預算。配接器自行管理的傳輸,以及收到上游回應標頭之後的轉換串流故障,均不在此選項範圍內。 | | `autoToolChoiceOnlyModels?` | `string[]` | 其 `tool_choice` 僅接受 `auto` 或 `none` 的模型;強制選擇被降級。 | @@ -128,8 +143,8 @@ ocx models provider openrouter on | `escapeBuiltinToolNames?` | `boolean` | 為 Anthropic 相容閘道轉義內建工具名稱,並在回傳的呼叫中還原它們。 | | `googleMode?` | `"ai-studio" \| "vertex" \| "cloud-code-assist"` | Google 傳輸/認證模式。預設 `ai-studio`。 | | `googleToolSchemaPolicy?` | `"compatible" \| "reject-lossy"` | 僅限 Google。省略或設為 `compatible` 時保留相容結構描述與既有的非直接 400 修復。`reject-lossy` 會在傳送前拒絕初始損失或結果不確定的有界比較,並阻止會放寬限制的 Vertex 或 Cloud Code Assist 修復。直接 AI Studio 不會執行該修復。 | -| `project?` | `string` | Vertex 或 Antigravity Cloud Code Assist 專案 id。 | -| `location?` | `string` | Vertex 位置;環境後備為 `GOOGLE_CLOUD_LOCATION`。 | +| `project?` | `string` | Vertex 或 Antigravity Cloud Code Assist 專案 id。設定後,Vertex API 金鑰使用專案端點;未設定時,即使有 `GOOGLE_CLOUD_PROJECT` 也使用 express 模式。ADC 後備為 `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT`。設為 `"auto"` 時,每把 Vertex API 金鑰各自解析所屬專案(解析成功的結果依金鑰快取;解析失敗的金鑰會在後續請求中再次探測),同一個金鑰池可跨多個專案;ADC 不接受 `"auto"`。 | +| `location?` | `string` | Vertex 位置。API 金鑰的專案端點預設為 `global`,並忽略 `GOOGLE_CLOUD_LOCATION`;ADC 需要此欄位或 `GOOGLE_CLOUD_LOCATION` 後備。 | | `mcpServers?` | `Record` | 僅 Cursor:stdio 或 Streamable HTTP MCP 伺服器。 | | `desktopExecutor?` | `DesktopExecutorConfig` | 僅 Cursor:外部 computer-use 與 record-screen 指令。 | | `unsafeAllowNativeLocalExec?` | `boolean` | Cursor 舊版布林值,僅在較新欄位未設定時等同於 `nativeLocalExec: "on"`。 | @@ -510,3 +525,8 @@ Vercel AI Gateway 可在多個底層推論供應商之間路由一個模型。`v ### `anthropicAccountPool.routes` `anthropicAccountPool.routes` 將模型綁定至已儲存的 Anthropic OAuth 帳戶 ID。啟用帳戶池後,區分大小寫的 `match` 萬用模式依順序採用第一個符合的規則,限制首次選擇與 429 重試。僅當該規則沒有可用帳戶時,`fallback: true` 才會回退到一般帳戶池。 + + +### `promptLengthPricing` + +選用欄位 `promptLengthPricing` 擴充四項基礎費率。`mode: "automatic"`(或省略欄位)保留目錄的上下文計價規則;`mode: "flat"` 停用這些規則。`mode: "custom"` 使用正安全整數 `thresholdTokens`、值為 `"gt"`(>)或 `"gte"`(≥)的 `comparison`,以及含四項絕對替代費率的 `rates`。門檻計算所有提示詞 token,包括快取讀取與寫入,不包括輸出。所選費率適用於包含輸出的整個請求,並在門檻兩側皆取代目錄上下文規則。Fast/Priority 調整在選定費率後只套用一次,並遵循目錄為該模型公布的 Priority 關係;僅當目錄將該關係宣告為 `lower-bound`(目前為 xAI Grok)時,估算才會標示為下限;沒有目錄規則的模型只套用一次 Priority 倍率。可在 **Models → Price** 中設定。**Reset to automatic** 刪除包含基礎費率與規則的整個覆寫設定。舊設定保持相容;這些設定僅涉及顯示的費用估算。 diff --git a/docs-site/src/content/docs/zh-tw/reference/proxy-formats.md b/docs-site/src/content/docs/zh-tw/reference/proxy-formats.md index f45782de4b3..5830e03ea03 100644 --- a/docs-site/src/content/docs/zh-tw/reference/proxy-formats.md +++ b/docs-site/src/content/docs/zh-tw/reference/proxy-formats.md @@ -35,6 +35,8 @@ Chat Completions 與 Messages 端點支援延後提供的工具目錄:用戶 此外,在所有輸入協定中,供應商回傳的 `default.` 命名空間呼叫都會正規化為用戶端宣告的裸名稱,即使延後目錄規則停用了上述拒絕檢查。 +原生 Messages 也會辨識與 `tools: []` 一同提供的系統訊息行內工具定義。這些名稱在被永久移除前仍可呼叫,但不會授權其他用戶端工具。託管 MCP 呼叫和伺服器工具的參數串流保留原生格式。 + ## 端點概覽 | 客戶端介面 | 端點 | 成功的非串流結果 | 成功的串流或 socket 結果 | @@ -237,8 +239,14 @@ Compaction 為需要縮短長 Responses 對話的客戶端回傳取代歷史。 | 路由型別 | 行為 | | --- | --- | | 規範 ChatGPT 或官方 OpenAI 路由 | 以解析的帳號與模型認證將請求轉發到原生 `/responses/compact` 端點 | +| Explicitly opted-in custom `openai-responses` gateway | `supportsNativeCompactEndpoint: true` tries the native v1 endpoint for key/local/forward auth using the provider's normal Responses transport; HTTP 404 uses portable fallback; OAuth retains routed compaction and credential resolution | | 其他路由模型 | 執行一個內部、非串流、無工具的 compaction 回合,帶有 `compaction_trigger`;需要恰好一個合成的 `compaction` 項目,其 `encrypted_content` 為 `ocx1:` 封裝;將該摘要解碼為 v1 取代歷史 | +The custom v1 capability is independent of v2 `supportsNativeCompactionTrigger` and +`decodesNativeCompactionBlobs` for replay. None grants canonical identity or caller credential +forwarding; cross-provider overrides remain portable. See the canonical +[provider configuration](/reference/configuration/providers/) before opting in. + 原生 compact 回應以 32 MiB 上限緩衝,包含其宣告的 `Content-Length` 已超過限制的回應。Compact 專屬失敗包含: | 狀態 | 型別或代碼 | 意義 | @@ -300,3 +308,13 @@ Anthropic 來源的失敗以 Anthropic 的錯誤封裝渲染,因此該方言 重放的推理項攜帶的 `encrypted_content` 只有產生它的供應商與憑證才能讀取。若 opencodex 知道該對話上一次由另一個供應商處理,它會在送出前移除這個 blob,並保留該項的摘要。若那個供應商還使用了不同的 endpoint 或憑證,該項的 `rs_…` id 也會被移除,因為它指向新目標查不到的項目。若 opencodex 無從得知,例如代理重新啟動之後,新目標會拒絕這個 blob:OpenAI 與 Azure OpenAI 回傳 `400 invalid_encrypted_content`。此時 opencodex 會去掉上一個供應商的推理狀態(blob 與 `rs_…` id)後只重送一次請求;保留 id 會導致 `Item with id 'rs_…' not found`。 這項復原適用於所有使用 Responses 協定的 adapter,因此 `openai-responses` 與 `azure-openai` 的行為相同。復原成功後,該對話在同一目標上的後續輪次會在接下來五分鐘內於首次送出前移除這些狀態。重送計入請求的一般傳送預算。一般的 400 與 429 不會以這種方式重送,5xx 也不會,只有一個狹窄的例外:對於攜帶加密工具輸出的請求,回應本文恰好是該解密拒絕的 502 會得到同樣的一次重送。第二次拒絕會原樣回傳給客戶端。遇到這種情況,請在目標供應商上開始新的對話。 + +## Codex 原生一般 Lite 請求的搜尋歷史 + +當最終送出的請求未宣告任何 hosted 搜尋工具時(`tools` 或 Lite `additional_tools` 中都沒有 `web_search` 或 `web_search_preview`,包括移除供應商不支援的 hosted 工具之後),所有 Responses 目的地都會將未被同一輸入引用的回放 `web_search_call` 歷史轉為下文所述的附標籤 assistant 參考說明,與是否啟用該選項無關。原生 Sol 對這類歷史會回傳通用 `server_error`(#7250),因此在其他模型上搜尋過的對話可以在目前請求沒有搜尋工具的路由上繼續。名為 `web_search` 的 function 工具不算 hosted 宣告。不會新增搜尋工具、重新搜尋或捏造結果;被同一輸入 `item_reference` 引用的項目保持結構化。已宣告 hosted 搜尋的請求保留結構化歷史,除非下述選項生效。 + +已宣告 hosted 搜尋的一般請求預設保留結構化 hosted-search 歷史,Responses Lite 請求也一樣。若要選用有損參考說明,請在 `config.json` 中設定 `providers.openai.projectResponsesLiteHostedSearchHistory: true`(必須是布林值,不能是字串 `"true"`)。此設定按供應商配置且預設關閉;省略或設為 `false` 時保留結構化歷史。即使已啟用,也只有最終目的地精確為標準 ChatGPT Codex `/responses` 且目前有效意圖明確選擇 Responses Lite 的一般請求及原生 v2 `compaction_trigger` 壓縮請求,才會將未被同一輸入引用的剩餘 `web_search_call` 歷史轉為附標籤的 assistant 參考說明。最終 `x-openai-internal-codex-responses-lite` 標頭值精確為 `true` 時選擇 Lite;明確的 `false` 優先於訊框中繼資料。沒有可辨識的標頭值時,採用有效字串 map 中 `client_metadata.ws_request_header_x_openai_internal_codex_responses_lite` 的 `true` 值,沿用既有 WS 中繼資料語意。非 Lite 或無法辨識的有效意圖保留結構化歷史;格式無效的中繼資料不會覆寫明確的 HTTP `true`。模型名稱、訂閱認證、缺少 hosted 宣告或僅有 `additional_tools` 都不會選擇此策略;不會為觸發轉換而改寫標頭或中繼資料。啟用後適用於新對話、分支、跨模型接續、快取未命中或重新啟動,且不依賴診斷設定。只有有效、同範圍的 bridge memo 能證明並先還原原始函式呼叫及結果;ID 前綴、模型名稱或缺少 memo 不能判定來源。同一輸入的 `item_reference.id` 精確指向某 hosted 項目時,該項目會保留;`store:false` 會先移除引用,剩餘 hosted 歷史可被投影。工具宣告、Lite `additional_tools`、選擇、用戶端呼叫/結果配對、推理/原生密文、非 hosted 順序及呼叫端原始歷史均維持既有契約。 + +這是只在上述明確啟用的 Lite 邊界內採用的**有損歷史策略**,不是 hosted schema 無效或所有上游錯誤都已修復的斷言。說明是非可信的 status/action 參考中繼資料,只複製 action 與頂層 sources 中允許的 URL/title/type,並非指令或網頁內文。每個字串最多 2,048 個 UTF-16 單位、每份清單 20 項、所有投影說明合計 65,536 個序列化位元組,超出後改用省略說明。已轉換項目的 ID 和未知欄位會被捨棄。HTTP/SSE 與 WS 共用一般請求的最終 builder。 + +公開 OpenAI API 與其他目的地不新增一般或 native-compact 投影。原生 `/responses/compact` 保留既有端點、帳號清理、官方不透明輸出路徑及結構化 hosted 歷史。Portable/routed 摘要沿用既有投影觸發條件與預設引用行為;共用 renderer 在這些既有路徑也保留允許的頂層 sources。不會新增重試、來源分類或 compaction summarizer。 diff --git a/gui/bun.lock b/gui/bun.lock index 2cbae98b06d..c8a6d4a42d4 100644 --- a/gui/bun.lock +++ b/gui/bun.lock @@ -15,11 +15,11 @@ "@types/react": "^19.2.18", "@types/react-dom": "^19.2.4", "@vitejs/plugin-react": "^6.0.5", - "happy-dom": "20.11.2", + "happy-dom": "21.0.4", "oxlint": "1.78.0", "react-refresh": "^0.18.0", "typescript": "~6.0.2", - "vite": "^8.2.1", + "vite": "^8.2.4", }, }, }, @@ -29,7 +29,7 @@ "postcss": "8.5.26", }, "packages": { - "@oxc-project/types": ["@oxc-project/types@0.143.0", "", {}, "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA=="], + "@oxc-project/types": ["@oxc-project/types@0.154.0", "", {}, "sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA=="], "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.78.0", "", { "os": "android", "cpu": "arm" }, "sha512-Bu819lmAfZMUHErrpe0cEWj3iaefuUODHSU8+UbXy67V/r7/7f4K3FL0NmbD85E+wiFLDYuhP8Zlv0XnVeXshw=="], @@ -69,33 +69,35 @@ "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.78.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Sb5ocmLSuYeOuXd+CFOToGKp/gjXUEWDnvIGwhnh8aq8wY4TMmEnKnvbogSW7RdMZv77JSARduS7/gv+khYEjA=="], - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.3", "", { "os": "android", "cpu": "arm64" }, "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw=="], + "@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.14", "", { "os": "android", "cpu": "arm" }, "sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg=="], - "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.14", "", { "os": "android", "cpu": "arm64" }, "sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ=="], - "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA=="], + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw=="], - "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg=="], + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA=="], - "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.3", "", { "os": "linux", "cpu": "arm" }, "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw=="], + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.14", "", { "os": "freebsd", "cpu": "x64" }, "sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA=="], - "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw=="], + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.14", "", { "os": "linux", "cpu": "arm" }, "sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg=="], - "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q=="], + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ=="], - "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w=="], + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg=="], - "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg=="], + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.14", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q=="], - "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w=="], + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.14", "", { "os": "linux", "cpu": "s390x" }, "sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A=="], - "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A=="], + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.14", "", { "os": "linux", "cpu": "x64" }, "sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg=="], - "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.3", "", { "os": "none", "cpu": "arm64" }, "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug=="], + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.14", "", { "os": "linux", "cpu": "x64" }, "sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg=="], - "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw=="], + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.14", "", { "os": "none", "cpu": "arm64" }, "sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q=="], - "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.3", "", { "os": "win32", "cpu": "x64" }, "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg=="], + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.14", "", { "os": "win32", "cpu": "x64" }, "sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ=="], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], @@ -131,7 +133,7 @@ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - "happy-dom": ["happy-dom@20.11.2", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-7MB+bJLkxu3SowAfBJbjW+c55kNz5tkR45gu2qzrxznezhLeN5YIlJbwUgSzlGc+qWoZ8Ykg71H5ezz69xixrw=="], + "happy-dom": ["happy-dom@21.0.4", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-tFm+GaLBgo6o0awHnvHaNoraVo27qJ+2tZEtZ+cIa08hXbeitrxAJajLI05hsUy/2/YTKcUjQYzHe5mCcraLBw=="], "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], @@ -173,7 +175,7 @@ "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], - "rolldown": ["rolldown@1.2.3", "", { "dependencies": { "@oxc-project/types": "=0.143.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.3", "@rolldown/binding-darwin-arm64": "1.2.3", "@rolldown/binding-darwin-x64": "1.2.3", "@rolldown/binding-freebsd-x64": "1.2.3", "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", "@rolldown/binding-linux-arm64-gnu": "1.2.3", "@rolldown/binding-linux-arm64-musl": "1.2.3", "@rolldown/binding-linux-ppc64-gnu": "1.2.3", "@rolldown/binding-linux-s390x-gnu": "1.2.3", "@rolldown/binding-linux-x64-gnu": "1.2.3", "@rolldown/binding-linux-x64-musl": "1.2.3", "@rolldown/binding-openharmony-arm64": "1.2.3", "@rolldown/binding-win32-arm64-msvc": "1.2.3", "@rolldown/binding-win32-x64-msvc": "1.2.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A=="], + "rolldown": ["rolldown@1.2.14", "", { "dependencies": { "@oxc-project/types": "=0.154.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.14", "@rolldown/binding-android-arm64": "1.2.14", "@rolldown/binding-darwin-arm64": "1.2.14", "@rolldown/binding-darwin-x64": "1.2.14", "@rolldown/binding-freebsd-x64": "1.2.14", "@rolldown/binding-linux-arm-gnueabihf": "1.2.14", "@rolldown/binding-linux-arm64-gnu": "1.2.14", "@rolldown/binding-linux-arm64-musl": "1.2.14", "@rolldown/binding-linux-ppc64-gnu": "1.2.14", "@rolldown/binding-linux-s390x-gnu": "1.2.14", "@rolldown/binding-linux-x64-gnu": "1.2.14", "@rolldown/binding-linux-x64-musl": "1.2.14", "@rolldown/binding-openharmony-arm64": "1.2.14", "@rolldown/binding-win32-arm64-msvc": "1.2.14", "@rolldown/binding-win32-x64-msvc": "1.2.14" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw=="], "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], @@ -185,7 +187,7 @@ "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - "vite": ["vite@8.2.1", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.25", "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw=="], + "vite": ["vite@8.2.4", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.26", "rolldown": "~1.2.4", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0 || ^0.5.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-lfauXnrf2x0p+UoA+wXh0dgFWnUjCyG6Js95KBkgaCwUU9k0DAHI4W6Q0WCx+h+5g+Alce1y4Zi2QjjK0UvGBw=="], "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], diff --git a/gui/package.json b/gui/package.json index 0300db693ee..045ed340086 100644 --- a/gui/package.json +++ b/gui/package.json @@ -27,11 +27,11 @@ "@types/react": "^19.2.18", "@types/react-dom": "^19.2.4", "@vitejs/plugin-react": "^6.0.5", - "happy-dom": "20.11.2", + "happy-dom": "21.0.4", "oxlint": "1.78.0", "react-refresh": "^0.18.0", "typescript": "~6.0.2", - "vite": "^8.2.1" + "vite": "^8.2.4" }, "overrides": { "brace-expansion": "5.0.9", diff --git a/gui/public/provider-icons/README.md b/gui/public/provider-icons/README.md index 0e595d7b4f7..334f3159569 100644 --- a/gui/public/provider-icons/README.md +++ b/gui/public/provider-icons/README.md @@ -402,3 +402,12 @@ committing it. (1.75:1, inside the 2.5 lockup limit), pure geometry (`rect`/`path`), drawn in `currentColor`, so it is **masked**: as an image `currentColor` resolves to black and vanishes on the dark tile. The site favicon `https://opengateway.ai/icon.svg` was rejected: its "OG" is a `` glyph. + +## WorkBuddy AI (2026-10-22) + +- `workbuddy-color.svg` — unchanged SVG from the international WorkBuddy site's + declared favicon (`https://www.workbuddy.ai/`): + `https://download.codebuddy.ai/web/workbuddy/2365138d79c29bb1cbce4644f5cbbffd2b561a5c/assets/logo.svg`. + The 40×40 artwork retains its green background, gradients and white mark; + render it as an image in either theme. Used to identify the `workbuddy-ai` + provider, with the same asset embedded by the native tray. diff --git a/gui/public/provider-icons/workbuddy-color.svg b/gui/public/provider-icons/workbuddy-color.svg new file mode 100644 index 00000000000..dffdecbd0c7 --- /dev/null +++ b/gui/public/provider-icons/workbuddy-color.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/src/App.tsx b/gui/src/App.tsx index 99fa327d6b0..be51cc7f315 100644 --- a/gui/src/App.tsx +++ b/gui/src/App.tsx @@ -609,7 +609,7 @@ export default function App() { setSharedSessionEpoch(epoch => epoch + 1); }} /> )} - {page === "dashboard" && } {page === "startup" && } {page === "providers" && } diff --git a/gui/src/account-pause-display.ts b/gui/src/account-pause-display.ts new file mode 100644 index 00000000000..3a656bfe89d --- /dev/null +++ b/gui/src/account-pause-display.ts @@ -0,0 +1,20 @@ +import type { AccountPauseReason } from "../../src/types/account-auto-pause"; +import type { Locale, TFn, TKey } from "./i18n/shared"; + +const REASON_KEYS = { + oauth_organization_disabled: "accountPause.reason.organizationDisabled", + account_permission_denied: "accountPause.reason.permissionDenied", + subscription_inactive: "accountPause.reason.subscriptionInactive", + subscription_expired: "accountPause.reason.subscriptionExpired", + plan_downgraded: "accountPause.reason.planDowngraded", +} satisfies Record; + +/** Display fixed reason labels and timestamps in the selected dashboard language. */ +export function formatAccountPauseTooltip( + t: TFn, locale: Locale, details: { reason: string; pausedAt: number }, +): string { + const reason = Object.hasOwn(REASON_KEYS, details.reason) + ? t(REASON_KEYS[details.reason as AccountPauseReason]) : t("codexAuth.paused"); + const date = new Date(details.pausedAt); + return Number.isNaN(date.getTime()) ? reason : t("accountPause.tooltip", { reason, date: date.toLocaleString(locale) }); +} diff --git a/gui/src/advisor-models.ts b/gui/src/advisor-models.ts new file mode 100644 index 00000000000..7ff4ca2f2e8 --- /dev/null +++ b/gui/src/advisor-models.ts @@ -0,0 +1,11 @@ +export interface AdvisorModelRow { + namespaced: string; + disabled?: boolean; + initialSelectionPending?: boolean; +} + +/** Keep routing selectors opaque: labels and model display names are not model IDs. */ +export function advisorModelValues(rows: readonly AdvisorModelRow[]): string[] { + return [...new Set(rows.filter(row => !row.disabled && !row.initialSelectionPending) + .map(row => row.namespaced))]; +} diff --git a/gui/src/app-routing.ts b/gui/src/app-routing.ts index 195504dd223..ce6a22fab16 100644 --- a/gui/src/app-routing.ts +++ b/gui/src/app-routing.ts @@ -131,6 +131,7 @@ export const INTEGRATION_TAB_HASHES = [ "integrations/cursor", "integrations/opencode", "integrations/pi", + "integrations/gentle-shell", "integrations/omp", "integrations/hermes", "integrations/openclaw", @@ -148,6 +149,9 @@ export const INTEGRATION_TAB_HASHES = [ "integrations/kilo", "integrations/droid", "integrations/qoder", + "integrations/qoder-cn", + "integrations/workbuddy", + "integrations/workbuddy-ai", ] as const; /** diff --git a/gui/src/codex-quota-utils.ts b/gui/src/codex-quota-utils.ts index 57feab153f0..191f61ae866 100644 --- a/gui/src/codex-quota-utils.ts +++ b/gui/src/codex-quota-utils.ts @@ -1,6 +1,7 @@ import { isTerminalShortWindow } from "../../src/codex/quota-types"; export interface AccountQuota { + workbuddyCredits?: import("../../src/providers/workbuddy-credit-balance").WorkBuddyCreditBalance; weeklyPercent?: number; fiveHourPercent?: number; /** Codex account API aliases for the same five-hour window. */ @@ -13,7 +14,7 @@ export interface AccountQuota { shortObservedAt?: number; shortWindowSeconds?: number; monthlyResetAt?: number; - customWindows?: { label: string; percent: number; resetAt?: number }[]; + customWindows?: { label: string; percent: number; resetAt?: number; modelId?: string }[]; creditsUsd?: { used: number; limit: number; @@ -52,6 +53,7 @@ export function normalizeQuotaForPlan(quota: AccountQuota | null, plan: string | return { ...(normalized.monthlyPercent !== undefined ? { monthlyPercent: normalized.monthlyPercent } : {}), ...(normalized.monthlyResetAt !== undefined ? { monthlyResetAt: normalized.monthlyResetAt } : {}), + ...(normalized.workbuddyCredits !== undefined ? { workbuddyCredits: normalized.workbuddyCredits } : {}), ...(normalized.creditsUsd !== undefined ? { creditsUsd: normalized.creditsUsd } : {}), ...(normalized.resetCredits !== undefined ? { resetCredits: normalized.resetCredits } : {}), updatedAt: normalized.updatedAt, diff --git a/gui/src/combo-capabilities.ts b/gui/src/combo-capabilities.ts index 32f91f66b98..587a4d631e5 100644 --- a/gui/src/combo-capabilities.ts +++ b/gui/src/combo-capabilities.ts @@ -1,14 +1,74 @@ import type { ComboTarget } from "./combo-workspace-data"; import type { ModelOption } from "./components/combo-workspace-types"; -/** Whether every selected target advertises image input (incomplete rows fail closed). */ +type ComboImageMemberKind = "vision" | "sidecar" | "blocked" | "missing"; + +/** + * One combo member's image story. Classification must survive a reload, so it + * reads `visionSidecarConsumer` — the runtime predicate's verdict served on the + * /api/models row — before falling back to the DECLARED modalities + * (`inputModalitiesDeclared`). A model covered only by legacy `noVisionModels` + * has no declaration and its advertised modalities are widened to image, so the + * flag is what keeps it a sidecar member after reload. Rows with no known + * modalities, or modalities without text, cannot be covered and block the combo. + */ +function imageMemberKind(target: ComboTarget, models: ModelOption[]): ComboImageMemberKind { + const provider = target.provider.trim(); + const modelId = target.model.trim(); + if (!provider || !modelId) return "missing"; + const model = models.find((row) => row.provider === provider && row.id === modelId); + if (!model) return "missing"; + if (model.visionSidecarConsumer === true) return "sidecar"; + const declared = model.inputModalitiesDeclared ?? model.inputModalities; + if (!declared || declared.length === 0) return "blocked"; + if (declared.includes("image")) return "vision"; + return declared.includes("text") ? "sidecar" : "blocked"; +} + +/** Whether images can be enabled: every target is known and either images natively or can be declared text-only. */ export function comboImagesSupported(targets: ComboTarget[], models: ModelOption[]): boolean { if (targets.length === 0) return false; return targets.every((target) => { - const provider = target.provider.trim(); - const modelId = target.model.trim(); - if (!provider || !modelId) return false; - const model = models.find((row) => row.provider === provider && row.id === modelId); - return !!model?.inputModalities?.includes("image"); + const kind = imageMemberKind(target, models); + return kind === "vision" || kind === "sidecar"; }); } + +/** + * Exact targets that need a text-only declaration so the Vision Sidecar covers + * them when the combo accepts images. Deduplicated in submission order. Empty + * while any member blocks images: the switch is forced off then, so a save must + * not declare the remaining members text-only behind the operator's back. + */ +export function comboVisionSidecarTargets( + targets: ComboTarget[], + models: ModelOption[], +): Array<{ provider: string; model: string }> { + if (!comboImagesSupported(targets, models)) return []; + const seen = new Set(); + const out: Array<{ provider: string; model: string }> = []; + for (const target of targets) { + if (imageMemberKind(target, models) !== "sidecar") continue; + const provider = target.provider.trim(); + const model = target.model.trim(); + const key = `${provider}/${model}`; + if (seen.has(key)) continue; + seen.add(key); + out.push({ provider, model }); + } + return out; +} + +/** + * Exact targets that cannot be covered by the Vision Sidecar: their row is + * known but its modalities are unknown or have no text input. Named in the + * hint so the operator knows which member blocks the image switch. + */ +export function comboImageBlockedTargets( + targets: ComboTarget[], + models: ModelOption[], +): Array<{ provider: string; model: string }> { + return targets + .filter((target) => imageMemberKind(target, models) === "blocked") + .map((target) => ({ provider: target.provider.trim(), model: target.model.trim() })); +} diff --git a/gui/src/combo-workspace-data.ts b/gui/src/combo-workspace-data.ts index f579a6f2b28..84bbc12198c 100644 --- a/gui/src/combo-workspace-data.ts +++ b/gui/src/combo-workspace-data.ts @@ -5,6 +5,7 @@ import { SUPPORTED_NATIVE_OPENAI_SLUGS } from "../../src/codex/catalog/native-models"; import { PROVIDER_QUOTA_MAX_AGE_MS } from "../../src/providers/quota-types"; +import { normalizeJevSessionPin, type JevSessionPinConfig } from "../../src/combos/jev-session-pin-config"; import type { TKey } from "./i18n/shared"; import { CANONICAL_JEV_DECISION_PROVIDER, @@ -171,6 +172,8 @@ export interface ComboItem { decisionModel?: string | null; /** `jev` only: decision deadline in ms; null/omitted = the server default. */ decisionTimeoutMs?: number | null; + /** `jev` only: undefined preserves an omitted setting; null explicitly turns the pin off. */ + sessionPin?: JevSessionPinConfig | null; targets: ComboTarget[]; } @@ -300,6 +303,7 @@ export function parseComboList(payload: unknown): ComboItem[] { const decisionProvider = normalizeDecisionProvider(r.decisionProvider); const decisionModel = normalizeAlias(r.decisionModel); const decisionTimeoutMs = normalizeDecisionTimeoutMs(r.decisionTimeoutMs); + const sessionPin = r.sessionPin === null ? null : normalizeJevSessionPin(r.sessionPin); out.push({ id, model: typeof r.model === "string" && r.model.trim() @@ -317,6 +321,7 @@ export function parseComboList(payload: unknown): ComboItem[] { ...(decisionProvider !== null ? { decisionProvider } : {}), ...(decisionModel !== null ? { decisionModel } : {}), ...(decisionTimeoutMs !== null ? { decisionTimeoutMs } : {}), + ...(sessionPin !== undefined ? { sessionPin } : {}), targets, }); } @@ -491,6 +496,8 @@ export function draftEquals(a: ComboItem, b: ComboItem): boolean { (a.decisionProvider ?? null) !== (b.decisionProvider ?? null) || (a.decisionModel ?? null) !== (b.decisionModel ?? null) || (a.decisionTimeoutMs ?? null) !== (b.decisionTimeoutMs ?? null) + || (a.sessionPin === undefined) !== (b.sessionPin === undefined) + || (a.sessionPin?.ttlMs ?? null) !== (b.sessionPin?.ttlMs ?? null) )) ) return false; if (a.targets.length !== b.targets.length) return false; @@ -504,9 +511,13 @@ export function draftEquals(a: ComboItem, b: ComboItem): boolean { }); } -export function toPutBody(item: ComboItem, options: { renameFrom?: string } = {}): { +export function toPutBody( + item: ComboItem, + options: { renameFrom?: string; visionSidecarTargets?: Array<{ provider: string; model: string }> } = {}, +): { id: string; renameFrom?: string; + visionSidecarTargets?: Array<{ provider: string; model: string }>; combo: { targets: ComboTarget[]; strategy: ComboStrategy; @@ -520,12 +531,14 @@ export function toPutBody(item: ComboItem, options: { renameFrom?: string } = {} decisionProvider?: string | null; decisionModel?: string | null; decisionTimeoutMs?: number | null; + sessionPin?: JevSessionPinConfig | null; }; } { const weighted = item.strategy === "round-robin" || item.strategy === "random"; return { id: item.id.trim(), ...(options.renameFrom ? { renameFrom: options.renameFrom } : {}), + ...(options.visionSidecarTargets?.length ? { visionSidecarTargets: options.visionSidecarTargets } : {}), combo: { targets: item.targets.map((target) => ({ provider: target.provider.trim(), @@ -558,6 +571,7 @@ export function toPutBody(item: ComboItem, options: { renameFrom?: string } = {} decisionProvider: item.decisionModel?.trim() ? null : normalizeDecisionProvider(item.decisionProvider), decisionModel: item.decisionModel?.trim() || null, decisionTimeoutMs: item.decisionTimeoutMs ?? null, + ...(item.sessionPin !== undefined ? { sessionPin: item.sessionPin } : {}), } : {}), }, @@ -586,6 +600,7 @@ export type ComboDraftError = | "invalidReasoningEfforts" | "invalidModelProfile" | "invalidDecisionTimeout" + | "invalidSessionPin" | "invalidDecisionProvider" | "invalidDecisionModel" | "noEnabledTarget"; @@ -669,6 +684,9 @@ export function validateComboDraft( } } + if (item.strategy === "jev" && item.sessionPin != null && !normalizeJevSessionPin(item.sessionPin)) { + return "invalidSessionPin"; + } if (item.strategy === "jev" && item.decisionTimeoutMs != null && (!Number.isInteger(item.decisionTimeoutMs) || item.decisionTimeoutMs < JEV_DECISION_TIMEOUT_MIN_MS diff --git a/gui/src/components/AdvisorModelSelect.tsx b/gui/src/components/AdvisorModelSelect.tsx new file mode 100644 index 00000000000..75d7a32f635 --- /dev/null +++ b/gui/src/components/AdvisorModelSelect.tsx @@ -0,0 +1,59 @@ +import { useDataSurface } from "../data-surface"; +import { useT } from "../i18n/shared"; +import { Notice, Select } from "../ui"; +import { advisorModelValues, type AdvisorModelRow } from "../advisor-models"; + +export function AdvisorModelSelect({ apiBase, value, disabled, onChange }: { + apiBase: string; + value: string; + disabled: boolean; + onChange: (value: string) => void; +}) { + const t = useT(); + const resource = useDataSurface( + `advisor-models:${apiBase}`, + [apiBase], + async signal => { + const response = await fetch(`${apiBase}/api/models`, { signal }); + if (!response.ok) throw new Error(String(response.status)); + const rows: unknown = await response.json(); + if (!Array.isArray(rows) || !rows.every(row => row && typeof row.namespaced === "string" && row.namespaced.length > 0)) { + throw new Error("Invalid model catalog"); + } + return rows as AdvisorModelRow[]; + }, + { isEmpty: rows => rows.length === 0 }, + ); + const { state } = resource; + const values = advisorModelValues(state.data ?? []); + const unavailable = value !== "" && !values.includes(value); + const options = [ + { value: "", label: t("advisor.modelClear") }, + ...(unavailable ? [{ value, label: value }] : []), + ...values.map(model => ({ value: model, label: model })), + ]; + return ( +
+ model.namespaced)] + .filter((value): value is string => Boolean(value)))] + .map(value => ({ value, label: formatNamespacedModelId(value, t) }))} + onChange={changeFallbackModel} + disabled={fallbackSaving || loadError} + label={t("codexAuth.mainHardLockFallbackModel")} + align="right" /> + { - if (locked || requestRef.current) return; - const value = event.target.value; - setDraft(current => ({ - ...current, - cacheRead: current.cacheRead || "0", cacheWrite: current.cacheWrite || "0", - [field]: value, - })); - setErrorKey(null); - }} /> - - ))} +
+ {RATE_FIELDS.map(field => ( +
+ + { + if (node) customFieldRefs.current[field] = node; + else delete customFieldRefs.current[field]; + if (field === "input") inputRef.current = node; + }} id={`${id}-${field}`} + className="input" type="number" min={0} max={1_000_000} step="any" inputMode="decimal" + value={draft[field]} disabled={locked} + required={field === "input" || field === "output"} + aria-describedby={`${helpId}${errorKey ? ` ${errorId}` : ""}`} + aria-invalid={invalidField === field ? true : undefined} + onChange={event => { + if (locked || requestRef.current) return; + const value = event.target.value; + setDraft(current => ({ + ...current, + cacheRead: current.cacheRead || "0", + cacheWrite: current.cacheWrite || "0", + [field]: value, + })); + setErrorKey(null); + setInvalidField(null); + }} /> +
+ ))} +
+
+ {t("pricing.override.promptTitle")} + {(["automatic", "flat", "custom"] as const).map(mode => )} +
+ {pricingMode === "custom" &&
+

{t("pricing.override.bandTitle")}

+
+
+ + { if (node) customFieldRefs.current.threshold = node; else delete customFieldRefs.current.threshold; }} id={`${id}-threshold`} className="input" type="number" min={1} step={1} inputMode="numeric" + value={customDraft.threshold} disabled={locked} aria-describedby={`${helpId}${errorKey ? ` ${errorId}` : ""}`} + aria-invalid={invalidField === "threshold" ? true : undefined} + onChange={event => { setCustomDraft(current => ({ ...current, threshold: event.target.value })); setErrorKey(null); setInvalidField(null); }} /> +
+
+ + { const key = CUSTOM_FIELD[field]; if (node) customFieldRefs.current[key] = node; else delete customFieldRefs.current[key]; }} + id={`${id}-custom-${field}`} className="input" type="number" min={0} max={1_000_000} step="any" inputMode="decimal" + value={customDraft[field]} disabled={locked} required={field === "input" || field === "output"} + aria-describedby={`${helpId}${errorKey ? ` ${errorId}` : ""}`} + aria-invalid={invalidField === CUSTOM_FIELD[field] ? true : undefined} + onChange={event => { const value = event.target.value; setCustomDraft(current => ({ ...current, [field]: value })); setErrorKey(null); setInvalidField(null); }} /> +
+ ))} +
+
} +
{recovered &&

{t("pricing.override.recovered")}

} - {errorKey && } + {errorKey && }
diff --git a/gui/src/components/NativeReasoningRetentionPanel.tsx b/gui/src/components/NativeReasoningRetentionPanel.tsx new file mode 100644 index 00000000000..7729c050394 --- /dev/null +++ b/gui/src/components/NativeReasoningRetentionPanel.tsx @@ -0,0 +1,102 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import { useT } from "../i18n/shared"; +import { Switch } from "../ui"; +import { createBoundedFetch } from "../bounded-fetch"; +import { requireJson } from "../pages/dashboard-shared"; + +interface Settings { modelSwitch: boolean; accountSwitch: boolean } +function readSettings(payload: unknown): Settings { + if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("Invalid reasoning settings"); + const value = payload as Record; + if (typeof value.modelSwitch !== "boolean" || typeof value.accountSwitch !== "boolean") { + throw new Error("Missing reasoning settings"); + } + return { modelSwitch: value.modelSwitch, accountSwitch: value.accountSwitch }; +} + +export default function NativeReasoningRetentionPanel({ apiBase }: { apiBase: string }) { + return ; +} + +function NativeReasoningRetentionControls({ apiBase }: { apiBase: string }) { + const t = useT(); + const [saved, setSaved] = useState(); + const [modelSwitch, setModelSwitch] = useState(false); + const [accountSwitch, setAccountSwitch] = useState(false); + const [busy, setBusy] = useState(false); + const [loadError, setLoadError] = useState(false); + const [feedback, setFeedback] = useState<"saved" | "failed" | null>(null); + const active = useRef(false); + const pending = useRef | null>(null); + const accept = useCallback((value: Settings) => { + setSaved(value); setModelSwitch(value.modelSwitch); setAccountSwitch(value.accountSwitch); + }, []); + const load = useCallback(async () => { + if (pending.current) return; + const request = createBoundedFetch(15_000); + pending.current = request; setLoadError(false); + try { + const response = await fetch(`${apiBase}/api/native-reasoning-retention`, { signal: request.signal }); + const value = readSettings(await requireJson(response)); + if (active.current && pending.current === request) accept(value); + } catch { + if (active.current && pending.current === request) setLoadError(true); + } finally { + request.clear(); + if (pending.current === request) pending.current = null; + } + }, [apiBase, accept]); + useEffect(() => { + active.current = true; + const timer = window.setTimeout(() => { void load(); }, 0); + return () => { + window.clearTimeout(timer); active.current = false; + pending.current?.controller.abort(); pending.current?.clear(); pending.current = null; + }; + }, [load]); + const save = async (reset = false) => { + if (pending.current || saved === undefined) return; + const request = createBoundedFetch(15_000); + pending.current = request; setBusy(true); setFeedback(null); + try { + const response = await fetch(`${apiBase}/api/native-reasoning-retention`, { + method: "PUT", headers: { "Content-Type": "application/json" }, signal: request.signal, + body: JSON.stringify(reset ? null : { modelSwitch, accountSwitch }), + }); + const value = readSettings(await requireJson(response)); + if (active.current && pending.current === request) { accept(value); setFeedback("saved"); } + } catch { + if (active.current && pending.current === request) setFeedback("failed"); + } finally { + request.clear(); + if (active.current && pending.current === request) setBusy(false); + if (pending.current === request) pending.current = null; + } + }; + const disabled = busy || saved === undefined || loadError; + const changed = modelSwitch !== saved?.modelSwitch || accountSwitch !== saved?.accountSwitch; + return
+
{t("nativeReasoningRetention.title")}
+

{t("nativeReasoningRetention.description")}

+
+
+ {t("nativeReasoningRetention.modelSwitch")} + { setModelSwitch(value => !value); setFeedback(null); }} /> +
+
+ {t("nativeReasoningRetention.accountSwitch")} + { setAccountSwitch(value => !value); setFeedback(null); }} /> +
+

{t("nativeReasoningRetention.accountHint")}

+
+ + +
+
+ {loadError &&
{t("nativeReasoningRetention.loadFailed")}
} + {feedback === "failed" &&
{t("nativeReasoningRetention.saveFailed")}
} + {feedback === "saved" &&
{t("nativeReasoningRetention.saved")}
} +
; +} diff --git a/gui/src/components/QuotaBars.tsx b/gui/src/components/QuotaBars.tsx index fc72b0bb5f3..5763eb87899 100644 --- a/gui/src/components/QuotaBars.tsx +++ b/gui/src/components/QuotaBars.tsx @@ -9,6 +9,7 @@ import { type AccountQuota, normalizeQuotaForPlan } from "../codex-quota-utils"; export type QuotaWindowKey = "fiveHour" | "weekly" | "monthly"; export type QuotaBarRow = { + id: string; windowKey?: QuotaWindowKey; customLabel?: string; label: string; @@ -69,7 +70,7 @@ export function buildQuotaRows(quota: AccountQuota | null, plan: string | null | const displayQuota = normalizeQuotaForPlan(quota, plan); if (!displayQuota) return []; // Intrinsic ranks for the standard slots; custom windows rank on their RAW labels. - const ranked: Array<{ rank: number; row: QuotaBarRow }> = []; + const ranked: Array<{ rank: number; row: Omit & { id?: string } }> = []; if (typeof displayQuota.fiveHourPercent === "number") { ranked.push({ rank: 0, @@ -112,6 +113,7 @@ export function buildQuotaRows(quota: AccountQuota | null, plan: string | null | ranked.push({ rank: rawCustomWindowRank(customLabel), row: { + id: JSON.stringify([w.modelId ?? null, w.label, w.resetAt ?? null]), customLabel, label: localized, limitLabel: localized, @@ -122,7 +124,7 @@ export function buildQuotaRows(quota: AccountQuota | null, plan: string | null | } if (displayQuota.creditsUsd && typeof displayQuota.creditsUsd.percent === "number") { const hasSubscriptionCreditsCustom = displayQuota.customWindows?.some( - w => canonicalCustomWindowLabel(w.label) === SUBSCRIPTION_CREDITS_LABEL, + w => w.modelId === undefined && canonicalCustomWindowLabel(w.label) === SUBSCRIPTION_CREDITS_LABEL, ); if (!hasSubscriptionCreditsCustom) { const localized = localizeCustomQuotaLabel(SUBSCRIPTION_CREDITS_LABEL, t); @@ -138,7 +140,13 @@ export function buildQuotaRows(quota: AccountQuota | null, plan: string | null | }); } } - return ranked.sort((a, b) => a.rank - b.rank).map(entry => entry.row); + const identities = new Map(); + return ranked.sort((a, b) => a.rank - b.rank).map(({ row }) => { + const identity = row.id ?? row.windowKey ?? row.customLabel!; + const ordinal = identities.get(identity) ?? 0; + identities.set(identity, ordinal + 1); + return { ...row, id: JSON.stringify([identity, ordinal]) }; + }); } /** Max utilisation across known windows (for sorting providers by urgency). */ @@ -147,10 +155,11 @@ export function maxQuotaUtilisation(quota: AccountQuota | null): number { const vals = [quota.fiveHourPercent, quota.weeklyPercent, quota.monthlyPercent] .filter((n): n is number => typeof n === "number"); for (const w of quota.customWindows ?? []) { + if (w.modelId !== undefined) continue; if (typeof w.percent === "number") vals.push(w.percent); } const hasSubscriptionCreditsCustom = quota.customWindows?.some( - w => canonicalCustomWindowLabel(w.label) === SUBSCRIPTION_CREDITS_LABEL, + w => w.modelId === undefined && canonicalCustomWindowLabel(w.label) === SUBSCRIPTION_CREDITS_LABEL, ); if (!hasSubscriptionCreditsCustom && typeof quota.creditsUsd?.percent === "number") { vals.push(quota.creditsUsd.percent); @@ -329,7 +338,7 @@ export default function QuotaBars({ {observedLine} {rows.map(row => ( {observedLine} {rows.map(row => ( - + + {t("reasoningRetention.advanced")} + + ; +} +function ReasoningRetentionControls({ apiBase }: { apiBase: string }) { + const t = useT(); + const [saved, setSaved] = useState(); + const [enabled, setEnabled] = useState(false); + const [percent, setPercent] = useState("20"); + const [tokens, setTokens] = useState("100000"); + const [busy, setBusy] = useState(false); + const [loadError, setLoadError] = useState(false); + const [feedback, setFeedback] = useState<"saved" | "failed" | null>(null); + const active = useRef(false); + const pending = useRef | null>(null); + const accept = useCallback((value: Setting) => { + setSaved(value); + setEnabled(value?.enabled === true); + setPercent(String(value?.maxContextPercent ?? 20)); + setTokens(String(value?.maxTokens ?? 100000)); + }, []); + const load = useCallback(async () => { + if (pending.current) return; + const request = createBoundedFetch(15_000); + pending.current = request; + setLoadError(false); + try { + const response = await fetch(`${apiBase}/api/settings`, { signal: request.signal }); + const value = readSetting(await requireJson(response)); + if (active.current && pending.current === request) accept(value); + } catch { + if (active.current && pending.current === request) setLoadError(true); + } finally { + request.clear(); + if (pending.current === request) pending.current = null; + } + }, [apiBase, accept]); + useEffect(() => { + active.current = true; + const timer = window.setTimeout(() => { void load(); }, 0); + return () => { + window.clearTimeout(timer); + active.current = false; + pending.current?.controller.abort(); + pending.current?.clear(); + pending.current = null; + }; + }, [load]); + const validPercent = percent.trim() !== "" && Number.isFinite(Number(percent)) && Number(percent) > 0 && Number(percent) <= 100; + const validTokens = tokens.trim() !== "" && Number.isSafeInteger(Number(tokens)) && Number(tokens) > 0; + const valid = !enabled || (validPercent && validTokens); + const changed = enabled !== (saved?.enabled === true) || (enabled && ( + Number(percent) !== (saved?.maxContextPercent ?? 20) || Number(tokens) !== (saved?.maxTokens ?? 100000) + )); + const save = async (reset = false) => { + if (pending.current || saved === undefined || (!reset && !valid)) return; + const request = createBoundedFetch(15_000); + pending.current = request; + setBusy(true); setFeedback(null); + try { + const response = await fetch(`${apiBase}/api/settings`, { + method: "PUT", headers: { "Content-Type": "application/json" }, signal: request.signal, + body: JSON.stringify({ reasoningRetention: reset ? null : enabled + ? { enabled: true, maxContextPercent: Number(percent), maxTokens: Number(tokens) } + : { enabled: false } }), + }); + const value = readSetting(await requireJson(response)); + if (active.current && pending.current === request) { accept(value); setFeedback("saved"); } + } catch { + if (active.current && pending.current === request) setFeedback("failed"); + } finally { + request.clear(); + if (active.current && pending.current === request) setBusy(false); + if (pending.current === request) pending.current = null; + } + }; + const disabled = busy || saved === undefined || loadError; + return
+
{t("reasoningRetention.title")}
+

{t("reasoningRetention.description")}

+

{t("reasoningRetention.storageHint")}

+ { setEnabled(value => !value); setFeedback(null); }} /> +
+ {enabled && <> + } + + +
+ {!valid && } + {loadError &&
{t("reasoningRetention.loadFailed")}
} + {feedback === "failed" &&
{t("reasoningRetention.saveFailed")}
} + {feedback === "saved" &&
{t("reasoningRetention.saved")}
} +
; +} diff --git a/gui/src/components/apikeys-workspace/client-config-clients.ts b/gui/src/components/apikeys-workspace/client-config-clients.ts index 11b0d35b26e..64703e980a8 100644 --- a/gui/src/components/apikeys-workspace/client-config-clients.ts +++ b/gui/src/components/apikeys-workspace/client-config-clients.ts @@ -8,13 +8,17 @@ * with EXPORT_CLIENT_IDS by hand; adding a client server-side renders no row * until this tuple changes. */ -export const CLIENTS = ["qoder", "opencode", "pi", "omp", "hermes", "openclaw", "kimi", "gajae", "dsh", "mcode", "zcode", "commandcode", "prime", "aside", "raycast", "omo", "cline", "kilo", "droid"] as const; +export const CLIENTS = ["qoder", "qoder-cn", "workbuddy", "workbuddy-ai", "opencode", "pi", "omp", "hermes", "openclaw", "kimi", "gajae", "dsh", "mcode", "zcode", "commandcode", "prime", "aside", "raycast", "omo", "cline", "kilo", "droid", "gentle-shell"] as const; export type ExportClientId = (typeof CLIENTS)[number]; export const CLIENT_LABEL_KEYS = { qoder: "api.clientConfig.clientQoder", + "qoder-cn": "integrations.tab.qoderCn", + "workbuddy": "integrations.tab.workbuddy", + "workbuddy-ai": "integrations.tab.workbuddyAi", opencode: "api.clientConfig.clientOpencode", pi: "api.clientConfig.clientPi", + "gentle-shell": "integrations.tab.gentleShell", omp: "api.clientConfig.clientOmp", hermes: "api.clientConfig.clientHermes", openclaw: "api.clientConfig.clientOpenclaw", @@ -66,6 +70,7 @@ export const CLIENT_LABEL_KEYS = { */ export const CLIENT_MARKS: Partial> = { qoder: "/provider-icons/qoder.svg", + "qoder-cn": "/provider-icons/qoder.svg", opencode: "/provider-icons/opencode.svg", pi: "/provider-icons/pi.svg", omp: "/provider-icons/oh-my-pi.svg", diff --git a/gui/src/components/codex-account-pool-cards.tsx b/gui/src/components/codex-account-pool-cards.tsx index e607afbf1bf..d6d015dff1b 100644 --- a/gui/src/components/codex-account-pool-cards.tsx +++ b/gui/src/components/codex-account-pool-cards.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { formatAccountPauseTooltip } from "../account-pause-display"; import { useT, useI18n } from "../i18n/shared"; import { useCopyFeedback } from "./use-copy-feedback"; import { IconAlert, IconPause, IconPlay, IconX } from "../icons"; @@ -118,7 +119,7 @@ export function CodexAccountPoolCards({ )} {a.paused && ( - + {t("codexAuth.paused")} )} diff --git a/gui/src/components/combo-workspace-add-modal.tsx b/gui/src/components/combo-workspace-add-modal.tsx index 6ae3c6c3363..a085e5e56e8 100644 --- a/gui/src/components/combo-workspace-add-modal.tsx +++ b/gui/src/components/combo-workspace-add-modal.tsx @@ -204,6 +204,7 @@ export function AddComboModal({ decisionProvider={draft.decisionProvider ?? null} decisionModel={draft.decisionModel ?? null} decisionTimeoutMs={draft.decisionTimeoutMs ?? null} + sessionPin={draft.sessionPin} disabled={busy} onChange={(patch) => setDraft((d) => ({ ...d, ...patch }))} /> diff --git a/gui/src/components/combo-workspace-controls.tsx b/gui/src/components/combo-workspace-controls.tsx index 50f8cfb4dc7..d9ad1f6b337 100644 --- a/gui/src/components/combo-workspace-controls.tsx +++ b/gui/src/components/combo-workspace-controls.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import type { ComboEffort, ComboStrategy, ComboTarget, ProviderQuotaStates } from "../combo-workspace-data"; -import { comboImagesSupported } from "../combo-capabilities"; +import { comboImageBlockedTargets, comboImagesSupported, comboVisionSidecarTargets } from "../combo-capabilities"; import { COMBO_EFFORTS, COMBO_STRATEGIES, @@ -8,6 +8,7 @@ import { newComboTarget, } from "../combo-workspace-data"; import { IconArrowDown, IconArrowUp, IconGrip, IconPlus, IconTrash } from "../icons"; +import { navigateHash } from "../hash-routing"; import { useT } from "../i18n/shared"; import { Switch } from "../ui"; import { formatProviderDisplayName } from "../provider-icons"; @@ -93,6 +94,7 @@ export function ComboCapabilities({ models, imageInput, reasoningEffortMode, + visionEnabled, disabled, onChange, }: { @@ -100,13 +102,30 @@ export function ComboCapabilities({ models: ModelOption[]; imageInput: "auto" | "disabled"; reasoningEffortMode: "strict" | "adaptive"; + /** Vision Sidecar enabled state; undefined = unknown, renders no warning. */ + visionEnabled?: boolean; disabled?: boolean; onChange: (patch: { imageInput?: "auto" | "disabled"; reasoningEffortMode?: "strict" | "adaptive" }) => void; }) { const t = useT(); const imagesSupported = comboImagesSupported(targets, models); + const blockedTargets = comboImageBlockedTargets(targets, models); // Default: checked (auto) when supported; force off when any target lacks image. const effectiveOn = imagesSupported && imageInput !== "disabled"; + const sidecarTargets = comboVisionSidecarTargets(targets, models); + // Enrollment wording only while image input is enabled: a disabled combo is not + // enrolling anything, so it gets the plain hint instead of save-time promises. + const imageHint = !imagesSupported + ? blockedTargets.length > 0 + ? t("cws.capability.imageInputBlockedHint", { + models: blockedTargets.map(({ provider, model }) => `${provider}/${model}`).join(", "), + }) + : t("cws.capability.imageInputUnavailable") + : sidecarTargets.length > 0 && imageInput !== "disabled" + ? t("cws.capability.imageInputSidecarHint", { + models: sidecarTargets.map(({ provider, model }) => `${provider}/${model}`).join(", "), + }) + : t("cws.capability.imageInputHint"); return (
@@ -115,8 +134,18 @@ export function ComboCapabilities({
{t("cws.capability.imageInput")}

- {imagesSupported ? t("cws.capability.imageInputHint") : t("cws.capability.imageInputUnavailable")} + {imageHint}

+ {sidecarTargets.length > 0 && effectiveOn && visionEnabled === false ? ( +

+ {t("cws.capability.imageInputSidecarDisabled", { + models: sidecarTargets.map(({ provider, model }) => `${provider}/${model}`).join(", "), + })}{" "} + +

+ ) : null}
void; onSaved: (item: ComboItem) => void; onRequestRemove?: () => void; @@ -103,7 +106,7 @@ export function DetailPanel({ const [copied, setCopied] = useState(false); const dirty = !draftEquals(draft, baseline); const allTargetsExhausted = comboQuotaState(draft.targets, providerQuotaStates, providerMap) === "exhausted"; - const baselineSyncKey = JSON.stringify([baseline.id, baseline.alias, baseline.nativeAlias, baseline.displayName, baseline.strategy, baseline.stickyLimit, baseline.defaultEffort, baseline.imageInput, baseline.reasoningEffortMode, baseline.decisionProvider, baseline.decisionModel, baseline.decisionTimeoutMs, baseline.targets.map(t => [t.provider, t.model, t.weight, t.reasoningEfforts, t.modelProfile])]); + const baselineSyncKey = JSON.stringify([baseline.id, baseline.alias, baseline.nativeAlias, baseline.displayName, baseline.strategy, baseline.stickyLimit, baseline.defaultEffort, baseline.imageInput, baseline.reasoningEffortMode, baseline.decisionProvider, baseline.decisionModel, baseline.decisionTimeoutMs, baseline.sessionPin === undefined, baseline.sessionPin, baseline.targets.map(t => [t.provider, t.model, t.weight, t.reasoningEfforts, t.modelProfile])]); const effortMap = useMemo(() => { const map = new Map(); for (const model of capabilityModels) { @@ -167,7 +170,7 @@ export function DetailPanel({ displayName, model: comboPublicModelId(trimmedId, alias), // The server keeps these only for JEV, so the saved baseline must not carry stale ones. - ...(draft.strategy === "jev" ? {} : { decisionProvider: null, decisionModel: null, decisionTimeoutMs: null }), + ...(draft.strategy === "jev" ? {} : { decisionProvider: null, decisionModel: null, decisionTimeoutMs: null, sessionPin: undefined }), }; const renameFrom = !isCreate && trimmedId !== baseline.id ? baseline.id : undefined; try { @@ -355,6 +358,7 @@ export function DetailPanel({ decisionProvider={draft.decisionProvider ?? null} decisionModel={draft.decisionModel ?? null} decisionTimeoutMs={draft.decisionTimeoutMs ?? null} + sessionPin={draft.sessionPin} disabled={busy} onChange={(patch) => updateDraft((d) => ({ ...d, ...patch }))} /> @@ -411,6 +415,7 @@ export function DetailPanel({ models={capabilityModels} imageInput={draft.imageInput ?? "auto"} reasoningEffortMode={draft.reasoningEffortMode ?? "strict"} + visionEnabled={visionEnabled} disabled={busy} onChange={(patch) => updateDraft((d) => ({ ...d, ...patch }))} /> diff --git a/gui/src/components/combo-workspace-jev-decision.tsx b/gui/src/components/combo-workspace-jev-decision.tsx index f35c79db112..c2242ed52a1 100644 --- a/gui/src/components/combo-workspace-jev-decision.tsx +++ b/gui/src/components/combo-workspace-jev-decision.tsx @@ -1,4 +1,10 @@ import { useEffect, useMemo, useRef, useState } from "react"; +import { + JEV_SESSION_PIN_TTL_MIN_MS, + JEV_SESSION_PIN_TTL_MAX_MS, + normalizeJevSessionPin, + type JevSessionPinConfig, +} from "../../../src/combos/jev-session-pin-config"; import { JEV_DECISION_ISSUE_LABEL_KEYS, jevDecisionServiceOptions } from "../combo-workspace-data"; import { type TKey, useT } from "../i18n/shared"; import { @@ -16,6 +22,7 @@ export type JevDecisionPatch = { decisionProvider?: string | null; decisionModel?: string | null; decisionTimeoutMs?: number | null; + sessionPin?: JevSessionPinConfig | null; }; type DecisionTestResult = @@ -46,6 +53,7 @@ export function ComboJevDecisionSection({ decisionProvider, decisionModel, decisionTimeoutMs, + sessionPin, disabled, onChange, }: { @@ -59,11 +67,13 @@ export function ComboJevDecisionSection({ decisionProvider: string | null; decisionModel: string | null; decisionTimeoutMs: number | null; + sessionPin?: JevSessionPinConfig | null; disabled?: boolean; onChange: (patch: JevDecisionPatch) => void; }) { const t = useT(); const method = jevDecisionMethod({ decisionProvider, decisionModel }); + const pinInvalid = sessionPin != null && !normalizeJevSessionPin(sessionPin); const servers = useMemo( () => jevDecisionServiceOptions(providers, decisionProvider).filter(option => option.id !== null), [providers, decisionProvider], @@ -271,6 +281,49 @@ export function ComboJevDecisionSection({

+
+ +

+ {t("cws.jev.sessionPinHint", { min: JEV_SESSION_PIN_TTL_MIN_MS, max: JEV_SESSION_PIN_TTL_MAX_MS })} +

+ {sessionPin != null && <> + + { + if (e.target.value === "") { + onChange({ sessionPin: { ttlMs: 0 } }); + return; + } + const ttlMs = Number(e.target.value); + onChange({ sessionPin: { ttlMs: Number.isFinite(ttlMs) ? ttlMs : 0 } }); + }} + /> + {pinInvalid && } + } +
+
@@ -721,13 +724,14 @@ export default function ProviderAuthPanel({
)} + {apiBase && } {onEditSettings && (
+ {authMode !== "oauth" && authMode !== "forward" && }

{t("pws.pacingModelOverrides")}

diff --git a/gui/src/components/provider-workspace/WorkBuddyCredits.tsx b/gui/src/components/provider-workspace/WorkBuddyCredits.tsx new file mode 100644 index 00000000000..32874cac7de --- /dev/null +++ b/gui/src/components/provider-workspace/WorkBuddyCredits.tsx @@ -0,0 +1,35 @@ +import type { WorkBuddyCreditBalance } from "../../../../src/providers/workbuddy-credit-balance"; +import { useI18n } from "../../i18n/shared"; +import QuotaBars from "../QuotaBars"; + +export function WorkBuddyCredits({ credits, updatedAt }: { credits: WorkBuddyCreditBalance; updatedAt: number }) { + const { t, locale } = useI18n(); + const number = (value: number) => new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }).format(value); + const quantity = (value: number) => t("quota.workbuddy.amount", { amount: number(value) }); + const percent = credits.total > 0 ? Math.min(100, credits.used / credits.total * 100) : 0; + return
+
+ {t("quota.creditsBalance")} + {quantity(credits.remaining)} +
+ {/* These bars are a local display projection, never a server quota/routing window. */} + +
+
{t("quota.workbuddy.usage", { + used: number(credits.used), total: number(credits.total), + })}
+ {credits.groups.filter(group => group.total > 0 || group.remaining > 0).map(group =>
+ {t(`quota.workbuddy.${group.kind}`)} + {quantity(group.remaining)} / {number(group.total)} +
)} + {credits.frozen > 0 &&
+ {t("quota.workbuddy.frozen")}{quantity(credits.frozen)} +
} + {credits.expiring &&

{t("quota.workbuddy.expiring", { + amount: number(credits.expiring.amount), + date: new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeStyle: "short" }).format(credits.expiring.at), + })}

} +
+
; +} diff --git a/gui/src/components/provider-workspace/provider-completion-test.css b/gui/src/components/provider-workspace/provider-completion-test.css new file mode 100644 index 00000000000..43ab8da1482 --- /dev/null +++ b/gui/src/components/provider-workspace/provider-completion-test.css @@ -0,0 +1,22 @@ +.pws-completion-test { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: start; + gap: var(--space-2); + margin-top: var(--space-4); +} +.pws-completion-test > h4, +.pws-completion-result { + grid-column: 1 / -1; +} +.pws-completion-test > .custom-select { + min-width: 0; +} +.pws-completion-result { + overflow-wrap: anywhere; +} +@media (max-width: 640px) { + .pws-completion-test { + grid-template-columns: minmax(0, 1fr); + } +} diff --git a/gui/src/components/provider-workspace/types.ts b/gui/src/components/provider-workspace/types.ts index b8d8b7c1ee5..09933657ee3 100644 --- a/gui/src/components/provider-workspace/types.ts +++ b/gui/src/components/provider-workspace/types.ts @@ -62,9 +62,11 @@ export type OAuthAccountRow = AccountQuotaReading & { autoSelectable?: boolean; skipReason?: "needs_reauth" | "paused" | "suspended" | "cooldown" | "quota_exhausted"; paused?: boolean; + pauseDetails?: { reason: string; pausedAt: number }; autoSwitchThresholdOverride?: number | null; autoSwitchThreshold?: number; effectiveAutoSwitchThreshold?: number; + plan?: string | null; health?: { status: OAuthAccountHealthStatus; reason?: string; until?: string }; healthLabel?: string; healthSummary?: string; @@ -72,6 +74,8 @@ export type OAuthAccountRow = AccountQuotaReading & { }; export type ApiKeyRow = AccountQuotaReading & { + disabled?: boolean; + pauseDetails?: { reason: string; pausedAt: number }; id: string; label?: string; masked: string; diff --git a/gui/src/components/remote-workspace/RemoteWorkspaceChats.tsx b/gui/src/components/remote-workspace/RemoteWorkspaceChats.tsx new file mode 100644 index 00000000000..61f28ad2cf8 --- /dev/null +++ b/gui/src/components/remote-workspace/RemoteWorkspaceChats.tsx @@ -0,0 +1,157 @@ +import { useEffect, useRef, useState, type CSSProperties, type DragEvent, type ReactNode } from "react"; +import { IconArrowDown, IconChevron, IconGrip, IconX } from "../../icons"; +import { useT } from "../../i18n/shared"; +import { Notice } from "../../ui"; +import { EVENT_TKEY, PROFILE_LABEL, STATUS_TKEY, type RemoteDevice, type RemoteSession } from "./types"; +import { MAX_PANES, minLayoutWidth, openPane, paneIds, parseLayout, reconcileLayout, removePane, resizeSplit, splitPane, type PaneLayout, type SplitEdge } from "./split-layout"; +import { useSessionActions } from "./use-session-actions"; + +const DRAG_TYPE = "application/x-opencodex-session"; +const EDGES: SplitEdge[] = ["left", "right", "up", "down"]; + +// Only a nested split reserves its width; a leaf pane stays free to follow the ratio. +const trackMin = (child: PaneLayout) => child.type === "split" ? minLayoutWidth(child) : 0; + +function SplitBranch({ node, renderPane, resize }: { + node: PaneLayout; renderPane: (id: string) => ReactNode; resize: (id: string, ratio: number) => void; +}) { + const t = useT(); + const container = useRef(null); + const dragging = useRef(false); + if (node.type === "pane") return renderPane(node.sessionId); + const style = { "--split-ratio": node.ratio, "--split-min-width": minLayoutWidth(node), + "--split-first-min": trackMin(node.first), "--split-second-min": trackMin(node.second) } as CSSProperties; + return
+
+
{ if (event.button !== 0) return; event.preventDefault(); event.currentTarget.focus(); dragging.current = true; event.currentTarget.setPointerCapture(event.pointerId); }} + onPointerMove={event => { + if (!dragging.current || !container.current) return; + const rect = container.current.getBoundingClientRect(); + const size = node.axis === "row" ? rect.width : rect.height; + if (size > 0) resize(node.id, (node.axis === "row" ? event.clientX - rect.left : event.clientY - rect.top) / size); + }} + onPointerUp={event => { dragging.current = false; if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId); }} + onPointerCancel={() => { dragging.current = false; }} onLostPointerCapture={() => { dragging.current = false; }} + onKeyDown={event => { + const backward = node.axis === "row" ? "ArrowLeft" : "ArrowUp", forward = node.axis === "row" ? "ArrowRight" : "ArrowDown"; + if (![backward, forward, "Home", "End"].includes(event.key)) return; + event.preventDefault(); + resize(node.id, event.key === "Home" ? 0.2 : event.key === "End" ? 0.8 : node.ratio + (event.key === backward ? -0.05 : 0.05)); + }} /> +
+
; +} + +export function RemoteWorkspaceChats({ apiBase, sessions, devices, selectedSessionId, stale, refresh }: { + apiBase: string; sessions: RemoteSession[]; devices: RemoteDevice[]; selectedSessionId: string; stale: boolean; refresh: () => unknown; +}) { + const t = useT(); + const storageKey = `ocx-remote-workspace-layout:${apiBase}`; + const [layout, setLayout] = useState(() => { + try { return parseLayout(localStorage.getItem(storageKey)); } catch { return null; } + }); + const [active, setActive] = useState(""); + const [dragged, setDragged] = useState(null); + const [dropTarget, setDropTarget] = useState<{ id: string; edge: SplitEdge } | null>(null); + const [focusRequest, setFocusRequest] = useState<{ id: string } | null>(null); + const [lastSelection, setLastSelection] = useState(selectedSessionId); + const host = useRef(null); + const actions = useSessionActions(apiBase, sessions, devices, stale, refresh); + const ids = actions.sessions.map(session => session.id); + const fallback = [...actions.sessions].reverse().find(session => session.status !== "stopped")?.id ?? ids.at(-1); + const effectiveLayout = reconcileLayout(layout, ids, fallback); + const visible = paneIds(effectiveLayout); + const activeId = visible.includes(active) ? active : visible[0] ?? ""; + + if (lastSelection !== selectedSessionId) { + setLastSelection(selectedSessionId); + if (selectedSessionId && ids.includes(selectedSessionId)) { + setLayout(openPane(effectiveLayout, selectedSessionId, activeId)); + setActive(selectedSessionId); + } + } else if (layout !== effectiveLayout) setLayout(effectiveLayout); + + useEffect(() => { + try { localStorage.setItem(storageKey, JSON.stringify(effectiveLayout)); } catch { /* The workspace remains usable without storage. */ } + }, [effectiveLayout, storageKey]); + useEffect(() => { + if (!focusRequest) return; + const heading = [...(host.current?.querySelectorAll("[data-pane-heading]") ?? [])].find(element => element.dataset.paneHeading === focusRequest.id); + heading?.focus(); + }, [focusRequest]); + + const open = (id: string) => { setLayout(openPane(effectiveLayout, id, activeId)); setActive(id); }; + const split = (id: string, target: string, edge: SplitEdge) => { + if (!ids.includes(id)) return; + const next = splitPane(effectiveLayout, id, target, edge); + setLayout(next); + if (paneIds(next).includes(id)) setActive(id); + setDragged(null); setDropTarget(null); + }; + const close = (id: string) => { + const next = removePane(effectiveLayout, id); + const nextId = paneIds(next)[0] ?? ""; + setLayout(next); setActive(nextId); setFocusRequest({ id: nextId }); + }; + const startDrag = (event: DragEvent, id: string) => { event.dataTransfer.setData(DRAG_TYPE, id); event.dataTransfer.effectAllowed = "move"; setDragged(id); }; + const endDrag = () => { setDragged(null); setDropTarget(null); }; + const canSplit = (id: string, target: string) => id !== target && (visible.includes(id) || visible.length < MAX_PANES); + + const renderPane = (id: string) => { + const session = actions.sessions.find(value => value.id === id); + if (!session) return null; + const disabled = session.status === "stopped" || (session.status === "failed" && session.resumable === false); + return
setActive(id)} onPointerDown={() => setActive(id)}> +
+
startDrag(event, id)} onDragEnd={endDrag}> +
{PROFILE_LABEL[session.profile]}{session.deviceName}/{session.rootLabel}
+
+ {t(STATUS_TKEY[session.status])} + +
+ {session.accessMode === "read-only" ? t("remote.access.readOnly") : session.capabilities.includes("workspace.exec") ? t("remote.access.workspace") : t("remote.access.workspaceFilesOnly")} +
+ {session.events.length === 0 ?

{t("remote.noEvents")}

: session.events.map(event =>
+ {event.type === "assistant" ? PROFILE_LABEL[session.profile] : t(EVENT_TKEY[event.type])}

{event.text}

+
)} +
+ {session.status === "failed" && session.resumable === false ? {t("remote.notResumable")} : null} + {actions.errors[id] ? {actions.errors[id]} : null} +