@gajae-code/coding-agent 0.4.5 → 0.5.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 +62 -0
- package/dist/types/async/job-manager.d.ts +26 -0
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/list-models.d.ts +6 -0
- package/dist/types/commands/gc.d.ts +26 -0
- package/dist/types/commands/harness.d.ts +3 -0
- package/dist/types/config/file-lock-gc.d.ts +5 -0
- package/dist/types/config/file-lock.d.ts +7 -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 +3 -0
- package/dist/types/config/model-resolver.d.ts +2 -0
- package/dist/types/config/models-config-schema.d.ts +30 -0
- package/dist/types/config/settings-schema.d.ts +4 -3
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/defaults/gjc/extensions/grok-build/index.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/index.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +25 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.d.ts +27 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.d.ts +8 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.d.ts +5 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +10 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.d.ts +38 -0
- package/dist/types/defaults/gjc-grok-cli.d.ts +5 -0
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +35 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +103 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +112 -0
- package/dist/types/gjc-runtime/gc-render.d.ts +6 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +134 -0
- package/dist/types/gjc-runtime/ledger-event-renderer.d.ts +68 -0
- package/dist/types/gjc-runtime/team-gc.d.ts +7 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +5 -1
- package/dist/types/gjc-runtime/tmux-common.d.ts +14 -0
- package/dist/types/gjc-runtime/tmux-gc.d.ts +7 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +13 -0
- package/dist/types/harness-control-plane/gc-adapter.d.ts +3 -0
- package/dist/types/harness-control-plane/owner.d.ts +8 -1
- package/dist/types/harness-control-plane/receipt-spool.d.ts +19 -0
- package/dist/types/harness-control-plane/state-machine.d.ts +6 -1
- package/dist/types/harness-control-plane/storage.d.ts +20 -0
- package/dist/types/harness-control-plane/types.d.ts +4 -0
- package/dist/types/hindsight/mental-models.d.ts +5 -5
- package/dist/types/modes/components/hook-selector.d.ts +7 -1
- package/dist/types/modes/components/model-selector.d.ts +1 -12
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +2 -2
- package/dist/types/modes/rpc/rpc-mode.d.ts +16 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +4 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +13 -0
- package/dist/types/modes/shared/agent-wire/session-registry.d.ts +25 -0
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +2 -0
- package/dist/types/sdk.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +3 -1
- package/dist/types/session/blob-store.d.ts +59 -4
- package/dist/types/session/session-manager.d.ts +24 -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/skill-state/workflow-hud.d.ts +14 -0
- package/dist/types/task/receipt.d.ts +1 -0
- package/dist/types/task/types.d.ts +7 -0
- package/dist/types/thinking-metadata.d.ts +16 -0
- package/dist/types/thinking.d.ts +3 -12
- package/dist/types/tools/ask.d.ts +15 -1
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/resolve.d.ts +0 -10
- package/dist/types/tools/subagent.d.ts +6 -0
- package/dist/types/utils/tool-choice.d.ts +14 -1
- package/package.json +7 -7
- package/src/async/job-manager.ts +52 -0
- package/src/cli/args.ts +3 -0
- package/src/cli/auth-broker-cli.ts +1 -0
- package/src/cli/list-models.ts +13 -1
- package/src/cli.ts +9 -4
- package/src/commands/gc.ts +22 -0
- package/src/commands/harness.ts +43 -5
- package/src/commands/launch.ts +2 -2
- package/src/commands/session.ts +3 -1
- package/src/config/file-lock-gc.ts +181 -0
- package/src/config/file-lock.ts +14 -0
- package/src/config/model-profile-activation.ts +15 -3
- package/src/config/model-profiles.ts +264 -56
- package/src/config/model-resolver.ts +9 -6
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +6 -3
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/server.ts +513 -26
- package/src/cursor.ts +16 -2
- package/src/defaults/gjc/agent.models.grok-cli.yml +36 -0
- package/src/defaults/gjc/extensions/grok-build/index.ts +1 -0
- package/src/defaults/gjc/extensions/grok-build/package.json +7 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +39 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/package.json +8 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/index.ts +1 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +155 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +361 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.ts +57 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +99 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +50 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.ts +56 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +36 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.ts +44 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +131 -113
- package/src/defaults/gjc/skills/deep-interview/lateral-review-panel.md +49 -0
- package/src/defaults/gjc/skills/team/SKILL.md +3 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +8 -2
- package/src/defaults/gjc-defaults.ts +7 -0
- package/src/defaults/gjc-grok-cli.ts +22 -0
- package/src/export/html/index.ts +13 -9
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/prefix-command-bridge.ts +128 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +417 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +18 -26
- package/src/gjc-runtime/deep-interview-state.ts +324 -0
- package/src/gjc-runtime/gc-render.ts +70 -0
- package/src/gjc-runtime/gc-runtime.ts +403 -0
- package/src/gjc-runtime/ledger-event-renderer.ts +164 -0
- package/src/gjc-runtime/ralplan-runtime.ts +58 -7
- package/src/gjc-runtime/state-renderer.ts +12 -3
- package/src/gjc-runtime/state-runtime.ts +46 -29
- package/src/gjc-runtime/team-gc.ts +49 -0
- package/src/gjc-runtime/team-runtime.ts +211 -8
- package/src/gjc-runtime/tmux-common.ts +29 -0
- package/src/gjc-runtime/tmux-gc.ts +176 -0
- package/src/gjc-runtime/tmux-sessions.ts +68 -12
- package/src/gjc-runtime/ultragoal-runtime.ts +517 -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/gc-adapter.ts +184 -0
- package/src/harness-control-plane/owner.ts +89 -27
- package/src/harness-control-plane/receipt-spool.ts +128 -0
- package/src/harness-control-plane/state-machine.ts +27 -6
- package/src/harness-control-plane/storage.ts +93 -0
- package/src/harness-control-plane/types.ts +4 -0
- package/src/hindsight/mental-models.ts +17 -16
- package/src/internal-urls/docs-index.generated.ts +14 -8
- package/src/main.ts +7 -2
- package/src/modes/components/assistant-message.ts +26 -14
- package/src/modes/components/diff.ts +97 -0
- package/src/modes/components/hook-selector.ts +19 -0
- package/src/modes/components/model-selector.ts +370 -181
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/components/tool-execution.ts +30 -13
- package/src/modes/controllers/command-controller.ts +25 -6
- package/src/modes/controllers/extension-ui-controller.ts +3 -0
- package/src/modes/controllers/selector-controller.ts +34 -42
- package/src/modes/rpc/rpc-client.ts +3 -2
- package/src/modes/rpc/rpc-mode.ts +187 -39
- package/src/modes/rpc/rpc-types.ts +5 -2
- package/src/modes/shared/agent-wire/command-dispatch.ts +279 -257
- package/src/modes/shared/agent-wire/command-validation.ts +11 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +30 -1
- package/src/modes/shared/agent-wire/session-registry.ts +109 -0
- package/src/modes/shared/agent-wire/unattended-action-policy.ts +24 -0
- package/src/modes/shared/agent-wire/unattended-run-controller.ts +23 -3
- package/src/modes/shared/agent-wire/unattended-session.ts +16 -1
- package/src/sdk.ts +46 -5
- package/src/secrets/obfuscator.ts +102 -27
- package/src/session/agent-session.ts +179 -25
- package/src/session/blob-store.ts +148 -6
- package/src/session/session-manager.ts +311 -60
- 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 +7 -1
- package/src/skill-state/workflow-hud.ts +106 -10
- package/src/slash-commands/builtin-registry.ts +3 -2
- package/src/task/executor.ts +78 -6
- package/src/task/receipt.ts +5 -0
- package/src/task/render.ts +21 -1
- package/src/task/types.ts +8 -0
- package/src/thinking-metadata.ts +51 -0
- package/src/thinking.ts +26 -46
- package/src/tools/ask.ts +56 -1
- package/src/tools/bash.ts +1 -1
- package/src/tools/index.ts +2 -0
- package/src/tools/job.ts +3 -2
- package/src/tools/monitor.ts +36 -1
- package/src/tools/resolve.ts +93 -18
- package/src/tools/subagent-render.ts +9 -0
- package/src/tools/subagent.ts +26 -2
- package/src/utils/edit-mode.ts +1 -1
- package/src/utils/tool-choice.ts +45 -16
|
@@ -21,6 +21,8 @@ interface RalplanHudState extends WorkflowGateHudState {
|
|
|
21
21
|
stage?: string;
|
|
22
22
|
waiting?: string;
|
|
23
23
|
iteration?: number;
|
|
24
|
+
iterationFromIndex?: number;
|
|
25
|
+
stages?: string;
|
|
24
26
|
verdict?: string;
|
|
25
27
|
latestSummary?: string;
|
|
26
28
|
pendingApproval?: boolean;
|
|
@@ -100,6 +102,95 @@ export function buildDeepInterviewHudSummary(state: DeepInterviewHudState): Work
|
|
|
100
102
|
};
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
export interface DeepInterviewHudDeriveOptions {
|
|
106
|
+
phase?: string;
|
|
107
|
+
specStatus?: string;
|
|
108
|
+
updatedAt?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function diIsPlainObject(value: unknown): value is Record<string, unknown> {
|
|
112
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function latestScoredAmbiguity(rounds: unknown): number | undefined {
|
|
116
|
+
if (!Array.isArray(rounds)) return undefined;
|
|
117
|
+
for (let index = rounds.length - 1; index >= 0; index--) {
|
|
118
|
+
const round = rounds[index];
|
|
119
|
+
if (diIsPlainObject(round) && round.lifecycle === "scored" && typeof round.ambiguity === "number") {
|
|
120
|
+
return round.ambiguity;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function weakestDimensionFromTopology(
|
|
127
|
+
topology: Record<string, unknown>,
|
|
128
|
+
targetComponent: string | undefined,
|
|
129
|
+
): string | undefined {
|
|
130
|
+
if (!Array.isArray(topology.components)) return undefined;
|
|
131
|
+
const components = topology.components.filter(diIsPlainObject);
|
|
132
|
+
const dimensionOf = (component: Record<string, unknown>): string | undefined =>
|
|
133
|
+
typeof component.weakest_dimension === "string" && component.weakest_dimension.trim()
|
|
134
|
+
? component.weakest_dimension
|
|
135
|
+
: undefined;
|
|
136
|
+
if (targetComponent) {
|
|
137
|
+
const targeted = components.find(component => component.id === targetComponent && dimensionOf(component));
|
|
138
|
+
if (targeted) return dimensionOf(targeted);
|
|
139
|
+
}
|
|
140
|
+
const active = components.find(component => component.status !== "deferred" && dimensionOf(component));
|
|
141
|
+
if (active) return dimensionOf(active);
|
|
142
|
+
const any = components.find(component => dimensionOf(component));
|
|
143
|
+
return any ? dimensionOf(any) : undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Single source of deep-interview HUD derivation. Reads a complete (normalized)
|
|
148
|
+
* mode-state envelope so recorder, `gjc state write`, reconcile, seed, and handoff
|
|
149
|
+
* all produce identical chips. Topology-aware `target`/`weakest` come from
|
|
150
|
+
* `state.topology`; `legacy_missing` topology omits those chips (no synthetic values).
|
|
151
|
+
*/
|
|
152
|
+
export function deriveDeepInterviewHud(
|
|
153
|
+
payload: Record<string, unknown>,
|
|
154
|
+
options: DeepInterviewHudDeriveOptions = {},
|
|
155
|
+
): WorkflowHudSummary {
|
|
156
|
+
const stateField = diIsPlainObject(payload.state) ? payload.state : {};
|
|
157
|
+
const isNumber = (value: unknown): value is number => typeof value === "number" && Number.isFinite(value);
|
|
158
|
+
const isArray = (value: unknown): value is unknown[] => Array.isArray(value);
|
|
159
|
+
const pick = <T>(key: string, guard: (value: unknown) => value is T): T | undefined => {
|
|
160
|
+
const value = stateField[key] ?? payload[key];
|
|
161
|
+
return guard(value) ? value : undefined;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const phase = options.phase ?? (typeof payload.current_phase === "string" ? payload.current_phase : undefined);
|
|
165
|
+
const rounds = pick("rounds", isArray);
|
|
166
|
+
const ambiguity = pick("current_ambiguity", isNumber) ?? latestScoredAmbiguity(rounds);
|
|
167
|
+
const threshold = pick("threshold", isNumber);
|
|
168
|
+
const rawTopology = diIsPlainObject(stateField.topology)
|
|
169
|
+
? stateField.topology
|
|
170
|
+
: diIsPlainObject(payload.topology)
|
|
171
|
+
? payload.topology
|
|
172
|
+
: undefined;
|
|
173
|
+
// `legacy_missing` topology was never confirmed: omit target/weakest even if stale fields linger.
|
|
174
|
+
const topology = rawTopology && rawTopology.status !== "legacy_missing" ? rawTopology : undefined;
|
|
175
|
+
const targetComponent =
|
|
176
|
+
topology && typeof topology.last_targeted_component_id === "string"
|
|
177
|
+
? topology.last_targeted_component_id
|
|
178
|
+
: undefined;
|
|
179
|
+
const weakestDimension = topology ? weakestDimensionFromTopology(topology, targetComponent) : undefined;
|
|
180
|
+
const specStatus = options.specStatus ?? (typeof payload.spec_status === "string" ? payload.spec_status : undefined);
|
|
181
|
+
|
|
182
|
+
return buildDeepInterviewHudSummary({
|
|
183
|
+
phase,
|
|
184
|
+
ambiguity,
|
|
185
|
+
threshold,
|
|
186
|
+
roundCount: rounds?.length,
|
|
187
|
+
targetComponent,
|
|
188
|
+
weakestDimension,
|
|
189
|
+
specStatus,
|
|
190
|
+
updatedAt: options.updatedAt ?? new Date().toISOString(),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
103
194
|
export function buildRalplanHudSummary(state: RalplanHudState): WorkflowHudSummary {
|
|
104
195
|
const verdict = state.verdict?.toUpperCase();
|
|
105
196
|
const verdictSeverity =
|
|
@@ -118,7 +209,14 @@ export function buildRalplanHudSummary(state: RalplanHudState): WorkflowHudSumma
|
|
|
118
209
|
...gateChips(state, 6),
|
|
119
210
|
chip("stage", state.stage, 10),
|
|
120
211
|
chip("waiting", state.waiting, 20),
|
|
121
|
-
chip(
|
|
212
|
+
chip(
|
|
213
|
+
"iter",
|
|
214
|
+
(state.iterationFromIndex ?? state.iteration) === undefined
|
|
215
|
+
? undefined
|
|
216
|
+
: String(state.iterationFromIndex ?? state.iteration),
|
|
217
|
+
30,
|
|
218
|
+
),
|
|
219
|
+
chip("stages", state.stages, 35),
|
|
122
220
|
chip("verdict", verdict, 40, verdictSeverity),
|
|
123
221
|
]),
|
|
124
222
|
...(state.updatedAt ? { updated_at: state.updatedAt } : {}),
|
|
@@ -136,17 +234,15 @@ export function buildUltragoalHudSummary(state: UltragoalHudState): WorkflowHudS
|
|
|
136
234
|
chip("goals", `${complete}/${total}`, 10),
|
|
137
235
|
chip("current", state.currentGoal ? `${state.currentGoal.id}:${state.currentGoal.title}` : state.status, 20),
|
|
138
236
|
chip("status", state.status, 30, state.status === "complete" ? "success" : undefined),
|
|
237
|
+
chip(
|
|
238
|
+
"ledger",
|
|
239
|
+
state.latestLedgerEvent?.event
|
|
240
|
+
? [state.latestLedgerEvent.event, state.latestLedgerEvent.goalId].filter(Boolean).join(":")
|
|
241
|
+
: undefined,
|
|
242
|
+
35,
|
|
243
|
+
),
|
|
139
244
|
...gateChips(state, 40),
|
|
140
245
|
]),
|
|
141
|
-
details: state.latestLedgerEvent
|
|
142
|
-
? compactChips([
|
|
143
|
-
chip(
|
|
144
|
-
"ledger",
|
|
145
|
-
[state.latestLedgerEvent.event, state.latestLedgerEvent.goalId].filter(Boolean).join(":"),
|
|
146
|
-
100,
|
|
147
|
-
),
|
|
148
|
-
])
|
|
149
|
-
: undefined,
|
|
150
246
|
...(state.updatedAt ? { updated_at: state.updatedAt } : {}),
|
|
151
247
|
};
|
|
152
248
|
}
|
|
@@ -4,6 +4,7 @@ import type { ThinkingLevel } from "@gajae-code/agent-core";
|
|
|
4
4
|
import { type Model, modelsAreEqual } from "@gajae-code/ai";
|
|
5
5
|
import { getOAuthProviders } from "@gajae-code/ai/utils/oauth";
|
|
6
6
|
import { setProjectDir } from "@gajae-code/utils";
|
|
7
|
+
import { jobElapsedMs } from "../async";
|
|
7
8
|
import {
|
|
8
9
|
GJC_MODEL_ASSIGNMENT_TARGET_IDS,
|
|
9
10
|
GJC_MODEL_ASSIGNMENT_TARGETS,
|
|
@@ -506,14 +507,14 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
506
507
|
if (snapshot.running.length > 0) {
|
|
507
508
|
lines.push("", "Running Jobs");
|
|
508
509
|
for (const job of snapshot.running) {
|
|
509
|
-
lines.push(` [${job.id}] ${job.type} (${job.status}) — ${formatDuration(now
|
|
510
|
+
lines.push(` [${job.id}] ${job.type} (${job.status}) — ${formatDuration(jobElapsedMs(job, now))}`);
|
|
510
511
|
lines.push(` ${job.label}`);
|
|
511
512
|
}
|
|
512
513
|
}
|
|
513
514
|
if (snapshot.recent.length > 0) {
|
|
514
515
|
lines.push("", "Recent Jobs");
|
|
515
516
|
for (const job of snapshot.recent) {
|
|
516
|
-
lines.push(` [${job.id}] ${job.type} (${job.status}) — ${formatDuration(now
|
|
517
|
+
lines.push(` [${job.id}] ${job.type} (${job.status}) — ${formatDuration(jobElapsedMs(job, now))}`);
|
|
517
518
|
lines.push(` ${job.label}`);
|
|
518
519
|
}
|
|
519
520
|
}
|
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,20 +1137,39 @@ 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,
|
|
1108
1155
|
});
|
|
1109
1156
|
}
|
|
1157
|
+
// Record which model the subagent actually runs on (and any auth fallback,
|
|
1158
|
+
// see #985) so the subagent panel can surface it to the user.
|
|
1159
|
+
if (model) {
|
|
1160
|
+
AsyncJobManager.instance()?.updateSubagentModel?.(options.subagentId ?? id, {
|
|
1161
|
+
requestedModel: modelSubstitutionWarning?.requested ?? resolvedModelString,
|
|
1162
|
+
effectiveModel: resolvedModelString,
|
|
1163
|
+
modelFellBack: authFallbackUsed === true,
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1110
1166
|
if (model?.contextWindow && model.contextWindow > 0) {
|
|
1111
1167
|
progress.contextWindow = model.contextWindow;
|
|
1112
1168
|
}
|
|
1113
1169
|
const effectiveThinkingLevel = explicitThinkingLevel
|
|
1114
1170
|
? resolvedThinkingLevel
|
|
1115
1171
|
: (thinkingLevel ?? resolvedThinkingLevel);
|
|
1172
|
+
effectiveThinkingLevelForWarning = effectiveThinkingLevel;
|
|
1116
1173
|
|
|
1117
1174
|
const sessionManager = sessionFile
|
|
1118
1175
|
? await awaitAbortable(SessionManager.open(sessionFile))
|
|
@@ -1174,6 +1231,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1174
1231
|
settings: subagentSettings,
|
|
1175
1232
|
model,
|
|
1176
1233
|
thinkingLevel: effectiveThinkingLevel,
|
|
1234
|
+
modelSubstitution:
|
|
1235
|
+
modelSubstitutionWarning?.reason === "auth_unavailable" && requestedModel
|
|
1236
|
+
? { requestedModel, reason: modelSubstitutionWarning.reason }
|
|
1237
|
+
: undefined,
|
|
1177
1238
|
toolNames,
|
|
1178
1239
|
outputSchema,
|
|
1179
1240
|
requireYieldTool: true,
|
|
@@ -1412,7 +1473,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1412
1473
|
await awaitAbortable(session.waitForIdle());
|
|
1413
1474
|
}
|
|
1414
1475
|
|
|
1415
|
-
const
|
|
1476
|
+
const reminderToolChoiceResult = buildNamedToolChoiceResult("yield", session.model);
|
|
1416
1477
|
|
|
1417
1478
|
let retryCount = 0;
|
|
1418
1479
|
while (!paused && !yieldCalled && retryCount < MAX_YIELD_RETRIES && !abortSignal.aborted) {
|
|
@@ -1433,7 +1494,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1433
1494
|
await awaitAbortable(
|
|
1434
1495
|
session.prompt(reminder, {
|
|
1435
1496
|
attribution: "agent",
|
|
1436
|
-
...(isFinalRetry &&
|
|
1497
|
+
...(isFinalRetry && reminderToolChoiceResult.exactNamed && reminderToolChoiceResult.choice
|
|
1498
|
+
? { toolChoice: reminderToolChoiceResult.choice }
|
|
1499
|
+
: {}),
|
|
1437
1500
|
}),
|
|
1438
1501
|
);
|
|
1439
1502
|
await awaitAbortable(session.waitForIdle());
|
|
@@ -1466,6 +1529,14 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1466
1529
|
error = undefined;
|
|
1467
1530
|
}
|
|
1468
1531
|
}
|
|
1532
|
+
if (lastAssistantModelString && resolvedModelString && lastAssistantModelString !== resolvedModelString) {
|
|
1533
|
+
modelSubstitutionWarning ??= {
|
|
1534
|
+
requested: resolvedModelString,
|
|
1535
|
+
effective: lastAssistantModelString,
|
|
1536
|
+
reason: "assistant_model_mismatch",
|
|
1537
|
+
};
|
|
1538
|
+
progress.modelSubstitutionWarning = modelSubstitutionWarning;
|
|
1539
|
+
}
|
|
1469
1540
|
} catch (err) {
|
|
1470
1541
|
exitCode = 1;
|
|
1471
1542
|
if (!abortSignal.aborted) {
|
|
@@ -1642,6 +1713,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1642
1713
|
contextTokens: progress.contextTokens,
|
|
1643
1714
|
contextWindow: progress.contextWindow,
|
|
1644
1715
|
modelOverride,
|
|
1716
|
+
modelSubstitutionWarning,
|
|
1645
1717
|
error: exitCode !== 0 && stderr ? stderr : undefined,
|
|
1646
1718
|
aborted: wasAborted,
|
|
1647
1719
|
abortReason: finalAbortReason,
|
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;
|
|
@@ -78,6 +79,9 @@ function truncateText(value: string | undefined, maxChars: number): string | und
|
|
|
78
79
|
|
|
79
80
|
function buildSafeSynopsis(raw: SingleResult, outputRef: TaskResultReceipt["outputRef"]): string {
|
|
80
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
|
+
}
|
|
81
85
|
if (raw.retryFailure) {
|
|
82
86
|
return `Task ${status}; retry stopped after attempt ${raw.retryFailure.attempt}.`;
|
|
83
87
|
}
|
|
@@ -220,6 +224,7 @@ export function buildTaskReceipt(raw: SingleResult): TaskResultReceipt {
|
|
|
220
224
|
contextTokens: raw.contextTokens,
|
|
221
225
|
contextWindow: raw.contextWindow,
|
|
222
226
|
modelOverride: raw.modelOverride,
|
|
227
|
+
modelSubstitutionWarning: raw.modelSubstitutionWarning,
|
|
223
228
|
usage: raw.usage,
|
|
224
229
|
cost: raw.usage?.cost.total,
|
|
225
230
|
branchName: raw.branchName,
|
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
|
}
|
package/src/task/types.ts
CHANGED
|
@@ -215,6 +215,12 @@ export interface AgentDefinition {
|
|
|
215
215
|
filePath?: string;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
export interface ModelSubstitutionWarning {
|
|
219
|
+
requested: string;
|
|
220
|
+
effective: string;
|
|
221
|
+
reason: "auth_unavailable" | "assistant_model_mismatch";
|
|
222
|
+
}
|
|
223
|
+
|
|
218
224
|
/** Progress tracking for a single agent */
|
|
219
225
|
export interface AgentProgress {
|
|
220
226
|
index: number;
|
|
@@ -247,6 +253,7 @@ export interface AgentProgress {
|
|
|
247
253
|
cost: number;
|
|
248
254
|
durationMs: number;
|
|
249
255
|
modelOverride?: string | string[];
|
|
256
|
+
modelSubstitutionWarning?: ModelSubstitutionWarning;
|
|
250
257
|
/** Data extracted by registered subprocess tool handlers (keyed by tool name) */
|
|
251
258
|
extractedToolData?: Record<string, unknown[]>;
|
|
252
259
|
/**
|
|
@@ -306,6 +313,7 @@ export interface SingleResult {
|
|
|
306
313
|
/** Model's context window in tokens, when known. */
|
|
307
314
|
contextWindow?: number;
|
|
308
315
|
modelOverride?: string | string[];
|
|
316
|
+
modelSubstitutionWarning?: ModelSubstitutionWarning;
|
|
309
317
|
error?: string;
|
|
310
318
|
aborted?: boolean;
|
|
311
319
|
abortReason?: string;
|
|
@@ -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
|
+
}
|
package/src/thinking.ts
CHANGED
|
@@ -2,45 +2,7 @@ import { type ResolvedThinkingLevel, ThinkingLevel } from "@gajae-code/agent-cor
|
|
|
2
2
|
import { clampThinkingLevelForModel, type Effort, THINKING_EFFORTS } from "@gajae-code/ai/model-thinking";
|
|
3
3
|
import type { Model } from "@gajae-code/ai/types";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
* Metadata used to render thinking selector values in the coding-agent UI.
|
|
7
|
-
*/
|
|
8
|
-
export interface ThinkingLevelMetadata {
|
|
9
|
-
value: ThinkingLevel;
|
|
10
|
-
label: string;
|
|
11
|
-
description: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const THINKING_LEVEL_METADATA: Record<ThinkingLevel, ThinkingLevelMetadata> = {
|
|
15
|
-
[ThinkingLevel.Inherit]: {
|
|
16
|
-
value: ThinkingLevel.Inherit,
|
|
17
|
-
label: "inherit",
|
|
18
|
-
description: "Inherit session default",
|
|
19
|
-
},
|
|
20
|
-
[ThinkingLevel.Off]: { value: ThinkingLevel.Off, label: "off", description: "No reasoning" },
|
|
21
|
-
[ThinkingLevel.Minimal]: {
|
|
22
|
-
value: ThinkingLevel.Minimal,
|
|
23
|
-
label: "min",
|
|
24
|
-
description: "Very brief reasoning (~1k tokens)",
|
|
25
|
-
},
|
|
26
|
-
[ThinkingLevel.Low]: { value: ThinkingLevel.Low, label: "low", description: "Light reasoning (~2k tokens)" },
|
|
27
|
-
[ThinkingLevel.Medium]: {
|
|
28
|
-
value: ThinkingLevel.Medium,
|
|
29
|
-
label: "medium",
|
|
30
|
-
description: "Moderate reasoning (~8k tokens)",
|
|
31
|
-
},
|
|
32
|
-
[ThinkingLevel.High]: { value: ThinkingLevel.High, label: "high", description: "Deep reasoning (~16k tokens)" },
|
|
33
|
-
[ThinkingLevel.XHigh]: {
|
|
34
|
-
value: ThinkingLevel.XHigh,
|
|
35
|
-
label: "xhigh",
|
|
36
|
-
description: "Maximum reasoning (~32k tokens)",
|
|
37
|
-
},
|
|
38
|
-
[ThinkingLevel.Max]: {
|
|
39
|
-
value: ThinkingLevel.Max,
|
|
40
|
-
label: "max",
|
|
41
|
-
description: "Opus maximum reasoning",
|
|
42
|
-
},
|
|
43
|
-
};
|
|
5
|
+
export { getThinkingLevelMetadata, type ThinkingLevelMetadata } from "./thinking-metadata";
|
|
44
6
|
|
|
45
7
|
const THINKING_LEVELS = new Set<string>([ThinkingLevel.Inherit, ThinkingLevel.Off, ...THINKING_EFFORTS]);
|
|
46
8
|
const EFFORT_LEVELS = new Set<string>(THINKING_EFFORTS);
|
|
@@ -59,13 +21,6 @@ export function parseThinkingLevel(value: string | null | undefined): ThinkingLe
|
|
|
59
21
|
return value !== undefined && value !== null && THINKING_LEVELS.has(value) ? (value as ThinkingLevel) : undefined;
|
|
60
22
|
}
|
|
61
23
|
|
|
62
|
-
/**
|
|
63
|
-
* Returns display metadata for a thinking selector.
|
|
64
|
-
*/
|
|
65
|
-
export function getThinkingLevelMetadata(level: ThinkingLevel): ThinkingLevelMetadata {
|
|
66
|
-
return THINKING_LEVEL_METADATA[level];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
24
|
/**
|
|
70
25
|
* Converts an agent-local selector into the effort sent to providers.
|
|
71
26
|
*/
|
|
@@ -91,3 +46,28 @@ export function resolveThinkingLevelForModel(
|
|
|
91
46
|
}
|
|
92
47
|
return clampThinkingLevelForModel(model, level);
|
|
93
48
|
}
|
|
49
|
+
|
|
50
|
+
export function clampExplicitThinkingLevelForModel(
|
|
51
|
+
model: Model | undefined,
|
|
52
|
+
level: ThinkingLevel | undefined,
|
|
53
|
+
): ThinkingLevel | undefined {
|
|
54
|
+
if (level === undefined || level === ThinkingLevel.Inherit || level === ThinkingLevel.Off) {
|
|
55
|
+
return level;
|
|
56
|
+
}
|
|
57
|
+
return clampThinkingLevelForModel(model, level);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function formatClampedModelSelector(selector: string, model: Model | undefined): string {
|
|
61
|
+
const slashIdx = selector.indexOf("/");
|
|
62
|
+
if (slashIdx <= 0) return selector;
|
|
63
|
+
const id = selector.slice(slashIdx + 1);
|
|
64
|
+
const colonIdx = id.lastIndexOf(":");
|
|
65
|
+
if (colonIdx === -1) return selector;
|
|
66
|
+
const suffix = id.slice(colonIdx + 1);
|
|
67
|
+
const thinkingLevel = parseThinkingLevel(suffix);
|
|
68
|
+
if (!thinkingLevel) return selector;
|
|
69
|
+
const clamped = clampExplicitThinkingLevelForModel(model, thinkingLevel);
|
|
70
|
+
return clamped && clamped !== ThinkingLevel.Inherit
|
|
71
|
+
? `${selector.slice(0, slashIdx + 1)}${id.slice(0, colonIdx)}:${clamped}`
|
|
72
|
+
: selector.slice(0, slashIdx + 1) + id.slice(0, colonIdx);
|
|
73
|
+
}
|