@luchenglong/cli-bridge 0.2.4 → 0.2.5

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.
@@ -1,22 +1,13 @@
1
- // providers/opencode.mjs — opencode provider descriptor(第四家 CLI)。
1
+ // providers/opencode.mjs — opencode provider descriptor(第四家 CLI,无旧包可迁)。
2
2
  //
3
- // 移植基线 = 本机实测的 OpenCode **V2** CLI(v2.0.29,桌面版自带二进制
4
- // opencode-cli.exe)+ 官方 CLI 源码 emit 全集。不是旧包(本包新增,无旧包可迁)。
5
- //
6
- // 实测事实(2026-10-15,CLI v2.0.29):
7
- // opencode run [message..] 非交互主命令;prompt 是【位置参数】(run 子命令无
8
- // --prompt;-p 是 --password,切勿当 prompt 用)
9
- // --format json NDJSON 事件流(每行一个 JSON 对象)
10
- // --auto auto-approve 未显式拒绝的权限(不加会卡在权限提示)
11
- // -m, --model provider/model V2 支持 provider/model#variant
12
- // --session <id> / --continue 会话续接(无 `opencode continue` 子命令)
13
- // --agent / --title / --file / --thinking / --standalone
14
- // ⚠️ V2 无 --dir(V1 有)→ cwd 只能靠 spawn 的工作目录
15
- // ⚠️ V2 无 --variant(V1 有)→ variant 并入 --model 的 # 后缀
16
- // 退出码:0 成功 / 1 错误 / 130 SIGINT(退出码才是成败判据)
17
- // 事件 type(源码全集):step_start / text / reasoning / tool_use / step_finish / error
18
- // ⚠️ 不存在 result/complete/init 事件
19
- // ⚠️ 文本在 part.text;统计在 step_finish.part(cost/tokens)——两者是不同事件
3
+ // 基线 = 本机实测 OpenCode V2 CLI v2.0.29(桌面版自带 opencode-cli.exe)+ 源码 emit 全集。
4
+ // 完整 CLI 事实表见 docs/HANDOVER.md §13;这里只留写代码时最容易踩的几条:
5
+ // • 非交互 = `opencode run "<prompt>"`,prompt 是【位置参数】(run 子命令无 --prompt;
6
+ // `-p` 是 --password)。`--format json` → NDJSON。`--auto` = 自动批准权限。
7
+ // • ⚠️ V2 无 --dir(cwd 靠 spawn)、无 --variant(并入 --model#variant)。
8
+ // • 退出码才是成败判据:0 成功 / 1 错误 / 130 SIGINT。
9
+ // • ⚠️ 无 result/complete/init 事件;type 全集 = step_start/text/reasoning/tool_use/
10
+ // step_finish/error。文本在 part.text,统计在 step_finish.part —— 两者是不同事件。
20
11
  //
21
12
  // 红线(PROVIDER-CONTRACT.md §8 + §1):
22
13
  // - 本文件会被 build 内联进 dynamic 沙箱(new Function):绝不出现 import.meta 字面量;
