copass-core-agents 0.1.0__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.
@@ -0,0 +1,38 @@
1
+ # Dependencies
2
+ node_modules/
3
+
4
+ # Build output
5
+ dist/
6
+ *.tsbuildinfo
7
+
8
+ # Environment
9
+ .env
10
+ .env.*
11
+
12
+ # IDE
13
+ .vscode/
14
+ .idea/
15
+ *.swp
16
+ *.swo
17
+ *~
18
+
19
+ # OS
20
+ .DS_Store
21
+ Thumbs.db
22
+
23
+ # Test
24
+ coverage/
25
+
26
+ # Lerna
27
+ lerna-debug.log
28
+ .nx/cache
29
+ .nx/workspace-data
30
+
31
+ # Python
32
+ __pycache__/
33
+ *.pyc
34
+ *.pyo
35
+ *.egg-info/
36
+ .venv/
37
+ venv/
38
+ .olane
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: copass-core-agents
3
+ Version: 0.1.0
4
+ Summary: Provider-neutral agent primitives shared by every Copass agent SDK
5
+ Project-URL: Homepage, https://github.com/olane-labs/copass-harness
6
+ Project-URL: Repository, https://github.com/olane-labs/copass-harness.git
7
+ Author: Olane Inc.
8
+ License: MIT
9
+ Keywords: abc,agents,copass,knowledge-graph,primitives
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Requires-Python: >=3.10
16
+ Provides-Extra: dev
17
+ Requires-Dist: mypy>=1.10; extra == 'dev'
18
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
19
+ Requires-Dist: pytest>=8.0; extra == 'dev'
20
+ Requires-Dist: ruff>=0.5; extra == 'dev'
21
+ Description-Content-Type: text/markdown
22
+
23
+ # copass-core-agents
24
+
25
+ Provider-neutral agent primitives shared by every Copass agent SDK.
26
+
27
+ This package owns the ABCs, value types, and registries that each
28
+ provider-specific SDK (`copass-anthropic-agents`, future
29
+ `copass-openai-agents`, `copass-google-agents`, etc.) implements
30
+ against. It carries zero vendor dependencies.
31
+
32
+ ## What's in here
33
+
34
+ - `BaseAgent` — identity + prompt + tool surface + backend
35
+ - `AgentScope`, `AgentInvocationContext` — tenancy + per-call context
36
+ - `AgentTool`, `AgentToolRegistry`, `AgentToolResolver`, `ToolSpec`,
37
+ `ToolCall` — tool abstractions
38
+ - `AgentEvent` (plus `AgentTextDelta`, `AgentToolCall`,
39
+ `AgentToolResult`, `AgentFinish`) — streaming event union
40
+ - `AgentBackend` ABC + `AgentRunResult`
41
+ - `register_agent` / `register_agent_tool` registries
42
+
43
+ ## Which package should I install?
44
+
45
+ | I want to… | Install |
46
+ |---|---|
47
+ | Run a Claude Managed Agent (Anthropic) | `copass-anthropic-agents` (pulls this in transitively) |
48
+ | Build my own backend / extend the ABCs | `copass-core-agents` directly |
49
+
50
+ If you're end-user code invoking an agent, you almost never install
51
+ this package directly — you install a provider SDK and it re-exports
52
+ what you need.
53
+
54
+ ## Dependencies
55
+
56
+ Zero runtime dependencies. Python ≥ 3.10.
57
+
58
+ ## License
59
+
60
+ MIT.
@@ -0,0 +1,38 @@
1
+ # copass-core-agents
2
+
3
+ Provider-neutral agent primitives shared by every Copass agent SDK.
4
+
5
+ This package owns the ABCs, value types, and registries that each
6
+ provider-specific SDK (`copass-anthropic-agents`, future
7
+ `copass-openai-agents`, `copass-google-agents`, etc.) implements
8
+ against. It carries zero vendor dependencies.
9
+
10
+ ## What's in here
11
+
12
+ - `BaseAgent` — identity + prompt + tool surface + backend
13
+ - `AgentScope`, `AgentInvocationContext` — tenancy + per-call context
14
+ - `AgentTool`, `AgentToolRegistry`, `AgentToolResolver`, `ToolSpec`,
15
+ `ToolCall` — tool abstractions
16
+ - `AgentEvent` (plus `AgentTextDelta`, `AgentToolCall`,
17
+ `AgentToolResult`, `AgentFinish`) — streaming event union
18
+ - `AgentBackend` ABC + `AgentRunResult`
19
+ - `register_agent` / `register_agent_tool` registries
20
+
21
+ ## Which package should I install?
22
+
23
+ | I want to… | Install |
24
+ |---|---|
25
+ | Run a Claude Managed Agent (Anthropic) | `copass-anthropic-agents` (pulls this in transitively) |
26
+ | Build my own backend / extend the ABCs | `copass-core-agents` directly |
27
+
28
+ If you're end-user code invoking an agent, you almost never install
29
+ this package directly — you install a provider SDK and it re-exports
30
+ what you need.
31
+
32
+ ## Dependencies
33
+
34
+ Zero runtime dependencies. Python ≥ 3.10.
35
+
36
+ ## License
37
+
38
+ MIT.
@@ -0,0 +1,55 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "copass-core-agents"
7
+ version = "0.1.0"
8
+ description = "Provider-neutral agent primitives shared by every Copass agent SDK"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "Olane Inc." }]
12
+ requires-python = ">=3.10"
13
+ keywords = [
14
+ "copass",
15
+ "knowledge-graph",
16
+ "agents",
17
+ "abc",
18
+ "primitives",
19
+ ]
20
+ classifiers = [
21
+ "License :: OSI Approved :: MIT License",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ ]
27
+ dependencies = []
28
+
29
+ [project.optional-dependencies]
30
+ dev = [
31
+ "pytest>=8.0",
32
+ "pytest-asyncio>=0.23",
33
+ "mypy>=1.10",
34
+ "ruff>=0.5",
35
+ ]
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/olane-labs/copass-harness"
39
+ Repository = "https://github.com/olane-labs/copass-harness.git"
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/copass_core_agents"]
43
+
44
+ [tool.pytest.ini_options]
45
+ asyncio_mode = "auto"
46
+ testpaths = ["tests"]
47
+
48
+ [tool.ruff]
49
+ line-length = 100
50
+ target-version = "py310"
51
+
52
+ [tool.mypy]
53
+ python_version = "3.10"
54
+ strict = true
55
+ packages = ["copass_core_agents"]
@@ -0,0 +1,99 @@
1
+ """Copass Core Agents — provider-neutral agent primitives.
2
+
3
+ Shared ABCs and value types used by every provider-specific Copass
4
+ agent SDK (``copass-anthropic-agents``, future ``copass-openai-agents``,
5
+ ``copass-google-agents``, etc.). Concrete backends, convenience
6
+ subclasses, and vendor-specific wiring live in those per-provider
7
+ packages; this package owns nothing vendor-specific.
8
+
9
+ Public surface:
10
+
11
+ Core
12
+ BaseAgent — identity + prompt + tools + backend
13
+ AgentScope — tenancy payload
14
+ AgentInvocationContext — per-call runtime context
15
+ AgentTool — ABC for a tool an agent can invoke
16
+ AgentToolRegistry — per-agent collection of tools
17
+ AgentToolResolver — scope-aware dynamic tool producer
18
+ ToolSpec, ToolCall — tool catalog shapes
19
+ ToolConflictPolicy — "error" | "dynamic_wins" | "static_wins"
20
+ ToolConflictError
21
+
22
+ Events (emitted by AgentBackend.stream)
23
+ AgentEvent — tagged union
24
+ AgentTextDelta
25
+ AgentToolCall
26
+ AgentToolResult
27
+ AgentFinish
28
+
29
+ Backends
30
+ AgentBackend — ABC (concrete impls live per-provider)
31
+ AgentRunResult — reduced run() output
32
+
33
+ Registries (optional lookup-by-name)
34
+ register_agent, get_agent_class, list_agents
35
+ register_agent_tool, get_agent_tool, try_get_agent_tool,
36
+ list_agent_tools
37
+ """
38
+
39
+ from copass_core_agents.backends import AgentBackend, AgentRunResult
40
+ from copass_core_agents.base_agent import BaseAgent
41
+ from copass_core_agents.base_tool import AgentTool, ToolCall, ToolSpec
42
+ from copass_core_agents.events import (
43
+ AgentEvent,
44
+ AgentFinish,
45
+ AgentTextDelta,
46
+ AgentToolCall,
47
+ AgentToolResult,
48
+ )
49
+ from copass_core_agents.invocation_context import AgentInvocationContext
50
+ from copass_core_agents.registry import (
51
+ get_agent_class,
52
+ get_agent_tool,
53
+ list_agent_tools,
54
+ list_agents,
55
+ register_agent,
56
+ register_agent_tool,
57
+ try_get_agent_tool,
58
+ )
59
+ from copass_core_agents.scope import AgentScope
60
+ from copass_core_agents.tool_registry import AgentToolRegistry
61
+ from copass_core_agents.tool_resolver import (
62
+ AgentToolResolver,
63
+ ToolConflictError,
64
+ ToolConflictPolicy,
65
+ )
66
+
67
+ __version__ = "0.1.0"
68
+
69
+ __all__ = [
70
+ "__version__",
71
+ # Core
72
+ "BaseAgent",
73
+ "AgentScope",
74
+ "AgentInvocationContext",
75
+ "AgentTool",
76
+ "AgentToolRegistry",
77
+ "AgentToolResolver",
78
+ "ToolSpec",
79
+ "ToolCall",
80
+ "ToolConflictError",
81
+ "ToolConflictPolicy",
82
+ # Events
83
+ "AgentEvent",
84
+ "AgentTextDelta",
85
+ "AgentToolCall",
86
+ "AgentToolResult",
87
+ "AgentFinish",
88
+ # Backends
89
+ "AgentBackend",
90
+ "AgentRunResult",
91
+ # Registries
92
+ "register_agent",
93
+ "get_agent_class",
94
+ "list_agents",
95
+ "register_agent_tool",
96
+ "get_agent_tool",
97
+ "try_get_agent_tool",
98
+ "list_agent_tools",
99
+ ]
@@ -0,0 +1,10 @@
1
+ """Agent backend ABCs — concrete backends live in per-provider
2
+ packages (``copass-anthropic-agents``, future ``copass-openai-agents``,
3
+ ``copass-google-agents``)."""
4
+
5
+ from copass_core_agents.backends.base_backend import (
6
+ AgentBackend,
7
+ AgentRunResult,
8
+ )
9
+
10
+ __all__ = ["AgentBackend", "AgentRunResult"]
@@ -0,0 +1,87 @@
1
+ """AgentBackend — runtime ABC for executing an agent turn.
2
+
3
+ A backend is the seam between the provider-neutral agent surface
4
+ (``BaseAgent``, ``AgentTool``, ``AgentEvent``) and a concrete
5
+ SDK/provider.
6
+
7
+ Design rules:
8
+
9
+ - Base classes must NOT import any vendor SDK. This file only depends
10
+ on plain data types and the SDK's own ABCs.
11
+ - Backends translate between the provider's tool-use format and the
12
+ agent's ``AgentToolRegistry`` + ``AgentEvent`` stream.
13
+ - A single backend instance may be shared across agents and requests.
14
+ Per-request state lives on ``AgentInvocationContext``, not on the
15
+ backend.
16
+
17
+ Concrete backends live in per-provider packages
18
+ (``copass-anthropic-agents``, future ``copass-openai-agents``,
19
+ ``copass-google-agents``). This file and ``AgentRunResult`` are the
20
+ only ABC surface they implement against.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from abc import ABC, abstractmethod
26
+ from dataclasses import dataclass, field
27
+ from typing import TYPE_CHECKING, AsyncIterator, List, Optional
28
+
29
+ from copass_core_agents.events import AgentEvent
30
+ from copass_core_agents.invocation_context import AgentInvocationContext
31
+
32
+ if TYPE_CHECKING:
33
+ from copass_core_agents.base_agent import BaseAgent
34
+
35
+
36
+ @dataclass(frozen=True)
37
+ class AgentRunResult:
38
+ """Reduced output of a non-streaming ``AgentBackend.run``."""
39
+
40
+ final_text: str
41
+ tool_calls: List[dict] = field(default_factory=list)
42
+ stop_reason: str = "end_turn"
43
+ usage: dict = field(default_factory=dict)
44
+ session_id: Optional[str] = None
45
+
46
+
47
+ class AgentBackend(ABC):
48
+ """Runtime adapter between the agent surface and a provider SDK.
49
+
50
+ Construction takes an optional ``config`` dict. Backend-specific
51
+ knobs (API keys, model allow-lists, timeout overrides) live there
52
+ rather than as strong fields so the ABC signature stays stable as
53
+ providers evolve.
54
+ """
55
+
56
+ def __init__(self, *, config: Optional[dict] = None) -> None:
57
+ self._config = dict(config or {})
58
+
59
+ @property
60
+ def config(self) -> dict:
61
+ """Backend-specific configuration. Read-only view."""
62
+ return dict(self._config)
63
+
64
+ @abstractmethod
65
+ async def run(
66
+ self,
67
+ agent: "BaseAgent",
68
+ messages: List[dict],
69
+ context: AgentInvocationContext,
70
+ ) -> AgentRunResult:
71
+ """Drive the conversation to a stop condition, return a
72
+ reduced result."""
73
+ ...
74
+
75
+ @abstractmethod
76
+ def stream(
77
+ self,
78
+ agent: "BaseAgent",
79
+ messages: List[dict],
80
+ context: AgentInvocationContext,
81
+ ) -> AsyncIterator[AgentEvent]:
82
+ """Drive the conversation and yield ``AgentEvent`` as they
83
+ occur."""
84
+ ...
85
+
86
+
87
+ __all__ = ["AgentBackend", "AgentRunResult"]
@@ -0,0 +1,167 @@
1
+ """BaseAgent — ABC for an identity-bound, tool-equipped agent.
2
+
3
+ An agent is the composition of:
4
+
5
+ - an **identity** (stable string used in logs, prompts, and registry
6
+ lookups)
7
+ - a **model** (provider/SDK-specific model name — the backend
8
+ validates it)
9
+ - a **system prompt** (role + instructions fed on every turn)
10
+ - an **AgentToolRegistry** holding the *static* tools
11
+ - an optional **AgentToolResolver** producing *dynamic* tools per
12
+ invocation
13
+ - an **AgentBackend** (the runtime that actually drives turns)
14
+
15
+ ``BaseAgent`` itself has no turn-execution logic — both ``run`` and
16
+ ``stream`` compute the effective tool registry for the invocation
17
+ (via :meth:`build_tools`) and delegate to the backend.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import logging
23
+ from abc import ABC
24
+ from typing import AsyncIterator, List, Optional
25
+
26
+ from copass_core_agents.backends.base_backend import (
27
+ AgentBackend,
28
+ AgentRunResult,
29
+ )
30
+ from copass_core_agents.base_tool import AgentTool
31
+ from copass_core_agents.events import AgentEvent
32
+ from copass_core_agents.invocation_context import AgentInvocationContext
33
+ from copass_core_agents.tool_registry import AgentToolRegistry
34
+ from copass_core_agents.tool_resolver import (
35
+ AgentToolResolver,
36
+ ToolConflictError,
37
+ ToolConflictPolicy,
38
+ )
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+
43
+ class BaseAgent(ABC):
44
+ """Identity + prompt + tools + resolver + backend bundle.
45
+
46
+ Not abstract in the Python sense (no ``@abstractmethod``) — the
47
+ ``ABC`` inheritance is a signal to subclassers that this is a
48
+ base type to extend, not to instantiate directly. Instantiating
49
+ ``BaseAgent`` is valid for quick scripts/tests but production
50
+ callers should subclass with concrete identity and prompt.
51
+
52
+ Either ``tools`` or ``tool_resolver`` (or both) must be provided.
53
+ """
54
+
55
+ def __init__(
56
+ self,
57
+ *,
58
+ identity: str,
59
+ model: str,
60
+ system_prompt: str,
61
+ backend: AgentBackend,
62
+ tools: Optional[AgentToolRegistry] = None,
63
+ tool_resolver: Optional[AgentToolResolver] = None,
64
+ on_conflict: ToolConflictPolicy = "dynamic_wins",
65
+ ) -> None:
66
+ if tools is None and tool_resolver is None:
67
+ raise ValueError(
68
+ "BaseAgent requires at least one of `tools` or "
69
+ "`tool_resolver` — an agent with no capabilities has "
70
+ "no reason to exist."
71
+ )
72
+ if on_conflict not in ("error", "dynamic_wins", "static_wins"):
73
+ raise ValueError(
74
+ f"BaseAgent: invalid on_conflict policy {on_conflict!r}. "
75
+ f"Expected 'error', 'dynamic_wins', or 'static_wins'."
76
+ )
77
+ self.identity = identity
78
+ self.model = model
79
+ self.system_prompt = system_prompt
80
+ self.backend = backend
81
+ self.tools = tools if tools is not None else AgentToolRegistry()
82
+ self.tool_resolver = tool_resolver
83
+ self.on_conflict: ToolConflictPolicy = on_conflict
84
+
85
+ async def run(
86
+ self,
87
+ messages: List[dict],
88
+ *,
89
+ context: AgentInvocationContext,
90
+ ) -> AgentRunResult:
91
+ """Drive a turn to completion and return the reduced result."""
92
+ return await self.backend.run(self, messages, context)
93
+
94
+ async def stream(
95
+ self,
96
+ messages: List[dict],
97
+ *,
98
+ context: AgentInvocationContext,
99
+ ) -> AsyncIterator[AgentEvent]:
100
+ """Drive a turn and yield ``AgentEvent`` as they occur."""
101
+ async for evt in self.backend.stream(self, messages, context):
102
+ yield evt
103
+
104
+ async def build_tools(
105
+ self, context: AgentInvocationContext
106
+ ) -> AgentToolRegistry:
107
+ """Compute the effective tool registry for this invocation."""
108
+ if self.tool_resolver is None:
109
+ return self.tools
110
+
111
+ dynamic_tools: List[AgentTool] = await self.tool_resolver.resolve(context)
112
+
113
+ merged = AgentToolRegistry()
114
+ static_names = {tool.spec.name for tool in self.tools}
115
+ dynamic_names = {tool.spec.name for tool in dynamic_tools}
116
+ collisions = static_names & dynamic_names
117
+
118
+ if collisions and self.on_conflict == "error":
119
+ raise ToolConflictError(
120
+ f"BaseAgent {self.identity!r}: static and dynamic tools "
121
+ f"collide on names: {sorted(collisions)}. Set "
122
+ f"on_conflict='dynamic_wins' or 'static_wins' to pick "
123
+ f"a resolution policy, or fix the duplicated name."
124
+ )
125
+
126
+ if self.on_conflict == "static_wins":
127
+ for tool in dynamic_tools:
128
+ if tool.spec.name not in static_names:
129
+ merged.add(tool)
130
+ for tool in self.tools:
131
+ merged.add(tool)
132
+ else:
133
+ for tool in self.tools:
134
+ if tool.spec.name not in dynamic_names:
135
+ merged.add(tool)
136
+ for tool in dynamic_tools:
137
+ merged.add(tool)
138
+
139
+ if collisions:
140
+ logger.info(
141
+ "BaseAgent.build_tools: resolved tool name collisions",
142
+ extra={
143
+ "identity": self.identity,
144
+ "policy": self.on_conflict,
145
+ "collisions": sorted(collisions),
146
+ },
147
+ )
148
+ return merged
149
+
150
+ def __repr__(self) -> str:
151
+ resolver = (
152
+ self.tool_resolver.__class__.__name__
153
+ if self.tool_resolver is not None
154
+ else "None"
155
+ )
156
+ return (
157
+ f"{self.__class__.__name__}("
158
+ f"identity={self.identity!r}, "
159
+ f"model={self.model!r}, "
160
+ f"static_tools={len(self.tools)}, "
161
+ f"resolver={resolver}, "
162
+ f"backend={self.backend.__class__.__name__}"
163
+ f")"
164
+ )
165
+
166
+
167
+ __all__ = ["BaseAgent"]
@@ -0,0 +1,104 @@
1
+ """AgentTool — tool ABC callable by an agent during a turn.
2
+
3
+ ``ToolSpec`` / ``ToolCall`` are the provider-neutral catalog shapes.
4
+ Defined here in the core package; provider adapters (Anthropic,
5
+ OpenAI, Google) translate them into provider-specific tool-use
6
+ catalog payloads.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from abc import ABC, abstractmethod
12
+ from dataclasses import dataclass, field
13
+ from typing import Optional
14
+
15
+ from copass_core_agents.invocation_context import AgentInvocationContext
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class ToolSpec:
20
+ """Catalog entry for one tool — what the model sees.
21
+
22
+ Shape matches the JSON-schema-based tool-use conventions across
23
+ providers so a single ``ToolSpec`` can be passed to any backend's
24
+ ``tools=[]`` parameter after trivial adaptation.
25
+ """
26
+
27
+ name: str
28
+ description: str
29
+ input_schema: dict
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class ToolCall:
34
+ """Audit record of one tool invocation within a turn.
35
+
36
+ Not used by the agent runtime's hot path (events carry the
37
+ canonical structure); this type is the shape backends / consumers
38
+ serialize when they want to persist a turn's tool history.
39
+ """
40
+
41
+ name: str
42
+ arguments: dict
43
+ result: dict
44
+ error: Optional[str] = None
45
+ metadata: dict = field(default_factory=dict)
46
+
47
+
48
+ class AgentTool(ABC):
49
+ """One capability an agent can invoke during a turn.
50
+
51
+ Implementations must be:
52
+
53
+ - Stateless across calls (no per-invocation mutable state on the
54
+ instance). Concurrency is driven by the backend and multiple
55
+ ``invoke`` calls may run in parallel.
56
+ - JSON-result-returning. The returned dict is fed back to the
57
+ model verbatim; it must be serializable and should stay small
58
+ enough to fit in the model's context window.
59
+ - Schema-honoring. ``arguments`` always matches
60
+ ``spec.input_schema`` — the backend is responsible for
61
+ validating before calling ``invoke``.
62
+
63
+ Implementations must NOT:
64
+
65
+ - Raise arbitrary exceptions on recoverable errors. Convert
66
+ tool-internal failures into a dict result with an ``error``
67
+ field the model can read and retry against. Reserve raising
68
+ for programmer errors (schema drift, unreachable branches).
69
+ """
70
+
71
+ @property
72
+ @abstractmethod
73
+ def spec(self) -> ToolSpec:
74
+ """Return the catalog entry. Must be stable across calls."""
75
+ ...
76
+
77
+ @abstractmethod
78
+ async def invoke(
79
+ self,
80
+ arguments: dict,
81
+ *,
82
+ context: Optional[AgentInvocationContext] = None,
83
+ ) -> dict:
84
+ """Execute the tool.
85
+
86
+ Args:
87
+ arguments: JSON-decoded tool arguments. Matches
88
+ ``spec.input_schema``.
89
+ context: Per-invocation context (scope, dek, handles).
90
+ Optional — tools that don't need runtime handles
91
+ should tolerate ``None``.
92
+
93
+ Returns:
94
+ JSON-serializable dict. Goes straight back to the model.
95
+
96
+ Raises:
97
+ Exception: Only for programmer errors / unrecoverable
98
+ failures. Recoverable issues belong in the return
99
+ dict.
100
+ """
101
+ ...
102
+
103
+
104
+ __all__ = ["AgentTool", "ToolCall", "ToolSpec"]