@oh-my-pi/pi-coding-agent 13.10.0 → 13.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/package.json +7 -7
- package/src/commit/agentic/agent.ts +3 -1
- package/src/commit/agentic/index.ts +7 -1
- package/src/commit/analysis/conventional.ts +5 -1
- package/src/commit/analysis/summary.ts +5 -1
- package/src/commit/changelog/generate.ts +5 -1
- package/src/commit/changelog/index.ts +4 -0
- package/src/commit/map-reduce/index.ts +5 -0
- package/src/commit/map-reduce/map-phase.ts +17 -2
- package/src/commit/map-reduce/reduce-phase.ts +5 -1
- package/src/commit/model-selection.ts +38 -26
- package/src/commit/pipeline.ts +22 -11
- package/src/config/settings-schema.ts +20 -0
- package/src/config.ts +10 -3
- package/src/discovery/helpers.ts +7 -3
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/lsp/index.ts +4 -4
- package/src/lsp/utils.ts +81 -0
- package/src/main.ts +25 -14
- package/src/mcp/manager.ts +40 -2
- package/src/mcp/oauth-flow.ts +41 -0
- package/src/mcp/transports/http.ts +23 -0
- package/src/mcp/types.ts +6 -0
- package/src/modes/components/mcp-add-wizard.ts +12 -0
- package/src/modes/components/settings-defs.ts +2 -1
- package/src/modes/components/todo-reminder.ts +8 -1
- package/src/modes/controllers/command-controller.ts +75 -3
- package/src/modes/controllers/input-controller.ts +2 -3
- package/src/modes/controllers/mcp-command-controller.ts +9 -1
- package/src/modes/interactive-mode.ts +11 -7
- package/src/modes/theme/theme.ts +30 -27
- package/src/modes/types.ts +2 -1
- package/src/patch/hashline.ts +3 -6
- package/src/prompts/system/eager-todo.md +13 -0
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/find.md +1 -0
- package/src/prompts/tools/grep.md +1 -0
- package/src/prompts/tools/hashline.md +23 -111
- package/src/prompts/tools/todo-write.md +11 -1
- package/src/sdk.ts +1 -1
- package/src/session/agent-session.ts +85 -7
- package/src/session/session-manager.ts +5 -9
- package/src/slash-commands/builtin-registry.ts +10 -2
- package/src/task/executor.ts +9 -18
- package/src/task/index.ts +8 -4
- package/src/task/render.ts +5 -10
- package/src/task/template.ts +4 -1
- package/src/task/types.ts +2 -0
- package/src/tools/ast-edit.ts +26 -7
- package/src/tools/ast-grep.ts +26 -9
- package/src/tools/fetch.ts +36 -5
- package/src/tools/find.ts +13 -64
- package/src/tools/grep.ts +27 -10
- package/src/tools/json-tree.ts +1 -1
- package/src/tools/output-meta.ts +2 -1
- package/src/tools/path-utils.ts +348 -0
- package/src/tools/todo-write.ts +27 -4
- package/src/utils/commit-message-generator.ts +27 -22
- package/src/utils/image-input.ts +1 -1
- package/src/utils/image-resize.ts +4 -4
- package/src/utils/title-generator.ts +36 -23
- package/src/utils/tool-choice.ts +28 -0
- package/src/web/parallel.ts +346 -0
- package/src/web/scrapers/youtube.ts +29 -0
- package/src/web/search/provider.ts +4 -1
- package/src/web/search/providers/parallel.ts +63 -0
- package/src/web/search/types.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,58 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.11.0] - 2026-03-12
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- Added Parallel as a web search provider with support for fast and research modes
|
|
9
|
+
- Added Parallel extract API integration for URL content fetching and YouTube video extraction
|
|
10
|
+
- Added `providers.parallelFetch` setting to enable/disable Parallel extract for URL fetching
|
|
11
|
+
- Added `/login parallel` command support for Parallel API authentication
|
|
12
|
+
- Added subcommands to `/copy` command: `code` (copy last code block), `all` (copy all code blocks), `cmd` (copy last bash/python command), and `last` (copy full message)
|
|
13
|
+
- Added support for copying last executed bash or python command via `/copy cmd` subcommand
|
|
14
|
+
- Added `assignment` field to task progress and result objects to track the raw per-task assignment text separately from the full templated task
|
|
15
|
+
- Added `details` field to todo items for storing implementation specifics, file paths, and edge cases (shown only when task is active)
|
|
16
|
+
- Added support for multi-line details in todo items with automatic indentation in interactive and reminder displays
|
|
17
|
+
- Added `todo.eager` setting to automatically create a comprehensive todo list after the first user message
|
|
18
|
+
- Added `buildNamedToolChoice` utility function to build provider-aware tool choice constraints for named tools
|
|
19
|
+
- Support for comma/space-separated path lists in `find`, `grep`, `ast_grep`, and `ast_edit` tools (e.g., `apps/,packages/,phases/` or `apps/ packages/ phases/`)
|
|
20
|
+
- New `resolveMultiSearchPath` and `resolveMultiFindPattern` functions to handle multi-path search inputs with automatic common base path detection
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Updated HTML-to-text rendering to prefer Parallel extract when credentials are available, before falling back to jina, trafilatura, or lynx
|
|
25
|
+
- Updated YouTube scraper to prefer Parallel extract when credentials are available, before falling back to yt-dlp
|
|
26
|
+
- Updated web search provider priority order to include Parallel between Exa and Kagi
|
|
27
|
+
- Updated hashline tool documentation with explicit guidance on `replace` operation semantics, clarifying that `lines` must not extend past `end` to avoid unintended line duplication
|
|
28
|
+
- Improved diagnostic message formatting to group errors by file path with indented details for better readability
|
|
29
|
+
- Modified eager todo prelude to use hidden custom message type instead of visible developer message, preventing duplicate prompt text in session history
|
|
30
|
+
- Updated eager todo prompt to remove dynamic user request injection, simplifying the template and preventing request repetition in displayed messages
|
|
31
|
+
- Modified eager todo enforcement to prepend the todo reminder to the first user turn instead of executing it as a separate synthetic turn, reducing unnecessary prompt calls
|
|
32
|
+
- Updated task rendering to display assignment text instead of full task template when available, reducing noise in progress and result displays
|
|
33
|
+
- Modified task section rendering to show trimmed assignment text without stripping context blocks, simplifying the display logic
|
|
34
|
+
- Updated todo item display to show `details` field indented below active tasks in both interactive mode and todo reminder component
|
|
35
|
+
- Modified tool choice resolution to support per-turn tool choice overrides via `consumeNextToolChoiceOverride()`
|
|
36
|
+
- Updated tool documentation to clarify that `path` parameter accepts files, directories, glob patterns, or comma/space-separated path lists
|
|
37
|
+
- Refactored path resolution logic in `find`, `grep`, `ast_grep`, and `ast_edit` tools to use unified multi-path handling
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- Fixed hashline line normalization to trim trailing whitespace and strip carriage returns instead of removing all whitespace, preserving intentional spacing in code
|
|
42
|
+
- Fixed noop detection in hashline replace operations to check array length equality before comparing lines, preventing false noop classification when single-line replacements expand to multiple lines
|
|
43
|
+
- Fixed path resolution to accept bare directory names without trailing slashes in comma/space-separated path lists (e.g., `apps packages phases`)
|
|
44
|
+
- Per-role `modelRoles` thinking selectors now propagate through commit/title helper model selection, legacy commit analysis, and agentic commit sessions while preserving default thinking inheritance when no role override is configured
|
|
45
|
+
|
|
46
|
+
## [13.10.1] - 2026-03-10
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- Exported `submitInteractiveInput()` function for programmatic submission of user input in interactive mode
|
|
50
|
+
- Added proactive OAuth token refresh for MCP server connections with 5-minute expiry buffer
|
|
51
|
+
- Added reactive 401/403 retry with automatic token refresh on HTTP MCP transports
|
|
52
|
+
- Added `refreshMCPOAuthToken()` for standard OAuth 2.0 refresh_token grants
|
|
53
|
+
- Persisted `tokenUrl`, `clientId`, and `clientSecret` in MCP auth config for cross-session token refresh
|
|
54
|
+
### Fixed
|
|
55
|
+
- Respected `PI_CONFIG_DIR` when discovering native user config paths for slash commands and related config directories ([#349](https://github.com/can1357/oh-my-pi/issues/349))
|
|
56
|
+
|
|
5
57
|
## [13.10.0] - 2026-03-10
|
|
6
58
|
### Fixed
|
|
7
59
|
|
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.11.0",
|
|
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.11.0",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "13.11.0",
|
|
46
|
+
"@oh-my-pi/pi-ai": "13.11.0",
|
|
47
|
+
"@oh-my-pi/pi-natives": "13.11.0",
|
|
48
|
+
"@oh-my-pi/pi-tui": "13.11.0",
|
|
49
|
+
"@oh-my-pi/pi-utils": "13.11.0",
|
|
50
50
|
"@sinclair/typebox": "^0.34",
|
|
51
51
|
"@xterm/headless": "^6.0",
|
|
52
52
|
"ajv": "^8.18",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { INTENT_FIELD } from "@oh-my-pi/pi-agent-core";
|
|
1
|
+
import { INTENT_FIELD, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { Markdown } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import chalk from "chalk";
|
|
@@ -20,6 +20,7 @@ export interface CommitAgentInput {
|
|
|
20
20
|
cwd: string;
|
|
21
21
|
git: ControlledGit;
|
|
22
22
|
model: Model<Api>;
|
|
23
|
+
thinkingLevel?: ThinkingLevel;
|
|
23
24
|
settings: Settings;
|
|
24
25
|
modelRegistry: ModelRegistry;
|
|
25
26
|
authStorage: AuthStorage;
|
|
@@ -61,6 +62,7 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
|
|
|
61
62
|
modelRegistry: input.modelRegistry,
|
|
62
63
|
settings: input.settings,
|
|
63
64
|
model: input.model,
|
|
65
|
+
thinkingLevel: input.thinkingLevel,
|
|
64
66
|
systemPrompt,
|
|
65
67
|
customTools: tools,
|
|
66
68
|
enableLsp: false,
|
|
@@ -48,7 +48,12 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
|
|
|
48
48
|
const { model: primaryModel, apiKey: primaryApiKey } = primaryModelResult;
|
|
49
49
|
process.stdout.write(` └─ ${primaryModel.name}\n`);
|
|
50
50
|
|
|
51
|
-
const { model: agentModel } = await resolveSmolModel(
|
|
51
|
+
const { model: agentModel, thinkingLevel: agentThinkingLevel } = await resolveSmolModel(
|
|
52
|
+
settings,
|
|
53
|
+
modelRegistry,
|
|
54
|
+
primaryModel,
|
|
55
|
+
primaryApiKey,
|
|
56
|
+
);
|
|
52
57
|
|
|
53
58
|
if (stagedFiles.length === 0) {
|
|
54
59
|
process.stderr.write("No changes to commit.\n");
|
|
@@ -126,6 +131,7 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
|
|
|
126
131
|
cwd,
|
|
127
132
|
git,
|
|
128
133
|
model: agentModel,
|
|
134
|
+
thinkingLevel: agentThinkingLevel,
|
|
129
135
|
settings,
|
|
130
136
|
modelRegistry,
|
|
131
137
|
authStorage,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
1
2
|
import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
3
4
|
import { Type } from "@sinclair/typebox";
|
|
@@ -5,6 +6,7 @@ import analysisSystemPrompt from "../../commit/prompts/analysis-system.md" with
|
|
|
5
6
|
import analysisUserPrompt from "../../commit/prompts/analysis-user.md" with { type: "text" };
|
|
6
7
|
import type { ChangelogCategory, ConventionalAnalysis } from "../../commit/types";
|
|
7
8
|
import { renderPromptTemplate } from "../../config/prompt-templates";
|
|
9
|
+
import { toReasoningEffort } from "../../thinking";
|
|
8
10
|
import { extractTextContent, extractToolCall, normalizeAnalysis, parseJsonPayload } from "../utils";
|
|
9
11
|
|
|
10
12
|
const ConventionalAnalysisTool = {
|
|
@@ -49,6 +51,7 @@ const ConventionalAnalysisTool = {
|
|
|
49
51
|
export interface ConventionalAnalysisInput {
|
|
50
52
|
model: Model<Api>;
|
|
51
53
|
apiKey: string;
|
|
54
|
+
thinkingLevel?: ThinkingLevel;
|
|
52
55
|
contextFiles?: Array<{ path: string; content: string }>;
|
|
53
56
|
userContext?: string;
|
|
54
57
|
typesDescription?: string;
|
|
@@ -64,6 +67,7 @@ export interface ConventionalAnalysisInput {
|
|
|
64
67
|
export async function generateConventionalAnalysis({
|
|
65
68
|
model,
|
|
66
69
|
apiKey,
|
|
70
|
+
thinkingLevel,
|
|
67
71
|
contextFiles,
|
|
68
72
|
userContext,
|
|
69
73
|
typesDescription,
|
|
@@ -89,7 +93,7 @@ export async function generateConventionalAnalysis({
|
|
|
89
93
|
messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
|
|
90
94
|
tools: [ConventionalAnalysisTool],
|
|
91
95
|
},
|
|
92
|
-
{ apiKey, maxTokens: 2400 },
|
|
96
|
+
{ apiKey, maxTokens: 2400, reasoning: toReasoningEffort(thinkingLevel) },
|
|
93
97
|
);
|
|
94
98
|
|
|
95
99
|
return parseAnalysisFromResponse(response);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
1
2
|
import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
3
4
|
import { Type } from "@sinclair/typebox";
|
|
@@ -5,6 +6,7 @@ import summarySystemPrompt from "../../commit/prompts/summary-system.md" with {
|
|
|
5
6
|
import summaryUserPrompt from "../../commit/prompts/summary-user.md" with { type: "text" };
|
|
6
7
|
import type { CommitSummary } from "../../commit/types";
|
|
7
8
|
import { renderPromptTemplate } from "../../config/prompt-templates";
|
|
9
|
+
import { toReasoningEffort } from "../../thinking";
|
|
8
10
|
import { extractTextContent, extractToolCall } from "../utils";
|
|
9
11
|
|
|
10
12
|
const SummaryTool = {
|
|
@@ -18,6 +20,7 @@ const SummaryTool = {
|
|
|
18
20
|
export interface SummaryInput {
|
|
19
21
|
model: Model<Api>;
|
|
20
22
|
apiKey: string;
|
|
23
|
+
thinkingLevel?: ThinkingLevel;
|
|
21
24
|
commitType: string;
|
|
22
25
|
scope: string | null;
|
|
23
26
|
details: string[];
|
|
@@ -32,6 +35,7 @@ export interface SummaryInput {
|
|
|
32
35
|
export async function generateSummary({
|
|
33
36
|
model,
|
|
34
37
|
apiKey,
|
|
38
|
+
thinkingLevel,
|
|
35
39
|
commitType,
|
|
36
40
|
scope,
|
|
37
41
|
details,
|
|
@@ -53,7 +57,7 @@ export async function generateSummary({
|
|
|
53
57
|
messages: [{ role: "user", content: userPrompt, timestamp: Date.now() }],
|
|
54
58
|
tools: [SummaryTool],
|
|
55
59
|
},
|
|
56
|
-
{ apiKey, maxTokens: 200 },
|
|
60
|
+
{ apiKey, maxTokens: 200, reasoning: toReasoningEffort(thinkingLevel) },
|
|
57
61
|
);
|
|
58
62
|
|
|
59
63
|
return parseSummaryFromResponse(response, commitType, scope);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
1
2
|
import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
3
4
|
import { type TSchema, Type } from "@sinclair/typebox";
|
|
@@ -5,6 +6,7 @@ import changelogSystemPrompt from "../../commit/prompts/changelog-system.md" wit
|
|
|
5
6
|
import changelogUserPrompt from "../../commit/prompts/changelog-user.md" with { type: "text" };
|
|
6
7
|
import { CHANGELOG_CATEGORIES, type ChangelogCategory, type ChangelogGenerationResult } from "../../commit/types";
|
|
7
8
|
import { renderPromptTemplate } from "../../config/prompt-templates";
|
|
9
|
+
import { toReasoningEffort } from "../../thinking";
|
|
8
10
|
import { extractTextContent, extractToolCall, parseJsonPayload } from "../utils";
|
|
9
11
|
|
|
10
12
|
const changelogEntryProperties = CHANGELOG_CATEGORIES.reduce<Record<ChangelogCategory, TSchema>>(
|
|
@@ -28,6 +30,7 @@ export const changelogTool = {
|
|
|
28
30
|
export interface ChangelogPromptInput {
|
|
29
31
|
model: Model<Api>;
|
|
30
32
|
apiKey: string;
|
|
33
|
+
thinkingLevel?: ThinkingLevel;
|
|
31
34
|
changelogPath: string;
|
|
32
35
|
isPackageChangelog: boolean;
|
|
33
36
|
existingEntries?: string;
|
|
@@ -38,6 +41,7 @@ export interface ChangelogPromptInput {
|
|
|
38
41
|
export async function generateChangelogEntries({
|
|
39
42
|
model,
|
|
40
43
|
apiKey,
|
|
44
|
+
thinkingLevel,
|
|
41
45
|
changelogPath,
|
|
42
46
|
isPackageChangelog,
|
|
43
47
|
existingEntries,
|
|
@@ -58,7 +62,7 @@ export async function generateChangelogEntries({
|
|
|
58
62
|
messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
|
|
59
63
|
tools: [changelogTool],
|
|
60
64
|
},
|
|
61
|
-
{ apiKey, maxTokens: 1200 },
|
|
65
|
+
{ apiKey, maxTokens: 1200, reasoning: toReasoningEffort(thinkingLevel) },
|
|
62
66
|
);
|
|
63
67
|
|
|
64
68
|
const parsed = parseChangelogResponse(response);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
3
|
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
3
4
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
4
5
|
import type { ControlledGit } from "../../commit/git";
|
|
@@ -16,6 +17,7 @@ export interface ChangelogFlowInput {
|
|
|
16
17
|
cwd: string;
|
|
17
18
|
model: Model<Api>;
|
|
18
19
|
apiKey: string;
|
|
20
|
+
thinkingLevel?: ThinkingLevel;
|
|
19
21
|
stagedFiles: string[];
|
|
20
22
|
dryRun: boolean;
|
|
21
23
|
maxDiffChars?: number;
|
|
@@ -42,6 +44,7 @@ export async function runChangelogFlow({
|
|
|
42
44
|
cwd,
|
|
43
45
|
model,
|
|
44
46
|
apiKey,
|
|
47
|
+
thinkingLevel,
|
|
45
48
|
stagedFiles,
|
|
46
49
|
dryRun,
|
|
47
50
|
maxDiffChars,
|
|
@@ -72,6 +75,7 @@ export async function runChangelogFlow({
|
|
|
72
75
|
const generated = await generateChangelogEntries({
|
|
73
76
|
model,
|
|
74
77
|
apiKey,
|
|
78
|
+
thinkingLevel,
|
|
75
79
|
changelogPath: boundary.changelogPath,
|
|
76
80
|
isPackageChangelog,
|
|
77
81
|
existingEntries: existingEntries || undefined,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
1
2
|
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
3
4
|
import { parseFileDiffs } from "../../commit/git/diff";
|
|
@@ -21,8 +22,10 @@ export interface MapReduceSettings {
|
|
|
21
22
|
export interface MapReduceInput {
|
|
22
23
|
model: Model<Api>;
|
|
23
24
|
apiKey: string;
|
|
25
|
+
thinkingLevel?: ThinkingLevel;
|
|
24
26
|
smolModel: Model<Api>;
|
|
25
27
|
smolApiKey: string;
|
|
28
|
+
smolThinkingLevel?: ThinkingLevel;
|
|
26
29
|
diff: string;
|
|
27
30
|
stat: string;
|
|
28
31
|
scopeCandidates: string;
|
|
@@ -50,12 +53,14 @@ export async function runMapReduceAnalysis(input: MapReduceInput): Promise<Conve
|
|
|
50
53
|
const observations = await runMapPhase({
|
|
51
54
|
model: input.smolModel,
|
|
52
55
|
apiKey: input.smolApiKey,
|
|
56
|
+
thinkingLevel: input.smolThinkingLevel,
|
|
53
57
|
files: fileDiffs,
|
|
54
58
|
config: input.settings,
|
|
55
59
|
});
|
|
56
60
|
return runReducePhase({
|
|
57
61
|
model: input.model,
|
|
58
62
|
apiKey: input.apiKey,
|
|
63
|
+
thinkingLevel: input.thinkingLevel,
|
|
59
64
|
observations,
|
|
60
65
|
stat: input.stat,
|
|
61
66
|
scopeCandidates: input.scopeCandidates,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
1
2
|
import type { Api, AssistantMessage, Message, Model } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
3
4
|
import fileObserverSystemPrompt from "../../commit/prompts/file-observer-system.md" with { type: "text" };
|
|
@@ -5,6 +6,7 @@ import fileObserverUserPrompt from "../../commit/prompts/file-observer-user.md"
|
|
|
5
6
|
import type { FileDiff, FileObservation } from "../../commit/types";
|
|
6
7
|
import { isExcludedFile } from "../../commit/utils/exclusions";
|
|
7
8
|
import { renderPromptTemplate } from "../../config/prompt-templates";
|
|
9
|
+
import { toReasoningEffort } from "../../thinking";
|
|
8
10
|
import { truncateToTokenLimit } from "./utils";
|
|
9
11
|
|
|
10
12
|
const MAX_FILE_TOKENS = 50_000;
|
|
@@ -17,6 +19,7 @@ const RETRY_BACKOFF_MS = 1000;
|
|
|
17
19
|
export interface MapPhaseInput {
|
|
18
20
|
model: Model<Api>;
|
|
19
21
|
apiKey: string;
|
|
22
|
+
thinkingLevel?: ThinkingLevel;
|
|
20
23
|
files: FileDiff[];
|
|
21
24
|
config?: {
|
|
22
25
|
maxFileTokens?: number;
|
|
@@ -27,7 +30,13 @@ export interface MapPhaseInput {
|
|
|
27
30
|
};
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
export async function runMapPhase({
|
|
33
|
+
export async function runMapPhase({
|
|
34
|
+
model,
|
|
35
|
+
apiKey,
|
|
36
|
+
thinkingLevel,
|
|
37
|
+
files,
|
|
38
|
+
config,
|
|
39
|
+
}: MapPhaseInput): Promise<FileObservation[]> {
|
|
31
40
|
const filtered = files.filter(file => !isExcludedFile(file.filename));
|
|
32
41
|
const systemPrompt = renderPromptTemplate(fileObserverSystemPrompt);
|
|
33
42
|
const maxFileTokens = config?.maxFileTokens ?? MAX_FILE_TOKENS;
|
|
@@ -58,7 +67,13 @@ export async function runMapPhase({ model, apiKey, files, config }: MapPhaseInpu
|
|
|
58
67
|
};
|
|
59
68
|
|
|
60
69
|
const response = await withRetry(
|
|
61
|
-
() =>
|
|
70
|
+
() =>
|
|
71
|
+
completeSimple(model, request, {
|
|
72
|
+
apiKey,
|
|
73
|
+
maxTokens: 400,
|
|
74
|
+
reasoning: toReasoningEffort(thinkingLevel),
|
|
75
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
76
|
+
}),
|
|
62
77
|
maxRetries,
|
|
63
78
|
retryBackoffMs,
|
|
64
79
|
);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
1
2
|
import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
3
4
|
import { Type } from "@sinclair/typebox";
|
|
@@ -5,6 +6,7 @@ import reduceSystemPrompt from "../../commit/prompts/reduce-system.md" with { ty
|
|
|
5
6
|
import reduceUserPrompt from "../../commit/prompts/reduce-user.md" with { type: "text" };
|
|
6
7
|
import type { ChangelogCategory, ConventionalAnalysis, FileObservation } from "../../commit/types";
|
|
7
8
|
import { renderPromptTemplate } from "../../config/prompt-templates";
|
|
9
|
+
import { toReasoningEffort } from "../../thinking";
|
|
8
10
|
import { extractTextContent, extractToolCall, normalizeAnalysis, parseJsonPayload } from "../utils";
|
|
9
11
|
|
|
10
12
|
const ReduceTool = {
|
|
@@ -49,6 +51,7 @@ const ReduceTool = {
|
|
|
49
51
|
export interface ReducePhaseInput {
|
|
50
52
|
model: Model<Api>;
|
|
51
53
|
apiKey: string;
|
|
54
|
+
thinkingLevel?: ThinkingLevel;
|
|
52
55
|
observations: FileObservation[];
|
|
53
56
|
stat: string;
|
|
54
57
|
scopeCandidates: string;
|
|
@@ -58,6 +61,7 @@ export interface ReducePhaseInput {
|
|
|
58
61
|
export async function runReducePhase({
|
|
59
62
|
model,
|
|
60
63
|
apiKey,
|
|
64
|
+
thinkingLevel,
|
|
61
65
|
observations,
|
|
62
66
|
stat,
|
|
63
67
|
scopeCandidates,
|
|
@@ -76,7 +80,7 @@ export async function runReducePhase({
|
|
|
76
80
|
messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
|
|
77
81
|
tools: [ReduceTool],
|
|
78
82
|
},
|
|
79
|
-
{ apiKey, maxTokens: 2400 },
|
|
83
|
+
{ apiKey, maxTokens: 2400, reasoning: toReasoningEffort(thinkingLevel) },
|
|
80
84
|
);
|
|
81
85
|
|
|
82
86
|
return parseAnalysisResponse(response);
|
|
@@ -1,14 +1,34 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
1
2
|
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import { MODEL_ROLE_IDS } from "../config/model-registry";
|
|
3
|
-
import {
|
|
4
|
-
expandRoleAlias,
|
|
5
|
-
parseModelPattern,
|
|
6
|
-
resolveModelFromSettings,
|
|
7
|
-
resolveModelFromString,
|
|
8
|
-
} from "../config/model-resolver";
|
|
4
|
+
import { parseModelPattern, resolveModelRoleValue } from "../config/model-resolver";
|
|
9
5
|
import type { Settings } from "../config/settings";
|
|
10
6
|
import MODEL_PRIO from "../priority.json" with { type: "json" };
|
|
11
7
|
|
|
8
|
+
export interface ResolvedCommitModel {
|
|
9
|
+
model: Model<Api>;
|
|
10
|
+
apiKey: string;
|
|
11
|
+
thinkingLevel?: ThinkingLevel;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function resolveRoleSelection(
|
|
15
|
+
roles: readonly string[],
|
|
16
|
+
settings: Settings,
|
|
17
|
+
availableModels: Model<Api>[],
|
|
18
|
+
): { model: Model<Api>; thinkingLevel?: ThinkingLevel } | undefined {
|
|
19
|
+
const matchPreferences = { usageOrder: settings.getStorage()?.getModelUsageOrder() };
|
|
20
|
+
for (const role of roles) {
|
|
21
|
+
const resolved = resolveModelRoleValue(settings.getModelRole(role), availableModels, {
|
|
22
|
+
settings,
|
|
23
|
+
matchPreferences,
|
|
24
|
+
});
|
|
25
|
+
if (resolved.model) {
|
|
26
|
+
return { model: resolved.model, thinkingLevel: resolved.thinkingLevel };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
12
32
|
export async function resolvePrimaryModel(
|
|
13
33
|
override: string | undefined,
|
|
14
34
|
settings: Settings,
|
|
@@ -16,18 +36,13 @@ export async function resolvePrimaryModel(
|
|
|
16
36
|
getAvailable: () => Model<Api>[];
|
|
17
37
|
getApiKey: (model: Model<Api>) => Promise<string | undefined>;
|
|
18
38
|
},
|
|
19
|
-
): Promise<
|
|
39
|
+
): Promise<ResolvedCommitModel> {
|
|
20
40
|
const available = modelRegistry.getAvailable();
|
|
21
41
|
const matchPreferences = { usageOrder: settings.getStorage()?.getModelUsageOrder() };
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
settings,
|
|
27
|
-
availableModels: available,
|
|
28
|
-
matchPreferences,
|
|
29
|
-
roleOrder,
|
|
30
|
-
});
|
|
42
|
+
const resolved = override
|
|
43
|
+
? resolveModelRoleValue(override, available, { settings, matchPreferences })
|
|
44
|
+
: resolveRoleSelection(["commit", "smol", ...MODEL_ROLE_IDS], settings, available);
|
|
45
|
+
const model = resolved?.model;
|
|
31
46
|
if (!model) {
|
|
32
47
|
throw new Error("No model available for commit generation");
|
|
33
48
|
}
|
|
@@ -35,7 +50,7 @@ export async function resolvePrimaryModel(
|
|
|
35
50
|
if (!apiKey) {
|
|
36
51
|
throw new Error(`No API key available for model ${model.provider}/${model.id}`);
|
|
37
52
|
}
|
|
38
|
-
return { model, apiKey };
|
|
53
|
+
return { model, apiKey, thinkingLevel: resolved?.thinkingLevel };
|
|
39
54
|
}
|
|
40
55
|
|
|
41
56
|
export async function resolveSmolModel(
|
|
@@ -46,18 +61,15 @@ export async function resolveSmolModel(
|
|
|
46
61
|
},
|
|
47
62
|
fallbackModel: Model<Api>,
|
|
48
63
|
fallbackApiKey: string,
|
|
49
|
-
): Promise<
|
|
64
|
+
): Promise<ResolvedCommitModel> {
|
|
50
65
|
const available = modelRegistry.getAvailable();
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
: undefined;
|
|
56
|
-
if (roleModel) {
|
|
57
|
-
const apiKey = await modelRegistry.getApiKey(roleModel);
|
|
58
|
-
if (apiKey) return { model: roleModel, apiKey };
|
|
66
|
+
const resolvedSmol = resolveRoleSelection(["smol"], settings, available);
|
|
67
|
+
if (resolvedSmol?.model) {
|
|
68
|
+
const apiKey = await modelRegistry.getApiKey(resolvedSmol.model);
|
|
69
|
+
if (apiKey) return { model: resolvedSmol.model, apiKey, thinkingLevel: resolvedSmol.thinkingLevel };
|
|
59
70
|
}
|
|
60
71
|
|
|
72
|
+
const matchPreferences = { usageOrder: settings.getStorage()?.getModelUsageOrder() };
|
|
61
73
|
for (const pattern of MODEL_PRIO.smol) {
|
|
62
74
|
const candidate = parseModelPattern(pattern, available, matchPreferences).model;
|
|
63
75
|
if (!candidate) continue;
|
package/src/commit/pipeline.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
3
|
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
3
4
|
import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
|
4
5
|
import { ModelRegistry } from "../config/model-registry";
|
|
@@ -45,17 +46,16 @@ async function runLegacyCommitCommand(args: CommitCommandArgs): Promise<void> {
|
|
|
45
46
|
const modelRegistry = new ModelRegistry(authStorage);
|
|
46
47
|
await modelRegistry.refresh();
|
|
47
48
|
|
|
48
|
-
const {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
);
|
|
53
|
-
const {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
);
|
|
49
|
+
const {
|
|
50
|
+
model: primaryModel,
|
|
51
|
+
apiKey: primaryApiKey,
|
|
52
|
+
thinkingLevel: primaryThinkingLevel,
|
|
53
|
+
} = await resolvePrimaryModel(args.model, settings, modelRegistry);
|
|
54
|
+
const {
|
|
55
|
+
model: smolModel,
|
|
56
|
+
apiKey: smolApiKey,
|
|
57
|
+
thinkingLevel: smolThinkingLevel,
|
|
58
|
+
} = await resolveSmolModel(settings, modelRegistry, primaryModel, primaryApiKey);
|
|
59
59
|
|
|
60
60
|
const git = new ControlledGit(cwd);
|
|
61
61
|
let stagedFiles = await git.getStagedFiles();
|
|
@@ -75,6 +75,7 @@ async function runLegacyCommitCommand(args: CommitCommandArgs): Promise<void> {
|
|
|
75
75
|
cwd,
|
|
76
76
|
model: primaryModel,
|
|
77
77
|
apiKey: primaryApiKey,
|
|
78
|
+
thinkingLevel: primaryThinkingLevel,
|
|
78
79
|
stagedFiles,
|
|
79
80
|
dryRun: args.dryRun,
|
|
80
81
|
maxDiffChars: commitSettings.changelogMaxDiffChars,
|
|
@@ -101,8 +102,10 @@ async function runLegacyCommitCommand(args: CommitCommandArgs): Promise<void> {
|
|
|
101
102
|
userContext: args.context,
|
|
102
103
|
primaryModel,
|
|
103
104
|
primaryApiKey,
|
|
105
|
+
primaryThinkingLevel,
|
|
104
106
|
smolModel,
|
|
105
107
|
smolApiKey,
|
|
108
|
+
smolThinkingLevel,
|
|
106
109
|
commitSettings,
|
|
107
110
|
});
|
|
108
111
|
|
|
@@ -116,6 +119,7 @@ async function runLegacyCommitCommand(args: CommitCommandArgs): Promise<void> {
|
|
|
116
119
|
stat,
|
|
117
120
|
model: primaryModel,
|
|
118
121
|
apiKey: primaryApiKey,
|
|
122
|
+
thinkingLevel: primaryThinkingLevel,
|
|
119
123
|
userContext: args.context,
|
|
120
124
|
});
|
|
121
125
|
|
|
@@ -144,8 +148,10 @@ async function generateAnalysis(input: {
|
|
|
144
148
|
userContext?: string;
|
|
145
149
|
primaryModel: Model<Api>;
|
|
146
150
|
primaryApiKey: string;
|
|
151
|
+
primaryThinkingLevel?: ThinkingLevel;
|
|
147
152
|
smolModel: Model<Api>;
|
|
148
153
|
smolApiKey: string;
|
|
154
|
+
smolThinkingLevel?: ThinkingLevel;
|
|
149
155
|
commitSettings: {
|
|
150
156
|
mapReduceEnabled: boolean;
|
|
151
157
|
mapReduceMinFiles: number;
|
|
@@ -166,8 +172,10 @@ async function generateAnalysis(input: {
|
|
|
166
172
|
return runMapReduceAnalysis({
|
|
167
173
|
model: input.primaryModel,
|
|
168
174
|
apiKey: input.primaryApiKey,
|
|
175
|
+
thinkingLevel: input.primaryThinkingLevel,
|
|
169
176
|
smolModel: input.smolModel,
|
|
170
177
|
smolApiKey: input.smolApiKey,
|
|
178
|
+
smolThinkingLevel: input.smolThinkingLevel,
|
|
171
179
|
diff: input.diff,
|
|
172
180
|
stat: input.stat,
|
|
173
181
|
scopeCandidates: input.scopeCandidates,
|
|
@@ -185,6 +193,7 @@ async function generateAnalysis(input: {
|
|
|
185
193
|
return generateConventionalAnalysis({
|
|
186
194
|
model: input.primaryModel,
|
|
187
195
|
apiKey: input.primaryApiKey,
|
|
196
|
+
thinkingLevel: input.primaryThinkingLevel,
|
|
188
197
|
contextFiles: input.contextFiles,
|
|
189
198
|
userContext: input.userContext,
|
|
190
199
|
typesDescription: TYPES_DESCRIPTION,
|
|
@@ -200,6 +209,7 @@ async function generateSummaryWithRetry(input: {
|
|
|
200
209
|
stat: string;
|
|
201
210
|
model: Model<Api>;
|
|
202
211
|
apiKey: string;
|
|
212
|
+
thinkingLevel?: ThinkingLevel;
|
|
203
213
|
userContext?: string;
|
|
204
214
|
}): Promise<{ summary: string }> {
|
|
205
215
|
let context = input.userContext;
|
|
@@ -207,6 +217,7 @@ async function generateSummaryWithRetry(input: {
|
|
|
207
217
|
const result = await generateSummary({
|
|
208
218
|
model: input.model,
|
|
209
219
|
apiKey: input.apiKey,
|
|
220
|
+
thinkingLevel: input.thinkingLevel,
|
|
210
221
|
commitType: input.analysis.type,
|
|
211
222
|
scope: input.analysis.scope,
|
|
212
223
|
details: input.analysis.details.map(detail => detail.text),
|
|
@@ -470,6 +470,15 @@ export const SETTINGS_SCHEMA = {
|
|
|
470
470
|
submenu: true,
|
|
471
471
|
},
|
|
472
472
|
},
|
|
473
|
+
"todo.eager": {
|
|
474
|
+
type: "boolean",
|
|
475
|
+
default: false,
|
|
476
|
+
ui: {
|
|
477
|
+
tab: "agent",
|
|
478
|
+
label: "Eager todos",
|
|
479
|
+
description: "Automatically create a comprehensive todo list after the first message",
|
|
480
|
+
},
|
|
481
|
+
},
|
|
473
482
|
|
|
474
483
|
// ─────────────────────────────────────────────────────────────────────────
|
|
475
484
|
// Optional tools
|
|
@@ -833,6 +842,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
833
842
|
"tavily",
|
|
834
843
|
"kagi",
|
|
835
844
|
"synthetic",
|
|
845
|
+
"parallel",
|
|
836
846
|
] as const,
|
|
837
847
|
default: "auto",
|
|
838
848
|
ui: { tab: "services", label: "Web search provider", description: "Provider for web search tool", submenu: true },
|
|
@@ -871,6 +881,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
871
881
|
},
|
|
872
882
|
},
|
|
873
883
|
|
|
884
|
+
"providers.parallelFetch": {
|
|
885
|
+
type: "boolean",
|
|
886
|
+
default: true,
|
|
887
|
+
ui: {
|
|
888
|
+
tab: "services",
|
|
889
|
+
label: "Parallel fetch",
|
|
890
|
+
description: "Use Parallel extract API for URL fetching when credentials are available",
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
|
|
874
894
|
// ─────────────────────────────────────────────────────────────────────────
|
|
875
895
|
// Exa settings
|
|
876
896
|
// ─────────────────────────────────────────────────────────────────────────
|
package/src/config.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
CONFIG_DIR_NAME,
|
|
6
|
+
getAgentDir,
|
|
7
|
+
getConfigAgentDirName,
|
|
8
|
+
getProjectDir,
|
|
9
|
+
isEnoent,
|
|
10
|
+
logger,
|
|
11
|
+
} from "@oh-my-pi/pi-utils";
|
|
5
12
|
import type { TSchema } from "@sinclair/typebox";
|
|
6
13
|
import { Value } from "@sinclair/typebox/value";
|
|
7
14
|
import { Ajv, type ErrorObject, type ValidateFunction } from "ajv";
|
|
@@ -9,7 +16,7 @@ import { JSONC, YAML } from "bun";
|
|
|
9
16
|
import { expandTilde } from "./tools/path-utils";
|
|
10
17
|
|
|
11
18
|
const priorityList = [
|
|
12
|
-
{ dir: CONFIG_DIR_NAME, globalAgentDir:
|
|
19
|
+
{ dir: CONFIG_DIR_NAME, globalAgentDir: getConfigAgentDirName },
|
|
13
20
|
{ dir: ".claude" },
|
|
14
21
|
{ dir: ".codex" },
|
|
15
22
|
{ dir: ".gemini" },
|
|
@@ -251,7 +258,7 @@ export class ConfigFile<T> implements IConfigFile<T> {
|
|
|
251
258
|
* Project-level: .omp, .claude, .codex, .gemini
|
|
252
259
|
*/
|
|
253
260
|
const USER_CONFIG_BASES = priorityList.map(({ dir, globalAgentDir }) => ({
|
|
254
|
-
base: () => path.join(os.homedir(), globalAgentDir
|
|
261
|
+
base: () => path.join(os.homedir(), globalAgentDir ? globalAgentDir() : dir),
|
|
255
262
|
name: dir,
|
|
256
263
|
}));
|
|
257
264
|
|