mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Merge branch 'main' into feat/durable_task
This commit is contained in:
@@ -8,6 +8,10 @@ inputs:
|
|||||||
os:
|
os:
|
||||||
description: The operating system to set up
|
description: The operating system to set up
|
||||||
required: true
|
required: true
|
||||||
|
exclude-packages:
|
||||||
|
description: Space-separated list of packages to exclude from uv sync
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
@@ -19,6 +23,20 @@ runs:
|
|||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-suffix: ${{ inputs.os }}-${{ inputs.python-version }}
|
cache-suffix: ${{ inputs.os }}-${{ inputs.python-version }}
|
||||||
cache-dependency-glob: "**/uv.lock"
|
cache-dependency-glob: "**/uv.lock"
|
||||||
|
- name: Exclude incompatible workspace packages
|
||||||
|
if: ${{ inputs.exclude-packages != '' }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
for pkg in ${{ inputs.exclude-packages }}; do
|
||||||
|
for f in python/packages/*/pyproject.toml; do
|
||||||
|
if grep -q "name = \"$pkg\"" "$f"; then
|
||||||
|
pkg_dir=$(dirname "$f" | sed 's|python/||')
|
||||||
|
echo "Excluding workspace package: $pkg ($pkg_dir)"
|
||||||
|
sed -i.bak '/\[tool\.uv\.workspace\]/a\exclude = ["'"$pkg_dir"'"]' python/pyproject.toml
|
||||||
|
sed -i.bak '/'"$pkg"' = { workspace = true }/d' python/pyproject.toml
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
- name: Install the project
|
- name: Install the project
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.10"]
|
python-version: ["3.11"]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
defaults:
|
defaults:
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.10"]
|
python-version: ["3.11"]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
defaults:
|
defaults:
|
||||||
@@ -84,7 +84,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.10"]
|
python-version: ["3.11"]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
defaults:
|
defaults:
|
||||||
@@ -117,7 +117,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.10"]
|
python-version: ["3.11"]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
defaults:
|
defaults:
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ jobs:
|
|||||||
environment: integration
|
environment: integration
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
env:
|
env:
|
||||||
UV_PYTHON: "3.10"
|
UV_PYTHON: "3.11"
|
||||||
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
||||||
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
||||||
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
os: ${{ runner.os }}
|
os: ${{ runner.os }}
|
||||||
|
exclude-packages: ${{ matrix.python-version == '3.10' && 'agent-framework-github-copilot' || '' }}
|
||||||
env:
|
env:
|
||||||
# Configure a constant location for the uv cache
|
# Configure a constant location for the uv cache
|
||||||
UV_CACHE_DIR: /tmp/.uv-cache
|
UV_CACHE_DIR: /tmp/.uv-cache
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: integration
|
environment: integration
|
||||||
env:
|
env:
|
||||||
UV_PYTHON: "3.10"
|
UV_PYTHON: "3.11"
|
||||||
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
||||||
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
||||||
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: python
|
working-directory: python
|
||||||
env:
|
env:
|
||||||
UV_PYTHON: "3.10"
|
UV_PYTHON: "3.11"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
# Save the PR number to a file since the workflow_run event
|
# Save the PR number to a file since the workflow_run event
|
||||||
|
|||||||
@@ -34,12 +34,13 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
os: ${{ runner.os }}
|
os: ${{ runner.os }}
|
||||||
|
exclude-packages: ${{ matrix.python-version == '3.10' && 'agent-framework-github-copilot' || '' }}
|
||||||
env:
|
env:
|
||||||
# Configure a constant location for the uv cache
|
# Configure a constant location for the uv cache
|
||||||
UV_CACHE_DIR: /tmp/.uv-cache
|
UV_CACHE_DIR: /tmp/.uv-cache
|
||||||
# Unit tests
|
# Unit tests
|
||||||
- name: Run all tests
|
- name: Run all tests
|
||||||
run: uv run poe all-tests
|
run: uv run poe all-tests ${{ matrix.python-version == '3.10' && '--ignore-glob=packages/github_copilot/**' || '' }}
|
||||||
working-directory: ./python
|
working-directory: ./python
|
||||||
|
|
||||||
# Surface failing tests
|
# Surface failing tests
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ When making changes to a package, check if the following need updates:
|
|||||||
- The package's `AGENTS.md` file (adding/removing/renaming public APIs, architecture changes, import path changes)
|
- The package's `AGENTS.md` file (adding/removing/renaming public APIs, architecture changes, import path changes)
|
||||||
- The agent skills in `.github/skills/` if conventions, commands, or workflows change
|
- The agent skills in `.github/skills/` if conventions, commands, or workflows change
|
||||||
|
|
||||||
|
## Pull Request Description Guidance
|
||||||
|
|
||||||
|
When preparing a PR description:
|
||||||
|
- Follow the repository PR template at `.github/pull_request_template.md` and keep its structure/headings.
|
||||||
|
- Describe the net change relative to `main` (this is implied; do not call it out explicitly as "vs main").
|
||||||
|
- Do not add ad-hoc validation sections (for example, "Validation" or "Tests run"); CI/CD and the template checklist cover validation status.
|
||||||
|
|
||||||
## Quick Reference
|
## Quick Reference
|
||||||
|
|
||||||
Run `uv run poe` from the `python/` directory to see available commands. See [DEV_SETUP.md](DEV_SETUP.md) for detailed usage.
|
Run `uv run poe` from the `python/` directory to see available commands. See [DEV_SETUP.md](DEV_SETUP.md) for detailed usage.
|
||||||
|
|||||||
@@ -37,9 +37,8 @@ from agent_framework.openai._responses_client import RawOpenAIResponsesClient
|
|||||||
from azure.ai.projects.aio import AIProjectClient
|
from azure.ai.projects.aio import AIProjectClient
|
||||||
from azure.ai.projects.models import (
|
from azure.ai.projects.models import (
|
||||||
ApproximateLocation,
|
ApproximateLocation,
|
||||||
CodeInterpreterContainerAuto,
|
AutoCodeInterpreterToolParam,
|
||||||
CodeInterpreterTool,
|
CodeInterpreterTool,
|
||||||
FoundryFeaturesOptInKeys,
|
|
||||||
ImageGenTool,
|
ImageGenTool,
|
||||||
MCPTool,
|
MCPTool,
|
||||||
PromptAgentDefinition,
|
PromptAgentDefinition,
|
||||||
@@ -66,7 +65,6 @@ if sys.version_info >= (3, 11):
|
|||||||
else:
|
else:
|
||||||
from typing_extensions import Self, TypedDict # type: ignore # pragma: no cover
|
from typing_extensions import Self, TypedDict # type: ignore # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger("agent_framework.azure")
|
logger = logging.getLogger("agent_framework.azure")
|
||||||
|
|
||||||
|
|
||||||
@@ -79,9 +77,6 @@ class AzureAIProjectAgentOptions(OpenAIResponsesOptions, total=False):
|
|||||||
reasoning: Reasoning # type: ignore[misc]
|
reasoning: Reasoning # type: ignore[misc]
|
||||||
"""Configuration for enabling reasoning capabilities (requires azure.ai.projects.models.Reasoning)."""
|
"""Configuration for enabling reasoning capabilities (requires azure.ai.projects.models.Reasoning)."""
|
||||||
|
|
||||||
foundry_features: FoundryFeaturesOptInKeys | str
|
|
||||||
"""Optional Foundry preview feature opt-in for agent version creation."""
|
|
||||||
|
|
||||||
|
|
||||||
AzureAIClientOptionsT = TypeVar(
|
AzureAIClientOptionsT = TypeVar(
|
||||||
"AzureAIClientOptionsT",
|
"AzureAIClientOptionsT",
|
||||||
@@ -123,6 +118,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
|||||||
model_deployment_name: str | None = None,
|
model_deployment_name: str | None = None,
|
||||||
credential: AzureCredentialTypes | None = None,
|
credential: AzureCredentialTypes | None = None,
|
||||||
use_latest_version: bool | None = None,
|
use_latest_version: bool | None = None,
|
||||||
|
allow_preview: bool | None = None,
|
||||||
env_file_path: str | None = None,
|
env_file_path: str | None = None,
|
||||||
env_file_encoding: str | None = None,
|
env_file_encoding: str | None = None,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
@@ -148,6 +144,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
|||||||
AsyncTokenCredential, or a callable token provider.
|
AsyncTokenCredential, or a callable token provider.
|
||||||
use_latest_version: Boolean flag that indicates whether to use latest agent version
|
use_latest_version: Boolean flag that indicates whether to use latest agent version
|
||||||
if it exists in the service.
|
if it exists in the service.
|
||||||
|
allow_preview: Enables preview opt-in on internally-created ``AIProjectClient``.
|
||||||
env_file_path: Path to environment file for loading settings.
|
env_file_path: Path to environment file for loading settings.
|
||||||
env_file_encoding: Encoding of the environment file.
|
env_file_encoding: Encoding of the environment file.
|
||||||
kwargs: Additional keyword arguments passed to the parent class.
|
kwargs: Additional keyword arguments passed to the parent class.
|
||||||
@@ -208,11 +205,14 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
|||||||
# Use provided credential
|
# Use provided credential
|
||||||
if not credential:
|
if not credential:
|
||||||
raise ValueError("Azure credential is required when project_client is not provided.")
|
raise ValueError("Azure credential is required when project_client is not provided.")
|
||||||
project_client = AIProjectClient(
|
project_client_kwargs: dict[str, Any] = {
|
||||||
endpoint=resolved_endpoint,
|
"endpoint": resolved_endpoint,
|
||||||
credential=credential, # type: ignore[arg-type]
|
"credential": credential, # type: ignore[arg-type]
|
||||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
"user_agent": AGENT_FRAMEWORK_USER_AGENT,
|
||||||
)
|
}
|
||||||
|
if allow_preview is not None:
|
||||||
|
project_client_kwargs["allow_preview"] = allow_preview
|
||||||
|
project_client = AIProjectClient(**project_client_kwargs)
|
||||||
should_close_client = True
|
should_close_client = True
|
||||||
|
|
||||||
# Initialize parent
|
# Initialize parent
|
||||||
@@ -413,8 +413,6 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
|||||||
"definition": PromptAgentDefinition(**args),
|
"definition": PromptAgentDefinition(**args),
|
||||||
"description": self.agent_description,
|
"description": self.agent_description,
|
||||||
}
|
}
|
||||||
if foundry_features := run_options.get("foundry_features"):
|
|
||||||
create_version_kwargs["foundry_features"] = foundry_features
|
|
||||||
|
|
||||||
created_agent = await self.project_client.agents.create_version(**create_version_kwargs)
|
created_agent = await self.project_client.agents.create_version(**create_version_kwargs)
|
||||||
|
|
||||||
@@ -513,7 +511,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
|||||||
"temperature": ("temperature",),
|
"temperature": ("temperature",),
|
||||||
"top_p": ("top_p",),
|
"top_p": ("top_p",),
|
||||||
"reasoning": ("reasoning",),
|
"reasoning": ("reasoning",),
|
||||||
"foundry_features": ("foundry_features",),
|
"allow_preview": ("allow_preview",),
|
||||||
}
|
}
|
||||||
|
|
||||||
for run_keys in agent_level_option_to_run_keys.values():
|
for run_keys in agent_level_option_to_run_keys.values():
|
||||||
@@ -939,7 +937,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
|||||||
if file_ids is None and isinstance(container, dict):
|
if file_ids is None and isinstance(container, dict):
|
||||||
file_ids = container.get("file_ids")
|
file_ids = container.get("file_ids")
|
||||||
resolved = resolve_file_ids(file_ids)
|
resolved = resolve_file_ids(file_ids)
|
||||||
tool_container = CodeInterpreterContainerAuto(file_ids=resolved)
|
tool_container = AutoCodeInterpreterToolParam(file_ids=resolved)
|
||||||
return CodeInterpreterTool(container=tool_container, **kwargs)
|
return CodeInterpreterTool(container=tool_container, **kwargs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -1244,6 +1242,7 @@ class AzureAIClient(
|
|||||||
model_deployment_name: str | None = None,
|
model_deployment_name: str | None = None,
|
||||||
credential: AzureCredentialTypes | None = None,
|
credential: AzureCredentialTypes | None = None,
|
||||||
use_latest_version: bool | None = None,
|
use_latest_version: bool | None = None,
|
||||||
|
allow_preview: bool | None = None,
|
||||||
middleware: Sequence[ChatAndFunctionMiddlewareTypes] | None = None,
|
middleware: Sequence[ChatAndFunctionMiddlewareTypes] | None = None,
|
||||||
function_invocation_configuration: FunctionInvocationConfiguration | None = None,
|
function_invocation_configuration: FunctionInvocationConfiguration | None = None,
|
||||||
env_file_path: str | None = None,
|
env_file_path: str | None = None,
|
||||||
@@ -1268,6 +1267,7 @@ class AzureAIClient(
|
|||||||
or AsyncTokenCredential.
|
or AsyncTokenCredential.
|
||||||
use_latest_version: Boolean flag that indicates whether to use latest agent version
|
use_latest_version: Boolean flag that indicates whether to use latest agent version
|
||||||
if it exists in the service.
|
if it exists in the service.
|
||||||
|
allow_preview: Enables preview opt-in on internally-created ``AIProjectClient``
|
||||||
middleware: Optional sequence of chat middlewares to include.
|
middleware: Optional sequence of chat middlewares to include.
|
||||||
function_invocation_configuration: Optional function invocation configuration.
|
function_invocation_configuration: Optional function invocation configuration.
|
||||||
env_file_path: Path to environment file for loading settings.
|
env_file_path: Path to environment file for loading settings.
|
||||||
@@ -1318,6 +1318,7 @@ class AzureAIClient(
|
|||||||
model_deployment_name=model_deployment_name,
|
model_deployment_name=model_deployment_name,
|
||||||
credential=credential,
|
credential=credential,
|
||||||
use_latest_version=use_latest_version,
|
use_latest_version=use_latest_version,
|
||||||
|
allow_preview=allow_preview,
|
||||||
middleware=middleware,
|
middleware=middleware,
|
||||||
function_invocation_configuration=function_invocation_configuration,
|
function_invocation_configuration=function_invocation_configuration,
|
||||||
env_file_path=env_file_path,
|
env_file_path=env_file_path,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from agent_framework._sessions import AgentSession, BaseContextProvider, Session
|
|||||||
from agent_framework._settings import load_settings
|
from agent_framework._settings import load_settings
|
||||||
from agent_framework.azure._entra_id_authentication import AzureCredentialTypes
|
from agent_framework.azure._entra_id_authentication import AzureCredentialTypes
|
||||||
from azure.ai.projects.aio import AIProjectClient
|
from azure.ai.projects.aio import AIProjectClient
|
||||||
|
from openai.types.responses import ResponseInputItemParam
|
||||||
|
|
||||||
from ._shared import AzureAISettings
|
from ._shared import AzureAISettings
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ class FoundryMemoryProvider(BaseContextProvider):
|
|||||||
project_client: AIProjectClient | None = None,
|
project_client: AIProjectClient | None = None,
|
||||||
project_endpoint: str | None = None,
|
project_endpoint: str | None = None,
|
||||||
credential: AzureCredentialTypes | None = None,
|
credential: AzureCredentialTypes | None = None,
|
||||||
|
allow_preview: bool | None = None,
|
||||||
memory_store_name: str,
|
memory_store_name: str,
|
||||||
scope: str | None = None,
|
scope: str | None = None,
|
||||||
context_prompt: str | None = None,
|
context_prompt: str | None = None,
|
||||||
@@ -74,6 +76,7 @@ class FoundryMemoryProvider(BaseContextProvider):
|
|||||||
credential: Azure credential for authentication. Accepts a TokenCredential,
|
credential: Azure credential for authentication. Accepts a TokenCredential,
|
||||||
AsyncTokenCredential, or a callable token provider.
|
AsyncTokenCredential, or a callable token provider.
|
||||||
Required when project_client is not provided.
|
Required when project_client is not provided.
|
||||||
|
allow_preview: Enables preview opt-in on internally-created ``AIProjectClient``.
|
||||||
memory_store_name: The name of the memory store to use.
|
memory_store_name: The name of the memory store to use.
|
||||||
scope: The namespace that logically groups and isolates memories (e.g., user ID).
|
scope: The namespace that logically groups and isolates memories (e.g., user ID).
|
||||||
If None, `session_id` will be used.
|
If None, `session_id` will be used.
|
||||||
@@ -100,11 +103,14 @@ class FoundryMemoryProvider(BaseContextProvider):
|
|||||||
)
|
)
|
||||||
if not credential:
|
if not credential:
|
||||||
raise ValueError("Azure credential is required when project_client is not provided.")
|
raise ValueError("Azure credential is required when project_client is not provided.")
|
||||||
project_client = AIProjectClient(
|
project_client_kwargs: dict[str, Any] = {
|
||||||
endpoint=resolved_endpoint,
|
"endpoint": resolved_endpoint,
|
||||||
credential=credential, # type: ignore[arg-type]
|
"credential": credential, # type: ignore[arg-type]
|
||||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
"user_agent": AGENT_FRAMEWORK_USER_AGENT,
|
||||||
)
|
}
|
||||||
|
if allow_preview is not None:
|
||||||
|
project_client_kwargs["allow_preview"] = allow_preview
|
||||||
|
project_client = AIProjectClient(**project_client_kwargs)
|
||||||
|
|
||||||
if not memory_store_name:
|
if not memory_store_name:
|
||||||
raise ValueError("memory_store_name is required")
|
raise ValueError("memory_store_name is required")
|
||||||
@@ -169,8 +175,8 @@ class FoundryMemoryProvider(BaseContextProvider):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Convert input messages to memory search item format
|
# Convert input messages to memory search item format
|
||||||
items = [
|
items: list[ResponseInputItemParam] = [
|
||||||
{"type": "text", "text": msg.text}
|
{"type": "message", "role": "user", "content": msg.text}
|
||||||
for msg in context.input_messages
|
for msg in context.input_messages
|
||||||
if msg and msg.text and msg.text.strip()
|
if msg and msg.text and msg.text.strip()
|
||||||
]
|
]
|
||||||
@@ -224,7 +230,7 @@ class FoundryMemoryProvider(BaseContextProvider):
|
|||||||
messages_to_store.extend(context.response.messages)
|
messages_to_store.extend(context.response.messages)
|
||||||
|
|
||||||
# Filter and convert messages to memory update item format
|
# Filter and convert messages to memory update item format
|
||||||
items: list[dict[str, str]] = []
|
items: list[ResponseInputItemParam] = []
|
||||||
for message in messages_to_store:
|
for message in messages_to_store:
|
||||||
if message.role in {"user", "assistant", "system"} and message.text and message.text.strip():
|
if message.role in {"user", "assistant", "system"} and message.text and message.text.strip():
|
||||||
if message.role == "user":
|
if message.role == "user":
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
|||||||
project_endpoint: str | None = None,
|
project_endpoint: str | None = None,
|
||||||
model: str | None = None,
|
model: str | None = None,
|
||||||
credential: AzureCredentialTypes | None = None,
|
credential: AzureCredentialTypes | None = None,
|
||||||
|
allow_preview: bool | None = None,
|
||||||
env_file_path: str | None = None,
|
env_file_path: str | None = None,
|
||||||
env_file_encoding: str | None = None,
|
env_file_encoding: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -117,6 +118,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
|||||||
credential: Azure credential for authentication. Accepts a TokenCredential,
|
credential: Azure credential for authentication. Accepts a TokenCredential,
|
||||||
AsyncTokenCredential, or a callable token provider.
|
AsyncTokenCredential, or a callable token provider.
|
||||||
Required when project_client is not provided.
|
Required when project_client is not provided.
|
||||||
|
allow_preview: Enables preview opt-in on internally-created ``AIProjectClient``.
|
||||||
env_file_path: Path to environment file for loading settings.
|
env_file_path: Path to environment file for loading settings.
|
||||||
env_file_encoding: Encoding of the environment file.
|
env_file_encoding: Encoding of the environment file.
|
||||||
|
|
||||||
@@ -146,11 +148,14 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
|||||||
if not credential:
|
if not credential:
|
||||||
raise ValueError("Azure credential is required when project_client is not provided.")
|
raise ValueError("Azure credential is required when project_client is not provided.")
|
||||||
|
|
||||||
project_client = AIProjectClient(
|
project_client_kwargs: dict[str, Any] = {
|
||||||
endpoint=resolved_endpoint,
|
"endpoint": resolved_endpoint,
|
||||||
credential=credential, # type: ignore[arg-type]
|
"credential": credential, # type: ignore[arg-type]
|
||||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
"user_agent": AGENT_FRAMEWORK_USER_AGENT,
|
||||||
)
|
}
|
||||||
|
if allow_preview is not None:
|
||||||
|
project_client_kwargs["allow_preview"] = allow_preview
|
||||||
|
project_client = AIProjectClient(**project_client_kwargs)
|
||||||
self._should_close_client = True
|
self._should_close_client = True
|
||||||
|
|
||||||
self._project_client = project_client
|
self._project_client = project_client
|
||||||
@@ -199,7 +204,6 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
|||||||
response_format = opts.get("response_format")
|
response_format = opts.get("response_format")
|
||||||
rai_config = opts.get("rai_config")
|
rai_config = opts.get("rai_config")
|
||||||
reasoning = opts.get("reasoning")
|
reasoning = opts.get("reasoning")
|
||||||
foundry_features = opts.get("foundry_features")
|
|
||||||
|
|
||||||
args: dict[str, Any] = {"model": resolved_model}
|
args: dict[str, Any] = {"model": resolved_model}
|
||||||
|
|
||||||
@@ -246,8 +250,6 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
|||||||
"definition": PromptAgentDefinition(**args),
|
"definition": PromptAgentDefinition(**args),
|
||||||
"description": description,
|
"description": description,
|
||||||
}
|
}
|
||||||
if foundry_features:
|
|
||||||
create_version_kwargs["foundry_features"] = foundry_features
|
|
||||||
|
|
||||||
created_agent = await self._project_client.agents.create_version(**create_version_kwargs)
|
created_agent = await self._project_client.agents.create_version(**create_version_kwargs)
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ from azure.ai.agents.models import (
|
|||||||
from azure.ai.projects.models import (
|
from azure.ai.projects.models import (
|
||||||
CodeInterpreterTool,
|
CodeInterpreterTool,
|
||||||
MCPTool,
|
MCPTool,
|
||||||
TextResponseFormatConfigurationResponseFormatJsonObject,
|
TextResponseFormatJsonObject,
|
||||||
TextResponseFormatConfigurationResponseFormatText,
|
|
||||||
TextResponseFormatJsonSchema,
|
TextResponseFormatJsonSchema,
|
||||||
|
TextResponseFormatText,
|
||||||
Tool,
|
Tool,
|
||||||
WebSearchPreviewTool,
|
WebSearchPreviewTool,
|
||||||
)
|
)
|
||||||
@@ -479,11 +479,7 @@ def _prepare_mcp_tool_dict_for_azure_ai(tool_dict: dict[str, Any]) -> MCPTool:
|
|||||||
|
|
||||||
def create_text_format_config(
|
def create_text_format_config(
|
||||||
response_format: type[BaseModel] | Mapping[str, Any],
|
response_format: type[BaseModel] | Mapping[str, Any],
|
||||||
) -> (
|
) -> TextResponseFormatJsonSchema | TextResponseFormatJsonObject | TextResponseFormatText:
|
||||||
TextResponseFormatJsonSchema
|
|
||||||
| TextResponseFormatConfigurationResponseFormatJsonObject
|
|
||||||
| TextResponseFormatConfigurationResponseFormatText
|
|
||||||
):
|
|
||||||
"""Convert response_format into Azure text format configuration."""
|
"""Convert response_format into Azure text format configuration."""
|
||||||
if isinstance(response_format, type) and issubclass(response_format, BaseModel):
|
if isinstance(response_format, type) and issubclass(response_format, BaseModel):
|
||||||
schema = response_format.model_json_schema()
|
schema = response_format.model_json_schema()
|
||||||
@@ -513,9 +509,9 @@ def create_text_format_config(
|
|||||||
config_kwargs["description"] = format_config["description"]
|
config_kwargs["description"] = format_config["description"]
|
||||||
return TextResponseFormatJsonSchema(**config_kwargs)
|
return TextResponseFormatJsonSchema(**config_kwargs)
|
||||||
if format_type == "json_object":
|
if format_type == "json_object":
|
||||||
return TextResponseFormatConfigurationResponseFormatJsonObject()
|
return TextResponseFormatJsonObject()
|
||||||
if format_type == "text":
|
if format_type == "text":
|
||||||
return TextResponseFormatConfigurationResponseFormatText()
|
return TextResponseFormatText()
|
||||||
|
|
||||||
raise IntegrationInvalidRequestException("response_format must be a Pydantic model or mapping.")
|
raise IntegrationInvalidRequestException("response_format must be a Pydantic model or mapping.")
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from agent_framework.openai._responses_client import RawOpenAIResponsesClient
|
|||||||
from azure.ai.projects.aio import AIProjectClient
|
from azure.ai.projects.aio import AIProjectClient
|
||||||
from azure.ai.projects.models import (
|
from azure.ai.projects.models import (
|
||||||
ApproximateLocation,
|
ApproximateLocation,
|
||||||
CodeInterpreterContainerAuto,
|
AutoCodeInterpreterToolParam,
|
||||||
CodeInterpreterTool,
|
CodeInterpreterTool,
|
||||||
FileSearchTool,
|
FileSearchTool,
|
||||||
ImageGenTool,
|
ImageGenTool,
|
||||||
@@ -1296,7 +1296,7 @@ def test_from_azure_ai_tools_mcp() -> None:
|
|||||||
|
|
||||||
def test_from_azure_ai_tools_code_interpreter() -> None:
|
def test_from_azure_ai_tools_code_interpreter() -> None:
|
||||||
"""Test from_azure_ai_tools with Code Interpreter tool."""
|
"""Test from_azure_ai_tools with Code Interpreter tool."""
|
||||||
ci_tool = CodeInterpreterTool(container=CodeInterpreterContainerAuto(file_ids=["file-1"]))
|
ci_tool = CodeInterpreterTool(container=AutoCodeInterpreterToolParam(file_ids=["file-1"]))
|
||||||
parsed_tools = from_azure_ai_tools([ci_tool])
|
parsed_tools = from_azure_ai_tools([ci_tool])
|
||||||
assert len(parsed_tools) == 1
|
assert len(parsed_tools) == 1
|
||||||
assert parsed_tools[0]["type"] == "code_interpreter"
|
assert parsed_tools[0]["type"] == "code_interpreter"
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ class TestInit:
|
|||||||
provider = FoundryMemoryProvider(
|
provider = FoundryMemoryProvider(
|
||||||
project_endpoint="https://test.project.endpoint",
|
project_endpoint="https://test.project.endpoint",
|
||||||
credential=mock_credential, # type: ignore[arg-type]
|
credential=mock_credential, # type: ignore[arg-type]
|
||||||
|
allow_preview=True,
|
||||||
memory_store_name="test_store",
|
memory_store_name="test_store",
|
||||||
scope="user_123",
|
scope="user_123",
|
||||||
)
|
)
|
||||||
@@ -93,6 +94,7 @@ class TestInit:
|
|||||||
mock_ai_project_client.assert_called_once_with(
|
mock_ai_project_client.assert_called_once_with(
|
||||||
endpoint="https://test.project.endpoint",
|
endpoint="https://test.project.endpoint",
|
||||||
credential=mock_credential,
|
credential=mock_credential,
|
||||||
|
allow_preview=True,
|
||||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -112,9 +112,7 @@ class SkillResource:
|
|||||||
self._accepts_kwargs: bool = False
|
self._accepts_kwargs: bool = False
|
||||||
if function is not None:
|
if function is not None:
|
||||||
sig = inspect.signature(function)
|
sig = inspect.signature(function)
|
||||||
self._accepts_kwargs = any(
|
self._accepts_kwargs = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values())
|
||||||
p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Skill:
|
class Skill:
|
||||||
|
|||||||
@@ -1458,7 +1458,7 @@ def _update_conversation_id(
|
|||||||
if conversation_id is None:
|
if conversation_id is None:
|
||||||
return
|
return
|
||||||
if "chat_options" in kwargs:
|
if "chat_options" in kwargs:
|
||||||
kwargs["chat_options"].conversation_id = conversation_id
|
kwargs["chat_options"]["conversation_id"] = conversation_id
|
||||||
else:
|
else:
|
||||||
kwargs["conversation_id"] = conversation_id
|
kwargs["conversation_id"] = conversation_id
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ class AzureOpenAIResponsesClient( # type: ignore[misc]
|
|||||||
async_client: AsyncOpenAI | None = None,
|
async_client: AsyncOpenAI | None = None,
|
||||||
project_client: Any | None = None,
|
project_client: Any | None = None,
|
||||||
project_endpoint: str | None = None,
|
project_endpoint: str | None = None,
|
||||||
|
allow_preview: bool | None = None,
|
||||||
env_file_path: str | None = None,
|
env_file_path: str | None = None,
|
||||||
env_file_encoding: str | None = None,
|
env_file_encoding: str | None = None,
|
||||||
instruction_role: str | None = None,
|
instruction_role: str | None = None,
|
||||||
@@ -120,6 +121,7 @@ class AzureOpenAIResponsesClient( # type: ignore[misc]
|
|||||||
project_endpoint: The Azure AI Foundry project endpoint URL.
|
project_endpoint: The Azure AI Foundry project endpoint URL.
|
||||||
When provided with ``credential``, an ``AIProjectClient`` will be created
|
When provided with ``credential``, an ``AIProjectClient`` will be created
|
||||||
and used to obtain the OpenAI client. Requires the ``azure-ai-projects`` package.
|
and used to obtain the OpenAI client. Requires the ``azure-ai-projects`` package.
|
||||||
|
allow_preview: Enables preview opt-in on internally-created ``AIProjectClient``.
|
||||||
env_file_path: Use the environment settings file as a fallback to using env vars.
|
env_file_path: Use the environment settings file as a fallback to using env vars.
|
||||||
env_file_encoding: The encoding of the environment settings file, defaults to 'utf-8'.
|
env_file_encoding: The encoding of the environment settings file, defaults to 'utf-8'.
|
||||||
instruction_role: The role to use for 'instruction' messages, for example, summarization
|
instruction_role: The role to use for 'instruction' messages, for example, summarization
|
||||||
@@ -189,6 +191,7 @@ class AzureOpenAIResponsesClient( # type: ignore[misc]
|
|||||||
project_client=project_client,
|
project_client=project_client,
|
||||||
project_endpoint=project_endpoint,
|
project_endpoint=project_endpoint,
|
||||||
credential=credential,
|
credential=credential,
|
||||||
|
allow_preview=allow_preview,
|
||||||
)
|
)
|
||||||
|
|
||||||
azure_openai_settings = load_settings(
|
azure_openai_settings = load_settings(
|
||||||
@@ -246,21 +249,9 @@ class AzureOpenAIResponsesClient( # type: ignore[misc]
|
|||||||
project_client: AIProjectClient | None,
|
project_client: AIProjectClient | None,
|
||||||
project_endpoint: str | None,
|
project_endpoint: str | None,
|
||||||
credential: AzureCredentialTypes | AzureTokenProvider | None,
|
credential: AzureCredentialTypes | AzureTokenProvider | None,
|
||||||
|
allow_preview: bool | None = None,
|
||||||
) -> AsyncOpenAI:
|
) -> AsyncOpenAI:
|
||||||
"""Create an AsyncOpenAI client from an Azure AI Foundry project.
|
"""Create an AsyncOpenAI client from an Azure AI Foundry project."""
|
||||||
|
|
||||||
Args:
|
|
||||||
project_client: An existing AIProjectClient to use.
|
|
||||||
project_endpoint: The Azure AI Foundry project endpoint URL.
|
|
||||||
credential: Azure credential for authentication.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
An AsyncAzureOpenAI client obtained from the project client.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If required parameters are missing or
|
|
||||||
the azure-ai-projects package is not installed.
|
|
||||||
"""
|
|
||||||
if project_client is not None:
|
if project_client is not None:
|
||||||
return project_client.get_openai_client()
|
return project_client.get_openai_client()
|
||||||
|
|
||||||
@@ -268,11 +259,14 @@ class AzureOpenAIResponsesClient( # type: ignore[misc]
|
|||||||
raise ValueError("Azure AI project endpoint is required when project_client is not provided.")
|
raise ValueError("Azure AI project endpoint is required when project_client is not provided.")
|
||||||
if not credential:
|
if not credential:
|
||||||
raise ValueError("Azure credential is required when using project_endpoint without a project_client.")
|
raise ValueError("Azure credential is required when using project_endpoint without a project_client.")
|
||||||
project_client = AIProjectClient(
|
project_client_kwargs: dict[str, Any] = {
|
||||||
endpoint=project_endpoint,
|
"endpoint": project_endpoint,
|
||||||
credential=credential, # type: ignore[arg-type]
|
"credential": credential, # type: ignore[arg-type]
|
||||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
"user_agent": AGENT_FRAMEWORK_USER_AGENT,
|
||||||
)
|
}
|
||||||
|
if allow_preview is not None:
|
||||||
|
project_client_kwargs["allow_preview"] = allow_preview
|
||||||
|
project_client = AIProjectClient(**project_client_kwargs)
|
||||||
return project_client.get_openai_client()
|
return project_client.get_openai_client()
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -327,7 +327,9 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
|||||||
messages = prepend_instructions_to_messages(list(messages), instructions, role="system")
|
messages = prepend_instructions_to_messages(list(messages), instructions, role="system")
|
||||||
|
|
||||||
# Start with a copy of options
|
# Start with a copy of options
|
||||||
run_options = {k: v for k, v in options.items() if v is not None and k not in {"instructions", "tools"}}
|
run_options = {
|
||||||
|
k: v for k, v in options.items() if v is not None and k not in {"instructions", "tools", "conversation_id"}
|
||||||
|
}
|
||||||
|
|
||||||
# messages
|
# messages
|
||||||
if messages and "messages" not in run_options:
|
if messages and "messages" not in run_options:
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ dependencies = [
|
|||||||
# connectors and functions
|
# connectors and functions
|
||||||
"openai>=1.99.0",
|
"openai>=1.99.0",
|
||||||
"azure-identity>=1,<2",
|
"azure-identity>=1,<2",
|
||||||
"azure-ai-projects == 2.0.0b4",
|
"azure-ai-projects>=2.0.0,<3.0",
|
||||||
"mcp[ws]>=1.24.0,<2",
|
"mcp[ws]>=1.24.0,<2",
|
||||||
"packaging>=24.1",
|
"packaging>=24.1",
|
||||||
]
|
]
|
||||||
@@ -55,7 +55,7 @@ all = [
|
|||||||
"agent-framework-devui",
|
"agent-framework-devui",
|
||||||
"agent-framework-durabletask",
|
"agent-framework-durabletask",
|
||||||
"agent-framework-foundry-local",
|
"agent-framework-foundry-local",
|
||||||
"agent-framework-github-copilot",
|
"agent-framework-github-copilot; python_version >= '3.11'",
|
||||||
"agent-framework-lab",
|
"agent-framework-lab",
|
||||||
"agent-framework-mem0",
|
"agent-framework-mem0",
|
||||||
"agent-framework-ollama",
|
"agent-framework-ollama",
|
||||||
|
|||||||
@@ -626,6 +626,73 @@ async def test_streaming_with_none_delta(
|
|||||||
assert any(msg.contents for msg in results)
|
assert any(msg.contents for msg in results)
|
||||||
|
|
||||||
|
|
||||||
|
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
|
||||||
|
async def test_cmc_with_conversation_id(
|
||||||
|
mock_create: AsyncMock,
|
||||||
|
azure_openai_unit_test_env: dict[str, str],
|
||||||
|
chat_history: list[Message],
|
||||||
|
mock_chat_completion_response: ChatCompletion,
|
||||||
|
) -> None:
|
||||||
|
"""Test that conversation_id is excluded from the completions create call."""
|
||||||
|
mock_create.return_value = mock_chat_completion_response
|
||||||
|
chat_history.append(Message(text="hello world", role="user"))
|
||||||
|
|
||||||
|
azure_chat_client = AzureOpenAIChatClient()
|
||||||
|
await azure_chat_client.get_response(
|
||||||
|
messages=chat_history,
|
||||||
|
options={"conversation_id": "12345"},
|
||||||
|
)
|
||||||
|
|
||||||
|
call_kwargs = mock_create.call_args.kwargs
|
||||||
|
assert "conversation_id" not in call_kwargs
|
||||||
|
|
||||||
|
|
||||||
|
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
|
||||||
|
async def test_cmc_streaming_with_conversation_id(
|
||||||
|
mock_create: AsyncMock,
|
||||||
|
azure_openai_unit_test_env: dict[str, str],
|
||||||
|
chat_history: list[Message],
|
||||||
|
mock_streaming_chat_completion_response: AsyncStream[ChatCompletionChunk],
|
||||||
|
) -> None:
|
||||||
|
"""Test that conversation_id is excluded from the streaming completions create call."""
|
||||||
|
mock_create.return_value = mock_streaming_chat_completion_response
|
||||||
|
chat_history.append(Message(text="hello world", role="user"))
|
||||||
|
|
||||||
|
azure_chat_client = AzureOpenAIChatClient()
|
||||||
|
async for _ in azure_chat_client.get_response(
|
||||||
|
messages=chat_history,
|
||||||
|
options={"conversation_id": "12345"},
|
||||||
|
stream=True,
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
call_kwargs = mock_create.call_args.kwargs
|
||||||
|
assert "conversation_id" not in call_kwargs
|
||||||
|
|
||||||
|
|
||||||
|
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
|
||||||
|
async def test_cmc_agent_with_service_session_id(
|
||||||
|
mock_create: AsyncMock,
|
||||||
|
azure_openai_unit_test_env: dict[str, str],
|
||||||
|
mock_chat_completion_response: ChatCompletion,
|
||||||
|
) -> None:
|
||||||
|
"""Test that agent.run() with a session containing service_session_id works correctly."""
|
||||||
|
mock_create.return_value = mock_chat_completion_response
|
||||||
|
|
||||||
|
azure_chat_client = AzureOpenAIChatClient()
|
||||||
|
agent = azure_chat_client.as_agent(
|
||||||
|
name="TestAgent",
|
||||||
|
instructions="You are a helpful assistant.",
|
||||||
|
)
|
||||||
|
|
||||||
|
session = agent.get_session(service_session_id="12345")
|
||||||
|
response = await agent.run("hello", session=session)
|
||||||
|
|
||||||
|
assert response is not None
|
||||||
|
call_kwargs = mock_create.call_args.kwargs
|
||||||
|
assert "conversation_id" not in call_kwargs
|
||||||
|
|
||||||
|
|
||||||
@tool(approval_mode="never_require")
|
@tool(approval_mode="never_require")
|
||||||
def get_story_text() -> str:
|
def get_story_text() -> str:
|
||||||
"""Returns a story about Emily and David."""
|
"""Returns a story about Emily and David."""
|
||||||
|
|||||||
@@ -3449,3 +3449,66 @@ async def test_streaming_function_calling_response_includes_reasoning_and_tool_r
|
|||||||
reasoning_contents = [c for msg in response.messages for c in msg.contents if c.type == "text_reasoning"]
|
reasoning_contents = [c for msg in response.messages for c in msg.contents if c.type == "text_reasoning"]
|
||||||
assert len(reasoning_contents) >= 1
|
assert len(reasoning_contents) >= 1
|
||||||
assert reasoning_contents[0].id == "rs_test123"
|
assert reasoning_contents[0].id == "rs_test123"
|
||||||
|
|
||||||
|
|
||||||
|
# region _update_conversation_id unit tests
|
||||||
|
|
||||||
|
|
||||||
|
class TestUpdateConversationId:
|
||||||
|
"""Tests for _update_conversation_id handling dict chat_options."""
|
||||||
|
|
||||||
|
def test_chat_options_as_dict(self):
|
||||||
|
"""When chat_options is a plain dict, conversation_id should be set via key access."""
|
||||||
|
from agent_framework._tools import _update_conversation_id
|
||||||
|
|
||||||
|
kwargs: dict[str, Any] = {"chat_options": {}}
|
||||||
|
_update_conversation_id(kwargs, "conv_1")
|
||||||
|
assert kwargs["chat_options"]["conversation_id"] == "conv_1"
|
||||||
|
|
||||||
|
def test_chat_options_as_typed_dict(self):
|
||||||
|
"""When chat_options is a ChatOptions TypedDict, conversation_id should be set via key access."""
|
||||||
|
from agent_framework import ChatOptions
|
||||||
|
from agent_framework._tools import _update_conversation_id
|
||||||
|
|
||||||
|
opts: ChatOptions = {"temperature": 0.5}
|
||||||
|
kwargs: dict[str, Any] = {"chat_options": opts}
|
||||||
|
_update_conversation_id(kwargs, "conv_2")
|
||||||
|
assert kwargs["chat_options"]["conversation_id"] == "conv_2"
|
||||||
|
|
||||||
|
def test_no_chat_options_falls_back_to_kwargs(self):
|
||||||
|
"""When chat_options is absent, conversation_id should be set directly on kwargs."""
|
||||||
|
from agent_framework._tools import _update_conversation_id
|
||||||
|
|
||||||
|
kwargs: dict[str, Any] = {}
|
||||||
|
_update_conversation_id(kwargs, "conv_4")
|
||||||
|
assert kwargs["conversation_id"] == "conv_4"
|
||||||
|
|
||||||
|
def test_none_conversation_id_is_noop(self):
|
||||||
|
"""When conversation_id is None, kwargs should not be modified."""
|
||||||
|
from agent_framework._tools import _update_conversation_id
|
||||||
|
|
||||||
|
kwargs: dict[str, Any] = {"chat_options": {}}
|
||||||
|
_update_conversation_id(kwargs, None)
|
||||||
|
assert "conversation_id" not in kwargs["chat_options"]
|
||||||
|
assert "conversation_id" not in kwargs
|
||||||
|
|
||||||
|
def test_options_dict_also_updated(self):
|
||||||
|
"""The optional options dict should also receive conversation_id."""
|
||||||
|
from agent_framework._tools import _update_conversation_id
|
||||||
|
|
||||||
|
kwargs: dict[str, Any] = {"chat_options": {}}
|
||||||
|
options: dict[str, Any] = {}
|
||||||
|
_update_conversation_id(kwargs, "conv_5", options)
|
||||||
|
assert kwargs["chat_options"]["conversation_id"] == "conv_5"
|
||||||
|
assert options["conversation_id"] == "conv_5"
|
||||||
|
|
||||||
|
def test_dict_overwrites_existing_conversation_id(self):
|
||||||
|
"""When a dict already has a conversation_id, it should be overwritten."""
|
||||||
|
from agent_framework._tools import _update_conversation_id
|
||||||
|
|
||||||
|
kwargs: dict[str, Any] = {"chat_options": {"conversation_id": "old_id"}}
|
||||||
|
_update_conversation_id(kwargs, "new_id")
|
||||||
|
assert kwargs["chat_options"]["conversation_id"] == "new_id"
|
||||||
|
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|||||||
@@ -1161,6 +1161,21 @@ def test_prepare_options_removes_parallel_tool_calls_when_no_tools(openai_unit_t
|
|||||||
assert "parallel_tool_calls" not in prepared_options
|
assert "parallel_tool_calls" not in prepared_options
|
||||||
|
|
||||||
|
|
||||||
|
def test_prepare_options_excludes_conversation_id(openai_unit_test_env: dict[str, str]) -> None:
|
||||||
|
"""Test that conversation_id is excluded from prepared options for chat completions."""
|
||||||
|
client = OpenAIChatClient()
|
||||||
|
|
||||||
|
messages = [Message(role="user", text="test")]
|
||||||
|
options = {"conversation_id": "12345", "temperature": 0.7}
|
||||||
|
|
||||||
|
prepared_options = client._prepare_options(messages, options)
|
||||||
|
|
||||||
|
# conversation_id is not a valid parameter for AsyncCompletions.create()
|
||||||
|
assert "conversation_id" not in prepared_options
|
||||||
|
# Other options should still be present
|
||||||
|
assert prepared_options["temperature"] == 0.7
|
||||||
|
|
||||||
|
|
||||||
async def test_streaming_exception_handling(openai_unit_test_env: dict[str, str]) -> None:
|
async def test_streaming_exception_handling(openai_unit_test_env: dict[str, str]) -> None:
|
||||||
"""Test that streaming errors are properly handled."""
|
"""Test that streaming errors are properly handled."""
|
||||||
client = OpenAIChatClient()
|
client = OpenAIChatClient()
|
||||||
|
|||||||
@@ -544,19 +544,19 @@ class TestFunctionExecutor:
|
|||||||
static_wrapped = staticmethod(my_async_func)
|
static_wrapped = staticmethod(my_async_func)
|
||||||
|
|
||||||
# Direct check on descriptor object fails (this is the bug)
|
# Direct check on descriptor object fails (this is the bug)
|
||||||
assert not asyncio.iscoroutinefunction(static_wrapped)
|
assert not asyncio.iscoroutinefunction(static_wrapped) # type: ignore[reportDeprecated]
|
||||||
assert isinstance(static_wrapped, staticmethod)
|
assert isinstance(static_wrapped, staticmethod)
|
||||||
|
|
||||||
# But unwrapping __func__ reveals the async function
|
# But unwrapping __func__ reveals the async function
|
||||||
unwrapped = static_wrapped.__func__
|
unwrapped = static_wrapped.__func__
|
||||||
assert asyncio.iscoroutinefunction(unwrapped)
|
assert asyncio.iscoroutinefunction(unwrapped) # type: ignore[reportDeprecated]
|
||||||
|
|
||||||
# When accessed via class attribute, Python's descriptor protocol
|
# When accessed via class attribute, Python's descriptor protocol
|
||||||
# automatically unwraps it, so it works:
|
# automatically unwraps it, so it works:
|
||||||
class C:
|
class C:
|
||||||
async_static = static_wrapped
|
async_static = static_wrapped
|
||||||
|
|
||||||
assert asyncio.iscoroutinefunction(C.async_static) # Works via descriptor protocol
|
assert asyncio.iscoroutinefunction(C.async_static) # type: ignore[reportDeprecated] # Works via descriptor protocol
|
||||||
|
|
||||||
|
|
||||||
class TestExecutorExplicitTypes:
|
class TestExecutorExplicitTypes:
|
||||||
|
|||||||
+1
-2
@@ -15,11 +15,10 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from collections.abc import Mapping
|
from collections.abc import Callable, Mapping
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from inspect import isawaitable
|
from inspect import isawaitable
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
from collections.abc import Callable
|
|
||||||
|
|
||||||
from agent_framework import (
|
from agent_framework import (
|
||||||
Content,
|
Content,
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from typing import Any, cast
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
from typing import Any, cast
|
||||||
|
|
||||||
from pydantic import BaseModel, Field, field_validator
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
|
|||||||
@@ -26,12 +26,11 @@ from agent_framework._tools import FunctionTool, ToolTypes
|
|||||||
from agent_framework._types import AgentRunInputs, normalize_tools
|
from agent_framework._types import AgentRunInputs, normalize_tools
|
||||||
from agent_framework.exceptions import AgentException
|
from agent_framework.exceptions import AgentException
|
||||||
from copilot import CopilotClient, CopilotSession
|
from copilot import CopilotClient, CopilotSession
|
||||||
from copilot.generated.session_events import SessionEvent, SessionEventType
|
from copilot.generated.session_events import PermissionRequest, SessionEvent, SessionEventType
|
||||||
from copilot.types import (
|
from copilot.types import (
|
||||||
CopilotClientOptions,
|
CopilotClientOptions,
|
||||||
MCPServerConfig,
|
MCPServerConfig,
|
||||||
MessageOptions,
|
MessageOptions,
|
||||||
PermissionRequest,
|
|
||||||
PermissionRequestResult,
|
PermissionRequestResult,
|
||||||
ResumeSessionConfig,
|
ResumeSessionConfig,
|
||||||
SessionConfig,
|
SessionConfig,
|
||||||
@@ -529,7 +528,7 @@ class GitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|||||||
"""Convert an FunctionTool to a Copilot SDK tool."""
|
"""Convert an FunctionTool to a Copilot SDK tool."""
|
||||||
|
|
||||||
async def handler(invocation: ToolInvocation) -> ToolResult:
|
async def handler(invocation: ToolInvocation) -> ToolResult:
|
||||||
args = invocation.get("arguments", {})
|
args: dict[str, Any] = invocation.arguments or {}
|
||||||
try:
|
try:
|
||||||
if ai_func.input_model:
|
if ai_func.input_model:
|
||||||
args_instance = ai_func.input_model(**args)
|
args_instance = ai_func.input_model(**args)
|
||||||
@@ -537,13 +536,13 @@ class GitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|||||||
else:
|
else:
|
||||||
result = await ai_func.invoke(arguments=args)
|
result = await ai_func.invoke(arguments=args)
|
||||||
return ToolResult(
|
return ToolResult(
|
||||||
textResultForLlm=str(result),
|
text_result_for_llm=str(result),
|
||||||
resultType="success",
|
result_type="success",
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return ToolResult(
|
return ToolResult(
|
||||||
textResultForLlm=f"Error: {e}",
|
text_result_for_llm=f"Error: {e}",
|
||||||
resultType="failure",
|
result_type="failure",
|
||||||
error=str(e),
|
error=str(e),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name = "agent-framework-github-copilot"
|
|||||||
description = "GitHub Copilot integration for Microsoft Agent Framework."
|
description = "GitHub Copilot integration for Microsoft Agent Framework."
|
||||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.11"
|
||||||
version = "1.0.0b260304"
|
version = "1.0.0b260304"
|
||||||
license-files = ["LICENSE"]
|
license-files = ["LICENSE"]
|
||||||
urls.homepage = "https://aka.ms/agent-framework"
|
urls.homepage = "https://aka.ms/agent-framework"
|
||||||
@@ -15,7 +15,6 @@ classifiers = [
|
|||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 4 - Beta",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.10",
|
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
"Programming Language :: Python :: 3.13",
|
"Programming Language :: Python :: 3.13",
|
||||||
@@ -24,7 +23,7 @@ classifiers = [
|
|||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"agent-framework-core>=1.0.0rc3",
|
"agent-framework-core>=1.0.0rc3",
|
||||||
"github-copilot-sdk>=0.1.0",
|
"github-copilot-sdk>=0.1.32",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
@@ -66,7 +65,7 @@ include = ["agent_framework_github_copilot"]
|
|||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
plugins = ['pydantic.mypy']
|
plugins = ['pydantic.mypy']
|
||||||
strict = true
|
strict = true
|
||||||
python_version = "3.10"
|
python_version = "3.11"
|
||||||
ignore_missing_imports = true
|
ignore_missing_imports = true
|
||||||
disallow_untyped_defs = true
|
disallow_untyped_defs = true
|
||||||
no_implicit_optional = true
|
no_implicit_optional = true
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from agent_framework import (
|
|||||||
)
|
)
|
||||||
from agent_framework.exceptions import AgentException
|
from agent_framework.exceptions import AgentException
|
||||||
from copilot.generated.session_events import Data, SessionEvent, SessionEventType
|
from copilot.generated.session_events import Data, SessionEvent, SessionEventType
|
||||||
|
from copilot.types import ToolInvocation, ToolResult
|
||||||
|
|
||||||
from agent_framework_github_copilot import GitHubCopilotAgent, GitHubCopilotOptions
|
from agent_framework_github_copilot import GitHubCopilotAgent, GitHubCopilotOptions
|
||||||
|
|
||||||
@@ -745,10 +746,11 @@ class TestGitHubCopilotAgentToolConversion:
|
|||||||
config = call_args[0][0]
|
config = call_args[0][0]
|
||||||
copilot_tool = config["tools"][0]
|
copilot_tool = config["tools"][0]
|
||||||
|
|
||||||
result = await copilot_tool.handler({"arguments": {"arg": "test"}})
|
result = await copilot_tool.handler(ToolInvocation(arguments={"arg": "test"}))
|
||||||
|
|
||||||
assert result["resultType"] == "success"
|
assert isinstance(result, ToolResult)
|
||||||
assert result["textResultForLlm"] == "Result: test"
|
assert result.result_type == "success"
|
||||||
|
assert result.text_result_for_llm == "Result: test"
|
||||||
|
|
||||||
async def test_tool_handler_returns_failure_result_on_error(
|
async def test_tool_handler_returns_failure_result_on_error(
|
||||||
self,
|
self,
|
||||||
@@ -770,11 +772,61 @@ class TestGitHubCopilotAgentToolConversion:
|
|||||||
config = call_args[0][0]
|
config = call_args[0][0]
|
||||||
copilot_tool = config["tools"][0]
|
copilot_tool = config["tools"][0]
|
||||||
|
|
||||||
result = await copilot_tool.handler({"arguments": {"arg": "test"}})
|
result = await copilot_tool.handler(ToolInvocation(arguments={"arg": "test"}))
|
||||||
|
|
||||||
assert result["resultType"] == "failure"
|
assert isinstance(result, ToolResult)
|
||||||
assert "Something went wrong" in result["textResultForLlm"]
|
assert result.result_type == "failure"
|
||||||
assert "Something went wrong" in result["error"]
|
assert "Something went wrong" in result.text_result_for_llm
|
||||||
|
assert "Something went wrong" in result.error
|
||||||
|
|
||||||
|
async def test_tool_handler_rejects_raw_dict_invocation(
|
||||||
|
self,
|
||||||
|
mock_client: MagicMock,
|
||||||
|
mock_session: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test that tool handler raises TypeError when called with a raw dict instead of ToolInvocation."""
|
||||||
|
|
||||||
|
def my_tool(arg: str) -> str:
|
||||||
|
"""A test tool."""
|
||||||
|
return f"Result: {arg}"
|
||||||
|
|
||||||
|
agent = GitHubCopilotAgent(client=mock_client, tools=[my_tool])
|
||||||
|
await agent.start()
|
||||||
|
|
||||||
|
await agent._get_or_create_session(AgentSession()) # type: ignore
|
||||||
|
|
||||||
|
call_args = mock_client.create_session.call_args
|
||||||
|
config = call_args[0][0]
|
||||||
|
copilot_tool = config["tools"][0]
|
||||||
|
|
||||||
|
with pytest.raises((TypeError, AttributeError)):
|
||||||
|
await copilot_tool.handler({"arguments": {"arg": "test"}})
|
||||||
|
|
||||||
|
async def test_tool_handler_with_empty_arguments(
|
||||||
|
self,
|
||||||
|
mock_client: MagicMock,
|
||||||
|
mock_session: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test that tool handler handles ToolInvocation with empty arguments."""
|
||||||
|
|
||||||
|
def no_args_tool() -> str:
|
||||||
|
"""A tool with no arguments."""
|
||||||
|
return "no args result"
|
||||||
|
|
||||||
|
agent = GitHubCopilotAgent(client=mock_client, tools=[no_args_tool])
|
||||||
|
await agent.start()
|
||||||
|
|
||||||
|
await agent._get_or_create_session(AgentSession()) # type: ignore
|
||||||
|
|
||||||
|
call_args = mock_client.create_session.call_args
|
||||||
|
config = call_args[0][0]
|
||||||
|
copilot_tool = config["tools"][0]
|
||||||
|
|
||||||
|
result = await copilot_tool.handler(ToolInvocation(arguments={}))
|
||||||
|
|
||||||
|
assert isinstance(result, ToolResult)
|
||||||
|
assert result.result_type == "success"
|
||||||
|
assert result.text_result_for_llm == "no args result"
|
||||||
|
|
||||||
def test_copilot_tool_passthrough(
|
def test_copilot_tool_passthrough(
|
||||||
self,
|
self,
|
||||||
@@ -784,7 +836,7 @@ class TestGitHubCopilotAgentToolConversion:
|
|||||||
from copilot.types import Tool as CopilotTool
|
from copilot.types import Tool as CopilotTool
|
||||||
|
|
||||||
async def tool_handler(invocation: Any) -> Any:
|
async def tool_handler(invocation: Any) -> Any:
|
||||||
return {"textResultForLlm": "result", "resultType": "success"}
|
return {"text_result_for_llm": "result", "result_type": "success"}
|
||||||
|
|
||||||
copilot_tool = CopilotTool(
|
copilot_tool = CopilotTool(
|
||||||
name="direct_tool",
|
name="direct_tool",
|
||||||
@@ -813,7 +865,7 @@ class TestGitHubCopilotAgentToolConversion:
|
|||||||
return arg
|
return arg
|
||||||
|
|
||||||
async def tool_handler(invocation: Any) -> Any:
|
async def tool_handler(invocation: Any) -> Any:
|
||||||
return {"textResultForLlm": "result", "resultType": "success"}
|
return {"text_result_for_llm": "result", "result_type": "success"}
|
||||||
|
|
||||||
copilot_tool = CopilotTool(
|
copilot_tool = CopilotTool(
|
||||||
name="direct_tool",
|
name="direct_tool",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Azure AI Agent Examples
|
# Azure AI Agent Examples
|
||||||
|
|
||||||
This folder contains examples demonstrating different ways to create and use agents with the Azure AI client from the `agent_framework.azure` package. These examples use the `AzureAIClient` with the `azure-ai-projects` 2.x (V2) API surface (see [changelog](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/CHANGELOG.md#200b1-2025-11-11)). For V1 (`azure-ai-agents` 1.x) samples using `AzureAIAgentClient`, see the [Azure AI V1 examples folder](../azure_ai_agent/).
|
This folder contains examples demonstrating different ways to create and use agents with the Azure AI client from the `agent_framework.azure` package. These examples use the `AzureAIClient` with the `azure-ai-projects` 2.x (V2) API surface (see [changelog](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/CHANGELOG.md#200b1-2025-11-11)). For V1 (`azure-ai-agents` 1.x) samples using `AzureAIAgentClient`, see the [Azure AI V1 examples folder](../azure_ai_agent/). When using preview-only agent creation features on GA SDK versions, create `AIProjectClient` with `allow_preview=True`.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|||||||
+8
-6
@@ -15,16 +15,18 @@ SECURITY NOTE: Only enable file permissions when you trust the agent's actions.
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from agent_framework.github import GitHubCopilotAgent
|
from agent_framework.github import GitHubCopilotAgent
|
||||||
from copilot.types import PermissionRequest, PermissionRequestResult
|
from copilot.generated.session_events import PermissionRequest
|
||||||
|
from copilot.types import PermissionRequestResult
|
||||||
|
|
||||||
|
|
||||||
def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
|
def prompt_permission(
|
||||||
|
request: PermissionRequest, context: dict[str, str]
|
||||||
|
) -> PermissionRequestResult:
|
||||||
"""Permission handler that prompts the user for approval."""
|
"""Permission handler that prompts the user for approval."""
|
||||||
kind = request.get("kind", "unknown")
|
print(f"\n[Permission Request: {request.kind}]")
|
||||||
print(f"\n[Permission Request: {kind}]")
|
|
||||||
|
|
||||||
if "path" in request:
|
if request.path is not None:
|
||||||
print(f" Path: {request.get('path')}")
|
print(f" Path: {request.path}")
|
||||||
|
|
||||||
response = input("Approve? (y/n): ").strip().lower()
|
response = input("Approve? (y/n): ").strip().lower()
|
||||||
if response in ("y", "yes"):
|
if response in ("y", "yes"):
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ of MCP-related actions.
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from agent_framework.github import GitHubCopilotAgent
|
from agent_framework.github import GitHubCopilotAgent
|
||||||
from copilot.types import MCPServerConfig, PermissionRequest, PermissionRequestResult
|
from copilot.generated.session_events import PermissionRequest
|
||||||
|
from copilot.types import MCPServerConfig, PermissionRequestResult
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
# Load environment variables from .env file
|
# Load environment variables from .env file
|
||||||
@@ -24,8 +25,7 @@ load_dotenv()
|
|||||||
|
|
||||||
def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
|
def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
|
||||||
"""Permission handler that prompts the user for approval."""
|
"""Permission handler that prompts the user for approval."""
|
||||||
kind = request.get("kind", "unknown")
|
print(f"\n[Permission Request: {request.kind}]")
|
||||||
print(f"\n[Permission Request: {kind}]")
|
|
||||||
|
|
||||||
response = input("Approve? (y/n): ").strip().lower()
|
response = input("Approve? (y/n): ").strip().lower()
|
||||||
if response in ("y", "yes"):
|
if response in ("y", "yes"):
|
||||||
|
|||||||
+7
-7
@@ -21,18 +21,18 @@ More permissions mean more potential for unintended actions.
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from agent_framework.github import GitHubCopilotAgent
|
from agent_framework.github import GitHubCopilotAgent
|
||||||
from copilot.types import PermissionRequest, PermissionRequestResult
|
from copilot.generated.session_events import PermissionRequest
|
||||||
|
from copilot.types import PermissionRequestResult
|
||||||
|
|
||||||
|
|
||||||
def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
|
def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
|
||||||
"""Permission handler that prompts the user for approval."""
|
"""Permission handler that prompts the user for approval."""
|
||||||
kind = request.get("kind", "unknown")
|
print(f"\n[Permission Request: {request.kind}]")
|
||||||
print(f"\n[Permission Request: {kind}]")
|
|
||||||
|
|
||||||
if "command" in request:
|
if request.full_command_text is not None:
|
||||||
print(f" Command: {request.get('command')}")
|
print(f" Command: {request.full_command_text}")
|
||||||
if "path" in request:
|
if request.path is not None:
|
||||||
print(f" Path: {request.get('path')}")
|
print(f" Path: {request.path}")
|
||||||
|
|
||||||
response = input("Approve? (y/n): ").strip().lower()
|
response = input("Approve? (y/n): ").strip().lower()
|
||||||
if response in ("y", "yes"):
|
if response in ("y", "yes"):
|
||||||
|
|||||||
@@ -14,16 +14,16 @@ Shell commands have full access to your system within the permissions of the run
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from agent_framework.github import GitHubCopilotAgent
|
from agent_framework.github import GitHubCopilotAgent
|
||||||
from copilot.types import PermissionRequest, PermissionRequestResult
|
from copilot.generated.session_events import PermissionRequest
|
||||||
|
from copilot.types import PermissionRequestResult
|
||||||
|
|
||||||
|
|
||||||
def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
|
def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
|
||||||
"""Permission handler that prompts the user for approval."""
|
"""Permission handler that prompts the user for approval."""
|
||||||
kind = request.get("kind", "unknown")
|
print(f"\n[Permission Request: {request.kind}]")
|
||||||
print(f"\n[Permission Request: {kind}]")
|
|
||||||
|
|
||||||
if "command" in request:
|
if request.full_command_text is not None:
|
||||||
print(f" Command: {request.get('command')}")
|
print(f" Command: {request.full_command_text}")
|
||||||
|
|
||||||
response = input("Approve? (y/n): ").strip().lower()
|
response = input("Approve? (y/n): ").strip().lower()
|
||||||
if response in ("y", "yes"):
|
if response in ("y", "yes"):
|
||||||
|
|||||||
@@ -14,16 +14,16 @@ URL fetching allows the agent to access any URL accessible from your network.
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from agent_framework.github import GitHubCopilotAgent
|
from agent_framework.github import GitHubCopilotAgent
|
||||||
from copilot.types import PermissionRequest, PermissionRequestResult
|
from copilot.generated.session_events import PermissionRequest
|
||||||
|
from copilot.types import PermissionRequestResult
|
||||||
|
|
||||||
|
|
||||||
def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
|
def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
|
||||||
"""Permission handler that prompts the user for approval."""
|
"""Permission handler that prompts the user for approval."""
|
||||||
kind = request.get("kind", "unknown")
|
print(f"\n[Permission Request: {request.kind}]")
|
||||||
print(f"\n[Permission Request: {kind}]")
|
|
||||||
|
|
||||||
if "url" in request:
|
if request.url is not None:
|
||||||
print(f" URL: {request.get('url')}")
|
print(f" URL: {request.url}")
|
||||||
|
|
||||||
response = input("Approve? (y/n): ").strip().lower()
|
response = input("Approve? (y/n): ").strip().lower()
|
||||||
if response in ("y", "yes"):
|
if response in ("y", "yes"):
|
||||||
|
|||||||
Generated
+291
-1178
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user