@oh-my-pi/pi-coding-agent 13.7.6 → 13.9.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 +43 -0
- package/package.json +7 -7
- package/scripts/generate-docs-index.ts +3 -3
- package/src/capability/context-file.ts +6 -3
- package/src/capability/fs.ts +18 -0
- package/src/capability/index.ts +3 -2
- package/src/capability/rule.ts +0 -4
- package/src/capability/types.ts +2 -0
- package/src/cli/agents-cli.ts +1 -1
- package/src/cli/args.ts +7 -12
- package/src/commands/launch.ts +3 -2
- package/src/config/model-resolver.ts +118 -33
- package/src/config/settings-schema.ts +14 -2
- package/src/config/settings.ts +1 -17
- package/src/discovery/agents-md.ts +3 -4
- package/src/discovery/agents.ts +104 -84
- package/src/discovery/builtin.ts +28 -15
- package/src/discovery/claude.ts +27 -9
- package/src/discovery/helpers.ts +10 -17
- package/src/extensibility/extensions/loader.ts +1 -2
- package/src/extensibility/extensions/types.ts +2 -1
- package/src/extensibility/skills.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/main.ts +21 -10
- package/src/modes/components/agent-dashboard.ts +12 -13
- package/src/modes/components/model-selector.ts +157 -59
- package/src/modes/components/read-tool-group.ts +36 -2
- package/src/modes/components/settings-defs.ts +11 -8
- package/src/modes/components/settings-selector.ts +1 -1
- package/src/modes/components/thinking-selector.ts +3 -15
- package/src/modes/controllers/selector-controller.ts +6 -4
- package/src/modes/rpc/rpc-client.ts +2 -2
- package/src/modes/rpc/rpc-types.ts +2 -2
- package/src/modes/theme/theme.ts +2 -1
- package/src/patch/hashline.ts +113 -0
- package/src/patch/index.ts +27 -18
- package/src/prompts/tools/hashline.md +9 -10
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +21 -25
- package/src/session/agent-session.ts +54 -59
- package/src/task/executor.ts +10 -8
- package/src/task/types.ts +1 -2
- package/src/tools/fetch.ts +152 -4
- package/src/tools/read.ts +88 -264
- package/src/utils/frontmatter.ts +25 -4
- package/src/web/scrapers/choosealicense.ts +1 -1
package/src/patch/hashline.ts
CHANGED
|
@@ -710,3 +710,116 @@ export function applyHashlineEdits(
|
|
|
710
710
|
}
|
|
711
711
|
}
|
|
712
712
|
}
|
|
713
|
+
|
|
714
|
+
export interface CompactHashlineDiffPreview {
|
|
715
|
+
preview: string;
|
|
716
|
+
addedLines: number;
|
|
717
|
+
removedLines: number;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export interface CompactHashlineDiffOptions {
|
|
721
|
+
maxUnchangedRun?: number;
|
|
722
|
+
maxAdditionRun?: number;
|
|
723
|
+
maxDeletionRun?: number;
|
|
724
|
+
maxOutputLines?: number;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const NUMBERED_DIFF_LINE_RE = /^([ +-])(\d+)\|(.*)$/;
|
|
728
|
+
|
|
729
|
+
type DiffRunKind = " " | "+" | "-" | "meta";
|
|
730
|
+
type DiffRun = { kind: DiffRunKind; lines: string[] };
|
|
731
|
+
|
|
732
|
+
function collapseFromStart(lines: string[], maxLines: number, label: string): string[] {
|
|
733
|
+
if (lines.length <= maxLines) return lines;
|
|
734
|
+
const hidden = lines.length - maxLines;
|
|
735
|
+
return [...lines.slice(0, maxLines), ` ... ${hidden} more ${label} lines`];
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function collapseFromEnd(lines: string[], maxLines: number, label: string): string[] {
|
|
739
|
+
if (lines.length <= maxLines) return lines;
|
|
740
|
+
const hidden = lines.length - maxLines;
|
|
741
|
+
return [` ... ${hidden} more ${label} lines`, ...lines.slice(-maxLines)];
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function collapseFromMiddle(lines: string[], maxLines: number, label: string): string[] {
|
|
745
|
+
if (lines.length <= maxLines * 2) return lines;
|
|
746
|
+
const hidden = lines.length - maxLines * 2;
|
|
747
|
+
return [...lines.slice(0, maxLines), ` ... ${hidden} more ${label} lines`, ...lines.slice(-maxLines)];
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function splitDiffRuns(lines: string[]): DiffRun[] {
|
|
751
|
+
const runs: DiffRun[] = [];
|
|
752
|
+
for (const line of lines) {
|
|
753
|
+
const match = NUMBERED_DIFF_LINE_RE.exec(line);
|
|
754
|
+
const kind = (match?.[1] as " " | "+" | "-" | undefined) ?? "meta";
|
|
755
|
+
const prev = runs[runs.length - 1];
|
|
756
|
+
if (prev && prev.kind === kind) {
|
|
757
|
+
prev.lines.push(line);
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
runs.push({ kind, lines: [line] });
|
|
761
|
+
}
|
|
762
|
+
return runs;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Build a compact diff preview suitable for model-visible tool responses.
|
|
767
|
+
*
|
|
768
|
+
* Collapses long unchanged runs and long consecutive additions/removals so the
|
|
769
|
+
* model sees the shape of edits without replaying full file content.
|
|
770
|
+
*/
|
|
771
|
+
export function buildCompactHashlineDiffPreview(
|
|
772
|
+
diff: string,
|
|
773
|
+
options: CompactHashlineDiffOptions = {},
|
|
774
|
+
): CompactHashlineDiffPreview {
|
|
775
|
+
const maxUnchangedRun = options.maxUnchangedRun ?? 2;
|
|
776
|
+
const maxAdditionRun = options.maxAdditionRun ?? 2;
|
|
777
|
+
const maxDeletionRun = options.maxDeletionRun ?? 2;
|
|
778
|
+
const maxOutputLines = options.maxOutputLines ?? 16;
|
|
779
|
+
|
|
780
|
+
const inputLines = diff.length === 0 ? [] : diff.split("\n");
|
|
781
|
+
const runs = splitDiffRuns(inputLines);
|
|
782
|
+
|
|
783
|
+
const out: string[] = [];
|
|
784
|
+
let addedLines = 0;
|
|
785
|
+
let removedLines = 0;
|
|
786
|
+
|
|
787
|
+
for (let runIndex = 0; runIndex < runs.length; runIndex++) {
|
|
788
|
+
const run = runs[runIndex];
|
|
789
|
+
switch (run.kind) {
|
|
790
|
+
case "meta":
|
|
791
|
+
out.push(...run.lines);
|
|
792
|
+
break;
|
|
793
|
+
case "+":
|
|
794
|
+
addedLines += run.lines.length;
|
|
795
|
+
out.push(...collapseFromStart(run.lines, maxAdditionRun, "added"));
|
|
796
|
+
break;
|
|
797
|
+
case "-":
|
|
798
|
+
removedLines += run.lines.length;
|
|
799
|
+
out.push(...collapseFromStart(run.lines, maxDeletionRun, "removed"));
|
|
800
|
+
break;
|
|
801
|
+
case " ":
|
|
802
|
+
if (runIndex === 0) {
|
|
803
|
+
out.push(...collapseFromEnd(run.lines, maxUnchangedRun, "unchanged"));
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
if (runIndex === runs.length - 1) {
|
|
807
|
+
out.push(...collapseFromStart(run.lines, maxUnchangedRun, "unchanged"));
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
out.push(...collapseFromMiddle(run.lines, maxUnchangedRun, "unchanged"));
|
|
811
|
+
break;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
if (out.length > maxOutputLines) {
|
|
816
|
+
const hidden = out.length - maxOutputLines;
|
|
817
|
+
return {
|
|
818
|
+
preview: [...out.slice(0, maxOutputLines), ` ... ${hidden} more preview lines`].join("\n"),
|
|
819
|
+
addedLines,
|
|
820
|
+
removedLines,
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
return { preview: out.join("\n"), addedLines, removedLines };
|
|
825
|
+
}
|
package/src/patch/index.ts
CHANGED
|
@@ -34,7 +34,14 @@ import { enforcePlanModeWrite, resolvePlanPath } from "../tools/plan-mode-guard"
|
|
|
34
34
|
import { applyPatch } from "./applicator";
|
|
35
35
|
import { generateDiffString, generateUnifiedDiffString, replaceText } from "./diff";
|
|
36
36
|
import { findMatch } from "./fuzzy";
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
type Anchor,
|
|
39
|
+
applyHashlineEdits,
|
|
40
|
+
buildCompactHashlineDiffPreview,
|
|
41
|
+
computeLineHash,
|
|
42
|
+
type HashlineEdit,
|
|
43
|
+
parseTag,
|
|
44
|
+
} from "./hashline";
|
|
38
45
|
import { detectLineEnding, normalizeToLF, restoreLineEndings, stripBom } from "./normalize";
|
|
39
46
|
import { type EditToolDetails, getLspBatchRequest } from "./shared";
|
|
40
47
|
// Internal imports
|
|
@@ -315,18 +322,10 @@ export type EditMode = "replace" | "patch" | "hashline";
|
|
|
315
322
|
|
|
316
323
|
export const DEFAULT_EDIT_MODE: EditMode = "hashline";
|
|
317
324
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
case "patch":
|
|
323
|
-
return "patch";
|
|
324
|
-
case "hashline":
|
|
325
|
-
return "hashline";
|
|
326
|
-
default:
|
|
327
|
-
return null;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
325
|
+
const EDIT_ID: Record<string, EditMode> = Object.fromEntries(
|
|
326
|
+
["replace", "patch", "hashline"].map(mode => [mode, mode as EditMode]),
|
|
327
|
+
);
|
|
328
|
+
export const normalizeEditMode = (mode?: string | null): EditMode | undefined => EDIT_ID[mode ?? ""];
|
|
330
329
|
|
|
331
330
|
/**
|
|
332
331
|
* Edit tool implementation.
|
|
@@ -401,11 +400,17 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
401
400
|
*/
|
|
402
401
|
get mode(): EditMode {
|
|
403
402
|
if (this.#editMode) return this.#editMode;
|
|
403
|
+
// 1. Check if edit mode is explicitly set for this model
|
|
404
404
|
const activeModel = this.session.getActiveModelString?.();
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
405
|
+
const modelVariant = this.session.settings.getEditVariantForModel(activeModel);
|
|
406
|
+
if (modelVariant) return modelVariant;
|
|
407
|
+
// 2. Check if model contains "-spark" substring (default to replace mode)
|
|
408
|
+
if (activeModel?.includes("-spark")) return "replace";
|
|
409
|
+
// 3. Check if edit mode is explicitly set in session settings
|
|
410
|
+
const settingsMode = normalizeEditMode(this.session.settings.get("edit.mode"));
|
|
411
|
+
if (settingsMode) return settingsMode;
|
|
412
|
+
// 4. Default to DEFAULT_EDIT_MODE
|
|
413
|
+
return DEFAULT_EDIT_MODE;
|
|
409
414
|
}
|
|
410
415
|
|
|
411
416
|
/**
|
|
@@ -597,11 +602,15 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
597
602
|
.get();
|
|
598
603
|
|
|
599
604
|
const resultText = move ? `Moved ${path} to ${move}` : `Updated ${path}`;
|
|
605
|
+
const preview = buildCompactHashlineDiffPreview(diffResult.diff);
|
|
606
|
+
const summaryLine = `Changes: +${preview.addedLines} -${preview.removedLines}${preview.preview ? "" : " (no textual diff preview)"}`;
|
|
607
|
+
const warningsBlock = result.warnings?.length ? `\n\nWarnings:\n${result.warnings.join("\n")}` : "";
|
|
608
|
+
const previewBlock = preview.preview ? `\n\nDiff preview:\n${preview.preview}` : "";
|
|
600
609
|
return {
|
|
601
610
|
content: [
|
|
602
611
|
{
|
|
603
612
|
type: "text",
|
|
604
|
-
text: `${resultText}
|
|
613
|
+
text: `${resultText}\n${summaryLine}${previewBlock}${warningsBlock}`,
|
|
605
614
|
},
|
|
606
615
|
],
|
|
607
616
|
details: {
|
|
@@ -12,23 +12,22 @@ Follow these steps in order for every edit:
|
|
|
12
12
|
**`move`** — if set, move the file to the given path.
|
|
13
13
|
**`delete`** — if true, delete the file.
|
|
14
14
|
**`edits[n].pos`** — the anchor line. Meaning depends on `op`:
|
|
15
|
-
- `replace`:
|
|
16
|
-
- `prepend`: insert new lines **before
|
|
17
|
-
- `append`: insert new lines **after
|
|
15
|
+
- if `replace`: line to rewrite
|
|
16
|
+
- if `prepend`: line to insert new lines **before**; omit for beginning of file
|
|
17
|
+
- if `append`: line to insert new lines **after**; omit for end of file
|
|
18
18
|
**`edits[n].end`** — range replace only. The last line of the range (inclusive). Omit for single-line replace.
|
|
19
19
|
**`edits[n].lines`** — the replacement content:
|
|
20
|
-
- `["line1", "line2"]` —
|
|
21
|
-
- `"
|
|
22
|
-
- `[
|
|
23
|
-
- `null` or `[]` — **delete** the line(s) entirely
|
|
20
|
+
- `["line1", "line2"]` — insert `line1` and `line2`
|
|
21
|
+
- `[""]` — blank line
|
|
22
|
+
- `null` or `[]` — delete if replace, no-op if append or prepend
|
|
24
23
|
|
|
25
24
|
Tags are applied bottom-up: later edits (by position) are applied first, so earlier tags remain valid even when subsequent ops add or remove lines. Tags **MUST** be referenced from the most recent `read` output.
|
|
26
25
|
</operations>
|
|
27
26
|
|
|
28
27
|
<rules>
|
|
29
|
-
1. **Anchor on unique, structural lines.**
|
|
30
|
-
2. **
|
|
31
|
-
3. **
|
|
28
|
+
1. **Anchor on unique, structural lines.** When inserting between blocks, anchor on the nearest unique declaration using `prepend` or `append`.
|
|
29
|
+
2. **Use `prepend`/`append` only when the anchor line itself is not changing.** Inserting near an unchanged boundary keeps the edit minimal.
|
|
30
|
+
3. **Use range `replace` when any line in the span changes.** If you need to both insert lines and modify a neighboring line, a range replace covering all lines to remove is way to go.
|
|
32
31
|
</rules>
|
|
33
32
|
|
|
34
33
|
<recovery>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Reads files from local filesystem or internal URLs.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
- Reads up to {{
|
|
5
|
-
- Use `offset` and `limit` for large files
|
|
4
|
+
- Reads up to {{DEFAULT_LIMIT}} lines default
|
|
5
|
+
- Use `offset` and `limit` for large files; max {{DEFAULT_MAX_LINES}} lines per call
|
|
6
6
|
{{#if IS_HASHLINE_MODE}}
|
|
7
7
|
- Text output is CID prefixed: `LINE#ID:content`
|
|
8
8
|
{{else}}
|
package/src/sdk.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
type AgentMessage,
|
|
5
|
-
type AgentTool,
|
|
6
|
-
INTENT_FIELD,
|
|
7
|
-
type ThinkingLevel,
|
|
8
|
-
} from "@oh-my-pi/pi-agent-core";
|
|
9
|
-
import { type Message, type Model, supportsXhigh } from "@oh-my-pi/pi-ai";
|
|
1
|
+
import { Agent, type AgentEvent, type AgentMessage, type AgentTool, INTENT_FIELD } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import { type Message, type Model, supportsXhigh, type ThinkingLevel } from "@oh-my-pi/pi-ai";
|
|
3
|
+
|
|
10
4
|
import { prewarmOpenAICodexResponses } from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
|
|
11
5
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
12
6
|
import { $env, getAgentDbPath, getAgentDir, getProjectDir, logger, postmortem } from "@oh-my-pi/pi-utils";
|
|
@@ -15,7 +9,7 @@ import { AsyncJobManager } from "./async";
|
|
|
15
9
|
import { loadCapability } from "./capability";
|
|
16
10
|
import { type Rule, ruleCapability } from "./capability/rule";
|
|
17
11
|
import { ModelRegistry } from "./config/model-registry";
|
|
18
|
-
import { formatModelString, parseModelPattern, parseModelString } from "./config/model-resolver";
|
|
12
|
+
import { formatModelString, parseModelPattern, parseModelString, resolveModelRoleValue } from "./config/model-resolver";
|
|
19
13
|
import {
|
|
20
14
|
loadPromptTemplates as loadPromptTemplatesInternal,
|
|
21
15
|
type PromptTemplate,
|
|
@@ -651,6 +645,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
651
645
|
const hasThinkingEntry = sessionManager.getBranch().some(entry => entry.type === "thinking_level_change");
|
|
652
646
|
|
|
653
647
|
const hasExplicitModel = options.model !== undefined || options.modelPattern !== undefined;
|
|
648
|
+
const modelMatchPreferences = {
|
|
649
|
+
usageOrder: settings.getStorage()?.getModelUsageOrder(),
|
|
650
|
+
};
|
|
651
|
+
const defaultRoleSpec = resolveModelRoleValue(settings.getModelRole("default"), modelRegistry.getAvailable(), {
|
|
652
|
+
settings,
|
|
653
|
+
matchPreferences: modelMatchPreferences,
|
|
654
|
+
});
|
|
654
655
|
let model = options.model;
|
|
655
656
|
let modelFallbackMessage: string | undefined;
|
|
656
657
|
// If session has data, try to restore model from it.
|
|
@@ -671,16 +672,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
671
672
|
|
|
672
673
|
// If still no model, try settings default.
|
|
673
674
|
// Skip settings fallback when an explicit model was requested.
|
|
674
|
-
if (!hasExplicitModel && !model) {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const parsedModel = parseModelString(settingsDefaultModel);
|
|
678
|
-
if (parsedModel) {
|
|
679
|
-
const settingsModel = modelRegistry.find(parsedModel.provider, parsedModel.id);
|
|
680
|
-
if (settingsModel && (await hasModelApiKey(settingsModel))) {
|
|
681
|
-
model = settingsModel;
|
|
682
|
-
}
|
|
683
|
-
}
|
|
675
|
+
if (!hasExplicitModel && !model && defaultRoleSpec.model) {
|
|
676
|
+
if (await hasModelApiKey(defaultRoleSpec.model)) {
|
|
677
|
+
model = defaultRoleSpec.model;
|
|
684
678
|
}
|
|
685
679
|
}
|
|
686
680
|
|
|
@@ -700,11 +694,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
700
694
|
|
|
701
695
|
let thinkingLevel = options.thinkingLevel;
|
|
702
696
|
|
|
703
|
-
// If session has data
|
|
704
|
-
if (thinkingLevel === undefined && hasExistingSession) {
|
|
705
|
-
thinkingLevel =
|
|
706
|
-
|
|
707
|
-
|
|
697
|
+
// If session has data and includes a thinking entry, restore it
|
|
698
|
+
if (thinkingLevel === undefined && hasExistingSession && hasThinkingEntry) {
|
|
699
|
+
thinkingLevel = existingSession.thinkingLevel as ThinkingLevel;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (thinkingLevel === undefined && !hasExplicitModel && !hasThinkingEntry && defaultRoleSpec.explicitThinkingLevel) {
|
|
703
|
+
thinkingLevel = defaultRoleSpec.thinkingLevel;
|
|
708
704
|
}
|
|
709
705
|
|
|
710
706
|
// Fall back to settings default
|
|
@@ -24,7 +24,6 @@ import {
|
|
|
24
24
|
type AgentState,
|
|
25
25
|
type AgentTool,
|
|
26
26
|
INTENT_FIELD,
|
|
27
|
-
type ThinkingLevel,
|
|
28
27
|
} from "@oh-my-pi/pi-agent-core";
|
|
29
28
|
import type {
|
|
30
29
|
AssistantMessage,
|
|
@@ -33,6 +32,7 @@ import type {
|
|
|
33
32
|
Model,
|
|
34
33
|
ProviderSessionState,
|
|
35
34
|
TextContent,
|
|
35
|
+
ThinkingLevel,
|
|
36
36
|
ToolCall,
|
|
37
37
|
ToolChoice,
|
|
38
38
|
Usage,
|
|
@@ -40,6 +40,7 @@ import type {
|
|
|
40
40
|
} from "@oh-my-pi/pi-ai";
|
|
41
41
|
import {
|
|
42
42
|
calculateRateLimitBackoffMs,
|
|
43
|
+
getAvailableThinkingLevels,
|
|
43
44
|
isContextOverflow,
|
|
44
45
|
modelsAreEqual,
|
|
45
46
|
parseRateLimitReason,
|
|
@@ -49,7 +50,7 @@ import { abortableSleep, getAgentDbPath, isEnoent, logger } from "@oh-my-pi/pi-u
|
|
|
49
50
|
import type { AsyncJob, AsyncJobManager } from "../async";
|
|
50
51
|
import type { Rule } from "../capability/rule";
|
|
51
52
|
import { MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "../config/model-registry";
|
|
52
|
-
import {
|
|
53
|
+
import { extractExplicitThinkingSelector, parseModelString, resolveModelRoleValue } from "../config/model-resolver";
|
|
53
54
|
import { expandPromptTemplate, type PromptTemplate, renderPromptTemplate } from "../config/prompt-templates";
|
|
54
55
|
import type { Settings, SkillsSettings } from "../config/settings";
|
|
55
56
|
import { type BashResult, executeBash as executeBashCommand } from "../exec/bash-executor";
|
|
@@ -252,10 +253,6 @@ export interface HandoffResult {
|
|
|
252
253
|
// ============================================================================
|
|
253
254
|
|
|
254
255
|
/** Standard thinking levels */
|
|
255
|
-
const THINKING_LEVELS: ThinkingLevel[] = ["off", "minimal", "low", "medium", "high"];
|
|
256
|
-
|
|
257
|
-
/** Thinking levels including xhigh (for supported models) */
|
|
258
|
-
const THINKING_LEVELS_WITH_XHIGH: ThinkingLevel[] = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
259
256
|
|
|
260
257
|
const noOpUIContext: ExtensionUIContext = {
|
|
261
258
|
select: async (_title, _options, _dialogOptions) => undefined,
|
|
@@ -295,7 +292,6 @@ export class AgentSession {
|
|
|
295
292
|
readonly settings: Settings;
|
|
296
293
|
|
|
297
294
|
#asyncJobManager: AsyncJobManager | undefined = undefined;
|
|
298
|
-
|
|
299
295
|
#scopedModels: Array<{ model: Model; thinkingLevel: ThinkingLevel }>;
|
|
300
296
|
#promptTemplates: PromptTemplate[];
|
|
301
297
|
#slashCommands: FileSlashCommand[];
|
|
@@ -2630,7 +2626,7 @@ export class AgentSession {
|
|
|
2630
2626
|
|
|
2631
2627
|
this.#setModelWithProviderSessionReset(model);
|
|
2632
2628
|
this.sessionManager.appendModelChange(`${model.provider}/${model.id}`, role);
|
|
2633
|
-
this.settings.setModelRole(role,
|
|
2629
|
+
this.settings.setModelRole(role, this.#formatRoleModelValue(role, model));
|
|
2634
2630
|
this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
2635
2631
|
|
|
2636
2632
|
// Re-clamp thinking level for new model's capabilities without persisting settings
|
|
@@ -2684,7 +2680,13 @@ export class AgentSession {
|
|
|
2684
2680
|
|
|
2685
2681
|
const currentModel = this.model;
|
|
2686
2682
|
if (!currentModel) return undefined;
|
|
2687
|
-
const
|
|
2683
|
+
const matchPreferences = { usageOrder: this.settings.getStorage()?.getModelUsageOrder() };
|
|
2684
|
+
const roleModels: Array<{
|
|
2685
|
+
role: ModelRole;
|
|
2686
|
+
model: Model;
|
|
2687
|
+
thinkingLevel?: ThinkingLevel;
|
|
2688
|
+
explicitThinkingLevel: boolean;
|
|
2689
|
+
}> = [];
|
|
2688
2690
|
|
|
2689
2691
|
for (const role of roleOrder) {
|
|
2690
2692
|
const roleModelStr =
|
|
@@ -2693,18 +2695,18 @@ export class AgentSession {
|
|
|
2693
2695
|
: this.settings.getModelRole(role);
|
|
2694
2696
|
if (!roleModelStr) continue;
|
|
2695
2697
|
|
|
2696
|
-
const
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
}
|
|
2702
|
-
if (!match) {
|
|
2703
|
-
match = availableModels.find(m => m.id.toLowerCase() === expandedRoleModelStr.toLowerCase());
|
|
2704
|
-
}
|
|
2705
|
-
if (!match) continue;
|
|
2698
|
+
const resolved = resolveModelRoleValue(roleModelStr, availableModels, {
|
|
2699
|
+
settings: this.settings,
|
|
2700
|
+
matchPreferences,
|
|
2701
|
+
});
|
|
2702
|
+
if (!resolved.model) continue;
|
|
2706
2703
|
|
|
2707
|
-
roleModels.push({
|
|
2704
|
+
roleModels.push({
|
|
2705
|
+
role,
|
|
2706
|
+
model: resolved.model,
|
|
2707
|
+
thinkingLevel: resolved.thinkingLevel,
|
|
2708
|
+
explicitThinkingLevel: resolved.explicitThinkingLevel,
|
|
2709
|
+
});
|
|
2708
2710
|
}
|
|
2709
2711
|
|
|
2710
2712
|
if (roleModels.length <= 1) return undefined;
|
|
@@ -2724,6 +2726,10 @@ export class AgentSession {
|
|
|
2724
2726
|
await this.setModel(next.model, next.role);
|
|
2725
2727
|
}
|
|
2726
2728
|
|
|
2729
|
+
if (next.explicitThinkingLevel && next.thinkingLevel !== undefined) {
|
|
2730
|
+
this.setThinkingLevel(next.thinkingLevel);
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2727
2733
|
return { model: next.model, thinkingLevel: this.thinkingLevel, role: next.role };
|
|
2728
2734
|
}
|
|
2729
2735
|
|
|
@@ -2764,7 +2770,7 @@ export class AgentSession {
|
|
|
2764
2770
|
// Apply model
|
|
2765
2771
|
this.#setModelWithProviderSessionReset(next.model);
|
|
2766
2772
|
this.sessionManager.appendModelChange(`${next.model.provider}/${next.model.id}`);
|
|
2767
|
-
this.settings.setModelRole("default",
|
|
2773
|
+
this.settings.setModelRole("default", this.#formatRoleModelValue("default", next.model));
|
|
2768
2774
|
this.settings.getStorage()?.recordModelUsage(`${next.model.provider}/${next.model.id}`);
|
|
2769
2775
|
|
|
2770
2776
|
// Apply thinking level (setThinkingLevel clamps to model capabilities)
|
|
@@ -2792,7 +2798,7 @@ export class AgentSession {
|
|
|
2792
2798
|
|
|
2793
2799
|
this.#setModelWithProviderSessionReset(nextModel);
|
|
2794
2800
|
this.sessionManager.appendModelChange(`${nextModel.provider}/${nextModel.id}`);
|
|
2795
|
-
this.settings.setModelRole("default",
|
|
2801
|
+
this.settings.setModelRole("default", this.#formatRoleModelValue("default", nextModel));
|
|
2796
2802
|
this.settings.getStorage()?.recordModelUsage(`${nextModel.provider}/${nextModel.id}`);
|
|
2797
2803
|
|
|
2798
2804
|
// Re-clamp thinking level for new model's capabilities without persisting settings
|
|
@@ -2854,9 +2860,9 @@ export class AgentSession {
|
|
|
2854
2860
|
* Get available thinking levels for current model.
|
|
2855
2861
|
* The provider will clamp to what the specific model supports internally.
|
|
2856
2862
|
*/
|
|
2857
|
-
getAvailableThinkingLevels(): ThinkingLevel
|
|
2863
|
+
getAvailableThinkingLevels(): ReadonlyArray<ThinkingLevel> {
|
|
2858
2864
|
if (!this.supportsThinking()) return ["off"];
|
|
2859
|
-
return this.supportsXhighThinking()
|
|
2865
|
+
return getAvailableThinkingLevels(this.supportsXhighThinking());
|
|
2860
2866
|
}
|
|
2861
2867
|
|
|
2862
2868
|
/**
|
|
@@ -2873,8 +2879,8 @@ export class AgentSession {
|
|
|
2873
2879
|
return !!this.model?.reasoning;
|
|
2874
2880
|
}
|
|
2875
2881
|
|
|
2876
|
-
#clampThinkingLevel(level: ThinkingLevel, availableLevels: ThinkingLevel
|
|
2877
|
-
const ordered =
|
|
2882
|
+
#clampThinkingLevel(level: ThinkingLevel, availableLevels: ReadonlyArray<ThinkingLevel>): ThinkingLevel {
|
|
2883
|
+
const ordered = getAvailableThinkingLevels(true);
|
|
2878
2884
|
const available = new Set(availableLevels);
|
|
2879
2885
|
const requestedIndex = ordered.indexOf(level);
|
|
2880
2886
|
if (requestedIndex === -1) {
|
|
@@ -3531,33 +3537,13 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3531
3537
|
const availableModels = this.#modelRegistry.getAvailable();
|
|
3532
3538
|
if (availableModels.length === 0) return undefined;
|
|
3533
3539
|
|
|
3534
|
-
const
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
candidates.push(candidate);
|
|
3542
|
-
};
|
|
3543
|
-
|
|
3544
|
-
addCandidate(this.#resolveContextPromotionConfiguredTarget(currentModel, availableModels));
|
|
3545
|
-
|
|
3546
|
-
const sameProviderLarger = [...availableModels]
|
|
3547
|
-
.filter(
|
|
3548
|
-
m => m.provider === currentModel.provider && m.api === currentModel.api && m.contextWindow > contextWindow,
|
|
3549
|
-
)
|
|
3550
|
-
.sort((a, b) => a.contextWindow - b.contextWindow);
|
|
3551
|
-
addCandidate(sameProviderLarger[0]);
|
|
3552
|
-
for (const candidate of candidates) {
|
|
3553
|
-
if (modelsAreEqual(candidate, currentModel)) continue;
|
|
3554
|
-
if (candidate.contextWindow <= contextWindow) continue;
|
|
3555
|
-
const apiKey = await this.#modelRegistry.getApiKey(candidate, this.sessionId);
|
|
3556
|
-
if (!apiKey) continue;
|
|
3557
|
-
return candidate;
|
|
3558
|
-
}
|
|
3559
|
-
|
|
3560
|
-
return undefined;
|
|
3540
|
+
const candidate = this.#resolveContextPromotionConfiguredTarget(currentModel, availableModels);
|
|
3541
|
+
if (!candidate) return undefined;
|
|
3542
|
+
if (modelsAreEqual(candidate, currentModel)) return undefined;
|
|
3543
|
+
if (candidate.contextWindow <= contextWindow) return undefined;
|
|
3544
|
+
const apiKey = await this.#modelRegistry.getApiKey(candidate, this.sessionId);
|
|
3545
|
+
if (!apiKey) return undefined;
|
|
3546
|
+
return candidate;
|
|
3561
3547
|
}
|
|
3562
3548
|
|
|
3563
3549
|
#setModelWithProviderSessionReset(model: Model): void {
|
|
@@ -3597,6 +3583,15 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3597
3583
|
return `${model.provider}/${model.id}`;
|
|
3598
3584
|
}
|
|
3599
3585
|
|
|
3586
|
+
#formatRoleModelValue(role: ModelRole, model: Model): string {
|
|
3587
|
+
const modelKey = `${model.provider}/${model.id}`;
|
|
3588
|
+
const existingRoleValue = this.settings.getModelRole(role);
|
|
3589
|
+
if (!existingRoleValue) return modelKey;
|
|
3590
|
+
|
|
3591
|
+
const thinkingLevel = extractExplicitThinkingSelector(existingRoleValue, this.settings);
|
|
3592
|
+
if (thinkingLevel === undefined) return modelKey;
|
|
3593
|
+
return `${modelKey}:${thinkingLevel}`;
|
|
3594
|
+
}
|
|
3600
3595
|
#resolveContextPromotionConfiguredTarget(currentModel: Model, availableModels: Model[]): Model | undefined {
|
|
3601
3596
|
const configuredTarget = currentModel.contextPromotionTarget?.trim();
|
|
3602
3597
|
if (!configuredTarget) return undefined;
|
|
@@ -3619,12 +3614,10 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3619
3614
|
|
|
3620
3615
|
if (!roleModelStr) return undefined;
|
|
3621
3616
|
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
}
|
|
3626
|
-
const roleLower = roleModelStr.toLowerCase();
|
|
3627
|
-
return availableModels.find(m => m.id.toLowerCase() === roleLower);
|
|
3617
|
+
return resolveModelRoleValue(roleModelStr, availableModels, {
|
|
3618
|
+
settings: this.settings,
|
|
3619
|
+
matchPreferences: { usageOrder: this.settings.getStorage()?.getModelUsageOrder() },
|
|
3620
|
+
}).model;
|
|
3628
3621
|
}
|
|
3629
3622
|
|
|
3630
3623
|
#getCompactionModelCandidates(availableModels: Model[]): Model[] {
|
|
@@ -4575,6 +4568,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
4575
4568
|
|
|
4576
4569
|
if (!skipConversationRestore) {
|
|
4577
4570
|
this.agent.replaceMessages(sessionContext.messages);
|
|
4571
|
+
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
4578
4572
|
}
|
|
4579
4573
|
|
|
4580
4574
|
return { selectedText, cancelled: false };
|
|
@@ -4729,6 +4723,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
4729
4723
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
4730
4724
|
this.agent.replaceMessages(sessionContext.messages);
|
|
4731
4725
|
this.#syncTodoPhasesFromBranch();
|
|
4726
|
+
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
4732
4727
|
|
|
4733
4728
|
// Emit session_tree event
|
|
4734
4729
|
if (this.#extensionRunner) {
|
package/src/task/executor.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Runs each subagent on the main thread and forwards AgentEvents for progress tracking.
|
|
5
5
|
*/
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import type { AgentEvent
|
|
8
|
-
import type { Api, Model, ToolChoice } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import type { AgentEvent } from "@oh-my-pi/pi-agent-core";
|
|
8
|
+
import type { Api, Model, ThinkingLevel, ToolChoice } from "@oh-my-pi/pi-ai";
|
|
9
9
|
import { logger, untilAborted } from "@oh-my-pi/pi-utils";
|
|
10
10
|
import type { TSchema } from "@sinclair/typebox";
|
|
11
11
|
import Ajv, { type ValidateFunction } from "ajv";
|
|
@@ -938,12 +938,14 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
938
938
|
await modelRegistry.refresh();
|
|
939
939
|
checkAbort();
|
|
940
940
|
|
|
941
|
-
const {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
);
|
|
946
|
-
const effectiveThinkingLevel =
|
|
941
|
+
const {
|
|
942
|
+
model,
|
|
943
|
+
thinkingLevel: resolvedThinkingLevel,
|
|
944
|
+
explicitThinkingLevel,
|
|
945
|
+
} = resolveModelOverride(modelPatterns, modelRegistry, settings);
|
|
946
|
+
const effectiveThinkingLevel = explicitThinkingLevel
|
|
947
|
+
? resolvedThinkingLevel
|
|
948
|
+
: (thinkingLevel ?? resolvedThinkingLevel);
|
|
947
949
|
|
|
948
950
|
const sessionManager = sessionFile
|
|
949
951
|
? await SessionManager.open(sessionFile)
|
package/src/task/types.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { ThinkingLevel } from "@oh-my-pi/pi-
|
|
2
|
-
import type { Usage } from "@oh-my-pi/pi-ai";
|
|
1
|
+
import type { ThinkingLevel, Usage } from "@oh-my-pi/pi-ai";
|
|
3
2
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
4
3
|
import { type Static, Type } from "@sinclair/typebox";
|
|
5
4
|
import type { NestedRepoPatch } from "./worktree";
|