dispatch_agents 0.11.0__tar.gz → 0.12.2__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.
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/PKG-INFO +1 -1
- dispatch_agents-0.12.2/RELEASE_NOTES.md +1 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/config.py +114 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/integrations/github/client.py +4 -1
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/llm.py +11 -1
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/proxy/server.py +2 -2
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/hello_world/agent.py +50 -1
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/pyproject.toml +1 -1
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_config.py +207 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_github_client.py +1 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_proxy_server.py +8 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/uv.lock +7 -7
- dispatch_agents-0.11.0/RELEASE_NOTES.md +0 -2
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/.claude-plugin/marketplace.json +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/.github/scripts/change_scope.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/.github/scripts/ci_git.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/.github/scripts/version_policy.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/.github/tests/test_change_scope.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/.github/tests/test_ci_git.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/.github/tests/test_version_policy.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/.github/workflows/ci-reusable.yml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/.github/workflows/feature-branch.yml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/.github/workflows/release.yml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/.github/workflows/version-policy-reusable.yml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/.gitignore +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/CONTRIBUTING.md +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/LICENSE +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/LICENSE-3rdparty.csv +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/NOTICE +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/README.md +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/agentservice/__init__.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/agentservice/py.typed +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/agentservice/v1/__init__.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/agentservice/v1/message_pb2.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/agentservice/v1/message_pb2.pyi +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/agentservice/v1/message_pb2_grpc.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/agentservice/v1/request_response_pb2.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/agentservice/v1/request_response_pb2.pyi +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/agentservice/v1/request_response_pb2_grpc.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/agentservice/v1/service_pb2.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/agentservice/v1/service_pb2.pyi +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/agentservice/v1/service_pb2_grpc.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/__init__.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/agent_service.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/contrib/__init__.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/contrib/claude/__init__.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/contrib/openai/__init__.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/events.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/grpc_server.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/instrument.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/integrations/__init__.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/integrations/github/README.md +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/integrations/github/__init__.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/invocation.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/llm_langchain.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/logging_config.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/mcp.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/memory.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/models.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/proxy/__init__.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/proxy/sse_utils.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/py.typed +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/resources.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/version.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/README.md +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/company-researcher/.gitignore +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/company-researcher/AGENTS.md +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/company-researcher/README.md +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/company-researcher/agent.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/company-researcher/dispatch.yaml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/company-researcher/pyproject.toml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/company-researcher/uv.lock +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/conversational-agent/README.md +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/conversational-agent/agent.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/conversational-agent/dispatch.yaml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/conversational-agent/pyproject.toml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/daily-digest/README.md +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/daily-digest/agent.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/daily-digest/dispatch.yaml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/daily-digest/pyproject.toml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/deep-research/README.md +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/deep-research/agent.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/deep-research/configuration.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/deep-research/deep_researcher.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/deep-research/dispatch.yaml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/deep-research/prompts.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/deep-research/pyproject.toml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/deep-research/state.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/deep-research/tools.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/hello_world/.gitignore +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/hello_world/AGENTS.md +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/hello_world/dispatch.yaml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/hello_world/pyproject.toml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/hello_world/test_agent.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/hello_world/uv.lock +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/knowledge-base-query/.env.example +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/knowledge-base-query/.gitignore +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/knowledge-base-query/AGENTS.md +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/knowledge-base-query/agent.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/knowledge-base-query/dispatch.yaml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/knowledge-base-query/pyproject.toml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/multi-framework/README.md +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/multi-framework/agent.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/multi-framework/dispatch.yaml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/multi-framework/pyproject.toml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/pyproject.toml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/uv.lock +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/weather-assistant/.gitignore +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/weather-assistant/AGENTS.md +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/weather-assistant/agent.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/weather-assistant/dispatch.yaml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/weather-assistant/pyproject.toml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/weather-assistant/uv.lock +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/weather-service/.gitignore +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/weather-service/AGENTS.md +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/weather-service/agent.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/weather-service/dispatch.yaml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/weather-service/pyproject.toml +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/weather-service/uv.lock +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/internal/py.typed +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/plugins/README.md +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/__init__.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/e2e_claude_mcp_proxy.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/schemas/README.md +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/schemas/octokit-webhooks.json +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_agent_service.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_agent_uid.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_contrib_claude.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_contrib_openai.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_dev_mode_isolation.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_extra_headers.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_fn_decorator.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_github_integration.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_github_schema_compliance.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_grpc_server.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_init.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_instrument.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_llm_langchain.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_llm_logging.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_logging_config.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_mcp.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_memory.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_proxy_e2e.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_resources.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_sse_utils.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_trace_context.py +0 -0
- {dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/tests/test_typed_events.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
BREAKING CHANGE: Compatibility changes to remove LiteLLM from backend.
|
|
@@ -5,6 +5,7 @@ shared between CLI and backend.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
|
+
import re
|
|
8
9
|
from enum import StrEnum
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
@@ -25,6 +26,13 @@ RESERVED_ENV_VARS: frozenset[str] = frozenset(
|
|
|
25
26
|
}
|
|
26
27
|
)
|
|
27
28
|
|
|
29
|
+
# Domain validation pattern for egress allow list.
|
|
30
|
+
# Accepts exact FQDNs (api.openai.com) and wildcard prefixes (*.github.com).
|
|
31
|
+
# Rejects URLs with schemes/ports/paths, IP addresses, and bare wildcards.
|
|
32
|
+
_DOMAIN_PATTERN = re.compile(
|
|
33
|
+
r"^(\*\.)?([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$"
|
|
34
|
+
)
|
|
35
|
+
|
|
28
36
|
|
|
29
37
|
class VolumeMode(StrEnum):
|
|
30
38
|
"""Volume access mode for persistent storage.
|
|
@@ -322,6 +330,98 @@ class ResourceConfig(BaseModel):
|
|
|
322
330
|
)
|
|
323
331
|
|
|
324
332
|
|
|
333
|
+
class DomainSelector(BaseModel):
|
|
334
|
+
"""A single domain selector -- exactly one of match_name or match_pattern.
|
|
335
|
+
|
|
336
|
+
match_name is an exact FQDN (e.g. api.openai.com).
|
|
337
|
+
match_pattern is a wildcard prefix (e.g. *.github.com).
|
|
338
|
+
|
|
339
|
+
Serialises with camelCase aliases (matchName / matchPattern) to match the
|
|
340
|
+
downstream Cilium FQDN selector API.
|
|
341
|
+
"""
|
|
342
|
+
|
|
343
|
+
match_name: str | None = Field(
|
|
344
|
+
default=None,
|
|
345
|
+
description="Exact FQDN to allow. Must match the entire domain name exactly "
|
|
346
|
+
"(e.g. 'api.openai.com' matches only 'api.openai.com').",
|
|
347
|
+
)
|
|
348
|
+
match_pattern: str | None = Field(
|
|
349
|
+
default=None,
|
|
350
|
+
description="Wildcard pattern to allow. Uses '*.domain.com' syntax to match "
|
|
351
|
+
"any subdomain of the specified domain (e.g. '*.github.com' matches "
|
|
352
|
+
"'api.github.com' and 'raw.github.com' but not 'github.com' itself).",
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
@model_validator(mode="after")
|
|
356
|
+
def _validate_exactly_one_field(self) -> "DomainSelector":
|
|
357
|
+
if self.match_name and self.match_pattern:
|
|
358
|
+
raise ValueError(
|
|
359
|
+
"Exactly one of match_name or match_pattern must be set, not both"
|
|
360
|
+
)
|
|
361
|
+
if not self.match_name and not self.match_pattern:
|
|
362
|
+
raise ValueError("Exactly one of match_name or match_pattern must be set")
|
|
363
|
+
domain = self.match_name or self.match_pattern or ""
|
|
364
|
+
if not _DOMAIN_PATTERN.match(domain):
|
|
365
|
+
raise ValueError(
|
|
366
|
+
f"Invalid domain '{domain}'. "
|
|
367
|
+
"Must be an exact FQDN (e.g., api.openai.com) "
|
|
368
|
+
"or wildcard prefix (e.g., *.github.com). "
|
|
369
|
+
"URL schemes, ports, paths, IP addresses, "
|
|
370
|
+
"and bare wildcards are not allowed."
|
|
371
|
+
)
|
|
372
|
+
return self
|
|
373
|
+
|
|
374
|
+
model_config = {"extra": "forbid"}
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
class EgressConfig(BaseModel):
|
|
378
|
+
"""Configuration for network egress allow list.
|
|
379
|
+
|
|
380
|
+
Domains are specified as objects with either matchName (exact FQDN)
|
|
381
|
+
or matchPattern (wildcard prefix). This is a subset of the
|
|
382
|
+
downstream Cilium FQDN selector API.
|
|
383
|
+
|
|
384
|
+
Example:
|
|
385
|
+
network:
|
|
386
|
+
egress:
|
|
387
|
+
allow_domains:
|
|
388
|
+
- match_name: api.openai.com
|
|
389
|
+
- match_pattern: "*.github.com"
|
|
390
|
+
"""
|
|
391
|
+
|
|
392
|
+
allow_domains: list[DomainSelector] = Field(
|
|
393
|
+
default_factory=list,
|
|
394
|
+
description="Domains allowed for egress as Cilium FQDN selectors.",
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
@field_validator("allow_domains")
|
|
398
|
+
@classmethod
|
|
399
|
+
def _validate_allow_domains(cls, v: list[DomainSelector]) -> list[DomainSelector]:
|
|
400
|
+
if len(v) > 50:
|
|
401
|
+
raise ValueError(
|
|
402
|
+
f"allow_domains cannot have more than 50 entries, got {len(v)}"
|
|
403
|
+
)
|
|
404
|
+
return v
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class NetworkConfig(BaseModel):
|
|
408
|
+
"""Network configuration for an agent.
|
|
409
|
+
|
|
410
|
+
When present in dispatch.yaml, CiliumNetworkPolicies are created to
|
|
411
|
+
restrict the agent's outbound traffic to platform services and any
|
|
412
|
+
listed allow_domains. When absent, all egress is unrestricted.
|
|
413
|
+
|
|
414
|
+
Example:
|
|
415
|
+
network:
|
|
416
|
+
egress:
|
|
417
|
+
allow_domains:
|
|
418
|
+
- match_name: api.openai.com
|
|
419
|
+
- match_pattern: "*.github.com"
|
|
420
|
+
"""
|
|
421
|
+
|
|
422
|
+
egress: EgressConfig = Field(default_factory=EgressConfig)
|
|
423
|
+
|
|
424
|
+
|
|
325
425
|
class DispatchConfig(BaseModel):
|
|
326
426
|
"""Configuration model for dispatch.yaml files.
|
|
327
427
|
|
|
@@ -416,6 +516,10 @@ class DispatchConfig(BaseModel):
|
|
|
416
516
|
default_factory=ResourceConfig,
|
|
417
517
|
description="Container resource limits (CPU and memory)",
|
|
418
518
|
)
|
|
519
|
+
network: NetworkConfig | None = Field(
|
|
520
|
+
default=None,
|
|
521
|
+
description="Network egress restrictions. When set, CiliumNetworkPolicies restrict outbound traffic.",
|
|
522
|
+
)
|
|
419
523
|
|
|
420
524
|
@field_validator("env")
|
|
421
525
|
@classmethod
|
|
@@ -485,6 +589,16 @@ class DispatchConfig(BaseModel):
|
|
|
485
589
|
}
|
|
486
590
|
result["resources"] = {"limits": limits_dict}
|
|
487
591
|
|
|
592
|
+
if self.network is not None:
|
|
593
|
+
result["network"] = {
|
|
594
|
+
"egress": {
|
|
595
|
+
"allow_domains": [
|
|
596
|
+
d.model_dump(exclude_none=True)
|
|
597
|
+
for d in self.network.egress.allow_domains
|
|
598
|
+
],
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
488
602
|
return result
|
|
489
603
|
|
|
490
604
|
model_config = {"populate_by_name": True}
|
{dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/integrations/github/client.py
RENAMED
|
@@ -38,6 +38,7 @@ async def get_github_app_token() -> GitHubAppToken:
|
|
|
38
38
|
RuntimeError: If DISPATCH_API_KEY environment variable is not set.
|
|
39
39
|
RuntimeError: If no GitHub installation is configured for this org (404).
|
|
40
40
|
RuntimeError: If authentication fails — check DISPATCH_API_KEY (401).
|
|
41
|
+
RuntimeError: If access is forbidden (403).
|
|
41
42
|
RuntimeError: If the backend returns an unexpected HTTP status code.
|
|
42
43
|
|
|
43
44
|
Note:
|
|
@@ -76,7 +77,7 @@ async def get_github_app_token() -> GitHubAppToken:
|
|
|
76
77
|
if not os.getenv("DISPATCH_API_KEY"):
|
|
77
78
|
raise RuntimeError(
|
|
78
79
|
"DISPATCH_API_KEY environment variable is not set. "
|
|
79
|
-
"GitHub installation token requires
|
|
80
|
+
"GitHub installation token requires an authenticated Dispatch agent."
|
|
80
81
|
)
|
|
81
82
|
|
|
82
83
|
if _cached_token is not None:
|
|
@@ -96,6 +97,8 @@ async def get_github_app_token() -> GitHubAppToken:
|
|
|
96
97
|
"GitHub installation token request failed: unauthorized. "
|
|
97
98
|
"Check that DISPATCH_API_KEY is valid."
|
|
98
99
|
)
|
|
100
|
+
if response.status_code == 403:
|
|
101
|
+
raise RuntimeError("GitHub installation token request failed: forbidden.")
|
|
99
102
|
if response.status_code == 404:
|
|
100
103
|
raise RuntimeError(
|
|
101
104
|
"No GitHub installation found for this organization. "
|
|
@@ -470,7 +470,17 @@ class LLMClient:
|
|
|
470
470
|
headers=auth_headers,
|
|
471
471
|
timeout=600.0, # 10min — matches ALB idle timeout for long-context LLM calls
|
|
472
472
|
)
|
|
473
|
-
response.
|
|
473
|
+
if response.status_code >= 400:
|
|
474
|
+
try:
|
|
475
|
+
error_body = response.json()
|
|
476
|
+
detail = error_body.get("detail", response.text)
|
|
477
|
+
except Exception:
|
|
478
|
+
detail = response.text
|
|
479
|
+
raise httpx.HTTPStatusError(
|
|
480
|
+
f"LLM inference failed ({response.status_code}): {detail}",
|
|
481
|
+
request=response.request,
|
|
482
|
+
response=response,
|
|
483
|
+
)
|
|
474
484
|
data = response.json()
|
|
475
485
|
|
|
476
486
|
# Parse tool calls if present
|
|
@@ -764,7 +764,7 @@ async def _call_provider_directly_streaming(
|
|
|
764
764
|
) -> Response:
|
|
765
765
|
"""Call the LLM provider directly with streaming (fallback when backend has no config).
|
|
766
766
|
|
|
767
|
-
Uses raw httpx streaming
|
|
767
|
+
Uses raw httpx streaming.
|
|
768
768
|
Buffers SSE events for usage extraction and logs to /llm/log after stream completes.
|
|
769
769
|
"""
|
|
770
770
|
|
|
@@ -947,10 +947,10 @@ async def _proxy_passthrough(request: Request, provider_format: str) -> Response
|
|
|
947
947
|
)
|
|
948
948
|
|
|
949
949
|
# Build passthrough request for backend.
|
|
950
|
-
# provider_format is no longer sent; the backend derives it from path.
|
|
951
950
|
backend_payload: dict[str, Any] = {
|
|
952
951
|
"path": path,
|
|
953
952
|
"method": method,
|
|
953
|
+
"provider_format": provider_format,
|
|
954
954
|
}
|
|
955
955
|
if body_dict is not None:
|
|
956
956
|
backend_payload["body"] = body_dict
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
|
|
5
|
+
import aiohttp
|
|
5
6
|
import dispatch_agents
|
|
6
7
|
from dispatch_agents import BasePayload, fn, on
|
|
7
8
|
from dispatch_agents.integrations.github import PullRequestReviewCommentCreated
|
|
@@ -11,7 +12,7 @@ from pydantic import Field, PositiveInt
|
|
|
11
12
|
class GreetingPayload(BasePayload):
|
|
12
13
|
"""Input payload for greeting requests."""
|
|
13
14
|
|
|
14
|
-
subject: str = Field(description="The name or subject to greet")
|
|
15
|
+
subject: str = Field(default="World", description="The name or subject to greet")
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class GreetingResponse(BasePayload):
|
|
@@ -110,3 +111,51 @@ async def reverse(payload: ReverseRequest) -> ReverseResponse:
|
|
|
110
111
|
"""Reverse the provided text string."""
|
|
111
112
|
print(f"Reversing: {payload.text!r}")
|
|
112
113
|
return ReverseResponse(reversed_text=payload.text[::-1])
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class EgressTestRequest(BasePayload):
|
|
117
|
+
"""Input for the egress test function."""
|
|
118
|
+
|
|
119
|
+
url: str = Field(
|
|
120
|
+
default="https://jsonplaceholder.typicode.com/todos/1",
|
|
121
|
+
description="URL to attempt to fetch",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class EgressTestResponse(BasePayload):
|
|
126
|
+
"""Output of the egress test function."""
|
|
127
|
+
|
|
128
|
+
success: bool = Field(description="Whether the request succeeded")
|
|
129
|
+
status_code: int | None = Field(
|
|
130
|
+
default=None, description="HTTP status code if successful"
|
|
131
|
+
)
|
|
132
|
+
body: str = Field(default="", description="Response body or error message")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@fn()
|
|
136
|
+
async def test_egress(payload: EgressTestRequest) -> EgressTestResponse:
|
|
137
|
+
"""Test outbound HTTP connectivity by fetching a URL.
|
|
138
|
+
|
|
139
|
+
Useful for verifying network egress controls. When network.egress is
|
|
140
|
+
configured, this request will be blocked unless the target domain is
|
|
141
|
+
in allow_domains.
|
|
142
|
+
"""
|
|
143
|
+
print(f"Testing egress to: {payload.url}")
|
|
144
|
+
try:
|
|
145
|
+
async with aiohttp.ClientSession(
|
|
146
|
+
timeout=aiohttp.ClientTimeout(total=10)
|
|
147
|
+
) as session:
|
|
148
|
+
async with session.get(payload.url) as resp:
|
|
149
|
+
body = await resp.text()
|
|
150
|
+
print(f"Response: {resp.status} ({len(body)} bytes)")
|
|
151
|
+
return EgressTestResponse(
|
|
152
|
+
success=True,
|
|
153
|
+
status_code=resp.status,
|
|
154
|
+
body=body[:1000],
|
|
155
|
+
)
|
|
156
|
+
except Exception as e:
|
|
157
|
+
print(f"Request failed: {type(e).__name__}: {e}")
|
|
158
|
+
return EgressTestResponse(
|
|
159
|
+
success=False,
|
|
160
|
+
body=f"{type(e).__name__}: {e}",
|
|
161
|
+
)
|
|
@@ -4,7 +4,10 @@ import pytest
|
|
|
4
4
|
|
|
5
5
|
from dispatch_agents.config import (
|
|
6
6
|
DispatchConfig,
|
|
7
|
+
DomainSelector,
|
|
8
|
+
EgressConfig,
|
|
7
9
|
MCPServerConfig,
|
|
10
|
+
NetworkConfig,
|
|
8
11
|
ResourceConfig,
|
|
9
12
|
ResourceLimits,
|
|
10
13
|
SecretConfig,
|
|
@@ -510,3 +513,207 @@ class TestVolumeMode:
|
|
|
510
513
|
def test_string_comparison(self):
|
|
511
514
|
"""Should compare as string."""
|
|
512
515
|
assert VolumeMode.READ_WRITE_MANY == "read_write_many"
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
class TestDomainSelector:
|
|
519
|
+
"""Tests for DomainSelector model."""
|
|
520
|
+
|
|
521
|
+
def test_valid_match_name(self):
|
|
522
|
+
"""Should accept exact FQDN via matchName."""
|
|
523
|
+
ds = DomainSelector(match_name="api.openai.com")
|
|
524
|
+
assert ds.match_name == "api.openai.com"
|
|
525
|
+
assert ds.match_pattern is None
|
|
526
|
+
|
|
527
|
+
def test_valid_match_pattern(self):
|
|
528
|
+
"""Should accept wildcard via matchPattern."""
|
|
529
|
+
ds = DomainSelector(match_pattern="*.github.com")
|
|
530
|
+
assert ds.match_pattern == "*.github.com"
|
|
531
|
+
assert ds.match_name is None
|
|
532
|
+
|
|
533
|
+
def test_rejects_both_fields(self):
|
|
534
|
+
"""Should reject when both matchName and matchPattern are set."""
|
|
535
|
+
with pytest.raises(ValueError, match="not both"):
|
|
536
|
+
DomainSelector(match_name="api.openai.com", match_pattern="*.github.com")
|
|
537
|
+
|
|
538
|
+
def test_rejects_neither_field(self):
|
|
539
|
+
"""Should reject when neither field is set."""
|
|
540
|
+
with pytest.raises(ValueError, match="must be set"):
|
|
541
|
+
DomainSelector()
|
|
542
|
+
|
|
543
|
+
def test_rejects_invalid_match_name(self):
|
|
544
|
+
"""Should reject invalid domain in matchName."""
|
|
545
|
+
with pytest.raises(ValueError, match="Invalid domain"):
|
|
546
|
+
DomainSelector(match_name="https://api.openai.com")
|
|
547
|
+
|
|
548
|
+
def test_rejects_invalid_match_pattern(self):
|
|
549
|
+
"""Should reject invalid pattern in matchPattern."""
|
|
550
|
+
with pytest.raises(ValueError, match="Invalid domain"):
|
|
551
|
+
DomainSelector(match_pattern="*")
|
|
552
|
+
|
|
553
|
+
def test_model_dump_excludes_none(self):
|
|
554
|
+
"""Should produce clean dict when dumped with exclude_none."""
|
|
555
|
+
ds = DomainSelector(match_name="api.openai.com")
|
|
556
|
+
assert ds.model_dump(exclude_none=True) == {"match_name": "api.openai.com"}
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
class TestEgressConfig:
|
|
560
|
+
"""Tests for EgressConfig model and validators."""
|
|
561
|
+
|
|
562
|
+
def test_empty_allow_domains(self):
|
|
563
|
+
"""Should accept empty allow_domains list."""
|
|
564
|
+
config = EgressConfig()
|
|
565
|
+
assert config.allow_domains == []
|
|
566
|
+
|
|
567
|
+
def test_valid_exact_domain(self):
|
|
568
|
+
"""Should accept exact FQDNs via matchName."""
|
|
569
|
+
config = EgressConfig(
|
|
570
|
+
allow_domains=[DomainSelector(match_name="api.openai.com")]
|
|
571
|
+
)
|
|
572
|
+
assert config.allow_domains[0].match_name == "api.openai.com"
|
|
573
|
+
|
|
574
|
+
def test_valid_wildcard_domain(self):
|
|
575
|
+
"""Should accept wildcard prefixes via matchPattern."""
|
|
576
|
+
config = EgressConfig(
|
|
577
|
+
allow_domains=[DomainSelector(match_pattern="*.github.com")]
|
|
578
|
+
)
|
|
579
|
+
assert config.allow_domains[0].match_pattern == "*.github.com"
|
|
580
|
+
|
|
581
|
+
def test_valid_multiple_domains(self):
|
|
582
|
+
"""Should accept multiple valid domains."""
|
|
583
|
+
domains = [
|
|
584
|
+
DomainSelector(match_name="api.openai.com"),
|
|
585
|
+
DomainSelector(match_pattern="*.github.com"),
|
|
586
|
+
DomainSelector(match_name="httpbin.org"),
|
|
587
|
+
]
|
|
588
|
+
config = EgressConfig(allow_domains=domains)
|
|
589
|
+
assert len(config.allow_domains) == 3
|
|
590
|
+
|
|
591
|
+
def test_rejects_url_with_scheme(self):
|
|
592
|
+
"""Should reject domains with URL schemes."""
|
|
593
|
+
with pytest.raises(ValueError, match="Invalid domain"):
|
|
594
|
+
EgressConfig(
|
|
595
|
+
allow_domains=[DomainSelector(match_name="https://api.openai.com")]
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
def test_rejects_domain_with_port(self):
|
|
599
|
+
"""Should reject domains with ports."""
|
|
600
|
+
with pytest.raises(ValueError, match="Invalid domain"):
|
|
601
|
+
EgressConfig(
|
|
602
|
+
allow_domains=[DomainSelector(match_name="api.openai.com:443")]
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
def test_rejects_domain_with_path(self):
|
|
606
|
+
"""Should reject domains with paths."""
|
|
607
|
+
with pytest.raises(ValueError, match="Invalid domain"):
|
|
608
|
+
EgressConfig(allow_domains=[DomainSelector(match_name="api.openai.com/v1")])
|
|
609
|
+
|
|
610
|
+
def test_rejects_ip_address(self):
|
|
611
|
+
"""Should reject IP addresses."""
|
|
612
|
+
with pytest.raises(ValueError, match="Invalid domain"):
|
|
613
|
+
EgressConfig(allow_domains=[DomainSelector(match_name="192.168.1.1")])
|
|
614
|
+
|
|
615
|
+
def test_rejects_bare_wildcard(self):
|
|
616
|
+
"""Should reject bare wildcard."""
|
|
617
|
+
with pytest.raises(ValueError, match="Invalid domain"):
|
|
618
|
+
EgressConfig(allow_domains=[DomainSelector(match_pattern="*")])
|
|
619
|
+
|
|
620
|
+
def test_rejects_wildcard_tld(self):
|
|
621
|
+
"""Should reject wildcard on TLD only."""
|
|
622
|
+
with pytest.raises(ValueError, match="Invalid domain"):
|
|
623
|
+
EgressConfig(allow_domains=[DomainSelector(match_pattern="*.com")])
|
|
624
|
+
|
|
625
|
+
def test_rejects_too_many_domains(self):
|
|
626
|
+
"""Should reject more than 50 entries."""
|
|
627
|
+
domains = [
|
|
628
|
+
DomainSelector(match_name=f"domain{i}.example.com") for i in range(51)
|
|
629
|
+
]
|
|
630
|
+
with pytest.raises(ValueError, match="cannot have more than 50"):
|
|
631
|
+
EgressConfig(allow_domains=domains)
|
|
632
|
+
|
|
633
|
+
def test_accepts_50_domains(self):
|
|
634
|
+
"""Should accept exactly 50 entries."""
|
|
635
|
+
domains = [
|
|
636
|
+
DomainSelector(match_name=f"domain{i}.example.com") for i in range(50)
|
|
637
|
+
]
|
|
638
|
+
config = EgressConfig(allow_domains=domains)
|
|
639
|
+
assert len(config.allow_domains) == 50
|
|
640
|
+
|
|
641
|
+
def test_rejects_bare_tld(self):
|
|
642
|
+
"""Should reject bare TLD."""
|
|
643
|
+
with pytest.raises(ValueError, match="Invalid domain"):
|
|
644
|
+
EgressConfig(allow_domains=[DomainSelector(match_name="com")])
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
class TestNetworkConfig:
|
|
648
|
+
"""Tests for NetworkConfig model."""
|
|
649
|
+
|
|
650
|
+
def test_default_egress(self):
|
|
651
|
+
"""Should default to empty allow_domains."""
|
|
652
|
+
config = NetworkConfig()
|
|
653
|
+
assert config.egress.allow_domains == []
|
|
654
|
+
|
|
655
|
+
def test_custom_egress(self):
|
|
656
|
+
"""Should accept custom egress config."""
|
|
657
|
+
config = NetworkConfig(
|
|
658
|
+
egress=EgressConfig(
|
|
659
|
+
allow_domains=[DomainSelector(match_name="api.openai.com")]
|
|
660
|
+
)
|
|
661
|
+
)
|
|
662
|
+
assert config.egress.allow_domains[0].match_name == "api.openai.com"
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
class TestDispatchConfigNetwork:
|
|
666
|
+
"""Tests for network field on DispatchConfig."""
|
|
667
|
+
|
|
668
|
+
def test_network_none_by_default(self):
|
|
669
|
+
"""Should default to None (no restrictions)."""
|
|
670
|
+
config = DispatchConfig()
|
|
671
|
+
assert config.network is None
|
|
672
|
+
|
|
673
|
+
def test_network_with_egress(self):
|
|
674
|
+
"""Should accept network config with egress."""
|
|
675
|
+
config = DispatchConfig(
|
|
676
|
+
network=NetworkConfig(
|
|
677
|
+
egress=EgressConfig(
|
|
678
|
+
allow_domains=[DomainSelector(match_name="api.openai.com")]
|
|
679
|
+
)
|
|
680
|
+
)
|
|
681
|
+
)
|
|
682
|
+
assert config.network is not None
|
|
683
|
+
assert config.network.egress.allow_domains[0].match_name == "api.openai.com"
|
|
684
|
+
|
|
685
|
+
def test_to_yaml_dict_includes_network(self):
|
|
686
|
+
"""Should include network in YAML serialization when set."""
|
|
687
|
+
config = DispatchConfig(
|
|
688
|
+
network=NetworkConfig(
|
|
689
|
+
egress=EgressConfig(
|
|
690
|
+
allow_domains=[
|
|
691
|
+
DomainSelector(match_name="api.openai.com"),
|
|
692
|
+
DomainSelector(match_pattern="*.github.com"),
|
|
693
|
+
]
|
|
694
|
+
)
|
|
695
|
+
)
|
|
696
|
+
)
|
|
697
|
+
result = config.to_yaml_dict()
|
|
698
|
+
assert "network" in result
|
|
699
|
+
assert result["network"] == {
|
|
700
|
+
"egress": {
|
|
701
|
+
"allow_domains": [
|
|
702
|
+
{"match_name": "api.openai.com"},
|
|
703
|
+
{"match_pattern": "*.github.com"},
|
|
704
|
+
],
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
def test_to_yaml_dict_includes_network_empty_domains(self):
|
|
709
|
+
"""Should include network even with empty allow_domains (triggers policy creation)."""
|
|
710
|
+
config = DispatchConfig(network=NetworkConfig())
|
|
711
|
+
result = config.to_yaml_dict()
|
|
712
|
+
assert "network" in result
|
|
713
|
+
assert result["network"] == {"egress": {"allow_domains": []}}
|
|
714
|
+
|
|
715
|
+
def test_to_yaml_dict_excludes_network_when_none(self):
|
|
716
|
+
"""Should not include network when None."""
|
|
717
|
+
config = DispatchConfig()
|
|
718
|
+
result = config.to_yaml_dict()
|
|
719
|
+
assert "network" not in result
|
|
@@ -213,6 +213,7 @@ async def test_normalizes_naive_expiry_timestamp(monkeypatch):
|
|
|
213
213
|
("status_code", "expected_message"),
|
|
214
214
|
[
|
|
215
215
|
(401, "DISPATCH_API_KEY"),
|
|
216
|
+
(403, "request failed: forbidden"),
|
|
216
217
|
(404, "No GitHub installation found"),
|
|
217
218
|
(500, "backend returned HTTP 500"),
|
|
218
219
|
],
|
|
@@ -1054,6 +1054,10 @@ class TestPassthroughRoute:
|
|
|
1054
1054
|
resp = client.get("/openai/v1/models")
|
|
1055
1055
|
assert resp.status_code == 200
|
|
1056
1056
|
|
|
1057
|
+
# Verify provider_format is sent in the payload
|
|
1058
|
+
payload = mock_client.post.call_args.kwargs["json"]
|
|
1059
|
+
assert payload["provider_format"] == "openai"
|
|
1060
|
+
|
|
1057
1061
|
@patch("dispatch_agents.proxy.server.httpx.AsyncClient")
|
|
1058
1062
|
def test_anthropic_passthrough_get(self, mock_client_cls, client):
|
|
1059
1063
|
"""GET /anthropic/v1/models goes through passthrough."""
|
|
@@ -1071,6 +1075,10 @@ class TestPassthroughRoute:
|
|
|
1071
1075
|
resp = client.get("/anthropic/v1/models")
|
|
1072
1076
|
assert resp.status_code == 200
|
|
1073
1077
|
|
|
1078
|
+
# Verify provider_format is sent in the payload
|
|
1079
|
+
payload = mock_client.post.call_args.kwargs["json"]
|
|
1080
|
+
assert payload["provider_format"] == "anthropic"
|
|
1081
|
+
|
|
1074
1082
|
|
|
1075
1083
|
# ── Auth Error Detection ─────────────────────────────────────────────
|
|
1076
1084
|
|
|
@@ -3,15 +3,15 @@ revision = 3
|
|
|
3
3
|
requires-python = ">=3.11"
|
|
4
4
|
|
|
5
5
|
[options]
|
|
6
|
-
exclude-newer = "2026-
|
|
6
|
+
exclude-newer = "2026-03-03T20:09:23.684706Z"
|
|
7
7
|
exclude-newer-span = "P30D"
|
|
8
8
|
|
|
9
9
|
[options.exclude-newer-package]
|
|
10
|
-
dispatch-agents = { timestamp = "2026-
|
|
11
|
-
claude-agent-sdk = { timestamp = "2026-03-
|
|
12
|
-
openai = { timestamp = "2026-03-
|
|
13
|
-
langchain-openai = { timestamp = "2026-03-
|
|
14
|
-
openai-agents = { timestamp = "2026-03-
|
|
10
|
+
dispatch-agents = { timestamp = "2026-04-02T20:09:23.684712Z", span = "PT0S" }
|
|
11
|
+
claude-agent-sdk = { timestamp = "2026-03-19T20:09:23.684719Z", span = "P14D" }
|
|
12
|
+
openai = { timestamp = "2026-03-19T20:09:23.684719Z", span = "P14D" }
|
|
13
|
+
langchain-openai = { timestamp = "2026-03-19T20:09:23.68472Z", span = "P14D" }
|
|
14
|
+
openai-agents = { timestamp = "2026-03-19T20:09:23.684719Z", span = "P14D" }
|
|
15
15
|
|
|
16
16
|
[[package]]
|
|
17
17
|
name = "aiohappyeyeballs"
|
|
@@ -551,7 +551,7 @@ wheels = [
|
|
|
551
551
|
|
|
552
552
|
[[package]]
|
|
553
553
|
name = "dispatch-agents"
|
|
554
|
-
version = "0.
|
|
554
|
+
version = "0.12.2"
|
|
555
555
|
source = { editable = "." }
|
|
556
556
|
dependencies = [
|
|
557
557
|
{ name = "aiohttp" },
|
|
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
|
{dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/.github/workflows/version-policy-reusable.yml
RENAMED
|
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
|
{dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/agentservice/v1/request_response_pb2_grpc.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/contrib/claude/__init__.py
RENAMED
|
File without changes
|
{dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/contrib/openai/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/integrations/github/README.md
RENAMED
|
File without changes
|
{dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/dispatch_agents/integrations/github/__init__.py
RENAMED
|
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
|
{dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/company-researcher/pyproject.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/conversational-agent/dispatch.yaml
RENAMED
|
File without changes
|
{dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/conversational-agent/pyproject.toml
RENAMED
|
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
|
{dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/knowledge-base-query/.env.example
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/knowledge-base-query/dispatch.yaml
RENAMED
|
File without changes
|
{dispatch_agents-0.11.0 → dispatch_agents-0.12.2}/examples/knowledge-base-query/pyproject.toml
RENAMED
|
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
|