@oh-my-pi/pi-coding-agent 15.0.0 → 15.0.2
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 +79 -0
- package/examples/extensions/plan-mode.ts +0 -1
- package/package.json +10 -10
- package/scripts/build-binary.ts +5 -0
- package/src/autoresearch/helpers.ts +17 -0
- package/src/autoresearch/tools/log-experiment.ts +9 -17
- package/src/autoresearch/tools/run-experiment.ts +2 -17
- package/src/capability/skill.ts +7 -0
- package/src/cli/list-models.ts +1 -1
- package/src/cli/shell-cli.ts +3 -13
- package/src/cli/update-cli.ts +1 -1
- package/src/cli.ts +10 -29
- package/src/commands/commit.ts +10 -0
- package/src/commit/agentic/tools/propose-changelog.ts +8 -1
- package/src/commit/analysis/conventional.ts +8 -66
- package/src/commit/map-reduce/reduce-phase.ts +6 -65
- package/src/commit/pipeline.ts +2 -2
- package/src/commit/shared-llm.ts +89 -0
- package/src/config/config-file.ts +210 -0
- package/src/config/model-equivalence.ts +8 -11
- package/src/config/model-registry.ts +44 -3
- package/src/config/model-resolver.ts +1 -4
- package/src/config/settings-schema.ts +82 -1
- package/src/config/settings.ts +1 -1
- package/src/config.ts +3 -219
- package/src/discovery/claude-plugins.ts +19 -7
- package/src/edit/renderer.ts +7 -1
- package/src/eval/js/executor.ts +3 -0
- package/src/eval/js/shared/rewrite-imports.ts +2 -2
- package/src/eval/py/executor.ts +5 -0
- package/src/eval/py/runner.py +42 -11
- package/src/eval/py/runtime.ts +1 -0
- package/src/exa/factory.ts +2 -2
- package/src/exa/mcp-client.ts +74 -1
- package/src/exec/bash-executor.ts +5 -1
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +0 -11
- package/src/extensibility/extensions/get-commands-handler.ts +77 -0
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +89 -223
- package/src/extensibility/hooks/types.ts +89 -314
- package/src/extensibility/plugins/legacy-pi-compat.ts +48 -31
- package/src/extensibility/shared-events.ts +343 -0
- package/src/extensibility/skills.ts +9 -0
- package/src/goals/index.ts +3 -0
- package/src/goals/runtime.ts +500 -0
- package/src/goals/state.ts +37 -0
- package/src/goals/tools/goal-tool.ts +237 -0
- package/src/hashline/anchors.ts +2 -2
- package/src/hashline/input.ts +2 -1
- package/src/hashline/parser.ts +27 -3
- package/src/hindsight/mental-models.ts +1 -1
- package/src/internal-urls/agent-protocol.ts +1 -20
- package/src/internal-urls/artifact-protocol.ts +1 -19
- package/src/internal-urls/docs-index.generated.ts +11 -12
- package/src/internal-urls/registry-helpers.ts +25 -0
- package/src/internal-urls/router.ts +8 -0
- package/src/internal-urls/types.ts +21 -0
- package/src/lsp/config.ts +15 -6
- package/src/lsp/defaults.json +6 -2
- package/src/main.ts +11 -2
- package/src/mcp/oauth-flow.ts +20 -0
- package/src/modes/acp/acp-agent.ts +327 -95
- package/src/modes/components/assistant-message.ts +14 -8
- package/src/modes/components/bash-execution.ts +24 -63
- package/src/modes/components/custom-message.ts +14 -40
- package/src/modes/components/eval-execution.ts +27 -57
- package/src/modes/components/execution-shared.ts +102 -0
- package/src/modes/components/hook-message.ts +17 -49
- package/src/modes/components/mcp-add-wizard.ts +26 -5
- package/src/modes/components/message-frame.ts +88 -0
- package/src/modes/components/model-selector.ts +1 -1
- package/src/modes/components/session-observer-overlay.ts +6 -2
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +93 -8
- package/src/modes/components/status-line/types.ts +4 -0
- package/src/modes/components/status-line.ts +28 -10
- package/src/modes/components/tool-execution.ts +7 -8
- package/src/modes/controllers/command-controller-shared.ts +108 -0
- package/src/modes/controllers/command-controller.ts +13 -4
- package/src/modes/controllers/event-controller.ts +36 -7
- package/src/modes/controllers/extension-ui-controller.ts +3 -2
- package/src/modes/controllers/input-controller.ts +13 -0
- package/src/modes/controllers/mcp-command-controller.ts +56 -61
- package/src/modes/controllers/ssh-command-controller.ts +18 -57
- package/src/modes/interactive-mode.ts +624 -52
- package/src/modes/print-mode.ts +16 -86
- package/src/modes/rpc/host-uris.ts +235 -0
- package/src/modes/rpc/rpc-mode.ts +41 -88
- package/src/modes/rpc/rpc-types.ts +57 -0
- package/src/modes/runtime-init.ts +116 -0
- package/src/modes/theme/defaults/dark-poimandres.json +3 -0
- package/src/modes/theme/defaults/light-poimandres.json +3 -0
- package/src/modes/theme/theme.ts +24 -6
- package/src/modes/types.ts +14 -3
- package/src/modes/utils/context-usage.ts +13 -13
- package/src/modes/utils/ui-helpers.ts +10 -3
- package/src/plan-mode/approved-plan.ts +35 -1
- package/src/prompts/goals/goal-budget-limit.md +16 -0
- package/src/prompts/goals/goal-continuation.md +28 -0
- package/src/prompts/goals/goal-mode-active.md +23 -0
- package/src/prompts/system/plan-mode-active.md +5 -5
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
- package/src/prompts/tools/bash.md +6 -0
- package/src/prompts/tools/github.md +4 -4
- package/src/prompts/tools/goal.md +13 -0
- package/src/prompts/tools/hashline.md +101 -117
- package/src/prompts/tools/read.md +55 -36
- package/src/prompts/tools/resolve.md +6 -5
- package/src/sdk.ts +12 -5
- package/src/session/agent-session.ts +428 -106
- package/src/session/blob-store.ts +36 -3
- package/src/session/messages.ts +67 -2
- package/src/session/session-manager.ts +131 -12
- package/src/session/session-storage.ts +33 -15
- package/src/session/streaming-output.ts +309 -13
- package/src/slash-commands/builtin-registry.ts +18 -0
- package/src/ssh/ssh-executor.ts +5 -0
- package/src/system-prompt.ts +4 -2
- package/src/task/discovery.ts +5 -2
- package/src/task/executor.ts +19 -8
- package/src/task/index.ts +3 -0
- package/src/task/render.ts +21 -15
- package/src/task/types.ts +4 -0
- package/src/tools/ast-edit.ts +21 -120
- package/src/tools/ast-grep.ts +21 -119
- package/src/tools/bash-command-fixup.ts +47 -0
- package/src/tools/bash-interactive.ts +9 -1
- package/src/tools/bash.ts +66 -19
- package/src/tools/browser/attach.ts +3 -3
- package/src/tools/browser/launch.ts +81 -18
- package/src/tools/browser/registry.ts +1 -5
- package/src/tools/browser/render.ts +2 -2
- package/src/tools/browser/tab-supervisor.ts +51 -14
- package/src/tools/conflict-detect.ts +15 -4
- package/src/tools/eval.ts +12 -2
- package/src/tools/find.ts +20 -38
- package/src/tools/gh.ts +44 -10
- package/src/tools/index.ts +22 -11
- package/src/tools/inspect-image.ts +3 -10
- package/src/tools/job.ts +16 -7
- package/src/tools/output-meta.ts +202 -37
- package/src/tools/path-utils.ts +125 -2
- package/src/tools/read.ts +548 -237
- package/src/tools/render-utils.ts +92 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +72 -44
- package/src/tools/search.ts +120 -186
- package/src/tools/ssh.ts +3 -2
- package/src/tools/write.ts +64 -9
- package/src/utils/file-mentions.ts +1 -1
- package/src/utils/image-loading.ts +7 -3
- package/src/utils/image-resize.ts +32 -43
- package/src/vim/parser.ts +0 -17
- package/src/vim/render.ts +1 -1
- package/src/vim/types.ts +1 -1
- package/src/web/search/providers/anthropic.ts +5 -0
- package/src/web/search/providers/exa.ts +3 -0
- package/src/web/search/providers/gemini.ts +40 -95
- package/src/web/search/providers/jina.ts +5 -2
- package/src/web/search/providers/zai.ts +5 -2
- package/src/prompts/tools/exit-plan-mode.md +0 -6
- package/src/tools/exit-plan-mode.ts +0 -97
- package/src/utils/fuzzy.ts +0 -108
- package/src/utils/image-convert.ts +0 -27
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import { validateToolCall } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import { Type } from "@sinclair/typebox";
|
|
4
|
+
import type { ChangelogCategory, ConventionalAnalysis } from "./types";
|
|
5
|
+
import { extractTextContent, extractToolCall, normalizeAnalysis, parseJsonPayload } from "./utils";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shared TypeBox schema for the `create_conventional_analysis` tool used by
|
|
9
|
+
* both the single-pass analysis call and the map-reduce reduce phase. Schemas
|
|
10
|
+
* are identical across phases — only the surrounding tool `description`
|
|
11
|
+
* differs to reflect the input the phase is summarizing.
|
|
12
|
+
*/
|
|
13
|
+
export const conventionalAnalysisParameters = Type.Object({
|
|
14
|
+
type: Type.Union([
|
|
15
|
+
Type.Literal("feat"),
|
|
16
|
+
Type.Literal("fix"),
|
|
17
|
+
Type.Literal("refactor"),
|
|
18
|
+
Type.Literal("docs"),
|
|
19
|
+
Type.Literal("test"),
|
|
20
|
+
Type.Literal("chore"),
|
|
21
|
+
Type.Literal("style"),
|
|
22
|
+
Type.Literal("perf"),
|
|
23
|
+
Type.Literal("build"),
|
|
24
|
+
Type.Literal("ci"),
|
|
25
|
+
Type.Literal("revert"),
|
|
26
|
+
]),
|
|
27
|
+
scope: Type.Union([Type.String(), Type.Null()]),
|
|
28
|
+
details: Type.Array(
|
|
29
|
+
Type.Object({
|
|
30
|
+
text: Type.String(),
|
|
31
|
+
changelog_category: Type.Optional(
|
|
32
|
+
Type.Union([
|
|
33
|
+
Type.Literal("Added"),
|
|
34
|
+
Type.Literal("Changed"),
|
|
35
|
+
Type.Literal("Fixed"),
|
|
36
|
+
Type.Literal("Deprecated"),
|
|
37
|
+
Type.Literal("Removed"),
|
|
38
|
+
Type.Literal("Security"),
|
|
39
|
+
Type.Literal("Breaking Changes"),
|
|
40
|
+
]),
|
|
41
|
+
),
|
|
42
|
+
user_visible: Type.Optional(Type.Boolean()),
|
|
43
|
+
}),
|
|
44
|
+
),
|
|
45
|
+
issue_refs: Type.Array(Type.String()),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export interface ConventionalAnalysisTool {
|
|
49
|
+
name: "create_conventional_analysis";
|
|
50
|
+
description: string;
|
|
51
|
+
parameters: typeof conventionalAnalysisParameters;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Build a `create_conventional_analysis` tool descriptor. Phase-specific
|
|
56
|
+
* `description` text is the only thing that varies between callers.
|
|
57
|
+
*/
|
|
58
|
+
export function createConventionalAnalysisTool(description: string): ConventionalAnalysisTool {
|
|
59
|
+
return {
|
|
60
|
+
name: "create_conventional_analysis",
|
|
61
|
+
description,
|
|
62
|
+
parameters: conventionalAnalysisParameters,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface ParsedConventionalAnalysis {
|
|
67
|
+
type: ConventionalAnalysis["type"];
|
|
68
|
+
scope: string | null;
|
|
69
|
+
details: Array<{ text: string; changelog_category?: ChangelogCategory; user_visible?: boolean }>;
|
|
70
|
+
issue_refs: string[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Extract a {@link ConventionalAnalysis} from an assistant response, preferring
|
|
75
|
+
* a structured tool call and falling back to JSON embedded in text content.
|
|
76
|
+
*/
|
|
77
|
+
export function parseConventionalAnalysisResponse(
|
|
78
|
+
message: AssistantMessage,
|
|
79
|
+
tool: ConventionalAnalysisTool,
|
|
80
|
+
): ConventionalAnalysis {
|
|
81
|
+
const toolCall = extractToolCall(message, tool.name);
|
|
82
|
+
if (toolCall) {
|
|
83
|
+
const parsed = validateToolCall([tool], toolCall) as ParsedConventionalAnalysis;
|
|
84
|
+
return normalizeAnalysis(parsed);
|
|
85
|
+
}
|
|
86
|
+
const text = extractTextContent(message);
|
|
87
|
+
const parsed = parseJsonPayload(text) as ParsedConventionalAnalysis;
|
|
88
|
+
return normalizeAnalysis(parsed);
|
|
89
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { getAgentDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
4
|
+
import type { TSchema } from "@sinclair/typebox";
|
|
5
|
+
import { Value } from "@sinclair/typebox/value";
|
|
6
|
+
import type { ErrorObject } from "ajv";
|
|
7
|
+
import { JSONC, YAML } from "bun";
|
|
8
|
+
|
|
9
|
+
function migrateJsonToYml(jsonPath: string, ymlPath: string) {
|
|
10
|
+
try {
|
|
11
|
+
if (fs.existsSync(ymlPath)) return;
|
|
12
|
+
if (!fs.existsSync(jsonPath)) return;
|
|
13
|
+
|
|
14
|
+
const content = fs.readFileSync(jsonPath, "utf-8");
|
|
15
|
+
const parsed = JSON.parse(content);
|
|
16
|
+
if (!parsed) {
|
|
17
|
+
logger.warn("migrateJsonToYml: invalid json structure", { path: jsonPath });
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
fs.writeFileSync(ymlPath, YAML.stringify(parsed, null, 2));
|
|
21
|
+
} catch (error) {
|
|
22
|
+
logger.warn("migrateJsonToYml: migration failed", { error: String(error) });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface IConfigFile<T> {
|
|
27
|
+
readonly id: string;
|
|
28
|
+
readonly schema: TSchema;
|
|
29
|
+
path?(): string;
|
|
30
|
+
load(): T | null;
|
|
31
|
+
invalidate?(): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class ConfigError extends Error {
|
|
35
|
+
readonly #message: string;
|
|
36
|
+
constructor(
|
|
37
|
+
public readonly id: string,
|
|
38
|
+
public readonly schemaErrors: ErrorObject[] | null | undefined,
|
|
39
|
+
public readonly other?: { err: unknown; stage: string },
|
|
40
|
+
) {
|
|
41
|
+
let messages: string[] | undefined;
|
|
42
|
+
let cause: Error | undefined;
|
|
43
|
+
let klass: string;
|
|
44
|
+
|
|
45
|
+
if (schemaErrors) {
|
|
46
|
+
klass = "Schema";
|
|
47
|
+
messages = schemaErrors.map(e => `${e.instancePath || "root"}: ${e.message}`);
|
|
48
|
+
} else if (other) {
|
|
49
|
+
klass = other.stage;
|
|
50
|
+
if (other.err instanceof Error) {
|
|
51
|
+
messages = [other.err.message];
|
|
52
|
+
cause = other.err;
|
|
53
|
+
} else {
|
|
54
|
+
messages = [String(other.err)];
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
klass = "Unknown";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const title = `Failed to load config file ${id}, ${klass} error:`;
|
|
61
|
+
let message: string;
|
|
62
|
+
switch (messages?.length ?? 0) {
|
|
63
|
+
case 0:
|
|
64
|
+
message = title.slice(0, -1);
|
|
65
|
+
break;
|
|
66
|
+
case 1:
|
|
67
|
+
message = `${title} ${messages![0]}`;
|
|
68
|
+
break;
|
|
69
|
+
default:
|
|
70
|
+
message = `${title}\n${messages!.map(m => ` - ${m}`).join("\n")}`;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
super(message, { cause });
|
|
75
|
+
this.name = "LoadError";
|
|
76
|
+
this.#message = message;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get message(): string {
|
|
80
|
+
return this.#message;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
toString(): string {
|
|
84
|
+
return this.message;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type LoadStatus = "ok" | "error" | "not-found";
|
|
89
|
+
|
|
90
|
+
export type LoadResult<T> =
|
|
91
|
+
| { value?: null; error: ConfigError; status: "error" }
|
|
92
|
+
| { value: T; error?: undefined; status: "ok" }
|
|
93
|
+
| { value?: null; error?: unknown; status: "not-found" };
|
|
94
|
+
|
|
95
|
+
export class ConfigFile<T> implements IConfigFile<T> {
|
|
96
|
+
readonly #basePath: string;
|
|
97
|
+
#cache?: LoadResult<T>;
|
|
98
|
+
#auxValidate?: (value: T) => void;
|
|
99
|
+
|
|
100
|
+
constructor(
|
|
101
|
+
readonly id: string,
|
|
102
|
+
readonly schema: TSchema,
|
|
103
|
+
configPath: string = path.join(getAgentDir(), `${id}.yml`),
|
|
104
|
+
) {
|
|
105
|
+
this.#basePath = configPath;
|
|
106
|
+
if (configPath.endsWith(".yml")) {
|
|
107
|
+
const jsonPath = `${configPath.slice(0, -4)}.json`;
|
|
108
|
+
migrateJsonToYml(jsonPath, configPath);
|
|
109
|
+
} else if (configPath.endsWith(".yaml")) {
|
|
110
|
+
const jsonPath = `${configPath.slice(0, -5)}.json`;
|
|
111
|
+
migrateJsonToYml(jsonPath, configPath);
|
|
112
|
+
} else if (configPath.endsWith(".json") || configPath.endsWith(".jsonc")) {
|
|
113
|
+
// JSON configs are still supported without migration.
|
|
114
|
+
} else {
|
|
115
|
+
throw new Error(`Invalid config file path: ${configPath}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
relocate(configPath?: string): ConfigFile<T> {
|
|
120
|
+
if (!configPath || configPath === this.#basePath) return this;
|
|
121
|
+
const result = new ConfigFile<T>(this.id, this.schema, configPath);
|
|
122
|
+
result.#auxValidate = this.#auxValidate;
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getMtimeMs(): number | null {
|
|
127
|
+
try {
|
|
128
|
+
return fs.statSync(this.path()).mtimeMs;
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if (isEnoent(err)) return null;
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
withValidation(name: string, validate: (value: T) => void): this {
|
|
136
|
+
const prev = this.#auxValidate;
|
|
137
|
+
this.#auxValidate = (value: T) => {
|
|
138
|
+
prev?.(value);
|
|
139
|
+
try {
|
|
140
|
+
validate(value);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
throw new ConfigError(this.id, undefined, { err: error, stage: `Validate(${name})` });
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
createDefault(): T {
|
|
149
|
+
return Value.Default(this.schema, [], undefined) as T;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#storeCache(result: LoadResult<T>): LoadResult<T> {
|
|
153
|
+
this.#cache = result;
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
tryLoad(): LoadResult<T> {
|
|
158
|
+
if (this.#cache) return this.#cache;
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const content = fs.readFileSync(this.path(), "utf-8").trim();
|
|
162
|
+
|
|
163
|
+
let parsed: unknown;
|
|
164
|
+
if (this.#basePath.endsWith(".json") || this.#basePath.endsWith(".jsonc")) {
|
|
165
|
+
parsed = JSONC.parse(content);
|
|
166
|
+
} else if (this.#basePath.endsWith(".yml") || this.#basePath.endsWith(".yaml")) {
|
|
167
|
+
parsed = YAML.parse(content);
|
|
168
|
+
} else {
|
|
169
|
+
throw new Error(`Invalid config file path: ${this.#basePath}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!Value.Check(this.schema, parsed)) {
|
|
173
|
+
const schemaErrors: ErrorObject[] = [];
|
|
174
|
+
for (const err of Value.Errors(this.schema, parsed)) {
|
|
175
|
+
schemaErrors.push({ instancePath: err.path, message: err.message } as ErrorObject);
|
|
176
|
+
if (schemaErrors.length >= 50) break;
|
|
177
|
+
}
|
|
178
|
+
const error = new ConfigError(this.id, schemaErrors);
|
|
179
|
+
logger.warn("Failed to parse config file", { path: this.path(), error });
|
|
180
|
+
return this.#storeCache({ error, status: "error" });
|
|
181
|
+
}
|
|
182
|
+
return this.#storeCache({ value: parsed as T, status: "ok" });
|
|
183
|
+
} catch (error) {
|
|
184
|
+
if (isEnoent(error)) {
|
|
185
|
+
return this.#storeCache({ status: "not-found" });
|
|
186
|
+
}
|
|
187
|
+
logger.warn("Failed to parse config file", { path: this.path(), error });
|
|
188
|
+
return this.#storeCache({
|
|
189
|
+
error: new ConfigError(this.id, undefined, { err: error, stage: "Unexpected" }),
|
|
190
|
+
status: "error",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
load(): T | null {
|
|
196
|
+
return this.tryLoad().value ?? null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
loadOrDefault(): T {
|
|
200
|
+
return this.tryLoad().value ?? this.createDefault();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
path(): string {
|
|
204
|
+
return this.#basePath;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
invalidate() {
|
|
208
|
+
this.#cache = undefined;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -72,15 +72,12 @@ const TRAILING_MARKER_SUFFIXES: readonly string[] = (() => {
|
|
|
72
72
|
})();
|
|
73
73
|
const WRAPPER_PREFIXES = ["duo-chat-"] as const;
|
|
74
74
|
|
|
75
|
-
let
|
|
75
|
+
let referenceDataCache: CanonicalReferenceData | undefined;
|
|
76
76
|
const EMPTY_COMPILED_EQUIVALENCE: CompiledEquivalenceConfig = {
|
|
77
77
|
overrides: new Map<string, string>(),
|
|
78
78
|
exclude: new Set<string>(),
|
|
79
79
|
};
|
|
80
|
-
const
|
|
81
|
-
CompiledEquivalenceConfig,
|
|
82
|
-
WeakMap<Model<Api>, ResolvedCanonicalModel>
|
|
83
|
-
> = new WeakMap();
|
|
80
|
+
const resolutionCache: WeakMap<CompiledEquivalenceConfig, WeakMap<Model<Api>, ResolvedCanonicalModel>> = new WeakMap();
|
|
84
81
|
const FAMILY_EXTRACTION_PATTERNS = [
|
|
85
82
|
/(?:^|[/:._-])((?:claude|gemini|gpt|grok|glm|qwen|minimax|kimi|deepseek|llama|gemma|nova|mistral|ministral|pixtral|codestral|devstral|magistral|ernie|doubao|seed|aion|olmo|molmo|nemotron|palmyra|command|codex|coder|o[1345])[-a-z0-9.]+)(?::|$)/i,
|
|
86
83
|
/(?:^|[/:._-])((?:claude|gemini|gpt|grok|glm|qwen|minimax|kimi|deepseek|llama|gemma|nova|mistral|ministral|pixtral|codestral|devstral|magistral|ernie|doubao|seed|aion|olmo|molmo|nemotron|palmyra|command|codex|coder|o[1345])[-a-z0-9.]+(?:[-_/][a-z0-9.]+)*)(?::|$)/i,
|
|
@@ -98,8 +95,8 @@ function shouldReplaceReference(existing: Model<Api> | undefined, candidate: Mod
|
|
|
98
95
|
}
|
|
99
96
|
|
|
100
97
|
function createCanonicalReferenceData(): CanonicalReferenceData {
|
|
101
|
-
if (
|
|
102
|
-
return
|
|
98
|
+
if (referenceDataCache) {
|
|
99
|
+
return referenceDataCache;
|
|
103
100
|
}
|
|
104
101
|
const references = new Map<string, Model<Api>>();
|
|
105
102
|
for (const provider of getBundledProviders()) {
|
|
@@ -112,11 +109,11 @@ function createCanonicalReferenceData(): CanonicalReferenceData {
|
|
|
112
109
|
}
|
|
113
110
|
}
|
|
114
111
|
const officialIds = new Set(references.keys());
|
|
115
|
-
|
|
112
|
+
referenceDataCache = {
|
|
116
113
|
references: Object.freeze(references) as Map<string, Model<Api>>,
|
|
117
114
|
officialIds: Object.freeze(officialIds) as Set<string>,
|
|
118
115
|
};
|
|
119
|
-
return
|
|
116
|
+
return referenceDataCache;
|
|
120
117
|
}
|
|
121
118
|
|
|
122
119
|
function normalizeSelectorKey(selector: string): string {
|
|
@@ -668,10 +665,10 @@ export function buildCanonicalModelIndex(
|
|
|
668
665
|
const byId = new Map<string, CanonicalModelRecord>();
|
|
669
666
|
const bySelector = new Map<string, string>();
|
|
670
667
|
|
|
671
|
-
let modelCache =
|
|
668
|
+
let modelCache = resolutionCache.get(compiledEquivalence);
|
|
672
669
|
if (!modelCache) {
|
|
673
670
|
modelCache = new WeakMap<Model<Api>, ResolvedCanonicalModel>();
|
|
674
|
-
|
|
671
|
+
resolutionCache.set(compiledEquivalence, modelCache);
|
|
675
672
|
}
|
|
676
673
|
|
|
677
674
|
for (const model of models) {
|
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
registerCustomApi,
|
|
19
19
|
type SimpleStreamOptions,
|
|
20
20
|
type ThinkingConfig,
|
|
21
|
+
UNK_CONTEXT_WINDOW,
|
|
22
|
+
UNK_MAX_TOKENS,
|
|
21
23
|
unregisterCustomApis,
|
|
22
24
|
} from "@oh-my-pi/pi-ai";
|
|
23
25
|
|
|
@@ -29,10 +31,10 @@ import { registerOAuthProvider, unregisterOAuthProviders } from "@oh-my-pi/pi-ai
|
|
|
29
31
|
import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/utils/oauth/types";
|
|
30
32
|
import { isRecord, logger } from "@oh-my-pi/pi-utils";
|
|
31
33
|
import { type Static, Type } from "@sinclair/typebox";
|
|
32
|
-
import { type ConfigError, ConfigFile } from "../config";
|
|
33
34
|
import { parseModelString, resolveProviderModelReference } from "../config/model-resolver";
|
|
34
35
|
import { isValidThemeColor, type ThemeColor } from "../modes/theme/theme";
|
|
35
36
|
import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
|
|
37
|
+
import { type ConfigError, ConfigFile } from "./config-file";
|
|
36
38
|
import {
|
|
37
39
|
buildCanonicalModelIndex,
|
|
38
40
|
type CanonicalModelIndex,
|
|
@@ -1013,8 +1015,12 @@ export class ModelRegistry {
|
|
|
1013
1015
|
|
|
1014
1016
|
this.#addImplicitDiscoverableProviders(configuredProviders);
|
|
1015
1017
|
const builtInModels = this.#applyHardcodedModelPolicies(this.#loadBuiltInModels(overrides));
|
|
1018
|
+
const cachedStandardModels = this.#applyHardcodedModelPolicies(this.#loadCachedStandardProviderModels());
|
|
1016
1019
|
const cachedDiscoveries = this.#applyHardcodedModelPolicies(this.#loadCachedDiscoverableModels());
|
|
1017
|
-
const resolvedDefaults = this.#mergeResolvedModels(
|
|
1020
|
+
const resolvedDefaults = this.#mergeResolvedModels(
|
|
1021
|
+
this.#mergeResolvedModels(builtInModels, cachedStandardModels),
|
|
1022
|
+
cachedDiscoveries,
|
|
1023
|
+
);
|
|
1018
1024
|
const withConfigModels = this.#mergeCustomModels(resolvedDefaults, this.#customModelOverlays);
|
|
1019
1025
|
// Merge runtime extension models so they survive refresh() cycles
|
|
1020
1026
|
const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
|
|
@@ -1053,7 +1059,16 @@ export class ModelRegistry {
|
|
|
1053
1059
|
const key = `${replacementModel.provider}\u0000${replacementModel.id}`;
|
|
1054
1060
|
const existingIndex = indexByKey.get(key);
|
|
1055
1061
|
if (existingIndex !== undefined) {
|
|
1056
|
-
merged[existingIndex]
|
|
1062
|
+
const existing = merged[existingIndex];
|
|
1063
|
+
merged[existingIndex] = {
|
|
1064
|
+
...replacementModel,
|
|
1065
|
+
contextWindow:
|
|
1066
|
+
replacementModel.contextWindow === UNK_CONTEXT_WINDOW
|
|
1067
|
+
? existing.contextWindow
|
|
1068
|
+
: replacementModel.contextWindow,
|
|
1069
|
+
maxTokens:
|
|
1070
|
+
replacementModel.maxTokens === UNK_MAX_TOKENS ? existing.maxTokens : replacementModel.maxTokens,
|
|
1071
|
+
};
|
|
1057
1072
|
} else {
|
|
1058
1073
|
merged.push(replacementModel);
|
|
1059
1074
|
indexByKey.set(key, merged.length - 1);
|
|
@@ -1104,6 +1119,32 @@ export class ModelRegistry {
|
|
|
1104
1119
|
return merged;
|
|
1105
1120
|
}
|
|
1106
1121
|
|
|
1122
|
+
#loadCachedStandardProviderModels(): Model<Api>[] {
|
|
1123
|
+
const configuredDiscoveryProviders = new Set(this.#discoverableProviders.map(provider => provider.provider));
|
|
1124
|
+
const cachedModels: Model<Api>[] = [];
|
|
1125
|
+
for (const descriptor of PROVIDER_DESCRIPTORS) {
|
|
1126
|
+
if (configuredDiscoveryProviders.has(descriptor.providerId)) {
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
const cache = readModelCache<Api>(descriptor.providerId, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
|
|
1130
|
+
if (!cache) {
|
|
1131
|
+
continue;
|
|
1132
|
+
}
|
|
1133
|
+
const models = cache.models.map(model =>
|
|
1134
|
+
model.provider === descriptor.providerId ? model : { ...model, provider: descriptor.providerId },
|
|
1135
|
+
);
|
|
1136
|
+
const providerOverride = this.#providerOverrides.get(descriptor.providerId);
|
|
1137
|
+
const withTransport = providerOverride
|
|
1138
|
+
? models.map(model => this.#applyProviderTransportOverride(model, providerOverride))
|
|
1139
|
+
: models;
|
|
1140
|
+
const withCompat = providerOverride?.compat
|
|
1141
|
+
? withTransport.map(model => ({ ...model, compat: mergeCompat(model.compat, providerOverride.compat) }))
|
|
1142
|
+
: withTransport;
|
|
1143
|
+
cachedModels.push(...this.#applyProviderModelOverrides(descriptor.providerId, withCompat));
|
|
1144
|
+
}
|
|
1145
|
+
return cachedModels;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1107
1148
|
#loadCachedDiscoverableModels(): Model<Api>[] {
|
|
1108
1149
|
const cachedModels: Model<Api>[] = [];
|
|
1109
1150
|
for (const providerConfig of this.#discoverableProviders) {
|
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
type Model,
|
|
13
13
|
modelsAreEqual,
|
|
14
14
|
} from "@oh-my-pi/pi-ai";
|
|
15
|
+
import { fuzzyMatch } from "@oh-my-pi/pi-tui";
|
|
15
16
|
import chalk from "chalk";
|
|
16
17
|
import MODEL_PRIO from "../priority.json" with { type: "json" };
|
|
17
18
|
import { parseThinkingLevel, resolveThinkingLevelForModel } from "../thinking";
|
|
18
|
-
import { fuzzyMatch } from "../utils/fuzzy";
|
|
19
19
|
import { isAuthenticated, kNoAuth, MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "./model-registry";
|
|
20
20
|
import type { Settings } from "./settings";
|
|
21
21
|
|
|
@@ -607,9 +607,6 @@ export function resolveModelRoleValue(
|
|
|
607
607
|
return { model: undefined, thinkingLevel: undefined, explicitThinkingLevel: false, warning: undefined };
|
|
608
608
|
}
|
|
609
609
|
|
|
610
|
-
const lastColonIndex = normalized.lastIndexOf(":");
|
|
611
|
-
const _thinkingSelector =
|
|
612
|
-
lastColonIndex > PREFIX_MODEL_ROLE.length ? parseThinkingLevel(normalized.slice(lastColonIndex + 1)) : undefined;
|
|
613
610
|
const effectivePatterns = resolveConfiguredRolePattern(normalized, options?.settings);
|
|
614
611
|
if (!effectivePatterns || effectivePatterns.length === 0) {
|
|
615
612
|
return { model: undefined, thinkingLevel: undefined, explicitThinkingLevel: false, warning: undefined };
|
|
@@ -460,6 +460,46 @@ export const SETTINGS_SCHEMA = {
|
|
|
460
460
|
],
|
|
461
461
|
},
|
|
462
462
|
},
|
|
463
|
+
"tools.artifactHeadBytes": {
|
|
464
|
+
type: "number",
|
|
465
|
+
default: 20,
|
|
466
|
+
ui: {
|
|
467
|
+
tab: "tools",
|
|
468
|
+
label: "Artifact head size (KB)",
|
|
469
|
+
description:
|
|
470
|
+
"Amount of head content kept inline alongside the tail when output spills to artifact (middle elision). 0 disables — keep tail only.",
|
|
471
|
+
options: [
|
|
472
|
+
{ value: "0", label: "0 KB", description: "Disabled; tail-only truncation" },
|
|
473
|
+
{ value: "1", label: "1 KB", description: "~250 tokens" },
|
|
474
|
+
{ value: "2.5", label: "2.5 KB", description: "~625 tokens" },
|
|
475
|
+
{ value: "5", label: "5 KB", description: "~1.25K tokens" },
|
|
476
|
+
{ value: "10", label: "10 KB", description: "~2.5K tokens" },
|
|
477
|
+
{ value: "20", label: "20 KB", description: "Default; ~5K tokens" },
|
|
478
|
+
{ value: "50", label: "50 KB", description: "~12.5K tokens" },
|
|
479
|
+
{ value: "100", label: "100 KB", description: "~25K tokens" },
|
|
480
|
+
{ value: "200", label: "200 KB", description: "~50K tokens" },
|
|
481
|
+
],
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
"tools.outputMaxColumns": {
|
|
485
|
+
type: "number",
|
|
486
|
+
default: 768,
|
|
487
|
+
ui: {
|
|
488
|
+
tab: "tools",
|
|
489
|
+
label: "Output column cap",
|
|
490
|
+
description:
|
|
491
|
+
"Per-line byte cap for streaming tool outputs (bash, ssh, python, js eval) and `read`. Lines wider than this are ellipsis-truncated; remaining bytes up to the next newline are dropped. 0 disables.",
|
|
492
|
+
options: [
|
|
493
|
+
{ value: "0", label: "Off", description: "No per-line cap" },
|
|
494
|
+
{ value: "256", label: "256", description: "Tight" },
|
|
495
|
+
{ value: "512", label: "512" },
|
|
496
|
+
{ value: "768", label: "768", description: "Default" },
|
|
497
|
+
{ value: "1024", label: "1024" },
|
|
498
|
+
{ value: "2048", label: "2048" },
|
|
499
|
+
{ value: "4096", label: "4096", description: "Loose" },
|
|
500
|
+
],
|
|
501
|
+
},
|
|
502
|
+
},
|
|
463
503
|
"tools.artifactTailLines": {
|
|
464
504
|
type: "number",
|
|
465
505
|
default: 500,
|
|
@@ -1498,7 +1538,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
1498
1538
|
|
|
1499
1539
|
"read.defaultLimit": {
|
|
1500
1540
|
type: "number",
|
|
1501
|
-
default:
|
|
1541
|
+
default: 300,
|
|
1502
1542
|
ui: {
|
|
1503
1543
|
tab: "editing",
|
|
1504
1544
|
label: "Default Read Limit",
|
|
@@ -1608,6 +1648,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
1608
1648
|
},
|
|
1609
1649
|
"bashInterceptor.patterns": { type: "array", default: DEFAULT_BASH_INTERCEPTOR_RULES },
|
|
1610
1650
|
|
|
1651
|
+
"bash.stripTrailingHeadTail": {
|
|
1652
|
+
type: "boolean",
|
|
1653
|
+
default: true,
|
|
1654
|
+
ui: {
|
|
1655
|
+
tab: "editing",
|
|
1656
|
+
label: "Strip Trailing head/tail",
|
|
1657
|
+
description:
|
|
1658
|
+
"Silently drop trailing `| head`/`| tail` pipes from single-line bash commands. Output is already truncated automatically.",
|
|
1659
|
+
},
|
|
1660
|
+
},
|
|
1661
|
+
|
|
1611
1662
|
// Shell output minimizer
|
|
1612
1663
|
"shellMinimizer.enabled": {
|
|
1613
1664
|
type: "boolean",
|
|
@@ -2095,6 +2146,36 @@ export const SETTINGS_SCHEMA = {
|
|
|
2095
2146
|
},
|
|
2096
2147
|
},
|
|
2097
2148
|
|
|
2149
|
+
"goal.enabled": {
|
|
2150
|
+
type: "boolean",
|
|
2151
|
+
default: true,
|
|
2152
|
+
ui: {
|
|
2153
|
+
tab: "tasks",
|
|
2154
|
+
label: "Goal Mode",
|
|
2155
|
+
description: "Enable per-session goal mode and the hidden goal tool",
|
|
2156
|
+
},
|
|
2157
|
+
},
|
|
2158
|
+
|
|
2159
|
+
"goal.statusInFooter": {
|
|
2160
|
+
type: "boolean",
|
|
2161
|
+
default: true,
|
|
2162
|
+
ui: {
|
|
2163
|
+
tab: "tasks",
|
|
2164
|
+
label: "Goal Status In Footer",
|
|
2165
|
+
description: "Show token budget alongside the goal indicator in the status line",
|
|
2166
|
+
},
|
|
2167
|
+
},
|
|
2168
|
+
|
|
2169
|
+
"goal.continuationModes": {
|
|
2170
|
+
type: "array",
|
|
2171
|
+
default: ["interactive"],
|
|
2172
|
+
ui: {
|
|
2173
|
+
tab: "tasks",
|
|
2174
|
+
label: "Goal Continuation Modes",
|
|
2175
|
+
description: "Run modes where active goals may auto-continue between turns",
|
|
2176
|
+
},
|
|
2177
|
+
},
|
|
2178
|
+
|
|
2098
2179
|
// Delegation
|
|
2099
2180
|
"task.isolation.mode": {
|
|
2100
2181
|
type: "enum",
|
package/src/config/settings.ts
CHANGED
|
@@ -850,7 +850,7 @@ export function isSettingsInitialized(): boolean {
|
|
|
850
850
|
* Reset the global singleton for testing.
|
|
851
851
|
* @internal
|
|
852
852
|
*/
|
|
853
|
-
export function
|
|
853
|
+
export function resetSettingsForTest(): void {
|
|
854
854
|
globalInstance = null;
|
|
855
855
|
globalInstancePromise = null;
|
|
856
856
|
}
|