aidial-client 0.17.0.dev5__tar.gz → 0.17.0.dev7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/PKG-INFO +227 -1
  2. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/README.md +226 -0
  3. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_client.py +17 -2
  4. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_constants.py +7 -3
  5. aidial_client-0.17.0.dev7/aidial_client/helpers/storage_resource.py +361 -0
  6. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/resources/__init__.py +10 -0
  7. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/resources/bucket.py +3 -3
  8. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/resources/files.py +27 -42
  9. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/resources/metadata.py +7 -7
  10. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/resources/prompts.py +20 -37
  11. aidial_client-0.17.0.dev7/aidial_client/resources/skills.py +495 -0
  12. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/metadata.py +38 -1
  13. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/pyproject.toml +1 -1
  14. aidial_client-0.17.0.dev5/aidial_client/helpers/storage_resource.py +0 -202
  15. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/LICENSE +0 -0
  16. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/__init__.py +0 -0
  17. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_auth.py +0 -0
  18. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_client_pool.py +0 -0
  19. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_compatibility/__init__.py +0 -0
  20. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_compatibility/openai.py +0 -0
  21. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_compatibility/pydantic.py +0 -0
  22. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_compatibility/pydantic_v1.py +0 -0
  23. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_exception.py +0 -0
  24. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_http_client/__init__.py +0 -0
  25. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_http_client/_async.py +0 -0
  26. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_http_client/_base.py +0 -0
  27. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_http_client/_sse.py +0 -0
  28. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_http_client/_sync.py +0 -0
  29. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_internal_types/__init__.py +0 -0
  30. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_internal_types/_defaults.py +0 -0
  31. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_internal_types/_generic.py +0 -0
  32. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_internal_types/_http_request.py +0 -0
  33. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_internal_types/_json_rpc.py +0 -0
  34. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_internal_types/_model.py +0 -0
  35. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_log.py +0 -0
  36. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_utils/__init__.py +0 -0
  37. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_utils/_alias.py +0 -0
  38. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_utils/_dict.py +0 -0
  39. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_utils/_openai.py +0 -0
  40. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_utils/_response_processing.py +0 -0
  41. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/_utils/_type_guard.py +0 -0
  42. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/helpers/__init__.py +0 -0
  43. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/helpers/_url.py +0 -0
  44. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/py.typed +0 -0
  45. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/resources/application.py +0 -0
  46. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/resources/base.py +0 -0
  47. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/resources/chat/__init__.py +0 -0
  48. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/resources/chat/completions.py +0 -0
  49. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/resources/client_channel.py +0 -0
  50. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/resources/deployments.py +0 -0
  51. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/resources/model.py +0 -0
  52. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/resources/resource_permissions.py +0 -0
  53. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/resources/toolset.py +0 -0
  54. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/resources/user.py +0 -0
  55. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/__init__.py +0 -0
  56. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/application.py +0 -0
  57. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/bucket.py +0 -0
  58. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/__init__.py +0 -0
  59. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/cache.py +0 -0
  60. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/function.py +0 -0
  61. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/legacy/__init__.py +0 -0
  62. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/legacy/application_request.py +0 -0
  63. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/legacy/chat_completion.py +0 -0
  64. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/request.py +0 -0
  65. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/request_param.py +0 -0
  66. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/response.py +0 -0
  67. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/chat/tool.py +0 -0
  68. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/client_channel.py +0 -0
  69. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/deployment.py +0 -0
  70. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/file.py +0 -0
  71. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/model.py +0 -0
  72. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/prompt.py +0 -0
  73. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/toolset.py +0 -0
  74. {aidial_client-0.17.0.dev5 → aidial_client-0.17.0.dev7}/aidial_client/types/user.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aidial-client
