@oh-my-pi/pi-coding-agent 13.15.3 → 13.16.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 +30 -16
- package/package.json +7 -7
- package/src/commit/agentic/tools/analyze-file.ts +1 -0
- package/src/config/model-registry.ts +215 -57
- package/src/config/settings-schema.ts +27 -0
- package/src/extensibility/custom-tools/types.ts +3 -0
- package/src/extensibility/extensions/runner.ts +7 -0
- package/src/extensibility/extensions/types.ts +10 -1
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/ipy/cancellation.ts +28 -0
- package/src/ipy/executor.ts +252 -77
- package/src/ipy/kernel.ts +181 -35
- package/src/ipy/modules.ts +39 -4
- package/src/modes/acp/acp-agent.ts +1 -0
- package/src/modes/components/hook-editor.ts +57 -8
- package/src/modes/components/model-selector.ts +48 -29
- package/src/modes/components/settings-defs.ts +10 -1
- package/src/modes/components/settings-selector.ts +92 -5
- package/src/modes/controllers/extension-ui-controller.ts +35 -4
- package/src/modes/controllers/input-controller.ts +4 -3
- package/src/modes/controllers/selector-controller.ts +2 -2
- package/src/modes/interactive-mode.ts +7 -2
- package/src/modes/print-mode.ts +1 -0
- package/src/modes/prompt-action-autocomplete.ts +5 -3
- package/src/modes/rpc/rpc-mode.ts +79 -30
- package/src/modes/rpc/rpc-types.ts +9 -1
- package/src/modes/theme/theme.ts +70 -0
- package/src/modes/types.ts +6 -1
- package/src/prompts/system/custom-system-prompt.md +5 -0
- package/src/prompts/system/system-prompt.md +6 -0
- package/src/prompts/tools/ask.md +1 -0
- package/src/prompts/tools/grep.md +1 -1
- package/src/prompts/tools/hashline.md +20 -5
- package/src/sdk.ts +26 -2
- package/src/session/agent-session.ts +18 -11
- package/src/system-prompt.ts +63 -2
- package/src/task/executor.ts +4 -0
- package/src/task/index.ts +2 -0
- package/src/tools/ask.ts +109 -61
- package/src/tools/ast-edit.ts +2 -16
- package/src/tools/ast-grep.ts +2 -17
- package/src/tools/browser.ts +35 -17
- package/src/tools/find.ts +1 -0
- package/src/tools/grep.ts +25 -34
- package/src/tools/index.ts +3 -0
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/python.ts +3 -2
- package/src/tools/render-utils.ts +27 -0
- package/src/tui/tree-list.ts +51 -22
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.16.1] - 2026-03-27
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `searchDb` parameter to `PromptActionAutocompleteProvider` constructor for native search database integration in autocomplete workflows
|
|
10
|
+
- Added `searchDb` parameter to enable native search database integration for grep and find operations
|
|
11
|
+
- Exported `SearchDb` type from tools module for type-safe search database usage
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Updated grep tool to accept and utilize `searchDb` parameter for improved search performance
|
|
16
|
+
- Updated find tool to pass `searchDb` parameter to underlying search operations
|
|
17
|
+
- Updated grep tool description to remove ripgrep-specific implementation detail
|
|
18
|
+
|
|
19
|
+
## [13.16.0] - 2026-03-27
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- Implemented root path alias: bare `/` in tool inputs now resolves to the session working directory instead of the filesystem root
|
|
23
|
+
- Added `browser.screenshotDir` setting to configure screenshot save directory with path expansion
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Improved hashline tool documentation with clearer guidance on block boundary handling and closing delimiter duplication prevention
|
|
28
|
+
- Updated screenshot path resolution to use `resolveToCwd` for consistent workspace-relative path handling
|
|
29
|
+
- Updated hook editor hint text to include `ctrl+g external editor` option when using prompt style
|
|
30
|
+
- Refactored question result formatting to consistently include question ID in output
|
|
31
|
+
|
|
5
32
|
## [13.15.3] - 2026-03-26
|
|
6
33
|
|
|
7
34
|
### Added
|
|
@@ -21,6 +48,8 @@
|
|
|
21
48
|
|
|
22
49
|
### Added
|
|
23
50
|
|
|
51
|
+
- Added custom model roles/tags via config YAML
|
|
52
|
+
- Added ability to reorder model role/tag cycling via config YAML
|
|
24
53
|
- Added prompt for tradeoff metrics during autoresearch setup to collect secondary metrics alongside primary metric
|
|
25
54
|
- Added validation of contract path specifications to reject absolute paths and parent directory references
|
|
26
55
|
- Added stricter benchmark command validation in `isAutoresearchShCommand()` to reject chained commands, pipes, and redirects
|
|
@@ -74,22 +103,10 @@
|
|
|
74
103
|
- Added ACP (Agent Client Protocol) mode for headless agent operation via `--mode acp`
|
|
75
104
|
- Added support for Agent Client Protocol SDK integration with session management, MCP server configuration, and streaming communication
|
|
76
105
|
- Added `ensureOnDisk()` method to SessionManager to persist sessions immediately for ACP discovery
|
|
106
|
+
- Added multiline custom input for `ask` custom answers, using the prompt-style editor without inactivity timeout while composing ([#506](https://github.com/can1357/oh-my-pi/issues/506))
|
|
77
107
|
|
|
78
108
|
### Changed
|
|
79
109
|
|
|
80
|
-
- Changed `isAutoresearchShCommand()` to use proper command-line argument parsing instead of regex, improving accuracy for complex shell invocations
|
|
81
|
-
- Changed autoresearch initialization prompt to display collected tradeoff metrics in the setup summary
|
|
82
|
-
- Changed `command-initialize.md` template to include guidance on preflight requirements, comparability invariants, and marking measurement-critical files as off-limits
|
|
83
|
-
- Changed `command-initialize.md` to instruct users to write or update `autoresearch.program.md` with durable heuristics and repo-specific strategy
|
|
84
|
-
- Changed autoresearch resume guidance to emphasize continuing on the current protected branch rather than switching branches
|
|
85
|
-
- Changed autoresearch prompt to clarify that `autoresearch.md` holds durable conclusions while `autoresearch.ideas.md` is the scratch backlog
|
|
86
|
-
- Changed autoresearch prompt guidance to require stable measurement harness and fixed benchmark inputs unless intentionally starting a new segment
|
|
87
|
-
- Changed autoresearch prompt to recommend keeping equal or near-equal results when they materially simplify implementation
|
|
88
|
-
- Changed `init_experiment` to reset pending run state (checks, duration, ASI, artifact directory) when initializing a new segment
|
|
89
|
-
- Changed `log_experiment` to set `autoResumeArmed` flag after successfully logging a run to enable auto-resume on next agent turn
|
|
90
|
-
- Changed `run_experiment` to set `autoResumeArmed` flag and update dashboard after completing a run
|
|
91
|
-
- Changed auto-resume logic to only prompt when a new pending run exists or when `autoResumeArmed` is explicitly set, preventing duplicate prompts
|
|
92
|
-
- Changed path normalization in contract validation to use `path.posix.normalize()` for consistent path handling
|
|
93
110
|
- Changed autoresearch initialization to collect and validate benchmark command, metric definition, scope paths, off-limits list, and constraints before `init_experiment`
|
|
94
111
|
- Changed `init_experiment` to require exact benchmark command, metric definition, scope, off-limits, and constraints matching collected contract
|
|
95
112
|
- Changed `log_experiment` to record run number, benchmark command, scope paths, off-limits list, constraints, and segment fingerprint with each result
|
|
@@ -139,9 +156,6 @@
|
|
|
139
156
|
|
|
140
157
|
### Fixed
|
|
141
158
|
|
|
142
|
-
- Fixed boundary duplication warnings to always display when replacement lines match the next surviving line, even when auto-correction is disabled
|
|
143
|
-
- Fixed secondary metrics validation to properly reject missing configured metrics and new metrics without force flag
|
|
144
|
-
- Fixed ASI data cloning to prevent prototype pollution attacks by filtering reserved property names
|
|
145
159
|
- Fixed autoresearch resume to detect and recover pending run artifacts that were left unlogged from previous sessions
|
|
146
160
|
- Fixed dashboard overlay to display when running experiment even with zero completed results
|
|
147
161
|
- Fixed tab character rendering in dashboard command display and tool output summaries
|
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.16.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",
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
44
44
|
"@mozilla/readability": "^0.6",
|
|
45
|
-
"@oh-my-pi/omp-stats": "13.
|
|
46
|
-
"@oh-my-pi/pi-agent-core": "13.
|
|
47
|
-
"@oh-my-pi/pi-ai": "13.
|
|
48
|
-
"@oh-my-pi/pi-natives": "13.
|
|
49
|
-
"@oh-my-pi/pi-tui": "13.
|
|
50
|
-
"@oh-my-pi/pi-utils": "13.
|
|
45
|
+
"@oh-my-pi/omp-stats": "13.16.1",
|
|
46
|
+
"@oh-my-pi/pi-agent-core": "13.16.1",
|
|
47
|
+
"@oh-my-pi/pi-ai": "13.16.1",
|
|
48
|
+
"@oh-my-pi/pi-natives": "13.16.1",
|
|
49
|
+
"@oh-my-pi/pi-tui": "13.16.1",
|
|
50
|
+
"@oh-my-pi/pi-utils": "13.16.1",
|
|
51
51
|
"@sinclair/typebox": "^0.34",
|
|
52
52
|
"@xterm/headless": "^6.0",
|
|
53
53
|
"ajv": "^8.18",
|
|
@@ -28,8 +28,9 @@ import {
|
|
|
28
28
|
import { isRecord, logger } from "@oh-my-pi/pi-utils";
|
|
29
29
|
import { type Static, Type } from "@sinclair/typebox";
|
|
30
30
|
import { type ConfigError, ConfigFile } from "../config";
|
|
31
|
-
import type
|
|
31
|
+
import { isValidThemeColor, type ThemeColor } from "../modes/theme/theme";
|
|
32
32
|
import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
|
|
33
|
+
import type { Settings } from "./settings";
|
|
33
34
|
|
|
34
35
|
export const kNoAuth = "N/A";
|
|
35
36
|
|
|
@@ -57,6 +58,53 @@ export const MODEL_ROLES: Record<ModelRole, ModelRoleInfo> = {
|
|
|
57
58
|
|
|
58
59
|
export const MODEL_ROLE_IDS: ModelRole[] = ["default", "smol", "slow", "vision", "plan", "commit", "task"];
|
|
59
60
|
|
|
61
|
+
/** Alias for ModelRoleInfo - used for both built-in and custom roles */
|
|
62
|
+
export type RoleInfo = ModelRoleInfo;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Return the canonical set of known roles for selector/carousel UI.
|
|
66
|
+
*
|
|
67
|
+
* Built-ins always come first. Configured cycle order, model assignments, and
|
|
68
|
+
* tag metadata can introduce additional custom roles without requiring duplicate
|
|
69
|
+
* entries across settings.
|
|
70
|
+
*/
|
|
71
|
+
export function getKnownRoleIds(settings: Settings): string[] {
|
|
72
|
+
const roles = [...MODEL_ROLE_IDS] as string[];
|
|
73
|
+
const seen = new Set<string>(roles);
|
|
74
|
+
const addRole = (role: string) => {
|
|
75
|
+
if (seen.has(role)) return;
|
|
76
|
+
seen.add(role);
|
|
77
|
+
roles.push(role);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
for (const role of settings.get("cycleOrder")) addRole(role);
|
|
81
|
+
for (const role of Object.keys(settings.getModelRoles())) addRole(role);
|
|
82
|
+
for (const role of Object.keys(settings.get("modelTags"))) addRole(role);
|
|
83
|
+
|
|
84
|
+
return roles;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get role info for a role name (built-in or custom).
|
|
89
|
+
* Configured metadata overrides built-in defaults when present.
|
|
90
|
+
*/
|
|
91
|
+
export function getRoleInfo(role: string, settings: Settings): RoleInfo {
|
|
92
|
+
const builtIn = role in MODEL_ROLES ? MODEL_ROLES[role as ModelRole] : undefined;
|
|
93
|
+
const configured = settings.get("modelTags")[role];
|
|
94
|
+
|
|
95
|
+
if (configured) {
|
|
96
|
+
return {
|
|
97
|
+
tag: builtIn?.tag,
|
|
98
|
+
name: configured.name || builtIn?.name || role,
|
|
99
|
+
color: configured.color && isValidThemeColor(configured.color) ? configured.color : builtIn?.color,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (builtIn) return builtIn;
|
|
104
|
+
|
|
105
|
+
return { name: role, color: "muted" };
|
|
106
|
+
}
|
|
107
|
+
|
|
60
108
|
const OpenRouterRoutingSchema = Type.Object({
|
|
61
109
|
only: Type.Optional(Type.Array(Type.String())),
|
|
62
110
|
order: Type.Optional(Type.Array(Type.String())),
|
|
@@ -357,7 +405,7 @@ export interface ProviderDiscoveryState {
|
|
|
357
405
|
|
|
358
406
|
/** Result of loading custom models from models.json */
|
|
359
407
|
interface CustomModelsResult {
|
|
360
|
-
models?:
|
|
408
|
+
models?: CustomModelOverlay[];
|
|
361
409
|
overrides?: Map<string, ProviderOverride>;
|
|
362
410
|
modelOverrides?: Map<string, Map<string, ModelOverride>>;
|
|
363
411
|
keylessProviders?: Set<string>;
|
|
@@ -552,6 +600,24 @@ interface CustomModelBuildOptions {
|
|
|
552
600
|
useDefaults: boolean;
|
|
553
601
|
}
|
|
554
602
|
|
|
603
|
+
type CustomModelOverlay = {
|
|
604
|
+
id: string;
|
|
605
|
+
provider: string;
|
|
606
|
+
api: Api;
|
|
607
|
+
baseUrl: string;
|
|
608
|
+
name?: string;
|
|
609
|
+
reasoning?: boolean;
|
|
610
|
+
thinking?: ThinkingConfig;
|
|
611
|
+
input?: ("text" | "image")[];
|
|
612
|
+
cost?: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
613
|
+
contextWindow?: number;
|
|
614
|
+
maxTokens?: number;
|
|
615
|
+
headers?: Record<string, string>;
|
|
616
|
+
compat?: Model<Api>["compat"];
|
|
617
|
+
contextPromotionTarget?: string;
|
|
618
|
+
premiumMultiplier?: number;
|
|
619
|
+
};
|
|
620
|
+
|
|
555
621
|
function mergeCustomModelHeaders(
|
|
556
622
|
providerHeaders: Record<string, string> | undefined,
|
|
557
623
|
modelHeaders: Record<string, string> | undefined,
|
|
@@ -568,7 +634,7 @@ function mergeCustomModelHeaders(
|
|
|
568
634
|
return headers;
|
|
569
635
|
}
|
|
570
636
|
|
|
571
|
-
function
|
|
637
|
+
function buildCustomModelOverlay(
|
|
572
638
|
providerName: string,
|
|
573
639
|
providerBaseUrl: string,
|
|
574
640
|
providerApi: Api | undefined,
|
|
@@ -577,32 +643,84 @@ function buildCustomModel(
|
|
|
577
643
|
authHeader: boolean | undefined,
|
|
578
644
|
providerCompat: Model<Api>["compat"] | undefined,
|
|
579
645
|
modelDef: CustomModelDefinitionLike,
|
|
580
|
-
|
|
581
|
-
): Model<Api> | undefined {
|
|
646
|
+
): CustomModelOverlay | undefined {
|
|
582
647
|
const api = modelDef.api ?? providerApi;
|
|
583
648
|
if (!api) return undefined;
|
|
584
|
-
|
|
585
|
-
const cost = modelDef.cost ?? (withDefaults ? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } : undefined);
|
|
586
|
-
const input = modelDef.input ?? (withDefaults ? ["text"] : undefined);
|
|
587
|
-
return enrichModelThinking({
|
|
649
|
+
return {
|
|
588
650
|
id: modelDef.id,
|
|
589
|
-
name: modelDef.name ?? (withDefaults ? modelDef.id : undefined),
|
|
590
|
-
api,
|
|
591
651
|
provider: providerName,
|
|
652
|
+
api,
|
|
592
653
|
baseUrl: modelDef.baseUrl ?? providerBaseUrl,
|
|
593
|
-
|
|
654
|
+
name: modelDef.name,
|
|
655
|
+
reasoning: modelDef.reasoning,
|
|
594
656
|
thinking: modelDef.thinking as ThinkingConfig | undefined,
|
|
595
|
-
input: input as ("text" | "image")[],
|
|
596
|
-
cost,
|
|
597
|
-
contextWindow: modelDef.contextWindow
|
|
598
|
-
maxTokens: modelDef.maxTokens
|
|
657
|
+
input: modelDef.input as ("text" | "image")[] | undefined,
|
|
658
|
+
cost: modelDef.cost,
|
|
659
|
+
contextWindow: modelDef.contextWindow,
|
|
660
|
+
maxTokens: modelDef.maxTokens,
|
|
599
661
|
headers: mergeCustomModelHeaders(providerHeaders, modelDef.headers, authHeader, providerApiKey),
|
|
600
662
|
compat: mergeCompat(providerCompat, modelDef.compat),
|
|
601
663
|
contextPromotionTarget: modelDef.contextPromotionTarget,
|
|
602
664
|
premiumMultiplier: modelDef.premiumMultiplier,
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function applyStandaloneCustomModelPolicies(model: CustomModelOverlay): CustomModelOverlay {
|
|
669
|
+
if (model.id !== "gpt-5.4" || model.provider === "github-copilot" || model.contextWindow !== undefined) {
|
|
670
|
+
return model;
|
|
671
|
+
}
|
|
672
|
+
return { ...model, contextWindow: 1_000_000 };
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuildOptions): Model<Api> {
|
|
676
|
+
const resolvedModel = options.useDefaults ? applyStandaloneCustomModelPolicies(model) : model;
|
|
677
|
+
const cost =
|
|
678
|
+
resolvedModel.cost ?? (options.useDefaults ? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } : undefined);
|
|
679
|
+
const input = resolvedModel.input ?? (options.useDefaults ? ["text"] : undefined);
|
|
680
|
+
return enrichModelThinking({
|
|
681
|
+
id: resolvedModel.id,
|
|
682
|
+
name: resolvedModel.name ?? (options.useDefaults ? resolvedModel.id : undefined),
|
|
683
|
+
api: resolvedModel.api,
|
|
684
|
+
provider: resolvedModel.provider,
|
|
685
|
+
baseUrl: resolvedModel.baseUrl,
|
|
686
|
+
reasoning: resolvedModel.reasoning ?? (options.useDefaults ? false : undefined),
|
|
687
|
+
thinking: resolvedModel.thinking,
|
|
688
|
+
input: input as ("text" | "image")[],
|
|
689
|
+
cost,
|
|
690
|
+
contextWindow: resolvedModel.contextWindow ?? (options.useDefaults ? 128000 : undefined),
|
|
691
|
+
maxTokens: resolvedModel.maxTokens ?? (options.useDefaults ? 16384 : undefined),
|
|
692
|
+
headers: resolvedModel.headers,
|
|
693
|
+
compat: resolvedModel.compat,
|
|
694
|
+
contextPromotionTarget: resolvedModel.contextPromotionTarget,
|
|
695
|
+
premiumMultiplier: resolvedModel.premiumMultiplier,
|
|
603
696
|
} as Model<Api>);
|
|
604
697
|
}
|
|
605
698
|
|
|
699
|
+
function buildCustomModel(
|
|
700
|
+
providerName: string,
|
|
701
|
+
providerBaseUrl: string,
|
|
702
|
+
providerApi: Api | undefined,
|
|
703
|
+
providerHeaders: Record<string, string> | undefined,
|
|
704
|
+
providerApiKey: string | undefined,
|
|
705
|
+
authHeader: boolean | undefined,
|
|
706
|
+
providerCompat: Model<Api>["compat"] | undefined,
|
|
707
|
+
modelDef: CustomModelDefinitionLike,
|
|
708
|
+
options: CustomModelBuildOptions,
|
|
709
|
+
): Model<Api> | undefined {
|
|
710
|
+
const model = buildCustomModelOverlay(
|
|
711
|
+
providerName,
|
|
712
|
+
providerBaseUrl,
|
|
713
|
+
providerApi,
|
|
714
|
+
providerHeaders,
|
|
715
|
+
providerApiKey,
|
|
716
|
+
authHeader,
|
|
717
|
+
providerCompat,
|
|
718
|
+
modelDef,
|
|
719
|
+
);
|
|
720
|
+
if (!model) return undefined;
|
|
721
|
+
return finalizeCustomModel(model, options);
|
|
722
|
+
}
|
|
723
|
+
|
|
606
724
|
/**
|
|
607
725
|
* Model registry - loads and manages models, resolves API keys via AuthStorage.
|
|
608
726
|
*/
|
|
@@ -611,6 +729,8 @@ export class ModelRegistry {
|
|
|
611
729
|
#customProviderApiKeys: Map<string, string> = new Map();
|
|
612
730
|
#keylessProviders: Set<string> = new Set();
|
|
613
731
|
#discoverableProviders: DiscoveryProviderConfig[] = [];
|
|
732
|
+
#customModelOverlays: CustomModelOverlay[] = [];
|
|
733
|
+
#providerOverrides: Map<string, ProviderOverride> = new Map();
|
|
614
734
|
#modelOverrides: Map<string, Map<string, ModelOverride>> = new Map();
|
|
615
735
|
#configError: ConfigError | undefined = undefined;
|
|
616
736
|
#modelsConfigFile: ConfigFile<ModelsConfig>;
|
|
@@ -677,6 +797,7 @@ export class ModelRegistry {
|
|
|
677
797
|
this.#customProviderApiKeys.clear();
|
|
678
798
|
this.#keylessProviders.clear();
|
|
679
799
|
this.#discoverableProviders = [];
|
|
800
|
+
this.#providerOverrides.clear();
|
|
680
801
|
this.#modelOverrides.clear();
|
|
681
802
|
this.#configError = undefined;
|
|
682
803
|
this.#providerDiscoveryStates.clear();
|
|
@@ -704,54 +825,83 @@ export class ModelRegistry {
|
|
|
704
825
|
this.#configError = configError;
|
|
705
826
|
this.#keylessProviders = keylessProviders;
|
|
706
827
|
this.#discoverableProviders = discoverableProviders;
|
|
828
|
+
this.#customModelOverlays = customModels;
|
|
829
|
+
this.#providerOverrides = overrides;
|
|
707
830
|
this.#modelOverrides = modelOverrides;
|
|
708
831
|
|
|
709
832
|
this.#addImplicitDiscoverableProviders(configuredProviders);
|
|
710
|
-
const builtInModels = this.#loadBuiltInModels(overrides
|
|
711
|
-
const cachedDiscoveries = this.#loadCachedDiscoverableModels();
|
|
712
|
-
const
|
|
833
|
+
const builtInModels = this.#applyHardcodedModelPolicies(this.#loadBuiltInModels(overrides));
|
|
834
|
+
const cachedDiscoveries = this.#applyHardcodedModelPolicies(this.#loadCachedDiscoverableModels());
|
|
835
|
+
const resolvedDefaults = this.#mergeResolvedModels(builtInModels, cachedDiscoveries);
|
|
836
|
+
const combined = this.#mergeCustomModels(resolvedDefaults, this.#customModelOverlays);
|
|
713
837
|
|
|
714
|
-
this.#models = this.#
|
|
838
|
+
this.#models = this.#applyModelOverrides(combined, this.#modelOverrides);
|
|
715
839
|
}
|
|
716
840
|
|
|
717
|
-
/** Load built-in models, applying provider
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
modelOverrides: Map<string, Map<string, ModelOverride>>,
|
|
721
|
-
): Model<Api>[] {
|
|
841
|
+
/** Load built-in models, applying provider-level overrides only.
|
|
842
|
+
* Per-model overrides are applied later by #applyModelOverrides. */
|
|
843
|
+
#loadBuiltInModels(overrides: Map<string, ProviderOverride>): Model<Api>[] {
|
|
722
844
|
return getBundledProviders().flatMap(provider => {
|
|
723
845
|
const models = getBundledModels(provider as Parameters<typeof getBundledModels>[0]) as Model<Api>[];
|
|
724
846
|
const providerOverride = overrides.get(provider);
|
|
725
|
-
const perModelOverrides = modelOverrides.get(provider);
|
|
726
847
|
|
|
727
848
|
return models.map(m => {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
};
|
|
736
|
-
}
|
|
737
|
-
const modelOverride = perModelOverrides?.get(m.id);
|
|
738
|
-
if (modelOverride) {
|
|
739
|
-
model = applyModelOverride(model, modelOverride);
|
|
740
|
-
}
|
|
741
|
-
return model;
|
|
849
|
+
if (!providerOverride) return m;
|
|
850
|
+
return {
|
|
851
|
+
...m,
|
|
852
|
+
baseUrl: providerOverride.baseUrl ?? m.baseUrl,
|
|
853
|
+
headers: providerOverride.headers ? { ...m.headers, ...providerOverride.headers } : m.headers,
|
|
854
|
+
compat: mergeCompat(m.compat, providerOverride.compat),
|
|
855
|
+
};
|
|
742
856
|
});
|
|
743
857
|
});
|
|
744
858
|
}
|
|
745
859
|
|
|
860
|
+
#mergeResolvedModels(baseModels: Model<Api>[], replacementModels: Model<Api>[]): Model<Api>[] {
|
|
861
|
+
const merged = [...baseModels];
|
|
862
|
+
for (const replacementModel of replacementModels) {
|
|
863
|
+
const existingIndex = merged.findIndex(
|
|
864
|
+
m => m.provider === replacementModel.provider && m.id === replacementModel.id,
|
|
865
|
+
);
|
|
866
|
+
if (existingIndex >= 0) {
|
|
867
|
+
merged[existingIndex] = replacementModel;
|
|
868
|
+
} else {
|
|
869
|
+
merged.push(replacementModel);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
return merged;
|
|
873
|
+
}
|
|
874
|
+
|
|
746
875
|
/** Merge custom models with built-in, replacing by provider+id match */
|
|
747
|
-
#mergeCustomModels(builtInModels: Model<Api>[], customModels:
|
|
876
|
+
#mergeCustomModels(builtInModels: Model<Api>[], customModels: CustomModelOverlay[]): Model<Api>[] {
|
|
748
877
|
const merged = [...builtInModels];
|
|
749
878
|
for (const customModel of customModels) {
|
|
750
879
|
const existingIndex = merged.findIndex(m => m.provider === customModel.provider && m.id === customModel.id);
|
|
751
880
|
if (existingIndex >= 0) {
|
|
752
|
-
merged[existingIndex]
|
|
881
|
+
const existingModel = merged[existingIndex];
|
|
882
|
+
merged[existingIndex] = enrichModelThinking({
|
|
883
|
+
...existingModel,
|
|
884
|
+
id: customModel.id,
|
|
885
|
+
provider: customModel.provider,
|
|
886
|
+
api: customModel.api,
|
|
887
|
+
baseUrl: customModel.baseUrl,
|
|
888
|
+
name: customModel.name ?? existingModel.name,
|
|
889
|
+
reasoning: customModel.reasoning ?? existingModel.reasoning,
|
|
890
|
+
thinking: customModel.thinking ?? existingModel.thinking,
|
|
891
|
+
input: customModel.input ?? existingModel.input,
|
|
892
|
+
cost: customModel.cost ?? existingModel.cost,
|
|
893
|
+
contextWindow: customModel.contextWindow ?? existingModel.contextWindow,
|
|
894
|
+
maxTokens: customModel.maxTokens ?? existingModel.maxTokens,
|
|
895
|
+
// Same-id custom definitions replace bundled transport behavior. Provider-level
|
|
896
|
+
// headers/compat were already folded into customModel during parsing; do not
|
|
897
|
+
// re-merge bundled transport metadata here.
|
|
898
|
+
headers: customModel.headers,
|
|
899
|
+
compat: customModel.compat,
|
|
900
|
+
contextPromotionTarget: customModel.contextPromotionTarget ?? existingModel.contextPromotionTarget,
|
|
901
|
+
premiumMultiplier: customModel.premiumMultiplier ?? existingModel.premiumMultiplier,
|
|
902
|
+
} as Model<Api>);
|
|
753
903
|
} else {
|
|
754
|
-
merged.push(customModel);
|
|
904
|
+
merged.push(finalizeCustomModel(customModel, { useDefaults: true }));
|
|
755
905
|
}
|
|
756
906
|
}
|
|
757
907
|
return merged;
|
|
@@ -936,22 +1086,31 @@ export class ModelRegistry {
|
|
|
936
1086
|
if (discovered.length === 0) {
|
|
937
1087
|
return;
|
|
938
1088
|
}
|
|
939
|
-
const
|
|
940
|
-
this.#models,
|
|
1089
|
+
const discoveredModels = this.#applyHardcodedModelPolicies(
|
|
941
1090
|
discovered.map(model => {
|
|
942
|
-
const existing =
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1091
|
+
const existing = this.find(model.provider, model.id);
|
|
1092
|
+
if (existing) {
|
|
1093
|
+
return {
|
|
1094
|
+
...model,
|
|
1095
|
+
baseUrl: existing.baseUrl,
|
|
1096
|
+
headers: existing.headers ? { ...existing.headers, ...model.headers } : model.headers,
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
const providerOverride = this.#providerOverrides.get(model.provider);
|
|
1100
|
+
return providerOverride
|
|
946
1101
|
? {
|
|
947
1102
|
...model,
|
|
948
|
-
baseUrl:
|
|
949
|
-
headers:
|
|
1103
|
+
baseUrl: providerOverride.baseUrl ?? model.baseUrl,
|
|
1104
|
+
headers: providerOverride.headers
|
|
1105
|
+
? { ...model.headers, ...providerOverride.headers }
|
|
1106
|
+
: model.headers,
|
|
950
1107
|
}
|
|
951
1108
|
: model;
|
|
952
1109
|
}),
|
|
953
1110
|
);
|
|
954
|
-
|
|
1111
|
+
const resolved = this.#mergeResolvedModels(this.#models, discoveredModels);
|
|
1112
|
+
const combined = this.#mergeCustomModels(resolved, this.#customModelOverlays);
|
|
1113
|
+
this.#models = this.#applyModelOverrides(combined, this.#modelOverrides);
|
|
955
1114
|
}
|
|
956
1115
|
|
|
957
1116
|
async #discoverProviderModels(
|
|
@@ -1455,8 +1614,8 @@ export class ModelRegistry {
|
|
|
1455
1614
|
});
|
|
1456
1615
|
}
|
|
1457
1616
|
|
|
1458
|
-
#parseModels(config: ModelsConfig):
|
|
1459
|
-
const models:
|
|
1617
|
+
#parseModels(config: ModelsConfig): CustomModelOverlay[] {
|
|
1618
|
+
const models: CustomModelOverlay[] = [];
|
|
1460
1619
|
|
|
1461
1620
|
for (const [providerName, providerConfig] of Object.entries(config.providers)) {
|
|
1462
1621
|
const modelDefs = providerConfig.models ?? [];
|
|
@@ -1465,7 +1624,7 @@ export class ModelRegistry {
|
|
|
1465
1624
|
this.#customProviderApiKeys.set(providerName, providerConfig.apiKey);
|
|
1466
1625
|
}
|
|
1467
1626
|
for (const modelDef of modelDefs) {
|
|
1468
|
-
const model =
|
|
1627
|
+
const model = buildCustomModelOverlay(
|
|
1469
1628
|
providerName,
|
|
1470
1629
|
providerConfig.baseUrl!,
|
|
1471
1630
|
providerConfig.api as Api | undefined,
|
|
@@ -1474,7 +1633,6 @@ export class ModelRegistry {
|
|
|
1474
1633
|
providerConfig.authHeader,
|
|
1475
1634
|
providerConfig.compat,
|
|
1476
1635
|
modelDef as CustomModelDefinitionLike,
|
|
1477
|
-
{ useDefaults: true },
|
|
1478
1636
|
);
|
|
1479
1637
|
if (!model) continue;
|
|
1480
1638
|
models.push(model);
|
|
@@ -1636,7 +1794,7 @@ export class ModelRegistry {
|
|
|
1636
1794
|
config.authHeader,
|
|
1637
1795
|
config.compat,
|
|
1638
1796
|
modelDef as CustomModelDefinitionLike,
|
|
1639
|
-
{ useDefaults:
|
|
1797
|
+
{ useDefaults: true },
|
|
1640
1798
|
);
|
|
1641
1799
|
if (!model) {
|
|
1642
1800
|
throw new Error(`Provider ${providerName}, model ${modelDef.id}: no "api" specified.`);
|
|
@@ -135,10 +135,21 @@ type SettingDef =
|
|
|
135
135
|
// Schema Definition
|
|
136
136
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
137
137
|
|
|
138
|
+
export interface ModelTagDef {
|
|
139
|
+
name: string;
|
|
140
|
+
color?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface ModelTagsSettings {
|
|
144
|
+
[key: string]: ModelTagDef;
|
|
145
|
+
}
|
|
146
|
+
|
|
138
147
|
// Typed defaults for array/record settings — named constants avoid `as` casts
|
|
139
148
|
// under `as const` while still letting SettingValue infer the correct element type.
|
|
140
149
|
const EMPTY_STRING_ARRAY: string[] = [];
|
|
141
150
|
const EMPTY_STRING_RECORD: Record<string, string> = {};
|
|
151
|
+
const DEFAULT_CYCLE_ORDER: string[] = ["smol", "default", "slow"];
|
|
152
|
+
const EMPTY_MODEL_TAGS_RECORD: ModelTagsSettings = {};
|
|
142
153
|
export const DEFAULT_BASH_INTERCEPTOR_RULES: BashInterceptorRule[] = [
|
|
143
154
|
{
|
|
144
155
|
pattern: "^\\s*(cat|head|tail|less|more)\\s+",
|
|
@@ -195,6 +206,10 @@ export const SETTINGS_SCHEMA = {
|
|
|
195
206
|
|
|
196
207
|
modelRoles: { type: "record", default: EMPTY_STRING_RECORD },
|
|
197
208
|
|
|
209
|
+
modelTags: { type: "record", default: EMPTY_MODEL_TAGS_RECORD },
|
|
210
|
+
|
|
211
|
+
cycleOrder: { type: "array", default: DEFAULT_CYCLE_ORDER },
|
|
212
|
+
|
|
198
213
|
// ────────────────────────────────────────────────────────────────────────
|
|
199
214
|
// Appearance
|
|
200
215
|
// ────────────────────────────────────────────────────────────────────────
|
|
@@ -1183,6 +1198,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
1183
1198
|
description: "Launch browser in headless mode (disable to show browser UI)",
|
|
1184
1199
|
},
|
|
1185
1200
|
},
|
|
1201
|
+
"browser.screenshotDir": {
|
|
1202
|
+
type: "string",
|
|
1203
|
+
default: undefined,
|
|
1204
|
+
ui: {
|
|
1205
|
+
tab: "tools",
|
|
1206
|
+
label: "Screenshot directory",
|
|
1207
|
+
description:
|
|
1208
|
+
"Directory to save screenshots. If unset, screenshots go to a temp file. Supports ~. Examples: ~/Downloads, ~/Desktop, /sdcard/Download (Android)",
|
|
1209
|
+
},
|
|
1210
|
+
},
|
|
1186
1211
|
|
|
1187
1212
|
// Tool execution
|
|
1188
1213
|
"tools.intentTracing": {
|
|
@@ -1767,6 +1792,8 @@ export interface GroupTypeMap {
|
|
|
1767
1792
|
thinkingBudgets: ThinkingBudgetsSettings;
|
|
1768
1793
|
stt: SttSettings;
|
|
1769
1794
|
modelRoles: Record<string, string>;
|
|
1795
|
+
modelTags: ModelTagsSettings;
|
|
1796
|
+
cycleOrder: string[];
|
|
1770
1797
|
}
|
|
1771
1798
|
|
|
1772
1799
|
export type GroupPrefix = keyof GroupTypeMap;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
8
8
|
import type { Model } from "@oh-my-pi/pi-ai";
|
|
9
|
+
import type { SearchDb } from "@oh-my-pi/pi-natives";
|
|
9
10
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
10
11
|
import type { Static, TSchema } from "@sinclair/typebox";
|
|
11
12
|
import type { Rule } from "../../capability/rule";
|
|
@@ -71,6 +72,8 @@ export interface CustomToolContext {
|
|
|
71
72
|
modelRegistry: ModelRegistry;
|
|
72
73
|
/** Current model (may be undefined if no model is selected yet) */
|
|
73
74
|
model: Model | undefined;
|
|
75
|
+
/** Shared native search DB for grep/glob/fuzzyFind-backed workflows. */
|
|
76
|
+
searchDb?: SearchDb;
|
|
74
77
|
/** Whether the agent is idle (not streaming) */
|
|
75
78
|
isIdle(): boolean;
|
|
76
79
|
/** Whether there are queued messages waiting to be processed */
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
5
5
|
import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
|
|
6
|
+
import type { SearchDb } from "@oh-my-pi/pi-natives";
|
|
6
7
|
import type { KeyId } from "@oh-my-pi/pi-tui";
|
|
7
8
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
8
9
|
import type { ModelRegistry } from "../../config/model-registry";
|
|
@@ -160,6 +161,7 @@ export class ExtensionRunner {
|
|
|
160
161
|
#uiContext: ExtensionUIContext;
|
|
161
162
|
#errorListeners: Set<ExtensionErrorListener> = new Set();
|
|
162
163
|
#getModel: () => Model | undefined = () => undefined;
|
|
164
|
+
#getSearchDbFn: () => SearchDb | undefined = () => undefined;
|
|
163
165
|
#isIdleFn: () => boolean = () => true;
|
|
164
166
|
#waitForIdleFn: () => Promise<void> = async () => {};
|
|
165
167
|
#abortFn: () => void = () => {};
|
|
@@ -205,6 +207,7 @@ export class ExtensionRunner {
|
|
|
205
207
|
|
|
206
208
|
// Context actions (required)
|
|
207
209
|
this.#getModel = contextActions.getModel;
|
|
210
|
+
this.#getSearchDbFn = contextActions.getSearchDb ?? (() => undefined);
|
|
208
211
|
this.#isIdleFn = contextActions.isIdle;
|
|
209
212
|
this.#abortFn = contextActions.abort;
|
|
210
213
|
this.#hasPendingMessagesFn = contextActions.hasPendingMessages;
|
|
@@ -376,6 +379,7 @@ export class ExtensionRunner {
|
|
|
376
379
|
|
|
377
380
|
createContext(): ExtensionContext {
|
|
378
381
|
const getModel = this.#getModel;
|
|
382
|
+
const getSearchDb = this.#getSearchDbFn;
|
|
379
383
|
return {
|
|
380
384
|
ui: this.#uiContext,
|
|
381
385
|
getContextUsage: () => this.#getContextUsageFn(),
|
|
@@ -387,6 +391,9 @@ export class ExtensionRunner {
|
|
|
387
391
|
get model() {
|
|
388
392
|
return getModel();
|
|
389
393
|
},
|
|
394
|
+
get searchDb() {
|
|
395
|
+
return getSearchDb();
|
|
396
|
+
},
|
|
390
397
|
isIdle: () => this.#isIdleFn(),
|
|
391
398
|
abort: () => this.#abortFn(),
|
|
392
399
|
hasPendingMessages: () => this.#hasPendingMessagesFn(),
|
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
ToolResultMessage,
|
|
23
23
|
} from "@oh-my-pi/pi-ai";
|
|
24
24
|
import type * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
|
|
25
|
+
import type { SearchDb } from "@oh-my-pi/pi-natives";
|
|
25
26
|
import type { AutocompleteItem, Component, EditorComponent, EditorTheme, KeyId, TUI } from "@oh-my-pi/pi-tui";
|
|
26
27
|
import type { Static, TSchema } from "@sinclair/typebox";
|
|
27
28
|
import type { Rule } from "../../capability/rule";
|
|
@@ -161,7 +162,12 @@ export interface ExtensionUIContext {
|
|
|
161
162
|
getEditorText(): string;
|
|
162
163
|
|
|
163
164
|
/** Show a multi-line editor for text editing. */
|
|
164
|
-
editor(
|
|
165
|
+
editor(
|
|
166
|
+
title: string,
|
|
167
|
+
prefill?: string,
|
|
168
|
+
dialogOptions?: ExtensionUIDialogOptions,
|
|
169
|
+
editorOptions?: { promptStyle?: boolean },
|
|
170
|
+
): Promise<string | undefined>;
|
|
165
171
|
|
|
166
172
|
/** Set a custom editor component via factory function, or undefined to restore the default editor. */
|
|
167
173
|
setEditorComponent(
|
|
@@ -224,6 +230,8 @@ export interface ExtensionContext {
|
|
|
224
230
|
modelRegistry: ModelRegistry;
|
|
225
231
|
/** Current model (may be undefined) */
|
|
226
232
|
model: Model | undefined;
|
|
233
|
+
/** Shared native search DB for grep/glob/fuzzyFind-backed workflows. */
|
|
234
|
+
searchDb?: SearchDb;
|
|
227
235
|
/** Whether the agent is idle (not streaming) */
|
|
228
236
|
isIdle(): boolean;
|
|
229
237
|
/** Abort the current agent operation */
|
|
@@ -1290,6 +1298,7 @@ export interface ExtensionActions {
|
|
|
1290
1298
|
/** Actions for ExtensionContext (ctx.* in event handlers). */
|
|
1291
1299
|
export interface ExtensionContextActions {
|
|
1292
1300
|
getModel: () => Model | undefined;
|
|
1301
|
+
getSearchDb?: () => SearchDb | undefined;
|
|
1293
1302
|
isIdle: () => boolean;
|
|
1294
1303
|
abort: () => void;
|
|
1295
1304
|
hasPendingMessages: () => boolean;
|