diff --git a/api/controllers/console/app/agent_drive_inspector.py b/api/controllers/console/app/agent_drive_inspector.py index b8d1d487808..bd639955d9c 100644 --- a/api/controllers/console/app/agent_drive_inspector.py +++ b/api/controllers/console/app/agent_drive_inspector.py @@ -10,8 +10,12 @@ backend — drive data lives in the API's own DB/storage, served straight from from __future__ import annotations +import json +from collections.abc import Mapping +from typing import Any from uuid import UUID +from flask import Response from flask_restx import Resource from pydantic import BaseModel, Field @@ -49,6 +53,10 @@ class AgentDriveFileByAgentQuery(BaseModel): key: str = Field(min_length=1, description="Drive key, e.g. tender-analyzer/SKILL.md") +class AgentDriveSkillInspectQuery(BaseModel): + node_id: str | None = Field(default=None, description="Workflow node ID (workflow composer variant)") + + class AgentDriveItemResponse(ResponseModel): key: str size: int | None = None @@ -56,12 +64,63 @@ class AgentDriveItemResponse(ResponseModel): hash: str | None = None file_kind: str created_at: int | None = None + is_skill: bool | None = None + skill_metadata: str | None = None class AgentDriveListResponse(ResponseModel): items: list[AgentDriveItemResponse] = Field(default_factory=list) +class AgentDriveSkillItemResponse(ResponseModel): + path: str + skill_md_key: str + archive_key: str | None = None + name: str + description: str + size: int | None = None + mime_type: str | None = None + hash: str | None = None + created_at: int | None = None + + +class AgentDriveSkillListResponse(ResponseModel): + items: list[AgentDriveSkillItemResponse] = Field(default_factory=list) + + +class AgentDriveSkillFileResponse(ResponseModel): + path: str + name: str + type: str + drive_key: str | None = None + available_in_drive: bool + + +class AgentDriveSkillMarkdownResponse(ResponseModel): + key: str + size: int | None = None + truncated: bool + binary: bool + text: str | None = None + + +class AgentDriveSkillInspectResponse(ResponseModel): + path: str + skill_md_key: str + archive_key: str | None = None + name: str + description: str + size: int | None = None + mime_type: str | None = None + hash: str | None = None + created_at: int | None = None + source: str + files: list[AgentDriveSkillFileResponse] = Field(default_factory=list) + file_tree: list[dict[str, Any]] = Field(default_factory=list) + skill_md: AgentDriveSkillMarkdownResponse + warnings: list[str] = Field(default_factory=list) + + class AgentDrivePreviewResponse(ResponseModel): key: str size: int | None = None @@ -75,7 +134,12 @@ class AgentDriveDownloadResponse(ResponseModel): register_response_schema_models( - console_ns, AgentDriveListResponse, AgentDrivePreviewResponse, AgentDriveDownloadResponse + console_ns, + AgentDriveDownloadResponse, + AgentDriveListResponse, + AgentDrivePreviewResponse, + AgentDriveSkillInspectResponse, + AgentDriveSkillListResponse, ) @@ -96,6 +160,13 @@ def _handle(exc: AgentDriveError) -> tuple[dict[str, object], int]: return {"code": exc.code, "message": exc.message}, exc.status_code +def _json_response(data: Mapping[str, Any]): + return Response( + response=json.dumps(data, ensure_ascii=False, separators=(",", ":")), + content_type="application/json; charset=utf-8", + ) + + _WORKFLOW_APP_MODES = [AppMode.WORKFLOW, AppMode.ADVANCED_CHAT] @@ -119,6 +190,49 @@ class AgentDriveListByAgentApi(Resource): return {"items": [{k: v for k, v in item.items() if k != "file_id"} for item in items]} +@console_ns.route("/agent//drive/skills") +class AgentDriveSkillListByAgentApi(Resource): + @console_ns.doc("list_agent_drive_skills_by_agent") + @console_ns.doc(description="List drive-backed skills for an Agent App") + @console_ns.doc(params={"agent_id": "Agent ID"}) + @console_ns.response(200, "Drive skills", console_ns.models[AgentDriveSkillListResponse.__name__]) + @setup_required + @login_required + @account_initialization_required + @with_current_tenant_id + def get(self, tenant_id: str, agent_id: UUID): + resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id) + try: + items = AgentDriveService().list_skills(tenant_id=tenant_id, agent_id=str(agent_id)) + except AgentDriveError as exc: + return _handle(exc) + return {"items": items} + + +@console_ns.route("/agent//drive/skills//inspect") +class AgentDriveSkillInspectByAgentApi(Resource): + @console_ns.doc("inspect_agent_drive_skill_by_agent") + @console_ns.doc(description="Inspect one drive-backed skill for slash-menu hover/detail UI") + @console_ns.doc(params={"agent_id": "Agent ID", "skill_path": "Skill path/slug, e.g. tender-analyzer"}) + @console_ns.response(200, "Drive skill inspect view", console_ns.models[AgentDriveSkillInspectResponse.__name__]) + @setup_required + @login_required + @account_initialization_required + @with_current_tenant_id + def get(self, tenant_id: str, agent_id: UUID, skill_path: str): + resolve_agent_app_model(tenant_id=tenant_id, agent_id=agent_id) + try: + return _json_response( + AgentDriveService().inspect_skill( + tenant_id=tenant_id, + agent_id=str(agent_id), + skill_path=skill_path, + ) + ) + except AgentDriveError as exc: + return _handle(exc) + + @console_ns.route("/agent//drive/files/preview") class AgentDrivePreviewByAgentApi(Resource): @console_ns.doc("preview_agent_drive_file_by_agent") @@ -182,6 +296,61 @@ class AgentDriveListApi(Resource): return {"items": [{k: v for k, v in item.items() if k != "file_id"} for item in items]} +@console_ns.route("/apps//agent/drive/skills") +class AgentDriveSkillListApi(Resource): + @console_ns.doc("list_agent_drive_skills") + @console_ns.doc(description="List drive-backed skills for the bound agent") + @console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(AgentDriveListQuery)}) + @console_ns.response(200, "Drive skills", console_ns.models[AgentDriveSkillListResponse.__name__]) + @setup_required + @login_required + @account_initialization_required + @get_app_model(mode=_WORKFLOW_APP_MODES) + def get(self, app_model: App): + query = query_params_from_request(AgentDriveListQuery) + agent_id = _resolve_agent_id(app_model, query.node_id) + if not agent_id: + return _agent_not_bound() + try: + items = AgentDriveService().list_skills(tenant_id=app_model.tenant_id, agent_id=agent_id) + except AgentDriveError as exc: + return _handle(exc) + return {"items": items} + + +@console_ns.route("/apps//agent/drive/skills//inspect") +class AgentDriveSkillInspectApi(Resource): + @console_ns.doc("inspect_agent_drive_skill") + @console_ns.doc(description="Inspect one drive-backed skill for slash-menu hover/detail UI") + @console_ns.doc( + params={ + "app_id": "Application ID", + "skill_path": "Skill path/slug, e.g. tender-analyzer", + **query_params_from_model(AgentDriveSkillInspectQuery), + } + ) + @console_ns.response(200, "Drive skill inspect view", console_ns.models[AgentDriveSkillInspectResponse.__name__]) + @setup_required + @login_required + @account_initialization_required + @get_app_model(mode=_WORKFLOW_APP_MODES) + def get(self, app_model: App, skill_path: str): + query = query_params_from_request(AgentDriveSkillInspectQuery) + agent_id = _resolve_agent_id(app_model, query.node_id) + if not agent_id: + return _agent_not_bound() + try: + return _json_response( + AgentDriveService().inspect_skill( + tenant_id=app_model.tenant_id, + agent_id=agent_id, + skill_path=skill_path, + ) + ) + except AgentDriveError as exc: + return _handle(exc) + + @console_ns.route("/apps//agent/drive/files/preview") class AgentDrivePreviewApi(Resource): @console_ns.doc("preview_agent_drive_file") @@ -232,4 +401,8 @@ __all__ = [ "AgentDriveListByAgentApi", "AgentDrivePreviewApi", "AgentDrivePreviewByAgentApi", + "AgentDriveSkillInspectApi", + "AgentDriveSkillInspectByAgentApi", + "AgentDriveSkillListApi", + "AgentDriveSkillListByAgentApi", ] diff --git a/api/migrations/versions/2026_06_18_2300-b2515f9d4c2a_agent_drive_skill_metadata_refactor.py b/api/migrations/versions/2026_06_18_2300-b2515f9d4c2a_agent_drive_skill_metadata_refactor.py new file mode 100644 index 00000000000..9dc85d2a89b --- /dev/null +++ b/api/migrations/versions/2026_06_18_2300-b2515f9d4c2a_agent_drive_skill_metadata_refactor.py @@ -0,0 +1,39 @@ +"""agent drive skill metadata refactor + +Revision ID: b2515f9d4c2a +Revises: 4f7b2c8d9a10 +Create Date: 2026-06-18 23:00:00.000000 + +""" + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = "b2515f9d4c2a" +down_revision = "4f7b2c8d9a10" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column( + "agent_drive_files", + sa.Column("is_skill", sa.Boolean(), nullable=False, server_default=sa.text("false")), + ) + op.add_column( + "agent_drive_files", + sa.Column("skill_metadata", sa.Text().with_variant(mysql.LONGTEXT(), "mysql"), nullable=True), + ) + op.create_index( + "agent_drive_files_tenant_agent_is_skill_key_idx", + "agent_drive_files", + ["tenant_id", "agent_id", "is_skill", "key"], + ) + + +def downgrade() -> None: + op.drop_index("agent_drive_files_tenant_agent_is_skill_key_idx", table_name="agent_drive_files") + op.drop_column("agent_drive_files", "skill_metadata") + op.drop_column("agent_drive_files", "is_skill") diff --git a/api/models/agent.py b/api/models/agent.py index 1a13ccde77b..80abf810922 100644 --- a/api/models/agent.py +++ b/api/models/agent.py @@ -430,14 +430,17 @@ class AgentDriveFile(DefaultFieldsMixin, Base): synced. ``value_owned_by_drive`` gates physical cleanup: only drive-owned values (created by the agent runtime or Skill standardization, not shared with other business records) have their storage object + record deleted when the KV entry is - overwritten or removed; otherwise only the KV row is dropped. Lifecycle never relies - on ``UploadFile.used/used_by`` (not a reliable refcount). + overwritten or removed; otherwise only the KV row is dropped. Skills are represented + by the canonical ``/SKILL.md`` row with ``is_skill=True`` and a serialized + ``skill_metadata`` string. Lifecycle never relies on ``UploadFile.used/used_by`` + (not a reliable refcount). """ __tablename__ = "agent_drive_files" __table_args__ = ( sa.PrimaryKeyConstraint("id", name="agent_drive_file_pkey"), UniqueConstraint("tenant_id", "agent_id", "key", name="agent_drive_file_scope_key_unique"), + Index("agent_drive_files_tenant_agent_is_skill_key_idx", "tenant_id", "agent_id", "is_skill", "key"), ) tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) @@ -453,6 +456,8 @@ class AgentDriveFile(DefaultFieldsMixin, Base): value_owned_by_drive: Mapped[bool] = mapped_column( sa.Boolean, nullable=False, default=False, server_default=sa.text("false") ) + is_skill: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, default=False, server_default=sa.text("false")) + skill_metadata: Mapped[str | None] = mapped_column(LongText, nullable=True) size: Mapped[int | None] = mapped_column(sa.BigInteger, nullable=True) hash: Mapped[str | None] = mapped_column(String(255), nullable=True) mime_type: Mapped[str | None] = mapped_column(String(255), nullable=True) diff --git a/api/openapi/markdown/console-openapi.md b/api/openapi/markdown/console-openapi.md index 123a2e6e04b..85141d63618 100644 --- a/api/openapi/markdown/console-openapi.md +++ b/api/openapi/markdown/console-openapi.md @@ -576,6 +576,37 @@ Truncated text preview of one Agent App drive value | ---- | ----------- | ------ | | 200 | Preview | **application/json**: [AgentDrivePreviewResponse](#agentdrivepreviewresponse)
| +### [GET] /agent/{agent_id}/drive/skills +List drive-backed skills for an Agent App + +#### Parameters + +| Name | Located in | Description | Required | Schema | +| ---- | ---------- | ----------- | -------- | ------ | +| agent_id | path | Agent ID | Yes | string (uuid) | + +#### Responses + +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Drive skills | **application/json**: [AgentDriveSkillListResponse](#agentdriveskilllistresponse)
| + +### [GET] /agent/{agent_id}/drive/skills/{skill_path}/inspect +Inspect one drive-backed skill for slash-menu hover/detail UI + +#### Parameters + +| Name | Located in | Description | Required | Schema | +| ---- | ---------- | ----------- | -------- | ------ | +| agent_id | path | Agent ID | Yes | string (uuid) | +| skill_path | path | Skill path/slug, e.g. tender-analyzer | Yes | string | + +#### Responses + +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Drive skill inspect view | **application/json**: [AgentDriveSkillInspectResponse](#agentdriveskillinspectresponse)
| + ### [POST] /agent/{agent_id}/features Update an Agent App's presentation features (opener, follow-up, citations, ...) @@ -1454,6 +1485,40 @@ Truncated text preview of one drive value (binary-safe; SKILL.md is the main cas | ---- | ----------- | ------ | | 200 | Preview | **application/json**: [AgentDrivePreviewResponse](#agentdrivepreviewresponse)
| +### [GET] /apps/{app_id}/agent/drive/skills +List drive-backed skills for the bound agent + +#### Parameters + +| Name | Located in | Description | Required | Schema | +| ---- | ---------- | ----------- | -------- | ------ | +| app_id | path | Application ID | Yes | string (uuid) | +| node_id | query | Workflow node ID (workflow composer variant) | No | string | +| prefix | query | Key prefix filter: '/' for one skill, 'files/' for files | No | string | + +#### Responses + +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Drive skills | **application/json**: [AgentDriveSkillListResponse](#agentdriveskilllistresponse)
| + +### [GET] /apps/{app_id}/agent/drive/skills/{skill_path}/inspect +Inspect one drive-backed skill for slash-menu hover/detail UI + +#### Parameters + +| Name | Located in | Description | Required | Schema | +| ---- | ---------- | ----------- | -------- | ------ | +| app_id | path | Application ID | Yes | string (uuid) | +| skill_path | path | Skill path/slug, e.g. tender-analyzer | Yes | string | +| node_id | query | Workflow node ID (workflow composer variant) | No | string | + +#### Responses + +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Drive skill inspect view | **application/json**: [AgentDriveSkillInspectResponse](#agentdriveskillinspectresponse)
| + ### [DELETE] /apps/{app_id}/agent/files Delete one drive file by key; soul ref first, then the KV row (ENG-625 D5) @@ -12425,9 +12490,11 @@ Audit operation recorded for Agent Soul version/revision changes. | created_at | integer | | No | | file_kind | string | | Yes | | hash | string | | No | +| is_skill | boolean | | No | | key | string | | Yes | | mime_type | string | | No | | size | integer | | No | +| skill_metadata | string | | No | #### AgentDriveListResponse @@ -12445,6 +12512,65 @@ Audit operation recorded for Agent Soul version/revision changes. | text | string | | No | | truncated | boolean | | Yes | +#### AgentDriveSkillFileResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| available_in_drive | boolean | | Yes | +| drive_key | string | | No | +| name | string | | Yes | +| path | string | | Yes | +| type | string | | Yes | + +#### AgentDriveSkillInspectResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| archive_key | string | | No | +| created_at | integer | | No | +| description | string | | Yes | +| file_tree | [ object ] | | No | +| files | [ [AgentDriveSkillFileResponse](#agentdriveskillfileresponse) ] | | No | +| hash | string | | No | +| mime_type | string | | No | +| name | string | | Yes | +| path | string | | Yes | +| size | integer | | No | +| skill_md | [AgentDriveSkillMarkdownResponse](#agentdriveskillmarkdownresponse) | | Yes | +| skill_md_key | string | | Yes | +| source | string | | Yes | +| warnings | [ string ] | | No | + +#### AgentDriveSkillItemResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| archive_key | string | | No | +| created_at | integer | | No | +| description | string | | Yes | +| hash | string | | No | +| mime_type | string | | No | +| name | string | | Yes | +| path | string | | Yes | +| size | integer | | No | +| skill_md_key | string | | Yes | + +#### AgentDriveSkillListResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| items | [ [AgentDriveSkillItemResponse](#agentdriveskillitemresponse) ] | | No | + +#### AgentDriveSkillMarkdownResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| binary | boolean | | Yes | +| key | string | | Yes | +| size | integer | | No | +| text | string | | No | +| truncated | boolean | | Yes | + #### AgentEnvVariableConfig | Name | Type | Description | Required | diff --git a/api/services/agent/skill_standardize_service.py b/api/services/agent/skill_standardize_service.py index b83004f3c4b..3fbcb81e61f 100644 --- a/api/services/agent/skill_standardize_service.py +++ b/api/services/agent/skill_standardize_service.py @@ -23,7 +23,7 @@ from typing import Any from core.tools.tool_file_manager import ToolFileManager from models.agent_config_entities import AgentSkillRefConfig from services.agent.skill_package_service import SkillPackageService -from services.agent_drive_service import AgentDriveService, DriveCommitItem, DriveFileRef +from services.agent_drive_service import AgentDriveService, DriveCommitItem, DriveFileRef, DriveSkillMetadata _FULL_ARCHIVE_NAME = ".DIFY-SKILL-FULL.zip" _SKILL_MD_NAME = "SKILL.md" @@ -91,6 +91,12 @@ class SkillStandardizeService: key=skill_md_key, file_ref=DriveFileRef(kind="tool_file", id=md_tool_file.id), value_owned_by_drive=True, + is_skill=True, + skill_metadata=DriveSkillMetadata( + name=manifest.name, + description=manifest.description, + manifest_files=manifest.files, + ), ), DriveCommitItem( key=archive_key, diff --git a/api/services/agent_drive_service.py b/api/services/agent_drive_service.py index bb3f8ca69e3..62b6056412e 100644 --- a/api/services/agent_drive_service.py +++ b/api/services/agent_drive_service.py @@ -17,12 +17,14 @@ ToolFile records (see ``AgentDriveFile``). This service is the control plane: from __future__ import annotations +import json import logging import re import urllib.parse -from typing import Any, Literal +from typing import Any, Literal, TypedDict +from urllib.parse import unquote -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict, field_validator from sqlalchemy import func, select from sqlalchemy.exc import DataError, SQLAlchemyError from sqlalchemy.orm import Session @@ -41,6 +43,8 @@ logger = logging.getLogger(__name__) _MAX_KEY_LENGTH = 512 _DRIVE_REF_PREFIX = "agent-" +_SKILL_MD_SUFFIX = "/SKILL.md" +_SKILL_ARCHIVE_NAME = ".DIFY-SKILL-FULL.zip" class AgentDriveError(Exception): @@ -58,16 +62,86 @@ class AgentDriveError(Exception): class DriveFileRef(BaseModel): + model_config = ConfigDict(extra="forbid") + kind: Literal["upload_file", "tool_file"] id: str +class DriveSkillMetadata(BaseModel): + """Validated skill catalog metadata stored as a JSON string on the drive row.""" + + model_config = ConfigDict(extra="forbid") + + name: str + description: str = "" + # Safe archive member paths captured during skill standardization. The drive + # stores only canonical SKILL.md + full archive, so the UI uses this manifest + # to show the original uploaded package contents. + manifest_files: list[str] | None = None + + @field_validator("name") + @classmethod + def _validate_name(cls, value: str) -> str: + normalized = value.strip() + if not normalized: + raise ValueError("skill metadata name must not be blank") + return normalized + + class DriveCommitItem(BaseModel): + model_config = ConfigDict(extra="forbid") + key: str file_ref: DriveFileRef # Drive-owned values may be physically cleaned on overwrite/removal; refs to # files shared with other business records should set this False. value_owned_by_drive: bool = True + is_skill: bool = False + skill_metadata: DriveSkillMetadata | None = None + + +class AgentDriveSkillInfo(TypedDict): + path: str + skill_md_key: str + archive_key: str | None + name: str + description: str + size: int | None + mime_type: str | None + hash: str | None + created_at: int | None + + +class AgentDriveSkillFileInfo(TypedDict): + path: str + name: str + type: str + drive_key: str | None + available_in_drive: bool + + +class AgentDriveSkillInspectInfo(TypedDict): + path: str + skill_md_key: str + archive_key: str | None + name: str + description: str + size: int | None + mime_type: str | None + hash: str | None + created_at: int | None + source: str + files: list[AgentDriveSkillFileInfo] + file_tree: list[dict[str, Any]] + skill_md: dict[str, Any] + warnings: list[str] + + +def decode_drive_mention_ref(ref_id: str) -> str: + """Decode the prompt token's URL-encoded drive-key field.""" + + return unquote(ref_id or "") def parse_agent_drive_ref(drive_ref: str) -> str: @@ -132,6 +206,8 @@ class AgentDriveService: "mime_type": row.mime_type, "file_kind": row.file_kind.value, "file_id": row.file_id, + "is_skill": row.is_skill, + "skill_metadata": row.skill_metadata, "created_at": int(row.created_at.timestamp()) if row.created_at else None, } if include_download_url: @@ -217,6 +293,87 @@ class AgentDriveService: self._delete_storage(storage_key) return removed_keys + def list_skills(self, *, tenant_id: str, agent_id: str) -> list[AgentDriveSkillInfo]: + """Return the drive-backed skill catalog derived from canonical ``SKILL.md`` rows.""" + + with session_factory.create_session() as session: + self._assert_agent_belongs_to_tenant(session, tenant_id=tenant_id, agent_id=agent_id) + skill_rows = list( + session.scalars( + select(AgentDriveFile) + .where( + AgentDriveFile.tenant_id == tenant_id, + AgentDriveFile.agent_id == agent_id, + AgentDriveFile.is_skill.is_(True), + ) + .order_by(AgentDriveFile.key) + ) + ) + archive_keys = set( + session.scalars( + select(AgentDriveFile.key).where( + AgentDriveFile.tenant_id == tenant_id, + AgentDriveFile.agent_id == agent_id, + AgentDriveFile.key.in_([self._skill_archive_key(row.key) for row in skill_rows]), + ) + ) + ) + + skills: list[AgentDriveSkillInfo] = [] + for row in skill_rows: + metadata = self._parse_skill_metadata(row.key, row.skill_metadata) + archive_key = self._skill_archive_key(row.key) + skills.append( + { + "path": self._skill_path_from_key(row.key), + "skill_md_key": row.key, + "archive_key": archive_key if archive_key in archive_keys else None, + "name": metadata.name, + "description": metadata.description, + "size": row.size, + "mime_type": row.mime_type, + "hash": row.hash, + "created_at": int(row.created_at.timestamp()) if row.created_at else None, + } + ) + return skills + + def inspect_skill(self, *, tenant_id: str, agent_id: str, skill_path: str) -> AgentDriveSkillInspectInfo: + """Return the UI-facing skill inspect view for slash-menu hover/detail.""" + + skill_path = normalize_drive_key(skill_path) + skill_md_key = skill_path if skill_path.endswith(_SKILL_MD_SUFFIX) else f"{skill_path}{_SKILL_MD_SUFFIX}" + skill_path = self._skill_path_from_key(skill_md_key) + catalog = next( + (item for item in self.list_skills(tenant_id=tenant_id, agent_id=agent_id) if item["path"] == skill_path), + None, + ) + if catalog is None: + raise AgentDriveError("skill_not_found", "no drive-backed skill for this path", status_code=404) + + manifest_files = self._manifest_files_from_skill_metadata( + tenant_id=tenant_id, + agent_id=agent_id, + skill_md_key=skill_md_key, + ) + drive_items = self.manifest(tenant_id=tenant_id, agent_id=agent_id, prefix=f"{skill_path}/") + drive_keys = {item["key"] for item in drive_items} + preview = self.preview(tenant_id=tenant_id, agent_id=agent_id, key=skill_md_key) + files, warnings = self._skill_file_entries( + skill_path=skill_path, + skill_md_key=skill_md_key, + manifest_files=manifest_files, + drive_keys=drive_keys, + ) + return { + **catalog, + "source": "skill_md", + "files": files, + "file_tree": self._build_file_tree(files), + "skill_md": preview, + "warnings": warnings, + } + def _commit_one( self, session: Session, @@ -228,9 +385,10 @@ class AgentDriveService: pending_storage_deletes: list[str], ) -> dict[str, Any]: key = normalize_drive_key(item.key) + skill_metadata = self._validate_skill_commit_fields(key=key, item=item) file_kind = AgentDriveFileKind(item.file_ref.kind) file_id = item.file_ref.id - size, mime_type = self._validate_source( + size, mime_type, file_hash = self._validate_source( session, tenant_id=tenant_id, user_id=user_id, file_kind=file_kind, file_id=file_id ) @@ -245,6 +403,11 @@ class AgentDriveService: # Idempotent re-commit of the same value: leave it (do not clean). if existing.file_kind == file_kind and existing.file_id == file_id: existing.value_owned_by_drive = item.value_owned_by_drive + existing.is_skill = item.is_skill + existing.skill_metadata = skill_metadata + existing.size = size + existing.mime_type = mime_type + existing.hash = file_hash return self._row_dict(existing) # Overwrite: clean the previous drive-owned value if no longer referenced. if existing.value_owned_by_drive: @@ -259,7 +422,10 @@ class AgentDriveService: existing.file_kind = file_kind existing.file_id = file_id existing.value_owned_by_drive = item.value_owned_by_drive + existing.is_skill = item.is_skill + existing.skill_metadata = skill_metadata existing.size = size + existing.hash = file_hash existing.mime_type = mime_type return self._row_dict(existing) @@ -271,7 +437,10 @@ class AgentDriveService: file_kind=file_kind, file_id=file_id, value_owned_by_drive=item.value_owned_by_drive, + is_skill=item.is_skill, + skill_metadata=skill_metadata, size=size, + hash=file_hash, mime_type=mime_type, created_by=user_id, ) @@ -287,8 +456,187 @@ class AgentDriveService: "size": row.size, "mime_type": row.mime_type, "value_owned_by_drive": row.value_owned_by_drive, + "is_skill": row.is_skill, + "skill_metadata": row.skill_metadata, } + @staticmethod + def _skill_path_from_key(key: str) -> str: + if not key.endswith(_SKILL_MD_SUFFIX): + raise AgentDriveError( + "invalid_skill_key", + "skill rows must use the canonical '/SKILL.md' key", + status_code=500, + ) + path = key[: -len(_SKILL_MD_SUFFIX)] + if not path: + raise AgentDriveError( + "invalid_skill_key", + "skill rows must use the canonical '/SKILL.md' key", + status_code=500, + ) + return path + + @classmethod + def _skill_archive_key(cls, key: str) -> str: + return f"{cls._skill_path_from_key(key)}/{_SKILL_ARCHIVE_NAME}" + + @classmethod + def _validate_skill_commit_fields(cls, *, key: str, item: DriveCommitItem) -> str | None: + if not item.is_skill: + if item.skill_metadata is not None: + raise AgentDriveError( + "invalid_skill_metadata", + "skill metadata is only allowed for canonical skill rows", + status_code=400, + ) + return None + cls._skill_path_from_key(key) + if item.skill_metadata is None: + raise AgentDriveError( + "invalid_skill_metadata", + "skill metadata is required for canonical skill rows", + status_code=400, + ) + return json.dumps( + item.skill_metadata.model_dump(mode="json", exclude_none=True), + separators=(",", ":"), + sort_keys=True, + ) + + @staticmethod + def _parse_skill_metadata(key: str, raw_metadata: str | None) -> DriveSkillMetadata: + if raw_metadata is None: + raise AgentDriveError( + "invalid_skill_metadata", + f"skill row '{key}' is missing required metadata", + status_code=500, + ) + try: + return DriveSkillMetadata.model_validate(json.loads(raw_metadata)) + except (ValueError, TypeError) as exc: + raise AgentDriveError( + "invalid_skill_metadata", + f"skill row '{key}' has invalid stored metadata", + status_code=500, + ) from exc + + @staticmethod + def _manifest_files_from_skill_metadata(*, tenant_id: str, agent_id: str, skill_md_key: str) -> list[str] | None: + with session_factory.create_session() as session: + row = session.scalar( + select(AgentDriveFile).where( + AgentDriveFile.tenant_id == tenant_id, + AgentDriveFile.agent_id == agent_id, + AgentDriveFile.key == skill_md_key, + AgentDriveFile.is_skill.is_(True), + ) + ) + if row is None: + return None + try: + metadata = AgentDriveService._parse_skill_metadata(row.key, row.skill_metadata) + except Exception: + logger.warning("drive skill inspect: malformed skill metadata for %s", skill_md_key, exc_info=True) + return None + return [str(item) for item in (metadata.manifest_files or []) if str(item).strip()] or None + + @classmethod + def _skill_file_entries( + cls, + *, + skill_path: str, + skill_md_key: str, + manifest_files: list[str] | None, + drive_keys: set[str], + ) -> tuple[list[AgentDriveSkillFileInfo], list[str]]: + warnings: list[str] = [] + if manifest_files: + paths = sorted({normalize_drive_key(path) for path in manifest_files}) + else: + paths = sorted( + { + key.removeprefix(f"{skill_path}/") + for key in drive_keys + if not key.endswith(f"/{_SKILL_ARCHIVE_NAME}") + } + ) + warnings.append("manifest_files_unavailable") + + files: list[AgentDriveSkillFileInfo] = [] + for path in paths: + if path == _SKILL_ARCHIVE_NAME: + continue + drive_key = f"{skill_path}/{path}" + files.append( + { + "path": path, + "name": path.rsplit("/", 1)[-1], + "type": "file", + "drive_key": drive_key if drive_key in drive_keys else None, + "available_in_drive": drive_key in drive_keys, + } + ) + if "SKILL.md" not in {file["path"] for file in files}: + files.insert( + 0, + { + "path": "SKILL.md", + "name": "SKILL.md", + "type": "file", + "drive_key": skill_md_key, + "available_in_drive": skill_md_key in drive_keys, + }, + ) + return files, warnings + + @staticmethod + def _build_file_tree(files: list[AgentDriveSkillFileInfo]) -> list[dict[str, Any]]: + root: dict[str, Any] = {} + for file in files: + cursor = root + parts = [part for part in file["path"].split("/") if part] + path_parts: list[str] = [] + for part in parts[:-1]: + path_parts.append(part) + directory = cursor.setdefault( + part, + { + "name": part, + "path": "/".join(path_parts), + "type": "directory", + "children": {}, + }, + ) + cursor = directory["children"] + leaf_name = parts[-1] if parts else file["name"] + cursor[leaf_name] = { + "name": leaf_name, + "path": file["path"], + "type": file["type"], + "drive_key": file["drive_key"], + "available_in_drive": file["available_in_drive"], + } + + def serialize(node: dict[str, Any]) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = [] + for item in sorted(node.values(), key=lambda value: (value["type"] != "directory", value["name"])): + if item["type"] == "directory": + children = serialize(item["children"]) + result.append( + { + "name": item["name"], + "path": item["path"], + "type": "directory", + "children": children, + } + ) + else: + result.append(item) + return result + + return serialize(root) + @staticmethod def _assert_agent_belongs_to_tenant(session: Session, *, tenant_id: str, agent_id: str) -> None: try: @@ -309,7 +657,7 @@ class AgentDriveService: user_id: str, file_kind: AgentDriveFileKind, file_id: str, - ) -> tuple[int | None, str | None]: + ) -> tuple[int | None, str | None, str | None]: """Verify the source file exists for the tenant (and user, for ToolFile). Malformed ids (e.g. a non-UUID hitting a UUID column) are treated as a @@ -328,7 +676,7 @@ class AgentDriveService: raise AgentDriveError( "source_not_found", "source ToolFile not found for this tenant/user", status_code=404 ) - return tool_file.size, tool_file.mimetype + return tool_file.size, tool_file.mimetype, None upload_file = session.scalar( select(UploadFile).where(UploadFile.id == file_id, UploadFile.tenant_id == tenant_id) ) @@ -337,7 +685,7 @@ class AgentDriveService: raise AgentDriveError("source_not_found", "source file ref is invalid", status_code=404) from exc if upload_file is None: raise AgentDriveError("source_not_found", "source UploadFile not found for this tenant", status_code=404) - return upload_file.size, upload_file.mime_type + return upload_file.size, upload_file.mime_type, upload_file.hash def _cleanup_value( self, @@ -509,6 +857,8 @@ __all__ = [ "AgentDriveService", "DriveCommitItem", "DriveFileRef", + "DriveSkillMetadata", + "decode_drive_mention_ref", "normalize_drive_key", "parse_agent_drive_ref", ] diff --git a/api/tests/unit_tests/controllers/console/app/test_agent_drive_inspector.py b/api/tests/unit_tests/controllers/console/app/test_agent_drive_inspector.py index 9d1b6c4c0e9..81f6fcf36bf 100644 --- a/api/tests/unit_tests/controllers/console/app/test_agent_drive_inspector.py +++ b/api/tests/unit_tests/controllers/console/app/test_agent_drive_inspector.py @@ -20,6 +20,10 @@ from controllers.console.app.agent_drive_inspector import ( AgentDriveListByAgentApi, AgentDrivePreviewApi, AgentDrivePreviewByAgentApi, + AgentDriveSkillInspectApi, + AgentDriveSkillInspectByAgentApi, + AgentDriveSkillListApi, + AgentDriveSkillListByAgentApi, ) from services.agent_drive_service import AgentDriveError @@ -97,6 +101,124 @@ def test_list_resolves_workflow_node_binding_agent(): assert composer.resolve_workflow_node_agent_id.call_args.kwargs["node_id"] == "agent-node-1" +def test_skill_list_by_agent_calls_service(): + raw = _raw(AgentDriveSkillListByAgentApi.get) + with app.test_request_context("/"): + with ( + patch(f"{_MOD}.resolve_agent_app_model", return_value=_APP) as resolve_app, + patch(f"{_MOD}.AgentDriveService") as drive, + ): + drive.return_value.list_skills.return_value = [ + { + "path": "pdf-toolkit", + "skill_md_key": "pdf-toolkit/SKILL.md", + "archive_key": "pdf-toolkit/.DIFY-SKILL-FULL.zip", + "name": "PDF Toolkit", + "description": "Work with PDFs.", + "size": 5, + "mime_type": "text/markdown", + "hash": None, + "created_at": 1718000000, + } + ] + body = raw(AgentDriveSkillListByAgentApi(), "tenant-1", "agent-1") + + assert body["items"][0]["path"] == "pdf-toolkit" + resolve_app.assert_called_once_with(tenant_id="tenant-1", agent_id="agent-1") + assert drive.return_value.list_skills.call_args.kwargs["agent_id"] == "agent-1" + + +def test_skill_list_resolves_workflow_node_binding_agent(): + raw = _raw(AgentDriveSkillListApi.get) + with app.test_request_context("/?node_id=agent-node-1"): + with ( + patch(f"{_MOD}.AgentComposerService") as composer, + patch(f"{_MOD}.AgentDriveService") as drive, + ): + composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9" + drive.return_value.list_skills.return_value = [] + body = raw(AgentDriveSkillListApi(), _APP) + + assert body == {"items": []} + assert drive.return_value.list_skills.call_args.kwargs["agent_id"] == "wf-agent-9" + + +def test_skill_inspect_by_agent_returns_strict_json_response(): + raw = _raw(AgentDriveSkillInspectByAgentApi.get) + payload = { + "path": "pdf-toolkit", + "skill_md_key": "pdf-toolkit/SKILL.md", + "archive_key": "pdf-toolkit/.DIFY-SKILL-FULL.zip", + "name": "PDF Toolkit", + "description": "Work with PDFs.", + "size": 5, + "mime_type": "text/markdown", + "hash": None, + "created_at": 1718000000, + "source": "skill_md", + "files": [ + { + "path": "SKILL.md", + "name": "SKILL.md", + "type": "file", + "drive_key": "pdf-toolkit/SKILL.md", + "available_in_drive": True, + } + ], + "file_tree": [], + "skill_md": { + "key": "pdf-toolkit/SKILL.md", + "size": 5, + "truncated": False, + "binary": False, + "text": "# PDF Toolkit\nUse it.\n", + }, + "warnings": [], + } + with app.test_request_context("/"): + with ( + patch(f"{_MOD}.resolve_agent_app_model", return_value=_APP), + patch(f"{_MOD}.AgentDriveService") as drive, + ): + drive.return_value.inspect_skill.return_value = payload + response = raw(AgentDriveSkillInspectByAgentApi(), "tenant-1", "agent-1", "pdf-toolkit") + + assert response.status_code == 200 + assert response.get_json()["skill_md"]["text"] == "# PDF Toolkit\nUse it.\n" + assert b"# PDF Toolkit\\nUse it.\\n" in response.get_data() + + +def test_skill_inspect_resolves_workflow_node_binding_agent(): + raw = _raw(AgentDriveSkillInspectApi.get) + payload = { + "path": "pdf-toolkit", + "skill_md_key": "pdf-toolkit/SKILL.md", + "archive_key": None, + "name": "PDF Toolkit", + "description": "", + "size": 5, + "mime_type": "text/markdown", + "hash": None, + "created_at": None, + "source": "skill_md", + "files": [], + "file_tree": [], + "skill_md": {"key": "pdf-toolkit/SKILL.md", "size": 5, "truncated": False, "binary": False, "text": "# hi"}, + "warnings": [], + } + with app.test_request_context("/?node_id=agent-node-1"): + with ( + patch(f"{_MOD}.AgentComposerService") as composer, + patch(f"{_MOD}.AgentDriveService") as drive, + ): + composer.resolve_workflow_node_agent_id.return_value = "wf-agent-9" + drive.return_value.inspect_skill.return_value = payload + response = raw(AgentDriveSkillInspectApi(), _APP, "pdf-toolkit") + + assert response.get_json()["path"] == "pdf-toolkit" + assert drive.return_value.inspect_skill.call_args.kwargs["agent_id"] == "wf-agent-9" + + def test_list_400_when_no_agent_bound(): raw = _raw(AgentDriveListApi.get) app_without_agent = SimpleNamespace(id="app-1", tenant_id="tenant-1", bound_agent_id=None) diff --git a/api/tests/unit_tests/services/agent/test_skill_standardize_service.py b/api/tests/unit_tests/services/agent/test_skill_standardize_service.py index 128a6c42801..29b4c7e59d6 100644 --- a/api/tests/unit_tests/services/agent/test_skill_standardize_service.py +++ b/api/tests/unit_tests/services/agent/test_skill_standardize_service.py @@ -67,6 +67,10 @@ def test_standardize_creates_two_drive_owned_toolfiles_and_commits(): assert [item.key for item in items] == ["pdf-toolkit/SKILL.md", "pdf-toolkit/.DIFY-SKILL-FULL.zip"] assert all(item.value_owned_by_drive for item in items) assert [item.file_ref.id for item in items] == ["md-tool-file", "zip-tool-file"] + assert items[0].is_skill is True + assert items[0].skill_metadata.name == "PDF Toolkit" + assert items[0].skill_metadata.manifest_files == ["SKILL.md", "scripts/run.py"] + assert items[1].is_skill is False # The returned skill ref carries stable drive paths + file ids. skill = result["skill"] diff --git a/api/tests/unit_tests/services/test_agent_drive_service.py b/api/tests/unit_tests/services/test_agent_drive_service.py index 09cde917946..afc3f08124a 100644 --- a/api/tests/unit_tests/services/test_agent_drive_service.py +++ b/api/tests/unit_tests/services/test_agent_drive_service.py @@ -23,6 +23,7 @@ from services.agent_drive_service import ( AgentDriveError, AgentDriveService, DriveCommitItem, + DriveSkillMetadata, normalize_drive_key, parse_agent_drive_ref, ) @@ -515,3 +516,104 @@ def test_manifest_items_carry_created_at_for_inspector(): _commit("files/x.txt", tf) items = AgentDriveService().manifest(tenant_id=TENANT, agent_id=AGENT) assert items[0]["created_at"] is None or isinstance(items[0]["created_at"], int) + + +# ── DIFY-2517: skill catalog / inspect ─────────────────────────────────────── + + +def _commit_skill(*, manifest_files: list[str] | None = None) -> None: + md = _seed_tool_file(name="SKILL.md") + zf = _seed_tool_file(name="full.zip") + AgentDriveService().commit( + tenant_id=TENANT, + user_id=USER, + agent_id=AGENT, + items=[ + DriveCommitItem( + key="pdf-toolkit/SKILL.md", + file_ref={"kind": "tool_file", "id": md}, + value_owned_by_drive=True, + is_skill=True, + skill_metadata=DriveSkillMetadata( + name="PDF Toolkit", + description="Work with PDFs.", + manifest_files=manifest_files, + ), + ), + DriveCommitItem( + key="pdf-toolkit/.DIFY-SKILL-FULL.zip", + file_ref={"kind": "tool_file", "id": zf}, + value_owned_by_drive=True, + ), + ], + ) + + +def test_list_skills_uses_canonical_skill_rows(): + _commit_skill(manifest_files=["SKILL.md", "scripts/run.py"]) + + skills = AgentDriveService().list_skills(tenant_id=TENANT, agent_id=AGENT) + + created_at = skills[0].pop("created_at") + assert skills == [ + { + "path": "pdf-toolkit", + "skill_md_key": "pdf-toolkit/SKILL.md", + "archive_key": "pdf-toolkit/.DIFY-SKILL-FULL.zip", + "name": "PDF Toolkit", + "description": "Work with PDFs.", + "size": 5, + "mime_type": "text/plain", + "hash": None, + } + ] + assert created_at is None or isinstance(created_at, int) + + +def test_inspect_skill_returns_manifest_files_and_file_tree(): + _commit_skill(manifest_files=["SKILL.md", "references/guide.md", "scripts/run.py"]) + + with patch("services.agent_drive_service.storage") as storage_mock: + storage_mock.load_stream.return_value = iter([b"# PDF Toolkit\n"]) + result = AgentDriveService().inspect_skill(tenant_id=TENANT, agent_id=AGENT, skill_path="pdf-toolkit") + + assert result["source"] == "skill_md" + assert result["warnings"] == [] + assert [file["path"] for file in result["files"]] == ["SKILL.md", "references/guide.md", "scripts/run.py"] + assert result["files"][0]["available_in_drive"] is True + assert result["files"][1]["available_in_drive"] is False + assert result["file_tree"][0]["name"] == "references" + assert result["file_tree"][1]["name"] == "scripts" + assert result["file_tree"][2]["name"] == "SKILL.md" + assert result["skill_md"]["text"] == "# PDF Toolkit\n" + + +def test_inspect_skill_falls_back_to_drive_keys_when_manifest_missing(): + _commit_skill(manifest_files=None) + + with patch("services.agent_drive_service.storage") as storage_mock: + storage_mock.load_stream.return_value = iter([b"# PDF Toolkit\n"]) + result = AgentDriveService().inspect_skill(tenant_id=TENANT, agent_id=AGENT, skill_path="pdf-toolkit") + + assert result["warnings"] == ["manifest_files_unavailable"] + assert [file["path"] for file in result["files"]] == ["SKILL.md"] + + +def test_skill_metadata_rejects_non_canonical_rows(): + tf = _seed_tool_file(name="not-skill.md") + with pytest.raises(AgentDriveError) as exc_info: + AgentDriveService().commit( + tenant_id=TENANT, + user_id=USER, + agent_id=AGENT, + items=[ + DriveCommitItem( + key="files/not-skill.md", + file_ref={"kind": "tool_file", "id": tf}, + value_owned_by_drive=True, + is_skill=True, + skill_metadata=DriveSkillMetadata(name="Bad"), + ) + ], + ) + assert exc_info.value.code == "invalid_skill_key" diff --git a/packages/contracts/generated/api/console/agent/orpc.gen.ts b/packages/contracts/generated/api/console/agent/orpc.gen.ts index fbfca3be118..597649d21c2 100644 --- a/packages/contracts/generated/api/console/agent/orpc.gen.ts +++ b/packages/contracts/generated/api/console/agent/orpc.gen.ts @@ -29,6 +29,10 @@ import { zGetAgentByAgentIdDriveFilesPreviewResponse, zGetAgentByAgentIdDriveFilesQuery, zGetAgentByAgentIdDriveFilesResponse, + zGetAgentByAgentIdDriveSkillsBySkillPathInspectPath, + zGetAgentByAgentIdDriveSkillsBySkillPathInspectResponse, + zGetAgentByAgentIdDriveSkillsPath, + zGetAgentByAgentIdDriveSkillsResponse, zGetAgentByAgentIdLogsByConversationIdMessagesPath, zGetAgentByAgentIdLogsByConversationIdMessagesQuery, zGetAgentByAgentIdLogsByConversationIdMessagesResponse, @@ -336,8 +340,52 @@ export const files = { preview, } +/** + * Inspect one drive-backed skill for slash-menu hover/detail UI + */ +export const get9 = oc + .route({ + description: 'Inspect one drive-backed skill for slash-menu hover/detail UI', + inputStructure: 'detailed', + method: 'GET', + operationId: 'getAgentByAgentIdDriveSkillsBySkillPathInspect', + path: '/agent/{agent_id}/drive/skills/{skill_path}/inspect', + tags: ['console'], + }) + .input(z.object({ params: zGetAgentByAgentIdDriveSkillsBySkillPathInspectPath })) + .output(zGetAgentByAgentIdDriveSkillsBySkillPathInspectResponse) + +export const inspect = { + get: get9, +} + +export const bySkillPath = { + inspect, +} + +/** + * List drive-backed skills for an Agent App + */ +export const get10 = oc + .route({ + description: 'List drive-backed skills for an Agent App', + inputStructure: 'detailed', + method: 'GET', + operationId: 'getAgentByAgentIdDriveSkills', + path: '/agent/{agent_id}/drive/skills', + tags: ['console'], + }) + .input(z.object({ params: zGetAgentByAgentIdDriveSkillsPath })) + .output(zGetAgentByAgentIdDriveSkillsResponse) + +export const skills = { + get: get10, + bySkillPath, +} + export const drive = { files, + skills, } /** @@ -420,7 +468,7 @@ export const files2 = { post: post6, } -export const get9 = oc +export const get11 = oc .route({ inputStructure: 'detailed', method: 'GET', @@ -432,10 +480,10 @@ export const get9 = oc .output(zGetAgentByAgentIdLogSourcesResponse) export const logSources = { - get: get9, + get: get11, } -export const get10 = oc +export const get12 = oc .route({ inputStructure: 'detailed', method: 'GET', @@ -452,14 +500,14 @@ export const get10 = oc .output(zGetAgentByAgentIdLogsByConversationIdMessagesResponse) export const messages = { - get: get10, + get: get12, } export const byConversationId = { messages, } -export const get11 = oc +export const get13 = oc .route({ inputStructure: 'detailed', method: 'GET', @@ -473,14 +521,14 @@ export const get11 = oc .output(zGetAgentByAgentIdLogsResponse) export const logs = { - get: get11, + get: get13, byConversationId, } /** * Get Agent App message details by ID */ -export const get12 = oc +export const get14 = oc .route({ description: 'Get Agent App message details by ID', inputStructure: 'detailed', @@ -493,7 +541,7 @@ export const get12 = oc .output(zGetAgentByAgentIdMessagesByMessageIdResponse) export const byMessageId2 = { - get: get12, + get: get14, } export const messages2 = { @@ -503,7 +551,7 @@ export const messages2 = { /** * List workflow apps that reference this Agent App's bound Agent (read-only) */ -export const get13 = oc +export const get15 = oc .route({ description: 'List workflow apps that reference this Agent App\'s bound Agent (read-only)', inputStructure: 'detailed', @@ -516,13 +564,13 @@ export const get13 = oc .output(zGetAgentByAgentIdReferencingWorkflowsResponse) export const referencingWorkflows = { - get: get13, + get: get15, } /** * Read a text/binary preview file in an Agent App conversation sandbox */ -export const get14 = oc +export const get16 = oc .route({ description: 'Read a text/binary preview file in an Agent App conversation sandbox', inputStructure: 'detailed', @@ -540,7 +588,7 @@ export const get14 = oc .output(zGetAgentByAgentIdSandboxFilesReadResponse) export const read = { - get: get14, + get: get16, } /** @@ -570,7 +618,7 @@ export const upload = { /** * List a directory in an Agent App conversation sandbox */ -export const get15 = oc +export const get17 = oc .route({ description: 'List a directory in an Agent App conversation sandbox', inputStructure: 'detailed', @@ -588,7 +636,7 @@ export const get15 = oc .output(zGetAgentByAgentIdSandboxFilesResponse) export const files3 = { - get: get15, + get: get17, read, upload, } @@ -661,12 +709,12 @@ export const bySlug = { inferTools, } -export const skills = { +export const skills2 = { upload: upload2, bySlug, } -export const get16 = oc +export const get18 = oc .route({ inputStructure: 'detailed', method: 'GET', @@ -683,14 +731,14 @@ export const get16 = oc .output(zGetAgentByAgentIdStatisticsSummaryResponse) export const summary = { - get: get16, + get: get18, } export const statistics = { summary, } -export const get17 = oc +export const get19 = oc .route({ inputStructure: 'detailed', method: 'GET', @@ -702,10 +750,10 @@ export const get17 = oc .output(zGetAgentByAgentIdVersionsByVersionIdResponse) export const byVersionId = { - get: get17, + get: get19, } -export const get18 = oc +export const get20 = oc .route({ inputStructure: 'detailed', method: 'GET', @@ -717,7 +765,7 @@ export const get18 = oc .output(zGetAgentByAgentIdVersionsResponse) export const versions = { - get: get18, + get: get20, byVersionId, } @@ -733,7 +781,7 @@ export const delete3 = oc .input(z.object({ params: zDeleteAgentByAgentIdPath })) .output(zDeleteAgentByAgentIdResponse) -export const get19 = oc +export const get21 = oc .route({ inputStructure: 'detailed', method: 'GET', @@ -757,7 +805,7 @@ export const put2 = oc export const byAgentId = { delete: delete3, - get: get19, + get: get21, put: put2, chatMessages, composer, @@ -771,12 +819,12 @@ export const byAgentId = { messages: messages2, referencingWorkflows, sandbox, - skills, + skills: skills2, statistics, versions, } -export const get20 = oc +export const get22 = oc .route({ inputStructure: 'detailed', method: 'GET', @@ -800,7 +848,7 @@ export const post10 = oc .output(zPostAgentResponse) export const agent = { - get: get20, + get: get22, post: post10, inviteOptions, byAgentId, diff --git a/packages/contracts/generated/api/console/agent/types.gen.ts b/packages/contracts/generated/api/console/agent/types.gen.ts index 64afc442406..4192dfbaf9e 100644 --- a/packages/contracts/generated/api/console/agent/types.gen.ts +++ b/packages/contracts/generated/api/console/agent/types.gen.ts @@ -148,6 +148,29 @@ export type AgentDrivePreviewResponse = { truncated: boolean } +export type AgentDriveSkillListResponse = { + items?: Array +} + +export type AgentDriveSkillInspectResponse = { + archive_key?: string | null + created_at?: number | null + description: string + file_tree?: Array<{ + [key: string]: unknown + }> + files?: Array + hash?: string | null + mime_type?: string | null + name: string + path: string + size?: number | null + skill_md: AgentDriveSkillMarkdownResponse + skill_md_key: string + source: string + warnings?: Array +} + export type AgentAppFeaturesPayload = { opening_statement?: string | null retriever_resource?: AgentFeatureToggleConfig | null @@ -530,9 +553,39 @@ export type AgentDriveItemResponse = { created_at?: number | null file_kind: string hash?: string | null + is_skill?: boolean | null key: string mime_type?: string | null size?: number | null + skill_metadata?: string | null +} + +export type AgentDriveSkillItemResponse = { + archive_key?: string | null + created_at?: number | null + description: string + hash?: string | null + mime_type?: string | null + name: string + path: string + size?: number | null + skill_md_key: string +} + +export type AgentDriveSkillFileResponse = { + available_in_drive: boolean + drive_key?: string | null + name: string + path: string + type: string +} + +export type AgentDriveSkillMarkdownResponse = { + binary: boolean + key: string + size?: number | null + text?: string | null + truncated: boolean } export type AgentFeatureToggleConfig = { @@ -1837,6 +1890,39 @@ export type GetAgentByAgentIdDriveFilesPreviewResponses = { export type GetAgentByAgentIdDriveFilesPreviewResponse = GetAgentByAgentIdDriveFilesPreviewResponses[keyof GetAgentByAgentIdDriveFilesPreviewResponses] +export type GetAgentByAgentIdDriveSkillsData = { + body?: never + path: { + agent_id: string + } + query?: never + url: '/agent/{agent_id}/drive/skills' +} + +export type GetAgentByAgentIdDriveSkillsResponses = { + 200: AgentDriveSkillListResponse +} + +export type GetAgentByAgentIdDriveSkillsResponse + = GetAgentByAgentIdDriveSkillsResponses[keyof GetAgentByAgentIdDriveSkillsResponses] + +export type GetAgentByAgentIdDriveSkillsBySkillPathInspectData = { + body?: never + path: { + agent_id: string + skill_path: string + } + query?: never + url: '/agent/{agent_id}/drive/skills/{skill_path}/inspect' +} + +export type GetAgentByAgentIdDriveSkillsBySkillPathInspectResponses = { + 200: AgentDriveSkillInspectResponse +} + +export type GetAgentByAgentIdDriveSkillsBySkillPathInspectResponse + = GetAgentByAgentIdDriveSkillsBySkillPathInspectResponses[keyof GetAgentByAgentIdDriveSkillsBySkillPathInspectResponses] + export type PostAgentByAgentIdFeaturesData = { body: AgentAppFeaturesPayload path: { diff --git a/packages/contracts/generated/api/console/agent/zod.gen.ts b/packages/contracts/generated/api/console/agent/zod.gen.ts index 7d6bd6f5eb2..5dca172d9ea 100644 --- a/packages/contracts/generated/api/console/agent/zod.gen.ts +++ b/packages/contracts/generated/api/console/agent/zod.gen.ts @@ -286,9 +286,11 @@ export const zAgentDriveItemResponse = z.object({ created_at: z.int().nullish(), file_kind: z.string(), hash: z.string().nullish(), + is_skill: z.boolean().nullish(), key: z.string(), mime_type: z.string().nullish(), size: z.int().nullish(), + skill_metadata: z.string().nullish(), }) /** @@ -298,6 +300,70 @@ export const zAgentDriveListResponse = z.object({ items: z.array(zAgentDriveItemResponse).optional(), }) +/** + * AgentDriveSkillItemResponse + */ +export const zAgentDriveSkillItemResponse = z.object({ + archive_key: z.string().nullish(), + created_at: z.int().nullish(), + description: z.string(), + hash: z.string().nullish(), + mime_type: z.string().nullish(), + name: z.string(), + path: z.string(), + size: z.int().nullish(), + skill_md_key: z.string(), +}) + +/** + * AgentDriveSkillListResponse + */ +export const zAgentDriveSkillListResponse = z.object({ + items: z.array(zAgentDriveSkillItemResponse).optional(), +}) + +/** + * AgentDriveSkillFileResponse + */ +export const zAgentDriveSkillFileResponse = z.object({ + available_in_drive: z.boolean(), + drive_key: z.string().nullish(), + name: z.string(), + path: z.string(), + type: z.string(), +}) + +/** + * AgentDriveSkillMarkdownResponse + */ +export const zAgentDriveSkillMarkdownResponse = z.object({ + binary: z.boolean(), + key: z.string(), + size: z.int().nullish(), + text: z.string().nullish(), + truncated: z.boolean(), +}) + +/** + * AgentDriveSkillInspectResponse + */ +export const zAgentDriveSkillInspectResponse = z.object({ + archive_key: z.string().nullish(), + created_at: z.int().nullish(), + description: z.string(), + file_tree: z.array(z.record(z.string(), z.unknown())).optional(), + files: z.array(zAgentDriveSkillFileResponse).optional(), + hash: z.string().nullish(), + mime_type: z.string().nullish(), + name: z.string(), + path: z.string(), + size: z.int().nullish(), + skill_md: zAgentDriveSkillMarkdownResponse, + skill_md_key: z.string(), + source: z.string(), + warnings: z.array(z.string()).optional(), +}) + /** * AgentFeatureToggleConfig */ @@ -2304,6 +2370,26 @@ export const zGetAgentByAgentIdDriveFilesPreviewQuery = z.object({ */ export const zGetAgentByAgentIdDriveFilesPreviewResponse = zAgentDrivePreviewResponse +export const zGetAgentByAgentIdDriveSkillsPath = z.object({ + agent_id: z.uuid(), +}) + +/** + * Drive skills + */ +export const zGetAgentByAgentIdDriveSkillsResponse = zAgentDriveSkillListResponse + +export const zGetAgentByAgentIdDriveSkillsBySkillPathInspectPath = z.object({ + agent_id: z.uuid(), + skill_path: z.string(), +}) + +/** + * Drive skill inspect view + */ +export const zGetAgentByAgentIdDriveSkillsBySkillPathInspectResponse + = zAgentDriveSkillInspectResponse + export const zPostAgentByAgentIdFeaturesBody = zAgentAppFeaturesPayload export const zPostAgentByAgentIdFeaturesPath = z.object({ diff --git a/packages/contracts/generated/api/console/apps/orpc.gen.ts b/packages/contracts/generated/api/console/apps/orpc.gen.ts index eab3c17eb43..f24407a17d4 100644 --- a/packages/contracts/generated/api/console/apps/orpc.gen.ts +++ b/packages/contracts/generated/api/console/apps/orpc.gen.ts @@ -54,6 +54,12 @@ import { zGetAppsByAppIdAgentDriveFilesPreviewResponse, zGetAppsByAppIdAgentDriveFilesQuery, zGetAppsByAppIdAgentDriveFilesResponse, + zGetAppsByAppIdAgentDriveSkillsBySkillPathInspectPath, + zGetAppsByAppIdAgentDriveSkillsBySkillPathInspectQuery, + zGetAppsByAppIdAgentDriveSkillsBySkillPathInspectResponse, + zGetAppsByAppIdAgentDriveSkillsPath, + zGetAppsByAppIdAgentDriveSkillsQuery, + zGetAppsByAppIdAgentDriveSkillsResponse, zGetAppsByAppIdAgentLogsPath, zGetAppsByAppIdAgentLogsQuery, zGetAppsByAppIdAgentLogsResponse, @@ -861,8 +867,62 @@ export const files = { preview: preview2, } +/** + * Inspect one drive-backed skill for slash-menu hover/detail UI + */ +export const get8 = oc + .route({ + description: 'Inspect one drive-backed skill for slash-menu hover/detail UI', + inputStructure: 'detailed', + method: 'GET', + operationId: 'getAppsByAppIdAgentDriveSkillsBySkillPathInspect', + path: '/apps/{app_id}/agent/drive/skills/{skill_path}/inspect', + tags: ['console'], + }) + .input( + z.object({ + params: zGetAppsByAppIdAgentDriveSkillsBySkillPathInspectPath, + query: zGetAppsByAppIdAgentDriveSkillsBySkillPathInspectQuery.optional(), + }), + ) + .output(zGetAppsByAppIdAgentDriveSkillsBySkillPathInspectResponse) + +export const inspect = { + get: get8, +} + +export const bySkillPath = { + inspect, +} + +/** + * List drive-backed skills for the bound agent + */ +export const get9 = oc + .route({ + description: 'List drive-backed skills for the bound agent', + inputStructure: 'detailed', + method: 'GET', + operationId: 'getAppsByAppIdAgentDriveSkills', + path: '/apps/{app_id}/agent/drive/skills', + tags: ['console'], + }) + .input( + z.object({ + params: zGetAppsByAppIdAgentDriveSkillsPath, + query: zGetAppsByAppIdAgentDriveSkillsQuery.optional(), + }), + ) + .output(zGetAppsByAppIdAgentDriveSkillsResponse) + +export const skills = { + get: get9, + bySkillPath, +} + export const drive = { files, + skills, } /** @@ -920,7 +980,7 @@ export const files2 = { * * Get agent execution logs for an application */ -export const get8 = oc +export const get10 = oc .route({ description: 'Get agent execution logs for an application', inputStructure: 'detailed', @@ -934,7 +994,7 @@ export const get8 = oc .output(zGetAppsByAppIdAgentLogsResponse) export const logs = { - get: get8, + get: get10, } /** @@ -1021,7 +1081,7 @@ export const bySlug = { inferTools, } -export const skills = { +export const skills2 = { upload, bySlug, } @@ -1030,13 +1090,13 @@ export const agent = { drive, files: files2, logs, - skills, + skills: skills2, } /** * Get status of annotation reply action job */ -export const get9 = oc +export const get11 = oc .route({ description: 'Get status of annotation reply action job', inputStructure: 'detailed', @@ -1049,7 +1109,7 @@ export const get9 = oc .output(zGetAppsByAppIdAnnotationReplyByActionStatusByJobIdResponse) export const byJobId = { - get: get9, + get: get11, } export const status = { @@ -1088,7 +1148,7 @@ export const annotationReply = { /** * Get annotation settings for an app */ -export const get10 = oc +export const get12 = oc .route({ description: 'Get annotation settings for an app', inputStructure: 'detailed', @@ -1101,7 +1161,7 @@ export const get10 = oc .output(zGetAppsByAppIdAnnotationSettingResponse) export const annotationSetting = { - get: get10, + get: get12, } /** @@ -1154,7 +1214,7 @@ export const batchImport = { /** * Get status of batch import job */ -export const get11 = oc +export const get13 = oc .route({ description: 'Get status of batch import job', inputStructure: 'detailed', @@ -1167,7 +1227,7 @@ export const get11 = oc .output(zGetAppsByAppIdAnnotationsBatchImportStatusByJobIdResponse) export const byJobId2 = { - get: get11, + get: get13, } export const batchImportStatus = { @@ -1177,7 +1237,7 @@ export const batchImportStatus = { /** * Get count of message annotations for the app */ -export const get12 = oc +export const get14 = oc .route({ description: 'Get count of message annotations for the app', inputStructure: 'detailed', @@ -1190,13 +1250,13 @@ export const get12 = oc .output(zGetAppsByAppIdAnnotationsCountResponse) export const count2 = { - get: get12, + get: get14, } /** * Export all annotations for an app with CSV injection protection */ -export const get13 = oc +export const get15 = oc .route({ description: 'Export all annotations for an app with CSV injection protection', inputStructure: 'detailed', @@ -1209,13 +1269,13 @@ export const get13 = oc .output(zGetAppsByAppIdAnnotationsExportResponse) export const export_ = { - get: get13, + get: get15, } /** * Get hit histories for an annotation */ -export const get14 = oc +export const get16 = oc .route({ description: 'Get hit histories for an annotation', inputStructure: 'detailed', @@ -1233,7 +1293,7 @@ export const get14 = oc .output(zGetAppsByAppIdAnnotationsByAnnotationIdHitHistoriesResponse) export const hitHistories = { - get: get14, + get: get16, } export const delete3 = oc @@ -1289,7 +1349,7 @@ export const delete4 = oc /** * Get annotations for an app with pagination */ -export const get15 = oc +export const get17 = oc .route({ description: 'Get annotations for an app with pagination', inputStructure: 'detailed', @@ -1326,7 +1386,7 @@ export const post16 = oc export const annotations = { delete: delete4, - get: get15, + get: get17, post: post16, batchImport, batchImportStatus, @@ -1392,7 +1452,7 @@ export const delete5 = oc /** * Get chat conversation details */ -export const get16 = oc +export const get18 = oc .route({ description: 'Get chat conversation details', inputStructure: 'detailed', @@ -1406,13 +1466,13 @@ export const get16 = oc export const byConversationId = { delete: delete5, - get: get16, + get: get18, } /** * Get chat conversations with pagination, filtering and summary */ -export const get17 = oc +export const get19 = oc .route({ description: 'Get chat conversations with pagination, filtering and summary', inputStructure: 'detailed', @@ -1430,14 +1490,14 @@ export const get17 = oc .output(zGetAppsByAppIdChatConversationsResponse) export const chatConversations = { - get: get17, + get: get19, byConversationId, } /** * Get suggested questions for a message */ -export const get18 = oc +export const get20 = oc .route({ description: 'Get suggested questions for a message', inputStructure: 'detailed', @@ -1450,7 +1510,7 @@ export const get18 = oc .output(zGetAppsByAppIdChatMessagesByMessageIdSuggestedQuestionsResponse) export const suggestedQuestions = { - get: get18, + get: get20, } export const byMessageId = { @@ -1483,7 +1543,7 @@ export const byTaskId = { /** * Get chat messages for a conversation with pagination */ -export const get19 = oc +export const get21 = oc .route({ description: 'Get chat messages for a conversation with pagination', inputStructure: 'detailed', @@ -1498,7 +1558,7 @@ export const get19 = oc .output(zGetAppsByAppIdChatMessagesResponse) export const chatMessages = { - get: get19, + get: get21, byMessageId, byTaskId, } @@ -1522,7 +1582,7 @@ export const delete6 = oc /** * Get completion conversation details with messages */ -export const get20 = oc +export const get22 = oc .route({ description: 'Get completion conversation details with messages', inputStructure: 'detailed', @@ -1536,13 +1596,13 @@ export const get20 = oc export const byConversationId2 = { delete: delete6, - get: get20, + get: get22, } /** * Get completion conversations with pagination and filtering */ -export const get21 = oc +export const get23 = oc .route({ description: 'Get completion conversations with pagination and filtering', inputStructure: 'detailed', @@ -1560,7 +1620,7 @@ export const get21 = oc .output(zGetAppsByAppIdCompletionConversationsResponse) export const completionConversations = { - get: get21, + get: get23, byConversationId: byConversationId2, } @@ -1615,7 +1675,7 @@ export const completionMessages = { /** * Get conversation variables for an application */ -export const get22 = oc +export const get24 = oc .route({ description: 'Get conversation variables for an application', inputStructure: 'detailed', @@ -1633,7 +1693,7 @@ export const get22 = oc .output(zGetAppsByAppIdConversationVariablesResponse) export const conversationVariables = { - get: get22, + get: get24, } /** @@ -1694,7 +1754,7 @@ export const copy = { * * Export application configuration as DSL */ -export const get23 = oc +export const get25 = oc .route({ description: 'Export application configuration as DSL', inputStructure: 'detailed', @@ -1710,13 +1770,13 @@ export const get23 = oc .output(zGetAppsByAppIdExportResponse) export const export2 = { - get: get23, + get: get25, } /** * Export user feedback data for Google Sheets */ -export const get24 = oc +export const get26 = oc .route({ description: 'Export user feedback data for Google Sheets', inputStructure: 'detailed', @@ -1734,7 +1794,7 @@ export const get24 = oc .output(zGetAppsByAppIdFeedbacksExportResponse) export const export3 = { - get: get24, + get: get26, } /** @@ -1779,7 +1839,7 @@ export const icon = { /** * Get message details by ID */ -export const get25 = oc +export const get27 = oc .route({ description: 'Get message details by ID', inputStructure: 'detailed', @@ -1792,7 +1852,7 @@ export const get25 = oc .output(zGetAppsByAppIdMessagesByMessageIdResponse) export const byMessageId2 = { - get: get25, + get: get27, } export const messages = { @@ -1864,7 +1924,7 @@ export const publishToCreatorsPlatform = { /** * Get MCP server configuration for an application */ -export const get26 = oc +export const get28 = oc .route({ description: 'Get MCP server configuration for an application', inputStructure: 'detailed', @@ -1908,7 +1968,7 @@ export const put = oc .output(zPutAppsByAppIdServerResponse) export const server = { - get: get26, + get: get28, post: post29, put, } @@ -2009,7 +2069,7 @@ export const star = { /** * Get average response time statistics for an application */ -export const get27 = oc +export const get29 = oc .route({ description: 'Get average response time statistics for an application', inputStructure: 'detailed', @@ -2027,13 +2087,13 @@ export const get27 = oc .output(zGetAppsByAppIdStatisticsAverageResponseTimeResponse) export const averageResponseTime = { - get: get27, + get: get29, } /** * Get average session interaction statistics for an application */ -export const get28 = oc +export const get30 = oc .route({ description: 'Get average session interaction statistics for an application', inputStructure: 'detailed', @@ -2051,13 +2111,13 @@ export const get28 = oc .output(zGetAppsByAppIdStatisticsAverageSessionInteractionsResponse) export const averageSessionInteractions = { - get: get28, + get: get30, } /** * Get daily conversation statistics for an application */ -export const get29 = oc +export const get31 = oc .route({ description: 'Get daily conversation statistics for an application', inputStructure: 'detailed', @@ -2075,13 +2135,13 @@ export const get29 = oc .output(zGetAppsByAppIdStatisticsDailyConversationsResponse) export const dailyConversations = { - get: get29, + get: get31, } /** * Get daily terminal/end-user statistics for an application */ -export const get30 = oc +export const get32 = oc .route({ description: 'Get daily terminal/end-user statistics for an application', inputStructure: 'detailed', @@ -2099,13 +2159,13 @@ export const get30 = oc .output(zGetAppsByAppIdStatisticsDailyEndUsersResponse) export const dailyEndUsers = { - get: get30, + get: get32, } /** * Get daily message statistics for an application */ -export const get31 = oc +export const get33 = oc .route({ description: 'Get daily message statistics for an application', inputStructure: 'detailed', @@ -2123,13 +2183,13 @@ export const get31 = oc .output(zGetAppsByAppIdStatisticsDailyMessagesResponse) export const dailyMessages = { - get: get31, + get: get33, } /** * Get daily token cost statistics for an application */ -export const get32 = oc +export const get34 = oc .route({ description: 'Get daily token cost statistics for an application', inputStructure: 'detailed', @@ -2147,13 +2207,13 @@ export const get32 = oc .output(zGetAppsByAppIdStatisticsTokenCostsResponse) export const tokenCosts = { - get: get32, + get: get34, } /** * Get tokens per second statistics for an application */ -export const get33 = oc +export const get35 = oc .route({ description: 'Get tokens per second statistics for an application', inputStructure: 'detailed', @@ -2171,13 +2231,13 @@ export const get33 = oc .output(zGetAppsByAppIdStatisticsTokensPerSecondResponse) export const tokensPerSecond = { - get: get33, + get: get35, } /** * Get user satisfaction rate statistics for an application */ -export const get34 = oc +export const get36 = oc .route({ description: 'Get user satisfaction rate statistics for an application', inputStructure: 'detailed', @@ -2195,7 +2255,7 @@ export const get34 = oc .output(zGetAppsByAppIdStatisticsUserSatisfactionRateResponse) export const userSatisfactionRate = { - get: get34, + get: get36, } export const statistics = { @@ -2212,7 +2272,7 @@ export const statistics = { /** * Get available TTS voices for a specific language */ -export const get35 = oc +export const get37 = oc .route({ description: 'Get available TTS voices for a specific language', inputStructure: 'detailed', @@ -2230,7 +2290,7 @@ export const get35 = oc .output(zGetAppsByAppIdTextToAudioVoicesResponse) export const voices = { - get: get35, + get: get37, } /** @@ -2260,7 +2320,7 @@ export const textToAudio = { * * Get app tracing configuration */ -export const get36 = oc +export const get38 = oc .route({ description: 'Get app tracing configuration', inputStructure: 'detailed', @@ -2289,7 +2349,7 @@ export const post35 = oc .output(zPostAppsByAppIdTraceResponse) export const trace = { - get: get36, + get: get38, post: post35, } @@ -2320,7 +2380,7 @@ export const delete8 = oc /** * Get tracing configuration for an application */ -export const get37 = oc +export const get39 = oc .route({ description: 'Get tracing configuration for an application', inputStructure: 'detailed', @@ -2377,7 +2437,7 @@ export const post36 = oc export const traceConfig = { delete: delete8, - get: get37, + get: get39, patch, post: post36, } @@ -2409,7 +2469,7 @@ export const triggerEnable = { /** * Get app triggers list */ -export const get38 = oc +export const get40 = oc .route({ inputStructure: 'detailed', method: 'GET', @@ -2422,7 +2482,7 @@ export const get38 = oc .output(zGetAppsByAppIdTriggersResponse) export const triggers = { - get: get38, + get: get40, } /** @@ -2430,7 +2490,7 @@ export const triggers = { * * Get workflow application execution logs */ -export const get39 = oc +export const get41 = oc .route({ description: 'Get workflow application execution logs', inputStructure: 'detailed', @@ -2449,7 +2509,7 @@ export const get39 = oc .output(zGetAppsByAppIdWorkflowAppLogsResponse) export const workflowAppLogs = { - get: get39, + get: get41, } /** @@ -2457,7 +2517,7 @@ export const workflowAppLogs = { * * Get workflow archived execution logs */ -export const get40 = oc +export const get42 = oc .route({ description: 'Get workflow archived execution logs', inputStructure: 'detailed', @@ -2476,7 +2536,7 @@ export const get40 = oc .output(zGetAppsByAppIdWorkflowArchivedLogsResponse) export const workflowArchivedLogs = { - get: get40, + get: get42, } /** @@ -2484,7 +2544,7 @@ export const workflowArchivedLogs = { * * Get workflow runs count statistics */ -export const get41 = oc +export const get43 = oc .route({ description: 'Get workflow runs count statistics', inputStructure: 'detailed', @@ -2503,7 +2563,7 @@ export const get41 = oc .output(zGetAppsByAppIdWorkflowRunsCountResponse) export const count3 = { - get: get41, + get: get43, } /** @@ -2539,7 +2599,7 @@ export const tasks = { /** * Generate a download URL for an archived workflow run. */ -export const get42 = oc +export const get44 = oc .route({ description: 'Generate a download URL for an archived workflow run.', inputStructure: 'detailed', @@ -2552,7 +2612,7 @@ export const get42 = oc .output(zGetAppsByAppIdWorkflowRunsByRunIdExportResponse) export const export4 = { - get: get42, + get: get44, } /** @@ -2560,7 +2620,7 @@ export const export4 = { * * Get workflow run node execution list */ -export const get43 = oc +export const get45 = oc .route({ description: 'Get workflow run node execution list', inputStructure: 'detailed', @@ -2574,7 +2634,7 @@ export const get43 = oc .output(zGetAppsByAppIdWorkflowRunsByRunIdNodeExecutionsResponse) export const nodeExecutions = { - get: get43, + get: get45, } /** @@ -2582,7 +2642,7 @@ export const nodeExecutions = { * * Get workflow run detail */ -export const get44 = oc +export const get46 = oc .route({ description: 'Get workflow run detail', inputStructure: 'detailed', @@ -2596,7 +2656,7 @@ export const get44 = oc .output(zGetAppsByAppIdWorkflowRunsByRunIdResponse) export const byRunId = { - get: get44, + get: get46, export: export4, nodeExecutions, } @@ -2604,7 +2664,7 @@ export const byRunId = { /** * Read a text/binary preview file in a workflow Agent node sandbox */ -export const get45 = oc +export const get47 = oc .route({ description: 'Read a text/binary preview file in a workflow Agent node sandbox', inputStructure: 'detailed', @@ -2622,7 +2682,7 @@ export const get45 = oc .output(zGetAppsByAppIdWorkflowRunsByWorkflowRunIdAgentNodesByNodeIdSandboxFilesReadResponse) export const read = { - get: get45, + get: get47, } /** @@ -2652,7 +2712,7 @@ export const upload2 = { /** * List a directory in a workflow Agent node sandbox */ -export const get46 = oc +export const get48 = oc .route({ description: 'List a directory in a workflow Agent node sandbox', inputStructure: 'detailed', @@ -2671,7 +2731,7 @@ export const get46 = oc .output(zGetAppsByAppIdWorkflowRunsByWorkflowRunIdAgentNodesByNodeIdSandboxFilesResponse) export const files3 = { - get: get46, + get: get48, read, upload: upload2, } @@ -2697,7 +2757,7 @@ export const byWorkflowRunId = { * * Get workflow run list */ -export const get47 = oc +export const get49 = oc .route({ description: 'Get workflow run list', inputStructure: 'detailed', @@ -2716,7 +2776,7 @@ export const get47 = oc .output(zGetAppsByAppIdWorkflowRunsResponse) export const workflowRuns2 = { - get: get47, + get: get49, count: count3, tasks, byRunId, @@ -2728,7 +2788,7 @@ export const workflowRuns2 = { * * Get all users in current tenant for mentions */ -export const get48 = oc +export const get50 = oc .route({ description: 'Get all users in current tenant for mentions', inputStructure: 'detailed', @@ -2742,7 +2802,7 @@ export const get48 = oc .output(zGetAppsByAppIdWorkflowCommentsMentionUsersResponse) export const mentionUsers = { - get: get48, + get: get50, } /** @@ -2867,7 +2927,7 @@ export const delete10 = oc * * Get a specific workflow comment */ -export const get49 = oc +export const get51 = oc .route({ description: 'Get a specific workflow comment', inputStructure: 'detailed', @@ -2905,7 +2965,7 @@ export const put3 = oc export const byCommentId = { delete: delete10, - get: get49, + get: get51, put: put3, replies, resolve, @@ -2916,7 +2976,7 @@ export const byCommentId = { * * Get all comments for a workflow */ -export const get50 = oc +export const get52 = oc .route({ description: 'Get all comments for a workflow', inputStructure: 'detailed', @@ -2954,7 +3014,7 @@ export const post42 = oc .output(zPostAppsByAppIdWorkflowCommentsResponse) export const comments = { - get: get50, + get: get52, post: post42, mentionUsers, byCommentId, @@ -2963,7 +3023,7 @@ export const comments = { /** * Get workflow average app interaction statistics */ -export const get51 = oc +export const get53 = oc .route({ description: 'Get workflow average app interaction statistics', inputStructure: 'detailed', @@ -2981,13 +3041,13 @@ export const get51 = oc .output(zGetAppsByAppIdWorkflowStatisticsAverageAppInteractionsResponse) export const averageAppInteractions = { - get: get51, + get: get53, } /** * Get workflow daily runs statistics */ -export const get52 = oc +export const get54 = oc .route({ description: 'Get workflow daily runs statistics', inputStructure: 'detailed', @@ -3005,13 +3065,13 @@ export const get52 = oc .output(zGetAppsByAppIdWorkflowStatisticsDailyConversationsResponse) export const dailyConversations2 = { - get: get52, + get: get54, } /** * Get workflow daily terminals statistics */ -export const get53 = oc +export const get55 = oc .route({ description: 'Get workflow daily terminals statistics', inputStructure: 'detailed', @@ -3029,13 +3089,13 @@ export const get53 = oc .output(zGetAppsByAppIdWorkflowStatisticsDailyTerminalsResponse) export const dailyTerminals = { - get: get53, + get: get55, } /** * Get workflow daily token cost statistics */ -export const get54 = oc +export const get56 = oc .route({ description: 'Get workflow daily token cost statistics', inputStructure: 'detailed', @@ -3053,7 +3113,7 @@ export const get54 = oc .output(zGetAppsByAppIdWorkflowStatisticsTokenCostsResponse) export const tokenCosts2 = { - get: get54, + get: get56, } export const statistics2 = { @@ -3073,7 +3133,7 @@ export const workflow = { * * Get default block configuration by type */ -export const get55 = oc +export const get57 = oc .route({ description: 'Get default block configuration by type', inputStructure: 'detailed', @@ -3092,7 +3152,7 @@ export const get55 = oc .output(zGetAppsByAppIdWorkflowsDefaultWorkflowBlockConfigsByBlockTypeResponse) export const byBlockType = { - get: get55, + get: get57, } /** @@ -3100,7 +3160,7 @@ export const byBlockType = { * * Get default block configurations for workflow */ -export const get56 = oc +export const get58 = oc .route({ description: 'Get default block configurations for workflow', inputStructure: 'detailed', @@ -3114,14 +3174,14 @@ export const get56 = oc .output(zGetAppsByAppIdWorkflowsDefaultWorkflowBlockConfigsResponse) export const defaultWorkflowBlockConfigs = { - get: get56, + get: get58, byBlockType, } /** * Get conversation variables for workflow */ -export const get57 = oc +export const get59 = oc .route({ description: 'Get conversation variables for workflow', inputStructure: 'detailed', @@ -3154,7 +3214,7 @@ export const post43 = oc .output(zPostAppsByAppIdWorkflowsDraftConversationVariablesResponse) export const conversationVariables2 = { - get: get57, + get: get59, post: post43, } @@ -3163,7 +3223,7 @@ export const conversationVariables2 = { * * Get environment variables for workflow */ -export const get58 = oc +export const get60 = oc .route({ description: 'Get environment variables for workflow', inputStructure: 'detailed', @@ -3197,7 +3257,7 @@ export const post44 = oc .output(zPostAppsByAppIdWorkflowsDraftEnvironmentVariablesResponse) export const environmentVariables = { - get: get58, + get: get60, post: post44, } @@ -3402,7 +3462,7 @@ export const loop2 = { nodes: nodes6, } -export const get59 = oc +export const get61 = oc .route({ inputStructure: 'detailed', method: 'GET', @@ -3416,7 +3476,7 @@ export const get59 = oc .output(zGetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerCandidatesResponse) export const candidates = { - get: get59, + get: get61, } export const post51 = oc @@ -3479,7 +3539,7 @@ export const validate = { post: post53, } -export const get60 = oc +export const get62 = oc .route({ inputStructure: 'detailed', method: 'GET', @@ -3507,7 +3567,7 @@ export const put4 = oc .output(zPutAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerResponse) export const agentComposer = { - get: get60, + get: get62, put: put4, candidates, impact, @@ -3518,7 +3578,7 @@ export const agentComposer = { /** * Get last run result for draft workflow node */ -export const get61 = oc +export const get63 = oc .route({ description: 'Get last run result for draft workflow node', inputStructure: 'detailed', @@ -3531,7 +3591,7 @@ export const get61 = oc .output(zGetAppsByAppIdWorkflowsDraftNodesByNodeIdLastRunResponse) export const lastRun = { - get: get61, + get: get63, } /** @@ -3606,7 +3666,7 @@ export const delete11 = oc /** * Get variables for a specific node */ -export const get62 = oc +export const get64 = oc .route({ description: 'Get variables for a specific node', inputStructure: 'detailed', @@ -3620,7 +3680,7 @@ export const get62 = oc export const variables = { delete: delete11, - get: get62, + get: get64, } export const byNodeId8 = { @@ -3665,7 +3725,7 @@ export const run10 = { /** * Server-Sent Events stream of inspector deltas for a draft workflow run. */ -export const get63 = oc +export const get65 = oc .route({ description: 'Server-Sent Events stream of inspector deltas for a draft workflow run.', inputStructure: 'detailed', @@ -3678,13 +3738,13 @@ export const get63 = oc .output(zGetAppsByAppIdWorkflowsDraftRunsByRunIdNodeOutputsEventsResponse) export const events = { - get: get63, + get: get65, } /** * Full value for one declared output, including signed download URL for files. */ -export const get64 = oc +export const get66 = oc .route({ description: 'Full value for one declared output, including signed download URL for files.', inputStructure: 'detailed', @@ -3701,7 +3761,7 @@ export const get64 = oc .output(zGetAppsByAppIdWorkflowsDraftRunsByRunIdNodeOutputsByNodeIdByOutputNamePreviewResponse) export const preview4 = { - get: get64, + get: get66, } export const byOutputName = { @@ -3711,7 +3771,7 @@ export const byOutputName = { /** * One node's declared outputs for a draft workflow run. */ -export const get65 = oc +export const get67 = oc .route({ description: 'One node\'s declared outputs for a draft workflow run.', inputStructure: 'detailed', @@ -3724,14 +3784,14 @@ export const get65 = oc .output(zGetAppsByAppIdWorkflowsDraftRunsByRunIdNodeOutputsByNodeIdResponse) export const byNodeId9 = { - get: get65, + get: get67, byOutputName, } /** * Snapshot of every node's declared outputs for a draft workflow run. */ -export const get66 = oc +export const get68 = oc .route({ description: 'Snapshot of every node\'s declared outputs for a draft workflow run.', inputStructure: 'detailed', @@ -3744,7 +3804,7 @@ export const get66 = oc .output(zGetAppsByAppIdWorkflowsDraftRunsByRunIdNodeOutputsResponse) export const nodeOutputs = { - get: get66, + get: get68, events, byNodeId: byNodeId9, } @@ -3760,7 +3820,7 @@ export const runs = { /** * Get system variables for workflow */ -export const get67 = oc +export const get69 = oc .route({ description: 'Get system variables for workflow', inputStructure: 'detailed', @@ -3773,7 +3833,7 @@ export const get67 = oc .output(zGetAppsByAppIdWorkflowsDraftSystemVariablesResponse) export const systemVariables = { - get: get67, + get: get69, } /** @@ -3873,7 +3933,7 @@ export const delete12 = oc /** * Get a specific workflow variable */ -export const get68 = oc +export const get70 = oc .route({ description: 'Get a specific workflow variable', inputStructure: 'detailed', @@ -3907,7 +3967,7 @@ export const patch2 = oc export const byVariableId = { delete: delete12, - get: get68, + get: get70, patch: patch2, reset, } @@ -3933,7 +3993,7 @@ export const delete13 = oc * * Get draft workflow variables */ -export const get69 = oc +export const get71 = oc .route({ description: 'Get draft workflow variables', inputStructure: 'detailed', @@ -3953,7 +4013,7 @@ export const get69 = oc export const variables2 = { delete: delete13, - get: get69, + get: get71, byVariableId, } @@ -3962,7 +4022,7 @@ export const variables2 = { * * Get draft workflow for an application */ -export const get70 = oc +export const get72 = oc .route({ description: 'Get draft workflow for an application', inputStructure: 'detailed', @@ -3999,7 +4059,7 @@ export const post59 = oc .output(zPostAppsByAppIdWorkflowsDraftResponse) export const draft2 = { - get: get70, + get: get72, post: post59, conversationVariables: conversationVariables2, environmentVariables, @@ -4020,7 +4080,7 @@ export const draft2 = { * * Get published workflow for an application */ -export const get71 = oc +export const get73 = oc .route({ description: 'Get published workflow for an application', inputStructure: 'detailed', @@ -4054,14 +4114,14 @@ export const post60 = oc .output(zPostAppsByAppIdWorkflowsPublishResponse) export const publish = { - get: get71, + get: get73, post: post60, } /** * Server-Sent Events stream of inspector deltas for a published workflow run. */ -export const get72 = oc +export const get74 = oc .route({ description: 'Server-Sent Events stream of inspector deltas for a published workflow run.', inputStructure: 'detailed', @@ -4074,13 +4134,13 @@ export const get72 = oc .output(zGetAppsByAppIdWorkflowsPublishedRunsByRunIdNodeOutputsEventsResponse) export const events2 = { - get: get72, + get: get74, } /** * Full value for one declared output of a published run. */ -export const get73 = oc +export const get75 = oc .route({ description: 'Full value for one declared output of a published run.', inputStructure: 'detailed', @@ -4101,7 +4161,7 @@ export const get73 = oc ) export const preview5 = { - get: get73, + get: get75, } export const byOutputName2 = { @@ -4111,7 +4171,7 @@ export const byOutputName2 = { /** * One node's declared outputs for a published workflow run. */ -export const get74 = oc +export const get76 = oc .route({ description: 'One node\'s declared outputs for a published workflow run.', inputStructure: 'detailed', @@ -4124,14 +4184,14 @@ export const get74 = oc .output(zGetAppsByAppIdWorkflowsPublishedRunsByRunIdNodeOutputsByNodeIdResponse) export const byNodeId10 = { - get: get74, + get: get76, byOutputName: byOutputName2, } /** * Snapshot of every node's declared outputs for a published workflow run. */ -export const get75 = oc +export const get77 = oc .route({ description: 'Snapshot of every node\'s declared outputs for a published workflow run.', inputStructure: 'detailed', @@ -4144,7 +4204,7 @@ export const get75 = oc .output(zGetAppsByAppIdWorkflowsPublishedRunsByRunIdNodeOutputsResponse) export const nodeOutputs2 = { - get: get75, + get: get77, events: events2, byNodeId: byNodeId10, } @@ -4164,7 +4224,7 @@ export const published = { /** * Get webhook trigger for a node */ -export const get76 = oc +export const get78 = oc .route({ inputStructure: 'detailed', method: 'GET', @@ -4182,7 +4242,7 @@ export const get76 = oc .output(zGetAppsByAppIdWorkflowsTriggersWebhookResponse) export const webhook = { - get: get76, + get: get78, } export const triggers2 = { @@ -4258,7 +4318,7 @@ export const byWorkflowId = { * * Get all published workflows for an application */ -export const get77 = oc +export const get79 = oc .route({ description: 'Get all published workflows for an application', inputStructure: 'detailed', @@ -4277,7 +4337,7 @@ export const get77 = oc .output(zGetAppsByAppIdWorkflowsResponse) export const workflows3 = { - get: get77, + get: get79, defaultWorkflowBlockConfigs, draft: draft2, publish, @@ -4310,7 +4370,7 @@ export const delete15 = oc * * Get application details */ -export const get78 = oc +export const get80 = oc .route({ description: 'Get application details', inputStructure: 'detailed', @@ -4343,7 +4403,7 @@ export const put6 = oc export const byAppId2 = { delete: delete15, - get: get78, + get: get80, put: put6, advancedChat, agent, @@ -4412,7 +4472,7 @@ export const byApiKeyId = { * * Get all API keys for an app */ -export const get79 = oc +export const get81 = oc .route({ description: 'Get all API keys for an app', inputStructure: 'detailed', @@ -4445,7 +4505,7 @@ export const post62 = oc .output(zPostAppsByResourceIdApiKeysResponse) export const apiKeys = { - get: get79, + get: get81, post: post62, byApiKeyId, } @@ -4457,7 +4517,7 @@ export const byResourceId = { /** * Refresh MCP server configuration and regenerate server code */ -export const get80 = oc +export const get82 = oc .route({ description: 'Refresh MCP server configuration and regenerate server code', inputStructure: 'detailed', @@ -4470,7 +4530,7 @@ export const get80 = oc .output(zGetAppsByServerIdServerRefreshResponse) export const refresh = { - get: get80, + get: get82, } export const server2 = { @@ -4486,7 +4546,7 @@ export const byServerId = { * * Get list of applications with pagination and filtering */ -export const get81 = oc +export const get83 = oc .route({ description: 'Get list of applications with pagination and filtering', inputStructure: 'detailed', @@ -4519,7 +4579,7 @@ export const post63 = oc .output(zPostAppsResponse) export const apps = { - get: get81, + get: get83, post: post63, imports, starred, diff --git a/packages/contracts/generated/api/console/apps/types.gen.ts b/packages/contracts/generated/api/console/apps/types.gen.ts index 904252c77eb..a3ab6a37c56 100644 --- a/packages/contracts/generated/api/console/apps/types.gen.ts +++ b/packages/contracts/generated/api/console/apps/types.gen.ts @@ -193,6 +193,29 @@ export type AgentDrivePreviewResponse = { truncated: boolean } +export type AgentDriveSkillListResponse = { + items?: Array +} + +export type AgentDriveSkillInspectResponse = { + archive_key?: string | null + created_at?: number | null + description: string + file_tree?: Array<{ + [key: string]: unknown + }> + files?: Array + hash?: string | null + mime_type?: string | null + name: string + path: string + size?: number | null + skill_md: AgentDriveSkillMarkdownResponse + skill_md_key: string + source: string + warnings?: Array +} + export type AgentDriveDeleteResponse = { config_version_id?: string | null removed_keys?: Array @@ -1274,9 +1297,39 @@ export type AgentDriveItemResponse = { created_at?: number | null file_kind: string hash?: string | null + is_skill?: boolean | null key: string mime_type?: string | null size?: number | null + skill_metadata?: string | null +} + +export type AgentDriveSkillItemResponse = { + archive_key?: string | null + created_at?: number | null + description: string + hash?: string | null + mime_type?: string | null + name: string + path: string + size?: number | null + skill_md_key: string +} + +export type AgentDriveSkillFileResponse = { + available_in_drive: boolean + drive_key?: string | null + name: string + path: string + type: string +} + +export type AgentDriveSkillMarkdownResponse = { + binary: boolean + key: string + size?: number | null + text?: string | null + truncated: boolean } export type AgentDriveFileResponse = { @@ -3125,6 +3178,44 @@ export type GetAppsByAppIdAgentDriveFilesPreviewResponses = { export type GetAppsByAppIdAgentDriveFilesPreviewResponse = GetAppsByAppIdAgentDriveFilesPreviewResponses[keyof GetAppsByAppIdAgentDriveFilesPreviewResponses] +export type GetAppsByAppIdAgentDriveSkillsData = { + body?: never + path: { + app_id: string + } + query?: { + node_id?: string + prefix?: string + } + url: '/apps/{app_id}/agent/drive/skills' +} + +export type GetAppsByAppIdAgentDriveSkillsResponses = { + 200: AgentDriveSkillListResponse +} + +export type GetAppsByAppIdAgentDriveSkillsResponse + = GetAppsByAppIdAgentDriveSkillsResponses[keyof GetAppsByAppIdAgentDriveSkillsResponses] + +export type GetAppsByAppIdAgentDriveSkillsBySkillPathInspectData = { + body?: never + path: { + app_id: string + skill_path: string + } + query?: { + node_id?: string + } + url: '/apps/{app_id}/agent/drive/skills/{skill_path}/inspect' +} + +export type GetAppsByAppIdAgentDriveSkillsBySkillPathInspectResponses = { + 200: AgentDriveSkillInspectResponse +} + +export type GetAppsByAppIdAgentDriveSkillsBySkillPathInspectResponse + = GetAppsByAppIdAgentDriveSkillsBySkillPathInspectResponses[keyof GetAppsByAppIdAgentDriveSkillsBySkillPathInspectResponses] + export type DeleteAppsByAppIdAgentFilesData = { body?: never path: { diff --git a/packages/contracts/generated/api/console/apps/zod.gen.ts b/packages/contracts/generated/api/console/apps/zod.gen.ts index 4a6f397bcbd..be116b5c795 100644 --- a/packages/contracts/generated/api/console/apps/zod.gen.ts +++ b/packages/contracts/generated/api/console/apps/zod.gen.ts @@ -918,9 +918,11 @@ export const zAgentDriveItemResponse = z.object({ created_at: z.int().nullish(), file_kind: z.string(), hash: z.string().nullish(), + is_skill: z.boolean().nullish(), key: z.string(), mime_type: z.string().nullish(), size: z.int().nullish(), + skill_metadata: z.string().nullish(), }) /** @@ -930,6 +932,70 @@ export const zAgentDriveListResponse = z.object({ items: z.array(zAgentDriveItemResponse).optional(), }) +/** + * AgentDriveSkillItemResponse + */ +export const zAgentDriveSkillItemResponse = z.object({ + archive_key: z.string().nullish(), + created_at: z.int().nullish(), + description: z.string(), + hash: z.string().nullish(), + mime_type: z.string().nullish(), + name: z.string(), + path: z.string(), + size: z.int().nullish(), + skill_md_key: z.string(), +}) + +/** + * AgentDriveSkillListResponse + */ +export const zAgentDriveSkillListResponse = z.object({ + items: z.array(zAgentDriveSkillItemResponse).optional(), +}) + +/** + * AgentDriveSkillFileResponse + */ +export const zAgentDriveSkillFileResponse = z.object({ + available_in_drive: z.boolean(), + drive_key: z.string().nullish(), + name: z.string(), + path: z.string(), + type: z.string(), +}) + +/** + * AgentDriveSkillMarkdownResponse + */ +export const zAgentDriveSkillMarkdownResponse = z.object({ + binary: z.boolean(), + key: z.string(), + size: z.int().nullish(), + text: z.string().nullish(), + truncated: z.boolean(), +}) + +/** + * AgentDriveSkillInspectResponse + */ +export const zAgentDriveSkillInspectResponse = z.object({ + archive_key: z.string().nullish(), + created_at: z.int().nullish(), + description: z.string(), + file_tree: z.array(z.record(z.string(), z.unknown())).optional(), + files: z.array(zAgentDriveSkillFileResponse).optional(), + hash: z.string().nullish(), + mime_type: z.string().nullish(), + name: z.string(), + path: z.string(), + size: z.int().nullish(), + skill_md: zAgentDriveSkillMarkdownResponse, + skill_md_key: z.string(), + source: z.string(), + warnings: z.array(z.string()).optional(), +}) + /** * AgentDriveFileResponse */ @@ -3916,6 +3982,35 @@ export const zGetAppsByAppIdAgentDriveFilesPreviewQuery = z.object({ */ export const zGetAppsByAppIdAgentDriveFilesPreviewResponse = zAgentDrivePreviewResponse +export const zGetAppsByAppIdAgentDriveSkillsPath = z.object({ + app_id: z.uuid(), +}) + +export const zGetAppsByAppIdAgentDriveSkillsQuery = z.object({ + node_id: z.string().optional(), + prefix: z.string().optional().default(''), +}) + +/** + * Drive skills + */ +export const zGetAppsByAppIdAgentDriveSkillsResponse = zAgentDriveSkillListResponse + +export const zGetAppsByAppIdAgentDriveSkillsBySkillPathInspectPath = z.object({ + app_id: z.uuid(), + skill_path: z.string(), +}) + +export const zGetAppsByAppIdAgentDriveSkillsBySkillPathInspectQuery = z.object({ + node_id: z.string().optional(), +}) + +/** + * Drive skill inspect view + */ +export const zGetAppsByAppIdAgentDriveSkillsBySkillPathInspectResponse + = zAgentDriveSkillInspectResponse + export const zDeleteAppsByAppIdAgentFilesPath = z.object({ app_id: z.uuid(), })