1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
|
-- The `subagents.models` tool: a bounded window onto the model catalog.
--
-- The catalog is far too large to inline into `subagents.run`'s description or
-- schema, so it is queried on demand instead. Four forms, chosen in this order:
--
-- { agent = "reviewer" } what that profile will actually run on
-- { model = "anthropic:sonnet"} exact lookup: wire name, reasoning levels
-- { provider =, query =, limit=} bounded search
-- { } inherited model/reasoning + provider counts
--
-- The agent form is the join the primary actually wants before overriding a
-- profile: it reports the profile's own model, or says the profile inherits
-- the primary model and shows what that is.
--
-- Results are short readable lines, not JSON. A truncated search says so
-- explicitly so the primary refines the query instead of assuming it saw
-- everything; `limit` is clamped to 1..50 (default 10) so no query can dump
-- the whole registry into the conversation.
--
-- The host owns the catalog and the provider-specific reasoning rules,
-- including effort levels a Lua protocol reports dynamically. Anything this
-- tool cannot confirm is still validated at spawn time — the catalog is
-- advice, the runtime is authoritative.
local spawn = require("subagents.spawn")
local DEFAULT_LIMIT = 10
local MAX_LIMIT = 50
local M = {}
local function host()
return require("panto").ext
end
local function ask(q)
local ok, result = pcall(host().models, q)
if not ok then
return nil, tostring(result)
end
if type(result) ~= "table" then
return nil, "the host returned no model information"
end
return result
end
local function format_overview(result)
local out = {
"inherited model: " .. (result.model or "(unknown)"),
"inherited reasoning: " .. (result.reasoning or "(provider default)"),
}
local providers = result.providers or {}
if #providers == 0 then
out[#out + 1] = "providers: (none configured)"
return table.concat(out, "\n")
end
out[#out + 1] = "providers:"
for _, provider in ipairs(providers) do
out[#out + 1] = string.format(" %s (%s, %d models)",
tostring(provider.name), tostring(provider.style or "?"), tonumber(provider.models) or 0)
end
return table.concat(out, "\n")
end
local function format_match(match)
local detail = {}
if match.wire_model then
detail[#detail + 1] = "wire " .. tostring(match.wire_model)
end
if match.reasoning_default then
detail[#detail + 1] = "default reasoning " .. tostring(match.reasoning_default)
end
if match.context_window then
detail[#detail + 1] = "context " .. tostring(match.context_window)
end
if match.max_tokens then
detail[#detail + 1] = "max tokens " .. tostring(match.max_tokens)
end
local line = " " .. tostring(match.ref)
if #detail > 0 then
line = line .. " — " .. table.concat(detail, ", ")
end
return line
end
local function format_exact(result, ref)
if not result.found then
return string.format(
"No configured model matches '%s'. Search with subagents.models { query = \"...\" }.", ref)
end
local out = { "model: " .. tostring(result.ref or ref) }
if result.wire_model then
out[#out + 1] = "wire model: " .. tostring(result.wire_model)
end
out[#out + 1] = "default reasoning: " .. (result.reasoning_default or "(provider default)")
local levels = result.reasoning_levels
if type(levels) == "table" and #levels > 0 then
out[#out + 1] = "reasoning levels: " .. table.concat(levels, ", ")
end
if result.context_window then
out[#out + 1] = "context window: " .. tostring(result.context_window)
end
if result.max_tokens then
out[#out + 1] = "max tokens: " .. tostring(result.max_tokens)
end
return table.concat(out, "\n")
end
local function format_search(result)
local matches = result.matches or {}
if #matches == 0 then
return "No models match that query."
end
local out = { string.format("%d match(es):", #matches) }
for _, match in ipairs(matches) do
out[#out + 1] = format_match(match)
end
if result.truncated then
out[#out + 1] = string.format("%d shown, more exist — refine the query", #matches)
end
return table.concat(out, "\n")
end
local function optional_string(value, field)
if value == nil then
return nil, nil
end
if type(value) ~= "string" or value == "" then
return nil, string.format("Error: `%s` must be a non-empty string when given.", field)
end
return value, nil
end
local function clamp_limit(value)
local limit = tonumber(value) or DEFAULT_LIMIT
limit = math.floor(limit)
if limit < 1 then
return 1
end
if limit > MAX_LIMIT then
return MAX_LIMIT
end
return limit
end
-- The agent form: report the profile's effective model, or say it inherits.
local function describe_agent(name, profiles)
profiles = spawn.profiles(profiles)
local profile = (profiles.by_name or {})[name]
if not profile then
return string.format("Error: unknown agent '%s'; known: %s", name, spawn.agent_names(profiles))
end
local out = { "agent: " .. profile.name }
if profile.description ~= "" then
out[#out + 1] = "description: " .. profile.description
end
if profile.model then
local result, err = ask({ model = profile.model })
if not result then
return "Error: " .. err
end
out[#out + 1] = format_exact(result, profile.model)
else
out[#out + 1] = "model: inherits the primary model"
local result, err = ask({})
if not result then
return "Error: " .. err
end
out[#out + 1] = format_overview(result)
end
if profile.reasoning then
out[#out + 1] = "profile reasoning: " .. profile.reasoning
end
return table.concat(out, "\n")
end
function M.handle(input, profiles)
input = input or {}
if type(input) ~= "table" then
return "Error: expected a table of arguments."
end
local agent, err = optional_string(input.agent, "agent")
if err then
return err
end
local model
model, err = optional_string(input.model, "model")
if err then
return err
end
local provider
provider, err = optional_string(input.provider, "provider")
if err then
return err
end
local text
text, err = optional_string(input.query, "query")
if err then
return err
end
if agent then
return describe_agent(agent, profiles)
end
if model then
local result, ask_err = ask({ model = model })
if not result then
return "Error: " .. ask_err
end
return format_exact(result, model)
end
if provider or text then
local result, ask_err = ask({ provider = provider, query = text, limit = clamp_limit(input.limit) })
if not result then
return "Error: " .. ask_err
end
return format_search(result)
end
local result, ask_err = ask({})
if not result then
return "Error: " .. ask_err
end
return format_overview(result)
end
return M
|