tansr-sdk 0.1.0__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 (72) hide show
  1. tansr_sdk-0.1.0/LICENSE +21 -0
  2. tansr_sdk-0.1.0/MANIFEST.in +4 -0
  3. tansr_sdk-0.1.0/PKG-INFO +114 -0
  4. tansr_sdk-0.1.0/README.md +60 -0
  5. tansr_sdk-0.1.0/pyproject.toml +72 -0
  6. tansr_sdk-0.1.0/setup.cfg +4 -0
  7. tansr_sdk-0.1.0/src/tansr_sdk/__init__.py +8 -0
  8. tansr_sdk-0.1.0/src/tansr_sdk/_resolver.py +205 -0
  9. tansr_sdk-0.1.0/src/tansr_sdk/api/__init__.py +4 -0
  10. tansr_sdk-0.1.0/src/tansr_sdk/api/_schema_data.py +18 -0
  11. tansr_sdk-0.1.0/src/tansr_sdk/api/client.py +846 -0
  12. tansr_sdk-0.1.0/src/tansr_sdk/api/operations.py +27 -0
  13. tansr_sdk-0.1.0/src/tansr_sdk/api/schema.py +319 -0
  14. tansr_sdk-0.1.0/src/tansr_sdk/api/types.py +40 -0
  15. tansr_sdk-0.1.0/src/tansr_sdk/archive/__init__.py +25 -0
  16. tansr_sdk-0.1.0/src/tansr_sdk/archive/_validation.py +213 -0
  17. tansr_sdk-0.1.0/src/tansr_sdk/archive/async_client.py +272 -0
  18. tansr_sdk-0.1.0/src/tansr_sdk/archive/client.py +340 -0
  19. tansr_sdk-0.1.0/src/tansr_sdk/archive/intent.py +53 -0
  20. tansr_sdk-0.1.0/src/tansr_sdk/archive/material.py +228 -0
  21. tansr_sdk-0.1.0/src/tansr_sdk/archive/store.py +533 -0
  22. tansr_sdk-0.1.0/src/tansr_sdk/archive/sync.py +180 -0
  23. tansr_sdk-0.1.0/src/tansr_sdk/archive/types.py +96 -0
  24. tansr_sdk-0.1.0/src/tansr_sdk/canonical.py +80 -0
  25. tansr_sdk-0.1.0/src/tansr_sdk/errors.py +47 -0
  26. tansr_sdk-0.1.0/src/tansr_sdk/executor/__init__.py +14 -0
  27. tansr_sdk-0.1.0/src/tansr_sdk/executor/_common.py +105 -0
  28. tansr_sdk-0.1.0/src/tansr_sdk/executor/async_client.py +181 -0
  29. tansr_sdk-0.1.0/src/tansr_sdk/executor/client.py +244 -0
  30. tansr_sdk-0.1.0/src/tansr_sdk/executor/journal.py +105 -0
  31. tansr_sdk-0.1.0/src/tansr_sdk/executor/output.py +382 -0
  32. tansr_sdk-0.1.0/src/tansr_sdk/executor/runner.py +489 -0
  33. tansr_sdk-0.1.0/src/tansr_sdk/executor/tool.py +177 -0
  34. tansr_sdk-0.1.0/src/tansr_sdk/lifecycle.py +233 -0
  35. tansr_sdk-0.1.0/src/tansr_sdk/py.typed +0 -0
  36. tansr_sdk-0.1.0/src/tansr_sdk/session/__init__.py +64 -0
  37. tansr_sdk-0.1.0/src/tansr_sdk/session/_validation.py +126 -0
  38. tansr_sdk-0.1.0/src/tansr_sdk/session/async_client.py +323 -0
  39. tansr_sdk-0.1.0/src/tansr_sdk/session/client.py +633 -0
  40. tansr_sdk-0.1.0/src/tansr_sdk/session/events.py +284 -0
  41. tansr_sdk-0.1.0/src/tansr_sdk/session/types.py +166 -0
  42. tansr_sdk-0.1.0/src/tansr_sdk/sse.py +100 -0
  43. tansr_sdk-0.1.0/src/tansr_sdk/storage/__init__.py +6 -0
  44. tansr_sdk-0.1.0/src/tansr_sdk/storage/_posix.py +156 -0
  45. tansr_sdk-0.1.0/src/tansr_sdk/storage/_windows.py +342 -0
  46. tansr_sdk-0.1.0/src/tansr_sdk/storage/encrypted_store.py +237 -0
  47. tansr_sdk-0.1.0/src/tansr_sdk/storage/private_directory.py +455 -0
  48. tansr_sdk-0.1.0/src/tansr_sdk/strict_json.py +298 -0
  49. tansr_sdk-0.1.0/src/tansr_sdk/transport.py +661 -0
  50. tansr_sdk-0.1.0/src/tansr_sdk.egg-info/PKG-INFO +114 -0
  51. tansr_sdk-0.1.0/src/tansr_sdk.egg-info/SOURCES.txt +70 -0
  52. tansr_sdk-0.1.0/src/tansr_sdk.egg-info/dependency_links.txt +1 -0
  53. tansr_sdk-0.1.0/src/tansr_sdk.egg-info/requires.txt +11 -0
  54. tansr_sdk-0.1.0/src/tansr_sdk.egg-info/top_level.txt +1 -0
  55. tansr_sdk-0.1.0/tests/test_api.py +436 -0
  56. tansr_sdk-0.1.0/tests/test_api_adversarial.py +394 -0
  57. tansr_sdk-0.1.0/tests/test_archive.py +787 -0
  58. tansr_sdk-0.1.0/tests/test_contract_public.py +168 -0
  59. tansr_sdk-0.1.0/tests/test_contract_runtime.py +80 -0
  60. tansr_sdk-0.1.0/tests/test_contract_schema.py +157 -0
  61. tansr_sdk-0.1.0/tests/test_demo.py +381 -0
  62. tansr_sdk-0.1.0/tests/test_executor.py +582 -0
  63. tansr_sdk-0.1.0/tests/test_executor_adversarial.py +379 -0
  64. tansr_sdk-0.1.0/tests/test_executor_node_vectors.py +24 -0
  65. tansr_sdk-0.1.0/tests/test_json.py +138 -0
  66. tansr_sdk-0.1.0/tests/test_public_api.py +25 -0
  67. tansr_sdk-0.1.0/tests/test_session.py +581 -0
  68. tansr_sdk-0.1.0/tests/test_source_export.py +188 -0
  69. tansr_sdk-0.1.0/tests/test_sse.py +60 -0
  70. tansr_sdk-0.1.0/tests/test_storage.py +305 -0
  71. tansr_sdk-0.1.0/tests/test_storage_security.py +215 -0
  72. tansr_sdk-0.1.0/tests/test_transport.py +685 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tansr (tansr.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include README.md
2
+ include LICENSE
3
+ include pyproject.toml
4
+ recursive-include src *.py *.typed
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.4
2
+ Name: tansr-sdk
3
+ Version: 0.1.0
4
+ Summary: Native Python client SDK for the Tansr Serve unified API
5
+ Author: Tansr
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Tansr (tansr.com)
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://www.tansr.com/
29
+ Project-URL: Repository, https://github.com/tansrai/tansr-python
30
+ Project-URL: Documentation, https://github.com/tansrai/tansr-python/blob/main/doc/guide.md
31
+ Project-URL: Issues, https://github.com/tansrai/tansr-python/issues
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3.7
37
+ Classifier: Programming Language :: Python :: 3.8
38
+ Classifier: Programming Language :: Python :: 3.9
39
+ Classifier: Programming Language :: Python :: 3.10
40
+ Classifier: Programming Language :: Python :: 3.11
41
+ Classifier: Programming Language :: Python :: 3.12
42
+ Classifier: Programming Language :: Python :: 3.13
43
+ Classifier: Programming Language :: Python :: 3.14
44
+ Classifier: Typing :: Typed
45
+ Requires-Python: !=3.9.0,!=3.9.1,>=3.7
46
+ Description-Content-Type: text/markdown
47
+ License-File: LICENSE
48
+ Requires-Dist: cryptography==45.0.7; python_version < "3.8"
49
+ Requires-Dist: cffi==1.15.1; python_version < "3.8"
50
+ Requires-Dist: pycparser==2.21; python_version < "3.8"
51
+ Requires-Dist: cryptography==47.0.0; python_version >= "3.8" and python_version < "3.9"
52
+ Requires-Dist: cryptography==50.0.2; python_version >= "3.9"
53
+ Dynamic: license-file
54
+
55
+ # Tansr Python SDK
56
+
57
+ 原生 Python 3.7+ SDK,通过统一 `/api` 连接 Serve。Serve 负责运行、会话、上下文、记忆、权限和工具调度;Python 提供同步与 asyncio 接口、显式业务工具、分块输出和私有本地档案。不捆绑 Node、C++ 或 Rust 运行时。
58
+
59
+ `0.1.0` 的 SDK 与 Demo 均采用 [MIT 许可证](https://github.com/tansrai/tansr-python/blob/main/LICENSE)。源码渠道为 [tansrai/tansr-python](https://github.com/tansrai/tansr-python),包渠道为 PyPI 的 [tansr-sdk](https://pypi.org/project/tansr-sdk/) 与 [tansr-sdk-demo](https://pypi.org/project/tansr-sdk-demo/)。已验证环境与适用限制见发行说明。
60
+
61
+ 使用文档:[中文指南](https://github.com/tansrai/tansr-python/blob/main/doc/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97.md) · [English guide](https://github.com/tansrai/tansr-python/blob/main/doc/guide.md) · [发行说明](https://github.com/tansrai/tansr-python/blob/main/doc/%E5%8F%91%E8%A1%8C%E8%AF%B4%E6%98%8E.md) · [Demo 入口](https://github.com/tansrai/tansr-python/blob/main/demo/README.md)。English readers: version 0.1.0 includes the SDK and separate Demo package, both under the [MIT license](https://github.com/tansrai/tansr-python/blob/main/LICENSE).
62
+
63
+ | 能力 | 公开入口 |
64
+ | --- | --- |
65
+ | 81 项统一 API、严格 JSON 与错误分类 | `Client`、`AsyncClient` |
66
+ | 会话、事件、权限、提问、输入与检查点 | `SessionClient`、`AsyncSessionClient` |
67
+ | 显式工具、续租、耐久业务回执与独立输出确认 | `ExecutorClient`、`Runner`、`AsyncRunner` |
68
+ | 创建意图、加密档案、耐久 ACK 与材料供给 | `ArchiveClient`、`AsyncArchiveClient`、`FileStore` |
69
+ | 独立 Demo 包 | `tansr-py-chat`、`tansr-py-tools`、`tansr-py-archive` |
70
+
71
+ 嵌入自己的应用只安装 `tansr-sdk`;需要试用三个命令行示例时再安装独立的 `tansr-sdk-demo`。SDK 不依赖 Demo,宿主通过 `AuthToken` provider 接入自己的登录和短期凭据,不必沿用 Demo 的文件布局。同步和异步高层共享同一协议实现;已有 asyncio 应用直接 `await`,不接管宿主事件循环。
72
+
73
+ 在虚拟环境中从 PyPI 按需安装同版包:
74
+
75
+ ```text
76
+ python -m pip install tansr-sdk==0.1.0
77
+ python -m pip install tansr-sdk-demo==0.1.0
78
+ ```
79
+
80
+ 离线环境可安装已核验的同版 wheel;以下路径需替换为实际文件:
81
+
82
+ ```text
83
+ python -m pip install /absolute/artifacts/tansr_sdk-0.1.0-py3-none-any.whl /absolute/artifacts/tansr_sdk_demo-0.1.0-py3-none-any.whl
84
+ python -m pip check
85
+ tansr-py-chat --help
86
+ tansr-py-tools --help
87
+ tansr-py-archive --help
88
+ ```
89
+
90
+ Demo 精确依赖 `tansr-sdk==0.1.0`,不复制 SDK。模块入口分别为 `python -m tansr_demo.chat`、`python -m tansr_demo.tools`、`python -m tansr_demo.archive`;另有可执行的 `python -m tansr_demo.async_chat --message ...` asyncio 示例。
91
+
92
+ 接线顺序:先由开发者部署和配置 Serve,再取得本应用/用户的身份与能力,选定会话族并创建或接入会话,最后按需装配终端工具及档案。HTTP 请求提交控制操作,SSE 传输事件;本实现不以 WebSocket 为必需。已有完整 Node/Electron SDK 的运行方式不受此包影响。
93
+
94
+ `sdk1` 保留原完整会话存储路径;`sdk2-offload-v1` 需要 Serve 的授权 Source 装配,并使用稳定创建意图。两族不得对同一会话随意切换。应用能力、权限、审批和核心工具路由仍由 Serve 决定;Python `Runner` 只执行宿主显式注册的业务工具,不把设备操作回落到 Serve 机器,也不附送通用 Shell。
95
+
96
+ `ArchiveClient` 提供的是原档案与材料供给高层,不是另一套上下文/记忆管理器。记忆配置、记忆命令与缓存等已有冻结操作可经 `Client.call` 消费;首版未提供独立 `MemoryClient` / `CacheClient`,不会把通用 API 数量宣传为这些高层已经实现。能力范围和恢复处理见双语指南。
97
+
98
+ 连接已有 Serve 前,按指南准备 `TANSR_TOKEN_FILE`、`TANSR_SCOPE_FILE` 与服务地址;档案另需宿主管理的 `TANSR_ARCHIVE_KEY_FILE`。token/scope 共用私有凭据目录,创建意图、journal 和档案文件使用另外的私有状态目录;Demo 拒绝两种目录重合,避免授权回调重入事务锁。凭据不由 Demo 签发,也不打印到日志。恢复必须保留原会话、请求、截止、档案和密钥;本地取消不证明远端撤销,`received` 不代表 `core-consumed`。
99
+
100
+ 运行源码最低支持 CPython 3.7,排除 3.9.0/3.9.1。旧解释器兼容不恢复其上游安全维护。实际依赖锁、平台环境和独立 wheel/sdist 消费方法见 [依赖说明](https://github.com/tansrai/tansr-python/blob/main/requirements/README.md);SDK 自身的纯 Python wheel 不代表加密依赖没有原生二进制。
101
+
102
+ 默认 HTTP 传输在 HTTPS 的 DNS/连接之前检查 stdlib SSL 运行时;已知受影响 OpenSSL 或未验证的其他 provider 被拒绝为 `unsupported_tls_runtime`,没有绕过开关。Windows 官方 Python 3.7.9 所带 OpenSSL 1.1.1g 因此不能使用默认 HTTPS;回环 HTTP 成功不能替代 TLS 正向证据。
103
+
104
+ 最低 Python 版本与 SSL 安全基线是两个条件。本轮已验证独立 prefix 中的 Anaconda CPython 3.7.9 `h60c2a47_0` + OpenSSL 1.1.1w `h2bbff1b_0`,安装原 SDK wheel 后默认信任 HTTPS 与原传输验收通过;精确包/SHA、安装和子进程运行方式见 [发行说明](https://github.com/tansrai/tansr-python/blob/main/doc/%E5%8F%91%E8%A1%8C%E8%AF%B4%E6%98%8E.md)。这是 legacy 兼容路线,Python 3.7 与 OpenSSL 1.1.1 均已结束维护,不称当前受维护的 TLS 栈。新项目优先选择受维护的解释器;原 1.1.1g 拒绝保持,不关闭验证、不覆盖系统 DLL。
105
+
106
+ SDK 面向 UAPI r7;操作与 schema 已嵌入运行包,正常运行不读取 `contract/`,也不需要生成器。公开源码检出中的开发检查示例:
107
+
108
+ ```text
109
+ python tools/contract_check.py --mode public
110
+ python tools/generate_api.py --mode public --check
111
+ python -m pytest
112
+ ```
113
+
114
+ 公开源码检查使用显式 `public` 模式,并核验仓库中的公开合同清单;缺少或不匹配时检查失败。真实 Serve 验证使用合成身份、平台及模型,不代表已调用付费模型。源码测试、各系统环境、安装消费和正式发布分别记录,适用范围见发行说明。
@@ -0,0 +1,60 @@
1
+ # Tansr Python SDK
2
+
3
+ 原生 Python 3.7+ SDK,通过统一 `/api` 连接 Serve。Serve 负责运行、会话、上下文、记忆、权限和工具调度;Python 提供同步与 asyncio 接口、显式业务工具、分块输出和私有本地档案。不捆绑 Node、C++ 或 Rust 运行时。
4
+
5
+ `0.1.0` 的 SDK 与 Demo 均采用 [MIT 许可证](https://github.com/tansrai/tansr-python/blob/main/LICENSE)。源码渠道为 [tansrai/tansr-python](https://github.com/tansrai/tansr-python),包渠道为 PyPI 的 [tansr-sdk](https://pypi.org/project/tansr-sdk/) 与 [tansr-sdk-demo](https://pypi.org/project/tansr-sdk-demo/)。已验证环境与适用限制见发行说明。
6
+
7
+ 使用文档:[中文指南](https://github.com/tansrai/tansr-python/blob/main/doc/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97.md) · [English guide](https://github.com/tansrai/tansr-python/blob/main/doc/guide.md) · [发行说明](https://github.com/tansrai/tansr-python/blob/main/doc/%E5%8F%91%E8%A1%8C%E8%AF%B4%E6%98%8E.md) · [Demo 入口](https://github.com/tansrai/tansr-python/blob/main/demo/README.md)。English readers: version 0.1.0 includes the SDK and separate Demo package, both under the [MIT license](https://github.com/tansrai/tansr-python/blob/main/LICENSE).
8
+
9
+ | 能力 | 公开入口 |
10
+ | --- | --- |
11
+ | 81 项统一 API、严格 JSON 与错误分类 | `Client`、`AsyncClient` |
12
+ | 会话、事件、权限、提问、输入与检查点 | `SessionClient`、`AsyncSessionClient` |
13
+ | 显式工具、续租、耐久业务回执与独立输出确认 | `ExecutorClient`、`Runner`、`AsyncRunner` |
14
+ | 创建意图、加密档案、耐久 ACK 与材料供给 | `ArchiveClient`、`AsyncArchiveClient`、`FileStore` |
15
+ | 独立 Demo 包 | `tansr-py-chat`、`tansr-py-tools`、`tansr-py-archive` |
16
+
17
+ 嵌入自己的应用只安装 `tansr-sdk`;需要试用三个命令行示例时再安装独立的 `tansr-sdk-demo`。SDK 不依赖 Demo,宿主通过 `AuthToken` provider 接入自己的登录和短期凭据,不必沿用 Demo 的文件布局。同步和异步高层共享同一协议实现;已有 asyncio 应用直接 `await`,不接管宿主事件循环。
18
+
19
+ 在虚拟环境中从 PyPI 按需安装同版包:
20
+
21
+ ```text
22
+ python -m pip install tansr-sdk==0.1.0
23
+ python -m pip install tansr-sdk-demo==0.1.0
24
+ ```
25
+
26
+ 离线环境可安装已核验的同版 wheel;以下路径需替换为实际文件:
27
+
28
+ ```text
29
+ python -m pip install /absolute/artifacts/tansr_sdk-0.1.0-py3-none-any.whl /absolute/artifacts/tansr_sdk_demo-0.1.0-py3-none-any.whl
30
+ python -m pip check
31
+ tansr-py-chat --help
32
+ tansr-py-tools --help
33
+ tansr-py-archive --help
34
+ ```
35
+
36
+ Demo 精确依赖 `tansr-sdk==0.1.0`,不复制 SDK。模块入口分别为 `python -m tansr_demo.chat`、`python -m tansr_demo.tools`、`python -m tansr_demo.archive`;另有可执行的 `python -m tansr_demo.async_chat --message ...` asyncio 示例。
37
+
38
+ 接线顺序:先由开发者部署和配置 Serve,再取得本应用/用户的身份与能力,选定会话族并创建或接入会话,最后按需装配终端工具及档案。HTTP 请求提交控制操作,SSE 传输事件;本实现不以 WebSocket 为必需。已有完整 Node/Electron SDK 的运行方式不受此包影响。
39
+
40
+ `sdk1` 保留原完整会话存储路径;`sdk2-offload-v1` 需要 Serve 的授权 Source 装配,并使用稳定创建意图。两族不得对同一会话随意切换。应用能力、权限、审批和核心工具路由仍由 Serve 决定;Python `Runner` 只执行宿主显式注册的业务工具,不把设备操作回落到 Serve 机器,也不附送通用 Shell。
41
+
42
+ `ArchiveClient` 提供的是原档案与材料供给高层,不是另一套上下文/记忆管理器。记忆配置、记忆命令与缓存等已有冻结操作可经 `Client.call` 消费;首版未提供独立 `MemoryClient` / `CacheClient`,不会把通用 API 数量宣传为这些高层已经实现。能力范围和恢复处理见双语指南。
43
+
44
+ 连接已有 Serve 前,按指南准备 `TANSR_TOKEN_FILE`、`TANSR_SCOPE_FILE` 与服务地址;档案另需宿主管理的 `TANSR_ARCHIVE_KEY_FILE`。token/scope 共用私有凭据目录,创建意图、journal 和档案文件使用另外的私有状态目录;Demo 拒绝两种目录重合,避免授权回调重入事务锁。凭据不由 Demo 签发,也不打印到日志。恢复必须保留原会话、请求、截止、档案和密钥;本地取消不证明远端撤销,`received` 不代表 `core-consumed`。
45
+
46
+ 运行源码最低支持 CPython 3.7,排除 3.9.0/3.9.1。旧解释器兼容不恢复其上游安全维护。实际依赖锁、平台环境和独立 wheel/sdist 消费方法见 [依赖说明](https://github.com/tansrai/tansr-python/blob/main/requirements/README.md);SDK 自身的纯 Python wheel 不代表加密依赖没有原生二进制。
47
+
48
+ 默认 HTTP 传输在 HTTPS 的 DNS/连接之前检查 stdlib SSL 运行时;已知受影响 OpenSSL 或未验证的其他 provider 被拒绝为 `unsupported_tls_runtime`,没有绕过开关。Windows 官方 Python 3.7.9 所带 OpenSSL 1.1.1g 因此不能使用默认 HTTPS;回环 HTTP 成功不能替代 TLS 正向证据。
49
+
50
+ 最低 Python 版本与 SSL 安全基线是两个条件。本轮已验证独立 prefix 中的 Anaconda CPython 3.7.9 `h60c2a47_0` + OpenSSL 1.1.1w `h2bbff1b_0`,安装原 SDK wheel 后默认信任 HTTPS 与原传输验收通过;精确包/SHA、安装和子进程运行方式见 [发行说明](https://github.com/tansrai/tansr-python/blob/main/doc/%E5%8F%91%E8%A1%8C%E8%AF%B4%E6%98%8E.md)。这是 legacy 兼容路线,Python 3.7 与 OpenSSL 1.1.1 均已结束维护,不称当前受维护的 TLS 栈。新项目优先选择受维护的解释器;原 1.1.1g 拒绝保持,不关闭验证、不覆盖系统 DLL。
51
+
52
+ SDK 面向 UAPI r7;操作与 schema 已嵌入运行包,正常运行不读取 `contract/`,也不需要生成器。公开源码检出中的开发检查示例:
53
+
54
+ ```text
55
+ python tools/contract_check.py --mode public
56
+ python tools/generate_api.py --mode public --check
57
+ python -m pytest
58
+ ```
59
+
60
+ 公开源码检查使用显式 `public` 模式,并核验仓库中的公开合同清单;缺少或不匹配时检查失败。真实 Serve 验证使用合成身份、平台及模型,不代表已调用付费模型。源码测试、各系统环境、安装消费和正式发布分别记录,适用范围见发行说明。
@@ -0,0 +1,72 @@
1
+ [build-system]
2
+ requires = [
3
+ "setuptools==68.0.0; python_version < '3.8'",
4
+ "setuptools==75.3.2; python_version == '3.8'",
5
+ "setuptools==80.9.0; python_version == '3.9'",
6
+ "setuptools==84.0.0; python_version >= '3.10'",
7
+ "wheel==0.42.0; python_version < '3.8'",
8
+ "wheel==0.45.1; python_version == '3.8'",
9
+ "wheel==0.48.0; python_version >= '3.9'",
10
+ ]
11
+ build-backend = "setuptools.build_meta"
12
+
13
+ [project]
14
+ name = "tansr-sdk"
15
+ version = "0.1.0"
16
+ description = "Native Python client SDK for the Tansr Serve unified API"
17
+ readme = "README.md"
18
+ requires-python = ">=3.7,!=3.9.0,!=3.9.1"
19
+ license = {file = "LICENSE"}
20
+ authors = [{name = "Tansr"}]
21
+ dependencies = [
22
+ "cryptography==45.0.7; python_version < '3.8'",
23
+ "cffi==1.15.1; python_version < '3.8'",
24
+ "pycparser==2.21; python_version < '3.8'",
25
+ "cryptography==47.0.0; python_version >= '3.8' and python_version < '3.9'",
26
+ "cryptography==50.0.2; python_version >= '3.9'",
27
+ ]
28
+ classifiers = [
29
+ "Development Status :: 3 - Alpha",
30
+ "Intended Audience :: Developers",
31
+ "License :: OSI Approved :: MIT License",
32
+ "Programming Language :: Python :: 3",
33
+ "Programming Language :: Python :: 3.7",
34
+ "Programming Language :: Python :: 3.8",
35
+ "Programming Language :: Python :: 3.9",
36
+ "Programming Language :: Python :: 3.10",
37
+ "Programming Language :: Python :: 3.11",
38
+ "Programming Language :: Python :: 3.12",
39
+ "Programming Language :: Python :: 3.13",
40
+ "Programming Language :: Python :: 3.14",
41
+ "Typing :: Typed",
42
+ ]
43
+
44
+ [project.urls]
45
+ Homepage = "https://www.tansr.com/"
46
+ Repository = "https://github.com/tansrai/tansr-python"
47
+ Documentation = "https://github.com/tansrai/tansr-python/blob/main/doc/guide.md"
48
+ Issues = "https://github.com/tansrai/tansr-python/issues"
49
+
50
+ [tool.setuptools.packages.find]
51
+ where = ["src"]
52
+
53
+ [tool.setuptools.package-data]
54
+ tansr_sdk = ["py.typed"]
55
+
56
+ [tool.pytest.ini_options]
57
+ testpaths = ["tests"]
58
+ pythonpath = ["src"]
59
+ addopts = "--strict-markers --strict-config"
60
+
61
+ [tool.ruff]
62
+ target-version = "py37"
63
+ line-length = 120
64
+
65
+ [tool.ruff.lint]
66
+ select = ["E4", "E7", "E9", "F"]
67
+
68
+ [tool.mypy]
69
+ files = ["src/tansr_sdk"]
70
+ check_untyped_defs = true
71
+ ignore_missing_imports = true
72
+ no_implicit_optional = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,8 @@
1
+ """Tansr 原生 Python 接入层;智能体核心运行在 Serve。"""
2
+
3
+ from .errors import Error
4
+ from .lifecycle import CancellationToken
5
+ from .api import Client, AsyncClient, AuthToken, CallOptions, ApiResponse
6
+
7
+ __version__ = "0.1.0"
8
+ __all__ = ["Client", "AsyncClient", "AuthToken", "CallOptions", "ApiResponse", "Error", "CancellationToken"]
@@ -0,0 +1,205 @@
1
+ """有界专有 DNS 子进程;绝不把取消误作 getaddrinfo 线程已经结束。"""
2
+
3
+ import ipaddress
4
+ import json
5
+ import os
6
+ import socket
7
+ import subprocess
8
+ import sys
9
+ import threading
10
+ import time
11
+ from typing import List, Optional, Tuple
12
+
13
+ from .errors import Error
14
+ from .lifecycle import CancellationToken, now_ms
15
+
16
+ _MAX_REPLY = 16384
17
+ _MAX_ADDRESSES = 64
18
+ _WORKER = r"""
19
+ import json, socket, sys
20
+ try:
21
+ host, port = sys.argv[1], int(sys.argv[2])
22
+ rows = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
23
+ unique = []
24
+ for family, kind, protocol, name, address in rows:
25
+ if family not in (socket.AF_INET, socket.AF_INET6): continue
26
+ value = [family, list(address)]
27
+ if value not in unique: unique.append(value)
28
+ if len(unique) >= 64: break
29
+ raw = json.dumps(unique, separators=(',', ':')).encode('ascii')
30
+ if not unique or len(raw) > 16384: sys.exit(2)
31
+ sys.stdout.buffer.write(raw)
32
+ except Exception:
33
+ sys.exit(2)
34
+ """
35
+
36
+
37
+ def _environment() -> dict:
38
+ # Windows 解释器/系统解析所需变量;不继承 token、代理、PYTHONPATH 等。
39
+ allowed = {"SYSTEMROOT", "WINDIR", "SYSTEMDRIVE", "COMSPEC"}
40
+ return {key: value for key, value in os.environ.items() if key.upper() in allowed}
41
+
42
+
43
+ def _numeric(host: str, port: int) -> Optional[List[Tuple[int, tuple]]]:
44
+ try:
45
+ address = ipaddress.ip_address(host)
46
+ except ValueError:
47
+ return None
48
+ if address.version == 4:
49
+ return [(socket.AF_INET, (str(address), port))]
50
+ return [(socket.AF_INET6, (str(address), port, 0, 0))]
51
+
52
+
53
+ class Resolver:
54
+ def __init__(self, max_processes: int = 2) -> None:
55
+ if isinstance(max_processes, bool) or not isinstance(max_processes, int) or max_processes < 1:
56
+ raise Error("invalid_argument")
57
+ self._slots = threading.BoundedSemaphore(max_processes)
58
+ self._lock = threading.Lock()
59
+ self._processes = set() # type: set
60
+ self._closed = False
61
+
62
+ def resolve(self, host: str, port: int, deadline_ms: int, cancel: CancellationToken) -> List[Tuple[int, tuple]]:
63
+ if not isinstance(host, str) or not host or len(host) > 253:
64
+ raise Error("invalid_argument")
65
+ if any(ord(char) < 33 or ord(char) > 126 for char in host):
66
+ raise Error("invalid_argument")
67
+ if isinstance(port, bool) or not isinstance(port, int) or not 0 < port < 65536:
68
+ raise Error("invalid_argument")
69
+ end = time.monotonic() + max(0, deadline_ms - now_ms()) / 1000.0
70
+
71
+ def check() -> None:
72
+ cancel.check(deadline_ms)
73
+ if time.monotonic() >= end:
74
+ raise Error("timeout")
75
+ with self._lock:
76
+ if self._closed:
77
+ raise Error("closed")
78
+
79
+ check()
80
+ numeric = _numeric(host, port)
81
+ if numeric is not None:
82
+ return numeric
83
+ while not self._slots.acquire(timeout=min(0.025, max(0.001, end - time.monotonic()))):
84
+ check()
85
+ process = None
86
+ reader = None
87
+ chunks = [] # type: list
88
+ reader_error = [] # type: list
89
+ overflow = threading.Event()
90
+ try:
91
+ check()
92
+ executable = os.path.abspath(sys.executable) if sys.executable else ""
93
+ if not executable or not os.path.isfile(executable) or getattr(sys, "frozen", False):
94
+ raise Error("unsupported", "Host must provide a cancellable resolver")
95
+ # -I -S 不装载宿主 site/customize;参数不经 shell 或 pickle。
96
+ with self._lock:
97
+ if self._closed:
98
+ raise Error("closed")
99
+ process = subprocess.Popen(
100
+ [executable, "-I", "-S", "-c", _WORKER, host, str(port)],
101
+ stdin=subprocess.DEVNULL,
102
+ stdout=subprocess.PIPE,
103
+ stderr=subprocess.DEVNULL,
104
+ env=_environment(),
105
+ shell=False,
106
+ close_fds=True,
107
+ creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
108
+ )
109
+ self._processes.add(process)
110
+ stdout = process.stdout
111
+ if stdout is None:
112
+ raise Error("network", "DNS reply pipe is not available")
113
+
114
+ def read_reply() -> None:
115
+ try:
116
+ data = stdout.read(_MAX_REPLY + 1)
117
+ chunks.append(data)
118
+ if len(data) > _MAX_REPLY:
119
+ overflow.set()
120
+ process.kill()
121
+ except (OSError, ValueError) as exc:
122
+ reader_error.append(type(exc).__name__)
123
+
124
+ reader = threading.Thread(target=read_reply, name="tansr-dns-reply")
125
+ try:
126
+ reader.start()
127
+ except RuntimeError:
128
+ raise Error("resource_limit", "Cannot start DNS reply reader") from None
129
+ while process.poll() is None:
130
+ check()
131
+ if overflow.is_set():
132
+ raise Error("resource_limit")
133
+ cancel.wait(min(0.02, max(0.001, end - time.monotonic())))
134
+ reader.join()
135
+ check()
136
+ if overflow.is_set():
137
+ raise Error("resource_limit")
138
+ if process.returncode != 0 or reader_error or len(chunks) != 1:
139
+ raise Error("network", "DNS resolution failed")
140
+ try:
141
+ values = json.loads(chunks[0].decode("ascii"))
142
+ if not isinstance(values, list) or not 1 <= len(values) <= _MAX_ADDRESSES:
143
+ raise ValueError()
144
+ result = []
145
+ for value in values:
146
+ if not isinstance(value, list) or len(value) != 2:
147
+ raise ValueError()
148
+ family, address = value
149
+ if type(family) is not int or family not in (socket.AF_INET, socket.AF_INET6):
150
+ raise ValueError()
151
+ if not isinstance(address, list) or len(address) != (2 if family == socket.AF_INET else 4):
152
+ raise ValueError()
153
+ if not isinstance(address[0], str) or type(address[1]) is not int or address[1] != port:
154
+ raise ValueError()
155
+ parsed = ipaddress.ip_address(address[0])
156
+ if parsed.version != (4 if family == socket.AF_INET else 6):
157
+ raise ValueError()
158
+ if family == socket.AF_INET6 and any(
159
+ type(v) is not int or not 0 <= v <= 0xFFFFFFFF for v in address[2:]
160
+ ):
161
+ raise ValueError()
162
+ result.append((family, tuple(address)))
163
+ return result
164
+ except (ValueError, TypeError, UnicodeError, OverflowError):
165
+ raise Error("network", "Invalid DNS resolver reply") from None
166
+ except Error:
167
+ raise
168
+ except (OSError, ValueError):
169
+ cancel.check(deadline_ms)
170
+ raise Error("network", "Cannot start DNS resolver") from None
171
+ finally:
172
+ if process is not None:
173
+ if process.poll() is None:
174
+ try:
175
+ process.kill()
176
+ except OSError:
177
+ if process.poll() is None:
178
+ raise
179
+ process.wait()
180
+ if reader is not None and reader.ident is not None:
181
+ reader.join()
182
+ if process.stdout is not None:
183
+ process.stdout.close()
184
+ with self._lock:
185
+ self._processes.discard(process)
186
+ self._slots.release()
187
+
188
+ def close(self, timeout: float = 30) -> bool:
189
+ with self._lock:
190
+ self._closed = True
191
+ processes = list(self._processes)
192
+ for process in processes:
193
+ if process.poll() is None:
194
+ try:
195
+ process.kill()
196
+ except OSError:
197
+ pass
198
+ end = time.monotonic() + max(0, timeout)
199
+ while True:
200
+ with self._lock:
201
+ if not self._processes:
202
+ return True
203
+ if time.monotonic() >= end:
204
+ return False
205
+ time.sleep(0.005)
@@ -0,0 +1,4 @@
1
+ from .types import AuthToken, CallOptions, ApiResponse
2
+ from .client import Client, AsyncClient, EventStream, AsyncEventStream
3
+
4
+ __all__ = ["AuthToken", "CallOptions", "ApiResponse", "Client", "AsyncClient", "EventStream", "AsyncEventStream"]