Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions examples/tools/container_shell_inline_skill.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import argparse
import asyncio
import base64
from pathlib import Path
from tempfile import TemporaryDirectory
from zipfile import ZIP_DEFLATED, ZipFile

from openai.types.responses import ResponseFunctionShellToolCall
from openai.types.responses.response_container_reference import ResponseContainerReference

from agents import Agent, Runner, ShellTool, ShellToolInlineSkill, trace
from agents.items import ModelResponse

SKILL_NAME = "csv-workbench"
SKILL_DIR = Path(__file__).resolve().parent / "skills" / SKILL_NAME


def build_skill_zip_bundle() -> bytes:
with TemporaryDirectory(prefix="agents-inline-skill-") as temp_dir:
zip_path = Path(temp_dir) / f"{SKILL_NAME}.zip"
with ZipFile(zip_path, "w", compression=ZIP_DEFLATED) as archive:
for path in sorted(SKILL_DIR.rglob("*")):
if path.is_file():
archive.write(path, f"{SKILL_NAME}/{path.relative_to(SKILL_DIR)}")
return zip_path.read_bytes()


def build_inline_skill() -> ShellToolInlineSkill:
bundle = build_skill_zip_bundle()
return {
"type": "inline",
"name": SKILL_NAME,
"description": "Analyze CSV files in /mnt/data and return concise numeric summaries.",
"source": {
"type": "base64",
"media_type": "application/zip",
"data": base64.b64encode(bundle).decode("ascii"),
},
}


def extract_container_id(raw_responses: list[ModelResponse]) -> str | None:
for response in raw_responses:
for item in response.output:
if isinstance(item, ResponseFunctionShellToolCall) and isinstance(
item.environment, ResponseContainerReference
):
return item.environment.container_id

return None


async def main(model: str) -> None:
inline_skill = build_inline_skill()

with trace("container_shell_inline_skill_example"):
agent1 = Agent(
name="Container Shell Agent (Inline Skill)",
model=model,
instructions="Use the available container skill to answer user requests.",
tools=[
ShellTool(
environment={
"type": "container_auto",
"network_policy": {"type": "disabled"},
"skills": [inline_skill],
}
)
],
)

result1 = await Runner.run(
agent1,
(
"Use the csv-workbench skill. Create /mnt/data/orders.csv with columns "
"id,region,amount,status and at least 6 rows. Then report total amount by "
"region and count failed orders."
),
)
print(f"Agent: {result1.final_output}")

container_id = extract_container_id(result1.raw_responses)
if not container_id:
raise RuntimeError("Container ID was not returned in shell call output.")

print(f"[info] Reusing container_id={container_id}")

agent2 = Agent(
name="Container Reference Shell Agent",
model=model,
instructions="Reuse the existing shell container and answer concisely.",
tools=[
ShellTool(
environment={
"type": "container_reference",
"container_id": container_id,
}
)
],
)

result2 = await Runner.run(
agent2,
"Run `ls -la /mnt/data`, then summarize in one sentence.",
)
print(f"Agent (container reuse): {result2.final_output}")


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--model",
default="gpt-5.2",
help="Model name to use.",
)
args = parser.parse_args()
asyncio.run(main(args.model))
112 changes: 112 additions & 0 deletions examples/tools/container_shell_skill_reference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import argparse
import asyncio
import os

from openai.types.responses import ResponseFunctionShellToolCall
from openai.types.responses.response_container_reference import ResponseContainerReference

from agents import Agent, Runner, ShellTool, ShellToolSkillReference, trace
from agents.items import ModelResponse

SHELL_SKILL_ID_ENV = "OPENAI_SHELL_SKILL_ID"
SHELL_SKILL_VERSION_ENV = "OPENAI_SHELL_SKILL_VERSION"
DEFAULT_SKILL_REFERENCE: ShellToolSkillReference = {
"type": "skill_reference",
"skill_id": "skill_698bbe879adc81918725cbc69dcae7960bc5613dadaed377",
"version": "1",
}


def resolve_skill_reference() -> ShellToolSkillReference:
skill_id = os.environ.get(SHELL_SKILL_ID_ENV)
if not skill_id:
return DEFAULT_SKILL_REFERENCE

reference: ShellToolSkillReference = {"type": "skill_reference", "skill_id": skill_id}
skill_version = os.environ.get(SHELL_SKILL_VERSION_ENV)
if skill_version:
reference["version"] = skill_version
return reference


def extract_container_id(raw_responses: list[ModelResponse]) -> str | None:
for response in raw_responses:
for item in response.output:
if isinstance(item, ResponseFunctionShellToolCall) and isinstance(
item.environment, ResponseContainerReference
):
return item.environment.container_id

return None


async def main(model: str) -> None:
skill_reference = resolve_skill_reference()
print(
"[info] Using skill reference:",
skill_reference["skill_id"],
f"(version {skill_reference.get('version', 'default')})",
)

with trace("container_shell_skill_reference_example"):
agent1 = Agent(
name="Container Shell Agent (Skill Reference)",
model=model,
instructions="Use the available container skill to answer user requests.",
tools=[
ShellTool(
environment={
"type": "container_auto",
"network_policy": {"type": "disabled"},
"skills": [skill_reference],
}
)
],
)

