@ebony-vinyl/dsh-our-free-model 2.7.4 → 2.8.2

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 (44) hide show
  1. package/README.md +23 -8
  2. package/client.js +894 -6
  3. package/cordis.patch.yml +1 -1
  4. package/index.js +248 -11
  5. package/package.json +6 -2
  6. package/src/adapter.js +54 -6
  7. package/src/aggregate.js +53 -13
  8. package/src/bun-fetch.js +33 -10
  9. package/src/catalog.js +238 -58
  10. package/src/core.js +838 -0
  11. package/src/egress.js +204 -3
  12. package/src/engine-parts/win32-x64.000.part +0 -0
  13. package/src/engine-parts/win32-x64.001.part +0 -0
  14. package/src/engine-parts/win32-x64.002.part +0 -0
  15. package/src/engine-parts/win32-x64.003.part +0 -0
  16. package/src/engine-parts/win32-x64.004.part +0 -0
  17. package/src/engine-parts/win32-x64.005.part +0 -0
  18. package/src/engine-parts/win32-x64.006.part +0 -0
  19. package/src/extensions.js +1888 -0
  20. package/src/feed.js +6 -0
  21. package/src/http.js +15 -2
  22. package/src/kilo.js +55 -5
  23. package/src/magpie-engine.js +61 -2
  24. package/src/magpie-models.js +31 -3
  25. package/src/model-test.js +3 -1
  26. package/src/o2a.js +956 -0
  27. package/src/open-external.js +43 -5
  28. package/src/plugins.js +1240 -0
  29. package/src/providers/registry.json +72 -30
  30. package/src/semver.js +40 -0
  31. package/src/store.js +17 -1
  32. package/src/stream.js +39 -4
  33. package/src/subscription-completion.js +12 -3
  34. package/src/updater.js +5 -33
  35. package/src/upstream.js +283 -363
  36. package/vendor/channel-pack/NOTICE.md +1 -1
  37. package/vendor/channel-pack/pack.js +7242 -3952
  38. package/vendor/magpie/NOTICE.md +159 -6
  39. package/vendor/magpie/engine.json +58 -27
  40. package/src/core/completion.js +0 -196
  41. package/src/core/runtime.js +0 -456
  42. package/src/core/stats.js +0 -98
  43. package/src/engine-parts/win32-x64.007.part +0 -0
  44. package/src/engine-parts/win32-x64.008.part +0 -0
@@ -3827,63 +3827,105 @@
3827
3827
  "env": "KILO_API_KEY",
3828
3828
  "url": "https://app.kilo.ai"
3829
3829
  },
3830
- "baseUrl": "https://api.kilo.ai/api/gateway",
3830
+ "baseUrl": "https://ai-gateway.kilo.ai/api/v1",
3831
3831
  "format": "openai-chat",
