@bitkyc08/opencodex 2.6.32 → 2.7.0

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.
Files changed (55) hide show
  1. package/README.ko.md +9 -5
  2. package/README.md +7 -4
  3. package/README.zh-CN.md +8 -4
  4. package/gui/dist/assets/index-BGdxwydf.js +34 -0
  5. package/gui/dist/assets/index-DANCQ2Jt.css +1 -0
  6. package/gui/dist/index.html +2 -2
  7. package/package.json +1 -1
  8. package/src/adapters/anthropic.ts +62 -1
  9. package/src/adapters/cursor/cursor-errors.ts +28 -1
  10. package/src/adapters/cursor/discovery.ts +56 -10
  11. package/src/adapters/cursor/effort-map.ts +35 -7
  12. package/src/adapters/cursor/live-models.ts +3 -0
  13. package/src/adapters/cursor/live-transport.ts +136 -7
  14. package/src/adapters/cursor/protobuf-request.ts +24 -1
  15. package/src/adapters/cursor/request-builder.ts +6 -5
  16. package/src/adapters/cursor/transport-retry.ts +22 -3
  17. package/src/adapters/cursor.ts +2 -1
  18. package/src/adapters/openai-chat.ts +75 -26
  19. package/src/bridge.ts +42 -3
  20. package/src/cli/debug.ts +203 -0
  21. package/src/cli/doctor.ts +11 -0
  22. package/src/cli/help.ts +11 -0
  23. package/src/cli/index.ts +10 -0
  24. package/src/cli/v2.ts +131 -0
  25. package/src/codex/auth-api.ts +7 -3
  26. package/src/codex/catalog.ts +334 -31
  27. package/src/codex/data/upstream-models.json +830 -0
  28. package/src/codex/features.ts +178 -0
  29. package/src/codex/project-config-warnings.ts +388 -0
  30. package/src/codex/sync.ts +8 -0
  31. package/src/codex/warmup.ts +62 -6
  32. package/src/config.ts +7 -5
  33. package/src/lib/debug-log-buffer.ts +42 -0
  34. package/src/lib/debug-settings.ts +84 -0
  35. package/src/lib/debug.ts +18 -9
  36. package/src/lib/errors.ts +104 -1
  37. package/src/oauth/cursor.ts +35 -12
  38. package/src/oauth/store.ts +4 -3
  39. package/src/providers/derive.ts +8 -0
  40. package/src/providers/registry.ts +56 -21
  41. package/src/reasoning-effort.ts +32 -9
  42. package/src/responses/parser.ts +7 -2
  43. package/src/router.ts +5 -0
  44. package/src/server/adapter-resolve.ts +1 -1
  45. package/src/server/index.ts +27 -3
  46. package/src/server/management-api.ts +168 -7
  47. package/src/server/relay.ts +2 -2
  48. package/src/server/request-log.ts +78 -0
  49. package/src/server/responses.ts +209 -0
  50. package/src/types.ts +28 -1
  51. package/src/usage/debug.ts +32 -5
  52. package/src/usage/summary.ts +6 -6
  53. package/src/web-search/index.ts +1 -1
  54. package/gui/dist/assets/index-ByGC8-Bm.css +0 -1
  55. package/gui/dist/assets/index-D_JZzI0r.js +0 -15
