@oh-my-pi/pi-coding-agent 15.5.15 → 15.6.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 +46 -0
- package/dist/types/cli/classify-install-target.d.ts +0 -10
- package/dist/types/cli/initial-message.d.ts +1 -1
- package/dist/types/cli/tiny-models-cli.d.ts +9 -0
- package/dist/types/commands/tiny-models.d.ts +22 -0
- package/dist/types/commit/analysis/conventional.d.ts +1 -1
- package/dist/types/commit/analysis/summary.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +1 -1
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
- package/dist/types/config/model-id-affixes.d.ts +10 -0
- package/dist/types/config/settings-schema.d.ts +232 -7
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +16 -1
- package/dist/types/internal-urls/agent-protocol.d.ts +2 -1
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -1
- package/dist/types/internal-urls/local-protocol.d.ts +2 -1
- package/dist/types/internal-urls/memory-protocol.d.ts +2 -1
- package/dist/types/internal-urls/omp-protocol.d.ts +2 -1
- package/dist/types/internal-urls/router.d.ts +8 -1
- package/dist/types/internal-urls/rule-protocol.d.ts +2 -1
- package/dist/types/internal-urls/skill-protocol.d.ts +2 -1
- package/dist/types/internal-urls/types.d.ts +26 -0
- package/dist/types/memory-backend/index.d.ts +1 -0
- package/dist/types/memory-backend/resolve.d.ts +2 -1
- package/dist/types/memory-backend/types.d.ts +7 -1
- package/dist/types/mnemosyne/backend.d.ts +4 -0
- package/dist/types/mnemosyne/config.d.ts +29 -0
- package/dist/types/mnemosyne/index.d.ts +3 -0
- package/dist/types/mnemosyne/state.d.ts +72 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -3
- package/dist/types/modes/components/hook-selector.d.ts +27 -0
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/context-thresholds.d.ts +6 -0
- package/dist/types/modes/components/tiny-title-download-progress.d.ts +11 -0
- package/dist/types/modes/components/welcome.d.ts +1 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +4 -1
- package/dist/types/modes/gradient-highlight.d.ts +23 -0
- package/dist/types/modes/interactive-mode.d.ts +4 -2
- package/dist/types/modes/internal-url-autocomplete.d.ts +43 -0
- package/dist/types/modes/orchestrate.d.ts +10 -0
- package/dist/types/modes/theme/defaults/index.d.ts +8406 -8406
- package/dist/types/modes/ultrathink.d.ts +3 -3
- package/dist/types/modes/utils/keybinding-matchers.d.ts +5 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +33 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/task/render.d.ts +5 -1
- package/dist/types/tiny/models.d.ts +185 -0
- package/dist/types/tiny/text.d.ts +4 -0
- package/dist/types/tiny/title-client.d.ts +24 -0
- package/dist/types/tiny/title-protocol.d.ts +74 -0
- package/dist/types/tiny/worker.d.ts +2 -0
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/index.d.ts +7 -3
- package/dist/types/tools/memory-edit.d.ts +40 -0
- package/dist/types/tools/{hindsight-recall.d.ts → memory-recall.d.ts} +6 -6
- package/dist/types/tools/{hindsight-reflect.d.ts → memory-reflect.d.ts} +6 -6
- package/dist/types/tools/memory-render.d.ts +60 -0
- package/dist/types/tools/{hindsight-retain.d.ts → memory-retain.d.ts} +6 -6
- package/dist/types/tools/todo-write.d.ts +8 -0
- package/dist/types/tools/tool-result.d.ts +2 -0
- package/dist/types/utils/title-generator.d.ts +3 -0
- package/package.json +18 -14
- package/scripts/build-binary.ts +1 -0
- package/src/cli/tiny-models-cli.ts +127 -0
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +8 -8
- package/src/commands/tiny-models.ts +36 -0
- package/src/config/model-equivalence.ts +43 -2
- package/src/config/model-id-affixes.ts +64 -0
- package/src/config/model-registry.ts +84 -10
- package/src/config/settings-schema.ts +205 -4
- package/src/edit/hashline/diff.ts +5 -7
- package/src/eval/__tests__/shared-executors.test.ts +36 -0
- package/src/eval/js/shared/local-module-loader.ts +13 -1
- package/src/eval/js/shared/rewrite-imports.ts +31 -26
- package/src/internal-urls/agent-protocol.ts +18 -1
- package/src/internal-urls/artifact-protocol.ts +19 -1
- package/src/internal-urls/docs-index.generated.ts +3 -1
- package/src/internal-urls/local-protocol.ts +14 -1
- package/src/internal-urls/memory-protocol.ts +6 -1
- package/src/internal-urls/omp-protocol.ts +5 -1
- package/src/internal-urls/router.ts +20 -1
- package/src/internal-urls/rule-protocol.ts +8 -1
- package/src/internal-urls/skill-protocol.ts +8 -1
- package/src/internal-urls/types.ts +27 -0
- package/src/lsp/render.ts +1 -1
- package/src/mcp/oauth-flow.ts +2 -2
- package/src/memory-backend/index.ts +1 -0
- package/src/memory-backend/resolve.ts +4 -1
- package/src/memory-backend/types.ts +8 -1
- package/src/mnemosyne/backend.ts +374 -0
- package/src/mnemosyne/config.ts +160 -0
- package/src/mnemosyne/index.ts +3 -0
- package/src/mnemosyne/state.ts +548 -0
- package/src/modes/acp/acp-agent.ts +11 -6
- package/src/modes/components/agent-dashboard.ts +4 -4
- package/src/modes/components/custom-editor.ts +3 -2
- package/src/modes/components/diff.ts +2 -2
- package/src/modes/components/extensions/extension-list.ts +3 -2
- package/src/modes/components/footer.ts +5 -6
- package/src/modes/components/history-search.ts +3 -3
- package/src/modes/components/hook-selector.ts +94 -8
- package/src/modes/components/index.ts +1 -0
- package/src/modes/components/mcp-add-wizard.ts +3 -3
- package/src/modes/components/model-selector.ts +5 -4
- package/src/modes/components/oauth-selector.ts +3 -3
- package/src/modes/components/session-observer-overlay.ts +19 -13
- package/src/modes/components/session-selector.ts +3 -3
- package/src/modes/components/settings-defs.ts +7 -0
- package/src/modes/components/status-line/context-thresholds.ts +11 -0
- package/src/modes/components/status-line/segments.ts +2 -2
- package/src/modes/components/tiny-title-download-progress.ts +90 -0
- package/src/modes/components/tips.txt +12 -0
- package/src/modes/components/tool-execution.ts +67 -3
- package/src/modes/components/tree-selector.ts +3 -3
- package/src/modes/components/user-message-selector.ts +3 -3
- package/src/modes/components/welcome.ts +55 -1
- package/src/modes/controllers/command-controller.ts +16 -1
- package/src/modes/controllers/extension-ui-controller.ts +3 -1
- package/src/modes/controllers/input-controller.ts +57 -0
- package/src/modes/gradient-highlight.ts +70 -0
- package/src/modes/interactive-mode.ts +58 -109
- package/src/modes/internal-url-autocomplete.ts +143 -0
- package/src/modes/orchestrate.ts +36 -0
- package/src/modes/prompt-action-autocomplete.ts +12 -0
- package/src/modes/ultrathink.ts +9 -53
- package/src/modes/utils/keybinding-matchers.ts +11 -0
- package/src/prompts/system/memory-consolidation-system.md +8 -0
- package/src/prompts/system/memory-extraction-system.md +26 -0
- package/src/prompts/{commands/orchestrate.md → system/orchestrate-notice.md} +5 -16
- package/src/prompts/system/system-prompt.md +2 -0
- package/src/prompts/system/tiny-title-system.md +8 -0
- package/src/prompts/tools/memory-edit.md +8 -0
- package/src/prompts/tools/task.md +4 -7
- package/src/sdk.ts +8 -6
- package/src/session/agent-session.ts +128 -44
- package/src/slash-commands/builtin-registry.ts +10 -1
- package/src/system-prompt.ts +4 -0
- package/src/task/commands.ts +1 -5
- package/src/task/executor.ts +8 -0
- package/src/task/index.ts +2 -0
- package/src/task/render.ts +69 -26
- package/src/tiny/models.ts +217 -0
- package/src/tiny/text.ts +19 -0
- package/src/tiny/title-client.ts +340 -0
- package/src/tiny/title-protocol.ts +51 -0
- package/src/tiny/worker.ts +523 -0
- package/src/tools/bash.ts +58 -16
- package/src/tools/browser/tab-worker.ts +1 -1
- package/src/tools/index.ts +17 -11
- package/src/tools/memory-edit.ts +59 -0
- package/src/tools/memory-recall.ts +100 -0
- package/src/tools/memory-reflect.ts +88 -0
- package/src/tools/memory-render.ts +185 -0
- package/src/tools/memory-retain.ts +91 -0
- package/src/tools/renderers.ts +4 -0
- package/src/tools/todo-write.ts +128 -29
- package/src/tools/tool-result.ts +8 -0
- package/src/utils/title-generator.ts +115 -13
- package/src/tools/hindsight-recall.ts +0 -69
- package/src/tools/hindsight-reflect.ts +0 -58
- package/src/tools/hindsight-retain.ts +0 -57
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import * as z from "zod/v4";
|
|
3
3
|
import type { ToolSession } from ".";
|
|
4
|
-
declare const
|
|
4
|
+
declare const memoryRetainSchema: z.ZodObject<{
|
|
5
5
|
items: z.ZodArray<z.ZodObject<{
|
|
6
6
|
content: z.ZodString;
|
|
7
7
|
context: z.ZodOptional<z.ZodString>;
|
|
8
8
|
}, z.core.$strip>>;
|
|
9
9
|
}, z.core.$strip>;
|
|
10
|
-
export type
|
|
11
|
-
export declare class
|
|
10
|
+
export type MemoryRetainParams = z.infer<typeof memoryRetainSchema>;
|
|
11
|
+
export declare class MemoryRetainTool implements AgentTool<typeof memoryRetainSchema> {
|
|
12
12
|
private readonly session;
|
|
13
13
|
readonly name = "retain";
|
|
14
14
|
readonly approval: "read";
|
|
@@ -22,9 +22,9 @@ export declare class HindsightRetainTool implements AgentTool<typeof hindsightRe
|
|
|
22
22
|
}, z.core.$strip>;
|
|
23
23
|
readonly strict = true;
|
|
24
24
|
readonly loadMode = "discoverable";
|
|
25
|
-
readonly summary = "Store important facts in
|
|
25
|
+
readonly summary = "Store important facts in long-term memory";
|
|
26
26
|
constructor(session: ToolSession);
|
|
27
|
-
static createIf(session: ToolSession):
|
|
28
|
-
execute(_id: string, params:
|
|
27
|
+
static createIf(session: ToolSession): MemoryRetainTool | null;
|
|
28
|
+
execute(_id: string, params: MemoryRetainParams): Promise<AgentToolResult>;
|
|
29
29
|
}
|
|
30
30
|
export {};
|
|
@@ -21,9 +21,14 @@ export interface TodoPhase {
|
|
|
21
21
|
name: string;
|
|
22
22
|
tasks: TodoItem[];
|
|
23
23
|
}
|
|
24
|
+
export interface TodoCompletionTransition {
|
|
25
|
+
phase: string;
|
|
26
|
+
content: string;
|
|
27
|
+
}
|
|
24
28
|
export interface TodoWriteToolDetails {
|
|
25
29
|
phases: TodoPhase[];
|
|
26
30
|
storage: "session" | "memory";
|
|
31
|
+
completedTasks?: TodoCompletionTransition[];
|
|
27
32
|
}
|
|
28
33
|
declare const todoWriteSchema: z.ZodObject<{
|
|
29
34
|
ops: z.ZodArray<z.ZodObject<{
|
|
@@ -137,6 +142,9 @@ type TodoWriteRenderArgs = {
|
|
|
137
142
|
export declare function phaseRomanNumeral(oneBasedIndex: number): string;
|
|
138
143
|
/** Display-only phase header: `I. Foundation`. State and prompts never see this. */
|
|
139
144
|
export declare function formatPhaseDisplayName(name: string, oneBasedIndex: number): string;
|
|
145
|
+
export declare const TODO_WRITE_STRIKE_HOLD_FRAMES = 2;
|
|
146
|
+
export declare const TODO_WRITE_STRIKE_REVEAL_FRAMES = 12;
|
|
147
|
+
export declare const TODO_WRITE_STRIKE_TOTAL_FRAMES: number;
|
|
140
148
|
export declare const todoWriteToolRenderer: {
|
|
141
149
|
renderCall(args: TodoWriteRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component;
|
|
142
150
|
renderResult(result: {
|
|
@@ -24,6 +24,8 @@ export declare class ToolResultBuilder<TDetails extends DetailsWithMeta> {
|
|
|
24
24
|
sourcePath(value: string): this;
|
|
25
25
|
sourceInternal(value: string): this;
|
|
26
26
|
diagnostics(summary: string, messages: string[]): this;
|
|
27
|
+
/** Flag the result as a non-throwing failure (agent-loop surfaces it as a tool error). */
|
|
28
|
+
error(value?: boolean): this;
|
|
27
29
|
done(): AgentToolResult<TDetails>;
|
|
28
30
|
}
|
|
29
31
|
export declare function toolResult<TDetails extends DetailsWithMeta>(details?: TDetails): ToolResultBuilder<TDetails>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type Api, type Model } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import type { ModelRegistry } from "../config/model-registry";
|
|
3
3
|
import type { Settings } from "../config/settings";
|
|
4
|
+
export declare const TITLE_LOCAL_FALLBACK_DELAY_MS = 10000;
|
|
5
|
+
export declare function raceFirstNonNull<T>(primary: Promise<T | null>, startFallback: () => Promise<T | null>, delayMs?: number, onPrimaryWinAfterFallback?: () => void): Promise<T | null>;
|
|
4
6
|
/**
|
|
5
7
|
* Generate a title for a session based on the first user message.
|
|
6
8
|
*
|
|
@@ -15,6 +17,7 @@ import type { Settings } from "../config/settings";
|
|
|
15
17
|
* reflects the credential actually selected for this request.
|
|
16
18
|
*/
|
|
17
19
|
export declare function generateSessionTitle(firstMessage: string, registry: ModelRegistry, settings: Settings, sessionId?: string, currentModel?: Model<Api>, metadataResolver?: (provider: string) => Record<string, unknown> | undefined): Promise<string | null>;
|
|
20
|
+
export declare function generateTitleOnline(firstMessage: string, registry: ModelRegistry, settings: Settings, sessionId?: string, currentModel?: Model<Api>, metadataResolver?: (provider: string) => Record<string, unknown> | undefined, signal?: AbortSignal): Promise<string | null>;
|
|
18
21
|
export declare function formatSessionTerminalTitle(sessionName: string | undefined, cwd?: string): string;
|
|
19
22
|
/**
|
|
20
23
|
* Set the terminal title using OSC 0 (sets both tab and window title). Unsupported terminals ignore it.
|
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": "15.
|
|
4
|
+
"version": "15.6.0",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -44,31 +44,35 @@
|
|
|
44
44
|
"generate-template": "bun scripts/generate-template.ts"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@agentclientprotocol/sdk": "0.
|
|
48
|
-
"@babel/parser": "^7.29.
|
|
47
|
+
"@agentclientprotocol/sdk": "0.22.1",
|
|
48
|
+
"@babel/parser": "^7.29.7",
|
|
49
49
|
"@mozilla/readability": "^0.6.0",
|
|
50
|
-
"@oh-my-pi/hashline": "15.
|
|
51
|
-
"@oh-my-pi/omp-stats": "15.
|
|
52
|
-
"@oh-my-pi/pi-agent-core": "15.
|
|
53
|
-
"@oh-my-pi/pi-ai": "15.
|
|
54
|
-
"@oh-my-pi/pi-
|
|
55
|
-
"@oh-my-pi/pi-
|
|
56
|
-
"@oh-my-pi/pi-
|
|
57
|
-
"@
|
|
50
|
+
"@oh-my-pi/hashline": "15.6.0",
|
|
51
|
+
"@oh-my-pi/omp-stats": "15.6.0",
|
|
52
|
+
"@oh-my-pi/pi-agent-core": "15.6.0",
|
|
53
|
+
"@oh-my-pi/pi-ai": "15.6.0",
|
|
54
|
+
"@oh-my-pi/pi-mnemosyne": "15.6.0",
|
|
55
|
+
"@oh-my-pi/pi-natives": "15.6.0",
|
|
56
|
+
"@oh-my-pi/pi-tui": "15.6.0",
|
|
57
|
+
"@oh-my-pi/pi-utils": "15.6.0",
|
|
58
|
+
"@puppeteer/browsers": "^3.0.4",
|
|
58
59
|
"@types/turndown": "5.0.6",
|
|
59
60
|
"@xterm/headless": "^6.0.0",
|
|
60
61
|
"chalk": "^5.6.2",
|
|
61
62
|
"diff": "^9.0.0",
|
|
62
|
-
"fflate": "0.8.
|
|
63
|
+
"fflate": "0.8.3",
|
|
63
64
|
"handlebars": "^4.7.9",
|
|
64
65
|
"linkedom": "^0.18.12",
|
|
65
|
-
"lru-cache": "11.
|
|
66
|
+
"lru-cache": "11.5.1",
|
|
66
67
|
"markit-ai": "0.5.3",
|
|
67
|
-
"puppeteer-core": "^
|
|
68
|
+
"puppeteer-core": "^25.1.0",
|
|
68
69
|
"turndown": "7.2.4",
|
|
69
70
|
"turndown-plugin-gfm": "1.0.2",
|
|
70
71
|
"zod": "4.4.3"
|
|
71
72
|
},
|
|
73
|
+
"optionalDependencies": {
|
|
74
|
+
"@huggingface/transformers": "^4.2.0"
|
|
75
|
+
},
|
|
72
76
|
"devDependencies": {
|
|
73
77
|
"@types/bun": "^1.3.14"
|
|
74
78
|
},
|
package/scripts/build-binary.ts
CHANGED
|
@@ -56,6 +56,7 @@ async function main(): Promise<void> {
|
|
|
56
56
|
"../stats/src/sync-worker.ts",
|
|
57
57
|
"./src/tools/browser/tab-worker-entry.ts",
|
|
58
58
|
"./src/eval/js/worker-entry.ts",
|
|
59
|
+
"./src/tiny/worker.ts",
|
|
59
60
|
// Legacy pi-* extension compat entrypoints served by
|
|
60
61
|
// `legacy-pi-compat.ts`. These are reached via computed bunfs paths
|
|
61
62
|
// (which `--compile`'s static analyzer cannot trace), so each must be
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { formatBytes } from "@oh-my-pi/pi-utils";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_TINY_TITLE_LOCAL_MODEL_KEY,
|
|
5
|
+
getTinyLocalModelSpec,
|
|
6
|
+
isTinyLocalModelKey,
|
|
7
|
+
TINY_LOCAL_MODELS,
|
|
8
|
+
type TinyLocalModelKey,
|
|
9
|
+
} from "../tiny/models";
|
|
10
|
+
import { shutdownTinyTitleClient, tinyTitleClient } from "../tiny/title-client";
|
|
11
|
+
import type { TinyTitleProgressEvent } from "../tiny/title-protocol";
|
|
12
|
+
|
|
13
|
+
export type TinyModelsAction = "download" | "list";
|
|
14
|
+
|
|
15
|
+
export interface TinyModelsCommandArgs {
|
|
16
|
+
action: TinyModelsAction;
|
|
17
|
+
model?: string;
|
|
18
|
+
flags: {
|
|
19
|
+
json?: boolean;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ProgressReporter {
|
|
24
|
+
onProgress(event: TinyTitleProgressEvent): void;
|
|
25
|
+
finish(ok: boolean): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface DownloadResult {
|
|
29
|
+
model: TinyLocalModelKey;
|
|
30
|
+
ok: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function writeLine(text = ""): void {
|
|
34
|
+
process.stdout.write(`${text}\n`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveModels(model: string | undefined): TinyLocalModelKey[] {
|
|
38
|
+
if (!model) return [DEFAULT_TINY_TITLE_LOCAL_MODEL_KEY];
|
|
39
|
+
if (model === "all") return TINY_LOCAL_MODELS.map(spec => spec.key);
|
|
40
|
+
if (!isTinyLocalModelKey(model)) {
|
|
41
|
+
const values = TINY_LOCAL_MODELS.map(spec => spec.key).join(", ");
|
|
42
|
+
throw new Error(`Unknown tiny local model: ${model}. Expected one of: ${values}, all`);
|
|
43
|
+
}
|
|
44
|
+
return [model];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function listModels(json: boolean | undefined): void {
|
|
48
|
+
if (json) {
|
|
49
|
+
writeLine(JSON.stringify({ models: TINY_LOCAL_MODELS }));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
writeLine(chalk.bold("Tiny local models"));
|
|
53
|
+
for (const spec of TINY_LOCAL_MODELS) {
|
|
54
|
+
const defaultMark = spec.key === DEFAULT_TINY_TITLE_LOCAL_MODEL_KEY ? chalk.cyan(" default") : "";
|
|
55
|
+
writeLine(`${chalk.cyan(spec.key)}${defaultMark}`);
|
|
56
|
+
writeLine(` ${spec.label} — ${spec.description}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function makeProgressReporter(modelKey: TinyLocalModelKey, json: boolean | undefined): ProgressReporter {
|
|
61
|
+
if (json || !process.stdout.isTTY) {
|
|
62
|
+
return { onProgress: () => undefined, finish: () => undefined };
|
|
63
|
+
}
|
|
64
|
+
const label = getTinyLocalModelSpec(modelKey)?.label ?? modelKey;
|
|
65
|
+
let lastWidth = 0;
|
|
66
|
+
let lastProgress = -1;
|
|
67
|
+
const render = (event: TinyTitleProgressEvent): void => {
|
|
68
|
+
const progress = event.progress ?? lastProgress;
|
|
69
|
+
if (progress >= 0 && progress < lastProgress + 1 && event.status !== "ready") return;
|
|
70
|
+
if (progress >= 0) lastProgress = progress;
|
|
71
|
+
const ratio = progress >= 0 ? Math.max(0, Math.min(1, progress / 100)) : 0;
|
|
72
|
+
const barWidth = 30;
|
|
73
|
+
const filled = Math.round(ratio * barWidth);
|
|
74
|
+
const bar = `${"█".repeat(filled)}${"░".repeat(barWidth - filled)}`;
|
|
75
|
+
const pct = progress >= 0 ? `${Math.floor(progress).toString().padStart(3, " ")}%` : " --%";
|
|
76
|
+
const bytes = event.loaded && event.total ? ` ${formatBytes(event.loaded)}/${formatBytes(event.total)}` : "";
|
|
77
|
+
const file = event.file ? ` ${event.file.split("/").at(-1) ?? event.file}` : "";
|
|
78
|
+
const statusLabel = event.status === "ready" ? "Ready" : "Downloading";
|
|
79
|
+
const line = `${chalk.cyan(statusLabel)} ${label} [${bar}] ${pct}${bytes}${file}`;
|
|
80
|
+
process.stdout.write(`\r${line.padEnd(lastWidth)}`);
|
|
81
|
+
lastWidth = line.length;
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
onProgress(event) {
|
|
85
|
+
if (event.modelKey !== modelKey) return;
|
|
86
|
+
render(event);
|
|
87
|
+
},
|
|
88
|
+
finish(ok) {
|
|
89
|
+
const suffix = ok ? chalk.green("done") : chalk.red("failed");
|
|
90
|
+
process.stdout.write(`\r${`${label}: ${suffix}`.padEnd(lastWidth)}\n`);
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function downloadOne(modelKey: TinyLocalModelKey, json: boolean | undefined): Promise<DownloadResult> {
|
|
96
|
+
const label = getTinyLocalModelSpec(modelKey)?.label ?? modelKey;
|
|
97
|
+
if (!json && !process.stdout.isTTY) writeLine(`Downloading ${label} (${modelKey})...`);
|
|
98
|
+
const progress = makeProgressReporter(modelKey, json);
|
|
99
|
+
const ok = await tinyTitleClient.downloadModel(modelKey, { onProgress: progress.onProgress });
|
|
100
|
+
progress.finish(ok);
|
|
101
|
+
if (!json && !process.stdout.isTTY) writeLine(ok ? `Downloaded ${label}.` : `Failed to download ${label}.`);
|
|
102
|
+
return { model: modelKey, ok };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function runTinyModelsCommand(command: TinyModelsCommandArgs): Promise<void> {
|
|
106
|
+
if (command.action === "list") {
|
|
107
|
+
listModels(command.flags.json);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const models = resolveModels(command.model);
|
|
112
|
+
const results: DownloadResult[] = [];
|
|
113
|
+
try {
|
|
114
|
+
for (const model of models) {
|
|
115
|
+
results.push(await downloadOne(model, command.flags.json));
|
|
116
|
+
}
|
|
117
|
+
} finally {
|
|
118
|
+
await shutdownTinyTitleClient();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (command.flags.json) {
|
|
122
|
+
writeLine(JSON.stringify({ results }));
|
|
123
|
+
}
|
|
124
|
+
if (results.some(result => !result.ok)) {
|
|
125
|
+
throw new Error("One or more tiny title models failed to download");
|
|
126
|
+
}
|
|
127
|
+
}
|
package/src/cli-commands.ts
CHANGED
|
@@ -28,6 +28,7 @@ export const commands: CommandEntry[] = [
|
|
|
28
28
|
{ name: "ssh", load: () => import("./commands/ssh").then(m => m.default) },
|
|
29
29
|
{ name: "stats", load: () => import("./commands/stats").then(m => m.default) },
|
|
30
30
|
{ name: "update", load: () => import("./commands/update").then(m => m.default) },
|
|
31
|
+
{ name: "tiny-models", load: () => import("./commands/tiny-models").then(m => m.default) },
|
|
31
32
|
{ name: "worktree", load: () => import("./commands/worktree").then(m => m.default), aliases: ["wt"] },
|
|
32
33
|
{ name: "search", load: () => import("./commands/web-search").then(m => m.default), aliases: ["q"] },
|
|
33
34
|
];
|
package/src/cli.ts
CHANGED
|
@@ -32,20 +32,20 @@ async function showHelp(config: CliConfig): Promise<void> {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
* Smoke-test entry. Spawns
|
|
35
|
+
* Smoke-test entry. Spawns bundled workers, pings them, exits.
|
|
36
36
|
*
|
|
37
37
|
* Purpose: catch the silent worker-load regressions that hit compiled
|
|
38
|
-
* binaries (issues #1011 and #1027).
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* `scripts/install-tests/run-ci.sh` so binary / source-link / tarball
|
|
44
|
-
* installs all exercise it on every CI run.
|
|
38
|
+
* binaries (issues #1011 and #1027). Version/help paths do not spawn worker
|
|
39
|
+
* modules on a fresh install, so this probe is the minimal end-to-end test
|
|
40
|
+
* that proves `new Worker(...)` resolves and bundled worker modules evaluate.
|
|
41
|
+
* Wired into `scripts/install-tests/run-ci.sh` so binary / source-link /
|
|
42
|
+
* tarball installs all exercise it on every CI run.
|
|
45
43
|
*/
|
|
46
44
|
async function runSmokeTest(): Promise<void> {
|
|
47
45
|
const { smokeTestSyncWorker } = await import("@oh-my-pi/omp-stats");
|
|
46
|
+
const { smokeTestTinyTitleWorker } = await import("./tiny/title-client");
|
|
48
47
|
await smokeTestSyncWorker();
|
|
48
|
+
await smokeTestTinyTitleWorker();
|
|
49
49
|
process.stdout.write("smoke-test: ok\n");
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
2
|
+
import { runTinyModelsCommand, type TinyModelsAction, type TinyModelsCommandArgs } from "../cli/tiny-models-cli";
|
|
3
|
+
|
|
4
|
+
const ACTIONS: TinyModelsAction[] = ["download", "list"];
|
|
5
|
+
|
|
6
|
+
export default class TinyModels extends Command {
|
|
7
|
+
static description = "Download tiny local models (session titles + memory)";
|
|
8
|
+
|
|
9
|
+
static args = {
|
|
10
|
+
action: Args.string({
|
|
11
|
+
description: "Action to perform",
|
|
12
|
+
required: false,
|
|
13
|
+
options: ACTIONS,
|
|
14
|
+
}),
|
|
15
|
+
model: Args.string({
|
|
16
|
+
description: "Model key, or all",
|
|
17
|
+
required: false,
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
static flags = {
|
|
22
|
+
json: Flags.boolean({ description: "Output JSON" }),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
async run(): Promise<void> {
|
|
26
|
+
const { args, flags } = await this.parse(TinyModels);
|
|
27
|
+
const command: TinyModelsCommandArgs = {
|
|
28
|
+
action: (args.action ?? "download") as TinyModelsAction,
|
|
29
|
+
model: args.model,
|
|
30
|
+
flags: {
|
|
31
|
+
json: flags.json,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
await runTinyModelsCommand(command);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { type Api, getBundledModels, getBundledProviders, type Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import {
|
|
3
|
+
getBracketStrippedModelIdCandidates,
|
|
4
|
+
getLongestModelLikeIdSegment,
|
|
5
|
+
getModelLikeIdSegments,
|
|
6
|
+
} from "./model-id-affixes";
|
|
2
7
|
|
|
3
8
|
export type CanonicalModelSource = "override" | "bundled" | "heuristic" | "fallback";
|
|
4
9
|
|
|
@@ -29,6 +34,7 @@ export interface CanonicalModelIndex {
|
|
|
29
34
|
interface CanonicalReferenceData {
|
|
30
35
|
references: Map<string, Model<Api>>;
|
|
31
36
|
officialIds: Set<string>;
|
|
37
|
+
suffixAliases: Map<string, string>;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
interface CompiledEquivalenceConfig {
|
|
@@ -70,6 +76,26 @@ function shouldReplaceReference(existing: Model<Api> | undefined, candidate: Mod
|
|
|
70
76
|
return existing.provider !== "openai" && candidate.provider === "openai";
|
|
71
77
|
}
|
|
72
78
|
|
|
79
|
+
function buildCanonicalSuffixAliasMap(references: ReadonlyMap<string, Model<Api>>): Map<string, string> {
|
|
80
|
+
const aliases = new Map<string, string>();
|
|
81
|
+
for (const reference of references.values()) {
|
|
82
|
+
const slashIndex = reference.id.lastIndexOf("/");
|
|
83
|
+
if (slashIndex === -1) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const suffix = reference.id.slice(slashIndex + 1);
|
|
87
|
+
const alias = getLongestModelLikeIdSegment(suffix);
|
|
88
|
+
if (!alias) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const existing = aliases.get(alias);
|
|
92
|
+
if (!existing || compareCandidatePreference(reference.id, existing) < 0) {
|
|
93
|
+
aliases.set(alias, reference.id);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return new Map([...aliases.entries()].map(([alias, referenceId]) => [normalizeCanonicalIdKey(alias), referenceId]));
|
|
97
|
+
}
|
|
98
|
+
|
|
73
99
|
function createCanonicalReferenceData(): CanonicalReferenceData {
|
|
74
100
|
if (referenceDataCache) {
|
|
75
101
|
return referenceDataCache;
|
|
@@ -85,9 +111,11 @@ function createCanonicalReferenceData(): CanonicalReferenceData {
|
|
|
85
111
|
}
|
|
86
112
|
}
|
|
87
113
|
const officialIds = new Set(references.keys());
|
|
114
|
+
const suffixAliases = buildCanonicalSuffixAliasMap(references);
|
|
88
115
|
referenceDataCache = {
|
|
89
116
|
references: Object.freeze(references) as Map<string, Model<Api>>,
|
|
90
117
|
officialIds: Object.freeze(officialIds) as Set<string>,
|
|
118
|
+
suffixAliases: Object.freeze(suffixAliases) as Map<string, string>,
|
|
91
119
|
};
|
|
92
120
|
return referenceDataCache;
|
|
93
121
|
}
|
|
@@ -590,6 +618,13 @@ function expandHeavyCanonicalCandidates(normalized: string, queue: string[]): vo
|
|
|
590
618
|
queue.push(wrapperCandidate);
|
|
591
619
|
}
|
|
592
620
|
|
|
621
|
+
for (const strippedAffixCandidate of getBracketStrippedModelIdCandidates(normalized)) {
|
|
622
|
+
queue.push(strippedAffixCandidate);
|
|
623
|
+
}
|
|
624
|
+
for (const segment of getModelLikeIdSegments(normalized)) {
|
|
625
|
+
queue.push(segment);
|
|
626
|
+
}
|
|
627
|
+
|
|
593
628
|
const strippedSyntheticPrefix = stripSyntheticPrefix(normalized);
|
|
594
629
|
if (strippedSyntheticPrefix) {
|
|
595
630
|
queue.push(strippedSyntheticPrefix);
|
|
@@ -718,9 +753,15 @@ function resolveCanonicalIdForModel(
|
|
|
718
753
|
}
|
|
719
754
|
|
|
720
755
|
const heuristicCandidates = getHeuristicCanonicalCandidates(model.id, referenceData.officialIds);
|
|
721
|
-
const officialMatches = heuristicCandidates.filter(candidate => referenceData.officialIds.has(candidate));
|
|
756
|
+
const officialMatches = new Set(heuristicCandidates.filter(candidate => referenceData.officialIds.has(candidate)));
|
|
757
|
+
for (const candidate of heuristicCandidates) {
|
|
758
|
+
const aliased = referenceData.suffixAliases.get(normalizeCanonicalIdKey(candidate));
|
|
759
|
+
if (aliased) {
|
|
760
|
+
officialMatches.add(aliased);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
722
763
|
const preferredFallback = getPreferredFallbackCanonicalCandidate(model.id, heuristicCandidates);
|
|
723
|
-
const match = selectBestOfficialCandidate(officialMatches);
|
|
764
|
+
const match = selectBestOfficialCandidate([...officialMatches]);
|
|
724
765
|
if (match) {
|
|
725
766
|
if (
|
|
726
767
|
preferredFallback &&
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const LEADING_BRACKETED_AFFIX_PATTERN = /^(?:\s*(?:\[|【)[^\]】]+(?:\]|】)\s*)+/u;
|
|
2
|
+
const TRAILING_BRACKETED_AFFIX_PATTERN = /(?:\s*(?:\[|【)[^\]】]+(?:\]|】)\s*)+$/u;
|
|
3
|
+
const MODEL_ID_SEGMENT_PATTERN = /[a-z0-9.:-]+/g;
|
|
4
|
+
const MODEL_FAMILY_PREFIX_PATTERN =
|
|
5
|
+
/^(claude|gemini|gpt|grok|glm|qwen|deepseek|kimi|mimo|doubao|ernie|gpt-oss|gemma|minimax|step|command|jamba|llama|o[1345])/i;
|
|
6
|
+
|
|
7
|
+
function hasDigit(value: string): boolean {
|
|
8
|
+
return /\d/.test(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function compareSegmentPreference(left: string, right: string): number {
|
|
12
|
+
if (left.length !== right.length) {
|
|
13
|
+
return right.length - left.length;
|
|
14
|
+
}
|
|
15
|
+
return left.localeCompare(right);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getModelLikeIdSegments(modelId: string): string[] {
|
|
19
|
+
const normalized = normalizeModelIdWhitespace(modelId).toLowerCase();
|
|
20
|
+
if (!normalized) return [];
|
|
21
|
+
const segments = (normalized.match(MODEL_ID_SEGMENT_PATTERN) ?? []).filter(
|
|
22
|
+
segment => MODEL_FAMILY_PREFIX_PATTERN.test(segment) && hasDigit(segment),
|
|
23
|
+
);
|
|
24
|
+
const unique = [...new Set(segments)];
|
|
25
|
+
unique.sort(compareSegmentPreference);
|
|
26
|
+
return unique;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getLongestModelLikeIdSegment(modelId: string): string | undefined {
|
|
30
|
+
return getModelLikeIdSegments(modelId)[0];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function normalizeModelIdWhitespace(value: string): string {
|
|
34
|
+
return value.trim().replace(/\s+/g, " ");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Strip reseller / wrapper tags that are injected as bracketed affixes around an
|
|
39
|
+
* upstream model id, e.g.
|
|
40
|
+
* "[Kiro] claude-opus-4-8" -> "claude-opus-4-8"
|
|
41
|
+
* "[gcli转] gemini-3.1-pro-preview [假流]" -> "gemini-3.1-pro-preview"
|
|
42
|
+
*/
|
|
43
|
+
export function getBracketStrippedModelIdCandidates(modelId: string): string[] {
|
|
44
|
+
const normalized = normalizeModelIdWhitespace(modelId);
|
|
45
|
+
if (!normalized) return [];
|
|
46
|
+
|
|
47
|
+
const candidates = new Set<string>();
|
|
48
|
+
const withoutLeading = normalizeModelIdWhitespace(normalized.replace(LEADING_BRACKETED_AFFIX_PATTERN, ""));
|
|
49
|
+
const withoutTrailing = normalizeModelIdWhitespace(normalized.replace(TRAILING_BRACKETED_AFFIX_PATTERN, ""));
|
|
50
|
+
const withoutBoth = normalizeModelIdWhitespace(
|
|
51
|
+
normalized.replace(LEADING_BRACKETED_AFFIX_PATTERN, "").replace(TRAILING_BRACKETED_AFFIX_PATTERN, ""),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
for (const candidate of [withoutBoth, withoutLeading, withoutTrailing]) {
|
|
55
|
+
if (candidate && candidate !== normalized) {
|
|
56
|
+
candidates.add(candidate);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return [...candidates];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function stripBracketedModelIdAffixes(modelId: string): string | undefined {
|
|
63
|
+
return getBracketStrippedModelIdCandidates(modelId)[0];
|
|
64
|
+
}
|