Skip to content

Commit c0dac34

Browse files
committed
docs(examples): add Northflank examples
Three example runners: * examples/northflank/list_services.py — agent uses northflank_tools() to enumerate services in a project and summarise them. * examples/northflank/remote_shell.py — agent uses ShellTool wired to NorthflankShellExecutor so every shell command runs inside a target Northflank service. * examples/sandbox/extensions/northflank_runner.py — manual sandbox runner: small workspace, one shell tool, plus a stop/resume snapshot round-trip check against a real Northflank service.
1 parent c0da00e commit c0dac34

3 files changed

Lines changed: 448 additions & 0 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Minimal example: ask an agent to list services in a Northflank project.
2+
3+
Run::
4+
5+
OPENAI_API_KEY=... NF_API_TOKEN=... \\
6+
python examples/northflank/list_services.py demo-app
7+
"""
8+
9+
from __future__ import annotations
10+
11+
import asyncio
12+
import sys
13+
14+
from northflank import AsyncApiClient
15+
16+
from agents import Agent, Runner
17+
from agents.extensions.northflank import NorthflankCtx, northflank_tools
18+
19+
20+
async def main(project_id: str) -> None:
21+
client = AsyncApiClient()
22+
23+
agent = Agent(
24+
name="northflank-ops",
25+
instructions=(
26+
"You manage Northflank services on the user's behalf. "
27+
"When the user asks about services, call nf_list_services and "
28+
"summarise the result in two or three sentences."
29+
),
30+
tools=list(northflank_tools()),
31+
)
32+
33+
result = await Runner.run(
34+
agent,
35+
f"Which services are running in project {project_id}? Group them by status.",
36+
context=NorthflankCtx(client=client, project_id=project_id),
37+
)
38+
print(result.final_output)
39+
40+
41+
if __name__ == "__main__":
42+
if len(sys.argv) != 2:
43+
sys.exit(f"usage: {sys.argv[0]} <project_id>")
44+
asyncio.run(main(sys.argv[1]))
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""Example: a ShellTool whose every command runs *inside* a Northflank service.
2+
3+
Run::
4+
5+
OPENAI_API_KEY=... NF_API_TOKEN=... \\
6+
python examples/northflank/remote_shell.py demo-app api
7+
"""
8+
9+
from __future__ import annotations
10+
11+
import asyncio
12+
import os
13+
import sys
14+
from collections.abc import Sequence
15+
16+
from northflank import AsyncApiClient
17+
18+
from agents import Agent, ModelSettings, Runner, ShellTool
19+
from agents.extensions.northflank import NorthflankCtx, NorthflankShellExecutor
20+
from agents.items import ToolApprovalItem
21+
from agents.run_context import RunContextWrapper
22+
from agents.tool import ShellOnApprovalFunctionResult
23+
24+
AUTO_APPROVE = os.environ.get("SHELL_AUTO_APPROVE") == "1"
25+
26+
27+
async def prompt_for_approval(commands: Sequence[str]) -> bool:
28+
if AUTO_APPROVE:
29+
return True
30+
print("Approve these commands?")
31+
for command in commands:
32+
print(" $", command)
33+
return input("[y/N] ").strip().lower() in {"y", "yes"}
34+
35+
36+
async def on_shell_approval(
37+
_ctx: RunContextWrapper, item: ToolApprovalItem
38+
) -> ShellOnApprovalFunctionResult:
39+
raw = item.raw_item
40+
commands: Sequence[str] = ()
41+
if isinstance(raw, dict):
42+
action = raw.get("action", {})
43+
if isinstance(action, dict):
44+
commands = action.get("commands", [])
45+
else:
46+
action_obj = getattr(raw, "action", None)
47+
if action_obj is not None and hasattr(action_obj, "commands"):
48+
commands = action_obj.commands
49+
approved = await prompt_for_approval(commands)
50+
return {"approve": approved, "reason": "ok" if approved else "user rejected"}
51+
52+
53+
async def main(project_id: str, service_id: str) -> None:
54+
client = AsyncApiClient()
55+
executor = NorthflankShellExecutor(service_id=service_id, shell="sh")
56+
57+
agent = Agent(
58+
name="northflank-shell",
59+
instructions=(
60+
"Diagnose a misbehaving Northflank service. Run a small number of "
61+
"shell commands inside the target container and explain the output."
62+
),
63+
tools=[
64+
ShellTool(
65+
executor=executor,
66+
needs_approval=True,
67+
on_approval=on_shell_approval,
68+
)
69+
],
70+
model_settings=ModelSettings(tool_choice="auto"),
71+
)
72+
73+
result = await Runner.run(
74+
agent,
75+
"Check memory usage and list the most recent files in /tmp.",
76+
context=NorthflankCtx(client=client, project_id=project_id),
77+
)
78+
print("\n--- final ---")
79+
print(result.final_output)
80+
81+
82+
if __name__ == "__main__":
83+
if len(sys.argv) != 3:
84+
sys.exit(f"usage: {sys.argv[0]} <project_id> <service_id>")
85+
asyncio.run(main(sys.argv[1], sys.argv[2]))

0 commit comments

Comments
 (0)