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 +7 -0
- package/dist/customTools.js +5 -5
- package/dist/index.js +15 -12
- package/dist/mcp.js +5 -3
- package/dist/piAgentRunner.js +3 -3
- package/package.json +1 -1
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` 已列出所有可配置项。常用说明如下:
|
package/dist/customTools.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{Type as o}from"@sinclair/typebox";import{config as
|
|
2
|
-
${
|
|
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
|
|
6
|
-
${
|
|
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
|
|
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
|
|
2
|
-
`)}function
|
|
3
|
-
`)}function
|
|
4
|
-
`)}
|
|
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
|
|
7
|
-
args: ${
|
|
8
|
-
`)}async function
|
|
9
|
-
`);await
|
|
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
|
|
12
|
-
`);await
|
|
13
|
-
`));return}const
|
|
14
|
-
`))},onPrompt:
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
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};
|
package/dist/piAgentRunner.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{createAgentSession as
|
|
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
|
|
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
|
-
|
|
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};
|