@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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.9.0] - 2026-03-05
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- Added `read.defaultLimit` setting to configure default number of lines returned by read tool when no limit is specified (default: 300 lines)
|
|
9
|
+
- Added preset options for read default limit (200, 300, 500, 1000, 5000 lines) in settings UI
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Updated read tool prompt to distinguish between default limit and maximum limit per call
|
|
14
|
+
- Moved `ThinkingLevel` type from `@oh-my-pi/pi-agent-core` to `@oh-my-pi/pi-ai` for centralized thinking level definitions
|
|
15
|
+
- Replaced local thinking level validation with `parseThinkingLevel()` and `ALL_THINKING_LEVELS` from `@oh-my-pi/pi-ai`
|
|
16
|
+
- Updated thinking level option providers to use `THINKING_MODE_DESCRIPTIONS` from `@oh-my-pi/pi-ai` for consistent descriptions
|
|
17
|
+
- Renamed `RoleThinkingMode` type to `ThinkingMode` and changed default value from `'default'` to `'inherit'` for clarity
|
|
18
|
+
- Replaced `formatThinkingEffortLabel()` utility with `formatThinking()` from `@oh-my-pi/pi-ai`
|
|
19
|
+
- Renamed `extractExplicitThinkingLevel()` to `extractExplicitThinkingSelector()` in model resolver
|
|
20
|
+
- Updated thinking level clamping to use `getAvailableThinkingLevel()` from `@oh-my-pi/pi-ai`
|
|
21
|
+
|
|
22
|
+
### Removed
|
|
23
|
+
|
|
24
|
+
- Removed `thinking-effort-label.ts` utility file (functionality moved to `@oh-my-pi/pi-ai`)
|
|
25
|
+
- Removed local `VALID_THINKING_LEVELS` constant definitions across multiple files
|
|
26
|
+
- Removed `isValidThinkingLevel()` function (replaced by `parseThinkingLevel()` from `@oh-my-pi/pi-ai`)
|
|
27
|
+
- Removed `parseThinkingLevel()` helper from discovery module (now uses centralized version from `@oh-my-pi/pi-ai`)
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Fixed provider session state not being cleared when branching or navigating tree history, preventing resource leaks with codex provider sessions
|
|
32
|
+
|
|
33
|
+
## [13.8.0] - 2026-03-04
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- Added `buildCompactHashlineDiffPreview()` function to generate compact diff previews for model-visible tool responses, collapsing long unchanged runs and consecutive additions/removals to show edit shape without full file content
|
|
37
|
+
- Added project-level discovery for `.agent/` and `.agents/` directories, walking up from cwd to repo root (matching behavior of other providers like `.omp`, `.claude`, `.codex`). Applies to skills, rules, prompts, commands, context files (AGENTS.md), and system prompts (SYSTEM.md)
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- Changed edit tool response to include diff summary with line counts (+added -removed) and a compact diff preview instead of warnings-only output
|
|
42
|
+
- Limited auto context promotion to models with explicit `contextPromotionTarget`; models without a configured target now compact on overflow instead of switching to arbitrary larger models ([#282](https://github.com/can1357/oh-my-pi/issues/282))
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- Fixed `:thinking` suffix in `modelRoles` config values silently breaking model resolution (e.g., `slow: anthropic/claude-opus-4-6:high`) and being stripped on Ctrl+P role cycling
|
|
47
|
+
|
|
5
48
|
## [13.7.6] - 2026-03-04
|
|
6
49
|
### Added
|
|
7
50
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "13.
|
|
4
|
+
"version": "13.9.1",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@mozilla/readability": "^0.6",
|
|
44
|
-
"@oh-my-pi/omp-stats": "13.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "13.
|
|
46
|
-
"@oh-my-pi/pi-ai": "13.
|
|
47
|
-
"@oh-my-pi/pi-natives": "13.
|
|
48
|
-
"@oh-my-pi/pi-tui": "13.
|
|
49
|
-
"@oh-my-pi/pi-utils": "13.
|
|
44
|
+
"@oh-my-pi/omp-stats": "13.9.1",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "13.9.1",
|
|
46
|
+
"@oh-my-pi/pi-ai": "13.9.1",
|
|
47
|
+
"@oh-my-pi/pi-natives": "13.9.1",
|
|
48
|
+
"@oh-my-pi/pi-tui": "13.9.1",
|
|
49
|
+
"@oh-my-pi/pi-utils": "13.9.1",
|
|
50
50
|
"@sinclair/typebox": "^0.34",
|
|
51
51
|
"@xterm/headless": "^6.0",
|
|
52
52
|
"ajv": "^8.18",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import { Glob } from "bun";
|
|
4
3
|
import * as path from "node:path";
|
|
4
|
+
import { Glob } from "bun";
|
|
5
5
|
|
|
6
6
|
const docsDir = path.resolve(import.meta.dir, "../../../docs");
|
|
7
7
|
const outputPath = path.resolve(import.meta.dir, "../src/internal-urls/docs-index.generated.ts");
|
|
@@ -14,10 +14,10 @@ for await (const relativePath of glob.scan(docsDir)) {
|
|
|
14
14
|
entries.sort();
|
|
15
15
|
|
|
16
16
|
const docsWithContent = await Promise.all(
|
|
17
|
-
entries.map(async
|
|
17
|
+
entries.map(async relativePath => ({
|
|
18
18
|
relativePath,
|
|
19
19
|
content: await Bun.file(path.join(docsDir, relativePath)).text(),
|
|
20
|
-
}))
|
|
20
|
+
})),
|
|
21
21
|
);
|
|
22
22
|
|
|
23
23
|
const filenamesLiteral = JSON.stringify(entries);
|
|
@@ -27,9 +27,12 @@ export const contextFileCapability = defineCapability<ContextFile>({
|
|
|
27
27
|
id: "context-files",
|
|
28
28
|
displayName: "Context Files",
|
|
29
29
|
description: "Persistent instruction files (CLAUDE.md, AGENTS.md, etc.) that guide agent behavior",
|
|
30
|
-
// Deduplicate by
|
|
31
|
-
//
|
|
32
|
-
|
|
30
|
+
// Deduplicate by scope: one user-level file, and one project-level file per directory depth.
|
|
31
|
+
// Within each depth level, higher-priority providers shadow lower-priority ones.
|
|
32
|
+
// This supports monorepo hierarchies where AGENTS.md exists at multiple ancestor levels.
|
|
33
|
+
// Clamp depth >= 0: files inside config subdirectories of an ancestor (e.g. .claude/, .github/)
|
|
34
|
+
// are same-scope as the ancestor itself.
|
|
35
|
+
key: file => (file.level === "user" ? "user" : `project:${Math.max(0, file.depth ?? 0)}`),
|
|
33
36
|
validate: file => {
|
|
34
37
|
if (!file.path) return "Missing path";
|
|
35
38
|
if (file.content === undefined) return "Missing content";
|
package/src/capability/fs.ts
CHANGED
|
@@ -66,6 +66,24 @@ export async function walkUp(
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Walk up from startDir looking for a `.git` entry (file or directory).
|
|
71
|
+
* Returns the directory containing `.git` (the repo root), or null if not in a git repo.
|
|
72
|
+
* Results are based on the cached readDirEntries, so repeated calls are cheap.
|
|
73
|
+
*/
|
|
74
|
+
export async function findRepoRoot(startDir: string): Promise<string | null> {
|
|
75
|
+
let current = resolvePath(startDir);
|
|
76
|
+
while (true) {
|
|
77
|
+
const entries = await readDirEntries(current);
|
|
78
|
+
if (entries.some(e => e.name === ".git")) {
|
|
79
|
+
return current;
|
|
80
|
+
}
|
|
81
|
+
const parent = path.dirname(current);
|
|
82
|
+
if (parent === current) return null;
|
|
83
|
+
current = parent;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
69
87
|
export function cacheStats(): { content: number; dir: number } {
|
|
70
88
|
return {
|
|
71
89
|
content: contentCache.size,
|
package/src/capability/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ import * as path from "node:path";
|
|
|
11
11
|
import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
|
12
12
|
|
|
13
13
|
import type { Settings } from "../config/settings";
|
|
14
|
-
import { clearCache as clearFsCache, cacheStats as fsCacheStats, invalidate as invalidateFs } from "./fs";
|
|
14
|
+
import { clearCache as clearFsCache, findRepoRoot, cacheStats as fsCacheStats, invalidate as invalidateFs } from "./fs";
|
|
15
15
|
import type {
|
|
16
16
|
Capability,
|
|
17
17
|
CapabilityInfo,
|
|
@@ -220,7 +220,8 @@ export async function loadCapability<T>(capabilityId: string, options: LoadOptio
|
|
|
220
220
|
|
|
221
221
|
const cwd = options.cwd ?? getProjectDir();
|
|
222
222
|
const home = os.homedir();
|
|
223
|
-
const
|
|
223
|
+
const repoRoot = await findRepoRoot(cwd);
|
|
224
|
+
const ctx: LoadContext = { cwd, home, repoRoot };
|
|
224
225
|
const providers = filterProviders(capability, options);
|
|
225
226
|
|
|
226
227
|
return await loadImpl(capability, providers, ctx, options);
|
package/src/capability/rule.ts
CHANGED
|
@@ -20,10 +20,6 @@ export interface RuleFrontmatter {
|
|
|
20
20
|
condition?: string | string[];
|
|
21
21
|
/** New key for TTSR stream scope. */
|
|
22
22
|
scope?: string | string[];
|
|
23
|
-
/** Legacy key accepted for backward compatibility with existing rules. */
|
|
24
|
-
ttsr_trigger?: string | string[];
|
|
25
|
-
/** Legacy camelCase key accepted for backward compatibility with existing rules. */
|
|
26
|
-
ttsrTrigger?: string | string[];
|
|
27
23
|
[key: string]: unknown;
|
|
28
24
|
}
|
|
29
25
|
|
package/src/capability/types.ts
CHANGED
package/src/cli/agents-cli.ts
CHANGED
|
@@ -61,7 +61,7 @@ function toFrontmatter(agent: AgentDefinition): Record<string, unknown> {
|
|
|
61
61
|
if (agent.tools && agent.tools.length > 0) frontmatter.tools = agent.tools;
|
|
62
62
|
if (agent.spawns !== undefined) frontmatter.spawns = agent.spawns;
|
|
63
63
|
if (agent.model && agent.model.length > 0) frontmatter.model = agent.model;
|
|
64
|
-
if (agent.thinkingLevel) frontmatter
|
|
64
|
+
if (agent.thinkingLevel) frontmatter.thinkingLevel = agent.thinkingLevel;
|
|
65
65
|
if (agent.output !== undefined) frontmatter.output = agent.output;
|
|
66
66
|
if (agent.blocking) frontmatter.blocking = true;
|
|
67
67
|
|
package/src/cli/args.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI argument parsing and help display
|
|
3
3
|
*/
|
|
4
|
-
import type
|
|
4
|
+
import { getAvailableThinkingLevels, parseThinkingLevel, type ThinkingLevel } from "@oh-my-pi/pi-ai";
|
|
5
5
|
import { APP_NAME, CONFIG_DIR_NAME, logger } from "@oh-my-pi/pi-utils";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import { BUILTIN_TOOLS } from "../tools";
|
|
@@ -48,12 +48,6 @@ export interface Args {
|
|
|
48
48
|
unknownFlags: Map<string, boolean | string>;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const VALID_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
|
|
52
|
-
|
|
53
|
-
export function isValidThinkingLevel(level: string): level is ThinkingLevel {
|
|
54
|
-
return VALID_THINKING_LEVELS.includes(level as ThinkingLevel);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
51
|
export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "boolean" | "string" }>): Args {
|
|
58
52
|
const result: Args = {
|
|
59
53
|
messages: [],
|
|
@@ -127,13 +121,14 @@ export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "
|
|
|
127
121
|
}
|
|
128
122
|
result.tools = validTools;
|
|
129
123
|
} else if (arg === "--thinking" && i + 1 < args.length) {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
124
|
+
const rawThinking = args[++i];
|
|
125
|
+
const thinking = parseThinkingLevel(rawThinking);
|
|
126
|
+
if (thinking !== undefined) {
|
|
127
|
+
result.thinking = thinking;
|
|
133
128
|
} else {
|
|
134
129
|
logger.warn("Invalid thinking level passed to --thinking", {
|
|
135
|
-
level,
|
|
136
|
-
validThinkingLevels:
|
|
130
|
+
level: rawThinking,
|
|
131
|
+
validThinkingLevels: getAvailableThinkingLevels(),
|
|
137
132
|
});
|
|
138
133
|
}
|
|
139
134
|
} else if (arg === "--print" || arg === "-p") {
|
package/src/commands/launch.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Root command for the coding agent CLI.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { getAvailableThinkingLevels } from "@oh-my-pi/pi-ai";
|
|
5
6
|
import { APP_NAME } from "@oh-my-pi/pi-utils";
|
|
6
7
|
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
7
8
|
import { parseArgs } from "../cli/args";
|
|
@@ -85,8 +86,8 @@ export default class Index extends Command {
|
|
|
85
86
|
description: "Comma-separated list of tools to enable (default: all)",
|
|
86
87
|
}),
|
|
87
88
|
thinking: Flags.string({
|
|
88
|
-
description:
|
|
89
|
-
options:
|
|
89
|
+
description: `Set thinking level: ${getAvailableThinkingLevels().join(", ")}`,
|
|
90
|
+
options: getAvailableThinkingLevels(),
|
|
90
91
|
}),
|
|
91
92
|
hook: Flags.string({
|
|
92
93
|
description: "Load a hook/extension file (can be used multiple times)",
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Model resolution, scoping, and initial selection
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
type Api,
|
|
6
|
+
DEFAULT_MODEL_PER_PROVIDER,
|
|
7
|
+
type KnownProvider,
|
|
8
|
+
type Model,
|
|
9
|
+
modelsAreEqual,
|
|
10
|
+
parseThinkingLevel,
|
|
11
|
+
type ThinkingLevel,
|
|
12
|
+
} from "@oh-my-pi/pi-ai";
|
|
6
13
|
import chalk from "chalk";
|
|
7
|
-
import { isValidThinkingLevel } from "../cli/args";
|
|
8
14
|
import MODEL_PRIO from "../priority.json" with { type: "json" };
|
|
9
15
|
import { fuzzyMatch } from "../utils/fuzzy";
|
|
10
16
|
import { MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "./model-registry";
|
|
@@ -23,10 +29,23 @@ export interface ScopedModel {
|
|
|
23
29
|
* Parse a model string in "provider/modelId" format.
|
|
24
30
|
* Returns undefined if the format is invalid.
|
|
25
31
|
*/
|
|
26
|
-
export function parseModelString(
|
|
32
|
+
export function parseModelString(
|
|
33
|
+
modelStr: string,
|
|
34
|
+
): { provider: string; id: string; thinkingLevel?: ThinkingLevel } | undefined {
|
|
27
35
|
const slashIdx = modelStr.indexOf("/");
|
|
28
36
|
if (slashIdx <= 0) return undefined;
|
|
29
|
-
|
|
37
|
+
const id = modelStr.slice(slashIdx + 1);
|
|
38
|
+
const provider = modelStr.slice(0, slashIdx);
|
|
39
|
+
// Strip valid thinking level suffix (e.g., "claude-sonnet-4-6:high" -> id "claude-sonnet-4-6", thinkingLevel "high")
|
|
40
|
+
const colonIdx = id.lastIndexOf(":");
|
|
41
|
+
if (colonIdx !== -1) {
|
|
42
|
+
const suffix = id.slice(colonIdx + 1);
|
|
43
|
+
const thinkingLevel = parseThinkingLevel(suffix);
|
|
44
|
+
if (thinkingLevel) {
|
|
45
|
+
return { provider, id: id.slice(0, colonIdx), thinkingLevel };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { provider, id };
|
|
30
49
|
}
|
|
31
50
|
|
|
32
51
|
/**
|
|
@@ -230,7 +249,7 @@ function parseModelPatternWithContext(
|
|
|
230
249
|
pattern: string,
|
|
231
250
|
availableModels: Model<Api>[],
|
|
232
251
|
context: ModelPreferenceContext,
|
|
233
|
-
options?: {
|
|
252
|
+
options?: { allowInvalidThinkingSelectorFallback?: boolean },
|
|
234
253
|
): ParsedModelResult {
|
|
235
254
|
// Try exact match first
|
|
236
255
|
const exactMatch = tryMatchModel(pattern, availableModels, context);
|
|
@@ -248,7 +267,8 @@ function parseModelPatternWithContext(
|
|
|
248
267
|
const prefix = pattern.substring(0, lastColonIndex);
|
|
249
268
|
const suffix = pattern.substring(lastColonIndex + 1);
|
|
250
269
|
|
|
251
|
-
|
|
270
|
+
const parsedThinkingLevel = parseThinkingLevel(suffix);
|
|
271
|
+
if (parsedThinkingLevel) {
|
|
252
272
|
// Valid thinking level - recurse on prefix and use this level
|
|
253
273
|
const result = parseModelPatternWithContext(prefix, availableModels, context, options);
|
|
254
274
|
if (result.model) {
|
|
@@ -256,7 +276,7 @@ function parseModelPatternWithContext(
|
|
|
256
276
|
const explicitThinkingLevel = !result.warning;
|
|
257
277
|
return {
|
|
258
278
|
model: result.model,
|
|
259
|
-
thinkingLevel: explicitThinkingLevel ?
|
|
279
|
+
thinkingLevel: explicitThinkingLevel ? parsedThinkingLevel : undefined,
|
|
260
280
|
warning: result.warning,
|
|
261
281
|
explicitThinkingLevel,
|
|
262
282
|
};
|
|
@@ -264,7 +284,7 @@ function parseModelPatternWithContext(
|
|
|
264
284
|
return result;
|
|
265
285
|
}
|
|
266
286
|
|
|
267
|
-
const allowFallback = options?.
|
|
287
|
+
const allowFallback = options?.allowInvalidThinkingSelectorFallback ?? true;
|
|
268
288
|
if (!allowFallback) {
|
|
269
289
|
return { model: undefined, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
|
|
270
290
|
}
|
|
@@ -286,7 +306,7 @@ export function parseModelPattern(
|
|
|
286
306
|
pattern: string,
|
|
287
307
|
availableModels: Model<Api>[],
|
|
288
308
|
preferences?: ModelMatchPreferences,
|
|
289
|
-
options?: {
|
|
309
|
+
options?: { allowInvalidThinkingSelectorFallback?: boolean },
|
|
290
310
|
): ParsedModelResult {
|
|
291
311
|
const context = buildPreferenceContext(availableModels, preferences);
|
|
292
312
|
return parseModelPatternWithContext(pattern, availableModels, context, options);
|
|
@@ -319,6 +339,74 @@ export function expandRoleAlias(value: string, settings?: Settings): string {
|
|
|
319
339
|
return settings?.getModelRole(role) ?? value;
|
|
320
340
|
}
|
|
321
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Resolve a model role value into a concrete model and thinking metadata.
|
|
344
|
+
*/
|
|
345
|
+
export interface ResolvedModelRoleValue {
|
|
346
|
+
model: Model<Api> | undefined;
|
|
347
|
+
thinkingLevel?: ThinkingLevel;
|
|
348
|
+
explicitThinkingLevel: boolean;
|
|
349
|
+
warning: string | undefined;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function resolveModelRoleValue(
|
|
353
|
+
roleValue: string | undefined,
|
|
354
|
+
availableModels: Model<Api>[],
|
|
355
|
+
options?: { settings?: Settings; matchPreferences?: ModelMatchPreferences },
|
|
356
|
+
): ResolvedModelRoleValue {
|
|
357
|
+
if (!roleValue) {
|
|
358
|
+
return { model: undefined, thinkingLevel: undefined, explicitThinkingLevel: false, warning: undefined };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const normalized = roleValue.trim();
|
|
362
|
+
if (!normalized || normalized === DEFAULT_MODEL_ROLE) {
|
|
363
|
+
return { model: undefined, thinkingLevel: undefined, explicitThinkingLevel: false, warning: undefined };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const lastColonIndex = normalized.lastIndexOf(":");
|
|
367
|
+
const hasThinkingSuffix =
|
|
368
|
+
lastColonIndex > PREFIX_MODEL_ROLE.length && parseThinkingLevel(normalized.slice(lastColonIndex + 1));
|
|
369
|
+
const aliasCandidate = hasThinkingSuffix ? normalized.slice(0, lastColonIndex) : normalized;
|
|
370
|
+
const effectivePattern = expandRoleAlias(aliasCandidate, options?.settings);
|
|
371
|
+
const patternWithSuffix = hasThinkingSuffix
|
|
372
|
+
? `${effectivePattern}:${normalized.slice(lastColonIndex + 1)}`
|
|
373
|
+
: effectivePattern;
|
|
374
|
+
const { model, thinkingLevel, warning, explicitThinkingLevel } = parseModelPattern(
|
|
375
|
+
patternWithSuffix,
|
|
376
|
+
availableModels,
|
|
377
|
+
options?.matchPreferences,
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
return { model, thinkingLevel, explicitThinkingLevel, warning };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function extractExplicitThinkingSelector(
|
|
384
|
+
value: string | undefined,
|
|
385
|
+
settings?: Settings,
|
|
386
|
+
): ThinkingLevel | undefined {
|
|
387
|
+
if (!value) return undefined;
|
|
388
|
+
const normalized = value.trim();
|
|
389
|
+
if (!normalized || normalized === DEFAULT_MODEL_ROLE) return undefined;
|
|
390
|
+
|
|
391
|
+
const visited = new Set<string>();
|
|
392
|
+
let current = normalized;
|
|
393
|
+
while (!visited.has(current)) {
|
|
394
|
+
visited.add(current);
|
|
395
|
+
const lastColonIndex = current.lastIndexOf(":");
|
|
396
|
+
const hasThinkingSuffix =
|
|
397
|
+
lastColonIndex > PREFIX_MODEL_ROLE.length && parseThinkingLevel(current.slice(lastColonIndex + 1));
|
|
398
|
+
if (hasThinkingSuffix) {
|
|
399
|
+
return current.slice(lastColonIndex + 1) as ThinkingLevel;
|
|
400
|
+
}
|
|
401
|
+
const expanded = expandRoleAlias(current, settings).trim();
|
|
402
|
+
if (!expanded || expanded === current) break;
|
|
403
|
+
if (expanded === DEFAULT_MODEL_ROLE) return undefined;
|
|
404
|
+
current = expanded;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return undefined;
|
|
408
|
+
}
|
|
409
|
+
|
|
322
410
|
/**
|
|
323
411
|
* Resolve a model identifier or pattern to a Model instance.
|
|
324
412
|
*/
|
|
@@ -329,7 +417,8 @@ export function resolveModelFromString(
|
|
|
329
417
|
): Model<Api> | undefined {
|
|
330
418
|
const parsed = parseModelString(value);
|
|
331
419
|
if (parsed) {
|
|
332
|
-
|
|
420
|
+
const exact = available.find(model => model.provider === parsed.provider && model.id === parsed.id);
|
|
421
|
+
if (exact) return exact;
|
|
333
422
|
}
|
|
334
423
|
return parseModelPattern(value, available, matchPreferences).model;
|
|
335
424
|
}
|
|
@@ -361,25 +450,20 @@ export function resolveModelOverride(
|
|
|
361
450
|
modelPatterns: string[],
|
|
362
451
|
modelRegistry: ModelRegistry,
|
|
363
452
|
settings?: Settings,
|
|
364
|
-
): { model?: Model<Api>; thinkingLevel?: ThinkingLevel } {
|
|
365
|
-
if (modelPatterns.length === 0) return {};
|
|
453
|
+
): { model?: Model<Api>; thinkingLevel?: ThinkingLevel; explicitThinkingLevel: boolean } {
|
|
454
|
+
if (modelPatterns.length === 0) return { explicitThinkingLevel: false };
|
|
455
|
+
const availableModels = modelRegistry.getAvailable();
|
|
366
456
|
const matchPreferences = { usageOrder: settings?.getStorage()?.getModelUsageOrder() };
|
|
367
457
|
for (const pattern of modelPatterns) {
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
const effectivePattern = expandRoleAlias(pattern, settings);
|
|
373
|
-
const { model, thinkingLevel } = parseModelPattern(
|
|
374
|
-
effectivePattern,
|
|
375
|
-
modelRegistry.getAvailable(),
|
|
458
|
+
const { model, thinkingLevel, explicitThinkingLevel } = resolveModelRoleValue(pattern, availableModels, {
|
|
459
|
+
settings,
|
|
376
460
|
matchPreferences,
|
|
377
|
-
);
|
|
461
|
+
});
|
|
378
462
|
if (model) {
|
|
379
|
-
return { model, thinkingLevel
|
|
463
|
+
return { model, thinkingLevel, explicitThinkingLevel };
|
|
380
464
|
}
|
|
381
465
|
}
|
|
382
|
-
return {};
|
|
466
|
+
return { explicitThinkingLevel: false };
|
|
383
467
|
}
|
|
384
468
|
|
|
385
469
|
/**
|
|
@@ -413,8 +497,9 @@ export async function resolveModelScope(
|
|
|
413
497
|
|
|
414
498
|
if (colonIdx !== -1) {
|
|
415
499
|
const suffix = pattern.substring(colonIdx + 1);
|
|
416
|
-
|
|
417
|
-
|
|
500
|
+
const parsedThinkingLevel = parseThinkingLevel(suffix);
|
|
501
|
+
if (parsedThinkingLevel) {
|
|
502
|
+
thinkingLevel = parsedThinkingLevel;
|
|
418
503
|
explicitThinkingLevel = true;
|
|
419
504
|
globPattern = pattern.substring(0, colonIdx);
|
|
420
505
|
}
|
|
@@ -541,7 +626,7 @@ export function resolveCliModel(options: {
|
|
|
541
626
|
|
|
542
627
|
const candidates = provider ? availableModels.filter(model => model.provider === provider) : availableModels;
|
|
543
628
|
const { model, thinkingLevel, warning } = parseModelPattern(pattern, candidates, preferences, {
|
|
544
|
-
|
|
629
|
+
allowInvalidThinkingSelectorFallback: false,
|
|
545
630
|
});
|
|
546
631
|
|
|
547
632
|
if (!model) {
|
|
@@ -578,7 +663,7 @@ export async function findInitialModel(options: {
|
|
|
578
663
|
isContinuing: boolean;
|
|
579
664
|
defaultProvider?: string;
|
|
580
665
|
defaultModelId?: string;
|
|
581
|
-
|
|
666
|
+
defaultThinkingSelector?: ThinkingLevel;
|
|
582
667
|
modelRegistry: ModelRegistry;
|
|
583
668
|
}): Promise<InitialModelResult> {
|
|
584
669
|
const {
|
|
@@ -588,7 +673,7 @@ export async function findInitialModel(options: {
|
|
|
588
673
|
isContinuing,
|
|
589
674
|
defaultProvider,
|
|
590
675
|
defaultModelId,
|
|
591
|
-
|
|
676
|
+
defaultThinkingSelector,
|
|
592
677
|
modelRegistry,
|
|
593
678
|
} = options;
|
|
594
679
|
|
|
@@ -608,10 +693,10 @@ export async function findInitialModel(options: {
|
|
|
608
693
|
// 2. Use first model from scoped models (skip if continuing/resuming)
|
|
609
694
|
if (scopedModels.length > 0 && !isContinuing) {
|
|
610
695
|
const scoped = scopedModels[0];
|
|
611
|
-
const
|
|
696
|
+
const scopedThinkingSelector = scoped.thinkingLevel ?? defaultThinkingSelector ?? "off";
|
|
612
697
|
return {
|
|
613
698
|
model: scoped.model,
|
|
614
|
-
thinkingLevel:
|
|
699
|
+
thinkingLevel: scopedThinkingSelector,
|
|
615
700
|
fallbackMessage: undefined,
|
|
616
701
|
};
|
|
617
702
|
}
|
|
@@ -621,8 +706,8 @@ export async function findInitialModel(options: {
|
|
|
621
706
|
const found = modelRegistry.find(defaultProvider, defaultModelId);
|
|
622
707
|
if (found) {
|
|
623
708
|
model = found;
|
|
624
|
-
if (
|
|
625
|
-
thinkingLevel =
|
|
709
|
+
if (defaultThinkingSelector) {
|
|
710
|
+
thinkingLevel = defaultThinkingSelector;
|
|
626
711
|
}
|
|
627
712
|
return { model, thinkingLevel, fallbackMessage: undefined };
|
|
628
713
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { getAvailableThinkingLevels } from "@oh-my-pi/pi-ai";
|
|
2
|
+
|
|
3
|
+
/** Unified settings schema - single source of truth for all settings.
|
|
2
4
|
* Unified settings schema - single source of truth for all settings.
|
|
3
5
|
*
|
|
4
6
|
* Each setting is defined once here with:
|
|
@@ -190,7 +192,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
190
192
|
},
|
|
191
193
|
defaultThinkingLevel: {
|
|
192
194
|
type: "enum",
|
|
193
|
-
values:
|
|
195
|
+
values: getAvailableThinkingLevels(),
|
|
194
196
|
default: "high",
|
|
195
197
|
ui: {
|
|
196
198
|
tab: "agent",
|
|
@@ -283,6 +285,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
283
285
|
description: "Include line hashes in read output for hashline edit mode (LINE#ID:content)",
|
|
284
286
|
},
|
|
285
287
|
},
|
|
288
|
+
"read.defaultLimit": {
|
|
289
|
+
type: "number",
|
|
290
|
+
default: 300,
|
|
291
|
+
ui: {
|
|
292
|
+
tab: "tools",
|
|
293
|
+
label: "Read default limit",
|
|
294
|
+
description: "Default number of lines returned when agent calls read without a limit",
|
|
295
|
+
submenu: true,
|
|
296
|
+
},
|
|
297
|
+
},
|
|
286
298
|
showHardwareCursor: {
|
|
287
299
|
type: "boolean",
|
|
288
300
|
default: true, // will be computed based on platform if undefined
|
package/src/config/settings.ts
CHANGED
|
@@ -318,21 +318,6 @@ export class Settings {
|
|
|
318
318
|
return result as unknown as GroupTypeMap[G];
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
/**
|
|
322
|
-
* Get edit model variants (typed accessor for complex nested config).
|
|
323
|
-
*/
|
|
324
|
-
getEditModelVariants(): Record<string, EditMode | null> {
|
|
325
|
-
const variants = (this.#merged.edit as { modelVariants?: Record<string, string> })?.modelVariants ?? {};
|
|
326
|
-
const result: Record<string, EditMode | null> = {};
|
|
327
|
-
for (const pattern in variants) {
|
|
328
|
-
const value = normalizeEditMode(variants[pattern]);
|
|
329
|
-
if (value) {
|
|
330
|
-
result[pattern] = value;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
return result;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
321
|
/**
|
|
337
322
|
* Get the edit variant for a specific model.
|
|
338
323
|
* Returns "patch", "replace", "hashline", or null (use global default).
|
|
@@ -341,9 +326,8 @@ export class Settings {
|
|
|
341
326
|
if (!model) return null;
|
|
342
327
|
const variants = (this.#merged.edit as { modelVariants?: Record<string, string> })?.modelVariants;
|
|
343
328
|
if (!variants) return null;
|
|
344
|
-
const modelLower = model.toLowerCase();
|
|
345
329
|
for (const pattern in variants) {
|
|
346
|
-
if (
|
|
330
|
+
if (model.includes(pattern)) {
|
|
347
331
|
const value = normalizeEditMode(variants[pattern]);
|
|
348
332
|
if (value) {
|
|
349
333
|
return value;
|
|
@@ -14,7 +14,6 @@ import { calculateDepth, createSourceMeta } from "./helpers";
|
|
|
14
14
|
|
|
15
15
|
const PROVIDER_ID = "agents-md";
|
|
16
16
|
const DISPLAY_NAME = "AGENTS.md";
|
|
17
|
-
const MAX_DEPTH = 20; // Prevent walking up excessively far from cwd
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
19
|
* Load standalone AGENTS.md files.
|
|
@@ -25,9 +24,8 @@ async function loadAgentsMd(ctx: LoadContext): Promise<LoadResult<ContextFile>>
|
|
|
25
24
|
|
|
26
25
|
// Walk up from cwd looking for AGENTS.md files
|
|
27
26
|
let current = ctx.cwd;
|
|
28
|
-
let depth = 0;
|
|
29
27
|
|
|
30
|
-
while (
|
|
28
|
+
while (true) {
|
|
31
29
|
const candidate = path.join(current, "AGENTS.md");
|
|
32
30
|
const content = await readFile(candidate);
|
|
33
31
|
|
|
@@ -49,11 +47,12 @@ async function loadAgentsMd(ctx: LoadContext): Promise<LoadResult<ContextFile>>
|
|
|
49
47
|
}
|
|
50
48
|
}
|
|
51
49
|
|
|
50
|
+
if (current === (ctx.repoRoot ?? ctx.home)) break; // scanned repo root or home, stop
|
|
51
|
+
|
|
52
52
|
// Move to parent directory
|
|
53
53
|
const parent = path.dirname(current);
|
|
54
54
|
if (parent === current) break; // Reached filesystem root
|
|
55
55
|
current = parent;
|
|
56
|
-
depth++;
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
return { items, warnings };
|