@ottocode/sdk 0.1.298 → 0.1.300
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/package.json +2 -2
- package/src/core/src/index.ts +1 -0
- package/src/core/src/providers/resolver.ts +5 -0
- package/src/core/src/tools/builtin/fs/read.ts +15 -3
- package/src/core/src/tools/builtin/fs/read.txt +2 -0
- package/src/core/src/tools/builtin/patch/repair.ts +22 -7
- package/src/core/src/tools/builtin/patch.ts +15 -2
- package/src/core/src/tools/builtin/ripgrep.ts +80 -25
- package/src/core/src/tools/builtin/ripgrep.txt +2 -0
- package/src/core/src/tools/builtin/shell.ts +116 -11
- package/src/core/src/tools/builtin/shell.txt +4 -2
- package/src/core/src/tools/lazy/registry.ts +11 -5
- package/src/index.ts +7 -1
- package/src/providers/src/catalog-manual.ts +55 -0
- package/src/providers/src/catalog.ts +265 -2823
- package/src/providers/src/index.ts +6 -1
- package/src/providers/src/model-catalog-cache.ts +5 -0
- package/src/providers/src/oauth-models.ts +13 -1
- package/src/providers/src/utils.ts +3 -0
- package/src/providers/src/validate.ts +5 -1
- package/src/providers/src/xai-client.ts +50 -3
- package/src/types/src/provider.ts +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ottocode/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.300",
|
|
4
4
|
"description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
|
|
5
5
|
"author": "nitishxyz",
|
|
6
6
|
"license": "MIT",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"@modelcontextprotocol/sdk": "^1.12",
|
|
103
103
|
"@openauthjs/openauth": "^0.4.3",
|
|
104
104
|
"@openrouter/ai-sdk-provider": "^1.2.0",
|
|
105
|
-
"@ottorouter/ai-sdk": "0.2.
|
|
105
|
+
"@ottorouter/ai-sdk": "0.2.6",
|
|
106
106
|
"@solana/web3.js": "^1.98.0",
|
|
107
107
|
"ai": "^6.0.170",
|
|
108
108
|
"ai-sdk-ollama": "^3.8.3",
|
package/src/core/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
createXaiModel,
|
|
14
14
|
createZaiCodingModel,
|
|
15
15
|
createZaiModel,
|
|
16
|
+
isXaiGrokCliModel,
|
|
16
17
|
normalizeOllamaBaseURL,
|
|
17
18
|
resolveOpenAIResponsesModel,
|
|
18
19
|
shouldUseOpenAIResponsesApi,
|
|
@@ -195,10 +196,14 @@ export async function resolveModel(
|
|
|
195
196
|
}
|
|
196
197
|
|
|
197
198
|
if (provider === 'xai') {
|
|
199
|
+
if (isXaiGrokCliModel(model) && !config.oauth) {
|
|
200
|
+
throw new Error('Grok Build and Grok Composer 2.5 require xAI OAuth.');
|
|
201
|
+
}
|
|
198
202
|
return createXaiModel(model, {
|
|
199
203
|
apiKey: config.oauth?.access ?? config.apiKey,
|
|
200
204
|
baseURL: config.baseURL,
|
|
201
205
|
useResponses: !!config.oauth,
|
|
206
|
+
useGrokCliProxy: !!config.oauth && isXaiGrokCliModel(model),
|
|
202
207
|
});
|
|
203
208
|
}
|
|
204
209
|
|
|
@@ -81,15 +81,25 @@ export function buildReadTool(projectRoot: string): {
|
|
|
81
81
|
.describe(
|
|
82
82
|
'Ending line number (1-indexed, inclusive). Required if startLine is provided.',
|
|
83
83
|
),
|
|
84
|
+
maxLines: z
|
|
85
|
+
.number()
|
|
86
|
+
.int()
|
|
87
|
+
.min(1)
|
|
88
|
+
.optional()
|
|
89
|
+
.describe(
|
|
90
|
+
'Number of lines to read starting at startLine. Ignored when endLine is provided.',
|
|
91
|
+
),
|
|
84
92
|
}),
|
|
85
93
|
async execute({
|
|
86
94
|
path,
|
|
87
95
|
startLine,
|
|
88
96
|
endLine,
|
|
97
|
+
maxLines,
|
|
89
98
|
}: {
|
|
90
99
|
path: string;
|
|
91
100
|
startLine?: number;
|
|
92
101
|
endLine?: number;
|
|
102
|
+
maxLines?: number;
|
|
93
103
|
}): Promise<ToolResponse<ReadResult>> {
|
|
94
104
|
if (!path || path.trim().length === 0) {
|
|
95
105
|
return createToolError(
|
|
@@ -111,10 +121,12 @@ export function buildReadTool(projectRoot: string): {
|
|
|
111
121
|
const indent = detectIndentation(content);
|
|
112
122
|
await rememberFileRead(projectRoot, abs);
|
|
113
123
|
|
|
114
|
-
if (startLine !== undefined
|
|
124
|
+
if (startLine !== undefined) {
|
|
115
125
|
const lines = content.split('\n');
|
|
126
|
+
const requestedEndLine =
|
|
127
|
+
endLine ?? startLine + Math.max(1, maxLines ?? 1) - 1;
|
|
116
128
|
const start = Math.max(1, startLine) - 1;
|
|
117
|
-
const end = Math.min(lines.length,
|
|
129
|
+
const end = Math.min(lines.length, requestedEndLine);
|
|
118
130
|
const selectedLines = lines.slice(start, end);
|
|
119
131
|
content = selectedLines.join('\n');
|
|
120
132
|
const result: ReadResult = {
|
|
@@ -122,7 +134,7 @@ export function buildReadTool(projectRoot: string): {
|
|
|
122
134
|
path: req,
|
|
123
135
|
content,
|
|
124
136
|
size: content.length,
|
|
125
|
-
lineRange: `@${startLine}-${
|
|
137
|
+
lineRange: `@${startLine}-${requestedEndLine}`,
|
|
126
138
|
totalLines: lines.length,
|
|
127
139
|
};
|
|
128
140
|
if (indent) result.indentation = indent;
|
|
@@ -6,3 +6,5 @@
|
|
|
6
6
|
Usage tips:
|
|
7
7
|
- Prefer relative project paths when possible (more portable)
|
|
8
8
|
- For large files or searches, use the Grep or Ripgrep tool
|
|
9
|
+
- Use startLine/endLine or startLine/maxLines for targeted reads of large files
|
|
10
|
+
- If startLine is provided without endLine or maxLines, only that one line is read
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
PATCH_ADD_PREFIX,
|
|
3
|
+
PATCH_BEGIN_MARKER,
|
|
4
|
+
PATCH_DELETE_LINES_PREFIX,
|
|
5
|
+
PATCH_DELETE_PREFIX,
|
|
6
|
+
PATCH_END_MARKER,
|
|
7
|
+
PATCH_INSERT_AFTER_PREFIX,
|
|
8
|
+
PATCH_INSERT_BEFORE_PREFIX,
|
|
9
|
+
PATCH_REPLACE_LINES_PREFIX,
|
|
10
|
+
PATCH_REPLACE_PREFIX,
|
|
11
|
+
PATCH_UPDATE_PREFIX,
|
|
12
|
+
} from './constants.ts';
|
|
2
13
|
|
|
3
14
|
export function repairPatchContent(patch: string): string {
|
|
4
15
|
patch = extractPatchFromWrappedJson(patch);
|
|
@@ -35,12 +46,16 @@ function appendMissingEndMarker(patch: string): string {
|
|
|
35
46
|
if (trimmed.includes(PATCH_END_MARKER)) return patch;
|
|
36
47
|
|
|
37
48
|
const hasContent =
|
|
38
|
-
trimmed.includes(
|
|
39
|
-
trimmed.includes(
|
|
40
|
-
trimmed.includes(
|
|
41
|
-
trimmed.includes(
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
trimmed.includes(PATCH_UPDATE_PREFIX) ||
|
|
50
|
+
trimmed.includes(PATCH_ADD_PREFIX) ||
|
|
51
|
+
trimmed.includes(PATCH_DELETE_PREFIX) ||
|
|
52
|
+
trimmed.includes(PATCH_REPLACE_PREFIX) ||
|
|
53
|
+
trimmed.includes(PATCH_DELETE_LINES_PREFIX) ||
|
|
54
|
+
trimmed.includes(PATCH_REPLACE_LINES_PREFIX) ||
|
|
55
|
+
trimmed.includes(PATCH_INSERT_BEFORE_PREFIX) ||
|
|
56
|
+
trimmed.includes(PATCH_INSERT_AFTER_PREFIX);
|
|
57
|
+
|
|
58
|
+
if (hasContent || trimmed.trim() === PATCH_BEGIN_MARKER) {
|
|
44
59
|
return `${trimmed}\n${PATCH_END_MARKER}`;
|
|
45
60
|
}
|
|
46
61
|
|
|
@@ -110,11 +110,24 @@ export function buildApplyPatchTool(projectRoot: string): {
|
|
|
110
110
|
const message = error instanceof Error ? error.message : String(error);
|
|
111
111
|
return createToolError(message, 'validation', {
|
|
112
112
|
parameter: 'patch',
|
|
113
|
-
suggestion:
|
|
114
|
-
'
|
|
113
|
+
suggestion: message.includes('Missing "*** End Patch"')
|
|
114
|
+
? 'The patch tool automatically appends a missing end marker when real patch operations are present. This input appears incomplete; resend the full patch body, not just the begin marker.'
|
|
115
|
+
: 'Provide patch content using the enveloped format (*** Begin Patch ... *** End Patch) or standard unified diff format (---/+++ headers).',
|
|
115
116
|
});
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
if (operations.length === 0) {
|
|
120
|
+
return createToolError(
|
|
121
|
+
'Patch contains no operations. Do not retry the same empty patch; include at least one Add/Update/Delete/Replace directive between the begin and end markers.',
|
|
122
|
+
'validation',
|
|
123
|
+
{
|
|
124
|
+
parameter: 'patch',
|
|
125
|
+
suggestion:
|
|
126
|
+
'Provide a complete patch body such as *** Replace in: path, *** Find:, *** With:, then *** End Patch.',
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
118
131
|
try {
|
|
119
132
|
const result = await applyPatchOperations(projectRoot, operations, {
|
|
120
133
|
useFuzzy: fuzzyMatch,
|
|
@@ -42,6 +42,9 @@ export function buildRipgrepTool(projectRoot: string): {
|
|
|
42
42
|
ToolResponse<{
|
|
43
43
|
count: number;
|
|
44
44
|
matches: Array<{ file: string; line: number; text: string }>;
|
|
45
|
+
truncated?: boolean;
|
|
46
|
+
shownMatches?: number;
|
|
47
|
+
files?: Array<{ file: string; matches: number }>;
|
|
45
48
|
}>
|
|
46
49
|
> {
|
|
47
50
|
function expandTilde(p: string) {
|
|
@@ -54,7 +57,14 @@ export function buildRipgrepTool(projectRoot: string): {
|
|
|
54
57
|
const p = expandTilde(String(path ?? '.')).trim();
|
|
55
58
|
const isAbs = p.startsWith('/') || /^[A-Za-z]:[\\/]/.test(p);
|
|
56
59
|
const target = p ? (isAbs ? p : join(projectRoot, p)) : projectRoot;
|
|
57
|
-
const args = [
|
|
60
|
+
const args = [
|
|
61
|
+
'--no-heading',
|
|
62
|
+
'--line-number',
|
|
63
|
+
'--color=never',
|
|
64
|
+
'--max-columns',
|
|
65
|
+
'240',
|
|
66
|
+
'--max-columns-preview',
|
|
67
|
+
];
|
|
58
68
|
if (ignoreCase) args.push('-i');
|
|
59
69
|
if (Array.isArray(glob)) for (const g of glob) args.push('-g', g);
|
|
60
70
|
args.push('--max-count', String(maxResults));
|
|
@@ -65,11 +75,72 @@ export function buildRipgrepTool(projectRoot: string): {
|
|
|
65
75
|
const rgBin = await resolveBinary('rg');
|
|
66
76
|
return await new Promise((resolve) => {
|
|
67
77
|
const proc = spawn(rgBin, args, { cwd: projectRoot });
|
|
68
|
-
let stdout = '';
|
|
69
78
|
let stderr = '';
|
|
79
|
+
let pendingLine = '';
|
|
80
|
+
let truncated = false;
|
|
81
|
+
let settled = false;
|
|
82
|
+
const TEXT_MAX = 200;
|
|
83
|
+
const matches: Array<{ file: string; line: number; text: string }> =
|
|
84
|
+
[];
|
|
85
|
+
const fileCounts = new Map<string, number>();
|
|
86
|
+
|
|
87
|
+
const parseLine = (lineText: string) => {
|
|
88
|
+
if (!lineText || matches.length >= maxResults) return;
|
|
89
|
+
const m = lineText.match(/^(.+?):(\d+):(.*)$/s);
|
|
90
|
+
const match = (() => {
|
|
91
|
+
if (!m) {
|
|
92
|
+
return {
|
|
93
|
+
file: '',
|
|
94
|
+
line: 0,
|
|
95
|
+
text:
|
|
96
|
+
lineText.length > TEXT_MAX
|
|
97
|
+
? `${lineText.slice(0, TEXT_MAX)}…`
|
|
98
|
+
: lineText,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const file = m[1];
|
|
102
|
+
const line = Number.parseInt(m[2], 10);
|
|
103
|
+
const raw = m[3];
|
|
104
|
+
const text =
|
|
105
|
+
raw.length > TEXT_MAX ? `${raw.slice(0, TEXT_MAX)}…` : raw;
|
|
106
|
+
return { file, line, text };
|
|
107
|
+
})();
|
|
108
|
+
matches.push(match);
|
|
109
|
+
if (match.file) {
|
|
110
|
+
fileCounts.set(match.file, (fileCounts.get(match.file) ?? 0) + 1);
|
|
111
|
+
}
|
|
112
|
+
if (matches.length >= maxResults) {
|
|
113
|
+
truncated = true;
|
|
114
|
+
proc.kill('SIGTERM');
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const resolveSuccess = () => {
|
|
119
|
+
if (settled) return;
|
|
120
|
+
settled = true;
|
|
121
|
+
const files = Array.from(fileCounts.entries()).map(
|
|
122
|
+
([file, count]) => ({ file, matches: count }),
|
|
123
|
+
);
|
|
124
|
+
resolve({
|
|
125
|
+
ok: true,
|
|
126
|
+
count: matches.length,
|
|
127
|
+
matches,
|
|
128
|
+
...(truncated
|
|
129
|
+
? { truncated: true, shownMatches: matches.length }
|
|
130
|
+
: {}),
|
|
131
|
+
...(files.length ? { files } : {}),
|
|
132
|
+
});
|
|
133
|
+
};
|
|
70
134
|
|
|
71
135
|
proc.stdout.on('data', (data) => {
|
|
72
|
-
|
|
136
|
+
if (matches.length >= maxResults) return;
|
|
137
|
+
pendingLine += data.toString();
|
|
138
|
+
const lines = pendingLine.split('\n');
|
|
139
|
+
pendingLine = lines.pop() ?? '';
|
|
140
|
+
for (const line of lines) {
|
|
141
|
+
parseLine(line);
|
|
142
|
+
if (matches.length >= maxResults) break;
|
|
143
|
+
}
|
|
73
144
|
});
|
|
74
145
|
|
|
75
146
|
proc.stderr.on('data', (data) => {
|
|
@@ -77,7 +148,9 @@ export function buildRipgrepTool(projectRoot: string): {
|
|
|
77
148
|
});
|
|
78
149
|
|
|
79
150
|
proc.on('close', (code) => {
|
|
80
|
-
if (
|
|
151
|
+
if (pendingLine && matches.length < maxResults)
|
|
152
|
+
parseLine(pendingLine);
|
|
153
|
+
if (!truncated && code !== 0 && code !== 1) {
|
|
81
154
|
resolve(
|
|
82
155
|
createToolError(
|
|
83
156
|
stderr.trim() || 'ripgrep failed',
|
|
@@ -91,30 +164,12 @@ export function buildRipgrepTool(projectRoot: string): {
|
|
|
91
164
|
return;
|
|
92
165
|
}
|
|
93
166
|
|
|
94
|
-
|
|
95
|
-
.split('\n')
|
|
96
|
-
.filter(Boolean)
|
|
97
|
-
.slice(0, maxResults);
|
|
98
|
-
const TEXT_MAX = 200;
|
|
99
|
-
const matches = lines.map((l) => {
|
|
100
|
-
const m = l.match(/^(.+?):(\d+):(.*)$/s);
|
|
101
|
-
if (!m)
|
|
102
|
-
return {
|
|
103
|
-
file: '',
|
|
104
|
-
line: 0,
|
|
105
|
-
text: l.length > TEXT_MAX ? `${l.slice(0, TEXT_MAX)}…` : l,
|
|
106
|
-
};
|
|
107
|
-
const file = m[1];
|
|
108
|
-
const line = Number.parseInt(m[2], 10);
|
|
109
|
-
const raw = m[3];
|
|
110
|
-
const text =
|
|
111
|
-
raw.length > TEXT_MAX ? `${raw.slice(0, TEXT_MAX)}…` : raw;
|
|
112
|
-
return { file, line, text };
|
|
113
|
-
});
|
|
114
|
-
resolve({ ok: true, count: matches.length, matches });
|
|
167
|
+
resolveSuccess();
|
|
115
168
|
});
|
|
116
169
|
|
|
117
170
|
proc.on('error', (err) => {
|
|
171
|
+
if (settled) return;
|
|
172
|
+
settled = true;
|
|
118
173
|
resolve(
|
|
119
174
|
createToolError(String(err), 'execution', {
|
|
120
175
|
suggestion: 'Ensure ripgrep (rg) is installed',
|
|
@@ -8,5 +8,7 @@ This is the only content-search tool available. Use it for any text search acros
|
|
|
8
8
|
## Usage tips
|
|
9
9
|
|
|
10
10
|
- Narrow the search set with `glob` first if the pattern may match too broadly.
|
|
11
|
+
- Prefer narrow `path` and `glob` values over repo-wide searches.
|
|
12
|
+
- Keep `maxResults` low for broad searches; the tool stops after the global limit.
|
|
11
13
|
- Batch independent searches (e.g. multiple function names) in a single turn for parallel execution.
|
|
12
14
|
- Use `ignoreCase: true` for case-insensitive matching; pass `glob` patterns (e.g. `["*.ts"]`) to limit file types.
|
|
@@ -42,9 +42,44 @@ function killProcessTree(pid: number) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export type ShellOutputMode = 'full' | 'tail';
|
|
45
|
+
export type ShellOutputMode = 'auto' | 'full' | 'tail';
|
|
46
46
|
|
|
47
47
|
const DEFAULT_TAIL_LINES = 100;
|
|
48
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 128_000;
|
|
49
|
+
|
|
50
|
+
type CompactTextResult = {
|
|
51
|
+
text: string;
|
|
52
|
+
truncated: boolean;
|
|
53
|
+
originalBytes: number;
|
|
54
|
+
shownBytes: number;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function compactTextByBytes(
|
|
58
|
+
text: string,
|
|
59
|
+
maxBytes: number,
|
|
60
|
+
label: string,
|
|
61
|
+
): CompactTextResult {
|
|
62
|
+
const originalBytes = Buffer.byteLength(text, 'utf8');
|
|
63
|
+
if (maxBytes <= 0 || originalBytes <= maxBytes) {
|
|
64
|
+
return { text, truncated: false, originalBytes, shownBytes: originalBytes };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const marker = `\n… omitted ${originalBytes - maxBytes} bytes from ${label} …\n`;
|
|
68
|
+
const markerBytes = Buffer.byteLength(marker, 'utf8');
|
|
69
|
+
const budget = Math.max(0, maxBytes - markerBytes);
|
|
70
|
+
const headBytes = Math.floor(budget / 2);
|
|
71
|
+
const tailBytes = budget - headBytes;
|
|
72
|
+
const buffer = Buffer.from(text, 'utf8');
|
|
73
|
+
const compacted = `${buffer.subarray(0, headBytes).toString('utf8')}${marker}${buffer
|
|
74
|
+
.subarray(buffer.byteLength - tailBytes)
|
|
75
|
+
.toString('utf8')}`;
|
|
76
|
+
return {
|
|
77
|
+
text: compacted,
|
|
78
|
+
truncated: true,
|
|
79
|
+
originalBytes,
|
|
80
|
+
shownBytes: Buffer.byteLength(compacted, 'utf8'),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
48
83
|
|
|
49
84
|
export function appendTailLines(
|
|
50
85
|
current: string,
|
|
@@ -66,6 +101,13 @@ type ShellResult = ToolResponse<{
|
|
|
66
101
|
stderr: string;
|
|
67
102
|
outputMode?: ShellOutputMode;
|
|
68
103
|
tailLines?: number;
|
|
104
|
+
maxOutputBytes?: number;
|
|
105
|
+
stdoutTruncated?: boolean;
|
|
106
|
+
stdoutOriginalBytes?: number;
|
|
107
|
+
stdoutShownBytes?: number;
|
|
108
|
+
stderrTruncated?: boolean;
|
|
109
|
+
stderrOriginalBytes?: number;
|
|
110
|
+
stderrShownBytes?: number;
|
|
69
111
|
}>;
|
|
70
112
|
|
|
71
113
|
type ShellStreamChunk =
|
|
@@ -110,11 +152,11 @@ const shellInputSchema = z
|
|
|
110
152
|
.default(300000)
|
|
111
153
|
.describe('Timeout in milliseconds (default: 300000 = 5 minutes)'),
|
|
112
154
|
outputMode: z
|
|
113
|
-
.enum(['full', 'tail'])
|
|
155
|
+
.enum(['auto', 'full', 'tail'])
|
|
114
156
|
.optional()
|
|
115
|
-
.default('
|
|
157
|
+
.default('auto')
|
|
116
158
|
.describe(
|
|
117
|
-
'Output capture mode. Use "full" for
|
|
159
|
+
'Output capture mode. Use "auto" for bounded output, "full" for full output up to maxOutputBytes, or "tail" to keep only the last tailLines lines.',
|
|
118
160
|
),
|
|
119
161
|
tailLines: z
|
|
120
162
|
.number()
|
|
@@ -126,6 +168,16 @@ const shellInputSchema = z
|
|
|
126
168
|
.describe(
|
|
127
169
|
'Number of trailing stdout/stderr lines to keep when outputMode is "tail"',
|
|
128
170
|
),
|
|
171
|
+
maxOutputBytes: z
|
|
172
|
+
.number()
|
|
173
|
+
.int()
|
|
174
|
+
.min(0)
|
|
175
|
+
.max(10_000_000)
|
|
176
|
+
.optional()
|
|
177
|
+
.default(DEFAULT_MAX_OUTPUT_BYTES)
|
|
178
|
+
.describe(
|
|
179
|
+
'Maximum bytes to keep per stdout/stderr in the final tool result. Use 0 to disable byte capping.',
|
|
180
|
+
),
|
|
129
181
|
})
|
|
130
182
|
.strict();
|
|
131
183
|
|
|
@@ -154,8 +206,9 @@ export function buildShellTool(projectRoot: string): {
|
|
|
154
206
|
cwd,
|
|
155
207
|
allowNonZeroExit,
|
|
156
208
|
timeout = 300000,
|
|
157
|
-
outputMode = '
|
|
209
|
+
outputMode = 'auto',
|
|
158
210
|
tailLines = DEFAULT_TAIL_LINES,
|
|
211
|
+
maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,
|
|
159
212
|
}: ShellInput,
|
|
160
213
|
options?: { abortSignal?: AbortSignal },
|
|
161
214
|
): AsyncIterable<ShellStreamChunk> | ShellResult {
|
|
@@ -179,6 +232,7 @@ export function buildShellTool(projectRoot: string): {
|
|
|
179
232
|
timeout,
|
|
180
233
|
outputMode,
|
|
181
234
|
tailLines,
|
|
235
|
+
maxOutputBytes,
|
|
182
236
|
},
|
|
183
237
|
options,
|
|
184
238
|
) as AsyncIterable<ShellStreamChunk> | ShellResult;
|
|
@@ -217,6 +271,49 @@ export function buildShellTool(projectRoot: string): {
|
|
|
217
271
|
const settle = (result: ShellResult) => {
|
|
218
272
|
if (settled) return;
|
|
219
273
|
settled = true;
|
|
274
|
+
if ('stdout' in result && 'stderr' in result) {
|
|
275
|
+
const stdoutCompact = compactTextByBytes(
|
|
276
|
+
result.stdout,
|
|
277
|
+
maxOutputBytes,
|
|
278
|
+
'shell stdout',
|
|
279
|
+
);
|
|
280
|
+
const stderrCompact = compactTextByBytes(
|
|
281
|
+
result.stderr,
|
|
282
|
+
maxOutputBytes,
|
|
283
|
+
'shell stderr',
|
|
284
|
+
);
|
|
285
|
+
result.stdout = stdoutCompact.text;
|
|
286
|
+
result.stderr = stderrCompact.text;
|
|
287
|
+
result.maxOutputBytes = maxOutputBytes;
|
|
288
|
+
if (stdoutCompact.truncated) {
|
|
289
|
+
result.stdoutTruncated = true;
|
|
290
|
+
result.stdoutOriginalBytes = stdoutCompact.originalBytes;
|
|
291
|
+
result.stdoutShownBytes = stdoutCompact.shownBytes;
|
|
292
|
+
}
|
|
293
|
+
if (stderrCompact.truncated) {
|
|
294
|
+
result.stderrTruncated = true;
|
|
295
|
+
result.stderrOriginalBytes = stderrCompact.originalBytes;
|
|
296
|
+
result.stderrShownBytes = stderrCompact.shownBytes;
|
|
297
|
+
}
|
|
298
|
+
} else if ('details' in result && result.details) {
|
|
299
|
+
const details = result.details;
|
|
300
|
+
for (const field of ['stdout', 'stderr'] as const) {
|
|
301
|
+
const value = details[field];
|
|
302
|
+
if (typeof value !== 'string') continue;
|
|
303
|
+
const compact = compactTextByBytes(
|
|
304
|
+
value,
|
|
305
|
+
maxOutputBytes,
|
|
306
|
+
`shell ${field}`,
|
|
307
|
+
);
|
|
308
|
+
details[field] = compact.text;
|
|
309
|
+
if (compact.truncated) {
|
|
310
|
+
details[`${field}Truncated`] = true;
|
|
311
|
+
details[`${field}OriginalBytes`] = compact.originalBytes;
|
|
312
|
+
details[`${field}ShownBytes`] = compact.shownBytes;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
details.maxOutputBytes = maxOutputBytes;
|
|
316
|
+
}
|
|
220
317
|
if (timeoutId) clearTimeout(timeoutId);
|
|
221
318
|
if (abortSignal) {
|
|
222
319
|
abortSignal.removeEventListener('abort', onAbort);
|
|
@@ -248,7 +345,7 @@ export function buildShellTool(projectRoot: string): {
|
|
|
248
345
|
proc.stdout?.on('data', (chunk) => {
|
|
249
346
|
const text = chunk.toString();
|
|
250
347
|
stdout =
|
|
251
|
-
outputMode === 'tail'
|
|
348
|
+
outputMode === 'tail' || outputMode === 'auto'
|
|
252
349
|
? appendTailLines(stdout, text, tailLines)
|
|
253
350
|
: `${stdout}${text}`;
|
|
254
351
|
pushDelta(text);
|
|
@@ -257,7 +354,7 @@ export function buildShellTool(projectRoot: string): {
|
|
|
257
354
|
proc.stderr?.on('data', (chunk) => {
|
|
258
355
|
const text = chunk.toString();
|
|
259
356
|
stderr =
|
|
260
|
-
outputMode === 'tail'
|
|
357
|
+
outputMode === 'tail' || outputMode === 'auto'
|
|
261
358
|
? appendTailLines(stderr, text, tailLines)
|
|
262
359
|
: `${stderr}${text}`;
|
|
263
360
|
pushDelta(text);
|
|
@@ -270,7 +367,9 @@ export function buildShellTool(projectRoot: string): {
|
|
|
270
367
|
cmd,
|
|
271
368
|
stdout,
|
|
272
369
|
stderr,
|
|
273
|
-
...(outputMode === 'tail'
|
|
370
|
+
...(outputMode === 'tail' || outputMode === 'auto'
|
|
371
|
+
? { outputMode, tailLines, maxOutputBytes }
|
|
372
|
+
: { outputMode, maxOutputBytes }),
|
|
274
373
|
}),
|
|
275
374
|
);
|
|
276
375
|
return;
|
|
@@ -286,7 +385,9 @@ export function buildShellTool(projectRoot: string): {
|
|
|
286
385
|
value: timeout,
|
|
287
386
|
stdout,
|
|
288
387
|
stderr,
|
|
289
|
-
...(outputMode === 'tail'
|
|
388
|
+
...(outputMode === 'tail' || outputMode === 'auto'
|
|
389
|
+
? { outputMode, tailLines, maxOutputBytes }
|
|
390
|
+
: { outputMode, maxOutputBytes }),
|
|
290
391
|
suggestion: 'Increase timeout or optimize the command',
|
|
291
392
|
},
|
|
292
393
|
),
|
|
@@ -303,7 +404,9 @@ export function buildShellTool(projectRoot: string): {
|
|
|
303
404
|
stdout,
|
|
304
405
|
stderr,
|
|
305
406
|
cmd,
|
|
306
|
-
...(outputMode === 'tail'
|
|
407
|
+
...(outputMode === 'tail' || outputMode === 'auto'
|
|
408
|
+
? { outputMode, tailLines, maxOutputBytes }
|
|
409
|
+
: { outputMode, maxOutputBytes }),
|
|
307
410
|
suggestion: 'Check command syntax or use allowNonZeroExit: true',
|
|
308
411
|
}),
|
|
309
412
|
);
|
|
@@ -315,7 +418,9 @@ export function buildShellTool(projectRoot: string): {
|
|
|
315
418
|
exitCode: exitCode ?? 0,
|
|
316
419
|
stdout,
|
|
317
420
|
stderr,
|
|
318
|
-
...(outputMode === 'tail'
|
|
421
|
+
...(outputMode === 'tail' || outputMode === 'auto'
|
|
422
|
+
? { outputMode, tailLines, maxOutputBytes }
|
|
423
|
+
: { outputMode, maxOutputBytes }),
|
|
319
424
|
});
|
|
320
425
|
});
|
|
321
426
|
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
## Usage tips
|
|
8
8
|
|
|
9
9
|
- Chain commands with `&&` to fail-fast.
|
|
10
|
-
- For long outputs, redirect to a file
|
|
11
|
-
-
|
|
10
|
+
- For long outputs, redirect to a file, inspect `wc -c`, then read a small range or tail.
|
|
11
|
+
- `outputMode: "auto"` is the default and keeps bounded tail output. Use `outputMode: "tail"` with `tailLines` for verbose builds/tests, or `outputMode: "full"` only when complete output is truly needed.
|
|
12
|
+
- Final `stdout`/`stderr` are capped by `maxOutputBytes` per stream to avoid huge tool results. Set `maxOutputBytes: 0` only when you intentionally need uncapped output.
|
|
13
|
+
- For binary, minified, or `strings` searches, cap line width explicitly, e.g. `rg -o '.{0,80}needle.{0,120}' | head -50`.
|
|
12
14
|
- For long-running non-interactive commands, set an appropriate `timeout` and ensure the command exits on its own.
|
|
13
15
|
- Batch independent checks (e.g. `git status && git diff`) in parallel tool calls rather than sequential shell chains when you need results separately.
|
|
14
16
|
- Never use `shell` with `sed`/`awk` for programmatic file editing — use the dedicated file-editing tools instead.
|
|
@@ -42,10 +42,16 @@ export function buildLazyToolsRecord(
|
|
|
42
42
|
return record;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export function buildLoadFirstPartyToolsTool(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
export function buildLoadFirstPartyToolsTool(allowedNames?: Iterable<string>): {
|
|
46
|
+
name: string;
|
|
47
|
+
tool: Tool;
|
|
48
|
+
} {
|
|
49
|
+
const allowed = allowedNames ? new Set(allowedNames) : null;
|
|
50
|
+
const briefs = getLazyToolDefinitions()
|
|
51
|
+
.filter(({ name }) => !allowed || allowed.has(name))
|
|
52
|
+
.map(({ name, description }) => ({
|
|
53
|
+
name,
|
|
54
|
+
description,
|
|
55
|
+
}));
|
|
50
56
|
return buildLoadToolsTool(briefs);
|
|
51
57
|
}
|
package/src/index.ts
CHANGED
|
@@ -144,7 +144,12 @@ export {
|
|
|
144
144
|
export type { AnthropicOAuthConfig } from './providers/src/index.ts';
|
|
145
145
|
export { createGoogleModel } from './providers/src/index.ts';
|
|
146
146
|
export type { GoogleProviderConfig } from './providers/src/index.ts';
|
|
147
|
-
export {
|
|
147
|
+
export {
|
|
148
|
+
createXaiModel,
|
|
149
|
+
getGrokCliHeaders,
|
|
150
|
+
isXaiGrokCliModel,
|
|
151
|
+
XAI_GROK_CLI_MODEL_IDS,
|
|
152
|
+
} from './providers/src/index.ts';
|
|
148
153
|
export type { XaiProviderConfig } from './providers/src/index.ts';
|
|
149
154
|
export { createZaiModel, createZaiCodingModel } from './providers/src/index.ts';
|
|
150
155
|
export type { ZaiProviderConfig } from './providers/src/index.ts';
|
|
@@ -306,6 +311,7 @@ export {
|
|
|
306
311
|
buildLazyToolsRecord,
|
|
307
312
|
buildLoadFirstPartyToolsTool,
|
|
308
313
|
buildSimulatorTool,
|
|
314
|
+
getLazyToolDefinitions,
|
|
309
315
|
} from './core/src/index.ts';
|
|
310
316
|
export {
|
|
311
317
|
appendCoAuthorTrailer,
|