@danypops/jittor 0.13.0 → 0.15.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 (59) hide show
  1. package/package.json +4 -3
  2. package/src/adapters/artificial-analysis-direct-source.ts +42 -11
  3. package/src/adapters/lmarena-hf-source.ts +37 -15
  4. package/src/adapters/metric-benchmark-store.ts +29 -18
  5. package/src/adapters/openrouter-benchmark-source.ts +75 -19
  6. package/src/adapters/openrouter-design-arena-source.ts +36 -19
  7. package/src/adapters/sqlite-metric-store.ts +48 -25
  8. package/src/adapters/sqlite-session-identity-store.ts +13 -7
  9. package/src/cli-commands/benchmarks.ts +87 -22
  10. package/src/cli-commands/compaction.ts +7 -2
  11. package/src/cli-commands/context.ts +10 -2
  12. package/src/cli-commands/metrics.ts +128 -31
  13. package/src/cli-commands/op.ts +6 -1
  14. package/src/cli-commands/route-args.ts +5 -1
  15. package/src/cli-commands/router.ts +61 -18
  16. package/src/cli-commands/service-daemon.ts +28 -12
  17. package/src/cli-commands/session.ts +15 -4
  18. package/src/cli-commands/support.ts +1 -1
  19. package/src/cli.ts +30 -23
  20. package/src/client.ts +1 -1
  21. package/src/constants.ts +6 -2
  22. package/src/daemon.ts +44 -24
  23. package/src/domain/benchmark.ts +72 -40
  24. package/src/domain/codex-recovery.ts +34 -25
  25. package/src/domain/context-hub.ts +44 -25
  26. package/src/domain/context-telemetry.ts +106 -35
  27. package/src/domain/metric.ts +11 -11
  28. package/src/domain/model-observation.ts +139 -55
  29. package/src/domain/model-ranking-service.ts +13 -3
  30. package/src/domain/model-ranking.ts +126 -60
  31. package/src/domain/task-cost.ts +52 -11
  32. package/src/domain/task-focus.ts +11 -8
  33. package/src/domain/usage.ts +2 -2
  34. package/src/index.ts +69 -69
  35. package/src/log.ts +7 -2
  36. package/src/operations/benchmark-operations.ts +1 -1
  37. package/src/operations/context-operations.ts +15 -6
  38. package/src/operations/metrics-operations.ts +63 -28
  39. package/src/operations/model-ranking-operations.ts +9 -2
  40. package/src/operations/router-operations.ts +8 -4
  41. package/src/operations/session-identity-operations.ts +1 -1
  42. package/src/operations/session-scope.ts +5 -3
  43. package/src/policy.ts +22 -17
  44. package/src/ports/benchmark-controller.ts +1 -5
  45. package/src/ports/metric-store.ts +1 -1
  46. package/src/providers/anthropic-contracts.ts +13 -3
  47. package/src/providers/codex-contracts.ts +60 -52
  48. package/src/providers/codex.ts +16 -19
  49. package/src/providers/google-vertex-budget-contracts.ts +24 -14
  50. package/src/providers/google-vertex-budget.ts +15 -13
  51. package/src/providers/google-vertex-contracts.ts +36 -24
  52. package/src/providers/openrouter-contracts.ts +49 -51
  53. package/src/providers/openrouter.ts +21 -15
  54. package/src/providers/telemetry-sources.ts +13 -10
  55. package/src/router.ts +92 -43
  56. package/src/service.ts +93 -32
  57. package/src/session-identity-service.ts +10 -2
  58. package/src/state.ts +4 -10
  59. package/src/vehicle-registration.ts +154 -0
@@ -6,10 +6,10 @@ import {
6
6
  BENCHMARK_MAX_TEXT_CHARACTERS,
7
7
  BENCHMARK_REFRESH_INTERVAL_MS,
8
8
  } from "../constants.ts";
9
- import { METRIC_UNITS, type MetricUnit } from "./metric.ts";
10
9
  import type { BenchmarkController } from "../ports/benchmark-controller.ts";
11
10
  import type { BenchmarkSource as BenchmarkSourcePort } from "../ports/benchmark-source.ts";
12
11
  import type { BenchmarkStore } from "../ports/benchmark-store.ts";
