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.
Files changed (98) hide show
  1. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/PKG-INFO +25 -4
  2. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/README.md +24 -3
  3. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/pyproject.toml +1 -1
  4. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/__init__.py +1 -1
  5. ai_lib_python-0.7.1/src/ai_lib_python/computer_use/__init__.py +228 -0
  6. ai_lib_python-0.7.1/src/ai_lib_python/drivers/__init__.py +140 -0
  7. ai_lib_python-0.7.1/src/ai_lib_python/drivers/anthropic.py +173 -0
  8. ai_lib_python-0.7.1/src/ai_lib_python/drivers/gemini.py +177 -0
  9. ai_lib_python-0.7.1/src/ai_lib_python/drivers/openai.py +133 -0
  10. ai_lib_python-0.7.1/src/ai_lib_python/mcp/__init__.py +181 -0
  11. ai_lib_python-0.7.1/src/ai_lib_python/multimodal/__init__.py +138 -0
  12. ai_lib_python-0.7.1/src/ai_lib_python/protocol/v2/__init__.py +22 -0
  13. ai_lib_python-0.7.1/src/ai_lib_python/protocol/v2/capabilities.py +198 -0
  14. ai_lib_python-0.7.1/src/ai_lib_python/protocol/v2/manifest.py +256 -0
  15. ai_lib_python-0.7.1/src/ai_lib_python/registry/__init__.py +174 -0
  16. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/.gitignore +0 -0
  17. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/LICENSE-APACHE +0 -0
  18. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/LICENSE-MIT +0 -0
  19. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/_features.py +0 -0
  20. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/batch/__init__.py +0 -0
  21. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/batch/collector.py +0 -0
  22. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/batch/executor.py +0 -0
  23. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/cache/__init__.py +0 -0
  24. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/cache/backends.py +0 -0
  25. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/cache/key.py +0 -0
  26. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/cache/manager.py +0 -0
  27. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/client/__init__.py +0 -0
  28. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/client/builder.py +0 -0
  29. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/client/cancel.py +0 -0
  30. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/client/core.py +0 -0
  31. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/client/response.py +0 -0
  32. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/embeddings/__init__.py +0 -0
  33. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/embeddings/client.py +0 -0
  34. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/embeddings/types.py +0 -0
  35. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/embeddings/vectors.py +0 -0
  36. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/errors/__init__.py +0 -0
  37. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/errors/base.py +0 -0
  38. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/errors/classification.py +0 -0
  39. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/errors/standard_codes.py +0 -0
  40. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/guardrails/__init__.py +0 -0
  41. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/guardrails/base.py +0 -0
  42. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/guardrails/filters.py +0 -0
  43. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/guardrails/validators.py +0 -0
  44. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/pipeline/__init__.py +0 -0
  45. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/pipeline/accumulate.py +0 -0
  46. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/pipeline/base.py +0 -0
  47. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/pipeline/decode.py +0 -0
  48. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/pipeline/event_map.py +0 -0
  49. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/pipeline/fan_out.py +0 -0
  50. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/pipeline/select.py +0 -0
  51. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/plugins/__init__.py +0 -0
  52. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/plugins/base.py +0 -0
  53. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/plugins/hooks.py +0 -0
  54. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/plugins/middleware.py +0 -0
  55. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/plugins/registry.py +0 -0
  56. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/protocol/__init__.py +0 -0
  57. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/protocol/loader.py +0 -0
  58. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/protocol/manifest.py +0 -0
  59. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/protocol/validator.py +0 -0
  60. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/py.typed +0 -0
  61. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/__init__.py +0 -0
  62. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/backpressure.py +0 -0
  63. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/circuit_breaker.py +0 -0
  64. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/executor.py +0 -0
  65. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/fallback.py +0 -0
  66. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/preflight.py +0 -0
  67. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/rate_limiter.py +0 -0
  68. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/retry.py +0 -0
  69. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/resilience/signals.py +0 -0
  70. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/routing/__init__.py +0 -0
  71. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/routing/manager.py +0 -0
  72. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/routing/strategy.py +0 -0
  73. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/routing/types.py +0 -0
  74. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/structured/__init__.py +0 -0
  75. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/structured/json_mode.py +0 -0
  76. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/structured/schema.py +0 -0
  77. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/structured/validator.py +0 -0
  78. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/telemetry/__init__.py +0 -0
  79. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/telemetry/exporters/__init__.py +0 -0
  80. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/telemetry/exporters/prometheus.py +0 -0
  81. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/telemetry/feedback.py +0 -0
  82. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/telemetry/health.py +0 -0
  83. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/telemetry/logger.py +0 -0
  84. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/telemetry/metrics.py +0 -0
  85. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/telemetry/tracer.py +0 -0
  86. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/tokens/__init__.py +0 -0
  87. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/tokens/counter.py +0 -0
  88. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/tokens/estimator.py +0 -0
  89. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/transport/__init__.py +0 -0
  90. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/transport/auth.py +0 -0
  91. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/transport/http.py +0 -0
  92. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/transport/pool.py +0 -0
  93. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/types/__init__.py +0 -0
  94. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/types/events.py +0 -0
  95. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/types/message.py +0 -0
  96. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/types/tool.py +0 -0
  97. {ai_lib_python-0.6.0 → ai_lib_python-0.7.1}/src/ai_lib_python/utils/__init__.py +0 -0
  98. {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.6.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.6.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"
@@ -27,7 +27,7 @@ from ai_lib_python.types.message import (
27
27
  )
28
28
  from ai_lib_python.types.tool import ToolCall, ToolDefinition
29
29
 
30
- __version__ = "0.6.0"
30
+ __version__ = "0.7.0"
31
31
 
32
32
  __all__ = [
33
33
  # Client
@@ -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
+ ]