tinker-agent 1.9.0 → 1.11.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 +36 -1
- package/README.md +64 -6
- package/package.json +1 -1
- package/src/agent/loop.ts +17 -0
- package/src/agent/runtime-session.ts +341 -1
- package/src/agent/session-ledger.ts +100 -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 +28 -4
- 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/observation-text-log.ts +4 -0
- package/src/events/stdout-event-printer.ts +5 -0
- package/src/events/types.ts +5 -1
- package/src/model/fake-model-client.ts +55 -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/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 +134 -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 +7 -2
- package/src/tools/task-output-snapshot.ts +6 -22
- package/src/tools/task-output.ts +23 -27
- package/src/tui/app.tsx +153 -11
- package/src/tui/components/footer.tsx +6 -1
- package/src/tui/components/prompt-input.tsx +9 -1
- package/src/tui/event-store.ts +15 -0
- package/src/tui/slash-commands.ts +20 -0
- package/src/tui/tui-session-controller.ts +14 -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,6 +2,11 @@ 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
|
|
|
@@ -53,6 +58,8 @@ export function createModelClient(
|
|
|
53
58
|
config: Pick<
|
|
54
59
|
RunnerConfig,
|
|
55
60
|
| "modelName"
|
|
61
|
+
| "api"
|
|
62
|
+
| "reasoning"
|
|
56
63
|
| "includeReasoningContent"
|
|
57
64
|
| "stream"
|
|
58
65
|
| "contextBudget"
|
|
@@ -62,13 +69,19 @@ export function createModelClient(
|
|
|
62
69
|
| "tokenEstimator"
|
|
63
70
|
>,
|
|
64
71
|
env: NodeJS.ProcessEnv = process.env,
|
|
72
|
+
reasoningEffort?: ReasoningEffortController,
|
|
65
73
|
): ModelClient {
|
|
74
|
+
const activeReasoningEffort =
|
|
75
|
+
reasoningEffort ?? createReasoningEffortController(config.reasoning);
|
|
66
76
|
const fakeMode = env.TINKER_TEST_FAKE_MODEL;
|
|
67
77
|
if (fakeMode !== undefined && fakeMode !== "") {
|
|
68
78
|
return new FakeModelClient(fakeMode, {
|
|
69
79
|
model: config.modelName,
|
|
70
80
|
contextBudget: config.contextBudget,
|
|
71
81
|
inputModalities: config.inputModalities,
|
|
82
|
+
...(activeReasoningEffort === undefined
|
|
83
|
+
? {}
|
|
84
|
+
: { reasoningEffort: activeReasoningEffort }),
|
|
72
85
|
...(config.tokenEstimator === undefined
|
|
73
86
|
? {}
|
|
74
87
|
: { tokenEstimator: config.tokenEstimator }),
|
|
@@ -79,17 +92,26 @@ export function createModelClient(
|
|
|
79
92
|
});
|
|
80
93
|
}
|
|
81
94
|
|
|
82
|
-
|
|
95
|
+
const common = {
|
|
83
96
|
apiKey: config.apiKey,
|
|
84
97
|
baseURL: config.apiBase,
|
|
85
|
-
includeReasoningContent: config.includeReasoningContent,
|
|
86
98
|
model: config.modelName,
|
|
87
99
|
stream: config.stream,
|
|
88
100
|
contextBudget: config.contextBudget,
|
|
89
101
|
inputModalities: config.inputModalities,
|
|
102
|
+
...(activeReasoningEffort === undefined
|
|
103
|
+
? {}
|
|
104
|
+
: { reasoningEffort: activeReasoningEffort }),
|
|
90
105
|
...(config.tokenEstimator === undefined
|
|
91
106
|
? {}
|
|
92
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,
|
|
93
115
|
});
|
|
94
116
|
}
|
|
95
117
|
|
|
@@ -97,16 +119,18 @@ export function createRunnerModelClient(
|
|
|
97
119
|
config: Parameters<typeof createModelClient>[0],
|
|
98
120
|
injected?: ModelClient,
|
|
99
121
|
env?: NodeJS.ProcessEnv,
|
|
122
|
+
reasoningEffort?: ReasoningEffortController,
|
|
100
123
|
): ModelClient {
|
|
101
|
-
return injected ?? createModelClient(config, env);
|
|
124
|
+
return injected ?? createModelClient(config, env, reasoningEffort);
|
|
102
125
|
}
|
|
103
126
|
|
|
104
127
|
export function createWebFetchRefiner(
|
|
105
128
|
config: Parameters<typeof createModelClient>[0],
|
|
106
129
|
env?: NodeJS.ProcessEnv,
|
|
130
|
+
reasoningEffort?: ReasoningEffortController,
|
|
107
131
|
): Refiner {
|
|
108
132
|
return createModelRefiner({
|
|
109
|
-
createModelClient: () => createModelClient(config, env),
|
|
133
|
+
createModelClient: () => createModelClient(config, env, reasoningEffort),
|
|
110
134
|
contextBudget: config.contextBudget,
|
|
111
135
|
});
|
|
112
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
|
}
|