@oeronteros-1/opencode-orchestra 1.0.22 → 1.0.24
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/README.md +19 -5
- package/dashboard-dist/assets/index-Dc5lp8wX.js +124 -0
- package/dashboard-dist/index.html +1 -1
- package/dist/agents/build.js +4 -0
- package/dist/agents/build.js.map +1 -1
- package/dist/agents/editor.d.ts +3 -0
- package/dist/agents/editor.js +12 -0
- package/dist/agents/editor.js.map +1 -0
- package/dist/agents/integrator.d.ts +3 -0
- package/dist/agents/integrator.js +12 -0
- package/dist/agents/integrator.js.map +1 -0
- package/dist/agents/lead.js +3 -1
- package/dist/agents/lead.js.map +1 -1
- package/dist/agents/workers.js +1 -6
- package/dist/agents/workers.js.map +1 -1
- package/dist/cache-refresh.d.ts +42 -0
- package/dist/cache-refresh.js +130 -0
- package/dist/cache-refresh.js.map +1 -0
- package/dist/cli.d.ts +16 -2
- package/dist/cli.js +59 -26
- package/dist/cli.js.map +1 -1
- package/dist/config/schema.d.ts +11 -1
- package/dist/config/schema.js +30 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/dashboard/server.js +2 -2
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +35 -12
- package/dist/index.js.map +1 -1
- package/dist/orchestration/ownership.d.ts +7 -0
- package/dist/orchestration/ownership.js +45 -0
- package/dist/orchestration/ownership.js.map +1 -0
- package/dist/orchestration/worktree-adapter.d.ts +3 -0
- package/dist/orchestration/worktree-adapter.js +45 -0
- package/dist/orchestration/worktree-adapter.js.map +1 -0
- package/dist/orchestration/worktrees.d.ts +22 -0
- package/dist/orchestration/worktrees.js +27 -0
- package/dist/orchestration/worktrees.js.map +1 -0
- package/dist/pricing/cost.d.ts +29 -0
- package/dist/pricing/cost.js +47 -0
- package/dist/pricing/cost.js.map +1 -0
- package/dist/pricing/model-match.d.ts +59 -0
- package/dist/pricing/model-match.js +134 -0
- package/dist/pricing/model-match.js.map +1 -0
- package/dist/pricing/openrouter.d.ts +60 -0
- package/dist/pricing/openrouter.js +150 -0
- package/dist/pricing/openrouter.js.map +1 -0
- package/dist/pricing/resolver.d.ts +50 -0
- package/dist/pricing/resolver.js +145 -0
- package/dist/pricing/resolver.js.map +1 -0
- package/dist/prompts/load.js +1 -1
- package/dist/prompts/load.js.map +1 -1
- package/dist/routing/planner.d.ts +14 -1
- package/dist/routing/planner.js +23 -1
- package/dist/routing/planner.js.map +1 -1
- package/dist/routing/pricing/estimate.d.ts +14 -1
- package/dist/routing/pricing/estimate.js +81 -20
- package/dist/routing/pricing/estimate.js.map +1 -1
- package/dist/telemetry/ledger.d.ts +6 -1
- package/dist/telemetry/ledger.js +15 -3
- package/dist/telemetry/ledger.js.map +1 -1
- package/dist/tools.d.ts +3 -0
- package/dist/tools.js +44 -1
- package/dist/tools.js.map +1 -1
- package/package.json +16 -2
- package/prompts/lead.md +1 -0
- package/schema/opencode-orchestra.schema.json +23 -1
- package/dashboard-dist/assets/index-BZTn_d2K.js +0 -124
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export function normalizeOwnedPath(path) {
|
|
2
|
+
const normalized = path.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
|
|
3
|
+
if (!normalized || normalized.includes("\0") || normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized) || normalized.split("/").includes(".."))
|
|
4
|
+
throw new Error("ownership path must be repository-relative: " + path);
|
|
5
|
+
return normalized;
|
|
6
|
+
}
|
|
7
|
+
function overlaps(a, b) { return a === b || a.startsWith(b + "/") || b.startsWith(a + "/"); }
|
|
8
|
+
export function validateOwnership(partitions) {
|
|
9
|
+
const errors = [];
|
|
10
|
+
const normalized = partitions.map((p) => ({ id: p.id, paths: p.paths.map((path) => { try {
|
|
11
|
+
return normalizeOwnedPath(path);
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
errors.push(e.message);
|
|
15
|
+
return "";
|
|
16
|
+
} }).filter(Boolean) }));
|
|
17
|
+
for (let i = 0; i < normalized.length; i++)
|
|
18
|
+
for (let j = i + 1; j < normalized.length; j++)
|
|
19
|
+
for (const a of normalized[i].paths)
|
|
20
|
+
for (const b of normalized[j].paths)
|
|
21
|
+
if (overlaps(a, b))
|
|
22
|
+
errors.push("ownership overlap: " + normalized[i].id + ":" + a + " and " + normalized[j].id + ":" + b);
|
|
23
|
+
return [...new Set(errors)].sort();
|
|
24
|
+
}
|
|
25
|
+
export function validateChangedFiles(partitions, changed) {
|
|
26
|
+
const errors = validateOwnership(partitions);
|
|
27
|
+
const byId = new Map(partitions.map((p) => [p.id, p.paths.map(normalizeOwnedPath)]));
|
|
28
|
+
for (const [id, files] of Object.entries(changed))
|
|
29
|
+
for (const raw of files) {
|
|
30
|
+
let file;
|
|
31
|
+
try {
|
|
32
|
+
file = normalizeOwnedPath(raw);
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
errors.push(e.message);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
;
|
|
39
|
+
const owners = [...byId].filter(([, paths]) => paths.some((p) => file === p || file.startsWith(p + "/"))).map(([owner]) => owner);
|
|
40
|
+
if (owners.length !== 1 || owners[0] !== id)
|
|
41
|
+
errors.push("changed file " + file + " is not exclusively owned by " + id);
|
|
42
|
+
}
|
|
43
|
+
return [...new Set(errors)].sort();
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=ownership.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ownership.js","sourceRoot":"","sources":["../../src/orchestration/ownership.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
function safeId(value) {
|
|
5
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 80);
|
|
6
|
+
}
|
|
7
|
+
function runGit(cwd, args) {
|
|
8
|
+
return new Promise((resolvePromise, reject) => {
|
|
9
|
+
const child = spawn("git", args, { cwd, stdio: "ignore", windowsHide: true });
|
|
10
|
+
child.once("error", reject);
|
|
11
|
+
child.once("exit", (code) => code === 0 ? resolvePromise() : reject(new Error("git " + args.join(" ") + " exited with " + code)));
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/** OpenCode experimental workspace adapter backed by isolated local git worktrees. */
|
|
15
|
+
export function createGitWorktreeAdapter(repository, root = ".orchestra/worktrees") {
|
|
16
|
+
const base = resolve(repository);
|
|
17
|
+
const worktreeRoot = resolve(base, root);
|
|
18
|
+
const directoryOf = (config) => join(worktreeRoot, safeId(config.id));
|
|
19
|
+
return {
|
|
20
|
+
name: "Orchestra Git Worktree",
|
|
21
|
+
description: "Creates one isolated git worktree per parallel Orchestra editor.",
|
|
22
|
+
configure(config) {
|
|
23
|
+
return { ...config, directory: directoryOf(config), branch: config.branch ?? "orchestra/" + safeId(config.id) };
|
|
24
|
+
},
|
|
25
|
+
async create(config, _env, from) {
|
|
26
|
+
const directory = directoryOf(config);
|
|
27
|
+
const branch = config.branch ?? "orchestra/" + safeId(config.id);
|
|
28
|
+
await mkdir(dirname(directory), { recursive: true });
|
|
29
|
+
await runGit(base, ["worktree", "add", "-b", branch, directory, from?.branch ?? "HEAD"]);
|
|
30
|
+
},
|
|
31
|
+
async remove(config) {
|
|
32
|
+
const directory = directoryOf(config);
|
|
33
|
+
const branch = config.branch ?? "orchestra/" + safeId(config.id);
|
|
34
|
+
try {
|
|
35
|
+
await runGit(base, ["worktree", "remove", "--force", directory]);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
await rm(directory, { recursive: true, force: true });
|
|
39
|
+
await runGit(base, ["branch", "-D", branch]).catch(() => undefined);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
target(config) { return { type: "local", directory: directoryOf(config) }; },
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=worktree-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktree-adapter.js","sourceRoot":"","sources":["../../src/orchestration/worktree-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface GitResult {
|
|
2
|
+
stdout: string;
|
|
3
|
+
stderr: string;
|
|
4
|
+
exitCode: number;
|
|
5
|
+
}
|
|
6
|
+
export interface GitRunner {
|
|
7
|
+
run(args: string[], cwd: string): Promise<GitResult>;
|
|
8
|
+
}
|
|
9
|
+
export declare const systemGit: GitRunner;
|
|
10
|
+
export interface ChangedPath {
|
|
11
|
+
status: string;
|
|
12
|
+
path: string;
|
|
13
|
+
oldPath?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function parseNameStatusZ(output: string): ChangedPath[];
|
|
16
|
+
export declare function createEditorWorktree(git: GitRunner, repo: string, taskId: string, nodeId: string, baseSha: string, root: string): Promise<{
|
|
17
|
+
path: string;
|
|
18
|
+
branch: string;
|
|
19
|
+
}>;
|
|
20
|
+
export declare function assertCommitDescendsFromBase(git: GitRunner, repo: string, baseSha: string, commitSha: string): Promise<void>;
|
|
21
|
+
export declare function integrateValidatedCommits(git: GitRunner, repo: string, commits: string[]): Promise<void>;
|
|
22
|
+
export declare function collectCommitChanges(git: GitRunner, repo: string, baseSha: string, commitSha: string): Promise<ChangedPath[]>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
export const systemGit = { run(args, cwd) { return new Promise((resolve, reject) => { const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], windowsHide: true }); let stdout = "", stderr = ""; child.stdout.on("data", (chunk) => stdout += chunk); child.stderr.on("data", (chunk) => stderr += chunk); child.once("error", reject); child.once("exit", (exitCode) => resolve({ stdout, stderr, exitCode: exitCode ?? 1 })); }); } };
|
|
3
|
+
export function parseNameStatusZ(output) { const tokens = output.split("\0"); const result = []; for (let i = 0; i < tokens.length - 1;) {
|
|
4
|
+
const status = tokens[i++];
|
|
5
|
+
if (!status)
|
|
6
|
+
continue;
|
|
7
|
+
const code = status[0];
|
|
8
|
+
const first = tokens[i++];
|
|
9
|
+
if ((code === "R" || code === "C") && i < tokens.length)
|
|
10
|
+
result.push({ status, oldPath: first, path: tokens[i++] });
|
|
11
|
+
else
|
|
12
|
+
result.push({ status, path: first });
|
|
13
|
+
} return result; }
|
|
14
|
+
export async function createEditorWorktree(git, repo, taskId, nodeId, baseSha, root) { const safe = (value) => value.replace(/[^a-zA-Z0-9._-]/g, "-"); const branch = "orch/" + safe(taskId) + "/" + safe(nodeId); const path = root + "/" + safe(taskId) + "-" + safe(nodeId); const result = await git.run(["worktree", "add", "-b", branch, path, baseSha], repo); if (result.exitCode !== 0)
|
|
15
|
+
throw new Error(result.stderr || "git worktree add failed"); return { path, branch }; }
|
|
16
|
+
export async function assertCommitDescendsFromBase(git, repo, baseSha, commitSha) { const result = await git.run(["merge-base", "--is-ancestor", baseSha, commitSha], repo); if (result.exitCode !== 0)
|
|
17
|
+
throw new Error("commit is not descended from base: " + commitSha); }
|
|
18
|
+
export async function integrateValidatedCommits(git, repo, commits) { for (const commit of commits) {
|
|
19
|
+
const result = await git.run(["cherry-pick", commit], repo);
|
|
20
|
+
if (result.exitCode !== 0) {
|
|
21
|
+
await git.run(["cherry-pick", "--abort"], repo);
|
|
22
|
+
throw new Error("cherry-pick conflict for " + commit + ": " + result.stderr);
|
|
23
|
+
}
|
|
24
|
+
} }
|
|
25
|
+
export async function collectCommitChanges(git, repo, baseSha, commitSha) { const result = await git.run(["diff", "--name-status", "-z", "--find-renames", baseSha + "..." + commitSha], repo); if (result.exitCode !== 0)
|
|
26
|
+
throw new Error(result.stderr || "git diff failed"); return parseNameStatusZ(result.stdout); }
|
|
27
|
+
//# sourceMappingURL=worktrees.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktrees.js","sourceRoot":"","sources":["../../src/orchestration/worktrees.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type PricingStatus = "paid" | "free" | "subscription" | "unknown";
|
|
2
|
+
export interface PricingResolution {
|
|
3
|
+
status: PricingStatus;
|
|
4
|
+
/** USD per 1M input tokens (paid only). */
|
|
5
|
+
input?: number;
|
|
6
|
+
/** USD per 1M output tokens (paid only). */
|
|
7
|
+
output?: number;
|
|
8
|
+
/** USD per 1M reasoning tokens, when priced separately (paid only). */
|
|
9
|
+
reasoning?: number;
|
|
10
|
+
/** USD per 1M cached-input read tokens, when priced separately (paid only). */
|
|
11
|
+
cacheRead?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface UsageTokens {
|
|
14
|
+
input: number;
|
|
15
|
+
output: number;
|
|
16
|
+
reasoning?: number;
|
|
17
|
+
cacheRead?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface UsageCost {
|
|
20
|
+
inputTokens: number;
|
|
21
|
+
outputTokens: number;
|
|
22
|
+
reasoningTokens: number;
|
|
23
|
+
cacheReadTokens: number;
|
|
24
|
+
totalTokens: number;
|
|
25
|
+
/** USD; null exactly when pricing is unknown. Free/subscription cost 0. */
|
|
26
|
+
cost: number | null;
|
|
27
|
+
pricingStatus: PricingStatus;
|
|
28
|
+
}
|
|
29
|
+
export declare function calcCost(resolution: PricingResolution, usage: UsageTokens): UsageCost;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Pure cost calculation. Usage (tokens) and pricing (USD per 1M tokens) are
|
|
2
|
+
// separate concerns: tokens are always counted, while cost may be 0 (free /
|
|
3
|
+
// subscription) or null (unknown pricing). Unknown must never be silently
|
|
4
|
+
// treated as free.
|
|
5
|
+
export function calcCost(resolution, usage) {
|
|
6
|
+
const input = Math.max(0, usage.input ?? 0);
|
|
7
|
+
const output = Math.max(0, usage.output ?? 0);
|
|
8
|
+
const reasoning = Math.max(0, usage.reasoning ?? 0);
|
|
9
|
+
const cacheRead = Math.max(0, usage.cacheRead ?? 0);
|
|
10
|
+
const totalTokens = input + output + reasoning + cacheRead;
|
|
11
|
+
if (resolution.status === "paid") {
|
|
12
|
+
const cost = (input * (resolution.input ?? 0)
|
|
13
|
+
+ output * (resolution.output ?? 0)
|
|
14
|
+
+ reasoning * (resolution.reasoning ?? 0)
|
|
15
|
+
+ cacheRead * (resolution.cacheRead ?? 0)) / 1_000_000;
|
|
16
|
+
return {
|
|
17
|
+
inputTokens: input,
|
|
18
|
+
outputTokens: output,
|
|
19
|
+
reasoningTokens: reasoning,
|
|
20
|
+
cacheReadTokens: cacheRead,
|
|
21
|
+
totalTokens,
|
|
22
|
+
cost,
|
|
23
|
+
pricingStatus: "paid",
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (resolution.status === "free" || resolution.status === "subscription") {
|
|
27
|
+
return {
|
|
28
|
+
inputTokens: input,
|
|
29
|
+
outputTokens: output,
|
|
30
|
+
reasoningTokens: reasoning,
|
|
31
|
+
cacheReadTokens: cacheRead,
|
|
32
|
+
totalTokens,
|
|
33
|
+
cost: 0,
|
|
34
|
+
pricingStatus: resolution.status,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
inputTokens: input,
|
|
39
|
+
outputTokens: output,
|
|
40
|
+
reasoningTokens: reasoning,
|
|
41
|
+
cacheReadTokens: cacheRead,
|
|
42
|
+
totalTokens,
|
|
43
|
+
cost: null,
|
|
44
|
+
pricingStatus: "unknown",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=cost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost.js","sourceRoot":"","sources":["../../src/pricing/cost.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,4EAA4E;AAC5E,0EAA0E;AAC1E,mBAAmB;AAkCnB,MAAM,UAAU,QAAQ,CAAC,UAA6B,EAAE,KAAkB;IACxE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,CAAA;IAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAA;IAC7C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,SAAS,IAAI,CAAC,CAAC,CAAA;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,SAAS,IAAI,CAAC,CAAC,CAAA;IACnD,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAA;IAE1D,IAAI,UAAU,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,CAAC,KAAK,GAAG,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,CAAC;cACzC,MAAM,GAAG,CAAC,UAAU,CAAC,MAAM,IAAI,CAAC,CAAC;cACjC,SAAS,GAAG,CAAC,UAAU,CAAC,SAAS,IAAI,CAAC,CAAC;cACvC,SAAS,GAAG,CAAC,UAAU,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,GAAG,SAAS,CAAA;QACxD,OAAO;YACL,WAAW,EAAE,KAAK;YAClB,YAAY,EAAE,MAAM;YACpB,eAAe,EAAE,SAAS;YAC1B,eAAe,EAAE,SAAS;YAC1B,WAAW;YACX,IAAI;YACJ,aAAa,EAAE,MAAM;SACtB,CAAA;IACH,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,KAAK,MAAM,IAAI,UAAU,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;QACzE,OAAO;YACL,WAAW,EAAE,KAAK;YAClB,YAAY,EAAE,MAAM;YACpB,eAAe,EAAE,SAAS;YAC1B,eAAe,EAAE,SAAS;YAC1B,WAAW;YACX,IAAI,EAAE,CAAC;YACP,aAAa,EAAE,UAAU,CAAC,MAAM;SACjC,CAAA;IACH,CAAC;IAED,OAAO;QACL,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,MAAM;QACpB,eAAe,EAAE,SAAS;QAC1B,eAAe,EAAE,SAAS;QAC1B,WAAW;QACX,IAAI,EAAE,IAAI;QACV,aAAa,EAAE,SAAS;KACzB,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export interface ModelEntry {
|
|
2
|
+
/** Canonical normalized model id, e.g. "gpt-5-6-sol" (no provider prefix). */
|
|
3
|
+
id: string;
|
|
4
|
+
/** Optional provider hint, normalized (e.g. "openai"). */
|
|
5
|
+
provider?: string;
|
|
6
|
+
/** Raw display names that map to this entry, e.g. "GPT-5.6 Sol". */
|
|
7
|
+
aliases?: string[];
|
|
8
|
+
}
|
|
9
|
+
export type MatchMethod = "exact" | "alias" | "keyword" | "fuzzy" | "none";
|
|
10
|
+
export interface ModelMatch {
|
|
11
|
+
/** Canonical normalized model id (best effort when method is "none"). */
|
|
12
|
+
canonical: string;
|
|
13
|
+
/** How the match was established. */
|
|
14
|
+
method: MatchMethod;
|
|
15
|
+
/** Fuzzy tier only: score gap to the runner-up candidate. */
|
|
16
|
+
margin?: number;
|
|
17
|
+
/** True when several catalog entries could match and the raw id lacks a discriminator. */
|
|
18
|
+
familyAmbiguous?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface ParsedModelId {
|
|
21
|
+
/** Normalized provider prefix ("cx"), empty when the raw id had no "/". */
|
|
22
|
+
provider: string;
|
|
23
|
+
/** Normalized base model name ("gpt-5-6-sol"). */
|
|
24
|
+
model: string;
|
|
25
|
+
/** Optional pricing variant suffix (":free", ":thinking", ...). */
|
|
26
|
+
variant?: string;
|
|
27
|
+
/** The raw id as passed in. */
|
|
28
|
+
raw: string;
|
|
29
|
+
}
|
|
30
|
+
/** Lowercase and fold all non-alphanumeric separators into "-". */
|
|
31
|
+
export declare function normalizeModelName(name: string): string;
|
|
32
|
+
/** Split a raw id at the FIRST slash only; later slashes belong to the model part. */
|
|
33
|
+
export declare function splitProviderId(raw: string): {
|
|
34
|
+
provider: string;
|
|
35
|
+
rest: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Parse a raw model id into a normalized provider / model / variant triple.
|
|
39
|
+
* Variants are OpenRouter-style ":suffix" segments that change pricing
|
|
40
|
+
* ("x:free") and must not block matching the base model.
|
|
41
|
+
*/
|
|
42
|
+
export declare function parseModelId(raw: string): ParsedModelId;
|
|
43
|
+
export interface MatchOptions {
|
|
44
|
+
/** Minimum bigram-Dice similarity for the fuzzy tier. */
|
|
45
|
+
fuzzyThreshold?: number;
|
|
46
|
+
/** Minimum score gap over the runner-up for the fuzzy tier. */
|
|
47
|
+
fuzzyMargin?: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a raw model id against a catalog of known models.
|
|
51
|
+
*
|
|
52
|
+
* Ladder: exact normalized id -> normalized alias -> keyword (all raw tokens
|
|
53
|
+
* present in exactly one entry) -> gated fuzzy similarity. The keyword tier
|
|
54
|
+
* short-circuits with `familyAmbiguous` when several entries could fit, so
|
|
55
|
+
* "gpt-5.6" never silently becomes "gpt-5.6-sol" when family members exist.
|
|
56
|
+
* A raw id with an extra wrapper namespace ("provider/custom-prefix/X") is
|
|
57
|
+
* retried with the final path segment as the base model.
|
|
58
|
+
*/
|
|
59
|
+
export declare function matchModel(rawId: string, entries: ModelEntry[], opts?: MatchOptions): ModelMatch;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Pure model-identity normalization and matching. Zero I/O: these helpers turn
|
|
2
|
+
// an arbitrary raw model id ("CX/GPT-5.6 Sol") into a canonical normalized
|
|
3
|
+
// form ("gpt-5-6-sol") that pricing lookups key on, without collapsing
|
|
4
|
+
// genuinely different models into one another.
|
|
5
|
+
/** Lowercase and fold all non-alphanumeric separators into "-". */
|
|
6
|
+
export function normalizeModelName(name) {
|
|
7
|
+
return name
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
10
|
+
.replace(/^-+|-+$/g, "");
|
|
11
|
+
}
|
|
12
|
+
/** Split a raw id at the FIRST slash only; later slashes belong to the model part. */
|
|
13
|
+
export function splitProviderId(raw) {
|
|
14
|
+
const idx = raw.indexOf("/");
|
|
15
|
+
if (idx === -1)
|
|
16
|
+
return { provider: "", rest: raw };
|
|
17
|
+
return { provider: raw.slice(0, idx).trim(), rest: raw.slice(idx + 1) };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse a raw model id into a normalized provider / model / variant triple.
|
|
21
|
+
* Variants are OpenRouter-style ":suffix" segments that change pricing
|
|
22
|
+
* ("x:free") and must not block matching the base model.
|
|
23
|
+
*/
|
|
24
|
+
export function parseModelId(raw) {
|
|
25
|
+
const { provider, rest } = splitProviderId(raw);
|
|
26
|
+
const variantIdx = rest.lastIndexOf(":");
|
|
27
|
+
const base = variantIdx === -1 ? rest : rest.slice(0, variantIdx);
|
|
28
|
+
const variant = variantIdx === -1 ? undefined : rest.slice(variantIdx + 1).trim();
|
|
29
|
+
return {
|
|
30
|
+
provider: provider.toLowerCase(),
|
|
31
|
+
model: normalizeModelName(base),
|
|
32
|
+
...(variant ? { variant } : {}),
|
|
33
|
+
raw,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function tokensOf(normalized) {
|
|
37
|
+
return normalized.split("-").filter(Boolean);
|
|
38
|
+
}
|
|
39
|
+
function hasAllTokens(entryTokens, candidateTokens) {
|
|
40
|
+
return candidateTokens.every((token) => entryTokens.has(token));
|
|
41
|
+
}
|
|
42
|
+
function entryTokenSet(entry) {
|
|
43
|
+
const set = new Set(tokensOf(entry.id));
|
|
44
|
+
for (const alias of entry.aliases ?? []) {
|
|
45
|
+
for (const token of tokensOf(normalizeModelName(alias)))
|
|
46
|
+
set.add(token);
|
|
47
|
+
}
|
|
48
|
+
return set;
|
|
49
|
+
}
|
|
50
|
+
function bigrams(s) {
|
|
51
|
+
const out = new Set();
|
|
52
|
+
for (let i = 0; i < s.length - 1; i++)
|
|
53
|
+
out.add(s.slice(i, i + 2));
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
function bigramDice(a, b) {
|
|
57
|
+
if (a === b)
|
|
58
|
+
return 1;
|
|
59
|
+
const A = bigrams(a);
|
|
60
|
+
const B = bigrams(b);
|
|
61
|
+
if (A.size === 0 || B.size === 0)
|
|
62
|
+
return 0;
|
|
63
|
+
let shared = 0;
|
|
64
|
+
for (const gram of A)
|
|
65
|
+
if (B.has(gram))
|
|
66
|
+
shared += 1;
|
|
67
|
+
return (2 * shared) / (A.size + B.size);
|
|
68
|
+
}
|
|
69
|
+
const DEFAULT_FUZZY_THRESHOLD = 0.85;
|
|
70
|
+
const DEFAULT_FUZZY_MARGIN = 0.1;
|
|
71
|
+
/**
|
|
72
|
+
* Resolve a raw model id against a catalog of known models.
|
|
73
|
+
*
|
|
74
|
+
* Ladder: exact normalized id -> normalized alias -> keyword (all raw tokens
|
|
75
|
+
* present in exactly one entry) -> gated fuzzy similarity. The keyword tier
|
|
76
|
+
* short-circuits with `familyAmbiguous` when several entries could fit, so
|
|
77
|
+
* "gpt-5.6" never silently becomes "gpt-5.6-sol" when family members exist.
|
|
78
|
+
* A raw id with an extra wrapper namespace ("provider/custom-prefix/X") is
|
|
79
|
+
* retried with the final path segment as the base model.
|
|
80
|
+
*/
|
|
81
|
+
export function matchModel(rawId, entries, opts) {
|
|
82
|
+
const threshold = opts?.fuzzyThreshold ?? DEFAULT_FUZZY_THRESHOLD;
|
|
83
|
+
const margin = opts?.fuzzyMargin ?? DEFAULT_FUZZY_MARGIN;
|
|
84
|
+
const { provider, rest } = splitProviderId(rawId);
|
|
85
|
+
const variantIdx = rest.lastIndexOf(":");
|
|
86
|
+
const baseRest = variantIdx === -1 ? rest : rest.slice(0, variantIdx);
|
|
87
|
+
const tries = [baseRest];
|
|
88
|
+
if (baseRest.includes("/")) {
|
|
89
|
+
const last = baseRest.slice(baseRest.lastIndexOf("/") + 1);
|
|
90
|
+
if (last !== baseRest)
|
|
91
|
+
tries.push(last);
|
|
92
|
+
}
|
|
93
|
+
const seen = new Set();
|
|
94
|
+
for (const attempt of tries) {
|
|
95
|
+
const norm = normalizeModelName(attempt);
|
|
96
|
+
if (seen.has(norm))
|
|
97
|
+
continue;
|
|
98
|
+
seen.add(norm);
|
|
99
|
+
const exact = entries.find((entry) => entry.id === norm);
|
|
100
|
+
if (exact)
|
|
101
|
+
return { canonical: exact.id, method: "exact" };
|
|
102
|
+
const alias = entries.find((entry) => (entry.aliases ?? []).some((a) => normalizeModelName(a) === norm));
|
|
103
|
+
if (alias)
|
|
104
|
+
return { canonical: alias.id, method: "alias" };
|
|
105
|
+
const candidateTokens = tokensOf(norm);
|
|
106
|
+
const keywordHits = entries.filter((entry) => hasAllTokens(entryTokenSet(entry), candidateTokens));
|
|
107
|
+
if (keywordHits.length === 1) {
|
|
108
|
+
return { canonical: keywordHits[0].id, method: "keyword" };
|
|
109
|
+
}
|
|
110
|
+
if (keywordHits.length > 1) {
|
|
111
|
+
return { canonical: norm, method: "none", familyAmbiguous: true };
|
|
112
|
+
}
|
|
113
|
+
const scored = entries
|
|
114
|
+
.map((entry) => {
|
|
115
|
+
if (provider && entry.provider && entry.provider !== provider)
|
|
116
|
+
return undefined;
|
|
117
|
+
const score = Math.max(bigramDice(norm, entry.id), ...(entry.aliases ?? []).map((a) => bigramDice(norm, normalizeModelName(a))));
|
|
118
|
+
return { entry, score };
|
|
119
|
+
})
|
|
120
|
+
.filter((item) => item !== undefined && item.score >= threshold)
|
|
121
|
+
.sort((a, b) => b.score - a.score);
|
|
122
|
+
if (scored.length === 0)
|
|
123
|
+
continue;
|
|
124
|
+
const top = scored[0];
|
|
125
|
+
const runnerUp = scored[1]?.score ?? 0;
|
|
126
|
+
const gap = top.score - runnerUp;
|
|
127
|
+
if (gap >= margin) {
|
|
128
|
+
return { canonical: top.entry.id, method: "fuzzy", margin: gap };
|
|
129
|
+
}
|
|
130
|
+
return { canonical: norm, method: "none", familyAmbiguous: scored.length > 1 };
|
|
131
|
+
}
|
|
132
|
+
return { canonical: normalizeModelName(baseRest), method: "none" };
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=model-match.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-match.js","sourceRoot":"","sources":["../../src/pricing/model-match.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type ModelEntry } from "./model-match.js";
|
|
2
|
+
export interface OpenRouterPricing {
|
|
3
|
+
/** USD per 1M input tokens. */
|
|
4
|
+
input?: number;
|
|
5
|
+
/** USD per 1M output tokens. */
|
|
6
|
+
output?: number;
|
|
7
|
+
/** USD per 1M cached-input read tokens. */
|
|
8
|
+
cacheRead?: number;
|
|
9
|
+
/** USD per 1M internal reasoning tokens. */
|
|
10
|
+
reasoning?: number;
|
|
11
|
+
/** USD per request (per-request billing, not token billing). */
|
|
12
|
+
request?: number;
|
|
13
|
+
/** USD per image (per-unit billing, not token billing). */
|
|
14
|
+
image?: number;
|
|
15
|
+
/** USD per audio unit (per-unit billing, not token billing). */
|
|
16
|
+
audio?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface OpenRouterModel {
|
|
19
|
+
/** Lowercase raw model id without the variant suffix, e.g. "openai/gpt-4o-mini". */
|
|
20
|
+
id: string;
|
|
21
|
+
/** OpenRouter variant suffix ("free", "thinking", "nitro", ...), when present. */
|
|
22
|
+
variant?: string;
|
|
23
|
+
/** Display name as reported by OpenRouter. */
|
|
24
|
+
name?: string;
|
|
25
|
+
/** Provider-stripped canonical slug (a dated alias of the same model). */
|
|
26
|
+
canonicalSlug?: string;
|
|
27
|
+
contextLength?: number | null;
|
|
28
|
+
/** True when the id carried a ":free" suffix. */
|
|
29
|
+
isFreeVariant: boolean;
|
|
30
|
+
pricing: OpenRouterPricing;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Parse an OpenRouter `/api/v1/models` response into normalized models.
|
|
34
|
+
* Missing or invalid price fields become undefined (never 0, never throws).
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseOpenRouterModels(text: string): OpenRouterModel[];
|
|
37
|
+
/** Build matchModel catalog entries from a parsed OpenRouter list. */
|
|
38
|
+
export declare function toModelEntries(models: OpenRouterModel[]): ModelEntry[];
|
|
39
|
+
export interface OpenRouterCacheOptions {
|
|
40
|
+
/** Cache lifetime in milliseconds. */
|
|
41
|
+
ttlMs: number;
|
|
42
|
+
/** Endpoint override (tests / self-hosted mirrors). */
|
|
43
|
+
endpoint?: string;
|
|
44
|
+
fetchImpl?: typeof fetch;
|
|
45
|
+
now?: () => number;
|
|
46
|
+
}
|
|
47
|
+
export interface OpenRouterCache {
|
|
48
|
+
/** Models from cache, refetching lazily when cold/stale or when forced. */
|
|
49
|
+
getModels(force?: boolean): Promise<OpenRouterModel[]>;
|
|
50
|
+
/** Epoch ms of the last successful fetch. */
|
|
51
|
+
readonly fetchedAt: number | undefined;
|
|
52
|
+
/** Last fetch error message, when any. */
|
|
53
|
+
readonly lastError: string | undefined;
|
|
54
|
+
}
|
|
55
|
+
export declare const DEFAULT_OPENROUTER_ENDPOINT = "https://openrouter.ai/api/v1/models";
|
|
56
|
+
/**
|
|
57
|
+
* TTL-on-data cache over the OpenRouter models list. Concurrent callers
|
|
58
|
+
* share one in-flight fetch; a failed refresh keeps the previous snapshot.
|
|
59
|
+
*/
|
|
60
|
+
export declare function createOpenRouterCache(options: OpenRouterCacheOptions): OpenRouterCache;
|