3
- Version: 0.17.0.dev5
3
+ Version: 0.17.0.dev7
4
4
  Summary: A Python client library for the AI DIAL API
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -59,6 +59,11 @@ Description-Content-Type: text/markdown
59
59
  - [Get Prompt](#get-prompt)
60
60
  - [Get Prompt Metadata](#get-prompt-metadata)
61
61
  - [Delete Prompt](#delete-prompt)
62
+ - [Skills](#skills)
63
+ - [Listing Skills](#listing-skills)
64
+ - [Listing Files in a Skill](#listing-files-in-a-skill)
65
+ - [Reading a File from a Skill](#reading-a-file-from-a-skill)
66
+ - [Downloading a Skill](#downloading-a-skill)
62
67
  - [Applications](#applications)
63
68
  - [List Applications](#list-applications)
64
69
  - [Get Application by Id](#get-application-by-id)
@@ -817,6 +822,227 @@ client.prompts.delete("prompts/my-bucket/my-folder/my-prompt")
817
822
  await async_client.prompts.delete("prompts/my-bucket/my-folder/my-prompt")
818
823
  ```
819
824
 
825
+ ### Skills
826
+
827
+ A DIAL *skill* is a folder-shaped resource served by DIAL Core's `/v2/skills`
828
+ API: a mandatory `SKILL.md` manifest plus an arbitrary hierarchy of bundled
829
+ files, addressed as a unit at `skills/{bucket}/{path}`.
830
+
831
+ > [!NOTE]
832
+ > The `/v2/skills` endpoints are marked as preview in DIAL Core, so their
833
+ > contract may still change. The client currently supports the read
834
+ > operations; writes are tracked separately.
835
+
836
+ Unlike the other resources, `client.skills` is not called with a URL you build
837
+ yourself. It is a *reference* that you narrow step by step, and each step
838
+ returns a new reference:
839
+
840
+ ```python
841
+ skill = client.skills / "writing" / "tone-of-voice"
842
+ # equivalently: client.skills(path="writing/tone-of-voice")
843
+ ```
844
+
845
+ References are immutable, validate every path segment as they are built, and
846
+ issue no request until a terminal call (`list()`, `read()`, `download()`,
847
+ `stream()`, `stream_download()`). Building one is identical for the sync and
848
+ async clients — only the terminal call is awaited.
849
+
850
+ A reference points at your own bucket unless told otherwise. Use `bucket=` for
851
+ a shared bucket such as `public`, and `url=` to follow an entry returned by a
852
+ listing:
853
+
854
+ ```python
855
+ client.skills(bucket="public") / "demo" / "azure-resource-visualizer"
856
+
857
+ appdata = client.my_appdata()
858
+ client.skills(bucket=appdata.user_bucket, path=f"appdata/{appdata.app_name}")
859
+ ```
860
+
861
+ #### Listing Skills
862
+
863
+ `list()` returns the skills and grouping folders at the reference. With no
864
+ narrowing it lists your bucket root:
865
+
866
+ ```python
867
+ # Sync
868
+ listing = client.skills.list()
869
+ # Async
870
+ listing = await async_client.skills.list()
871
+
872
+ for item in listing.items or []:
873
+ # "ITEM" is a skill, "FOLDER" is a grouping folder
874
+ print(item.node_type, item.url)
875
+
876
+ # Follow either one with url=
877
+ nested = client.skills(url=item.url)
878
+ ```
879
+
880
+ Narrow first to list a grouping folder, and pass the listing options to the
881
+ terminal call:
882
+
883
+ ```python
884
+ page = (client.skills / "writing").list(recursive=True, limit=1000)
885
+ ```
886
+
887
+ Example of the response:
888
+
889
+ ```python
890
+ SkillMetadata(
891
+ name="writing",
892
+ parent_path=None,
893
+ bucket="my-bucket",
894
+ url="skills/my-bucket/writing/",
895
+ node_type="FOLDER",
896
+ resource_type="SKILL",
897
+ next_token=None,
898
+ items=[
899
+ SkillItem(
900
+ name="tone-of-voice",
901
+ parent_path="writing",
902
+ bucket="my-bucket",
903
+ url="skills/my-bucket/writing/tone-of-voice",
904
+ node_type="ITEM",
905
+ resource_type="SKILL",
906
+ created_at=1724836229736,
907
+ updated_at=1724836248936,
908
+ author="user@example.com",
909
+ etag=None,
910
+ )
911
+ ],
912
+ )
913
+ ```
914
+
915
+ > [!NOTE]
916
+ > DIAL Core builds this listing without reading each skill's marker, so
917
+ > `etag` is always `None` here and the skill's `name`/`description` from
918
+ > `SKILL.md` are not included. Read `SKILL.md` itself if you need them.
919
+
920
+ #### Listing Files in a Skill
921
+
922
+ `skill.files` is a reference to the skill's bundled files. A page may hold
923
+ fewer entries than `limit`, so follow `next_token` until it is `None` —
924
+ building the reference once and varying only the token:
925
+
926
+ ```python
927
+ skill = client.skills / "writing" / "tone-of-voice"
928
+ files = skill.files
929
+
930
+ token = None
931
+ while True:
932
+ page = files.list(recursive=True, limit=1000, token=token)
933
+ for item in page.items or []:
934
+ print(item.node_type, item.url)
935
+ token = page.next_token
936
+ if token is None:
937
+ break
938
+ ```
939
+
940
+ Narrow to a subfolder the same way as anywhere else:
941
+
942
+ ```python
943
+ page = await (async_skill.files / "references").list()
944
+ ```
945
+
946
+ Example of the response:
947
+
948
+ ```python
949
+ SkillFileMetadata(
950
+ name="files",
951
+ parent_path="writing/tone-of-voice",
952
+ bucket="my-bucket",
953
+ url="skills/my-bucket/writing/tone-of-voice/files/",
954
+ node_type="FOLDER",
955
+ resource_type="SKILL",
956
+ next_token=None,
957
+ items=[
958
+ SkillFileItem(
959
+ name="SKILL.md",
960
+ parent_path="writing/tone-of-voice/files",
961
+ bucket="my-bucket",
962
+ url="skills/my-bucket/writing/tone-of-voice/files/SKILL.md",
963
+ node_type="ITEM",
964
+ resource_type="SKILL",
965
+ updated_at=1724836248936,
966
+ ),
967
+ SkillFileItem(
968
+ # A subfolder, as returned by a non-recursive listing.
969
+ name="references",
970
+ parent_path="writing/tone-of-voice/files",
971
+ bucket="my-bucket",
972
+ url="skills/my-bucket/writing/tone-of-voice/files/references/",
973
+ node_type="FOLDER",
974
+ resource_type="SKILL",
975
+ ),
976
+ ],
977
+ )
978
+ ```
979
+
980
+ > [!NOTE]
981
+ > The two modes answer different questions. `recursive=True` flattens the
982
+ > tree: every file at every depth, no folder entries at all, with
983
+ > `parent_path` showing where each file sits. A non-recursive listing returns
984
+ > the immediate children, folders included, distinguished by
985
+ > `node_type == "FOLDER"`. Empty folders never appear in either mode.
986
+
987
+ Unlike the `/v1` files listing, these entries are sparse: no
988
+ `content_length`, no `content_type`, and in observed responses no `etag`
989
+ either. Folder entries carry no `updated_at`. Treat every field except
990
+ `name`, `url`, `node_type` and `resource_type` as optional here.
991
+
992
+ #### Reading a File from a Skill
993
+
994
+ Name the file with `path=`, relative to the skill root, then `read()`:
995
+
996
+ ```python
997
+ # Sync
998
+ manifest = skill.files(path="SKILL.md").read()
999
+ print(manifest.get_content().decode())
1000
+
1001
+ # Async
1002
+ manifest = await async_skill.files(path="SKILL.md").read()
1003
+ schema = await async_skill.files(path="references/api-schema.md").read()
1004
+ await schema.awrite_to("api-schema.md")
1005
+ ```
1006
+
1007
+ An entry from a files listing can be followed directly, without slicing its
1008
+ url apart:
1009
+
1010
+ ```python
1011
+ for item in files.list().items or []:
1012
+ if item.node_type == "ITEM":
1013
+ content = skill.files(url=item.url).read()
1014
+ ```
1015
+
1016
+ The async client can stream instead, which avoids holding the file in memory:
1017
+
1018
+ ```python
1019
+ async with async_skill.files(path="assets/logo.png").stream() as file:
1020
+ await file.awrite_to("logo.png")
1021
+ ```
1022
+
1023
+ #### Downloading a Skill
1024
+
1025
+ `download()` fetches the whole skill as a ZIP archive:
1026
+
1027
+ ```python
1028
+ # Sync
1029
+ archive = skill.download()
1030
+ archive.write_to("tone-of-voice.zip")
1031
+
1032
+ # Async, streamed
1033
+ async with async_skill.stream_download() as archive:
1034
+ await archive.awrite_to("tone-of-voice.zip")
1035
+ ```
1036
+
1037
+ DIAL Core sends no `Content-Disposition` for this endpoint, so `filename`
1038
+ defaults to the skill name with a `.zip` suffix (`tone-of-voice.zip` above).
1039
+ The response's `ETag` header carries the skill's aggregate etag:
1040
+
1041
+ ```python
1042
+ etag = archive.headers["etag"]
1043
+ ```
1044
+
1045
+
820
1046
  ### Applications
821
1047
 
822
1048
  #### List Applications
@@ -37,6 +37,11 @@
37
37
  - [Get Prompt](#get-prompt)
38
38
  - [Get Prompt Metadata](#get-prompt-metadata)
39
39
  - [Delete Prompt](#delete-prompt)
40
+ - [Skills](#skills)
41
+ - [Listing Skills](#listing-skills)
42
+ - [Listing Files in a Skill](#listing-files-in-a-skill)
43
+ - [Reading a File from a Skill](#reading-a-file-from-a-skill)
44
+ - [Downloading a Skill](#downloading-a-skill)
40
45
  - [Applications](#applications)
41
46
  - [List Applications](#list-applications)
42
47
  - [Get Application by Id](#get-application-by-id)
@@ -795,6 +800,227 @@ client.prompts.delete("prompts/my-bucket/my-folder/my-prompt")
795
800
  await async_client.prompts.delete("prompts/my-bucket/my-folder/my-prompt")
796
801
  ```
797
802
 
803
+ ### Skills
804
+
805
+ A DIAL *skill* is a folder-shaped resource served by DIAL Core's `/v2/skills`
806
+ API: a mandatory `SKILL.md` manifest plus an arbitrary hierarchy of bundled
807
+ files, addressed as a unit at `skills/{bucket}/{path}`.
808
+
809
+ > [!NOTE]
810
+ > The `/v2/skills` endpoints are marked as preview in DIAL Core, so their
811
+ > contract may still change. The client currently supports the read
812
+ > operations; writes are tracked separately.
813
+
814
+ Unlike the other resources, `client.skills` is not called with a URL you build
815
+ yourself. It is a *reference* that you narrow step by step, and each step
816
+ returns a new reference:
817
+
818
+ ```python
819
+ skill = client.skills / "writing" / "tone-of-voice"
820
+ # equivalently: client.skills(path="writing/tone-of-voice")
821
+ ```
822
+
823
+ References are immutable, validate every path segment as they are built, and
824
+ issue no request until a terminal call (`list()`, `read()`, `download()`,
825
+ `stream()`, `stream_download()`). Building one is identical for the sync and
826
+ async clients — only the terminal call is awaited.
827
+
828
+ A reference points at your own bucket unless told otherwise. Use `bucket=` for
829
+ a shared bucket such as `public`, and `url=` to follow an entry returned by a
830
+ listing:
831
+
832
+ ```python
833
+ client.skills(bucket="public") / "demo" / "azure-resource-visualizer"
834
+
835
+ appdata = client.my_appdata()
836
+ client.skills(bucket=appdata.user_bucket, path=f"appdata/{appdata.app_name}")
837
+ ```
838
+
839
+ #### Listing Skills
840
+
841
+ `list()` returns the skills and grouping folders at the reference. With no
842
+ narrowing it lists your bucket root:
843
+
844
+ ```python
845
+ # Sync
846
+ listing = client.skills.list()
847
+ # Async
848
+ listing = await async_client.skills.list()
849
+
850
+ for item in listing.items or []:
851
+ # "ITEM" is a skill, "FOLDER" is a grouping folder
852
+ print(item.node_type, item.url)
853
+
854
+ # Follow either one with url=
855
+ nested = client.skills(url=item.url)
856
+ ```
857
+
858
+ Narrow first to list a grouping folder, and pass the listing options to the
859
+ terminal call:
860
+
861
+ ```python
862
+ page = (client.skills / "writing").list(recursive=True, limit=1000)
863
+ ```
864
+
865
+ Example of the response:
866
+
867
+ ```python
868
+ SkillMetadata(
869
+ name="writing",
870
+ parent_path=None,
871
+ bucket="my-bucket",
872
+ url="skills/my-bucket/writing/",
873
+ node_type="FOLDER",
874
+ resource_type="SKILL",
875
+ next_token=None,
876
+ items=[
877
+ SkillItem(
878
+ name="tone-of-voice",
879
+ parent_path="writing",
880
+ bucket="my-bucket",
881
+ url="skills/my-bucket/writing/tone-of-voice",
882
+ node_type="ITEM",
883
+ resource_type="SKILL",
884
+ created_at=1724836229736,
885
+ updated_at=1724836248936,
886
+ author="user@example.com",
887
+ etag=None,
888
+ )
889
+ ],
890
+ )
891
+ ```
892
+
893
+ > [!NOTE]
894
+ > DIAL Core builds this listing without reading each skill's marker, so
895
+ > `etag` is always `None` here and the skill's `name`/`description` from
896
+ > `SKILL.md` are not included. Read `SKILL.md` itself if you need them.
897
+
898
+ #### Listing Files in a Skill
899
+
900
+ `skill.files` is a reference to the skill's bundled files. A page may hold
901
+ fewer entries than `limit`, so follow `next_token` until it is `None` —
902
+ building the reference once and varying only the token:
903
+
904
+ ```python
905
+ skill = client.skills / "writing" / "tone-of-voice"
906
+ files = skill.files
907
+
908
+ token = None
909
+ while True:
910
+ page = files.list(recursive=True, limit=1000, token=token)
911
+ for item in page.items or []:
912
+ print(item.node_type, item.url)
913
+ token = page.next_token
914
+ if token is None:
915
+ break
916
+ ```
917
+
918
+ Narrow to a subfolder the same way as anywhere else:
919
+
920
+ ```python
921
+ page = await (async_skill.files / "references").list()
922
+ ```
923
+
924
+ Example of the response:
925
+
926
+ ```python
927
+ SkillFileMetadata(
928
+ name="files",
929
+ parent_path="writing/tone-of-voice",
930
+ bucket="my-bucket",
931
+ url="skills/my-bucket/writing/tone-of-voice/files/",
932
+ node_type="FOLDER",
933
+ resource_type="SKILL",
934
+ next_token=None,
935
+ items=[
936
+ SkillFileItem(
937
+ name="SKILL.md",
938
+ parent_path="writing/tone-of-voice/files",
939
+ bucket="my-bucket",
940
+ url="skills/my-bucket/writing/tone-of-voice/files/SKILL.md",
941
+ node_type="ITEM",
942
+ resource_type="SKILL",
943
+ updated_at=1724836248936,
944
+ ),
945
+ SkillFileItem(
946
+ # A subfolder, as returned by a non-recursive listing.
947
+ name="references",
948
+ parent_path="writing/tone-of-voice/files",
949
+ bucket="my-bucket",
950
+ url="skills/my-bucket/writing/tone-of-voice/files/references/",
951
+ node_type="FOLDER",
952
+ resource_type="SKILL",
953
+ ),
954
+ ],
955
+ )
956
+ ```
957
+
958
+ > [!NOTE]
959
+ > The two modes answer different questions. `recursive=True` flattens the
960
+ > tree: every file at every depth, no folder entries at all, with
961
+ > `parent_path` showing where each file sits. A non-recursive listing returns
962
+ > the immediate children, folders included, distinguished by
963
+ > `node_type == "FOLDER"`. Empty folders never appear in either mode.
964
+
965
+ Unlike the `/v1` files listing, these entries are sparse: no
966
+ `content_length`, no `content_type`, and in observed responses no `etag`
967
+ either. Folder entries carry no `updated_at`. Treat every field except
968
+ `name`, `url`, `node_type` and `resource_type` as optional here.
969
+
970
+ #### Reading a File from a Skill
971
+
972
+ Name the file with `path=`, relative to the skill root, then `read()`:
973
+
974
+ ```python
975
+ # Sync
976
+ manifest = skill.files(path="SKILL.md").read()
977
+ print(manifest.get_content().decode())
978
+
979
+ # Async
980
+ manifest = await async_skill.files(path="SKILL.md").read()
981
+ schema = await async_skill.files(path="references/api-schema.md").read()
982
+ await schema.awrite_to("api-schema.md")
983
+ ```
984
+
985
+ An entry from a files listing can be followed directly, without slicing its
986
+ url apart:
987
+
988
+ ```python
989
+ for item in files.list().items or []:
990
+ if item.node_type == "ITEM":
991
+ content = skill.files(url=item.url).read()
992
+ ```
993
+
994
+ The async client can stream instead, which avoids holding the file in memory:
995
+
996
+ ```python
997
+ async with async_skill.files(path="assets/logo.png").stream() as file:
998
+ await file.awrite_to("logo.png")
999
+ ```
1000
+
1001
+ #### Downloading a Skill
1002
+
1003
+ `download()` fetches the whole skill as a ZIP archive:
1004
+
1005
+ ```python
1006
+ # Sync
1007
+ archive = skill.download()
1008
+ archive.write_to("tone-of-voice.zip")
1009
+
1010
+ # Async, streamed
1011
+ async with async_skill.stream_download() as archive:
1012
+ await archive.awrite_to("tone-of-voice.zip")
1013
+ ```
1014
+
1015
+ DIAL Core sends no `Content-Disposition` for this endpoint, so `filename`
1016
+ defaults to the skill name with a `.zip` suffix (`tone-of-voice.zip` above).
1017
+ The response's `ETag` header carries the skill's aggregate etag:
1018
+
1019
+ ```python
1020
+ etag = archive.headers["etag"]
1021
+ ```
1022
+
1023
+
798
1024
  ### Applications
799
1025
 
800
1026
  #### List Applications
@@ -15,7 +15,8 @@ from aidial_client._auth import (
15
15
  validate_auth,
16
16
  )
17
17
  from aidial_client._constants import (
18
- API_PREFIX,
18
+ API_PREFIX_V1,
19
+ API_PREFIX_V2,
19
20
  DEFAULT_MAX_RETRIES,
20
21
  DEFAULT_TIMEOUT,
21
22
  OPENAI_PREFIX,
@@ -71,7 +72,11 @@ class BaseDialClient(Generic[_HttpClientT, AuthValueT], ABC):
71
72
 
72
73
  @property
73
74
  def api_url(self) -> str:
74
- return urljoin(self._base_url, API_PREFIX)
75
+ return urljoin(self._base_url, API_PREFIX_V1)
76
+
77
+ @property
78
+ def api_url_v2(self) -> str:
79
+ return urljoin(self._base_url, API_PREFIX_V2)
75
80
 
76
81
  @property
77
82
  def base_url(self) -> str:
@@ -110,6 +115,11 @@ class Dial(BaseDialClient[SyncHTTPClient, SyncAuthValue]):
110
115
  metadata=self.metadata,
111
116
  dial_api_url=self.api_url,
112
117
  )
118
+ self.skills = resources.SkillsRef(
119
+ http_client=self._http_client,
120
+ dial_api_url=self.api_url_v2,
121
+ resolve_bucket=self.my_bucket,
122
+ )
113
123
  self.deployments = resources.Deployments(http_client=self._http_client)
114
124
  self.application = resources.Application(http_client=self._http_client)
115
125
  self.toolset = resources.Toolset(http_client=self._http_client)
@@ -211,6 +221,11 @@ class AsyncDial(BaseDialClient[AsyncHTTPClient, AsyncAuthValue]):
211
221
  metadata=self.metadata,
212
222
  dial_api_url=self.api_url,
213
223
  )
224
+ self.skills = resources.AsyncSkillsRef(
225
+ http_client=self._http_client,
226
+ dial_api_url=self.api_url_v2,
227
+ resolve_bucket=self.my_bucket,
228
+ )
214
229
  self.deployments = resources.AsyncDeployments(
215
230
  http_client=self._http_client
216
231
  )
@@ -9,9 +9,13 @@ DEFAULT_CONNECTION_LIMITS = httpx.Limits(
9
9
  )
10
10
  INITIAL_RETRY_DELAY = 0.5
11
11
  MAX_RETRY_DELAY = 8.0
12
- API_PREFIX = "v1/"
13
- METADATA_PREFIX = urljoin(API_PREFIX, "metadata/")
14
- FILES_PREFIX = urljoin(API_PREFIX, "files/")
12
+ API_PREFIX_V1 = "v1/"
13
+ METADATA_PREFIX_V1 = urljoin(API_PREFIX_V1, "metadata/")
14
+ FILES_PREFIX = urljoin(API_PREFIX_V1, "files/")
15
+
16
+ # DIAL Core exposes folder-shaped resources (agent skills) under /v2.
17
+ API_PREFIX_V2 = "v2/"
18
+ METADATA_PREFIX_V2 = urljoin(API_PREFIX_V2, "metadata/")
15
19
 
16
20
 
17
21
  OPENAI_PREFIX = "openai/"