@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.
- package/README.md +23 -8
- package/client.js +894 -6
- package/cordis.patch.yml +1 -1
- package/index.js +248 -11
- package/package.json +6 -2
- package/src/adapter.js +54 -6
- package/src/aggregate.js +53 -13
- package/src/bun-fetch.js +33 -10
- package/src/catalog.js +238 -58
- package/src/core.js +838 -0
- package/src/egress.js +204 -3
- package/src/engine-parts/win32-x64.000.part +0 -0
- package/src/engine-parts/win32-x64.001.part +0 -0
- package/src/engine-parts/win32-x64.002.part +0 -0
- package/src/engine-parts/win32-x64.003.part +0 -0
- package/src/engine-parts/win32-x64.004.part +0 -0
- package/src/engine-parts/win32-x64.005.part +0 -0
- package/src/engine-parts/win32-x64.006.part +0 -0
- package/src/extensions.js +1888 -0
- package/src/feed.js +6 -0
- package/src/http.js +15 -2
- package/src/kilo.js +55 -5
- package/src/magpie-engine.js +61 -2
- package/src/magpie-models.js +31 -3
- package/src/model-test.js +3 -1
- package/src/o2a.js +956 -0
- package/src/open-external.js +43 -5
- package/src/plugins.js +1240 -0
- package/src/providers/registry.json +72 -30
- package/src/semver.js +40 -0
- package/src/store.js +17 -1
- package/src/stream.js +39 -4
- package/src/subscription-completion.js +12 -3
- package/src/updater.js +5 -33
- package/src/upstream.js +283 -363
- package/vendor/channel-pack/NOTICE.md +1 -1
- package/vendor/channel-pack/pack.js +7242 -3952
- package/vendor/magpie/NOTICE.md +159 -6
- package/vendor/magpie/engine.json +58 -27
- package/src/core/completion.js +0 -196
- package/src/core/runtime.js +0 -456
- package/src/core/stats.js +0 -98
- package/src/engine-parts/win32-x64.007.part +0 -0
- 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://
|
|
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": "
|
|
3835
|
+
"name": "Auto Free",
|
|
3836
3836
|
"contextWindow": 256000,
|
|
3837
|
-
"vision":
|
|
3838
|
-
"reasoning":
|
|
3837
|
+
"vision": false,
|
|
3838
|
+
"reasoning": true
|
|
3839
3839
|
},
|
|
3840
3840
|
{
|
|
3841
|
-
"id": "nvidia/nemotron-3-super-
|
|
3842
|
-
"name": "Nemotron 3 Super
|
|
3843
|
-
"contextWindow":
|
|
3844
|
-
"vision":
|
|
3845
|
-
"reasoning":
|
|
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": "
|
|
3849
|
-
"name": "
|
|
3850
|
-
"contextWindow":
|
|
3851
|
-
"vision":
|
|
3852
|
-
"reasoning":
|
|
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": "
|
|
3856
|
-
"name": "
|
|
3855
|
+
"id": "stealth/glyph-cluster",
|
|
3856
|
+
"name": "Stealth: Glyph Cluster",
|
|
3857
3857
|
"contextWindow": 256000,
|
|
3858
|
-
"vision":
|
|
3859
|
-
"reasoning":
|
|
3858
|
+
"vision": false,
|
|
3859
|
+
"reasoning": true
|
|
3860
3860
|
},
|
|
3861
3861
|
{
|
|
3862
|
-
"id": "
|
|
3863
|
-
"name": "
|
|
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":
|
|
3866
|
-
"reasoning":
|
|
3879
|
+
"vision": false,
|
|
3880
|
+
"reasoning": true
|
|
3867
3881
|
},
|
|
3868
3882
|
{
|
|
3869
|
-
"id": "
|
|
3870
|
-
"name": "
|
|
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":
|
|
3873
|
-
"reasoning":
|
|
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
|
|
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-
|
|
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
|
-
"
|
|
3925
|
+
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
|
|
3926
|
+
"openrouter/free"
|
|
3885
3927
|
],
|
|
3886
|
-
"modelsUrl": "https://
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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) {
|