@wlv-zedd/dsh-chatgpt-web 1.0.0 → 1.0.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 (81) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +140 -140
  3. package/cordis.patch.yml +4 -4
  4. package/lib/cli.js +13269 -11860
  5. package/lib/plugin.js +3 -50
  6. package/package.json +89 -88
  7. package/src/adapters/base.ts +16 -16
  8. package/src/adapters/chatgpt-web/adapter-error.ts +59 -59
  9. package/src/adapters/chatgpt-web/browser-helper-main.ts +513 -513
  10. package/src/adapters/chatgpt-web/browser-helper-prompt-selection.ts +27 -27
  11. package/src/adapters/chatgpt-web/browser-worker.ts +4944 -4944
  12. package/src/adapters/chatgpt-web/codex-rollout-environment.ts +628 -628
  13. package/src/adapters/chatgpt-web/compaction-handoff.ts +533 -533
  14. package/src/adapters/chatgpt-web/compaction-transaction.ts +142 -142
  15. package/src/adapters/chatgpt-web/concurrency.ts +6 -6
  16. package/src/adapters/chatgpt-web/conversation-key.ts +58 -58
  17. package/src/adapters/chatgpt-web/environment.ts +669 -669
  18. package/src/adapters/chatgpt-web/index.ts +1544 -1544
  19. package/src/adapters/chatgpt-web/input-tokens.ts +74 -74
  20. package/src/adapters/chatgpt-web/launcher-helper-client.ts +695 -695
  21. package/src/adapters/chatgpt-web/markdown.ts +418 -418
  22. package/src/adapters/chatgpt-web/mcp-main.ts +25 -25
  23. package/src/adapters/chatgpt-web/mcp-server.ts +933 -933
  24. package/src/adapters/chatgpt-web/model.ts +70 -70
  25. package/src/adapters/chatgpt-web/native-compaction-control.ts +74 -74
  26. package/src/adapters/chatgpt-web/output-validation.ts +62 -62
  27. package/src/adapters/chatgpt-web/process-line-writer.ts +46 -46
  28. package/src/adapters/chatgpt-web/prompt.ts +702 -702
  29. package/src/adapters/chatgpt-web/retry-policy.ts +73 -73
  30. package/src/adapters/chatgpt-web/rolling-checkpoint.ts +384 -384
  31. package/src/adapters/chatgpt-web/thread-environment.ts +238 -238
  32. package/src/adapters/chatgpt-web/tool-stream-parser.ts +1 -1
  33. package/src/adapters/chatgpt-web/turn-broker.ts +1481 -1481
  34. package/src/adapters/chatgpt-web/turn-execution.ts +816 -816
  35. package/src/adapters/chatgpt-web/turn-progress.ts +292 -292
  36. package/src/adapters/chatgpt-web/usage.ts +121 -121
  37. package/src/adapters/image.ts +9 -9
  38. package/src/bridge.ts +1083 -1083
  39. package/src/browser-login.ts +521 -521
  40. package/src/chatgpt-session.ts +240 -240
  41. package/src/chatgpt-web-models.ts +400 -400
  42. package/src/cli.ts +568 -568
  43. package/src/codex-integration-document.ts +824 -824
  44. package/src/codex-integration-journal.ts +212 -212
  45. package/src/codex-integration-route.ts +515 -515
  46. package/src/codex-integration-shared.ts +332 -332
  47. package/src/codex-integration.ts +529 -529
  48. package/src/codex-interrupt-hook.ts +158 -158
  49. package/src/config.ts +616 -616
  50. package/src/dev-chat/cli.ts +432 -432
  51. package/src/dev-chat/constants.ts +3 -3
  52. package/src/dev-chat/driver.ts +655 -655
  53. package/src/dev-chat/profile.ts +223 -223
  54. package/src/dev-chat/session.ts +287 -287
  55. package/src/dev-chat/transport.ts +54 -54
  56. package/src/doctor.ts +237 -237
  57. package/src/event-queue.ts +45 -45
  58. package/src/http-body.ts +30 -30
  59. package/src/launcher-browser-host.ts +695 -695
  60. package/src/lib/errors.ts +281 -281
  61. package/src/lib/token-estimate.ts +42 -42
  62. package/src/login-helper.cjs +1 -1
  63. package/src/model-catalog.ts +197 -197
  64. package/src/native-passthrough.ts +261 -261
  65. package/src/plugin.ts +191 -191
  66. package/src/process.ts +45 -45
  67. package/src/responses/compaction.ts +199 -199
  68. package/src/responses/parser.ts +633 -633
  69. package/src/responses/reasoning-envelope.ts +49 -49
  70. package/src/responses/schema.ts +172 -172
  71. package/src/responses/state.ts +230 -230
  72. package/src/server.ts +1111 -1111
  73. package/src/service.ts +315 -315
  74. package/src/setup.ts +671 -671
  75. package/src/stall-timeout.ts +23 -23
  76. package/src/tunnel-service.ts +160 -160
  77. package/src/tunnel.ts +417 -417
  78. package/src/turndown-plugin-gfm.d.ts +5 -5
  79. package/src/types.ts +307 -307
  80. package/src/usage/totals.ts +12 -12
  81. package/src/version.ts +1 -1
