@agimon-ai/model-proxy-mcp 0.16.0 → 0.17.1
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/dist/cli.cjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const e=require(`./stdio-
|
|
2
|
+
const e=require(`./stdio-BeNlCIcP.cjs`);let t=require(`@hono/node-server`),n=require(`node:path`);n=e.d(n,1);let r=require(`node:crypto`),i=require(`node:fs`),a=require(`node:url`),o=require(`node:fs/promises`);o=e.d(o,1);let s=require(`commander`),c=require(`node:child_process`),l=require(`node:util`),u=require(`@agimon-ai/foundation-port-registry`),d=require(`@agimon-ai/foundation-process-registry`);var f=class{async check(e){try{let t=await fetch(`http://127.0.0.1:${e}/health`);return t.ok?{healthy:!0,serviceName:(await t.json()).service}:{healthy:!1,error:`Health check failed with status ${t.status}`}}catch(e){return{healthy:!1,error:e instanceof Error?e.message:String(e)}}}};const p=[`pnpm-workspace.yaml`,`nx.json`,`.git`],m=`service`,h=(0,l.promisify)(c.execFile);function g(e=process.cwd()){let t=n.default.resolve(e);for(;;){for(let e of p)if((0,i.existsSync)(n.default.join(t,e)))return t;let e=n.default.dirname(t);if(e===t)return process.cwd();t=e}}var _=class{repositoryPath=g(process.cwd());serviceName=e.l;portRegistry;constructor(e=new f){this.healthCheck=e,this.portRegistry=new u.PortRegistryService(process.env.PORT_REGISTRY_PATH)}createEmptyStatus(e={}){return{running:!1,scope:`default`,activeProfileId:null,auth:{configured:!1,authFilePath:``},profiles:[],slotModels:{},...e}}async getRegistration(){let e=await this.portRegistry.getPort({repositoryPath:this.repositoryPath,serviceName:this.serviceName,serviceType:m});return!e.success||!e.record?null:{port:e.record.port,pid:e.record.pid}}async releaseService(e){await this.portRegistry.releasePort({repositoryPath:this.repositoryPath,serviceName:this.serviceName,serviceType:m,pid:e})}async listListeningProcesses(e){try{let{stdout:t}=await h(`sh`,[`-lc`,`lsof -n -P -sTCP:LISTEN -iTCP:${e} || true`],{encoding:`utf8`});return t.split(`
|
|
3
3
|
`).slice(1).map(e=>e.trim()).filter(Boolean).map(e=>e.split(/\s+/)).map(t=>({pid:Number.parseInt(t[1]||``,10),port:e})).filter(e=>Number.isInteger(e.pid)&&e.pid>0)}catch{return[]}}async findHealthyServicePort(){let e=await this.getRegistration();if(e){let t=await this.healthCheck.check(e.port);if(t.healthy&&t.serviceName===this.serviceName)return e.port}return null}async clearPortListeners(e,t){let n=await this.listListeningProcesses(e);for(let e of n)t&&e.pid===t||await this.killProcess(e.pid)}async registerService(e,t){await this.portRegistry.reservePort({repositoryPath:this.repositoryPath,serviceName:this.serviceName,serviceType:m,preferredPort:e,portRange:{min:e,max:e},pid:t,host:`127.0.0.1`,force:!0})}async findAvailablePort(t){let n=await this.portRegistry.findAvailablePort({repositoryPath:this.repositoryPath,serviceName:this.serviceName,serviceType:m,host:`127.0.0.1`,preferredPort:t,portRange:e.u});if(!n.success||n.port===void 0)throw Error(`No available port found from ${t}`);return n.port}async fileExists(e){try{return await o.default.access(e),!0}catch{return!1}}async resolveCliPath(){let e=n.default.dirname((0,a.fileURLToPath)(require(`url`).pathToFileURL(__filename).href)),t=n.default.resolve(e,`cli.mjs`);if(await this.fileExists(t))return{cliPath:t,runtime:`node`};let r=n.default.resolve(e,`..`,`cli.mjs`);if(await this.fileExists(r))return{cliPath:r,runtime:`node`};let i=n.default.join(this.repositoryPath,`packages`,`mcp`,`model-proxy-mcp`,`dist`,`cli.mjs`);if(await this.fileExists(i))return{cliPath:i,runtime:`node`};for(let t of[n.default.resolve(e,`cli.ts`),n.default.resolve(e,`..`,`cli.ts`)])if(await this.fileExists(t))return{cliPath:t,runtime:`bun`};let o=n.default.join(this.repositoryPath,`packages`,`mcp`,`model-proxy-mcp`,`src`,`cli.ts`);if(await this.fileExists(o))return{cliPath:o,runtime:`bun`};throw Error(`Cannot find model-proxy-mcp CLI`)}async startHttpServer(e){let{cliPath:t,runtime:n}=await this.resolveCliPath(),r=(0,c.spawn)(n===`bun`?process.versions.bun?process.execPath:`bun`:`node`,n===`bun`?[`run`,t,`http-serve`,`--port`,String(e)]:[t,`http-serve`,`--port`,String(e)],{detached:!0,stdio:`ignore`,env:{...process.env,NODE_ENV:process.env.NODE_ENV||`development`}});if(r.unref(),!r.pid)throw Error(`Failed to spawn HTTP server`);return r.pid}async killProcess(e){try{process.kill(e,`SIGTERM`),await new Promise(e=>setTimeout(e,1e3));try{process.kill(e,0),process.kill(e,`SIGKILL`)}catch{return}}catch{return}}async waitForHealthy(e){let t=Date.now()+5e3,n=`Server failed health check after startup`;for(;Date.now()<t;){let t=await this.healthCheck.check(e);if(t.healthy&&t.serviceName===this.serviceName)return{healthy:!0};n=t.error||`Unexpected service on port ${e}`,await new Promise(e=>setTimeout(e,250))}return{healthy:!1,error:n}}async ensureRunning(t=e.c){try{let e=await this.findHealthyServicePort();if(e!==null){let t=(await this.listListeningProcesses(e))[0]?.pid;return await this.registerService(e,t??process.pid),this.createEmptyStatus({running:!0,port:e,pid:t})}let n=await this.getRegistration();if(n){let e=await this.healthCheck.check(n.port);if(e.healthy&&e.serviceName===this.serviceName)return this.createEmptyStatus({running:!0,port:n.port,pid:n.pid});n.pid&&await this.killProcess(n.pid),await this.clearPortListeners(n.port,n.pid),await this.releaseService(n.pid)}await this.clearPortListeners(t);let r=await this.findAvailablePort(t),i=await this.startHttpServer(r),a=await this.waitForHealthy(r);return a.healthy?(await this.registerService(r,i),this.createEmptyStatus({running:!0,port:r,pid:i})):(await this.killProcess(i),await this.clearPortListeners(r,i),await this.releaseService(i),this.createEmptyStatus({error:a.error||`Server failed health check after startup`}))}catch(e){return this.createEmptyStatus({error:e instanceof Error?e.message:String(e)})}}startHealthMonitor(e,t=1e4){let n=!1,r=0,i=2e3,a=6e4,o=async()=>{if(!n)try{if((await this.getStatus()).running)r=0,n||setTimeout(()=>void o(),t);else{r++;let t=Math.min(i*2**(r-1),a);await this.ensureRunning(e),n||setTimeout(()=>void o(),t)}}catch{r++;let e=Math.min(i*2**(r-1),a);n||setTimeout(()=>void o(),e)}};return setTimeout(()=>void o(),t),()=>{n=!0}}async stop(){let e=await this.getRegistration(),t=!1;e?.pid&&(await this.killProcess(e.pid),t=!0),e&&(await this.clearPortListeners(e.port,e.pid),await this.releaseService(e.pid));let n=await this.findHealthyServicePort();if(n!==null){let e=await this.listListeningProcesses(n);for(let n of e)await this.killProcess(n.pid),t=!0;await this.releaseService()}return t}async getStatus(){let e=await this.getRegistration();if(e&&(await this.healthCheck.check(e.port)).healthy)return this.createEmptyStatus({running:!0,port:e.port,pid:e.pid});let t=await this.findHealthyServicePort();if(t!==null){let e=(await this.listListeningProcesses(t))[0]?.pid;return e&&await this.registerService(t,e),this.createEmptyStatus({running:!0,port:t,pid:e})}return this.createEmptyStatus({error:e?`Registered server is unhealthy`:`No HTTP server registered`})}};const v=`claude`,y=`utf8`,b=`[model-proxy-mcp]`,x=`Recovery: verify HOME, proxy port, and that \`${v}\` is on PATH.`;var S=class extends Error{constructor(e,t,n){super(e,n),this.code=t,this.name=`ClaudeCommandError`}},C=class extends S{constructor(e,t){super(e,`CLAUDE_COMMAND_CONFIG_ERROR`,t),this.name=`ClaudeCommandConfigError`}},w=class extends S{constructor(e,t){super(e,`CLAUDE_COMMAND_VALIDATION_ERROR`,t),this.name=`ClaudeCommandValidationError`}},T=class extends S{constructor(e,t){super(e,`CLAUDE_COMMAND_LAUNCH_ERROR`,t),this.name=`ClaudeCommandLaunchError`}};function E(e){return e.trim().replace(/[^a-zA-Z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``)||`default`}function D(){return`scope-${(0,r.randomBytes)(3).toString(`hex`)}`}function O(e,t,n=process.env){return{...n,ANTHROPIC_BASE_URL:`http://127.0.0.1:${e}/scopes/${t}`,ANTHROPIC_DEFAULT_OPUS_MODEL:n.ANTHROPIC_DEFAULT_OPUS_MODEL||`ccproxy-opus`,ANTHROPIC_DEFAULT_SONNET_MODEL:n.ANTHROPIC_DEFAULT_SONNET_MODEL||`ccproxy-sonnet`,ANTHROPIC_DEFAULT_HAIKU_MODEL:n.ANTHROPIC_DEFAULT_HAIKU_MODEL||`ccproxy-haiku`,CLAUDE_CODE_SUBAGENT_MODEL:n.CLAUDE_CODE_SUBAGENT_MODEL||`ccproxy-subagent`,MODEL_PROXY_MCP_SCOPE:n.MODEL_PROXY_MCP_SCOPE||t,MODEL_PROXY_MCP_SLOT:n.MODEL_PROXY_MCP_SLOT||`default`}}async function k(e,t=process.cwd()){let r=e?n.default.resolve(t,e):n.default.join(t,`model-proxy-mcp.yaml`);try{if(!(await o.default.stat(r)).isFile()){if(e)throw new C(`Scope seed config path is not a file: ${r}`);return}return r}catch(t){if(t.code===`ENOENT`){if(e)throw new C(`Scope seed config file not found: ${r}`,{cause:t});return}throw t instanceof C?t:new C(`Failed to read scope seed config file: ${r}`,{cause:t})}}function A(){let e=process.env.HOME||process.env.USERPROFILE;if(!e)throw new C(`HOME or USERPROFILE is not set`);return n.default.join(e,`.claude-sessions`)}async function j(){let e=A();return await o.default.mkdir(e,{recursive:!0}),e}async function M(e){try{return(await o.default.readFile(e,y)).trim()||null}catch(t){if(t.code===`ENOENT`)return null;throw new C(`Failed to read Claude session file: ${e}`,{cause:t})}}async function N(e,t){let i=await j(),a=n.default.join(i,e);if(t)try{await o.default.rm(a,{force:!0})}catch(e){throw new C(`Failed to clear Claude session file: ${a}`,{cause:e})}let s=await M(a);if(s)return{sessionId:s,resume:!0};let c=(0,r.randomUUID)().toLowerCase();try{await o.default.writeFile(a,`${c}\n`,y)}catch(e){throw new C(`Failed to write Claude session file: ${a}`,{cause:e})}return{sessionId:c,resume:!1}}async function P(t,n,r,i,a){let o=await k(a);await new e.a().ensureConfig(t,o);let s=await new _().ensureRunning(n);if(!s.running||!s.port)throw new T(s.error||`Failed to start model proxy HTTP server`);let{sessionId:l,resume:u}=await N(t,r),d=O(s.port,t),f=[`--dangerously-skip-permissions`,...u?[`--resume`,l]:[`--session-id`,l],...i];return console.log(`${b} Scope: ${t}`),console.log(`${b} Proxy: ${d.ANTHROPIC_BASE_URL}`),console.log(`${b} Session: ${l}${u?` (resume)`:` (new)`}`),o&&console.log(`${b} Scope config seed: ${o}`),new Promise((e,t)=>{let n=(0,c.spawn)(v,f,{stdio:`inherit`,env:d});n.on(`error`,e=>{t(new T(`Failed to launch ${v}`,{cause:e}))}),n.on(`exit`,(n,r)=>{if(r){t(new T(`${v} exited with signal ${r}`));return}e(n??0)})})}const F=new s.Command(`claude`).description(`Launch Claude Code through the model proxy`).option(`-s, --scope <scope>`,`Proxy scope to use`).option(`-p, --port <port>`,`Preferred HTTP port for the proxy`,String(e.c)).option(`-c, --config-file <path>`,`Seed a new scope from the specified scope config file`).option(`--config <path>`,`Alias for --config-file`).option(`--clear-session`,`Start with a fresh Claude session for the selected scope`,!1).allowUnknownOption(!0).allowExcessArguments(!0).argument(`[claudeArgs...]`).action(async(e,t)=>{try{let n=E(t.scope||D()),r=Number.parseInt(t.port,10);if(!Number.isInteger(r)||r<=0)throw new w(`Invalid port: ${t.port}`);let i=await P(n,r,t.clearSession,e,t.configFile??t.config);process.exit(i)}catch(e){let t=e instanceof S?e:new T(`Failed to launch Claude`,{cause:e});console.error(`${b} ${t.code}: ${t.message}`),console.error(`${b} ${x}`),t.cause&&console.error(`${b} Cause:`,t.cause),process.exit(1)}}),I=`127.0.0.1`,L=[`pnpm-workspace.yaml`,`nx.json`,`.git`],R=`service`;function z(e=process.cwd()){let t=n.default.resolve(e);for(;;){for(let e of L)if((0,i.existsSync)(n.default.join(t,e)))return t;let e=n.default.dirname(t);if(e===t)return process.cwd();t=e}}const B=new s.Command(`http-serve`).description(`Start the Claude-compatible HTTP model proxy server`).option(`-p, --port <port>`,`Port to listen on`,String(e.c)).action(async n=>{let r;try{let i=Number.parseInt(n.port,10);if(!Number.isInteger(i)||i<=0)throw Error(`Invalid port: ${n.port}`);let a=new u.PortRegistryService(process.env.PORT_REGISTRY_PATH),o=z(),s=await a.reservePort({repositoryPath:o,serviceName:e.l,serviceType:R,preferredPort:i,portRange:e.u,pid:process.pid,host:I,force:!0});if(!s.success||!s.record)throw Error(s.error||`Failed to reserve port ${i}`);let c=s.record.port;r=await(0,d.createProcessLease)({repositoryPath:o,serviceName:e.l,serviceType:R,pid:process.pid,port:c,host:I,command:process.argv[1],args:process.argv.slice(2)});let l=new e.i;await l.ensureConfig();let f=(0,t.serve)({fetch:e.r(l).fetch,port:c,hostname:I});console.log(`model-proxy-mcp listening on http://${I}:${c}`),console.log(`Claude base URL: http://${I}:${c}`);let p=async t=>{console.log(`\n${t} received. Shutting down...`),f.close(),await r?.release(),await a.releasePort({repositoryPath:o,serviceName:e.l,serviceType:R,pid:process.pid}),process.exit(0)};process.once(`SIGINT`,()=>p(`SIGINT`)),process.once(`SIGTERM`,()=>p(`SIGTERM`))}catch(e){await r?.release(),console.error(`Failed to start HTTP server:`,e),process.exit(1)}}),V=new s.Command(`mcp-serve`).description(`Start MCP server with stdio transport`).option(`--cleanup`,`Stop HTTP server on shutdown`,!1).option(`-p, --port <port>`,`Port for HTTP server`,String(e.c)).action(async t=>{try{let n=Number.parseInt(t.port,10);if(!Number.isInteger(n)||n<=0)throw Error(`Invalid port: ${t.port}`);let r=new e.i;await r.ensureConfig(process.env.MODEL_PROXY_MCP_SCOPE||`default`);let i=new _,a=await i.ensureRunning(n);a.running||console.error(`Warning: HTTP server failed to start: ${a.error}`);let o;a.running&&(o=i.startHealthMonitor(n));let s=new e.t(e.n(r)),c=async e=>{console.error(`\nReceived ${e}, shutting down gracefully...`),o?.(),await s.stop(),t.cleanup&&a.running&&await i.stop(),process.exit(0)};process.once(`SIGINT`,()=>void c(`SIGINT`).catch(e=>{console.error(`Failed to shut down after SIGINT:`,e),process.exit(1)})),process.once(`SIGTERM`,()=>void c(`SIGTERM`).catch(e=>{console.error(`Failed to shut down after SIGTERM:`,e),process.exit(1)})),await s.start()}catch(e){console.error(`Failed to start MCP server:`,e),process.exit(1)}}),H=new s.Command(`start`).description(`Start HTTP and/or MCP server for the model proxy`).option(`--mcp-only`,`Start only the MCP server`,!1).option(`--http-only`,`Start only the HTTP server`,!1).option(`-p, --port <port>`,`Port for HTTP server`,String(e.c)).action(async t=>{try{let n=new _,r=Number.parseInt(t.port,10),i=!t.mcpOnly,a=!t.httpOnly;if(i){let e=await n.ensureRunning(r);e.running||(console.error(`HTTP server failed to start: ${e.error}`),process.exit(1)),console.log(`HTTP server running on http://127.0.0.1:${e.port}`)}if(a){let t=new e.t(e.n());await t.start();let n=async e=>{console.error(`\n${e} received. Shutting down...`),await t.stop(),process.exit(0)};process.on(`SIGINT`,()=>n(`SIGINT`)),process.on(`SIGTERM`,()=>n(`SIGTERM`))}else process.exit(0)}catch(e){console.error(`Failed to start services:`,e),process.exit(1)}}),U=`status`,W=`Failed to read status`;var G=class extends Error{code=`STATUS_CHECK_FAILED`;constructor(e){super(W,{cause:e instanceof Error?e:Error(String(e))}),this.name=`StatusCommandError`}};const K=new s.Command(U).description(`Show proxy server and profile status`).option(`-s, --scope <scope>`,`Configuration scope`,`default`).action(async t=>{try{let n=await new _().getStatus(),r=await new e.i().getStatus(t.scope,n.port,n.pid);r.running=n.running,r.error=n.error,e.s.info(JSON.stringify(r,null,2)),process.exit(0)}catch(n){let r=new G(n);e.s.error(W,{command:U,code:r.code,scope:t.scope,cause:r.cause}),process.exit(1)}}),q=new s.Command(`stop`).description(`Stop the background HTTP server`).action(async()=>{try{let e=await new _().stop();console.log(e?`HTTP server stopped`:`No HTTP server running`),process.exit(0)}catch(e){console.error(`Failed to stop HTTP server:`,e),process.exit(1)}}),J=(0,n.dirname)((0,a.fileURLToPath)(require(`url`).pathToFileURL(__filename).href)),Y=JSON.parse((0,i.readFileSync)((0,n.join)(J,`../package.json`),`utf-8`));async function X(){let e=new s.Command;e.name(`model-proxy-mcp`).description(`Claude-compatible model proxy MCP`).version(Y.version),e.addCommand(H),e.addCommand(q),e.addCommand(K),e.addCommand(F),e.addCommand(B),e.addCommand(V),await e.parseAsync(process.argv)}X();
|
package/dist/cli.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as e,c as t,i as n,l as r,n as i,r as a,s as o,t as s,u as c}from"./stdio-
|
|
2
|
+
import{a as e,c as t,i as n,l as r,n as i,r as a,s as o,t as s,u as c}from"./stdio-BledsuMz.mjs";import{serve as l}from"@hono/node-server";import u,{dirname as d,join as f}from"node:path";import{randomBytes as p,randomUUID as m}from"node:crypto";import{existsSync as h,readFileSync as g}from"node:fs";import{fileURLToPath as _}from"node:url";import v from"node:fs/promises";import{Command as y}from"commander";import{execFile as ee,spawn as b}from"node:child_process";import{promisify as x}from"node:util";import{PortRegistryService as S}from"@agimon-ai/foundation-port-registry";import{createProcessLease as C}from"@agimon-ai/foundation-process-registry";var w=class{async check(e){try{let t=await fetch(`http://127.0.0.1:${e}/health`);return t.ok?{healthy:!0,serviceName:(await t.json()).service}:{healthy:!1,error:`Health check failed with status ${t.status}`}}catch(e){return{healthy:!1,error:e instanceof Error?e.message:String(e)}}}};const T=[`pnpm-workspace.yaml`,`nx.json`,`.git`],E=`service`,D=x(ee);function O(e=process.cwd()){let t=u.resolve(e);for(;;){for(let e of T)if(h(u.join(t,e)))return t;let e=u.dirname(t);if(e===t)return process.cwd();t=e}}var k=class{repositoryPath=O(process.cwd());serviceName=r;portRegistry;constructor(e=new w){this.healthCheck=e,this.portRegistry=new S(process.env.PORT_REGISTRY_PATH)}createEmptyStatus(e={}){return{running:!1,scope:`default`,activeProfileId:null,auth:{configured:!1,authFilePath:``},profiles:[],slotModels:{},...e}}async getRegistration(){let e=await this.portRegistry.getPort({repositoryPath:this.repositoryPath,serviceName:this.serviceName,serviceType:E});return!e.success||!e.record?null:{port:e.record.port,pid:e.record.pid}}async releaseService(e){await this.portRegistry.releasePort({repositoryPath:this.repositoryPath,serviceName:this.serviceName,serviceType:E,pid:e})}async listListeningProcesses(e){try{let{stdout:t}=await D(`sh`,[`-lc`,`lsof -n -P -sTCP:LISTEN -iTCP:${e} || true`],{encoding:`utf8`});return t.split(`
|
|
3
3
|
`).slice(1).map(e=>e.trim()).filter(Boolean).map(e=>e.split(/\s+/)).map(t=>({pid:Number.parseInt(t[1]||``,10),port:e})).filter(e=>Number.isInteger(e.pid)&&e.pid>0)}catch{return[]}}async findHealthyServicePort(){let e=await this.getRegistration();if(e){let t=await this.healthCheck.check(e.port);if(t.healthy&&t.serviceName===this.serviceName)return e.port}return null}async clearPortListeners(e,t){let n=await this.listListeningProcesses(e);for(let e of n)t&&e.pid===t||await this.killProcess(e.pid)}async registerService(e,t){await this.portRegistry.reservePort({repositoryPath:this.repositoryPath,serviceName:this.serviceName,serviceType:E,preferredPort:e,portRange:{min:e,max:e},pid:t,host:`127.0.0.1`,force:!0})}async findAvailablePort(e){let t=await this.portRegistry.findAvailablePort({repositoryPath:this.repositoryPath,serviceName:this.serviceName,serviceType:E,host:`127.0.0.1`,preferredPort:e,portRange:c});if(!t.success||t.port===void 0)throw Error(`No available port found from ${e}`);return t.port}async fileExists(e){try{return await v.access(e),!0}catch{return!1}}async resolveCliPath(){let e=u.dirname(_(import.meta.url)),t=u.resolve(e,`cli.mjs`);if(await this.fileExists(t))return{cliPath:t,runtime:`node`};let n=u.resolve(e,`..`,`cli.mjs`);if(await this.fileExists(n))return{cliPath:n,runtime:`node`};let r=u.join(this.repositoryPath,`packages`,`mcp`,`model-proxy-mcp`,`dist`,`cli.mjs`);if(await this.fileExists(r))return{cliPath:r,runtime:`node`};for(let t of[u.resolve(e,`cli.ts`),u.resolve(e,`..`,`cli.ts`)])if(await this.fileExists(t))return{cliPath:t,runtime:`bun`};let i=u.join(this.repositoryPath,`packages`,`mcp`,`model-proxy-mcp`,`src`,`cli.ts`);if(await this.fileExists(i))return{cliPath:i,runtime:`bun`};throw Error(`Cannot find model-proxy-mcp CLI`)}async startHttpServer(e){let{cliPath:t,runtime:n}=await this.resolveCliPath(),r=b(n===`bun`?process.versions.bun?process.execPath:`bun`:`node`,n===`bun`?[`run`,t,`http-serve`,`--port`,String(e)]:[t,`http-serve`,`--port`,String(e)],{detached:!0,stdio:`ignore`,env:{...process.env,NODE_ENV:process.env.NODE_ENV||`development`}});if(r.unref(),!r.pid)throw Error(`Failed to spawn HTTP server`);return r.pid}async killProcess(e){try{process.kill(e,`SIGTERM`),await new Promise(e=>setTimeout(e,1e3));try{process.kill(e,0),process.kill(e,`SIGKILL`)}catch{return}}catch{return}}async waitForHealthy(e){let t=Date.now()+5e3,n=`Server failed health check after startup`;for(;Date.now()<t;){let t=await this.healthCheck.check(e);if(t.healthy&&t.serviceName===this.serviceName)return{healthy:!0};n=t.error||`Unexpected service on port ${e}`,await new Promise(e=>setTimeout(e,250))}return{healthy:!1,error:n}}async ensureRunning(e=t){try{let t=await this.findHealthyServicePort();if(t!==null){let e=(await this.listListeningProcesses(t))[0]?.pid;return await this.registerService(t,e??process.pid),this.createEmptyStatus({running:!0,port:t,pid:e})}let n=await this.getRegistration();if(n){let e=await this.healthCheck.check(n.port);if(e.healthy&&e.serviceName===this.serviceName)return this.createEmptyStatus({running:!0,port:n.port,pid:n.pid});n.pid&&await this.killProcess(n.pid),await this.clearPortListeners(n.port,n.pid),await this.releaseService(n.pid)}await this.clearPortListeners(e);let r=await this.findAvailablePort(e),i=await this.startHttpServer(r),a=await this.waitForHealthy(r);return a.healthy?(await this.registerService(r,i),this.createEmptyStatus({running:!0,port:r,pid:i})):(await this.killProcess(i),await this.clearPortListeners(r,i),await this.releaseService(i),this.createEmptyStatus({error:a.error||`Server failed health check after startup`}))}catch(e){return this.createEmptyStatus({error:e instanceof Error?e.message:String(e)})}}startHealthMonitor(e,t=1e4){let n=!1,r=0,i=2e3,a=6e4,o=async()=>{if(!n)try{if((await this.getStatus()).running)r=0,n||setTimeout(()=>void o(),t);else{r++;let t=Math.min(i*2**(r-1),a);await this.ensureRunning(e),n||setTimeout(()=>void o(),t)}}catch{r++;let e=Math.min(i*2**(r-1),a);n||setTimeout(()=>void o(),e)}};return setTimeout(()=>void o(),t),()=>{n=!0}}async stop(){let e=await this.getRegistration(),t=!1;e?.pid&&(await this.killProcess(e.pid),t=!0),e&&(await this.clearPortListeners(e.port,e.pid),await this.releaseService(e.pid));let n=await this.findHealthyServicePort();if(n!==null){let e=await this.listListeningProcesses(n);for(let n of e)await this.killProcess(n.pid),t=!0;await this.releaseService()}return t}async getStatus(){let e=await this.getRegistration();if(e&&(await this.healthCheck.check(e.port)).healthy)return this.createEmptyStatus({running:!0,port:e.port,pid:e.pid});let t=await this.findHealthyServicePort();if(t!==null){let e=(await this.listListeningProcesses(t))[0]?.pid;return e&&await this.registerService(t,e),this.createEmptyStatus({running:!0,port:t,pid:e})}return this.createEmptyStatus({error:e?`Registered server is unhealthy`:`No HTTP server registered`})}};const A=`claude`,j=`utf8`,M=`[model-proxy-mcp]`,te=`Recovery: verify HOME, proxy port, and that \`${A}\` is on PATH.`;var N=class extends Error{constructor(e,t,n){super(e,n),this.code=t,this.name=`ClaudeCommandError`}},P=class extends N{constructor(e,t){super(e,`CLAUDE_COMMAND_CONFIG_ERROR`,t),this.name=`ClaudeCommandConfigError`}},F=class extends N{constructor(e,t){super(e,`CLAUDE_COMMAND_VALIDATION_ERROR`,t),this.name=`ClaudeCommandValidationError`}},I=class extends N{constructor(e,t){super(e,`CLAUDE_COMMAND_LAUNCH_ERROR`,t),this.name=`ClaudeCommandLaunchError`}};function L(e){return e.trim().replace(/[^a-zA-Z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``)||`default`}function R(){return`scope-${p(3).toString(`hex`)}`}function z(e,t,n=process.env){return{...n,ANTHROPIC_BASE_URL:`http://127.0.0.1:${e}/scopes/${t}`,ANTHROPIC_DEFAULT_OPUS_MODEL:n.ANTHROPIC_DEFAULT_OPUS_MODEL||`ccproxy-opus`,ANTHROPIC_DEFAULT_SONNET_MODEL:n.ANTHROPIC_DEFAULT_SONNET_MODEL||`ccproxy-sonnet`,ANTHROPIC_DEFAULT_HAIKU_MODEL:n.ANTHROPIC_DEFAULT_HAIKU_MODEL||`ccproxy-haiku`,CLAUDE_CODE_SUBAGENT_MODEL:n.CLAUDE_CODE_SUBAGENT_MODEL||`ccproxy-subagent`,MODEL_PROXY_MCP_SCOPE:n.MODEL_PROXY_MCP_SCOPE||t,MODEL_PROXY_MCP_SLOT:n.MODEL_PROXY_MCP_SLOT||`default`}}async function B(e,t=process.cwd()){let n=e?u.resolve(t,e):u.join(t,`model-proxy-mcp.yaml`);try{if(!(await v.stat(n)).isFile()){if(e)throw new P(`Scope seed config path is not a file: ${n}`);return}return n}catch(t){if(t.code===`ENOENT`){if(e)throw new P(`Scope seed config file not found: ${n}`,{cause:t});return}throw t instanceof P?t:new P(`Failed to read scope seed config file: ${n}`,{cause:t})}}function V(){let e=process.env.HOME||process.env.USERPROFILE;if(!e)throw new P(`HOME or USERPROFILE is not set`);return u.join(e,`.claude-sessions`)}async function H(){let e=V();return await v.mkdir(e,{recursive:!0}),e}async function U(e){try{return(await v.readFile(e,j)).trim()||null}catch(t){if(t.code===`ENOENT`)return null;throw new P(`Failed to read Claude session file: ${e}`,{cause:t})}}async function W(e,t){let n=await H(),r=u.join(n,e);if(t)try{await v.rm(r,{force:!0})}catch(e){throw new P(`Failed to clear Claude session file: ${r}`,{cause:e})}let i=await U(r);if(i)return{sessionId:i,resume:!0};let a=m().toLowerCase();try{await v.writeFile(r,`${a}\n`,j)}catch(e){throw new P(`Failed to write Claude session file: ${r}`,{cause:e})}return{sessionId:a,resume:!1}}async function G(t,n,r,i,a){let o=await B(a);await new e().ensureConfig(t,o);let s=await new k().ensureRunning(n);if(!s.running||!s.port)throw new I(s.error||`Failed to start model proxy HTTP server`);let{sessionId:c,resume:l}=await W(t,r),u=z(s.port,t),d=[`--dangerously-skip-permissions`,...l?[`--resume`,c]:[`--session-id`,c],...i];return console.log(`${M} Scope: ${t}`),console.log(`${M} Proxy: ${u.ANTHROPIC_BASE_URL}`),console.log(`${M} Session: ${c}${l?` (resume)`:` (new)`}`),o&&console.log(`${M} Scope config seed: ${o}`),new Promise((e,t)=>{let n=b(A,d,{stdio:`inherit`,env:u});n.on(`error`,e=>{t(new I(`Failed to launch ${A}`,{cause:e}))}),n.on(`exit`,(n,r)=>{if(r){t(new I(`${A} exited with signal ${r}`));return}e(n??0)})})}const K=new y(`claude`).description(`Launch Claude Code through the model proxy`).option(`-s, --scope <scope>`,`Proxy scope to use`).option(`-p, --port <port>`,`Preferred HTTP port for the proxy`,String(t)).option(`-c, --config-file <path>`,`Seed a new scope from the specified scope config file`).option(`--config <path>`,`Alias for --config-file`).option(`--clear-session`,`Start with a fresh Claude session for the selected scope`,!1).allowUnknownOption(!0).allowExcessArguments(!0).argument(`[claudeArgs...]`).action(async(e,t)=>{try{let n=L(t.scope||R()),r=Number.parseInt(t.port,10);if(!Number.isInteger(r)||r<=0)throw new F(`Invalid port: ${t.port}`);let i=await G(n,r,t.clearSession,e,t.configFile??t.config);process.exit(i)}catch(e){let t=e instanceof N?e:new I(`Failed to launch Claude`,{cause:e});console.error(`${M} ${t.code}: ${t.message}`),console.error(`${M} ${te}`),t.cause&&console.error(`${M} Cause:`,t.cause),process.exit(1)}}),q=`127.0.0.1`,J=[`pnpm-workspace.yaml`,`nx.json`,`.git`],Y=`service`;function X(e=process.cwd()){let t=u.resolve(e);for(;;){for(let e of J)if(h(u.join(t,e)))return t;let e=u.dirname(t);if(e===t)return process.cwd();t=e}}const Z=new y(`http-serve`).description(`Start the Claude-compatible HTTP model proxy server`).option(`-p, --port <port>`,`Port to listen on`,String(t)).action(async e=>{let t;try{let i=Number.parseInt(e.port,10);if(!Number.isInteger(i)||i<=0)throw Error(`Invalid port: ${e.port}`);let o=new S(process.env.PORT_REGISTRY_PATH),s=X(),u=await o.reservePort({repositoryPath:s,serviceName:r,serviceType:Y,preferredPort:i,portRange:c,pid:process.pid,host:q,force:!0});if(!u.success||!u.record)throw Error(u.error||`Failed to reserve port ${i}`);let d=u.record.port;t=await C({repositoryPath:s,serviceName:r,serviceType:Y,pid:process.pid,port:d,host:q,command:process.argv[1],args:process.argv.slice(2)});let f=new n;await f.ensureConfig();let p=l({fetch:a(f).fetch,port:d,hostname:q});console.log(`model-proxy-mcp listening on http://${q}:${d}`),console.log(`Claude base URL: http://${q}:${d}`);let m=async e=>{console.log(`\n${e} received. Shutting down...`),p.close(),await t?.release(),await o.releasePort({repositoryPath:s,serviceName:r,serviceType:Y,pid:process.pid}),process.exit(0)};process.once(`SIGINT`,()=>m(`SIGINT`)),process.once(`SIGTERM`,()=>m(`SIGTERM`))}catch(e){await t?.release(),console.error(`Failed to start HTTP server:`,e),process.exit(1)}}),ne=new y(`mcp-serve`).description(`Start MCP server with stdio transport`).option(`--cleanup`,`Stop HTTP server on shutdown`,!1).option(`-p, --port <port>`,`Port for HTTP server`,String(t)).action(async e=>{try{let t=Number.parseInt(e.port,10);if(!Number.isInteger(t)||t<=0)throw Error(`Invalid port: ${e.port}`);let r=new n;await r.ensureConfig(process.env.MODEL_PROXY_MCP_SCOPE||`default`);let a=new k,o=await a.ensureRunning(t);o.running||console.error(`Warning: HTTP server failed to start: ${o.error}`);let c;o.running&&(c=a.startHealthMonitor(t));let l=new s(i(r)),u=async t=>{console.error(`\nReceived ${t}, shutting down gracefully...`),c?.(),await l.stop(),e.cleanup&&o.running&&await a.stop(),process.exit(0)};process.once(`SIGINT`,()=>void u(`SIGINT`).catch(e=>{console.error(`Failed to shut down after SIGINT:`,e),process.exit(1)})),process.once(`SIGTERM`,()=>void u(`SIGTERM`).catch(e=>{console.error(`Failed to shut down after SIGTERM:`,e),process.exit(1)})),await l.start()}catch(e){console.error(`Failed to start MCP server:`,e),process.exit(1)}}),re=new y(`start`).description(`Start HTTP and/or MCP server for the model proxy`).option(`--mcp-only`,`Start only the MCP server`,!1).option(`--http-only`,`Start only the HTTP server`,!1).option(`-p, --port <port>`,`Port for HTTP server`,String(t)).action(async e=>{try{let t=new k,n=Number.parseInt(e.port,10),r=!e.mcpOnly,a=!e.httpOnly;if(r){let e=await t.ensureRunning(n);e.running||(console.error(`HTTP server failed to start: ${e.error}`),process.exit(1)),console.log(`HTTP server running on http://127.0.0.1:${e.port}`)}if(a){let e=new s(i());await e.start();let t=async t=>{console.error(`\n${t} received. Shutting down...`),await e.stop(),process.exit(0)};process.on(`SIGINT`,()=>t(`SIGINT`)),process.on(`SIGTERM`,()=>t(`SIGTERM`))}else process.exit(0)}catch(e){console.error(`Failed to start services:`,e),process.exit(1)}}),Q=`status`,$=`Failed to read status`;var ie=class extends Error{code=`STATUS_CHECK_FAILED`;constructor(e){super($,{cause:e instanceof Error?e:Error(String(e))}),this.name=`StatusCommandError`}};const ae=new y(Q).description(`Show proxy server and profile status`).option(`-s, --scope <scope>`,`Configuration scope`,`default`).action(async e=>{try{let t=await new k().getStatus(),r=await new n().getStatus(e.scope,t.port,t.pid);r.running=t.running,r.error=t.error,o.info(JSON.stringify(r,null,2)),process.exit(0)}catch(t){let n=new ie(t);o.error($,{command:Q,code:n.code,scope:e.scope,cause:n.cause}),process.exit(1)}}),oe=new y(`stop`).description(`Stop the background HTTP server`).action(async()=>{try{let e=await new k().stop();console.log(e?`HTTP server stopped`:`No HTTP server running`),process.exit(0)}catch(e){console.error(`Failed to stop HTTP server:`,e),process.exit(1)}}),se=d(_(import.meta.url)),ce=JSON.parse(g(f(se,`../package.json`),`utf-8`));async function le(){let e=new y;e.name(`model-proxy-mcp`).description(`Claude-compatible model proxy MCP`).version(ce.version),e.addCommand(re),e.addCommand(oe),e.addCommand(ae),e.addCommand(K),e.addCommand(Z),e.addCommand(ne),await e.parseAsync(process.argv)}le();export{};
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./stdio-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./stdio-BeNlCIcP.cjs`);exports.ConversationHistoryService=e.o,exports.GatewayService=e.i,exports.ProfileStore=e.a,exports.StdioTransportHandler=e.t,exports.createHttpServer=e.r,exports.createServer=e.n;
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,i as t,n,o as r,r as i,t as a}from"./stdio-
|
|
1
|
+
import{a as e,i as t,n,o as r,r as i,t as a}from"./stdio-BledsuMz.mjs";export{r as ConversationHistoryService,t as GatewayService,e as ProfileStore,a as StdioTransportHandler,i as createHttpServer,n as createServer};
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));require(`@hono/node-server`);let c=require(`hono`),l=require(`zod`),u=require(`node:os`);u=s(u,1);let d=require(`node:path`);d=s(d,1);let f=require(`node:crypto`),p=require(`ulidx`),m=require(`node:fs`);m=s(m,1);let h=require(`node:url`),g=require(`node:fs/promises`);
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));require(`@hono/node-server`);let c=require(`hono`),l=require(`zod`),u=require(`node:os`);u=s(u,1);let d=require(`node:path`);d=s(d,1);let f=require(`node:crypto`),p=require(`ulidx`),m=require(`node:fs`);m=s(m,1);let h=require(`node:url`),g=require(`@agimon-ai/log-sink-mcp`),_=require(`node:fs/promises`);_=s(_,1);let ee=require(`better-sqlite3`);ee=s(ee,1);let v=require(`yaml`),te=require(`@modelcontextprotocol/sdk/server/index.js`),ne=require(`@modelcontextprotocol/sdk/types.js`),re=require(`@modelcontextprotocol/sdk/server/stdio.js`);const y=d.default.join(u.default.homedir(),`.model-proxy`),ie=d.default.join(y,`model-provider.yaml`),ae=d.default.join(y,`model-list.yaml`),oe=d.default.join(y,`scopes`),se=d.default.join(y,`history.sqlite`),ce=d.default.join(u.default.homedir(),`.codex`,`auth.json`),b=`model-proxy-mcp-http`,le={min:43e3,max:44e3},ue={"chatgpt-codex":{type:`chatgpt-codex`,endpoint:`https://chatgpt.com/backend-api/codex/responses`,authTokenEnvVar:null,apiTimeoutMs:null},"zai-anthropic-compat":{type:`anthropic-compatible`,endpoint:`https://api.z.ai/api/anthropic/v1/messages`,authTokenEnvVar:`ZAI_ANTHROPIC_AUTH_TOKEN`,apiTimeoutMs:3e6},"google-gemini-direct":{type:`gemini-direct`,endpoint:`https://generativelanguage.googleapis.com`,authTokenEnvVar:`GEMINI_API_KEY`,apiTimeoutMs:3e5,authMode:`auto`,apiKeyEnvVar:`GEMINI_API_KEY`,project:null,location:`global`,apiVersion:`v1beta`}},de=[{id:`chatgpt-codex-gpt-5.3-codex`,label:`ChatGPT Codex GPT-5.3 Codex`,provider:`chatgpt-codex`,model:`gpt-5.3-codex`,reasoningEffort:`medium`,enabled:!0},{id:`chatgpt-codex-gpt-5.3-codex-low`,label:`ChatGPT Codex GPT-5.3 Codex Low`,provider:`chatgpt-codex`,model:`gpt-5.3-codex`,reasoningEffort:`low`,enabled:!0},{id:`chatgpt-codex-gpt-5.4`,label:`ChatGPT Codex GPT-5.4`,provider:`chatgpt-codex`,model:`gpt-5.4`,reasoningEffort:`medium`,enabled:!0},{id:`chatgpt-codex-gpt-5.2`,label:`ChatGPT Codex GPT-5.2`,provider:`chatgpt-codex`,model:`gpt-5.2`,reasoningEffort:`medium`,enabled:!0},{id:`zai-anthropic-compat-glm-4.7`,label:`Z.ai GLM-4.7`,provider:`zai-anthropic-compat`,model:`GLM-4.7`,reasoningEffort:`high`,enabled:!0},{id:`zai-anthropic-compat-glm-5`,label:`Z.ai GLM-5`,provider:`zai-anthropic-compat`,model:`glm-5`,reasoningEffort:`high`,enabled:!0},{id:`zai-anthropic-compat-glm-4.5-air`,label:`Z.ai GLM-4.5-Air`,provider:`zai-anthropic-compat`,model:`GLM-4.5-Air`,reasoningEffort:`medium`,enabled:!0},{id:`google-gemini-direct-gemini-2.5-flash`,label:`Google Gemini 2.5 Flash`,provider:`google-gemini-direct`,model:`gemini-2.5-flash`,reasoningEffort:`medium`,enabled:!0},{id:`google-gemini-direct-gemini-3-flash-preview`,label:`Google Gemini 3 Flash Preview`,provider:`google-gemini-direct`,model:`gemini-3-flash-preview`,reasoningEffort:`medium`,enabled:!0},{id:`google-gemini-direct-gemini-3.1-pro-preview`,label:`Google Gemini 3.1 Pro Preview`,provider:`google-gemini-direct`,model:`gemini-3.1-pro-preview`,reasoningEffort:`high`,enabled:!0}],fe={models:{default:{main:{provider:`chatgpt-codex`,model:`gpt-5.4`,reasoningEffort:`medium`},fallbacks:[{provider:`chatgpt-codex`,model:`gpt-5.4`,reasoningEffort:`low`}]},sonnet:{main:{provider:`chatgpt-codex`,model:`gpt-5.3-codex`,reasoningEffort:`medium`},fallbacks:[{provider:`chatgpt-codex`,model:`gpt-5.4`,reasoningEffort:`medium`}]},opus:{main:{provider:`chatgpt-codex`,model:`gpt-5.4`,reasoningEffort:`medium`},fallbacks:[{provider:`chatgpt-codex`,model:`gpt-5.4`,reasoningEffort:`low`}]},haiku:{main:{provider:`chatgpt-codex`,model:`gpt-5.3-codex`,reasoningEffort:`low`},fallbacks:[{provider:`chatgpt-codex`,model:`gpt-5.4`,reasoningEffort:`medium`}]},subagent:{main:{provider:`chatgpt-codex`,model:`gpt-5.3-codex`,reasoningEffort:`low`},fallbacks:[{provider:`chatgpt-codex`,model:`gpt-5.4`,reasoningEffort:`medium`}]}}},pe=(0,h.fileURLToPath)(require(`url`).pathToFileURL(__filename).href),me=d.default.dirname(pe),he=d.default.join(me,`codex.md`);let x=null;const ge=e=>typeof e==`object`&&!!e&&!Array.isArray(e);var _e=class{config;codexInstructions;constructor(e){this.config=e,this.codexInstructions=this.loadCodexInstructions()}loadCodexInstructions(){if(x!==null)return x;try{return x=m.default.readFileSync(he,`utf-8`),x}catch{return console.warn(`Warning: Could not load codex.md from ${he}`),x=``,``}}async transform(e,t){try{let e=JSON.parse(t);this.config.logger?.debug(`[ClaudeToOpenAI] ===== ORIGINAL CLAUDE REQUEST =====`),this.config.logger?.debug(`[ClaudeToOpenAI] Original body`,{body:JSON.stringify(e,null,2)});let n=this.config.sessionId||(0,f.randomUUID)(),r=n,i=this.config.sessionReasoningEffort,a=e.model&&e.model.toLowerCase().includes(`haiku`),o=i||(a?`minimal`:`medium`);this.config.logger?.debug(`[ClaudeToOpenAI] Model detection and reasoning effort`,{originalModel:e.model,isHaikuModel:a,sessionReasoningEffort:i||`none`,finalReasoningEffort:o,source:i?`session override`:`model-based`});let s={model:this.config.toModel||`gpt-5`,stream:!0,store:!1,tool_choice:`auto`,parallel_tool_calls:!1,prompt_cache_key:n};this.config.thinkingDisabled||(s.reasoning={effort:o,summary:`auto`},s.include=[`reasoning.encrypted_content`]),this.config.logger?.debug(`[ClaudeToOpenAI] Thinking mode`,{thinkingDisabled:this.config.thinkingDisabled??!1}),s.instructions=this.adaptInstructionsForChatGPT(this.codexInstructions);let c=[],l=``;if(e.system){let t=this.extractSystemMessages(e.system);t&&Array.isArray(t)&&t.length>0&&(l=t.map(e=>e.content).join(`
|
|
2
2
|
|
|
3
3
|
`))}if(l=this.removeClaudeCodeInstructions(l),l&&c.push({type:`message`,role:`user`,content:[{type:`input_text`,text:l}]}),e.messages&&Array.isArray(e.messages))for(let t of e.messages){let e=this.convertMessageToInput(t);Array.isArray(e)?c.push(...e):e&&c.push(e)}if(s.input=c,e.tools&&Array.isArray(e.tools)){this.config.logger?.debug(`[ClaudeToOpenAI] Original Claude tools`,{tools:JSON.stringify(e.tools,null,2)});let t=this.convertTools(e.tools);this.config.logger?.debug(`[ClaudeToOpenAI] Converted tools`,{tools:JSON.stringify(t,null,2)}),t.length>0?(s.tools=t,this.config.logger?.debug(`[ClaudeToOpenAI] Added tools to responsesRequest`,{toolCount:t.length}),this.config.logger?.debug(`[ClaudeToOpenAI] Verify responsesRequest.tools exists`,{hasTools:!!s.tools,toolsLength:s.tools?.length,keys:Object.keys(s)})):this.config.logger?.warn(`[ClaudeToOpenAI] No valid tools after conversion, omitting tools field`)}else this.config.logger?.debug(`[ClaudeToOpenAI] No tools in Claude request`,{hasTools:!!e.tools,isArray:Array.isArray(e.tools)});let u=this.config.toEndpoint||`https://chatgpt.com/backend-api/codex/responses`,d={version:`0.46.0`,"openai-beta":`responses=experimental`,conversation_id:r,session_id:n,accept:`text/event-stream`,"content-type":`application/json`,"user-agent":`codex_cli_rs/0.46.0 (Mac OS 15.6.0; arm64) iTerm.app/3.6.2`,originator:`codex_cli_rs`},p=this.config.resolvedAuth;return p?.accessToken?(this.config.logger?.debug(`[ClaudeToOpenAI] Raw access token`,{token:`${p.accessToken.substring(0,30)}...`}),d.authorization=p.accessToken.startsWith(`Bearer `)?p.accessToken:`Bearer ${p.accessToken}`,p.accountId&&(d[`chatgpt-account-id`]=p.accountId)):this.config.toApiKey&&(d.authorization=`Bearer ${this.config.toApiKey}`),d.authorization?.startsWith(`Bearer `)&&this.config.logger?.debug(`[ClaudeToOpenAI] Added Bearer auth header`),this.config.logger?.debug(`[ClaudeToOpenAI] ===== REQUEST DETAILS =====`),this.config.logger?.debug(`[ClaudeToOpenAI] Target URL`,{targetUrl:u}),this.config.logger?.debug(`[ClaudeToOpenAI] Headers`,{headers:{...d,authorization:d.authorization?`${d.authorization.substring(0,30)}...`:void 0}}),this.config.logger?.debug(`[ClaudeToOpenAI] Body`,{body:JSON.stringify(s,null,2)}),this.config.logger?.debug(`[ClaudeToOpenAI] ===============================`),this.config.logger?.debug(`[ClaudeToOpenAI] ===== FINAL TRANSFORMED REQUEST =====`),this.config.logger?.debug(`[ClaudeToOpenAI] Pre-final check - responsesRequest.tools`,{hasTools:!!s.tools,toolsLength:s.tools?.length,keys:Object.keys(s)}),this.config.logger?.debug(`[ClaudeToOpenAI] Final body`,{body:JSON.stringify(s,null,2)}),this.config.logger?.debug(`[ClaudeToOpenAI] ===== END FINAL TRANSFORMED REQUEST =====`),{url:u,body:JSON.stringify(s),headers:d}}catch(e){throw Error(`Failed to transform Claude request to OpenAI: ${e instanceof Error?e.message:String(e)}`,{cause:e})}}adaptInstructionsForChatGPT(e){let t=e;return t=t.replace(/You are powered by the model named Sonnet 4\.5\. The exact model ID is claude-sonnet-4-5-\d+\./g,`You are powered by ChatGPT (GPT-5 reasoning model).`),t=t.replace(/Assistant knowledge cutoff is January 2025/g,`Assistant knowledge cutoff is October 2023`),t=t.replace(/\bClaude\b/g,`ChatGPT`),t=t.replace(/\bAnthropic\b/g,`OpenAI`),t}removeClaudeCodeInstructions(e){let t=[/You are Claude Code, Anthropic's official CLI for Claude\.[\s\S]*?claude_code_docs_map\.md/,/You are Claude Code[\s\S]*?using Claude Code\n/],n=e;for(let e of t)n=n.replace(e,``);return n=n.replace(/\n{3,}/g,`
|
|
4
4
|
|
|
5
|
-
`).trim(),n}extractSystemMessages(e){let t=[];if(typeof e==`string`)t.push({role:`system`,content:e});else if(Array.isArray(e))for(let n of e)typeof n==`string`?t.push({role:`system`,content:n}):
|
|
5
|
+
`).trim(),n}extractSystemMessages(e){let t=[];if(typeof e==`string`)t.push({role:`system`,content:e});else if(Array.isArray(e))for(let n of e)typeof n==`string`?t.push({role:`system`,content:n}):ge(n)&&n.type===`text`&&typeof n.text==`string`&&t.push({role:`system`,content:n.text});else ge(e)&&e.type===`text`&&typeof e.text==`string`&&t.push({role:`system`,content:e.text});return t}convertMessageToInput(e){let t=e.role,n=e.content;if(!t||!n)return null;let r=t===`assistant`?`output_text`:`input_text`;if(Array.isArray(n)){let e=[],i=[],a=()=>{i.length!==0&&(e.push({type:`message`,role:t,content:i}),i=[])};for(let t of n)if(t.type===`tool_result`){a();let n=typeof t.content==`string`?t.content:JSON.stringify(t.content);e.push({type:`function_call_output`,call_id:t.tool_use_id,output:n})}else if(t.type===`text`)i.push({type:r,text:t.text||``});else if(t.type===`image`)if(t.source&&t.source.type===`base64`&&t.source.data){let e=t.source.media_type||`image/jpeg`,n=`data:${e};base64,${t.source.data}`;i.push({type:`input_image`,image_url:n}),this.config.logger?.debug(`[ClaudeToOpenAI] Converted image block`,{mediaType:e,dataLength:t.source.data.length})}else this.config.logger?.warn(`[ClaudeToOpenAI] Unsupported image format`,{source:t.source});else t.type===`tool_use`&&(a(),e.push({type:`function_call`,call_id:t.id,name:t.name,arguments:JSON.stringify(t.input||{})}));return a(),e.length>1?e:e.length===1?e[0]:null}return typeof n==`string`?{type:`message`,role:t,content:[{type:r,text:n}]}:null}convertTools(e){return!e||!Array.isArray(e)?[]:e.filter(e=>!(!e||typeof e!=`object`||!e.name)).map(e=>({type:`function`,name:e.name,description:e.description||``,parameters:e.input_schema||e.parameters||{}}))}};let ve;function ye(){return ve??=(0,g.createNodeTelemetry)({serviceName:`@agimon-ai/model-proxy-mcp`,workspaceRoot:process.cwd()}),ve}function be(e){if(e===void 0)return;if(e instanceof Error)return{exception:e,attributes:{error:e.message}};if(typeof e!=`object`||!e)return{attributes:{data:String(e)}};let t={};for(let[n,r]of Object.entries(e))r!=null&&(t[n]=typeof r==`string`||typeof r==`number`||typeof r==`boolean`?r:JSON.stringify(r));return{attributes:Object.keys(t).length>0?t:void 0}}function S(e,t,n){console.error(t,n??``),ye().then(r=>{r.logger[e](t,be(n))}).catch(()=>void 0)}const C={info:(e,t)=>S(`info`,e,t),error:(e,t)=>S(`error`,e,t),debug:(e,t)=>S(`debug`,e,t),warn:(e,t)=>S(`warn`,e,t)};var xe=class e{static TOKEN_REFRESH_URL=`https://auth.openai.com/oauth/token`;static CLIENT_ID=`app_EMoamEEZ73f0CkXaXp7hrann`;constructor(e=C,t=d.default.join(process.env.HOME||``,`.codex`,`auth.json`)){this.logger=e,this.authFilePath=t}getAuthFilePath(){return this.authFilePath}async getAuthStatus(){let e=this.readAuthFile();return{configured:!!e?.tokens?.refresh_token,accountId:e?.tokens?.account_id??null,authFilePath:this.authFilePath,lastRefresh:e?.last_refresh??null}}async getAccountId(){return(await this.resolveAuth()).accountId}async getAccessToken(){return(await this.resolveAuth()).accessToken}async resolveAuth(){let e=this.readAuthFile();if(!e?.tokens)return this.logger.warn(`[CodexAuth] No tokens found in auth file`),{accessToken:null,accountId:null};let t=e.tokens.account_id||null,n=e.tokens.access_token;if(n.startsWith(`Bearer `)&&(n=n.slice(7)),!this.isTokenExpired(n))return{accessToken:n,accountId:t};let r=await this.refreshAccessToken(e.tokens.refresh_token);return r?(this.saveTokens(r),{accessToken:r.access_token,accountId:r.account_id||t}):{accessToken:null,accountId:t}}readAuthFile(){try{return m.default.existsSync(this.authFilePath)?JSON.parse(m.default.readFileSync(this.authFilePath,`utf8`)):null}catch(e){return this.logger.error(`[CodexAuth] Failed to read auth file`,e),null}}isTokenExpired(e){let t=this.decodeJWT(e);return t?.exp?t.exp*1e3-Date.now()<300*1e3:!0}decodeJWT(e){try{let[,t]=e.split(`.`);return t?JSON.parse(Buffer.from(t,`base64url`).toString(`utf8`)):null}catch(e){return this.logger.error(`[CodexAuth] Failed to decode JWT`,e),null}}async refreshAccessToken(t){try{let n=await fetch(e.TOKEN_REFRESH_URL,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:new URLSearchParams({grant_type:`refresh_token`,refresh_token:t,client_id:e.CLIENT_ID})});if(!n.ok)return this.logger.error(`[CodexAuth] Token refresh failed`,await n.text()),null;let r=await n.json();return{id_token:r.id_token??``,access_token:(r.access_token??``).replace(/^Bearer\s+/i,``),refresh_token:r.refresh_token||t,account_id:r.account_id??``}}catch(e){return this.logger.error(`[CodexAuth] Failed to refresh token`,e),null}}saveTokens(e){try{let t=this.readAuthFile();if(!t)return;t.tokens=e,t.last_refresh=new Date().toISOString(),m.default.writeFileSync(this.authFilePath,JSON.stringify(t,null,2),`utf8`)}catch(e){this.logger.error(`[CodexAuth] Failed to save tokens`,e)}}};const w=e=>typeof e==`object`&&!!e&&!Array.isArray(e);function Se(e){let t=e?.error;if(typeof t==`string`)return t;if(w(t)){if(typeof t.message==`string`)return t.message;if(typeof t.error==`string`)return t.error}return typeof e?.message==`string`?e.message:`Unexpected API error`}var Ce=class{logger;thinkingDisabled;constructor(e,t=!1){this.logger=e,this.thinkingDisabled=t}formatSseEvent(e,t){return`event: ${e}\ndata: ${JSON.stringify(t)}\n\n`}transform(e){try{return!e||e.trim()===``?this.createEmptyClaudeResponse():e.includes(`data:`)?this.convertStreamingResponse(e):this.convertNonStreamingResponse(e)}catch(e){return this.logger?.error(`[OpenAIToClaude] ERROR in transform`,e),this.createEmptyClaudeResponse()}}transformStream(e){let t={encoder:new TextEncoder,messageId:`msg_${(0,p.ulid)()}`,model:`gpt-5`,inputTokens:0,outputTokens:0,cachedTokens:0,reasoningTokens:0,reasoningEffort:void 0,stopReason:`end_turn`,messageStarted:!1,messageStopped:!1,thinkingBlockStarted:!1,textBlockStarted:!1,emittedThinking:!1,emittedText:!1,lastThinkingChunk:null,lastTextChunk:null,streamedReasoningKeys:new Set,streamedTextKeys:new Set,emittedToolCallIndexes:new Set,toolCalls:new Map},n=new TextDecoder,r=``;return new ReadableStream({start:async i=>{let a=e.getReader();try{for(;;){let{done:e,value:o}=await a.read();if(e)break;r+=n.decode(o,{stream:!0}),r=this.processBufferedEvents(r,t,i)}r+=n.decode(),r=this.processBufferedEvents(r,t,i,!0),t.messageStopped||(t.messageStarted?this.finishStreamingResponse(i,t):i.enqueue(t.encoder.encode(this.createEmptyClaudeResponse())))}catch(e){this.logger?.error(`[OpenAIToClaude] ERROR in transformStream`,e),t.messageStopped||(t.messageStarted?this.emitStreamingError(i,t,`Unexpected API error`):i.enqueue(t.encoder.encode(this.createClaudeErrorStream(`Unexpected API error`))))}finally{i.close(),a.releaseLock()}}})}createEmptyClaudeResponse(){let e=`msg_${(0,p.ulid)()}`,t=[];return t.push(`event: message_start`),t.push(`data: ${JSON.stringify({type:`message_start`,message:{id:e,type:`message`,role:`assistant`,content:[],model:`gpt-5`,stop_reason:null,stop_sequence:null,usage:{input_tokens:0,output_tokens:0}}})}`),t.push(``),t.push(`event: message_stop`),t.push(`data: {"type":"message_stop"}`),t.push(``),t.join(`
|
|
6
6
|
`)}convertStreamingResponse(e){let t=this.parseOpenAIStream(e),n=this.createClaudeStreamFromParsed(t),r=t.errorMessage||`Empty streaming response from provider`;return this.ensureValidClaudeStream(n,r)}parseOpenAIStream(e){let t={textSegments:[],thinkingSegments:[],toolCalls:new Map,streamedReasoningKeys:new Set,streamedTextKeys:new Set,model:`gpt-5`,inputTokens:0,outputTokens:0,cachedTokens:0,reasoningTokens:0,reasoningEffort:void 0,stopReason:void 0,errorMessage:void 0},n=e.split(`
|
|
7
|
-
`),r=``,i=/event:\s*response\./i.test(e)||/"type"\s*:\s*"response\./i.test(e)||/"response"\s*:\s*\{/i.test(e);for(let e of n){let n=e.trim();if(!n)continue;if(n.startsWith(`event:`)){r=n.slice(6).trim();continue}if(!n.startsWith(`data:`))continue;let a=n.slice(5).trim();if(!a||a===`[DONE]`)continue;let o;try{o=JSON.parse(a)}catch{continue}if(o?.error){t.errorMessage=
|
|
7
|
+
`),r=``,i=/event:\s*response\./i.test(e)||/"type"\s*:\s*"response\./i.test(e)||/"response"\s*:\s*\{/i.test(e);for(let e of n){let n=e.trim();if(!n)continue;if(n.startsWith(`event:`)){r=n.slice(6).trim();continue}if(!n.startsWith(`data:`))continue;let a=n.slice(5).trim();if(!a||a===`[DONE]`)continue;let o;try{o=JSON.parse(a)}catch{continue}if(o?.error){t.errorMessage=Se(o);break}i||r.startsWith(`response.`)?this.handleResponsesEvent(r,o,t):this.handleChatCompletionChunk(o,t)}return t}handleResponsesEvent(e,t,n){let r=typeof t?.type==`string`?t.type:e;switch(t?.model&&typeof t.model==`string`&&(n.model=t.model),t?.usage&&(n.inputTokens=t.usage.input_tokens??t.usage.prompt_tokens??n.inputTokens,n.outputTokens=t.usage.output_tokens??t.usage.completion_tokens??n.outputTokens),r){case`response.created`:t?.response?.model&&(n.model=t.response.model);break;case`response.reasoning.delta`:case`response.reasoning_summary_text.delta`:case`response.function_call_arguments.delta`:case`response.function_call_arguments.done`:case`response.in_progress`:case`response.output_item.added`:case`response.output_item.done`:case`response.content_part.added`:case`response.content_part.done`:case`response.reasoning_summary_part.added`:case`response.reasoning_summary_part.done`:case`response.reasoning_summary_text.done`:case`response.output_text.delta`:case`response.output_text.done`:case`response.delta`:break;case`response.completed`:case`response.done`:this.collectCompletedResponse(t,n);break;case`response.error`:n.errorMessage=Se(t);break;default:t?.delta&&(this.collectTextFromNode(t.delta,n.textSegments),t.delta.tool_calls&&this.collectToolCalls(t.delta.tool_calls,n.toolCalls));break}}handleChatCompletionChunk(e,t){if(e&&(e.model&&typeof e.model==`string`&&(t.model=e.model),e.usage&&(t.inputTokens=e.usage.prompt_tokens??t.inputTokens,t.outputTokens=e.usage.completion_tokens??t.outputTokens),Array.isArray(e.choices)))for(let n of e.choices)n?.delta&&(this.collectTextFromNode(n.delta,t.textSegments),n.delta.tool_calls&&this.collectToolCalls(n.delta.tool_calls,t.toolCalls)),n?.message?.content&&this.collectTextFromNode(n.message.content,t.textSegments),n?.finish_reason&&(t.stopReason=this.mapFinishReason(n.finish_reason))}collectCompletedResponse(e,t){let n=!1;if(e?.response?.model&&(t.model=e.response.model),e?.response?.usage&&(t.inputTokens=e.response.usage.input_tokens??e.response.usage.prompt_tokens??t.inputTokens,t.outputTokens=e.response.usage.output_tokens??e.response.usage.completion_tokens??t.outputTokens,e.response.usage.input_tokens_details?.cached_tokens&&(t.cachedTokens=e.response.usage.input_tokens_details.cached_tokens),e.response.usage.output_tokens_details?.reasoning_tokens&&(t.reasoningTokens=e.response.usage.output_tokens_details.reasoning_tokens)),e?.response?.reasoning?.effort&&(t.reasoningEffort=e.response.reasoning.effort),Array.isArray(e?.response?.tool_calls)&&this.collectToolCalls(e.response.tool_calls,t.toolCalls),Array.isArray(e?.response?.output)){for(let[r,i]of e.response.output.entries())if(i?.type===`reasoning`){let e={output_index:r,item:i};if(!this.shouldEmitTerminalContent(e,t.streamedReasoningKeys,`reasoning`,t.thinkingSegments.length>0))continue;if(Array.isArray(i?.summary))for(let e of i.summary)e?.type===`summary_text`&&e?.text&&t.thinkingSegments.push(e.text)}else if(i?.type===`message`){let e={output_index:r,item:i};if(!this.shouldEmitTerminalContent(e,t.streamedTextKeys,`text`,t.textSegments.length>0))continue;if(Array.isArray(i?.content))for(let e of i.content)(e?.type===`output_text`||e?.type===`text`)&&e?.text&&(t.textSegments.push(e.text),n=!0)}else if(i?.type===`function_call`){let e={index:t.toolCalls.size,id:i.id||`tool_${(0,p.ulid)()}`,function:{name:i.name,arguments:i.arguments}};this.collectToolCalls([e],t.toolCalls)}}e?.response?.output_text&&!n&&this.shouldEmitTerminalContent({output_index:0},t.streamedTextKeys,`text`,t.textSegments.length>0)&&this.collectTextFromNode(e.response.output_text,t.textSegments),t.stopReason=this.mapResponseStatusToStopReason(e?.response?.status)}collectTextFromNode(e,t){if(e!=null){if(typeof e==`string`){e.length>0&&t.push(e);return}if(Array.isArray(e)){for(let n of e)this.collectTextFromNode(n,t);return}if(w(e)){typeof e.text==`string`&&t.push(e.text),typeof e.output_text==`string`&&t.push(e.output_text),typeof e.value==`string`&&t.push(e.value),typeof e.delta==`string`?t.push(e.delta):e.delta&&this.collectTextFromNode(e.delta,t),w(e.token)&&typeof e.token.text==`string`&&t.push(e.token.text);for(let n of[`content`,`output`,`output_text`,`message`,`choices`,`segments`])if(e[n]!==void 0)if(n===`choices`&&Array.isArray(e[n]))for(let r of e[n])r?.message?.content&&this.collectTextFromNode(r.message.content,t),r?.delta&&this.collectTextFromNode(r.delta,t);else this.collectTextFromNode(e[n],t)}}}collectToolCalls(e,t){if(!e)return;let n=Array.isArray(e)?e:[e];for(let e of n){if(!w(e))continue;let n=typeof e.index==`number`?e.index:t.size,r=t.get(n)||{id:``,name:``,argumentChunks:[]};typeof e.id==`string`&&!r.id&&(r.id=e.id);let i=w(e.function)?e.function:null,a=i?.name??e.name;typeof a==`string`&&a.length>0&&(r.name=a);let o=i?.arguments??e.arguments;typeof o==`string`&&o.length>0&&r.argumentChunks.push(o),t.set(n,r)}}collectResponsesToolCallDelta(e,t){let n=typeof e?.output_index==`number`?e.output_index:t.size,r=t.get(n)||{id:``,name:``,argumentChunks:[]},i=e?.item_id??e?.id;typeof i==`string`&&!r.id&&(r.id=i);let a=e?.name??e?.item?.name??e?.item?.call_id;typeof a==`string`&&a.length>0&&(r.name=a);let o=e?.delta??e?.arguments??e?.item?.arguments;typeof o==`string`&&o.length>0&&r.argumentChunks.push(o),t.set(n,r)}collectResponsesOutputItem(e,t,n=!1){let r=e?.item;if(r?.type===`message`){if(n&&!this.shouldEmitTerminalContent(e,t.streamedTextKeys,`text`,t.textSegments.length>0))return;this.collectTextFromNode(r.content,t.textSegments);return}if(r?.type===`reasoning`){if(n&&!this.shouldEmitTerminalContent(e,t.streamedReasoningKeys,`reasoning`,t.thinkingSegments.length>0))return;this.collectTextFromNode(r.summary??r.content,t.thinkingSegments);return}this.collectResponsesToolCallDelta(e,t.toolCalls)}parseToolCallInput(e){if(!e)return{};try{let t=JSON.parse(e);return t&&typeof t==`object`&&!Array.isArray(t)?t:{}}catch{return{}}}createClaudeStreamFromParsed(e){if(e.errorMessage)return this.createClaudeErrorStream(e.errorMessage);let t=this.thinkingDisabled?[]:this.mergeAndChunkSegments(this.dedupeSegments(e.thinkingSegments)),n=this.mergeAndChunkSegments(this.dedupeSegments(e.textSegments)),r=new Set,i=Array.from(e.toolCalls.entries()).sort((e,t)=>e[0]-t[0]).map(([e,t])=>({index:e,id:t.id,name:t.name,arguments:this.normalizeToolCallArguments(t.argumentChunks)})).filter(e=>{if(!e.name)return!1;let t=`${e.id}\u0000${e.name}\u0000${e.arguments}`;return r.has(t)?!1:(r.add(t),!0)});if(t.length===0&&n.length===0&&i.length===0)return this.createClaudeErrorStream(`Empty streaming response from provider`);let a=`msg_${(0,p.ulid)()}`,o=e.model||`gpt-5`,s=[];s.push(`event: message_start`),s.push(`data: ${JSON.stringify({type:`message_start`,message:{id:a,type:`message`,role:`assistant`,content:[],model:o,stop_reason:null,stop_sequence:null,usage:{input_tokens:e.inputTokens??0,output_tokens:0}}})}`),s.push(``);let c=0;if(t.length>0){s.push(`event: content_block_start`),s.push(`data: ${JSON.stringify({type:`content_block_start`,index:c,content_block:{type:`thinking`,thinking:``}})}`),s.push(``);for(let e of t)e&&(s.push(`event: content_block_delta`),s.push(`data: ${JSON.stringify({type:`content_block_delta`,index:c,delta:{type:`thinking_delta`,thinking:e}})}`),s.push(``));s.push(`event: content_block_stop`),s.push(`data: ${JSON.stringify({type:`content_block_stop`,index:c})}`),s.push(``),c+=1}if(n.length>0){s.push(`event: content_block_start`),s.push(`data: ${JSON.stringify({type:`content_block_start`,index:c,content_block:{type:`text`,text:``}})}`),s.push(``);for(let e of n)e&&(s.push(`event: content_block_delta`),s.push(`data: ${JSON.stringify({type:`content_block_delta`,index:c,delta:{type:`text_delta`,text:e}})}`),s.push(``));s.push(`event: content_block_stop`),s.push(`data: ${JSON.stringify({type:`content_block_stop`,index:c})}`),s.push(``),c+=1}for(let e of i){let t=c+e.index,n=e.id||`tool_${(0,p.ulid)()}`;s.push(`event: content_block_start`),s.push(`data: ${JSON.stringify({type:`content_block_start`,index:t,content_block:{type:`tool_use`,id:n,name:e.name,input:{}}})}`),s.push(``),e.arguments&&(s.push(`event: content_block_delta`),s.push(`data: ${JSON.stringify({type:`content_block_delta`,index:t,delta:{type:`input_json_delta`,partial_json:e.arguments}})}`),s.push(``)),s.push(`event: content_block_stop`),s.push(`data: ${JSON.stringify({type:`content_block_stop`,index:t})}`),s.push(``)}let l=e.stopReason||`end_turn`,u={output_tokens:e.outputTokens??0};return(e.cachedTokens||e.reasoningTokens||e.reasoningEffort)&&(u.metadata={},e.cachedTokens&&(u.metadata.cached_tokens=e.cachedTokens),e.reasoningTokens&&(u.metadata.reasoning_tokens=e.reasoningTokens),e.reasoningEffort&&(u.metadata.reasoning_effort=e.reasoningEffort)),s.push(`event: message_delta`),s.push(`data: ${JSON.stringify({type:`message_delta`,delta:{stop_reason:l,stop_sequence:null},usage:u})}`),s.push(``),s.push(`event: message_stop`),s.push(`data: {"type":"message_stop"}`),s.push(``),s.join(`
|
|
8
8
|
`)}mergeAndChunkSegments(e,t=2e3){if(!e||e.length===0)return[];let n=this.collapseRepeatedWholeText(e.join(``));if(!n)return[];let r=[];for(let e=0;e<n.length;e+=t)r.push(n.slice(e,e+t));return r}dedupeSegments(e){if(e.length<2)return e;let t=[];for(let n of e)n&&t.at(-1)!==n&&t.push(n);return t}normalizeToolCallArguments(e){return e.length===0?``:this.sanitizeToolCallArgumentsJson(this.collapseRepeatedWholeText(this.dedupeSegments(e).join(``)))}sanitizeToolCallArgumentsJson(e){if(!e)return``;try{let t=JSON.parse(e),n=this.stripEmptyStringValues(t);return JSON.stringify(n)}catch{return e}}stripEmptyStringValues(e){return Array.isArray(e)?e.map(e=>this.stripEmptyStringValues(e)):!e||typeof e!=`object`?e:Object.fromEntries(Object.entries(e).filter(([,e])=>e!==``).map(([e,t])=>[e,this.stripEmptyStringValues(t)]))}collapseRepeatedWholeText(e){if(!e||e.length<2)return e;for(let t=4;t>=2;--t){if(e.length%t!==0)continue;let n=e.slice(0,e.length/t);if(n&&n.repeat(t)===e)return n}return e}markStreamedContentKey(e,t,n){let r=this.getResponseContentKey(e,n);r&&t.add(r)}shouldEmitTerminalContent(e,t,n,r){let i=this.getResponseContentKey(e,n);return i?!t.has(i):!r}getResponseContentKey(e,t){let n=e?.item_id??e?.item?.id??e?.id,r=typeof e?.output_index==`number`?e.output_index:null,i=typeof e?.content_index==`number`?e.content_index:null;return typeof n==`string`&&n.length>0?`${t}:item:${n}:${i??`na`}`:r===null?null:`${t}:output:${r}:${i??`na`}`}mapResponseStatusToStopReason(e){return e&&{completed:`end_turn`,completed_with_error:`error`,completed_with_streaming_error:`error`,cancelled:`error`,errored:`error`}[e]||`end_turn`}convertNonStreamingResponse(e){try{let t=JSON.parse(e);if(t?.error){let e=t.error?.message||t.error?.error||(typeof t.error==`string`?t.error:`Unexpected API error`);return this.createClaudeErrorResponse(e)}let n=t?.response&&typeof t.response==`object`?t.response:t;if(typeof n?.type==`string`?n.type.startsWith(`response`):Array.isArray(n?.output)||n?.output_text!==void 0){let e={textSegments:[],thinkingSegments:[],toolCalls:new Map,streamedReasoningKeys:new Set,streamedTextKeys:new Set,model:`gpt-5`,inputTokens:0,outputTokens:0,cachedTokens:0,reasoningTokens:0,reasoningEffort:void 0,stopReason:void 0,errorMessage:void 0};this.collectCompletedResponse({response:n},e);let t=this.createClaudeStreamFromParsed(e);return this.ensureValidClaudeStream(t,`Empty response from provider`)}let r=t.choices?.[0];if(!r)return this.createClaudeErrorStream(`Empty response from provider`);let i={textSegments:[],thinkingSegments:[],toolCalls:new Map,streamedReasoningKeys:new Set,streamedTextKeys:new Set,model:t.model||`gpt-4-turbo`,inputTokens:t.usage?.prompt_tokens||0,outputTokens:t.usage?.completion_tokens||0,cachedTokens:0,reasoningTokens:0,reasoningEffort:void 0,stopReason:this.mapFinishReason(r.finish_reason),errorMessage:void 0};this.collectTextFromNode(r.message?.content,i.textSegments);let a=this.createClaudeStreamFromParsed(i);return this.ensureValidClaudeStream(a,`Empty response from provider`)}catch(t){this.logger?.error(`Failed to transform OpenAI response to Claude format`,t);let n=typeof e==`string`&&e.trim()?e.trim():`Unexpected API error`;return this.createClaudeErrorStream(n)}}mapFinishReason(e){return e?{stop:`end_turn`,length:`max_tokens`,function_call:`tool_use`,tool_calls:`tool_use`,content_filter:`stop_sequence`}[e]||`end_turn`:null}createClaudeErrorResponse(e){let t=`msg_${(0,p.ulid)()}`;return JSON.stringify({id:t,type:`message`,role:`assistant`,content:[{type:`text`,text:e||`Unexpected API error`}],model:`gpt-5`,stop_reason:`error`,stop_sequence:null,usage:{input_tokens:0,output_tokens:0}})}createClaudeErrorStream(e){let t=`msg_${(0,p.ulid)()}`,n=e||`Unexpected API error`,r=[];return r.push(`event: message_start`),r.push(`data: ${JSON.stringify({type:`message_start`,message:{id:t,type:`message`,role:`assistant`,content:[],model:`gpt-5`,stop_reason:null,stop_sequence:null,usage:{input_tokens:0,output_tokens:0}}})}`),r.push(``),r.push(`event: content_block_start`),r.push(`data: ${JSON.stringify({type:`content_block_start`,index:0,content_block:{type:`text`,text:``}})}`),r.push(``),r.push(`event: content_block_delta`),r.push(`data: ${JSON.stringify({type:`content_block_delta`,index:0,delta:{type:`text_delta`,text:n}})}`),r.push(``),r.push(`event: content_block_stop`),r.push(`data: ${JSON.stringify({type:`content_block_stop`,index:0})}`),r.push(``),r.push(`event: message_delta`),r.push(`data: ${JSON.stringify({type:`message_delta`,delta:{stop_reason:`error`,stop_sequence:null},usage:{output_tokens:0}})}`),r.push(``),r.push(`event: message_stop`),r.push(`data: {"type":"message_stop"}`),r.push(``),r.join(`
|
|
9
9
|
`)}ensureValidClaudeStream(e,t){return!e||!e.includes(`event: message_start`)?this.createClaudeErrorStream(t):e}processBufferedEvents(e,t,n,r=!1){let i=e.replace(/\r\n/g,`
|
|
10
10
|
`).split(`
|
|
11
11
|
|
|
12
12
|
`),a=r?``:i.pop()??``;for(let e of i)this.processSseChunk(e,t,n);return r&&a.trim()&&this.processSseChunk(a,t,n),a}processSseChunk(e,t,n){let r=e.split(`
|
|
13
13
|
`),i=``,a=[];for(let e of r){let t=e.replace(/\r$/,``);t.startsWith(`event:`)?i=t.slice(6).trim():t.startsWith(`data:`)&&a.push(t.slice(5).trim())}let o=a.join(`
|
|
14
|
-
`).trim();if(!(!o||o===`[DONE]`))try{let e=JSON.parse(o),r=typeof e?.type==`string`?e.type:i,a=r.startsWith(`response.`)||i.startsWith(`response.`);if(e?.error||r===`response.error`){let r=e?.error?.message||e?.error?.error||e?.message||(typeof e?.error==`string`?e.error:`Unexpected API error`);this.emitStreamingError(n,t,r);return}if(a){this.processResponsesStreamingEvent(r,e,t,n);return}this.processChatCompletionStreamingChunk(e,t,n)}catch{}}processResponsesStreamingEvent(e,t,n,r){switch(t?.model&&typeof t.model==`string`&&(n.model=t.model),e){case`response.created`:typeof t?.response?.model==`string`&&(n.model=t.response.model);break;case`response.reasoning.delta`:case`response.reasoning_summary_text.delta`:{let e=[];this.markStreamedContentKey(t,n.streamedReasoningKeys,`reasoning`),this.collectTextFromNode(t?.delta??t?.text??t?.summary_text,e);for(let t of e)this.emitThinkingDelta(r,n,t);break}case`response.output_text.delta`:case`response.delta`:{let e=[];this.markStreamedContentKey(t,n.streamedTextKeys,`text`),this.collectTextFromNode(t?.delta??t?.output_text,e);for(let t of e)this.emitTextDelta(r,n,t);t?.delta?.tool_calls&&this.collectToolCalls(t.delta.tool_calls,n.toolCalls);break}case`response.function_call_arguments.delta`:case`response.function_call_arguments.done`:case`response.output_item.added`:case`response.output_item.done`:if(t?.item?.type===`message`){if(this.shouldEmitTerminalContent(t,n.streamedTextKeys,`text`,n.emittedText)){let e=[];this.collectTextFromNode(t.item.content,e);let i=e.join(``);if(i&&i===n.lastTextChunk)break;for(let t of e)this.emitTextDelta(r,n,t)}}else if(t?.item?.type===`reasoning`){if(this.shouldEmitTerminalContent(t,n.streamedReasoningKeys,`reasoning`,n.emittedThinking)){let e=[];this.collectTextFromNode(t.item.summary??t.item.content,e);let i=e.join(``);if(i&&i===n.lastThinkingChunk)break;for(let t of e)this.emitThinkingDelta(r,n,t)}}else this.collectResponsesToolCallDelta(t,n.toolCalls);break;case`response.output_text.done`:case`response.content_part.done`:if(n.textBlockStarted)break;if(this.shouldEmitTerminalContent(t,n.streamedTextKeys,`text`,n.emittedText)){let e=[];this.collectTextFromNode(t?.delta??t?.output_text??t?.text??t?.part,e);let i=e.join(``);if(i&&i===n.lastTextChunk)break;for(let t of e)this.emitTextDelta(r,n,t)}break;case`response.completed`:case`response.done`:{let e={textSegments:[],thinkingSegments:[],toolCalls:new Map(n.toolCalls),streamedReasoningKeys:new Set(n.streamedReasoningKeys),streamedTextKeys:new Set(n.streamedTextKeys),model:n.model,inputTokens:n.inputTokens,outputTokens:n.outputTokens,cachedTokens:n.cachedTokens,reasoningTokens:n.reasoningTokens,reasoningEffort:n.reasoningEffort,stopReason:n.stopReason};if(this.collectCompletedResponse(t,e),n.model=e.model,n.inputTokens=e.inputTokens,n.outputTokens=e.outputTokens,n.cachedTokens=e.cachedTokens,n.reasoningTokens=e.reasoningTokens,n.reasoningEffort=e.reasoningEffort,n.stopReason=e.stopReason,n.toolCalls=e.toolCalls,!n.emittedThinking)for(let t of e.thinkingSegments)this.emitThinkingDelta(r,n,t);if(!n.emittedText)for(let t of e.textSegments)this.emitTextDelta(r,n,t);this.emitPendingToolCalls(r,n),this.finishStreamingResponse(r,n);break}default:break}}processChatCompletionStreamingChunk(e,t,n){if(e&&(typeof e.model==`string`&&(t.model=e.model),e.usage&&(t.inputTokens=e.usage.prompt_tokens??t.inputTokens,t.outputTokens=e.usage.completion_tokens??t.outputTokens),Array.isArray(e.choices)))for(let r of e.choices){let e=[];this.collectTextFromNode(r?.delta??r?.message?.content,e);for(let r of e)this.emitTextDelta(n,t,r);r?.delta?.tool_calls&&this.collectToolCalls(r.delta.tool_calls,t.toolCalls),r?.finish_reason&&(t.stopReason=this.mapFinishReason(r.finish_reason))}}emitPendingToolCalls(e,t){let n=Array.from(t.toolCalls.entries()).sort((e,t)=>e[0]-t[0]).map(([e,t])=>({index:e,id:t.id||`tool_${(0,p.ulid)()}`,name:t.name,arguments:t.argumentChunks.join(``)})).filter(e=>e.name&&!t.emittedToolCallIndexes.has(e.index));for(let r of n){this.stopOpenContentBlocks(e,t);let n=this.getNextToolBlockIndex(r.index);e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_start`,{type:`content_block_start`,index:n,content_block:{type:`tool_use`,id:r.id,name:r.name,input:{}}})+(r.arguments?this.formatSseEvent(`content_block_delta`,{type:`content_block_delta`,index:n,delta:{type:`input_json_delta`,partial_json:r.arguments}}):``)+this.formatSseEvent(`content_block_stop`,{type:`content_block_stop`,index:n}))),t.emittedToolCallIndexes.add(r.index)}}emitThinkingDelta(e,t,n){this.thinkingDisabled||!n||(this.ensureMessageStarted(e,t),t.textBlockStarted&&this.stopTextBlock(e,t),t.thinkingBlockStarted||=(e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_start`,{type:`content_block_start`,index:0,content_block:{type:`thinking`,thinking:``}}))),!0),e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_delta`,{type:`content_block_delta`,index:0,delta:{type:`thinking_delta`,thinking:n}}))),t.emittedThinking=!0,t.lastThinkingChunk=n)}emitTextDelta(e,t,n){n&&(this.ensureMessageStarted(e,t),t.textBlockStarted||=(t.thinkingBlockStarted&&this.stopThinkingBlock(e,t),e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_start`,{type:`content_block_start`,index:this.getTextBlockIndex(),content_block:{type:`text`,text:``}}))),!0),e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_delta`,{type:`content_block_delta`,index:this.getTextBlockIndex(),delta:{type:`text_delta`,text:n}}))),t.emittedText=!0,t.lastTextChunk=n)}emitStreamingError(e,t,n){if(!t.messageStarted){e.enqueue(t.encoder.encode(this.createClaudeErrorStream(n))),t.messageStarted=!0,t.messageStopped=!0;return}!t.textBlockStarted&&!t.thinkingBlockStarted&&this.emitTextDelta(e,t,n),t.stopReason=`error`,this.finishStreamingResponse(e,t)}finishStreamingResponse(e,t){if(t.messageStopped)return;this.stopOpenContentBlocks(e,t);let n={output_tokens:t.outputTokens??0};if(t.cachedTokens||t.reasoningTokens||t.reasoningEffort){n.metadata={};let e=n.metadata;t.cachedTokens&&(e.cached_tokens=t.cachedTokens),t.reasoningTokens&&(e.reasoning_tokens=t.reasoningTokens),t.reasoningEffort&&(e.reasoning_effort=t.reasoningEffort)}e.enqueue(t.encoder.encode(this.formatSseEvent(`message_delta`,{type:`message_delta`,delta:{stop_reason:t.stopReason||`end_turn`,stop_sequence:null},usage:n})+this.formatSseEvent(`message_stop`,{type:`message_stop`}))),t.messageStopped=!0}ensureMessageStarted(e,t){t.messageStarted||=(e.enqueue(t.encoder.encode(this.formatSseEvent(`message_start`,{type:`message_start`,message:{id:t.messageId,type:`message`,role:`assistant`,content:[],model:t.model,stop_reason:null,stop_sequence:null,usage:{input_tokens:t.inputTokens??0,output_tokens:0}}}))),!0)}stopOpenContentBlocks(e,t){this.stopTextBlock(e,t),this.stopThinkingBlock(e,t)}stopThinkingBlock(e,t){t.thinkingBlockStarted&&=(e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_stop`,{type:`content_block_stop`,index:0}))),!1)}stopTextBlock(e,t){t.textBlockStarted&&=(e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_stop`,{type:`content_block_stop`,index:this.getTextBlockIndex()}))),!1)}getTextBlockIndex(){return+!this.thinkingDisabled}getNextToolBlockIndex(e){return(this.thinkingDisabled?1:2)+e}};const ye=new Set([`type`,`format`,`description`,`nullable`,`enum`,`items`,`maxItems`,`minItems`,`properties`,`required`,`propertyOrdering`,`maxProperties`,`minProperties`,`minimum`,`maximum`,`minLength`,`maxLength`,`pattern`,`example`,`anyOf`,`title`]);var be=class{transform(e,t,n,r=!1){let i=this.toGeminiContents(e.messages),a=this.toSystemInstruction(e.system),o=this.toGeminiTools(e.tools);return{modelPath:this.toGeminiModelPath(t),body:{contents:i,system_instruction:a,tools:o,tool_config:{function_calling_config:{mode:o.length>0?`AUTO`:`NONE`}},generationConfig:{maxOutputTokens:this.resolveMaxOutputTokens(e.max_tokens,n,r)}}}}toGeminiModelPath(e){return e.startsWith(`models/`)?e:`models/${e}`}toSystemInstruction(e){let t=this.extractTextParts(e);if(t.length!==0)return{parts:t.map(e=>({text:e}))}}toGeminiContents(e){let t=[];for(let n of e){let e=this.toGeminiParts(n.content);e.length!==0&&t.push({role:n.role===`assistant`?`model`:`user`,parts:e})}return t}toGeminiParts(e){if(typeof e==`string`)return e.trim()?[{text:e}]:[];if(!Array.isArray(e))return[];let t=[];for(let n of e)n.type===`text`&&n.text&&t.push({text:n.text}),n.type===`tool_use`&&t.push({functionCall:{name:n.name,args:n.input}}),n.type===`tool_result`&&t.push({functionResponse:{name:n.tool_use_id,response:{content:typeof n.content==`string`?n.content:JSON.stringify(n.content??{})}}});return t}toGeminiTools(e){return!e||e.length===0?[]:[{function_declarations:e.map(e=>({name:e.name,description:e.description,parameters:this.toGeminiParameters(e.input_schema)}))}]}toGeminiParameters(e){if(e)return this.sanitizeGeminiSchema(e)}sanitizeGeminiSchema(e){if(Array.isArray(e))return e.map(e=>this.sanitizeGeminiSchema(e));if(!e||typeof e!=`object`)return e;let t=Object.entries(e).flatMap(([e,t])=>{if(!ye.has(e))return[];if(e===`properties`&&t&&typeof t==`object`&&!Array.isArray(t)){let n=Object.entries(t).map(([e,t])=>[e,this.sanitizeGeminiSchema(t)]);return[[e,Object.fromEntries(n)]]}return[[e,this.sanitizeGeminiSchema(t)]]});return Object.fromEntries(t)}extractTextParts(e){return typeof e==`string`?e.trim()?[e]:[]:Array.isArray(e)?e.map(e=>typeof e==`string`?e:e&&typeof e==`object`&&`text`in e&&typeof e.text==`string`?e.text:null).filter(e=>!!e?.trim()):[]}resolveMaxOutputTokens(e,t,n){return typeof e==`number`&&e>0?e:n||t===`minimal`?2048:4096}};const xe=`GEMINI_AUTH_CONFIG_ERROR`;var T=class extends Error{constructor(e,t){super(e),this.code=t,this.name=`GeminiAuthError`}},Se=class{constructor(e=C,t=process.env){this.logger=e,this.env=t}async resolveHeaders(e){let t=e.authMode??`auto`;return t===`api-key`?this.resolveApiKeyHeaders(e):t===`oauth`?this.resolveOAuthHeaders():await this.tryResolveApiKeyHeaders(e)??this.resolveOAuthHeaders()}getGeminiDirectory(){let e=this.env.GEMINI_CLI_HOME||u.default.homedir();return d.default.join(e,`.gemini`)}getSettingsPath(){return d.default.join(this.getGeminiDirectory(),`settings.json`)}getOAuthPath(){return d.default.join(this.getGeminiDirectory(),`oauth_creds.json`)}async tryResolveApiKeyHeaders(e){try{return await this.resolveApiKeyHeaders(e)}catch{return null}}async resolveApiKeyHeaders(e){let t=e.apiKeyEnvVar??e.authTokenEnvVar??`GEMINI_API_KEY`,n=this.env[t]?.trim();if(!n)throw new T(`Missing Gemini API key. Set ${t}.`,xe);return{headers:{"x-goog-api-key":n},authMode:`api-key`,authSource:`env`}}async resolveOAuthHeaders(){let e=await this.readSettings(),t=await this.readOAuthCredentials(),n=await this.refreshOAuthCredentialsIfNeeded(t),r=e?.security?.auth?.selectedType??null,i=n.access_token?.trim();if(!i)throw new T(`Missing Gemini OAuth credentials. Expected ${this.getOAuthPath()} or GEMINI_API_KEY.`,xe);return{headers:{Authorization:`Bearer ${i}`},authMode:`oauth`,authSource:`gemini-home`,authType:r}}async readOAuthCredentials(){try{let e=await g.default.readFile(this.getOAuthPath(),`utf8`);return JSON.parse(e)}catch(e){throw this.logger.warn(`[GeminiAuth] Failed to read oauth credentials`,{oauthPath:this.getOAuthPath(),cause:e}),new T(`Unable to read Gemini OAuth credentials from ${this.getOAuthPath()}.`,xe)}}async readSettings(){try{let e=await g.default.readFile(this.getSettingsPath(),`utf8`);return JSON.parse(e)}catch(e){return e?.code===`ENOENT`||this.logger.warn(`[GeminiAuth] Failed to read settings`,{settingsPath:this.getSettingsPath(),cause:e}),null}}async refreshOAuthCredentialsIfNeeded(e){if(!this.shouldRefreshCredentials(e))return e;let t=e.refresh_token?.trim();if(!t)return e;let n=new URLSearchParams({client_id:`681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com`,client_secret:`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`,grant_type:`refresh_token`,refresh_token:t}),r=await fetch(`https://oauth2.googleapis.com/token`,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:n});if(!r.ok)return this.logger.warn(`[GeminiAuth] Failed to refresh oauth credentials`,{status:r.status,oauthPath:this.getOAuthPath()}),e;let i=await r.json(),a={...e,access_token:i.access_token??e.access_token,token_type:i.token_type??e.token_type,scope:i.scope??e.scope,expiry_date:typeof i.expires_in==`number`?Date.now()+i.expires_in*1e3:e.expiry_date};return await g.default.writeFile(this.getOAuthPath(),`${JSON.stringify(a,null,2)}\n`,`utf8`),a}shouldRefreshCredentials(e){let t=e.access_token?.trim(),n=e.expiry_date;return t?typeof n==`number`?n<=Date.now()+6e4:!1:!0}};const E=`event: `,D=`data: `,Ce=`message_start`,we=`content_block_start`,Te=`content_block_delta`,Ee=`content_block_stop`,De=`message_delta`,Oe=`message_stop`,ke=`end_turn`;var Ae=class{transformBuffered(e,t){let n=JSON.parse(e);return this.toClaudeStream([n],t)}transformStreaming(e,t){let n=[];for(let t of e.split(`
|
|
14
|
+
`).trim();if(!(!o||o===`[DONE]`))try{let e=JSON.parse(o),r=typeof e?.type==`string`?e.type:i,a=r.startsWith(`response.`)||i.startsWith(`response.`);if(e?.error||r===`response.error`){let r=e?.error?.message||e?.error?.error||e?.message||(typeof e?.error==`string`?e.error:`Unexpected API error`);this.emitStreamingError(n,t,r);return}if(a){this.processResponsesStreamingEvent(r,e,t,n);return}this.processChatCompletionStreamingChunk(e,t,n)}catch{}}processResponsesStreamingEvent(e,t,n,r){switch(t?.model&&typeof t.model==`string`&&(n.model=t.model),e){case`response.created`:typeof t?.response?.model==`string`&&(n.model=t.response.model);break;case`response.reasoning.delta`:case`response.reasoning_summary_text.delta`:{let e=[];this.markStreamedContentKey(t,n.streamedReasoningKeys,`reasoning`),this.collectTextFromNode(t?.delta??t?.text??t?.summary_text,e);for(let t of e)this.emitThinkingDelta(r,n,t);break}case`response.output_text.delta`:case`response.delta`:{let e=[];this.markStreamedContentKey(t,n.streamedTextKeys,`text`),this.collectTextFromNode(t?.delta??t?.output_text,e);for(let t of e)this.emitTextDelta(r,n,t);t?.delta?.tool_calls&&this.collectToolCalls(t.delta.tool_calls,n.toolCalls);break}case`response.function_call_arguments.delta`:case`response.function_call_arguments.done`:case`response.output_item.added`:case`response.output_item.done`:if(t?.item?.type===`message`){if(this.shouldEmitTerminalContent(t,n.streamedTextKeys,`text`,n.emittedText)){let e=[];this.collectTextFromNode(t.item.content,e);let i=e.join(``);if(i&&i===n.lastTextChunk)break;for(let t of e)this.emitTextDelta(r,n,t)}}else if(t?.item?.type===`reasoning`){if(this.shouldEmitTerminalContent(t,n.streamedReasoningKeys,`reasoning`,n.emittedThinking)){let e=[];this.collectTextFromNode(t.item.summary??t.item.content,e);let i=e.join(``);if(i&&i===n.lastThinkingChunk)break;for(let t of e)this.emitThinkingDelta(r,n,t)}}else this.collectResponsesToolCallDelta(t,n.toolCalls);break;case`response.output_text.done`:case`response.content_part.done`:if(n.textBlockStarted)break;if(this.shouldEmitTerminalContent(t,n.streamedTextKeys,`text`,n.emittedText)){let e=[];this.collectTextFromNode(t?.delta??t?.output_text??t?.text??t?.part,e);let i=e.join(``);if(i&&i===n.lastTextChunk)break;for(let t of e)this.emitTextDelta(r,n,t)}break;case`response.completed`:case`response.done`:{let e={textSegments:[],thinkingSegments:[],toolCalls:new Map(n.toolCalls),streamedReasoningKeys:new Set(n.streamedReasoningKeys),streamedTextKeys:new Set(n.streamedTextKeys),model:n.model,inputTokens:n.inputTokens,outputTokens:n.outputTokens,cachedTokens:n.cachedTokens,reasoningTokens:n.reasoningTokens,reasoningEffort:n.reasoningEffort,stopReason:n.stopReason};if(this.collectCompletedResponse(t,e),n.model=e.model,n.inputTokens=e.inputTokens,n.outputTokens=e.outputTokens,n.cachedTokens=e.cachedTokens,n.reasoningTokens=e.reasoningTokens,n.reasoningEffort=e.reasoningEffort,n.stopReason=e.stopReason,n.toolCalls=e.toolCalls,!n.emittedThinking)for(let t of e.thinkingSegments)this.emitThinkingDelta(r,n,t);if(!n.emittedText)for(let t of e.textSegments)this.emitTextDelta(r,n,t);this.emitPendingToolCalls(r,n),this.finishStreamingResponse(r,n);break}default:break}}processChatCompletionStreamingChunk(e,t,n){if(e&&(typeof e.model==`string`&&(t.model=e.model),e.usage&&(t.inputTokens=e.usage.prompt_tokens??t.inputTokens,t.outputTokens=e.usage.completion_tokens??t.outputTokens),Array.isArray(e.choices)))for(let r of e.choices){let e=[];this.collectTextFromNode(r?.delta??r?.message?.content,e);for(let r of e)this.emitTextDelta(n,t,r);r?.delta?.tool_calls&&this.collectToolCalls(r.delta.tool_calls,t.toolCalls),r?.finish_reason&&(t.stopReason=this.mapFinishReason(r.finish_reason))}}emitPendingToolCalls(e,t){let n=Array.from(t.toolCalls.entries()).sort((e,t)=>e[0]-t[0]).map(([e,t])=>({index:e,id:t.id||`tool_${(0,p.ulid)()}`,name:t.name,arguments:t.argumentChunks.join(``)})).filter(e=>e.name&&!t.emittedToolCallIndexes.has(e.index));for(let r of n){this.stopOpenContentBlocks(e,t);let n=this.getNextToolBlockIndex(r.index);e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_start`,{type:`content_block_start`,index:n,content_block:{type:`tool_use`,id:r.id,name:r.name,input:{}}})+(r.arguments?this.formatSseEvent(`content_block_delta`,{type:`content_block_delta`,index:n,delta:{type:`input_json_delta`,partial_json:r.arguments}}):``)+this.formatSseEvent(`content_block_stop`,{type:`content_block_stop`,index:n}))),t.emittedToolCallIndexes.add(r.index)}}emitThinkingDelta(e,t,n){this.thinkingDisabled||!n||(this.ensureMessageStarted(e,t),t.textBlockStarted&&this.stopTextBlock(e,t),t.thinkingBlockStarted||=(e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_start`,{type:`content_block_start`,index:0,content_block:{type:`thinking`,thinking:``}}))),!0),e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_delta`,{type:`content_block_delta`,index:0,delta:{type:`thinking_delta`,thinking:n}}))),t.emittedThinking=!0,t.lastThinkingChunk=n)}emitTextDelta(e,t,n){n&&(this.ensureMessageStarted(e,t),t.textBlockStarted||=(t.thinkingBlockStarted&&this.stopThinkingBlock(e,t),e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_start`,{type:`content_block_start`,index:this.getTextBlockIndex(),content_block:{type:`text`,text:``}}))),!0),e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_delta`,{type:`content_block_delta`,index:this.getTextBlockIndex(),delta:{type:`text_delta`,text:n}}))),t.emittedText=!0,t.lastTextChunk=n)}emitStreamingError(e,t,n){if(!t.messageStarted){e.enqueue(t.encoder.encode(this.createClaudeErrorStream(n))),t.messageStarted=!0,t.messageStopped=!0;return}!t.textBlockStarted&&!t.thinkingBlockStarted&&this.emitTextDelta(e,t,n),t.stopReason=`error`,this.finishStreamingResponse(e,t)}finishStreamingResponse(e,t){if(t.messageStopped)return;this.stopOpenContentBlocks(e,t);let n={output_tokens:t.outputTokens??0};if(t.cachedTokens||t.reasoningTokens||t.reasoningEffort){n.metadata={};let e=n.metadata;t.cachedTokens&&(e.cached_tokens=t.cachedTokens),t.reasoningTokens&&(e.reasoning_tokens=t.reasoningTokens),t.reasoningEffort&&(e.reasoning_effort=t.reasoningEffort)}e.enqueue(t.encoder.encode(this.formatSseEvent(`message_delta`,{type:`message_delta`,delta:{stop_reason:t.stopReason||`end_turn`,stop_sequence:null},usage:n})+this.formatSseEvent(`message_stop`,{type:`message_stop`}))),t.messageStopped=!0}ensureMessageStarted(e,t){t.messageStarted||=(e.enqueue(t.encoder.encode(this.formatSseEvent(`message_start`,{type:`message_start`,message:{id:t.messageId,type:`message`,role:`assistant`,content:[],model:t.model,stop_reason:null,stop_sequence:null,usage:{input_tokens:t.inputTokens??0,output_tokens:0}}}))),!0)}stopOpenContentBlocks(e,t){this.stopTextBlock(e,t),this.stopThinkingBlock(e,t)}stopThinkingBlock(e,t){t.thinkingBlockStarted&&=(e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_stop`,{type:`content_block_stop`,index:0}))),!1)}stopTextBlock(e,t){t.textBlockStarted&&=(e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_stop`,{type:`content_block_stop`,index:this.getTextBlockIndex()}))),!1)}getTextBlockIndex(){return+!this.thinkingDisabled}getNextToolBlockIndex(e){return(this.thinkingDisabled?1:2)+e}};const we=new Set([`type`,`format`,`description`,`nullable`,`enum`,`items`,`maxItems`,`minItems`,`properties`,`required`,`propertyOrdering`,`maxProperties`,`minProperties`,`minimum`,`maximum`,`minLength`,`maxLength`,`pattern`,`example`,`anyOf`,`title`]);var Te=class{transform(e,t,n,r=!1){let i=this.toGeminiContents(e.messages),a=this.toSystemInstruction(e.system),o=this.toGeminiTools(e.tools);return{modelPath:this.toGeminiModelPath(t),body:{contents:i,system_instruction:a,tools:o,tool_config:{function_calling_config:{mode:o.length>0?`AUTO`:`NONE`}},generationConfig:{maxOutputTokens:this.resolveMaxOutputTokens(e.max_tokens,n,r)}}}}toGeminiModelPath(e){return e.startsWith(`models/`)?e:`models/${e}`}toSystemInstruction(e){let t=this.extractTextParts(e);if(t.length!==0)return{parts:t.map(e=>({text:e}))}}toGeminiContents(e){let t=[];for(let n of e){let e=this.toGeminiParts(n.content);e.length!==0&&t.push({role:n.role===`assistant`?`model`:`user`,parts:e})}return t}toGeminiParts(e){if(typeof e==`string`)return e.trim()?[{text:e}]:[];if(!Array.isArray(e))return[];let t=[];for(let n of e)n.type===`text`&&n.text&&t.push({text:n.text}),n.type===`tool_use`&&t.push({functionCall:{name:n.name,args:n.input}}),n.type===`tool_result`&&t.push({functionResponse:{name:n.tool_use_id,response:{content:typeof n.content==`string`?n.content:JSON.stringify(n.content??{})}}});return t}toGeminiTools(e){return!e||e.length===0?[]:[{function_declarations:e.map(e=>({name:e.name,description:e.description,parameters:this.toGeminiParameters(e.input_schema)}))}]}toGeminiParameters(e){if(e)return this.sanitizeGeminiSchema(e)}sanitizeGeminiSchema(e){if(Array.isArray(e))return e.map(e=>this.sanitizeGeminiSchema(e));if(!e||typeof e!=`object`)return e;let t=Object.entries(e).flatMap(([e,t])=>{if(!we.has(e))return[];if(e===`properties`&&t&&typeof t==`object`&&!Array.isArray(t)){let n=Object.entries(t).map(([e,t])=>[e,this.sanitizeGeminiSchema(t)]);return[[e,Object.fromEntries(n)]]}return[[e,this.sanitizeGeminiSchema(t)]]});return Object.fromEntries(t)}extractTextParts(e){return typeof e==`string`?e.trim()?[e]:[]:Array.isArray(e)?e.map(e=>typeof e==`string`?e:e&&typeof e==`object`&&`text`in e&&typeof e.text==`string`?e.text:null).filter(e=>!!e?.trim()):[]}resolveMaxOutputTokens(e,t,n){return typeof e==`number`&&e>0?e:n||t===`minimal`?2048:4096}};const T=`GEMINI_AUTH_CONFIG_ERROR`;var E=class extends Error{constructor(e,t){super(e),this.code=t,this.name=`GeminiAuthError`}},Ee=class{constructor(e=C,t=process.env){this.logger=e,this.env=t}async resolveHeaders(e){let t=e.authMode??`auto`;return t===`api-key`?this.resolveApiKeyHeaders(e):t===`oauth`?this.resolveOAuthHeaders():await this.tryResolveApiKeyHeaders(e)??this.resolveOAuthHeaders()}getGeminiDirectory(){let e=this.env.GEMINI_CLI_HOME||u.default.homedir();return d.default.join(e,`.gemini`)}getSettingsPath(){return d.default.join(this.getGeminiDirectory(),`settings.json`)}getOAuthPath(){return d.default.join(this.getGeminiDirectory(),`oauth_creds.json`)}async tryResolveApiKeyHeaders(e){try{return await this.resolveApiKeyHeaders(e)}catch{return null}}async resolveApiKeyHeaders(e){let t=e.apiKeyEnvVar??e.authTokenEnvVar??`GEMINI_API_KEY`,n=this.env[t]?.trim();if(!n)throw new E(`Missing Gemini API key. Set ${t}.`,T);return{headers:{"x-goog-api-key":n},authMode:`api-key`,authSource:`env`}}async resolveOAuthHeaders(){let e=await this.readSettings(),t=await this.readOAuthCredentials(),n=await this.refreshOAuthCredentialsIfNeeded(t),r=e?.security?.auth?.selectedType??null,i=n.access_token?.trim();if(!i)throw new E(`Missing Gemini OAuth credentials. Expected ${this.getOAuthPath()} or GEMINI_API_KEY.`,T);return{headers:{Authorization:`Bearer ${i}`},authMode:`oauth`,authSource:`gemini-home`,authType:r}}async readOAuthCredentials(){try{let e=await _.default.readFile(this.getOAuthPath(),`utf8`);return JSON.parse(e)}catch(e){throw this.logger.warn(`[GeminiAuth] Failed to read oauth credentials`,{oauthPath:this.getOAuthPath(),cause:e}),new E(`Unable to read Gemini OAuth credentials from ${this.getOAuthPath()}.`,T)}}async readSettings(){try{let e=await _.default.readFile(this.getSettingsPath(),`utf8`);return JSON.parse(e)}catch(e){return e?.code===`ENOENT`||this.logger.warn(`[GeminiAuth] Failed to read settings`,{settingsPath:this.getSettingsPath(),cause:e}),null}}async refreshOAuthCredentialsIfNeeded(e){if(!this.shouldRefreshCredentials(e))return e;let t=e.refresh_token?.trim();if(!t)return e;let n=new URLSearchParams({client_id:`681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com`,client_secret:`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`,grant_type:`refresh_token`,refresh_token:t}),r=await fetch(`https://oauth2.googleapis.com/token`,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:n});if(!r.ok)return this.logger.warn(`[GeminiAuth] Failed to refresh oauth credentials`,{status:r.status,oauthPath:this.getOAuthPath()}),e;let i=await r.json(),a={...e,access_token:i.access_token??e.access_token,token_type:i.token_type??e.token_type,scope:i.scope??e.scope,expiry_date:typeof i.expires_in==`number`?Date.now()+i.expires_in*1e3:e.expiry_date};return await _.default.writeFile(this.getOAuthPath(),`${JSON.stringify(a,null,2)}\n`,`utf8`),a}shouldRefreshCredentials(e){let t=e.access_token?.trim(),n=e.expiry_date;return t?typeof n==`number`?n<=Date.now()+6e4:!1:!0}};const D=`event: `,O=`data: `,De=`message_start`,Oe=`content_block_start`,ke=`content_block_delta`,Ae=`content_block_stop`,je=`message_delta`,Me=`message_stop`,Ne=`end_turn`;var Pe=class{transformBuffered(e,t){let n=JSON.parse(e);return this.toClaudeStream([n],t)}transformStreaming(e,t){let n=[];for(let t of e.split(`
|
|
15
15
|
|
|
16
16
|
`)){let e=t.split(`
|
|
17
|
-
`).find(e=>e.startsWith(
|
|
18
|
-
`)}mapStopReason(e){return e?e===`STOP`?
|
|
17
|
+
`).find(e=>e.startsWith(O))?.slice(6).trim();!e||e===`[DONE]`||n.push(JSON.parse(e))}return this.toClaudeStream(n,t)}toClaudeStream(e,t){let n=e.flatMap(e=>e.candidates??[]).flatMap(e=>e.content?.parts??[]).map(e=>`text`in e&&typeof e.text==`string`?e.text:``).join(``),r=e.map(e=>e.candidates?.[0]?.finishReason).find(e=>typeof e==`string`),i=e.find(e=>e.usageMetadata)?.usageMetadata,a=`msg_${(0,p.ulid)()}`,o=[];return o.push(`${D}${De}`),o.push(`${O}${JSON.stringify({type:De,message:{id:a,type:`message`,role:`assistant`,content:[],model:t,stop_reason:null,stop_sequence:null,usage:{input_tokens:i?.promptTokenCount??0,output_tokens:0}}})}`),o.push(``),o.push(`${D}${Oe}`),o.push(`${O}${JSON.stringify({type:Oe,index:0,content_block:{type:`text`,text:``}})}`),o.push(``),n&&(o.push(`${D}${ke}`),o.push(`${O}${JSON.stringify({type:ke,index:0,delta:{type:`text_delta`,text:n}})}`),o.push(``)),o.push(`${D}${Ae}`),o.push(`${O}${JSON.stringify({type:Ae,index:0})}`),o.push(``),o.push(`${D}${je}`),o.push(`${O}${JSON.stringify({type:je,delta:{stop_reason:this.mapStopReason(r),stop_sequence:null},usage:{output_tokens:i?.candidatesTokenCount??0}})}`),o.push(``),o.push(`${D}${Me}`),o.push(`${O}${JSON.stringify({type:Me})}`),o.push(``),o.join(`
|
|
18
|
+
`)}mapStopReason(e){return e?e===`STOP`?Ne:`tool_use`:Ne}};const k=`conversation_history`,A={appendFailed:`HISTORY_APPEND_FAILED`,listFailed:`HISTORY_LIST_FAILED`,clearFailed:`HISTORY_CLEAR_FAILED`,statsFailed:`HISTORY_STATS_FAILED`,initFailed:`HISTORY_INIT_FAILED`},Fe=l.z.string().transform(e=>Number(e)).refine(e=>Number.isInteger(e)&&e>0,`Cursor must be a positive integer`);var j=class extends Error{constructor(e,t,n){super(e,n),this.code=t,this.name=`ConversationHistoryServiceError`}},Ie=class{sqlite=null;constructor(e=process.env.MODEL_PROXY_MCP_DB_PATH||se,t=1e3,n=C){this.dbPath=e,this.retentionLimit=t,this.logger=n}async ensureInitialized(){await this.getDb()}async appendEntries(e){if(e.length!==0)try{let t=await this.getDb(),n=t.prepare(`
|
|
19
19
|
INSERT INTO conversation_history (
|
|
20
20
|
id, scope, request_id, direction, role, message_type, model, slot, payload_json, created_at
|
|
21
21
|
) VALUES (
|
|
22
22
|
@id, @scope, @request_id, @direction, @role, @message_type, @model, @slot, @payload_json, @created_at
|
|
23
23
|
)
|
|
24
|
-
`),r=t.transaction(e=>{for(let t of e)n.run({id:t.id??(0,p.ulid)(),scope:t.scope,request_id:t.requestId,direction:t.direction,role:t.role,message_type:t.messageType,model:t.model,slot:t.slot,payload_json:t.payloadJson,created_at:t.createdAt??new Date().toISOString()})}),i=new Map;for(let t of e){let e=i.get(t.scope)??[];e.push(t),i.set(t.scope,e)}for(let e of i.values())r(e);for(let e of i.keys())await this.pruneScope(e)}catch(e){throw this.logger.error(`[model-proxy-mcp] Failed to append history`,{code:
|
|
24
|
+
`),r=t.transaction(e=>{for(let t of e)n.run({id:t.id??(0,p.ulid)(),scope:t.scope,request_id:t.requestId,direction:t.direction,role:t.role,message_type:t.messageType,model:t.model,slot:t.slot,payload_json:t.payloadJson,created_at:t.createdAt??new Date().toISOString()})}),i=new Map;for(let t of e){let e=i.get(t.scope)??[];e.push(t),i.set(t.scope,e)}for(let e of i.values())r(e);for(let e of i.keys())await this.pruneScope(e)}catch(e){throw this.logger.error(`[model-proxy-mcp] Failed to append history`,{code:A.appendFailed,dbPath:this.dbPath,cause:e}),new j(`Failed to append conversation history`,A.appendFailed,{cause:e})}}async listHistory(e,t=50,n){try{let r=await this.getDb(),i=n?Fe.parse(n):void 0,a=Math.max(1,Math.min(t,200)),o=i?r.prepare(`
|
|
25
25
|
SELECT sequence, id, scope, request_id, direction, role, message_type, model, slot, payload_json, created_at
|
|
26
|
-
FROM ${
|
|
26
|
+
FROM ${k}
|
|
27
27
|
WHERE scope = ? AND sequence < ?
|
|
28
28
|
ORDER BY sequence DESC
|
|
29
29
|
LIMIT ?
|
|
30
30
|
`).all(e,i,a+1):r.prepare(`
|
|
31
31
|
SELECT sequence, id, scope, request_id, direction, role, message_type, model, slot, payload_json, created_at
|
|
32
|
-
FROM ${
|
|
32
|
+
FROM ${k}
|
|
33
33
|
WHERE scope = ?
|
|
34
34
|
ORDER BY sequence DESC
|
|
35
35
|
LIMIT ?
|
|
36
|
-
`).all(e,a+1),s=Number(r.prepare(`SELECT COUNT(*) as count FROM ${
|
|
36
|
+
`).all(e,a+1),s=Number(r.prepare(`SELECT COUNT(*) as count FROM ${k} WHERE scope = ?`).get(e).count),c=o.length>a?o.pop():void 0;return{items:o.map(e=>this.toEntry(e)),nextCursor:c?String(c.sequence):null,total:s}}catch(t){throw this.logger.error(`[model-proxy-mcp] Failed to list history`,{code:A.listFailed,scope:e,cause:t}),new j(`Failed to list conversation history`,A.listFailed,{cause:t})}}async clearScope(e){try{return(await this.getDb()).prepare(`DELETE FROM ${k} WHERE scope = ?`).run(e).changes}catch(t){throw this.logger.error(`[model-proxy-mcp] Failed to clear history`,{code:A.clearFailed,scope:e,cause:t}),new j(`Failed to clear conversation history`,A.clearFailed,{cause:t})}}async getStats(e){try{let t=(await this.getDb()).prepare(`
|
|
37
37
|
SELECT COUNT(*) as count, MIN(created_at) as oldest, MAX(created_at) as newest
|
|
38
|
-
FROM ${
|
|
38
|
+
FROM ${k}
|
|
39
39
|
WHERE scope = ?
|
|
40
|
-
`).get(e);return{scope:e,retentionLimit:this.retentionLimit,totalMessages:Number(t.count),oldestCreatedAt:t.oldest,newestCreatedAt:t.newest}}catch(t){throw this.logger.error(`[model-proxy-mcp] Failed to read history stats`,{code:
|
|
41
|
-
DELETE FROM ${
|
|
40
|
+
`).get(e);return{scope:e,retentionLimit:this.retentionLimit,totalMessages:Number(t.count),oldestCreatedAt:t.oldest,newestCreatedAt:t.newest}}catch(t){throw this.logger.error(`[model-proxy-mcp] Failed to read history stats`,{code:A.statsFailed,scope:e,cause:t}),new j(`Failed to read history stats`,A.statsFailed,{cause:t})}}async listScopes(){try{return(await this.getDb()).prepare(`SELECT DISTINCT scope FROM ${k} ORDER BY scope ASC`).all().map(e=>e.scope)}catch(e){return this.logger.error(`[model-proxy-mcp] Failed to list history scopes`,{code:A.listFailed,cause:e}),[]}}async pruneScope(e){let t=await this.getDb(),n=t.prepare(`SELECT COUNT(*) as count FROM ${k} WHERE scope = ?`).get(e),r=Number(n.count)-this.retentionLimit;r<=0||t.prepare(`
|
|
41
|
+
DELETE FROM ${k}
|
|
42
42
|
WHERE sequence IN (
|
|
43
43
|
SELECT sequence
|
|
44
|
-
FROM ${
|
|
44
|
+
FROM ${k}
|
|
45
45
|
WHERE scope = ?
|
|
46
46
|
ORDER BY sequence ASC
|
|
47
47
|
LIMIT ?
|
|
48
48
|
)
|
|
49
|
-
`).run(e,r)}async getDb(){if(this.sqlite)return this.sqlite;try{return await
|
|
50
|
-
CREATE TABLE IF NOT EXISTS ${
|
|
49
|
+
`).run(e,r)}async getDb(){if(this.sqlite)return this.sqlite;try{return await _.default.mkdir(d.default.dirname(this.dbPath),{recursive:!0}),this.sqlite=new ee.default(this.dbPath),this.sqlite.pragma(`journal_mode = WAL`),this.sqlite.exec(`
|
|
50
|
+
CREATE TABLE IF NOT EXISTS ${k} (
|
|
51
51
|
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
52
52
|
id TEXT NOT NULL UNIQUE,
|
|
53
53
|
scope TEXT NOT NULL,
|
|
@@ -60,14 +60,14 @@ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=
|
|
|
60
60
|
payload_json TEXT NOT NULL,
|
|
61
61
|
created_at TEXT NOT NULL
|
|
62
62
|
)
|
|
63
|
-
`),this.sqlite.exec(`CREATE INDEX IF NOT EXISTS conversation_history_scope_sequence_idx ON ${O} (scope, sequence DESC)`),this.sqlite.exec(`CREATE INDEX IF NOT EXISTS conversation_history_scope_request_idx ON ${O} (scope, request_id)`),this.sqlite}catch(e){throw this.logger.error(`[model-proxy-mcp] Failed to initialize history database`,{code:k.initFailed,dbPath:this.dbPath,cause:e}),new A(`Failed to initialize history database`,k.initFailed,{cause:e})}}toEntry(e){return{id:e.id,scope:e.scope,requestId:e.request_id,direction:e.direction,role:e.role,messageType:e.message_type,model:e.model,slot:e.slot,payloadJson:e.payload_json,createdAt:e.created_at}}};const j=`default`,Ne=`zai-anthropic-compat`,Pe=`.yaml`,M=`medium`,N=[`default`,`sonnet`,`opus`,`haiku`,`subagent`],P=`[model-proxy-mcp]`,F=`utf8`,Fe=[`chatgpt-codex`,`anthropic-compatible`,`gemini-direct`],I={profileNotFound:`PROFILE_NOT_FOUND`,settingsReadFailed:`SETTINGS_READ_FAILED`,settingsWriteFailed:`SETTINGS_WRITE_FAILED`},Ie=l.z.enum([`minimal`,`low`,`medium`,`high`]),Le=l.z.object({type:l.z.enum(Fe),endpoint:l.z.url(),authTokenEnvVar:l.z.string().min(1).nullable().optional(),apiTimeoutMs:l.z.number().int().positive().nullable().optional(),authMode:l.z.enum([`auto`,`api-key`,`oauth`]).nullable().optional(),apiKeyEnvVar:l.z.string().min(1).nullable().optional(),project:l.z.string().min(1).nullable().optional(),location:l.z.string().min(1).nullable().optional(),apiVersion:l.z.string().min(1).nullable().optional()}),Re=l.z.object({providers:l.z.record(l.z.string().min(1),Le)}),L=l.z.object({id:l.z.string().min(1),label:l.z.string().min(1),provider:l.z.string().min(1),model:l.z.string().min(1),reasoningEffort:Ie.default(M),enabled:l.z.boolean().default(!0)}),ze=l.z.object({provider:l.z.string().min(1),model:l.z.string().min(1),reasoningEffort:Ie.default(M),thinkingDisabled:l.z.boolean().optional()}),R=l.z.object({main:ze,fallbacks:l.z.array(ze).default([])}),z=l.z.object({models:l.z.object({default:R.optional(),sonnet:R.optional(),opus:R.optional(),haiku:R.optional(),subagent:R.optional()}).default({})}),Be=l.z.array(L),Ve=l.z.object({models:l.z.object({default:R.nullable().optional(),sonnet:R.nullable().optional(),opus:R.nullable().optional(),haiku:R.nullable().optional(),subagent:R.nullable().optional()}).optional()});var B=class extends Error{constructor(e,t,n){super(e,n),this.code=t,this.name=`ProfileStoreError`}},He=class{constructor(e=process.env.MODEL_PROXY_MCP_PROVIDER_PATH||b,t=process.env.MODEL_PROXY_MCP_MODEL_LIST_PATH||d.default.join(d.default.dirname(process.env.MODEL_PROXY_MCP_PROVIDER_PATH||b),d.default.basename(re)),n=process.env.MODEL_PROXY_MCP_SCOPE_DIR||d.default.join(d.default.dirname(process.env.MODEL_PROXY_MCP_PROVIDER_PATH||b),d.default.basename(ie)),r=C){this.providerConfigPath=e,this.modelListPath=t,this.scopeSettingsDir=n,this.logger=r}getConfigPath(e=j){return this.getScopeConfigPath(e)}async ensureConfig(e=j,t){return this.getConfig(e,t)}async listScopes(){try{let e=await g.default.readdir(this.scopeSettingsDir,{withFileTypes:!0}),t=new Set([j]);for(let n of e){if(!n.isFile()||!n.name.endsWith(Pe))continue;let e=n.name.slice(0,-5);e&&t.add(e)}return Array.from(t).sort()}catch(e){if(this.isFileNotFoundError(e))return[j];throw this.logger.error(`${P} Failed to list scopes`,{scopeSettingsDir:this.scopeSettingsDir,cause:e}),new B(`Failed to list scopes in ${this.scopeSettingsDir}`,I.settingsReadFailed,{cause:e})}}async getConfig(e=j,t){return this.toProxyConfig(await this.getSettings(e,t))}async getAdminConfig(e=j){let t=await this.getSettings(e),n=this.toProfiles(t),r=Object.fromEntries(N.map(e=>[e,this.resolveSlotConfig(t,n,e)]));return{scope:e,providerConfigPath:this.providerConfigPath,modelListPath:this.modelListPath,scopeConfigPath:this.getScopeConfigPath(e),providers:t.providers,models:n,scopeModels:t.scope.models,slots:r}}async listProfiles(e=j){return(await this.getAdminConfig(e)).models}async getActiveProfile(e=j,t=j){let n=await this.getAdminConfig(e),r=n.slots[t];return n.models.find(e=>e.id===r.profileId&&e.enabled)??null}async getResolvedSlotConfig(e=j,t=j){return(await this.getAdminConfig(e)).slots[t]}async setActiveProfile(e,t=j,n=j){let r=await this.getSettings(t),i=this.toProfiles(r).find(t=>t.id===e&&t.enabled);if(!i)throw new B(`Profile not found or disabled: ${e}`,I.profileNotFound,{cause:{profileId:e,scope:t,slot:n}});return this.updateConfig({models:{[n]:{main:{provider:i.provider,model:i.model,reasoningEffort:i.reasoningEffort,thinkingDisabled:r.scope.models[n]?.main.thinkingDisabled??!1},fallbacks:r.scope.models[n]?.fallbacks??[]}}},t)}async upsertProfile(e,t=j){let n=L.parse({id:e.id,label:e.label,provider:e.provider,model:e.model,reasoningEffort:e.reasoningEffort,enabled:e.enabled}),r=await this.getSettings(t),i=r.models.filter(e=>e.id!==n.id);i.push(n);let a=this.normalizeSettings({providers:r.providers,models:i,scope:r.scope});return await this.saveSettings(a,t),this.toProxyConfig(a)}async updateConfig(e,t=j){let n=Ve.parse(e),r=await this.getSettings(t),i={...r.scope.models};for(let e of N){let t=n.models?.[e];if(t!==void 0){if(t===null){delete i[e];continue}i[e]=t}}let a=this.normalizeSettings({providers:r.providers,models:r.models,scope:{models:i}});return await this.saveSettings(a,t),this.toProxyConfig(a)}getScopeConfigPath(e){return d.default.join(this.scopeSettingsDir,`${this.sanitizeScope(e)}${Pe}`)}sanitizeScope(e){return e.replace(/[^a-zA-Z0-9._-]/g,`-`).replace(/--+/g,`-`).replace(/^-|-$/g,``)||j}async getSettings(e,t){let n=await this.readSettings(e,t),r=this.normalizeSettings(n);return JSON.stringify(r)!==JSON.stringify(n)&&await this.saveSettings(r,e),r}async readSettings(e,t){let n=this.getScopeConfigPath(e),[r,i,a]=await Promise.allSettled([g.default.readFile(this.providerConfigPath,F),g.default.readFile(this.modelListPath,F),g.default.readFile(n,F)]);try{let n=r.status===`fulfilled`?r.value:null,o=i.status===`fulfilled`?i.value:null,s=a.status===`fulfilled`?a.value:null,c=r.status===`rejected`&&this.isFileNotFoundError(r.reason),l=i.status===`rejected`&&this.isFileNotFoundError(i.reason),u=a.status===`rejected`&&this.isFileNotFoundError(a.reason);if(!n&&r.status===`rejected`&&!c)throw r.reason;if(!o&&i.status===`rejected`&&!l)throw i.reason;if(!s&&a.status===`rejected`&&!u)throw a.reason;let d=s?z.parse((0,v.parse)(s)):await this.loadScopeSeedSettings(e,t),f=this.normalizeSettings({providers:n?Re.parse((0,v.parse)(n)).providers:structuredClone(ce),models:o?Be.parse((0,v.parse)(o)):structuredClone(le),scope:d});return(c||l||u)&&await this.saveSettings(f,e),f}catch(t){throw this.logger.error(`${P} Failed to read settings`,{scope:e,providerConfigPath:this.providerConfigPath,modelListPath:this.modelListPath,scopeConfigPath:n,code:I.settingsReadFailed,cause:t}),new B(`Failed to read settings from ${this.providerConfigPath}, ${this.modelListPath}, and ${n}`,I.settingsReadFailed,{cause:t})}}isFileNotFoundError(e){return e?.code===`ENOENT`}async loadScopeSeedSettings(e,t){let n=[t,this.getDefaultScopeSeedPath(e)].filter(e=>!!e);for(let t of n)try{let e=await g.default.readFile(t,F);return z.parse((0,v.parse)(e))}catch(n){if(this.isFileNotFoundError(n))continue;throw this.logger.error(`${P} Failed to read scope seed settings`,{scope:e,seedConfigPath:t,cause:n}),n}return structuredClone(ue)}getDefaultScopeSeedPath(e){let t=this.getScopeConfigPath(j);return e===j?null:t}normalizeSettings(e){let t=this.normalizeProviders(Object.keys(e.providers).length>0?e.providers:structuredClone(ce)),n=e.models.map(e=>L.parse(e)),r=this.normalizeSlotSelection(e.scope.models.default?.main??null,n,t,this.getDefaultSelection(n,t)),i={default:r?{main:r,fallbacks:this.normalizeFallbacks(e.scope.models.default?.fallbacks??[],r,n,t)}:void 0};for(let a of N){if(a===j)continue;let o=r??this.getDefaultSelection(n,t),s=this.normalizeSlotSelection(e.scope.models[a]?.main??null,n,t,o);i[a]=s?{main:s,fallbacks:this.normalizeFallbacks(e.scope.models[a]?.fallbacks??[],s,n,t)}:void 0}return{providers:t,models:n,scope:{models:i}}}normalizeProviders(e){let t={...e},n=t[Ne];return n?.type===`anthropic-compatible`&&n.authTokenEnvVar===`ANTHROPIC_AUTH_TOKEN`&&(t[Ne]={...n,authTokenEnvVar:`ZAI_ANTHROPIC_AUTH_TOKEN`}),t}getDefaultSelection(e,t){let n=e.find(e=>e.enabled&&t[e.provider]);return n?{provider:n.provider,model:n.model,reasoningEffort:n.reasoningEffort,thinkingDisabled:!1}:null}normalizeSlotSelection(e,t,n,r){if(!e)return r?{...r}:null;let i=n[e.provider]?e.provider:r?.provider??null;if(!i)return null;if(i!==e.provider)return r&&n[r.provider]?{...r}:null;let a=t.find(t=>t.enabled&&t.provider===i&&t.model===e.model);return{provider:i,model:a?.model??e.model,reasoningEffort:e.reasoningEffort??a?.reasoningEffort??M,thinkingDisabled:e.thinkingDisabled??!1}}normalizeFallbacks(e,t,n,r){let i=[],a=new Set([`${t.provider}:${t.model}:${t.reasoningEffort}:${t.thinkingDisabled?`off`:`on`}`]);for(let t of e){let e=this.normalizeSlotSelection(t,n,r,null);if(!e)continue;let o=`${e.provider}:${e.model}:${e.reasoningEffort}:${e.thinkingDisabled?`off`:`on`}`;a.has(o)||(a.add(o),i.push(e))}return i}toProxyConfig(e){let t=this.toProfiles(e),n=Object.fromEntries(N.map(n=>[n,this.resolveSlotConfig(e,t,n).profileId]));return{activeProfileId:n.default??null,slotProfileIds:n,providers:e.providers,profiles:t,scope:e.scope}}resolveSlotConfig(e,t,n){let r=e.scope.models[n]??e.scope.models.default,i=r?.main??null,a=i?t.find(e=>e.provider===i.provider&&e.model===i.model&&e.enabled):void 0,o=i?.provider?e.providers[i.provider]:void 0;return{slot:n,profileId:a?.id??null,label:a?.label??null,provider:i?.provider??null,providerType:o?.type??null,endpoint:o?.endpoint??null,model:i?.model??null,reasoningEffort:i?.reasoningEffort??a?.reasoningEffort??M,thinkingDisabled:i?.thinkingDisabled??!1,fallbacks:r?.fallbacks??[]}}toProfiles(e){return e.models.map(t=>({id:t.id,label:t.label,provider:t.provider,providerType:e.providers[t.provider]?.type??null,model:t.model,endpoint:e.providers[t.provider]?.endpoint??null,reasoningEffort:t.reasoningEffort,enabled:t.enabled}))}async saveSettings(e,t=j){let n=this.getScopeConfigPath(t);try{let t=new Set([d.default.dirname(this.providerConfigPath),d.default.dirname(this.modelListPath),d.default.dirname(n)]);await Promise.all(Array.from(t,e=>g.default.mkdir(e,{recursive:!0}))),await Promise.all([g.default.writeFile(this.providerConfigPath,(0,v.stringify)(Re.parse({providers:e.providers}),{indent:2}),F),g.default.writeFile(this.modelListPath,(0,v.stringify)(Be.parse(e.models),{indent:2}),F),g.default.writeFile(n,(0,v.stringify)(z.parse(e.scope),{indent:2}),F)])}catch(e){throw this.logger.error(`${P} Failed to write settings`,{scope:t,providerConfigPath:this.providerConfigPath,modelListPath:this.modelListPath,scopeConfigPath:n,code:I.settingsWriteFailed,cause:e}),new B(`Failed to write settings to ${this.providerConfigPath}, ${this.modelListPath}, and ${n}`,I.settingsWriteFailed,{cause:e})}}};const Ue={"ccproxy-default":`default`,"ccproxy-sonnet":`sonnet`,"ccproxy-opus":`opus`,"ccproxy-haiku":`haiku`,"ccproxy-subagent":`subagent`},V=`default`,H=`default`,We=[`default`,`sonnet`,`opus`,`haiku`,`subagent`],Ge=`https://api.anthropic.com/v1/messages`,Ke=`anthropic-compatible`,qe=`ANTHROPIC_AUTH_TOKEN`,U=`application/json`,Je=`text/event-stream; charset=utf-8`,W=`content-type`,Ye=`cache-control`,Xe=`connection`,G=`accept`,Ze=`anthropic-version`,Qe=`anthropic-beta`,$e=`no-cache`,et=`keep-alive`,tt=`request`,nt=`response`,rt=`system`,it=`assistant`,K=`MODEL_NOT_FOUND`,at=`REQUEST_FORWARD_FAILED`,ot=`generateContent`,st=l.z.object({model:l.z.string().optional(),system:l.z.unknown().optional(),messages:l.z.array(l.z.unknown()).default([]),tools:l.z.array(l.z.unknown()).optional(),max_tokens:l.z.number().int().positive().optional(),stream:l.z.boolean().optional()});var q=class extends Error{constructor(e,t,n,r){super(e,r),this.code=t,this.status=n,this.name=new.target.name}},ct=class extends q{},J=class extends q{},Y=class extends q{},X=class extends q{constructor(e,t,n,r,i){super(e,at,t,i),this.upstreamBody=n,this.attemptLabel=r}};const lt=new Set([408,429,500,502,503,504]);var ut=class{codeAssistProjectCache=new Map;constructor(e=new He,t=new ge(C,oe),n=C,r=fetch,i=new Me){this.profileStore=e,this.codexAuth=t,this.logger=n,this.fetchImpl=r,this.historyService=i}async listProfiles(e=V){return this.profileStore.listProfiles(e)}async ensureConfig(e=V){return await this.historyService.ensureInitialized(),this.profileStore.ensureConfig(e)}async getAdminConfig(e=V){return this.profileStore.getAdminConfig(e)}async updateAdminConfig(e,t=V){return await this.profileStore.updateConfig(e,t),this.profileStore.getAdminConfig(t)}async getActiveProfile(e=V,t=H){return this.profileStore.getActiveProfile(e,t)}async upsertProfile(e,t=V){return this.profileStore.upsertProfile(e,t)}async setActiveProfile(e,t=V,n=H){return this.profileStore.setActiveProfile(e,t,n)}async getCurrentModel(e=V,t=H){return this.profileStore.getActiveProfile(e,t)}async switchModel(e,t=V,n=H){let r=await this.profileStore.listProfiles(t),i=await this.profileStore.getResolvedSlotConfig(t,n),a=r.find(t=>t.model===e&&t.provider===i.provider&&t.enabled)??r.find(t=>t.model===e&&t.enabled);if(!a)throw new J(`Model not found or disabled: ${e}`,K,400,{cause:{model:e,scope:t,slot:n}});return this.profileStore.updateConfig({models:{[n]:{main:{provider:a.provider,model:a.model,reasoningEffort:a.reasoningEffort},fallbacks:(await this.profileStore.getAdminConfig(t)).scopeModels[n]?.fallbacks??[]}}},t)}async listHistory(e=V,t=50,n){return this.historyService.listHistory(e,t,n)}async clearHistory(e=V){return{deleted:await this.historyService.clearScope(e)}}async listScopes(){let[e,t]=await Promise.all([this.profileStore.listScopes(),this.historyService.listScopes()]);return Array.from(new Set([...e,...t,V])).sort()}async getHistoryStats(e=V){return this.historyService.getStats(e)}async getStatus(e=V,t,n){let r=await this.profileStore.getConfig(e),i=await this.profileStore.getResolvedSlotConfig(e,H),a=await this.codexAuth.getAuthStatus(),o=Object.fromEntries(await Promise.all(We.map(async t=>{let n=await this.profileStore.getResolvedSlotConfig(e,t);return[t,{profileId:n.profileId,provider:n.provider,model:n.model,reasoningEffort:n.reasoningEffort,thinkingDisabled:n.thinkingDisabled??!1}]})));return{running:!0,port:t,pid:n,scope:e,activeProfileId:r.activeProfileId,activeModel:i.model??void 0,activeReasoningEffort:i.reasoningEffort,slotModels:o,auth:a,profiles:r.profiles}}async getModels(e=V){return{object:`list`,data:(await this.profileStore.listProfiles(e)).map(e=>({id:e.model,object:`model`,created:0,owned_by:`${e.provider}:${e.id}`,metadata:{profileId:e.id,reasoningEffort:e.reasoningEffort}}))}}async forwardClaudeRequest(e,t=V,n=new Headers){let r=(0,p.ulid)(),i=H,a=null;try{let o=this.parseClaudeRequest(e),s=await this.profileStore.getAdminConfig(t);if(i=this.resolveSlot(o.model,s),a=this.resolveRequestedModel(o.model,s,i),!a.model){let n=`No active model configured for scope '${t}' and slot '${i}'`;return await this.recordError(t,i,a,r,n,e),this.createErrorResponse(400,n)}let c=this.buildAttemptTargets(s,a,i);if(c.length===0){let n=`No provider configured for scope '${t}' and slot '${i}'`;return await this.recordError(t,i,a,r,n,e),this.createErrorResponse(400,n)}await this.recordRequest(t,i,c[0].resolved,r,o,e);let l=null;for(let a of c){let s=await this.forwardSingleAttempt(a,o,e,t,i,n);if(s.ok)return await this.recordResponse(t,i,a.resolved,r,s.success.history.upstreamText,s.success.history.claudeBody),s.success.response;let{error:c,payloadForHistory:u}=s.failure;if(!this.isRecoverableUpstreamError(c))return this.logger.error(`[model-proxy-mcp] Non-recoverable model attempt failed`,{scope:t,slot:i,model:a.resolved.model,code:c.code,status:c.status,message:c.message}),await this.recordError(t,i,a.resolved,r,c.message,u),this.createErrorResponse(c.status,c.message);l=s.failure,this.logger.warn(`[model-proxy-mcp] Recoverable model attempt failed; trying fallback`,{scope:t,slot:i,model:a.resolved.model,code:c.code,status:c.status,message:c.message}),await this.recordError(t,i,a.resolved,r,`Attempt failed for ${a.label}: ${c.message}`,u)}if(l)return await this.recordError(t,i,a,r,l.error.message,l.payloadForHistory),this.createErrorResponse(l.error.status,l.error.message);let u=`No model attempt could be executed`;return await this.recordError(t,i,a,r,u,e),this.createErrorResponse(500,u)}catch(n){let o=n instanceof q?n:new q(`Failed to process Claude proxy request`,at,500,{cause:n});return this.logger.error(`[model-proxy-mcp] Request forwarding failed`,{scope:t,slot:i,code:o.code,message:o.message,cause:o.cause}),await this.recordError(t,i,a,r,o.message,e),this.createErrorResponse(o.status,o.message)}}buildAttemptTargets(e,t,n){let r=[],i=new Set,a=[t,...t.fallbacks.map(e=>({...t,...e,slot:n}))];for(let t of a){let a=t.provider,o=t.model;if(!a||!o)continue;let s=e.providers[a];if(!s)continue;let c=`${a}:${o}:${t.reasoningEffort}:${t.thinkingDisabled?`off`:`on`}`;i.has(c)||(i.add(c),r.push({resolved:{...t,slot:n,provider:a,model:o,providerType:s.type,endpoint:s.endpoint,reasoningEffort:t.reasoningEffort,thinkingDisabled:t.thinkingDisabled??!1,fallbacks:[]},provider:s,label:`${a}/${o}`}))}return r}async forwardSingleAttempt(e,t,n,r,i,a){try{return e.resolved.providerType===Ke?{ok:!0,success:await this.forwardAnthropicCompatibleRequest(n,r,i,e.resolved,e.provider,a)}:e.resolved.providerType===`gemini-direct`?{ok:!0,success:await this.forwardGeminiDirectRequest(t,r,i,e.resolved,e.provider)}:{ok:!0,success:await this.forwardCodexRequest(n,r,i,e.resolved,e.provider)}}catch(e){return{ok:!1,failure:{error:e instanceof q?e:new q(`Failed to process Claude proxy request`,at,500,{cause:e}),payloadForHistory:e instanceof X?e.upstreamBody:n}}}}isRecoverableUpstreamError(e){return e instanceof X&<.has(e.status)}createCodexSessionId(e,t,n){let r=[x,`codex`,e,t,n.provider??`unknown-provider`,n.model??`unknown-model`].join(`:`);return(0,f.createHash)(`sha256`).update(r).digest(`hex`)}createGeminiSessionId(e,t,n){let r=[x,`gemini-code-assist`,e,t,n.provider??`unknown-provider`,n.model??`unknown-model`].join(`:`);return(0,f.createHash)(`sha256`).update(r).digest(`hex`)}createCodeAssistProjectCacheKey(e){let t=process.env.GOOGLE_CLOUD_PROJECT||process.env.GOOGLE_CLOUD_PROJECT_ID||``,n=Object.entries(e.headers).sort(([e],[t])=>e.localeCompare(t));return JSON.stringify({authMode:e.authMode,authSource:e.authSource,authType:e.authType??null,configuredProject:t,headers:n})}async forwardCodexRequest(e,t,n,r,i){let a=r.model;if(!a)throw new J(`No active model configured for slot '${n}'`,K,400);let o=await this.codexAuth.resolveAuth(),s=i.apiKeyEnvVar?process.env[i.apiKeyEnvVar]:void 0;if(!o.accessToken&&!s)throw new Y(`Missing Codex auth. Sign in with the official Codex CLI first.`,`AUTH_MISSING`,401);let c=new he({toProvider:`chatgpt-codex`,toModel:a,toEndpoint:r.endpoint??Ge,toApiKey:s,sessionId:this.createCodexSessionId(t,n,r),sessionReasoningEffort:r.reasoningEffort,thinkingDisabled:r.thinkingDisabled??!1,logger:this.logger,resolvedAuth:o}),l=new ve(this.logger,r.thinkingDisabled??!1),u=await c.transform(Ge,e),d=await this.fetchImpl(u.url,{method:`POST`,headers:u.headers,body:u.body});if(!d.ok){let e=await d.text();throw this.logger.error(`[model-proxy-mcp] Upstream request failed`,{scope:t,slot:n,profileId:r.profileId,model:r.model,status:d.status,body:e}),new X(e||`Codex upstream request failed`,d.status,e,`${r.provider}/${r.model}`)}let f=await d.text(),p=l.transform(f);return{response:{status:200,body:p,headers:this.createSuccessHeaders(t,n)},history:{upstreamText:f,claudeBody:p}}}async forwardGeminiDirectRequest(e,t,n,r,i){try{let a=r.model;if(!a)throw new J(`No active Gemini model configured for scope '${t}' and slot '${n}'`,K,400);let o=await new Se(this.logger).resolveHeaders(i),s=new be,c=new Ae,l={model:e.model,system:e.system,messages:e.messages,tools:e.tools,max_tokens:e.max_tokens},u=s.transform(l,a,r.reasoningEffort,r.thinkingDisabled??!1);if(o.authType===`oauth-personal`)return await this.forwardGeminiCodeAssistRequest(u.body,t,n,r,o,c);let d=this.createGeminiRequestUrl(i,u.modelPath,ot),f=new Headers(o.headers);f.set(W,U),f.set(G,U);let p=i.apiTimeoutMs??this.resolveUpstreamTimeoutMs(),m=await this.fetchImpl(d,{method:`POST`,headers:f,body:JSON.stringify(u.body),signal:p?AbortSignal.timeout(p):void 0}),h=await m.text();if(!m.ok)throw this.logger.error(`[model-proxy-mcp] Gemini upstream request failed`,{scope:t,slot:n,profileId:r.profileId,model:r.model,status:m.status,body:h,authMode:o.authMode,authSource:o.authSource}),new X(h||`Gemini upstream request failed`,m.status,h,`${r.provider}/${r.model}`);let g=c.transformBuffered(h,a);return{response:{status:200,body:g,headers:this.createSuccessHeaders(t,n)},history:{upstreamText:h,claudeBody:g}}}catch(e){throw e instanceof T?new Y(e.message,e.code,401,{cause:e}):e}}async forwardGeminiCodeAssistRequest(e,t,n,r,i,a){let o=await this.resolveCodeAssistProjectId(i),s=this.createCodeAssistUrl(ot),c=new Headers(i.headers);c.set(W,U),c.set(G,U);let l={model:r.model,project:o,user_prompt_id:(0,p.ulid)(),request:this.toCodeAssistGenerateContentRequest(e,this.createGeminiSessionId(t,n,r))},u=await this.fetchImpl(s,{method:`POST`,headers:c,body:JSON.stringify(l)}),d=await u.text();if(!u.ok)throw this.logger.error(`[model-proxy-mcp] Gemini Code Assist request failed`,{scope:t,slot:n,profileId:r.profileId,model:r.model,status:u.status,body:d,authMode:i.authMode,authSource:i.authSource,authType:i.authType}),new X(d||`Gemini Code Assist request failed`,u.status,d,`${r.provider}/${r.model}`);let f=this.normalizeCodeAssistBufferedResponse(d),m=a.transformBuffered(f,r.model??`gemini`);return{response:{status:200,body:m,headers:this.createSuccessHeaders(t,n)},history:{upstreamText:d,claudeBody:m}}}createSuccessHeaders(e,t){return{[W]:Je,[Ye]:$e,[Xe]:et,"x-model-proxy-service":x,"x-model-proxy-scope":e,"x-model-proxy-slot":t}}createGeminiRequestUrl(e,t,n){let r=e.endpoint.replace(/\/$/,``),i=e.apiVersion??`v1beta`;return new URL(`${r}/${i}/${t}:${n}`).toString()}createCodeAssistUrl(e){return new URL(`https://cloudcode-pa.googleapis.com/v1internal:${e}`).toString()}async resolveCodeAssistProjectId(e){let t=this.createCodeAssistProjectCacheKey(e),n=this.codeAssistProjectCache.get(t);if(n)return n;let r=new Headers(e.headers);r.set(W,U);let i=process.env.GOOGLE_CLOUD_PROJECT||process.env.GOOGLE_CLOUD_PROJECT_ID||void 0,a=await this.fetchImpl(this.createCodeAssistUrl(`loadCodeAssist`),{method:`POST`,headers:r,body:JSON.stringify({cloudaicompanionProject:i,metadata:{ideType:`IDE_UNSPECIFIED`,platform:`PLATFORM_UNSPECIFIED`,pluginType:`GEMINI`,duetProject:i}})}),o=await a.text();if(!a.ok)throw new X(o||`Failed to load Gemini Code Assist project context`,a.status,o,`google-gemini-direct/loadCodeAssist`);let s=JSON.parse(o).cloudaicompanionProject||i;if(!s)throw new Y(`Gemini Code Assist did not return a usable project ID.`,`GEMINI_PROJECT_MISSING`,401);return this.codeAssistProjectCache.set(t,s),s}toCodeAssistGenerateContentRequest(e,t){return{contents:e.contents,systemInstruction:e.system_instruction,tools:e.tools,toolConfig:e.tool_config,generationConfig:e.generationConfig,session_id:t}}normalizeCodeAssistBufferedResponse(e){let t=JSON.parse(e);return JSON.stringify(this.extractCodeAssistResponse(t))}normalizeCodeAssistStreamingResponse(e){let t=[];for(let n of e.split(`
|
|
63
|
+
`),this.sqlite.exec(`CREATE INDEX IF NOT EXISTS conversation_history_scope_sequence_idx ON ${k} (scope, sequence DESC)`),this.sqlite.exec(`CREATE INDEX IF NOT EXISTS conversation_history_scope_request_idx ON ${k} (scope, request_id)`),this.sqlite}catch(e){throw this.logger.error(`[model-proxy-mcp] Failed to initialize history database`,{code:A.initFailed,dbPath:this.dbPath,cause:e}),new j(`Failed to initialize history database`,A.initFailed,{cause:e})}}toEntry(e){return{id:e.id,scope:e.scope,requestId:e.request_id,direction:e.direction,role:e.role,messageType:e.message_type,model:e.model,slot:e.slot,payloadJson:e.payload_json,createdAt:e.created_at}}};const M=`default`,Le=`zai-anthropic-compat`,Re=`.yaml`,N=`medium`,P=[`default`,`sonnet`,`opus`,`haiku`,`subagent`],F=`[model-proxy-mcp]`,I=`utf8`,ze=[`chatgpt-codex`,`anthropic-compatible`,`gemini-direct`],L={profileNotFound:`PROFILE_NOT_FOUND`,settingsReadFailed:`SETTINGS_READ_FAILED`,settingsWriteFailed:`SETTINGS_WRITE_FAILED`},Be=l.z.enum([`minimal`,`low`,`medium`,`high`]),Ve=l.z.object({type:l.z.enum(ze),endpoint:l.z.url(),authTokenEnvVar:l.z.string().min(1).nullable().optional(),apiTimeoutMs:l.z.number().int().positive().nullable().optional(),authMode:l.z.enum([`auto`,`api-key`,`oauth`]).nullable().optional(),apiKeyEnvVar:l.z.string().min(1).nullable().optional(),project:l.z.string().min(1).nullable().optional(),location:l.z.string().min(1).nullable().optional(),apiVersion:l.z.string().min(1).nullable().optional()}),He=l.z.object({providers:l.z.record(l.z.string().min(1),Ve)}),Ue=l.z.object({id:l.z.string().min(1),label:l.z.string().min(1),provider:l.z.string().min(1),model:l.z.string().min(1),reasoningEffort:Be.default(N),enabled:l.z.boolean().default(!0)}),We=l.z.object({provider:l.z.string().min(1),model:l.z.string().min(1),reasoningEffort:Be.default(N),thinkingDisabled:l.z.boolean().optional()}),R=l.z.object({main:We,fallbacks:l.z.array(We).default([])}),Ge=l.z.object({models:l.z.object({default:R.optional(),sonnet:R.optional(),opus:R.optional(),haiku:R.optional(),subagent:R.optional()}).default({})}),Ke=l.z.array(Ue),qe=l.z.object({models:l.z.object({default:R.nullable().optional(),sonnet:R.nullable().optional(),opus:R.nullable().optional(),haiku:R.nullable().optional(),subagent:R.nullable().optional()}).optional()});var z=class extends Error{constructor(e,t,n){super(e,n),this.code=t,this.name=`ProfileStoreError`}},Je=class{constructor(e=process.env.MODEL_PROXY_MCP_PROVIDER_PATH||ie,t=process.env.MODEL_PROXY_MCP_MODEL_LIST_PATH||d.default.join(d.default.dirname(process.env.MODEL_PROXY_MCP_PROVIDER_PATH||ie),d.default.basename(ae)),n=process.env.MODEL_PROXY_MCP_SCOPE_DIR||d.default.join(d.default.dirname(process.env.MODEL_PROXY_MCP_PROVIDER_PATH||ie),d.default.basename(oe)),r=C){this.providerConfigPath=e,this.modelListPath=t,this.scopeSettingsDir=n,this.logger=r}getConfigPath(e=M){return this.getScopeConfigPath(e)}async ensureConfig(e=M,t){return this.getConfig(e,t)}async listScopes(){try{let e=await _.default.readdir(this.scopeSettingsDir,{withFileTypes:!0}),t=new Set([M]);for(let n of e){if(!n.isFile()||!n.name.endsWith(Re))continue;let e=n.name.slice(0,-5);e&&t.add(e)}return Array.from(t).sort()}catch(e){if(this.isFileNotFoundError(e))return[M];throw this.logger.error(`${F} Failed to list scopes`,{scopeSettingsDir:this.scopeSettingsDir,cause:e}),new z(`Failed to list scopes in ${this.scopeSettingsDir}`,L.settingsReadFailed,{cause:e})}}async getConfig(e=M,t){return this.toProxyConfig(await this.getSettings(e,t))}async getAdminConfig(e=M){let t=await this.getSettings(e),n=this.toProfiles(t),r=Object.fromEntries(P.map(e=>[e,this.resolveSlotConfig(t,n,e)]));return{scope:e,providerConfigPath:this.providerConfigPath,modelListPath:this.modelListPath,scopeConfigPath:this.getScopeConfigPath(e),providers:t.providers,models:n,scopeModels:t.scope.models,slots:r}}async listProfiles(e=M){return(await this.getAdminConfig(e)).models}async getActiveProfile(e=M,t=M){let n=await this.getAdminConfig(e),r=n.slots[t];return n.models.find(e=>e.id===r.profileId&&e.enabled)??null}async getResolvedSlotConfig(e=M,t=M){return(await this.getAdminConfig(e)).slots[t]}async setActiveProfile(e,t=M,n=M){let r=await this.getSettings(t),i=this.toProfiles(r).find(t=>t.id===e&&t.enabled);if(!i)throw new z(`Profile not found or disabled: ${e}`,L.profileNotFound,{cause:{profileId:e,scope:t,slot:n}});return this.updateConfig({models:{[n]:{main:{provider:i.provider,model:i.model,reasoningEffort:i.reasoningEffort,thinkingDisabled:r.scope.models[n]?.main.thinkingDisabled??!1},fallbacks:r.scope.models[n]?.fallbacks??[]}}},t)}async upsertProfile(e,t=M){let n=Ue.parse({id:e.id,label:e.label,provider:e.provider,model:e.model,reasoningEffort:e.reasoningEffort,enabled:e.enabled}),r=await this.getSettings(t),i=r.models.filter(e=>e.id!==n.id);i.push(n);let a=this.normalizeSettings({providers:r.providers,models:i,scope:r.scope});return await this.saveSettings(a,t),this.toProxyConfig(a)}async updateConfig(e,t=M){let n=qe.parse(e),r=await this.getSettings(t),i={...r.scope.models};for(let e of P){let t=n.models?.[e];if(t!==void 0){if(t===null){delete i[e];continue}i[e]=t}}let a=this.normalizeSettings({providers:r.providers,models:r.models,scope:{models:i}});return await this.saveSettings(a,t),this.toProxyConfig(a)}getScopeConfigPath(e){return d.default.join(this.scopeSettingsDir,`${this.sanitizeScope(e)}${Re}`)}sanitizeScope(e){return e.replace(/[^a-zA-Z0-9._-]/g,`-`).replace(/--+/g,`-`).replace(/^-|-$/g,``)||M}async getSettings(e,t){let n=await this.readSettings(e,t),r=this.normalizeSettings(n);return JSON.stringify(r)!==JSON.stringify(n)&&await this.saveSettings(r,e),r}async readSettings(e,t){let n=this.getScopeConfigPath(e),[r,i,a]=await Promise.allSettled([_.default.readFile(this.providerConfigPath,I),_.default.readFile(this.modelListPath,I),_.default.readFile(n,I)]);try{let n=r.status===`fulfilled`?r.value:null,o=i.status===`fulfilled`?i.value:null,s=a.status===`fulfilled`?a.value:null,c=r.status===`rejected`&&this.isFileNotFoundError(r.reason),l=i.status===`rejected`&&this.isFileNotFoundError(i.reason),u=a.status===`rejected`&&this.isFileNotFoundError(a.reason);if(!n&&r.status===`rejected`&&!c)throw r.reason;if(!o&&i.status===`rejected`&&!l)throw i.reason;if(!s&&a.status===`rejected`&&!u)throw a.reason;let d=s?Ge.parse((0,v.parse)(s)):await this.loadScopeSeedSettings(e,t),f=this.normalizeSettings({providers:n?He.parse((0,v.parse)(n)).providers:structuredClone(ue),models:o?Ke.parse((0,v.parse)(o)):structuredClone(de),scope:d});return(c||l||u)&&await this.saveSettings(f,e),f}catch(t){throw this.logger.error(`${F} Failed to read settings`,{scope:e,providerConfigPath:this.providerConfigPath,modelListPath:this.modelListPath,scopeConfigPath:n,code:L.settingsReadFailed,cause:t}),new z(`Failed to read settings from ${this.providerConfigPath}, ${this.modelListPath}, and ${n}`,L.settingsReadFailed,{cause:t})}}isFileNotFoundError(e){return e?.code===`ENOENT`}async loadScopeSeedSettings(e,t){let n=[t,this.getDefaultScopeSeedPath(e)].filter(e=>!!e);for(let t of n)try{let e=await _.default.readFile(t,I);return Ge.parse((0,v.parse)(e))}catch(n){if(this.isFileNotFoundError(n))continue;throw this.logger.error(`${F} Failed to read scope seed settings`,{scope:e,seedConfigPath:t,cause:n}),n}return structuredClone(fe)}getDefaultScopeSeedPath(e){let t=this.getScopeConfigPath(M);return e===M?null:t}normalizeSettings(e){let t=this.normalizeProviders(Object.keys(e.providers).length>0?e.providers:structuredClone(ue)),n=e.models.map(e=>Ue.parse(e)),r=this.normalizeSlotSelection(e.scope.models.default?.main??null,n,t,this.getDefaultSelection(n,t)),i={default:r?{main:r,fallbacks:this.normalizeFallbacks(e.scope.models.default?.fallbacks??[],r,n,t)}:void 0};for(let a of P){if(a===M)continue;let o=r??this.getDefaultSelection(n,t),s=this.normalizeSlotSelection(e.scope.models[a]?.main??null,n,t,o);i[a]=s?{main:s,fallbacks:this.normalizeFallbacks(e.scope.models[a]?.fallbacks??[],s,n,t)}:void 0}return{providers:t,models:n,scope:{models:i}}}normalizeProviders(e){let t={...e},n=t[Le];return n?.type===`anthropic-compatible`&&n.authTokenEnvVar===`ANTHROPIC_AUTH_TOKEN`&&(t[Le]={...n,authTokenEnvVar:`ZAI_ANTHROPIC_AUTH_TOKEN`}),t}getDefaultSelection(e,t){let n=e.find(e=>e.enabled&&t[e.provider]);return n?{provider:n.provider,model:n.model,reasoningEffort:n.reasoningEffort,thinkingDisabled:!1}:null}normalizeSlotSelection(e,t,n,r){if(!e)return r?{...r}:null;let i=n[e.provider]?e.provider:r?.provider??null;if(!i)return null;if(i!==e.provider)return r&&n[r.provider]?{...r}:null;let a=t.find(t=>t.enabled&&t.provider===i&&t.model===e.model);return{provider:i,model:a?.model??e.model,reasoningEffort:e.reasoningEffort??a?.reasoningEffort??N,thinkingDisabled:e.thinkingDisabled??!1}}normalizeFallbacks(e,t,n,r){let i=[],a=new Set([`${t.provider}:${t.model}:${t.reasoningEffort}:${t.thinkingDisabled?`off`:`on`}`]);for(let t of e){let e=this.normalizeSlotSelection(t,n,r,null);if(!e)continue;let o=`${e.provider}:${e.model}:${e.reasoningEffort}:${e.thinkingDisabled?`off`:`on`}`;a.has(o)||(a.add(o),i.push(e))}return i}toProxyConfig(e){let t=this.toProfiles(e),n=Object.fromEntries(P.map(n=>[n,this.resolveSlotConfig(e,t,n).profileId]));return{activeProfileId:n.default??null,slotProfileIds:n,providers:e.providers,profiles:t,scope:e.scope}}resolveSlotConfig(e,t,n){let r=e.scope.models[n]??e.scope.models.default,i=r?.main??null,a=i?t.find(e=>e.provider===i.provider&&e.model===i.model&&e.enabled):void 0,o=i?.provider?e.providers[i.provider]:void 0;return{slot:n,profileId:a?.id??null,label:a?.label??null,provider:i?.provider??null,providerType:o?.type??null,endpoint:o?.endpoint??null,model:i?.model??null,reasoningEffort:i?.reasoningEffort??a?.reasoningEffort??N,thinkingDisabled:i?.thinkingDisabled??!1,fallbacks:r?.fallbacks??[]}}toProfiles(e){return e.models.map(t=>({id:t.id,label:t.label,provider:t.provider,providerType:e.providers[t.provider]?.type??null,model:t.model,endpoint:e.providers[t.provider]?.endpoint??null,reasoningEffort:t.reasoningEffort,enabled:t.enabled}))}async saveSettings(e,t=M){let n=this.getScopeConfigPath(t);try{let t=new Set([d.default.dirname(this.providerConfigPath),d.default.dirname(this.modelListPath),d.default.dirname(n)]);await Promise.all(Array.from(t,e=>_.default.mkdir(e,{recursive:!0}))),await Promise.all([_.default.writeFile(this.providerConfigPath,(0,v.stringify)(He.parse({providers:e.providers}),{indent:2}),I),_.default.writeFile(this.modelListPath,(0,v.stringify)(Ke.parse(e.models),{indent:2}),I),_.default.writeFile(n,(0,v.stringify)(Ge.parse(e.scope),{indent:2}),I)])}catch(e){throw this.logger.error(`${F} Failed to write settings`,{scope:t,providerConfigPath:this.providerConfigPath,modelListPath:this.modelListPath,scopeConfigPath:n,code:L.settingsWriteFailed,cause:e}),new z(`Failed to write settings to ${this.providerConfigPath}, ${this.modelListPath}, and ${n}`,L.settingsWriteFailed,{cause:e})}}};const Ye={"ccproxy-default":`default`,"ccproxy-sonnet":`sonnet`,"ccproxy-opus":`opus`,"ccproxy-haiku":`haiku`,"ccproxy-subagent":`subagent`},B=`default`,V=`default`,Xe=[`default`,`sonnet`,`opus`,`haiku`,`subagent`],Ze=`https://api.anthropic.com/v1/messages`,Qe=`anthropic-compatible`,$e=`ANTHROPIC_AUTH_TOKEN`,H=`application/json`,et=`text/event-stream; charset=utf-8`,U=`content-type`,tt=`cache-control`,nt=`connection`,W=`accept`,rt=`anthropic-version`,it=`anthropic-beta`,at=`no-cache`,ot=`keep-alive`,st=`request`,ct=`response`,lt=`system`,ut=`assistant`,G=`MODEL_NOT_FOUND`,dt=`REQUEST_FORWARD_FAILED`,ft=`generateContent`,pt=l.z.object({model:l.z.string().optional(),system:l.z.unknown().optional(),messages:l.z.array(l.z.unknown()).default([]),tools:l.z.array(l.z.unknown()).optional(),max_tokens:l.z.number().int().positive().optional(),stream:l.z.boolean().optional()});var K=class extends Error{constructor(e,t,n,r){super(e,r),this.code=t,this.status=n,this.name=new.target.name}},mt=class extends K{},q=class extends K{},J=class extends K{},Y=class extends K{constructor(e,t,n,r,i){super(e,dt,t,i),this.upstreamBody=n,this.attemptLabel=r}};const ht=new Set([408,429,500,502,503,504]);var gt=class{codeAssistProjectCache=new Map;constructor(e=new Je,t=new xe(C,ce),n=C,r=fetch,i=new Ie){this.profileStore=e,this.codexAuth=t,this.logger=n,this.fetchImpl=r,this.historyService=i}async listProfiles(e=B){return this.profileStore.listProfiles(e)}async ensureConfig(e=B){return await this.historyService.ensureInitialized(),this.profileStore.ensureConfig(e)}async getAdminConfig(e=B){return this.profileStore.getAdminConfig(e)}async updateAdminConfig(e,t=B){return await this.profileStore.updateConfig(e,t),this.profileStore.getAdminConfig(t)}async getActiveProfile(e=B,t=V){return this.profileStore.getActiveProfile(e,t)}async upsertProfile(e,t=B){return this.profileStore.upsertProfile(e,t)}async setActiveProfile(e,t=B,n=V){return this.profileStore.setActiveProfile(e,t,n)}async getCurrentModel(e=B,t=V){return this.profileStore.getActiveProfile(e,t)}async switchModel(e,t=B,n=V){let r=await this.profileStore.listProfiles(t),i=await this.profileStore.getResolvedSlotConfig(t,n),a=r.find(t=>t.model===e&&t.provider===i.provider&&t.enabled)??r.find(t=>t.model===e&&t.enabled);if(!a)throw new q(`Model not found or disabled: ${e}`,G,400,{cause:{model:e,scope:t,slot:n}});return this.profileStore.updateConfig({models:{[n]:{main:{provider:a.provider,model:a.model,reasoningEffort:a.reasoningEffort},fallbacks:(await this.profileStore.getAdminConfig(t)).scopeModels[n]?.fallbacks??[]}}},t)}async listHistory(e=B,t=50,n){return this.historyService.listHistory(e,t,n)}async clearHistory(e=B){return{deleted:await this.historyService.clearScope(e)}}async listScopes(){let[e,t]=await Promise.all([this.profileStore.listScopes(),this.historyService.listScopes()]);return Array.from(new Set([...e,...t,B])).sort()}async getHistoryStats(e=B){return this.historyService.getStats(e)}async getStatus(e=B,t,n){let r=await this.profileStore.getConfig(e),i=await this.profileStore.getResolvedSlotConfig(e,V),a=await this.codexAuth.getAuthStatus(),o=Object.fromEntries(await Promise.all(Xe.map(async t=>{let n=await this.profileStore.getResolvedSlotConfig(e,t);return[t,{profileId:n.profileId,provider:n.provider,model:n.model,reasoningEffort:n.reasoningEffort,thinkingDisabled:n.thinkingDisabled??!1}]})));return{running:!0,port:t,pid:n,scope:e,activeProfileId:r.activeProfileId,activeModel:i.model??void 0,activeReasoningEffort:i.reasoningEffort,slotModels:o,auth:a,profiles:r.profiles}}async getModels(e=B){return{object:`list`,data:(await this.profileStore.listProfiles(e)).map(e=>({id:e.model,object:`model`,created:0,owned_by:`${e.provider}:${e.id}`,metadata:{profileId:e.id,reasoningEffort:e.reasoningEffort}}))}}async forwardClaudeRequest(e,t=B,n=new Headers){let r=(0,p.ulid)(),i=V,a=null;try{let o=this.parseClaudeRequest(e),s=await this.profileStore.getAdminConfig(t);if(i=this.resolveSlot(o.model,s),a=this.resolveRequestedModel(o.model,s,i),!a.model){let n=`No active model configured for scope '${t}' and slot '${i}'`;return await this.recordError(t,i,a,r,n,e),this.createErrorResponse(400,n)}let c=this.buildAttemptTargets(s,a,i);if(c.length===0){let n=`No provider configured for scope '${t}' and slot '${i}'`;return await this.recordError(t,i,a,r,n,e),this.createErrorResponse(400,n)}await this.recordRequest(t,i,c[0].resolved,r,o,e);let l=null;for(let a of c){let s=await this.forwardSingleAttempt(a,o,e,t,i,n);if(s.ok)return await this.recordResponse(t,i,a.resolved,r,s.success.history.upstreamText,s.success.history.claudeBody),s.success.response;let{error:c,payloadForHistory:u}=s.failure;if(!this.isRecoverableUpstreamError(c))return this.logger.error(`[model-proxy-mcp] Non-recoverable model attempt failed`,{scope:t,slot:i,model:a.resolved.model,code:c.code,status:c.status,message:c.message}),await this.recordError(t,i,a.resolved,r,c.message,u),this.createErrorResponse(c.status,c.message);l=s.failure,this.logger.warn(`[model-proxy-mcp] Recoverable model attempt failed; trying fallback`,{scope:t,slot:i,model:a.resolved.model,code:c.code,status:c.status,message:c.message}),await this.recordError(t,i,a.resolved,r,`Attempt failed for ${a.label}: ${c.message}`,u)}if(l)return await this.recordError(t,i,a,r,l.error.message,l.payloadForHistory),this.createErrorResponse(l.error.status,l.error.message);let u=`No model attempt could be executed`;return await this.recordError(t,i,a,r,u,e),this.createErrorResponse(500,u)}catch(n){let o=n instanceof K?n:new K(`Failed to process Claude proxy request`,dt,500,{cause:n});return this.logger.error(`[model-proxy-mcp] Request forwarding failed`,{scope:t,slot:i,code:o.code,message:o.message,cause:o.cause}),await this.recordError(t,i,a,r,o.message,e),this.createErrorResponse(o.status,o.message)}}buildAttemptTargets(e,t,n){let r=[],i=new Set,a=[t,...t.fallbacks.map(e=>({...t,...e,slot:n}))];for(let t of a){let a=t.provider,o=t.model;if(!a||!o)continue;let s=e.providers[a];if(!s)continue;let c=`${a}:${o}:${t.reasoningEffort}:${t.thinkingDisabled?`off`:`on`}`;i.has(c)||(i.add(c),r.push({resolved:{...t,slot:n,provider:a,model:o,providerType:s.type,endpoint:s.endpoint,reasoningEffort:t.reasoningEffort,thinkingDisabled:t.thinkingDisabled??!1,fallbacks:[]},provider:s,label:`${a}/${o}`}))}return r}async forwardSingleAttempt(e,t,n,r,i,a){try{return e.resolved.providerType===Qe?{ok:!0,success:await this.forwardAnthropicCompatibleRequest(n,r,i,e.resolved,e.provider,a)}:e.resolved.providerType===`gemini-direct`?{ok:!0,success:await this.forwardGeminiDirectRequest(t,r,i,e.resolved,e.provider)}:{ok:!0,success:await this.forwardCodexRequest(n,r,i,e.resolved,e.provider)}}catch(e){return{ok:!1,failure:{error:e instanceof K?e:new K(`Failed to process Claude proxy request`,dt,500,{cause:e}),payloadForHistory:e instanceof Y?e.upstreamBody:n}}}}isRecoverableUpstreamError(e){return e instanceof Y&&ht.has(e.status)}createCodexSessionId(e,t,n){let r=[b,`codex`,e,t,n.provider??`unknown-provider`,n.model??`unknown-model`].join(`:`);return(0,f.createHash)(`sha256`).update(r).digest(`hex`)}createGeminiSessionId(e,t,n){let r=[b,`gemini-code-assist`,e,t,n.provider??`unknown-provider`,n.model??`unknown-model`].join(`:`);return(0,f.createHash)(`sha256`).update(r).digest(`hex`)}createCodeAssistProjectCacheKey(e){let t=process.env.GOOGLE_CLOUD_PROJECT||process.env.GOOGLE_CLOUD_PROJECT_ID||``,n=Object.entries(e.headers).sort(([e],[t])=>e.localeCompare(t));return JSON.stringify({authMode:e.authMode,authSource:e.authSource,authType:e.authType??null,configuredProject:t,headers:n})}async forwardCodexRequest(e,t,n,r,i){let a=r.model;if(!a)throw new q(`No active model configured for slot '${n}'`,G,400);let o=await this.codexAuth.resolveAuth(),s=i.apiKeyEnvVar?process.env[i.apiKeyEnvVar]:void 0;if(!o.accessToken&&!s)throw new J(`Missing Codex auth. Sign in with the official Codex CLI first.`,`AUTH_MISSING`,401);let c=new _e({toProvider:`chatgpt-codex`,toModel:a,toEndpoint:r.endpoint??Ze,toApiKey:s,sessionId:this.createCodexSessionId(t,n,r),sessionReasoningEffort:r.reasoningEffort,thinkingDisabled:r.thinkingDisabled??!1,logger:this.logger,resolvedAuth:o}),l=new Ce(this.logger,r.thinkingDisabled??!1),u=await c.transform(Ze,e),d=await this.fetchImpl(u.url,{method:`POST`,headers:u.headers,body:u.body});if(!d.ok){let e=await d.text();throw this.logger.error(`[model-proxy-mcp] Upstream request failed`,{scope:t,slot:n,profileId:r.profileId,model:r.model,status:d.status,body:e}),new Y(e||`Codex upstream request failed`,d.status,e,`${r.provider}/${r.model}`)}let f=await d.text(),p=l.transform(f);return{response:{status:200,body:p,headers:this.createSuccessHeaders(t,n)},history:{upstreamText:f,claudeBody:p}}}async forwardGeminiDirectRequest(e,t,n,r,i){try{let a=r.model;if(!a)throw new q(`No active Gemini model configured for scope '${t}' and slot '${n}'`,G,400);let o=await new Ee(this.logger).resolveHeaders(i),s=new Te,c=new Pe,l={model:e.model,system:e.system,messages:e.messages,tools:e.tools,max_tokens:e.max_tokens},u=s.transform(l,a,r.reasoningEffort,r.thinkingDisabled??!1);if(o.authType===`oauth-personal`)return await this.forwardGeminiCodeAssistRequest(u.body,t,n,r,o,c);let d=this.createGeminiRequestUrl(i,u.modelPath,ft),f=new Headers(o.headers);f.set(U,H),f.set(W,H);let p=i.apiTimeoutMs??this.resolveUpstreamTimeoutMs(),m=await this.fetchImpl(d,{method:`POST`,headers:f,body:JSON.stringify(u.body),signal:p?AbortSignal.timeout(p):void 0}),h=await m.text();if(!m.ok)throw this.logger.error(`[model-proxy-mcp] Gemini upstream request failed`,{scope:t,slot:n,profileId:r.profileId,model:r.model,status:m.status,body:h,authMode:o.authMode,authSource:o.authSource}),new Y(h||`Gemini upstream request failed`,m.status,h,`${r.provider}/${r.model}`);let g=c.transformBuffered(h,a);return{response:{status:200,body:g,headers:this.createSuccessHeaders(t,n)},history:{upstreamText:h,claudeBody:g}}}catch(e){throw e instanceof E?new J(e.message,e.code,401,{cause:e}):e}}async forwardGeminiCodeAssistRequest(e,t,n,r,i,a){let o=await this.resolveCodeAssistProjectId(i),s=this.createCodeAssistUrl(ft),c=new Headers(i.headers);c.set(U,H),c.set(W,H);let l={model:r.model,project:o,user_prompt_id:(0,p.ulid)(),request:this.toCodeAssistGenerateContentRequest(e,this.createGeminiSessionId(t,n,r))},u=await this.fetchImpl(s,{method:`POST`,headers:c,body:JSON.stringify(l)}),d=await u.text();if(!u.ok)throw this.logger.error(`[model-proxy-mcp] Gemini Code Assist request failed`,{scope:t,slot:n,profileId:r.profileId,model:r.model,status:u.status,body:d,authMode:i.authMode,authSource:i.authSource,authType:i.authType}),new Y(d||`Gemini Code Assist request failed`,u.status,d,`${r.provider}/${r.model}`);let f=this.normalizeCodeAssistBufferedResponse(d),m=a.transformBuffered(f,r.model??`gemini`);return{response:{status:200,body:m,headers:this.createSuccessHeaders(t,n)},history:{upstreamText:d,claudeBody:m}}}createSuccessHeaders(e,t){return{[U]:et,[tt]:at,[nt]:ot,"x-model-proxy-service":b,"x-model-proxy-scope":e,"x-model-proxy-slot":t}}createGeminiRequestUrl(e,t,n){let r=e.endpoint.replace(/\/$/,``),i=e.apiVersion??`v1beta`;return new URL(`${r}/${i}/${t}:${n}`).toString()}createCodeAssistUrl(e){return new URL(`https://cloudcode-pa.googleapis.com/v1internal:${e}`).toString()}async resolveCodeAssistProjectId(e){let t=this.createCodeAssistProjectCacheKey(e),n=this.codeAssistProjectCache.get(t);if(n)return n;let r=new Headers(e.headers);r.set(U,H);let i=process.env.GOOGLE_CLOUD_PROJECT||process.env.GOOGLE_CLOUD_PROJECT_ID||void 0,a=await this.fetchImpl(this.createCodeAssistUrl(`loadCodeAssist`),{method:`POST`,headers:r,body:JSON.stringify({cloudaicompanionProject:i,metadata:{ideType:`IDE_UNSPECIFIED`,platform:`PLATFORM_UNSPECIFIED`,pluginType:`GEMINI`,duetProject:i}})}),o=await a.text();if(!a.ok)throw new Y(o||`Failed to load Gemini Code Assist project context`,a.status,o,`google-gemini-direct/loadCodeAssist`);let s=JSON.parse(o).cloudaicompanionProject||i;if(!s)throw new J(`Gemini Code Assist did not return a usable project ID.`,`GEMINI_PROJECT_MISSING`,401);return this.codeAssistProjectCache.set(t,s),s}toCodeAssistGenerateContentRequest(e,t){return{contents:e.contents,systemInstruction:e.system_instruction,tools:e.tools,toolConfig:e.tool_config,generationConfig:e.generationConfig,session_id:t}}normalizeCodeAssistBufferedResponse(e){let t=JSON.parse(e);return JSON.stringify(this.extractCodeAssistResponse(t))}normalizeCodeAssistStreamingResponse(e){let t=[];for(let n of e.split(`
|
|
64
64
|
|
|
65
65
|
`)){let e=n.split(`
|
|
66
66
|
`).find(e=>e.startsWith(`data:`))?.slice(5).trim();if(!e||e===`[DONE]`)continue;let r=JSON.parse(e);t.push(`data: ${JSON.stringify(this.extractCodeAssistResponse(r))}`),t.push(``)}return t.join(`
|
|
67
|
-
`)}extractCodeAssistResponse(e){return{candidates:e.response?.candidates??[],usageMetadata:e.response?.usageMetadata,modelVersion:e.response?.modelVersion}}resolveSlot(e,t){if(!e)return
|
|
67
|
+
`)}extractCodeAssistResponse(e){return{candidates:e.response?.candidates??[],usageMetadata:e.response?.usageMetadata,modelVersion:e.response?.modelVersion}}resolveSlot(e,t){if(!e)return V;let n=Ye[e];if(n)return n;for(let n of Xe)if(t.slots[n].model===e)return n;return V}resolveRequestedModel(e,t,n){let r=t.slots[n];if(!e||Ye[e])return r;let i=t.models.find(t=>t.model===e&&t.provider===r.provider&&t.enabled)??t.models.find(t=>t.model===e&&t.enabled);if(!i&&r.model!==e)throw new q(`Model not found or disabled: ${e}`,G,400,{cause:{model:e,slot:n,scope:t.scope}});return{...r,profileId:i?.id??null,label:i?.label??null,provider:i?.provider??r.provider,providerType:i?.providerType??r.providerType,endpoint:i?.endpoint??r.endpoint,model:i?.model??e,reasoningEffort:i?.reasoningEffort??r.reasoningEffort,thinkingDisabled:r.thinkingDisabled??!1}}async forwardAnthropicCompatibleRequest(e,t,n,r,i,a){let o=this.getAnthropicCompatibleAuthToken(i.authTokenEnvVar);if(!o)throw new J(`Missing upstream auth token for ${Qe}. Set ${i.authTokenEnvVar||$e}.`,`UPSTREAM_AUTH_MISSING`,401);let s=this.sanitizeClaudePayloadForAnthropic({...pt.parse(JSON.parse(e)),model:r.model}),c=new Headers;c.set(W,a.get(W)??et),c.set(U,H),c.set(rt,a.get(rt)??`2023-06-01`),c.set(`x-api-key`,o),c.set(`authorization`,`Bearer ${o}`);let l=a.get(it);l&&c.set(it,l);let u=i.apiTimeoutMs??this.resolveUpstreamTimeoutMs(),d=await this.fetchImpl(i.endpoint,{method:`POST`,headers:c,body:JSON.stringify(s),signal:u?AbortSignal.timeout(u):void 0}),f=await d.text();if(!d.ok)throw this.logger.error(`[model-proxy-mcp] Anthropic-compatible upstream request failed`,{scope:t,slot:n,profileId:r.profileId,model:r.model,status:d.status,body:f}),new Y(f||`Anthropic-compatible upstream request failed`,d.status,f,`${r.provider}/${r.model}`);return{response:{status:d.status,body:f,headers:{[U]:d.headers.get(U)??et,[tt]:d.headers.get(tt)??at,[nt]:d.headers.get(nt)??ot,"x-model-proxy-service":b,"x-model-proxy-scope":t,"x-model-proxy-slot":n}},history:{upstreamText:f,claudeBody:f}}}getAnthropicCompatibleAuthToken(e){let t=e||process.env.MODEL_PROXY_MCP_UPSTREAM_AUTH_ENV||$e;return process.env[t]||process.env.MODEL_PROXY_MCP_UPSTREAM_AUTH_TOKEN||null}resolveUpstreamTimeoutMs(){let e=process.env.API_TIMEOUT_MS||process.env.MODEL_PROXY_MCP_UPSTREAM_TIMEOUT_MS;if(!e)return null;let t=Number.parseInt(e,10);return Number.isInteger(t)&&t>0?t:null}createErrorResponse(e,t){return{status:e,body:JSON.stringify({type:`error`,error:{type:`api_error`,message:t}}),headers:{"content-type":`application/json; charset=utf-8`}}}parseClaudeRequest(e){try{return pt.parse(JSON.parse(e))}catch(e){throw e instanceof SyntaxError||e instanceof l.ZodError?new mt(`Invalid Claude request payload`,`REQUEST_VALIDATION_FAILED`,400,{cause:e}):e}}async recordRequest(e,t,n,r,i,a){let o=[],s=this.normalizeSystemPayloads(i.system);for(let i of s)o.push({scope:e,requestId:r,direction:st,role:lt,messageType:lt,model:n.model,slot:t,payloadJson:JSON.stringify(i)});let c=Array.isArray(i.messages)?i.messages:[];for(let i of c){let a=this.extractRole(i);o.push({scope:e,requestId:r,direction:st,role:a,messageType:`message`,model:n.model,slot:t,payloadJson:JSON.stringify(i)})}o.length===0&&o.push({scope:e,requestId:r,direction:st,role:null,messageType:`raw-request`,model:n.model,slot:t,payloadJson:a}),await this.historyService.appendEntries(o)}async recordResponse(e,t,n,r,i,a){let o=this.normalizeResponsePayloads(e,r,t,n.model,i);if(o.length>0){await this.historyService.appendEntries(o);return}await this.historyService.appendEntries([{scope:e,requestId:r,direction:ct,role:ut,messageType:`response-stream`,model:n.model,slot:t,payloadJson:JSON.stringify({upstreamText:i,claudeBody:a})}])}async recordError(e,t,n,r,i,a){await this.historyService.appendEntries([{scope:e,requestId:r,direction:`error`,role:null,messageType:`error`,model:n?.model??null,slot:t,payloadJson:JSON.stringify({message:i,payloadPreview:this.summarizePayload(a)})}])}normalizeSystemPayloads(e){return e?typeof e==`string`?[{type:`text`,text:e}]:Array.isArray(e)?e:[e]:[]}sanitizeClaudePayloadForAnthropic(e){return this.stripPrivateChatGptMetadata(e)}stripPrivateChatGptMetadata(e){return Array.isArray(e)?e.map(e=>this.stripPrivateChatGptMetadata(e)):!e||typeof e!=`object`?e:Object.fromEntries(Object.entries(e).filter(([e])=>!e.startsWith(`_chatgpt_`)).map(([e,t])=>[e,this.stripPrivateChatGptMetadata(t)]))}extractRole(e){if(!e||typeof e!=`object`)return null;let t=e.role;return typeof t==`string`?t:null}normalizeResponsePayloads(e,t,n,r,i){let a=[];for(let o of i.split(`
|
|
68
68
|
|
|
69
69
|
`)){let i=o.split(`
|
|
70
|
-
`).find(e=>e.startsWith(`data:`))?.slice(5).trim();if(!i||i===`[DONE]`)continue;let s;try{s=JSON.parse(i)}catch{continue}let c=this.extractCompletedResponse(s);if(!(!c?.output||!Array.isArray(c.output)))for(let i of c.output)a.push({scope:e,requestId:t,direction:
|
|
70
|
+
`).find(e=>e.startsWith(`data:`))?.slice(5).trim();if(!i||i===`[DONE]`)continue;let s;try{s=JSON.parse(i)}catch{continue}let c=this.extractCompletedResponse(s);if(!(!c?.output||!Array.isArray(c.output)))for(let i of c.output)a.push({scope:e,requestId:t,direction:ct,role:ut,messageType:typeof i.type==`string`?i.type:`response-item`,model:typeof c.model==`string`?c.model:r,slot:n,payloadJson:JSON.stringify(i)})}return a}extractCompletedResponse(e){if(!e||typeof e!=`object`)return null;let t=e;return t.type===`response.completed`&&t.response&&typeof t.response==`object`||t.response&&typeof t.response==`object`?t.response:{model:typeof t.model==`string`?t.model:void 0,output:Array.isArray(t.output)?t.output:void 0}}summarizePayload(e){let t=e.replace(/\s+/g,` `).trim();return t.length<=240?t:`${t.slice(0,240)}...`}};const _t=l.z.enum([`minimal`,`low`,`medium`,`high`]),vt=l.z.enum([`default`,`sonnet`,`opus`,`haiku`,`subagent`]),yt=l.z.object({provider:l.z.string().min(1),model:l.z.string().min(1),reasoningEffort:_t,thinkingDisabled:l.z.boolean().optional()}),X=l.z.object({main:yt,fallbacks:l.z.array(yt).default([])}),bt=l.z.object({models:l.z.object({default:X.nullable().optional(),sonnet:X.nullable().optional(),opus:X.nullable().optional(),haiku:X.nullable().optional(),subagent:X.nullable().optional()}).optional()}),xt=l.z.object({id:l.z.string().min(1),label:l.z.string().min(1),provider:l.z.string().min(1),model:l.z.string().min(1),endpoint:l.z.url().nullable(),reasoningEffort:_t.default(`medium`),enabled:l.z.boolean().default(!0),providerType:l.z.enum([`chatgpt-codex`,`anthropic-compatible`,`gemini-direct`]).nullable().optional()});function St(e=new gt){let t=new c.Hono,n=e=>e||`default`;return t.get(`/health`,e=>e.json({status:`healthy`,service:b})),t.get(`/status`,async t=>{let n=await e.getStatus();return t.json(n)}),t.get(`/v1/models`,async t=>t.json(await e.getModels())),t.get(`/scopes/:scope/status`,async t=>t.json(await e.getStatus(n(t.req.param(`scope`))))),t.get(`/scopes/:scope/v1/models`,async t=>t.json(await e.getModels(n(t.req.param(`scope`))))),t.post(`/v1/messages`,async t=>{let n=await t.req.text(),r=await e.forwardClaudeRequest(n,`default`,t.req.raw.headers);return new Response(r.body,{status:r.status,headers:r.headers})}),t.post(`/scopes/:scope/v1/messages`,async t=>{let r=await t.req.text(),i=await e.forwardClaudeRequest(r,n(t.req.param(`scope`)),t.req.raw.headers);return new Response(i.body,{status:i.status,headers:i.headers})}),t.get(`/admin`,()=>new Response(`<!DOCTYPE html>
|
|
71
71
|
<html lang="en">
|
|
72
72
|
<head>
|
|
73
73
|
<meta charset="utf-8" />
|
|
@@ -598,4 +598,4 @@ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=
|
|
|
598
598
|
<\/script>
|
|
599
599
|
</body>
|
|
600
600
|
</html>
|
|
601
|
-
`,{headers:{"content-type":`text/html; charset=utf-8`}})),t.get(`/admin/scopes`,async t=>t.json({scopes:await e.listScopes()})),t.get(`/admin/config`,async t=>{let r=n(t.req.query(`scope`));return t.json(await e.getAdminConfig(r))}),t.get(`/admin/profiles`,async t=>{let r=n(t.req.query(`scope`));return t.json({profiles:await e.listProfiles(r)})}),t.get(`/admin/current-model`,async t=>{let r=n(t.req.query(`scope`)),i=t.req.query(`slot`)||`default`;return t.json({profile:await e.getCurrentModel(r,i)})}),t.get(`/admin/history`,async t=>{let r=n(t.req.query(`scope`)),i=l.z.coerce.number().int().positive().max(200).default(50).parse(t.req.query(`limit`)??50),a=t.req.query(`cursor`)||void 0;return t.json(await e.listHistory(r,i,a))}),t.get(`/admin/history/stats`,async t=>{let r=n(t.req.query(`scope`));return t.json(await e.getHistoryStats(r))}),t.put(`/admin/profiles/:id`,async t=>{let r=
|
|
601
|
+
`,{headers:{"content-type":`text/html; charset=utf-8`}})),t.get(`/admin/scopes`,async t=>t.json({scopes:await e.listScopes()})),t.get(`/admin/config`,async t=>{let r=n(t.req.query(`scope`));return t.json(await e.getAdminConfig(r))}),t.get(`/admin/profiles`,async t=>{let r=n(t.req.query(`scope`));return t.json({profiles:await e.listProfiles(r)})}),t.get(`/admin/current-model`,async t=>{let r=n(t.req.query(`scope`)),i=t.req.query(`slot`)||`default`;return t.json({profile:await e.getCurrentModel(r,i)})}),t.get(`/admin/history`,async t=>{let r=n(t.req.query(`scope`)),i=l.z.coerce.number().int().positive().max(200).default(50).parse(t.req.query(`limit`)??50),a=t.req.query(`cursor`)||void 0;return t.json(await e.listHistory(r,i,a))}),t.get(`/admin/history/stats`,async t=>{let r=n(t.req.query(`scope`));return t.json(await e.getHistoryStats(r))}),t.put(`/admin/profiles/:id`,async t=>{let r=xt.parse(await t.req.json()),i=n(t.req.query(`scope`)),a=await e.upsertProfile({...r,id:t.req.param(`id`),providerType:r.providerType??null},i);return t.json(a)}),t.put(`/admin/active-profile`,async t=>{let r=n(t.req.query(`scope`)),i=l.z.object({profileId:l.z.string().min(1),slot:vt.default(`default`)}).parse(await t.req.json()),a=await e.setActiveProfile(i.profileId,r,i.slot);return t.json(a)}),t.put(`/admin/current-model`,async t=>{let r=n(t.req.query(`scope`)),i=l.z.object({model:l.z.string().min(1),slot:vt.default(`default`)}).parse(await t.req.json()),a=await e.switchModel(i.model,r,i.slot);return t.json(a)}),t.put(`/admin/config`,async t=>{let r=n(t.req.query(`scope`)),i=bt.parse(await t.req.json());return t.json(await e.updateAdminConfig(i,r))}),t.delete(`/admin/history`,async t=>{let r=n(t.req.query(`scope`));return t.json(await e.clearHistory(r))}),t}const Z=`@agimon-ai/model-proxy-mcp`,Q=l.z.enum([`default`,`sonnet`,`opus`,`haiku`,`subagent`]);let Ct;function wt(){return Ct??=(0,g.createNodeTelemetry)({serviceName:Z,workspaceRoot:process.cwd()}),Ct}async function Tt(e,t,n){let r=await wt();return r.runInSpan(e,{attributes:t},async()=>{try{return await n()}catch(n){throw r.logger.error(`${e} failed`,{attributes:t,exception:n}),n}})}function Et(e=new gt,t=process.env){let n=e=>{let n=e?.scope;return typeof n==`string`&&n.trim()?n:t.MODEL_PROXY_MCP_SCOPE||`default`},r=e=>{let n=typeof e?.slot==`string`?e.slot:t.MODEL_PROXY_MCP_SLOT||`default`;return Q.parse(n)},i=new te.Server({name:`model-proxy-mcp`,version:`0.1.0`},{capabilities:{tools:{}}});return i.setRequestHandler(ne.ListToolsRequestSchema,async()=>Tt(`mcp.server.list_tools`,{"mcp.service":Z},async()=>({tools:[{name:`get_proxy_status`,description:`Get the HTTP proxy status, auth status, and active profile.`,inputSchema:{type:`object`,properties:{scope:{type:`string`}},additionalProperties:!1}},{name:`list_profiles`,description:`List all configured model proxy profiles.`,inputSchema:{type:`object`,properties:{scope:{type:`string`}},additionalProperties:!1}},{name:`get_active_profile`,description:`Get the active model proxy profile.`,inputSchema:{type:`object`,properties:{scope:{type:`string`},slot:{type:`string`,enum:Q.options}},additionalProperties:!1}},{name:`get_current_model`,description:`Get the currently active model and profile settings.`,inputSchema:{type:`object`,properties:{scope:{type:`string`},slot:{type:`string`,enum:Q.options}},additionalProperties:!1}},{name:`set_model_target`,description:`Set the scoped slot main target directly by provider, model, and reasoning effort.`,inputSchema:{type:`object`,properties:{provider:{type:`string`},model:{type:`string`},reasoningEffort:{type:`string`,enum:[`minimal`,`low`,`medium`,`high`]},scope:{type:`string`},slot:{type:`string`,enum:Q.options},thinkingDisabled:{type:`boolean`}},required:[`provider`,`model`,`reasoningEffort`],additionalProperties:!1}},{name:`upsert_profile`,description:`Create or update a Codex model profile.`,inputSchema:{type:`object`,properties:{id:{type:`string`},label:{type:`string`},provider:{type:`string`},model:{type:`string`},endpoint:{type:`string`},reasoningEffort:{type:`string`,enum:[`minimal`,`low`,`medium`,`high`]},enabled:{type:`boolean`},scope:{type:`string`}},required:[`id`,`label`,`model`,`endpoint`,`reasoningEffort`,`enabled`],additionalProperties:!1}}]}))),i.setRequestHandler(ne.CallToolRequestSchema,async t=>{let{name:i,arguments:a}=t.params;return Tt(`mcp.server.call_tool`,{"mcp.service":Z,"mcp.tool.name":i},async()=>{try{switch(i){case`get_proxy_status`:return $(JSON.stringify(await e.getStatus(n(a)),null,2));case`list_profiles`:return $(JSON.stringify(await e.listProfiles(n(a)),null,2));case`get_active_profile`:return $(JSON.stringify(await e.getActiveProfile(n(a),r(a)),null,2));case`get_current_model`:return $(JSON.stringify(await e.getCurrentModel(n(a),r(a)),null,2));case`set_model_target`:{let t=n(a),i=r(a),o=await e.getAdminConfig(t);return $(JSON.stringify(await e.updateAdminConfig({models:{[i]:{main:{provider:String(a?.provider),model:String(a?.model),reasoningEffort:l.z.enum([`minimal`,`low`,`medium`,`high`]).parse(String(a?.reasoningEffort)),thinkingDisabled:a?.thinkingDisabled===void 0?!1:!!a.thinkingDisabled},fallbacks:o.scopeModels[i]?.fallbacks??[]}}},t),null,2))}case`upsert_profile`:return $(JSON.stringify(await e.upsertProfile({id:String(a?.id),label:String(a?.label),provider:String(a?.provider||`chatgpt-codex`),providerType:null,model:String(a?.model),endpoint:String(a?.endpoint),reasoningEffort:String(a?.reasoningEffort),enabled:!!a?.enabled},n(a)),null,2));default:throw Error(`Unknown tool: ${i}`)}}catch(e){return(await wt()).logger.error(`model proxy MCP tool failed`,{attributes:{"mcp.service":Z,"mcp.tool.name":i},exception:e}),{content:[{type:`text`,text:`Error executing tool ${i}: ${e instanceof Error?e.message:String(e)}`}],isError:!0}}})}),i}function $(e){return{content:[{type:`text`,text:e}]}}var Dt=class{transport=null;constructor(e){this.server=e}async start(){this.transport=new re.StdioServerTransport,await this.server.connect(this.transport)}async stop(){this.transport&&=(await this.transport.close(),null)}};Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return Je}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return 43191}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return gt}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return Et}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return Ie}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return St}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return C}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return Dt}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return le}});
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import"@hono/node-server";import{Hono as e}from"hono";import{ZodError as t,z as n}from"zod";import r from"node:os";import i from"node:path";import{createHash as a,randomUUID as o}from"node:crypto";import{ulid as s}from"ulidx";import c from"node:fs";import{fileURLToPath as l}from"node:url";import u from"node:fs/promises";import
|
|
1
|
+
import"@hono/node-server";import{Hono as e}from"hono";import{ZodError as t,z as n}from"zod";import r from"node:os";import i from"node:path";import{createHash as a,randomUUID as o}from"node:crypto";import{ulid as s}from"ulidx";import c from"node:fs";import{fileURLToPath as l}from"node:url";import{createNodeTelemetry as u}from"@agimon-ai/log-sink-mcp";import d from"node:fs/promises";import f from"better-sqlite3";import{parse as p,stringify as m}from"yaml";import{Server as h}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as g,ListToolsRequestSchema as ee}from"@modelcontextprotocol/sdk/types.js";import{StdioServerTransport as te}from"@modelcontextprotocol/sdk/server/stdio.js";const _=i.join(r.homedir(),`.model-proxy`),v=i.join(_,`model-provider.yaml`),ne=i.join(_,`model-list.yaml`),re=i.join(_,`scopes`),ie=i.join(_,`history.sqlite`),ae=i.join(r.homedir(),`.codex`,`auth.json`),oe=43191,y=`model-proxy-mcp-http`,se={min:43e3,max:44e3},ce={"chatgpt-codex":{type:`chatgpt-codex`,endpoint:`https://chatgpt.com/backend-api/codex/responses`,authTokenEnvVar:null,apiTimeoutMs:null},"zai-anthropic-compat":{type:`anthropic-compatible`,endpoint:`https://api.z.ai/api/anthropic/v1/messages`,authTokenEnvVar:`ZAI_ANTHROPIC_AUTH_TOKEN`,apiTimeoutMs:3e6},"google-gemini-direct":{type:`gemini-direct`,endpoint:`https://generativelanguage.googleapis.com`,authTokenEnvVar:`GEMINI_API_KEY`,apiTimeoutMs:3e5,authMode:`auto`,apiKeyEnvVar:`GEMINI_API_KEY`,project:null,location:`global`,apiVersion:`v1beta`}},le=[{id:`chatgpt-codex-gpt-5.3-codex`,label:`ChatGPT Codex GPT-5.3 Codex`,provider:`chatgpt-codex`,model:`gpt-5.3-codex`,reasoningEffort:`medium`,enabled:!0},{id:`chatgpt-codex-gpt-5.3-codex-low`,label:`ChatGPT Codex GPT-5.3 Codex Low`,provider:`chatgpt-codex`,model:`gpt-5.3-codex`,reasoningEffort:`low`,enabled:!0},{id:`chatgpt-codex-gpt-5.4`,label:`ChatGPT Codex GPT-5.4`,provider:`chatgpt-codex`,model:`gpt-5.4`,reasoningEffort:`medium`,enabled:!0},{id:`chatgpt-codex-gpt-5.2`,label:`ChatGPT Codex GPT-5.2`,provider:`chatgpt-codex`,model:`gpt-5.2`,reasoningEffort:`medium`,enabled:!0},{id:`zai-anthropic-compat-glm-4.7`,label:`Z.ai GLM-4.7`,provider:`zai-anthropic-compat`,model:`GLM-4.7`,reasoningEffort:`high`,enabled:!0},{id:`zai-anthropic-compat-glm-5`,label:`Z.ai GLM-5`,provider:`zai-anthropic-compat`,model:`glm-5`,reasoningEffort:`high`,enabled:!0},{id:`zai-anthropic-compat-glm-4.5-air`,label:`Z.ai GLM-4.5-Air`,provider:`zai-anthropic-compat`,model:`GLM-4.5-Air`,reasoningEffort:`medium`,enabled:!0},{id:`google-gemini-direct-gemini-2.5-flash`,label:`Google Gemini 2.5 Flash`,provider:`google-gemini-direct`,model:`gemini-2.5-flash`,reasoningEffort:`medium`,enabled:!0},{id:`google-gemini-direct-gemini-3-flash-preview`,label:`Google Gemini 3 Flash Preview`,provider:`google-gemini-direct`,model:`gemini-3-flash-preview`,reasoningEffort:`medium`,enabled:!0},{id:`google-gemini-direct-gemini-3.1-pro-preview`,label:`Google Gemini 3.1 Pro Preview`,provider:`google-gemini-direct`,model:`gemini-3.1-pro-preview`,reasoningEffort:`high`,enabled:!0}],ue={models:{default:{main:{provider:`chatgpt-codex`,model:`gpt-5.4`,reasoningEffort:`medium`},fallbacks:[{provider:`chatgpt-codex`,model:`gpt-5.4`,reasoningEffort:`low`}]},sonnet:{main:{provider:`chatgpt-codex`,model:`gpt-5.3-codex`,reasoningEffort:`medium`},fallbacks:[{provider:`chatgpt-codex`,model:`gpt-5.4`,reasoningEffort:`medium`}]},opus:{main:{provider:`chatgpt-codex`,model:`gpt-5.4`,reasoningEffort:`medium`},fallbacks:[{provider:`chatgpt-codex`,model:`gpt-5.4`,reasoningEffort:`low`}]},haiku:{main:{provider:`chatgpt-codex`,model:`gpt-5.3-codex`,reasoningEffort:`low`},fallbacks:[{provider:`chatgpt-codex`,model:`gpt-5.4`,reasoningEffort:`medium`}]},subagent:{main:{provider:`chatgpt-codex`,model:`gpt-5.3-codex`,reasoningEffort:`low`},fallbacks:[{provider:`chatgpt-codex`,model:`gpt-5.4`,reasoningEffort:`medium`}]}}},de=l(import.meta.url),fe=i.dirname(de),pe=i.join(fe,`codex.md`);let b=null;const me=e=>typeof e==`object`&&!!e&&!Array.isArray(e);var he=class{config;codexInstructions;constructor(e){this.config=e,this.codexInstructions=this.loadCodexInstructions()}loadCodexInstructions(){if(b!==null)return b;try{return b=c.readFileSync(pe,`utf-8`),b}catch{return console.warn(`Warning: Could not load codex.md from ${pe}`),b=``,``}}async transform(e,t){try{let e=JSON.parse(t);this.config.logger?.debug(`[ClaudeToOpenAI] ===== ORIGINAL CLAUDE REQUEST =====`),this.config.logger?.debug(`[ClaudeToOpenAI] Original body`,{body:JSON.stringify(e,null,2)});let n=this.config.sessionId||o(),r=n,i=this.config.sessionReasoningEffort,a=e.model&&e.model.toLowerCase().includes(`haiku`),s=i||(a?`minimal`:`medium`);this.config.logger?.debug(`[ClaudeToOpenAI] Model detection and reasoning effort`,{originalModel:e.model,isHaikuModel:a,sessionReasoningEffort:i||`none`,finalReasoningEffort:s,source:i?`session override`:`model-based`});let c={model:this.config.toModel||`gpt-5`,stream:!0,store:!1,tool_choice:`auto`,parallel_tool_calls:!1,prompt_cache_key:n};this.config.thinkingDisabled||(c.reasoning={effort:s,summary:`auto`},c.include=[`reasoning.encrypted_content`]),this.config.logger?.debug(`[ClaudeToOpenAI] Thinking mode`,{thinkingDisabled:this.config.thinkingDisabled??!1}),c.instructions=this.adaptInstructionsForChatGPT(this.codexInstructions);let l=[],u=``;if(e.system){let t=this.extractSystemMessages(e.system);t&&Array.isArray(t)&&t.length>0&&(u=t.map(e=>e.content).join(`
|
|
2
2
|
|
|
3
3
|
`))}if(u=this.removeClaudeCodeInstructions(u),u&&l.push({type:`message`,role:`user`,content:[{type:`input_text`,text:u}]}),e.messages&&Array.isArray(e.messages))for(let t of e.messages){let e=this.convertMessageToInput(t);Array.isArray(e)?l.push(...e):e&&l.push(e)}if(c.input=l,e.tools&&Array.isArray(e.tools)){this.config.logger?.debug(`[ClaudeToOpenAI] Original Claude tools`,{tools:JSON.stringify(e.tools,null,2)});let t=this.convertTools(e.tools);this.config.logger?.debug(`[ClaudeToOpenAI] Converted tools`,{tools:JSON.stringify(t,null,2)}),t.length>0?(c.tools=t,this.config.logger?.debug(`[ClaudeToOpenAI] Added tools to responsesRequest`,{toolCount:t.length}),this.config.logger?.debug(`[ClaudeToOpenAI] Verify responsesRequest.tools exists`,{hasTools:!!c.tools,toolsLength:c.tools?.length,keys:Object.keys(c)})):this.config.logger?.warn(`[ClaudeToOpenAI] No valid tools after conversion, omitting tools field`)}else this.config.logger?.debug(`[ClaudeToOpenAI] No tools in Claude request`,{hasTools:!!e.tools,isArray:Array.isArray(e.tools)});let d=this.config.toEndpoint||`https://chatgpt.com/backend-api/codex/responses`,f={version:`0.46.0`,"openai-beta":`responses=experimental`,conversation_id:r,session_id:n,accept:`text/event-stream`,"content-type":`application/json`,"user-agent":`codex_cli_rs/0.46.0 (Mac OS 15.6.0; arm64) iTerm.app/3.6.2`,originator:`codex_cli_rs`},p=this.config.resolvedAuth;return p?.accessToken?(this.config.logger?.debug(`[ClaudeToOpenAI] Raw access token`,{token:`${p.accessToken.substring(0,30)}...`}),f.authorization=p.accessToken.startsWith(`Bearer `)?p.accessToken:`Bearer ${p.accessToken}`,p.accountId&&(f[`chatgpt-account-id`]=p.accountId)):this.config.toApiKey&&(f.authorization=`Bearer ${this.config.toApiKey}`),f.authorization?.startsWith(`Bearer `)&&this.config.logger?.debug(`[ClaudeToOpenAI] Added Bearer auth header`),this.config.logger?.debug(`[ClaudeToOpenAI] ===== REQUEST DETAILS =====`),this.config.logger?.debug(`[ClaudeToOpenAI] Target URL`,{targetUrl:d}),this.config.logger?.debug(`[ClaudeToOpenAI] Headers`,{headers:{...f,authorization:f.authorization?`${f.authorization.substring(0,30)}...`:void 0}}),this.config.logger?.debug(`[ClaudeToOpenAI] Body`,{body:JSON.stringify(c,null,2)}),this.config.logger?.debug(`[ClaudeToOpenAI] ===============================`),this.config.logger?.debug(`[ClaudeToOpenAI] ===== FINAL TRANSFORMED REQUEST =====`),this.config.logger?.debug(`[ClaudeToOpenAI] Pre-final check - responsesRequest.tools`,{hasTools:!!c.tools,toolsLength:c.tools?.length,keys:Object.keys(c)}),this.config.logger?.debug(`[ClaudeToOpenAI] Final body`,{body:JSON.stringify(c,null,2)}),this.config.logger?.debug(`[ClaudeToOpenAI] ===== END FINAL TRANSFORMED REQUEST =====`),{url:d,body:JSON.stringify(c),headers:f}}catch(e){throw Error(`Failed to transform Claude request to OpenAI: ${e instanceof Error?e.message:String(e)}`,{cause:e})}}adaptInstructionsForChatGPT(e){let t=e;return t=t.replace(/You are powered by the model named Sonnet 4\.5\. The exact model ID is claude-sonnet-4-5-\d+\./g,`You are powered by ChatGPT (GPT-5 reasoning model).`),t=t.replace(/Assistant knowledge cutoff is January 2025/g,`Assistant knowledge cutoff is October 2023`),t=t.replace(/\bClaude\b/g,`ChatGPT`),t=t.replace(/\bAnthropic\b/g,`OpenAI`),t}removeClaudeCodeInstructions(e){let t=[/You are Claude Code, Anthropic's official CLI for Claude\.[\s\S]*?claude_code_docs_map\.md/,/You are Claude Code[\s\S]*?using Claude Code\n/],n=e;for(let e of t)n=n.replace(e,``);return n=n.replace(/\n{3,}/g,`
|
|
4
4
|
|
|
5
|
-
`).trim(),n}extractSystemMessages(e){let t=[];if(typeof e==`string`)t.push({role:`system`,content:e});else if(Array.isArray(e))for(let n of e)typeof n==`string`?t.push({role:`system`,content:n}):
|
|
5
|
+
`).trim(),n}extractSystemMessages(e){let t=[];if(typeof e==`string`)t.push({role:`system`,content:e});else if(Array.isArray(e))for(let n of e)typeof n==`string`?t.push({role:`system`,content:n}):me(n)&&n.type===`text`&&typeof n.text==`string`&&t.push({role:`system`,content:n.text});else me(e)&&e.type===`text`&&typeof e.text==`string`&&t.push({role:`system`,content:e.text});return t}convertMessageToInput(e){let t=e.role,n=e.content;if(!t||!n)return null;let r=t===`assistant`?`output_text`:`input_text`;if(Array.isArray(n)){let e=[],i=[],a=()=>{i.length!==0&&(e.push({type:`message`,role:t,content:i}),i=[])};for(let t of n)if(t.type===`tool_result`){a();let n=typeof t.content==`string`?t.content:JSON.stringify(t.content);e.push({type:`function_call_output`,call_id:t.tool_use_id,output:n})}else if(t.type===`text`)i.push({type:r,text:t.text||``});else if(t.type===`image`)if(t.source&&t.source.type===`base64`&&t.source.data){let e=t.source.media_type||`image/jpeg`,n=`data:${e};base64,${t.source.data}`;i.push({type:`input_image`,image_url:n}),this.config.logger?.debug(`[ClaudeToOpenAI] Converted image block`,{mediaType:e,dataLength:t.source.data.length})}else this.config.logger?.warn(`[ClaudeToOpenAI] Unsupported image format`,{source:t.source});else t.type===`tool_use`&&(a(),e.push({type:`function_call`,call_id:t.id,name:t.name,arguments:JSON.stringify(t.input||{})}));return a(),e.length>1?e:e.length===1?e[0]:null}return typeof n==`string`?{type:`message`,role:t,content:[{type:r,text:n}]}:null}convertTools(e){return!e||!Array.isArray(e)?[]:e.filter(e=>!(!e||typeof e!=`object`||!e.name)).map(e=>({type:`function`,name:e.name,description:e.description||``,parameters:e.input_schema||e.parameters||{}}))}};let ge;function _e(){return ge??=u({serviceName:`@agimon-ai/model-proxy-mcp`,workspaceRoot:process.cwd()}),ge}function ve(e){if(e===void 0)return;if(e instanceof Error)return{exception:e,attributes:{error:e.message}};if(typeof e!=`object`||!e)return{attributes:{data:String(e)}};let t={};for(let[n,r]of Object.entries(e))r!=null&&(t[n]=typeof r==`string`||typeof r==`number`||typeof r==`boolean`?r:JSON.stringify(r));return{attributes:Object.keys(t).length>0?t:void 0}}function x(e,t,n){console.error(t,n??``),_e().then(r=>{r.logger[e](t,ve(n))}).catch(()=>void 0)}const S={info:(e,t)=>x(`info`,e,t),error:(e,t)=>x(`error`,e,t),debug:(e,t)=>x(`debug`,e,t),warn:(e,t)=>x(`warn`,e,t)};var ye=class e{static TOKEN_REFRESH_URL=`https://auth.openai.com/oauth/token`;static CLIENT_ID=`app_EMoamEEZ73f0CkXaXp7hrann`;constructor(e=S,t=i.join(process.env.HOME||``,`.codex`,`auth.json`)){this.logger=e,this.authFilePath=t}getAuthFilePath(){return this.authFilePath}async getAuthStatus(){let e=this.readAuthFile();return{configured:!!e?.tokens?.refresh_token,accountId:e?.tokens?.account_id??null,authFilePath:this.authFilePath,lastRefresh:e?.last_refresh??null}}async getAccountId(){return(await this.resolveAuth()).accountId}async getAccessToken(){return(await this.resolveAuth()).accessToken}async resolveAuth(){let e=this.readAuthFile();if(!e?.tokens)return this.logger.warn(`[CodexAuth] No tokens found in auth file`),{accessToken:null,accountId:null};let t=e.tokens.account_id||null,n=e.tokens.access_token;if(n.startsWith(`Bearer `)&&(n=n.slice(7)),!this.isTokenExpired(n))return{accessToken:n,accountId:t};let r=await this.refreshAccessToken(e.tokens.refresh_token);return r?(this.saveTokens(r),{accessToken:r.access_token,accountId:r.account_id||t}):{accessToken:null,accountId:t}}readAuthFile(){try{return c.existsSync(this.authFilePath)?JSON.parse(c.readFileSync(this.authFilePath,`utf8`)):null}catch(e){return this.logger.error(`[CodexAuth] Failed to read auth file`,e),null}}isTokenExpired(e){let t=this.decodeJWT(e);return t?.exp?t.exp*1e3-Date.now()<300*1e3:!0}decodeJWT(e){try{let[,t]=e.split(`.`);return t?JSON.parse(Buffer.from(t,`base64url`).toString(`utf8`)):null}catch(e){return this.logger.error(`[CodexAuth] Failed to decode JWT`,e),null}}async refreshAccessToken(t){try{let n=await fetch(e.TOKEN_REFRESH_URL,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:new URLSearchParams({grant_type:`refresh_token`,refresh_token:t,client_id:e.CLIENT_ID})});if(!n.ok)return this.logger.error(`[CodexAuth] Token refresh failed`,await n.text()),null;let r=await n.json();return{id_token:r.id_token??``,access_token:(r.access_token??``).replace(/^Bearer\s+/i,``),refresh_token:r.refresh_token||t,account_id:r.account_id??``}}catch(e){return this.logger.error(`[CodexAuth] Failed to refresh token`,e),null}}saveTokens(e){try{let t=this.readAuthFile();if(!t)return;t.tokens=e,t.last_refresh=new Date().toISOString(),c.writeFileSync(this.authFilePath,JSON.stringify(t,null,2),`utf8`)}catch(e){this.logger.error(`[CodexAuth] Failed to save tokens`,e)}}};const C=e=>typeof e==`object`&&!!e&&!Array.isArray(e);function be(e){let t=e?.error;if(typeof t==`string`)return t;if(C(t)){if(typeof t.message==`string`)return t.message;if(typeof t.error==`string`)return t.error}return typeof e?.message==`string`?e.message:`Unexpected API error`}var xe=class{logger;thinkingDisabled;constructor(e,t=!1){this.logger=e,this.thinkingDisabled=t}formatSseEvent(e,t){return`event: ${e}\ndata: ${JSON.stringify(t)}\n\n`}transform(e){try{return!e||e.trim()===``?this.createEmptyClaudeResponse():e.includes(`data:`)?this.convertStreamingResponse(e):this.convertNonStreamingResponse(e)}catch(e){return this.logger?.error(`[OpenAIToClaude] ERROR in transform`,e),this.createEmptyClaudeResponse()}}transformStream(e){let t={encoder:new TextEncoder,messageId:`msg_${s()}`,model:`gpt-5`,inputTokens:0,outputTokens:0,cachedTokens:0,reasoningTokens:0,reasoningEffort:void 0,stopReason:`end_turn`,messageStarted:!1,messageStopped:!1,thinkingBlockStarted:!1,textBlockStarted:!1,emittedThinking:!1,emittedText:!1,lastThinkingChunk:null,lastTextChunk:null,streamedReasoningKeys:new Set,streamedTextKeys:new Set,emittedToolCallIndexes:new Set,toolCalls:new Map},n=new TextDecoder,r=``;return new ReadableStream({start:async i=>{let a=e.getReader();try{for(;;){let{done:e,value:o}=await a.read();if(e)break;r+=n.decode(o,{stream:!0}),r=this.processBufferedEvents(r,t,i)}r+=n.decode(),r=this.processBufferedEvents(r,t,i,!0),t.messageStopped||(t.messageStarted?this.finishStreamingResponse(i,t):i.enqueue(t.encoder.encode(this.createEmptyClaudeResponse())))}catch(e){this.logger?.error(`[OpenAIToClaude] ERROR in transformStream`,e),t.messageStopped||(t.messageStarted?this.emitStreamingError(i,t,`Unexpected API error`):i.enqueue(t.encoder.encode(this.createClaudeErrorStream(`Unexpected API error`))))}finally{i.close(),a.releaseLock()}}})}createEmptyClaudeResponse(){let e=`msg_${s()}`,t=[];return t.push(`event: message_start`),t.push(`data: ${JSON.stringify({type:`message_start`,message:{id:e,type:`message`,role:`assistant`,content:[],model:`gpt-5`,stop_reason:null,stop_sequence:null,usage:{input_tokens:0,output_tokens:0}}})}`),t.push(``),t.push(`event: message_stop`),t.push(`data: {"type":"message_stop"}`),t.push(``),t.join(`
|
|
6
6
|
`)}convertStreamingResponse(e){let t=this.parseOpenAIStream(e),n=this.createClaudeStreamFromParsed(t),r=t.errorMessage||`Empty streaming response from provider`;return this.ensureValidClaudeStream(n,r)}parseOpenAIStream(e){let t={textSegments:[],thinkingSegments:[],toolCalls:new Map,streamedReasoningKeys:new Set,streamedTextKeys:new Set,model:`gpt-5`,inputTokens:0,outputTokens:0,cachedTokens:0,reasoningTokens:0,reasoningEffort:void 0,stopReason:void 0,errorMessage:void 0},n=e.split(`
|
|
7
|
-
`),r=``,i=/event:\s*response\./i.test(e)||/"type"\s*:\s*"response\./i.test(e)||/"response"\s*:\s*\{/i.test(e);for(let e of n){let n=e.trim();if(!n)continue;if(n.startsWith(`event:`)){r=n.slice(6).trim();continue}if(!n.startsWith(`data:`))continue;let a=n.slice(5).trim();if(!a||a===`[DONE]`)continue;let o;try{o=JSON.parse(a)}catch{continue}if(o?.error){t.errorMessage=
|
|
7
|
+
`),r=``,i=/event:\s*response\./i.test(e)||/"type"\s*:\s*"response\./i.test(e)||/"response"\s*:\s*\{/i.test(e);for(let e of n){let n=e.trim();if(!n)continue;if(n.startsWith(`event:`)){r=n.slice(6).trim();continue}if(!n.startsWith(`data:`))continue;let a=n.slice(5).trim();if(!a||a===`[DONE]`)continue;let o;try{o=JSON.parse(a)}catch{continue}if(o?.error){t.errorMessage=be(o);break}i||r.startsWith(`response.`)?this.handleResponsesEvent(r,o,t):this.handleChatCompletionChunk(o,t)}return t}handleResponsesEvent(e,t,n){let r=typeof t?.type==`string`?t.type:e;switch(t?.model&&typeof t.model==`string`&&(n.model=t.model),t?.usage&&(n.inputTokens=t.usage.input_tokens??t.usage.prompt_tokens??n.inputTokens,n.outputTokens=t.usage.output_tokens??t.usage.completion_tokens??n.outputTokens),r){case`response.created`:t?.response?.model&&(n.model=t.response.model);break;case`response.reasoning.delta`:case`response.reasoning_summary_text.delta`:case`response.function_call_arguments.delta`:case`response.function_call_arguments.done`:case`response.in_progress`:case`response.output_item.added`:case`response.output_item.done`:case`response.content_part.added`:case`response.content_part.done`:case`response.reasoning_summary_part.added`:case`response.reasoning_summary_part.done`:case`response.reasoning_summary_text.done`:case`response.output_text.delta`:case`response.output_text.done`:case`response.delta`:break;case`response.completed`:case`response.done`:this.collectCompletedResponse(t,n);break;case`response.error`:n.errorMessage=be(t);break;default:t?.delta&&(this.collectTextFromNode(t.delta,n.textSegments),t.delta.tool_calls&&this.collectToolCalls(t.delta.tool_calls,n.toolCalls));break}}handleChatCompletionChunk(e,t){if(e&&(e.model&&typeof e.model==`string`&&(t.model=e.model),e.usage&&(t.inputTokens=e.usage.prompt_tokens??t.inputTokens,t.outputTokens=e.usage.completion_tokens??t.outputTokens),Array.isArray(e.choices)))for(let n of e.choices)n?.delta&&(this.collectTextFromNode(n.delta,t.textSegments),n.delta.tool_calls&&this.collectToolCalls(n.delta.tool_calls,t.toolCalls)),n?.message?.content&&this.collectTextFromNode(n.message.content,t.textSegments),n?.finish_reason&&(t.stopReason=this.mapFinishReason(n.finish_reason))}collectCompletedResponse(e,t){let n=!1;if(e?.response?.model&&(t.model=e.response.model),e?.response?.usage&&(t.inputTokens=e.response.usage.input_tokens??e.response.usage.prompt_tokens??t.inputTokens,t.outputTokens=e.response.usage.output_tokens??e.response.usage.completion_tokens??t.outputTokens,e.response.usage.input_tokens_details?.cached_tokens&&(t.cachedTokens=e.response.usage.input_tokens_details.cached_tokens),e.response.usage.output_tokens_details?.reasoning_tokens&&(t.reasoningTokens=e.response.usage.output_tokens_details.reasoning_tokens)),e?.response?.reasoning?.effort&&(t.reasoningEffort=e.response.reasoning.effort),Array.isArray(e?.response?.tool_calls)&&this.collectToolCalls(e.response.tool_calls,t.toolCalls),Array.isArray(e?.response?.output)){for(let[r,i]of e.response.output.entries())if(i?.type===`reasoning`){let e={output_index:r,item:i};if(!this.shouldEmitTerminalContent(e,t.streamedReasoningKeys,`reasoning`,t.thinkingSegments.length>0))continue;if(Array.isArray(i?.summary))for(let e of i.summary)e?.type===`summary_text`&&e?.text&&t.thinkingSegments.push(e.text)}else if(i?.type===`message`){let e={output_index:r,item:i};if(!this.shouldEmitTerminalContent(e,t.streamedTextKeys,`text`,t.textSegments.length>0))continue;if(Array.isArray(i?.content))for(let e of i.content)(e?.type===`output_text`||e?.type===`text`)&&e?.text&&(t.textSegments.push(e.text),n=!0)}else if(i?.type===`function_call`){let e={index:t.toolCalls.size,id:i.id||`tool_${s()}`,function:{name:i.name,arguments:i.arguments}};this.collectToolCalls([e],t.toolCalls)}}e?.response?.output_text&&!n&&this.shouldEmitTerminalContent({output_index:0},t.streamedTextKeys,`text`,t.textSegments.length>0)&&this.collectTextFromNode(e.response.output_text,t.textSegments),t.stopReason=this.mapResponseStatusToStopReason(e?.response?.status)}collectTextFromNode(e,t){if(e!=null){if(typeof e==`string`){e.length>0&&t.push(e);return}if(Array.isArray(e)){for(let n of e)this.collectTextFromNode(n,t);return}if(C(e)){typeof e.text==`string`&&t.push(e.text),typeof e.output_text==`string`&&t.push(e.output_text),typeof e.value==`string`&&t.push(e.value),typeof e.delta==`string`?t.push(e.delta):e.delta&&this.collectTextFromNode(e.delta,t),C(e.token)&&typeof e.token.text==`string`&&t.push(e.token.text);for(let n of[`content`,`output`,`output_text`,`message`,`choices`,`segments`])if(e[n]!==void 0)if(n===`choices`&&Array.isArray(e[n]))for(let r of e[n])r?.message?.content&&this.collectTextFromNode(r.message.content,t),r?.delta&&this.collectTextFromNode(r.delta,t);else this.collectTextFromNode(e[n],t)}}}collectToolCalls(e,t){if(!e)return;let n=Array.isArray(e)?e:[e];for(let e of n){if(!C(e))continue;let n=typeof e.index==`number`?e.index:t.size,r=t.get(n)||{id:``,name:``,argumentChunks:[]};typeof e.id==`string`&&!r.id&&(r.id=e.id);let i=C(e.function)?e.function:null,a=i?.name??e.name;typeof a==`string`&&a.length>0&&(r.name=a);let o=i?.arguments??e.arguments;typeof o==`string`&&o.length>0&&r.argumentChunks.push(o),t.set(n,r)}}collectResponsesToolCallDelta(e,t){let n=typeof e?.output_index==`number`?e.output_index:t.size,r=t.get(n)||{id:``,name:``,argumentChunks:[]},i=e?.item_id??e?.id;typeof i==`string`&&!r.id&&(r.id=i);let a=e?.name??e?.item?.name??e?.item?.call_id;typeof a==`string`&&a.length>0&&(r.name=a);let o=e?.delta??e?.arguments??e?.item?.arguments;typeof o==`string`&&o.length>0&&r.argumentChunks.push(o),t.set(n,r)}collectResponsesOutputItem(e,t,n=!1){let r=e?.item;if(r?.type===`message`){if(n&&!this.shouldEmitTerminalContent(e,t.streamedTextKeys,`text`,t.textSegments.length>0))return;this.collectTextFromNode(r.content,t.textSegments);return}if(r?.type===`reasoning`){if(n&&!this.shouldEmitTerminalContent(e,t.streamedReasoningKeys,`reasoning`,t.thinkingSegments.length>0))return;this.collectTextFromNode(r.summary??r.content,t.thinkingSegments);return}this.collectResponsesToolCallDelta(e,t.toolCalls)}parseToolCallInput(e){if(!e)return{};try{let t=JSON.parse(e);return t&&typeof t==`object`&&!Array.isArray(t)?t:{}}catch{return{}}}createClaudeStreamFromParsed(e){if(e.errorMessage)return this.createClaudeErrorStream(e.errorMessage);let t=this.thinkingDisabled?[]:this.mergeAndChunkSegments(this.dedupeSegments(e.thinkingSegments)),n=this.mergeAndChunkSegments(this.dedupeSegments(e.textSegments)),r=new Set,i=Array.from(e.toolCalls.entries()).sort((e,t)=>e[0]-t[0]).map(([e,t])=>({index:e,id:t.id,name:t.name,arguments:this.normalizeToolCallArguments(t.argumentChunks)})).filter(e=>{if(!e.name)return!1;let t=`${e.id}\u0000${e.name}\u0000${e.arguments}`;return r.has(t)?!1:(r.add(t),!0)});if(t.length===0&&n.length===0&&i.length===0)return this.createClaudeErrorStream(`Empty streaming response from provider`);let a=`msg_${s()}`,o=e.model||`gpt-5`,c=[];c.push(`event: message_start`),c.push(`data: ${JSON.stringify({type:`message_start`,message:{id:a,type:`message`,role:`assistant`,content:[],model:o,stop_reason:null,stop_sequence:null,usage:{input_tokens:e.inputTokens??0,output_tokens:0}}})}`),c.push(``);let l=0;if(t.length>0){c.push(`event: content_block_start`),c.push(`data: ${JSON.stringify({type:`content_block_start`,index:l,content_block:{type:`thinking`,thinking:``}})}`),c.push(``);for(let e of t)e&&(c.push(`event: content_block_delta`),c.push(`data: ${JSON.stringify({type:`content_block_delta`,index:l,delta:{type:`thinking_delta`,thinking:e}})}`),c.push(``));c.push(`event: content_block_stop`),c.push(`data: ${JSON.stringify({type:`content_block_stop`,index:l})}`),c.push(``),l+=1}if(n.length>0){c.push(`event: content_block_start`),c.push(`data: ${JSON.stringify({type:`content_block_start`,index:l,content_block:{type:`text`,text:``}})}`),c.push(``);for(let e of n)e&&(c.push(`event: content_block_delta`),c.push(`data: ${JSON.stringify({type:`content_block_delta`,index:l,delta:{type:`text_delta`,text:e}})}`),c.push(``));c.push(`event: content_block_stop`),c.push(`data: ${JSON.stringify({type:`content_block_stop`,index:l})}`),c.push(``),l+=1}for(let e of i){let t=l+e.index,n=e.id||`tool_${s()}`;c.push(`event: content_block_start`),c.push(`data: ${JSON.stringify({type:`content_block_start`,index:t,content_block:{type:`tool_use`,id:n,name:e.name,input:{}}})}`),c.push(``),e.arguments&&(c.push(`event: content_block_delta`),c.push(`data: ${JSON.stringify({type:`content_block_delta`,index:t,delta:{type:`input_json_delta`,partial_json:e.arguments}})}`),c.push(``)),c.push(`event: content_block_stop`),c.push(`data: ${JSON.stringify({type:`content_block_stop`,index:t})}`),c.push(``)}let u=e.stopReason||`end_turn`,d={output_tokens:e.outputTokens??0};return(e.cachedTokens||e.reasoningTokens||e.reasoningEffort)&&(d.metadata={},e.cachedTokens&&(d.metadata.cached_tokens=e.cachedTokens),e.reasoningTokens&&(d.metadata.reasoning_tokens=e.reasoningTokens),e.reasoningEffort&&(d.metadata.reasoning_effort=e.reasoningEffort)),c.push(`event: message_delta`),c.push(`data: ${JSON.stringify({type:`message_delta`,delta:{stop_reason:u,stop_sequence:null},usage:d})}`),c.push(``),c.push(`event: message_stop`),c.push(`data: {"type":"message_stop"}`),c.push(``),c.join(`
|
|
8
8
|
`)}mergeAndChunkSegments(e,t=2e3){if(!e||e.length===0)return[];let n=this.collapseRepeatedWholeText(e.join(``));if(!n)return[];let r=[];for(let e=0;e<n.length;e+=t)r.push(n.slice(e,e+t));return r}dedupeSegments(e){if(e.length<2)return e;let t=[];for(let n of e)n&&t.at(-1)!==n&&t.push(n);return t}normalizeToolCallArguments(e){return e.length===0?``:this.sanitizeToolCallArgumentsJson(this.collapseRepeatedWholeText(this.dedupeSegments(e).join(``)))}sanitizeToolCallArgumentsJson(e){if(!e)return``;try{let t=JSON.parse(e),n=this.stripEmptyStringValues(t);return JSON.stringify(n)}catch{return e}}stripEmptyStringValues(e){return Array.isArray(e)?e.map(e=>this.stripEmptyStringValues(e)):!e||typeof e!=`object`?e:Object.fromEntries(Object.entries(e).filter(([,e])=>e!==``).map(([e,t])=>[e,this.stripEmptyStringValues(t)]))}collapseRepeatedWholeText(e){if(!e||e.length<2)return e;for(let t=4;t>=2;--t){if(e.length%t!==0)continue;let n=e.slice(0,e.length/t);if(n&&n.repeat(t)===e)return n}return e}markStreamedContentKey(e,t,n){let r=this.getResponseContentKey(e,n);r&&t.add(r)}shouldEmitTerminalContent(e,t,n,r){let i=this.getResponseContentKey(e,n);return i?!t.has(i):!r}getResponseContentKey(e,t){let n=e?.item_id??e?.item?.id??e?.id,r=typeof e?.output_index==`number`?e.output_index:null,i=typeof e?.content_index==`number`?e.content_index:null;return typeof n==`string`&&n.length>0?`${t}:item:${n}:${i??`na`}`:r===null?null:`${t}:output:${r}:${i??`na`}`}mapResponseStatusToStopReason(e){return e&&{completed:`end_turn`,completed_with_error:`error`,completed_with_streaming_error:`error`,cancelled:`error`,errored:`error`}[e]||`end_turn`}convertNonStreamingResponse(e){try{let t=JSON.parse(e);if(t?.error){let e=t.error?.message||t.error?.error||(typeof t.error==`string`?t.error:`Unexpected API error`);return this.createClaudeErrorResponse(e)}let n=t?.response&&typeof t.response==`object`?t.response:t;if(typeof n?.type==`string`?n.type.startsWith(`response`):Array.isArray(n?.output)||n?.output_text!==void 0){let e={textSegments:[],thinkingSegments:[],toolCalls:new Map,streamedReasoningKeys:new Set,streamedTextKeys:new Set,model:`gpt-5`,inputTokens:0,outputTokens:0,cachedTokens:0,reasoningTokens:0,reasoningEffort:void 0,stopReason:void 0,errorMessage:void 0};this.collectCompletedResponse({response:n},e);let t=this.createClaudeStreamFromParsed(e);return this.ensureValidClaudeStream(t,`Empty response from provider`)}let r=t.choices?.[0];if(!r)return this.createClaudeErrorStream(`Empty response from provider`);let i={textSegments:[],thinkingSegments:[],toolCalls:new Map,streamedReasoningKeys:new Set,streamedTextKeys:new Set,model:t.model||`gpt-4-turbo`,inputTokens:t.usage?.prompt_tokens||0,outputTokens:t.usage?.completion_tokens||0,cachedTokens:0,reasoningTokens:0,reasoningEffort:void 0,stopReason:this.mapFinishReason(r.finish_reason),errorMessage:void 0};this.collectTextFromNode(r.message?.content,i.textSegments);let a=this.createClaudeStreamFromParsed(i);return this.ensureValidClaudeStream(a,`Empty response from provider`)}catch(t){this.logger?.error(`Failed to transform OpenAI response to Claude format`,t);let n=typeof e==`string`&&e.trim()?e.trim():`Unexpected API error`;return this.createClaudeErrorStream(n)}}mapFinishReason(e){return e?{stop:`end_turn`,length:`max_tokens`,function_call:`tool_use`,tool_calls:`tool_use`,content_filter:`stop_sequence`}[e]||`end_turn`:null}createClaudeErrorResponse(e){let t=`msg_${s()}`;return JSON.stringify({id:t,type:`message`,role:`assistant`,content:[{type:`text`,text:e||`Unexpected API error`}],model:`gpt-5`,stop_reason:`error`,stop_sequence:null,usage:{input_tokens:0,output_tokens:0}})}createClaudeErrorStream(e){let t=`msg_${s()}`,n=e||`Unexpected API error`,r=[];return r.push(`event: message_start`),r.push(`data: ${JSON.stringify({type:`message_start`,message:{id:t,type:`message`,role:`assistant`,content:[],model:`gpt-5`,stop_reason:null,stop_sequence:null,usage:{input_tokens:0,output_tokens:0}}})}`),r.push(``),r.push(`event: content_block_start`),r.push(`data: ${JSON.stringify({type:`content_block_start`,index:0,content_block:{type:`text`,text:``}})}`),r.push(``),r.push(`event: content_block_delta`),r.push(`data: ${JSON.stringify({type:`content_block_delta`,index:0,delta:{type:`text_delta`,text:n}})}`),r.push(``),r.push(`event: content_block_stop`),r.push(`data: ${JSON.stringify({type:`content_block_stop`,index:0})}`),r.push(``),r.push(`event: message_delta`),r.push(`data: ${JSON.stringify({type:`message_delta`,delta:{stop_reason:`error`,stop_sequence:null},usage:{output_tokens:0}})}`),r.push(``),r.push(`event: message_stop`),r.push(`data: {"type":"message_stop"}`),r.push(``),r.join(`
|
|
9
9
|
`)}ensureValidClaudeStream(e,t){return!e||!e.includes(`event: message_start`)?this.createClaudeErrorStream(t):e}processBufferedEvents(e,t,n,r=!1){let i=e.replace(/\r\n/g,`
|
|
10
10
|
`).split(`
|
|
11
11
|
|
|
12
12
|
`),a=r?``:i.pop()??``;for(let e of i)this.processSseChunk(e,t,n);return r&&a.trim()&&this.processSseChunk(a,t,n),a}processSseChunk(e,t,n){let r=e.split(`
|
|
13
13
|
`),i=``,a=[];for(let e of r){let t=e.replace(/\r$/,``);t.startsWith(`event:`)?i=t.slice(6).trim():t.startsWith(`data:`)&&a.push(t.slice(5).trim())}let o=a.join(`
|
|
14
|
-
`).trim();if(!(!o||o===`[DONE]`))try{let e=JSON.parse(o),r=typeof e?.type==`string`?e.type:i,a=r.startsWith(`response.`)||i.startsWith(`response.`);if(e?.error||r===`response.error`){let r=e?.error?.message||e?.error?.error||e?.message||(typeof e?.error==`string`?e.error:`Unexpected API error`);this.emitStreamingError(n,t,r);return}if(a){this.processResponsesStreamingEvent(r,e,t,n);return}this.processChatCompletionStreamingChunk(e,t,n)}catch{}}processResponsesStreamingEvent(e,t,n,r){switch(t?.model&&typeof t.model==`string`&&(n.model=t.model),e){case`response.created`:typeof t?.response?.model==`string`&&(n.model=t.response.model);break;case`response.reasoning.delta`:case`response.reasoning_summary_text.delta`:{let e=[];this.markStreamedContentKey(t,n.streamedReasoningKeys,`reasoning`),this.collectTextFromNode(t?.delta??t?.text??t?.summary_text,e);for(let t of e)this.emitThinkingDelta(r,n,t);break}case`response.output_text.delta`:case`response.delta`:{let e=[];this.markStreamedContentKey(t,n.streamedTextKeys,`text`),this.collectTextFromNode(t?.delta??t?.output_text,e);for(let t of e)this.emitTextDelta(r,n,t);t?.delta?.tool_calls&&this.collectToolCalls(t.delta.tool_calls,n.toolCalls);break}case`response.function_call_arguments.delta`:case`response.function_call_arguments.done`:case`response.output_item.added`:case`response.output_item.done`:if(t?.item?.type===`message`){if(this.shouldEmitTerminalContent(t,n.streamedTextKeys,`text`,n.emittedText)){let e=[];this.collectTextFromNode(t.item.content,e);let i=e.join(``);if(i&&i===n.lastTextChunk)break;for(let t of e)this.emitTextDelta(r,n,t)}}else if(t?.item?.type===`reasoning`){if(this.shouldEmitTerminalContent(t,n.streamedReasoningKeys,`reasoning`,n.emittedThinking)){let e=[];this.collectTextFromNode(t.item.summary??t.item.content,e);let i=e.join(``);if(i&&i===n.lastThinkingChunk)break;for(let t of e)this.emitThinkingDelta(r,n,t)}}else this.collectResponsesToolCallDelta(t,n.toolCalls);break;case`response.output_text.done`:case`response.content_part.done`:if(n.textBlockStarted)break;if(this.shouldEmitTerminalContent(t,n.streamedTextKeys,`text`,n.emittedText)){let e=[];this.collectTextFromNode(t?.delta??t?.output_text??t?.text??t?.part,e);let i=e.join(``);if(i&&i===n.lastTextChunk)break;for(let t of e)this.emitTextDelta(r,n,t)}break;case`response.completed`:case`response.done`:{let e={textSegments:[],thinkingSegments:[],toolCalls:new Map(n.toolCalls),streamedReasoningKeys:new Set(n.streamedReasoningKeys),streamedTextKeys:new Set(n.streamedTextKeys),model:n.model,inputTokens:n.inputTokens,outputTokens:n.outputTokens,cachedTokens:n.cachedTokens,reasoningTokens:n.reasoningTokens,reasoningEffort:n.reasoningEffort,stopReason:n.stopReason};if(this.collectCompletedResponse(t,e),n.model=e.model,n.inputTokens=e.inputTokens,n.outputTokens=e.outputTokens,n.cachedTokens=e.cachedTokens,n.reasoningTokens=e.reasoningTokens,n.reasoningEffort=e.reasoningEffort,n.stopReason=e.stopReason,n.toolCalls=e.toolCalls,!n.emittedThinking)for(let t of e.thinkingSegments)this.emitThinkingDelta(r,n,t);if(!n.emittedText)for(let t of e.textSegments)this.emitTextDelta(r,n,t);this.emitPendingToolCalls(r,n),this.finishStreamingResponse(r,n);break}default:break}}processChatCompletionStreamingChunk(e,t,n){if(e&&(typeof e.model==`string`&&(t.model=e.model),e.usage&&(t.inputTokens=e.usage.prompt_tokens??t.inputTokens,t.outputTokens=e.usage.completion_tokens??t.outputTokens),Array.isArray(e.choices)))for(let r of e.choices){let e=[];this.collectTextFromNode(r?.delta??r?.message?.content,e);for(let r of e)this.emitTextDelta(n,t,r);r?.delta?.tool_calls&&this.collectToolCalls(r.delta.tool_calls,t.toolCalls),r?.finish_reason&&(t.stopReason=this.mapFinishReason(r.finish_reason))}}emitPendingToolCalls(e,t){let n=Array.from(t.toolCalls.entries()).sort((e,t)=>e[0]-t[0]).map(([e,t])=>({index:e,id:t.id||`tool_${s()}`,name:t.name,arguments:t.argumentChunks.join(``)})).filter(e=>e.name&&!t.emittedToolCallIndexes.has(e.index));for(let r of n){this.stopOpenContentBlocks(e,t);let n=this.getNextToolBlockIndex(r.index);e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_start`,{type:`content_block_start`,index:n,content_block:{type:`tool_use`,id:r.id,name:r.name,input:{}}})+(r.arguments?this.formatSseEvent(`content_block_delta`,{type:`content_block_delta`,index:n,delta:{type:`input_json_delta`,partial_json:r.arguments}}):``)+this.formatSseEvent(`content_block_stop`,{type:`content_block_stop`,index:n}))),t.emittedToolCallIndexes.add(r.index)}}emitThinkingDelta(e,t,n){this.thinkingDisabled||!n||(this.ensureMessageStarted(e,t),t.textBlockStarted&&this.stopTextBlock(e,t),t.thinkingBlockStarted||=(e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_start`,{type:`content_block_start`,index:0,content_block:{type:`thinking`,thinking:``}}))),!0),e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_delta`,{type:`content_block_delta`,index:0,delta:{type:`thinking_delta`,thinking:n}}))),t.emittedThinking=!0,t.lastThinkingChunk=n)}emitTextDelta(e,t,n){n&&(this.ensureMessageStarted(e,t),t.textBlockStarted||=(t.thinkingBlockStarted&&this.stopThinkingBlock(e,t),e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_start`,{type:`content_block_start`,index:this.getTextBlockIndex(),content_block:{type:`text`,text:``}}))),!0),e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_delta`,{type:`content_block_delta`,index:this.getTextBlockIndex(),delta:{type:`text_delta`,text:n}}))),t.emittedText=!0,t.lastTextChunk=n)}emitStreamingError(e,t,n){if(!t.messageStarted){e.enqueue(t.encoder.encode(this.createClaudeErrorStream(n))),t.messageStarted=!0,t.messageStopped=!0;return}!t.textBlockStarted&&!t.thinkingBlockStarted&&this.emitTextDelta(e,t,n),t.stopReason=`error`,this.finishStreamingResponse(e,t)}finishStreamingResponse(e,t){if(t.messageStopped)return;this.stopOpenContentBlocks(e,t);let n={output_tokens:t.outputTokens??0};if(t.cachedTokens||t.reasoningTokens||t.reasoningEffort){n.metadata={};let e=n.metadata;t.cachedTokens&&(e.cached_tokens=t.cachedTokens),t.reasoningTokens&&(e.reasoning_tokens=t.reasoningTokens),t.reasoningEffort&&(e.reasoning_effort=t.reasoningEffort)}e.enqueue(t.encoder.encode(this.formatSseEvent(`message_delta`,{type:`message_delta`,delta:{stop_reason:t.stopReason||`end_turn`,stop_sequence:null},usage:n})+this.formatSseEvent(`message_stop`,{type:`message_stop`}))),t.messageStopped=!0}ensureMessageStarted(e,t){t.messageStarted||=(e.enqueue(t.encoder.encode(this.formatSseEvent(`message_start`,{type:`message_start`,message:{id:t.messageId,type:`message`,role:`assistant`,content:[],model:t.model,stop_reason:null,stop_sequence:null,usage:{input_tokens:t.inputTokens??0,output_tokens:0}}}))),!0)}stopOpenContentBlocks(e,t){this.stopTextBlock(e,t),this.stopThinkingBlock(e,t)}stopThinkingBlock(e,t){t.thinkingBlockStarted&&=(e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_stop`,{type:`content_block_stop`,index:0}))),!1)}stopTextBlock(e,t){t.textBlockStarted&&=(e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_stop`,{type:`content_block_stop`,index:this.getTextBlockIndex()}))),!1)}getTextBlockIndex(){return+!this.thinkingDisabled}getNextToolBlockIndex(e){return(this.thinkingDisabled?1:2)+e}};const ve=new Set([`type`,`format`,`description`,`nullable`,`enum`,`items`,`maxItems`,`minItems`,`properties`,`required`,`propertyOrdering`,`maxProperties`,`minProperties`,`minimum`,`maximum`,`minLength`,`maxLength`,`pattern`,`example`,`anyOf`,`title`]);var ye=class{transform(e,t,n,r=!1){let i=this.toGeminiContents(e.messages),a=this.toSystemInstruction(e.system),o=this.toGeminiTools(e.tools);return{modelPath:this.toGeminiModelPath(t),body:{contents:i,system_instruction:a,tools:o,tool_config:{function_calling_config:{mode:o.length>0?`AUTO`:`NONE`}},generationConfig:{maxOutputTokens:this.resolveMaxOutputTokens(e.max_tokens,n,r)}}}}toGeminiModelPath(e){return e.startsWith(`models/`)?e:`models/${e}`}toSystemInstruction(e){let t=this.extractTextParts(e);if(t.length!==0)return{parts:t.map(e=>({text:e}))}}toGeminiContents(e){let t=[];for(let n of e){let e=this.toGeminiParts(n.content);e.length!==0&&t.push({role:n.role===`assistant`?`model`:`user`,parts:e})}return t}toGeminiParts(e){if(typeof e==`string`)return e.trim()?[{text:e}]:[];if(!Array.isArray(e))return[];let t=[];for(let n of e)n.type===`text`&&n.text&&t.push({text:n.text}),n.type===`tool_use`&&t.push({functionCall:{name:n.name,args:n.input}}),n.type===`tool_result`&&t.push({functionResponse:{name:n.tool_use_id,response:{content:typeof n.content==`string`?n.content:JSON.stringify(n.content??{})}}});return t}toGeminiTools(e){return!e||e.length===0?[]:[{function_declarations:e.map(e=>({name:e.name,description:e.description,parameters:this.toGeminiParameters(e.input_schema)}))}]}toGeminiParameters(e){if(e)return this.sanitizeGeminiSchema(e)}sanitizeGeminiSchema(e){if(Array.isArray(e))return e.map(e=>this.sanitizeGeminiSchema(e));if(!e||typeof e!=`object`)return e;let t=Object.entries(e).flatMap(([e,t])=>{if(!ve.has(e))return[];if(e===`properties`&&t&&typeof t==`object`&&!Array.isArray(t)){let n=Object.entries(t).map(([e,t])=>[e,this.sanitizeGeminiSchema(t)]);return[[e,Object.fromEntries(n)]]}return[[e,this.sanitizeGeminiSchema(t)]]});return Object.fromEntries(t)}extractTextParts(e){return typeof e==`string`?e.trim()?[e]:[]:Array.isArray(e)?e.map(e=>typeof e==`string`?e:e&&typeof e==`object`&&`text`in e&&typeof e.text==`string`?e.text:null).filter(e=>!!e?.trim()):[]}resolveMaxOutputTokens(e,t,n){return typeof e==`number`&&e>0?e:n||t===`minimal`?2048:4096}};const C=`GEMINI_AUTH_CONFIG_ERROR`;var w=class extends Error{constructor(e,t){super(e),this.code=t,this.name=`GeminiAuthError`}},be=class{constructor(e=x,t=process.env){this.logger=e,this.env=t}async resolveHeaders(e){let t=e.authMode??`auto`;return t===`api-key`?this.resolveApiKeyHeaders(e):t===`oauth`?this.resolveOAuthHeaders():await this.tryResolveApiKeyHeaders(e)??this.resolveOAuthHeaders()}getGeminiDirectory(){let e=this.env.GEMINI_CLI_HOME||r.homedir();return i.join(e,`.gemini`)}getSettingsPath(){return i.join(this.getGeminiDirectory(),`settings.json`)}getOAuthPath(){return i.join(this.getGeminiDirectory(),`oauth_creds.json`)}async tryResolveApiKeyHeaders(e){try{return await this.resolveApiKeyHeaders(e)}catch{return null}}async resolveApiKeyHeaders(e){let t=e.apiKeyEnvVar??e.authTokenEnvVar??`GEMINI_API_KEY`,n=this.env[t]?.trim();if(!n)throw new w(`Missing Gemini API key. Set ${t}.`,C);return{headers:{"x-goog-api-key":n},authMode:`api-key`,authSource:`env`}}async resolveOAuthHeaders(){let e=await this.readSettings(),t=await this.readOAuthCredentials(),n=await this.refreshOAuthCredentialsIfNeeded(t),r=e?.security?.auth?.selectedType??null,i=n.access_token?.trim();if(!i)throw new w(`Missing Gemini OAuth credentials. Expected ${this.getOAuthPath()} or GEMINI_API_KEY.`,C);return{headers:{Authorization:`Bearer ${i}`},authMode:`oauth`,authSource:`gemini-home`,authType:r}}async readOAuthCredentials(){try{let e=await u.readFile(this.getOAuthPath(),`utf8`);return JSON.parse(e)}catch(e){throw this.logger.warn(`[GeminiAuth] Failed to read oauth credentials`,{oauthPath:this.getOAuthPath(),cause:e}),new w(`Unable to read Gemini OAuth credentials from ${this.getOAuthPath()}.`,C)}}async readSettings(){try{let e=await u.readFile(this.getSettingsPath(),`utf8`);return JSON.parse(e)}catch(e){return e?.code===`ENOENT`||this.logger.warn(`[GeminiAuth] Failed to read settings`,{settingsPath:this.getSettingsPath(),cause:e}),null}}async refreshOAuthCredentialsIfNeeded(e){if(!this.shouldRefreshCredentials(e))return e;let t=e.refresh_token?.trim();if(!t)return e;let n=new URLSearchParams({client_id:`681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com`,client_secret:`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`,grant_type:`refresh_token`,refresh_token:t}),r=await fetch(`https://oauth2.googleapis.com/token`,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:n});if(!r.ok)return this.logger.warn(`[GeminiAuth] Failed to refresh oauth credentials`,{status:r.status,oauthPath:this.getOAuthPath()}),e;let i=await r.json(),a={...e,access_token:i.access_token??e.access_token,token_type:i.token_type??e.token_type,scope:i.scope??e.scope,expiry_date:typeof i.expires_in==`number`?Date.now()+i.expires_in*1e3:e.expiry_date};return await u.writeFile(this.getOAuthPath(),`${JSON.stringify(a,null,2)}\n`,`utf8`),a}shouldRefreshCredentials(e){let t=e.access_token?.trim(),n=e.expiry_date;return t?typeof n==`number`?n<=Date.now()+6e4:!1:!0}};const T=`event: `,E=`data: `,xe=`message_start`,Se=`content_block_start`,Ce=`content_block_delta`,we=`content_block_stop`,Te=`message_delta`,Ee=`message_stop`,De=`end_turn`;var Oe=class{transformBuffered(e,t){let n=JSON.parse(e);return this.toClaudeStream([n],t)}transformStreaming(e,t){let n=[];for(let t of e.split(`
|
|
14
|
+
`).trim();if(!(!o||o===`[DONE]`))try{let e=JSON.parse(o),r=typeof e?.type==`string`?e.type:i,a=r.startsWith(`response.`)||i.startsWith(`response.`);if(e?.error||r===`response.error`){let r=e?.error?.message||e?.error?.error||e?.message||(typeof e?.error==`string`?e.error:`Unexpected API error`);this.emitStreamingError(n,t,r);return}if(a){this.processResponsesStreamingEvent(r,e,t,n);return}this.processChatCompletionStreamingChunk(e,t,n)}catch{}}processResponsesStreamingEvent(e,t,n,r){switch(t?.model&&typeof t.model==`string`&&(n.model=t.model),e){case`response.created`:typeof t?.response?.model==`string`&&(n.model=t.response.model);break;case`response.reasoning.delta`:case`response.reasoning_summary_text.delta`:{let e=[];this.markStreamedContentKey(t,n.streamedReasoningKeys,`reasoning`),this.collectTextFromNode(t?.delta??t?.text??t?.summary_text,e);for(let t of e)this.emitThinkingDelta(r,n,t);break}case`response.output_text.delta`:case`response.delta`:{let e=[];this.markStreamedContentKey(t,n.streamedTextKeys,`text`),this.collectTextFromNode(t?.delta??t?.output_text,e);for(let t of e)this.emitTextDelta(r,n,t);t?.delta?.tool_calls&&this.collectToolCalls(t.delta.tool_calls,n.toolCalls);break}case`response.function_call_arguments.delta`:case`response.function_call_arguments.done`:case`response.output_item.added`:case`response.output_item.done`:if(t?.item?.type===`message`){if(this.shouldEmitTerminalContent(t,n.streamedTextKeys,`text`,n.emittedText)){let e=[];this.collectTextFromNode(t.item.content,e);let i=e.join(``);if(i&&i===n.lastTextChunk)break;for(let t of e)this.emitTextDelta(r,n,t)}}else if(t?.item?.type===`reasoning`){if(this.shouldEmitTerminalContent(t,n.streamedReasoningKeys,`reasoning`,n.emittedThinking)){let e=[];this.collectTextFromNode(t.item.summary??t.item.content,e);let i=e.join(``);if(i&&i===n.lastThinkingChunk)break;for(let t of e)this.emitThinkingDelta(r,n,t)}}else this.collectResponsesToolCallDelta(t,n.toolCalls);break;case`response.output_text.done`:case`response.content_part.done`:if(n.textBlockStarted)break;if(this.shouldEmitTerminalContent(t,n.streamedTextKeys,`text`,n.emittedText)){let e=[];this.collectTextFromNode(t?.delta??t?.output_text??t?.text??t?.part,e);let i=e.join(``);if(i&&i===n.lastTextChunk)break;for(let t of e)this.emitTextDelta(r,n,t)}break;case`response.completed`:case`response.done`:{let e={textSegments:[],thinkingSegments:[],toolCalls:new Map(n.toolCalls),streamedReasoningKeys:new Set(n.streamedReasoningKeys),streamedTextKeys:new Set(n.streamedTextKeys),model:n.model,inputTokens:n.inputTokens,outputTokens:n.outputTokens,cachedTokens:n.cachedTokens,reasoningTokens:n.reasoningTokens,reasoningEffort:n.reasoningEffort,stopReason:n.stopReason};if(this.collectCompletedResponse(t,e),n.model=e.model,n.inputTokens=e.inputTokens,n.outputTokens=e.outputTokens,n.cachedTokens=e.cachedTokens,n.reasoningTokens=e.reasoningTokens,n.reasoningEffort=e.reasoningEffort,n.stopReason=e.stopReason,n.toolCalls=e.toolCalls,!n.emittedThinking)for(let t of e.thinkingSegments)this.emitThinkingDelta(r,n,t);if(!n.emittedText)for(let t of e.textSegments)this.emitTextDelta(r,n,t);this.emitPendingToolCalls(r,n),this.finishStreamingResponse(r,n);break}default:break}}processChatCompletionStreamingChunk(e,t,n){if(e&&(typeof e.model==`string`&&(t.model=e.model),e.usage&&(t.inputTokens=e.usage.prompt_tokens??t.inputTokens,t.outputTokens=e.usage.completion_tokens??t.outputTokens),Array.isArray(e.choices)))for(let r of e.choices){let e=[];this.collectTextFromNode(r?.delta??r?.message?.content,e);for(let r of e)this.emitTextDelta(n,t,r);r?.delta?.tool_calls&&this.collectToolCalls(r.delta.tool_calls,t.toolCalls),r?.finish_reason&&(t.stopReason=this.mapFinishReason(r.finish_reason))}}emitPendingToolCalls(e,t){let n=Array.from(t.toolCalls.entries()).sort((e,t)=>e[0]-t[0]).map(([e,t])=>({index:e,id:t.id||`tool_${s()}`,name:t.name,arguments:t.argumentChunks.join(``)})).filter(e=>e.name&&!t.emittedToolCallIndexes.has(e.index));for(let r of n){this.stopOpenContentBlocks(e,t);let n=this.getNextToolBlockIndex(r.index);e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_start`,{type:`content_block_start`,index:n,content_block:{type:`tool_use`,id:r.id,name:r.name,input:{}}})+(r.arguments?this.formatSseEvent(`content_block_delta`,{type:`content_block_delta`,index:n,delta:{type:`input_json_delta`,partial_json:r.arguments}}):``)+this.formatSseEvent(`content_block_stop`,{type:`content_block_stop`,index:n}))),t.emittedToolCallIndexes.add(r.index)}}emitThinkingDelta(e,t,n){this.thinkingDisabled||!n||(this.ensureMessageStarted(e,t),t.textBlockStarted&&this.stopTextBlock(e,t),t.thinkingBlockStarted||=(e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_start`,{type:`content_block_start`,index:0,content_block:{type:`thinking`,thinking:``}}))),!0),e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_delta`,{type:`content_block_delta`,index:0,delta:{type:`thinking_delta`,thinking:n}}))),t.emittedThinking=!0,t.lastThinkingChunk=n)}emitTextDelta(e,t,n){n&&(this.ensureMessageStarted(e,t),t.textBlockStarted||=(t.thinkingBlockStarted&&this.stopThinkingBlock(e,t),e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_start`,{type:`content_block_start`,index:this.getTextBlockIndex(),content_block:{type:`text`,text:``}}))),!0),e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_delta`,{type:`content_block_delta`,index:this.getTextBlockIndex(),delta:{type:`text_delta`,text:n}}))),t.emittedText=!0,t.lastTextChunk=n)}emitStreamingError(e,t,n){if(!t.messageStarted){e.enqueue(t.encoder.encode(this.createClaudeErrorStream(n))),t.messageStarted=!0,t.messageStopped=!0;return}!t.textBlockStarted&&!t.thinkingBlockStarted&&this.emitTextDelta(e,t,n),t.stopReason=`error`,this.finishStreamingResponse(e,t)}finishStreamingResponse(e,t){if(t.messageStopped)return;this.stopOpenContentBlocks(e,t);let n={output_tokens:t.outputTokens??0};if(t.cachedTokens||t.reasoningTokens||t.reasoningEffort){n.metadata={};let e=n.metadata;t.cachedTokens&&(e.cached_tokens=t.cachedTokens),t.reasoningTokens&&(e.reasoning_tokens=t.reasoningTokens),t.reasoningEffort&&(e.reasoning_effort=t.reasoningEffort)}e.enqueue(t.encoder.encode(this.formatSseEvent(`message_delta`,{type:`message_delta`,delta:{stop_reason:t.stopReason||`end_turn`,stop_sequence:null},usage:n})+this.formatSseEvent(`message_stop`,{type:`message_stop`}))),t.messageStopped=!0}ensureMessageStarted(e,t){t.messageStarted||=(e.enqueue(t.encoder.encode(this.formatSseEvent(`message_start`,{type:`message_start`,message:{id:t.messageId,type:`message`,role:`assistant`,content:[],model:t.model,stop_reason:null,stop_sequence:null,usage:{input_tokens:t.inputTokens??0,output_tokens:0}}}))),!0)}stopOpenContentBlocks(e,t){this.stopTextBlock(e,t),this.stopThinkingBlock(e,t)}stopThinkingBlock(e,t){t.thinkingBlockStarted&&=(e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_stop`,{type:`content_block_stop`,index:0}))),!1)}stopTextBlock(e,t){t.textBlockStarted&&=(e.enqueue(t.encoder.encode(this.formatSseEvent(`content_block_stop`,{type:`content_block_stop`,index:this.getTextBlockIndex()}))),!1)}getTextBlockIndex(){return+!this.thinkingDisabled}getNextToolBlockIndex(e){return(this.thinkingDisabled?1:2)+e}};const Se=new Set([`type`,`format`,`description`,`nullable`,`enum`,`items`,`maxItems`,`minItems`,`properties`,`required`,`propertyOrdering`,`maxProperties`,`minProperties`,`minimum`,`maximum`,`minLength`,`maxLength`,`pattern`,`example`,`anyOf`,`title`]);var Ce=class{transform(e,t,n,r=!1){let i=this.toGeminiContents(e.messages),a=this.toSystemInstruction(e.system),o=this.toGeminiTools(e.tools);return{modelPath:this.toGeminiModelPath(t),body:{contents:i,system_instruction:a,tools:o,tool_config:{function_calling_config:{mode:o.length>0?`AUTO`:`NONE`}},generationConfig:{maxOutputTokens:this.resolveMaxOutputTokens(e.max_tokens,n,r)}}}}toGeminiModelPath(e){return e.startsWith(`models/`)?e:`models/${e}`}toSystemInstruction(e){let t=this.extractTextParts(e);if(t.length!==0)return{parts:t.map(e=>({text:e}))}}toGeminiContents(e){let t=[];for(let n of e){let e=this.toGeminiParts(n.content);e.length!==0&&t.push({role:n.role===`assistant`?`model`:`user`,parts:e})}return t}toGeminiParts(e){if(typeof e==`string`)return e.trim()?[{text:e}]:[];if(!Array.isArray(e))return[];let t=[];for(let n of e)n.type===`text`&&n.text&&t.push({text:n.text}),n.type===`tool_use`&&t.push({functionCall:{name:n.name,args:n.input}}),n.type===`tool_result`&&t.push({functionResponse:{name:n.tool_use_id,response:{content:typeof n.content==`string`?n.content:JSON.stringify(n.content??{})}}});return t}toGeminiTools(e){return!e||e.length===0?[]:[{function_declarations:e.map(e=>({name:e.name,description:e.description,parameters:this.toGeminiParameters(e.input_schema)}))}]}toGeminiParameters(e){if(e)return this.sanitizeGeminiSchema(e)}sanitizeGeminiSchema(e){if(Array.isArray(e))return e.map(e=>this.sanitizeGeminiSchema(e));if(!e||typeof e!=`object`)return e;let t=Object.entries(e).flatMap(([e,t])=>{if(!Se.has(e))return[];if(e===`properties`&&t&&typeof t==`object`&&!Array.isArray(t)){let n=Object.entries(t).map(([e,t])=>[e,this.sanitizeGeminiSchema(t)]);return[[e,Object.fromEntries(n)]]}return[[e,this.sanitizeGeminiSchema(t)]]});return Object.fromEntries(t)}extractTextParts(e){return typeof e==`string`?e.trim()?[e]:[]:Array.isArray(e)?e.map(e=>typeof e==`string`?e:e&&typeof e==`object`&&`text`in e&&typeof e.text==`string`?e.text:null).filter(e=>!!e?.trim()):[]}resolveMaxOutputTokens(e,t,n){return typeof e==`number`&&e>0?e:n||t===`minimal`?2048:4096}};const w=`GEMINI_AUTH_CONFIG_ERROR`;var T=class extends Error{constructor(e,t){super(e),this.code=t,this.name=`GeminiAuthError`}},we=class{constructor(e=S,t=process.env){this.logger=e,this.env=t}async resolveHeaders(e){let t=e.authMode??`auto`;return t===`api-key`?this.resolveApiKeyHeaders(e):t===`oauth`?this.resolveOAuthHeaders():await this.tryResolveApiKeyHeaders(e)??this.resolveOAuthHeaders()}getGeminiDirectory(){let e=this.env.GEMINI_CLI_HOME||r.homedir();return i.join(e,`.gemini`)}getSettingsPath(){return i.join(this.getGeminiDirectory(),`settings.json`)}getOAuthPath(){return i.join(this.getGeminiDirectory(),`oauth_creds.json`)}async tryResolveApiKeyHeaders(e){try{return await this.resolveApiKeyHeaders(e)}catch{return null}}async resolveApiKeyHeaders(e){let t=e.apiKeyEnvVar??e.authTokenEnvVar??`GEMINI_API_KEY`,n=this.env[t]?.trim();if(!n)throw new T(`Missing Gemini API key. Set ${t}.`,w);return{headers:{"x-goog-api-key":n},authMode:`api-key`,authSource:`env`}}async resolveOAuthHeaders(){let e=await this.readSettings(),t=await this.readOAuthCredentials(),n=await this.refreshOAuthCredentialsIfNeeded(t),r=e?.security?.auth?.selectedType??null,i=n.access_token?.trim();if(!i)throw new T(`Missing Gemini OAuth credentials. Expected ${this.getOAuthPath()} or GEMINI_API_KEY.`,w);return{headers:{Authorization:`Bearer ${i}`},authMode:`oauth`,authSource:`gemini-home`,authType:r}}async readOAuthCredentials(){try{let e=await d.readFile(this.getOAuthPath(),`utf8`);return JSON.parse(e)}catch(e){throw this.logger.warn(`[GeminiAuth] Failed to read oauth credentials`,{oauthPath:this.getOAuthPath(),cause:e}),new T(`Unable to read Gemini OAuth credentials from ${this.getOAuthPath()}.`,w)}}async readSettings(){try{let e=await d.readFile(this.getSettingsPath(),`utf8`);return JSON.parse(e)}catch(e){return e?.code===`ENOENT`||this.logger.warn(`[GeminiAuth] Failed to read settings`,{settingsPath:this.getSettingsPath(),cause:e}),null}}async refreshOAuthCredentialsIfNeeded(e){if(!this.shouldRefreshCredentials(e))return e;let t=e.refresh_token?.trim();if(!t)return e;let n=new URLSearchParams({client_id:`681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com`,client_secret:`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`,grant_type:`refresh_token`,refresh_token:t}),r=await fetch(`https://oauth2.googleapis.com/token`,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:n});if(!r.ok)return this.logger.warn(`[GeminiAuth] Failed to refresh oauth credentials`,{status:r.status,oauthPath:this.getOAuthPath()}),e;let i=await r.json(),a={...e,access_token:i.access_token??e.access_token,token_type:i.token_type??e.token_type,scope:i.scope??e.scope,expiry_date:typeof i.expires_in==`number`?Date.now()+i.expires_in*1e3:e.expiry_date};return await d.writeFile(this.getOAuthPath(),`${JSON.stringify(a,null,2)}\n`,`utf8`),a}shouldRefreshCredentials(e){let t=e.access_token?.trim(),n=e.expiry_date;return t?typeof n==`number`?n<=Date.now()+6e4:!1:!0}};const E=`event: `,D=`data: `,Te=`message_start`,Ee=`content_block_start`,De=`content_block_delta`,Oe=`content_block_stop`,ke=`message_delta`,Ae=`message_stop`,je=`end_turn`;var Me=class{transformBuffered(e,t){let n=JSON.parse(e);return this.toClaudeStream([n],t)}transformStreaming(e,t){let n=[];for(let t of e.split(`
|
|
15
15
|
|
|
16
16
|
`)){let e=t.split(`
|
|
17
|
-
`).find(e=>e.startsWith(
|
|
18
|
-
`)}mapStopReason(e){return e?e===`STOP`?
|
|
17
|
+
`).find(e=>e.startsWith(D))?.slice(6).trim();!e||e===`[DONE]`||n.push(JSON.parse(e))}return this.toClaudeStream(n,t)}toClaudeStream(e,t){let n=e.flatMap(e=>e.candidates??[]).flatMap(e=>e.content?.parts??[]).map(e=>`text`in e&&typeof e.text==`string`?e.text:``).join(``),r=e.map(e=>e.candidates?.[0]?.finishReason).find(e=>typeof e==`string`),i=e.find(e=>e.usageMetadata)?.usageMetadata,a=`msg_${s()}`,o=[];return o.push(`${E}${Te}`),o.push(`${D}${JSON.stringify({type:Te,message:{id:a,type:`message`,role:`assistant`,content:[],model:t,stop_reason:null,stop_sequence:null,usage:{input_tokens:i?.promptTokenCount??0,output_tokens:0}}})}`),o.push(``),o.push(`${E}${Ee}`),o.push(`${D}${JSON.stringify({type:Ee,index:0,content_block:{type:`text`,text:``}})}`),o.push(``),n&&(o.push(`${E}${De}`),o.push(`${D}${JSON.stringify({type:De,index:0,delta:{type:`text_delta`,text:n}})}`),o.push(``)),o.push(`${E}${Oe}`),o.push(`${D}${JSON.stringify({type:Oe,index:0})}`),o.push(``),o.push(`${E}${ke}`),o.push(`${D}${JSON.stringify({type:ke,delta:{stop_reason:this.mapStopReason(r),stop_sequence:null},usage:{output_tokens:i?.candidatesTokenCount??0}})}`),o.push(``),o.push(`${E}${Ae}`),o.push(`${D}${JSON.stringify({type:Ae})}`),o.push(``),o.join(`
|
|
18
|
+
`)}mapStopReason(e){return e?e===`STOP`?je:`tool_use`:je}};const O=`conversation_history`,k={appendFailed:`HISTORY_APPEND_FAILED`,listFailed:`HISTORY_LIST_FAILED`,clearFailed:`HISTORY_CLEAR_FAILED`,statsFailed:`HISTORY_STATS_FAILED`,initFailed:`HISTORY_INIT_FAILED`},Ne=n.string().transform(e=>Number(e)).refine(e=>Number.isInteger(e)&&e>0,`Cursor must be a positive integer`);var A=class extends Error{constructor(e,t,n){super(e,n),this.code=t,this.name=`ConversationHistoryServiceError`}},Pe=class{sqlite=null;constructor(e=process.env.MODEL_PROXY_MCP_DB_PATH||ie,t=1e3,n=S){this.dbPath=e,this.retentionLimit=t,this.logger=n}async ensureInitialized(){await this.getDb()}async appendEntries(e){if(e.length!==0)try{let t=await this.getDb(),n=t.prepare(`
|
|
19
19
|
INSERT INTO conversation_history (
|
|
20
20
|
id, scope, request_id, direction, role, message_type, model, slot, payload_json, created_at
|
|
21
21
|
) VALUES (
|
|
22
22
|
@id, @scope, @request_id, @direction, @role, @message_type, @model, @slot, @payload_json, @created_at
|
|
23
23
|
)
|
|
24
|
-
`),r=t.transaction(e=>{for(let t of e)n.run({id:t.id??s(),scope:t.scope,request_id:t.requestId,direction:t.direction,role:t.role,message_type:t.messageType,model:t.model,slot:t.slot,payload_json:t.payloadJson,created_at:t.createdAt??new Date().toISOString()})}),i=new Map;for(let t of e){let e=i.get(t.scope)??[];e.push(t),i.set(t.scope,e)}for(let e of i.values())r(e);for(let e of i.keys())await this.pruneScope(e)}catch(e){throw this.logger.error(`[model-proxy-mcp] Failed to append history`,{code:
|
|
24
|
+
`),r=t.transaction(e=>{for(let t of e)n.run({id:t.id??s(),scope:t.scope,request_id:t.requestId,direction:t.direction,role:t.role,message_type:t.messageType,model:t.model,slot:t.slot,payload_json:t.payloadJson,created_at:t.createdAt??new Date().toISOString()})}),i=new Map;for(let t of e){let e=i.get(t.scope)??[];e.push(t),i.set(t.scope,e)}for(let e of i.values())r(e);for(let e of i.keys())await this.pruneScope(e)}catch(e){throw this.logger.error(`[model-proxy-mcp] Failed to append history`,{code:k.appendFailed,dbPath:this.dbPath,cause:e}),new A(`Failed to append conversation history`,k.appendFailed,{cause:e})}}async listHistory(e,t=50,n){try{let r=await this.getDb(),i=n?Ne.parse(n):void 0,a=Math.max(1,Math.min(t,200)),o=i?r.prepare(`
|
|
25
25
|
SELECT sequence, id, scope, request_id, direction, role, message_type, model, slot, payload_json, created_at
|
|
26
|
-
FROM ${
|
|
26
|
+
FROM ${O}
|
|
27
27
|
WHERE scope = ? AND sequence < ?
|
|
28
28
|
ORDER BY sequence DESC
|
|
29
29
|
LIMIT ?
|
|
30
30
|
`).all(e,i,a+1):r.prepare(`
|
|
31
31
|
SELECT sequence, id, scope, request_id, direction, role, message_type, model, slot, payload_json, created_at
|
|
32
|
-
FROM ${
|
|
32
|
+
FROM ${O}
|
|
33
33
|
WHERE scope = ?
|
|
34
34
|
ORDER BY sequence DESC
|
|
35
35
|
LIMIT ?
|
|
36
|
-
`).all(e,a+1),s=Number(r.prepare(`SELECT COUNT(*) as count FROM ${
|
|
36
|
+
`).all(e,a+1),s=Number(r.prepare(`SELECT COUNT(*) as count FROM ${O} WHERE scope = ?`).get(e).count),c=o.length>a?o.pop():void 0;return{items:o.map(e=>this.toEntry(e)),nextCursor:c?String(c.sequence):null,total:s}}catch(t){throw this.logger.error(`[model-proxy-mcp] Failed to list history`,{code:k.listFailed,scope:e,cause:t}),new A(`Failed to list conversation history`,k.listFailed,{cause:t})}}async clearScope(e){try{return(await this.getDb()).prepare(`DELETE FROM ${O} WHERE scope = ?`).run(e).changes}catch(t){throw this.logger.error(`[model-proxy-mcp] Failed to clear history`,{code:k.clearFailed,scope:e,cause:t}),new A(`Failed to clear conversation history`,k.clearFailed,{cause:t})}}async getStats(e){try{let t=(await this.getDb()).prepare(`
|
|
37
37
|
SELECT COUNT(*) as count, MIN(created_at) as oldest, MAX(created_at) as newest
|
|
38
|
-
FROM ${
|
|
38
|
+
FROM ${O}
|
|
39
39
|
WHERE scope = ?
|
|
40
|
-
`).get(e);return{scope:e,retentionLimit:this.retentionLimit,totalMessages:Number(t.count),oldestCreatedAt:t.oldest,newestCreatedAt:t.newest}}catch(t){throw this.logger.error(`[model-proxy-mcp] Failed to read history stats`,{code:
|
|
41
|
-
DELETE FROM ${
|
|
40
|
+
`).get(e);return{scope:e,retentionLimit:this.retentionLimit,totalMessages:Number(t.count),oldestCreatedAt:t.oldest,newestCreatedAt:t.newest}}catch(t){throw this.logger.error(`[model-proxy-mcp] Failed to read history stats`,{code:k.statsFailed,scope:e,cause:t}),new A(`Failed to read history stats`,k.statsFailed,{cause:t})}}async listScopes(){try{return(await this.getDb()).prepare(`SELECT DISTINCT scope FROM ${O} ORDER BY scope ASC`).all().map(e=>e.scope)}catch(e){return this.logger.error(`[model-proxy-mcp] Failed to list history scopes`,{code:k.listFailed,cause:e}),[]}}async pruneScope(e){let t=await this.getDb(),n=t.prepare(`SELECT COUNT(*) as count FROM ${O} WHERE scope = ?`).get(e),r=Number(n.count)-this.retentionLimit;r<=0||t.prepare(`
|
|
41
|
+
DELETE FROM ${O}
|
|
42
42
|
WHERE sequence IN (
|
|
43
43
|
SELECT sequence
|
|
44
|
-
FROM ${
|
|
44
|
+
FROM ${O}
|
|
45
45
|
WHERE scope = ?
|
|
46
46
|
ORDER BY sequence ASC
|
|
47
47
|
LIMIT ?
|
|
48
48
|
)
|
|
49
|
-
`).run(e,r)}async getDb(){if(this.sqlite)return this.sqlite;try{return await
|
|
50
|
-
CREATE TABLE IF NOT EXISTS ${
|
|
49
|
+
`).run(e,r)}async getDb(){if(this.sqlite)return this.sqlite;try{return await d.mkdir(i.dirname(this.dbPath),{recursive:!0}),this.sqlite=new f(this.dbPath),this.sqlite.pragma(`journal_mode = WAL`),this.sqlite.exec(`
|
|
50
|
+
CREATE TABLE IF NOT EXISTS ${O} (
|
|
51
51
|
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
52
52
|
id TEXT NOT NULL UNIQUE,
|
|
53
53
|
scope TEXT NOT NULL,
|
|
@@ -60,14 +60,14 @@ import"@hono/node-server";import{Hono as e}from"hono";import{ZodError as t,z as
|
|
|
60
60
|
payload_json TEXT NOT NULL,
|
|
61
61
|
created_at TEXT NOT NULL
|
|
62
62
|
)
|
|
63
|
-
`),this.sqlite.exec(`CREATE INDEX IF NOT EXISTS conversation_history_scope_sequence_idx ON ${D} (scope, sequence DESC)`),this.sqlite.exec(`CREATE INDEX IF NOT EXISTS conversation_history_scope_request_idx ON ${D} (scope, request_id)`),this.sqlite}catch(e){throw this.logger.error(`[model-proxy-mcp] Failed to initialize history database`,{code:O.initFailed,dbPath:this.dbPath,cause:e}),new k(`Failed to initialize history database`,O.initFailed,{cause:e})}}toEntry(e){return{id:e.id,scope:e.scope,requestId:e.request_id,direction:e.direction,role:e.role,messageType:e.message_type,model:e.model,slot:e.slot,payloadJson:e.payload_json,createdAt:e.created_at}}};const A=`default`,je=`zai-anthropic-compat`,Me=`.yaml`,j=`medium`,M=[`default`,`sonnet`,`opus`,`haiku`,`subagent`],N=`[model-proxy-mcp]`,P=`utf8`,Ne=[`chatgpt-codex`,`anthropic-compatible`,`gemini-direct`],F={profileNotFound:`PROFILE_NOT_FOUND`,settingsReadFailed:`SETTINGS_READ_FAILED`,settingsWriteFailed:`SETTINGS_WRITE_FAILED`},Pe=n.enum([`minimal`,`low`,`medium`,`high`]),Fe=n.object({type:n.enum(Ne),endpoint:n.url(),authTokenEnvVar:n.string().min(1).nullable().optional(),apiTimeoutMs:n.number().int().positive().nullable().optional(),authMode:n.enum([`auto`,`api-key`,`oauth`]).nullable().optional(),apiKeyEnvVar:n.string().min(1).nullable().optional(),project:n.string().min(1).nullable().optional(),location:n.string().min(1).nullable().optional(),apiVersion:n.string().min(1).nullable().optional()}),Ie=n.object({providers:n.record(n.string().min(1),Fe)}),I=n.object({id:n.string().min(1),label:n.string().min(1),provider:n.string().min(1),model:n.string().min(1),reasoningEffort:Pe.default(j),enabled:n.boolean().default(!0)}),Le=n.object({provider:n.string().min(1),model:n.string().min(1),reasoningEffort:Pe.default(j),thinkingDisabled:n.boolean().optional()}),L=n.object({main:Le,fallbacks:n.array(Le).default([])}),R=n.object({models:n.object({default:L.optional(),sonnet:L.optional(),opus:L.optional(),haiku:L.optional(),subagent:L.optional()}).default({})}),Re=n.array(I),ze=n.object({models:n.object({default:L.nullable().optional(),sonnet:L.nullable().optional(),opus:L.nullable().optional(),haiku:L.nullable().optional(),subagent:L.nullable().optional()}).optional()});var z=class extends Error{constructor(e,t,n){super(e,n),this.code=t,this.name=`ProfileStoreError`}},Be=class{constructor(e=process.env.MODEL_PROXY_MCP_PROVIDER_PATH||v,t=process.env.MODEL_PROXY_MCP_MODEL_LIST_PATH||i.join(i.dirname(process.env.MODEL_PROXY_MCP_PROVIDER_PATH||v),i.basename(te)),n=process.env.MODEL_PROXY_MCP_SCOPE_DIR||i.join(i.dirname(process.env.MODEL_PROXY_MCP_PROVIDER_PATH||v),i.basename(ne)),r=x){this.providerConfigPath=e,this.modelListPath=t,this.scopeSettingsDir=n,this.logger=r}getConfigPath(e=A){return this.getScopeConfigPath(e)}async ensureConfig(e=A,t){return this.getConfig(e,t)}async listScopes(){try{let e=await u.readdir(this.scopeSettingsDir,{withFileTypes:!0}),t=new Set([A]);for(let n of e){if(!n.isFile()||!n.name.endsWith(Me))continue;let e=n.name.slice(0,-5);e&&t.add(e)}return Array.from(t).sort()}catch(e){if(this.isFileNotFoundError(e))return[A];throw this.logger.error(`${N} Failed to list scopes`,{scopeSettingsDir:this.scopeSettingsDir,cause:e}),new z(`Failed to list scopes in ${this.scopeSettingsDir}`,F.settingsReadFailed,{cause:e})}}async getConfig(e=A,t){return this.toProxyConfig(await this.getSettings(e,t))}async getAdminConfig(e=A){let t=await this.getSettings(e),n=this.toProfiles(t),r=Object.fromEntries(M.map(e=>[e,this.resolveSlotConfig(t,n,e)]));return{scope:e,providerConfigPath:this.providerConfigPath,modelListPath:this.modelListPath,scopeConfigPath:this.getScopeConfigPath(e),providers:t.providers,models:n,scopeModels:t.scope.models,slots:r}}async listProfiles(e=A){return(await this.getAdminConfig(e)).models}async getActiveProfile(e=A,t=A){let n=await this.getAdminConfig(e),r=n.slots[t];return n.models.find(e=>e.id===r.profileId&&e.enabled)??null}async getResolvedSlotConfig(e=A,t=A){return(await this.getAdminConfig(e)).slots[t]}async setActiveProfile(e,t=A,n=A){let r=await this.getSettings(t),i=this.toProfiles(r).find(t=>t.id===e&&t.enabled);if(!i)throw new z(`Profile not found or disabled: ${e}`,F.profileNotFound,{cause:{profileId:e,scope:t,slot:n}});return this.updateConfig({models:{[n]:{main:{provider:i.provider,model:i.model,reasoningEffort:i.reasoningEffort,thinkingDisabled:r.scope.models[n]?.main.thinkingDisabled??!1},fallbacks:r.scope.models[n]?.fallbacks??[]}}},t)}async upsertProfile(e,t=A){let n=I.parse({id:e.id,label:e.label,provider:e.provider,model:e.model,reasoningEffort:e.reasoningEffort,enabled:e.enabled}),r=await this.getSettings(t),i=r.models.filter(e=>e.id!==n.id);i.push(n);let a=this.normalizeSettings({providers:r.providers,models:i,scope:r.scope});return await this.saveSettings(a,t),this.toProxyConfig(a)}async updateConfig(e,t=A){let n=ze.parse(e),r=await this.getSettings(t),i={...r.scope.models};for(let e of M){let t=n.models?.[e];if(t!==void 0){if(t===null){delete i[e];continue}i[e]=t}}let a=this.normalizeSettings({providers:r.providers,models:r.models,scope:{models:i}});return await this.saveSettings(a,t),this.toProxyConfig(a)}getScopeConfigPath(e){return i.join(this.scopeSettingsDir,`${this.sanitizeScope(e)}${Me}`)}sanitizeScope(e){return e.replace(/[^a-zA-Z0-9._-]/g,`-`).replace(/--+/g,`-`).replace(/^-|-$/g,``)||A}async getSettings(e,t){let n=await this.readSettings(e,t),r=this.normalizeSettings(n);return JSON.stringify(r)!==JSON.stringify(n)&&await this.saveSettings(r,e),r}async readSettings(e,t){let n=this.getScopeConfigPath(e),[r,i,a]=await Promise.allSettled([u.readFile(this.providerConfigPath,P),u.readFile(this.modelListPath,P),u.readFile(n,P)]);try{let n=r.status===`fulfilled`?r.value:null,o=i.status===`fulfilled`?i.value:null,s=a.status===`fulfilled`?a.value:null,c=r.status===`rejected`&&this.isFileNotFoundError(r.reason),l=i.status===`rejected`&&this.isFileNotFoundError(i.reason),u=a.status===`rejected`&&this.isFileNotFoundError(a.reason);if(!n&&r.status===`rejected`&&!c)throw r.reason;if(!o&&i.status===`rejected`&&!l)throw i.reason;if(!s&&a.status===`rejected`&&!u)throw a.reason;let d=s?R.parse(f(s)):await this.loadScopeSeedSettings(e,t),p=this.normalizeSettings({providers:n?Ie.parse(f(n)).providers:structuredClone(se),models:o?Re.parse(f(o)):structuredClone(ce),scope:d});return(c||l||u)&&await this.saveSettings(p,e),p}catch(t){throw this.logger.error(`${N} Failed to read settings`,{scope:e,providerConfigPath:this.providerConfigPath,modelListPath:this.modelListPath,scopeConfigPath:n,code:F.settingsReadFailed,cause:t}),new z(`Failed to read settings from ${this.providerConfigPath}, ${this.modelListPath}, and ${n}`,F.settingsReadFailed,{cause:t})}}isFileNotFoundError(e){return e?.code===`ENOENT`}async loadScopeSeedSettings(e,t){let n=[t,this.getDefaultScopeSeedPath(e)].filter(e=>!!e);for(let t of n)try{let e=await u.readFile(t,P);return R.parse(f(e))}catch(n){if(this.isFileNotFoundError(n))continue;throw this.logger.error(`${N} Failed to read scope seed settings`,{scope:e,seedConfigPath:t,cause:n}),n}return structuredClone(le)}getDefaultScopeSeedPath(e){let t=this.getScopeConfigPath(A);return e===A?null:t}normalizeSettings(e){let t=this.normalizeProviders(Object.keys(e.providers).length>0?e.providers:structuredClone(se)),n=e.models.map(e=>I.parse(e)),r=this.normalizeSlotSelection(e.scope.models.default?.main??null,n,t,this.getDefaultSelection(n,t)),i={default:r?{main:r,fallbacks:this.normalizeFallbacks(e.scope.models.default?.fallbacks??[],r,n,t)}:void 0};for(let a of M){if(a===A)continue;let o=r??this.getDefaultSelection(n,t),s=this.normalizeSlotSelection(e.scope.models[a]?.main??null,n,t,o);i[a]=s?{main:s,fallbacks:this.normalizeFallbacks(e.scope.models[a]?.fallbacks??[],s,n,t)}:void 0}return{providers:t,models:n,scope:{models:i}}}normalizeProviders(e){let t={...e},n=t[je];return n?.type===`anthropic-compatible`&&n.authTokenEnvVar===`ANTHROPIC_AUTH_TOKEN`&&(t[je]={...n,authTokenEnvVar:`ZAI_ANTHROPIC_AUTH_TOKEN`}),t}getDefaultSelection(e,t){let n=e.find(e=>e.enabled&&t[e.provider]);return n?{provider:n.provider,model:n.model,reasoningEffort:n.reasoningEffort,thinkingDisabled:!1}:null}normalizeSlotSelection(e,t,n,r){if(!e)return r?{...r}:null;let i=n[e.provider]?e.provider:r?.provider??null;if(!i)return null;if(i!==e.provider)return r&&n[r.provider]?{...r}:null;let a=t.find(t=>t.enabled&&t.provider===i&&t.model===e.model);return{provider:i,model:a?.model??e.model,reasoningEffort:e.reasoningEffort??a?.reasoningEffort??j,thinkingDisabled:e.thinkingDisabled??!1}}normalizeFallbacks(e,t,n,r){let i=[],a=new Set([`${t.provider}:${t.model}:${t.reasoningEffort}:${t.thinkingDisabled?`off`:`on`}`]);for(let t of e){let e=this.normalizeSlotSelection(t,n,r,null);if(!e)continue;let o=`${e.provider}:${e.model}:${e.reasoningEffort}:${e.thinkingDisabled?`off`:`on`}`;a.has(o)||(a.add(o),i.push(e))}return i}toProxyConfig(e){let t=this.toProfiles(e),n=Object.fromEntries(M.map(n=>[n,this.resolveSlotConfig(e,t,n).profileId]));return{activeProfileId:n.default??null,slotProfileIds:n,providers:e.providers,profiles:t,scope:e.scope}}resolveSlotConfig(e,t,n){let r=e.scope.models[n]??e.scope.models.default,i=r?.main??null,a=i?t.find(e=>e.provider===i.provider&&e.model===i.model&&e.enabled):void 0,o=i?.provider?e.providers[i.provider]:void 0;return{slot:n,profileId:a?.id??null,label:a?.label??null,provider:i?.provider??null,providerType:o?.type??null,endpoint:o?.endpoint??null,model:i?.model??null,reasoningEffort:i?.reasoningEffort??a?.reasoningEffort??j,thinkingDisabled:i?.thinkingDisabled??!1,fallbacks:r?.fallbacks??[]}}toProfiles(e){return e.models.map(t=>({id:t.id,label:t.label,provider:t.provider,providerType:e.providers[t.provider]?.type??null,model:t.model,endpoint:e.providers[t.provider]?.endpoint??null,reasoningEffort:t.reasoningEffort,enabled:t.enabled}))}async saveSettings(e,t=A){let n=this.getScopeConfigPath(t);try{let t=new Set([i.dirname(this.providerConfigPath),i.dirname(this.modelListPath),i.dirname(n)]);await Promise.all(Array.from(t,e=>u.mkdir(e,{recursive:!0}))),await Promise.all([u.writeFile(this.providerConfigPath,p(Ie.parse({providers:e.providers}),{indent:2}),P),u.writeFile(this.modelListPath,p(Re.parse(e.models),{indent:2}),P),u.writeFile(n,p(R.parse(e.scope),{indent:2}),P)])}catch(e){throw this.logger.error(`${N} Failed to write settings`,{scope:t,providerConfigPath:this.providerConfigPath,modelListPath:this.modelListPath,scopeConfigPath:n,code:F.settingsWriteFailed,cause:e}),new z(`Failed to write settings to ${this.providerConfigPath}, ${this.modelListPath}, and ${n}`,F.settingsWriteFailed,{cause:e})}}};const Ve={"ccproxy-default":`default`,"ccproxy-sonnet":`sonnet`,"ccproxy-opus":`opus`,"ccproxy-haiku":`haiku`,"ccproxy-subagent":`subagent`},B=`default`,V=`default`,He=[`default`,`sonnet`,`opus`,`haiku`,`subagent`],Ue=`https://api.anthropic.com/v1/messages`,We=`anthropic-compatible`,Ge=`ANTHROPIC_AUTH_TOKEN`,H=`application/json`,U=`text/event-stream; charset=utf-8`,W=`content-type`,Ke=`cache-control`,qe=`connection`,G=`accept`,Je=`anthropic-version`,Ye=`anthropic-beta`,Xe=`no-cache`,Ze=`keep-alive`,Qe=`request`,$e=`response`,et=`system`,tt=`assistant`,K=`MODEL_NOT_FOUND`,nt=`REQUEST_FORWARD_FAILED`,rt=`generateContent`,it=n.object({model:n.string().optional(),system:n.unknown().optional(),messages:n.array(n.unknown()).default([]),tools:n.array(n.unknown()).optional(),max_tokens:n.number().int().positive().optional(),stream:n.boolean().optional()});var q=class extends Error{constructor(e,t,n,r){super(e,r),this.code=t,this.status=n,this.name=new.target.name}},at=class extends q{},J=class extends q{},Y=class extends q{},X=class extends q{constructor(e,t,n,r,i){super(e,nt,t,i),this.upstreamBody=n,this.attemptLabel=r}};const ot=new Set([408,429,500,502,503,504]);var st=class{codeAssistProjectCache=new Map;constructor(e=new Be,t=new he(x,ie),n=x,r=fetch,i=new Ae){this.profileStore=e,this.codexAuth=t,this.logger=n,this.fetchImpl=r,this.historyService=i}async listProfiles(e=B){return this.profileStore.listProfiles(e)}async ensureConfig(e=B){return await this.historyService.ensureInitialized(),this.profileStore.ensureConfig(e)}async getAdminConfig(e=B){return this.profileStore.getAdminConfig(e)}async updateAdminConfig(e,t=B){return await this.profileStore.updateConfig(e,t),this.profileStore.getAdminConfig(t)}async getActiveProfile(e=B,t=V){return this.profileStore.getActiveProfile(e,t)}async upsertProfile(e,t=B){return this.profileStore.upsertProfile(e,t)}async setActiveProfile(e,t=B,n=V){return this.profileStore.setActiveProfile(e,t,n)}async getCurrentModel(e=B,t=V){return this.profileStore.getActiveProfile(e,t)}async switchModel(e,t=B,n=V){let r=await this.profileStore.listProfiles(t),i=await this.profileStore.getResolvedSlotConfig(t,n),a=r.find(t=>t.model===e&&t.provider===i.provider&&t.enabled)??r.find(t=>t.model===e&&t.enabled);if(!a)throw new J(`Model not found or disabled: ${e}`,K,400,{cause:{model:e,scope:t,slot:n}});return this.profileStore.updateConfig({models:{[n]:{main:{provider:a.provider,model:a.model,reasoningEffort:a.reasoningEffort},fallbacks:(await this.profileStore.getAdminConfig(t)).scopeModels[n]?.fallbacks??[]}}},t)}async listHistory(e=B,t=50,n){return this.historyService.listHistory(e,t,n)}async clearHistory(e=B){return{deleted:await this.historyService.clearScope(e)}}async listScopes(){let[e,t]=await Promise.all([this.profileStore.listScopes(),this.historyService.listScopes()]);return Array.from(new Set([...e,...t,B])).sort()}async getHistoryStats(e=B){return this.historyService.getStats(e)}async getStatus(e=B,t,n){let r=await this.profileStore.getConfig(e),i=await this.profileStore.getResolvedSlotConfig(e,V),a=await this.codexAuth.getAuthStatus(),o=Object.fromEntries(await Promise.all(He.map(async t=>{let n=await this.profileStore.getResolvedSlotConfig(e,t);return[t,{profileId:n.profileId,provider:n.provider,model:n.model,reasoningEffort:n.reasoningEffort,thinkingDisabled:n.thinkingDisabled??!1}]})));return{running:!0,port:t,pid:n,scope:e,activeProfileId:r.activeProfileId,activeModel:i.model??void 0,activeReasoningEffort:i.reasoningEffort,slotModels:o,auth:a,profiles:r.profiles}}async getModels(e=B){return{object:`list`,data:(await this.profileStore.listProfiles(e)).map(e=>({id:e.model,object:`model`,created:0,owned_by:`${e.provider}:${e.id}`,metadata:{profileId:e.id,reasoningEffort:e.reasoningEffort}}))}}async forwardClaudeRequest(e,t=B,n=new Headers){let r=s(),i=V,a=null;try{let o=this.parseClaudeRequest(e),s=await this.profileStore.getAdminConfig(t);if(i=this.resolveSlot(o.model,s),a=this.resolveRequestedModel(o.model,s,i),!a.model){let n=`No active model configured for scope '${t}' and slot '${i}'`;return await this.recordError(t,i,a,r,n,e),this.createErrorResponse(400,n)}let c=this.buildAttemptTargets(s,a,i);if(c.length===0){let n=`No provider configured for scope '${t}' and slot '${i}'`;return await this.recordError(t,i,a,r,n,e),this.createErrorResponse(400,n)}await this.recordRequest(t,i,c[0].resolved,r,o,e);let l=null;for(let a of c){let s=await this.forwardSingleAttempt(a,o,e,t,i,n);if(s.ok)return await this.recordResponse(t,i,a.resolved,r,s.success.history.upstreamText,s.success.history.claudeBody),s.success.response;let{error:c,payloadForHistory:u}=s.failure;if(!this.isRecoverableUpstreamError(c))return this.logger.error(`[model-proxy-mcp] Non-recoverable model attempt failed`,{scope:t,slot:i,model:a.resolved.model,code:c.code,status:c.status,message:c.message}),await this.recordError(t,i,a.resolved,r,c.message,u),this.createErrorResponse(c.status,c.message);l=s.failure,this.logger.warn(`[model-proxy-mcp] Recoverable model attempt failed; trying fallback`,{scope:t,slot:i,model:a.resolved.model,code:c.code,status:c.status,message:c.message}),await this.recordError(t,i,a.resolved,r,`Attempt failed for ${a.label}: ${c.message}`,u)}if(l)return await this.recordError(t,i,a,r,l.error.message,l.payloadForHistory),this.createErrorResponse(l.error.status,l.error.message);let u=`No model attempt could be executed`;return await this.recordError(t,i,a,r,u,e),this.createErrorResponse(500,u)}catch(n){let o=n instanceof q?n:new q(`Failed to process Claude proxy request`,nt,500,{cause:n});return this.logger.error(`[model-proxy-mcp] Request forwarding failed`,{scope:t,slot:i,code:o.code,message:o.message,cause:o.cause}),await this.recordError(t,i,a,r,o.message,e),this.createErrorResponse(o.status,o.message)}}buildAttemptTargets(e,t,n){let r=[],i=new Set,a=[t,...t.fallbacks.map(e=>({...t,...e,slot:n}))];for(let t of a){let a=t.provider,o=t.model;if(!a||!o)continue;let s=e.providers[a];if(!s)continue;let c=`${a}:${o}:${t.reasoningEffort}:${t.thinkingDisabled?`off`:`on`}`;i.has(c)||(i.add(c),r.push({resolved:{...t,slot:n,provider:a,model:o,providerType:s.type,endpoint:s.endpoint,reasoningEffort:t.reasoningEffort,thinkingDisabled:t.thinkingDisabled??!1,fallbacks:[]},provider:s,label:`${a}/${o}`}))}return r}async forwardSingleAttempt(e,t,n,r,i,a){try{return e.resolved.providerType===We?{ok:!0,success:await this.forwardAnthropicCompatibleRequest(n,r,i,e.resolved,e.provider,a)}:e.resolved.providerType===`gemini-direct`?{ok:!0,success:await this.forwardGeminiDirectRequest(t,r,i,e.resolved,e.provider)}:{ok:!0,success:await this.forwardCodexRequest(n,r,i,e.resolved,e.provider)}}catch(e){return{ok:!1,failure:{error:e instanceof q?e:new q(`Failed to process Claude proxy request`,nt,500,{cause:e}),payloadForHistory:e instanceof X?e.upstreamBody:n}}}}isRecoverableUpstreamError(e){return e instanceof X&&ot.has(e.status)}createCodexSessionId(e,t,n){let r=[y,`codex`,e,t,n.provider??`unknown-provider`,n.model??`unknown-model`].join(`:`);return a(`sha256`).update(r).digest(`hex`)}createGeminiSessionId(e,t,n){let r=[y,`gemini-code-assist`,e,t,n.provider??`unknown-provider`,n.model??`unknown-model`].join(`:`);return a(`sha256`).update(r).digest(`hex`)}createCodeAssistProjectCacheKey(e){let t=process.env.GOOGLE_CLOUD_PROJECT||process.env.GOOGLE_CLOUD_PROJECT_ID||``,n=Object.entries(e.headers).sort(([e],[t])=>e.localeCompare(t));return JSON.stringify({authMode:e.authMode,authSource:e.authSource,authType:e.authType??null,configuredProject:t,headers:n})}async forwardCodexRequest(e,t,n,r,i){let a=r.model;if(!a)throw new J(`No active model configured for slot '${n}'`,K,400);let o=await this.codexAuth.resolveAuth(),s=i.apiKeyEnvVar?process.env[i.apiKeyEnvVar]:void 0;if(!o.accessToken&&!s)throw new Y(`Missing Codex auth. Sign in with the official Codex CLI first.`,`AUTH_MISSING`,401);let c=new me({toProvider:`chatgpt-codex`,toModel:a,toEndpoint:r.endpoint??Ue,toApiKey:s,sessionId:this.createCodexSessionId(t,n,r),sessionReasoningEffort:r.reasoningEffort,thinkingDisabled:r.thinkingDisabled??!1,logger:this.logger,resolvedAuth:o}),l=new _e(this.logger,r.thinkingDisabled??!1),u=await c.transform(Ue,e),d=await this.fetchImpl(u.url,{method:`POST`,headers:u.headers,body:u.body});if(!d.ok){let e=await d.text();throw this.logger.error(`[model-proxy-mcp] Upstream request failed`,{scope:t,slot:n,profileId:r.profileId,model:r.model,status:d.status,body:e}),new X(e||`Codex upstream request failed`,d.status,e,`${r.provider}/${r.model}`)}let f=await d.text(),p=l.transform(f);return{response:{status:200,body:p,headers:this.createSuccessHeaders(t,n)},history:{upstreamText:f,claudeBody:p}}}async forwardGeminiDirectRequest(e,t,n,r,i){try{let a=r.model;if(!a)throw new J(`No active Gemini model configured for scope '${t}' and slot '${n}'`,K,400);let o=await new be(this.logger).resolveHeaders(i),s=new ye,c=new Oe,l={model:e.model,system:e.system,messages:e.messages,tools:e.tools,max_tokens:e.max_tokens},u=s.transform(l,a,r.reasoningEffort,r.thinkingDisabled??!1);if(o.authType===`oauth-personal`)return await this.forwardGeminiCodeAssistRequest(u.body,t,n,r,o,c);let d=this.createGeminiRequestUrl(i,u.modelPath,rt),f=new Headers(o.headers);f.set(W,H),f.set(G,H);let p=i.apiTimeoutMs??this.resolveUpstreamTimeoutMs(),m=await this.fetchImpl(d,{method:`POST`,headers:f,body:JSON.stringify(u.body),signal:p?AbortSignal.timeout(p):void 0}),h=await m.text();if(!m.ok)throw this.logger.error(`[model-proxy-mcp] Gemini upstream request failed`,{scope:t,slot:n,profileId:r.profileId,model:r.model,status:m.status,body:h,authMode:o.authMode,authSource:o.authSource}),new X(h||`Gemini upstream request failed`,m.status,h,`${r.provider}/${r.model}`);let g=c.transformBuffered(h,a);return{response:{status:200,body:g,headers:this.createSuccessHeaders(t,n)},history:{upstreamText:h,claudeBody:g}}}catch(e){throw e instanceof w?new Y(e.message,e.code,401,{cause:e}):e}}async forwardGeminiCodeAssistRequest(e,t,n,r,i,a){let o=await this.resolveCodeAssistProjectId(i),c=this.createCodeAssistUrl(rt),l=new Headers(i.headers);l.set(W,H),l.set(G,H);let u={model:r.model,project:o,user_prompt_id:s(),request:this.toCodeAssistGenerateContentRequest(e,this.createGeminiSessionId(t,n,r))},d=await this.fetchImpl(c,{method:`POST`,headers:l,body:JSON.stringify(u)}),f=await d.text();if(!d.ok)throw this.logger.error(`[model-proxy-mcp] Gemini Code Assist request failed`,{scope:t,slot:n,profileId:r.profileId,model:r.model,status:d.status,body:f,authMode:i.authMode,authSource:i.authSource,authType:i.authType}),new X(f||`Gemini Code Assist request failed`,d.status,f,`${r.provider}/${r.model}`);let p=this.normalizeCodeAssistBufferedResponse(f),m=a.transformBuffered(p,r.model??`gemini`);return{response:{status:200,body:m,headers:this.createSuccessHeaders(t,n)},history:{upstreamText:f,claudeBody:m}}}createSuccessHeaders(e,t){return{[W]:U,[Ke]:Xe,[qe]:Ze,"x-model-proxy-service":y,"x-model-proxy-scope":e,"x-model-proxy-slot":t}}createGeminiRequestUrl(e,t,n){let r=e.endpoint.replace(/\/$/,``),i=e.apiVersion??`v1beta`;return new URL(`${r}/${i}/${t}:${n}`).toString()}createCodeAssistUrl(e){return new URL(`https://cloudcode-pa.googleapis.com/v1internal:${e}`).toString()}async resolveCodeAssistProjectId(e){let t=this.createCodeAssistProjectCacheKey(e),n=this.codeAssistProjectCache.get(t);if(n)return n;let r=new Headers(e.headers);r.set(W,H);let i=process.env.GOOGLE_CLOUD_PROJECT||process.env.GOOGLE_CLOUD_PROJECT_ID||void 0,a=await this.fetchImpl(this.createCodeAssistUrl(`loadCodeAssist`),{method:`POST`,headers:r,body:JSON.stringify({cloudaicompanionProject:i,metadata:{ideType:`IDE_UNSPECIFIED`,platform:`PLATFORM_UNSPECIFIED`,pluginType:`GEMINI`,duetProject:i}})}),o=await a.text();if(!a.ok)throw new X(o||`Failed to load Gemini Code Assist project context`,a.status,o,`google-gemini-direct/loadCodeAssist`);let s=JSON.parse(o).cloudaicompanionProject||i;if(!s)throw new Y(`Gemini Code Assist did not return a usable project ID.`,`GEMINI_PROJECT_MISSING`,401);return this.codeAssistProjectCache.set(t,s),s}toCodeAssistGenerateContentRequest(e,t){return{contents:e.contents,systemInstruction:e.system_instruction,tools:e.tools,toolConfig:e.tool_config,generationConfig:e.generationConfig,session_id:t}}normalizeCodeAssistBufferedResponse(e){let t=JSON.parse(e);return JSON.stringify(this.extractCodeAssistResponse(t))}normalizeCodeAssistStreamingResponse(e){let t=[];for(let n of e.split(`
|
|
63
|
+
`),this.sqlite.exec(`CREATE INDEX IF NOT EXISTS conversation_history_scope_sequence_idx ON ${O} (scope, sequence DESC)`),this.sqlite.exec(`CREATE INDEX IF NOT EXISTS conversation_history_scope_request_idx ON ${O} (scope, request_id)`),this.sqlite}catch(e){throw this.logger.error(`[model-proxy-mcp] Failed to initialize history database`,{code:k.initFailed,dbPath:this.dbPath,cause:e}),new A(`Failed to initialize history database`,k.initFailed,{cause:e})}}toEntry(e){return{id:e.id,scope:e.scope,requestId:e.request_id,direction:e.direction,role:e.role,messageType:e.message_type,model:e.model,slot:e.slot,payloadJson:e.payload_json,createdAt:e.created_at}}};const j=`default`,Fe=`zai-anthropic-compat`,Ie=`.yaml`,M=`medium`,N=[`default`,`sonnet`,`opus`,`haiku`,`subagent`],P=`[model-proxy-mcp]`,F=`utf8`,Le=[`chatgpt-codex`,`anthropic-compatible`,`gemini-direct`],I={profileNotFound:`PROFILE_NOT_FOUND`,settingsReadFailed:`SETTINGS_READ_FAILED`,settingsWriteFailed:`SETTINGS_WRITE_FAILED`},Re=n.enum([`minimal`,`low`,`medium`,`high`]),ze=n.object({type:n.enum(Le),endpoint:n.url(),authTokenEnvVar:n.string().min(1).nullable().optional(),apiTimeoutMs:n.number().int().positive().nullable().optional(),authMode:n.enum([`auto`,`api-key`,`oauth`]).nullable().optional(),apiKeyEnvVar:n.string().min(1).nullable().optional(),project:n.string().min(1).nullable().optional(),location:n.string().min(1).nullable().optional(),apiVersion:n.string().min(1).nullable().optional()}),Be=n.object({providers:n.record(n.string().min(1),ze)}),L=n.object({id:n.string().min(1),label:n.string().min(1),provider:n.string().min(1),model:n.string().min(1),reasoningEffort:Re.default(M),enabled:n.boolean().default(!0)}),Ve=n.object({provider:n.string().min(1),model:n.string().min(1),reasoningEffort:Re.default(M),thinkingDisabled:n.boolean().optional()}),R=n.object({main:Ve,fallbacks:n.array(Ve).default([])}),He=n.object({models:n.object({default:R.optional(),sonnet:R.optional(),opus:R.optional(),haiku:R.optional(),subagent:R.optional()}).default({})}),Ue=n.array(L),We=n.object({models:n.object({default:R.nullable().optional(),sonnet:R.nullable().optional(),opus:R.nullable().optional(),haiku:R.nullable().optional(),subagent:R.nullable().optional()}).optional()});var z=class extends Error{constructor(e,t,n){super(e,n),this.code=t,this.name=`ProfileStoreError`}},Ge=class{constructor(e=process.env.MODEL_PROXY_MCP_PROVIDER_PATH||v,t=process.env.MODEL_PROXY_MCP_MODEL_LIST_PATH||i.join(i.dirname(process.env.MODEL_PROXY_MCP_PROVIDER_PATH||v),i.basename(ne)),n=process.env.MODEL_PROXY_MCP_SCOPE_DIR||i.join(i.dirname(process.env.MODEL_PROXY_MCP_PROVIDER_PATH||v),i.basename(re)),r=S){this.providerConfigPath=e,this.modelListPath=t,this.scopeSettingsDir=n,this.logger=r}getConfigPath(e=j){return this.getScopeConfigPath(e)}async ensureConfig(e=j,t){return this.getConfig(e,t)}async listScopes(){try{let e=await d.readdir(this.scopeSettingsDir,{withFileTypes:!0}),t=new Set([j]);for(let n of e){if(!n.isFile()||!n.name.endsWith(Ie))continue;let e=n.name.slice(0,-5);e&&t.add(e)}return Array.from(t).sort()}catch(e){if(this.isFileNotFoundError(e))return[j];throw this.logger.error(`${P} Failed to list scopes`,{scopeSettingsDir:this.scopeSettingsDir,cause:e}),new z(`Failed to list scopes in ${this.scopeSettingsDir}`,I.settingsReadFailed,{cause:e})}}async getConfig(e=j,t){return this.toProxyConfig(await this.getSettings(e,t))}async getAdminConfig(e=j){let t=await this.getSettings(e),n=this.toProfiles(t),r=Object.fromEntries(N.map(e=>[e,this.resolveSlotConfig(t,n,e)]));return{scope:e,providerConfigPath:this.providerConfigPath,modelListPath:this.modelListPath,scopeConfigPath:this.getScopeConfigPath(e),providers:t.providers,models:n,scopeModels:t.scope.models,slots:r}}async listProfiles(e=j){return(await this.getAdminConfig(e)).models}async getActiveProfile(e=j,t=j){let n=await this.getAdminConfig(e),r=n.slots[t];return n.models.find(e=>e.id===r.profileId&&e.enabled)??null}async getResolvedSlotConfig(e=j,t=j){return(await this.getAdminConfig(e)).slots[t]}async setActiveProfile(e,t=j,n=j){let r=await this.getSettings(t),i=this.toProfiles(r).find(t=>t.id===e&&t.enabled);if(!i)throw new z(`Profile not found or disabled: ${e}`,I.profileNotFound,{cause:{profileId:e,scope:t,slot:n}});return this.updateConfig({models:{[n]:{main:{provider:i.provider,model:i.model,reasoningEffort:i.reasoningEffort,thinkingDisabled:r.scope.models[n]?.main.thinkingDisabled??!1},fallbacks:r.scope.models[n]?.fallbacks??[]}}},t)}async upsertProfile(e,t=j){let n=L.parse({id:e.id,label:e.label,provider:e.provider,model:e.model,reasoningEffort:e.reasoningEffort,enabled:e.enabled}),r=await this.getSettings(t),i=r.models.filter(e=>e.id!==n.id);i.push(n);let a=this.normalizeSettings({providers:r.providers,models:i,scope:r.scope});return await this.saveSettings(a,t),this.toProxyConfig(a)}async updateConfig(e,t=j){let n=We.parse(e),r=await this.getSettings(t),i={...r.scope.models};for(let e of N){let t=n.models?.[e];if(t!==void 0){if(t===null){delete i[e];continue}i[e]=t}}let a=this.normalizeSettings({providers:r.providers,models:r.models,scope:{models:i}});return await this.saveSettings(a,t),this.toProxyConfig(a)}getScopeConfigPath(e){return i.join(this.scopeSettingsDir,`${this.sanitizeScope(e)}${Ie}`)}sanitizeScope(e){return e.replace(/[^a-zA-Z0-9._-]/g,`-`).replace(/--+/g,`-`).replace(/^-|-$/g,``)||j}async getSettings(e,t){let n=await this.readSettings(e,t),r=this.normalizeSettings(n);return JSON.stringify(r)!==JSON.stringify(n)&&await this.saveSettings(r,e),r}async readSettings(e,t){let n=this.getScopeConfigPath(e),[r,i,a]=await Promise.allSettled([d.readFile(this.providerConfigPath,F),d.readFile(this.modelListPath,F),d.readFile(n,F)]);try{let n=r.status===`fulfilled`?r.value:null,o=i.status===`fulfilled`?i.value:null,s=a.status===`fulfilled`?a.value:null,c=r.status===`rejected`&&this.isFileNotFoundError(r.reason),l=i.status===`rejected`&&this.isFileNotFoundError(i.reason),u=a.status===`rejected`&&this.isFileNotFoundError(a.reason);if(!n&&r.status===`rejected`&&!c)throw r.reason;if(!o&&i.status===`rejected`&&!l)throw i.reason;if(!s&&a.status===`rejected`&&!u)throw a.reason;let d=s?He.parse(p(s)):await this.loadScopeSeedSettings(e,t),f=this.normalizeSettings({providers:n?Be.parse(p(n)).providers:structuredClone(ce),models:o?Ue.parse(p(o)):structuredClone(le),scope:d});return(c||l||u)&&await this.saveSettings(f,e),f}catch(t){throw this.logger.error(`${P} Failed to read settings`,{scope:e,providerConfigPath:this.providerConfigPath,modelListPath:this.modelListPath,scopeConfigPath:n,code:I.settingsReadFailed,cause:t}),new z(`Failed to read settings from ${this.providerConfigPath}, ${this.modelListPath}, and ${n}`,I.settingsReadFailed,{cause:t})}}isFileNotFoundError(e){return e?.code===`ENOENT`}async loadScopeSeedSettings(e,t){let n=[t,this.getDefaultScopeSeedPath(e)].filter(e=>!!e);for(let t of n)try{let e=await d.readFile(t,F);return He.parse(p(e))}catch(n){if(this.isFileNotFoundError(n))continue;throw this.logger.error(`${P} Failed to read scope seed settings`,{scope:e,seedConfigPath:t,cause:n}),n}return structuredClone(ue)}getDefaultScopeSeedPath(e){let t=this.getScopeConfigPath(j);return e===j?null:t}normalizeSettings(e){let t=this.normalizeProviders(Object.keys(e.providers).length>0?e.providers:structuredClone(ce)),n=e.models.map(e=>L.parse(e)),r=this.normalizeSlotSelection(e.scope.models.default?.main??null,n,t,this.getDefaultSelection(n,t)),i={default:r?{main:r,fallbacks:this.normalizeFallbacks(e.scope.models.default?.fallbacks??[],r,n,t)}:void 0};for(let a of N){if(a===j)continue;let o=r??this.getDefaultSelection(n,t),s=this.normalizeSlotSelection(e.scope.models[a]?.main??null,n,t,o);i[a]=s?{main:s,fallbacks:this.normalizeFallbacks(e.scope.models[a]?.fallbacks??[],s,n,t)}:void 0}return{providers:t,models:n,scope:{models:i}}}normalizeProviders(e){let t={...e},n=t[Fe];return n?.type===`anthropic-compatible`&&n.authTokenEnvVar===`ANTHROPIC_AUTH_TOKEN`&&(t[Fe]={...n,authTokenEnvVar:`ZAI_ANTHROPIC_AUTH_TOKEN`}),t}getDefaultSelection(e,t){let n=e.find(e=>e.enabled&&t[e.provider]);return n?{provider:n.provider,model:n.model,reasoningEffort:n.reasoningEffort,thinkingDisabled:!1}:null}normalizeSlotSelection(e,t,n,r){if(!e)return r?{...r}:null;let i=n[e.provider]?e.provider:r?.provider??null;if(!i)return null;if(i!==e.provider)return r&&n[r.provider]?{...r}:null;let a=t.find(t=>t.enabled&&t.provider===i&&t.model===e.model);return{provider:i,model:a?.model??e.model,reasoningEffort:e.reasoningEffort??a?.reasoningEffort??M,thinkingDisabled:e.thinkingDisabled??!1}}normalizeFallbacks(e,t,n,r){let i=[],a=new Set([`${t.provider}:${t.model}:${t.reasoningEffort}:${t.thinkingDisabled?`off`:`on`}`]);for(let t of e){let e=this.normalizeSlotSelection(t,n,r,null);if(!e)continue;let o=`${e.provider}:${e.model}:${e.reasoningEffort}:${e.thinkingDisabled?`off`:`on`}`;a.has(o)||(a.add(o),i.push(e))}return i}toProxyConfig(e){let t=this.toProfiles(e),n=Object.fromEntries(N.map(n=>[n,this.resolveSlotConfig(e,t,n).profileId]));return{activeProfileId:n.default??null,slotProfileIds:n,providers:e.providers,profiles:t,scope:e.scope}}resolveSlotConfig(e,t,n){let r=e.scope.models[n]??e.scope.models.default,i=r?.main??null,a=i?t.find(e=>e.provider===i.provider&&e.model===i.model&&e.enabled):void 0,o=i?.provider?e.providers[i.provider]:void 0;return{slot:n,profileId:a?.id??null,label:a?.label??null,provider:i?.provider??null,providerType:o?.type??null,endpoint:o?.endpoint??null,model:i?.model??null,reasoningEffort:i?.reasoningEffort??a?.reasoningEffort??M,thinkingDisabled:i?.thinkingDisabled??!1,fallbacks:r?.fallbacks??[]}}toProfiles(e){return e.models.map(t=>({id:t.id,label:t.label,provider:t.provider,providerType:e.providers[t.provider]?.type??null,model:t.model,endpoint:e.providers[t.provider]?.endpoint??null,reasoningEffort:t.reasoningEffort,enabled:t.enabled}))}async saveSettings(e,t=j){let n=this.getScopeConfigPath(t);try{let t=new Set([i.dirname(this.providerConfigPath),i.dirname(this.modelListPath),i.dirname(n)]);await Promise.all(Array.from(t,e=>d.mkdir(e,{recursive:!0}))),await Promise.all([d.writeFile(this.providerConfigPath,m(Be.parse({providers:e.providers}),{indent:2}),F),d.writeFile(this.modelListPath,m(Ue.parse(e.models),{indent:2}),F),d.writeFile(n,m(He.parse(e.scope),{indent:2}),F)])}catch(e){throw this.logger.error(`${P} Failed to write settings`,{scope:t,providerConfigPath:this.providerConfigPath,modelListPath:this.modelListPath,scopeConfigPath:n,code:I.settingsWriteFailed,cause:e}),new z(`Failed to write settings to ${this.providerConfigPath}, ${this.modelListPath}, and ${n}`,I.settingsWriteFailed,{cause:e})}}};const Ke={"ccproxy-default":`default`,"ccproxy-sonnet":`sonnet`,"ccproxy-opus":`opus`,"ccproxy-haiku":`haiku`,"ccproxy-subagent":`subagent`},B=`default`,V=`default`,qe=[`default`,`sonnet`,`opus`,`haiku`,`subagent`],Je=`https://api.anthropic.com/v1/messages`,Ye=`anthropic-compatible`,Xe=`ANTHROPIC_AUTH_TOKEN`,H=`application/json`,Ze=`text/event-stream; charset=utf-8`,U=`content-type`,Qe=`cache-control`,$e=`connection`,W=`accept`,et=`anthropic-version`,tt=`anthropic-beta`,nt=`no-cache`,rt=`keep-alive`,it=`request`,at=`response`,ot=`system`,st=`assistant`,G=`MODEL_NOT_FOUND`,ct=`REQUEST_FORWARD_FAILED`,lt=`generateContent`,ut=n.object({model:n.string().optional(),system:n.unknown().optional(),messages:n.array(n.unknown()).default([]),tools:n.array(n.unknown()).optional(),max_tokens:n.number().int().positive().optional(),stream:n.boolean().optional()});var K=class extends Error{constructor(e,t,n,r){super(e,r),this.code=t,this.status=n,this.name=new.target.name}},dt=class extends K{},q=class extends K{},J=class extends K{},Y=class extends K{constructor(e,t,n,r,i){super(e,ct,t,i),this.upstreamBody=n,this.attemptLabel=r}};const ft=new Set([408,429,500,502,503,504]);var pt=class{codeAssistProjectCache=new Map;constructor(e=new Ge,t=new ye(S,ae),n=S,r=fetch,i=new Pe){this.profileStore=e,this.codexAuth=t,this.logger=n,this.fetchImpl=r,this.historyService=i}async listProfiles(e=B){return this.profileStore.listProfiles(e)}async ensureConfig(e=B){return await this.historyService.ensureInitialized(),this.profileStore.ensureConfig(e)}async getAdminConfig(e=B){return this.profileStore.getAdminConfig(e)}async updateAdminConfig(e,t=B){return await this.profileStore.updateConfig(e,t),this.profileStore.getAdminConfig(t)}async getActiveProfile(e=B,t=V){return this.profileStore.getActiveProfile(e,t)}async upsertProfile(e,t=B){return this.profileStore.upsertProfile(e,t)}async setActiveProfile(e,t=B,n=V){return this.profileStore.setActiveProfile(e,t,n)}async getCurrentModel(e=B,t=V){return this.profileStore.getActiveProfile(e,t)}async switchModel(e,t=B,n=V){let r=await this.profileStore.listProfiles(t),i=await this.profileStore.getResolvedSlotConfig(t,n),a=r.find(t=>t.model===e&&t.provider===i.provider&&t.enabled)??r.find(t=>t.model===e&&t.enabled);if(!a)throw new q(`Model not found or disabled: ${e}`,G,400,{cause:{model:e,scope:t,slot:n}});return this.profileStore.updateConfig({models:{[n]:{main:{provider:a.provider,model:a.model,reasoningEffort:a.reasoningEffort},fallbacks:(await this.profileStore.getAdminConfig(t)).scopeModels[n]?.fallbacks??[]}}},t)}async listHistory(e=B,t=50,n){return this.historyService.listHistory(e,t,n)}async clearHistory(e=B){return{deleted:await this.historyService.clearScope(e)}}async listScopes(){let[e,t]=await Promise.all([this.profileStore.listScopes(),this.historyService.listScopes()]);return Array.from(new Set([...e,...t,B])).sort()}async getHistoryStats(e=B){return this.historyService.getStats(e)}async getStatus(e=B,t,n){let r=await this.profileStore.getConfig(e),i=await this.profileStore.getResolvedSlotConfig(e,V),a=await this.codexAuth.getAuthStatus(),o=Object.fromEntries(await Promise.all(qe.map(async t=>{let n=await this.profileStore.getResolvedSlotConfig(e,t);return[t,{profileId:n.profileId,provider:n.provider,model:n.model,reasoningEffort:n.reasoningEffort,thinkingDisabled:n.thinkingDisabled??!1}]})));return{running:!0,port:t,pid:n,scope:e,activeProfileId:r.activeProfileId,activeModel:i.model??void 0,activeReasoningEffort:i.reasoningEffort,slotModels:o,auth:a,profiles:r.profiles}}async getModels(e=B){return{object:`list`,data:(await this.profileStore.listProfiles(e)).map(e=>({id:e.model,object:`model`,created:0,owned_by:`${e.provider}:${e.id}`,metadata:{profileId:e.id,reasoningEffort:e.reasoningEffort}}))}}async forwardClaudeRequest(e,t=B,n=new Headers){let r=s(),i=V,a=null;try{let o=this.parseClaudeRequest(e),s=await this.profileStore.getAdminConfig(t);if(i=this.resolveSlot(o.model,s),a=this.resolveRequestedModel(o.model,s,i),!a.model){let n=`No active model configured for scope '${t}' and slot '${i}'`;return await this.recordError(t,i,a,r,n,e),this.createErrorResponse(400,n)}let c=this.buildAttemptTargets(s,a,i);if(c.length===0){let n=`No provider configured for scope '${t}' and slot '${i}'`;return await this.recordError(t,i,a,r,n,e),this.createErrorResponse(400,n)}await this.recordRequest(t,i,c[0].resolved,r,o,e);let l=null;for(let a of c){let s=await this.forwardSingleAttempt(a,o,e,t,i,n);if(s.ok)return await this.recordResponse(t,i,a.resolved,r,s.success.history.upstreamText,s.success.history.claudeBody),s.success.response;let{error:c,payloadForHistory:u}=s.failure;if(!this.isRecoverableUpstreamError(c))return this.logger.error(`[model-proxy-mcp] Non-recoverable model attempt failed`,{scope:t,slot:i,model:a.resolved.model,code:c.code,status:c.status,message:c.message}),await this.recordError(t,i,a.resolved,r,c.message,u),this.createErrorResponse(c.status,c.message);l=s.failure,this.logger.warn(`[model-proxy-mcp] Recoverable model attempt failed; trying fallback`,{scope:t,slot:i,model:a.resolved.model,code:c.code,status:c.status,message:c.message}),await this.recordError(t,i,a.resolved,r,`Attempt failed for ${a.label}: ${c.message}`,u)}if(l)return await this.recordError(t,i,a,r,l.error.message,l.payloadForHistory),this.createErrorResponse(l.error.status,l.error.message);let u=`No model attempt could be executed`;return await this.recordError(t,i,a,r,u,e),this.createErrorResponse(500,u)}catch(n){let o=n instanceof K?n:new K(`Failed to process Claude proxy request`,ct,500,{cause:n});return this.logger.error(`[model-proxy-mcp] Request forwarding failed`,{scope:t,slot:i,code:o.code,message:o.message,cause:o.cause}),await this.recordError(t,i,a,r,o.message,e),this.createErrorResponse(o.status,o.message)}}buildAttemptTargets(e,t,n){let r=[],i=new Set,a=[t,...t.fallbacks.map(e=>({...t,...e,slot:n}))];for(let t of a){let a=t.provider,o=t.model;if(!a||!o)continue;let s=e.providers[a];if(!s)continue;let c=`${a}:${o}:${t.reasoningEffort}:${t.thinkingDisabled?`off`:`on`}`;i.has(c)||(i.add(c),r.push({resolved:{...t,slot:n,provider:a,model:o,providerType:s.type,endpoint:s.endpoint,reasoningEffort:t.reasoningEffort,thinkingDisabled:t.thinkingDisabled??!1,fallbacks:[]},provider:s,label:`${a}/${o}`}))}return r}async forwardSingleAttempt(e,t,n,r,i,a){try{return e.resolved.providerType===Ye?{ok:!0,success:await this.forwardAnthropicCompatibleRequest(n,r,i,e.resolved,e.provider,a)}:e.resolved.providerType===`gemini-direct`?{ok:!0,success:await this.forwardGeminiDirectRequest(t,r,i,e.resolved,e.provider)}:{ok:!0,success:await this.forwardCodexRequest(n,r,i,e.resolved,e.provider)}}catch(e){return{ok:!1,failure:{error:e instanceof K?e:new K(`Failed to process Claude proxy request`,ct,500,{cause:e}),payloadForHistory:e instanceof Y?e.upstreamBody:n}}}}isRecoverableUpstreamError(e){return e instanceof Y&&ft.has(e.status)}createCodexSessionId(e,t,n){let r=[y,`codex`,e,t,n.provider??`unknown-provider`,n.model??`unknown-model`].join(`:`);return a(`sha256`).update(r).digest(`hex`)}createGeminiSessionId(e,t,n){let r=[y,`gemini-code-assist`,e,t,n.provider??`unknown-provider`,n.model??`unknown-model`].join(`:`);return a(`sha256`).update(r).digest(`hex`)}createCodeAssistProjectCacheKey(e){let t=process.env.GOOGLE_CLOUD_PROJECT||process.env.GOOGLE_CLOUD_PROJECT_ID||``,n=Object.entries(e.headers).sort(([e],[t])=>e.localeCompare(t));return JSON.stringify({authMode:e.authMode,authSource:e.authSource,authType:e.authType??null,configuredProject:t,headers:n})}async forwardCodexRequest(e,t,n,r,i){let a=r.model;if(!a)throw new q(`No active model configured for slot '${n}'`,G,400);let o=await this.codexAuth.resolveAuth(),s=i.apiKeyEnvVar?process.env[i.apiKeyEnvVar]:void 0;if(!o.accessToken&&!s)throw new J(`Missing Codex auth. Sign in with the official Codex CLI first.`,`AUTH_MISSING`,401);let c=new he({toProvider:`chatgpt-codex`,toModel:a,toEndpoint:r.endpoint??Je,toApiKey:s,sessionId:this.createCodexSessionId(t,n,r),sessionReasoningEffort:r.reasoningEffort,thinkingDisabled:r.thinkingDisabled??!1,logger:this.logger,resolvedAuth:o}),l=new xe(this.logger,r.thinkingDisabled??!1),u=await c.transform(Je,e),d=await this.fetchImpl(u.url,{method:`POST`,headers:u.headers,body:u.body});if(!d.ok){let e=await d.text();throw this.logger.error(`[model-proxy-mcp] Upstream request failed`,{scope:t,slot:n,profileId:r.profileId,model:r.model,status:d.status,body:e}),new Y(e||`Codex upstream request failed`,d.status,e,`${r.provider}/${r.model}`)}let f=await d.text(),p=l.transform(f);return{response:{status:200,body:p,headers:this.createSuccessHeaders(t,n)},history:{upstreamText:f,claudeBody:p}}}async forwardGeminiDirectRequest(e,t,n,r,i){try{let a=r.model;if(!a)throw new q(`No active Gemini model configured for scope '${t}' and slot '${n}'`,G,400);let o=await new we(this.logger).resolveHeaders(i),s=new Ce,c=new Me,l={model:e.model,system:e.system,messages:e.messages,tools:e.tools,max_tokens:e.max_tokens},u=s.transform(l,a,r.reasoningEffort,r.thinkingDisabled??!1);if(o.authType===`oauth-personal`)return await this.forwardGeminiCodeAssistRequest(u.body,t,n,r,o,c);let d=this.createGeminiRequestUrl(i,u.modelPath,lt),f=new Headers(o.headers);f.set(U,H),f.set(W,H);let p=i.apiTimeoutMs??this.resolveUpstreamTimeoutMs(),m=await this.fetchImpl(d,{method:`POST`,headers:f,body:JSON.stringify(u.body),signal:p?AbortSignal.timeout(p):void 0}),h=await m.text();if(!m.ok)throw this.logger.error(`[model-proxy-mcp] Gemini upstream request failed`,{scope:t,slot:n,profileId:r.profileId,model:r.model,status:m.status,body:h,authMode:o.authMode,authSource:o.authSource}),new Y(h||`Gemini upstream request failed`,m.status,h,`${r.provider}/${r.model}`);let g=c.transformBuffered(h,a);return{response:{status:200,body:g,headers:this.createSuccessHeaders(t,n)},history:{upstreamText:h,claudeBody:g}}}catch(e){throw e instanceof T?new J(e.message,e.code,401,{cause:e}):e}}async forwardGeminiCodeAssistRequest(e,t,n,r,i,a){let o=await this.resolveCodeAssistProjectId(i),c=this.createCodeAssistUrl(lt),l=new Headers(i.headers);l.set(U,H),l.set(W,H);let u={model:r.model,project:o,user_prompt_id:s(),request:this.toCodeAssistGenerateContentRequest(e,this.createGeminiSessionId(t,n,r))},d=await this.fetchImpl(c,{method:`POST`,headers:l,body:JSON.stringify(u)}),f=await d.text();if(!d.ok)throw this.logger.error(`[model-proxy-mcp] Gemini Code Assist request failed`,{scope:t,slot:n,profileId:r.profileId,model:r.model,status:d.status,body:f,authMode:i.authMode,authSource:i.authSource,authType:i.authType}),new Y(f||`Gemini Code Assist request failed`,d.status,f,`${r.provider}/${r.model}`);let p=this.normalizeCodeAssistBufferedResponse(f),m=a.transformBuffered(p,r.model??`gemini`);return{response:{status:200,body:m,headers:this.createSuccessHeaders(t,n)},history:{upstreamText:f,claudeBody:m}}}createSuccessHeaders(e,t){return{[U]:Ze,[Qe]:nt,[$e]:rt,"x-model-proxy-service":y,"x-model-proxy-scope":e,"x-model-proxy-slot":t}}createGeminiRequestUrl(e,t,n){let r=e.endpoint.replace(/\/$/,``),i=e.apiVersion??`v1beta`;return new URL(`${r}/${i}/${t}:${n}`).toString()}createCodeAssistUrl(e){return new URL(`https://cloudcode-pa.googleapis.com/v1internal:${e}`).toString()}async resolveCodeAssistProjectId(e){let t=this.createCodeAssistProjectCacheKey(e),n=this.codeAssistProjectCache.get(t);if(n)return n;let r=new Headers(e.headers);r.set(U,H);let i=process.env.GOOGLE_CLOUD_PROJECT||process.env.GOOGLE_CLOUD_PROJECT_ID||void 0,a=await this.fetchImpl(this.createCodeAssistUrl(`loadCodeAssist`),{method:`POST`,headers:r,body:JSON.stringify({cloudaicompanionProject:i,metadata:{ideType:`IDE_UNSPECIFIED`,platform:`PLATFORM_UNSPECIFIED`,pluginType:`GEMINI`,duetProject:i}})}),o=await a.text();if(!a.ok)throw new Y(o||`Failed to load Gemini Code Assist project context`,a.status,o,`google-gemini-direct/loadCodeAssist`);let s=JSON.parse(o).cloudaicompanionProject||i;if(!s)throw new J(`Gemini Code Assist did not return a usable project ID.`,`GEMINI_PROJECT_MISSING`,401);return this.codeAssistProjectCache.set(t,s),s}toCodeAssistGenerateContentRequest(e,t){return{contents:e.contents,systemInstruction:e.system_instruction,tools:e.tools,toolConfig:e.tool_config,generationConfig:e.generationConfig,session_id:t}}normalizeCodeAssistBufferedResponse(e){let t=JSON.parse(e);return JSON.stringify(this.extractCodeAssistResponse(t))}normalizeCodeAssistStreamingResponse(e){let t=[];for(let n of e.split(`
|
|
64
64
|
|
|
65
65
|
`)){let e=n.split(`
|
|
66
66
|
`).find(e=>e.startsWith(`data:`))?.slice(5).trim();if(!e||e===`[DONE]`)continue;let r=JSON.parse(e);t.push(`data: ${JSON.stringify(this.extractCodeAssistResponse(r))}`),t.push(``)}return t.join(`
|
|
67
|
-
`)}extractCodeAssistResponse(e){return{candidates:e.response?.candidates??[],usageMetadata:e.response?.usageMetadata,modelVersion:e.response?.modelVersion}}resolveSlot(e,t){if(!e)return V;let n=
|
|
67
|
+
`)}extractCodeAssistResponse(e){return{candidates:e.response?.candidates??[],usageMetadata:e.response?.usageMetadata,modelVersion:e.response?.modelVersion}}resolveSlot(e,t){if(!e)return V;let n=Ke[e];if(n)return n;for(let n of qe)if(t.slots[n].model===e)return n;return V}resolveRequestedModel(e,t,n){let r=t.slots[n];if(!e||Ke[e])return r;let i=t.models.find(t=>t.model===e&&t.provider===r.provider&&t.enabled)??t.models.find(t=>t.model===e&&t.enabled);if(!i&&r.model!==e)throw new q(`Model not found or disabled: ${e}`,G,400,{cause:{model:e,slot:n,scope:t.scope}});return{...r,profileId:i?.id??null,label:i?.label??null,provider:i?.provider??r.provider,providerType:i?.providerType??r.providerType,endpoint:i?.endpoint??r.endpoint,model:i?.model??e,reasoningEffort:i?.reasoningEffort??r.reasoningEffort,thinkingDisabled:r.thinkingDisabled??!1}}async forwardAnthropicCompatibleRequest(e,t,n,r,i,a){let o=this.getAnthropicCompatibleAuthToken(i.authTokenEnvVar);if(!o)throw new J(`Missing upstream auth token for ${Ye}. Set ${i.authTokenEnvVar||Xe}.`,`UPSTREAM_AUTH_MISSING`,401);let s=this.sanitizeClaudePayloadForAnthropic({...ut.parse(JSON.parse(e)),model:r.model}),c=new Headers;c.set(W,a.get(W)??Ze),c.set(U,H),c.set(et,a.get(et)??`2023-06-01`),c.set(`x-api-key`,o),c.set(`authorization`,`Bearer ${o}`);let l=a.get(tt);l&&c.set(tt,l);let u=i.apiTimeoutMs??this.resolveUpstreamTimeoutMs(),d=await this.fetchImpl(i.endpoint,{method:`POST`,headers:c,body:JSON.stringify(s),signal:u?AbortSignal.timeout(u):void 0}),f=await d.text();if(!d.ok)throw this.logger.error(`[model-proxy-mcp] Anthropic-compatible upstream request failed`,{scope:t,slot:n,profileId:r.profileId,model:r.model,status:d.status,body:f}),new Y(f||`Anthropic-compatible upstream request failed`,d.status,f,`${r.provider}/${r.model}`);return{response:{status:d.status,body:f,headers:{[U]:d.headers.get(U)??Ze,[Qe]:d.headers.get(Qe)??nt,[$e]:d.headers.get($e)??rt,"x-model-proxy-service":y,"x-model-proxy-scope":t,"x-model-proxy-slot":n}},history:{upstreamText:f,claudeBody:f}}}getAnthropicCompatibleAuthToken(e){let t=e||process.env.MODEL_PROXY_MCP_UPSTREAM_AUTH_ENV||Xe;return process.env[t]||process.env.MODEL_PROXY_MCP_UPSTREAM_AUTH_TOKEN||null}resolveUpstreamTimeoutMs(){let e=process.env.API_TIMEOUT_MS||process.env.MODEL_PROXY_MCP_UPSTREAM_TIMEOUT_MS;if(!e)return null;let t=Number.parseInt(e,10);return Number.isInteger(t)&&t>0?t:null}createErrorResponse(e,t){return{status:e,body:JSON.stringify({type:`error`,error:{type:`api_error`,message:t}}),headers:{"content-type":`application/json; charset=utf-8`}}}parseClaudeRequest(e){try{return ut.parse(JSON.parse(e))}catch(e){throw e instanceof SyntaxError||e instanceof t?new dt(`Invalid Claude request payload`,`REQUEST_VALIDATION_FAILED`,400,{cause:e}):e}}async recordRequest(e,t,n,r,i,a){let o=[],s=this.normalizeSystemPayloads(i.system);for(let i of s)o.push({scope:e,requestId:r,direction:it,role:ot,messageType:ot,model:n.model,slot:t,payloadJson:JSON.stringify(i)});let c=Array.isArray(i.messages)?i.messages:[];for(let i of c){let a=this.extractRole(i);o.push({scope:e,requestId:r,direction:it,role:a,messageType:`message`,model:n.model,slot:t,payloadJson:JSON.stringify(i)})}o.length===0&&o.push({scope:e,requestId:r,direction:it,role:null,messageType:`raw-request`,model:n.model,slot:t,payloadJson:a}),await this.historyService.appendEntries(o)}async recordResponse(e,t,n,r,i,a){let o=this.normalizeResponsePayloads(e,r,t,n.model,i);if(o.length>0){await this.historyService.appendEntries(o);return}await this.historyService.appendEntries([{scope:e,requestId:r,direction:at,role:st,messageType:`response-stream`,model:n.model,slot:t,payloadJson:JSON.stringify({upstreamText:i,claudeBody:a})}])}async recordError(e,t,n,r,i,a){await this.historyService.appendEntries([{scope:e,requestId:r,direction:`error`,role:null,messageType:`error`,model:n?.model??null,slot:t,payloadJson:JSON.stringify({message:i,payloadPreview:this.summarizePayload(a)})}])}normalizeSystemPayloads(e){return e?typeof e==`string`?[{type:`text`,text:e}]:Array.isArray(e)?e:[e]:[]}sanitizeClaudePayloadForAnthropic(e){return this.stripPrivateChatGptMetadata(e)}stripPrivateChatGptMetadata(e){return Array.isArray(e)?e.map(e=>this.stripPrivateChatGptMetadata(e)):!e||typeof e!=`object`?e:Object.fromEntries(Object.entries(e).filter(([e])=>!e.startsWith(`_chatgpt_`)).map(([e,t])=>[e,this.stripPrivateChatGptMetadata(t)]))}extractRole(e){if(!e||typeof e!=`object`)return null;let t=e.role;return typeof t==`string`?t:null}normalizeResponsePayloads(e,t,n,r,i){let a=[];for(let o of i.split(`
|
|
68
68
|
|
|
69
69
|
`)){let i=o.split(`
|
|
70
|
-
`).find(e=>e.startsWith(`data:`))?.slice(5).trim();if(!i||i===`[DONE]`)continue;let s;try{s=JSON.parse(i)}catch{continue}let c=this.extractCompletedResponse(s);if(!(!c?.output||!Array.isArray(c.output)))for(let i of c.output)a.push({scope:e,requestId:t,direction
|
|
70
|
+
`).find(e=>e.startsWith(`data:`))?.slice(5).trim();if(!i||i===`[DONE]`)continue;let s;try{s=JSON.parse(i)}catch{continue}let c=this.extractCompletedResponse(s);if(!(!c?.output||!Array.isArray(c.output)))for(let i of c.output)a.push({scope:e,requestId:t,direction:at,role:st,messageType:typeof i.type==`string`?i.type:`response-item`,model:typeof c.model==`string`?c.model:r,slot:n,payloadJson:JSON.stringify(i)})}return a}extractCompletedResponse(e){if(!e||typeof e!=`object`)return null;let t=e;return t.type===`response.completed`&&t.response&&typeof t.response==`object`||t.response&&typeof t.response==`object`?t.response:{model:typeof t.model==`string`?t.model:void 0,output:Array.isArray(t.output)?t.output:void 0}}summarizePayload(e){let t=e.replace(/\s+/g,` `).trim();return t.length<=240?t:`${t.slice(0,240)}...`}};const mt=n.enum([`minimal`,`low`,`medium`,`high`]),ht=n.enum([`default`,`sonnet`,`opus`,`haiku`,`subagent`]),gt=n.object({provider:n.string().min(1),model:n.string().min(1),reasoningEffort:mt,thinkingDisabled:n.boolean().optional()}),X=n.object({main:gt,fallbacks:n.array(gt).default([])}),_t=n.object({models:n.object({default:X.nullable().optional(),sonnet:X.nullable().optional(),opus:X.nullable().optional(),haiku:X.nullable().optional(),subagent:X.nullable().optional()}).optional()}),vt=n.object({id:n.string().min(1),label:n.string().min(1),provider:n.string().min(1),model:n.string().min(1),endpoint:n.url().nullable(),reasoningEffort:mt.default(`medium`),enabled:n.boolean().default(!0),providerType:n.enum([`chatgpt-codex`,`anthropic-compatible`,`gemini-direct`]).nullable().optional()});function yt(t=new pt){let r=new e,i=e=>e||`default`;return r.get(`/health`,e=>e.json({status:`healthy`,service:y})),r.get(`/status`,async e=>{let n=await t.getStatus();return e.json(n)}),r.get(`/v1/models`,async e=>e.json(await t.getModels())),r.get(`/scopes/:scope/status`,async e=>e.json(await t.getStatus(i(e.req.param(`scope`))))),r.get(`/scopes/:scope/v1/models`,async e=>e.json(await t.getModels(i(e.req.param(`scope`))))),r.post(`/v1/messages`,async e=>{let n=await e.req.text(),r=await t.forwardClaudeRequest(n,`default`,e.req.raw.headers);return new Response(r.body,{status:r.status,headers:r.headers})}),r.post(`/scopes/:scope/v1/messages`,async e=>{let n=await e.req.text(),r=await t.forwardClaudeRequest(n,i(e.req.param(`scope`)),e.req.raw.headers);return new Response(r.body,{status:r.status,headers:r.headers})}),r.get(`/admin`,()=>new Response(`<!DOCTYPE html>
|
|
71
71
|
<html lang="en">
|
|
72
72
|
<head>
|
|
73
73
|
<meta charset="utf-8" />
|
|
@@ -598,4 +598,4 @@ import"@hono/node-server";import{Hono as e}from"hono";import{ZodError as t,z as
|
|
|
598
598
|
<\/script>
|
|
599
599
|
</body>
|
|
600
600
|
</html>
|
|
601
|
-
`,{headers:{"content-type":`text/html; charset=utf-8`}})),r.get(`/admin/scopes`,async e=>e.json({scopes:await t.listScopes()})),r.get(`/admin/config`,async e=>{let n=i(e.req.query(`scope`));return e.json(await t.getAdminConfig(n))}),r.get(`/admin/profiles`,async e=>{let n=i(e.req.query(`scope`));return e.json({profiles:await t.listProfiles(n)})}),r.get(`/admin/current-model`,async e=>{let n=i(e.req.query(`scope`)),r=e.req.query(`slot`)||`default`;return e.json({profile:await t.getCurrentModel(n,r)})}),r.get(`/admin/history`,async e=>{let r=i(e.req.query(`scope`)),a=n.coerce.number().int().positive().max(200).default(50).parse(e.req.query(`limit`)??50),o=e.req.query(`cursor`)||void 0;return e.json(await t.listHistory(r,a,o))}),r.get(`/admin/history/stats`,async e=>{let n=i(e.req.query(`scope`));return e.json(await t.getHistoryStats(n))}),r.put(`/admin/profiles/:id`,async e=>{let n=
|
|
601
|
+
`,{headers:{"content-type":`text/html; charset=utf-8`}})),r.get(`/admin/scopes`,async e=>e.json({scopes:await t.listScopes()})),r.get(`/admin/config`,async e=>{let n=i(e.req.query(`scope`));return e.json(await t.getAdminConfig(n))}),r.get(`/admin/profiles`,async e=>{let n=i(e.req.query(`scope`));return e.json({profiles:await t.listProfiles(n)})}),r.get(`/admin/current-model`,async e=>{let n=i(e.req.query(`scope`)),r=e.req.query(`slot`)||`default`;return e.json({profile:await t.getCurrentModel(n,r)})}),r.get(`/admin/history`,async e=>{let r=i(e.req.query(`scope`)),a=n.coerce.number().int().positive().max(200).default(50).parse(e.req.query(`limit`)??50),o=e.req.query(`cursor`)||void 0;return e.json(await t.listHistory(r,a,o))}),r.get(`/admin/history/stats`,async e=>{let n=i(e.req.query(`scope`));return e.json(await t.getHistoryStats(n))}),r.put(`/admin/profiles/:id`,async e=>{let n=vt.parse(await e.req.json()),r=i(e.req.query(`scope`)),a=await t.upsertProfile({...n,id:e.req.param(`id`),providerType:n.providerType??null},r);return e.json(a)}),r.put(`/admin/active-profile`,async e=>{let r=i(e.req.query(`scope`)),a=n.object({profileId:n.string().min(1),slot:ht.default(`default`)}).parse(await e.req.json()),o=await t.setActiveProfile(a.profileId,r,a.slot);return e.json(o)}),r.put(`/admin/current-model`,async e=>{let r=i(e.req.query(`scope`)),a=n.object({model:n.string().min(1),slot:ht.default(`default`)}).parse(await e.req.json()),o=await t.switchModel(a.model,r,a.slot);return e.json(o)}),r.put(`/admin/config`,async e=>{let n=i(e.req.query(`scope`)),r=_t.parse(await e.req.json());return e.json(await t.updateAdminConfig(r,n))}),r.delete(`/admin/history`,async e=>{let n=i(e.req.query(`scope`));return e.json(await t.clearHistory(n))}),r}const Z=`@agimon-ai/model-proxy-mcp`,Q=n.enum([`default`,`sonnet`,`opus`,`haiku`,`subagent`]);let bt;function xt(){return bt??=u({serviceName:Z,workspaceRoot:process.cwd()}),bt}async function St(e,t,n){let r=await xt();return r.runInSpan(e,{attributes:t},async()=>{try{return await n()}catch(n){throw r.logger.error(`${e} failed`,{attributes:t,exception:n}),n}})}function Ct(e=new pt,t=process.env){let r=e=>{let n=e?.scope;return typeof n==`string`&&n.trim()?n:t.MODEL_PROXY_MCP_SCOPE||`default`},i=e=>{let n=typeof e?.slot==`string`?e.slot:t.MODEL_PROXY_MCP_SLOT||`default`;return Q.parse(n)},a=new h({name:`model-proxy-mcp`,version:`0.1.0`},{capabilities:{tools:{}}});return a.setRequestHandler(ee,async()=>St(`mcp.server.list_tools`,{"mcp.service":Z},async()=>({tools:[{name:`get_proxy_status`,description:`Get the HTTP proxy status, auth status, and active profile.`,inputSchema:{type:`object`,properties:{scope:{type:`string`}},additionalProperties:!1}},{name:`list_profiles`,description:`List all configured model proxy profiles.`,inputSchema:{type:`object`,properties:{scope:{type:`string`}},additionalProperties:!1}},{name:`get_active_profile`,description:`Get the active model proxy profile.`,inputSchema:{type:`object`,properties:{scope:{type:`string`},slot:{type:`string`,enum:Q.options}},additionalProperties:!1}},{name:`get_current_model`,description:`Get the currently active model and profile settings.`,inputSchema:{type:`object`,properties:{scope:{type:`string`},slot:{type:`string`,enum:Q.options}},additionalProperties:!1}},{name:`set_model_target`,description:`Set the scoped slot main target directly by provider, model, and reasoning effort.`,inputSchema:{type:`object`,properties:{provider:{type:`string`},model:{type:`string`},reasoningEffort:{type:`string`,enum:[`minimal`,`low`,`medium`,`high`]},scope:{type:`string`},slot:{type:`string`,enum:Q.options},thinkingDisabled:{type:`boolean`}},required:[`provider`,`model`,`reasoningEffort`],additionalProperties:!1}},{name:`upsert_profile`,description:`Create or update a Codex model profile.`,inputSchema:{type:`object`,properties:{id:{type:`string`},label:{type:`string`},provider:{type:`string`},model:{type:`string`},endpoint:{type:`string`},reasoningEffort:{type:`string`,enum:[`minimal`,`low`,`medium`,`high`]},enabled:{type:`boolean`},scope:{type:`string`}},required:[`id`,`label`,`model`,`endpoint`,`reasoningEffort`,`enabled`],additionalProperties:!1}}]}))),a.setRequestHandler(g,async t=>{let{name:a,arguments:o}=t.params;return St(`mcp.server.call_tool`,{"mcp.service":Z,"mcp.tool.name":a},async()=>{try{switch(a){case`get_proxy_status`:return $(JSON.stringify(await e.getStatus(r(o)),null,2));case`list_profiles`:return $(JSON.stringify(await e.listProfiles(r(o)),null,2));case`get_active_profile`:return $(JSON.stringify(await e.getActiveProfile(r(o),i(o)),null,2));case`get_current_model`:return $(JSON.stringify(await e.getCurrentModel(r(o),i(o)),null,2));case`set_model_target`:{let t=r(o),a=i(o),s=await e.getAdminConfig(t);return $(JSON.stringify(await e.updateAdminConfig({models:{[a]:{main:{provider:String(o?.provider),model:String(o?.model),reasoningEffort:n.enum([`minimal`,`low`,`medium`,`high`]).parse(String(o?.reasoningEffort)),thinkingDisabled:o?.thinkingDisabled===void 0?!1:!!o.thinkingDisabled},fallbacks:s.scopeModels[a]?.fallbacks??[]}}},t),null,2))}case`upsert_profile`:return $(JSON.stringify(await e.upsertProfile({id:String(o?.id),label:String(o?.label),provider:String(o?.provider||`chatgpt-codex`),providerType:null,model:String(o?.model),endpoint:String(o?.endpoint),reasoningEffort:String(o?.reasoningEffort),enabled:!!o?.enabled},r(o)),null,2));default:throw Error(`Unknown tool: ${a}`)}}catch(e){return(await xt()).logger.error(`model proxy MCP tool failed`,{attributes:{"mcp.service":Z,"mcp.tool.name":a},exception:e}),{content:[{type:`text`,text:`Error executing tool ${a}: ${e instanceof Error?e.message:String(e)}`}],isError:!0}}})}),a}function $(e){return{content:[{type:`text`,text:e}]}}var wt=class{transport=null;constructor(e){this.server=e}async start(){this.transport=new te,await this.server.connect(this.transport)}async stop(){this.transport&&=(await this.transport.close(),null)}};export{Ge as a,oe as c,pt as i,y as l,Ct as n,Pe as o,yt as r,S as s,wt as t,se as u};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/model-proxy-mcp",
|
|
3
3
|
"description": "Claude-compatible model proxy MCP with ChatGPT Codex upstream support",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.17.1",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mcp",
|
|
@@ -29,8 +29,9 @@
|
|
|
29
29
|
"ulidx": "2.4.1",
|
|
30
30
|
"yaml": "2.8.3",
|
|
31
31
|
"zod": "4.4.1",
|
|
32
|
-
"@agimon-ai/foundation-port-registry": "0.
|
|
33
|
-
"@agimon-ai/foundation-process-registry": "0.
|
|
32
|
+
"@agimon-ai/foundation-port-registry": "0.17.1",
|
|
33
|
+
"@agimon-ai/foundation-process-registry": "0.17.1",
|
|
34
|
+
"@agimon-ai/log-sink-mcp": "0.17.1"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/better-sqlite3": "7.6.13",
|