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/config.ts
CHANGED
|
@@ -1,253 +1,180 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import type {
|
|
3
|
-
import { FakeModelClient } from "../model/fake-model-client";
|
|
4
|
-
import { OpenAIChatModelClient } from "../model/openai-chat-model-client";
|
|
2
|
+
import type { SessionId } from "../ids/runtime-id";
|
|
5
3
|
import {
|
|
4
|
+
createModelContextProfile,
|
|
6
5
|
deriveModelContextBudget,
|
|
7
|
-
readModelContextProfileFromEnv,
|
|
8
6
|
type ModelContextBudget,
|
|
9
7
|
type ModelContextProfile,
|
|
10
8
|
} from "../model/model-context-profile";
|
|
11
|
-
import { createModelRefiner, type Refiner } from "../tools/web-fetch/refiner";
|
|
12
|
-
import { createUuidV7 } from "../ids/uuid-v7";
|
|
13
|
-
import type { SessionId } from "../ids/runtime-id";
|
|
14
|
-
import { renderRecallRetirementContract } from "../context/recall-retirement-contract";
|
|
15
9
|
import {
|
|
10
|
+
loadModelProfiles,
|
|
11
|
+
persistDefaultProfile,
|
|
16
12
|
profileToContextProfile,
|
|
17
|
-
type ModelProfile,
|
|
18
13
|
type ModelInputModality,
|
|
14
|
+
type ModelProfile,
|
|
19
15
|
type ModelProfiles,
|
|
20
16
|
type ModelTokenEstimatorProfile,
|
|
21
17
|
unknownProfileError,
|
|
22
18
|
} from "./model-profiles";
|
|
19
|
+
import {
|
|
20
|
+
parsePublicEnvironment,
|
|
21
|
+
type ParsedPublicEnvironment,
|
|
22
|
+
type PublicToolingConfig,
|
|
23
|
+
} from "./public-config-contract";
|
|
23
24
|
|
|
24
|
-
export
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
export type RunnerConfig = {
|
|
26
|
+
readonly sessionId: SessionId;
|
|
27
|
+
readonly workspaceRoot: string;
|
|
28
|
+
readonly modelName: string;
|
|
29
|
+
readonly apiKey: string;
|
|
30
|
+
readonly apiBase: string;
|
|
31
|
+
readonly maxIterations: number;
|
|
32
|
+
readonly includeReasoningContent: boolean;
|
|
33
|
+
readonly stream: boolean;
|
|
34
|
+
readonly contextProfile: ModelContextProfile;
|
|
35
|
+
readonly contextBudget: ModelContextBudget;
|
|
36
|
+
readonly profileName?: string;
|
|
37
|
+
readonly inputModalities: readonly ModelInputModality[];
|
|
38
|
+
readonly tokenEstimator?: ModelTokenEstimatorProfile;
|
|
39
|
+
};
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
export type RunnerConfigSelection = {
|
|
42
|
+
readonly sessionId: SessionId;
|
|
43
|
+
readonly profileName?: string;
|
|
44
|
+
};
|
|
37
45
|
|
|
38
|
-
|
|
39
|
-
Use Glob to find files by name or path pattern.
|
|
40
|
-
Use Grep to search file contents. Do not use Bash with grep or rg for routine content searches.
|
|
41
|
-
With Grep, start with output_mode="files_with_matches" to narrow scope, then use output_mode="content" when you need matching lines.
|
|
42
|
-
Use head_limit and offset to page through large Grep result sets instead of requesting unlimited output.
|
|
43
|
-
Use Read to open specific files returned by Grep.
|
|
44
|
-
Use Edit to replace exact strings in existing files. Set old_string="" to create a file or write to an empty file.
|
|
45
|
-
Use Read before the first Write of an existing file in the current runtime.
|
|
46
|
-
Write creates missing parent directories when creating a file.
|
|
47
|
-
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.
|
|
48
|
-
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.
|
|
49
|
-
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.
|
|
50
|
-
Use WebFetch to read the content of a specific URL, such as documentation pages found via WebSearch or local dev server pages.
|
|
51
|
-
Use Bash to run tests, formatters, linters, read-only git checks, and project commands.
|
|
52
|
-
Prefer Read for reading files instead of using cat on large files.
|
|
53
|
-
Prefer Write or Edit for changing files instead of shell redirection.
|
|
54
|
-
Use run_in_background=true for dev servers, watch commands, long-running builds, and long-running test services.
|
|
55
|
-
Do not add & to Bash commands; background execution is handled by the Bash tool.
|
|
56
|
-
Use TaskList to list background shell tasks in the current session.
|
|
57
|
-
Use TaskOutput to inspect a task's current status and latest output.
|
|
58
|
-
Use TaskStop to stop a background task that is no longer needed.
|
|
59
|
-
Do not use ad-hoc kill commands to manage tasks created by Bash.
|
|
60
|
-
Bash and TaskOutput return outputFilePath. Use Read on outputFilePath when you need complete or paginated output.
|
|
61
|
-
${renderRecallRetirementContract()}
|
|
62
|
-
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.
|
|
63
|
-
When an active Agent Skill refers to a relative resource path, resolve it from the Skill directory shown with that skill.
|
|
64
|
-
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.
|
|
46
|
+
type RunnerConfigTemplate = Omit<RunnerConfig, "sessionId">;
|
|
65
47
|
|
|
66
|
-
|
|
48
|
+
export type ResolvedPublicConfig =
|
|
49
|
+
| {
|
|
50
|
+
readonly mode: "env";
|
|
51
|
+
readonly tooling: PublicToolingConfig;
|
|
52
|
+
readonly template: RunnerConfigTemplate;
|
|
53
|
+
}
|
|
54
|
+
| {
|
|
55
|
+
readonly mode: "profile";
|
|
56
|
+
readonly tooling: PublicToolingConfig;
|
|
57
|
+
readonly profiles: ModelProfiles;
|
|
58
|
+
readonly templates: ReadonlyMap<string, RunnerConfigTemplate>;
|
|
59
|
+
readonly persistDefaultProfile: (profileName: string) => Promise<void>;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export async function resolvePublicConfig(input: {
|
|
63
|
+
readonly env: NodeJS.ProcessEnv;
|
|
64
|
+
readonly cwd: string;
|
|
65
|
+
}): Promise<ResolvedPublicConfig> {
|
|
66
|
+
const environment = parsePublicEnvironment(input.env, input.cwd);
|
|
67
|
+
const profiles =
|
|
68
|
+
environment.mode === "profile"
|
|
69
|
+
? await loadModelProfiles(environment.modelsPath)
|
|
70
|
+
: undefined;
|
|
71
|
+
return createResolvedPublicConfig(environment, profiles);
|
|
72
|
+
}
|
|
67
73
|
|
|
68
|
-
export
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
74
|
+
export function createResolvedPublicConfig(
|
|
75
|
+
environment: ParsedPublicEnvironment,
|
|
76
|
+
profiles?: ModelProfiles,
|
|
77
|
+
): ResolvedPublicConfig {
|
|
78
|
+
if (environment.mode === "profile") {
|
|
79
|
+
if (profiles === undefined) {
|
|
80
|
+
throw new Error("Resolved profile-mode config requires loaded model profiles.");
|
|
81
|
+
}
|
|
82
|
+
const templates = new Map(
|
|
83
|
+
[...profiles.profiles].map(([name, profile]) => [
|
|
84
|
+
name,
|
|
85
|
+
runnerConfigTemplateFromProfile(environment, profile),
|
|
86
|
+
]),
|
|
87
|
+
);
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
mode: "profile",
|
|
90
|
+
tooling: environment.tooling,
|
|
91
|
+
profiles,
|
|
92
|
+
templates,
|
|
93
|
+
persistDefaultProfile: (profileName: string) =>
|
|
94
|
+
persistDefaultProfile(profileName, environment.modelsPath),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
84
97
|
|
|
85
|
-
|
|
98
|
+
if (profiles !== undefined) {
|
|
99
|
+
throw new Error("Env-mode config must not include model profiles.");
|
|
100
|
+
}
|
|
101
|
+
return Object.freeze({
|
|
102
|
+
mode: "env",
|
|
103
|
+
tooling: environment.tooling,
|
|
104
|
+
template: runnerConfigTemplateFromEnvironment(environment),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
86
107
|
|
|
87
|
-
export function
|
|
88
|
-
|
|
89
|
-
|
|
108
|
+
export function deriveRunnerConfig(
|
|
109
|
+
snapshot: ResolvedPublicConfig,
|
|
110
|
+
selection: RunnerConfigSelection,
|
|
90
111
|
): RunnerConfig {
|
|
91
|
-
if (
|
|
92
|
-
const profileName =
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
95
|
-
throw unknownProfileError(profileName, profiles);
|
|
112
|
+
if (snapshot.mode === "profile") {
|
|
113
|
+
const profileName = selection.profileName ?? snapshot.profiles.defaultProfile;
|
|
114
|
+
const template = snapshot.templates.get(profileName);
|
|
115
|
+
if (template === undefined) {
|
|
116
|
+
throw unknownProfileError(profileName, snapshot.profiles);
|
|
96
117
|
}
|
|
97
|
-
return
|
|
118
|
+
return runnerConfigFromTemplate(template, selection.sessionId);
|
|
98
119
|
}
|
|
99
|
-
|
|
120
|
+
|
|
121
|
+
if (selection.profileName !== undefined) {
|
|
100
122
|
throw new Error(
|
|
101
|
-
`Cannot select model profile ${JSON.stringify(
|
|
123
|
+
`Cannot select model profile ${JSON.stringify(selection.profileName)} because TINKER_MODELS is not configured.`,
|
|
102
124
|
);
|
|
103
125
|
}
|
|
104
|
-
|
|
105
|
-
return runnerConfigFromEnv(overrides);
|
|
126
|
+
return runnerConfigFromTemplate(snapshot.template, selection.sessionId);
|
|
106
127
|
}
|
|
107
128
|
|
|
108
|
-
function
|
|
129
|
+
function runnerConfigTemplateFromProfile(
|
|
130
|
+
environment: Extract<ParsedPublicEnvironment, { mode: "profile" }>,
|
|
109
131
|
profile: ModelProfile,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const contextBudget = deriveModelContextBudget(contextProfile);
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
sessionId: overrides.sessionId ?? (createUuidV7() as SessionId),
|
|
118
|
-
workspaceRoot: path.resolve(
|
|
119
|
-
overrides.workspaceRoot ?? process.env.TINKER_WORKSPACE ?? process.cwd(),
|
|
120
|
-
),
|
|
132
|
+
): RunnerConfigTemplate {
|
|
133
|
+
const contextProfile = profileToContextProfile(profile);
|
|
134
|
+
return Object.freeze({
|
|
135
|
+
workspaceRoot: environment.workspaceRoot,
|
|
121
136
|
modelName: profile.model,
|
|
122
137
|
apiKey: profile.apiKey,
|
|
123
138
|
apiBase: profile.apiBase,
|
|
124
|
-
maxIterations:
|
|
125
|
-
overrides.maxIterations ??
|
|
126
|
-
parsePositiveInteger(
|
|
127
|
-
process.env.TINKER_MAX_ITERATIONS,
|
|
128
|
-
DEFAULT_MAX_ITERATIONS,
|
|
129
|
-
"TINKER_MAX_ITERATIONS",
|
|
130
|
-
),
|
|
139
|
+
maxIterations: environment.maxIterations,
|
|
131
140
|
includeReasoningContent: profile.includeReasoningContent,
|
|
132
141
|
stream: profile.stream,
|
|
133
142
|
contextProfile,
|
|
134
|
-
contextBudget,
|
|
143
|
+
contextBudget: deriveModelContextBudget(contextProfile),
|
|
135
144
|
profileName: profile.name,
|
|
136
|
-
profiles,
|
|
137
145
|
inputModalities: profile.inputModalities,
|
|
138
146
|
...(profile.tokenEstimator === undefined
|
|
139
147
|
? {}
|
|
140
148
|
: { tokenEstimator: profile.tokenEstimator }),
|
|
141
|
-
};
|
|
149
|
+
});
|
|
142
150
|
}
|
|
143
151
|
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const contextProfile =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
workspaceRoot:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
maxIterations:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
process.env.TINKER_MAX_ITERATIONS,
|
|
160
|
-
DEFAULT_MAX_ITERATIONS,
|
|
161
|
-
"TINKER_MAX_ITERATIONS",
|
|
162
|
-
),
|
|
163
|
-
includeReasoningContent:
|
|
164
|
-
overrides.includeReasoningContent ??
|
|
165
|
-
parseBoolean(
|
|
166
|
-
process.env.TINKER_INCLUDE_REASONING_CONTENT,
|
|
167
|
-
DEFAULT_INCLUDE_REASONING_CONTENT,
|
|
168
|
-
"TINKER_INCLUDE_REASONING_CONTENT",
|
|
169
|
-
),
|
|
170
|
-
stream:
|
|
171
|
-
overrides.stream ??
|
|
172
|
-
parseBoolean(process.env.TINKER_STREAM, DEFAULT_STREAM, "TINKER_STREAM"),
|
|
152
|
+
function runnerConfigTemplateFromEnvironment(
|
|
153
|
+
environment: Extract<ParsedPublicEnvironment, { mode: "env" }>,
|
|
154
|
+
): RunnerConfigTemplate {
|
|
155
|
+
const contextProfile = createModelContextProfile({
|
|
156
|
+
contextWindowTokens: environment.contextWindowTokens,
|
|
157
|
+
maxSupportedOutputTokens: environment.maxSupportedOutputTokens,
|
|
158
|
+
});
|
|
159
|
+
return Object.freeze({
|
|
160
|
+
workspaceRoot: environment.workspaceRoot,
|
|
161
|
+
modelName: environment.modelName,
|
|
162
|
+
apiKey: environment.apiKey,
|
|
163
|
+
apiBase: environment.apiBase,
|
|
164
|
+
maxIterations: environment.maxIterations,
|
|
165
|
+
includeReasoningContent: environment.includeReasoningContent,
|
|
166
|
+
stream: environment.stream,
|
|
173
167
|
contextProfile,
|
|
174
|
-
contextBudget,
|
|
175
|
-
inputModalities:
|
|
176
|
-
...(overrides.tokenEstimator === undefined
|
|
177
|
-
? {}
|
|
178
|
-
: { tokenEstimator: overrides.tokenEstimator }),
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function createModelClientFromEnv(
|
|
183
|
-
config: Pick<
|
|
184
|
-
RunnerConfig,
|
|
185
|
-
| "modelName"
|
|
186
|
-
| "includeReasoningContent"
|
|
187
|
-
| "stream"
|
|
188
|
-
| "contextBudget"
|
|
189
|
-
| "apiKey"
|
|
190
|
-
| "apiBase"
|
|
191
|
-
> &
|
|
192
|
-
Partial<Pick<RunnerConfig, "inputModalities" | "tokenEstimator">>,
|
|
193
|
-
): ModelClient {
|
|
194
|
-
const fakeMode = process.env.TINKER_TEST_FAKE_MODEL;
|
|
195
|
-
if (fakeMode !== undefined && fakeMode !== "") {
|
|
196
|
-
return new FakeModelClient(fakeMode, {
|
|
197
|
-
model: config.modelName,
|
|
198
|
-
contextBudget: config.contextBudget,
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const apiKey = config.apiKey ?? readRequiredEnv("TINKER_API_KEY");
|
|
203
|
-
const baseURL = config.apiBase ?? readRequiredEnv("TINKER_BASE_URL");
|
|
204
|
-
|
|
205
|
-
return new OpenAIChatModelClient({
|
|
206
|
-
apiKey,
|
|
207
|
-
baseURL,
|
|
208
|
-
includeReasoningContent: config.includeReasoningContent,
|
|
209
|
-
model: config.modelName,
|
|
210
|
-
stream: config.stream,
|
|
211
|
-
contextBudget: config.contextBudget,
|
|
212
|
-
inputModalities: config.inputModalities ?? Object.freeze(["text"]),
|
|
213
|
-
...(config.tokenEstimator === undefined
|
|
214
|
-
? {}
|
|
215
|
-
: { tokenEstimator: config.tokenEstimator }),
|
|
168
|
+
contextBudget: deriveModelContextBudget(contextProfile),
|
|
169
|
+
inputModalities: Object.freeze(["text"] as const),
|
|
216
170
|
});
|
|
217
171
|
}
|
|
218
172
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
| "stream"
|
|
225
|
-
| "contextBudget"
|
|
226
|
-
| "apiKey"
|
|
227
|
-
| "apiBase"
|
|
228
|
-
> &
|
|
229
|
-
Partial<Pick<RunnerConfig, "inputModalities" | "tokenEstimator">>,
|
|
230
|
-
injected?: ModelClient,
|
|
231
|
-
): ModelClient {
|
|
232
|
-
return injected ?? createModelClientFromEnv(config);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
export function createWebFetchRefinerFromEnv(
|
|
236
|
-
config: Pick<
|
|
237
|
-
RunnerConfig,
|
|
238
|
-
| "modelName"
|
|
239
|
-
| "includeReasoningContent"
|
|
240
|
-
| "stream"
|
|
241
|
-
| "contextBudget"
|
|
242
|
-
| "apiKey"
|
|
243
|
-
| "apiBase"
|
|
244
|
-
> &
|
|
245
|
-
Partial<Pick<RunnerConfig, "inputModalities" | "tokenEstimator">>,
|
|
246
|
-
): Refiner {
|
|
247
|
-
return createModelRefiner({
|
|
248
|
-
createModelClient: () => createModelClientFromEnv(config),
|
|
249
|
-
contextBudget: config.contextBudget,
|
|
250
|
-
});
|
|
173
|
+
function runnerConfigFromTemplate(
|
|
174
|
+
template: RunnerConfigTemplate,
|
|
175
|
+
sessionId: SessionId,
|
|
176
|
+
): RunnerConfig {
|
|
177
|
+
return Object.freeze({ ...template, sessionId });
|
|
251
178
|
}
|
|
252
179
|
|
|
253
180
|
export function eventLogPath(workspaceRoot: string, sessionId: SessionId): string {
|
|
@@ -264,63 +191,3 @@ export function observationLogPath(
|
|
|
264
191
|
export function promptHistoryPath(workspaceRoot: string): string {
|
|
265
192
|
return path.join(workspaceRoot, ".tinker", "prompt-history.jsonl");
|
|
266
193
|
}
|
|
267
|
-
|
|
268
|
-
function readRequiredEnv(name: string): string {
|
|
269
|
-
const value = process.env[name];
|
|
270
|
-
if (value === undefined || value.trim() === "") {
|
|
271
|
-
throw new Error(`${name} is required. Put it in .env or the process environment.`);
|
|
272
|
-
}
|
|
273
|
-
return value.trim();
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function parsePositiveInteger(
|
|
277
|
-
value: string | undefined,
|
|
278
|
-
fallback: number,
|
|
279
|
-
name: string,
|
|
280
|
-
): number {
|
|
281
|
-
if (value === undefined) {
|
|
282
|
-
return fallback;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const parsed = Number(value);
|
|
286
|
-
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
287
|
-
throw new Error(`${name} must be a positive integer; received ${value}`);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return parsed;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function parseBoolean(
|
|
294
|
-
value: string | undefined,
|
|
295
|
-
fallback: boolean,
|
|
296
|
-
name: string,
|
|
297
|
-
): boolean {
|
|
298
|
-
if (value === undefined || value.trim() === "") {
|
|
299
|
-
return fallback;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const normalized = value.trim().toLowerCase();
|
|
303
|
-
if (["1", "true", "yes", "on"].includes(normalized)) {
|
|
304
|
-
return true;
|
|
305
|
-
}
|
|
306
|
-
if (["0", "false", "no", "off"].includes(normalized)) {
|
|
307
|
-
return false;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
throw new Error(
|
|
311
|
-
`${name} must be one of true/false, 1/0, yes/no, or on/off; received ${value}`,
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function validateWebFetchRefinerModel(mainModelName: string): void {
|
|
316
|
-
const refinerModel = process.env.TINKER_WEBFETCH_REFINE_MODEL;
|
|
317
|
-
if (
|
|
318
|
-
refinerModel !== undefined &&
|
|
319
|
-
refinerModel.trim() !== "" &&
|
|
320
|
-
refinerModel !== mainModelName
|
|
321
|
-
) {
|
|
322
|
-
throw new Error(
|
|
323
|
-
`TINKER_WEBFETCH_REFINE_MODEL must match TINKER_MODEL in F2; received ${JSON.stringify(refinerModel)} for main model ${JSON.stringify(mainModelName)}.`,
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
}
|
package/src/cli/index.ts
CHANGED
|
@@ -1,29 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { runOneShot } from "./run-runner";
|
|
3
|
-
import { runTui } from "./tui-runner";
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
type MainModule = typeof import("./main");
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
export async function runExecutable(
|
|
6
|
+
input: {
|
|
7
|
+
readonly args: readonly string[];
|
|
8
|
+
readonly stdin: AsyncIterable<unknown>;
|
|
9
|
+
readonly stdout: NodeJS.WriteStream;
|
|
10
|
+
readonly stderr: NodeJS.WriteStream;
|
|
11
|
+
readonly cwd: string;
|
|
12
|
+
readonly env: NodeJS.ProcessEnv;
|
|
13
|
+
} = {
|
|
14
|
+
args: process.argv.slice(2),
|
|
15
|
+
stdin: process.stdin,
|
|
16
|
+
stdout: process.stdout,
|
|
17
|
+
stderr: process.stderr,
|
|
18
|
+
cwd: process.cwd(),
|
|
19
|
+
env: process.env,
|
|
20
|
+
},
|
|
21
|
+
loadMain: () => Promise<MainModule> = () => import("./main"),
|
|
22
|
+
): Promise<number> {
|
|
23
|
+
let mainModule: MainModule;
|
|
24
|
+
try {
|
|
25
|
+
mainModule = await loadMain();
|
|
26
|
+
} catch {
|
|
27
|
+
input.stderr.write("Tinker failed to start. Reinstall tinker-agent.\n");
|
|
28
|
+
return 1;
|
|
13
29
|
}
|
|
14
30
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const profileName = args[0];
|
|
21
|
-
if (profileName === undefined) {
|
|
22
|
-
process.stderr.write("Usage: tinker --profile <profile-name>\n");
|
|
23
|
-
process.exit(2);
|
|
31
|
+
try {
|
|
32
|
+
return await mainModule.main(input);
|
|
33
|
+
} catch {
|
|
34
|
+
input.stderr.write("Tinker failed unexpectedly.\n");
|
|
35
|
+
return 1;
|
|
24
36
|
}
|
|
25
|
-
await runTui({ profileName });
|
|
26
|
-
process.exit(0);
|
|
27
37
|
}
|
|
28
38
|
|
|
29
|
-
|
|
39
|
+
if (import.meta.main) {
|
|
40
|
+
process.exitCode = await runExecutable();
|
|
41
|
+
}
|