@aiwayds/dsh-tui-pi 2.0.0 → 2.0.1
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 +2 -0
- package/bin/dsh-tui-pi +2 -0
- package/bin/preflight-projcache.mjs +134 -0
- package/lib/model-sync.d.ts +122 -0
- package/lib/model-sync.js +256 -0
- package/lib/model-sync.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,6 +40,8 @@ dsh plugin --profile tui add @aiwayds/dsh-dcp # optional
|
|
|
40
40
|
dsh --profile tui # launch (or: dsh-tui-pi)
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
The `dsh-tui-pi` launcher runs a silent preflight before booting dsh: it migrates legacy `session_projcache` records that are missing `identity.isSeeded`/`identity.inheritedEventCount` (records written before dsh 0.1.2-alpha.4 would otherwise crash the boot). It is idempotent — already-migrated records are never touched — and a preflight failure never blocks startup.
|
|
44
|
+
|
|
43
45
|
Everything that used to need manual patching — the canvas background, the `@deepseek-ai` module closure, the compaction backend — now happens automatically. Upgrade an existing profile after a release:
|
|
44
46
|
|
|
45
47
|
```sh
|
package/bin/dsh-tui-pi
CHANGED
|
@@ -2,4 +2,6 @@
|
|
|
2
2
|
# Convenience launcher: boots the dsh `tui` profile that mounts @aiwayds/dsh-tui-pi.
|
|
3
3
|
# Requires the dsh CLI on PATH and @aiwayds/dsh-tui-pi installed into that profile
|
|
4
4
|
# (dsh plugin --profile tui add @aiwayds/dsh-tui-pi).
|
|
5
|
+
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$(readlink -f -- "$0" 2>/dev/null || echo "$0")")" && pwd)
|
|
6
|
+
node "$SCRIPT_DIR/preflight-projcache.mjs" >/dev/null 2>&1 || true
|
|
5
7
|
exec dsh --profile tui "$@"
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Preflight migration for the dsh `session_projcache` storage domain.
|
|
4
|
+
*
|
|
5
|
+
* Background: dsh 0.1.2-alpha.4 extends the session-projection-cache record
|
|
6
|
+
* schema with `identity.isSeeded: boolean` and `identity.inheritedEventCount:
|
|
7
|
+
* number`. The boot plugin tree opens the storage domain (allSettled, no
|
|
8
|
+
* migrate hook) and fail-fast validates those fields — records written by
|
|
9
|
+
* older dsh builds (0.1.1-rc.2 era) lack them, so the whole dsh process dies
|
|
10
|
+
* with a bare stack trace before the TUI ever mounts. This script runs from
|
|
11
|
+
* the launcher (bin/dsh-tui-pi) before `exec dsh` and backfills the two
|
|
12
|
+
* fields on every legacy record under
|
|
13
|
+
*
|
|
14
|
+
* <DSH_HOME>/storages/session_projcache/sessions/session-*.json
|
|
15
|
+
*
|
|
16
|
+
* - records that already carry both fields are left byte-identical (no IO);
|
|
17
|
+
* - every changed record is backed up next to the original, then rewritten
|
|
18
|
+
* atomically (tmp file + rename);
|
|
19
|
+
* - the script NEVER blocks startup: any unexpected error is swallowed with
|
|
20
|
+
* a one-line stderr warning and a 0 exit code.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import fs from 'node:fs'
|
|
24
|
+
import os from 'node:os'
|
|
25
|
+
import path from 'node:path'
|
|
26
|
+
import { fileURLToPath } from 'node:url'
|
|
27
|
+
|
|
28
|
+
const BACKFILL = { isSeeded: false, inheritedEventCount: 0 }
|
|
29
|
+
|
|
30
|
+
function isPlainObject(value) {
|
|
31
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function backupPathFor(file) {
|
|
35
|
+
const stamp = new Date().toISOString()
|
|
36
|
+
let candidate = `${file}.bak-preflight-${stamp}`
|
|
37
|
+
for (let n = 2; fs.existsSync(candidate); n++) {
|
|
38
|
+
candidate = `${file}.bak-preflight-${stamp}-${n}`
|
|
39
|
+
}
|
|
40
|
+
return candidate
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Backfill missing identity fields on every session-*.json record under dir.
|
|
45
|
+
* Returns { checked, fixed } — `fixed` counts records rewritten (or, in
|
|
46
|
+
* check mode, records that would be rewritten). Unparsable records are
|
|
47
|
+
* warned about on stderr and skipped.
|
|
48
|
+
*/
|
|
49
|
+
export function preflightProjcache(dir, { check = false } = {}) {
|
|
50
|
+
const result = { checked: 0, fixed: 0 }
|
|
51
|
+
if (!fs.existsSync(dir)) return result
|
|
52
|
+
|
|
53
|
+
for (const name of fs.readdirSync(dir)) {
|
|
54
|
+
if (!/^session-.*\.json$/.test(name)) continue
|
|
55
|
+
const file = path.join(dir, name)
|
|
56
|
+
if (!fs.statSync(file).isFile()) continue
|
|
57
|
+
result.checked++
|
|
58
|
+
|
|
59
|
+
let text
|
|
60
|
+
try {
|
|
61
|
+
text = fs.readFileSync(file, 'utf8')
|
|
62
|
+
} catch (err) {
|
|
63
|
+
process.stderr.write(`[preflight-projcache] unreadable ${name}: ${err?.message ?? err}\n`)
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
let obj
|
|
67
|
+
try {
|
|
68
|
+
obj = JSON.parse(text)
|
|
69
|
+
} catch {
|
|
70
|
+
process.stderr.write(`[preflight-projcache] skipping unparsable ${name}\n`)
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
if (!isPlainObject(obj)) continue
|
|
74
|
+
|
|
75
|
+
let identity = obj.identity
|
|
76
|
+
if (identity === undefined) {
|
|
77
|
+
identity = {}
|
|
78
|
+
obj.identity = identity
|
|
79
|
+
}
|
|
80
|
+
if (!isPlainObject(identity)) continue
|
|
81
|
+
|
|
82
|
+
let changed = false
|
|
83
|
+
for (const [field, value] of Object.entries(BACKFILL)) {
|
|
84
|
+
if (identity[field] === undefined) {
|
|
85
|
+
identity[field] = value
|
|
86
|
+
changed = true
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!changed) continue
|
|
90
|
+
|
|
91
|
+
result.fixed++
|
|
92
|
+
if (check) continue
|
|
93
|
+
try {
|
|
94
|
+
fs.writeFileSync(backupPathFor(file), text)
|
|
95
|
+
const migrated = JSON.stringify(obj, null, 2) + (text.endsWith('\n') ? '\n' : '')
|
|
96
|
+
const tmp = `${file}.tmp-preflight-${process.pid}`
|
|
97
|
+
fs.writeFileSync(tmp, migrated)
|
|
98
|
+
fs.renameSync(tmp, file)
|
|
99
|
+
} catch (err) {
|
|
100
|
+
result.fixed--
|
|
101
|
+
process.stderr.write(`[preflight-projcache] failed to rewrite ${name}: ${err?.message ?? err}\n`)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return result
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isMainEntry() {
|
|
108
|
+
try {
|
|
109
|
+
return Boolean(process.argv[1])
|
|
110
|
+
&& fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url))
|
|
111
|
+
} catch {
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function main() {
|
|
117
|
+
const home = process.env.DSH_HOME || path.join(os.homedir(), '.dsh')
|
|
118
|
+
const dir = path.join(home, 'storages', 'session_projcache', 'sessions')
|
|
119
|
+
const check = process.argv.includes('--check')
|
|
120
|
+
const { fixed } = preflightProjcache(dir, { check })
|
|
121
|
+
if (check) process.stdout.write(`${fixed} session_projcache record(s) need migration\n`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Run the CLI shell only when executed directly — importing this module
|
|
125
|
+
// (from the tests) must have no side effects.
|
|
126
|
+
if (isMainEntry()) {
|
|
127
|
+
try {
|
|
128
|
+
main()
|
|
129
|
+
} catch (err) {
|
|
130
|
+
// Never block startup: a preflight failure is a warning, not an error.
|
|
131
|
+
process.stderr.write(`[preflight-projcache] skipped: ${err?.message ?? err}\n`)
|
|
132
|
+
}
|
|
133
|
+
process.exit(0)
|
|
134
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /model-sync (singular) — discover models for CUSTOM provider routes and
|
|
3
|
+
* merge them back into the llm-pi-ai settings section.
|
|
4
|
+
*
|
|
5
|
+
* A custom route is a hand-declared `llm-pi-ai.providers.<id>` profile that
|
|
6
|
+
* carries a `baseURL` and is NOT one of the built-in catalog routes
|
|
7
|
+
* (provider-catalog.ts): discovery against a catalog route would short-circuit
|
|
8
|
+
* back to the installed pi-ai catalog. This command interrogates
|
|
9
|
+
* each route's `GET ${baseURL}/models` through the official seam
|
|
10
|
+
* (`LlmRuntime.discoverModels`, registered for the `llm-pi-ai` namespace by
|
|
11
|
+
* dsh-llm-pi-ai with catalog short-circuit + stored-credential resolution),
|
|
12
|
+
* then merges the answer into the profile's `models` array.
|
|
13
|
+
*
|
|
14
|
+
* The merge is additive-only: existing entries are preserved verbatim (local
|
|
15
|
+
* overrides such as reasoningEfforts survive), discovered ids already present
|
|
16
|
+
* are skipped, new ids are appended with sanitized metadata. The write goes
|
|
17
|
+
* through `settings.mutate` at the revision read at execution time, with one
|
|
18
|
+
* retry after SettingsConflictError (the persistDefaultModel pattern in
|
|
19
|
+
* session.ts). Pure logic lives here as exported functions; src/index.ts only
|
|
20
|
+
* wires the command.
|
|
21
|
+
*
|
|
22
|
+
* @module dsh-tui-pi/model-sync
|
|
23
|
+
*/
|
|
24
|
+
import type { SettingsProvider } from '@deepseek-ai/dsh-settings';
|
|
25
|
+
import type { LlmRuntime } from '@deepseek-ai/dsh-llm';
|
|
26
|
+
/**
|
|
27
|
+
* The services /model-sync needs, narrowed to the exact surface it touches so
|
|
28
|
+
* tests can supply fakes without a live tree.
|
|
29
|
+
*/
|
|
30
|
+
export interface ModelSyncDeps {
|
|
31
|
+
/** The settings provider (describe for revisions, mutate for the write). */
|
|
32
|
+
settings: Pick<SettingsProvider, 'describe' | 'mutate'>;
|
|
33
|
+
/** The LLM runtime whose discoverModels serves the `llm-pi-ai` namespace. */
|
|
34
|
+
llm: Pick<LlmRuntime, 'discoverModels'>;
|
|
35
|
+
}
|
|
36
|
+
/** One models entry as stored in a profile — passed through untouched. */
|
|
37
|
+
type StoredModel = Record<string, unknown>;
|
|
38
|
+
/**
|
|
39
|
+
* A custom route selected for sync: the dict key plus the endpoint facts
|
|
40
|
+
* discovery needs. `api` is optional — discovery defaults the protocol.
|
|
41
|
+
*/
|
|
42
|
+
export interface CustomProviderRoute {
|
|
43
|
+
id: string;
|
|
44
|
+
baseURL: string;
|
|
45
|
+
api?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A sanitized discovered model — the writable shape of one endpoint row:
|
|
49
|
+
* always an id; name/capacities carried only when present and valid.
|
|
50
|
+
*/
|
|
51
|
+
export interface SanitizedModel {
|
|
52
|
+
id: string;
|
|
53
|
+
name?: string;
|
|
54
|
+
contextWindow?: number;
|
|
55
|
+
maxTokens?: number;
|
|
56
|
+
}
|
|
57
|
+
/** Outcome of merging one route's discovery into its stored models. */
|
|
58
|
+
export interface ModelSyncMergeResult {
|
|
59
|
+
/**
|
|
60
|
+
* The merged models array to store: existing entries first in their stored
|
|
61
|
+
* order, newly discovered ids appended at the end in discovery order.
|
|
62
|
+
*/
|
|
63
|
+
models: StoredModel[];
|
|
64
|
+
/** Discovered rows appended as new entries. */
|
|
65
|
+
added: number;
|
|
66
|
+
/** Existing entries preserved verbatim. */
|
|
67
|
+
kept: number;
|
|
68
|
+
/**
|
|
69
|
+
* Discovered rows dropped entirely: unusable id, or an id already covered
|
|
70
|
+
* by an existing entry (local state wins) or by an earlier row of this batch.
|
|
71
|
+
*/
|
|
72
|
+
skipped: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Select the custom (hand-declared) routes worth syncing from a providers
|
|
76
|
+
* dict: profiles carrying a non-empty `baseURL` whose key is not a built-in
|
|
77
|
+
* catalog route — a catalog key would short-circuit discovery back to the
|
|
78
|
+
* installed catalog. Sorted by route key so the
|
|
79
|
+
* report order is stable regardless of document order.
|
|
80
|
+
*/
|
|
81
|
+
export declare function selectCustomProviders(providers: Record<string, unknown> | undefined): CustomProviderRoute[];
|
|
82
|
+
/**
|
|
83
|
+
* Clean one discovered row into a writable models entry. The id is mandatory
|
|
84
|
+
* (blank/absent → undefined); name must be a non-blank string; capacities
|
|
85
|
+
* must be integers ≥ 1 — the llm-pi-ai schema rejects anything else at write
|
|
86
|
+
* time (`contextWindow >= 1`, integer step), and a field that fails stays
|
|
87
|
+
* behind rather than sinking the whole row (verified against the shipped
|
|
88
|
+
* schema: absent name/contextWindow/maxTokens validate fine).
|
|
89
|
+
*/
|
|
90
|
+
export declare function sanitizeDiscoveredModel(raw: unknown): SanitizedModel | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* Merge one route's discovery into its stored models — additive-only:
|
|
93
|
+
*
|
|
94
|
+
* - existing entries are the base and are preserved VERBATIM (local overrides
|
|
95
|
+
* like reasoningEfforts/input/compat survive; nothing is overwritten or
|
|
96
|
+
* deleted);
|
|
97
|
+
* - discovered rows join by id: an id already present (or repeated within the
|
|
98
|
+
* batch) counts as skipped, local state wins;
|
|
99
|
+
* - new ids are appended with `sanitizeDiscoveredModel`'s cleaned fields;
|
|
100
|
+
* - the stored order is never touched: existing entries keep their positions
|
|
101
|
+
* (id-less rows included) and new ids join at the end in discovery order.
|
|
102
|
+
*/
|
|
103
|
+
export declare function mergeModels(existing: ReadonlyArray<object>, discovered: ReadonlyArray<unknown>): ModelSyncMergeResult;
|
|
104
|
+
/**
|
|
105
|
+
* Run one /model-sync round: select the target routes (the rawInput-named one,
|
|
106
|
+
* or every custom route), discover each endpoint's models, merge, and write
|
|
107
|
+
* back. One route's failure never aborts the rest — its line reports the
|
|
108
|
+
* reason instead. Agentless by design: nothing here touches a session.
|
|
109
|
+
*
|
|
110
|
+
* @returns the command result — per-provider `<id>: added N · kept M ·
|
|
111
|
+
* skipped K` lines on success, `<id>: <reason>` for failed routes; `kind:
|
|
112
|
+
* 'error'` only for preconditions (missing namespace, unknown/non-custom
|
|
113
|
+
* named route) or when EVERY route failed.
|
|
114
|
+
*/
|
|
115
|
+
export declare function runModelSync(deps: ModelSyncDeps, options?: {
|
|
116
|
+
rawInput?: string;
|
|
117
|
+
signal?: AbortSignal;
|
|
118
|
+
}): Promise<{
|
|
119
|
+
kind: 'success' | 'error';
|
|
120
|
+
text: string;
|
|
121
|
+
}>;
|
|
122
|
+
export {};
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /model-sync (singular) — discover models for CUSTOM provider routes and
|
|
3
|
+
* merge them back into the llm-pi-ai settings section.
|
|
4
|
+
*
|
|
5
|
+
* A custom route is a hand-declared `llm-pi-ai.providers.<id>` profile that
|
|
6
|
+
* carries a `baseURL` and is NOT one of the built-in catalog routes
|
|
7
|
+
* (provider-catalog.ts): discovery against a catalog route would short-circuit
|
|
8
|
+
* back to the installed pi-ai catalog. This command interrogates
|
|
9
|
+
* each route's `GET ${baseURL}/models` through the official seam
|
|
10
|
+
* (`LlmRuntime.discoverModels`, registered for the `llm-pi-ai` namespace by
|
|
11
|
+
* dsh-llm-pi-ai with catalog short-circuit + stored-credential resolution),
|
|
12
|
+
* then merges the answer into the profile's `models` array.
|
|
13
|
+
*
|
|
14
|
+
* The merge is additive-only: existing entries are preserved verbatim (local
|
|
15
|
+
* overrides such as reasoningEfforts survive), discovered ids already present
|
|
16
|
+
* are skipped, new ids are appended with sanitized metadata. The write goes
|
|
17
|
+
* through `settings.mutate` at the revision read at execution time, with one
|
|
18
|
+
* retry after SettingsConflictError (the persistDefaultModel pattern in
|
|
19
|
+
* session.ts). Pure logic lives here as exported functions; src/index.ts only
|
|
20
|
+
* wires the command.
|
|
21
|
+
*
|
|
22
|
+
* @module dsh-tui-pi/model-sync
|
|
23
|
+
*/
|
|
24
|
+
import { settingsNamespace, SettingsConflictError } from '@deepseek-ai/dsh-settings';
|
|
25
|
+
import { catalogEntry } from "./provider-catalog.js";
|
|
26
|
+
/** The settings namespace dsh-llm-pi-ai owns its provider routes under. */
|
|
27
|
+
const NS_LLM_PI_AI = settingsNamespace('llm-pi-ai');
|
|
28
|
+
/**
|
|
29
|
+
* Read the providers dict out of an llm-pi-ai descriptor value. Tolerant of
|
|
30
|
+
* every malformed shape — a section that is not an object yields no routes.
|
|
31
|
+
*/
|
|
32
|
+
function readProviders(value) {
|
|
33
|
+
if (typeof value !== 'object' || value === null)
|
|
34
|
+
return {};
|
|
35
|
+
const providers = value.providers;
|
|
36
|
+
if (typeof providers !== 'object' || providers === null)
|
|
37
|
+
return {};
|
|
38
|
+
return providers;
|
|
39
|
+
}
|
|
40
|
+
function readProfile(raw) {
|
|
41
|
+
const source = typeof raw === 'object' && raw !== null ? raw : {};
|
|
42
|
+
const baseURL = typeof source.baseURL === 'string' && source.baseURL !== '' ? source.baseURL : undefined;
|
|
43
|
+
const api = typeof source.api === 'string' && source.api !== '' ? source.api : undefined;
|
|
44
|
+
const rawModels = Array.isArray(source.models) ? source.models : [];
|
|
45
|
+
// Only well-formed entries are merged against; a malformed stored row is
|
|
46
|
+
// left alone rather than deleted (additive-only merge).
|
|
47
|
+
const models = rawModels.filter((entry) => typeof entry === 'object' && entry !== null);
|
|
48
|
+
return { ...(baseURL !== undefined ? { baseURL } : {}), ...(api !== undefined ? { api } : {}), models };
|
|
49
|
+
}
|
|
50
|
+
/** The stored entry's id, when it is a non-empty string. */
|
|
51
|
+
function storedModelId(entry) {
|
|
52
|
+
const id = entry.id;
|
|
53
|
+
return typeof id === 'string' && id !== '' ? id : undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Select the custom (hand-declared) routes worth syncing from a providers
|
|
57
|
+
* dict: profiles carrying a non-empty `baseURL` whose key is not a built-in
|
|
58
|
+
* catalog route — a catalog key would short-circuit discovery back to the
|
|
59
|
+
* installed catalog. Sorted by route key so the
|
|
60
|
+
* report order is stable regardless of document order.
|
|
61
|
+
*/
|
|
62
|
+
export function selectCustomProviders(providers) {
|
|
63
|
+
const selected = [];
|
|
64
|
+
for (const [id, raw] of Object.entries(providers ?? {})) {
|
|
65
|
+
if (catalogEntry(id) !== undefined)
|
|
66
|
+
continue;
|
|
67
|
+
const profile = readProfile(raw);
|
|
68
|
+
if (profile.baseURL === undefined)
|
|
69
|
+
continue;
|
|
70
|
+
selected.push({
|
|
71
|
+
id,
|
|
72
|
+
baseURL: profile.baseURL,
|
|
73
|
+
...(profile.api !== undefined ? { api: profile.api } : {}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
selected.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
77
|
+
return selected;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Clean one discovered row into a writable models entry. The id is mandatory
|
|
81
|
+
* (blank/absent → undefined); name must be a non-blank string; capacities
|
|
82
|
+
* must be integers ≥ 1 — the llm-pi-ai schema rejects anything else at write
|
|
83
|
+
* time (`contextWindow >= 1`, integer step), and a field that fails stays
|
|
84
|
+
* behind rather than sinking the whole row (verified against the shipped
|
|
85
|
+
* schema: absent name/contextWindow/maxTokens validate fine).
|
|
86
|
+
*/
|
|
87
|
+
export function sanitizeDiscoveredModel(raw) {
|
|
88
|
+
if (typeof raw !== 'object' || raw === null)
|
|
89
|
+
return undefined;
|
|
90
|
+
const row = raw;
|
|
91
|
+
if (typeof row.id !== 'string')
|
|
92
|
+
return undefined;
|
|
93
|
+
const id = row.id.trim();
|
|
94
|
+
if (id === '')
|
|
95
|
+
return undefined;
|
|
96
|
+
const name = typeof row.name === 'string' && row.name.trim() !== '' ? row.name.trim() : undefined;
|
|
97
|
+
const capacity = (value) => typeof value === 'number' && Number.isInteger(value) && value >= 1 ? value : undefined;
|
|
98
|
+
const contextWindow = capacity(row.contextWindow);
|
|
99
|
+
const maxTokens = capacity(row.maxTokens);
|
|
100
|
+
return {
|
|
101
|
+
id,
|
|
102
|
+
...(name !== undefined ? { name } : {}),
|
|
103
|
+
...(contextWindow !== undefined ? { contextWindow } : {}),
|
|
104
|
+
...(maxTokens !== undefined ? { maxTokens } : {}),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Merge one route's discovery into its stored models — additive-only:
|
|
109
|
+
*
|
|
110
|
+
* - existing entries are the base and are preserved VERBATIM (local overrides
|
|
111
|
+
* like reasoningEfforts/input/compat survive; nothing is overwritten or
|
|
112
|
+
* deleted);
|
|
113
|
+
* - discovered rows join by id: an id already present (or repeated within the
|
|
114
|
+
* batch) counts as skipped, local state wins;
|
|
115
|
+
* - new ids are appended with `sanitizeDiscoveredModel`'s cleaned fields;
|
|
116
|
+
* - the stored order is never touched: existing entries keep their positions
|
|
117
|
+
* (id-less rows included) and new ids join at the end in discovery order.
|
|
118
|
+
*/
|
|
119
|
+
export function mergeModels(existing, discovered) {
|
|
120
|
+
const models = existing.map(entry => entry);
|
|
121
|
+
const ids = new Set();
|
|
122
|
+
for (const entry of models) {
|
|
123
|
+
const id = storedModelId(entry);
|
|
124
|
+
if (id !== undefined)
|
|
125
|
+
ids.add(id);
|
|
126
|
+
}
|
|
127
|
+
let added = 0;
|
|
128
|
+
let skipped = 0;
|
|
129
|
+
for (const raw of discovered) {
|
|
130
|
+
const clean = sanitizeDiscoveredModel(raw);
|
|
131
|
+
if (clean === undefined || ids.has(clean.id)) {
|
|
132
|
+
skipped += 1;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
ids.add(clean.id);
|
|
136
|
+
// A fresh literal (not a cast): interfaces carry no implicit index
|
|
137
|
+
// signature, so the entry must be rebuilt as a plain record.
|
|
138
|
+
models.push({ ...clean });
|
|
139
|
+
added += 1;
|
|
140
|
+
}
|
|
141
|
+
// No reordering: existing entries stay exactly where the user stored them
|
|
142
|
+
// and new ids are appended at the end in discovery order — a no-op sync
|
|
143
|
+
// therefore leaves the stored array byte-identical.
|
|
144
|
+
return { models, added, kept: existing.length, skipped };
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Write one route's merged models with optimistic concurrency: mutate at the
|
|
148
|
+
* revision read NOW (not at command start), one retry after
|
|
149
|
+
* SettingsConflictError with a freshly read revision — the same bottom pattern
|
|
150
|
+
* as persistDefaultModel in session.ts. A second conflict surfaces.
|
|
151
|
+
*/
|
|
152
|
+
async function writeModels(settings, providerId, models) {
|
|
153
|
+
const ops = [
|
|
154
|
+
{ op: 'set', path: ['providers', providerId, 'models'], value: models },
|
|
155
|
+
];
|
|
156
|
+
const ns = NS_LLM_PI_AI;
|
|
157
|
+
for (let attempt = 0;; attempt++) {
|
|
158
|
+
const descriptor = currentDescriptor(settings);
|
|
159
|
+
try {
|
|
160
|
+
await settings.mutate(ns, ops, descriptor?.revision);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (attempt === 0 && error instanceof SettingsConflictError)
|
|
165
|
+
continue;
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/** The live llm-pi-ai descriptor, re-read on demand (revision freshness). */
|
|
171
|
+
function currentDescriptor(settings) {
|
|
172
|
+
return settings.describe().find(d => d.ns === NS_LLM_PI_AI);
|
|
173
|
+
}
|
|
174
|
+
function errorText(error) {
|
|
175
|
+
return error instanceof Error ? error.message : String(error);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Run one /model-sync round: select the target routes (the rawInput-named one,
|
|
179
|
+
* or every custom route), discover each endpoint's models, merge, and write
|
|
180
|
+
* back. One route's failure never aborts the rest — its line reports the
|
|
181
|
+
* reason instead. Agentless by design: nothing here touches a session.
|
|
182
|
+
*
|
|
183
|
+
* @returns the command result — per-provider `<id>: added N · kept M ·
|
|
184
|
+
* skipped K` lines on success, `<id>: <reason>` for failed routes; `kind:
|
|
185
|
+
* 'error'` only for preconditions (missing namespace, unknown/non-custom
|
|
186
|
+
* named route) or when EVERY route failed.
|
|
187
|
+
*/
|
|
188
|
+
export async function runModelSync(deps, options = {}) {
|
|
189
|
+
const descriptor = currentDescriptor(deps.settings);
|
|
190
|
+
if (descriptor === undefined) {
|
|
191
|
+
return { kind: 'error', text: 'The llm-pi-ai settings namespace is not registered — nothing to sync.' };
|
|
192
|
+
}
|
|
193
|
+
const wanted = options.rawInput?.trim() ?? '';
|
|
194
|
+
let targets;
|
|
195
|
+
if (wanted !== '') {
|
|
196
|
+
const providers = readProviders(descriptor.value);
|
|
197
|
+
if (!(wanted in providers)) {
|
|
198
|
+
return { kind: 'error', text: `Provider "${wanted}" is not configured under llm-pi-ai providers.` };
|
|
199
|
+
}
|
|
200
|
+
targets = selectCustomProviders({ [wanted]: providers[wanted] });
|
|
201
|
+
if (targets.length === 0) {
|
|
202
|
+
// Two distinct shapes land here: a built-in catalog key (excluded by
|
|
203
|
+
// selection even when the profile carries a baseURL) and a genuinely
|
|
204
|
+
// non-custom route without a baseURL. Name each accurately.
|
|
205
|
+
if (catalogEntry(wanted) !== undefined) {
|
|
206
|
+
return {
|
|
207
|
+
kind: 'error',
|
|
208
|
+
text: `Provider "${wanted}" is a built-in catalog route — /model-sync syncs hand-declared (baseURL) routes only.`,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
kind: 'error',
|
|
213
|
+
text: `Provider "${wanted}" has no baseURL — /model-sync syncs hand-declared routes only.`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
targets = selectCustomProviders(readProviders(descriptor.value));
|
|
219
|
+
if (targets.length === 0) {
|
|
220
|
+
return { kind: 'success', text: 'No hand-declared (baseURL) providers configured — nothing to sync.' };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const lines = [];
|
|
224
|
+
let failures = 0;
|
|
225
|
+
for (const target of targets) {
|
|
226
|
+
try {
|
|
227
|
+
// Re-read the live section per route: an api added/removed between the
|
|
228
|
+
// selection and this write still reaches discovery (and a vanished
|
|
229
|
+
// namespace degrades into that route's failure line, not a crash).
|
|
230
|
+
const liveValue = currentDescriptor(deps.settings)?.value;
|
|
231
|
+
const profile = readProfile(readProviders(liveValue)[target.id]);
|
|
232
|
+
const discovered = await deps.llm.discoverModels(NS_LLM_PI_AI, {
|
|
233
|
+
provider: target.id,
|
|
234
|
+
baseURL: target.baseURL,
|
|
235
|
+
...(profile.api !== undefined ? { api: profile.api } : {}),
|
|
236
|
+
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
237
|
+
});
|
|
238
|
+
const merged = mergeModels(profile.models, discovered);
|
|
239
|
+
// A no-op sync must produce zero settings mutations: write only when
|
|
240
|
+
// discovery actually contributed new ids.
|
|
241
|
+
if (merged.added > 0) {
|
|
242
|
+
await writeModels(deps.settings, target.id, merged.models);
|
|
243
|
+
}
|
|
244
|
+
lines.push(`${target.id}: added ${merged.added} · kept ${merged.kept} · skipped ${merged.skipped}`);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
failures += 1;
|
|
248
|
+
lines.push(`${target.id}: ${errorText(error)}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (failures === targets.length && failures > 0) {
|
|
252
|
+
return { kind: 'error', text: lines.join('\n') };
|
|
253
|
+
}
|
|
254
|
+
return { kind: 'success', text: lines.join('\n') };
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=model-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-sync.js","sourceRoot":"","sources":["../src/model-sync.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED