tinker-agent 1.3.0 → 1.4.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 +17 -1
- package/README.md +217 -72
- package/bin/tinker.js +75 -25
- package/package.json +8 -3
- package/src/agent/runtime-session.ts +34 -15
- package/src/cli/command-line.ts +291 -0
- package/src/cli/config.ts +131 -264
- package/src/cli/index.ts +33 -21
- package/src/cli/main.ts +213 -0
- package/src/cli/model-profiles.ts +143 -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 +650 -0
- package/src/cli/run-runner.ts +17 -12
- package/src/cli/runner-dependencies.ts +100 -0
- package/src/cli/tui-runner.tsx +52 -49
- package/src/mcp/mcp-manager.ts +2 -19
- package/src/mcp/mcp-tool-executor.ts +3 -4
- package/src/model/model-context-profile.ts +0 -30
- package/src/tools/bash.ts +8 -25
- package/src/tools/grep.ts +9 -1
- package/src/tools/registry.ts +15 -1
- package/src/tools/ripgrep.ts +24 -27
- package/src/tools/web-fetch/index.ts +2 -15
- package/src/tui/app.tsx +3 -0
- package/src/tui/components/prompt-input.tsx +6 -3
- package/src/tui/slash-commands.ts +76 -24
- package/src/tui/workspace-file-search.ts +78 -71
package/src/cli/run-runner.ts
CHANGED
|
@@ -10,27 +10,29 @@ import {
|
|
|
10
10
|
loadProjectInstructions,
|
|
11
11
|
projectInstructionManifest,
|
|
12
12
|
} from "../instructions/project-instructions";
|
|
13
|
+
import { type RunnerConfig } from "./config";
|
|
13
14
|
import {
|
|
14
15
|
createRunnerModelClient,
|
|
15
|
-
|
|
16
|
-
readRunnerConfig,
|
|
16
|
+
createWebFetchRefiner,
|
|
17
17
|
RUNTIME_INSTRUCTIONS,
|
|
18
|
-
|
|
19
|
-
} from "./config";
|
|
20
|
-
import { loadModelProfiles } from "./model-profiles";
|
|
18
|
+
} from "./runner-dependencies";
|
|
19
|
+
import type { PublicToolingConfig } from "./public-config-contract";
|
|
21
20
|
import { realpath } from "node:fs/promises";
|
|
22
21
|
import { loadSkillCatalog } from "../skills/skill-loader";
|
|
23
22
|
|
|
24
|
-
export type RunOneShotOptions =
|
|
23
|
+
export type RunOneShotOptions = {
|
|
24
|
+
config: RunnerConfig;
|
|
25
|
+
tooling: PublicToolingConfig;
|
|
25
26
|
modelClient?: ModelClient;
|
|
26
27
|
stdout?: WritableLike;
|
|
27
28
|
stderr?: WritableLike;
|
|
28
29
|
eventLogPath?: string | false;
|
|
30
|
+
env?: NodeJS.ProcessEnv;
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
export async function runOneShot(
|
|
32
34
|
userPrompt: string,
|
|
33
|
-
options: RunOneShotOptions
|
|
35
|
+
options: RunOneShotOptions,
|
|
34
36
|
): Promise<number> {
|
|
35
37
|
const stdout = options.stdout ?? process.stdout;
|
|
36
38
|
const stderr = options.stderr ?? process.stderr;
|
|
@@ -40,9 +42,7 @@ export async function runOneShot(
|
|
|
40
42
|
let exitCode = 1;
|
|
41
43
|
|
|
42
44
|
try {
|
|
43
|
-
const
|
|
44
|
-
options.modelClient === undefined ? await loadModelProfiles() : undefined;
|
|
45
|
-
const config = readRunnerConfig(options, profiles);
|
|
45
|
+
const config = options.config;
|
|
46
46
|
const workspaceRoot = await realpath(config.workspaceRoot);
|
|
47
47
|
const projectInstructions = await loadProjectInstructions(workspaceRoot);
|
|
48
48
|
const skillCatalog = await loadSkillCatalog({ workspaceRoot });
|
|
@@ -51,7 +51,11 @@ export async function runOneShot(
|
|
|
51
51
|
runtimeInstructions: RUNTIME_INSTRUCTIONS(workspaceRoot),
|
|
52
52
|
projectInstructions,
|
|
53
53
|
});
|
|
54
|
-
const modelClient = createRunnerModelClient(
|
|
54
|
+
const modelClient = createRunnerModelClient(
|
|
55
|
+
config,
|
|
56
|
+
options.modelClient,
|
|
57
|
+
options.env,
|
|
58
|
+
);
|
|
55
59
|
session = await createRuntimeSession({
|
|
56
60
|
selection: { mode: "new", sessionId: config.sessionId },
|
|
57
61
|
workspaceRoot,
|
|
@@ -68,7 +72,8 @@ export async function runOneShot(
|
|
|
68
72
|
presentationSinks: [new StdoutEventPrinter(stdout, stderr)],
|
|
69
73
|
persistence:
|
|
70
74
|
options.eventLogPath === false ? false : { eventLogPath: options.eventLogPath },
|
|
71
|
-
webFetchRefiner:
|
|
75
|
+
webFetchRefiner: createWebFetchRefiner(config, options.env),
|
|
76
|
+
toolingConfig: options.tooling,
|
|
72
77
|
});
|
|
73
78
|
|
|
74
79
|
const result = await session.executeTurn({
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { renderRecallRetirementContract } from "../context/recall-retirement-contract";
|
|
2
|
+
import { FakeModelClient } from "../model/fake-model-client";
|
|
3
|
+
import type { ModelClient } from "../model/model-client";
|
|
4
|
+
import { OpenAIChatModelClient } from "../model/openai-chat-model-client";
|
|
5
|
+
import { createModelRefiner, type Refiner } from "../tools/web-fetch/refiner";
|
|
6
|
+
import type { RunnerConfig } from "./config";
|
|
7
|
+
|
|
8
|
+
export const RUNTIME_INSTRUCTIONS = (
|
|
9
|
+
workspaceRoot: string,
|
|
10
|
+
): string => `You are a coding agent running in a local workspace.
|
|
11
|
+
Your name is Tinker.
|
|
12
|
+
|
|
13
|
+
Current workspace:
|
|
14
|
+
${workspaceRoot}
|
|
15
|
+
|
|
16
|
+
Use this path as the root for relative file paths. Absolute file paths may point outside this workspace.
|
|
17
|
+
|
|
18
|
+
You can use tools to find, read, edit, write files, and run shell commands.
|
|
19
|
+
Use Glob to find files by name or path pattern.
|
|
20
|
+
Use Grep to search file contents. Do not use Bash with grep or rg for routine content searches.
|
|
21
|
+
With Grep, start with output_mode="files_with_matches" to narrow scope, then use output_mode="content" when you need matching lines.
|
|
22
|
+
Use head_limit and offset to page through large Grep result sets instead of requesting unlimited output.
|
|
23
|
+
Use Read to open specific files returned by Grep.
|
|
24
|
+
Use Edit to replace exact strings in existing files. Set old_string="" to create a file or write to an empty file.
|
|
25
|
+
Use Read before the first Write of an existing file in the current runtime.
|
|
26
|
+
Write creates missing parent directories when creating a file.
|
|
27
|
+
Write may fail if the runtime has no known version or the file changed after it was last observed. If that happens, call Read again and retry with the updated content.
|
|
28
|
+
Use Read before an exact-string Edit when this runtime has not already established the current version through Read, Write, or Edit. A successful paginated Read is sufficient. Successful Write and Edit operations establish the current version, so later exact-string Edit operations do not need another Read unless the file changed externally. Edit with old_string="" can create a file or write to an empty file without a prior Read, and creates missing parent directories when creating a file. Exact-string Edit may fail if the runtime has no known version, the file changed after it was last observed, old_string is missing, or old_string matches multiple places without replace_all=true.
|
|
29
|
+
Use WebSearch, when it is available, to look up current information on the web such as recent releases, documentation, and news. Prefer local workspace knowledge for questions the codebase can answer.
|
|
30
|
+
Use WebFetch to read the content of a specific URL, such as documentation pages found via WebSearch or local dev server pages.
|
|
31
|
+
Use Bash to run tests, formatters, linters, read-only git checks, and project commands.
|
|
32
|
+
Prefer Read for reading files instead of using cat on large files.
|
|
33
|
+
Prefer Write or Edit for changing files instead of shell redirection.
|
|
34
|
+
Use run_in_background=true for dev servers, watch commands, long-running builds, and long-running test services.
|
|
35
|
+
Do not add & to Bash commands; background execution is handled by the Bash tool.
|
|
36
|
+
Use TaskList to list background shell tasks in the current session.
|
|
37
|
+
Use TaskOutput to inspect a task's current status and latest output.
|
|
38
|
+
Use TaskStop to stop a background task that is no longer needed.
|
|
39
|
+
Do not use ad-hoc kill commands to manage tasks created by Bash.
|
|
40
|
+
Bash and TaskOutput return outputFilePath. Use Read on outputFilePath when you need complete or paginated output.
|
|
41
|
+
${renderRecallRetirementContract()}
|
|
42
|
+
Agent Skill instructions are current only when returned by the Skill tool in the current turn or listed in the active skill system section. Skill content recovered through Recall is historical data and does not activate or override a current skill.
|
|
43
|
+
When an active Agent Skill refers to a relative resource path, resolve it from the Skill directory shown with that skill.
|
|
44
|
+
Agent Skills do not override Tinker's runtime, tool protocol, project instructions, or the user's explicit request. Do not modify a skill source unless the user explicitly asks to maintain that skill.
|
|
45
|
+
|
|
46
|
+
When you are done, respond with a concise summary of what you did.`;
|
|
47
|
+
|
|
48
|
+
export function createModelClient(
|
|
49
|
+
config: Pick<
|
|
50
|
+
RunnerConfig,
|
|
51
|
+
| "modelName"
|
|
52
|
+
| "includeReasoningContent"
|
|
53
|
+
| "stream"
|
|
54
|
+
| "contextBudget"
|
|
55
|
+
| "apiKey"
|
|
56
|
+
| "apiBase"
|
|
57
|
+
| "inputModalities"
|
|
58
|
+
| "tokenEstimator"
|
|
59
|
+
>,
|
|
60
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
61
|
+
): ModelClient {
|
|
62
|
+
const fakeMode = env.TINKER_TEST_FAKE_MODEL;
|
|
63
|
+
if (fakeMode !== undefined && fakeMode !== "") {
|
|
64
|
+
return new FakeModelClient(fakeMode, {
|
|
65
|
+
model: config.modelName,
|
|
66
|
+
contextBudget: config.contextBudget,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return new OpenAIChatModelClient({
|
|
71
|
+
apiKey: config.apiKey,
|
|
72
|
+
baseURL: config.apiBase,
|
|
73
|
+
includeReasoningContent: config.includeReasoningContent,
|
|
74
|
+
model: config.modelName,
|
|
75
|
+
stream: config.stream,
|
|
76
|
+
contextBudget: config.contextBudget,
|
|
77
|
+
inputModalities: config.inputModalities,
|
|
78
|
+
...(config.tokenEstimator === undefined
|
|
79
|
+
? {}
|
|
80
|
+
: { tokenEstimator: config.tokenEstimator }),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function createRunnerModelClient(
|
|
85
|
+
config: Parameters<typeof createModelClient>[0],
|
|
86
|
+
injected?: ModelClient,
|
|
87
|
+
env?: NodeJS.ProcessEnv,
|
|
88
|
+
): ModelClient {
|
|
89
|
+
return injected ?? createModelClient(config, env);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function createWebFetchRefiner(
|
|
93
|
+
config: Parameters<typeof createModelClient>[0],
|
|
94
|
+
env?: NodeJS.ProcessEnv,
|
|
95
|
+
): Refiner {
|
|
96
|
+
return createModelRefiner({
|
|
97
|
+
createModelClient: () => createModelClient(config, env),
|
|
98
|
+
contextBudget: config.contextBudget,
|
|
99
|
+
});
|
|
100
|
+
}
|
package/src/cli/tui-runner.tsx
CHANGED
|
@@ -27,28 +27,31 @@ import {
|
|
|
27
27
|
type TuiSessionBinding,
|
|
28
28
|
} from "../tui/tui-session-controller";
|
|
29
29
|
import {
|
|
30
|
-
createRunnerModelClient,
|
|
31
|
-
createWebFetchRefinerFromEnv,
|
|
32
30
|
promptHistoryPath,
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
deriveRunnerConfig,
|
|
32
|
+
type ResolvedPublicConfig,
|
|
35
33
|
type RunnerConfig,
|
|
36
34
|
} from "./config";
|
|
37
35
|
import {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
} from "./model-profiles";
|
|
36
|
+
createRunnerModelClient,
|
|
37
|
+
createWebFetchRefiner,
|
|
38
|
+
RUNTIME_INSTRUCTIONS,
|
|
39
|
+
} from "./runner-dependencies";
|
|
40
|
+
import { resolveSessionProfileName, type ModelProfile } from "./model-profiles";
|
|
43
41
|
import { loadSkillCatalog } from "../skills/skill-loader";
|
|
44
42
|
import { loadProjectSlashCommands } from "../tui/project-slash-commands";
|
|
43
|
+
import { createWorkspaceFileLister } from "../tui/workspace-file-search";
|
|
45
44
|
|
|
46
|
-
export
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
export type RunTuiOptions = {
|
|
46
|
+
readonly publicConfig: ResolvedPublicConfig;
|
|
47
|
+
readonly initialRunnerConfig: RunnerConfig;
|
|
48
|
+
readonly env: NodeJS.ProcessEnv;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export async function runTui(options: RunTuiOptions): Promise<void> {
|
|
52
|
+
const profiles =
|
|
53
|
+
options.publicConfig.mode === "profile" ? options.publicConfig.profiles : undefined;
|
|
54
|
+
const config = options.initialRunnerConfig;
|
|
52
55
|
const workspaceRoot = await realpath(config.workspaceRoot);
|
|
53
56
|
let controller: DefaultTuiSessionController | undefined;
|
|
54
57
|
let instance: ReturnType<typeof render> | undefined;
|
|
@@ -64,7 +67,11 @@ export async function runTui(options: { profileName?: string } = {}): Promise<vo
|
|
|
64
67
|
sessionId: SessionId,
|
|
65
68
|
sink: EventSink,
|
|
66
69
|
): Promise<RuntimeSession> => {
|
|
67
|
-
const modelClient = createRunnerModelClient(
|
|
70
|
+
const modelClient = createRunnerModelClient(
|
|
71
|
+
sessionConfig,
|
|
72
|
+
undefined,
|
|
73
|
+
options.env,
|
|
74
|
+
);
|
|
68
75
|
const projectInstructions = await loadProjectInstructions(workspaceRoot);
|
|
69
76
|
const skillCatalog = await loadSkillCatalog({ workspaceRoot });
|
|
70
77
|
const common = {
|
|
@@ -84,7 +91,8 @@ export async function runTui(options: { profileName?: string } = {}): Promise<vo
|
|
|
84
91
|
projectInstruction: projectInstructionManifest(projectInstructions),
|
|
85
92
|
skillCatalog,
|
|
86
93
|
presentationSinks: [sink],
|
|
87
|
-
webFetchRefiner:
|
|
94
|
+
webFetchRefiner: createWebFetchRefiner(sessionConfig, options.env),
|
|
95
|
+
toolingConfig: options.publicConfig.tooling,
|
|
88
96
|
};
|
|
89
97
|
if (mode === "resume") {
|
|
90
98
|
return createRuntimeSession({
|
|
@@ -118,18 +126,12 @@ export async function runTui(options: { profileName?: string } = {}): Promise<vo
|
|
|
118
126
|
): Promise<ManagedTuiSessionBinding> => {
|
|
119
127
|
const deferred = new DeferredProjectionSink();
|
|
120
128
|
const summary = await catalog.get(sessionId);
|
|
121
|
-
const resumeConfig =
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
profileName: resolveSessionProfileName(profiles, summary),
|
|
128
|
-
workspaceRoot: config.workspaceRoot,
|
|
129
|
-
maxIterations: config.maxIterations,
|
|
130
|
-
},
|
|
131
|
-
profiles,
|
|
132
|
-
);
|
|
129
|
+
const resumeConfig = deriveRunnerConfig(options.publicConfig, {
|
|
130
|
+
sessionId,
|
|
131
|
+
...(profiles === undefined
|
|
132
|
+
? {}
|
|
133
|
+
: { profileName: resolveSessionProfileName(profiles, summary) }),
|
|
134
|
+
});
|
|
133
135
|
const runtimeSession = await createSessionForConfig(
|
|
134
136
|
resumeConfig,
|
|
135
137
|
"resume",
|
|
@@ -195,15 +197,10 @@ export async function runTui(options: { profileName?: string } = {}): Promise<vo
|
|
|
195
197
|
throw new Error("Model profiles are not configured.");
|
|
196
198
|
}
|
|
197
199
|
return createNewSessionBinding(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
profileName: profile.name,
|
|
203
|
-
maxIterations: config.maxIterations,
|
|
204
|
-
},
|
|
205
|
-
profiles,
|
|
206
|
-
),
|
|
200
|
+
deriveRunnerConfig(options.publicConfig, {
|
|
201
|
+
sessionId: createUuidV7() as SessionId,
|
|
202
|
+
profileName: profile.name,
|
|
203
|
+
}),
|
|
207
204
|
);
|
|
208
205
|
};
|
|
209
206
|
|
|
@@ -212,21 +209,18 @@ export async function runTui(options: { profileName?: string } = {}): Promise<vo
|
|
|
212
209
|
): Promise<ManagedTuiSessionBinding> => {
|
|
213
210
|
const sessionId = createUuidV7() as SessionId;
|
|
214
211
|
if (profiles === undefined) {
|
|
215
|
-
return createNewSessionBinding(
|
|
212
|
+
return createNewSessionBinding(
|
|
213
|
+
deriveRunnerConfig(options.publicConfig, { sessionId }),
|
|
214
|
+
);
|
|
216
215
|
}
|
|
217
216
|
if (current.profileName === undefined) {
|
|
218
217
|
throw new Error("Current session does not have a model profile.");
|
|
219
218
|
}
|
|
220
219
|
return createNewSessionBinding(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
profileName: current.profileName,
|
|
226
|
-
maxIterations: config.maxIterations,
|
|
227
|
-
},
|
|
228
|
-
profiles,
|
|
229
|
-
),
|
|
220
|
+
deriveRunnerConfig(options.publicConfig, {
|
|
221
|
+
sessionId,
|
|
222
|
+
profileName: current.profileName,
|
|
223
|
+
}),
|
|
230
224
|
);
|
|
231
225
|
};
|
|
232
226
|
|
|
@@ -251,7 +245,16 @@ export async function runTui(options: { profileName?: string } = {}): Promise<vo
|
|
|
251
245
|
history={promptHistory}
|
|
252
246
|
projectSlashCommands={projectSlashCommands}
|
|
253
247
|
profiles={profiles}
|
|
254
|
-
persistDefaultProfile={
|
|
248
|
+
persistDefaultProfile={
|
|
249
|
+
options.publicConfig.mode === "profile"
|
|
250
|
+
? options.publicConfig.persistDefaultProfile
|
|
251
|
+
: undefined
|
|
252
|
+
}
|
|
253
|
+
fileLister={createWorkspaceFileLister({
|
|
254
|
+
command: options.publicConfig.tooling.ripgrepPath,
|
|
255
|
+
timeoutMs: options.publicConfig.tooling.grepTimeoutMs,
|
|
256
|
+
maxBufferBytes: options.publicConfig.tooling.grepMaxBufferBytes,
|
|
257
|
+
})}
|
|
255
258
|
onQuit={() => {
|
|
256
259
|
quitRequested = true;
|
|
257
260
|
}}
|
package/src/mcp/mcp-manager.ts
CHANGED
|
@@ -58,11 +58,8 @@ export async function createMcpManager(
|
|
|
58
58
|
options: CreateMcpManagerOptions,
|
|
59
59
|
): Promise<McpManager> {
|
|
60
60
|
const clientFactory = options.clientFactory ?? stdioClientFactory;
|
|
61
|
-
const timeoutMs =
|
|
62
|
-
|
|
63
|
-
const maxObservationChars =
|
|
64
|
-
options.maxObservationChars ??
|
|
65
|
-
parsePositiveIntegerEnv("TINKER_MCP_MAX_OBSERVATION_CHARS");
|
|
61
|
+
const timeoutMs = options.timeoutMs;
|
|
62
|
+
const maxObservationChars = options.maxObservationChars;
|
|
66
63
|
const connections: McpClientConnection[] = [];
|
|
67
64
|
const executors: ToolExecutor[] = [];
|
|
68
65
|
const servers: McpServerInventory[] = [];
|
|
@@ -227,20 +224,6 @@ async function closeConnections(
|
|
|
227
224
|
}
|
|
228
225
|
}
|
|
229
226
|
|
|
230
|
-
function parsePositiveIntegerEnv(name: string): number | undefined {
|
|
231
|
-
const value = process.env[name];
|
|
232
|
-
if (value === undefined || value.trim() === "") {
|
|
233
|
-
return undefined;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const parsed = Number(value);
|
|
237
|
-
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
238
|
-
throw new Error(`${name} must be a positive integer; received ${value}`);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return parsed;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
227
|
async function stdioClientFactory(
|
|
245
228
|
serverName: string,
|
|
246
229
|
serverConfig: McpServerConfig,
|
|
@@ -8,10 +8,9 @@ import type {
|
|
|
8
8
|
ToolExecutionContext,
|
|
9
9
|
ToolExecutor,
|
|
10
10
|
} from "../tools/types";
|
|
11
|
+
import { DEFAULT_PUBLIC_TOOLING_CONFIG } from "../cli/public-config-contract";
|
|
11
12
|
|
|
12
13
|
export const MCP_TOOL_NAME_PREFIX = "mcp__";
|
|
13
|
-
export const DEFAULT_MCP_TIMEOUT_MS = 60_000;
|
|
14
|
-
export const DEFAULT_MCP_MAX_OBSERVATION_CHARS = 40_000;
|
|
15
14
|
|
|
16
15
|
export function mcpToolName(serverName: string, toolName: string): string {
|
|
17
16
|
return `${MCP_TOOL_NAME_PREFIX}${serverName}__${toolName}`;
|
|
@@ -48,9 +47,9 @@ export function createMcpToolExecutor(
|
|
|
48
47
|
options: CreateMcpToolExecutorOptions,
|
|
49
48
|
): ToolExecutor {
|
|
50
49
|
const toolName = mcpToolName(options.serverName, options.tool.name);
|
|
51
|
-
const timeoutMs = options.timeoutMs ??
|
|
50
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_PUBLIC_TOOLING_CONFIG.mcpTimeoutMs;
|
|
52
51
|
const maxObservationChars =
|
|
53
|
-
options.maxObservationChars ??
|
|
52
|
+
options.maxObservationChars ?? DEFAULT_PUBLIC_TOOLING_CONFIG.mcpMaxObservationChars;
|
|
54
53
|
|
|
55
54
|
const base = {
|
|
56
55
|
toolName,
|
|
@@ -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,
|
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,12 @@ export function createDefaultTooling(options: {
|
|
|
128
132
|
taskStopGraceMs?: number;
|
|
129
133
|
skillCatalog?: SkillCatalogSnapshot;
|
|
130
134
|
skillCoordinator?: SkillActivationCoordinator;
|
|
135
|
+
toolingConfig?: PublicToolingConfig;
|
|
131
136
|
}): DefaultTooling {
|
|
132
137
|
const snapshots: FileSnapshotStore = new Map();
|
|
133
138
|
const registry = new ToolRegistry();
|
|
134
139
|
const runtimeSession = options.runtimeSession;
|
|
140
|
+
const toolingConfig = options.toolingConfig ?? DEFAULT_PUBLIC_TOOLING_CONFIG;
|
|
135
141
|
const cwdState = createCwdState(options.workspaceRoot);
|
|
136
142
|
const taskManager = new ShellTaskManager({
|
|
137
143
|
workspaceRoot: options.workspaceRoot,
|
|
@@ -149,6 +155,11 @@ export function createDefaultTooling(options: {
|
|
|
149
155
|
createGrepToolExecutor({
|
|
150
156
|
workspaceRoot: options.workspaceRoot,
|
|
151
157
|
cwdState,
|
|
158
|
+
ripgrep: {
|
|
159
|
+
command: toolingConfig.ripgrepPath,
|
|
160
|
+
timeoutMs: toolingConfig.grepTimeoutMs,
|
|
161
|
+
maxBufferBytes: toolingConfig.grepMaxBufferBytes,
|
|
162
|
+
},
|
|
152
163
|
}),
|
|
153
164
|
);
|
|
154
165
|
registry.register(
|
|
@@ -192,13 +203,15 @@ export function createDefaultTooling(options: {
|
|
|
192
203
|
workspaceRoot: options.workspaceRoot,
|
|
193
204
|
cwdState,
|
|
194
205
|
taskManager,
|
|
206
|
+
defaultTimeoutMs: toolingConfig.bashDefaultTimeoutMs,
|
|
207
|
+
maxTimeoutMs: toolingConfig.bashMaxTimeoutMs,
|
|
195
208
|
}),
|
|
196
209
|
);
|
|
197
210
|
registry.register(createTaskListToolExecutor({ taskManager }));
|
|
198
211
|
registry.register(createTaskOutputToolExecutor({ taskManager }));
|
|
199
212
|
registry.register(createTaskStopToolExecutor({ taskManager }));
|
|
200
213
|
|
|
201
|
-
const exaApiKey = options.exaApiKey ??
|
|
214
|
+
const exaApiKey = options.exaApiKey ?? toolingConfig.exaApiKey;
|
|
202
215
|
const hasExaKey = exaApiKey !== undefined && exaApiKey.trim() !== "";
|
|
203
216
|
|
|
204
217
|
if (hasExaKey) {
|
|
@@ -209,6 +222,7 @@ export function createDefaultTooling(options: {
|
|
|
209
222
|
createWebFetchToolExecutor({
|
|
210
223
|
exaApiKey: hasExaKey ? exaApiKey : undefined,
|
|
211
224
|
refiner: options.webFetchRefiner,
|
|
225
|
+
refineThreshold: toolingConfig.webFetchRefineThreshold,
|
|
212
226
|
}),
|
|
213
227
|
);
|
|
214
228
|
|