ai-dev-workflow 0.1.0__py3-none-any.whl

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_provider.py ADDED
@@ -0,0 +1,149 @@
1
+ """Provider-neutral AI provider configuration and validation."""
2
+ from __future__ import annotations
3
+
4
+ from urllib.parse import urlparse
5
+
6
+ PROVIDER_TYPES = {
7
+ "interactive",
8
+ "openai",
9
+ "chatgpt-codex",
10
+ "anthropic",
11
+ "litellm",
12
+ "openai-compatible",
13
+ "local-openai-compatible",
14
+ }
15
+
16
+ AUTH_TYPES = {"none", "chatgpt-oauth", "api-key-env", "bearer-env"}
17
+
18
+ DEFAULT_CAPABILITIES = {
19
+ "interactive": ["planning", "review"],
20
+ "openai": ["planning", "test", "implementation", "review"],
21
+ "chatgpt-codex": ["planning", "test", "implementation", "review"],
22
+ "anthropic": ["planning", "test", "implementation", "review"],
23
+ "litellm": ["planning", "test", "implementation", "review"],
24
+ "openai-compatible": ["planning", "test", "implementation", "review"],
25
+ "local-openai-compatible": ["planning", "test", "implementation", "review"],
26
+ }
27
+
28
+
29
+ def provider_template(
30
+ provider_type: str,
31
+ *,
32
+ model: str | None = None,
33
+ base_url: str | None = None,
34
+ auth_type: str | None = None,
35
+ env: str | None = None,
36
+ capabilities: list[str] | None = None,
37
+ ) -> dict:
38
+ if provider_type not in PROVIDER_TYPES:
39
+ raise ValueError(f"unsupported provider type: {provider_type}")
40
+
41
+ if auth_type is None:
42
+ auth_type = {
43
+ "interactive": "none",
44
+ "chatgpt-codex": "chatgpt-oauth",
45
+ "local-openai-compatible": "none",
46
+ "openai": "api-key-env",
47
+ "anthropic": "api-key-env",
48
+ "litellm": "api-key-env",
49
+ "openai-compatible": "api-key-env",
50
+ }[provider_type]
51
+
52
+ auth = {"type": auth_type}
53
+ if env:
54
+ auth["env"] = env
55
+
56
+ result = {
57
+ "type": provider_type,
58
+ "auth": auth,
59
+ "capabilities": capabilities or list(DEFAULT_CAPABILITIES[provider_type]),
60
+ }
61
+ if model:
62
+ result["model"] = model
63
+ if base_url:
64
+ result["base_url"] = base_url
65
+ return result
66
+
67
+
68
+ def _is_http_url(value: str) -> bool:
69
+ try:
70
+ parsed = urlparse(value)
71
+ return parsed.scheme in ("http", "https") and bool(parsed.netloc)
72
+ except Exception:
73
+ return False
74
+
75
+
76
+ def validate_provider(provider_id: str, config: dict) -> list[str]:
77
+ errors = []
78
+ provider_type = config.get("type")
79
+ if provider_type not in PROVIDER_TYPES:
80
+ return [f"provider {provider_id}: unsupported type {provider_type!r}"]
81
+
82
+ auth = config.get("auth") or {}
83
+ auth_type = auth.get("type")
84
+ if auth_type not in AUTH_TYPES:
85
+ errors.append(f"provider {provider_id}: unsupported auth type {auth_type!r}")
86
+ if auth_type in ("api-key-env", "bearer-env") and not auth.get("env"):
87
+ errors.append(f"provider {provider_id}: auth env is required for {auth_type}")
88
+
89
+ if provider_type in ("litellm", "openai-compatible", "local-openai-compatible"):
90
+ base_url = config.get("base_url")
91
+ if not base_url or not _is_http_url(base_url):
92
+ errors.append(f"provider {provider_id}: valid base_url is required")
93
+
94
+ capabilities = config.get("capabilities")
95
+ if not isinstance(capabilities, list) or not capabilities:
96
+ errors.append(f"provider {provider_id}: at least one capability is required")
97
+
98
+ return errors
99
+
100
+
101
+ def validate_provider_registry(providers: dict, agents: dict) -> list[str]:
102
+ errors = []
103
+ for provider_id, cfg in providers.items():
104
+ if not isinstance(cfg, dict):
105
+ errors.append(f"provider {provider_id}: config must be an object")
106
+ continue
107
+ errors.extend(validate_provider(provider_id, cfg))
108
+
109
+ for agent_key, agent in agents.items():
110
+ if not isinstance(agent, dict):
111
+ continue
112
+ provider_id = agent.get("provider")
113
+ if provider_id in (None, "none", "project-defined"):
114
+ continue
115
+ if provider_id not in providers:
116
+ errors.append(f"agent {agent_key}: unknown provider {provider_id}")
117
+
118
+ return errors
119
+
120
+
121
+ def credential_requirements_for_providers(providers: dict) -> list[dict]:
122
+ result = []
123
+ seen = set()
124
+ for provider_id, cfg in providers.items():
125
+ auth = cfg.get("auth") or {}
126
+ auth_type = auth.get("type")
127
+ key = (provider_id, auth_type, auth.get("env"))
128
+ if key in seen:
129
+ continue
130
+ seen.add(key)
131
+
132
+ if auth_type == "none":
133
+ continue
134
+ if auth_type == "chatgpt-oauth":
135
+ result.append({
136
+ "name": f"{provider_id} ChatGPT sign-in",
137
+ "purpose": "Authenticate this provider using interactive ChatGPT OAuth.",
138
+ "required_for_setup": False,
139
+ "store": "provider/client auth store; never commit tokens",
140
+ })
141
+ elif auth_type in ("api-key-env", "bearer-env"):
142
+ env = auth.get("env")
143
+ result.append({
144
+ "name": env or f"{provider_id} credential",
145
+ "purpose": f"Authenticate AI provider '{provider_id}'.",
146
+ "required_for_setup": False,
147
+ "store": "environment/secret store; never commit",
148
+ })
149
+ return result