axion-code 1.0.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.
- axion/__init__.py +3 -0
- axion/api/__init__.py +0 -0
- axion/api/anthropic.py +460 -0
- axion/api/client.py +259 -0
- axion/api/error.py +161 -0
- axion/api/ollama.py +597 -0
- axion/api/openai_compat.py +805 -0
- axion/api/openai_responses.py +627 -0
- axion/api/prompt_cache.py +31 -0
- axion/api/sse.py +98 -0
- axion/api/types.py +451 -0
- axion/cli/__init__.py +0 -0
- axion/cli/init_cmd.py +50 -0
- axion/cli/input.py +290 -0
- axion/cli/main.py +2953 -0
- axion/cli/render.py +489 -0
- axion/cli/tui.py +766 -0
- axion/commands/__init__.py +0 -0
- axion/commands/handlers/__init__.py +0 -0
- axion/commands/handlers/agents.py +51 -0
- axion/commands/handlers/builtin_commands.py +367 -0
- axion/commands/handlers/mcp.py +59 -0
- axion/commands/handlers/models.py +75 -0
- axion/commands/handlers/plugins.py +55 -0
- axion/commands/handlers/skills.py +61 -0
- axion/commands/parsing.py +317 -0
- axion/commands/registry.py +166 -0
- axion/compat_harness/__init__.py +0 -0
- axion/compat_harness/extractor.py +145 -0
- axion/plugins/__init__.py +0 -0
- axion/plugins/hooks.py +22 -0
- axion/plugins/manager.py +391 -0
- axion/plugins/manifest.py +270 -0
- axion/runtime/__init__.py +0 -0
- axion/runtime/bash.py +388 -0
- axion/runtime/bootstrap.py +39 -0
- axion/runtime/claude_subscription.py +300 -0
- axion/runtime/compact.py +233 -0
- axion/runtime/config.py +397 -0
- axion/runtime/conversation.py +1073 -0
- axion/runtime/file_ops.py +613 -0
- axion/runtime/git.py +213 -0
- axion/runtime/hooks.py +235 -0
- axion/runtime/image.py +212 -0
- axion/runtime/lanes.py +282 -0
- axion/runtime/lsp.py +425 -0
- axion/runtime/mcp/__init__.py +0 -0
- axion/runtime/mcp/client.py +76 -0
- axion/runtime/mcp/lifecycle.py +96 -0
- axion/runtime/mcp/stdio.py +318 -0
- axion/runtime/mcp/tool_bridge.py +79 -0
- axion/runtime/memory.py +196 -0
- axion/runtime/oauth.py +329 -0
- axion/runtime/openai_subscription.py +346 -0
- axion/runtime/permissions.py +247 -0
- axion/runtime/plan_mode.py +96 -0
- axion/runtime/policy_engine.py +259 -0
- axion/runtime/prompt.py +586 -0
- axion/runtime/recovery.py +261 -0
- axion/runtime/remote.py +28 -0
- axion/runtime/sandbox.py +68 -0
- axion/runtime/scheduler.py +231 -0
- axion/runtime/session.py +365 -0
- axion/runtime/sharing.py +159 -0
- axion/runtime/skills.py +124 -0
- axion/runtime/tasks.py +258 -0
- axion/runtime/usage.py +241 -0
- axion/runtime/workers.py +186 -0
- axion/telemetry/__init__.py +0 -0
- axion/telemetry/events.py +67 -0
- axion/telemetry/profile.py +49 -0
- axion/telemetry/sink.py +60 -0
- axion/telemetry/tracer.py +95 -0
- axion/tools/__init__.py +0 -0
- axion/tools/lane_completion.py +33 -0
- axion/tools/registry.py +853 -0
- axion/tools/tool_search.py +226 -0
- axion_code-1.0.0.dist-info/METADATA +709 -0
- axion_code-1.0.0.dist-info/RECORD +82 -0
- axion_code-1.0.0.dist-info/WHEEL +4 -0
- axion_code-1.0.0.dist-info/entry_points.txt +2 -0
- axion_code-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""ToolSearch - deferred tool schema loading.
|
|
2
|
+
|
|
3
|
+
When tools are too numerous to include in every request, ToolSearch
|
|
4
|
+
lets the model discover tools by keyword, then fetch their full schemas
|
|
5
|
+
on demand. This keeps the context window lean while still exposing a
|
|
6
|
+
large tool surface.
|
|
7
|
+
|
|
8
|
+
Usage by the model:
|
|
9
|
+
1. Call ToolSearch with a query like "notebook" or "select:Read,Edit"
|
|
10
|
+
2. Get back matching tool names + descriptions
|
|
11
|
+
3. Call ToolSearch with "select:ToolName" to fetch full schema
|
|
12
|
+
4. Now the tool can be invoked normally
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from axion.tools.registry import GlobalToolRegistry, ToolSpec, get_tool_registry
|
|
21
|
+
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# Types
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ToolSearchResult:
|
|
28
|
+
"""A single tool match from a search."""
|
|
29
|
+
|
|
30
|
+
name: str
|
|
31
|
+
description: str
|
|
32
|
+
score: float = 0.0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class ToolSearchOutput:
|
|
37
|
+
"""Output from a ToolSearch query."""
|
|
38
|
+
|
|
39
|
+
query: str
|
|
40
|
+
results: list[ToolSearchResult] = field(default_factory=list)
|
|
41
|
+
schemas: list[dict[str, Any]] = field(default_factory=list)
|
|
42
|
+
message: str = ""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Deferred tool registry
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
class DeferredToolRegistry:
|
|
50
|
+
"""Manages tools that are loaded on demand.
|
|
51
|
+
|
|
52
|
+
Tools can be registered as "deferred" — only their name and description
|
|
53
|
+
are included in the system prompt. Their full schema is fetched via
|
|
54
|
+
ToolSearch when the model needs them.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, full_registry: GlobalToolRegistry | None = None) -> None:
|
|
58
|
+
self._full_registry = full_registry or get_tool_registry()
|
|
59
|
+
self._deferred: dict[str, ToolSpec] = {}
|
|
60
|
+
self._active: set[str] = set() # Tools whose schemas are active
|
|
61
|
+
|
|
62
|
+
def defer_tool(self, name: str) -> None:
|
|
63
|
+
"""Mark a tool as deferred (schema not included by default)."""
|
|
64
|
+
tool = self._full_registry.get(name)
|
|
65
|
+
if tool:
|
|
66
|
+
self._deferred[name] = tool.spec
|
|
67
|
+
self._active.discard(name)
|
|
68
|
+
|
|
69
|
+
def activate_tool(self, name: str) -> ToolSpec | None:
|
|
70
|
+
"""Activate a deferred tool (include its schema)."""
|
|
71
|
+
if name in self._deferred:
|
|
72
|
+
self._active.add(name)
|
|
73
|
+
return self._deferred[name]
|
|
74
|
+
# Also check full registry
|
|
75
|
+
tool = self._full_registry.get(name)
|
|
76
|
+
if tool:
|
|
77
|
+
self._active.add(name)
|
|
78
|
+
return tool.spec
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
def deferred_tool_names(self) -> list[str]:
|
|
82
|
+
"""Get names of tools that are deferred but not yet active."""
|
|
83
|
+
return [n for n in self._deferred if n not in self._active]
|
|
84
|
+
|
|
85
|
+
def active_tool_names(self) -> list[str]:
|
|
86
|
+
"""Get names of tools whose schemas are currently active."""
|
|
87
|
+
return list(self._active)
|
|
88
|
+
|
|
89
|
+
def is_active(self, name: str) -> bool:
|
|
90
|
+
return name in self._active
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
# Search implementation
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
def tool_search(
|
|
98
|
+
query: str,
|
|
99
|
+
registry: GlobalToolRegistry | None = None,
|
|
100
|
+
max_results: int = 5,
|
|
101
|
+
) -> ToolSearchOutput:
|
|
102
|
+
"""Search for tools by query.
|
|
103
|
+
|
|
104
|
+
Query formats:
|
|
105
|
+
- "select:Read,Edit,Grep" — fetch exact tools by name (returns full schemas)
|
|
106
|
+
- "notebook jupyter" — keyword search, returns top matches by relevance
|
|
107
|
+
- "+slack send" — require "slack" in name, rank by remaining terms
|
|
108
|
+
"""
|
|
109
|
+
reg = registry or get_tool_registry()
|
|
110
|
+
all_tools = reg.all_tools()
|
|
111
|
+
|
|
112
|
+
# Parse query
|
|
113
|
+
query = query.strip()
|
|
114
|
+
|
|
115
|
+
# Direct selection: "select:Tool1,Tool2"
|
|
116
|
+
if query.lower().startswith("select:"):
|
|
117
|
+
names = [n.strip() for n in query[7:].split(",") if n.strip()]
|
|
118
|
+
return _select_tools(names, reg)
|
|
119
|
+
|
|
120
|
+
# Required name prefix: "+prefix keyword"
|
|
121
|
+
required_prefix = None
|
|
122
|
+
search_terms = query.lower().split()
|
|
123
|
+
if search_terms and search_terms[0].startswith("+"):
|
|
124
|
+
required_prefix = search_terms[0][1:]
|
|
125
|
+
search_terms = search_terms[1:]
|
|
126
|
+
|
|
127
|
+
# Keyword search
|
|
128
|
+
results: list[ToolSearchResult] = []
|
|
129
|
+
|
|
130
|
+
for tool_def in all_tools:
|
|
131
|
+
spec = tool_def.spec
|
|
132
|
+
name_lower = spec.name.lower()
|
|
133
|
+
desc_lower = spec.description.lower()
|
|
134
|
+
|
|
135
|
+
# Required prefix filter
|
|
136
|
+
if required_prefix and required_prefix not in name_lower:
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
# Score by keyword matches
|
|
140
|
+
score = 0.0
|
|
141
|
+
for term in search_terms:
|
|
142
|
+
if term in name_lower:
|
|
143
|
+
score += 2.0 # Name match worth more
|
|
144
|
+
if term in desc_lower:
|
|
145
|
+
score += 1.0
|
|
146
|
+
|
|
147
|
+
# Also match if query is substring of name
|
|
148
|
+
if query.lower() in name_lower:
|
|
149
|
+
score += 3.0
|
|
150
|
+
|
|
151
|
+
if score > 0:
|
|
152
|
+
results.append(ToolSearchResult(
|
|
153
|
+
name=spec.name,
|
|
154
|
+
description=spec.description[:200],
|
|
155
|
+
score=score,
|
|
156
|
+
))
|
|
157
|
+
|
|
158
|
+
# Sort by score descending
|
|
159
|
+
results.sort(key=lambda r: r.score, reverse=True)
|
|
160
|
+
results = results[:max_results]
|
|
161
|
+
|
|
162
|
+
return ToolSearchOutput(
|
|
163
|
+
query=query,
|
|
164
|
+
results=results,
|
|
165
|
+
message=f"Found {len(results)} tool(s) matching '{query}'",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _select_tools(
|
|
170
|
+
names: list[str], registry: GlobalToolRegistry
|
|
171
|
+
) -> ToolSearchOutput:
|
|
172
|
+
"""Fetch full schemas for specific tools by name."""
|
|
173
|
+
schemas: list[dict[str, Any]] = []
|
|
174
|
+
not_found: list[str] = []
|
|
175
|
+
|
|
176
|
+
for name in names:
|
|
177
|
+
tool = registry.get(name)
|
|
178
|
+
if tool:
|
|
179
|
+
schemas.append({
|
|
180
|
+
"name": tool.spec.name,
|
|
181
|
+
"description": tool.spec.description,
|
|
182
|
+
"input_schema": tool.spec.input_schema,
|
|
183
|
+
})
|
|
184
|
+
else:
|
|
185
|
+
not_found.append(name)
|
|
186
|
+
|
|
187
|
+
msg = f"Loaded {len(schemas)} tool schema(s)"
|
|
188
|
+
if not_found:
|
|
189
|
+
msg += f". Not found: {', '.join(not_found)}"
|
|
190
|
+
|
|
191
|
+
return ToolSearchOutput(
|
|
192
|
+
query=f"select:{','.join(names)}",
|
|
193
|
+
schemas=schemas,
|
|
194
|
+
message=msg,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# ---------------------------------------------------------------------------
|
|
199
|
+
# Tool spec for ToolSearch itself
|
|
200
|
+
# ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
TOOL_SEARCH_SPEC = ToolSpec(
|
|
203
|
+
name="ToolSearch",
|
|
204
|
+
description=(
|
|
205
|
+
"Fetches full schema definitions for deferred tools so they can be called. "
|
|
206
|
+
"Use 'select:Read,Edit' for exact selection, or keywords to search."
|
|
207
|
+
),
|
|
208
|
+
input_schema={
|
|
209
|
+
"type": "object",
|
|
210
|
+
"properties": {
|
|
211
|
+
"query": {
|
|
212
|
+
"type": "string",
|
|
213
|
+
"description": (
|
|
214
|
+
"Query to find tools. Use 'select:<name>' for direct selection, "
|
|
215
|
+
"or keywords to search."
|
|
216
|
+
),
|
|
217
|
+
},
|
|
218
|
+
"max_results": {
|
|
219
|
+
"type": "number",
|
|
220
|
+
"description": "Maximum results (default 5)",
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
"required": ["query"],
|
|
224
|
+
},
|
|
225
|
+
required_permission="read-only",
|
|
226
|
+
)
|