agent-framework-github-copilot 1.0.1__tar.gz → 1.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,22 +1,21 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-framework-github-copilot
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: GitHub Copilot integration for Microsoft Agent Framework.
5
5
  Author-email: Microsoft <af-support@microsoft.com>
6
- Requires-Python: >=3.10
6
+ Requires-Python: >=3.11
7
7
  Description-Content-Type: text/markdown
8
8
  Classifier: License :: OSI Approved :: MIT License
9
9
  Classifier: Development Status :: 5 - Production/Stable
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.10
13
12
  Classifier: Programming Language :: Python :: 3.11
14
13
  Classifier: Programming Language :: Python :: 3.12
15
14
  Classifier: Programming Language :: Python :: 3.13
16
15
  Classifier: Programming Language :: Python :: 3.14
17
16
  Classifier: Typing :: Typed
18
17
  License-File: LICENSE
19
- Requires-Dist: agent-framework-core>=1.13.0,<2
18
+ Requires-Dist: agent-framework-core>=1.15.0,<2
20
19
  Requires-Dist: github-copilot-sdk==1.0.2; python_version >= '3.11'
21
20
  Project-URL: homepage, https://aka.ms/agent-framework
22
21
  Project-URL: issues, https://github.com/microsoft/agent-framework/issues
@@ -75,6 +74,34 @@ agent = GitHubCopilotAgent(
75
74
  > Note: with the default (deny-all) permission handler, an `always_require` tool is denied
76
75
  > unless you wire an approving `on_permission_request`.
77
76
 
77
+ ### Approving for the rest of the session
78
+
79
+ `PermissionDecisionApproveForSession` scopes its approval with either an `approval` (tool
80
+ prompts) or a `domain` (URL prompts). Both are optional, so a bare
81
+ `PermissionDecisionApproveForSession()` carries no scope at all and the Copilot CLI cannot
82
+ interpret it.
83
+
84
+ `GitHubCopilotAgent` therefore scopes such a decision automatically, using the request that
85
+ triggered it — a shell prompt becomes an approval for that prompt's command identifiers, an
86
+ MCP prompt an approval for that server and tool, a URL prompt an approval for that URL's
87
+ domain, and so on:
88
+
89
+ ```python
90
+ from copilot.generated.rpc import PermissionDecisionApproveForSession
91
+
92
+
93
+ def on_permission_request(request, invocation):
94
+ # Scoped to `request` automatically; approves that kind of call for the whole session.
95
+ return PermissionDecisionApproveForSession()
96
+ ```
97
+
98
+ The decision is only ever narrowed, never widened. When the prompt reports that it cannot
99
+ offer session-scoped approval (`can_offer_session_approval=False`), or the request kind has
100
+ no session-scoped approval at all (such as a `hook` prompt), the decision is downgraded to a
101
+ single-use approval and a warning is logged. Pass an explicit `approval=` or `domain=` when
102
+ you want to approve something other than the request being handled — decisions that already
103
+ specify a scope are forwarded unchanged.
104
+
78
105
  ### Deprecated: `on_function_approval`
79
106
 
80
107
  The `on_function_approval` callback is **deprecated**. It still works (and is still enforced
@@ -50,6 +50,34 @@ agent = GitHubCopilotAgent(
50
50
  > Note: with the default (deny-all) permission handler, an `always_require` tool is denied
51
51
  > unless you wire an approving `on_permission_request`.
52
52
 
53
+ ### Approving for the rest of the session
54
+
55
+ `PermissionDecisionApproveForSession` scopes its approval with either an `approval` (tool
56
+ prompts) or a `domain` (URL prompts). Both are optional, so a bare
57
+ `PermissionDecisionApproveForSession()` carries no scope at all and the Copilot CLI cannot
58
+ interpret it.
59
+
60
+ `GitHubCopilotAgent` therefore scopes such a decision automatically, using the request that
61
+ triggered it — a shell prompt becomes an approval for that prompt's command identifiers, an
62
+ MCP prompt an approval for that server and tool, a URL prompt an approval for that URL's
63
+ domain, and so on:
64
+
65
+ ```python
66
+ from copilot.generated.rpc import PermissionDecisionApproveForSession
67
+
68
+
69
+ def on_permission_request(request, invocation):
70
+ # Scoped to `request` automatically; approves that kind of call for the whole session.
71
+ return PermissionDecisionApproveForSession()
72
+ ```
73
+
74
+ The decision is only ever narrowed, never widened. When the prompt reports that it cannot
75
+ offer session-scoped approval (`can_offer_session_approval=False`), or the request kind has
76
+ no session-scoped approval at all (such as a `hook` prompt), the decision is downgraded to a
77
+ single-use approval and a warning is logged. Pass an explicit `approval=` or `domain=` when
78
+ you want to approve something other than the request being handled — decisions that already
79
+ specify a scope are forwarded unchanged.
80
+
53
81
  ### Deprecated: `on_function_approval`
54
82
 
55
83
  The `on_function_approval` callback is **deprecated**. It still works (and is still enforced
@@ -5,11 +5,13 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  import contextlib
7
7
  import inspect
8
+ import json
8
9
  import logging
9
10
  import sys
10
11
  import warnings
11
12
  from collections.abc import AsyncIterable, Awaitable, Callable, Mapping, MutableMapping, Sequence
12
13
  from typing import Any, ClassVar, Generic, Literal, TypedDict, cast, overload
14
+ from urllib.parse import urlparse
13
15
 
14
16
  from agent_framework import (
15
17
  AgentMiddlewareLayer,
@@ -51,8 +53,26 @@ else:
51
53
  from typing_extensions import TypeVar # pragma: no cover
52
54
 
53
55
  try:
54
- from copilot import CopilotClient, CopilotSession, RuntimeConnection
55
- from copilot.generated.rpc import PermissionDecisionUserNotAvailable
56
+ from copilot import (
57
+ CopilotClient,
58
+ CopilotSession,
59
+ RuntimeConnection,
60
+ TelemetryConfig,
61
+ )
62
+ from copilot.generated.rpc import (
63
+ PermissionDecisionApproveForSession,
64
+ PermissionDecisionApproveForSessionApproval,
65
+ PermissionDecisionApproveForSessionApprovalCommands,
66
+ PermissionDecisionApproveForSessionApprovalCustomTool,
67
+ PermissionDecisionApproveForSessionApprovalExtensionManagement,
68
+ PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess,
69
+ PermissionDecisionApproveForSessionApprovalMCP,
70
+ PermissionDecisionApproveForSessionApprovalMemory,
71
+ PermissionDecisionApproveForSessionApprovalRead,
72
+ PermissionDecisionApproveForSessionApprovalWrite,
73
+ PermissionDecisionApproveOnce,
74
+ PermissionDecisionUserNotAvailable,
75
+ )
56
76
  from copilot.session import (
57
77
  Attachment,
58
78
  BlobAttachment,
@@ -64,7 +84,21 @@ try:
64
84
  SessionHooks,
65
85
  SystemMessageConfig,
66
86
  )
67
- from copilot.session_events import AssistantUsageData, PermissionRequest, SessionEvent, SessionEventType
87
+ from copilot.session_events import (
88
+ AssistantUsageData,
89
+ PermissionRequest,
90
+ PermissionRequestCustomTool,
91
+ PermissionRequestExtensionManagement,
92
+ PermissionRequestExtensionPermissionAccess,
93
+ PermissionRequestMcp,
94
+ PermissionRequestMemory,
95
+ PermissionRequestRead,
96
+ PermissionRequestShell,
97
+ PermissionRequestUrl,
98
+ PermissionRequestWrite,
99
+ SessionEvent,
100
+ SessionEventType,
101
+ )
68
102
  from copilot.tools import Tool as CopilotTool
69
103
  from copilot.tools import ToolInvocation, ToolResult
70
104
  except ImportError as _copilot_import_error:
@@ -81,6 +115,9 @@ PermissionHandlerType = Callable[
81
115
  ]
82
116
  """Type for permission request handlers. Supports both sync and async callbacks."""
83
117
 
118
+ AsyncPermissionHandlerType = Callable[[PermissionRequest, dict[str, str]], "Awaitable[PermissionRequestResult]"]
119
+ """Type for permission request handlers that are always asynchronous."""
120
+
84
121
 
85
122
  FunctionApprovalCallback = Callable[[Content], "bool | Awaitable[bool]"]
86
123
  """Deprecated approval callback for ``FunctionTool`` instances declared with
@@ -140,6 +177,193 @@ def _deny_all_permissions(
140
177
  return PermissionDecisionUserNotAvailable()
141
178
 
142
179
 
180
+ def _derive_session_approval(request: PermissionRequest) -> PermissionDecisionApproveForSessionApproval | None:
181
+ """Build the session-scoped approval implied by ``request``.
182
+
183
+ ``PermissionDecisionApproveForSession.approval`` describes *what* is being approved for
184
+ the remainder of the session. Its shape is dictated by the prompt that triggered it, so
185
+ it can be reconstructed from the request itself.
186
+
187
+ Args:
188
+ request: The permission request the decision is responding to.
189
+
190
+ Returns:
191
+ The approval covering ``request``, or ``None`` for request kinds that have no
192
+ session-scoped approval representation (such as ``hook`` prompts).
193
+ """
194
+ if isinstance(request, PermissionRequestShell):
195
+ return PermissionDecisionApproveForSessionApprovalCommands(
196
+ command_identifiers=[command.identifier for command in request.commands]
197
+ )
198
+ if isinstance(request, PermissionRequestRead):
199
+ return PermissionDecisionApproveForSessionApprovalRead()
200
+ if isinstance(request, PermissionRequestWrite):
201
+ return PermissionDecisionApproveForSessionApprovalWrite()
202
+ if isinstance(request, PermissionRequestMcp):
203
+ return PermissionDecisionApproveForSessionApprovalMCP(
204
+ server_name=request.server_name, tool_name=request.tool_name
205
+ )
206
+ if isinstance(request, PermissionRequestCustomTool):
207
+ return PermissionDecisionApproveForSessionApprovalCustomTool(tool_name=request.tool_name)
208
+ if isinstance(request, PermissionRequestMemory):
209
+ return PermissionDecisionApproveForSessionApprovalMemory()
210
+ if isinstance(request, PermissionRequestExtensionManagement):
211
+ return PermissionDecisionApproveForSessionApprovalExtensionManagement(operation=request.operation)
212
+ if isinstance(request, PermissionRequestExtensionPermissionAccess):
213
+ return PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess(
214
+ extension_name=request.extension_name
215
+ )
216
+ return None
217
+
218
+
219
+ # Characters the WHATWG URL parser (used by the Copilot CLI) treats specially for
220
+ # special-scheme URLs in ways that can move the authority boundary: backslashes are
221
+ # normalized to forward slashes, and tabs/newlines/carriage returns are stripped before
222
+ # parsing. Python's ``urlparse`` does none of this, so a URL containing any of them may
223
+ # resolve to a different host than the CLI actually contacts.
224
+ _WHATWG_AMBIGUOUS_URL_CHARS = ("\\", "\t", "\n", "\r")
225
+
226
+
227
+ def _derive_url_session_domain(url: str) -> str | None:
228
+ """Return the domain to persist for a URL prompt, or ``None`` when it is unsafe to.
229
+
230
+ The persisted domain must match the host the Copilot CLI actually contacts, but the CLI
231
+ parses URLs with WHATWG semantics while this runs on Python's ``urlparse``. The two
232
+ disagree on crafted authorities -- a backslash before the ``@`` in
233
+ ``https://example.com<backslash>@evil.com`` resolves to ``example.com`` under the CLI
234
+ but ``evil.com`` under ``urlparse`` -- so trusting ``urlparse`` here could persist a
235
+ session-wide approval for an unrelated, attacker-chosen domain.
236
+
237
+ To keep the "narrow, never widen" guarantee, the domain is only returned when the URL
238
+ contains none of the characters the two parsers handle differently; any ambiguity (or a
239
+ URL with no derivable host) yields ``None`` so the caller can approve the single request
240
+ without persisting a domain.
241
+
242
+ Args:
243
+ url: The URL from the permission request.
244
+
245
+ Returns:
246
+ The lower-cased host to approve for the session, or ``None`` when the URL is
247
+ parser-ambiguous or has no host.
248
+ """
249
+ if any(char in url for char in _WHATWG_AMBIGUOUS_URL_CHARS):
250
+ return None
251
+ return urlparse(url).hostname or None
252
+
253
+
254
+ def _normalize_permission_decision(
255
+ decision: PermissionRequestResult,
256
+ request: PermissionRequest,
257
+ ) -> PermissionRequestResult:
258
+ """Fill in the missing scope of an under-specified ``approve-for-session`` decision.
259
+
260
+ ``PermissionDecisionApproveForSession`` carries an optional ``approval`` (tool prompts)
261
+ and an optional ``domain`` (URL prompts), so ``PermissionDecisionApproveForSession()``
262
+ is constructible with neither. That serializes to ``{"kind": "approve-for-session"}``,
263
+ which the Copilot CLI cannot interpret -- it crashes with ``Cannot read properties of
264
+ undefined (reading 'commandIdentifiers')``, taking the whole run down with it. This
265
+ reconstructs the intended scope from ``request``.
266
+
267
+ The decision is only ever narrowed, never widened: when the prompt does not offer
268
+ session-scoped approval, or the request kind has no session approval representation,
269
+ the decision is downgraded to a single-use approval.
270
+
271
+ Args:
272
+ decision: The decision returned by the caller's permission handler.
273
+ request: The permission request the decision is responding to.
274
+
275
+ Returns:
276
+ ``decision`` unchanged unless it is an ``approve-for-session`` decision missing both
277
+ ``approval`` and ``domain``, in which case an equivalent fully-scoped decision (or a
278
+ narrower single-use approval) is returned. The input is never mutated.
279
+ """
280
+ if not isinstance(decision, PermissionDecisionApproveForSession):
281
+ return decision
282
+ if decision.approval is not None or decision.domain is not None:
283
+ return decision
284
+
285
+ try:
286
+ if isinstance(request, PermissionRequestUrl):
287
+ domain = _derive_url_session_domain(request.url)
288
+ if domain:
289
+ return PermissionDecisionApproveForSession(domain=domain)
290
+ logger.warning(
291
+ "Permission handler returned an unscoped 'approve-for-session' decision for a URL prompt, "
292
+ "but no unambiguous domain could be derived from '%s'. Approving this request only. Return "
293
+ "PermissionDecisionApproveForSession(domain=...) to approve a domain for the session.",
294
+ request.url,
295
+ )
296
+ return PermissionDecisionApproveOnce()
297
+
298
+ # Only shell and write prompts advertise this; other kinds always allow session approval.
299
+ if not getattr(request, "can_offer_session_approval", True):
300
+ logger.warning(
301
+ "Permission handler returned an 'approve-for-session' decision for a '%s' prompt that does not "
302
+ "offer session-scoped approval. Approving this request only.",
303
+ request.kind,
304
+ )
305
+ return PermissionDecisionApproveOnce()
306
+
307
+ approval = _derive_session_approval(request)
308
+ except Exception:
309
+ logger.exception(
310
+ "Failed to derive the session approval for a '%s' permission prompt. Approving this request only.",
311
+ getattr(request, "kind", "unknown"),
312
+ )
313
+ return PermissionDecisionApproveOnce()
314
+
315
+ if approval is None:
316
+ logger.warning(
317
+ "Permission handler returned an unscoped 'approve-for-session' decision for a '%s' prompt, which has "
318
+ "no session-scoped approval. Approving this request only.",
319
+ request.kind,
320
+ )
321
+ return PermissionDecisionApproveOnce()
322
+ return PermissionDecisionApproveForSession(approval=approval)
323
+
324
+
325
+ def _with_normalized_permission_decisions(handler: PermissionHandlerType) -> AsyncPermissionHandlerType:
326
+ """Wrap a permission handler so its decisions are normalized before reaching the SDK.
327
+
328
+ Exceptions raised by ``handler`` deliberately propagate: the SDK already catches them
329
+ and denies the request, and preserving that keeps the secure-by-default behavior.
330
+
331
+ Args:
332
+ handler: The caller-supplied permission handler. May be sync or async.
333
+
334
+ Returns:
335
+ An async handler delegating to ``handler`` and normalizing its result.
336
+ """
337
+
338
+ async def normalized_handler(request: PermissionRequest, invocation: dict[str, str]) -> PermissionRequestResult:
339
+ result = handler(request, invocation)
340
+ if inspect.isawaitable(result):
341
+ result = await result
342
+ return _normalize_permission_decision(result, request)
343
+
344
+ return normalized_handler
345
+
346
+
347
+ def _parse_telemetry_config(raw: str) -> TelemetryConfig | None:
348
+ # GITHUB_COPILOT_TELEMETRY and matching .env values are read as plain strings while the
349
+ # Copilot SDK expects a mapping, so parse here before the value reaches CopilotClient.
350
+ # Malformed values are logged and ignored so a bad telemetry setting cannot prevent the
351
+ # agent from starting.
352
+ try:
353
+ parsed = json.loads(raw)
354
+ except json.JSONDecodeError:
355
+ logger.warning(
356
+ "Ignoring malformed GITHUB_COPILOT_TELEMETRY value; expected a JSON object with TelemetryConfig keys."
357
+ )
358
+ return None
359
+ if not isinstance(parsed, dict):
360
+ logger.warning(
361
+ "Ignoring invalid GITHUB_COPILOT_TELEMETRY value; expected a JSON object with TelemetryConfig keys."
362
+ )
363
+ return None
364
+ return cast(TelemetryConfig, parsed)
365
+
366
+
143
367
  class GitHubCopilotSettings(TypedDict, total=False):
144
368
  """GitHub Copilot model settings.
145
369
 
@@ -161,6 +385,10 @@ class GitHubCopilotSettings(TypedDict, total=False):
161
385
  GITHUB_COPILOT_BASE_DIRECTORY. Defaults to ~/.copilot when not set.
162
386
  Only applicable when the SDK spawns the CLI process (ignored when
163
387
  connecting to an external server via a pre-configured client).
388
+ telemetry: OpenTelemetry configuration for the Copilot CLI process. This is
389
+ passed to the SDK client when it is created by the agent. Values coming
390
+ from GITHUB_COPILOT_TELEMETRY or a .env file arrive as a JSON string and
391
+ are parsed into a mapping before they reach the SDK.
164
392
  """
165
393
 
166
394
  cli_path: str | None
@@ -168,6 +396,7 @@ class GitHubCopilotSettings(TypedDict, total=False):
168
396
  timeout: float | None
169
397
  log_level: str | None
170
398
  base_directory: str | None
399
+ telemetry: dict[str, Any] | str | None
171
400
 
172
401
 
173
402
  class GitHubCopilotOptions(TypedDict, total=False):
@@ -239,6 +468,9 @@ class GitHubCopilotOptions(TypedDict, total=False):
239
468
  base_directory: str
240
469
  """Directory where the CLI stores session state, configuration, and other persistent data."""
241
470
 
471
+ telemetry: TelemetryConfig
472
+ """OpenTelemetry configuration for the Copilot CLI process."""
473
+
242
474
  on_pre_tool_use: PreToolUseHandler
243
475
  """Pre-tool-use hook handler for the Copilot SDK.
244
476
 
@@ -376,6 +608,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
376
608
  on_pre_tool_use: PreToolUseHandler | None = opts.pop("on_pre_tool_use", None)
377
609
  on_function_approval: FunctionApprovalCallback | None = opts.pop("on_function_approval", None)
378
610
  base_directory = opts.pop("base_directory", None)
611
+ telemetry = opts.pop("telemetry", None)
379
612
 
380
613
  if on_function_approval is not None and on_pre_tool_use is not None:
381
614
  raise ValueError(
@@ -402,6 +635,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
402
635
  timeout=timeout,
403
636
  log_level=log_level,
404
637
  base_directory=base_directory,
638
+ telemetry=telemetry,
405
639
  env_file_path=env_file_path,
406
640
  env_file_encoding=env_file_encoding,
407
641
  )
@@ -442,6 +676,9 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
442
676
  cli_path = self._settings.get("cli_path") or None
443
677
  log_level = self._settings.get("log_level") or None
444
678
  base_directory = self._settings.get("base_directory") or None
679
+ telemetry = self._settings.get("telemetry") or None
680
+ if isinstance(telemetry, str):
681
+ telemetry = _parse_telemetry_config(telemetry)
445
682
 
446
683
  client_kwargs: dict[str, Any] = {}
447
684
  if cli_path:
@@ -450,6 +687,8 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
450
687
  client_kwargs["log_level"] = log_level
451
688
  if base_directory:
452
689
  client_kwargs["base_directory"] = base_directory
690
+ if telemetry:
691
+ client_kwargs["telemetry"] = telemetry
453
692
  self._client = CopilotClient(**client_kwargs)
454
693
 
455
694
  try:
@@ -1201,9 +1440,10 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
1201
1440
  the Copilot SDK, so any ``create_session`` parameter is supported without a
1202
1441
  dedicated mapping here (an unknown name surfaces as a ``TypeError`` from the
1203
1442
  SDK). A few keys are handled specially because they need a secure default
1204
- (``on_permission_request`` defaults to denying all requests) or transforming:
1205
- ``tools`` are merged with the agent's tools and converted to SDK tools, and
1206
- approval callbacks are turned into ``hooks``.
1443
+ (``on_permission_request`` defaults to denying all requests, and is wrapped so
1444
+ under-specified ``approve-for-session`` decisions are scoped to the request that
1445
+ triggered them) or transforming: ``tools`` are merged with the agent's tools and
1446
+ converted to SDK tools, and approval callbacks are turned into ``hooks``.
1207
1447
 
1208
1448
  Args:
1209
1449
  streaming: Whether to enable streaming for the session.
@@ -1227,7 +1467,7 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
1227
1467
  # back to the resolved setting (which carries the default_options / env model).
1228
1468
  if not kwargs.get("model"):
1229
1469
  kwargs["model"] = self._settings.get("model") or None
1230
- kwargs["on_permission_request"] = (
1470
+ kwargs["on_permission_request"] = _with_normalized_permission_decisions(
1231
1471
  opts.get("on_permission_request") or self._permission_handler or _deny_all_permissions
1232
1472
  )
1233
1473
  kwargs["hooks"] = self._build_session_hooks(all_tools, kwargs)
@@ -1235,7 +1475,15 @@ class RawGitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
1235
1475
  # Strip agent-internal and client-level keys that are consumed here or in the
1236
1476
  # run methods (and settings) but are NOT valid create_session parameters, so
1237
1477
  # they don't leak through the passthrough layer and raise TypeError.
1238
- for key in ("on_pre_tool_use", "on_function_approval", "timeout", "cli_path", "log_level", "base_directory"):
1478
+ for key in (
1479
+ "on_pre_tool_use",
1480
+ "on_function_approval",
1481
+ "timeout",
1482
+ "cli_path",
1483
+ "log_level",
1484
+ "base_directory",
1485
+ "telemetry",
1486
+ ):
1239
1487
  kwargs.pop(key, None)
1240
1488
 
1241
1489
  return kwargs
@@ -3,8 +3,8 @@ name = "agent-framework-github-copilot"
3
3
  description = "GitHub Copilot integration for Microsoft Agent Framework."
4
4
  authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
5
5
  readme = "README.md"
6
- requires-python = ">=3.10"
7
- version = "1.0.1"
6
+ requires-python = ">=3.11"
7
+ version = "1.0.3"
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"
@@ -15,7 +15,6 @@ classifiers = [
15
15
  "Development Status :: 5 - Production/Stable",
16
16
  "Intended Audience :: Developers",
17
17
  "Programming Language :: Python :: 3",
18
- "Programming Language :: Python :: 3.10",
19
18
  "Programming Language :: Python :: 3.11",
20
19
  "Programming Language :: Python :: 3.12",
21
20
  "Programming Language :: Python :: 3.13",
@@ -23,7 +22,7 @@ classifiers = [
23
22
  "Typing :: Typed",
24
23
  ]
25
24
  dependencies = [
26
- "agent-framework-core>=1.13.0,<2",
25
+ "agent-framework-core>=1.15.0,<2",
27
26
  "github-copilot-sdk==1.0.2; python_version >= '3.11'",
28
27
  ]
29
28