vanspod 0.0.0-stage → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +74 -2
  2. package/bin/register.cjs +11 -0
  3. package/bin/vanspod.cjs +2 -0
  4. package/dist/adapterSettings.js +37 -0
  5. package/dist/adapters/chat-output.js +69 -0
  6. package/dist/adapters/chat-stream.js +44 -0
  7. package/dist/adapters/chat.js +63 -0
  8. package/dist/adapters/claude-reasoning.js +118 -0
  9. package/dist/adapters/claude-request.js +190 -0
  10. package/dist/adapters/claude-response.js +79 -0
  11. package/dist/adapters/index.js +19 -0
  12. package/dist/adapters/messages-stream.js +126 -0
  13. package/dist/adapters/messages.js +21 -0
  14. package/dist/adapters/responses-stream.js +70 -0
  15. package/dist/adapters/responses.js +28 -0
  16. package/dist/adapters/stream.js +99 -0
  17. package/dist/adapters/transport.js +26 -0
  18. package/dist/adapters/types.js +35 -0
  19. package/dist/appearance.js +10 -0
  20. package/dist/classroomApp.js +1814 -0
  21. package/dist/classroomUpdate.js +155 -0
  22. package/dist/claudeWebSearch.js +248 -0
  23. package/dist/clientDetection.js +35 -0
  24. package/dist/clientIcons.js +33 -0
  25. package/dist/clientRouteRuntime.js +149 -0
  26. package/dist/clientRoutes.js +118 -0
  27. package/dist/clientSettingsBackup.js +110 -0
  28. package/dist/clientTraffic.js +26 -0
  29. package/dist/clipboard.js +32 -0
  30. package/dist/codexWebSearch.js +80 -0
  31. package/dist/codexWire/apply-patch-envelope.js +139 -0
  32. package/dist/codexWire/chat-options.js +51 -0
  33. package/dist/codexWire/chat-tools.js +80 -0
  34. package/dist/codexWire/freeform-wrapper-scan.js +326 -0
  35. package/dist/codexWire/progressive-freeform-input.js +133 -0
  36. package/dist/codexWire/request.js +73 -0
  37. package/dist/codexWire/responses-tools.js +88 -0
  38. package/dist/codexWire/tool-arguments.js +39 -0
  39. package/dist/codexWire/tool-guidance.js +77 -0
  40. package/dist/commandRunner.js +100 -0
  41. package/dist/connectionMascot.js +235 -0
  42. package/dist/courseCatalog.js +131 -0
  43. package/dist/desktop.js +25 -0
  44. package/dist/entry.js +17 -0
  45. package/dist/environmentIcons.js +11 -0
  46. package/dist/exitGuard.js +17 -0
  47. package/dist/folderPicker.js +35 -0
  48. package/dist/installProgress.js +171 -0
  49. package/dist/installations.js +72 -0
  50. package/dist/instance.js +160 -0
  51. package/dist/launcher.js +155 -0
  52. package/dist/legacyMigration.js +87 -0
  53. package/dist/lifecyclePage.js +41 -0
  54. package/dist/modelProxy.js +105 -0
  55. package/dist/modelStatus.js +42 -0
  56. package/dist/page.js +1016 -0
  57. package/dist/pagePrototype.js +528 -0
  58. package/dist/platformStorage.js +91 -0
  59. package/dist/removal.js +41 -0
  60. package/dist/responsesChat.js +835 -0
  61. package/dist/routeFiles.js +646 -0
  62. package/dist/routerSessionModels.js +32 -0
  63. package/dist/runtimeOptions.js +14 -0
  64. package/dist/secureStorage.js +63 -0
  65. package/dist/settingsActions.js +100 -0
  66. package/dist/start.js +822 -0
  67. package/dist/storageErrors.js +17 -0
  68. package/dist/storageLocation.js +58 -0
  69. package/dist/version.js +4 -0
  70. package/dist/vsCodeRouteSettings.js +208 -0
  71. package/dist/webSearch.js +56 -0
  72. package/dist/windowsSecureStorage.js +163 -0
  73. package/media/brand-icon.ico +0 -0
  74. package/media/brand-logo.png +0 -0
  75. package/media/fadou-superhero-blink.png +0 -0
  76. package/media/fadou-superhero.png +0 -0
  77. package/package.json +27 -4
  78. package/scripts/tray.ps1 +40 -0
  79. package/scripts/uninstall.ps1 +133 -0
