@datename/fastcode-termux 0.1.6-beta → 0.1.6
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/README.md +6 -24
- package/dist/agent/guards.d.ts +0 -8
- package/dist/agent/guards.js +0 -195
- package/dist/agent/protocol.js +1 -4
- package/dist/agent/tools.d.ts +1 -3
- package/dist/agent/tools.js +377 -2
- package/dist/app.js +103 -335
- package/dist/cli.js +311 -362
- package/dist/commands/index.js +0 -22
- package/dist/commands/login.d.ts +2 -4
- package/dist/commands/login.js +10 -12
- package/dist/commands/logout.js +16 -8
- package/dist/commands/model.d.ts +0 -13
- package/dist/commands/model.js +22 -42
- package/dist/commands/provider.d.ts +0 -13
- package/dist/commands/provider.js +9 -20
- package/dist/commands/whoami.js +5 -6
- package/dist/config/store.d.ts +12 -32
- package/dist/config/store.js +65 -117
- package/dist/config/trust.d.ts +8 -21
- package/dist/config/trust.js +13 -99
- package/dist/lib/diagnostics.d.ts +4 -29
- package/dist/lib/diagnostics.js +4 -60
- package/dist/lib/http.d.ts +1 -11
- package/dist/lib/http.js +4 -62
- package/dist/providers/anthropic.d.ts +0 -1
- package/dist/providers/anthropic.js +0 -12
- package/dist/providers/custom.d.ts +0 -9
- package/dist/providers/deepseek.d.ts +0 -10
- package/dist/providers/deepseek.js +1 -2
- package/dist/providers/google.d.ts +0 -9
- package/dist/providers/groq.d.ts +0 -10
- package/dist/providers/groq.js +1 -2
- package/dist/providers/index.d.ts +201 -10
- package/dist/providers/index.js +0 -30
- package/dist/providers/mistral.d.ts +0 -10
- package/dist/providers/mistral.js +1 -2
- package/dist/providers/moonshot.d.ts +0 -10
- package/dist/providers/moonshot.js +1 -2
- package/dist/providers/ollama.d.ts +0 -9
- package/dist/providers/openai.d.ts +0 -10
- package/dist/providers/openai.js +1 -2
- package/dist/providers/openaiCompatible.d.ts +0 -9
- package/dist/providers/openaiCompatible.js +10 -58
- package/dist/providers/openrouter.d.ts +0 -10
- package/dist/providers/openrouter.js +1 -2
- package/dist/providers/qwen.d.ts +0 -10
- package/dist/providers/qwen.js +1 -2
- package/dist/providers/xai.d.ts +0 -10
- package/dist/providers/xai.js +1 -2
- package/dist/providers/zhipu.d.ts +0 -10
- package/dist/providers/zhipu.js +1 -2
- package/dist/session/log.d.ts +12 -35
- package/dist/session/log.js +10 -206
- package/dist/types.d.ts +1 -60
- package/dist/ui/Message.d.ts +7 -1
- package/dist/ui/Message.js +2 -13
- package/dist/ui/Prompt.d.ts +2 -1
- package/dist/ui/Prompt.js +2 -2
- package/dist/ui/Spinner.js +5 -11
- package/dist/ui/StatusBar.d.ts +1 -2
- package/dist/ui/StatusBar.js +5 -6
- package/package.json +1 -1
- package/src/agent/guards.ts +0 -196
- package/src/agent/protocol.ts +1 -4
- package/src/agent/tools.ts +377 -3
- package/src/app.ts +103 -335
- package/src/cli.ts +3 -30
- package/src/commands/index.ts +0 -22
- package/src/commands/login.ts +10 -12
- package/src/commands/logout.ts +16 -8
- package/src/commands/model.ts +22 -42
- package/src/commands/provider.ts +9 -20
- package/src/commands/whoami.ts +5 -6
- package/src/config/store.ts +69 -122
- package/src/config/trust.ts +21 -137
- package/src/lib/diagnostics.ts +4 -60
- package/src/lib/http.ts +4 -66
- package/src/providers/anthropic.ts +1 -13
- package/src/providers/deepseek.ts +1 -2
- package/src/providers/groq.ts +1 -2
- package/src/providers/index.ts +1 -32
- package/src/providers/mistral.ts +1 -2
- package/src/providers/moonshot.ts +1 -2
- package/src/providers/openai.ts +1 -2
- package/src/providers/openaiCompatible.ts +10 -58
- package/src/providers/openrouter.ts +1 -2
- package/src/providers/qwen.ts +1 -2
- package/src/providers/xai.ts +1 -2
- package/src/providers/zhipu.ts +1 -2
- package/src/session/log.ts +28 -267
- package/src/types.ts +1 -63
- package/src/ui/Message.ts +2 -13
- package/src/ui/Prompt.ts +2 -2
- package/src/ui/Spinner.ts +5 -11
- package/src/ui/StatusBar.ts +5 -6
- package/dist/agent/approval.d.ts +0 -50
- package/dist/agent/approval.js +0 -7
- package/dist/agent/compaction.d.ts +0 -39
- package/dist/agent/compaction.js +0 -227
- package/dist/agent/context.d.ts +0 -29
- package/dist/agent/context.js +0 -62
- package/dist/agent/ignore.d.ts +0 -11
- package/dist/agent/ignore.js +0 -129
- package/dist/agent/process-tree.d.ts +0 -2
- package/dist/agent/process-tree.js +0 -31
- package/dist/agent/protocol-mode.d.ts +0 -6
- package/dist/agent/protocol-mode.js +0 -51
- package/dist/agent/registry.d.ts +0 -17
- package/dist/agent/registry.js +0 -90
- package/dist/agent/runner.d.ts +0 -106
- package/dist/agent/runner.js +0 -352
- package/dist/agent/shell-session.d.ts +0 -37
- package/dist/agent/shell-session.js +0 -400
- package/dist/agent/stream.d.ts +0 -29
- package/dist/agent/stream.js +0 -103
- package/dist/agent/tool-schemas.d.ts +0 -2
- package/dist/agent/tool-schemas.js +0 -63
- package/dist/agent/tools/append_file.d.ts +0 -2
- package/dist/agent/tools/append_file.js +0 -26
- package/dist/agent/tools/apply_patch.d.ts +0 -10
- package/dist/agent/tools/apply_patch.js +0 -52
- package/dist/agent/tools/copy_file.d.ts +0 -2
- package/dist/agent/tools/copy_file.js +0 -30
- package/dist/agent/tools/delete_file.d.ts +0 -2
- package/dist/agent/tools/delete_file.js +0 -24
- package/dist/agent/tools/grep.d.ts +0 -2
- package/dist/agent/tools/grep.js +0 -23
- package/dist/agent/tools/index.d.ts +0 -2
- package/dist/agent/tools/index.js +0 -32
- package/dist/agent/tools/list_files.d.ts +0 -2
- package/dist/agent/tools/list_files.js +0 -17
- package/dist/agent/tools/make_dir.d.ts +0 -2
- package/dist/agent/tools/make_dir.js +0 -24
- package/dist/agent/tools/move_path.d.ts +0 -2
- package/dist/agent/tools/move_path.js +0 -47
- package/dist/agent/tools/paths.d.ts +0 -7
- package/dist/agent/tools/paths.js +0 -84
- package/dist/agent/tools/pwd.d.ts +0 -2
- package/dist/agent/tools/pwd.js +0 -10
- package/dist/agent/tools/read_file.d.ts +0 -2
- package/dist/agent/tools/read_file.js +0 -21
- package/dist/agent/tools/read_many.d.ts +0 -2
- package/dist/agent/tools/read_many.js +0 -25
- package/dist/agent/tools/readers.d.ts +0 -6
- package/dist/agent/tools/readers.js +0 -216
- package/dist/agent/tools/replace_in_file.d.ts +0 -2
- package/dist/agent/tools/replace_in_file.js +0 -30
- package/dist/agent/tools/shell.d.ts +0 -2
- package/dist/agent/tools/shell.js +0 -48
- package/dist/agent/tools/stat_path.d.ts +0 -2
- package/dist/agent/tools/stat_path.js +0 -31
- package/dist/agent/tools/types.d.ts +0 -39
- package/dist/agent/tools/types.js +0 -1
- package/dist/agent/tools/validate.d.ts +0 -6
- package/dist/agent/tools/validate.js +0 -29
- package/dist/agent/tools/write_file.d.ts +0 -2
- package/dist/agent/tools/write_file.js +0 -26
- package/dist/commands/attach.d.ts +0 -9
- package/dist/commands/attach.js +0 -15
- package/dist/commands/bg.d.ts +0 -7
- package/dist/commands/bg.js +0 -9
- package/dist/commands/cancel.d.ts +0 -10
- package/dist/commands/cancel.js +0 -10
- package/dist/commands/compact.d.ts +0 -10
- package/dist/commands/compact.js +0 -22
- package/dist/commands/context.d.ts +0 -7
- package/dist/commands/context.js +0 -24
- package/dist/commands/edit.d.ts +0 -10
- package/dist/commands/edit.js +0 -36
- package/dist/commands/effort.d.ts +0 -25
- package/dist/commands/effort.js +0 -48
- package/dist/commands/expand.d.ts +0 -10
- package/dist/commands/expand.js +0 -8
- package/dist/commands/jobs.d.ts +0 -7
- package/dist/commands/jobs.js +0 -16
- package/dist/commands/kill.d.ts +0 -7
- package/dist/commands/kill.js +0 -11
- package/dist/commands/steer.d.ts +0 -11
- package/dist/commands/steer.js +0 -13
- package/dist/lib/model-meta.d.ts +0 -19
- package/dist/lib/model-meta.js +0 -113
- package/dist/lib/native-tools.d.ts +0 -12
- package/dist/lib/native-tools.js +0 -21
- package/dist/lib/net.d.ts +0 -33
- package/dist/lib/net.js +0 -168
- package/dist/patch/apply.d.ts +0 -15
- package/dist/patch/apply.js +0 -172
- package/dist/patch/parse.d.ts +0 -29
- package/dist/patch/parse.js +0 -112
- package/dist/patch/plan.d.ts +0 -49
- package/dist/patch/plan.js +0 -273
- package/dist/session/projection.d.ts +0 -43
- package/dist/session/projection.js +0 -257
- package/dist/ui/Picker.d.ts +0 -8
- package/dist/ui/Picker.js +0 -24
- package/src/agent/approval.ts +0 -47
- package/src/agent/compaction.ts +0 -276
- package/src/agent/context.ts +0 -97
- package/src/agent/ignore.ts +0 -147
- package/src/agent/process-tree.ts +0 -32
- package/src/agent/protocol-mode.ts +0 -59
- package/src/agent/registry.ts +0 -98
- package/src/agent/runner.ts +0 -443
- package/src/agent/shell-session.ts +0 -449
- package/src/agent/stream.ts +0 -143
- package/src/agent/tool-schemas.ts +0 -67
- package/src/agent/tools/append_file.ts +0 -28
- package/src/agent/tools/apply_patch.ts +0 -54
- package/src/agent/tools/copy_file.ts +0 -32
- package/src/agent/tools/delete_file.ts +0 -26
- package/src/agent/tools/grep.ts +0 -25
- package/src/agent/tools/index.ts +0 -34
- package/src/agent/tools/list_files.ts +0 -19
- package/src/agent/tools/make_dir.ts +0 -26
- package/src/agent/tools/move_path.ts +0 -50
- package/src/agent/tools/paths.ts +0 -92
- package/src/agent/tools/pwd.ts +0 -12
- package/src/agent/tools/read_file.ts +0 -23
- package/src/agent/tools/read_many.ts +0 -27
- package/src/agent/tools/readers.ts +0 -228
- package/src/agent/tools/replace_in_file.ts +0 -32
- package/src/agent/tools/shell.ts +0 -53
- package/src/agent/tools/stat_path.ts +0 -33
- package/src/agent/tools/types.ts +0 -42
- package/src/agent/tools/validate.ts +0 -35
- package/src/agent/tools/write_file.ts +0 -28
- package/src/commands/attach.ts +0 -16
- package/src/commands/bg.ts +0 -9
- package/src/commands/cancel.ts +0 -10
- package/src/commands/compact.ts +0 -23
- package/src/commands/context.ts +0 -25
- package/src/commands/edit.ts +0 -37
- package/src/commands/effort.ts +0 -49
- package/src/commands/expand.ts +0 -8
- package/src/commands/jobs.ts +0 -17
- package/src/commands/kill.ts +0 -12
- package/src/commands/steer.ts +0 -13
- package/src/lib/model-meta.ts +0 -138
- package/src/lib/native-tools.ts +0 -32
- package/src/lib/net.ts +0 -204
- package/src/patch/apply.ts +0 -194
- package/src/patch/parse.ts +0 -131
- package/src/patch/plan.ts +0 -317
- package/src/session/projection.ts +0 -308
- package/src/ui/Picker.ts +0 -24
package/src/agent/approval.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
export type ApprovalScope = "once" | "session" | "always";
|
|
2
|
-
|
|
3
|
-
export type ApprovalReason =
|
|
4
|
-
| "trust"
|
|
5
|
-
| "sensitive_path"
|
|
6
|
-
| "ambiguous_match"
|
|
7
|
-
| "destructive"
|
|
8
|
-
| "large_diff";
|
|
9
|
-
|
|
10
|
-
export interface ChoiceItem {
|
|
11
|
-
label: string;
|
|
12
|
-
value: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type ApprovalPreview =
|
|
16
|
-
| { kind: "diff"; text: string }
|
|
17
|
-
| { kind: "choices"; items: ChoiceItem[] };
|
|
18
|
-
|
|
19
|
-
export type ApprovalOption =
|
|
20
|
-
| { kind: "allow"; scope: ApprovalScope; label: string }
|
|
21
|
-
| { kind: "deny"; label: string }
|
|
22
|
-
| { kind: "choose"; label: string; value: string }
|
|
23
|
-
| { kind: "feedback"; label: string; prompt: string };
|
|
24
|
-
|
|
25
|
-
export interface ApprovalRequest {
|
|
26
|
-
id: string;
|
|
27
|
-
tool: string;
|
|
28
|
-
args: unknown;
|
|
29
|
-
reason: ApprovalReason;
|
|
30
|
-
options: ApprovalOption[];
|
|
31
|
-
preview?: ApprovalPreview;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export type ApprovalDecision =
|
|
35
|
-
| { kind: "allow"; scope: ApprovalScope }
|
|
36
|
-
| { kind: "deny"; reason?: string; feedback?: string }
|
|
37
|
-
| { kind: "pending"; request: ApprovalRequest };
|
|
38
|
-
|
|
39
|
-
export type ApprovalResolver = (request: ApprovalRequest) => ApprovalDecision | Promise<ApprovalDecision>;
|
|
40
|
-
|
|
41
|
-
export const DEFAULT_ALLOW_OPTIONS: ApprovalOption[] = [
|
|
42
|
-
{ kind: "allow", scope: "once", label: "Allow once" },
|
|
43
|
-
{ kind: "allow", scope: "session", label: "Allow for this session" },
|
|
44
|
-
{ kind: "allow", scope: "always", label: "Always allow" },
|
|
45
|
-
{ kind: "deny", label: "Deny" },
|
|
46
|
-
{ kind: "feedback", label: "Deny with feedback", prompt: "Why should this be denied?" },
|
|
47
|
-
];
|
package/src/agent/compaction.ts
DELETED
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { estMessagesTokens, estTokens } from "../lib/model-meta.js";
|
|
4
|
-
import { getPaths } from "../config/paths.js";
|
|
5
|
-
import type { ChatMessage } from "../types.js";
|
|
6
|
-
import { parseToolCalls, stripToolCalls } from "./protocol.js";
|
|
7
|
-
|
|
8
|
-
export interface CompactionConfig {
|
|
9
|
-
enabled: boolean;
|
|
10
|
-
localThresholdRatio: number;
|
|
11
|
-
keepTurns: number;
|
|
12
|
-
serverCompactionAware: boolean;
|
|
13
|
-
serverCompactThreshold?: number;
|
|
14
|
-
clampAboveServerByTokens: number;
|
|
15
|
-
maxSummaryTokens: number;
|
|
16
|
-
heapLimitMb: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const DEFAULT_COMPACTION_CONFIG: CompactionConfig = {
|
|
20
|
-
enabled: true,
|
|
21
|
-
localThresholdRatio: 0.7,
|
|
22
|
-
keepTurns: 6,
|
|
23
|
-
serverCompactionAware: false,
|
|
24
|
-
clampAboveServerByTokens: 8192,
|
|
25
|
-
maxSummaryTokens: 1500,
|
|
26
|
-
heapLimitMb: 220,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export interface CompactionOptions {
|
|
30
|
-
contextWindow: number;
|
|
31
|
-
providerId?: string;
|
|
32
|
-
model?: string;
|
|
33
|
-
force?: boolean;
|
|
34
|
-
serverOpaqueState?: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface CompactionResult {
|
|
38
|
-
messages: ChatMessage[];
|
|
39
|
-
compacted: boolean;
|
|
40
|
-
reason?: "threshold" | "heap";
|
|
41
|
-
source: "local" | "server" | "none";
|
|
42
|
-
beforeTokens: number;
|
|
43
|
-
afterTokens: number;
|
|
44
|
-
beforeChars: number;
|
|
45
|
-
afterChars: number;
|
|
46
|
-
droppedMessages: number;
|
|
47
|
-
keptMessages: number;
|
|
48
|
-
summary?: string;
|
|
49
|
-
summaryFile?: string;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function compactionConfigFromEnv(): CompactionConfig {
|
|
53
|
-
const config = { ...DEFAULT_COMPACTION_CONFIG };
|
|
54
|
-
const disabled = (process.env["FASTCODE_COMPACTION"] ?? "").trim().toLowerCase();
|
|
55
|
-
if (["0", "false", "no", "off"].includes(disabled))
|
|
56
|
-
config.enabled = false;
|
|
57
|
-
const heap = Number(process.env["FASTCODE_HEAP_LIMIT_MB"]);
|
|
58
|
-
if (Number.isFinite(heap) && heap > 0)
|
|
59
|
-
config.heapLimitMb = heap;
|
|
60
|
-
const server = (process.env["FASTCODE_SERVER_COMPACTION"] ?? "").trim().toLowerCase();
|
|
61
|
-
if (["1", "true", "yes", "on"].includes(server))
|
|
62
|
-
config.serverCompactionAware = true;
|
|
63
|
-
return config;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function localThresholdTokens(config: CompactionConfig, contextWindow: number): number {
|
|
67
|
-
const local = Math.floor(Math.max(1, contextWindow) * config.localThresholdRatio);
|
|
68
|
-
if (config.serverCompactionAware && typeof config.serverCompactThreshold === "number" && config.serverCompactThreshold > 0) {
|
|
69
|
-
return Math.max(local, config.serverCompactThreshold + config.clampAboveServerByTokens);
|
|
70
|
-
}
|
|
71
|
-
return local;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function heapExceeded(config: CompactionConfig): boolean {
|
|
75
|
-
const used = process.memoryUsage().heapUsed;
|
|
76
|
-
return used > Math.max(0, config.heapLimitMb) * 1024 * 1024;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function shouldCompact(messages: ChatMessage[], config: CompactionConfig, options: CompactionOptions): boolean {
|
|
80
|
-
if (!config.enabled)
|
|
81
|
-
return false;
|
|
82
|
-
if (options.force || heapExceeded(config))
|
|
83
|
-
return true;
|
|
84
|
-
return messageTokens(messages, options) > localThresholdTokens(config, options.contextWindow);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function compactMessages(messages: ChatMessage[], config: CompactionConfig = DEFAULT_COMPACTION_CONFIG, options: CompactionOptions): CompactionResult {
|
|
88
|
-
const beforeTokens = messageTokens(messages, options);
|
|
89
|
-
const beforeChars = messageChars(messages);
|
|
90
|
-
const heap = heapExceeded(config);
|
|
91
|
-
const noop: CompactionResult = {
|
|
92
|
-
messages,
|
|
93
|
-
compacted: false,
|
|
94
|
-
source: "none",
|
|
95
|
-
beforeTokens,
|
|
96
|
-
afterTokens: beforeTokens,
|
|
97
|
-
beforeChars,
|
|
98
|
-
afterChars: beforeChars,
|
|
99
|
-
droppedMessages: 0,
|
|
100
|
-
keptMessages: messages.length,
|
|
101
|
-
};
|
|
102
|
-
if (!config.enabled)
|
|
103
|
-
return noop;
|
|
104
|
-
const threshold = localThresholdTokens(config, options.contextWindow);
|
|
105
|
-
if (!options.force && !heap && beforeTokens <= threshold)
|
|
106
|
-
return noop;
|
|
107
|
-
const reason: "threshold" | "heap" = heap && beforeTokens <= threshold ? "heap" : "threshold";
|
|
108
|
-
if (config.serverCompactionAware && options.serverOpaqueState) {
|
|
109
|
-
return {
|
|
110
|
-
...noop,
|
|
111
|
-
compacted: true,
|
|
112
|
-
reason,
|
|
113
|
-
source: "server",
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
const headCount = messages[0]?.role === "user" ? 1 : 0;
|
|
117
|
-
const keep = Math.max(2, Math.floor(config.keepTurns * 2));
|
|
118
|
-
const tailStart = Math.max(headCount, messages.length - keep);
|
|
119
|
-
if (tailStart <= headCount)
|
|
120
|
-
return noop;
|
|
121
|
-
const middle = messages.slice(headCount, tailStart);
|
|
122
|
-
const summary = buildSummary(middle, config, options);
|
|
123
|
-
const summaryFile = writeSummaryFile(summary);
|
|
124
|
-
const marker: ChatMessage = {
|
|
125
|
-
role: "system",
|
|
126
|
-
content: `[context compacted: ${middle.length} earlier messages were summarized to stay within the context window.` +
|
|
127
|
-
`${summaryFile ? ` Summary file: ${summaryFile}.` : ""} Recent turns are kept verbatim.` +
|
|
128
|
-
" If an earlier detail is missing, re-read that file with a tool.]\n\n" +
|
|
129
|
-
summary,
|
|
130
|
-
};
|
|
131
|
-
const next = [...messages.slice(0, headCount), marker, ...messages.slice(tailStart)];
|
|
132
|
-
return {
|
|
133
|
-
messages: next,
|
|
134
|
-
compacted: true,
|
|
135
|
-
reason,
|
|
136
|
-
source: "local",
|
|
137
|
-
beforeTokens,
|
|
138
|
-
afterTokens: messageTokens(next, options),
|
|
139
|
-
beforeChars,
|
|
140
|
-
afterChars: messageChars(next),
|
|
141
|
-
droppedMessages: middle.length,
|
|
142
|
-
keptMessages: next.length - headCount - 1,
|
|
143
|
-
summary,
|
|
144
|
-
summaryFile,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function messageTokens(messages: ChatMessage[], options: CompactionOptions): number {
|
|
149
|
-
return estMessagesTokens(messages, { providerId: options.providerId, model: options.model });
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function messageChars(messages: ChatMessage[]): number {
|
|
153
|
-
return messages.reduce((sum, message) => sum + (message.content?.length ?? 0), 0);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export function buildSummary(messages: ChatMessage[], config: CompactionConfig, options: Pick<CompactionOptions, "providerId" | "model">): string {
|
|
157
|
-
const requests: string[] = [];
|
|
158
|
-
const actions: string[] = [];
|
|
159
|
-
const outcomes: string[] = [];
|
|
160
|
-
const notes: string[] = [];
|
|
161
|
-
const files = new Set<string>();
|
|
162
|
-
for (const message of messages) {
|
|
163
|
-
if (!message.content)
|
|
164
|
-
continue;
|
|
165
|
-
if (message.role === "user") {
|
|
166
|
-
if (message.content.startsWith("FastCode executed the requested local tools.")) {
|
|
167
|
-
for (const line of message.content.split(/\r?\n/)) {
|
|
168
|
-
if (/error|failed|not found|denied|timed out/i.test(line))
|
|
169
|
-
outcomes.push(clip(line, 240));
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
requests.push(clip(message.content, 300));
|
|
174
|
-
}
|
|
175
|
-
collectFiles(message.content, files);
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
if (message.role === "assistant") {
|
|
179
|
-
const calls = parseToolCalls(message.content);
|
|
180
|
-
for (const call of calls)
|
|
181
|
-
actions.push(describeCall(call));
|
|
182
|
-
const visible = stripToolCalls(message.content);
|
|
183
|
-
if (visible)
|
|
184
|
-
notes.push(clip(visible, 300));
|
|
185
|
-
collectFiles(message.content, files);
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
const lines = message.content.split(/\r?\n/);
|
|
189
|
-
for (const line of lines) {
|
|
190
|
-
if (/error|failed|not found|denied|timed out/i.test(line))
|
|
191
|
-
outcomes.push(clip(line, 240));
|
|
192
|
-
}
|
|
193
|
-
collectFiles(message.content, files);
|
|
194
|
-
}
|
|
195
|
-
const sections = [
|
|
196
|
-
"# Session summary",
|
|
197
|
-
"",
|
|
198
|
-
"## Requests",
|
|
199
|
-
...limitLines(requests, 8).map((line) => `- ${line}`),
|
|
200
|
-
"",
|
|
201
|
-
"## Actions",
|
|
202
|
-
...limitLines(actions, 20).map((line) => `- ${line}`),
|
|
203
|
-
"",
|
|
204
|
-
"## Outcomes",
|
|
205
|
-
...limitLines(outcomes, 10).map((line) => `- ${line}`),
|
|
206
|
-
"",
|
|
207
|
-
"## Files touched",
|
|
208
|
-
...(files.size > 0 ? Array.from(files).slice(0, 40).map((file) => `- ${file}`) : ["- (none recorded)"]),
|
|
209
|
-
"",
|
|
210
|
-
"## Notes",
|
|
211
|
-
...limitLines(notes, 10).map((line) => `- ${line}`),
|
|
212
|
-
];
|
|
213
|
-
const budget = Math.max(200, Math.floor(config.maxSummaryTokens));
|
|
214
|
-
const kept: string[] = [];
|
|
215
|
-
let used = 0;
|
|
216
|
-
for (const line of sections) {
|
|
217
|
-
const cost = estTokens(line + "\n", options);
|
|
218
|
-
if (used + cost > budget) {
|
|
219
|
-
kept.push("[... summary truncated ...]");
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
kept.push(line);
|
|
223
|
-
used += cost;
|
|
224
|
-
}
|
|
225
|
-
return kept.join("\n").trimEnd() + "\n";
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function describeCall(call: any): string {
|
|
229
|
-
const tool = String(call?.tool ?? "unknown");
|
|
230
|
-
if (tool === "shell" && typeof call.command === "string")
|
|
231
|
-
return `shell: ${clip(call.command, 200)}`;
|
|
232
|
-
if (typeof call.path === "string")
|
|
233
|
-
return `${tool}: ${call.path}`;
|
|
234
|
-
if (typeof call.from === "string" || typeof call.to === "string")
|
|
235
|
-
return `${tool}: ${call.from ?? "?"} -> ${call.to ?? "?"}`;
|
|
236
|
-
if (typeof call.pattern === "string")
|
|
237
|
-
return `${tool}: /${clip(call.pattern, 120)}/`;
|
|
238
|
-
return tool;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function collectFiles(text: string, files: Set<string>): void {
|
|
242
|
-
const matches = text.match(/[A-Za-z0-9_@./\\-]+\.[A-Za-z0-9]{1,8}\b/g);
|
|
243
|
-
if (!matches)
|
|
244
|
-
return;
|
|
245
|
-
for (const match of matches) {
|
|
246
|
-
if (match.length <= 3 || match.length > 240)
|
|
247
|
-
continue;
|
|
248
|
-
if (/^\d+(\.\d+)+$/.test(match))
|
|
249
|
-
continue;
|
|
250
|
-
files.add(match);
|
|
251
|
-
if (files.size >= 200)
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function limitLines(lines: string[], max: number): string[] {
|
|
257
|
-
return lines.slice(0, max);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function clip(text: string, max: number): string {
|
|
261
|
-
const oneLine = String(text).replace(/\s+/g, " ").trim();
|
|
262
|
-
return oneLine.length > max ? oneLine.slice(0, max) + "…" : oneLine;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function writeSummaryFile(summary: string): string | undefined {
|
|
266
|
-
try {
|
|
267
|
-
const dir = getPaths().sessions;
|
|
268
|
-
mkdirSync(dir, { recursive: true });
|
|
269
|
-
const file = join(dir, `summary-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.md`);
|
|
270
|
-
writeFileSync(file, summary, "utf8");
|
|
271
|
-
return file;
|
|
272
|
-
}
|
|
273
|
-
catch {
|
|
274
|
-
return undefined;
|
|
275
|
-
}
|
|
276
|
-
}
|
package/src/agent/context.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Context compaction for long agent sessions.
|
|
3
|
-
*
|
|
4
|
-
* The chat loop keeps appending tool results to the conversation, so a long
|
|
5
|
-
* investigation can grow past the model's window and fail with a provider 400.
|
|
6
|
-
* Instead of silently truncating the tail (which loses the current task), we
|
|
7
|
-
* keep the first user request and the most recent turns, and shrink the middle
|
|
8
|
-
* into short summaries with an explicit marker.
|
|
9
|
-
*
|
|
10
|
-
* Token counts are approximated at 4 characters per token. That is deliberately
|
|
11
|
-
* crude: it must work for every provider without a tokenizer dependency.
|
|
12
|
-
*/
|
|
13
|
-
import type { ChatMessage } from "../types.js";
|
|
14
|
-
|
|
15
|
-
export interface CompactOptions {
|
|
16
|
-
/** Character budget for the whole history (default 400k ≈ 100k tokens). */
|
|
17
|
-
maxChars?: number;
|
|
18
|
-
/** Number of trailing messages to keep verbatim. */
|
|
19
|
-
keepRecent?: number;
|
|
20
|
-
/** Maximum characters kept from one old tool result. */
|
|
21
|
-
toolSummaryChars?: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface CompactResult {
|
|
25
|
-
messages: ChatMessage[];
|
|
26
|
-
compacted: boolean;
|
|
27
|
-
beforeChars: number;
|
|
28
|
-
afterChars: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const TOOL_MARKER = "FastCode executed the requested local tools.";
|
|
32
|
-
|
|
33
|
-
export function historyChars(messages: ChatMessage[]): number {
|
|
34
|
-
return messages.reduce((sum, message) => sum + (message.content?.length ?? 0), 0);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function compactHistory(messages: ChatMessage[], options: CompactOptions = {}): CompactResult {
|
|
38
|
-
const beforeChars = historyChars(messages);
|
|
39
|
-
const maxChars = positive(options.maxChars, 400_000);
|
|
40
|
-
const keepRecent = Math.max(2, Math.floor(positive(options.keepRecent, 6)));
|
|
41
|
-
const toolSummaryChars = Math.max(200, Math.floor(positive(options.toolSummaryChars, 2_000)));
|
|
42
|
-
if (beforeChars <= maxChars || messages.length <= keepRecent + 1) {
|
|
43
|
-
return { messages, compacted: false, beforeChars, afterChars: beforeChars };
|
|
44
|
-
}
|
|
45
|
-
// Keep the original request (usually the first message) plus the newest
|
|
46
|
-
// turns. Everything between is summarized, never dropped without a marker.
|
|
47
|
-
const head = messages[0]?.role === "user" ? [messages[0]] : [];
|
|
48
|
-
const tailStart = Math.max(head.length, messages.length - keepRecent);
|
|
49
|
-
const middle = messages.slice(head.length, tailStart);
|
|
50
|
-
const summarized = middle.map((message) => ({
|
|
51
|
-
role: message.role,
|
|
52
|
-
content: summarizeMessage(message.content, toolSummaryChars),
|
|
53
|
-
}));
|
|
54
|
-
const marker: ChatMessage = {
|
|
55
|
-
role: "system",
|
|
56
|
-
content: `[context compacted: ${middle.length} earlier message(s) were summarized to stay within the context window. ` +
|
|
57
|
-
`Recent tool results are kept in full. If an earlier detail is missing, re-read that file with a tool.]`,
|
|
58
|
-
};
|
|
59
|
-
const next = [...head, marker, ...summarized, ...messages.slice(tailStart)];
|
|
60
|
-
return {
|
|
61
|
-
messages: next,
|
|
62
|
-
compacted: true,
|
|
63
|
-
beforeChars,
|
|
64
|
-
afterChars: historyChars(next),
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function summarizeMessage(content: string, limit: number): string {
|
|
69
|
-
if (!content)
|
|
70
|
-
return content;
|
|
71
|
-
if (content.includes(TOOL_MARKER))
|
|
72
|
-
return summarizeToolMessage(content, limit);
|
|
73
|
-
if (content.length <= limit)
|
|
74
|
-
return content;
|
|
75
|
-
const head = content.slice(0, Math.floor(limit * 0.6));
|
|
76
|
-
const tail = content.slice(-Math.floor(limit * 0.3));
|
|
77
|
-
return `${head}\n[… ${content.length - head.length - tail.length} chars summarized …]\n${tail}`;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Tool results have the shape:
|
|
82
|
-
* FastCode executed ...\n\nTool result 1 (ok)\ncall: {...}\noutput:\n```...```
|
|
83
|
-
* Keep the call and the start/end of each output so file paths and errors
|
|
84
|
-
* survive compaction.
|
|
85
|
-
*/
|
|
86
|
-
function summarizeToolMessage(content: string, limit: number): string {
|
|
87
|
-
if (content.length <= limit)
|
|
88
|
-
return content;
|
|
89
|
-
const keep = Math.max(120, Math.floor((limit - 240) / 2));
|
|
90
|
-
const head = content.slice(0, keep);
|
|
91
|
-
const tail = content.slice(-keep);
|
|
92
|
-
return `${head}\n[… ${content.length - keep * 2} chars of tool output summarized; re-run the tool if you need the middle …]\n${tail}`;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function positive(value: number | undefined, fallback: number): number {
|
|
96
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
97
|
-
}
|
package/src/agent/ignore.ts
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ignore rules for directory walks and reads.
|
|
3
|
-
*
|
|
4
|
-
* Defaults cover the usual dependency/build folders. Users can add more with
|
|
5
|
-
* `.fastcodeignore` in the project root, using a gitignore-like syntax:
|
|
6
|
-
*
|
|
7
|
-
* node_modules/ # trailing slash = directories only
|
|
8
|
-
* *.log # basename at any depth
|
|
9
|
-
* /secrets.txt # anchored to the project root
|
|
10
|
-
* !important.log # re-include (last matching rule wins)
|
|
11
|
-
*/
|
|
12
|
-
import { readFileSync, statSync } from "node:fs";
|
|
13
|
-
import { join, relative, sep } from "node:path";
|
|
14
|
-
|
|
15
|
-
const DEFAULT_NAMES = new Set([
|
|
16
|
-
"node_modules",
|
|
17
|
-
".git",
|
|
18
|
-
".hg",
|
|
19
|
-
".svn",
|
|
20
|
-
"dist",
|
|
21
|
-
"build",
|
|
22
|
-
"out",
|
|
23
|
-
".next",
|
|
24
|
-
".nuxt",
|
|
25
|
-
".output",
|
|
26
|
-
".turbo",
|
|
27
|
-
".parcel-cache",
|
|
28
|
-
".cache",
|
|
29
|
-
"coverage",
|
|
30
|
-
".nyc_output",
|
|
31
|
-
"__pycache__",
|
|
32
|
-
".venv",
|
|
33
|
-
"venv",
|
|
34
|
-
"target",
|
|
35
|
-
".gradle",
|
|
36
|
-
".idea",
|
|
37
|
-
]);
|
|
38
|
-
|
|
39
|
-
const BINARY_EXT = /\.(png|jpe?g|gif|webp|ico|bmp|tiff?|pdf|zip|gz|tgz|bz2|xz|7z|rar|exe|dll|so|dylib|bin|wasm|mp3|mp4|mov|avi|mkv|wav|flac|ttf|otf|woff2?|eot|class|jar|apk|ipa|sqlite|db|lock)$/i;
|
|
40
|
-
|
|
41
|
-
export interface IgnoreRule {
|
|
42
|
-
regex: RegExp;
|
|
43
|
-
negate: boolean;
|
|
44
|
-
dirOnly: boolean;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface IgnoreMatcher {
|
|
48
|
-
ignored(name: string, fullPath: string, isDir: boolean): boolean;
|
|
49
|
-
isBinary(name: string): boolean;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function isBinaryName(name: string): boolean {
|
|
53
|
-
return BINARY_EXT.test(name);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function createIgnore(root: string): IgnoreMatcher {
|
|
57
|
-
const rules = loadRules(root);
|
|
58
|
-
return {
|
|
59
|
-
ignored(name, fullPath, isDir) {
|
|
60
|
-
if (DEFAULT_NAMES.has(name))
|
|
61
|
-
return true;
|
|
62
|
-
if (rules.length === 0)
|
|
63
|
-
return false;
|
|
64
|
-
const rel = relative(root, fullPath).split(sep).join("/");
|
|
65
|
-
let ignored = false;
|
|
66
|
-
for (const rule of rules) {
|
|
67
|
-
if (rule.dirOnly && !isDir)
|
|
68
|
-
continue;
|
|
69
|
-
if (rule.regex.test(rel) || rule.regex.test(name))
|
|
70
|
-
ignored = !rule.negate;
|
|
71
|
-
}
|
|
72
|
-
return ignored;
|
|
73
|
-
},
|
|
74
|
-
isBinary(name) {
|
|
75
|
-
return BINARY_EXT.test(name);
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function loadRules(root: string): IgnoreRule[] {
|
|
81
|
-
const file = join(root, ".fastcodeignore");
|
|
82
|
-
let text = "";
|
|
83
|
-
try {
|
|
84
|
-
const info = statSync(file);
|
|
85
|
-
if (!info.isFile())
|
|
86
|
-
return [];
|
|
87
|
-
text = readFileSync(file, "utf8");
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
return [];
|
|
91
|
-
}
|
|
92
|
-
const rules: IgnoreRule[] = [];
|
|
93
|
-
for (const raw of text.split(/\r?\n/)) {
|
|
94
|
-
const line = raw.trim();
|
|
95
|
-
if (!line || line.startsWith("#"))
|
|
96
|
-
continue;
|
|
97
|
-
const negate = line.startsWith("!");
|
|
98
|
-
const body = negate ? line.slice(1).trim() : line;
|
|
99
|
-
if (!body)
|
|
100
|
-
continue;
|
|
101
|
-
const dirOnly = body.endsWith("/");
|
|
102
|
-
const pattern = dirOnly ? body.slice(0, -1) : body;
|
|
103
|
-
const anchored = pattern.startsWith("/");
|
|
104
|
-
const glob = anchored ? pattern.slice(1) : pattern;
|
|
105
|
-
try {
|
|
106
|
-
rules.push({
|
|
107
|
-
regex: globToRegExp(glob, anchored),
|
|
108
|
-
negate,
|
|
109
|
-
dirOnly,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
// A bad pattern must not disable the whole ignore file.
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return rules;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function globToRegExp(glob: string, anchored: boolean): RegExp {
|
|
120
|
-
let source = "";
|
|
121
|
-
for (let i = 0; i < glob.length; i += 1) {
|
|
122
|
-
const char = glob[i];
|
|
123
|
-
if (char === "*") {
|
|
124
|
-
if (glob[i + 1] === "*") {
|
|
125
|
-
source += ".*";
|
|
126
|
-
i += 1;
|
|
127
|
-
if (glob[i + 1] === "/")
|
|
128
|
-
i += 1;
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
source += "[^/]*";
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
else if (char === "?") {
|
|
135
|
-
source += "[^/]";
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
source += escapeRegExp(char);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
// Unanchored patterns match a basename at any depth.
|
|
142
|
-
return new RegExp("^" + (anchored ? "" : "(?:.*/)?") + source + "$");
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function escapeRegExp(value: string): string {
|
|
146
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
147
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { spawn, type ChildProcess } from "node:child_process";
|
|
2
|
-
|
|
3
|
-
export function killProcessTree(child: ChildProcess | undefined, signal: NodeJS.Signals = "SIGTERM"): void {
|
|
4
|
-
if (!child || typeof child.pid !== "number")
|
|
5
|
-
return;
|
|
6
|
-
if (process.platform === "win32") {
|
|
7
|
-
try {
|
|
8
|
-
const killer = spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" });
|
|
9
|
-
killer.unref();
|
|
10
|
-
}
|
|
11
|
-
catch {
|
|
12
|
-
try {
|
|
13
|
-
child.kill();
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
// The process may already be gone.
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
try {
|
|
22
|
-
process.kill(-child.pid, signal);
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
try {
|
|
26
|
-
child.kill(signal);
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
// Same as above.
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { getModelMeta } from "../lib/model-meta.js";
|
|
2
|
-
|
|
3
|
-
export type ToolProtocol = "legacy" | "native";
|
|
4
|
-
export type ToolCapability = "native" | "pseudo";
|
|
5
|
-
|
|
6
|
-
const NATIVE_PROVIDERS = new Set([
|
|
7
|
-
"openai",
|
|
8
|
-
"openrouter",
|
|
9
|
-
"deepseek",
|
|
10
|
-
"groq",
|
|
11
|
-
"mistral",
|
|
12
|
-
"xai",
|
|
13
|
-
"moonshot",
|
|
14
|
-
"zhipu",
|
|
15
|
-
"qwen",
|
|
16
|
-
"ollama",
|
|
17
|
-
]);
|
|
18
|
-
|
|
19
|
-
export function toolCapabilityKey(providerId: string, model: string): string {
|
|
20
|
-
return `${providerId}::${model}`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function resolveToolProtocol(config: any, providerId: string, model: string, env = process.env): ToolProtocol {
|
|
24
|
-
const mode = String(env["FASTCODE_TOOL_PROTOCOL"] ?? "legacy").trim().toLowerCase();
|
|
25
|
-
if (mode === "legacy")
|
|
26
|
-
return "legacy";
|
|
27
|
-
if (mode === "native")
|
|
28
|
-
return "native";
|
|
29
|
-
if (mode !== "auto")
|
|
30
|
-
return "legacy";
|
|
31
|
-
const cached = config?.capabilities?.toolCalling?.[toolCapabilityKey(providerId, model)];
|
|
32
|
-
if (cached === "pseudo")
|
|
33
|
-
return "legacy";
|
|
34
|
-
if (cached === "native")
|
|
35
|
-
return "native";
|
|
36
|
-
if (!NATIVE_PROVIDERS.has(providerId))
|
|
37
|
-
return "legacy";
|
|
38
|
-
return getModelMeta(providerId, model).supportsTools ? "native" : "legacy";
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function withToolCapability(config: any, providerId: string, model: string, capability: ToolCapability): any {
|
|
42
|
-
const key = toolCapabilityKey(providerId, model);
|
|
43
|
-
return {
|
|
44
|
-
...config,
|
|
45
|
-
capabilities: {
|
|
46
|
-
...(config?.capabilities ?? {}),
|
|
47
|
-
toolCalling: {
|
|
48
|
-
...(config?.capabilities?.toolCalling ?? {}),
|
|
49
|
-
[key]: capability,
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function isNativeUnsupportedError(message: string): boolean {
|
|
56
|
-
return /\b400\b/.test(message)
|
|
57
|
-
&& /tool|function/i.test(message)
|
|
58
|
-
&& /not supported|unsupported|unrecognized|unknown|invalid|does not support/i.test(message);
|
|
59
|
-
}
|