tinker-agent 1.3.0 → 1.5.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 +39 -1
- package/README.md +271 -72
- package/bin/tinker.js +75 -25
- package/package.json +12 -3
- package/src/agent/runtime-session.ts +113 -15
- package/src/cli/command-line.ts +291 -0
- package/src/cli/config.ts +158 -262
- package/src/cli/index.ts +33 -21
- package/src/cli/main.ts +213 -0
- package/src/cli/model-profiles.ts +226 -72
- package/src/cli/output.ts +113 -0
- package/src/cli/package-metadata.ts +36 -0
- package/src/cli/prompt-source.ts +229 -0
- package/src/cli/public-cli-contract.ts +69 -0
- package/src/cli/public-config-contract.ts +732 -0
- package/src/cli/run-runner.ts +17 -12
- package/src/cli/runner-dependencies.ts +108 -0
- package/src/cli/tui-memory.ts +67 -0
- package/src/cli/tui-runner.tsx +79 -49
- package/src/context/context-policy.ts +2 -2
- package/src/events/stdout-event-printer.ts +1 -0
- package/src/mcp/mcp-manager.ts +2 -19
- package/src/mcp/mcp-tool-executor.ts +3 -4
- package/src/memory/contracts.ts +148 -0
- package/src/memory/embedding-client.ts +105 -0
- package/src/memory/memory-coordinator.ts +556 -0
- package/src/memory/memory-extractor.ts +231 -0
- package/src/memory/memory-log.ts +88 -0
- package/src/memory/memory-search-tool.ts +100 -0
- package/src/memory/memory-store.ts +687 -0
- package/src/memory/vector.ts +153 -0
- package/src/model/fake-model-client.ts +971 -3
- package/src/model/model-context-profile.ts +0 -30
- package/src/observation/observation-builder.ts +20 -0
- package/src/session/session-store.ts +123 -0
- package/src/tools/bash.ts +8 -25
- package/src/tools/grep.ts +9 -1
- package/src/tools/registry.ts +19 -1
- package/src/tools/ripgrep.ts +24 -27
- package/src/tools/types.ts +16 -0
- package/src/tools/web-fetch/index.ts +2 -15
- package/src/tui/app.tsx +72 -2
- package/src/tui/clipboard.ts +22 -0
- package/src/tui/components/footer.tsx +9 -4
- package/src/tui/components/memory-browser.tsx +151 -0
- package/src/tui/components/prompt-input.tsx +6 -3
- package/src/tui/event-store.ts +9 -2
- package/src/tui/slash-commands.ts +88 -24
- package/src/tui/workspace-file-search.ts +78 -71
|
@@ -14,21 +14,6 @@ export type ModelContextBudget = ModelContextProfile & {
|
|
|
14
14
|
triggerTokens: number;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
export function readModelContextProfileFromEnv(
|
|
18
|
-
env: NodeJS.ProcessEnv = process.env,
|
|
19
|
-
): ModelContextProfile {
|
|
20
|
-
return createModelContextProfile({
|
|
21
|
-
contextWindowTokens: parseRequiredTokenCount(
|
|
22
|
-
env.TINKER_CONTEXT_WINDOW_TOKENS,
|
|
23
|
-
"TINKER_CONTEXT_WINDOW_TOKENS",
|
|
24
|
-
),
|
|
25
|
-
maxSupportedOutputTokens: parseRequiredTokenCount(
|
|
26
|
-
env.TINKER_MAX_SUPPORTED_OUTPUT_TOKENS,
|
|
27
|
-
"TINKER_MAX_SUPPORTED_OUTPUT_TOKENS",
|
|
28
|
-
),
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
17
|
export function createModelContextProfile(
|
|
33
18
|
input: ModelContextProfile,
|
|
34
19
|
): ModelContextProfile {
|
|
@@ -102,21 +87,6 @@ export function assertMatchingContextBudget(
|
|
|
102
87
|
}
|
|
103
88
|
}
|
|
104
89
|
|
|
105
|
-
function parseRequiredTokenCount(value: string | undefined, name: string): number {
|
|
106
|
-
if (value === undefined || value.trim() === "") {
|
|
107
|
-
throw new Error(`${name} is required; received ${displayValue(value)}.`);
|
|
108
|
-
}
|
|
109
|
-
if (!/^\d+$/.test(value)) {
|
|
110
|
-
throw new Error(
|
|
111
|
-
`${name} must be a positive safe integer token count; received ${displayValue(value)}.`,
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const parsed = Number(value);
|
|
116
|
-
requirePositiveSafeInteger(parsed, name, value);
|
|
117
|
-
return parsed;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
90
|
function requirePositiveSafeInteger(
|
|
121
91
|
value: number,
|
|
122
92
|
name: string,
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
GenericToolRawResult,
|
|
6
6
|
GlobRawResult,
|
|
7
7
|
GrepRawResult,
|
|
8
|
+
MemorySearchRawResult,
|
|
8
9
|
McpToolRawResult,
|
|
9
10
|
ReadFileRawResult,
|
|
10
11
|
RecallRawResult,
|
|
@@ -33,6 +34,8 @@ export class ObservationBuilder {
|
|
|
33
34
|
return { content: renderReadObservation(input.raw) };
|
|
34
35
|
case "recall":
|
|
35
36
|
return { content: renderRecallObservation(input.raw) };
|
|
37
|
+
case "memory_search":
|
|
38
|
+
return { content: renderMemorySearchObservation(input.raw) };
|
|
36
39
|
case "skill":
|
|
37
40
|
return { content: renderSkillObservation(input.raw) };
|
|
38
41
|
case "write":
|
|
@@ -227,6 +230,23 @@ function renderRecallObservation(raw: RecallRawResult): string {
|
|
|
227
230
|
return [header, ...hits].join("\n\n");
|
|
228
231
|
}
|
|
229
232
|
|
|
233
|
+
function renderMemorySearchObservation(raw: MemorySearchRawResult): string {
|
|
234
|
+
if (!raw.ok) {
|
|
235
|
+
return `MemorySearch unavailable: ${raw.error}`;
|
|
236
|
+
}
|
|
237
|
+
if (raw.matches.length === 0) {
|
|
238
|
+
return "MemorySearch found no stored memories.";
|
|
239
|
+
}
|
|
240
|
+
const header = `MemorySearch returned ${raw.matches.length} derived memories. They may be stale or wrong; verify current workspace facts.`;
|
|
241
|
+
return [
|
|
242
|
+
header,
|
|
243
|
+
...raw.matches.map(
|
|
244
|
+
(match, index) =>
|
|
245
|
+
`${index + 1}. score=${match.score.toFixed(3)} created_at=${match.createdAt} workspace=${match.sourceWorkspace}\n ${match.text}`,
|
|
246
|
+
),
|
|
247
|
+
].join("\n\n");
|
|
248
|
+
}
|
|
249
|
+
|
|
230
250
|
export function renderSkillObservation(raw: SkillRawResult): string {
|
|
231
251
|
if (!raw.ok) {
|
|
232
252
|
return `Skill failed for ${raw.name || "(unknown skill)"} (${raw.errorCode}): ${raw.error}`;
|
|
@@ -144,6 +144,29 @@ export type SessionImageInputCompatibility = {
|
|
|
144
144
|
readonly tokenEstimator?: InputTokenEstimatorCompatibility;
|
|
145
145
|
};
|
|
146
146
|
|
|
147
|
+
export type CompletedTurnMessageSnapshot =
|
|
148
|
+
| {
|
|
149
|
+
readonly ordinal: number;
|
|
150
|
+
readonly role: "user";
|
|
151
|
+
readonly content: string;
|
|
152
|
+
}
|
|
153
|
+
| {
|
|
154
|
+
readonly ordinal: number;
|
|
155
|
+
readonly role: "assistant";
|
|
156
|
+
readonly content: string | null;
|
|
157
|
+
readonly reasoningContent?: string | null;
|
|
158
|
+
}
|
|
159
|
+
| {
|
|
160
|
+
readonly ordinal: number;
|
|
161
|
+
readonly role: "tool";
|
|
162
|
+
readonly name: string;
|
|
163
|
+
readonly content: string;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export type CompletedTurnSnapshot = {
|
|
167
|
+
readonly messages: readonly CompletedTurnMessageSnapshot[];
|
|
168
|
+
};
|
|
169
|
+
|
|
147
170
|
export type SessionCompatibilityContract = {
|
|
148
171
|
modelName: string;
|
|
149
172
|
profileName?: string;
|
|
@@ -2082,6 +2105,105 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
2082
2105
|
});
|
|
2083
2106
|
}
|
|
2084
2107
|
|
|
2108
|
+
readCompletedTurnSnapshot(turnId: TurnId): CompletedTurnSnapshot {
|
|
2109
|
+
this.requireOpen();
|
|
2110
|
+
const turnRow = this.database
|
|
2111
|
+
.query("SELECT status FROM turns WHERE turn_id = ?")
|
|
2112
|
+
.get(turnId);
|
|
2113
|
+
const status = enumFromSql(
|
|
2114
|
+
recordFromSql(turnRow, "completed turn").status,
|
|
2115
|
+
["open", "completed", "failed", "cancelled", "interrupted"] as const,
|
|
2116
|
+
"turn status",
|
|
2117
|
+
);
|
|
2118
|
+
if (status !== "completed") {
|
|
2119
|
+
throw new Error(`Turn ${turnId} is not completed.`);
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
const rows = this.database
|
|
2123
|
+
.query(
|
|
2124
|
+
`SELECT ordinal, role, content, reasoning_content,
|
|
2125
|
+
reasoning_content_present, name
|
|
2126
|
+
FROM messages
|
|
2127
|
+
WHERE turn_id = ?
|
|
2128
|
+
ORDER BY ordinal`,
|
|
2129
|
+
)
|
|
2130
|
+
.all(turnId);
|
|
2131
|
+
if (rows.length === 0) {
|
|
2132
|
+
throw new Error(`Completed turn ${turnId} has no messages.`);
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
let previousOrdinal = 0;
|
|
2136
|
+
const messages = rows.map((value): CompletedTurnMessageSnapshot => {
|
|
2137
|
+
const row = recordFromSql(value, "completed turn message");
|
|
2138
|
+
const ordinal = numberFromSql(row.ordinal, "completed turn ordinal");
|
|
2139
|
+
if (ordinal < 1 || ordinal <= previousOrdinal) {
|
|
2140
|
+
throw new Error("Completed turn message ordinals are invalid.");
|
|
2141
|
+
}
|
|
2142
|
+
previousOrdinal = ordinal;
|
|
2143
|
+
const role = enumFromSql(
|
|
2144
|
+
row.role,
|
|
2145
|
+
["user", "assistant", "tool"] as const,
|
|
2146
|
+
"completed turn message role",
|
|
2147
|
+
);
|
|
2148
|
+
if (role === "user") {
|
|
2149
|
+
if (
|
|
2150
|
+
row.reasoning_content !== null ||
|
|
2151
|
+
numberFromSql(row.reasoning_content_present, "reasoning_content_present") !==
|
|
2152
|
+
0 ||
|
|
2153
|
+
row.name !== null
|
|
2154
|
+
) {
|
|
2155
|
+
throw new Error("Completed user message fields are invalid.");
|
|
2156
|
+
}
|
|
2157
|
+
return Object.freeze({
|
|
2158
|
+
ordinal,
|
|
2159
|
+
role,
|
|
2160
|
+
content: stringFromSql(row.content, "completed user content"),
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
if (role === "assistant") {
|
|
2164
|
+
if (row.name !== null) {
|
|
2165
|
+
throw new Error("Completed assistant message name must be null.");
|
|
2166
|
+
}
|
|
2167
|
+
const reasoningPresent = numberFromSql(
|
|
2168
|
+
row.reasoning_content_present,
|
|
2169
|
+
"reasoning_content_present",
|
|
2170
|
+
);
|
|
2171
|
+
if (reasoningPresent !== 0 && reasoningPresent !== 1) {
|
|
2172
|
+
throw new Error("reasoning_content_present must be 0 or 1.");
|
|
2173
|
+
}
|
|
2174
|
+
if (reasoningPresent === 0 && row.reasoning_content !== null) {
|
|
2175
|
+
throw new Error("Absent assistant reasoning content must be null.");
|
|
2176
|
+
}
|
|
2177
|
+
return Object.freeze({
|
|
2178
|
+
ordinal,
|
|
2179
|
+
role,
|
|
2180
|
+
content: nullableTextFromSql(row.content, "completed assistant content"),
|
|
2181
|
+
...(reasoningPresent === 0
|
|
2182
|
+
? {}
|
|
2183
|
+
: {
|
|
2184
|
+
reasoningContent: nullableTextFromSql(
|
|
2185
|
+
row.reasoning_content,
|
|
2186
|
+
"completed assistant reasoning content",
|
|
2187
|
+
),
|
|
2188
|
+
}),
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
if (
|
|
2192
|
+
row.reasoning_content !== null ||
|
|
2193
|
+
numberFromSql(row.reasoning_content_present, "reasoning_content_present") !== 0
|
|
2194
|
+
) {
|
|
2195
|
+
throw new Error("Completed tool message reasoning fields are invalid.");
|
|
2196
|
+
}
|
|
2197
|
+
return Object.freeze({
|
|
2198
|
+
ordinal,
|
|
2199
|
+
role,
|
|
2200
|
+
name: stringFromSql(row.name, "completed tool name"),
|
|
2201
|
+
content: stringFromSql(row.content, "completed tool content"),
|
|
2202
|
+
});
|
|
2203
|
+
});
|
|
2204
|
+
return Object.freeze({ messages: Object.freeze(messages) });
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2085
2207
|
loadProtocolView(): ProtocolContextView {
|
|
2086
2208
|
this.requireOpen();
|
|
2087
2209
|
const imageAttachments = loadMessageImageAttachments(this.database);
|
|
@@ -4725,6 +4847,7 @@ export function decodeStoredToolRawResult(value: unknown): ToolRawResult {
|
|
|
4725
4847
|
"web_search",
|
|
4726
4848
|
"web_fetch",
|
|
4727
4849
|
"recall",
|
|
4850
|
+
"memory_search",
|
|
4728
4851
|
"skill",
|
|
4729
4852
|
"mcp",
|
|
4730
4853
|
"generic",
|
package/src/tools/bash.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { buildOutputSnapshotFromText } from "./task-output-snapshot";
|
|
|
11
11
|
import { defineToolExecutor } from "./types";
|
|
12
12
|
import type { TaskOutputSnapshot } from "./task-output";
|
|
13
13
|
import type { BashRawResult, ToolExecutionContext, ToolExecutor } from "./types";
|
|
14
|
+
import { DEFAULT_PUBLIC_TOOLING_CONFIG } from "../cli/public-config-contract";
|
|
14
15
|
|
|
15
16
|
type BashArgs = {
|
|
16
17
|
command: string;
|
|
@@ -27,25 +28,16 @@ export type BashToolOptions = {
|
|
|
27
28
|
maxTimeoutMs?: number;
|
|
28
29
|
};
|
|
29
30
|
|
|
30
|
-
const defaultForegroundTimeoutMs = 5_000;
|
|
31
|
-
const defaultMaxForegroundTimeoutMs = 600_000;
|
|
32
|
-
|
|
33
31
|
export function createBashToolExecutor(options: BashToolOptions): ToolExecutor {
|
|
34
32
|
const maxTimeoutMs =
|
|
35
|
-
options.maxTimeoutMs ??
|
|
36
|
-
parsePositiveInteger(
|
|
37
|
-
process.env.TINKER_BASH_MAX_TIMEOUT_MS,
|
|
38
|
-
defaultMaxForegroundTimeoutMs,
|
|
39
|
-
);
|
|
33
|
+
options.maxTimeoutMs ?? DEFAULT_PUBLIC_TOOLING_CONFIG.bashMaxTimeoutMs;
|
|
40
34
|
const defaultTimeoutMs =
|
|
41
|
-
options.defaultTimeoutMs ??
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
defaultForegroundTimeoutMs,
|
|
46
|
-
),
|
|
47
|
-
maxTimeoutMs,
|
|
35
|
+
options.defaultTimeoutMs ?? DEFAULT_PUBLIC_TOOLING_CONFIG.bashDefaultTimeoutMs;
|
|
36
|
+
if (defaultTimeoutMs > maxTimeoutMs) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Bash default timeout must not exceed max timeout; received ${defaultTimeoutMs} > ${maxTimeoutMs}.`,
|
|
48
39
|
);
|
|
40
|
+
}
|
|
49
41
|
|
|
50
42
|
return defineToolExecutor("bash", {
|
|
51
43
|
definition: {
|
|
@@ -161,7 +153,7 @@ export function createBashToolExecutor(options: BashToolOptions): ToolExecutor {
|
|
|
161
153
|
|
|
162
154
|
export function parseBashArgs(
|
|
163
155
|
args: unknown,
|
|
164
|
-
maxTimeoutMs =
|
|
156
|
+
maxTimeoutMs = DEFAULT_PUBLIC_TOOLING_CONFIG.bashMaxTimeoutMs,
|
|
165
157
|
): { ok: true; value: BashArgs } | { ok: false; error: string } {
|
|
166
158
|
if (!isRecord(args)) {
|
|
167
159
|
return { ok: false, error: "Bash arguments must be an object." };
|
|
@@ -401,15 +393,6 @@ function parseOptionalTimeout(
|
|
|
401
393
|
return { ok: true, value };
|
|
402
394
|
}
|
|
403
395
|
|
|
404
|
-
function parsePositiveInteger(value: string | undefined, fallback: number): number {
|
|
405
|
-
if (value === undefined) {
|
|
406
|
-
return fallback;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const parsed = Number(value);
|
|
410
|
-
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
396
|
function lastPipelineCommandName(command: string): string | undefined {
|
|
414
397
|
const segments = command.split("|");
|
|
415
398
|
const lastSegment = segments.at(-1)?.trim();
|
package/src/tools/grep.ts
CHANGED
|
@@ -32,6 +32,11 @@ type GrepArgs = {
|
|
|
32
32
|
export type GrepToolOptions = {
|
|
33
33
|
workspaceRoot: string;
|
|
34
34
|
cwdState: CwdState;
|
|
35
|
+
ripgrep?: {
|
|
36
|
+
command?: string;
|
|
37
|
+
timeoutMs?: number;
|
|
38
|
+
maxBufferBytes?: number;
|
|
39
|
+
};
|
|
35
40
|
};
|
|
36
41
|
|
|
37
42
|
const ignoredDirectories = [
|
|
@@ -174,7 +179,10 @@ export function createGrepToolExecutor(options: GrepToolOptions): ToolExecutor {
|
|
|
174
179
|
}
|
|
175
180
|
|
|
176
181
|
const rgArgs = buildRipgrepArgs(input, mode, absoluteSearchPath);
|
|
177
|
-
const rg = await ripGrep(rgArgs, {
|
|
182
|
+
const rg = await ripGrep(rgArgs, {
|
|
183
|
+
signal: context.signal,
|
|
184
|
+
...options.ripgrep,
|
|
185
|
+
});
|
|
178
186
|
|
|
179
187
|
if (!rg.ok) {
|
|
180
188
|
return grepFailure({
|
package/src/tools/registry.ts
CHANGED
|
@@ -31,6 +31,10 @@ import { ToolExecutionFatalError } from "./types";
|
|
|
31
31
|
import type { SkillCatalogSnapshot } from "../skills/skill-loader";
|
|
32
32
|
import type { SkillActivationCoordinator } from "../skills/skill-context";
|
|
33
33
|
import { createSkillToolExecutor } from "../skills/skill-tool";
|
|
34
|
+
import {
|
|
35
|
+
DEFAULT_PUBLIC_TOOLING_CONFIG,
|
|
36
|
+
type PublicToolingConfig,
|
|
37
|
+
} from "../cli/public-config-contract";
|
|
34
38
|
|
|
35
39
|
export class ToolRegistry {
|
|
36
40
|
private readonly tools = new Map<string, ToolExecutor>();
|
|
@@ -128,10 +132,13 @@ export function createDefaultTooling(options: {
|
|
|
128
132
|
taskStopGraceMs?: number;
|
|
129
133
|
skillCatalog?: SkillCatalogSnapshot;
|
|
130
134
|
skillCoordinator?: SkillActivationCoordinator;
|
|
135
|
+
toolingConfig?: PublicToolingConfig;
|
|
136
|
+
memorySearch?: ToolExecutor;
|
|
131
137
|
}): DefaultTooling {
|
|
132
138
|
const snapshots: FileSnapshotStore = new Map();
|
|
133
139
|
const registry = new ToolRegistry();
|
|
134
140
|
const runtimeSession = options.runtimeSession;
|
|
141
|
+
const toolingConfig = options.toolingConfig ?? DEFAULT_PUBLIC_TOOLING_CONFIG;
|
|
135
142
|
const cwdState = createCwdState(options.workspaceRoot);
|
|
136
143
|
const taskManager = new ShellTaskManager({
|
|
137
144
|
workspaceRoot: options.workspaceRoot,
|
|
@@ -149,6 +156,11 @@ export function createDefaultTooling(options: {
|
|
|
149
156
|
createGrepToolExecutor({
|
|
150
157
|
workspaceRoot: options.workspaceRoot,
|
|
151
158
|
cwdState,
|
|
159
|
+
ripgrep: {
|
|
160
|
+
command: toolingConfig.ripgrepPath,
|
|
161
|
+
timeoutMs: toolingConfig.grepTimeoutMs,
|
|
162
|
+
maxBufferBytes: toolingConfig.grepMaxBufferBytes,
|
|
163
|
+
},
|
|
152
164
|
}),
|
|
153
165
|
);
|
|
154
166
|
registry.register(
|
|
@@ -159,6 +171,9 @@ export function createDefaultTooling(options: {
|
|
|
159
171
|
}),
|
|
160
172
|
);
|
|
161
173
|
registry.register(createRecallToolExecutor({ historyReader: options.historyReader }));
|
|
174
|
+
if (options.memorySearch !== undefined) {
|
|
175
|
+
registry.register(options.memorySearch);
|
|
176
|
+
}
|
|
162
177
|
if (options.skillCatalog !== undefined) {
|
|
163
178
|
if (options.skillCatalog.skills.size === 0) {
|
|
164
179
|
throw new Error("An empty Agent Skill catalog must not register tooling.");
|
|
@@ -192,13 +207,15 @@ export function createDefaultTooling(options: {
|
|
|
192
207
|
workspaceRoot: options.workspaceRoot,
|
|
193
208
|
cwdState,
|
|
194
209
|
taskManager,
|
|
210
|
+
defaultTimeoutMs: toolingConfig.bashDefaultTimeoutMs,
|
|
211
|
+
maxTimeoutMs: toolingConfig.bashMaxTimeoutMs,
|
|
195
212
|
}),
|
|
196
213
|
);
|
|
197
214
|
registry.register(createTaskListToolExecutor({ taskManager }));
|
|
198
215
|
registry.register(createTaskOutputToolExecutor({ taskManager }));
|
|
199
216
|
registry.register(createTaskStopToolExecutor({ taskManager }));
|
|
200
217
|
|
|
201
|
-
const exaApiKey = options.exaApiKey ??
|
|
218
|
+
const exaApiKey = options.exaApiKey ?? toolingConfig.exaApiKey;
|
|
202
219
|
const hasExaKey = exaApiKey !== undefined && exaApiKey.trim() !== "";
|
|
203
220
|
|
|
204
221
|
if (hasExaKey) {
|
|
@@ -209,6 +226,7 @@ export function createDefaultTooling(options: {
|
|
|
209
226
|
createWebFetchToolExecutor({
|
|
210
227
|
exaApiKey: hasExaKey ? exaApiKey : undefined,
|
|
211
228
|
refiner: options.webFetchRefiner,
|
|
229
|
+
refineThreshold: toolingConfig.webFetchRefineThreshold,
|
|
212
230
|
}),
|
|
213
231
|
);
|
|
214
232
|
|
package/src/tools/ripgrep.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
-
import { rgPath } from "@vscode/ripgrep";
|
|
3
2
|
import { cancellationError, throwIfTurnCancelled } from "../agent/turn-cancellation";
|
|
3
|
+
import { DEFAULT_PUBLIC_TOOLING_CONFIG } from "../cli/public-config-contract";
|
|
4
4
|
|
|
5
5
|
export const RIPGREP_MISSING_ERROR =
|
|
6
6
|
"Tinker's bundled ripgrep executable is unavailable. Reinstall tinker-agent.";
|
|
7
7
|
|
|
8
|
-
const defaultTimeoutMs = 20_000;
|
|
9
|
-
const defaultMaxBufferBytes = 20_000_000;
|
|
10
|
-
|
|
11
8
|
export type RipgrepResult = {
|
|
12
9
|
ok: boolean;
|
|
13
10
|
lines: string[];
|
|
@@ -18,12 +15,13 @@ export type RipgrepResult = {
|
|
|
18
15
|
|
|
19
16
|
export type RipgrepOptions = {
|
|
20
17
|
signal: AbortSignal;
|
|
18
|
+
command?: string;
|
|
21
19
|
timeoutMs?: number;
|
|
22
20
|
maxBufferBytes?: number;
|
|
23
21
|
};
|
|
24
22
|
|
|
25
|
-
export function findRipgrepCommand(): string {
|
|
26
|
-
return
|
|
23
|
+
export function findRipgrepCommand(command?: string): string {
|
|
24
|
+
return command ?? DEFAULT_PUBLIC_TOOLING_CONFIG.ripgrepPath;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
export async function ripGrep(
|
|
@@ -31,21 +29,28 @@ export async function ripGrep(
|
|
|
31
29
|
options: RipgrepOptions,
|
|
32
30
|
): Promise<RipgrepResult> {
|
|
33
31
|
throwIfTurnCancelled(options.signal);
|
|
34
|
-
const timeoutMs =
|
|
35
|
-
options.timeoutMs ??
|
|
36
|
-
parsePositiveInteger(process.env.TINKER_GREP_TIMEOUT_MS, defaultTimeoutMs);
|
|
32
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_PUBLIC_TOOLING_CONFIG.grepTimeoutMs;
|
|
37
33
|
const maxBufferBytes =
|
|
38
|
-
options.maxBufferBytes ??
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
options.maxBufferBytes ?? DEFAULT_PUBLIC_TOOLING_CONFIG.grepMaxBufferBytes;
|
|
35
|
+
const command = findRipgrepCommand(options.command);
|
|
36
|
+
|
|
37
|
+
const first = await runRipgrep(
|
|
38
|
+
command,
|
|
39
|
+
args,
|
|
40
|
+
timeoutMs,
|
|
41
|
+
maxBufferBytes,
|
|
42
|
+
options.signal,
|
|
43
|
+
);
|
|
45
44
|
if (first.retryWithSingleThread) {
|
|
46
45
|
throwIfTurnCancelled(options.signal);
|
|
47
46
|
return finalizeResult(
|
|
48
|
-
await runRipgrep(
|
|
47
|
+
await runRipgrep(
|
|
48
|
+
command,
|
|
49
|
+
["-j", "1", ...args],
|
|
50
|
+
timeoutMs,
|
|
51
|
+
maxBufferBytes,
|
|
52
|
+
options.signal,
|
|
53
|
+
),
|
|
49
54
|
);
|
|
50
55
|
}
|
|
51
56
|
|
|
@@ -62,6 +67,7 @@ type RipgrepAttempt = {
|
|
|
62
67
|
};
|
|
63
68
|
|
|
64
69
|
function runRipgrep(
|
|
70
|
+
command: string,
|
|
65
71
|
args: string[],
|
|
66
72
|
timeoutMs: number,
|
|
67
73
|
maxBufferBytes: number,
|
|
@@ -74,7 +80,7 @@ function runRipgrep(
|
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
execFile(
|
|
77
|
-
|
|
83
|
+
command,
|
|
78
84
|
args,
|
|
79
85
|
{ timeout: timeoutMs, maxBuffer: maxBufferBytes, signal },
|
|
80
86
|
(error, stdout, stderr) => {
|
|
@@ -210,12 +216,3 @@ function isEagainError(
|
|
|
210
216
|
stderr.includes("Resource temporarily unavailable")
|
|
211
217
|
);
|
|
212
218
|
}
|
|
213
|
-
|
|
214
|
-
function parsePositiveInteger(value: string | undefined, fallback: number): number {
|
|
215
|
-
if (value === undefined || value.trim() === "") {
|
|
216
|
-
return fallback;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const parsed = Number(value);
|
|
220
|
-
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
221
|
-
}
|
package/src/tools/types.ts
CHANGED
|
@@ -245,6 +245,21 @@ export type RecallGetRawResult =
|
|
|
245
245
|
|
|
246
246
|
export type RecallRawResult = RecallSearchRawResult | RecallGetRawResult;
|
|
247
247
|
|
|
248
|
+
export type MemorySearchRawResult =
|
|
249
|
+
| {
|
|
250
|
+
ok: true;
|
|
251
|
+
matches: readonly {
|
|
252
|
+
text: string;
|
|
253
|
+
score: number;
|
|
254
|
+
sourceWorkspace: string;
|
|
255
|
+
createdAt: string;
|
|
256
|
+
}[];
|
|
257
|
+
}
|
|
258
|
+
| {
|
|
259
|
+
ok: false;
|
|
260
|
+
error: string;
|
|
261
|
+
};
|
|
262
|
+
|
|
248
263
|
export type SkillRawResult =
|
|
249
264
|
| {
|
|
250
265
|
ok: true;
|
|
@@ -307,6 +322,7 @@ export type ToolRawResultByKind = {
|
|
|
307
322
|
web_search: WebSearchRawResult;
|
|
308
323
|
web_fetch: WebFetchRawResult;
|
|
309
324
|
recall: RecallRawResult;
|
|
325
|
+
memory_search: MemorySearchRawResult;
|
|
310
326
|
skill: SkillRawResult;
|
|
311
327
|
mcp: McpToolRawResult;
|
|
312
328
|
generic: GenericToolRawResult;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cancellationError, throwIfTurnCancelled } from "../../agent/turn-cancellation";
|
|
2
|
+
import { DEFAULT_PUBLIC_TOOLING_CONFIG } from "../../cli/public-config-contract";
|
|
2
3
|
import { defineToolExecutor } from "../types";
|
|
3
4
|
import type { ToolExecutionContext, ToolExecutor, WebFetchRawResult } from "../types";
|
|
4
5
|
import type { WebFetchBackend, WebFetchBackendResult, WebFetchRoute } from "./backend";
|
|
@@ -25,7 +26,6 @@ export type WebFetchToolOptions = {
|
|
|
25
26
|
browserBackend?: WebFetchBackend | false;
|
|
26
27
|
};
|
|
27
28
|
|
|
28
|
-
export const WEB_FETCH_DEFAULT_REFINE_THRESHOLD = 2000;
|
|
29
29
|
export const WEB_FETCH_DEFAULT_CACHE_TTL_MS = 15 * 60 * 1000;
|
|
30
30
|
|
|
31
31
|
type CacheEntry = {
|
|
@@ -50,11 +50,7 @@ export function createWebFetchToolExecutor(
|
|
|
50
50
|
: (options.browserBackend ??
|
|
51
51
|
(isBrowserBackendAvailable() ? createBrowserWebFetchBackend() : undefined));
|
|
52
52
|
const refineThreshold =
|
|
53
|
-
options.refineThreshold ??
|
|
54
|
-
parsePositiveInteger(
|
|
55
|
-
process.env.TINKER_WEBFETCH_REFINE_THRESHOLD,
|
|
56
|
-
WEB_FETCH_DEFAULT_REFINE_THRESHOLD,
|
|
57
|
-
);
|
|
53
|
+
options.refineThreshold ?? DEFAULT_PUBLIC_TOOLING_CONFIG.webFetchRefineThreshold;
|
|
58
54
|
const cacheTtlMs = options.cacheTtlMs ?? WEB_FETCH_DEFAULT_CACHE_TTL_MS;
|
|
59
55
|
const cache = new Map<string, CacheEntry>();
|
|
60
56
|
|
|
@@ -284,15 +280,6 @@ function pruneCache(cache: Map<string, CacheEntry>): void {
|
|
|
284
280
|
}
|
|
285
281
|
}
|
|
286
282
|
|
|
287
|
-
function parsePositiveInteger(value: string | undefined, fallback: number): number {
|
|
288
|
-
if (value === undefined || value.trim() === "") {
|
|
289
|
-
return fallback;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const parsed = Number(value);
|
|
293
|
-
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
283
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
297
284
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
298
285
|
}
|