lark-remote 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/CHANGELOG.md +52 -0
- package/LICENSE +21 -0
- package/README.en.md +162 -0
- package/README.md +161 -0
- package/dist/binder.d.ts +57 -0
- package/dist/binder.js +105 -0
- package/dist/bridge/index.d.ts +338 -0
- package/dist/bridge/index.js +1281 -0
- package/dist/bridge/queue-manager.d.ts +277 -0
- package/dist/bridge/queue-manager.js +729 -0
- package/dist/card/bash-card-session.d.ts +78 -0
- package/dist/card/bash-card-session.js +174 -0
- package/dist/card/bash-renderer.d.ts +24 -0
- package/dist/card/bash-renderer.js +187 -0
- package/dist/card/card-budget.d.ts +31 -0
- package/dist/card/card-budget.js +280 -0
- package/dist/card/card-session.d.ts +83 -0
- package/dist/card/card-session.js +185 -0
- package/dist/card/card-shared.d.ts +28 -0
- package/dist/card/card-shared.js +99 -0
- package/dist/card/collapsible.d.ts +26 -0
- package/dist/card/collapsible.js +87 -0
- package/dist/card/run-card-session.d.ts +78 -0
- package/dist/card/run-card-session.js +165 -0
- package/dist/card/run-renderer.d.ts +66 -0
- package/dist/card/run-renderer.js +841 -0
- package/dist/card/run-state.d.ts +103 -0
- package/dist/card/run-state.js +313 -0
- package/dist/card/text-truncate.d.ts +56 -0
- package/dist/card/text-truncate.js +184 -0
- package/dist/card/time.d.ts +1 -0
- package/dist/card/time.js +14 -0
- package/dist/card/tool-render.d.ts +25 -0
- package/dist/card/tool-render.js +176 -0
- package/dist/cli.d.ts +28 -0
- package/dist/cli.js +89 -0
- package/dist/common/guards.d.ts +37 -0
- package/dist/common/guards.js +65 -0
- package/dist/config/codex-config.d.ts +113 -0
- package/dist/config/codex-config.js +428 -0
- package/dist/config/dir.d.ts +17 -0
- package/dist/config/dir.js +81 -0
- package/dist/config/index.d.ts +219 -0
- package/dist/config/index.js +388 -0
- package/dist/config/kimi-config.d.ts +33 -0
- package/dist/config/kimi-config.js +111 -0
- package/dist/config/opencode-config.d.ts +20 -0
- package/dist/config/opencode-config.js +124 -0
- package/dist/config/pi-config.d.ts +49 -0
- package/dist/config/pi-config.js +151 -0
- package/dist/config/wizard.d.ts +12 -0
- package/dist/config/wizard.js +84 -0
- package/dist/connector/file-limits.d.ts +7 -0
- package/dist/connector/file-limits.js +8 -0
- package/dist/connector/index.d.ts +86 -0
- package/dist/connector/index.js +340 -0
- package/dist/error-classification.d.ts +15 -0
- package/dist/error-classification.js +24 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +580 -0
- package/dist/instance-lock.d.ts +30 -0
- package/dist/instance-lock.js +184 -0
- package/dist/logger/index.d.ts +57 -0
- package/dist/logger/index.js +178 -0
- package/dist/order/index.d.ts +27 -0
- package/dist/order/index.js +90 -0
- package/dist/persistence/atomic-write.d.ts +10 -0
- package/dist/persistence/atomic-write.js +65 -0
- package/dist/persistence/load-json-file.d.ts +9 -0
- package/dist/persistence/load-json-file.js +23 -0
- package/dist/restart.d.ts +24 -0
- package/dist/restart.js +73 -0
- package/dist/router/card-helpers.d.ts +28 -0
- package/dist/router/card-helpers.js +65 -0
- package/dist/router/config/claude.d.ts +14 -0
- package/dist/router/config/claude.js +72 -0
- package/dist/router/config/codex.d.ts +22 -0
- package/dist/router/config/codex.js +100 -0
- package/dist/router/config/common/render.d.ts +16 -0
- package/dist/router/config/common/render.js +158 -0
- package/dist/router/config/index.d.ts +18 -0
- package/dist/router/config/index.js +38 -0
- package/dist/router/config/kimi.d.ts +21 -0
- package/dist/router/config/kimi.js +63 -0
- package/dist/router/config/opencode.d.ts +12 -0
- package/dist/router/config/opencode.js +44 -0
- package/dist/router/config/pi.d.ts +12 -0
- package/dist/router/config/pi.js +50 -0
- package/dist/router/config/types.d.ts +43 -0
- package/dist/router/config/types.js +8 -0
- package/dist/router/index.d.ts +318 -0
- package/dist/router/index.js +2715 -0
- package/dist/router/order-exec-dispatch.d.ts +30 -0
- package/dist/router/order-exec-dispatch.js +48 -0
- package/dist/router/utils.d.ts +44 -0
- package/dist/router/utils.js +116 -0
- package/dist/runner/bash/index.d.ts +4 -0
- package/dist/runner/bash/index.js +5 -0
- package/dist/runner/bash/runner.d.ts +55 -0
- package/dist/runner/bash/runner.js +213 -0
- package/dist/runner/claude/index.d.ts +4 -0
- package/dist/runner/claude/index.js +5 -0
- package/dist/runner/claude/runner.d.ts +29 -0
- package/dist/runner/claude/runner.js +83 -0
- package/dist/runner/codex/argv.d.ts +33 -0
- package/dist/runner/codex/argv.js +40 -0
- package/dist/runner/codex/index.d.ts +1 -0
- package/dist/runner/codex/index.js +2 -0
- package/dist/runner/codex/jsonl.d.ts +37 -0
- package/dist/runner/codex/jsonl.js +221 -0
- package/dist/runner/codex/runner.d.ts +65 -0
- package/dist/runner/codex/runner.js +124 -0
- package/dist/runner/common/exec-translator.d.ts +73 -0
- package/dist/runner/common/exec-translator.js +105 -0
- package/dist/runner/common/jsonl-stream.d.ts +39 -0
- package/dist/runner/common/jsonl-stream.js +203 -0
- package/dist/runner/common/process-stopper.d.ts +28 -0
- package/dist/runner/common/process-stopper.js +79 -0
- package/dist/runner/common/runner-utils.d.ts +21 -0
- package/dist/runner/common/runner-utils.js +65 -0
- package/dist/runner/common/spawn-heartbeat.d.ts +33 -0
- package/dist/runner/common/spawn-heartbeat.js +51 -0
- package/dist/runner/common/spawning-runner.d.ts +321 -0
- package/dist/runner/common/spawning-runner.js +634 -0
- package/dist/runner/common/usage.d.ts +31 -0
- package/dist/runner/common/usage.js +29 -0
- package/dist/runner/index.d.ts +19 -0
- package/dist/runner/index.js +24 -0
- package/dist/runner/kimi/index.d.ts +1 -0
- package/dist/runner/kimi/index.js +2 -0
- package/dist/runner/kimi/jsonl.d.ts +28 -0
- package/dist/runner/kimi/jsonl.js +143 -0
- package/dist/runner/kimi/runner.d.ts +50 -0
- package/dist/runner/kimi/runner.js +105 -0
- package/dist/runner/opencode/argv.d.ts +32 -0
- package/dist/runner/opencode/argv.js +35 -0
- package/dist/runner/opencode/index.d.ts +1 -0
- package/dist/runner/opencode/index.js +2 -0
- package/dist/runner/opencode/jsonl.d.ts +68 -0
- package/dist/runner/opencode/jsonl.js +295 -0
- package/dist/runner/opencode/runner.d.ts +63 -0
- package/dist/runner/opencode/runner.js +93 -0
- package/dist/runner/pi/index.d.ts +1 -0
- package/dist/runner/pi/index.js +2 -0
- package/dist/runner/pi/jsonl.d.ts +128 -0
- package/dist/runner/pi/jsonl.js +249 -0
- package/dist/runner/pi/runner.d.ts +37 -0
- package/dist/runner/pi/runner.js +76 -0
- package/dist/runner/registry.d.ts +54 -0
- package/dist/runner/registry.js +75 -0
- package/dist/runner/resolve-agent-choices.d.ts +7 -0
- package/dist/runner/resolve-agent-choices.js +76 -0
- package/dist/runner/sync-agent-choices.d.ts +6 -0
- package/dist/runner/sync-agent-choices.js +73 -0
- package/dist/runner/types.d.ts +250 -0
- package/dist/runner/types.js +8 -0
- package/dist/session/claude/index.d.ts +1 -0
- package/dist/session/claude/index.js +2 -0
- package/dist/session/claude/session-reader.d.ts +34 -0
- package/dist/session/claude/session-reader.js +46 -0
- package/dist/session/claude/sessions.d.ts +53 -0
- package/dist/session/claude/sessions.js +528 -0
- package/dist/session/codex/index.d.ts +1 -0
- package/dist/session/codex/index.js +2 -0
- package/dist/session/codex/rollout-reader.d.ts +88 -0
- package/dist/session/codex/rollout-reader.js +473 -0
- package/dist/session/codex/sessions.d.ts +21 -0
- package/dist/session/codex/sessions.js +40 -0
- package/dist/session/common/catch-up.d.ts +12 -0
- package/dist/session/common/catch-up.js +31 -0
- package/dist/session/common/constants.d.ts +11 -0
- package/dist/session/common/constants.js +12 -0
- package/dist/session/common/content-blocks.d.ts +37 -0
- package/dist/session/common/content-blocks.js +68 -0
- package/dist/session/common/jsonl.d.ts +77 -0
- package/dist/session/common/jsonl.js +351 -0
- package/dist/session/common/usage-accumulator.d.ts +58 -0
- package/dist/session/common/usage-accumulator.js +72 -0
- package/dist/session/index.d.ts +7 -0
- package/dist/session/index.js +11 -0
- package/dist/session/kimi/index.d.ts +1 -0
- package/dist/session/kimi/index.js +2 -0
- package/dist/session/kimi/sessions.d.ts +81 -0
- package/dist/session/kimi/sessions.js +527 -0
- package/dist/session/opencode/index.d.ts +1 -0
- package/dist/session/opencode/index.js +2 -0
- package/dist/session/opencode/sessions.d.ts +81 -0
- package/dist/session/opencode/sessions.js +388 -0
- package/dist/session/pi/index.d.ts +1 -0
- package/dist/session/pi/index.js +2 -0
- package/dist/session/pi/sessions.d.ts +27 -0
- package/dist/session/pi/sessions.js +399 -0
- package/dist/session/registry.d.ts +12 -0
- package/dist/session/registry.js +20 -0
- package/dist/session/session-store.d.ts +86 -0
- package/dist/session/session-store.js +450 -0
- package/dist/startup-contact.d.ts +18 -0
- package/dist/startup-contact.js +51 -0
- package/dist/workspace/index.d.ts +19 -0
- package/dist/workspace/index.js +53 -0
- package/package.json +93 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kimi config loader
|
|
3
|
+
*
|
|
4
|
+
* Dynamically reads available models from `kimi provider list --json`,
|
|
5
|
+
* provides dynamic dropdown options for /config card.
|
|
6
|
+
*/
|
|
7
|
+
import { execFileSync } from 'node:child_process';
|
|
8
|
+
import { getLogger } from '../logger/index.js';
|
|
9
|
+
/** Valid thinking effort values for kimi (Layer 1: Schema enum source) */
|
|
10
|
+
export const KIMI_THINKING_EFFORTS = ['low', 'high', 'max'];
|
|
11
|
+
/** Fallback efforts when all values are filtered out */
|
|
12
|
+
export const FALLBACK_EFFORTS = ['low', 'high', 'max'];
|
|
13
|
+
/** 清空缓存(测试与配置热更新用,与 codex/opencode 的 invalidate 对齐) */
|
|
14
|
+
export function invalidateKimiConfigCache() {
|
|
15
|
+
kimiConfigCache = null;
|
|
16
|
+
}
|
|
17
|
+
/** Cache for loadKimiConfig to avoid repeated sync calls */
|
|
18
|
+
let kimiConfigCache = null;
|
|
19
|
+
const KIMI_CONFIG_CACHE_TTL_MS = 60000; // 1 minute TTL
|
|
20
|
+
/**
|
|
21
|
+
* Load Kimi models from `kimi provider list --json`.
|
|
22
|
+
* Falls back to defaults if the command fails.
|
|
23
|
+
* Uses caching to avoid repeated sync calls.
|
|
24
|
+
*/
|
|
25
|
+
export function loadKimiConfig() {
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
// Return cached result if still valid
|
|
28
|
+
if (kimiConfigCache && now - kimiConfigCache.timestamp < KIMI_CONFIG_CACHE_TTL_MS) {
|
|
29
|
+
return kimiConfigCache.result;
|
|
30
|
+
}
|
|
31
|
+
const logger = getLogger();
|
|
32
|
+
try {
|
|
33
|
+
const output = execFileSync('kimi', ['provider', 'list', '--json'], {
|
|
34
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
35
|
+
timeout: 10000,
|
|
36
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
37
|
+
});
|
|
38
|
+
const data = JSON.parse(output.toString());
|
|
39
|
+
if (!data.models || Object.keys(data.models).length === 0) {
|
|
40
|
+
logger.warn('[kimi-config] no models found in provider list, using defaults');
|
|
41
|
+
return buildDefaultResult();
|
|
42
|
+
}
|
|
43
|
+
const modelOptions = [];
|
|
44
|
+
const modelDisplayNames = {};
|
|
45
|
+
const modelEfforts = {};
|
|
46
|
+
const modelDefaultEfforts = {};
|
|
47
|
+
for (const [modelId, modelInfo] of Object.entries(data.models)) {
|
|
48
|
+
modelOptions.push(modelId);
|
|
49
|
+
modelDisplayNames[modelId] = modelInfo.displayName;
|
|
50
|
+
// Layer 2: Filter effort values to only include valid ones from KIMI_THINKING_EFFORTS
|
|
51
|
+
const efforts = modelInfo.supportEfforts ?? [...FALLBACK_EFFORTS];
|
|
52
|
+
const filteredEfforts = efforts.filter((e) => KIMI_THINKING_EFFORTS.includes(e));
|
|
53
|
+
// Fallback to default if all filtered out
|
|
54
|
+
modelEfforts[modelId] = filteredEfforts.length > 0 ? filteredEfforts : [...FALLBACK_EFFORTS];
|
|
55
|
+
// Layer 2: Validate default effort, fallback to 'max' if invalid
|
|
56
|
+
const def = modelInfo.defaultEffort ?? 'max';
|
|
57
|
+
modelDefaultEfforts[modelId] = KIMI_THINKING_EFFORTS.includes(def)
|
|
58
|
+
? def
|
|
59
|
+
: 'max';
|
|
60
|
+
}
|
|
61
|
+
// Find current default model from the first provider
|
|
62
|
+
let currentModel = 'kimi-code/k3';
|
|
63
|
+
if (data.models['kimi-code/k3']) {
|
|
64
|
+
currentModel = 'kimi-code/k3';
|
|
65
|
+
}
|
|
66
|
+
else if (modelOptions.length > 0) {
|
|
67
|
+
currentModel = modelOptions[0];
|
|
68
|
+
}
|
|
69
|
+
const result = {
|
|
70
|
+
currentModel,
|
|
71
|
+
modelOptions,
|
|
72
|
+
modelDisplayNames,
|
|
73
|
+
modelEfforts,
|
|
74
|
+
modelDefaultEfforts,
|
|
75
|
+
};
|
|
76
|
+
kimiConfigCache = { result, timestamp: Date.now() };
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
logger.warn(`[kimi-config] failed to load models: ${err.message}, using defaults`);
|
|
81
|
+
const result = buildDefaultResult();
|
|
82
|
+
kimiConfigCache = { result, timestamp: Date.now() };
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function buildDefaultResult() {
|
|
87
|
+
return {
|
|
88
|
+
currentModel: 'kimi-code/k3',
|
|
89
|
+
modelOptions: [
|
|
90
|
+
'kimi-code/k3',
|
|
91
|
+
'kimi-code/kimi-for-coding',
|
|
92
|
+
'kimi-code/kimi-for-coding-highspeed',
|
|
93
|
+
],
|
|
94
|
+
modelDisplayNames: {
|
|
95
|
+
'kimi-code/k3': 'K3',
|
|
96
|
+
'kimi-code/kimi-for-coding': 'K2.7 Coding',
|
|
97
|
+
'kimi-code/kimi-for-coding-highspeed': 'K2.7 Coding Highspeed',
|
|
98
|
+
},
|
|
99
|
+
modelEfforts: {
|
|
100
|
+
'kimi-code/k3': ['low', 'high', 'max'],
|
|
101
|
+
'kimi-code/kimi-for-coding': ['low', 'high', 'max'],
|
|
102
|
+
'kimi-code/kimi-for-coding-highspeed': ['low', 'high', 'max'],
|
|
103
|
+
},
|
|
104
|
+
modelDefaultEfforts: {
|
|
105
|
+
'kimi-code/k3': 'max',
|
|
106
|
+
'kimi-code/kimi-for-coding': 'max',
|
|
107
|
+
'kimi-code/kimi-for-coding-highspeed': 'max',
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=kimi-config.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode 配置读取工具
|
|
3
|
+
*
|
|
4
|
+
* 从 opencode models --verbose 命令获取 provider 和 model 信息
|
|
5
|
+
*/
|
|
6
|
+
interface OpencodeConfigResult {
|
|
7
|
+
/** provider 名称列表 */
|
|
8
|
+
providerNames: string[];
|
|
9
|
+
/** 根据 provider 获取可用模型列表(返回纯 model ID,不带 provider 前缀) */
|
|
10
|
+
modelOptions: (provider?: string) => string[];
|
|
11
|
+
}
|
|
12
|
+
/** 清空缓存(测试与配置热更新用,与 codex-config 的 invalidateCodexBundledCache 对齐) */
|
|
13
|
+
export declare function invalidateOpencodeConfigCache(): void;
|
|
14
|
+
/**
|
|
15
|
+
* 加载 opencode 配置,执行 opencode models --verbose 并解析
|
|
16
|
+
*
|
|
17
|
+
* @returns provider 列表和 model 查询接口
|
|
18
|
+
*/
|
|
19
|
+
export declare function loadOpencodeConfig(): OpencodeConfigResult;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode 配置读取工具
|
|
3
|
+
*
|
|
4
|
+
* 从 opencode models --verbose 命令获取 provider 和 model 信息
|
|
5
|
+
*/
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
7
|
+
import { getLogger } from '../logger/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* 内置的 fallback 列表
|
|
10
|
+
* 当 opencode 命令失败时使用
|
|
11
|
+
* 格式:provider/model(内部用,modelOptions 返回时会去掉 provider 前缀)
|
|
12
|
+
*/
|
|
13
|
+
const FALLBACK_PROVIDERS = [
|
|
14
|
+
'opencode',
|
|
15
|
+
'deepseek',
|
|
16
|
+
'minimax-cn-coding-plan',
|
|
17
|
+
'myprovider',
|
|
18
|
+
'volcengine-plan',
|
|
19
|
+
];
|
|
20
|
+
const FALLBACK_MODELS = {
|
|
21
|
+
opencode: ['big-pickle'],
|
|
22
|
+
deepseek: ['deepseek-chat'],
|
|
23
|
+
'minimax-cn-coding-plan': ['MiniMax-M2.5'],
|
|
24
|
+
};
|
|
25
|
+
/** 正缓存 TTL:opencode models 列表短时间不变,1 分钟(与 kimi-config 对齐) */
|
|
26
|
+
const OPENCODE_CONFIG_CACHE_TTL_MS = 60_000;
|
|
27
|
+
/**
|
|
28
|
+
* 失败负缓存 TTL:命令失败后 30s 内直接用 fallback,避免每次 /config 卡片构建
|
|
29
|
+
* 都同步阻塞事件循环最长 10s(§P1-8,与 codex-config 负缓存对齐)。
|
|
30
|
+
*/
|
|
31
|
+
const OPENCODE_NEGATIVE_CACHE_TTL_MS = 30_000;
|
|
32
|
+
let opencodeConfigCache = null;
|
|
33
|
+
let opencodeConfigFailedAt = null;
|
|
34
|
+
/** 清空缓存(测试与配置热更新用,与 codex-config 的 invalidateCodexBundledCache 对齐) */
|
|
35
|
+
export function invalidateOpencodeConfigCache() {
|
|
36
|
+
opencodeConfigCache = null;
|
|
37
|
+
opencodeConfigFailedAt = null;
|
|
38
|
+
}
|
|
39
|
+
function buildFallbackResult() {
|
|
40
|
+
return {
|
|
41
|
+
providerNames: FALLBACK_PROVIDERS,
|
|
42
|
+
modelOptions: (provider) => {
|
|
43
|
+
if (!provider) {
|
|
44
|
+
// 无 provider 时返回所有 model ID(去重)
|
|
45
|
+
const allModels = new Set();
|
|
46
|
+
for (const models of Object.values(FALLBACK_MODELS)) {
|
|
47
|
+
for (const m of models) {
|
|
48
|
+
allModels.add(m);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return Array.from(allModels).sort();
|
|
52
|
+
}
|
|
53
|
+
return FALLBACK_MODELS[provider] ?? [];
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 加载 opencode 配置,执行 opencode models --verbose 并解析
|
|
59
|
+
*
|
|
60
|
+
* @returns provider 列表和 model 查询接口
|
|
61
|
+
*/
|
|
62
|
+
export function loadOpencodeConfig() {
|
|
63
|
+
const now = Date.now();
|
|
64
|
+
// 正缓存命中:TTL 内不重复 execSync(P1-8)
|
|
65
|
+
if (opencodeConfigCache && now - opencodeConfigCache.timestamp < OPENCODE_CONFIG_CACHE_TTL_MS) {
|
|
66
|
+
return opencodeConfigCache.result;
|
|
67
|
+
}
|
|
68
|
+
// 失败负缓存:命令最近失败过,TTL 内直接用 fallback,不再同步阻塞
|
|
69
|
+
if (opencodeConfigFailedAt !== null &&
|
|
70
|
+
now - opencodeConfigFailedAt < OPENCODE_NEGATIVE_CACHE_TTL_MS) {
|
|
71
|
+
return buildFallbackResult();
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
// 执行 opencode models --verbose 获取所有模型信息
|
|
75
|
+
const output = execSync('opencode models --verbose', {
|
|
76
|
+
encoding: 'utf-8',
|
|
77
|
+
// P1-8:30s → 10s,与 codex/kimi 对齐;maxBuffer 防模型清单 ENOBUFS 隐性截断
|
|
78
|
+
timeout: 10000,
|
|
79
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
80
|
+
});
|
|
81
|
+
// 按 provider 分组收集模型(只存 model ID,不存 provider/model 完整格式)
|
|
82
|
+
const providerModels = new Map();
|
|
83
|
+
// 解析输出:每个模型以 provider/model 格式开头,后面跟着 JSON 块
|
|
84
|
+
const lines = output.split('\n');
|
|
85
|
+
for (const line of lines) {
|
|
86
|
+
// 检测模型头如 "opencode/big-pickle" 或 "deepseek/deepseek-chat"
|
|
87
|
+
const match = line.match(/^([a-zA-Z0-9_-]+)\/([a-zA-Z0-9_.-]+)$/);
|
|
88
|
+
if (match) {
|
|
89
|
+
const providerID = match[1];
|
|
90
|
+
const modelID = match[2];
|
|
91
|
+
if (!providerModels.has(providerID)) {
|
|
92
|
+
providerModels.set(providerID, []);
|
|
93
|
+
}
|
|
94
|
+
providerModels.get(providerID).push(modelID);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const providerNames = Array.from(providerModels.keys()).sort();
|
|
98
|
+
const modelOptions = (provider) => {
|
|
99
|
+
if (!provider) {
|
|
100
|
+
// 无 provider 时返回所有 model ID(去重)
|
|
101
|
+
const allModels = new Set();
|
|
102
|
+
for (const models of providerModels.values()) {
|
|
103
|
+
for (const m of models) {
|
|
104
|
+
allModels.add(m);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return Array.from(allModels).sort();
|
|
108
|
+
}
|
|
109
|
+
return providerModels.get(provider) ?? [];
|
|
110
|
+
};
|
|
111
|
+
const result = { providerNames, modelOptions };
|
|
112
|
+
opencodeConfigCache = { result, timestamp: now };
|
|
113
|
+
opencodeConfigFailedAt = null;
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
// 如果命令失败,使用 fallback
|
|
118
|
+
const logger = getLogger();
|
|
119
|
+
logger.warn(`[opencode-config] failed to load opencode models: ${error}`);
|
|
120
|
+
opencodeConfigFailedAt = now;
|
|
121
|
+
return buildFallbackResult();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=opencode-config.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi 配置读取工具
|
|
3
|
+
*
|
|
4
|
+
* 从 ~/.pi/agent/models.json 和 auth.json 读取 provider 和 model 信息
|
|
5
|
+
*/
|
|
6
|
+
interface PiProviderInfo {
|
|
7
|
+
/** Provider 名称 */
|
|
8
|
+
name: string;
|
|
9
|
+
/** API 端点(可选) */
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
/** API 类型 */
|
|
12
|
+
api?: string;
|
|
13
|
+
/** 该 provider 下的模型列表 */
|
|
14
|
+
models: string[];
|
|
15
|
+
/** 是否有认证信息 */
|
|
16
|
+
isAuthenticated: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** loadPiConfig() 的返回类型,包含 provider 和 model 的聚合信息 */
|
|
19
|
+
export interface PiConfigResult {
|
|
20
|
+
/** provider 名称列表 */
|
|
21
|
+
providerNames: string[];
|
|
22
|
+
/** 获取指定 provider 的 model 选项 */
|
|
23
|
+
modelOptions: (provider?: string) => string[];
|
|
24
|
+
}
|
|
25
|
+
/** @internal Exported for testing only — verify env var is respected. */
|
|
26
|
+
export declare function _getModelsFilePath(): string;
|
|
27
|
+
/** @internal Exported for testing only. */
|
|
28
|
+
export declare function _getAuthFilePath(): string;
|
|
29
|
+
/**
|
|
30
|
+
* 读取 pi 的 provider 和 model 配置
|
|
31
|
+
*
|
|
32
|
+
* @returns provider 列表,每个 provider 包含名称、模型列表和认证状态
|
|
33
|
+
*/
|
|
34
|
+
export declare function loadPiProviderConfig(): PiProviderInfo[];
|
|
35
|
+
/**
|
|
36
|
+
* 聚合加载 pi 配置,只读一次文件,返回 provider 和 model 的查询接口
|
|
37
|
+
*
|
|
38
|
+
* 用于 buildConfigCard 等需要同时获取 provider 和 model 的场景,
|
|
39
|
+
* 避免重复 I/O。
|
|
40
|
+
*/
|
|
41
|
+
export declare function loadPiConfig(): PiConfigResult;
|
|
42
|
+
/**
|
|
43
|
+
* 获取可用的 model 列表(纯 model ID,不带 provider 前缀)
|
|
44
|
+
*
|
|
45
|
+
* @param provider 可选,指定 provider 时只返回该 provider 的模型
|
|
46
|
+
* @returns model 名称数组(纯 modelId)
|
|
47
|
+
*/
|
|
48
|
+
export declare function getPiModelOptions(provider?: string): string[];
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi 配置读取工具
|
|
3
|
+
*
|
|
4
|
+
* 从 ~/.pi/agent/models.json 和 auth.json 读取 provider 和 model 信息
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
/**
|
|
10
|
+
* 解析 pi 配置目录。优先使用 PI_CONFIG_DIR 环境变量(测试隔离用),
|
|
11
|
+
* 默认 ~/.pi/agent。每次调用时动态读取环境变量,避免模块级缓存导致
|
|
12
|
+
* singleFork 模式下测试设置 env 过晚(与 CLAUDE_SETTINGS_PATH 模式类似,
|
|
13
|
+
* 但 pi-config 的常量原本在模块级求值,改为惰性求值以支持运行时切换)。
|
|
14
|
+
*/
|
|
15
|
+
function getPiConfigDir() {
|
|
16
|
+
return process.env.PI_CONFIG_DIR ?? path.join(os.homedir(), '.pi', 'agent');
|
|
17
|
+
}
|
|
18
|
+
/** @internal Exported for testing only — verify env var is respected. */
|
|
19
|
+
export function _getModelsFilePath() {
|
|
20
|
+
return path.join(getPiConfigDir(), 'models.json');
|
|
21
|
+
}
|
|
22
|
+
/** @internal Exported for testing only. */
|
|
23
|
+
export function _getAuthFilePath() {
|
|
24
|
+
return path.join(getPiConfigDir(), 'auth.json');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 内置的 fallback 列表(provider 和 model 同源)
|
|
28
|
+
* 当配置文件不存在时使用
|
|
29
|
+
*
|
|
30
|
+
* 语义约束:每个 provider fallback 都必须在此有至少 1 个 model,
|
|
31
|
+
* 每个 model 中出现的 provider 都必须在 provider fallback 中。
|
|
32
|
+
*/
|
|
33
|
+
const FALLBACK_ENTRIES = [
|
|
34
|
+
{ modelId: 'glm-5.2', provider: 'Volcano' },
|
|
35
|
+
{ modelId: 'glm-5.1', provider: 'lt' },
|
|
36
|
+
{ modelId: 'claude-sonnet-4-20250514', provider: 'anthropic' },
|
|
37
|
+
{ modelId: 'gpt-4o', provider: 'openai' },
|
|
38
|
+
{ modelId: 'gemini-2.5-pro', provider: 'google' },
|
|
39
|
+
{ modelId: 'deepseek-chat', provider: 'deepseek' },
|
|
40
|
+
{ modelId: 'llama3', provider: 'ollama' },
|
|
41
|
+
];
|
|
42
|
+
function getPiProviderFallback() {
|
|
43
|
+
return [...new Set(FALLBACK_ENTRIES.map((e) => e.provider))];
|
|
44
|
+
}
|
|
45
|
+
function getPiModelFallback(provider) {
|
|
46
|
+
// 方案一:返回纯 model ID,不带 provider 后缀
|
|
47
|
+
const all = FALLBACK_ENTRIES.map((e) => e.modelId);
|
|
48
|
+
if (!provider)
|
|
49
|
+
return all;
|
|
50
|
+
return all.filter((m) => {
|
|
51
|
+
const entry = FALLBACK_ENTRIES.find((e) => e.modelId === m);
|
|
52
|
+
return entry?.provider === provider;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 读取 pi 的 provider 和 model 配置
|
|
57
|
+
*
|
|
58
|
+
* @returns provider 列表,每个 provider 包含名称、模型列表和认证状态
|
|
59
|
+
*/
|
|
60
|
+
export function loadPiProviderConfig() {
|
|
61
|
+
const results = new Map();
|
|
62
|
+
const modelsFile = _getModelsFilePath();
|
|
63
|
+
const authFile = _getAuthFilePath();
|
|
64
|
+
// 1. 读取 models.json
|
|
65
|
+
let modelsData = {};
|
|
66
|
+
if (fs.existsSync(modelsFile)) {
|
|
67
|
+
try {
|
|
68
|
+
modelsData = JSON.parse(fs.readFileSync(modelsFile, 'utf-8'));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// 文件格式错误,跳过
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// 2. 读取 auth.json
|
|
75
|
+
let authData = {};
|
|
76
|
+
if (fs.existsSync(authFile)) {
|
|
77
|
+
try {
|
|
78
|
+
authData = JSON.parse(fs.readFileSync(authFile, 'utf-8')) ?? {};
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// 文件格式错误,跳过
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// 3. 从 models.json 提取 providers
|
|
85
|
+
const providers = modelsData.providers ?? {};
|
|
86
|
+
for (const [name, config] of Object.entries(providers)) {
|
|
87
|
+
const cfg = config;
|
|
88
|
+
results.set(name, {
|
|
89
|
+
name,
|
|
90
|
+
baseUrl: cfg.baseUrl,
|
|
91
|
+
api: cfg.api,
|
|
92
|
+
models: (cfg.models ?? []).map((m) => m.id),
|
|
93
|
+
isAuthenticated: !!authData[name],
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// 4. 对于 auth.json 中有凭据但未在 models.json 中定义的 provider
|
|
97
|
+
// 添加为占位项
|
|
98
|
+
for (const name of Object.keys(authData)) {
|
|
99
|
+
if (!results.has(name)) {
|
|
100
|
+
results.set(name, {
|
|
101
|
+
name,
|
|
102
|
+
models: [],
|
|
103
|
+
isAuthenticated: true,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return Array.from(results.values());
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 聚合加载 pi 配置,只读一次文件,返回 provider 和 model 的查询接口
|
|
111
|
+
*
|
|
112
|
+
* 用于 buildConfigCard 等需要同时获取 provider 和 model 的场景,
|
|
113
|
+
* 避免重复 I/O。
|
|
114
|
+
*/
|
|
115
|
+
export function loadPiConfig() {
|
|
116
|
+
const config = loadPiProviderConfig();
|
|
117
|
+
if (config.length === 0) {
|
|
118
|
+
return {
|
|
119
|
+
providerNames: getPiProviderFallback(),
|
|
120
|
+
modelOptions: (provider) => getPiModelFallback(provider),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const providerNames = config.map((p) => p.name);
|
|
124
|
+
const modelOptions = (provider) => {
|
|
125
|
+
const providers = provider ? config.filter((p) => p.name === provider) : config;
|
|
126
|
+
const models = [];
|
|
127
|
+
for (const p of providers) {
|
|
128
|
+
if (p.models.length > 0) {
|
|
129
|
+
for (const modelId of p.models) {
|
|
130
|
+
// 方案一:返回纯 model ID,不带 provider 后缀
|
|
131
|
+
models.push(modelId);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (models.length === 0) {
|
|
136
|
+
return getPiModelFallback(provider);
|
|
137
|
+
}
|
|
138
|
+
return models;
|
|
139
|
+
};
|
|
140
|
+
return { providerNames, modelOptions };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 获取可用的 model 列表(纯 model ID,不带 provider 前缀)
|
|
144
|
+
*
|
|
145
|
+
* @param provider 可选,指定 provider 时只返回该 provider 的模型
|
|
146
|
+
* @returns model 名称数组(纯 modelId)
|
|
147
|
+
*/
|
|
148
|
+
export function getPiModelOptions(provider) {
|
|
149
|
+
return loadPiConfig().modelOptions(provider);
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=pi-config.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure a usable config exists before `loadConfig` is called.
|
|
3
|
+
*
|
|
4
|
+
* - If credentials are already present, do nothing.
|
|
5
|
+
* - In an interactive terminal with missing credentials, run the QR-code
|
|
6
|
+
* registration wizard and write the resulting credentials back to the
|
|
7
|
+
* config file so startup can proceed.
|
|
8
|
+
* - In a non-interactive context, do nothing here; `loadConfig` will then
|
|
9
|
+
* generate its template and exit with instructions (preserving the
|
|
10
|
+
* original behavior for tests and headless setups).
|
|
11
|
+
*/
|
|
12
|
+
export declare function ensureConfig(configPath: string): Promise<void>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { registerApp } from '@larksuite/channel';
|
|
2
|
+
import qrcode from 'qrcode-terminal';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import YAML from 'yaml';
|
|
6
|
+
import { AppConfigSchema } from './index.js';
|
|
7
|
+
/**
|
|
8
|
+
* Run the QR-code registration wizard. The user scans the QR with their
|
|
9
|
+
* Feishu/Lark app to create a new application; the SDK polls until the
|
|
10
|
+
* creation completes and returns the credentials.
|
|
11
|
+
*/
|
|
12
|
+
async function runRegistrationWizard() {
|
|
13
|
+
console.log('\n[lark-remote] 未检测到飞书应用配置,进入扫码创建向导。\n');
|
|
14
|
+
const result = await registerApp({
|
|
15
|
+
source: 'lark-remote',
|
|
16
|
+
onQRCodeReady: (info) => {
|
|
17
|
+
console.log('请用飞书 App 扫描以下二维码完成应用创建:\n');
|
|
18
|
+
qrcode.generate(info.url, { small: true });
|
|
19
|
+
const mins = Math.max(1, Math.round(info.expireIn / 60));
|
|
20
|
+
console.log(`\n二维码有效期:约 ${mins} 分钟`);
|
|
21
|
+
console.log(`也可以直接在浏览器打开:${info.url}\n`);
|
|
22
|
+
},
|
|
23
|
+
onStatusChange: (info) => {
|
|
24
|
+
if (info.status === 'domain_switched') {
|
|
25
|
+
console.log('识别到国际版租户,已切换到 larksuite.com 域名。');
|
|
26
|
+
}
|
|
27
|
+
else if (info.status === 'slow_down') {
|
|
28
|
+
console.log('轮询速度过快,已自动降速。');
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
console.log('\n✓ 应用创建成功');
|
|
33
|
+
console.log(` App ID: ${result.client_id}`);
|
|
34
|
+
if (result.user_info?.tenant_brand) {
|
|
35
|
+
console.log(` Tenant: ${result.user_info.tenant_brand}`);
|
|
36
|
+
}
|
|
37
|
+
console.log('');
|
|
38
|
+
return { appId: result.client_id, appSecret: result.client_secret };
|
|
39
|
+
}
|
|
40
|
+
function isInteractiveTerminal() {
|
|
41
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
42
|
+
}
|
|
43
|
+
/** True when the config file already has non-empty feishu credentials. */
|
|
44
|
+
function hasFeishuCredentials(configPath) {
|
|
45
|
+
if (!fs.existsSync(configPath))
|
|
46
|
+
return false;
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = YAML.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const feishu = parsed?.feishu;
|
|
55
|
+
return Boolean(feishu && feishu.appId && feishu.appSecret);
|
|
56
|
+
}
|
|
57
|
+
/** Persist a full config file seeded with the given feishu credentials. */
|
|
58
|
+
function writeConfigWithCredentials(configPath, appId, appSecret) {
|
|
59
|
+
const dir = path.dirname(configPath);
|
|
60
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
61
|
+
const config = AppConfigSchema.parse({ feishu: { appId, appSecret } });
|
|
62
|
+
fs.writeFileSync(configPath, YAML.stringify(config), 'utf-8');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Ensure a usable config exists before `loadConfig` is called.
|
|
66
|
+
*
|
|
67
|
+
* - If credentials are already present, do nothing.
|
|
68
|
+
* - In an interactive terminal with missing credentials, run the QR-code
|
|
69
|
+
* registration wizard and write the resulting credentials back to the
|
|
70
|
+
* config file so startup can proceed.
|
|
71
|
+
* - In a non-interactive context, do nothing here; `loadConfig` will then
|
|
72
|
+
* generate its template and exit with instructions (preserving the
|
|
73
|
+
* original behavior for tests and headless setups).
|
|
74
|
+
*/
|
|
75
|
+
export async function ensureConfig(configPath) {
|
|
76
|
+
if (hasFeishuCredentials(configPath))
|
|
77
|
+
return;
|
|
78
|
+
if (!isInteractiveTerminal())
|
|
79
|
+
return;
|
|
80
|
+
const { appId, appSecret } = await runRegistrationWizard();
|
|
81
|
+
writeConfigWithCredentials(configPath, appId, appSecret);
|
|
82
|
+
console.log(`[lark-remote] 凭证已写入 ${configPath},继续启动...\n`);
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=wizard.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File upload size limit constant.
|
|
3
|
+
*
|
|
4
|
+
* Aligned with Feishu im/v1/files API limit (30MB for file_type=stream).
|
|
5
|
+
* Single source of truth — router and connector both import from here.
|
|
6
|
+
*/
|
|
7
|
+
export const MAX_FILE_UPLOAD_SIZE = 30 * 1024 * 1024; // 30MB
|
|
8
|
+
//# sourceMappingURL=file-limits.js.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type CardActionEvent, type CardActionResponse, type CardStreamProducer } from '@larksuite/channel';
|
|
2
|
+
import type { AppConfig } from '../config/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* dedup 缓存 TTL(毫秒)。控制飞书事件去重窗口,全局作用于 message + cardAction。
|
|
5
|
+
*
|
|
6
|
+
* 必须远小于用户连击间隔,否则同一按钮的连续点击会被当成"重复事件"丢弃:
|
|
7
|
+
* - SDK 的 cardAction dedup eventId = `card:{messageId}:{operator.openId}:{actionId}`
|
|
8
|
+
* - actionId = `tag|name|option|JSON.stringify(value)`,不含时间戳/事件序号
|
|
9
|
+
* - config 卡片原地更新(updateCardInPlace),用户在同一张卡上连点 toggle:
|
|
10
|
+
* messageId / operator / value 三段都相同 → eventId 完全相同 → 第二次点击被
|
|
11
|
+
* seenCache drop,toggle 不可逆("显示工具结果"等开关第二次点击静默失效)。
|
|
12
|
+
*
|
|
13
|
+
* SDK 默认 12h 太长;60s 仍会误伤连击。300ms 挡飞书瞬时重投递(<100ms 级),
|
|
14
|
+
* 放过用户连击(慢点两次通常 >500ms)。代价:削弱 message 秒级重投递防护——
|
|
15
|
+
* 飞书 WS 正常连接不重发,重连补发延迟常 >60s 本就挡不住,串行队列(§9.6)
|
|
16
|
+
* 还兜底防并发,影响可接受。
|
|
17
|
+
*
|
|
18
|
+
* 此 bug 无法在 stub connector 测试中复现(测试绕过 SDK safety 层)。
|
|
19
|
+
* 详见 design.md §9.8。原地更新逻辑新增按钮时参考该节去重风险分类。
|
|
20
|
+
*/
|
|
21
|
+
export declare const DEDUP_TTL_MS = 300;
|
|
22
|
+
interface FeishuMessage {
|
|
23
|
+
userId: string;
|
|
24
|
+
messageId: string;
|
|
25
|
+
chatId: string;
|
|
26
|
+
content: string;
|
|
27
|
+
}
|
|
28
|
+
type MessageHandler = (msg: FeishuMessage) => void;
|
|
29
|
+
/**
|
|
30
|
+
* CardAction handler may return a CardActionResponse (e.g. `{ toast: {...} }`)
|
|
31
|
+
* to give the clicking user native immediate feedback. The SDK passes the
|
|
32
|
+
* return value back to Feishu as the callback response. Returning void/undefined
|
|
33
|
+
* means "no immediate response" (original behavior).
|
|
34
|
+
*/
|
|
35
|
+
type CardActionHandler = (action: CardActionEvent) => void | CardActionResponse | Promise<void | CardActionResponse>;
|
|
36
|
+
export declare class FeishuConnector {
|
|
37
|
+
private channel;
|
|
38
|
+
private onMessage?;
|
|
39
|
+
private onCardAction?;
|
|
40
|
+
private isConnected;
|
|
41
|
+
private appId;
|
|
42
|
+
private appSecret;
|
|
43
|
+
constructor(config: AppConfig);
|
|
44
|
+
get connected(): boolean;
|
|
45
|
+
setMessageHandler(handler: MessageHandler): void;
|
|
46
|
+
setCardActionHandler(handler: CardActionHandler): void;
|
|
47
|
+
connect(): Promise<void>;
|
|
48
|
+
disconnect(): Promise<void>;
|
|
49
|
+
reconnect(): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Send a message with automatic rate-limit retry (§9.5).
|
|
52
|
+
* Encounters rate_limited / feishuCode 99991400|99991401(SDK 归类为
|
|
53
|
+
* permission_denied)→ sleep 200ms → retry once.
|
|
54
|
+
*/
|
|
55
|
+
sendWithRetry(chatId: string, input: {
|
|
56
|
+
text: string;
|
|
57
|
+
} | {
|
|
58
|
+
markdown: string;
|
|
59
|
+
} | {
|
|
60
|
+
card: object;
|
|
61
|
+
}, opts?: {
|
|
62
|
+
replyTo?: string;
|
|
63
|
+
}): Promise<string>;
|
|
64
|
+
streamCard(chatId: string, initial: object, producer: CardStreamProducer, opts?: {
|
|
65
|
+
replyTo?: string;
|
|
66
|
+
}): Promise<string>;
|
|
67
|
+
updateCard(messageId: string, card: object): Promise<void>;
|
|
68
|
+
/** Format error for logging without causing circular serialization */
|
|
69
|
+
private formatError;
|
|
70
|
+
addReaction(messageId: string, emoji: string): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Upload a file to Feishu and send it to the specified chat.
|
|
73
|
+
* Uses the im/v1/files upload API.
|
|
74
|
+
*/
|
|
75
|
+
sendFile(chatId: string, filePath: string): Promise<string>;
|
|
76
|
+
/**
|
|
77
|
+
* Cached tenant_access_token for sendFile (P2-18). The token is valid ~2h;
|
|
78
|
+
* fetching it on every file send wastes a round-trip and an unguarded fetch
|
|
79
|
+
* could return data.code != 0 with an undefined token. Cache with expiry and
|
|
80
|
+
* validate the response code.
|
|
81
|
+
*/
|
|
82
|
+
private cachedToken;
|
|
83
|
+
private cachedTokenExpireAt;
|
|
84
|
+
private getTenantAccessToken;
|
|
85
|
+
}
|
|
86
|
+
export {};
|