chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:25 +08:00
commit db620d33df
5151 changed files with 925932 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
# Claude Package (agent-framework-claude)
Integration with Anthropic Claude as a managed agent (Claude Agent SDK).
## Main Classes
- **`ClaudeAgent`** - Agent using Claude's native agent capabilities
- **`ClaudeAgentOptions`** - Options for Claude agent configuration
- **`ClaudeAgentSettings`** - Pydantic settings for configuration
## Usage
```python
from agent_framework_claude import ClaudeAgent
agent = ClaudeAgent(...)
response = await agent.run("Hello")
```
## Import Path
```python
from agent_framework_claude import ClaudeAgent
```
## Note
This package is for Claude's managed agent functionality. For basic Claude chat, use `agent-framework-anthropic` instead.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
+11
View File
@@ -0,0 +1,11 @@
# Get Started with Microsoft Agent Framework Claude
Please install this package via pip:
```bash
pip install agent-framework-claude --pre
```
## Claude Agent
The Claude agent enables integration with Claude Agent SDK, allowing you to interact with Claude's agentic capabilities through the Agent Framework.
@@ -0,0 +1,18 @@
# Copyright (c) Microsoft. All rights reserved.
import importlib.metadata
from ._agent import ClaudeAgent, ClaudeAgentOptions, ClaudeAgentSettings, RawClaudeAgent
try:
__version__ = importlib.metadata.version(__name__)
except importlib.metadata.PackageNotFoundError:
__version__ = "0.0.0" # Fallback for development mode
__all__ = [
"ClaudeAgent",
"ClaudeAgentOptions",
"ClaudeAgentSettings",
"RawClaudeAgent",
"__version__",
]
@@ -0,0 +1,951 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import contextlib
import inspect
import logging
import sys
from collections.abc import AsyncIterable, Awaitable, Callable, Mapping, MutableMapping, Sequence
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal, cast, overload
from agent_framework import (
AgentMiddlewareTypes,
AgentResponse,
AgentResponseUpdate,
AgentRunInputs,
AgentSession,
BaseAgent,
Content,
ContextProvider,
FunctionTool,
Message,
ResponseStream,
ToolTypes,
UsageDetails,
load_settings,
normalize_messages,
normalize_tools,
)
from agent_framework.exceptions import AgentException
from agent_framework.observability import AgentTelemetryLayer
from claude_agent_sdk import (
AssistantMessage,
ClaudeSDKClient,
ResultMessage,
SdkMcpTool,
create_sdk_mcp_server,
)
from claude_agent_sdk import (
ClaudeAgentOptions as SDKOptions,
)
from claude_agent_sdk.types import StreamEvent, TextBlock
if sys.version_info >= (3, 13):
from typing import TypeVar # pragma: no cover
else:
from typing_extensions import TypeVar # pragma: no cover
if sys.version_info >= (3, 11):
from typing import TypedDict # pragma: no cover
else:
from typing_extensions import TypedDict # pragma: no cover
if TYPE_CHECKING:
from claude_agent_sdk import (
AgentDefinition,
CanUseTool,
HookMatcher,
McpServerConfig,
PermissionMode,
SandboxSettings,
SdkBeta,
SdkPluginConfig,
SettingSource,
)
from claude_agent_sdk.types import ThinkingConfig
logger = logging.getLogger("agent_framework.claude")
# Name of the in-process MCP server that hosts Agent Framework tools.
# FunctionTool instances are converted to SDK MCP tools and served
# through this server, as Claude Code CLI only supports tools via MCP.
TOOLS_MCP_SERVER_NAME = "_agent_framework_tools"
FunctionApprovalCallback = Callable[[Content], "bool | Awaitable[bool]"]
"""Callback invoked by the agent before executing a FunctionTool that requires approval.
The callback receives a ``FunctionCallContent`` describing the pending call
(``name``, ``arguments``, and a synthetic ``call_id``) and must return ``True``
to allow execution or ``False`` to deny it. Both synchronous and ``await``-able
return values are supported.
The Claude Agent SDK manages its own tool-calling loop, so the framework cannot
round-trip a ``FunctionApprovalRequestContent`` / ``FunctionApprovalResponseContent``
pair the way the standard chat-client pipeline does. This callback is the
agent-level enforcement point for tools declared with
``approval_mode="always_require"``: when no callback is configured the agent
denies these calls by default.
"""
async def _resolve_function_approval(
callback: FunctionApprovalCallback | None,
func_tool: FunctionTool,
arguments: Mapping[str, Any] | None,
) -> bool:
"""Run the agent-level approval callback for a pending tool call.
Returns ``True`` only when ``callback`` is configured and explicitly returns
a truthy value. A missing callback or any callback failure is treated as a
denial so the secure-by-default policy holds even if the user code raises.
"""
if callback is None:
return False
request = Content.from_function_call(
call_id=f"af-claude-approval::{func_tool.name}",
name=func_tool.name,
arguments=None if arguments is None else dict(arguments),
)
try:
outcome = callback(request)
if inspect.isawaitable(outcome):
outcome = await outcome
except Exception:
logger.exception(
"on_function_approval callback raised for tool '%s'; denying execution.",
func_tool.name,
)
return False
return bool(outcome)
class ClaudeAgentSettings(TypedDict, total=False):
"""Claude Agent settings.
Settings are resolved in this order: explicit keyword arguments, values from an
explicitly provided .env file, then environment variables with the prefix
'CLAUDE_AGENT_'.
Keys:
cli_path: The path to Claude CLI executable.
model: The model to use (sonnet, opus, haiku).
cwd: The working directory for Claude CLI.
permission_mode: Permission mode (default, acceptEdits, plan, bypassPermissions).
max_turns: Maximum number of conversation turns.
max_budget_usd: Maximum budget in USD.
"""
cli_path: str | None
model: str | None
cwd: str | None
permission_mode: str | None
max_turns: int | None
max_budget_usd: float | None
class ClaudeAgentOptions(TypedDict, total=False):
"""Claude Agent-specific options."""
system_prompt: str
"""System prompt for the agent."""
cli_path: str | Path
"""Path to Claude CLI executable. Default: auto-detected."""
cwd: str | Path
"""Working directory for Claude CLI. Default: current working directory."""
env: dict[str, str]
"""Environment variables to pass to CLI."""
settings: str
"""Path to Claude settings file."""
model: str
"""Model to use ("sonnet", "opus", "haiku"). Default: "sonnet"."""
fallback_model: str
"""Fallback model if primary fails."""
allowed_tools: list[str]
"""Allowlist of tools. If set, Claude can ONLY use tools in this list."""
disallowed_tools: list[str]
"""Blocklist of tools. Claude cannot use these tools."""
mcp_servers: dict[str, McpServerConfig]
"""MCP server configurations for external tools."""
permission_mode: PermissionMode
"""Permission handling mode ("default", "acceptEdits", "plan", "bypassPermissions")."""
can_use_tool: CanUseTool
"""Permission callback for tool use."""
max_turns: int
"""Maximum conversation turns."""
max_budget_usd: float
"""Budget limit in USD."""
hooks: dict[str, list[HookMatcher]]
"""Pre/post tool hooks."""
add_dirs: list[str | Path]
"""Additional directories to add to context."""
sandbox: SandboxSettings
"""Sandbox configuration for bash isolation."""
agents: dict[str, AgentDefinition]
"""Custom agent definitions."""
output_format: dict[str, Any]
"""Structured output format (JSON schema)."""
enable_file_checkpointing: bool
"""Enable file checkpointing for rewind."""
betas: list[SdkBeta]
"""Beta features to enable."""
plugins: list[SdkPluginConfig]
"""Plugin configurations for custom commands and capabilities."""
setting_sources: list[SettingSource]
"""Which Claude settings files to load ("user", "project", "local")."""
thinking: ThinkingConfig
"""Extended thinking configuration (adaptive, enabled, or disabled)."""
effort: Literal["low", "medium", "high", "xhigh", "max"]
"""Effort level for thinking depth."""
skills: list[str] | Literal["all"]
"""Skills to enable for the main session. Use ``"all"`` for every discovered skill,
a list of named skills, or ``[]`` to suppress all skills."""
session_id: str
"""Use a specific session ID (must be a valid UUID) instead of auto-generated."""
task_budget: dict[str, int]
"""API-side task budget in tokens for pacing tool use."""
include_hook_events: bool
"""When True, hook lifecycle events are emitted in the message stream."""
strict_mcp_config: bool
"""When True, only use MCP servers passed via ``mcp_servers``, ignoring all others."""
continue_conversation: bool
"""Continue the most recent conversation instead of starting a new one."""
fork_session: bool
"""When True, resumed sessions fork to a new session ID."""
on_function_approval: FunctionApprovalCallback
"""Approval callback for ``FunctionTool`` instances declared with
``approval_mode="always_require"``. The callback is awaited (sync or async)
inside the SDK tool-handler before the tool is executed; a falsy return
value denies the call. If omitted, calls to such tools are denied with an
explanatory message returned to the model."""
OptionsT = TypeVar(
"OptionsT",
bound=TypedDict, # type: ignore[valid-type]
default="ClaudeAgentOptions",
covariant=True,
)
class RawClaudeAgent(BaseAgent, Generic[OptionsT]):
"""Claude Agent using Claude Code CLI without telemetry layers.
This is the core Claude agent implementation without OpenTelemetry instrumentation.
For most use cases, prefer :class:`ClaudeAgent` which includes telemetry support.
Wraps the Claude Agent SDK to provide agentic capabilities including
tool use, session management, and streaming responses.
This agent communicates with Claude through the Claude Code CLI,
enabling access to Claude's full agentic capabilities like file
editing, code execution, and tool use.
The agent can be used as an async context manager to ensure proper cleanup:
Examples:
Basic usage with context manager:
.. code-block:: python
from agent_framework.anthropic import RawClaudeAgent
async with RawClaudeAgent(
instructions="You are a helpful assistant.",
) as agent:
response = await agent.run("Hello!")
print(response.text)
"""
AGENT_PROVIDER_NAME: ClassVar[str] = "anthropic.claude"
def __init__(
self,
instructions: str | None = None,
*,
client: ClaudeSDKClient | None = None,
id: str | None = None,
name: str | None = None,
description: str | None = None,
context_providers: Sequence[ContextProvider] | None = None,
middleware: Sequence[AgentMiddlewareTypes] | None = None,
tools: ToolTypes | Callable[..., Any] | str | Sequence[ToolTypes | Callable[..., Any] | str] | None = None,
default_options: OptionsT | MutableMapping[str, Any] | None = None,
env_file_path: str | None = None,
env_file_encoding: str | None = None,
) -> None:
"""Initialize a RawClaudeAgent instance.
Args:
instructions: System prompt for the agent.
Keyword Args:
client: Optional pre-configured ClaudeSDKClient instance. If not provided,
a new client will be created using the other parameters.
id: Unique identifier for the agent.
name: Name of the agent.
description: Description of the agent.
context_providers: Context providers for the agent.
middleware: List of middleware.
tools: Tools for the agent. Can be:
- Strings for built-in tools (e.g., "Read", "Write", "Bash", "Glob")
- Functions for custom tools
default_options: Default ClaudeAgentOptions including system_prompt, model, etc.
env_file_path: Path to .env file.
env_file_encoding: Encoding of .env file.
"""
super().__init__(
id=id,
name=name,
description=description,
context_providers=context_providers,
middleware=middleware,
)
self._client = client
self._owns_client = client is None
# Parse options
opts: dict[str, Any] = dict(default_options) if default_options else {}
# Handle instructions parameter - set as system_prompt in options
if instructions is not None:
opts["system_prompt"] = instructions
cli_path = opts.pop("cli_path", None)
model = opts.pop("model", None)
cwd = opts.pop("cwd", None)
permission_mode = opts.pop("permission_mode", None)
max_turns = opts.pop("max_turns", None)
max_budget_usd = opts.pop("max_budget_usd", None)
self._mcp_servers: dict[str, Any] = opts.pop("mcp_servers", None) or {}
self._function_approval_handler: FunctionApprovalCallback | None = opts.pop("on_function_approval", None)
# Load settings from environment and options
self._settings = load_settings(
ClaudeAgentSettings,
env_prefix="CLAUDE_AGENT_",
cli_path=cli_path,
model=model,
cwd=cwd,
permission_mode=permission_mode,
max_turns=max_turns,
max_budget_usd=max_budget_usd,
env_file_path=env_file_path,
env_file_encoding=env_file_encoding,
)
# Separate built-in tools (strings) from custom tools (callables/FunctionTool)
self._builtin_tools: list[str] = []
self._custom_tools: list[ToolTypes] = []
self._normalize_tools(tools)
self._default_options = opts
self._started = False
self._current_session_id: str | None = None
self._structured_output: Any = None
def _normalize_tools(
self,
tools: ToolTypes | Callable[..., Any] | str | Sequence[ToolTypes | Callable[..., Any] | str] | None,
) -> None:
"""Separate built-in tools (strings) from custom tools.
Args:
tools: Mixed list of tool names and custom tools.
"""
if tools is None:
return
non_builtin_tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] = []
if not isinstance(tools, list):
tools = [tools]
for tool in tools: # type: ignore[reportUnknownVariableType]
if isinstance(tool, str):
self._builtin_tools.append(tool)
else:
non_builtin_tools.append(tool) # type: ignore[union-attr, reportUnknownArgumentType]
if not non_builtin_tools:
return
self._custom_tools.extend(normalize_tools(non_builtin_tools))
async def __aenter__(self) -> RawClaudeAgent[OptionsT]:
"""Start the agent when entering async context."""
await self.start()
return self
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
"""Stop the agent when exiting async context."""
await self.stop()
async def start(self) -> None:
"""Start the Claude SDK client.
This method initializes the Claude SDK client and establishes a connection
to the Claude Code CLI. It is called automatically when using the agent
as an async context manager.
Raises:
AgentException: If the client fails to start.
"""
await self._ensure_session()
async def stop(self) -> None:
"""Stop the Claude SDK client and clean up resources.
Stops the client if owned by this agent. Called automatically when
using the agent as an async context manager.
"""
if self._client and self._owns_client:
with contextlib.suppress(Exception):
await self._client.disconnect()
self._started = False
self._current_session_id = None
async def _ensure_session(self, session_id: str | None = None) -> None:
"""Ensure the client is connected for the specified session.
If the requested session differs from the current one, recreates the client.
Args:
session_id: The session ID to use, or None for a new session.
"""
needs_new_client = (
not self._started or self._client is None or (session_id and session_id != self._current_session_id)
)
if needs_new_client:
# Stop existing client if any
if self._client and self._owns_client:
with contextlib.suppress(Exception):
await self._client.disconnect()
self._started = False
# Create new client with resume option if needed
opts = self._prepare_client_options(resume_session_id=session_id)
self._client = ClaudeSDKClient(options=opts)
self._owns_client = True
try:
await self._client.connect()
self._started = True
self._current_session_id = session_id
except Exception as ex:
self._client = None
raise AgentException(f"Failed to start Claude SDK client: {ex}") from ex
def _prepare_client_options(self, resume_session_id: str | None = None) -> SDKOptions:
"""Prepare SDK options for client initialization.
Args:
resume_session_id: Optional session ID to resume.
Returns:
SDKOptions instance configured for the client.
"""
opts: dict[str, Any] = {}
# Set resume option if provided
if resume_session_id:
opts["resume"] = resume_session_id
# Apply settings from environment
if cli_path := self._settings.get("cli_path"):
opts["cli_path"] = cli_path
if model := self._settings.get("model"):
opts["model"] = model
if cwd := self._settings.get("cwd"):
opts["cwd"] = cwd
if permission_mode := self._settings.get("permission_mode"):
opts["permission_mode"] = permission_mode
if max_turns := self._settings.get("max_turns"):
opts["max_turns"] = max_turns
if max_budget_usd := self._settings.get("max_budget_usd"):
opts["max_budget_usd"] = max_budget_usd
# Apply default options
for key, value in self._default_options.items():
if value is not None:
opts[key] = value
# Add built-in tools (strings like "Read", "Write", "Bash")
if self._builtin_tools:
opts["tools"] = self._builtin_tools
# Prepare custom tools (FunctionTool instances)
custom_tools_server, custom_tool_names = (
self._prepare_tools(self._custom_tools) if self._custom_tools else (None, [])
)
# MCP servers - merge user-provided servers with custom tools server
mcp_servers = dict(self._mcp_servers) if self._mcp_servers else {}
if custom_tools_server:
mcp_servers[TOOLS_MCP_SERVER_NAME] = custom_tools_server
if mcp_servers:
opts["mcp_servers"] = mcp_servers
# Add custom tools to allowed_tools so they can be executed
if custom_tool_names:
existing_allowed = opts.get("allowed_tools", [])
opts["allowed_tools"] = list(existing_allowed) + custom_tool_names
# Always enable partial messages for streaming support
opts["include_partial_messages"] = True
return SDKOptions(**opts)
def _prepare_tools(
self,
tools: Sequence[ToolTypes],
) -> tuple[Any, list[str]]:
"""Convert Agent Framework tools to SDK MCP server.
Args:
tools: List of Agent Framework tools.
Returns:
Tuple of (MCP server config, list of allowed tool names).
"""
sdk_tools: list[SdkMcpTool[Any]] = []
tool_names: list[str] = []
for tool in tools:
if isinstance(tool, FunctionTool):
sdk_tools.append(self._function_tool_to_sdk_mcp_tool(tool))
# Claude Agent SDK convention: MCP tools use format "mcp__{server}__{tool}"
tool_names.append(f"mcp__{TOOLS_MCP_SERVER_NAME}__{tool.name}")
else:
# Non-FunctionTool items (e.g., dict-based hosted tools) cannot be converted to SDK MCP tools
logger.debug(f"Unsupported tool type: {type(tool)}")
if not sdk_tools:
return None, []
return create_sdk_mcp_server(name=TOOLS_MCP_SERVER_NAME, tools=sdk_tools), tool_names
def _function_tool_to_sdk_mcp_tool(self, func_tool: FunctionTool) -> SdkMcpTool[Any]:
"""Convert a FunctionTool to an SDK MCP tool.
Args:
func_tool: The FunctionTool to convert.
Returns:
An SdkMcpTool instance.
"""
approval_handler = self._function_approval_handler
requires_approval = func_tool.approval_mode == "always_require"
async def handler(args: dict[str, Any]) -> dict[str, Any]:
"""Handler that invokes the FunctionTool."""
try:
if requires_approval and not await _resolve_function_approval(approval_handler, func_tool, args):
deny_text = (
f"Tool '{func_tool.name}' requires human approval "
"(approval_mode='always_require') and the request was denied."
if approval_handler is not None
else (
f"Tool '{func_tool.name}' requires human approval "
"(approval_mode='always_require') but no on_function_approval "
"callback is configured on the agent; the request was denied."
)
)
logger.warning(
"Denying execution of tool '%s' (approval_mode='always_require', %s)",
func_tool.name,
"callback denied" if approval_handler is not None else "no callback configured",
)
return {"content": [{"type": "text", "text": deny_text}]}
if func_tool.input_model:
args_instance = func_tool.input_model(**args)
result = await func_tool.invoke(arguments=args_instance)
else:
result = await func_tool.invoke(arguments=args)
content_blocks: list[dict[str, str]] = []
for c in result:
if c.type == "text" and c.text:
content_blocks.append({"type": "text", "text": c.text})
elif c.type in ("data", "uri"):
logger.warning(
"Claude Agent SDK does not support rich content (images, audio) "
"in tool results. Rich content items will be omitted."
)
return {"content": content_blocks or [{"type": "text", "text": ""}]}
except Exception as e:
return {"content": [{"type": "text", "text": f"Error: {e}"}]}
# Get JSON schema from pydantic model
schema: dict[str, Any] = func_tool.input_model.model_json_schema() if func_tool.input_model else {}
input_schema: dict[str, Any] = {
"type": "object",
"properties": schema.get("properties", {}),
"required": schema.get("required", []),
}
# Preserve $defs for nested type references (Pydantic uses $defs for nested models)
if "$defs" in schema:
input_schema["$defs"] = schema["$defs"]
return SdkMcpTool(
name=func_tool.name,
description=func_tool.description,
input_schema=input_schema,
handler=handler,
)
async def _apply_runtime_options(self, options: dict[str, Any] | None) -> None:
"""Apply runtime options that can be changed dynamically.
The Claude SDK supports changing model and permission_mode after connection.
Args:
options: Runtime options to apply.
"""
if not options or not self._client:
return
if "on_function_approval" in options:
raise ValueError(
"on_function_approval is a security-sensitive option and must be set "
"via default_options at agent construction time. It cannot be overridden "
"per run."
)
if "model" in options:
await self._client.set_model(options["model"])
if "permission_mode" in options:
await self._client.set_permission_mode(options["permission_mode"])
def _format_prompt(self, messages: list[Message] | None) -> str:
"""Format messages into a prompt string.
Args:
messages: List of chat messages.
Returns:
Formatted prompt string.
"""
if not messages:
return ""
return "\n".join([msg.text or "" for msg in messages])
@property
def default_options(self) -> dict[str, Any]:
"""Expose options with ``instructions`` key.
Maps ``system_prompt`` to ``instructions`` for compatibility with
:class:`AgentTelemetryLayer`, which reads the system prompt from
the ``instructions`` key.
"""
opts = dict(self._default_options)
system_prompt = opts.pop("system_prompt", None)
if system_prompt is not None:
opts["instructions"] = system_prompt
return opts
def _finalize_response(self, updates: Sequence[AgentResponseUpdate]) -> AgentResponse[Any]:
"""Build AgentResponse and propagate structured_output as value.
Args:
updates: The collected stream updates.
Returns:
An AgentResponse with structured_output set as value if present.
"""
return AgentResponse.from_updates(updates, value=self._structured_output)
@overload
def run(
self,
messages: AgentRunInputs | None = None,
*,
stream: Literal[False] = ...,
session: AgentSession | None = None,
options: OptionsT | None = None,
**kwargs: Any,
) -> Awaitable[AgentResponse[Any]]: ...
@overload
def run(
self,
messages: AgentRunInputs | None = None,
*,
stream: Literal[True],
session: AgentSession | None = None,
options: OptionsT | None = None,
**kwargs: Any,
) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ...
def run(
self,
messages: AgentRunInputs | None = None,
*,
stream: bool = False,
session: AgentSession | None = None,
options: OptionsT | None = None,
**kwargs: Any,
) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]:
"""Run the agent with the given messages.
Args:
messages: The messages to process.
Keyword Args:
stream: If True, returns an async iterable of updates. If False (default),
returns an awaitable AgentResponse.
session: The conversation session. If session has service_session_id set,
the agent will resume that session.
options: Runtime options. Model and permission_mode can be changed per request.
kwargs: Additional keyword arguments for compatibility with the shared agent
interface (e.g. compaction_strategy, tokenizer). Not used by ClaudeAgent.
Returns:
When stream=True: An ResponseStream for streaming updates.
When stream=False: An Awaitable[AgentResponse] with the complete response.
"""
response = ResponseStream(
self._get_stream(messages, session=session, options=options),
finalizer=self._finalize_response,
)
if stream:
return response
return response.get_final_response()
async def _get_stream(
self,
messages: AgentRunInputs | None = None,
*,
session: AgentSession | None = None,
options: OptionsT | None = None,
) -> AsyncIterable[AgentResponseUpdate]:
"""Internal streaming implementation."""
session = session or self.create_session()
# Ensure we're connected to the right session
await self._ensure_session(self._get_chat_conversation_id(session))
if not self._client:
raise RuntimeError("Claude SDK client not initialized.")
prompt = self._format_prompt(normalize_messages(messages))
# Apply runtime options (model, permission_mode)
await self._apply_runtime_options(dict(options) if options else None)
session_id: str | None = None
structured_output: Any = None
await self._client.query(prompt)
async for message in self._client.receive_response():
if isinstance(message, StreamEvent):
# Handle streaming events - extract text/thinking deltas
event = message.event
if event.get("type") == "content_block_delta":
delta = event.get("delta", {})
delta_type = delta.get("type")
if delta_type == "text_delta":
text = delta.get("text", "")
if text:
yield AgentResponseUpdate(
role="assistant",
contents=[Content.from_text(text=text, raw_representation=message)],
raw_representation=message,
)
elif delta_type == "thinking_delta":
thinking = delta.get("thinking", "")
if thinking:
yield AgentResponseUpdate(
role="assistant",
contents=[Content.from_text_reasoning(text=thinking, raw_representation=message)],
raw_representation=message,
)
elif isinstance(message, AssistantMessage):
# Handle AssistantMessage - check for API errors
# Note: In streaming mode, the content was already yielded via StreamEvent,
# so we only check for errors here, not re-emit content.
if message.error:
# Map error types to descriptive messages
error_messages = {
"authentication_failed": "Authentication failed with Claude API",
"billing_error": "Billing error with Claude API",
"rate_limit": "Rate limit exceeded for Claude API",
"invalid_request": "Invalid request to Claude API",
"server_error": "Claude API server error",
"unknown": "Unknown error from Claude API",
}
error_msg = error_messages.get(message.error, f"Claude API error: {message.error}")
# Extract any error details from content blocks
if message.content:
for block in message.content:
if isinstance(block, TextBlock):
error_msg = f"{error_msg}: {block.text}"
break
raise AgentException(error_msg)
elif isinstance(message, ResultMessage):
# Check for errors in result message
if message.is_error:
error_msg = message.result or "Unknown error from Claude API"
raise AgentException(f"Claude API error: {error_msg}")
session_id = message.session_id
structured_output = message.structured_output
usage = message.usage or {}
input_tokens = usage.get("input_tokens")
output_tokens = usage.get("output_tokens")
total_token_count = (
input_tokens + output_tokens
if isinstance(input_tokens, int) and isinstance(output_tokens, int)
else None
)
usage_details = UsageDetails(**{
key: value
for key, value in {
"input_token_count": input_tokens,
"output_token_count": output_tokens,
"total_token_count": total_token_count,
"cache_creation_input_token_count": usage.get("cache_creation_input_tokens"),
"cache_read_input_token_count": usage.get("cache_read_input_tokens"),
}.items()
if isinstance(value, int)
})
finish_reason = message.stop_reason
if usage_details or finish_reason:
yield AgentResponseUpdate(
contents=[Content.from_usage(usage_details, raw_representation=message)]
if usage_details
else None,
finish_reason=cast(Any, finish_reason),
raw_representation=message,
)
# Update session with session ID
if session_id:
session.service_session_id = session_id
# Store structured output for the finalizer
self._structured_output = structured_output
class ClaudeAgent(AgentTelemetryLayer, RawClaudeAgent[OptionsT], Generic[OptionsT]):
"""Claude Agent with OpenTelemetry instrumentation.
This is the recommended agent class for most use cases. It includes
OpenTelemetry-based telemetry for observability. For a minimal
implementation without telemetry, use :class:`RawClaudeAgent`.
Examples:
Basic usage with context manager:
.. code-block:: python
from agent_framework.anthropic import ClaudeAgent
async with ClaudeAgent(
instructions="You are a helpful assistant.",
) as agent:
response = await agent.run("Hello!")
print(response.text)
"""
@overload
def run(
self,
messages: AgentRunInputs | None = None,
*,
stream: Literal[False] = ...,
session: AgentSession | None = None,
middleware: Sequence[AgentMiddlewareTypes] | None = None,
options: OptionsT | None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
compaction_strategy: Any = None,
tokenizer: Any = None,
function_invocation_kwargs: dict[str, Any] | None = None,
client_kwargs: dict[str, Any] | None = None,
**kwargs: Any,
) -> Awaitable[AgentResponse[Any]]: ...
@overload
def run(
self,
messages: AgentRunInputs | None = None,
*,
stream: Literal[True],
session: AgentSession | None = None,
middleware: Sequence[AgentMiddlewareTypes] | None = None,
options: OptionsT | None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
compaction_strategy: Any = None,
tokenizer: Any = None,
function_invocation_kwargs: dict[str, Any] | None = None,
client_kwargs: dict[str, Any] | None = None,
**kwargs: Any,
) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ...
def run( # pyright: ignore[reportIncompatibleMethodOverride]
self,
messages: AgentRunInputs | None = None,
*,
stream: bool = False,
session: AgentSession | None = None,
middleware: Sequence[AgentMiddlewareTypes] | None = None,
options: OptionsT | None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
compaction_strategy: Any = None,
tokenizer: Any = None,
function_invocation_kwargs: dict[str, Any] | None = None,
client_kwargs: dict[str, Any] | None = None,
**kwargs: Any,
) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]:
"""Run the Claude agent with telemetry enabled."""
super_run = cast(
"Callable[..., Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]]",
super().run,
)
return super_run(
messages=messages,
stream=stream,
session=session,
middleware=middleware,
options=options,
tools=tools,
compaction_strategy=compaction_strategy,
tokenizer=tokenizer,
function_invocation_kwargs=function_invocation_kwargs,
client_kwargs=client_kwargs,
**kwargs,
)
+99
View File
@@ -0,0 +1,99 @@
[project]
name = "agent-framework-claude"
description = "Claude Agent SDK integration for Microsoft Agent Framework."
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
readme = "README.md"
requires-python = ">=3.10"
version = "1.0.0b260709"
license-files = ["LICENSE"]
urls.homepage = "https://aka.ms/agent-framework"
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
urls.release_notes = "https://github.com/microsoft/agent-framework/releases?q=tag%3Apython-1&expanded=true"
urls.issues = "https://github.com/microsoft/agent-framework/issues"
classifiers = [
"License :: OSI Approved :: MIT License",
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Typing :: Typed",
]
dependencies = [
"agent-framework-core>=1.11.0,<2",
"claude-agent-sdk>=0.1.46,<0.3",
]
[tool.uv]
prerelease = "if-necessary-or-explicit"
environments = [
"sys_platform == 'darwin'",
"sys_platform == 'linux'",
"sys_platform == 'win32'"
]
[tool.uv-dynamic-versioning]
fallback-version = "0.0.0"
[tool.pytest.ini_options]
testpaths = 'tests'
addopts = "-ra -q -r fEX"
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
filterwarnings = [
"ignore:Support for class-based `config` is deprecated:DeprecationWarning:pydantic.*"
]
timeout = 120
markers = [
"integration: marks tests as integration tests that require external services",
]
[tool.ruff]
extend = "../../pyproject.toml"
[tool.coverage.run]
omit = [
"**/__init__.py"
]
[tool.pyright]
extends = "../../pyproject.toml"
include = ["agent_framework_claude"]
exclude = ['tests']
[tool.mypy]
plugins = ['pydantic.mypy']
strict = true
python_version = "3.10"
ignore_missing_imports = true
disallow_untyped_defs = true
no_implicit_optional = true
check_untyped_defs = true
warn_return_any = true
show_error_codes = true
warn_unused_ignores = false
disallow_incomplete_defs = true
disallow_untyped_decorators = true
[tool.bandit]
targets = ["agent_framework_claude"]
exclude_dirs = ["tests"]
[tool.poe]
executor.type = "uv"
include = "../../shared_tasks.toml"
[tool.poe.tasks.mypy]
help = "Run MyPy for this package."
cmd = "mypy --config-file $POE_ROOT/pyproject.toml agent_framework_claude"
[tool.poe.tasks.test]
help = "Run the default unit test suite for this package."
cmd = 'pytest -m "not integration" --cov=agent_framework_claude --cov-report=term-missing:skip-covered tests'
[build-system]
requires = ["flit-core >= 3.11,<4.0"]
build-backend = "flit_core.buildapi"
File diff suppressed because it is too large Load Diff