@@ -209,16 +200,35 @@ export default function defineProvider(deps = {}) {
209
200
  let settingsRef = { binPath: '', defaultModel: '', defaultAgent: '' }
210
201
 
211
202
  /**
212
- * 取当前生效的偏好设置(宿主经 hostOpts.getSettings 注入;无则退回 env/默认)。
203
+ * 取当前生效的偏好设置。三条来源按优先级合并,每次 buildArgv 都重新读 ——
204
+ * 面板改默认模型后无需重启即生效:
205
+ * 1) `host.getSettings('opencode')` —— preset/dynamic 形态(attach 时由宿主注入);
206
+ * 2) `deps.readSettings()` —— MCP 形态(真 Node 进程,每次现读设置文件);
207
+ * 3) `settingsRef` —— attach 时固化的一次性快照 / 缺省值。
208
+ * 三条都拿不到 → 缺省空值(= 不传 --model,交给 opencode 自身配置决定)。
209
+ *
210
+ * 历史坑:MCP 形态不 attach(自建 engine),只认 host.getSettings 会让面板设置的
211
+ * defaultModel 在 MCP 面对 opencode_run 完全失效 —— 只有 args.model 才落地。
212
+ * 三个形态行为不一致是缺陷,故此处统一把 readSettings 纳入(与 mimo 同构)。
213
213
  */
214
214
  function currentSettings() {
215
+ const out = Object.assign({}, settingsRef)
216
+ try {
217
+ if (typeof deps.readSettings === 'function') {
218
+ const r = deps.readSettings()
219
+ if (r && typeof r === 'object') {
220
+ const part = ('partition' in r || 'path' in r || 'home' in r) ? r.partition : r
221
+ if (part && typeof part === 'object') Object.assign(out, part)
222
+ }
223
+ }
224
+ } catch (e) { }
215
225
  try {
216
226
  if (host && typeof host.getSettings === 'function') {
217
227
  const s = host.getSettings('opencode')
218
- if (s && typeof s === 'object') return Object.assign({}, settingsRef, s)
228
+ if (s && typeof s === 'object') Object.assign(out, s)
219
229
  }
220
230
  } catch (e) { }
221
- return settingsRef
231
+ return out
222
232
  }
223
233
 
224
234
  const descriptor = {
package/scripts/build.mjs CHANGED
@@ -50,14 +50,22 @@ function emit(rel, content) {
50
50
 
51
51
  // ── 1. dynamic host(文本内联,沙箱禁 import)──────────────────────────────────
52
52
  function stripModuleSyntax(src, label) {
53
- const out = src
53
+ // 多行 import(`import {\n a, b\n} from '...'`)在单行过滤下会残留 `import ` 前缀并
54
+ // 触发下面的守卫误报 —— 先把跨行 import 折叠成一行再过滤(与单行同规则)。
55
+ const collapsed = src.replace(
56
+ /^\s*import\s*\{[^}]*\}\s*from\s*['"][^'"]+['"];?\s*$/gms,
57
+ (m) => '// ' + m.replace(/\s+/g, ' ').trim()
58
+ )
59
+ const out = collapsed
54
60
  .split('\n')
55
61
  .filter((l) => !/^\s*import\s[^(]*from\s*['"][^'"]+['"];?\s*$/.test(l) && !/^\s*import\s*['"]/.test(l))
56
62
  .join('\n')
57
- .replace(/^export\s+(const|let|var|function|async function|class)\s/gm, '$1 ')
58
- .replace(/^export\s+default\s+(async\s+)?function\s+([A-Za-z0-9_$]+)\s*/gm, '$1function $2 ')
59
- .replace(/^export\s+default\s+(async\s+)?function\s*\(/gm, '$1function __default__(')
60
- .replace(/^export\s*\{[^}]*\};?\s*$/gm, '')
63
+ // 缩进形态也要剥:`export function f()` 可能带前导空格(从 descriptor 闭包里
64
+ // 搬出来的函数保留原缩进),只匹配行首会漏掉 → 残留 export 在脚本体里直接语法错。
65
+ .replace(/^(\s*)export\s+(const|let|var|function|async function|class)\s/gm, '$1$2 ')
66
+ .replace(/^(\s*)export\s+default\s+(async\s+)?function\s+([A-Za-z0-9_$]+)\s*/gm, '$1$2function $3 ')
67
+ .replace(/^(\s*)export\s+default\s+(async\s+)?function\s*\(/gm, '$1$2function __default__(')
68
+ .replace(/^(\s*)export\s*\{[^}]*\};?\s*$/gm, '')
61
69
  // 内联目标是「脚本体」:残留的 export default/import 说明源用了本转换器不支持
62
70
  // 的形态。静默产出坏代码会让 dynamic host 在沙箱里炸得莫名其妙,必须大声失败。
63
71
  if (/^\s*(export\s+default|import\s|import\()/m.test(out)) {
@@ -77,10 +85,20 @@ function buildDynamicHost(id) {
77
85
  // 模板没有 /*__SCHED__*/ 占位(旧模板)→ 不注入,保持向后兼容。
78
86
  const sched = stripModuleSyntax(read('core/scheduling.mjs'), 'core/scheduling.mjs')
79
87
  + '\nconst SCHED_ALL_IDS = ' + JSON.stringify(ids) + '\n'
88
+ // 可选的第二段内联:providers/<id>-catalog.mjs。存在且模板声明了占位符才注入 ——
89
+ // 语义与 __SCHED__ 完全同构(模板没有该占位符 → 不注入,向后兼容)。
90
+ // 目的:providers/<id>.mjs 超 600 行红线时把纯逻辑拆出去,沙箱里仍是单脚本。
91
+ const extraRel = 'providers/' + id + '-catalog.mjs'
92
+ const extra = existsSync(join(ROOT, extraRel)) ? stripModuleSyntax(read(extraRel), extraRel) : null
80
93
  if (!tpl.includes('/*__KIT__*/') || !tpl.includes('/*__PROVIDER__*/')) {
81
94
  throw new Error(tplRel + ' missing /*__KIT__*/ or /*__PROVIDER__*/ placeholder')
82
95
  }
83
96
  let out = tpl.replace('/*__KIT__*/', () => kit).replace('/*__PROVIDER__*/', () => prov)
97
+ // __EXTRA__ 放在 KIT/PROVIDER 之后替换:避免其内容里若含同名占位符被二次展开。
98
+ if (out.includes('/*__EXTRA__*/')) {
99
+ if (!extra) throw new Error(tplRel + ': __EXTRA__ placeholder present but ' + extraRel + ' missing')
100
+ out = out.replace('/*__EXTRA__*/', () => extra)
101
+ } else if (tpl.includes('/*__EXTRA__*/')) throw new Error(tplRel + ': __EXTRA__ placeholder vanished after other replacements')
84
102
  if (out.includes('/*__SCHED__*/')) out = out.replace('/*__SCHED__*/', () => sched)
85
103
  else if (tpl.includes('/*__SCHED__*/')) throw new Error(tplRel + ': __SCHED__ placeholder vanished after other replacements')
86
104
  return out