result1 = await Runner.run(
agent1,
(
"Use the csv-workbench skill. Create /mnt/data/orders.csv with columns "
"id,region,amount,status and at least 6 rows. Then report total amount by "
"region and count failed orders."
),
)
print(f"Agent: {result1.final_output}")

container_id = extract_container_id(result1.raw_responses)
if not container_id:
raise RuntimeError("Container ID was not returned in shell call output.")

print(f"[info] Reusing container_id={container_id}")

agent2 = Agent(
name="Container Reference Shell Agent",
model=model,
instructions="Reuse the existing shell container and answer concisely.",
tools=[
ShellTool(
environment={
"type": "container_reference",
"container_id": container_id,
}
)
],
)

result2 = await Runner.run(
agent2,
"Run `ls -la /mnt/data`, then summarize in one sentence.",
)
print(f"Agent (container reuse): {result2.final_output}")


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--model",
default="gpt-5.2",
help="Model name to use.",
)
args = parser.parse_args()
asyncio.run(main(args.model))
20 changes: 20 additions & 0 deletions examples/tools/skills/csv-workbench/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: csv-workbench
description: Analyze CSV files in /mnt/data and return concise numeric summaries.
---

# CSV Workbench

Use this skill when the user asks for quick analysis of tabular data.

## Workflow

1. Inspect the CSV schema first (`head`, `python csv.DictReader`, or both).
2. Compute requested aggregates with a short Python script.
3. Return concise results with concrete numbers and units when available.

## Constraints

- Prefer Python stdlib for portability.
- If data is missing or malformed, state assumptions clearly.
- Keep the final answer short and actionable.
32 changes: 32 additions & 0 deletions examples/tools/skills/csv-workbench/playbook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# CSV Playbook

## Quick checks

- Preview rows: `head -n 10 /mnt/data/your-file.csv`.
- Count rows:

```bash
python - <<'PY'
import csv

with open('/mnt/data/your-file.csv', newline='') as f:
print(sum(1 for _ in csv.DictReader(f)))
PY
```

## Grouped totals template

```bash
python - <<'PY'
import csv
from collections import defaultdict

totals = defaultdict(float)
with open('/mnt/data/your-file.csv', newline='') as f:
for row in csv.DictReader(f):
totals[row['region']] += float(row['amount'])

for region in sorted(totals):
print(region, round(totals[region], 2))
PY
```
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ requires-python = ">=3.9"
license = "MIT"
authors = [{ name = "OpenAI", email = "support@openai.com" }]
dependencies = [
"openai>=2.9.0,<3",
"openai>=2.19.0,<3",
"pydantic>=2.12.3, <3",
"griffe>=1.5.6, <2",
"typing-extensions>=4.12.2, <5",
Expand Down
28 changes: 28 additions & 0 deletions src/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@
ShellExecutor,
ShellResult,
ShellTool,
ShellToolContainerAutoEnvironment,
ShellToolContainerNetworkPolicy,
ShellToolContainerNetworkPolicyAllowlist,
ShellToolContainerNetworkPolicyDisabled,
ShellToolContainerNetworkPolicyDomainSecret,
ShellToolContainerReferenceEnvironment,
ShellToolContainerSkill,
ShellToolEnvironment,
ShellToolHostedEnvironment,
ShellToolInlineSkill,
ShellToolInlineSkillSource,
ShellToolLocalEnvironment,
ShellToolLocalSkill,
ShellToolSkillReference,
Tool,
ToolOutputFileContent,
ToolOutputFileContentDict,
Expand Down Expand Up @@ -351,6 +365,20 @@ def enable_verbose_stdout_logging():
"ShellCallOutcome",
"ShellCommandOutput",
"ShellCommandRequest",
"ShellToolLocalSkill",
"ShellToolSkillReference",
"ShellToolInlineSkillSource",
"ShellToolInlineSkill",
"ShellToolContainerSkill",
"ShellToolContainerNetworkPolicyDomainSecret",
"ShellToolContainerNetworkPolicyAllowlist",
"ShellToolContainerNetworkPolicyDisabled",
"ShellToolContainerNetworkPolicy",
"ShellToolLocalEnvironment",
"ShellToolContainerAutoEnvironment",
"ShellToolContainerReferenceEnvironment",
"ShellToolHostedEnvironment",
"ShellToolEnvironment",
"ShellExecutor",
"ShellResult",
"ShellTool",
Expand Down
2 changes: 2 additions & 0 deletions src/agents/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
Response,
ResponseComputerToolCall,
ResponseFileSearchToolCall,
ResponseFunctionShellToolCallOutput,
ResponseFunctionToolCall,
ResponseFunctionWebSearch,
ResponseInputItemParam,
Expand Down Expand Up @@ -253,6 +254,7 @@ class ToolCallItem(RunItemBase[Any]):
FunctionCallOutput,
ComputerCallOutput,
LocalShellCallOutput,
ResponseFunctionShellToolCallOutput,
dict[str, Any],
]

Expand Down
Loading