tinker-agent 1.11.0 → 2.1.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 +44 -1
- package/README.md +33 -24
- package/package.json +2 -1
- package/src/agent/context-meter.ts +12 -85
- package/src/agent/loop.ts +1 -14
- package/src/agent/runtime-session.ts +9 -25
- package/src/agent/session-ledger.ts +12 -5
- package/src/agent/tool-result-content.ts +76 -0
- package/src/agent/types.ts +14 -2
- package/src/cli/config.ts +4 -5
- package/src/cli/model-profiles.ts +38 -97
- package/src/cli/public-config-contract.ts +24 -88
- package/src/cli/runner-dependencies.ts +5 -7
- package/src/cli/tui-memory.ts +1 -3
- package/src/cli/tui-runner.tsx +4 -0
- package/src/context/compiled-context-hash.ts +2 -1
- package/src/context/compiled-context-validator.ts +13 -4
- package/src/context/context-protocol-validator.ts +33 -2
- package/src/context/context-revision-compiler.ts +2 -1
- package/src/context/context-revision.ts +8 -2
- package/src/context/context-swap-renderer.ts +46 -12
- package/src/context/prefix-retirement-planner.ts +13 -9
- package/src/context/protocol-frame.ts +74 -7
- package/src/context/swap-planner.ts +19 -14
- package/src/events/observation-text-log.ts +1 -1
- package/src/events/stdout-event-printer.ts +6 -0
- package/src/image/image-asset-store.ts +32 -3
- package/src/image/image-input-policy.ts +53 -2
- package/src/image/image-probe.ts +8 -2
- package/src/image/provider-image.ts +99 -0
- package/src/memory/contracts.ts +61 -3
- package/src/memory/memory-coordinator.ts +313 -49
- package/src/memory/memory-extractor.ts +48 -48
- package/src/memory/memory-get-tool.ts +86 -0
- package/src/memory/memory-search-tool.ts +122 -33
- package/src/memory/memory-store.ts +227 -20
- package/src/model/fake-model-client.ts +177 -124
- package/src/model/model-client.ts +62 -11
- package/src/model/model-request-preflight.ts +0 -1
- package/src/model/openai-chat-mapping.ts +2 -1
- package/src/model/openai-chat-model-client.ts +26 -38
- package/src/model/openai-model-utils.ts +109 -40
- package/src/model/openai-responses-mapping.ts +25 -1
- package/src/model/openai-responses-model-client.ts +27 -40
- package/src/model/token-estimator.ts +26 -3
- package/src/observation/observation-builder.ts +100 -25
- package/src/session/session-history-reader.ts +128 -5
- package/src/session/session-schema.ts +59 -9
- package/src/session/session-store.ts +342 -205
- package/src/tools/registry.ts +18 -0
- package/src/tools/types.ts +46 -0
- package/src/tools/view-image.ts +89 -0
- package/src/tools/wait.ts +85 -0
- package/src/tui/components/memory-browser.tsx +3 -0
- package/src/tui/event-store.ts +61 -2
- package/src/model/input-token-estimator.ts +0 -25
- package/src/model/moonshot-input-token-estimator.ts +0 -111
- package/src/model/openai-responses-token-estimator.ts +0 -155
package/src/cli/config.ts
CHANGED
|
@@ -12,9 +12,9 @@ import {
|
|
|
12
12
|
persistDefaultProfile,
|
|
13
13
|
profileToContextProfile,
|
|
14
14
|
type ModelInputModality,
|
|
15
|
+
type ToolResultModality,
|
|
15
16
|
type ModelProfile,
|
|
16
17
|
type ModelProfiles,
|
|
17
|
-
type ModelTokenEstimatorProfile,
|
|
18
18
|
unknownProfileError,
|
|
19
19
|
} from "./model-profiles";
|
|
20
20
|
import type { MemoryEmbeddingConfig } from "../memory/contracts";
|
|
@@ -40,7 +40,7 @@ export type RunnerConfig = {
|
|
|
40
40
|
readonly contextBudget: ModelContextBudget;
|
|
41
41
|
readonly profileName?: string;
|
|
42
42
|
readonly inputModalities: readonly ModelInputModality[];
|
|
43
|
-
readonly
|
|
43
|
+
readonly toolResultModalities: readonly ToolResultModality[];
|
|
44
44
|
readonly bashGuardMode: "guard" | "yolo";
|
|
45
45
|
readonly bashGuardSource: "default" | "environment" | "cli";
|
|
46
46
|
};
|
|
@@ -182,11 +182,9 @@ function runnerConfigTemplateFromProfile(
|
|
|
182
182
|
contextBudget: deriveModelContextBudget(contextProfile),
|
|
183
183
|
profileName: profile.name,
|
|
184
184
|
inputModalities: profile.inputModalities,
|
|
185
|
+
toolResultModalities: profile.toolResultModalities,
|
|
185
186
|
bashGuardMode: environment.bashGuardMode,
|
|
186
187
|
bashGuardSource: environment.bashGuardSource,
|
|
187
|
-
...(profile.tokenEstimator === undefined
|
|
188
|
-
? {}
|
|
189
|
-
: { tokenEstimator: profile.tokenEstimator }),
|
|
190
188
|
});
|
|
191
189
|
}
|
|
192
190
|
|
|
@@ -210,6 +208,7 @@ function runnerConfigTemplateFromEnvironment(
|
|
|
210
208
|
contextProfile,
|
|
211
209
|
contextBudget: deriveModelContextBudget(contextProfile),
|
|
212
210
|
inputModalities: Object.freeze(["text"] as const),
|
|
211
|
+
toolResultModalities: Object.freeze(["text"] as const),
|
|
213
212
|
bashGuardMode: environment.bashGuardMode,
|
|
214
213
|
bashGuardSource: environment.bashGuardSource,
|
|
215
214
|
});
|
|
@@ -10,12 +10,10 @@ import {
|
|
|
10
10
|
MODEL_PROFILE_FIELDS,
|
|
11
11
|
MODEL_PROFILES_DOCUMENT_FIELDS,
|
|
12
12
|
MODEL_REASONING_FIELDS,
|
|
13
|
-
MODEL_TOKEN_ESTIMATOR_FIELDS,
|
|
14
|
-
type ModelTokenEstimatorKind,
|
|
15
|
-
type ModelTokenEstimatorMaxRetries,
|
|
16
13
|
} from "./public-config-contract";
|
|
17
14
|
import type { MemoryEmbeddingConfig } from "../memory/contracts";
|
|
18
15
|
import type { ReasoningEffortConfig } from "../model/reasoning-effort";
|
|
16
|
+
import type { ModelInputModality, ToolResultModality } from "../model/model-client";
|
|
19
17
|
|
|
20
18
|
export type ModelProfile = {
|
|
21
19
|
readonly name: string;
|
|
@@ -29,19 +27,9 @@ export type ModelProfile = {
|
|
|
29
27
|
readonly includeReasoningContent: boolean;
|
|
30
28
|
readonly stream: boolean;
|
|
31
29
|
readonly inputModalities: readonly ModelInputModality[];
|
|
32
|
-
readonly
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export type ModelInputModality = "text" | "image";
|
|
36
|
-
|
|
37
|
-
export type ModelTokenEstimatorProfile = {
|
|
38
|
-
readonly kind: ModelTokenEstimatorKind;
|
|
39
|
-
readonly model: string;
|
|
40
|
-
readonly apiBase: string;
|
|
41
|
-
readonly apiKey: string;
|
|
42
|
-
readonly timeoutMs: number;
|
|
43
|
-
readonly maxRetries: ModelTokenEstimatorMaxRetries;
|
|
30
|
+
readonly toolResultModalities: readonly ToolResultModality[];
|
|
44
31
|
};
|
|
32
|
+
export type { ModelInputModality, ToolResultModality } from "../model/model-client";
|
|
45
33
|
|
|
46
34
|
export type ModelProfiles = {
|
|
47
35
|
readonly defaultProfile: string;
|
|
@@ -285,16 +273,15 @@ function parseProfile(
|
|
|
285
273
|
value.inputModalities,
|
|
286
274
|
`${where}: "inputModalities"`,
|
|
287
275
|
);
|
|
288
|
-
const
|
|
289
|
-
value.
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (
|
|
276
|
+
const toolResultModalities = parseToolResultModalities(
|
|
277
|
+
value.toolResultModalities,
|
|
278
|
+
`${where}: "toolResultModalities"`,
|
|
279
|
+
);
|
|
280
|
+
if (toolResultModalities.includes("image") && !inputModalities.includes("image")) {
|
|
293
281
|
throw new Error(
|
|
294
|
-
`${where}: image
|
|
282
|
+
`${where}: "toolResultModalities" cannot include "image" unless "inputModalities" also includes "image".`,
|
|
295
283
|
);
|
|
296
284
|
}
|
|
297
|
-
|
|
298
285
|
createModelContextProfile({
|
|
299
286
|
contextWindowTokens,
|
|
300
287
|
maxSupportedOutputTokens,
|
|
@@ -312,10 +299,38 @@ function parseProfile(
|
|
|
312
299
|
includeReasoningContent,
|
|
313
300
|
stream,
|
|
314
301
|
inputModalities,
|
|
315
|
-
|
|
302
|
+
toolResultModalities,
|
|
316
303
|
});
|
|
317
304
|
}
|
|
318
305
|
|
|
306
|
+
function parseToolResultModalities(
|
|
307
|
+
value: unknown,
|
|
308
|
+
name: string,
|
|
309
|
+
): readonly ToolResultModality[] {
|
|
310
|
+
if (value === undefined) {
|
|
311
|
+
return modelProfileField("toolResultModalities").defaultValue;
|
|
312
|
+
}
|
|
313
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
314
|
+
throw new Error(`${name} must be a non-empty array.`);
|
|
315
|
+
}
|
|
316
|
+
const modalities = value.map((rawEntry): ToolResultModality => {
|
|
317
|
+
const entry: unknown = rawEntry;
|
|
318
|
+
if (entry !== "text" && entry !== "image") {
|
|
319
|
+
throw new Error(`${name} contains an unsupported modality.`);
|
|
320
|
+
}
|
|
321
|
+
return entry;
|
|
322
|
+
});
|
|
323
|
+
if (new Set(modalities).size !== modalities.length) {
|
|
324
|
+
throw new Error(`${name} must not contain duplicates.`);
|
|
325
|
+
}
|
|
326
|
+
if (!modalities.includes("text")) {
|
|
327
|
+
throw new Error(`${name} must include "text".`);
|
|
328
|
+
}
|
|
329
|
+
return Object.freeze(
|
|
330
|
+
modalities.includes("image") ? (["text", "image"] as const) : (["text"] as const),
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
319
334
|
function parseReasoning(value: unknown, where: string): ReasoningEffortConfig {
|
|
320
335
|
if (!isRecord(value)) {
|
|
321
336
|
throw new Error(`${where} must be an object.`);
|
|
@@ -434,54 +449,7 @@ function parseInputModalities(
|
|
|
434
449
|
);
|
|
435
450
|
}
|
|
436
451
|
|
|
437
|
-
function parseTokenEstimator(value: unknown, name: string): ModelTokenEstimatorProfile {
|
|
438
|
-
if (!isRecord(value)) {
|
|
439
|
-
throw new Error(`${name} must be an object.`);
|
|
440
|
-
}
|
|
441
|
-
assertKnownKeys(
|
|
442
|
-
value,
|
|
443
|
-
MODEL_TOKEN_ESTIMATOR_FIELDS.map((field) => field.name),
|
|
444
|
-
name,
|
|
445
|
-
);
|
|
446
|
-
const kindField = tokenEstimatorField("kind");
|
|
447
|
-
if (value.kind !== kindField.literalValue) {
|
|
448
|
-
throw new Error(`${name}.kind must be ${JSON.stringify(kindField.literalValue)}.`);
|
|
449
|
-
}
|
|
450
|
-
const model = parseTokenEstimatorString(value, "model", name);
|
|
451
|
-
const apiBase = parseTokenEstimatorString(value, "apiBase", name);
|
|
452
|
-
const apiKey = parseTokenEstimatorString(value, "apiKey", name);
|
|
453
|
-
const timeoutField = tokenEstimatorField("timeoutMs");
|
|
454
|
-
if (timeoutField.valueKind !== "positive-integer") {
|
|
455
|
-
throw new Error("Token estimator timeoutMs contract kind is invalid.");
|
|
456
|
-
}
|
|
457
|
-
const timeoutMs = requirePositiveInteger(value.timeoutMs, `${name}.timeoutMs`);
|
|
458
|
-
if (
|
|
459
|
-
timeoutField.minimum === undefined ||
|
|
460
|
-
timeoutField.maximum === undefined ||
|
|
461
|
-
timeoutMs < timeoutField.minimum ||
|
|
462
|
-
timeoutMs > timeoutField.maximum
|
|
463
|
-
) {
|
|
464
|
-
throw new Error(
|
|
465
|
-
`${name}.timeoutMs must be between ${timeoutField.minimum} and ${timeoutField.maximum}.`,
|
|
466
|
-
);
|
|
467
|
-
}
|
|
468
|
-
const maxRetriesField = tokenEstimatorField("maxRetries");
|
|
469
|
-
if (value.maxRetries !== maxRetriesField.literalValue) {
|
|
470
|
-
throw new Error(`${name}.maxRetries must be ${maxRetriesField.literalValue}.`);
|
|
471
|
-
}
|
|
472
|
-
return Object.freeze({
|
|
473
|
-
kind: kindField.literalValue,
|
|
474
|
-
model,
|
|
475
|
-
apiBase,
|
|
476
|
-
apiKey,
|
|
477
|
-
timeoutMs,
|
|
478
|
-
maxRetries: maxRetriesField.literalValue,
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
|
|
482
452
|
type ModelProfileFieldName = (typeof MODEL_PROFILE_FIELDS)[number]["name"];
|
|
483
|
-
type ModelTokenEstimatorFieldName =
|
|
484
|
-
(typeof MODEL_TOKEN_ESTIMATOR_FIELDS)[number]["name"];
|
|
485
453
|
|
|
486
454
|
function modelProfileField<Name extends ModelProfileFieldName>(
|
|
487
455
|
name: Name,
|
|
@@ -496,21 +464,6 @@ function modelProfileField<Name extends ModelProfileFieldName>(
|
|
|
496
464
|
>;
|
|
497
465
|
}
|
|
498
466
|
|
|
499
|
-
function tokenEstimatorField<Name extends ModelTokenEstimatorFieldName>(
|
|
500
|
-
name: Name,
|
|
501
|
-
): Extract<(typeof MODEL_TOKEN_ESTIMATOR_FIELDS)[number], { readonly name: Name }> {
|
|
502
|
-
const field = MODEL_TOKEN_ESTIMATOR_FIELDS.find(
|
|
503
|
-
(candidate) => candidate.name === name,
|
|
504
|
-
);
|
|
505
|
-
if (field === undefined) {
|
|
506
|
-
throw new Error(`Missing token estimator field contract for ${name}.`);
|
|
507
|
-
}
|
|
508
|
-
return field as Extract<
|
|
509
|
-
(typeof MODEL_TOKEN_ESTIMATOR_FIELDS)[number],
|
|
510
|
-
{ readonly name: Name }
|
|
511
|
-
>;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
467
|
function parseProfileString(
|
|
515
468
|
value: Record<string, unknown>,
|
|
516
469
|
name: "model" | "apiBase" | "apiKey",
|
|
@@ -555,18 +508,6 @@ function parseProfileBoolean(
|
|
|
555
508
|
: parseBoolean(value[name], `${where}: ${JSON.stringify(name)}`);
|
|
556
509
|
}
|
|
557
510
|
|
|
558
|
-
function parseTokenEstimatorString(
|
|
559
|
-
value: Record<string, unknown>,
|
|
560
|
-
name: "model" | "apiBase" | "apiKey",
|
|
561
|
-
where: string,
|
|
562
|
-
): string {
|
|
563
|
-
const field = tokenEstimatorField(name);
|
|
564
|
-
if (field.valueKind !== "non-empty-string") {
|
|
565
|
-
throw new Error(`Token estimator field ${name} has an invalid contract kind.`);
|
|
566
|
-
}
|
|
567
|
-
return requireString(value[name], `${where}.${name}`);
|
|
568
|
-
}
|
|
569
|
-
|
|
570
511
|
function assertKnownKeys(
|
|
571
512
|
value: Record<string, unknown>,
|
|
572
513
|
allowed: readonly string[],
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os from "node:os";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { rgPath } from "@vscode/ripgrep";
|
|
3
4
|
import { parseModelApi, type ModelApi } from "../model/model-api";
|
|
@@ -29,7 +30,7 @@ export const PUBLIC_CONFIG_FIELDS = Object.freeze([
|
|
|
29
30
|
secret: false,
|
|
30
31
|
section: "model",
|
|
31
32
|
description:
|
|
32
|
-
"Optional model profiles JSON path.
|
|
33
|
+
"Optional model profiles JSON path. A leading ~ expands to the home directory; other relative paths resolve from the process cwd.",
|
|
33
34
|
}),
|
|
34
35
|
publicField({
|
|
35
36
|
name: "TINKER_MODEL",
|
|
@@ -126,7 +127,8 @@ export const PUBLIC_CONFIG_FIELDS = Object.freeze([
|
|
|
126
127
|
defaultSource: "process-cwd",
|
|
127
128
|
secret: false,
|
|
128
129
|
section: "workspace",
|
|
129
|
-
description:
|
|
130
|
+
description:
|
|
131
|
+
"Workspace path. A leading ~ expands to the home directory; other relative paths resolve from the process cwd.",
|
|
130
132
|
}),
|
|
131
133
|
publicField({
|
|
132
134
|
name: "TINKER_MAX_ITERATIONS",
|
|
@@ -245,8 +247,8 @@ export type ModelProfileField = {
|
|
|
245
247
|
readonly valueKind:
|
|
246
248
|
| PublicConfigValueKind
|
|
247
249
|
| "input-modalities"
|
|
248
|
-
| "
|
|
249
|
-
| "
|
|
250
|
+
| "tool-result-modalities"
|
|
251
|
+
| "reasoning";
|
|
250
252
|
readonly required: boolean;
|
|
251
253
|
readonly defaultValue?: string | number | boolean | readonly string[];
|
|
252
254
|
readonly secret: boolean;
|
|
@@ -338,11 +340,13 @@ export const MODEL_PROFILE_FIELDS = Object.freeze([
|
|
|
338
340
|
'Accepted model input modalities; normalizes to ["text"] or ["text", "image"].',
|
|
339
341
|
}),
|
|
340
342
|
profileField({
|
|
341
|
-
name: "
|
|
342
|
-
valueKind: "
|
|
343
|
+
name: "toolResultModalities",
|
|
344
|
+
valueKind: "tool-result-modalities",
|
|
343
345
|
required: false,
|
|
344
|
-
|
|
345
|
-
|
|
346
|
+
defaultValue: Object.freeze(["text"] as const),
|
|
347
|
+
secret: false,
|
|
348
|
+
description:
|
|
349
|
+
'Accepted tool-result modalities; normalizes to ["text"] or ["text", "image"].',
|
|
346
350
|
}),
|
|
347
351
|
]);
|
|
348
352
|
|
|
@@ -375,82 +379,6 @@ export const MODEL_REASONING_FIELDS = Object.freeze([
|
|
|
375
379
|
}),
|
|
376
380
|
]);
|
|
377
381
|
|
|
378
|
-
export type ModelTokenEstimatorField = {
|
|
379
|
-
readonly name: string;
|
|
380
|
-
readonly valueKind: PublicConfigValueKind | "literal-string" | "literal-number";
|
|
381
|
-
readonly required: true;
|
|
382
|
-
readonly secret: boolean;
|
|
383
|
-
readonly literalValue?: string | number;
|
|
384
|
-
readonly minimum?: number;
|
|
385
|
-
readonly maximum?: number;
|
|
386
|
-
readonly description: string;
|
|
387
|
-
};
|
|
388
|
-
|
|
389
|
-
function estimatorField<const T extends ModelTokenEstimatorField>(
|
|
390
|
-
field: T,
|
|
391
|
-
): Readonly<T> {
|
|
392
|
-
return Object.freeze(field);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
export const MODEL_TOKEN_ESTIMATOR_FIELDS = Object.freeze([
|
|
396
|
-
estimatorField({
|
|
397
|
-
name: "kind",
|
|
398
|
-
valueKind: "literal-string",
|
|
399
|
-
required: true,
|
|
400
|
-
secret: false,
|
|
401
|
-
literalValue: "moonshot-estimate-token-count-v1",
|
|
402
|
-
description: "Estimator protocol discriminator.",
|
|
403
|
-
}),
|
|
404
|
-
estimatorField({
|
|
405
|
-
name: "model",
|
|
406
|
-
valueKind: "non-empty-string",
|
|
407
|
-
required: true,
|
|
408
|
-
secret: false,
|
|
409
|
-
description: "Estimator model name.",
|
|
410
|
-
}),
|
|
411
|
-
estimatorField({
|
|
412
|
-
name: "apiBase",
|
|
413
|
-
valueKind: "non-empty-string",
|
|
414
|
-
required: true,
|
|
415
|
-
secret: false,
|
|
416
|
-
description: "Estimator API base URL.",
|
|
417
|
-
}),
|
|
418
|
-
estimatorField({
|
|
419
|
-
name: "apiKey",
|
|
420
|
-
valueKind: "non-empty-string",
|
|
421
|
-
required: true,
|
|
422
|
-
secret: true,
|
|
423
|
-
description: "Estimator API credential.",
|
|
424
|
-
}),
|
|
425
|
-
estimatorField({
|
|
426
|
-
name: "timeoutMs",
|
|
427
|
-
valueKind: "positive-integer",
|
|
428
|
-
required: true,
|
|
429
|
-
secret: false,
|
|
430
|
-
minimum: 1_000,
|
|
431
|
-
maximum: 60_000,
|
|
432
|
-
description: "Estimator request timeout in milliseconds.",
|
|
433
|
-
}),
|
|
434
|
-
estimatorField({
|
|
435
|
-
name: "maxRetries",
|
|
436
|
-
valueKind: "literal-number",
|
|
437
|
-
required: true,
|
|
438
|
-
secret: false,
|
|
439
|
-
literalValue: 0,
|
|
440
|
-
description: "Estimator retry count; retries are disabled.",
|
|
441
|
-
}),
|
|
442
|
-
]);
|
|
443
|
-
|
|
444
|
-
export type ModelTokenEstimatorKind = Extract<
|
|
445
|
-
(typeof MODEL_TOKEN_ESTIMATOR_FIELDS)[number],
|
|
446
|
-
{ readonly name: "kind" }
|
|
447
|
-
>["literalValue"];
|
|
448
|
-
|
|
449
|
-
export type ModelTokenEstimatorMaxRetries = Extract<
|
|
450
|
-
(typeof MODEL_TOKEN_ESTIMATOR_FIELDS)[number],
|
|
451
|
-
{ readonly name: "maxRetries" }
|
|
452
|
-
>["literalValue"];
|
|
453
|
-
|
|
454
382
|
export type MemoryConfigField = {
|
|
455
383
|
readonly name: "profile" | "embedding";
|
|
456
384
|
readonly valueKind: "non-empty-string" | "embedding-profile";
|
|
@@ -610,7 +538,7 @@ export function parsePublicEnvironment(
|
|
|
610
538
|
values[field.name] = parseEnvironmentField(field, env[field.name], mode, cwd);
|
|
611
539
|
}
|
|
612
540
|
|
|
613
|
-
const workspaceRoot =
|
|
541
|
+
const workspaceRoot = resolveUserPath(
|
|
614
542
|
cwd,
|
|
615
543
|
requiredStringValue(values, "TINKER_WORKSPACE"),
|
|
616
544
|
);
|
|
@@ -659,9 +587,7 @@ export function parsePublicEnvironment(
|
|
|
659
587
|
return Object.freeze({
|
|
660
588
|
...common,
|
|
661
589
|
mode,
|
|
662
|
-
modelsPath:
|
|
663
|
-
? configuredModelsPath
|
|
664
|
-
: path.resolve(cwd, configuredModelsPath),
|
|
590
|
+
modelsPath: resolveUserPath(cwd, configuredModelsPath),
|
|
665
591
|
});
|
|
666
592
|
}
|
|
667
593
|
|
|
@@ -760,6 +686,16 @@ function optionalRawString(value: string | undefined): string | undefined {
|
|
|
760
686
|
return normalized === undefined || normalized === "" ? undefined : normalized;
|
|
761
687
|
}
|
|
762
688
|
|
|
689
|
+
function resolveUserPath(cwd: string, value: string): string {
|
|
690
|
+
let expanded = value;
|
|
691
|
+
if (value === "~") {
|
|
692
|
+
expanded = os.homedir();
|
|
693
|
+
} else if (value.startsWith("~/") || value.startsWith(`~${path.sep}`)) {
|
|
694
|
+
expanded = path.join(os.homedir(), value.slice(2));
|
|
695
|
+
}
|
|
696
|
+
return path.isAbsolute(expanded) ? expanded : path.resolve(cwd, expanded);
|
|
697
|
+
}
|
|
698
|
+
|
|
763
699
|
function optionalStringValue(
|
|
764
700
|
values: Record<PublicConfigFieldName, ParsedPrimitive>,
|
|
765
701
|
name: PublicConfigFieldName,
|
|
@@ -66,7 +66,8 @@ export function createModelClient(
|
|
|
66
66
|
| "apiKey"
|
|
67
67
|
| "apiBase"
|
|
68
68
|
| "inputModalities"
|
|
69
|
-
| "
|
|
69
|
+
| "toolResultModalities"
|
|
70
|
+
| "profileName"
|
|
70
71
|
>,
|
|
71
72
|
env: NodeJS.ProcessEnv = process.env,
|
|
72
73
|
reasoningEffort?: ReasoningEffortController,
|
|
@@ -79,12 +80,10 @@ export function createModelClient(
|
|
|
79
80
|
model: config.modelName,
|
|
80
81
|
contextBudget: config.contextBudget,
|
|
81
82
|
inputModalities: config.inputModalities,
|
|
83
|
+
toolResultModalities: config.toolResultModalities,
|
|
82
84
|
...(activeReasoningEffort === undefined
|
|
83
85
|
? {}
|
|
84
86
|
: { reasoningEffort: activeReasoningEffort }),
|
|
85
|
-
...(config.tokenEstimator === undefined
|
|
86
|
-
? {}
|
|
87
|
-
: { tokenEstimator: config.tokenEstimator }),
|
|
88
87
|
...(env.TINKER_TEST_FAKE_MODEL_REQUEST_LOG === undefined ||
|
|
89
88
|
env.TINKER_TEST_FAKE_MODEL_REQUEST_LOG === ""
|
|
90
89
|
? {}
|
|
@@ -99,12 +98,11 @@ export function createModelClient(
|
|
|
99
98
|
stream: config.stream,
|
|
100
99
|
contextBudget: config.contextBudget,
|
|
101
100
|
inputModalities: config.inputModalities,
|
|
101
|
+
toolResultModalities: config.toolResultModalities,
|
|
102
|
+
profileName: config.profileName,
|
|
102
103
|
...(activeReasoningEffort === undefined
|
|
103
104
|
? {}
|
|
104
105
|
: { reasoningEffort: activeReasoningEffort }),
|
|
105
|
-
...(config.tokenEstimator === undefined
|
|
106
|
-
? {}
|
|
107
|
-
: { tokenEstimator: config.tokenEstimator }),
|
|
108
106
|
};
|
|
109
107
|
if (config.api === "responses") {
|
|
110
108
|
return new OpenAIResponsesModelClient(common);
|
package/src/cli/tui-memory.ts
CHANGED
|
@@ -47,9 +47,7 @@ export async function initializeTuiMemory(input: {
|
|
|
47
47
|
stream: profile.stream,
|
|
48
48
|
contextBudget: memoryConfig.contextBudget,
|
|
49
49
|
inputModalities: profile.inputModalities,
|
|
50
|
-
|
|
51
|
-
? {}
|
|
52
|
-
: { tokenEstimator: profile.tokenEstimator }),
|
|
50
|
+
toolResultModalities: profile.toolResultModalities,
|
|
53
51
|
},
|
|
54
52
|
input.env,
|
|
55
53
|
);
|
package/src/cli/tui-runner.tsx
CHANGED
|
@@ -129,6 +129,10 @@ export async function runTui(options: RunTuiOptions): Promise<void> {
|
|
|
129
129
|
workspaceRoot,
|
|
130
130
|
sessionId,
|
|
131
131
|
}),
|
|
132
|
+
memoryGet: memoryCoordinator.createGetToolExecutor({
|
|
133
|
+
workspaceRoot,
|
|
134
|
+
sessionId,
|
|
135
|
+
}),
|
|
132
136
|
completedTurnHook: memoryCoordinator,
|
|
133
137
|
}),
|
|
134
138
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sha256, stableJsonStringify } from "../model/model-request-preflight";
|
|
2
|
+
import { toolResultText } from "../agent/tool-result-content";
|
|
2
3
|
import type { CompiledContextEntry, SwapOverride } from "./context-revision";
|
|
3
4
|
import {
|
|
4
5
|
contentHash,
|
|
@@ -132,7 +133,7 @@ function renderedMessageDescriptor(entry: CompiledContextEntry): unknown {
|
|
|
132
133
|
contentSha256:
|
|
133
134
|
entry.representation === "canonical"
|
|
134
135
|
? entry.sourceContentSha256
|
|
135
|
-
: contentHash(message.content),
|
|
136
|
+
: contentHash(toolResultText(message.content)),
|
|
136
137
|
};
|
|
137
138
|
}
|
|
138
139
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentMessage } from "../agent/types";
|
|
2
|
+
import { toolResultDisplayText, toolResultText } from "../agent/tool-result-content";
|
|
2
3
|
import { stableJsonStringify } from "../model/model-request-preflight";
|
|
3
4
|
import { formatMessageSource } from "./context-source";
|
|
4
5
|
import type { CompiledRevisionContext, SwapOverride } from "./context-revision";
|
|
@@ -157,7 +158,7 @@ export class CompiledContextValidator {
|
|
|
157
158
|
entry.message.toolCallId !== record.toolCallId ||
|
|
158
159
|
entry.message.providerToolCallId !== record.providerToolCallId ||
|
|
159
160
|
entry.message.name !== record.name ||
|
|
160
|
-
entry.message.content !== override.renderedContent
|
|
161
|
+
toolResultText(entry.message.content) !== override.renderedContent
|
|
161
162
|
) {
|
|
162
163
|
fail(
|
|
163
164
|
`Swapped tool entry changed protocol identity at ordinal ${entry.ordinal}.`,
|
|
@@ -197,7 +198,11 @@ function validateOverrideIdentity(
|
|
|
197
198
|
fail(`Swap override changed canonical identity at ordinal ${record.ordinal}.`);
|
|
198
199
|
}
|
|
199
200
|
const originalBytes = Buffer.byteLength(
|
|
200
|
-
record.role === "assistant"
|
|
201
|
+
record.role === "assistant"
|
|
202
|
+
? (record.content ?? "")
|
|
203
|
+
: record.role === "tool"
|
|
204
|
+
? record.displayText
|
|
205
|
+
: record.content,
|
|
201
206
|
"utf8",
|
|
202
207
|
);
|
|
203
208
|
const renderedBytes = Buffer.byteLength(override.renderedContent, "utf8");
|
|
@@ -208,8 +213,11 @@ function validateOverrideIdentity(
|
|
|
208
213
|
override.originalBytes !== originalBytes ||
|
|
209
214
|
override.renderedBytes !== renderedBytes ||
|
|
210
215
|
override.byteSavings !== originalBytes - renderedBytes ||
|
|
211
|
-
(rendererFormat === "swap-observation-v1"
|
|
216
|
+
((rendererFormat === "swap-observation-v1" ||
|
|
217
|
+
rendererFormat === "skill-activation-receipt-v1") &&
|
|
218
|
+
override.byteSavings <= 0) ||
|
|
212
219
|
(rendererFormat !== "swap-observation-v1" &&
|
|
220
|
+
rendererFormat !== "swap-tool-image-v1" &&
|
|
213
221
|
rendererFormat !== "skill-activation-receipt-v1")
|
|
214
222
|
) {
|
|
215
223
|
fail(`Swap override metadata is invalid at ordinal ${record.ordinal}.`);
|
|
@@ -266,7 +274,8 @@ function assertSameMessage(actual: AgentMessage, record: CanonicalMessageRecord)
|
|
|
266
274
|
actual.toolCallId !== record.toolCallId ||
|
|
267
275
|
actual.providerToolCallId !== record.providerToolCallId ||
|
|
268
276
|
actual.name !== record.name ||
|
|
269
|
-
actual.content !== record.content
|
|
277
|
+
stableJsonStringify(actual.content) !== stableJsonStringify(record.content) ||
|
|
278
|
+
toolResultDisplayText(actual.content) !== record.displayText
|
|
270
279
|
) {
|
|
271
280
|
fail(`Canonical entry changed tool data at ordinal ${record.ordinal}.`);
|
|
272
281
|
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { MessageId, ProtocolFrameId, ToolCallId } from "../ids/runtime-id";
|
|
2
|
+
import {
|
|
3
|
+
canonicalToolResultContentHash,
|
|
4
|
+
toolResultDisplayText,
|
|
5
|
+
validateToolResultContent,
|
|
6
|
+
} from "../agent/tool-result-content";
|
|
2
7
|
import {
|
|
3
8
|
contentHash,
|
|
4
9
|
userMessageHash,
|
|
5
10
|
rawResultHash,
|
|
11
|
+
validateReturnedToolObservation,
|
|
6
12
|
type CanonicalMessageRecord,
|
|
7
13
|
type ProtocolContextView,
|
|
8
14
|
type ProtocolFrame,
|
|
@@ -113,7 +119,9 @@ export class ContextProtocolValidator {
|
|
|
113
119
|
? {}
|
|
114
120
|
: { attachments: message.attachments }),
|
|
115
121
|
})
|
|
116
|
-
:
|
|
122
|
+
: message.role === "tool"
|
|
123
|
+
? canonicalToolResultContentHash(message.content)
|
|
124
|
+
: contentHash(message.content))
|
|
117
125
|
) {
|
|
118
126
|
fail(
|
|
119
127
|
"content_hash_mismatch",
|
|
@@ -405,11 +413,19 @@ function validateToolExchange(
|
|
|
405
413
|
);
|
|
406
414
|
}
|
|
407
415
|
const result = requireItem(results, 0, "tool result");
|
|
416
|
+
validateToolResultContent(message.content);
|
|
417
|
+
if (message.displayText !== toolResultDisplayText(message.content)) {
|
|
418
|
+
fail(
|
|
419
|
+
"content_hash_mismatch",
|
|
420
|
+
`Tool display projection does not match message ${message.messageId}.`,
|
|
421
|
+
identityForMessage(message),
|
|
422
|
+
);
|
|
423
|
+
}
|
|
408
424
|
if (
|
|
409
425
|
result.sessionId !== frame.sessionId ||
|
|
410
426
|
result.frameId !== frame.frameId ||
|
|
411
427
|
result.toolMessageId !== message.messageId ||
|
|
412
|
-
result.observationSha256 !==
|
|
428
|
+
result.observationSha256 !== canonicalToolResultContentHash(message.content)
|
|
413
429
|
) {
|
|
414
430
|
fail(
|
|
415
431
|
"tool_result_mismatch",
|
|
@@ -428,6 +444,21 @@ function validateToolExchange(
|
|
|
428
444
|
{ ...identityForMessage(message), toolCallId: call.toolCallId },
|
|
429
445
|
);
|
|
430
446
|
}
|
|
447
|
+
if (input.fullIntegrity && result.completion.kind === "returned") {
|
|
448
|
+
try {
|
|
449
|
+
validateReturnedToolObservation({
|
|
450
|
+
toolName: message.name,
|
|
451
|
+
raw: result.completion.raw,
|
|
452
|
+
content: message.content,
|
|
453
|
+
});
|
|
454
|
+
} catch (error) {
|
|
455
|
+
fail(
|
|
456
|
+
"tool_result_mismatch",
|
|
457
|
+
`Tool result for ${call.toolCallId} has invalid canonical content: ${error instanceof Error ? error.message : String(error)}`,
|
|
458
|
+
{ ...identityForMessage(message), toolCallId: call.toolCallId },
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
431
462
|
input.usedResultCallIds.add(call.toolCallId);
|
|
432
463
|
}
|
|
433
464
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentMessage } from "../agent/types";
|
|
2
|
+
import { textToolResultContent } from "../agent/tool-result-content";
|
|
2
3
|
import type { ContextRevisionId } from "../ids/runtime-id";
|
|
3
4
|
import { CompiledContextValidator } from "./compiled-context-validator";
|
|
4
5
|
import {
|
|
@@ -263,7 +264,7 @@ function swappedToolMessage(
|
|
|
263
264
|
}
|
|
264
265
|
return {
|
|
265
266
|
...message,
|
|
266
|
-
content: override.renderedContent,
|
|
267
|
+
content: textToolResultContent(override.renderedContent),
|
|
267
268
|
};
|
|
268
269
|
}
|
|
269
270
|
|
|
@@ -133,12 +133,18 @@ export type SwapOverride = {
|
|
|
133
133
|
readonly originalBytes: number;
|
|
134
134
|
readonly renderedBytes: number;
|
|
135
135
|
readonly byteSavings: number;
|
|
136
|
-
readonly rendererFormat?:
|
|
136
|
+
readonly rendererFormat?:
|
|
137
|
+
| "swap-observation-v1"
|
|
138
|
+
| "swap-tool-image-v1"
|
|
139
|
+
| "skill-activation-receipt-v1";
|
|
137
140
|
};
|
|
138
141
|
|
|
139
142
|
export type StoredContextOverrideV8 = SwapOverride & {
|
|
140
143
|
readonly introducedRevisionId: ContextRevisionId;
|
|
141
|
-
readonly rendererFormat:
|
|
144
|
+
readonly rendererFormat:
|
|
145
|
+
| "swap-observation-v1"
|
|
146
|
+
| "swap-tool-image-v1"
|
|
147
|
+
| "skill-activation-receipt-v1";
|
|
142
148
|
readonly createdAt: string;
|
|
143
149
|
};
|
|
144
150
|
|