w-dispatch-ai 1.0.26 → 1.0.27

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.
Files changed (51) hide show
  1. package/README.md +1 -0
  2. package/dist/w-dispatch-ai.umd.js +2 -2
  3. package/dist/w-dispatch-ai.umd.js.map +1 -1
  4. package/docs/WDispatchAi.mjs.html +1 -1
  5. package/docs/adapters.mjs.html +1 -1
  6. package/docs/budgetFor.mjs.html +1 -1
  7. package/docs/buildValidator.mjs.html +1 -1
  8. package/docs/castPintOr.mjs.html +1 -1
  9. package/docs/dfTimeoutMs.mjs.html +1 -1
  10. package/docs/dispatchAi.mjs.html +1 -1
  11. package/docs/dispatchAiFallback.mjs.html +1 -1
  12. package/docs/dispatchAiWkf.mjs.html +1 -1
  13. package/docs/dispatchAntigravity.mjs.html +1 -1
  14. package/docs/dispatchApiOpenaiCompat.mjs.html +1 -1
  15. package/docs/dispatchApiOpenaiResponses.mjs.html +1 -1
  16. package/docs/dispatchApiTypesafeSystemone.mjs.html +1 -1
  17. package/docs/dispatchClaude.mjs.html +1 -1
  18. package/docs/dispatchCodex.mjs.html +1 -1
  19. package/docs/dispatchOpencode.mjs.html +26 -4
  20. package/docs/getCliArgs.mjs.html +1 -1
  21. package/docs/getErrorResult.mjs.html +1 -1
  22. package/docs/getErrorType.mjs.html +1 -1
  23. package/docs/global.html +43 -3
  24. package/docs/index.html +1 -1
  25. package/docs/quota_dfQuotaTimeoutMs.mjs.html +1 -1
  26. package/docs/quota_fetchQuotaJson.mjs.html +1 -1
  27. package/docs/quota_fromCodexUsageHttp.mjs.html +1 -1
  28. package/docs/quota_getQuotaAntigravity.mjs.html +1 -1
  29. package/docs/quota_getQuotaClaude.mjs.html +1 -1
  30. package/docs/quota_getQuotaCodex.mjs.html +1 -1
  31. package/docs/quota_readJsonOrNull.mjs.html +1 -1
  32. package/docs/quota_toQuotaLabel.mjs.html +1 -1
  33. package/docs/quota_toQuotaResult.mjs.html +1 -1
  34. package/docs/quota_toQuotaScopedLabel.mjs.html +1 -1
  35. package/docs/quota_toQuotaWindow.mjs.html +1 -1
  36. package/docs/readEnvFile.mjs.html +1 -1
  37. package/docs/resolveProviders.mjs.html +1 -1
  38. package/docs/wkf_callAiWithFallback.mjs.html +1 -1
  39. package/docs/wkf_createFileStore.mjs.html +1 -1
  40. package/docs/wkf_createUsageCounter.mjs.html +1 -1
  41. package/docs/wkf_extractJsonLoose.mjs.html +1 -1
  42. package/docs/wkf_noSideEffectPrefix.mjs.html +1 -1
  43. package/docs/wkf_runFanout.mjs.html +1 -1
  44. package/docs/wkf_runFanoutPipeline.mjs.html +1 -1
  45. package/docs/wkf_runRolePipeline.mjs.html +1 -1
  46. package/docs/wkf_salvageTruncatedArray.mjs.html +1 -1
  47. package/package.json +1 -1
  48. package/src/dispatchOpencode.mjs +25 -3
  49. package/src/providers.mjs +6 -3
  50. package/test/unit-dispatchOpencode.test.mjs +25 -0
  51. package/test/unit-providers.test.mjs +7 -0
@@ -18,6 +18,19 @@ import dfTimeoutMs from './dfTimeoutMs.mjs'
18
18
  // 帶入真實key → 正常回應。以env傳入「不會改寫」auth.json(實測md5前後一致),
19
19
  // 屬process層級,可與同時執行的其他專案並行而互不干擾。
