@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
|
@@ -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,
|
|
@@ -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
|
-
|
|
659
|
-
|
|
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
|
|
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;
|
|
@@ -1785,7 +1847,7 @@ export class ModelRegistry {
|
|
|
1785
1847
|
throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
|
|
1786
1848
|
}
|
|
1787
1849
|
const payload = (await response.json()) as {
|
|
1788
|
-
data?: Array<{ id?: string; supported_endpoint_types?: string[] }>;
|
|
1850
|
+
data?: Array<{ id?: string; name?: string; supported_endpoint_types?: string[] }>;
|
|
1789
1851
|
};
|
|
1790
1852
|
const items = payload.data ?? [];
|
|
1791
1853
|
const discovered: Model<Api>[] = [];
|
|
@@ -1800,23 +1862,35 @@ export class ModelRegistry {
|
|
|
1800
1862
|
: providerConfig.api;
|
|
1801
1863
|
if (!api) continue;
|
|
1802
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;
|
|
1803
1872
|
discovered.push(
|
|
1804
1873
|
enrichModelThinking({
|
|
1805
1874
|
id,
|
|
1806
|
-
name:
|
|
1875
|
+
name: displayName,
|
|
1807
1876
|
api,
|
|
1808
1877
|
provider: providerConfig.provider,
|
|
1809
1878
|
baseUrl,
|
|
1810
|
-
reasoning: false,
|
|
1811
|
-
|
|
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.
|
|
1812
1885
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
1813
|
-
contextWindow: 128000,
|
|
1814
|
-
maxTokens: 8192,
|
|
1886
|
+
contextWindow: reference?.contextWindow ?? 128000,
|
|
1887
|
+
maxTokens: reference?.maxTokens ?? 8192,
|
|
1815
1888
|
headers,
|
|
1816
1889
|
// OpenAI-compat fields are no-ops on anthropic models; the
|
|
1817
1890
|
// Anthropic SDK ignores them. Provider-level disableStrictTools
|
|
1818
1891
|
// flows in via #applyProviderCompat for the third-party-Anthropic
|
|
1819
|
-
// path.
|
|
1892
|
+
// path. Cross-wire bundled compat is intentionally not copied:
|
|
1893
|
+
// request-shaping fields are provider-wire specific.
|
|
1820
1894
|
compat: isAnthropic
|
|
1821
1895
|
? undefined
|
|
1822
1896
|
: {
|
|
@@ -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.
|
|
@@ -1292,23 +1300,192 @@ export const SETTINGS_SCHEMA = {
|
|
|
1292
1300
|
"memories.summaryInjectionTokenLimit": { type: "number", default: 5000 },
|
|
1293
1301
|
|
|
1294
1302
|
// Memory backend selector — picks between local memories pipeline,
|
|
1295
|
-
// Hindsight remote memory, or off. Legacy
|
|
1296
|
-
// the local backend; see config/settings.ts
|
|
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.
|
|
1297
1306
|
"memory.backend": {
|
|
1298
1307
|
type: "enum",
|
|
1299
|
-
values: ["off", "local", "hindsight"] as const,
|
|
1308
|
+
values: ["off", "local", "hindsight", "mnemosyne"] as const,
|
|
1300
1309
|
default: "off",
|
|
1301
1310
|
ui: {
|
|
1302
1311
|
tab: "memory",
|
|
1303
1312
|
label: "Memory Backend",
|
|
1304
|
-
description: "Off, local
|
|
1313
|
+
description: "Off, local summary pipeline, Mnemosyne SQLite, or Hindsight remote memory",
|
|
1305
1314
|
options: [
|
|
1306
1315
|
{ value: "off", label: "Off", description: "No memory subsystem runs" },
|
|
1307
1316
|
{ value: "local", label: "Local", description: "Local rollout summarisation pipeline (memory_summary.md)" },
|
|
1308
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
|
+
},
|
|
1323
|
+
],
|
|
1324
|
+
},
|
|
1325
|
+
},
|
|
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" },
|
|
1309
1450
|
],
|
|
1310
1451
|
},
|
|
1311
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 },
|
|
1312
1489
|
|
|
1313
1490
|
// Hindsight (https://hindsight.vectorize.io)
|
|
1314
1491
|
"hindsight.apiUrl": {
|
|
@@ -2727,6 +2904,30 @@ export const SETTINGS_SCHEMA = {
|
|
|
2727
2904
|
],
|
|
2728
2905
|
},
|
|
2729
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
|
+
},
|
|
2730
2931
|
|
|
2731
2932
|
"providers.kimiApiFormat": {
|
|
2732
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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;
|
|
@@ -492,6 +492,42 @@ display({"label": "A"})`,
|
|
|
492
492
|
expect(reloaded.output.trim()).toBe("2");
|
|
493
493
|
});
|
|
494
494
|
|
|
495
|
+
it("loads TypeScript type-only imports in cells and local modules", async () => {
|
|
496
|
+
using tempDir = TempDir.createSync("@omp-eval-js-type-imports-");
|
|
497
|
+
const sessionFile = path.join(tempDir.path(), "session.jsonl");
|
|
498
|
+
const sessionId = `js-type-imports:${crypto.randomUUID()}`;
|
|
499
|
+
const session = createToolSession(tempDir.path(), sessionFile);
|
|
500
|
+
const typesPath = path.join(tempDir.path(), "types.ts");
|
|
501
|
+
const valuesPath = path.join(tempDir.path(), "values.ts");
|
|
502
|
+
const entryPath = path.join(tempDir.path(), "entry.ts");
|
|
503
|
+
const typesSpec = JSON.stringify(typesPath);
|
|
504
|
+
const entrySpec = JSON.stringify(entryPath);
|
|
505
|
+
await Bun.write(typesPath, "export interface TypeOnly { value: number }\n");
|
|
506
|
+
await Bun.write(valuesPath, "export interface InlineOnly { value: number }\nexport const imported = 41;\n");
|
|
507
|
+
await Bun.write(
|
|
508
|
+
entryPath,
|
|
509
|
+
[
|
|
510
|
+
'import type { TypeOnly } from "./types.ts";',
|
|
511
|
+
'import { type InlineOnly, imported } from "./values.ts";',
|
|
512
|
+
"export const typeOnly = 1;",
|
|
513
|
+
"export const inlineType = imported;",
|
|
514
|
+
"",
|
|
515
|
+
].join("\n"),
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
const result = await executeJs(
|
|
519
|
+
`import type { TypeOnly } from ${typesSpec};\nconst mod = await import(${entrySpec});\nreturn mod.typeOnly + mod.inlineType;`,
|
|
520
|
+
{
|
|
521
|
+
sessionId,
|
|
522
|
+
session,
|
|
523
|
+
sessionFile,
|
|
524
|
+
},
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
expect(result.exitCode).toBe(0);
|
|
528
|
+
expect(result.output.trim()).toBe("42");
|
|
529
|
+
});
|
|
530
|
+
|
|
495
531
|
it("refreshes the Python tool proxy when bridge env appears after kernel warm-up", async () => {
|
|
496
532
|
using tempDir = TempDir.createSync("@omp-eval-py-tool-proxy-");
|
|
497
533
|
const sessionFile = path.join(tempDir.path(), "session.jsonl");
|
|
@@ -88,7 +88,10 @@ export class LocalModuleLoader {
|
|
|
88
88
|
|
|
89
89
|
async #buildLocalModule(modulePath: string): Promise<LocalModuleEntry> {
|
|
90
90
|
const rawSource = fs.readFileSync(modulePath, "utf8");
|
|
91
|
-
const stripped = stripTypeScriptSyntax(rawSource
|
|
91
|
+
const stripped = stripTypeScriptSyntax(rawSource, {
|
|
92
|
+
force: isTypeScriptModulePath(modulePath),
|
|
93
|
+
loader: stripLoaderForPath(modulePath),
|
|
94
|
+
});
|
|
92
95
|
const moduleDir = path.dirname(modulePath);
|
|
93
96
|
const localDeps = new Set<string>();
|
|
94
97
|
for (const specifier of collectModuleSourceSpecifiers(stripped)) {
|
|
@@ -251,6 +254,15 @@ function isLocalPathSpecifier(source: string): boolean {
|
|
|
251
254
|
);
|
|
252
255
|
}
|
|
253
256
|
|
|
257
|
+
function isTypeScriptModulePath(modulePath: string): boolean {
|
|
258
|
+
const ext = path.extname(modulePath);
|
|
259
|
+
return ext === ".ts" || ext === ".tsx" || ext === ".mts";
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function stripLoaderForPath(modulePath: string): "ts" | "tsx" {
|
|
263
|
+
return path.extname(modulePath) === ".tsx" ? "tsx" : "ts";
|
|
264
|
+
}
|
|
265
|
+
|
|
254
266
|
function isManagedLocalModulePath(target: string): boolean {
|
|
255
267
|
return (
|
|
256
268
|
path.isAbsolute(target) &&
|
|
@@ -75,6 +75,7 @@ function parseProgram(code: string): { program: { body: ReadonlyArray<BabelProgr
|
|
|
75
75
|
allowSuperOutsideMethod: true,
|
|
76
76
|
allowUndeclaredExports: true,
|
|
77
77
|
errorRecovery: true,
|
|
78
|
+
plugins: ["typescript"],
|
|
78
79
|
}) as unknown as { program: { body: ReadonlyArray<BabelProgramNode> } };
|
|
79
80
|
} catch {
|
|
80
81
|
return null;
|
|
@@ -178,8 +179,7 @@ export function rewriteImports(code: string): string {
|
|
|
178
179
|
if (node.type !== "CallExpression") return;
|
|
179
180
|
const call = node as unknown as { callee?: { type?: string; start?: number; end?: number } };
|
|
180
181
|
const callee = call.callee;
|
|
181
|
-
if (
|
|
182
|
-
return;
|
|
182
|
+
if (callee?.type !== "Import" || typeof callee.start !== "number" || typeof callee.end !== "number") return;
|
|
183
183
|
edits.push({ start: callee.start, end: callee.end, text: "__omp_import__" });
|
|
184
184
|
});
|
|
185
185
|
|
|
@@ -252,12 +252,7 @@ export function rewriteDynamicImports(code: string, callee = "__omp_import__"):
|
|
|
252
252
|
if (node.type !== "CallExpression") return;
|
|
253
253
|
const call = node as unknown as { callee?: { type?: string; start?: number; end?: number } };
|
|
254
254
|
const callCallee = call.callee;
|
|
255
|
-
if (
|
|
256
|
-
!callCallee ||
|
|
257
|
-
callCallee.type !== "Import" ||
|
|
258
|
-
typeof callCallee.start !== "number" ||
|
|
259
|
-
typeof callCallee.end !== "number"
|
|
260
|
-
) {
|
|
255
|
+
if (callCallee?.type !== "Import" || typeof callCallee.start !== "number" || typeof callCallee.end !== "number") {
|
|
261
256
|
return;
|
|
262
257
|
}
|
|
263
258
|
edits.push({ start: callCallee.start, end: callCallee.end, text: callee });
|
|
@@ -453,38 +448,48 @@ function requiresAsyncWrapper(code: string): boolean {
|
|
|
453
448
|
}
|
|
454
449
|
|
|
455
450
|
/**
|
|
456
|
-
* Strip TypeScript syntax (type annotations, `interface`, `as`,
|
|
457
|
-
* call expressions, etc.) before the import/lexical rewriters parse
|
|
458
|
-
* native transpiler
|
|
459
|
-
*
|
|
451
|
+
* Strip TypeScript syntax (type annotations, type-only imports/exports, `interface`, `as`,
|
|
452
|
+
* `satisfies`, generics in call expressions, etc.) before the import/lexical rewriters parse
|
|
453
|
+
* the code. Bun's native transpiler preserves `import`/`export` declarations, so downstream
|
|
454
|
+
* Babel rewrites still control module resolution.
|
|
460
455
|
*
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
*
|
|
456
|
+
* Eval cells use a cheap "looks like TS" heuristic to avoid transpiling ordinary JS. Known
|
|
457
|
+
* TypeScript modules pass `force` because a file can contain TS-only module syntax such as
|
|
458
|
+
* `import type` without any value-level type annotations.
|
|
464
459
|
*/
|
|
465
|
-
|
|
466
|
-
|
|
460
|
+
type TypeScriptStripLoader = "ts" | "tsx";
|
|
461
|
+
|
|
462
|
+
const TS_TRANSPILER = new Bun.Transpiler({ loader: "ts" });
|
|
463
|
+
const TSX_TRANSPILER = new Bun.Transpiler({ loader: "tsx" });
|
|
464
|
+
|
|
465
|
+
function stripTypeScript(code: string, options: { force?: boolean; loader?: TypeScriptStripLoader } = {}): string {
|
|
466
|
+
if (!options.force && !LOOKS_LIKE_TS.test(code)) return code;
|
|
467
467
|
try {
|
|
468
|
-
|
|
468
|
+
const transpiler = options.loader === "tsx" ? TSX_TRANSPILER : TS_TRANSPILER;
|
|
469
|
+
return transpiler.transformSync(code);
|
|
469
470
|
} catch {
|
|
470
471
|
// Transpiler failed (e.g. unrecoverable syntax). Hand the original source back so the
|
|
471
472
|
// downstream rewriter / VM surfaces the real error to the user.
|
|
472
473
|
return code;
|
|
473
474
|
}
|
|
474
475
|
}
|
|
475
|
-
export function stripTypeScriptSyntax(
|
|
476
|
-
|
|
476
|
+
export function stripTypeScriptSyntax(
|
|
477
|
+
code: string,
|
|
478
|
+
options: { force?: boolean; loader?: TypeScriptStripLoader } = {},
|
|
479
|
+
): string {
|
|
480
|
+
return stripTypeScript(code, options);
|
|
477
481
|
}
|
|
478
482
|
|
|
479
|
-
// Heuristic:
|
|
480
|
-
// won't match because we require a leading word boundary plus a
|
|
483
|
+
// Heuristic: obvious TS-only tokens, including type-only module syntax. Plain JS using `as`
|
|
484
|
+
// only inside strings won't match because we require a leading word boundary plus a
|
|
485
|
+
// colon/keyword neighbor.
|
|
481
486
|
const LOOKS_LIKE_TS =
|
|
482
|
-
/(?:\binterface\s+\w|\btype\s+\w+\s*=|\b(?:as|satisfies)\s+(?:[A-Z]|\bconst\b)|:\s*(?:string|number|boolean|any|unknown|void|never|object|[A-Z]\w*)\b|<\s*[A-Z]\w*\s*[,>])/;
|
|
487
|
+
/(?:\bimport\s+type\b|\bexport\s+type\b|\b(?:import|export)\s*\{[^}\n]*\btype\s+\w|\binterface\s+\w|\btype\s+\w+\s*=|\b(?:as|satisfies)\s+(?:[A-Z]|\bconst\b)|:\s*(?:string|number|boolean|any|unknown|void|never|object|[A-Z]\w*)\b|<\s*[A-Z]\w*\s*[,>])/;
|
|
483
488
|
|
|
484
489
|
export function wrapCode(code: string): { source: string; asyncWrapped: boolean; finalExpressionReturned: boolean } {
|
|
485
|
-
const
|
|
486
|
-
const
|
|
487
|
-
const importsRewritten = rewriteImports(
|
|
490
|
+
const finalExpression = returnFinalExpression(code);
|
|
491
|
+
const stripped = stripTypeScript(finalExpression.source);
|
|
492
|
+
const importsRewritten = rewriteImports(stripped);
|
|
488
493
|
const needsAsyncWrapper = requiresAsyncWrapper(importsRewritten);
|
|
489
494
|
const rewritten = {
|
|
490
495
|
source: demoteTopLevelLexicals(importsRewritten, { publishGlobals: needsAsyncWrapper }),
|
|
@@ -16,7 +16,7 @@ import * as path from "node:path";
|
|
|
16
16
|
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
17
17
|
import { applyQuery, pathToQuery } from "./json-query";
|
|
18
18
|
import { artifactsDirsFromRegistry } from "./registry-helpers";
|
|
19
|
-
import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
|
|
19
|
+
import type { InternalResource, InternalUrl, ProtocolHandler, UrlCompletion } from "./types";
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Handler for agent:// URLs.
|
|
@@ -126,4 +126,21 @@ export class AgentProtocolHandler implements ProtocolHandler {
|
|
|
126
126
|
notes,
|
|
127
127
|
};
|
|
128
128
|
}
|
|
129
|
+
|
|
130
|
+
async complete(): Promise<UrlCompletion[]> {
|
|
131
|
+
const ids = new Set<string>();
|
|
132
|
+
for (const dir of artifactsDirsFromRegistry()) {
|
|
133
|
+
let files: string[];
|
|
134
|
+
try {
|
|
135
|
+
files = await fs.readdir(dir);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
if (isEnoent(err)) continue;
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
for (const f of files) {
|
|
141
|
+
if (f.endsWith(".md")) ids.add(f.slice(0, -3));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return [...ids].sort().map(value => ({ value }));
|
|
145
|
+
}
|
|
129
146
|
}
|
|
@@ -13,7 +13,7 @@ import * as fs from "node:fs/promises";
|
|
|
13
13
|
import * as path from "node:path";
|
|
14
14
|
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
15
15
|
import { artifactsDirsFromRegistry } from "./registry-helpers";
|
|
16
|
-
import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
|
|
16
|
+
import type { InternalResource, InternalUrl, ProtocolHandler, UrlCompletion } from "./types";
|
|
17
17
|
|
|
18
18
|
export class ArtifactProtocolHandler implements ProtocolHandler {
|
|
19
19
|
readonly scheme = "artifact";
|
|
@@ -77,4 +77,22 @@ export class ArtifactProtocolHandler implements ProtocolHandler {
|
|
|
77
77
|
sourcePath: foundPath,
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
|
+
|
|
81
|
+
async complete(): Promise<UrlCompletion[]> {
|
|
82
|
+
const ids = new Set<string>();
|
|
83
|
+
for (const dir of artifactsDirsFromRegistry()) {
|
|
84
|
+
let files: string[];
|
|
85
|
+
try {
|
|
86
|
+
files = await fs.readdir(dir);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (isEnoent(err)) continue;
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
for (const f of files) {
|
|
92
|
+
const m = f.match(/^(\d+)\./);
|
|
93
|
+
if (m) ids.add(m[1]!);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return [...ids].sort((a, b) => Number(a) - Number(b)).map(value => ({ value }));
|
|
97
|
+
}
|
|
80
98
|
}
|