codex-chatgpt-control 0.1.0-alpha.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 (179) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +54 -0
  4. package/contracts/v1/fixtures/backend-capabilities.json +54 -0
  5. package/contracts/v1/fixtures/backend-error-event-missing-stream-input.json +10 -0
  6. package/contracts/v1/fixtures/backend-error-missing-run-input.json +10 -0
  7. package/contracts/v1/fixtures/backend-runner-plan-request.json +12 -0
  8. package/contracts/v1/fixtures/backend-version.json +10 -0
  9. package/contracts/v1/fixtures/command-descriptors.json +803 -0
  10. package/contracts/v1/fixtures/describe-runner-run.json +31 -0
  11. package/contracts/v1/fixtures/doctor-bridge-upload.json +34 -0
  12. package/contracts/v1/fixtures/help-root.json +6 -0
  13. package/contracts/v1/fixtures/named-plan-two-turn.json +39 -0
  14. package/contracts/v1/fixtures/output-json-parse-success.json +115 -0
  15. package/contracts/v1/fixtures/primitive-bootstrap-blocker.json +43 -0
  16. package/contracts/v1/fixtures/report-redaction-default.json +16 -0
  17. package/contracts/v1/fixtures/reports-create-redacted.json +16 -0
  18. package/contracts/v1/fixtures/reports-summarize-redacted.json +26 -0
  19. package/contracts/v1/fixtures/responses-basic-success.json +27 -0
  20. package/contracts/v1/fixtures/responses-hidden-instructions-unsupported.json +22 -0
  21. package/contracts/v1/fixtures/responses-unknown-field-unsupported.json +22 -0
  22. package/contracts/v1/fixtures/responses-unsupported-previous-response-id.json +22 -0
  23. package/contracts/v1/fixtures/responses-unsupported-temperature.json +22 -0
  24. package/contracts/v1/fixtures/run-basic-success.json +36 -0
  25. package/contracts/v1/fixtures/run-browser-bridge-blocker.json +180 -0
  26. package/contracts/v1/fixtures/run-file-attach-success.json +63 -0
  27. package/contracts/v1/fixtures/run-report-redacted.json +52 -0
  28. package/contracts/v1/fixtures/run-selector-drift-blocker.json +60 -0
  29. package/contracts/v1/fixtures/run-timeout-partial.json +81 -0
  30. package/contracts/v1/fixtures/run-two-turn-success.json +83 -0
  31. package/contracts/v1/fixtures/run-upload-permission-blocker.json +56 -0
  32. package/contracts/v1/fixtures/runner-budget-blocker.json +106 -0
  33. package/contracts/v1/fixtures/runner-full-agent-config.json +50 -0
  34. package/contracts/v1/fixtures/runner-input-items-and-files-plan.json +53 -0
  35. package/contracts/v1/fixtures/runner-metadata-only-plan.json +31 -0
  36. package/contracts/v1/fixtures/runner-visible-prefix-plan.json +28 -0
  37. package/contracts/v1/fixtures/runner-visible-setup-plan.json +37 -0
  38. package/contracts/v1/fixtures/stream-basic.ndjson +3 -0
  39. package/contracts/v1/fixtures/stream-blocked.ndjson +2 -0
  40. package/contracts/v1/fixtures/stream-submitted-completed.ndjson +3 -0
  41. package/contracts/v1/fixtures/workflow-ask-success.json +67 -0
  42. package/contracts/v1/manifest.json +210 -0
  43. package/contracts/v1/parity-suite.json +635 -0
  44. package/contracts/v1/schemas/agent.schema.json +52 -0
  45. package/contracts/v1/schemas/backend-event.schema.json +74 -0
  46. package/contracts/v1/schemas/backend-request.schema.json +59 -0
  47. package/contracts/v1/schemas/backend-response.schema.json +42 -0
  48. package/contracts/v1/schemas/capabilities.schema.json +33 -0
  49. package/contracts/v1/schemas/command-descriptor.schema.json +31 -0
  50. package/contracts/v1/schemas/command-result.schema.json +99 -0
  51. package/contracts/v1/schemas/manifest.schema.json +69 -0
  52. package/contracts/v1/schemas/response.schema.json +51 -0
  53. package/contracts/v1/schemas/run-result.schema.json +138 -0
  54. package/contracts/v1/schemas/sequence-plan.schema.json +44 -0
  55. package/contracts/v1/schemas/stream-event.schema.json +25 -0
  56. package/dist/codex-chatgpt-control-backend.mjs +5284 -0
  57. package/dist/codex-chatgpt-control.bundle.mjs +6013 -0
  58. package/dist/src/backend/client.d.ts +129 -0
  59. package/dist/src/backend/client.js +415 -0
  60. package/dist/src/backend/protocol.d.ts +74 -0
  61. package/dist/src/backend/protocol.js +128 -0
  62. package/dist/src/backend/session.d.ts +11 -0
  63. package/dist/src/backend/session.js +223 -0
  64. package/dist/src/backend/stdio-server.d.ts +9 -0
  65. package/dist/src/backend/stdio-server.js +88 -0
  66. package/dist/src/browser/attach.d.ts +8 -0
  67. package/dist/src/browser/attach.js +456 -0
  68. package/dist/src/browser/clipboard.d.ts +2 -0
  69. package/dist/src/browser/clipboard.js +26 -0
  70. package/dist/src/browser/downloads.d.ts +7 -0
  71. package/dist/src/browser/downloads.js +29 -0
  72. package/dist/src/browser/page-state.d.ts +17 -0
  73. package/dist/src/browser/page-state.js +66 -0
  74. package/dist/src/client.d.ts +167 -0
  75. package/dist/src/client.js +666 -0
  76. package/dist/src/commands/confirmations.d.ts +9 -0
  77. package/dist/src/commands/confirmations.js +33 -0
  78. package/dist/src/commands/context.d.ts +2 -0
  79. package/dist/src/commands/context.js +32 -0
  80. package/dist/src/commands/doctor.d.ts +16 -0
  81. package/dist/src/commands/doctor.js +120 -0
  82. package/dist/src/commands/files.d.ts +4 -0
  83. package/dist/src/commands/files.js +314 -0
  84. package/dist/src/commands/helpers.d.ts +16 -0
  85. package/dist/src/commands/helpers.js +128 -0
  86. package/dist/src/commands/messages.d.ts +16 -0
  87. package/dist/src/commands/messages.js +538 -0
  88. package/dist/src/commands/modes.d.ts +9 -0
  89. package/dist/src/commands/modes.js +323 -0
  90. package/dist/src/commands/output.d.ts +5 -0
  91. package/dist/src/commands/output.js +31 -0
  92. package/dist/src/commands/registry.d.ts +17 -0
  93. package/dist/src/commands/registry.js +275 -0
  94. package/dist/src/commands/reports.d.ts +14 -0
  95. package/dist/src/commands/reports.js +47 -0
  96. package/dist/src/commands/response-actions.d.ts +2 -0
  97. package/dist/src/commands/response-actions.js +141 -0
  98. package/dist/src/commands/sequence.d.ts +9 -0
  99. package/dist/src/commands/sequence.js +228 -0
  100. package/dist/src/commands/session.d.ts +2 -0
  101. package/dist/src/commands/session.js +25 -0
  102. package/dist/src/commands/threads.d.ts +6 -0
  103. package/dist/src/commands/threads.js +300 -0
  104. package/dist/src/dom/fixtures.d.ts +1 -0
  105. package/dist/src/dom/fixtures.js +8 -0
  106. package/dist/src/dom/menus.d.ts +8 -0
  107. package/dist/src/dom/menus.js +40 -0
  108. package/dist/src/dom/message-format.d.ts +30 -0
  109. package/dist/src/dom/message-format.js +593 -0
  110. package/dist/src/dom/messages.d.ts +40 -0
  111. package/dist/src/dom/messages.js +127 -0
  112. package/dist/src/dom/selectors.d.ts +20 -0
  113. package/dist/src/dom/selectors.js +72 -0
  114. package/dist/src/dom/visible-text.d.ts +5 -0
  115. package/dist/src/dom/visible-text.js +27 -0
  116. package/dist/src/errors.d.ts +32 -0
  117. package/dist/src/errors.js +127 -0
  118. package/dist/src/index.d.ts +34 -0
  119. package/dist/src/index.js +34 -0
  120. package/dist/src/logger.d.ts +15 -0
  121. package/dist/src/logger.js +25 -0
  122. package/dist/src/runner/agent.d.ts +2 -0
  123. package/dist/src/runner/agent.js +17 -0
  124. package/dist/src/runner/index.d.ts +7 -0
  125. package/dist/src/runner/index.js +7 -0
  126. package/dist/src/runner/interruptions.d.ts +3 -0
  127. package/dist/src/runner/interruptions.js +65 -0
  128. package/dist/src/runner/responses.d.ts +30 -0
  129. package/dist/src/runner/responses.js +171 -0
  130. package/dist/src/runner/result.d.ts +3 -0
  131. package/dist/src/runner/result.js +128 -0
  132. package/dist/src/runner/resume.d.ts +11 -0
  133. package/dist/src/runner/resume.js +26 -0
  134. package/dist/src/runner/stream.d.ts +13 -0
  135. package/dist/src/runner/stream.js +66 -0
  136. package/dist/src/runner/types.d.ts +264 -0
  137. package/dist/src/runner/types.js +1 -0
  138. package/dist/src/safety/blockers.d.ts +7 -0
  139. package/dist/src/safety/blockers.js +60 -0
  140. package/dist/src/safety/redaction.d.ts +2 -0
  141. package/dist/src/safety/redaction.js +18 -0
  142. package/dist/src/safety/report-redaction.d.ts +8 -0
  143. package/dist/src/safety/report-redaction.js +74 -0
  144. package/dist/src/safety/risk.d.ts +26 -0
  145. package/dist/src/safety/risk.js +28 -0
  146. package/dist/src/scripts/backend-server.d.ts +2 -0
  147. package/dist/src/scripts/backend-server.js +7 -0
  148. package/dist/src/scripts/check-parity-suite.d.ts +1 -0
  149. package/dist/src/scripts/check-parity-suite.js +10 -0
  150. package/dist/src/scripts/continue-thread.d.ts +27 -0
  151. package/dist/src/scripts/continue-thread.js +388 -0
  152. package/dist/src/scripts/live-smoke/harness.d.ts +11 -0
  153. package/dist/src/scripts/live-smoke/harness.js +151 -0
  154. package/dist/src/scripts/live-smoke/scenarios.d.ts +3 -0
  155. package/dist/src/scripts/live-smoke/scenarios.js +648 -0
  156. package/dist/src/scripts/live-smoke/types.d.ts +53 -0
  157. package/dist/src/scripts/live-smoke/types.js +1 -0
  158. package/dist/src/scripts/live-smoke-module.d.ts +3 -0
  159. package/dist/src/scripts/live-smoke-module.js +2 -0
  160. package/dist/src/scripts/live-smoke.d.ts +1 -0
  161. package/dist/src/scripts/live-smoke.js +54 -0
  162. package/dist/src/scripts/smoke-attach-file.d.ts +1 -0
  163. package/dist/src/scripts/smoke-attach-file.js +39 -0
  164. package/dist/src/scripts/smoke-hi.d.ts +1 -0
  165. package/dist/src/scripts/smoke-hi.js +25 -0
  166. package/dist/src/scripts/smoke-search-open-read.d.ts +1 -0
  167. package/dist/src/scripts/smoke-search-open-read.js +41 -0
  168. package/dist/src/testing/parity-suite.d.ts +12 -0
  169. package/dist/src/testing/parity-suite.js +213 -0
  170. package/dist/src/types.d.ts +495 -0
  171. package/dist/src/types.js +1 -0
  172. package/package.json +81 -0
  173. package/references/agents-runner.md +32 -0
  174. package/references/backend-protocol.md +238 -0
  175. package/references/python-parity.md +168 -0
  176. package/references/responses-adapter.md +28 -0
  177. package/references/safety.md +14 -0
  178. package/references/streaming.md +15 -0
  179. package/references/troubleshooting.md +91 -0
