agent-framework-github-copilot 1.0.0rc1__tar.gz → 1.0.0rc3__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.0rc3
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.11.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,13 +7,9 @@ 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
- from typing import Any, ClassVar, Generic, Literal, TypedDict, overload
12
-
13
- if sys.version_info >= (3, 11):
14
- from typing import Self # pragma: no cover
15
- else:
16
- from typing_extensions import Self # pragma: no cover
12
+ from typing import Any, ClassVar, Generic, Literal, TypedDict, cast, overload
17
13
 
18
14
  from agent_framework import (
19
15
  AgentMiddlewareLayer,
@@ -28,6 +24,8 @@ from agent_framework import (
28
24
  Message,
29
25
  ResponseStream,
30
26
  SessionContext,
27
+ UsageDetails,
28
+ add_usage_details,
31
29
  normalize_messages,
32
30
  )
33
31
  from agent_framework._settings import load_settings
@@ -36,11 +34,28 @@ from agent_framework._types import AgentRunInputs, normalize_tools
36
34
  from agent_framework.exceptions import AgentException
37
35
  from agent_framework.observability import AgentTelemetryLayer
38
36
 
37
+ if sys.version_info >= (3, 11):
38
+ from typing import Self # pragma: no cover
39
+ else:
40
+ from typing_extensions import Self # pragma: no cover
41
+ if sys.version_info >= (3, 13):
42
+ from typing import TypeVar # pragma: no cover
43
+ else:
44
+ from typing_extensions import TypeVar # pragma: no cover
45
+
39
46
  try:
40
47
  from copilot import CopilotClient, CopilotSession, RuntimeConnection
41
48
  from copilot.generated.rpc import PermissionDecisionUserNotAvailable
42
- from copilot.session import MCPServerConfig, PermissionRequestResult, ProviderConfig, SystemMessageConfig
43
- from copilot.session_events import PermissionRequest, SessionEvent, SessionEventType
49
+ from copilot.session import (
50
+ MCPServerConfig,
51
+ PermissionRequestResult,
52
+ PreToolUseHandler,
53
+ PreToolUseHookOutput,
54
+ ProviderConfig,
55
+ SessionHooks,
56
+ SystemMessageConfig,
57
+ )
58
+ from copilot.session_events import AssistantUsageData, PermissionRequest, SessionEvent, SessionEventType
44
59
  from copilot.tools import Tool as CopilotTool
45
60
  from copilot.tools import ToolInvocation, ToolResult
46
61
  except ImportError as _copilot_import_error:
@@ -49,12 +64,6 @@ except ImportError as _copilot_import_error:
49
64
  "Please use Python 3.11 or later."
50
65
  ) from _copilot_import_error
51
66
 
52
- if sys.version_info >= (3, 13):
53
- from typing import TypeVar
54
- else:
55
- from typing_extensions import TypeVar
56
-
57
-
58
67
  DEFAULT_TIMEOUT_SECONDS: float = 60.0
59
68
  """Default timeout in seconds for Copilot requests."""
60
69
 
