agent-framework-github-copilot 2.0.0__tar.gz → 2.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agent_framework_github_copilot-2.0.0 → agent_framework_github_copilot-2.0.2}/PKG-INFO +3 -3
- {agent_framework_github_copilot-2.0.0 → agent_framework_github_copilot-2.0.2}/agent_framework_github_copilot/_agent.py +259 -37
- agent_framework_github_copilot-2.0.2/agent_framework_github_copilot/py.typed +0 -0
- {agent_framework_github_copilot-2.0.0 → agent_framework_github_copilot-2.0.2}/pyproject.toml +3 -3
- {agent_framework_github_copilot-2.0.0 → agent_framework_github_copilot-2.0.2}/LICENSE +0 -0
- {agent_framework_github_copilot-2.0.0 → agent_framework_github_copilot-2.0.2}/README.md +0 -0
- {agent_framework_github_copilot-2.0.0 → agent_framework_github_copilot-2.0.2}/agent_framework_github_copilot/__init__.py +0 -0
- {agent_framework_github_copilot-2.0.0 → agent_framework_github_copilot-2.0.2}/agent_framework_github_copilot/_feature_usage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-framework-github-copilot
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.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
|
|
@@ -15,8 +15,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
16
|
Classifier: Typing :: Typed
|
|
17
17
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: agent-framework-core>=1.
|
|
19
|
-
Requires-Dist: github-copilot-sdk
|
|
18
|
+
Requires-Dist: agent-framework-core>=1.21.0,<2
|
|
19
|
+
Requires-Dist: github-copilot-sdk>=1.0.16,<2.0; python_version >= '3.11'
|
|
20
20
|
Project-URL: homepage, https://aka.ms/agent-framework
|
|
21
21
|
Project-URL: issues, https://github.com/microsoft/agent-framework/issues
|
|
22
22
|
Project-URL: release_notes, https://github.com/microsoft/agent-framework/releases?q=tag%3Apython-1&expanded=true
|
|
@@ -9,7 +9,17 @@ import json
|
|
|
9
9
|
import logging
|
|
10
10
|
import sys
|
|
11
11
|
import warnings
|
|
12
|
-
|
|
12
|
+
import weakref
|
|
13
|
+
from collections.abc import (
|
|
14
|
+
AsyncGenerator,
|
|
15
|
+
AsyncIterable,
|
|
16
|
+
Awaitable,
|
|
17
|
+
Callable,
|
|
18
|
+
Collection,
|
|
19
|
+
Mapping,
|
|
20
|
+
MutableMapping,
|
|
21
|
+
Sequence,
|
|
22
|
+
)
|
|
13
23
|
from pathlib import Path
|
|
14
24
|
from typing import Any, ClassVar, Generic, Literal, TypedDict, cast, overload
|
|
15
25
|
from urllib.parse import urlparse
|
|
@@ -23,6 +33,7 @@ from agent_framework import (
|
|
|
23
33
|
BaseAgent,
|
|
24
34
|
Content,
|
|
25
35
|
ContextProvider,
|
|
36
|
+
FunctionInvocationContext,
|
|
26
37
|
HistoryProvider,
|
|
27
38
|
Message,
|
|
28
39
|
ResponseStream,
|
|
@@ -181,6 +192,21 @@ async def _resolve_function_approval(
|
|
|
181
192
|
|
|
182
193
|
logger = logging.getLogger("agent_framework.github_copilot")
|
|
183
194
|
|
|
195
|
+
|
|
196
|
+
def _provider_service_session_state_keys(agent: object, client: object | None) -> frozenset[str]:
|
|
197
|
+
"""Return provider-owned session-state keys declared by the Copilot agent or client."""
|
|
198
|
+
keys: set[str] = set()
|
|
199
|
+
for owner in (agent, client):
|
|
200
|
+
declared = getattr(owner, "service_session_state_keys", ())
|
|
201
|
+
if isinstance(declared, (list, tuple, set, frozenset)):
|
|
202
|
+
keys.update(key for key in cast(Collection[Any], declared) if isinstance(key, str))
|
|
203
|
+
return frozenset(keys)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# One lock per (CopilotClient, service session ID), shared across agents that share a client.
|
|
207
|
+
# Entries disappear once no run holds or awaits the lock.
|
|
208
|
+
_SESSION_RUN_LOCKS: weakref.WeakValueDictionary[tuple[int, str], asyncio.Lock] = weakref.WeakValueDictionary()
|
|
209
|
+
|
|
184
210
|
_MCP_TOOL_MESSAGE = (
|
|
185
211
|
"MCP server '{name}' cannot be passed to GitHubCopilotAgent as a tool: the Copilot SDK "
|
|
186
212
|
"connects to MCP servers itself, so a framework-managed MCPTool would keep none of its "
|
|
@@ -813,6 +839,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
813
839
|
session: AgentSession | None = None,
|
|
814
840
|
middleware: Sequence[AgentMiddlewareTypes] | None = None,
|
|
815
841
|
options: OptionsT | None = None,
|
|
842
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
816
843
|
**kwargs: Any,
|
|
817
844
|
) -> Awaitable[AgentResponse]: ...
|
|
818
845
|
|
|
@@ -825,6 +852,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
825
852
|
session: AgentSession | None = None,
|
|
826
853
|
middleware: Sequence[AgentMiddlewareTypes] | None = None,
|
|
827
854
|
options: OptionsT | None = None,
|
|
855
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
828
856
|
**kwargs: Any,
|
|
829
857
|
) -> ResponseStream[AgentResponseUpdate, AgentResponse]: ...
|
|
830
858
|
|
|
@@ -836,6 +864,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
836
864
|
session: AgentSession | None = None,
|
|
837
865
|
middleware: Sequence[AgentMiddlewareTypes] | None = None,
|
|
838
866
|
options: OptionsT | None = None,
|
|
867
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
839
868
|
**kwargs: Any,
|
|
840
869
|
) -> Awaitable[AgentResponse] | ResponseStream[AgentResponseUpdate, AgentResponse]:
|
|
841
870
|
"""Get a response from the agent.
|
|
@@ -854,6 +883,9 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
854
883
|
compatibility; pass middleware via :class:`GitHubCopilotAgent` which
|
|
855
884
|
forwards it through :class:`AgentTelemetryLayer`.
|
|
856
885
|
options: Runtime options (model, timeout, etc.).
|
|
886
|
+
function_invocation_kwargs: Keyword arguments forwarded to tool invocations.
|
|
887
|
+
Tools receive them through ``FunctionInvocationContext.kwargs`` when they
|
|
888
|
+
declare a ``FunctionInvocationContext`` parameter.
|
|
857
889
|
kwargs: Additional keyword arguments for compatibility with the shared agent
|
|
858
890
|
interface (e.g. compaction_strategy, tokenizer). Not used by this agent.
|
|
859
891
|
|
|
@@ -887,11 +919,22 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
887
919
|
return AgentResponse.from_updates(updates)
|
|
888
920
|
|
|
889
921
|
return ResponseStream(
|
|
890
|
-
self._stream_updates(
|
|
922
|
+
self._stream_updates(
|
|
923
|
+
messages=messages,
|
|
924
|
+
session=session,
|
|
925
|
+
options=options,
|
|
926
|
+
function_invocation_kwargs=function_invocation_kwargs,
|
|
927
|
+
_ctx_holder=ctx_holder,
|
|
928
|
+
),
|
|
891
929
|
finalizer=_finalize,
|
|
892
930
|
result_hooks=[_after_run_hook],
|
|
893
931
|
)
|
|
894
|
-
return self._run_impl(
|
|
932
|
+
return self._run_impl(
|
|
933
|
+
messages=messages,
|
|
934
|
+
session=session,
|
|
935
|
+
options=options,
|
|
936
|
+
function_invocation_kwargs=function_invocation_kwargs,
|
|
937
|
+
)
|
|
895
938
|
|
|
896
939
|
@staticmethod
|
|
897
940
|
def _parse_usage_details_from_copilot(data: AssistantUsageData) -> UsageDetails | None:
|
|
@@ -920,6 +963,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
920
963
|
*,
|
|
921
964
|
session: AgentSession | None = None,
|
|
922
965
|
options: OptionsT | None = None,
|
|
966
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
923
967
|
) -> AgentResponse:
|
|
924
968
|
"""Non-streaming implementation of run."""
|
|
925
969
|
if not self._started:
|
|
@@ -952,7 +996,6 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
952
996
|
existing = list(opts.get("tools") or [])
|
|
953
997
|
opts["tools"] = existing + list(session_context.tools)
|
|
954
998
|
|
|
955
|
-
copilot_session = await self._get_or_create_session(session, streaming=False, runtime_options=opts)
|
|
956
999
|
usage_details: UsageDetails | None = None
|
|
957
1000
|
finish_reason: str | None = None
|
|
958
1001
|
model: str | None = None
|
|
@@ -986,14 +1029,20 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
986
1029
|
prompt = "\n".join(session_context.instructions) + "\n" + prompt
|
|
987
1030
|
attachments = self._prepare_attachments_for_copilot(context_messages)
|
|
988
1031
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1032
|
+
async with self._session_run_scope(
|
|
1033
|
+
session,
|
|
1034
|
+
streaming=False,
|
|
1035
|
+
runtime_options=opts,
|
|
1036
|
+
function_invocation_kwargs=function_invocation_kwargs,
|
|
1037
|
+
) as copilot_session:
|
|
1038
|
+
unsubscribe = copilot_session.on(usage_event_handler)
|
|
1039
|
+
try:
|
|
1040
|
+
mark_feature_used(FeatureIndex.GITHUB_COPILOT)
|
|
1041
|
+
response_event = await copilot_session.send_and_wait(prompt, attachments=attachments, timeout=timeout)
|
|
1042
|
+
except Exception as ex:
|
|
1043
|
+
raise AgentException(f"GitHub Copilot request failed: {ex}") from ex
|
|
1044
|
+
finally:
|
|
1045
|
+
unsubscribe()
|
|
997
1046
|
|
|
998
1047
|
response_messages: list[Message] = []
|
|
999
1048
|
response_id: str | None = None
|
|
@@ -1032,6 +1081,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1032
1081
|
*,
|
|
1033
1082
|
session: AgentSession | None = None,
|
|
1034
1083
|
options: OptionsT | None = None,
|
|
1084
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
1035
1085
|
_ctx_holder: dict[str, Any] | None = None,
|
|
1036
1086
|
) -> AsyncIterable[AgentResponseUpdate]:
|
|
1037
1087
|
"""Internal method to stream updates from GitHub Copilot.
|
|
@@ -1042,6 +1092,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1042
1092
|
Keyword Args:
|
|
1043
1093
|
session: The conversation session associated with the message(s).
|
|
1044
1094
|
options: Runtime options (model, timeout, etc.).
|
|
1095
|
+
function_invocation_kwargs: Keyword arguments forwarded to tool invocations.
|
|
1045
1096
|
_ctx_holder: Internal dict populated with session_context and session
|
|
1046
1097
|
so that the caller (via a ResponseStream result_hook) can run
|
|
1047
1098
|
after_run providers without duplicating the updates buffer.
|
|
@@ -1081,12 +1132,6 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1081
1132
|
existing = list(opts.get("tools") or [])
|
|
1082
1133
|
opts["tools"] = existing + list(session_context.tools)
|
|
1083
1134
|
|
|
1084
|
-
copilot_session = await self._get_or_create_session(session, streaming=True, runtime_options=opts)
|
|
1085
|
-
|
|
1086
|
-
if _ctx_holder is not None:
|
|
1087
|
-
_ctx_holder["session_context"] = session_context
|
|
1088
|
-
_ctx_holder["session"] = session
|
|
1089
|
-
|
|
1090
1135
|
# Build the prompt from the full session context so provider-injected messages are included.
|
|
1091
1136
|
context_messages = session_context.get_messages(include_input=True)
|
|
1092
1137
|
prompt = "\n".join([message.text for message in context_messages])
|
|
@@ -1172,18 +1217,28 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1172
1217
|
error_msg = error_data.message or "Unknown error"
|
|
1173
1218
|
queue.put_nowait(AgentException(f"GitHub Copilot session error: {error_msg}"))
|
|
1174
1219
|
|
|
1175
|
-
|
|
1220
|
+
async with self._session_run_scope(
|
|
1221
|
+
session,
|
|
1222
|
+
streaming=True,
|
|
1223
|
+
runtime_options=opts,
|
|
1224
|
+
function_invocation_kwargs=function_invocation_kwargs,
|
|
1225
|
+
) as copilot_session:
|
|
1226
|
+
if _ctx_holder is not None:
|
|
1227
|
+
_ctx_holder["session_context"] = session_context
|
|
1228
|
+
_ctx_holder["session"] = session
|
|
1176
1229
|
|
|
1177
|
-
|
|
1178
|
-
mark_feature_used(FeatureIndex.GITHUB_COPILOT)
|
|
1179
|
-
await copilot_session.send(prompt, attachments=attachments)
|
|
1230
|
+
unsubscribe = copilot_session.on(event_handler)
|
|
1180
1231
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1232
|
+
try:
|
|
1233
|
+
mark_feature_used(FeatureIndex.GITHUB_COPILOT)
|
|
1234
|
+
await copilot_session.send(prompt, attachments=attachments)
|
|
1235
|
+
|
|
1236
|
+
while (item := await queue.get()) is not None:
|
|
1237
|
+
if isinstance(item, Exception):
|
|
1238
|
+
raise item
|
|
1239
|
+
yield item
|
|
1240
|
+
finally:
|
|
1241
|
+
unsubscribe()
|
|
1187
1242
|
|
|
1188
1243
|
async def _run_before_providers(
|
|
1189
1244
|
self,
|
|
@@ -1301,40 +1356,77 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1301
1356
|
def _prepare_tools(
|
|
1302
1357
|
self,
|
|
1303
1358
|
tools: Sequence[ToolTypes | CopilotTool],
|
|
1359
|
+
*,
|
|
1360
|
+
agent_session: AgentSession | None = None,
|
|
1361
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
1304
1362
|
) -> list[CopilotTool]:
|
|
1305
1363
|
"""Convert Agent Framework tools to Copilot SDK tools.
|
|
1306
1364
|
|
|
1307
1365
|
Args:
|
|
1308
1366
|
tools: List of Agent Framework tools.
|
|
1309
1367
|
|
|
1368
|
+
Keyword Args:
|
|
1369
|
+
agent_session: The conversation session, exposed to tools through their
|
|
1370
|
+
``FunctionInvocationContext``.
|
|
1371
|
+
function_invocation_kwargs: Keyword arguments forwarded to tool invocations.
|
|
1372
|
+
|
|
1310
1373
|
Returns:
|
|
1311
1374
|
List of Copilot SDK tools.
|
|
1312
1375
|
"""
|
|
1313
1376
|
copilot_tools: list[CopilotTool] = []
|
|
1377
|
+
parent_service_session_state_keys = _provider_service_session_state_keys(self, self._client)
|
|
1314
1378
|
|
|
1315
1379
|
for tool in tools:
|
|
1316
1380
|
if isinstance(tool, CopilotTool):
|
|
1317
1381
|
copilot_tools.append(tool)
|
|
1318
1382
|
elif isinstance(tool, FunctionTool):
|
|
1319
|
-
copilot_tools.append(
|
|
1383
|
+
copilot_tools.append(
|
|
1384
|
+
self._tool_to_copilot_tool(
|
|
1385
|
+
tool,
|
|
1386
|
+
agent_session=agent_session,
|
|
1387
|
+
function_invocation_kwargs=function_invocation_kwargs,
|
|
1388
|
+
parent_service_session_state_keys=parent_service_session_state_keys,
|
|
1389
|
+
)
|
|
1390
|
+
)
|
|
1320
1391
|
elif isinstance(tool, MutableMapping):
|
|
1321
1392
|
copilot_tools.append(tool) # type: ignore[arg-type]
|
|
1322
1393
|
# Note: Other tool types (e.g., dict-based hosted tools) are skipped
|
|
1323
1394
|
|
|
1324
1395
|
return copilot_tools
|
|
1325
1396
|
|
|
1326
|
-
def _tool_to_copilot_tool(
|
|
1397
|
+
def _tool_to_copilot_tool(
|
|
1398
|
+
self,
|
|
1399
|
+
ai_func: FunctionTool,
|
|
1400
|
+
*,
|
|
1401
|
+
agent_session: AgentSession | None = None,
|
|
1402
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
1403
|
+
parent_service_session_state_keys: frozenset[str] = frozenset(),
|
|
1404
|
+
) -> CopilotTool:
|
|
1327
1405
|
"""Convert an FunctionTool to a Copilot SDK tool.
|
|
1328
1406
|
|
|
1407
|
+
Each invocation receives a ``FunctionInvocationContext`` carrying the run's
|
|
1408
|
+
``function_invocation_kwargs`` and ``agent_session``, so tools that declare a
|
|
1409
|
+
``FunctionInvocationContext`` parameter can read them the same way they do
|
|
1410
|
+
with a chat-client-backed agent.
|
|
1411
|
+
|
|
1329
1412
|
Approval for tools declared with ``approval_mode="always_require"`` is normally
|
|
1330
1413
|
enforced by the Copilot SDK's native ``on_pre_tool_use`` hook (see
|
|
1331
1414
|
:meth:`_build_session_hooks`). When the deprecated ``on_function_approval``
|
|
1332
1415
|
callback is configured instead, approval is enforced inside this handler for
|
|
1333
1416
|
backward compatibility. (``on_function_approval`` and ``on_pre_tool_use`` are
|
|
1334
1417
|
mutually exclusive, so only one mechanism is ever active.)
|
|
1418
|
+
|
|
1419
|
+
Args:
|
|
1420
|
+
ai_func: Agent Framework function tool to convert.
|
|
1421
|
+
|
|
1422
|
+
Keyword Args:
|
|
1423
|
+
agent_session: Parent agent session passed to the tool.
|
|
1424
|
+
function_invocation_kwargs: Runtime keyword arguments forwarded to the tool.
|
|
1425
|
+
parent_service_session_state_keys: Provider-owned state declared by the parent agent and client.
|
|
1335
1426
|
"""
|
|
1336
1427
|
approval_handler = self._function_approval_handler
|
|
1337
1428
|
enforce = approval_handler is not None and ai_func.approval_mode == "always_require"
|
|
1429
|
+
runtime_kwargs = dict(function_invocation_kwargs) if function_invocation_kwargs is not None else {}
|
|
1338
1430
|
|
|
1339
1431
|
async def handler(invocation: ToolInvocation) -> ToolResult:
|
|
1340
1432
|
args: dict[str, Any] = invocation.arguments or {}
|
|
@@ -1353,11 +1445,22 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1353
1445
|
result_type="failure",
|
|
1354
1446
|
error="approval_denied",
|
|
1355
1447
|
)
|
|
1448
|
+
context = FunctionInvocationContext(
|
|
1449
|
+
function=ai_func,
|
|
1450
|
+
arguments=args,
|
|
1451
|
+
session=agent_session,
|
|
1452
|
+
kwargs=runtime_kwargs,
|
|
1453
|
+
parent_service_session_state_keys=parent_service_session_state_keys,
|
|
1454
|
+
)
|
|
1356
1455
|
if ai_func.input_model:
|
|
1357
1456
|
args_instance = ai_func.input_model(**args)
|
|
1358
|
-
result = await ai_func.invoke(
|
|
1457
|
+
result = await ai_func.invoke(
|
|
1458
|
+
arguments=args_instance, context=context, tool_call_id=invocation.tool_call_id or None
|
|
1459
|
+
)
|
|
1359
1460
|
else:
|
|
1360
|
-
result = await ai_func.invoke(
|
|
1461
|
+
result = await ai_func.invoke(
|
|
1462
|
+
arguments=args, context=context, tool_call_id=invocation.tool_call_id or None
|
|
1463
|
+
)
|
|
1361
1464
|
rich = [c for c in result if c.type in ("data", "uri")]
|
|
1362
1465
|
if rich:
|
|
1363
1466
|
logger.warning(
|
|
@@ -1471,11 +1574,73 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1471
1574
|
|
|
1472
1575
|
return {"on_pre_tool_use": default_pre_tool_use}
|
|
1473
1576
|
|
|
1577
|
+
def _session_run_lock(self, service_session_id: str) -> asyncio.Lock:
|
|
1578
|
+
"""Return the lock that serializes runs on one Copilot service session."""
|
|
1579
|
+
key = (id(self._client), service_session_id)
|
|
1580
|
+
lock = _SESSION_RUN_LOCKS.get(key)
|
|
1581
|
+
if lock is None:
|
|
1582
|
+
lock = asyncio.Lock()
|
|
1583
|
+
_SESSION_RUN_LOCKS[key] = lock
|
|
1584
|
+
return lock
|
|
1585
|
+
|
|
1586
|
+
@contextlib.asynccontextmanager
|
|
1587
|
+
async def _session_run_scope(
|
|
1588
|
+
self,
|
|
1589
|
+
agent_session: AgentSession,
|
|
1590
|
+
*,
|
|
1591
|
+
streaming: bool,
|
|
1592
|
+
runtime_options: dict[str, Any],
|
|
1593
|
+
function_invocation_kwargs: Mapping[str, Any] | None,
|
|
1594
|
+
) -> AsyncGenerator[CopilotSession]:
|
|
1595
|
+
"""Open the Copilot session for one run and hold it exclusively until the run ends.
|
|
1596
|
+
|
|
1597
|
+
The Copilot SDK keeps a single session object per service session ID and
|
|
1598
|
+
re-registers tools on it on every resume, dispatching tool calls by session ID.
|
|
1599
|
+
Tool handlers capture the run's ``function_invocation_kwargs`` and session, so
|
|
1600
|
+
an overlapping resume would let one run's tool calls execute with another run's
|
|
1601
|
+
context. Runs on the same service session are therefore serialized.
|
|
1602
|
+
|
|
1603
|
+
Args:
|
|
1604
|
+
agent_session: The conversation session.
|
|
1605
|
+
|
|
1606
|
+
Keyword Args:
|
|
1607
|
+
streaming: Whether to enable streaming for the session.
|
|
1608
|
+
runtime_options: Runtime options from run that take precedence.
|
|
1609
|
+
function_invocation_kwargs: Keyword arguments forwarded to tool invocations.
|
|
1610
|
+
|
|
1611
|
+
Yields:
|
|
1612
|
+
The created or resumed CopilotSession.
|
|
1613
|
+
"""
|
|
1614
|
+
lock: asyncio.Lock | None = None
|
|
1615
|
+
service_session_id = agent_session.service_session_id
|
|
1616
|
+
if isinstance(service_session_id, str):
|
|
1617
|
+
resume_lock = self._session_run_lock(service_session_id)
|
|
1618
|
+
await resume_lock.acquire()
|
|
1619
|
+
lock = resume_lock
|
|
1620
|
+
try:
|
|
1621
|
+
copilot_session = await self._get_or_create_session(
|
|
1622
|
+
agent_session,
|
|
1623
|
+
streaming=streaming,
|
|
1624
|
+
runtime_options=runtime_options,
|
|
1625
|
+
function_invocation_kwargs=function_invocation_kwargs,
|
|
1626
|
+
)
|
|
1627
|
+
if lock is None:
|
|
1628
|
+
# A newly created service session ID is not known to any other run yet,
|
|
1629
|
+
# so this acquire never waits.
|
|
1630
|
+
create_lock = self._session_run_lock(copilot_session.session_id)
|
|
1631
|
+
await create_lock.acquire()
|
|
1632
|
+
lock = create_lock
|
|
1633
|
+
yield copilot_session
|
|
1634
|
+
finally:
|
|
1635
|
+
if lock is not None:
|
|
1636
|
+
lock.release()
|
|
1637
|
+
|
|
1474
1638
|
async def _get_or_create_session(
|
|
1475
1639
|
self,
|
|
1476
1640
|
agent_session: AgentSession,
|
|
1477
1641
|
streaming: bool = False,
|
|
1478
1642
|
runtime_options: dict[str, Any] | None = None,
|
|
1643
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
1479
1644
|
) -> CopilotSession:
|
|
1480
1645
|
"""Get an existing session or create a new one for the session.
|
|
1481
1646
|
|
|
@@ -1483,6 +1648,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1483
1648
|
agent_session: The conversation session.
|
|
1484
1649
|
streaming: Whether to enable streaming for the session.
|
|
1485
1650
|
runtime_options: Runtime options from run that take precedence.
|
|
1651
|
+
function_invocation_kwargs: Keyword arguments forwarded to tool invocations.
|
|
1486
1652
|
|
|
1487
1653
|
Returns:
|
|
1488
1654
|
A CopilotSession instance.
|
|
@@ -1500,9 +1666,20 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1500
1666
|
raise AgentException(
|
|
1501
1667
|
"GitHubCopilotAgent expects a string service_session_id for session resumption."
|
|
1502
1668
|
)
|
|
1503
|
-
return await self._resume_session(
|
|
1669
|
+
return await self._resume_session(
|
|
1670
|
+
service_session_id,
|
|
1671
|
+
streaming,
|
|
1672
|
+
runtime_options,
|
|
1673
|
+
agent_session=agent_session,
|
|
1674
|
+
function_invocation_kwargs=function_invocation_kwargs,
|
|
1675
|
+
)
|
|
1504
1676
|
|
|
1505
|
-
session = await self._create_session(
|
|
1677
|
+
session = await self._create_session(
|
|
1678
|
+
streaming,
|
|
1679
|
+
runtime_options,
|
|
1680
|
+
agent_session=agent_session,
|
|
1681
|
+
function_invocation_kwargs=function_invocation_kwargs,
|
|
1682
|
+
)
|
|
1506
1683
|
agent_session.service_session_id = session.session_id
|
|
1507
1684
|
return session
|
|
1508
1685
|
except Exception as ex:
|
|
@@ -1533,6 +1710,9 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1533
1710
|
self,
|
|
1534
1711
|
streaming: bool,
|
|
1535
1712
|
runtime_options: dict[str, Any] | None,
|
|
1713
|
+
*,
|
|
1714
|
+
agent_session: AgentSession | None = None,
|
|
1715
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
1536
1716
|
) -> dict[str, Any]:
|
|
1537
1717
|
"""Assemble keyword arguments for ``create_session`` / ``resume_session``.
|
|
1538
1718
|
|
|
@@ -1551,6 +1731,11 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1551
1731
|
streaming: Whether to enable streaming for the session.
|
|
1552
1732
|
runtime_options: Runtime options that take precedence over default_options.
|
|
1553
1733
|
|
|
1734
|
+
Keyword Args:
|
|
1735
|
+
agent_session: The conversation session, exposed to tools through their
|
|
1736
|
+
``FunctionInvocationContext``.
|
|
1737
|
+
function_invocation_kwargs: Keyword arguments forwarded to tool invocations.
|
|
1738
|
+
|
|
1554
1739
|
Returns:
|
|
1555
1740
|
The keyword arguments to splat into the SDK session factory.
|
|
1556
1741
|
"""
|
|
@@ -1565,7 +1750,15 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1565
1750
|
# and flattens tool-collection wrappers, which can otherwise hide an MCPTool.
|
|
1566
1751
|
all_tools = normalize_tools(list(self._tools or []) + list(kwargs.get("tools") or []))
|
|
1567
1752
|
_reject_mcp_tools(all_tools)
|
|
1568
|
-
kwargs["tools"] =
|
|
1753
|
+
kwargs["tools"] = (
|
|
1754
|
+
self._prepare_tools(
|
|
1755
|
+
all_tools,
|
|
1756
|
+
agent_session=agent_session,
|
|
1757
|
+
function_invocation_kwargs=function_invocation_kwargs,
|
|
1758
|
+
)
|
|
1759
|
+
if all_tools
|
|
1760
|
+
else None
|
|
1761
|
+
)
|
|
1569
1762
|
|
|
1570
1763
|
kwargs["streaming"] = streaming
|
|
1571
1764
|
# model may already be present from per-run options (merged above); otherwise fall
|
|
@@ -1606,23 +1799,40 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1606
1799
|
self,
|
|
1607
1800
|
streaming: bool,
|
|
1608
1801
|
runtime_options: dict[str, Any] | None = None,
|
|
1802
|
+
*,
|
|
1803
|
+
agent_session: AgentSession | None = None,
|
|
1804
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
1609
1805
|
) -> CopilotSession:
|
|
1610
1806
|
"""Create a new Copilot session.
|
|
1611
1807
|
|
|
1612
1808
|
Args:
|
|
1613
1809
|
streaming: Whether to enable streaming for the session.
|
|
1614
1810
|
runtime_options: Runtime options that take precedence over default_options.
|
|
1811
|
+
|
|
1812
|
+
Keyword Args:
|
|
1813
|
+
agent_session: The conversation session, exposed to tools.
|
|
1814
|
+
function_invocation_kwargs: Keyword arguments forwarded to tool invocations.
|
|
1615
1815
|
"""
|
|
1616
1816
|
if not self._client:
|
|
1617
1817
|
raise RuntimeError("GitHub Copilot client not initialized. Call start() first.")
|
|
1618
1818
|
|
|
1619
|
-
return await self._client.create_session(
|
|
1819
|
+
return await self._client.create_session(
|
|
1820
|
+
**self._build_session_kwargs(
|
|
1821
|
+
streaming,
|
|
1822
|
+
runtime_options,
|
|
1823
|
+
agent_session=agent_session,
|
|
1824
|
+
function_invocation_kwargs=function_invocation_kwargs,
|
|
1825
|
+
)
|
|
1826
|
+
)
|
|
1620
1827
|
|
|
1621
1828
|
async def _resume_session(
|
|
1622
1829
|
self,
|
|
1623
1830
|
session_id: str,
|
|
1624
1831
|
streaming: bool,
|
|
1625
1832
|
runtime_options: dict[str, Any] | None = None,
|
|
1833
|
+
*,
|
|
1834
|
+
agent_session: AgentSession | None = None,
|
|
1835
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
1626
1836
|
) -> CopilotSession:
|
|
1627
1837
|
"""Resume an existing Copilot session by ID.
|
|
1628
1838
|
|
|
@@ -1630,11 +1840,23 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
|
|
1630
1840
|
session_id: The session ID to resume.
|
|
1631
1841
|
streaming: Whether to enable streaming for the session.
|
|
1632
1842
|
runtime_options: Runtime options that take precedence over default_options.
|
|
1843
|
+
|
|
1844
|
+
Keyword Args:
|
|
1845
|
+
agent_session: The conversation session, exposed to tools.
|
|
1846
|
+
function_invocation_kwargs: Keyword arguments forwarded to tool invocations.
|
|
1633
1847
|
"""
|
|
1634
1848
|
if not self._client:
|
|
1635
1849
|
raise RuntimeError("GitHub Copilot client not initialized. Call start() first.")
|
|
1636
1850
|
|
|
1637
|
-
return await self._client.resume_session(
|
|
1851
|
+
return await self._client.resume_session(
|
|
1852
|
+
session_id,
|
|
1853
|
+
**self._build_session_kwargs(
|
|
1854
|
+
streaming,
|
|
1855
|
+
runtime_options,
|
|
1856
|
+
agent_session=agent_session,
|
|
1857
|
+
function_invocation_kwargs=function_invocation_kwargs,
|
|
1858
|
+
),
|
|
1859
|
+
)
|
|
1638
1860
|
|
|
1639
1861
|
|
|
1640
1862
|
class GitHubCopilotAgent( # type: ignore[misc]
|
|
File without changes
|
{agent_framework_github_copilot-2.0.0 → agent_framework_github_copilot-2.0.2}/pyproject.toml
RENAMED
|
@@ -4,7 +4,7 @@ description = "GitHub Copilot integration for Microsoft Agent Framework."
|
|
|
4
4
|
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.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"
|
|
@@ -22,8 +22,8 @@ classifiers = [
|
|
|
22
22
|
"Typing :: Typed",
|
|
23
23
|
]
|
|
24
24
|
dependencies = [
|
|
25
|
-
"agent-framework-core>=1.
|
|
26
|
-
"github-copilot-sdk
|
|
25
|
+
"agent-framework-core>=1.21.0,<2",
|
|
26
|
+
"github-copilot-sdk>=1.0.16,<2.0; python_version >= '3.11'",
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
[tool.uv]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|