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,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex config loader
|
|
3
|
+
*
|
|
4
|
+
* Reads model_provider and model from ~/.codex/config.toml,
|
|
5
|
+
* provides dynamic dropdown options for /config card.
|
|
6
|
+
*
|
|
7
|
+
* Model filtering strategy:
|
|
8
|
+
* - catalog 模式(config.toml 声明 model_catalog_json):模型列表/档位来自活动目录
|
|
9
|
+
* - 非 catalog 模式:openai 用 bundled 全量;anthropic 仅在用户显式配置时存在
|
|
10
|
+
*/
|
|
11
|
+
import { execFileSync } from 'node:child_process';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import os from 'node:os';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import TOML from '@iarna/toml';
|
|
16
|
+
import { getLogger } from '../logger/index.js';
|
|
17
|
+
/**
|
|
18
|
+
* Resolve codex home directory.
|
|
19
|
+
* Priority: explicit codexHome param > $CODEX_HOME env > ~/.codex
|
|
20
|
+
*/
|
|
21
|
+
export function resolveCodexHome(codexHome) {
|
|
22
|
+
return codexHome ?? process.env.CODEX_HOME ?? path.join(os.homedir(), '.codex');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Fallback lists
|
|
26
|
+
* Used when config.toml doesn't exist or cannot be parsed, and bundled command also fails
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* 与 codex 内置 provider 注册表对齐(model-provider-info built_in_model_providers:
|
|
30
|
+
* openai/amazon-bedrock/ollama/lmstudio)。anthropic 不是内置 provider,必须用户显式
|
|
31
|
+
* 配置 [model_providers.anthropic] 才存在,fallback 阶段不得虚构。
|
|
32
|
+
*/
|
|
33
|
+
const FALLBACK_PROVIDERS = ['openai'];
|
|
34
|
+
const FALLBACK_MODELS = [
|
|
35
|
+
'o3',
|
|
36
|
+
'o4-mini',
|
|
37
|
+
'gpt-4o',
|
|
38
|
+
'gpt-4.1',
|
|
39
|
+
'gpt-4.1-mini',
|
|
40
|
+
'gpt-4.1-nano',
|
|
41
|
+
'claude-sonnet-4-20250514',
|
|
42
|
+
'claude-opus-4-20250115',
|
|
43
|
+
];
|
|
44
|
+
/** Bundled models from codex binary, 1h TTL is enough (only change on binary upgrade) */
|
|
45
|
+
const BUNDLED_CACHE_TTL_MS = 60 * 60 * 1000;
|
|
46
|
+
/** 失败/空结果负缓存 TTL:避免每次卡片构建同步阻塞最长 8s(P3-5) */
|
|
47
|
+
const NEGATIVE_CACHE_TTL_MS = 30 * 1000;
|
|
48
|
+
let catalogCache = null;
|
|
49
|
+
/**
|
|
50
|
+
* Read config.toml `model_catalog_json` path (tilde-expanded).
|
|
51
|
+
* 返回语义:
|
|
52
|
+
* - undefined:config.toml 不存在或**未声明**该键 → 非 catalog 模式;
|
|
53
|
+
* - ''(空串哨兵):已声明但不可用(值为空串/非字符串)→ 按"catalog 声明退化"处理
|
|
54
|
+
* (P2-1,绝不回退 bundled/FALLBACK);
|
|
55
|
+
* - TOML_PARSE_FAILED_SENTINEL:config.toml 存在但解析失败 → 无法得知是否声明,
|
|
56
|
+
* 按非 catalog 处理(用户未声明 model_catalog_json 时的默认行为);
|
|
57
|
+
* - 具体路径:声明有效(含文件缺失,文件缺失由调用方按 active 命令失败处理)。
|
|
58
|
+
*/
|
|
59
|
+
const TOML_PARSE_FAILED_SENTINEL = '\u0000toml-parse-failed';
|
|
60
|
+
function readModelCatalogJsonPath(codexHome) {
|
|
61
|
+
const configFile = path.join(codexHome, 'config.toml');
|
|
62
|
+
if (!fs.existsSync(configFile))
|
|
63
|
+
return undefined;
|
|
64
|
+
try {
|
|
65
|
+
const parsed = TOML.parse(fs.readFileSync(configFile, 'utf-8'));
|
|
66
|
+
const raw = parsed.model_catalog_json;
|
|
67
|
+
if (raw === undefined)
|
|
68
|
+
return undefined; // 未声明 → 非 catalog
|
|
69
|
+
if (typeof raw !== 'string' || raw.length === 0)
|
|
70
|
+
return '';
|
|
71
|
+
if (raw === '~')
|
|
72
|
+
return os.homedir();
|
|
73
|
+
if (raw.startsWith('~/'))
|
|
74
|
+
return path.join(os.homedir(), raw.slice(2));
|
|
75
|
+
if (path.isAbsolute(raw))
|
|
76
|
+
return raw;
|
|
77
|
+
// 与 codex AbsolutePathBuf::resolve_path_against_base 一致:相对路径按
|
|
78
|
+
// config.toml 所在目录解析(而非进程 cwd)。
|
|
79
|
+
return path.resolve(path.dirname(configFile), raw);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// config.toml 解析失败:codex 对非法配置是硬错误;此处无法得知是否声明了
|
|
83
|
+
// model_catalog_json,按非 catalog 处理(P3-5),不冒充 catalog 声明退化
|
|
84
|
+
return TOML_PARSE_FAILED_SENTINEL;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Catalog mode = config.toml **声明了** model_catalog_json(键存在即算,不要求文件存在)。
|
|
89
|
+
* 依据:codex 运行时对"已配置但文件缺失/不可解析"的目录是硬错误
|
|
90
|
+
* (core/src/config/mod.rs load_model_catalog ?),绝不会回退内置目录;因此键存在即
|
|
91
|
+
* catalog 模式,文件缺失自然落入 `codex debug models` 失败路径 → 模型列表回退
|
|
92
|
+
* [currentModel](P1-1)。
|
|
93
|
+
*/
|
|
94
|
+
export function isCodexCatalogMode(codexHome) {
|
|
95
|
+
const home = resolveCodexHome(codexHome);
|
|
96
|
+
const p = readModelCatalogJsonPath(home);
|
|
97
|
+
return p !== undefined && p !== TOML_PARSE_FAILED_SENTINEL;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 单一解析器:把 `codex debug models`(活动目录)或 `--bundled`(内置目录)的原始 JSON
|
|
101
|
+
* 输出解析为模型列表(P2-4 去重)。过滤/排序镜像 codex:
|
|
102
|
+
* - show_in_picker = visibility == List(openai_models.rs ModelPreset::from);
|
|
103
|
+
* - API 模式(lark 用 API key 认证)下 codex filter_by_auth 过滤 supported_in_api=false;
|
|
104
|
+
* - 按 priority 升序(build_available_models)。
|
|
105
|
+
* 档位按目录声明**原样透传**(含 custom/none,P2-5),不按标准枚举过滤、不虚构兜底:
|
|
106
|
+
* 未声明档位 → 空列表(codex with_model 语义:len=0 时用声明 default,都没有则不发
|
|
107
|
+
* effort;P1)。空串档位/空串 default 被过滤(P3-9:codex ReasoningEffort::from_str
|
|
108
|
+
* 对 "" 硬错误)。supported_in_api===true 过滤等价于 codex 的 chatgpt_mode ||
|
|
109
|
+
* supported_in_api:本项目以 API-key 模式运行 codex,chatgpt_mode 恒 false(P3-3)。
|
|
110
|
+
*/
|
|
111
|
+
export function parseCodexModelsOutput(stdout) {
|
|
112
|
+
const parsed = JSON.parse(stdout);
|
|
113
|
+
const modelsRaw = Array.isArray(parsed?.models) ? parsed.models : [];
|
|
114
|
+
return (modelsRaw
|
|
115
|
+
.filter((m) => m && typeof m === 'object')
|
|
116
|
+
// 严格镜像 codex API 模式 filter_by_auth:show_in_picker=list 且 supported_in_api=true
|
|
117
|
+
.filter((m) => m.visibility === 'list' && m.supported_in_api === true)
|
|
118
|
+
.sort((a, b) => {
|
|
119
|
+
const pa = typeof a.priority === 'number' ? a.priority : Infinity;
|
|
120
|
+
const pb = typeof b.priority === 'number' ? b.priority : Infinity;
|
|
121
|
+
return pa - pb;
|
|
122
|
+
})
|
|
123
|
+
.map((m) => {
|
|
124
|
+
const supportedReasoningLevels = [];
|
|
125
|
+
if (Array.isArray(m.supported_reasoning_levels)) {
|
|
126
|
+
const declared = m.supported_reasoning_levels
|
|
127
|
+
.filter((r) => r !== null && typeof r === 'object' && 'effort' in r)
|
|
128
|
+
.map((r) => String(r.effort))
|
|
129
|
+
// P3-9:codex 拒绝空串档位(openai_models.rs from_str "" → Err)
|
|
130
|
+
.filter((effort) => effort.length > 0);
|
|
131
|
+
supportedReasoningLevels.push(...declared);
|
|
132
|
+
}
|
|
133
|
+
const defaultReasoningLevel = typeof m.default_reasoning_level === 'string' && m.default_reasoning_level.length > 0
|
|
134
|
+
? m.default_reasoning_level
|
|
135
|
+
: undefined;
|
|
136
|
+
return {
|
|
137
|
+
slug: String(m.slug ?? ''),
|
|
138
|
+
displayName: String(m.display_name ?? m.slug ?? ''),
|
|
139
|
+
priority: typeof m.priority === 'number' ? m.priority : Infinity,
|
|
140
|
+
supportedReasoningLevels,
|
|
141
|
+
defaultReasoningLevel,
|
|
142
|
+
};
|
|
143
|
+
})
|
|
144
|
+
.filter((m) => m.slug.length > 0));
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 统一目录入口:catalog 模式(model_catalog_json 已声明,含声明退化 '')跑
|
|
148
|
+
* `debug models`(无 --bundled),否则(未声明或 config.toml 解析失败)跑
|
|
149
|
+
* `debug models --bundled`。注(P3-4):codex 无参 `debug models` 在
|
|
150
|
+
* uses_codex_backend()/has_command_auth() 时会请求远端 /models(models-manager
|
|
151
|
+
* manager.rs / models_endpoint.rs);本项目以 API-key 模式运行(无 ChatGPT/命令
|
|
152
|
+
* 认证),结果与 bundled 一致,故非 catalog 用 --bundled(已文档化)。
|
|
153
|
+
* 缓存:键含 binary/home/mode/models.json mtime:size + config.toml mtime:size
|
|
154
|
+
* (P3-4/P3-15/P3-5);成功结果 TTL 1h,失败/空结果短 TTL 负缓存;stat 异常时
|
|
155
|
+
* 指纹为空(P1-1/P3-10)。
|
|
156
|
+
*/
|
|
157
|
+
export function getCodexCatalogModels(binary, codexHome) {
|
|
158
|
+
const home = resolveCodexHome(codexHome);
|
|
159
|
+
const catalogPath = readModelCatalogJsonPath(home);
|
|
160
|
+
const bundled = catalogPath === undefined || catalogPath === TOML_PARSE_FAILED_SENTINEL;
|
|
161
|
+
let configFingerprint = '';
|
|
162
|
+
let catalogFingerprint = '';
|
|
163
|
+
const configFile = path.join(home, 'config.toml');
|
|
164
|
+
try {
|
|
165
|
+
const st = fs.statSync(configFile);
|
|
166
|
+
configFingerprint = `${st.mtimeMs}:${st.size}`;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
configFingerprint = '';
|
|
170
|
+
}
|
|
171
|
+
if (!bundled) {
|
|
172
|
+
try {
|
|
173
|
+
const st = fs.statSync(catalogPath);
|
|
174
|
+
catalogFingerprint = `${st.mtimeMs}:${st.size}`;
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
catalogFingerprint = '';
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const key = `${binary}\u0000${home}\u0000${bundled ? 'bundled' : 'active'}\u0000${catalogFingerprint}\u0000${configFingerprint}`;
|
|
181
|
+
const now = Date.now();
|
|
182
|
+
if (catalogCache && catalogCache.key === key) {
|
|
183
|
+
const ttl = catalogCache.failed ? NEGATIVE_CACHE_TTL_MS : BUNDLED_CACHE_TTL_MS;
|
|
184
|
+
if (now - catalogCache.ts < ttl) {
|
|
185
|
+
return catalogCache.models;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const args = bundled ? ['debug', 'models', '--bundled'] : ['debug', 'models'];
|
|
190
|
+
const stdout = execFileSync(binary, args, {
|
|
191
|
+
encoding: 'utf-8',
|
|
192
|
+
timeout: 8_000,
|
|
193
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
194
|
+
env: process.env,
|
|
195
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
196
|
+
});
|
|
197
|
+
const models = parseCodexModelsOutput(stdout);
|
|
198
|
+
catalogCache = { key, models, ts: now, failed: models.length === 0 };
|
|
199
|
+
return models;
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
getLogger().warn(`[codex-config] models unavailable for binary "${binary}": ${err.message}`);
|
|
203
|
+
catalogCache = { key, models: [], ts: now, failed: true };
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Test utility: clear the catalog cache. Tests that exercise the bundled
|
|
209
|
+
* command path (`--bundled`) directly use `invalidateCodexBundledTestCache`
|
|
210
|
+
* from `codex-bundled-test-helpers.ts`.
|
|
211
|
+
*/
|
|
212
|
+
export function invalidateCodexBundledCache() {
|
|
213
|
+
catalogCache = null;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Get supported reasoning effort options for a model.
|
|
217
|
+
* @param codexHome 可选 codex 配置目录覆盖(与 loadCodexConfig 的 codexHome 对齐,
|
|
218
|
+
* 与 loadCodexConfig 的 codexHome 对齐);省略时走 $CODEX_HOME → ~/.codex。
|
|
219
|
+
*/
|
|
220
|
+
export function getReasoningEffortOptions(model, binary = 'codex', codexHome) {
|
|
221
|
+
const models = getCodexCatalogModels(binary, codexHome);
|
|
222
|
+
const found = models.find((m) => m.slug === model);
|
|
223
|
+
// 未命中(含未知模型,codex fallback 元数据 supported 为空)→ [],不虚构档位(P1)
|
|
224
|
+
return found?.supportedReasoningLevels ?? [];
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Get default reasoning effort for a model.
|
|
228
|
+
* 未声明/未知模型 → undefined(codex 语义:ModelPreset.default_reasoning_effort
|
|
229
|
+
* unwrap_or(ReasoningEffort::None),即不传 effort;P3-12)。
|
|
230
|
+
*/
|
|
231
|
+
export function getDefaultReasoningEffort(model, binary = 'codex', codexHome) {
|
|
232
|
+
const models = getCodexCatalogModels(binary, codexHome);
|
|
233
|
+
return models.find((m) => m.slug === model)?.defaultReasoningLevel;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Extract custom provider name from model_providers config.
|
|
237
|
+
* Skips built-in openai/anthropic, returns first custom provider.
|
|
238
|
+
*/
|
|
239
|
+
function extractCustomProvider(providers) {
|
|
240
|
+
const keys = Object.keys(providers);
|
|
241
|
+
return keys.find((k) => k !== 'openai' && k !== 'anthropic');
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Read codex config.toml, extract provider and model info
|
|
245
|
+
*
|
|
246
|
+
* Model filtering strategy:
|
|
247
|
+
* - Custom provider: model from config.toml (single model)
|
|
248
|
+
* - openai: all bundled models
|
|
249
|
+
* - anthropic: bundled models with 'claude-*' prefix
|
|
250
|
+
*/
|
|
251
|
+
export function loadCodexConfig(opts = {}) {
|
|
252
|
+
const binary = opts.binary ?? 'codex';
|
|
253
|
+
const codexHome = resolveCodexHome(opts.codexHome);
|
|
254
|
+
const configFile = path.join(codexHome, 'config.toml');
|
|
255
|
+
const catalogMode = isCodexCatalogMode(codexHome);
|
|
256
|
+
// 统一目录来源:catalog 模式 = 活动目录(model_catalog_json 整体替换内置目录);
|
|
257
|
+
// 否则 = bundled(与原行为一致)
|
|
258
|
+
const catalogSlugs = getCodexCatalogModels(binary, codexHome).map((m) => m.slug);
|
|
259
|
+
const allModels = catalogSlugs.length > 0 ? catalogSlugs : FALLBACK_MODELS;
|
|
260
|
+
const anthropicModels = allModels.filter((m) => m.startsWith('claude-'));
|
|
261
|
+
if (!fs.existsSync(configFile)) {
|
|
262
|
+
// 无 config.toml:默认模型取目录首个可用模型(codex default_model_from_available),
|
|
263
|
+
// 目录不可用时回退 'o3'(FALLBACK_MODELS 首项,非 catalog 默认)
|
|
264
|
+
const currentModel = allModels[0] ?? 'o3';
|
|
265
|
+
// No config file: only openai has bundled models
|
|
266
|
+
const providerModels = {
|
|
267
|
+
openai: allModels,
|
|
268
|
+
};
|
|
269
|
+
return {
|
|
270
|
+
currentModel,
|
|
271
|
+
currentProvider: FALLBACK_PROVIDERS[0],
|
|
272
|
+
providerNames: FALLBACK_PROVIDERS,
|
|
273
|
+
providerEnvKeys: { openai: 'OPENAI_API_KEY' },
|
|
274
|
+
providerModels,
|
|
275
|
+
modelOptions: (provider) => buildModelOptions(currentModel, FALLBACK_PROVIDERS[0], provider, providerModels),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
const raw = fs.readFileSync(configFile, 'utf-8');
|
|
280
|
+
const parsed = TOML.parse(raw);
|
|
281
|
+
const modelKeyRaw = typeof parsed.model === 'string' && parsed.model.length > 0 ? parsed.model : undefined;
|
|
282
|
+
const currentProvider = String(parsed.model_provider ?? FALLBACK_PROVIDERS[0]);
|
|
283
|
+
// Extract model_providers section
|
|
284
|
+
const modelProviders = (parsed.model_providers ?? {});
|
|
285
|
+
const rawProviderNames = Object.keys(modelProviders);
|
|
286
|
+
let providerNames;
|
|
287
|
+
let providerEnvKeys;
|
|
288
|
+
let providerModels;
|
|
289
|
+
if (catalogMode) {
|
|
290
|
+
// catalog 模式:provider 列表 = 内置 openai + [model_providers.*](对齐 codex
|
|
291
|
+
// merge_configured_model_providers(built_in_model_providers, cfg.model_providers),
|
|
292
|
+
// core/src/config/mod.rs:3655;model_provider 未声明时默认 "openai",mod.rs:3659)。
|
|
293
|
+
// anthropic 不是 codex 内置 provider,未显式配置时不得出现。模型列表仍只来自
|
|
294
|
+
// 活动目录——openai+内置 gpt-5.x 的失效路径不因 provider 合并复活。
|
|
295
|
+
providerNames = rawProviderNames.includes('openai')
|
|
296
|
+
? rawProviderNames
|
|
297
|
+
: ['openai', ...rawProviderNames];
|
|
298
|
+
providerEnvKeys = { openai: 'OPENAI_API_KEY' };
|
|
299
|
+
for (const [name, config] of Object.entries(modelProviders)) {
|
|
300
|
+
if (typeof config.env_key === 'string') {
|
|
301
|
+
providerEnvKeys[name] = config.env_key;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// 活动目录全局:codex 运行时 provider 与模型无绑定(StaticModelsManager),
|
|
305
|
+
// 每个 provider 下拉都展示目录全部 slug
|
|
306
|
+
// 活动目录不可用(命令失败/空)时回退 [currentModel]——无目录元数据时档位/能力
|
|
307
|
+
// 不可信;禁止泄漏 FALLBACK_MODELS/内置目录。model 键缺失时不得虚构 'o3'(P1-2)。
|
|
308
|
+
const catalogCurrentModel = modelKeyRaw ?? catalogSlugs[0] ?? '';
|
|
309
|
+
const providerModelList = catalogSlugs.length > 0 ? catalogSlugs : modelKeyRaw ? [modelKeyRaw] : [];
|
|
310
|
+
providerModels = {};
|
|
311
|
+
for (const name of providerNames) {
|
|
312
|
+
providerModels[name] = providerModelList;
|
|
313
|
+
}
|
|
314
|
+
const resolvedProvider = typeof parsed.model_provider === 'string' && parsed.model_provider.length > 0
|
|
315
|
+
? parsed.model_provider
|
|
316
|
+
: 'openai';
|
|
317
|
+
return {
|
|
318
|
+
currentModel: catalogCurrentModel,
|
|
319
|
+
currentProvider: resolvedProvider,
|
|
320
|
+
providerNames,
|
|
321
|
+
providerEnvKeys,
|
|
322
|
+
providerModels,
|
|
323
|
+
modelOptions: (provider) => buildModelOptions(catalogCurrentModel, resolvedProvider, provider, providerModels),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
// fallback 模式:保持原行为(openai/anthropic 内置 + bundled 模型)
|
|
327
|
+
const currentModel = modelKeyRaw ?? catalogSlugs[0] ?? 'o3';
|
|
328
|
+
providerNames = rawProviderNames;
|
|
329
|
+
if (!providerNames.includes('openai')) {
|
|
330
|
+
providerNames.unshift('openai');
|
|
331
|
+
}
|
|
332
|
+
const customProvider = extractCustomProvider(modelProviders);
|
|
333
|
+
providerEnvKeys = {};
|
|
334
|
+
providerEnvKeys['openai'] = 'OPENAI_API_KEY';
|
|
335
|
+
for (const [name, config] of Object.entries(modelProviders)) {
|
|
336
|
+
if (typeof config.env_key === 'string') {
|
|
337
|
+
providerEnvKeys[name] = config.env_key;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// anthropic 显式配置但未写 env_key 时保留默认键(P3-9:恢复旧版无条件映射的
|
|
341
|
+
// 配置场景行为;未配置时不再虚构)
|
|
342
|
+
if (rawProviderNames.includes('anthropic') &&
|
|
343
|
+
!modelProviders['anthropic']?.env_key &&
|
|
344
|
+
providerEnvKeys['anthropic'] === undefined) {
|
|
345
|
+
providerEnvKeys['anthropic'] = 'ANTHROPIC_API_KEY';
|
|
346
|
+
}
|
|
347
|
+
providerModels = { openai: allModels };
|
|
348
|
+
// anthropic 仅在用户显式配置时才存在(非 codex 内置 provider)
|
|
349
|
+
if (rawProviderNames.includes('anthropic')) {
|
|
350
|
+
providerModels['anthropic'] = anthropicModels;
|
|
351
|
+
}
|
|
352
|
+
if (customProvider) {
|
|
353
|
+
// Custom provider: use the model from config.toml
|
|
354
|
+
providerModels[customProvider] = [currentModel];
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
currentModel,
|
|
358
|
+
currentProvider,
|
|
359
|
+
providerNames,
|
|
360
|
+
providerEnvKeys,
|
|
361
|
+
providerModels,
|
|
362
|
+
modelOptions: (provider) => buildModelOptions(currentModel, currentProvider, provider, providerModels),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
// config.toml 解析失败(readModelCatalogJsonPath 已把此场景标记为 catalog 声明退化 ''):
|
|
367
|
+
// catalogMode 为 true 时绝不回退 bundled/FALLBACK(P2-1),返回空 provider/模型列表;
|
|
368
|
+
// 非 catalog 才走 FALLBACK 兜底。
|
|
369
|
+
if (catalogMode) {
|
|
370
|
+
return {
|
|
371
|
+
currentModel: '',
|
|
372
|
+
currentProvider: '',
|
|
373
|
+
providerNames: [],
|
|
374
|
+
providerEnvKeys: {},
|
|
375
|
+
providerModels: {},
|
|
376
|
+
modelOptions: () => [],
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
const currentModel = catalogSlugs[0] ?? 'o3';
|
|
380
|
+
const providerModels = {
|
|
381
|
+
openai: allModels,
|
|
382
|
+
};
|
|
383
|
+
return {
|
|
384
|
+
currentModel,
|
|
385
|
+
currentProvider: FALLBACK_PROVIDERS[0],
|
|
386
|
+
providerNames: FALLBACK_PROVIDERS,
|
|
387
|
+
providerEnvKeys: { openai: 'OPENAI_API_KEY' },
|
|
388
|
+
providerModels,
|
|
389
|
+
modelOptions: (provider) => buildModelOptions(currentModel, FALLBACK_PROVIDERS[0], provider, providerModels),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Build model options for a provider.
|
|
395
|
+
* - If provider specified: return that provider's models.
|
|
396
|
+
* Only prepend currentModel if it belongs to currentProvider (same provider filter).
|
|
397
|
+
* Cross-provider currentModel is NOT leaked into another provider's list.
|
|
398
|
+
* - If no provider: return all models from all providers (with currentModel prepended if not in any list)
|
|
399
|
+
*/
|
|
400
|
+
function buildModelOptions(currentModel, currentProvider, provider, providerModels) {
|
|
401
|
+
// 不为空 currentModel 虚构默认值(catalog 退化场景由上层显式传入,P1-2);
|
|
402
|
+
// 空值时直接返回原始列表
|
|
403
|
+
const cm = currentModel;
|
|
404
|
+
if (provider && providerModels[provider]) {
|
|
405
|
+
// Return models for the specified provider
|
|
406
|
+
const models = providerModels[provider];
|
|
407
|
+
// Only prepend currentModel if it belongs to this provider (currentProvider matches)
|
|
408
|
+
// Cross-provider currentModel must NOT leak into another provider's list
|
|
409
|
+
if (cm && !models.includes(cm) && currentProvider === provider) {
|
|
410
|
+
return [cm, ...models];
|
|
411
|
+
}
|
|
412
|
+
return models;
|
|
413
|
+
}
|
|
414
|
+
// No provider specified: return all unique models
|
|
415
|
+
const allModels = Object.values(providerModels).flat();
|
|
416
|
+
const unique = [...new Set(allModels)];
|
|
417
|
+
// Ensure currentModel is always first (even if bundled failed)
|
|
418
|
+
if (cm && !unique.includes(cm)) {
|
|
419
|
+
return [cm, ...unique];
|
|
420
|
+
}
|
|
421
|
+
// If currentModel already in list but not first, move it to front
|
|
422
|
+
if (cm && unique[0] !== cm) {
|
|
423
|
+
const filtered = unique.filter((m) => m !== cm);
|
|
424
|
+
return [cm, ...filtered];
|
|
425
|
+
}
|
|
426
|
+
return unique;
|
|
427
|
+
}
|
|
428
|
+
//# sourceMappingURL=codex-config.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface CliArgs {
|
|
2
|
+
configDir?: string;
|
|
3
|
+
settings?: string;
|
|
4
|
+
help?: boolean;
|
|
5
|
+
version?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function resolveConfigDir(configDirArg: string | undefined): string;
|
|
8
|
+
export declare function getConfigDir(): string;
|
|
9
|
+
export declare function setConfigDir(dir: string): void;
|
|
10
|
+
export declare function parseCliArgs(args?: string[]): CliArgs;
|
|
11
|
+
/** Read the project version from package.json (single source of truth). */
|
|
12
|
+
export declare function getVersion(): string;
|
|
13
|
+
/** Print version to stdout. */
|
|
14
|
+
export declare function printVersion(): void;
|
|
15
|
+
/** Print CLI help to stdout. */
|
|
16
|
+
export declare function printHelp(): void;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
const DEFAULT_DIR = '.lark-remote';
|
|
6
|
+
let cachedConfigDir = null;
|
|
7
|
+
export function resolveConfigDir(configDirArg) {
|
|
8
|
+
if (configDirArg) {
|
|
9
|
+
let p = configDirArg;
|
|
10
|
+
if (p === '~')
|
|
11
|
+
p = os.homedir();
|
|
12
|
+
else if (p.startsWith('~/'))
|
|
13
|
+
p = path.join(os.homedir(), p.slice(2));
|
|
14
|
+
return path.resolve(p);
|
|
15
|
+
}
|
|
16
|
+
return path.join(os.homedir(), DEFAULT_DIR);
|
|
17
|
+
}
|
|
18
|
+
export function getConfigDir() {
|
|
19
|
+
if (!cachedConfigDir) {
|
|
20
|
+
cachedConfigDir = path.join(os.homedir(), DEFAULT_DIR);
|
|
21
|
+
}
|
|
22
|
+
return cachedConfigDir;
|
|
23
|
+
}
|
|
24
|
+
export function setConfigDir(dir) {
|
|
25
|
+
cachedConfigDir = dir;
|
|
26
|
+
}
|
|
27
|
+
export function parseCliArgs(args = process.argv.slice(2)) {
|
|
28
|
+
const result = {};
|
|
29
|
+
for (let i = 0; i < args.length; i++) {
|
|
30
|
+
const arg = args[i];
|
|
31
|
+
if (arg === '-h' || arg === '--help') {
|
|
32
|
+
result.help = true;
|
|
33
|
+
}
|
|
34
|
+
else if (arg === '-v' || arg === '--version') {
|
|
35
|
+
result.version = true;
|
|
36
|
+
}
|
|
37
|
+
else if (arg === '--config-dir' && i + 1 < args.length) {
|
|
38
|
+
// P1-20: peek, don't consume — when the next arg is itself a flag,
|
|
39
|
+
// roll back so it continues parsing (previously --settings/--help
|
|
40
|
+
// right after --config-dir were silently swallowed).
|
|
41
|
+
const nextArg = args[i + 1];
|
|
42
|
+
if (nextArg && !nextArg.startsWith('--')) {
|
|
43
|
+
result.configDir = args[++i];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else if (arg === '--settings' && i + 1 < args.length) {
|
|
47
|
+
const nextArg = args[i + 1];
|
|
48
|
+
if (nextArg && !nextArg.startsWith('--')) {
|
|
49
|
+
result.settings = args[++i];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
/** Read the project version from package.json (single source of truth). */
|
|
56
|
+
export function getVersion() {
|
|
57
|
+
const pkgPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json');
|
|
58
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
59
|
+
return pkg.version ?? '0.0.0';
|
|
60
|
+
}
|
|
61
|
+
/** Print version to stdout. */
|
|
62
|
+
export function printVersion() {
|
|
63
|
+
process.stdout.write(`lark-remote ${getVersion()}\n`);
|
|
64
|
+
}
|
|
65
|
+
/** Print CLI help to stdout. */
|
|
66
|
+
export function printHelp() {
|
|
67
|
+
const lines = [
|
|
68
|
+
'lark-remote — 飞书私聊 ↔ 本地 Coding Agent CLI 桥接',
|
|
69
|
+
'',
|
|
70
|
+
'Usage:',
|
|
71
|
+
' lark-remote [options]',
|
|
72
|
+
'',
|
|
73
|
+
'Options:',
|
|
74
|
+
' --config-dir <path> 自定义配置目录(默认 ~/.lark-remote,可用于同机多实例)',
|
|
75
|
+
' --settings <path> 指定 Claude 配置文件路径',
|
|
76
|
+
' -h, --help 显示本帮助信息',
|
|
77
|
+
' -v, --version 显示版本号',
|
|
78
|
+
];
|
|
79
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=dir.js.map
|