mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-07-22 09:45:24 -04:00
fix(code): resolve editable SDK version metadata (#4394)
Editable installs now report dcode and SDK version metadata from the same resolver used elsewhere, so diagnostics reflect the checked-out source instead of stale package metadata. The stream config still keeps the heavier resolver off the module import path and treats SDK version reporting as best-effort metadata.
This commit is contained in:
@@ -14,7 +14,7 @@ import sys
|
||||
import threading
|
||||
from dataclasses import dataclass, field as dataclass_field
|
||||
from enum import StrEnum
|
||||
from importlib.metadata import PackageNotFoundError, distribution, version
|
||||
from importlib.metadata import PackageNotFoundError, distribution
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Protocol
|
||||
from urllib.parse import unquote, urlparse
|
||||
@@ -980,20 +980,43 @@ Kept short so tracing metadata can never stall app flows.
|
||||
|
||||
|
||||
def _get_deepagents_version() -> str | None:
|
||||
"""Read the installed Deep Agents SDK version from package metadata.
|
||||
"""Resolve the installed Deep Agents SDK version for diagnostics.
|
||||
|
||||
This intentionally calls `importlib.metadata.version` directly instead of
|
||||
`resolve_sdk_version`: `config` is on the startup hot path, while
|
||||
`resolve_sdk_version` lives in `extras_info` and imports `packaging`.
|
||||
Editable installs can leave package metadata behind the source checkout, so
|
||||
this uses the shared resolver that prefers the editable source version and
|
||||
falls back to metadata when needed.
|
||||
|
||||
Returns:
|
||||
The installed Deep Agents SDK version, or `None` when package metadata
|
||||
is unavailable.
|
||||
The resolved Deep Agents SDK version, or `None` when unavailable.
|
||||
"""
|
||||
# Imported lazily on purpose: `extras_info` pulls in `packaging`, which we
|
||||
# keep off `config`'s module-import path (the startup hot path). Do not
|
||||
# hoist this to the top of the module. The import is also guarded so a
|
||||
# broken/absent `packaging` can never crash best-effort diagnostic metadata.
|
||||
try:
|
||||
return version("deepagents")
|
||||
except PackageNotFoundError:
|
||||
from deepagents_code.extras_info import resolve_sdk_version
|
||||
|
||||
sdk_version, status = resolve_sdk_version()
|
||||
except ImportError:
|
||||
logger.warning(
|
||||
"Could not import resolve_sdk_version for SDK version metadata",
|
||||
exc_info=True,
|
||||
)
|
||||
return None
|
||||
return sdk_version if status == "resolved" else None
|
||||
|
||||
|
||||
def _format_lc_version(base_version: str, *, editable: bool) -> str:
|
||||
"""Format an `lc_versions` value with editable-install context.
|
||||
|
||||
Args:
|
||||
base_version: The base version string.
|
||||
editable: Whether the distribution is installed in editable mode.
|
||||
|
||||
Returns:
|
||||
The version string, suffixed with ` (editable)` when `editable`.
|
||||
"""
|
||||
return f"{base_version} (editable)" if editable else base_version
|
||||
|
||||
|
||||
def _resolve_editable_info() -> tuple[bool, str | None]:
|
||||
@@ -1467,7 +1490,11 @@ def build_stream_config(
|
||||
|
||||
# Legacy / diagnostic keys preserved for backward-compatibility during the
|
||||
# coding-agent-v1 rollout (not part of the contract).
|
||||
metadata["lc_versions"] = {"deepagents-code": __version__}
|
||||
metadata["lc_versions"] = {
|
||||
"deepagents-code": _format_lc_version(
|
||||
__version__, editable=_is_editable_install()
|
||||
)
|
||||
}
|
||||
deepagents_version = _get_deepagents_version()
|
||||
if deepagents_version is not None:
|
||||
metadata["dcode_client_deepagents_version"] = deepagents_version
|
||||
|
||||
@@ -5,7 +5,6 @@ import sys
|
||||
from asyncio import Future
|
||||
from collections.abc import AsyncIterator, Awaitable, Callable, Generator
|
||||
from datetime import datetime
|
||||
from importlib.metadata import PackageNotFoundError
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
@@ -804,12 +803,35 @@ class TestBuildStreamConfig:
|
||||
"""CLI version should always be present in metadata.lc_versions."""
|
||||
from deepagents_code._version import __version__
|
||||
|
||||
config = build_stream_config("t-ver", assistant_id=None)
|
||||
with (
|
||||
patch("deepagents_code.config._is_editable_install", return_value=False),
|
||||
patch("deepagents_code.config._get_deepagents_version", return_value=None),
|
||||
):
|
||||
config = build_stream_config("t-ver", assistant_id=None)
|
||||
assert config["metadata"]["lc_versions"] == {"deepagents-code": __version__}
|
||||
|
||||
def test_versions_marks_editable_cli_version(self) -> None:
|
||||
"""Editable dcode installs should be visible in metadata.lc_versions."""
|
||||
from deepagents_code._version import __version__
|
||||
|
||||
with (
|
||||
patch("deepagents_code.config._is_editable_install", return_value=True),
|
||||
patch("deepagents_code.config._get_deepagents_version", return_value=None),
|
||||
):
|
||||
config = build_stream_config("t-editable", assistant_id=None)
|
||||
assert config["metadata"]["lc_versions"] == {
|
||||
"deepagents-code": f"{__version__} (editable)"
|
||||
}
|
||||
|
||||
def test_dcode_client_deepagents_version_is_diagnostic_metadata(self) -> None:
|
||||
"""Client-side SDK version should not be reported as graph instrumentation."""
|
||||
with patch("deepagents_code.config.version", return_value="1.2.3"):
|
||||
with (
|
||||
patch("deepagents_code.config._is_editable_install", return_value=False),
|
||||
patch(
|
||||
"deepagents_code.extras_info.resolve_sdk_version",
|
||||
return_value=("1.2.3", "resolved"),
|
||||
),
|
||||
):
|
||||
config = build_stream_config("t-sdk", assistant_id=None)
|
||||
assert config["metadata"]["dcode_client_deepagents_version"] == "1.2.3"
|
||||
assert "deepagents" not in config["metadata"]["lc_versions"]
|
||||
@@ -818,9 +840,12 @@ class TestBuildStreamConfig:
|
||||
self,
|
||||
) -> None:
|
||||
"""Missing SDK metadata should not prevent stream config construction."""
|
||||
with patch(
|
||||
"deepagents_code.config.version",
|
||||
side_effect=PackageNotFoundError("deepagents"),
|
||||
with (
|
||||
patch("deepagents_code.config._is_editable_install", return_value=False),
|
||||
patch(
|
||||
"deepagents_code.extras_info.resolve_sdk_version",
|
||||
return_value=(None, "not_installed"),
|
||||
),
|
||||
):
|
||||
config = build_stream_config("t-missing-sdk", assistant_id=None)
|
||||
|
||||
@@ -835,7 +860,7 @@ class TestBuildStreamConfig:
|
||||
if module == "deepagents" or module.startswith("deepagents."):
|
||||
monkeypatch.delitem(sys.modules, module, raising=False)
|
||||
|
||||
with patch("deepagents_code.config.version", return_value="1.2.3"):
|
||||
with patch("deepagents_code.config._is_editable_install", return_value=False):
|
||||
build_stream_config("t-no-sdk-import", assistant_id=None)
|
||||
|
||||
assert not any(
|
||||
@@ -843,6 +868,44 @@ class TestBuildStreamConfig:
|
||||
for module in sys.modules
|
||||
)
|
||||
|
||||
def test_get_deepagents_version_maps_status_to_value(self) -> None:
|
||||
"""Only a `resolved` status yields a version; other statuses map to None.
|
||||
|
||||
The guard keys on `status`, not on the version string, so a non-resolved
|
||||
status must drop even a non-`None` version the resolver flagged as
|
||||
untrustworthy.
|
||||
"""
|
||||
from deepagents_code.config import _get_deepagents_version
|
||||
|
||||
with patch(
|
||||
"deepagents_code.extras_info.resolve_sdk_version",
|
||||
return_value=("1.2.3", "error"),
|
||||
):
|
||||
assert _get_deepagents_version() is None
|
||||
|
||||
with patch(
|
||||
"deepagents_code.extras_info.resolve_sdk_version",
|
||||
return_value=("1.2.3", "resolved"),
|
||||
):
|
||||
assert _get_deepagents_version() == "1.2.3"
|
||||
|
||||
def test_versions_editable_with_resolved_sdk_version(self) -> None:
|
||||
"""Editable suffix and SDK diagnostic version are populated independently."""
|
||||
from deepagents_code._version import __version__
|
||||
|
||||
with (
|
||||
patch("deepagents_code.config._is_editable_install", return_value=True),
|
||||
patch(
|
||||
"deepagents_code.extras_info.resolve_sdk_version",
|
||||
return_value=("1.2.3", "resolved"),
|
||||
),
|
||||
):
|
||||
config = build_stream_config("t-editable-sdk", assistant_id=None)
|
||||
assert config["metadata"]["lc_versions"] == {
|
||||
"deepagents-code": f"{__version__} (editable)"
|
||||
}
|
||||
assert config["metadata"]["dcode_client_deepagents_version"] == "1.2.3"
|
||||
|
||||
def test_user_id_included_when_set(self) -> None:
|
||||
"""DEEPAGENTS_CODE_USER_ID should appear in metadata when set."""
|
||||
with patch.dict("os.environ", {"DEEPAGENTS_CODE_USER_ID": "mason"}):
|
||||
|
||||
Reference in New Issue
Block a user