ai-lib-python 0.6.0__tar.gz → 0.7.1__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.
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/PKG-INFO +25 -4
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/README.md +24 -3
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/pyproject.toml +1 -1
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/__init__.py +1 -1
- ai_lib_python-0.7.1/src/ai_lib_python/computer_use/__init__.py +228 -0
- ai_lib_python-0.7.1/src/ai_lib_python/drivers/__init__.py +140 -0
- ai_lib_python-0.7.1/src/ai_lib_python/drivers/anthropic.py +173 -0
- ai_lib_python-0.7.1/src/ai_lib_python/drivers/gemini.py +177 -0
- ai_lib_python-0.7.1/src/ai_lib_python/drivers/openai.py +133 -0
- ai_lib_python-0.7.1/src/ai_lib_python/mcp/__init__.py +181 -0
- ai_lib_python-0.7.1/src/ai_lib_python/multimodal/__init__.py +138 -0
- ai_lib_python-0.7.1/src/ai_lib_python/protocol/v2/__init__.py +22 -0
- ai_lib_python-0.7.1/src/ai_lib_python/protocol/v2/capabilities.py +198 -0
- ai_lib_python-0.7.1/src/ai_lib_python/protocol/v2/manifest.py +256 -0
- ai_lib_python-0.7.1/src/ai_lib_python/registry/__init__.py +174 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/.gitignore +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/LICENSE-APACHE +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/LICENSE-MIT +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/_features.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/batch/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/batch/collector.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/batch/executor.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/cache/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/cache/backends.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/cache/key.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/cache/manager.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/client/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/client/builder.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/client/cancel.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/client/core.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/client/response.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/embeddings/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/embeddings/client.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/embeddings/types.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/embeddings/vectors.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/errors/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/errors/base.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/errors/classification.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/errors/standard_codes.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/guardrails/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/guardrails/base.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/guardrails/filters.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/guardrails/validators.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/pipeline/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/pipeline/accumulate.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/pipeline/base.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/pipeline/decode.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/pipeline/event_map.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/pipeline/fan_out.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/pipeline/select.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/plugins/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/plugins/base.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/plugins/hooks.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/plugins/middleware.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/plugins/registry.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/protocol/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/protocol/loader.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/protocol/manifest.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/protocol/validator.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/py.typed +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/backpressure.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/circuit_breaker.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/executor.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/fallback.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/preflight.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/rate_limiter.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/retry.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/signals.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/routing/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/routing/manager.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/routing/strategy.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/routing/types.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/structured/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/structured/json_mode.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/structured/schema.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/structured/validator.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/telemetry/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/telemetry/exporters/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/telemetry/exporters/prometheus.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/telemetry/feedback.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/telemetry/health.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/telemetry/logger.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/telemetry/metrics.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/telemetry/tracer.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/tokens/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/tokens/counter.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/tokens/estimator.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/transport/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/transport/auth.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/transport/http.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/transport/pool.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/types/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/types/events.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/types/message.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/types/tool.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/utils/__init__.py +0 -0
- {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/utils/tool_call_assembler.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ai-lib-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Official Python Runtime for AI-Protocol - The canonical Pythonic implementation for unified AI model interaction
|
|
5
5
|
Project-URL: Homepage, https://github.com/hiddenpath/ai-lib-python
|
|
6
6
|
Project-URL: Documentation, https://github.com/hiddenpath/ai-lib-python#readme
|
|
@@ -138,7 +138,7 @@ asyncio.run(main())
|
|
|
138
138
|
|
|
139
139
|
## 🔄 V2 Protocol Alignment
|
|
140
140
|
|
|
141
|
-
Starting with v0.5.0, `ai-lib-python` aligns with the **AI-Protocol V2** specification
|
|
141
|
+
Starting with v0.5.0, `ai-lib-python` aligns with the **AI-Protocol V2** specification. V0.7.0 adds full V2 runtime support including V2 manifest parsing, provider drivers, MCP tool bridge, Computer Use abstraction, extended multimodal, and capability registry.
|
|
142
142
|
|
|
143
143
|
### Standard Error Codes (V2)
|
|
144
144
|
|
|
@@ -176,6 +176,20 @@ COMPLIANCE_DIR=../ai-protocol/tests/compliance pytest tests/compliance/ -v
|
|
|
176
176
|
|
|
177
177
|
For details, see [CROSS_RUNTIME.md](https://github.com/hiddenpath/ai-protocol/blob/main/docs/CROSS_RUNTIME.md).
|
|
178
178
|
|
|
179
|
+
### Testing with ai-protocol-mock
|
|
180
|
+
|
|
181
|
+
For integration and MCP e2e tests without real API calls, use [ai-protocol-mock](https://github.com/hiddenpath/ai-protocol-mock):
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# Start mock server (from ai-protocol-mock repo)
|
|
185
|
+
docker-compose up -d
|
|
186
|
+
|
|
187
|
+
# Run tests with mock
|
|
188
|
+
MOCK_HTTP_URL=http://localhost:4010 MOCK_MCP_URL=http://localhost:4010/mcp pytest tests/ -v
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Or in code: `AiClient.create("openai/gpt-4o", base_url="http://localhost:4010")`
|
|
192
|
+
|
|
179
193
|
## 📦 Installation
|
|
180
194
|
|
|
181
195
|
```bash
|
|
@@ -228,6 +242,8 @@ Provider manifests are resolved in a backward-compatible order:
|
|
|
228
242
|
| `AI_LIB_RPS` | Rate limit (requests per second) | - |
|
|
229
243
|
| `AI_LIB_BREAKER_FAILURE_THRESHOLD` | Circuit breaker failure threshold | 5 |
|
|
230
244
|
| `AI_LIB_BREAKER_COOLDOWN_SECS` | Circuit breaker cooldown seconds | 30 |
|
|
245
|
+
| `MOCK_HTTP_URL` | Mock server URL for testing (ai-protocol-mock) | - |
|
|
246
|
+
| `MOCK_MCP_URL` | Mock MCP endpoint for testing | - |
|
|
231
247
|
|
|
232
248
|
### Provider API Keys
|
|
233
249
|
|
|
@@ -945,10 +961,15 @@ ai-lib-python/
|
|
|
945
961
|
│ │ └── validator.py # OutputValidator
|
|
946
962
|
│ ├── utils/ # Utilities
|
|
947
963
|
│ │ └── tool_call_assembler.py # ToolCallAssembler
|
|
964
|
+
│ ├── drivers/ # V2 provider drivers (OpenAI, Anthropic, Gemini)
|
|
965
|
+
│ ├── mcp/ # MCP tool bridge
|
|
966
|
+
│ ├── computer_use/ # Computer Use abstraction
|
|
967
|
+
│ ├── multimodal/ # Extended multimodal support
|
|
968
|
+
│ ├── registry/ # Capability registry
|
|
948
969
|
│ └── errors/ # Error hierarchy
|
|
949
970
|
├── tests/
|
|
950
971
|
│ ├── unit/ # Unit tests
|
|
951
|
-
│ └── integration/ # Integration tests
|
|
972
|
+
│ └── integration/ # Integration tests (incl. V2 compliance)
|
|
952
973
|
├── docs/ # Documentation
|
|
953
974
|
├── examples/ # Example scripts
|
|
954
975
|
└── pyproject.toml
|
|
@@ -956,7 +977,7 @@ ai-lib-python/
|
|
|
956
977
|
|
|
957
978
|
## 📖 Related Projects
|
|
958
979
|
|
|
959
|
-
- [AI-Protocol](https://github.com/hiddenpath/ai-protocol) - Protocol specification (v1.5)
|
|
980
|
+
- [AI-Protocol](https://github.com/hiddenpath/ai-protocol) - Protocol specification (v1.5 / V2)
|
|
960
981
|
- [ai-lib-rust](https://github.com/hiddenpath/ai-lib-rust) - Rust runtime implementation
|
|
961
982
|
|
|
962
983
|
## 🤝 Contributing
|
|
@@ -68,7 +68,7 @@ asyncio.run(main())
|
|
|
68
68
|
|
|
69
69
|
## 🔄 V2 Protocol Alignment
|
|
70
70
|
|
|
71
|
-
Starting with v0.5.0, `ai-lib-python` aligns with the **AI-Protocol V2** specification
|
|
71
|
+
Starting with v0.5.0, `ai-lib-python` aligns with the **AI-Protocol V2** specification. V0.7.0 adds full V2 runtime support including V2 manifest parsing, provider drivers, MCP tool bridge, Computer Use abstraction, extended multimodal, and capability registry.
|
|
72
72
|
|
|
73
73
|
### Standard Error Codes (V2)
|
|
74
74
|
|
|
@@ -106,6 +106,20 @@ COMPLIANCE_DIR=../ai-protocol/tests/compliance pytest tests/compliance/ -v
|
|
|
106
106
|
|
|
107
107
|
For details, see [CROSS_RUNTIME.md](https://github.com/hiddenpath/ai-protocol/blob/main/docs/CROSS_RUNTIME.md).
|
|
108
108
|
|
|
109
|
+
### Testing with ai-protocol-mock
|
|
110
|
+
|
|
111
|
+
For integration and MCP e2e tests without real API calls, use [ai-protocol-mock](https://github.com/hiddenpath/ai-protocol-mock):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Start mock server (from ai-protocol-mock repo)
|
|
115
|
+
docker-compose up -d
|
|
116
|
+
|
|
117
|
+
# Run tests with mock
|
|
118
|
+
MOCK_HTTP_URL=http://localhost:4010 MOCK_MCP_URL=http://localhost:4010/mcp pytest tests/ -v
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Or in code: `AiClient.create("openai/gpt-4o", base_url="http://localhost:4010")`
|
|
122
|
+
|
|
109
123
|
## 📦 Installation
|
|
110
124
|
|
|
111
125
|
```bash
|
|
@@ -158,6 +172,8 @@ Provider manifests are resolved in a backward-compatible order:
|
|
|
158
172
|
| `AI_LIB_RPS` | Rate limit (requests per second) | - |
|
|
159
173
|
| `AI_LIB_BREAKER_FAILURE_THRESHOLD` | Circuit breaker failure threshold | 5 |
|
|
160
174
|
| `AI_LIB_BREAKER_COOLDOWN_SECS` | Circuit breaker cooldown seconds | 30 |
|
|
175
|
+
| `MOCK_HTTP_URL` | Mock server URL for testing (ai-protocol-mock) | - |
|
|
176
|
+
| `MOCK_MCP_URL` | Mock MCP endpoint for testing | - |
|
|
161
177
|
|
|
162
178
|
### Provider API Keys
|
|
163
179
|
|
|
@@ -875,10 +891,15 @@ ai-lib-python/
|
|
|
875
891
|
│ │ └── validator.py # OutputValidator
|
|
876
892
|
│ ├── utils/ # Utilities
|
|
877
893
|
│ │ └── tool_call_assembler.py # ToolCallAssembler
|
|
894
|
+
│ ├── drivers/ # V2 provider drivers (OpenAI, Anthropic, Gemini)
|
|
895
|
+
│ ├── mcp/ # MCP tool bridge
|
|
896
|
+
│ ├── computer_use/ # Computer Use abstraction
|
|
897
|
+
│ ├── multimodal/ # Extended multimodal support
|
|
898
|
+
│ ├── registry/ # Capability registry
|
|
878
899
|
│ └── errors/ # Error hierarchy
|
|
879
900
|
├── tests/
|
|
880
901
|
│ ├── unit/ # Unit tests
|
|
881
|
-
│ └── integration/ # Integration tests
|
|
902
|
+
│ └── integration/ # Integration tests (incl. V2 compliance)
|
|
882
903
|
├── docs/ # Documentation
|
|
883
904
|
├── examples/ # Example scripts
|
|
884
905
|
└── pyproject.toml
|
|
@@ -886,7 +907,7 @@ ai-lib-python/
|
|
|
886
907
|
|
|
887
908
|
## 📖 Related Projects
|
|
888
909
|
|
|
889
|
-
- [AI-Protocol](https://github.com/hiddenpath/ai-protocol) - Protocol specification (v1.5)
|
|
910
|
+
- [AI-Protocol](https://github.com/hiddenpath/ai-protocol) - Protocol specification (v1.5 / V2)
|
|
890
911
|
- [ai-lib-rust](https://github.com/hiddenpath/ai-lib-rust) - Rust runtime implementation
|
|
891
912
|
|
|
892
913
|
## 🤝 Contributing
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ai-lib-python"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.1"
|
|
8
8
|
description = "Official Python Runtime for AI-Protocol - The canonical Pythonic implementation for unified AI model interaction"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT OR Apache-2.0"
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""Computer Use 抽象层 — 提供跨厂商的 GUI 自动化操作标准化和安全控制。
|
|
2
|
+
|
|
3
|
+
Computer Use abstraction layer for AI-Protocol. Provides:
|
|
4
|
+
- Normalized action types across providers (screen_based, tool_based)
|
|
5
|
+
- Safety policy enforcement (confirmation, sandbox, logging, domain allowlist)
|
|
6
|
+
- Provider-specific configuration extraction
|
|
7
|
+
- Action validation before execution
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from typing import Any
|
|
15
|
+
from urllib.parse import urlparse
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ─── Normalized Action Types ────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ActionType(str, Enum):
|
|
22
|
+
"""Normalized computer use action types."""
|
|
23
|
+
|
|
24
|
+
SCREENSHOT = "screenshot"
|
|
25
|
+
MOUSE_CLICK = "mouse_click"
|
|
26
|
+
MOUSE_DOUBLE_CLICK = "mouse_double_click"
|
|
27
|
+
MOUSE_DRAG = "mouse_drag"
|
|
28
|
+
SCROLL = "scroll"
|
|
29
|
+
MOUSE_MOVE = "mouse_move"
|
|
30
|
+
KEYBOARD_TYPE = "keyboard_type"
|
|
31
|
+
KEYBOARD_SHORTCUT = "keyboard_shortcut"
|
|
32
|
+
BROWSER_NAVIGATE = "browser_navigate"
|
|
33
|
+
BROWSER_CLICK_ELEMENT = "browser_click_element"
|
|
34
|
+
BROWSER_FILL_FORM = "browser_fill_form"
|
|
35
|
+
ZOOM_REGION = "zoom_region"
|
|
36
|
+
FILE_READ = "file_read"
|
|
37
|
+
FILE_WRITE = "file_write"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class MouseButton(str, Enum):
|
|
41
|
+
LEFT = "left"
|
|
42
|
+
RIGHT = "right"
|
|
43
|
+
MIDDLE = "middle"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class ComputerAction:
|
|
48
|
+
"""A normalized computer use action — provider-agnostic."""
|
|
49
|
+
|
|
50
|
+
action_type: ActionType
|
|
51
|
+
params: dict[str, Any] = field(default_factory=dict)
|
|
52
|
+
|
|
53
|
+
# -- convenience factories --
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def screenshot(cls, fmt: str = "png") -> ComputerAction:
|
|
57
|
+
return cls(ActionType.SCREENSHOT, {"format": fmt})
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def mouse_click(
|
|
61
|
+
cls, x: float, y: float, button: MouseButton = MouseButton.LEFT
|
|
62
|
+
) -> ComputerAction:
|
|
63
|
+
return cls(ActionType.MOUSE_CLICK, {"x": x, "y": y, "button": button.value})
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def keyboard_type(cls, text: str) -> ComputerAction:
|
|
67
|
+
return cls(ActionType.KEYBOARD_TYPE, {"text": text})
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def keyboard_shortcut(cls, keys: list[str]) -> ComputerAction:
|
|
71
|
+
return cls(ActionType.KEYBOARD_SHORTCUT, {"keys": keys})
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def browser_navigate(cls, url: str) -> ComputerAction:
|
|
75
|
+
return cls(ActionType.BROWSER_NAVIGATE, {"url": url})
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def file_read(cls, path: str) -> ComputerAction:
|
|
79
|
+
return cls(ActionType.FILE_READ, {"path": path})
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def file_write(cls, path: str, content: str) -> ComputerAction:
|
|
83
|
+
return cls(ActionType.FILE_WRITE, {"path": path, "content": content})
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ImplementationStyle(str, Enum):
|
|
87
|
+
"""Provider implementation approach."""
|
|
88
|
+
|
|
89
|
+
SCREEN_BASED = "screen_based"
|
|
90
|
+
TOOL_BASED = "tool_based"
|
|
91
|
+
HYBRID = "hybrid"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class SandboxMode(str, Enum):
|
|
95
|
+
REQUIRED = "required"
|
|
96
|
+
RECOMMENDED = "recommended"
|
|
97
|
+
OPTIONAL = "optional"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ─── Safety Policy ──────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class SafetyViolation(Exception):
|
|
104
|
+
"""Raised when a computer use action violates the safety policy."""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class SafetyPolicy:
|
|
109
|
+
"""Safety policy for computer use actions.
|
|
110
|
+
|
|
111
|
+
Loaded from the manifest's ``computer_use.safety`` configuration.
|
|
112
|
+
All validations are enforced *before* the action is dispatched.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
confirmation_required: bool = True
|
|
116
|
+
sandbox_mode: SandboxMode = SandboxMode.RECOMMENDED
|
|
117
|
+
action_logging: bool = True
|
|
118
|
+
domain_allowlist: set[str] = field(default_factory=set)
|
|
119
|
+
sensitive_data_protection: bool = True
|
|
120
|
+
max_actions_per_turn: int = 0
|
|
121
|
+
action_timeout_ms: int = 30_000
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def from_config(cls, safety_dict: dict[str, Any] | None) -> SafetyPolicy:
|
|
125
|
+
"""Build a safety policy from a manifest's ``computer_use.safety`` dict."""
|
|
126
|
+
if not safety_dict:
|
|
127
|
+
return cls()
|
|
128
|
+
return cls(
|
|
129
|
+
confirmation_required=safety_dict.get("confirmation_required", True),
|
|
130
|
+
sandbox_mode=SandboxMode(safety_dict.get("sandbox_mode", "recommended")),
|
|
131
|
+
action_logging=safety_dict.get("action_logging", True),
|
|
132
|
+
domain_allowlist=set(safety_dict.get("domain_allowlist_entries", [])),
|
|
133
|
+
sensitive_data_protection=safety_dict.get("sensitive_data_protection", True),
|
|
134
|
+
max_actions_per_turn=safety_dict.get("max_actions_per_turn", 0),
|
|
135
|
+
action_timeout_ms=safety_dict.get("action_timeout_ms", 30_000),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def validate_action(
|
|
139
|
+
self,
|
|
140
|
+
action: ComputerAction,
|
|
141
|
+
actions_this_turn: int = 0,
|
|
142
|
+
) -> None:
|
|
143
|
+
"""Validate an action against this policy. Raises :class:`SafetyViolation`."""
|
|
144
|
+
if self.max_actions_per_turn > 0 and actions_this_turn >= self.max_actions_per_turn:
|
|
145
|
+
raise SafetyViolation(
|
|
146
|
+
f"Max actions per turn exceeded: limit={self.max_actions_per_turn}, "
|
|
147
|
+
f"attempted={actions_this_turn + 1}"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if action.action_type == ActionType.BROWSER_NAVIGATE and self.domain_allowlist:
|
|
151
|
+
url = action.params.get("url", "")
|
|
152
|
+
domain = _extract_domain(url)
|
|
153
|
+
if domain not in self.domain_allowlist:
|
|
154
|
+
raise SafetyViolation(
|
|
155
|
+
f"Domain '{domain}' is not in the allowlist: {sorted(self.domain_allowlist)}"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if self.sensitive_data_protection and action.action_type in (
|
|
159
|
+
ActionType.FILE_READ,
|
|
160
|
+
ActionType.FILE_WRITE,
|
|
161
|
+
):
|
|
162
|
+
path = action.params.get("path", "")
|
|
163
|
+
if _is_sensitive_path(path):
|
|
164
|
+
raise SafetyViolation(f"Access to sensitive path '{path}' is blocked")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# ─── Provider Configuration ─────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@dataclass
|
|
171
|
+
class CuProviderConfig:
|
|
172
|
+
"""Provider-specific computer use configuration."""
|
|
173
|
+
|
|
174
|
+
tool_type: str = "computer_use"
|
|
175
|
+
beta_header: str | None = None
|
|
176
|
+
implementation: ImplementationStyle = ImplementationStyle.SCREEN_BASED
|
|
177
|
+
model_requirement: str | None = None
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def extract_provider_config(cu_config: dict[str, Any] | None) -> CuProviderConfig | None:
|
|
181
|
+
"""Extract provider-specific CU configuration from a manifest section."""
|
|
182
|
+
if not cu_config or not cu_config.get("supported"):
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
impl_str = cu_config.get("implementation", "screen_based")
|
|
186
|
+
implementation = ImplementationStyle(impl_str)
|
|
187
|
+
|
|
188
|
+
mapping = cu_config.get("provider_mapping", {})
|
|
189
|
+
return CuProviderConfig(
|
|
190
|
+
tool_type=mapping.get("tool_type", "computer_use"),
|
|
191
|
+
beta_header=mapping.get("beta_header"),
|
|
192
|
+
implementation=implementation,
|
|
193
|
+
model_requirement=mapping.get("model_requirement"),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ─── Helpers ────────────────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
_SENSITIVE_PATTERNS = (
|
|
200
|
+
".ssh", ".gnupg", ".aws", "credentials", "secrets",
|
|
201
|
+
".env", "password", "token", ".kube/config",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _extract_domain(url: str) -> str:
|
|
206
|
+
try:
|
|
207
|
+
parsed = urlparse(url)
|
|
208
|
+
return parsed.hostname or ""
|
|
209
|
+
except Exception:
|
|
210
|
+
return url.split("//")[-1].split("/")[0].split(":")[0]
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _is_sensitive_path(path: str) -> bool:
|
|
214
|
+
lower = path.lower()
|
|
215
|
+
return any(p in lower for p in _SENSITIVE_PATTERNS)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
__all__ = [
|
|
219
|
+
"ActionType",
|
|
220
|
+
"ComputerAction",
|
|
221
|
+
"CuProviderConfig",
|
|
222
|
+
"ImplementationStyle",
|
|
223
|
+
"MouseButton",
|
|
224
|
+
"SafetyPolicy",
|
|
225
|
+
"SafetyViolation",
|
|
226
|
+
"SandboxMode",
|
|
227
|
+
"extract_provider_config",
|
|
228
|
+
]
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Provider 驱动抽象层 — 通过 ABC 实现多厂商 API 适配的动态分发。
|
|
2
|
+
|
|
3
|
+
Provider driver abstraction layer implementing the ProviderContract specification.
|
|
4
|
+
Uses abstract base class + factory for runtime polymorphism, enabling the same
|
|
5
|
+
client code to work with OpenAI, Anthropic, Gemini, and any compatible provider.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from ai_lib_python.protocol.v2.capabilities import Capability
|
|
15
|
+
from ai_lib_python.protocol.v2.manifest import ApiStyle
|
|
16
|
+
from ai_lib_python.types.events import StreamingEvent
|
|
17
|
+
from ai_lib_python.types.message import Message
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class DriverRequest:
|
|
22
|
+
"""Unified HTTP request representation for provider communication."""
|
|
23
|
+
|
|
24
|
+
url: str = ""
|
|
25
|
+
method: str = "POST"
|
|
26
|
+
headers: dict[str, str] = field(default_factory=dict)
|
|
27
|
+
body: dict[str, Any] = field(default_factory=dict)
|
|
28
|
+
stream: bool = False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class DriverResponse:
|
|
33
|
+
"""Unified chat response from provider."""
|
|
34
|
+
|
|
35
|
+
content: str | None = None
|
|
36
|
+
finish_reason: str | None = None
|
|
37
|
+
usage: UsageInfo | None = None
|
|
38
|
+
tool_calls: list[dict[str, Any]] = field(default_factory=list)
|
|
39
|
+
raw: dict[str, Any] = field(default_factory=dict)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class UsageInfo:
|
|
44
|
+
"""Token usage information."""
|
|
45
|
+
|
|
46
|
+
prompt_tokens: int = 0
|
|
47
|
+
completion_tokens: int = 0
|
|
48
|
+
total_tokens: int = 0
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ProviderDriver(ABC):
|
|
52
|
+
"""Core abstract class for provider-specific API adaptation.
|
|
53
|
+
|
|
54
|
+
Each provider API style (OpenAI, Anthropic, Gemini) has a concrete
|
|
55
|
+
implementation. The runtime selects the correct driver based on the
|
|
56
|
+
manifest's ``api_style`` or ``provider_contract``.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def provider_id(self) -> str:
|
|
62
|
+
"""Unique provider identifier (matches manifest ``id``)."""
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def api_style(self) -> ApiStyle:
|
|
67
|
+
"""API style this driver implements."""
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def build_request(
|
|
71
|
+
self,
|
|
72
|
+
messages: list[Message],
|
|
73
|
+
model: str,
|
|
74
|
+
*,
|
|
75
|
+
temperature: float | None = None,
|
|
76
|
+
max_tokens: int | None = None,
|
|
77
|
+
stream: bool = False,
|
|
78
|
+
extra: dict[str, Any] | None = None,
|
|
79
|
+
) -> DriverRequest:
|
|
80
|
+
"""Build a provider-specific HTTP request from unified parameters."""
|
|
81
|
+
|
|
82
|
+
@abstractmethod
|
|
83
|
+
def parse_response(self, body: dict[str, Any]) -> DriverResponse:
|
|
84
|
+
"""Parse a non-streaming response into unified format."""
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
def parse_stream_event(self, data: str) -> StreamingEvent | None:
|
|
88
|
+
"""Parse a single streaming event from raw SSE/NDJSON data."""
|
|
89
|
+
|
|
90
|
+
@abstractmethod
|
|
91
|
+
def supported_capabilities(self) -> list[Capability]:
|
|
92
|
+
"""Get the list of capabilities this driver supports."""
|
|
93
|
+
|
|
94
|
+
@abstractmethod
|
|
95
|
+
def is_stream_done(self, data: str) -> bool:
|
|
96
|
+
"""Check if the done signal has been received in streaming."""
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
# Concrete drivers (imported lazily to avoid circular deps)
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
from ai_lib_python.drivers.anthropic import AnthropicDriver # noqa: E402
|
|
104
|
+
from ai_lib_python.drivers.gemini import GeminiDriver # noqa: E402
|
|
105
|
+
from ai_lib_python.drivers.openai import OpenAiDriver # noqa: E402
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def create_driver(
|
|
109
|
+
api_style: ApiStyle,
|
|
110
|
+
provider_id: str,
|
|
111
|
+
capabilities: list[Capability] | None = None,
|
|
112
|
+
) -> ProviderDriver:
|
|
113
|
+
"""Factory: create the appropriate driver from an API style.
|
|
114
|
+
|
|
115
|
+
``Custom`` falls back to OpenAI-compatible, which covers most
|
|
116
|
+
providers that follow the OpenAI chat completions format (DeepSeek,
|
|
117
|
+
Moonshot, Zhipu, etc.).
|
|
118
|
+
"""
|
|
119
|
+
caps = capabilities or []
|
|
120
|
+
match api_style:
|
|
121
|
+
case ApiStyle.OPENAI_COMPATIBLE | ApiStyle.CUSTOM:
|
|
122
|
+
return OpenAiDriver(provider_id=provider_id, capabilities=caps)
|
|
123
|
+
case ApiStyle.ANTHROPIC_MESSAGES:
|
|
124
|
+
return AnthropicDriver(provider_id=provider_id, capabilities=caps)
|
|
125
|
+
case ApiStyle.GEMINI_GENERATE:
|
|
126
|
+
return GeminiDriver(provider_id=provider_id, capabilities=caps)
|
|
127
|
+
case _:
|
|
128
|
+
return OpenAiDriver(provider_id=provider_id, capabilities=caps)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
__all__ = [
|
|
132
|
+
"AnthropicDriver",
|
|
133
|
+
"DriverRequest",
|
|
134
|
+
"DriverResponse",
|
|
135
|
+
"GeminiDriver",
|
|
136
|
+
"OpenAiDriver",
|
|
137
|
+
"ProviderDriver",
|
|
138
|
+
"UsageInfo",
|
|
139
|
+
"create_driver",
|
|
140
|
+
]
|