@oh-my-pi/pi-coding-agent 15.5.13 → 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.
Files changed (192) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/dist/types/cli/classify-install-target.d.ts +0 -10
  3. package/dist/types/cli/initial-message.d.ts +1 -1
  4. package/dist/types/cli/tiny-models-cli.d.ts +9 -0
  5. package/dist/types/commands/tiny-models.d.ts +22 -0
  6. package/dist/types/commit/analysis/conventional.d.ts +1 -1
  7. package/dist/types/commit/analysis/summary.d.ts +1 -1
  8. package/dist/types/commit/changelog/generate.d.ts +1 -1
  9. package/dist/types/commit/changelog/index.d.ts +2 -2
  10. package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
  11. package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
  12. package/dist/types/config/model-id-affixes.d.ts +10 -0
  13. package/dist/types/config/model-registry.d.ts +1 -1
  14. package/dist/types/config/models-config-schema.d.ts +2 -0
  15. package/dist/types/config/settings-schema.d.ts +233 -17
  16. package/dist/types/discovery/helpers.d.ts +1 -1
  17. package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
  18. package/dist/types/eval/__tests__/llm-bridge.test.d.ts +1 -0
  19. package/dist/types/eval/js/shared/rewrite-imports.d.ts +16 -1
  20. package/dist/types/eval/llm-bridge.d.ts +25 -0
  21. package/dist/types/export/html/template.generated.d.ts +1 -1
  22. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +15 -0
  23. package/dist/types/internal-urls/agent-protocol.d.ts +2 -1
  24. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -1
  25. package/dist/types/internal-urls/local-protocol.d.ts +2 -1
  26. package/dist/types/internal-urls/memory-protocol.d.ts +2 -1
  27. package/dist/types/internal-urls/omp-protocol.d.ts +2 -1
  28. package/dist/types/internal-urls/router.d.ts +8 -1
  29. package/dist/types/internal-urls/rule-protocol.d.ts +2 -1
  30. package/dist/types/internal-urls/skill-protocol.d.ts +2 -1
  31. package/dist/types/internal-urls/types.d.ts +26 -0
  32. package/dist/types/memory-backend/index.d.ts +1 -0
  33. package/dist/types/memory-backend/resolve.d.ts +2 -1
  34. package/dist/types/memory-backend/types.d.ts +7 -1
  35. package/dist/types/mnemosyne/backend.d.ts +4 -0
  36. package/dist/types/mnemosyne/config.d.ts +29 -0
  37. package/dist/types/mnemosyne/index.d.ts +3 -0
  38. package/dist/types/mnemosyne/state.d.ts +72 -0
  39. package/dist/types/modes/components/custom-editor.d.ts +2 -3
  40. package/dist/types/modes/components/hook-selector.d.ts +27 -0
  41. package/dist/types/modes/components/index.d.ts +1 -0
  42. package/dist/types/modes/components/status-line/context-thresholds.d.ts +6 -0
  43. package/dist/types/modes/components/tiny-title-download-progress.d.ts +11 -0
  44. package/dist/types/modes/components/welcome.d.ts +1 -0
  45. package/dist/types/modes/controllers/extension-ui-controller.d.ts +4 -1
  46. package/dist/types/modes/gradient-highlight.d.ts +23 -0
  47. package/dist/types/modes/interactive-mode.d.ts +4 -2
  48. package/dist/types/modes/internal-url-autocomplete.d.ts +43 -0
  49. package/dist/types/modes/orchestrate.d.ts +10 -0
  50. package/dist/types/modes/theme/defaults/index.d.ts +8406 -8406
  51. package/dist/types/modes/theme/theme.d.ts +2 -1
  52. package/dist/types/modes/ultrathink.d.ts +3 -3
  53. package/dist/types/modes/utils/keybinding-matchers.d.ts +5 -0
  54. package/dist/types/sdk.d.ts +3 -0
  55. package/dist/types/session/agent-session.d.ts +35 -0
  56. package/dist/types/system-prompt.d.ts +2 -0
  57. package/dist/types/task/executor.d.ts +2 -0
  58. package/dist/types/task/render.d.ts +5 -1
  59. package/dist/types/tiny/models.d.ts +185 -0
  60. package/dist/types/tiny/text.d.ts +4 -0
  61. package/dist/types/tiny/title-client.d.ts +24 -0
  62. package/dist/types/tiny/title-protocol.d.ts +74 -0
  63. package/dist/types/tiny/worker.d.ts +2 -0
  64. package/dist/types/tools/bash.d.ts +3 -1
  65. package/dist/types/tools/index.d.ts +7 -4
  66. package/dist/types/tools/memory-edit.d.ts +40 -0
  67. package/dist/types/tools/{hindsight-recall.d.ts → memory-recall.d.ts} +6 -6
  68. package/dist/types/tools/{hindsight-reflect.d.ts → memory-reflect.d.ts} +6 -6
  69. package/dist/types/tools/memory-render.d.ts +60 -0
  70. package/dist/types/tools/{hindsight-retain.d.ts → memory-retain.d.ts} +6 -6
  71. package/dist/types/tools/todo-write.d.ts +8 -0
  72. package/dist/types/tools/tool-result.d.ts +2 -0
  73. package/dist/types/utils/title-generator.d.ts +3 -0
  74. package/package.json +18 -14
  75. package/scripts/build-binary.ts +1 -0
  76. package/src/cli/tiny-models-cli.ts +127 -0
  77. package/src/cli-commands.ts +1 -0
  78. package/src/cli.ts +8 -8
  79. package/src/commands/tiny-models.ts +36 -0
  80. package/src/config/model-equivalence.ts +43 -2
  81. package/src/config/model-id-affixes.ts +64 -0
  82. package/src/config/model-registry.ts +166 -8
  83. package/src/config/models-config-schema.ts +1 -1
  84. package/src/config/settings-schema.ts +206 -14
  85. package/src/edit/hashline/diff.ts +5 -7
  86. package/src/eval/__tests__/llm-bridge.test.ts +297 -0
  87. package/src/eval/__tests__/shared-executors.test.ts +36 -0
  88. package/src/eval/js/shared/local-module-loader.ts +13 -1
  89. package/src/eval/js/shared/prelude.txt +8 -0
  90. package/src/eval/js/shared/rewrite-imports.ts +31 -26
  91. package/src/eval/js/tool-bridge.ts +4 -0
  92. package/src/eval/llm-bridge.ts +181 -0
  93. package/src/eval/py/prelude.py +52 -31
  94. package/src/export/html/template.generated.ts +1 -1
  95. package/src/export/html/template.js +0 -13
  96. package/src/extensibility/plugins/legacy-pi-compat.ts +60 -23
  97. package/src/internal-urls/agent-protocol.ts +18 -1
  98. package/src/internal-urls/artifact-protocol.ts +19 -1
  99. package/src/internal-urls/docs-index.generated.ts +5 -4
  100. package/src/internal-urls/local-protocol.ts +14 -1
  101. package/src/internal-urls/memory-protocol.ts +6 -1
  102. package/src/internal-urls/omp-protocol.ts +5 -1
  103. package/src/internal-urls/router.ts +20 -1
  104. package/src/internal-urls/rule-protocol.ts +8 -1
  105. package/src/internal-urls/skill-protocol.ts +8 -1
  106. package/src/internal-urls/types.ts +27 -0
  107. package/src/lsp/render.ts +1 -1
  108. package/src/main.ts +4 -0
  109. package/src/mcp/oauth-flow.ts +2 -2
  110. package/src/memory-backend/index.ts +1 -0
  111. package/src/memory-backend/resolve.ts +4 -1
  112. package/src/memory-backend/types.ts +8 -1
  113. package/src/mnemosyne/backend.ts +374 -0
  114. package/src/mnemosyne/config.ts +160 -0
  115. package/src/mnemosyne/index.ts +3 -0
  116. package/src/mnemosyne/state.ts +548 -0
  117. package/src/modes/acp/acp-agent.ts +11 -6
  118. package/src/modes/components/agent-dashboard.ts +4 -4
  119. package/src/modes/components/custom-editor.ts +3 -2
  120. package/src/modes/components/diff.ts +2 -2
  121. package/src/modes/components/extensions/extension-list.ts +3 -2
  122. package/src/modes/components/footer.ts +5 -6
  123. package/src/modes/components/history-search.ts +3 -3
  124. package/src/modes/components/hook-selector.ts +94 -8
  125. package/src/modes/components/index.ts +1 -0
  126. package/src/modes/components/mcp-add-wizard.ts +3 -3
  127. package/src/modes/components/model-selector.ts +124 -26
  128. package/src/modes/components/oauth-selector.ts +3 -3
  129. package/src/modes/components/session-observer-overlay.ts +19 -13
  130. package/src/modes/components/session-selector.ts +3 -3
  131. package/src/modes/components/settings-defs.ts +7 -0
  132. package/src/modes/components/status-line/context-thresholds.ts +11 -0
  133. package/src/modes/components/status-line/presets.ts +1 -0
  134. package/src/modes/components/status-line/segments.ts +25 -2
  135. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  136. package/src/modes/components/tips.txt +12 -0
  137. package/src/modes/components/tool-execution.ts +67 -3
  138. package/src/modes/components/tree-selector.ts +3 -3
  139. package/src/modes/components/user-message-selector.ts +3 -3
  140. package/src/modes/components/welcome.ts +55 -1
  141. package/src/modes/controllers/command-controller.ts +16 -1
  142. package/src/modes/controllers/extension-ui-controller.ts +3 -1
  143. package/src/modes/controllers/input-controller.ts +57 -0
  144. package/src/modes/gradient-highlight.ts +70 -0
  145. package/src/modes/interactive-mode.ts +80 -196
  146. package/src/modes/internal-url-autocomplete.ts +143 -0
  147. package/src/modes/orchestrate.ts +36 -0
  148. package/src/modes/prompt-action-autocomplete.ts +12 -0
  149. package/src/modes/theme/theme.ts +7 -0
  150. package/src/modes/ultrathink.ts +9 -53
  151. package/src/modes/utils/keybinding-matchers.ts +11 -0
  152. package/src/prompts/system/memory-consolidation-system.md +8 -0
  153. package/src/prompts/system/memory-extraction-system.md +26 -0
  154. package/src/prompts/{commands/orchestrate.md → system/orchestrate-notice.md} +5 -16
  155. package/src/prompts/system/system-prompt.md +2 -0
  156. package/src/prompts/system/tiny-title-system.md +8 -0
  157. package/src/prompts/tools/eval.md +2 -0
  158. package/src/prompts/tools/memory-edit.md +8 -0
  159. package/src/prompts/tools/task.md +4 -7
  160. package/src/sdk.ts +8 -6
  161. package/src/session/agent-session.ts +147 -44
  162. package/src/session/session-manager.ts +47 -0
  163. package/src/slash-commands/builtin-registry.ts +10 -1
  164. package/src/system-prompt.ts +4 -0
  165. package/src/task/commands.ts +1 -5
  166. package/src/task/executor.ts +8 -0
  167. package/src/task/index.ts +2 -0
  168. package/src/task/render.ts +69 -26
  169. package/src/tiny/models.ts +217 -0
  170. package/src/tiny/text.ts +19 -0
  171. package/src/tiny/title-client.ts +340 -0
  172. package/src/tiny/title-protocol.ts +51 -0
  173. package/src/tiny/worker.ts +523 -0
  174. package/src/tools/bash.ts +58 -16
  175. package/src/tools/browser/tab-worker.ts +1 -1
  176. package/src/tools/eval.ts +24 -48
  177. package/src/tools/index.ts +17 -15
  178. package/src/tools/memory-edit.ts +59 -0
  179. package/src/tools/memory-recall.ts +100 -0
  180. package/src/tools/memory-reflect.ts +88 -0
  181. package/src/tools/memory-render.ts +185 -0
  182. package/src/tools/memory-retain.ts +91 -0
  183. package/src/tools/renderers.ts +4 -2
  184. package/src/tools/todo-write.ts +128 -29
  185. package/src/tools/tool-result.ts +8 -0
  186. package/src/utils/title-generator.ts +115 -13
  187. package/dist/types/tools/calculator.d.ts +0 -77
  188. package/src/prompts/tools/calculator.md +0 -10
  189. package/src/tools/calculator.ts +0 -541
  190. package/src/tools/hindsight-recall.ts +0 -69
  191. package/src/tools/hindsight-reflect.ts +0 -58
  192. package/src/tools/hindsight-retain.ts +0 -57