@@ -0,0 +1,646 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.VS_CODE_ENV = void 0;
7
+ exports.anthropicBaseValues = anthropicBaseValues;
8
+ exports.createRouteFiles = createRouteFiles;
9
+ const node_util_1 = require("node:util");
10
+ const clientSettingsBackup_1 = require("./clientSettingsBackup");
11
+ const node_child_process_1 = require("node:child_process");
12
+ const promises_1 = __importDefault(require("node:fs/promises"));
13
+ const node_path_1 = __importDefault(require("node:path"));
14
+ exports.VS_CODE_ENV = "claudeCode.environmentVariables";
15
+ function anthropicBaseValues(env) {
16
+ if (Array.isArray(env)) {
17
+ return env
18
+ .filter(isAnthropicBaseEntry)
19
+ .map((entry) => entry.value);
20
+ }
21
+ if (env && typeof env === "object") {
22
+ return [env.ANTHROPIC_BASE_URL];
23
+ }
24
+ return [];
25
+ }
26
+ function withoutVsCodeBaseUrl(doc) {
27
+ const env = doc[exports.VS_CODE_ENV];
28
+ if (Array.isArray(env)) {
29
+ return {
30
+ ...doc,
31
+ [exports.VS_CODE_ENV]: env.filter((entry) => !isAnthropicBaseEntry(entry)),
32
+ };
33
+ }
34
+ if (env && typeof env === "object") {
35
+ return {
36
+ ...doc,
37
+ [exports.VS_CODE_ENV]: withoutEnvKey(env, "ANTHROPIC_BASE_URL"),
38
+ };
39
+ }
40
+ return { ...doc };
41
+ }
42
+ function isAnthropicBaseEntry(entry) {
43
+ return (!!entry &&
44
+ typeof entry === "object" &&
45
+ entry.name === "ANTHROPIC_BASE_URL");
46
+ }
47
+ function withoutManagedVsCodeBaseUrl(doc, proxy) {
48
+ const env = doc[exports.VS_CODE_ENV];
49
+ if (!Array.isArray(env))
50
+ return withoutVsCodeBaseUrl(doc);
51
+ return { ...doc, [exports.VS_CODE_ENV]: env.filter(entry => !isAnthropicBaseEntry(entry) || !isManagedUrl(entry.value, proxy)) };
52
+ }
53
+ function withoutEnvKey(doc, key) {
54
+ const next = { ...doc };
55
+ delete next[key];
56
+ return next;
57
+ }
58
+ function createRouteFiles(paths) {
59
+ const codexBackup = (0, clientSettingsBackup_1.clientSettingsBackup)(node_path_1.default.join(node_path_1.default.dirname(paths.codex), "classroom-codex-settings-backup.json"), {
60
+ config: { file: paths.codex, restore: restoreTomlFields },
61
+ cache: { file: node_path_1.default.join(node_path_1.default.dirname(paths.codex), "models_cache.json"), restore: current => current },
62
+ catalog: { file: node_path_1.default.join(node_path_1.default.dirname(paths.codex), "classroom-catalog.json"), restore: current => current },
63
+ });
64
+ // Read-only compatibility journal. New writes use the route-specific journals below.
65
+ const legacyClaudeBackup = (0, clientSettingsBackup_1.clientSettingsBackup)(node_path_1.default.join(node_path_1.default.dirname(paths.claude), "classroom-claude-settings-backup.json"), {
66
+ config: { file: paths.claude, restore: restoreJsonFields },
67
+ vscode: { file: paths.vsCode, restore: restoreJsonFields },
68
+ gateway: { file: gatewayCachePath(paths.claude), restore: current => current },
69
+ });
70
+ const claudeCodeBackup = (0, clientSettingsBackup_1.clientSettingsBackup)(node_path_1.default.join(node_path_1.default.dirname(paths.claude), "classroom-claude-code-settings-backup.json"), {
71
+ config: { file: paths.claude, restore: restoreJsonFields },
72
+ });
73
+ async function migrateLegacyRouteState(proxy, clients) {
74
+ // ADR-0005 wrote one Claude/VS Code journal. Restore it before the routes split;
75
+ // this journal is never used for a new write.
76
+ const legacyResources = [
77
+ ...(clients.claude ? ["config", "gateway"] : []),
78
+ ...(clients.vsCodeChat ? ["vscode"] : []),
79
+ ];
80
+ if (legacyResources.length)
81
+ await legacyClaudeBackup.restore(legacyResources);
82
+ const saved = clients.codex || clients.claude ? await readDiskBackup(paths) : undefined;
83
+ const route = clients.codex
84
+ ? await (0, clientSettingsBackup_1.optionalText)(node_path_1.default.join(node_path_1.default.dirname(paths.codex), "classroom-codex-route-backup.json"))
85
+ : null;
86
+ if (clients.codex && (saved && (saved.clients?.codex ?? true) || route !== null)) {
87
+ let original = await readText(paths.codex);
88
+ const keys = [];
89
+ if (saved && (saved.clients?.codex ?? true)) {
90
+ if (readRootTomlString(original, "model")?.startsWith("VCRouter/")) {
91
+ original = setTomlField(original, "model", saved.codexModel);
92
+ keys.push("model");
93
+ }
94
+ if (readRootTomlString(original, "model_catalog_json") === node_path_1.default.join(node_path_1.default.dirname(paths.codex), "classroom-catalog.json")) {
95
+ original = setTomlField(original, "model_catalog_json", saved.catalogPath);
96
+ keys.push("model_catalog_json");
97
+ }
98
+ const cache = parseJsonObject(await readText(node_path_1.default.join(node_path_1.default.dirname(paths.codex), "models_cache.json")));
99
+ if (Array.isArray(cache?.models) && cache.models.length && cache.models.every(value => String(asRecord(value)?.slug).startsWith("VCRouter/")))
100
+ await codexBackup.seed("cache", saved.modelsCache ?? null, []);
101
+ }
102
+ if (route !== null) {
103
+ const value = JSON.parse(route).baseUrl;
104
+ if (value !== null && typeof value !== "string")
105
+ throw new Error("Invalid Codex route backup");
106
+ if (isManagedUrl(readRootTomlString(original, "openai_base_url"), proxy)) {
107
+ original = setTomlField(original, "openai_base_url", isManagedUrl(value, proxy) ? undefined : value ?? undefined);
108
+ keys.push("openai_base_url");
109
+ }
110
+ }
111
+ await codexBackup.seed("config", original, keys);
112
+ }
113
+ if (clients.claude && saved && (saved.clients?.claude ?? true)) {
114
+ const current = await readText(paths.claude);
115
+ const doc = parseJsonObject(current);
116
+ if (!doc)
117
+ throw new Error("Invalid Claude settings");
118
+ const gateway = parseJsonObject(await readText(gatewayCachePath(paths.claude)));
119
+ const keys = [];
120
+ if (isManagedUrl(asRecord(doc.env)?.ANTHROPIC_BASE_URL, proxy))
121
+ keys.push("env.ANTHROPIC_BASE_URL");
122
+ if (isManagedUrl(doc.ANTHROPIC_BASE_URL, proxy))
123
+ keys.push("ANTHROPIC_BASE_URL");
124
+ if (isManagedUrl(gateway?.baseUrl, proxy)) {
125
+ if (Array.isArray(gateway?.models) && gateway.models.some(value => asRecord(value)?.id === doc.model))
126
+ keys.push("model");
127
+ if (typeof saved.gateway === "string") {
128
+ await writeText(gatewayCachePath(paths.claude), saved.gateway);
129
+ }
130
+ else {
131
+ await promises_1.default.rm(gatewayCachePath(paths.claude), { force: true });
132
+ }
133
+ }
134
+ const original = restoreJsonFields(current, personalClaudeText(saved.claudeFile ?? "", proxy), current, keys);
135
+ await claudeCodeBackup.seed("config", original, keys);
136
+ }
137
+ // New durable journals exist before obsolete backup files are removed.
138
+ const savedCodexPending = !!saved && (saved.clients?.codex ?? true) && !clients.codex;
139
+ const savedClaudePending = !!saved && (saved.clients?.claude ?? true) && !clients.claude;
140
+ if (saved && !savedCodexPending && !savedClaudePending)
141
+ await promises_1.default.rm(backupFile(paths), { force: true });
142
+ if (clients.codex && route !== null)
143
+ await promises_1.default.rm(node_path_1.default.join(node_path_1.default.dirname(paths.codex), "classroom-codex-route-backup.json"), { force: true });
144
+ }
145
+ return {
146
+ async migrateLegacyState(proxyBaseUrl, clients = { codex: true, claude: true, vsCodeChat: true }) {
147
+ await migrateLegacyRouteState(proxyBaseUrl, clients);
148
+ const text = clients.claude ? await (0, clientSettingsBackup_1.optionalText)(paths.claude) : null;
149
+ if (clients.claude && text !== null) {
150
+ let next = personalClaudeText(text, proxyBaseUrl);
151
+ const gateway = parseJsonObject(await readText(gatewayCachePath(paths.claude)));
152
+ if (isManagedUrl(gateway?.baseUrl, proxyBaseUrl)) {
153
+ const doc = parseJsonObject(next);
154
+ if (Array.isArray(gateway?.models) && gateway.models.some(value => asRecord(value)?.id === doc.model)) {
155
+ delete doc.model;
156
+ next = JSON.stringify(doc, null, 2) + "\n";
157
+ }
158
+ }
159
+ if (next !== text)
160
+ await writeText(paths.claude, next);
161
+ if (isManagedUrl(gateway?.baseUrl, proxyBaseUrl))
162
+ await promises_1.default.rm(gatewayCachePath(paths.claude), { force: true });
163
+ }
164
+ const vscode = clients.vsCodeChat ? await (0, clientSettingsBackup_1.optionalText)(paths.vsCode) : null;
165
+ if (clients.vsCodeChat && vscode !== null) {
166
+ const doc = parseJsonObject(vscode);
167
+ if (!doc)
168
+ throw new Error("Invalid VS Code settings");
169
+ if (anthropicBaseValues(doc[exports.VS_CODE_ENV]).some(value => isManagedUrl(value, proxyBaseUrl))) {
170
+ await writeText(paths.vsCode, JSON.stringify(withoutManagedVsCodeBaseUrl(doc, proxyBaseUrl), null, 2) + "\n");
171
+ }
172
+ }
173
+ },
174
+ async setCodexRoute(options) {
175
+ const proxyBaseUrl = options.proxyBaseUrl;
176
+ const text = await readText(paths.codex);
177
+ if (options.mode === "classroom") {
178
+ const original = isManagedUrl(readRootTomlString(text, "openai_base_url"), proxyBaseUrl) ? removeRootTomlKey(text, "openai_base_url") : text;
179
+ const catalogPath = node_path_1.default.join(node_path_1.default.dirname(paths.codex), "classroom-catalog.json");
180
+ const cachePath = node_path_1.default.join(node_path_1.default.dirname(paths.codex), "models_cache.json");
181
+ const liveCache = await readOptional(cachePath);
182
+ const models = catalogModels(options.ids, pickTemplate(liveCache));
183
+ await codexBackup.write("catalog", `${JSON.stringify({ models }, null, 2)}\n`, []);
184
+ await codexBackup.write("cache", `${JSON.stringify({ fetched_at: "2000-01-01T00:00:00Z", client_version: "0.0.0", models }, null, 2)}\n`, []);
185
+ let next = upsertRootTomlString(text, "openai_base_url", proxyBaseUrl.replace(/\/$/, "") + "/v1");
186
+ next = upsertRootTomlString(next, "model_catalog_json", catalogPath);
187
+ next = upsertRootTomlString(next, "model", catalogSlug(options.selectedModel));
188
+ await codexBackup.write("config", next, ["openai_base_url", "model", "model_catalog_json"], original === text ? undefined : original);
189
+ if (options.restart)
190
+ await restartCodexAppServers();
191
+ }
192
+ else {
193
+ await codexBackup.restore();
194
+ const current = await readText(paths.codex);
195
+ let next = current;
196
+ if (isManagedUrl(readRootTomlString(next, "openai_base_url"), proxyBaseUrl))
197
+ next = removeRootTomlKey(next, "openai_base_url");
198
+ if (readRootTomlString(next, "model")?.startsWith("VCRouter/"))
199
+ next = removeRootTomlKey(next, "model");
200
+ if (readRootTomlString(next, "model_catalog_json") === node_path_1.default.join(node_path_1.default.dirname(paths.codex), "classroom-catalog.json"))
201
+ next = removeRootTomlKey(next, "model_catalog_json");
202
+ if (next !== current)
203
+ await writeText(paths.codex, next);
204
+ const cachePath = node_path_1.default.join(node_path_1.default.dirname(paths.codex), "models_cache.json");
205
+ const cache = parseJsonObject(await readText(cachePath));
206
+ if (cache?.fetched_at === "2000-01-01T00:00:00Z" && Array.isArray(cache.models) && cache.models.every(value => String(asRecord(value)?.slug).startsWith("VCRouter/")))
207
+ await promises_1.default.rm(cachePath, { force: true });
208
+ }
209
+ },
210
+ async setClaudeCodeRoute(classroom, proxyBaseUrl, model) {
211
+ if (!classroom) {
212
+ await claudeCodeBackup.restore();
213
+ const text = await (0, clientSettingsBackup_1.optionalText)(paths.claude);
214
+ if (text !== null) {
215
+ const next = personalClaudeText(text, proxyBaseUrl);
216
+ if (next !== text)
217
+ await writeText(paths.claude, next);
218
+ }
219
+ return;
220
+ }
221
+ const text = await readText(paths.claude);
222
+ const doc = parseJsonObject(text);
223
+ if (!doc)
224
+ throw new Error("Invalid Claude settings");
225
+ // Gateway model discovery drops ids without "claude"/"anthropic" and needs a
226
+ // non-OAuth credential, so the /model picker entry comes from this option.
227
+ const env = { ...(asRecord(doc.env) ?? {}), ANTHROPIC_BASE_URL: proxyBaseUrl, ...(model ? { ANTHROPIC_CUSTOM_MODEL_OPTION: model } : {}) };
228
+ const next = replaceJsonProperty(text, "env", env);
229
+ const personal = personalClaudeText(text, proxyBaseUrl);
230
+ const keys = ["env.ANTHROPIC_BASE_URL", ...(model ? ["env.ANTHROPIC_CUSTOM_MODEL_OPTION"] : [])];
231
+ await claudeCodeBackup.write("config", next, keys, personal === text ? undefined : personal);
232
+ },
233
+ };
234
+ }
235
+ function isManagedUrl(value, proxy) {
236
+ if (typeof value !== "string")
237
+ return false;
238
+ try {
239
+ const origin = new URL(value).origin;
240
+ return origin === new URL(proxy).origin || origin === "http://127.0.0.1:47821";
241
+ }
242
+ catch {
243
+ return false;
244
+ }
245
+ }
246
+ function setTomlField(text, key, value) {
247
+ return value === undefined ? removeRootTomlKey(text, key) : upsertRootTomlString(text, key, value);
248
+ }
249
+ function restoreTomlFields(current, original, written, keys) {
250
+ return keys.reduce((text, key) => readRootTomlString(text, key) === readRootTomlString(written, key)
251
+ ? setTomlField(text, key, readRootTomlString(original, key)) : text, current);
252
+ }
253
+ function personalClaudeText(text, proxy) {
254
+ const doc = parseJsonObject(text);
255
+ if (!doc)
256
+ throw new Error("Invalid Claude settings");
257
+ const env = asRecord(doc.env);
258
+ if (!isManagedUrl(env?.ANTHROPIC_BASE_URL, proxy) && !isManagedUrl(doc.ANTHROPIC_BASE_URL, proxy))
259
+ return text;
260
+ if (env && isManagedUrl(env.ANTHROPIC_BASE_URL, proxy)) {
261
+ delete env.ANTHROPIC_BASE_URL;
262
+ if (!Object.keys(env).length)
263
+ delete doc.env;
264
+ }
265
+ if (isManagedUrl(doc.ANTHROPIC_BASE_URL, proxy))
266
+ delete doc.ANTHROPIC_BASE_URL;
267
+ return JSON.stringify(doc, null, 2) + "\n";
268
+ }
269
+ function restoreJsonFields(current, original, written, keys) {
270
+ const doc = parseJsonObject(current), before = parseJsonObject(original), managed = parseJsonObject(written);
271
+ if (!doc || !before || !managed)
272
+ throw new Error("Invalid client settings during restore");
273
+ let changed = false;
274
+ for (const key of keys) {
275
+ if (key === "vscodeBase") {
276
+ if (!(0, node_util_1.isDeepStrictEqual)(anthropicBaseValues(doc[exports.VS_CODE_ENV]), anthropicBaseValues(managed[exports.VS_CODE_ENV])))
277
+ continue;
278
+ const env = doc[exports.VS_CODE_ENV], previous = before[exports.VS_CODE_ENV];
279
+ if (Array.isArray(env)) {
280
+ const bases = Array.isArray(previous) ? previous.filter(isAnthropicBaseEntry)
281
+ : asRecord(previous)?.ANTHROPIC_BASE_URL === undefined ? [] : [{ name: "ANTHROPIC_BASE_URL", value: asRecord(previous)?.ANTHROPIC_BASE_URL }];
282
+ doc[exports.VS_CODE_ENV] = [...env.filter(entry => !isAnthropicBaseEntry(entry)), ...bases];
283
+ }
284
+ else if (asRecord(env)) {
285
+ const value = Array.isArray(previous) ? previous.find(isAnthropicBaseEntry)?.value : asRecord(previous)?.ANTHROPIC_BASE_URL;
286
+ const next = { ...asRecord(env) };
287
+ if (value === undefined)
288
+ delete next.ANTHROPIC_BASE_URL;
289
+ else
290
+ next.ANTHROPIC_BASE_URL = value;
291
+ doc[exports.VS_CODE_ENV] = next;
292
+ }
293
+ changed = true;
294
+ }
295
+ else if (key.startsWith("env.")) {
296
+ const name = key.slice("env.".length);
297
+ const env = asRecord(doc.env), old = asRecord(before.env), expected = asRecord(managed.env);
298
+ if (!env || !(0, node_util_1.isDeepStrictEqual)(env[name], expected?.[name]))
299
+ continue;
300
+ if (old?.[name] === undefined)
301
+ delete env[name];
302
+ else
303
+ env[name] = old[name];
304
+ if (!Object.keys(env).length)
305
+ delete doc.env;
306
+ changed = true;
307
+ }
308
+ else if ((0, node_util_1.isDeepStrictEqual)(doc[key], managed[key])) {
309
+ if (before[key] === undefined)
310
+ delete doc[key];
311
+ else
312
+ doc[key] = before[key];
313
+ changed = true;
314
+ }
315
+ }
316
+ return changed ? JSON.stringify(doc, null, 2) + "\n" : current;
317
+ }
318
+ function gatewayCachePath(claudeSettings) {
319
+ return node_path_1.default.join(node_path_1.default.dirname(claudeSettings), "cache", "gateway-models.json");
320
+ }
321
+ const ROUTED_PREFIX = "VCRouter/";
322
+ function catalogSlug(id) {
323
+ return id.startsWith(ROUTED_PREFIX) ? id : `${ROUTED_PREFIX}${id}`;
324
+ }
325
+ function pickTemplate(raw) {
326
+ if (!raw) {
327
+ return undefined;
328
+ }
329
+ let parsed;
330
+ try {
331
+ parsed = JSON.parse(raw);
332
+ }
333
+ catch {
334
+ return undefined;
335
+ }
336
+ const models = Array.isArray(parsed)
337
+ ? parsed
338
+ : parsed && typeof parsed === "object" && Array.isArray(parsed.models)
339
+ ? parsed.models
340
+ : [];
341
+ const listed = models.find((model) => {
342
+ if (!model || typeof model !== "object" || Array.isArray(model)) {
343
+ return false;
344
+ }
345
+ return model.visibility === "list";
346
+ });
347
+ if (!listed || typeof listed !== "object" || Array.isArray(listed)) {
348
+ return undefined;
349
+ }
350
+ return structuredClone(listed);
351
+ }
352
+ function catalogModels(ids, template) {
353
+ return ids.map((id, index) => {
354
+ const slug = catalogSlug(id);
355
+ const entry = template ? structuredClone(template) : {
356
+ shell_type: "shell_command",
357
+ visibility: "list",
358
+ supported_in_api: true,
359
+ default_reasoning_level: "medium",
360
+ supported_reasoning_levels: [
361
+ { effort: "low", description: "Fast responses with lighter reasoning" },
362
+ { effort: "medium", description: "Balances speed and reasoning depth for everyday tasks" },
363
+ { effort: "high", description: "Greater reasoning depth for complex problems" },
364
+ { effort: "xhigh", description: "Extra high reasoning depth for complex problems" },
365
+ { effort: "max", description: "Maximum reasoning depth for the hardest problems" },
366
+ { effort: "ultra", description: "Maximum reasoning with automatic task delegation" },
367
+ ],
368
+ base_instructions: "You are a helpful coding assistant.",
369
+ supports_parallel_tool_calls: true,
370
+ context_window: 128000,
371
+ max_context_window: 128000,
372
+ input_modalities: ["text", "image"],
373
+ };
374
+ entry.slug = slug;
375
+ entry.display_name = slug;
376
+ entry.description = `Routed via classroom → VCRouter (${id}).`;
377
+ entry.visibility = "list";
378
+ entry.supported_in_api = true;
379
+ entry.priority = index + 1;
380
+ entry.upgrade = null;
381
+ return entry;
382
+ });
383
+ }
384
+ function rootTomlLines(text) {
385
+ const lines = text.split("\n");
386
+ const table = lines.findIndex((line) => /^\s*\[/.test(line));
387
+ return { lines, rootEnd: table === -1 ? lines.length : table };
388
+ }
389
+ function readRootTomlString(text, key) {
390
+ const { lines, rootEnd } = rootTomlLines(text);
391
+ const pattern = new RegExp(`^\\s*${key}\\s*=\\s*"((?:\\\\.|[^"\\\\])*)"`);
392
+ for (let i = 0; i < rootEnd; i += 1) {
393
+ const match = lines[i]?.match(pattern);
394
+ if (match?.[1] !== undefined) {
395
+ return match[1].replaceAll("\\\\", "\\").replaceAll('\\"', '"');
396
+ }
397
+ }
398
+ return undefined;
399
+ }
400
+ function upsertRootTomlString(text, key, value) {
401
+ const { lines, rootEnd } = rootTomlLines(stripKeyOutsideRoot(text, key));
402
+ const escaped = value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
403
+ const line = `${key} = "${escaped}"`;
404
+ const pattern = new RegExp(`^\\s*${key}\\s*=`);
405
+ for (let i = 0; i < rootEnd; i += 1) {
406
+ if (pattern.test(lines[i] ?? "")) {
407
+ lines[i] = line;
408
+ return lines.join("\n");
409
+ }
410
+ }
411
+ let insertAt = rootEnd;
412
+ while (insertAt > 0 && (lines[insertAt - 1] ?? "").trim() === "") {
413
+ insertAt -= 1;
414
+ }
415
+ lines.splice(insertAt, 0, line);
416
+ return lines.join("\n");
417
+ }
418
+ function removeRootTomlKey(text, key) {
419
+ const { lines, rootEnd } = rootTomlLines(text);
420
+ const pattern = new RegExp(`^\\s*${key}\\s*=`);
421
+ return lines.filter((line, index) => index >= rootEnd || !pattern.test(line)).join("\n");
422
+ }
423
+ function stripKeyOutsideRoot(text, key) {
424
+ const { lines, rootEnd } = rootTomlLines(text);
425
+ const pattern = new RegExp(`^\\s*${key}\\s*=`);
426
+ return lines.filter((line, index) => index < rootEnd || !pattern.test(line)).join("\n");
427
+ }
428
+ function restartCodexAppServers() {
429
+ if (process.platform !== "win32") {
430
+ return Promise.resolve();
431
+ }
432
+ const script = [
433
+ "$ErrorActionPreference='SilentlyContinue'",
434
+ "Get-CimInstance Win32_Process | Where-Object {",
435
+ " $_.CommandLine -and $_.CommandLine -match '(?i)(^|\\\\|\\s)codex(\\.exe|\\.cmd)?(\\s|\").*\\bapp-server\\b'",
436
+ "} | ForEach-Object { $_.ProcessId }",
437
+ ].join("\n");
438
+ return new Promise((resolve) => {
439
+ (0, node_child_process_1.execFile)("powershell.exe", ["-NoProfile", "-NoLogo", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", script], { timeout: 12_000, windowsHide: true }, (_error, stdout) => {
440
+ const pids = String(stdout)
441
+ .split(/\r?\n/)
442
+ .map((line) => Number(line.trim()))
443
+ .filter((pid) => Number.isSafeInteger(pid) && pid > 1);
444
+ if (pids.length === 0) {
445
+ resolve();
446
+ return;
447
+ }
448
+ let left = pids.length;
449
+ for (const pid of pids) {
450
+ (0, node_child_process_1.execFile)("taskkill", ["/PID", String(pid), "/T"], { windowsHide: true }, () => {
451
+ left -= 1;
452
+ if (left === 0) {
453
+ resolve();
454
+ }
455
+ });
456
+ }
457
+ });
458
+ });
459
+ }
460
+ async function readOptional(file) {
461
+ return (await (0, clientSettingsBackup_1.optionalText)(file)) ?? undefined;
462
+ }
463
+ function backupFile(paths) {
464
+ return node_path_1.default.join(node_path_1.default.dirname(paths.codex), "classroom-model-backup.json");
465
+ }
466
+ async function readDiskBackup(paths) {
467
+ const raw = await readOptional(backupFile(paths));
468
+ if (!raw) {
469
+ return undefined;
470
+ }
471
+ const parsed = JSON.parse(raw);
472
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
473
+ return undefined;
474
+ }
475
+ return parsed;
476
+ }
477
+ async function readText(file) {
478
+ return (await (0, clientSettingsBackup_1.optionalText)(file)) ?? "";
479
+ }
480
+ async function writeText(file, text) {
481
+ await promises_1.default.mkdir(node_path_1.default.dirname(file), { recursive: true });
482
+ await promises_1.default.writeFile(file, text, "utf8");
483
+ }
484
+ function asRecord(value) {
485
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
486
+ return undefined;
487
+ }
488
+ return value;
489
+ }
490
+ function parseJsonObject(text) {
491
+ if (text.trim() === "") {
492
+ return {};
493
+ }
494
+ try {
495
+ const parsed = JSON.parse(relaxJson(text));
496
+ return asRecord(parsed);
497
+ }
498
+ catch {
499
+ return undefined;
500
+ }
501
+ }
502
+ // ponytail: trailing-comma removal can match inside strings. Upgrade path: a JSONC parser.
503
+ function relaxJson(text) {
504
+ return stripJsonComments(text).replace(/,\s*([}\]])/g, "$1");
505
+ }
506
+ function stripJsonComments(text) {
507
+ let out = "";
508
+ let inString = false;
509
+ let escape = false;
510
+ for (let i = 0; i < text.length; i += 1) {
511
+ const c = text[i];
512
+ const next = text[i + 1];
513
+ if (inString) {
514
+ out += c;
515
+ if (escape) {
516
+ escape = false;
517
+ }
518
+ else if (c === "\\") {
519
+ escape = true;
520
+ }
521
+ else if (c === '"') {
522
+ inString = false;
523
+ }
524
+ continue;
525
+ }
526
+ if (c === '"') {
527
+ inString = true;
528
+ out += c;
529
+ continue;
530
+ }
531
+ if (c === "/" && next === "/") {
532
+ i += 1;
533
+ while (i + 1 < text.length && text[i + 1] !== "\n") {
534
+ i += 1;
535
+ }
536
+ continue;
537
+ }
538
+ if (c === "/" && next === "*") {
539
+ i += 2;
540
+ while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) {
541
+ i += 1;
542
+ }
543
+ i += 1;
544
+ continue;
545
+ }
546
+ out += c;
547
+ }
548
+ return out;
549
+ }
550
+ // ponytail: replaces the first property with this name. Upgrade path: a JSONC editor.
551
+ function replaceJsonProperty(text, key, value) {
552
+ const serialized = JSON.stringify(value);
553
+ const needle = `"${key}"`;
554
+ const at = text.indexOf(needle);
555
+ if (at < 0) {
556
+ return insertJsonProperty(text, key, serialized);
557
+ }
558
+ let i = at + needle.length;
559
+ while (i < text.length && /\s/.test(text[i])) {
560
+ i += 1;
561
+ }
562
+ if (text[i] !== ":") {
563
+ return text;
564
+ }
565
+ i += 1;
566
+ while (i < text.length && /\s/.test(text[i])) {
567
+ i += 1;
568
+ }
569
+ const end = jsonValueEnd(text, i);
570
+ if (end < 0) {
571
+ return text;
572
+ }
573
+ return `${text.slice(0, i)}${serialized}${text.slice(end)}`;
574
+ }
575
+ function insertJsonProperty(text, key, serialized) {
576
+ if (text.trim() === "") {
577
+ return `{\n "${key}": ${serialized}\n}\n`;
578
+ }
579
+ const end = text.lastIndexOf("}");
580
+ if (end < 0) {
581
+ return text;
582
+ }
583
+ const before = text.slice(0, end).trimEnd();
584
+ const comma = before.endsWith("{") || before.endsWith(",") ? "" : ",";
585
+ return `${before}${comma}\n "${key}": ${serialized}\n${text.slice(end)}`;
586
+ }
587
+ function jsonValueEnd(text, start) {
588
+ const c = text[start];
589
+ if (c === '"') {
590
+ let escape = false;
591
+ for (let i = start + 1; i < text.length; i += 1) {
592
+ if (escape) {
593
+ escape = false;
594
+ continue;
595
+ }
596
+ if (text[i] === "\\") {
597
+ escape = true;
598
+ continue;
599
+ }
600
+ if (text[i] === '"') {
601
+ return i + 1;
602
+ }
603
+ }
604
+ return -1;
605
+ }
606
+ if (c === "{" || c === "[") {
607
+ const close = c === "{" ? "}" : "]";
608
+ let depth = 0;
609
+ let inString = false;
610
+ let escape = false;
611
+ for (let i = start; i < text.length; i += 1) {
612
+ const ch = text[i];
613
+ if (inString) {
614
+ if (escape) {
615
+ escape = false;
616
+ }
617
+ else if (ch === "\\") {
618
+ escape = true;
619
+ }
620
+ else if (ch === '"') {
621
+ inString = false;
622
+ }
623
+ continue;
624
+ }
625
+ if (ch === '"') {
626
+ inString = true;
627
+ continue;
628
+ }
629
+ if (ch === c) {
630
+ depth += 1;
631
+ }
632
+ else if (ch === close) {
633
+ depth -= 1;
634
+ if (depth === 0) {
635
+ return i + 1;
636
+ }
637
+ }
638
+ }
639
+ return -1;
640
+ }
641
+ let i = start;
642
+ while (i < text.length && !/[\s,}\]]/.test(text[i])) {
643
+ i += 1;
644
+ }
645
+ return i;
646
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchSessionModelProviders = fetchSessionModelProviders;
4
+ function isProviderRecord(value) {
5
+ if (!value || typeof value !== "object" || Array.isArray(value))
6
+ return false;
7
+ const models = value.models;
8
+ return Array.isArray(models) && models.every(model => {
9
+ if (!model || typeof model !== "object" || Array.isArray(model))
10
+ return false;
11
+ const id = model.id;
12
+ return typeof id === "string" && id.trim().length > 0;
13
+ });
14
+ }
15
+ async function fetchSessionModelProviders(options) {
16
+ const request = options.request ?? fetch;
17
+ const root = options.root.replace(/\/+$/, "");
18
+ const response = await request(`${root}/extension/chat-language-models`, {
19
+ signal: AbortSignal.timeout(5_000),
20
+ headers: {
21
+ Accept: "application/json",
22
+ Authorization: `Bearer ${options.apiKey}`,
23
+ },
24
+ });
25
+ if (!response.ok)
26
+ throw new Error("session models failed");
27
+ const payload = await response.json();
28
+ if (!Array.isArray(payload) || !payload.every(isProviderRecord)) {
29
+ throw new Error("session models failed");
30
+ }
31
+ return payload;
32
+ }