@@ -0,0 +1,129 @@
1
+ import type { AskAndDownloadWorkflowArgs, AskInThreadWorkflowArgs, AskWithFilesWorkflowArgs, AskWorkflowArgs, NamedWorkflowInvocation, RunMessagesArgs, WorkflowThread } from "../client.js";
2
+ import type { DoctorArgs, DoctorReport } from "../commands/doctor.js";
3
+ import type { RunReportData, RunReportOptions } from "../commands/reports.js";
4
+ import type { CommandDescriptor } from "../commands/registry.js";
5
+ import type { ChatGPTResponsesCreateArgs } from "../runner/responses.js";
6
+ import type { ChatGPTRunStream } from "../runner/stream.js";
7
+ import type { ChatGPTAgent, ChatGPTAgentConfig, ChatGPTResponse, ChatGPTRunInput, ChatGPTRunResult } from "../runner/types.js";
8
+ import type { ReportRedactionOptions } from "../safety/report-redaction.js";
9
+ import type { BootstrapArgs, CommandResult, CopyResponseArgs, DownloadLatestArgs, NewThreadArgs, OpenThreadArgs, ReadLatestArgs, SearchThreadsArgs, SelectToolArgs, SequencePlan, SetModeArgs, WaitArgs } from "../types.js";
10
+ import { type BackendEvent, type BackendRequest, type BackendResponse } from "./protocol.js";
11
+ export type BackendTransport = {
12
+ request(request: BackendRequest): Promise<BackendResponse>;
13
+ stream(request: BackendRequest): AsyncIterable<BackendEvent>;
14
+ close?: () => Promise<void> | void;
15
+ };
16
+ export declare class BackendClientError extends Error {
17
+ readonly code: string;
18
+ readonly recoverable: boolean;
19
+ constructor(code: string, message: string, recoverable: boolean);
20
+ }
21
+ export type ChatGPTBackendRunner = {
22
+ run<TOutput = string>(agent: ChatGPTAgent<TOutput>, input: ChatGPTRunInput): Promise<ChatGPTRunResult<TOutput>>;
23
+ plan<TOutput = string>(agent: ChatGPTAgent<TOutput>, input: ChatGPTRunInput): Promise<SequencePlan>;
24
+ stream<TOutput = string>(agent: ChatGPTAgent<TOutput>, input: ChatGPTRunInput): ChatGPTRunStream<TOutput>;
25
+ };
26
+ export type ChatGPTBackendClient = {
27
+ agent<TOutput = string>(config: ChatGPTAgentConfig<TOutput>): ChatGPTAgent<TOutput>;
28
+ run<TOutput = string>(agent: ChatGPTAgent<TOutput>, input: ChatGPTRunInput): Promise<ChatGPTRunResult<TOutput>>;
29
+ runner: ChatGPTBackendRunner;
30
+ responses: {
31
+ create(args: ChatGPTResponsesCreateArgs | Record<string, unknown>): Promise<ChatGPTResponse>;
32
+ };
33
+ commands(filter?: {
34
+ layer?: CommandDescriptor["layer"];
35
+ }): Promise<CommandDescriptor[]>;
36
+ describe(name: string): Promise<CommandDescriptor | undefined>;
37
+ help(topic?: string): Promise<string>;
38
+ ask(args: AskWorkflowArgs): Promise<CommandResult<unknown>>;
39
+ askInThread(args: AskInThreadWorkflowArgs): Promise<CommandResult<unknown>>;
40
+ askWithFiles(args: AskWithFilesWorkflowArgs): Promise<CommandResult<unknown>>;
41
+ askAndDownload(args: AskAndDownloadWorkflowArgs): Promise<CommandResult<unknown>>;
42
+ runMessages(args: RunMessagesArgs): Promise<CommandResult<unknown>>;
43
+ openThread(thread: WorkflowThread): Promise<CommandResult<unknown>>;
44
+ readLatest(args?: ReadLatestArgs): Promise<CommandResult<unknown>>;
45
+ copyLatest(args?: CopyResponseArgs): Promise<CommandResult<unknown>>;
46
+ downloadLatest(args: DownloadLatestArgs): Promise<CommandResult<unknown>>;
47
+ runPlan(plan: SequencePlan | NamedWorkflowInvocation): Promise<CommandResult<unknown>>;
48
+ doctor(args?: DoctorArgs): Promise<CommandResult<DoctorReport>>;
49
+ createReport(result: CommandResult<unknown>, args?: RunReportOptions): Promise<CommandResult<RunReportData>>;
50
+ reports: {
51
+ create(result: CommandResult<unknown>, args?: RunReportOptions): Promise<CommandResult<RunReportData>>;
52
+ redact(value: unknown, args?: ReportRedactionOptions): Promise<CommandResult<unknown>>;
53
+ summarize(result: CommandResult<unknown>, args?: ReportRedactionOptions): Promise<CommandResult<unknown>>;
54
+ };
55
+ session: {
56
+ bootstrap(args?: BootstrapArgs): Promise<CommandResult<unknown>>;
57
+ };
58
+ threads: {
59
+ "new"(args?: NewThreadArgs): Promise<CommandResult<unknown>>;
60
+ search(args: SearchThreadsArgs): Promise<CommandResult<unknown>>;
61
+ open(args: OpenThreadArgs): Promise<CommandResult<unknown>>;
62
+ };
63
+ messages: {
64
+ compose(args: {
65
+ text: string;
66
+ mode?: "replace" | "append";
67
+ timeoutMs?: number;
68
+ }): Promise<CommandResult<unknown>>;
69
+ submit(args?: {
70
+ text?: string;
71
+ previousTurnCount?: number;
72
+ timeoutMs?: number;
73
+ }): Promise<CommandResult<unknown>>;
74
+ ask(args: {
75
+ text: string;
76
+ wait?: boolean | WaitArgs;
77
+ read?: boolean | ReadLatestArgs;
78
+ timeoutMs?: number;
79
+ }): Promise<CommandResult<unknown>>;
80
+ wait(args?: WaitArgs): Promise<CommandResult<unknown>>;
81
+ readLatest(args?: ReadLatestArgs): Promise<CommandResult<unknown>>;
82
+ waitAndRead(args?: WaitArgs & ReadLatestArgs): Promise<CommandResult<unknown>>;
83
+ };
84
+ files: {
85
+ attach(args: {
86
+ paths: string[];
87
+ timeoutMs?: number;
88
+ }): Promise<CommandResult<unknown>>;
89
+ downloadLatest(args: DownloadLatestArgs): Promise<CommandResult<unknown>>;
90
+ };
91
+ modes: {
92
+ set(args: SetModeArgs): Promise<CommandResult<unknown>>;
93
+ };
94
+ tools: {
95
+ select(args: SelectToolArgs): Promise<CommandResult<unknown>>;
96
+ };
97
+ response: {
98
+ copy(args?: CopyResponseArgs): Promise<CommandResult<unknown>>;
99
+ };
100
+ close(): Promise<void>;
101
+ };
102
+ export declare function createChatGPTBackendClient(transport: BackendTransport): ChatGPTBackendClient;
103
+ export type StdioBackendTransportOptions = {
104
+ command: string[];
105
+ cwd?: string;
106
+ env?: NodeJS.ProcessEnv;
107
+ timeoutMs?: number;
108
+ };
109
+ export declare class StdioBackendTransport implements BackendTransport {
110
+ private readonly options;
111
+ private child;
112
+ private stdout;
113
+ private pendingResponses;
114
+ private pendingStreams;
115
+ private stderrText;
116
+ constructor(options: StdioBackendTransportOptions);
117
+ request(request: BackendRequest): Promise<BackendResponse>;
118
+ stream(request: BackendRequest): AsyncIterable<BackendEvent>;
119
+ close(): Promise<void>;
120
+ private start;
121
+ private write;
122
+ private handleLine;
123
+ private handleResponse;
124
+ private handleEvent;
125
+ private failAll;
126
+ private clearResponse;
127
+ private clearStream;
128
+ private createDeadline;
129
+ }
@@ -0,0 +1,415 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createInterface } from "node:readline";
3
+ import { createChatGPTAgent } from "../runner/agent.js";
4
+ import { BACKEND_REQUEST_SCHEMA_VERSION, BACKEND_RESPONSE_SCHEMA_VERSION, BACKEND_EVENT_SCHEMA_VERSION } from "./protocol.js";
5
+ export class BackendClientError extends Error {
6
+ code;
7
+ recoverable;
8
+ constructor(code, message, recoverable) {
9
+ super(message);
10
+ this.code = code;
11
+ this.recoverable = recoverable;
12
+ this.name = "BackendClientError";
13
+ }
14
+ }
15
+ export function createChatGPTBackendClient(transport) {
16
+ let nextRequestId = 0;
17
+ const request = async (command, payload = {}) => {
18
+ const response = await transport.request({
19
+ schemaVersion: BACKEND_REQUEST_SCHEMA_VERSION,
20
+ requestId: `req_${++nextRequestId}`,
21
+ command,
22
+ payload
23
+ });
24
+ return unwrapResponse(response);
25
+ };
26
+ const runner = {
27
+ run: (agent, input) => request("runner.run", { agent, input }),
28
+ plan: (agent, input) => request("runner.plan", { agent, input }),
29
+ stream: (agent, input) => streamFromBackendEvents(transport.stream({
30
+ schemaVersion: BACKEND_REQUEST_SCHEMA_VERSION,
31
+ requestId: `req_${++nextRequestId}`,
32
+ command: "runner.stream",
33
+ payload: { agent, input }
34
+ }))
35
+ };
36
+ return {
37
+ agent: config => createChatGPTAgent(config),
38
+ run: runner.run,
39
+ runner,
40
+ responses: {
41
+ create: args => request("responses.create", args)
42
+ },
43
+ commands: filter => request("commands", filter === undefined ? {} : { filter }),
44
+ describe: name => request("describe", { name }),
45
+ help: topic => request("help", topic === undefined ? {} : { topic }),
46
+ ask: args => request("ask", args),
47
+ askInThread: args => request("askInThread", args),
48
+ askWithFiles: args => request("askWithFiles", args),
49
+ askAndDownload: args => request("askAndDownload", args),
50
+ runMessages: args => request("runMessages", args),
51
+ openThread: thread => request("openThread", thread),
52
+ readLatest: args => request("readLatest", args ?? {}),
53
+ copyLatest: args => request("copyLatest", args ?? {}),
54
+ downloadLatest: args => request("downloadLatest", args),
55
+ runPlan: plan => request("runPlan", plan),
56
+ doctor: args => request("doctor", args ?? {}),
57
+ createReport: (result, args) => request("createReport", args === undefined ? { result } : { result, args }),
58
+ reports: {
59
+ create: (result, args) => request("reports.create", args === undefined ? { result } : { result, args }),
60
+ redact: (value, args) => request("reports.redact", args === undefined ? { value } : { value, args }),
61
+ summarize: (result, args) => request("reports.summarize", args === undefined ? { result } : { result, args })
62
+ },
63
+ session: {
64
+ bootstrap: args => request("session.bootstrap", args ?? {})
65
+ },
66
+ threads: {
67
+ new: args => request("threads.new", args ?? {}),
68
+ search: args => request("threads.search", args),
69
+ open: args => request("threads.open", args)
70
+ },
71
+ messages: {
72
+ compose: args => request("messages.compose", args),
73
+ submit: args => request("messages.submit", args ?? {}),
74
+ ask: args => request("messages.ask", args),
75
+ wait: args => request("messages.wait", args ?? {}),
76
+ readLatest: args => request("messages.readLatest", args ?? {}),
77
+ waitAndRead: args => request("messages.waitAndRead", args)
78
+ },
79
+ files: {
80
+ attach: args => request("files.attach", args),
81
+ downloadLatest: args => request("files.downloadLatest", args)
82
+ },
83
+ modes: {
84
+ set: args => request("modes.set", args)
85
+ },
86
+ tools: {
87
+ select: args => request("tools.select", args)
88
+ },
89
+ response: {
90
+ copy: args => request("response.copy", args ?? {})
91
+ },
92
+ close: async () => {
93
+ await transport.close?.();
94
+ }
95
+ };
96
+ }
97
+ const DEFAULT_BACKEND_TIMEOUT_MS = 600_000;
98
+ export class StdioBackendTransport {
99
+ options;
100
+ child;
101
+ stdout;
102
+ pendingResponses = new Map();
103
+ pendingStreams = new Map();
104
+ stderrText = "";
105
+ constructor(options) {
106
+ this.options = options;
107
+ }
108
+ async request(request) {
109
+ const requestId = requireRequestId(request);
110
+ this.start();
111
+ return new Promise((resolve, reject) => {
112
+ const timeout = this.createDeadline(requestId);
113
+ this.pendingResponses.set(requestId, { resolve, reject, timeout });
114
+ this.write(request, error => {
115
+ this.clearResponse(requestId);
116
+ reject(error);
117
+ });
118
+ });
119
+ }
120
+ stream(request) {
121
+ const requestId = requireRequestId(request);
122
+ this.start();
123
+ const queue = new AsyncQueue();
124
+ const timeout = this.createDeadline(requestId);
125
+ this.pendingStreams.set(requestId, { queue, timeout });
126
+ this.write(request, error => {
127
+ this.clearStream(requestId);
128
+ queue.fail(error);
129
+ });
130
+ return queue;
131
+ }
132
+ async close() {
133
+ const child = this.child;
134
+ if (child === undefined)
135
+ return;
136
+ this.child = undefined;
137
+ this.stdout?.close();
138
+ this.stdout = undefined;
139
+ child.removeAllListeners("error");
140
+ child.removeAllListeners("exit");
141
+ child.kill();
142
+ this.failAll(new BackendClientError("backend_closed", "Backend transport was closed.", true));
143
+ }
144
+ start() {
145
+ if (this.child !== undefined)
146
+ return;
147
+ const [command, ...args] = this.options.command;
148
+ if (command === undefined) {
149
+ throw new BackendClientError("invalid_backend_command", "Stdio backend command must not be empty.", false);
150
+ }
151
+ const child = spawn(command, args, {
152
+ cwd: this.options.cwd,
153
+ env: this.options.env,
154
+ stdio: ["pipe", "pipe", "pipe"]
155
+ });
156
+ this.child = child;
157
+ this.stdout = createInterface({ input: child.stdout, crlfDelay: Infinity });
158
+ this.stdout.on("line", line => {
159
+ if (this.child !== child)
160
+ return;
161
+ this.handleLine(line);
162
+ });
163
+ child.stderr.setEncoding("utf8");
164
+ child.stderr.on("data", chunk => {
165
+ if (this.child !== child)
166
+ return;
167
+ this.stderrText = `${this.stderrText}${String(chunk)}`.slice(-4000);
168
+ });
169
+ child.on("error", error => {
170
+ if (this.child !== child)
171
+ return;
172
+ this.child = undefined;
173
+ this.stdout?.close();
174
+ this.stdout = undefined;
175
+ this.failAll(error);
176
+ });
177
+ child.on("exit", (code, signal) => {
178
+ if (this.child !== child)
179
+ return;
180
+ const suffix = this.stderrText.length > 0 ? ` stderr=${this.stderrText}` : "";
181
+ this.child = undefined;
182
+ this.stdout?.close();
183
+ this.stdout = undefined;
184
+ this.failAll(new BackendClientError("backend_exited", `Backend process exited with code ${String(code)} signal ${String(signal)}.${suffix}`, true));
185
+ });
186
+ }
187
+ write(request, reject) {
188
+ const child = this.child;
189
+ if (child === undefined) {
190
+ reject(new BackendClientError("backend_not_started", "Backend process is not running.", true));
191
+ return;
192
+ }
193
+ child.stdin.write(`${JSON.stringify(request)}\n`, error => {
194
+ if (error !== null && error !== undefined)
195
+ reject(error);
196
+ });
197
+ }
198
+ handleLine(line) {
199
+ let value;
200
+ try {
201
+ value = JSON.parse(line);
202
+ }
203
+ catch (error) {
204
+ this.failAll(new BackendClientError("invalid_backend_json", error instanceof Error ? error.message : String(error), true));
205
+ return;
206
+ }
207
+ if (!isRecord(value)) {
208
+ this.failAll(new BackendClientError("invalid_backend_message", "Backend protocol line must be a JSON object.", true));
209
+ return;
210
+ }
211
+ if (value.schemaVersion === BACKEND_RESPONSE_SCHEMA_VERSION) {
212
+ this.handleResponse(value);
213
+ return;
214
+ }
215
+ if (value.schemaVersion === BACKEND_EVENT_SCHEMA_VERSION) {
216
+ this.handleEvent(value);
217
+ return;
218
+ }
219
+ this.failAll(new BackendClientError("unsupported_backend_schema", `Unsupported backend protocol schemaVersion: ${String(value.schemaVersion)}`, true));
220
+ }
221
+ handleResponse(response) {
222
+ const requestId = response.requestId;
223
+ if (requestId === undefined) {
224
+ this.failAll(new BackendClientError("missing_backend_request_id", "Backend response is missing requestId.", true));
225
+ return;
226
+ }
227
+ const pending = this.pendingResponses.get(requestId);
228
+ if (pending === undefined) {
229
+ const stream = this.pendingStreams.get(requestId);
230
+ if (stream !== undefined) {
231
+ stream.queue.fail(new BackendClientError("unexpected_backend_response", `Backend sent a response for streaming requestId ${requestId}.`, true));
232
+ this.clearStream(requestId);
233
+ return;
234
+ }
235
+ this.failAll(new BackendClientError("unknown_backend_request_id", `Backend response used unknown requestId ${requestId}.`, true));
236
+ return;
237
+ }
238
+ if (typeof response.ok !== "boolean") {
239
+ this.clearResponse(requestId);
240
+ pending.reject(new BackendClientError("invalid_backend_response", `Backend response for requestId ${requestId} is missing boolean ok.`, true));
241
+ return;
242
+ }
243
+ this.clearResponse(requestId);
244
+ pending.resolve(response);
245
+ }
246
+ handleEvent(event) {
247
+ const requestId = event.requestId;
248
+ if (requestId === undefined) {
249
+ this.failAll(new BackendClientError("missing_backend_request_id", "Backend event is missing requestId.", true));
250
+ return;
251
+ }
252
+ const pending = this.pendingStreams.get(requestId);
253
+ if (pending === undefined) {
254
+ const response = this.pendingResponses.get(requestId);
255
+ if (response !== undefined) {
256
+ this.clearResponse(requestId);
257
+ response.reject(new BackendClientError("unexpected_backend_event", `Backend sent an event for non-streaming requestId ${requestId}.`, true));
258
+ return;
259
+ }
260
+ this.failAll(new BackendClientError("unknown_backend_request_id", `Backend event used unknown requestId ${requestId}.`, true));
261
+ return;
262
+ }
263
+ if (typeof event.type !== "string") {
264
+ pending.queue.fail(new BackendClientError("invalid_backend_event", `Backend event for requestId ${requestId} is missing type.`, true));
265
+ this.clearStream(requestId);
266
+ return;
267
+ }
268
+ pending.queue.push(event);
269
+ if (event.type === "completed") {
270
+ pending.queue.finish();
271
+ this.clearStream(requestId);
272
+ }
273
+ if (event.type === "error") {
274
+ pending.queue.fail(new BackendClientError(event.error.code, event.error.message, event.error.recoverable));
275
+ this.clearStream(requestId);
276
+ }
277
+ }
278
+ failAll(error) {
279
+ for (const pending of this.pendingResponses.values()) {
280
+ clearTimeout(pending.timeout);
281
+ pending.reject(error);
282
+ }
283
+ this.pendingResponses.clear();
284
+ for (const pending of this.pendingStreams.values()) {
285
+ clearTimeout(pending.timeout);
286
+ pending.queue.fail(error);
287
+ }
288
+ this.pendingStreams.clear();
289
+ }
290
+ clearResponse(requestId) {
291
+ const pending = this.pendingResponses.get(requestId);
292
+ if (pending !== undefined) {
293
+ clearTimeout(pending.timeout);
294
+ this.pendingResponses.delete(requestId);
295
+ }
296
+ }
297
+ clearStream(requestId) {
298
+ const pending = this.pendingStreams.get(requestId);
299
+ if (pending !== undefined) {
300
+ clearTimeout(pending.timeout);
301
+ this.pendingStreams.delete(requestId);
302
+ }
303
+ }
304
+ createDeadline(requestId) {
305
+ const timeoutMs = this.options.timeoutMs ?? DEFAULT_BACKEND_TIMEOUT_MS;
306
+ return setTimeout(() => {
307
+ const error = new BackendClientError("backend_timeout", `Backend request ${requestId} timed out after ${timeoutMs}ms.`, true);
308
+ const child = this.child;
309
+ this.child = undefined;
310
+ this.stdout?.close();
311
+ this.stdout = undefined;
312
+ if (child !== undefined) {
313
+ child.removeAllListeners("error");
314
+ child.removeAllListeners("exit");
315
+ child.kill();
316
+ }
317
+ this.failAll(error);
318
+ }, timeoutMs);
319
+ }
320
+ }
321
+ function unwrapResponse(response) {
322
+ if (response.ok)
323
+ return response.result;
324
+ throw new BackendClientError(response.error.code, response.error.message, response.error.recoverable);
325
+ }
326
+ function streamFromBackendEvents(events) {
327
+ const queue = new AsyncQueue();
328
+ let resolveCompleted;
329
+ let rejectCompleted;
330
+ const completed = new Promise((resolve, reject) => {
331
+ resolveCompleted = resolve;
332
+ rejectCompleted = reject;
333
+ });
334
+ void (async () => {
335
+ try {
336
+ for await (const event of events) {
337
+ if (event.type === "run_item_stream_event") {
338
+ queue.push({
339
+ type: "run_item_stream_event",
340
+ name: event.name,
341
+ item: event.item
342
+ });
343
+ continue;
344
+ }
345
+ if (event.type === "completed") {
346
+ resolveCompleted(event.result);
347
+ queue.finish();
348
+ return;
349
+ }
350
+ if (event.type === "error") {
351
+ throw new BackendClientError(event.error.code, event.error.message, event.error.recoverable);
352
+ }
353
+ }
354
+ throw new BackendClientError("stream_incomplete", "Backend stream ended before a completed event.", true);
355
+ }
356
+ catch (error) {
357
+ queue.fail(error);
358
+ rejectCompleted(error);
359
+ }
360
+ })();
361
+ return {
362
+ completed,
363
+ [Symbol.asyncIterator]: () => queue[Symbol.asyncIterator]()
364
+ };
365
+ }
366
+ function requireRequestId(request) {
367
+ if (request.requestId === undefined) {
368
+ throw new BackendClientError("missing_request_id", "Backend transport requests require requestId.", false);
369
+ }
370
+ return request.requestId;
371
+ }
372
+ class AsyncQueue {
373
+ values = [];
374
+ waiters = [];
375
+ done = false;
376
+ error;
377
+ push(value) {
378
+ if (this.done || this.error !== undefined)
379
+ return;
380
+ this.values.push(value);
381
+ this.wake();
382
+ }
383
+ finish() {
384
+ this.done = true;
385
+ this.wake();
386
+ }
387
+ fail(error) {
388
+ this.error = error;
389
+ this.wake();
390
+ }
391
+ async *[Symbol.asyncIterator]() {
392
+ while (true) {
393
+ const value = this.values.shift();
394
+ if (value !== undefined) {
395
+ yield value;
396
+ continue;
397
+ }
398
+ if (this.error !== undefined)
399
+ throw this.error;
400
+ if (this.done)
401
+ return;
402
+ await new Promise(resolve => {
403
+ this.waiters.push(resolve);
404
+ });
405
+ }
406
+ }
407
+ wake() {
408
+ const waiters = this.waiters.splice(0);
409
+ for (const waiter of waiters)
410
+ waiter();
411
+ }
412
+ }
413
+ function isRecord(value) {
414
+ return value !== null && typeof value === "object" && !Array.isArray(value);
415
+ }
@@ -0,0 +1,74 @@
1
+ export declare const BACKEND_REQUEST_SCHEMA_VERSION: "chatgpt.browser_control.backend_request.v1";
2
+ export declare const BACKEND_RESPONSE_SCHEMA_VERSION: "chatgpt.browser_control.backend_response.v1";
3
+ export declare const BACKEND_EVENT_SCHEMA_VERSION: "chatgpt.browser_control.backend_event.v1";
4
+ export declare const backendCommands: readonly ["backend.version", "backend.health", "backend.capabilities", "runner.run", "runner.plan", "runner.stream", "responses.create", "ask", "askInThread", "askWithFiles", "askAndDownload", "runMessages", "openThread", "readLatest", "copyLatest", "downloadLatest", "runPlan", "doctor", "createReport", "reports.create", "reports.redact", "reports.summarize", "commands", "describe", "help", "session.bootstrap", "threads.new", "threads.search", "threads.open", "messages.compose", "messages.submit", "messages.ask", "messages.wait", "messages.readLatest", "messages.waitAndRead", "files.attach", "files.downloadLatest", "modes.set", "tools.select", "response.copy"];
5
+ export type BackendCommand = typeof backendCommands[number];
6
+ export type BackendRequest = {
7
+ schemaVersion: typeof BACKEND_REQUEST_SCHEMA_VERSION;
8
+ requestId?: string;
9
+ command: BackendCommand;
10
+ payload: Record<string, unknown>;
11
+ };
12
+ export type BackendProtocolErrorCode = "invalid_request" | "unsupported_schema_version" | "unknown_command";
13
+ export declare class ProtocolError extends Error {
14
+ readonly code: BackendProtocolErrorCode;
15
+ readonly recoverable: boolean;
16
+ constructor(code: BackendProtocolErrorCode, message: string, recoverable: boolean);
17
+ }
18
+ export type BackendResponseOk<TResult = unknown> = {
19
+ schemaVersion: typeof BACKEND_RESPONSE_SCHEMA_VERSION;
20
+ requestId?: string;
21
+ ok: true;
22
+ result: TResult;
23
+ };
24
+ export type BackendResponseError = {
25
+ schemaVersion: typeof BACKEND_RESPONSE_SCHEMA_VERSION;
26
+ requestId?: string;
27
+ ok: false;
28
+ error: {
29
+ code: BackendProtocolErrorCode | string;
30
+ message: string;
31
+ recoverable: boolean;
32
+ };
33
+ };
34
+ export type BackendResponse<TResult = unknown> = BackendResponseOk<TResult> | BackendResponseError;
35
+ export type BackendCapabilities = {
36
+ protocolVersion: typeof BACKEND_REQUEST_SCHEMA_VERSION;
37
+ commands: BackendCommand[];
38
+ transports: Array<"stdio" | "http">;
39
+ streaming: {
40
+ modes: Array<"ndjson" | "sse">;
41
+ tokenDeltas: false;
42
+ };
43
+ };
44
+ export type BackendRunItemStreamEvent = {
45
+ type: "run_item_stream_event";
46
+ name: string;
47
+ item: Record<string, unknown>;
48
+ };
49
+ export type BackendAgentUpdatedStreamEvent = {
50
+ type: "agent_updated_stream_event";
51
+ agent: Record<string, unknown>;
52
+ };
53
+ export type BackendCompletedEvent = {
54
+ type: "completed";
55
+ result: unknown;
56
+ };
57
+ export type BackendErrorEvent = {
58
+ type: "error";
59
+ error: {
60
+ code: string;
61
+ message: string;
62
+ recoverable: boolean;
63
+ };
64
+ };
65
+ export type BackendEventPayload = BackendRunItemStreamEvent | BackendAgentUpdatedStreamEvent | BackendCompletedEvent | BackendErrorEvent;
66
+ export type BackendEvent = BackendEventPayload & {
67
+ schemaVersion: typeof BACKEND_EVENT_SCHEMA_VERSION;
68
+ requestId?: string;
69
+ };
70
+ export declare function parseBackendRequest(raw: unknown): BackendRequest;
71
+ export declare function backendResponseOk<TResult>(requestId: string | undefined, result: TResult): BackendResponseOk<TResult>;
72
+ export declare function backendResponseError(requestId: string | undefined, error: ProtocolError | Error): BackendResponseError;
73
+ export declare function backendEvent(requestId: string | undefined, payload: BackendEventPayload): BackendEvent;
74
+ export declare function backendEventCompleted(requestId: string | undefined, result: unknown): BackendEvent;