modelalive 0.5.0 → 1.0.2
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.d.ts +12 -0
- package/dist/index.js +51 -9
- package/dist/normalize.d.ts +1 -0
- package/dist/normalize.js +17 -0
- package/dist/smoke.test.js +8 -1
- package/package.json +1 -1
- package/registry.json +3855 -3
package/dist/index.d.ts
CHANGED
|
@@ -5,8 +5,20 @@ export declare function check(model: string, opts?: {
|
|
|
5
5
|
today?: Date;
|
|
6
6
|
}): AliveResult;
|
|
7
7
|
export declare function resolve(model: string, today?: Date): string;
|
|
8
|
+
export interface ResolveDetail {
|
|
9
|
+
queried_model: string;
|
|
10
|
+
resolved: string;
|
|
11
|
+
chain: string[];
|
|
12
|
+
breaking_changes: string[];
|
|
13
|
+
}
|
|
14
|
+
export declare function resolveDetail(model: string, today?: Date): ResolveDetail;
|
|
8
15
|
export declare function ensure(model: string, opts?: {
|
|
9
16
|
strictUnknown?: boolean;
|
|
10
17
|
today?: Date;
|
|
11
18
|
}): string;
|
|
19
|
+
export declare function gate<T>(model: string, fn: (safeModel: string) => T, opts?: {
|
|
20
|
+
strictUnknown?: boolean;
|
|
21
|
+
today?: Date;
|
|
22
|
+
}): T;
|
|
12
23
|
export * from "./types.js";
|
|
24
|
+
export { normalizeModel } from "./normalize.js";
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { daysUntil, effectiveStatus, loadRegistry, resolveAlias, } from "./registry.js";
|
|
2
|
+
import { normalizeModel } from "./normalize.js";
|
|
2
3
|
import { ModelRetiredError, ModelUnknownError } from "./types.js";
|
|
4
|
+
const MAX_DEPTH = 12;
|
|
3
5
|
export function alive(model, today = new Date()) {
|
|
4
6
|
const registry = loadRegistry();
|
|
5
|
-
const queried = model
|
|
7
|
+
const queried = normalizeModel(model);
|
|
6
8
|
const [canonical, aliased] = resolveAlias(queried, registry);
|
|
7
9
|
const entry = registry.models[canonical];
|
|
8
10
|
if (!entry) {
|
|
@@ -63,24 +65,60 @@ export function check(model, opts = {}) {
|
|
|
63
65
|
return result;
|
|
64
66
|
}
|
|
65
67
|
export function resolve(model, today = new Date()) {
|
|
66
|
-
|
|
68
|
+
return resolveDetail(model, today).resolved;
|
|
69
|
+
}
|
|
70
|
+
export function resolveDetail(model, today = new Date()) {
|
|
71
|
+
let current = normalizeModel(model);
|
|
67
72
|
const visited = new Set();
|
|
68
|
-
|
|
73
|
+
const chain = [];
|
|
74
|
+
const breakingChanges = [];
|
|
75
|
+
for (let i = 0; i < MAX_DEPTH; i++) {
|
|
69
76
|
if (visited.has(current))
|
|
70
77
|
break;
|
|
71
78
|
visited.add(current);
|
|
79
|
+
chain.push(current);
|
|
72
80
|
const result = alive(current, today);
|
|
81
|
+
for (const change of result.breaking_changes ?? []) {
|
|
82
|
+
if (!breakingChanges.includes(change))
|
|
83
|
+
breakingChanges.push(change);
|
|
84
|
+
}
|
|
73
85
|
if (result.status === "active" || result.status === "unknown") {
|
|
74
|
-
return
|
|
86
|
+
return {
|
|
87
|
+
queried_model: model,
|
|
88
|
+
resolved: result.canonical_model ?? current,
|
|
89
|
+
chain,
|
|
90
|
+
breaking_changes: breakingChanges,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (!result.replacement) {
|
|
94
|
+
return {
|
|
95
|
+
queried_model: model,
|
|
96
|
+
resolved: result.canonical_model ?? current,
|
|
97
|
+
chain,
|
|
98
|
+
breaking_changes: breakingChanges,
|
|
99
|
+
};
|
|
75
100
|
}
|
|
76
|
-
if (!result.replacement)
|
|
77
|
-
return result.canonical_model ?? current;
|
|
78
101
|
const repl = alive(result.replacement, today);
|
|
79
|
-
if (repl.status === "active")
|
|
80
|
-
|
|
102
|
+
if (repl.status === "active") {
|
|
103
|
+
for (const change of repl.breaking_changes ?? []) {
|
|
104
|
+
if (!breakingChanges.includes(change))
|
|
105
|
+
breakingChanges.push(change);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
queried_model: model,
|
|
109
|
+
resolved: result.replacement,
|
|
110
|
+
chain: [...chain, result.replacement],
|
|
111
|
+
breaking_changes: breakingChanges,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
81
114
|
current = result.replacement;
|
|
82
115
|
}
|
|
83
|
-
return
|
|
116
|
+
return {
|
|
117
|
+
queried_model: model,
|
|
118
|
+
resolved: current,
|
|
119
|
+
chain,
|
|
120
|
+
breaking_changes: breakingChanges,
|
|
121
|
+
};
|
|
84
122
|
}
|
|
85
123
|
export function ensure(model, opts = {}) {
|
|
86
124
|
const result = alive(model, opts.today);
|
|
@@ -92,4 +130,8 @@ export function ensure(model, opts = {}) {
|
|
|
92
130
|
}
|
|
93
131
|
return resolve(model, opts.today);
|
|
94
132
|
}
|
|
133
|
+
export function gate(model, fn, opts = {}) {
|
|
134
|
+
return fn(ensure(model, opts));
|
|
135
|
+
}
|
|
95
136
|
export * from "./types.js";
|
|
137
|
+
export { normalizeModel } from "./normalize.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function normalizeModel(model: string): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const MODEL_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,200}$/;
|
|
2
|
+
const FINE_TUNED_PREFIX = /^ft:([^:]+(?::[^:]+)*)/;
|
|
3
|
+
export function normalizeModel(model) {
|
|
4
|
+
let cleaned = model.trim();
|
|
5
|
+
if (!cleaned)
|
|
6
|
+
throw new Error("Model ID cannot be empty");
|
|
7
|
+
const ftMatch = FINE_TUNED_PREFIX.exec(cleaned);
|
|
8
|
+
if (ftMatch) {
|
|
9
|
+
const base = ftMatch[1].split(":")[0];
|
|
10
|
+
if (base)
|
|
11
|
+
cleaned = base;
|
|
12
|
+
}
|
|
13
|
+
if (!MODEL_ID_PATTERN.test(cleaned)) {
|
|
14
|
+
throw new Error(`Invalid model ID format: ${model}`);
|
|
15
|
+
}
|
|
16
|
+
return cleaned;
|
|
17
|
+
}
|
package/dist/smoke.test.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { alive, ensure, resolve } from "./index.js";
|
|
2
|
+
import { alive, ensure, gate, normalizeModel, resolve, resolveDetail, } from "./index.js";
|
|
3
3
|
assert.equal(alive("claude-sonnet-4-20250514").status, "retired");
|
|
4
4
|
assert.equal(resolve("claude-sonnet-4-20250514"), "claude-sonnet-4-6");
|
|
5
5
|
assert.equal(ensure("claude-sonnet-4-20250514"), "claude-sonnet-4-6");
|
|
6
6
|
assert.equal(alive("claude-sonnet-4-6").status, "active");
|
|
7
|
+
const detail = resolveDetail("claude-opus-4-20250514");
|
|
8
|
+
assert.equal(detail.resolved, "claude-opus-4-8");
|
|
9
|
+
assert.ok(detail.breaking_changes.length >= 1);
|
|
10
|
+
assert.equal(normalizeModel("ft:gpt-4o-mini:org:foo:bar"), "gpt-4o-mini");
|
|
11
|
+
assert.equal(alive("anthropic.claude-sonnet-4-6-v1:0").canonical_model, "claude-sonnet-4-6");
|
|
12
|
+
const gated = gate("gemini-2.0-flash", (safe) => safe);
|
|
13
|
+
assert.equal(gated, "gemini-3.5-flash");
|
|
7
14
|
console.log("modelalive js: ok");
|