imtoagent 0.3.6 โ†’ 0.3.8

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
@@ -4,12 +4,50 @@ Connect Feishu, Telegram, personal WeChat, and WeCom to AI coding agents like Cl
4
4
 
5
5
  One gateway, multiple IMs, multiple agents, unified port proxy.
6
6
 
7
+ ## ๐Ÿš€ Quick Start (5 Minutes)
8
+
9
+ ### Step 1: Install (One Command)
10
+
11
+ ```bash
12
+ curl -fsSL https://raw.githubusercontent.com/imtoagent/imtoagent/main/install.sh | bash
13
+ ```
14
+
15
+ This script detects your environment, installs bun if needed, installs imtoagent, and guides you through setup.
16
+
17
+ ### Step 2: Start
18
+
19
+ ```bash
20
+ imtoagent setup
21
+ ```
22
+
23
+ The interactive wizard guides you through:
24
+ 1. Select IM platform (Feishu/Telegram/WeChat/WeCom)
25
+ 2. Enter Bot credentials
26
+ 3. Choose Agent backend (Claude Code/Codex/OpenCode)
27
+ 4. Configure model providers (API keys)
28
+ 5. Generate soul files for personality injection
29
+
30
+ ### Step 2: Verify
31
+
32
+ ```bash
33
+ imtoagent status # check it's running
34
+ ```
35
+
36
+ That's it! Send `/help` to your Bot in the IM to see available commands.
37
+
38
+ ---
39
+
40
+ **Alternative install methods:** See [Installation Methods](#installation-methods) below for npm global install or source install.
41
+
42
+ ---
43
+
7
44
  ## Architecture
8
45
 
9
46
  ```
10
- ้ฃžไนฆ/Telegram/ๅพฎไฟก/ไผๅพฎ โ†’ IM Registry ๅทฅๅŽ‚ โ†’ Bot ๅฎžไพ‹
11
- โ†’ AgentRuntime SDK โ†’ Agent Adapter
12
- โ†’ ็ปŸไธ€ Proxy (:18899) โ†’ ไธŠๆธธๆจกๅž‹
47
+ IM Platform (Feishu/Telegram/WeChat/WeCom)
48
+ โ†’ IM Registry Factory โ†’ Bot Instance
49
+ โ†’ AgentRuntime SDK โ†’ Agent Adapter
50
+ โ†’ Unified Proxy (:18899) โ†’ Upstream Models
13
51
  ```
14
52
 
15
53
  ### Supported IM Adapters
@@ -43,63 +81,115 @@ One gateway, multiple IMs, multiple agents, unified port proxy.
43
81
  | Codex | `npm install -g @openai/codex` |
44
82
  | OpenCode | `npm install -g opencode` |
45
83
 
46
- ### Installation
84
+ ## Installation Methods
47
85
 
48
- #### Method 1: npm global install (recommended)
86
+ ### Method 1: One-Click Install (Recommended)
87
+
88
+ ```bash
89
+ curl -fsSL https://raw.githubusercontent.com/imtoagent/imtoagent/main/install.sh | bash
90
+ ```
91
+
92
+ This script does everything automatically:
93
+ - Detects your OS and environment
94
+ - Installs bun if missing
95
+ - Installs or upgrades imtoagent
96
+ - Runs the setup wizard if no configuration exists
97
+ - Starts the gateway and verifies it's running
98
+
99
+ **Flags:**
100
+ - `--non-interactive` โ€” Skip all prompts, auto-install and auto-start
101
+ - `--skip-bun` โ€” Skip bun installation check
102
+ - `--skip-start` โ€” Don't start the gateway after install
103
+
104
+ ### Method 2: npm Global Install
49
105
 
50
106
  ```bash
51
107
  npm install -g imtoagent
52
108
  ```
53
109
 
54
- After installation, it automatically checks whether initial configuration is needed. An interactive terminal will guide you through the setup wizard.
110
+ This is the simplest approach. After installation, the post-install script checks if you need initial configuration.
55
111
 
56
- #### Method 2: Source install
112
+ ### Method 3: Source Install
57
113
 
58
114
  ```bash
59
- git clone https://github.com/YOUR_USERNAME/imtoagent.git
115
+ git clone https://github.com/imtoagent/imtoagent.git
60
116
  cd imtoagent
61
117
  bun install
62
118
  bun run bin/imtoagent setup
63
119
  ```
64
120
 
65
- ### First-Time Configuration
121
+ Use this for development or if you want to modify the source code.
122
+
123
+ ### Prerequisites
124
+
125
+ - **Bun** runtime (โ‰ฅ1.0.0): `brew install oven-sh/bun/bun`
126
+ - **macOS or Linux**
127
+ - **At least one Agent backend** installed (see below)
128
+
129
+ ### Agent Backend Installation
130
+
131
+ | Backend | Install Command |
132
+ |---------|----------------|
133
+ | Claude Code | `npm install -g @anthropic-ai/claude-agent-sdk` |
134
+ | Codex | `npm install -g @openai/codex` |
135
+ | OpenCode | `npm install -g opencode` |
136
+
137
+ You can install backends before or after installing imtoagent.
138
+
139
+ ## Configuration
140
+
141
+ ### First-Time Setup
66
142
 
67
143
  ```bash
68
144
  imtoagent setup
69
145
  ```
70
146
 
71
- The interactive setup wizard guides you through:
147
+ The interactive wizard will guide you through:
72
148
 
73
- 1. **Configure Bot** โ€” Select IM platform + Agent backend
149
+ 1. **Configure Bot** โ€” Select IM platform + Agent backend combination
74
150
  2. **Configure Model Providers** โ€” Add API credentials (DeepSeek, Dashscope, etc.)
75
- 3. **Generate Soul Files** โ€” Create rules.md / identity.md etc. for each Bot
151
+ 3. **Generate Soul Files** โ€” Create personality files (rules.md, identity.md, etc.)
76
152
  4. **Write Config Files** โ€” Auto-generate `~/.imtoagent/config.json`
77
153
 
78
- #### Feishu Bot Requirements
154
+ ### Platform-Specific Requirements
79
155
 
80
- - Feishu App ID (`cli_...`)
81
- - Feishu App Secret
82
- - Feishu app must enable: Bot, Event Subscription, Message Send/Receive permissions
156
+ #### Feishu
157
+ - **App ID** (`cli_...`) and **App Secret**
158
+ - Enable in Feishu app console: Bot, Event Subscription, Message Send/Receive permissions
83
159
 
84
- #### Telegram Bot Requirements
85
-
86
- - Telegram Bot Token (obtain from @BotFather)
87
- - Optional: Proxy address (e.g., `http://127.0.0.1:7890`)
160
+ #### Telegram
161
+ - **Bot Token** from @BotFather
162
+ - Optional: HTTP proxy (e.g., `http://127.0.0.1:7890`)
88
163
 
89
164
  #### Personal WeChat
165
+ - QR code automatically appears on first `imtoagent start`
166
+ - Scan with WeChat on your phone to complete binding
90
167
 
91
- - QR code automatically pops up on first run of `imtoagent start`
92
- - Scan with your phone's WeChat to complete binding
168
+ #### WeCom (Enterprise WeChat)
169
+ - Webhook callback URL configuration
170
+ - REST API credentials
93
171
 
94
- ### Start the Gateway
172
+ ## Running the Gateway
95
173
 
96
- ```bash
97
- imtoagent start # Start in background
98
- imtoagent status # Check running status
99
- imtoagent stop # Stop the gateway
100
- ```
174
+ ### Basic Commands
175
+
176
+ | Command | Description |
177
+ |---------|-------------|
178
+ | `imtoagent start` | Start gateway in background |
179
+ | `imtoagent stop` | Stop the gateway |
180
+ | `imtoagent status` | Check running status |
181
+ | `imtoagent restore` | Hot reload recovery |
182
+ | `imtoagent daemon` | Foreground daemon mode (crash auto-restart) |
183
+
184
+ ### Running Modes
101
185
 
102
- ### Auto-Start on Boot (macOS launchd)
186
+ - **`start`** โ€” Background mode, terminal returns immediately
187
+ - **`run`** โ€” Foreground mode, real-time logs, Ctrl+C to stop
188
+ - **`daemon`** โ€” Foreground with auto-restart on crash
189
+
190
+ ### Auto-Start on Boot
191
+
192
+ #### macOS (launchd)
103
193
 
104
194
  ```bash
105
195
  # Create launchd configuration
@@ -134,36 +224,54 @@ cat > ~/Library/LaunchAgents/com.imtoagent.plist << 'EOF'
134
224
  </plist>
135
225
  EOF
136
226
 
137
- # Load
227
+ # Load the service
138
228
  launchctl load ~/Library/LaunchAgents/com.imtoagent.plist
139
229
  ```
140
230
 
141
- ### Common Commands
231
+ #### Linux (systemd)
142
232
 
143
- | Command | Description |
144
- |------|------|
145
- | `imtoagent setup` | Interactive setup wizard |
146
- | `imtoagent start` | Start gateway in background |
147
- | `imtoagent stop` | Stop the gateway |
148
- | `imtoagent status` | Check running status |
149
- | `imtoagent restore` | Hot reload recovery |
150
- | `imtoagent daemon` | Foreground daemon mode (suitable for launchd/systemd) |
233
+ Create `/etc/systemd/system/imtoagent.service`:
151
234
 
152
- ### Built-In Gateway Commands
235
+ ```ini
236
+ [Unit]
237
+ Description=IMtoAgent Gateway
238
+ After=network.target
153
239
 
154
- Send to the Bot in IM chat:
240
+ [Service]
241
+ Type=simple
242
+ User=youruser
243
+ WorkingDirectory=/home/youruser/.imtoagent
244
+ ExecStart=/usr/bin/bun run /usr/lib/node_modules/imtoagent/index.ts daemon
245
+ Restart=on-failure
246
+ RestartSec=5
247
+
248
+ [Install]
249
+ WantedBy=multi-user.target
250
+ ```
251
+
252
+ ```bash
253
+ systemctl daemon-reload
254
+ systemctl enable imtoagent
255
+ systemctl start imtoagent
256
+ ```
257
+
258
+ ## Using the Gateway
259
+
260
+ ### Built-In Commands
261
+
262
+ Send these to your Bot in the IM chat:
155
263
 
156
264
  | Command | Description |
157
- |------|------|
158
- | `/help` | Help information |
265
+ |---------|-------------|
266
+ | `/help` | Show available commands |
159
267
  | `/status` | Gateway status |
160
268
  | `/stats` | Usage statistics |
161
- | `/model` | Switch model |
162
- | `/providers` | View providers |
163
- | `/memory` | View memory |
269
+ | `/model` | Switch AI model |
270
+ | `/providers` | View model providers |
271
+ | `/memory` | View memory status |
164
272
  | `/soul` | Soul management |
165
- | `/reload` | Reload |
166
- | `/clear` | Clear session |
273
+ | `/reload` | Reload configuration |
274
+ | `/clear` | Clear conversation session |
167
275
  | `/mode` | Switch mode (permission/auto/plan) |
168
276
  | `/dir` | Switch working directory |
169
277
 
@@ -206,27 +314,66 @@ imtoagent/
206
314
 
207
315
  ## Data Directory
208
316
 
209
- All runtime data is stored centrally in `~/.imtoagent/`:
317
+ All runtime data is stored in `~/.imtoagent/`:
210
318
 
211
319
  ```
212
320
  ~/.imtoagent/
213
321
  โ”œโ”€โ”€ config.json # Main config (Bot + providers + system)
214
- โ”œโ”€โ”€ providers.json # Model provider config
215
- โ”œโ”€โ”€ opencode.json # OpenCode config
216
- โ”œโ”€โ”€ sessions/ # Session persistence
322
+ โ”œโ”€โ”€ providers.json # Model provider configurations
323
+ โ”œโ”€โ”€ opencode.json # OpenCode-specific config
324
+ โ”œโ”€โ”€ sessions/ # Conversation persistence
217
325
  โ”œโ”€โ”€ logs/ # Runtime logs
218
- โ””โ”€โ”€ soul/ # Soul files (one directory per Bot)
326
+ โ””โ”€โ”€ soul/ # Bot personality files
219
327
  โ”œโ”€โ”€ ClaudeBot/
220
328
  โ”œโ”€โ”€ CodexBot/
221
- โ””โ”€โ”€ ...
329
+ โ””โ”€โ”€ ... # One directory per Bot
330
+ ```
331
+
332
+ ## Troubleshooting
333
+
334
+ ### Common Issues
335
+
336
+ **Gateway won't start**
337
+ - Check `imtoagent status` for details
338
+ - Verify config with `cat ~/.imtoagent/config.json`
339
+ - Check logs: `cat ~/.imtoagent/logs/*.log`
340
+
341
+ **Setup wizard stuck**
342
+ - Ensure terminal supports interactive input
343
+ - Try running in a standard terminal (not IDE integrated)
344
+
345
+ **Bot not responding in IM**
346
+ - Verify credentials in config.json
347
+ - Check IM platform permissions (Feishu events, Telegram webhook, etc.)
348
+ - Ensure the gateway is running: `imtoagent status`
349
+
350
+ **Port 18899 already in use**
351
+ - Another service is using the proxy port
352
+ - Kill the existing process or change port in config
353
+
354
+ ### Getting Help
355
+
356
+ - Check logs: `~/.imtoagent/logs/`
357
+ - Run `imtoagent status` for runtime information
358
+ - Open an issue on GitHub with logs attached
222
359
  ```
223
360
 
224
361
  ## Development
225
362
 
226
363
  ```bash
364
+ # Clone and setup development environment
365
+ git clone https://github.com/imtoagent/imtoagent.git
366
+ cd imtoagent
227
367
  bun install
228
- bun run index.ts # Run directly
229
- bun run bin/imtoagent setup # Run setup wizard
368
+
369
+ # Run directly
370
+ bun run index.ts
371
+
372
+ # Run setup wizard
373
+ bun run bin/imtoagent setup
374
+
375
+ # Run CLI from source
376
+ bun run bin/imtoagent status
230
377
  ```
231
378
 
232
379
  ## License
package/index.ts CHANGED
@@ -72,8 +72,7 @@ registerIM('wechat', {
72
72
  },
73
73
  });
74
74
  import { startAnthropicProxy, stopAnthropicProxy } from './modules/proxy/anthropic-proxy';
75
- import { getProxyUsage, resetProxyUsage, initCodexProxyConfig } from './modules/proxy/codex-proxy';
76
- import { initOpenCodeConfig } from './modules/agent/opencode';
75
+ import { initCodexProxyConfig } from './modules/proxy/codex-proxy';
77
76
  import { checkRateLimit, setRateLimitConfig } from './modules/rate-limiter';
78
77
  import { setCurrentBot } from './modules/bot-context';
79
78
  import { getDataDir, getSessionsDir, getSoulDir, getBotKey, getRestoreMarkerPath } from './modules/utils/paths';
@@ -904,11 +903,6 @@ async function main() {
904
903
  upstream: codexCfg.upstream || 'https://api.deepseek.com/v1/chat/completions',
905
904
  apiKey,
906
905
  });
907
- const ocCfg = config.opencode || {};
908
- initOpenCodeConfig({
909
- serverUrl: ocCfg.serverUrl || 'http://localhost:4096',
910
- defaultModel: ocCfg.defaultModel || { providerID: 'anthropic', modelID: 'claude-sonnet-4-5' },
911
- });
912
906
  const rlCfg = config.rateLimit || {};
913
907
  if (rlCfg.enabled !== false) {
914
908
  setRateLimitConfig({
@@ -22,9 +22,6 @@ let _config: ExecServerConfig = {
22
22
  maxToolCallsPerTurn: 80,
23
23
  };
24
24
 
25
- export function setExecServerConfig(cfg: Partial<ExecServerConfig>) {
26
- _config = { ..._config, ...cfg };
27
- }
28
25
 
29
26
  // ================================================================
30
27
  // ไบ‹ไปถ็ฑปๅž‹
@@ -510,4 +507,3 @@ export async function shutdownAppServer(): Promise<void> {
510
507
  export const shutdownExecServer = shutdownAppServer;
511
508
 
512
509
  // ๅ‘ๅŽๅ…ผๅฎน็š„็ฑปๅž‹ๅˆซๅ
513
- export type CodexExecServerClient = CodexAppServerClient;
@@ -525,19 +525,10 @@ async function streamResponse(upstreamRes: Response, resWriter: WritableStreamDe
525
525
  }
526
526
 
527
527
  // ================================================================
528
- // ================================================================
529
- // usage ็ดฏๅŠ ๅ™จ โ€” ไพ›็ฝ‘ๅ…ณ่ฏปๅ– Codex ็š„ Token/ๆˆๆœฌ็ปŸ่ฎก
528
+ // usage ็ดฏๅŠ ๅ™จ โ€” ๅ†…้ƒจ็ปŸ่ฎก๏ผŒไพ› accumulateProxyUsage ่ฎฐๅฝ•
530
529
  // ================================================================
531
530
  let _proxyUsage = { inputTokens: 0, outputTokens: 0 };
532
531
 
533
- export function getProxyUsage() {
534
- return { ..._proxyUsage };
535
- }
536
-
537
- export function resetProxyUsage() {
538
- _proxyUsage = { inputTokens: 0, outputTokens: 0 };
539
- }
540
-
541
532
  export function accumulateProxyUsage(inputTokens: number, outputTokens: number) {
542
533
  _proxyUsage.inputTokens += inputTokens;
543
534
  _proxyUsage.outputTokens += outputTokens;
@@ -646,12 +637,3 @@ export async function handleCodexRequest(
646
637
  res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'internal error' })); return;
647
638
  }
648
639
  }
649
-
650
- // ๅ…ผๅฎนๆ—งๅผ•็”จ๏ผˆไธๅ†ๅฏๅŠจ็‹ฌ็ซ‹ๆœๅŠกๅ™จ๏ผ‰
651
- export function startCodexProxy(_port?: number): Promise<number> {
652
- console.log('[Codex Proxy] Merged into port 18899');
653
- return Promise.resolve(18899);
654
- }
655
- export function stopCodexProxy(): Promise<void> {
656
- return Promise.resolve();
657
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imtoagent",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "IM โ†” Agent ็ปŸไธ€็ฝ‘ๅ…ณ โ€” ้ฃžไนฆ/Telegram/ๅพฎไฟก/ไผไธšๅพฎไฟกๅฏนๆŽฅ Claude Code/Codex/OpenCode",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,7 +32,8 @@
32
32
  },
33
33
  "repository": {
34
34
  "type": "git",
35
- "url": "git+https://github.com/YOUR_USERNAME/imtoagent.git"
35
+ "url": "https://github.com/imtoagent/imtoagent.git",
36
+ "directory": ""
36
37
  },
37
38
  "keywords": [
38
39
  "im",
@@ -50,7 +51,7 @@
50
51
  "author": "Keyi",
51
52
  "license": "MIT",
52
53
  "bugs": {
53
- "url": "https://github.com/YOUR_USERNAME/imtoagent/issues"
54
+ "url": "https://github.com/imtoagent/imtoagent/issues"
54
55
  },
55
- "homepage": "https://github.com/YOUR_USERNAME/imtoagent#readme"
56
+ "homepage": "https://github.com/imtoagent/imtoagent#readme"
56
57
  }
@@ -1,160 +0,0 @@
1
- // Claude Agent ๆจกๅ—
2
- // ๅฏนๆŽฅ Claude Agent SDK๏ผŒ้€š่ฟ‡ :18899 Proxy ่ฐƒ็”จ Provider
3
-
4
- import { query } from '@anthropic-ai/claude-agent-sdk';
5
- import type { AgentContext } from '../types';
6
- import { parseToBlocks, type UnifiedBlock } from '../capabilities';
7
- import { buildSystemPrompt, resolveCapabilities } from '../prompt-builder';
8
- import type { SDKMessage } from '@anthropic-ai/claude-agent-sdk';
9
-
10
- // ================================================================
11
- // ๅทฅๅ…ทๅ‡ฝๆ•ฐ
12
- // ================================================================
13
- function resolveAlias(modelSpec: string): string {
14
- const i = modelSpec.indexOf('/');
15
- return i >= 0 ? modelSpec.slice(i + 1) : modelSpec;
16
- }
17
-
18
- function extractText(msg: SDKMessage): string | null {
19
- if (msg.type !== 'assistant') return null;
20
- const content = (msg as any).message?.content;
21
- if (!Array.isArray(content)) return null;
22
- return content.filter((b: any) => b.type === 'text').map((b: any) => b.text).join('') || null;
23
- }
24
-
25
- function extractToolInfo(msg: SDKMessage): { name: string; summary: string } | null {
26
- if (msg.type !== 'assistant') return null;
27
- const content = (msg as any).message?.content;
28
- if (!Array.isArray(content)) return null;
29
- const tool = content.find((b: any) => b.type === 'tool_use');
30
- if (!tool?.name) return null;
31
- const input = tool.input || {};
32
- let summary = '';
33
- if (['Read','Edit','Write'].includes(tool.name) && input.file_path) {
34
- const p = String(input.file_path);
35
- summary = p.includes('/') ? p.split('/').pop()! : p;
36
- } else if (tool.name === 'Bash' && input.command) {
37
- summary = String(input.command).trim().slice(0, 60);
38
- }
39
- return { name: tool.name, summary };
40
- }
41
-
42
- // ================================================================
43
- // Claude ๆจกๅ—็ฑป
44
- // ================================================================
45
-
46
- /**
47
- * ๆณจๆ„๏ผšๆญคๆจกๅ—็›ฎๅ‰ไพ่ต–ๅฎฟไธป Bot ๅฎžไพ‹็š„ๆ–นๆณ•๏ผˆreply/sendProgress/addToolLog ็ญ‰๏ผ‰ใ€‚
48
- * Phase 2 ็ฌฌไธ€ๆญฅๅ…ˆๅŽŸๆ ทๆๅ–๏ผŒๅŽ็ปญ้€ๆญฅ่งฃ่€ฆไธบๅนฒๅ‡€ๆŽฅๅฃใ€‚
49
- */
50
- export class ClaudeAgentModule {
51
- private ctx: AgentContext;
52
-
53
- constructor(ctx: AgentContext) {
54
- this.ctx = ctx;
55
- }
56
-
57
- async handleMessage(chatId: string, text: string, session: any) {
58
- session.generator.push({
59
- type: 'user', message: { role: 'user', content: [{ type: 'text', text }] },
60
- });
61
- if (!session.running) this._startLoop(chatId);
62
- }
63
-
64
- private async _startLoop(chatId: string) {
65
- const ctx = this.ctx;
66
- const session = ctx.sessions.get(chatId);
67
- if (!session || session.running) return;
68
- session.running = true;
69
- console.log(`[${ctx.name}] Claude loop started chat=${chatId.slice(-8)}`);
70
-
71
- try {
72
- const modelSpec = ctx.activeModel;
73
- const modelName = modelSpec.slice(modelSpec.indexOf('/') + 1);
74
- const aliases = ctx.modelAliases;
75
- const customEnv: Record<string, string> = {
76
- ...process.env,
77
- ANTHROPIC_BASE_URL: 'http://localhost:18899',
78
- ANTHROPIC_API_KEY: '', ANTHROPIC_AUTH_TOKEN: '', ANTHROPIC_MODEL: '',
79
- ANTHROPIC_DEFAULT_SONNET_MODEL: resolveAlias(aliases.sonnet),
80
- ANTHROPIC_DEFAULT_OPUS_MODEL: resolveAlias(aliases.opus),
81
- ANTHROPIC_DEFAULT_HAIKU_MODEL: resolveAlias(aliases.haiku),
82
- };
83
-
84
- const queryOptions: any = {
85
- cwd: session.cwd || ctx.defaultCwd,
86
- maxTurns: 50, model: modelName,
87
- permissionMode: session.permissionMode || 'bypassPermissions',
88
- persistSession: true,
89
- };
90
- if (session.sdkSessionId) {
91
- queryOptions.resume = session.sdkSessionId;
92
- } else {
93
- queryOptions.sessionId = crypto.randomUUID();
94
- }
95
-
96
- const botName = ctx.name;
97
- const systemPrompt = buildSystemPrompt({
98
- imModule: ctx.imModule || null,
99
- botName,
100
- });
101
- console.log(`[Claude] ๐Ÿ“ system prompt built (${systemPrompt.length} chars, bot=${botName})`);
102
- queryOptions.systemPrompt = systemPrompt;
103
-
104
- const q = query({
105
- prompt: session.generator.generate(),
106
- options: queryOptions, env: customEnv,
107
- });
108
-
109
- let fullResponse = '', toolCalls = 0;
110
- let callInput = 0, callOutput = 0, callCost = 0, callDur = 0;
111
-
112
- for await (const msg of q) {
113
- const msgAny = msg as any;
114
- if (msgAny.session_id && !session.sdkSessionId) {
115
- session.sdkSessionId = msgAny.session_id;
116
- ctx.persistSession(chatId, session);
117
- }
118
- const text = extractText(msg);
119
- if (text) fullResponse += text;
120
- const toolInfo = extractToolInfo(msg);
121
- if (toolInfo) { toolCalls++; ctx.addToolLog(chatId, toolInfo); }
122
-
123
- if (msg.type === 'result') {
124
- const result = msg as any;
125
- callInput = result.usage?.input_tokens || 0;
126
- callOutput = result.usage?.output_tokens || 0;
127
- callCost = result.total_cost_usd || 0;
128
- callDur = result.duration_ms || 0;
129
-
130
- ctx.accumulateStats(session, {
131
- inputTokens: callInput, outputTokens: callOutput,
132
- costUSD: callCost, durationMs: callDur,
133
- numTurns: result.num_turns || 0,
134
- });
135
-
136
- if (result.subtype === 'error' || result.subtype === 'cancelled') {
137
- await ctx.reply(chatId, `โŒ ${result.error || result.result || 'Unknown error'}`);
138
- } else if (fullResponse) {
139
- await ctx.sendFormattedReply(chatId, fullResponse);
140
- } else {
141
- await ctx.reply(chatId, `โœ… Completed (${toolCalls} steps)`);
142
- }
143
-
144
- ctx.flushToolLog(chatId);
145
- const costStr = callCost > 0 ? `Cost $${callCost.toFixed(4)}\n` : '';
146
- await ctx.sendProgress(chatId,
147
- `โœ… Completed (${toolCalls} steps)\nInput ${callInput.toLocaleString()} Token\nOutput ${callOutput.toLocaleString()} Token\n${costStr}Duration ${(callDur/1000).toFixed(1)}s`);
148
- fullResponse = ''; toolCalls = 0;
149
- }
150
- }
151
- } catch (e: any) {
152
- console.error(`[${ctx.name}] Claude error: ${e.message}`);
153
- await ctx.reply(chatId, `โŒ ${e.message}`);
154
- } finally {
155
- session.running = false;
156
- session.generator.close();
157
- ctx.persistSession(chatId, session);
158
- }
159
- }
160
- }
@@ -1,275 +0,0 @@
1
- // Codex Agent ๆจกๅ—
2
- // ๅฏนๆŽฅ Codex CLI๏ผŒ้€š่ฟ‡ :18899 Proxy ่ฐƒ็”จ Provider
3
-
4
- import { getProxyUsage, resetProxyUsage } from '../proxy/codex-proxy';
5
- import { calculateCost } from '../proxy/anthropic-proxy';
6
- import type { AgentContext } from '../types';
7
- import { getAppServerManager, type AgentEvent } from './codex-exec-server';
8
- // ================================================================
9
- // ็ฑปๅž‹
10
- // ================================================================
11
- interface CodexJsonEvent {
12
- type: string;
13
- thread_id?: string;
14
- item?: { type: string; text?: string; name?: string; arguments?: string; output?: string };
15
- text?: string;
16
- delta?: string;
17
- message?: { content?: { type: string; text?: string }[] };
18
- error?: string;
19
- }
20
-
21
- const TOOL_NAMES: Record<string, string> = {
22
- Bash: 'Execute command', Read: 'Read file', Edit: 'Edit file', Write: 'Write file',
23
- Glob: 'Search files', Grep: 'Search content', WebSearch: 'Web search', WebFetch: 'Fetch webpage',
24
- NotebookEdit: 'Edit Notebook',
25
- // Codex tool names
26
- command_execution: 'Execute command', exec_command: 'Execute command', write_stdin: 'Write to stdin', update_plan: 'Update plan',
27
- request_user_input: 'Request input', apply_patch: 'Apply patch', view_image: 'View image',
28
- spawn_agent: 'Spawn agent', send_input: 'Send input', resume_agent: 'Resume agent',
29
- wait_agent: 'Wait agent', close_agent: 'Close agent',
30
- };
31
-
32
- // ================================================================
33
- // Codex CLI ่ฐƒ็”จ
34
- // ================================================================
35
- function processCodexStream(stdout: string, onTool?: (name: string, args: Record<string, any>) => void): { threadId: string; response: string } {
36
- let threadId = '', response = '';
37
- for (const line of stdout.split('\n')) {
38
- if (!line.trim()) continue;
39
- try {
40
- const evt: CodexJsonEvent = JSON.parse(line);
41
- if (evt.type === 'thread.started' && evt.thread_id) {
42
- threadId = evt.thread_id;
43
- } else if (evt.type === 'item.completed') {
44
- if (evt.item?.type === 'agent_message') {
45
- response = (response ? response + '\n' : '') + (evt.item.text || '');
46
- } else if (TOOL_NAMES[evt.item?.type || ''] && onTool) {
47
- onTool(evt.item.type || 'unknown', { command: (evt.item as any).command || '' });
48
- }
49
- } else if (evt.type === 'error' || evt.type === 'thread.error') {
50
- console.error(`[Codex] event error: ${(evt as any).message || (evt as any).error || JSON.stringify(evt)}`);
51
- }
52
- } catch {}
53
- }
54
- return { threadId, response };
55
- }
56
-
57
- async function spawnCodexExec(
58
- cwd: string, prompt: string,
59
- onTool?: (name: string, args: Record<string, any>) => void
60
- ): Promise<{ threadId: string; response: string }> {
61
- console.error(`[Codex] spawnExec cwd=${cwd} prompt_len=${prompt.length}`);
62
- const child = Bun.spawn(['codex', 'exec', '-p', 'imtoagent', '-s', 'danger-full-access',
63
- '--skip-git-repo-check', '--json', prompt], {
64
- cwd, stdout: 'pipe', stderr: 'pipe',
65
- });
66
-
67
- // Safe read stdout/stderr: catch subprocess kill exceptions, ensure reject carries Error object
68
- let stdout = '', stderr = '';
69
- try {
70
- [stdout, stderr] = await Promise.all([
71
- new Response(child.stdout).text().catch((e: any) => { throw new Error(`stdout read failed: ${e?.message || e}`); }),
72
- new Response(child.stderr).text().catch((e: any) => { throw new Error(`stderr read failed: ${e?.message || e}`); }),
73
- ]);
74
- } catch (ioErr: any) {
75
- // Subprocess may have been killed, try to get exit code
76
- try { child.kill('SIGKILL'); } catch {}
77
- throw new Error(`codex exec I/O error: ${ioErr.message}`);
78
- }
79
-
80
- const code = await child.exited.catch(() => -1);
81
- console.error(`[Codex] exec exit=${code} stdout_len=${stdout.length} stderr_len=${stderr.length}`);
82
- const { threadId, response } = processCodexStream(stdout, onTool);
83
- if (code !== 0 || !threadId) throw new Error(`codex exec exit ${code}: ${stderr.slice(0, 300)}`);
84
- return { threadId, response };
85
- }
86
-
87
- async function spawnCodexResume(
88
- cwd: string, threadId: string, prompt: string,
89
- onTool?: (name: string, args: Record<string, any>) => void
90
- ): Promise<{ response: string }> {
91
- console.error(`[Codex] spawnResume cwd=${cwd} threadId=${threadId.slice(-8)} prompt_len=${prompt.length}`);
92
- const child = Bun.spawn(['codex', 'exec', 'resume', threadId,
93
- '--dangerously-bypass-approvals-and-sandbox', '-c', 'model_provider=imtoagent', '-c', 'model=gpt-5.5', '--json', '--skip-git-repo-check', prompt], {
94
- cwd, stdout: 'pipe', stderr: 'pipe',
95
- });
96
-
97
- let stdout = '', stderr = '';
98
- try {
99
- [stdout, stderr] = await Promise.all([
100
- new Response(child.stdout).text().catch((e: any) => { throw new Error(`stdout read failed: ${e?.message || e}`); }),
101
- new Response(child.stderr).text().catch((e: any) => { throw new Error(`stderr read failed: ${e?.message || e}`); }),
102
- ]);
103
- } catch (ioErr: any) {
104
- try { child.kill('SIGKILL'); } catch {}
105
- throw new Error(`codex exec resume I/O error: ${ioErr.message}`);
106
- }
107
-
108
- const code = await child.exited.catch(() => -1);
109
- console.error(`[Codex] resume exit=${code} stdout_len=${stdout.length} stderr_len=${stderr.length}`);
110
- if (code !== 0) throw new Error(`codex exec resume exit ${code}: ${stderr.slice(0, 300)}`);
111
- return { response: processCodexStream(stdout, onTool).response };
112
- }
113
-
114
- // ================================================================
115
- // App-Server ่ทฏๅพ„๏ผˆไผ˜ๅ…ˆไฝฟ็”จโ€”โ€”ๆตๅผ่พ“ๅ‡บ + ้•ฟ่ฎฐๅฟ† + ๅดฉๆบƒไธไธขไธŠไธ‹ๆ–‡๏ผ‰
116
- // ================================================================
117
- async function runViaAppServer(
118
- cwd: string, prompt: string, chatId: string, session: any,
119
- onTool: (name: string, args: Record<string, any>) => void,
120
- isFresh: boolean
121
- ): Promise<{ threadId: string; response: string; usage: { inputTokens: number; outputTokens: number } }> {
122
- const manager = getAppServerManager();
123
- const client = await manager.getClient(chatId);
124
-
125
- // app-server ๅŒ่ฟ›็จ‹ๅ†…็บฟ็จ‹ๅญ˜ๆดป
126
- // ไฝ†่ฟ›็จ‹้‡ๅฏๅŽๆ—ง thread ่ฟ‡ๆœŸ๏ผŒ้œ€่ฆๅˆคๆ–ญไปฃ้™…
127
- const currentGen = manager.generation;
128
- const threadExpired = session._appServerGen !== currentGen;
129
- if (isFresh || !session.codexThreadId || threadExpired) {
130
- session.codexThreadId = await client.startThread(cwd);
131
- session._appServerGen = currentGen;
132
- console.log(`[Codex] app-server new thread=${session.codexThreadId.slice(-8)}${threadExpired ? ' (process restarted)' : ''}`);
133
- }
134
- // ๅŽ็ปญๆถˆๆฏ็›ดๆŽฅ turn/start๏ผˆๅŒ็บฟ็จ‹ๅปถ็ปญไธŠไธ‹ๆ–‡๏ผ‰
135
-
136
- await client.sendPrompt(session.codexThreadId, prompt, cwd);
137
-
138
- let response = '';
139
- let totalUsage = { inputTokens: 0, outputTokens: 0 };
140
- const startTime = Date.now();
141
- const MAX_DURATION = 600_000; // 10 ๅˆ†้’Ÿๆ€ป่ถ…ๆ—ถ
142
-
143
- for await (const event of client.receiveEvents()) {
144
- // ่ถ…ๆ—ถไฟๆŠค
145
- if (Date.now() - startTime > MAX_DURATION) {
146
- console.error('[Codex] app-server task timed out (10min)');
147
- break;
148
- }
149
-
150
- switch (event.type) {
151
- case 'text_delta':
152
- response += event.textDelta || '';
153
- break;
154
- case 'tool_call':
155
- onTool(event.toolName || 'unknown', event.toolInput || {});
156
- break;
157
- case 'turn_result':
158
- // ็ดฏๅŠ ๅคš่ฝฎ token๏ผˆ้ž็ปˆ็ซฏ turn_result ๆฅ่‡ชๆฏ่ฝฎ็š„ turn/completed๏ผ‰
159
- totalUsage.inputTokens += event.usage?.inputTokens || 0;
160
- totalUsage.outputTokens += event.usage?.outputTokens || 0;
161
- break;
162
- case 'error':
163
- throw new Error(`app-server error: ${event.error}`);
164
- }
165
- }
166
-
167
- return { threadId: session.codexThreadId, response, usage: totalUsage };
168
- }
169
-
170
- // ================================================================
171
- // Codex ๆจกๅ—็ฑป
172
- // ================================================================
173
- export class CodexAgentModule {
174
- private ctx: AgentContext;
175
-
176
- constructor(ctx: AgentContext) {
177
- this.ctx = ctx;
178
- }
179
-
180
- async handleMessage(chatId: string, text: string, session: any) {
181
- const ctx = this.ctx;
182
- const cwd = session.cwd || ctx.defaultCwd;
183
- console.log(`[${ctx.name}] Codex chat=${chatId.slice(-8)} startFresh=${session.startFresh || false}`);
184
-
185
- const onTool = (name: string, args: Record<string, any>) => {
186
- const cmd = args.cmd || args.command || '';
187
- const justification = args.justification || args.description || '';
188
- const summary = cmd ? cmd.slice(0, 80) : justification.slice(0, 80);
189
- ctx.addToolLog(chatId, { name, summary });
190
- };
191
-
192
- resetProxyUsage();
193
- try {
194
- let effectiveText = text;
195
- if (session.codexMode === 'plan') {
196
- effectiveText = `[Mode: Plan then execute] Please create a clear plan first, wait for my confirmation before executing. User request: ${text}`;
197
- }
198
-
199
- const isFresh = session.startFresh || !session.codexThreadId;
200
- let response: string;
201
- let execServerUsage: { inputTokens: number; outputTokens: number } | null = null;
202
-
203
- session.startFresh = false;
204
- await ctx.sendProgress(chatId, '๐Ÿ’ญ Thinking...');
205
-
206
- // ไผ˜ๅ…ˆๅฐ่ฏ• app-server
207
- console.error(`[${ctx.name}] DEBUG entering app-server branch, isFresh=${isFresh}, threadId=${session.codexThreadId?.slice(-8)}`);
208
- let useExecFallback = false;
209
- try {
210
- const r = await runViaAppServer(cwd, effectiveText, chatId, session, onTool, isFresh);
211
- response = r.response;
212
- execServerUsage = r.usage;
213
- } catch (appErr: any) {
214
- const errMsg = appErr.message || '';
215
- console.error(`[${ctx.name}] app-server failed: ${errMsg}`);
216
-
217
- // thread not found โ†’ app-server ่ฟ›็จ‹ๅ†…็บฟ็จ‹ไธขไบ†๏ผŒๅฐ่ฏ•้‡ๆ–ฐๅˆ›ๅปบ
218
- if (errMsg.includes('thread not found') || errMsg.includes('Thread not found')) {
219
- try {
220
- session.codexThreadId = undefined;
221
- const r2 = await runViaAppServer(cwd, effectiveText, chatId, session, onTool, true);
222
- response = r2.response;
223
- execServerUsage = r2.usage;
224
- console.error(`[${ctx.name}] app-server thread rebuilt successfully`);
225
- } catch {
226
- useExecFallback = true;
227
- }
228
- } else {
229
- useExecFallback = true;
230
- }
231
- }
232
-
233
- if (useExecFallback) {
234
- getAppServerManager().removeClient(chatId);
235
- if (isFresh || !session.codexThreadId) {
236
- const r = await spawnCodexExec(cwd, effectiveText, onTool);
237
- session.codexThreadId = r.threadId;
238
- response = r.response;
239
- console.log(`[${ctx.name}] Fresh session thread=${r.threadId.slice(-8)}`);
240
- } else {
241
- const r = await spawnCodexResume(cwd, session.codexThreadId, effectiveText, onTool);
242
- response = r.response;
243
- }
244
- }
245
- ctx.flushToolLog(chatId);
246
-
247
- // ไผ˜ๅ…ˆไฝฟ็”จ app-server ่ฟ”ๅ›ž็š„ usage๏ผŒๅฆๅˆ™ไปŽ proxy ่Žทๅ–
248
- const usage = execServerUsage || getProxyUsage();
249
- if (usage.inputTokens > 0 || usage.outputTokens > 0) {
250
- const cost = calculateCost(ctx.activeModel, usage.inputTokens, usage.outputTokens);
251
- ctx.accumulateStats(session, { ...usage, costUSD: cost });
252
- await ctx.sendProgress(chatId,
253
- `Input ${usage.inputTokens.toLocaleString()} Token\nOutput ${usage.outputTokens.toLocaleString()} Token\nCost $${cost.toFixed(4)}`);
254
- }
255
-
256
- if (response) {
257
- await ctx.sendFormattedReply(chatId, response);
258
- }
259
- else await ctx.reply(chatId, 'โœ… Completed');
260
- ctx.persistSession(chatId, session);
261
- } catch (e: any) {
262
- console.error(`[${ctx.name}] Codex error: ${e.message}`);
263
- session.codexThreadId = undefined;
264
- try {
265
- const r = await spawnCodexExec(cwd, text, onTool);
266
- session.codexThreadId = r.threadId;
267
- if (r.response) {
268
- await ctx.sendFormattedReply(chatId, r.response);
269
- }
270
- } catch (e2: any) {
271
- await ctx.reply(chatId, `โŒ Processing failed: ${e2.message}`);
272
- }
273
- }
274
- }
275
- }
@@ -1,247 +0,0 @@
1
- // OpenCode Agent ๆจกๅ—
2
- // ๅฏนๆŽฅ opencode serve HTTP API๏ผŒ้€š่ฟ‡ :18899 Anthropic Proxy ่ฐƒ Provider
3
- //
4
- // ่ฎพ่ฎก๏ผš่–„ๆจกๅ— โ€” oc serve ็ฎก็† session/ๅทฅๅ…ท/provider๏ผŒGateway ๅชๅš IM ็ฟป่ฏ‘
5
-
6
- import type { AgentContext, SessionData } from '../types';
7
- import { parseToBlocks } from '../capabilities';
8
- import { resolveCapabilities, buildSystemPrompt } from '../prompt-builder';
9
- import { calculateCost } from '../proxy/anthropic-proxy';
10
- import * as path from 'path';
11
- import * as fs from 'fs';
12
- import { getDataDir } from '../utils/paths';
13
-
14
- // ================================================================
15
- // ้…็ฝฎ๏ผˆไปŽ config.json ่ฏปๅ–๏ผ‰
16
- // ================================================================
17
-
18
- interface OpenCodeConfig {
19
- serverUrl: string;
20
- defaultModel: { providerID: string; modelID: string };
21
- }
22
-
23
- let _ocConfig: OpenCodeConfig | null = null;
24
-
25
- export function initOpenCodeConfig(cfg: OpenCodeConfig) {
26
- _ocConfig = cfg;
27
- }
28
-
29
- function getOcConfig(): OpenCodeConfig {
30
- if (!_ocConfig) {
31
- try {
32
-
33
- const raw = JSON.parse(fs.readFileSync(path.join(getDataDir(), 'config.json'), 'utf-8'));
34
- const oc = raw.opencode || {};
35
- _ocConfig = {
36
- serverUrl: oc.serverUrl || 'http://localhost:4096',
37
- defaultModel: oc.defaultModel || { providerID: 'anthropic', modelID: 'claude-sonnet-4-5' },
38
- };
39
- } catch {
40
- _ocConfig = {
41
- serverUrl: 'http://localhost:4096',
42
- defaultModel: { providerID: 'anthropic', modelID: 'claude-sonnet-4-5' },
43
- };
44
- }
45
- }
46
- return _ocConfig;
47
- }
48
-
49
- const OC_SERVER_URL = () => getOcConfig().serverUrl;
50
- const OC_DEFAULT_MODEL = () => getOcConfig().defaultModel;
51
-
52
- // ================================================================
53
- // OpenCode Server HTTP ๅฎขๆˆท็ซฏ
54
- // ================================================================
55
-
56
- interface OcMessagePart {
57
- type: string;
58
- text?: string;
59
- tool_call?: { name: string; arguments: Record<string, any> };
60
- tool_result?: { content: string };
61
- }
62
-
63
- interface OcMessage {
64
- info: { id: string; role: string; model?: string };
65
- parts: OcMessagePart[];
66
- }
67
-
68
- async function ocCreateSession(title: string): Promise<string> {
69
- const ac = new AbortController();
70
- const timer = setTimeout(() => ac.abort(), 300_000);
71
- const res = await fetch(`${OC_SERVER_URL()}/session`, {
72
- method: 'POST',
73
- headers: { 'Content-Type': 'application/json' },
74
- body: JSON.stringify({ title }),
75
- signal: ac.signal,
76
- }).finally(() => clearTimeout(timer));
77
- if (!res.ok) throw new Error(`oc create session: ${res.status} ${await res.text()}`);
78
- const data = await res.json();
79
- return data.id;
80
- }
81
-
82
- async function ocSendPrompt(
83
- sessionId: string,
84
- initialText: string,
85
- system?: string,
86
- onTool?: (name: string, args: Record<string, any>) => void
87
- ): Promise<{ response: string }> {
88
- const MAX_TURNS = 50;
89
- const TURN_TIMEOUT = 300_000;
90
- const startTime = Date.now();
91
- const MAX_DURATION = 600_000; // 10 ๅˆ†้’Ÿๆ€ป่ถ…ๆ—ถ
92
-
93
- let promptText = initialText;
94
- let accumulatedResponse = '';
95
- let turn = 0;
96
-
97
- while (turn < MAX_TURNS) {
98
- if (Date.now() - startTime > MAX_DURATION) {
99
- console.error('[OpenCode] Task timed out (10min)');
100
- break;
101
- }
102
- turn++;
103
-
104
- const body: any = {
105
- model: OC_DEFAULT_MODEL(),
106
- parts: [{ type: 'text', text: promptText }],
107
- };
108
- if (turn === 1 && system) body.system = system;
109
-
110
- const ac = new AbortController();
111
- const timer = setTimeout(() => ac.abort(), TURN_TIMEOUT);
112
- const res = await fetch(`${OC_SERVER_URL()}/session/${sessionId}/message`, {
113
- method: 'POST',
114
- headers: { 'Content-Type': 'application/json' },
115
- body: JSON.stringify(body),
116
- signal: ac.signal,
117
- }).finally(() => clearTimeout(timer));
118
- if (!res.ok) throw new Error(`oc send prompt: ${res.status} ${await res.text()}`);
119
-
120
- const data: OcMessage = await res.json();
121
- let hasToolCall = false;
122
-
123
- for (const part of data.parts || []) {
124
- if (part.type === 'text' && part.text) {
125
- accumulatedResponse += (accumulatedResponse ? '\n' : '') + part.text;
126
- } else if (part.type === 'tool_call' && part.tool_call) {
127
- hasToolCall = true;
128
- if (onTool) onTool(part.tool_call.name, part.tool_call.arguments);
129
- }
130
- }
131
-
132
- // ็บฏๆ–‡ๆœฌๅ›žๅคๆˆ–ๆฒกๆœ‰ tool_call โ†’ ไปปๅŠกๅฎŒๆˆ
133
- if (!hasToolCall) break;
134
-
135
- // ๆœ‰ tool_call๏ผŒ็ปง็ปญๆŽจ่ฟ›๏ผˆ็ฉบ prompt๏ผ‰
136
- promptText = 'Continue';
137
- }
138
-
139
- return { response: accumulatedResponse };
140
- }
141
-
142
- async function ocDeleteSession(sessionId: string): Promise<void> {
143
- await fetch(`${OC_SERVER_URL()}/session/${sessionId}`, { method: 'DELETE' }).catch(() => {});
144
- }
145
-
146
- async function ocHealthCheck(): Promise<boolean> {
147
- try {
148
- const res = await fetch(`${OC_SERVER_URL()}/global/health`);
149
- return res.ok;
150
- } catch {
151
- return false;
152
- }
153
- }
154
-
155
- // ================================================================
156
- // Agent ๆจกๅ—็ฑป
157
- // ================================================================
158
- export class OpenCodeAgentModule {
159
- private ctx: AgentContext;
160
-
161
- constructor(ctx: AgentContext) {
162
- this.ctx = ctx;
163
- }
164
-
165
- async handleMessage(chatId: string, text: string, session: SessionData) {
166
- const ctx = this.ctx;
167
- console.log(`[${ctx.name}] OpenCode chat=${chatId.slice(-8)}`);
168
-
169
- // ๅทฅๅ…ทๅ›ž่ฐƒ
170
- const onTool = (name: string, args: Record<string, any>) => {
171
- const summary = args.command || args.cmd || args.query || JSON.stringify(args).slice(0, 80);
172
- ctx.addToolLog(chatId, { name, summary });
173
- };
174
-
175
- try {
176
- // โ‘  Plan ๆจกๅผๅค„็†
177
- let effectiveText = text;
178
- if (session.codexMode === 'plan') {
179
- effectiveText = `[Mode: Plan then execute] Please create a clear plan first, wait for my confirmation before executing. User request: ${text}`;
180
- }
181
-
182
- // โ‘ก ๆธ…็†ๆ ‡่ฎฐ
183
- const shouldClear = session.startFresh;
184
- session.startFresh = false;
185
-
186
- // โ‘ข ่Žทๅ–ๆˆ–ๅˆ›ๅปบ OpenCode session
187
- if (shouldClear || !session.ocSessionId) {
188
- if (session.ocSessionId) {
189
- await ocDeleteSession(session.ocSessionId);
190
- console.log(`[${ctx.name}] Cleared oc session=${session.ocSessionId.slice(-8)}`);
191
- }
192
- session.ocSessionId = await ocCreateSession(chatId);
193
- console.log(`[${ctx.name}] Created oc session=${session.ocSessionId.slice(-8)}`);
194
- }
195
-
196
- // โ‘ฃ ๅ‘้€่ฟ›ๅบฆๆ็คบ
197
- await ctx.sendProgress(chatId, '๐Ÿ’ญ Thinking...');
198
-
199
- // โ‘ฃ.โ‘ค ๆž„ๅปบ็ณป็ปŸๆ็คบ่ฏ
200
- const systemPrompt = buildSystemPrompt({
201
- imModule: ctx.imModule || null,
202
- botName: ctx.name,
203
- });
204
- console.log(`[${ctx.name}] ๐Ÿ“ system prompt built (${systemPrompt.length} chars, bot=${ctx.name})`);
205
-
206
- // โ‘ค ๅ‘้€ prompt๏ผˆๅคš่ฝฎๅพช็Žฏ๏ผ‰
207
- const { response } = await ocSendPrompt(
208
- session.ocSessionId,
209
- effectiveText,
210
- systemPrompt,
211
- onTool,
212
- );
213
-
214
- // โ‘ฅ ๅˆทๆ–ฐๅทฅๅ…ทๆ—ฅๅฟ—
215
- ctx.flushToolLog(chatId);
216
-
217
- // โ‘ฆ ่พ“ๅ‡บ
218
- if (response) {
219
- await ctx.sendFormattedReply(chatId, response);
220
- } else {
221
- await ctx.reply(chatId, 'โœ… Completed');
222
- }
223
-
224
- // โ‘ง ็ปŸ่ฎก
225
- const { sharedState } = await import('../proxy/anthropic-proxy');
226
- const lastUsage = (sharedState as any).lastCallUsage;
227
- if (lastUsage && (lastUsage.inputTokens > 0 || lastUsage.outputTokens > 0)) {
228
- const cost = calculateCost(ctx.activeModel, lastUsage.inputTokens, lastUsage.outputTokens);
229
- ctx.accumulateStats(session, { ...lastUsage, costUSD: cost });
230
- await ctx.sendProgress(chatId,
231
- `Input ${lastUsage.inputTokens.toLocaleString()} Token\nOutput ${lastUsage.outputTokens.toLocaleString()} Token\nCost $${cost.toFixed(4)}`);
232
- }
233
-
234
- // โ‘จ ๆŒไน…ๅŒ–ไผš่ฏ
235
- ctx.persistSession(chatId, session);
236
-
237
- } catch (err: any) {
238
- console.error(`[${ctx.name}] OpenCode error: ${err.message}`);
239
- await ctx.reply(chatId, `โš ๏ธ OpenCode error: ${err.message}`);
240
- }
241
- }
242
-
243
- /** ๅฅๅบทๆฃ€ๆŸฅ */
244
- async healthCheck(): Promise<boolean> {
245
- return ocHealthCheck();
246
- }
247
- }
@@ -1,70 +0,0 @@
1
- #!/usr/bin/env bun
2
- // ================================================================
3
- // postinstall.ts โ€” npm ๅฎ‰่ฃ…ๅŽๅผ•ๅฏผ่„šๆœฌ
4
- // ================================================================
5
- // package.json ไธญ "scripts": { "postinstall": "bun run scripts/postinstall.ts" }
6
- // ๅฎ‰่ฃ…ๅŽ่‡ชๅŠจ่ฟ่กŒ๏ผŒๆฃ€ๆต‹ๆ˜ฏๅฆ้œ€่ฆๅˆๅง‹ๅŒ–้…็ฝฎ
7
- // ๅฆ‚ๆžœๆ˜ฏๅ…จๆ–ฐๅฎ‰่ฃ…ไธ”็ปˆ็ซฏไบคไบ’๏ผŒ่‡ชๅŠจๅผ•ๅฏผ่ฟ›ๅ…ฅ setup
8
- // ================================================================
9
-
10
- import * as fs from 'fs';
11
- import * as path from 'path';
12
- import { execSync } from 'child_process';
13
-
14
- const HOME = process.env.HOME || process.env.USERPROFILE || '';
15
- const DATA_DIR = path.join(HOME, '.imtoagent');
16
-
17
- try {
18
- const configExists = fs.existsSync(path.join(DATA_DIR, 'config.json'));
19
-
20
- if (configExists) {
21
- console.log(`
22
- โœ… imtoagent upgraded successfully!
23
- Data directory: ${DATA_DIR}
24
- Configuration file kept as-is, no need to reconfigure.
25
- Run "imtoagent start" to start the gateway.
26
- `);
27
- } else {
28
- console.log(`
29
- โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
30
- โ•‘ โ•‘
31
- โ•‘ ๐ŸŽ‰ imtoagent installed successfully! โ•‘
32
- โ•‘ โ•‘
33
- โ•‘ First-time use requires configuring IM credentials and a model provider โ•‘
34
- โ•‘ โ•‘
35
- โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
36
- `);
37
-
38
- // ๆฃ€ๆต‹ๆ˜ฏๅฆไธบไบคไบ’ๅผ็ปˆ็ซฏ๏ผŒๆ˜ฏๅˆ™่‡ชๅŠจๅผ•ๅฏผ่ฟ›ๅ…ฅ setup
39
- if (process.stdin.isTTY) {
40
- const readline = await import('readline');
41
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
42
- const answer = await new Promise<string>(resolve => {
43
- rl.question('Launch the configuration wizard now? [Y/n]: ', resolve);
44
- });
45
- rl.close();
46
-
47
- const yes = answer.trim().toLowerCase();
48
- if (yes === '' || yes === 'y' || yes === 'yes') {
49
- console.log('\n๐Ÿš€ Launching configuration wizard...\n');
50
- // ่ฐƒ็”จ setup ๅ‘ๅฏผ
51
- const pkgDir = path.resolve(import.meta.dirname, '..');
52
- execSync('bun run bin/imtoagent setup', {
53
- cwd: pkgDir,
54
- stdio: 'inherit',
55
- env: { ...process.env },
56
- });
57
- } else {
58
- console.log('\nRun "imtoagent setup" later to configure.');
59
- }
60
- } else {
61
- console.log(' Run "imtoagent setup" to start configuring');
62
- console.log(' Then run "imtoagent start" to start the gateway\n');
63
- }
64
- }
65
- } catch (e: any) {
66
- // Silently fail, do not affect installation
67
- if (e.message && !e.message.includes('readline')) {
68
- console.error(`[postinstall] Failed to display message: ${e.message}`);
69
- }
70
- }