tinker-agent 1.9.0 → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -1
- package/README.md +64 -6
- package/package.json +1 -1
- package/src/agent/loop.ts +13 -0
- package/src/agent/runtime-session.ts +165 -0
- package/src/agent/session-ledger.ts +20 -3
- package/src/cli/config.ts +11 -2
- package/src/cli/model-profiles.ts +58 -0
- package/src/cli/public-config-contract.ts +73 -7
- package/src/cli/run-runner.ts +4 -1
- package/src/cli/runner-dependencies.ts +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/model/fake-model-client.ts +26 -16
- package/src/model/model-api.ts +12 -0
- package/src/model/model-client.ts +9 -1
- package/src/model/moonshot-input-token-estimator.ts +5 -1
- package/src/model/openai-chat-mapping.ts +2 -24
- package/src/model/openai-chat-model-client.ts +18 -294
- package/src/model/openai-image-mapping.ts +20 -0
- package/src/model/openai-model-utils.ts +304 -0
- package/src/model/openai-responses-mapping.ts +532 -0
- package/src/model/openai-responses-model-client.ts +295 -0
- package/src/model/openai-responses-stream.ts +96 -0
- package/src/model/openai-responses-token-estimator.ts +155 -0
- package/src/model/reasoning-effort.ts +60 -0
- package/src/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 +105 -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 +82 -5
- package/src/tui/components/prompt-input.tsx +9 -1
- package/src/tui/slash-commands.ts +20 -0
- package/src/tui/tui-session-controller.ts +7 -0
package/src/tui/app.tsx
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from "../context/context-manager";
|
|
18
18
|
import { ContextBudgetExceededError } from "../model/model-request-preflight";
|
|
19
19
|
import { ModelRequestMediaAggregateError } from "../model/model-client";
|
|
20
|
+
import type { ReasoningEffortSnapshot } from "../model/reasoning-effort";
|
|
20
21
|
import type { SessionId } from "../ids/runtime-id";
|
|
21
22
|
import { readLastAssistantResponse } from "../session/session-last-response-reader";
|
|
22
23
|
import type { PromptHistory } from "./prompt-history";
|
|
@@ -180,15 +181,43 @@ export function App(props: AppProps) {
|
|
|
180
181
|
state.activeTurn === undefined &&
|
|
181
182
|
props.profiles !== undefined &&
|
|
182
183
|
props.profiles.profiles.size > 1;
|
|
184
|
+
const reasoningEffort = binding.reasoningEffort?.();
|
|
185
|
+
const hasReasoningEffort = reasoningEffort !== undefined;
|
|
183
186
|
const activeResumePicker =
|
|
184
187
|
resumePicker?.ownerSessionId === binding.sessionId ? resumePicker : undefined;
|
|
185
188
|
|
|
186
|
-
const builtInCommands =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
+
const builtInCommands = SLASH_COMMANDS.filter(
|
|
190
|
+
(command) =>
|
|
191
|
+
(command.name !== "model" || canSwitchModel) &&
|
|
192
|
+
(command.name !== "reasoning" || hasReasoningEffort),
|
|
193
|
+
);
|
|
189
194
|
const availableCommands = [...builtInCommands, ...(props.projectSlashCommands ?? [])];
|
|
190
195
|
|
|
191
196
|
const profileList = props.profiles ? [...props.profiles.profiles.values()] : [];
|
|
197
|
+
const cycleReasoningEffort = () => {
|
|
198
|
+
const current = binding.reasoningEffort?.();
|
|
199
|
+
if (current === undefined) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const currentIndex = current.supportedEfforts.indexOf(current.effort);
|
|
203
|
+
const nextIndex =
|
|
204
|
+
currentIndex < 0 ? 0 : (currentIndex + 1) % current.supportedEfforts.length;
|
|
205
|
+
const nextEffort = current.supportedEfforts[nextIndex];
|
|
206
|
+
if (nextEffort === undefined) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const updated = binding.setReasoningEffort?.(nextEffort);
|
|
211
|
+
if (updated === undefined) {
|
|
212
|
+
throw new Error("Reasoning effort control is unavailable.");
|
|
213
|
+
}
|
|
214
|
+
setNotice(
|
|
215
|
+
`Reasoning effort cycled from ${JSON.stringify(current.effort)} to ${JSON.stringify(updated.effort)} (Ctrl+R).`,
|
|
216
|
+
);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
setNotice(errorMessage(error));
|
|
219
|
+
}
|
|
220
|
+
};
|
|
192
221
|
|
|
193
222
|
useEffect(() => {
|
|
194
223
|
if (readGitBranch === undefined) {
|
|
@@ -660,6 +689,42 @@ export function App(props: AppProps) {
|
|
|
660
689
|
exit();
|
|
661
690
|
return true;
|
|
662
691
|
}
|
|
692
|
+
if (
|
|
693
|
+
command.type === "reasoning_status" ||
|
|
694
|
+
command.type === "reasoning_reset" ||
|
|
695
|
+
command.type === "reasoning_set"
|
|
696
|
+
) {
|
|
697
|
+
const current = binding.reasoningEffort?.();
|
|
698
|
+
if (current === undefined) {
|
|
699
|
+
setNotice("Current model profile does not configure reasoning effort.");
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
try {
|
|
703
|
+
if (command.type === "reasoning_status") {
|
|
704
|
+
setNotice(formatReasoningEffortStatus(current));
|
|
705
|
+
} else if (command.type === "reasoning_reset") {
|
|
706
|
+
const reset = binding.resetReasoningEffort?.();
|
|
707
|
+
if (reset === undefined) {
|
|
708
|
+
throw new Error("Reasoning effort control is unavailable.");
|
|
709
|
+
}
|
|
710
|
+
setNotice(
|
|
711
|
+
`Reasoning effort reset to profile default ${JSON.stringify(reset.defaultEffort)}.`,
|
|
712
|
+
);
|
|
713
|
+
} else {
|
|
714
|
+
const updated = binding.setReasoningEffort?.(command.effort);
|
|
715
|
+
if (updated === undefined) {
|
|
716
|
+
throw new Error("Reasoning effort control is unavailable.");
|
|
717
|
+
}
|
|
718
|
+
setNotice(
|
|
719
|
+
`Reasoning effort set to ${JSON.stringify(updated.effort)} for this session runtime (profile default: ${JSON.stringify(updated.defaultEffort)}).`,
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
} catch (error) {
|
|
723
|
+
setNotice(errorMessage(error));
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
return true;
|
|
727
|
+
}
|
|
663
728
|
if (command.type === "model" || command.type === "model_switch") {
|
|
664
729
|
if (!canSwitchModel) {
|
|
665
730
|
setNotice(
|
|
@@ -821,6 +886,7 @@ export function App(props: AppProps) {
|
|
|
821
886
|
) : (
|
|
822
887
|
<PromptInput
|
|
823
888
|
modelName={binding.modelName}
|
|
889
|
+
reasoningEffort={reasoningEffort?.effort}
|
|
824
890
|
workspaceRoot={binding.workspaceRoot}
|
|
825
891
|
gitBranch={gitBranch}
|
|
826
892
|
contextUsage={state.contextUsage}
|
|
@@ -835,6 +901,9 @@ export function App(props: AppProps) {
|
|
|
835
901
|
fileLister={props.fileLister}
|
|
836
902
|
importImage={binding.importImage}
|
|
837
903
|
verifyImageAssets={binding.verifyImageAssets}
|
|
904
|
+
onCycleReasoningEffort={
|
|
905
|
+
hasReasoningEffort ? cycleReasoningEffort : undefined
|
|
906
|
+
}
|
|
838
907
|
onSubmit={onSubmit}
|
|
839
908
|
onMaintenance={onMaintenance}
|
|
840
909
|
placeholder='Enter a coding request, or "/" for commands'
|
|
@@ -850,6 +919,14 @@ export function App(props: AppProps) {
|
|
|
850
919
|
);
|
|
851
920
|
}
|
|
852
921
|
|
|
922
|
+
function formatReasoningEffortStatus(snapshot: ReasoningEffortSnapshot): string {
|
|
923
|
+
const source =
|
|
924
|
+
snapshot.source === "profile_default"
|
|
925
|
+
? "profile default"
|
|
926
|
+
: `session override; profile default: ${snapshot.defaultEffort}`;
|
|
927
|
+
return `Reasoning effort: ${snapshot.effort} (${source}). Available: ${snapshot.supportedEfforts.join(", ")}.`;
|
|
928
|
+
}
|
|
929
|
+
|
|
853
930
|
function errorMessage(error: unknown): string {
|
|
854
931
|
return error instanceof Error ? error.message : String(error);
|
|
855
932
|
}
|
|
@@ -947,7 +1024,7 @@ export function formatContextRetirementNotice(result: ContextRetirementResult):
|
|
|
947
1024
|
const before = result.guardedTokensBefore.toLocaleString("en-US");
|
|
948
1025
|
const after = result.guardedTokensAfter.toLocaleString("en-US");
|
|
949
1026
|
if (result.outcome === "retirement_floor") {
|
|
950
|
-
return `Context prefix retired: revision ${result.previousRevisionNumber} -> ${result.revisionNumber}, ${result.retiredTurnCount} turns removed from the active request, ${before} -> ${after} estimated tokens; target ${result.targetTokens.toLocaleString("en-US")} was not reached. Run /compact first when retained tool output is still eligible. Older history remains available through
|
|
1027
|
+
return `Context prefix retired: revision ${result.previousRevisionNumber} -> ${result.revisionNumber}, ${result.retiredTurnCount} turns removed from the active request, ${before} -> ${after} estimated tokens; target ${result.targetTokens.toLocaleString("en-US")} was not reached. Run /compact first when retained tool output is still eligible. Older history remains available through RecallSearch and RecallGet.`;
|
|
951
1028
|
}
|
|
952
1029
|
const reduction =
|
|
953
1030
|
result.guardedTokensBefore === 0
|
|
@@ -957,7 +1034,7 @@ export function formatContextRetirementNotice(result: ContextRetirementResult):
|
|
|
957
1034
|
result.guardedTokensBefore) *
|
|
958
1035
|
100
|
|
959
1036
|
).toFixed(1);
|
|
960
|
-
return `Context prefix retired: revision ${result.previousRevisionNumber} -> ${result.revisionNumber}, ${result.retiredTurnCount} turns removed from the active request, ${before} -> ${after} estimated tokens (-${reduction}%). Older history remains available through
|
|
1037
|
+
return `Context prefix retired: revision ${result.previousRevisionNumber} -> ${result.revisionNumber}, ${result.retiredTurnCount} turns removed from the active request, ${before} -> ${after} estimated tokens (-${reduction}%). Older history remains available through RecallSearch and RecallGet.`;
|
|
961
1038
|
}
|
|
962
1039
|
|
|
963
1040
|
export function formatContextRetirementFailureNotice(error: unknown): string {
|
|
@@ -60,6 +60,7 @@ export type PromptSubmissionOutcome =
|
|
|
60
60
|
|
|
61
61
|
export type PromptInputProps = {
|
|
62
62
|
modelName: string;
|
|
63
|
+
reasoningEffort?: string;
|
|
63
64
|
workspaceRoot: string;
|
|
64
65
|
gitBranch?: string;
|
|
65
66
|
contextUsage?: ContextUsageSnapshot;
|
|
@@ -81,6 +82,7 @@ export type PromptInputProps = {
|
|
|
81
82
|
assets: readonly ImageAssetRef[],
|
|
82
83
|
signal: AbortSignal,
|
|
83
84
|
) => Promise<void>;
|
|
85
|
+
onCycleReasoningEffort?: () => void;
|
|
84
86
|
onSubmit: (
|
|
85
87
|
submission: PromptSubmission,
|
|
86
88
|
signal: AbortSignal,
|
|
@@ -605,6 +607,10 @@ export function PromptInput(props: PromptInputProps) {
|
|
|
605
607
|
}
|
|
606
608
|
return;
|
|
607
609
|
}
|
|
610
|
+
if (key.ctrl && input === "r") {
|
|
611
|
+
props.onCycleReasoningEffort?.();
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
608
614
|
const selectedFile = fileMatches[selectedIndex];
|
|
609
615
|
const selectedCommand = suggestions[selectedIndex];
|
|
610
616
|
if (key.return) {
|
|
@@ -736,7 +742,9 @@ export function PromptInput(props: PromptInputProps) {
|
|
|
736
742
|
{showSuggestions ? null : (
|
|
737
743
|
<Box>
|
|
738
744
|
<Text dimColor>
|
|
739
|
-
{props.modelName}
|
|
745
|
+
{props.modelName}
|
|
746
|
+
{props.reasoningEffort === undefined ? null : ` ${props.reasoningEffort}`} ·{" "}
|
|
747
|
+
{formatWorkspacePath(props.workspaceRoot)}
|
|
740
748
|
{props.gitBranch === undefined ? null : ` · ${props.gitBranch}`}
|
|
741
749
|
{state.phase.kind === "idle" ? null : ` · ${phaseLabel(state.phase)}`}
|
|
742
750
|
</Text>
|
|
@@ -65,6 +65,11 @@ export const SLASH_COMMANDS: readonly BuiltInSlashCommand[] = [
|
|
|
65
65
|
usage: "/model [profile-name]",
|
|
66
66
|
description: "Switch model profile (new session)",
|
|
67
67
|
},
|
|
68
|
+
{
|
|
69
|
+
name: "reasoning",
|
|
70
|
+
usage: "/reasoning [effort|reset]",
|
|
71
|
+
description: "Show or change reasoning effort for this session runtime",
|
|
72
|
+
},
|
|
68
73
|
{
|
|
69
74
|
name: "resume",
|
|
70
75
|
usage: "/resume [session-id]",
|
|
@@ -95,6 +100,9 @@ export type ParsedSlashCommand =
|
|
|
95
100
|
| { type: "quit" }
|
|
96
101
|
| { type: "model" }
|
|
97
102
|
| { type: "model_switch"; profileName: string }
|
|
103
|
+
| { type: "reasoning_status" }
|
|
104
|
+
| { type: "reasoning_reset" }
|
|
105
|
+
| { type: "reasoning_set"; effort: string }
|
|
98
106
|
| { type: "resume_list" }
|
|
99
107
|
| { type: "resume"; sessionId: SessionId }
|
|
100
108
|
| { type: "session_delete"; sessionId: SessionId };
|
|
@@ -193,6 +201,18 @@ export function parseSlashCommand(input: string): ParsedSlashCommand {
|
|
|
193
201
|
}
|
|
194
202
|
throw slashCommandUsageError("model");
|
|
195
203
|
}
|
|
204
|
+
if (command === "/reasoning") {
|
|
205
|
+
if (tokens.length === 1) {
|
|
206
|
+
return { type: "reasoning_status" };
|
|
207
|
+
}
|
|
208
|
+
if (tokens.length === 2 && tokens[1] === "reset") {
|
|
209
|
+
return { type: "reasoning_reset" };
|
|
210
|
+
}
|
|
211
|
+
if (tokens.length === 2) {
|
|
212
|
+
return { type: "reasoning_set", effort: tokens[1] };
|
|
213
|
+
}
|
|
214
|
+
throw slashCommandUsageError("reasoning");
|
|
215
|
+
}
|
|
196
216
|
if (command === "/resume") {
|
|
197
217
|
if (tokens.length === 1) {
|
|
198
218
|
return { type: "resume_list" };
|
|
@@ -18,6 +18,7 @@ import type { SessionId } from "../ids/runtime-id";
|
|
|
18
18
|
import { createUuidV7 } from "../ids/uuid-v7";
|
|
19
19
|
import type { ModelProfile } from "../cli/model-profiles";
|
|
20
20
|
import type { McpInventorySnapshot } from "../mcp/mcp-manager";
|
|
21
|
+
import type { ReasoningEffortSnapshot } from "../model/reasoning-effort";
|
|
21
22
|
import { SessionCatalog, type SessionSummary } from "../session/session-catalog";
|
|
22
23
|
import type { TuiProjectionStore } from "./tui-projection-store";
|
|
23
24
|
|
|
@@ -29,6 +30,9 @@ export type TuiSessionBinding = {
|
|
|
29
30
|
projectionStore: TuiProjectionStore;
|
|
30
31
|
skills(): RuntimeSkillsSnapshot;
|
|
31
32
|
mcp(): McpInventorySnapshot;
|
|
33
|
+
reasoningEffort?: () => ReasoningEffortSnapshot | undefined;
|
|
34
|
+
setReasoningEffort?: (effort: string) => ReasoningEffortSnapshot;
|
|
35
|
+
resetReasoningEffort?: () => ReasoningEffortSnapshot;
|
|
32
36
|
supportsImageInput?: () => boolean;
|
|
33
37
|
importImage?: (
|
|
34
38
|
sourcePath: string,
|
|
@@ -227,6 +231,9 @@ export function managedTuiBinding(input: {
|
|
|
227
231
|
runtimeSession: input.runtimeSession,
|
|
228
232
|
skills: () => input.runtimeSession.skills(),
|
|
229
233
|
mcp: () => input.runtimeSession.mcp(),
|
|
234
|
+
reasoningEffort: () => input.runtimeSession.reasoningEffort(),
|
|
235
|
+
setReasoningEffort: (effort) => input.runtimeSession.setReasoningEffort(effort),
|
|
236
|
+
resetReasoningEffort: () => input.runtimeSession.resetReasoningEffort(),
|
|
230
237
|
supportsImageInput: () => input.runtimeSession.supportsImageInput(),
|
|
231
238
|
importImage: (sourcePath, signal, prospectiveMessageImageCount) =>
|
|
232
239
|
input.runtimeSession.importImage(
|