@wu529778790/open-im 0.1.0
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/.env.example +16 -0
- package/LICENSE +21 -0
- package/README.md +51 -0
- package/dist/access/access-control.d.ts +5 -0
- package/dist/access/access-control.js +11 -0
- package/dist/adapters/claude-adapter.d.ts +7 -0
- package/dist/adapters/claude-adapter.js +17 -0
- package/dist/adapters/registry.d.ts +4 -0
- package/dist/adapters/registry.js +11 -0
- package/dist/adapters/tool-adapter.interface.d.ts +35 -0
- package/dist/adapters/tool-adapter.interface.js +4 -0
- package/dist/claude/cli-runner.d.ts +28 -0
- package/dist/claude/cli-runner.js +166 -0
- package/dist/claude/stream-parser.d.ts +17 -0
- package/dist/claude/stream-parser.js +50 -0
- package/dist/claude/types.d.ts +54 -0
- package/dist/claude/types.js +21 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +6 -0
- package/dist/commands/handler.d.ts +30 -0
- package/dist/commands/handler.js +122 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.js +88 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +15 -0
- package/dist/hook/permission-server.d.ts +4 -0
- package/dist/hook/permission-server.js +12 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +76 -0
- package/dist/logger.d.ts +16 -0
- package/dist/logger.js +65 -0
- package/dist/queue/request-queue.d.ts +6 -0
- package/dist/queue/request-queue.js +43 -0
- package/dist/sanitize.d.ts +1 -0
- package/dist/sanitize.js +11 -0
- package/dist/session/session-manager.d.ts +26 -0
- package/dist/session/session-manager.js +207 -0
- package/dist/setup.d.ts +5 -0
- package/dist/setup.js +97 -0
- package/dist/shared/active-chats.d.ts +4 -0
- package/dist/shared/active-chats.js +55 -0
- package/dist/shared/ai-task.d.ts +42 -0
- package/dist/shared/ai-task.js +167 -0
- package/dist/shared/message-dedup.d.ts +4 -0
- package/dist/shared/message-dedup.js +25 -0
- package/dist/shared/task-cleanup.d.ts +2 -0
- package/dist/shared/task-cleanup.js +19 -0
- package/dist/shared/types.d.ts +9 -0
- package/dist/shared/types.js +1 -0
- package/dist/shared/utils.d.ts +7 -0
- package/dist/shared/utils.js +72 -0
- package/dist/telegram/client.d.ts +6 -0
- package/dist/telegram/client.js +27 -0
- package/dist/telegram/event-handler.d.ts +8 -0
- package/dist/telegram/event-handler.js +174 -0
- package/dist/telegram/message-sender.d.ts +7 -0
- package/dist/telegram/message-sender.js +102 -0
- package/package.json +52 -0
package/.env.example
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Telegram Bot Token (from @BotFather)
|
|
2
|
+
TELEGRAM_BOT_TOKEN=
|
|
3
|
+
|
|
4
|
+
# Allowed user IDs (comma-separated, empty = allow all in dev)
|
|
5
|
+
ALLOWED_USER_IDS=
|
|
6
|
+
|
|
7
|
+
# AI tool: claude | codex | cursor
|
|
8
|
+
AI_COMMAND=claude
|
|
9
|
+
|
|
10
|
+
# Claude CLI (when AI_COMMAND=claude)
|
|
11
|
+
CLAUDE_CLI_PATH=claude
|
|
12
|
+
CLAUDE_WORK_DIR=.
|
|
13
|
+
CLAUDE_SKIP_PERMISSIONS=true
|
|
14
|
+
CLAUDE_TIMEOUT_MS=600000
|
|
15
|
+
CLAUDE_MODEL=
|
|
16
|
+
ALLOWED_BASE_DIRS=
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wu529778790
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# open-im
|
|
2
|
+
|
|
3
|
+
多平台 IM 桥接,支持多种 AI CLI 工具(Claude Code、Codex、Cursor 等)。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- **多 AI 工具**:通过 `AI_COMMAND` 配置切换 Claude / Codex / Cursor
|
|
8
|
+
- **流式输出**:节流更新,Telegram editMessage 实时展示
|
|
9
|
+
- **会话管理**:每用户独立 session,`/new` 重置
|
|
10
|
+
- **命令**:`/help` `/new` `/cd` `/pwd` `/status`
|
|
11
|
+
|
|
12
|
+
## 快速开始
|
|
13
|
+
|
|
14
|
+
### 快速开始
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm run build
|
|
18
|
+
npm run dev # 或 npm start
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
首次运行会进入交互式配置,按提示输入后自动启动服务。配置保存到 `~/.open-im/config.json`。
|
|
22
|
+
|
|
23
|
+
## 配置
|
|
24
|
+
|
|
25
|
+
| 变量 | 说明 |
|
|
26
|
+
|------|------|
|
|
27
|
+
| `TELEGRAM_BOT_TOKEN` | Telegram Bot Token |
|
|
28
|
+
| `ALLOWED_USER_IDS` | 白名单用户 ID(逗号分隔,空=所有人) |
|
|
29
|
+
| `AI_COMMAND` | `claude` \| `codex` \| `cursor`,默认 `claude` |
|
|
30
|
+
| `CLAUDE_CLI_PATH` | Claude CLI 路径,默认 `claude` |
|
|
31
|
+
| `CLAUDE_WORK_DIR` | 工作目录 |
|
|
32
|
+
| `CLAUDE_SKIP_PERMISSIONS` | 跳过权限确认,默认 `true` |
|
|
33
|
+
|
|
34
|
+
## 项目结构
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
src/
|
|
38
|
+
├── adapters/ # ToolAdapter 抽象与实现
|
|
39
|
+
│ ├── tool-adapter.interface.ts
|
|
40
|
+
│ ├── claude-adapter.ts
|
|
41
|
+
│ └── registry.ts
|
|
42
|
+
├── claude/ # Claude CLI 运行与解析
|
|
43
|
+
├── shared/ # ai-task、utils、types
|
|
44
|
+
├── telegram/ # Telegram 事件与消息
|
|
45
|
+
├── session/ # 会话管理
|
|
46
|
+
└── commands/ # 命令分发
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ToolAdapter, RunCallbacks, RunOptions, RunHandle } from './tool-adapter.interface.js';
|
|
2
|
+
export declare class ClaudeAdapter implements ToolAdapter {
|
|
3
|
+
private cliPath;
|
|
4
|
+
readonly toolId = "claude";
|
|
5
|
+
constructor(cliPath: string);
|
|
6
|
+
run(prompt: string, sessionId: string | undefined, workDir: string, callbacks: RunCallbacks, options?: RunOptions): RunHandle;
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { runClaude } from '../claude/cli-runner.js';
|
|
2
|
+
export class ClaudeAdapter {
|
|
3
|
+
cliPath;
|
|
4
|
+
toolId = 'claude';
|
|
5
|
+
constructor(cliPath) {
|
|
6
|
+
this.cliPath = cliPath;
|
|
7
|
+
}
|
|
8
|
+
run(prompt, sessionId, workDir, callbacks, options) {
|
|
9
|
+
return runClaude(this.cliPath, prompt, sessionId, workDir, callbacks, {
|
|
10
|
+
skipPermissions: options?.skipPermissions,
|
|
11
|
+
timeoutMs: options?.timeoutMs,
|
|
12
|
+
model: options?.model,
|
|
13
|
+
chatId: options?.chatId,
|
|
14
|
+
hookPort: options?.hookPort,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ClaudeAdapter } from './claude-adapter.js';
|
|
2
|
+
const adapters = new Map();
|
|
3
|
+
export function initAdapters(config) {
|
|
4
|
+
adapters.clear();
|
|
5
|
+
if (config.aiCommand === 'claude') {
|
|
6
|
+
adapters.set('claude', new ClaudeAdapter(config.claudeCliPath));
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function getAdapter(aiCommand) {
|
|
10
|
+
return adapters.get(aiCommand);
|
|
11
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolAdapter 接口 - 多 AI CLI 统一抽象
|
|
3
|
+
*/
|
|
4
|
+
export interface ParsedResult {
|
|
5
|
+
success: boolean;
|
|
6
|
+
result: string;
|
|
7
|
+
accumulated: string;
|
|
8
|
+
cost: number;
|
|
9
|
+
durationMs: number;
|
|
10
|
+
model?: string;
|
|
11
|
+
numTurns: number;
|
|
12
|
+
toolStats: Record<string, number>;
|
|
13
|
+
}
|
|
14
|
+
export interface RunCallbacks {
|
|
15
|
+
onText: (accumulated: string) => void;
|
|
16
|
+
onThinking?: (accumulated: string) => void;
|
|
17
|
+
onToolUse?: (toolName: string, toolInput?: Record<string, unknown>) => void;
|
|
18
|
+
onComplete: (result: ParsedResult) => void;
|
|
19
|
+
onError: (error: string) => void;
|
|
20
|
+
onSessionId?: (sessionId: string) => void;
|
|
21
|
+
}
|
|
22
|
+
export interface RunOptions {
|
|
23
|
+
skipPermissions?: boolean;
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
model?: string;
|
|
26
|
+
chatId?: string;
|
|
27
|
+
hookPort?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface RunHandle {
|
|
30
|
+
abort: () => void;
|
|
31
|
+
}
|
|
32
|
+
export interface ToolAdapter {
|
|
33
|
+
readonly toolId: string;
|
|
34
|
+
run(prompt: string, sessionId: string | undefined, workDir: string, callbacks: RunCallbacks, options?: RunOptions): RunHandle;
|
|
35
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface ClaudeRunCallbacks {
|
|
2
|
+
onText: (accumulated: string) => void;
|
|
3
|
+
onThinking?: (accumulated: string) => void;
|
|
4
|
+
onToolUse?: (toolName: string, toolInput?: Record<string, unknown>) => void;
|
|
5
|
+
onComplete: (result: {
|
|
6
|
+
success: boolean;
|
|
7
|
+
result: string;
|
|
8
|
+
accumulated: string;
|
|
9
|
+
cost: number;
|
|
10
|
+
durationMs: number;
|
|
11
|
+
model?: string;
|
|
12
|
+
numTurns: number;
|
|
13
|
+
toolStats: Record<string, number>;
|
|
14
|
+
}) => void;
|
|
15
|
+
onError: (error: string) => void;
|
|
16
|
+
onSessionId?: (sessionId: string) => void;
|
|
17
|
+
}
|
|
18
|
+
export interface ClaudeRunOptions {
|
|
19
|
+
skipPermissions?: boolean;
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
model?: string;
|
|
22
|
+
chatId?: string;
|
|
23
|
+
hookPort?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface ClaudeRunHandle {
|
|
26
|
+
abort: () => void;
|
|
27
|
+
}
|
|
28
|
+
export declare function runClaude(cliPath: string, prompt: string, sessionId: string | undefined, workDir: string, callbacks: ClaudeRunCallbacks, options?: ClaudeRunOptions): ClaudeRunHandle;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
import { parseStreamLine, extractTextDelta, extractThinkingDelta, extractResult } from './stream-parser.js';
|
|
4
|
+
import { isStreamInit, isContentBlockStart, isContentBlockDelta, isContentBlockStop } from './types.js';
|
|
5
|
+
import { createLogger } from '../logger.js';
|
|
6
|
+
const log = createLogger('CliRunner');
|
|
7
|
+
export function runClaude(cliPath, prompt, sessionId, workDir, callbacks, options) {
|
|
8
|
+
const args = ['-p', '--output-format', 'stream-json', '--verbose', '--include-partial-messages'];
|
|
9
|
+
if (options?.skipPermissions)
|
|
10
|
+
args.push('--dangerously-skip-permissions');
|
|
11
|
+
if (options?.model)
|
|
12
|
+
args.push('--model', options.model);
|
|
13
|
+
if (sessionId)
|
|
14
|
+
args.push('--resume', sessionId);
|
|
15
|
+
args.push('--', prompt);
|
|
16
|
+
const env = {};
|
|
17
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
18
|
+
if (v !== undefined)
|
|
19
|
+
env[k] = v;
|
|
20
|
+
}
|
|
21
|
+
if (options?.chatId)
|
|
22
|
+
env.CC_IM_CHAT_ID = options.chatId;
|
|
23
|
+
if (options?.hookPort)
|
|
24
|
+
env.CC_IM_HOOK_PORT = String(options.hookPort);
|
|
25
|
+
const child = spawn(cliPath, args, { cwd: workDir, stdio: ['ignore', 'pipe', 'pipe'], env });
|
|
26
|
+
log.debug(`Claude CLI spawned: pid=${child.pid}, cwd=${workDir}, session=${sessionId ?? 'new'}`);
|
|
27
|
+
let accumulated = '';
|
|
28
|
+
let accumulatedThinking = '';
|
|
29
|
+
let completed = false;
|
|
30
|
+
let model = '';
|
|
31
|
+
const toolStats = {};
|
|
32
|
+
const pendingToolInputs = new Map();
|
|
33
|
+
const MAX_TIMEOUT = 2_147_483_647;
|
|
34
|
+
const timeoutMs = options?.timeoutMs && options.timeoutMs > 0 ? Math.min(options.timeoutMs, MAX_TIMEOUT) : 0;
|
|
35
|
+
let timeoutHandle = null;
|
|
36
|
+
if (timeoutMs > 0) {
|
|
37
|
+
timeoutHandle = setTimeout(() => {
|
|
38
|
+
if (!completed && !child.killed) {
|
|
39
|
+
completed = true;
|
|
40
|
+
log.warn(`Claude CLI timeout after ${timeoutMs}ms, killing pid=${child.pid}`);
|
|
41
|
+
child.kill('SIGTERM');
|
|
42
|
+
callbacks.onError(`执行超时(${timeoutMs}ms),已终止进程`);
|
|
43
|
+
}
|
|
44
|
+
}, timeoutMs);
|
|
45
|
+
}
|
|
46
|
+
const rl = createInterface({ input: child.stdout });
|
|
47
|
+
rl.on('line', (line) => {
|
|
48
|
+
const event = parseStreamLine(line);
|
|
49
|
+
if (!event)
|
|
50
|
+
return;
|
|
51
|
+
if (isStreamInit(event)) {
|
|
52
|
+
model = event.model;
|
|
53
|
+
callbacks.onSessionId?.(event.session_id);
|
|
54
|
+
}
|
|
55
|
+
const delta = extractTextDelta(event);
|
|
56
|
+
if (delta) {
|
|
57
|
+
accumulated += delta.text;
|
|
58
|
+
callbacks.onText(accumulated);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const thinking = extractThinkingDelta(event);
|
|
62
|
+
if (thinking) {
|
|
63
|
+
accumulatedThinking += thinking.text;
|
|
64
|
+
callbacks.onThinking?.(accumulatedThinking);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (isContentBlockStart(event) && event.event.content_block?.type === 'tool_use') {
|
|
68
|
+
const name = event.event.content_block.name;
|
|
69
|
+
if (name)
|
|
70
|
+
pendingToolInputs.set(event.event.index, { name, json: '' });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (isContentBlockDelta(event) && event.event.delta?.type === 'input_json_delta') {
|
|
74
|
+
const pending = pendingToolInputs.get(event.event.index);
|
|
75
|
+
if (pending)
|
|
76
|
+
pending.json += event.event.delta.partial_json ?? '';
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (isContentBlockStop(event)) {
|
|
80
|
+
const pending = pendingToolInputs.get(event.event.index);
|
|
81
|
+
if (pending) {
|
|
82
|
+
toolStats[pending.name] = (toolStats[pending.name] || 0) + 1;
|
|
83
|
+
let input;
|
|
84
|
+
try {
|
|
85
|
+
input = JSON.parse(pending.json);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
/* empty */
|
|
89
|
+
}
|
|
90
|
+
callbacks.onToolUse?.(pending.name, input);
|
|
91
|
+
pendingToolInputs.delete(event.event.index);
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const result = extractResult(event);
|
|
96
|
+
if (result) {
|
|
97
|
+
completed = true;
|
|
98
|
+
if (timeoutHandle)
|
|
99
|
+
clearTimeout(timeoutHandle);
|
|
100
|
+
const fullResult = {
|
|
101
|
+
...result,
|
|
102
|
+
accumulated,
|
|
103
|
+
model,
|
|
104
|
+
toolStats,
|
|
105
|
+
};
|
|
106
|
+
if (!accumulated && fullResult.result)
|
|
107
|
+
accumulated = fullResult.result;
|
|
108
|
+
callbacks.onComplete(fullResult);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
let exitCode = null;
|
|
112
|
+
let rlClosed = false;
|
|
113
|
+
let childClosed = false;
|
|
114
|
+
const finalize = () => {
|
|
115
|
+
if (!rlClosed || !childClosed)
|
|
116
|
+
return;
|
|
117
|
+
if (timeoutHandle)
|
|
118
|
+
clearTimeout(timeoutHandle);
|
|
119
|
+
if (!completed) {
|
|
120
|
+
if (exitCode !== null && exitCode !== 0) {
|
|
121
|
+
callbacks.onError(`Claude CLI exited with code ${exitCode}`);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
callbacks.onComplete({
|
|
125
|
+
success: true,
|
|
126
|
+
result: accumulated,
|
|
127
|
+
accumulated,
|
|
128
|
+
cost: 0,
|
|
129
|
+
durationMs: 0,
|
|
130
|
+
model,
|
|
131
|
+
numTurns: 0,
|
|
132
|
+
toolStats,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
child.on('close', (code) => {
|
|
138
|
+
exitCode = code;
|
|
139
|
+
childClosed = true;
|
|
140
|
+
finalize();
|
|
141
|
+
});
|
|
142
|
+
rl.on('close', () => {
|
|
143
|
+
rlClosed = true;
|
|
144
|
+
finalize();
|
|
145
|
+
});
|
|
146
|
+
child.on('error', (err) => {
|
|
147
|
+
log.error(`Claude CLI error: ${err.message}`);
|
|
148
|
+
if (timeoutHandle)
|
|
149
|
+
clearTimeout(timeoutHandle);
|
|
150
|
+
if (!completed) {
|
|
151
|
+
completed = true;
|
|
152
|
+
callbacks.onError(`Failed to start Claude CLI: ${err.message}`);
|
|
153
|
+
}
|
|
154
|
+
childClosed = true;
|
|
155
|
+
finalize();
|
|
156
|
+
});
|
|
157
|
+
return {
|
|
158
|
+
abort: () => {
|
|
159
|
+
if (timeoutHandle)
|
|
160
|
+
clearTimeout(timeoutHandle);
|
|
161
|
+
rl.close();
|
|
162
|
+
if (!child.killed)
|
|
163
|
+
child.kill('SIGTERM');
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { StreamEvent } from './types.js';
|
|
2
|
+
export declare function parseStreamLine(line: string): StreamEvent | null;
|
|
3
|
+
export declare function extractTextDelta(event: StreamEvent): {
|
|
4
|
+
text: string;
|
|
5
|
+
} | null;
|
|
6
|
+
export declare function extractThinkingDelta(event: StreamEvent): {
|
|
7
|
+
text: string;
|
|
8
|
+
} | null;
|
|
9
|
+
export declare function extractResult(event: StreamEvent): {
|
|
10
|
+
success: boolean;
|
|
11
|
+
result: string;
|
|
12
|
+
accumulated: string;
|
|
13
|
+
cost: number;
|
|
14
|
+
durationMs: number;
|
|
15
|
+
numTurns: number;
|
|
16
|
+
toolStats: Record<string, number>;
|
|
17
|
+
} | null;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export function parseStreamLine(line) {
|
|
2
|
+
const trimmed = line.trim();
|
|
3
|
+
if (!trimmed)
|
|
4
|
+
return null;
|
|
5
|
+
try {
|
|
6
|
+
const parsed = JSON.parse(trimmed);
|
|
7
|
+
if (typeof parsed === 'object' && parsed !== null && 'type' in parsed) {
|
|
8
|
+
return parsed;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
/* ignore */
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
export function extractTextDelta(event) {
|
|
17
|
+
const e = event;
|
|
18
|
+
if (e.type === 'stream_event' &&
|
|
19
|
+
e.event?.type === 'content_block_delta' &&
|
|
20
|
+
e.event.delta?.type === 'text_delta' &&
|
|
21
|
+
e.event.delta.text) {
|
|
22
|
+
return { text: e.event.delta.text };
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
export function extractThinkingDelta(event) {
|
|
27
|
+
const e = event;
|
|
28
|
+
if (e.type === 'stream_event' &&
|
|
29
|
+
e.event?.type === 'content_block_delta' &&
|
|
30
|
+
e.event.delta?.type === 'thinking_delta' &&
|
|
31
|
+
e.event.delta.thinking) {
|
|
32
|
+
return { text: e.event.delta.thinking };
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
export function extractResult(event) {
|
|
37
|
+
if (event.type === 'result' && 'subtype' in event) {
|
|
38
|
+
const e = event;
|
|
39
|
+
return {
|
|
40
|
+
success: e.subtype === 'success',
|
|
41
|
+
result: e.result,
|
|
42
|
+
accumulated: '',
|
|
43
|
+
cost: e.total_cost_usd ?? 0,
|
|
44
|
+
durationMs: e.duration_ms ?? 0,
|
|
45
|
+
numTurns: e.num_turns ?? 0,
|
|
46
|
+
toolStats: {},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface StreamInit {
|
|
2
|
+
type: 'system';
|
|
3
|
+
subtype: 'init';
|
|
4
|
+
session_id: string;
|
|
5
|
+
model: string;
|
|
6
|
+
}
|
|
7
|
+
export interface StreamContentBlockDelta {
|
|
8
|
+
type: 'stream_event';
|
|
9
|
+
event: {
|
|
10
|
+
type: 'content_block_delta';
|
|
11
|
+
index: number;
|
|
12
|
+
delta: {
|
|
13
|
+
type: string;
|
|
14
|
+
text?: string;
|
|
15
|
+
thinking?: string;
|
|
16
|
+
partial_json?: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface StreamContentBlockStop {
|
|
21
|
+
type: 'stream_event';
|
|
22
|
+
event: {
|
|
23
|
+
type: 'content_block_stop';
|
|
24
|
+
index: number;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export interface StreamContentBlockStart {
|
|
28
|
+
type: 'stream_event';
|
|
29
|
+
event: {
|
|
30
|
+
type: 'content_block_start';
|
|
31
|
+
index: number;
|
|
32
|
+
content_block: {
|
|
33
|
+
type: string;
|
|
34
|
+
name?: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export interface StreamResult {
|
|
39
|
+
type: 'result';
|
|
40
|
+
subtype: 'success' | 'error';
|
|
41
|
+
result: string;
|
|
42
|
+
total_cost_usd: number;
|
|
43
|
+
duration_ms: number;
|
|
44
|
+
num_turns: number;
|
|
45
|
+
}
|
|
46
|
+
export type StreamEvent = StreamInit | StreamContentBlockDelta | StreamContentBlockStart | StreamContentBlockStop | StreamResult | {
|
|
47
|
+
type: string;
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
};
|
|
50
|
+
export declare function isStreamInit(e: StreamEvent): e is StreamInit;
|
|
51
|
+
export declare function isContentBlockDelta(e: StreamEvent): e is StreamContentBlockDelta;
|
|
52
|
+
export declare function isContentBlockStart(e: StreamEvent): e is StreamContentBlockStart;
|
|
53
|
+
export declare function isContentBlockStop(e: StreamEvent): e is StreamContentBlockStop;
|
|
54
|
+
export declare function isStreamResult(e: StreamEvent): e is StreamResult;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function isStreamInit(e) {
|
|
2
|
+
return e.type === 'system' && 'subtype' in e && e.subtype === 'init';
|
|
3
|
+
}
|
|
4
|
+
export function isContentBlockDelta(e) {
|
|
5
|
+
return (e.type === 'stream_event' &&
|
|
6
|
+
typeof e.event === 'object' &&
|
|
7
|
+
e.event.type === 'content_block_delta');
|
|
8
|
+
}
|
|
9
|
+
export function isContentBlockStart(e) {
|
|
10
|
+
return (e.type === 'stream_event' &&
|
|
11
|
+
typeof e.event === 'object' &&
|
|
12
|
+
e.event.type === 'content_block_start');
|
|
13
|
+
}
|
|
14
|
+
export function isContentBlockStop(e) {
|
|
15
|
+
return (e.type === 'stream_event' &&
|
|
16
|
+
typeof e.event === 'object' &&
|
|
17
|
+
e.event.type === 'content_block_stop');
|
|
18
|
+
}
|
|
19
|
+
export function isStreamResult(e) {
|
|
20
|
+
return e.type === 'result' && 'subtype' in e;
|
|
21
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Config } from '../config.js';
|
|
2
|
+
import type { SessionManager } from '../session/session-manager.js';
|
|
3
|
+
import type { RequestQueue } from '../queue/request-queue.js';
|
|
4
|
+
import type { ThreadContext, CostRecord } from '../shared/types.js';
|
|
5
|
+
export type { ThreadContext, CostRecord };
|
|
6
|
+
export interface MessageSender {
|
|
7
|
+
sendTextReply(chatId: string, text: string, threadCtx?: ThreadContext): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export interface CommandHandlerDeps {
|
|
10
|
+
config: Config;
|
|
11
|
+
sessionManager: SessionManager;
|
|
12
|
+
requestQueue: RequestQueue;
|
|
13
|
+
sender: MessageSender;
|
|
14
|
+
userCosts: Map<string, CostRecord>;
|
|
15
|
+
getRunningTasksSize: () => number;
|
|
16
|
+
}
|
|
17
|
+
export type ClaudeRequestHandler = (userId: string, chatId: string, prompt: string, workDir: string, convId?: string, threadCtx?: ThreadContext, replyToMessageId?: string) => Promise<void>;
|
|
18
|
+
export declare class CommandHandler {
|
|
19
|
+
private deps;
|
|
20
|
+
constructor(deps: CommandHandlerDeps);
|
|
21
|
+
dispatch(text: string, chatId: string, userId: string, platform: 'feishu' | 'telegram', handleClaudeRequest: ClaudeRequestHandler): Promise<boolean>;
|
|
22
|
+
private handleHelp;
|
|
23
|
+
private handleNew;
|
|
24
|
+
private handlePwd;
|
|
25
|
+
private handleStatus;
|
|
26
|
+
private handleCd;
|
|
27
|
+
private handleAllow;
|
|
28
|
+
private handleDeny;
|
|
29
|
+
private getClaudeVersion;
|
|
30
|
+
}
|