3832
3832
  "models": [
3833
3833
  {
3834
3834
  "id": "kilo-auto/free",
3835
- "name": "Kilo Auto Free",
3835
+ "name": "Auto Free",
3836
3836
  "contextWindow": 256000,
3837
- "vision": null,
3838
- "reasoning": null
3837
+ "vision": false,
3838
+ "reasoning": true
3839
3839
  },
3840
3840
  {
3841
- "id": "nvidia/nemotron-3-super-120b-a12b:free",
3842
- "name": "Nemotron 3 Super 120B (Free)",
3843
- "contextWindow": 262144,
3844
- "vision": null,
3845
- "reasoning": null
3841
+ "id": "nvidia/nemotron-3.5-super-vl:free",
3842
+ "name": "NVIDIA: Nemotron 3.5 Super VL",
3843
+ "contextWindow": 1000000,
3844
+ "vision": true,
3845
+ "reasoning": true
3846
3846
  },
3847
3847
  {
3848
- "id": "nvidia/nemotron-3-ultra-550b-a55b:free",
3849
- "name": "Nemotron 3 Ultra 550B (Free)",
3850
- "contextWindow": 1000000,
3851
- "vision": null,
3852
- "reasoning": null
3848
+ "id": "reflection/beam-free",
3849
+ "name": "Reflection: Beam 1.0",
3850
+ "contextWindow": 1048576,
3851
+ "vision": false,
3852
+ "reasoning": true
3853
3853
  },
3854
3854
  {
3855
- "id": "kwaipilot/kat-coder-pro-v2.5:free",
3856
- "name": "Kat Coder Pro v2.5 (Free)",
3855
+ "id": "stealth/glyph-cluster",
3856
+ "name": "Stealth: Glyph Cluster",
3857
3857
  "contextWindow": 256000,
3858
- "vision": null,
3859
- "reasoning": null
3858
+ "vision": false,
3859
+ "reasoning": true
3860
3860
  },
3861
3861
  {
3862
- "id": "kilo-auto/frontier",
3863
- "name": "Kilo Auto Frontier",
3862
+ "id": "stealth/crouton-alpha",
3863
+ "name": "Crouton Alpha",
3864
+ "contextWindow": 1048576,
3865
+ "vision": true,
3866
+ "reasoning": true
3867
+ },
3868
+ {
3869
+ "id": "dots-studio/dots-3-note-preview:free",
3870
+ "name": "Dots Studio: Dots3-Note Preview",
3871
+ "contextWindow": 512000,
3872
+ "vision": true,
3873
+ "reasoning": true
3874
+ },
3875
+ {
3876
+ "id": "nvidia/nemotron-3.5-lightning:free",
3877
+ "name": "NVIDIA: Nemotron 3.5 Lightning",
3864
3878
  "contextWindow": 1000000,
3865
- "vision": null,
3866
- "reasoning": null
3879
+ "vision": false,
3880
+ "reasoning": true
3867
3881
  },
3868
3882
  {
3869
- "id": "kilo-auto/balanced",
3870
- "name": "Kilo Auto Balanced",
3883
+ "id": "cohere/north-mini-code:free",
3884
+ "name": "Cohere: North Mini Code",
3885
+ "contextWindow": 256000,
3886
+ "vision": false,
3887
+ "reasoning": true
3888
+ },
3889
+ {
3890
+ "id": "nvidia/nemotron-3-ultra-550b-a55b:free",
3891
+ "name": "NVIDIA: Nemotron 3 Ultra",
3871
3892
  "contextWindow": 1000000,
3872
- "vision": null,
3873
- "reasoning": null
3893
+ "vision": false,
3894
+ "reasoning": true
3895
+ },
3896
+ {
3897
+ "id": "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
3898
+ "name": "NVIDIA: Nemotron 3 Nano Omni",
3899
+ "contextWindow": 256000,
3900
+ "vision": true,
3901
+ "reasoning": true
3902
+ },
3903
+ {
3904
+ "id": "openrouter/free",
3905
+ "name": "OpenRouter Free Models Router",
3906
+ "contextWindow": 200000,
3907
+ "vision": true,
3908
+ "reasoning": true
3874
3909
  }
3875
3910
  ],
3876
- "notes": "Kilo gateway free pool; the plugin’s own Kilo channel reads the same host.",
3911
+ "notes": "Kilo gateway free pool, snapshot 2026-11-07; the plugin’s own Kilo channel serves it keyless from the live listing — this row is the aggregate-lane fallback for a pasted key.",
3877
3912
  "website": "https://kilo.ai/",
3878
3913
  "local": false,
3879
3914
  "source": "fcc",
3880
3915
  "freeModelsHint": [
3881
3916
  "kilo-auto/free",
3882
- "nvidia/nemotron-3-super-120b-a12b:free",
3917
+ "nvidia/nemotron-3.5-super-vl:free",
3918
+ "reflection/beam-free",
3919
+ "stealth/glyph-cluster",
3920
+ "stealth/crouton-alpha",
3921
+ "dots-studio/dots-3-note-preview:free",
3922
+ "nvidia/nemotron-3.5-lightning:free",
3923
+ "cohere/north-mini-code:free",
3883
3924
  "nvidia/nemotron-3-ultra-550b-a55b:free",
3884
- "kwaipilot/kat-coder-pro-v2.5:free"
3925
+ "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
3926
+ "openrouter/free"
3885
3927
  ],
