deepparallel 0.5.0__tar.gz → 0.5.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.
- {deepparallel-0.5.0 → deepparallel-0.5.1}/PKG-INFO +1 -1
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/__init__.py +1 -1
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/backend.py +137 -37
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/cli.py +7 -5
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/config.py +28 -4
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/serve.py +27 -5
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel.egg-info/PKG-INFO +1 -1
- {deepparallel-0.5.0 → deepparallel-0.5.1}/pyproject.toml +1 -1
- {deepparallel-0.5.0 → deepparallel-0.5.1}/README.md +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/agent.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/branding.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/crowe_id.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/dsml.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/fusion.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/licensing.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/registry.json +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/renderer.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/research/__init__.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/research/conduit.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/research/provider.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/routing.example.json +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/routing.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/supply_chain.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/system_prompt.txt +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/tools/__init__.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/tools/codeast.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/tools/edit.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/tools/files.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/tools/mcp.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/tools/registry.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/tools/sandbox.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/tools/search.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/tools/shell.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/tools/vision.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/tools/web.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel/userinput.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel.egg-info/SOURCES.txt +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel.egg-info/dependency_links.txt +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel.egg-info/entry_points.txt +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel.egg-info/requires.txt +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/deepparallel.egg-info/top_level.txt +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/setup.cfg +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_agent.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_backend.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_backend_chat.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_backend_stream.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_branding.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_cli.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_config.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_crowe_backend.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_crowe_gateway_backend.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_crowe_id_auth.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_crowe_payment_required.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_dsml.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_fusion.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_issuer_signer.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_licensing.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_renderer.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_research.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_research_provider.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_routing.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_serve.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_spinner_color.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_supply_chain.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_tool_registry.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_tools_codeast.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_tools_edit.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_tools_files.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_tools_mcp.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_tools_sandbox.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_tools_search.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_tools_shell.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_tools_vision.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_tools_web.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_userinput.py +0 -0
- {deepparallel-0.5.0 → deepparallel-0.5.1}/tests/test_userinput_paste.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepparallel
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: DeepParallel - a multi-model agentic coding CLI with cross-model Guardian review, served via Crowe Logic.
|
|
5
5
|
Author-email: Michael Crowe <michael@crowelogic.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -11,6 +11,8 @@ stream_chat yields (channel, text) tuples where channel is "content" or
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
import json
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
14
16
|
from typing import Iterator, Protocol
|
|
15
17
|
from urllib.parse import urlparse
|
|
16
18
|
|
|
@@ -142,6 +144,24 @@ class Backend(Protocol):
|
|
|
142
144
|
) -> Iterator[Chunk]: ...
|
|
143
145
|
|
|
144
146
|
|
|
147
|
+
def _should_failover(exc: Exception) -> bool:
|
|
148
|
+
"""Fail over to direct Azure only on transport errors or upstream 5xx;
|
|
149
|
+
a 4xx means the request itself is bad, so retrying elsewhere is pointless."""
|
|
150
|
+
if isinstance(exc, httpx.TransportError):
|
|
151
|
+
return True
|
|
152
|
+
if isinstance(exc, httpx.HTTPStatusError):
|
|
153
|
+
return exc.response.status_code >= 500
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _log_failover(label: str, exc: Exception) -> None:
|
|
158
|
+
sys.stderr.write(
|
|
159
|
+
f"[deepparallel] {label}: primary endpoint failed "
|
|
160
|
+
f"({exc.__class__.__name__}); failing over to direct Azure\n"
|
|
161
|
+
)
|
|
162
|
+
sys.stderr.flush()
|
|
163
|
+
|
|
164
|
+
|
|
145
165
|
class AzureBackend:
|
|
146
166
|
label = "Azure OpenAI"
|
|
147
167
|
|
|
@@ -151,13 +171,56 @@ class AzureBackend:
|
|
|
151
171
|
self._deployment = deployment
|
|
152
172
|
self._api_version = api_version
|
|
153
173
|
|
|
154
|
-
|
|
155
|
-
|
|
174
|
+
def _build_url(self, endpoint: str) -> str:
|
|
175
|
+
# Cloudflare AI Gateway azure-openai routes carry the resource in the
|
|
176
|
+
# path and drop the native "/openai/deployments" segment; native Azure
|
|
177
|
+
# endpoints keep it.
|
|
178
|
+
endpoint = endpoint.rstrip("/")
|
|
179
|
+
if "/azure-openai/" in endpoint:
|
|
180
|
+
return (
|
|
181
|
+
f"{endpoint}/{self._deployment}"
|
|
182
|
+
f"/chat/completions?api-version={self._api_version}"
|
|
183
|
+
)
|
|
156
184
|
return (
|
|
157
|
-
f"{
|
|
185
|
+
f"{endpoint}/openai/deployments/{self._deployment}"
|
|
158
186
|
f"/chat/completions?api-version={self._api_version}"
|
|
159
187
|
)
|
|
160
188
|
|
|
189
|
+
def _endpoints(self) -> list[str]:
|
|
190
|
+
# Primary is whatever is configured (typically the Cloudflare AI Gateway
|
|
191
|
+
# route). When that primary is a gateway route, derive the direct Azure
|
|
192
|
+
# endpoint from its resource segment and append it as automatic failover:
|
|
193
|
+
# fail-open, so a gateway outage degrades to direct Azure instead of
|
|
194
|
+
# taking down every CroweLM request. The failover request is not logged
|
|
195
|
+
# by the gateway -- the acceptable cost of staying available.
|
|
196
|
+
eps = [self._endpoint]
|
|
197
|
+
marker = "/azure-openai/"
|
|
198
|
+
if marker in self._endpoint:
|
|
199
|
+
resource = self._endpoint.split(marker, 1)[1].split("/", 1)[0]
|
|
200
|
+
if resource:
|
|
201
|
+
eps.append(f"https://{resource}.cognitiveservices.azure.com")
|
|
202
|
+
return eps
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def _url(self) -> str:
|
|
206
|
+
return self._build_url(self._endpoint)
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def _headers(self) -> dict:
|
|
210
|
+
# cf-aig-* headers configure AI Gateway per-request (no management API
|
|
211
|
+
# needed). Cache TTL is operationally tunable via DEEPPARALLEL_CACHE_TTL
|
|
212
|
+
# (seconds; "0" or empty disables caching for this agentic workload).
|
|
213
|
+
# Ignored by direct Azure on the failover path.
|
|
214
|
+
headers = {
|
|
215
|
+
"api-key": self._api_key,
|
|
216
|
+
"content-type": "application/json",
|
|
217
|
+
}
|
|
218
|
+
ttl = os.getenv("DEEPPARALLEL_CACHE_TTL", "300").strip()
|
|
219
|
+
if ttl and ttl != "0":
|
|
220
|
+
headers["cf-aig-cache-ttl"] = ttl
|
|
221
|
+
headers["cf-aig-metadata"] = '{"via":"deepparallel-gateway"}'
|
|
222
|
+
return headers
|
|
223
|
+
|
|
161
224
|
def check(self) -> tuple[bool, str]:
|
|
162
225
|
if not self._endpoint or not self._api_key:
|
|
163
226
|
return False, "Azure endpoint or API key not configured."
|
|
@@ -167,49 +230,86 @@ class AzureBackend:
|
|
|
167
230
|
return False, f"Azure endpoint unreachable ({e.__class__.__name__})"
|
|
168
231
|
return True, f"Azure @ {_host(self._endpoint)}"
|
|
169
232
|
|
|
233
|
+
def _payload(self, messages, stream, temperature, max_tokens) -> dict:
|
|
234
|
+
# GPT-5 family deployments require `max_completion_tokens` and reject a
|
|
235
|
+
# custom `temperature` (only the default is accepted) -> they 400 on the
|
|
236
|
+
# legacy `max_tokens`/`temperature` shape. Everything else uses the
|
|
237
|
+
# classic params.
|
|
238
|
+
payload = {"messages": messages, "stream": stream}
|
|
239
|
+
dep = self._deployment.lower()
|
|
240
|
+
if dep.startswith("gpt-5") or dep.startswith("gpt-chat"):
|
|
241
|
+
# GPT-5 family + gpt-chat-latest require max_completion_tokens and
|
|
242
|
+
# reject a custom temperature (only the default is accepted).
|
|
243
|
+
payload["max_completion_tokens"] = max_tokens
|
|
244
|
+
else:
|
|
245
|
+
payload["temperature"] = temperature
|
|
246
|
+
payload["max_tokens"] = max_tokens
|
|
247
|
+
return payload
|
|
248
|
+
|
|
170
249
|
def stream_chat(self, messages, temperature, max_tokens):
|
|
171
|
-
payload =
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
250
|
+
payload = self._payload(messages, True, temperature, max_tokens)
|
|
251
|
+
urls = [self._build_url(e) for e in self._endpoints()]
|
|
252
|
+
for i, url in enumerate(urls):
|
|
253
|
+
last = i == len(urls) - 1
|
|
254
|
+
started = False
|
|
255
|
+
try:
|
|
256
|
+
with httpx.stream(
|
|
257
|
+
"POST", url, json=payload, headers=self._headers, timeout=_STREAM_TIMEOUT
|
|
258
|
+
) as r:
|
|
259
|
+
r.raise_for_status()
|
|
260
|
+
for chunk in parse_sse_lines(r.iter_lines()):
|
|
261
|
+
started = True
|
|
262
|
+
yield chunk
|
|
263
|
+
return
|
|
264
|
+
except (httpx.TransportError, httpx.HTTPStatusError) as e:
|
|
265
|
+
if last or started or not _should_failover(e):
|
|
266
|
+
raise
|
|
267
|
+
_log_failover(self.label, e)
|
|
268
|
+
continue
|
|
183
269
|
|
|
184
270
|
def chat(self, messages, tools, temperature, max_tokens) -> dict:
|
|
185
|
-
payload =
|
|
186
|
-
"messages": messages,
|
|
187
|
-
"stream": False,
|
|
188
|
-
"temperature": temperature,
|
|
189
|
-
"max_tokens": max_tokens,
|
|
190
|
-
}
|
|
271
|
+
payload = self._payload(messages, False, temperature, max_tokens)
|
|
191
272
|
if tools:
|
|
192
273
|
payload["tools"] = tools
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
274
|
+
urls = [self._build_url(e) for e in self._endpoints()]
|
|
275
|
+
for i, url in enumerate(urls):
|
|
276
|
+
last = i == len(urls) - 1
|
|
277
|
+
try:
|
|
278
|
+
r = httpx.post(url, json=payload, headers=self._headers, timeout=_STREAM_TIMEOUT)
|
|
279
|
+
r.raise_for_status()
|
|
280
|
+
return _message_from_choice(r.json()["choices"][0])
|
|
281
|
+
except (httpx.TransportError, httpx.HTTPStatusError) as e:
|
|
282
|
+
if last or not _should_failover(e):
|
|
283
|
+
raise
|
|
284
|
+
_log_failover(self.label, e)
|
|
285
|
+
continue
|
|
197
286
|
|
|
198
287
|
def stream_chat_tools(self, messages, tools, temperature, max_tokens):
|
|
199
|
-
payload =
|
|
200
|
-
"messages": messages,
|
|
201
|
-
"stream": True,
|
|
202
|
-
"temperature": temperature,
|
|
203
|
-
"max_tokens": max_tokens,
|
|
204
|
-
}
|
|
288
|
+
payload = self._payload(messages, True, temperature, max_tokens)
|
|
205
289
|
if tools:
|
|
206
290
|
payload["tools"] = tools
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
291
|
+
urls = [self._build_url(e) for e in self._endpoints()]
|
|
292
|
+
for i, url in enumerate(urls):
|
|
293
|
+
last = i == len(urls) - 1
|
|
294
|
+
started = False
|
|
295
|
+
try:
|
|
296
|
+
with httpx.stream(
|
|
297
|
+
"POST", url, json=payload, headers=self._headers, timeout=_STREAM_TIMEOUT
|
|
298
|
+
) as r:
|
|
299
|
+
r.raise_for_status()
|
|
300
|
+
gen = parse_sse_stream(r.iter_lines())
|
|
301
|
+
while True:
|
|
302
|
+
try:
|
|
303
|
+
chunk = next(gen)
|
|
304
|
+
except StopIteration as stop:
|
|
305
|
+
return stop.value
|
|
306
|
+
started = True
|
|
307
|
+
yield chunk
|
|
308
|
+
except (httpx.TransportError, httpx.HTTPStatusError) as e:
|
|
309
|
+
if last or started or not _should_failover(e):
|
|
310
|
+
raise
|
|
311
|
+
_log_failover(self.label, e)
|
|
312
|
+
continue
|
|
213
313
|
|
|
214
314
|
|
|
215
315
|
class FoundryBackend:
|
|
@@ -495,13 +495,15 @@ def review(ctx: click.Context, as_diff: bool, path: str | None) -> None:
|
|
|
495
495
|
|
|
496
496
|
Reviews a file (PATH) or a unified diff (--diff, from stdin) with a second
|
|
497
497
|
model and prints a verdict. Exit code: 0 safe, 1 risky, 2 bug - so it can
|
|
498
|
-
gate a commit or PR.
|
|
498
|
+
gate a commit or PR. Free with your own key (DEEPPARALLEL_BACKEND=openai or
|
|
499
|
+
ollama); Pro unlocks the hosted Crowe Logic model stack.
|
|
499
500
|
"""
|
|
500
501
|
settings: Settings = ctx.obj["settings"]
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
502
|
+
if not settings.byok:
|
|
503
|
+
ok, msg = licensing.check_feature("review")
|
|
504
|
+
if not ok:
|
|
505
|
+
branding.error(msg)
|
|
506
|
+
sys.exit(3)
|
|
505
507
|
if as_diff:
|
|
506
508
|
content = sys.stdin.read()
|
|
507
509
|
elif path:
|
|
@@ -45,6 +45,10 @@ class Settings:
|
|
|
45
45
|
mycelium_key: str | None = None
|
|
46
46
|
mycelium_secret: str | None = None
|
|
47
47
|
mycelium_model: str = "Mcrowe1210/gemma-4-mycelium-e4b"
|
|
48
|
+
# True when the user brings their own key/model (openai/ollama/foundry):
|
|
49
|
+
# the FREE tier. review + the agent run unlicensed; only the Crowe-hosted
|
|
50
|
+
# stack (azure/crowe) is gated to Pro.
|
|
51
|
+
byok: bool = False
|
|
48
52
|
# Crowe ID agent identity (backend="crowe"): route through the Foundry gateway
|
|
49
53
|
# authenticated by a client_credentials token instead of a raw provider key.
|
|
50
54
|
gateway_url: str | None = None
|
|
@@ -102,17 +106,36 @@ def _int_env(name: str, default: int) -> int:
|
|
|
102
106
|
|
|
103
107
|
def resolve_settings() -> Settings:
|
|
104
108
|
backend = os.environ.get("DEEPPARALLEL_BACKEND", "azure").strip().lower()
|
|
105
|
-
if backend not in {"azure", "foundry", "crowe"}:
|
|
109
|
+
if backend not in {"azure", "foundry", "crowe", "openai", "ollama"}:
|
|
106
110
|
backend = "azure"
|
|
111
|
+
|
|
112
|
+
# BYOK (bring-your-own-key) backends are the FREE tier. "openai" and "ollama"
|
|
113
|
+
# are friendly aliases onto the OpenAI-compatible foundry transport with
|
|
114
|
+
# sensible defaults, so `dp` works with no Crowe account and no license.
|
|
115
|
+
foundry_base_url = os.environ.get("FOUNDRY_BASE_URL")
|
|
116
|
+
foundry_api_key = os.environ.get("FOUNDRY_API_KEY")
|
|
117
|
+
foundry_model = os.environ.get("DEEPPARALLEL_FOUNDRY_MODEL", "DeepSeek-V3-1")
|
|
118
|
+
if backend == "openai":
|
|
119
|
+
foundry_base_url = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com")
|
|
120
|
+
foundry_api_key = os.environ.get("OPENAI_API_KEY") or foundry_api_key
|
|
121
|
+
foundry_model = os.environ.get("DEEPPARALLEL_MODEL", "gpt-4o-mini")
|
|
122
|
+
backend = "foundry"
|
|
123
|
+
elif backend == "ollama":
|
|
124
|
+
foundry_base_url = os.environ.get("OLLAMA_HOST", "http://localhost:11434")
|
|
125
|
+
foundry_api_key = foundry_api_key or "ollama"
|
|
126
|
+
foundry_model = os.environ.get("DEEPPARALLEL_MODEL", "llama3.1")
|
|
127
|
+
backend = "foundry"
|
|
128
|
+
byok = backend == "foundry"
|
|
129
|
+
|
|
107
130
|
return Settings(
|
|
108
131
|
backend=backend,
|
|
109
132
|
azure_endpoint=os.environ.get("AZURE_CORE_ENDPOINT"),
|
|
110
133
|
azure_api_key=os.environ.get("AZURE_CORE_API_KEY"),
|
|
111
134
|
deployment=os.environ.get("DEEPPARALLEL_DEPLOYMENT", "DeepSeek-V4-Pro"),
|
|
112
135
|
api_version=os.environ.get("DEEPPARALLEL_API_VERSION", "2024-08-01-preview"),
|
|
113
|
-
foundry_base_url=
|
|
114
|
-
foundry_api_key=
|
|
115
|
-
foundry_model=
|
|
136
|
+
foundry_base_url=foundry_base_url,
|
|
137
|
+
foundry_api_key=foundry_api_key,
|
|
138
|
+
foundry_model=foundry_model,
|
|
116
139
|
temperature=_float_env("DEEPPARALLEL_TEMPERATURE", 0.4),
|
|
117
140
|
max_tokens=_int_env("DEEPPARALLEL_MAX_TOKENS", 8192),
|
|
118
141
|
show_thinking=_bool_env("DEEPPARALLEL_THINK", False),
|
|
@@ -149,6 +172,7 @@ def resolve_settings() -> Settings:
|
|
|
149
172
|
crowe_id_client_id=os.environ.get("CROWE_ID_CLIENT_ID"),
|
|
150
173
|
crowe_id_client_secret=os.environ.get("CROWE_ID_CLIENT_SECRET"),
|
|
151
174
|
crowe_id_audience=os.environ.get("CROWE_ID_AUDIENCE"),
|
|
175
|
+
byok=byok,
|
|
152
176
|
)
|
|
153
177
|
|
|
154
178
|
|
|
@@ -51,16 +51,35 @@ _SCRUB: list[tuple[re.Pattern[str], str]] = [
|
|
|
51
51
|
# The listing must never leak raw deployment names; chat accepts either the
|
|
52
52
|
# alias or the raw name, so existing callers keep working.
|
|
53
53
|
_MODEL_ALIASES: dict[str, str] = {
|
|
54
|
+
# DeepSeek family
|
|
54
55
|
"crowelm-apex": "DeepSeek-V4-Pro",
|
|
55
56
|
"crowelm-reason": "DeepSeek-R1-0528",
|
|
56
57
|
"crowelm-flash": "DeepSeek-V4-Flash",
|
|
57
|
-
"crowelm-vector": "DeepSeek-V3
|
|
58
|
+
"crowelm-vector": "DeepSeek-V3-1",
|
|
59
|
+
# GPT family (Azure frontier)
|
|
60
|
+
"crowelm-zenith": "gpt-5.5",
|
|
61
|
+
# --- gpt-5.4 family back-burnered (de-prioritized) — uncomment to re-enable ---
|
|
62
|
+
# "crowelm-prime": "gpt-5.4",
|
|
63
|
+
# "crowelm-prime-mini": "gpt-5.4-mini",
|
|
64
|
+
# "crowelm-prime-nano": "gpt-5.4-nano",
|
|
65
|
+
"crowelm-chat": "gpt-chat-latest",
|
|
66
|
+
"crowelm-swift": "gpt-4o",
|
|
67
|
+
# Grok family
|
|
58
68
|
"crowelm-quasar": "grok-4-3",
|
|
59
69
|
"crowelm-quasar-fast": "grok-4-1-fast-reasoning",
|
|
60
|
-
"crowelm-
|
|
70
|
+
"crowelm-quasar-lite": "grok-4-1-fast-non-r",
|
|
71
|
+
"crowelm-quasar-max": "grok-4-20-reasoning",
|
|
72
|
+
# Kimi family
|
|
61
73
|
"crowelm-eclipse": "Kimi-K2-6",
|
|
74
|
+
"crowelm-eclipse-lite": "Kimi-K2.5",
|
|
75
|
+
# Llama family
|
|
62
76
|
"crowelm-titan": "Llama-3-3-70B",
|
|
63
|
-
"crowelm-
|
|
77
|
+
"crowelm-maverick": "Llama-4-Maverick",
|
|
78
|
+
"crowelm-scout": "Llama-4-Scout",
|
|
79
|
+
# Specialist tiers
|
|
80
|
+
"crowelm-herald": "Cohere-Command-A",
|
|
81
|
+
"crowelm-forge": "Codestral-2501",
|
|
82
|
+
"crowelm-router": "model-router",
|
|
64
83
|
# Free base tier: the sovereign Gemma 4 Mycelium model on Modal. Only listed
|
|
65
84
|
# in /v1/models when MODAL_MYCELIUM_ENDPOINT is configured (see below).
|
|
66
85
|
"crowelm-mycelium": "Mcrowe1210/gemma-4-mycelium-e4b",
|
|
@@ -174,8 +193,11 @@ def _models_payload(settings) -> dict:
|
|
|
174
193
|
# this gateway process. Never list raw deployment names; chat still accepts
|
|
175
194
|
# them for existing callers, but discovery should not advertise unknown
|
|
176
195
|
# deployments.
|
|
177
|
-
|
|
178
|
-
|
|
196
|
+
ids = [
|
|
197
|
+
alias
|
|
198
|
+
for alias, deployment in _MODEL_ALIASES.items()
|
|
199
|
+
if _deployment_available(deployment, settings)
|
|
200
|
+
]
|
|
179
201
|
created = int(time.time())
|
|
180
202
|
return {
|
|
181
203
|
"object": "list",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepparallel
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: DeepParallel - a multi-model agentic coding CLI with cross-model Guardian review, served via Crowe Logic.
|
|
5
5
|
Author-email: Michael Crowe <michael@crowelogic.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "deepparallel"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.1"
|
|
8
8
|
description = "DeepParallel - a multi-model agentic coding CLI with cross-model Guardian review, served via Crowe Logic."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|