tinker-agent 1.8.0 → 1.10.1
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 +41 -1
- package/README.md +65 -6
- package/package.json +1 -1
- package/src/agent/loop.ts +13 -0
- package/src/agent/runtime-session.ts +165 -0
- package/src/agent/session-ledger.ts +20 -3
- package/src/cli/config.ts +11 -2
- package/src/cli/model-profiles.ts +58 -0
- package/src/cli/public-config-contract.ts +73 -7
- package/src/cli/run-runner.ts +4 -1
- package/src/cli/runner-dependencies.ts +35 -10
- package/src/cli/tui-memory.ts +4 -0
- package/src/cli/tui-runner.tsx +8 -1
- package/src/context/context-automation-policy.ts +22 -21
- package/src/context/context-manager.ts +91 -15
- package/src/context/context-policy.ts +0 -2
- package/src/context/context-swap-renderer.ts +1 -1
- package/src/context/prefix-retirement-planner.ts +58 -8
- package/src/context/recall-retirement-contract.ts +5 -4
- package/src/context/swap-planner.ts +33 -27
- package/src/events/stdout-event-printer.ts +17 -0
- package/src/model/fake-model-client.ts +26 -16
- package/src/model/model-api.ts +12 -0
- package/src/model/model-client.ts +9 -1
- package/src/model/moonshot-input-token-estimator.ts +5 -1
- package/src/model/openai-chat-mapping.ts +2 -24
- package/src/model/openai-chat-model-client.ts +18 -294
- package/src/model/openai-image-mapping.ts +20 -0
- package/src/model/openai-model-utils.ts +304 -0
- package/src/model/openai-responses-mapping.ts +532 -0
- package/src/model/openai-responses-model-client.ts +295 -0
- package/src/model/openai-responses-stream.ts +96 -0
- package/src/model/openai-responses-token-estimator.ts +155 -0
- package/src/model/reasoning-effort.ts +60 -0
- package/src/observation/observation-builder.ts +7 -0
- package/src/session/session-catalog.ts +2 -2
- package/src/session/session-history-reader.ts +6 -1
- package/src/session/session-schema.ts +268 -4
- package/src/session/session-store.ts +106 -26
- package/src/skills/skill-context.ts +2 -2
- package/src/tools/bounded-output-preview.ts +276 -0
- package/src/tools/recall.ts +67 -36
- package/src/tools/registry.ts +9 -2
- package/src/tools/task-output-snapshot.ts +6 -22
- package/src/tools/task-output.ts +23 -27
- package/src/tools/types.ts +19 -0
- package/src/tools/update-plan.ts +166 -0
- package/src/tui/app.tsx +82 -5
- package/src/tui/components/plan-view.tsx +43 -0
- package/src/tui/components/prompt-input.tsx +9 -1
- package/src/tui/components/timeline.tsx +7 -0
- package/src/tui/event-store.ts +28 -2
- package/src/tui/slash-commands.ts +20 -0
- package/src/tui/tui-session-controller.ts +7 -0
|
@@ -3,24 +3,29 @@ import {
|
|
|
3
3
|
createModelContextProfile,
|
|
4
4
|
type ModelContextProfile,
|
|
5
5
|
} from "../model/model-context-profile";
|
|
6
|
+
import { parseModelApi, type ModelApi } from "../model/model-api";
|
|
6
7
|
import {
|
|
7
8
|
MEMORY_CONFIG_FIELDS,
|
|
8
9
|
MEMORY_EMBEDDING_FIELDS,
|
|
9
10
|
MODEL_PROFILE_FIELDS,
|
|
10
11
|
MODEL_PROFILES_DOCUMENT_FIELDS,
|
|
12
|
+
MODEL_REASONING_FIELDS,
|
|
11
13
|
MODEL_TOKEN_ESTIMATOR_FIELDS,
|
|
12
14
|
type ModelTokenEstimatorKind,
|
|
13
15
|
type ModelTokenEstimatorMaxRetries,
|
|
14
16
|
} from "./public-config-contract";
|
|
15
17
|
import type { MemoryEmbeddingConfig } from "../memory/contracts";
|
|
18
|
+
import type { ReasoningEffortConfig } from "../model/reasoning-effort";
|
|
16
19
|
|
|
17
20
|
export type ModelProfile = {
|
|
18
21
|
readonly name: string;
|
|
19
22
|
readonly model: string;
|
|
23
|
+
readonly api: ModelApi;
|
|
20
24
|
readonly apiBase: string;
|
|
21
25
|
readonly apiKey: string;
|
|
22
26
|
readonly contextWindowTokens: number;
|
|
23
27
|
readonly maxSupportedOutputTokens: number;
|
|
28
|
+
readonly reasoning?: ReasoningEffortConfig;
|
|
24
29
|
readonly includeReasoningContent: boolean;
|
|
25
30
|
readonly stream: boolean;
|
|
26
31
|
readonly inputModalities: readonly ModelInputModality[];
|
|
@@ -249,6 +254,7 @@ function parseProfile(
|
|
|
249
254
|
);
|
|
250
255
|
|
|
251
256
|
const model = parseProfileString(value, "model", where);
|
|
257
|
+
const api = parseProfileApi(value, where);
|
|
252
258
|
const apiBase = parseProfileString(value, "apiBase", where);
|
|
253
259
|
const apiKey = parseProfileString(value, "apiKey", where);
|
|
254
260
|
|
|
@@ -263,6 +269,11 @@ function parseProfile(
|
|
|
263
269
|
where,
|
|
264
270
|
);
|
|
265
271
|
|
|
272
|
+
const reasoning =
|
|
273
|
+
value.reasoning === undefined
|
|
274
|
+
? undefined
|
|
275
|
+
: parseReasoning(value.reasoning, `${where}: "reasoning"`);
|
|
276
|
+
|
|
266
277
|
const includeReasoningContent = parseProfileBoolean(
|
|
267
278
|
value,
|
|
268
279
|
"includeReasoningContent",
|
|
@@ -292,10 +303,12 @@ function parseProfile(
|
|
|
292
303
|
return Object.freeze({
|
|
293
304
|
name: profileName,
|
|
294
305
|
model,
|
|
306
|
+
api,
|
|
295
307
|
apiBase,
|
|
296
308
|
apiKey,
|
|
297
309
|
contextWindowTokens,
|
|
298
310
|
maxSupportedOutputTokens,
|
|
311
|
+
...(reasoning === undefined ? {} : { reasoning }),
|
|
299
312
|
includeReasoningContent,
|
|
300
313
|
stream,
|
|
301
314
|
inputModalities,
|
|
@@ -303,6 +316,45 @@ function parseProfile(
|
|
|
303
316
|
});
|
|
304
317
|
}
|
|
305
318
|
|
|
319
|
+
function parseReasoning(value: unknown, where: string): ReasoningEffortConfig {
|
|
320
|
+
if (!isRecord(value)) {
|
|
321
|
+
throw new Error(`${where} must be an object.`);
|
|
322
|
+
}
|
|
323
|
+
assertKnownKeys(
|
|
324
|
+
value,
|
|
325
|
+
MODEL_REASONING_FIELDS.map((field) => field.name),
|
|
326
|
+
where,
|
|
327
|
+
);
|
|
328
|
+
if (!Array.isArray(value.supportedEfforts) || value.supportedEfforts.length === 0) {
|
|
329
|
+
throw new Error(`${where}.supportedEfforts must be a non-empty array.`);
|
|
330
|
+
}
|
|
331
|
+
const supportedEfforts = value.supportedEfforts.map((entry, index) => {
|
|
332
|
+
const effort = requireString(entry, `${where}.supportedEfforts[${index}]`);
|
|
333
|
+
if (effort !== effort.trim() || /\s/u.test(effort)) {
|
|
334
|
+
throw new Error(
|
|
335
|
+
`${where}.supportedEfforts[${index}] must not contain whitespace.`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
if (effort === "reset") {
|
|
339
|
+
throw new Error(
|
|
340
|
+
`${where}.supportedEfforts[${index}] must not use the reserved value "reset".`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
return effort;
|
|
344
|
+
});
|
|
345
|
+
if (new Set(supportedEfforts).size !== supportedEfforts.length) {
|
|
346
|
+
throw new Error(`${where}.supportedEfforts must not contain duplicates.`);
|
|
347
|
+
}
|
|
348
|
+
const defaultEffort = requireString(value.defaultEffort, `${where}.defaultEffort`);
|
|
349
|
+
if (!supportedEfforts.includes(defaultEffort)) {
|
|
350
|
+
throw new Error(`${where}.defaultEffort must be listed in supportedEfforts.`);
|
|
351
|
+
}
|
|
352
|
+
return Object.freeze({
|
|
353
|
+
supportedEfforts: Object.freeze(supportedEfforts),
|
|
354
|
+
defaultEffort,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
306
358
|
function parseMemoryConfig(
|
|
307
359
|
value: unknown,
|
|
308
360
|
profiles: ReadonlyMap<string, ModelProfile>,
|
|
@@ -471,6 +523,12 @@ function parseProfileString(
|
|
|
471
523
|
return requireString(value[name], `${where}: ${JSON.stringify(name)}`);
|
|
472
524
|
}
|
|
473
525
|
|
|
526
|
+
function parseProfileApi(value: Record<string, unknown>, where: string): ModelApi {
|
|
527
|
+
const field = modelProfileField("api");
|
|
528
|
+
const configured = value.api ?? field.defaultValue;
|
|
529
|
+
return parseModelApi(configured, `${where}: "api"`);
|
|
530
|
+
}
|
|
531
|
+
|
|
474
532
|
function parseProfilePositiveInteger(
|
|
475
533
|
value: Record<string, unknown>,
|
|
476
534
|
name: "contextWindowTokens" | "maxSupportedOutputTokens",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { rgPath } from "@vscode/ripgrep";
|
|
3
|
+
import { parseModelApi, type ModelApi } from "../model/model-api";
|
|
3
4
|
|
|
4
5
|
export type PublicConfigValueKind = "non-empty-string" | "positive-integer" | "boolean";
|
|
5
6
|
|
|
@@ -39,6 +40,16 @@ export const PUBLIC_CONFIG_FIELDS = Object.freeze([
|
|
|
39
40
|
section: "model",
|
|
40
41
|
description: "Model name used when model profiles are not configured.",
|
|
41
42
|
}),
|
|
43
|
+
publicField({
|
|
44
|
+
name: "TINKER_API",
|
|
45
|
+
valueKind: "non-empty-string",
|
|
46
|
+
requiredIn: "never",
|
|
47
|
+
appliesIn: "env-mode",
|
|
48
|
+
defaultValue: "chat-completions",
|
|
49
|
+
secret: false,
|
|
50
|
+
section: "model",
|
|
51
|
+
description: 'Model API adapter: "chat-completions" or "responses".',
|
|
52
|
+
}),
|
|
42
53
|
publicField({
|
|
43
54
|
name: "TINKER_BASE_URL",
|
|
44
55
|
valueKind: "non-empty-string",
|
|
@@ -46,7 +57,8 @@ export const PUBLIC_CONFIG_FIELDS = Object.freeze([
|
|
|
46
57
|
appliesIn: "env-mode",
|
|
47
58
|
secret: false,
|
|
48
59
|
section: "model",
|
|
49
|
-
description:
|
|
60
|
+
description:
|
|
61
|
+
"OpenAI-compatible API root URL; do not append /chat/completions or /responses.",
|
|
50
62
|
}),
|
|
51
63
|
publicField({
|
|
52
64
|
name: "TINKER_API_KEY",
|
|
@@ -84,7 +96,8 @@ export const PUBLIC_CONFIG_FIELDS = Object.freeze([
|
|
|
84
96
|
defaultValue: false,
|
|
85
97
|
secret: false,
|
|
86
98
|
section: "model",
|
|
87
|
-
description:
|
|
99
|
+
description:
|
|
100
|
+
"Replay provider reasoning_content in Chat Completions history; ignored by Responses.",
|
|
88
101
|
}),
|
|
89
102
|
publicField({
|
|
90
103
|
name: "TINKER_STREAM",
|
|
@@ -94,7 +107,7 @@ export const PUBLIC_CONFIG_FIELDS = Object.freeze([
|
|
|
94
107
|
defaultValue: true,
|
|
95
108
|
secret: false,
|
|
96
109
|
section: "model",
|
|
97
|
-
description: "Use streaming
|
|
110
|
+
description: "Use streaming transport for the selected model API.",
|
|
98
111
|
}),
|
|
99
112
|
publicField({
|
|
100
113
|
name: "TINKER_WEBFETCH_REFINE_MODEL",
|
|
@@ -229,7 +242,11 @@ export const PUBLIC_CONFIG_FIELDS = Object.freeze([
|
|
|
229
242
|
|
|
230
243
|
export type ModelProfileField = {
|
|
231
244
|
readonly name: string;
|
|
232
|
-
readonly valueKind:
|
|
245
|
+
readonly valueKind:
|
|
246
|
+
| PublicConfigValueKind
|
|
247
|
+
| "input-modalities"
|
|
248
|
+
| "reasoning"
|
|
249
|
+
| "token-estimator";
|
|
233
250
|
readonly required: boolean;
|
|
234
251
|
readonly defaultValue?: string | number | boolean | readonly string[];
|
|
235
252
|
readonly secret: boolean;
|
|
@@ -248,12 +265,21 @@ export const MODEL_PROFILE_FIELDS = Object.freeze([
|
|
|
248
265
|
secret: false,
|
|
249
266
|
description: "Provider model name.",
|
|
250
267
|
}),
|
|
268
|
+
profileField({
|
|
269
|
+
name: "api",
|
|
270
|
+
valueKind: "non-empty-string",
|
|
271
|
+
required: false,
|
|
272
|
+
defaultValue: "chat-completions",
|
|
273
|
+
secret: false,
|
|
274
|
+
description: 'Model API adapter: "chat-completions" or "responses".',
|
|
275
|
+
}),
|
|
251
276
|
profileField({
|
|
252
277
|
name: "apiBase",
|
|
253
278
|
valueKind: "non-empty-string",
|
|
254
279
|
required: true,
|
|
255
280
|
secret: false,
|
|
256
|
-
description:
|
|
281
|
+
description:
|
|
282
|
+
"OpenAI-compatible API root URL; do not append /chat/completions or /responses.",
|
|
257
283
|
}),
|
|
258
284
|
profileField({
|
|
259
285
|
name: "apiKey",
|
|
@@ -277,13 +303,22 @@ export const MODEL_PROFILE_FIELDS = Object.freeze([
|
|
|
277
303
|
description:
|
|
278
304
|
"Maximum output-token count supported by the model; must not exceed contextWindowTokens.",
|
|
279
305
|
}),
|
|
306
|
+
profileField({
|
|
307
|
+
name: "reasoning",
|
|
308
|
+
valueKind: "reasoning",
|
|
309
|
+
required: false,
|
|
310
|
+
secret: false,
|
|
311
|
+
description:
|
|
312
|
+
"Provider-specific reasoning efforts and the default for each new session runtime.",
|
|
313
|
+
}),
|
|
280
314
|
profileField({
|
|
281
315
|
name: "includeReasoningContent",
|
|
282
316
|
valueKind: "boolean",
|
|
283
317
|
required: false,
|
|
284
318
|
defaultValue: false,
|
|
285
319
|
secret: false,
|
|
286
|
-
description:
|
|
320
|
+
description:
|
|
321
|
+
"Replay provider reasoning_content in Chat Completions history; ignored by Responses.",
|
|
287
322
|
}),
|
|
288
323
|
profileField({
|
|
289
324
|
name: "stream",
|
|
@@ -291,7 +326,7 @@ export const MODEL_PROFILE_FIELDS = Object.freeze([
|
|
|
291
326
|
required: false,
|
|
292
327
|
defaultValue: true,
|
|
293
328
|
secret: false,
|
|
294
|
-
description: "Use streaming
|
|
329
|
+
description: "Use streaming transport for the selected model API.",
|
|
295
330
|
}),
|
|
296
331
|
profileField({
|
|
297
332
|
name: "inputModalities",
|
|
@@ -311,6 +346,35 @@ export const MODEL_PROFILE_FIELDS = Object.freeze([
|
|
|
311
346
|
}),
|
|
312
347
|
]);
|
|
313
348
|
|
|
349
|
+
export type ModelReasoningField = {
|
|
350
|
+
readonly name: string;
|
|
351
|
+
readonly valueKind: "non-empty-string" | "non-empty-string-array";
|
|
352
|
+
readonly required: true;
|
|
353
|
+
readonly secret: false;
|
|
354
|
+
readonly description: string;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
function reasoningField<const T extends ModelReasoningField>(field: T): Readonly<T> {
|
|
358
|
+
return Object.freeze(field);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export const MODEL_REASONING_FIELDS = Object.freeze([
|
|
362
|
+
reasoningField({
|
|
363
|
+
name: "supportedEfforts",
|
|
364
|
+
valueKind: "non-empty-string-array",
|
|
365
|
+
required: true,
|
|
366
|
+
secret: false,
|
|
367
|
+
description: "Provider-supported effort values exposed by the /reasoning command.",
|
|
368
|
+
}),
|
|
369
|
+
reasoningField({
|
|
370
|
+
name: "defaultEffort",
|
|
371
|
+
valueKind: "non-empty-string",
|
|
372
|
+
required: true,
|
|
373
|
+
secret: false,
|
|
374
|
+
description: "Effort used whenever a session runtime is created or reopened.",
|
|
375
|
+
}),
|
|
376
|
+
]);
|
|
377
|
+
|
|
314
378
|
export type ModelTokenEstimatorField = {
|
|
315
379
|
readonly name: string;
|
|
316
380
|
readonly valueKind: PublicConfigValueKind | "literal-string" | "literal-number";
|
|
@@ -502,6 +566,7 @@ export type ParsedPublicEnvironment =
|
|
|
502
566
|
| (ParsedCommonEnvironment & {
|
|
503
567
|
readonly mode: "env";
|
|
504
568
|
readonly modelName: string;
|
|
569
|
+
readonly api: ModelApi;
|
|
505
570
|
readonly apiBase: string;
|
|
506
571
|
readonly apiKey: string;
|
|
507
572
|
readonly contextWindowTokens: number;
|
|
@@ -615,6 +680,7 @@ export function parsePublicEnvironment(
|
|
|
615
680
|
...common,
|
|
616
681
|
mode,
|
|
617
682
|
modelName,
|
|
683
|
+
api: parseModelApi(requiredStringValue(values, "TINKER_API"), "TINKER_API"),
|
|
618
684
|
apiBase: requiredStringValue(values, "TINKER_BASE_URL"),
|
|
619
685
|
apiKey: requiredStringValue(values, "TINKER_API_KEY"),
|
|
620
686
|
contextWindowTokens: requiredNumberValue(values, "TINKER_CONTEXT_WINDOW_TOKENS"),
|
package/src/cli/run-runner.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
createWebFetchRefiner,
|
|
17
17
|
RUNTIME_INSTRUCTIONS,
|
|
18
18
|
} from "./runner-dependencies";
|
|
19
|
+
import { createReasoningEffortController } from "../model/reasoning-effort";
|
|
19
20
|
import type { PublicToolingConfig } from "./public-config-contract";
|
|
20
21
|
import { realpath } from "node:fs/promises";
|
|
21
22
|
import { loadSkillCatalog } from "../skills/skill-loader";
|
|
@@ -51,10 +52,12 @@ export async function runOneShot(
|
|
|
51
52
|
runtimeInstructions: RUNTIME_INSTRUCTIONS(workspaceRoot),
|
|
52
53
|
projectInstructions,
|
|
53
54
|
});
|
|
55
|
+
const reasoningEffort = createReasoningEffortController(config.reasoning);
|
|
54
56
|
const modelClient = createRunnerModelClient(
|
|
55
57
|
config,
|
|
56
58
|
options.modelClient,
|
|
57
59
|
options.env,
|
|
60
|
+
reasoningEffort,
|
|
58
61
|
);
|
|
59
62
|
session = await createRuntimeSession({
|
|
60
63
|
selection: { mode: "new", sessionId: config.sessionId },
|
|
@@ -72,7 +75,7 @@ export async function runOneShot(
|
|
|
72
75
|
presentationSinks: [new StdoutEventPrinter(stdout, stderr)],
|
|
73
76
|
persistence:
|
|
74
77
|
options.eventLogPath === false ? false : { eventLogPath: options.eventLogPath },
|
|
75
|
-
webFetchRefiner: createWebFetchRefiner(config, options.env),
|
|
78
|
+
webFetchRefiner: createWebFetchRefiner(config, options.env, reasoningEffort),
|
|
76
79
|
toolingConfig: options.tooling,
|
|
77
80
|
bashGuard: {
|
|
78
81
|
mode: config.bashGuardMode,
|
|
@@ -2,13 +2,17 @@ import { renderRecallRetirementContract } from "../context/recall-retirement-con
|
|
|
2
2
|
import { FakeModelClient } from "../model/fake-model-client";
|
|
3
3
|
import type { ModelClient } from "../model/model-client";
|
|
4
4
|
import { OpenAIChatModelClient } from "../model/openai-chat-model-client";
|
|
5
|
+
import { OpenAIResponsesModelClient } from "../model/openai-responses-model-client";
|
|
6
|
+
import {
|
|
7
|
+
createReasoningEffortController,
|
|
8
|
+
type ReasoningEffortController,
|
|
9
|
+
} from "../model/reasoning-effort";
|
|
5
10
|
import { createModelRefiner, type Refiner } from "../tools/web-fetch/refiner";
|
|
6
11
|
import type { RunnerConfig } from "./config";
|
|
7
12
|
|
|
8
13
|
export const RUNTIME_INSTRUCTIONS = (
|
|
9
14
|
workspaceRoot: string,
|
|
10
|
-
): string => `You are a coding agent
|
|
11
|
-
Your name is Tinker.
|
|
15
|
+
): string => `You are a coding agent. Your name is Tinker.
|
|
12
16
|
|
|
13
17
|
Current workspace:
|
|
14
18
|
${workspaceRoot}
|
|
@@ -17,7 +21,7 @@ Use this path as the root for relative file paths. Absolute file paths may point
|
|
|
17
21
|
|
|
18
22
|
You can use tools to find, read, edit, write files, and run shell commands.
|
|
19
23
|
Use Glob to find files by name or path pattern.
|
|
20
|
-
Use Grep to search file contents.
|
|
24
|
+
Use Grep to search file contents.
|
|
21
25
|
With Grep, start with output_mode="files_with_matches" to narrow scope, then use output_mode="content" when you need matching lines.
|
|
22
26
|
Use head_limit and offset to page through large Grep result sets instead of requesting unlimited output.
|
|
23
27
|
Use Read to open specific files returned by Grep.
|
|
@@ -27,8 +31,7 @@ Write creates missing parent directories when creating a file.
|
|
|
27
31
|
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
32
|
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
33
|
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
|
|
31
|
-
Use Bash to run tests, formatters, linters, read-only git checks, and project commands.
|
|
34
|
+
Use WebFetch to read the content of a specific URL, such as documentation pages found via WebSearch.
|
|
32
35
|
Prefer Read for reading files instead of using cat on large files.
|
|
33
36
|
Prefer Write or Edit for changing files instead of shell redirection.
|
|
34
37
|
Use run_in_background=true for dev servers, watch commands, long-running builds, and long-running test services.
|
|
@@ -41,17 +44,22 @@ Use TaskStop to stop a background task that is no longer needed.
|
|
|
41
44
|
Do not use ad-hoc kill commands to manage tasks created by Bash.
|
|
42
45
|
Bash and TaskOutput return outputFilePath. Use Read on outputFilePath when you need complete or paginated output.
|
|
43
46
|
Do not send passwords, tokens, or other secrets through TaskInput because tool arguments are stored in session history.
|
|
47
|
+
Use UpdatePlan for non-trivial work with multiple meaningful phases, when sequencing or checkpoints help the user follow progress. Do not use it for simple or single-step tasks.
|
|
48
|
+
Each UpdatePlan call replaces the complete plan. Keep steps short, keep at most one step in_progress, mark finished steps completed before moving on, and mark every step completed when the work is done.
|
|
49
|
+
Do not repeat the full plan in ordinary assistant text after calling UpdatePlan; summarize only important changes or the next action.
|
|
44
50
|
${renderRecallRetirementContract()}
|
|
45
51
|
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.
|
|
46
52
|
When an active Agent Skill refers to a relative resource path, resolve it from the Skill directory shown with that skill.
|
|
47
53
|
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.
|
|
48
54
|
|
|
49
|
-
|
|
55
|
+
`;
|
|
50
56
|
|
|
51
57
|
export function createModelClient(
|
|
52
58
|
config: Pick<
|
|
53
59
|
RunnerConfig,
|
|
54
60
|
| "modelName"
|
|
61
|
+
| "api"
|
|
62
|
+
| "reasoning"
|
|
55
63
|
| "includeReasoningContent"
|
|
56
64
|
| "stream"
|
|
57
65
|
| "contextBudget"
|
|
@@ -61,13 +69,19 @@ export function createModelClient(
|
|
|
61
69
|
| "tokenEstimator"
|
|
62
70
|
>,
|
|
63
71
|
env: NodeJS.ProcessEnv = process.env,
|
|
72
|
+
reasoningEffort?: ReasoningEffortController,
|
|
64
73
|
): ModelClient {
|
|
74
|
+
const activeReasoningEffort =
|
|
75
|
+
reasoningEffort ?? createReasoningEffortController(config.reasoning);
|
|
65
76
|
const fakeMode = env.TINKER_TEST_FAKE_MODEL;
|
|
66
77
|
if (fakeMode !== undefined && fakeMode !== "") {
|
|
67
78
|
return new FakeModelClient(fakeMode, {
|
|
68
79
|
model: config.modelName,
|
|
69
80
|
contextBudget: config.contextBudget,
|
|
70
81
|
inputModalities: config.inputModalities,
|
|
82
|
+
...(activeReasoningEffort === undefined
|
|
83
|
+
? {}
|
|
84
|
+
: { reasoningEffort: activeReasoningEffort }),
|
|
71
85
|
...(config.tokenEstimator === undefined
|
|
72
86
|
? {}
|
|
73
87
|
: { tokenEstimator: config.tokenEstimator }),
|
|
@@ -78,17 +92,26 @@ export function createModelClient(
|
|
|
78
92
|
});
|
|
79
93
|
}
|
|
80
94
|
|
|
81
|
-
|
|
95
|
+
const common = {
|
|
82
96
|
apiKey: config.apiKey,
|
|
83
97
|
baseURL: config.apiBase,
|
|
84
|
-
includeReasoningContent: config.includeReasoningContent,
|
|
85
98
|
model: config.modelName,
|
|
86
99
|
stream: config.stream,
|
|
87
100
|
contextBudget: config.contextBudget,
|
|
88
101
|
inputModalities: config.inputModalities,
|
|
102
|
+
...(activeReasoningEffort === undefined
|
|
103
|
+
? {}
|
|
104
|
+
: { reasoningEffort: activeReasoningEffort }),
|
|
89
105
|
...(config.tokenEstimator === undefined
|
|
90
106
|
? {}
|
|
91
107
|
: { tokenEstimator: config.tokenEstimator }),
|
|
108
|
+
};
|
|
109
|
+
if (config.api === "responses") {
|
|
110
|
+
return new OpenAIResponsesModelClient(common);
|
|
111
|
+
}
|
|
112
|
+
return new OpenAIChatModelClient({
|
|
113
|
+
...common,
|
|
114
|
+
includeReasoningContent: config.includeReasoningContent,
|
|
92
115
|
});
|
|
93
116
|
}
|
|
94
117
|
|
|
@@ -96,16 +119,18 @@ export function createRunnerModelClient(
|
|
|
96
119
|
config: Parameters<typeof createModelClient>[0],
|
|
97
120
|
injected?: ModelClient,
|
|
98
121
|
env?: NodeJS.ProcessEnv,
|
|
122
|
+
reasoningEffort?: ReasoningEffortController,
|
|
99
123
|
): ModelClient {
|
|
100
|
-
return injected ?? createModelClient(config, env);
|
|
124
|
+
return injected ?? createModelClient(config, env, reasoningEffort);
|
|
101
125
|
}
|
|
102
126
|
|
|
103
127
|
export function createWebFetchRefiner(
|
|
104
128
|
config: Parameters<typeof createModelClient>[0],
|
|
105
129
|
env?: NodeJS.ProcessEnv,
|
|
130
|
+
reasoningEffort?: ReasoningEffortController,
|
|
106
131
|
): Refiner {
|
|
107
132
|
return createModelRefiner({
|
|
108
|
-
createModelClient: () => createModelClient(config, env),
|
|
133
|
+
createModelClient: () => createModelClient(config, env, reasoningEffort),
|
|
109
134
|
contextBudget: config.contextBudget,
|
|
110
135
|
});
|
|
111
136
|
}
|
package/src/cli/tui-memory.ts
CHANGED
|
@@ -37,8 +37,12 @@ export async function initializeTuiMemory(input: {
|
|
|
37
37
|
return createModelClient(
|
|
38
38
|
{
|
|
39
39
|
modelName: profile.model,
|
|
40
|
+
api: profile.api,
|
|
40
41
|
apiKey: profile.apiKey,
|
|
41
42
|
apiBase: profile.apiBase,
|
|
43
|
+
...(profile.reasoning === undefined
|
|
44
|
+
? {}
|
|
45
|
+
: { reasoning: profile.reasoning }),
|
|
42
46
|
includeReasoningContent: profile.includeReasoningContent,
|
|
43
47
|
stream: profile.stream,
|
|
44
48
|
contextBudget: memoryConfig.contextBudget,
|
package/src/cli/tui-runner.tsx
CHANGED
|
@@ -48,6 +48,7 @@ import { createWorkspaceFileLister } from "../tui/workspace-file-search";
|
|
|
48
48
|
import { clipboardWriterForEnvironment } from "../tui/clipboard";
|
|
49
49
|
import { initializeTuiMemory } from "./tui-memory";
|
|
50
50
|
import { prepareShikiHighlighter } from "../tui/shiki-highlighter";
|
|
51
|
+
import { createReasoningEffortController } from "../model/reasoning-effort";
|
|
51
52
|
|
|
52
53
|
export type RunTuiOptions = {
|
|
53
54
|
readonly publicConfig: ResolvedPublicConfig;
|
|
@@ -82,10 +83,12 @@ export async function runTui(options: RunTuiOptions): Promise<void> {
|
|
|
82
83
|
sessionId: SessionId,
|
|
83
84
|
sink: EventSink & AssistantTextDeltaSink,
|
|
84
85
|
): Promise<RuntimeSession> => {
|
|
86
|
+
const reasoningEffort = createReasoningEffortController(sessionConfig.reasoning);
|
|
85
87
|
const modelClient = createRunnerModelClient(
|
|
86
88
|
sessionConfig,
|
|
87
89
|
undefined,
|
|
88
90
|
options.env,
|
|
91
|
+
reasoningEffort,
|
|
89
92
|
);
|
|
90
93
|
const projectInstructions = await loadProjectInstructions(workspaceRoot);
|
|
91
94
|
const skillCatalog = await loadSkillCatalog({ workspaceRoot });
|
|
@@ -107,7 +110,11 @@ export async function runTui(options: RunTuiOptions): Promise<void> {
|
|
|
107
110
|
skillCatalog,
|
|
108
111
|
presentationSinks: [sink],
|
|
109
112
|
assistantTextDeltaSink: sink,
|
|
110
|
-
webFetchRefiner: createWebFetchRefiner(
|
|
113
|
+
webFetchRefiner: createWebFetchRefiner(
|
|
114
|
+
sessionConfig,
|
|
115
|
+
options.env,
|
|
116
|
+
reasoningEffort,
|
|
117
|
+
),
|
|
111
118
|
toolingConfig: options.publicConfig.tooling,
|
|
112
119
|
enableTurnUndo: true,
|
|
113
120
|
bashGuard: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { stableJsonStringify, sha256 } from "../model/model-request-preflight";
|
|
2
|
-
import {
|
|
2
|
+
import { RECALL_TOOL_DEFINITIONS } from "../tools/recall";
|
|
3
3
|
import type { ToolDefinition } from "../tools/types";
|
|
4
4
|
import type { StoredContextSurfaceV8 } from "./context-surface";
|
|
5
5
|
import {
|
|
@@ -17,26 +17,26 @@ export const I4_ACTIVE_RECALL_QUALIFICATION = Object.freeze({
|
|
|
17
17
|
policyVersion: "active-recall-qualification-policy-v1",
|
|
18
18
|
policySha256: "77ca611594d4e9b7b5a597a3a33e35fcaaffae284dc2ac9953ce9a63cce1c009",
|
|
19
19
|
positiveReportSha256:
|
|
20
|
-
"
|
|
20
|
+
"e827e5e94171328bb2dd7fcaeff91881f04bdfc78361a45e2548da323229b02a",
|
|
21
21
|
negativeReportSha256:
|
|
22
|
-
"
|
|
22
|
+
"ed379843aee0f193f398a4dff9a18ed338f0edab2cbaf9b628d0dfc402d925a4",
|
|
23
23
|
resolvedModel: "deepseek-v4-flash",
|
|
24
24
|
recallContractVersion: CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION,
|
|
25
25
|
recallContractSha256:
|
|
26
|
-
"
|
|
26
|
+
"3b6d1a452efea1db5920eb13542571b038667ef4f635551bea375bda6562a39f",
|
|
27
27
|
recallToolDefinitionSha256:
|
|
28
|
-
"
|
|
28
|
+
"e63ada7cdf9591d1e933cf5e190ea30586e02bbf73aeb5e648db449d75aae009",
|
|
29
29
|
metrics: Object.freeze({
|
|
30
|
-
fullHistoryTaskSuccessRate:
|
|
31
|
-
swapOnlyTaskSuccessRate:
|
|
32
|
-
recallOnlyTaskSuccessRate:
|
|
33
|
-
recallOnlyActiveRecallRate:
|
|
34
|
-
recallOnlySearchGetSuccessRate: 0.
|
|
35
|
-
minimumCounterfactualGroupTaskSuccessRate:
|
|
36
|
-
invalidRecallCallsPerRecallOnlyTrial: 0
|
|
30
|
+
fullHistoryTaskSuccessRate: 0.9667,
|
|
31
|
+
swapOnlyTaskSuccessRate: 0.9667,
|
|
32
|
+
recallOnlyTaskSuccessRate: 1,
|
|
33
|
+
recallOnlyActiveRecallRate: 1,
|
|
34
|
+
recallOnlySearchGetSuccessRate: 0.3333,
|
|
35
|
+
minimumCounterfactualGroupTaskSuccessRate: 1,
|
|
36
|
+
invalidRecallCallsPerRecallOnlyTrial: 0,
|
|
37
37
|
negativeUnnecessaryRecallRate: 0,
|
|
38
|
-
recallOnlyTokenRatioToFullHistory: 1.
|
|
39
|
-
recallOnlyLatencyRatioToFullHistory: 1.
|
|
38
|
+
recallOnlyTokenRatioToFullHistory: 1.3739,
|
|
39
|
+
recallOnlyLatencyRatioToFullHistory: 1.207,
|
|
40
40
|
}),
|
|
41
41
|
passed: true,
|
|
42
42
|
} as const);
|
|
@@ -80,13 +80,14 @@ export function selectContextAutomation(
|
|
|
80
80
|
) {
|
|
81
81
|
return disabled("recall_contract_mismatch");
|
|
82
82
|
}
|
|
83
|
-
const
|
|
84
|
-
(definition) =>
|
|
83
|
+
const recallTools = input.surface.toolDefinitions.filter(
|
|
84
|
+
(definition) =>
|
|
85
|
+
definition.name === "RecallSearch" || definition.name === "RecallGet",
|
|
85
86
|
);
|
|
86
87
|
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
recallTools.length !== 2 ||
|
|
89
|
+
toolDefinitionsHash(recallTools) !== evidence.recallToolDefinitionSha256 ||
|
|
90
|
+
toolDefinitionsHash(RECALL_TOOL_DEFINITIONS) !== evidence.recallToolDefinitionSha256
|
|
90
91
|
) {
|
|
91
92
|
return disabled("recall_tool_mismatch");
|
|
92
93
|
}
|
|
@@ -116,6 +117,6 @@ function disabled(
|
|
|
116
117
|
});
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
function
|
|
120
|
-
return sha256(stableJsonStringify(
|
|
120
|
+
function toolDefinitionsHash(definitions: readonly ToolDefinition[]): string {
|
|
121
|
+
return sha256(stableJsonStringify(definitions));
|
|
121
122
|
}
|