agent-framework-github-copilot 1.0.0b260521__tar.gz → 1.0.0rc2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_framework_github_copilot-1.0.0rc2/PKG-INFO +87 -0
- agent_framework_github_copilot-1.0.0rc2/README.md +61 -0
- {agent_framework_github_copilot-1.0.0b260521 → agent_framework_github_copilot-1.0.0rc2}/agent_framework_github_copilot/_agent.py +202 -57
- {agent_framework_github_copilot-1.0.0b260521 → agent_framework_github_copilot-1.0.0rc2}/pyproject.toml +3 -3
- agent_framework_github_copilot-1.0.0b260521/PKG-INFO +0 -37
- agent_framework_github_copilot-1.0.0b260521/README.md +0 -11
- {agent_framework_github_copilot-1.0.0b260521 → agent_framework_github_copilot-1.0.0rc2}/LICENSE +0 -0
- {agent_framework_github_copilot-1.0.0b260521 → agent_framework_github_copilot-1.0.0rc2}/agent_framework_github_copilot/__init__.py +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-framework-github-copilot
|
|
3
|
+
Version: 1.0.0rc2
|
|
4
|
+
Summary: GitHub Copilot integration for Microsoft Agent Framework.
|
|
5
|
+
Author-email: Microsoft <af-support@microsoft.com>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: agent-framework-core>=1.10.0,<2
|
|
20
|
+
Requires-Dist: github-copilot-sdk==1.0.2; python_version >= '3.11'
|
|
21
|
+
Project-URL: homepage, https://aka.ms/agent-framework
|
|
22
|
+
Project-URL: issues, https://github.com/microsoft/agent-framework/issues
|
|
23
|
+
Project-URL: release_notes, https://github.com/microsoft/agent-framework/releases?q=tag%3Apython-1&expanded=true
|
|
24
|
+
Project-URL: source, https://github.com/microsoft/agent-framework/tree/main/python
|
|
25
|
+
|
|
26
|
+
# Get Started with Microsoft Agent Framework GitHub Copilot
|
|
27
|
+
|
|
28
|
+
Please install this package via pip:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install agent-framework-github-copilot --pre
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## GitHub Copilot Agent
|
|
35
|
+
|
|
36
|
+
The GitHub Copilot agent enables integration with GitHub Copilot, allowing you to interact with Copilot's agentic capabilities through the Agent Framework.
|
|
37
|
+
|
|
38
|
+
## Tool approval (`approval_mode="always_require"`)
|
|
39
|
+
|
|
40
|
+
The GitHub Copilot SDK owns the tool-calling loop for this provider, so approval for
|
|
41
|
+
custom function tools is enforced through the SDK's native pre-execution hook rather
|
|
42
|
+
than the standard Agent Framework approval round-trip.
|
|
43
|
+
|
|
44
|
+
When you register a `FunctionTool` declared with `approval_mode="always_require"` and you
|
|
45
|
+
do **not** supply your own `on_pre_tool_use` hook, `GitHubCopilotAgent` installs a default
|
|
46
|
+
`on_pre_tool_use` hook that returns `"ask"` for that tool and defers (`None`) for all other
|
|
47
|
+
tools. The `"ask"` decision routes to your `on_permission_request` handler, where you
|
|
48
|
+
approve or deny the call:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from agent_framework import tool
|
|
52
|
+
from agent_framework.github import GitHubCopilotAgent, GitHubCopilotOptions
|
|
53
|
+
from copilot.session import PermissionHandler
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@tool(approval_mode="always_require")
|
|
57
|
+
def delete_file(path: str) -> str:
|
|
58
|
+
"""Delete a file."""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
agent = GitHubCopilotAgent(
|
|
63
|
+
tools=[delete_file],
|
|
64
|
+
# The "ask" decision is routed here; approve or deny the call.
|
|
65
|
+
default_options=GitHubCopilotOptions(on_permission_request=PermissionHandler.approve_all),
|
|
66
|
+
)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
> **⚠️ If you provide your own `on_pre_tool_use` hook**, it takes precedence and the agent
|
|
70
|
+
> does **not** install its default approval hook. In that case **you are fully responsible**
|
|
71
|
+
> for enforcing approval — including for any `approval_mode="always_require"` tool (e.g. by
|
|
72
|
+
> returning a `"deny"` or `"ask"` decision). The agent logs a warning naming any
|
|
73
|
+
> approval-required tool that your hook must handle.
|
|
74
|
+
>
|
|
75
|
+
> Note: with the default (deny-all) permission handler, an `always_require` tool is denied
|
|
76
|
+
> unless you wire an approving `on_permission_request`.
|
|
77
|
+
|
|
78
|
+
### Deprecated: `on_function_approval`
|
|
79
|
+
|
|
80
|
+
The `on_function_approval` callback is **deprecated**. It still works (and is still enforced
|
|
81
|
+
inside the tool handler for backward compatibility), but it emits a `DeprecationWarning` and
|
|
82
|
+
will be removed in a future version. Migrate to the `on_pre_tool_use` + `on_permission_request`
|
|
83
|
+
model described above. When `on_function_approval` is set, it gates `always_require` tools and
|
|
84
|
+
the default ask-hook is not installed. It is **mutually exclusive** with `on_pre_tool_use` —
|
|
85
|
+
setting both (whether at construction or per run) raises `ValueError`.
|
|
86
|
+
|
|
87
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Get Started with Microsoft Agent Framework GitHub Copilot
|
|
2
|
+
|
|
3
|
+
Please install this package via pip:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install agent-framework-github-copilot --pre
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## GitHub Copilot Agent
|
|
10
|
+
|
|
11
|
+
The GitHub Copilot agent enables integration with GitHub Copilot, allowing you to interact with Copilot's agentic capabilities through the Agent Framework.
|
|
12
|
+
|
|
13
|
+
## Tool approval (`approval_mode="always_require"`)
|
|
14
|
+
|
|
15
|
+
The GitHub Copilot SDK owns the tool-calling loop for this provider, so approval for
|
|
16
|
+
custom function tools is enforced through the SDK's native pre-execution hook rather
|
|
17
|
+
than the standard Agent Framework approval round-trip.
|
|
18
|
+
|
|
19
|
+
When you register a `FunctionTool` declared with `approval_mode="always_require"` and you
|
|
20
|
+
do **not** supply your own `on_pre_tool_use` hook, `GitHubCopilotAgent` installs a default
|
|
21
|
+
`on_pre_tool_use` hook that returns `"ask"` for that tool and defers (`None`) for all other
|
|
22
|
+
tools. The `"ask"` decision routes to your `on_permission_request` handler, where you
|
|
23
|
+
approve or deny the call:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from agent_framework import tool
|
|
27
|
+
from agent_framework.github import GitHubCopilotAgent, GitHubCopilotOptions
|
|
28
|
+
from copilot.session import PermissionHandler
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@tool(approval_mode="always_require")
|
|
32
|
+
def delete_file(path: str) -> str:
|
|
33
|
+
"""Delete a file."""
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
agent = GitHubCopilotAgent(
|
|
38
|
+
tools=[delete_file],
|
|
39
|
+
# The "ask" decision is routed here; approve or deny the call.
|
|
40
|
+
default_options=GitHubCopilotOptions(on_permission_request=PermissionHandler.approve_all),
|
|
41
|
+
)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> **⚠️ If you provide your own `on_pre_tool_use` hook**, it takes precedence and the agent
|
|
45
|
+
> does **not** install its default approval hook. In that case **you are fully responsible**
|
|
46
|
+
> for enforcing approval — including for any `approval_mode="always_require"` tool (e.g. by
|
|
47
|
+
> returning a `"deny"` or `"ask"` decision). The agent logs a warning naming any
|
|
48
|
+
> approval-required tool that your hook must handle.
|
|
49
|
+
>
|
|
50
|
+
> Note: with the default (deny-all) permission handler, an `always_require` tool is denied
|
|
51
|
+
> unless you wire an approving `on_permission_request`.
|
|
52
|
+
|
|
53
|
+
### Deprecated: `on_function_approval`
|
|
54
|
+
|
|
55
|
+
The `on_function_approval` callback is **deprecated**. It still works (and is still enforced
|
|
56
|
+
inside the tool handler for backward compatibility), but it emits a `DeprecationWarning` and
|
|
57
|
+
will be removed in a future version. Migrate to the `on_pre_tool_use` + `on_permission_request`
|
|
58
|
+
model described above. When `on_function_approval` is set, it gates `always_require` tools and
|
|
59
|
+
the default ask-hook is not installed. It is **mutually exclusive** with `on_pre_tool_use` —
|
|
60
|
+
setting both (whether at construction or per run) raises `ValueError`.
|
|
61
|
+
|
|
@@ -7,6 +7,7 @@ import contextlib
|
|
|
7
7
|
import inspect
|
|
8
8
|
import logging
|
|
9
9
|
import sys
|
|
10
|
+
import warnings
|
|
10
11
|
from collections.abc import AsyncIterable, Awaitable, Callable, Mapping, MutableMapping, Sequence
|
|
11
12
|
from typing import Any, ClassVar, Generic, Literal, TypedDict, overload
|
|
12
13
|
|
|
@@ -37,9 +38,18 @@ from agent_framework.exceptions import AgentException
|
|
|
37
38
|
from agent_framework.observability import AgentTelemetryLayer
|
|
38
39
|
|
|
39
40
|
try:
|
|
40
|
-
from copilot import CopilotClient, CopilotSession,
|
|
41
|
-
from copilot.generated.
|
|
42
|
-
from copilot.session import
|
|
41
|
+
from copilot import CopilotClient, CopilotSession, RuntimeConnection
|
|
42
|
+
from copilot.generated.rpc import PermissionDecisionUserNotAvailable
|
|
43
|
+
from copilot.session import (
|
|
44
|
+
MCPServerConfig,
|
|
45
|
+
PermissionRequestResult,
|
|
46
|
+
PreToolUseHandler,
|
|
47
|
+
PreToolUseHookOutput,
|
|
48
|
+
ProviderConfig,
|
|
49
|
+
SessionHooks,
|
|
50
|
+
SystemMessageConfig,
|
|
51
|
+
)
|
|
52
|
+
from copilot.session_events import PermissionRequest, SessionEvent, SessionEventType
|
|
43
53
|
from copilot.tools import Tool as CopilotTool
|
|
44
54
|
from copilot.tools import ToolInvocation, ToolResult
|
|
45
55
|
except ImportError as _copilot_import_error:
|
|
@@ -49,36 +59,33 @@ except ImportError as _copilot_import_error:
|
|
|
49
59
|
) from _copilot_import_error
|
|
50
60
|
|
|
51
61
|
if sys.version_info >= (3, 13):
|
|
52
|
-
from typing import TypeVar
|
|
62
|
+
from typing import TypeVar # pragma: no cover
|
|
53
63
|
else:
|
|
54
|
-
from typing_extensions import TypeVar
|
|
64
|
+
from typing_extensions import TypeVar # pragma: no cover
|
|
55
65
|
|
|
56
66
|
|
|
57
67
|
DEFAULT_TIMEOUT_SECONDS: float = 60.0
|
|
58
68
|
"""Default timeout in seconds for Copilot requests."""
|
|
59
69
|
|
|
60
|
-
PermissionHandlerType = Callable[
|
|
61
|
-
|
|
70
|
+
PermissionHandlerType = Callable[
|
|
71
|
+
[PermissionRequest, dict[str, str]], "PermissionRequestResult | Awaitable[PermissionRequestResult]"
|
|
72
|
+
]
|
|
73
|
+
"""Type for permission request handlers. Supports both sync and async callbacks."""
|
|
62
74
|
|
|
63
75
|
|
|
64
76
|
FunctionApprovalCallback = Callable[[Content], "bool | Awaitable[bool]"]
|
|
65
|
-
"""
|
|
77
|
+
"""Deprecated approval callback for ``FunctionTool`` instances declared with
|
|
78
|
+
``approval_mode="always_require"``.
|
|
79
|
+
|
|
80
|
+
.. deprecated::
|
|
81
|
+
Use the SDK ``on_pre_tool_use`` hook together with ``on_permission_request``
|
|
82
|
+
instead. The default ``on_pre_tool_use`` hook returns ``"ask"`` for
|
|
83
|
+
``always_require`` tools and routes the decision to ``on_permission_request``.
|
|
66
84
|
|
|
67
85
|
The callback receives a ``FunctionCallContent`` describing the pending call
|
|
68
86
|
(``name``, ``arguments``, and a synthetic ``call_id``) and must return ``True``
|
|
69
87
|
to allow execution or ``False`` to deny it. Both synchronous and ``await``-able
|
|
70
88
|
return values are supported.
|
|
71
|
-
|
|
72
|
-
The Copilot CLI manages its own tool-calling loop, so the framework cannot
|
|
73
|
-
round-trip a ``FunctionApprovalRequestContent`` / ``FunctionApprovalResponseContent``
|
|
74
|
-
pair the way the standard chat-client pipeline does. This callback is the
|
|
75
|
-
agent-level enforcement point for tools declared with
|
|
76
|
-
``approval_mode="always_require"``: when no callback is configured the agent
|
|
77
|
-
denies these calls by default.
|
|
78
|
-
|
|
79
|
-
Note: this is independent of ``on_permission_request``, which gates the
|
|
80
|
-
Copilot SDK's *built-in* shell/file actions; ``on_function_approval`` gates
|
|
81
|
-
agent-framework ``FunctionTool`` calls.
|
|
82
89
|
"""
|
|
83
90
|
|
|
84
91
|
|
|
@@ -87,7 +94,7 @@ async def _resolve_function_approval(
|
|
|
87
94
|
func_tool: FunctionTool,
|
|
88
95
|
arguments: Mapping[str, Any] | None,
|
|
89
96
|
) -> bool:
|
|
90
|
-
"""Run the agent-level approval callback for a pending tool call.
|
|
97
|
+
"""Run the deprecated agent-level approval callback for a pending tool call.
|
|
91
98
|
|
|
92
99
|
Returns ``True`` only when ``callback`` is configured and explicitly returns
|
|
93
100
|
a truthy value. A missing callback or any callback failure is treated as a
|
|
@@ -121,7 +128,7 @@ def _deny_all_permissions(
|
|
|
121
128
|
_invocation: dict[str, str],
|
|
122
129
|
) -> PermissionRequestResult:
|
|
123
130
|
"""Default permission handler that denies all requests."""
|
|
124
|
-
return
|
|
131
|
+
return PermissionDecisionUserNotAvailable()
|
|
125
132
|
|
|
126
133
|
|
|
127
134
|
class GitHubCopilotSettings(TypedDict, total=False):
|
|
@@ -140,9 +147,9 @@ class GitHubCopilotSettings(TypedDict, total=False):
|
|
|
140
147
|
Can be set via environment variable GITHUB_COPILOT_TIMEOUT.
|
|
141
148
|
log_level: CLI log level.
|
|
142
149
|
Can be set via environment variable GITHUB_COPILOT_LOG_LEVEL.
|
|
143
|
-
|
|
150
|
+
base_directory: Directory where the CLI stores session state, configuration,
|
|
144
151
|
and other persistent data. Can be set via environment variable
|
|
145
|
-
|
|
152
|
+
GITHUB_COPILOT_BASE_DIRECTORY. Defaults to ~/.copilot when not set.
|
|
146
153
|
Only applicable when the SDK spawns the CLI process (ignored when
|
|
147
154
|
connecting to an external server via a pre-configured client).
|
|
148
155
|
"""
|
|
@@ -151,7 +158,7 @@ class GitHubCopilotSettings(TypedDict, total=False):
|
|
|
151
158
|
model: str | None
|
|
152
159
|
timeout: float | None
|
|
153
160
|
log_level: str | None
|
|
154
|
-
|
|
161
|
+
base_directory: str | None
|
|
155
162
|
|
|
156
163
|
|
|
157
164
|
class GitHubCopilotOptions(TypedDict, total=False):
|
|
@@ -199,13 +206,39 @@ class GitHubCopilotOptions(TypedDict, total=False):
|
|
|
199
206
|
files beyond the default locations.
|
|
200
207
|
"""
|
|
201
208
|
|
|
209
|
+
base_directory: str
|
|
210
|
+
"""Directory where the CLI stores session state, configuration, and other persistent data."""
|
|
211
|
+
|
|
212
|
+
on_pre_tool_use: PreToolUseHandler
|
|
213
|
+
"""Pre-tool-use hook handler for the Copilot SDK.
|
|
214
|
+
|
|
215
|
+
Called by the Copilot SDK before any tool is executed. The handler receives a
|
|
216
|
+
``PreToolUseHookInput`` and a context dict, and returns a ``PreToolUseHookOutput``
|
|
217
|
+
(or ``None`` to defer). Returning ``{"permissionDecision": "ask"}`` routes the
|
|
218
|
+
decision to ``on_permission_request``; ``"allow"`` / ``"deny"`` gate the call
|
|
219
|
+
directly.
|
|
220
|
+
|
|
221
|
+
If you do **not** supply this hook, the agent installs a default ``on_pre_tool_use``
|
|
222
|
+
hook that returns ``"ask"`` for ``FunctionTool`` instances declared with
|
|
223
|
+
``approval_mode="always_require"`` (deferring all other tools), so those tools are
|
|
224
|
+
gated through ``on_permission_request``. If you **do** supply your own hook, it
|
|
225
|
+
takes precedence and **you** are responsible for enforcing approval for any
|
|
226
|
+
``always_require`` tool; the agent logs a warning naming such tools."""
|
|
227
|
+
|
|
202
228
|
on_function_approval: FunctionApprovalCallback
|
|
203
|
-
"""
|
|
204
|
-
``approval_mode="always_require"``.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
229
|
+
"""Deprecated approval callback for ``FunctionTool`` instances declared with
|
|
230
|
+
``approval_mode="always_require"``.
|
|
231
|
+
|
|
232
|
+
.. deprecated::
|
|
233
|
+
Use ``on_pre_tool_use`` together with ``on_permission_request`` instead.
|
|
234
|
+
When neither this callback nor ``on_pre_tool_use`` is set, the agent
|
|
235
|
+
installs a default ``on_pre_tool_use`` hook that returns ``"ask"`` for
|
|
236
|
+
``always_require`` tools and routes the decision to ``on_permission_request``.
|
|
237
|
+
|
|
238
|
+
When set, this callback is enforced inside the SDK tool-handler before the tool
|
|
239
|
+
runs; a falsy return value denies the call. Setting it emits a
|
|
240
|
+
``DeprecationWarning``. It is **mutually exclusive** with ``on_pre_tool_use`` —
|
|
241
|
+
setting both raises ``ValueError``."""
|
|
209
242
|
|
|
210
243
|
|
|
211
244
|
OptionsT = TypeVar(
|
|
@@ -313,8 +346,26 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
313
346
|
mcp_servers: dict[str, MCPServerConfig] | None = opts.pop("mcp_servers", None)
|
|
314
347
|
provider: ProviderConfig | None = opts.pop("provider", None)
|
|
315
348
|
instruction_directories: list[str] | None = opts.pop("instruction_directories", None)
|
|
349
|
+
on_pre_tool_use: PreToolUseHandler | None = opts.pop("on_pre_tool_use", None)
|
|
316
350
|
on_function_approval: FunctionApprovalCallback | None = opts.pop("on_function_approval", None)
|
|
317
|
-
|
|
351
|
+
base_directory = opts.pop("base_directory", None)
|
|
352
|
+
|
|
353
|
+
if on_function_approval is not None and on_pre_tool_use is not None:
|
|
354
|
+
raise ValueError(
|
|
355
|
+
"on_function_approval and on_pre_tool_use cannot both be set. "
|
|
356
|
+
"on_function_approval is deprecated; use on_pre_tool_use together with "
|
|
357
|
+
"on_permission_request instead."
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
if on_function_approval is not None:
|
|
361
|
+
warnings.warn(
|
|
362
|
+
"on_function_approval is deprecated and will be removed in a future version. "
|
|
363
|
+
"Use the SDK 'on_pre_tool_use' hook together with 'on_permission_request' instead: "
|
|
364
|
+
"the default 'on_pre_tool_use' hook returns 'ask' for approval_mode='always_require' "
|
|
365
|
+
"tools and routes the decision to 'on_permission_request'.",
|
|
366
|
+
DeprecationWarning,
|
|
367
|
+
stacklevel=2,
|
|
368
|
+
)
|
|
318
369
|
|
|
319
370
|
self._settings = load_settings(
|
|
320
371
|
GitHubCopilotSettings,
|
|
@@ -323,13 +374,14 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
323
374
|
model=model,
|
|
324
375
|
timeout=timeout,
|
|
325
376
|
log_level=log_level,
|
|
326
|
-
|
|
377
|
+
base_directory=base_directory,
|
|
327
378
|
env_file_path=env_file_path,
|
|
328
379
|
env_file_encoding=env_file_encoding,
|
|
329
380
|
)
|
|
330
381
|
|
|
331
382
|
self._tools = normalize_tools(tools)
|
|
332
383
|
self._permission_handler = on_permission_request
|
|
384
|
+
self._on_pre_tool_use: PreToolUseHandler | None = on_pre_tool_use
|
|
333
385
|
self._function_approval_handler: FunctionApprovalCallback | None = on_function_approval
|
|
334
386
|
self._mcp_servers = mcp_servers
|
|
335
387
|
self._provider = provider
|
|
@@ -362,14 +414,16 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
362
414
|
if self._client is None:
|
|
363
415
|
cli_path = self._settings.get("cli_path") or None
|
|
364
416
|
log_level = self._settings.get("log_level") or None
|
|
365
|
-
|
|
417
|
+
base_directory = self._settings.get("base_directory") or None
|
|
366
418
|
|
|
367
|
-
|
|
419
|
+
client_kwargs: dict[str, Any] = {}
|
|
420
|
+
if cli_path:
|
|
421
|
+
client_kwargs["connection"] = RuntimeConnection.for_stdio(path=cli_path)
|
|
368
422
|
if log_level:
|
|
369
|
-
|
|
370
|
-
if
|
|
371
|
-
|
|
372
|
-
self._client = CopilotClient(
|
|
423
|
+
client_kwargs["log_level"] = log_level
|
|
424
|
+
if base_directory:
|
|
425
|
+
client_kwargs["base_directory"] = base_directory
|
|
426
|
+
self._client = CopilotClient(**client_kwargs)
|
|
373
427
|
|
|
374
428
|
try:
|
|
375
429
|
await self._client.start()
|
|
@@ -436,7 +490,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
436
490
|
session: AgentSession | None = None,
|
|
437
491
|
middleware: Sequence[AgentMiddlewareTypes] | None = None,
|
|
438
492
|
options: OptionsT | None = None,
|
|
439
|
-
**kwargs: Any,
|
|
493
|
+
**kwargs: Any,
|
|
440
494
|
) -> Awaitable[AgentResponse] | ResponseStream[AgentResponseUpdate, AgentResponse]:
|
|
441
495
|
"""Get a response from the agent.
|
|
442
496
|
|
|
@@ -514,6 +568,12 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
514
568
|
"via default_options at agent construction time. It cannot be overridden "
|
|
515
569
|
"per run."
|
|
516
570
|
)
|
|
571
|
+
if "on_pre_tool_use" in opts and self._function_approval_handler is not None:
|
|
572
|
+
raise ValueError(
|
|
573
|
+
"on_pre_tool_use cannot be combined with the deprecated on_function_approval "
|
|
574
|
+
"(set via default_options). Remove on_function_approval and use on_pre_tool_use "
|
|
575
|
+
"together with on_permission_request instead."
|
|
576
|
+
)
|
|
517
577
|
timeout = opts.get("timeout") or self._settings.get("timeout") or DEFAULT_TIMEOUT_SECONDS
|
|
518
578
|
|
|
519
579
|
input_messages = normalize_messages(messages)
|
|
@@ -603,6 +663,12 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
603
663
|
"via default_options at agent construction time. It cannot be overridden "
|
|
604
664
|
"per run."
|
|
605
665
|
)
|
|
666
|
+
if "on_pre_tool_use" in opts and self._function_approval_handler is not None:
|
|
667
|
+
raise ValueError(
|
|
668
|
+
"on_pre_tool_use cannot be combined with the deprecated on_function_approval "
|
|
669
|
+
"(set via default_options). Remove on_function_approval and use on_pre_tool_use "
|
|
670
|
+
"together with on_permission_request instead."
|
|
671
|
+
)
|
|
606
672
|
|
|
607
673
|
input_messages = normalize_messages(messages)
|
|
608
674
|
|
|
@@ -727,7 +793,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
727
793
|
if isinstance(provider, HistoryProvider) and not provider.load_messages:
|
|
728
794
|
continue
|
|
729
795
|
await provider.before_run(
|
|
730
|
-
agent=self,
|
|
796
|
+
agent=self,
|
|
731
797
|
session=session,
|
|
732
798
|
context=session_context,
|
|
733
799
|
state=session.state.setdefault(provider.source_id, {}),
|
|
@@ -776,7 +842,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
776
842
|
if isinstance(tool, CopilotTool):
|
|
777
843
|
copilot_tools.append(tool)
|
|
778
844
|
elif isinstance(tool, FunctionTool):
|
|
779
|
-
copilot_tools.append(self._tool_to_copilot_tool(tool))
|
|
845
|
+
copilot_tools.append(self._tool_to_copilot_tool(tool))
|
|
780
846
|
elif isinstance(tool, MutableMapping):
|
|
781
847
|
copilot_tools.append(tool) # type: ignore[arg-type]
|
|
782
848
|
# Note: Other tool types (e.g., dict-based hosted tools) are skipped
|
|
@@ -784,31 +850,32 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
784
850
|
return copilot_tools
|
|
785
851
|
|
|
786
852
|
def _tool_to_copilot_tool(self, ai_func: FunctionTool) -> CopilotTool:
|
|
787
|
-
"""Convert an FunctionTool to a Copilot SDK tool.
|
|
853
|
+
"""Convert an FunctionTool to a Copilot SDK tool.
|
|
854
|
+
|
|
855
|
+
Approval for tools declared with ``approval_mode="always_require"`` is normally
|
|
856
|
+
enforced by the Copilot SDK's native ``on_pre_tool_use`` hook (see
|
|
857
|
+
:meth:`_build_session_hooks`). When the deprecated ``on_function_approval``
|
|
858
|
+
callback is configured instead, approval is enforced inside this handler for
|
|
859
|
+
backward compatibility. (``on_function_approval`` and ``on_pre_tool_use`` are
|
|
860
|
+
mutually exclusive, so only one mechanism is ever active.)
|
|
861
|
+
"""
|
|
788
862
|
approval_handler = self._function_approval_handler
|
|
789
|
-
|
|
863
|
+
enforce = approval_handler is not None and ai_func.approval_mode == "always_require"
|
|
790
864
|
|
|
791
865
|
async def handler(invocation: ToolInvocation) -> ToolResult:
|
|
792
866
|
args: dict[str, Any] = invocation.arguments or {}
|
|
793
867
|
try:
|
|
794
|
-
if
|
|
795
|
-
deny_text = (
|
|
796
|
-
f"Tool '{ai_func.name}' requires human approval "
|
|
797
|
-
"(approval_mode='always_require') and the request was denied."
|
|
798
|
-
if approval_handler is not None
|
|
799
|
-
else (
|
|
800
|
-
f"Tool '{ai_func.name}' requires human approval "
|
|
801
|
-
"(approval_mode='always_require') but no on_function_approval "
|
|
802
|
-
"callback is configured on the agent; the request was denied."
|
|
803
|
-
)
|
|
804
|
-
)
|
|
868
|
+
if enforce and not await _resolve_function_approval(approval_handler, ai_func, args):
|
|
805
869
|
logger.info(
|
|
806
|
-
"Denying execution of tool '%s' (approval_mode='always_require',
|
|
870
|
+
"Denying execution of tool '%s' (approval_mode='always_require', "
|
|
871
|
+
"on_function_approval callback denied).",
|
|
807
872
|
ai_func.name,
|
|
808
|
-
"callback denied" if approval_handler is not None else "no callback configured",
|
|
809
873
|
)
|
|
810
874
|
return ToolResult(
|
|
811
|
-
text_result_for_llm=
|
|
875
|
+
text_result_for_llm=(
|
|
876
|
+
f"Tool '{ai_func.name}' requires human approval "
|
|
877
|
+
"(approval_mode='always_require') and the request was denied."
|
|
878
|
+
),
|
|
812
879
|
result_type="failure",
|
|
813
880
|
error="approval_denied",
|
|
814
881
|
)
|
|
@@ -842,6 +909,80 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
842
909
|
parameters=ai_func.parameters(),
|
|
843
910
|
)
|
|
844
911
|
|
|
912
|
+
def _build_session_hooks(
|
|
913
|
+
self,
|
|
914
|
+
all_tools: Sequence[ToolTypes | CopilotTool],
|
|
915
|
+
opts: Mapping[str, Any],
|
|
916
|
+
) -> SessionHooks | None:
|
|
917
|
+
"""Build the ``SessionHooks`` to pass to the Copilot SDK for this session.
|
|
918
|
+
|
|
919
|
+
Approval enforcement for ``FunctionTool`` instances declared with
|
|
920
|
+
``approval_mode="always_require"`` is delegated to the Copilot SDK's native
|
|
921
|
+
``on_pre_tool_use`` hook:
|
|
922
|
+
|
|
923
|
+
- If the caller supplies their own ``on_pre_tool_use`` (via per-run ``options``
|
|
924
|
+
or ``default_options``), it takes precedence and is returned unchanged. A
|
|
925
|
+
warning is logged naming any approval-required tool that will therefore not
|
|
926
|
+
be automatically gated, since the caller's hook is responsible for enforcing
|
|
927
|
+
approval.
|
|
928
|
+
- Otherwise, when any approval-required tool is present, a default hook is
|
|
929
|
+
installed that returns ``"ask"`` for those tools (routing the decision to
|
|
930
|
+
``on_permission_request``) and defers (``None``) for all other tools.
|
|
931
|
+
- The default hook is **not** installed when the deprecated
|
|
932
|
+
``on_function_approval`` callback is configured: in that case approval is
|
|
933
|
+
enforced inside the tool handler (see :meth:`_tool_to_copilot_tool`) to
|
|
934
|
+
preserve backward-compatible behavior.
|
|
935
|
+
- When there are no approval-required tools and no caller hook, ``None`` is
|
|
936
|
+
returned so no hooks are registered.
|
|
937
|
+
|
|
938
|
+
Args:
|
|
939
|
+
all_tools: The full set of tools resolved for the session.
|
|
940
|
+
opts: Runtime options that take precedence over ``default_options``.
|
|
941
|
+
|
|
942
|
+
Returns:
|
|
943
|
+
The hooks to register for the session, or ``None`` if none are needed.
|
|
944
|
+
"""
|
|
945
|
+
user_hook: PreToolUseHandler | None = opts.get("on_pre_tool_use") or self._on_pre_tool_use
|
|
946
|
+
|
|
947
|
+
approval_required_names = {
|
|
948
|
+
tool.name for tool in all_tools if isinstance(tool, FunctionTool) and tool.approval_mode == "always_require"
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
if user_hook is not None:
|
|
952
|
+
if approval_required_names:
|
|
953
|
+
logger.warning(
|
|
954
|
+
"A custom 'on_pre_tool_use' hook is configured, so %d approval-required tool(s) (%s) "
|
|
955
|
+
"will not be automatically gated by GitHubCopilotAgent. The custom hook is responsible "
|
|
956
|
+
"for enforcing approval (for example, by returning a 'deny' or 'ask' decision).",
|
|
957
|
+
len(approval_required_names),
|
|
958
|
+
", ".join(sorted(approval_required_names)),
|
|
959
|
+
)
|
|
960
|
+
return {"on_pre_tool_use": user_hook}
|
|
961
|
+
|
|
962
|
+
if not approval_required_names:
|
|
963
|
+
return None
|
|
964
|
+
|
|
965
|
+
# The deprecated on_function_approval callback enforces approval in the tool
|
|
966
|
+
# handler; don't also install the default ask-hook (which would double-gate).
|
|
967
|
+
if self._function_approval_handler is not None:
|
|
968
|
+
return None
|
|
969
|
+
|
|
970
|
+
def default_pre_tool_use(
|
|
971
|
+
hook_input: Mapping[str, Any],
|
|
972
|
+
_context: Mapping[str, str],
|
|
973
|
+
) -> PreToolUseHookOutput | None:
|
|
974
|
+
tool_name = hook_input.get("toolName")
|
|
975
|
+
if tool_name in approval_required_names:
|
|
976
|
+
return {
|
|
977
|
+
"permissionDecision": "ask",
|
|
978
|
+
"permissionDecisionReason": (
|
|
979
|
+
f"Tool '{tool_name}' is marked as requiring approval (approval_mode='always_require')."
|
|
980
|
+
),
|
|
981
|
+
}
|
|
982
|
+
return None
|
|
983
|
+
|
|
984
|
+
return {"on_pre_tool_use": default_pre_tool_use}
|
|
985
|
+
|
|
845
986
|
async def _get_or_create_session(
|
|
846
987
|
self,
|
|
847
988
|
agent_session: AgentSession,
|
|
@@ -899,6 +1040,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
899
1040
|
instruction_directories = opts.get("instruction_directories", self._instruction_directories)
|
|
900
1041
|
all_tools = list(self._tools or []) + list(opts.get("tools") or [])
|
|
901
1042
|
tools = self._prepare_tools(all_tools) if all_tools else None
|
|
1043
|
+
hooks = self._build_session_hooks(all_tools, opts)
|
|
902
1044
|
|
|
903
1045
|
return await self._client.create_session(
|
|
904
1046
|
on_permission_request=permission_handler,
|
|
@@ -909,6 +1051,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
909
1051
|
mcp_servers=mcp_servers or None,
|
|
910
1052
|
provider=provider or None,
|
|
911
1053
|
instruction_directories=instruction_directories,
|
|
1054
|
+
hooks=hooks,
|
|
912
1055
|
)
|
|
913
1056
|
|
|
914
1057
|
async def _resume_session(
|
|
@@ -938,6 +1081,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
938
1081
|
instruction_directories = opts.get("instruction_directories", self._instruction_directories)
|
|
939
1082
|
all_tools = list(self._tools or []) + list(opts.get("tools") or [])
|
|
940
1083
|
tools = self._prepare_tools(all_tools) if all_tools else None
|
|
1084
|
+
hooks = self._build_session_hooks(all_tools, opts)
|
|
941
1085
|
|
|
942
1086
|
return await self._client.resume_session(
|
|
943
1087
|
session_id,
|
|
@@ -949,6 +1093,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
949
1093
|
mcp_servers=mcp_servers or None,
|
|
950
1094
|
provider=provider or None,
|
|
951
1095
|
instruction_directories=instruction_directories,
|
|
1096
|
+
hooks=hooks,
|
|
952
1097
|
)
|
|
953
1098
|
|
|
954
1099
|
|
|
@@ -4,7 +4,7 @@ description = "GitHub Copilot integration for Microsoft Agent Framework."
|
|
|
4
4
|
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.0rc2"
|
|
8
8
|
license-files = ["LICENSE"]
|
|
9
9
|
urls.homepage = "https://aka.ms/agent-framework"
|
|
10
10
|
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
|
@@ -23,8 +23,8 @@ classifiers = [
|
|
|
23
23
|
"Typing :: Typed",
|
|
24
24
|
]
|
|
25
25
|
dependencies = [
|
|
26
|
-
"agent-framework-core>=1.
|
|
27
|
-
"github-copilot-sdk
|
|
26
|
+
"agent-framework-core>=1.10.0,<2",
|
|
27
|
+
"github-copilot-sdk==1.0.2; python_version >= '3.11'",
|
|
28
28
|
]
|
|
29
29
|
|
|
30
30
|
[tool.uv]
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: agent-framework-github-copilot
|
|
3
|
-
Version: 1.0.0b260521
|
|
4
|
-
Summary: GitHub Copilot integration for Microsoft Agent Framework.
|
|
5
|
-
Author-email: Microsoft <af-support@microsoft.com>
|
|
6
|
-
Requires-Python: >=3.10
|
|
7
|
-
Description-Content-Type: text/markdown
|
|
8
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
-
Classifier: Development Status :: 4 - Beta
|
|
10
|
-
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
-
Classifier: Typing :: Typed
|
|
18
|
-
License-File: LICENSE
|
|
19
|
-
Requires-Dist: agent-framework-core>=1.6.0,<2
|
|
20
|
-
Requires-Dist: github-copilot-sdk>=1.0.0b2,<=1.0.0b2; python_version >= '3.11'
|
|
21
|
-
Project-URL: homepage, https://aka.ms/agent-framework
|
|
22
|
-
Project-URL: issues, https://github.com/microsoft/agent-framework/issues
|
|
23
|
-
Project-URL: release_notes, https://github.com/microsoft/agent-framework/releases?q=tag%3Apython-1&expanded=true
|
|
24
|
-
Project-URL: source, https://github.com/microsoft/agent-framework/tree/main/python
|
|
25
|
-
|
|
26
|
-
# Get Started with Microsoft Agent Framework GitHub Copilot
|
|
27
|
-
|
|
28
|
-
Please install this package via pip:
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
pip install agent-framework-github-copilot --pre
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## GitHub Copilot Agent
|
|
35
|
-
|
|
36
|
-
The GitHub Copilot agent enables integration with GitHub Copilot, allowing you to interact with Copilot's agentic capabilities through the Agent Framework.
|
|
37
|
-
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# Get Started with Microsoft Agent Framework GitHub Copilot
|
|
2
|
-
|
|
3
|
-
Please install this package via pip:
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
pip install agent-framework-github-copilot --pre
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
## GitHub Copilot Agent
|
|
10
|
-
|
|
11
|
-
The GitHub Copilot agent enables integration with GitHub Copilot, allowing you to interact with Copilot's agentic capabilities through the Agent Framework.
|
{agent_framework_github_copilot-1.0.0b260521 → agent_framework_github_copilot-1.0.0rc2}/LICENSE
RENAMED
|
File without changes
|