agent-framework-github-copilot 2.1.0__tar.gz → 2.1.2__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-2.1.0 → agent_framework_github_copilot-2.1.2}/PKG-INFO +41 -13
- {agent_framework_github_copilot-2.1.0 → agent_framework_github_copilot-2.1.2}/README.md +39 -11
- {agent_framework_github_copilot-2.1.0 → agent_framework_github_copilot-2.1.2}/agent_framework_github_copilot/_agent.py +46 -70
- {agent_framework_github_copilot-2.1.0 → agent_framework_github_copilot-2.1.2}/pyproject.toml +2 -2
- {agent_framework_github_copilot-2.1.0 → agent_framework_github_copilot-2.1.2}/LICENSE +0 -0
- {agent_framework_github_copilot-2.1.0 → agent_framework_github_copilot-2.1.2}/agent_framework_github_copilot/__init__.py +0 -0
- {agent_framework_github_copilot-2.1.0 → agent_framework_github_copilot-2.1.2}/agent_framework_github_copilot/_feature_usage.py +0 -0
- {agent_framework_github_copilot-2.1.0 → agent_framework_github_copilot-2.1.2}/agent_framework_github_copilot/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agent-framework-github-copilot
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: GitHub Copilot integration for Microsoft Agent Framework.
|
|
5
5
|
Author-email: Microsoft <af-support@microsoft.com>
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.15
|
|
|
17
17
|
Classifier: Typing :: Typed
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: agent-framework-core>=1.22.0,<2
|
|
20
|
-
Requires-Dist: github-copilot-sdk>=1.0.16,<2.0
|
|
20
|
+
Requires-Dist: github-copilot-sdk>=1.0.16,<2.0
|
|
21
21
|
Project-URL: homepage, https://aka.ms/agent-framework
|
|
22
22
|
Project-URL: issues, https://github.com/microsoft/agent-framework/issues
|
|
23
23
|
Project-URL: release_notes, https://github.com/microsoft/agent-framework/releases?q=tag%3Apython-1&expanded=true
|
|
@@ -36,6 +36,17 @@ pip install agent-framework-github-copilot
|
|
|
36
36
|
|
|
37
37
|
The GitHub Copilot agent enables integration with GitHub Copilot, allowing you to interact with Copilot's agentic capabilities through the Agent Framework.
|
|
38
38
|
|
|
39
|
+
## Context providers and function middleware
|
|
40
|
+
|
|
41
|
+
Context providers can contribute messages, instructions, and tools to a run.
|
|
42
|
+
`GitHubCopilotAgent` supports agent middleware, but the SDK owns its tool-calling loop
|
|
43
|
+
and does not run Agent Framework function middleware. If a context provider contributes
|
|
44
|
+
function middleware through `SessionContext.extend_middleware()`, the run raises
|
|
45
|
+
`MiddlewareException` before creating or resuming an SDK session or registering its tools.
|
|
46
|
+
This applies to both response modes and to `RawGitHubCopilotAgent`.
|
|
47
|
+
|
|
48
|
+
Use `Agent` with a chat client when provider-contributed function middleware is required.
|
|
49
|
+
|
|
39
50
|
## Tool approval (`approval_mode="always_require"`)
|
|
40
51
|
|
|
41
52
|
The GitHub Copilot SDK owns the tool-calling loop for this provider, so approval for
|
|
@@ -46,7 +57,8 @@ When you register a `FunctionTool` declared with `approval_mode="always_require"
|
|
|
46
57
|
do **not** supply your own `on_pre_tool_use` hook, `GitHubCopilotAgent` installs a default
|
|
47
58
|
`on_pre_tool_use` hook that returns `"ask"` for that tool and defers (`None`) for all other
|
|
48
59
|
tools. The `"ask"` decision routes to your `on_permission_request` handler, where you
|
|
49
|
-
approve or deny the call
|
|
60
|
+
approve or deny the call. Other session hooks, such as `on_post_tool_use` or
|
|
61
|
+
`on_session_start`, are preserved alongside this default approval hook:
|
|
50
62
|
|
|
51
63
|
```python
|
|
52
64
|
from agent_framework import tool
|
|
@@ -83,10 +95,9 @@ prompts) or a `domain` (URL prompts). Both are optional, so a bare
|
|
|
83
95
|
`PermissionDecisionApproveForSession()` carries no scope at all and the Copilot CLI cannot
|
|
84
96
|
interpret it.
|
|
85
97
|
|
|
86
|
-
`GitHubCopilotAgent` therefore scopes such a decision automatically
|
|
87
|
-
|
|
88
|
-
MCP prompt an approval for that server and tool,
|
|
89
|
-
domain, and so on:
|
|
98
|
+
`GitHubCopilotAgent` therefore scopes such a decision automatically when the request provides
|
|
99
|
+
a structured session approval — a shell prompt becomes an approval for that prompt's command
|
|
100
|
+
identifiers, an MCP prompt an approval for that server and tool, and so on:
|
|
90
101
|
|
|
91
102
|
```python
|
|
92
103
|
from copilot.generated.rpc import PermissionDecisionApproveForSession
|
|
@@ -97,12 +108,13 @@ def on_permission_request(request, invocation):
|
|
|
97
108
|
return PermissionDecisionApproveForSession()
|
|
98
109
|
```
|
|
99
110
|
|
|
100
|
-
The decision is only ever narrowed, never widened.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
111
|
+
The decision is only ever narrowed, never widened. A bare decision for a URL prompt is
|
|
112
|
+
downgraded to a single-use approval because the framework cannot safely infer the Copilot
|
|
113
|
+
CLI's exact URL permission scope. The same downgrade occurs when the prompt reports that it
|
|
114
|
+
cannot offer session-scoped approval (`can_offer_session_approval=False`), or the request
|
|
115
|
+
kind has no session-scoped approval at all (such as a `hook` prompt). A warning is logged in
|
|
116
|
+
each case. Pass an explicit `approval=` or `domain=` when you want to approve a specific
|
|
117
|
+
scope for the session — decisions that already specify a scope are forwarded unchanged.
|
|
106
118
|
|
|
107
119
|
### Deprecated: `on_function_approval`
|
|
108
120
|
|
|
@@ -142,4 +154,20 @@ directory that defines hooks it is not loading. See
|
|
|
142
154
|
[`github_copilot_with_file_hooks.py`](../../samples/02-agents/providers/github_copilot/github_copilot_with_file_hooks.py)
|
|
143
155
|
for a runnable example.
|
|
144
156
|
|
|
157
|
+
## MCP configuration and session lifetime
|
|
158
|
+
|
|
159
|
+
Constructor `default_options` provide SDK session defaults. Per-run `options` override
|
|
160
|
+
the corresponding defaults for that request, including `mcp_servers`. Reusing an
|
|
161
|
+
`AgentSession` with a `service_session_id` resumes SDK/CLI-owned service-session state.
|
|
162
|
+
Forwarding a narrower MCP configuration, such as changing a server's `tools` from
|
|
163
|
+
`["*"]` to `[]`, is not an authorization or revocation guarantee for capabilities
|
|
164
|
+
already resident in that session. Enforce authorization at the MCP service/tool boundary.
|
|
165
|
+
|
|
166
|
+
Use a fresh service session when narrowing configured capabilities: create
|
|
167
|
+
`fresh_session = agent.create_session()`, then pass it to
|
|
168
|
+
`await agent.run("Start a new conversation", session=fresh_session, options=restricted_options)`,
|
|
169
|
+
where `restricted_options` contains the narrowed `mcp_servers` configuration.
|
|
170
|
+
The new session has no `service_session_id`; do not copy the previous service-session
|
|
171
|
+
state or reuse its SDK `session_id` in constructor or per-run options. A fresh session
|
|
172
|
+
starts a new conversation rather than continuing the previous one.
|
|
145
173
|
|
|
@@ -10,6 +10,17 @@ pip install agent-framework-github-copilot
|
|
|
10
10
|
|
|
11
11
|
The GitHub Copilot agent enables integration with GitHub Copilot, allowing you to interact with Copilot's agentic capabilities through the Agent Framework.
|
|
12
12
|
|
|
13
|
+
## Context providers and function middleware
|
|
14
|
+
|
|
15
|
+
Context providers can contribute messages, instructions, and tools to a run.
|
|
16
|
+
`GitHubCopilotAgent` supports agent middleware, but the SDK owns its tool-calling loop
|
|
17
|
+
and does not run Agent Framework function middleware. If a context provider contributes
|
|
18
|
+
function middleware through `SessionContext.extend_middleware()`, the run raises
|
|
19
|
+
`MiddlewareException` before creating or resuming an SDK session or registering its tools.
|
|
20
|
+
This applies to both response modes and to `RawGitHubCopilotAgent`.
|
|
21
|
+
|
|
22
|
+
Use `Agent` with a chat client when provider-contributed function middleware is required.
|
|
23
|
+
|
|
13
24
|
## Tool approval (`approval_mode="always_require"`)
|
|
14
25
|
|
|
15
26
|
The GitHub Copilot SDK owns the tool-calling loop for this provider, so approval for
|
|
@@ -20,7 +31,8 @@ When you register a `FunctionTool` declared with `approval_mode="always_require"
|
|
|
20
31
|
do **not** supply your own `on_pre_tool_use` hook, `GitHubCopilotAgent` installs a default
|
|
21
32
|
`on_pre_tool_use` hook that returns `"ask"` for that tool and defers (`None`) for all other
|
|
22
33
|
tools. The `"ask"` decision routes to your `on_permission_request` handler, where you
|
|
23
|
-
approve or deny the call
|
|
34
|
+
approve or deny the call. Other session hooks, such as `on_post_tool_use` or
|
|
35
|
+
`on_session_start`, are preserved alongside this default approval hook:
|
|
24
36
|
|
|
25
37
|
```python
|
|
26
38
|
from agent_framework import tool
|
|
@@ -57,10 +69,9 @@ prompts) or a `domain` (URL prompts). Both are optional, so a bare
|
|
|
57
69
|
`PermissionDecisionApproveForSession()` carries no scope at all and the Copilot CLI cannot
|
|
58
70
|
interpret it.
|
|
59
71
|
|
|
60
|
-
`GitHubCopilotAgent` therefore scopes such a decision automatically
|
|
61
|
-
|
|
62
|
-
MCP prompt an approval for that server and tool,
|
|
63
|
-
domain, and so on:
|
|
72
|
+
`GitHubCopilotAgent` therefore scopes such a decision automatically when the request provides
|
|
73
|
+
a structured session approval — a shell prompt becomes an approval for that prompt's command
|
|
74
|
+
identifiers, an MCP prompt an approval for that server and tool, and so on:
|
|
64
75
|
|
|
65
76
|
```python
|
|
66
77
|
from copilot.generated.rpc import PermissionDecisionApproveForSession
|
|
@@ -71,12 +82,13 @@ def on_permission_request(request, invocation):
|
|
|
71
82
|
return PermissionDecisionApproveForSession()
|
|
72
83
|
```
|
|
73
84
|
|
|
74
|
-
The decision is only ever narrowed, never widened.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
The decision is only ever narrowed, never widened. A bare decision for a URL prompt is
|
|
86
|
+
downgraded to a single-use approval because the framework cannot safely infer the Copilot
|
|
87
|
+
CLI's exact URL permission scope. The same downgrade occurs when the prompt reports that it
|
|
88
|
+
cannot offer session-scoped approval (`can_offer_session_approval=False`), or the request
|
|
89
|
+
kind has no session-scoped approval at all (such as a `hook` prompt). A warning is logged in
|
|
90
|
+
each case. Pass an explicit `approval=` or `domain=` when you want to approve a specific
|
|
91
|
+
scope for the session — decisions that already specify a scope are forwarded unchanged.
|
|
80
92
|
|
|
81
93
|
### Deprecated: `on_function_approval`
|
|
82
94
|
|
|
@@ -116,3 +128,19 @@ directory that defines hooks it is not loading. See
|
|
|
116
128
|
[`github_copilot_with_file_hooks.py`](../../samples/02-agents/providers/github_copilot/github_copilot_with_file_hooks.py)
|
|
117
129
|
for a runnable example.
|
|
118
130
|
|
|
131
|
+
## MCP configuration and session lifetime
|
|
132
|
+
|
|
133
|
+
Constructor `default_options` provide SDK session defaults. Per-run `options` override
|
|
134
|
+
the corresponding defaults for that request, including `mcp_servers`. Reusing an
|
|
135
|
+
`AgentSession` with a `service_session_id` resumes SDK/CLI-owned service-session state.
|
|
136
|
+
Forwarding a narrower MCP configuration, such as changing a server's `tools` from
|
|
137
|
+
`["*"]` to `[]`, is not an authorization or revocation guarantee for capabilities
|
|
138
|
+
already resident in that session. Enforce authorization at the MCP service/tool boundary.
|
|
139
|
+
|
|
140
|
+
Use a fresh service session when narrowing configured capabilities: create
|
|
141
|
+
`fresh_session = agent.create_session()`, then pass it to
|
|
142
|
+
`await agent.run("Start a new conversation", session=fresh_session, options=restricted_options)`,
|
|
143
|
+
where `restricted_options` contains the narrowed `mcp_servers` configuration.
|
|
144
|
+
The new session has no `service_session_id`; do not copy the previous service-session
|
|
145
|
+
state or reuse its SDK `session_id` in constructor or per-run options. A fresh session
|
|
146
|
+
starts a new conversation rather than continuing the previous one.
|
|
@@ -21,8 +21,7 @@ from collections.abc import (
|
|
|
21
21
|
Sequence,
|
|
22
22
|
)
|
|
23
23
|
from pathlib import Path
|
|
24
|
-
from typing import Any, ClassVar, Generic, Literal, TypedDict, cast, overload
|
|
25
|
-
from urllib.parse import urlparse
|
|
24
|
+
from typing import Any, ClassVar, Generic, Literal, Self, TypedDict, cast, overload
|
|
26
25
|
|
|
27
26
|
from agent_framework import (
|
|
28
27
|
AgentMiddlewareLayer,
|
|
@@ -43,6 +42,7 @@ from agent_framework import (
|
|
|
43
42
|
normalize_messages,
|
|
44
43
|
)
|
|
45
44
|
from agent_framework._mcp import MCPTool
|
|
45
|
+
from agent_framework._middleware import categorize_middleware
|
|
46
46
|
from agent_framework._settings import load_settings
|
|
47
47
|
from agent_framework._telemetry import mark_feature_used
|
|
48
48
|
from agent_framework._tools import FunctionTool, ToolTypes
|
|
@@ -51,15 +51,11 @@ from agent_framework._types import (
|
|
|
51
51
|
_get_data_bytes_as_str, # pyright: ignore[reportPrivateUsage]
|
|
52
52
|
normalize_tools,
|
|
53
53
|
)
|
|
54
|
-
from agent_framework.exceptions import AgentException, ContentError
|
|
54
|
+
from agent_framework.exceptions import AgentException, ContentError, MiddlewareException
|
|
55
55
|
from agent_framework.observability import AgentTelemetryLayer
|
|
56
56
|
|
|
57
57
|
from ._feature_usage import FeatureIndex
|
|
58
58
|
|
|
59
|
-
if sys.version_info >= (3, 11):
|
|
60
|
-
from typing import Self # pragma: no cover
|
|
61
|
-
else:
|
|
62
|
-
from typing_extensions import Self # pragma: no cover
|
|
63
59
|
if sys.version_info >= (3, 13):
|
|
64
60
|
from typing import TypeVar # pragma: no cover
|
|
65
61
|
else:
|
|
@@ -271,41 +267,6 @@ def _derive_session_approval(request: PermissionRequest) -> PermissionDecisionAp
|
|
|
271
267
|
return None
|
|
272
268
|
|
|
273
269
|
|
|
274
|
-
# Characters the WHATWG URL parser (used by the Copilot CLI) treats specially for
|
|
275
|
-
# special-scheme URLs in ways that can move the authority boundary: backslashes are
|
|
276
|
-
# normalized to forward slashes, and tabs/newlines/carriage returns are stripped before
|
|
277
|
-
# parsing. Python's ``urlparse`` does none of this, so a URL containing any of them may
|
|
278
|
-
# resolve to a different host than the CLI actually contacts.
|
|
279
|
-
_WHATWG_AMBIGUOUS_URL_CHARS = ("\\", "\t", "\n", "\r")
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
def _derive_url_session_domain(url: str) -> str | None:
|
|
283
|
-
"""Return the domain to persist for a URL prompt, or ``None`` when it is unsafe to.
|
|
284
|
-
|
|
285
|
-
The persisted domain must match the host the Copilot CLI actually contacts, but the CLI
|
|
286
|
-
parses URLs with WHATWG semantics while this runs on Python's ``urlparse``. The two
|
|
287
|
-
disagree on crafted authorities -- a backslash before the ``@`` in
|
|
288
|
-
``https://example.com<backslash>@evil.com`` resolves to ``example.com`` under the CLI
|
|
289
|
-
but ``evil.com`` under ``urlparse`` -- so trusting ``urlparse`` here could persist a
|
|
290
|
-
session-wide approval for an unrelated, attacker-chosen domain.
|
|
291
|
-
|
|
292
|
-
To keep the "narrow, never widen" guarantee, the domain is only returned when the URL
|
|
293
|
-
contains none of the characters the two parsers handle differently; any ambiguity (or a
|
|
294
|
-
URL with no derivable host) yields ``None`` so the caller can approve the single request
|
|
295
|
-
without persisting a domain.
|
|
296
|
-
|
|
297
|
-
Args:
|
|
298
|
-
url: The URL from the permission request.
|
|
299
|
-
|
|
300
|
-
Returns:
|
|
301
|
-
The lower-cased host to approve for the session, or ``None`` when the URL is
|
|
302
|
-
parser-ambiguous or has no host.
|
|
303
|
-
"""
|
|
304
|
-
if any(char in url for char in _WHATWG_AMBIGUOUS_URL_CHARS):
|
|
305
|
-
return None
|
|
306
|
-
return urlparse(url).hostname or None
|
|
307
|
-
|
|
308
|
-
|
|
309
270
|
def _normalize_permission_decision(
|
|
310
271
|
decision: PermissionRequestResult,
|
|
311
272
|
request: PermissionRequest,
|
|
@@ -317,11 +278,12 @@ def _normalize_permission_decision(
|
|
|
317
278
|
is constructible with neither. That serializes to ``{"kind": "approve-for-session"}``,
|
|
318
279
|
which the Copilot CLI cannot interpret -- it crashes with ``Cannot read properties of
|
|
319
280
|
undefined (reading 'commandIdentifiers')``, taking the whole run down with it. This
|
|
320
|
-
reconstructs
|
|
281
|
+
reconstructs structured tool scopes from ``request`` and narrows to a single request
|
|
282
|
+
when an exact session scope cannot be safely inferred.
|
|
321
283
|
|
|
322
|
-
The decision is only ever narrowed, never widened:
|
|
323
|
-
session-scoped approval,
|
|
324
|
-
|
|
284
|
+
The decision is only ever narrowed, never widened: URL prompts, prompts that do not
|
|
285
|
+
offer session-scoped approval, and request kinds with no session approval representation
|
|
286
|
+
are downgraded to a single-use approval.
|
|
325
287
|
|
|
326
288
|
Args:
|
|
327
289
|
decision: The decision returned by the caller's permission handler.
|
|
@@ -339,14 +301,11 @@ def _normalize_permission_decision(
|
|
|
339
301
|
|
|
340
302
|
try:
|
|
341
303
|
if isinstance(request, PermissionRequestUrl):
|
|
342
|
-
domain = _derive_url_session_domain(request.url)
|
|
343
|
-
if domain:
|
|
344
|
-
return PermissionDecisionApproveForSession(domain=domain)
|
|
345
304
|
logger.warning(
|
|
346
305
|
"Permission handler returned an unscoped 'approve-for-session' decision for a URL prompt, "
|
|
347
|
-
"but
|
|
306
|
+
"but Agent Framework cannot safely derive the Copilot CLI's exact URL permission scope. "
|
|
307
|
+
"Approving this request only. Return "
|
|
348
308
|
"PermissionDecisionApproveForSession(domain=...) to approve a domain for the session.",
|
|
349
|
-
request.url,
|
|
350
309
|
)
|
|
351
310
|
return PermissionDecisionApproveOnce()
|
|
352
311
|
|
|
@@ -895,6 +854,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
895
854
|
|
|
896
855
|
Raises:
|
|
897
856
|
AgentException: If the request fails.
|
|
857
|
+
MiddlewareException: If a context provider contributes unsupported function middleware.
|
|
898
858
|
"""
|
|
899
859
|
if middleware:
|
|
900
860
|
logger.warning(
|
|
@@ -1102,6 +1062,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1102
1062
|
|
|
1103
1063
|
Raises:
|
|
1104
1064
|
AgentException: If the request fails.
|
|
1065
|
+
MiddlewareException: If a context provider contributes unsupported function middleware.
|
|
1105
1066
|
"""
|
|
1106
1067
|
if not self._started:
|
|
1107
1068
|
await self.start()
|
|
@@ -1168,7 +1129,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1168
1129
|
contents=[
|
|
1169
1130
|
Content.from_usage(
|
|
1170
1131
|
usage_details,
|
|
1171
|
-
merge_mode="
|
|
1132
|
+
merge_mode="append",
|
|
1172
1133
|
raw_representation=event.data,
|
|
1173
1134
|
)
|
|
1174
1135
|
]
|
|
@@ -1268,6 +1229,10 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1268
1229
|
|
|
1269
1230
|
Returns:
|
|
1270
1231
|
The SessionContext with provider context populated.
|
|
1232
|
+
|
|
1233
|
+
Raises:
|
|
1234
|
+
MiddlewareException: If a context provider contributes function middleware,
|
|
1235
|
+
which cannot be applied to the SDK-owned tool loop.
|
|
1271
1236
|
"""
|
|
1272
1237
|
session_context = SessionContext(
|
|
1273
1238
|
session_id=session.session_id,
|
|
@@ -1286,6 +1251,12 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1286
1251
|
state=session.state.setdefault(provider.source_id, {}),
|
|
1287
1252
|
)
|
|
1288
1253
|
|
|
1254
|
+
if categorize_middleware(session_context.get_middleware())["function"]:
|
|
1255
|
+
raise MiddlewareException(
|
|
1256
|
+
"GitHubCopilotAgent does not support function middleware contributed by context providers. "
|
|
1257
|
+
"Use Agent with a chat client when function middleware is required."
|
|
1258
|
+
)
|
|
1259
|
+
|
|
1289
1260
|
return session_context
|
|
1290
1261
|
|
|
1291
1262
|
@staticmethod
|
|
@@ -1505,17 +1476,16 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1505
1476
|
``approval_mode="always_require"`` is delegated to the Copilot SDK's native
|
|
1506
1477
|
``on_pre_tool_use`` hook:
|
|
1507
1478
|
|
|
1508
|
-
- If the caller supplies their own
|
|
1509
|
-
``hooks`` dict or the convenience
|
|
1510
|
-
``options`` or ``default_options``) --
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
be automatically gated, since the caller's hooks are responsible for
|
|
1515
|
-
enforcing approval.
|
|
1479
|
+
- If the caller supplies their own ``on_pre_tool_use`` handler -- either in
|
|
1480
|
+
the SDK-native ``hooks`` dict or through the convenience option (via per-run
|
|
1481
|
+
``options`` or ``default_options``) -- it takes precedence. When both are
|
|
1482
|
+
given, the explicit ``hooks`` dict wins for any key it defines and the
|
|
1483
|
+
shortcut fills in that key otherwise. A warning names any approval-required
|
|
1484
|
+
tool that the caller's pre-tool-use handler is responsible for gating.
|
|
1516
1485
|
- Otherwise, when any approval-required tool is present, a default hook is
|
|
1517
1486
|
installed that returns ``"ask"`` for those tools (routing the decision to
|
|
1518
1487
|
``on_permission_request``) and defers (``None``) for all other tools.
|
|
1488
|
+
Unrelated caller hooks are preserved and do not replace this default.
|
|
1519
1489
|
- The default hook is **not** installed when the deprecated
|
|
1520
1490
|
``on_function_approval`` callback is configured: in that case approval is
|
|
1521
1491
|
enforced inside the tool handler (see :meth:`_tool_to_copilot_tool`) to
|
|
@@ -1532,12 +1502,12 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1532
1502
|
The hooks to register for the session, or ``None`` if none are needed.
|
|
1533
1503
|
"""
|
|
1534
1504
|
user_hook: PreToolUseHandler | None = options.get("on_pre_tool_use") or self._on_pre_tool_use
|
|
1535
|
-
caller_hooks:
|
|
1505
|
+
caller_hooks: SessionHooks | None = options.get("hooks")
|
|
1536
1506
|
|
|
1537
1507
|
# Combine caller-provided hooks: the SDK-native ``hooks`` dict plus the
|
|
1538
1508
|
# convenience ``on_pre_tool_use`` shortcut. The explicit dict wins for the
|
|
1539
1509
|
# keys it defines; the shortcut only fills in ``on_pre_tool_use`` otherwise.
|
|
1540
|
-
combined:
|
|
1510
|
+
combined: SessionHooks = {}
|
|
1541
1511
|
if user_hook is not None:
|
|
1542
1512
|
combined["on_pre_tool_use"] = user_hook
|
|
1543
1513
|
if caller_hooks:
|
|
@@ -1547,7 +1517,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1547
1517
|
tool.name for tool in all_tools if isinstance(tool, FunctionTool) and tool.approval_mode == "always_require"
|
|
1548
1518
|
}
|
|
1549
1519
|
|
|
1550
|
-
if combined:
|
|
1520
|
+
if combined.get("on_pre_tool_use") is not None:
|
|
1551
1521
|
if approval_required_names:
|
|
1552
1522
|
logger.warning(
|
|
1553
1523
|
"Custom session hooks are configured, so %d approval-required tool(s) (%s) "
|
|
@@ -1556,15 +1526,15 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1556
1526
|
len(approval_required_names),
|
|
1557
1527
|
", ".join(sorted(approval_required_names)),
|
|
1558
1528
|
)
|
|
1559
|
-
return
|
|
1529
|
+
return combined
|
|
1560
1530
|
|
|
1561
1531
|
if not approval_required_names:
|
|
1562
|
-
return None
|
|
1532
|
+
return combined or None
|
|
1563
1533
|
|
|
1564
1534
|
# The deprecated on_function_approval callback enforces approval in the tool
|
|
1565
1535
|
# handler; don't also install the default ask-hook (which would double-gate).
|
|
1566
1536
|
if self._function_approval_handler is not None:
|
|
1567
|
-
return None
|
|
1537
|
+
return combined or None
|
|
1568
1538
|
|
|
1569
1539
|
def default_pre_tool_use(
|
|
1570
1540
|
hook_input: Mapping[str, Any],
|
|
@@ -1580,7 +1550,8 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1580
1550
|
}
|
|
1581
1551
|
return None
|
|
1582
1552
|
|
|
1583
|
-
|
|
1553
|
+
combined["on_pre_tool_use"] = default_pre_tool_use
|
|
1554
|
+
return combined
|
|
1584
1555
|
|
|
1585
1556
|
def _session_run_lock(self, service_session_id: str) -> asyncio.Lock:
|
|
1586
1557
|
"""Return the lock that serializes runs on one Copilot service session."""
|
|
@@ -1735,6 +1706,11 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1735
1706
|
merged with the agent's tools and converted to SDK tools, and approval callbacks
|
|
1736
1707
|
are turned into ``hooks``.
|
|
1737
1708
|
|
|
1709
|
+
Forwarding options alone is not an authorization or revocation guarantee for
|
|
1710
|
+
capabilities already resident in a resumed SDK/CLI service session. Enforce
|
|
1711
|
+
authorization at the MCP service/tool boundary and use a fresh service session
|
|
1712
|
+
when narrowing configured capabilities.
|
|
1713
|
+
|
|
1738
1714
|
Args:
|
|
1739
1715
|
streaming: Whether to enable streaming for the session.
|
|
1740
1716
|
runtime_options: Runtime options that take precedence over default_options.
|
|
@@ -1873,10 +1849,10 @@ class GitHubCopilotAgent( # type: ignore[misc]
|
|
|
1873
1849
|
RawGitHubCopilotAgent[OptionsT],
|
|
1874
1850
|
Generic[OptionsT],
|
|
1875
1851
|
):
|
|
1876
|
-
"""A GitHub Copilot Agent with
|
|
1852
|
+
"""A GitHub Copilot Agent with agent middleware and telemetry support.
|
|
1877
1853
|
|
|
1878
1854
|
This is the recommended agent class for most use cases. It includes
|
|
1879
|
-
middleware
|
|
1855
|
+
agent middleware and OpenTelemetry-based telemetry for observability,
|
|
1880
1856
|
with middleware running outside the telemetry span so middleware execution
|
|
1881
1857
|
time is not captured in traces. For a minimal implementation without these
|
|
1882
1858
|
layers, use :class:`RawGitHubCopilotAgent`.
|
|
@@ -1926,7 +1902,7 @@ class GitHubCopilotAgent( # type: ignore[misc]
|
|
|
1926
1902
|
env_file_path: str | None = None,
|
|
1927
1903
|
env_file_encoding: str | None = None,
|
|
1928
1904
|
) -> None:
|
|
1929
|
-
"""Initialize a GitHub Copilot Agent with
|
|
1905
|
+
"""Initialize a GitHub Copilot Agent with agent middleware and telemetry.
|
|
1930
1906
|
|
|
1931
1907
|
Args:
|
|
1932
1908
|
instructions: System message for the agent.
|
{agent_framework_github_copilot-2.1.0 → agent_framework_github_copilot-2.1.2}/pyproject.toml
RENAMED
|
@@ -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.11"
|
|
7
|
-
version = "2.1.
|
|
7
|
+
version = "2.1.2"
|
|
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"
|
|
@@ -24,7 +24,7 @@ classifiers = [
|
|
|
24
24
|
]
|
|
25
25
|
dependencies = [
|
|
26
26
|
"agent-framework-core>=1.22.0,<2",
|
|
27
|
-
"github-copilot-sdk>=1.0.16,<2.0
|
|
27
|
+
"github-copilot-sdk>=1.0.16,<2.0",
|
|
28
28
|
]
|
|
29
29
|
|
|
30
30
|
[tool.uv]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|