@gajae-code/coding-agent 0.4.4 → 0.5.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 +83 -0
- package/dist/types/cli/fast-help.d.ts +1 -0
- package/dist/types/cli/setup-cli.d.ts +2 -0
- package/dist/types/commands/harness.d.ts +6 -0
- package/dist/types/commands/setup.d.ts +6 -0
- package/dist/types/config/model-profile-activation.d.ts +11 -2
- package/dist/types/config/model-profiles.d.ts +7 -0
- package/dist/types/config/model-registry.d.ts +6 -0
- package/dist/types/config/model-resolver.d.ts +2 -0
- package/dist/types/config/models-config-schema.d.ts +35 -0
- package/dist/types/config/settings-schema.d.ts +4 -3
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/server.d.ts +8 -2
- package/dist/types/gjc-runtime/team-runtime.d.ts +0 -1
- package/dist/types/gjc-runtime/tmux-common.d.ts +3 -0
- package/dist/types/harness-control-plane/finalize.d.ts +5 -0
- package/dist/types/harness-control-plane/owner.d.ts +1 -1
- package/dist/types/harness-control-plane/phase-rollup.d.ts +23 -0
- package/dist/types/harness-control-plane/receipt-ingest.d.ts +19 -0
- package/dist/types/harness-control-plane/receipt-spool.d.ts +19 -0
- package/dist/types/harness-control-plane/receipts.d.ts +46 -0
- package/dist/types/harness-control-plane/rpc-adapter.d.ts +3 -0
- package/dist/types/harness-control-plane/state-machine.d.ts +6 -1
- package/dist/types/harness-control-plane/types.d.ts +13 -1
- package/dist/types/hindsight/mental-models.d.ts +5 -5
- package/dist/types/main.d.ts +2 -2
- package/dist/types/modes/components/model-selector.d.ts +1 -12
- package/dist/types/modes/rpc/rpc-client.d.ts +2 -2
- package/dist/types/modes/rpc/rpc-types.d.ts +4 -1
- package/dist/types/modes/utils/abort-message.d.ts +4 -0
- package/dist/types/sdk.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/blob-store.d.ts +20 -1
- package/dist/types/session/session-manager.d.ts +32 -6
- package/dist/types/session/streaming-output.d.ts +3 -2
- package/dist/types/session/tool-choice-queue.d.ts +6 -0
- package/dist/types/setup/hermes-setup.d.ts +7 -0
- package/dist/types/task/fork-context-advisory.d.ts +13 -0
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/roi-reconciliation.d.ts +27 -0
- package/dist/types/task/types.d.ts +17 -0
- package/dist/types/thinking-metadata.d.ts +16 -0
- package/dist/types/thinking.d.ts +3 -12
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/resolve.d.ts +0 -10
- package/dist/types/utils/tool-choice.d.ts +14 -1
- package/package.json +8 -7
- package/scripts/build-binary.ts +4 -0
- package/src/cli/fast-help.ts +80 -0
- package/src/cli/setup-cli.ts +12 -3
- package/src/cli.ts +112 -17
- package/src/commands/coordinator.ts +44 -1
- package/src/commands/harness.ts +128 -11
- package/src/commands/launch.ts +2 -2
- package/src/commands/mcp-serve.ts +3 -2
- package/src/commands/session.ts +3 -1
- package/src/commands/setup.ts +4 -0
- package/src/config/model-profile-activation.ts +15 -3
- package/src/config/model-profiles.ts +255 -56
- package/src/config/model-resolver.ts +9 -6
- package/src/config/models-config-schema.ts +2 -0
- package/src/config/settings-schema.ts +6 -3
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/server.ts +427 -193
- package/src/cursor.ts +46 -4
- package/src/defaults/gjc/skills/team/SKILL.md +3 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +8 -2
- package/src/export/html/index.ts +13 -9
- package/src/gjc-runtime/launch-worktree.ts +12 -1
- package/src/gjc-runtime/session-state-sidecar.ts +38 -0
- package/src/gjc-runtime/team-runtime.ts +33 -7
- package/src/gjc-runtime/tmux-common.ts +15 -0
- package/src/gjc-runtime/tmux-sessions.ts +19 -11
- package/src/gjc-runtime/ultragoal-runtime.ts +505 -41
- package/src/gjc-runtime/workflow-manifest.generated.json +27 -1
- package/src/gjc-runtime/workflow-manifest.ts +16 -1
- package/src/harness-control-plane/finalize.ts +39 -5
- package/src/harness-control-plane/owner.ts +87 -28
- package/src/harness-control-plane/phase-rollup.ts +96 -0
- package/src/harness-control-plane/receipt-ingest.ts +127 -0
- package/src/harness-control-plane/receipt-spool.ts +128 -0
- package/src/harness-control-plane/receipts.ts +229 -1
- package/src/harness-control-plane/rpc-adapter.ts +8 -0
- package/src/harness-control-plane/state-machine.ts +27 -6
- package/src/harness-control-plane/storage.ts +23 -0
- package/src/harness-control-plane/types.ts +33 -1
- package/src/hindsight/mental-models.ts +17 -16
- package/src/internal-urls/docs-index.generated.ts +8 -7
- package/src/main.ts +7 -3
- package/src/modes/components/assistant-message.ts +26 -14
- package/src/modes/components/diff.ts +97 -0
- package/src/modes/components/model-selector.ts +353 -181
- package/src/modes/components/status-line.ts +6 -6
- package/src/modes/components/tool-execution.ts +30 -13
- package/src/modes/controllers/event-controller.ts +5 -4
- package/src/modes/controllers/selector-controller.ts +33 -42
- package/src/modes/interactive-mode.ts +4 -5
- package/src/modes/print-mode.ts +1 -1
- package/src/modes/rpc/rpc-client.ts +3 -2
- package/src/modes/rpc/rpc-mode.ts +44 -14
- package/src/modes/rpc/rpc-types.ts +5 -2
- package/src/modes/shared/agent-wire/command-dispatch.ts +10 -5
- package/src/modes/shared/agent-wire/command-validation.ts +11 -0
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/utils/abort-message.ts +41 -0
- package/src/modes/utils/context-usage.ts +15 -8
- package/src/modes/utils/ui-helpers.ts +5 -6
- package/src/sdk.ts +38 -6
- package/src/secrets/obfuscator.ts +102 -27
- package/src/session/agent-session.ts +121 -25
- package/src/session/blob-store.ts +89 -3
- package/src/session/session-manager.ts +328 -57
- package/src/session/streaming-output.ts +185 -122
- package/src/session/tool-choice-queue.ts +23 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +3 -2
- package/src/setup/hermes-setup.ts +63 -8
- package/src/task/executor.ts +69 -6
- package/src/task/fork-context-advisory.ts +99 -0
- package/src/task/index.ts +31 -2
- package/src/task/receipt.ts +7 -0
- package/src/task/render.ts +21 -1
- package/src/task/roi-reconciliation.ts +90 -0
- package/src/task/types.ts +15 -0
- package/src/thinking-metadata.ts +51 -0
- package/src/thinking.ts +26 -46
- package/src/tools/bash.ts +1 -1
- package/src/tools/index.ts +4 -2
- package/src/tools/resolve.ts +93 -18
- package/src/tools/subagent-render.ts +10 -1
- package/src/utils/edit-mode.ts +1 -1
- package/src/utils/title-generator.ts +16 -2
- package/src/utils/tool-choice.ts +45 -16
package/src/task/executor.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { type JsonSchemaValidationIssue, validateJsonSchemaValue } from "@gajae-
|
|
|
13
13
|
import { logger, prompt, untilAborted } from "@gajae-code/utils";
|
|
14
14
|
import { AsyncJobManager } from "../async";
|
|
15
15
|
import { ModelRegistry } from "../config/model-registry";
|
|
16
|
-
import { resolveModelOverrideWithAuthFallback } from "../config/model-resolver";
|
|
16
|
+
import { formatModelString, resolveModelOverrideWithAuthFallback } from "../config/model-resolver";
|
|
17
17
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
18
18
|
import { Settings } from "../config/settings";
|
|
19
19
|
import { SETTINGS_SCHEMA, type SettingPath } from "../config/settings-schema";
|
|
@@ -38,7 +38,7 @@ import { jtdToJsonSchema, normalizeSchema } from "../tools/jtd-to-json-schema";
|
|
|
38
38
|
import { type ReportFindingDetails, toReviewFinding } from "../tools/review";
|
|
39
39
|
import { ToolAbortError } from "../tools/tool-errors";
|
|
40
40
|
import type { EventBus } from "../utils/event-bus";
|
|
41
|
-
import {
|
|
41
|
+
import { buildNamedToolChoiceResult } from "../utils/tool-choice";
|
|
42
42
|
import type { WorkspaceTree } from "../workspace-tree";
|
|
43
43
|
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
44
44
|
import {
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
type AgentProgress,
|
|
47
47
|
MAX_OUTPUT_BYTES,
|
|
48
48
|
MAX_OUTPUT_LINES,
|
|
49
|
+
type ModelSubstitutionWarning,
|
|
49
50
|
type ReviewFinding,
|
|
50
51
|
type SingleResult,
|
|
51
52
|
TASK_SUBAGENT_EVENT_CHANNEL,
|
|
@@ -627,6 +628,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
627
628
|
let yieldCalled = false;
|
|
628
629
|
let pauseRequested = false;
|
|
629
630
|
let paused = false;
|
|
631
|
+
let modelSubstitutionWarning: ModelSubstitutionWarning | undefined;
|
|
632
|
+
let resolvedModelString: string | undefined;
|
|
633
|
+
let lastAssistantModelString: string | undefined;
|
|
634
|
+
let effectiveThinkingLevelForWarning: ThinkingLevel | undefined;
|
|
630
635
|
|
|
631
636
|
// Accumulate usage incrementally from message_end events (no memory for streaming events)
|
|
632
637
|
const accumulatedUsage = {
|
|
@@ -762,6 +767,14 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
762
767
|
return undefined;
|
|
763
768
|
};
|
|
764
769
|
|
|
770
|
+
const getMessageModelString = (message: unknown): string | undefined => {
|
|
771
|
+
if (!message || typeof message !== "object") return undefined;
|
|
772
|
+
const record = message as { provider?: unknown; model?: unknown };
|
|
773
|
+
return typeof record.provider === "string" && typeof record.model === "string"
|
|
774
|
+
? `${record.provider}/${record.model}`
|
|
775
|
+
: undefined;
|
|
776
|
+
};
|
|
777
|
+
|
|
765
778
|
const updateRecentOutputLines = () => {
|
|
766
779
|
const lines = recentOutputTail.split("\n").filter(line => line.trim());
|
|
767
780
|
progress.recentOutput = lines.slice(-8).reverse();
|
|
@@ -964,6 +977,29 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
964
977
|
}
|
|
965
978
|
}
|
|
966
979
|
}
|
|
980
|
+
const assistantModel = getMessageModelString(event.message);
|
|
981
|
+
if (assistantModel) {
|
|
982
|
+
lastAssistantModelString = assistantModel;
|
|
983
|
+
if (resolvedModelString && assistantModel !== resolvedModelString && !modelSubstitutionWarning) {
|
|
984
|
+
modelSubstitutionWarning = {
|
|
985
|
+
requested: resolvedModelString,
|
|
986
|
+
effective: assistantModel,
|
|
987
|
+
reason: "assistant_model_mismatch",
|
|
988
|
+
};
|
|
989
|
+
progress.modelSubstitutionWarning = modelSubstitutionWarning;
|
|
990
|
+
activeSession?.sessionManager.appendModelChange(assistantModel, undefined, {
|
|
991
|
+
previousModel: resolvedModelString,
|
|
992
|
+
reason: modelSubstitutionWarning.reason,
|
|
993
|
+
thinkingLevel: effectiveThinkingLevelForWarning ?? null,
|
|
994
|
+
});
|
|
995
|
+
logger.warn("Subagent assistant response reported a substituted effective model", {
|
|
996
|
+
requested: resolvedModelString,
|
|
997
|
+
effective: assistantModel,
|
|
998
|
+
agent: agent.name,
|
|
999
|
+
id,
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
967
1003
|
}
|
|
968
1004
|
// Extract and accumulate usage (prefer message.usage, fallback to event.usage)
|
|
969
1005
|
const messageUsage = getMessageUsage(event.message) || (event as AgentEvent & { usage?: unknown }).usage;
|
|
@@ -1090,6 +1126,8 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1090
1126
|
thinkingLevel: resolvedThinkingLevel,
|
|
1091
1127
|
explicitThinkingLevel,
|
|
1092
1128
|
authFallbackUsed,
|
|
1129
|
+
requestedModel,
|
|
1130
|
+
fallbackReason,
|
|
1093
1131
|
} = await awaitAbortable(
|
|
1094
1132
|
resolveModelOverrideWithAuthFallback(
|
|
1095
1133
|
modelPatterns,
|
|
@@ -1099,9 +1137,18 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1099
1137
|
options.parentSessionId,
|
|
1100
1138
|
),
|
|
1101
1139
|
);
|
|
1102
|
-
if (
|
|
1140
|
+
if (model) {
|
|
1141
|
+
resolvedModelString = formatModelString(model);
|
|
1142
|
+
}
|
|
1143
|
+
if (authFallbackUsed && model && requestedModel) {
|
|
1144
|
+
modelSubstitutionWarning = {
|
|
1145
|
+
requested: formatModelString(requestedModel),
|
|
1146
|
+
effective: formatModelString(model),
|
|
1147
|
+
reason: fallbackReason ?? "auth_unavailable",
|
|
1148
|
+
};
|
|
1149
|
+
progress.modelSubstitutionWarning = modelSubstitutionWarning;
|
|
1103
1150
|
logger.warn("Subagent model has no working credentials; falling back to parent session model", {
|
|
1104
|
-
requested:
|
|
1151
|
+
requested: modelSubstitutionWarning.requested,
|
|
1105
1152
|
parentModel: options.parentActiveModelPattern,
|
|
1106
1153
|
resolvedProvider: model.provider,
|
|
1107
1154
|
resolvedModel: model.id,
|
|
@@ -1113,6 +1160,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1113
1160
|
const effectiveThinkingLevel = explicitThinkingLevel
|
|
1114
1161
|
? resolvedThinkingLevel
|
|
1115
1162
|
: (thinkingLevel ?? resolvedThinkingLevel);
|
|
1163
|
+
effectiveThinkingLevelForWarning = effectiveThinkingLevel;
|
|
1116
1164
|
|
|
1117
1165
|
const sessionManager = sessionFile
|
|
1118
1166
|
? await awaitAbortable(SessionManager.open(sessionFile))
|
|
@@ -1174,6 +1222,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1174
1222
|
settings: subagentSettings,
|
|
1175
1223
|
model,
|
|
1176
1224
|
thinkingLevel: effectiveThinkingLevel,
|
|
1225
|
+
modelSubstitution:
|
|
1226
|
+
modelSubstitutionWarning?.reason === "auth_unavailable" && requestedModel
|
|
1227
|
+
? { requestedModel, reason: modelSubstitutionWarning.reason }
|
|
1228
|
+
: undefined,
|
|
1177
1229
|
toolNames,
|
|
1178
1230
|
outputSchema,
|
|
1179
1231
|
requireYieldTool: true,
|
|
@@ -1412,7 +1464,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1412
1464
|
await awaitAbortable(session.waitForIdle());
|
|
1413
1465
|
}
|
|
1414
1466
|
|
|
1415
|
-
const
|
|
1467
|
+
const reminderToolChoiceResult = buildNamedToolChoiceResult("yield", session.model);
|
|
1416
1468
|
|
|
1417
1469
|
let retryCount = 0;
|
|
1418
1470
|
while (!paused && !yieldCalled && retryCount < MAX_YIELD_RETRIES && !abortSignal.aborted) {
|
|
@@ -1433,7 +1485,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1433
1485
|
await awaitAbortable(
|
|
1434
1486
|
session.prompt(reminder, {
|
|
1435
1487
|
attribution: "agent",
|
|
1436
|
-
...(isFinalRetry &&
|
|
1488
|
+
...(isFinalRetry && reminderToolChoiceResult.exactNamed && reminderToolChoiceResult.choice
|
|
1489
|
+
? { toolChoice: reminderToolChoiceResult.choice }
|
|
1490
|
+
: {}),
|
|
1437
1491
|
}),
|
|
1438
1492
|
);
|
|
1439
1493
|
await awaitAbortable(session.waitForIdle());
|
|
@@ -1466,6 +1520,14 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1466
1520
|
error = undefined;
|
|
1467
1521
|
}
|
|
1468
1522
|
}
|
|
1523
|
+
if (lastAssistantModelString && resolvedModelString && lastAssistantModelString !== resolvedModelString) {
|
|
1524
|
+
modelSubstitutionWarning ??= {
|
|
1525
|
+
requested: resolvedModelString,
|
|
1526
|
+
effective: lastAssistantModelString,
|
|
1527
|
+
reason: "assistant_model_mismatch",
|
|
1528
|
+
};
|
|
1529
|
+
progress.modelSubstitutionWarning = modelSubstitutionWarning;
|
|
1530
|
+
}
|
|
1469
1531
|
} catch (err) {
|
|
1470
1532
|
exitCode = 1;
|
|
1471
1533
|
if (!abortSignal.aborted) {
|
|
@@ -1642,6 +1704,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1642
1704
|
contextTokens: progress.contextTokens,
|
|
1643
1705
|
contextWindow: progress.contextWindow,
|
|
1644
1706
|
modelOverride,
|
|
1707
|
+
modelSubstitutionWarning,
|
|
1645
1708
|
error: exitCode !== 0 && stderr ? stderr : undefined,
|
|
1646
1709
|
aborted: wasAborted,
|
|
1647
1710
|
abortReason: finalAbortReason,
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { ForkContextMode } from "./types";
|
|
2
|
+
|
|
3
|
+
export interface ForkContextAdvisory {
|
|
4
|
+
recommendedMode: ForkContextMode;
|
|
5
|
+
reasons: string[];
|
|
6
|
+
estimatedClonedTokens: Record<ForkContextMode, number>;
|
|
7
|
+
callerModeRespected: true;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Per-mode clone budget ceilings (advisory estimates only). These bound how
|
|
12
|
+
* many parent-context tokens each mode may clone into the child; the actual
|
|
13
|
+
* cloned amount can never exceed the parent context itself.
|
|
14
|
+
*/
|
|
15
|
+
const CLONE_BUDGET_BY_MODE = {
|
|
16
|
+
none: 0,
|
|
17
|
+
receipt: 2000,
|
|
18
|
+
"last-turn": 4000,
|
|
19
|
+
bounded: 8000,
|
|
20
|
+
full: 15000,
|
|
21
|
+
} as const satisfies Record<ForkContextMode, number>;
|
|
22
|
+
|
|
23
|
+
const RECEIPT_TRIGGERS = [
|
|
24
|
+
{ pattern: /as discussed/i, reason: "prior-session-reference:as-discussed" },
|
|
25
|
+
{ pattern: /as decided/i, reason: "prior-session-reference:as-decided" },
|
|
26
|
+
{ pattern: /earlier in this session/i, reason: "prior-session-reference:earlier-in-this-session" },
|
|
27
|
+
{ pattern: /per the plan above/i, reason: "prior-session-reference:per-the-plan-above" },
|
|
28
|
+
{ pattern: /the previous review/i, reason: "prior-session-reference:the-previous-review" },
|
|
29
|
+
{ pattern: /\.gjc\/plans\//i, reason: "prior-session-reference:gjc-plans-path" },
|
|
30
|
+
{ pattern: /\.gjc\/specs\//i, reason: "prior-session-reference:gjc-specs-path" },
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
const LAST_TURN_TRIGGERS = [
|
|
34
|
+
{ pattern: /the last message/i, reason: "last-turn-reference:the-last-message" },
|
|
35
|
+
{ pattern: /the previous turn/i, reason: "last-turn-reference:the-previous-turn" },
|
|
36
|
+
{ pattern: /see above/i, reason: "last-turn-reference:see-above" },
|
|
37
|
+
] as const;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Estimated tokens cloned into the child per mode: the per-mode budget
|
|
41
|
+
* ceiling, capped by the actual parent context (you can never clone more
|
|
42
|
+
* than exists). Negative parent contexts are normalized to 0.
|
|
43
|
+
*/
|
|
44
|
+
function estimateClonedTokens(parentContextTokens: number): Record<ForkContextMode, number> {
|
|
45
|
+
const parent = Math.max(0, parentContextTokens);
|
|
46
|
+
return {
|
|
47
|
+
none: Math.min(parent, CLONE_BUDGET_BY_MODE.none),
|
|
48
|
+
receipt: Math.min(parent, CLONE_BUDGET_BY_MODE.receipt),
|
|
49
|
+
"last-turn": Math.min(parent, CLONE_BUDGET_BY_MODE["last-turn"]),
|
|
50
|
+
bounded: Math.min(parent, CLONE_BUDGET_BY_MODE.bounded),
|
|
51
|
+
full: Math.min(parent, CLONE_BUDGET_BY_MODE.full),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function adviseForkContextMode(input: {
|
|
56
|
+
assignment: string;
|
|
57
|
+
context?: string;
|
|
58
|
+
explicitMode?: ForkContextMode;
|
|
59
|
+
parentContextTokens?: number;
|
|
60
|
+
}): ForkContextAdvisory {
|
|
61
|
+
const parentContextTokens = input.parentContextTokens ?? 0;
|
|
62
|
+
const estimatedClonedTokens = estimateClonedTokens(parentContextTokens);
|
|
63
|
+
|
|
64
|
+
if (input.explicitMode !== undefined) {
|
|
65
|
+
return {
|
|
66
|
+
recommendedMode: input.explicitMode,
|
|
67
|
+
reasons: ["explicit-caller-mode"],
|
|
68
|
+
estimatedClonedTokens,
|
|
69
|
+
callerModeRespected: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const text = `${input.assignment}\n${input.context ?? ""}`;
|
|
74
|
+
const reasons: string[] = [];
|
|
75
|
+
let recommendedMode: ForkContextMode = "none";
|
|
76
|
+
|
|
77
|
+
for (const trigger of LAST_TURN_TRIGGERS) {
|
|
78
|
+
if (trigger.pattern.test(text)) {
|
|
79
|
+
reasons.push(trigger.reason);
|
|
80
|
+
recommendedMode = "last-turn";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const trigger of RECEIPT_TRIGGERS) {
|
|
85
|
+
if (trigger.pattern.test(text)) {
|
|
86
|
+
reasons.push(trigger.reason);
|
|
87
|
+
if (recommendedMode === "none") {
|
|
88
|
+
recommendedMode = "receipt";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
recommendedMode,
|
|
95
|
+
reasons,
|
|
96
|
+
estimatedClonedTokens,
|
|
97
|
+
callerModeRespected: true,
|
|
98
|
+
};
|
|
99
|
+
}
|
package/src/task/index.ts
CHANGED
|
@@ -47,11 +47,13 @@ import { generateCommitMessage } from "../utils/commit-message-generator";
|
|
|
47
47
|
import * as git from "../utils/git";
|
|
48
48
|
import { discoverAgents, filterVisibleAgents, getAgent } from "./discovery";
|
|
49
49
|
import { runSubprocess } from "./executor";
|
|
50
|
+
import { adviseForkContextMode } from "./fork-context-advisory";
|
|
50
51
|
import { getTaskIdValidationError, validateAllocatedTaskId } from "./id";
|
|
51
52
|
import { AgentOutputManager } from "./output-manager";
|
|
52
53
|
import { mapWithConcurrencyLimit, Semaphore } from "./parallel";
|
|
53
54
|
import { assertNoRawTaskFields, buildTaskReceipt, buildTaskRoiSummary } from "./receipt";
|
|
54
55
|
import { renderResult, renderCall as renderTaskCall } from "./render";
|
|
56
|
+
import { reconcileSpawnRoi } from "./roi-reconciliation";
|
|
55
57
|
import { getTaskSimpleModeCapabilities, type TaskSimpleMode } from "./simple-mode";
|
|
56
58
|
import { DEFAULT_SPAWN_THRESHOLD, evaluateReviewerExploreGate, evaluateSpawnGate } from "./spawn-gate";
|
|
57
59
|
import {
|
|
@@ -578,6 +580,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
578
580
|
runMode: message ? "message" : "resume",
|
|
579
581
|
resumeMessage: message,
|
|
580
582
|
sessionFiles: new Map([[descriptor.task.id, descriptor.sessionFile]]),
|
|
583
|
+
suppressRoiReconciliation: true,
|
|
581
584
|
},
|
|
582
585
|
);
|
|
583
586
|
const finalText = result.content.find(part => part.type === "text")?.text ?? "(no output)";
|
|
@@ -675,6 +678,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
675
678
|
frozenForkSeeds,
|
|
676
679
|
{
|
|
677
680
|
sessionFiles: new Map([[uniqueId, subtaskSessionFile]]),
|
|
681
|
+
suppressRoiReconciliation: true,
|
|
678
682
|
},
|
|
679
683
|
);
|
|
680
684
|
const finalText = result.content.find(part => part.type === "text")?.text ?? "(no output)";
|
|
@@ -874,6 +878,12 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
874
878
|
runMode?: "initial" | "resume" | "message";
|
|
875
879
|
resumeMessage?: string;
|
|
876
880
|
sessionFiles?: ReadonlyMap<string, string | null>;
|
|
881
|
+
/**
|
|
882
|
+
* Set for per-child async runs: the spawnPlan is carried for gate
|
|
883
|
+
* consistency, but batch-level ROI reconciliation must not be computed
|
|
884
|
+
* against a single child's receipts.
|
|
885
|
+
*/
|
|
886
|
+
suppressRoiReconciliation?: boolean;
|
|
877
887
|
},
|
|
878
888
|
): Promise<AgentToolResult<TaskToolDetails>> {
|
|
879
889
|
const startTime = Date.now();
|
|
@@ -1293,6 +1303,17 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1293
1303
|
const forkContext = requestsForkContext(task)
|
|
1294
1304
|
? { mode: task.inheritContext, clonedTokens: forkContextSeed?.metadata.approximateTokens ?? 0 }
|
|
1295
1305
|
: undefined;
|
|
1306
|
+
// Advisory-only recommendation (logged on the receipt); never overrides
|
|
1307
|
+
// the caller's explicit inheritContext mode.
|
|
1308
|
+
const advisory = adviseForkContextMode({
|
|
1309
|
+
assignment: task.assignment,
|
|
1310
|
+
context: sharedContext,
|
|
1311
|
+
explicitMode: task.inheritContext,
|
|
1312
|
+
});
|
|
1313
|
+
const forkContextAdvisory = {
|
|
1314
|
+
recommendedMode: advisory.recommendedMode,
|
|
1315
|
+
reasons: advisory.reasons,
|
|
1316
|
+
};
|
|
1296
1317
|
const taskSessionFile = overrides?.sessionFile ?? executionOverrides?.sessionFiles?.get(task.id) ?? null;
|
|
1297
1318
|
if (!isIsolated) {
|
|
1298
1319
|
const result = await runSubprocess({
|
|
@@ -1341,7 +1362,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1341
1362
|
parentTelemetry: this.session.getTelemetry?.(),
|
|
1342
1363
|
forkContextSeed,
|
|
1343
1364
|
});
|
|
1344
|
-
return
|
|
1365
|
+
return { ...result, ...(forkContext ? { forkContext } : {}), forkContextAdvisory };
|
|
1345
1366
|
}
|
|
1346
1367
|
|
|
1347
1368
|
const taskStart = Date.now();
|
|
@@ -1402,7 +1423,11 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1402
1423
|
parentTelemetry: this.session.getTelemetry?.(),
|
|
1403
1424
|
forkContextSeed,
|
|
1404
1425
|
});
|
|
1405
|
-
const resultWithForkContext =
|
|
1426
|
+
const resultWithForkContext = {
|
|
1427
|
+
...result,
|
|
1428
|
+
...(forkContext ? { forkContext } : {}),
|
|
1429
|
+
forkContextAdvisory,
|
|
1430
|
+
};
|
|
1406
1431
|
if (mergeMode === "branch" && resultWithForkContext.exitCode === 0) {
|
|
1407
1432
|
try {
|
|
1408
1433
|
const commitMsg =
|
|
@@ -1697,6 +1722,9 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1697
1722
|
|
|
1698
1723
|
const receipts = results.map(buildTaskReceipt);
|
|
1699
1724
|
const roiSummary = buildTaskRoiSummary(receipts);
|
|
1725
|
+
const roiReconciliation = executionOverrides?.suppressRoiReconciliation
|
|
1726
|
+
? undefined
|
|
1727
|
+
: reconcileSpawnRoi(params.spawnPlan, receipts);
|
|
1700
1728
|
const summaries = receipts.map(r => {
|
|
1701
1729
|
const status = r.status === "merge_failed" ? "merge failed" : r.status;
|
|
1702
1730
|
return {
|
|
@@ -1739,6 +1767,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1739
1767
|
usage: hasAggregatedUsage ? aggregatedUsage : undefined,
|
|
1740
1768
|
forkContextClonedTokens: forkContextClonedTokens > 0 ? forkContextClonedTokens : undefined,
|
|
1741
1769
|
roiSummary,
|
|
1770
|
+
roiReconciliation,
|
|
1742
1771
|
};
|
|
1743
1772
|
assertNoRawTaskFields(details, "task.return.details");
|
|
1744
1773
|
return {
|
package/src/task/receipt.ts
CHANGED
|
@@ -29,6 +29,7 @@ export interface TaskResultReceipt {
|
|
|
29
29
|
contextTokens?: number;
|
|
30
30
|
contextWindow?: number;
|
|
31
31
|
modelOverride?: string | string[];
|
|
32
|
+
modelSubstitutionWarning?: SingleResult["modelSubstitutionWarning"];
|
|
32
33
|
usage?: SingleResult["usage"];
|
|
33
34
|
cost?: number;
|
|
34
35
|
branchName?: string;
|
|
@@ -46,6 +47,7 @@ export interface TaskResultReceipt {
|
|
|
46
47
|
};
|
|
47
48
|
extractedToolCounts?: Record<string, number>;
|
|
48
49
|
forkContext?: SingleResult["forkContext"];
|
|
50
|
+
forkContextAdvisory?: SingleResult["forkContextAdvisory"];
|
|
49
51
|
roi?: TaskRoi;
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -77,6 +79,9 @@ function truncateText(value: string | undefined, maxChars: number): string | und
|
|
|
77
79
|
|
|
78
80
|
function buildSafeSynopsis(raw: SingleResult, outputRef: TaskResultReceipt["outputRef"]): string {
|
|
79
81
|
const status = getStatus(raw);
|
|
82
|
+
if (raw.modelSubstitutionWarning) {
|
|
83
|
+
return `Task ${status}; requested model substituted from ${raw.modelSubstitutionWarning.requested} to ${raw.modelSubstitutionWarning.effective}.`;
|
|
84
|
+
}
|
|
80
85
|
if (raw.retryFailure) {
|
|
81
86
|
return `Task ${status}; retry stopped after attempt ${raw.retryFailure.attempt}.`;
|
|
82
87
|
}
|
|
@@ -219,6 +224,7 @@ export function buildTaskReceipt(raw: SingleResult): TaskResultReceipt {
|
|
|
219
224
|
contextTokens: raw.contextTokens,
|
|
220
225
|
contextWindow: raw.contextWindow,
|
|
221
226
|
modelOverride: raw.modelOverride,
|
|
227
|
+
modelSubstitutionWarning: raw.modelSubstitutionWarning,
|
|
222
228
|
usage: raw.usage,
|
|
223
229
|
cost: raw.usage?.cost.total,
|
|
224
230
|
branchName: raw.branchName,
|
|
@@ -234,6 +240,7 @@ export function buildTaskReceipt(raw: SingleResult): TaskResultReceipt {
|
|
|
234
240
|
review: buildReview(raw),
|
|
235
241
|
extractedToolCounts,
|
|
236
242
|
forkContext: raw.forkContext,
|
|
243
|
+
forkContextAdvisory: raw.forkContextAdvisory,
|
|
237
244
|
roi: buildTaskRoi(raw),
|
|
238
245
|
};
|
|
239
246
|
}
|
package/src/task/render.ts
CHANGED
|
@@ -119,6 +119,10 @@ function normalizeReportFindings(value: unknown): ReportFindingDetails[] {
|
|
|
119
119
|
return findings;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
function formatModelSubstitutionWarning(warning: { requested: string; effective: string }): string {
|
|
123
|
+
return `Requested model substituted: ${warning.requested} -> ${warning.effective}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
122
126
|
function formatJsonScalar(value: unknown, _theme: Theme): string {
|
|
123
127
|
if (value === null) return "null";
|
|
124
128
|
if (typeof value === "string") {
|
|
@@ -566,6 +570,14 @@ function renderAgentProgress(
|
|
|
566
570
|
lines.push(statusLine);
|
|
567
571
|
|
|
568
572
|
lines.push(...renderTaskSection(progress.assignment ?? progress.task, continuePrefix, expanded, theme));
|
|
573
|
+
if (progress.modelSubstitutionWarning) {
|
|
574
|
+
lines.push(
|
|
575
|
+
`${continuePrefix}${theme.fg(
|
|
576
|
+
"warning",
|
|
577
|
+
truncateToWidth(replaceTabs(formatModelSubstitutionWarning(progress.modelSubstitutionWarning)), 90),
|
|
578
|
+
)}`,
|
|
579
|
+
);
|
|
580
|
+
}
|
|
569
581
|
|
|
570
582
|
// Current tool (if running) or most recent completed tool
|
|
571
583
|
if (progress.status === "running") {
|
|
@@ -862,9 +874,17 @@ function renderAgentResult(result: TaskResultReceipt, isLast: boolean, expanded:
|
|
|
862
874
|
}
|
|
863
875
|
}
|
|
864
876
|
}
|
|
865
|
-
} else {
|
|
877
|
+
} else if (!result.modelSubstitutionWarning) {
|
|
866
878
|
lines.push(...renderOutputSection(result.preview, continuePrefix, expanded, theme, 3, 12));
|
|
867
879
|
}
|
|
880
|
+
if (result.modelSubstitutionWarning) {
|
|
881
|
+
lines.push(
|
|
882
|
+
`${continuePrefix}${theme.fg(
|
|
883
|
+
"warning",
|
|
884
|
+
truncateToWidth(replaceTabs(formatModelSubstitutionWarning(result.modelSubstitutionWarning)), 90),
|
|
885
|
+
)}`,
|
|
886
|
+
);
|
|
887
|
+
}
|
|
868
888
|
if (result.roi?.lowRoi) {
|
|
869
889
|
lines.push(`${continuePrefix}${theme.fg("warning", "low ROI: produced no material contribution")}`);
|
|
870
890
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { TaskResultReceipt } from "./receipt";
|
|
2
|
+
import type { SpawnPlanReceipt } from "./spawn-gate";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pure, advisory-only reconciliation between a spawn plan's inline-token promise
|
|
6
|
+
* and receipt-safe child outputs. These signals never change task success/failure
|
|
7
|
+
* semantics or runtime behavior; they only describe budget/ROI observations for
|
|
8
|
+
* model-facing summaries.
|
|
9
|
+
*/
|
|
10
|
+
export interface SpawnRoiChildReconciliation {
|
|
11
|
+
id: string;
|
|
12
|
+
inlineTokens: number;
|
|
13
|
+
maxInlineTokens: number;
|
|
14
|
+
overBudget: boolean;
|
|
15
|
+
overageTokens: number;
|
|
16
|
+
lowRoi: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SpawnRoiReconciliation {
|
|
20
|
+
childCount: number;
|
|
21
|
+
promisedMaxInlineTokens: number;
|
|
22
|
+
children: SpawnRoiChildReconciliation[];
|
|
23
|
+
overBudgetChildIds: string[];
|
|
24
|
+
lowRoiChildIds: string[];
|
|
25
|
+
totalInlineTokens: number;
|
|
26
|
+
totalOverageTokens: number;
|
|
27
|
+
advisoryFlags: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function estimateTextTokens(text: string | undefined): number {
|
|
31
|
+
if (!text) return 0;
|
|
32
|
+
return Math.ceil(text.length / 4);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Estimate model-facing inline cost from receipt-safe fields only. The proxy uses
|
|
37
|
+
* one token per four characters, rounded up independently for the preview and
|
|
38
|
+
* review summaries that may be inlined for the parent model.
|
|
39
|
+
*/
|
|
40
|
+
function estimateInlineTokens(receipt: TaskResultReceipt): number {
|
|
41
|
+
const review = receipt.review;
|
|
42
|
+
const reviewSummaryChars =
|
|
43
|
+
(review?.overallCorrectness?.length ?? 0) +
|
|
44
|
+
(review?.findings?.reduce((total, finding) => total + finding.summary.length, 0) ?? 0);
|
|
45
|
+
return estimateTextTokens(receipt.preview) + Math.ceil(reviewSummaryChars / 4);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function reconcileSpawnRoi(
|
|
49
|
+
plan: SpawnPlanReceipt | undefined,
|
|
50
|
+
receipts: readonly TaskResultReceipt[],
|
|
51
|
+
): SpawnRoiReconciliation | undefined {
|
|
52
|
+
if (!plan) return undefined;
|
|
53
|
+
|
|
54
|
+
const children = receipts.map(receipt => {
|
|
55
|
+
const inlineTokens = estimateInlineTokens(receipt);
|
|
56
|
+
const overageTokens = Math.max(0, inlineTokens - plan.maxInlineTokens);
|
|
57
|
+
return {
|
|
58
|
+
id: receipt.id,
|
|
59
|
+
inlineTokens,
|
|
60
|
+
maxInlineTokens: plan.maxInlineTokens,
|
|
61
|
+
overBudget: overageTokens > 0,
|
|
62
|
+
overageTokens,
|
|
63
|
+
lowRoi: Boolean(receipt.roi?.lowRoi),
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
const overBudgetChildIds = children
|
|
67
|
+
.filter(child => child.overBudget)
|
|
68
|
+
.map(child => child.id)
|
|
69
|
+
.toSorted();
|
|
70
|
+
const lowRoiChildIds = children
|
|
71
|
+
.filter(child => child.lowRoi)
|
|
72
|
+
.map(child => child.id)
|
|
73
|
+
.toSorted();
|
|
74
|
+
const advisoryFlags = [
|
|
75
|
+
...(overBudgetChildIds.length > 0 ? ["over-inline-budget"] : []),
|
|
76
|
+
...(lowRoiChildIds.length > 0 ? ["low-roi-children"] : []),
|
|
77
|
+
...(children.length > 0 && lowRoiChildIds.length === children.length ? ["all-children-low-roi"] : []),
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
childCount: children.length,
|
|
82
|
+
promisedMaxInlineTokens: plan.maxInlineTokens,
|
|
83
|
+
children,
|
|
84
|
+
overBudgetChildIds,
|
|
85
|
+
lowRoiChildIds,
|
|
86
|
+
totalInlineTokens: children.reduce((total, child) => total + child.inlineTokens, 0),
|
|
87
|
+
totalOverageTokens: children.reduce((total, child) => total + child.overageTokens, 0),
|
|
88
|
+
advisoryFlags,
|
|
89
|
+
};
|
|
90
|
+
}
|
package/src/task/types.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { $env } from "@gajae-code/utils";
|
|
|
4
4
|
import * as z from "zod/v4";
|
|
5
5
|
import { isValidTaskId, TASK_ID_DESCRIPTION } from "./id";
|
|
6
6
|
import type { TaskResultReceipt } from "./receipt";
|
|
7
|
+
import type { SpawnRoiReconciliation } from "./roi-reconciliation";
|
|
7
8
|
import { getTaskSimpleModeCapabilities, type TaskSimpleMode } from "./simple-mode";
|
|
8
9
|
import type { SpawnPlanReceipt } from "./spawn-gate";
|
|
9
10
|
import type { NestedRepoPatch } from "./worktree";
|
|
@@ -214,6 +215,12 @@ export interface AgentDefinition {
|
|
|
214
215
|
filePath?: string;
|
|
215
216
|
}
|
|
216
217
|
|
|
218
|
+
export interface ModelSubstitutionWarning {
|
|
219
|
+
requested: string;
|
|
220
|
+
effective: string;
|
|
221
|
+
reason: "auth_unavailable" | "assistant_model_mismatch";
|
|
222
|
+
}
|
|
223
|
+
|
|
217
224
|
/** Progress tracking for a single agent */
|
|
218
225
|
export interface AgentProgress {
|
|
219
226
|
index: number;
|
|
@@ -246,6 +253,7 @@ export interface AgentProgress {
|
|
|
246
253
|
cost: number;
|
|
247
254
|
durationMs: number;
|
|
248
255
|
modelOverride?: string | string[];
|
|
256
|
+
modelSubstitutionWarning?: ModelSubstitutionWarning;
|
|
249
257
|
/** Data extracted by registered subprocess tool handlers (keyed by tool name) */
|
|
250
258
|
extractedToolData?: Record<string, unknown[]>;
|
|
251
259
|
/**
|
|
@@ -305,6 +313,7 @@ export interface SingleResult {
|
|
|
305
313
|
/** Model's context window in tokens, when known. */
|
|
306
314
|
contextWindow?: number;
|
|
307
315
|
modelOverride?: string | string[];
|
|
316
|
+
modelSubstitutionWarning?: ModelSubstitutionWarning;
|
|
308
317
|
error?: string;
|
|
309
318
|
aborted?: boolean;
|
|
310
319
|
abortReason?: string;
|
|
@@ -337,6 +346,11 @@ export interface SingleResult {
|
|
|
337
346
|
outputMeta?: { lineCount: number; charCount: number; byteSize?: number; sha256?: string };
|
|
338
347
|
/** Fork-context seed accounting for this subagent, when inherited parent context was cloned. */
|
|
339
348
|
forkContext?: { mode: ForkContextMode; clonedTokens: number };
|
|
349
|
+
/**
|
|
350
|
+
* Advisory fork-context mode recommendation for this task (logged only;
|
|
351
|
+
* never changes the actual mode selection).
|
|
352
|
+
*/
|
|
353
|
+
forkContextAdvisory?: { recommendedMode: ForkContextMode; reasons: string[] };
|
|
340
354
|
}
|
|
341
355
|
|
|
342
356
|
/** Tool details for TUI rendering */
|
|
@@ -356,6 +370,7 @@ export interface TaskToolDetails {
|
|
|
356
370
|
/** Advisory ids for terminal children that spent tokens without detectable output/review/changes. */
|
|
357
371
|
lowRoiChildIds: string[];
|
|
358
372
|
};
|
|
373
|
+
roiReconciliation?: SpawnRoiReconciliation;
|
|
359
374
|
progress?: AgentProgress[];
|
|
360
375
|
async?: {
|
|
361
376
|
state: "running" | "paused" | "queued" | "completed" | "failed";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type ThinkingLevelValue = "inherit" | "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Metadata used to render thinking selector values in the coding-agent UI.
|
|
5
|
+
*
|
|
6
|
+
* This module is intentionally provider/native-free so schema generation can
|
|
7
|
+
* import settings metadata before native addons have been built in CI.
|
|
8
|
+
*/
|
|
9
|
+
export interface ThinkingLevelMetadata {
|
|
10
|
+
value: ThinkingLevelValue;
|
|
11
|
+
label: string;
|
|
12
|
+
description: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const THINKING_LEVEL_METADATA: Record<ThinkingLevelValue, ThinkingLevelMetadata> = {
|
|
16
|
+
inherit: {
|
|
17
|
+
value: "inherit",
|
|
18
|
+
label: "inherit",
|
|
19
|
+
description: "Inherit session default",
|
|
20
|
+
},
|
|
21
|
+
off: { value: "off", label: "off", description: "No reasoning" },
|
|
22
|
+
minimal: {
|
|
23
|
+
value: "minimal",
|
|
24
|
+
label: "min",
|
|
25
|
+
description: "Very brief reasoning (~1k tokens)",
|
|
26
|
+
},
|
|
27
|
+
low: { value: "low", label: "low", description: "Light reasoning (~2k tokens)" },
|
|
28
|
+
medium: {
|
|
29
|
+
value: "medium",
|
|
30
|
+
label: "medium",
|
|
31
|
+
description: "Moderate reasoning (~8k tokens)",
|
|
32
|
+
},
|
|
33
|
+
high: { value: "high", label: "high", description: "Deep reasoning (~16k tokens)" },
|
|
34
|
+
xhigh: {
|
|
35
|
+
value: "xhigh",
|
|
36
|
+
label: "xhigh",
|
|
37
|
+
description: "Maximum reasoning (~32k tokens)",
|
|
38
|
+
},
|
|
39
|
+
max: {
|
|
40
|
+
value: "max",
|
|
41
|
+
label: "max",
|
|
42
|
+
description: "Opus maximum reasoning",
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns display metadata for a thinking selector.
|
|
48
|
+
*/
|
|
49
|
+
export function getThinkingLevelMetadata(level: ThinkingLevelValue): ThinkingLevelMetadata {
|
|
50
|
+
return THINKING_LEVEL_METADATA[level];
|
|
51
|
+
}
|