agent-framework-github-copilot 2.1.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agent-framework-github-copilot
3
- Version: 2.1.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; python_version >= '3.11'
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
@@ -84,10 +95,9 @@ prompts) or a `domain` (URL prompts). Both are optional, so a bare
84
95
  `PermissionDecisionApproveForSession()` carries no scope at all and the Copilot CLI cannot
85
96
  interpret it.
86
97
 
87
- `GitHubCopilotAgent` therefore scopes such a decision automatically, using the request that
88
- triggered it — a shell prompt becomes an approval for that prompt's command identifiers, an
89
- MCP prompt an approval for that server and tool, a URL prompt an approval for that URL's
90
- 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:
91
101
 
92
102
  ```python
93
103
  from copilot.generated.rpc import PermissionDecisionApproveForSession
@@ -98,12 +108,13 @@ def on_permission_request(request, invocation):
98
108
  return PermissionDecisionApproveForSession()
99
109
  ```
100
110
 
101
- The decision is only ever narrowed, never widened. When the prompt reports that it cannot
102
- offer session-scoped approval (`can_offer_session_approval=False`), or the request kind has
103
- no session-scoped approval at all (such as a `hook` prompt), the decision is downgraded to a
104
- single-use approval and a warning is logged. Pass an explicit `approval=` or `domain=` when
105
- you want to approve something other than the request being handled — decisions that already
106
- specify a scope are forwarded unchanged.
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.
107
118
 
108
119
  ### Deprecated: `on_function_approval`
109
120
 
@@ -143,4 +154,20 @@ directory that defines hooks it is not loading. See
143
154
  [`github_copilot_with_file_hooks.py`](../../samples/02-agents/providers/github_copilot/github_copilot_with_file_hooks.py)
144
155
  for a runnable example.
145
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.
146
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
@@ -58,10 +69,9 @@ prompts) or a `domain` (URL prompts). Both are optional, so a bare
58
69
  `PermissionDecisionApproveForSession()` carries no scope at all and the Copilot CLI cannot
59
70
  interpret it.
60
71
 
61
- `GitHubCopilotAgent` therefore scopes such a decision automatically, using the request that
62
- triggered it — a shell prompt becomes an approval for that prompt's command identifiers, an
63
- MCP prompt an approval for that server and tool, a URL prompt an approval for that URL's
64
- 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:
65
75
 
66
76
  ```python
67
77
  from copilot.generated.rpc import PermissionDecisionApproveForSession
@@ -72,12 +82,13 @@ def on_permission_request(request, invocation):
72
82
  return PermissionDecisionApproveForSession()
73
83
  ```
74
84
 
75
- The decision is only ever narrowed, never widened. When the prompt reports that it cannot
76
- offer session-scoped approval (`can_offer_session_approval=False`), or the request kind has
77
- no session-scoped approval at all (such as a `hook` prompt), the decision is downgraded to a
78
- single-use approval and a warning is logged. Pass an explicit `approval=` or `domain=` when
79
- you want to approve something other than the request being handled — decisions that already
80
- specify a scope are forwarded unchanged.
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.
81
92
 
82
93
  ### Deprecated: `on_function_approval`
83
94
 
@@ -117,3 +128,19 @@ directory that defines hooks it is not loading. See
117
128
  [`github_copilot_with_file_hooks.py`](../../samples/02-agents/providers/github_copilot/github_copilot_with_file_hooks.py)
118
129
  for a runnable example.
119
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 the intended scope from ``request``.
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: when the prompt does not offer
323
- session-scoped approval, or the request kind has no session approval representation,
324
- the decision is downgraded to a single-use approval.
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 no unambiguous domain could be derived from '%s'. Approving this request only. Return "
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="replace",
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
@@ -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 full middleware and telemetry support.
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 support and OpenTelemetry-based telemetry for observability,
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 full middleware and telemetry.
1905
+ """Initialize a GitHub Copilot Agent with agent middleware and telemetry.
1930
1906
 
1931
1907
  Args:
1932
1908
  instructions: System message for the agent.
@@ -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.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; python_version >= '3.11'",
27
+ "github-copilot-sdk>=1.0.16,<2.0",
28
28
  ]
29
29
 
30
30
  [tool.uv]