@datename/fastcode-termux 0.1.6 → 0.1.8-beta.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/LICENSE +4 -0
- package/README.md +67 -16
- package/dist/agent/approval-json.d.ts +43 -0
- package/dist/agent/approval-json.js +69 -0
- package/dist/agent/approval-native.d.ts +3 -0
- package/dist/agent/approval-native.js +165 -0
- package/dist/agent/approval-store.d.ts +16 -0
- package/dist/agent/approval-store.js +21 -0
- package/dist/agent/approval.d.ts +83 -0
- package/dist/agent/approval.js +42 -0
- package/dist/agent/compaction.d.ts +41 -0
- package/dist/agent/compaction.js +270 -0
- package/dist/agent/context.d.ts +29 -0
- package/dist/agent/context.js +62 -0
- package/dist/agent/guards.d.ts +24 -16
- package/dist/agent/guards.js +1167 -18
- package/dist/agent/ignore.d.ts +11 -0
- package/dist/agent/ignore.js +129 -0
- package/dist/agent/policy/defaults.d.ts +16 -0
- package/dist/agent/policy/defaults.js +17 -0
- package/dist/agent/policy/egress.d.ts +32 -0
- package/dist/agent/policy/egress.js +280 -0
- package/dist/agent/process-tree.d.ts +22 -0
- package/dist/agent/process-tree.js +102 -0
- package/dist/agent/protocol-mode.d.ts +6 -0
- package/dist/agent/protocol-mode.js +51 -0
- package/dist/agent/protocol.d.ts +20 -1
- package/dist/agent/protocol.js +169 -39
- package/dist/agent/registry.d.ts +19 -0
- package/dist/agent/registry.js +247 -0
- package/dist/agent/runner.d.ts +127 -0
- package/dist/agent/runner.js +391 -0
- package/dist/agent/shell-ast.d.ts +11 -0
- package/dist/agent/shell-ast.js +204 -0
- package/dist/agent/shell-session.d.ts +51 -0
- package/dist/agent/shell-session.js +611 -0
- package/dist/agent/stream.d.ts +29 -0
- package/dist/agent/stream.js +103 -0
- package/dist/agent/tool-schemas.d.ts +2 -0
- package/dist/agent/tool-schemas.js +82 -0
- package/dist/agent/tools/append_file.d.ts +2 -0
- package/dist/agent/tools/append_file.js +28 -0
- package/dist/agent/tools/apply_patch.d.ts +10 -0
- package/dist/agent/tools/apply_patch.js +92 -0
- package/dist/agent/tools/copy_file.d.ts +2 -0
- package/dist/agent/tools/copy_file.js +53 -0
- package/dist/agent/tools/delete_file.d.ts +2 -0
- package/dist/agent/tools/delete_file.js +43 -0
- package/dist/agent/tools/grep.d.ts +2 -0
- package/dist/agent/tools/grep.js +54 -0
- package/dist/agent/tools/index.d.ts +2 -0
- package/dist/agent/tools/index.js +32 -0
- package/dist/agent/tools/list_files.d.ts +2 -0
- package/dist/agent/tools/list_files.js +17 -0
- package/dist/agent/tools/make_dir.d.ts +2 -0
- package/dist/agent/tools/make_dir.js +26 -0
- package/dist/agent/tools/move_path.d.ts +9 -0
- package/dist/agent/tools/move_path.js +73 -0
- package/dist/agent/tools/paths.d.ts +7 -0
- package/dist/agent/tools/paths.js +84 -0
- package/dist/agent/tools/pwd.d.ts +2 -0
- package/dist/agent/tools/pwd.js +10 -0
- package/dist/agent/tools/read_file.d.ts +2 -0
- package/dist/agent/tools/read_file.js +81 -0
- package/dist/agent/tools/read_many.d.ts +2 -0
- package/dist/agent/tools/read_many.js +43 -0
- package/dist/agent/tools/readers.d.ts +58 -0
- package/dist/agent/tools/readers.js +426 -0
- package/dist/agent/tools/replace_in_file.d.ts +2 -0
- package/dist/agent/tools/replace_in_file.js +63 -0
- package/dist/agent/tools/shell.d.ts +2 -0
- package/dist/agent/tools/shell.js +48 -0
- package/dist/agent/tools/stat_path.d.ts +2 -0
- package/dist/agent/tools/stat_path.js +31 -0
- package/dist/agent/tools/types.d.ts +65 -0
- package/dist/agent/tools/types.js +1 -0
- package/dist/agent/tools/validate.d.ts +19 -0
- package/dist/agent/tools/validate.js +92 -0
- package/dist/agent/tools/write_file.d.ts +2 -0
- package/dist/agent/tools/write_file.js +28 -0
- package/dist/agent/tools.d.ts +3 -1
- package/dist/agent/tools.js +2 -377
- package/dist/app.d.ts +2 -1
- package/dist/app.js +846 -173
- package/dist/cli-main.js +363 -0
- package/dist/cli.js +444 -333
- package/dist/commands/about.js +1 -1
- package/dist/commands/attach.d.ts +9 -0
- package/dist/commands/attach.js +15 -0
- package/dist/commands/bg.d.ts +7 -0
- package/dist/commands/bg.js +9 -0
- package/dist/commands/cancel.d.ts +10 -0
- package/dist/commands/cancel.js +10 -0
- package/dist/commands/clear.d.ts +1 -0
- package/dist/commands/clear.js +1 -1
- package/dist/commands/compact.d.ts +22 -0
- package/dist/commands/compact.js +34 -0
- package/dist/commands/context.d.ts +7 -0
- package/dist/commands/context.js +25 -0
- package/dist/commands/edit.d.ts +10 -0
- package/dist/commands/edit.js +36 -0
- package/dist/commands/effort.d.ts +25 -0
- package/dist/commands/effort.js +48 -0
- package/dist/commands/expand.d.ts +10 -0
- package/dist/commands/expand.js +8 -0
- package/dist/commands/index.js +24 -0
- package/dist/commands/jobs.d.ts +7 -0
- package/dist/commands/jobs.js +17 -0
- package/dist/commands/kill.d.ts +7 -0
- package/dist/commands/kill.js +13 -0
- package/dist/commands/login.d.ts +6 -4
- package/dist/commands/login.js +24 -22
- package/dist/commands/logout.js +8 -16
- package/dist/commands/model.d.ts +1 -0
- package/dist/commands/model.js +10 -22
- package/dist/commands/provider.d.ts +1 -2
- package/dist/commands/provider.js +4 -11
- package/dist/commands/skills.js +34 -4
- package/dist/commands/steer.d.ts +11 -0
- package/dist/commands/steer.js +13 -0
- package/dist/commands/threads.d.ts +9 -0
- package/dist/commands/threads.js +26 -0
- package/dist/commands/whoami.js +6 -5
- package/dist/config/store.d.ts +53 -12
- package/dist/config/store.js +163 -73
- package/dist/config/trust.d.ts +30 -8
- package/dist/config/trust.js +139 -14
- package/dist/headless.d.ts +7 -0
- package/dist/headless.js +213 -0
- package/dist/index.js +19 -3
- package/dist/lib/debug.d.ts +2 -0
- package/dist/lib/debug.js +22 -0
- package/dist/lib/diagnostics.d.ts +48 -4
- package/dist/lib/diagnostics.js +90 -8
- package/dist/lib/highlight-subset.d.ts +21 -0
- package/dist/lib/highlight-subset.js +88 -0
- package/dist/lib/http.d.ts +11 -1
- package/dist/lib/http.js +62 -4
- package/dist/lib/lifecycle.d.ts +5 -0
- package/dist/lib/lifecycle.js +62 -0
- package/dist/lib/markdown.js +22 -4
- package/dist/lib/model-meta.d.ts +19 -0
- package/dist/lib/model-meta.js +113 -0
- package/dist/lib/native-tools.d.ts +12 -0
- package/dist/lib/native-tools.js +21 -0
- package/dist/lib/net.d.ts +33 -0
- package/dist/lib/net.js +168 -0
- package/dist/patch/apply.d.ts +15 -0
- package/dist/patch/apply.js +172 -0
- package/dist/patch/parse.d.ts +29 -0
- package/dist/patch/parse.js +112 -0
- package/dist/patch/plan.d.ts +49 -0
- package/dist/patch/plan.js +273 -0
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.js +12 -0
- package/dist/providers/custom.d.ts +9 -0
- package/dist/providers/deepseek.d.ts +10 -0
- package/dist/providers/deepseek.js +3 -2
- package/dist/providers/google.d.ts +9 -0
- package/dist/providers/groq.d.ts +10 -0
- package/dist/providers/groq.js +2 -1
- package/dist/providers/index.d.ts +10 -201
- package/dist/providers/index.js +30 -0
- package/dist/providers/mistral.d.ts +10 -0
- package/dist/providers/mistral.js +2 -1
- package/dist/providers/moonshot.d.ts +10 -0
- package/dist/providers/moonshot.js +2 -1
- package/dist/providers/ollama.d.ts +9 -0
- package/dist/providers/openai.d.ts +10 -0
- package/dist/providers/openai.js +2 -1
- package/dist/providers/openaiCompatible.d.ts +33 -2
- package/dist/providers/openaiCompatible.js +78 -16
- package/dist/providers/openrouter.d.ts +10 -0
- package/dist/providers/openrouter.js +2 -1
- package/dist/providers/qwen.d.ts +10 -0
- package/dist/providers/qwen.js +2 -1
- package/dist/providers/spec.d.ts +12 -0
- package/dist/providers/spec.js +29 -0
- package/dist/providers/xai.d.ts +10 -0
- package/dist/providers/xai.js +2 -1
- package/dist/providers/zhipu.d.ts +10 -0
- package/dist/providers/zhipu.js +2 -1
- package/dist/session/log.d.ts +49 -14
- package/dist/session/log.js +236 -11
- package/dist/session/projection.d.ts +76 -0
- package/dist/session/projection.js +526 -0
- package/dist/skills/manager.d.ts +15 -0
- package/dist/skills/manager.js +135 -18
- package/dist/types.d.ts +75 -1
- package/dist/ui/Message.d.ts +1 -7
- package/dist/ui/Message.js +13 -2
- package/dist/ui/Picker.d.ts +8 -0
- package/dist/ui/Picker.js +24 -0
- package/dist/ui/Prompt.d.ts +1 -2
- package/dist/ui/Prompt.js +2 -2
- package/dist/ui/Spinner.js +11 -5
- package/dist/ui/StatusBar.d.ts +2 -1
- package/dist/ui/StatusBar.js +6 -5
- package/package.json +3 -2
- package/src/agent/approval-json.ts +103 -0
- package/src/agent/approval-native.ts +168 -0
- package/src/agent/approval-store.ts +38 -0
- package/src/agent/approval.ts +94 -0
- package/src/agent/compaction.ts +321 -0
- package/src/agent/context.ts +97 -0
- package/src/agent/guards.ts +1239 -19
- package/src/agent/ignore.ts +147 -0
- package/src/agent/policy/defaults.ts +34 -0
- package/src/agent/policy/egress.ts +335 -0
- package/src/agent/process-tree.ts +114 -0
- package/src/agent/protocol-mode.ts +59 -0
- package/src/agent/protocol.ts +191 -40
- package/src/agent/registry.ts +270 -0
- package/src/agent/runner.ts +486 -0
- package/src/agent/shell-ast.ts +227 -0
- package/src/agent/shell-session.ts +673 -0
- package/src/agent/stream.ts +143 -0
- package/src/agent/tool-schemas.ts +86 -0
- package/src/agent/tools/append_file.ts +30 -0
- package/src/agent/tools/apply_patch.ts +95 -0
- package/src/agent/tools/copy_file.ts +56 -0
- package/src/agent/tools/delete_file.ts +45 -0
- package/src/agent/tools/grep.ts +57 -0
- package/src/agent/tools/index.ts +34 -0
- package/src/agent/tools/list_files.ts +19 -0
- package/src/agent/tools/make_dir.ts +28 -0
- package/src/agent/tools/move_path.ts +83 -0
- package/src/agent/tools/paths.ts +92 -0
- package/src/agent/tools/pwd.ts +12 -0
- package/src/agent/tools/read_file.ts +84 -0
- package/src/agent/tools/read_many.ts +45 -0
- package/src/agent/tools/readers.ts +491 -0
- package/src/agent/tools/replace_in_file.ts +66 -0
- package/src/agent/tools/shell.ts +53 -0
- package/src/agent/tools/stat_path.ts +33 -0
- package/src/agent/tools/types.ts +84 -0
- package/src/agent/tools/validate.ts +104 -0
- package/src/agent/tools/write_file.ts +30 -0
- package/src/agent/tools.ts +3 -377
- package/src/app.ts +853 -170
- package/src/{cli.ts → cli-main.ts} +64 -9
- package/src/commands/about.ts +1 -1
- package/src/commands/attach.ts +16 -0
- package/src/commands/bg.ts +9 -0
- package/src/commands/cancel.ts +10 -0
- package/src/commands/clear.ts +1 -1
- package/src/commands/compact.ts +35 -0
- package/src/commands/context.ts +26 -0
- package/src/commands/edit.ts +37 -0
- package/src/commands/effort.ts +49 -0
- package/src/commands/expand.ts +8 -0
- package/src/commands/index.ts +24 -0
- package/src/commands/jobs.ts +18 -0
- package/src/commands/kill.ts +14 -0
- package/src/commands/login.ts +24 -22
- package/src/commands/logout.ts +8 -16
- package/src/commands/model.ts +10 -22
- package/src/commands/provider.ts +4 -11
- package/src/commands/skills.ts +34 -4
- package/src/commands/steer.ts +13 -0
- package/src/commands/threads.ts +28 -0
- package/src/commands/whoami.ts +6 -5
- package/src/config/store.ts +168 -77
- package/src/config/trust.ts +181 -22
- package/src/headless.ts +225 -0
- package/src/index.ts +19 -3
- package/src/lib/debug.ts +24 -0
- package/src/lib/diagnostics.ts +90 -8
- package/src/lib/highlight-subset.ts +93 -0
- package/src/lib/http.ts +66 -4
- package/src/lib/lifecycle.ts +69 -0
- package/src/lib/markdown.ts +22 -4
- package/src/lib/model-meta.ts +138 -0
- package/src/lib/native-tools.ts +32 -0
- package/src/lib/net.ts +204 -0
- package/src/patch/apply.ts +194 -0
- package/src/patch/parse.ts +131 -0
- package/src/patch/plan.ts +317 -0
- package/src/providers/anthropic.ts +13 -1
- package/src/providers/deepseek.ts +3 -2
- package/src/providers/groq.ts +2 -1
- package/src/providers/index.ts +32 -1
- package/src/providers/mistral.ts +2 -1
- package/src/providers/moonshot.ts +2 -1
- package/src/providers/openai.ts +2 -1
- package/src/providers/openaiCompatible.ts +104 -19
- package/src/providers/openrouter.ts +2 -1
- package/src/providers/qwen.ts +2 -1
- package/src/providers/spec.ts +42 -0
- package/src/providers/xai.ts +2 -1
- package/src/providers/zhipu.ts +2 -1
- package/src/session/log.ts +311 -29
- package/src/session/projection.ts +621 -0
- package/src/skills/manager.ts +140 -18
- package/src/types/highlight-shims.d.ts +14 -0
- package/src/types.ts +79 -2
- package/src/ui/Message.ts +13 -2
- package/src/ui/Picker.ts +24 -0
- package/src/ui/Prompt.ts +2 -2
- package/src/ui/Spinner.ts +11 -5
- package/src/ui/StatusBar.ts +6 -5
- package/tsconfig.json +1 -0
- /package/dist/{cli.d.ts → cli-main.d.ts} +0 -0
package/dist/lib/diagnostics.js
CHANGED
|
@@ -5,18 +5,27 @@
|
|
|
5
5
|
* diagnosed without opening the TUI: Node version, Termux detection, whether
|
|
6
6
|
* stdout is a real terminal, the config root, and which provider keys exist.
|
|
7
7
|
*/
|
|
8
|
+
import { existsSync, statSync } from "node:fs";
|
|
9
|
+
import { totalmem, freemem, tmpdir as osTmpdir } from "node:os";
|
|
8
10
|
import { detectPlatform, platformLabel } from "./platform.js";
|
|
9
11
|
import { readPackageMeta } from "./pkg.js";
|
|
10
12
|
import { shouldUseStableTerminalUi } from "./uiMode.js";
|
|
11
13
|
import { getPaths } from "../config/paths.js";
|
|
12
|
-
import { loadConfig, resolveApiKey, resolveProviderKey } from "../config/store.js";
|
|
14
|
+
import { configCacheStatus, loadConfig, resolveApiKey, resolveProviderKey } from "../config/store.js";
|
|
15
|
+
import { debugEnabled } from "./debug.js";
|
|
13
16
|
import { PROVIDERS } from "../providers/index.js";
|
|
14
|
-
import { listInstalledSkills } from "../skills/manager.js";
|
|
17
|
+
import { listInstalledSkills, resolveRegistry } from "../skills/manager.js";
|
|
15
18
|
import { listInstalledPlugins } from "../plugins/manager.js";
|
|
16
|
-
import { loggingEnabled, listSessions, sessionCacheLimitBytes } from "../session/log.js";
|
|
19
|
+
import { findUnclosedSessions, fsyncMode, loggingEnabled, listSessions, sessionCacheLimitBytes } from "../session/log.js";
|
|
17
20
|
import { isTrusted } from "../config/trust.js";
|
|
18
|
-
import { DEFAULT_DENY_PATTERNS } from "../agent/guards.js";
|
|
21
|
+
import { DEFAULT_DENY_PATTERNS, loopExemptTools } from "../agent/guards.js";
|
|
19
22
|
import { toolOutputLimit } from "../agent/limits.js";
|
|
23
|
+
import { grepMaxBytes, readManyMaxBytes, readMaxBytes, readScanMaxBytes } from "../agent/tools/readers.js";
|
|
24
|
+
import { normalizeEgressConfig } from "../agent/policy/egress.js";
|
|
25
|
+
import { compactionConfigFromEnv, heapExceeded } from "../agent/compaction.js";
|
|
26
|
+
import { shellPoolStats } from "../agent/shell-session.js";
|
|
27
|
+
import { resolveToolProtocol, toolCapabilityKey } from "../agent/protocol-mode.js";
|
|
28
|
+
import { getModelMeta, modelMetaOverridePath } from "./model-meta.js";
|
|
20
29
|
const NODE_REQUIRED = "18.17";
|
|
21
30
|
function readVersion() {
|
|
22
31
|
return readPackageMeta().version;
|
|
@@ -28,6 +37,7 @@ function nodeIsSupported() {
|
|
|
28
37
|
}
|
|
29
38
|
export function collectDiagnostics() {
|
|
30
39
|
const config = loadConfig();
|
|
40
|
+
const egress = normalizeEgressConfig(config.egress);
|
|
31
41
|
let paths;
|
|
32
42
|
try {
|
|
33
43
|
paths = getPaths();
|
|
@@ -55,6 +65,16 @@ export function collectDiagnostics() {
|
|
|
55
65
|
// Same for plugins.
|
|
56
66
|
}
|
|
57
67
|
const platform = detectPlatform();
|
|
68
|
+
const compaction = compactionConfigFromEnv();
|
|
69
|
+
const shells = shellPoolStats();
|
|
70
|
+
let authMode = "missing";
|
|
71
|
+
try {
|
|
72
|
+
if (paths?.auth && existsSync(paths.auth))
|
|
73
|
+
authMode = (statSync(paths.auth).mode & 0o777).toString(8).padStart(4, "0");
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
authMode = "unreadable";
|
|
77
|
+
}
|
|
58
78
|
return {
|
|
59
79
|
version: readVersion(),
|
|
60
80
|
node: process.version,
|
|
@@ -69,17 +89,26 @@ export function collectDiagnostics() {
|
|
|
69
89
|
configRoot: paths?.root,
|
|
70
90
|
configFile: paths?.config,
|
|
71
91
|
authFile: paths?.auth,
|
|
92
|
+
authMode,
|
|
93
|
+
tmpdir: process.env["TMPDIR"] || osTmpdir(),
|
|
94
|
+
memTotalMb: totalmem() / 1024 / 1024,
|
|
95
|
+
memFreeMb: freemem() / 1024 / 1024,
|
|
72
96
|
providerId: config.providerId,
|
|
73
97
|
model: config.model,
|
|
74
98
|
keyPresent,
|
|
75
99
|
keyStatus: provider ? keyLookup.status : "missing",
|
|
76
100
|
keyEnv: provider?.authEnv?.[0],
|
|
77
101
|
skillsInstalled,
|
|
102
|
+
skillsRegistry: resolveRegistry(process.env["FASTCODE_SKILLS_REGISTRY"], config.skillsRegistry),
|
|
103
|
+
debug: debugEnabled(),
|
|
104
|
+
configCache: configCacheStatus().cached,
|
|
78
105
|
pluginsInstalled,
|
|
79
106
|
enabledSkills: (config.enabledSkills ?? []).length,
|
|
80
107
|
enabledPlugins: (config.enabledPlugins ?? []).length,
|
|
81
108
|
sessionLogEnabled: loggingEnabled(),
|
|
82
109
|
sessionCacheMb: Math.round(sessionCacheLimitBytes() / (1024 * 1024)),
|
|
110
|
+
sessionFsync: fsyncMode(),
|
|
111
|
+
unclosedSessions: findUnclosedSessions().length,
|
|
83
112
|
sessionsDir: paths?.sessions,
|
|
84
113
|
sessionsCount: listSessions().length,
|
|
85
114
|
sessionsBytes: listSessions().reduce((sum, entry) => sum + entry.bytes, 0),
|
|
@@ -88,6 +117,41 @@ export function collectDiagnostics() {
|
|
|
88
117
|
shellDenyPatterns: DEFAULT_DENY_PATTERNS.length,
|
|
89
118
|
allowDangerous: ["1", "true", "yes", "on"].includes((process.env["FASTCODE_ALLOW_DANGEROUS"] ?? "").trim().toLowerCase()),
|
|
90
119
|
toolOutputLimit: toolOutputLimit(),
|
|
120
|
+
readMaxBytes: readMaxBytes(),
|
|
121
|
+
readScanMaxBytes: readScanMaxBytes(),
|
|
122
|
+
readManyMaxBytes: readManyMaxBytes(),
|
|
123
|
+
grepMaxBytes: grepMaxBytes(),
|
|
124
|
+
egress: {
|
|
125
|
+
enabled: egress.enabled,
|
|
126
|
+
mode: egress.mode,
|
|
127
|
+
maxScanBytes: egress.maxScanBytes,
|
|
128
|
+
denyPaths: egress.denyPaths.length,
|
|
129
|
+
denyGlobs: egress.denyGlobs.length,
|
|
130
|
+
denyContentMarkers: egress.denyContentMarkers.length,
|
|
131
|
+
scanToolOutput: egress.scanToolOutput,
|
|
132
|
+
},
|
|
133
|
+
tokenBudget: positiveEnv("FASTCODE_TOKEN_BUDGET"),
|
|
134
|
+
costBudgetUsd: positiveEnv("FASTCODE_COST_BUDGET_USD"),
|
|
135
|
+
compactionEnabled: compaction.enabled,
|
|
136
|
+
compactionRatio: compaction.localThresholdRatio,
|
|
137
|
+
compactionKeepTurns: compaction.keepTurns,
|
|
138
|
+
compactionSummaryTokens: compaction.maxSummaryTokens,
|
|
139
|
+
compactionHeapMb: compaction.heapLimitMb,
|
|
140
|
+
compactionHeapUsedMb: process.memoryUsage().heapUsed / 1024 / 1024,
|
|
141
|
+
compactionForced: heapExceeded(compaction),
|
|
142
|
+
serverCompactionAware: compaction.serverCompactionAware,
|
|
143
|
+
shellActive: shells.active,
|
|
144
|
+
shellPool: shells.pool,
|
|
145
|
+
shellPoolMax: shells.maxPool,
|
|
146
|
+
shellSpawns: shells.spawns,
|
|
147
|
+
shellMaxSpawn: shells.maxSpawnPerTurn,
|
|
148
|
+
sseIdleMs: positiveEnv("FASTCODE_SSE_IDLE_MS") || 25_000,
|
|
149
|
+
toolProtocol: resolveToolProtocol(config, config.providerId, config.model),
|
|
150
|
+
toolProtocolMode: (process.env["FASTCODE_TOOL_PROTOCOL"] ?? "legacy").trim().toLowerCase(),
|
|
151
|
+
toolCapability: config.capabilities?.toolCalling?.[toolCapabilityKey(config.providerId, config.model)] ?? "unknown",
|
|
152
|
+
loopExempt: Array.from(loopExemptTools()),
|
|
153
|
+
modelMetaFile: modelMetaOverridePath(),
|
|
154
|
+
modelMeta: getModelMeta(config.providerId, config.model),
|
|
91
155
|
providers: PROVIDERS.map((p) => ({
|
|
92
156
|
id: p.id,
|
|
93
157
|
label: p.label,
|
|
@@ -96,6 +160,10 @@ export function collectDiagnostics() {
|
|
|
96
160
|
})),
|
|
97
161
|
};
|
|
98
162
|
}
|
|
163
|
+
function positiveEnv(name) {
|
|
164
|
+
const value = Number(process.env[name]);
|
|
165
|
+
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
166
|
+
}
|
|
99
167
|
export function formatDiagnostics(info) {
|
|
100
168
|
const lines = [];
|
|
101
169
|
lines.push(`FastCode ${info.version} — environment check`);
|
|
@@ -106,13 +174,25 @@ export function formatDiagnostics(info) {
|
|
|
106
174
|
lines.push(` stable UI ${info.stableUi ? "on (anti-flicker)" : "off"}`);
|
|
107
175
|
lines.push(" config root " + (info.configRoot ?? "(unavailable)"));
|
|
108
176
|
lines.push(" config file " + (info.configFile ?? "(unavailable)"));
|
|
109
|
-
lines.push(" keys file " + (info.authFile ?? "(unavailable)") + " (
|
|
177
|
+
lines.push(" keys file " + (info.authFile ?? "(unavailable)") + " mode " + info.authMode + (info.authMode !== "missing" && info.authMode !== "0600" ? " [WARN: expected 0600]" : ""));
|
|
178
|
+
lines.push(" tmpdir " + info.tmpdir + (info.termux && !String(info.tmpdir).startsWith("/data/data/com.termux") ? " [WARN: use $PREFIX/tmp]" : ""));
|
|
179
|
+
lines.push(" memory " + info.memFreeMb.toFixed(0) + "MB free / " + info.memTotalMb.toFixed(0) + "MB total");
|
|
110
180
|
lines.push(" trusted cwd " + (info.trusted ? "yes" : "NO - agent tools ask first or stay off") + " (" + (info.trustFile ?? "?") + ")");
|
|
111
181
|
lines.push(" session log " + (info.sessionLogEnabled ? "on" : "off") + " cache cap " + info.sessionCacheMb + "MB, " + info.sessionsCount + " file(s), " + (info.sessionsBytes / 1024 / 1024).toFixed(2) + "MB used");
|
|
112
|
-
lines.push(" session
|
|
113
|
-
lines.push(" shell guard " + info.shellDenyPatterns + "
|
|
182
|
+
lines.push(" session fsync " + info.sessionFsync + " unclosed " + info.unclosedSessions + " " + (info.sessionsDir ?? "(unavailable)"));
|
|
183
|
+
lines.push(" shell guard " + info.shellDenyPatterns + " legacy soft patterns" + (info.allowDangerous ? " [FASTCODE_ALLOW_DANGEROUS is ON]" : ""));
|
|
114
184
|
lines.push(" tool output " + info.toolOutputLimit + " chars max (streaming cap)");
|
|
185
|
+
lines.push(` read limits read ${info.readMaxBytes}B · scan ${info.readScanMaxBytes}B · read_many ${info.readManyMaxBytes}B · grep ${info.grepMaxBytes}B`);
|
|
186
|
+
lines.push(` egress ${info.egress.enabled ? "enforce" : "audit-only"} · mode ${info.egress.mode} · scan ${info.egress.maxScanBytes}B · paths ${info.egress.denyPaths} · globs ${info.egress.denyGlobs} · markers ${info.egress.denyContentMarkers}`);
|
|
187
|
+
lines.push(` budgets ${info.tokenBudget > 0 ? "tokens ≤ " + info.tokenBudget : "token budget off"} · ${info.costBudgetUsd > 0 ? "$" + info.costBudgetUsd : "cost budget off"}`);
|
|
188
|
+
lines.push(` compaction ${info.compactionEnabled ? "on" : "off"} · ${Math.round(info.compactionRatio * 100)}% · keep ${info.compactionKeepTurns} turns · summary ${info.compactionSummaryTokens} tok · heap ${info.compactionHeapUsedMb.toFixed(1)}/${info.compactionHeapMb}MB${info.compactionForced ? " [FORCED]" : ""}${info.serverCompactionAware ? " · server-aware" : ""}`);
|
|
189
|
+
lines.push(` shell pool ${info.shellActive} active · ${info.shellPool}/${info.shellPoolMax} resident · ${info.shellSpawns} spawns · cap ${info.shellMaxSpawn}/turn`);
|
|
190
|
+
lines.push(` loop exempt ${info.loopExempt.length > 0 ? info.loopExempt.join(", ") : "(none)"}`);
|
|
191
|
+
lines.push(` model meta ctx ${info.modelMeta.contextWindow} · ~${info.modelMeta.charsPerToken} chars/token · ${info.modelMeta.supportsTools ? "tools" : "no tools"} · ${info.modelMetaFile}`);
|
|
192
|
+
lines.push(` sse timeout ${info.sseIdleMs}ms idle`);
|
|
193
|
+
lines.push(` tool protocol ${info.toolProtocol} (${info.toolProtocolMode}) · cached ${info.toolCapability}`);
|
|
115
194
|
lines.push(` provider ${info.providerId} · model ${info.model}`);
|
|
195
|
+
lines.push(` config cache ${info.configCache ? "warm" : "cold"} · debug ${info.debug ? "on" : "off"}`);
|
|
116
196
|
let apiKeyStatus;
|
|
117
197
|
if (info.keyPresent)
|
|
118
198
|
apiKeyStatus = "present";
|
|
@@ -121,7 +201,7 @@ export function formatDiagnostics(info) {
|
|
|
121
201
|
else
|
|
122
202
|
apiKeyStatus = `MISSING — set ${info.keyEnv ?? "the provider env var"} or /login ${info.providerId} ${info.model} <key>`;
|
|
123
203
|
lines.push(` api key ${apiKeyStatus}`);
|
|
124
|
-
lines.push(` skills ${info.skillsInstalled} installed, ${info.enabledSkills} enabled`);
|
|
204
|
+
lines.push(` skills ${info.skillsInstalled} installed, ${info.enabledSkills} enabled (${info.skillsRegistry || "no registry configured"})`);
|
|
125
205
|
lines.push(` plugins ${info.pluginsInstalled} installed, ${info.enabledPlugins} enabled`);
|
|
126
206
|
lines.push("");
|
|
127
207
|
lines.push(" provider keys:");
|
|
@@ -134,6 +214,8 @@ export function formatDiagnostics(info) {
|
|
|
134
214
|
lines.push(" - pkg install nodejs git");
|
|
135
215
|
lines.push(" - keep the session alive with: termux-wake-lock");
|
|
136
216
|
lines.push(" - add an extra-keys row for Esc/Ctrl in ~/.termux/termux.properties");
|
|
217
|
+
lines.push(" - long tasks: export TMPDIR=$PREFIX/tmp and use termux-wake-lock");
|
|
218
|
+
lines.push(` - Android 12+ caps phantom processes (~32); background shells are pooled (${info.shellPool} resident, cap ${info.shellMaxSpawn}/turn)`);
|
|
137
219
|
}
|
|
138
220
|
return lines.join("\n");
|
|
139
221
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trimmed highlight.js registry.
|
|
3
|
+
*
|
|
4
|
+
* cli-highlight imports the full `highlight.js` package, which registers every
|
|
5
|
+
* language and adds roughly a megabyte of language definitions to the bundle
|
|
6
|
+
* for a TUI that mostly renders JS/TS/JSON/sh/Python. The esbuild plugin in
|
|
7
|
+
* scripts/esbuild-plugins.mjs aliases the bare `highlight.js` import to this
|
|
8
|
+
* module so only the common set below is bundled.
|
|
9
|
+
*
|
|
10
|
+
* Unknown languages still work: cli-highlight throws, marked-terminal catches
|
|
11
|
+
* and renders the fence as plain styled text. `renderMarkdown` adds a short
|
|
12
|
+
* dimmed hint instead of failing or blanking the message.
|
|
13
|
+
*/
|
|
14
|
+
import hljs from "highlight.js/lib/core.js";
|
|
15
|
+
export declare const COMMON_HIGHLIGHT_LANGUAGES: string[];
|
|
16
|
+
export declare function supportsHighlightLanguage(language: string | undefined): boolean;
|
|
17
|
+
export declare const highlight: any;
|
|
18
|
+
export declare const highlightAuto: any;
|
|
19
|
+
export declare const listLanguages: any;
|
|
20
|
+
export declare const getLanguage: any;
|
|
21
|
+
export default hljs;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trimmed highlight.js registry.
|
|
3
|
+
*
|
|
4
|
+
* cli-highlight imports the full `highlight.js` package, which registers every
|
|
5
|
+
* language and adds roughly a megabyte of language definitions to the bundle
|
|
6
|
+
* for a TUI that mostly renders JS/TS/JSON/sh/Python. The esbuild plugin in
|
|
7
|
+
* scripts/esbuild-plugins.mjs aliases the bare `highlight.js` import to this
|
|
8
|
+
* module so only the common set below is bundled.
|
|
9
|
+
*
|
|
10
|
+
* Unknown languages still work: cli-highlight throws, marked-terminal catches
|
|
11
|
+
* and renders the fence as plain styled text. `renderMarkdown` adds a short
|
|
12
|
+
* dimmed hint instead of failing or blanking the message.
|
|
13
|
+
*/
|
|
14
|
+
import hljs from "highlight.js/lib/core.js";
|
|
15
|
+
import javascript from "highlight.js/lib/languages/javascript.js";
|
|
16
|
+
import typescript from "highlight.js/lib/languages/typescript.js";
|
|
17
|
+
import json from "highlight.js/lib/languages/json.js";
|
|
18
|
+
import bash from "highlight.js/lib/languages/bash.js";
|
|
19
|
+
import python from "highlight.js/lib/languages/python.js";
|
|
20
|
+
import go from "highlight.js/lib/languages/go.js";
|
|
21
|
+
import rust from "highlight.js/lib/languages/rust.js";
|
|
22
|
+
import java from "highlight.js/lib/languages/java.js";
|
|
23
|
+
import c from "highlight.js/lib/languages/c.js";
|
|
24
|
+
import cpp from "highlight.js/lib/languages/cpp.js";
|
|
25
|
+
import csharp from "highlight.js/lib/languages/csharp.js";
|
|
26
|
+
import css from "highlight.js/lib/languages/css.js";
|
|
27
|
+
import xml from "highlight.js/lib/languages/xml.js";
|
|
28
|
+
import yaml from "highlight.js/lib/languages/yaml.js";
|
|
29
|
+
import markdown from "highlight.js/lib/languages/markdown.js";
|
|
30
|
+
import sql from "highlight.js/lib/languages/sql.js";
|
|
31
|
+
import diff from "highlight.js/lib/languages/diff.js";
|
|
32
|
+
import dockerfile from "highlight.js/lib/languages/dockerfile.js";
|
|
33
|
+
import powershell from "highlight.js/lib/languages/powershell.js";
|
|
34
|
+
import kotlin from "highlight.js/lib/languages/kotlin.js";
|
|
35
|
+
import swift from "highlight.js/lib/languages/swift.js";
|
|
36
|
+
import ruby from "highlight.js/lib/languages/ruby.js";
|
|
37
|
+
import php from "highlight.js/lib/languages/php.js";
|
|
38
|
+
import lua from "highlight.js/lib/languages/lua.js";
|
|
39
|
+
import r from "highlight.js/lib/languages/r.js";
|
|
40
|
+
import scala from "highlight.js/lib/languages/scala.js";
|
|
41
|
+
import ini from "highlight.js/lib/languages/ini.js";
|
|
42
|
+
import makefile from "highlight.js/lib/languages/makefile.js";
|
|
43
|
+
import plaintext from "highlight.js/lib/languages/plaintext.js";
|
|
44
|
+
const LANGUAGES = {
|
|
45
|
+
javascript,
|
|
46
|
+
typescript,
|
|
47
|
+
json,
|
|
48
|
+
bash,
|
|
49
|
+
python,
|
|
50
|
+
go,
|
|
51
|
+
rust,
|
|
52
|
+
java,
|
|
53
|
+
c,
|
|
54
|
+
cpp,
|
|
55
|
+
csharp,
|
|
56
|
+
css,
|
|
57
|
+
xml,
|
|
58
|
+
yaml,
|
|
59
|
+
markdown,
|
|
60
|
+
sql,
|
|
61
|
+
diff,
|
|
62
|
+
dockerfile,
|
|
63
|
+
powershell,
|
|
64
|
+
kotlin,
|
|
65
|
+
swift,
|
|
66
|
+
ruby,
|
|
67
|
+
php,
|
|
68
|
+
lua,
|
|
69
|
+
r,
|
|
70
|
+
scala,
|
|
71
|
+
ini,
|
|
72
|
+
makefile,
|
|
73
|
+
plaintext,
|
|
74
|
+
};
|
|
75
|
+
for (const [name, definition] of Object.entries(LANGUAGES))
|
|
76
|
+
hljs.registerLanguage(name, definition);
|
|
77
|
+
export const COMMON_HIGHLIGHT_LANGUAGES = Object.keys(LANGUAGES);
|
|
78
|
+
export function supportsHighlightLanguage(language) {
|
|
79
|
+
if (!language)
|
|
80
|
+
return true;
|
|
81
|
+
const name = language.trim().toLowerCase().split(/\s+/)[0] ?? "";
|
|
82
|
+
return Boolean(name) && Boolean(hljs.getLanguage(name));
|
|
83
|
+
}
|
|
84
|
+
export const highlight = hljs.highlight.bind(hljs);
|
|
85
|
+
export const highlightAuto = hljs.highlightAuto.bind(hljs);
|
|
86
|
+
export const listLanguages = hljs.listLanguages.bind(hljs);
|
|
87
|
+
export const getLanguage = hljs.getLanguage.bind(hljs);
|
|
88
|
+
export default hljs;
|
package/dist/lib/http.d.ts
CHANGED
|
@@ -6,8 +6,18 @@
|
|
|
6
6
|
* Anthropic's older streaming) handle their own parsing.
|
|
7
7
|
*/
|
|
8
8
|
export declare function fetchJson(url: string, init?: any): Promise<any>;
|
|
9
|
+
export declare class NetworkStallError extends Error {
|
|
10
|
+
readonly code = "NETWORK_STALL";
|
|
11
|
+
readonly bytesReceived: number;
|
|
12
|
+
constructor(message: string, bytesReceived?: number);
|
|
13
|
+
}
|
|
14
|
+
export interface StreamSseOptions {
|
|
15
|
+
idleTimeoutMs?: number;
|
|
16
|
+
totalTimeoutMs?: number;
|
|
17
|
+
}
|
|
9
18
|
/** Async iterator over SSE events from a streaming HTTP response. */
|
|
10
|
-
export declare function streamSSE(res: any): AsyncGenerator<{
|
|
19
|
+
export declare function streamSSE(res: any, options?: StreamSseOptions): AsyncGenerator<{
|
|
11
20
|
event: any;
|
|
12
21
|
data: string;
|
|
13
22
|
}, void, unknown>;
|
|
23
|
+
export declare function probeConnectivity(url: string, timeoutMs?: number): Promise<boolean>;
|
package/dist/lib/http.js
CHANGED
|
@@ -16,8 +16,17 @@ export async function fetchJson(url, init = {}) {
|
|
|
16
16
|
}
|
|
17
17
|
return (await res.json());
|
|
18
18
|
}
|
|
19
|
+
export class NetworkStallError extends Error {
|
|
20
|
+
code = "NETWORK_STALL";
|
|
21
|
+
bytesReceived;
|
|
22
|
+
constructor(message, bytesReceived = 0) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = "NetworkStallError";
|
|
25
|
+
this.bytesReceived = bytesReceived;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
19
28
|
/** Async iterator over SSE events from a streaming HTTP response. */
|
|
20
|
-
export async function* streamSSE(res) {
|
|
29
|
+
export async function* streamSSE(res, options = {}) {
|
|
21
30
|
if (!res.ok) {
|
|
22
31
|
const body = await res.text().catch(() => "");
|
|
23
32
|
throw new Error(`${res.status} ${res.statusText}: ${body.slice(0, 500)}`);
|
|
@@ -26,19 +35,50 @@ export async function* streamSSE(res) {
|
|
|
26
35
|
throw new Error("Empty response body");
|
|
27
36
|
const reader = res.body.getReader();
|
|
28
37
|
const decoder = new TextDecoder("utf-8");
|
|
38
|
+
const envIdle = Number(process.env["FASTCODE_SSE_IDLE_MS"]);
|
|
39
|
+
const idleMs = positive(options.idleTimeoutMs, positive(envIdle, 25_000));
|
|
40
|
+
const totalMs = positive(options.totalTimeoutMs, 0);
|
|
41
|
+
const startedAt = Date.now();
|
|
42
|
+
let bytesReceived = 0;
|
|
29
43
|
let buffer = "";
|
|
30
44
|
while (true) {
|
|
31
|
-
const
|
|
45
|
+
const remaining = totalMs > 0 ? totalMs - (Date.now() - startedAt) : Number.POSITIVE_INFINITY;
|
|
46
|
+
if (remaining <= 0)
|
|
47
|
+
throw new NetworkStallError(`SSE total timeout after ${totalMs}ms`, bytesReceived);
|
|
48
|
+
const boundedByTotal = remaining <= idleMs;
|
|
49
|
+
const waitMs = boundedByTotal ? Math.max(1, remaining) : idleMs;
|
|
50
|
+
let timer;
|
|
51
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
52
|
+
timer = setTimeout(() => {
|
|
53
|
+
reject(new NetworkStallError(boundedByTotal
|
|
54
|
+
? `SSE total timeout after ${totalMs}ms`
|
|
55
|
+
: `SSE idle timeout after ${idleMs}ms (no data received; set FASTCODE_SSE_IDLE_MS to wait longer)`, bytesReceived));
|
|
56
|
+
}, waitMs);
|
|
57
|
+
});
|
|
58
|
+
let chunk;
|
|
59
|
+
try {
|
|
60
|
+
chunk = await Promise.race([reader.read(), timeout]);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
await reader.cancel().catch(() => undefined);
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
if (timer)
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
}
|
|
70
|
+
const { value, done } = chunk;
|
|
32
71
|
if (done)
|
|
33
72
|
break;
|
|
73
|
+
bytesReceived += value?.byteLength ?? 0;
|
|
34
74
|
// Normalize CR/LF so both "\n\n" and "\r\n\r\n" frame separators
|
|
35
75
|
// are detected, including a CR/LF split across two chunks.
|
|
36
76
|
buffer = (buffer + decoder.decode(value, { stream: true })).replace(/\r\n?/g, "\n");
|
|
37
77
|
let sep = buffer.indexOf("\n\n");
|
|
38
78
|
while (sep !== -1) {
|
|
39
|
-
const
|
|
79
|
+
const chunkText = buffer.slice(0, sep);
|
|
40
80
|
buffer = buffer.slice(sep + 2);
|
|
41
|
-
const ev = parseSseChunk(
|
|
81
|
+
const ev = parseSseChunk(chunkText);
|
|
42
82
|
if (ev)
|
|
43
83
|
yield ev;
|
|
44
84
|
sep = buffer.indexOf("\n\n");
|
|
@@ -52,6 +92,24 @@ export async function* streamSSE(res) {
|
|
|
52
92
|
yield ev;
|
|
53
93
|
}
|
|
54
94
|
}
|
|
95
|
+
export async function probeConnectivity(url, timeoutMs = 3000) {
|
|
96
|
+
const controller = new AbortController();
|
|
97
|
+
const timer = setTimeout(() => controller.abort(), positive(timeoutMs, 3000));
|
|
98
|
+
try {
|
|
99
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
100
|
+
await res.body?.cancel().catch(() => undefined);
|
|
101
|
+
return res.ok;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function positive(value, fallback) {
|
|
111
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
112
|
+
}
|
|
55
113
|
function parseSseChunk(chunk) {
|
|
56
114
|
let event;
|
|
57
115
|
const dataLines = [];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-level shutdown guards.
|
|
3
|
+
*
|
|
4
|
+
* The TUI already calls shutdownShells() from its React cleanup, but fatal
|
|
5
|
+
* errors and SIGTERM/SIGHUP can bypass that path. These guards are best-effort
|
|
6
|
+
* and idempotent; they do not turn FastCode into a kernel-level sandbox.
|
|
7
|
+
*/
|
|
8
|
+
import { shutdownShells } from "../agent/shell-session.js";
|
|
9
|
+
let installed = false;
|
|
10
|
+
let cleaned = false;
|
|
11
|
+
export function cleanupProcesses() {
|
|
12
|
+
if (cleaned)
|
|
13
|
+
return;
|
|
14
|
+
cleaned = true;
|
|
15
|
+
try {
|
|
16
|
+
shutdownShells();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Best effort: never mask the original failure.
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function installProcessGuards(options = {}) {
|
|
23
|
+
if (installed)
|
|
24
|
+
return () => undefined;
|
|
25
|
+
installed = true;
|
|
26
|
+
const onSignal = (signal) => {
|
|
27
|
+
process.stderr.write(`[fastcode] ${signal} received; shutting down.\n`);
|
|
28
|
+
cleanupProcesses();
|
|
29
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
30
|
+
};
|
|
31
|
+
const onFatal = (error) => {
|
|
32
|
+
const detail = error instanceof Error ? (error.stack ?? error.message) : String(error);
|
|
33
|
+
process.stderr.write(`[fastcode] fatal: ${detail}\n`);
|
|
34
|
+
cleanupProcesses();
|
|
35
|
+
options.onFatal?.(error);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
};
|
|
38
|
+
const signals = ["SIGTERM", "SIGHUP"];
|
|
39
|
+
for (const signal of signals) {
|
|
40
|
+
try {
|
|
41
|
+
process.on(signal, onSignal);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Some platforms do not expose every signal.
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
process.on("uncaughtException", onFatal);
|
|
48
|
+
process.on("unhandledRejection", onFatal);
|
|
49
|
+
return () => {
|
|
50
|
+
for (const signal of signals) {
|
|
51
|
+
try {
|
|
52
|
+
process.removeListener(signal, onSignal);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Ignore unsupported signals.
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
process.removeListener("uncaughtException", onFatal);
|
|
59
|
+
process.removeListener("unhandledRejection", onFatal);
|
|
60
|
+
installed = false;
|
|
61
|
+
};
|
|
62
|
+
}
|
package/dist/lib/markdown.js
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* markdown parser. The result is a fully-formatted ANSI string we can
|
|
7
7
|
* print directly inside an Ink <Text>.
|
|
8
8
|
*/
|
|
9
|
+
import chalk from "chalk";
|
|
9
10
|
import { marked } from "marked";
|
|
10
11
|
import { markedTerminal } from "marked-terminal";
|
|
12
|
+
import { supportsHighlightLanguage } from "./highlight-subset.js";
|
|
11
13
|
let configured = false;
|
|
12
14
|
function configure() {
|
|
13
15
|
if (configured)
|
|
@@ -15,16 +17,32 @@ function configure() {
|
|
|
15
17
|
marked.use(markedTerminal({
|
|
16
18
|
reflowText: false,
|
|
17
19
|
tab: 2,
|
|
20
|
+
// marked-terminal calls this only when highlighting fails; keep the
|
|
21
|
+
// cold-language fallback as plain text instead of colored source.
|
|
22
|
+
code: (text) => text,
|
|
18
23
|
}));
|
|
19
24
|
configured = true;
|
|
20
25
|
}
|
|
26
|
+
function unsupportedFenceLanguages(md) {
|
|
27
|
+
const missing = new Set();
|
|
28
|
+
for (const match of md.matchAll(/(?:^|\n)\s*(?:`{3,}|~{3,})([^\n]*)/g)) {
|
|
29
|
+
const info = (match[1] ?? "").trim();
|
|
30
|
+
if (!info)
|
|
31
|
+
continue;
|
|
32
|
+
const language = info.split(/\s+/)[0] ?? "";
|
|
33
|
+
if (language && !supportsHighlightLanguage(language))
|
|
34
|
+
missing.add(language);
|
|
35
|
+
}
|
|
36
|
+
return Array.from(missing);
|
|
37
|
+
}
|
|
21
38
|
export function renderMarkdown(md) {
|
|
22
39
|
configure();
|
|
23
40
|
try {
|
|
24
|
-
const out = marked.parse(md);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
const out = marked.parse(md).replace(/\s+$/, "");
|
|
42
|
+
const missing = unsupportedFenceLanguages(String(md));
|
|
43
|
+
if (missing.length === 0)
|
|
44
|
+
return out;
|
|
45
|
+
return out + "\n\n" + chalk.dim(`[fastcode] no syntax highlighting for ${missing.join(", ")}; rendered as plain text.`);
|
|
28
46
|
}
|
|
29
47
|
catch {
|
|
30
48
|
return md;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface ModelMeta {
|
|
2
|
+
contextWindow: number;
|
|
3
|
+
supportsTools: boolean;
|
|
4
|
+
charsPerToken: number;
|
|
5
|
+
maxOutputTokens?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface EstTokenOptions {
|
|
8
|
+
providerId?: string;
|
|
9
|
+
model?: string;
|
|
10
|
+
charsPerToken?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare const UNKNOWN_MODEL_META: ModelMeta;
|
|
13
|
+
export declare function modelMetaOverridePath(): string;
|
|
14
|
+
export declare function clearModelMetaCache(): void;
|
|
15
|
+
export declare function getModelMeta(providerId?: string, model?: string): ModelMeta;
|
|
16
|
+
export declare function estTokens(text: string, options?: number | EstTokenOptions): number;
|
|
17
|
+
export declare function estMessagesTokens(messages: Array<{
|
|
18
|
+
content: string;
|
|
19
|
+
}>, options?: EstTokenOptions): number;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
export const UNKNOWN_MODEL_META = {
|
|
5
|
+
contextWindow: 32_768,
|
|
6
|
+
supportsTools: false,
|
|
7
|
+
charsPerToken: 2.5,
|
|
8
|
+
};
|
|
9
|
+
const PROVIDER_META = {
|
|
10
|
+
anthropic: { contextWindow: 1_000_000, supportsTools: true, charsPerToken: 3.8, maxOutputTokens: 64_000 },
|
|
11
|
+
openai: { contextWindow: 400_000, supportsTools: true, charsPerToken: 3.8, maxOutputTokens: 32_000 },
|
|
12
|
+
google: { contextWindow: 1_048_576, supportsTools: true, charsPerToken: 3.6, maxOutputTokens: 65_536 },
|
|
13
|
+
deepseek: { contextWindow: 128_000, supportsTools: true, charsPerToken: 1.7, maxOutputTokens: 8_192 },
|
|
14
|
+
openrouter: { contextWindow: 128_000, supportsTools: true, charsPerToken: 2.5, maxOutputTokens: 16_384 },
|
|
15
|
+
groq: { contextWindow: 128_000, supportsTools: true, charsPerToken: 3.8, maxOutputTokens: 8_192 },
|
|
16
|
+
mistral: { contextWindow: 128_000, supportsTools: true, charsPerToken: 3.8, maxOutputTokens: 8_192 },
|
|
17
|
+
xai: { contextWindow: 256_000, supportsTools: true, charsPerToken: 3.8, maxOutputTokens: 16_384 },
|
|
18
|
+
moonshot: { contextWindow: 256_000, supportsTools: true, charsPerToken: 2.0, maxOutputTokens: 16_384 },
|
|
19
|
+
zhipu: { contextWindow: 128_000, supportsTools: true, charsPerToken: 1.7, maxOutputTokens: 8_192 },
|
|
20
|
+
qwen: { contextWindow: 128_000, supportsTools: true, charsPerToken: 1.7, maxOutputTokens: 8_192 },
|
|
21
|
+
ollama: { contextWindow: 32_768, supportsTools: true, charsPerToken: 2.5, maxOutputTokens: 4_096 },
|
|
22
|
+
custom: { contextWindow: 32_768, supportsTools: false, charsPerToken: 2.5 },
|
|
23
|
+
};
|
|
24
|
+
const MODEL_OVERRIDES = {
|
|
25
|
+
"claude-haiku-5.5": { contextWindow: 200_000 },
|
|
26
|
+
"claude-haiku-4.5": { contextWindow: 200_000 },
|
|
27
|
+
"claude-sonnet-5": { contextWindow: 1_000_000 },
|
|
28
|
+
"claude-opus-5": { contextWindow: 1_000_000 },
|
|
29
|
+
"claude-sonnet-4.6": { contextWindow: 1_000_000 },
|
|
30
|
+
"claude-opus-4.6": { contextWindow: 1_000_000 },
|
|
31
|
+
"gpt-4o": { contextWindow: 128_000 },
|
|
32
|
+
"gpt-4o-mini": { contextWindow: 128_000 },
|
|
33
|
+
"gpt-4.1": { contextWindow: 1_000_000 },
|
|
34
|
+
o3: { contextWindow: 200_000 },
|
|
35
|
+
"o4-mini": { contextWindow: 200_000 },
|
|
36
|
+
"gemini-3.5-flash-lite": { contextWindow: 1_048_576 },
|
|
37
|
+
"gemini-3.1-flash-lite": { contextWindow: 1_048_576 },
|
|
38
|
+
"deepseek-v4-pro": { contextWindow: 128_000, charsPerToken: 1.7 },
|
|
39
|
+
"deepseek-flash": { contextWindow: 128_000, charsPerToken: 1.7 },
|
|
40
|
+
"glm-5.3": { contextWindow: 128_000, charsPerToken: 1.7 },
|
|
41
|
+
"glm-5.2": { contextWindow: 128_000, charsPerToken: 1.7 },
|
|
42
|
+
"qwen3.8-max": { contextWindow: 128_000, charsPerToken: 1.7 },
|
|
43
|
+
"qwen3.8-flash": { contextWindow: 128_000, charsPerToken: 1.7 },
|
|
44
|
+
"kimi-k3": { contextWindow: 256_000, charsPerToken: 2.0 },
|
|
45
|
+
"grok-4.7": { contextWindow: 256_000, charsPerToken: 3.8 },
|
|
46
|
+
"mistral-large-latest": { contextWindow: 128_000, charsPerToken: 3.8 },
|
|
47
|
+
};
|
|
48
|
+
let overrideCache;
|
|
49
|
+
export function modelMetaOverridePath() {
|
|
50
|
+
const root = process.env["FASTCODE_HOME"] ?? join(homedir(), ".fastcode");
|
|
51
|
+
return join(root, "model-meta.json");
|
|
52
|
+
}
|
|
53
|
+
export function clearModelMetaCache() {
|
|
54
|
+
overrideCache = undefined;
|
|
55
|
+
}
|
|
56
|
+
function loadOverrides() {
|
|
57
|
+
if (overrideCache)
|
|
58
|
+
return overrideCache;
|
|
59
|
+
const file = modelMetaOverridePath();
|
|
60
|
+
if (!existsSync(file)) {
|
|
61
|
+
overrideCache = {};
|
|
62
|
+
return overrideCache;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const raw = JSON.parse(readFileSync(file, "utf8"));
|
|
66
|
+
overrideCache = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
overrideCache = {};
|
|
70
|
+
}
|
|
71
|
+
return overrideCache;
|
|
72
|
+
}
|
|
73
|
+
function usable(meta) {
|
|
74
|
+
return Boolean(meta) && typeof meta === "object";
|
|
75
|
+
}
|
|
76
|
+
function apply(base, ...layers) {
|
|
77
|
+
const next = { ...base };
|
|
78
|
+
for (const layer of layers) {
|
|
79
|
+
if (!usable(layer))
|
|
80
|
+
continue;
|
|
81
|
+
if (typeof layer.contextWindow === "number" && layer.contextWindow > 0)
|
|
82
|
+
next.contextWindow = Math.floor(layer.contextWindow);
|
|
83
|
+
if (typeof layer.charsPerToken === "number" && layer.charsPerToken > 0)
|
|
84
|
+
next.charsPerToken = layer.charsPerToken;
|
|
85
|
+
if (typeof layer.supportsTools === "boolean")
|
|
86
|
+
next.supportsTools = layer.supportsTools;
|
|
87
|
+
if (typeof layer.maxOutputTokens === "number" && layer.maxOutputTokens > 0)
|
|
88
|
+
next.maxOutputTokens = Math.floor(layer.maxOutputTokens);
|
|
89
|
+
}
|
|
90
|
+
return next;
|
|
91
|
+
}
|
|
92
|
+
export function getModelMeta(providerId, model) {
|
|
93
|
+
const provider = providerId && PROVIDER_META[providerId] ? PROVIDER_META[providerId] : UNKNOWN_MODEL_META;
|
|
94
|
+
const overrides = loadOverrides();
|
|
95
|
+
const keys = [
|
|
96
|
+
providerId && model ? `${providerId}/${model}` : undefined,
|
|
97
|
+
providerId && model ? `${providerId}::${model}` : undefined,
|
|
98
|
+
model,
|
|
99
|
+
].filter((key) => Boolean(key));
|
|
100
|
+
const layers = keys.map((key) => overrides[key]);
|
|
101
|
+
return apply(provider, model ? MODEL_OVERRIDES[model] : undefined, ...layers);
|
|
102
|
+
}
|
|
103
|
+
export function estTokens(text, options = {}) {
|
|
104
|
+
if (!text)
|
|
105
|
+
return 0;
|
|
106
|
+
const ratio = typeof options === "number"
|
|
107
|
+
? options
|
|
108
|
+
: options.charsPerToken ?? getModelMeta(options.providerId, options.model).charsPerToken;
|
|
109
|
+
return Math.ceil(text.length / Math.max(0.1, ratio));
|
|
110
|
+
}
|
|
111
|
+
export function estMessagesTokens(messages, options = {}) {
|
|
112
|
+
return messages.reduce((sum, message) => sum + estTokens(message.content ?? "", options), 0);
|
|
113
|
+
}
|