166 lines
5.8 KiB
Python
166 lines
5.8 KiB
Python
import asyncio
|
|
from datetime import datetime
|
|
|
|
from agents import Agent, Runner, gen_trace_id, trace
|
|
|
|
# This tool is still in experimental phase and the details could be changed until being GAed.
|
|
from agents.extensions.experimental.codex import (
|
|
CodexToolStreamEvent,
|
|
CommandExecutionItem,
|
|
ErrorItem,
|
|
FileChangeItem,
|
|
ItemCompletedEvent,
|
|
ItemStartedEvent,
|
|
ItemUpdatedEvent,
|
|
McpToolCallItem,
|
|
ReasoningItem,
|
|
ThreadErrorEvent,
|
|
ThreadOptions,
|
|
ThreadStartedEvent,
|
|
TodoListItem,
|
|
TurnCompletedEvent,
|
|
TurnFailedEvent,
|
|
TurnOptions,
|
|
TurnStartedEvent,
|
|
WebSearchItem,
|
|
codex_tool,
|
|
)
|
|
|
|
|
|
# This example runs the Codex CLI via the Codex tool wrapper.
|
|
# You can configure the CLI path with CODEX_PATH or CodexOptions(codex_path_override="...").
|
|
# codex_tool accepts options as keyword arguments or a plain dict.
|
|
# For example: codex_tool(sandbox_mode="read-only") or codex_tool({"sandbox_mode": "read-only"}).
|
|
async def on_codex_stream(payload: CodexToolStreamEvent) -> None:
|
|
event = payload.event
|
|
|
|
if isinstance(event, ThreadStartedEvent):
|
|
log(f"codex thread started: {event.thread_id}")
|
|
return
|
|
if isinstance(event, TurnStartedEvent):
|
|
log("codex turn started")
|
|
return
|
|
if isinstance(event, TurnCompletedEvent):
|
|
usage = event.usage
|
|
log(f"codex turn completed, usage: {usage}")
|
|
return
|
|
if isinstance(event, TurnFailedEvent):
|
|
error = event.error.message
|
|
log(f"codex turn failed: {error}")
|
|
return
|
|
if isinstance(event, ThreadErrorEvent):
|
|
log(f"codex stream error: {event.message}")
|
|
return
|
|
|
|
if not isinstance(event, ItemStartedEvent | ItemUpdatedEvent | ItemCompletedEvent):
|
|
return
|
|
|
|
item = event.item
|
|
|
|
if isinstance(item, ReasoningItem):
|
|
text = item.text
|
|
log(f"codex reasoning ({event.type}): {text}")
|
|
return
|
|
if isinstance(item, CommandExecutionItem):
|
|
command = item.command
|
|
output = item.aggregated_output
|
|
output_preview = output[-200:] if isinstance(output, str) else ""
|
|
status = item.status
|
|
log(f"codex command {event.type}: {command} | status={status} | output={output_preview}")
|
|
return
|
|
if isinstance(item, McpToolCallItem):
|
|
server = item.server
|
|
tool = item.tool
|
|
status = item.status
|
|
log(f"codex mcp {event.type}: {server}.{tool} | status={status}")
|
|
return
|
|
if isinstance(item, FileChangeItem):
|
|
changes = item.changes
|
|
status = item.status
|
|
log(f"codex file change {event.type}: {status} | {changes}")
|
|
return
|
|
if isinstance(item, WebSearchItem):
|
|
log(f"codex web search {event.type}: {item.query}")
|
|
return
|
|
if isinstance(item, TodoListItem):
|
|
items = item.items
|
|
log(f"codex todo list {event.type}: {len(items)} items")
|
|
return
|
|
if isinstance(item, ErrorItem):
|
|
log(f"codex error {event.type}: {item.message}")
|
|
|
|
|
|
def _timestamp() -> str:
|
|
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
|
def log(message: str) -> None:
|
|
timestamp = _timestamp()
|
|
lines = str(message).splitlines() or [""]
|
|
for line in lines:
|
|
print(f"{timestamp} {line}")
|
|
|
|
|
|
async def main() -> None:
|
|
agent = Agent(
|
|
name="Codex Agent",
|
|
instructions=(
|
|
"Use the codex tool to inspect the workspace in read-only mode and answer the question. "
|
|
"When skill names, which usually starts with `$`, are mentioned, "
|
|
"you must rely on the codex tool to use the skill and answer the question.\n\n"
|
|
"When you send the final answer, you must include the following info at the end:\n\n"
|
|
"Run `codex resume <thread_id>` to continue the codex session."
|
|
),
|
|
tools=[
|
|
# Run local Codex CLI as a sub process
|
|
codex_tool(
|
|
sandbox_mode="read-only",
|
|
default_thread_options=ThreadOptions(
|
|
# You can pass a Codex instance to customize CLI details
|
|
# codex=Codex(executable_path="/path/to/codex", base_url="..."),
|
|
model="gpt-5.5",
|
|
model_reasoning_effort="low",
|
|
network_access_enabled=True,
|
|
web_search_enabled=False,
|
|
approval_policy="never", # We'll update this example once the HITL is implemented
|
|
),
|
|
default_turn_options=TurnOptions(
|
|
# Abort Codex CLI if no events arrive within this many seconds.
|
|
idle_timeout_seconds=60,
|
|
),
|
|
on_stream=on_codex_stream,
|
|
)
|
|
],
|
|
)
|
|
trace_id = gen_trace_id()
|
|
log(f"View trace: https://platform.openai.com/traces/trace?trace_id={trace_id}")
|
|
|
|
with trace("Codex tool example", trace_id=trace_id):
|
|
log("Using the Codex tool to inspect pyproject.toml and summarize Python requirements...")
|
|
result = await Runner.run(
|
|
agent,
|
|
(
|
|
"Inspect pyproject.toml in this repository and summarize the supported Python "
|
|
"version plus the main local test command. Do not modify any files."
|
|
),
|
|
)
|
|
log(result.final_output)
|
|
|
|
# Use local inspection in read-only mode.
|
|
log(
|
|
"Using the Codex tool to inspect AGENTS.md and summarize the local verification workflow..."
|
|
)
|
|
result = await Runner.run(
|
|
agent,
|
|
(
|
|
"Inspect AGENTS.md and summarize the mandatory local verification commands for this "
|
|
"repository. Do not modify any files or suggest code changes."
|
|
),
|
|
)
|
|
log(result.final_output)
|
|
# (A read-only summary of the local verification workflow will be displayed.)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|