codex-chatgpt-control 0.1.0a1__py3-none-any.whl

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.
@@ -0,0 +1,101 @@
1
+ from .async_client import (
2
+ AsyncChatGPT,
3
+ AsyncChatGPTRunner,
4
+ AsyncCommandClient,
5
+ AsyncPrimitiveGroup,
6
+ AsyncReportsClient,
7
+ AsyncResponsesClient,
8
+ AsyncRunResultStreaming,
9
+ AsyncWorkflowClient,
10
+ )
11
+ from .agent import Agent, AgentConfig
12
+ from .backend import (
13
+ BACKEND_EVENT_SCHEMA_VERSION,
14
+ BACKEND_REQUEST_SCHEMA_VERSION,
15
+ BACKEND_RESPONSE_SCHEMA_VERSION,
16
+ BackendClient,
17
+ BackendProtocolError,
18
+ BackendRequest,
19
+ BackendTransportError,
20
+ StdioBackendTransport,
21
+ )
22
+ from .client import ChatGPT, ChatGPTAgent, ChatGPTRunner
23
+ from .commands import CommandClient
24
+ from .models import (
25
+ BackendCapabilities,
26
+ BackendEvent,
27
+ BackendResponse,
28
+ ChatGPTAgentModel,
29
+ ChatGPTResponse,
30
+ ChatGPTRunInput,
31
+ ChatGPTRunResult,
32
+ ChatGPTRunState,
33
+ ChatGPTStreamEvent,
34
+ CommandDescriptor,
35
+ CommandResult,
36
+ DoctorReport,
37
+ RunReportData,
38
+ SequencePlan,
39
+ )
40
+ from .transport import NodeSidecarError, NodeSidecarTransport
41
+ from .runner import RunResult, RunResultStreaming, RunState, Runner
42
+ from .responses import ResponsesClient, ResponsesValidationResult
43
+ from .primitives import FilesClient, MessagesClient, ModesClient, ResponseClient, SessionClient, ThreadsClient, ToolsClient
44
+ from .reports import ReportsClient
45
+ from .workflows import WorkflowClient
46
+
47
+ __all__ = [
48
+ "Agent",
49
+ "AgentConfig",
50
+ "AsyncChatGPT",
51
+ "AsyncChatGPTRunner",
52
+ "AsyncCommandClient",
53
+ "AsyncPrimitiveGroup",
54
+ "AsyncReportsClient",
55
+ "AsyncResponsesClient",
56
+ "AsyncRunResultStreaming",
57
+ "AsyncWorkflowClient",
58
+ "BACKEND_EVENT_SCHEMA_VERSION",
59
+ "BACKEND_REQUEST_SCHEMA_VERSION",
60
+ "BACKEND_RESPONSE_SCHEMA_VERSION",
61
+ "BackendClient",
62
+ "BackendProtocolError",
63
+ "BackendRequest",
64
+ "BackendTransportError",
65
+ "BackendCapabilities",
66
+ "BackendEvent",
67
+ "BackendResponse",
68
+ "ChatGPT",
69
+ "ChatGPTAgentModel",
70
+ "ChatGPTAgent",
71
+ "ChatGPTRunner",
72
+ "ChatGPTRunInput",
73
+ "ChatGPTResponse",
74
+ "ChatGPTRunResult",
75
+ "ChatGPTRunState",
76
+ "ChatGPTStreamEvent",
77
+ "CommandClient",
78
+ "CommandDescriptor",
79
+ "CommandResult",
80
+ "DoctorReport",
81
+ "FilesClient",
82
+ "MessagesClient",
83
+ "ModesClient",
84
+ "NodeSidecarError",
85
+ "NodeSidecarTransport",
86
+ "ReportsClient",
87
+ "ResponseClient",
88
+ "RunReportData",
89
+ "RunResult",
90
+ "RunResultStreaming",
91
+ "RunState",
92
+ "Runner",
93
+ "ResponsesClient",
94
+ "ResponsesValidationResult",
95
+ "SessionClient",
96
+ "SequencePlan",
97
+ "StdioBackendTransport",
98
+ "ThreadsClient",
99
+ "ToolsClient",
100
+ "WorkflowClient",
101
+ ]
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, Literal
5
+
6
+
7
+ InstructionsMode = Literal["visible_prefix", "visible_setup_message", "metadata_only"]
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class Agent:
12
+ name: str
13
+ instructions: str | None = None
14
+ instructions_mode: InstructionsMode = "visible_prefix"
15
+ defaults: dict[str, Any] = field(default_factory=dict)
16
+ tools: list[dict[str, Any]] = field(default_factory=list)
17
+ guardrails: list[dict[str, Any]] = field(default_factory=list)
18
+ output: dict[str, Any] | None = None
19
+ metadata: dict[str, Any] | None = None
20
+
21
+ def __post_init__(self) -> None:
22
+ normalized = self.name.strip()
23
+ if not normalized:
24
+ raise ValueError("Agent name must not be empty.")
25
+ object.__setattr__(self, "name", normalized)
26
+
27
+ def to_wire(self) -> dict[str, Any]:
28
+ payload: dict[str, Any] = {
29
+ "kind": "chatgpt_browser_agent",
30
+ "name": self.name,
31
+ "instructionsMode": self.instructions_mode,
32
+ "defaults": self.defaults,
33
+ "tools": self.tools,
34
+ "guardrails": self.guardrails,
35
+ }
36
+ if self.instructions is not None:
37
+ payload["instructions"] = self.instructions
38
+ if self.output is not None:
39
+ payload["output"] = self.output
40
+ if self.metadata is not None:
41
+ payload["metadata"] = self.metadata
42
+ return payload
43
+
44
+
45
+ AgentConfig = Agent
@@ -0,0 +1,369 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ from collections.abc import Iterator
5
+ from dataclasses import dataclass
6
+ from datetime import datetime, timezone
7
+ from typing import Any, Protocol
8
+
9
+ from .agent import Agent
10
+ from .commands import wire_kwargs
11
+ from .models import BackendEvent, ChatGPTResponse, ChatGPTRunResult, CommandDescriptor, CommandResult, SequencePlan
12
+ from .responses import (
13
+ normalize_create_args,
14
+ response_from_run_result,
15
+ responses_create_args_to_run_input,
16
+ unsupported_response,
17
+ validate_responses_create_args,
18
+ )
19
+
20
+
21
+ class AsyncBackendProtocol(Protocol):
22
+ def request(self, command: str, payload: dict[str, Any] | None = None) -> Any:
23
+ ...
24
+
25
+
26
+ class AsyncRunTransport(Protocol):
27
+ async def run(self, payload: dict[str, Any]) -> dict[str, Any]:
28
+ """Legacy async sidecar run shape kept as a compatibility fallback."""
29
+ ...
30
+
31
+
32
+ async def maybe_await(value: Any) -> Any:
33
+ if inspect.isawaitable(value):
34
+ return await value
35
+ return value
36
+
37
+
38
+ async def async_request_backend(backend: Any, command: str, payload: dict[str, Any] | None = None) -> Any:
39
+ request = getattr(backend, "request", None)
40
+ if not callable(request):
41
+ raise RuntimeError(f"This ChatGPT backend does not support {command}.")
42
+ return await maybe_await(request(command, payload or {}))
43
+
44
+
45
+ def command_result_from_wire(value: Any, command: str) -> CommandResult:
46
+ if not isinstance(value, dict):
47
+ raise RuntimeError(f"{command} backend result must be a CommandResult object.")
48
+ return CommandResult.from_wire(value)
49
+
50
+
51
+ class AsyncChatGPTRunner:
52
+ def __init__(self, backend: Any) -> None:
53
+ self._backend = backend
54
+
55
+ async def run(self, agent: Agent, input: Any) -> ChatGPTRunResult:
56
+ runner_run = getattr(self._backend, "runner_run", None)
57
+ if callable(runner_run):
58
+ result = await maybe_await(runner_run(agent.to_wire(), input))
59
+ if not isinstance(result, dict):
60
+ raise RuntimeError("runner.run backend result must be a JSON object.")
61
+ return ChatGPTRunResult.from_wire(result)
62
+
63
+ request = getattr(self._backend, "request", None)
64
+ if callable(request):
65
+ result = await async_request_backend(self._backend, "runner.run", {"agent": agent.to_wire(), "input": input})
66
+ if not isinstance(result, dict):
67
+ raise RuntimeError("runner.run backend result must be a JSON object.")
68
+ return ChatGPTRunResult.from_wire(result)
69
+
70
+ legacy_run = getattr(self._backend, "run", None)
71
+ if not callable(legacy_run):
72
+ raise RuntimeError("This ChatGPT backend does not support runner.run.")
73
+ payload = {
74
+ "schemaVersion": "chatgpt.browser_control.run.v1",
75
+ "agent": agent.to_wire(),
76
+ "input": input,
77
+ }
78
+ return ChatGPTRunResult.from_wire(await maybe_await(legacy_run(payload)))
79
+
80
+ async def plan(self, agent: Agent, input: Any) -> SequencePlan:
81
+ runner_plan = getattr(self._backend, "runner_plan", None)
82
+ if callable(runner_plan):
83
+ result = await maybe_await(runner_plan(agent.to_wire(), input))
84
+ else:
85
+ result = await async_request_backend(self._backend, "runner.plan", {"agent": agent.to_wire(), "input": input})
86
+ if not isinstance(result, dict):
87
+ raise RuntimeError("runner.plan backend result must be a JSON object.")
88
+ return SequencePlan.from_wire(result)
89
+
90
+ def run_streamed(self, agent: Agent, input: Any) -> "AsyncRunResultStreaming":
91
+ runner_stream = getattr(self._backend, "runner_stream", None)
92
+ if callable(runner_stream):
93
+ events = runner_stream(agent.to_wire(), input)
94
+ else:
95
+ stream = getattr(self._backend, "stream", None)
96
+ if not callable(stream):
97
+ raise RuntimeError("This ChatGPT backend does not support runner.stream.")
98
+ events = stream("runner.stream", {"agent": agent.to_wire(), "input": input})
99
+ return AsyncRunResultStreaming(events)
100
+
101
+
102
+ @dataclass
103
+ class AsyncRunResultStreaming:
104
+ _events: Any
105
+ final_result: ChatGPTRunResult | None = None
106
+ _iterator: Iterator[Any] | None = None
107
+
108
+ def __aiter__(self) -> "AsyncRunResultStreaming":
109
+ return self
110
+
111
+ async def __anext__(self) -> BackendEvent:
112
+ if hasattr(self._events, "__anext__"):
113
+ raw = await self._events.__anext__()
114
+ else:
115
+ if self._iterator is None:
116
+ self._iterator = iter(self._events)
117
+ try:
118
+ raw = next(self._iterator)
119
+ except StopIteration as exc:
120
+ raise StopAsyncIteration from exc
121
+ event = BackendEvent.from_wire(raw)
122
+ if event.type == "completed" and isinstance(event.result, ChatGPTRunResult):
123
+ self.final_result = event.result
124
+ return event
125
+
126
+
127
+ class AsyncResponsesClient:
128
+ def __init__(self, backend: Any) -> None:
129
+ self._backend = backend
130
+
131
+ async def create(self, args: dict[str, Any] | None = None, **kwargs: Any) -> ChatGPTResponse:
132
+ payload = normalize_create_args({**(args or {}), **kwargs})
133
+ validation = validate_responses_create_args(payload)
134
+ now = datetime.now(timezone.utc)
135
+ if not validation.ok:
136
+ return unsupported_response(validation.unsupported, now)
137
+
138
+ request = getattr(self._backend, "request", None)
139
+ if callable(request):
140
+ result = await async_request_backend(self._backend, "responses.create", payload)
141
+ if not isinstance(result, dict):
142
+ raise RuntimeError("responses.create backend result must be a JSON object.")
143
+ return ChatGPTResponse.from_wire(result)
144
+
145
+ responses_create = getattr(self._backend, "responses_create", None)
146
+ if callable(responses_create):
147
+ result = await maybe_await(responses_create(payload))
148
+ if not isinstance(result, dict):
149
+ raise RuntimeError("responses.create backend result must be a JSON object.")
150
+ return ChatGPTResponse.from_wire(result)
151
+
152
+ legacy_run = getattr(self._backend, "run", None)
153
+ if not callable(legacy_run):
154
+ raise RuntimeError("This ChatGPT backend does not support responses.create.")
155
+ agent = Agent(
156
+ name="responses-adapter",
157
+ instructions=payload.get("instructions") if isinstance(payload.get("instructions"), str) else None,
158
+ instructions_mode=payload.get("instructionsMode", "visible_prefix"), # type: ignore[arg-type]
159
+ )
160
+ result = await maybe_await(legacy_run({
161
+ "schemaVersion": "chatgpt.browser_control.run.v1",
162
+ "agent": agent.to_wire(),
163
+ "input": responses_create_args_to_run_input(payload),
164
+ }))
165
+ return response_from_run_result(ChatGPTRunResult.from_wire(result), now)
166
+
167
+
168
+ class AsyncWorkflowClient:
169
+ def __init__(self, backend: Any) -> None:
170
+ self._backend = backend
171
+
172
+ async def ask(self, **kwargs: Any) -> CommandResult:
173
+ return command_result_from_wire(await async_request_backend(self._backend, "ask", wire_kwargs(**kwargs)), "ask")
174
+
175
+ async def ask_in_thread(self, **kwargs: Any) -> CommandResult:
176
+ return command_result_from_wire(await async_request_backend(self._backend, "askInThread", wire_kwargs(**kwargs)), "askInThread")
177
+
178
+ async def ask_with_files(self, **kwargs: Any) -> CommandResult:
179
+ return command_result_from_wire(await async_request_backend(self._backend, "askWithFiles", wire_kwargs(**kwargs)), "askWithFiles")
180
+
181
+ async def ask_and_download(self, **kwargs: Any) -> CommandResult:
182
+ return command_result_from_wire(await async_request_backend(self._backend, "askAndDownload", wire_kwargs(**kwargs)), "askAndDownload")
183
+
184
+ async def run_messages(self, **kwargs: Any) -> CommandResult:
185
+ return command_result_from_wire(await async_request_backend(self._backend, "runMessages", wire_kwargs(**kwargs)), "runMessages")
186
+
187
+ async def open_thread(self, thread: dict[str, Any]) -> CommandResult:
188
+ return command_result_from_wire(await async_request_backend(self._backend, "openThread", thread), "openThread")
189
+
190
+ async def read_latest(self, **kwargs: Any) -> CommandResult:
191
+ return command_result_from_wire(await async_request_backend(self._backend, "readLatest", wire_kwargs(**kwargs)), "readLatest")
192
+
193
+ async def copy_latest(self, **kwargs: Any) -> CommandResult:
194
+ return command_result_from_wire(await async_request_backend(self._backend, "copyLatest", wire_kwargs(**kwargs)), "copyLatest")
195
+
196
+ async def download_latest(self, **kwargs: Any) -> CommandResult:
197
+ return command_result_from_wire(await async_request_backend(self._backend, "downloadLatest", wire_kwargs(**kwargs)), "downloadLatest")
198
+
199
+ async def run_plan(self, plan: dict[str, Any]) -> CommandResult:
200
+ return command_result_from_wire(await async_request_backend(self._backend, "runPlan", plan), "runPlan")
201
+
202
+ async def doctor(self, **kwargs: Any) -> CommandResult:
203
+ return command_result_from_wire(await async_request_backend(self._backend, "doctor", wire_kwargs(**kwargs)), "doctor")
204
+
205
+ async def create_report(self, result: dict[str, Any], **kwargs: Any) -> CommandResult:
206
+ payload: dict[str, Any] = {"result": result}
207
+ args = wire_kwargs(**kwargs)
208
+ if args:
209
+ payload["args"] = args
210
+ return command_result_from_wire(await async_request_backend(self._backend, "createReport", payload), "createReport")
211
+
212
+
213
+ class AsyncCommandClient:
214
+ def __init__(self, backend: Any) -> None:
215
+ self._backend = backend
216
+
217
+ async def commands(self, *, layer: str | None = None) -> list[CommandDescriptor]:
218
+ payload: dict[str, Any] = {}
219
+ if layer is not None:
220
+ payload["filter"] = {"layer": layer}
221
+ result = await async_request_backend(self._backend, "commands", payload)
222
+ if not isinstance(result, list):
223
+ raise RuntimeError("commands backend result must be a list.")
224
+ return [CommandDescriptor.from_wire(item) for item in result]
225
+
226
+ async def describe(self, name: str) -> CommandDescriptor:
227
+ result = await async_request_backend(self._backend, "describe", {"name": name})
228
+ if not isinstance(result, dict):
229
+ raise RuntimeError("describe backend result must be a command descriptor.")
230
+ return CommandDescriptor.from_wire(result)
231
+
232
+ async def help(self, topic: str | None = None) -> str:
233
+ payload = {} if topic is None else {"topic": topic}
234
+ result = await async_request_backend(self._backend, "help", payload)
235
+ if not isinstance(result, str):
236
+ raise RuntimeError("help backend result must be a string.")
237
+ return result
238
+
239
+
240
+ class AsyncReportsClient:
241
+ def __init__(self, backend: Any) -> None:
242
+ self._backend = backend
243
+
244
+ async def create(self, result: dict[str, Any], **kwargs: Any) -> CommandResult:
245
+ return await async_report_command(self._backend, "reports.create", {"result": result}, **kwargs)
246
+
247
+ async def redact(self, value: Any, **kwargs: Any) -> CommandResult:
248
+ return await async_report_command(self._backend, "reports.redact", {"value": value}, **kwargs)
249
+
250
+ async def summarize(self, result: dict[str, Any], **kwargs: Any) -> CommandResult:
251
+ return await async_report_command(self._backend, "reports.summarize", {"result": result}, **kwargs)
252
+
253
+
254
+ async def async_report_command(backend: Any, command: str, payload: dict[str, Any], **kwargs: Any) -> CommandResult:
255
+ args = wire_kwargs(**kwargs)
256
+ if args:
257
+ payload["args"] = args
258
+ return command_result_from_wire(await async_request_backend(backend, command, payload), command)
259
+
260
+
261
+ class AsyncPrimitiveGroup:
262
+ def __init__(self, backend: Any, commands: dict[str, str]) -> None:
263
+ self._backend = backend
264
+ self._commands = commands
265
+
266
+ def __getattr__(self, name: str):
267
+ command = self._commands.get(name)
268
+ if command is None:
269
+ raise AttributeError(name)
270
+
271
+ async def call(**kwargs: Any) -> CommandResult:
272
+ return command_result_from_wire(await async_request_backend(self._backend, command, wire_kwargs(**kwargs)), command)
273
+
274
+ return call
275
+
276
+
277
+ class AsyncChatGPT:
278
+ def __init__(self, transport: Any) -> None:
279
+ self._backend = transport
280
+ self.responses = AsyncResponsesClient(transport)
281
+ self.runner = AsyncChatGPTRunner(transport)
282
+ self._workflows = AsyncWorkflowClient(transport)
283
+ self._commands = AsyncCommandClient(transport)
284
+ self.session = AsyncPrimitiveGroup(transport, {"bootstrap": "session.bootstrap"})
285
+ self.threads = AsyncPrimitiveGroup(transport, {"new": "threads.new", "search": "threads.search", "open": "threads.open"})
286
+ self.messages = AsyncPrimitiveGroup(transport, {
287
+ "compose": "messages.compose",
288
+ "submit": "messages.submit",
289
+ "ask": "messages.ask",
290
+ "wait": "messages.wait",
291
+ "read_latest": "messages.readLatest",
292
+ "wait_and_read": "messages.waitAndRead",
293
+ })
294
+ self.files = AsyncPrimitiveGroup(transport, {"attach": "files.attach", "download_latest": "files.downloadLatest"})
295
+ self.modes = AsyncPrimitiveGroup(transport, {"set": "modes.set"})
296
+ self.tools = AsyncPrimitiveGroup(transport, {"select": "tools.select"})
297
+ self.response = AsyncPrimitiveGroup(transport, {"copy": "response.copy"})
298
+ self.reports = AsyncReportsClient(transport)
299
+
300
+ async def run(self, agent: Agent, input: Any) -> ChatGPTRunResult:
301
+ return await self.runner.run(agent, input)
302
+
303
+ async def ask(self, **kwargs: Any) -> CommandResult:
304
+ return await self._workflows.ask(**kwargs)
305
+
306
+ async def ask_in_thread(self, **kwargs: Any) -> CommandResult:
307
+ return await self._workflows.ask_in_thread(**kwargs)
308
+
309
+ async def ask_with_files(self, **kwargs: Any) -> CommandResult:
310
+ return await self._workflows.ask_with_files(**kwargs)
311
+
312
+ async def ask_and_download(self, **kwargs: Any) -> CommandResult:
313
+ return await self._workflows.ask_and_download(**kwargs)
314
+
315
+ async def run_messages(self, **kwargs: Any) -> CommandResult:
316
+ return await self._workflows.run_messages(**kwargs)
317
+
318
+ async def open_thread(self, thread: dict[str, Any]) -> CommandResult:
319
+ return await self._workflows.open_thread(thread)
320
+
321
+ async def read_latest(self, **kwargs: Any) -> CommandResult:
322
+ return await self._workflows.read_latest(**kwargs)
323
+
324
+ async def copy_latest(self, **kwargs: Any) -> CommandResult:
325
+ return await self._workflows.copy_latest(**kwargs)
326
+
327
+ async def download_latest(self, **kwargs: Any) -> CommandResult:
328
+ return await self._workflows.download_latest(**kwargs)
329
+
330
+ async def run_plan(self, plan: dict[str, Any]) -> CommandResult:
331
+ return await self._workflows.run_plan(plan)
332
+
333
+ async def doctor(self, **kwargs: Any) -> CommandResult:
334
+ return await self._workflows.doctor(**kwargs)
335
+
336
+ async def create_report(self, result: dict[str, Any], **kwargs: Any) -> CommandResult:
337
+ return await self._workflows.create_report(result, **kwargs)
338
+
339
+ async def commands(self, *, layer: str | None = None) -> list[CommandDescriptor]:
340
+ return await self._commands.commands(layer=layer)
341
+
342
+ async def describe(self, name: str) -> CommandDescriptor:
343
+ return await self._commands.describe(name)
344
+
345
+ async def help(self, topic: str | None = None) -> str:
346
+ return await self._commands.help(topic)
347
+
348
+ def agent(
349
+ self,
350
+ *,
351
+ name: str,
352
+ instructions: str | None = None,
353
+ instructions_mode: str = "visible_prefix",
354
+ defaults: dict[str, Any] | None = None,
355
+ tools: list[dict[str, Any]] | None = None,
356
+ guardrails: list[dict[str, Any]] | None = None,
357
+ output: dict[str, Any] | None = None,
358
+ metadata: dict[str, Any] | None = None,
359
+ ) -> Agent:
360
+ return Agent(
361
+ name=name,
362
+ instructions=instructions,
363
+ instructions_mode=instructions_mode, # type: ignore[arg-type]
364
+ defaults=defaults or {},
365
+ tools=tools or [],
366
+ guardrails=guardrails or [],
367
+ output=output,
368
+ metadata=metadata,
369
+ )