@@ -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)}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}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}.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{color:var(--muted);border-radius:var(--radius-sm);padding:5px;transition:background .12s,color .12s}.btn-icon:hover{background:var(--raised-hover);color:var(--text)}.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:center;gap:6px;font-size:12px;display:flex}.update-restart{border-top:1px solid var(--border-soft);padding-top:12px}.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-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{cursor:pointer;background:var(--lightningcss-light,#c5c9d2)var(--lightningcss-dark,#3a3f4b);border:none;border-radius:99px;flex-shrink:0;width:34px;height:19px;padding:0;transition:background .15s;position:relative}.switch.on{background:var(--accent)}.switch:disabled{opacity:.6;cursor:default}.switch .knob{background:#fff;border-radius:50%;width:15px;height:15px;transition:left .15s;position:absolute;top:2px;left:2px;box-shadow:0 1px 2px #1018284d}.switch.on .knob{left:17px}.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{background:var(--rail)}.modal-card{background:var(--surface)}}@media (prefers-reduced-transparency:reduce){body:before{display:none}.sidebar{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}}.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}
@@ -16,8 +16,8 @@
16
16
  } catch (e) {}
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-D_JZzI0r.js"></script>
20
- <link rel="stylesheet" crossorigin href="/assets/index-ByGC8-Bm.css">
19
+ <script type="module" crossorigin src="/assets/index-BGdxwydf.js"></script>
20
+ <link rel="stylesheet" crossorigin href="/assets/index-DANCQ2Jt.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitkyc08/opencodex",
3
- "version": "2.6.32",
3
+ "version": "2.7.0",
4
4
  "description": "Universal provider proxy for OpenAI Codex — use any LLM with Codex CLI/App/SDK",
5
5
  "type": "module",
6
6
  "main": "./bin/package-main.mjs",
@@ -482,11 +482,72 @@ function toolsToAnthropicFormat(parsed: OcxParsedRequest, toolNames: { toWire: (
482
482
  const converted = tools.map(t => ({
483
483
  name: toolNames.toWire(namespacedToolName(t.namespace, t.name)),
484
484
  description: t.description,
485
- input_schema: t.parameters,
485
+ input_schema: normalizeAnthropicInputSchema(t.parameters),
486
486
  }));
487
487
  return converted;
488
488
  }
489
489
 
490
+ function normalizeAnthropicInputSchema(schema: unknown): Record<string, unknown> {
491
+ const obj = schema && typeof schema === "object" && !Array.isArray(schema)
492
+ ? schema as Record<string, unknown>
493
+ : {};
494
+ // Anthropic rejects root-level missing type and oneOf/anyOf/allOf in input_schema.
495
+ // Normalize the root only: ensure type:"object" + properties, flatten root composition
496
+ // while preserving nested schemas. Mirrors kiro-tools.ts ensureRootObjectType.
497
+ // Known limitation: Object.assign on branch properties means later branches overwrite
498
+ // earlier ones when the same property name appears with different schemas.
499
+ const compositionKeys = ["oneOf", "anyOf", "allOf"] as const;
500
+ const hasRootComposition = compositionKeys.some(key => Array.isArray(obj[key]));
501
+ const type = obj.type;
502
+ const rootObjectType = type === "object" || (Array.isArray(type) && type.includes("object"));
503
+
504
+ if (!hasRootComposition) {
505
+ const normalized: Record<string, unknown> = rootObjectType && type === "object"
506
+ ? { ...obj }
507
+ : { ...obj, type: "object" };
508
+ if (normalized.properties === undefined || normalized.properties === null) {
509
+ normalized.properties = {};
510
+ }
511
+ return normalized;
512
+ }
513
+
514
+ const properties: Record<string, unknown> = {};
515
+ const required = new Set<string>();
516
+ if (obj.properties && typeof obj.properties === "object" && !Array.isArray(obj.properties)) {
517
+ Object.assign(properties, obj.properties as Record<string, unknown>);
518
+ }
519
+ if (Array.isArray(obj.required)) {
520
+ for (const item of obj.required) if (typeof item === "string") required.add(item);
521
+ }
522
+
523
+ for (const key of compositionKeys) {
524
+ const variants = obj[key];
525
+ if (!Array.isArray(variants)) continue;
526
+ const mergeRequired = key === "allOf";
527
+ for (const variant of variants) {
528
+ if (!variant || typeof variant !== "object" || Array.isArray(variant)) continue;
529
+ const v = variant as Record<string, unknown>;
530
+ if (v.properties && typeof v.properties === "object" && !Array.isArray(v.properties)) {
531
+ Object.assign(properties, v.properties as Record<string, unknown>);
532
+ }
533
+ if (mergeRequired && Array.isArray(v.required)) {
534
+ for (const item of v.required) if (typeof item === "string") required.add(item);
535
+ }
536
+ }
537
+ }
538
+
539
+ const normalized: Record<string, unknown> = {};
540
+ for (const [key, value] of Object.entries(obj)) {
541
+ if (key === "oneOf" || key === "anyOf" || key === "allOf") continue;
542
+ if (key === "type" || key === "properties" || key === "required") continue;
543
+ normalized[key] = value;
544
+ }
545
+ normalized.type = "object";
546
+ normalized.properties = properties;
547
+ if (required.size > 0) normalized.required = [...required];
548
+ return normalized;
549
+ }
550
+
490
551
  export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetention?: "none" | "short" | "long"): ProviderAdapter {
491
552
  const isOAuth = provider.authMode === "oauth";
492
553
  const toolNames = buildToolNameTransforms(provider);
@@ -11,6 +11,31 @@ function sanitize(value: string): string {
11
11
  .replace(ABSOLUTE_PATH_PATTERN, "[REDACTED_PATH]");
12
12
  }
13
13
 
14
+ function errorMessage(value: unknown): string {
15
+ if (value instanceof Error) return value.message;
16
+ if (typeof value === "string") return value;
17
+ return String(value ?? "");
18
+ }
19
+
20
+ function errorCode(value: unknown): string {
21
+ if (typeof value !== "object" || !value || !("code" in value)) return "";
22
+ const code = (value as { code?: unknown }).code;
23
+ return code === undefined || code === null ? "" : String(code);
24
+ }
25
+
26
+ /**
27
+ * True when Cursor intentionally cancelled the HTTP/2 stream after a client-tool suspend.
28
+ * These are expected between multi-turn Responses bridge cycles, not upstream failures.
29
+ */
30
+ export function isCursorBenignCancelError(value: unknown): boolean {
31
+ const message = errorMessage(value).toLowerCase();
32
+ const code = errorCode(value).toUpperCase();
33
+ if (code === "NGHTTP2_CANCEL") return true;
34
+ if (message.includes("nghttp2_cancel")) return true;
35
+ if (message.includes("cursor stream suspended")) return true;
36
+ return false;
37
+ }
38
+
14
39
  /**
15
40
  * Classify a Cursor transport/Connect/gRPC error message into an actionable category.
16
41
  * The returned prefix string is recognized by `src/lib/errors.ts` `classifyError` keywords,
@@ -19,13 +44,15 @@ function sanitize(value: string): string {
19
44
  export function classifyCursorError(message: string): string {
20
45
  const lower = message.toLowerCase();
21
46
 
47
+ if (isCursorBenignCancelError(message)) return "Cursor stream suspended";
48
+
22
49
  if (
23
50
  lower.includes("resource_exhausted") ||
24
51
  lower.includes("resource exhausted") ||
25
52
  lower.includes("rate limit") ||
26
53
  lower.includes("rate-limit") ||
27
54
  lower.includes("too many requests") ||
28
- lower.includes("quota")
55
+ lower.includes("throttling")
29
56
  ) return "Cursor rate limit exceeded";
30
57
 
31
58
  if (
@@ -1,3 +1,5 @@
1
+ import { cursorModelEffortLadder } from "./effort-map";
2
+
1
3
  export interface CursorModelInfo {
2
4
  id: string;
3
5
  contextWindow?: number;
@@ -22,6 +24,7 @@ export function inferCursorContextWindow(modelId: string): number {
22
24
  if (id.startsWith("gemini-")) return CONTEXT_1M;
23
25
  if (id === "glm-5.2") return CONTEXT_1M;
24
26
  if (id.startsWith("gpt-5") || id === "gpt-5-codex") return CONTEXT_272K;
27
+ if (id.startsWith("grok-4.5")) return 500_000;
25
28
  if (id.startsWith("grok-")) return CONTEXT_256K;
26
29
  if (id.includes("claude")) return CONTEXT_200K;
27
30
  return CURSOR_DEFAULT_CONTEXT_WINDOW;
@@ -51,6 +54,42 @@ export function normalizeCursorModels(models: readonly CursorModelInfo[]): Curso
51
54
  return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
52
55
  }
53
56
 
57
+ // Live GetUsableModels ids append effort tiers to the base id (`claude-4.6-opus-high`). Only
58
+ // these suffixes may activate a base model — otherwise a sibling model like `claude-4-sonnet-1m`
59
+ // would falsely activate `claude-4-sonnet` (PR #73 review finding).
60
+ const LIVE_EFFORT_SUFFIXES = ["low", "medium", "high", "xhigh", "max"] as const;
61
+
62
+ /**
63
+ * True when a configured Cursor base model should remain exposed after live GetUsableModels filtering.
64
+ * Live ids are full effort-suffixed variants (`claude-4.6-opus-high`); base ids match exactly or by prefix.
65
+ */
66
+ export function isCursorModelAvailableForAccount(modelId: string, liveIds: readonly string[]): boolean {
67
+ return liveIds.some(id =>
68
+ id === modelId || LIVE_EFFORT_SUFFIXES.some(suffix => id === `${modelId}-${suffix}`));
69
+ }
70
+
71
+ /** Codex-facing id for Cursor's auto-router. Always kept in the catalog even when live discovery omits it. */
72
+ export const CURSOR_AUTO_MODEL_ID = "auto";
73
+
74
+ /** Wire id Cursor Connect expects for the auto-router (GetUsableModels returns `default`, not `auto`). */
75
+ export const CURSOR_AUTO_WIRE_MODEL_ID = "default";
76
+
77
+ /** Map a Codex-facing Cursor model id to the upstream wire id. */
78
+ export function cursorCodexToWireModelId(modelId: string): string {
79
+ const normalized = modelId.startsWith("cursor/") ? modelId.slice("cursor/".length) : modelId;
80
+ return normalized === CURSOR_AUTO_MODEL_ID ? CURSOR_AUTO_WIRE_MODEL_ID : normalized;
81
+ }
82
+
83
+ /** Filter the static Cursor seed to models this account can use. */
84
+ export function filterCursorConfiguredModelsByLiveDiscovery<T extends { id: string }>(
85
+ configured: readonly T[],
86
+ liveIds: readonly string[],
87
+ ): T[] {
88
+ return configured.filter(model =>
89
+ model.id === CURSOR_AUTO_MODEL_ID || isCursorModelAvailableForAccount(model.id, liveIds),
90
+ );
91
+ }
92
+
54
93
  export const CURSOR_STATIC_MODELS: readonly CursorModelInfo[] = normalizeCursorModels([
55
94
  // Context windows and the model lineup mirror Cursor's public models/pricing docs plus the jawcode
56
95
  // SOT (../jawcode/packages/ai/src/models.json, `cursor` provider), which mirrors the real
@@ -62,7 +101,7 @@ export const CURSOR_STATIC_MODELS: readonly CursorModelInfo[] = normalizeCursorM
62
101
  // gemini/grok/kimi/gpt-5-mini are reasoning models in the SOT but are sent bare (no tier picker).
63
102
  { id: "auto", contextWindow: CONTEXT_200K, supportsReasoningEffort: false },
64
103
 
65
- { id: "claude-sonnet-5", contextWindow: CONTEXT_200K },
104
+ { id: "claude-sonnet-5", contextWindow: CONTEXT_200K, supportsReasoningEffort: true },
66
105
  { id: "claude-4-sonnet", contextWindow: CONTEXT_200K },
67
106
  { id: "claude-4-sonnet-1m", contextWindow: CONTEXT_1M },
68
107
  { id: "claude-4.5-haiku", contextWindow: CONTEXT_200K },
@@ -71,12 +110,12 @@ export const CURSOR_STATIC_MODELS: readonly CursorModelInfo[] = normalizeCursorM
71
110
  { id: "claude-4.6-opus", contextWindow: CONTEXT_200K, supportsReasoningEffort: true },
72
111
  { id: "claude-4.6-sonnet", contextWindow: CONTEXT_200K, supportsReasoningEffort: true },
73
112
  { id: "claude-opus-4-7", contextWindow: CONTEXT_200K, supportsReasoningEffort: true },
113
+ // opus-4-7-fast: effort-suffix tiers unverified -> no tier picker; sent bare like live-only ids.
114
+ { id: "claude-opus-4-7-fast", contextWindow: CONTEXT_200K },
74
115
  { id: "claude-opus-4-8", contextWindow: CONTEXT_200K, supportsReasoningEffort: true },
75
116
  { id: "claude-fable-5", contextWindow: CONTEXT_200K, supportsReasoningEffort: true },
76
117
 
77
118
  { id: "composer-1", contextWindow: CONTEXT_200K },
78
- { id: "composer-1.5", contextWindow: CONTEXT_200K },
79
- { id: "composer-2", contextWindow: CONTEXT_200K },
80
119
  { id: "composer-2.5", contextWindow: CONTEXT_200K },
81
120
  { id: "composer-2.5-fast", contextWindow: CONTEXT_200K },
82
121
 
@@ -101,15 +140,20 @@ export const CURSOR_STATIC_MODELS: readonly CursorModelInfo[] = normalizeCursorM
101
140
  { id: "gpt-5.4-mini", contextWindow: CONTEXT_272K, supportsReasoningEffort: true },
102
141
  { id: "gpt-5.4-nano", contextWindow: CONTEXT_272K, supportsReasoningEffort: true },
103
142
  { id: "gpt-5.5", contextWindow: CONTEXT_272K, supportsReasoningEffort: true },
143
+ // gpt-5.5-extra: absent from cursor.com docs but SURVIVES the live GetUsableModels filter
144
+ // (account-verified 260709, devlog/model_update/260709_model_refresh/004_live_snapshot.md).
104
145
  { id: "gpt-5.5-extra", contextWindow: CONTEXT_200K, supportsReasoningEffort: true },
105
146
 
106
- { id: "grok-4.3", contextWindow: CONTEXT_200K, supportsReasoningEffort: true },
107
- { id: "grok-4.20", contextWindow: CONTEXT_200K, supportsReasoningEffort: true },
108
- { id: "grok-build-0.1", contextWindow: CONTEXT_256K },
109
- { id: "grok-code-fast-1", contextWindow: CONTEXT_256K },
147
+ // 260709 refresh: stale grok/composer/kimi/gpt ids dropped per current cursor.com docs; the
148
+ // 260709 note: grok-4.5 was deferred; confirmed live 260708 (cursor.com/models, xAI launch).
149
+
150
+ // Conflict resolution (260709): keep the refreshed 1M context + kimi-k2.7-code from de12fc8,
151
+ // take PR #73's supportsReasoningEffort for glm-5.2 (its effort-map tiers landed with the PR).
152
+ { id: "glm-5.2", contextWindow: CONTEXT_1M, supportsReasoningEffort: true },
153
+ { id: "kimi-k2.7-code", contextWindow: CONTEXT_262K },
110
154
 
111
- { id: "glm-5.2", contextWindow: CONTEXT_200K },
112
- { id: "kimi-k2.5", contextWindow: CONTEXT_262K },
155
+ { id: "grok-4.5", contextWindow: 500_000, supportsReasoningEffort: true },
156
+ { id: "grok-4.5-fast", contextWindow: 500_000 },
113
157
  ]);
114
158
 
115
159
  export function cursorModelIds(models: readonly CursorModelInfo[] = CURSOR_STATIC_MODELS): string[] {
@@ -138,7 +182,9 @@ export function cursorModelReasoningEfforts(
138
182
  return Object.fromEntries(
139
183
  normalizeCursorModels(models).map(model => [
140
184
  model.id,
141
- model.supportsReasoningEffort === true ? [...CURSOR_REASONING_EFFORTS] : [],
185
+ model.supportsReasoningEffort === true
186
+ ? cursorModelEffortLadder(model.id) ?? [...CURSOR_REASONING_EFFORTS]
187
+ : [],
142
188
  ]),
143
189
  );
144
190
  }
@@ -6,19 +6,30 @@
6
6
  * `claude-4.6-sonnet` only has `-medium`, and `composer`/`grok`/`gemini` take no suffix at all. A bare
7
7
  * id for a model that requires a suffix is rejected `ERROR_BAD_MODEL_NAME` (devlog 350.105).
8
8
  *
9
+ * Canonical effort order is always low < medium < high < xhigh < max (max is the top tier, confirmed
10
+ * against Anthropic docs and Cursor's live lineup). Tiers are stored in ascending canonical order.
11
+ *
9
12
  * `CURSOR_MODEL_EFFORT_TIERS` is the real catalog (from the Cursor `GetUsableModels` naming, mirrored in
10
13
  * jawcode's bundle), each base model -> its available suffixes in ascending order. `cursorEffortSuffix`
11
- * clamps a Codex reasoning effort onto that model's tiers so Codex's TOP effort maps to the model's TOP
12
- * tier (a `-max` model -> max, an `-xhigh` model -> xhigh), per the requested behavior.
14
+ * is literal-first: when the requested effort is one of the model's tiers, the effort you name is the
15
+ * suffix Cursor receives. It only clamps Codex effort ranks for efforts outside that model's tier set.
13
16
  */
14
17
 
15
18
  const CURSOR_MODEL_EFFORT_TIERS: Record<string, readonly string[]> = {
16
19
  "claude-4.5-opus": ["high"],
17
20
  "claude-4.6-opus": ["high", "max"],
18
21
  "claude-4.6-sonnet": ["medium"],
19
- "claude-fable-5": ["low", "medium", "high", "max", "xhigh"],
20
- "claude-opus-4-7": ["low", "medium", "high", "max", "xhigh"],
21
- "claude-opus-4-8": ["low", "medium", "high", "max", "xhigh"],
22
+ // max is always the top tier (canonical order: low < medium < high < xhigh < max), confirmed
23
+ // against Anthropic's effort ladder docs and Cursor's live model lineup.
24
+ "claude-fable-5": ["low", "medium", "high", "xhigh", "max"],
25
+ "claude-opus-4-7": ["low", "medium", "high", "xhigh", "max"],
26
+ "claude-opus-4-8": ["low", "medium", "high", "xhigh", "max"],
27
+ "claude-sonnet-5": ["low", "medium", "high", "xhigh", "max"],
28
+ "glm-5.2": ["high", "max"],
29
+ // GetUsableModels (2026-07-09) lists grok-4.5-{medium,high,xhigh} and grok-4.5-fast-{medium,high,xhigh};
30
+ // the bare "grok-4.5-fast" id was removed upstream and now returns not_found.
31
+ "grok-4.5": ["medium", "high", "xhigh"],
32
+ "grok-4.5-fast": ["medium", "high", "xhigh"],
22
33
  "gpt-5.1": ["low", "high"],
23
34
  "gpt-5.1-codex-max": ["low", "medium", "high", "xhigh"],
24
35
  "gpt-5.1-codex-mini": ["low", "high"],
@@ -32,9 +43,16 @@ const CURSOR_MODEL_EFFORT_TIERS: Record<string, readonly string[]> = {
32
43
  "gpt-5.5-extra": ["high"],
33
44
  };
34
45
 
46
+ const CANONICAL_CODEX_EFFORT_ORDER = ["low", "medium", "high", "xhigh", "max"] as const;
47
+
48
+ function normalizeRequestedEffort(reasoning: string | undefined): string | undefined {
49
+ const normalized = reasoning?.toLowerCase();
50
+ return normalized === "ultra" ? "max" : normalized;
51
+ }
52
+
35
53
  /** Collapse a Codex reasoning-effort label to a low/medium/high rank for clamping onto a model's tiers. */
36
54
  function codexEffortRank(reasoning: string | undefined): "low" | "medium" | "high" {
37
- switch ((reasoning ?? "").toLowerCase()) {
55
+ switch (normalizeRequestedEffort(reasoning) ?? "") {
38
56
  case "none":
39
57
  case "minimal":
40
58
  case "low":
@@ -53,11 +71,13 @@ function codexEffortRank(reasoning: string | undefined): "low" | "medium" | "hig
53
71
 
54
72
  /**
55
73
  * The Cursor effort suffix to use for `baseModelId` given a Codex reasoning effort, or `undefined` when
56
- * the model takes no suffix (bare). Codex's top effort maps to the model's top tier (max / xhigh).
74
+ * the model takes no suffix (bare). Literal model tiers pass through; unknown efforts clamp by rank.
57
75
  */
58
76
  export function cursorEffortSuffix(baseModelId: string, reasoning: string | undefined): string | undefined {
59
77
  const tiers = CURSOR_MODEL_EFFORT_TIERS[baseModelId];
60
78
  if (!tiers || tiers.length === 0) return undefined;
79
+ const requested = normalizeRequestedEffort(reasoning);
80
+ if (requested && tiers.includes(requested)) return requested;
61
81
  switch (codexEffortRank(reasoning)) {
62
82
  case "low":
63
83
  return tiers[0];
@@ -68,6 +88,14 @@ export function cursorEffortSuffix(baseModelId: string, reasoning: string | unde
68
88
  }
69
89
  }
70
90
 
91
+ /** The Codex-facing picker ladder for a Cursor model, sorted in canonical Codex effort order. */
92
+ export function cursorModelEffortLadder(baseModelId: string): string[] | undefined {
93
+ const tiers = CURSOR_MODEL_EFFORT_TIERS[baseModelId];
94
+ if (!tiers || tiers.length === 0) return undefined;
95
+ const tierSet = new Set(tiers);
96
+ return CANONICAL_CODEX_EFFORT_ORDER.filter(effort => tierSet.has(effort));
97
+ }
98
+
71
99
  /** Base models known to carry a reasoning-effort suffix (everything else is sent bare). */
72
100
  export function cursorModelHasEffortTiers(baseModelId: string): boolean {
73
101
  return (CURSOR_MODEL_EFFORT_TIERS[baseModelId]?.length ?? 0) > 0;
@@ -66,6 +66,7 @@ export async function fetchCursorUsableModels(opts: CursorUsableModelsOptions):
66
66
  "x-ghost-mode": "true",
67
67
  "x-cursor-client-version": opts.clientVersion ?? CURSOR_DISCOVERY_CLIENT_VERSION,
68
68
  "x-cursor-client-type": "cli",
69
+ "x-session-id": crypto.randomUUID(),
69
70
  });
70
71
 
71
72
  let status = 0;
@@ -79,6 +80,8 @@ export async function fetchCursorUsableModels(opts: CursorUsableModelsOptions):
79
80
  if (status !== 200) return close(null);
80
81
  try {
81
82
  const response = fromBinary(GetUsableModelsResponseSchema, new Uint8Array(Buffer.concat(chunks)));
83
+ // Account filtering uses wire `model_id` values only. Aliases like `composer-2-5` must not
84
+ // make stale configured ids such as `composer-2` look activated.
82
85
  const ids = (response.models ?? [])
83
86
  .map(model => (model as { modelId?: string }).modelId)
84
87
  .filter((id): id is string => typeof id === "string" && id.length > 0);