agent-framework-github-copilot 1.0.0b260428__tar.gz → 1.0.0b260507__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agent_framework_github_copilot-1.0.0b260428 → agent_framework_github_copilot-1.0.0b260507}/PKG-INFO +3 -3
- {agent_framework_github_copilot-1.0.0b260428 → agent_framework_github_copilot-1.0.0b260507}/agent_framework_github_copilot/_agent.py +160 -15
- {agent_framework_github_copilot-1.0.0b260428 → agent_framework_github_copilot-1.0.0b260507}/pyproject.toml +3 -3
- {agent_framework_github_copilot-1.0.0b260428 → agent_framework_github_copilot-1.0.0b260507}/LICENSE +0 -0
- {agent_framework_github_copilot-1.0.0b260428 → agent_framework_github_copilot-1.0.0b260507}/README.md +0 -0
- {agent_framework_github_copilot-1.0.0b260428 → agent_framework_github_copilot-1.0.0b260507}/agent_framework_github_copilot/__init__.py +0 -0
{agent_framework_github_copilot-1.0.0b260428 → agent_framework_github_copilot-1.0.0b260507}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-framework-github-copilot
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.0b260507
|
|
4
4
|
Summary: GitHub Copilot integration for Microsoft Agent Framework.
|
|
5
5
|
Author-email: Microsoft <af-support@microsoft.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -16,8 +16,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
17
17
|
Classifier: Typing :: Typed
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: agent-framework-core>=1.
|
|
20
|
-
Requires-Dist: github-copilot-sdk>=0.
|
|
19
|
+
Requires-Dist: agent-framework-core>=1.3.0,<2
|
|
20
|
+
Requires-Dist: github-copilot-sdk>=1.0.0b2,<=1.0.0b2; python_version >= '3.11'
|
|
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
|
|
@@ -4,9 +4,10 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import contextlib
|
|
7
|
+
import inspect
|
|
7
8
|
import logging
|
|
8
9
|
import sys
|
|
9
|
-
from collections.abc import AsyncIterable, Awaitable, Callable, MutableMapping, Sequence
|
|
10
|
+
from collections.abc import AsyncIterable, Awaitable, Callable, Mapping, MutableMapping, Sequence
|
|
10
11
|
from typing import Any, ClassVar, Generic, Literal, TypedDict, overload
|
|
11
12
|
|
|
12
13
|
if sys.version_info >= (3, 11):
|
|
@@ -59,6 +60,59 @@ DEFAULT_TIMEOUT_SECONDS: float = 60.0
|
|
|
59
60
|
PermissionHandlerType = Callable[[PermissionRequest, dict[str, str]], PermissionRequestResult]
|
|
60
61
|
"""Type for permission request handlers."""
|
|
61
62
|
|
|
63
|
+
|
|
64
|
+
FunctionApprovalCallback = Callable[[Content], "bool | Awaitable[bool]"]
|
|
65
|
+
"""Callback invoked by the agent before executing a FunctionTool that requires approval.
|
|
66
|
+
|
|
67
|
+
The callback receives a ``FunctionCallContent`` describing the pending call
|
|
68
|
+
(``name``, ``arguments``, and a synthetic ``call_id``) and must return ``True``
|
|
69
|
+
to allow execution or ``False`` to deny it. Both synchronous and ``await``-able
|
|
70
|
+
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
|
+
"""
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def _resolve_function_approval(
|
|
86
|
+
callback: FunctionApprovalCallback | None,
|
|
87
|
+
func_tool: FunctionTool,
|
|
88
|
+
arguments: Mapping[str, Any] | None,
|
|
89
|
+
) -> bool:
|
|
90
|
+
"""Run the agent-level approval callback for a pending tool call.
|
|
91
|
+
|
|
92
|
+
Returns ``True`` only when ``callback`` is configured and explicitly returns
|
|
93
|
+
a truthy value. A missing callback or any callback failure is treated as a
|
|
94
|
+
denial so the secure-by-default policy holds even if the user code raises.
|
|
95
|
+
"""
|
|
96
|
+
if callback is None:
|
|
97
|
+
return False
|
|
98
|
+
request = Content.from_function_call(
|
|
99
|
+
call_id=f"af-copilot-approval::{func_tool.name}",
|
|
100
|
+
name=func_tool.name,
|
|
101
|
+
arguments=None if arguments is None else dict(arguments),
|
|
102
|
+
)
|
|
103
|
+
try:
|
|
104
|
+
outcome = callback(request)
|
|
105
|
+
if inspect.isawaitable(outcome):
|
|
106
|
+
outcome = await outcome
|
|
107
|
+
except Exception:
|
|
108
|
+
logger.exception(
|
|
109
|
+
"on_function_approval callback raised for tool '%s'; denying execution.",
|
|
110
|
+
func_tool.name,
|
|
111
|
+
)
|
|
112
|
+
return False
|
|
113
|
+
return bool(outcome)
|
|
114
|
+
|
|
115
|
+
|
|
62
116
|
logger = logging.getLogger("agent_framework.github_copilot")
|
|
63
117
|
|
|
64
118
|
|
|
@@ -86,12 +140,18 @@ class GitHubCopilotSettings(TypedDict, total=False):
|
|
|
86
140
|
Can be set via environment variable GITHUB_COPILOT_TIMEOUT.
|
|
87
141
|
log_level: CLI log level.
|
|
88
142
|
Can be set via environment variable GITHUB_COPILOT_LOG_LEVEL.
|
|
143
|
+
copilot_home: Directory where the CLI stores session state, configuration,
|
|
144
|
+
and other persistent data. Can be set via environment variable
|
|
145
|
+
GITHUB_COPILOT_COPILOT_HOME. Defaults to ~/.copilot when not set.
|
|
146
|
+
Only applicable when the SDK spawns the CLI process (ignored when
|
|
147
|
+
connecting to an external server via a pre-configured client).
|
|
89
148
|
"""
|
|
90
149
|
|
|
91
150
|
cli_path: str | None
|
|
92
151
|
model: str | None
|
|
93
152
|
timeout: float | None
|
|
94
153
|
log_level: str | None
|
|
154
|
+
copilot_home: str | None
|
|
95
155
|
|
|
96
156
|
|
|
97
157
|
class GitHubCopilotOptions(TypedDict, total=False):
|
|
@@ -133,6 +193,20 @@ class GitHubCopilotOptions(TypedDict, total=False):
|
|
|
133
193
|
instead of the default GitHub Copilot backend.
|
|
134
194
|
"""
|
|
135
195
|
|
|
196
|
+
instruction_directories: list[str]
|
|
197
|
+
"""Additional directories to search for custom instruction files.
|
|
198
|
+
Lets applications point the CLI at project-specific or team-shared instruction
|
|
199
|
+
files beyond the default locations.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
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."""
|
|
209
|
+
|
|
136
210
|
|
|
137
211
|
OptionsT = TypeVar(
|
|
138
212
|
"OptionsT",
|
|
@@ -238,6 +312,9 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
238
312
|
on_permission_request: PermissionHandlerType | None = opts.pop("on_permission_request", None)
|
|
239
313
|
mcp_servers: dict[str, MCPServerConfig] | None = opts.pop("mcp_servers", None)
|
|
240
314
|
provider: ProviderConfig | None = opts.pop("provider", None)
|
|
315
|
+
instruction_directories: list[str] | None = opts.pop("instruction_directories", None)
|
|
316
|
+
on_function_approval: FunctionApprovalCallback | None = opts.pop("on_function_approval", None)
|
|
317
|
+
copilot_home = opts.pop("copilot_home", None)
|
|
241
318
|
|
|
242
319
|
self._settings = load_settings(
|
|
243
320
|
GitHubCopilotSettings,
|
|
@@ -246,14 +323,17 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
246
323
|
model=model,
|
|
247
324
|
timeout=timeout,
|
|
248
325
|
log_level=log_level,
|
|
326
|
+
copilot_home=copilot_home,
|
|
249
327
|
env_file_path=env_file_path,
|
|
250
328
|
env_file_encoding=env_file_encoding,
|
|
251
329
|
)
|
|
252
330
|
|
|
253
331
|
self._tools = normalize_tools(tools)
|
|
254
332
|
self._permission_handler = on_permission_request
|
|
333
|
+
self._function_approval_handler: FunctionApprovalCallback | None = on_function_approval
|
|
255
334
|
self._mcp_servers = mcp_servers
|
|
256
335
|
self._provider = provider
|
|
336
|
+
self._instruction_directories = instruction_directories
|
|
257
337
|
self._default_options = opts
|
|
258
338
|
self._started = False
|
|
259
339
|
|
|
@@ -282,10 +362,13 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
282
362
|
if self._client is None:
|
|
283
363
|
cli_path = self._settings.get("cli_path") or None
|
|
284
364
|
log_level = self._settings.get("log_level") or None
|
|
365
|
+
copilot_home = self._settings.get("copilot_home") or None
|
|
285
366
|
|
|
286
367
|
subprocess_kwargs: dict[str, Any] = {"cli_path": cli_path}
|
|
287
368
|
if log_level:
|
|
288
369
|
subprocess_kwargs["log_level"] = log_level
|
|
370
|
+
if copilot_home:
|
|
371
|
+
subprocess_kwargs["copilot_home"] = copilot_home
|
|
289
372
|
self._client = CopilotClient(SubprocessConfig(**subprocess_kwargs))
|
|
290
373
|
|
|
291
374
|
try:
|
|
@@ -425,6 +508,12 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
425
508
|
session = self.create_session()
|
|
426
509
|
|
|
427
510
|
opts: dict[str, Any] = dict(options) if options else {}
|
|
511
|
+
if "on_function_approval" in opts:
|
|
512
|
+
raise ValueError(
|
|
513
|
+
"on_function_approval is a security-sensitive option and must be set "
|
|
514
|
+
"via default_options at agent construction time. It cannot be overridden "
|
|
515
|
+
"per run."
|
|
516
|
+
)
|
|
428
517
|
timeout = opts.get("timeout") or self._settings.get("timeout") or DEFAULT_TIMEOUT_SECONDS
|
|
429
518
|
|
|
430
519
|
input_messages = normalize_messages(messages)
|
|
@@ -453,13 +542,14 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
453
542
|
# send_and_wait returns only the final ASSISTANT_MESSAGE event;
|
|
454
543
|
# other events (deltas, tool calls) are handled internally by the SDK.
|
|
455
544
|
if response_event and response_event.type == SessionEventType.ASSISTANT_MESSAGE:
|
|
456
|
-
|
|
545
|
+
data: Any = response_event.data
|
|
546
|
+
message_id = data.message_id
|
|
457
547
|
|
|
458
|
-
if
|
|
548
|
+
if data.content:
|
|
459
549
|
response_messages.append(
|
|
460
550
|
Message(
|
|
461
551
|
role="assistant",
|
|
462
|
-
contents=[Content.from_text(
|
|
552
|
+
contents=[Content.from_text(data.content)],
|
|
463
553
|
message_id=message_id,
|
|
464
554
|
raw_representation=response_event,
|
|
465
555
|
)
|
|
@@ -504,6 +594,12 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
504
594
|
session = self.create_session()
|
|
505
595
|
|
|
506
596
|
opts: dict[str, Any] = dict(options) if options else {}
|
|
597
|
+
if "on_function_approval" in opts:
|
|
598
|
+
raise ValueError(
|
|
599
|
+
"on_function_approval is a security-sensitive option and must be set "
|
|
600
|
+
"via default_options at agent construction time. It cannot be overridden "
|
|
601
|
+
"per run."
|
|
602
|
+
)
|
|
507
603
|
|
|
508
604
|
input_messages = normalize_messages(messages)
|
|
509
605
|
|
|
@@ -527,12 +623,13 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
527
623
|
|
|
528
624
|
def event_handler(event: SessionEvent) -> None:
|
|
529
625
|
if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
|
|
530
|
-
|
|
626
|
+
data: Any = event.data
|
|
627
|
+
if data.delta_content:
|
|
531
628
|
update = AgentResponseUpdate(
|
|
532
629
|
role="assistant",
|
|
533
|
-
contents=[Content.from_text(
|
|
534
|
-
response_id=
|
|
535
|
-
message_id=
|
|
630
|
+
contents=[Content.from_text(data.delta_content)],
|
|
631
|
+
response_id=data.message_id,
|
|
632
|
+
message_id=data.message_id,
|
|
536
633
|
raw_representation=event,
|
|
537
634
|
)
|
|
538
635
|
queue.put_nowait(update)
|
|
@@ -576,7 +673,8 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
576
673
|
elif event.type == SessionEventType.SESSION_IDLE:
|
|
577
674
|
queue.put_nowait(None)
|
|
578
675
|
elif event.type == SessionEventType.SESSION_ERROR:
|
|
579
|
-
|
|
676
|
+
error_data: Any = event.data
|
|
677
|
+
error_msg = error_data.message or "Unknown error"
|
|
580
678
|
queue.put_nowait(AgentException(f"GitHub Copilot session error: {error_msg}"))
|
|
581
679
|
|
|
582
680
|
unsubscribe = copilot_session.on(event_handler)
|
|
@@ -681,10 +779,33 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
681
779
|
|
|
682
780
|
def _tool_to_copilot_tool(self, ai_func: FunctionTool) -> CopilotTool:
|
|
683
781
|
"""Convert an FunctionTool to a Copilot SDK tool."""
|
|
782
|
+
approval_handler = self._function_approval_handler
|
|
783
|
+
requires_approval = ai_func.approval_mode == "always_require"
|
|
684
784
|
|
|
685
785
|
async def handler(invocation: ToolInvocation) -> ToolResult:
|
|
686
786
|
args: dict[str, Any] = invocation.arguments or {}
|
|
687
787
|
try:
|
|
788
|
+
if requires_approval and not await _resolve_function_approval(approval_handler, ai_func, args):
|
|
789
|
+
deny_text = (
|
|
790
|
+
f"Tool '{ai_func.name}' requires human approval "
|
|
791
|
+
"(approval_mode='always_require') and the request was denied."
|
|
792
|
+
if approval_handler is not None
|
|
793
|
+
else (
|
|
794
|
+
f"Tool '{ai_func.name}' requires human approval "
|
|
795
|
+
"(approval_mode='always_require') but no on_function_approval "
|
|
796
|
+
"callback is configured on the agent; the request was denied."
|
|
797
|
+
)
|
|
798
|
+
)
|
|
799
|
+
logger.info(
|
|
800
|
+
"Denying execution of tool '%s' (approval_mode='always_require', %s)",
|
|
801
|
+
ai_func.name,
|
|
802
|
+
"callback denied" if approval_handler is not None else "no callback configured",
|
|
803
|
+
)
|
|
804
|
+
return ToolResult(
|
|
805
|
+
text_result_for_llm=deny_text,
|
|
806
|
+
result_type="failure",
|
|
807
|
+
error="approval_denied",
|
|
808
|
+
)
|
|
688
809
|
if ai_func.input_model:
|
|
689
810
|
args_instance = ai_func.input_model(**args)
|
|
690
811
|
result = await ai_func.invoke(arguments=args_instance)
|
|
@@ -739,7 +860,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
739
860
|
|
|
740
861
|
try:
|
|
741
862
|
if agent_session.service_session_id:
|
|
742
|
-
return await self._resume_session(agent_session.service_session_id, streaming)
|
|
863
|
+
return await self._resume_session(agent_session.service_session_id, streaming, runtime_options)
|
|
743
864
|
|
|
744
865
|
session = await self._create_session(streaming, runtime_options)
|
|
745
866
|
agent_session.service_session_id = session.session_id
|
|
@@ -769,6 +890,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
769
890
|
)
|
|
770
891
|
mcp_servers = opts.get("mcp_servers") or self._mcp_servers or None
|
|
771
892
|
provider = opts.get("provider") or self._provider or None
|
|
893
|
+
instruction_directories = opts.get("instruction_directories", self._instruction_directories)
|
|
772
894
|
tools = self._prepare_tools(self._tools) if self._tools else None
|
|
773
895
|
|
|
774
896
|
return await self._client.create_session(
|
|
@@ -779,23 +901,46 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
779
901
|
tools=tools or None,
|
|
780
902
|
mcp_servers=mcp_servers or None,
|
|
781
903
|
provider=provider or None,
|
|
904
|
+
instruction_directories=instruction_directories,
|
|
782
905
|
)
|
|
783
906
|
|
|
784
|
-
async def _resume_session(
|
|
785
|
-
|
|
907
|
+
async def _resume_session(
|
|
908
|
+
self,
|
|
909
|
+
session_id: str,
|
|
910
|
+
streaming: bool,
|
|
911
|
+
runtime_options: dict[str, Any] | None = None,
|
|
912
|
+
) -> CopilotSession:
|
|
913
|
+
"""Resume an existing Copilot session by ID.
|
|
914
|
+
|
|
915
|
+
Args:
|
|
916
|
+
session_id: The session ID to resume.
|
|
917
|
+
streaming: Whether to enable streaming for the session.
|
|
918
|
+
runtime_options: Runtime options that take precedence over default_options.
|
|
919
|
+
"""
|
|
786
920
|
if not self._client:
|
|
787
921
|
raise RuntimeError("GitHub Copilot client not initialized. Call start() first.")
|
|
788
922
|
|
|
789
|
-
|
|
923
|
+
opts = runtime_options or {}
|
|
924
|
+
model = opts.get("model") or self._settings.get("model") or None
|
|
925
|
+
system_message = opts.get("system_message") or self._default_options.get("system_message") or None
|
|
926
|
+
permission_handler: PermissionHandlerType = (
|
|
927
|
+
opts.get("on_permission_request") or self._permission_handler or _deny_all_permissions
|
|
928
|
+
)
|
|
929
|
+
mcp_servers = opts.get("mcp_servers") or self._mcp_servers or None
|
|
930
|
+
provider = opts.get("provider") or self._provider or None
|
|
931
|
+
instruction_directories = opts.get("instruction_directories", self._instruction_directories)
|
|
790
932
|
tools = self._prepare_tools(self._tools) if self._tools else None
|
|
791
933
|
|
|
792
934
|
return await self._client.resume_session(
|
|
793
935
|
session_id,
|
|
794
936
|
on_permission_request=permission_handler,
|
|
795
937
|
streaming=streaming,
|
|
938
|
+
model=model or None,
|
|
939
|
+
system_message=system_message or None,
|
|
796
940
|
tools=tools or None,
|
|
797
|
-
mcp_servers=
|
|
798
|
-
provider=
|
|
941
|
+
mcp_servers=mcp_servers or None,
|
|
942
|
+
provider=provider or None,
|
|
943
|
+
instruction_directories=instruction_directories,
|
|
799
944
|
)
|
|
800
945
|
|
|
801
946
|
|
|
@@ -4,7 +4,7 @@ description = "GitHub Copilot integration for Microsoft Agent Framework."
|
|
|
4
4
|
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.0b260507"
|
|
8
8
|
license-files = ["LICENSE"]
|
|
9
9
|
urls.homepage = "https://aka.ms/agent-framework"
|
|
10
10
|
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
|
@@ -23,8 +23,8 @@ classifiers = [
|
|
|
23
23
|
"Typing :: Typed",
|
|
24
24
|
]
|
|
25
25
|
dependencies = [
|
|
26
|
-
"agent-framework-core>=1.
|
|
27
|
-
"github-copilot-sdk>=0.
|
|
26
|
+
"agent-framework-core>=1.3.0,<2",
|
|
27
|
+
"github-copilot-sdk>=1.0.0b2,<=1.0.0b2; python_version >= '3.11'",
|
|
28
28
|
]
|
|
29
29
|
|
|
30
30
|
[tool.uv]
|
{agent_framework_github_copilot-1.0.0b260428 → agent_framework_github_copilot-1.0.0b260507}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|