teapot-coding-agent 0.37.76 → 0.37.78
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.
- package/dist/agent/agent.js +55 -2
- package/dist/agent/tool-catalog.js +210 -0
- package/dist/agent/tools.js +128 -4
- package/dist/config-schema.js +8 -0
- package/dist/master.js +42 -30
- package/dist/migrations/session-titles.js +22 -24
- package/dist/model-meta.js +80 -6
- package/package.json +1 -1
- package/public/assets/{index-CzLw5lqw.js → index-DYA1sY0b.js} +9 -9
- package/public/assets/index-DYA1sY0b.js.map +1 -0
- package/public/assets/{shiki-C0hgNXdD.js → shiki-vS_Tti6t.js} +2 -2
- package/public/assets/{shiki-C0hgNXdD.js.map → shiki-vS_Tti6t.js.map} +1 -1
- package/public/index.html +1 -1
- package/public/assets/index-CzLw5lqw.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { closeSync,
|
|
1
|
+
import { closeSync, openSync, readSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
export const sessionTitles = {
|
|
4
4
|
order: 30,
|
|
@@ -7,6 +7,7 @@ export const sessionTitles = {
|
|
|
7
7
|
run(ctx) {
|
|
8
8
|
const raw = ctx.raw();
|
|
9
9
|
const agents = Array.isArray(raw.agents) ? raw.agents : [];
|
|
10
|
+
const isRecord = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
10
11
|
let changed = false;
|
|
11
12
|
/**
|
|
12
13
|
* Move the title out of `config.json` and into the session's own `session.json`.
|
|
@@ -15,32 +16,27 @@ export const sessionTitles = {
|
|
|
15
16
|
* and the copy that went stale. A title already present wins over a derived one, so an operator's
|
|
16
17
|
* rename is never replaced by the auto-generated name.
|
|
17
18
|
*/
|
|
19
|
+
/**
|
|
20
|
+
* Move `agents[].sessionTitle` into the `sessionTitles` MAP, keyed by session id.
|
|
21
|
+
*
|
|
22
|
+
* Per-agent was the wrong key: an agent moves between sessions, so the field had to be cleared on
|
|
23
|
+
* every switch (which reverted a rename), and a session no agent points at could not keep a name.
|
|
24
|
+
* A map also keeps the left panel to ONE config read instead of a `session.json` per directory —
|
|
25
|
+
* MEASURED: 838 sessions = 13.2ms of directory reads vs 1.2ms for the config file.
|
|
26
|
+
*/
|
|
27
|
+
const titles = (raw.sessionTitles = isRecord(raw.sessionTitles) ? raw.sessionTitles : {});
|
|
18
28
|
for (const a of agents) {
|
|
19
29
|
const session = typeof a.session === "string" ? a.session : "";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
}
|
|
32
|
-
const already = typeof manifest.title === "string" ? manifest.title.trim() : "";
|
|
33
|
-
if (!already) {
|
|
34
|
-
const fromConfig = typeof a.sessionTitle === "string" ? a.sessionTitle.trim() : "";
|
|
35
|
-
const derived = fromConfig || firstUserPromptText(path.join(dir, "chat.jsonl")) || session;
|
|
36
|
-
manifest.title = derived;
|
|
37
|
-
try {
|
|
38
|
-
mkdirSync(dir, { recursive: true });
|
|
39
|
-
writeFileSync(file, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
30
|
+
const legacy = typeof a.sessionTitle === "string" ? a.sessionTitle.trim() : "";
|
|
31
|
+
if (session && !session.includes("/") && !session.includes("..")) {
|
|
32
|
+
const already = typeof titles[session] === "string" ? String(titles[session]).trim() : "";
|
|
33
|
+
if (!already) {
|
|
34
|
+
const derived = legacy ||
|
|
35
|
+
firstUserPromptText(path.join(ctx.dataDir, "sessions", session, "chat.jsonl")) ||
|
|
36
|
+
session;
|
|
37
|
+
titles[session] = derived;
|
|
38
|
+
changed = true;
|
|
40
39
|
}
|
|
41
|
-
catch {
|
|
42
|
-
}
|
|
43
|
-
changed = true;
|
|
44
40
|
}
|
|
45
41
|
/**
|
|
46
42
|
* Only report a change when one happened: a migration that claims success every boot rewrites
|
|
@@ -51,6 +47,8 @@ export const sessionTitles = {
|
|
|
51
47
|
changed = true;
|
|
52
48
|
}
|
|
53
49
|
}
|
|
50
|
+
if (Object.keys(titles).length === 0 && raw.sessionTitles !== undefined)
|
|
51
|
+
delete raw.sessionTitles;
|
|
54
52
|
if (changed)
|
|
55
53
|
ctx.touch();
|
|
56
54
|
return changed;
|
package/dist/model-meta.js
CHANGED
|
@@ -173,13 +173,87 @@ export function metaFrom(m) {
|
|
|
173
173
|
...(m.reasoning === true ? { reasoning: true } : {}),
|
|
174
174
|
};
|
|
175
175
|
}
|
|
176
|
-
/**
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
/**
|
|
177
|
+
* The model list for a provider: the PROVIDER's own catalogue, enriched by models.dev.
|
|
178
|
+
*
|
|
179
|
+
* The provider is the authority on which models exist — models.dev lags and omits entries. MEASURED:
|
|
180
|
+
* `reflection/beam:free` is served by Command Code (94 models) and absent from models.dev's
|
|
181
|
+
* `commandcode` record (72), so a models.dev-only list silently drops models the operator can
|
|
182
|
+
* actually call.
|
|
183
|
+
*
|
|
184
|
+
* models.dev is still needed for CAPABILITY, because most providers publish none: Command Code's
|
|
185
|
+
* `/models` returns only `{context_length, created, id, name, object, owned_by, supported_endpoints}`
|
|
186
|
+
* while OpenRouter publishes `architecture` (modalities) and `supported_parameters`. So:
|
|
187
|
+
*
|
|
188
|
+
* - provider list wins for MEMBERSHIP;
|
|
189
|
+
* - a provider field, when present, wins for that field;
|
|
190
|
+
* - models.dev fills only what the provider did not say.
|
|
191
|
+
*
|
|
192
|
+
* When the provider cannot be reached at all we fall back to the models.dev list rather than showing
|
|
193
|
+
* nothing.
|
|
194
|
+
*/
|
|
195
|
+
export async function fetchModelList(baseUrl, apiKey) {
|
|
196
|
+
const [snap, upstream] = await Promise.all([
|
|
197
|
+
loadDb(),
|
|
198
|
+
fetchProviderModels(baseUrl, apiKey).catch(() => null),
|
|
199
|
+
]);
|
|
179
200
|
const provider = providerFor(snap.providers, baseUrl);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
201
|
+
const dbModels = provider?.models ? Object.values(provider.models).map(metaFrom).filter((m) => m.id) : [];
|
|
202
|
+
if (!upstream)
|
|
203
|
+
return dbModels;
|
|
204
|
+
const byId = new Map(dbModels.map((m) => [m.id, m]));
|
|
205
|
+
return upstream
|
|
206
|
+
.map((m) => mergeMeta(m, byId.get(m.id)))
|
|
207
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
208
|
+
}
|
|
209
|
+
/** a provider's own `/models`, normalised, or null when it does not answer usefully */
|
|
210
|
+
async function fetchProviderModels(baseUrl, apiKey) {
|
|
211
|
+
const url = `${String(baseUrl).replace(/\/+$/, "")}/models`;
|
|
212
|
+
const res = await fetch(url, {
|
|
213
|
+
headers: { ...(apiKey ? { authorization: `Bearer ${apiKey}` } : {}), ...providerHeaders(baseUrl) },
|
|
214
|
+
signal: AbortSignal.timeout(15_000),
|
|
215
|
+
});
|
|
216
|
+
if (!res.ok)
|
|
217
|
+
return null;
|
|
218
|
+
const j = (await res.json());
|
|
219
|
+
const out = [];
|
|
220
|
+
for (const m of j.data ?? []) {
|
|
221
|
+
if (typeof m.id !== "string" || !m.id)
|
|
222
|
+
continue;
|
|
223
|
+
const input = strings(m.architecture?.input_modalities);
|
|
224
|
+
const output = strings(m.architecture?.output_modalities);
|
|
225
|
+
const ctx = typeof m.context_length === "number" ? m.context_length : undefined;
|
|
226
|
+
// `reasoning` in supported_parameters means the model accepts a reasoning_effort-style control
|
|
227
|
+
const params = strings(m.supported_parameters);
|
|
228
|
+
out.push({
|
|
229
|
+
id: m.id,
|
|
230
|
+
...(typeof m.name === "string" ? { name: m.name } : {}),
|
|
231
|
+
...(ctx !== undefined ? { contextLength: ctx } : {}),
|
|
232
|
+
...(input ? { modalities: { input, output: output ?? ["text"] } } : {}),
|
|
233
|
+
...(params?.some((p) => p === "reasoning" || p === "reasoning_effort") ? { reasoning: true } : {}),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return out.length ? out : null;
|
|
237
|
+
}
|
|
238
|
+
/** provider fields win; models.dev fills only the gaps */
|
|
239
|
+
function mergeMeta(own, db) {
|
|
240
|
+
if (!db)
|
|
241
|
+
return own;
|
|
242
|
+
return {
|
|
243
|
+
id: own.id,
|
|
244
|
+
...(own.name ?? db.name ? { name: own.name ?? db.name } : {}),
|
|
245
|
+
...(own.contextLength ?? db.contextLength ? { contextLength: own.contextLength ?? db.contextLength } : {}),
|
|
246
|
+
...(own.modalities ?? db.modalities
|
|
247
|
+
? { modalities: own.modalities ?? db.modalities }
|
|
248
|
+
: {}),
|
|
249
|
+
/**
|
|
250
|
+
* Efforts come from models.dev even when the provider listed the model: no provider publishes the
|
|
251
|
+
* effort LEVELS, only that reasoning exists. `own.reasoning` still wins for the yes/no question.
|
|
252
|
+
*/
|
|
253
|
+
...(db.supportedEfforts ? { supportedEfforts: db.supportedEfforts } : {}),
|
|
254
|
+
...(own.reasoning ?? db.reasoning ? { reasoning: own.reasoning ?? db.reasoning } : {}),
|
|
255
|
+
...(db.pricing ? { pricing: db.pricing } : {}),
|
|
256
|
+
};
|
|
183
257
|
}
|
|
184
258
|
export function peekModelList(baseUrl) {
|
|
185
259
|
if (!fresh(db))
|