@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,34 +1,12 @@
1
1
  import { fetchJson, streamSSE } from "../lib/http.js";
2
- import { finalizeToolCalls, mergeToolCallDelta, type ToolCallAccumulator } from "../lib/native-tools.js";
3
2
  /** Build a chat-completions body in the classic or the newer (compat) dialect. */
4
- function buildBody(req, compat, opts, includeReasoning = true) {
3
+ function buildBody(req, compat) {
5
4
  const messages = [];
6
5
  if (req.system)
7
6
  messages.push({ role: "system", content: req.system });
8
- for (const m of req.messages) {
9
- if (m.role === "tool") {
10
- messages.push({ role: "tool", tool_call_id: m.toolCallId, content: m.content });
11
- continue;
12
- }
13
- if (m.role === "assistant" && m.toolCalls && m.toolCalls.length > 0) {
14
- messages.push({
15
- role: "assistant",
16
- content: m.content || null,
17
- tool_calls: m.toolCalls.map((call) => ({
18
- id: call.id,
19
- type: "function",
20
- function: { name: call.name, arguments: call.arguments },
21
- })),
22
- });
23
- continue;
24
- }
7
+ for (const m of req.messages)
25
8
  messages.push({ role: m.role, content: m.content });
26
- }
27
9
  const body: any = { model: req.model, messages, stream: true };
28
- if (req.tools && req.tools.length > 0) {
29
- body.tools = req.tools;
30
- body.tool_choice = "auto";
31
- }
32
10
  if (req.maxOutputTokens !== undefined) {
33
11
  if (compat)
34
12
  body.max_completion_tokens = req.maxOutputTokens;
@@ -37,18 +15,10 @@ function buildBody(req, compat, opts, includeReasoning = true) {
37
15
  }
38
16
  if (req.temperature !== undefined && !compat)
39
17
  body.temperature = req.temperature;
40
- if (includeReasoning && req.reasoningEffort && req.reasoningEffort !== "auto") {
41
- const effort = req.reasoningEffort === "max" ? "high" : req.reasoningEffort;
42
- if (opts.reasoning === "openai")
43
- body.reasoning_effort = effort;
44
- else if (opts.reasoning === "openrouter")
45
- body.reasoning = { effort };
46
- }
47
18
  return body;
48
19
  }
49
20
  export async function* streamChatCompletion(req, opts) {
50
- const hasReasoning = Boolean(req.reasoningEffort && req.reasoningEffort !== "auto" && opts.reasoning && opts.reasoning !== "none");
51
- const send = async (compat, includeReasoning = hasReasoning) => {
21
+ const send = async (compat) => {
52
22
  const headers = {
53
23
  "content-type": "application/json",
54
24
  accept: "text/event-stream",
@@ -56,36 +26,27 @@ export async function* streamChatCompletion(req, opts) {
56
26
  };
57
27
  if (opts.apiKey)
58
28
  headers["authorization"] = (opts.authHeader ?? "Bearer") + " " + opts.apiKey;
59
- if (req.clientRequestId)
60
- headers["idempotency-key"] = req.clientRequestId;
61
29
  return await fetch(trim(opts.baseUrl) + "/chat/completions", {
62
30
  method: "POST",
63
31
  headers,
64
- body: JSON.stringify(buildBody(req, compat, opts, includeReasoning)),
32
+ body: JSON.stringify(buildBody(req, compat)),
65
33
  signal: req.signal,
66
34
  });
67
35
  };
68
36
  let res = await send(false);
69
37
  if (!res.ok) {
70
- let detail = await res.text().catch(() => "");
71
- // Some models reject the thinking parameter even though the provider
72
- // as a whole accepts it. Drop it once, then keep the request alive.
73
- if (res.status === 400 && hasReasoning && /reasoning|thinking|effort|unsupported|unrecognized|unknown parameter/i.test(detail)) {
74
- res = await send(false, false);
75
- detail = res.ok ? detail : await res.text().catch(() => "");
76
- }
38
+ const detail = await res.text().catch(() => "");
77
39
  // Newer OpenAI-style models reject max_tokens and temperature. Retry once
78
40
  // in the compatibility dialect before surfacing the real error.
79
- if (!res.ok && res.status === 400 && /max_tokens|max_completion_tokens|temperature|unsupported|unrecognized|unknown parameter/i.test(detail)) {
80
- res = await send(true, false);
81
- detail = res.ok ? detail : await res.text().catch(() => "");
41
+ if (res.status === 400 && /max_tokens|max_completion_tokens|temperature|unsupported|unrecognized|unknown parameter/i.test(detail)) {
42
+ res = await send(true);
82
43
  }
83
44
  if (!res.ok) {
84
- throw new Error(res.status + " " + res.statusText + ": " + detail.slice(0, 500));
45
+ const body = await res.text().catch(() => "");
46
+ throw new Error(res.status + " " + res.statusText + ": " + body.slice(0, 500));
85
47
  }
86
48
  }
87
49
  let usage;
88
- const toolCalls: ToolCallAccumulator = new Map();
89
50
  for await (const ev of streamSSE(res)) {
90
51
  if (ev.data === "[DONE]")
91
52
  break;
@@ -100,14 +61,6 @@ export async function* streamChatCompletion(req, opts) {
100
61
  const choice = obj.choices?.[0];
101
62
  const raw = choice?.delta?.content ?? choice?.text;
102
63
  const reasoning = choice?.delta?.reasoning_content ?? choice?.delta?.reasoning;
103
- if (Array.isArray(choice?.delta?.tool_calls)) {
104
- for (const delta of choice.delta.tool_calls)
105
- mergeToolCallDelta(toolCalls, delta);
106
- }
107
- if (Array.isArray(choice?.message?.tool_calls)) {
108
- for (const delta of choice.message.tool_calls)
109
- mergeToolCallDelta(toolCalls, delta);
110
- }
111
64
  if (obj.usage) {
112
65
  usage = {
113
66
  inputTokens: obj.usage.prompt_tokens,
@@ -125,8 +78,7 @@ export async function* streamChatCompletion(req, opts) {
125
78
  else if (typeof reasoning === "string" && reasoning)
126
79
  yield { delta: "", done: false, reasoning };
127
80
  }
128
- const calls = finalizeToolCalls(toolCalls);
129
- yield calls.length > 0 ? { delta: "", done: true, usage, toolCalls: calls } : { delta: "", done: true, usage };
81
+ yield { delta: "", done: true, usage };
130
82
  }
131
83
  export async function listModelsHttp(opts) {
132
84
  const staticModels = opts.staticModels ?? [];
@@ -22,6 +22,5 @@ export const openrouterProvider = {
22
22
  baseUrl: BASE,
23
23
  defaultModel: "anthropic/claude-sonnet-5.5",
24
24
  listModels: (apiKey) => listModelsHttp({ baseUrl: BASE, apiKey, staticModels: STATIC, extraHeaders: EXTRA_HEADERS }),
25
- stream: (req, apiKey) => streamChatCompletion(req, { baseUrl: BASE, apiKey, staticModels: STATIC, extraHeaders: EXTRA_HEADERS, reasoning: "openrouter" }),
26
- supportsReasoning: () => true,
25
+ stream: (req, apiKey) => streamChatCompletion(req, { baseUrl: BASE, apiKey, staticModels: STATIC, extraHeaders: EXTRA_HEADERS }),
27
26
  };
@@ -19,6 +19,5 @@ export const qwenProvider = {
19
19
  },
20
20
  defaultModel: "qwen3.8-max",
21
21
  listModels: (apiKey) => listModelsHttp({ baseUrl: baseUrl(), apiKey, staticModels: STATIC }),
22
- stream: (req, apiKey) => streamChatCompletion(req, { baseUrl: baseUrl(), apiKey, staticModels: STATIC, reasoning: "openai" }),
23
- supportsReasoning: () => true,
22
+ stream: (req, apiKey) => streamChatCompletion(req, { baseUrl: baseUrl(), apiKey, staticModels: STATIC }),
24
23
  };
@@ -14,6 +14,5 @@ export const xaiProvider = {
14
14
  baseUrl: BASE,
15
15
  defaultModel: "grok-4.7",
16
16
  listModels: (apiKey) => listModelsHttp({ baseUrl: BASE, apiKey, staticModels: STATIC }),
17
- stream: (req, apiKey) => streamChatCompletion(req, { baseUrl: BASE, apiKey, staticModels: STATIC, reasoning: "openai" }),
18
- supportsReasoning: () => true,
17
+ stream: (req, apiKey) => streamChatCompletion(req, { baseUrl: BASE, apiKey, staticModels: STATIC }),
19
18
  };
@@ -19,6 +19,5 @@ export const zhipuProvider = {
19
19
  },
20
20
  defaultModel: "glm-5.3",
21
21
  listModels: (apiKey) => listModelsHttp({ baseUrl: baseUrl(), apiKey, staticModels: STATIC }),
22
- stream: (req, apiKey) => streamChatCompletion(req, { baseUrl: baseUrl(), apiKey, staticModels: STATIC, reasoning: "openai" }),
23
- supportsReasoning: () => true,
22
+ stream: (req, apiKey) => streamChatCompletion(req, { baseUrl: baseUrl(), apiKey, staticModels: STATIC }),
24
23
  };
@@ -8,118 +8,31 @@
8
8
  *
9
9
  * FASTCODE_SESSION_LOG=0 disable logging
10
10
  * FASTCODE_SESSION_CACHE_MB=N sessions cache cap in MB (default 256)
11
- * FASTCODE_FSYNC=0|1 force checkpoint fsync off/on
12
11
  */
13
- import { appendFileSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
14
- import { basename, join } from "node:path";
12
+ import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync } from "node:fs";
13
+ import { join } from "node:path";
15
14
  import { getPaths } from "../config/paths.js";
16
-
17
15
  const NL = String.fromCharCode(10);
18
-
19
- export interface ActiveFile {
20
- path: string;
21
- sha1_8: string;
22
- mtime: string;
23
- }
24
-
25
- export interface SessionSummary {
26
- id: string;
27
- file: string;
28
- cwd: string;
29
- providerId: string;
30
- model: string;
31
- turns: number;
32
- activeFiles: ActiveFile[];
33
- messages: number;
34
- lastTs: number;
35
- }
36
-
37
- export function sessionCacheLimitBytes(): number {
16
+ export function sessionCacheLimitBytes() {
38
17
  const mb = Number(process.env["FASTCODE_SESSION_CACHE_MB"]);
39
18
  return (Number.isFinite(mb) && mb > 0 ? mb : 256) * 1024 * 1024;
40
19
  }
41
-
42
- export function loggingEnabled(): boolean {
20
+ export function loggingEnabled() {
43
21
  return process.env["FASTCODE_SESSION_LOG"] !== "0";
44
22
  }
45
-
46
- export function projectionPath(file: string): string {
47
- return file.replace(/[.]jsonl$/, ".projection.json");
48
- }
49
-
50
- export function fsyncMode(): "on" | "off" | "auto" {
51
- const raw = (process.env["FASTCODE_FSYNC"] ?? "").trim().toLowerCase();
52
- if (["1", "true", "yes", "on"].includes(raw))
53
- return "on";
54
- if (["0", "false", "no", "off"].includes(raw))
55
- return "off";
56
- return "auto";
57
- }
58
-
59
- function onSharedStorage(root: string): boolean {
60
- const normalized = root.replace(/\\/g, "/");
61
- return normalized === "/sdcard"
62
- || normalized.startsWith("/sdcard/")
63
- || normalized === "/storage/emulated"
64
- || normalized.startsWith("/storage/emulated/");
65
- }
66
-
67
- export function fsyncEnabled(type = "checkpoint"): boolean {
68
- if (type !== "checkpoint")
69
- return false;
70
- const mode = fsyncMode();
71
- if (mode === "on")
72
- return true;
73
- if (mode === "off")
74
- return false;
75
- return onSharedStorage(getPaths().root);
76
- }
77
-
78
- function writeLine(file: string, line: string, type: string): void {
79
- if (!fsyncEnabled(type)) {
80
- appendFileSync(file, line, "utf8");
81
- return;
82
- }
83
- const fd = openSync(file, "a");
84
- try {
85
- writeSync(fd, line);
86
- fsyncSync(fd);
87
- }
88
- finally {
89
- closeSync(fd);
90
- }
91
- }
92
-
93
- function countLines(file: string): number {
94
- if (!existsSync(file))
95
- return 0;
96
- try {
97
- return readFileSync(file, "utf8").split(NL).filter(Boolean).length;
98
- }
99
- catch {
100
- return 0;
101
- }
102
- }
103
-
104
- function newId(): string {
23
+ function newId() {
105
24
  return new Date().toISOString().replace(/[:.]/g, "-") + "-" + Math.random().toString(36).slice(2, 6);
106
25
  }
107
-
108
- function openLog(id: string | undefined, file: string, meta: Record<string, any>) {
26
+ function openLog(id, file, meta) {
109
27
  let closed = false;
110
- let seq = countLines(file);
111
- const append = (entry: Record<string, any>) => {
28
+ const append = (entry) => {
112
29
  if (closed || !loggingEnabled())
113
- return 0;
114
- const next = seq + 1;
115
- const line = JSON.stringify({ ts: Date.now(), seq: next, ...entry }) + NL;
30
+ return;
116
31
  try {
117
- writeLine(file, line, String(entry.type ?? ""));
118
- seq = next;
119
- return next;
32
+ appendFileSync(file, JSON.stringify({ ts: Date.now(), ...entry }) + NL, "utf8");
120
33
  }
121
34
  catch {
122
- return 0;
35
+ // A read-only or full filesystem must not break the TUI.
123
36
  }
124
37
  };
125
38
  append({ type: "session_start", id, cwd: process.cwd(), ...meta });
@@ -127,9 +40,6 @@ function openLog(id: string | undefined, file: string, meta: Record<string, any>
127
40
  id,
128
41
  file,
129
42
  append,
130
- count() {
131
- return seq;
132
- },
133
43
  close() {
134
44
  append({ type: "session_end" });
135
45
  closed = true;
@@ -142,21 +52,18 @@ function openLog(id: string | undefined, file: string, meta: Record<string, any>
142
52
  },
143
53
  };
144
54
  }
145
-
146
- export function createSessionLog(meta: Record<string, any> = {}) {
55
+ export function createSessionLog(meta = {}) {
147
56
  const { sessions } = getPaths();
148
57
  mkdirSync(sessions, { recursive: true });
149
58
  const id = newId();
150
59
  const file = join(sessions, id + ".jsonl");
151
60
  return openLog(id, file, meta);
152
61
  }
153
-
154
- export function openSessionLog(file: string, meta: Record<string, any> = {}) {
62
+ export function openSessionLog(file) {
155
63
  const { sessions } = getPaths();
156
64
  mkdirSync(sessions, { recursive: true });
157
- return openLog(undefined, existsSync(file) ? file : join(sessions, file), meta);
65
+ return openLog(undefined, existsSync(file) ? file : join(sessions, file), {});
158
66
  }
159
-
160
67
  export function listSessions() {
161
68
  const { sessions } = getPaths();
162
69
  if (!existsSync(sessions))
@@ -172,9 +79,7 @@ export function listSessions() {
172
79
  const info = statSync(file);
173
80
  bytes = info.size;
174
81
  modified = info.mtime.toISOString();
175
- messages = readFileSync(file, "utf8").split(NL).filter((line) =>
176
- line.includes('"type":"user"') ||
177
- (line.includes('"type":"message"') && line.includes('"role":"user"'))).length;
82
+ messages = readFileSync(file, "utf8").split(NL).filter((line) => line.includes('"type":"message"')).length;
178
83
  }
179
84
  catch {
180
85
  // unreadable: report what we have
@@ -183,14 +88,12 @@ export function listSessions() {
183
88
  })
184
89
  .sort((a, b) => (a.modified < b.modified ? 1 : -1));
185
90
  }
186
-
187
- export function resolveSessionFile(idOrFile: string): string {
91
+ export function resolveSessionFile(idOrFile) {
188
92
  const { sessions } = getPaths();
189
93
  const looksLikePath = idOrFile.includes("/") || idOrFile.includes(String.fromCharCode(92)) || idOrFile.endsWith(".jsonl");
190
94
  return looksLikePath ? idOrFile : join(sessions, idOrFile + ".jsonl");
191
95
  }
192
-
193
- export function readSession(idOrFile: string) {
96
+ export function readSession(idOrFile) {
194
97
  const file = resolveSessionFile(idOrFile);
195
98
  if (!existsSync(file))
196
99
  return [];
@@ -198,171 +101,29 @@ export function readSession(idOrFile: string) {
198
101
  .split(NL)
199
102
  .filter(Boolean)
200
103
  .map((line) => {
201
- try {
202
- return JSON.parse(line);
203
- }
204
- catch {
205
- return undefined;
206
- }
207
- })
208
- .filter(Boolean);
209
- }
210
-
211
- export function loadSessionMessages(idOrFile: string) {
212
- return readSession(idOrFile)
213
- .filter((entry) => (entry.type === "message" || entry.type === "user" || entry.type === "assistant_raw") && typeof entry.content === "string")
214
- .map((entry) => ({
215
- role: entry.type === "assistant_raw" ? "assistant" : entry.role ?? "user",
216
- content: entry.content,
217
- }));
218
- }
219
-
220
- export function latestSessionFile(): string | undefined {
221
- const all = listSessions();
222
- return all.length > 0 ? all[0].file : undefined;
223
- }
224
-
225
- function summarize(id: string, file: string, entries: any[]): SessionSummary {
226
- let cwd = "";
227
- let providerId = "";
228
- let model = "";
229
- let turns = 0;
230
- let activeFiles: ActiveFile[] = [];
231
- let messages = 0;
232
- let lastTs = 0;
233
- for (const entry of entries) {
234
- if (typeof entry.ts === "number")
235
- lastTs = entry.ts;
236
- if (entry.type === "session_start") {
237
- cwd = typeof entry.cwd === "string" ? entry.cwd : cwd;
238
- providerId = typeof entry.providerId === "string" ? entry.providerId : providerId;
239
- model = typeof entry.model === "string" ? entry.model : model;
240
- }
241
- else if (entry.type === "message") {
242
- messages += 1;
243
- }
244
- else if (entry.type === "checkpoint") {
245
- cwd = typeof entry.cwd === "string" ? entry.cwd : cwd;
246
- providerId = typeof entry.providerId === "string" ? entry.providerId : providerId;
247
- model = typeof entry.model === "string" ? entry.model : model;
248
- if (typeof entry.turn === "number")
249
- turns = entry.turn;
250
- if (Array.isArray(entry.activeFiles))
251
- activeFiles = entry.activeFiles;
252
- }
253
- }
254
- return { id, file, cwd, providerId, model, turns, activeFiles, messages, lastTs };
255
- }
256
-
257
- function readLastEntry(file: string): any | undefined {
258
- let size = 0;
259
- try {
260
- size = statSync(file).size;
261
- }
262
- catch {
263
- return undefined;
264
- }
265
- if (size === 0)
266
- return undefined;
267
- const length = Math.min(size, 64 * 1024);
268
- const fd = openSync(file, "r");
269
- try {
270
- const buffer = Buffer.alloc(length);
271
- readSync(fd, buffer, 0, length, size - length);
272
- const lines = buffer.toString("utf8").trimEnd().split(NL);
273
- for (let i = lines.length - 1; i >= 0; i -= 1) {
274
- if (!lines[i])
275
- continue;
276
- try {
277
- return JSON.parse(lines[i]);
278
- }
279
- catch {
280
- return { type: "unparsable_tail" };
281
- }
282
- }
283
- return undefined;
284
- }
285
- finally {
286
- closeSync(fd);
287
- }
288
- }
289
-
290
- export function findUnclosedSessions(): SessionSummary[] {
291
- const { sessions } = getPaths();
292
- if (!existsSync(sessions))
293
- return [];
294
- const files = readdirSync(sessions)
295
- .filter((name) => name.endsWith(".jsonl"))
296
- .map((name) => {
297
- const file = join(sessions, name);
298
- let modified = 0;
299
- try {
300
- modified = statSync(file).mtimeMs;
301
- }
302
- catch {
303
- // unreadable
304
- }
305
- return { name, file, modified };
306
- })
307
- .sort((a, b) => b.modified - a.modified);
308
- const out: SessionSummary[] = [];
309
- for (const entry of files) {
310
- const last = readLastEntry(entry.file);
311
- if (!last || last.type === "session_end")
312
- continue;
313
- let entries: any[];
314
104
  try {
315
- entries = readSession(entry.file);
105
+ return JSON.parse(line);
316
106
  }
317
107
  catch {
318
- continue;
108
+ return undefined;
319
109
  }
320
- if (entries.length === 0 || !entries.some((item) => item.type === "session_start"))
321
- continue;
322
- out.push(summarize(entry.name.replace(/[.]jsonl$/, ""), entry.file, entries));
323
- }
324
- return out;
110
+ })
111
+ .filter(Boolean);
325
112
  }
326
-
327
- export function latestUnclosedSession(): SessionSummary | undefined {
328
- return findUnclosedSessions()[0];
113
+ export function loadSessionMessages(idOrFile) {
114
+ return readSession(idOrFile)
115
+ .filter((entry) => entry.type === "message" && typeof entry.content === "string")
116
+ .map((entry) => ({ role: entry.role, content: entry.content }));
329
117
  }
330
-
331
- function ensureProjection(file: string): void {
332
- const target = projectionPath(file);
333
- if (existsSync(target))
334
- return;
335
- const entries = readSession(file);
336
- if (entries.length === 0)
337
- return;
338
- const summary = summarize(basename(file).replace(/[.]jsonl$/, ""), file, entries);
339
- const lastUser = [...entries].reverse().find((entry) => entry.type === "message" && entry.role === "user");
340
- const lastAssistant = [...entries].reverse().find((entry) => entry.type === "message" && entry.role === "assistant");
341
- const payload = {
342
- version: 1,
343
- kind: "pruned_session",
344
- source: basename(file),
345
- createdAt: new Date().toISOString(),
346
- summary: {
347
- cwd: summary.cwd,
348
- providerId: summary.providerId,
349
- model: summary.model,
350
- turns: summary.turns,
351
- messages: summary.messages,
352
- activeFiles: summary.activeFiles.slice(0, 200),
353
- lastUser: typeof lastUser?.content === "string" ? lastUser.content.slice(0, 2000) : "",
354
- lastAssistant: typeof lastAssistant?.content === "string" ? lastAssistant.content.slice(0, 2000) : "",
355
- },
356
- };
357
- writeFileSync(target, JSON.stringify(payload, null, 2), "utf8");
118
+ export function latestSessionFile() {
119
+ const all = listSessions();
120
+ return all.length > 0 ? all[0].file : undefined;
358
121
  }
359
-
360
- export function pruneSessions(limit = sessionCacheLimitBytes()): number {
122
+ export function pruneSessions(limit = sessionCacheLimitBytes()) {
361
123
  const all = listSessions();
362
124
  let total = all.reduce((sum, entry) => sum + entry.bytes, 0);
363
125
  for (let i = all.length - 1; i >= 0 && total > limit; i -= 1) {
364
126
  try {
365
- ensureProjection(all[i].file);
366
127
  rmSync(all[i].file, { force: true });
367
128
  total -= all[i].bytes;
368
129
  }
package/src/types.ts CHANGED
@@ -5,29 +5,10 @@
5
5
  * what the chat loop needs (streaming completions, model list, auth).
6
6
  * Providers can layer richer behavior on top in their own modules.
7
7
  */
8
- export type Role = "system" | "user" | "assistant" | "tool";
9
- export type ReasoningEffort = "auto" | "low" | "medium" | "high" | "max";
10
-
11
- export interface NativeToolCall {
12
- id: string;
13
- name: string;
14
- arguments: string;
15
- }
16
-
17
- export interface ToolSchema {
18
- type: "function";
19
- function: {
20
- name: string;
21
- description: string;
22
- parameters: Record<string, any>;
23
- };
24
- }
25
-
8
+ export type Role = "system" | "user" | "assistant";
26
9
  export interface ChatMessage {
27
10
  role: Role;
28
11
  content: string;
29
- toolCalls?: NativeToolCall[];
30
- toolCallId?: string;
31
12
  }
32
13
  export interface ChatRequest {
33
14
  model: string;
@@ -38,26 +19,14 @@ export interface ChatRequest {
38
19
  temperature?: number;
39
20
  /** Soft cap for output tokens. */
40
21
  maxOutputTokens?: number;
41
- /**
42
- * Requested thinking depth. Providers that support it map this to their
43
- * native parameter (`reasoning_effort`, `thinking.budget_tokens`, …);
44
- * providers that don't should ignore it.
45
- */
46
- reasoningEffort?: ReasoningEffort;
47
22
  /** AbortSignal for cancellation (Esc / Ctrl+C). */
48
23
  signal?: AbortSignal;
49
- /** Stable idempotency key reused across retries of the same logical request. */
50
- clientRequestId?: string;
51
- /** Native function-calling schemas (auto mode only). */
52
- tools?: ToolSchema[];
53
24
  }
54
25
  export interface ChatChunk {
55
26
  /** Token text appended to the response. */
56
27
  delta: string;
57
28
  /** True on the final chunk. */
58
29
  done: boolean;
59
- /** Complete native tool calls (OpenAI-style) on the final chunk. */
60
- toolCalls?: NativeToolCall[];
61
30
  /** Provider-specific usage info on the final chunk. */
62
31
  usage?: {
63
32
  inputTokens?: number;
@@ -94,25 +63,6 @@ export interface Provider {
94
63
  listModels(apiKey: string | undefined): Promise<ProviderModel[]>;
95
64
  /** Stream a chat completion. */
96
65
  stream(req: ChatRequest, apiKey: string | undefined): ChatStream;
97
- /** Whether this provider/model honors ChatRequest.reasoningEffort. */
98
- supportsReasoning?(model: string): boolean;
99
- }
100
- export interface PickerItem {
101
- value: string;
102
- label: string;
103
- hint?: string;
104
- }
105
- /**
106
- * An interactive list a command can hand back to the app. The app renders it
107
- * with arrow keys, number shortcuts, and Enter (Esc cancels).
108
- */
109
- export interface PickerRequest {
110
- title: string;
111
- items: PickerItem[];
112
- /** Currently selected value, highlighted on open. */
113
- current?: string;
114
- onSelect(value: string, item: PickerItem): void;
115
- onCancel?(): void;
116
66
  }
117
67
  export interface SlashCommand {
118
68
  name: string;
@@ -131,8 +81,6 @@ export interface CommandResult {
131
81
  silent?: boolean;
132
82
  /** Quit the app after this command. */
133
83
  quit?: boolean;
134
- /** Open an interactive picker after the command runs. */
135
- picker?: PickerRequest;
136
84
  }
137
85
  export interface CommandContext {
138
86
  args: string[];
@@ -153,10 +101,6 @@ export interface AppStateSnapshot {
153
101
  enabledSkills: string[];
154
102
  enabledPlugins: string[];
155
103
  skillsRegistry: string;
156
- reasoningEffort: ReasoningEffort;
157
- expanded: boolean;
158
- busy: boolean;
159
- draft: string;
160
104
  }
161
105
  export interface AppActions {
162
106
  setProvider(id: string): void;
@@ -165,17 +109,11 @@ export interface AppActions {
165
109
  setAgentMode(enabled: boolean): void;
166
110
  setSystemPrompt(prompt: string): void;
167
111
  setSkillsRegistry(url: string): void;
168
- setReasoningEffort(effort: ReasoningEffort): void;
169
112
  enableSkill(name: string): void;
170
113
  disableSkill(name: string): void;
171
114
  enablePlugin(name: string): void;
172
115
  disablePlugin(name: string): void;
173
116
  clearHistory(): void;
174
- replaceHistory(messages: ChatMessage[]): void;
175
- cancelRun(): void;
176
- steer(text: string): void;
177
- setExpanded(value: boolean): void;
178
- setInput(text: string): void;
179
117
  pushSystemMessage(text: string): void;
180
118
  /** Begin the guided API-key flow (choose provider → model → key). */
181
119
  startLogin(options: { provider?: string; model?: string }): void;
package/src/ui/Message.ts CHANGED
@@ -6,19 +6,8 @@ const SIGIL = {
6
6
  assistant: "▍ FastCode",
7
7
  system: "✦ system",
8
8
  };
9
- const markdownCache = new Map();
10
- function cachedMarkdown(md) {
11
- const hit = markdownCache.get(md);
12
- if (hit !== undefined)
13
- return hit;
14
- const rendered = renderMarkdown(md);
15
- if (markdownCache.size > 200)
16
- markdownCache.clear();
17
- markdownCache.set(md, rendered);
18
- return rendered;
19
- }
20
- export const MessageBubble = ({ theme, role, content, streaming, markdown }: any) => {
9
+ export const MessageBubble = ({ theme, role, content, streaming, markdown, }) => {
21
10
  const color = role === "user" ? theme.user : role === "assistant" ? theme.assistant : theme.system;
22
- const display = markdown ? cachedMarkdown(content) : content;
11
+ const display = markdown ? renderMarkdown(content) : content;
23
12
  return (_jsxs(Box, { flexDirection: "column", marginY: 0, children: [_jsxs(Text, { color: color, bold: true, children: [SIGIL[role], streaming ? _jsx(Text, { color: theme.muted, children: " (streaming)" }) : null] }), _jsx(Box, { marginLeft: 2, flexDirection: "column", children: _jsx(Text, { color: color, children: display || (streaming ? "…" : "") }) })] }));
24
13
  };