indusagi-coding-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2249 -0
- package/README.md +546 -0
- package/dist/cli/args.js +282 -0
- package/dist/cli/config-selector.js +30 -0
- package/dist/cli/file-processor.js +78 -0
- package/dist/cli/list-models.js +91 -0
- package/dist/cli/session-picker.js +31 -0
- package/dist/cli.js +10 -0
- package/dist/config.js +158 -0
- package/dist/core/agent-session.js +2097 -0
- package/dist/core/auth-storage.js +278 -0
- package/dist/core/bash-executor.js +211 -0
- package/dist/core/compaction/branch-summarization.js +241 -0
- package/dist/core/compaction/compaction.js +606 -0
- package/dist/core/compaction/index.js +6 -0
- package/dist/core/compaction/utils.js +137 -0
- package/dist/core/diagnostics.js +1 -0
- package/dist/core/event-bus.js +24 -0
- package/dist/core/exec.js +70 -0
- package/dist/core/export-html/ansi-to-html.js +248 -0
- package/dist/core/export-html/index.js +221 -0
- package/dist/core/export-html/template.css +905 -0
- package/dist/core/export-html/template.html +54 -0
- package/dist/core/export-html/template.js +1549 -0
- package/dist/core/export-html/tool-renderer.js +56 -0
- package/dist/core/export-html/vendor/highlight.min.js +1213 -0
- package/dist/core/export-html/vendor/marked.min.js +6 -0
- package/dist/core/extensions/index.js +8 -0
- package/dist/core/extensions/loader.js +395 -0
- package/dist/core/extensions/runner.js +499 -0
- package/dist/core/extensions/types.js +31 -0
- package/dist/core/extensions/wrapper.js +101 -0
- package/dist/core/footer-data-provider.js +133 -0
- package/dist/core/index.js +8 -0
- package/dist/core/keybindings.js +140 -0
- package/dist/core/messages.js +122 -0
- package/dist/core/model-registry.js +454 -0
- package/dist/core/model-resolver.js +309 -0
- package/dist/core/package-manager.js +1142 -0
- package/dist/core/prompt-templates.js +250 -0
- package/dist/core/resource-loader.js +569 -0
- package/dist/core/sdk.js +225 -0
- package/dist/core/session-manager.js +1078 -0
- package/dist/core/settings-manager.js +430 -0
- package/dist/core/skills.js +339 -0
- package/dist/core/system-prompt.js +136 -0
- package/dist/core/timings.js +24 -0
- package/dist/core/tools/bash.js +226 -0
- package/dist/core/tools/edit-diff.js +242 -0
- package/dist/core/tools/edit.js +145 -0
- package/dist/core/tools/find.js +205 -0
- package/dist/core/tools/grep.js +238 -0
- package/dist/core/tools/index.js +60 -0
- package/dist/core/tools/ls.js +117 -0
- package/dist/core/tools/path-utils.js +52 -0
- package/dist/core/tools/read.js +165 -0
- package/dist/core/tools/truncate.js +204 -0
- package/dist/core/tools/write.js +77 -0
- package/dist/index.js +41 -0
- package/dist/main.js +565 -0
- package/dist/migrations.js +260 -0
- package/dist/modes/index.js +7 -0
- package/dist/modes/interactive/components/armin.js +328 -0
- package/dist/modes/interactive/components/assistant-message.js +86 -0
- package/dist/modes/interactive/components/bash-execution.js +155 -0
- package/dist/modes/interactive/components/bordered-loader.js +47 -0
- package/dist/modes/interactive/components/branch-summary-message.js +41 -0
- package/dist/modes/interactive/components/compaction-summary-message.js +42 -0
- package/dist/modes/interactive/components/config-selector.js +458 -0
- package/dist/modes/interactive/components/countdown-timer.js +27 -0
- package/dist/modes/interactive/components/custom-editor.js +61 -0
- package/dist/modes/interactive/components/custom-message.js +80 -0
- package/dist/modes/interactive/components/diff.js +132 -0
- package/dist/modes/interactive/components/dynamic-border.js +19 -0
- package/dist/modes/interactive/components/extension-editor.js +96 -0
- package/dist/modes/interactive/components/extension-input.js +54 -0
- package/dist/modes/interactive/components/extension-selector.js +70 -0
- package/dist/modes/interactive/components/footer.js +213 -0
- package/dist/modes/interactive/components/index.js +31 -0
- package/dist/modes/interactive/components/keybinding-hints.js +60 -0
- package/dist/modes/interactive/components/login-dialog.js +138 -0
- package/dist/modes/interactive/components/model-selector.js +253 -0
- package/dist/modes/interactive/components/oauth-selector.js +91 -0
- package/dist/modes/interactive/components/scoped-models-selector.js +262 -0
- package/dist/modes/interactive/components/session-selector-search.js +145 -0
- package/dist/modes/interactive/components/session-selector.js +698 -0
- package/dist/modes/interactive/components/settings-selector.js +250 -0
- package/dist/modes/interactive/components/show-images-selector.js +33 -0
- package/dist/modes/interactive/components/skill-invocation-message.js +44 -0
- package/dist/modes/interactive/components/theme-selector.js +43 -0
- package/dist/modes/interactive/components/thinking-selector.js +45 -0
- package/dist/modes/interactive/components/tool-execution.js +608 -0
- package/dist/modes/interactive/components/tree-selector.js +892 -0
- package/dist/modes/interactive/components/user-message-selector.js +109 -0
- package/dist/modes/interactive/components/user-message.js +15 -0
- package/dist/modes/interactive/components/visual-truncate.js +32 -0
- package/dist/modes/interactive/interactive-mode.js +3576 -0
- package/dist/modes/interactive/theme/dark.json +85 -0
- package/dist/modes/interactive/theme/light.json +84 -0
- package/dist/modes/interactive/theme/theme-schema.json +335 -0
- package/dist/modes/interactive/theme/theme.js +938 -0
- package/dist/modes/print-mode.js +96 -0
- package/dist/modes/rpc/rpc-client.js +390 -0
- package/dist/modes/rpc/rpc-mode.js +448 -0
- package/dist/modes/rpc/rpc-types.js +7 -0
- package/dist/utils/changelog.js +86 -0
- package/dist/utils/clipboard-image.js +116 -0
- package/dist/utils/clipboard.js +58 -0
- package/dist/utils/frontmatter.js +25 -0
- package/dist/utils/git.js +5 -0
- package/dist/utils/image-convert.js +34 -0
- package/dist/utils/image-resize.js +180 -0
- package/dist/utils/mime.js +25 -0
- package/dist/utils/photon.js +120 -0
- package/dist/utils/shell.js +164 -0
- package/dist/utils/sleep.js +16 -0
- package/dist/utils/tools-manager.js +186 -0
- package/docs/compaction.md +390 -0
- package/docs/custom-provider.md +538 -0
- package/docs/development.md +69 -0
- package/docs/extensions.md +1733 -0
- package/docs/images/doom-extension.png +0 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/images/tree-view.png +0 -0
- package/docs/json.md +79 -0
- package/docs/keybindings.md +162 -0
- package/docs/models.md +193 -0
- package/docs/packages.md +163 -0
- package/docs/prompt-templates.md +67 -0
- package/docs/providers.md +147 -0
- package/docs/rpc.md +1048 -0
- package/docs/sdk.md +957 -0
- package/docs/session.md +412 -0
- package/docs/settings.md +216 -0
- package/docs/shell-aliases.md +13 -0
- package/docs/skills.md +226 -0
- package/docs/terminal-setup.md +65 -0
- package/docs/themes.md +295 -0
- package/docs/tree.md +219 -0
- package/docs/tui.md +887 -0
- package/docs/windows.md +17 -0
- package/examples/README.md +25 -0
- package/examples/extensions/README.md +192 -0
- package/examples/extensions/antigravity-image-gen.ts +414 -0
- package/examples/extensions/auto-commit-on-exit.ts +49 -0
- package/examples/extensions/bookmark.ts +50 -0
- package/examples/extensions/claude-rules.ts +86 -0
- package/examples/extensions/confirm-destructive.ts +59 -0
- package/examples/extensions/custom-compaction.ts +115 -0
- package/examples/extensions/custom-footer.ts +65 -0
- package/examples/extensions/custom-header.ts +73 -0
- package/examples/extensions/custom-provider-anthropic/index.ts +605 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
- package/examples/extensions/custom-provider-anthropic/package.json +19 -0
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +350 -0
- package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +83 -0
- package/examples/extensions/dirty-repo-guard.ts +56 -0
- package/examples/extensions/doom-overlay/README.md +46 -0
- package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
- package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/examples/extensions/doom-overlay/doom/build.sh +152 -0
- package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
- package/examples/extensions/doom-overlay/doom-component.ts +133 -0
- package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
- package/examples/extensions/doom-overlay/doom-keys.ts +105 -0
- package/examples/extensions/doom-overlay/index.ts +74 -0
- package/examples/extensions/doom-overlay/wad-finder.ts +51 -0
- package/examples/extensions/event-bus.ts +43 -0
- package/examples/extensions/file-trigger.ts +41 -0
- package/examples/extensions/git-checkpoint.ts +53 -0
- package/examples/extensions/handoff.ts +151 -0
- package/examples/extensions/hello.ts +25 -0
- package/examples/extensions/inline-bash.ts +94 -0
- package/examples/extensions/input-transform.ts +43 -0
- package/examples/extensions/interactive-shell.ts +196 -0
- package/examples/extensions/mac-system-theme.ts +47 -0
- package/examples/extensions/message-renderer.ts +60 -0
- package/examples/extensions/modal-editor.ts +86 -0
- package/examples/extensions/model-status.ts +31 -0
- package/examples/extensions/notify.ts +25 -0
- package/examples/extensions/overlay-qa-tests.ts +882 -0
- package/examples/extensions/overlay-test.ts +151 -0
- package/examples/extensions/permission-gate.ts +34 -0
- package/examples/extensions/pirate.ts +47 -0
- package/examples/extensions/plan-mode/README.md +65 -0
- package/examples/extensions/plan-mode/index.ts +341 -0
- package/examples/extensions/plan-mode/utils.ts +168 -0
- package/examples/extensions/preset.ts +399 -0
- package/examples/extensions/protected-paths.ts +30 -0
- package/examples/extensions/qna.ts +120 -0
- package/examples/extensions/question.ts +265 -0
- package/examples/extensions/questionnaire.ts +428 -0
- package/examples/extensions/rainbow-editor.ts +88 -0
- package/examples/extensions/sandbox/index.ts +318 -0
- package/examples/extensions/sandbox/package-lock.json +92 -0
- package/examples/extensions/sandbox/package.json +19 -0
- package/examples/extensions/send-user-message.ts +97 -0
- package/examples/extensions/session-name.ts +27 -0
- package/examples/extensions/shutdown-command.ts +63 -0
- package/examples/extensions/snake.ts +344 -0
- package/examples/extensions/space-invaders.ts +561 -0
- package/examples/extensions/ssh.ts +220 -0
- package/examples/extensions/status-line.ts +40 -0
- package/examples/extensions/subagent/README.md +172 -0
- package/examples/extensions/subagent/agents/planner.md +37 -0
- package/examples/extensions/subagent/agents/reviewer.md +35 -0
- package/examples/extensions/subagent/agents/scout.md +50 -0
- package/examples/extensions/subagent/agents/worker.md +24 -0
- package/examples/extensions/subagent/agents.ts +127 -0
- package/examples/extensions/subagent/index.ts +964 -0
- package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
- package/examples/extensions/subagent/prompts/implement.md +10 -0
- package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
- package/examples/extensions/summarize.ts +196 -0
- package/examples/extensions/timed-confirm.ts +70 -0
- package/examples/extensions/todo.ts +300 -0
- package/examples/extensions/tool-override.ts +144 -0
- package/examples/extensions/tools.ts +147 -0
- package/examples/extensions/trigger-compact.ts +40 -0
- package/examples/extensions/truncated-tool.ts +193 -0
- package/examples/extensions/widget-placement.ts +17 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +22 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +50 -0
- package/examples/sdk/03-custom-prompt.ts +55 -0
- package/examples/sdk/04-skills.ts +46 -0
- package/examples/sdk/05-tools.ts +56 -0
- package/examples/sdk/06-extensions.ts +88 -0
- package/examples/sdk/07-context-files.ts +40 -0
- package/examples/sdk/08-prompt-templates.ts +47 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +48 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +82 -0
- package/examples/sdk/13-codex-oauth.ts +37 -0
- package/examples/sdk/README.md +144 -0
- package/package.json +85 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { constants } from "fs";
|
|
3
|
+
import { access as fsAccess, readFile as fsReadFile } from "fs/promises";
|
|
4
|
+
import { formatDimensionNote, resizeImage } from "../../utils/image-resize.js";
|
|
5
|
+
import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime.js";
|
|
6
|
+
import { resolveReadPath } from "./path-utils.js";
|
|
7
|
+
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHead } from "./truncate.js";
|
|
8
|
+
const readSchema = Type.Object({
|
|
9
|
+
path: Type.String({ description: "Path to the file to read (relative or absolute)" }),
|
|
10
|
+
offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
|
|
11
|
+
limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
|
|
12
|
+
});
|
|
13
|
+
const defaultReadOperations = {
|
|
14
|
+
readFile: (path) => fsReadFile(path),
|
|
15
|
+
access: (path) => fsAccess(path, constants.R_OK),
|
|
16
|
+
detectImageMimeType: detectSupportedImageMimeTypeFromFile,
|
|
17
|
+
};
|
|
18
|
+
export function createReadTool(cwd, options) {
|
|
19
|
+
const autoResizeImages = options?.autoResizeImages ?? true;
|
|
20
|
+
const ops = options?.operations ?? defaultReadOperations;
|
|
21
|
+
return {
|
|
22
|
+
name: "read",
|
|
23
|
+
label: "read",
|
|
24
|
+
description: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,
|
|
25
|
+
parameters: readSchema,
|
|
26
|
+
execute: async (_toolCallId, { path, offset, limit }, signal) => {
|
|
27
|
+
const absolutePath = resolveReadPath(path, cwd);
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
// Check if already aborted
|
|
30
|
+
if (signal?.aborted) {
|
|
31
|
+
reject(new Error("Operation aborted"));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
let aborted = false;
|
|
35
|
+
// Set up abort handler
|
|
36
|
+
const onAbort = () => {
|
|
37
|
+
aborted = true;
|
|
38
|
+
reject(new Error("Operation aborted"));
|
|
39
|
+
};
|
|
40
|
+
if (signal) {
|
|
41
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
42
|
+
}
|
|
43
|
+
// Perform the read operation
|
|
44
|
+
(async () => {
|
|
45
|
+
try {
|
|
46
|
+
// Check if file exists
|
|
47
|
+
await ops.access(absolutePath);
|
|
48
|
+
// Check if aborted before reading
|
|
49
|
+
if (aborted) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const mimeType = ops.detectImageMimeType ? await ops.detectImageMimeType(absolutePath) : undefined;
|
|
53
|
+
// Read the file based on type
|
|
54
|
+
let content;
|
|
55
|
+
let details;
|
|
56
|
+
if (mimeType) {
|
|
57
|
+
// Read as image (binary)
|
|
58
|
+
const buffer = await ops.readFile(absolutePath);
|
|
59
|
+
const base64 = buffer.toString("base64");
|
|
60
|
+
if (autoResizeImages) {
|
|
61
|
+
// Resize image if needed
|
|
62
|
+
const resized = await resizeImage({ type: "image", data: base64, mimeType });
|
|
63
|
+
const dimensionNote = formatDimensionNote(resized);
|
|
64
|
+
let textNote = `Read image file [${resized.mimeType}]`;
|
|
65
|
+
if (dimensionNote) {
|
|
66
|
+
textNote += `\n${dimensionNote}`;
|
|
67
|
+
}
|
|
68
|
+
content = [
|
|
69
|
+
{ type: "text", text: textNote },
|
|
70
|
+
{ type: "image", data: resized.data, mimeType: resized.mimeType },
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const textNote = `Read image file [${mimeType}]`;
|
|
75
|
+
content = [
|
|
76
|
+
{ type: "text", text: textNote },
|
|
77
|
+
{ type: "image", data: base64, mimeType },
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Read as text
|
|
83
|
+
const buffer = await ops.readFile(absolutePath);
|
|
84
|
+
const textContent = buffer.toString("utf-8");
|
|
85
|
+
const allLines = textContent.split("\n");
|
|
86
|
+
const totalFileLines = allLines.length;
|
|
87
|
+
// Apply offset if specified (1-indexed to 0-indexed)
|
|
88
|
+
const startLine = offset ? Math.max(0, offset - 1) : 0;
|
|
89
|
+
const startLineDisplay = startLine + 1; // For display (1-indexed)
|
|
90
|
+
// Check if offset is out of bounds
|
|
91
|
+
if (startLine >= allLines.length) {
|
|
92
|
+
throw new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);
|
|
93
|
+
}
|
|
94
|
+
// If limit is specified by user, use it; otherwise we'll let truncateHead decide
|
|
95
|
+
let selectedContent;
|
|
96
|
+
let userLimitedLines;
|
|
97
|
+
if (limit !== undefined) {
|
|
98
|
+
const endLine = Math.min(startLine + limit, allLines.length);
|
|
99
|
+
selectedContent = allLines.slice(startLine, endLine).join("\n");
|
|
100
|
+
userLimitedLines = endLine - startLine;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
selectedContent = allLines.slice(startLine).join("\n");
|
|
104
|
+
}
|
|
105
|
+
// Apply truncation (respects both line and byte limits)
|
|
106
|
+
const truncation = truncateHead(selectedContent);
|
|
107
|
+
let outputText;
|
|
108
|
+
if (truncation.firstLineExceedsLimit) {
|
|
109
|
+
// First line at offset exceeds 30KB - tell model to use bash
|
|
110
|
+
const firstLineSize = formatSize(Buffer.byteLength(allLines[startLine], "utf-8"));
|
|
111
|
+
outputText = `[Line ${startLineDisplay} is ${firstLineSize}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Use bash: sed -n '${startLineDisplay}p' ${path} | head -c ${DEFAULT_MAX_BYTES}]`;
|
|
112
|
+
details = { truncation };
|
|
113
|
+
}
|
|
114
|
+
else if (truncation.truncated) {
|
|
115
|
+
// Truncation occurred - build actionable notice
|
|
116
|
+
const endLineDisplay = startLineDisplay + truncation.outputLines - 1;
|
|
117
|
+
const nextOffset = endLineDisplay + 1;
|
|
118
|
+
outputText = truncation.content;
|
|
119
|
+
if (truncation.truncatedBy === "lines") {
|
|
120
|
+
outputText += `\n\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue.]`;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
outputText += `\n\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Use offset=${nextOffset} to continue.]`;
|
|
124
|
+
}
|
|
125
|
+
details = { truncation };
|
|
126
|
+
}
|
|
127
|
+
else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {
|
|
128
|
+
// User specified limit, there's more content, but no truncation
|
|
129
|
+
const remaining = allLines.length - (startLine + userLimitedLines);
|
|
130
|
+
const nextOffset = startLine + userLimitedLines + 1;
|
|
131
|
+
outputText = truncation.content;
|
|
132
|
+
outputText += `\n\n[${remaining} more lines in file. Use offset=${nextOffset} to continue.]`;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// No truncation, no user limit exceeded
|
|
136
|
+
outputText = truncation.content;
|
|
137
|
+
}
|
|
138
|
+
content = [{ type: "text", text: outputText }];
|
|
139
|
+
}
|
|
140
|
+
// Check if aborted after reading
|
|
141
|
+
if (aborted) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// Clean up abort handler
|
|
145
|
+
if (signal) {
|
|
146
|
+
signal.removeEventListener("abort", onAbort);
|
|
147
|
+
}
|
|
148
|
+
resolve({ content, details });
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
// Clean up abort handler
|
|
152
|
+
if (signal) {
|
|
153
|
+
signal.removeEventListener("abort", onAbort);
|
|
154
|
+
}
|
|
155
|
+
if (!aborted) {
|
|
156
|
+
reject(error);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
})();
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/** Default read tool using process.cwd() - for backwards compatibility */
|
|
165
|
+
export const readTool = createReadTool(process.cwd());
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared truncation utilities for tool outputs.
|
|
3
|
+
*
|
|
4
|
+
* Truncation is based on two independent limits - whichever is hit first wins:
|
|
5
|
+
* - Line limit (default: 2000 lines)
|
|
6
|
+
* - Byte limit (default: 50KB)
|
|
7
|
+
*
|
|
8
|
+
* Never returns partial lines (except bash tail truncation edge case).
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_MAX_LINES = 2000;
|
|
11
|
+
export const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB
|
|
12
|
+
export const GREP_MAX_LINE_LENGTH = 500; // Max chars per grep match line
|
|
13
|
+
/**
|
|
14
|
+
* Format bytes as human-readable size.
|
|
15
|
+
*/
|
|
16
|
+
export function formatSize(bytes) {
|
|
17
|
+
if (bytes < 1024) {
|
|
18
|
+
return `${bytes}B`;
|
|
19
|
+
}
|
|
20
|
+
else if (bytes < 1024 * 1024) {
|
|
21
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Truncate content from the head (keep first N lines/bytes).
|
|
29
|
+
* Suitable for file reads where you want to see the beginning.
|
|
30
|
+
*
|
|
31
|
+
* Never returns partial lines. If first line exceeds byte limit,
|
|
32
|
+
* returns empty content with firstLineExceedsLimit=true.
|
|
33
|
+
*/
|
|
34
|
+
export function truncateHead(content, options = {}) {
|
|
35
|
+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
36
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
37
|
+
const totalBytes = Buffer.byteLength(content, "utf-8");
|
|
38
|
+
const lines = content.split("\n");
|
|
39
|
+
const totalLines = lines.length;
|
|
40
|
+
// Check if no truncation needed
|
|
41
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
42
|
+
return {
|
|
43
|
+
content,
|
|
44
|
+
truncated: false,
|
|
45
|
+
truncatedBy: null,
|
|
46
|
+
totalLines,
|
|
47
|
+
totalBytes,
|
|
48
|
+
outputLines: totalLines,
|
|
49
|
+
outputBytes: totalBytes,
|
|
50
|
+
lastLinePartial: false,
|
|
51
|
+
firstLineExceedsLimit: false,
|
|
52
|
+
maxLines,
|
|
53
|
+
maxBytes,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// Check if first line alone exceeds byte limit
|
|
57
|
+
const firstLineBytes = Buffer.byteLength(lines[0], "utf-8");
|
|
58
|
+
if (firstLineBytes > maxBytes) {
|
|
59
|
+
return {
|
|
60
|
+
content: "",
|
|
61
|
+
truncated: true,
|
|
62
|
+
truncatedBy: "bytes",
|
|
63
|
+
totalLines,
|
|
64
|
+
totalBytes,
|
|
65
|
+
outputLines: 0,
|
|
66
|
+
outputBytes: 0,
|
|
67
|
+
lastLinePartial: false,
|
|
68
|
+
firstLineExceedsLimit: true,
|
|
69
|
+
maxLines,
|
|
70
|
+
maxBytes,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// Collect complete lines that fit
|
|
74
|
+
const outputLinesArr = [];
|
|
75
|
+
let outputBytesCount = 0;
|
|
76
|
+
let truncatedBy = "lines";
|
|
77
|
+
for (let i = 0; i < lines.length && i < maxLines; i++) {
|
|
78
|
+
const line = lines[i];
|
|
79
|
+
const lineBytes = Buffer.byteLength(line, "utf-8") + (i > 0 ? 1 : 0); // +1 for newline
|
|
80
|
+
if (outputBytesCount + lineBytes > maxBytes) {
|
|
81
|
+
truncatedBy = "bytes";
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
outputLinesArr.push(line);
|
|
85
|
+
outputBytesCount += lineBytes;
|
|
86
|
+
}
|
|
87
|
+
// If we exited due to line limit
|
|
88
|
+
if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
|
|
89
|
+
truncatedBy = "lines";
|
|
90
|
+
}
|
|
91
|
+
const outputContent = outputLinesArr.join("\n");
|
|
92
|
+
const finalOutputBytes = Buffer.byteLength(outputContent, "utf-8");
|
|
93
|
+
return {
|
|
94
|
+
content: outputContent,
|
|
95
|
+
truncated: true,
|
|
96
|
+
truncatedBy,
|
|
97
|
+
totalLines,
|
|
98
|
+
totalBytes,
|
|
99
|
+
outputLines: outputLinesArr.length,
|
|
100
|
+
outputBytes: finalOutputBytes,
|
|
101
|
+
lastLinePartial: false,
|
|
102
|
+
firstLineExceedsLimit: false,
|
|
103
|
+
maxLines,
|
|
104
|
+
maxBytes,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Truncate content from the tail (keep last N lines/bytes).
|
|
109
|
+
* Suitable for bash output where you want to see the end (errors, final results).
|
|
110
|
+
*
|
|
111
|
+
* May return partial first line if the last line of original content exceeds byte limit.
|
|
112
|
+
*/
|
|
113
|
+
export function truncateTail(content, options = {}) {
|
|
114
|
+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
115
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
116
|
+
const totalBytes = Buffer.byteLength(content, "utf-8");
|
|
117
|
+
const lines = content.split("\n");
|
|
118
|
+
const totalLines = lines.length;
|
|
119
|
+
// Check if no truncation needed
|
|
120
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
121
|
+
return {
|
|
122
|
+
content,
|
|
123
|
+
truncated: false,
|
|
124
|
+
truncatedBy: null,
|
|
125
|
+
totalLines,
|
|
126
|
+
totalBytes,
|
|
127
|
+
outputLines: totalLines,
|
|
128
|
+
outputBytes: totalBytes,
|
|
129
|
+
lastLinePartial: false,
|
|
130
|
+
firstLineExceedsLimit: false,
|
|
131
|
+
maxLines,
|
|
132
|
+
maxBytes,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// Work backwards from the end
|
|
136
|
+
const outputLinesArr = [];
|
|
137
|
+
let outputBytesCount = 0;
|
|
138
|
+
let truncatedBy = "lines";
|
|
139
|
+
let lastLinePartial = false;
|
|
140
|
+
for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {
|
|
141
|
+
const line = lines[i];
|
|
142
|
+
const lineBytes = Buffer.byteLength(line, "utf-8") + (outputLinesArr.length > 0 ? 1 : 0); // +1 for newline
|
|
143
|
+
if (outputBytesCount + lineBytes > maxBytes) {
|
|
144
|
+
truncatedBy = "bytes";
|
|
145
|
+
// Edge case: if we haven't added ANY lines yet and this line exceeds maxBytes,
|
|
146
|
+
// take the end of the line (partial)
|
|
147
|
+
if (outputLinesArr.length === 0) {
|
|
148
|
+
const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);
|
|
149
|
+
outputLinesArr.unshift(truncatedLine);
|
|
150
|
+
outputBytesCount = Buffer.byteLength(truncatedLine, "utf-8");
|
|
151
|
+
lastLinePartial = true;
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
outputLinesArr.unshift(line);
|
|
156
|
+
outputBytesCount += lineBytes;
|
|
157
|
+
}
|
|
158
|
+
// If we exited due to line limit
|
|
159
|
+
if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
|
|
160
|
+
truncatedBy = "lines";
|
|
161
|
+
}
|
|
162
|
+
const outputContent = outputLinesArr.join("\n");
|
|
163
|
+
const finalOutputBytes = Buffer.byteLength(outputContent, "utf-8");
|
|
164
|
+
return {
|
|
165
|
+
content: outputContent,
|
|
166
|
+
truncated: true,
|
|
167
|
+
truncatedBy,
|
|
168
|
+
totalLines,
|
|
169
|
+
totalBytes,
|
|
170
|
+
outputLines: outputLinesArr.length,
|
|
171
|
+
outputBytes: finalOutputBytes,
|
|
172
|
+
lastLinePartial,
|
|
173
|
+
firstLineExceedsLimit: false,
|
|
174
|
+
maxLines,
|
|
175
|
+
maxBytes,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Truncate a string to fit within a byte limit (from the end).
|
|
180
|
+
* Handles multi-byte UTF-8 characters correctly.
|
|
181
|
+
*/
|
|
182
|
+
function truncateStringToBytesFromEnd(str, maxBytes) {
|
|
183
|
+
const buf = Buffer.from(str, "utf-8");
|
|
184
|
+
if (buf.length <= maxBytes) {
|
|
185
|
+
return str;
|
|
186
|
+
}
|
|
187
|
+
// Start from the end, skip maxBytes back
|
|
188
|
+
let start = buf.length - maxBytes;
|
|
189
|
+
// Find a valid UTF-8 boundary (start of a character)
|
|
190
|
+
while (start < buf.length && (buf[start] & 0xc0) === 0x80) {
|
|
191
|
+
start++;
|
|
192
|
+
}
|
|
193
|
+
return buf.slice(start).toString("utf-8");
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Truncate a single line to max characters, adding [truncated] suffix.
|
|
197
|
+
* Used for grep match lines.
|
|
198
|
+
*/
|
|
199
|
+
export function truncateLine(line, maxChars = GREP_MAX_LINE_LENGTH) {
|
|
200
|
+
if (line.length <= maxChars) {
|
|
201
|
+
return { text: line, wasTruncated: false };
|
|
202
|
+
}
|
|
203
|
+
return { text: `${line.slice(0, maxChars)}... [truncated]`, wasTruncated: true };
|
|
204
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { mkdir as fsMkdir, writeFile as fsWriteFile } from "fs/promises";
|
|
3
|
+
import { dirname } from "path";
|
|
4
|
+
import { resolveToCwd } from "./path-utils.js";
|
|
5
|
+
const writeSchema = Type.Object({
|
|
6
|
+
path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
|
|
7
|
+
content: Type.String({ description: "Content to write to the file" }),
|
|
8
|
+
});
|
|
9
|
+
const defaultWriteOperations = {
|
|
10
|
+
writeFile: (path, content) => fsWriteFile(path, content, "utf-8"),
|
|
11
|
+
mkdir: (dir) => fsMkdir(dir, { recursive: true }).then(() => { }),
|
|
12
|
+
};
|
|
13
|
+
export function createWriteTool(cwd, options) {
|
|
14
|
+
const ops = options?.operations ?? defaultWriteOperations;
|
|
15
|
+
return {
|
|
16
|
+
name: "write",
|
|
17
|
+
label: "write",
|
|
18
|
+
description: "Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.",
|
|
19
|
+
parameters: writeSchema,
|
|
20
|
+
execute: async (_toolCallId, { path, content }, signal) => {
|
|
21
|
+
const absolutePath = resolveToCwd(path, cwd);
|
|
22
|
+
const dir = dirname(absolutePath);
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
// Check if already aborted
|
|
25
|
+
if (signal?.aborted) {
|
|
26
|
+
reject(new Error("Operation aborted"));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
let aborted = false;
|
|
30
|
+
// Set up abort handler
|
|
31
|
+
const onAbort = () => {
|
|
32
|
+
aborted = true;
|
|
33
|
+
reject(new Error("Operation aborted"));
|
|
34
|
+
};
|
|
35
|
+
if (signal) {
|
|
36
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
37
|
+
}
|
|
38
|
+
// Perform the write operation
|
|
39
|
+
(async () => {
|
|
40
|
+
try {
|
|
41
|
+
// Create parent directories if needed
|
|
42
|
+
await ops.mkdir(dir);
|
|
43
|
+
// Check if aborted before writing
|
|
44
|
+
if (aborted) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Write the file
|
|
48
|
+
await ops.writeFile(absolutePath, content);
|
|
49
|
+
// Check if aborted after writing
|
|
50
|
+
if (aborted) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Clean up abort handler
|
|
54
|
+
if (signal) {
|
|
55
|
+
signal.removeEventListener("abort", onAbort);
|
|
56
|
+
}
|
|
57
|
+
resolve({
|
|
58
|
+
content: [{ type: "text", text: `Successfully wrote ${content.length} bytes to ${path}` }],
|
|
59
|
+
details: undefined,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
// Clean up abort handler
|
|
64
|
+
if (signal) {
|
|
65
|
+
signal.removeEventListener("abort", onAbort);
|
|
66
|
+
}
|
|
67
|
+
if (!aborted) {
|
|
68
|
+
reject(error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
})();
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** Default write tool using process.cwd() - for backwards compatibility */
|
|
77
|
+
export const writeTool = createWriteTool(process.cwd());
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Core session management
|
|
2
|
+
// Config paths
|
|
3
|
+
export { getAgentDir, VERSION } from "./config.js";
|
|
4
|
+
export { AgentSession, parseSkillBlock, } from "./core/agent-session.js";
|
|
5
|
+
// Auth and model registry
|
|
6
|
+
export { AuthStorage } from "./core/auth-storage.js";
|
|
7
|
+
// Compaction
|
|
8
|
+
export { calculateContextTokens, collectEntriesForBranchSummary, compact, DEFAULT_COMPACTION_SETTINGS, estimateTokens, findCutPoint, findTurnStartIndex, generateBranchSummary, generateSummary, getLastAssistantUsage, prepareBranchEntries, serializeConversation, shouldCompact, } from "./core/compaction/index.js";
|
|
9
|
+
export { createEventBus } from "./core/event-bus.js";
|
|
10
|
+
export { createExtensionRuntime, ExtensionRunner, isBashToolResult, isEditToolResult, isFindToolResult, isGrepToolResult, isLsToolResult, isReadToolResult, isWriteToolResult, wrapRegisteredTool, wrapRegisteredTools, wrapToolsWithExtensions, wrapToolWithExtensions, } from "./core/extensions/index.js";
|
|
11
|
+
export { convertToLlm } from "./core/messages.js";
|
|
12
|
+
export { ModelRegistry } from "./core/model-registry.js";
|
|
13
|
+
export { DefaultPackageManager } from "./core/package-manager.js";
|
|
14
|
+
export { DefaultResourceLoader } from "./core/resource-loader.js";
|
|
15
|
+
// SDK for programmatic usage
|
|
16
|
+
export {
|
|
17
|
+
// Factory
|
|
18
|
+
createAgentSession, createBashTool,
|
|
19
|
+
// Tool factories (for custom cwd)
|
|
20
|
+
createCodingTools, createEditTool, createFindTool, createGrepTool, createLsTool, createReadOnlyTools, createReadTool, createWriteTool,
|
|
21
|
+
// Pre-built tools (use process.cwd())
|
|
22
|
+
readOnlyTools, } from "./core/sdk.js";
|
|
23
|
+
export { buildSessionContext, CURRENT_SESSION_VERSION, getLatestCompactionEntry, migrateSessionEntries, parseSessionEntries, SessionManager, } from "./core/session-manager.js";
|
|
24
|
+
export { SettingsManager, } from "./core/settings-manager.js";
|
|
25
|
+
// Skills
|
|
26
|
+
export { formatSkillsForPrompt, loadSkills, loadSkillsFromDir, } from "./core/skills.js";
|
|
27
|
+
// Tools
|
|
28
|
+
export { bashTool, codingTools, DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, editTool, findTool, formatSize, grepTool, lsTool, readTool, truncateHead, truncateLine, truncateTail, writeTool, } from "./core/tools/index.js";
|
|
29
|
+
// Main entry point
|
|
30
|
+
export { main } from "./main.js";
|
|
31
|
+
// Run modes for programmatic SDK usage
|
|
32
|
+
export { InteractiveMode, runPrintMode, runRpcMode, } from "./modes/index.js";
|
|
33
|
+
// UI components for extensions
|
|
34
|
+
export { ArminComponent, AssistantMessageComponent, appKey, appKeyHint, BashExecutionComponent, BorderedLoader, BranchSummaryMessageComponent, CompactionSummaryMessageComponent, CustomEditor, CustomMessageComponent, DynamicBorder, ExtensionEditorComponent, ExtensionInputComponent, ExtensionSelectorComponent, editorKey, FooterComponent, keyHint, LoginDialogComponent, ModelSelectorComponent, OAuthSelectorComponent, rawKeyHint, renderDiff, SessionSelectorComponent, SettingsSelectorComponent, ShowImagesSelectorComponent, SkillInvocationMessageComponent, ThemeSelectorComponent, ThinkingSelectorComponent, ToolExecutionComponent, TreeSelectorComponent, truncateToVisualLines, UserMessageComponent, UserMessageSelectorComponent, } from "./modes/interactive/components/index.js";
|
|
35
|
+
// Theme utilities for custom tools and extensions
|
|
36
|
+
export { getLanguageFromPath, getMarkdownTheme, getSelectListTheme, getSettingsListTheme, highlightCode, initTheme, Theme, } from "./modes/interactive/theme/theme.js";
|
|
37
|
+
// Clipboard utilities
|
|
38
|
+
export { copyToClipboard } from "./utils/clipboard.js";
|
|
39
|
+
export { parseFrontmatter, stripFrontmatter } from "./utils/frontmatter.js";
|
|
40
|
+
// Shell utilities
|
|
41
|
+
export { getShellConfig } from "./utils/shell.js";
|