@fancyboi999/open-tag-daemon 0.2.0 → 0.3.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 (2) hide show
  1. package/dist/cli.mjs +188 -1
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -5669,6 +5669,190 @@ async function readWorkspaceFile(agentId, rel) {
5669
5669
  }
5670
5670
  }
5671
5671
 
5672
+ // src/daemon/listModels.ts
5673
+ import { spawn as spawn8 } from "node:child_process";
5674
+ var titleCase = (s) => s ? s[0].toUpperCase() + s.slice(1) : s;
5675
+ function isModelId(s) {
5676
+ return /^[A-Za-z][A-Za-z0-9\-_./]*$/.test(s);
5677
+ }
5678
+ var CLAUDE_MODELS = [
5679
+ { id: "sonnet", label: "Sonnet" },
5680
+ { id: "opus", label: "Opus" },
5681
+ { id: "haiku", label: "Haiku" }
5682
+ ];
5683
+ var CLAUDE_EFFORT_LABEL = {
5684
+ low: "Low",
5685
+ medium: "Medium",
5686
+ high: "High",
5687
+ xhigh: "Extra high",
5688
+ max: "Max"
5689
+ };
5690
+ var CLAUDE_MODEL_EFFORT_ALLOW = {
5691
+ opus: /* @__PURE__ */ new Set(["low", "medium", "high", "xhigh", "max"]),
5692
+ sonnet: /* @__PURE__ */ new Set(["low", "medium", "high", "max"]),
5693
+ haiku: /* @__PURE__ */ new Set(["low", "medium", "high"])
5694
+ };
5695
+ function parseClaudeEffortLevels(helpText) {
5696
+ const m = /--effort\s*(?:<[^>]+>)?\s*(?:Effort level[^(]*)?\(([^)]+)\)/.exec(helpText);
5697
+ if (!m) return [];
5698
+ return m[1].split(",").map((s) => s.trim()).filter((s) => /^[a-z]+$/i.test(s));
5699
+ }
5700
+ function claudeThinkingForModel(modelId, superset) {
5701
+ const allow = CLAUDE_MODEL_EFFORT_ALLOW[modelId];
5702
+ const levels = superset.filter((v) => !allow || allow.has(v)).map((v) => ({ value: v, label: CLAUDE_EFFORT_LABEL[v] ?? titleCase(v) }));
5703
+ if (!levels.length) return void 0;
5704
+ return { levels, default: levels.some((l) => l.value === "medium") ? "medium" : levels[0].value };
5705
+ }
5706
+ function parseCodexModels(jsonStr) {
5707
+ let parsed;
5708
+ try {
5709
+ parsed = JSON.parse(jsonStr);
5710
+ } catch {
5711
+ return [];
5712
+ }
5713
+ const models = Array.isArray(parsed?.models) ? parsed.models : [];
5714
+ const out = [];
5715
+ for (const m of models) {
5716
+ if (m?.visibility !== "list") continue;
5717
+ const slug = typeof m?.slug === "string" ? m.slug : "";
5718
+ if (!slug) continue;
5719
+ const raw = Array.isArray(m?.supported_reasoning_levels) ? m.supported_reasoning_levels : [];
5720
+ const levels = raw.map((l) => ({ value: String(l?.effort ?? ""), label: titleCase(String(l?.effort ?? "")), description: typeof l?.description === "string" ? l.description : void 0 })).filter((l) => l.value);
5721
+ const thinking = levels.length ? { levels, default: typeof m?.default_reasoning_level === "string" ? m.default_reasoning_level : void 0 } : void 0;
5722
+ out.push({ id: slug, label: typeof m?.display_name === "string" && m.display_name ? m.display_name : slug, provider: "openai", ...thinking ? { thinking } : {} });
5723
+ }
5724
+ return out;
5725
+ }
5726
+ function parseOpencodeModels(stdout) {
5727
+ const out = [];
5728
+ for (const raw of stdout.split("\n")) {
5729
+ const line = raw.trim();
5730
+ if (!line) continue;
5731
+ if (line.startsWith("{") || line.startsWith('"') || line.startsWith("}")) continue;
5732
+ if (line === line.toUpperCase() && /[A-Z]/.test(line)) continue;
5733
+ const id = line.split(/\s+/)[0];
5734
+ const slash = id.indexOf("/");
5735
+ if (slash <= 0 || slash >= id.length - 1) continue;
5736
+ out.push({ id, label: id, provider: id.slice(0, slash) });
5737
+ }
5738
+ return out;
5739
+ }
5740
+ function parseCursorModels(stdout) {
5741
+ const out = [];
5742
+ for (const raw of stdout.split("\n")) {
5743
+ const line = raw.trim();
5744
+ if (!line) continue;
5745
+ const sep = line.indexOf(" - ");
5746
+ if (sep < 0) continue;
5747
+ const id = line.slice(0, sep).trim();
5748
+ if (!isModelId(id)) continue;
5749
+ let label = line.slice(sep + 3).trim();
5750
+ const isDefault = /default/i.test(label);
5751
+ const paren = label.indexOf("(");
5752
+ if (paren >= 0) label = label.slice(0, paren).trim();
5753
+ out.push({ id, label: label || id, provider: "cursor", ...isDefault ? { default: true } : {} });
5754
+ }
5755
+ return out;
5756
+ }
5757
+ function parsePiModels(out) {
5758
+ const res = [];
5759
+ for (const raw of out.split("\n")) {
5760
+ const line = raw.trim();
5761
+ if (!line) continue;
5762
+ if (isPiNoise(line)) continue;
5763
+ const fields = line.split(/\s+/);
5764
+ const first = fields[0];
5765
+ if (first.toLowerCase() === "provider") continue;
5766
+ let id;
5767
+ if (first.includes(":") || first.includes("/")) id = first.replace(":", "/");
5768
+ else if (fields.length >= 2) id = `${first}/${fields[1]}`;
5769
+ else continue;
5770
+ const slash = id.indexOf("/");
5771
+ if (slash <= 0 || slash >= id.length - 1) continue;
5772
+ res.push({ id, label: id, provider: id.slice(0, slash) });
5773
+ }
5774
+ return res;
5775
+ }
5776
+ function isPiNoise(line) {
5777
+ const l = line.toLowerCase();
5778
+ return l.includes("no models match pattern") || l.startsWith("warning:") || l.startsWith("error:") || l.startsWith("info:");
5779
+ }
5780
+ var LIST_TIMEOUT_MS = 7e3;
5781
+ var OUT_CAP = 256 * 1024;
5782
+ function runList(bin, args2, timeoutMs = LIST_TIMEOUT_MS) {
5783
+ return new Promise((resolve) => {
5784
+ const env = { ...process.env };
5785
+ delete env.NODE_OPTIONS;
5786
+ let proc;
5787
+ try {
5788
+ proc = spawn8(bin, args2, { stdio: ["ignore", "pipe", "pipe"], env });
5789
+ } catch (e) {
5790
+ return resolve({ stdout: "", stderr: String(e?.message ?? e), code: 1 });
5791
+ }
5792
+ let stdout = "";
5793
+ let stderr = "";
5794
+ proc.stdout?.on("data", (c) => {
5795
+ if (stdout.length < OUT_CAP) stdout += c.toString();
5796
+ });
5797
+ proc.stderr?.on("data", (c) => {
5798
+ if (stderr.length < OUT_CAP) stderr += c.toString();
5799
+ });
5800
+ const timer = setTimeout(() => {
5801
+ try {
5802
+ proc.kill("SIGKILL");
5803
+ } catch {
5804
+ }
5805
+ }, timeoutMs);
5806
+ proc.on("error", (e) => {
5807
+ clearTimeout(timer);
5808
+ resolve({ stdout, stderr: stderr || String(e?.message ?? e), code: 1 });
5809
+ });
5810
+ proc.on("exit", (code) => {
5811
+ clearTimeout(timer);
5812
+ resolve({ stdout, stderr, code });
5813
+ });
5814
+ });
5815
+ }
5816
+ async function listModels(runtime) {
5817
+ switch (runtime) {
5818
+ case "opencode": {
5819
+ let r = await runList("opencode", ["models", "--verbose"], 5e3);
5820
+ let models = parseOpencodeModels(r.stdout);
5821
+ if (!models.length) {
5822
+ r = await runList("opencode", ["models"], 2e3);
5823
+ models = parseOpencodeModels(r.stdout);
5824
+ }
5825
+ return models.length ? models : null;
5826
+ }
5827
+ case "cursor": {
5828
+ const r = await runList("cursor-agent", ["--list-models"]);
5829
+ const models = parseCursorModels(r.stdout);
5830
+ return models.length ? models : null;
5831
+ }
5832
+ case "pi": {
5833
+ const r = await runList("pi", ["--list-models"]);
5834
+ const models = parsePiModels(r.stdout || r.stderr);
5835
+ return models.length ? models : null;
5836
+ }
5837
+ case "claude": {
5838
+ const r = await runList("claude", ["--help"]);
5839
+ const superset = parseClaudeEffortLevels(r.stdout || r.stderr);
5840
+ if (!superset.length) return null;
5841
+ return CLAUDE_MODELS.map((m) => {
5842
+ const thinking = claudeThinkingForModel(m.id, superset);
5843
+ return { ...m, provider: "anthropic", ...thinking ? { thinking } : {} };
5844
+ });
5845
+ }
5846
+ case "codex": {
5847
+ const r = await runList("codex", ["debug", "models"]);
5848
+ const models = parseCodexModels(r.stdout);
5849
+ return models.length ? models : null;
5850
+ }
5851
+ default:
5852
+ return null;
5853
+ }
5854
+ }
5855
+
5672
5856
  // src/daemon/index.ts
