Accept string input for Python turns

This commit is contained in:
Ahmed Ibrahim
2026-05-17 07:03:40 -07:00
parent b2becbfa87
commit 7a7fdbf22f
31 changed files with 209 additions and 140 deletions

View File

@@ -11,9 +11,20 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 1,
"id": "1b6614a5",
"metadata": {},
"outputs": [],
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Kernel: /Users/aibrahim/code/codex/.venv/bin/python\n",
"SDK source: /Users/aibrahim/code/codex/sdk/python/src\n",
"Runtime package: 0.131.0a4\n"
]
}
],
"source": [
"# Cell 1: bootstrap local SDK imports + pinned runtime package\n",
"import os\n",
@@ -96,7 +107,8 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 2,
"id": "137a6d64",
"metadata": {},
"outputs": [],
"source": [
@@ -115,53 +127,72 @@
{
"cell_type": "code",
"execution_count": null,
"id": "5fae892d",
"metadata": {},
"outputs": [],
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Please complete login at: https://auth.openai.com/oauth/authorize?response_type=code&client_id=app_EMoamEEZ73f0CkXaXp7hrann&redirect_uri=http%3A%2F%2Flocalhost%3A1455%2Fauth%2Fcallback&scope=openid%20profile%20email%20offline_access%20api.connectors.read%20api.connectors.invoke&code_challenge=Yl9Dc0ExoOBhzb_EhaEoHYEvUDVmqxbsTDG8S6Svj9E&code_challenge_method=S256&id_token_add_organizations=true&codex_cli_simplified_flow=true&state=BGkpiZDe6h7RymsrDCIgdr9f4cPPWNxVpQvo0owT9pg&originator=codex_python_sdk\n",
"login.id: 7f768161-e216-49a2-906c-587853917e75\n",
"login.auth_url: https://auth.openai.com/oauth/authorize?response_type=code&client_id=app_EMoamEEZ73f0CkXaXp7hrann&redirect_uri=http%3A%2F%2Flocalhost%3A1455%2Fauth%2Fcallback&scope=openid%20profile%20email%20offline_access%20api.connectors.read%20api.connectors.invoke&code_challenge=Yl9Dc0ExoOBhzb_EhaEoHYEvUDVmqxbsTDG8S6Svj9E&code_challenge_method=S256&id_token_add_organizations=true&codex_cli_simplified_flow=true&state=BGkpiZDe6h7RymsrDCIgdr9f4cPPWNxVpQvo0owT9pg&originator=codex_python_sdk\n",
"login.completed.success: True\n",
"account: account=Account(root=ChatgptAccount(email='aibrahim@openai.com', plan_type=<PlanType.business: 'business'>, type='chatgpt')) requires_openai_auth=True\n"
]
}
],
"source": [
"# Cell 2b: browser login handle lifecycle\n",
"with Codex() as codex:\n",
" # Open this URL and call `wait()` without canceling when completing login for real.\n",
" login = codex.login_chatgpt()\n",
" canceled = login.cancel()\n",
" print('Please complete login at:', login.auth_url)\n",
" completed = login.wait()\n",
" account = codex.account()\n",
"\n",
" print('login.id:', login.login_id)\n",
" print('login.auth_url:', login.auth_url)\n",
" print('login.cancel.status:', canceled.status)\n",
" print('login.completed.success:', completed.success)\n",
" print('account.requires_openai_auth:', account.requires_openai_auth)\n"
" print('account:', account.email)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 12,
"id": "ebdc04d9",
"metadata": {},
"outputs": [],
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"- Gradient descent is a method for minimizing a function by repeatedly moving parameters a small step in the direction that most decreases the loss.\n",
"- It uses the gradient, which tells you the slope of the loss surface, so each update is typically `new = old - learning_rate * gradient`.\n",
"- Over many steps, it usually moves toward a local minimum, with speed and stability depending a lot on the learning rate.\n"
]
}
],
"source": [
"# Cell 3: simple sync conversation\n",
"with Codex() as codex:\n",
" thread = codex.thread_start(model='gpt-5.4', config={'model_reasoning_effort': 'high'})\n",
" turn = thread.turn(TextInput('Explain gradient descent in 3 bullets.'))\n",
" result = turn.run()\n",
" print('server:', server_label(codex.metadata))\n",
" print('status:', result.status)\n",
" print(result.final_response)\n",
" print('items:', len(result.items))\n"
" result = thread.run('Explain gradient descent in 3 bullets.')\n",
" print(result.final_response)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bb4abb96",
"metadata": {},
"outputs": [],
"source": [
"# Cell 4: multi-turn continuity in same thread\n",
"with Codex() as codex:\n",
" thread = codex.thread_start(model='gpt-5.4', config={'model_reasoning_effort': 'high'})\n",
"\n",
" first = thread.turn(TextInput('Give a short summary of transformers.')).run()\n",
" second = thread.turn(TextInput('Now explain that to a high-school student.')).run()\n",
" first = thread.turn('Give a short summary of transformers.').run()\n",
" second = thread.turn('Now explain that to a high-school student.').run()\n",
" print('first status:', first.status)\n",
" print('second status:', second.status)\n",
" print('second text:', second.final_response)\n"
@@ -170,14 +201,15 @@
{
"cell_type": "code",
"execution_count": null,
"id": "8b0c80fd",
"metadata": {},
"outputs": [],
"source": [
"# Cell 5: full thread lifecycle and branching (sync)\n",
"with Codex() as codex:\n",
" thread = codex.thread_start(model='gpt-5.4', config={'model_reasoning_effort': 'high'})\n",
" first = thread.turn(TextInput('One sentence about structured planning.')).run()\n",
" second = thread.turn(TextInput('Now restate it for a junior engineer.')).run()\n",
" first = thread.turn('One sentence about structured planning.').run()\n",
" second = thread.turn('Now restate it for a junior engineer.').run()\n",
"\n",
" reopened = codex.thread_resume(thread.id)\n",
" listing_active = codex.thread_list(limit=20, archived=False)\n",
@@ -193,10 +225,10 @@
" model='gpt-5.4',\n",
" config={'model_reasoning_effort': 'high'},\n",
" )\n",
" resumed_result = resumed.turn(TextInput('Continue in one short sentence.')).run()\n",
" resumed_result = resumed.turn('Continue in one short sentence.').run()\n",
"\n",
" forked = codex.thread_fork(unarchived.id, model='gpt-5.4')\n",
" forked_result = forked.turn(TextInput('Take a different angle in one short sentence.')).run()\n",
" forked_result = forked.turn('Take a different angle in one short sentence.').run()\n",
"\n",
" compact_result = unarchived.compact()\n",
"\n",
@@ -214,6 +246,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "310db8c0",
"metadata": {},
"outputs": [],
"source": [
@@ -242,7 +275,7 @@
"with Codex() as codex:\n",
" thread = codex.thread_start(model='gpt-5.4', config={'model_reasoning_effort': 'high'})\n",
" turn = thread.turn(\n",
" TextInput('Propose a safe production feature-flag rollout. Return JSON matching the schema.'),\n",
" 'Propose a safe production feature-flag rollout. Return JSON matching the schema.',\n",
" cwd=str(Path.cwd()),\n",
" effort=ReasoningEffort.medium,\n",
" model='gpt-5.4',\n",
@@ -259,6 +292,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "7a33c97d",
"metadata": {},
"outputs": [],
"source": [
@@ -321,7 +355,7 @@
" thread = codex.thread_start(model=selected_model.model, config={'model_reasoning_effort': selected_effort.value})\n",
"\n",
" first = thread.turn(\n",
" TextInput('Give one short sentence about reliable production releases.'),\n",
" 'Give one short sentence about reliable production releases.',\n",
" model=selected_model.model,\n",
" effort=selected_effort,\n",
" ).run()\n",
@@ -329,7 +363,7 @@
" print('items:', len(first.items))\n",
"\n",
" second = thread.turn(\n",
" TextInput('Return JSON for a safe feature-flag rollout plan.'),\n",
" 'Return JSON for a safe feature-flag rollout plan.',\n",
" cwd=str(Path.cwd()),\n",
" effort=selected_effort,\n",
" model=selected_model.model,\n",
@@ -345,6 +379,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "e9aef26a",
"metadata": {},
"outputs": [],
"source": [
@@ -364,6 +399,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "a0cecc6c",
"metadata": {},
"outputs": [],
"source": [
@@ -382,6 +418,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "91afa2b8",
"metadata": {},
"outputs": [],
"source": [
@@ -390,7 +427,7 @@
" thread = codex.thread_start(model='gpt-5.4', config={'model_reasoning_effort': 'high'})\n",
"\n",
" result = retry_on_overload(\n",
" lambda: thread.turn(TextInput('List 5 failure modes in distributed systems.')).run(),\n",
" lambda: thread.turn('List 5 failure modes in distributed systems.').run(),\n",
" max_attempts=3,\n",
" initial_delay_s=0.25,\n",
" max_delay_s=2.0,\n",
@@ -402,6 +439,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "103be934",
"metadata": {},
"outputs": [],
"source": [
@@ -412,8 +450,8 @@
"async def async_lifecycle_demo():\n",
" async with AsyncCodex() as codex:\n",
" thread = await codex.thread_start(model='gpt-5.4', config={'model_reasoning_effort': 'high'})\n",
" first = await (await thread.turn(TextInput('One sentence about structured planning.'))).run()\n",
" second = await (await thread.turn(TextInput('Now restate it for a junior engineer.'))).run()\n",
" first = await (await thread.turn('One sentence about structured planning.')).run()\n",
" second = await (await thread.turn('Now restate it for a junior engineer.')).run()\n",
"\n",
" reopened = await codex.thread_resume(thread.id)\n",
" listing_active = await codex.thread_list(limit=20, archived=False)\n",
@@ -429,10 +467,10 @@
" model='gpt-5.4',\n",
" config={'model_reasoning_effort': 'high'},\n",
" )\n",
" resumed_result = await (await resumed.turn(TextInput('Continue in one short sentence.'))).run()\n",
" resumed_result = await (await resumed.turn('Continue in one short sentence.')).run()\n",
"\n",
" forked = await codex.thread_fork(unarchived.id, model='gpt-5.4')\n",
" forked_result = await (await forked.turn(TextInput('Take a different angle in one short sentence.'))).run()\n",
" forked_result = await (await forked.turn('Take a different angle in one short sentence.')).run()\n",
"\n",
" compact_result = await unarchived.compact()\n",
"\n",
@@ -453,6 +491,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "365aa10c",
"metadata": {},
"outputs": [],
"source": [
@@ -463,9 +502,9 @@
"async def async_stream_demo():\n",
" async with AsyncCodex() as codex:\n",
" thread = await codex.thread_start(model='gpt-5.4', config={'model_reasoning_effort': 'high'})\n",
" steer_turn = await thread.turn(TextInput('Count from 1 to 40 with commas, then one summary sentence.'))\n",
" steer_turn = await thread.turn('Count from 1 to 40 with commas, then one summary sentence.')\n",
"\n",
" steer_result = await steer_turn.steer(TextInput('Keep it brief and stop after 10 numbers.'))\n",
" steer_result = await steer_turn.steer('Keep it brief and stop after 10 numbers.')\n",
"\n",
" steer_event_count = 0\n",
" steer_completed_status = None\n",
@@ -482,7 +521,7 @@
" raise RuntimeError('stream ended without turn/completed')\n",
" steer_preview = ''.join(steer_deltas).strip()\n",
"\n",
" interrupt_turn = await thread.turn(TextInput('Count from 1 to 200 with commas, then one summary sentence.'))\n",
" interrupt_turn = await thread.turn('Count from 1 to 200 with commas, then one summary sentence.')\n",
" interrupt_result = await interrupt_turn.interrupt()\n",
"\n",
" interrupt_event_count = 0\n",
@@ -516,13 +555,21 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"version": "3.10+"
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.14.3"
}
},
"nbformat": 4,