@bitkyc08/opencodex 2.7.8 → 2.7.9-preview.20260712
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +2 -0
- package/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/gui/dist/assets/index-BcaDQD3i.js +40 -0
- package/gui/dist/assets/index-Cq8maiJf.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +11 -9
- package/src/adapters/cursor/exec-policy.ts +38 -0
- package/src/adapters/cursor/live-transport.ts +4 -3
- package/src/adapters/cursor/protobuf-request.ts +20 -0
- package/src/adapters/cursor/transport.ts +5 -0
- package/src/adapters/cursor.ts +2 -2
- package/src/bridge.ts +4 -2
- package/src/claude/agents-inject.ts +198 -0
- package/src/claude/alias.ts +69 -0
- package/src/claude/context-windows.ts +189 -0
- package/src/claude/desktop-3p.ts +254 -0
- package/src/claude/gateway-cache.ts +70 -0
- package/src/claude/inbound-debug.ts +114 -0
- package/src/claude/inbound.ts +481 -0
- package/src/claude/model-info.ts +145 -0
- package/src/claude/outbound.ts +487 -0
- package/src/cli/claude.ts +157 -0
- package/src/cli/help.ts +12 -0
- package/src/cli/index.ts +86 -7
- package/src/cli/v2.ts +23 -18
- package/src/codex/features.ts +288 -16
- package/src/lib/crash-guard.ts +11 -1
- package/src/lib/debug-settings.ts +14 -2
- package/src/lib/token-estimate.ts +27 -1
- package/src/providers/registry.ts +1 -1
- package/src/server/auth-cors.ts +4 -2
- package/src/server/claude-messages.ts +494 -0
- package/src/server/index.ts +72 -0
- package/src/server/management-api.ts +226 -34
- package/src/server/request-log.ts +19 -4
- package/src/server/responses.ts +13 -1
- package/src/server/system-env.ts +314 -0
- package/src/types.ts +108 -0
- package/src/usage/log.ts +8 -2
- package/src/usage/summary.ts +18 -1
- package/src/usage/totals.ts +7 -18
- package/gui/dist/assets/index-Bp8dDrs5.js +0 -40
- package/gui/dist/assets/index-C0xVu72_.css +0 -1
|
@@ -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);--toggle-w:36px;--toggle-h:20px;--toggle-dot:14px;--toggle-off-bg:var(--lightningcss-light,#d4d4d4)var(--lightningcss-dark,#4a4a4a);--toggle-on-bg:var(--lightningcss-light,#0d0d0d)var(--lightningcss-dark,#ececec);--toggle-dot-color:var(--lightningcss-light,#fff)var(--lightningcss-dark,#0d0d0d)}@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}.toggle{width:var(--toggle-w);height:var(--toggle-h);flex-shrink:0;display:inline-block;position:relative}.toggle input{opacity:0;width:0;height:0;position:absolute}.toggle .slider{background:var(--toggle-off-bg);border-radius:var(--radius-pill);cursor:pointer;transition:background .15s;position:absolute;inset:0}.toggle .slider:after{content:"";width:var(--toggle-dot);height:var(--toggle-dot);background:var(--toggle-dot-color);border-radius:50%;transition:transform .15s;position:absolute;top:3px;left:3px}.toggle input:checked+.slider{background:var(--toggle-on-bg)}.toggle input:checked+.slider:after{transform:translateX(calc(var(--toggle-w) - var(--toggle-dot) - 6px))}.toggle input:focus-visible+.slider{outline:2px solid var(--accent-ring);outline-offset:2px}.setting-row{justify-content:space-between;align-items:center;gap:16px;padding:12px 16px;display:flex}.setting-row+.setting-row{border-top:1px solid var(--border-soft)}.setting-label{flex-direction:column;gap:2px;min-width:0;display:flex}.setting-label .title{color:var(--text);font-size:13.5px;font-weight:550}.setting-label .desc{color:var(--muted);font-size:12px;line-height:1.4}
|
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-BcaDQD3i.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Cq8maiJf.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -291,13 +291,17 @@ function adaptiveEffort(effort: string): string {
|
|
|
291
291
|
function usageFromAnthropic(usage: Record<string, number> | undefined): OcxUsage | undefined {
|
|
292
292
|
if (!usage) return undefined;
|
|
293
293
|
const hasCache = usage.cache_read_input_tokens !== undefined || usage.cache_creation_input_tokens !== undefined;
|
|
294
|
+
const read = usage.cache_read_input_tokens ?? 0;
|
|
295
|
+
const write = usage.cache_creation_input_tokens ?? 0;
|
|
296
|
+
// Anthropic reports input_tokens EXCLUSIVE of cache read/write; normalize to the
|
|
297
|
+
// canonical inclusive convention (types.ts OcxUsage / devlog 070).
|
|
294
298
|
return {
|
|
295
|
-
inputTokens: usage.input_tokens ?? 0,
|
|
299
|
+
inputTokens: (usage.input_tokens ?? 0) + read + write,
|
|
296
300
|
outputTokens: usage.output_tokens ?? 0,
|
|
297
301
|
...(hasCache ? {
|
|
298
|
-
cachedInputTokens:
|
|
299
|
-
cacheReadInputTokens:
|
|
300
|
-
cacheCreationInputTokens:
|
|
302
|
+
cachedInputTokens: read,
|
|
303
|
+
cacheReadInputTokens: read,
|
|
304
|
+
cacheCreationInputTokens: write,
|
|
301
305
|
} : {}),
|
|
302
306
|
};
|
|
303
307
|
}
|
|
@@ -308,11 +312,9 @@ function mergeAnthropicUsage(
|
|
|
308
312
|
): Record<string, number> | undefined {
|
|
309
313
|
if (!next) return base;
|
|
310
314
|
if (!base) return { ...next };
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
return merged;
|
|
315
|
+
// Anthropic `message_delta.usage` values are CUMULATIVE; adding them to the
|
|
316
|
+
// message_start snapshot double-counted output tokens. Later frames win per key.
|
|
317
|
+
return { ...base, ...next };
|
|
316
318
|
}
|
|
317
319
|
|
|
318
320
|
function buildToolNameTransforms(provider: OcxProviderConfig): { toWire: (name: string) => string; fromWire: (name: string) => string } {
|
|
@@ -1,5 +1,43 @@
|
|
|
1
|
+
import type { OcxProviderConfig } from "../../types";
|
|
1
2
|
import type { CursorClientMessage } from "./types";
|
|
2
3
|
|
|
4
|
+
export type CursorNativeExecMode = "off" | "codex-sandbox" | "on";
|
|
5
|
+
|
|
6
|
+
/** Codex permissions template marker, e.g. "`sandbox_mode` is `danger-full-access`". */
|
|
7
|
+
export const CURSOR_SANDBOX_FULL_ACCESS_RE = /sandbox_mode[^\n]{0,80}danger-full-access/i;
|
|
8
|
+
|
|
9
|
+
/** Config-owner-selected policy; explicit mode wins, legacy boolean maps to "on". */
|
|
10
|
+
export function resolveCursorNativeExecMode(provider: OcxProviderConfig): CursorNativeExecMode {
|
|
11
|
+
const mode = provider.nativeLocalExec;
|
|
12
|
+
if (mode === "off" || mode === "codex-sandbox" || mode === "on") return mode;
|
|
13
|
+
return provider.unsafeAllowNativeLocalExec === true ? "on" : "off";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* True when the request itself declares the Codex full-access sandbox. Carriers are the
|
|
18
|
+
* system/instructions entries and developer-role messages ONLY — user/assistant/tool text
|
|
19
|
+
* never authorizes (smallest spoof surface inside the codex-sandbox mode).
|
|
20
|
+
*/
|
|
21
|
+
export function cursorRequestDeclaresFullAccess(
|
|
22
|
+
request: { system: string[]; messages: Array<{ role: string; content: string }> },
|
|
23
|
+
): boolean {
|
|
24
|
+
for (const entry of request.system) {
|
|
25
|
+
if (CURSOR_SANDBOX_FULL_ACCESS_RE.test(entry)) return true;
|
|
26
|
+
}
|
|
27
|
+
for (const message of request.messages) {
|
|
28
|
+
if (message.role === "developer" && CURSOR_SANDBOX_FULL_ACCESS_RE.test(message.content)) return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Effective per-request allowance: "on" always, "codex-sandbox" only when declared, "off" never. */
|
|
34
|
+
export function effectiveCursorNativeExecAllow(provider: OcxProviderConfig, requestDeclaresFullAccess: boolean): boolean {
|
|
35
|
+
const mode = resolveCursorNativeExecMode(provider);
|
|
36
|
+
if (mode === "on") return true;
|
|
37
|
+
if (mode === "codex-sandbox") return requestDeclaresFullAccess;
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
3
41
|
export const CURSOR_EXEC_CASES_DENIED = [
|
|
4
42
|
"readArgs",
|
|
5
43
|
"lsArgs",
|
|
@@ -32,7 +32,8 @@ import { debugProviderDiagnostic } from "../../lib/debug";
|
|
|
32
32
|
import { classifyCursorError, isCursorBenignCancelError, safeCursorErrorMessage } from "./cursor-errors";
|
|
33
33
|
import { mcpArgsFromToolCall } from "./protobuf-events";
|
|
34
34
|
import { OCX_RESPONSES_TOOL_PROVIDER } from "./tool-definitions";
|
|
35
|
-
import {
|
|
35
|
+
import { handleCursorNativeExec, handleCursorNativeKv, type CursorNativeExecContext } from "./native-exec";
|
|
36
|
+
import { effectiveCursorNativeExecAllow } from "./exec-policy";
|
|
36
37
|
import { resolveMcpServers } from "./mcp-config";
|
|
37
38
|
import { CursorMcpManager } from "./mcp-manager";
|
|
38
39
|
import { buildMcpToolDefinitions, mcpDepsFromManager } from "./native-exec-mcp";
|
|
@@ -331,7 +332,7 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
331
332
|
this.activeClientToolFinalizeGraceMs = this.clientToolFinalizeGraceMs;
|
|
332
333
|
// Desktop (computer-use / record-screen) executors are available even with no MCP servers.
|
|
333
334
|
this.desktopDeps = desktopDepsFromConfig(input.provider.desktopExecutor);
|
|
334
|
-
this.execContext = { ...this.desktopDeps, unsafeAllowNativeLocalExec:
|
|
335
|
+
this.execContext = { ...this.desktopDeps, unsafeAllowNativeLocalExec: effectiveCursorNativeExecAllow(input.provider, input.requestDeclaresFullAccess === true) };
|
|
335
336
|
const servers = resolveMcpServers(input.provider);
|
|
336
337
|
if (servers.length > 0) {
|
|
337
338
|
this.mcpManager = new CursorMcpManager(servers, {
|
|
@@ -356,7 +357,7 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
356
357
|
...this.desktopDeps,
|
|
357
358
|
...mcpDepsFromManager(this.mcpManager!),
|
|
358
359
|
mcpToolDefs,
|
|
359
|
-
unsafeAllowNativeLocalExec:
|
|
360
|
+
unsafeAllowNativeLocalExec: effectiveCursorNativeExecAllow(this.input.provider, this.input.requestDeclaresFullAccess === true),
|
|
360
361
|
};
|
|
361
362
|
} catch (err) {
|
|
362
363
|
throw new Error(`Cursor MCP preparation failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
McpToolCallSchema,
|
|
21
21
|
McpToolResultContentItemSchema,
|
|
22
22
|
McpToolResultSchema,
|
|
23
|
+
McpToolsSchema,
|
|
23
24
|
ModelDetailsSchema,
|
|
24
25
|
ResumeActionSchema,
|
|
25
26
|
RequestContextSchema,
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
appendCursorShellAliasHint,
|
|
35
36
|
cursorToolsForActivePrompt,
|
|
36
37
|
buildCursorToolGuidanceSystemNote,
|
|
38
|
+
buildCursorToolDefinitions,
|
|
37
39
|
cursorRequestHasShellAlias,
|
|
38
40
|
CURSOR_SHELL_ALIAS_SYSTEM_NOTE,
|
|
39
41
|
OCX_RESPONSES_TOOL_PROVIDER,
|
|
@@ -361,6 +363,24 @@ export function encodeCursorRunRequest(request: CursorRunRequest): Uint8Array {
|
|
|
361
363
|
displayNameShort: request.modelId,
|
|
362
364
|
aliases: [],
|
|
363
365
|
}),
|
|
366
|
+
// Mirror the client (Responses) tool definitions into the top-level AgentRunRequest.mcp_tools
|
|
367
|
+
// channel. Advertising them ONLY via native-exec `requestContextArgs` (RequestContext.tools) is
|
|
368
|
+
// insufficient: cursor models report those tools as unavailable and fall back to native tools.
|
|
369
|
+
// Populating mcp_tools registers them into the model's callable catalog (verified live: the
|
|
370
|
+
// model actually calls the injected tool on gpt-5.6-luna and claude-4.5-sonnet). Phase 42 tried
|
|
371
|
+
// this but assigned the field with the wrong shape and crashed Cursor's binary parser ("illegal
|
|
372
|
+
// tag"); the correct `McpTools` wrapper is wire-compatible (verified — no parse crash on either
|
|
373
|
+
// model family). See devlog/260711_cursor_browser_bridge/004.
|
|
374
|
+
//
|
|
375
|
+
// Use the SAME `cursorToolsForActivePrompt`-filtered visible set that RequestContext.tools and
|
|
376
|
+
// the event-state `clientToolNames` use (live-transport.ts). Advertising the raw `request.tools`
|
|
377
|
+
// here would let mcp_tools expose a tool that the event state does not recognize for a generic
|
|
378
|
+
// tool-count prompt, so a call to it would be rejected as an unknown Responses tool.
|
|
379
|
+
...(() => {
|
|
380
|
+
const visibleTools = cursorToolsForActivePrompt(request.tools, activePromptText(request), request.toolChoice);
|
|
381
|
+
const mcpToolDefs = buildCursorToolDefinitions(visibleTools, request.toolChoice);
|
|
382
|
+
return mcpToolDefs.length > 0 ? { mcpTools: create(McpToolsSchema, { mcpTools: mcpToolDefs }) } : {};
|
|
383
|
+
})(),
|
|
364
384
|
});
|
|
365
385
|
|
|
366
386
|
const message = create(AgentClientMessageSchema, {
|
|
@@ -23,6 +23,11 @@ export interface CursorTransportFactoryInput {
|
|
|
23
23
|
* announced in a later receive chunk can revoke a premature finalize. Defaults to 50ms.
|
|
24
24
|
*/
|
|
25
25
|
clientToolFinalizeGraceMs?: number;
|
|
26
|
+
/**
|
|
27
|
+
* True when the inbound request's system/developer text declares the Codex full-access
|
|
28
|
+
* sandbox; consumed by nativeLocalExec:"codex-sandbox" policy (exec-policy.ts).
|
|
29
|
+
*/
|
|
30
|
+
requestDeclaresFullAccess?: boolean;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
export type CursorTransportFactory = (input: CursorTransportFactoryInput) => CursorTransport;
|
package/src/adapters/cursor.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AdapterEvent, OcxProviderConfig } from "../types";
|
|
2
2
|
import type { ProviderAdapter } from "./base";
|
|
3
|
-
import { cursorExecDeniedMessage } from "./cursor/exec-policy";
|
|
3
|
+
import { cursorExecDeniedMessage, cursorRequestDeclaresFullAccess } from "./cursor/exec-policy";
|
|
4
4
|
import { isCursorBenignCancelError, safeCursorErrorMessage } from "./cursor/cursor-errors";
|
|
5
5
|
import { createCursorKvStore, type CursorKvStore } from "./cursor/kv-store";
|
|
6
6
|
import { mapCursorServerMessage } from "./cursor/message-mapper";
|
|
@@ -73,7 +73,7 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
|
|
|
73
73
|
const request = createCursorRequest(_parsed);
|
|
74
74
|
await runCursorTurnWithRetry(
|
|
75
75
|
makeTransport,
|
|
76
|
-
{ provider, headers: incoming.headers },
|
|
76
|
+
{ provider, headers: incoming.headers, requestDeclaresFullAccess: cursorRequestDeclaresFullAccess(request) },
|
|
77
77
|
request,
|
|
78
78
|
incoming.abortSignal,
|
|
79
79
|
(message, activeTransport) => {
|
package/src/bridge.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { AdapterEvent, OcxUsage } from "./types";
|
|
|
2
2
|
import { adapterFailureFromMessage, classifyError, type OcxErrorPayload } from "./lib/errors";
|
|
3
3
|
import { encodeCompactionSummary } from "./responses/compaction";
|
|
4
4
|
import { encodeReasoningEnvelope, type ReasoningEnvelope } from "./responses/reasoning-envelope";
|
|
5
|
-
import { usageDisplayTotalTokens
|
|
5
|
+
import { usageDisplayTotalTokens } from "./usage/totals";
|
|
6
6
|
|
|
7
7
|
function uuid(): string {
|
|
8
8
|
return crypto.randomUUID().replace(/-/g, "");
|
|
@@ -14,7 +14,8 @@ function sseEvent(name: string, data: Record<string, unknown>): string {
|
|
|
14
14
|
|
|
15
15
|
function responsesUsage(usage: OcxUsage | undefined): Record<string, unknown> {
|
|
16
16
|
if (!usage) return { input_tokens: 0, output_tokens: 0, total_tokens: 0 };
|
|
17
|
-
|
|
17
|
+
// inputTokens is already inclusive of cache read/write (types.ts convention).
|
|
18
|
+
const inputTokens = usage.inputTokens;
|
|
18
19
|
const out: Record<string, unknown> = {
|
|
19
20
|
input_tokens: inputTokens,
|
|
20
21
|
output_tokens: usage.outputTokens,
|
|
@@ -22,6 +23,7 @@ function responsesUsage(usage: OcxUsage | undefined): Record<string, unknown> {
|
|
|
22
23
|
};
|
|
23
24
|
const inputDetails: Record<string, number> = {};
|
|
24
25
|
if (usage.cachedInputTokens !== undefined) {
|
|
26
|
+
// cached_tokens carries cache READS only, matching OpenAI semantics.
|
|
25
27
|
inputDetails.cached_tokens = usage.cachedInputTokens;
|
|
26
28
|
}
|
|
27
29
|
if (usage.cacheCreationInputTokens !== undefined) {
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code custom-agent definition injection (devlog 260712 070).
|
|
3
|
+
*
|
|
4
|
+
* The Agent tool's `model` argument is a hard 4-alias enum (2.1.207 binary), but an
|
|
5
|
+
* agent DEFINITION's frontmatter `model:` is a free string ("Model alias this agent
|
|
6
|
+
* uses. If omitted, inherits the parent's model"). So we sync the featured
|
|
7
|
+
* subagent roster (config.subagentModels, <=5) plus the main model (when not
|
|
8
|
+
* already covered) into ~/.claude/agents/ocx-*.md — one dispatchable
|
|
9
|
+
* `subagent_type` per routed model, loaded at the next session start.
|
|
10
|
+
*
|
|
11
|
+
* Ownership contract: this module only creates/overwrites/deletes files matching
|
|
12
|
+
* `ocx-*.md` inside the agents dir. User-authored agents are never touched.
|
|
13
|
+
*/
|
|
14
|
+
import { lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import type { OcxConfig } from "../types";
|
|
17
|
+
import { claudeCodeAlias, claudeCodeNativeAlias } from "./alias";
|
|
18
|
+
import { resolveAutoContext, withOneMillionMarker } from "./context-windows";
|
|
19
|
+
import { claudeConfigDir } from "./gateway-cache";
|
|
20
|
+
import { DEFAULT_SUBAGENT_MODELS } from "../config";
|
|
21
|
+
|
|
22
|
+
export interface ClaudeAgentDef {
|
|
23
|
+
file: string;
|
|
24
|
+
name: string;
|
|
25
|
+
model: string;
|
|
26
|
+
description: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const OWNED_PREFIX = "ocx-";
|
|
30
|
+
/** Ownership proof (audit 071 #2): a file without this marker is NEVER touched. */
|
|
31
|
+
const GENERATED_MARKER = "generated-by: opencodex";
|
|
32
|
+
|
|
33
|
+
function sanitizeName(value: string): string {
|
|
34
|
+
const cleaned = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
35
|
+
return cleaned.length > 0 ? cleaned : "model";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The user's default model as saved by the /model picker (settings.json `model`).
|
|
40
|
+
* `model: "inherit"` in agent frontmatter is DISPROVEN on 2.1.207 (live: a
|
|
41
|
+
* no-model ocx-self dispatch fell back to claude-fable-5 — devlog 072), so the
|
|
42
|
+
* self-clone pins this value instead, refreshed at every launch-time sync.
|
|
43
|
+
*/
|
|
44
|
+
function pickerDefaultModel(configDir: string): string | null {
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(readFileSync(join(configDir, "settings.json"), "utf8")) as Record<string, unknown>;
|
|
47
|
+
return typeof parsed.model === "string" && parsed.model.trim() !== "" ? parsed.model.trim() : null;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Roster entry -> alias + display parts. Entries are bare native slugs or "provider/id". */
|
|
54
|
+
function entryParts(entry: string): { alias: string; id: string; provider: string } {
|
|
55
|
+
const slash = entry.indexOf("/");
|
|
56
|
+
if (slash > 0) {
|
|
57
|
+
const provider = entry.slice(0, slash);
|
|
58
|
+
const id = entry.slice(slash + 1);
|
|
59
|
+
return { alias: claudeCodeAlias(provider, id), id, provider };
|
|
60
|
+
}
|
|
61
|
+
return { alias: claudeCodeNativeAlias(entry), id: entry, provider: "native" };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function buildClaudeAgentDefs(config: OcxConfig, windows: Record<string, number>, configDir = claudeConfigDir()): ClaudeAgentDef[] {
|
|
65
|
+
const auto = resolveAutoContext(config.claudeCode);
|
|
66
|
+
const defs: ClaudeAgentDef[] = [];
|
|
67
|
+
const usedNames = new Set<string>();
|
|
68
|
+
const coveredModels = new Set<string>();
|
|
69
|
+
|
|
70
|
+
const push = (name: string, alias: string, description: string) => {
|
|
71
|
+
// Effective model value: [1m] marking follows the same predicate as env slots.
|
|
72
|
+
const model = withOneMillionMarker(alias, windows, auto) ?? alias;
|
|
73
|
+
const bare = alias.toLowerCase();
|
|
74
|
+
if (coveredModels.has(bare)) return;
|
|
75
|
+
coveredModels.add(bare);
|
|
76
|
+
let unique = name;
|
|
77
|
+
for (let i = 2; usedNames.has(unique); i++) unique = `${name}-${i}`;
|
|
78
|
+
usedNames.add(unique);
|
|
79
|
+
defs.push({ file: `${OWNED_PREFIX}${unique}.md`, name: `${OWNED_PREFIX}${unique}`, model, description });
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Default roster applies only when the field is UNSET — an explicit [] is
|
|
83
|
+
// respected (audit 071 #6: an upgraded config must not lose the default five).
|
|
84
|
+
const roster = config.subagentModels === undefined ? DEFAULT_SUBAGENT_MODELS : config.subagentModels;
|
|
85
|
+
for (const entry of roster.slice(0, 5)) {
|
|
86
|
+
if (typeof entry !== "string" || entry.trim() === "") continue;
|
|
87
|
+
const { alias, id, provider } = entryParts(entry.trim());
|
|
88
|
+
push(sanitizeName(id), alias, `Delegate work to ${id} (${provider}) via opencodex routing. General-purpose worker/explorer on that model. ${NO_MODEL_ARG}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Self-clone slot: pin the picker-saved default (settings.json), falling back to
|
|
92
|
+
// config.claudeCode.model. `inherit` is NOT honored by 2.1.207 (live-disproven,
|
|
93
|
+
// devlog 072); a session started with a divergent --model stays divergent until
|
|
94
|
+
// the next launch sync — documented limit. No resolvable default -> no self def.
|
|
95
|
+
const selfModel = pickerDefaultModel(configDir) ?? (config.claudeCode?.model?.trim() || null);
|
|
96
|
+
if (selfModel) {
|
|
97
|
+
const marked = withOneMillionMarker(selfModel, windows, auto) ?? selfModel;
|
|
98
|
+
defs.push({
|
|
99
|
+
file: `${OWNED_PREFIX}self.md`,
|
|
100
|
+
name: `${OWNED_PREFIX}self`,
|
|
101
|
+
model: marked,
|
|
102
|
+
description: `Self-clone: delegate to your default main model (${marked}), synced from the /model picker at launch. ${NO_MODEL_ARG}`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return defs;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function renderAgentDef(def: ClaudeAgentDef): string {
|
|
109
|
+
// YAML frontmatter: model ids carry dots/brackets — always double-quote scalars.
|
|
110
|
+
return [
|
|
111
|
+
"---",
|
|
112
|
+
`name: ${JSON.stringify(def.name)}`,
|
|
113
|
+
`description: ${JSON.stringify(def.description)}`,
|
|
114
|
+
`model: ${JSON.stringify(def.model)}`,
|
|
115
|
+
"---",
|
|
116
|
+
"",
|
|
117
|
+
`<!-- ${GENERATED_MARKER} -->`,
|
|
118
|
+
// Proxy routing directive (devlog 072): 2.1.207 does not honor custom gateway
|
|
119
|
+
// ids in agent frontmatter (falls back to sonnet — live-proven), but the agent
|
|
120
|
+
// BODY rides the subagent's system prompt verbatim. The proxy detects this
|
|
121
|
+
// directive and overrides the request model before routing/passthrough.
|
|
122
|
+
`<!-- ocx-route: ${def.model} -->`,
|
|
123
|
+
"",
|
|
124
|
+
`You are a delegated worker running on \`${def.model}\` through the local opencodex proxy.`,
|
|
125
|
+
`IDENTITY: your ACTUAL underlying model is \`${def.model}\` — the opencodex proxy routes this`,
|
|
126
|
+
"session there regardless of what model name the Claude Code harness displays or claims.",
|
|
127
|
+
"If asked which model you are, answer with the id above; do not guess a Claude model name.",
|
|
128
|
+
"",
|
|
129
|
+
"Complete the dispatched task directly and report results concisely. This file is",
|
|
130
|
+
"auto-generated by opencodex (`ocx claude`) from the featured subagent roster —",
|
|
131
|
+
"manual edits will be overwritten; remove the model from the roster to drop it.",
|
|
132
|
+
"",
|
|
133
|
+
].join("\n");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** True only for a REGULAR file we generated (marker present; symlinks never owned). */
|
|
137
|
+
function isOwnedFile(path: string): boolean {
|
|
138
|
+
try {
|
|
139
|
+
const st = lstatSync(path);
|
|
140
|
+
if (!st.isFile()) return false; // symlink or dir: never touch (audit 071 #2)
|
|
141
|
+
return readFileSync(path, "utf8").includes(GENERATED_MARKER);
|
|
142
|
+
} catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Sync owned agent files: write/overwrite current defs, prune stale ocx-*.md,
|
|
149
|
+
* never touch anything else. Ownership requires the generated marker; writes are
|
|
150
|
+
* atomic (tmp + rename). Best-effort — returns null on any failure.
|
|
151
|
+
*/
|
|
152
|
+
export function syncClaudeAgentDefs(defs: readonly ClaudeAgentDef[], configDir = claudeConfigDir()): string[] | null {
|
|
153
|
+
try {
|
|
154
|
+
const dir = join(configDir, "agents");
|
|
155
|
+
mkdirSync(dir, { recursive: true });
|
|
156
|
+
const keep = new Set(defs.map(d => d.file));
|
|
157
|
+
for (const existing of readdirSync(dir)) {
|
|
158
|
+
if (!existing.startsWith(OWNED_PREFIX) || !existing.endsWith(".md")) continue;
|
|
159
|
+
if (!keep.has(existing) && isOwnedFile(join(dir, existing))) {
|
|
160
|
+
try { unlinkSync(join(dir, existing)); } catch { /* best-effort prune */ }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const written: string[] = [];
|
|
164
|
+
for (const def of defs) {
|
|
165
|
+
const target = join(dir, def.file);
|
|
166
|
+
// A pre-existing ocx-* file WITHOUT our marker is user property: skip the def.
|
|
167
|
+
try {
|
|
168
|
+
lstatSync(target);
|
|
169
|
+
if (!isOwnedFile(target)) continue;
|
|
170
|
+
} catch { /* does not exist: ours to create */ }
|
|
171
|
+
const tmp = `${target}.tmp-${process.pid}`;
|
|
172
|
+
writeFileSync(tmp, renderAgentDef(def), { encoding: "utf8", mode: 0o644 });
|
|
173
|
+
renameSync(tmp, target);
|
|
174
|
+
written.push(def.file);
|
|
175
|
+
}
|
|
176
|
+
return written;
|
|
177
|
+
} catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Launch-time hook: gate + build + sync in one call (used by ocx claude and systemEnv). */
|
|
183
|
+
export function injectClaudeAgentDefs(config: OcxConfig, windows: Record<string, number>, configDir?: string): string[] | null {
|
|
184
|
+
if (config.claudeCode?.enabled === false || config.claudeCode?.injectAgents === false) {
|
|
185
|
+
// Disabled: prune verified-owned files so stale definitions stop loading
|
|
186
|
+
// in future sessions (audit 071 #3).
|
|
187
|
+
return syncClaudeAgentDefs([], configDir);
|
|
188
|
+
}
|
|
189
|
+
return syncClaudeAgentDefs(buildClaudeAgentDefs(config, windows, configDir), configDir);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Dispatcher directive appended to every ocx-* description. The ocx-route body
|
|
193
|
+
* directive makes the Agent tool's `model` argument INERT (the proxy overrides
|
|
194
|
+
* the request model before routing — live-proven), so instead of asking the
|
|
195
|
+
* dispatcher to omit it (which caused schema-anxiety loops), we hand it a fixed
|
|
196
|
+
* placeholder: any value works, "sonnet" is the cheap canonical one.
|
|
197
|
+
*/
|
|
198
|
+
const NO_MODEL_ARG = "NOTE: this agent's real model is pinned by the opencodex proxy — the `model` argument is ignored. Pass model: \"sonnet\" as a placeholder (or omit it); routing is unaffected either way.";
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway model-discovery aliases (devlog/260711_claude_inbound/020, 003 G1-G6).
|
|
3
|
+
*
|
|
4
|
+
* Claude Code's /model picker only lists discovery entries whose id literally
|
|
5
|
+
* begins with `claude` or `anthropic`, so routed models are exposed as
|
|
6
|
+
* `claude-ocx-<provider>--<model>` with an honest display_name. Aliases must be
|
|
7
|
+
* deterministic, reversible, and STABLE across releases (picker selections
|
|
8
|
+
* persist to Claude Code's settings.json `model` field).
|
|
9
|
+
*
|
|
10
|
+
* Reversibility rules:
|
|
11
|
+
* - providers containing `--` or `/` are not aliased (split boundary safety);
|
|
12
|
+
* - model ids containing `/` are not aliased (would be ambiguous on resolve);
|
|
13
|
+
* - model ids MAY contain `--` (resolve splits on the FIRST `--` only);
|
|
14
|
+
* - native OpenAI slugs use the pseudo-provider `native` and resolve back to
|
|
15
|
+
* the bare slug; a real provider named "native" is therefore never aliased.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { desktop3pAlias } from "./desktop-3p";
|
|
19
|
+
|
|
20
|
+
export const CLAUDE_ALIAS_PREFIX = "claude-ocx-";
|
|
21
|
+
const NATIVE_PSEUDO_PROVIDER = "native";
|
|
22
|
+
|
|
23
|
+
/** Alias for a routed "<provider>/<model>" pair; null when not representable. */
|
|
24
|
+
export function aliasForRoute(provider: string, modelId: string): string | null {
|
|
25
|
+
if (!provider || provider.includes("--") || provider.includes("/") || provider === NATIVE_PSEUDO_PROVIDER) return null;
|
|
26
|
+
if (!modelId || modelId.includes("/")) return null;
|
|
27
|
+
return `${CLAUDE_ALIAS_PREFIX}${provider}--${modelId}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Alias for a native OpenAI slug (bare model id, no provider namespace). */
|
|
31
|
+
export function aliasForNative(slug: string): string | null {
|
|
32
|
+
if (!slug || slug.includes("/") || slug.includes("--")) return null;
|
|
33
|
+
return `${CLAUDE_ALIAS_PREFIX}${NATIVE_PSEUDO_PROVIDER}--${slug}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Reverse an alias to the inbound model string routeModel understands:
|
|
38
|
+
* routed -> "<provider>/<model>", native -> bare slug. Null when not an alias.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveAlias(id: string): string | null {
|
|
41
|
+
if (!id.startsWith(CLAUDE_ALIAS_PREFIX)) return null;
|
|
42
|
+
const rest = id.slice(CLAUDE_ALIAS_PREFIX.length);
|
|
43
|
+
const sep = rest.indexOf("--");
|
|
44
|
+
if (sep <= 0) return null;
|
|
45
|
+
const provider = rest.slice(0, sep);
|
|
46
|
+
const model = rest.slice(sep + 2);
|
|
47
|
+
if (!model) return null;
|
|
48
|
+
return provider === NATIVE_PSEUDO_PROVIDER ? model : `${provider}/${model}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Claude Code (CLI) surface alias — devlog 050 + audit 051 #2.
|
|
53
|
+
*
|
|
54
|
+
* The readable `claude-ocx-*` form when representable; otherwise the desktop-3p
|
|
55
|
+
* hash so the model still appears in discovery (collisions follow the same
|
|
56
|
+
* first-wins policy as the desktop registry — audit 051 #1). Real Anthropic
|
|
57
|
+
* models pass through unchanged (they must keep hitting the sk-ant passthrough).
|
|
58
|
+
* Both families keep decoding forever in resolveInboundModel, so ids persisted
|
|
59
|
+
* in Claude Code's settings.json never break when the surface style changes.
|
|
60
|
+
*/
|
|
61
|
+
export function claudeCodeAlias(provider: string, modelId: string): string {
|
|
62
|
+
if (provider === "anthropic" && modelId.startsWith("claude-")) return modelId;
|
|
63
|
+
return aliasForRoute(provider, modelId) ?? desktop3pAlias(provider, modelId);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Claude Code (CLI) surface alias for a native OpenAI slug. */
|
|
67
|
+
export function claudeCodeNativeAlias(slug: string): string {
|
|
68
|
+
return aliasForNative(slug) ?? desktop3pAlias("native", slug);
|
|
69
|
+
}
|