-
Notifications
You must be signed in to change notification settings - Fork 6
Integrate with Claude Agent SDK #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
d064d6f
d67e148
0973770
4d31efa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
|
|
||
| { | ||
| "claude_allowed_tools": [ | ||
| "Bash(*)", | ||
| "Read", | ||
| "Write", | ||
| "Edit", | ||
| "Glob", | ||
| "Grep", | ||
| "WebFetch", | ||
| "Task" | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| fastapi>=0.115.0 | ||
| uvicorn>=0.32.0 | ||
| claude-agent-sdk>=0.1.0 |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,125 @@ | ||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||
| FastAPI server that exposes the /oape:api-implement Claude Code skill | ||||||||||||||||||||||||
| via the Claude Agent SDK. | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| Usage: | ||||||||||||||||||||||||
| uvicorn api.server:app --reload | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| Endpoint: | ||||||||||||||||||||||||
| GET /api-implement?ep_url=<enhancement-pr-url>&cwd=<operator-repo-path> | ||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import anyio | ||||||||||||||||||||||||
| from fastapi import FastAPI, HTTPException, Query | ||||||||||||||||||||||||
| from claude_agent_sdk import ( | ||||||||||||||||||||||||
| query, | ||||||||||||||||||||||||
| ClaudeAgentOptions, | ||||||||||||||||||||||||
| AssistantMessage, | ||||||||||||||||||||||||
| ResultMessage, | ||||||||||||||||||||||||
| TextBlock, | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| with open("config.json") as cf: | ||||||||||||||||||||||||
| config_json_str = cf.read() | ||||||||||||||||||||||||
| CONFIGS = json.loads(config_json_str) | ||||||||||||||||||||||||
|
Comment on lines
+28
to
+30
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Config file path is relative to CWD, not the module location. If the server is started from a directory other than 🐛 Proposed fix to resolve path relative to module-with open("config.json") as cf:
+with open(Path(__file__).parent / "config.json") as cf:
config_json_str = cf.read()
CONFIGS = json.loads(config_json_str)🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| app = FastAPI( | ||||||||||||||||||||||||
| title="OAPE Operator Feature Developer", | ||||||||||||||||||||||||
| description="Invokes the /oape:api-implement Claude Code command to generate " | ||||||||||||||||||||||||
| "controller/reconciler code from an OpenShift enhancement proposal.", | ||||||||||||||||||||||||
| version="0.1.0", | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| EP_URL_PATTERN = re.compile( | ||||||||||||||||||||||||
| r"^https://github\.com/openshift/enhancements/pull/\d+/?$" | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # Resolve the plugin directory (repo root) relative to this file. | ||||||||||||||||||||||||
| # The SDK expects the path to the plugin root (containing .claude-plugin/). | ||||||||||||||||||||||||
| PLUGIN_DIR = str(Path(__file__).resolve().parent.parent / "plugins" / "oape") | ||||||||||||||||||||||||
| print(PLUGIN_DIR) | ||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove debug print statement.
🧹 Proposed fix PLUGIN_DIR = str(Path(__file__).resolve().parent.parent / "plugins" / "oape")
-print(PLUGIN_DIR)
+# Optionally log at debug level:
+# logging.getLogger(__name__).debug("PLUGIN_DIR=%s", PLUGIN_DIR)🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @app.get("/api-implement") | ||||||||||||||||||||||||
| async def api_implement( | ||||||||||||||||||||||||
| ep_url: str = Query( | ||||||||||||||||||||||||
| ..., | ||||||||||||||||||||||||
| description="GitHub PR URL for the OpenShift enhancement proposal " | ||||||||||||||||||||||||
| "(e.g. https://github.com/openshift/enhancements/pull/1234)", | ||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||
| cwd: str = Query( | ||||||||||||||||||||||||
| default="", | ||||||||||||||||||||||||
| description="Absolute path to the operator repository where code " | ||||||||||||||||||||||||
| "will be generated. Defaults to the current working directory.", | ||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||
| """Generate controller/reconciler code from an enhancement proposal.""" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # --- Validate EP URL --- | ||||||||||||||||||||||||
| if not EP_URL_PATTERN.match(ep_url.rstrip("/")): | ||||||||||||||||||||||||
| raise HTTPException( | ||||||||||||||||||||||||
| status_code=400, | ||||||||||||||||||||||||
| detail=( | ||||||||||||||||||||||||
| "Invalid enhancement PR URL. " | ||||||||||||||||||||||||
| "Expected format: https://github.com/openshift/enhancements/pull/<number>" | ||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # --- Resolve working directory --- | ||||||||||||||||||||||||
| working_dir = cwd if cwd else os.getcwd() | ||||||||||||||||||||||||
| if not os.path.isdir(working_dir): | ||||||||||||||||||||||||
| raise HTTPException( | ||||||||||||||||||||||||
| status_code=400, | ||||||||||||||||||||||||
| detail=f"The provided cwd is not a valid directory: {working_dir}", | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # --- Build SDK options --- | ||||||||||||||||||||||||
| options = ClaudeAgentOptions( | ||||||||||||||||||||||||
| system_prompt=( | ||||||||||||||||||||||||
| "You are an OpenShift operator code generation assistant. " | ||||||||||||||||||||||||
| "Execute the oape:api-implement plugin with the provided EP URL. " | ||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||
| cwd=working_dir, | ||||||||||||||||||||||||
| permission_mode="bypassPermissions", | ||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Document or make Setting
🔒 Proposed fix to make permission mode configurable options = ClaudeAgentOptions(
system_prompt=(
"You are an OpenShift operator code generation assistant. "
"Execute the oape:api-implement plugin with the provided EP URL. "
),
cwd=working_dir,
- permission_mode="bypassPermissions",
+ permission_mode=CONFIGS.get("permission_mode", "default"),
allowed_tools=CONFIGS['claude_allowed_tools'],
plugins=[{"type": "local", "path": PLUGIN_DIR}],
)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| allowed_tools=CONFIGS['claude_allowed_tools'], | ||||||||||||||||||||||||
| plugins=[{"type": "local", "path": PLUGIN_DIR}], | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # --- Run the agent --- | ||||||||||||||||||||||||
| output_parts: list[str] = [] | ||||||||||||||||||||||||
| cost_usd = 0.0 | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||
| async for message in query( | ||||||||||||||||||||||||
| prompt=f"/oape:api-implement {ep_url}", | ||||||||||||||||||||||||
| # prompt="explain the enhancement proposal to me like I'm 5 in 10 sentences, {ep_url}", | ||||||||||||||||||||||||
| options=options, | ||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||
| if isinstance(message, AssistantMessage): | ||||||||||||||||||||||||
| for block in message.content: | ||||||||||||||||||||||||
| if isinstance(block, TextBlock): | ||||||||||||||||||||||||
| output_parts.append(block.text) | ||||||||||||||||||||||||
| elif isinstance(message, ResultMessage): | ||||||||||||||||||||||||
| cost_usd = message.total_cost_usd | ||||||||||||||||||||||||
| if message.result: | ||||||||||||||||||||||||
| output_parts.append(message.result) | ||||||||||||||||||||||||
| except Exception as exc: | ||||||||||||||||||||||||
| raise HTTPException( | ||||||||||||||||||||||||
| status_code=500, | ||||||||||||||||||||||||
| detail=f"Agent execution failed: {exc}", | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
| except Exception as exc: | |
| raise HTTPException( | |
| status_code=500, | |
| detail=f"Agent execution failed: {exc}", | |
| ) | |
| except Exception as exc: | |
| raise HTTPException( | |
| status_code=500, | |
| detail=f"Agent execution failed: {exc}", | |
| ) from exc |
🧰 Tools
🪛 Ruff (0.15.0)
[warning] 113-113: Do not catch blind exception: Exception
(BLE001)
[warning] 114-117: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
🤖 Prompt for AI Agents
In `@server/server.py` around lines 113 - 117, The current broad "except Exception
as exc" block re-raises an HTTPException without chaining, which loses the
original traceback; modify the handler to either catch specific agent/SDK
exceptions (e.g., AgentExecutionError or SDK-specific exceptions) or, if you
must catch Exception, re-raise the HTTPException using exception chaining (raise
HTTPException(status_code=500, detail=f"Agent execution failed: {exc}") from
exc) so the original traceback is preserved; update the except block that
contains "except Exception as exc" and reference HTTPException to implement this
change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct the module path in the usage snippet.
Line 6 points to
uvicorn api.server:app, but the module path here isserver.server. This will fail unless anapipackage exists.🛠️ Proposed fix
🤖 Prompt for AI Agents