@subshell-ai/plugin-oh-my-pi 0.2.2 → 0.2.3
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/dist/index.js +165 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -486,7 +486,7 @@ export default function (pi) {
|
|
|
486
486
|
const parsed = parseManifest({
|
|
487
487
|
name: "@subshell-ai/plugin-oh-my-pi",
|
|
488
488
|
description: "Oh My Pi harness plugin for Subshell",
|
|
489
|
-
version: "0.2.
|
|
489
|
+
version: "0.2.3",
|
|
490
490
|
license: "Apache-2.0",
|
|
491
491
|
type: "module",
|
|
492
492
|
main: "dist/index.js",
|
|
@@ -598,6 +598,163 @@ const SETTINGS = [
|
|
|
598
598
|
*/
|
|
599
599
|
const TITLE_PREFIX = /^π(?:$|\s+(?:[>!:]|[⠁-⣿]|[◔◑◕○●]|[-\\/|])|:(?=\s|$))\s*/u;
|
|
600
600
|
/**
|
|
601
|
+
* Loads that provider from the environment. The source is fixed: it names the
|
|
602
|
+
* variable and never interpolates its value, so no preset data (and no
|
|
603
|
+
* credential) is written to a node. A launch without the variable loads
|
|
604
|
+
* nothing, which keeps the module inert for every other omp preset.
|
|
605
|
+
*
|
|
606
|
+
* The value is preset-controlled, so malformed or incomplete JSON leaves the
|
|
607
|
+
* module inert too. It must match what `model-cli omp` generates and nothing
|
|
608
|
+
* else: the provider keeps model-cli's namespace, so a preset cannot override
|
|
609
|
+
* a built-in provider; the apiKey is the env-var name rather than a literal
|
|
610
|
+
* secret; and no field may carry omp's `!command` config syntax, which would
|
|
611
|
+
* run shell commands as the pane user. Registration is queued outside the
|
|
612
|
+
* extension factory's error guard, so a value omp would reject there would
|
|
613
|
+
* take the pane down at startup instead of warning. One debug line names the
|
|
614
|
+
* problem, never the value.
|
|
615
|
+
*
|
|
616
|
+
* A preset built from `model-cli omp` used to name its provider by an
|
|
617
|
+
* `--extension` path in the generating host's cache, so it only worked on
|
|
618
|
+
* machines where model-cli had run. This module rides the per-launch
|
|
619
|
+
* extension directory the host already ships to every node instead.
|
|
620
|
+
*/
|
|
621
|
+
const MODEL_CLI_PROVIDER_SHIM = `export default function(pi) {
|
|
622
|
+
const raw = process.env.MODEL_CLI_OMP_CONFIG;
|
|
623
|
+
if (!raw) return;
|
|
624
|
+
let config;
|
|
625
|
+
try {
|
|
626
|
+
config = JSON.parse(raw);
|
|
627
|
+
} catch {
|
|
628
|
+
pi.logger?.debug?.("model-cli provider config ignored: MODEL_CLI_OMP_CONFIG is not JSON");
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const CONNECTION_KEYS = ["baseUrl", "api", "apiKey", "models"];
|
|
633
|
+
const MODEL_KEYS = [
|
|
634
|
+
"id",
|
|
635
|
+
"name",
|
|
636
|
+
"contextWindow",
|
|
637
|
+
"maxTokens",
|
|
638
|
+
"reasoning",
|
|
639
|
+
"input",
|
|
640
|
+
"cost",
|
|
641
|
+
"compat",
|
|
642
|
+
"thinking",
|
|
643
|
+
];
|
|
644
|
+
const COST_KEYS = ["input", "output", "cacheRead", "cacheWrite"];
|
|
645
|
+
const COMPAT_KEYS = [
|
|
646
|
+
"maxTokensField",
|
|
647
|
+
"reasoningContentField",
|
|
648
|
+
"replayReasoningContent",
|
|
649
|
+
"requiresReasoningContentForAllAssistantTurns",
|
|
650
|
+
"supportsDeveloperRole",
|
|
651
|
+
"supportsStore",
|
|
652
|
+
"supportsStrictMode",
|
|
653
|
+
"thinkingFormat",
|
|
654
|
+
];
|
|
655
|
+
const THINKING_KEYS = ["mode", "efforts", "effortMap"];
|
|
656
|
+
|
|
657
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
658
|
+
const only = (value, keys) => Object.keys(value).every((key) => keys.includes(key));
|
|
659
|
+
const text = (value) => typeof value === "string" && value !== "";
|
|
660
|
+
const amount = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
661
|
+
const positive = (value) => amount(value) && value > 0;
|
|
662
|
+
const command = (value) => {
|
|
663
|
+
if (typeof value === "string") return value.startsWith("!");
|
|
664
|
+
if (Array.isArray(value)) return value.some(command);
|
|
665
|
+
if (isObject(value)) return Object.values(value).some(command);
|
|
666
|
+
return false;
|
|
667
|
+
};
|
|
668
|
+
const cost = (value) => isObject(value) && only(value, COST_KEYS) && Object.values(value).every(amount);
|
|
669
|
+
const compat = (value) =>
|
|
670
|
+
isObject(value) &&
|
|
671
|
+
only(value, COMPAT_KEYS) &&
|
|
672
|
+
Object.values(value).every((item) => typeof item === "string" || typeof item === "boolean");
|
|
673
|
+
const thinking = (value) =>
|
|
674
|
+
isObject(value) &&
|
|
675
|
+
only(value, THINKING_KEYS) &&
|
|
676
|
+
(value.mode === "anthropic-adaptive" || value.mode === "effort") &&
|
|
677
|
+
Array.isArray(value.efforts) &&
|
|
678
|
+
value.efforts.every(text) &&
|
|
679
|
+
isObject(value.effortMap) &&
|
|
680
|
+
Object.values(value.effortMap).every(text);
|
|
681
|
+
const model = (value) =>
|
|
682
|
+
isObject(value) &&
|
|
683
|
+
only(value, MODEL_KEYS) &&
|
|
684
|
+
text(value.id) &&
|
|
685
|
+
(value.name === undefined || text(value.name)) &&
|
|
686
|
+
(value.contextWindow === undefined || positive(value.contextWindow)) &&
|
|
687
|
+
(value.maxTokens === undefined || positive(value.maxTokens)) &&
|
|
688
|
+
(value.reasoning === undefined || typeof value.reasoning === "boolean") &&
|
|
689
|
+
(value.input === undefined ||
|
|
690
|
+
(Array.isArray(value.input) && value.input.every((item) => item === "text" || item === "image"))) &&
|
|
691
|
+
(value.cost === undefined || cost(value.cost)) &&
|
|
692
|
+
(value.compat === undefined || compat(value.compat)) &&
|
|
693
|
+
(value.thinking === undefined || thinking(value.thinking));
|
|
694
|
+
|
|
695
|
+
const connection = config?.connection;
|
|
696
|
+
const models = connection?.models;
|
|
697
|
+
const usable =
|
|
698
|
+
typeof config?.provider === "string" &&
|
|
699
|
+
/^model-cli-omp-(?:litellm|direct)-\\S+$/.test(config.provider) &&
|
|
700
|
+
isObject(connection) &&
|
|
701
|
+
only(connection, CONNECTION_KEYS) &&
|
|
702
|
+
typeof connection.baseUrl === "string" &&
|
|
703
|
+
/^https?:\\/\\/.+/.test(connection.baseUrl) &&
|
|
704
|
+
(connection.api === "anthropic-messages" || connection.api === "openai-completions") &&
|
|
705
|
+
connection.apiKey === "MODEL_CLI_OMP_API_KEY" &&
|
|
706
|
+
Array.isArray(models) &&
|
|
707
|
+
models.length > 0 &&
|
|
708
|
+
models.every(model) &&
|
|
709
|
+
!command(config);
|
|
710
|
+
if (!usable) {
|
|
711
|
+
pi.logger?.debug?.("model-cli provider config ignored: MODEL_CLI_OMP_CONFIG is not the model-cli shape");
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
pi.registerProvider(config.provider, connection);
|
|
715
|
+
}
|
|
716
|
+
`;
|
|
717
|
+
/**
|
|
718
|
+
* A preset built from `model-cli omp` used to prove its provider with an
|
|
719
|
+
* extension path in the generating host's cache. That file is writable by
|
|
720
|
+
* anything running as the pane user and persists between launches, so the
|
|
721
|
+
* provider module below is a replacement, not a companion: the legacy flag is
|
|
722
|
+
* dropped when this launch writes its own module. omp accepts the path as
|
|
723
|
+
* `--extension`, `--trusted-extension`, `-e` or `--hook`, so every spelling is
|
|
724
|
+
* matched, and the path is normalized first because `./` and doubled
|
|
725
|
+
* separators name the same file.
|
|
726
|
+
*/
|
|
727
|
+
const LEGACY_PROVIDER_FLAGS = [
|
|
728
|
+
"--extension",
|
|
729
|
+
"--trusted-extension",
|
|
730
|
+
"-e",
|
|
731
|
+
"--hook"
|
|
732
|
+
];
|
|
733
|
+
const LEGACY_PROVIDER_INLINE = [
|
|
734
|
+
"--extension=",
|
|
735
|
+
"--trusted-extension=",
|
|
736
|
+
"-e=",
|
|
737
|
+
"--hook="
|
|
738
|
+
];
|
|
739
|
+
function isLegacyProviderPath(value) {
|
|
740
|
+
const segments = value.trim().replace(/\\/g, "/").split("/").filter((segment) => segment !== "" && segment !== ".");
|
|
741
|
+
return /(?:^|\/)model-cli\/omp-provider\.mjs$/.test(segments.join("/"));
|
|
742
|
+
}
|
|
743
|
+
function withoutLegacyProviderExtension(flags) {
|
|
744
|
+
const kept = [];
|
|
745
|
+
for (let i = 0; i < flags.length; i++) {
|
|
746
|
+
const flag = flags[i];
|
|
747
|
+
if (LEGACY_PROVIDER_FLAGS.includes(flag) && isLegacyProviderPath(flags[i + 1] ?? "")) {
|
|
748
|
+
i += 1;
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
const inline = LEGACY_PROVIDER_INLINE.find((prefix) => flag.startsWith(prefix));
|
|
752
|
+
if (inline && isLegacyProviderPath(flag.slice(inline.length))) continue;
|
|
753
|
+
kept.push(flag);
|
|
754
|
+
}
|
|
755
|
+
return kept;
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
601
758
|
* Oh My Pi uses its own executable and native MCP discovery, independently of
|
|
602
759
|
* pi. An explicit extension directory contributes mcp.json without redirecting
|
|
603
760
|
* the user's config or credentials. Verified against omp 18.12.6.
|
|
@@ -630,7 +787,7 @@ const createPlugin = () => ({
|
|
|
630
787
|
const value = settings[key];
|
|
631
788
|
if (typeof value === "string" && value) args.push(`--${key}`, value);
|
|
632
789
|
}
|
|
633
|
-
args.push(...preset.flags, ...extraFlags ?? []);
|
|
790
|
+
args.push(...mcp ? withoutLegacyProviderExtension(preset.flags) : preset.flags, ...extraFlags ?? []);
|
|
634
791
|
return args;
|
|
635
792
|
},
|
|
636
793
|
mcpRegistration(launch, configPath) {
|
|
@@ -646,10 +803,15 @@ const createPlugin = () => ({
|
|
|
646
803
|
name: "subshell-mcp",
|
|
647
804
|
private: true,
|
|
648
805
|
type: "module",
|
|
649
|
-
omp: { extensions: [
|
|
806
|
+
omp: { extensions: [
|
|
807
|
+
"bridge.mjs",
|
|
808
|
+
"subagents.mjs",
|
|
809
|
+
"provider.mjs"
|
|
810
|
+
] }
|
|
650
811
|
}),
|
|
651
812
|
"bridge.mjs": "export default function () {}\n",
|
|
652
813
|
"subagents.mjs": SUBAGENT_BRIDGE,
|
|
814
|
+
"provider.mjs": MODEL_CLI_PROVIDER_SHIM,
|
|
653
815
|
"mcp.json": fileContent
|
|
654
816
|
},
|
|
655
817
|
args: ["--extension", `${configPath}.d`]
|