3886
- "modelsUrl": "https://api.kilo.ai/api/gateway/models"
3928
+ "modelsUrl": "https://ai-gateway.kilo.ai/api/v1/models"
3887
3929
  },
3888
3930
  {
3889
3931
  "id": "kimchi",
package/src/semver.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Semver comparison shared by the self-updater and the extension platform.
3
+ * Lives apart from updater.js because the standalone package deliberately
4
+ * ships without the updater — the extension runtime still needs to compare a
5
+ * registry entry's minHost against the host version.
6
+ */
7
+
8
+ /** Parse `1.2.3` / `1.2.3-rc.4` into a comparable tuple; `null` when malformed. */
9
+ export function parseVersion(value) {
10
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([\w.]+))?$/.exec(String(value ?? '').trim())
11
+ if (match === null) return null
12
+ const pre = match[4] === undefined ? null : match[4].split('.')
13
+ return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]), pre }
14
+ }
15
+
16
+ /** -1 | 0 | 1 in semver precedence; a pre-release sorts before its release. */
17
+ export function compareVersions(a, b) {
18
+ const left = parseVersion(a)
19
+ const right = parseVersion(b)
20
+ if (left === null || right === null) return a === b ? 0 : a > b ? 1 : -1
21
+ for (const key of ['major', 'minor', 'patch']) {
22
+ if (left[key] !== right[key]) return left[key] < right[key] ? -1 : 1
23
+ }
24
+ if (left.pre === null && right.pre === null) return 0
25
+ if (left.pre === null) return 1
26
+ if (right.pre === null) return -1
27
+ const width = Math.max(left.pre.length, right.pre.length)
28
+ for (let i = 0; i < width; i += 1) {
29
+ const x = left.pre[i]
30
+ const y = right.pre[i]
31
+ if (x === undefined) return -1
32
+ if (y === undefined) return 1
33
+ const xn = /^\d+$/.test(x)
34
+ const yn = /^\d+$/.test(y)
35
+ if (xn && yn && Number(x) !== Number(y)) return Number(x) < Number(y) ? -1 : 1
36
+ if (xn !== yn) return xn ? -1 : 1
37
+ if (x !== y) return x < y ? -1 : 1
38
+ }
39
+ return 0
40
+ }
package/src/store.js CHANGED
@@ -159,6 +159,15 @@ export const SETTINGS_INITIAL = {
159
159
  version: 1,
160
160
  /** Master switch: when off the routes stay registered but serve nothing. */
161
161
  enabled: true,
162
+ /** Allow installing unsigned local plugin packages from disk. Off by
163
+ * default: the signed marketplace is the trust root, and a local package is
164
+ * an explicit developer action with its own warning and badge. */
165
+ developerPlugins: false,
166
+ /** Install extension updates automatically once the registry tick runs and
167
+ * the newer version has soaked past its supply-chain window (see
168
+ * AUTO_UPDATE_SOAK_MS in src/extensions.js). Off by default: the update
169
+ * badge is the prompt, the button is the decision. */
170
+ autoUpdateExtensions: false,
162
171
  /** Show region-limited models in the picker as soon as they answer. */
163
172
  exposeRegionModels: true,
164
173
  /** Advertisement only: retain catalog entries and historical model resolution. */
@@ -167,8 +176,15 @@ export const SETTINGS_INITIAL = {
167
176
  hideEacModels: false,
168
177
  /** Minutes between background catalog refreshes. Automatic availability
169
178
  * probing is disabled by owner policy; only the manual reprobe button sends
170
- * probe requests. */
179
+ * probe requests. Since 2.8.1 this cadence also cannot re-fetch the listing
180
+ * more often than once a listing window (see src/core/runtime.js). */
171
181
  probeIntervalMinutes: 15,
182
+ /** The OpenCode lane's own proxy exit (issue #708's method): when set, this
183
+ * lane's requests dial through it instead of the process's global egress,
184
+ * which is how a burned per-IP quota window is left behind. Empty means the
185
+ * global outlet when one runs, then the HTTP(S)_PROXY environment, then
186
+ * direct. Never auto-rotated — the address is the user's own. */
187
+ freeLane: { proxyUrl: '' },
172
188
  /** Serve the OpenAI-compatible forward listener for other local harnesses. */
173
189
  forward: {
174
190
  enabled: false,
package/src/stream.js CHANGED
@@ -88,11 +88,13 @@ function argumentsCutMidway(raw) {
88
88
  }
89
89
 
90
90
  class BlockSink {
91
- constructor(yieldChunk, startIndex, checkpointLimit, onProgress) {
91
+ constructor(yieldChunk, startIndex, checkpointLimit, onProgress, renameMap) {
92
92
  this.emit = yieldChunk
93
93
  this.next = startIndex
94
94
  this.onProgress = typeof onProgress === 'function' ? onProgress : () => {}
95
- /** @type {Map<string, {index:number, kind:string, text:string, id?:string, name?:string, args?:string}>} */
95
+ /** The rename map the raw Chat tool names resolve through. */
96
+ this.renameMap = renameMap
97
+ /** @type {Map<string, {index:number, kind:string, text:string, id?:string, name?:string, rawName?:string, args?:string}>} */
96
98
  this.open = new Map()
97
99
  this.usage = undefined
98
100
  this.sawReasoning = false
@@ -186,10 +188,40 @@ class BlockSink {
186
188
  && (block.name !== priorName || block.id !== priorId)) this.onProgress()
187
189
  }
188
190
 
191
+ /**
192
+ * Buffer one fragment of a Chat tool call's wire name (`toolNameFragment`).
193
+ *
194
+ * Some gateways split `tool_calls[].function.name` across delta frames — and
195
+ * resolving every fragment on arrival used to fold a half name into a wrong
196
+ * or unknown tool, surfacing ghost calls (opencode2api v1.4.7 buffers the
197
+ * same way). The raw name accumulates here and resolves through
198
+ * {@link resolveRawName} once the call's arguments start or the block closes;
199
+ * until then the emitted deltas simply carry no name, which downstream
200
+ * consumers already tolerate (arguments can legitimately precede a name).
201
+ */
202
+ toolNameFragment(key, id, fragment) {
203
+ const block = this.slot(key, 'tool-call')
204
+ if (id) block.id = id
205
+ // Real servers repeat the *complete* name on every delta; a genuinely split
206
+ // name arrives as disjoint fragments. Extend when the fragment continues
207
+ // (or repeats) what we hold, concatenate only when it cannot be a repeat —
208
+ // that covers both shapes without ever doubling a repeated name.
209
+ const held = typeof block.rawName === 'string' ? block.rawName : ''
210
+ block.rawName = fragment.startsWith(held) ? fragment : held + fragment
211
+ }
212
+
213
+ /** Resolve a buffered raw wire name once, right before its first consumer needs it. */
214
+ #resolveRawName(block) {
215
+ if (block.name || typeof block.rawName !== 'string' || block.rawName === '') return
216
+ block.name = restoreToolName(block.rawName, this.renameMap)
217
+ this.onProgress()
218
+ }
219
+
189
220
  toolArgs(key, delta) {
190
221
  this.sawToolCall = true
191
222
  if (!delta) return
192
223
  const block = this.slot(key, 'tool-call')
224
+ this.#resolveRawName(block)
193
225
  block.args += delta
194
226
  // Arguments can arrive before the wire has named the call (a Claude-relay
195
227
  // stream does exactly this). The harness re-reads every chunk through a
@@ -211,6 +243,7 @@ class BlockSink {
211
243
  if (block.kind === 'text' && block.text !== '') this.emit({ type: 'block-end', index: block.index, block: { type: 'text', text: block.text } })
212
244
  else if (block.kind === 'reasoning' && block.text !== '') this.emit({ type: 'block-end', index: block.index, block: { type: 'reasoning', text: block.text } })
213
245
  else if (block.kind === 'tool-call') {
246
+ this.#resolveRawName(block)
214
247
  // Arguments that never parse are an unexecutable call. The gateway
215
248
  // reports finish "tool_calls" even when the output ceiling cut the JSON
216
249
  // mid-string (verified live 2026-09-25), so the finish token alone
@@ -359,7 +392,9 @@ function feedChat(sink, payload, renameMap, onFinish) {
359
392
  sink.sawToolCall = true
360
393
  const key = sink.toolKey(call)
361
394
  const name = call.function?.name
362
- if (typeof name === 'string' && name !== '') sink.toolStart(key, call.id ?? '', restoreToolName(name, renameMap))
395
+ // The name is buffered, not resolved per fragment: split frames used to
396
+ // fold a half name into a ghost tool (see BlockSink.toolNameFragment).
397
+ if (typeof name === 'string' && name !== '') sink.toolNameFragment(key, call.id ?? '', name)
363
398
  else if (call.id) sink.toolStart(key, call.id, '')
364
399
  if (call.function?.arguments) sink.toolArgs(key, call.function.arguments)
365
400
  }
@@ -506,7 +541,7 @@ export async function * readStream(lines, wire, renameMap, now = () => Date.now(
506
541
  const outbox = []
507
542
  const startIndex = Number.isSafeInteger(options?.startIndex) && options.startIndex >= 0 ? options.startIndex : 0
508
543
  const checkpointLimit = Number.isSafeInteger(options?.checkpointLimit) && options.checkpointLimit >= 0 ? options.checkpointLimit : 131072
509
- const sink = new BlockSink(chunk => outbox.push(chunk), startIndex, checkpointLimit, options?.onProgress)
544
+ const sink = new BlockSink(chunk => outbox.push(chunk), startIndex, checkpointLimit, options?.onProgress, renameMap)
510
545
  const state = { usage: undefined, finish: undefined, sawFinish: false, sawUsage: false, sawToolCall: false, firstDeltaAt: undefined, sawReasoning: false, sawText: false, brokenToolCall: false }
511
546
  const snapshot = () => ({
512
547
  ...state,
@@ -1,5 +1,5 @@
1
1
  import crypto from 'node:crypto'
2
- import { fromOpenAiMessages, foldForwardOutcome } from './core/completion.js'
2
+ import { fromOpenAiMessages, foldForwardOutcome } from './core.js'
3
3
  import { toChatMessages, toClaudeMessages, toResponseInput, toToolDefs, repairToolPairing } from './messages.js'
4
4
  import { readStream, finishReason } from './stream.js'
5
5
  import { readSse } from './http.js'
@@ -335,10 +335,11 @@ function credentialRedactor(secrets, deliver) {
335
335
  * complete() may override it with request.resolveImage for that call only.
336
336
  */
337
337
  export function createSubscriptionRuntime({
338
- subscriptions, stats, logger, fetchImpl = globalThis.fetch, timeoutMs = 300000, resolveImage,
338
+ subscriptions, stats, logger, fetchImpl = globalThis.fetch, timeoutMs = 300000, resolveImage, onPayload,
339
339
  } = {}) {
340
340
  if (typeof subscriptions?.list !== 'function' || typeof subscriptions?.resolve !== 'function'
341
341
  || (resolveImage !== undefined && typeof resolveImage !== 'function')
342
+ || (onPayload !== undefined && typeof onPayload !== 'function')
342
343
  || typeof fetchImpl !== 'function' || !Number.isFinite(timeoutMs) || timeoutMs < 1 || timeoutMs > 300000) {
343
344
  throw failure('INVALID_INPUT', 'Invalid subscription runtime configuration', 400)
344
345
  }
@@ -388,6 +389,14 @@ export function createSubscriptionRuntime({
388
389
  onChunk(chunk)
389
390
  foldForwardOutcome(outcome, chunk)
390
391
  })
392
+ let body = payload
393
+ if (onPayload !== undefined) {
394
+ // The owner's body seam (enhancement extensions on the magpie lane):
395
+ // fail-open is the hook's own contract, but a broken seam itself must
396
+ // not take the turn down — ship the unmodified payload instead.
397
+ try { body = (await Promise.race([onPayload(payload, { provider: config.provider, model: config.model, wire: config.wire }), interrupted])) ?? payload }
398
+ catch (error) { if (controller.signal.aborted) throw error }
399
+ }
391
400
  try {
392
401
  response = await Promise.race([fetchImpl(config.endpoint, {
393
402
  method: 'POST', redirect: 'error', signal: controller.signal,
@@ -395,7 +404,7 @@ export function createSubscriptionRuntime({
395
404
  ...config.headers, 'content-type': 'application/json', accept: 'text/event-stream',
396
405
  ...(config.provider === 'gemini' ? { 'user-agent': `GeminiCLI/0.61.0/${config.model} (${process.platform}; ${process.arch}; terminal)` } : {}),
397
406
  },
398
- body: JSON.stringify(payload),
407
+ body: JSON.stringify(body),
399
408
  }), interrupted])
400
409
  } catch (error) {
401
410
  if (controller.signal.aborted) throw error
package/src/updater.js CHANGED
@@ -161,39 +161,11 @@ const restartRequiredVersions = globalThis[RESTART_MARKER] ??= new Map()
161
161
  const UPGRADE_LOCKS = Symbol.for('our-free-model.upgrade-locks')
162
162
  const upgradeLocks = globalThis[UPGRADE_LOCKS] ??= new Set()
163
163
 
164
- /** Parse `1.2.3` / `1.2.3-rc.4` into a comparable tuple; `null` when malformed. */
165
- export function parseVersion(value) {
166
- const match = /^(\d+)\.(\d+)\.(\d+)(?:-([\w.]+))?$/.exec(String(value ?? '').trim())
167
- if (match === null) return null
168
- const pre = match[4] === undefined ? null : match[4].split('.')
169
- return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]), pre }
170
- }
171
-
172
- /** -1 | 0 | 1 in semver precedence; a pre-release sorts before its release. */
173
- export function compareVersions(a, b) {
174
- const left = parseVersion(a)
175
- const right = parseVersion(b)
176
- if (left === null || right === null) return a === b ? 0 : a > b ? 1 : -1
177
- for (const key of ['major', 'minor', 'patch']) {
178
- if (left[key] !== right[key]) return left[key] < right[key] ? -1 : 1
179
- }
180
- if (left.pre === null && right.pre === null) return 0
181
- if (left.pre === null) return 1
182
- if (right.pre === null) return -1
183
- const width = Math.max(left.pre.length, right.pre.length)
184
- for (let i = 0; i < width; i += 1) {
185
- const x = left.pre[i]
186
- const y = right.pre[i]
187
- if (x === undefined) return -1
188
- if (y === undefined) return 1
189
- const xn = /^\d+$/.test(x)
190
- const yn = /^\d+$/.test(y)
191
- if (xn && yn && Number(x) !== Number(y)) return Number(x) < Number(y) ? -1 : 1
192
- if (xn !== yn) return xn ? -1 : 1
193
- if (x !== y) return x < y ? -1 : 1
194
- }
195
- return 0
196
- }
164
+ // Semver comparison lives in ./semver.js — the standalone package ships
165
+ // without the updater but with the extension platform, which compares a
166
+ // registry entry's minHost against the host version.
167
+ import { parseVersion, compareVersions } from './semver.js'
168
+ export { parseVersion, compareVersions }
197
169
 
198
170
  /** Detect a plugin install whose package files still belong to a package-manager store. */
199
171
  export function packageUsesHardLinks(pkgDir) {