@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
@@ -0,0 +1,252 @@
1
+ /** Owned OpenCode catalog. Public listings are untrusted; no pi catalog participates. */
2
+ import { mkdir, readFile, rename, writeFile, rm } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { homedir } from 'node:os';
5
+ import { abortable } from './abortable.js';
6
+ import { randomUUID } from 'node:crypto';
7
+ export const OPEN_CODE_ROUTES = ['opencode', 'opencode-go'];
8
+ export const OPEN_CODE_URLS = {
9
+ opencode: 'https://opencode.ai/zen/v1', 'opencode-go': 'https://opencode.ai/zen/go/v1',
10
+ };
11
+ const PROTOCOLS = {
12
+ '@ai-sdk/anthropic': 'messages', '@ai-sdk/openai': 'responses',
13
+ '@ai-sdk/openai-compatible': 'chat', '@ai-sdk/google': 'google',
14
+ };
15
+ function object(value) {
16
+ if (!value || typeof value !== 'object' || Array.isArray(value))
17
+ throw new Error('catalog expected an object');
18
+ return value;
19
+ }
20
+ function positive(value) { return typeof value === 'number' && Number.isSafeInteger(value) && value > 0; }
21
+ export function rosterIds(payload) {
22
+ const data = object(payload).data;
23
+ if (!Array.isArray(data) || !data.length)
24
+ throw new Error('catalog has no non-empty model roster');
25
+ return [...new Set(data.map(row => {
26
+ const id = object(row).id;
27
+ if (typeof id !== 'string' || !id.trim() || id.length > 512)
28
+ throw new Error('catalog has an invalid model id');
29
+ return id;
30
+ }))].sort();
31
+ }
32
+ /** Normalize only exact-route metadata; every roster id has an explicit outcome. */
33
+ export function normalizeCatalog(provider, roster, metadata, fetchedAt, previous) {
34
+ const section = object(metadata);
35
+ const entries = object(section.models);
36
+ const models = [];
37
+ const excluded = [];
38
+ for (const id of [...new Set(roster)].sort()) {
39
+ try {
40
+ if (entries[id] === undefined) {
41
+ const old = previous?.provider === provider ? previous.models.find(model => model.id === id) : undefined;
42
+ if (old) {
43
+ models.push(structuredClone(old));
44
+ continue;
45
+ }
46
+ throw new Error('metadata unavailable for this route and id');
47
+ }
48
+ const raw = object(entries[id]);
49
+ const wire = raw.provider === undefined ? section.npm : object(raw.provider).npm;
50
+ const protocol = typeof wire === 'string' && Object.hasOwn(PROTOCOLS, wire) ? PROTOCOLS[wire] : undefined;
51
+ const limit = object(raw.limit);
52
+ const modalities = object(raw.modalities);
53
+ if (!protocol)
54
+ throw new Error('unsupported or unknown wire protocol');
55
+ if (!positive(limit.context) || !positive(limit.output))
56
+ throw new Error('context or output limit unavailable');
57
+ if (!Array.isArray(modalities.input) || !modalities.input.includes('text') || !Array.isArray(modalities.output) || !modalities.output.includes('text'))
58
+ throw new Error('not a verified text-generation model');
59
+ if (typeof raw.tool_call !== 'boolean' || typeof raw.reasoning !== 'boolean' || typeof raw.temperature !== 'boolean')
60
+ throw new Error('model capabilities unavailable');
61
+ const effortOption = Array.isArray(raw.reasoning_options) ? raw.reasoning_options.map(object).find(option => option.type === 'effort') : undefined;
62
+ const efforts = effortOption?.values ?? [];
63
+ if (!Array.isArray(efforts) || !efforts.every(value => typeof value === 'string'))
64
+ throw new Error('invalid reasoning efforts');
65
+ const cost = Object.fromEntries(Object.entries(raw.cost === undefined ? {} : object(raw.cost)).filter(([, value]) => typeof value === 'number' && Number.isFinite(value) && value >= 0));
66
+ models.push({ id, name: typeof raw.name === 'string' ? raw.name : id, protocol, contextWindow: limit.context, maxTokens: limit.output,
67
+ inputModalities: modalities.input.includes('image') ? ['text', 'image'] : ['text'], toolCall: raw.tool_call, reasoning: raw.reasoning,
68
+ temperature: raw.temperature, efforts: [...efforts], ...(Object.keys(cost).length ? { cost } : {}) });
69
+ }
70
+ catch (error) {
71
+ excluded.push({ id, reason: error instanceof Error ? error.message : 'invalid metadata' });
72
+ }
73
+ }
74
+ return { version: 1, provider, fetchedAt, roster: [...new Set(roster)].sort(), models, excluded };
75
+ }
76
+ /** Validate cache/snapshot data using the same normalizer as fetched metadata. */
77
+ export function parseSnapshot(value, provider) {
78
+ const raw = object(value);
79
+ if (raw.version !== 1 || raw.provider !== provider || typeof raw.fetchedAt !== 'number' || !Number.isSafeInteger(raw.fetchedAt) || raw.fetchedAt < 0 || !Array.isArray(raw.roster) || !raw.roster.length || !raw.roster.every(id => typeof id === 'string' && id.trim() && id.length <= 512) || !Array.isArray(raw.models) || !Array.isArray(raw.excluded))
80
+ throw new Error('invalid catalog snapshot');
81
+ const roster = raw.roster;
82
+ if (new Set(roster).size !== roster.length)
83
+ throw new Error('duplicate snapshot roster id');
84
+ const models = raw.models.map(value => {
85
+ const model = object(value);
86
+ if (typeof model.id !== 'string' || typeof model.protocol !== 'string' || !Object.values(PROTOCOLS).includes(model.protocol) || !positive(model.contextWindow) || !positive(model.maxTokens) || typeof model.name !== 'string' || !model.name || !Array.isArray(model.inputModalities) || !model.inputModalities.includes('text') || model.inputModalities.some(v => v !== 'text' && v !== 'image') || !Array.isArray(model.efforts) || !model.efforts.every(v => typeof v === 'string' && v.length > 0) || [model.toolCall, model.reasoning, model.temperature].some(v => typeof v !== 'boolean'))
87
+ throw new Error('invalid cached model');
88
+ const cost = model.cost === undefined ? undefined : object(model.cost);
89
+ if (cost && Object.values(cost).some(value => typeof value !== 'number' || !Number.isFinite(value) || value < 0))
90
+ throw new Error('invalid cached cost');
91
+ return { id: model.id, name: model.name, protocol: model.protocol, contextWindow: model.contextWindow, maxTokens: model.maxTokens,
92
+ inputModalities: [...new Set(model.inputModalities)], efforts: [...new Set(model.efforts)],
93
+ toolCall: model.toolCall, reasoning: model.reasoning, temperature: model.temperature,
94
+ ...(cost ? { cost: { ...cost } } : {}) };
95
+ });
96
+ const excluded = raw.excluded.map(value => { const entry = object(value); if (typeof entry.id !== 'string' || typeof entry.reason !== 'string' || !entry.reason)
97
+ throw new Error('invalid exclusion reason'); return { id: entry.id, reason: entry.reason }; });
98
+ const ids = [...models, ...excluded].map(model => model.id);
99
+ if (new Set(ids).size !== ids.length || ids.length !== roster.length || ids.some(id => !roster.includes(id)))
100
+ throw new Error('incomplete catalog snapshot');
101
+ return { version: 1, provider, fetchedAt: raw.fetchedAt, roster: [...roster], models, excluded };
102
+ }
103
+ export async function fetchCatalogJson(url, fetcher, signal) {
104
+ const timeout = AbortSignal.any([AbortSignal.timeout(30_000), ...(signal ? [signal] : [])]);
105
+ const response = await abortable(fetcher(url, { signal: timeout, headers: { accept: 'application/json' }, redirect: 'error' }), timeout);
106
+ if (!response.ok)
107
+ throw new Error(`catalog request failed: HTTP ${response.status}`);
108
+ if (!response.body)
109
+ throw new Error('empty catalog response');
110
+ const reader = response.body.getReader();
111
+ const chunks = [];
112
+ let size = 0;
113
+ try {
114
+ while (true) {
115
+ const item = await abortable(reader.read(), timeout);
116
+ if (item.done)
117
+ break;
118
+ size += item.value.byteLength;
119
+ if (size > 32 * 1024 * 1024)
120
+ throw new Error('catalog exceeds 32 MiB');
121
+ chunks.push(item.value);
122
+ }
123
+ return JSON.parse(Buffer.concat(chunks).toString('utf8'));
124
+ }
125
+ finally {
126
+ void reader.cancel().catch(() => undefined);
127
+ reader.releaseLock();
128
+ }
129
+ }
130
+ export class OpenCodeCatalog {
131
+ provider;
132
+ options;
133
+ snapshot;
134
+ source = 'bundled';
135
+ warning;
136
+ pending;
137
+ disposed = false;
138
+ abort = new AbortController();
139
+ timer;
140
+ started = false;
141
+ constructor(provider, bundled, options = {}) {
142
+ this.provider = provider;
143
+ this.options = options;
144
+ this.snapshot = parseSnapshot(bundled, provider);
145
+ }
146
+ get path() { return join(this.options.directory ?? join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'dsh-auth', 'catalog-v1'), `${this.provider}.json`); }
147
+ models() { return structuredClone(this.snapshot.models); }
148
+ status() { return { provider: this.provider, source: this.source, fetchedAt: this.snapshot.fetchedAt, available: this.snapshot.models.length, excluded: structuredClone(this.snapshot.excluded), ...(this.warning ? { warning: this.warning } : {}) }; }
149
+ async start() {
150
+ if (this.started || this.disposed)
151
+ return;
152
+ this.started = true;
153
+ try {
154
+ const cached = parseSnapshot(JSON.parse(await readFile(this.path, 'utf8')), this.provider);
155
+ if (!this.disposed && cached.fetchedAt > this.snapshot.fetchedAt) {
156
+ this.snapshot = cached;
157
+ this.source = 'cache';
158
+ this.options.changed?.();
159
+ }
160
+ }
161
+ catch (error) {
162
+ if (error.code !== 'ENOENT')
163
+ this.warning = 'catalog cache unreadable; using bundled information';
164
+ }
165
+ if (this.disposed)
166
+ return;
167
+ if ((this.options.now?.() ?? Date.now()) - this.snapshot.fetchedAt >= 3_600_000)
168
+ void this.refresh();
169
+ this.timer = setInterval(() => { void this.refresh(); }, 3_600_000);
170
+ this.timer.unref();
171
+ }
172
+ refresh() {
173
+ if (this.disposed)
174
+ return Promise.reject(new Error('catalog disposed'));
175
+ if (this.pending)
176
+ return this.pending;
177
+ this.pending = this.update().finally(() => { this.pending = undefined; });
178
+ return this.pending;
179
+ }
180
+ async update() {
181
+ const startedAt = this.options.now?.() ?? Date.now();
182
+ try {
183
+ const [listing, details] = await Promise.all([fetchCatalogJson(`${OPEN_CODE_URLS[this.provider]}/models`, this.options.fetcher ?? fetch, this.abort.signal), fetchCatalogJson('https://models.dev/api.json', this.options.fetcher ?? fetch, this.abort.signal).then(value => ({ value, failed: false }), () => ({ value: undefined, failed: true }))]);
184
+ let section = { models: {} };
185
+ let detailsUnavailable = details.failed;
186
+ if (!details.failed) {
187
+ try {
188
+ section = object(details.value)[this.provider] ?? { models: {} };
189
+ }
190
+ catch {
191
+ detailsUnavailable = true;
192
+ }
193
+ }
194
+ const next = normalizeCatalog(this.provider, rosterIds(listing), section, startedAt, this.snapshot);
195
+ if (this.disposed || next.fetchedAt < this.snapshot.fetchedAt)
196
+ return this.status();
197
+ this.snapshot = next;
198
+ this.source = 'live';
199
+ this.warning = detailsUnavailable ? 'model details unavailable; reusing exact-route verified metadata' : undefined;
200
+ this.options.changed?.();
201
+ try {
202
+ await this.save(next);
203
+ }
204
+ catch {
205
+ if (!this.disposed)
206
+ this.warning = 'catalog updated in memory; cache save failed';
207
+ }
208
+ }
209
+ catch {
210
+ if (!this.disposed)
211
+ this.warning = 'catalog refresh failed; retaining last verified information';
212
+ }
213
+ return this.status();
214
+ }
215
+ /** Compare and replace under a bounded cross-process lock; failed writes retain the old cache. */
216
+ async save(next) {
217
+ await mkdir(join(this.path, '..'), { recursive: true });
218
+ const lock = `${this.path}.lock`;
219
+ const deadline = Date.now() + 5000;
220
+ while (true) {
221
+ this.abort.signal.throwIfAborted();
222
+ try {
223
+ await mkdir(lock);
224
+ break;
225
+ }
226
+ catch (error) {
227
+ if (error.code !== 'EEXIST' || Date.now() >= deadline)
228
+ throw error;
229
+ await new Promise(resolve => setTimeout(resolve, 25));
230
+ }
231
+ }
232
+ const temp = `${this.path}.${randomUUID()}.tmp`;
233
+ try {
234
+ try {
235
+ const current = parseSnapshot(JSON.parse(await readFile(this.path, 'utf8')), this.provider);
236
+ if (current.fetchedAt > next.fetchedAt)
237
+ return;
238
+ }
239
+ catch { /* An invalid cache may be replaced by verified public data. */ }
240
+ this.abort.signal.throwIfAborted();
241
+ await writeFile(temp, JSON.stringify(next), { mode: 0o600 });
242
+ this.abort.signal.throwIfAborted();
243
+ await rename(temp, this.path);
244
+ }
245
+ finally {
246
+ await rm(temp, { force: true });
247
+ await rm(lock, { recursive: true });
248
+ }
249
+ }
250
+ dispose() { this.disposed = true; this.abort.abort(); if (this.timer)
251
+ clearInterval(this.timer); }
252
+ }
@@ -0,0 +1,6 @@
1
+ /** Convert DSH conversation blocks into low-level SDK prompts without an agent loop. */
2
+ import { type GenerateOptions } from '@deepseek-ai/dsh-llm';
3
+ import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
4
+ import type { LanguageModelV4Prompt } from '@ai-sdk/provider';
5
+ import type { OpenCodeModel } from './opencode-catalog.js';
6
+ export declare function encodePrompt(options: GenerateOptions, model: OpenCodeModel, attachments?: () => AttachmentStore | undefined, warn?: (reason: string) => void): Promise<LanguageModelV4Prompt>;
@@ -0,0 +1,93 @@
1
+ /** Convert DSH conversation blocks into low-level SDK prompts without an agent loop. */
2
+ import { LlmError, projectToolUpdates, fileHandleText } from '@deepseek-ai/dsh-llm';
3
+ import { projectReplay, requireReplaySignature } from './opencode-replay.js';
4
+ export async function encodePrompt(options, model, attachments, warn) {
5
+ const prompt = [];
6
+ const names = new Map();
7
+ const callIds = new Map();
8
+ if (options.system)
9
+ prompt.push({ role: 'system', content: options.system });
10
+ const projection = projectToolUpdates(options.messages, options.tools, undefined, options.toolHistory);
11
+ let imageBytes = 0;
12
+ const imagePart = async (block) => {
13
+ if (block.offloaded || !model.inputModalities.includes('image'))
14
+ return { type: 'text', text: '[image omitted from this request]' };
15
+ const store = attachments?.();
16
+ if (!store)
17
+ throw new LlmError('Image attachment service unavailable', 'ATTACHMENT_UNAVAILABLE');
18
+ const { requestImageDimensions } = await import('@deepseek-ai/dsh-attachment');
19
+ const image = await store.readImageRequest(block.attachment, { ...requestImageDimensions(block.attachment.width, block.attachment.height, 4_194_304), maxBytes: 1024 * 1024 }, options.signal);
20
+ imageBytes += Math.ceil(image.bytes / 3) * 4;
21
+ if (imageBytes > 20 * 1024 * 1024)
22
+ throw new LlmError('Image request exceeds 20 MiB', 'IMAGE_OFFLOAD_REQUIRED', { offloadImages: 1 });
23
+ return { type: 'file', mediaType: image.mediaType, data: { type: 'data', data: image.data } };
24
+ };
25
+ for (const message of projection.messages) {
26
+ if (message.role === 'system') {
27
+ prompt.push({ role: 'system', content: message.content.filter(block => block.type === 'text').map(block => block.text).join('\n') });
28
+ continue;
29
+ }
30
+ if (message.role === 'developer') {
31
+ const text = message.content.filter(block => block.type === 'text').map(block => block.text).join('\n');
32
+ if (text)
33
+ prompt.push({ role: 'system', content: text });
34
+ continue;
35
+ }
36
+ if (message.role === 'tool') {
37
+ const toolName = names.get(message.toolCallId);
38
+ if (!toolName)
39
+ throw new LlmError('Stored tool result has no matching call', 'INVALID_REPLAY_STATE');
40
+ const parts = [];
41
+ for (const block of message.content) {
42
+ if (block.type === 'text')
43
+ parts.push({ type: 'text', text: block.text });
44
+ else if (block.type === 'file')
45
+ parts.push({ type: 'text', text: fileHandleText(block.attachment, attachments?.()?.fileHostPath(block.attachment)) });
46
+ else if (block.type === 'image')
47
+ parts.push(await imagePart(block));
48
+ }
49
+ prompt.push({ role: 'tool', content: [{ type: 'tool-result', toolCallId: callIds.get(message.toolCallId) ?? message.toolCallId, toolName,
50
+ output: message.isError ? { type: 'error-text', value: parts.map(part => part.type === 'text' ? part.text : '[image]').join('\n') } : { type: 'content', value: parts } }] });
51
+ continue;
52
+ }
53
+ const content = [];
54
+ const replay = projectReplay('source' in message ? message.source : undefined, message.content, model, options.provider, warn);
55
+ for (const [original, wire] of replay.callIds)
56
+ callIds.set(original, wire);
57
+ for (const [index, block] of message.content.entries()) {
58
+ const providerOptions = replay.blocks[index];
59
+ if (block.type === 'text')
60
+ content.push({ type: 'text', text: block.text, providerOptions });
61
+ else if (block.type === 'file')
62
+ content.push({ type: 'text', text: fileHandleText(block.attachment, attachments?.()?.fileHostPath(block.attachment)) });
63
+ else if (block.type === 'reasoning' && message.role === 'assistant') {
64
+ requireReplaySignature(block, model.protocol, providerOptions);
65
+ content.push({ type: 'reasoning', text: block.text, providerOptions });
66
+ }
67
+ else if (block.type === 'tool-call' && message.role === 'assistant') {
68
+ if (model.protocol === 'responses' && block.id.includes('|') && !replay.callIds.has(block.id))
69
+ throw new LlmError('Cannot restore legacy Responses tool call pairing', 'INVALID_REPLAY_STATE');
70
+ if (names.has(block.id))
71
+ throw new LlmError('Duplicate stored tool call id', 'INVALID_REPLAY_STATE');
72
+ if (model.protocol === 'google' && model.reasoning && !providerOptions?.google?.thoughtSignature && !message.content.slice(0, index).some(part => part.type === 'tool-call'))
73
+ throw new LlmError('Cannot replay Google tool call without its verified thought signature', 'INVALID_REPLAY_STATE');
74
+ names.set(block.id, block.name);
75
+ let input;
76
+ try {
77
+ input = JSON.parse(block.arguments);
78
+ }
79
+ catch {
80
+ throw new LlmError('Stored tool arguments are not valid JSON', 'INVALID_REPLAY_STATE');
81
+ }
82
+ content.push({ type: 'tool-call', toolCallId: callIds.get(block.id) ?? block.id, toolName: block.name, input, providerOptions });
83
+ }
84
+ else if (block.type === 'image') {
85
+ if (message.role === 'assistant')
86
+ throw new LlmError('Structured assistant image output is unsupported', 'UNSUPPORTED_CONTENT');
87
+ content.push(await imagePart(block));
88
+ }
89
+ }
90
+ prompt.push({ role: message.role, content });
91
+ }
92
+ return prompt;
93
+ }
@@ -0,0 +1,113 @@
1
+ /** Generated from official rosters and exact-route models.dev metadata. */
2
+ export declare const OPEN_CODE_SNAPSHOTS: {
3
+ opencode: {
4
+ version: number;
5
+ provider: string;
6
+ fetchedAt: number;
7
+ roster: string[];
8
+ models: ({
9
+ id: string;
10
+ name: string;
11
+ protocol: string;
12
+ contextWindow: number;
13
+ maxTokens: number;
14
+ inputModalities: string[];
15
+ toolCall: boolean;
16
+ reasoning: boolean;
17
+ temperature: boolean;
18
+ efforts: string[];
19
+ cost: {
20
+ input: number;
21
+ output: number;
22
+ cache_read: number;
23
+ cache_write: number;
24
+ input_audio?: undefined;
25
+ };
26
+ } | {
27
+ id: string;
28
+ name: string;
29
+ protocol: string;
30
+ contextWindow: number;
31
+ maxTokens: number;
32
+ inputModalities: string[];
33
+ toolCall: boolean;
34
+ reasoning: boolean;
35
+ temperature: boolean;
36
+ efforts: string[];
37
+ cost: {
38
+ input: number;
39
+ output: number;
40
+ cache_read: number;
41
+ cache_write?: undefined;
42
+ input_audio?: undefined;
43
+ };
44
+ } | {
45
+ id: string;
46
+ name: string;
47
+ protocol: string;
48
+ contextWindow: number;
49
+ maxTokens: number;
50
+ inputModalities: string[];
51
+ toolCall: boolean;
52
+ reasoning: boolean;
53
+ temperature: boolean;
54
+ efforts: string[];
55
+ cost: {
56
+ input: number;
57
+ output: number;
58
+ cache_read: number;
59
+ input_audio: number;
60
+ cache_write?: undefined;
61
+ };
62
+ })[];
63
+ excluded: {
64
+ id: string;
65
+ reason: string;
66
+ }[];
67
+ };
68
+ "opencode-go": {
69
+ version: number;
70
+ provider: string;
71
+ fetchedAt: number;
72
+ roster: string[];
73
+ models: ({
74
+ id: string;
75
+ name: string;
76
+ protocol: string;
77
+ contextWindow: number;
78
+ maxTokens: number;
79
+ inputModalities: string[];
80
+ toolCall: boolean;
81
+ reasoning: boolean;
82
+ temperature: boolean;
83
+ efforts: string[];
84
+ cost: {
85
+ input: number;
86
+ output: number;
87
+ cache_read: number;
88
+ cache_write?: undefined;
89
+ };
90
+ } | {
91
+ id: string;
92
+ name: string;
93
+ protocol: string;
94
+ contextWindow: number;
95
+ maxTokens: number;
96
+ inputModalities: string[];
97
+ toolCall: boolean;
98
+ reasoning: boolean;
99
+ temperature: boolean;
100
+ efforts: string[];
101
+ cost: {
102
+ input: number;
103
+ output: number;
104
+ cache_read: number;
105
+ cache_write: number;
106
+ };
107
+ })[];
108
+ excluded: {
109
+ id: string;
110
+ reason: string;
111
+ }[];
112
+ };
113
+ };