From 7839bea1f045da09928ce701677f8f7dfa5d3733 Mon Sep 17 00:00:00 2001 From: "ark-hand[bot]" Date: Mon, 14 Sep 2026 13:53:46 +0000 Subject: [PATCH] feat(skill): support create skill version Hand-written change from the internal SDK repository. The original commit message is intentionally not mirrored; see the source repository for details. Sync-Source-Commit: 7e147e6ac4559876458d3321308a0e72d06d9414 Hand-Written-Reason: No Ark-APIs provenance marker; treated as a hand-written source commit. Release-Version: 0.8.0 --- src/arkruntime/resources/skills/skills.py | 59 ++++++++++++++ src/arkruntime/types/skill/__init__.py | 4 + .../skill/create_skill_version_request.py | 32 ++++++++ src/arkruntime/types/skill/skill_version.py | 49 ++++++++++++ tests/test_skills.py | 79 +++++++++++++++++++ 5 files changed, 223 insertions(+) create mode 100644 src/arkruntime/types/skill/create_skill_version_request.py create mode 100644 src/arkruntime/types/skill/skill_version.py create mode 100644 tests/test_skills.py diff --git a/src/arkruntime/resources/skills/skills.py b/src/arkruntime/resources/skills/skills.py index ec6cde5..a08055b 100644 --- a/src/arkruntime/resources/skills/skills.py +++ b/src/arkruntime/resources/skills/skills.py @@ -6,6 +6,7 @@ from ..._resource import AsyncAPIResource, SyncAPIResource from ..._types import NOT_GIVEN, NotGiven from ...types.skill.skill import Skill +from ...types.skill.skill_version import SkillVersion __all__ = ["Skills", "AsyncSkills"] @@ -70,6 +71,35 @@ def retrieve(self, skill_id: str, *, extra_headers=None, extra_query=None, extra cast_to=Skill, ) + def create_version( + self, + skill_id: str, + *, + files: FileArg, + display_title: Optional[str] | NotGiven = NOT_GIVEN, + extra_headers=None, + extra_query=None, + extra_body=None, + timeout=None, + ) -> SkillVersion: + if not skill_id: + raise ValueError("skill_id is required") + body: dict = {} + if display_title is not NOT_GIVEN and display_title is not None: + body["display_title"] = display_title + return self._post( + f"{_PREFIX}/{skill_id}/versions", + body=body, + files=_files_kwarg(files), + options=make_request_options( + extra_headers=_multipart_headers(extra_headers), + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + cast_to=SkillVersion, + ) + class AsyncSkills(AsyncAPIResource): async def create( @@ -110,3 +140,32 @@ async def retrieve( ), cast_to=Skill, ) + + async def create_version( + self, + skill_id: str, + *, + files: FileArg, + display_title: Optional[str] | NotGiven = NOT_GIVEN, + extra_headers=None, + extra_query=None, + extra_body=None, + timeout=None, + ) -> SkillVersion: + if not skill_id: + raise ValueError("skill_id is required") + body: dict = {} + if display_title is not NOT_GIVEN and display_title is not None: + body["display_title"] = display_title + return await self._post( + f"{_PREFIX}/{skill_id}/versions", + body=body, + files=_files_kwarg(files), + options=make_request_options( + extra_headers=_multipart_headers(extra_headers), + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + cast_to=SkillVersion, + ) diff --git a/src/arkruntime/types/skill/__init__.py b/src/arkruntime/types/skill/__init__.py index 4312b39..d4b25f8 100644 --- a/src/arkruntime/types/skill/__init__.py +++ b/src/arkruntime/types/skill/__init__.py @@ -7,11 +7,15 @@ from __future__ import annotations from .create_skill_request import CreateSkillRequest +from .create_skill_version_request import CreateSkillVersionRequest from .skill import Skill +from .skill_version import SkillVersion __all__ = [ "CreateSkillRequest", + "CreateSkillVersionRequest", "Skill", + "SkillVersion", ] # Hand-written extras (preserved across regen via Makefile rsync --exclude=*_shim.py). diff --git a/src/arkruntime/types/skill/create_skill_version_request.py b/src/arkruntime/types/skill/create_skill_version_request.py new file mode 100644 index 0000000..655c143 --- /dev/null +++ b/src/arkruntime/types/skill/create_skill_version_request.py @@ -0,0 +1,32 @@ +# Generated by datamodel-code-generator from ark-apis typespec/openapi. +# DO NOT EDIT — regenerate with `make gen-py-` in ark-apis. +# +# Copyright (c) 2026 ByteDance Ltd. and/or its affiliates. +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import List, Optional + +from typing_extensions import Annotated + +from arkruntime._models import BaseModel +from pydantic import Field + + +class CreateSkillVersionRequest(BaseModel): + display_title: Optional[str] = None + """ + Skill 版本展示名。 + """ + files: Optional[List[object]] = None + """ + 新版本技能包内容。可以是单个 `.zip`,也可以是一组带相对路径的松散文件; + 该字段名允许在同一次请求中重复出现以传多个文件。 + + 与 `files[]` 语义等价,两者至少提供其一;同时提供时服务端会合并处理。 + """ + files__: Annotated[Optional[List[object]], Field(alias="files[]")] = None + """ + `files` 的可重复字段名别名,语义完全等价。 + """ diff --git a/src/arkruntime/types/skill/skill_version.py b/src/arkruntime/types/skill/skill_version.py new file mode 100644 index 0000000..99a3505 --- /dev/null +++ b/src/arkruntime/types/skill/skill_version.py @@ -0,0 +1,49 @@ +# Generated by datamodel-code-generator from ark-apis typespec/openapi. +# DO NOT EDIT — regenerate with `make gen-py-` in ark-apis. +# +# Copyright (c) 2026 ByteDance Ltd. and/or its affiliates. +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import Literal, Optional + +from arkruntime._models import BaseModel + + +class SkillVersion(BaseModel): + """ + Skill 版本摘要。 + + CreateSkillVersion / ListSkillVersions / GetSkillVersion 返回该对象;主 + Skill 的 latest_version 会由服务端在新版本创建成功后推进。 + """ + + id: str + """ + SkillVersion ID,形如 `skillver-...`。 + """ + type: Literal["skill_version"] + """ + 固定 `"skill_version"`。 + """ + skill_id: str + """ + 归属 Skill ID。 + """ + version: str + """ + 版本号,服务端按字符串形式自增,例如 `"1"`、`"2"`。 + """ + display_title: str + """ + 该版本的展示名。 + """ + description: Optional[str] = None + """ + 该版本的描述信息。 + """ + created_at: str + """ + 创建时间,RFC 3339 UTC 秒级精度。 + """ diff --git a/tests/test_skills.py b/tests/test_skills.py new file mode 100644 index 0000000..ae45d9d --- /dev/null +++ b/tests/test_skills.py @@ -0,0 +1,79 @@ +# Copyright (c) 2026 ByteDance Ltd. and/or its affiliates. +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import pytest + +from arkruntime.resources.skills.skills import AsyncSkills, Skills + + +class _SyncClient: + def __init__(self) -> None: + self.request: dict[str, object] | None = None + + def post(self, *args: object, **kwargs: object) -> dict[str, object]: + self.request = {"args": args, **kwargs} + return {} + + def post_without_retry(self, *args: object, **kwargs: object) -> dict[str, object]: + return self.post(*args, **kwargs) + + def get(self, *args: object, **kwargs: object) -> dict[str, object]: + return {} + + def delete(self, *args: object, **kwargs: object) -> dict[str, object]: + return {} + + def get_api_list(self, *args: object, **kwargs: object) -> list[object]: + return [] + + +class _AsyncClient: + def __init__(self) -> None: + self.request: dict[str, object] | None = None + + async def post(self, *args: object, **kwargs: object) -> dict[str, object]: + self.request = {"args": args, **kwargs} + return {} + + async def post_without_retry(self, *args: object, **kwargs: object) -> dict[str, object]: + return await self.post(*args, **kwargs) + + async def get(self, *args: object, **kwargs: object) -> dict[str, object]: + return {} + + async def delete(self, *args: object, **kwargs: object) -> dict[str, object]: + return {} + + async def get_api_list(self, *args: object, **kwargs: object) -> list[object]: + return [] + + +def test_create_version_builds_multipart_request() -> None: + client = _SyncClient() + Skills(client).create_version( + "skill-1", + files=("skill.zip", b"zip-bytes"), + display_title="Readiness Skill v2", + ) + + assert client.request is not None + assert client.request["args"] == ("/skills/skill-1/versions",) + assert client.request["body"] == {"display_title": "Readiness Skill v2"} + assert client.request["files"] == {"files": ("skill.zip", b"zip-bytes")} + + +@pytest.mark.asyncio +async def test_async_create_version_builds_multipart_request() -> None: + client = _AsyncClient() + await AsyncSkills(client).create_version( + "skill-1", + files=("skill.zip", b"zip-bytes"), + display_title="Readiness Skill v2", + ) + + assert client.request is not None + assert client.request["args"] == ("/skills/skill-1/versions",) + assert client.request["body"] == {"display_title": "Readiness Skill v2"} + assert client.request["files"] == {"files": ("skill.zip", b"zip-bytes")}