tg-agent 1.0.1 → 1.1.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/README.md CHANGED
@@ -12,6 +12,7 @@
12
12
  - 多 Provider / 多 Model 选择(支持 Codex / antigravity / Gemini CLI 等)
13
13
  - OAuth 登录与注销(/login, /logout)
14
14
  - 工具调用:内置 `fetch`,可选 MCP(HTTP/stdio)
15
+ - 图片/文件上传与发送(Telegram 附件、`send_photo`/`send_file` 工具)
15
16
  - 代理支持:模型与工具请求可分别配置
16
17
 
17
18
  ## 快速开始
@@ -74,6 +75,12 @@ npm start
74
75
 
75
76
  默认仅支持私聊(DM)。
76
77
 
78
+ ### 图片与文件
79
+
80
+ - 直接发送图片或文件给机器人,会保存到 `~/.tg-agent/uploads`,并自动附加到提示词中。
81
+ - 图片会作为多模态输入传给模型(若模型支持图像)。
82
+ - Agent 可使用 `send_photo` / `send_file` 工具把本地文件发回 Telegram(路径需在工作目录或 uploads 下)。
83
+
77
84
  ## 环境变量
78
85
 
79
86
  `.env.example` 已列出所有可配置项。常用说明如下:
@@ -1,9 +1,9 @@
1
- import{Type as o}from"@sinclair/typebox";import{config as m}from"./config.js";import{callMcpServer as g,formatMcpTarget as T,loadMcpServers as v,loadMcpServersSync as M}from"./mcp.js";const $=2e5,S=6e4;function x(e,t,r){return Number.isNaN(e)?t:Math.min(r,Math.max(t,e))}function _(e){if(!e)return{};const t={};if(Array.isArray(e)){for(const r of e)r?.name&&(t[r.name]=r.value??"");return t}for(const[r,c]of Object.entries(e))r&&(t[r]=c);return t}function h(e){const t=m.fetchTimeoutMs>0?m.fetchTimeoutMs:S;return x(e||t,1e3,12e4)}function b(e){const t=m.fetchMaxBytes>0?m.fetchMaxBytes:$;return x(e||t,1024,5e6)}function C(e){try{const t=new URL(e);return t.protocol==="http:"||t.protocol==="https:"}catch{return!1}}function P(e,t){const r=new Uint8Array(t);let c=0;for(const d of e)r.set(d,c),c+=d.byteLength;return r}async function w(e,t,r){const c=e.body?.getReader?.();if(!c){const a=await e.text?.()??"",i=Math.min(a.length,t),y=a.length>t;return{text:a.slice(0,t),bytes:i,truncated:y}}const d=new TextDecoder("utf-8"),p=[];let u=0,l=!1;for(;;){if(r?.aborted){try{await c.cancel?.()}catch{}throw new Error("Fetch aborted")}const{done:a,value:i}=await c.read();if(a)break;if(!i)continue;const y=u+i.byteLength;if(y>t){const f=Math.max(0,t-u);f>0&&(p.push(i.slice(0,f)),u+=f),l=!0;try{await c.cancel?.()}catch{}break}p.push(i),u=y}const s=P(p,u);return{text:d.decode(s),bytes:u,truncated:l}}function O(e,t){const r=`HTTP ${e.status} ${e.statusText}`.trim(),c=[`url: ${e.url}`,`bytes: ${e.bytes}${e.truncated?" (truncated)":""}`,`content-type: ${e.contentType??"unknown"}`];return`${r}
2
- ${c.join(`
1
+ import f from"node:path";import $ from"node:fs/promises";import{Type as o}from"@sinclair/typebox";import{config as y}from"./config.js";import{callMcpServer as w,formatMcpTarget as x,loadMcpServers as S,loadMcpServersSync as _}from"./mcp.js";import{expandHome as P}from"./utils.js";const C=2e5,k=6e4;function b(e,t,r){return Number.isNaN(e)?t:Math.min(r,Math.max(t,e))}function O(e){if(!e)return{};const t={};if(Array.isArray(e)){for(const r of e)r?.name&&(t[r.name]=r.value??"");return t}for(const[r,s]of Object.entries(e))r&&(t[r]=s);return t}function T(e){const t=y.fetchTimeoutMs>0?y.fetchTimeoutMs:k;return b(e||t,1e3,12e4)}function g(e){const t=y.fetchMaxBytes>0?y.fetchMaxBytes:C;return b(e||t,1024,5e6)}function v(e,t,r){const s=e.trim();if(!s)return null;const n=P(s),l=f.isAbsolute(n)?n:f.join(r,n),u=f.resolve(l);for(const d of t){const c=f.resolve(d);if(u===c||u.startsWith(`${c}${f.sep}`))return u}return null}async function M(e){try{const t=await $.stat(e);return t.isFile()?{bytes:t.size}:null}catch{return null}}function F(e){try{const t=new URL(e);return t.protocol==="http:"||t.protocol==="https:"}catch{return!1}}function A(e,t){const r=new Uint8Array(t);let s=0;for(const n of e)r.set(n,s),s+=n.byteLength;return r}async function j(e,t,r){const s=e.body?.getReader?.();if(!s){const i=await e.text?.()??"",p=Math.min(i.length,t),m=i.length>t;return{text:i.slice(0,t),bytes:p,truncated:m}}const n=new TextDecoder("utf-8"),l=[];let u=0,d=!1;for(;;){if(r?.aborted){try{await s.cancel?.()}catch{}throw new Error("Fetch aborted")}const{done:i,value:p}=await s.read();if(i)break;if(!p)continue;const m=u+p.byteLength;if(m>t){const h=Math.max(0,t-u);h>0&&(l.push(p.slice(0,h)),u+=h),d=!0;try{await s.cancel?.()}catch{}break}l.push(p),u=m}const c=A(l,u);return{text:n.decode(c),bytes:u,truncated:d}}function L(e,t){const r=`HTTP ${e.status} ${e.statusText}`.trim(),s=[`url: ${e.url}`,`bytes: ${e.bytes}${e.truncated?" (truncated)":""}`,`content-type: ${e.contentType??"unknown"}`];return`${r}
2
+ ${s.join(`
3
3
  `)}
4
4
 
5
- ${t}`}const k=o.Object({url:o.String({description:"HTTP or HTTPS URL"}),method:o.Optional(o.String({description:"HTTP method (default: GET)"})),headers:o.Optional(o.Array(o.Object({name:o.String({description:"Header name"}),value:o.String({description:"Header value"})}),{description:"Request headers as name/value pairs"})),body:o.Optional(o.String({description:"Request body (string)"})),timeoutMs:o.Optional(o.Integer({description:"Timeout in milliseconds",minimum:1e3,maximum:12e4})),maxBytes:o.Optional(o.Integer({description:"Max response bytes",minimum:1024,maximum:5e6}))});function A(){return{name:"fetch",label:"fetch",description:"Fetch a URL via HTTP(S) and return the response body.",parameters:k,execute:async(e,t,r,c,d)=>{if(!C(t.url))return{content:[{type:"text",text:"Invalid URL. Only http(s) is allowed."}],details:{url:t.url,status:0,statusText:"invalid_url",bytes:0,truncated:!1,contentType:null}};const p=h(t.timeoutMs),u=b(t.maxBytes),l=new AbortController,s=()=>l.abort();d?.addEventListener("abort",s,{once:!0});const n=setTimeout(()=>l.abort(),p);try{r?.({content:[{type:"text",text:`Fetching ${t.url}...`}],details:{url:t.url,status:0,statusText:"pending",bytes:0,truncated:!1,contentType:null}});const a=await fetch(t.url,{method:t.method?.toUpperCase()??"GET",headers:_(t.headers),body:t.body,signal:l.signal}),i=await w(a,u,d),y=a.headers.get("content-type");return{content:[{type:"text",text:O({url:a.url,status:a.status,statusText:a.statusText,bytes:i.bytes,truncated:i.truncated,contentType:y},i.text)}],details:{url:a.url,status:a.status,statusText:a.statusText,bytes:i.bytes,truncated:i.truncated,contentType:y}}}catch(a){return{content:[{type:"text",text:`Fetch failed: ${a instanceof Error?a.message:String(a)}`}],details:{url:t.url,status:0,statusText:"error",bytes:0,truncated:!1,contentType:null}}}finally{clearTimeout(n),d?.removeEventListener("abort",s)}}}}const L=o.Object({server:o.Optional(o.String({description:"MCP server name"})),method:o.String({description:"MCP method name"}),params:o.Optional(o.Any({description:"MCP params payload"}))});function E(e,t,r){const c=t.status>0?`HTTP ${t.status} ${t.statusText}`.trim():t.statusText,d=[`server: ${e.name}`,`type: ${e.type}`,`target: ${t.target}`,`bytes: ${t.bytes}${t.truncated?" (truncated)":""}`,`content-type: ${t.contentType??"unknown"}`];return`${c}
6
- ${d.join(`
5
+ ${t}`}const D=o.Object({url:o.String({description:"HTTP or HTTPS URL"}),method:o.Optional(o.String({description:"HTTP method (default: GET)"})),headers:o.Optional(o.Array(o.Object({name:o.String({description:"Header name"}),value:o.String({description:"Header value"})}),{description:"Request headers as name/value pairs"})),body:o.Optional(o.String({description:"Request body (string)"})),timeoutMs:o.Optional(o.Integer({description:"Timeout in milliseconds",minimum:1e3,maximum:12e4})),maxBytes:o.Optional(o.Integer({description:"Max response bytes",minimum:1024,maximum:5e6}))});function E(){return{name:"fetch",label:"fetch",description:"Fetch a URL via HTTP(S) and return the response body.",parameters:D,execute:async(e,t,r,s,n)=>{if(!F(t.url))return{content:[{type:"text",text:"Invalid URL. Only http(s) is allowed."}],details:{url:t.url,status:0,statusText:"invalid_url",bytes:0,truncated:!1,contentType:null}};const l=T(t.timeoutMs),u=g(t.maxBytes),d=new AbortController,c=()=>d.abort();n?.addEventListener("abort",c,{once:!0});const a=setTimeout(()=>d.abort(),l);try{r?.({content:[{type:"text",text:`Fetching ${t.url}...`}],details:{url:t.url,status:0,statusText:"pending",bytes:0,truncated:!1,contentType:null}});const i=await fetch(t.url,{method:t.method?.toUpperCase()??"GET",headers:O(t.headers),body:t.body,signal:d.signal}),p=await j(i,u,n),m=i.headers.get("content-type");return{content:[{type:"text",text:L({url:i.url,status:i.status,statusText:i.statusText,bytes:p.bytes,truncated:p.truncated,contentType:m},p.text)}],details:{url:i.url,status:i.status,statusText:i.statusText,bytes:p.bytes,truncated:p.truncated,contentType:m}}}catch(i){return{content:[{type:"text",text:`Fetch failed: ${i instanceof Error?i.message:String(i)}`}],details:{url:t.url,status:0,statusText:"error",bytes:0,truncated:!1,contentType:null}}}finally{clearTimeout(a),n?.removeEventListener("abort",c)}}}}const H=o.Object({server:o.Optional(o.String({description:"MCP server name"})),method:o.String({description:"MCP method name"}),params:o.Optional(o.Any({description:"MCP params payload"}))});function I(e,t,r){const s=t.status>0?`HTTP ${t.status} ${t.statusText}`.trim():t.statusText,n=[`server: ${e.name}`,`type: ${e.type}`,`target: ${t.target}`,`bytes: ${t.bytes}${t.truncated?" (truncated)":""}`,`content-type: ${t.contentType??"unknown"}`];return`${s}
6
+ ${n.join(`
7
7
  `)}
8
8
 
9
- ${r}`}function H(e){return{name:"mcp",label:"mcp",description:"Call an MCP endpoint via JSON-RPC.",parameters:L,execute:async(t,r,c,d,p)=>{const u=await e();if(u.length===0)return{content:[{type:"text",text:"MCP is not configured. Add [mcp_servers.*] to ~/.tg-agent/config.toml."}],details:{server:"",type:"http",target:"",status:0,statusText:"not_configured",bytes:0,truncated:!1,contentType:null}};let l=r.server?.trim()??"";if(!l)if(u.length===1)l=u[0]?.name??"";else return{content:[{type:"text",text:`Multiple MCP servers configured. Provide server name. Available: ${u.map(n=>n.name).join(", ")}`}],details:{server:"",type:"http",target:"",status:0,statusText:"server_required",bytes:0,truncated:!1,contentType:null}};const s=u.find(n=>n.name===l);if(!s)return{content:[{type:"text",text:`MCP server not found: ${l}. Available: ${u.map(n=>n.name).join(", ")}`}],details:{server:l,type:"http",target:"",status:0,statusText:"server_not_found",bytes:0,truncated:!1,contentType:null}};try{c?.({content:[{type:"text",text:`Calling MCP ${s.name} ${r.method}...`}],details:{server:s.name,type:s.type,target:T(s),status:0,statusText:"pending",bytes:0,truncated:!1,contentType:null}});const n=await g(s,r.method,r.params??{},{timeoutMs:h(void 0),maxBytes:b(void 0),signal:p});return{content:[{type:"text",text:E(s,{server:s.name,type:s.type,target:T(s),status:n.statusCode??0,statusText:n.statusText??(n.ok?"ok":"error"),bytes:n.bytes,truncated:n.truncated,contentType:n.contentType??null},n.output)}],details:{server:s.name,type:s.type,target:T(s),status:n.statusCode??0,statusText:n.statusText??(n.ok?"ok":"error"),bytes:n.bytes,truncated:n.truncated,contentType:n.contentType??null}}}catch(n){return{content:[{type:"text",text:`MCP failed: ${n instanceof Error?n.message:String(n)}`}],details:{server:s.name,type:s.type,target:T(s),status:0,statusText:"error",bytes:0,truncated:!1,contentType:null}}}}}}function F(){const e=[A()];return M(m.agentDir).length>0&&e.push(H(()=>v(m.agentDir))),e}export{F as createCustomTools};
9
+ ${r}`}function R(e){return{name:"mcp",label:"mcp",description:"Call an MCP endpoint via JSON-RPC.",parameters:H,execute:async(t,r,s,n,l)=>{const u=await e();if(u.length===0)return{content:[{type:"text",text:"MCP is not configured. Add [mcp_servers.*] to ~/.tg-agent/config.toml."}],details:{server:"",type:"http",target:"",status:0,statusText:"not_configured",bytes:0,truncated:!1,contentType:null}};let d=r.server?.trim()??"";if(!d)if(u.length===1)d=u[0]?.name??"";else return{content:[{type:"text",text:`Multiple MCP servers configured. Provide server name. Available: ${u.map(a=>a.name).join(", ")}`}],details:{server:"",type:"http",target:"",status:0,statusText:"server_required",bytes:0,truncated:!1,contentType:null}};const c=u.find(a=>a.name===d);if(!c)return{content:[{type:"text",text:`MCP server not found: ${d}. Available: ${u.map(a=>a.name).join(", ")}`}],details:{server:d,type:"http",target:"",status:0,statusText:"server_not_found",bytes:0,truncated:!1,contentType:null}};try{s?.({content:[{type:"text",text:`Calling MCP ${c.name} ${r.method}...`}],details:{server:c.name,type:c.type,target:x(c),status:0,statusText:"pending",bytes:0,truncated:!1,contentType:null}});const a=await w(c,r.method,r.params??{},{timeoutMs:T(void 0),maxBytes:g(void 0),signal:l});return{content:[{type:"text",text:I(c,{server:c.name,type:c.type,target:x(c),status:a.statusCode??0,statusText:a.statusText??(a.ok?"ok":"error"),bytes:a.bytes,truncated:a.truncated,contentType:a.contentType??null},a.output)}],details:{server:c.name,type:c.type,target:x(c),status:a.statusCode??0,statusText:a.statusText??(a.ok?"ok":"error"),bytes:a.bytes,truncated:a.truncated,contentType:a.contentType??null}}}catch(a){return{content:[{type:"text",text:`MCP failed: ${a instanceof Error?a.message:String(a)}`}],details:{server:c.name,type:c.type,target:x(c),status:0,statusText:"error",bytes:0,truncated:!1,contentType:null}}}}}}const U=o.Object({path:o.String({description:"Image file path (relative to workspace or uploads)."}),caption:o.Optional(o.String({description:"Caption text (plain text)."}))}),B=o.Object({path:o.String({description:"File path (relative to workspace or uploads)."}),caption:o.Optional(o.String({description:"Caption text (plain text)."}))});function N(e){const t=[y.workspaceDir,f.join(y.agentDir,"uploads")];return{name:"send_photo",label:"send_photo",description:"Send an image file to the current Telegram chat.",parameters:U,execute:async(r,s)=>{const n=v(s.path,t,y.workspaceDir);if(!n)return{content:[{type:"text",text:"Invalid path. Only workspace or uploads are allowed."}],details:{path:s.path,bytes:0}};const l=await M(n);return l?(await e.sendPhoto(n,s.caption?.trim()||void 0),{content:[{type:"text",text:`Photo sent: ${f.basename(n)}`}],details:{path:n,bytes:l.bytes}}):{content:[{type:"text",text:`File not found: ${n}`}],details:{path:n,bytes:0}}}}}function z(e){const t=[y.workspaceDir,f.join(y.agentDir,"uploads")];return{name:"send_file",label:"send_file",description:"Send a file to the current Telegram chat.",parameters:B,execute:async(r,s)=>{const n=v(s.path,t,y.workspaceDir);if(!n)return{content:[{type:"text",text:"Invalid path. Only workspace or uploads are allowed."}],details:{path:s.path,bytes:0}};const l=await M(n);return l?(await e.sendDocument(n,s.caption?.trim()||void 0),{content:[{type:"text",text:`File sent: ${f.basename(n)}`}],details:{path:n,bytes:l.bytes}}):{content:[{type:"text",text:`File not found: ${n}`}],details:{path:n,bytes:0}}}}}function K(e){const t=[E()];return e?.telegram&&(t.push(N(e.telegram)),t.push(z(e.telegram))),_(y.agentDir).length>0&&t.push(R(()=>S(y.agentDir))),t}export{K as createCustomTools};
package/dist/index.js CHANGED
@@ -1,14 +1,17 @@
1
- import V from"node-telegram-bot-api";import{discoverAuthStorage as B,discoverModels as J}from"@mariozechner/pi-coding-agent";import{getOAuthProviders as K}from"@mariozechner/pi-ai";import{config as f,assertConfig as Y}from"./config.js";import{resolveApiKeyForProvider as G,resolveProxyInfo as Z}from"./auth.js";import{applyFetchProxy as Q}from"./proxy.js";import{runPiAgentPrompt as I}from"./piAgentRunner.js";import{readCodexOAuth as ee}from"./codexAuth.js";import{formatMcpTarget as te,loadMcpServers as oe,loadMcpServersSync as ne,probeMcpServer as re}from"./mcp.js";import{appendMessage as D,closeSession as se,createSession as R,deleteSessionFile as A,getActiveSession as M,listSessions as ie,loadUserState as b,pruneExpiredSessions as O,resetSession as ae,saveUserState as p,setActiveSession as ce}from"./sessionStore.js";import{chunkText as le,createQueueMap as ue,createSemaphore as de,ensureDir as U,nowMs as P}from"./utils.js";Y();const y=new V(f.telegramToken,{polling:!0}),fe=ue(),me=de(f.maxConcurrent),T=new Map;function ge(e,t){T.set(e,t)}function pe(e){T.delete(e)}function we(e){const t=e.trim();if(t){if(t==="Markdown"||t==="MarkdownV2"||t==="HTML")return t;console.warn(`[tg-agent] invalid TELEGRAM_PARSE_MODE=${t}, ignoring`)}}const $e=new Set(["_","*","[","]","(",")","~","`",">","#","+","-","=","|","{","}",".","!"]);function _(e,t){let o=0;for(let r=0;r<e.length;r+=1){const s=e[r];if(s==="\\"){r+=1;continue}s===t&&(o+=1)}return o}function ye(e){if(_(e,"*")%2!==0||_(e,"_")%2!==0||_(e,"`")%2!==0)return!1;const s=_(e,"["),u=_(e,"]");return s===u}function ve(e){for(let t=0;t<e.length;t+=1){const o=e[t];if(o==="\\"){t+=1;continue}if($e.has(o))return!1}return!0}function L(e){const t=we(f.telegramParseMode);return t?[t]:ve(e)?["MarkdownV2","Markdown"]:ye(e)?["Markdown"]:[]}const he=600*1e3,$=new Map,x=new Set,Se=new Map([["codex","openai-codex"],["antigravity","google-antigravity"],["gemini","google-gemini-cli"],["gemini-cli","google-gemini-cli"]]),Me={"codex-mini-latest":"gpt-5.1-codex-mini","codex-max-latest":"gpt-5.1-codex-max","codex-latest":"gpt-5.2-codex"};function h(e){const t=e.trim().toLowerCase();return Se.get(t)??t}function _e(e,t){return e!=="openai-codex"?t:Me[t]??t}async function S(){await U(f.agentDir);const e=B(f.agentDir),t=ee();t&&e.setRuntimeApiKey("openai-codex",t.accessToken);const o=J(e,f.agentDir);return{authStorage:e,modelRegistry:o}}function N(e){const t=$.get(e);t&&(clearTimeout(t.timeoutId),$.delete(e))}function Ae(e,t){const o=$.get(e);o&&(clearTimeout(o.timeoutId),$.delete(e),o.reject(new Error(t)))}async function z(e,t,o){if($.has(e))throw new Error("Login is already awaiting input.");const r=o.placeholder?`${o.message} (${o.placeholder})`:o.message;return await c(t,r),await new Promise((s,u)=>{const n=setTimeout(()=>{$.delete(e),u(new Error("Login prompt timed out."))},he);$.set(e,{resolve:s,reject:u,timeoutId:n,chatId:t})})}async function Pe(e,t){const o=T.get(t);if(!o){await c(e,"No active request to stop.");return}o.cancelRequested=!0,o.abortRequested=!0,o.status&&o.status.update("Cancelled by user.",!0),o.abort&&o.abort(),(!o.status||o.chatId!==e)&&await c(e,"Stopping current request...")}function Ee(){const e={https_proxy:process.env.https_proxy??process.env.HTTPS_PROXY??"",http_proxy:process.env.http_proxy??process.env.HTTP_PROXY??"",all_proxy:process.env.all_proxy??process.env.ALL_PROXY??"",no_proxy:process.env.no_proxy??process.env.NO_PROXY??""},t={NODE_EXTRA_CA_CERTS:process.env.NODE_EXTRA_CA_CERTS??"",NODE_TLS_REJECT_UNAUTHORIZED:process.env.NODE_TLS_REJECT_UNAUTHORIZED??""};console.log(`[tg-agent] modelProvider=${f.modelProvider} modelRef=${f.modelRef} sessionDir=${f.sessionDir} maxConcurrent=${f.maxConcurrent}`),console.log(`[tg-agent] agentDir=${f.agentDir} workspaceDir=${f.workspaceDir}`);const o=ne(f.agentDir),r=o.length>0?`on (${o.length})`:"off";console.log(`[tg-agent] tools fetchMaxBytes=${f.fetchMaxBytes} fetchTimeoutMs=${f.fetchTimeoutMs} mcp=${r}`),console.log(`[tg-agent] proxy https=${e.https_proxy||"(empty)"} http=${e.http_proxy||"(empty)"} all=${e.all_proxy||"(empty)"} no=${e.no_proxy||"(empty)"}`),console.log(`[tg-agent] tls extra_ca=${t.NODE_EXTRA_CA_CERTS||"(empty)"} reject_unauthorized=${t.NODE_TLS_REJECT_UNAUTHORIZED||"(empty)"}`);try{const{source:u}=G(f.modelProvider);console.log(`[tg-agent] authSource=${u}`)}catch(u){const n=u instanceof Error?u.message:String(u);console.warn(`[tg-agent] authSource=missing (${n})`)}const s=Z();console.log(s?`[tg-agent] proxyUrl=${s.url} kind=${s.kind} source=${s.source}`:"[tg-agent] proxyUrl=(none)")}Ee(),U(f.agentDir).catch(e=>{console.warn(`[tg-agent] ensure agentDir failed: ${e instanceof Error?e.message:String(e)}`)});const E=Q();console.log(E?`[tg-agent] fetchProxy=${E.url} kind=${E.kind} source=${E.source}`:"[tg-agent] fetchProxy=(none)");function q(e){if(!e.startsWith("/"))return null;const t=e.trim(),[o,...r]=t.split(" ");return{command:o.split("@")[0].slice(1).toLowerCase(),args:r.join(" ").trim()}}async function j(e,t){let o=M(e);return o||(o=R(e,""),await p(e),await c(t,`Created session ${o.id}.`)),o}function Re(e,t){const o=e.getAll(),r=new Map;for(const u of o){const n=r.get(u.provider)??{count:0,auth:t.hasAuth(u.provider)};n.count+=1,r.set(u.provider,n)}return r.size===0?"No providers found.":["Providers:",...Array.from(r.entries()).sort((u,n)=>u[0].localeCompare(n[0])).map(([u,n])=>`- ${u} (models: ${n.count}, auth: ${n.auth?"ok":"missing"})`),"","Use /models <provider> to list models."].join(`
2
- `)}function Te(e,t,o){const r=e.getAll().filter(u=>u.provider===t);if(r.length===0)return`No models found for provider ${t}.`;const s=r.map(u=>`- ${u.id} | ${u.name}`);return[`Models for ${t} (auth: ${o?"ok":"missing"}):`,...s].join(`
3
- `)}function xe(e){const t=e.trim();if(!t)return null;if(t.includes("/")){const[o,r]=t.split("/",2);return!o||!r?null:{provider:h(o),modelId:r.trim()}}return{modelId:t}}const ke=6e4,F=new Map;function Ce(e,t){const o=Date.now(),r=F.get(e)??0;o-r<ke||(F.set(e,o),console.warn(`[tg-agent] unauthorized user=${e} chat=${t}`))}function De(e){const t=f.telegramAllowedUsers;return!t||t.size===0?!0:t.has(e)}function be(e){return e.length===0?"No sessions found.":["Sessions:",...e.map(o=>{const r=new Date(o.updatedAt).toISOString();return`- ${o.id} | ${o.title} | updated ${r}`})].join(`
4
- `)}async function H(e,t,o){return o?await y.sendMessage(e,t,{parse_mode:o}):await y.sendMessage(e,t)}async function W(e,t,o,r){if(r){await y.editMessageText(o,{chat_id:e,message_id:t,parse_mode:r});return}await y.editMessageText(o,{chat_id:e,message_id:t})}async function X(e,t){let o;for(const r of t)try{return await e(r)}catch(s){o=s;const u=s instanceof Error?s.message:String(s);console.warn(`[tg-agent] parse_mode ${r} failed, trying fallback: ${u}`)}try{return await e(void 0)}catch(r){if(o){const s=r instanceof Error?r.message:String(r);console.warn(`[tg-agent] parse_mode fallback to plain failed: ${s}`)}throw r}}async function Oe(e,t){return await X(o=>H(e,t,o),L(t))}async function Ue(e,t,o){await X(r=>W(e,t,o,r),L(o))}async function c(e,t){const o=le(t,3900);for(const r of o)await Oe(e,r)}const Le=3900,Ne=1200;function k(e,t=Le){return e.length<=t?{text:e,truncated:!1}:{text:`${e.slice(0,Math.max(0,t-15))}...
1
+ import b from"node:path";import B from"node:fs/promises";import te from"node-telegram-bot-api";import{discoverAuthStorage as ne,discoverModels as oe}from"@mariozechner/pi-coding-agent";import{getOAuthProviders as re}from"@mariozechner/pi-ai";import{config as m,assertConfig as se}from"./config.js";import{resolveApiKeyForProvider as ie,resolveProxyInfo as ae}from"./auth.js";import{applyFetchProxy as ce}from"./proxy.js";import{runPiAgentPrompt as le}from"./piAgentRunner.js";import{readCodexOAuth as ue}from"./codexAuth.js";import{formatMcpTarget as de,freezeMcpCatalog as F,getMcpCatalog as q,loadMcpServers as me,loadMcpServersSync as fe,probeMcpServer as ge,refreshMcpCatalog as pe}from"./mcp.js";import{appendMessage as W,closeSession as we,createSession as L,deleteSessionFile as k,getActiveSession as x,listSessions as he,loadUserState as H,pruneExpiredSessions as X,resetSession as ye,saveUserState as w,setActiveSession as $e}from"./sessionStore.js";import{chunkText as ve,createQueueMap as Se,createSemaphore as Me,ensureDir as U,nowMs as C}from"./utils.js";se();const y=new te(m.telegramToken,{polling:!0}),_e=Se(),Ae=Me(m.maxConcurrent),O=new Map;function Pe(e,t){O.set(e,t)}function Te(e){O.delete(e)}function xe(e){const t=e.trim();if(t){if(t==="Markdown"||t==="MarkdownV2"||t==="HTML")return t;console.warn(`[tg-agent] invalid TELEGRAM_PARSE_MODE=${t}, ignoring`)}}const Ee="image/jpeg";function Re(){return b.join(m.agentDir,"uploads")}function ke(e){return e.length===0?null:e.reduce((t,n)=>{const o=t.file_size??t.width*t.height;return(n.file_size??n.width*n.height)>o?n:t})}function Ce(e){return!!e?.startsWith("image/")}function De(e){switch(b.extname(e).toLowerCase()){case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".webp":return"image/webp";case".gif":return"image/gif";default:return}}function be(e){if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KB","MB","GB"];let n=e,o=0;for(;n>=1024&&o<t.length-1;)n/=1024,o+=1;return`${n.toFixed(n>=10||o===0?0:1)} ${t[o]}`}function Le(e){return e.length===0?"":e.map(t=>{const n=[];t.mimeType&&n.push(t.mimeType),t.bytes>0&&n.push(be(t.bytes));const o=n.length>0?` (${n.join(", ")})`:"";return`Attachment saved: ${t.path}${o}`}).join(`
2
+ `)}function Ue(e){const t=[];if(e.photo&&e.photo.length>0){const n=ke(e.photo);n&&t.push({kind:"photo",fileId:n.file_id,fileSize:n.file_size})}return e.document&&t.push({kind:"document",fileId:e.document.file_id,fileName:e.document.file_name,mimeType:e.document.mime_type,fileSize:e.document.file_size}),t}async function Oe(e){if(e.length===0)return{resolved:[],images:[]};const t=Re();await U(t);const n=[],o=[];for(const s of e)try{const a=await y.downloadFile(s.fileId,t),r=await B.stat(a),c=s.fileName??b.basename(a),u=s.mimeType??De(a),i=s.kind==="photo"||Ce(u),d={path:a,name:c,mimeType:u,bytes:r.size,isImage:i};if(n.push(d),i){const f=await B.readFile(a);o.push({type:"image",data:f.toString("base64"),mimeType:u??Ee})}}catch(a){console.warn("[tg-agent] attachment download failed",a)}return{resolved:n,images:o}}const ze=new Set(["_","*","[","]","(",")","~","`",">","#","+","-","=","|","{","}",".","!"]);function E(e,t){let n=0;for(let o=0;o<e.length;o+=1){const s=e[o];if(s==="\\"){o+=1;continue}s===t&&(n+=1)}return n}function Ne(e){if(E(e,"*")%2!==0||E(e,"_")%2!==0||E(e,"`")%2!==0)return!1;const s=E(e,"["),a=E(e,"]");return s===a}function je(e){for(let t=0;t<e.length;t+=1){const n=e[t];if(n==="\\"){t+=1;continue}if(ze.has(n))return!1}return!0}function V(e){const t=xe(m.telegramParseMode);return t?[t]:je(e)?["MarkdownV2","Markdown"]:Ne(e)?["Markdown"]:[]}const Be=600*1e3,S=new Map,z=new Set,Fe=new Map([["codex","openai-codex"],["antigravity","google-antigravity"],["gemini","google-gemini-cli"],["gemini-cli","google-gemini-cli"]]),qe={"codex-mini-latest":"gpt-5.1-codex-mini","codex-max-latest":"gpt-5.1-codex-max","codex-latest":"gpt-5.2-codex"};function A(e){const t=e.trim().toLowerCase();return Fe.get(t)??t}function We(e,t){return e!=="openai-codex"?t:qe[t]??t}async function P(){await U(m.agentDir);const e=ne(m.agentDir),t=ue();t&&e.setRuntimeApiKey("openai-codex",t.accessToken);const n=oe(e,m.agentDir);return{authStorage:e,modelRegistry:n}}function K(e){const t=S.get(e);t&&(clearTimeout(t.timeoutId),S.delete(e))}function He(e,t){const n=S.get(e);n&&(clearTimeout(n.timeoutId),S.delete(e),n.reject(new Error(t)))}async function G(e,t,n){if(S.has(e))throw new Error("Login is already awaiting input.");const o=n.placeholder?`${n.message} (${n.placeholder})`:n.message;return await l(t,o),await new Promise((s,a)=>{const r=setTimeout(()=>{S.delete(e),a(new Error("Login prompt timed out."))},Be);S.set(e,{resolve:s,reject:a,timeoutId:r,chatId:t})})}async function Xe(e,t){const n=O.get(t);if(!n){await l(e,"No active request to stop.");return}n.cancelRequested=!0,n.abortRequested=!0,n.status&&n.status.update("Cancelled by user.",!0),n.abort&&n.abort(),(!n.status||n.chatId!==e)&&await l(e,"Stopping current request...")}function Ve(){const e={https_proxy:process.env.https_proxy??process.env.HTTPS_PROXY??"",http_proxy:process.env.http_proxy??process.env.HTTP_PROXY??"",all_proxy:process.env.all_proxy??process.env.ALL_PROXY??"",no_proxy:process.env.no_proxy??process.env.NO_PROXY??""},t={NODE_EXTRA_CA_CERTS:process.env.NODE_EXTRA_CA_CERTS??"",NODE_TLS_REJECT_UNAUTHORIZED:process.env.NODE_TLS_REJECT_UNAUTHORIZED??""};console.log(`[tg-agent] modelProvider=${m.modelProvider} modelRef=${m.modelRef} sessionDir=${m.sessionDir} maxConcurrent=${m.maxConcurrent}`),console.log(`[tg-agent] agentDir=${m.agentDir} workspaceDir=${m.workspaceDir}`);const n=fe(m.agentDir),o=n.length>0?`on (${n.length})`:"off";console.log(`[tg-agent] tools fetchMaxBytes=${m.fetchMaxBytes} fetchTimeoutMs=${m.fetchTimeoutMs} mcp=${o}`),console.log(`[tg-agent] proxy https=${e.https_proxy||"(empty)"} http=${e.http_proxy||"(empty)"} all=${e.all_proxy||"(empty)"} no=${e.no_proxy||"(empty)"}`),console.log(`[tg-agent] tls extra_ca=${t.NODE_EXTRA_CA_CERTS||"(empty)"} reject_unauthorized=${t.NODE_TLS_REJECT_UNAUTHORIZED||"(empty)"}`);try{const{source:a}=ie(m.modelProvider);console.log(`[tg-agent] authSource=${a}`)}catch(a){const r=a instanceof Error?a.message:String(a);console.warn(`[tg-agent] authSource=missing (${r})`)}const s=ae();console.log(s?`[tg-agent] proxyUrl=${s.url} kind=${s.kind} source=${s.source}`:"[tg-agent] proxyUrl=(none)")}Ve(),U(m.agentDir).catch(e=>{console.warn(`[tg-agent] ensure agentDir failed: ${e instanceof Error?e.message:String(e)}`)}),(async()=>{try{await q(m.agentDir,{timeoutMs:3e3,maxBytes:m.fetchMaxBytes,maxChars:2e3}),F()}catch(e){const t=e instanceof Error?e.message:String(e);console.warn(`[tg-agent] mcp catalog warmup failed: ${t}`)}})();const D=ce();console.log(D?`[tg-agent] fetchProxy=${D.url} kind=${D.kind} source=${D.source}`:"[tg-agent] fetchProxy=(none)");function J(e){if(!e.startsWith("/"))return null;const t=e.trim(),[n,...o]=t.split(" ");return{command:n.split("@")[0].slice(1).toLowerCase(),args:o.join(" ").trim()}}async function Y(e,t){let n=x(e);return n||(n=L(e,""),await w(e),await l(t,`Created session ${n.id}.`)),n}function Ke(e,t){const n=e.getAll(),o=new Map;for(const a of n){const r=o.get(a.provider)??{count:0,auth:t.hasAuth(a.provider)};r.count+=1,o.set(a.provider,r)}return o.size===0?"No providers found.":["Providers:",...Array.from(o.entries()).sort((a,r)=>a[0].localeCompare(r[0])).map(([a,r])=>`- ${a} (models: ${r.count}, auth: ${r.auth?"ok":"missing"})`),"","Use /models <provider> to list models."].join(`
3
+ `)}function Ge(e,t,n){const o=e.getAll().filter(a=>a.provider===t);if(o.length===0)return`No models found for provider ${t}.`;const s=o.map(a=>`- ${a.id} | ${a.name}`);return[`Models for ${t} (auth: ${n?"ok":"missing"}):`,...s].join(`
4
+ `)}function Je(e){const t=e.trim();if(!t)return null;if(t.includes("/")){const[n,o]=t.split("/",2);return!n||!o?null:{provider:A(n),modelId:o.trim()}}return{modelId:t}}const Ye=6e4,Z=new Map;function Ze(e,t){const n=Date.now(),o=Z.get(e)??0;n-o<Ye||(Z.set(e,n),console.warn(`[tg-agent] unauthorized user=${e} chat=${t}`))}function Qe(e){const t=m.telegramAllowedUsers;return!t||t.size===0?!0:t.has(e)}function Ie(e){return e.length===0?"No sessions found.":["Sessions:",...e.map(n=>{const o=new Date(n.updatedAt).toISOString();return`- ${n.id} | ${n.title} | updated ${o}`})].join(`
5
+ `)}async function Q(e,t,n){return n?await y.sendMessage(e,t,{parse_mode:n}):await y.sendMessage(e,t)}async function I(e,t,n,o){if(o){await y.editMessageText(n,{chat_id:e,message_id:t,parse_mode:o});return}await y.editMessageText(n,{chat_id:e,message_id:t})}async function ee(e,t){let n;for(const o of t)try{return await e(o)}catch(s){n=s;const a=s instanceof Error?s.message:String(s);console.warn(`[tg-agent] parse_mode ${o} failed, trying fallback: ${a}`)}try{return await e(void 0)}catch(o){if(n){const s=o instanceof Error?o.message:String(o);console.warn(`[tg-agent] parse_mode fallback to plain failed: ${s}`)}throw o}}async function et(e,t){return await ee(n=>Q(e,t,n),V(t))}async function tt(e,t,n){await ee(o=>I(e,t,n,o),V(n))}async function l(e,t){const n=ve(t,3900);for(const o of n)await et(e,o)}const nt=3900,ot=1200;function N(e,t=nt){return e.length<=t?{text:e,truncated:!1}:{text:`${e.slice(0,Math.max(0,t-15))}...
5
6
 
6
- [truncated]`,truncated:!0}}async function ze(e){const t="Working...",r=(await H(e,t)).message_id;let s=t,u=0,n=Promise.resolve();const d=(i,a=!1)=>{const m=Date.now();!a&&m-u<Ne||i!==s&&(s=i,u=m,n=n.then(async()=>{await W(e,r,i)}).catch(g=>{console.warn("[tg-agent] status edit failed",g)}))},l=i=>{s=i,u=Date.now(),n=n.then(async()=>{await Ue(e,r,i)}).catch(a=>{console.warn("[tg-agent] status edit failed",a)})};return{update:(i,a=!1)=>{const m=k(i).text;d(m,a)},finalize:async i=>{const a=k(i).text;l(a),await n},fail:async i=>{const a=k(i).text;l(a),await n}}}function qe(e){switch(e.type){case"agent_start":return"Working...";case"tool_start":return`Running tool: ${e.name}
7
- args: ${je(e.args)}`;case"tool_end":return e.ok?`Tool finished: ${e.name} (${e.durationMs}ms)`:`Tool failed: ${e.name} (${e.durationMs}ms)`;case"message_start":return e.role==="assistant"?"Generating reply...":null;case"message_end":return e.role==="assistant"?"Finalizing reply...":null;case"heartbeat":return`Working... ${Math.max(1,Math.round(e.elapsedMs/1e3))}s`;default:return null}}function je(e){try{const t=new WeakSet,o=200,r=12,s=12,u=3,n=(i,a)=>{if(i==null)return i;if(typeof i=="string")return i.length>o?`${i.slice(0,o)}...`:i;if(typeof i=="number"||typeof i=="boolean")return i;if(typeof i=="bigint")return i.toString();if(typeof i=="function")return"[function]";if(typeof i!="object")return String(i);if(t.has(i))return"[circular]";if(a>=u)return"[truncated]";if(t.add(i),Array.isArray(i)){const w=i.slice(0,s).map(v=>n(v,a+1));return i.length>s&&w.push("[truncated]"),w}const m=Object.entries(i),g={};for(const[w,v]of m.slice(0,r))g[w]=n(v,a+1);return m.length>r&&(g._truncated=!0),g},d=n(e,0),l=JSON.stringify(d);return l?l.length>700?`${l.slice(0,700)}...`:l:"null"}catch{return"[unavailable]"}}function C(e){const t=e instanceof Error?e.message:String(e),o=e?.cause;return o instanceof Error?`${t} (${o.message})`:o?`${t} (${String(o)})`:t}function Fe(e,t=140){return e.length<=t?e:`${e.slice(0,t-3)}...`}async function He(){const e=await oe(f.agentDir);if(e.length===0)return"No MCP servers configured. Add [mcp_servers.*] to ~/.tg-agent/config.toml.";const t=await Promise.all(e.map(r=>re(r))),o=["MCP servers:"];for(let r=0;r<e.length;r+=1){const s=e[r],u=t[r],n=u.ok?"ok":`error: ${Fe(u.error??"unknown")}`;o.push(`- ${s.name} (${s.type}) ${te(s)} status=${n} (${u.durationMs}ms)`)}return o.join(`
8
- `)}async function We(e,t,o,r){const s=await b(t),u=O(s);switch(u.length>0&&(await p(s),await Promise.all(u.map(n=>A(t,n).catch(d=>{console.warn(`[tg-agent] cleanup session file failed id=${n}`,d)})))),o){case"start":case"help":{const n=["Commands:","/new [title] - create a new session","/list - list sessions","/use <id> - switch active session","/close [id] - close a session (default: active)","/reset - clear active session history","/providers - list available providers","/models [provider] - list models for provider","/provider <name> - set provider for current session","/model <provider>/<model> - set model for current session","/mcp - list configured MCP servers","/status - show session model settings","/login <provider> - login to OAuth provider","/logout <provider> - logout from provider","/stop - stop the current running request"].join(`
9
- `);await c(e,n);return}case"new":{try{const d=R(s,r||"");await p(s),await c(e,`Created session ${d.id} (${d.title}).`)}catch(n){await c(e,n.message)}return}case"list":{await p(s),await c(e,be(ie(s)));return}case"use":{if(!r){await c(e,"Usage: /use <id>");return}if(!ce(s,r)){await c(e,`Session not found: ${r}`);return}await p(s),await c(e,`Active session set to ${r}.`);return}case"close":{const n=r||s.activeSessionId;if(!n){await c(e,"No active session to close.");return}if(!se(s,n)){await c(e,`Session not found: ${n}`);return}await p(s),await A(t,n).catch(l=>{console.warn(`[tg-agent] delete session file failed id=${n}`,l)}),await c(e,`Closed session ${n}.`);return}case"reset":{const n=M(s);if(!n){await c(e,"No active session.");return}ae(n),await p(s),await A(t,n.id).catch(d=>{console.warn(`[tg-agent] reset session file failed id=${n.id}`,d)}),await c(e,`Reset session ${n.id}.`);return}case"providers":{const{authStorage:n,modelRegistry:d}=await S(),l=d.getError(),i=Re(d,n),a=l?`Warning: ${l}
7
+ [truncated]`,truncated:!0}}async function rt(e){const t="Working...",o=(await Q(e,t)).message_id;let s=t,a=0,r=Promise.resolve();const c=(i,d=!1)=>{const f=Date.now();!d&&f-a<ot||i!==s&&(s=i,a=f,r=r.then(async()=>{await I(e,o,i)}).catch(h=>{console.warn("[tg-agent] status edit failed",h)}))},u=i=>{s=i,a=Date.now(),r=r.then(async()=>{await tt(e,o,i)}).catch(d=>{console.warn("[tg-agent] status edit failed",d)})};return{update:(i,d=!1)=>{const f=N(i).text;c(f,d)},finalize:async i=>{const d=N(i).text;u(d),await r},fail:async i=>{const d=N(i).text;u(d),await r}}}function st(e){switch(e.type){case"agent_start":return"Working...";case"tool_start":return`Running tool: ${e.name}
8
+ args: ${it(e.args)}`;case"tool_end":return e.ok?`Tool finished: ${e.name} (${e.durationMs}ms)`:`Tool failed: ${e.name} (${e.durationMs}ms)`;case"message_start":return e.role==="assistant"?"Generating reply...":null;case"message_end":return e.role==="assistant"?"Finalizing reply...":null;case"heartbeat":return`Working... ${Math.max(1,Math.round(e.elapsedMs/1e3))}s`;default:return null}}function it(e){try{const t=new WeakSet,n=200,o=12,s=12,a=3,r=(i,d)=>{if(i==null)return i;if(typeof i=="string")return i.length>n?`${i.slice(0,n)}...`:i;if(typeof i=="number"||typeof i=="boolean")return i;if(typeof i=="bigint")return i.toString();if(typeof i=="function")return"[function]";if(typeof i!="object")return String(i);if(t.has(i))return"[circular]";if(d>=a)return"[truncated]";if(t.add(i),Array.isArray(i)){const M=i.slice(0,s).map(g=>r(g,d+1));return i.length>s&&M.push("[truncated]"),M}const f=Object.entries(i),h={};for(const[M,g]of f.slice(0,o))h[M]=r(g,d+1);return f.length>o&&(h._truncated=!0),h},c=r(e,0),u=JSON.stringify(c);return u?u.length>700?`${u.slice(0,700)}...`:u:"null"}catch{return"[unavailable]"}}function j(e){const t=e instanceof Error?e.message:String(e),n=e?.cause;return n instanceof Error?`${t} (${n.message})`:n?`${t} (${String(n)})`:t}function at(e,t=140){return e.length<=t?e:`${e.slice(0,t-3)}...`}async function ct(){const e=await me(m.agentDir);if(e.length===0)return"No MCP servers configured. Add [mcp_servers.*] to ~/.tg-agent/config.toml.";const t=await Promise.all(e.map(o=>ge(o))),n=["MCP servers:"];for(let o=0;o<e.length;o+=1){const s=e[o],a=t[o],r=a.ok?"ok":`error: ${at(a.error??"unknown")}`;n.push(`- ${s.name} (${s.type}) ${de(s)} status=${r} (${a.durationMs}ms)`)}return n.join(`
9
+ `)}async function lt(e,t,n,o){const s=await H(t),a=X(s);switch(a.length>0&&(await w(s),await Promise.all(a.map(r=>k(t,r).catch(c=>{console.warn(`[tg-agent] cleanup session file failed id=${r}`,c)})))),n){case"start":case"help":{const r=["Commands:","/new [title] - create a new session","/list - list sessions","/use <id> - switch active session","/close [id] - close a session (default: active)","/reset - clear active session history","/providers - list available providers","/models [provider] - list models for provider","/provider <name> - set provider for current session","/model <provider>/<model> - set model for current session","/mcp - list configured MCP servers","/mcp refresh - reload MCP catalog","/status - show session model settings","/login <provider> - login to OAuth provider","/logout <provider> - logout from provider","/stop - stop the current running request","","Tips:","Send images or files to attach them to the prompt."].join(`
10
+ `);await l(e,r);return}case"new":{try{const c=L(s,o||"");await w(s),await l(e,`Created session ${c.id} (${c.title}).`)}catch(r){await l(e,r.message)}return}case"list":{await w(s),await l(e,Ie(he(s)));return}case"use":{if(!o){await l(e,"Usage: /use <id>");return}if(!$e(s,o)){await l(e,`Session not found: ${o}`);return}await w(s),await l(e,`Active session set to ${o}.`);return}case"close":{const r=o||s.activeSessionId;if(!r){await l(e,"No active session to close.");return}if(!we(s,r)){await l(e,`Session not found: ${r}`);return}await w(s),await k(t,r).catch(u=>{console.warn(`[tg-agent] delete session file failed id=${r}`,u)}),await l(e,`Closed session ${r}.`);return}case"reset":{const r=x(s);if(!r){await l(e,"No active session.");return}ye(r),await w(s),await k(t,r.id).catch(c=>{console.warn(`[tg-agent] reset session file failed id=${r.id}`,c)}),await l(e,`Reset session ${r.id}.`);return}case"providers":{const{authStorage:r,modelRegistry:c}=await P(),u=c.getError(),i=Ke(c,r),d=u?`Warning: ${u}
10
11
 
11
- ${i}`:i;await c(e,a);return}case"models":{const{authStorage:n,modelRegistry:d}=await S();let l=r?h(r):"";if(!l){const a=M(s);if(!a?.modelProvider){await c(e,"Usage: /models <provider>");return}l=a.modelProvider}const i=n.hasAuth(l);await c(e,Te(d,l,i));return}case"provider":{if(!r){await c(e,"Usage: /provider <name>");return}const n=h(r),{modelRegistry:d}=await S();if(!d.getAll().some(a=>a.provider===n)){await c(e,`Unknown provider: ${n}`);return}const i=await j(s,e);i.modelProvider=n,i.modelId=void 0,i.updatedAt=P(),await p(s),await c(e,`Session ${i.id} provider set to ${n}.`);return}case"model":{const n=xe(r);if(!n){await c(e,"Usage: /model <provider>/<model>");return}const d=await j(s,e),l=h(n.provider??d.modelProvider??"");if(!l){await c(e,"Usage: /model <provider>/<model>");return}const{modelRegistry:i}=await S(),a=_e(l,n.modelId);if(!i.find(l,a)){await c(e,`Model not found: ${l}/${a}`);return}d.modelProvider=l,d.modelId=a,d.updatedAt=P(),await p(s),await c(e,`Session ${d.id} model set to ${l}/${a}.`);return}case"status":{const n=M(s);if(!n){await c(e,"No active session.");return}const d=f.modelRef.includes("/")?f.modelRef:`${f.modelProvider}/${f.modelRef}`,l=n.modelProvider||n.modelId?`Session model: ${n.modelProvider??"?"}/${n.modelId??"?"}`:`Default model: ${d}`,i=[`Session: ${n.id} (${n.title})`,l].join(`
12
- `);await c(e,i);return}case"mcp":{const n=await He();await c(e,n);return}case"login":{const n=K();if(!r){const i=n.map(a=>`- ${a.id} (${a.name})`);await c(e,["OAuth providers:",...i].join(`
13
- `));return}const d=h(r);if(x.has(t)){await c(e,"Login already in progress.");return}const l=n.find(i=>i.id===d);if(!l){await c(e,`Unknown OAuth provider: ${d}`);return}x.add(t);try{const{authStorage:i}=await S();await c(e,`Starting login for ${l.id}...`),await i.login(l.id,{onAuth:({url:a,instructions:m})=>{const g=["Open this URL in your browser:",a];m&&g.push("",m),c(e,g.join(`
14
- `))},onPrompt:a=>z(t,e,a),onProgress:a=>{c(e,a)},onManualCodeInput:()=>z(t,e,{message:"Paste the authorization code:"})}),await c(e,`Login completed for ${l.id}.`)}catch(i){const a=C(i);await c(e,`Login failed: ${a}`)}finally{x.delete(t),N(t)}return}case"logout":{if(!r){await c(e,"Usage: /logout <provider>");return}const n=h(r),{authStorage:d}=await S();d.logout(n),await c(e,`Logged out from ${n}.`);return}default:await c(e,`Unknown command: ${o}`)}}async function Xe(e,t,o){const r=q(o);if(r){await We(e,t,r.command,r.args);return}const s=await b(t),u=O(s);u.length>0&&(await p(s),await Promise.all(u.map(a=>A(t,a).catch(m=>{console.warn(`[tg-agent] cleanup session file failed id=${a}`,m)}))));let n=M(s);if(!n)try{n=R(s,""),await p(s),await c(e,`Created session ${n.id}.`)}catch(a){await c(e,a.message);return}const d={role:"user",content:o,ts:P()};D(n,d,f.maxHistoryMessages),await p(s),console.log(`[tg-agent] request user=${t} session=${n.id} messages=${n.messages.length} textLen=${o.length} provider=${n.modelProvider??"-"} model=${n.modelId??"-"}`);let l=null;try{l=await ze(e)}catch(a){console.warn("[tg-agent] status message failed",a)}const i={sessionId:n.id,chatId:e,status:l,abortRequested:!1,cancelRequested:!1};ge(t,i);try{const a=await me(async()=>{const g=await I({userId:t,sessionId:n.id,prompt:o,systemPrompt:f.systemPrompt,modelProvider:n.modelProvider,modelId:n.modelId,onAbortReady:w=>{i.abort=w,i.abortRequested&&w()},onStatus:l?w=>{const v=qe(w);v&&l?.update(v)}:void 0});return console.log(`[tg-agent] response session=${n.id} model=${g.modelProvider}/${g.modelId} file=${g.sessionFile}`),g.text});if(i.cancelRequested){l?await l.finalize("Cancelled by user."):await c(e,"Cancelled by user.");return}const m={role:"assistant",content:a,ts:P()};D(n,m,f.maxHistoryMessages),await p(s),l?await l.finalize(a):await c(e,a)}catch(a){if(console.error("[tg-agent] runModel error",a),i.cancelRequested){l?await l.fail("Cancelled by user."):await c(e,"Cancelled by user.");return}const m=C(a);l?await l.fail(`Error: ${m}`):await c(e,`Error: ${m}`)}finally{pe(t)}}y.on("message",e=>{if(!e.text)return;if(e.chat.type!=="private"){y.sendMessage(e.chat.id,"This bot only supports direct messages.");return}if(!e.from?.id)return;const t=String(e.from.id),o=e.chat.id;if(!De(t)){Ce(t,o);return}const r=e.text.trim(),s=$.get(t);if(s){if(r==="/stop"||r==="/cancel"){Ae(t,"Login cancelled by user."),c(o,"Login cancelled.");return}N(t),s.resolve(r);return}if(q(r)?.command==="stop"){Pe(o,t);return}fe(t,()=>Xe(o,t,r)).catch(async n=>{console.error("[tg-agent] runModel error",n);const d=C(n);await c(o,`Error: ${d}`)})}),y.on("polling_error",e=>{console.error("Polling error",e)}),console.log("tg-agent started");
12
+ ${i}`:i;await l(e,d);return}case"models":{const{authStorage:r,modelRegistry:c}=await P();let u=o?A(o):"";if(!u){const d=x(s);if(!d?.modelProvider){await l(e,"Usage: /models <provider>");return}u=d.modelProvider}const i=r.hasAuth(u);await l(e,Ge(c,u,i));return}case"provider":{if(!o){await l(e,"Usage: /provider <name>");return}const r=A(o),{modelRegistry:c}=await P();if(!c.getAll().some(d=>d.provider===r)){await l(e,`Unknown provider: ${r}`);return}const i=await Y(s,e);i.modelProvider=r,i.modelId=void 0,i.updatedAt=C(),await w(s),await l(e,`Session ${i.id} provider set to ${r}.`);return}case"model":{const r=Je(o);if(!r){await l(e,"Usage: /model <provider>/<model>");return}const c=await Y(s,e),u=A(r.provider??c.modelProvider??"");if(!u){await l(e,"Usage: /model <provider>/<model>");return}const{modelRegistry:i}=await P(),d=We(u,r.modelId);if(!i.find(u,d)){await l(e,`Model not found: ${u}/${d}`);return}c.modelProvider=u,c.modelId=d,c.updatedAt=C(),await w(s),await l(e,`Session ${c.id} model set to ${u}/${d}.`);return}case"status":{const r=x(s);if(!r){await l(e,"No active session.");return}const c=m.modelRef.includes("/")?m.modelRef:`${m.modelProvider}/${m.modelRef}`,u=r.modelProvider||r.modelId?`Session model: ${r.modelProvider??"?"}/${r.modelId??"?"}`:`Default model: ${c}`,i=[`Session: ${r.id} (${r.title})`,u].join(`
13
+ `);await l(e,i);return}case"mcp":{if(o.trim().toLowerCase()==="refresh"){pe(),await q(m.agentDir,{timeoutMs:5e3,maxBytes:m.fetchMaxBytes,maxChars:3e3}),F(),await l(e,"MCP catalog refreshed.");return}const r=await ct();await l(e,r);return}case"login":{const r=re();if(!o){const i=r.map(d=>`- ${d.id} (${d.name})`);await l(e,["OAuth providers:",...i].join(`
14
+ `));return}const c=A(o);if(z.has(t)){await l(e,"Login already in progress.");return}const u=r.find(i=>i.id===c);if(!u){await l(e,`Unknown OAuth provider: ${c}`);return}z.add(t);try{const{authStorage:i}=await P();await l(e,`Starting login for ${u.id}...`),await i.login(u.id,{onAuth:({url:d,instructions:f})=>{const h=["Open this URL in your browser:",d];f&&h.push("",f),l(e,h.join(`
15
+ `))},onPrompt:d=>G(t,e,d),onProgress:d=>{l(e,d)},onManualCodeInput:()=>G(t,e,{message:"Paste the authorization code:"})}),await l(e,`Login completed for ${u.id}.`)}catch(i){const d=j(i);await l(e,`Login failed: ${d}`)}finally{z.delete(t),K(t)}return}case"logout":{if(!o){await l(e,"Usage: /logout <provider>");return}const r=A(o),{authStorage:c}=await P();c.logout(r),await l(e,`Logged out from ${r}.`);return}default:await l(e,`Unknown command: ${n}`)}}async function ut(e,t,n,o=[]){const s=J(n);if(s){await lt(e,t,s.command,s.args);return}const a=await H(t),r=X(a);r.length>0&&(await w(a),await Promise.all(r.map(p=>k(t,p).catch(_=>{console.warn(`[tg-agent] cleanup session file failed id=${p}`,_)}))));let c=x(a);if(!c)try{c=L(a,""),await w(a),await l(e,`Created session ${c.id}.`)}catch(p){await l(e,p.message);return}const{resolved:u,images:i}=await Oe(o);if(o.length>0&&u.length===0&&!n.trim()){await l(e,"Failed to download attachments.");return}const d=Le(u),f=[];n.trim()&&f.push(n.trim()),d&&f.push(d);const h=f.join(`
16
+
17
+ `)||"Attachment received.",M={role:"user",content:h,ts:C()};W(c,M,m.maxHistoryMessages),await w(a),console.log(`[tg-agent] request user=${t} session=${c.id} messages=${c.messages.length} textLen=${h.length} attachments=${u.length} provider=${c.modelProvider??"-"} model=${c.modelId??"-"}`);let g=null;try{g=await rt(e)}catch(p){console.warn("[tg-agent] status message failed",p)}const T={sessionId:c.id,chatId:e,status:g,abortRequested:!1,cancelRequested:!1};Pe(t,T);try{const p=await Ae(async()=>{const R=await le({userId:t,sessionId:c.id,prompt:h,images:i,systemPrompt:m.systemPrompt,modelProvider:c.modelProvider,modelId:c.modelId,telegram:{chatId:e,sendPhoto:async($,v)=>{await y.sendPhoto(e,$,v?{caption:v}:void 0)},sendDocument:async($,v)=>{await y.sendDocument(e,$,v?{caption:v}:void 0)}},onAbortReady:$=>{T.abort=$,T.abortRequested&&$()},onStatus:g?$=>{const v=st($);v&&g?.update(v)}:void 0});return console.log(`[tg-agent] response session=${c.id} model=${R.modelProvider}/${R.modelId} file=${R.sessionFile}`),R.text});if(T.cancelRequested){g?await g.finalize("Cancelled by user."):await l(e,"Cancelled by user.");return}const _={role:"assistant",content:p,ts:C()};W(c,_,m.maxHistoryMessages),await w(a),g?await g.finalize(p):await l(e,p)}catch(p){if(console.error("[tg-agent] runModel error",p),T.cancelRequested){g?await g.fail("Cancelled by user."):await l(e,"Cancelled by user.");return}const _=j(p);g?await g.fail(`Error: ${_}`):await l(e,`Error: ${_}`)}finally{Te(t)}}y.on("message",e=>{if(e.chat.type!=="private"){y.sendMessage(e.chat.id,"This bot only supports direct messages.");return}if(!e.from?.id)return;const t=String(e.from.id),n=e.chat.id;if(!Qe(t)){Ze(t,n);return}const o=(e.text??e.caption??"").trim(),s=Ue(e);if(!o&&s.length===0)return;const a=S.get(t);if(a){if(o==="/stop"||o==="/cancel"){He(t,"Login cancelled by user."),l(n,"Login cancelled.");return}if(!o){l(n,"Login expects a text response.");return}K(t),a.resolve(o);return}if(J(o)?.command==="stop"){Xe(n,t);return}_e(t,()=>ut(n,t,o,s)).catch(async c=>{console.error("[tg-agent] runModel error",c);const u=j(c);await l(n,`Error: ${u}`)})}),y.on("polling_error",e=>{console.error("Polling error",e)}),console.log("tg-agent started");
package/dist/mcp.js CHANGED
@@ -1,3 +1,5 @@
1
- import E from"node:fs";import W from"node:fs/promises";import C from"node:path";import N from"node:readline";import{spawn as D}from"node:child_process";const P=6e4,L=2e5;function M(t){return C.join(t,"config.toml")}function U(t){const n=M(t);try{const o=E.readFileSync(n,"utf8");return x(o)}catch{return[]}}async function Q(t){const n=M(t);try{const o=await W.readFile(n,"utf8");return x(o)}catch{return[]}}function B(t){if(t.type==="http")return t.url?`url=${t.url}`:"url=(missing)";const n=t.args&&t.args.length>0?` ${t.args.join(" ")}`:"";return t.command?`command=${t.command}${n}`:"command=(missing)"}async function G(t,n=5e3){const o=Date.now();try{return await O(t,"tools/list",{},{timeoutMs:n}),{ok:!0,durationMs:Date.now()-o}}catch(e){const i=e instanceof Error?e.message:String(e);return{ok:!1,durationMs:Date.now()-o,error:i}}}async function O(t,n,o,e={}){const i=S(e.timeoutMs??P,1e3,12e4),a=S(e.maxBytes??L,1024,5e6),r=new AbortController,c=()=>r.abort();e.signal?.addEventListener("abort",c,{once:!0});const u=setTimeout(()=>r.abort(),i);try{return t.type==="http"?await _(t,n,o,a,r.signal):await J(t,n,o,a,r.signal)}finally{clearTimeout(u),e.signal?.removeEventListener("abort",c)}}function S(t,n,o){return Number.isNaN(t)?n:Math.min(o,Math.max(n,t))}async function _(t,n,o,e,i){if(!t.url)throw new Error("MCP server url is missing.");const a={jsonrpc:"2.0",id:`tg-agent-${Date.now()}`,method:n,params:o??{}},r={"content-type":"application/json"};t.auth&&(r.authorization=t.auth.startsWith("Bearer ")?t.auth:`Bearer ${t.auth}`);const c=Date.now(),u=await fetch(t.url,{method:"POST",headers:r,body:JSON.stringify(a),signal:i}),s=await $(u,e,i),d=u.headers.get("content-type");let l=s.text;try{const f=JSON.parse(s.text);l=JSON.stringify(f,null,2)}catch{}return{ok:u.ok,output:l,durationMs:Date.now()-c,bytes:s.bytes,truncated:s.truncated,statusCode:u.status,statusText:u.statusText,contentType:d}}async function J(t,n,o,e,i){if(!t.command)throw new Error("MCP server command is missing.");const a={jsonrpc:"2.0",id:`tg-agent-${Date.now()}`,method:n,params:o??{}},r=Date.now(),c=D(t.command,t.args??[],{stdio:["pipe","pipe","pipe"]}),u=()=>{c.killed||c.kill("SIGTERM")};if(i.aborted)throw u(),new Error("MCP request aborted.");i.addEventListener("abort",u,{once:!0});let s="",d="",l=!1,f;const h=N.createInterface({input:c.stdout,terminal:!1});h.on("line",m=>{if(s.length+m.length+1>e){l=!0;return}if(s+=m+`
2
- `,f===void 0)try{f=JSON.parse(m)}catch{}}),c.stderr?.on("data",m=>{const g=m.toString();if(d.length+g.length>e){l=!0;return}d+=g});const p=`${JSON.stringify(a)}
3
- `;c.stdin?.write(p),c.stdin?.end();const T=await new Promise(m=>{c.on("exit",(g,k)=>m({code:g,signal:k}))});if(h.close(),i.removeEventListener("abort",u),i.aborted)throw new Error("MCP request aborted.");if(!f&&s.trim())try{f=JSON.parse(s.trim())}catch{}if(T.code!==0&&!f){const m=d.trim()||"stdio process failed";throw new Error(m)}if(!f){const m=d.trim();throw m?new Error(m):new Error("MCP stdio returned no JSON.")}const b=JSON.stringify(f,null,2);return{ok:!0,output:b,durationMs:Date.now()-r,bytes:Math.min(b.length,e),truncated:l}}async function $(t,n,o){const e=t.body?.getReader?.();if(!e){const l=await t.text(),f=Math.min(l.length,n);return{text:l.slice(0,n),bytes:f,truncated:l.length>n}}const i=new TextDecoder("utf-8"),a=[];let r=0,c=!1;for(;;){if(o.aborted){try{await e.cancel()}catch{}throw new Error("MCP request aborted.")}const{done:l,value:f}=await e.read();if(l)break;if(!f)continue;const h=r+f.byteLength;if(h>n){const p=Math.max(0,n-r);p>0&&(a.push(f.slice(0,p)),r+=p),c=!0;try{await e.cancel()}catch{}break}a.push(f),r=h}const u=new Uint8Array(r);let s=0;for(const l of a)u.set(l,s),s+=l.byteLength;return{text:i.decode(u),bytes:r,truncated:c}}function x(t){const n=[];let o=null,e={};const i=()=>{if(!o)return;const a=e.type??(e.url?"http":e.command?"stdio":void 0);if(!a)return;const r=a==="http"||a==="stdio"?a:void 0;r&&(r==="http"&&!e.url||r==="stdio"&&!e.command||n.push({name:o,type:r,url:e.url,command:e.command,args:e.args,auth:e.auth}))};for(const a of t.split(/\r?\n/)){const r=q(a).trim();if(!r)continue;if(r.startsWith("[")&&r.endsWith("]")){i();const d=r.slice(1,-1).trim();if(!d.startsWith("mcp_servers.")){o=null,e={};continue}let l=d.slice(12).trim();if(l=w(l),!l){o=null,e={};continue}o=l,e={};continue}if(!o)continue;const c=r.indexOf("=");if(c===-1)continue;const u=r.slice(0,c).trim(),s=r.slice(c+1).trim();if(u)switch(u){case"type":e.type=y(s).toLowerCase();break;case"url":e.url=y(s);break;case"command":e.command=y(s);break;case"args":e.args=A(s);break;case"auth":case"authorization":case"token":e.auth=y(s);break;default:break}}return i(),n}function w(t){return t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1):t}function y(t){const n=t.trim();return n?n.startsWith('"')&&n.endsWith('"')?n.slice(1,-1).replace(/\\\\/g,"\\").replace(/\\"/g,'"'):n.startsWith("'")&&n.endsWith("'")?n.slice(1,-1):n:""}function A(t){const n=t.trim();if(!n.startsWith("[")||!n.endsWith("]"))return[];const o=n.slice(1,-1).trim();if(!o)return[];const e=[];let i="",a=!1,r="";for(let u=0;u<o.length;u+=1){const s=o[u];if(!a&&(s==='"'||s==="'")){a=!0,r=s;continue}if(a&&s===r){a=!1;continue}if(!a&&s===","){const d=i.trim();d&&e.push(w(d)),i="";continue}i+=s}const c=i.trim();return c&&e.push(w(c)),e}function q(t){let n=!1,o="";for(let e=0;e<t.length;e+=1){const i=t[e];if(!n&&(i==='"'||i==="'")){n=!0,o=i;continue}if(n&&i===o){n=!1;continue}if(!n&&(i==="#"||i===";"))return t.slice(0,e)}return t}export{O as callMcpServer,B as formatMcpTarget,M as getMcpConfigPath,Q as loadMcpServers,U as loadMcpServersSync,G as probeMcpServer};
1
+ import R from"node:fs";import H from"node:fs/promises";import q from"node:path";import J from"node:readline";import{spawn as U}from"node:child_process";const G=6e4,A=2e5,Q=40,X=3500;let w=null,P=!1;const h=new Map,V=1500;function z(t){return q.join(t,"config.toml")}function dt(t){const e=z(t);try{const r=R.readFileSync(e,"utf8");return O(r)}catch{return[]}}async function Y(t){const e=z(t);try{const r=await H.readFile(e,"utf8");return O(r)}catch{return[]}}async function pt(t,e={}){if(w)return w;const r=await Y(t);if(r.length===0)return w="","";const n=e.maxTools??Q,s=e.maxChars??X,i=e.timeoutMs??4e3,o=e.maxBytes??A,a=["MCP catalog (auto-discovered):","Use tool 'mcp' with server=<name> method=<tool> params=<object>."];for(const u of r){const l=await K(u,{timeoutMs:i,maxBytes:o});if(a.push(`${u.name} (${u.type})`),l.length===0){a.push(" - tools unavailable");continue}const d=l.slice(0,n);for(const p of d){const y=p.description?`: ${p.description}`:"";a.push(` - ${p.name}${y}`)}l.length>d.length&&a.push(` - ...and ${l.length-d.length} more`)}let c=a.join(`
2
+ `);return c.length>s&&(c=`${c.slice(0,s-20)}...
3
+ (truncated)`),w=c,c}function mt(){w&&(P=!0)}function ht(){w=null,P=!1,h.clear()}function gt(t){if(t.type==="http")return t.url?`url=${t.url}`:"url=(missing)";const e=t.args&&t.args.length>0?` ${t.args.join(" ")}`:"";return t.command?`command=${t.command}${e}`:"command=(missing)"}async function yt(t,e=5e3){const r=Date.now();try{return await $(t,"tools/list",{},{timeoutMs:e}),{ok:!0,durationMs:Date.now()-r}}catch(n){const s=n instanceof Error?n.message:String(n);return{ok:!1,durationMs:Date.now()-r,error:s}}}async function $(t,e,r,n={}){const s=j(n.timeoutMs??G,1e3,12e4),i=j(n.maxBytes??A,1024,5e6),o=new AbortController,a=()=>o.abort();n.signal?.addEventListener("abort",a,{once:!0});const c=setTimeout(()=>o.abort(),s);try{return t.type==="http"?(e!=="initialize"&&e!=="notifications/initialized"&&await v(t,i,o.signal),await B(t,e,r,i,o.signal)):await nt(t,e,r,i,o.signal)}finally{clearTimeout(c),n.signal?.removeEventListener("abort",a)}}function j(t,e,r){return Number.isNaN(t)?e:Math.min(r,Math.max(e,t))}async function K(t,e){try{const r=await $(t,"tools/list",{},e);return Z(r.output)}catch(r){const n=r instanceof Error?r.message:String(r);return console.warn(`[tg-agent] mcp tools/list failed server=${t.name} error=${n}`),[]}}function Z(t){let e;try{e=JSON.parse(t)}catch{return[]}if(!e||typeof e!="object")return[];const r=e,s=(r.result??r).tools??r.tools;if(!Array.isArray(s))return[];const i=[];for(const o of s){if(!o||typeof o!="object")continue;const a=o,c=typeof a.name=="string"?a.name:"";if(!c)continue;const u=typeof a.description=="string"?a.description:void 0,l=a.inputSchema;i.push({name:c,description:u,inputSchema:l})}return i}async function B(t,e,r,n,s){if(!t.url)throw new Error("MCP server url is missing.");const i=Date.now(),o=await k(t,e,r,n,s),a=I(o.parsed);if(a)throw new Error(a);let c=o.bodyText;return o.parsed&&(c=JSON.stringify(o.parsed,null,2)),{ok:o.statusCode>=200&&o.statusCode<300,output:c,durationMs:Date.now()-i,bytes:o.bytes,truncated:o.truncated,statusCode:o.statusCode,statusText:o.statusText,contentType:o.contentType}}async function k(t,e,r,n,s){if(!t.url)throw new Error("MCP server url is missing.");const i=h.get(t.name),o={jsonrpc:"2.0",id:`tg-agent-${Date.now()}`,method:e,params:r??{}},a={"content-type":"application/json"};t.auth&&(a.authorization=t.auth.startsWith("Bearer ")?t.auth:`Bearer ${t.auth}`),i?.cookie&&(a.cookie=i.cookie),i?.sessionId&&(a["mcp-session-id"]=i.sessionId);const c=await fetch(t.url,{method:"POST",headers:a,body:JSON.stringify(o),signal:s});tt(t.name,c);const u=await ot(c,n,s);let l;try{l=JSON.parse(u.text)}catch{l=void 0}return et(t.name,l),{statusCode:c.status,statusText:c.statusText,contentType:c.headers.get("content-type"),bodyText:u.text,parsed:l,bytes:u.bytes,truncated:u.truncated}}function I(t){if(!t||typeof t!="object")return null;const e=t;if(e.error&&typeof e.error=="object"){const r=e.error;if(typeof r.message=="string")return r.message}return typeof e.error_description=="string"?e.error_description:typeof e.error=="string"?e.error:null}async function v(t,e,r){const n=h.get(t.name);if(n?.initializedAt)return;if(n?.inFlight){await n.inFlight;return}const s=(async()=>{const i=await k(t,"initialize",{protocolVersion:"2024-11-05",clientInfo:{name:"tg-agent",version:process.env.npm_package_version??"dev"},capabilities:{}},e,r),o=I(i.parsed);if(o){const u=o.toLowerCase();if(u.includes("method")&&u.includes("not"))return;throw new Error(o)}const a=new AbortController,c=setTimeout(()=>a.abort(),V);try{await k(t,"notifications/initialized",{},e,a.signal)}catch{}finally{clearTimeout(c)}})();h.set(t.name,{initializedAt:Date.now(),inFlight:s});try{await s;const i=h.get(t.name);h.set(t.name,{initializedAt:Date.now(),cookie:i?.cookie,sessionId:i?.sessionId})}catch(i){throw h.delete(t.name),i}}function tt(t,e){const n=h.get(t)??{initializedAt:0},s=e.headers.get("set-cookie"),i=e.headers.getSetCookie?.(),o=i&&i.length>0?i.join("; "):s;o&&h.set(t,{...n,cookie:o});const a=e.headers.get("mcp-session-id")??e.headers.get("x-mcp-session-id");a&&h.set(t,{...n,sessionId:a})}function et(t,e){if(!e||typeof e!="object")return;const n=e.result;if(!n)return;const s=typeof n.sessionId=="string"&&n.sessionId||typeof n.session_id=="string"&&n.session_id;if(!s)return;const i=h.get(t)??{initializedAt:0};h.set(t,{...i,sessionId:s})}async function nt(t,e,r,n,s){if(!t.command)throw new Error("MCP server command is missing.");const i=Date.now(),o=U(t.command,t.args??[],{stdio:["pipe","pipe","pipe"]}),a=()=>{o.killed||o.kill("SIGTERM")};if(s.aborted)throw a(),new Error("MCP request aborted.");const c=new Map;let u=!1,l="";const d=new Error("MCP request aborted."),p=J.createInterface({input:o.stdout,terminal:!1});p.on("line",f=>{if(f.length>n){u=!0;return}let m=null;try{m=JSON.parse(f)}catch{return}const g=typeof m.id=="string"?m.id:typeof m.id=="number"?String(m.id):"";if(!g)return;const x=c.get(g);x&&(c.delete(g),x.resolve(m))}),o.stderr?.on("data",f=>{const m=f.toString();if(l.length+m.length>n){u=!0;return}l+=m});const y=()=>{for(const f of c.values())f.reject(d);c.clear(),a()};s.addEventListener("abort",y,{once:!0});const b=f=>{const m=f===0?"MCP stdio exited.":l.trim()||"MCP stdio exited with error.";for(const g of c.values())g.reject(new Error(m));c.clear()};o.once("exit",f=>b(f));const _=async(f,m)=>{const g=`tg-agent-${Date.now()}-${Math.random().toString(16).slice(2,8)}`,x={jsonrpc:"2.0",id:g,method:f,params:m??{}};return await new Promise((W,N)=>{c.set(g,{resolve:W,reject:N}),o.stdin?.write(`${JSON.stringify(x)}
4
+ `)})},S=await _("initialize",{protocolVersion:"2024-11-05",clientInfo:{name:"tg-agent",version:process.env.npm_package_version??"dev"},capabilities:{}});if(S.error&&typeof S.error=="object"){const f=S.error.message??"MCP initialize failed.";throw a(),new Error(String(f))}const L={jsonrpc:"2.0",method:"notifications/initialized"};o.stdin?.write(`${JSON.stringify(L)}
5
+ `);const T=await _(e,r);s.removeEventListener("abort",y),p.close(),o.stdin?.end(),a();const D=new Promise(f=>{o.once("exit",()=>f())});if(await Promise.race([D,new Promise(f=>setTimeout(f,500))]),T.error&&typeof T.error=="object"){const f=T.error.message??"MCP request failed.";throw new Error(String(f))}const M=JSON.stringify(T,null,2),F=M.length>n?M.slice(0,n):M;return M.length>n&&(u=!0),{ok:!0,output:F,durationMs:Date.now()-i,bytes:Math.min(M.length,n),truncated:u}}async function ot(t,e,r){const n=t.body?.getReader?.();if(!n){const d=await t.text(),p=Math.min(d.length,e);return{text:d.slice(0,e),bytes:p,truncated:d.length>e}}const s=new TextDecoder("utf-8"),i=[];let o=0,a=!1;for(;;){if(r.aborted){try{await n.cancel()}catch{}throw new Error("MCP request aborted.")}const{done:d,value:p}=await n.read();if(d)break;if(!p)continue;const y=o+p.byteLength;if(y>e){const b=Math.max(0,e-o);b>0&&(i.push(p.slice(0,b)),o+=b),a=!0;try{await n.cancel()}catch{}break}i.push(p),o=y}const c=new Uint8Array(o);let u=0;for(const d of i)c.set(d,u),u+=d.byteLength;return{text:s.decode(c),bytes:o,truncated:a}}function O(t){const e=[];let r=null,n={};const s=()=>{if(!r)return;const i=n.type??(n.url?"http":n.command?"stdio":void 0);if(!i)return;const o=i==="http"||i==="stdio"?i:void 0;o&&(o==="http"&&!n.url||o==="stdio"&&!n.command||e.push({name:r,type:o,url:n.url,command:n.command,args:n.args,auth:n.auth}))};for(const i of t.split(/\r?\n/)){const o=it(i).trim();if(!o)continue;if(o.startsWith("[")&&o.endsWith("]")){s();const l=o.slice(1,-1).trim();if(!l.startsWith("mcp_servers.")){r=null,n={};continue}let d=l.slice(12).trim();if(d=E(d),!d){r=null,n={};continue}r=d,n={};continue}if(!r)continue;const a=o.indexOf("=");if(a===-1)continue;const c=o.slice(0,a).trim(),u=o.slice(a+1).trim();if(c)switch(c){case"type":n.type=C(u).toLowerCase();break;case"url":n.url=C(u);break;case"command":n.command=C(u);break;case"args":n.args=rt(u);break;case"auth":case"authorization":case"token":n.auth=C(u);break;default:break}}return s(),e}function E(t){return t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1):t}function C(t){const e=t.trim();return e?e.startsWith('"')&&e.endsWith('"')?e.slice(1,-1).replace(/\\\\/g,"\\").replace(/\\"/g,'"'):e.startsWith("'")&&e.endsWith("'")?e.slice(1,-1):e:""}function rt(t){const e=t.trim();if(!e.startsWith("[")||!e.endsWith("]"))return[];const r=e.slice(1,-1).trim();if(!r)return[];const n=[];let s="",i=!1,o="";for(let c=0;c<r.length;c+=1){const u=r[c];if(!i&&(u==='"'||u==="'")){i=!0,o=u;continue}if(i&&u===o){i=!1;continue}if(!i&&u===","){const l=s.trim();l&&n.push(E(l)),s="";continue}s+=u}const a=s.trim();return a&&n.push(E(a)),n}function it(t){let e=!1,r="";for(let n=0;n<t.length;n+=1){const s=t[n];if(!e&&(s==='"'||s==="'")){e=!0,r=s;continue}if(e&&s===r){e=!1;continue}if(!e&&(s==="#"||s===";"))return t.slice(0,n)}return t}export{$ as callMcpServer,gt as formatMcpTarget,mt as freezeMcpCatalog,pt as getMcpCatalog,z as getMcpConfigPath,Y as loadMcpServers,dt as loadMcpServersSync,yt as probeMcpServer,ht as refreshMcpCatalog};
@@ -1,7 +1,7 @@
1
- import{createAgentSession as O,discoverAuthStorage as P,discoverModels as j,SessionManager as B,SettingsManager as N}from"@mariozechner/pi-coding-agent";import{config as d}from"./config.js";import{readCodexOAuth as L}from"./codexAuth.js";import{createCustomTools as F}from"./customTools.js";import{ensureDir as k}from"./utils.js";import{sessionFilePath as z}from"./sessionStore.js";const G={"codex-mini-latest":"gpt-5.1-codex-mini","codex-max-latest":"gpt-5.1-codex-max","codex-latest":"gpt-5.2-codex"};function T(e){const t=e.trim().toLowerCase();return t==="codex"?"openai-codex":t}function q(e,t){const n=e.trim();if(!n)return{provider:t,modelId:""};if(n.includes("/")){const[i,r]=n.split("/",2);return{provider:i.trim()||t,modelId:r.trim()}}return{provider:t,modelId:n}}function K(e,t){return e!=="openai-codex"?t:G[t]??t}function V(e,t){const n=e.getAvailable().filter(r=>r.provider===t);return n.length>0?n[0]:e.getAll().filter(r=>r.provider===t)[0]}function X(e,t){const n=!!t?.provider?.trim(),i=!!t?.modelId?.trim(),r=T(t?.provider||d.modelProvider||"openai-codex"),s=n||i?"":d.modelRef||"",{provider:w,modelId:f}=q(s,r),c=t?.provider?r:T(w);let g=i?t?.modelId?.trim()||"":f;if(!g){const $=V(e,c);return $?{model:$,provider:c,modelId:$.id}:{model:void 0,provider:c,modelId:""}}const a=K(c,g);return{model:e.find(c,a),provider:c,modelId:a}}function H(e){const t=L();return t?(e.setRuntimeApiKey("openai-codex",t.accessToken),{source:t.source,expiresAt:t.expiresAt}):null}function J(e){if(e?.role!=="assistant")return"";const n=e.content;if(typeof n=="string")return n.trim();if(!Array.isArray(n))return"";const i=n.filter(s=>typeof s=="object"&&s&&s.type==="text").map(s=>s.text??"").map(s=>s.trim()).filter(Boolean);if(i.length>0)return i.join(`
1
+ import{createAgentSession as P,discoverAuthStorage as j,discoverModels as B,SessionManager as N,SettingsManager as L}from"@mariozechner/pi-coding-agent";import{config as d}from"./config.js";import{readCodexOAuth as F}from"./codexAuth.js";import{createCustomTools as z}from"./customTools.js";import{getMcpCatalog as G}from"./mcp.js";import{ensureDir as k}from"./utils.js";import{sessionFilePath as q}from"./sessionStore.js";const K={"codex-mini-latest":"gpt-5.1-codex-mini","codex-max-latest":"gpt-5.1-codex-max","codex-latest":"gpt-5.2-codex"};function C(e){const t=e.trim().toLowerCase();return t==="codex"?"openai-codex":t}function V(e,t){const n=e.trim();if(!n)return{provider:t,modelId:""};if(n.includes("/")){const[i,r]=n.split("/",2);return{provider:i.trim()||t,modelId:r.trim()}}return{provider:t,modelId:n}}function X(e,t){return e!=="openai-codex"?t:K[t]??t}function H(e,t){const n=e.getAvailable().filter(r=>r.provider===t);return n.length>0?n[0]:e.getAll().filter(r=>r.provider===t)[0]}function J(e,t){const n=!!t?.provider?.trim(),i=!!t?.modelId?.trim(),r=C(t?.provider||d.modelProvider||"openai-codex"),s=n||i?"":d.modelRef||"",{provider:w,modelId:p}=V(s,r),u=t?.provider?r:C(w);let m=i?t?.modelId?.trim()||"":p;if(!m){const $=H(e,u);return $?{model:$,provider:u,modelId:$.id}:{model:void 0,provider:u,modelId:""}}const I=X(u,m);return{model:e.find(u,I),provider:u,modelId:I}}function Q(e){const t=F();return t?(e.setRuntimeApiKey("openai-codex",t.accessToken),{source:t.source,expiresAt:t.expiresAt}):null}function U(e){if(e?.role!=="assistant")return"";const n=e.content;if(typeof n=="string")return n.trim();if(!Array.isArray(n))return"";const i=n.filter(s=>typeof s=="object"&&s&&s.type==="text").map(s=>s.text??"").map(s=>s.trim()).filter(Boolean);if(i.length>0)return i.join(`
2
2
 
3
3
  `);const r=n.filter(s=>typeof s=="object"&&s&&s.type==="thinking").map(s=>s.thinking??"").map(s=>s.trim()).filter(Boolean);return r.length>0?r.join(`
4
4
 
5
- `):""}function Q(e){if(e.length===0)return"messages=0";const t=e.slice(-6).map(n=>{const i=n?.role??"unknown",r=n.content,s=n?.stopReason,w=n?.errorMessage;if(typeof r=="string")return`${i}(text:${r.length}${s?`,stop=${s}`:""})`;if(Array.isArray(r)){const c=[`blocks:${r.map(g=>typeof g=="object"&&g&&"type"in g?String(g.type||"unknown"):typeof g).join(",")||"none"}`,s?`stop=${s}`:null,w?"error=1":null].filter(Boolean).join(",");return`${i}(${c})`}if(r==null){const f=["empty",s?`stop=${s}`:null,w?"error=1":null].filter(Boolean).join(",");return`${i}(${f})`}return`${i}(${typeof r}${s?`,stop=${s}`:""})`});return`messages=${e.length} last=[${t.join(" ")}]`}function U(e,t){const n=t?.getLastAssistantText?.();if(n?.trim())return n.trim();for(let i=e.length-1;i>=0;i-=1){const r=J(e[i]);if(r)return r}return""}function W(e){for(let t=e.length-1;t>=0;t-=1)if(e[t]?.role==="assistant")return e[t]}async function se(e){await k(d.sessionDir),await k(d.agentDir),await k(d.workspaceDir);const t=z(e.userId,e.sessionId),n=P(d.agentDir),i=H(n);i&&console.log(`[tg-agent] codex oauth source=${i.source} expiresAt=${new Date(i.expiresAt).toISOString()}`);const r=j(n,d.agentDir),{model:s,provider:w,modelId:f}=X(r,{provider:e.modelProvider,modelId:e.modelId});f&&!s&&console.warn(`[tg-agent] model not found: ${w}/${f}`);const c=B.open(t),g=N.create(d.workspaceDir,d.agentDir),{session:a,modelFallbackMessage:y}=await O({cwd:d.workspaceDir,agentDir:d.agentDir,authStorage:n,modelRegistry:r,model:s??void 0,sessionManager:c,settingsManager:g,customTools:F(),systemPrompt:o=>{const l=e.systemPrompt?.trim();return l?`${o}
5
+ `):""}function W(e){if(e.length===0)return"messages=0";const t=e.slice(-6).map(n=>{const i=n?.role??"unknown",r=n.content,s=n?.stopReason,w=n?.errorMessage;if(typeof r=="string")return`${i}(text:${r.length}${s?`,stop=${s}`:""})`;if(Array.isArray(r)){const u=[`blocks:${r.map(m=>typeof m=="object"&&m&&"type"in m?String(m.type||"unknown"):typeof m).join(",")||"none"}`,s?`stop=${s}`:null,w?"error=1":null].filter(Boolean).join(",");return`${i}(${u})`}if(r==null){const p=["empty",s?`stop=${s}`:null,w?"error=1":null].filter(Boolean).join(",");return`${i}(${p})`}return`${i}(${typeof r}${s?`,stop=${s}`:""})`});return`messages=${e.length} last=[${t.join(" ")}]`}function Y(e,t){const n=t?.getLastAssistantText?.();if(n?.trim())return n.trim();for(let i=e.length-1;i>=0;i-=1){const r=U(e[i]);if(r)return r}return""}function Z(e){for(let t=e.length-1;t>=0;t-=1)if(e[t]?.role==="assistant")return e[t]}async function ae(e){await k(d.sessionDir),await k(d.agentDir),await k(d.workspaceDir);const t=q(e.userId,e.sessionId),n=j(d.agentDir),i=Q(n);i&&console.log(`[tg-agent] codex oauth source=${i.source} expiresAt=${new Date(i.expiresAt).toISOString()}`);const r=B(n,d.agentDir),{model:s,provider:w,modelId:p}=J(r,{provider:e.modelProvider,modelId:e.modelId});p&&!s&&console.warn(`[tg-agent] model not found: ${w}/${p}`);const u=N.open(t),m=L.create(d.workspaceDir,d.agentDir);let I="";try{I=await G(d.agentDir,{timeoutMs:4e3,maxBytes:d.fetchMaxBytes,maxChars:3e3})}catch(o){const a=o instanceof Error?o.message:String(o);console.warn(`[tg-agent] mcp catalog error: ${a}`)}const{session:l,modelFallbackMessage:$}=await P({cwd:d.workspaceDir,agentDir:d.agentDir,authStorage:n,modelRegistry:r,model:s??void 0,sessionManager:u,settingsManager:m,customTools:z({telegram:e.telegram}),systemPrompt:o=>{const a=e.systemPrompt?.trim(),c=[o];return a&&c.push(a),I&&c.push(I),c.join(`
6
6
 
7
- ${l}`:o}});y&&console.warn(`[tg-agent] modelFallback=${y}`);let $=!1;const R=()=>{$=!0,a.abort()};e.onAbortReady?.(R);const I=process.env.LOG_AGENT_EVENTS!=="0",C=process.env.LOG_AGENT_STREAM==="1",S=new Map,v=a.subscribe(o=>{switch(o.type){case"agent_start":I&&console.log(`[tg-agent] agent start session=${e.sessionId}`),m=Date.now(),e.onStatus?.({type:"agent_start"});break;case"agent_end":I&&console.log(`[tg-agent] agent end session=${e.sessionId}`),e.onStatus?.({type:"agent_end"});break;case"turn_start":I&&console.log(`[tg-agent] turn start session=${e.sessionId}`),m=Date.now(),e.onStatus?.({type:"turn_start"});break;case"turn_end":I&&console.log(`[tg-agent] turn end session=${e.sessionId} toolResults=${o.toolResults.length}`),m=Date.now(),e.onStatus?.({type:"turn_end",toolResults:o.toolResults.length});break;case"message_start":{if(I){const l=o.message.role??"unknown";console.log(`[tg-agent] message start session=${e.sessionId} role=${l}`)}m=Date.now();{const l=o.message.role??"unknown";e.onStatus?.({type:"message_start",role:l})}break}case"message_end":{if(I){const l=o.message.role??"unknown";console.log(`[tg-agent] message end session=${e.sessionId} role=${l}`)}m=Date.now();{const l=o.message.role??"unknown";e.onStatus?.({type:"message_end",role:l})}break}case"message_update":{if(!C)break;if(o.assistantMessageEvent.type==="text_delta"){const p=(o.assistantMessageEvent.delta??"").length;p>0&&console.log(`[tg-agent] stream delta session=${e.sessionId} chars=${p}`),m=Date.now()}o.assistantMessageEvent.type==="thinking_delta"&&(m=Date.now());break}case"tool_execution_start":{S.set(o.toolCallId,Date.now()),console.log(`[tg-agent] tool start name=${o.toolName} id=${o.toolCallId}`),A+=1,m=Date.now(),e.onStatus?.({type:"tool_start",name:o.toolName,id:o.toolCallId,args:o.args});break}case"tool_execution_end":{const l=S.get(o.toolCallId),p=l?Date.now()-l:0;console.log(`[tg-agent] tool end name=${o.toolName} id=${o.toolCallId} ok=${!o.isError} durationMs=${p}`),S.delete(o.toolCallId),A=Math.max(0,A-1),m=Date.now(),e.onStatus?.({type:"tool_end",name:o.toolName,id:o.toolCallId,ok:!o.isError,durationMs:p});break}default:break}});let x=null,_=null,h=!1,b=0,E=!1,m=Date.now(),A=0;try{const o=Date.now();x=setInterval(()=>{const u=Date.now()-o;console.log(`[tg-agent] prompt running session=${e.sessionId} elapsedMs=${u} streaming=${a.isStreaming}`),e.onStatus?.({type:"heartbeat",elapsedMs:u,streaming:a.isStreaming})},15e3),_=setInterval(()=>{if(h||A>0)return;const u=Date.now()-m,M=a.isStreaming?d.modelTimeoutStreamingMs:d.modelTimeoutMs,D=Math.max(1e3,M);u>=D&&(h=!0,b=D,E=a.isStreaming,console.warn(`[tg-agent] model timeout session=${e.sessionId} elapsedMs=${u}`),a.abort())},2e3);try{if(await a.prompt(e.prompt),h)throw new Error(`Model request timed out after ${d.modelTimeoutMs}ms`)}catch(u){if($)throw new Error("Cancelled by user.");if(h){const M=E?" (streaming)":"",D=b||d.modelTimeoutMs;throw new Error(`Model request timed out after ${D}ms${M}`)}throw u}x&&clearInterval(x);const l=W(a.messages);if(l){const u=l?.stopReason,M=l?.errorMessage;if(u==="error")throw console.warn(`[tg-agent] model error session=${e.sessionId} error=${M??"unknown"}`),new Error(M??"Model error without details.")}const p=U(a.messages,a);if(!p){const u=Q(a.messages);throw console.warn(`[tg-agent] empty response session=${e.sessionId} ${u}`),new Error("No assistant response.")}return{text:p,sessionFile:t,sessionId:a.sessionId,modelProvider:a.model?.provider??w,modelId:a.model?.id??f,modelFallbackMessage:y}}finally{x&&clearInterval(x),_&&clearInterval(_),v(),c.flushPendingToolResults?.(),a.dispose()}}export{se as runPiAgentPrompt};
7
+ `)}});$&&console.warn(`[tg-agent] modelFallback=${$}`);let b=!1;const R=()=>{b=!0,l.abort()};e.onAbortReady?.(R);const M=process.env.LOG_AGENT_EVENTS!=="0",v=process.env.LOG_AGENT_STREAM==="1",S=new Map,O=l.subscribe(o=>{switch(o.type){case"agent_start":M&&console.log(`[tg-agent] agent start session=${e.sessionId}`),f=Date.now(),e.onStatus?.({type:"agent_start"});break;case"agent_end":M&&console.log(`[tg-agent] agent end session=${e.sessionId}`),e.onStatus?.({type:"agent_end"});break;case"turn_start":M&&console.log(`[tg-agent] turn start session=${e.sessionId}`),f=Date.now(),e.onStatus?.({type:"turn_start"});break;case"turn_end":M&&console.log(`[tg-agent] turn end session=${e.sessionId} toolResults=${o.toolResults.length}`),f=Date.now(),e.onStatus?.({type:"turn_end",toolResults:o.toolResults.length});break;case"message_start":{if(M){const a=o.message.role??"unknown";console.log(`[tg-agent] message start session=${e.sessionId} role=${a}`)}f=Date.now();{const a=o.message.role??"unknown";e.onStatus?.({type:"message_start",role:a})}break}case"message_end":{if(M){const a=o.message.role??"unknown";console.log(`[tg-agent] message end session=${e.sessionId} role=${a}`)}f=Date.now();{const a=o.message.role??"unknown";e.onStatus?.({type:"message_end",role:a})}break}case"message_update":{if(!v)break;if(o.assistantMessageEvent.type==="text_delta"){const c=(o.assistantMessageEvent.delta??"").length;c>0&&console.log(`[tg-agent] stream delta session=${e.sessionId} chars=${c}`),f=Date.now()}o.assistantMessageEvent.type==="thinking_delta"&&(f=Date.now());break}case"tool_execution_start":{S.set(o.toolCallId,Date.now()),console.log(`[tg-agent] tool start name=${o.toolName} id=${o.toolCallId}`),A+=1,f=Date.now(),e.onStatus?.({type:"tool_start",name:o.toolName,id:o.toolCallId,args:o.args});break}case"tool_execution_end":{const a=S.get(o.toolCallId),c=a?Date.now()-a:0;console.log(`[tg-agent] tool end name=${o.toolName} id=${o.toolCallId} ok=${!o.isError} durationMs=${c}`),S.delete(o.toolCallId),A=Math.max(0,A-1),f=Date.now(),e.onStatus?.({type:"tool_end",name:o.toolName,id:o.toolCallId,ok:!o.isError,durationMs:c});break}default:break}});let h=null,_=null,y=!1,E=0,T=!1,f=Date.now(),A=0;try{const o=Date.now();h=setInterval(()=>{const g=Date.now()-o;console.log(`[tg-agent] prompt running session=${e.sessionId} elapsedMs=${g} streaming=${l.isStreaming}`),e.onStatus?.({type:"heartbeat",elapsedMs:g,streaming:l.isStreaming})},15e3),_=setInterval(()=>{if(y||A>0)return;const g=Date.now()-f,x=l.isStreaming?d.modelTimeoutStreamingMs:d.modelTimeoutMs,D=Math.max(1e3,x);g>=D&&(y=!0,E=D,T=l.isStreaming,console.warn(`[tg-agent] model timeout session=${e.sessionId} elapsedMs=${g}`),l.abort())},2e3);try{if(await l.prompt(e.prompt,e.images&&e.images.length>0?{images:e.images}:void 0),y)throw new Error(`Model request timed out after ${d.modelTimeoutMs}ms`)}catch(g){if(b)throw new Error("Cancelled by user.");if(y){const x=T?" (streaming)":"",D=E||d.modelTimeoutMs;throw new Error(`Model request timed out after ${D}ms${x}`)}throw g}h&&clearInterval(h);const a=Z(l.messages);if(a){const g=a?.stopReason,x=a?.errorMessage;if(g==="error")throw console.warn(`[tg-agent] model error session=${e.sessionId} error=${x??"unknown"}`),new Error(x??"Model error without details.")}const c=Y(l.messages,l);if(!c){const g=W(l.messages);throw console.warn(`[tg-agent] empty response session=${e.sessionId} ${g}`),new Error("No assistant response.")}return{text:c,sessionFile:t,sessionId:l.sessionId,modelProvider:l.model?.provider??w,modelId:l.model?.id??p,modelFallbackMessage:$}}finally{h&&clearInterval(h),_&&clearInterval(_),O(),u.flushPendingToolResults?.(),l.dispose()}}export{ae as runPiAgentPrompt};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tg-agent",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "Telegram Codex agent",
5
5
  "type": "module",
6
6
  "bin": {