@@ -10,6 +10,7 @@ import { Container, Text } from "@oh-my-pi/pi-tui";
10
10
  import { formatNumber } from "@oh-my-pi/pi-utils";
11
11
  import { settings } from "../config/settings";
12
12
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
13
+ import { formatContextUsage } from "../modes/components/status-line/context-thresholds";
13
14
  import type { Theme } from "../modes/theme/theme";
14
15
  import {
15
16
  formatBadge,
@@ -29,7 +30,7 @@ import {
29
30
  } from "../tools/review";
30
31
  import { Ellipsis, Hasher, type RenderCache, renderStatusLine } from "../tui";
31
32
  import { subprocessToolRegistry } from "./subprocess-tool-registry";
32
- import type { AgentProgress, SingleResult, TaskParams, TaskToolDetails } from "./types";
33
+ import type { AgentProgress, SingleResult, TaskItem, TaskParams, TaskToolDetails } from "./types";
33
34
 
34
35
  /**
35
36
  * Get status icon for agent state.
@@ -51,7 +52,9 @@ function getStatusIcon(status: AgentProgress["status"], theme: Theme, spinnerFra
51
52
  }
52
53
  }
53
54
 
54
- /** Append tool-count, context, cumulative-tokens, and cost stats to a status line string. */
55
+ /**
56
+ * Append tool-count, context, and cost stats to a status line string.
57
+ */
55
58
  function appendAgentStats(
56
59
  line: string,
57
60
  opts: {
@@ -66,22 +69,15 @@ function appendAgentStats(
66
69
  theme: Theme,
67
70
  ): string {
68
71
  if (opts.toolCount) {
69
- line += `${theme.sep.dot}${theme.fg("dim", `${opts.toolCount} tools`)}`;
72
+ line += `${theme.sep.dot}${theme.fg("dim", `${formatNumber(opts.toolCount)} ${theme.icon.extensionTool}`)}`;
70
73
  }
71
- // Current per-turn context — what the user reads as "how full is the context".
72
- // Cumulative tokens (billing volume) renders separately with a Σ sigil to avoid
73
- // being mistaken for current window pressure.
74
+ // Current per-turn context — match the status line's `<pct>%/<window>` gauge (e.g. `5.1%/1M`).
74
75
  if (opts.contextTokens && opts.contextTokens > 0) {
75
76
  const ctx =
76
77
  opts.contextWindow && opts.contextWindow > 0
77
- ? `${formatNumber(opts.contextTokens)}/${formatNumber(opts.contextWindow)} ctx`
78
- : `${formatNumber(opts.contextTokens)} ctx`;
78
+ ? formatContextUsage((opts.contextTokens / opts.contextWindow) * 100, opts.contextWindow)
79
+ : `${formatNumber(opts.contextTokens)}`;
79
80
  line += `${theme.sep.dot}${theme.fg("dim", ctx)}`;
80
- if (opts.tokens > 0) {
81
- line += `${theme.sep.dot}${theme.fg("dim", `Σ${formatNumber(opts.tokens)}`)}`;
82
- }
83
- } else if (opts.tokens > 0) {
84
- line += `${theme.sep.dot}${theme.fg("dim", `Σ${formatNumber(opts.tokens)}`)}`;
85
81
  }
86
82
  if (opts.cost > 0) {
87
83
  line += `${theme.sep.dot}${theme.fg("statusLineCost", `$${opts.cost.toFixed(2)}`)}`;
@@ -490,20 +486,63 @@ function formatOutputInline(data: unknown, theme: Theme, maxWidth = 80): string
490
486
  return `Output: ${pairs.join(", ")}`;
491
487
  }
492
488
 
489
+ /**
490
+ * Render the per-task list (`id` + ui `description`) for the streaming call
491
+ * preview. The args stream in token by token, so the array grows over time and
492
+ * trailing entries may be partially parsed — every field access is defensive.
493
+ */
494
+ function renderTaskItemLines(
495
+ tasks: TaskItem[] | undefined,
496
+ contPrefix: string,
497
+ expanded: boolean,
498
+ theme: Theme,
499
+ ): string[] {
500
+ const items = tasks ?? [];
501
+ if (items.length === 0) return [];
502
+
503
+ const branch = theme.fg("dim", theme.tree.branch);
504
+ const last = theme.fg("dim", theme.tree.last);
505
+ const cap = expanded ? items.length : Math.min(items.length, 12);
506
+ const truncated = cap < items.length;
507
+
508
+ const lines: string[] = [];
509
+ for (let i = 0; i < cap; i++) {
510
+ const task = items[i] as Partial<TaskItem> | undefined;
511
+ const isLastLine = !truncated && i === items.length - 1;
512
+ const connector = isLastLine ? last : branch;
513
+ const rawId = task?.id?.trim();
514
+ const idLabel = rawId ? formatTaskId(rawId) : `#${i + 1}`;
515
+ let line = `${contPrefix}${connector} ${theme.fg("accent", theme.bold(idLabel))}`;
516
+ const desc = task?.description?.trim();
517
+ if (desc) {
518
+ line += `: ${theme.fg("muted", truncateToWidth(replaceTabs(desc), 64))}`;
519
+ }
520
+ lines.push(line);
521
+ }
522
+ if (truncated) {
523
+ lines.push(`${contPrefix}${last} ${theme.fg("dim", formatMoreItems(items.length - cap, "agent"))}`);
524
+ }
525
+ return lines;
526
+ }
527
+
493
528
  /**
494
529
  * Render the tool call arguments.
495
530
  */
496
- export function renderCall(args: TaskParams, _options: RenderResultOptions, theme: Theme): Component {
531
+ export function renderCall(
532
+ args: TaskParams,
533
+ options: RenderResultOptions & { renderContext?: { hasResult?: boolean } },
534
+ theme: Theme,
535
+ ): Component {
497
536
  const lines: string[] = [];
498
537
  lines.push(renderStatusLine({ icon: "pending", title: "Task", description: args.agent }, theme));
499
538
 
500
- const contextTemplate = args.context ?? "";
501
- const context = contextTemplate.trim();
539
+ const context = (args.context ?? "").trim();
502
540
  const hasContext = context.length > 0;
503
541
  const branch = theme.fg("dim", theme.tree.branch);
504
542
  const last = theme.fg("dim", theme.tree.last);
505
543
  const vertical = theme.fg("dim", theme.tree.vertical);
506
544
  const showIsolated = "isolated" in args && args.isolated === true;
545
+ const taskCount = args.tasks?.length ?? 0;
507
546
 
508
547
  if (hasContext) {
509
548
  lines.push(` ${branch} ${theme.fg("dim", "Context")}`);
@@ -511,19 +550,23 @@ export function renderCall(args: TaskParams, _options: RenderResultOptions, them
511
550
  const content = line ? theme.fg("muted", replaceTabs(line)) : "";
512
551
  lines.push(` ${vertical} ${content}`);
513
552
  }
514
- const taskPrefix = showIsolated ? branch : last;
515
- lines.push(
516
- ` ${taskPrefix} ${theme.fg("dim", "Tasks")}: ${theme.fg("muted", `${args.tasks?.length ?? 0} agents`)}`,
517
- );
518
- if (showIsolated) {
519
- lines.push(` ${last} ${theme.fg("dim", "Isolated")}: ${theme.fg("muted", "true")}`);
520
- }
521
- return new Text(lines.join("\n"), 0, 0);
522
553
  }
523
554
 
524
- lines.push(`${theme.fg("dim", "Tasks")}: ${theme.fg("muted", `${args.tasks?.length ?? 0} agents`)}`);
555
+ // `Tasks` is the last child unless the isolation flag follows it.
556
+ const tasksIsLast = !showIsolated;
557
+ const tasksPrefix = tasksIsLast ? last : branch;
558
+ lines.push(` ${tasksPrefix} ${theme.fg("dim", "Tasks")} ${theme.fg("muted", `(${taskCount})`)}`);
559
+ const tasksContPrefix = tasksIsLast ? " " : ` ${vertical} `;
560
+ // The per-task preview list only exists to surface dispatched agents while
561
+ // the call args stream in. Once a result snapshot exists, `renderResult`
562
+ // draws the same agents as progress/result lines (id + description), so
563
+ // emitting the preview here would render every task twice.
564
+ if (!options.renderContext?.hasResult) {
565
+ lines.push(...renderTaskItemLines(args.tasks, tasksContPrefix, options.expanded, theme));
566
+ }
567
+
525
568
  if (showIsolated) {
526
- lines.push(`${theme.fg("dim", "Isolated")}: ${theme.fg("muted", "true")}`);
569
+ lines.push(` ${last} ${theme.fg("dim", "Isolated")}: ${theme.fg("muted", "true")}`);
527
570
  }
528
571
 
529
572
  return new Text(lines.join("\n"), 0, 0);
@@ -0,0 +1,217 @@
1
+ /** Default session-title model: the online pi/smol path (no local download / CPU inference). */
2
+ export const ONLINE_TINY_TITLE_MODEL_KEY = "online";
3
+ /** Local model the `tiny-models` CLI downloads when none is named. Not the session-title default — that is {@link ONLINE_TINY_TITLE_MODEL_KEY}. */
4
+ export const DEFAULT_TINY_TITLE_LOCAL_MODEL_KEY = "lfm2-700m";
5
+
6
+ export interface TinyTitleLocalModelSpec {
7
+ key: string;
8
+ repo: string;
9
+ dtype: "q4";
10
+ label: string;
11
+ description: string;
12
+ contextNote: string;
13
+ }
14
+
15
+ export const TINY_TITLE_LOCAL_MODELS = [
16
+ {
17
+ key: "lfm2-350m",
18
+ repo: "onnx-community/LFM2-350M-ONNX",
19
+ dtype: "q4",
20
+ label: "LFM2 350M",
21
+ description: "Recommended local model; best speed/quality balance, about 212 MB cached.",
22
+ contextNote: "Best local default from the CPU title-generation spike.",
23
+ },
24
+ {
25
+ key: "qwen3-0.6b",
26
+ repo: "onnx-community/Qwen3-0.6B-ONNX",
27
+ dtype: "q4",
28
+ label: "Qwen3 0.6B",
29
+ description: "Most robust local option; slower first load, about 500 MB cached.",
30
+ contextNote: "Use when title quality matters more than local startup cost.",
31
+ },
32
+ {
33
+ key: "gemma-270m",
34
+ repo: "onnx-community/gemma-3-270m-it-ONNX",
35
+ dtype: "q4",
36
+ label: "Gemma 270M",
37
+ description: "Smallest viable local option; lower quality, lowest cache footprint.",
38
+ contextNote: "Use on constrained machines that still need local titles.",
39
+ },
40
+ {
41
+ key: "qwen2.5-0.5b",
42
+ repo: "onnx-community/Qwen2.5-0.5B-Instruct",
43
+ dtype: "q4",
44
+ label: "Qwen2.5 0.5B",
45
+ description: "Balanced local fallback; moderate quality and cache footprint.",
46
+ contextNote: "Useful when Qwen3 is too heavy but Gemma quality is insufficient.",
47
+ },
48
+ {
49
+ key: "lfm2-700m",
50
+ repo: "onnx-community/LFM2-700M-ONNX",
51
+ dtype: "q4",
52
+ label: "LFM2 700M",
53
+ description: "Highest-quality local option; larger and slower than LFM2 350M.",
54
+ contextNote: "Use when local title quality is preferred over startup cost.",
55
+ },
56
+ ] as const satisfies readonly TinyTitleLocalModelSpec[];
57
+
58
+ export const TINY_TITLE_MODEL_VALUES = [
59
+ ONLINE_TINY_TITLE_MODEL_KEY,
60
+ "lfm2-350m",
61
+ "qwen3-0.6b",
62
+ "gemma-270m",
63
+ "qwen2.5-0.5b",
64
+ "lfm2-700m",
65
+ ] as const;
66
+
67
+ export type TinyTitleModelKey = (typeof TINY_TITLE_MODEL_VALUES)[number];
68
+ export type TinyTitleLocalModelKey = (typeof TINY_TITLE_LOCAL_MODELS)[number]["key"];
69
+
70
+ type MissingTinyTitleModelValue = Exclude<
71
+ typeof ONLINE_TINY_TITLE_MODEL_KEY | TinyTitleLocalModelKey,
72
+ TinyTitleModelKey
73
+ >;
74
+ type ExtraTinyTitleModelValue = Exclude<TinyTitleModelKey, typeof ONLINE_TINY_TITLE_MODEL_KEY | TinyTitleLocalModelKey>;
75
+ const TINY_TITLE_MODEL_VALUES_MATCH_REGISTRY: MissingTinyTitleModelValue extends never
76
+ ? ExtraTinyTitleModelValue extends never
77
+ ? true
78
+ : never
79
+ : never = true;
80
+ void TINY_TITLE_MODEL_VALUES_MATCH_REGISTRY;
81
+
82
+ export const TINY_TITLE_MODEL_OPTIONS = [
83
+ {
84
+ value: ONLINE_TINY_TITLE_MODEL_KEY,
85
+ label: "Online (pi/smol)",
86
+ description: "Current online title generation path; no local model download or CPU inference.",
87
+ },
88
+ ...TINY_TITLE_LOCAL_MODELS.map(model => ({
89
+ value: model.key,
90
+ label: model.label,
91
+ description: model.description,
92
+ })),
93
+ ] satisfies ReadonlyArray<{ value: TinyTitleModelKey; label: string; description: string }>;
94
+
95
+ export function isTinyTitleLocalModelKey(value: string): value is TinyTitleLocalModelKey {
96
+ return TINY_TITLE_LOCAL_MODELS.some(model => model.key === value);
97
+ }
98
+
99
+ export function getTinyTitleModelSpec(key: TinyTitleLocalModelKey): (typeof TINY_TITLE_LOCAL_MODELS)[number] {
100
+ const spec = TINY_TITLE_LOCAL_MODELS.find(model => model.key === key);
101
+ if (!spec) throw new Error(`Unknown tiny title model: ${key}`);
102
+ return spec;
103
+ }
104
+
105
+ /** Default memory model: the online path (the configured smol / remote LLM; no local download). */
106
+ export const ONLINE_MEMORY_MODEL_KEY = "online";
107
+ /** Recommended local model for memory tasks when none is named. */
108
+ export const DEFAULT_MEMORY_LOCAL_MODEL_KEY = "qwen3-1.7b";
109
+
110
+ /**
111
+ * Local models for Mnemosyne memory tasks (fact extraction + consolidation).
112
+ * These are larger (1B-1.7B) than the title models: structured extraction and
113
+ * faithful summarization need more capacity than 3-6 word titles. All q4, CPU.
114
+ * Ranking/recipe rationale lives in docs/local-models.md.
115
+ */
116
+ export const TINY_MEMORY_LOCAL_MODELS = [
117
+ {
118
+ key: "qwen3-1.7b",
119
+ repo: "onnx-community/Qwen3-1.7B-ONNX",
120
+ dtype: "q4",
121
+ label: "Qwen3 1.7B",
122
+ description:
123
+ "Recommended; most disciplined extraction (ignores chit-chat), good consolidation, about 1.1 GB cached.",
124
+ contextNote: "Best single-model pick for memory from the CPU experiment.",
125
+ },
126
+ {
127
+ key: "gemma-3-1b",
128
+ repo: "onnx-community/gemma-3-1b-it-ONNX",
129
+ dtype: "q4",
130
+ label: "Gemma 3 1B",
131
+ description: "Best consolidation/dedup; lighter footprint, but leaks small talk during extraction.",
132
+ contextNote: "Use when consolidation quality and size matter most.",
133
+ },
134
+ {
135
+ key: "qwen2.5-1.5b",
136
+ repo: "onnx-community/Qwen2.5-1.5B-Instruct",
137
+ dtype: "q4",
138
+ label: "Qwen2.5 1.5B",
139
+ description: "Best extraction granularity (atomic facts); weaker consolidation.",
140
+ contextNote: "Use when fine-grained, deduplicatable facts matter more than summaries.",
141
+ },
142
+ {
143
+ key: "lfm2-1.2b",
144
+ repo: "onnx-community/LFM2-1.2B-ONNX",
145
+ dtype: "q4",
146
+ label: "LFM2 1.2B",
147
+ description: "Fastest load; solid all-rounder, slightly noisier extraction labels.",
148
+ contextNote: "Use when local startup cost is the priority.",
149
+ },
150
+ ] as const satisfies readonly TinyTitleLocalModelSpec[];
151
+
152
+ export const TINY_MEMORY_MODEL_VALUES = [
153
+ ONLINE_MEMORY_MODEL_KEY,
154
+ "qwen3-1.7b",
155
+ "gemma-3-1b",
156
+ "qwen2.5-1.5b",
157
+ "lfm2-1.2b",
158
+ ] as const;
159
+
160
+ export type TinyMemoryModelKey = (typeof TINY_MEMORY_MODEL_VALUES)[number];
161
+ export type TinyMemoryLocalModelKey = (typeof TINY_MEMORY_LOCAL_MODELS)[number]["key"];
162
+
163
+ type MissingTinyMemoryModelValue = Exclude<
164
+ typeof ONLINE_MEMORY_MODEL_KEY | TinyMemoryLocalModelKey,
165
+ TinyMemoryModelKey
166
+ >;
167
+ type ExtraTinyMemoryModelValue = Exclude<TinyMemoryModelKey, typeof ONLINE_MEMORY_MODEL_KEY | TinyMemoryLocalModelKey>;
168
+ const TINY_MEMORY_MODEL_VALUES_MATCH_REGISTRY: MissingTinyMemoryModelValue extends never
169
+ ? ExtraTinyMemoryModelValue extends never
170
+ ? true
171
+ : never
172
+ : never = true;
173
+ void TINY_MEMORY_MODEL_VALUES_MATCH_REGISTRY;
174
+
175
+ export const TINY_MEMORY_MODEL_OPTIONS = [
176
+ {
177
+ value: ONLINE_MEMORY_MODEL_KEY,
178
+ label: "Online (smol/remote)",
179
+ description: "Use the configured Mnemosyne LLM mode (smol or remote); no local model download or CPU inference.",
180
+ },
181
+ ...TINY_MEMORY_LOCAL_MODELS.map(model => ({
182
+ value: model.key,
183
+ label: model.label,
184
+ description: model.description,
185
+ })),
186
+ ] satisfies ReadonlyArray<{ value: TinyMemoryModelKey; label: string; description: string }>;
187
+
188
+ export function isTinyMemoryLocalModelKey(value: string): value is TinyMemoryLocalModelKey {
189
+ return TINY_MEMORY_LOCAL_MODELS.some(model => model.key === value);
190
+ }
191
+
192
+ export function getTinyMemoryModelSpec(key: TinyMemoryLocalModelKey): (typeof TINY_MEMORY_LOCAL_MODELS)[number] {
193
+ const spec = TINY_MEMORY_LOCAL_MODELS.find(model => model.key === key);
194
+ if (!spec) throw new Error(`Unknown tiny memory model: ${key}`);
195
+ return spec;
196
+ }
197
+
198
+ /** Any local model key (title or memory), used by the shared inference worker. */
199
+ export type TinyLocalModelKey = TinyTitleLocalModelKey | TinyMemoryLocalModelKey;
200
+
201
+ /** Resolve a local model spec by key across both the title and memory registries. */
202
+ export function getTinyLocalModelSpec(key: string): TinyTitleLocalModelSpec | undefined {
203
+ return (
204
+ TINY_TITLE_LOCAL_MODELS.find(model => model.key === key) ??
205
+ TINY_MEMORY_LOCAL_MODELS.find(model => model.key === key)
206
+ );
207
+ }
208
+
209
+ export function isTinyLocalModelKey(value: string): value is TinyLocalModelKey {
210
+ return getTinyLocalModelSpec(value) !== undefined;
211
+ }
212
+
213
+ /** Combined local model registry (title + memory) for the shared tiny-models CLI. */
214
+ export const TINY_LOCAL_MODELS = [
215
+ ...TINY_TITLE_LOCAL_MODELS,
216
+ ...TINY_MEMORY_LOCAL_MODELS,
217
+ ] as const satisfies readonly TinyTitleLocalModelSpec[];
@@ -0,0 +1,19 @@
1
+ export const MAX_TITLE_INPUT_CHARS = 2000;
2
+
3
+ export function truncateTitleInput(message: string): string {
4
+ return message.length > MAX_TITLE_INPUT_CHARS ? `${message.slice(0, MAX_TITLE_INPUT_CHARS)}…` : message;
5
+ }
6
+
7
+ export function formatTitleUserMessage(message: string): string {
8
+ return `<user-message>\n${truncateTitleInput(message)}\n</user-message>`;
9
+ }
10
+
11
+ export function normalizeGeneratedTitle(value: string | null | undefined): string | null {
12
+ const firstLine = value?.trim().split(/\r?\n/, 1)[0]?.trim();
13
+ if (!firstLine) return null;
14
+ const title = firstLine
15
+ .replace(/^["']|["']$/g, "")
16
+ .replace(/[.!?]$/, "")
17
+ .trim();
18
+ return title || null;
19
+ }