@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.
Files changed (246) hide show
  1. package/README.md +6 -24
  2. package/dist/agent/guards.d.ts +0 -8
  3. package/dist/agent/guards.js +0 -195
  4. package/dist/agent/protocol.js +1 -4
  5. package/dist/agent/tools.d.ts +1 -3
  6. package/dist/agent/tools.js +377 -2
  7. package/dist/app.js +103 -335
  8. package/dist/cli.js +311 -362
  9. package/dist/commands/index.js +0 -22
  10. package/dist/commands/login.d.ts +2 -4
  11. package/dist/commands/login.js +10 -12
  12. package/dist/commands/logout.js +16 -8
  13. package/dist/commands/model.d.ts +0 -13
  14. package/dist/commands/model.js +22 -42
  15. package/dist/commands/provider.d.ts +0 -13
  16. package/dist/commands/provider.js +9 -20
  17. package/dist/commands/whoami.js +5 -6
  18. package/dist/config/store.d.ts +12 -32
  19. package/dist/config/store.js +65 -117
  20. package/dist/config/trust.d.ts +8 -21
  21. package/dist/config/trust.js +13 -99
  22. package/dist/lib/diagnostics.d.ts +4 -29
  23. package/dist/lib/diagnostics.js +4 -60
  24. package/dist/lib/http.d.ts +1 -11
  25. package/dist/lib/http.js +4 -62
  26. package/dist/providers/anthropic.d.ts +0 -1
  27. package/dist/providers/anthropic.js +0 -12
  28. package/dist/providers/custom.d.ts +0 -9
  29. package/dist/providers/deepseek.d.ts +0 -10
  30. package/dist/providers/deepseek.js +1 -2
  31. package/dist/providers/google.d.ts +0 -9
  32. package/dist/providers/groq.d.ts +0 -10
  33. package/dist/providers/groq.js +1 -2
  34. package/dist/providers/index.d.ts +201 -10
  35. package/dist/providers/index.js +0 -30
  36. package/dist/providers/mistral.d.ts +0 -10
  37. package/dist/providers/mistral.js +1 -2
  38. package/dist/providers/moonshot.d.ts +0 -10
  39. package/dist/providers/moonshot.js +1 -2
  40. package/dist/providers/ollama.d.ts +0 -9
  41. package/dist/providers/openai.d.ts +0 -10
  42. package/dist/providers/openai.js +1 -2
  43. package/dist/providers/openaiCompatible.d.ts +0 -9
  44. package/dist/providers/openaiCompatible.js +10 -58
  45. package/dist/providers/openrouter.d.ts +0 -10
  46. package/dist/providers/openrouter.js +1 -2
  47. package/dist/providers/qwen.d.ts +0 -10
  48. package/dist/providers/qwen.js +1 -2
  49. package/dist/providers/xai.d.ts +0 -10
  50. package/dist/providers/xai.js +1 -2
  51. package/dist/providers/zhipu.d.ts +0 -10
  52. package/dist/providers/zhipu.js +1 -2
  53. package/dist/session/log.d.ts +12 -35
  54. package/dist/session/log.js +10 -206
  55. package/dist/types.d.ts +1 -60
  56. package/dist/ui/Message.d.ts +7 -1
  57. package/dist/ui/Message.js +2 -13
  58. package/dist/ui/Prompt.d.ts +2 -1
  59. package/dist/ui/Prompt.js +2 -2
  60. package/dist/ui/Spinner.js +5 -11
  61. package/dist/ui/StatusBar.d.ts +1 -2
  62. package/dist/ui/StatusBar.js +5 -6
  63. package/package.json +1 -1
  64. package/src/agent/guards.ts +0 -196
  65. package/src/agent/protocol.ts +1 -4
  66. package/src/agent/tools.ts +377 -3
  67. package/src/app.ts +103 -335
  68. package/src/cli.ts +3 -30
  69. package/src/commands/index.ts +0 -22
  70. package/src/commands/login.ts +10 -12
  71. package/src/commands/logout.ts +16 -8
  72. package/src/commands/model.ts +22 -42
  73. package/src/commands/provider.ts +9 -20
  74. package/src/commands/whoami.ts +5 -6
  75. package/src/config/store.ts +69 -122
  76. package/src/config/trust.ts +21 -137
  77. package/src/lib/diagnostics.ts +4 -60
  78. package/src/lib/http.ts +4 -66
  79. package/src/providers/anthropic.ts +1 -13
  80. package/src/providers/deepseek.ts +1 -2
  81. package/src/providers/groq.ts +1 -2
  82. package/src/providers/index.ts +1 -32
  83. package/src/providers/mistral.ts +1 -2
  84. package/src/providers/moonshot.ts +1 -2
  85. package/src/providers/openai.ts +1 -2
  86. package/src/providers/openaiCompatible.ts +10 -58
  87. package/src/providers/openrouter.ts +1 -2
  88. package/src/providers/qwen.ts +1 -2
  89. package/src/providers/xai.ts +1 -2
  90. package/src/providers/zhipu.ts +1 -2
  91. package/src/session/log.ts +28 -267
  92. package/src/types.ts +1 -63
  93. package/src/ui/Message.ts +2 -13
  94. package/src/ui/Prompt.ts +2 -2
  95. package/src/ui/Spinner.ts +5 -11
  96. package/src/ui/StatusBar.ts +5 -6
  97. package/dist/agent/approval.d.ts +0 -50
  98. package/dist/agent/approval.js +0 -7
  99. package/dist/agent/compaction.d.ts +0 -39
  100. package/dist/agent/compaction.js +0 -227
  101. package/dist/agent/context.d.ts +0 -29
  102. package/dist/agent/context.js +0 -62
  103. package/dist/agent/ignore.d.ts +0 -11
  104. package/dist/agent/ignore.js +0 -129
  105. package/dist/agent/process-tree.d.ts +0 -2
  106. package/dist/agent/process-tree.js +0 -31
  107. package/dist/agent/protocol-mode.d.ts +0 -6
  108. package/dist/agent/protocol-mode.js +0 -51
  109. package/dist/agent/registry.d.ts +0 -17
  110. package/dist/agent/registry.js +0 -90
  111. package/dist/agent/runner.d.ts +0 -106
  112. package/dist/agent/runner.js +0 -352
  113. package/dist/agent/shell-session.d.ts +0 -37
  114. package/dist/agent/shell-session.js +0 -400
  115. package/dist/agent/stream.d.ts +0 -29
  116. package/dist/agent/stream.js +0 -103
  117. package/dist/agent/tool-schemas.d.ts +0 -2
  118. package/dist/agent/tool-schemas.js +0 -63
  119. package/dist/agent/tools/append_file.d.ts +0 -2
  120. package/dist/agent/tools/append_file.js +0 -26
  121. package/dist/agent/tools/apply_patch.d.ts +0 -10
  122. package/dist/agent/tools/apply_patch.js +0 -52
  123. package/dist/agent/tools/copy_file.d.ts +0 -2
  124. package/dist/agent/tools/copy_file.js +0 -30
  125. package/dist/agent/tools/delete_file.d.ts +0 -2
  126. package/dist/agent/tools/delete_file.js +0 -24
  127. package/dist/agent/tools/grep.d.ts +0 -2
  128. package/dist/agent/tools/grep.js +0 -23
  129. package/dist/agent/tools/index.d.ts +0 -2
  130. package/dist/agent/tools/index.js +0 -32
  131. package/dist/agent/tools/list_files.d.ts +0 -2
  132. package/dist/agent/tools/list_files.js +0 -17
  133. package/dist/agent/tools/make_dir.d.ts +0 -2
  134. package/dist/agent/tools/make_dir.js +0 -24
  135. package/dist/agent/tools/move_path.d.ts +0 -2
  136. package/dist/agent/tools/move_path.js +0 -47
  137. package/dist/agent/tools/paths.d.ts +0 -7
  138. package/dist/agent/tools/paths.js +0 -84
  139. package/dist/agent/tools/pwd.d.ts +0 -2
  140. package/dist/agent/tools/pwd.js +0 -10
  141. package/dist/agent/tools/read_file.d.ts +0 -2
  142. package/dist/agent/tools/read_file.js +0 -21
  143. package/dist/agent/tools/read_many.d.ts +0 -2
  144. package/dist/agent/tools/read_many.js +0 -25
  145. package/dist/agent/tools/readers.d.ts +0 -6
  146. package/dist/agent/tools/readers.js +0 -216
  147. package/dist/agent/tools/replace_in_file.d.ts +0 -2
  148. package/dist/agent/tools/replace_in_file.js +0 -30
  149. package/dist/agent/tools/shell.d.ts +0 -2
  150. package/dist/agent/tools/shell.js +0 -48
  151. package/dist/agent/tools/stat_path.d.ts +0 -2
  152. package/dist/agent/tools/stat_path.js +0 -31
  153. package/dist/agent/tools/types.d.ts +0 -39
  154. package/dist/agent/tools/types.js +0 -1
  155. package/dist/agent/tools/validate.d.ts +0 -6
  156. package/dist/agent/tools/validate.js +0 -29
  157. package/dist/agent/tools/write_file.d.ts +0 -2
  158. package/dist/agent/tools/write_file.js +0 -26
  159. package/dist/commands/attach.d.ts +0 -9
  160. package/dist/commands/attach.js +0 -15
  161. package/dist/commands/bg.d.ts +0 -7
  162. package/dist/commands/bg.js +0 -9
  163. package/dist/commands/cancel.d.ts +0 -10
  164. package/dist/commands/cancel.js +0 -10
  165. package/dist/commands/compact.d.ts +0 -10
  166. package/dist/commands/compact.js +0 -22
  167. package/dist/commands/context.d.ts +0 -7
  168. package/dist/commands/context.js +0 -24
  169. package/dist/commands/edit.d.ts +0 -10
  170. package/dist/commands/edit.js +0 -36
  171. package/dist/commands/effort.d.ts +0 -25
  172. package/dist/commands/effort.js +0 -48
  173. package/dist/commands/expand.d.ts +0 -10
  174. package/dist/commands/expand.js +0 -8
  175. package/dist/commands/jobs.d.ts +0 -7
  176. package/dist/commands/jobs.js +0 -16
  177. package/dist/commands/kill.d.ts +0 -7
  178. package/dist/commands/kill.js +0 -11
  179. package/dist/commands/steer.d.ts +0 -11
  180. package/dist/commands/steer.js +0 -13
  181. package/dist/lib/model-meta.d.ts +0 -19
  182. package/dist/lib/model-meta.js +0 -113
  183. package/dist/lib/native-tools.d.ts +0 -12
  184. package/dist/lib/native-tools.js +0 -21
  185. package/dist/lib/net.d.ts +0 -33
  186. package/dist/lib/net.js +0 -168
  187. package/dist/patch/apply.d.ts +0 -15
  188. package/dist/patch/apply.js +0 -172
  189. package/dist/patch/parse.d.ts +0 -29
  190. package/dist/patch/parse.js +0 -112
  191. package/dist/patch/plan.d.ts +0 -49
  192. package/dist/patch/plan.js +0 -273
  193. package/dist/session/projection.d.ts +0 -43
  194. package/dist/session/projection.js +0 -257
  195. package/dist/ui/Picker.d.ts +0 -8
  196. package/dist/ui/Picker.js +0 -24
  197. package/src/agent/approval.ts +0 -47
  198. package/src/agent/compaction.ts +0 -276
  199. package/src/agent/context.ts +0 -97
  200. package/src/agent/ignore.ts +0 -147
  201. package/src/agent/process-tree.ts +0 -32
  202. package/src/agent/protocol-mode.ts +0 -59
  203. package/src/agent/registry.ts +0 -98
  204. package/src/agent/runner.ts +0 -443
  205. package/src/agent/shell-session.ts +0 -449
  206. package/src/agent/stream.ts +0 -143
  207. package/src/agent/tool-schemas.ts +0 -67
  208. package/src/agent/tools/append_file.ts +0 -28
  209. package/src/agent/tools/apply_patch.ts +0 -54
  210. package/src/agent/tools/copy_file.ts +0 -32
  211. package/src/agent/tools/delete_file.ts +0 -26
  212. package/src/agent/tools/grep.ts +0 -25
  213. package/src/agent/tools/index.ts +0 -34
  214. package/src/agent/tools/list_files.ts +0 -19
  215. package/src/agent/tools/make_dir.ts +0 -26
  216. package/src/agent/tools/move_path.ts +0 -50
  217. package/src/agent/tools/paths.ts +0 -92
  218. package/src/agent/tools/pwd.ts +0 -12
  219. package/src/agent/tools/read_file.ts +0 -23
  220. package/src/agent/tools/read_many.ts +0 -27
  221. package/src/agent/tools/readers.ts +0 -228
  222. package/src/agent/tools/replace_in_file.ts +0 -32
  223. package/src/agent/tools/shell.ts +0 -53
  224. package/src/agent/tools/stat_path.ts +0 -33
  225. package/src/agent/tools/types.ts +0 -42
  226. package/src/agent/tools/validate.ts +0 -35
  227. package/src/agent/tools/write_file.ts +0 -28
  228. package/src/commands/attach.ts +0 -16
  229. package/src/commands/bg.ts +0 -9
  230. package/src/commands/cancel.ts +0 -10
  231. package/src/commands/compact.ts +0 -23
  232. package/src/commands/context.ts +0 -25
  233. package/src/commands/edit.ts +0 -37
  234. package/src/commands/effort.ts +0 -49
  235. package/src/commands/expand.ts +0 -8
  236. package/src/commands/jobs.ts +0 -17
  237. package/src/commands/kill.ts +0 -12
  238. package/src/commands/steer.ts +0 -13
  239. package/src/lib/model-meta.ts +0 -138
  240. package/src/lib/native-tools.ts +0 -32
  241. package/src/lib/net.ts +0 -204
  242. package/src/patch/apply.ts +0 -194
  243. package/src/patch/parse.ts +0 -131
  244. package/src/patch/plan.ts +0 -317
  245. package/src/session/projection.ts +0 -308
  246. package/src/ui/Picker.ts +0 -24
