diff --git a/services/core/models/tests/integration/conftest.py b/services/core/models/tests/integration/conftest.py index 9837d52d1c..0328a8158d 100644 --- a/services/core/models/tests/integration/conftest.py +++ b/services/core/models/tests/integration/conftest.py @@ -15,6 +15,7 @@ from nemo_platform.types.inference.model_deployment_config import ModelDeploymentConfig from nemo_platform.types.models.model_entity import ModelEntity from nmp.common.secrets.encryption import get_base64_encoded_random_bytes +from nmp.core.files.app.backends.base import FileInfo from nmp.core.files.app.backends.huggingface import HuggingfaceStorageImpl from nmp.core.models.controllers.backends.backends import DeploymentStatusUpdate, ServiceBackend from nmp.core.models.controllers.backends.registry import BackendRegistry @@ -49,8 +50,12 @@ async def _validate_noop(self): async def _resolve_passthrough(self): return self.config + async def _list_files_stub(_self: HuggingfaceStorageImpl, _path: str | None = None) -> list[FileInfo]: + return [FileInfo(path="config.json", size=2)] + monkeypatch.setattr(HuggingfaceStorageImpl, "validate_storage", _validate_noop) monkeypatch.setattr(HuggingfaceStorageImpl, "resolve_config", _resolve_passthrough) + monkeypatch.setattr(HuggingfaceStorageImpl, "list_files", _list_files_stub) # ============================================================================= diff --git a/services/studio/src/nmp/studio/coding_agent_artifacts.py b/services/studio/src/nmp/studio/coding_agent_artifacts.py new file mode 100644 index 0000000000..f1815c2bd8 --- /dev/null +++ b/services/studio/src/nmp/studio/coding_agent_artifacts.py @@ -0,0 +1,349 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Artifact extraction for Studio coding-agent chat history.""" + +import json +import re +from typing import Any + +from nmp.studio import studio_links +from pydantic import BaseModel, Field + + +class ChatSelectionArtifactResponse(BaseModel): + """A user selection captured during the chat.""" + + label: str + value: str + + +class ChatFileArtifactResponse(BaseModel): + """A file touched by the local coding agent.""" + + action: str + path: str + + +class ChatLinkArtifactResponse(BaseModel): + """A Studio link requested by the local coding agent.""" + + label: str + destination: str | None = None + href: str | None = None + + +class ChatArtifactsResponse(BaseModel): + """Structured chat metadata shown in Studio's artifacts pane.""" + + agent: str | None = None + model: str | None = None + model_source: str | None = None + coding_agent_model: str | None = None + workspace: str | None = None + selections: list[ChatSelectionArtifactResponse] = Field(default_factory=list) + files: list[ChatFileArtifactResponse] = Field(default_factory=list) + links: list[ChatLinkArtifactResponse] = Field(default_factory=list) + tools: list[str] = Field(default_factory=list) + + +_ANSWER_PAIR_RE = re.compile(r'"((?:\\.|[^"\\])*)"\s*=\s*"((?:\\.|[^"\\])*)"') +_INLINE_CODE_VALUE_RE = re.compile(r"(`+)(?P.*?)\1", re.DOTALL) +_MARKDOWN_LINK_RE = re.compile(r"\[(?P