@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
@@ -42,6 +42,12 @@ import {
42
42
  formatCanonicalVariantSelector,
43
43
  type ModelEquivalenceConfig,
44
44
  } from "./model-equivalence";
45
+ import {
46
+ getBracketStrippedModelIdCandidates,
47
+ getLongestModelLikeIdSegment,
48
+ getModelLikeIdSegments,
49
+ stripBracketedModelIdAffixes,
50
+ } from "./model-id-affixes";
45
51
  import {
46
52
  type ModelOverride,
47
53
  type ModelsConfig,
@@ -192,7 +198,7 @@ function validateProviderConfiguration(
192
198
  }
193
199
  }
194
200
 
195
- if (mode === "models-config" && config.discovery && !config.api) {
201
+ if (mode === "models-config" && config.discovery && !config.api && config.discovery.type !== "proxy") {
196
202
  throw new Error(`Provider ${providerName}: "api" is required when discovery is enabled at provider level.`);
197
203
  }
198
204
 
@@ -650,20 +656,53 @@ function shouldReplaceCustomReference(existing: Model<Api> | undefined, candidat
650
656
  return existing.provider !== "openai" && candidate.provider === "openai";
651
657
  }
652
658
 
659
+ function normalizeCustomReferenceKey(value: string): string {
660
+ return value.trim().toLowerCase();
661
+ }
662
+
653
663
  function buildCustomReferenceMap(): Map<string, Model<Api>> {
654
664
  const references = new Map<string, Model<Api>>();
655
665
  for (const provider of getBundledProviders()) {
656
666
  for (const model of getBundledModels(provider as Parameters<typeof getBundledModels>[0])) {
657
667
  const candidate = model as Model<Api>;
658
- if (shouldReplaceCustomReference(references.get(candidate.id), candidate)) {
659
- references.set(candidate.id, candidate);
668
+ const key = normalizeCustomReferenceKey(candidate.id);
669
+ if (shouldReplaceCustomReference(references.get(key), candidate)) {
670
+ references.set(key, candidate);
660
671
  }
661
672
  }
662
673
  }
663
674
  return references;
664
675
  }
665
676
 
677
+ function buildCustomReferenceSuffixAliasMap(exactReferences: ReadonlyMap<string, Model<Api>>): Map<string, Model<Api>> {
678
+ const aliases = new Map<string, Model<Api>>();
679
+ for (const reference of exactReferences.values()) {
680
+ const slashIndex = reference.id.lastIndexOf("/");
681
+ if (slashIndex === -1) {
682
+ continue;
683
+ }
684
+ const suffix = reference.id.slice(slashIndex + 1);
685
+ const alias = getLongestModelLikeIdSegment(suffix);
686
+ if (!alias) {
687
+ continue;
688
+ }
689
+ if (shouldReplaceCustomReference(aliases.get(alias), reference)) {
690
+ aliases.set(alias, reference);
691
+ }
692
+ }
693
+ return aliases;
694
+ }
695
+
666
696
  const customReferenceMap = buildCustomReferenceMap();
697
+ const customReferenceSuffixAliasMap = buildCustomReferenceSuffixAliasMap(customReferenceMap);
698
+
699
+ const CUSTOM_REFERENCE_TRAILING_MARKER_PATTERN =
700
+ /[-:](?:thinking|customtools|high|low|medium|minimal|xhigh|free|cloud|exacto|nitro|original|optimized|nvfp4|fp8|fp4|bf16|int8|int4|search)$/i;
701
+
702
+ function stripCustomReferenceTrailingMarker(candidate: string): string | undefined {
703
+ const match = CUSTOM_REFERENCE_TRAILING_MARKER_PATTERN.exec(candidate);
704
+ return match ? candidate.slice(0, match.index) : undefined;
705
+ }
667
706
 
668
707
  function getCustomReferenceCandidateIds(modelId: string): string[] {
669
708
  const candidates = new Set<string>();
@@ -673,23 +712,46 @@ function getCustomReferenceCandidateIds(modelId: string): string[] {
673
712
  if (!candidate || candidates.has(candidate)) continue;
674
713
  candidates.add(candidate);
675
714
 
715
+ for (const stripped of getBracketStrippedModelIdCandidates(candidate)) {
716
+ queue.push(stripped);
717
+ }
718
+ for (const segment of getModelLikeIdSegments(candidate)) {
719
+ queue.push(segment);
720
+ }
721
+
676
722
  for (const suffix of [":cloud", "-cloud"] as const) {
677
723
  if (candidate.toLowerCase().endsWith(suffix)) {
678
724
  queue.push(candidate.slice(0, -suffix.length));
679
725
  }
680
726
  }
681
727
 
728
+ const slashIndex = candidate.lastIndexOf("/");
729
+ if (slashIndex !== -1) {
730
+ queue.push(candidate.slice(slashIndex + 1));
731
+ }
732
+
682
733
  const colonToDash = candidate.replace(/:/g, "-");
683
734
  if (colonToDash !== candidate) {
684
735
  queue.push(colonToDash);
685
736
  }
737
+
738
+ const lowercased = candidate.toLowerCase();
739
+ if (lowercased !== candidate) {
740
+ queue.push(lowercased);
741
+ }
742
+
743
+ const strippedMarker = stripCustomReferenceTrailingMarker(candidate);
744
+ if (strippedMarker) {
745
+ queue.push(strippedMarker);
746
+ }
686
747
  }
687
748
  return [...candidates];
688
749
  }
689
750
 
690
751
  function resolveCustomModelReference(modelId: string): Model<Api> | undefined {
691
752
  for (const candidate of getCustomReferenceCandidateIds(modelId)) {
692
- const reference = customReferenceMap.get(candidate);
753
+ const key = normalizeCustomReferenceKey(candidate);
754
+ const reference = customReferenceMap.get(key) ?? customReferenceSuffixAliasMap.get(key);
693
755
  if (reference) return reference;
694
756
  }
695
757
  return undefined;
@@ -1209,13 +1271,17 @@ export class ModelRegistry {
1209
1271
  keylessProviders.add(providerName);
1210
1272
  }
1211
1273
 
1212
- if (providerConfig.discovery && providerConfig.api) {
1274
+ if (providerConfig.discovery && (providerConfig.api || providerConfig.discovery.type === "proxy")) {
1275
+ const disableStrictCompat = providerConfig.disableStrictTools ? { disableStrictTools: true } : undefined;
1213
1276
  discoverableProviders.push({
1214
1277
  provider: providerName,
1215
- api: providerConfig.api as Api,
1278
+ // Proxy discovery derives per-model api from /v1/models's
1279
+ // supported_endpoint_types; the provider-level api is only a
1280
+ // fallback for entries that don't advertise one.
1281
+ api: (providerConfig.api ?? "openai-completions") as Api,
1216
1282
  baseUrl: providerConfig.baseUrl,
1217
1283
  headers: providerConfig.headers,
1218
- compat: providerConfig.compat,
1284
+ compat: mergeCompat(providerConfig.compat, disableStrictCompat),
1219
1285
  discovery: providerConfig.discovery,
1220
1286
  optional: false,
1221
1287
  });
@@ -1385,6 +1451,8 @@ export class ModelRegistry {
1385
1451
  case "lm-studio":
1386
1452
  case "openai-models-list":
1387
1453
  return this.#discoverOpenAIModelsList(providerConfig);
1454
+ case "proxy":
1455
+ return this.#discoverProxyModels(providerConfig);
1388
1456
  }
1389
1457
  }
1390
1458
 
@@ -1711,7 +1779,7 @@ export class ModelRegistry {
1711
1779
 
1712
1780
  const response = await fetch(modelsUrl, {
1713
1781
  headers,
1714
- signal: AbortSignal.timeout(250),
1782
+ signal: AbortSignal.timeout(10_000),
1715
1783
  });
1716
1784
  if (!response.ok) {
1717
1785
  throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
@@ -1746,6 +1814,96 @@ export class ModelRegistry {
1746
1814
  return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
1747
1815
  }
1748
1816
 
1817
+ /**
1818
+ * Discover models from an Anthropic+OpenAI-compatible reseller proxy that
1819
+ * exposes both `/v1/messages` and `/v1/chat/completions`, advertising each
1820
+ * model's wire capabilities through `supported_endpoint_types` on
1821
+ * `GET /v1/models` (new-api / one-api-style proxies).
1822
+ *
1823
+ * Routing per model:
1824
+ * supported_endpoint_types: ["anthropic", ...] -> api: "anthropic-messages"
1825
+ * supported_endpoint_types: ["openai"] -> api: "openai-completions"
1826
+ * missing / neither -> provider-level api fallback
1827
+ *
1828
+ * Anthropic models share the same baseUrl; the Anthropic SDK strips a
1829
+ * trailing `/v1` itself before appending `/v1/messages`, so the discovery
1830
+ * URL (which ends in `/v1`) round-trips correctly.
1831
+ */
1832
+ async #discoverProxyModels(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
1833
+ const baseUrl = this.#normalizeOpenAIModelsListBaseUrl(providerConfig.baseUrl);
1834
+ const modelsUrl = `${baseUrl}/models`;
1835
+
1836
+ const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
1837
+ const apiKey = await this.authStorage.getApiKey(providerConfig.provider);
1838
+ if (apiKey && apiKey !== DEFAULT_LOCAL_TOKEN && apiKey !== kNoAuth) {
1839
+ headers.Authorization = `Bearer ${apiKey}`;
1840
+ }
1841
+
1842
+ const response = await fetch(modelsUrl, {
1843
+ headers,
1844
+ signal: AbortSignal.timeout(10_000),
1845
+ });
1846
+ if (!response.ok) {
1847
+ throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
1848
+ }
1849
+ const payload = (await response.json()) as {
1850
+ data?: Array<{ id?: string; name?: string; supported_endpoint_types?: string[] }>;
1851
+ };
1852
+ const items = payload.data ?? [];
1853
+ const discovered: Model<Api>[] = [];
1854
+ for (const item of items) {
1855
+ const id = item.id;
1856
+ if (!id) continue;
1857
+ const endpoints = item.supported_endpoint_types ?? [];
1858
+ const api: Api | undefined = endpoints.includes("anthropic")
1859
+ ? "anthropic-messages"
1860
+ : endpoints.includes("openai")
1861
+ ? "openai-completions"
1862
+ : providerConfig.api;
1863
+ if (!api) continue;
1864
+ const isAnthropic = api === "anthropic-messages";
1865
+ const reference = resolveCustomModelReference(id);
1866
+ const discoveryName = typeof item.name === "string" ? item.name.trim() : "";
1867
+ const displayName =
1868
+ reference?.name ??
1869
+ (discoveryName && discoveryName !== id ? discoveryName : undefined) ??
1870
+ stripBracketedModelIdAffixes(id) ??
1871
+ id;
1872
+ discovered.push(
1873
+ enrichModelThinking({
1874
+ id,
1875
+ name: displayName,
1876
+ api,
1877
+ provider: providerConfig.provider,
1878
+ baseUrl,
1879
+ reasoning: reference?.reasoning ?? false,
1880
+ thinking: reference?.thinking,
1881
+ input: reference?.input ?? ["text"],
1882
+ // Proxy pricing is provider-specific and usually does not match
1883
+ // upstream bundled catalogs, so keep costs local-unknown even when
1884
+ // we successfully recover the upstream model identity.
1885
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1886
+ contextWindow: reference?.contextWindow ?? 128000,
1887
+ maxTokens: reference?.maxTokens ?? 8192,
1888
+ headers,
1889
+ // OpenAI-compat fields are no-ops on anthropic models; the
1890
+ // Anthropic SDK ignores them. Provider-level disableStrictTools
1891
+ // flows in via #applyProviderCompat for the third-party-Anthropic
1892
+ // path. Cross-wire bundled compat is intentionally not copied:
1893
+ // request-shaping fields are provider-wire specific.
1894
+ compat: isAnthropic
1895
+ ? undefined
1896
+ : {
1897
+ supportsStore: false,
1898
+ supportsDeveloperRole: false,
1899
+ supportsReasoningEffort: false,
1900
+ },
1901
+ }),
1902
+ );
1903
+ }
1904
+ return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
1905
+ }
1906
+
1749
1907
  #normalizeLlamaCppBaseUrl(baseUrl?: string): string {
1750
1908
  const defaultBaseUrl = "http://127.0.0.1:8080";
1751
1909
  const raw = baseUrl || defaultBaseUrl;
@@ -121,7 +121,7 @@ export const ModelOverrideSchema = z.object({
121
121
  export type ModelOverride = z.infer<typeof ModelOverrideSchema>;
122
122
 
123
123
  export const ProviderDiscoverySchema = z.object({
124
- type: z.enum(["ollama", "llama.cpp", "lm-studio", "openai-models-list"]),
124
+ type: z.enum(["ollama", "llama.cpp", "lm-studio", "openai-models-list", "proxy"]),
125
125
  });
126
126
 
127
127
  export const ProviderAuthSchema = z.enum(["apiKey", "none", "oauth"]);
@@ -1,6 +1,14 @@
1
1
  import { THINKING_EFFORTS } from "@oh-my-pi/pi-ai";
2
2
  import { TASK_SIMPLE_MODES } from "../task/simple-mode";
3
3
  import { getThinkingLevelMetadata } from "../thinking";
4
+ import {
5
+ ONLINE_MEMORY_MODEL_KEY,
6
+ ONLINE_TINY_TITLE_MODEL_KEY,
7
+ TINY_MEMORY_MODEL_OPTIONS,
8
+ TINY_MEMORY_MODEL_VALUES,
9
+ TINY_TITLE_MODEL_OPTIONS,
10
+ TINY_TITLE_MODEL_VALUES,
11
+ } from "../tiny/models";
4
12
  import { EDIT_MODES } from "../utils/edit-mode";
5
13
 
6
14
  /** Unified settings schema - single source of truth for all settings.
@@ -81,6 +89,7 @@ export type StatusLineSegmentId =
81
89
  | "hostname"
82
90
  | "cache_read"
83
91
  | "cache_write"
92
+ | "cache_hit"
84
93
  | "session_name"
85
94
  | "usage";
86
95
 
@@ -1291,24 +1300,193 @@ export const SETTINGS_SCHEMA = {
1291
1300
  "memories.summaryInjectionTokenLimit": { type: "number", default: 5000 },
1292
1301
 
1293
1302
  // Memory backend selector — picks between local memories pipeline,
1294
- // Hindsight remote memory, or off. Legacy `memories.enabled` keeps gating
1295
- // the local backend; see config/settings.ts migration for details.
1303
+ // Mnemosyne local SQLite, Hindsight remote memory, or off. Legacy
1304
+ // `memories.enabled` keeps gating the local backend; see config/settings.ts
1305
+ // migration for details.
1296
1306
  "memory.backend": {
1297
1307
  type: "enum",
1298
- values: ["off", "local", "hindsight"] as const,
1308
+ values: ["off", "local", "hindsight", "mnemosyne"] as const,
1299
1309
  default: "off",
1300
1310
  ui: {
1301
1311
  tab: "memory",
1302
1312
  label: "Memory Backend",
1303
- description: "Off, local memory pipeline, or Hindsight remote memory",
1313
+ description: "Off, local summary pipeline, Mnemosyne SQLite, or Hindsight remote memory",
1304
1314
  options: [
1305
1315
  { value: "off", label: "Off", description: "No memory subsystem runs" },
1306
1316
  { value: "local", label: "Local", description: "Local rollout summarisation pipeline (memory_summary.md)" },
1307
1317
  { value: "hindsight", label: "Hindsight", description: "Vectorize Hindsight remote memory service" },
1318
+ {
1319
+ value: "mnemosyne",
1320
+ label: "Mnemosyne",
1321
+ description: "Local SQLite recall/retain backend with optional embeddings",
1322
+ },
1308
1323
  ],
1309
1324
  },
1310
1325
  },
1311
1326
 
1327
+ // Mnemosyne local SQLite memory backend.
1328
+ "mnemosyne.dbPath": {
1329
+ type: "string",
1330
+ default: undefined,
1331
+ ui: {
1332
+ tab: "memory",
1333
+ label: "Mnemosyne DB Path",
1334
+ description: "Optional SQLite DB path. Defaults to the agent memories directory.",
1335
+ condition: "mnemosyneActive",
1336
+ },
1337
+ },
1338
+ "mnemosyne.bank": {
1339
+ type: "string",
1340
+ default: undefined,
1341
+ ui: {
1342
+ tab: "memory",
1343
+ label: "Mnemosyne Bank",
1344
+ description: "Optional shared bank base name. Per-project modes derive project-local banks from it.",
1345
+ condition: "mnemosyneActive",
1346
+ },
1347
+ },
1348
+ "mnemosyne.scoping": {
1349
+ type: "enum",
1350
+ values: ["global", "per-project", "per-project-tagged"] as const,
1351
+ default: "per-project",
1352
+ ui: {
1353
+ tab: "memory",
1354
+ label: "Mnemosyne Scoping",
1355
+ description:
1356
+ "global = one shared bank; per-project = isolated bank per cwd; per-project-tagged = project-local writes plus global recall visibility",
1357
+ options: [
1358
+ {
1359
+ value: "global",
1360
+ label: "Global",
1361
+ description: "One shared Mnemosyne bank for every project",
1362
+ },
1363
+ {
1364
+ value: "per-project",
1365
+ label: "Per project",
1366
+ description: "Project-local Mnemosyne bank per cwd basename",
1367
+ },
1368
+ {
1369
+ value: "per-project-tagged",
1370
+ label: "Per project (tagged)",
1371
+ description: "Write to a project-local bank but merge project + shared recall results",
1372
+ },
1373
+ ],
1374
+ condition: "mnemosyneActive",
1375
+ },
1376
+ },
1377
+ "mnemosyne.autoRecall": {
1378
+ type: "boolean",
1379
+ default: true,
1380
+ ui: {
1381
+ tab: "memory",
1382
+ label: "Mnemosyne Auto Recall",
1383
+ description: "Recall local memories into the first turn of each session",
1384
+ condition: "mnemosyneActive",
1385
+ },
1386
+ },
1387
+ "mnemosyne.autoRetain": {
1388
+ type: "boolean",
1389
+ default: true,
1390
+ ui: {
1391
+ tab: "memory",
1392
+ label: "Mnemosyne Auto Retain",
1393
+ description: "Retain completed conversation turns into local Mnemosyne memory",
1394
+ condition: "mnemosyneActive",
1395
+ },
1396
+ },
1397
+ "mnemosyne.noEmbeddings": {
1398
+ type: "boolean",
1399
+ default: false,
1400
+ ui: {
1401
+ tab: "memory",
1402
+ label: "Mnemosyne Disable Embeddings",
1403
+ description: "Force deterministic FTS-only recall instead of vector embeddings",
1404
+ condition: "mnemosyneActive",
1405
+ },
1406
+ },
1407
+ "mnemosyne.embeddingModel": {
1408
+ type: "string",
1409
+ default: undefined,
1410
+ ui: {
1411
+ tab: "memory",
1412
+ label: "Mnemosyne Embedding Model",
1413
+ description: "Optional embedding model override passed to Mnemosyne",
1414
+ condition: "mnemosyneActive",
1415
+ },
1416
+ },
1417
+ "mnemosyne.embeddingApiUrl": {
1418
+ type: "string",
1419
+ default: undefined,
1420
+ ui: {
1421
+ tab: "memory",
1422
+ label: "Mnemosyne Embedding API URL",
1423
+ description: "Optional OpenAI-compatible embedding endpoint passed to Mnemosyne",
1424
+ condition: "mnemosyneActive",
1425
+ },
1426
+ },
1427
+ "mnemosyne.embeddingApiKey": {
1428
+ type: "string",
1429
+ default: undefined,
1430
+ ui: {
1431
+ tab: "memory",
1432
+ label: "Mnemosyne Embedding API Key",
1433
+ description: "Optional embedding API key passed to Mnemosyne",
1434
+ condition: "mnemosyneActive",
1435
+ },
1436
+ },
1437
+ "mnemosyne.llmMode": {
1438
+ type: "enum",
1439
+ values: ["none", "smol", "remote"] as const,
1440
+ default: "smol",
1441
+ ui: {
1442
+ tab: "memory",
1443
+ label: "Mnemosyne LLM Mode",
1444
+ description: "Use no LLM, the configured smol model, or a remote OpenAI-compatible endpoint",
1445
+ condition: "mnemosyneActive",
1446
+ options: [
1447
+ { value: "none", label: "None", description: "Disable Mnemosyne LLM-backed extraction" },
1448
+ { value: "smol", label: "Smol", description: "Use the configured pi-ai smol model" },
1449
+ { value: "remote", label: "Remote", description: "Use the Mnemosyne remote LLM settings below" },
1450
+ ],
1451
+ },
1452
+ },
1453
+ "mnemosyne.llmBaseUrl": {
1454
+ type: "string",
1455
+ default: undefined,
1456
+ ui: {
1457
+ tab: "memory",
1458
+ label: "Mnemosyne LLM Base URL",
1459
+ description: "Optional OpenAI-compatible LLM endpoint for Mnemosyne remote mode",
1460
+ condition: "mnemosyneActive",
1461
+ },
1462
+ },
1463
+ "mnemosyne.llmApiKey": {
1464
+ type: "string",
1465
+ default: undefined,
1466
+ ui: {
1467
+ tab: "memory",
1468
+ label: "Mnemosyne LLM API Key",
1469
+ description: "Optional LLM API key for Mnemosyne remote mode",
1470
+ condition: "mnemosyneActive",
1471
+ },
1472
+ },
1473
+ "mnemosyne.llmModel": {
1474
+ type: "string",
1475
+ default: undefined,
1476
+ ui: {
1477
+ tab: "memory",
1478
+ label: "Mnemosyne LLM Model",
1479
+ description: "Optional LLM model name for Mnemosyne remote mode",
1480
+ condition: "mnemosyneActive",
1481
+ },
1482
+ },
1483
+ "mnemosyne.retainEveryNTurns": { type: "number", default: 4 },
1484
+ "mnemosyne.recallLimit": { type: "number", default: 8 },
1485
+ "mnemosyne.recallContextTurns": { type: "number", default: 3 },
1486
+ "mnemosyne.recallMaxQueryChars": { type: "number", default: 4000 },
1487
+ "mnemosyne.injectionTokenLimit": { type: "number", default: 5000 },
1488
+ "mnemosyne.debug": { type: "boolean", default: false },
1489
+
1312
1490
  // Hindsight (https://hindsight.vectorize.io)
1313
1491
  "hindsight.apiUrl": {
1314
1492
  type: "string",
@@ -2016,16 +2194,6 @@ export const SETTINGS_SCHEMA = {
2016
2194
  },
2017
2195
  },
2018
2196
 
2019
- "calc.enabled": {
2020
- type: "boolean",
2021
- default: false,
2022
- ui: {
2023
- tab: "tools",
2024
- label: "Calculator",
2025
- description: "Enable the calculator tool for basic calculations",
2026
- },
2027
- },
2028
-
2029
2197
  "tts.enabled": {
2030
2198
  type: "boolean",
2031
2199
  default: false,
@@ -2736,6 +2904,30 @@ export const SETTINGS_SCHEMA = {
2736
2904
  ],
2737
2905
  },
2738
2906
  },
2907
+ "providers.tinyModel": {
2908
+ type: "enum",
2909
+ values: TINY_TITLE_MODEL_VALUES,
2910
+ default: ONLINE_TINY_TITLE_MODEL_KEY,
2911
+ ui: {
2912
+ tab: "providers",
2913
+ label: "Tiny Model",
2914
+ description: "Session-title model: online pi/smol by default, or a local on-device model",
2915
+ options: TINY_TITLE_MODEL_OPTIONS,
2916
+ },
2917
+ },
2918
+ "providers.memoryModel": {
2919
+ type: "enum",
2920
+ values: TINY_MEMORY_MODEL_VALUES,
2921
+ default: ONLINE_MEMORY_MODEL_KEY,
2922
+ ui: {
2923
+ tab: "memory",
2924
+ label: "Memory Model",
2925
+ description:
2926
+ "Mnemosyne LLM for fact extraction + consolidation: online (smol/remote) by default, or a local on-device model",
2927
+ condition: "mnemosyneActive",
2928
+ options: TINY_MEMORY_MODEL_OPTIONS,
2929
+ },
2930
+ },
2739
2931
 
2740
2932
  "providers.kimiApiFormat": {
2741
2933
  type: "enum",
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import {
13
13
  Patch as HashlinePatch,
14
+ missingSnapshotTagMessage,
14
15
  normalizeToLF,
15
16
  type Patch,
16
17
  type PatchSection,
@@ -40,10 +41,6 @@ async function readSectionText(absolutePath: string, sectionPath: string): Promi
40
41
  }
41
42
  }
42
43
 
43
- function hasAnchorScoped(section: PatchSection): boolean {
44
- return section.hasAnchorScopedEdit;
45
- }
46
-
47
44
  function snapshotMatchesCurrent(snapshot: Snapshot, currentText: string): boolean {
48
45
  return snapshot.text === currentText;
49
46
  }
@@ -54,9 +51,10 @@ function validateSectionHash(
54
51
  snapshots: SnapshotStore,
55
52
  ): string | null {
56
53
  if (section.fileHash === undefined) {
57
- return hasAnchorScoped(section)
58
- ? `Missing hashline snapshot tag for anchored edit to ${section.path}; use \`¶${section.path}#tag\` from your latest read.`
59
- : null;
54
+ // The snapshot tag is mandatory on every section — head/tail inserts
55
+ // included to keep this preview path in lockstep with the apply path
56
+ // (`Patcher.prepare`), which rejects tagless sections unconditionally.
57
+ return missingSnapshotTagMessage(section.path);
60
58
  }
61
59
  const snapshot = snapshots.byHash(absolutePath, section.fileHash);
62
60
  if (snapshot && snapshotMatchesCurrent(snapshot, text)) return null;