12
+ import { METRIC_UNITS, type MetricUnit } from "./metric.ts";
13
13
 
14
14
  export type BenchmarkSourceType = "creator" | "marketplace" | "independent" | "operational" | "preference" | "local";
15
15
 
@@ -112,7 +112,9 @@ export function normalizeModelIdentity(provider: string, model: string, aliases:
112
112
  const normalizedModel = identityPart(model, "model", true);
113
113
  const version = VERSION_SUFFIX.exec(normalizedModel)?.[1] ?? null;
114
114
  const canonical = `${normalizedProvider}/${normalizedModel}`;
115
- const normalizedAliases = [...new Set(aliases.map((alias) => boundedText(alias, "alias", BENCHMARK_IDENTITY_MAX_CHARACTERS).toLowerCase()))]
115
+ const normalizedAliases = [
116
+ ...new Set(aliases.map((alias) => boundedText(alias, "alias", BENCHMARK_IDENTITY_MAX_CHARACTERS).toLowerCase())),
117
+ ]
116
118
  .filter((alias) => alias !== canonical)
117
119
  .sort();
118
120
  return { provider: normalizedProvider, model: normalizedModel, version, canonical, aliases: normalizedAliases };
@@ -121,9 +123,10 @@ export function normalizeModelIdentity(provider: string, model: string, aliases:
121
123
  function validateIdentity(value: unknown): ModelIdentity {
122
124
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("model identity is required");
123
125
  const input = value as Record<string, unknown>;
124
- if (!Array.isArray(input["aliases"]) || !input["aliases"].every((alias) => typeof alias === "string")) throw new Error("model aliases are invalid");
125
- const normalized = normalizeModelIdentity(String(input["provider"] ?? ""), String(input["model"] ?? ""), input["aliases"] as string[]);
126
- if (input["canonical"] !== normalized.canonical || input["version"] !== normalized.version) throw new Error("model identity is not normalized");
126
+ if (!Array.isArray(input.aliases) || !input.aliases.every((alias) => typeof alias === "string"))
127
+ throw new Error("model aliases are invalid");
128
+ const normalized = normalizeModelIdentity(String(input.provider ?? ""), String(input.model ?? ""), input.aliases as string[]);
129
+ if (input.canonical !== normalized.canonical || input.version !== normalized.version) throw new Error("model identity is not normalized");
127
130
  return normalized;
128
131
  }
129
132
 
@@ -136,26 +139,31 @@ function validateTimestamp(value: unknown, name: string, nullable = false): numb
136
139
  function validateProvenance(value: unknown): BenchmarkProvenance {
137
140
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("provenance is required");
138
141
  const input = value as Record<string, unknown>;
139
- const sourceId = identityPart(String(input["sourceId"] ?? ""), "source id");
140
- if (!SOURCE_TYPES.has(input["sourceType"] as BenchmarkSourceType)) throw new Error("source type is invalid");
142
+ const sourceId = identityPart(String(input.sourceId ?? ""), "source id");
143
+ if (!SOURCE_TYPES.has(input.sourceType as BenchmarkSourceType)) throw new Error("source type is invalid");
141
144
  let url: URL;
142
- try { url = new URL(boundedText(input["url"], "source URL")); } catch { throw new Error("source URL is invalid"); }
145
+ try {
146
+ url = new URL(boundedText(input.url, "source URL"));
147
+ } catch {
148
+ throw new Error("source URL is invalid");
149
+ }
143
150
  if (url.protocol !== "https:") throw new Error("source URL must use HTTPS");
144
- const confidence = input["confidence"];
145
- if (typeof confidence !== "number" || !Number.isFinite(confidence) || confidence < 0 || confidence > 1) throw new Error("confidence must be between zero and one");
146
- const retrievedAt = validateTimestamp(input["retrievedAt"], "retrieval time") as number;
147
- const freshUntil = validateTimestamp(input["freshUntil"], "freshness deadline") as number;
151
+ const confidence = input.confidence;
152
+ if (typeof confidence !== "number" || !Number.isFinite(confidence) || confidence < 0 || confidence > 1)
153
+ throw new Error("confidence must be between zero and one");
154
+ const retrievedAt = validateTimestamp(input.retrievedAt, "retrieval time") as number;
155
+ const freshUntil = validateTimestamp(input.freshUntil, "freshness deadline") as number;
148
156
  if (freshUntil < retrievedAt) throw new Error("freshness deadline precedes retrieval");
149
157
  return {
150
158
  sourceId,
151
- sourceType: input["sourceType"] as BenchmarkSourceType,
152
- publisher: boundedText(input["publisher"], "publisher"),
159
+ sourceType: input.sourceType as BenchmarkSourceType,
160
+ publisher: boundedText(input.publisher, "publisher"),
153
161
  url: url.toString(),
154
- revision: boundedText(input["revision"], "revision"),
155
- publishedAt: validateTimestamp(input["publishedAt"], "publication time", true),
162
+ revision: boundedText(input.revision, "revision"),
163
+ publishedAt: validateTimestamp(input.publishedAt, "publication time", true),
156
164
  retrievedAt,
157
165
  freshUntil,
158
- license: boundedText(input["license"], "license"),
166
+ license: boundedText(input.license, "license"),
159
167
  confidence,
160
168
  };
161
169
  }
@@ -170,16 +178,16 @@ function validateMethodology(value: unknown): BenchmarkObservation["methodology"
170
178
  export function validateBenchmarkObservation(value: unknown): BenchmarkObservation {
171
179
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("benchmark observation must be an object");
172
180
  const input = value as Record<string, unknown>;
173
- const numericValue = input["value"];
181
+ const numericValue = input.value;
174
182
  if (typeof numericValue !== "number" || !Number.isFinite(numericValue)) throw new Error("benchmark value must be finite");
175
- if (!METRIC_UNITS.includes(input["unit"] as MetricUnit)) throw new Error("benchmark unit is not supported");
183
+ if (!METRIC_UNITS.includes(input.unit as MetricUnit)) throw new Error("benchmark unit is not supported");
176
184
  return {
177
- model: validateIdentity(input["model"]),
178
- dimension: identityPart(String(input["dimension"] ?? ""), "dimension"),
185
+ model: validateIdentity(input.model),
186
+ dimension: identityPart(String(input.dimension ?? ""), "dimension"),
179
187
  value: numericValue,
180
- unit: input["unit"] as MetricUnit,
181
- provenance: validateProvenance(input["provenance"]),
182
- methodology: validateMethodology(input["methodology"]),
188
+ unit: input.unit as MetricUnit,
189
+ provenance: validateProvenance(input.provenance),
190
+ methodology: validateMethodology(input.methodology),
183
191
  };
184
192
  }
185
193
 
@@ -188,9 +196,12 @@ function validateSourceSnapshot(value: BenchmarkSourceSnapshot, expectedSourceId
188
196
  if (sourceId !== expectedSourceId) throw new Error("source snapshot identity mismatch");
189
197
  const snapshotId = boundedText(value.snapshotId, "snapshot id", BENCHMARK_IDENTITY_MAX_CHARACTERS);
190
198
  const retrievedAt = validateTimestamp(value.retrievedAt, "retrieval time") as number;
191
- if (!Array.isArray(value.observations) || value.observations.length > BENCHMARK_MAX_OBSERVATIONS_PER_SNAPSHOT) throw new Error("source snapshot exceeds the observation limit");
199
+ if (!Array.isArray(value.observations) || value.observations.length > BENCHMARK_MAX_OBSERVATIONS_PER_SNAPSHOT)
200
+ throw new Error("source snapshot exceeds the observation limit");
192
201
  const observations = value.observations.map(validateBenchmarkObservation);
193
- if (observations.some((observation) => observation.provenance.sourceId !== sourceId || observation.provenance.retrievedAt !== retrievedAt)) {
202
+ if (
203
+ observations.some((observation) => observation.provenance.sourceId !== sourceId || observation.provenance.retrievedAt !== retrievedAt)
204
+ ) {
194
205
  throw new Error("source snapshot provenance mismatch");
195
206
  }
196
207
  return { sourceId, snapshotId, retrievedAt, observations };
@@ -223,19 +234,36 @@ export class BenchmarkCatalog implements BenchmarkController {
223
234
 
224
235
  async refresh(force = false): Promise<BenchmarkRefreshResult> {
225
236
  const now = this.clock();
226
- await Promise.all(this.sources.map(async (source) => {
227
- const prior = this.states.get(source.id)!;
228
- if (!force && prior.lastAttemptAt !== null && now - prior.lastAttemptAt < this.refreshIntervalMs) return;
229
- this.states.set(source.id, { ...prior, lastAttemptAt: now });
230
- try {
231
- const snapshot = validateSourceSnapshot(await source.fetch(), source.id);
232
- const published = this.store.publish(snapshot.sourceId, snapshot.snapshotId, snapshot.observations);
233
- this.states.set(source.id, { id: source.id, ok: true, hasEvidence: true, lastAttemptAt: now, lastSuccessAt: published.retrievedAt, observations: published.observations.length });
234
- } catch {
235
- const evidence = this.store.latest(source.id);
236
- this.states.set(source.id, { id: source.id, ok: false, hasEvidence: evidence !== null, lastAttemptAt: now, lastSuccessAt: evidence?.retrievedAt ?? prior.lastSuccessAt, observations: evidence?.observations.length ?? prior.observations, error: "source refresh failed" });
237
- }
238
- }));
237
+ await Promise.all(
238
+ this.sources.map(async (source) => {
239
+ const prior = this.states.get(source.id)!;
240
+ if (!force && prior.lastAttemptAt !== null && now - prior.lastAttemptAt < this.refreshIntervalMs) return;
241
+ this.states.set(source.id, { ...prior, lastAttemptAt: now });
242
+ try {
243
+ const snapshot = validateSourceSnapshot(await source.fetch(), source.id);
244
+ const published = this.store.publish(snapshot.sourceId, snapshot.snapshotId, snapshot.observations);
245
+ this.states.set(source.id, {
246
+ id: source.id,
247
+ ok: true,
248
+ hasEvidence: true,
249
+ lastAttemptAt: now,
250
+ lastSuccessAt: published.retrievedAt,
251
+ observations: published.observations.length,
252
+ });
253
+ } catch {
254
+ const evidence = this.store.latest(source.id);
255
+ this.states.set(source.id, {
256
+ id: source.id,
257
+ ok: false,
258
+ hasEvidence: evidence !== null,
259
+ lastAttemptAt: now,
260
+ lastSuccessAt: evidence?.retrievedAt ?? prior.lastSuccessAt,
261
+ observations: evidence?.observations.length ?? prior.observations,
262
+ error: "source refresh failed",
263
+ });
264
+ }
265
+ }),
266
+ );
239
267
  return { observedAt: now, sources: this.status().sources };
240
268
  }
241
269
 
@@ -251,7 +279,11 @@ export class BenchmarkCatalog implements BenchmarkController {
251
279
  const limit = Math.max(1, Math.min(BENCHMARK_MAX_QUERY_LIMIT, requestedLimit));
252
280
  const model = input.model?.trim().toLowerCase();
253
281
  const dimension = input.dimension?.trim().toLowerCase();
254
- const matched = snapshot.observations.filter((observation) => (!model || observation.model.canonical === model || observation.model.aliases.includes(model)) && (!dimension || observation.dimension === dimension));
282
+ const matched = snapshot.observations.filter(
283
+ (observation) =>
284
+ (!model || observation.model.canonical === model || observation.model.aliases.includes(model)) &&
285
+ (!dimension || observation.dimension === dimension),
286
+ );
255
287
  const freshUntil = Math.min(...snapshot.observations.map((observation) => observation.provenance.freshUntil));
256
288
  return {
257
289
  ...structuredClone(snapshot),
@@ -1,8 +1,4 @@
1
- import {
2
- CODEX_ERROR_MESSAGE_LIMIT,
3
- CODEX_RETRY_AFTER_MAX_MS,
4
- MILLISECONDS_PER_SECOND,
5
- } from "../constants.ts";
1
+ import { CODEX_ERROR_MESSAGE_LIMIT, CODEX_RETRY_AFTER_MAX_MS, MILLISECONDS_PER_SECOND } from "../constants.ts";
6
2
 
7
3
  export type CodexFailureKind =
8
4
  | "concurrency"
@@ -57,10 +53,12 @@ export class CodexRecoveryPolicy {
57
53
  private readonly random: () => number = Math.random,
58
54
  ) {
59
55
  if (!Number.isFinite(options.baseDelayMs) || options.baseDelayMs < 0) throw new Error("baseDelayMs must be non-negative");
60
- if (!Number.isFinite(options.maxDelayMs) || options.maxDelayMs < options.baseDelayMs) throw new Error("maxDelayMs must be at least baseDelayMs");
56
+ if (!Number.isFinite(options.maxDelayMs) || options.maxDelayMs < options.baseDelayMs)
57
+ throw new Error("maxDelayMs must be at least baseDelayMs");
61
58
  if (!Number.isInteger(options.maxAttempts) || options.maxAttempts < 1) throw new Error("maxAttempts must be a positive integer");
62
59
  if (!Number.isFinite(options.attemptWindowMs) || options.attemptWindowMs <= 0) throw new Error("attemptWindowMs must be positive");
63
- if (!Number.isFinite(options.jitterRatio) || options.jitterRatio < 0 || options.jitterRatio > 1) throw new Error("jitterRatio must be between 0 and 1");
60
+ if (!Number.isFinite(options.jitterRatio) || options.jitterRatio < 0 || options.jitterRatio > 1)
61
+ throw new Error("jitterRatio must be between 0 and 1");
64
62
  }
65
63
 
66
64
  observeFailure(failure: CodexFailure, now: number): void {
@@ -88,13 +86,15 @@ export class CodexRecoveryPolicy {
88
86
  this.normalizeWindow(now);
89
87
  if (!this.pendingFailure) return { action: "wait", reason: "no transient Codex failure is pending" };
90
88
  if (this.attempts >= this.options.maxAttempts) {
91
- return { action: "exhausted", reason: `${this.options.maxAttempts} recovery attempts reached within ${this.options.attemptWindowMs}ms` };
89
+ return {
90
+ action: "exhausted",
91
+ reason: `${this.options.maxAttempts} recovery attempts reached within ${this.options.attemptWindowMs}ms`,
92
+ };
92
93
  }
93
- const base = this.pendingFailure.retryAfterMs
94
- ?? this.options.baseDelayMs * (2 ** this.attempts);
94
+ const base = this.pendingFailure.retryAfterMs ?? this.options.baseDelayMs * 2 ** this.attempts;
95
95
  const sample = this.random();
96
96
  const unit = Number.isFinite(sample) ? Math.min(1, Math.max(0, sample)) : 0;
97
- const multiplier = 1 + ((unit * 2) - 1) * this.options.jitterRatio;
97
+ const multiplier = 1 + (unit * 2 - 1) * this.options.jitterRatio;
98
98
  const jittered = Math.max(0, Math.round(base * multiplier));
99
99
  const delayMs = this.pendingFailure.retryAfterMs === undefined ? jittered : Math.max(base, jittered);
100
100
  return {
@@ -135,9 +135,7 @@ export class CodexRecoveryPolicy {
135
135
  }
136
136
 
137
137
  function asRecord(value: unknown): Record<string, unknown> | undefined {
138
- return typeof value === "object" && value !== null && !Array.isArray(value)
139
- ? value as Record<string, unknown>
140
- : undefined;
138
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
141
139
  }
142
140
 
143
141
  function firstString(...values: unknown[]): string | undefined {
@@ -160,15 +158,15 @@ function matches(value: string, patterns: readonly string[]): boolean {
160
158
 
161
159
  export function classifyCodexFailure(value: unknown, metadata: CodexFailureMetadata = {}): CodexFailure {
162
160
  const root = asRecord(value);
163
- const detail = asRecord(root?.["detail"]);
164
- const nestedError = asRecord(root?.["error"]);
165
- const code = firstString(detail?.["code"], detail?.["error_code"], nestedError?.["code"], root?.["code"]);
166
- const source = firstString(detail?.["source"], nestedError?.["source"], root?.["source"]);
161
+ const detail = asRecord(root?.detail);
162
+ const nestedError = asRecord(root?.error);
163
+ const code = firstString(detail?.code, detail?.error_code, nestedError?.code, root?.code);
164
+ const source = firstString(detail?.source, nestedError?.source, root?.source);
167
165
  const rawMessage = firstString(
168
- detail?.["message"],
169
- typeof root?.["error"] === "string" ? root["error"] : undefined,
170
- nestedError?.["message"],
171
- root?.["message"],
166
+ detail?.message,
167
+ typeof root?.error === "string" ? root.error : undefined,
168
+ nestedError?.message,
169
+ root?.message,
172
170
  typeof value === "string" ? value : undefined,
173
171
  );
174
172
  const message = rawMessage?.slice(0, CODEX_ERROR_MESSAGE_LIMIT);
@@ -183,16 +181,27 @@ export function classifyCodexFailure(value: unknown, metadata: CodexFailureMetad
183
181
  if (matches(evidence, ["insufficient_quota", "quota exceeded", "out of credits", "billing"])) {
184
182
  return { kind: "quota", transient: false, ...base };
185
183
  }
186
- if (metadata.status === 401 || metadata.status === 403 || matches(evidence, ["invalid_api_key", "authentication", "unauthorized", "permission_denied"])) {
184
+ if (
185
+ metadata.status === 401 ||
186
+ metadata.status === 403 ||
187
+ matches(evidence, ["invalid_api_key", "authentication", "unauthorized", "permission_denied"])
188
+ ) {
187
189
  return { kind: "authentication", transient: false, ...base };
188
190
  }
189
- if (matches(evidence, ["invalid_prompt", "invalid_request", "context_length_exceeded"]) || metadata.status === 400 || metadata.status === 422) {
191
+ if (
192
+ matches(evidence, ["invalid_prompt", "invalid_request", "context_length_exceeded"]) ||
193
+ metadata.status === 400 ||
194
+ metadata.status === 422
195
+ ) {
190
196
  return { kind: "invalid-request", transient: false, ...base };
191
197
  }
192
198
  if (matches(evidence, ["concurrency_limit", "too many concurrent requests", "throttled"])) {
193
199
  return { kind: "concurrency", transient: true, ...base };
194
200
  }
195
- if (matches(evidence, ["server_is_overloaded", "slow_down", "service unavailable", "overloaded"]) || (metadata.status !== undefined && metadata.status >= 500 && metadata.status <= 599)) {
201
+ if (
202
+ matches(evidence, ["server_is_overloaded", "slow_down", "service unavailable", "overloaded"]) ||
203
+ (metadata.status !== undefined && metadata.status >= 500 && metadata.status <= 599)
204
+ ) {
196
205
  return { kind: "overload", transient: true, ...base };
197
206
  }
198
207
  if (metadata.status === 429 || matches(evidence, ["rate_limit_exceeded", "rate limit", "too many requests"])) {
@@ -17,8 +17,8 @@ import {
17
17
  CONTEXT_HUB_CONTRIBUTION_MAX_AGE_MS,
18
18
  CONTEXT_HUB_CONTRIBUTION_SCHEMA,
19
19
  CONTEXT_HUB_ITEM_LABEL_MAX_CHARACTERS,
20
- CONTEXT_HUB_MAX_ITEMS_PER_SEGMENT,
21
20
  CONTEXT_HUB_MAX_ITEM_DEPTH,
21
+ CONTEXT_HUB_MAX_ITEMS_PER_SEGMENT,
22
22
  CONTEXT_HUB_PRODUCER_NAME_MAX_CHARACTERS,
23
23
  CONTEXT_HUB_SEGMENT_KEY_MAX_CHARACTERS,
24
24
  CONTEXT_HUB_SEGMENT_LABEL_MAX_CHARACTERS,
@@ -39,6 +39,14 @@ export interface ContextSegment {
39
39
  estimatedTokens: number;
40
40
  confidence: ContextConfidenceTier;
41
41
  items?: ContextSegmentItem[];
42
+ /**
43
+ * True when this segment's size is genuinely unmeasured (not yet observed), as opposed to
44
+ * measured-and-actually-zero -- e.g. the base system prompt before the first observed turn.
45
+ * A display layer that hides zero-token rows to cut noise must NOT hide an unknown segment
46
+ * just because its placeholder value happens to be zero -- that would silently misrepresent
47
+ * "we don't know" as "there is nothing here".
48
+ */
49
+ unknown?: boolean;
42
50
  }
43
51
 
44
52
  export interface ContextContribution {
@@ -50,12 +58,14 @@ export interface ContextContribution {
50
58
  }
51
59
 
52
60
  function nonEmptyString(value: unknown, name: string, maxLength: number): string {
53
- if (typeof value !== "string" || value.length === 0 || value.length > maxLength) throw new Error(`${name} must be a non-empty string of at most ${maxLength} characters`);
61
+ if (typeof value !== "string" || value.length === 0 || value.length > maxLength)
62
+ throw new Error(`${name} must be a non-empty string of at most ${maxLength} characters`);
54
63
  return value;
55
64
  }
56
65
 
57
66
  function boundedInteger(value: unknown, name: string): number {
58
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new Error(`${name} must be a bounded non-negative integer`);
67
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
68
+ throw new Error(`${name} must be a bounded non-negative integer`);
59
69
  return value;
60
70
  }
61
71
 
@@ -68,15 +78,16 @@ function validateSegmentItem(value: unknown, depth: number): ContextSegmentItem
68
78
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("context segment item must be an object");
69
79
  const input = value as Record<string, unknown>;
70
80
  for (const key of Object.keys(input)) {
71
- if (key !== "label" && key !== "estimatedTokens" && key !== "children") throw new Error(`context segment item contains unexpected field: ${key}`);
81
+ if (key !== "label" && key !== "estimatedTokens" && key !== "children")
82
+ throw new Error(`context segment item contains unexpected field: ${key}`);
72
83
  }
73
84
  const item: ContextSegmentItem = {
74
- label: nonEmptyString(input["label"], "item.label", CONTEXT_HUB_ITEM_LABEL_MAX_CHARACTERS),
75
- estimatedTokens: boundedInteger(input["estimatedTokens"], "item.estimatedTokens"),
85
+ label: nonEmptyString(input.label, "item.label", CONTEXT_HUB_ITEM_LABEL_MAX_CHARACTERS),
86
+ estimatedTokens: boundedInteger(input.estimatedTokens, "item.estimatedTokens"),
76
87
  };
77
- if (input["children"] !== undefined) {
78
- if (!Array.isArray(input["children"])) throw new Error("item.children must be an array");
79
- item.children = input["children"].map((child) => validateSegmentItem(child, depth + 1));
88
+ if (input.children !== undefined) {
89
+ if (!Array.isArray(input.children)) throw new Error("item.children must be an array");
90
+ item.children = input.children.map((child) => validateSegmentItem(child, depth + 1));
80
91
  }
81
92
  return item;
82
93
  }
@@ -86,24 +97,27 @@ export function validateContextSegment(value: unknown): ContextSegment {
86
97
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("context segment must be an object");
87
98
  const input = value as Record<string, unknown>;
88
99
  for (const key of Object.keys(input)) {
89
- if (key !== "key" && key !== "label" && key !== "estimatedTokens" && key !== "confidence" && key !== "items") {
100
+ if (key !== "key" && key !== "label" && key !== "estimatedTokens" && key !== "confidence" && key !== "items" && key !== "unknown") {
90
101
  throw new Error(`context segment contains unexpected field: ${key}`);
91
102
  }
92
103
  }
93
- const confidence = input["confidence"];
104
+ const confidence = input.confidence;
94
105
  if (typeof confidence !== "string" || !CONTEXT_HUB_CONFIDENCE_TIERS.includes(confidence as ContextConfidenceTier)) {
95
106
  throw new Error(`confidence must be one of ${CONTEXT_HUB_CONFIDENCE_TIERS.join(", ")}`);
96
107
  }
108
+ if (input.unknown !== undefined && typeof input.unknown !== "boolean") throw new Error("segment.unknown must be a boolean");
97
109
  const segment: ContextSegment = {
98
- key: nonEmptyString(input["key"], "segment.key", CONTEXT_HUB_SEGMENT_KEY_MAX_CHARACTERS),
99
- label: nonEmptyString(input["label"], "segment.label", CONTEXT_HUB_SEGMENT_LABEL_MAX_CHARACTERS),
100
- estimatedTokens: boundedInteger(input["estimatedTokens"], "segment.estimatedTokens"),
110
+ key: nonEmptyString(input.key, "segment.key", CONTEXT_HUB_SEGMENT_KEY_MAX_CHARACTERS),
111
+ label: nonEmptyString(input.label, "segment.label", CONTEXT_HUB_SEGMENT_LABEL_MAX_CHARACTERS),
112
+ estimatedTokens: boundedInteger(input.estimatedTokens, "segment.estimatedTokens"),
101
113
  confidence: confidence as ContextConfidenceTier,
114
+ ...(input.unknown !== undefined ? { unknown: input.unknown as boolean } : {}),
102
115
  };
103
- if (input["items"] !== undefined) {
104
- if (!Array.isArray(input["items"])) throw new Error("segment.items must be an array");
105
- const items = input["items"].map((item) => validateSegmentItem(item, 1));
106
- if (countItems(items) > CONTEXT_HUB_MAX_ITEMS_PER_SEGMENT) throw new Error(`segment.items exceeds ${CONTEXT_HUB_MAX_ITEMS_PER_SEGMENT} total items`);
116
+ if (input.items !== undefined) {
117
+ if (!Array.isArray(input.items)) throw new Error("segment.items must be an array");
118
+ const items = input.items.map((item) => validateSegmentItem(item, 1));
119
+ if (countItems(items) > CONTEXT_HUB_MAX_ITEMS_PER_SEGMENT)
120
+ throw new Error(`segment.items exceeds ${CONTEXT_HUB_MAX_ITEMS_PER_SEGMENT} total items`);
107
121
  segment.items = items;
108
122
  }
109
123
  return segment;
@@ -115,16 +129,17 @@ const CONTRIBUTION_FIELDS = new Set(["schema", "observedAt", "sequence", "produc
115
129
  export function validateContextContribution(value: unknown, now = Date.now()): ContextContribution {
116
130
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("context contribution must be an object");
117
131
  const input = value as Record<string, unknown>;
118
- for (const key of Object.keys(input)) if (!CONTRIBUTION_FIELDS.has(key)) throw new Error(`context contribution contains unexpected field: ${key}`);
119
- if (input["schema"] !== CONTEXT_HUB_CONTRIBUTION_SCHEMA) throw new Error("context contribution schema is not supported");
120
- const observedAt = boundedInteger(input["observedAt"], "observedAt");
132
+ for (const key of Object.keys(input))
133
+ if (!CONTRIBUTION_FIELDS.has(key)) throw new Error(`context contribution contains unexpected field: ${key}`);
134
+ if (input.schema !== CONTEXT_HUB_CONTRIBUTION_SCHEMA) throw new Error("context contribution schema is not supported");
135
+ const observedAt = boundedInteger(input.observedAt, "observedAt");
121
136
  if (Math.abs(now - observedAt) > CONTEXT_HUB_CONTRIBUTION_MAX_AGE_MS) throw new Error("context contribution is stale");
122
137
  return {
123
138
  schema: CONTEXT_HUB_CONTRIBUTION_SCHEMA,
124
139
  observedAt,
125
- sequence: boundedInteger(input["sequence"], "sequence"),
126
- producerName: nonEmptyString(input["producerName"], "producerName", CONTEXT_HUB_PRODUCER_NAME_MAX_CHARACTERS),
127
- segment: validateContextSegment(input["segment"]),
140
+ sequence: boundedInteger(input.sequence, "sequence"),
141
+ producerName: nonEmptyString(input.producerName, "producerName", CONTEXT_HUB_PRODUCER_NAME_MAX_CHARACTERS),
142
+ segment: validateContextSegment(input.segment),
128
143
  };
129
144
  }
130
145
 
@@ -189,7 +204,11 @@ export function computeToolSchemaLedger(tools: readonly ToolLedgerEntry[]): Tool
189
204
  for (const tool of tools) {
190
205
  const source = tool.sourceInfo?.source && tool.sourceInfo.source.length > 0 ? tool.sourceInfo.source : "unknown";
191
206
  const characters = toolCharacters(tool);
192
- const usage: ToolLedgerToolUsage = { name: tool.name, characters, estimatedTokens: Math.ceil(characters / CONTEXT_ESTIMATE_CHARACTERS_PER_TOKEN) };
207
+ const usage: ToolLedgerToolUsage = {
208
+ name: tool.name,
209
+ characters,
210
+ estimatedTokens: Math.ceil(characters / CONTEXT_ESTIMATE_CHARACTERS_PER_TOKEN),
211
+ };
193
212
  const existing = bySource.get(source);
194
213
  if (existing) existing.push(usage);
195
214
  else bySource.set(source, [usage]);