@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
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
80
|
-
res = await send(true
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
26
|
-
supportsReasoning: () => true,
|
|
25
|
+
stream: (req, apiKey) => streamChatCompletion(req, { baseUrl: BASE, apiKey, staticModels: STATIC, extraHeaders: EXTRA_HEADERS }),
|
|
27
26
|
};
|
package/src/providers/qwen.ts
CHANGED
|
@@ -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
|
|
23
|
-
supportsReasoning: () => true,
|
|
22
|
+
stream: (req, apiKey) => streamChatCompletion(req, { baseUrl: baseUrl(), apiKey, staticModels: STATIC }),
|
|
24
23
|
};
|
package/src/providers/xai.ts
CHANGED
|
@@ -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
|
|
18
|
-
supportsReasoning: () => true,
|
|
17
|
+
stream: (req, apiKey) => streamChatCompletion(req, { baseUrl: BASE, apiKey, staticModels: STATIC }),
|
|
19
18
|
};
|
package/src/providers/zhipu.ts
CHANGED
|
@@ -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
|
|
23
|
-
supportsReasoning: () => true,
|
|
22
|
+
stream: (req, apiKey) => streamChatCompletion(req, { baseUrl: baseUrl(), apiKey, staticModels: STATIC }),
|
|
24
23
|
};
|
package/src/session/log.ts
CHANGED
|
@@ -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,
|
|
14
|
-
import {
|
|
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
|
-
|
|
111
|
-
const append = (entry: Record<string, any>) => {
|
|
28
|
+
const append = (entry) => {
|
|
112
29
|
if (closed || !loggingEnabled())
|
|
113
|
-
return
|
|
114
|
-
const next = seq + 1;
|
|
115
|
-
const line = JSON.stringify({ ts: Date.now(), seq: next, ...entry }) + NL;
|
|
30
|
+
return;
|
|
116
31
|
try {
|
|
117
|
-
|
|
118
|
-
seq = next;
|
|
119
|
-
return next;
|
|
32
|
+
appendFileSync(file, JSON.stringify({ ts: Date.now(), ...entry }) + NL, "utf8");
|
|
120
33
|
}
|
|
121
34
|
catch {
|
|
122
|
-
|
|
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),
|
|
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
|
-
|
|
105
|
+
return JSON.parse(line);
|
|
316
106
|
}
|
|
317
107
|
catch {
|
|
318
|
-
|
|
108
|
+
return undefined;
|
|
319
109
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
out.push(summarize(entry.name.replace(/[.]jsonl$/, ""), entry.file, entries));
|
|
323
|
-
}
|
|
324
|
-
return out;
|
|
110
|
+
})
|
|
111
|
+
.filter(Boolean);
|
|
325
112
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
332
|
-
|
|
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"
|
|
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
|
|
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 ?
|
|
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
|
};
|