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.
- package/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +54 -0
- package/contracts/v1/fixtures/backend-capabilities.json +54 -0
- package/contracts/v1/fixtures/backend-error-event-missing-stream-input.json +10 -0
- package/contracts/v1/fixtures/backend-error-missing-run-input.json +10 -0
- package/contracts/v1/fixtures/backend-runner-plan-request.json +12 -0
- package/contracts/v1/fixtures/backend-version.json +10 -0
- package/contracts/v1/fixtures/command-descriptors.json +803 -0
- package/contracts/v1/fixtures/describe-runner-run.json +31 -0
- package/contracts/v1/fixtures/doctor-bridge-upload.json +34 -0
- package/contracts/v1/fixtures/help-root.json +6 -0
- package/contracts/v1/fixtures/named-plan-two-turn.json +39 -0
- package/contracts/v1/fixtures/output-json-parse-success.json +115 -0
- package/contracts/v1/fixtures/primitive-bootstrap-blocker.json +43 -0
- package/contracts/v1/fixtures/report-redaction-default.json +16 -0
- package/contracts/v1/fixtures/reports-create-redacted.json +16 -0
- package/contracts/v1/fixtures/reports-summarize-redacted.json +26 -0
- package/contracts/v1/fixtures/responses-basic-success.json +27 -0
- package/contracts/v1/fixtures/responses-hidden-instructions-unsupported.json +22 -0
- package/contracts/v1/fixtures/responses-unknown-field-unsupported.json +22 -0
- package/contracts/v1/fixtures/responses-unsupported-previous-response-id.json +22 -0
- package/contracts/v1/fixtures/responses-unsupported-temperature.json +22 -0
- package/contracts/v1/fixtures/run-basic-success.json +36 -0
- package/contracts/v1/fixtures/run-browser-bridge-blocker.json +180 -0
- package/contracts/v1/fixtures/run-file-attach-success.json +63 -0
- package/contracts/v1/fixtures/run-report-redacted.json +52 -0
- package/contracts/v1/fixtures/run-selector-drift-blocker.json +60 -0
- package/contracts/v1/fixtures/run-timeout-partial.json +81 -0
- package/contracts/v1/fixtures/run-two-turn-success.json +83 -0
- package/contracts/v1/fixtures/run-upload-permission-blocker.json +56 -0
- package/contracts/v1/fixtures/runner-budget-blocker.json +106 -0
- package/contracts/v1/fixtures/runner-full-agent-config.json +50 -0
- package/contracts/v1/fixtures/runner-input-items-and-files-plan.json +53 -0
- package/contracts/v1/fixtures/runner-metadata-only-plan.json +31 -0
- package/contracts/v1/fixtures/runner-visible-prefix-plan.json +28 -0
- package/contracts/v1/fixtures/runner-visible-setup-plan.json +37 -0
- package/contracts/v1/fixtures/stream-basic.ndjson +3 -0
- package/contracts/v1/fixtures/stream-blocked.ndjson +2 -0
- package/contracts/v1/fixtures/stream-submitted-completed.ndjson +3 -0
- package/contracts/v1/fixtures/workflow-ask-success.json +67 -0
- package/contracts/v1/manifest.json +210 -0
- package/contracts/v1/parity-suite.json +635 -0
- package/contracts/v1/schemas/agent.schema.json +52 -0
- package/contracts/v1/schemas/backend-event.schema.json +74 -0
- package/contracts/v1/schemas/backend-request.schema.json +59 -0
- package/contracts/v1/schemas/backend-response.schema.json +42 -0
- package/contracts/v1/schemas/capabilities.schema.json +33 -0
- package/contracts/v1/schemas/command-descriptor.schema.json +31 -0
- package/contracts/v1/schemas/command-result.schema.json +99 -0
- package/contracts/v1/schemas/manifest.schema.json +69 -0
- package/contracts/v1/schemas/response.schema.json +51 -0
- package/contracts/v1/schemas/run-result.schema.json +138 -0
- package/contracts/v1/schemas/sequence-plan.schema.json +44 -0
- package/contracts/v1/schemas/stream-event.schema.json +25 -0
- package/dist/codex-chatgpt-control-backend.mjs +5284 -0
- package/dist/codex-chatgpt-control.bundle.mjs +6013 -0
- package/dist/src/backend/client.d.ts +129 -0
- package/dist/src/backend/client.js +415 -0
- package/dist/src/backend/protocol.d.ts +74 -0
- package/dist/src/backend/protocol.js +128 -0
- package/dist/src/backend/session.d.ts +11 -0
- package/dist/src/backend/session.js +223 -0
- package/dist/src/backend/stdio-server.d.ts +9 -0
- package/dist/src/backend/stdio-server.js +88 -0
- package/dist/src/browser/attach.d.ts +8 -0
- package/dist/src/browser/attach.js +456 -0
- package/dist/src/browser/clipboard.d.ts +2 -0
- package/dist/src/browser/clipboard.js +26 -0
- package/dist/src/browser/downloads.d.ts +7 -0
- package/dist/src/browser/downloads.js +29 -0
- package/dist/src/browser/page-state.d.ts +17 -0
- package/dist/src/browser/page-state.js +66 -0
- package/dist/src/client.d.ts +167 -0
- package/dist/src/client.js +666 -0
- package/dist/src/commands/confirmations.d.ts +9 -0
- package/dist/src/commands/confirmations.js +33 -0
- package/dist/src/commands/context.d.ts +2 -0
- package/dist/src/commands/context.js +32 -0
- package/dist/src/commands/doctor.d.ts +16 -0
- package/dist/src/commands/doctor.js +120 -0
- package/dist/src/commands/files.d.ts +4 -0
- package/dist/src/commands/files.js +314 -0
- package/dist/src/commands/helpers.d.ts +16 -0
- package/dist/src/commands/helpers.js +128 -0
- package/dist/src/commands/messages.d.ts +16 -0
- package/dist/src/commands/messages.js +538 -0
- package/dist/src/commands/modes.d.ts +9 -0
- package/dist/src/commands/modes.js +323 -0
- package/dist/src/commands/output.d.ts +5 -0
- package/dist/src/commands/output.js +31 -0
- package/dist/src/commands/registry.d.ts +17 -0
- package/dist/src/commands/registry.js +275 -0
- package/dist/src/commands/reports.d.ts +14 -0
- package/dist/src/commands/reports.js +47 -0
- package/dist/src/commands/response-actions.d.ts +2 -0
- package/dist/src/commands/response-actions.js +141 -0
- package/dist/src/commands/sequence.d.ts +9 -0
- package/dist/src/commands/sequence.js +228 -0
- package/dist/src/commands/session.d.ts +2 -0
- package/dist/src/commands/session.js +25 -0
- package/dist/src/commands/threads.d.ts +6 -0
- package/dist/src/commands/threads.js +300 -0
- package/dist/src/dom/fixtures.d.ts +1 -0
- package/dist/src/dom/fixtures.js +8 -0
- package/dist/src/dom/menus.d.ts +8 -0
- package/dist/src/dom/menus.js +40 -0
- package/dist/src/dom/message-format.d.ts +30 -0
- package/dist/src/dom/message-format.js +593 -0
- package/dist/src/dom/messages.d.ts +40 -0
- package/dist/src/dom/messages.js +127 -0
- package/dist/src/dom/selectors.d.ts +20 -0
- package/dist/src/dom/selectors.js +72 -0
- package/dist/src/dom/visible-text.d.ts +5 -0
- package/dist/src/dom/visible-text.js +27 -0
- package/dist/src/errors.d.ts +32 -0
- package/dist/src/errors.js +127 -0
- package/dist/src/index.d.ts +34 -0
- package/dist/src/index.js +34 -0
- package/dist/src/logger.d.ts +15 -0
- package/dist/src/logger.js +25 -0
- package/dist/src/runner/agent.d.ts +2 -0
- package/dist/src/runner/agent.js +17 -0
- package/dist/src/runner/index.d.ts +7 -0
- package/dist/src/runner/index.js +7 -0
- package/dist/src/runner/interruptions.d.ts +3 -0
- package/dist/src/runner/interruptions.js +65 -0
- package/dist/src/runner/responses.d.ts +30 -0
- package/dist/src/runner/responses.js +171 -0
- package/dist/src/runner/result.d.ts +3 -0
- package/dist/src/runner/result.js +128 -0
- package/dist/src/runner/resume.d.ts +11 -0
- package/dist/src/runner/resume.js +26 -0
- package/dist/src/runner/stream.d.ts +13 -0
- package/dist/src/runner/stream.js +66 -0
- package/dist/src/runner/types.d.ts +264 -0
- package/dist/src/runner/types.js +1 -0
- package/dist/src/safety/blockers.d.ts +7 -0
- package/dist/src/safety/blockers.js +60 -0
- package/dist/src/safety/redaction.d.ts +2 -0
- package/dist/src/safety/redaction.js +18 -0
- package/dist/src/safety/report-redaction.d.ts +8 -0
- package/dist/src/safety/report-redaction.js +74 -0
- package/dist/src/safety/risk.d.ts +26 -0
- package/dist/src/safety/risk.js +28 -0
- package/dist/src/scripts/backend-server.d.ts +2 -0
- package/dist/src/scripts/backend-server.js +7 -0
- package/dist/src/scripts/check-parity-suite.d.ts +1 -0
- package/dist/src/scripts/check-parity-suite.js +10 -0
- package/dist/src/scripts/continue-thread.d.ts +27 -0
- package/dist/src/scripts/continue-thread.js +388 -0
- package/dist/src/scripts/live-smoke/harness.d.ts +11 -0
- package/dist/src/scripts/live-smoke/harness.js +151 -0
- package/dist/src/scripts/live-smoke/scenarios.d.ts +3 -0
- package/dist/src/scripts/live-smoke/scenarios.js +648 -0
- package/dist/src/scripts/live-smoke/types.d.ts +53 -0
- package/dist/src/scripts/live-smoke/types.js +1 -0
- package/dist/src/scripts/live-smoke-module.d.ts +3 -0
- package/dist/src/scripts/live-smoke-module.js +2 -0
- package/dist/src/scripts/live-smoke.d.ts +1 -0
- package/dist/src/scripts/live-smoke.js +54 -0
- package/dist/src/scripts/smoke-attach-file.d.ts +1 -0
- package/dist/src/scripts/smoke-attach-file.js +39 -0
- package/dist/src/scripts/smoke-hi.d.ts +1 -0
- package/dist/src/scripts/smoke-hi.js +25 -0
- package/dist/src/scripts/smoke-search-open-read.d.ts +1 -0
- package/dist/src/scripts/smoke-search-open-read.js +41 -0
- package/dist/src/testing/parity-suite.d.ts +12 -0
- package/dist/src/testing/parity-suite.js +213 -0
- package/dist/src/types.d.ts +495 -0
- package/dist/src/types.js +1 -0
- package/package.json +81 -0
- package/references/agents-runner.md +32 -0
- package/references/backend-protocol.md +238 -0
- package/references/python-parity.md +168 -0
- package/references/responses-adapter.md +28 -0
- package/references/safety.md +14 -0
- package/references/streaming.md +15 -0
- package/references/troubleshooting.md +91 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Backend Protocol
|
|
2
|
+
|
|
3
|
+
The local backend is a long-lived language-neutral service. The initial implementation is Node/TypeScript, exposed over stdio NDJSON.
|
|
4
|
+
|
|
5
|
+
This is the contract that keeps Node and Python deeply in sync:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Node in-process SDK
|
|
9
|
+
Node backend client
|
|
10
|
+
Python SDK
|
|
11
|
+
Future SDKs
|
|
12
|
+
-> backend protocol
|
|
13
|
+
-> compatible browser-control backend
|
|
14
|
+
-> browser bridge
|
|
15
|
+
-> chatgpt.com
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The current live backend is Node-backed. A future Python-native backend can replace it by implementing this protocol and passing the same contract fixtures and smoke gates.
|
|
19
|
+
|
|
20
|
+
## Stdio Transport
|
|
21
|
+
|
|
22
|
+
Each request is one JSON line on stdin. Backend stdout is reserved for protocol JSON only; diagnostics go to stderr.
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"schemaVersion": "chatgpt.browser_control.backend_request.v1",
|
|
27
|
+
"requestId": "req_1",
|
|
28
|
+
"command": "backend.health",
|
|
29
|
+
"payload": {}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Each non-streaming response is one JSON line:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"schemaVersion": "chatgpt.browser_control.backend_response.v1",
|
|
38
|
+
"requestId": "req_1",
|
|
39
|
+
"ok": true,
|
|
40
|
+
"result": {
|
|
41
|
+
"ok": true,
|
|
42
|
+
"status": "ok"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Protocol errors use the same response envelope:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"schemaVersion": "chatgpt.browser_control.backend_response.v1",
|
|
52
|
+
"requestId": "req_1",
|
|
53
|
+
"ok": false,
|
|
54
|
+
"error": {
|
|
55
|
+
"code": "unknown_command",
|
|
56
|
+
"message": "Unknown backend command: runner.nope",
|
|
57
|
+
"recoverable": false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Current protocol error codes are:
|
|
63
|
+
|
|
64
|
+
- `invalid_request`
|
|
65
|
+
- `unsupported_schema_version`
|
|
66
|
+
- `unknown_command`
|
|
67
|
+
|
|
68
|
+
Browser-control blockers are not protocol errors. They are normal command or runner results with `status: "blocked"`, `status: "partial"`, or `status: "needs_confirmation"` plus blocker/interruption details.
|
|
69
|
+
|
|
70
|
+
## Streaming
|
|
71
|
+
|
|
72
|
+
Streaming commands emit backend event lines until `completed` or `error`.
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"schemaVersion": "chatgpt.browser_control.backend_event.v1",
|
|
77
|
+
"requestId": "req_stream",
|
|
78
|
+
"type": "run_item_stream_event",
|
|
79
|
+
"name": "message_completed",
|
|
80
|
+
"item": {
|
|
81
|
+
"type": "message.completed"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The final event contains a normal runner result:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"schemaVersion": "chatgpt.browser_control.backend_event.v1",
|
|
91
|
+
"requestId": "req_stream",
|
|
92
|
+
"type": "completed",
|
|
93
|
+
"result": {
|
|
94
|
+
"status": "ok",
|
|
95
|
+
"output_text": "hi"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Streaming is milestone streaming only. It does not promise token deltas or OpenAI API stream-event parity.
|
|
101
|
+
|
|
102
|
+
## Required Backend Commands
|
|
103
|
+
|
|
104
|
+
The backend must support:
|
|
105
|
+
|
|
106
|
+
- lifecycle: `backend.version`, `backend.health`, `backend.capabilities`
|
|
107
|
+
- runner: `runner.run`, `runner.plan`, `runner.stream`
|
|
108
|
+
- Responses adapter: `responses.create`
|
|
109
|
+
- workflows: `ask`, `askInThread`, `askWithFiles`, `askAndDownload`, `runMessages`, `openThread`, `runPlan`
|
|
110
|
+
- reports: `createReport`, `reports.create`, `reports.redact`, `reports.summarize`
|
|
111
|
+
- command discovery: `commands`, `describe`, `help`
|
|
112
|
+
- primitives: `session.bootstrap`, `threads.*`, `messages.*`, `files.*`, `modes.set`, `tools.select`, `response.copy`
|
|
113
|
+
|
|
114
|
+
`session.bootstrap` accepts `existingTab` for explicit reuse of a user-open Chrome tab before any read or prompt step. The wire shape is shared by TypeScript and Python:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"existingTab": {
|
|
119
|
+
"target": { "type": "selected", "host": "chatgpt" },
|
|
120
|
+
"ifMissing": "block"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Other supported targets are `{ "type": "url", "url": "https://chatgpt.com/c/..." }`, `{ "type": "conversationId", "conversationId": "..." }`, and `{ "type": "tabId", "tabId": "..." }`. Explicit existing-tab reuse blocks by default when no matching tab is open. `ifMissing: "open"` may open URL or conversation-id targets, but selected-tab and tab-id targets remain claim-only because there is no deterministic URL to create.
|
|
126
|
+
|
|
127
|
+
`backend.capabilities` is the source of truth for supported commands, transports, and stream modes. The current backend advertises:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"protocolVersion": "chatgpt.browser_control.backend_request.v1",
|
|
132
|
+
"transports": ["stdio"],
|
|
133
|
+
"streaming": {
|
|
134
|
+
"modes": ["ndjson"],
|
|
135
|
+
"tokenDeltas": false
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## HTTP/SSE Status
|
|
141
|
+
|
|
142
|
+
HTTP/SSE is deferred in this phase. Stdio NDJSON is the default long-lived local transport because it has no port allocation, no browser-origin surface, and no local network security prompt. It also covers the current streaming requirement through backend event lines.
|
|
143
|
+
|
|
144
|
+
Any future HTTP/SSE implementation must use the same request, response, event, capabilities, fixture, and conformance shapes. It should add `http` and `sse` capabilities only after transport-specific tests pass.
|
|
145
|
+
|
|
146
|
+
## Runtime Boundary
|
|
147
|
+
|
|
148
|
+
Python is a native SDK facade over the protocol. The current browser-control runtime is still Node-backed.
|
|
149
|
+
|
|
150
|
+
An ordinary shell can launch:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
node ../node/dist/codex-chatgpt-control-backend.mjs
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
That is enough to validate protocol shape, command dispatch, contracts, and blocker handling. It is not enough to guarantee live ChatGPT control, because a plain subprocess does not automatically inherit Codex's JavaScript `globalThis.agent` browser bridge.
|
|
157
|
+
|
|
158
|
+
For live browser control, the backend process must have access to a compatible browser bridge through one of the backend runtime options:
|
|
159
|
+
|
|
160
|
+
- Codex-hosted JavaScript runtime with `globalThis.agent`.
|
|
161
|
+
- Explicit `RuntimeEnv.browser` or `RuntimeEnv.page` in a future embedding.
|
|
162
|
+
- A future Python-native/native-host/CDP backend that implements this same protocol.
|
|
163
|
+
|
|
164
|
+
Important: in Codex, `globalThis.agent` is not present until the Chrome plugin runtime is bootstrapped. Do not diagnose bridge availability by checking `globalThis.agent` in an ordinary shell or before calling the Chrome plugin's `setupBrowserRuntime({ globals: globalThis })`.
|
|
165
|
+
|
|
166
|
+
The live Chrome bootstrap is:
|
|
167
|
+
|
|
168
|
+
```js
|
|
169
|
+
const { setupBrowserRuntime } = await import("/example/user/.codex/plugins/cache/openai-bundled/chrome/26.602.40724/scripts/browser-client.mjs");
|
|
170
|
+
await setupBrowserRuntime({ globals: globalThis });
|
|
171
|
+
globalThis.browser = await agent.browsers.get("extension");
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Ordinary-Shell Smoke
|
|
175
|
+
|
|
176
|
+
Run from `packages/node`:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm run bundle:backend
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Run from `packages/python`:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
python scripts/live_smoke.py --mode ordinary-shell
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
In an ordinary shell without Codex browser bridge access, browser-required commands must return a structured `browser_bridge_unavailable` blocker. This is a successful smoke result when the backend process stays alive and protocol calls such as `backend.health` and `commands` succeed.
|
|
189
|
+
|
|
190
|
+
## Browser-Bridge Smoke
|
|
191
|
+
|
|
192
|
+
Run only when intentionally operating a backend with live browser access:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
python scripts/live_smoke.py --mode browser-bridge
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Use `CHATGPT_BROWSER_BACKEND_COMMAND` or `--backend-command` when the bridge-enabled backend is not the default bundle:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
CHATGPT_BROWSER_BACKEND_COMMAND="node /absolute/path/to/bridge-enabled-backend.mjs" \
|
|
202
|
+
python scripts/live_smoke.py --mode browser-bridge
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
When the bridge-enabled backend is running inside an active Codex Chrome-plugin JS execution rather than a standalone process, run Python through the stdio relay:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
CHATGPT_BROWSER_BACKEND_HTTP_URL=http://127.0.0.1:<relay-port> \
|
|
209
|
+
python scripts/live_smoke.py \
|
|
210
|
+
--mode browser-bridge \
|
|
211
|
+
--backend-command "node scripts/http_stdio_relay.mjs"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The bridge-hosted JS execution must remain active for the duration of the Python smoke. The relay path is:
|
|
215
|
+
|
|
216
|
+
```text
|
|
217
|
+
Python SDK -> stdio relay -> bridge-hosted Node backend -> Codex Chrome bridge -> ChatGPT
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The smoke covers `runner.run`, `runner.run_streamed`, `responses.create`, named `run_plan`, and redacted `reports.create`. It writes redacted JSON summaries and does not persist raw prompt/response content by default.
|
|
221
|
+
|
|
222
|
+
## Contract Fixtures
|
|
223
|
+
|
|
224
|
+
Shared fixtures live under:
|
|
225
|
+
|
|
226
|
+
```text
|
|
227
|
+
contracts/v1/
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Required gates:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
npm run contract:validate
|
|
234
|
+
npm run parity:fixtures
|
|
235
|
+
npm run test:backend-conformance
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Python must also load and round-trip the same fixtures through Pydantic models. Any future backend implementation should pass these fixtures before claiming compatibility.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Python Parity
|
|
2
|
+
|
|
3
|
+
The Python package is a parity client over the TypeScript browser-control runtime. Keep browser automation owned by `packages/node/`; keep Python synchronized through the versioned wire contract in `contracts/v1/`.
|
|
4
|
+
|
|
5
|
+
## Contract
|
|
6
|
+
|
|
7
|
+
- Shared fixtures live in `contracts/v1/fixtures/`.
|
|
8
|
+
- `npm run contract:validate` validates every fixture against JSON Schema.
|
|
9
|
+
- `npm run parity:fixtures` enforces fixture shape, stream settlement, and wire-field casing.
|
|
10
|
+
- `npm run parity:suite` validates `contracts/v1/parity-suite.json`, which ties every public backend command and fixture to TypeScript tests, Python tests, docs, and deterministic CI gates.
|
|
11
|
+
- Python tests load the same manifest and round-trip every JSON fixture through Pydantic models.
|
|
12
|
+
|
|
13
|
+
Wire fields stay TypeScript-compatible. Python exposes idiomatic aliases:
|
|
14
|
+
|
|
15
|
+
| Wire | Python |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `finalOutput` | `final_output` |
|
|
18
|
+
| `newItems` | `new_items` |
|
|
19
|
+
| `activeAgentName` | `active_agent_name` |
|
|
20
|
+
| `lastAgentName` | `last_agent_name` |
|
|
21
|
+
| `nextStepId` | `next_step_id` |
|
|
22
|
+
|
|
23
|
+
## Sync Python
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from codex_chatgpt_control import ChatGPT, NodeSidecarTransport
|
|
27
|
+
|
|
28
|
+
chatgpt = ChatGPT(
|
|
29
|
+
transport=NodeSidecarTransport(
|
|
30
|
+
command=["node", "dist/codex-chatgpt-control-backend.mjs"]
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
agent = chatgpt.agent(name="reviewer", instructions="Review deeply.")
|
|
34
|
+
result = chatgpt.runner.run(agent, input="Reply with hi.")
|
|
35
|
+
|
|
36
|
+
print(result.output_text)
|
|
37
|
+
print(result.final_output)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Async Python
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from codex_chatgpt_control import AsyncChatGPT
|
|
44
|
+
|
|
45
|
+
chatgpt = AsyncChatGPT(transport=my_async_transport)
|
|
46
|
+
agent = chatgpt.agent(name="reviewer", instructions="Review deeply.")
|
|
47
|
+
result = await chatgpt.runner.run(agent, input="Reply with hi.")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Responses Fixture Shape
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from codex_chatgpt_control import ChatGPTResponse
|
|
54
|
+
|
|
55
|
+
response = ChatGPTResponse.from_wire(payload["response"])
|
|
56
|
+
unsupported = response.unsupported_fields
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Unsupported OpenAI API-only fields stay explicit in `browser_control.unsupported[]`; the Python adapter must not submit them silently.
|
|
60
|
+
|
|
61
|
+
## Streaming
|
|
62
|
+
|
|
63
|
+
`stream-*.ndjson` fixtures are milestone streams. They are not token streams. The final `completed` event contains a normal `ChatGPTRunResult` wire object, including blockers when the run cannot proceed.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from codex_chatgpt_control import ChatGPTStreamEvent
|
|
67
|
+
|
|
68
|
+
event = ChatGPTStreamEvent.from_wire(payload)
|
|
69
|
+
if event.type == "completed" and event.result is not None:
|
|
70
|
+
print(event.result.status)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Required Gates
|
|
74
|
+
|
|
75
|
+
Run from `packages/node`:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm run bundle:backend
|
|
79
|
+
npm run contract:validate
|
|
80
|
+
npm run parity:fixtures
|
|
81
|
+
npm run parity:suite
|
|
82
|
+
npm run test:backend-conformance
|
|
83
|
+
npm test -- tests/unit/contract-fixtures.test.ts
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Run from `packages/python`:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
python -m pip install -e ".[dev]"
|
|
90
|
+
python -m unittest discover -s tests
|
|
91
|
+
python -m compileall -q src
|
|
92
|
+
python -m pyright --pythonpath "$(which python)" src tests
|
|
93
|
+
python scripts/live_smoke.py --mode ordinary-shell
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Backend Runtime
|
|
97
|
+
|
|
98
|
+
Python is a native SDK facade over the local backend protocol. The initial browser-control runtime is still the TypeScript backend service:
|
|
99
|
+
|
|
100
|
+
- `dist/codex-chatgpt-control-backend.mjs` is the stdio backend bundle.
|
|
101
|
+
- `BackendClient` and `StdioBackendTransport` keep Python backend calls long-lived.
|
|
102
|
+
- `NodeSidecarTransport.run(...)` remains as a compatibility wrapper over backend `runner.run`.
|
|
103
|
+
- Ordinary-shell smoke passes when browser-required calls return structured `browser_bridge_unavailable`.
|
|
104
|
+
- Browser-bridge runtime smoke remains explicitly gated because it can operate a real ChatGPT session.
|
|
105
|
+
|
|
106
|
+
## Browser-Bridge Smoke
|
|
107
|
+
|
|
108
|
+
Run this only when you intentionally want Python to drive a live backend with browser access:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
python scripts/live_smoke.py --mode browser-bridge
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The command covers:
|
|
115
|
+
|
|
116
|
+
- `runner.run` new ask/read.
|
|
117
|
+
- `runner.run_streamed` milestone streaming.
|
|
118
|
+
- `responses.create` basic.
|
|
119
|
+
- `run_plan` named `new-ask-read`.
|
|
120
|
+
- `reports.create` redacted report output.
|
|
121
|
+
|
|
122
|
+
The default backend command is the Node stdio bundle:
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
../node/dist/codex-chatgpt-control-backend.mjs
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
That default is enough for protocol validation and structured blockers, but a plain subprocess cannot inherit Codex's JavaScript `globalThis.agent` browser bridge. For a true live browser pass, point Python at a stdio backend command that already runs in a bridge-enabled host:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
CHATGPT_BROWSER_BACKEND_COMMAND="node /absolute/path/to/bridge-enabled-backend.mjs" \
|
|
132
|
+
python scripts/live_smoke.py --mode browser-bridge
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Codex Chrome Plugin Relay
|
|
136
|
+
|
|
137
|
+
When the live backend is hosted inside the Codex Chrome plugin runtime, do not test bridge availability from a normal shell or an unbootstrapped Node REPL. First initialize the Chrome runtime:
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
const { setupBrowserRuntime } = await import("/example/user/.codex/plugins/cache/openai-bundled/chrome/26.602.40724/scripts/browser-client.mjs");
|
|
141
|
+
await setupBrowserRuntime({ globals: globalThis });
|
|
142
|
+
globalThis.browser = await agent.browsers.get("extension");
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Then run the backend server inside that active JS execution context and point Python at the stdio-to-HTTP relay:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
CHATGPT_BROWSER_BACKEND_HTTP_URL=http://127.0.0.1:<relay-port> \
|
|
149
|
+
python scripts/live_smoke.py \
|
|
150
|
+
--mode browser-bridge \
|
|
151
|
+
--backend-command "node scripts/http_stdio_relay.mjs"
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Keep the bridge-hosted JS execution active while Python runs. If that JS execution returns first, the browser client no longer has an active execution context and operations can fail with `node_repl exec context not found`.
|
|
155
|
+
|
|
156
|
+
This is the intended live test chain:
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
Python SDK -> scripts/http_stdio_relay.mjs -> bridge-hosted Node backend -> Codex Chrome bridge -> ChatGPT
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Smoke output is a redacted JSON summary. It reports output matches and lengths, not raw prompts or raw responses. Exit codes are:
|
|
163
|
+
|
|
164
|
+
| Code | Meaning |
|
|
165
|
+
| --- | --- |
|
|
166
|
+
| `0` | All browser-bridge scenarios passed. |
|
|
167
|
+
| `1` | At least one scenario failed unexpectedly. |
|
|
168
|
+
| `2` | Scenarios recorded documented blockers such as `browser_bridge_unavailable`, `login_required`, or `selector_drift`. |
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Responses Adapter
|
|
2
|
+
|
|
3
|
+
`chatgpt.responses.create()` is a narrow convenience wrapper around visible ChatGPT browser control. It returns a Responses-shaped object with `object: "chatgpt.browser.response"`, but it is not the OpenAI Responses API and does not support hidden model controls.
|
|
4
|
+
|
|
5
|
+
Accepted fields:
|
|
6
|
+
|
|
7
|
+
- `input`
|
|
8
|
+
- `thread`
|
|
9
|
+
- `attachments`
|
|
10
|
+
- `mode`
|
|
11
|
+
- `tools`
|
|
12
|
+
- `text.format`
|
|
13
|
+
- `stream: false`
|
|
14
|
+
- `report`
|
|
15
|
+
- `instructions` only with `instructionsMode: "visible_prefix"`
|
|
16
|
+
|
|
17
|
+
Rejected API-only fields return `status: "unsupported"` before any prompt is submitted. The response includes `browser_control.unsupported[]` with `path`, `reason`, and `alternative`.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const response = await chatgpt.responses.create({
|
|
21
|
+
input: "Summarize the latest plan.",
|
|
22
|
+
thread: { type: "conversationId", conversationId: "abc-123" },
|
|
23
|
+
text: { format: "markdown" },
|
|
24
|
+
stream: false
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Use `chatgpt.runner.run()` for lower-level browser-control workflows, multi-step command planning, attachments, downloads, reports, and explicit interruption handling.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Safety Rules
|
|
2
|
+
|
|
3
|
+
- Treat ChatGPT conversation content as private unless the user explicitly says otherwise.
|
|
4
|
+
- Do not save raw ChatGPT conversation content to memory.
|
|
5
|
+
- Do not send ChatGPT content to external model tools without explicit user approval.
|
|
6
|
+
- Do not create shared links automatically.
|
|
7
|
+
- Do not delete, archive, move, or share threads without exact confirmation.
|
|
8
|
+
- Do not connect or disconnect ChatGPT apps/connectors automatically.
|
|
9
|
+
- Do not change account settings, memory, custom instructions, data controls, or workspace state automatically.
|
|
10
|
+
- Do not read browser cookies, localStorage, sessionStorage, auth tokens, hidden headers, or private network request internals.
|
|
11
|
+
- Prefer DOM/UI automation and official APIs over private endpoint replay.
|
|
12
|
+
- Runner `instructions` are visible prompt text unless `instructionsMode: "metadata_only"` keeps them local. Do not imply hidden system-message semantics.
|
|
13
|
+
- `chatgpt.responses.create()` is not the OpenAI Responses API. Reject API-only model controls before submitting a prompt.
|
|
14
|
+
- Runner streaming is milestone-only. Do not claim token-level streaming or API stream event parity.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Runner Streaming
|
|
2
|
+
|
|
3
|
+
`chatgpt.runner.run(agent, input, { stream: true })` returns an async iterable of runner milestone events plus a `completed` promise.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
const stream = chatgpt.runner.run(agent, "Reply with hi", { stream: true });
|
|
7
|
+
|
|
8
|
+
for await (const event of stream) {
|
|
9
|
+
console.log(event.name, event.item.type);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const result = await stream.completed;
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This is not token streaming. Events are emitted for browser-control milestones such as `message_submitted`, `message_completed`, `file_attached`, and `run_blocked`. Do not expect token deltas or OpenAI API stream event parity.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
## `browser_bridge_unavailable`
|
|
4
|
+
|
|
5
|
+
The backend process does not have access to a browser bridge. This is expected when a live-smoke command runs from an ordinary shell: it proves the protocol stayed alive and surfaced a structured blocker.
|
|
6
|
+
|
|
7
|
+
The structured blocker should include `code: "codex_chrome_bridge_unavailable"` plus `blocker.remediation[]`. Agents should read those remediation steps before asking the user to restart Chrome or change permissions.
|
|
8
|
+
|
|
9
|
+
Do not conclude that Chrome or the extension is broken from a plain shell result, or from checking `globalThis.agent` before the Chrome plugin runtime is initialized. For a true Codex Chrome-plugin live run, bootstrap the runtime first:
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
const { setupBrowserRuntime } = await import("/example/user/.codex/plugins/cache/openai-bundled/chrome/26.602.40724/scripts/browser-client.mjs");
|
|
13
|
+
await setupBrowserRuntime({ globals: globalThis });
|
|
14
|
+
globalThis.browser = await agent.browsers.get("extension");
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
If the command was intentionally running in a bridge-enabled host and still returns this blocker, verify that the Codex Chrome extension is installed and enabled, then restart Chrome or Codex if the backend is still unavailable. Do not keep retrying the same attach path indefinitely.
|
|
18
|
+
|
|
19
|
+
For Python live bridge smokes, a plain Python-spawned Node subprocess does not inherit Codex's in-process bridge. Use the relay into an active bridge-hosted backend:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
CHATGPT_BROWSER_BACKEND_HTTP_URL=http://127.0.0.1:<relay-port> \
|
|
23
|
+
python scripts/live_smoke.py \
|
|
24
|
+
--mode browser-bridge \
|
|
25
|
+
--backend-command "node scripts/http_stdio_relay.mjs"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Keep the bridge-hosted JS execution active while Python runs. If that call returns first, browser operations can fail with `node_repl exec context not found`.
|
|
29
|
+
|
|
30
|
+
## `login_required`
|
|
31
|
+
|
|
32
|
+
The user needs to sign in to ChatGPT in Chrome. Stop and ask the user to complete login.
|
|
33
|
+
|
|
34
|
+
## `captcha`
|
|
35
|
+
|
|
36
|
+
Stop. Do not attempt bypass.
|
|
37
|
+
|
|
38
|
+
## `rate_limit`
|
|
39
|
+
|
|
40
|
+
Return the visible limit text and stop unless the user asks to wait or try a different path.
|
|
41
|
+
|
|
42
|
+
## `selector_drift`
|
|
43
|
+
|
|
44
|
+
The ChatGPT UI changed or the page loaded an unexpected surface. Return visible menu/button candidates and a screenshot/DOM summary if available.
|
|
45
|
+
|
|
46
|
+
Runner results surface this as `interruptions[0].type === "selector_drift"` with `blocker.candidates` when visible candidates were available. Do not retry automatically; ask the user or update selectors.
|
|
47
|
+
|
|
48
|
+
## File Upload Permission
|
|
49
|
+
|
|
50
|
+
File uploads have two separate permission gates:
|
|
51
|
+
|
|
52
|
+
1. Codex app settings for Chrome uploads must allow `chatgpt.com`, or uploads must be set to always allow.
|
|
53
|
+
2. Chrome's extension details page for the Codex extension may also need `Allow access to file URLs`.
|
|
54
|
+
|
|
55
|
+
If `fileChooser.setFiles` returns `Not allowed`, the ChatGPT chooser was reached but Chrome refused the local file handoff. Check both gates before retrying.
|
|
56
|
+
|
|
57
|
+
Agent-facing remediation text should name both settings:
|
|
58
|
+
|
|
59
|
+
> File upload is blocked by Chrome/Codex permissions. Ask the user to enable both: Codex Settings > Computer Use > Chrome > Permissions > Uploads, and Chrome chrome://extensions > Codex extension > Details > Allow access to file URLs. Then retry.
|
|
60
|
+
|
|
61
|
+
## Clipboard Unavailable
|
|
62
|
+
|
|
63
|
+
`response.copy` falls back to DOM text extraction when the macOS system clipboard does not change.
|
|
64
|
+
|
|
65
|
+
## Flattened Or Unreadable Response Capture
|
|
66
|
+
|
|
67
|
+
Use `readLatest({ format: "markdown" })`, `copyLatest()`, or the default SDK `read: true` path for human-readable responses. Use `format: "normalized_text"` only for exact-string assertions or polling. Check `data.source`, `data.fidelity`, and `data.warnings`: clipboard output is highest fidelity, while DOM Markdown is semantic reconstruction. If Markdown capture degrades, return the command warning and save the structured `blocks` or diagnostic `html` representation instead of silently writing flattened text as a Markdown report.
|
|
68
|
+
|
|
69
|
+
## Response Branch Ambiguity
|
|
70
|
+
|
|
71
|
+
When ChatGPT exposes previous/next response controls, `readLatest` and `copyLatest` include `branch.current`, `branch.total`, `actions`, and `thoughtDurationText` when visible. If the branch state is missing but the user expects a rerun or edited-message branch, reload the thread and read again before claiming to have captured the latest answer.
|
|
72
|
+
|
|
73
|
+
## Download Unavailable
|
|
74
|
+
|
|
75
|
+
The command only downloads visible files with a download affordance. If no download control exists, ask ChatGPT to create or expose the file again.
|
|
76
|
+
|
|
77
|
+
## Redacted Reports
|
|
78
|
+
|
|
79
|
+
`createReport`, `chatgpt.reports.*`, SDK workflow reports, and live-smoke reports redact raw prompt/response/file content by default. Use `includeContent: true` only when the user explicitly asks to persist raw content.
|
|
80
|
+
|
|
81
|
+
## Responses Adapter Unsupported Fields
|
|
82
|
+
|
|
83
|
+
`chatgpt.responses.create()` rejects OpenAI API-only fields such as `model`, `temperature`, `logprobs`, `previous_response_id`, `store`, and `max_output_tokens` before submitting a prompt. Inspect `browser_control.unsupported[]` for `path`, `reason`, and `alternative`.
|
|
84
|
+
|
|
85
|
+
## Runner Milestone Streaming
|
|
86
|
+
|
|
87
|
+
`chatgpt.runner.run(agent, input, { stream: true })` returns milestone events and `stream.completed`. It does not stream token deltas. If an agent expects API stream events, use `event.name` and `event.item.type` instead.
|
|
88
|
+
|
|
89
|
+
## Doctor Preflight
|
|
90
|
+
|
|
91
|
+
Run `doctor({ check: ["bridge", "login", "upload", "download", "clipboard"] })` before long workflows when the browser state or permissions are uncertain. Upload readiness may remain `unknown` until a live attach attempt, but the remediation should still name both upload permission gates.
|