@@ -65,23 +74,18 @@ PermissionHandlerType = Callable[
65
74
 
66
75
 
67
76
  FunctionApprovalCallback = Callable[[Content], "bool | Awaitable[bool]"]
68
- """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``.
69
84
 
70
85
  The callback receives a ``FunctionCallContent`` describing the pending call
71
86
  (``name``, ``arguments``, and a synthetic ``call_id``) and must return ``True``
72
87
  to allow execution or ``False`` to deny it. Both synchronous and ``await``-able
73
88
  return values are supported.
74
-
75
- The Copilot CLI manages its own tool-calling loop, so the framework cannot
76
- round-trip a ``FunctionApprovalRequestContent`` / ``FunctionApprovalResponseContent``
77
- pair the way the standard chat-client pipeline does. This callback is the
78
- agent-level enforcement point for tools declared with
79
- ``approval_mode="always_require"``: when no callback is configured the agent
80
- denies these calls by default.
81
-
82
- Note: this is independent of ``on_permission_request``, which gates the
83
- Copilot SDK's *built-in* shell/file actions; ``on_function_approval`` gates
84
- agent-framework ``FunctionTool`` calls.
85
89
  """
86
90
 
87
91
 
@@ -90,7 +94,7 @@ async def _resolve_function_approval(
90
94
  func_tool: FunctionTool,
91
95
  arguments: Mapping[str, Any] | None,
92
96
  ) -> bool:
93
- """Run the agent-level approval callback for a pending tool call.
97
+ """Run the deprecated agent-level approval callback for a pending tool call.
94
98
 
95
99
  Returns ``True`` only when ``callback`` is configured and explicitly returns
96
100
  a truthy value. A missing callback or any callback failure is treated as a
@@ -202,13 +206,51 @@ class GitHubCopilotOptions(TypedDict, total=False):
202
206
  files beyond the default locations.
203
207
  """
204
208
 
209
+ skill_directories: list[str]
210
+ """Directories containing SKILL.md files to load into the Copilot CLI session.
211
+ These are loaded natively by the Copilot CLI process, letting applications point
212
+ the CLI at project-specific or team-shared skills beyond the default locations.
213
+ """
214
+
215
+ disabled_skills: list[str]
216
+ """Names of skills to disable for the session.
217
+ Lets applications opt out of specific skills that would otherwise be discovered
218
+ from ``skill_directories`` or the default locations.
219
+ """
220
+
221
+ base_directory: str
222
+ """Directory where the CLI stores session state, configuration, and other persistent data."""
223
+
224
+ on_pre_tool_use: PreToolUseHandler
225
+ """Pre-tool-use hook handler for the Copilot SDK.
226
+
227
+ Called by the Copilot SDK before any tool is executed. The handler receives a
228
+ ``PreToolUseHookInput`` and a context dict, and returns a ``PreToolUseHookOutput``
229
+ (or ``None`` to defer). Returning ``{"permissionDecision": "ask"}`` routes the
230
+ decision to ``on_permission_request``; ``"allow"`` / ``"deny"`` gate the call
231
+ directly.
232
+
233
+ If you do **not** supply this hook, the agent installs a default ``on_pre_tool_use``
234
+ hook that returns ``"ask"`` for ``FunctionTool`` instances declared with
235
+ ``approval_mode="always_require"`` (deferring all other tools), so those tools are
236
+ gated through ``on_permission_request``. If you **do** supply your own hook, it
237
+ takes precedence and **you** are responsible for enforcing approval for any
238
+ ``always_require`` tool; the agent logs a warning naming such tools."""
239
+
205
240
  on_function_approval: FunctionApprovalCallback
206
- """Approval callback for ``FunctionTool`` instances declared with
207
- ``approval_mode="always_require"``. The callback is awaited (sync or async)
208
- inside the SDK tool-handler before the tool is executed; a falsy return
209
- value denies the call. If omitted, calls to such tools are denied with an
210
- explanatory message returned to the model. This is independent of
211
- ``on_permission_request``, which gates the Copilot SDK's built-in actions."""
241
+ """Deprecated approval callback for ``FunctionTool`` instances declared with
242
+ ``approval_mode="always_require"``.
243
+
244
+ .. deprecated::
245
+ Use ``on_pre_tool_use`` together with ``on_permission_request`` instead.
246
+ When neither this callback nor ``on_pre_tool_use`` is set, the agent
247
+ installs a default ``on_pre_tool_use`` hook that returns ``"ask"`` for
248
+ ``always_require`` tools and routes the decision to ``on_permission_request``.
249
+
250
+ When set, this callback is enforced inside the SDK tool-handler before the tool
251
+ runs; a falsy return value denies the call. Setting it emits a
252
+ ``DeprecationWarning``. It is **mutually exclusive** with ``on_pre_tool_use`` —
253
+ setting both raises ``ValueError``."""
212
254
 
213
255
 
214
256
  OptionsT = TypeVar(
@@ -316,9 +358,29 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
316
358
  mcp_servers: dict[str, MCPServerConfig] | None = opts.pop("mcp_servers", None)
317
359
  provider: ProviderConfig | None = opts.pop("provider", None)
318
360
  instruction_directories: list[str] | None = opts.pop("instruction_directories", None)
361
+ skill_directories: list[str] | None = opts.pop("skill_directories", None)
362
+ disabled_skills: list[str] | None = opts.pop("disabled_skills", None)
363
+ on_pre_tool_use: PreToolUseHandler | None = opts.pop("on_pre_tool_use", None)
319
364
  on_function_approval: FunctionApprovalCallback | None = opts.pop("on_function_approval", None)
320
365
  base_directory = opts.pop("base_directory", None)
321
366
 
367
+ if on_function_approval is not None and on_pre_tool_use is not None:
368
+ raise ValueError(
369
+ "on_function_approval and on_pre_tool_use cannot both be set. "
370
+ "on_function_approval is deprecated; use on_pre_tool_use together with "
371
+ "on_permission_request instead."
372
+ )
373
+
374
+ if on_function_approval is not None:
375
+ warnings.warn(
376
+ "on_function_approval is deprecated and will be removed in a future version. "
377
+ "Use the SDK 'on_pre_tool_use' hook together with 'on_permission_request' instead: "
378
+ "the default 'on_pre_tool_use' hook returns 'ask' for approval_mode='always_require' "
379
+ "tools and routes the decision to 'on_permission_request'.",
380
+ DeprecationWarning,
381
+ stacklevel=2,
382
+ )
383
+
322
384
  self._settings = load_settings(
323
385
  GitHubCopilotSettings,
324
386
  env_prefix="GITHUB_COPILOT_",
@@ -333,10 +395,13 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
333
395
 
334
396
  self._tools = normalize_tools(tools)
335
397
  self._permission_handler = on_permission_request
398
+ self._on_pre_tool_use: PreToolUseHandler | None = on_pre_tool_use
336
399
  self._function_approval_handler: FunctionApprovalCallback | None = on_function_approval
337
400
  self._mcp_servers = mcp_servers
338
401
  self._provider = provider
339
402
  self._instruction_directories = instruction_directories
403
+ self._skill_directories = skill_directories
404
+ self._disabled_skills = disabled_skills
340
405
  self._default_options = opts
341
406
  self._started = False
342
407
 
@@ -441,7 +506,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
441
506
  session: AgentSession | None = None,
442
507
  middleware: Sequence[AgentMiddlewareTypes] | None = None,
443
508
  options: OptionsT | None = None,
444
- **kwargs: Any, # type: ignore[override]
509
+ **kwargs: Any,
445
510
  ) -> Awaitable[AgentResponse] | ResponseStream[AgentResponseUpdate, AgentResponse]:
446
511
  """Get a response from the agent.
447
512
 
@@ -498,6 +563,27 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
498
563
  )
499
564
  return self._run_impl(messages=messages, session=session, options=options)
500
565
 
566
+ @staticmethod
567
+ def _parse_usage_details_from_copilot(data: AssistantUsageData) -> UsageDetails | None:
568
+ total_token_count = (
569
+ data.input_tokens + data.output_tokens
570
+ if data.input_tokens is not None and data.output_tokens is not None
571
+ else None
572
+ )
573
+ usage_details = UsageDetails(**{
574
+ key: value
575
+ for key, value in {
576
+ "input_token_count": data.input_tokens,
577
+ "output_token_count": data.output_tokens,
578
+ "total_token_count": total_token_count,
579
+ "cache_read_input_token_count": data.cache_read_tokens,
580
+ "cache_creation_input_token_count": data.cache_write_tokens,
581
+ "reasoning_output_token_count": data.reasoning_tokens,
582
+ }.items()
583
+ if value is not None
584
+ })
585
+ return usage_details or None
586
+
501
587
  async def _run_impl(
502
588
  self,
503
589
  messages: AgentRunInputs | None = None,
@@ -519,6 +605,12 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
519
605
  "via default_options at agent construction time. It cannot be overridden "
520
606
  "per run."
521
607
  )
608
+ if "on_pre_tool_use" in opts and self._function_approval_handler is not None:
609
+ raise ValueError(
610
+ "on_pre_tool_use cannot be combined with the deprecated on_function_approval "
611
+ "(set via default_options). Remove on_function_approval and use on_pre_tool_use "
612
+ "together with on_permission_request instead."
613
+ )
522
614
  timeout = opts.get("timeout") or self._settings.get("timeout") or DEFAULT_TIMEOUT_SECONDS
523
615
 
524
616
  input_messages = normalize_messages(messages)
@@ -531,6 +623,27 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
531
623
  opts["tools"] = existing + list(session_context.tools)
532
624
 
533
625
  copilot_session = await self._get_or_create_session(session, streaming=False, runtime_options=opts)
626
+ usage_details: UsageDetails | None = None
627
+ finish_reason: str | None = None
628
+ model: str | None = None
629
+
630
+ def usage_event_handler(event: SessionEvent) -> None:
631
+ nonlocal usage_details, finish_reason, model
632
+ if event.type != SessionEventType.ASSISTANT_USAGE:
633
+ return
634
+ if isinstance(event.data, AssistantUsageData):
635
+ parsed_usage_details = self._parse_usage_details_from_copilot(event.data)
636
+ if parsed_usage_details:
637
+ usage_details = add_usage_details(usage_details, parsed_usage_details)
638
+ if event.data.finish_reason:
639
+ finish_reason = event.data.finish_reason
640
+ if event.data.model:
641
+ model = event.data.model
642
+ else:
643
+ logger.warning(
644
+ "Ignoring GitHub Copilot assistant usage event with unexpected payload type: %s",
645
+ type(event.data).__name__,
646
+ )
534
647
 
535
648
  # Build the prompt from the full set of messages in the session context,
536
649
  # so that any context/history provider-injected messages are included.
@@ -539,10 +652,13 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
539
652
  if session_context.instructions:
540
653
  prompt = "\n".join(session_context.instructions) + "\n" + prompt
541
654
 
655
+ unsubscribe = copilot_session.on(usage_event_handler)
542
656
  try:
543
657
  response_event = await copilot_session.send_and_wait(prompt, timeout=timeout)
544
658
  except Exception as ex:
545
659
  raise AgentException(f"GitHub Copilot request failed: {ex}") from ex
660
+ finally:
661
+ unsubscribe()
546
662
 
547
663
  response_messages: list[Message] = []
548
664
  response_id: str | None = None
@@ -564,7 +680,13 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
564
680
  )
565
681
  response_id = message_id
566
682
 
567
- response = AgentResponse(messages=response_messages, response_id=response_id)
683
+ response = AgentResponse(
684
+ messages=response_messages,
685
+ response_id=response_id,
686
+ finish_reason=cast(Any, finish_reason),
687
+ usage_details=usage_details,
688
+ additional_properties={"model": model} if model else None,
689
+ )
568
690
  session_context._response = response # type: ignore[assignment]
569
691
  await self._run_after_providers(session=session, context=session_context)
570
692
  return response
@@ -608,6 +730,12 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
608
730
  "via default_options at agent construction time. It cannot be overridden "
609
731
  "per run."
610
732
  )
733
+ if "on_pre_tool_use" in opts and self._function_approval_handler is not None:
734
+ raise ValueError(
735
+ "on_pre_tool_use cannot be combined with the deprecated on_function_approval "
736
+ "(set via default_options). Remove on_function_approval and use on_pre_tool_use "
737
+ "together with on_permission_request instead."
738
+ )
611
739
 
612
740
  input_messages = normalize_messages(messages)
613
741
 
@@ -644,6 +772,26 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
644
772
  raw_representation=event,
645
773
  )
646
774
  queue.put_nowait(update)
775
+ elif event.type == SessionEventType.ASSISTANT_USAGE:
776
+ if not isinstance(event.data, AssistantUsageData):
777
+ logger.warning(
778
+ "Ignoring GitHub Copilot assistant usage event with unexpected payload type: %s",
779
+ type(event.data).__name__,
780
+ )
781
+ return
782
+ usage_details = self._parse_usage_details_from_copilot(event.data)
783
+ finish_reason = event.data.finish_reason or None
784
+ model = event.data.model or None
785
+ if usage_details or finish_reason or model:
786
+ update = AgentResponseUpdate(
787
+ contents=[Content.from_usage(usage_details, raw_representation=event.data)]
788
+ if usage_details
789
+ else None,
790
+ finish_reason=cast(Any, finish_reason),
791
+ additional_properties={"model": model} if model else None,
792
+ raw_representation=event,
793
+ )
794
+ queue.put_nowait(update)
647
795
  elif event.type == SessionEventType.TOOL_EXECUTION_START:
648
796
  tool_call_id = getattr(event.data, "tool_call_id", None) or ""
649
797
  tool_name = getattr(event.data, "tool_name", None) or ""
@@ -732,7 +880,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
732
880
  if isinstance(provider, HistoryProvider) and not provider.load_messages:
733
881
  continue
734
882
  await provider.before_run(
735
- agent=self, # type: ignore[arg-type]
883
+ agent=self,
736
884
  session=session,
737
885
  context=session_context,
738
886
  state=session.state.setdefault(provider.source_id, {}),
@@ -781,7 +929,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
781
929
  if isinstance(tool, CopilotTool):
782
930
  copilot_tools.append(tool)
783
931
  elif isinstance(tool, FunctionTool):
784
- copilot_tools.append(self._tool_to_copilot_tool(tool)) # type: ignore
932
+ copilot_tools.append(self._tool_to_copilot_tool(tool))
785
933
  elif isinstance(tool, MutableMapping):
786
934
  copilot_tools.append(tool) # type: ignore[arg-type]
787
935
  # Note: Other tool types (e.g., dict-based hosted tools) are skipped
@@ -789,31 +937,32 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
789
937
  return copilot_tools
790
938
 
791
939
  def _tool_to_copilot_tool(self, ai_func: FunctionTool) -> CopilotTool:
792
- """Convert an FunctionTool to a Copilot SDK tool."""
940
+ """Convert an FunctionTool to a Copilot SDK tool.
941
+
942
+ Approval for tools declared with ``approval_mode="always_require"`` is normally
943
+ enforced by the Copilot SDK's native ``on_pre_tool_use`` hook (see
944
+ :meth:`_build_session_hooks`). When the deprecated ``on_function_approval``
945
+ callback is configured instead, approval is enforced inside this handler for
946
+ backward compatibility. (``on_function_approval`` and ``on_pre_tool_use`` are
947
+ mutually exclusive, so only one mechanism is ever active.)
948
+ """
793
949
  approval_handler = self._function_approval_handler
794
- requires_approval = ai_func.approval_mode == "always_require"
950
+ enforce = approval_handler is not None and ai_func.approval_mode == "always_require"
795
951
 
796
952
  async def handler(invocation: ToolInvocation) -> ToolResult:
797
953
  args: dict[str, Any] = invocation.arguments or {}
798
954
  try:
799
- if requires_approval and not await _resolve_function_approval(approval_handler, ai_func, args):
800
- deny_text = (
801
- f"Tool '{ai_func.name}' requires human approval "
802
- "(approval_mode='always_require') and the request was denied."
803
- if approval_handler is not None
804
- else (
805
- f"Tool '{ai_func.name}' requires human approval "
806
- "(approval_mode='always_require') but no on_function_approval "
807
- "callback is configured on the agent; the request was denied."
808
- )
809
- )
955
+ if enforce and not await _resolve_function_approval(approval_handler, ai_func, args):
810
956
  logger.info(
811
- "Denying execution of tool '%s' (approval_mode='always_require', %s)",
957
+ "Denying execution of tool '%s' (approval_mode='always_require', "
958
+ "on_function_approval callback denied).",
812
959
  ai_func.name,
813
- "callback denied" if approval_handler is not None else "no callback configured",
814
960
  )
815
961
  return ToolResult(
816
- text_result_for_llm=deny_text,
962
+ text_result_for_llm=(
963
+ f"Tool '{ai_func.name}' requires human approval "
964
+ "(approval_mode='always_require') and the request was denied."
965
+ ),
817
966
  result_type="failure",
818
967
  error="approval_denied",
819
968
  )
@@ -847,6 +996,80 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
847
996
  parameters=ai_func.parameters(),
848
997
  )
849
998
 
999
+ def _build_session_hooks(
1000
+ self,
1001
+ all_tools: Sequence[ToolTypes | CopilotTool],
1002
+ opts: Mapping[str, Any],
1003
+ ) -> SessionHooks | None:
1004
+ """Build the ``SessionHooks`` to pass to the Copilot SDK for this session.
1005
+
1006
+ Approval enforcement for ``FunctionTool`` instances declared with
1007
+ ``approval_mode="always_require"`` is delegated to the Copilot SDK's native
1008
+ ``on_pre_tool_use`` hook:
1009
+
1010
+ - If the caller supplies their own ``on_pre_tool_use`` (via per-run ``options``
1011
+ or ``default_options``), it takes precedence and is returned unchanged. A
1012
+ warning is logged naming any approval-required tool that will therefore not
1013
+ be automatically gated, since the caller's hook is responsible for enforcing
1014
+ approval.
1015
+ - Otherwise, when any approval-required tool is present, a default hook is
1016
+ installed that returns ``"ask"`` for those tools (routing the decision to
1017
+ ``on_permission_request``) and defers (``None``) for all other tools.
1018
+ - The default hook is **not** installed when the deprecated
1019
+ ``on_function_approval`` callback is configured: in that case approval is
1020
+ enforced inside the tool handler (see :meth:`_tool_to_copilot_tool`) to
1021
+ preserve backward-compatible behavior.
1022
+ - When there are no approval-required tools and no caller hook, ``None`` is
1023
+ returned so no hooks are registered.
1024
+
1025
+ Args:
1026
+ all_tools: The full set of tools resolved for the session.
1027
+ opts: Runtime options that take precedence over ``default_options``.
1028
+
1029
+ Returns:
1030
+ The hooks to register for the session, or ``None`` if none are needed.
1031
+ """
1032
+ user_hook: PreToolUseHandler | None = opts.get("on_pre_tool_use") or self._on_pre_tool_use
1033
+
1034
+ approval_required_names = {
1035
+ tool.name for tool in all_tools if isinstance(tool, FunctionTool) and tool.approval_mode == "always_require"
1036
+ }
1037
+
1038
+ if user_hook is not None:
1039
+ if approval_required_names:
1040
+ logger.warning(
1041
+ "A custom 'on_pre_tool_use' hook is configured, so %d approval-required tool(s) (%s) "
1042
+ "will not be automatically gated by GitHubCopilotAgent. The custom hook is responsible "
1043
+ "for enforcing approval (for example, by returning a 'deny' or 'ask' decision).",
1044
+ len(approval_required_names),
1045
+ ", ".join(sorted(approval_required_names)),
1046
+ )
1047
+ return {"on_pre_tool_use": user_hook}
1048
+
1049
+ if not approval_required_names:
1050
+ return None
1051
+
1052
+ # The deprecated on_function_approval callback enforces approval in the tool
1053
+ # handler; don't also install the default ask-hook (which would double-gate).
1054
+ if self._function_approval_handler is not None:
1055
+ return None
1056
+
1057
+ def default_pre_tool_use(
1058
+ hook_input: Mapping[str, Any],
1059
+ _context: Mapping[str, str],
1060
+ ) -> PreToolUseHookOutput | None:
1061
+ tool_name = hook_input.get("toolName")
1062
+ if tool_name in approval_required_names:
1063
+ return {
1064
+ "permissionDecision": "ask",
1065
+ "permissionDecisionReason": (
1066
+ f"Tool '{tool_name}' is marked as requiring approval (approval_mode='always_require')."
1067
+ ),
1068
+ }
1069
+ return None
1070
+
1071
+ return {"on_pre_tool_use": default_pre_tool_use}
1072
+
850
1073
  async def _get_or_create_session(
851
1074
  self,
852
1075
  agent_session: AgentSession,
@@ -871,7 +1094,12 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
871
1094
 
872
1095
  try:
873
1096
  if agent_session.service_session_id:
874
- return await self._resume_session(agent_session.service_session_id, streaming, runtime_options)
1097
+ service_session_id = agent_session.service_session_id
1098
+ if not isinstance(service_session_id, str):
1099
+ raise AgentException(
1100
+ "GitHubCopilotAgent expects a string service_session_id for session resumption."
1101
+ )
1102
+ return await self._resume_session(service_session_id, streaming, runtime_options)
875
1103
 
876
1104
  session = await self._create_session(streaming, runtime_options)
877
1105
  agent_session.service_session_id = session.session_id
@@ -902,8 +1130,11 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
902
1130
  mcp_servers = opts.get("mcp_servers") or self._mcp_servers or None
903
1131
  provider = opts.get("provider") or self._provider or None
904
1132
  instruction_directories = opts.get("instruction_directories", self._instruction_directories)
1133
+ skill_directories = opts.get("skill_directories", self._skill_directories)
1134
+ disabled_skills = opts.get("disabled_skills", self._disabled_skills)
905
1135
  all_tools = list(self._tools or []) + list(opts.get("tools") or [])
906
1136
  tools = self._prepare_tools(all_tools) if all_tools else None
1137
+ hooks = self._build_session_hooks(all_tools, opts)
907
1138
 
908
1139
  return await self._client.create_session(
909
1140
  on_permission_request=permission_handler,
@@ -914,6 +1145,9 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
914
1145
  mcp_servers=mcp_servers or None,
915
1146
  provider=provider or None,
916
1147
  instruction_directories=instruction_directories,
1148
+ skill_directories=skill_directories,
1149
+ disabled_skills=disabled_skills,
1150
+ hooks=hooks,
917
1151
  )
918
1152
 
919
1153
  async def _resume_session(
@@ -941,8 +1175,11 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
941
1175
  mcp_servers = opts.get("mcp_servers") or self._mcp_servers or None
942
1176
  provider = opts.get("provider") or self._provider or None
943
1177
  instruction_directories = opts.get("instruction_directories", self._instruction_directories)
1178
+ skill_directories = opts.get("skill_directories", self._skill_directories)
1179
+ disabled_skills = opts.get("disabled_skills", self._disabled_skills)
944
1180
  all_tools = list(self._tools or []) + list(opts.get("tools") or [])
945
1181
  tools = self._prepare_tools(all_tools) if all_tools else None
1182
+ hooks = self._build_session_hooks(all_tools, opts)
946
1183
 
947
1184
  return await self._client.resume_session(
948
1185
  session_id,
@@ -954,6 +1191,9 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
954
1191
  mcp_servers=mcp_servers or None,
955
1192
  provider=provider or None,
956
1193
  instruction_directories=instruction_directories,
1194
+ skill_directories=skill_directories,
1195
+ disabled_skills=disabled_skills,
1196
+ hooks=hooks,
957
1197
  )
958
1198
 
959
1199
 
@@ -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.0rc1"
7
+ version = "1.0.0rc3"
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.8.0,<2",
27
- "github-copilot-sdk>=1.0.0,<2; python_version >= '3.11'",
26
+ "agent-framework-core>=1.11.0,<2",
27
+ "github-copilot-sdk==1.0.2; python_version >= '3.11'",
28
28
  ]
29
29
 
30
30
  [tool.uv]
@@ -102,4 +102,3 @@ interpreter = "posix"
102
102
  [build-system]
103
103
  requires = ["flit-core >= 3.11,<4.0"]
104
104
  build-backend = "flit_core.buildapi"
105
-
@@ -1,37 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: agent-framework-github-copilot
3
- Version: 1.0.0rc1
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.8.0,<2
20
- Requires-Dist: github-copilot-sdk>=1.0.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
-
@@ -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.