@askdkc/dsh-cli 0.12.3 → 0.13.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.
Files changed (165) hide show
  1. package/cordis.patch.yml +12 -131
  2. package/lib/types/adapter/channel/session-projection.d.ts.map +1 -1
  3. package/lib/types/adapter/channel/session-projection.js +0 -7
  4. package/lib/types/adapter/ports/channel-view.d.ts +1 -1
  5. package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
  6. package/lib/types/dsh-adapter/bundled-presets.d.ts +3 -8
  7. package/lib/types/dsh-adapter/bundled-presets.d.ts.map +1 -1
  8. package/lib/types/dsh-adapter/bundled-presets.js +5 -20
  9. package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -1
  10. package/lib/types/dsh-adapter/channel/binding-events.js +2 -9
  11. package/lib/types/dsh-adapter/channel/external-commands.d.ts.map +1 -1
  12. package/lib/types/dsh-adapter/channel/external-commands.js +9 -33
  13. package/lib/types/dsh-adapter/channel/local-actions.d.ts +2 -1
  14. package/lib/types/dsh-adapter/channel/local-actions.d.ts.map +1 -1
  15. package/lib/types/dsh-adapter/channel/local-actions.js +6 -2
  16. package/lib/types/dsh-adapter/channel/model-actions.js +3 -3
  17. package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
  18. package/lib/types/dsh-adapter/channel/model-switch.js +0 -1
  19. package/lib/types/dsh-adapter/channel/permissions.d.ts +1 -6
  20. package/lib/types/dsh-adapter/channel/permissions.d.ts.map +1 -1
  21. package/lib/types/dsh-adapter/channel/permissions.js +2 -54
  22. package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -1
  23. package/lib/types/dsh-adapter/channel/projection.js +15 -70
  24. package/lib/types/dsh-adapter/channel/session-fork.d.ts.map +1 -1
  25. package/lib/types/dsh-adapter/channel/session-fork.js +2 -5
  26. package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -1
  27. package/lib/types/dsh-adapter/channel/session-metadata.js +1 -4
  28. package/lib/types/dsh-adapter/channel/session-resume.js +2 -2
  29. package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -1
  30. package/lib/types/dsh-adapter/channel/session-rewind.js +0 -1
  31. package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -1
  32. package/lib/types/dsh-adapter/channel/session-tree-actions.js +1 -2
  33. package/lib/types/dsh-adapter/channel/session-tree.d.ts.map +1 -1
  34. package/lib/types/dsh-adapter/channel/session-tree.js +9 -62
  35. package/lib/types/dsh-adapter/channel/skill-catalog.js +1 -1
  36. package/lib/types/dsh-adapter/channel/subagent-projection.d.ts.map +1 -1
  37. package/lib/types/dsh-adapter/channel/subagent-projection.js +2 -8
  38. package/lib/types/dsh-adapter/channel/transcript.d.ts +0 -16
  39. package/lib/types/dsh-adapter/channel/transcript.d.ts.map +1 -1
  40. package/lib/types/dsh-adapter/channel/transcript.js +0 -43
  41. package/lib/types/dsh-adapter/channel.js +2 -2
  42. package/lib/types/dsh-adapter/compat/index.d.ts +5 -25
  43. package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -1
  44. package/lib/types/dsh-adapter/compat/index.js +5 -25
  45. package/lib/types/dsh-adapter/compat/liveSession.d.ts +12 -23
  46. package/lib/types/dsh-adapter/compat/liveSession.d.ts.map +1 -1
  47. package/lib/types/dsh-adapter/compat/liveSession.js +15 -68
  48. package/lib/types/dsh-adapter/compat/messages.d.ts +1 -3
  49. package/lib/types/dsh-adapter/compat/messages.d.ts.map +1 -1
  50. package/lib/types/dsh-adapter/compat/messages.js +2 -10
  51. package/lib/types/dsh-adapter/compat/persistence.d.ts +2 -3
  52. package/lib/types/dsh-adapter/compat/persistence.d.ts.map +1 -1
  53. package/lib/types/dsh-adapter/compat/persistence.js +9 -14
  54. package/lib/types/dsh-adapter/compat/sessionLog.d.ts +10 -144
  55. package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -1
  56. package/lib/types/dsh-adapter/compat/sessionLog.js +45 -275
  57. package/lib/types/dsh-adapter/compat/settings.d.ts +4 -10
  58. package/lib/types/dsh-adapter/compat/settings.d.ts.map +1 -1
  59. package/lib/types/dsh-adapter/compat/settings.js +6 -23
  60. package/lib/types/dsh-adapter/compat/shell.d.ts +2 -3
  61. package/lib/types/dsh-adapter/compat/shell.d.ts.map +1 -1
  62. package/lib/types/dsh-adapter/compat/shell.js +2 -7
  63. package/lib/types/dsh-adapter/contract.d.ts +4 -34
  64. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  65. package/lib/types/dsh-adapter/contract.js +8 -83
  66. package/lib/types/dsh-adapter/packaged-presets.d.ts +0 -19
  67. package/lib/types/dsh-adapter/packaged-presets.d.ts.map +1 -1
  68. package/lib/types/dsh-adapter/packaged-presets.js +1 -83
  69. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  70. package/lib/types/dsh-adapter/plugin.js +26 -183
  71. package/lib/types/dsh-adapter/preset-resolution.d.ts +1 -22
  72. package/lib/types/dsh-adapter/preset-resolution.d.ts.map +1 -1
  73. package/lib/types/dsh-adapter/preset-resolution.js +0 -34
  74. package/lib/types/dsh-adapter/presets.d.ts +1 -4
  75. package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
  76. package/lib/types/dsh-adapter/presets.js +3 -6
  77. package/lib/types/dsh-adapter/questions-answerer.d.ts +5 -43
  78. package/lib/types/dsh-adapter/questions-answerer.d.ts.map +1 -1
  79. package/lib/types/dsh-adapter/questions-answerer.js +7 -63
  80. package/lib/types/dsh-adapter/sessionTree.d.ts +0 -19
  81. package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -1
  82. package/lib/types/dsh-adapter/sessionTree.js +2 -112
  83. package/lib/types/dsh-adapter/sessions/header.d.ts +2 -2
  84. package/lib/types/dsh-adapter/sessions/header.d.ts.map +1 -1
  85. package/lib/types/dsh-adapter/sessions/header.js +2 -8
  86. package/lib/types/dsh-adapter/sessions/list.d.ts +7 -28
  87. package/lib/types/dsh-adapter/sessions/list.d.ts.map +1 -1
  88. package/lib/types/dsh-adapter/sessions/list.js +48 -103
  89. package/lib/types/dsh-adapter/sessions/store.d.ts +1 -2
  90. package/lib/types/dsh-adapter/sessions/store.d.ts.map +1 -1
  91. package/lib/types/dsh-adapter/sessions/store.js +7 -10
  92. package/lib/types/dsh-adapter/sharp.d.ts.map +1 -1
  93. package/lib/types/dsh-adapter/sharp.js +0 -1
  94. package/lib/types/dsh-adapter/subagents.d.ts +1 -2
  95. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
  96. package/lib/types/dsh-adapter/subagents.js +1 -12
  97. package/lib/types/dsh-adapter/trajectory/aggregate.d.ts +1 -1
  98. package/lib/types/dsh-adapter/trajectory/aggregate.js +1 -1
  99. package/lib/types/dsh-adapter/trajectory/guards.d.ts +5 -22
  100. package/lib/types/dsh-adapter/trajectory/guards.d.ts.map +1 -1
  101. package/lib/types/dsh-adapter/trajectory/guards.js +5 -23
  102. package/lib/types/dsh-adapter/trajectory/projection.d.ts +5 -21
  103. package/lib/types/dsh-adapter/trajectory/projection.d.ts.map +1 -1
  104. package/lib/types/dsh-adapter/trajectory/projection.js +4 -38
  105. package/lib/types/dsh-adapter/trajectory/types.d.ts +1 -1
  106. package/lib/types/dsh-adapter/trajectory/types.d.ts.map +1 -1
  107. package/lib/types/i18n.d.ts +2 -22
  108. package/lib/types/i18n.d.ts.map +1 -1
  109. package/lib/types/i18n.js +2 -10
  110. package/lib/types/presetPrefs.d.ts +1 -3
  111. package/lib/types/presetPrefs.d.ts.map +1 -1
  112. package/lib/types/presetPrefs.js +2 -4
  113. package/lib/types/sessionHistory.d.ts +2 -0
  114. package/lib/types/sessionHistory.d.ts.map +1 -1
  115. package/lib/types/sessionHistory.js +8 -0
  116. package/node_modules/@askdkc/dsh-auth/README.ja.md +28 -9
  117. package/node_modules/@askdkc/dsh-auth/README.md +19 -8
  118. package/node_modules/@askdkc/dsh-auth/dsh-plugin.json +3 -3
  119. package/node_modules/@askdkc/dsh-auth/lib/THIRD_PARTY_NOTICES.txt +168 -0
  120. package/node_modules/@askdkc/dsh-auth/lib/abortable.d.ts +2 -0
  121. package/node_modules/@askdkc/dsh-auth/lib/abortable.js +9 -0
  122. package/node_modules/@askdkc/dsh-auth/lib/auth-contract.d.ts +66 -0
  123. package/node_modules/@askdkc/dsh-auth/lib/auth-contract.js +1 -0
  124. package/node_modules/@askdkc/dsh-auth/lib/command.js +15 -1
  125. package/node_modules/@askdkc/dsh-auth/lib/credentials.d.ts +8 -14
  126. package/node_modules/@askdkc/dsh-auth/lib/credentials.js +1 -1
  127. package/node_modules/@askdkc/dsh-auth/lib/index.d.ts +8 -28
  128. package/node_modules/@askdkc/dsh-auth/lib/index.js +67 -65
  129. package/node_modules/@askdkc/dsh-auth/lib/interaction.d.ts +5 -5
  130. package/node_modules/@askdkc/dsh-auth/lib/opencode-adapter.d.ts +37 -0
  131. package/node_modules/@askdkc/dsh-auth/lib/opencode-adapter.js +206 -0
  132. package/node_modules/@askdkc/dsh-auth/lib/opencode-catalog.d.ts +72 -0
  133. package/node_modules/@askdkc/dsh-auth/lib/opencode-catalog.js +252 -0
  134. package/node_modules/@askdkc/dsh-auth/lib/opencode-codec.d.ts +6 -0
  135. package/node_modules/@askdkc/dsh-auth/lib/opencode-codec.js +93 -0
  136. package/node_modules/@askdkc/dsh-auth/lib/opencode-owned.generated.d.ts +113 -0
  137. package/node_modules/@askdkc/dsh-auth/lib/opencode-owned.generated.js +2866 -0
  138. package/node_modules/@askdkc/dsh-auth/lib/opencode-replay.d.ts +15 -0
  139. package/node_modules/@askdkc/dsh-auth/lib/opencode-replay.js +141 -0
  140. package/node_modules/@askdkc/dsh-auth/lib/opencode-sdk.d.ts +5 -0
  141. package/node_modules/@askdkc/dsh-auth/lib/opencode-sdk.js +43465 -0
  142. package/node_modules/@askdkc/dsh-auth/lib/opencode-sdk.js.map +7 -0
  143. package/node_modules/@askdkc/dsh-auth/lib/opencode-transport.d.ts +5 -0
  144. package/node_modules/@askdkc/dsh-auth/lib/opencode-transport.js +69 -0
  145. package/node_modules/@askdkc/dsh-auth/lib/pi-routes.d.ts +32 -0
  146. package/node_modules/@askdkc/dsh-auth/lib/pi-routes.js +45 -0
  147. package/node_modules/@askdkc/dsh-auth/lib/profiles.d.ts +2 -20
  148. package/node_modules/@askdkc/dsh-auth/lib/profiles.js +6 -41
  149. package/node_modules/@askdkc/dsh-auth/lib/routes.d.ts +20 -0
  150. package/node_modules/@askdkc/dsh-auth/lib/routes.js +13 -0
  151. package/node_modules/@askdkc/dsh-auth/lib/service.d.ts +11 -2
  152. package/node_modules/@askdkc/dsh-auth/lib/service.js +26 -16
  153. package/node_modules/@askdkc/dsh-auth/package.json +26 -15
  154. package/package.json +100 -96
  155. package/lib/types/dsh-adapter/providerGuard.d.ts +0 -75
  156. package/lib/types/dsh-adapter/providerGuard.d.ts.map +0 -1
  157. package/lib/types/dsh-adapter/providerGuard.js +0 -104
  158. package/lib/types/migrationPrefs.d.ts +0 -60
  159. package/lib/types/migrationPrefs.d.ts.map +0 -1
  160. package/lib/types/migrationPrefs.js +0 -97
  161. package/node_modules/@askdkc/dsh-auth/lib/fresh-models.d.ts +0 -59
  162. package/node_modules/@askdkc/dsh-auth/lib/fresh-models.js +0 -246
  163. package/node_modules/@askdkc/dsh-auth/lib/opencode-snapshot.generated.d.ts +0 -321
  164. package/node_modules/@askdkc/dsh-auth/lib/opencode-snapshot.generated.js +0 -528
  165. package/presets/liangshen/.dsh-tui-managed.json +0 -5