@@ -1,51 +0,0 @@
1
- import { getModelMeta } from "../lib/model-meta.js";
2
- const NATIVE_PROVIDERS = new Set([
3
- "openai",
4
- "openrouter",
5
- "deepseek",
6
- "groq",
7
- "mistral",
8
- "xai",
9
- "moonshot",
10
- "zhipu",
11
- "qwen",
12
- "ollama",
13
- ]);
14
- export function toolCapabilityKey(providerId, model) {
15
- return `${providerId}::${model}`;
16
- }
17
- export function resolveToolProtocol(config, providerId, model, env = process.env) {
18
- const mode = String(env["FASTCODE_TOOL_PROTOCOL"] ?? "legacy").trim().toLowerCase();
19
- if (mode === "legacy")
20
- return "legacy";
21
- if (mode === "native")
22
- return "native";
23
- if (mode !== "auto")
24
- return "legacy";
25
- const cached = config?.capabilities?.toolCalling?.[toolCapabilityKey(providerId, model)];
26
- if (cached === "pseudo")
27
- return "legacy";
28
- if (cached === "native")
29
- return "native";
30
- if (!NATIVE_PROVIDERS.has(providerId))
31
- return "legacy";
32
- return getModelMeta(providerId, model).supportsTools ? "native" : "legacy";
33
- }
34
- export function withToolCapability(config, providerId, model, capability) {
35
- const key = toolCapabilityKey(providerId, model);
36
- return {
37
- ...config,
38
- capabilities: {
39
- ...(config?.capabilities ?? {}),
40
- toolCalling: {
41
- ...(config?.capabilities?.toolCalling ?? {}),
42
- [key]: capability,
43
- },
44
- },
45
- };
46
- }
47
- export function isNativeUnsupportedError(message) {
48
- return /\b400\b/.test(message)
49
- && /tool|function/i.test(message)
50
- && /not supported|unsupported|unrecognized|unknown|invalid|does not support/i.test(message);
51
- }
@@ -1,17 +0,0 @@
1
- import type { ApprovalDecision } from "./approval.js";
2
- import type { Tool, ToolCall, ToolCtx, ToolResult } from "./tools/types.js";
3
- export declare function assertToolContract(tools?: Tool[]): void;
4
- export declare function toolCatalogHash(tools?: Tool[]): string;
5
- export declare class ToolRegistry {
6
- private readonly tools;
7
- constructor(tools?: Tool[]);
8
- get(name: string): Tool | undefined;
9
- list(): Tool[];
10
- approve(call: ToolCall, ctx: ToolCtx): ApprovalDecision;
11
- execute(call: ToolCall, ctx: ToolCtx): Promise<ToolResult>;
12
- }
13
- export declare const registry: ToolRegistry;
14
- export declare function runAgentTool(call: ToolCall, cwd?: string, options?: {
15
- signal?: AbortSignal;
16
- turn?: number;
17
- }): Promise<ToolResult>;
@@ -1,90 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { isTrusted } from "../config/trust.js";
3
- import { evaluateShellCommand } from "./guards.js";
4
- import { TOOLS } from "./tools/index.js";
5
- const STATE_SCOPES = new Set(["call", "turn", "session"]);
6
- const REVALIDATE_KEYS = new Set(["trust", "sandboxRoots", "schema", "modelCaps"]);
7
- export function assertToolContract(tools = TOOLS) {
8
- const names = new Set();
9
- for (const tool of tools) {
10
- if (!tool.name || typeof tool.name !== "string")
11
- throw new Error("tool contract: every tool needs a name");
12
- if (names.has(tool.name))
13
- throw new Error(`tool contract: duplicate tool name ${tool.name}`);
14
- names.add(tool.name);
15
- if (!STATE_SCOPES.has(tool.stateScope))
16
- throw new Error(`tool contract: ${tool.name} has an invalid stateScope`);
17
- for (const key of tool.revalidate ?? []) {
18
- if (!REVALIDATE_KEYS.has(key))
19
- throw new Error(`tool contract: ${tool.name} has an invalid revalidate key ${String(key)}`);
20
- }
21
- if (tool.requiresTrust && !(tool.revalidate ?? []).includes("trust")) {
22
- throw new Error(`tool contract: ${tool.name} requiresTrust but does not revalidate "trust"`);
23
- }
24
- }
25
- }
26
- export function toolCatalogHash(tools = TOOLS) {
27
- const catalog = tools
28
- .map((tool) => ({
29
- name: tool.name,
30
- trust: Boolean(tool.requiresTrust),
31
- scope: tool.stateScope,
32
- revalidate: [...(tool.revalidate ?? [])].sort(),
33
- }))
34
- .sort((a, b) => a.name.localeCompare(b.name));
35
- return createHash("sha1").update(JSON.stringify(catalog)).digest("hex").slice(0, 8);
36
- }
37
- export class ToolRegistry {
38
- tools = new Map();
39
- constructor(tools = TOOLS) {
40
- assertToolContract(tools);
41
- for (const tool of tools)
42
- this.tools.set(tool.name, tool);
43
- }
44
- get(name) {
45
- return this.tools.get(name);
46
- }
47
- list() {
48
- return Array.from(this.tools.values());
49
- }
50
- approve(call, ctx) {
51
- const tool = this.get(call.tool);
52
- if (!tool)
53
- return { kind: "deny", reason: `unknown tool: ${String(call.tool)}` };
54
- if (tool.name === "shell") {
55
- const verdict = evaluateShellCommand(call.command);
56
- if (!verdict.allow)
57
- return { kind: "deny", reason: verdict.output };
58
- }
59
- if ((tool.revalidate ?? []).includes("trust") && !isTrusted(ctx.cwd)) {
60
- return {
61
- kind: "deny",
62
- reason: `project is not trusted: ${ctx.cwd}. Run \`fastcode trust ${ctx.cwd}\` or accept the trust prompt.`,
63
- };
64
- }
65
- return { kind: "allow", scope: "once" };
66
- }
67
- async execute(call, ctx) {
68
- const tool = this.get(call.tool);
69
- if (!tool)
70
- return { ok: false, output: `unknown tool: ${String(call.tool)}` };
71
- const prepared = tool.prepare(call, ctx);
72
- if (prepared.ok === false)
73
- return { ok: false, output: prepared.output };
74
- const decision = this.approve(call, ctx);
75
- if (decision.kind === "deny")
76
- return { ok: false, output: decision.reason ?? "denied by approval policy" };
77
- if (decision.kind === "pending")
78
- return { ok: false, output: "approval required, but no approval resolver is registered (fail closed)" };
79
- try {
80
- return await tool.execute(call, ctx);
81
- }
82
- catch (err) {
83
- return { ok: false, output: err?.message ?? String(err) };
84
- }
85
- }
86
- }
87
- export const registry = new ToolRegistry();
88
- export async function runAgentTool(call, cwd = process.cwd(), options = {}) {
89
- return registry.execute(call, { cwd, signal: options.signal, turn: options.turn });
90
- }
@@ -1,106 +0,0 @@
1
- import type { ChatChunk, ChatMessage, NativeToolCall, Provider, ReasoningEffort, ToolSchema } from "../types.js";
2
- import type { ActiveFile } from "../session/log.js";
3
- import type { SideEffects } from "./tools/types.js";
4
- import { type CompactionConfig } from "./compaction.js";
5
- import { type ToolProtocol } from "./protocol-mode.js";
6
- export interface ToolCall {
7
- tool: string;
8
- [key: string]: any;
9
- }
10
- export interface LoopDetectionOptions {
11
- readOnlyThreshold?: number;
12
- sideEffectThreshold?: number;
13
- window?: number;
14
- exemptReadOnly?: boolean;
15
- }
16
- export interface RunOptions {
17
- provider: Provider;
18
- apiKey?: string;
19
- model: string;
20
- messages?: ChatMessage[];
21
- prompt: string;
22
- system?: string;
23
- maxTurns?: number;
24
- allowTools?: boolean;
25
- tokenBudget?: number;
26
- costBudgetUsd?: number;
27
- signal?: AbortSignal;
28
- cwd?: string;
29
- idleTimeoutMs?: number;
30
- contextBudgetChars?: number;
31
- compaction?: CompactionConfig;
32
- contextWindow?: number;
33
- serverOpaqueState?: string;
34
- loopDetection?: LoopDetectionOptions;
35
- reasoningEffort?: ReasoningEffort;
36
- protocol?: ToolProtocol;
37
- tools?: ToolSchema[];
38
- onProtocolFallback?: (message: string) => void;
39
- onNativeOk?: () => void;
40
- takeSteering?: () => string[];
41
- onDelta?: (delta: string, text: string) => void;
42
- }
43
- export type AgentStopReason = "complete" | "max_turns" | "token_budget" | "cost_budget" | "loop_detected" | "aborted" | "incomplete" | "stream_error";
44
- export type AgentErrorKind = "token_budget" | "cost_budget" | "loop_detected" | "aborted" | "stream_error";
45
- export type AgentEvent = {
46
- type: "compaction";
47
- beforeChars: number;
48
- afterChars: number;
49
- beforeTokens: number;
50
- afterTokens: number;
51
- droppedMessages: number;
52
- keptMessages: number;
53
- reason: "threshold" | "heap";
54
- source: "local" | "server";
55
- summaryFile?: string;
56
- } | {
57
- type: "assistant_raw";
58
- text: string;
59
- toolCalls?: NativeToolCall[];
60
- } | {
61
- type: "assistant_text";
62
- text: string;
63
- } | {
64
- type: "tool_call";
65
- id: string;
66
- call: ToolCall;
67
- native?: boolean;
68
- } | {
69
- type: "tool_result";
70
- id: string;
71
- call: ToolCall;
72
- ok: boolean;
73
- output: string;
74
- sideEffects?: SideEffects;
75
- } | {
76
- type: "usage";
77
- usage: NonNullable<ChatChunk["usage"]>;
78
- } | {
79
- type: "loop_detected";
80
- fingerprint: string;
81
- occurrences: number;
82
- warned: boolean;
83
- } | {
84
- type: "notice";
85
- text: string;
86
- } | {
87
- type: "error";
88
- kind: AgentErrorKind;
89
- message: string;
90
- text?: string;
91
- used?: number;
92
- } | {
93
- type: "checkpoint";
94
- turn: number;
95
- cwd: string;
96
- providerId: string;
97
- model: string;
98
- activeFiles: ActiveFile[];
99
- } | {
100
- type: "done";
101
- history: ChatMessage[];
102
- finalText: string;
103
- stopReason: AgentStopReason;
104
- };
105
- export declare const LOOP_WARNING: string;
106
- export declare function runAgent(options: RunOptions): AsyncGenerator<AgentEvent, void, void>;
@@ -1,352 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { readFileSync, statSync } from "node:fs";
3
- import { estMessagesTokens, estTokens, getModelMeta } from "../lib/model-meta.js";
4
- import { compactMessages, DEFAULT_COMPACTION_CONFIG } from "./compaction.js";
5
- import { isLoopExempt, isReadOnlyToolCall, toolCallFingerprint } from "./guards.js";
6
- import { isNativeUnsupportedError } from "./protocol-mode.js";
7
- import { formatToolResults, parseToolCalls, stripToolCalls } from "./protocol.js";
8
- import { runStream } from "./stream.js";
9
- import { runAgentTool } from "./tools.js";
10
- export const LOOP_WARNING = "[loop detection] You appear to be repeating the same tool call. " +
11
- "Do not repeat it again: inspect the result you already have, or change approach.";
12
- /**
13
- * Sliding-window loop detector.
14
- *
15
- * Semantics (fixed by FC-004, do not re-interpret):
16
- * - hits count the last `window` tool calls, not turns;
17
- * - read-only tools use a higher threshold than side-effecting tools (D-02);
18
- * - the first threshold hit yields a warning and resets that fingerprint's count;
19
- * - only a second full accumulation yields a terminal error;
20
- * - the table lives for one run (turn), so a new user task starts clean;
21
- * - fingerprints are normalized per tool call (absolute paths, timeoutMs removed).
22
- */
23
- class LoopState {
24
- entries = new Map();
25
- calls = 0;
26
- readOnlyThreshold;
27
- sideEffectThreshold;
28
- window;
29
- exemptReadOnly;
30
- constructor(options = {}) {
31
- this.readOnlyThreshold = positive(options.readOnlyThreshold, 6);
32
- this.sideEffectThreshold = positive(options.sideEffectThreshold, 3);
33
- this.window = positive(options.window, 5);
34
- this.exemptReadOnly = options.exemptReadOnly === true;
35
- }
36
- check(fingerprint, readOnly) {
37
- if (readOnly && this.exemptReadOnly)
38
- return { action: "ok", occurrences: 0 };
39
- this.calls += 1;
40
- const index = this.calls;
41
- let entry = this.entries.get(fingerprint);
42
- if (!entry) {
43
- entry = { warned: false, hits: [] };
44
- this.entries.set(fingerprint, entry);
45
- }
46
- entry.hits = entry.hits.filter((hit) => index - hit < this.window);
47
- entry.hits.push(index);
48
- const threshold = readOnly ? this.readOnlyThreshold : this.sideEffectThreshold;
49
- if (entry.hits.length < threshold)
50
- return { action: "ok", occurrences: entry.hits.length };
51
- const occurrences = entry.hits.length;
52
- if (!entry.warned) {
53
- entry.warned = true;
54
- entry.hits = [];
55
- return { action: "warn", occurrences };
56
- }
57
- return { action: "stop", occurrences };
58
- }
59
- }
60
- export async function* runAgent(options) {
61
- const provider = options.provider;
62
- const model = options.model;
63
- const cwd = options.cwd ?? process.cwd();
64
- const signal = options.signal;
65
- const maxTurns = Math.max(1, Math.floor(positive(options.maxTurns, 8)));
66
- const tokenBudget = optionalPositive(options.tokenBudget);
67
- const costBudget = optionalPositive(options.costBudgetUsd);
68
- const loop = new LoopState(options.loopDetection);
69
- let history = [...(options.messages ?? []), { role: "user", content: options.prompt }];
70
- let currentText = "";
71
- let lastVisible = "";
72
- let consumedInput = 0;
73
- let spentCost = 0;
74
- let stopReason = "complete";
75
- let errorMessage = "";
76
- let naturalFinish = false;
77
- let toolCallSeq = 0;
78
- let protocol = options.protocol ?? "legacy";
79
- let fellBack = false;
80
- const activeFiles = new Map();
81
- for (let turn = 0; turn < maxTurns; turn += 1) {
82
- if (signal?.aborted) {
83
- stopReason = "aborted";
84
- break;
85
- }
86
- for (const steering of options.takeSteering?.() ?? []) {
87
- history = [...history, { role: "system", content: "[user steering] " + steering }];
88
- yield { type: "notice", text: "Steering applied to the next request." };
89
- }
90
- const contextWindow = options.contextWindow ?? getModelMeta(provider.id, model).contextWindow;
91
- const compacted = compactMessages(history, options.compaction ?? DEFAULT_COMPACTION_CONFIG, {
92
- contextWindow,
93
- providerId: provider.id,
94
- model,
95
- serverOpaqueState: options.serverOpaqueState,
96
- });
97
- if (compacted.compacted) {
98
- history = compacted.messages;
99
- yield {
100
- type: "compaction",
101
- beforeChars: compacted.beforeChars,
102
- afterChars: compacted.afterChars,
103
- beforeTokens: compacted.beforeTokens,
104
- afterTokens: compacted.afterTokens,
105
- droppedMessages: compacted.droppedMessages,
106
- keptMessages: compacted.keptMessages,
107
- reason: compacted.reason ?? "threshold",
108
- source: compacted.source === "server" ? "server" : "local",
109
- summaryFile: compacted.summaryFile,
110
- };
111
- }
112
- const promptTokens = estMessagesTokens(history, { providerId: provider.id, model }) +
113
- estTokens(options.system ?? "", { providerId: provider.id, model });
114
- if (tokenBudget !== undefined && consumedInput + promptTokens > tokenBudget) {
115
- stopReason = "token_budget";
116
- errorMessage = `Token budget exhausted (${consumedInput + promptTokens} > ${tokenBudget} estimated input tokens). Partial output is kept.`;
117
- yield { type: "error", kind: "token_budget", message: errorMessage, text: lastVisible, used: consumedInput + promptTokens };
118
- break;
119
- }
120
- if (costBudget !== undefined && spentCost >= costBudget) {
121
- stopReason = "cost_budget";
122
- errorMessage = `Cost budget exhausted ($${spentCost.toFixed(4)} of $${costBudget}). Partial output is kept.`;
123
- yield { type: "error", kind: "cost_budget", message: errorMessage, text: lastVisible };
124
- break;
125
- }
126
- currentText = "";
127
- let resetNotice = "";
128
- let streamed;
129
- try {
130
- streamed = await runStream(provider, {
131
- model,
132
- messages: history,
133
- system: options.system,
134
- reasoningEffort: options.reasoningEffort,
135
- ...(protocol === "native" && options.tools && options.tools.length > 0 ? { tools: options.tools } : {}),
136
- }, options.apiKey, {
137
- signal,
138
- idleTimeoutMs: options.idleTimeoutMs,
139
- onDelta: (delta, text) => {
140
- currentText = text;
141
- options.onDelta?.(delta, text);
142
- },
143
- onReset: () => {
144
- resetNotice = "Retried stream returned different content; the earlier partial output was discarded and replaced by the new stream.";
145
- },
146
- });
147
- }
148
- catch (err) {
149
- if (err?.name === "AbortError" || signal?.aborted) {
150
- stopReason = "aborted";
151
- break;
152
- }
153
- errorMessage = String(err?.message ?? err);
154
- if (protocol === "native" && !fellBack && isNativeUnsupportedError(errorMessage)) {
155
- fellBack = true;
156
- protocol = "legacy";
157
- options.onProtocolFallback?.(`Native tools were rejected (${errorMessage.slice(0, 160)}); retrying with the pseudo protocol.`);
158
- turn -= 1;
159
- continue;
160
- }
161
- stopReason = "stream_error";
162
- const partial = stripToolCalls(String(err?.partialText ?? "")).trim();
163
- if (partial)
164
- lastVisible = partial;
165
- yield { type: "error", kind: "stream_error", message: errorMessage, text: partial || undefined };
166
- break;
167
- }
168
- if (signal?.aborted) {
169
- stopReason = "aborted";
170
- break;
171
- }
172
- if (resetNotice)
173
- yield { type: "notice", text: resetNotice };
174
- if (streamed.usage) {
175
- consumedInput += streamed.usage.inputTokens ?? promptTokens;
176
- spentCost += streamed.usage.costUsd ?? 0;
177
- yield { type: "usage", usage: streamed.usage };
178
- }
179
- else {
180
- consumedInput += promptTokens;
181
- }
182
- if (streamed.incomplete) {
183
- const partial = stripToolCalls(streamed.text).trim();
184
- if (partial)
185
- lastVisible = partial;
186
- stopReason = "incomplete";
187
- errorMessage = `Connection ended before the response finished${streamed.retried > 0 ? ` (retried ${streamed.retried}×)` : ""}. ` +
188
- "Partial output is kept — type 继续 to continue.";
189
- yield { type: "notice", text: errorMessage };
190
- yield { type: "checkpoint", turn: turn + 1, cwd, providerId: provider.id, model, activeFiles: Array.from(activeFiles.values()) };
191
- break;
192
- }
193
- const visibleText = stripToolCalls(streamed.text);
194
- if (visibleText)
195
- lastVisible = visibleText;
196
- const nativeCalls = protocol === "native" && options.allowTools !== false ? (streamed.toolCalls ?? []) : [];
197
- if (nativeCalls.length > 0) {
198
- yield { type: "assistant_raw", text: streamed.text, toolCalls: nativeCalls };
199
- if (visibleText)
200
- yield { type: "assistant_text", text: visibleText };
201
- history = [...history, { role: "assistant", content: streamed.text, toolCalls: nativeCalls }];
202
- for (const native of nativeCalls) {
203
- if (signal?.aborted) {
204
- stopReason = "aborted";
205
- break;
206
- }
207
- const id = native.id || `tc_${++toolCallSeq}`;
208
- let call;
209
- let parseError = "";
210
- try {
211
- const args = native.arguments ? JSON.parse(native.arguments) : {};
212
- if (!args || typeof args !== "object" || Array.isArray(args))
213
- throw new Error("arguments must be a JSON object");
214
- call = { ...args, tool: native.name };
215
- }
216
- catch (err) {
217
- parseError = `invalid arguments for ${native.name}: ${err?.message ?? String(err)}`;
218
- call = { tool: native.name };
219
- }
220
- const fingerprint = toolCallFingerprint(call, cwd);
221
- if (!isLoopExempt(call.tool)) {
222
- const verdict = loop.check(fingerprint, isReadOnlyToolCall(call));
223
- if (verdict.action === "warn") {
224
- yield { type: "loop_detected", fingerprint, occurrences: verdict.occurrences, warned: false };
225
- history = [...history, { role: "system", content: LOOP_WARNING }];
226
- }
227
- else if (verdict.action === "stop") {
228
- stopReason = "loop_detected";
229
- errorMessage = `Loop detected: the same tool call repeated ${verdict.occurrences} times. Stopped before executing it again.`;
230
- yield { type: "error", kind: "loop_detected", message: errorMessage, text: lastVisible };
231
- break;
232
- }
233
- }
234
- yield { type: "tool_call", id, call, native: true };
235
- const result = parseError
236
- ? { ok: false, output: parseError }
237
- : await runAgentTool(call, cwd, { signal, turn: turn + 1 });
238
- trackSideEffects(activeFiles, result.sideEffects);
239
- yield { type: "tool_result", id, call, ok: Boolean(result.ok), output: String(result.output ?? ""), sideEffects: result.sideEffects };
240
- history = [...history, { role: "tool", toolCallId: id, content: String(result.output ?? "") }];
241
- }
242
- yield { type: "checkpoint", turn: turn + 1, cwd, providerId: provider.id, model, activeFiles: Array.from(activeFiles.values()) };
243
- if (stopReason === "complete")
244
- options.onNativeOk?.();
245
- if (stopReason !== "complete")
246
- break;
247
- continue;
248
- }
249
- const calls = options.allowTools === false ? [] : parseToolCalls(streamed.text);
250
- yield { type: "assistant_raw", text: streamed.text };
251
- if (calls.length === 0) {
252
- lastVisible = visibleText || streamed.text;
253
- naturalFinish = true;
254
- stopReason = "complete";
255
- yield { type: "checkpoint", turn: turn + 1, cwd, providerId: provider.id, model, activeFiles: Array.from(activeFiles.values()) };
256
- break;
257
- }
258
- history = [...history, { role: "assistant", content: streamed.text }];
259
- if (visibleText)
260
- yield { type: "assistant_text", text: visibleText };
261
- const results = [];
262
- for (const call of calls) {
263
- if (signal?.aborted) {
264
- stopReason = "aborted";
265
- break;
266
- }
267
- const fingerprint = toolCallFingerprint(call, cwd);
268
- if (!isLoopExempt(call.tool)) {
269
- const verdict = loop.check(fingerprint, isReadOnlyToolCall(call));
270
- if (verdict.action === "warn") {
271
- yield { type: "loop_detected", fingerprint, occurrences: verdict.occurrences, warned: false };
272
- history = [...history, { role: "system", content: LOOP_WARNING }];
273
- }
274
- else if (verdict.action === "stop") {
275
- stopReason = "loop_detected";
276
- errorMessage = `Loop detected: the same tool call repeated ${verdict.occurrences} times. Stopped before executing it again.`;
277
- yield { type: "error", kind: "loop_detected", message: errorMessage, text: lastVisible };
278
- break;
279
- }
280
- }
281
- const id = `tc_${++toolCallSeq}`;
282
- yield { type: "tool_call", id, call };
283
- const result = await runAgentTool(call, cwd, { signal, turn: turn + 1 });
284
- results.push({ call, ok: Boolean(result.ok), output: String(result.output ?? ""), sideEffects: result.sideEffects });
285
- trackSideEffects(activeFiles, result.sideEffects);
286
- yield { type: "tool_result", id, call, ok: Boolean(result.ok), output: String(result.output ?? ""), sideEffects: result.sideEffects };
287
- }
288
- if (results.length > 0)
289
- history = [...history, { role: "user", content: formatToolResults(results) }];
290
- yield { type: "checkpoint", turn: turn + 1, cwd, providerId: provider.id, model, activeFiles: Array.from(activeFiles.values()) };
291
- if (stopReason !== "complete")
292
- break;
293
- }
294
- if (stopReason === "stream_error") {
295
- const partial = lastVisible;
296
- if (partial)
297
- history = [...history, { role: "assistant", content: partial }];
298
- yield { type: "done", history, finalText: partial, stopReason };
299
- return;
300
- }
301
- if (stopReason === "complete" && !naturalFinish)
302
- stopReason = "max_turns";
303
- let finalText = "";
304
- if (stopReason === "aborted") {
305
- const partial = stripToolCalls(currentText).trim() || lastVisible;
306
- finalText = (partial ? partial + "\n\n" : "") + "[用户中断]";
307
- yield { type: "error", kind: "aborted", message: "Cancelled.", text: partial };
308
- }
309
- else if (stopReason === "token_budget" || stopReason === "cost_budget" || stopReason === "loop_detected" || stopReason === "incomplete") {
310
- finalText = lastVisible || errorMessage;
311
- }
312
- else if (stopReason === "max_turns") {
313
- finalText = `Reached the maximum of ${maxTurns} agent tool turns. The work so far is kept in this session — type 继续 to keep going.`;
314
- }
315
- else {
316
- finalText = lastVisible;
317
- }
318
- if (finalText)
319
- history = [...history, { role: "assistant", content: finalText }];
320
- yield { type: "done", history, finalText, stopReason };
321
- }
322
- function positive(value, fallback) {
323
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
324
- }
325
- function optionalPositive(value) {
326
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
327
- }
328
- function trackSideEffects(activeFiles, sideEffects) {
329
- if (!sideEffects)
330
- return;
331
- for (const file of [...(sideEffects.touched ?? []), ...(sideEffects.created ?? [])]) {
332
- const meta = describeActiveFile(file);
333
- if (meta)
334
- activeFiles.set(file, meta);
335
- }
336
- for (const file of sideEffects.removed ?? [])
337
- activeFiles.delete(file);
338
- }
339
- function describeActiveFile(file) {
340
- try {
341
- const info = statSync(file);
342
- if (!info.isFile())
343
- return undefined;
344
- const sha1_8 = info.size > 8 * 1024 * 1024
345
- ? "large"
346
- : createHash("sha1").update(readFileSync(file)).digest("hex").slice(0, 8);
347
- return { path: file, sha1_8, mtime: info.mtime.toISOString() };
348
- }
349
- catch {
350
- return undefined;
351
- }
352
- }
@@ -1,37 +0,0 @@
1
- export type ShellJobStatus = "running" | "done" | "failed" | "killed" | "timeout";
2
- export interface ShellExecResult {
3
- ok: boolean;
4
- status: ShellJobStatus;
5
- output: string;
6
- exitCode?: number | null;
7
- sessionId?: string;
8
- nextPollMs?: number;
9
- }
10
- export interface ShellJobView {
11
- id: string;
12
- command: string;
13
- cwd: string;
14
- status: ShellJobStatus;
15
- exitCode?: number | null;
16
- bytes: number;
17
- startedAt: number;
18
- pid?: number;
19
- }
20
- interface ExecOptions {
21
- signal?: AbortSignal;
22
- turn?: number;
23
- timeoutMs?: number;
24
- backgroundAfterMs?: number;
25
- }
26
- export declare function executeShellCommand(command: string, cwd: string, options?: ExecOptions): Promise<ShellExecResult>;
27
- export declare function pollShellSession(id: string, waitMs?: number): Promise<ShellExecResult | undefined>;
28
- export declare function killShellSession(id: string): boolean;
29
- export declare function listShellSessions(): ShellJobView[];
30
- export declare function shutdownShells(): void;
31
- export declare function shellPoolStats(): {
32
- spawns: number;
33
- active: number;
34
- pool: number;
35
- maxSpawnPerTurn: number;
36
- };
37
- export {};