@bitkyc08/opencodex 2.1.1 → 2.1.5
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 +29 -4
- package/README.md +30 -5
- package/README.zh-CN.md +6 -0
- package/gui/dist/assets/index-DB2i6w5f.js +9 -0
- package/gui/dist/assets/{index-cEIM1XWY.css → index-dCS-lwCM.css} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +6 -3
- package/src/adapters/azure.ts +7 -7
- package/src/adapters/google.ts +4 -3
- package/src/adapters/openai-chat.ts +2 -1
- package/src/adapters/openai-responses.ts +2 -1
- package/src/bridge.ts +125 -24
- package/src/cli.ts +166 -13
- package/src/codex-catalog.ts +1 -0
- package/src/codex-history-provider.ts +86 -0
- package/src/codex-inject.ts +9 -1
- package/src/codex-shim.ts +42 -24
- package/src/config.ts +31 -5
- package/src/init.ts +11 -0
- package/src/oauth/store.ts +10 -4
- package/src/open-url.ts +7 -3
- package/src/ports.ts +30 -0
- package/src/providers/registry.ts +1 -1
- package/src/responses/parser.ts +9 -6
- package/src/responses/schema.ts +1 -0
- package/src/server.ts +182 -13
- package/src/service.ts +29 -2
- package/src/types.ts +8 -0
- package/src/update.ts +12 -2
- package/src/web-search/loop.ts +4 -1
- package/src/ws-bridge.ts +1 -1
- package/gui/dist/assets/index-DgCnBxqJ.js +0 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;--bg:var(--lightningcss-light,#f6f7f9)var(--lightningcss-dark,#0c0d11);--rail:var(--lightningcss-light,#fff)var(--lightningcss-dark,#101218);--surface:var(--lightningcss-light,#fff)var(--lightningcss-dark,#15171d);--raised:var(--lightningcss-light,#f1f2f5)var(--lightningcss-dark,#1c1f27);--raised-hover:var(--lightningcss-light,#e8eaee)var(--lightningcss-dark,#242833);--border:var(--lightningcss-light,#e2e4e9)var(--lightningcss-dark,#2a2e39);--border-soft:var(--lightningcss-light,#ededf1)var(--lightningcss-dark,#20242d);--hover:var(--lightningcss-light,#11131c09)var(--lightningcss-dark,#ffffff06);--text:var(--lightningcss-light,#16181d)var(--lightningcss-dark,#edeef2);--muted:var(--lightningcss-light,#5b6270)var(--lightningcss-dark,#a3a9b5);--faint:var(--lightningcss-light,#868d9b)var(--lightningcss-dark,#6b7280);--accent:var(--lightningcss-light,#4f46e5)var(--lightningcss-dark,#6366f1);--accent-hover:var(--lightningcss-light,#4338ca)var(--lightningcss-dark,#818cf8);--accent-ink:#fff;--accent-soft:var(--lightningcss-light,#4f46e51a)var(--lightningcss-dark,#6366f129);--accent-ring:var(--lightningcss-light,#4f46e566)var(--lightningcss-dark,#6366f180);--green:var(--lightningcss-light,#047857)var(--lightningcss-dark,#34d399);--green-soft:var(--lightningcss-light,#0596691a)var(--lightningcss-dark,#34d39921);--red:var(--lightningcss-light,#b91c1c)var(--lightningcss-dark,#f87171);--red-soft:var(--lightningcss-light,#b91c1c17)var(--lightningcss-dark,#f8717121);--amber:var(--lightningcss-light,#b45309)var(--lightningcss-dark,#fbbf24);--amber-soft:var(--lightningcss-light,#b453091a)var(--lightningcss-dark,#fbbf2421);--radius:8px;--radius-sm:5px;--radius-xs:4px;--font:-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}*{box-sizing:border-box}html,body,#root{height:100%}body{background:var(--bg);color:var(--text);font-family:var(--font);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;margin:0;font-size:14px;line-height:1.5}a{color:var(--accent-hover);text-decoration:none}a:hover{text-decoration:underline}code,.mono{font-family:var(--mono);font-size:.92em}h1,h2,h3,h4{letter-spacing:-.01em;margin:0;font-weight:650}::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(--rail);flex-direction:column;align-self:start;gap:4px;height:100dvh;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:-.02em;font-size:15px;font-weight:700;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{background:var(--raised);color:var(--text)}.nav-item.active{background:var(--accent-soft);color:var(--text)}.nav-item svg{width:17px;height:17px;color:var(--faint);flex-shrink:0}.nav-item.active svg{color:var(--accent)}.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;display:flex}.sidebar-link:hover{background:var(--raised);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(--raised);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}.btn{border-radius:var(--radius-sm);font:inherit;cursor:pointer;white-space:nowrap;border:1px solid #0000;justify-content:center;align-items:center;gap:7px;padding:7px 14px;font-size:13px;font-weight:550;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(--raised);color:var(--text);border-color:var(--border)}.btn-ghost:hover:not(:disabled){background:var(--raised-hover)}.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-xs);padding:4px 9px;font-size:12px}.btn-icon{padding:5px}.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px}.panel-accent{background:linear-gradient(180deg, var(--accent-soft), transparent 120%), var(--surface);border-color:#7c5cff47}.stat-row{grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:28px;display:grid}.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(--accent-ring)}.stat .label{color:var(--muted);text-transform:uppercase;letter-spacing:.05em;align-items:center;gap:6px;margin-bottom:9px;font-size:11px;font-weight:600;display:flex}.stat .label svg{width:14px;height:14px}.stat .value{letter-spacing:-.02em;font-size:24px;font-weight:700;line-height:1.1}.stat .value.mono{font-family:var(--mono);font-size:19px}.model-group-head{color:var(--muted);text-transform:uppercase;letter-spacing:.04em;align-items:baseline;gap:8px;margin:0 0 8px;font-size:12px;font-weight:600;display:flex}.model-group-head .count{font-family:var(--mono);text-transform:none;letter-spacing:0;color:var(--faint);font-weight:500}.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(--accent-ring);background:var(--hover)}.model-card .id{font-family:var(--mono);letter-spacing:-.01em;color:var(--text);font-size:13px;font-weight:600}.badge{font-size:11px;font-weight:600;font-family:var(--mono);letter-spacing:.01em;border-radius:99px;align-items:center;gap:5px;padding:2px 8px;display:inline-flex}.badge-accent{background:var(--accent-soft);color:var(--accent-hover)}.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)}.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);text-transform:uppercase;letter-spacing:.04em;border-bottom:1px solid var(--border);padding:9px 12px;font-size:11.5px;font-weight:600}.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);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(--accent);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}.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}.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}}.modal-overlay{-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);z-index:50;background:var(--lightningcss-light,#11131c73)var(--lightningcss-dark,#0009);justify-content:center;align-items:flex-start;padding:8vh 16px;display:flex;position:fixed;inset:0}.modal-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);width:100%;max-width:520px;box-shadow:var(--shadow);max-height:84vh;padding:20px;overflow-y:auto}.modal-head{justify-content:space-between;align-items:center;margin-bottom:16px;display:flex}.modal-head h3{font-size:16px}.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{justify-content:space-between;align-items:flex-start;gap:12px;padding:15px 16px;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}@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{overscroll-behavior-x:contain;border-top:1px solid var(--border-soft);scrollbar-width:none;flex-direction:row;flex:100%;order:3;gap:2px;min-width:0;margin:0;padding:4px 0 8px;display:flex;overflow-x:auto}.sidebar nav::-webkit-scrollbar{display:none}.nav-item{white-space:nowrap;width:auto;min-height:44px;padding:9px 14px;font-size:14px}.main-inner{padding:22px 18px 48px}.stat-row{grid-template-columns:repeat(2,minmax(0,1fr))}.tbl{min-width:460px}}
|
|
1
|
+
:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;--bg:var(--lightningcss-light,#f6f7f9)var(--lightningcss-dark,#0c0d11);--rail:var(--lightningcss-light,#fff)var(--lightningcss-dark,#101218);--surface:var(--lightningcss-light,#fff)var(--lightningcss-dark,#15171d);--raised:var(--lightningcss-light,#f1f2f5)var(--lightningcss-dark,#1c1f27);--raised-hover:var(--lightningcss-light,#e8eaee)var(--lightningcss-dark,#242833);--border:var(--lightningcss-light,#e2e4e9)var(--lightningcss-dark,#2a2e39);--border-soft:var(--lightningcss-light,#ededf1)var(--lightningcss-dark,#20242d);--hover:var(--lightningcss-light,#11131c09)var(--lightningcss-dark,#ffffff06);--text:var(--lightningcss-light,#16181d)var(--lightningcss-dark,#edeef2);--muted:var(--lightningcss-light,#5b6270)var(--lightningcss-dark,#a3a9b5);--faint:var(--lightningcss-light,#868d9b)var(--lightningcss-dark,#6b7280);--accent:var(--lightningcss-light,#4f46e5)var(--lightningcss-dark,#6366f1);--accent-hover:var(--lightningcss-light,#4338ca)var(--lightningcss-dark,#818cf8);--accent-ink:#fff;--accent-soft:var(--lightningcss-light,#4f46e51a)var(--lightningcss-dark,#6366f129);--accent-ring:var(--lightningcss-light,#4f46e566)var(--lightningcss-dark,#6366f180);--green:var(--lightningcss-light,#047857)var(--lightningcss-dark,#34d399);--green-soft:var(--lightningcss-light,#0596691a)var(--lightningcss-dark,#34d39921);--red:var(--lightningcss-light,#b91c1c)var(--lightningcss-dark,#f87171);--red-soft:var(--lightningcss-light,#b91c1c17)var(--lightningcss-dark,#f8717121);--amber:var(--lightningcss-light,#b45309)var(--lightningcss-dark,#fbbf24);--amber-soft:var(--lightningcss-light,#b453091a)var(--lightningcss-dark,#fbbf2421);--radius:8px;--radius-sm:5px;--radius-xs:4px;--font:-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}*{box-sizing:border-box}html,body,#root{height:100%}body{background:var(--bg);color:var(--text);font-family:var(--font);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;margin:0;font-size:14px;line-height:1.5}a{color:var(--accent-hover);text-decoration:none}a:hover{text-decoration:underline}code,.mono{font-family:var(--mono);font-size:.92em}h1,h2,h3,h4{letter-spacing:-.01em;margin:0;font-weight:650}::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(--rail);flex-direction:column;align-self:start;gap:4px;height:100dvh;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:-.02em;font-size:15px;font-weight:700;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{background:var(--raised);color:var(--text)}.nav-item.active{background:var(--accent-soft);color:var(--text)}.nav-item svg{width:17px;height:17px;color:var(--faint);flex-shrink:0}.nav-item.active svg{color:var(--accent)}.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;display:flex}.sidebar-link:hover{background:var(--raised);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(--raised);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}.btn{border-radius:var(--radius-sm);font:inherit;cursor:pointer;white-space:nowrap;border:1px solid #0000;justify-content:center;align-items:center;gap:7px;padding:7px 14px;font-size:13px;font-weight:550;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(--raised);color:var(--text);border-color:var(--border)}.btn-ghost:hover:not(:disabled){background:var(--raised-hover)}.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-xs);padding:4px 9px;font-size:12px}.btn-icon{padding:5px}.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px}.panel-accent{background:linear-gradient(180deg, var(--accent-soft), transparent 120%), var(--surface);border-color:#7c5cff47}.stat-row{grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:28px;display:grid}.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(--accent-ring)}.stat .label{color:var(--muted);text-transform:uppercase;letter-spacing:.05em;align-items:center;gap:6px;margin-bottom:9px;font-size:11px;font-weight:600;display:flex}.stat .label svg{width:14px;height:14px}.stat .value{letter-spacing:-.02em;font-size:24px;font-weight:700;line-height:1.1}.stat .value.mono{font-family:var(--mono);font-size:19px}.model-group-head{color:var(--muted);text-transform:uppercase;letter-spacing:.04em;align-items:baseline;gap:8px;margin:0 0 8px;font-size:12px;font-weight:600;display:flex}.model-group-head .count{font-family:var(--mono);text-transform:none;letter-spacing:0;color:var(--faint);font-weight:500}.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(--accent-ring);background:var(--hover)}.model-card .id{font-family:var(--mono);letter-spacing:-.01em;color:var(--text);font-size:13px;font-weight:600}.badge{font-size:11px;font-weight:600;font-family:var(--mono);letter-spacing:.01em;border-radius:99px;align-items:center;gap:5px;padding:2px 8px;display:inline-flex}.badge-accent{background:var(--accent-soft);color:var(--accent-hover)}.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)}.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);text-transform:uppercase;letter-spacing:.04em;border-bottom:1px solid var(--border);padding:9px 12px;font-size:11.5px;font-weight:600}.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);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(--accent);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{border-radius:var(--radius-sm);background:var(--raised);border:1px solid var(--border);color:var(--text);font:inherit;cursor:pointer;padding:5px 8px;font-size:13px;transition:border-color .12s}.select-sm:focus{border-color:var(--accent);outline:none}.select-sm:disabled{opacity:.5;cursor:default}.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}.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}}.modal-overlay{-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);z-index:50;background:var(--lightningcss-light,#11131c73)var(--lightningcss-dark,#0009);justify-content:center;align-items:flex-start;padding:8vh 16px;display:flex;position:fixed;inset:0}.modal-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);width:100%;max-width:520px;box-shadow:var(--shadow);max-height:84vh;padding:20px;overflow-y:auto}.modal-head{justify-content:space-between;align-items:center;margin-bottom:16px;display:flex}.modal-head h3{font-size:16px}.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{justify-content:space-between;align-items:flex-start;gap:12px;padding:15px 16px;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}@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{overscroll-behavior-x:contain;border-top:1px solid var(--border-soft);scrollbar-width:none;flex-direction:row;flex:100%;order:3;gap:2px;min-width:0;margin:0;padding:4px 0 8px;display:flex;overflow-x:auto}.sidebar nav::-webkit-scrollbar{display:none}.nav-item{white-space:nowrap;width:auto;min-height:44px;padding:9px 14px;font-size:14px}.main-inner{padding:22px 18px 48px}.stat-row{grid-template-columns:repeat(2,minmax(0,1fr))}.tbl{min-width:460px}}
|
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-DB2i6w5f.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index-dCS-lwCM.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
OcxToolCall,
|
|
13
13
|
OcxUsage,
|
|
14
14
|
} from "../types";
|
|
15
|
+
import { namespacedToolName } from "../types";
|
|
15
16
|
import { ANTHROPIC_OAUTH_BETA, CLAUDE_CODE_SYSTEM_INSTRUCTION, applyClaudeToolPrefix, stripClaudeToolPrefix } from "../oauth/anthropic";
|
|
16
17
|
import { parseDataUrl } from "./image";
|
|
17
18
|
|
|
@@ -85,7 +86,8 @@ function messagesToAnthropicFormat(parsed: OcxParsedRequest, isOAuth: boolean):
|
|
|
85
86
|
content.push({ type: "thinking", thinking: t.thinking, ...(t.signature ? { signature: t.signature } : {}) });
|
|
86
87
|
} else if (part.type === "toolCall") {
|
|
87
88
|
const tc = part as OcxToolCall;
|
|
88
|
-
|
|
89
|
+
const flatName = namespacedToolName(tc.namespace, tc.name);
|
|
90
|
+
content.push({ type: "tool_use", id: tc.id, name: isOAuth ? applyClaudeToolPrefix(flatName) : flatName, input: tc.arguments });
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
messages.push({ role: "assistant", content });
|
|
@@ -116,7 +118,7 @@ function messagesToAnthropicFormat(parsed: OcxParsedRequest, isOAuth: boolean):
|
|
|
116
118
|
function toolsToAnthropicFormat(parsed: OcxParsedRequest, isOAuth: boolean): unknown[] | undefined {
|
|
117
119
|
if (!parsed.context.tools || parsed.context.tools.length === 0) return undefined;
|
|
118
120
|
return parsed.context.tools.map(t => ({
|
|
119
|
-
name: isOAuth ? applyClaudeToolPrefix(t.name) : t.name,
|
|
121
|
+
name: isOAuth ? applyClaudeToolPrefix(namespacedToolName(t.namespace, t.name)) : namespacedToolName(t.namespace, t.name),
|
|
120
122
|
description: t.description,
|
|
121
123
|
input_schema: t.parameters,
|
|
122
124
|
}));
|
|
@@ -175,7 +177,8 @@ export function createAnthropicAdapter(provider: OcxProviderConfig): ProviderAda
|
|
|
175
177
|
else if (typeof tc === "object" && "name" in tc) body.tool_choice = { type: "tool", name: isOAuth ? applyClaudeToolPrefix(tc.name) : tc.name };
|
|
176
178
|
}
|
|
177
179
|
|
|
178
|
-
const
|
|
180
|
+
const base = provider.baseUrl.replace(/\/v1\/?$/, "");
|
|
181
|
+
const url = `${base}/v1/messages`;
|
|
179
182
|
const headers: Record<string, string> = {
|
|
180
183
|
"Content-Type": "application/json",
|
|
181
184
|
"anthropic-version": "2023-06-01",
|
package/src/adapters/azure.ts
CHANGED
|
@@ -19,13 +19,13 @@ export function createAzureAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
19
19
|
headers["api-key"] = provider.apiKey;
|
|
20
20
|
delete headers["Authorization"];
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
url
|
|
27
|
-
|
|
28
|
-
};
|
|
22
|
+
let url = request.url;
|
|
23
|
+
if (!url.includes("/v1/")) {
|
|
24
|
+
const apiVersion = (provider.headers?.["api-version"]) ?? "2025-04-01-preview";
|
|
25
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
26
|
+
url = `${url}${separator}api-version=${apiVersion}`;
|
|
27
|
+
}
|
|
28
|
+
return { ...request, url, headers };
|
|
29
29
|
},
|
|
30
30
|
};
|
|
31
31
|
}
|
package/src/adapters/google.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
OcxToolCall,
|
|
11
11
|
OcxUsage,
|
|
12
12
|
} from "../types";
|
|
13
|
+
import { namespacedToolName } from "../types";
|
|
13
14
|
import { contentPartsToText, parseDataUrl } from "./image";
|
|
14
15
|
|
|
15
16
|
function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?: unknown; contents: unknown[] } {
|
|
@@ -46,7 +47,7 @@ function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?:
|
|
|
46
47
|
if (p.type === "text") parts.push({ text: (p as OcxTextContent).text });
|
|
47
48
|
else if (p.type === "toolCall") {
|
|
48
49
|
const tc = p as OcxToolCall;
|
|
49
|
-
parts.push({ functionCall: { name: tc.name, args: tc.arguments } });
|
|
50
|
+
parts.push({ functionCall: { name: namespacedToolName(tc.namespace, tc.name), args: tc.arguments } });
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
contents.push({ role: "model", parts });
|
|
@@ -55,7 +56,7 @@ function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?:
|
|
|
55
56
|
case "toolResult": {
|
|
56
57
|
contents.push({
|
|
57
58
|
role: "user",
|
|
58
|
-
parts: [{ functionResponse: { name: msg.toolName, response: { result: contentPartsToText(msg.content) } } }],
|
|
59
|
+
parts: [{ functionResponse: { name: namespacedToolName(msg.toolNamespace, msg.toolName), response: { result: contentPartsToText(msg.content) } } }],
|
|
59
60
|
});
|
|
60
61
|
break;
|
|
61
62
|
}
|
|
@@ -69,7 +70,7 @@ function toolsToGeminiFormat(parsed: OcxParsedRequest): unknown[] | undefined {
|
|
|
69
70
|
if (!parsed.context.tools?.length) return undefined;
|
|
70
71
|
return [{
|
|
71
72
|
functionDeclarations: parsed.context.tools.map(t => ({
|
|
72
|
-
name: t.name,
|
|
73
|
+
name: namespacedToolName(t.namespace, t.name),
|
|
73
74
|
description: t.description,
|
|
74
75
|
parameters: t.parameters,
|
|
75
76
|
})),
|
|
@@ -160,7 +160,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
160
160
|
stream: parsed.stream,
|
|
161
161
|
};
|
|
162
162
|
if (tools) body.tools = tools;
|
|
163
|
-
if (toolChoice !== undefined) {
|
|
163
|
+
if (tools && toolChoice !== undefined) {
|
|
164
164
|
body.tool_choice = modelInList(provider.autoToolChoiceOnlyModels, parsed.modelId)
|
|
165
165
|
? (toolChoice === "none" ? "none" : "auto")
|
|
166
166
|
: toolChoice;
|
|
@@ -182,6 +182,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
182
182
|
body.frequency_penalty = parsed.options.frequencyPenalty;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
if (tools) body.parallel_tool_calls = false;
|
|
185
186
|
if (parsed.stream) {
|
|
186
187
|
body.stream_options = { include_usage: true };
|
|
187
188
|
}
|
|
@@ -60,7 +60,8 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
60
60
|
if (v) headers[h] = v; // …so forwarded auth always wins.
|
|
61
61
|
}
|
|
62
62
|
} else {
|
|
63
|
-
|
|
63
|
+
const base = provider.baseUrl.replace(/\/v1\/?$/, "");
|
|
64
|
+
url = `${base}/v1/responses`;
|
|
64
65
|
if (provider.apiKey) headers["Authorization"] = `Bearer ${provider.apiKey}`;
|
|
65
66
|
if (provider.headers) Object.assign(headers, provider.headers);
|
|
66
67
|
}
|
package/src/bridge.ts
CHANGED
|
@@ -43,7 +43,7 @@ export function bridgeToResponsesSSE(
|
|
|
43
43
|
toolSearchToolNames?: Set<string>,
|
|
44
44
|
onCancel?: () => void,
|
|
45
45
|
heartbeatMs = 2_000,
|
|
46
|
-
options?: { responseId?: string },
|
|
46
|
+
options?: { responseId?: string; stallTimeoutSec?: number; hideThinkingSummary?: boolean },
|
|
47
47
|
): ReadableStream<Uint8Array> {
|
|
48
48
|
// Freeform/custom tools (apply_patch) carry their body in `input`; the model is given a
|
|
49
49
|
// function with `{input:string}`, so unwrap it here when relaying back as a custom_tool_call.
|
|
@@ -104,7 +104,8 @@ export function bridgeToResponsesSSE(
|
|
|
104
104
|
// whenever a real event was emitted since the last tick, so it only fires on a genuine stall.
|
|
105
105
|
const heartbeatFrame = encoder.encode('event: response.heartbeat\ndata: {"type":"response.heartbeat"}\n\n');
|
|
106
106
|
let stallTicks = 0;
|
|
107
|
-
const
|
|
107
|
+
const stallSec = Math.max(1, options?.stallTimeoutSec ?? 90);
|
|
108
|
+
const maxStallTicks = Math.ceil((stallSec * 1000) / heartbeatMs);
|
|
108
109
|
beat = setInterval(() => {
|
|
109
110
|
if (closed) return;
|
|
110
111
|
if (activity) { activity = false; stallTicks = 0; return; }
|
|
@@ -230,6 +231,8 @@ export function bridgeToResponsesSSE(
|
|
|
230
231
|
|
|
231
232
|
try {
|
|
232
233
|
for await (const event of events) {
|
|
234
|
+
activity = true;
|
|
235
|
+
stallTicks = 0;
|
|
233
236
|
switch (event.type) {
|
|
234
237
|
case "text_delta": {
|
|
235
238
|
if (currentReasoning) closeCurrentReasoning();
|
|
@@ -256,6 +259,7 @@ export function bridgeToResponsesSSE(
|
|
|
256
259
|
break;
|
|
257
260
|
}
|
|
258
261
|
case "thinking_delta": {
|
|
262
|
+
if (options?.hideThinkingSummary) break;
|
|
259
263
|
if (currentMsg) closeCurrentMessage();
|
|
260
264
|
if (currentRawReasoning) closeCurrentRawReasoning();
|
|
261
265
|
if (currentToolCall) closeCurrentToolCall();
|
|
@@ -406,46 +410,143 @@ export function bridgeToResponsesSSE(
|
|
|
406
410
|
export function buildResponseJSON(
|
|
407
411
|
events: AdapterEvent[],
|
|
408
412
|
modelId: string,
|
|
413
|
+
options?: {
|
|
414
|
+
hideThinkingSummary?: boolean;
|
|
415
|
+
toolNsMap?: Map<string, { namespace: string; name: string }>;
|
|
416
|
+
freeformToolNames?: Set<string>;
|
|
417
|
+
toolSearchToolNames?: Set<string>;
|
|
418
|
+
},
|
|
409
419
|
): Record<string, unknown> {
|
|
410
420
|
const responseId = `resp_${uuid()}`;
|
|
411
421
|
const output: OutputItem[] = [];
|
|
412
|
-
let text = "";
|
|
413
|
-
let summaryReasoning = "";
|
|
414
|
-
let rawReasoning = "";
|
|
415
422
|
let usage: OcxUsage | undefined;
|
|
423
|
+
let errorMessage: string | undefined;
|
|
416
424
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
425
|
+
let currentText = "";
|
|
426
|
+
let currentSummaryReasoning = "";
|
|
427
|
+
let currentRawReasoning = "";
|
|
428
|
+
let currentToolCallId = "";
|
|
429
|
+
let currentToolCallName = "";
|
|
430
|
+
let currentToolCallArgs = "";
|
|
431
|
+
|
|
432
|
+
const freeformInput = (args: string): string => {
|
|
433
|
+
try { const o = JSON.parse(args); if (o && typeof o.input === "string") return o.input; } catch { /* raw */ }
|
|
434
|
+
return args;
|
|
435
|
+
};
|
|
436
|
+
const parseArgsObj = (args: string): Record<string, unknown> => {
|
|
437
|
+
try { const o = JSON.parse(args); return o && typeof o === "object" ? o : {}; } catch { return {}; }
|
|
438
|
+
};
|
|
423
439
|
|
|
424
|
-
|
|
440
|
+
const flushText = () => {
|
|
441
|
+
if (!currentText) return;
|
|
425
442
|
output.push({
|
|
426
|
-
type: "
|
|
427
|
-
content: [{ type: "
|
|
443
|
+
type: "message", id: `msg_${uuid()}`, role: "assistant", status: "completed",
|
|
444
|
+
content: [{ type: "output_text", text: currentText, annotations: [] }],
|
|
428
445
|
});
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
446
|
+
currentText = "";
|
|
447
|
+
};
|
|
448
|
+
const flushSummaryReasoning = () => {
|
|
449
|
+
if (!currentSummaryReasoning || options?.hideThinkingSummary) { currentSummaryReasoning = ""; return; }
|
|
432
450
|
output.push({
|
|
433
451
|
type: "reasoning", id: `rs_${uuid()}`,
|
|
434
|
-
summary: [{ type: "summary_text", text:
|
|
452
|
+
summary: [{ type: "summary_text", text: currentSummaryReasoning }],
|
|
435
453
|
});
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
454
|
+
currentSummaryReasoning = "";
|
|
455
|
+
};
|
|
456
|
+
const flushRawReasoning = () => {
|
|
457
|
+
if (!currentRawReasoning) return;
|
|
439
458
|
output.push({
|
|
440
|
-
type: "
|
|
441
|
-
content: [{ type: "
|
|
459
|
+
type: "reasoning", id: `rs_${uuid()}`, summary: [],
|
|
460
|
+
content: [{ type: "reasoning_text", text: currentRawReasoning }],
|
|
442
461
|
});
|
|
462
|
+
currentRawReasoning = "";
|
|
463
|
+
};
|
|
464
|
+
const flushToolCall = () => {
|
|
465
|
+
if (!currentToolCallId) return;
|
|
466
|
+
const mapped = options?.toolNsMap?.get(currentToolCallName);
|
|
467
|
+
const realName = mapped?.name ?? currentToolCallName;
|
|
468
|
+
const ns = mapped?.namespace;
|
|
469
|
+
const toolSearch = options?.toolSearchToolNames?.has(realName) ?? false;
|
|
470
|
+
const freeform = !toolSearch && (options?.freeformToolNames?.has(realName) ?? false);
|
|
471
|
+
if (toolSearch) {
|
|
472
|
+
output.push({
|
|
473
|
+
type: "tool_search_call", id: `fc_${uuid()}`,
|
|
474
|
+
call_id: currentToolCallId, execution: "client",
|
|
475
|
+
arguments: parseArgsObj(currentToolCallArgs), status: "completed",
|
|
476
|
+
});
|
|
477
|
+
} else if (freeform) {
|
|
478
|
+
output.push({
|
|
479
|
+
type: "custom_tool_call", id: `fc_${uuid()}`,
|
|
480
|
+
call_id: currentToolCallId, name: realName,
|
|
481
|
+
input: freeformInput(currentToolCallArgs), status: "completed",
|
|
482
|
+
});
|
|
483
|
+
} else {
|
|
484
|
+
output.push({
|
|
485
|
+
type: "function_call", id: `fc_${uuid()}`,
|
|
486
|
+
call_id: currentToolCallId, name: realName,
|
|
487
|
+
arguments: currentToolCallArgs || "{}", status: "completed",
|
|
488
|
+
...(ns ? { namespace: ns } : {}),
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
currentToolCallId = "";
|
|
492
|
+
currentToolCallName = "";
|
|
493
|
+
currentToolCallArgs = "";
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
for (const e of events) {
|
|
497
|
+
switch (e.type) {
|
|
498
|
+
case "text_delta":
|
|
499
|
+
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
500
|
+
if (currentRawReasoning) flushRawReasoning();
|
|
501
|
+
if (currentToolCallId) flushToolCall();
|
|
502
|
+
currentText += e.text;
|
|
503
|
+
break;
|
|
504
|
+
case "thinking_delta":
|
|
505
|
+
if (currentText) flushText();
|
|
506
|
+
if (currentRawReasoning) flushRawReasoning();
|
|
507
|
+
if (currentToolCallId) flushToolCall();
|
|
508
|
+
currentSummaryReasoning += e.thinking;
|
|
509
|
+
break;
|
|
510
|
+
case "reasoning_raw_delta":
|
|
511
|
+
if (currentText) flushText();
|
|
512
|
+
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
513
|
+
if (currentToolCallId) flushToolCall();
|
|
514
|
+
currentRawReasoning += e.text;
|
|
515
|
+
break;
|
|
516
|
+
case "tool_call_start":
|
|
517
|
+
if (currentText) flushText();
|
|
518
|
+
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
519
|
+
if (currentRawReasoning) flushRawReasoning();
|
|
520
|
+
flushToolCall();
|
|
521
|
+
currentToolCallId = e.id;
|
|
522
|
+
currentToolCallName = e.name;
|
|
523
|
+
currentToolCallArgs = "";
|
|
524
|
+
break;
|
|
525
|
+
case "tool_call_delta":
|
|
526
|
+
currentToolCallArgs += e.arguments;
|
|
527
|
+
break;
|
|
528
|
+
case "tool_call_end":
|
|
529
|
+
flushToolCall();
|
|
530
|
+
break;
|
|
531
|
+
case "error":
|
|
532
|
+
errorMessage = e.message;
|
|
533
|
+
break;
|
|
534
|
+
case "done":
|
|
535
|
+
usage = e.usage;
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
443
538
|
}
|
|
539
|
+
flushText();
|
|
540
|
+
flushSummaryReasoning();
|
|
541
|
+
flushRawReasoning();
|
|
542
|
+
flushToolCall();
|
|
444
543
|
|
|
445
544
|
return {
|
|
446
545
|
id: responseId, object: "response",
|
|
447
546
|
created_at: Math.floor(Date.now() / 1000),
|
|
448
|
-
status: "
|
|
547
|
+
status: errorMessage ? "failed" : "completed",
|
|
548
|
+
model: modelId, output,
|
|
549
|
+
...(errorMessage ? { error: { message: errorMessage } } : {}),
|
|
449
550
|
usage: responsesUsage(usage),
|
|
450
551
|
};
|
|
451
552
|
}
|
package/src/cli.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { execFileSync, spawn } from "node:child_process";
|
|
3
|
+
import { rmSync } from "node:fs";
|
|
3
4
|
import { restoreNativeCodex } from "./codex-inject";
|
|
4
|
-
import { loadConfig, readPid, removePid, writePid } from "./config";
|
|
5
|
-
import {
|
|
5
|
+
import { codexAutoStartEnabled, getConfigDir, loadConfig, readPid, removePid, saveConfig, writePid } from "./config";
|
|
6
|
+
import { findAvailablePort } from "./ports";
|
|
7
|
+
import { serviceCommand, stopServiceIfInstalled, uninstallServiceIfInstalled } from "./service";
|
|
6
8
|
import { startServer } from "./server";
|
|
7
9
|
import { maybeShowStarPrompt } from "./star-prompt";
|
|
8
10
|
|
|
@@ -17,9 +19,12 @@ Usage:
|
|
|
17
19
|
ocx start [--port <port>] Start the proxy server (auto-syncs models to Codex)
|
|
18
20
|
ocx stop Stop the proxy AND restore native Codex (plain codex works again)
|
|
19
21
|
ocx restore Restore native Codex without stopping (alias: eject)
|
|
22
|
+
ocx uninstall Remove service/shim/config and restore native Codex
|
|
20
23
|
ocx service <sub> Run as a background service (install|start|stop|status|uninstall)
|
|
21
24
|
ocx codex-shim <sub> Auto-start proxy when \`codex\` launches (install|status|uninstall)
|
|
25
|
+
ocx ensure Ensure the proxy is running and Codex config/cache are current
|
|
22
26
|
ocx sync Fetch models from providers and inject into Codex config
|
|
27
|
+
ocx sync-cache Refresh Codex's model cache from the active catalog
|
|
23
28
|
ocx status Check proxy server status
|
|
24
29
|
ocx login <provider> OAuth login (xai) — opens browser, stores token in ~/.opencodex/auth.json
|
|
25
30
|
ocx logout <provider> Remove a stored OAuth login
|
|
@@ -55,23 +60,74 @@ async function syncModelsToCodex(port?: number) {
|
|
|
55
60
|
return result;
|
|
56
61
|
}
|
|
57
62
|
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
|
|
63
|
+
function parsePortOption(): number | undefined {
|
|
64
|
+
const portIdx = args.indexOf("--port");
|
|
65
|
+
if (portIdx === -1) return undefined;
|
|
66
|
+
const value = args[portIdx + 1];
|
|
67
|
+
const port = value ? parseInt(value, 10) : NaN;
|
|
68
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
69
|
+
console.error("Invalid port number");
|
|
62
70
|
process.exit(1);
|
|
63
71
|
}
|
|
72
|
+
return port;
|
|
73
|
+
}
|
|
64
74
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
function healthHost(hostname?: string): string {
|
|
76
|
+
return !hostname || hostname === "0.0.0.0" || hostname === "::" ? "127.0.0.1" : hostname;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function proxyHealthy(port?: number): Promise<boolean> {
|
|
80
|
+
const config = loadConfig();
|
|
81
|
+
const p = port ?? config.port ?? 10100;
|
|
82
|
+
try {
|
|
83
|
+
const res = await fetch(`http://${healthHost(config.hostname)}:${p}/healthz`, {
|
|
84
|
+
signal: AbortSignal.timeout(750),
|
|
85
|
+
});
|
|
86
|
+
return res.ok;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function waitForProxy(timeoutMs = 8_000): Promise<number | null> {
|
|
93
|
+
const deadline = Date.now() + timeoutMs;
|
|
94
|
+
while (Date.now() < deadline) {
|
|
95
|
+
const config = loadConfig();
|
|
96
|
+
const port = config.port ?? 10100;
|
|
97
|
+
if (await proxyHealthy(port)) return port;
|
|
98
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function chooseListenPort(requestedPort?: number): Promise<number> {
|
|
104
|
+
const config = loadConfig();
|
|
105
|
+
const preferred = requestedPort ?? config.port ?? 10100;
|
|
106
|
+
const selected = await findAvailablePort(preferred, config.hostname ?? "127.0.0.1");
|
|
107
|
+
if (selected !== preferred) {
|
|
108
|
+
console.log(`⚠️ Port ${preferred} is busy; starting opencodex on ${selected}.`);
|
|
109
|
+
}
|
|
110
|
+
if (config.port !== selected) {
|
|
111
|
+
config.port = selected;
|
|
112
|
+
saveConfig(config);
|
|
113
|
+
}
|
|
114
|
+
return selected;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function handleStart(options: { block?: boolean } = {}) {
|
|
118
|
+
const existingPid = readPid();
|
|
119
|
+
if (existingPid) {
|
|
120
|
+
const config = loadConfig();
|
|
121
|
+
if (await proxyHealthy(config.port)) {
|
|
122
|
+
console.error(`⚠️ Proxy already running (PID ${existingPid}). Use 'ocx stop' first.`);
|
|
71
123
|
process.exit(1);
|
|
72
124
|
}
|
|
125
|
+
removePid();
|
|
73
126
|
}
|
|
74
127
|
|
|
128
|
+
const requestedPort = parsePortOption();
|
|
129
|
+
const port = await chooseListenPort(requestedPort);
|
|
130
|
+
|
|
75
131
|
const server = startServer(port);
|
|
76
132
|
writePid(process.pid);
|
|
77
133
|
|
|
@@ -96,6 +152,39 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
96
152
|
}
|
|
97
153
|
}
|
|
98
154
|
|
|
155
|
+
async function handleEnsure() {
|
|
156
|
+
let config = loadConfig();
|
|
157
|
+
if (!codexAutoStartEnabled(config)) {
|
|
158
|
+
console.log("Codex autostart is disabled.");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (await proxyHealthy(config.port)) {
|
|
162
|
+
await syncModelsToCodex(config.port).catch(e => {
|
|
163
|
+
console.error(`⚠️ Model sync skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
164
|
+
});
|
|
165
|
+
console.log(`✅ Proxy running on port ${config.port}`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const child = spawn(process.execPath, [process.argv[1], "start"], {
|
|
170
|
+
detached: true,
|
|
171
|
+
stdio: "ignore",
|
|
172
|
+
env: { ...process.env, OCX_SERVICE: "1" },
|
|
173
|
+
});
|
|
174
|
+
child.unref();
|
|
175
|
+
|
|
176
|
+
const port = await waitForProxy();
|
|
177
|
+
if (!port) {
|
|
178
|
+
console.error("❌ Proxy did not become healthy after starting.");
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
config = loadConfig();
|
|
182
|
+
await syncModelsToCodex(config.port ?? port).catch(e => {
|
|
183
|
+
console.error(`⚠️ Model sync skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
184
|
+
});
|
|
185
|
+
console.log(`✅ Proxy running on port ${config.port ?? port}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
99
188
|
function killProxy(pid: number): void {
|
|
100
189
|
if (!isProcessAlive(pid)) return;
|
|
101
190
|
if (process.platform === "win32") {
|
|
@@ -154,6 +243,58 @@ function handleStop() {
|
|
|
154
243
|
if (stopFailed) process.exit(1);
|
|
155
244
|
}
|
|
156
245
|
|
|
246
|
+
async function handleUninstall() {
|
|
247
|
+
const failures: string[] = [];
|
|
248
|
+
|
|
249
|
+
const runStep = (label: string, step: () => void | boolean) => {
|
|
250
|
+
try {
|
|
251
|
+
const changed = step();
|
|
252
|
+
if (changed === false) console.log(`- ${label}: not installed`);
|
|
253
|
+
else console.log(`✅ ${label}`);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
failures.push(label);
|
|
256
|
+
console.error(`⚠️ ${label} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
runStep("service removed", () => {
|
|
261
|
+
stopServiceIfInstalled();
|
|
262
|
+
return uninstallServiceIfInstalled();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
runStep("proxy stopped", () => {
|
|
266
|
+
const pid = readPid();
|
|
267
|
+
if (!pid) return false;
|
|
268
|
+
killProxy(pid);
|
|
269
|
+
removePid();
|
|
270
|
+
return true;
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
runStep("native Codex restored", () => {
|
|
274
|
+
const r = restoreNativeCodex();
|
|
275
|
+
if (!r.success) throw new Error(r.message);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const { uninstallCodexShim } = await import("./codex-shim");
|
|
280
|
+
const r = uninstallCodexShim();
|
|
281
|
+
console.log(r.removed ? "✅ Codex autostart shim removed" : "- Codex autostart shim removed: not installed");
|
|
282
|
+
} catch (err) {
|
|
283
|
+
failures.push("Codex autostart shim removed");
|
|
284
|
+
console.error(`⚠️ Codex autostart shim removed failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
runStep("opencodex config removed", () => {
|
|
288
|
+
rmSync(getConfigDir(), { recursive: true, force: true });
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (failures.length > 0) {
|
|
292
|
+
console.error(`\nUninstall finished with ${failures.length} failed step(s): ${failures.join(", ")}`);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
console.log("\n✅ opencodex local state removed. Remove the package with: npm uninstall -g @bitkyc08/opencodex");
|
|
296
|
+
}
|
|
297
|
+
|
|
157
298
|
function handleStatus() {
|
|
158
299
|
const pid = readPid();
|
|
159
300
|
if (pid) {
|
|
@@ -182,9 +323,16 @@ switch (command) {
|
|
|
182
323
|
console.log("Plain `codex` now runs natively (no proxy).");
|
|
183
324
|
break;
|
|
184
325
|
}
|
|
326
|
+
case "uninstall":
|
|
327
|
+
case "remove":
|
|
328
|
+
await handleUninstall();
|
|
329
|
+
break;
|
|
185
330
|
case "status":
|
|
186
331
|
handleStatus();
|
|
187
332
|
break;
|
|
333
|
+
case "ensure":
|
|
334
|
+
await handleEnsure();
|
|
335
|
+
break;
|
|
188
336
|
case "login": {
|
|
189
337
|
const { handleLogin } = await import("./oauth/login-cli");
|
|
190
338
|
await handleLogin(args[1]);
|
|
@@ -201,6 +349,11 @@ switch (command) {
|
|
|
201
349
|
await syncModelsToCodex();
|
|
202
350
|
break;
|
|
203
351
|
}
|
|
352
|
+
case "sync-cache": {
|
|
353
|
+
const { invalidateCodexModelsCache } = await import("./codex-catalog");
|
|
354
|
+
invalidateCodexModelsCache();
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
204
357
|
case "gui": {
|
|
205
358
|
const cfg = await import("./config");
|
|
206
359
|
const config = cfg.loadConfig();
|
|
@@ -248,7 +401,7 @@ switch (command) {
|
|
|
248
401
|
}
|
|
249
402
|
case "update": {
|
|
250
403
|
const { runUpdate } = await import("./update");
|
|
251
|
-
runUpdate();
|
|
404
|
+
await runUpdate();
|
|
252
405
|
break;
|
|
253
406
|
}
|
|
254
407
|
case "help":
|