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,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMigratingConnectionStorage = createMigratingConnectionStorage;
4
+ const storageErrors_1 = require("./storageErrors");
5
+ function legacyConnection(raw) {
6
+ let value;
7
+ try {
8
+ value = JSON.parse(raw);
9
+ }
10
+ catch {
11
+ return undefined;
12
+ }
13
+ if (!value || typeof value !== "object")
14
+ return undefined;
15
+ const legacy = value;
16
+ if (legacy.secureStorage !== undefined || typeof legacy.apiKey !== "string" || !legacy.apiKey)
17
+ return undefined;
18
+ const session = {};
19
+ if (legacy.session && typeof legacy.session === "object") {
20
+ const oldSession = legacy.session;
21
+ for (const field of ["class_name", "name", "invite_code", "expires_at"]) {
22
+ if (typeof oldSession[field] === "string")
23
+ session[field] = oldSession[field];
24
+ }
25
+ }
26
+ const connection = { version: 1, apiKey: legacy.apiKey, session, mode: legacy.mode === "classroom" ? "classroom" : "native" };
27
+ if (typeof legacy.nickname === "string")
28
+ connection.nickname = legacy.nickname;
29
+ return connection;
30
+ }
31
+ /** Startup migration is shared by all reads and settles before later mutations. */
32
+ function createMigratingConnectionStorage(storage, filePath, files) {
33
+ let loaded;
34
+ async function migrate() {
35
+ try {
36
+ return await storage.getConnection();
37
+ }
38
+ catch (error) {
39
+ if (!(error instanceof storageErrors_1.StorageError) || error.code !== "legacy")
40
+ throw error;
41
+ }
42
+ try {
43
+ const legacy = legacyConnection(await files.read(filePath));
44
+ if (!legacy)
45
+ throw new storageErrors_1.StorageError("unavailable");
46
+ // A strict save performs protect, decrypt verification, protected temp write and replacement.
47
+ const result = await storage.setConnection(legacy);
48
+ if (result.persistence !== "persistent")
49
+ throw new storageErrors_1.StorageError("unavailable");
50
+ return legacy;
51
+ }
52
+ catch {
53
+ try {
54
+ await files.remove(filePath);
55
+ }
56
+ catch (error) {
57
+ if (error?.code !== "ENOENT")
58
+ throw new storageErrors_1.StorageError("legacy-cleanup-failed");
59
+ }
60
+ throw new storageErrors_1.StorageError("migration-failed");
61
+ }
62
+ }
63
+ function initialize() {
64
+ if (!loaded) {
65
+ loaded = migrate();
66
+ void loaded.catch(() => { });
67
+ }
68
+ return loaded;
69
+ }
70
+ return {
71
+ async getConnection() {
72
+ const connection = await initialize();
73
+ return connection && structuredClone(connection);
74
+ },
75
+ async setConnection(connection, options) {
76
+ await initialize().catch(() => { });
77
+ const result = await storage.setConnection(connection, options);
78
+ loaded = Promise.resolve(structuredClone(connection));
79
+ return result;
80
+ },
81
+ async clearConnection() {
82
+ await initialize().catch(() => { });
83
+ await storage.clearConnection();
84
+ loaded = Promise.resolve(undefined);
85
+ },
86
+ };
87
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.lifecyclePage = lifecyclePage;
4
+ function lifecyclePage(html, token) {
5
+ const controls = `<div style="margin-top:16px"><button type="button" data-lifecycle="stop">結束 VansPod</button> <button type="button" data-lifecycle="uninstall">移除 VansPod</button><p role="status" data-lifecycle-message></p></div>`;
6
+ const script = `<script>
7
+ (function () {
8
+ var token = ${JSON.stringify(token)};
9
+ async function request(action, body) {
10
+ var response = await fetch('/_vanspod/' + action, {method: body ? 'POST' : 'GET', headers: {'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json'}, body: body ? JSON.stringify(body) : undefined});
11
+ if (!response.ok) throw new Error('無法完成操作,請重新開啟 VansPod。');
12
+ return response.json();
13
+ }
14
+ document.addEventListener('click', async function (event) {
15
+ var button = event.target.closest && event.target.closest('[data-lifecycle]');
16
+ if (!button) return;
17
+ var message = document.querySelector('[data-lifecycle-message]');
18
+ button.disabled = true;
19
+ try {
20
+ var action = button.dataset.lifecycle;
21
+ var confirmed = false;
22
+ if (action === 'uninstall') {
23
+ var preview = await request('uninstall-preview');
24
+ if (!preview.ok) { message.textContent = preview.message; return; }
25
+ confirmed = window.confirm(preview.message + '\\n\\n確定完整移除 VansPod?');
26
+ if (!confirmed) return;
27
+ }
28
+ var result = await request(action, {confirmed: confirmed});
29
+ if (result.confirmation && window.confirm(result.message)) result = await request(action, {confirmed: true, interruptConfirmed: true});
30
+ message.textContent = result.message || (result.ok ? 'VansPod 已結束,可關閉此分頁。' : '操作未完成,請重試。');
31
+ } catch (error) { message.textContent = error.message; }
32
+ finally { button.disabled = false; }
33
+ });
34
+ request('status').then(function (status) {
35
+ if (status.startupError) { document.querySelector('[data-lifecycle-message]').textContent = status.startupError; return; }
36
+ if (status.launchVersion && status.launchVersion !== status.version) document.querySelector('[data-lifecycle-message]').textContent = '目前執行版本 ' + status.version + ',本次開啟版本 ' + status.launchVersion + '。換版請先結束 VansPod。';
37
+ }).catch(function () {});
38
+ })();
39
+ </script>`;
40
+ return html.replace(/<form method="post" action="\/stop">[\s\S]*?<\/form>/, controls).replace('</body>', `${script}</body>`);
41
+ }
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.proxyError = proxyError;
4
+ exports.isCodexSearch = isCodexSearch;
5
+ exports.sendRouter = sendRouter;
6
+ exports.relayResponse = relayResponse;
7
+ const adapters_1 = require("./adapters");
8
+ const types_1 = require("./adapters/types");
9
+ const stream_1 = require("./adapters/stream");
10
+ const claudeWebSearch_1 = require("./claudeWebSearch");
11
+ const codexWebSearch_1 = require("./codexWebSearch");
12
+ const node_events_1 = require("node:events");
13
+ function proxyError(client, message, status) {
14
+ const error = { type: status < 500 ? "invalid_request_error" : "api_error", message };
15
+ return Response.json(client === "claude" ? { type: "error", error } : { error }, { status });
16
+ }
17
+ // Codex's built-in web search (web__run) posts to /alpha/search on the model base
18
+ // URL. It is not a model request, so its body need not name a model at all.
19
+ function isCodexSearch(client, path) {
20
+ return client === "codex" && (path ?? "").split("?")[0].endsWith("/alpha/search");
21
+ }
22
+ async function sendRouter(upstream, baseUrl) {
23
+ const client = upstream.client === "claude" ? "claude" : "codex";
24
+ try {
25
+ const path = upstream.path ?? "";
26
+ if (isCodexSearch(client, path))
27
+ return await (0, codexWebSearch_1.codexWebSearch)(upstream, baseUrl);
28
+ const route = path.split("?")[0];
29
+ if (client === "codex" ? !route.endsWith("/responses") : !route.endsWith("/messages"))
30
+ throw new types_1.AdapterError(`此 API 路徑尚未支援所選 adapter:${route}`);
31
+ let body;
32
+ try {
33
+ body = JSON.parse(upstream.body ?? "");
34
+ }
35
+ catch {
36
+ throw new types_1.AdapterError("請求正文必須是有效 JSON。");
37
+ }
38
+ const parsed = (0, types_1.object)(body, "request");
39
+ if (parsed.stream !== undefined && typeof parsed.stream !== "boolean")
40
+ throw new types_1.AdapterError("stream 必須是布林值。");
41
+ const adapter = (0, adapters_1.modelApiAdapter)(upstream.adapter ?? "responses");
42
+ const request = { client, model: upstream.model, body: parsed,
43
+ apiKey: upstream.apiKey ?? "", routerBaseUrl: baseUrl.replace(/\/+$/, ""), headers: upstream.headers,
44
+ signal: upstream.signal ?? new AbortController().signal };
45
+ // Claude Code searches inside a model request: Router runs the searches, not the model upstream.
46
+ const response = (0, claudeWebSearch_1.declaresWebSearch)(request) ? await (0, claudeWebSearch_1.claudeWebSearch)(adapter, request) : await adapter.send(request);
47
+ if (response.ok)
48
+ return response;
49
+ let message = `Router 回傳 HTTP ${response.status}。`;
50
+ try {
51
+ const body = (0, types_1.object)(await response.json(), "upstream error");
52
+ const error = typeof body.error === "object" && body.error !== null ? (0, types_1.object)(body.error, "error") : body;
53
+ if (typeof error.message === "string")
54
+ message += ` ${error.message}`;
55
+ }
56
+ catch { /* HTTP status remains useful without exposing an arbitrary upstream body. */ }
57
+ if (upstream.apiKey)
58
+ message = message.split(upstream.apiKey).join("[redacted]");
59
+ return proxyError(client, message, response.status);
60
+ }
61
+ catch (error) {
62
+ if (upstream.signal?.aborted)
63
+ throw error;
64
+ return proxyError(client, error instanceof types_1.AdapterError ? error.message : "上游沒有回應或回應格式無效。", error instanceof types_1.AdapterError ? error.status : 502);
65
+ }
66
+ }
67
+ async function relayResponse(response, res, client, signal) {
68
+ const type = response.headers.get("content-type") ?? "application/json; charset=utf-8";
69
+ res.writeHead(response.status, { "Content-Type": type, "Cache-Control": "no-store" });
70
+ if (!response.body) {
71
+ res.end();
72
+ return;
73
+ }
74
+ const reader = response.body.getReader();
75
+ const abort = () => { void reader.cancel().catch(() => { }); };
76
+ signal.addEventListener("abort", abort, { once: true });
77
+ try {
78
+ while (!signal.aborted && !res.destroyed) {
79
+ const next = await reader.read();
80
+ if (next.done)
81
+ break;
82
+ if (!res.write(next.value)) {
83
+ // AbortSignal also breaks waits when the downstream connection closes.
84
+ await (0, node_events_1.once)(res, "drain", { signal });
85
+ }
86
+ }
87
+ }
88
+ catch {
89
+ if (!signal.aborted && !res.destroyed) {
90
+ if (type.includes("text/event-stream"))
91
+ res.write(client === "claude"
92
+ ? (0, stream_1.event)("error", { error: { type: "api_error", message: "上游串流中斷或內容無法轉換。" } })
93
+ : (0, stream_1.event)("response.failed", { response: { object: "response", status: "failed", error: { type: "upstream_error", message: "上游串流中斷或內容無法轉換。" } } }));
94
+ else
95
+ res.destroy();
96
+ }
97
+ }
98
+ finally {
99
+ signal.removeEventListener("abort", abort);
100
+ await reader.cancel().catch(() => { });
101
+ reader.releaseLock();
102
+ if (!res.destroyed)
103
+ res.end();
104
+ }
105
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.modelStatusScript = void 0;
4
+ exports.modelStatusScript = `
5
+ async function refreshModelStatus() {
6
+ try {
7
+ if (!document.hidden && !settingsBusy) {
8
+ var response = await fetch("/model-status", { cache: "no-store" });
9
+ if (!response.ok) throw new Error("status unavailable");
10
+ var status = await response.json();
11
+ // A settings action redraws the whole page; never paint an older answer over it.
12
+ if (!settingsBusy) applyModelStatus(status);
13
+ }
14
+ } catch (error) {}
15
+ setTimeout(refreshModelStatus, 1000);
16
+ }
17
+ function applyModelStatus(status) {
18
+ document.querySelectorAll("[data-client-light]").forEach(function (light) {
19
+ var next = status.lights[light.getAttribute("data-client-light")];
20
+ if (!next || (light.className === next.className && light.title === next.status)) return;
21
+ light.className = next.className;
22
+ light.title = next.status;
23
+ light.setAttribute("aria-label", next.status);
24
+ });
25
+ if (status.traffic) document.querySelectorAll("[data-client-flow]").forEach(function (flow) {
26
+ flow.classList.toggle("flowing", status.traffic[flow.getAttribute("data-client-flow")] === true);
27
+ });
28
+ var card = document.querySelector('[data-setting="模型"]');
29
+ if (!card) return;
30
+ var banner = card.querySelector(".model-update");
31
+ var holder = document.createElement("template");
32
+ holder.innerHTML = status.restart;
33
+ var updated = holder.content.firstElementChild;
34
+ if (typeof refreshClassroomRestart === 'function') refreshClassroomRestart(status.restart);
35
+ if (document.querySelector('[data-classroom-restart]')) return;
36
+ if (banner?.getAttribute("data-restart-clients") === updated?.getAttribute("data-restart-clients")) return;
37
+ if (banner && updated) banner.replaceWith(updated);
38
+ else if (banner) banner.remove();
39
+ else if (updated) card.querySelector(".settings-actions").before(updated);
40
+ }
41
+ setTimeout(refreshModelStatus, 1000);
42
+ `;