@@ -1,97 +0,0 @@
1
- /**
2
- * One-time local migrations (`~/.dsh-tui/migrations.json`), keyed by stable
3
- * migration id → ISO timestamp. Named keys (not a bare flag) so later
4
- * migrations can append entries without a format change. Best-effort like
5
- * every prefs file: a missing/corrupt file reads as "nothing applied yet",
6
- * and a failed write simply leaves the migration to retry on a later boot.
7
- */
8
- import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
9
- import { join } from 'node:path';
10
- import { DATA_DIR } from './utils/paths.js';
11
- const PREFS_DIR = DATA_DIR;
12
- const MIGRATIONS_FILE = 'migrations.json';
13
- /**
14
- * The fullscreen factory-default flip (schema + `cordis.patch.yml`, 0.9.x):
15
- * a `fullscreen: false` pinned in the settings user layer BEFORE the flip
16
- * keeps overriding the new default on every boot. The first boot past this
17
- * migration clears that stale explicit choice once — see
18
- * {@link planFullscreenFactoryMigration} for the decision table.
19
- */
20
- export const FULLSCREEN_FACTORY_MIGRATION = 'fullscreen-factory-default';
21
- /**
22
- * Read the applied-migration map.
23
- * @param dir - Prefs directory (injectable for tests).
24
- * @returns Parsed migration map; empty on any read/parse failure.
25
- */
26
- export function readAppliedMigrations(dir = PREFS_DIR) {
27
- try {
28
- const parsed = JSON.parse(readFileSync(join(dir, MIGRATIONS_FILE), 'utf8'));
29
- if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))
30
- return {};
31
- return parsed;
32
- }
33
- catch {
34
- return {};
35
- }
36
- }
37
- /**
38
- * Record a migration as applied (merge write — other keys are never dropped).
39
- * @param id - Migration id.
40
- * @param dir - Prefs directory (injectable for tests).
41
- * @returns True when the file was written, false on failure.
42
- */
43
- export function markMigrationApplied(id, dir = PREFS_DIR) {
44
- try {
45
- const current = readAppliedMigrations(dir);
46
- mkdirSync(dir, { recursive: true });
47
- writeFileSync(join(dir, MIGRATIONS_FILE), JSON.stringify({ ...current, [id]: new Date().toISOString() }, null, 2));
48
- return true;
49
- }
50
- catch {
51
- return false;
52
- }
53
- }
54
- /**
55
- * Decide the migration step from the settings scope's RESOLVED fullscreen
56
- * value. The dsh-tui namespace declares no schema default and no `base`
57
- * layer for `fullscreen`, so a resolved `false` can only come from the user
58
- * layer — the pre-flip explicit choice this migration exists to clear:
59
- * - `done` — marker present: never touch the user layer again, so a
60
- * `false` written AFTER the migration (a deliberate /settings choice)
61
- * always stands;
62
- * - `unset` — stale pre-flip explicit `false`: the caller clears it via a
63
- * settings `unset` op and boots fullscreen regardless — the doc write
64
- * is async and must not race the synchronous boot decision, so the
65
- * caller shadows the stale value out of its first settings apply;
66
- * - `mark` — nothing to clear (unset or already `true`): record the
67
- * marker now so a `false` set later is never mistaken for a stale one.
68
- */
69
- export function planFullscreenFactoryMigration(resolvedFullscreen, migrations) {
70
- if (migrations[FULLSCREEN_FACTORY_MIGRATION] !== undefined)
71
- return 'done';
72
- return resolvedFullscreen === false ? 'unset' : 'mark';
73
- }
74
- /**
75
- * Persist the plan's side effects. For `unset` the marker lands only after
76
- * the doc write succeeds — a failed write stays unmarked and the next boot
77
- * retries (that boot already ran fullscreen off the shadowed value).
78
- * Never rejects.
79
- * @param plan - Decision from {@link planFullscreenFactoryMigration}.
80
- * @param options - `unset` thunk issuing the settings write; `dir` injects
81
- * the prefs directory for tests.
82
- */
83
- export async function commitFullscreenFactoryMigration(plan, options) {
84
- if (plan === 'done')
85
- return;
86
- if (plan === 'mark') {
87
- markMigrationApplied(FULLSCREEN_FACTORY_MIGRATION, options.dir);
88
- return;
89
- }
90
- try {
91
- await options.unset();
92
- markMigrationApplied(FULLSCREEN_FACTORY_MIGRATION, options.dir);
93
- }
94
- catch {
95
- // Unmarked on purpose: retried next boot.
96
- }
97
- }
@@ -1,59 +0,0 @@
1
- /**
2
- * Fresh OpenCode rosters on top of the installed pi-ai catalog.
3
- *
4
- * The installed catalog is the wire authority (per-model `api` protocol,
5
- * base URLs, compat): overlapping ids are served untouched. The checked-in
6
- * snapshot (`opencode-snapshot.generated.ts`, refreshed from models.dev plus
7
- * the live Zen `/v1/models` rosters) contributes only roster ids the
8
- * installed catalog lacks — each synthesized with per-model protocol
9
- * resolution, never a route-level default:
10
- *
11
- * - models.dev metadata (name, reasoning, modalities, limits, cost) plus the
12
- * wire envelope (`api`, `baseUrl`, `compat`, thinking levels, image
13
- * budgets) inherited from the nearest same-route catalog sibling;
14
- * - ids neither source describes are cloned wholesale from that sibling and
15
- * keep working wherever the sibling does (provenance lives in the
16
- * snapshot, not on the model object).
17
- *
18
- * The merge only ever adds: the result always covers the installed catalog,
19
- * so a stale snapshot delays additions but can never remove working models.
20
- *
21
- * @module dsh-auth/fresh-models
22
- */
23
- import type { PiAiProvider } from './pi-ai.js';
24
- /** One allowlisted models.dev entry (see scripts/sync-opencode-models.mjs). */
25
- export interface FreshModelEntry {
26
- readonly id: string;
27
- readonly name: string;
28
- readonly reasoning?: boolean;
29
- readonly attachment?: boolean;
30
- readonly modalities: {
31
- readonly input: readonly string[];
32
- };
33
- readonly limit: {
34
- readonly context: number;
35
- readonly output?: number;
36
- };
37
- readonly cost: {
38
- readonly input: number;
39
- readonly output: number;
40
- readonly cache_read?: number;
41
- };
42
- readonly temperature?: boolean;
43
- readonly reasoning_options?: {
44
- readonly effort: readonly string[];
45
- };
46
- }
47
- export type FreshRouteId = 'opencode' | 'opencode-go';
48
- /**
49
- * Union the snapshot roster onto one catalog provider. Installed models win
50
- * on conflict and classifier-roster ids stay on pi-ai's classifier path, so
51
- * the merged list always covers the installed catalog — a stale snapshot
52
- * delays additions but never removes a working model.
53
- * @param catalog - the installed pi-ai catalog provider for one route.
54
- * @param route - the route the snapshot section is keyed under.
55
- * @returns the catalog provider, or a shallow clone serving the union.
56
- */
57
- export declare function withFreshModels(catalog: PiAiProvider, route: FreshRouteId): PiAiProvider;
58
- /** Snapshot roster ids for one route, for smoke coverage. */
59
- export declare function freshRosterIds(route: FreshRouteId): readonly string[];
@@ -1,246 +0,0 @@
1
- import { OPENCODE_SNAPSHOT } from './opencode-snapshot.generated.js';
2
- function record(value, what) {
3
- if (typeof value !== 'object' || value === null || Array.isArray(value)) {
4
- throw new Error(`dsh-auth: snapshot ${what} is not an object`);
5
- }
6
- return value;
7
- }
8
- function nonEmptyString(value) {
9
- return typeof value === 'string' && value.length > 0 ? value : undefined;
10
- }
11
- function boundedStringList(value, what, max) {
12
- if (!Array.isArray(value) || value.length > max || !value.every(item => typeof item === 'string')) {
13
- throw new Error(`dsh-auth: snapshot ${what} is not a string list`);
14
- }
15
- const list = value;
16
- if (new Set(list).size !== list.length)
17
- throw new Error(`dsh-auth: snapshot ${what} has duplicates`);
18
- return list;
19
- }
20
- function positiveInt(value) {
21
- return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : undefined;
22
- }
23
- function nonnegativeNumber(value) {
24
- return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
25
- }
26
- function parseEntry(route, key, value) {
27
- const row = record(value, `${route}/${key}`);
28
- if (row.id !== key)
29
- throw new Error(`dsh-auth: snapshot ${route}/${key} names "${String(row.id)}"`);
30
- const name = nonEmptyString(row.name);
31
- if (name === undefined)
32
- throw new Error(`dsh-auth: snapshot ${route}/${key} has no name`);
33
- const modalities = record(row.modalities ?? fail(route, key, 'modalities'), `${route}/${key} modalities`).input;
34
- if (!Array.isArray(modalities) || modalities.length === 0 || !modalities.every(m => typeof m === 'string')) {
35
- throw new Error(`dsh-auth: snapshot ${route}/${key} has no input modalities`);
36
- }
37
- const limit = record(row.limit ?? fail(route, key, 'limit'), `${route}/${key} limit`);
38
- const context = positiveInt(limit.context);
39
- if (context === undefined)
40
- throw new Error(`dsh-auth: snapshot ${route}/${key} has no context limit`);
41
- const output = limit.output === undefined ? undefined : positiveInt(limit.output);
42
- if (limit.output !== undefined && output === undefined) {
43
- throw new Error(`dsh-auth: snapshot ${route}/${key} has a malformed output limit`);
44
- }
45
- const cost = record(row.cost ?? fail(route, key, 'cost'), `${route}/${key} cost`);
46
- const input = nonnegativeNumber(cost.input);
47
- const outputCost = nonnegativeNumber(cost.output);
48
- if (input === undefined || outputCost === undefined) {
49
- throw new Error(`dsh-auth: snapshot ${route}/${key} has no pricing`);
50
- }
51
- const cacheRead = cost.cache_read === undefined ? undefined : nonnegativeNumber(cost.cache_read);
52
- if (cost.cache_read !== undefined && cacheRead === undefined) {
53
- throw new Error(`dsh-auth: snapshot ${route}/${key} has malformed cache pricing`);
54
- }
55
- const entry = {
56
- id: key,
57
- name,
58
- reasoning: typeof row.reasoning === 'boolean' ? row.reasoning : undefined,
59
- attachment: typeof row.attachment === 'boolean' ? row.attachment : undefined,
60
- temperature: typeof row.temperature === 'boolean' ? row.temperature : undefined,
61
- reasoning_options: parseEffort(route, key, row.reasoning_options),
62
- modalities: { input: modalities.slice() },
63
- limit: output === undefined ? { context } : { context, output },
64
- cost: cacheRead === undefined ? { input, output: outputCost } : { input, output: outputCost, cache_read: cacheRead },
65
- };
66
- return entry;
67
- }
68
- function parseEffort(route, key, value) {
69
- if (value === undefined)
70
- return undefined;
71
- const options = record(value, `${route}/${key} reasoning options`).effort;
72
- if (options === undefined)
73
- return undefined;
74
- if (!Array.isArray(options) || !options.every(v => typeof v === 'string')) {
75
- throw new Error(`dsh-auth: snapshot ${route}/${key} has malformed effort values`);
76
- }
77
- return { effort: options.slice() };
78
- }
79
- function fail(route, key, what) {
80
- throw new Error(`dsh-auth: snapshot ${route}/${key} has no ${what}`);
81
- }
82
- function parseRoute(route, value) {
83
- const row = record(value, route);
84
- const roster = boundedStringList(row.roster, `${route} roster`, 500);
85
- if (roster.some(id => id.length === 0))
86
- throw new Error(`dsh-auth: snapshot ${route} roster has an empty id`);
87
- const entriesRaw = record(row.entries ?? {}, `${route} entries`);
88
- const entries = {};
89
- for (const key of Object.keys(entriesRaw))
90
- entries[key] = parseEntry(route, key, entriesRaw[key]);
91
- const fallback = boundedStringList(row.fallback ?? [], `${route} fallback`, 500);
92
- return { roster, entries, fallback };
93
- }
94
- /** Validate the checked-in snapshot once; a malformed file is a boot error. */
95
- function parseSnapshotData(input) {
96
- const root = record(input, 'root');
97
- if (root.version !== 1)
98
- throw new Error('dsh-auth: snapshot has an unsupported version');
99
- if (nonEmptyString(root.generatedAt) === undefined)
100
- throw new Error('dsh-auth: snapshot has no timestamp');
101
- return {
102
- version: 1,
103
- generatedAt: root.generatedAt,
104
- piAiVersion: nonEmptyString(root.piAiVersion) ?? 'unknown',
105
- classifiers: boundedStringList(root.classifiers ?? [], 'classifiers', 500),
106
- opencode: parseRoute('opencode', root.opencode),
107
- 'opencode-go': parseRoute('opencode-go', root['opencode-go']),
108
- };
109
- }
110
- const SNAPSHOT = parseSnapshotData(OPENCODE_SNAPSHOT);
111
- /** Length of the id prefix two models share (`a-b` vs `a-c` share 2). */
112
- function sharedPrefixLength(a, b) {
113
- const end = Math.min(a.length, b.length);
114
- let length = 0;
115
- while (length < end && a[length] === b[length])
116
- length += 1;
117
- return length;
118
- }
119
- /** Wire protocols, most universal first, for deterministic sibling choice. */
120
- function apiRank(api) {
121
- if (api === 'openai-completions')
122
- return 0;
123
- if (api === 'openai-responses')
124
- return 1;
125
- if (api === 'anthropic-messages')
126
- return 2;
127
- if (api === 'google-generative-ai')
128
- return 3;
129
- return 4;
130
- }
131
- /**
132
- * Nearest same-route catalog sibling: longest shared id prefix wins, then
133
- * the most universal wire protocol, then alphabetical order. Documented
134
- * approximation, fully deterministic — the same snapshot and catalog always
135
- * resolve the same wire envelope.
136
- */
137
- function nearestSibling(catalog, id) {
138
- let best = catalog[0];
139
- if (best === undefined)
140
- throw new Error('dsh-auth: installed catalog is empty');
141
- for (const candidate of catalog) {
142
- const prefix = sharedPrefixLength(candidate.id, id);
143
- const bestPrefix = sharedPrefixLength(best.id, id);
144
- if (prefix > bestPrefix
145
- || (prefix === bestPrefix && apiRank(candidate.api) < apiRank(best.api))
146
- || (prefix === bestPrefix && apiRank(candidate.api) === apiRank(best.api) && candidate.id < best.id)) {
147
- best = candidate;
148
- }
149
- }
150
- return best;
151
- }
152
- /** pi-ai thinking levels; `off` is always unsupported, the rest map to themselves. */
153
- const THINKING_LEVELS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
154
- function thinkingLevels(effort, sibling) {
155
- const siblingMap = (sibling.thinkingLevelMap ?? {});
156
- const levels = new Set(effort);
157
- // Only narrow the sibling envelope when every effort value it names fits
158
- // inside it; otherwise the sibling map stands (documented approximation).
159
- if (![...levels].every(level => Object.hasOwn(siblingMap, level)))
160
- return sibling.thinkingLevelMap;
161
- const map = { off: null };
162
- for (const level of THINKING_LEVELS) {
163
- if (!Object.hasOwn(siblingMap, level))
164
- continue;
165
- map[level] = levels.has(level) ? level : null;
166
- }
167
- return map;
168
- }
169
- /**
170
- * Resolve one snapshot roster id to a pi-shaped model: wire envelope from
171
- * the sibling, display metadata and capacity from models.dev. pi-ai drops
172
- * video/pdf/audio modalities (text/image only) and never discloses
173
- * cache-write pricing, so both are inherited or omitted — see field notes.
174
- */
175
- function synthesize(catalog, route, id, entry) {
176
- const sibling = nearestSibling(catalog, id);
177
- // Same-route sibling, so its provider id is already the route's own.
178
- if (entry === undefined)
179
- return { ...sibling, id, name: id };
180
- const modalities = new Set(entry.modalities.input);
181
- if (!modalities.has('text')) {
182
- throw new Error(`dsh-auth: snapshot ${route}/${id} has no text modality`);
183
- }
184
- const compat = entry.temperature === false
185
- ? { ...sibling.compat, supportsTemperature: false }
186
- : sibling.compat;
187
- const input = modalities.has('image') ? ['text', 'image'] : ['text'];
188
- return {
189
- ...sibling,
190
- id,
191
- name: entry.name,
192
- reasoning: entry.reasoning ?? sibling.reasoning,
193
- input: [...input],
194
- cost: {
195
- input: entry.cost.input,
196
- output: entry.cost.output,
197
- cacheRead: entry.cost.cache_read ?? sibling.cost.cacheRead ?? 0,
198
- cacheWrite: sibling.cost.cacheWrite ?? 0,
199
- },
200
- contextWindow: entry.limit.context,
201
- maxTokens: entry.limit.output ?? sibling.maxTokens,
202
- compat,
203
- thinkingLevelMap: entry.reasoning === false
204
- ? undefined
205
- : entry.reasoning_options === undefined
206
- ? sibling.thinkingLevelMap
207
- : thinkingLevels(entry.reasoning_options.effort, sibling),
208
- type: 'chat',
209
- };
210
- }
211
- /**
212
- * Union the snapshot roster onto one catalog provider. Installed models win
213
- * on conflict and classifier-roster ids stay on pi-ai's classifier path, so
214
- * the merged list always covers the installed catalog — a stale snapshot
215
- * delays additions but never removes a working model.
216
- * @param catalog - the installed pi-ai catalog provider for one route.
217
- * @param route - the route the snapshot section is keyed under.
218
- * @returns the catalog provider, or a shallow clone serving the union.
219
- */
220
- export function withFreshModels(catalog, route) {
221
- const section = SNAPSHOT[route];
222
- const installed = new Map(catalog.getModels().map(model => [model.id, model]));
223
- const classifiers = new Set(SNAPSHOT.classifiers);
224
- const catalogModels = catalog.getModels();
225
- const extras = [];
226
- for (const id of section.roster) {
227
- if (installed.has(id) || classifiers.has(id))
228
- continue;
229
- const marker = section.fallback.includes(id);
230
- const key = marker ? undefined : section.entries[id];
231
- if (!marker && key === undefined) {
232
- throw new Error(`dsh-auth: snapshot ${route}/${id} is on the roster but has neither metadata nor a fallback marker`);
233
- }
234
- extras.push(synthesize(catalogModels, route, id, key));
235
- }
236
- if (extras.length === 0)
237
- return catalog;
238
- return {
239
- ...catalog,
240
- getModels: () => [...catalog.getModels(), ...extras],
241
- };
242
- }
243
- /** Snapshot roster ids for one route, for smoke coverage. */
244
- export function freshRosterIds(route) {
245
- return SNAPSHOT[route].roster.slice();
246
- }