20
20
  // 註:OPENCODE_API_KEY在auth.json已有憑證時不生效,故一律走OPENCODE_AUTH_CONTENT。
21
+ // 原始碼依據(anomalyco/opencode dev, packages/opencode/src/auth/index.ts之Auth.all):
22
+ // `if (process.env.OPENCODE_AUTH_CONTENT) return JSON.parse(...)`, 解析成功即整份取代, 不與auth.json合併;
23
+ // 空字串為falsy、非法JSON會落回讀auth.json。auth.json位於<XDG_DATA_HOME|~/.local/share>/opencode/。
24
+ //
25
+ // 【不讀auth.json之匿名呼叫: useStoredAuth:false(2026-09-17實測後新增)】
26
+ // 未注入金鑰時opencode會自動沿用auth.json之登入; 該帳號工作區若未開某免費模型(如union-alpha),
27
+ // 就回`Error: Model is disabled`, 而同一模型在「無auth.json」之機器上匿名可用——結果隨執行機器而異。
28
+ // useStoredAuth:false時注入OPENCODE_AUTH_CONTENT='{}'(空憑證), 令本次不讀auth.json而以匿名免費存取。
29
+ // 實測(以XDG_DATA_HOME指向內含opencode金鑰之暫存auth.json): 不帶key→Model is disabled;
30
+ // 加'{}'→7.0s成功; 改注入''→仍Model is disabled(空字串無效, 故不可讓呼叫端自填); auth.json前後md5一致。
31
+ // 同時給key與provider時金鑰注入本就整份取代auth.json, 此旗標不再作用。
32
+ // 風險備忘: 原始碼Auth.set會以Auth.all()(即注入內容)為底寫回auth.json, 僅於OAuth權杖刷新等寫入時發生;
33
+ // api型金鑰與空憑證皆無刷新, 實測未觸發。
21
34
  //
22
35
  // 【provider與model必須配對】不同provider的金鑰不可互換:
23
36
  // 實測把agnes-ai的金鑰用於opencode/...模型 → `Invalid API key`。
@@ -38,7 +51,7 @@ let DEFAULT_AGENT = 'build'
38
51
 
39
52
 
40
53
  //本轉接器自用之設定鍵, 其餘鍵一律原樣轉傳execCli
41
- let OWN_KEYS = ['exe', 'model', 'key', 'provider', 'agent', 'config', 'extraArgs', 'input', 'env']
54
+ let OWN_KEYS = ['exe', 'model', 'key', 'provider', 'useStoredAuth', 'agent', 'config', 'extraArgs', 'input', 'env']
42
55
 
43
56
 
44
57
  /**
@@ -49,7 +62,7 @@ let OWN_KEYS = ['exe', 'model', 'key', 'provider', 'agent', 'config', 'extraArgs
49
62
  * 而opencode run未帶位置message時即從stdin讀取;
50
63
  * 同時給予key與provider時,以OPENCODE_AUTH_CONTENT環境變數逐次注入金鑰,
51
64
  * 該注入僅作用於當次子進程且不改寫auth.json,故可多把金鑰輪替並與其他程序並行;
52
- * 未給key或provider時沿用CLI既有登入狀態;
65
+ * 未給key或provider時沿用CLI既有登入狀態(auth.json),useStoredAuth為false則改以匿名存取;
53
66
  * 使用opencode未內建之第三方provider時,須另以config給予其provider定義;
54
67
  * 本函數不會reject,一律以結果物件之ok與error欄位回報成敗
55
68
  *
@@ -59,6 +72,7 @@ let OWN_KEYS = ['exe', 'model', 'key', 'provider', 'agent', 'config', 'extraArgs
59
72
  * @param {String} [opt.model=''] 輸入模型ID字串,例如'opencode/deepseek-v4-flash-free',預設''代表不帶`-m`旗標
60
73
  * @param {String} [opt.key=''] 輸入該provider之API key字串,須與provider同時給予才會注入,預設''代表沿用CLI既有登入狀態
61
74
  * @param {String} [opt.provider=''] 輸入key所屬provider名稱字串,須與key同時給予才會注入,且須與model為同一組,預設''
75
+ * @param {Boolean} [opt.useStoredAuth=true] 輸入未注入金鑰時是否沿用本機auth.json之登入布林值,false代表注入空憑證令本次以匿名存取(適用opencode之免費模型,避免結果隨本機登入帳號而異);已同時給key與provider時不作用,預設true
62
76
  * @param {Object|String} [opt.config=null] 輸入opencode設定內容物件或其JSON字串,將以OPENCODE_CONFIG_CONTENT逐次注入,供補上第三方provider之定義,預設null代表沿用使用者既有設定檔
63
77
  * @param {String} [opt.agent='build'] 輸入opencode代理名稱字串,預設'build'
64
78
  * @param {Array} [opt.extraArgs=[]] 輸入額外命令列旗標字串陣列,將接於固定旗標之後,預設[]
@@ -166,15 +180,23 @@ async function dispatchOpencode(prompt, opt = {}) {
166
180
  }
167
181
  }
168
182
 
169
- //key與provider皆有效才注入, 否則沿用auth.json既有登入狀態(單金鑰時即為原有行為)
183
+ //key與provider皆有效才注入, 否則沿用auth.json既有登入狀態(單金鑰時即為原有行為);
184
+ //useStoredAuth為false且未注入金鑰時, 注入'{}'令opencode本次不讀auth.json(匿名, 見檔頭)
170
185
  let key = get(opt, 'key', null)
171
186
  let provider = get(opt, 'provider', null)
187
+ let useStoredAuth = get(opt, 'useStoredAuth', null) !== false
172
188
  if (isestr(key) && isestr(provider)) {
173
189
  env = {
174
190
  ...env,
175
191
  OPENCODE_AUTH_CONTENT: JSON.stringify({ [provider]: { type: 'api', key } }),
176
192
  }
177
193
  }
194
+ else if (!useStoredAuth) {
195
+ env = {
196
+ ...env,
197
+ OPENCODE_AUTH_CONTENT: '{}',
198
+ }
199
+ }
178
200
 
179
201
  //timeoutMs, 無效回退全套件統一預設(dfTimeoutMs=300000), 各轉接器一致令呼叫方無須記多套數字
180
202
  let timeoutMs = castPintOr(get(opt, 'timeoutMs', null), dfTimeoutMs)
package/src/providers.mjs CHANGED
@@ -173,13 +173,16 @@ let providers = [
173
173
  model: 'opencode/union-alpha',
174
174
  kind: 'opencode',
175
175
  provider: 'opencode',
176
+ useStoredAuth: false,
176
177
  config: {
177
178
  permission: { edit: 'deny', write: 'deny', bash: 'deny' },
178
179
  },
179
180
  //Union Alpha Free(官方文件: 限時免費之stealth模型, 端點/messages)。2026-09-17實測(opencode CLI 1.18.31):
180
- //刻意不帶envVar——不注入金鑰時opencode走自身免費存取, 實測6.8s成功; OPENCODE_KEYS之第1把金鑰
181
- //所屬工作區未開此模型(回Model is disabled), 第2把雖可用但該工作區會預設開啟新模型(可能非免費), 不採用。
182
- //前提是本機opencode未登入(無auth.json); 若日後以金鑰登入, 須於該工作區開啟此模型。
181
+ //刻意不帶envVar且useStoredAuth:false——以匿名免費存取呼叫, 不隨本機登入帳號而異。
182
+ //OPENCODE_KEYS之第1把金鑰所屬工作區未開此模型(回Model is disabled), 第2把雖可用但該工作區
183
+ //會預設開啟新模型(可能非免費), 皆不採用。原先只「不帶envVar」, 但未注入金鑰時opencode會沿用
184
+ //本機auth.json之登入: 登入帳號之工作區未開此模型的機器即回Model is disabled(使用端回報並經本機以
185
+ //XDG_DATA_HOME暫存auth.json重現), 故加useStoredAuth:false令本次不讀auth.json(實測7.0s成功)。
183
186
  //REST不收zen:版: /messages對免費模型回403 FreeTierError(free tier can only be used from within OpenCode)
184
187
  },
185
188
  {
@@ -82,6 +82,31 @@ describe('dispatchOpencode', function() {
82
82
  assert.strict.deepEqual(r, rr)
83
83
  })
84
84
 
85
+ it('useStoredAuth為false且未注入金鑰時注入空憑證\'{}\'(不讀auth.json); 非false一律不注入; 旗標不進命令列', async function() {
86
+ let t1 = await dispatchOpencode('abc', { exe: fake.exe, provider: 'opencode', useStoredAuth: false })
87
+ let t2 = await dispatchOpencode('abc', { exe: fake.exe, useStoredAuth: false })
88
+ let t3 = await dispatchOpencode('abc', { exe: fake.exe, useStoredAuth: true })
89
+ let t4 = await dispatchOpencode('abc', { exe: fake.exe, useStoredAuth: 'false' })
90
+ let r = [
91
+ JSON.parse(t1.stdout).env.OPENCODE_AUTH_CONTENT,
92
+ JSON.parse(t2.stdout).env.OPENCODE_AUTH_CONTENT,
93
+ JSON.parse(t3.stdout).env.OPENCODE_AUTH_CONTENT,
94
+ JSON.parse(t4.stdout).env.OPENCODE_AUTH_CONTENT,
95
+ JSON.parse(t1.stdout).args,
96
+ ]
97
+ let rr = ['{}', '{}', '', '', ['run', '--agent', 'build']]
98
+ assert.strict.deepEqual(r, rr)
99
+ })
100
+
101
+ it('useStoredAuth為false但同時給key與provider時以金鑰注入為準; 亦覆寫呼叫端env內之同名變數', async function() {
102
+ let t1 = await dispatchOpencode('abc', { exe: fake.exe, provider: 'opencode', key: 'sk-abc123', useStoredAuth: false })
103
+ let t2 = await dispatchOpencode('abc', { exe: fake.exe, useStoredAuth: false, env: { OPENCODE_AUTH_CONTENT: '', FAKE_ENV: 'x' } })
104
+ let o2 = JSON.parse(t2.stdout)
105
+ let r = [JSON.parse(JSON.parse(t1.stdout).env.OPENCODE_AUTH_CONTENT), o2.env.OPENCODE_AUTH_CONTENT, o2.env.FAKE_ENV]
106
+ let rr = [{ opencode: { type: 'api', key: 'sk-abc123' } }, '{}', 'x']
107
+ assert.strict.deepEqual(r, rr)
108
+ })
109
+
85
110
  it('金鑰注入僅作用於當次子進程, 不影響本進程之process.env', async function() {
86
111
  let before = process.env.OPENCODE_AUTH_CONTENT
87
112
  let t = await dispatchOpencode('abc', { exe: fake.exe, provider: 'opencode', key: 'sk-abc123' })
@@ -57,6 +57,13 @@ describe('providers', function() {
57
57
  assert.strict.deepEqual(r, rr)
58
58
  })
59
59
 
60
+ it('opencode條目不帶envVar者必設useStoredAuth:false(否則會沿用本機auth.json, 結果隨執行機器之登入帳號而異)', function() {
61
+ let noKey = providers.filter((p) => p.kind === 'opencode' && p.envVar === undefined)
62
+ let r = noKey.map((p) => [p.id, p.useStoredAuth])
63
+ let rr = noKey.map((p) => [p.id, false])
64
+ assert.strict.deepEqual([noKey.length > 0, r], [true, rr])
65
+ })
66
+
60
67
  it('決策型條目(api-typesafe-systemone)不位於清單末端, 免得全取遞補全敗時以「questions必填」掩蓋真正錯誤', function() {
61
68
  let idx = providers.map((p, i) => p.kind === 'api-typesafe-systemone' ? i : -1).filter((i) => i >= 0)
62
69
  let r = [idx.length > 0, idx.every((i) => i < providers.length - 1)]