@danypops/jittor 0.16.2 → 0.17.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/README.md +38 -13
- package/docs/COMPACTION_EFFECTIVENESS.md +34 -0
- package/docs/CONTEXT_SNAPSHOTS.md +53 -0
- package/docs/MODEL_CATALOG.md +43 -0
- package/docs/OTLP_EXPORT.md +63 -0
- package/docs/PROVIDER_RESEARCH.md +1 -1
- package/docs/TOKEN_MEASUREMENT.md +53 -0
- package/docs/USAGE_BACKFILL.md +35 -0
- package/package.json +4 -3
- package/src/{providers/anthropic-contracts.ts → anthropic/rate-limits.ts} +1 -1
- package/src/{adapters/artificial-analysis-direct-source.ts → artificial-analysis/benchmark-source.ts} +3 -3
- package/src/cli-commands/backfill.ts +51 -0
- package/src/cli-commands/benchmarks.ts +4 -4
- package/src/cli-commands/catalog.ts +123 -0
- package/src/cli-commands/compaction.ts +1 -1
- package/src/cli-commands/context.ts +77 -4
- package/src/cli-commands/export.ts +30 -0
- package/src/cli-commands/metrics.ts +8 -2
- package/src/cli-commands/op.ts +1 -1
- package/src/cli-commands/route-args.ts +2 -2
- package/src/cli-commands/router.ts +2 -2
- package/src/cli-commands/support.ts +2 -2
- package/src/cli.ts +54 -21
- package/src/{providers/codex-contracts.ts → codex/contracts.ts} +1 -1
- package/src/codex/source.ts +53 -0
- package/src/{providers/codex.ts → codex/telemetry.ts} +2 -2
- package/src/constants.ts +18 -0
- package/src/daemon.ts +53 -23
- package/src/{providers/google-vertex-budget-contracts.ts → google-vertex/budget-contracts.ts} +3 -3
- package/src/{providers/google-vertex-budget.ts → google-vertex/budget-telemetry.ts} +6 -6
- package/src/{providers/google-vertex-contracts.ts → google-vertex/failures.ts} +1 -1
- package/src/google-vertex/source.ts +35 -0
- package/src/index.ts +135 -55
- package/src/{adapters/lmarena-hf-source.ts → lmarena/benchmark-source.ts} +3 -3
- package/src/observability/budget.ts +14 -0
- package/src/observability/context-delta.ts +309 -0
- package/src/{domain → observability}/context-hub.ts +19 -2
- package/src/observability/context-snapshot-history.ts +281 -0
- package/src/{domain → observability}/context-telemetry.ts +232 -4
- package/src/observability/model-identity.ts +42 -0
- package/src/{domain → observability}/model-observation.ts +26 -11
- package/src/{ports/metric-store.ts → observability/store.ts} +2 -2
- package/src/{ports → observability}/telemetry-source.ts +2 -2
- package/src/observability/token-measurement.ts +194 -0
- package/src/observability/usage-import.ts +122 -0
- package/src/openai/token-counter.ts +63 -0
- package/src/{adapters/openrouter-benchmark-source.ts → openrouter/benchmark-source.ts} +3 -3
- package/src/{providers/openrouter-contracts.ts → openrouter/contracts.ts} +1 -1
- package/src/{adapters/openrouter-design-arena-source.ts → openrouter/design-arena-source.ts} +4 -4
- package/src/openrouter/source.ts +20 -0
- package/src/{providers/openrouter.ts → openrouter/telemetry.ts} +2 -2
- package/src/{domain → optimization/model-selection}/benchmark.ts +9 -27
- package/src/optimization/model-selection/catalog-store.ts +97 -0
- package/src/optimization/model-selection/catalog.ts +478 -0
- package/src/{ports/benchmark-controller.ts → optimization/model-selection/controller.ts} +1 -1
- package/src/{adapters/metric-benchmark-store.ts → optimization/model-selection/observation-store.ts} +5 -5
- package/src/{domain/model-ranking-service.ts → optimization/model-selection/ranker.ts} +6 -12
- package/src/{domain/model-ranking.ts → optimization/model-selection/ranking.ts} +8 -2
- package/src/{ports/benchmark-source.ts → optimization/model-selection/source.ts} +1 -1
- package/src/{ports/benchmark-store.ts → optimization/model-selection/store.ts} +1 -1
- package/src/{domain/codex-recovery.ts → optimization/recovery/codex.ts} +1 -1
- package/src/{ports/router-controller.ts → optimization/routing/controller.ts} +1 -1
- package/src/{policy.ts → optimization/routing/policy.ts} +4 -14
- package/src/{router.ts → optimization/routing/router.ts} +4 -10
- package/src/otlp/exporter.ts +354 -0
- package/src/pi/session-usage-source.ts +203 -0
- package/src/{session-identity-service.ts → sessions/identity.ts} +2 -2
- package/src/{operations/session-scope.ts → sessions/router-authorization.ts} +1 -1
- package/src/{db.ts → sqlite/database.ts} +14 -1
- package/src/{adapters/sqlite-metric-store.ts → sqlite/metric-store.ts} +4 -4
- package/src/{adapters/sqlite-session-identity-store.ts → sqlite/session-store.ts} +1 -1
- package/src/sqlite/usage-import-store.ts +133 -0
- package/src/telemetry-export/exporter.ts +84 -0
- package/src/{operations → vehicle}/benchmark-operations.ts +3 -3
- package/src/vehicle/catalog-operations.ts +10 -0
- package/src/{client.ts → vehicle/client.ts} +1 -1
- package/src/{operations → vehicle}/context-operations.ts +13 -5
- package/src/vehicle/export-operations.ts +12 -0
- package/src/{operations/metrics-operations.ts → vehicle/metric-operations.ts} +4 -4
- package/src/{operations → vehicle}/model-ranking-operations.ts +3 -3
- package/src/{vehicle-registration.ts → vehicle/registration.ts} +14 -4
- package/src/{operations/router-operations.ts → vehicle/routing-operations.ts} +4 -4
- package/src/{service.ts → vehicle/service.ts} +108 -26
- package/src/{operations/session-identity-operations.ts → vehicle/session-operations.ts} +3 -3
- package/src/vehicle/usage-import-operations.ts +10 -0
- package/src/providers/telemetry-sources.ts +0 -108
- /package/src/{providers/google-adc-auth.ts → google-vertex/auth.ts} +0 -0
- /package/src/{domain → observability}/metric.ts +0 -0
- /package/src/{domain → observability}/task-cost.ts +0 -0
- /package/src/{domain → observability}/task-focus.ts +0 -0
- /package/src/{domain → observability}/usage.ts +0 -0
- /package/src/{config.ts → optimization/routing/config.ts} +0 -0
- /package/src/{ports/session-identity-store.ts → sessions/store.ts} +0 -0
- /package/src/{operations/types.ts → vehicle/operation-types.ts} +0 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MODEL_CATALOG_DEFAULT_QUERY_LIMIT,
|
|
3
|
+
MODEL_CATALOG_FRESHNESS_MS,
|
|
4
|
+
MODEL_CATALOG_MAX_ENTRIES,
|
|
5
|
+
MODEL_CATALOG_MAX_QUERY_LIMIT,
|
|
6
|
+
MODEL_CATALOG_MAX_RESPONSE_BYTES,
|
|
7
|
+
MODEL_CATALOG_REQUEST_TIMEOUT_MS,
|
|
8
|
+
} from "../../constants.ts";
|
|
9
|
+
|
|
10
|
+
export type ModelCatalogAuthority = "models-dev-provider" | "user-override";
|
|
11
|
+
export type ModelModality = "text" | "audio" | "image" | "video" | "pdf";
|
|
12
|
+
|
|
13
|
+
export interface ModelCatalogProvenance {
|
|
14
|
+
sourceId: "models.dev";
|
|
15
|
+
sourceUrl: string;
|
|
16
|
+
revision: string;
|
|
17
|
+
retrievedAt: number;
|
|
18
|
+
freshUntil: number;
|
|
19
|
+
license: "MIT";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ModelCatalogFieldAuthority {
|
|
23
|
+
authority: ModelCatalogAuthority;
|
|
24
|
+
sourceId: "models.dev" | "local-configuration";
|
|
25
|
+
revision: string;
|
|
26
|
+
retrievedAt: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ModelCatalogPriceTier {
|
|
30
|
+
contextSize: number;
|
|
31
|
+
input: number;
|
|
32
|
+
output: number;
|
|
33
|
+
reasoning?: number;
|
|
34
|
+
cacheRead?: number;
|
|
35
|
+
cacheWrite?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ModelCatalogPricing {
|
|
39
|
+
input?: number;
|
|
40
|
+
output?: number;
|
|
41
|
+
reasoning?: number;
|
|
42
|
+
cacheRead?: number;
|
|
43
|
+
cacheWrite?: number;
|
|
44
|
+
contextOver200k?: Omit<ModelCatalogPricing, "contextOver200k" | "tiers">;
|
|
45
|
+
tiers?: ModelCatalogPriceTier[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ModelCatalogEntry {
|
|
49
|
+
provider: string;
|
|
50
|
+
model: string;
|
|
51
|
+
canonical: string;
|
|
52
|
+
aliases: string[];
|
|
53
|
+
name: string;
|
|
54
|
+
status: "active" | "alpha" | "beta" | "deprecated";
|
|
55
|
+
capabilities: { attachment: boolean; reasoning: boolean; toolCall: boolean; structuredOutput: boolean };
|
|
56
|
+
modalities: { input: ModelModality[]; output: ModelModality[] };
|
|
57
|
+
limits: { context: number; input?: number; output: number };
|
|
58
|
+
pricing?: ModelCatalogPricing;
|
|
59
|
+
fieldAuthority: Record<string, ModelCatalogFieldAuthority>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ModelCatalogSnapshot {
|
|
63
|
+
snapshotId: string;
|
|
64
|
+
provenance: ModelCatalogProvenance;
|
|
65
|
+
entries: ModelCatalogEntry[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ModelCatalogStore {
|
|
69
|
+
publish(snapshot: ModelCatalogSnapshot): ModelCatalogSnapshot;
|
|
70
|
+
latest(): ModelCatalogSnapshot | null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ModelCatalogSource {
|
|
74
|
+
readonly id: "models.dev";
|
|
75
|
+
fetch(): Promise<ModelCatalogSnapshot>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ModelCatalogStatus {
|
|
79
|
+
configured: boolean;
|
|
80
|
+
ok: boolean | null;
|
|
81
|
+
hasSnapshot: boolean;
|
|
82
|
+
lastAttemptAt: number | null;
|
|
83
|
+
lastSuccessAt: number | null;
|
|
84
|
+
entries: number;
|
|
85
|
+
revision: string | null;
|
|
86
|
+
error?: "catalog refresh failed";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface ModelCatalogOverrides {
|
|
90
|
+
contextTokens?: number;
|
|
91
|
+
inputTokens?: number;
|
|
92
|
+
outputTokens?: number;
|
|
93
|
+
inputPrice?: number;
|
|
94
|
+
outputPrice?: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ModelCatalogQuery {
|
|
98
|
+
provider?: string;
|
|
99
|
+
model?: string;
|
|
100
|
+
limit?: number;
|
|
101
|
+
overrides?: ModelCatalogOverrides;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface ModelCatalogQueryResult {
|
|
105
|
+
snapshotId: string;
|
|
106
|
+
provenance: ModelCatalogProvenance;
|
|
107
|
+
freshness: "fresh" | "stale";
|
|
108
|
+
completeness: "complete" | "truncated";
|
|
109
|
+
entries: ModelCatalogEntry[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function record(value: unknown, name: string): Record<string, unknown> {
|
|
113
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${name} must be an object`);
|
|
114
|
+
return value as Record<string, unknown>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function text(value: unknown, name: string, max = 200): string {
|
|
118
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${name} is required`);
|
|
119
|
+
const result = value.trim();
|
|
120
|
+
if (result.length > max || /\p{Cc}/u.test(result)) throw new Error(`${name} is invalid`);
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function identity(value: unknown, name: string): string {
|
|
125
|
+
const result = text(value, name).toLowerCase();
|
|
126
|
+
if (!/^[a-z0-9@~][a-z0-9@~._:+/-]*$/.test(result) || result.includes("//")) throw new Error(`${name} is invalid`);
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function nonnegative(value: unknown, name: string, integer = false): number {
|
|
131
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || (integer && !Number.isSafeInteger(value)))
|
|
132
|
+
throw new Error(`${name} must be a non-negative ${integer ? "integer" : "number"}`);
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function optionalNumber(value: unknown, name: string, integer = false): number | undefined {
|
|
137
|
+
return value === undefined ? undefined : nonnegative(value, name, integer);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const MODALITIES = new Set<ModelModality>(["text", "audio", "image", "video", "pdf"]);
|
|
141
|
+
function modalities(value: unknown, name: string): ModelModality[] {
|
|
142
|
+
if (!Array.isArray(value) || !value.every((item) => MODALITIES.has(item as ModelModality))) throw new Error(`${name} is invalid`);
|
|
143
|
+
return [...new Set(value as ModelModality[])];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function optionalBoolean(value: unknown, name: string, fallback = false): boolean {
|
|
147
|
+
if (value === undefined) return fallback;
|
|
148
|
+
if (typeof value !== "boolean") throw new Error(`${name} must be boolean`);
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function pricing(value: unknown, path: string): ModelCatalogPricing | undefined {
|
|
153
|
+
if (value === undefined) return undefined;
|
|
154
|
+
const input = record(value, path);
|
|
155
|
+
const result: ModelCatalogPricing = {
|
|
156
|
+
input: nonnegative(input.input, `${path}.input`),
|
|
157
|
+
output: nonnegative(input.output, `${path}.output`),
|
|
158
|
+
};
|
|
159
|
+
const reasoning = optionalNumber(input.reasoning, `${path}.reasoning`);
|
|
160
|
+
const cacheRead = optionalNumber(input.cache_read, `${path}.cache_read`);
|
|
161
|
+
const cacheWrite = optionalNumber(input.cache_write, `${path}.cache_write`);
|
|
162
|
+
if (reasoning !== undefined) result.reasoning = reasoning;
|
|
163
|
+
if (cacheRead !== undefined) result.cacheRead = cacheRead;
|
|
164
|
+
if (cacheWrite !== undefined) result.cacheWrite = cacheWrite;
|
|
165
|
+
if (input.context_over_200k !== undefined) {
|
|
166
|
+
const over = pricing(input.context_over_200k, `${path}.context_over_200k`)!;
|
|
167
|
+
delete over.contextOver200k;
|
|
168
|
+
delete over.tiers;
|
|
169
|
+
result.contextOver200k = over;
|
|
170
|
+
}
|
|
171
|
+
if (input.tiers !== undefined) {
|
|
172
|
+
if (!Array.isArray(input.tiers)) throw new Error(`${path}.tiers must be an array`);
|
|
173
|
+
result.tiers = input.tiers.map((value, index) => {
|
|
174
|
+
const tier = record(value, `${path}.tiers[${index}]`);
|
|
175
|
+
const definition = record(tier.tier, `${path}.tiers[${index}].tier`);
|
|
176
|
+
if (definition.type !== undefined && definition.type !== "context") throw new Error(`${path}.tiers[${index}].tier.type is invalid`);
|
|
177
|
+
const translated: ModelCatalogPriceTier = {
|
|
178
|
+
contextSize: nonnegative(definition.size, `${path}.tiers[${index}].tier.size`, true),
|
|
179
|
+
input: nonnegative(tier.input, `${path}.tiers[${index}].input`),
|
|
180
|
+
output: nonnegative(tier.output, `${path}.tiers[${index}].output`),
|
|
181
|
+
};
|
|
182
|
+
const tierReasoning = optionalNumber(tier.reasoning, `${path}.tiers[${index}].reasoning`);
|
|
183
|
+
const tierRead = optionalNumber(tier.cache_read, `${path}.tiers[${index}].cache_read`);
|
|
184
|
+
const tierWrite = optionalNumber(tier.cache_write, `${path}.tiers[${index}].cache_write`);
|
|
185
|
+
if (tierReasoning !== undefined) translated.reasoning = tierReasoning;
|
|
186
|
+
if (tierRead !== undefined) translated.cacheRead = tierRead;
|
|
187
|
+
if (tierWrite !== undefined) translated.cacheWrite = tierWrite;
|
|
188
|
+
return translated;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function authority(provenance: ModelCatalogProvenance): ModelCatalogFieldAuthority {
|
|
195
|
+
return {
|
|
196
|
+
authority: "models-dev-provider",
|
|
197
|
+
sourceId: "models.dev",
|
|
198
|
+
revision: provenance.revision,
|
|
199
|
+
retrievedAt: provenance.retrievedAt,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function fieldAuthorities(
|
|
204
|
+
entry: Omit<ModelCatalogEntry, "fieldAuthority">,
|
|
205
|
+
provenance: ModelCatalogProvenance,
|
|
206
|
+
): Record<string, ModelCatalogFieldAuthority> {
|
|
207
|
+
const fields = [
|
|
208
|
+
"status",
|
|
209
|
+
"limits.context",
|
|
210
|
+
"limits.output",
|
|
211
|
+
"capabilities.attachment",
|
|
212
|
+
"capabilities.reasoning",
|
|
213
|
+
"capabilities.toolCall",
|
|
214
|
+
"capabilities.structuredOutput",
|
|
215
|
+
"modalities.input",
|
|
216
|
+
"modalities.output",
|
|
217
|
+
];
|
|
218
|
+
if (entry.limits.input !== undefined) fields.push("limits.input");
|
|
219
|
+
if (entry.pricing) {
|
|
220
|
+
for (const key of ["input", "output", "reasoning", "cacheRead", "cacheWrite"] as const) {
|
|
221
|
+
if (entry.pricing[key] !== undefined) fields.push(`pricing.${key}`);
|
|
222
|
+
}
|
|
223
|
+
if (entry.pricing.contextOver200k) fields.push("pricing.contextOver200k");
|
|
224
|
+
if (entry.pricing.tiers) fields.push("pricing.tiers");
|
|
225
|
+
}
|
|
226
|
+
return Object.fromEntries(fields.map((field) => [field, authority(provenance)]));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function translateModelsDevCatalog(
|
|
230
|
+
value: unknown,
|
|
231
|
+
input: Omit<ModelCatalogProvenance, "sourceId" | "license">,
|
|
232
|
+
): ModelCatalogSnapshot {
|
|
233
|
+
const sourceUrl = new URL(input.sourceUrl);
|
|
234
|
+
if (sourceUrl.protocol !== "https:" && sourceUrl.hostname !== "127.0.0.1" && sourceUrl.hostname !== "localhost")
|
|
235
|
+
throw new Error("catalog source URL must use HTTPS or loopback HTTP");
|
|
236
|
+
if (!Number.isSafeInteger(input.retrievedAt) || input.retrievedAt <= 0) throw new Error("catalog retrieval time is invalid");
|
|
237
|
+
if (!Number.isSafeInteger(input.freshUntil) || input.freshUntil < input.retrievedAt)
|
|
238
|
+
throw new Error("catalog freshness deadline is invalid");
|
|
239
|
+
const provenance: ModelCatalogProvenance = { sourceId: "models.dev", license: "MIT", ...input, sourceUrl: sourceUrl.toString() };
|
|
240
|
+
const providers = record(value, "models.dev catalog");
|
|
241
|
+
const entries: ModelCatalogEntry[] = [];
|
|
242
|
+
for (const [providerKey, providerValue] of Object.entries(providers)) {
|
|
243
|
+
const provider = record(providerValue, `provider ${providerKey}`);
|
|
244
|
+
const providerId = identity(provider.id ?? providerKey, `provider ${providerKey}.id`);
|
|
245
|
+
if (providerId !== identity(providerKey, "provider key")) throw new Error(`provider ${providerKey} identity mismatch`);
|
|
246
|
+
const models = record(provider.models, `provider ${providerKey}.models`);
|
|
247
|
+
for (const [modelKey, modelValue] of Object.entries(models)) {
|
|
248
|
+
if (entries.length >= MODEL_CATALOG_MAX_ENTRIES) throw new Error("models.dev catalog exceeds the entry limit");
|
|
249
|
+
const path = `provider ${providerKey}.models.${modelKey}`;
|
|
250
|
+
const model = record(modelValue, path);
|
|
251
|
+
const modelId = identity(modelKey, `${path}.key`);
|
|
252
|
+
const reportedId = identity(model.id ?? modelKey, `${path}.id`);
|
|
253
|
+
const limit = record(model.limit, `${path}.limit`);
|
|
254
|
+
const modality = record(model.modalities, `${path}.modalities`);
|
|
255
|
+
const rawStatus = model.status ?? "active";
|
|
256
|
+
if (rawStatus !== "active" && rawStatus !== "alpha" && rawStatus !== "beta" && rawStatus !== "deprecated")
|
|
257
|
+
throw new Error(`${path}.status is invalid`);
|
|
258
|
+
const base: Omit<ModelCatalogEntry, "fieldAuthority"> = {
|
|
259
|
+
provider: providerId,
|
|
260
|
+
model: modelId,
|
|
261
|
+
canonical: `${providerId}/${modelId}`,
|
|
262
|
+
aliases: reportedId === modelId ? [] : [reportedId],
|
|
263
|
+
name: text(model.name, `${path}.name`),
|
|
264
|
+
status: rawStatus,
|
|
265
|
+
capabilities: {
|
|
266
|
+
attachment: optionalBoolean(model.attachment, `${path}.attachment`),
|
|
267
|
+
reasoning: optionalBoolean(model.reasoning, `${path}.reasoning`),
|
|
268
|
+
toolCall: optionalBoolean(model.tool_call, `${path}.tool_call`),
|
|
269
|
+
structuredOutput: optionalBoolean(model.structured_output, `${path}.structured_output`),
|
|
270
|
+
},
|
|
271
|
+
modalities: {
|
|
272
|
+
input: modalities(modality.input, `${path}.modalities.input`),
|
|
273
|
+
output: modalities(modality.output, `${path}.modalities.output`),
|
|
274
|
+
},
|
|
275
|
+
limits: {
|
|
276
|
+
context: nonnegative(limit.context, `${path}.limit.context`, true),
|
|
277
|
+
...(limit.input === undefined ? {} : { input: nonnegative(limit.input, `${path}.limit.input`, true) }),
|
|
278
|
+
output: nonnegative(limit.output, `${path}.limit.output`, true),
|
|
279
|
+
},
|
|
280
|
+
...(model.cost === undefined ? {} : { pricing: pricing(model.cost, `${path}.cost`) }),
|
|
281
|
+
};
|
|
282
|
+
entries.push({ ...base, fieldAuthority: fieldAuthorities(base, provenance) });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (entries.length === 0) throw new Error("models.dev catalog contains no models");
|
|
286
|
+
return { snapshotId: input.revision, provenance, entries };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function digestRevision(body: string): Promise<string> {
|
|
290
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(body));
|
|
291
|
+
return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export type ModelCatalogTransport = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
295
|
+
|
|
296
|
+
export interface ModelsDevCatalogSourceOptions {
|
|
297
|
+
sourceUrl?: string;
|
|
298
|
+
transport?: ModelCatalogTransport;
|
|
299
|
+
clock?: () => number;
|
|
300
|
+
timeoutMs?: number;
|
|
301
|
+
maxResponseBytes?: number;
|
|
302
|
+
freshnessMs?: number;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export class ModelsDevCatalogSource implements ModelCatalogSource {
|
|
306
|
+
readonly id = "models.dev" as const;
|
|
307
|
+
private readonly sourceUrl: string;
|
|
308
|
+
private readonly transport: ModelCatalogTransport;
|
|
309
|
+
private readonly clock: () => number;
|
|
310
|
+
private readonly timeoutMs: number;
|
|
311
|
+
private readonly maxResponseBytes: number;
|
|
312
|
+
private readonly freshnessMs: number;
|
|
313
|
+
|
|
314
|
+
constructor(options: ModelsDevCatalogSourceOptions = {}) {
|
|
315
|
+
this.sourceUrl = options.sourceUrl ?? "https://models.dev/api.json";
|
|
316
|
+
this.transport = options.transport ?? fetch;
|
|
317
|
+
this.clock = options.clock ?? Date.now;
|
|
318
|
+
this.timeoutMs = options.timeoutMs ?? MODEL_CATALOG_REQUEST_TIMEOUT_MS;
|
|
319
|
+
this.maxResponseBytes = options.maxResponseBytes ?? MODEL_CATALOG_MAX_RESPONSE_BYTES;
|
|
320
|
+
this.freshnessMs = options.freshnessMs ?? MODEL_CATALOG_FRESHNESS_MS;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async fetch(): Promise<ModelCatalogSnapshot> {
|
|
324
|
+
const url = new URL(this.sourceUrl);
|
|
325
|
+
if (url.protocol !== "https:" && url.hostname !== "127.0.0.1" && url.hostname !== "localhost")
|
|
326
|
+
throw new Error("catalog source URL must use HTTPS or loopback HTTP");
|
|
327
|
+
const controller = new AbortController();
|
|
328
|
+
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
329
|
+
try {
|
|
330
|
+
const response = await this.transport(url, { signal: controller.signal, headers: { accept: "application/json" } });
|
|
331
|
+
if (!response.ok) throw new Error(`models.dev request failed (${response.status})`);
|
|
332
|
+
const length = Number(response.headers.get("content-length") ?? 0);
|
|
333
|
+
if (Number.isFinite(length) && length > this.maxResponseBytes) throw new Error("models.dev response exceeds the byte limit");
|
|
334
|
+
const body = await response.text();
|
|
335
|
+
if (new TextEncoder().encode(body).byteLength > this.maxResponseBytes) throw new Error("models.dev response exceeds the byte limit");
|
|
336
|
+
const retrievedAt = this.clock();
|
|
337
|
+
let parsed: unknown;
|
|
338
|
+
try {
|
|
339
|
+
parsed = JSON.parse(body);
|
|
340
|
+
} catch {
|
|
341
|
+
throw new Error("models.dev response is not valid JSON");
|
|
342
|
+
}
|
|
343
|
+
return translateModelsDevCatalog(parsed, {
|
|
344
|
+
sourceUrl: url.toString(),
|
|
345
|
+
retrievedAt,
|
|
346
|
+
freshUntil: retrievedAt + this.freshnessMs,
|
|
347
|
+
revision: await digestRevision(body),
|
|
348
|
+
});
|
|
349
|
+
} finally {
|
|
350
|
+
clearTimeout(timeout);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function localAuthority(now: number): ModelCatalogFieldAuthority {
|
|
356
|
+
return { authority: "user-override", sourceId: "local-configuration", revision: "runtime", retrievedAt: now };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function applyOverrides(entry: ModelCatalogEntry, overrides: ModelCatalogOverrides | undefined, now: number): ModelCatalogEntry {
|
|
360
|
+
if (!overrides) return entry;
|
|
361
|
+
const result = structuredClone(entry);
|
|
362
|
+
const values: Array<[keyof ModelCatalogOverrides, string, (value: number) => void]> = [
|
|
363
|
+
["contextTokens", "limits.context", (value) => (result.limits.context = value)],
|
|
364
|
+
["inputTokens", "limits.input", (value) => (result.limits.input = value)],
|
|
365
|
+
["outputTokens", "limits.output", (value) => (result.limits.output = value)],
|
|
366
|
+
[
|
|
367
|
+
"inputPrice",
|
|
368
|
+
"pricing.input",
|
|
369
|
+
(value) => {
|
|
370
|
+
if (!result.pricing) result.pricing = {};
|
|
371
|
+
result.pricing.input = value;
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
[
|
|
375
|
+
"outputPrice",
|
|
376
|
+
"pricing.output",
|
|
377
|
+
(value) => {
|
|
378
|
+
if (!result.pricing) result.pricing = {};
|
|
379
|
+
result.pricing.output = value;
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
];
|
|
383
|
+
for (const [key, field, assign] of values) {
|
|
384
|
+
const value = overrides[key];
|
|
385
|
+
if (value === undefined) continue;
|
|
386
|
+
nonnegative(value, `override ${key}`, key.endsWith("Tokens"));
|
|
387
|
+
assign(value);
|
|
388
|
+
result.fieldAuthority[field] = localAuthority(now);
|
|
389
|
+
}
|
|
390
|
+
return result;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export interface ModelCatalogController {
|
|
394
|
+
refresh(force?: boolean): Promise<ModelCatalogStatus>;
|
|
395
|
+
status(): ModelCatalogStatus;
|
|
396
|
+
query(input?: ModelCatalogQuery): ModelCatalogQueryResult;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export class ModelCatalog implements ModelCatalogController {
|
|
400
|
+
private readonly clock: () => number;
|
|
401
|
+
private state: ModelCatalogStatus;
|
|
402
|
+
|
|
403
|
+
constructor(
|
|
404
|
+
private readonly store: ModelCatalogStore,
|
|
405
|
+
private readonly source?: ModelCatalogSource,
|
|
406
|
+
options: { clock?: () => number } = {},
|
|
407
|
+
) {
|
|
408
|
+
this.clock = options.clock ?? Date.now;
|
|
409
|
+
const latest = store.latest();
|
|
410
|
+
this.state = {
|
|
411
|
+
configured: source !== undefined,
|
|
412
|
+
ok: null,
|
|
413
|
+
hasSnapshot: latest !== null,
|
|
414
|
+
lastAttemptAt: null,
|
|
415
|
+
lastSuccessAt: latest?.provenance.retrievedAt ?? null,
|
|
416
|
+
entries: latest?.entries.length ?? 0,
|
|
417
|
+
revision: latest?.provenance.revision ?? null,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async refresh(force = false): Promise<ModelCatalogStatus> {
|
|
422
|
+
const attemptedAt = this.clock();
|
|
423
|
+
if (!this.source) return structuredClone(this.state);
|
|
424
|
+
const latestBeforeRefresh = this.store.latest();
|
|
425
|
+
if (!force && latestBeforeRefresh && attemptedAt <= latestBeforeRefresh.provenance.freshUntil) return structuredClone(this.state);
|
|
426
|
+
try {
|
|
427
|
+
const snapshot = this.store.publish(await this.source.fetch());
|
|
428
|
+
this.state = {
|
|
429
|
+
configured: true,
|
|
430
|
+
ok: true,
|
|
431
|
+
hasSnapshot: true,
|
|
432
|
+
lastAttemptAt: attemptedAt,
|
|
433
|
+
lastSuccessAt: snapshot.provenance.retrievedAt,
|
|
434
|
+
entries: snapshot.entries.length,
|
|
435
|
+
revision: snapshot.provenance.revision,
|
|
436
|
+
};
|
|
437
|
+
} catch {
|
|
438
|
+
const latest = this.store.latest();
|
|
439
|
+
this.state = {
|
|
440
|
+
...this.state,
|
|
441
|
+
configured: true,
|
|
442
|
+
ok: false,
|
|
443
|
+
hasSnapshot: latest !== null,
|
|
444
|
+
lastAttemptAt: attemptedAt,
|
|
445
|
+
entries: latest?.entries.length ?? this.state.entries,
|
|
446
|
+
revision: latest?.provenance.revision ?? this.state.revision,
|
|
447
|
+
error: "catalog refresh failed",
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
return structuredClone(this.state);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
status(): ModelCatalogStatus {
|
|
454
|
+
return structuredClone(this.state);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
query(input: ModelCatalogQuery = {}): ModelCatalogQueryResult {
|
|
458
|
+
const snapshot = this.store.latest();
|
|
459
|
+
if (!snapshot) throw new Error("model catalog is not available");
|
|
460
|
+
const provider = input.provider?.trim().toLowerCase();
|
|
461
|
+
const model = input.model?.trim().toLowerCase();
|
|
462
|
+
const matched = snapshot.entries.filter(
|
|
463
|
+
(entry) =>
|
|
464
|
+
(!provider || entry.provider === provider) &&
|
|
465
|
+
(!model || entry.model === model || entry.canonical === model || entry.aliases.includes(model)),
|
|
466
|
+
);
|
|
467
|
+
const requested = Number.isSafeInteger(input.limit) ? input.limit! : MODEL_CATALOG_DEFAULT_QUERY_LIMIT;
|
|
468
|
+
const limit = Math.max(1, Math.min(MODEL_CATALOG_MAX_QUERY_LIMIT, requested));
|
|
469
|
+
const now = this.clock();
|
|
470
|
+
return {
|
|
471
|
+
snapshotId: snapshot.snapshotId,
|
|
472
|
+
provenance: structuredClone(snapshot.provenance),
|
|
473
|
+
freshness: now <= snapshot.provenance.freshUntil ? "fresh" : "stale",
|
|
474
|
+
completeness: matched.length > limit ? "truncated" : "complete",
|
|
475
|
+
entries: matched.slice(0, limit).map((entry) => applyOverrides(structuredClone(entry), input.overrides, now)),
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BenchmarkQuery, BenchmarkQueryResult, BenchmarkRefreshResult } from "
|
|
1
|
+
import type { BenchmarkQuery, BenchmarkQueryResult, BenchmarkRefreshResult } from "./benchmark.ts";
|
|
2
2
|
|
|
3
3
|
export interface BenchmarkController {
|
|
4
4
|
refresh(force?: boolean): Promise<BenchmarkRefreshResult>;
|
package/src/{adapters/metric-benchmark-store.ts → optimization/model-selection/observation-store.ts}
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { BENCHMARK_MAX_OBSERVATIONS_PER_SNAPSHOT, BENCHMARK_STORE_QUERY_LIMIT } from "
|
|
2
|
-
import
|
|
3
|
-
import type {
|
|
4
|
-
import type
|
|
5
|
-
import type {
|
|
1
|
+
import { BENCHMARK_MAX_OBSERVATIONS_PER_SNAPSHOT, BENCHMARK_STORE_QUERY_LIMIT } from "../../constants.ts";
|
|
2
|
+
import type { StoredMetricObservation } from "../../observability/metric.ts";
|
|
3
|
+
import type { MetricStore } from "../../observability/store.ts";
|
|
4
|
+
import { type BenchmarkObservation, type BenchmarkSnapshot, validateBenchmarkObservation } from "./benchmark.ts";
|
|
5
|
+
import type { BenchmarkStore } from "./store.ts";
|
|
6
6
|
|
|
7
7
|
const COMPLETE_METRIC = "snapshot-complete";
|
|
8
8
|
const SNAPSHOT_SCOPE = "snapshot";
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import { MODEL_AGGREGATE_MAX_ROWS, MODEL_OBSERVATION_FRESH_MS, MODEL_RANKING_MAX_SOURCES } from "
|
|
2
|
-
import type {
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
type ModelCandidate,
|
|
8
|
-
type ModelRankingResult,
|
|
9
|
-
rankModelCandidates,
|
|
10
|
-
type ScopeAuthority,
|
|
11
|
-
type UtilityWeights,
|
|
12
|
-
} from "./model-ranking.ts";
|
|
1
|
+
import { MODEL_AGGREGATE_MAX_ROWS, MODEL_OBSERVATION_FRESH_MS, MODEL_RANKING_MAX_SOURCES } from "../../constants.ts";
|
|
2
|
+
import type { ModelTaskDomain, ModelTaskType } from "../../observability/model-observation.ts";
|
|
3
|
+
import { aggregateModelMetrics } from "../../observability/model-observation.ts";
|
|
4
|
+
import type { MetricStore } from "../../observability/store.ts";
|
|
5
|
+
import { type ModelCandidate, type ModelRankingResult, rankModelCandidates, type ScopeAuthority, type UtilityWeights } from "./ranking.ts";
|
|
6
|
+
import type { BenchmarkStore } from "./store.ts";
|
|
13
7
|
|
|
14
8
|
export interface ModelRecommendationInput {
|
|
15
9
|
candidates: ModelCandidate[];
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { BENCHMARK_MAX_OBSERVATIONS_PER_SNAPSHOT, MAX_DYNAMIC_ROUTES, MODEL_AGGREGATE_MAX_ROWS } from "
|
|
1
|
+
import { BENCHMARK_MAX_OBSERVATIONS_PER_SNAPSHOT, MAX_DYNAMIC_ROUTES, MODEL_AGGREGATE_MAX_ROWS } from "../../constants.ts";
|
|
2
|
+
import {
|
|
3
|
+
type ModelMetricAggregate,
|
|
4
|
+
type ModelTaskDomain,
|
|
5
|
+
type ModelTaskType,
|
|
6
|
+
TASK_DOMAINS,
|
|
7
|
+
TASK_TYPES,
|
|
8
|
+
} from "../../observability/model-observation.ts";
|
|
2
9
|
import { type BenchmarkObservation, normalizeModelIdentity } from "./benchmark.ts";
|
|
3
|
-
import { type ModelMetricAggregate, type ModelTaskDomain, type ModelTaskType, TASK_DOMAINS, TASK_TYPES } from "./model-observation.ts";
|
|
4
10
|
|
|
5
11
|
export type ScopeAuthority = "exact-session" | "available-models";
|
|
6
12
|
export type UtilityComponentName = "quality" | "cost" | "latency" | "context" | "reliability";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BenchmarkObservation, BenchmarkSnapshot } from "
|
|
1
|
+
import type { BenchmarkObservation, BenchmarkSnapshot } from "./benchmark.ts";
|
|
2
2
|
|
|
3
3
|
/** Immutable evidence snapshot boundary. Only complete snapshots become visible. */
|
|
4
4
|
export interface BenchmarkStore {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CODEX_ERROR_MESSAGE_LIMIT, CODEX_RETRY_AFTER_MAX_MS, MILLISECONDS_PER_SECOND } from "
|
|
1
|
+
import { CODEX_ERROR_MESSAGE_LIMIT, CODEX_RETRY_AFTER_MAX_MS, MILLISECONDS_PER_SECOND } from "../../constants.ts";
|
|
2
2
|
|
|
3
3
|
export type CodexFailureKind =
|
|
4
4
|
| "concurrency"
|
|
@@ -1,18 +1,8 @@
|
|
|
1
|
+
import type { BudgetWindow } from "../../observability/budget.ts";
|
|
2
|
+
|
|
3
|
+
export type { BudgetWindow, TelemetryFreshness } from "../../observability/budget.ts";
|
|
4
|
+
|
|
1
5
|
export type PolicyAction = "continue" | "throttle" | "lower-thinking" | "switch-model" | "switch-provider" | "halt";
|
|
2
|
-
export type TelemetryFreshness = "fresh" | "stale" | "failed";
|
|
3
|
-
|
|
4
|
-
export interface BudgetWindow {
|
|
5
|
-
id: string;
|
|
6
|
-
source: string;
|
|
7
|
-
scope: string;
|
|
8
|
-
usedFraction: number;
|
|
9
|
-
windowSeconds: number;
|
|
10
|
-
resetsAt: number;
|
|
11
|
-
observedAt: number;
|
|
12
|
-
freshness: TelemetryFreshness;
|
|
13
|
-
confidence: number;
|
|
14
|
-
observedBurnPerSecond?: number;
|
|
15
|
-
}
|
|
16
6
|
|
|
17
7
|
export interface Route {
|
|
18
8
|
provider: string;
|
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import { ROUTER_MAX_SESSION_SCOPES, ROUTER_SESSION_ID_MAX_CHARACTERS } from "
|
|
1
|
+
import { ROUTER_MAX_SESSION_SCOPES, ROUTER_SESSION_ID_MAX_CHARACTERS } from "../../constants.ts";
|
|
2
|
+
import type { MetricStore } from "../../observability/store.ts";
|
|
3
|
+
import type { TelemetrySource } from "../../observability/telemetry-source.ts";
|
|
4
|
+
import type { RouteOverride, RouterController, RouterStatus, TelemetryPollResult, TelemetrySourceStatus } from "./controller.ts";
|
|
2
5
|
import { type BudgetWindow, evaluateRoutingPolicy, type PolicyConfig, type PolicyDecision, type Route } from "./policy.ts";
|
|
3
|
-
import type { MetricStore } from "./ports/metric-store.ts";
|
|
4
|
-
import type {
|
|
5
|
-
RouteOverride,
|
|
6
|
-
RouterController,
|
|
7
|
-
RouterStatus,
|
|
8
|
-
TelemetryPollResult,
|
|
9
|
-
TelemetrySourceStatus,
|
|
10
|
-
} from "./ports/router-controller.ts";
|
|
11
|
-
import type { TelemetrySource } from "./ports/telemetry-source.ts";
|
|
12
6
|
|
|
13
7
|
export interface JittorRouterOptions {
|
|
14
8
|
metrics: MetricStore;
|