package/src/plugin.ts CHANGED
@@ -1,191 +1,191 @@
1
- import { spawn, type ChildProcess } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import { homedir } from "node:os";
4
- import { dirname, join, resolve } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
-
7
- export interface CordisContext {
8
- effect?: (cb: () => void | Promise<void> | (() => void) | (() => Promise<void>)) => void;
9
- logger?: (name: string) => {
10
- info(msg: string): void;
11
- warn(msg: string): void;
12
- error(msg: string): void;
13
- debug(msg: string): void;
14
- };
15
- }
16
-
17
- export const name = "dsh-chatgpt-web";
18
- export const inject = [];
19
-
20
- export interface ChatGPTWebPluginConfig {
21
- /** Host to bind or check for health (default: 127.0.0.1). */
22
- host?: string;
23
- /** Port the sidecar listens on (default: 17841). */
24
- port?: number;
25
- /** Automatically start the sidecar daemon if not running (default: true). */
26
- autoStart?: boolean;
27
- /** Maximum milliseconds to wait for the sidecar to report ready (default: 30000). */
28
- readyTimeoutMs?: number;
29
- /** Explicit path to the bun executable (optional). */
30
- bunPath?: string;
31
- }
32
-
33
- import { pathToFileURL } from "node:url";
34
-
35
- const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
36
-
37
- function resolveLauncher(customBunPath?: string): { cmd: string; args: string[] } {
38
- const libCliPath = resolve(ROOT_DIR, "lib", "cli.js");
39
- if (existsSync(libCliPath)) {
40
- return {
41
- cmd: process.execPath,
42
- args: [libCliPath, "serve"],
43
- };
44
- }
45
-
46
- // Development fallback: check for bun or tsx
47
- if (customBunPath && existsSync(customBunPath)) {
48
- return { cmd: customBunPath, args: ["run", "src/cli.ts", "serve"] };
49
- }
50
-
51
- const winBun = join(homedir(), ".bun", "bin", "bun.exe");
52
- if (existsSync(winBun)) {
53
- return { cmd: winBun, args: ["run", "src/cli.ts", "serve"] };
54
- }
55
-
56
- const tsxPath = resolve(ROOT_DIR, "../deepseek-harness/node_modules/tsx/dist/esm/index.mjs");
57
- if (existsSync(tsxPath)) {
58
- return {
59
- cmd: process.execPath,
60
- args: ["--import", pathToFileURL(tsxPath).href, "src/cli.ts", "serve"],
61
- };
62
- }
63
-
64
- return { cmd: process.execPath, args: ["src/cli.ts", "serve"] };
65
- }
66
-
67
- async function isSidecarHealthy(host: string, port: number): Promise<boolean> {
68
- try {
69
- const controller = new AbortController();
70
- const timer = setTimeout(() => controller.abort(), 1500);
71
- const res = await fetch(`http://${host}:${port}/healthz`, {
72
- signal: controller.signal,
73
- });
74
- clearTimeout(timer);
75
- if (!res.ok) return false;
76
- const body = (await res.json()) as { status?: string };
77
- return body.status === "ok";
78
- } catch {
79
- return false;
80
- }
81
- }
82
-
83
- export function apply(ctx: CordisContext, config: ChatGPTWebPluginConfig = {}): void {
84
- const host = config.host || "127.0.0.1";
85
- const port = config.port || 17841;
86
- const autoStart = config.autoStart !== false;
87
- const readyTimeoutMs = config.readyTimeoutMs || 30_000;
88
- const logger = typeof ctx.logger === "function" ? ctx.logger("chatgpt-web") : console;
89
-
90
- let spawnedProcess: ChildProcess | undefined;
91
-
92
- const startDaemon = async () => {
93
- const alreadyHealthy = await isSidecarHealthy(host, port);
94
- if (alreadyHealthy) {
95
- logger.info(`[dsh-chatgpt-web] Sidecar already running and healthy at http://${host}:${port}/v1`);
96
- return;
97
- }
98
-
99
- if (!autoStart) {
100
- logger.warn(`[dsh-chatgpt-web] Sidecar is offline and autoStart is false. Start it manually with 'bun run src/cli.ts serve'`);
101
- return;
102
- }
103
-
104
- const launcher = resolveLauncher(config.bunPath);
105
- logger.info(`[dsh-chatgpt-web] Starting dsh-chatgpt-web daemon via ${launcher.cmd} at http://${host}:${port}/v1...`);
106
-
107
- const child = spawn(launcher.cmd, launcher.args, {
108
- cwd: ROOT_DIR,
109
- stdio: ["ignore", "pipe", "pipe"],
110
- windowsHide: true,
111
- env: {
112
- ...process.env,
113
- },
114
- });
115
-
116
- spawnedProcess = child;
117
-
118
- child.stdout?.on("data", (chunk: Buffer) => {
119
- const text = chunk.toString().trim();
120
- if (text && typeof logger.debug === "function") logger.debug(`[sidecar] ${text}`);
121
- });
122
-
123
- child.stderr?.on("data", (chunk: Buffer) => {
124
- const text = chunk.toString().trim();
125
- if (text && typeof logger.debug === "function") logger.debug(`[sidecar:err] ${text}`);
126
- });
127
-
128
- child.on("error", (err) => {
129
- logger.error(`[dsh-chatgpt-web] Failed to launch sidecar process: ${err.message}`);
130
- });
131
-
132
- child.on("exit", (code, signal) => {
133
- if (code !== 0 && code !== null) {
134
- logger.warn(`[dsh-chatgpt-web] Sidecar process exited with code ${code} (signal: ${signal})`);
135
- }
136
- spawnedProcess = undefined;
137
- });
138
-
139
- // Wait for healthcheck
140
- const deadline = Date.now() + readyTimeoutMs;
141
- while (Date.now() < deadline) {
142
- if (await isSidecarHealthy(host, port)) {
143
- logger.info(`[dsh-chatgpt-web] Sidecar ready and accepting turns at http://${host}:${port}/v1`);
144
- return;
145
- }
146
- await new Promise((r) => setTimeout(r, 500));
147
- }
148
-
149
- logger.error(`[dsh-chatgpt-web] Sidecar did not become healthy within ${readyTimeoutMs}ms`);
150
- };
151
-
152
- const stopDaemon = async () => {
153
- if (!spawnedProcess) return;
154
-
155
- logger.info("[dsh-chatgpt-web] Stopping sidecar daemon...");
156
- try {
157
- const controller = new AbortController();
158
- const timer = setTimeout(() => controller.abort(), 2000);
159
- await fetch(`http://${host}:${port}/admin/shutdown`, {
160
- method: "POST",
161
- signal: controller.signal,
162
- }).catch(() => {});
163
- clearTimeout(timer);
164
- } catch {
165
- // ignore
166
- }
167
-
168
- if (spawnedProcess && !spawnedProcess.killed) {
169
- spawnedProcess.kill("SIGTERM");
170
- }
171
- spawnedProcess = undefined;
172
- };
173
-
174
- if (typeof ctx.effect === "function") {
175
- ctx.effect(() => {
176
- void startDaemon();
177
- return () => {
178
- void stopDaemon();
179
- };
180
- });
181
- } else {
182
- void startDaemon();
183
- process.once("beforeExit", () => void stopDaemon());
184
- }
185
- }
186
-
187
- export default {
188
- name,
189
- inject,
190
- apply,
191
- };
1
+ import { spawn, type ChildProcess } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ export interface CordisContext {
8
+ effect?: (cb: () => void | Promise<void> | (() => void) | (() => Promise<void>)) => void;
9
+ logger?: (name: string) => {
10
+ info(msg: string): void;
11
+ warn(msg: string): void;
12
+ error(msg: string): void;
13
+ debug(msg: string): void;
14
+ };
15
+ }
16
+
17
+ export const name = "dsh-chatgpt-web";
18
+ export const inject = [];
19
+
20
+ export interface ChatGPTWebPluginConfig {
21
+ /** Host to bind or check for health (default: 127.0.0.1). */
22
+ host?: string;
23
+ /** Port the sidecar listens on (default: 17841). */
24
+ port?: number;
25
+ /** Automatically start the sidecar daemon if not running (default: true). */
26
+ autoStart?: boolean;
27
+ /** Maximum milliseconds to wait for the sidecar to report ready (default: 30000). */
28
+ readyTimeoutMs?: number;
29
+ /** Explicit path to the bun executable (optional). */
30
+ bunPath?: string;
31
+ }
32
+
33
+ import { pathToFileURL } from "node:url";
34
+
35
+ const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
36
+
37
+ function resolveLauncher(customBunPath?: string): { cmd: string; args: string[] } {
38
+ const libCliPath = resolve(ROOT_DIR, "lib", "cli.js");
39
+ if (existsSync(libCliPath)) {
40
+ return {
41
+ cmd: process.execPath,
42
+ args: [libCliPath, "serve"],
43
+ };
44
+ }
45
+
46
+ // Development fallback: check for bun or tsx
47
+ if (customBunPath && existsSync(customBunPath)) {
48
+ return { cmd: customBunPath, args: ["run", "src/cli.ts", "serve"] };
49
+ }
50
+
51
+ const winBun = join(homedir(), ".bun", "bin", "bun.exe");
52
+ if (existsSync(winBun)) {
53
+ return { cmd: winBun, args: ["run", "src/cli.ts", "serve"] };
54
+ }
55
+
56
+ const tsxPath = resolve(ROOT_DIR, "../deepseek-harness/node_modules/tsx/dist/esm/index.mjs");
57
+ if (existsSync(tsxPath)) {
58
+ return {
59
+ cmd: process.execPath,
60
+ args: ["--import", pathToFileURL(tsxPath).href, "src/cli.ts", "serve"],
61
+ };
62
+ }
63
+
64
+ return { cmd: process.execPath, args: ["src/cli.ts", "serve"] };
65
+ }
66
+
67
+ async function isSidecarHealthy(host: string, port: number): Promise<boolean> {
68
+ try {
69
+ const controller = new AbortController();
70
+ const timer = setTimeout(() => controller.abort(), 1500);
71
+ const res = await fetch(`http://${host}:${port}/healthz`, {
72
+ signal: controller.signal,
73
+ });
74
+ clearTimeout(timer);
75
+ if (!res.ok) return false;
76
+ const body = (await res.json()) as { status?: string };
77
+ return body.status === "ok";
78
+ } catch {
79
+ return false;
80
+ }
81
+ }
82
+
83
+ export function apply(ctx: CordisContext, config: ChatGPTWebPluginConfig = {}): void {
84
+ const host = config.host || "127.0.0.1";
85
+ const port = config.port || 17841;
86
+ const autoStart = config.autoStart !== false;
87
+ const readyTimeoutMs = config.readyTimeoutMs || 30_000;
88
+ const logger = typeof ctx.logger === "function" ? ctx.logger("chatgpt-web") : console;
89
+
90
+ let spawnedProcess: ChildProcess | undefined;
91
+
92
+ const startDaemon = async () => {
93
+ const alreadyHealthy = await isSidecarHealthy(host, port);
94
+ if (alreadyHealthy) {
95
+ logger.info(`[dsh-chatgpt-web] Sidecar already running and healthy at http://${host}:${port}/v1`);
96
+ return;
97
+ }
98
+
99
+ if (!autoStart) {
100
+ logger.warn(`[dsh-chatgpt-web] Sidecar is offline and autoStart is false. Start it manually with 'bun run src/cli.ts serve'`);
101
+ return;
102
+ }
103
+
104
+ const launcher = resolveLauncher(config.bunPath);
105
+ logger.info(`[dsh-chatgpt-web] Starting dsh-chatgpt-web daemon via ${launcher.cmd} at http://${host}:${port}/v1...`);
106
+
107
+ const child = spawn(launcher.cmd, launcher.args, {
108
+ cwd: ROOT_DIR,
109
+ stdio: ["ignore", "pipe", "pipe"],
110
+ windowsHide: true,
111
+ env: {
112
+ ...process.env,
113
+ },
114
+ });
115
+
116
+ spawnedProcess = child;
117
+
118
+ child.stdout?.on("data", (chunk: Buffer) => {
119
+ const text = chunk.toString().trim();
120
+ if (text && typeof logger.debug === "function") logger.debug(`[sidecar] ${text}`);
121
+ });
122
+
123
+ child.stderr?.on("data", (chunk: Buffer) => {
124
+ const text = chunk.toString().trim();
125
+ if (text && typeof logger.debug === "function") logger.debug(`[sidecar:err] ${text}`);
126
+ });
127
+
128
+ child.on("error", (err) => {
129
+ logger.error(`[dsh-chatgpt-web] Failed to launch sidecar process: ${err.message}`);
130
+ });
131
+
132
+ child.on("exit", (code, signal) => {
133
+ if (code !== 0 && code !== null) {
134
+ logger.warn(`[dsh-chatgpt-web] Sidecar process exited with code ${code} (signal: ${signal})`);
135
+ }
136
+ spawnedProcess = undefined;
137
+ });
138
+
139
+ // Wait for healthcheck
140
+ const deadline = Date.now() + readyTimeoutMs;
141
+ while (Date.now() < deadline) {
142
+ if (await isSidecarHealthy(host, port)) {
143
+ logger.info(`[dsh-chatgpt-web] Sidecar ready and accepting turns at http://${host}:${port}/v1`);
144
+ return;
145
+ }
146
+ await new Promise((r) => setTimeout(r, 500));
147
+ }
148
+
149
+ logger.error(`[dsh-chatgpt-web] Sidecar did not become healthy within ${readyTimeoutMs}ms`);
150
+ };
151
+
152
+ const stopDaemon = async () => {
153
+ if (!spawnedProcess) return;
154
+
155
+ logger.info("[dsh-chatgpt-web] Stopping sidecar daemon...");
156
+ try {
157
+ const controller = new AbortController();
158
+ const timer = setTimeout(() => controller.abort(), 2000);
159
+ await fetch(`http://${host}:${port}/admin/shutdown`, {
160
+ method: "POST",
161
+ signal: controller.signal,
162
+ }).catch(() => {});
163
+ clearTimeout(timer);
164
+ } catch {
165
+ // ignore
166
+ }
167
+
168
+ if (spawnedProcess && !spawnedProcess.killed) {
169
+ spawnedProcess.kill("SIGTERM");
170
+ }
171
+ spawnedProcess = undefined;
172
+ };
173
+
174
+ if (typeof ctx.effect === "function") {
175
+ ctx.effect(() => {
176
+ void startDaemon();
177
+ return () => {
178
+ void stopDaemon();
179
+ };
180
+ });
181
+ } else {
182
+ void startDaemon();
183
+ process.once("beforeExit", () => void stopDaemon());
184
+ }
185
+ }
186
+
187
+ export default {
188
+ name,
189
+ inject,
190
+ apply,
191
+ };
package/src/process.ts CHANGED
@@ -1,45 +1,45 @@
1
- import { spawnSync, type SpawnSyncOptions } from "node:child_process";
2
-
3
- export interface CommandResult {
4
- status: number;
5
- stdout: string;
6
- stderr: string;
7
- }
8
-
9
- export function processRunning(
10
- pid: unknown,
11
- probe: (pid: number, signal: 0) => void = process.kill,
12
- ): boolean {
13
- if (!Number.isInteger(pid) || (pid as number) < 1) return false;
14
- try {
15
- probe(pid as number, 0);
16
- return true;
17
- } catch (error) {
18
- // Windows and hardened Unix environments can deny signalling an existing process. EPERM is
19
- // existence evidence, not proof that the launcher/browser/tunnel owner disappeared.
20
- return (error as NodeJS.ErrnoException)?.code === "EPERM";
21
- }
22
- }
23
-
24
- export function runCommand(command: string, args: string[], options: SpawnSyncOptions = {}): CommandResult {
25
- const result = spawnSync(command, args, {
26
- encoding: "utf8",
27
- stdio: "pipe",
28
- ...options,
29
- });
30
- if (result.error) throw result.error;
31
- return {
32
- status: result.status ?? 1,
33
- stdout: typeof result.stdout === "string" ? result.stdout : result.stdout?.toString("utf8") ?? "",
34
- stderr: typeof result.stderr === "string" ? result.stderr : result.stderr?.toString("utf8") ?? "",
35
- };
36
- }
37
-
38
- export function runChecked(command: string, args: string[], options: SpawnSyncOptions = {}): CommandResult {
39
- const result = runCommand(command, args, options);
40
- if (result.status !== 0) {
41
- const detail = result.stderr.trim() || result.stdout.trim() || `exit ${result.status}`;
42
- throw new Error(`${command} ${args.join(" ")} failed: ${detail}`);
43
- }
44
- return result;
45
- }
1
+ import { spawnSync, type SpawnSyncOptions } from "node:child_process";
2
+
3
+ export interface CommandResult {
4
+ status: number;
5
+ stdout: string;
6
+ stderr: string;
7
+ }
8
+
9
+ export function processRunning(
10
+ pid: unknown,
11
+ probe: (pid: number, signal: 0) => void = process.kill,
12
+ ): boolean {
13
+ if (!Number.isInteger(pid) || (pid as number) < 1) return false;
14
+ try {
15
+ probe(pid as number, 0);
16
+ return true;
17
+ } catch (error) {
18
+ // Windows and hardened Unix environments can deny signalling an existing process. EPERM is
19
+ // existence evidence, not proof that the launcher/browser/tunnel owner disappeared.
20
+ return (error as NodeJS.ErrnoException)?.code === "EPERM";
21
+ }
22
+ }
23
+
24
+ export function runCommand(command: string, args: string[], options: SpawnSyncOptions = {}): CommandResult {
25
+ const result = spawnSync(command, args, {
26
+ encoding: "utf8",
27
+ stdio: "pipe",
28
+ ...options,
29
+ });
30
+ if (result.error) throw result.error;
31
+ return {
32
+ status: result.status ?? 1,
33
+ stdout: typeof result.stdout === "string" ? result.stdout : result.stdout?.toString("utf8") ?? "",
34
+ stderr: typeof result.stderr === "string" ? result.stderr : result.stderr?.toString("utf8") ?? "",
35
+ };
36
+ }
37
+
38
+ export function runChecked(command: string, args: string[], options: SpawnSyncOptions = {}): CommandResult {
39
+ const result = runCommand(command, args, options);
40
+ if (result.status !== 0) {
41
+ const detail = result.stderr.trim() || result.stdout.trim() || `exit ${result.status}`;
42
+ throw new Error(`${command} ${args.join(" ")} failed: ${detail}`);
43
+ }
44
+ return result;
45
+ }