5673
5857
  var log = createLogger("daemon");
5674
5858
  var args = process.argv.slice(2);
@@ -5734,6 +5918,9 @@ conn = new Connection(serverUrl, apiKey, (msg) => {
5734
5918
  case "agent:skills:list":
5735
5919
  void listSkills(msg.agentId).then((r) => conn.send({ type: "skills:list", requestId: msg.requestId, agentId: msg.agentId, ...r }));
5736
5920
  break;
5921
+ case "probe-models":
5922
+ void listModels(msg.runtime ?? "").then((models) => conn.send({ type: "models", requestId: msg.requestId, runtime: msg.runtime, models })).catch((e) => conn.send({ type: "models", requestId: msg.requestId, runtime: msg.runtime, models: null, error: String(e?.message ?? e) }));
5923
+ break;
5737
5924
  case "ping":
5738
5925
  conn.send({ type: "pong" });
5739
5926
  break;
@@ -5748,7 +5935,7 @@ conn = new Connection(serverUrl, apiKey, (msg) => {
5748
5935
  runningAgents: mgr.running(),
5749
5936
  hostname: os4.hostname(),
5750
5937
  os: `${os4.platform()} ${os4.arch()}`,
5751
- daemonVersion: "0.1.0",
5938
+ daemonVersion: "0.3.0",
5752
5939
  machineId: readMachineId()
5753
5940
  // Stable identity: empty on first connection; server sends it back via ready:ack for persistence.
5754
5941
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fancyboi999/open-tag-daemon",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "open-tag compute-plane daemon — connect any machine to an open-tag server so its agents run there. No repo clone needed.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",