tinker-agent 1.0.65 → 1.2.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 -0
- package/LICENSE +201 -0
- package/NOTICE +6 -0
- package/README.md +90 -9
- package/bin/tinker.js +39 -0
- package/node_modules/ansi-regex/index.d.ts +33 -0
- package/node_modules/ansi-regex/index.js +14 -0
- package/node_modules/ansi-regex/license +9 -0
- package/node_modules/ansi-regex/package.json +61 -0
- package/node_modules/ansi-regex/readme.md +66 -0
- package/node_modules/ansi-styles/index.d.ts +236 -0
- package/node_modules/ansi-styles/index.js +223 -0
- package/node_modules/ansi-styles/license +9 -0
- package/node_modules/ansi-styles/package.json +54 -0
- package/node_modules/ansi-styles/readme.md +173 -0
- package/node_modules/chalk/license +9 -0
- package/node_modules/chalk/package.json +83 -0
- package/node_modules/chalk/readme.md +297 -0
- package/node_modules/chalk/source/index.d.ts +325 -0
- package/node_modules/chalk/source/index.js +225 -0
- package/node_modules/chalk/source/utilities.js +33 -0
- package/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
- package/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
- package/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
- package/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
- package/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
- package/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
- package/node_modules/character-entities/index.d.ts +6 -0
- package/node_modules/character-entities/index.js +2132 -0
- package/node_modules/character-entities/license +22 -0
- package/node_modules/character-entities/package.json +78 -0
- package/node_modules/character-entities/readme.md +152 -0
- package/node_modules/decode-named-character-reference/index.d.ts +13 -0
- package/node_modules/decode-named-character-reference/index.d.ts.map +1 -0
- package/node_modules/decode-named-character-reference/index.dom.d.ts +6 -0
- package/node_modules/decode-named-character-reference/index.dom.d.ts.map +1 -0
- package/node_modules/decode-named-character-reference/index.dom.js +32 -0
- package/node_modules/decode-named-character-reference/index.js +19 -0
- package/node_modules/decode-named-character-reference/license +22 -0
- package/node_modules/decode-named-character-reference/package.json +90 -0
- package/node_modules/decode-named-character-reference/readme.md +136 -0
- package/node_modules/get-east-asian-width/index.d.ts +60 -0
- package/node_modules/get-east-asian-width/index.js +30 -0
- package/node_modules/get-east-asian-width/license +9 -0
- package/node_modules/get-east-asian-width/lookup-data.js +21 -0
- package/node_modules/get-east-asian-width/lookup.js +138 -0
- package/node_modules/get-east-asian-width/package.json +71 -0
- package/node_modules/get-east-asian-width/readme.md +65 -0
- package/node_modules/get-east-asian-width/utilities.js +24 -0
- package/node_modules/has-flag/index.d.ts +39 -0
- package/node_modules/has-flag/index.js +8 -0
- package/node_modules/has-flag/license +9 -0
- package/node_modules/has-flag/package.json +49 -0
- package/node_modules/has-flag/readme.md +74 -0
- package/node_modules/is-fullwidth-code-point/index.d.ts +17 -0
- package/node_modules/is-fullwidth-code-point/index.js +12 -0
- package/node_modules/is-fullwidth-code-point/license +9 -0
- package/node_modules/is-fullwidth-code-point/package.json +53 -0
- package/node_modules/is-fullwidth-code-point/readme.md +31 -0
- package/node_modules/markdansi/LICENSE +21 -0
- package/node_modules/markdansi/README.md +167 -0
- package/node_modules/markdansi/dist/ast.d.ts +99 -0
- package/node_modules/markdansi/dist/ast.js +1 -0
- package/node_modules/markdansi/dist/cli.d.ts +17 -0
- package/node_modules/markdansi/dist/cli.js +208 -0
- package/node_modules/markdansi/dist/hyperlink.d.ts +9 -0
- package/node_modules/markdansi/dist/hyperlink.js +26 -0
- package/node_modules/markdansi/dist/index.d.ts +10 -0
- package/node_modules/markdansi/dist/index.js +16 -0
- package/node_modules/markdansi/dist/parser.d.ts +2 -0
- package/node_modules/markdansi/dist/parser.js +205 -0
- package/node_modules/markdansi/dist/render.d.ts +9 -0
- package/node_modules/markdansi/dist/render.js +680 -0
- package/node_modules/markdansi/dist/stream.d.ts +39 -0
- package/node_modules/markdansi/dist/stream.js +192 -0
- package/node_modules/markdansi/dist/theme.d.ts +18 -0
- package/node_modules/markdansi/dist/theme.js +105 -0
- package/node_modules/markdansi/dist/types.d.ts +58 -0
- package/node_modules/markdansi/dist/types.js +1 -0
- package/node_modules/markdansi/dist/wrap.d.ts +10 -0
- package/node_modules/markdansi/dist/wrap.js +73 -0
- package/node_modules/markdansi/docs/spec.md +110 -0
- package/node_modules/markdansi/package.json +78 -0
- package/node_modules/markdansi/tsconfig.json +18 -0
- package/node_modules/marked/LICENSE +44 -0
- package/node_modules/marked/README.md +115 -0
- package/node_modules/marked/bin/main.js +284 -0
- package/node_modules/marked/bin/marked.js +16 -0
- package/node_modules/marked/lib/marked.d.ts +760 -0
- package/node_modules/marked/lib/marked.esm.js +77 -0
- package/node_modules/marked/lib/marked.esm.js.map +7 -0
- package/node_modules/marked/lib/marked.umd.js +79 -0
- package/node_modules/marked/lib/marked.umd.js.map +7 -0
- package/node_modules/marked/man/marked.1 +113 -0
- package/node_modules/marked/man/marked.1.md +93 -0
- package/node_modules/marked/package.json +103 -0
- package/node_modules/slice-ansi/index.d.ts +19 -0
- package/node_modules/slice-ansi/index.js +317 -0
- package/node_modules/slice-ansi/license +10 -0
- package/node_modules/slice-ansi/package.json +59 -0
- package/node_modules/slice-ansi/readme.md +55 -0
- package/node_modules/slice-ansi/tokenize-ansi.js +752 -0
- package/node_modules/string-width/index.d.ts +39 -0
- package/node_modules/string-width/index.js +203 -0
- package/node_modules/string-width/license +9 -0
- package/node_modules/string-width/package.json +65 -0
- package/node_modules/string-width/readme.md +66 -0
- package/node_modules/strip-ansi/index.d.ts +15 -0
- package/node_modules/strip-ansi/index.js +19 -0
- package/node_modules/strip-ansi/license +9 -0
- package/node_modules/strip-ansi/package.json +59 -0
- package/node_modules/strip-ansi/readme.md +37 -0
- package/node_modules/supports-color/browser.d.ts +1 -0
- package/node_modules/supports-color/browser.js +35 -0
- package/node_modules/supports-color/index.d.ts +55 -0
- package/node_modules/supports-color/index.js +202 -0
- package/node_modules/supports-color/license +9 -0
- package/node_modules/supports-color/package.json +64 -0
- package/node_modules/supports-color/readme.md +75 -0
- package/node_modules/supports-hyperlinks/browser.js +10 -0
- package/node_modules/supports-hyperlinks/index.d.ts +25 -0
- package/node_modules/supports-hyperlinks/index.js +163 -0
- package/node_modules/supports-hyperlinks/license +10 -0
- package/node_modules/supports-hyperlinks/package.json +47 -0
- package/node_modules/supports-hyperlinks/readme.md +45 -0
- package/package.json +41 -10
- package/patches/markdansi@0.3.2.patch +22 -2
- package/src/agent/context-builder.ts +24 -10
- package/src/agent/context-meter.ts +89 -5
- package/src/agent/loop.ts +107 -10
- package/src/agent/runtime-session.ts +1437 -82
- package/src/agent/session-ledger.ts +169 -29
- package/src/agent/types.ts +12 -1
- package/src/agent/user-prompt-projection.ts +62 -0
- package/src/cli/config.ts +57 -14
- package/src/cli/model-profiles.ts +124 -0
- package/src/cli/run-runner.ts +4 -1
- package/src/cli/tui-runner.tsx +68 -30
- package/src/context/compiled-context-hash.ts +1 -1
- package/src/context/compiled-context-validator.ts +92 -15
- package/src/context/context-automation-policy.ts +121 -0
- package/src/context/context-manager.ts +390 -11
- package/src/context/context-policy.ts +8 -0
- package/src/context/context-protocol-validator.ts +11 -1
- package/src/context/context-revision-compiler.ts +190 -27
- package/src/context/context-revision.ts +105 -19
- package/src/context/context-surface.ts +329 -0
- package/src/context/prefix-retirement-planner.ts +570 -0
- package/src/context/protocol-frame.ts +30 -3
- package/src/context/recall-retirement-contract.ts +27 -0
- package/src/context/swap-planner.ts +34 -10
- package/src/events/observation-text-log.ts +3 -3
- package/src/events/stdout-event-printer.ts +39 -3
- package/src/events/types.ts +172 -35
- package/src/ids/runtime-id.ts +6 -0
- package/src/ids/uuid-v7.ts +7 -0
- package/src/image/image-asset-store.ts +319 -0
- package/src/image/image-input-policy.ts +14 -0
- package/src/image/image-probe.ts +233 -0
- package/src/image/image-types.ts +262 -0
- package/src/instructions/project-instructions.ts +4 -1
- package/src/mcp/mcp-manager.ts +83 -19
- package/src/model/fake-model-client.ts +9 -1
- package/src/model/input-token-estimator.ts +25 -0
- package/src/model/model-client.ts +60 -0
- package/src/model/model-request-preflight.ts +1 -0
- package/src/model/moonshot-input-token-estimator.ts +107 -0
- package/src/model/openai-chat-mapping.ts +77 -1
- package/src/model/openai-chat-model-client.ts +389 -59
- package/src/model/openai-chat-stream.ts +272 -0
- package/src/model/prompt-prefix-hash.ts +2 -4
- package/src/model/token-estimator.ts +4 -0
- package/src/observation/observation-builder.ts +29 -0
- package/src/session/resume-projection.ts +94 -34
- package/src/session/session-catalog.ts +4 -4
- package/src/session/session-errors.ts +1 -1
- package/src/session/session-history-reader.ts +151 -4
- package/src/session/session-last-response-reader.ts +189 -0
- package/src/session/session-schema.ts +349 -35
- package/src/session/session-store.ts +3266 -255
- package/src/session/sqlite-session-ledger.ts +9 -4
- package/src/skills/skill-catalog.ts +182 -0
- package/src/skills/skill-context.ts +364 -0
- package/src/skills/skill-loader.ts +779 -0
- package/src/skills/skill-tool.ts +297 -0
- package/src/tools/edit.ts +51 -64
- package/src/tools/ensure-parent-directory.ts +6 -0
- package/src/tools/read.ts +2 -2
- package/src/tools/recall.ts +43 -40
- package/src/tools/registry.ts +24 -3
- package/src/tools/ripgrep.ts +3 -2
- package/src/tools/types.ts +43 -4
- package/src/tools/write.ts +20 -31
- package/src/tui/app.tsx +333 -96
- package/src/tui/clipboard.ts +5 -0
- package/src/tui/components/mcp-panel.tsx +28 -0
- package/src/tui/components/prompt-input.tsx +790 -257
- package/src/tui/components/skills-panel.tsx +31 -0
- package/src/tui/components/timeline.tsx +30 -1
- package/src/tui/event-store.ts +85 -2
- package/src/tui/file-mention.ts +234 -0
- package/src/tui/line-editor.ts +1 -0
- package/src/tui/project-slash-commands.ts +229 -0
- package/src/tui/prompt-draft.ts +510 -0
- package/src/tui/prompt-history.ts +294 -22
- package/src/tui/slash-commands.ts +46 -2
- package/src/tui/tui-session-controller.ts +110 -48
- package/src/tui/workspace-file-search.ts +94 -0
package/package.json
CHANGED
|
@@ -1,45 +1,69 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tinker-agent",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A personal coding agent with an interactive TUI and one-shot CLI.",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"tinker": "
|
|
9
|
-
"tinker-agent": "src/cli/index.ts"
|
|
8
|
+
"tinker": "bin/tinker.js"
|
|
10
9
|
},
|
|
11
10
|
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"NOTICE",
|
|
14
|
+
"CHANGELOG.md",
|
|
12
15
|
"src/agent",
|
|
13
16
|
"src/cli",
|
|
14
17
|
"src/context",
|
|
15
18
|
"src/events",
|
|
16
19
|
"src/ids",
|
|
20
|
+
"src/image",
|
|
17
21
|
"src/instructions",
|
|
18
22
|
"src/mcp",
|
|
19
23
|
"src/model",
|
|
20
24
|
"src/observation",
|
|
21
25
|
"src/session",
|
|
26
|
+
"src/skills",
|
|
22
27
|
"src/tools",
|
|
23
28
|
"src/tui",
|
|
24
|
-
"patches"
|
|
25
|
-
"README.md"
|
|
29
|
+
"patches"
|
|
26
30
|
],
|
|
27
31
|
"engines": {
|
|
28
|
-
"
|
|
32
|
+
"node": ">=20.0.0"
|
|
29
33
|
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/ishowshao/tinker.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/ishowshao/tinker/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/ishowshao/tinker#readme",
|
|
30
42
|
"publishConfig": {
|
|
31
43
|
"access": "public"
|
|
32
44
|
},
|
|
45
|
+
"bundledDependencies": [
|
|
46
|
+
"markdansi"
|
|
47
|
+
],
|
|
33
48
|
"scripts": {
|
|
34
49
|
"bench:long-session": "bun scripts/bench-long-session-memory.ts",
|
|
50
|
+
"bench:i3-provider-smoke": "bun scripts/bench-i3-provider-smoke.ts",
|
|
51
|
+
"bench:k3-image-live": "bun scripts/k3-image-live-smoke.ts",
|
|
52
|
+
"bench:i4-active-recall": "bun scripts/bench-i4-active-recall.ts",
|
|
53
|
+
"qualify:i4-active-recall": "bun scripts/qualify-i4-active-recall.ts",
|
|
54
|
+
"release:verify": "bun scripts/verify-release-package.ts",
|
|
35
55
|
"bench:recall": "bun scripts/bench-recall.ts",
|
|
36
56
|
"bench:smoke": "bun scripts/bench-smoke.ts",
|
|
57
|
+
"chrome:build": "bun packages/tinker-chrome/scripts/build.ts",
|
|
58
|
+
"chrome:diagnose": "bun packages/tinker-chrome/src/cli.ts diagnose",
|
|
59
|
+
"chrome:install-host": "bun packages/tinker-chrome/src/cli.ts install-host",
|
|
60
|
+
"chrome:mcp": "bun packages/tinker-chrome/src/cli.ts mcp",
|
|
37
61
|
"tinker": "bun src/cli/index.ts",
|
|
38
62
|
"check": "bun run typecheck && bun run format:check && bun run lint && bun test && bun run bench:smoke",
|
|
39
63
|
"format": "biome format --write .",
|
|
40
64
|
"format:check": "biome format .",
|
|
41
|
-
"lint": "eslint \"src/**/*.{ts,tsx}\" \"scripts/**/*.ts\" --max-warnings=0",
|
|
42
|
-
"lint:fix": "eslint \"src/**/*.{ts,tsx}\" \"scripts/**/*.ts\" --fix",
|
|
65
|
+
"lint": "eslint \"bin/**/*.js\" \"src/**/*.{ts,tsx}\" \"scripts/**/*.ts\" \"packages/**/*.ts\" --max-warnings=0",
|
|
66
|
+
"lint:fix": "eslint \"bin/**/*.js\" \"src/**/*.{ts,tsx}\" \"scripts/**/*.ts\" \"packages/**/*.ts\" --fix",
|
|
43
67
|
"test": "bun test",
|
|
44
68
|
"typecheck": "tsc --noEmit"
|
|
45
69
|
},
|
|
@@ -49,20 +73,27 @@
|
|
|
49
73
|
"@inkjs/ui": "^2.0.0",
|
|
50
74
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
51
75
|
"@mozilla/readability": "^0.6.0",
|
|
76
|
+
"@vscode/ripgrep": "1.18.0",
|
|
77
|
+
"bun": "1.3.14",
|
|
78
|
+
"clipboardy": "^5.3.1",
|
|
52
79
|
"diff": "^9.0.0",
|
|
53
80
|
"glob": "^13.0.6",
|
|
54
81
|
"ink": "^7.1.0",
|
|
55
82
|
"linkedom": "^0.18.13",
|
|
83
|
+
"markdansi": "0.3.2",
|
|
56
84
|
"openai": "^6.45.0",
|
|
57
85
|
"react": "^19.2.7",
|
|
86
|
+
"sharp": "^0.35.3",
|
|
58
87
|
"shiki": "^4.3.1",
|
|
59
88
|
"turndown": "^7.2.4",
|
|
60
|
-
"uuid": "^14.0.1"
|
|
89
|
+
"uuid": "^14.0.1",
|
|
90
|
+
"yaml": "^2.9.0"
|
|
61
91
|
},
|
|
62
92
|
"devDependencies": {
|
|
63
93
|
"@biomejs/biome": "^2.5.2",
|
|
64
94
|
"@eslint/js": "^10.0.1",
|
|
65
95
|
"@types/bun": "^1.3.14",
|
|
96
|
+
"@types/chrome": "^0.2.2",
|
|
66
97
|
"@types/react": "^19.2.17",
|
|
67
98
|
"@types/turndown": "^5.0.6",
|
|
68
99
|
"eslint": "^10.6.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
diff --git a/dist/render.js b/dist/render.js
|
|
2
|
-
index 5de3a5d5c1e0c8bfe8dec992ca3d57987f2ba6ee..
|
|
2
|
+
index 5de3a5d5c1e0c8bfe8dec992ca3d57987f2ba6ee..9aff5e7c5553e53c92d3f1b8295e3a5995650767 100644
|
|
3
3
|
--- a/dist/render.js
|
|
4
4
|
+++ b/dist/render.js
|
|
5
5
|
@@ -572,7 +572,7 @@ function renderTable(node, ctx) {
|
|
@@ -11,7 +11,27 @@ index 5de3a5d5c1e0c8bfe8dec992ca3d57987f2ba6ee..53237b86315dc56fdb2e6295aea698ee
|
|
|
11
11
|
return wrapped.map((l) => {
|
|
12
12
|
const aligned = padCell(l, target, aligns[idx] ?? "left");
|
|
13
13
|
const padded = `${padStr}${aligned}${padStr}`;
|
|
14
|
-
@@ -
|
|
14
|
+
@@ -595,8 +595,8 @@ function renderTable(node, ctx) {
|
|
15
|
+
};
|
|
16
|
+
const headerRows = renderRow(header.children.map((c) => renderInline(c.children, ctx)), true);
|
|
17
|
+
- const bodyRows = rows.flatMap((r) => renderRow(r.children.map((c) => renderInline(c.children, ctx))));
|
|
18
|
+
+ const bodyRows = rows.map((r) => renderRow(r.children.map((c) => renderInline(c.children, ctx))));
|
|
19
|
+
if (ctx.options.tableBorder === "none") {
|
|
20
|
+
- const lines = [...headerRows, ...bodyRows].map((row) => row.join(" | ")).join("\n");
|
|
21
|
+
+ const lines = [...headerRows, ...bodyRows.flat()].map((row) => row.join(" | ")).join("\n");
|
|
22
|
+
return [`${lines}\n\n`];
|
|
23
|
+
}
|
|
24
|
+
const box = TABLE_BOX[ctx.options.tableBorder] || TABLE_BOX.unicode;
|
|
25
|
+
@@ -607,6 +607,7 @@ function renderTable(node, ctx) {
|
|
26
|
+
const renderFlat = (rowsArr) => rowsArr.map((r) => `${box.vSep}${r.map((c) => c).join(box.vSep)}${box.vSep}\n`).join("");
|
|
27
|
+
const dense = ctx.options.tableDense;
|
|
28
|
+
- const out = [top, renderFlat(headerRows), dense ? "" : mid, renderFlat(bodyRows), bottom, "\n"];
|
|
29
|
+
+ const renderedBody = bodyRows.map((row) => renderFlat(row)).join(dense ? "" : mid);
|
|
30
|
+
+ const out = [top, renderFlat(headerRows), dense ? "" : mid, renderedBody, bottom, "\n"];
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
function truncateCell(text, width, ellipsis) {
|
|
34
|
+
@@ -627,6 +628,23 @@ function sliceCellContent(text, width) {
|
|
15
35
|
}
|
|
16
36
|
return sliced;
|
|
17
37
|
}
|
|
@@ -2,42 +2,56 @@ import type { ToolDefinition } from "../tools/types";
|
|
|
2
2
|
import type {
|
|
3
3
|
BuiltContextRequest,
|
|
4
4
|
CompiledRevisionContext,
|
|
5
|
-
|
|
5
|
+
StoredContextRevisionV8,
|
|
6
6
|
SwapOverride,
|
|
7
7
|
} from "../context/context-revision";
|
|
8
8
|
import type { ProtocolContextView } from "../context/protocol-frame";
|
|
9
|
+
import type { StoredContextSurfaceV8 } from "../context/context-surface";
|
|
10
|
+
import { stableJsonStringify } from "../model/model-request-preflight";
|
|
11
|
+
import { validateUserMessage, type UserMessage } from "../image/image-types";
|
|
9
12
|
|
|
10
13
|
export class ContextBuilder {
|
|
11
14
|
build(input: {
|
|
12
15
|
canonical: ProtocolContextView;
|
|
13
|
-
revision:
|
|
16
|
+
revision: StoredContextRevisionV8;
|
|
14
17
|
activeOverrides: readonly SwapOverride[];
|
|
15
18
|
compiled: CompiledRevisionContext;
|
|
19
|
+
surface: StoredContextSurfaceV8;
|
|
16
20
|
tools: readonly ToolDefinition[];
|
|
17
|
-
|
|
21
|
+
candidateUserMessage?: UserMessage;
|
|
18
22
|
}): BuiltContextRequest {
|
|
19
23
|
if (input.canonical.sessionId !== input.compiled.sessionId) {
|
|
20
24
|
throw new Error(
|
|
21
25
|
"Compiled context and canonical history belong to different sessions.",
|
|
22
26
|
);
|
|
23
27
|
}
|
|
28
|
+
if (
|
|
29
|
+
input.surface.sessionId !== input.canonical.sessionId ||
|
|
30
|
+
input.surface.surfaceId !== input.revision.surfaceId ||
|
|
31
|
+
input.surface.surfaceSha256 !== input.revision.surfaceSha256 ||
|
|
32
|
+
stableJsonStringify(input.tools) !==
|
|
33
|
+
stableJsonStringify(input.surface.toolDefinitions)
|
|
34
|
+
) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
"Executable tool definitions do not match the active context surface.",
|
|
37
|
+
);
|
|
38
|
+
}
|
|
24
39
|
const messages = input.compiled.entries.map((entry) => entry.message);
|
|
25
|
-
if (input.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
messages.push({ role: "user", content: input.candidateUserPrompt });
|
|
40
|
+
if (input.candidateUserMessage !== undefined) {
|
|
41
|
+
validateUserMessage(input.candidateUserMessage);
|
|
42
|
+
messages.push(input.candidateUserMessage);
|
|
30
43
|
}
|
|
31
44
|
return Object.freeze({
|
|
32
45
|
canonical: input.canonical,
|
|
33
46
|
revision: input.revision,
|
|
47
|
+
surface: input.surface,
|
|
34
48
|
activeOverrides: input.activeOverrides,
|
|
35
49
|
compiled: input.compiled,
|
|
36
50
|
request: {
|
|
37
51
|
messages,
|
|
38
|
-
tools: [...input.
|
|
52
|
+
tools: [...input.surface.toolDefinitions],
|
|
39
53
|
},
|
|
40
|
-
candidateUserPromptIncluded: input.
|
|
54
|
+
candidateUserPromptIncluded: input.candidateUserMessage !== undefined,
|
|
41
55
|
});
|
|
42
56
|
}
|
|
43
57
|
}
|
|
@@ -2,8 +2,13 @@ import type { ModelContextBudget } from "../model/model-context-profile";
|
|
|
2
2
|
import type {
|
|
3
3
|
ModelRequestOutput,
|
|
4
4
|
ModelUsage,
|
|
5
|
+
MaterializedModelRequest,
|
|
5
6
|
PreparedModelRequest,
|
|
6
7
|
} from "../model/model-client";
|
|
8
|
+
import type {
|
|
9
|
+
InputTokenEstimate,
|
|
10
|
+
InputTokenEstimator,
|
|
11
|
+
} from "../model/input-token-estimator";
|
|
7
12
|
import {
|
|
8
13
|
assertContextBudget,
|
|
9
14
|
contextPressure,
|
|
@@ -63,6 +68,7 @@ export class ContextMeter {
|
|
|
63
68
|
private anchor?: MeasuredContextAnchor;
|
|
64
69
|
private lastProviderUsage?: ModelUsage;
|
|
65
70
|
private calibrationIdentity?: string;
|
|
71
|
+
private readonly providerEstimateCache = new Map<string, InputTokenEstimate>();
|
|
66
72
|
|
|
67
73
|
constructor(
|
|
68
74
|
private readonly budget: ModelContextBudget,
|
|
@@ -166,10 +172,12 @@ export class ContextMeter {
|
|
|
166
172
|
throw new Error("Cannot record provider usage before measuring the request.");
|
|
167
173
|
}
|
|
168
174
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
175
|
+
if (prepared.mediaOccurrenceCount === 0) {
|
|
176
|
+
this.calibration.record(
|
|
177
|
+
output.usage.promptTokens,
|
|
178
|
+
measurement.rawFullEstimate.totalTokens,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
173
181
|
this.lastProviderUsage = { ...output.usage };
|
|
174
182
|
const replaySegments = prepared.assistantReplaySegments(output.message);
|
|
175
183
|
const anchoredSegments = [...prepared.promptSegments, ...replaySegments];
|
|
@@ -209,6 +217,62 @@ export class ContextMeter {
|
|
|
209
217
|
};
|
|
210
218
|
}
|
|
211
219
|
|
|
220
|
+
applyProviderEstimate(
|
|
221
|
+
prepared: PreparedModelRequest,
|
|
222
|
+
estimate: {
|
|
223
|
+
inputTokens: number;
|
|
224
|
+
coverage: "messages" | "full_request";
|
|
225
|
+
},
|
|
226
|
+
): ContextUsageSnapshot {
|
|
227
|
+
if (!Number.isSafeInteger(estimate.inputTokens) || estimate.inputTokens < 0) {
|
|
228
|
+
throw new Error("Provider input estimate must be a non-negative safe integer.");
|
|
229
|
+
}
|
|
230
|
+
const local = this.measure(prepared);
|
|
231
|
+
const measurement = this.measurements.get(prepared)!;
|
|
232
|
+
const guardedTools =
|
|
233
|
+
estimate.coverage === "messages"
|
|
234
|
+
? Math.ceil(
|
|
235
|
+
measurement.rawFullEstimate.toolSchemaTokens * local.correctionFactor,
|
|
236
|
+
)
|
|
237
|
+
: 0;
|
|
238
|
+
const providerGuarded = estimate.inputTokens + guardedTools;
|
|
239
|
+
if (providerGuarded <= local.usedInputTokens) {
|
|
240
|
+
return local;
|
|
241
|
+
}
|
|
242
|
+
const snapshot: ContextUsageSnapshot = {
|
|
243
|
+
...local,
|
|
244
|
+
usedInputTokens: providerGuarded,
|
|
245
|
+
source: "provider_estimated",
|
|
246
|
+
pressure: contextPressure(providerGuarded, this.budget),
|
|
247
|
+
};
|
|
248
|
+
this.measurements.set(prepared, {
|
|
249
|
+
rawFullEstimate: measurement.rawFullEstimate,
|
|
250
|
+
snapshot,
|
|
251
|
+
});
|
|
252
|
+
return snapshot;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async estimateProviderInput(
|
|
256
|
+
prepared: MaterializedModelRequest,
|
|
257
|
+
estimator: InputTokenEstimator,
|
|
258
|
+
options: { signal: AbortSignal },
|
|
259
|
+
): Promise<InputTokenEstimate> {
|
|
260
|
+
options.signal.throwIfAborted();
|
|
261
|
+
const key = providerEstimateCacheKey(
|
|
262
|
+
prepared,
|
|
263
|
+
estimator.compatibility.coverageVersion,
|
|
264
|
+
);
|
|
265
|
+
const cached = this.providerEstimateCache.get(key);
|
|
266
|
+
if (cached !== undefined) {
|
|
267
|
+
return cached;
|
|
268
|
+
}
|
|
269
|
+
const estimate = await estimator.estimate(prepared, options);
|
|
270
|
+
options.signal.throwIfAborted();
|
|
271
|
+
const frozen = Object.freeze({ ...estimate });
|
|
272
|
+
this.providerEstimateCache.set(key, frozen);
|
|
273
|
+
return frozen;
|
|
274
|
+
}
|
|
275
|
+
|
|
212
276
|
assertWithinBudget(snapshot: ContextUsageSnapshot): void {
|
|
213
277
|
assertContextBudget({
|
|
214
278
|
usedInputTokens: snapshot.usedInputTokens,
|
|
@@ -231,6 +295,7 @@ export class ContextMeter {
|
|
|
231
295
|
this.lastProviderUsage = undefined;
|
|
232
296
|
this.measurements = new WeakMap();
|
|
233
297
|
this.calibration.clear();
|
|
298
|
+
this.providerEstimateCache.clear();
|
|
234
299
|
this.calibrationIdentity = nextIdentity;
|
|
235
300
|
throw new Error(
|
|
236
301
|
"Context revision changed the request configuration or tool schema.",
|
|
@@ -239,6 +304,7 @@ export class ContextMeter {
|
|
|
239
304
|
this.anchor = undefined;
|
|
240
305
|
this.lastProviderUsage = undefined;
|
|
241
306
|
this.measurements = new WeakMap();
|
|
307
|
+
this.providerEstimateCache.clear();
|
|
242
308
|
this.calibrationIdentity = nextIdentity;
|
|
243
309
|
}
|
|
244
310
|
|
|
@@ -249,6 +315,7 @@ export class ContextMeter {
|
|
|
249
315
|
this.measurements = new WeakMap();
|
|
250
316
|
this.calibration.clear();
|
|
251
317
|
this.calibrationIdentity = undefined;
|
|
318
|
+
this.providerEstimateCache.clear();
|
|
252
319
|
}
|
|
253
320
|
|
|
254
321
|
private usableAnchor(
|
|
@@ -263,7 +330,10 @@ export class ContextMeter {
|
|
|
263
330
|
anchor.requestConfigHash !== prepared.requestConfigHash ||
|
|
264
331
|
anchor.toolSchemaHash !== prepared.toolSchemaHash ||
|
|
265
332
|
anchor.segmentCount > prepared.promptSegments.length ||
|
|
266
|
-
prefixHashes[anchor.segmentCount] !== anchor.prefixHash
|
|
333
|
+
prefixHashes[anchor.segmentCount] !== anchor.prefixHash ||
|
|
334
|
+
prepared.promptSegments
|
|
335
|
+
.slice(anchor.segmentCount)
|
|
336
|
+
.some((segment) => (segment.media?.length ?? 0) > 0)
|
|
267
337
|
) {
|
|
268
338
|
this.anchor = undefined;
|
|
269
339
|
return undefined;
|
|
@@ -289,6 +359,20 @@ export class ContextMeter {
|
|
|
289
359
|
}
|
|
290
360
|
}
|
|
291
361
|
|
|
362
|
+
function providerEstimateCacheKey(
|
|
363
|
+
prepared: PreparedModelRequest,
|
|
364
|
+
coverageVersion: string,
|
|
365
|
+
): string {
|
|
366
|
+
return [
|
|
367
|
+
prepared.requestConfigHash,
|
|
368
|
+
prepared.toolSchemaHash,
|
|
369
|
+
lastPromptPrefixHash(
|
|
370
|
+
promptPrefixHashes(prepared.requestConfigHash, prepared.promptSegments),
|
|
371
|
+
),
|
|
372
|
+
coverageVersion,
|
|
373
|
+
].join(":");
|
|
374
|
+
}
|
|
375
|
+
|
|
292
376
|
function assertMeasuredContextAnchor(anchor: MeasuredContextAnchor): void {
|
|
293
377
|
for (const [name, value] of [
|
|
294
378
|
["promptTokens", anchor.promptTokens],
|
package/src/agent/loop.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
materializeModelRequest,
|
|
3
|
+
type MaterializedModelRequest,
|
|
4
|
+
type ModelClient,
|
|
5
|
+
type PreparedModelRequest,
|
|
6
|
+
} from "../model/model-client";
|
|
7
|
+
import type { ImageAssetStore } from "../image/image-asset-store";
|
|
2
8
|
import {
|
|
3
9
|
CommittedPrefixAuditError,
|
|
4
10
|
CommittedPrefixAuditor,
|
|
@@ -6,7 +12,7 @@ import {
|
|
|
6
12
|
import type { ObservationBuilder } from "../observation/observation-builder";
|
|
7
13
|
import type { ToolRegistry, ToolRuntime } from "../tools/registry";
|
|
8
14
|
import { ToolExecutionFatalError } from "../tools/types";
|
|
9
|
-
import type { ContextMeter } from "./context-meter";
|
|
15
|
+
import type { ContextMeter, ContextUsageSnapshot } from "./context-meter";
|
|
10
16
|
import type { RuntimeSessionContext } from "./runtime-session";
|
|
11
17
|
import type { AgentTurnLedger } from "./session-ledger";
|
|
12
18
|
import { ContextProtocolError } from "../context/context-protocol-validator";
|
|
@@ -21,6 +27,7 @@ import {
|
|
|
21
27
|
type SwapPlanner,
|
|
22
28
|
} from "../context/swap-planner";
|
|
23
29
|
import { SessionError } from "../session/session-errors";
|
|
30
|
+
import { stableJsonStringify } from "../model/model-request-preflight";
|
|
24
31
|
import {
|
|
25
32
|
normalizeSyntheticDetail,
|
|
26
33
|
type SyntheticToolCompletionInput,
|
|
@@ -59,6 +66,11 @@ export type RunAgentInput = {
|
|
|
59
66
|
runtimeSession: RuntimeSessionContext;
|
|
60
67
|
turn: TurnIdentity;
|
|
61
68
|
signal: AbortSignal;
|
|
69
|
+
assetStore?: ImageAssetStore;
|
|
70
|
+
initialRequest?: {
|
|
71
|
+
prepared: MaterializedModelRequest;
|
|
72
|
+
usage: ContextUsageSnapshot;
|
|
73
|
+
};
|
|
62
74
|
};
|
|
63
75
|
|
|
64
76
|
export class FatalAgentTurnError extends Error {
|
|
@@ -97,14 +109,20 @@ export async function runAgent(input: RunAgentInput): Promise<RunAgentResult> {
|
|
|
97
109
|
}
|
|
98
110
|
|
|
99
111
|
let prepared: PreparedModelRequest;
|
|
112
|
+
let request: PreparedModelRequest;
|
|
100
113
|
let built: BuiltContextRequest;
|
|
101
114
|
let preflight;
|
|
102
115
|
try {
|
|
103
116
|
throwIfTurnCancelled(input.signal);
|
|
104
117
|
built = input.ledger.buildModelRequest(input.tools.definitions());
|
|
105
|
-
|
|
118
|
+
const admission = iterationNumber === 1 ? input.initialRequest : undefined;
|
|
119
|
+
const candidate = input.model.prepare(built.request);
|
|
120
|
+
if (admission !== undefined && !samePreparedPlan(candidate, admission.prepared)) {
|
|
121
|
+
throw new Error("Accepted request no longer matches the first dispatch plan.");
|
|
122
|
+
}
|
|
123
|
+
prepared = admission?.prepared ?? candidate;
|
|
106
124
|
committedPrefixAuditor.audit(built.compiled.revisionId, prepared);
|
|
107
|
-
preflight = input.contextMeter.measure(prepared);
|
|
125
|
+
preflight = admission?.usage ?? input.contextMeter.measure(prepared);
|
|
108
126
|
} catch (error) {
|
|
109
127
|
if (input.signal.aborted) {
|
|
110
128
|
return cancelledResult(
|
|
@@ -135,6 +153,52 @@ export async function runAgent(input: RunAgentInput): Promise<RunAgentResult> {
|
|
|
135
153
|
return failedResult(error, iteration);
|
|
136
154
|
}
|
|
137
155
|
|
|
156
|
+
try {
|
|
157
|
+
if (prepared.mediaOccurrenceCount > 0) {
|
|
158
|
+
if (input.initialRequest?.prepared === prepared) {
|
|
159
|
+
request = prepared;
|
|
160
|
+
} else {
|
|
161
|
+
if (input.assetStore === undefined) {
|
|
162
|
+
throw new Error("Image model request has no asset store.");
|
|
163
|
+
}
|
|
164
|
+
request = await materializeModelRequest(input.model, prepared, {
|
|
165
|
+
assetStore: input.assetStore,
|
|
166
|
+
signal: input.signal,
|
|
167
|
+
});
|
|
168
|
+
if (preflight.source === "measured_plus_estimated_delta") {
|
|
169
|
+
preflight = input.contextMeter.measure(request);
|
|
170
|
+
} else {
|
|
171
|
+
const estimator = input.model.inputTokenEstimator;
|
|
172
|
+
if (estimator === undefined) {
|
|
173
|
+
throw new Error("Image model request has no input token estimator.");
|
|
174
|
+
}
|
|
175
|
+
const estimate = await input.contextMeter.estimateProviderInput(
|
|
176
|
+
request as MaterializedModelRequest,
|
|
177
|
+
estimator,
|
|
178
|
+
{ signal: input.signal },
|
|
179
|
+
);
|
|
180
|
+
preflight = input.contextMeter.applyProviderEstimate(request, estimate);
|
|
181
|
+
}
|
|
182
|
+
input.contextMeter.assertWithinBudget(preflight);
|
|
183
|
+
await input.runtimeSession.append({
|
|
184
|
+
type: "context.usage.updated",
|
|
185
|
+
...iteration,
|
|
186
|
+
data: { phase: "preflight", snapshot: preflight },
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
request = prepared;
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (input.signal.aborted) {
|
|
194
|
+
return cancelledResult(
|
|
195
|
+
cancellation(input.signal, iteration, "model_request"),
|
|
196
|
+
iteration,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return failedResult(error, iteration);
|
|
200
|
+
}
|
|
201
|
+
|
|
138
202
|
await input.runtimeSession.append({
|
|
139
203
|
type: "model.request.started",
|
|
140
204
|
...iteration,
|
|
@@ -144,7 +208,8 @@ export async function runAgent(input: RunAgentInput): Promise<RunAgentResult> {
|
|
|
144
208
|
let modelOutput;
|
|
145
209
|
try {
|
|
146
210
|
throwIfTurnCancelled(input.signal);
|
|
147
|
-
|
|
211
|
+
input.runtimeSession.prepareModelDispatch?.({ iteration, built });
|
|
212
|
+
modelOutput = await input.model.request(request, {
|
|
148
213
|
signal: input.signal,
|
|
149
214
|
identity: {
|
|
150
215
|
iteration,
|
|
@@ -167,8 +232,8 @@ export async function runAgent(input: RunAgentInput): Promise<RunAgentResult> {
|
|
|
167
232
|
input.ledger.appendAssistant({
|
|
168
233
|
iteration,
|
|
169
234
|
message: modelOutput.message,
|
|
170
|
-
provider:
|
|
171
|
-
model:
|
|
235
|
+
provider: request.provider,
|
|
236
|
+
model: request.model,
|
|
172
237
|
});
|
|
173
238
|
} catch (error) {
|
|
174
239
|
if (error instanceof ContextProtocolError) {
|
|
@@ -182,7 +247,7 @@ export async function runAgent(input: RunAgentInput): Promise<RunAgentResult> {
|
|
|
182
247
|
...iteration,
|
|
183
248
|
data: { output: modelOutput },
|
|
184
249
|
});
|
|
185
|
-
const measured = input.contextMeter.recordProviderUsage(
|
|
250
|
+
const measured = input.contextMeter.recordProviderUsage(request, modelOutput);
|
|
186
251
|
await input.runtimeSession.append({
|
|
187
252
|
type: "context.usage.updated",
|
|
188
253
|
...iteration,
|
|
@@ -259,14 +324,29 @@ export async function runAgent(input: RunAgentInput): Promise<RunAgentResult> {
|
|
|
259
324
|
}
|
|
260
325
|
|
|
261
326
|
const observation = input.observationBuilder.build({ call, raw });
|
|
262
|
-
|
|
327
|
+
const completions = [
|
|
263
328
|
{
|
|
264
329
|
call,
|
|
265
330
|
kind: "returned",
|
|
266
331
|
raw,
|
|
267
332
|
observation: observation.content,
|
|
268
333
|
},
|
|
269
|
-
]
|
|
334
|
+
] as const;
|
|
335
|
+
const committed = input.ledger.commitToolCompletions(completions);
|
|
336
|
+
if (
|
|
337
|
+
raw.kind === "skill" &&
|
|
338
|
+
raw.ok &&
|
|
339
|
+
raw.status === "loaded" &&
|
|
340
|
+
input.runtimeSession.onToolCompletionsCommitted === undefined
|
|
341
|
+
) {
|
|
342
|
+
throw new Error(
|
|
343
|
+
"Loaded Agent Skill completion has no runtime activation coordinator.",
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
input.runtimeSession.onToolCompletionsCommitted?.({
|
|
347
|
+
completions,
|
|
348
|
+
committed,
|
|
349
|
+
});
|
|
270
350
|
|
|
271
351
|
await input.runtimeSession.append({
|
|
272
352
|
type: "tool.raw_result",
|
|
@@ -342,6 +422,7 @@ async function runShadowPlanning(input: {
|
|
|
342
422
|
result = shadowPlanning.planner.plan({
|
|
343
423
|
active: input.built.compiled,
|
|
344
424
|
revision: input.built.revision,
|
|
425
|
+
surface: input.built.surface,
|
|
345
426
|
activeOverrides: input.built.activeOverrides,
|
|
346
427
|
canonical: input.built.canonical,
|
|
347
428
|
activePrepared: input.prepared,
|
|
@@ -418,6 +499,22 @@ function isCanonicalContextFailure(error: unknown): boolean {
|
|
|
418
499
|
);
|
|
419
500
|
}
|
|
420
501
|
|
|
502
|
+
function samePreparedPlan(
|
|
503
|
+
candidate: PreparedModelRequest,
|
|
504
|
+
accepted: PreparedModelRequest,
|
|
505
|
+
): boolean {
|
|
506
|
+
return (
|
|
507
|
+
candidate.provider === accepted.provider &&
|
|
508
|
+
candidate.model === accepted.model &&
|
|
509
|
+
candidate.requestConfigHash === accepted.requestConfigHash &&
|
|
510
|
+
candidate.toolSchemaHash === accepted.toolSchemaHash &&
|
|
511
|
+
candidate.requestMaxOutputTokens === accepted.requestMaxOutputTokens &&
|
|
512
|
+
candidate.mediaOccurrenceCount === accepted.mediaOccurrenceCount &&
|
|
513
|
+
stableJsonStringify(candidate.promptSegments) ===
|
|
514
|
+
stableJsonStringify(accepted.promptSegments)
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
421
518
|
function cancelledToolCompletions(
|
|
422
519
|
toolCalls: readonly ToolCall[],
|
|
423
520
|
startIndex: number,
|