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.
@@ -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, SubprocessConfig
41
- from copilot.generated.session_events import PermissionRequest, SessionEvent, SessionEventType
42
- from copilot.session import MCPServerConfig, PermissionRequestResult, ProviderConfig, SystemMessageConfig
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[[PermissionRequest, dict[str, str]], PermissionRequestResult]
61
- """Type for permission request handlers."""
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
- """Callback invoked by the agent before executing a FunctionTool that requires approval.
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 PermissionRequestResult()
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
- copilot_home: Directory where the CLI stores session state, configuration,
150
+ base_directory: Directory where the CLI stores session state, configuration,
144
151
  and other persistent data. Can be set via environment variable
145
- GITHUB_COPILOT_COPILOT_HOME. Defaults to ~/.copilot when not set.
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
- copilot_home: str | None
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
- """Approval callback for ``FunctionTool`` instances declared with
204
- ``approval_mode="always_require"``. The callback is awaited (sync or async)
205
- inside the SDK tool-handler before the tool is executed; a falsy return
206
- value denies the call. If omitted, calls to such tools are denied with an
207
- explanatory message returned to the model. This is independent of
208
- ``on_permission_request``, which gates the Copilot SDK's built-in actions."""
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
- copilot_home = opts.pop("copilot_home", None)
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
- copilot_home=copilot_home,
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
- copilot_home = self._settings.get("copilot_home") or None
417
+ base_directory = self._settings.get("base_directory") or None
366
418
 
367
- subprocess_kwargs: dict[str, Any] = {"cli_path": cli_path}
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
- subprocess_kwargs["log_level"] = log_level
370
- if copilot_home:
371
- subprocess_kwargs["copilot_home"] = copilot_home
372
- self._client = CopilotClient(SubprocessConfig(**subprocess_kwargs))
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, # type: ignore[override]
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, # type: ignore[arg-type]
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)) # type: ignore
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
- requires_approval = ai_func.approval_mode == "always_require"
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 requires_approval and not await _resolve_function_approval(approval_handler, ai_func, args):
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', %s)",
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=deny_text,
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.0b260521"
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.6.0,<2",
27
- "github-copilot-sdk>=1.0.0b2,<=1.0.0b2; python_version >= '3.11'",
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.