@bitkyc08/opencodex 2.7.6 → 2.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/index-Bp8dDrs5.js +40 -0
- package/gui/dist/assets/index-C0xVu72_.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/openai-responses.ts +42 -0
- package/src/codex/catalog.ts +13 -1
- package/src/providers/registry.ts +4 -0
- package/src/reasoning-effort.ts +5 -0
- package/src/responses/parser.ts +28 -0
- package/src/server/effort-policy.ts +172 -0
- package/src/server/index.ts +29 -1
- package/src/server/management-api.ts +28 -0
- package/src/server/responses.ts +70 -14
- package/src/server/search.ts +150 -0
- package/src/types.ts +25 -0
- package/gui/dist/assets/index-D7o1qwy-.css +0 -1
- package/gui/dist/assets/index-mEjIne-M.js +0 -40
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;--bg:var(--lightningcss-light,#fff)var(--lightningcss-dark,#212121);--rail:var(--lightningcss-light,#f9f9f9)var(--lightningcss-dark,#171717);--surface:var(--lightningcss-light,#fff)var(--lightningcss-dark,#262626);--raised:var(--lightningcss-light,#f4f4f4)var(--lightningcss-dark,#303030);--raised-hover:var(--lightningcss-light,#ececec)var(--lightningcss-dark,#3a3a3a);--border:var(--lightningcss-light,#e6e6e6)var(--lightningcss-dark,#3d3d3d);--border-soft:var(--lightningcss-light,#f0f0f0)var(--lightningcss-dark,#333);--hover:var(--lightningcss-light,#0d0d0d08)var(--lightningcss-dark,#ffffff08);--text:var(--lightningcss-light,#0d0d0d)var(--lightningcss-dark,#ececec);--muted:var(--lightningcss-light,#6e6e6e)var(--lightningcss-dark,#a6a6a6);--faint:var(--lightningcss-light,#707070)var(--lightningcss-dark,#9a9a9a);--accent:var(--lightningcss-light,#0d0d0d)var(--lightningcss-dark,#ececec);--accent-hover:var(--lightningcss-light,#3d3d3d)var(--lightningcss-dark,#fff);--accent-ink:var(--lightningcss-light,#fff)var(--lightningcss-dark,#0d0d0d);--accent-soft:var(--lightningcss-light,#0d0d0d0f)var(--lightningcss-dark,#ffffff17);--accent-ring:var(--lightningcss-light,#0d0d0d4d)var(--lightningcss-dark,#ffffff61);--green:var(--lightningcss-light,#0a7d5c)var(--lightningcss-dark,#4ecb9d);--green-soft:var(--lightningcss-light,#10a37f1a)var(--lightningcss-dark,#4ecb9d21);--red:var(--lightningcss-light,#b91c1c)var(--lightningcss-dark,#f87171);--red-soft:var(--lightningcss-light,#b91c1c17)var(--lightningcss-dark,#f8717121);--amber:var(--lightningcss-light,#9a4a08)var(--lightningcss-dark,#fbbf24);--amber-soft:var(--lightningcss-light,#b453091a)var(--lightningcss-dark,#fbbf2421);--radius:12px;--radius-sm:8px;--radius-xs:6px;--radius-pill:999px;--font:"OpenAI Sans", "Söhne", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, "Helvetica Neue", sans-serif;--mono:ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, Consolas, monospace;--shadow:0 1px 2px var(--lightningcss-light,#1018280f)var(--lightningcss-dark,#00000080), 0 10px 28px var(--lightningcss-light,#10182812)var(--lightningcss-dark,#0000004d);--shadow-sm:0 1px 2px var(--lightningcss-light,#1018280f)var(--lightningcss-dark,#0006)}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}:root[data-theme=light]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}:root[data-theme=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}:root{--glass-rail:var(--lightningcss-light,#f9f9f9a8)var(--lightningcss-dark,#1717179e);--glass-panel:var(--lightningcss-light,#ffffffc7)var(--lightningcss-dark,#262626d1);--glass-blur:saturate(1.6) blur(22px)}*{box-sizing:border-box}html,body,#root{height:100%}html{background:var(--bg);overflow-x:hidden}body{color:var(--text);font-family:var(--font);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;background:0 0;margin:0;font-size:14px;line-height:1.5;overflow-x:hidden}body:before{content:"";z-index:-1;pointer-events:none;filter:blur(70px);background:radial-gradient(42% 38% at 12% 6%,var(--lightningcss-light,#a4c4ff6b)var(--lightningcss-dark,#6082c829),#0000 70%),radial-gradient(46% 42% at 88% 18%,var(--lightningcss-light,#a8e2c561)var(--lightningcss-dark,#58a08221),#0000 70%),radial-gradient(40% 36% at 70% 92%,var(--lightningcss-light,#ffe0c24d)var(--lightningcss-dark,#b48c6414),#0000 72%);position:fixed;inset:-20%}a{color:var(--text);text-decoration:underline;-webkit-text-decoration-color:var(--faint);text-decoration-color:var(--faint);text-underline-offset:2px}a:hover{-webkit-text-decoration-color:var(--text);text-decoration-color:var(--text)}code,.mono{font-family:var(--mono);font-size:.92em}h1,h2,h3,h4{letter-spacing:0;margin:0;font-weight:600}::selection{background:var(--accent-soft)}input[type=checkbox],input[type=radio]{accent-color:var(--accent)}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-thumb{background:var(--border);border:2px solid var(--bg);border-radius:99px}::-webkit-scrollbar-thumb:hover{background:var(--faint)}:focus-visible{outline:2px solid var(--accent-ring);outline-offset:2px;border-radius:4px}.app{grid-template-columns:232px 1fr;min-height:100dvh;display:grid}.sidebar{border-right:1px solid var(--border);background:var(--glass-rail);height:100dvh;-webkit-backdrop-filter:var(--glass-blur);flex-direction:column;align-self:start;gap:4px;padding:18px 14px;display:flex;position:sticky;top:0}.brand{align-items:center;gap:10px;padding:6px 8px 14px;display:flex}.brand-logo{background:var(--text);flex-shrink:0;width:26px;height:26px;-webkit-mask:url(/logo.png) 50%/contain no-repeat;mask:url(/logo.png) 50%/contain no-repeat}.brand .name{letter-spacing:0;font-size:15px;font-weight:600;line-height:26px}.brand .ver{font-family:var(--mono);color:var(--muted);background:var(--raised);border:1px solid var(--border);border-radius:99px;align-self:center;padding:2px 6px;font-size:10px;line-height:1}.nav-item{border-radius:var(--radius-sm);text-align:left;cursor:pointer;width:100%;color:var(--muted);font:inherit;background:0 0;border:none;align-items:center;gap:10px;padding:8px 10px;font-size:13.5px;font-weight:500;transition:background .12s,color .12s;display:flex}.nav-item:hover,.nav-item.active{background:var(--accent-soft);color:var(--text)}.nav-item.active{font-weight:600}.nav-item svg{width:17px;height:17px;color:var(--faint);flex-shrink:0}.nav-item.active svg{color:var(--text)}.sidebar-foot{flex-direction:column;gap:2px;margin-top:auto;padding-top:12px;display:flex}.sidebar-link{color:var(--muted);border-radius:var(--radius-sm);align-items:center;gap:9px;padding:8px 10px;font-size:13px;text-decoration:none;display:flex}.sidebar-link:hover{background:var(--accent-soft);color:var(--text);text-decoration:none}.sidebar-link svg{width:16px;height:16px}.lang-toggle{width:100%;color:var(--muted);border-radius:var(--radius-sm);align-items:center;gap:9px;padding:8px 10px;font-size:13px;transition:background .12s,color .12s;display:flex;position:relative}.lang-toggle:hover{background:var(--accent-soft);color:var(--text)}.lang-toggle svg{flex-shrink:0;width:16px;height:16px}.lang-toggle .custom-select{width:100%;position:static!important}.lang-toggle .select-trigger{width:100%;color:inherit;min-height:auto;box-shadow:none;-webkit-backdrop-filter:none;background:0 0;border:none;justify-content:space-between;padding:0;font-size:13px}.lang-toggle .select-trigger:hover:not(:disabled){color:inherit;box-shadow:none;background:0 0;border:none}.lang-toggle .select-dropdown{background:var(--glass-rail);-webkit-backdrop-filter:var(--glass-blur);border:1px solid var(--border);box-shadow:var(--shadow-sm)}.lang-toggle .select-dropdown-beside{min-width:10rem;top:0;left:calc(100% + 6px);right:auto}.theme-toggle{text-align:left;cursor:pointer;width:100%;color:var(--muted);font:inherit;border-radius:var(--radius-sm);background:0 0;border:none;align-items:center;gap:9px;padding:8px 10px;font-size:13px;transition:background .12s,color .12s;display:flex}.theme-toggle:hover{background:var(--accent-soft);color:var(--text)}.theme-toggle svg{flex-shrink:0;width:16px;height:16px}.theme-toggle .mode{text-transform:capitalize}.stop-toggle{color:var(--red)}.stop-toggle:hover{background:var(--red-soft);color:var(--red)}.stop-toggle:disabled{opacity:.5;cursor:default}.main{min-width:0}.main-inner{max-width:980px;margin:0 auto;padding:32px 36px 64px}.page-head{justify-content:space-between;align-items:center;gap:16px;margin-bottom:6px;display:flex}.page-head h2{font-size:19px}.page-sub{color:var(--muted);max-width:70ch;margin:4px 0 22px;font-size:13.5px}.page-sub b{color:var(--text);font-weight:600}.api-page h2 svg{vertical-align:-.16em;width:1em;height:1em}.api-page .page-sub code{color:var(--text);background:var(--raised);border:1px solid var(--border-soft);border-radius:var(--radius-xs);padding:1px 4px;font-size:.92em}.btn{border-radius:var(--radius-pill);font:inherit;cursor:pointer;white-space:nowrap;border:1px solid #0000;justify-content:center;align-items:center;gap:7px;padding:8px 16px;font-size:13px;font-weight:500;transition:background .12s,border-color .12s,opacity .12s;display:inline-flex}.btn svg{width:15px;height:15px}.btn:disabled{opacity:.55;cursor:default}.btn-primary{background:var(--accent);color:var(--accent-ink)}.btn-primary:hover:not(:disabled){background:var(--accent-hover)}.btn-ghost{background:var(--bg);color:var(--text);border-color:var(--border)}.btn-ghost:hover:not(:disabled){background:var(--raised)}.btn-danger{color:var(--red);background:0 0;border-color:#f871714d}.btn-danger:hover:not(:disabled){background:var(--red-soft)}.btn-sm{border-radius:var(--radius-pill);padding:4px 12px;font-size:12px}.btn-icon{appearance:none;font:inherit;cursor:pointer;width:28px;height:28px;color:var(--muted);border-radius:var(--radius-sm);background:0 0;border:none;justify-content:center;align-items:center;padding:0;transition:background .12s,color .12s;display:inline-flex}.btn-icon:hover{background:var(--raised-hover);color:var(--text)}.btn-icon:focus-visible{outline:2px solid var(--accent-ring);outline-offset:1px}.btn-icon svg{width:16px;height:16px;display:block}.btn.btn-ghost.btn-icon{width:28px;height:28px;padding:0}.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);min-width:0}.panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px}.panel-accent{border-color:color-mix(in srgb, var(--accent) 28%, var(--border));background:color-mix(in srgb, var(--accent) 5%, var(--surface))}.api-panel{flex-direction:column;gap:10px;display:flex;overflow:hidden}.api-panel .panel-title,.api-panel .muted{margin:0}.api-form-row{align-items:center;gap:8px;min-width:0;display:flex}.api-form-row .input{flex:1;min-width:0}.api-code{min-width:0;color:var(--text);background:var(--raised);border:1px solid var(--border-soft);border-radius:var(--radius-sm);font-family:var(--mono);white-space:pre;margin:0;padding:9px 11px;font-size:12.5px;line-height:1.55;display:block;overflow-x:auto}.api-code-inline{white-space:nowrap}.api-actions{justify-content:flex-end;align-items:center;gap:4px;display:flex}.maintenance-head{flex-wrap:wrap;align-items:flex-start}.maintenance-actions{flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:8px;display:flex}.maintenance-notice{align-items:flex-start;margin:14px 0 0}.update-row{justify-content:space-between;align-items:center;gap:12px;margin-bottom:14px;display:flex}.update-row .field-label{margin:0}.update-empty{margin-bottom:14px;padding:18px}.update-box{flex-direction:column;gap:12px;display:flex}.update-command{flex-wrap:wrap;align-items:flex-start;gap:6px;font-size:12px;display:flex}.update-command .chip{white-space:pre-wrap;word-break:break-all;overflow-wrap:anywhere;flex:auto;min-width:0;max-width:100%;line-height:1.5}.update-recheck{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.update-recheck .btn{flex:none}.update-recheck-reason{min-width:0;color:var(--muted);flex:200px;font-size:12px}.update-restart{border-top:1px solid var(--border-soft);padding-top:12px}.injection-head{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.injection-label{font-weight:650}@media (width<=800px){.injection-head{gap:8px}.injection-label{flex:1 0 calc(100% - 70px);order:1}.injection-head>.badge{order:1;margin-left:auto}.injection-head>.custom-select{order:2}}.stat-row{grid-template-columns:repeat(6,1fr);gap:12px;margin-bottom:28px;display:grid}.stat-row>.stat{flex-direction:column;justify-content:center;min-height:80px;display:flex}@media (width<=1100px){.stat-row{grid-template-columns:repeat(3,1fr)}}@media (width<=640px){.stat-row{grid-template-columns:repeat(2,1fr)}}.stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;transition:border-color .12s}.stat:hover{border-color:var(--faint)}.stat .label{color:var(--muted);align-items:center;gap:6px;margin-bottom:6px;font-size:12px;font-weight:500;display:flex}.stat .label svg{width:14px;height:14px}.stat .value{letter-spacing:0;font-size:20px;font-weight:600;line-height:1.15}.stat .value.mono{font-family:var(--mono);font-size:17px}.model-group-head{color:var(--text);align-items:baseline;gap:8px;margin:0 0 8px;font-size:13px;font-weight:600;display:flex}.model-group-head .count{font-family:var(--mono);color:var(--faint);font-size:12px;font-weight:500}.group-head{cursor:pointer;background:var(--surface);padding:10px 12px;transition:background .12s}.group-head:hover{background:var(--hover)}.group-head.open{border-bottom:1px solid var(--border-soft)}.model-grid{grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px;display:grid}.model-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px 12px;transition:border-color .12s,background .12s}.model-card:hover{border-color:var(--faint);background:var(--hover)}.model-card .id{font-family:var(--mono);letter-spacing:0;color:var(--text);font-size:13px;font-weight:600}.badge{font-size:11px;font-weight:600;font-family:var(--mono);letter-spacing:0;border:1px solid #0000;border-radius:99px;align-items:center;gap:5px;padding:2px 8px;display:inline-flex}.badge-accent{background:var(--accent-soft);color:var(--text)}.badge-green{background:var(--green-soft);color:var(--green)}.badge-amber{background:var(--amber-soft);color:var(--amber)}.badge-muted{background:var(--raised);color:var(--muted);border:1px solid var(--border)}.badge-clickable{cursor:pointer;appearance:none;transition:filter .12s}.badge-clickable:hover{filter:brightness(1.1)}.badge-disabled{opacity:.5;cursor:default}.card-badges{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;display:inline-flex}.card-badges .badge{flex-shrink:0}.confirm-icon{background:var(--amber-soft);width:44px;height:44px;color:var(--amber);border-radius:50%;justify-content:center;align-items:center;margin:0 auto 12px;display:flex}.credit-list{flex-direction:column;gap:6px;display:flex}.credit-item{border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--raised);padding:8px 10px;transition:border-color .12s}.credit-next{border-color:var(--amber);background:var(--amber-soft)}.credit-item-head{color:var(--text);align-items:center;gap:6px;margin-bottom:3px;font-size:12px;font-weight:600;display:flex}.credit-item-head svg{color:var(--amber);flex-shrink:0}.credit-item-dates{font-size:11px;font-family:var(--mono);color:var(--muted);justify-content:space-between;display:flex}.credit-urgent{color:var(--red);font-weight:600}.dot{border-radius:50%;flex-shrink:0;width:7px;height:7px}.dot-green{background:var(--green);box-shadow:0 0 0 3px var(--green-soft)}.dot-red{background:var(--red);box-shadow:0 0 0 3px var(--red-soft)}.tbl{border-collapse:collapse;width:100%;font-size:13px}.tbl thead th{text-align:left;color:var(--muted);border-bottom:1px solid var(--border);padding:9px 12px;font-size:12px;font-weight:500}.tbl tbody td{border-bottom:1px solid var(--border-soft);padding:10px 12px}.tbl tbody tr:last-child td{border-bottom:none}.tbl tbody tr:hover td{background:var(--hover)}.tbl .num{text-align:right;font-family:var(--mono)}.tbl-wrap{border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);overflow-x:auto}.input,textarea.input{border-radius:var(--radius-sm);background:var(--raised);border:1px solid var(--border);width:100%;color:var(--text);font:inherit;padding:8px 11px;font-size:13px;transition:border-color .12s}.input::placeholder{color:var(--faint)}.input:focus{border-color:var(--faint);box-shadow:0 0 0 3px var(--accent-soft);outline:none}textarea.input{resize:vertical;font-family:var(--mono);line-height:1.55}.field-label{color:var(--muted);margin-bottom:5px;font-size:12px;font-weight:500;display:block}select.input{appearance:none}.select-sm{background:color-mix(in oklab, canvas 75%, transparent);-webkit-backdrop-filter:blur(12px)saturate(1.3);border:1px solid var(--border);color:var(--text);font:inherit;cursor:pointer;border-radius:999px;padding:6px 12px;font-size:13px;transition:border-color .12s,box-shadow .12s;box-shadow:0 2px 8px #0000000f}.select-sm:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft);outline:none}.select-sm:hover:not(:disabled){border-color:var(--faint);box-shadow:0 2px 12px #0000001a}.select-sm:disabled{opacity:.5;cursor:default;-webkit-backdrop-filter:none}.select-trigger{background:color-mix(in oklab, canvas 75%, transparent);-webkit-backdrop-filter:blur(12px)saturate(1.3);border:1px solid var(--border);color:var(--text);font:inherit;cursor:pointer;white-space:nowrap;border-radius:999px;align-items:center;gap:6px;padding:6px 12px;font-size:13px;transition:border-color .12s,box-shadow .12s;display:inline-flex;box-shadow:0 2px 8px #0000000f}.select-trigger:hover:not(:disabled){border-color:var(--faint);box-shadow:0 2px 12px #0000001a}.select-trigger:disabled{opacity:.5;cursor:default}.select-dropdown{z-index:40;background:color-mix(in oklab, canvas 84%, transparent);-webkit-backdrop-filter:blur(20px)saturate(1.4);border:1px solid var(--border);border-radius:12px;min-width:100%;max-height:280px;padding:4px;position:absolute;top:calc(100% + 4px);left:0;overflow-y:auto;box-shadow:0 8px 32px #00000029}.select-dropdown-right{right:0;left:auto!important}.select-dropdown-beside{min-width:10rem;top:0;left:calc(100% + 6px);right:auto}.select-option{text-align:left;width:100%;color:var(--text);font:inherit;cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;padding:7px 12px;font-size:13px;transition:background .1s;display:block}.select-option:hover{background:var(--hover)}.select-option.active{background:var(--accent-soft);color:var(--accent);font-weight:600}.switch{border:1px solid var(--border);cursor:pointer;background:var(--raised);vertical-align:middle;appearance:none;border-radius:99px;flex-shrink:0;align-items:center;width:34px;height:20px;padding:2px;line-height:0;transition:background .15s,border-color .15s;display:inline-flex}.switch.on{background:var(--accent);border-color:var(--accent)}.switch:disabled{opacity:.6;cursor:default}.switch .knob{background:var(--lightningcss-light,#fff)var(--lightningcss-dark,#ececec);border-radius:50%;width:14px;height:14px;transition:transform .15s,background .15s;transform:translate(0);box-shadow:0 0 0 1px #10182814,0 1px 1px #1018282e}.switch.on .knob{background:var(--accent-ink);transform:translate(14px);box-shadow:0 0 0 1px #0000001f}.muted{color:var(--muted)}.faint{color:var(--faint)}.row{align-items:center;gap:10px;display:flex}.spread{justify-content:space-between;align-items:center;gap:12px;display:flex}.setting-hint{margin-top:3px;font-size:13px}.stack{flex-direction:column;display:flex}.chip{font-family:var(--mono);background:var(--raised);border:1px solid var(--border);border-radius:var(--radius-xs);color:var(--text);padding:1px 7px;font-size:12px}.empty{text-align:center;border:1px dashed var(--border);border-radius:var(--radius);color:var(--muted);padding:56px 20px}.empty svg{width:30px;height:30px;color:var(--faint);margin-bottom:12px}.empty .title{color:var(--text);margin-bottom:6px;font-weight:600}.notice{border-radius:var(--radius-sm);align-items:center;gap:8px;margin-bottom:14px;padding:9px 12px;font-size:13px;display:flex}.notice svg{flex-shrink:0;width:15px;height:15px}.notice-ok{background:var(--green-soft);color:var(--green)}.notice-err{background:var(--red-soft);color:var(--red)}.h-section{color:var(--text);align-items:center;gap:8px;margin:30px 0 12px;font-size:13px;font-weight:600;display:flex}.h-section .count{color:var(--muted);font-weight:500;font-family:var(--mono);font-size:12px}.spin{border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;width:14px;height:14px;animation:.7s linear infinite spin;display:inline-block}@keyframes spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}@supports not ((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.sidebar,.lang-toggle .select-dropdown{background:var(--rail)}.modal-card{background:var(--surface)}}@media (prefers-reduced-transparency:reduce){body:before{display:none}.sidebar,.lang-toggle .select-dropdown{background:var(--rail);-webkit-backdrop-filter:none}.modal-card{background:var(--surface);-webkit-backdrop-filter:none}.modal-overlay{-webkit-backdrop-filter:none}}.modal-overlay{-webkit-backdrop-filter:blur(40px)saturate(1.2);z-index:50;background:var(--lightningcss-light,#11131cc7)var(--lightningcss-dark,#000000d1);justify-content:center;align-items:flex-start;padding:8vh 16px;display:flex;position:fixed;inset:0}.modal-card{background:color-mix(in oklab, canvas 92%, transparent);-webkit-backdrop-filter:blur(20px)saturate(1.4);border:1px solid var(--border);border-radius:16px;width:100%;max-width:520px;max-height:84vh;padding:20px;overflow-y:auto;box-shadow:0 8px 32px #0000002e}.modal-head{justify-content:space-between;align-items:center;margin-bottom:16px;display:flex}.modal-head h3{font-size:16px}.card-head{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;padding:14px 16px 6px;display:flex}.card-head strong{overflow-wrap:anywhere;min-width:0}.card-sub{color:var(--muted);overflow-wrap:anywhere;min-width:0;padding:0 16px 10px;font-size:12px}.card-active{border-color:var(--accent-ring)}.card-right{color:var(--faint);align-items:center;gap:4px;margin-left:auto;font-size:11px;display:flex}.btn-icon-danger.card-right{appearance:none;color:var(--red);cursor:pointer;background:0 0;border:1px solid #0000;justify-content:center}.btn-icon-danger.card-right:hover{background:var(--red-soft);border-color:var(--red-soft);color:var(--red)}.card-row{justify-content:space-between;align-items:center;padding:14px 16px;display:flex}.badge-primary{background:var(--accent-soft);color:var(--accent-hover)}.dot-blue{background:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}.dot-muted{background:var(--muted)}.dot-amber{background:var(--amber);box-shadow:0 0 0 3px var(--amber-soft)}.section-sep{align-items:center;gap:10px;margin:20px 0 12px;display:flex}.section-label{color:var(--muted);white-space:nowrap;font-size:12.5px;font-weight:500}.sep-line{background:var(--border);flex:1;height:1px}.quota-compact{gap:4px;padding:2px 16px 12px;display:grid}.quota-row{grid-template-columns:minmax(34px,max-content) 34px minmax(34px,44px) minmax(38px,42px) minmax(58px,1fr) minmax(30px,max-content);align-items:center;gap:8px;min-width:0;display:grid}.quota-label{color:var(--muted);font-size:11px;font-weight:600}.quota-val{font-size:11px;font-family:var(--mono);color:var(--text);text-align:right}.quota-reset-label{text-overflow:ellipsis;white-space:nowrap;color:var(--faint);font-size:11px;overflow:hidden}.quota-reset-day,.quota-reset-time{color:var(--muted);white-space:nowrap;font-size:11px}.quota-reset-time{font-family:var(--mono)}.bar{background:var(--raised);border-radius:3px;min-width:0;height:5px;overflow:hidden}.bar-fill{border-radius:3px;height:100%;transition:width .3s}.bar-green{background:var(--green)}.bar-amber{background:linear-gradient(90deg, var(--green), var(--amber))}.toggle{background:var(--raised);border:1px solid var(--border);cursor:pointer;border-radius:10px;flex-shrink:0;width:36px;height:20px;transition:background .15s;position:relative}.toggle.on{background:var(--accent);border-color:var(--accent)}.toggle-knob{background:#fff;border-radius:50%;width:16px;height:16px;transition:left .15s;position:absolute;top:1px;left:1px}.toggle.on .toggle-knob{left:17px}.notice-warn{border-radius:var(--radius-sm);background:var(--amber-soft);color:var(--amber);align-items:center;gap:6px;margin-bottom:12px;padding:8px 10px;font-size:12px;display:flex}.notice-warn svg{flex-shrink:0;width:14px;height:14px}.modal-desc{color:var(--muted);margin-bottom:14px;font-size:13px}.modal-actions{gap:8px;margin-top:16px;display:flex}.modal-actions .btn{flex:1}.log-reqid{-webkit-line-clamp:2;word-break:break-all;-webkit-box-orient:vertical;max-width:14ch;display:-webkit-box;overflow:hidden}.log-col-model{overflow-wrap:break-word;word-break:normal;max-width:18ch}.log-col-tokens{min-width:10ch}.log-status-cell{flex-direction:column;align-items:flex-start;gap:2px;display:inline-flex}.log-detail-btn{cursor:pointer;color:var(--accent-hover);font:inherit;white-space:nowrap;background:0 0;border:none;padding:0;font-size:11px;text-decoration:underline}.log-detail-grid{grid-template-columns:max-content minmax(0,1fr);gap:6px 14px;font-size:13px;display:grid}.log-detail-break{word-break:break-all}.log-detail-json{background:var(--raised);border:1px solid var(--border-soft);border-radius:var(--radius-sm);font-family:var(--mono);white-space:pre-wrap;word-break:break-all;max-height:40vh;margin:0;padding:10px 12px;font-size:11.5px;line-height:1.5;overflow:auto}.setup-guide{border:1px solid var(--border);border-radius:var(--radius-sm);margin-bottom:4px;padding:8px 12px;font-size:13px}.setup-guide summary{cursor:pointer;color:var(--accent-hover);font-weight:500}.setup-guide summary:hover{text-decoration:underline}.setup-guide a{color:var(--accent-hover)}.list-row{text-align:left;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--raised);cursor:pointer;width:100%;color:var(--text);font:inherit;justify-content:space-between;align-items:center;gap:10px;padding:11px 13px;transition:background .12s,border-color .12s;display:flex}.list-row:hover{background:var(--raised-hover);border-color:var(--accent-ring)}.list-row .title{font-size:14px;font-weight:600}.list-row .sub{color:var(--muted);margin-top:2px;font-size:12px}.prov-card{overflow:hidden}.prov-card-main{flex-wrap:wrap;justify-content:space-between;align-items:flex-start;gap:12px;padding:15px 16px;display:flex}.prov-card-disabled{opacity:.62}.prov-card-info{flex:360px;align-items:flex-start;gap:11px;min-width:0;display:flex}.prov-card-copy{flex:1;min-width:0}.prov-title{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;margin-bottom:5px;display:flex}.provider-icon{background:var(--raised);border:1px solid var(--border-soft);width:31px;height:31px;color:var(--text);border-radius:7px;flex:none;justify-content:center;align-items:center;display:inline-flex}.provider-icon img{object-fit:contain;width:19px;height:19px;display:block}.prov-meta{flex-wrap:wrap;align-items:center;gap:5px;min-width:0;display:flex}.prov-meta>span{text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:100%;overflow:hidden}.provider-quota{padding-left:58px}.provider-actions{flex-shrink:0;align-items:center;gap:8px;margin-left:auto;display:flex}.link-btn{color:var(--accent-hover);font:inherit;cursor:pointer;background:0 0;border:none;padding:6px 2px;font-size:13px;text-decoration:underline}.prov-accounts-toggle{border:none;border-top:1px solid var(--border-soft);width:100%;color:var(--muted);font:inherit;cursor:pointer;background:0 0;justify-content:center;align-items:center;gap:6px;min-height:24px;padding:4px 0;font-size:12px;display:flex}.prov-accounts-toggle:hover{color:var(--text);background:var(--raised)}.prov-accounts-toggle .chev{transition:transform .15s;display:inline-flex}.prov-accounts-toggle .chev svg{width:12px;height:12px;transform:rotate(90deg)}.prov-accounts-toggle.open .chev svg{transform:rotate(-90deg)}.prov-accounts-list{border-top:1px solid var(--border-soft);flex-direction:column;gap:2px;padding:6px 16px 10px;display:flex}.prov-account-row{text-align:left;border-radius:var(--radius-xs);width:100%;color:var(--text);font:inherit;cursor:pointer;background:0 0;border:none;align-items:center;gap:8px;min-height:32px;padding:6px 8px;font-size:13px;display:flex}.prov-account-row:hover{background:var(--raised)}.prov-account-row.active{cursor:default}.prov-account-row .prov-account-email{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}.prov-account-row .badge{flex:none}.prov-account-remove{color:var(--muted);cursor:pointer;border-radius:var(--radius-xs);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:4px;display:inline-flex}.prov-account-remove:hover{color:var(--red);background:var(--red-soft)}.prov-account-add{color:var(--muted);font-size:12.5px}.prov-account-add:hover{color:var(--accent-hover)}.prov-account-keyform{cursor:default;gap:6px}.prov-account-keyform:hover{background:0 0}.prov-account-keyform .input-sm{flex:auto;min-width:0;height:28px;padding:4px 8px;font-size:12.5px}.oauth-grid{grid-template-columns:minmax(140px,max-content) minmax(0,1fr) max-content;align-items:center;gap:10px 16px;display:grid}.oauth-row{grid-column:1/-1;grid-template-columns:subgrid;align-items:center;min-height:30px;display:grid}.oauth-name{align-items:center;gap:8px;min-width:0;font-size:13px;font-weight:600;display:inline-flex}.oauth-name-text{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.provider-icon-sm{border-radius:6px;width:24px;height:24px}.provider-icon-sm img{width:15px;height:15px}.oauth-status{align-items:center;gap:7px;min-width:0;font-size:13px;display:inline-flex}.oauth-email{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.oauth-actions{justify-content:flex-end;align-items:center;gap:8px;min-width:0;display:inline-flex}.oauth-login-hint{flex-wrap:wrap;grid-column:1/-1;align-items:center;gap:8px;font-size:12px;display:inline-flex}@media (width<=760px){.app{grid-template-columns:1fr}.sidebar{z-index:20;border-right:none;border-bottom:1px solid var(--border);background:var(--rail);flex-flow:wrap;align-items:center;gap:0;min-width:0;height:auto;padding:0 10px;position:sticky;top:0}.brand{flex:auto;order:1;width:auto;padding:10px 4px}.sidebar-foot{flex-direction:row;flex:none;order:2;gap:4px;margin:0;padding:0}.sidebar-foot .sidebar-link{display:none}.theme-toggle{justify-content:center;min-width:44px;min-height:44px;padding:8px}.theme-toggle .mode{display:none}.sidebar nav{border-top:1px solid var(--border-soft);flex:100%;order:3;grid-template-columns:repeat(4,minmax(0,1fr));gap:2px;min-width:0;margin:0;padding:4px 0 8px;display:grid}.nav-item{text-align:center;text-overflow:ellipsis;white-space:nowrap;flex-direction:column;justify-content:center;gap:3px;width:auto;min-height:44px;padding:6px 4px;font-size:12px;overflow:hidden}.nav-item svg{width:16px;height:16px}.main-inner{padding:22px 18px 48px}.setting-row{flex-wrap:wrap}.setting-row .setting-copy{flex:100%!important}.setting-row .setting-controls{width:100%}.setting-row .setting-controls .select-sm{flex:1 1 0;min-width:0}.setting-row>.select-sm{width:100%}.api-form-row{flex-direction:column;align-items:stretch}.api-form-row .btn{width:100%;min-height:40px}.stat-row>.stat{min-width:100px}.tbl{min-width:460px}.usage-cards{grid-template-columns:repeat(2,minmax(0,1fr))!important}.provider-quota{padding-left:16px}.prov-meta{grid-template-columns:max-content minmax(0,1fr);width:100%;display:grid}.prov-meta .chip~span:not(:last-child){display:none}.oauth-grid{grid-template-columns:minmax(0,auto) minmax(0,1fr) max-content;column-gap:8px}.lang-toggle .select-dropdown-beside{top:calc(100% + 6px);left:auto;right:0}}.usage-cards{grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:8px;display:grid}.usage-cards .stat-value{margin-top:4px;font-size:20px;font-weight:600}.usage-range{border:1px solid var(--border);background:var(--surface);border-radius:999px;gap:2px;padding:2px;display:inline-flex}.usage-range-btn{color:var(--muted);cursor:pointer;font:inherit;background:0 0;border:none;border-radius:999px;padding:4px 12px}.usage-range-btn.active{background:var(--raised);color:var(--text);font-weight:600}.panel-title{color:var(--text);margin:0 0 12px;font-size:14px;font-weight:600}.panel-head{justify-content:space-between;align-items:center;gap:12px;margin-bottom:12px;display:flex}.panel-head .panel-title{margin:0}.panel-head .input{max-width:220px}.heatmap{--hm-cell:11px;--hm-gap:3px;flex-direction:column;gap:6px;padding-bottom:4px;display:flex;overflow-x:auto}.heatmap-months{color:var(--muted);width:max-content;margin-bottom:-2px;font-size:11px;display:grid}.heatmap-day-spacer{grid-column:1}.heatmap-month{white-space:nowrap}.heatmap-body{gap:var(--hm-gap);width:max-content;display:flex}.heatmap-days{grid-template-rows:repeat(7, var(--hm-cell));row-gap:var(--hm-gap);color:var(--muted);background:var(--surface);z-index:1;flex-shrink:0;align-items:center;width:25px;font-size:10px;display:grid;position:sticky;left:0}.heatmap-grid{gap:var(--hm-gap);display:grid}.heatmap-week{grid-template-rows:repeat(7, var(--hm-cell));gap:var(--hm-gap);display:grid}.heatmap-cell{width:var(--hm-cell);height:var(--hm-cell);background:var(--border);border-radius:2px}.heatmap-cell-0{background:var(--border)}.heatmap-cell-1{background:color-mix(in oklch, var(--green) 25%, var(--surface))}.heatmap-cell-2{background:color-mix(in oklch, var(--green) 50%, var(--surface))}.heatmap-cell-3{background:color-mix(in oklch, var(--green) 75%, var(--surface))}.heatmap-cell-4{background:var(--green)}.heatmap-legend{align-self:flex-end;align-items:center;gap:4px;font-size:12px;display:inline-flex;position:sticky;right:0}.heatmap-legend .heatmap-cell{width:10px;height:10px}.heatmap-tip{z-index:10;pointer-events:none;background:var(--surface);border:1px solid var(--border);white-space:nowrap;border-radius:8px;padding:6px 10px;font-size:12px;position:fixed;transform:translate(-50%,-100%)translateY(-8px);box-shadow:0 6px 20px #00000059}.heatmap-tip-date{color:var(--text);margin-bottom:2px;font-weight:600}.heatmap-tip-val{color:var(--text);font-variant-numeric:tabular-nums}.heatmap-tip-req{font-size:11px}.usage-bar{background:var(--border);border-radius:999px;width:100%;min-width:60px;height:6px;overflow:hidden}.usage-bar-fill{background:var(--green);border-radius:999px;height:100%}.daybars{grid-template-columns:repeat(7,1fr);align-items:end;gap:10px;height:180px;padding-top:8px;display:grid}.daybar{flex-direction:column;justify-content:flex-end;align-items:center;gap:6px;height:100%;display:flex;position:relative}.daybar-track{background:var(--border);border-radius:6px;flex:1;align-items:flex-end;width:100%;max-width:48px;display:flex;overflow:hidden}.daybar-stack{border-radius:6px 6px 0 0;flex-direction:column-reverse;width:100%;min-height:2px;transition:height .2s;display:flex;overflow:hidden}.daybar-seg{width:100%;min-height:1px}.daybar-count{color:var(--text);font-size:12px;font-weight:600}.daybar-label{white-space:nowrap;font-size:11px}.daybar:hover .daybar-track{outline:1px solid var(--border)}.daybar-tip{z-index:5;background:var(--surface);border:1px solid var(--border);pointer-events:none;border-radius:8px;min-width:160px;padding:8px 10px;position:absolute;bottom:calc(100% + 6px);left:50%;transform:translate(-50%);box-shadow:0 6px 20px #00000059}.daybar-tip-date{color:var(--text);white-space:nowrap;margin-bottom:6px;font-size:12px;font-weight:600}.daybar-tip-row{align-items:center;gap:8px;font-size:12px;line-height:1.7;display:flex}.daybar-tip-swatch{border-radius:2px;flex-shrink:0;width:10px;height:10px}.daybar-tip-name{color:var(--text);white-space:nowrap;text-overflow:ellipsis;flex:1;max-width:160px;overflow:hidden}.daybar-tip-val{color:var(--muted);font-variant-numeric:tabular-nums}.usage-scroll{max-height:360px;overflow-y:auto}.usage-scroll thead th{z-index:1;background:var(--surface);position:sticky;top:0}
|
package/gui/dist/index.html
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
} catch (e) {}
|
|
17
17
|
})();
|
|
18
18
|
</script>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
20
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-Bp8dDrs5.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C0xVu72_.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -21,6 +21,7 @@ export const FORWARD_HEADERS = [
|
|
|
21
21
|
"x-codex-turn-state",
|
|
22
22
|
"x-codex-window-id",
|
|
23
23
|
"x-oai-attestation",
|
|
24
|
+
"x-openai-subagent",
|
|
24
25
|
"x-responsesapi-include-timing-metrics",
|
|
25
26
|
];
|
|
26
27
|
|
|
@@ -174,6 +175,46 @@ function stripPreviousResponseId(body: unknown, strip: boolean): unknown {
|
|
|
174
175
|
return rest;
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Hosted tool types whose server-side function names collide with the client tools Codex
|
|
180
|
+
* declares for the matching app skill. Codex sends BOTH (e.g. hosted `image_generation` plus a
|
|
181
|
+
* declared `image_gen.imagegen` function/namespace tool for the imagegen skill). The ChatGPT
|
|
182
|
+
* backend tolerates the pair, but the platform `/v1/responses` rejects it:
|
|
183
|
+
* `Invalid Value: 'tools'. Function 'image_gen.imagegen' conflicts with a hosted tool in the
|
|
184
|
+
* same request.` Keyed hosted-type → conflicting client tool-name prefix; the hosted entry is
|
|
185
|
+
* dropped (the declared tool wins — Codex executes the skill client-side either way).
|
|
186
|
+
*/
|
|
187
|
+
const HOSTED_TOOL_NAME_CONFLICTS: ReadonlyArray<{ hostedType: string; namePrefix: string }> = [
|
|
188
|
+
{ hostedType: "image_generation", namePrefix: "image_gen" },
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Drop hosted tools whose names collide with declared function/namespace tools (see
|
|
193
|
+
* HOSTED_TOOL_NAME_CONFLICTS). Only applies on the API-key platform path: the ChatGPT backend
|
|
194
|
+
* ("forward" mode) accepts the pair, and stripping there would disable native imagegen. No-op
|
|
195
|
+
* (returns the original reference) when nothing matches.
|
|
196
|
+
*/
|
|
197
|
+
function stripConflictingHostedTools(body: unknown): unknown {
|
|
198
|
+
if (!isPlainObject(body) || !Array.isArray(body.tools)) return body;
|
|
199
|
+
const allTools = body.tools;
|
|
200
|
+
|
|
201
|
+
const conflicting = HOSTED_TOOL_NAME_CONFLICTS.filter(c =>
|
|
202
|
+
allTools.some(t => {
|
|
203
|
+
if (!isPlainObject(t) || typeof t.name !== "string") return false;
|
|
204
|
+
if (t.type === "namespace") return t.name === c.namePrefix;
|
|
205
|
+
return t.name === c.namePrefix || t.name.startsWith(`${c.namePrefix}.`);
|
|
206
|
+
}),
|
|
207
|
+
);
|
|
208
|
+
if (conflicting.length === 0) return body;
|
|
209
|
+
|
|
210
|
+
const tools = allTools.filter(t => {
|
|
211
|
+
const type = isPlainObject(t) && typeof t.type === "string" ? t.type : undefined;
|
|
212
|
+
if (!type) return true;
|
|
213
|
+
return !conflicting.some(c => c.hostedType === type);
|
|
214
|
+
});
|
|
215
|
+
return tools.length === allTools.length ? body : { ...body, tools };
|
|
216
|
+
}
|
|
217
|
+
|
|
177
218
|
/**
|
|
178
219
|
* Remove hosted tool entries the target native slug rejects, so the OAuth-passthrough body never
|
|
179
220
|
* carries a tool the upstream model 400s on. No-op (returns the original reference) when nothing
|
|
@@ -236,6 +277,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
236
277
|
forward || parsed._previousResponseInputExpanded === true,
|
|
237
278
|
);
|
|
238
279
|
if (forward) outBody = repairOrphanedInputItems(outBody, unexpandedMiss);
|
|
280
|
+
else outBody = stripConflictingHostedTools(outBody);
|
|
239
281
|
return {
|
|
240
282
|
url,
|
|
241
283
|
method: "POST",
|
package/src/codex/catalog.ts
CHANGED
|
@@ -569,6 +569,18 @@ function codexCommandCandidates(): string[] {
|
|
|
569
569
|
return unique(candidates);
|
|
570
570
|
}
|
|
571
571
|
|
|
572
|
+
/**
|
|
573
|
+
* Windows probe guard: only PE/batch launchers can be spawned as processes. Anything
|
|
574
|
+
* else pulled from the shim state (the extensionless Git-Bash sh backup
|
|
575
|
+
* `codex.opencodex-real`, `.ps1` scripts) risks falling through to the cmd/ShellExecute
|
|
576
|
+
* document-association path — Windows then OPENS the file in the user's editor
|
|
577
|
+
* (e.g. VS Code) on every `codex` launch instead of executing it.
|
|
578
|
+
*/
|
|
579
|
+
export function isSpawnableCodexCandidate(path: string, platform: NodeJS.Platform = process.platform): boolean {
|
|
580
|
+
if (platform !== "win32") return true;
|
|
581
|
+
return /\.(cmd|bat|exe|com)$/i.test(path);
|
|
582
|
+
}
|
|
583
|
+
|
|
572
584
|
function codexShimCommandCandidates(): string[] {
|
|
573
585
|
try {
|
|
574
586
|
const state = JSON.parse(readFileSync(join(getConfigDir(), "codex-shim.json"), "utf8")) as {
|
|
@@ -582,7 +594,7 @@ function codexShimCommandCandidates(): string[] {
|
|
|
582
594
|
for (const file of files) {
|
|
583
595
|
for (const value of [file.backupPath, file.originalPath, file.wrapperPath]) {
|
|
584
596
|
if (typeof value !== "string" || value.length === 0) continue;
|
|
585
|
-
if (
|
|
597
|
+
if (!isSpawnableCodexCandidate(value)) continue;
|
|
586
598
|
out.push(value);
|
|
587
599
|
}
|
|
588
600
|
}
|
|
@@ -459,6 +459,10 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
459
459
|
modelReasoningEfforts: Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id, DEEPSEEK_THINKING_EFFORTS])),
|
|
460
460
|
modelReasoningEffortMap: Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id, DEEPSEEK_THINKING_REASONING_MAP])),
|
|
461
461
|
preserveReasoningContentModels: DEEPSEEK_THINKING_MODELS,
|
|
462
|
+
// Issue #88: every DeepSeek API model is text-only input (no image support upstream) — the
|
|
463
|
+
// vision sidecar describes attached images for them, and the catalog advertises image input
|
|
464
|
+
// on their behalf (same treatment as opencode-go's DeepSeek V4 entries above).
|
|
465
|
+
noVisionModels: ["deepseek-chat", "deepseek-reasoner", ...DEEPSEEK_THINKING_MODELS],
|
|
462
466
|
},
|
|
463
467
|
// llama-3.3-70b was deprecated by Cerebras on 2026-02-16. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
|
|
464
468
|
{ id: "cerebras", label: "Cerebras", baseUrl: "https://api.cerebras.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://cloud.cerebras.ai/platform/apikeys", defaultModel: "gpt-oss-120b" },
|
package/src/reasoning-effort.ts
CHANGED
|
@@ -19,6 +19,11 @@ export function isCodexReasoningEffort(effort: string): boolean {
|
|
|
19
19
|
return CODEX_REASONING_SET.has(effort);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/** Position of `effort` in the Codex ladder (low=0 .. ultra=5), or -1 when not a ladder member. */
|
|
23
|
+
export function codexEffortRank(effort: string): number {
|
|
24
|
+
return CODEX_REASONING_ORDER.indexOf(effort);
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
export function modelRecordValue<T>(record: Record<string, T> | undefined, modelId: string): T | undefined {
|
|
23
28
|
if (!record) return undefined;
|
|
24
29
|
if (Object.prototype.hasOwnProperty.call(record, modelId)) return record[modelId];
|
package/src/responses/parser.ts
CHANGED
|
@@ -278,6 +278,34 @@ export function parseRequest(body: unknown): OcxParsedRequest {
|
|
|
278
278
|
continue;
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
if (effectiveType === "agent_message") {
|
|
282
|
+
const agentMessage = item as {
|
|
283
|
+
author?: string;
|
|
284
|
+
recipient?: string;
|
|
285
|
+
content?: unknown;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const content = inputContentParts(
|
|
289
|
+
agentMessage.content as unknown[] | string | undefined,
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const hasContent =
|
|
293
|
+
typeof content === "string"
|
|
294
|
+
? content.trim().length > 0
|
|
295
|
+
: content.length > 0;
|
|
296
|
+
|
|
297
|
+
// An agent_message is external input delivered to the parent agent.
|
|
298
|
+
// Preserve it as a user-role turn so signed Anthropic thinking blocks
|
|
299
|
+
// on either side are never merged into one modified assistant response.
|
|
300
|
+
messages.push({
|
|
301
|
+
role: "user",
|
|
302
|
+
content: hasContent ? content : "(sub-agent message received)",
|
|
303
|
+
timestamp: now,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
|
|
281
309
|
if (effectiveType === "message") {
|
|
282
310
|
const msg = item as { role?: string; content?: unknown };
|
|
283
311
|
switch (msg.role) {
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hard reasoning-effort caps (devlog/260710_subagent_effort_intercept).
|
|
3
|
+
*
|
|
4
|
+
* Prompt-side effort designation (injectionEffort) is advisory only: codex-rs inherits the
|
|
5
|
+
* parent's effective effort when spawn_agent carries no model/effort args
|
|
6
|
+
* (multi_agents_common.rs resolve defaults), rejects overrides on full-history forks, and a
|
|
7
|
+
* non-empty agent-role file rebuilds the child Config and silently drops spawn-time
|
|
8
|
+
* model/effort. So a session whose config default is ultra leaks max-tier children whenever
|
|
9
|
+
* the parent model spawns bare. This module is the enforcement path: it rewrites the effort
|
|
10
|
+
* of proxied turns at the single choke point every HTTP/WS turn passes through
|
|
11
|
+
* (handleResponses), using the same dual-shape rewrite contract as nativeEffortClamp —
|
|
12
|
+
* parsed.options.reasoning feeds routed adapters, _rawBody.reasoning.effort feeds the
|
|
13
|
+
* ChatGPT passthrough serializer.
|
|
14
|
+
*/
|
|
15
|
+
import type { OcxConfig, OcxParsedRequest, OcxProviderConfig } from "../types";
|
|
16
|
+
import { modelInList } from "../types";
|
|
17
|
+
import { codexEffortRank, configuredReasoningEfforts, isCodexReasoningEffort, modelRecordValue } from "../reasoning-effort";
|
|
18
|
+
import { catalogModelEfforts } from "../codex/catalog";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* True when the request carries codex-rs's spawned-child markers, matched EXACTLY.
|
|
22
|
+
* Source of truth (openai/codex @ 6138909d): every collab-spawned child turn sends
|
|
23
|
+
* `x-openai-subagent: collab_spawn` (core/src/responses_metadata.rs) and embeds
|
|
24
|
+
* `"subagent_kind":"thread_spawn"` in the JSON `x-codex-turn-metadata` compatibility
|
|
25
|
+
* header. Both are checked: the WS bridge rebuilds internal requests from the
|
|
26
|
+
* FORWARD_HEADERS allowlist, so either header alone is sufficient evidence.
|
|
27
|
+
*
|
|
28
|
+
* Exact matching matters: upstream emits `x-openai-subagent` for OTHER internal
|
|
29
|
+
* turn categories too (review, compact, memory_consolidation, arbitrary "other"
|
|
30
|
+
* sources — responses_metadata.rs subagent_source). Those are maintenance turns,
|
|
31
|
+
* not spawned children, and must never trip subagentEffortCap.
|
|
32
|
+
*/
|
|
33
|
+
export function isThreadSpawnRequest(headers: Headers): boolean {
|
|
34
|
+
if (headers.get("x-openai-subagent") === "collab_spawn") return true;
|
|
35
|
+
const turnMeta = headers.get("x-codex-turn-metadata");
|
|
36
|
+
if (!turnMeta) return false;
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(turnMeta) as { subagent_kind?: unknown };
|
|
39
|
+
return parsed.subagent_kind === "thread_spawn";
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The effective ceiling for this turn, or undefined when no configured cap applies. */
|
|
46
|
+
export function effortCapFor(config: OcxConfig, subagent: boolean): string | undefined {
|
|
47
|
+
const caps: string[] = [];
|
|
48
|
+
if (config.effortCap && isCodexReasoningEffort(config.effortCap)) caps.push(config.effortCap);
|
|
49
|
+
if (subagent && config.subagentEffortCap && isCodexReasoningEffort(config.subagentEffortCap)) {
|
|
50
|
+
caps.push(config.subagentEffortCap);
|
|
51
|
+
}
|
|
52
|
+
if (caps.length === 0) return undefined;
|
|
53
|
+
return caps.reduce((low, cap) => (codexEffortRank(cap) < codexEffortRank(low) ? cap : low));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Whether the effort caps apply to this turn at all. Caps are a V2-surface feature
|
|
58
|
+
* (v1 sub-agents are pinned via explicit spawn args + injectionEffort prompting, so
|
|
59
|
+
* the ultra-default leak this module intercepts is v2-specific):
|
|
60
|
+
* - compaction turns are maintenance, not agent turns: they bypass caps entirely so
|
|
61
|
+
* native /v1/responses/compact (forwarded, never enters handleResponses) and routed
|
|
62
|
+
* compaction (synthesized internal request) get identical cap semantics.
|
|
63
|
+
* - multiAgentMode "v1" disables caps entirely (mirrors the GUI hiding the panel).
|
|
64
|
+
* - a main turn qualifies when its own tool list carries the v2 collab surface.
|
|
65
|
+
* - a CHILD turn is admitted by its spawned-child markers (isThreadSpawnRequest)
|
|
66
|
+
* REGARDLESS of tool surface: depth-limited leaves carry no collab tools (surface
|
|
67
|
+
* null) while children below the spawn-depth limit retain collab tools (spec_plan.rs
|
|
68
|
+
* leaf guard), so tool sniffing alone would cap siblings inconsistently.
|
|
69
|
+
* - a v1-surface MAIN turn (no child markers) never qualifies.
|
|
70
|
+
*/
|
|
71
|
+
export function effortCapAppliesTo(
|
|
72
|
+
surface: "v1" | "v2" | null,
|
|
73
|
+
headers: Headers,
|
|
74
|
+
config: OcxConfig,
|
|
75
|
+
compaction = false,
|
|
76
|
+
): boolean {
|
|
77
|
+
if (compaction) return false;
|
|
78
|
+
if (config.multiAgentMode === "v1") return false;
|
|
79
|
+
return surface === "v2" || isThreadSpawnRequest(headers);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The routed model's supported effort ladder for cap resolution, from the ROUTE's
|
|
84
|
+
* registry-merged provider (router.ts routedProviderConfig) — the persisted
|
|
85
|
+
* config.providers entry misses registry seeds, and bare ids can route via
|
|
86
|
+
* defaultModel/model-list/default-provider, so no "/" heuristic anywhere.
|
|
87
|
+
*
|
|
88
|
+
* - `[]` -> the model intentionally exposes no effort control (noReasoningModels or
|
|
89
|
+
* an explicitly empty configured ladder): cap resolution strips.
|
|
90
|
+
* - list -> sanitized + healed ladder (configuredReasoningEfforts).
|
|
91
|
+
* - undefined -> unknown. Includes the raw-nonempty-but-non-rankable case (e.g. a
|
|
92
|
+
* thinking-toggle ladder of ["enabled"]): sanitizing would flatten it to []
|
|
93
|
+
* and mis-classify it as "no effort control", so it stays unknown.
|
|
94
|
+
*
|
|
95
|
+
* Catalog fallback fires only for the ChatGPT-backend native passthrough IDENTITY
|
|
96
|
+
* (adapter "openai-responses" + authMode "forward", the fresh-install `openai`
|
|
97
|
+
* provider shape): the injected catalog is authoritative exactly for models Codex
|
|
98
|
+
* validates against that backend. A custom responses provider (key mode) serving a
|
|
99
|
+
* native-looking bare id must NOT inherit the unrelated native ladder.
|
|
100
|
+
*/
|
|
101
|
+
export function supportedLadderFor(route: { provider: OcxProviderConfig; modelId: string }): string[] | undefined {
|
|
102
|
+
const { provider, modelId } = route;
|
|
103
|
+
if (modelInList(provider.noReasoningModels, modelId)) return [];
|
|
104
|
+
const raw = modelRecordValue(provider.modelReasoningEfforts, modelId) ?? provider.reasoningEfforts;
|
|
105
|
+
if (raw !== undefined) {
|
|
106
|
+
const sanitized = configuredReasoningEfforts(provider, modelId) ?? [];
|
|
107
|
+
if (sanitized.length === 0 && raw.length > 0) return undefined;
|
|
108
|
+
return sanitized;
|
|
109
|
+
}
|
|
110
|
+
if (provider.adapter === "openai-responses" && provider.authMode === "forward") {
|
|
111
|
+
const efforts = catalogModelEfforts([modelId]).get(modelId);
|
|
112
|
+
if (efforts && efforts.length > 0) return efforts;
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Resolve the configured cap against the model's supported ladder. Returns the effective
|
|
119
|
+
* ceiling rung, or null when the turn must be STRIPPED of its effort entirely. The cap
|
|
120
|
+
* NEVER raises: when rankable rungs exist but none sits at or below the cap, the model
|
|
121
|
+
* cannot run within the ceiling, so the effort is stripped and the provider default
|
|
122
|
+
* applies (never a rung above the cap).
|
|
123
|
+
*/
|
|
124
|
+
export function resolveCappedEffort(cap: string, supported: readonly string[] | undefined): string | null {
|
|
125
|
+
if (supported === undefined) return cap;
|
|
126
|
+
const rankable = supported.filter(isCodexReasoningEffort);
|
|
127
|
+
if (rankable.length === 0) {
|
|
128
|
+
// Nonempty but non-rankable (e.g. ["enabled"]) -> unknown ladder, cap as-is.
|
|
129
|
+
// Genuinely empty -> no effort control at all -> strip.
|
|
130
|
+
return supported.length > 0 ? cap : null;
|
|
131
|
+
}
|
|
132
|
+
const capRank = codexEffortRank(cap);
|
|
133
|
+
let best: string | null = null;
|
|
134
|
+
for (const rung of rankable) {
|
|
135
|
+
const rank = codexEffortRank(rung);
|
|
136
|
+
if (rank <= capRank && (best === null || rank > codexEffortRank(best))) best = rung;
|
|
137
|
+
}
|
|
138
|
+
return best;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Cap the turn's reasoning effort in BOTH request shapes. Non-strip resolution only
|
|
143
|
+
* lowers: efforts at or below the resolved ceiling (and non-ladder/absent efforts) pass
|
|
144
|
+
* untouched. Strip resolution (model exposes no effort control, or no supported rung
|
|
145
|
+
* fits under the cap) removes whatever effort is present — regardless of its rank —
|
|
146
|
+
* from both shapes while preserving `reasoning.summary`. Returns the applied rewrite
|
|
147
|
+
* for request-log annotation (`to: "none"` on strip), or null when nothing changed.
|
|
148
|
+
*/
|
|
149
|
+
export function applyEffortCap(
|
|
150
|
+
parsed: OcxParsedRequest,
|
|
151
|
+
headers: Headers,
|
|
152
|
+
config: OcxConfig,
|
|
153
|
+
supported?: readonly string[] | undefined,
|
|
154
|
+
): { from: string; to: string; subagent: boolean } | null {
|
|
155
|
+
const subagent = isThreadSpawnRequest(headers);
|
|
156
|
+
const cap = effortCapFor(config, subagent);
|
|
157
|
+
if (!cap) return null;
|
|
158
|
+
const resolved = resolveCappedEffort(cap, supported);
|
|
159
|
+
const requested = parsed.options.reasoning;
|
|
160
|
+
const raw = parsed._rawBody as { reasoning?: { effort?: string } } | undefined;
|
|
161
|
+
if (resolved === null) {
|
|
162
|
+
if (!requested) return null;
|
|
163
|
+
parsed.options.reasoning = undefined;
|
|
164
|
+
if (raw?.reasoning && typeof raw.reasoning === "object") delete raw.reasoning.effort;
|
|
165
|
+
return { from: requested, to: "none", subagent };
|
|
166
|
+
}
|
|
167
|
+
if (!requested || !isCodexReasoningEffort(requested)) return null;
|
|
168
|
+
if (codexEffortRank(requested) <= codexEffortRank(resolved)) return null;
|
|
169
|
+
parsed.options.reasoning = resolved;
|
|
170
|
+
if (raw?.reasoning && typeof raw.reasoning === "object") raw.reasoning.effort = resolved;
|
|
171
|
+
return { from: requested, to: resolved, subagent };
|
|
172
|
+
}
|
package/src/server/index.ts
CHANGED
|
@@ -118,6 +118,7 @@ export {
|
|
|
118
118
|
import { disableResponsesRequestTimeout, handleResponses, handleResponsesCompact } from "./responses";
|
|
119
119
|
export { disableResponsesRequestTimeout, linkAbortSignal } from "./responses";
|
|
120
120
|
import { handleImages } from "./images";
|
|
121
|
+
import { handleSearch } from "./search";
|
|
121
122
|
import { fetchAllModels, handleManagementAPI, VERSION } from "./management-api";
|
|
122
123
|
|
|
123
124
|
const MAX_WS_FRAME_BYTES = 50 * 1024 * 1024;
|
|
@@ -331,6 +332,33 @@ export function startServer(port?: number) {
|
|
|
331
332
|
return withCors(response, req, config);
|
|
332
333
|
}
|
|
333
334
|
|
|
335
|
+
if (url.pathname === "/v1/alpha/search" && req.method === "POST") {
|
|
336
|
+
disableResponsesRequestTimeout(req, requestServer);
|
|
337
|
+
if (isDraining()) {
|
|
338
|
+
return new Response("Service shutting down", {
|
|
339
|
+
status: 503,
|
|
340
|
+
headers: { ...corsHeaders(req, config), "Retry-After": "5" },
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
const apiAuthError = requireApiAuth(req, config, "data-plane");
|
|
344
|
+
if (apiAuthError) return withCors(apiAuthError, req, config);
|
|
345
|
+
if (!isAllowedRequestOrigin(req, config)) {
|
|
346
|
+
return withCors(formatErrorResponse(403, "origin_rejected", "cross-origin data-plane request blocked"), req, config);
|
|
347
|
+
}
|
|
348
|
+
const start = Date.now();
|
|
349
|
+
const requestId = nextRequestLogId(start);
|
|
350
|
+
const logCtx: RequestLogContext = { model: "web_search", provider: "unknown" };
|
|
351
|
+
const response = await handleSearch(req, config, logCtx);
|
|
352
|
+
addFinalRequestLog(
|
|
353
|
+
requestId,
|
|
354
|
+
start,
|
|
355
|
+
logCtx,
|
|
356
|
+
response.status,
|
|
357
|
+
response.status === 499 ? { closeReason: "client_cancel" } : undefined,
|
|
358
|
+
);
|
|
359
|
+
return withCors(response, req, config);
|
|
360
|
+
}
|
|
361
|
+
|
|
334
362
|
if (url.pathname === "/v1/responses" && req.method === "POST") {
|
|
335
363
|
disableResponsesRequestTimeout(req, requestServer);
|
|
336
364
|
if (isDraining()) {
|
|
@@ -370,7 +398,7 @@ export function startServer(port?: number) {
|
|
|
370
398
|
|
|
371
399
|
// Data-plane guard: unknown /v1/* paths must fail with JSON 404, never fall through to the
|
|
372
400
|
// GUI static handler (extensionless paths would get index.html with HTTP 200 and codex-rs
|
|
373
|
-
// endpoint clients —
|
|
401
|
+
// endpoint clients — memories/*, realtime/* — would surface confusing
|
|
374
402
|
// serde decode errors instead of a clean not-found).
|
|
375
403
|
if (url.pathname.startsWith("/v1/")) {
|
|
376
404
|
return withCors(formatErrorResponse(404, "not_found", `Unknown endpoint: ${req.method} ${url.pathname}`), req, config);
|
|
@@ -572,6 +572,34 @@ export async function handleManagementAPI(req: Request, url: URL, config: OcxCon
|
|
|
572
572
|
return jsonResponse({ ok: true, model: config.injectionModel ?? null, effort: config.injectionEffort ?? null, prompt: config.injectionPrompt ?? null });
|
|
573
573
|
}
|
|
574
574
|
|
|
575
|
+
// Hard reasoning-effort caps (devlog/260710_subagent_effort_intercept): a global ceiling and a
|
|
576
|
+
// sub-agent-only ceiling, enforced per-request in handleResponses (src/server/effort-policy.ts).
|
|
577
|
+
// Key semantics per field: absent -> unchanged; null/"" -> clear; ladder value -> set; else 400.
|
|
578
|
+
if (url.pathname === "/api/effort-caps" && req.method === "GET") {
|
|
579
|
+
const { CODEX_REASONING_LEVELS } = await import("../reasoning-effort");
|
|
580
|
+
return jsonResponse({
|
|
581
|
+
effortCap: config.effortCap ?? null,
|
|
582
|
+
subagentEffortCap: config.subagentEffortCap ?? null,
|
|
583
|
+
efforts: CODEX_REASONING_LEVELS.map(l => l.effort),
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
if (url.pathname === "/api/effort-caps" && req.method === "PUT") {
|
|
587
|
+
let body: { effortCap?: unknown; subagentEffortCap?: unknown };
|
|
588
|
+
try { body = await req.json(); } catch { return jsonResponse({ error: "invalid JSON body" }, 400); }
|
|
589
|
+
const { isCodexReasoningEffort } = await import("../reasoning-effort");
|
|
590
|
+
for (const key of ["effortCap", "subagentEffortCap"] as const) {
|
|
591
|
+
if (!(key in body)) continue;
|
|
592
|
+
const value = body[key];
|
|
593
|
+
if (value === null || value === "") { delete config[key]; continue; }
|
|
594
|
+
if (typeof value !== "string" || !isCodexReasoningEffort(value)) {
|
|
595
|
+
return jsonResponse({ error: `unknown reasoning effort "${String(value)}"` }, 400);
|
|
596
|
+
}
|
|
597
|
+
config[key] = value;
|
|
598
|
+
}
|
|
599
|
+
saveConfig(config);
|
|
600
|
+
return jsonResponse({ ok: true, effortCap: config.effortCap ?? null, subagentEffortCap: config.subagentEffortCap ?? null });
|
|
601
|
+
}
|
|
602
|
+
|
|
575
603
|
// Subagent model picker: which ≤5 routed models Codex's spawn_agent advertises (it shows the
|
|
576
604
|
// first 5 routed catalog entries). PUT reorders the injected catalog so the chosen ones lead.
|
|
577
605
|
if (url.pathname === "/api/subagent-models" && req.method === "GET") {
|
package/src/server/responses.ts
CHANGED
|
@@ -298,10 +298,19 @@ function encryptedSlotParts(payload: string): Array<Record<string, string>> {
|
|
|
298
298
|
return parts.length > 0 ? parts : [{ type: "input_text", text: payload }];
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
function hasEncryptedContentPart(content: unknown): boolean {
|
|
302
|
+
return Array.isArray(content) && content.some(part => (
|
|
303
|
+
part && typeof part === "object"
|
|
304
|
+
&& (part as { type?: unknown }).type === "encrypted_content"
|
|
305
|
+
));
|
|
306
|
+
}
|
|
307
|
+
|
|
301
308
|
/**
|
|
302
309
|
* Rewrite non-ciphertext `{type:"encrypted_content"}` parts into `{type:"input_text"}`
|
|
303
|
-
* throughout a
|
|
304
|
-
*
|
|
310
|
+
* throughout a request's input items (message content and function_call_output content
|
|
311
|
+
* arrays share the part shape, codex-rs protocol/models.rs). An `agent_message` whose
|
|
312
|
+
* payload becomes entirely plaintext is normalized to a user message so routed parsers
|
|
313
|
+
* that do not understand the internal item type still deliver the spawn task.
|
|
305
314
|
* Genuine backend blobs are left byte-identical so replay/cache semantics survive, and
|
|
306
315
|
* MIXED slots (plaintext preamble + embedded Fernet task body) are split so the backend
|
|
307
316
|
* decrypts the blob while the prose passes as text. Returns the number of parts rewritten.
|
|
@@ -309,7 +318,8 @@ function encryptedSlotParts(payload: string): Array<Record<string, string>> {
|
|
|
309
318
|
export function sanitizeEncryptedContentInPlace(input: unknown): number {
|
|
310
319
|
if (!Array.isArray(input)) return 0;
|
|
311
320
|
let rewritten = 0;
|
|
312
|
-
const visit = (node: unknown):
|
|
321
|
+
const visit = (node: unknown): number => {
|
|
322
|
+
const before = rewritten;
|
|
313
323
|
if (Array.isArray(node)) {
|
|
314
324
|
for (let i = 0; i < node.length; i += 1) {
|
|
315
325
|
const child = node[i] as unknown;
|
|
@@ -327,13 +337,26 @@ export function sanitizeEncryptedContentInPlace(input: unknown): number {
|
|
|
327
337
|
continue;
|
|
328
338
|
}
|
|
329
339
|
}
|
|
330
|
-
visit(child);
|
|
340
|
+
const childRewrites = visit(child);
|
|
341
|
+
if (
|
|
342
|
+
childRewrites > 0
|
|
343
|
+
&& child && typeof child === "object"
|
|
344
|
+
&& (child as { type?: unknown }).type === "agent_message"
|
|
345
|
+
&& !hasEncryptedContentPart((child as { content?: unknown }).content)
|
|
346
|
+
) {
|
|
347
|
+
const message = child as { type: string; role?: string; author?: unknown; recipient?: unknown };
|
|
348
|
+
message.type = "message";
|
|
349
|
+
message.role = "user";
|
|
350
|
+
delete message.author;
|
|
351
|
+
delete message.recipient;
|
|
352
|
+
}
|
|
331
353
|
}
|
|
332
|
-
return;
|
|
354
|
+
return rewritten - before;
|
|
333
355
|
}
|
|
334
356
|
if (node && typeof node === "object") {
|
|
335
357
|
for (const value of Object.values(node)) visit(value);
|
|
336
358
|
}
|
|
359
|
+
return rewritten - before;
|
|
337
360
|
};
|
|
338
361
|
visit(input);
|
|
339
362
|
return rewritten;
|
|
@@ -410,6 +433,21 @@ export async function handleResponses(
|
|
|
410
433
|
body = expandPreviousResponseInput(body);
|
|
411
434
|
const previousResponseInputExpanded = body !== originalBody;
|
|
412
435
|
|
|
436
|
+
// Spawn-message compatibility (both directions): agent_message task payloads ride in
|
|
437
|
+
// encrypted_content slots as plaintext. Rewrite them to input_text on the RAW body BEFORE
|
|
438
|
+
// parsing so every consumer sees the payload: parseRequest (routed/translated providers read
|
|
439
|
+
// the parsed messages) and the native passthrough (_rawBody is this same object, serialized
|
|
440
|
+
// verbatim). Genuine backend ciphertext is left byte-identical (looksLikeBackendCiphertext).
|
|
441
|
+
{
|
|
442
|
+
const rewritten = sanitizeEncryptedContentInPlace(
|
|
443
|
+
(body as { input?: unknown } | undefined)?.input,
|
|
444
|
+
);
|
|
445
|
+
if (rewritten > 0)
|
|
446
|
+
console.warn(
|
|
447
|
+
`[opencodex] rewrote ${rewritten} plaintext encrypted_content part(s) to input_text (spawn-message compatibility)`,
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
413
451
|
let parsed;
|
|
414
452
|
try {
|
|
415
453
|
parsed = parseRequest(body);
|
|
@@ -452,15 +490,6 @@ export async function handleResponses(
|
|
|
452
490
|
// Runs BEFORE the mock-max clamp below so the synthetic top tier (ultra arrives
|
|
453
491
|
// as max on the codex wire) is still visible. Both request shapes are rewritten.
|
|
454
492
|
{
|
|
455
|
-
const requestedModelId = logCtx.requestedModel ?? route.modelId;
|
|
456
|
-
// Cross-provider spawn poison fix: native-bound requests may carry plaintext parked in
|
|
457
|
-
// encrypted_content slots (spawn messages minted under a routed parent). Rewrite them
|
|
458
|
-
// to input_text before the passthrough serializes _rawBody verbatim.
|
|
459
|
-
if (!requestedModelId.includes("/")) {
|
|
460
|
-
const raw = parsed._rawBody as { input?: unknown } | undefined;
|
|
461
|
-
const rewritten = sanitizeEncryptedContentInPlace(raw?.input);
|
|
462
|
-
if (rewritten > 0) console.warn(`[opencodex] ${route.modelId}: rewrote ${rewritten} plaintext encrypted_content part(s) to input_text (routed-parent spawn compatibility)`);
|
|
463
|
-
}
|
|
464
493
|
const guidance = await multiAgentGuidanceText(parsed, config.injectionModel, config.injectionEffort, config.subagentModels, config.injectionPrompt);
|
|
465
494
|
if (guidance) {
|
|
466
495
|
injectDeveloperMessage(parsed, guidance);
|
|
@@ -470,6 +499,33 @@ export async function handleResponses(
|
|
|
470
499
|
}
|
|
471
500
|
}
|
|
472
501
|
|
|
502
|
+
// Hard effort caps (effortCap / subagentEffortCap): enforcement companion to the advisory
|
|
503
|
+
// injection above — spawn-arg prompting cannot stop codex-rs from inheriting the parent's
|
|
504
|
+
// ultra-tier default on bare spawns (see src/server/effort-policy.ts). Runs BEFORE the
|
|
505
|
+
// mock-max clamp so a capped effort is what nativeness clamping then validates; rewrites
|
|
506
|
+
// both request shapes (same dual-write contract as the clamp below).
|
|
507
|
+
// GATE: v2 feature only (effortCapAppliesTo) — v2-surface main turns plus header-marked
|
|
508
|
+
// child turns admitted regardless of tool surface (depth-limited leaves carry no collab
|
|
509
|
+
// tools while shallower children do, so tool sniffing alone would cap siblings
|
|
510
|
+
// inconsistently); multiAgentMode "v1" disables caps entirely; compaction turns bypass
|
|
511
|
+
// caps so routed compaction matches native /v1/responses/compact (which never enters
|
|
512
|
+
// handleResponses).
|
|
513
|
+
{
|
|
514
|
+
const { applyEffortCap, effortCapAppliesTo, supportedLadderFor } = await import("./effort-policy");
|
|
515
|
+
const surface = collabSurface(parsed);
|
|
516
|
+
if (effortCapAppliesTo(surface, req.headers, config, parsed._compactionRequest === true)) {
|
|
517
|
+
const capped = applyEffortCap(parsed, req.headers, config, supportedLadderFor(route));
|
|
518
|
+
if (capped) {
|
|
519
|
+
logCtx.requestedEffort = `${capped.from}->${capped.to}`;
|
|
520
|
+
if (isInjectionDebugEnabled()) {
|
|
521
|
+
console.log(`[opencodex] ${route.modelId}: effort cap applied (${capped.from} -> ${capped.to}, ${capped.subagent ? "sub-agent" : "main"} turn)`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
} else if (isInjectionDebugEnabled() && (config.effortCap || config.subagentEffortCap)) {
|
|
525
|
+
console.log(`[opencodex] ${route.modelId}: effort cap skipped (surface=${surface ?? "none"}, v2 feature only)`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
473
529
|
// Mock-max clamp: native models whose real ladder stops below max (gpt-5.5/5.4/…)
|
|
474
530
|
// receive `max` when the user picks Ultra (codex converts ultra->max client-side).
|
|
475
531
|
// Clamp to the model's highest real effort BEFORE any adapter — the ChatGPT
|