flint-agent 1.14.0
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/.env.example +108 -0
- package/CHANGELOG.md +55 -0
- package/FEATURES.md +298 -0
- package/LICENSE +21 -0
- package/README.md +435 -0
- package/bin/flint.js +47 -0
- package/config/classifier-prompt.md +218 -0
- package/config/models-curated.json +4 -0
- package/config/providers.json +74 -0
- package/package.json +92 -0
- package/patches/ink+6.8.0.patch +78 -0
- package/profiles/desktop.md +65 -0
- package/profiles/generic.md +20 -0
- package/profiles/marketer.md +20 -0
- package/profiles/profiles.json +34 -0
- package/profiles/ux-reviewer.md +25 -0
- package/src/agent/agent.js +1743 -0
- package/src/agent/auto.js +346 -0
- package/src/agent/backoff.js +143 -0
- package/src/agent/compression.js +310 -0
- package/src/agent/content-resolver.js +180 -0
- package/src/agent/flow-controller.js +309 -0
- package/src/agent/intent-manifest.js +231 -0
- package/src/agent/intent-timeout.js +46 -0
- package/src/agent/intent.js +633 -0
- package/src/agent/knowledge.js +114 -0
- package/src/agent/learning.js +180 -0
- package/src/agent/modes.js +187 -0
- package/src/agent/outcome-ask.js +91 -0
- package/src/agent/project-context.js +76 -0
- package/src/agent/prompt-budget.js +117 -0
- package/src/agent/reflection-extractor.js +140 -0
- package/src/agent/steering.js +86 -0
- package/src/agent/supervisor.js +430 -0
- package/src/agent/swap.js +443 -0
- package/src/agent/system-prompt.js +446 -0
- package/src/agent/time-stamp.js +48 -0
- package/src/agent/tool-guard.js +201 -0
- package/src/agent/toolcall-text.js +162 -0
- package/src/agent/usage.js +297 -0
- package/src/agent/vision.js +94 -0
- package/src/agent/watchdog.js +139 -0
- package/src/agent/workspace-changes.js +177 -0
- package/src/api/address.js +14 -0
- package/src/api/client.js +280 -0
- package/src/api/server.js +535 -0
- package/src/api/stream-pipe.js +113 -0
- package/src/app-state.js +39 -0
- package/src/bootstrap.js +501 -0
- package/src/bus/drain-loop.js +497 -0
- package/src/bus/index.js +270 -0
- package/src/bus/plugins.js +65 -0
- package/src/child-idle.js +14 -0
- package/src/cli.js +118 -0
- package/src/commands/commands.js +1297 -0
- package/src/commands/registry.js +132 -0
- package/src/components/App.js +491 -0
- package/src/components/CarefulMenu.js +145 -0
- package/src/components/HistoryWriter.js +86 -0
- package/src/components/LineInput.js +69 -0
- package/src/components/LiveZone.js +294 -0
- package/src/components/OverlayMenu.js +179 -0
- package/src/components/SystemPanel.js +156 -0
- package/src/components/Table.js +54 -0
- package/src/config.js +249 -0
- package/src/free-models.js +230 -0
- package/src/index.js +1111 -0
- package/src/input-handler.js +13 -0
- package/src/input-text.js +123 -0
- package/src/launcher.js +129 -0
- package/src/logging/api-log.js +95 -0
- package/src/logging/chat-log-follower.js +113 -0
- package/src/logging/chat-log.js +15 -0
- package/src/logging/log-collector.js +182 -0
- package/src/logging/logger.js +112 -0
- package/src/logging/tool-log.js +20 -0
- package/src/mcp-client.js +314 -0
- package/src/memory/conversation-digest.js +113 -0
- package/src/memory/extract-facts.js +98 -0
- package/src/memory/facts.js +181 -0
- package/src/memory/inbox.js +63 -0
- package/src/memory/markdown.js +38 -0
- package/src/memory/patterns.js +185 -0
- package/src/memory/project.js +66 -0
- package/src/memory/reflections.js +74 -0
- package/src/memory/retrieval.js +84 -0
- package/src/memory/rules.js +105 -0
- package/src/memory/session-facts.js +125 -0
- package/src/memory/skills.js +191 -0
- package/src/memory/sqlite-store.js +653 -0
- package/src/memory/store.js +208 -0
- package/src/memory/tools.js +196 -0
- package/src/memory/user-model.js +86 -0
- package/src/message-handler.js +775 -0
- package/src/model-check.js +218 -0
- package/src/plugins/loader.js +120 -0
- package/src/plugins/manager.js +88 -0
- package/src/production-env.js +22 -0
- package/src/profiles.js +42 -0
- package/src/providers/adapters/anthropic.js +270 -0
- package/src/providers/adapters/openai.js +120 -0
- package/src/providers/keys-dpapi.js +41 -0
- package/src/providers/keys-fallback.js +31 -0
- package/src/providers/keys.js +132 -0
- package/src/providers/models.js +154 -0
- package/src/providers/registry.js +56 -0
- package/src/providers/state.js +56 -0
- package/src/registry.js +96 -0
- package/src/restart.js +29 -0
- package/src/sandbox/backend.js +130 -0
- package/src/security/api-auth.js +132 -0
- package/src/security/audit.js +98 -0
- package/src/security/child-policy.js +41 -0
- package/src/security/command-guard.js +173 -0
- package/src/security/content-fence.js +250 -0
- package/src/security/content-validator.js +132 -0
- package/src/security/index.js +143 -0
- package/src/security/network-guard.js +126 -0
- package/src/security/pairing.js +180 -0
- package/src/security/path-guard.js +140 -0
- package/src/security/persona-guard.js +67 -0
- package/src/security/policies.js +452 -0
- package/src/security/safety-constants.js +34 -0
- package/src/security/watchdog.js +107 -0
- package/src/sessions.js +130 -0
- package/src/spend.js +97 -0
- package/src/startup-watchdog.js +59 -0
- package/src/stdio/args.js +71 -0
- package/src/stdio/guard.js +59 -0
- package/src/stdio/protocol.js +167 -0
- package/src/stdio/run.js +106 -0
- package/src/stdio/session.js +180 -0
- package/src/store/agent-slice.js +306 -0
- package/src/store/dataset-slice.js +73 -0
- package/src/store/index.js +22 -0
- package/src/store/process-slice.js +135 -0
- package/src/store/session-slice.js +191 -0
- package/src/store/ui-slice.js +119 -0
- package/src/tasks/db.js +184 -0
- package/src/tasks/queries.js +589 -0
- package/src/tools/agent-tools.js +473 -0
- package/src/tools/checkpoint.js +152 -0
- package/src/tools/command-approvals.js +180 -0
- package/src/tools/dataset.js +50 -0
- package/src/tools/filesystem.js +682 -0
- package/src/tools/inbox-tools.js +48 -0
- package/src/tools/mesh.js +135 -0
- package/src/tools/own-env.js +136 -0
- package/src/tools/permissions.js +681 -0
- package/src/tools/plugin-tools.js +123 -0
- package/src/tools/process-tools.js +595 -0
- package/src/tools/registry.js +307 -0
- package/src/tools/swap-tools.js +72 -0
- package/src/tools/system.js +662 -0
- package/src/tools/tasks.js +532 -0
- package/src/tools/tool-search.js +171 -0
- package/src/ui/header.js +140 -0
- package/src/ui/input-cursor.js +23 -0
- package/src/ui/last-line.js +25 -0
- package/src/ui/line-edit.js +135 -0
- package/src/ui/output.js +399 -0
- package/src/ui/paste-tokens.js +131 -0
- package/src/ui/prompt-attention.js +134 -0
- package/src/ui/render-options.js +13 -0
- package/src/ui/replay.js +94 -0
- package/src/ui/splash.js +49 -0
- package/src/ui/status-level.js +36 -0
- package/src/ui/tool-ledger.js +203 -0
- package/src/ui/window-title.js +150 -0
- package/src/update.js +205 -0
- package/system.md +63 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// Free mode: OpenRouter's free models, listed with their current speed and
|
|
2
|
+
// uptime, the best picked, two fallbacks kept (docs/free-mode.md).
|
|
3
|
+
//
|
|
4
|
+
// Free means a zero prompt and completion price, not a ":free" suffix: five
|
|
5
|
+
// free models had none on 2026-10-02, stealth/space-bunny-alpha among them.
|
|
6
|
+
// Speed and uptime come from OpenRouter's endpoint stats, so listing spends
|
|
7
|
+
// no requests from the free daily allowance (50, or 1,000 after a one-time
|
|
8
|
+
// $10), which an agent turn of 6-150 calls uses up fast.
|
|
9
|
+
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
|
|
14
|
+
export const OPENROUTER_API = "https://openrouter.ai/api/v1";
|
|
15
|
+
const AVAILABLE_UPTIME = 90;
|
|
16
|
+
|
|
17
|
+
const vendorOf = (id) => String(id).split("/")[0];
|
|
18
|
+
|
|
19
|
+
const LIGHT_WORDS = /(^|[-_/.])(mini|small|nano|flash|lightning|lite|xs|tiny)([-_/.:]|$)/i;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A light model: small (under 15B parameters by its name) or named as a
|
|
23
|
+
* light variant. Speed alone put a 2.6B model second and the 550B one last
|
|
24
|
+
* (live list, 2026-10-02); OpenRouter gives no quality figure, so the name is
|
|
25
|
+
* the evidence there is.
|
|
26
|
+
*/
|
|
27
|
+
export function isLight(id) {
|
|
28
|
+
const name = String(id).split("/").slice(1).join("/").replace(/:free$/, "");
|
|
29
|
+
if (LIGHT_WORDS.test(name)) return true;
|
|
30
|
+
const size = name.match(/(?:^|[-_.])(\d+(?:\.\d+)?)b(?![a-z])/i);
|
|
31
|
+
return size ? Number(size[1]) < 15 : false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Free models that can call tools and answer in text. */
|
|
35
|
+
export function freeCandidates(models) {
|
|
36
|
+
return (models || [])
|
|
37
|
+
.filter((m) => Number(m?.pricing?.prompt) === 0 && Number(m?.pricing?.completion) === 0)
|
|
38
|
+
.filter((m) => (m.supported_parameters || []).includes("tools"))
|
|
39
|
+
.filter((m) => !m.architecture?.output_modalities || m.architecture.output_modalities.includes("text"))
|
|
40
|
+
.filter((m) => vendorOf(m.id) !== "openrouter") // routers, not models
|
|
41
|
+
.map((m) => ({
|
|
42
|
+
id: m.id,
|
|
43
|
+
name: m.name || m.id,
|
|
44
|
+
vendor: vendorOf(m.id),
|
|
45
|
+
context: m.context_length || null,
|
|
46
|
+
// An anonymous model on trial; such prompts are usually logged.
|
|
47
|
+
stealth: vendorOf(m.id) === "stealth" || /\b(stealth|anonymous)\b/i.test(m.description || ""),
|
|
48
|
+
light: isLight(m.id),
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Speed, first-token time and uptime from the endpoint with the best uptime. */
|
|
53
|
+
export function endpointStats(endpoints) {
|
|
54
|
+
const eps = (endpoints || []).filter(Boolean);
|
|
55
|
+
if (!eps.length) return { tps: null, firstMs: null, uptime: null, provider: null };
|
|
56
|
+
const best = [...eps].sort((a, b) => (b.uptime_last_30m ?? -1) - (a.uptime_last_30m ?? -1))[0];
|
|
57
|
+
return {
|
|
58
|
+
tps: best.throughput_last_30m?.p50 ?? null,
|
|
59
|
+
firstMs: best.latency_last_30m?.p50 ?? null,
|
|
60
|
+
uptime: best.uptime_last_30m ?? null,
|
|
61
|
+
provider: best.provider_name ?? null,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const available = (m) => m.uptime == null || m.uptime >= AVAILABLE_UPTIME;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Available ones first (uptime >= 90% or no data), then full-size before
|
|
69
|
+
* light ones, then faster, then quicker to start.
|
|
70
|
+
*/
|
|
71
|
+
export function rankFree(list) {
|
|
72
|
+
return [...(list || [])].sort((a, b) =>
|
|
73
|
+
(available(b) - available(a))
|
|
74
|
+
// A fresh model-check score (model-check.js) is measured; it outranks
|
|
75
|
+
// the guess from the name.
|
|
76
|
+
|| ((b.score ?? -1) - (a.score ?? -1))
|
|
77
|
+
|| ((a.light ? 1 : 0) - (b.light ? 1 : 0))
|
|
78
|
+
|| ((b.tps ?? 0) - (a.tps ?? 0))
|
|
79
|
+
|| ((a.firstMs ?? Infinity) - (b.firstMs ?? Infinity)));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The primary (the best, or the one named) and two fallbacks from other vendors. */
|
|
83
|
+
export function freeChain(ranked, primaryId = null) {
|
|
84
|
+
const primary = primaryId || ranked[0]?.id;
|
|
85
|
+
if (!primary) return [];
|
|
86
|
+
const chain = [primary];
|
|
87
|
+
const vendors = new Set([vendorOf(primary)]);
|
|
88
|
+
for (const m of ranked) {
|
|
89
|
+
if (chain.length === 3) break;
|
|
90
|
+
if (m.id === primary || vendors.has(m.vendor)) continue;
|
|
91
|
+
chain.push(m.id);
|
|
92
|
+
vendors.add(m.vendor);
|
|
93
|
+
}
|
|
94
|
+
return chain;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** 50 free requests a day on the free tier, 1,000 once credits were bought. */
|
|
98
|
+
export function dailyFreeLimit(keyInfo) {
|
|
99
|
+
return keyInfo && keyInfo.is_free_tier === false ? 1000 : 50;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── Saved state: the chain and today's count ───────────────
|
|
103
|
+
|
|
104
|
+
function stateFile() {
|
|
105
|
+
const dir = process.env.FLINT_DATA_DIR ? path.resolve(process.env.FLINT_DATA_DIR) : path.join(homedir(), ".flint");
|
|
106
|
+
return path.join(dir, "free.json");
|
|
107
|
+
}
|
|
108
|
+
function readState() {
|
|
109
|
+
try { return existsSync(stateFile()) ? JSON.parse(readFileSync(stateFile(), "utf8")) : {}; } catch { return {}; }
|
|
110
|
+
}
|
|
111
|
+
function writeState(s) {
|
|
112
|
+
try {
|
|
113
|
+
mkdirSync(path.dirname(stateFile()), { recursive: true });
|
|
114
|
+
writeFileSync(stateFile(), JSON.stringify(s, null, 2) + "\n");
|
|
115
|
+
} catch {}
|
|
116
|
+
}
|
|
117
|
+
const dayOf = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
118
|
+
|
|
119
|
+
export function saveFreeChain(chain, limit) {
|
|
120
|
+
const s = readState();
|
|
121
|
+
s.chain = chain && chain.length ? chain : null;
|
|
122
|
+
if (limit) s.limit = limit;
|
|
123
|
+
writeState(s);
|
|
124
|
+
}
|
|
125
|
+
export function loadFreeChain() {
|
|
126
|
+
return readState().chain || null;
|
|
127
|
+
}
|
|
128
|
+
export function savedFreeLimit() {
|
|
129
|
+
return readState().limit || 50;
|
|
130
|
+
}
|
|
131
|
+
export function recordFreeRequest(now = new Date()) {
|
|
132
|
+
const s = readState();
|
|
133
|
+
const day = dayOf(now);
|
|
134
|
+
s.usage = s.usage?.day === day ? { day, count: s.usage.count + 1 } : { day, count: 1 };
|
|
135
|
+
writeState(s);
|
|
136
|
+
}
|
|
137
|
+
export function freeUsedToday(now = new Date()) {
|
|
138
|
+
const u = readState().usage;
|
|
139
|
+
return u?.day === dayOf(now) ? u.count : 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── The request and its answer ───────────────────────────────
|
|
143
|
+
|
|
144
|
+
/** In free mode, the main model's request names the chain; nothing else does. */
|
|
145
|
+
export function freeRequestFields({ chain, provider, model, mainModel }) {
|
|
146
|
+
if (!chain?.length || provider?.id !== "openrouter" || model !== mainModel) return {};
|
|
147
|
+
return { models: chain };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** One line when a fallback answered instead of the primary, once per change. */
|
|
151
|
+
export function createServedModelNotifier() {
|
|
152
|
+
let last;
|
|
153
|
+
return (primary, served) => {
|
|
154
|
+
if (!served) return null;
|
|
155
|
+
if (served === primary) {
|
|
156
|
+
const back = last && last !== primary;
|
|
157
|
+
last = primary;
|
|
158
|
+
return back ? `free: back on ${primary}` : null;
|
|
159
|
+
}
|
|
160
|
+
if (last === served) return null;
|
|
161
|
+
last = served;
|
|
162
|
+
return `free: ${primary} did not answer; ${served} did`;
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function paymentRequiredMessage(inFreeMode) {
|
|
167
|
+
if (!inFreeMode) return null;
|
|
168
|
+
return "OpenRouter answered 402: the account balance is below zero, and that blocks free models too. Top up at https://openrouter.ai/credits.";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── Loading the list ─────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
let fetchJsonOverride = null;
|
|
174
|
+
/** For tests: replace the network. */
|
|
175
|
+
export function setFreeFetchJson(fn) { fetchJsonOverride = fn; }
|
|
176
|
+
|
|
177
|
+
async function defaultFetchJson(url) {
|
|
178
|
+
const { config } = await import("./config.js");
|
|
179
|
+
const r = await fetch(url, {
|
|
180
|
+
headers: config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {},
|
|
181
|
+
signal: AbortSignal.timeout(15000),
|
|
182
|
+
});
|
|
183
|
+
return r.ok ? r.json() : null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** The free models, ranked, each with its stats, and the account's daily limit. */
|
|
187
|
+
export async function loadFreeModels({ fetchJson = fetchJsonOverride || defaultFetchJson } = {}) {
|
|
188
|
+
const catalog = (await fetchJson(`${OPENROUTER_API}/models`))?.data || [];
|
|
189
|
+
const candidates = freeCandidates(catalog);
|
|
190
|
+
const withStats = await Promise.all(candidates.map(async (m) => {
|
|
191
|
+
try {
|
|
192
|
+
const eps = (await fetchJson(`${OPENROUTER_API}/models/${m.id}/endpoints`))?.data?.endpoints;
|
|
193
|
+
return { ...m, ...endpointStats(eps) };
|
|
194
|
+
} catch {
|
|
195
|
+
return { ...m, ...endpointStats([]) };
|
|
196
|
+
}
|
|
197
|
+
}));
|
|
198
|
+
let key = null;
|
|
199
|
+
try { key = (await fetchJson(`${OPENROUTER_API}/key`))?.data || null; } catch {}
|
|
200
|
+
const { withScores } = await import("./model-check.js");
|
|
201
|
+
return { models: rankFree(withScores(withStats)), limit: dailyFreeLimit(key), freeTier: key ? key.is_free_tier !== false : null };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Switch to a free chain: the model, its fallbacks, saved, and the price card
|
|
206
|
+
* of the primary for the footer. `log` gets one line.
|
|
207
|
+
*/
|
|
208
|
+
export async function applyFreeChain({ chain, limit, store, log }) {
|
|
209
|
+
const { config } = await import("./config.js");
|
|
210
|
+
const { setLastModel } = await import("./providers/state.js");
|
|
211
|
+
config.model = chain[0];
|
|
212
|
+
config.freeChain = chain;
|
|
213
|
+
saveFreeChain(chain, limit);
|
|
214
|
+
try { setLastModel(config.provider, chain[0]); } catch {}
|
|
215
|
+
store?.getState().setModel(chain[0]);
|
|
216
|
+
store?.setState({ _freeLimit: limit });
|
|
217
|
+
try {
|
|
218
|
+
const { fetchModelInfo } = await import("./api/client.js");
|
|
219
|
+
const info = await fetchModelInfo(chain[0]);
|
|
220
|
+
if (info) store?.getState().setPricing(info);
|
|
221
|
+
} catch {}
|
|
222
|
+
log?.(`free: ${chain[0]}${chain.length > 1 ? ` (fallbacks: ${chain.slice(1).join(", ")})` : ""} · limit ${limit} requests/day`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Leave free mode (another model was chosen). */
|
|
226
|
+
export async function clearFreeChain() {
|
|
227
|
+
const { config } = await import("./config.js");
|
|
228
|
+
config.freeChain = null;
|
|
229
|
+
saveFreeChain(null);
|
|
230
|
+
}
|