howtofixcli 0.7.13 → 0.7.15
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/chunk-27TRKD65.js +2 -0
- package/dist/chunk-3FYCFLNK.js +1 -0
- package/dist/chunk-56SEAGAQ.js +1 -0
- package/dist/chunk-6FUWMLWO.js +1 -0
- package/dist/chunk-7CHDNTH6.js +1 -0
- package/dist/chunk-7DTQKDO4.js +2 -0
- package/dist/chunk-AGGIYXN7.js +2 -0
- package/dist/chunk-AUDMRGWF.js +189 -0
- package/dist/chunk-CPJKKHGH.js +1 -0
- package/dist/chunk-DAPXGSGD.js +12 -0
- package/dist/chunk-DOD7MUGX.js +1 -0
- package/dist/chunk-DV2BBBBJ.js +2 -0
- package/dist/chunk-EIBOS5KO.js +5 -0
- package/dist/chunk-EYWOVCSS.js +6 -0
- package/dist/chunk-FPXVDP32.js +8 -0
- package/dist/chunk-GXB7RZHH.js +1 -0
- package/dist/chunk-HT3MFLAV.js +34 -0
- package/dist/chunk-ILDDDLRC.js +1 -0
- package/dist/chunk-JFYVRWJI.js +2 -0
- package/dist/chunk-JMKK26AT.js +2 -0
- package/dist/chunk-LAHVHRCS.js +2 -0
- package/dist/chunk-LOOFOBTS.js +1 -0
- package/dist/chunk-LSJC73DG.js +1 -0
- package/dist/chunk-MOKUBKE7.js +1 -0
- package/dist/chunk-N345KWWK.js +1 -0
- package/dist/chunk-ONZLNFQ2.js +3 -0
- package/dist/chunk-ORJ2QZBG.js +3 -0
- package/dist/chunk-PCALBZOO.js +2 -0
- package/dist/chunk-SX5VE6XA.js +110 -0
- package/dist/chunk-TSEKPGK4.js +1 -0
- package/dist/chunk-UNK5HRQR.js +1 -0
- package/dist/chunk-UTYFLLSM.js +1 -0
- package/dist/chunk-V2RPZMLK.js +1 -0
- package/dist/chunk-V3R4FYTJ.js +1 -0
- package/dist/chunk-V656XVXV.js +8 -0
- package/dist/chunk-VLMQSD25.js +1 -0
- package/dist/chunk-WKUJ7CFO.js +1 -0
- package/dist/chunk-WOT6VMZA.js +1 -0
- package/dist/chunk-WUPU6LTD.js +4 -0
- package/dist/chunk-WX3V5P7U.js +5 -0
- package/dist/chunk-Y7YM5R75.js +5 -0
- package/dist/chunk-YW7NORH3.js +2 -0
- package/dist/main.js +99 -540
- package/package.json +17 -64
- package/dist/account-credential.js +0 -136
- package/dist/account-credential.test.js +0 -38
- package/dist/activity.js +0 -124
- package/dist/agent-defs.js +0 -237
- package/dist/agent-limiter.js +0 -68
- package/dist/agents.js +0 -554
- package/dist/anim.js +0 -361
- package/dist/args.js +0 -228
- package/dist/at-file.js +0 -28
- package/dist/brand-extension.js +0 -87
- package/dist/brand.js +0 -76
- package/dist/browser.js +0 -58
- package/dist/cache-report.js +0 -139
- package/dist/checkpoint.js +0 -437
- package/dist/claims.js +0 -167
- package/dist/cli-auth.js +0 -142
- package/dist/cli-checkin.js +0 -120
- package/dist/cli-credentials.js +0 -254
- package/dist/clipboard.js +0 -149
- package/dist/collapse.js +0 -129
- package/dist/commands.js +0 -294
- package/dist/compaction-guard.js +0 -81
- package/dist/context-extension.js +0 -17
- package/dist/dcp/commands.js +0 -172
- package/dist/dcp/compress-state.js +0 -139
- package/dist/dcp/compress.js +0 -329
- package/dist/dcp/dcp-extension.js +0 -295
- package/dist/dcp/dedup.js +0 -93
- package/dist/dcp/ids.js +0 -189
- package/dist/dcp/index.js +0 -25
- package/dist/dcp/middle-prune.js +0 -76
- package/dist/dcp/prompts.js +0 -68
- package/dist/dcp/protected-content.js +0 -221
- package/dist/dcp/protected.js +0 -161
- package/dist/dcp/prune.js +0 -418
- package/dist/dcp/state.js +0 -176
- package/dist/device-id.js +0 -90
- package/dist/device-id.test.js +0 -23
- package/dist/errors.js +0 -63
- package/dist/extensions.js +0 -48
- package/dist/fatal.js +0 -35
- package/dist/feed.js +0 -498
- package/dist/fs-atomic.js +0 -54
- package/dist/gateway-errors.js +0 -90
- package/dist/git.js +0 -230
- package/dist/goal-extension.js +0 -88
- package/dist/goal.js +0 -222
- package/dist/hooks-extension.js +0 -176
- package/dist/hooks.js +0 -443
- package/dist/howtofix-account.js +0 -735
- package/dist/hud-line.js +0 -153
- package/dist/hwid.js +0 -130
- package/dist/interrupt.js +0 -60
- package/dist/json-events.js +0 -50
- package/dist/level-style.js +0 -112
- package/dist/loop-guard.js +0 -95
- package/dist/lsp-tools.js +0 -32
- package/dist/lsp.js +0 -429
- package/dist/mcp-config.js +0 -85
- package/dist/mcp-lifecycle.js +0 -147
- package/dist/model-catalog.js +0 -217
- package/dist/model-facts.js +0 -35
- package/dist/model-resolve.js +0 -65
- package/dist/notes.js +0 -78
- package/dist/oauth-login.js +0 -568
- package/dist/paths.js +0 -61
- package/dist/permissions.js +0 -298
- package/dist/plan-mode.js +0 -363
- package/dist/preset-command.js +0 -227
- package/dist/preset-extension.js +0 -35
- package/dist/preset-mcp.js +0 -292
- package/dist/presets.js +0 -727
- package/dist/prompt-files.js +0 -103
- package/dist/prompt-text.js +0 -41
- package/dist/proxy.js +0 -137
- package/dist/quality/ast.js +0 -196
- package/dist/quality/cognitive.js +0 -83
- package/dist/quality/cyclomatic.js +0 -30
- package/dist/quality/halstead.js +0 -83
- package/dist/quality/index.js +0 -18
- package/dist/quality/length.js +0 -3
- package/dist/quality/maintainability.js +0 -15
- package/dist/quality/nesting.js +0 -57
- package/dist/quality/report.js +0 -803
- package/dist/quality/smells.js +0 -67
- package/dist/quality-tool.js +0 -63
- package/dist/read-only-commands.js +0 -597
- package/dist/repo-map.js +0 -292
- package/dist/rtk.js +0 -193
- package/dist/sandbox.js +0 -105
- package/dist/schema.js +0 -214
- package/dist/session-lifecycle.js +0 -19
- package/dist/session-retention.js +0 -111
- package/dist/session.js +0 -447
- package/dist/sessions.js +0 -100
- package/dist/settings.js +0 -148
- package/dist/skills.js +0 -35
- package/dist/slop-tool.js +0 -96
- package/dist/spend-cap.js +0 -43
- package/dist/subagent.js +0 -171
- package/dist/tar.js +0 -146
- package/dist/theme.js +0 -79
- package/dist/thinking.js +0 -45
- package/dist/todo.js +0 -114
- package/dist/tool-loadout.js +0 -97
- package/dist/tool-render.js +0 -769
- package/dist/tools.js +0 -176
- package/dist/ui.js +0 -4778
- package/dist/update.js +0 -551
- package/dist/usage-segment.js +0 -69
- package/dist/web-tools.js +0 -46
- package/dist/web.js +0 -265
- package/dist/welcome.js +0 -179
- package/dist/workflow-engine.js +0 -246
- package/dist/workflow-progress.js +0 -446
- package/dist/workflow-view.js +0 -195
- package/dist/workflow.js +0 -349
package/dist/model-catalog.js
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Real context windows and reasoning support, from models.dev.
|
|
3
|
-
*
|
|
4
|
-
* `GET /v1/models` on the gateway returns ids and nothing else — verified against
|
|
5
|
-
* the live service, whose response carries only `id`, `object`, `created`,
|
|
6
|
-
* `owned_by` and `supported_endpoint_types`. So a synced model has no declared
|
|
7
|
-
* window and no declared capability, and `/connect` filled the gap with two
|
|
8
|
-
* guesses that were both wrong in the same direction:
|
|
9
|
-
*
|
|
10
|
-
* - `contextWindow: 128_000` for every model. The gateway serves models with
|
|
11
|
-
* 1,000,000-token windows (deepseek-v4.1-flash, kimi-k3, mimo-v2.5), so the
|
|
12
|
-
* context meter read ~8x too full and the CLI compacted a conversation that
|
|
13
|
-
* had barely started.
|
|
14
|
-
* - no `reasoning` field at all. The SDK gates thinking on it — the schema is
|
|
15
|
-
* `reasoning: boolean`, and `getSupportedThinkingLevels` returns `["off"]`
|
|
16
|
-
* the moment it is falsy — so every synced model lost its thinking levels
|
|
17
|
-
* even though the model reasons. That is the bug a user reports as "thinking
|
|
18
|
-
* is missing on a model that has it".
|
|
19
|
-
*
|
|
20
|
-
* models.dev publishes exactly these facts per model (`limit.context`,
|
|
21
|
-
* `limit.output`, `reasoning`). It is a public catalogue, so the CLI can read it
|
|
22
|
-
* instead of guessing. Nothing here is bundled: the catalogue is fetched once,
|
|
23
|
-
* slimmed to the three fields that are used, and cached next to the agent state,
|
|
24
|
-
* so a later start can repair models without a network call.
|
|
25
|
-
*
|
|
26
|
-
* The pure half (index, lookup) is separated from the network half so the
|
|
27
|
-
* matching rules are testable without a fetch.
|
|
28
|
-
*/
|
|
29
|
-
/** Where the catalogue lives. One document, every provider. */
|
|
30
|
-
export const MODELS_DEV_URL = "https://models.dev/api.json";
|
|
31
|
-
/** How long to wait for the catalogue. It is ~5 MB, so this is generous. */
|
|
32
|
-
export const CATALOG_TIMEOUT_MS = 20_000;
|
|
33
|
-
/**
|
|
34
|
-
* Providers whose entry wins when the same model is listed by several.
|
|
35
|
-
*
|
|
36
|
-
* The catalogue is community-maintained and mirrors every reseller, so one model
|
|
37
|
-
* id resolves to a dozen near-identical entries. The first-party listing is the
|
|
38
|
-
* one whose numbers describe the model rather than a middleman's limits, so the
|
|
39
|
-
* search prefers it. Order matters only among the preferred set; everything else
|
|
40
|
-
* is ordered alphabetically so the result never depends on object iteration.
|
|
41
|
-
*/
|
|
42
|
-
const PREFERRED_PROVIDERS = ["anthropic", "openai", "google", "deepseek", "zai", "xai", "moonshotai", "minimax", "alibaba", "xiaomi", "meta", "tencent", "nvidia", "venice", "openrouter"];
|
|
43
|
-
/** The comparison form of a model id: case and punctuation are not meaningful. */
|
|
44
|
-
export function normalizeModelId(id) {
|
|
45
|
-
return id.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
46
|
-
}
|
|
47
|
-
/** The last path segment of an id, normalized: `z-ai/glm-5.3-fast` -> `glm53fast`. */
|
|
48
|
-
function lastSegment(id) {
|
|
49
|
-
const parts = id.split("/");
|
|
50
|
-
return normalizeModelId(parts[parts.length - 1] ?? id);
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Index every model in the catalogue document.
|
|
54
|
-
*
|
|
55
|
-
* One entry per normalized id; when several providers list the same model, the
|
|
56
|
-
* preferred provider wins (see {@link PREFERRED_PROVIDERS}). A model whose entry
|
|
57
|
-
* carries no `limit.context` is skipped: without a window there is nothing to
|
|
58
|
-
* correct, and indexing it would let a bad entry shadow a good one.
|
|
59
|
-
*/
|
|
60
|
-
export function buildCatalogIndex(raw) {
|
|
61
|
-
const index = new Map();
|
|
62
|
-
const owners = new Map();
|
|
63
|
-
if (!raw || typeof raw !== "object")
|
|
64
|
-
return index;
|
|
65
|
-
const providers = raw;
|
|
66
|
-
const priority = (provider) => {
|
|
67
|
-
const at = PREFERRED_PROVIDERS.indexOf(provider);
|
|
68
|
-
return at === -1 ? PREFERRED_PROVIDERS.length : at;
|
|
69
|
-
};
|
|
70
|
-
// Sorted so the winner among equally-preferred providers is decided by name,
|
|
71
|
-
// not by the insertion order of a parsed JSON object — this number feeds a
|
|
72
|
-
// token budget, so it must not depend on how the document happened to be built.
|
|
73
|
-
for (const provider of Object.keys(providers).sort()) {
|
|
74
|
-
const models = providers[provider]?.models;
|
|
75
|
-
if (!models || typeof models !== "object")
|
|
76
|
-
continue;
|
|
77
|
-
for (const id of Object.keys(models)) {
|
|
78
|
-
const model = models[id];
|
|
79
|
-
const context = Number(model?.limit?.context);
|
|
80
|
-
if (!Number.isFinite(context) || context <= 0)
|
|
81
|
-
continue;
|
|
82
|
-
const key = normalizeModelId(id);
|
|
83
|
-
const rank = priority(provider);
|
|
84
|
-
const held = owners.get(key);
|
|
85
|
-
// A preferred provider always wins; otherwise the first (alphabetical) one
|
|
86
|
-
// keeps the slot. Never let a later, less-preferred listing overwrite it.
|
|
87
|
-
if (held !== undefined && held <= rank)
|
|
88
|
-
continue;
|
|
89
|
-
const output = Number(model?.limit?.output);
|
|
90
|
-
index.set(key, {
|
|
91
|
-
contextWindow: Math.floor(context),
|
|
92
|
-
maxTokens: Number.isFinite(output) && output > 0 ? Math.floor(output) : undefined,
|
|
93
|
-
reasoning: model?.reasoning === true,
|
|
94
|
-
vision: Array.isArray(model?.modalities?.input) && model.modalities.input.includes("image"),
|
|
95
|
-
});
|
|
96
|
-
owners.set(key, rank);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return index;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* The catalogue entry for a gateway model id, or undefined when it is unknown.
|
|
103
|
-
*
|
|
104
|
-
* Three passes, in order of how literal the match is: the whole id, the last path
|
|
105
|
-
* segment, then a suffix. The gateway renames models (`deepseek-v4.1-flash` is
|
|
106
|
-
* `DeepSeek-V4.1-Flash` in the catalogue, `glm-5.3-flash` is `zai/glm-5.3-flash`)
|
|
107
|
-
* and drops vendor prefixes (`anthropic/claude-sonnet-5` keeps its own), so a
|
|
108
|
-
* single exact match finds less than half. A miss is not a failure: the caller
|
|
109
|
-
* keeps its conservative default, which is the behaviour before this module.
|
|
110
|
-
*/
|
|
111
|
-
export function lookupModel(index, id) {
|
|
112
|
-
const wanted = normalizeModelId(id);
|
|
113
|
-
if (!wanted)
|
|
114
|
-
return undefined;
|
|
115
|
-
const exact = index.get(wanted);
|
|
116
|
-
if (exact)
|
|
117
|
-
return exact;
|
|
118
|
-
const wantedTail = lastSegment(id);
|
|
119
|
-
for (const [key, entry] of index) {
|
|
120
|
-
if (lastSegment(key) === wantedTail)
|
|
121
|
-
return entry;
|
|
122
|
-
}
|
|
123
|
-
for (const [key, entry] of index) {
|
|
124
|
-
if (key.endsWith(wanted) || wanted.endsWith(key))
|
|
125
|
-
return entry;
|
|
126
|
-
}
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
/** Where the slimmed catalogue is cached, so a later start needs no network. */
|
|
130
|
-
export function catalogCachePath(agentDir) {
|
|
131
|
-
return `${agentDir}/models-dev-cache.json`;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* The serialized form of an index: `{ id: [context, output, reasoning, vision] }`.
|
|
135
|
-
*
|
|
136
|
-
* An array rather than an object per entry because the catalogue is 8,000+ rows
|
|
137
|
-
* and repeating the keys eight thousand times triples the file for no reader's
|
|
138
|
-
* benefit. The cache is internal — nothing outside this module parses it.
|
|
139
|
-
*
|
|
140
|
-
* `vision` was added after the first release, so a cache written by the older
|
|
141
|
-
* build is a 3-tuple. {@link deserializeCatalog} reads the fourth slot as
|
|
142
|
-
* optional and defaults it to `false`, which is the behaviour that build had;
|
|
143
|
-
* the next fetch fills it in. Bumping a format number would instead throw the
|
|
144
|
-
* whole cache away and cost every user a 5 MB download on the next start.
|
|
145
|
-
*/
|
|
146
|
-
export function serializeCatalog(index) {
|
|
147
|
-
const out = {};
|
|
148
|
-
for (const [key, entry] of index) {
|
|
149
|
-
out[key] = [entry.contextWindow, entry.maxTokens ?? 0, entry.reasoning ? 1 : 0, entry.vision ? 1 : 0];
|
|
150
|
-
}
|
|
151
|
-
return JSON.stringify(out);
|
|
152
|
-
}
|
|
153
|
-
/** Read back {@link serializeCatalog}. Returns an empty index for anything unusable. */
|
|
154
|
-
export function deserializeCatalog(text) {
|
|
155
|
-
const index = new Map();
|
|
156
|
-
let parsed;
|
|
157
|
-
try {
|
|
158
|
-
parsed = JSON.parse(text);
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
return index;
|
|
162
|
-
}
|
|
163
|
-
if (!parsed || typeof parsed !== "object")
|
|
164
|
-
return index;
|
|
165
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
166
|
-
if (!Array.isArray(value) || value.length < 3)
|
|
167
|
-
continue;
|
|
168
|
-
const context = Number(value[0]);
|
|
169
|
-
const output = Number(value[1]);
|
|
170
|
-
if (!Number.isFinite(context) || context <= 0)
|
|
171
|
-
continue;
|
|
172
|
-
index.set(key, {
|
|
173
|
-
contextWindow: Math.floor(context),
|
|
174
|
-
maxTokens: Number.isFinite(output) && output > 0 ? Math.floor(output) : undefined,
|
|
175
|
-
reasoning: value[2] === 1 || value[2] === true,
|
|
176
|
-
// Absent in a cache written before this field existed: read it as "no
|
|
177
|
-
// vision", which is what that build assumed, and let the next fetch correct it.
|
|
178
|
-
vision: value[3] === 1 || value[3] === true,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
return index;
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Fetch the catalogue and index it.
|
|
185
|
-
*
|
|
186
|
-
* Never throws: a catalogue that cannot be fetched is a reason to keep the
|
|
187
|
-
* conservative defaults, not a reason for `/connect` to fail. The caller reports
|
|
188
|
-
* which of the two happened.
|
|
189
|
-
*/
|
|
190
|
-
export async function fetchCatalog(options) {
|
|
191
|
-
const url = options?.url ?? MODELS_DEV_URL;
|
|
192
|
-
const doFetch = options?.fetchImpl ?? fetch;
|
|
193
|
-
const timeoutMs = options?.timeoutMs ?? CATALOG_TIMEOUT_MS;
|
|
194
|
-
const controller = new AbortController();
|
|
195
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
196
|
-
try {
|
|
197
|
-
const response = await doFetch(url, { signal: controller.signal, headers: { Accept: "application/json" } });
|
|
198
|
-
if (!response.ok) {
|
|
199
|
-
return { ok: false, message: `the model catalogue answered ${response.status}` };
|
|
200
|
-
}
|
|
201
|
-
const index = buildCatalogIndex(await response.json());
|
|
202
|
-
if (index.size === 0) {
|
|
203
|
-
return { ok: false, message: "the model catalogue was empty or in an unexpected shape" };
|
|
204
|
-
}
|
|
205
|
-
return { ok: true, index };
|
|
206
|
-
}
|
|
207
|
-
catch (cause) {
|
|
208
|
-
const aborted = cause instanceof Error && cause.name === "AbortError";
|
|
209
|
-
return {
|
|
210
|
-
ok: false,
|
|
211
|
-
message: aborted ? `the model catalogue did not answer within ${Math.round(timeoutMs / 1000)}s` : "could not reach the model catalogue",
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
finally {
|
|
215
|
-
clearTimeout(timer);
|
|
216
|
-
}
|
|
217
|
-
}
|
package/dist/model-facts.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The two facts a model picker is useless without: how much context it takes and
|
|
3
|
-
* what it costs.
|
|
4
|
-
*
|
|
5
|
-
* Every model entry already carries `contextWindow` and `cost`, but the picker
|
|
6
|
-
* showed only the provider, so a free model and an expensive one looked the
|
|
7
|
-
* same. These helpers turn those numbers into one short line for the list.
|
|
8
|
-
*/
|
|
9
|
-
/** 200000 -> "200K", 1000000 -> "1M", 1500000 -> "1.5M". Empty when unknown. */
|
|
10
|
-
export function formatContextWindow(tokens) {
|
|
11
|
-
if (!tokens || !Number.isFinite(tokens) || tokens <= 0)
|
|
12
|
-
return "";
|
|
13
|
-
if (tokens >= 1_000_000) {
|
|
14
|
-
// Rounded to one decimal, then trimmed: 1_048_576 is "1M", not "1.0M".
|
|
15
|
-
return `${Number((tokens / 1_000_000).toFixed(1))}M`;
|
|
16
|
-
}
|
|
17
|
-
if (tokens >= 1_000)
|
|
18
|
-
return `${Math.round(tokens / 1_000)}K`;
|
|
19
|
-
return String(tokens);
|
|
20
|
-
}
|
|
21
|
-
/** Price per million tokens for input and output, or "free" when both are zero. */
|
|
22
|
-
export function formatModelPrice(cost) {
|
|
23
|
-
if (!cost)
|
|
24
|
-
return "";
|
|
25
|
-
if (cost.input <= 0 && cost.output <= 0)
|
|
26
|
-
return "free";
|
|
27
|
-
const money = (value) => (value === 0 ? "0" : String(Number(value >= 1 ? value.toFixed(2) : value.toFixed(3))));
|
|
28
|
-
return `$${money(cost.input)}/$${money(cost.output)} per M`;
|
|
29
|
-
}
|
|
30
|
-
/** "1M ctx · free" — the summary shown beside a model in the picker. */
|
|
31
|
-
export function describeModelFacts(model) {
|
|
32
|
-
const context = formatContextWindow(model.contextWindow);
|
|
33
|
-
const price = formatModelPrice(model.cost);
|
|
34
|
-
return [context ? `${context} ctx` : "", price].filter(Boolean).join(" · ");
|
|
35
|
-
}
|
package/dist/model-resolve.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolve a model spec the way the CLI's own `--model` flag does.
|
|
3
|
-
*
|
|
4
|
-
* Exact `provider/id` or a bare `id` first, then a case-insensitive substring
|
|
5
|
-
* match, so both the name `/model` prints and a shorthand work. Lifted out of
|
|
6
|
-
* `session.ts` when subagents gained a model of their own: two resolvers would
|
|
7
|
-
* drift, and a subagent picking a different model than the parent asked for
|
|
8
|
-
* would read as the option being broken.
|
|
9
|
-
*/
|
|
10
|
-
export function resolveModel(models, spec) {
|
|
11
|
-
const exact = models.find((model) => `${model.provider}/${model.id}` === spec || model.id === spec);
|
|
12
|
-
if (exact)
|
|
13
|
-
return exact;
|
|
14
|
-
const needle = spec.toLowerCase();
|
|
15
|
-
return models.find((model) => model.id.toLowerCase().includes(needle) || `${model.provider}/${model.id}`.toLowerCase().includes(needle));
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Order a catalogue for choosing a default model, best first.
|
|
19
|
-
*
|
|
20
|
-
* A gateway lists its catalogue in no useful order — `anthropic/claude-opus-4.8`
|
|
21
|
-
* can come first while the fast general-purpose models are far down the list — so
|
|
22
|
-
* "use whatever is first" hands a new user the most expensive thing on the
|
|
23
|
-
* account, or an image model that cannot hold a conversation.
|
|
24
|
-
*
|
|
25
|
-
* Image, embedding, audio and video models are pushed to the end: they cannot
|
|
26
|
-
* answer a coding prompt, so selecting one would look like a broken install. The
|
|
27
|
-
* caller then probes the first few and takes the first that answers.
|
|
28
|
-
*/
|
|
29
|
-
const PREFERRED_FAMILIES = ["deepseek", "qwen", "claude", "gpt-5", "gpt-4", "gemini", "glm", "mimo", "kimi", "llama"];
|
|
30
|
-
const NOT_CHAT = /(image|embedding|embed|whisper|tts|audio|video|rerank|moderation|dall-e|stable-diffusion|sora)/i;
|
|
31
|
-
export function orderModelsForDefault(ids) {
|
|
32
|
-
const rank = (id) => {
|
|
33
|
-
if (NOT_CHAT.test(id))
|
|
34
|
-
return 100;
|
|
35
|
-
const family = PREFERRED_FAMILIES.findIndex((name) => id.toLowerCase().includes(name));
|
|
36
|
-
return family === -1 ? 50 : family;
|
|
37
|
-
};
|
|
38
|
-
// Stable within a rank, so the gateway's own order decides ties.
|
|
39
|
-
return ids
|
|
40
|
-
.map((id, index) => ({ id, index }))
|
|
41
|
-
.sort((a, b) => rank(a.id) - rank(b.id) || a.index - b.index)
|
|
42
|
-
.map((entry) => entry.id);
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* The model a session should start on when nothing was asked for.
|
|
46
|
-
*
|
|
47
|
-
* The model runtime knows every model in the catalog it ships, including
|
|
48
|
-
* providers this account holds no credential for, and its own default is the
|
|
49
|
-
* catalog's — `opencode/kimi-k2.6` — not the account's. Starting there is not a
|
|
50
|
-
* cosmetic problem: the first prompt goes to a provider that cannot answer and
|
|
51
|
-
* comes back as an empty assistant message, with nothing in the transcript to
|
|
52
|
-
* explain why it looks hung.
|
|
53
|
-
*
|
|
54
|
-
* So the default comes from the models that are actually available, and a
|
|
55
|
-
* HowToFix provider wins over any other reachable one: an install that signed in
|
|
56
|
-
* to HowToFix should run HowToFix's models.
|
|
57
|
-
*/
|
|
58
|
-
export function defaultModelFor(available, ownProviders = []) {
|
|
59
|
-
if (available.length === 0)
|
|
60
|
-
return undefined;
|
|
61
|
-
const own = available.filter((model) => ownProviders.includes(model.provider));
|
|
62
|
-
const pool = own.length > 0 ? own : available;
|
|
63
|
-
const first = orderModelsForDefault(pool.map((model) => model.id))[0];
|
|
64
|
-
return pool.find((model) => model.id === first) ?? pool[0];
|
|
65
|
-
}
|
package/dist/notes.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A shared scratchpad for facts that must survive context compaction and reach
|
|
3
|
-
* every agent working on the task.
|
|
4
|
-
*
|
|
5
|
-
* A shared notes store for the session.
|
|
6
|
-
* Why it matters: the `task` tool spawns subagents with their own context
|
|
7
|
-
* window, so anything the main agent learned while scouting is invisible to them,
|
|
8
|
-
* and anything they discover dies with their run. A note written once is visible
|
|
9
|
-
* to the parent and to every sibling, and it survives a compaction that would
|
|
10
|
-
* otherwise drop the discovery from the transcript.
|
|
11
|
-
*
|
|
12
|
-
* The store is process-wide on purpose: `agents.ts` opens subagent sessions in
|
|
13
|
-
* this same process, so they share it. It is in-memory only — notes are working
|
|
14
|
-
* state for one session, not project documentation (that is `AGENTS.md`).
|
|
15
|
-
*/
|
|
16
|
-
import { StringEnum } from "@netrodev/hxf-ai";
|
|
17
|
-
import { defineTool } from "@netrodev/hxf-coding-agent";
|
|
18
|
-
import { Type } from "typebox";
|
|
19
|
-
const notes = new Map();
|
|
20
|
-
/** Longest note accepted, so one call cannot flood every later request. */
|
|
21
|
-
const MAX_VALUE = 2000;
|
|
22
|
-
/** Longest key accepted; keys are the lookup handle, so they stay short. */
|
|
23
|
-
const MAX_KEY = 80;
|
|
24
|
-
export function clearNotes() {
|
|
25
|
-
notes.clear();
|
|
26
|
-
}
|
|
27
|
-
export function noteCount() {
|
|
28
|
-
return notes.size;
|
|
29
|
-
}
|
|
30
|
-
/** The block injected into the prompt, or an empty string when there is nothing. */
|
|
31
|
-
export function renderNotes() {
|
|
32
|
-
if (notes.size === 0)
|
|
33
|
-
return "";
|
|
34
|
-
const lines = [...notes].map(([key, value]) => `- ${key}: ${value}`);
|
|
35
|
-
return ["<shared-notes>", "Working notes recorded with the `context` tool. They survive compaction and are visible to every agent on this task.", ...lines, "</shared-notes>"].join("\n");
|
|
36
|
-
}
|
|
37
|
-
export function applyNote(op, key, value) {
|
|
38
|
-
if (op === "list") {
|
|
39
|
-
if (notes.size === 0)
|
|
40
|
-
return { text: "No notes." };
|
|
41
|
-
return { text: [...notes].map(([k, v]) => `${k} = ${v}`).join("\n") };
|
|
42
|
-
}
|
|
43
|
-
if (!key)
|
|
44
|
-
return { text: "Error: key is required", error: "key required" };
|
|
45
|
-
if (key.length > MAX_KEY)
|
|
46
|
-
return { text: `Error: key longer than ${MAX_KEY} characters`, error: "key too long" };
|
|
47
|
-
if (op === "forget") {
|
|
48
|
-
const existed = notes.delete(key);
|
|
49
|
-
return { text: existed ? `Removed ${key}` : `No note named ${key}` };
|
|
50
|
-
}
|
|
51
|
-
if (op === "set") {
|
|
52
|
-
if (!value)
|
|
53
|
-
return { text: "Error: value is required for set", error: "value required" };
|
|
54
|
-
const clipped = value.length > MAX_VALUE ? `${value.slice(0, MAX_VALUE)}…` : value;
|
|
55
|
-
notes.set(key, clipped);
|
|
56
|
-
return { text: `Recorded ${key} (${notes.size} note${notes.size === 1 ? "" : "s"} held)` };
|
|
57
|
-
}
|
|
58
|
-
return { text: `Error: unknown op ${JSON.stringify(op)}`, error: "unknown op" };
|
|
59
|
-
}
|
|
60
|
-
export const contextTool = defineTool({
|
|
61
|
-
name: "context",
|
|
62
|
-
label: "context",
|
|
63
|
-
description: "Record a durable working note (a decision, a file's role, a fact you would otherwise re-derive). Notes survive context compaction and are visible to every agent on this task, including subagents. Use `set` to write, `list` to read, `forget` to remove.",
|
|
64
|
-
promptSnippet: "context: durable notes shared with every agent on this task",
|
|
65
|
-
parameters: Type.Object({
|
|
66
|
-
op: StringEnum(["set", "list", "forget"], { description: "set writes a note, list reads all, forget removes one" }),
|
|
67
|
-
key: Type.Optional(Type.String({ description: "Short lookup key, e.g. 'auth-entrypoint'" })),
|
|
68
|
-
value: Type.Optional(Type.String({ description: "The fact to keep (for set)" })),
|
|
69
|
-
}),
|
|
70
|
-
async execute(_toolCallId, params) {
|
|
71
|
-
const { text, error } = applyNote(params.op, params.key, params.value);
|
|
72
|
-
return {
|
|
73
|
-
content: [{ type: "text", text }],
|
|
74
|
-
details: { op: params.op, notes: Object.fromEntries(notes) },
|
|
75
|
-
...(error ? { isError: true } : {}),
|
|
76
|
-
};
|
|
77
|
-
},
|
|
78
|
-
});
|