@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
package/src/router.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  import { ROUTER_MAX_SESSION_SCOPES, ROUTER_SESSION_ID_MAX_CHARACTERS } from "./constants.ts";
2
+ import { type BudgetWindow, evaluateRoutingPolicy, type PolicyConfig, type PolicyDecision, type Route } from "./policy.ts";
2
3
  import type { MetricStore } from "./ports/metric-store.ts";
3
- import type { RouteOverride, RouterController, RouterStatus, TelemetryPollResult, TelemetrySourceStatus } from "./ports/router-controller.ts";
4
+ import type {
5
+ RouteOverride,
6
+ RouterController,
7
+ RouterStatus,
8
+ TelemetryPollResult,
9
+ TelemetrySourceStatus,
10
+ } from "./ports/router-controller.ts";
4
11
  import type { TelemetrySource } from "./ports/telemetry-source.ts";
5
- import {
6
- evaluateRoutingPolicy,
7
- type BudgetWindow,
8
- type PolicyConfig,
9
- type PolicyDecision,
10
- type Route,
11
- } from "./policy.ts";
12
12
 
13
13
  export interface JittorRouterOptions {
14
14
  metrics: MetricStore;
@@ -57,7 +57,9 @@ export class JittorRouter implements RouterController {
57
57
  }
58
58
 
59
59
  poll(): Promise<TelemetryPollResult> {
60
- this.inFlightPoll ??= this.runPoll().finally(() => { this.inFlightPoll = null; });
60
+ this.inFlightPoll ??= this.runPoll().finally(() => {
61
+ this.inFlightPoll = null;
62
+ });
61
63
  return this.inFlightPoll;
62
64
  }
63
65
 
@@ -79,33 +81,66 @@ export class JittorRouter implements RouterController {
79
81
  const now = this.clock();
80
82
  const state = this.sessionState(sessionId);
81
83
  this.expireOverride(state);
82
- if (state.paused) return this.remember(state, { action: "halt", pressure: Number.POSITIVE_INFINITY, reason: "Jittor is paused", decidedAt: now, trace: ["manual pause"] });
84
+ if (state.paused)
85
+ return this.remember(state, {
86
+ action: "halt",
87
+ pressure: Number.POSITIVE_INFINITY,
88
+ reason: "Jittor is paused",
89
+ decidedAt: now,
90
+ trace: ["manual pause"],
91
+ });
83
92
  if (state.override) {
84
93
  const route = state.override.route;
85
- const action = route.provider !== state.currentRoute.provider
86
- ? "switch-provider"
87
- : route.model !== state.currentRoute.model
88
- ? "switch-model"
89
- : route.thinking !== state.currentRoute.thinking ? "lower-thinking" : "continue";
90
- return this.remember(state, { action, route, pressure: 0, reason: "manual route override", decidedAt: now, trace: ["manual override"] });
94
+ const action =
95
+ route.provider !== state.currentRoute.provider
96
+ ? "switch-provider"
97
+ : route.model !== state.currentRoute.model
98
+ ? "switch-model"
99
+ : route.thinking !== state.currentRoute.thinking
100
+ ? "lower-thinking"
101
+ : "continue";
102
+ return this.remember(state, {
103
+ action,
104
+ route,
105
+ pressure: 0,
106
+ reason: "manual route override",
107
+ decidedAt: now,
108
+ trace: ["manual override"],
109
+ });
91
110
  }
92
- if (!this.isReady(state)) return this.remember(state, { action: "halt", pressure: Number.POSITIVE_INFINITY, reason: "required telemetry is not ready", decidedAt: now, trace: ["fail closed"] });
111
+ if (!this.isReady(state))
112
+ return this.remember(state, {
113
+ action: "halt",
114
+ pressure: Number.POSITIVE_INFINITY,
115
+ reason: "required telemetry is not ready",
116
+ decidedAt: now,
117
+ trace: ["fail closed"],
118
+ });
93
119
  const activeSources = this.options.sources.filter((source) => source.provider === state.currentRoute.provider);
94
120
  const activeSourceIds = new Set(activeSources.map((source) => source.id));
95
121
  const requiredSourceIds = new Set(activeSources.filter((source) => source.required).map((source) => source.id));
96
122
  const activeWindows = [...this.windows.entries()].filter(([sourceId]) => activeSourceIds.has(sourceId));
97
123
  const requiredWindows = activeWindows.filter(([sourceId]) => requiredSourceIds.has(sourceId)).flatMap(([, windows]) => windows);
98
124
  if (requiredSourceIds.size === 0 && activeWindows.every(([, windows]) => windows.length === 0)) {
99
- return this.rememberPolicy(state, { action: "continue", pressure: 0, reason: "provider has no enforceable budget window; monitor-only", decidedAt: now, trace: ["monitor-only"] });
125
+ return this.rememberPolicy(state, {
126
+ action: "continue",
127
+ pressure: 0,
128
+ reason: "provider has no enforceable budget window; monitor-only",
129
+ decidedAt: now,
130
+ trace: ["monitor-only"],
131
+ });
100
132
  }
101
- return this.rememberPolicy(state, evaluateRoutingPolicy({
102
- now,
103
- windows: requiredSourceIds.size > 0 && requiredWindows.length === 0 ? [] : activeWindows.flatMap(([, windows]) => windows),
104
- currentRoute: state.currentRoute,
105
- routes: state.availableRoutes,
106
- config: this.options.policy,
107
- previousDecision: state.previousPolicyDecision ?? undefined,
108
- }));
133
+ return this.rememberPolicy(
134
+ state,
135
+ evaluateRoutingPolicy({
136
+ now,
137
+ windows: requiredSourceIds.size > 0 && requiredWindows.length === 0 ? [] : activeWindows.flatMap(([, windows]) => windows),
138
+ currentRoute: state.currentRoute,
139
+ routes: state.availableRoutes,
140
+ config: this.options.policy,
141
+ previousDecision: state.previousPolicyDecision ?? undefined,
142
+ }),
143
+ );
109
144
  }
110
145
 
111
146
  pause(sessionId?: string): RouterStatus {
@@ -120,7 +155,8 @@ export class JittorRouter implements RouterController {
120
155
 
121
156
  setOverride(override: RouteOverride | undefined, sessionId?: string): RouterStatus {
122
157
  const state = this.sessionState(sessionId);
123
- if (!override || !state.availableRoutes.some((route) => sameRoute(route, override.route))) throw new Error("override route is not available in Pi");
158
+ if (!override || !state.availableRoutes.some((route) => sameRoute(route, override.route)))
159
+ throw new Error("override route is not available in Pi");
124
160
  if (override.expiresAt !== null && override.expiresAt <= this.clock()) throw new Error("override expiry must be in the future");
125
161
  state.override = structuredClone(override);
126
162
  return this.status(sessionId);
@@ -139,10 +175,18 @@ export class JittorRouter implements RouterController {
139
175
 
140
176
  setAvailableRoutes(routes: Route[], sessionId?: string): RouterStatus {
141
177
  if (!Array.isArray(routes)) throw new Error("available routes must be an array");
142
- const valid = routes.filter((route) => typeof route?.provider === "string" && route.provider.length > 0
143
- && typeof route.model === "string" && route.model.length > 0
144
- && typeof route.thinking === "string" && route.thinking.length > 0);
145
- this.sessionState(sessionId).availableRoutes = valid.filter((route, index) => valid.findIndex((candidate) => sameRoute(candidate, route)) === index).map((route) => structuredClone(route));
178
+ const valid = routes.filter(
179
+ (route) =>
180
+ typeof route?.provider === "string" &&
181
+ route.provider.length > 0 &&
182
+ typeof route.model === "string" &&
183
+ route.model.length > 0 &&
184
+ typeof route.thinking === "string" &&
185
+ route.thinking.length > 0,
186
+ );
187
+ this.sessionState(sessionId).availableRoutes = valid
188
+ .filter((route, index) => valid.findIndex((candidate) => sameRoute(candidate, route)) === index)
189
+ .map((route) => structuredClone(route));
146
190
  return this.status(sessionId);
147
191
  }
148
192
 
@@ -155,22 +199,27 @@ export class JittorRouter implements RouterController {
155
199
  .filter((route): route is Route => route !== undefined);
156
200
  const current = ranked.find((route) => sameRoute(route, state.currentRoute));
157
201
  if (!current) throw new Error("model ranking does not contain the current available route");
158
- state.availableRoutes = [structuredClone(current), ...ranked.filter((route) => !sameRoute(route, current)).map((route) => structuredClone(route))];
202
+ state.availableRoutes = [
203
+ structuredClone(current),
204
+ ...ranked.filter((route) => !sameRoute(route, current)).map((route) => structuredClone(route)),
205
+ ];
159
206
  return this.status(sessionId);
160
207
  }
161
208
 
162
209
  private async runPoll(): Promise<TelemetryPollResult> {
163
- const statuses = await Promise.all(this.options.sources.map(async (source): Promise<TelemetrySourceStatus> => {
164
- try {
165
- const batch = await source.poll();
166
- for (const observation of batch.metrics) this.options.metrics.record(observation);
167
- this.windows.set(source.id, batch.windows);
168
- return { id: source.id, provider: source.provider, ok: true, metrics: batch.metrics.length, observedAt: batch.observedAt };
169
- } catch {
170
- this.windows.delete(source.id);
171
- return { id: source.id, provider: source.provider, ok: false, metrics: 0, observedAt: this.clock(), error: "poll failed" };
172
- }
173
- }));
210
+ const statuses = await Promise.all(
211
+ this.options.sources.map(async (source): Promise<TelemetrySourceStatus> => {
212
+ try {
213
+ const batch = await source.poll();
214
+ for (const observation of batch.metrics) this.options.metrics.record(observation);
215
+ this.windows.set(source.id, batch.windows);
216
+ return { id: source.id, provider: source.provider, ok: true, metrics: batch.metrics.length, observedAt: batch.observedAt };
217
+ } catch {
218
+ this.windows.delete(source.id);
219
+ return { id: source.id, provider: source.provider, ok: false, metrics: 0, observedAt: this.clock(), error: "poll failed" };
220
+ }
221
+ }),
222
+ );
174
223
  this.sourceStatuses = statuses;
175
224
  return { sources: structuredClone(statuses), observedAt: this.clock() };
176
225
  }
package/src/service.ts CHANGED
@@ -1,26 +1,29 @@
1
+ import { VehicleRegistry } from "@danypops/vehicle-server";
2
+ import { createVehicleHttpApp } from "@danypops/vehicle-server/http";
1
3
  import { errorResponse, healthResponse, readyResponse, requireBearerToken } from "@danypops/vehicle-server/rpc-http";
2
4
  import { SERVICE_MAX_BODY_BYTES, SERVICE_MAX_RESPONSE_BYTES } from "./constants.ts";
3
- import { InvalidSessionSecretError, SessionIdentity, type RegisterSessionIdentityResult } from "./session-identity-service.ts";
4
- import { VERSION } from "./version.ts";
5
- import type { MetricObservation, MetricQuery, StoredMetricObservation } from "./domain/metric.ts";
5
+ import type { BenchmarkQuery, BenchmarkQueryResult, BenchmarkRefreshResult } from "./domain/benchmark.ts";
6
6
  import type { CompactionDurationEstimate, ContextAssessment } from "./domain/context-telemetry.ts";
7
+ import type { MetricObservation, MetricQuery, StoredMetricObservation } from "./domain/metric.ts";
8
+ import type { ModelRankingResult } from "./domain/model-ranking.ts";
9
+ import type { ModelRanker, ModelRecommendationInput } from "./domain/model-ranking-service.ts";
7
10
  import type { TaskCostSummary } from "./domain/task-cost.ts";
8
11
  import type { UsageAggregateRow } from "./domain/usage.ts";
9
- import type { BenchmarkQuery, BenchmarkQueryResult, BenchmarkRefreshResult } from "./domain/benchmark.ts";
10
- import type { ModelRanker, ModelRecommendationInput } from "./domain/model-ranking-service.ts";
11
- import type { ModelRankingResult } from "./domain/model-ranking.ts";
12
- import type { BenchmarkController } from "./ports/benchmark-controller.ts";
13
- import type { MetricStore } from "./ports/metric-store.ts";
14
- import type { RouteOverride, RouterController, RouterStatus, TelemetryPollResult } from "./ports/router-controller.ts";
15
- import type { PolicyDecision, Route } from "./policy.ts";
16
- import { metricsOperations } from "./operations/metrics-operations.ts";
17
12
  import { benchmarkOperations } from "./operations/benchmark-operations.ts";
18
13
  import { contextOperations } from "./operations/context-operations.ts";
19
- import { routerOperations } from "./operations/router-operations.ts";
14
+ import { metricsOperations } from "./operations/metrics-operations.ts";
20
15
  import { modelRankingOperations } from "./operations/model-ranking-operations.ts";
16
+ import { routerOperations } from "./operations/router-operations.ts";
21
17
  import { sessionIdentityOperations } from "./operations/session-identity-operations.ts";
22
18
  import { routerMutationAuthorizer } from "./operations/session-scope.ts";
23
19
  import type { OperationHandlerMap } from "./operations/types.ts";
20
+ import type { PolicyDecision, Route } from "./policy.ts";
21
+ import type { BenchmarkController } from "./ports/benchmark-controller.ts";
22
+ import type { MetricStore } from "./ports/metric-store.ts";
23
+ import type { RouteOverride, RouterController, RouterStatus, TelemetryPollResult } from "./ports/router-controller.ts";
24
+ import { InvalidSessionSecretError, type RegisterSessionIdentityResult, type SessionIdentity } from "./session-identity-service.ts";
25
+ import { registerJittorVehicleOperations } from "./vehicle-registration.ts";
26
+ import { VERSION } from "./version.ts";
24
27
 
25
28
  export const EXPECTED_OPERATION_NAMES = [
26
29
  "metrics.record",
@@ -50,8 +53,11 @@ export const EXPECTED_OPERATION_NAMES = [
50
53
  "router.available_routes",
51
54
  ] as const;
52
55
 
53
- export type OperationName = typeof EXPECTED_OPERATION_NAMES[number];
54
- interface RouterScopeInput { session_id?: string; session_secret?: string }
56
+ export type OperationName = (typeof EXPECTED_OPERATION_NAMES)[number];
57
+ interface RouterScopeInput {
58
+ session_id?: string;
59
+ session_secret?: string;
60
+ }
55
61
  export interface OperationInputs {
56
62
  "session.register": { session_id: string };
57
63
  "session.release": { session_id: string; session_secret?: string };
@@ -111,31 +117,73 @@ export class UnknownOperationError extends Error {}
111
117
  export { InvalidSessionSecretError };
112
118
 
113
119
  class UnavailableModelRanker implements ModelRanker {
114
- rank(): ModelRankingResult { throw new Error("model ranking is not configured"); }
120
+ rank(): ModelRankingResult {
121
+ throw new Error("model ranking is not configured");
122
+ }
115
123
  }
116
124
 
117
125
  class UnavailableBenchmarkController implements BenchmarkController {
118
- async refresh(): Promise<BenchmarkRefreshResult> { return this.status(); }
119
- status(): BenchmarkRefreshResult { return { observedAt: Date.now(), sources: [] }; }
120
- query(): BenchmarkQueryResult { throw new Error("benchmark evidence is not configured"); }
126
+ async refresh(): Promise<BenchmarkRefreshResult> {
127
+ return this.status();
128
+ }
129
+ status(): BenchmarkRefreshResult {
130
+ return { observedAt: Date.now(), sources: [] };
131
+ }
132
+ query(): BenchmarkQueryResult {
133
+ throw new Error("benchmark evidence is not configured");
134
+ }
121
135
  }
122
136
 
123
137
  class UnavailableRouter implements RouterController {
124
- private readonly unavailable: RouterStatus = { ready: false, paused: false, sources: [], lastDecision: null, override: null, currentRoute: null, availableRoutes: [] };
125
- async poll(): Promise<TelemetryPollResult> { return { sources: [], observedAt: Date.now() }; }
126
- status(): RouterStatus { return structuredClone(this.unavailable); }
127
- decide(): PolicyDecision { return { action: "halt", pressure: Number.POSITIVE_INFINITY, reason: "router is not configured", decidedAt: Date.now(), trace: ["fail closed"] }; }
128
- pause(): RouterStatus { return this.status(); }
129
- resume(): RouterStatus { return this.status(); }
130
- setOverride(): RouterStatus { return this.status(); }
131
- clearOverride(): RouterStatus { return this.status(); }
132
- setCurrentRoute(): RouterStatus { return this.status(); }
133
- setAvailableRoutes(): RouterStatus { return this.status(); }
138
+ private readonly unavailable: RouterStatus = {
139
+ ready: false,
140
+ paused: false,
141
+ sources: [],
142
+ lastDecision: null,
143
+ override: null,
144
+ currentRoute: null,
145
+ availableRoutes: [],
146
+ };
147
+ async poll(): Promise<TelemetryPollResult> {
148
+ return { sources: [], observedAt: Date.now() };
149
+ }
150
+ status(): RouterStatus {
151
+ return structuredClone(this.unavailable);
152
+ }
153
+ decide(): PolicyDecision {
154
+ return {
155
+ action: "halt",
156
+ pressure: Number.POSITIVE_INFINITY,
157
+ reason: "router is not configured",
158
+ decidedAt: Date.now(),
159
+ trace: ["fail closed"],
160
+ };
161
+ }
162
+ pause(): RouterStatus {
163
+ return this.status();
164
+ }
165
+ resume(): RouterStatus {
166
+ return this.status();
167
+ }
168
+ setOverride(): RouterStatus {
169
+ return this.status();
170
+ }
171
+ clearOverride(): RouterStatus {
172
+ return this.status();
173
+ }
174
+ setCurrentRoute(): RouterStatus {
175
+ return this.status();
176
+ }
177
+ setAvailableRoutes(): RouterStatus {
178
+ return this.status();
179
+ }
134
180
  }
135
181
 
136
182
  export class JittorService {
137
183
  private readonly router: RouterController;
138
184
  private readonly operations: OperationHandlerMap;
185
+ /** Every jittor operation, also projected onto the real Vehicle protocol -- see vehicle-registration.ts. Served alongside (not replacing) the /api/v1/ops route below. */
186
+ readonly vehicleRegistry: VehicleRegistry;
139
187
 
140
188
  constructor(
141
189
  private readonly metrics: MetricStore,
@@ -157,6 +205,12 @@ export class JittorService {
157
205
  ...modelRankingOperations(modelRanker, router, authorize),
158
206
  ...sessionIdentityOperations(sessionIdentity),
159
207
  };
208
+ this.vehicleRegistry = new VehicleRegistry({
209
+ name: "jittor",
210
+ version: "1.0.0",
211
+ description: "Just-in-Time Token Optimizing Router for Pi -- metrics, benchmark evidence, model ranking, and router policy.",
212
+ });
213
+ registerJittorVehicleOperations(this.vehicleRegistry, this.operations);
160
214
  }
161
215
 
162
216
  operationNames(): OperationName[] {
@@ -204,10 +258,16 @@ function json(value: unknown, status = 200): Response {
204
258
 
205
259
  export function createApp(options: JittorAppOptions): { fetch(request: Request): Promise<Response> } {
206
260
  const maxBodyBytes = options.maxBodyBytes ?? SERVICE_MAX_BODY_BYTES;
261
+ // A real second transport for every jittor operation (see vehicle-registration.ts) --
262
+ // composed here rather than replacing /api/v1/ops, matching every other Vehicle-migrated
263
+ // daemon in this ecosystem ("served alongside", not "instead of"). Routed before the
264
+ // top-level bearer check below since createVehicleHttpApp performs its own.
265
+ const vehicleApp = createVehicleHttpApp({ registry: options.service.vehicleRegistry, token: options.token });
207
266
  return {
208
267
  async fetch(request: Request): Promise<Response> {
209
- if (!requireBearerToken(request, options.token)) return errorResponse("unauthorized", 401);
210
268
  const url = new URL(request.url);
269
+ if (url.pathname.startsWith("/vehicle/")) return vehicleApp.fetch(request);
270
+ if (!requireBearerToken(request, options.token)) return errorResponse("unauthorized", 401);
211
271
  if (request.method === "GET" && url.pathname === "/health") return healthResponse(VERSION);
212
272
  if (request.method === "GET" && url.pathname === "/ready") return readyResponse(options.service.ready());
213
273
  if (request.method === "GET" && url.pathname === "/api/v1/ops") return json({ operations: options.service.operationNames() });
@@ -219,9 +279,10 @@ export function createApp(options: JittorAppOptions): { fetch(request: Request):
219
279
  try {
220
280
  const body = JSON.parse(text) as { op?: unknown; input?: unknown };
221
281
  if (typeof body.op !== "string") throw new Error("op is required");
222
- const input = typeof body.input === "object" && body.input !== null && !Array.isArray(body.input)
223
- ? body.input as Record<string, unknown>
224
- : {};
282
+ const input =
283
+ typeof body.input === "object" && body.input !== null && !Array.isArray(body.input)
284
+ ? (body.input as Record<string, unknown>)
285
+ : {};
225
286
  return json({ result: await options.service.execute(body.op, input) });
226
287
  } catch (error) {
227
288
  if (error instanceof UnknownOperationError) return json({ error: error.message }, 404);
@@ -1,4 +1,9 @@
1
- import { isSessionRegistered, registerSessionIdentity, releaseSessionIdentity, verifySessionSecret } from "@danypops/vehicle-server/session-identity";
1
+ import {
2
+ isSessionRegistered,
3
+ registerSessionIdentity,
4
+ releaseSessionIdentity,
5
+ verifySessionSecret,
6
+ } from "@danypops/vehicle-server/session-identity";
2
7
  import type { SessionIdentityStore } from "./ports/session-identity-store.ts";
3
8
 
4
9
  export interface RegisterSessionIdentityResult {
@@ -50,6 +55,9 @@ export class SessionIdentity {
50
55
  assertAuthorized(sessionId: string | undefined, secret: string | undefined): void {
51
56
  if (sessionId === undefined) return;
52
57
  if (!this.isRegistered(sessionId)) return;
53
- if (!this.verify(sessionId, secret)) throw new InvalidSessionSecretError(`session "${sessionId}" is registered; a valid session_secret is required to mutate its router state`);
58
+ if (!this.verify(sessionId, secret))
59
+ throw new InvalidSessionSecretError(
60
+ `session "${sessionId}" is registered; a valid session_secret is required to mutate its router state`,
61
+ );
54
62
  }
55
63
  }
package/src/state.ts CHANGED
@@ -6,22 +6,16 @@
6
6
  * client.ts, cli.ts, and their tests) is untouched by this migration.
7
7
  */
8
8
  import {
9
+ type DaemonHandle,
10
+ type DaemonPaths,
9
11
  ensureAuthToken as ensureVehicleAuthToken,
12
+ type PathEnvironment,
10
13
  readDaemonHandle as readVehicleHandle,
11
14
  removeDaemonHandle as removeVehicleHandle,
12
15
  resolveDaemonPaths,
13
16
  writeDaemonHandle as writeVehicleHandle,
14
- type DaemonHandle,
15
- type DaemonPaths,
16
- type PathEnvironment,
17
17
  } from "@danypops/vehicle-server/paths";
18
- import {
19
- DATABASE_FILENAME,
20
- HANDLE_FILENAME,
21
- JITTOR_STATE_DIRECTORY,
22
- SYSTEMD_UNIT_NAME,
23
- TOKEN_FILENAME,
24
- } from "./constants.ts";
18
+ import { DATABASE_FILENAME, HANDLE_FILENAME, JITTOR_STATE_DIRECTORY, SYSTEMD_UNIT_NAME, TOKEN_FILENAME } from "./constants.ts";
25
19
 
26
20
  export type JittorPaths = DaemonPaths;
27
21
  export type { DaemonHandle, PathEnvironment };
@@ -0,0 +1,154 @@
1
+ /**
2
+ * jittor's 25-operation surface projected onto the real Vehicle protocol.
3
+ * Every operation delegates to the exact same handler function service.ts's
4
+ * hand-rolled dispatch already calls (one implementation, two projections --
5
+ * the same shape every other Vehicle-migrated daemon in this ecosystem
6
+ * uses) -- no behavior change, only a second real transport served
7
+ * alongside (not replacing) the existing /api/v1/ops route.
8
+ *
9
+ * Every jittor operation already validates its own loosely-typed
10
+ * Record<string, unknown> input internally (see operations/*.ts) -- there
11
+ * is no separate Vehicle-side schema to duplicate that logic, so both input
12
+ * and output use passthroughVehicleSchema and let the real handler's own
13
+ * validation (already covered by service.test.ts) be the single source of
14
+ * truth for what's accepted.
15
+ *
16
+ * jittor's operations are never exposed as Pi tools (confirmed: pi-jittor
17
+ * has zero pi.registerTool() call sites -- its whole surface is consumed
18
+ * internally by the extension's own footer/context-hub/router capabilities),
19
+ * so effect/permission classification here is about standardized taxonomy
20
+ * and future eligibility (Vehicle Jobs, Approval Gate, Safety), not gating
21
+ * an agent-facing tool call the way it does for a Vehicle whose operations
22
+ * ARE projected as tools.
23
+ */
24
+
25
+ import type { VehicleEffect, VehicleIdempotency } from "@danypops/vehicle-core";
26
+ import { bindVehicleOperation, defineVehicleOperation, passthroughVehicleSchema, VehicleError } from "@danypops/vehicle-core";
27
+ import type { VehicleRegistry } from "@danypops/vehicle-server";
28
+ import type { OperationHandlerMap } from "./operations/types.ts";
29
+ import type { OperationName } from "./service.ts";
30
+ import { InvalidSessionSecretError } from "./session-identity-service.ts";
31
+
32
+ /**
33
+ * Preserves /api/v1/ops's own error-to-status behavior (see createApp() in
34
+ * service.ts): InvalidSessionSecretError -> 403, everything else a handler
35
+ * throws -> 400. Without this, VehicleRegistry's own invoke() wraps any
36
+ * handler-thrown error that isn't already a VehicleError into
37
+ * category:"internal" (HTTP 500), losing both the specific status and the
38
+ * error's own message (a VehicleFailure never serializes an error's cause
39
+ * over the wire) -- the same regression class found and fixed in
40
+ * web-spider's own migration (see its vehicle-error-parity.ts).
41
+ */
42
+ async function withJittorErrorParity<T>(run: () => T | Promise<T>): Promise<T> {
43
+ try {
44
+ return await run();
45
+ } catch (error) {
46
+ if (error instanceof VehicleError) throw error;
47
+ if (error instanceof InvalidSessionSecretError) {
48
+ throw new VehicleError("invalid-session-secret", error.message, { category: "authorization", cause: error });
49
+ }
50
+ const message = error instanceof Error ? error.message : String(error);
51
+ throw new VehicleError("operation-rejected", message, { category: "validation", cause: error });
52
+ }
53
+ }
54
+
55
+ const OWNER = "jittor";
56
+ const LIMITS = { defaultTimeoutMs: 10_000, maxTimeoutMs: 30_000, maxRequestBytes: 65_536, maxResponseBytes: 1_048_576 };
57
+
58
+ interface OperationMeta {
59
+ readonly description: string;
60
+ readonly effect: VehicleEffect;
61
+ }
62
+
63
+ const READ: VehicleIdempotency = { mode: "safe" };
64
+ const WRITE: VehicleIdempotency = { mode: "unsafe" };
65
+
66
+ /**
67
+ * One entry per EXPECTED_OPERATION_NAMES member -- effect classification
68
+ * rationale:
69
+ * - metrics.record/record_batch: local-write (jittor's own metric store).
70
+ * - metrics.query/distinct_scopes/usage_series/cost_by_task: read (pure
71
+ * projections of already-recorded local data).
72
+ * - metrics.prune: destructive (irreversible deletion; the handler itself
73
+ * already refuses a too-recent cutoff without force:true).
74
+ * - benchmark.refresh: external-write (refreshes from external model
75
+ * benchmark/leaderboard sources) -- a genuine future Vehicle Jobs
76
+ * candidate given its existing status/query split, deferred here (see
77
+ * this task's closing note) same as web-spider's fetch/crawl.
78
+ * - benchmark.status/query: read (local cache of the last refresh).
79
+ * - session.register/release: local-write (jittor's own session-identity
80
+ * registry).
81
+ * - models.rank: local-write -- primarily a read/scoring operation, but an
82
+ * automatic selection applies it as a real router mutation as a side
83
+ * effect (see model-ranking-operations.ts), so classified by its most
84
+ * consequential possible outcome, not its common case.
85
+ * - context.assess/compaction.estimate: read.
86
+ * - service.checkpoint: local-write (flushes the metric store's WAL).
87
+ * - telemetry.poll: read (reads external telemetry provider status; does
88
+ * not itself expose a way to write anything back to those providers).
89
+ * - router.status/decide: read (a decision query, not a mutation).
90
+ * - router.pause/resume/override/clear_override/current_route/
91
+ * available_routes: local-write (every one is a real router.set (or
92
+ * pause/resume) call, despite router.current_route's read-sounding name
93
+ * -- confirmed directly against router-operations.ts).
94
+ */
95
+ const OPERATION_META: Record<OperationName, OperationMeta> = {
96
+ "metrics.record": { description: "Records one metric observation.", effect: "local-write" },
97
+ "metrics.record_batch": { description: "Records a bounded batch of metric observations as one atomic unit.", effect: "local-write" },
98
+ "metrics.query": { description: "Queries recorded metric observations.", effect: "read" },
99
+ "metrics.distinct_scopes": { description: "Lists distinct scopes recorded for a source within a bounded time window.", effect: "read" },
100
+ "metrics.usage_series": { description: "Server-side bucketed usage aggregation for a source's distinct scopes.", effect: "read" },
101
+ "metrics.cost_by_task": { description: "Sums cost/token metrics by focused task within a bounded time window.", effect: "read" },
102
+ "metrics.prune": { description: "Deletes metric observations older than a cutoff. Irreversible.", effect: "destructive" },
103
+ "benchmark.refresh": { description: "Refreshes model benchmark evidence from external sources.", effect: "external-write" },
104
+ "benchmark.status": { description: "Reports the last benchmark refresh's own status.", effect: "read" },
105
+ "benchmark.query": { description: "Queries cached benchmark evidence.", effect: "read" },
106
+ "session.register": { description: "Registers a Pi session identity, issuing a session secret.", effect: "local-write" },
107
+ "session.release": { description: "Releases a registered Pi session identity.", effect: "local-write" },
108
+ "models.rank": {
109
+ description: "Scores model candidates; an automatic selection also applies it as a router mutation.",
110
+ effect: "local-write",
111
+ },
112
+ "context.assess": { description: "Assesses context-injection/compaction health within a bounded time window.", effect: "read" },
113
+ "compaction.estimate": { description: "Estimates compaction duration from recorded samples.", effect: "read" },
114
+ "service.checkpoint": { description: "Flushes the metric store's write-ahead log.", effect: "local-write" },
115
+ "telemetry.poll": { description: "Polls external telemetry provider status.", effect: "read" },
116
+ "router.status": { description: "Reports the router's current status.", effect: "read" },
117
+ "router.decide": { description: "Computes the router's current policy decision without applying it.", effect: "read" },
118
+ "router.pause": { description: "Pauses automatic routing.", effect: "local-write" },
119
+ "router.resume": { description: "Resumes automatic routing.", effect: "local-write" },
120
+ "router.override": { description: "Sets a time-bounded manual route override.", effect: "local-write" },
121
+ "router.clear_override": { description: "Clears a manual route override.", effect: "local-write" },
122
+ "router.current_route": { description: "Sets the router's currently-active route.", effect: "local-write" },
123
+ "router.available_routes": { description: "Sets the router's currently-available routes.", effect: "local-write" },
124
+ };
125
+
126
+ /** Read effects need only jittor:read; every other effect needs both (writes commonly also read first). */
127
+ function permissionsFor(effect: VehicleEffect): readonly string[] {
128
+ return effect === "read" ? ["jittor:read"] : ["jittor:read", "jittor:write"];
129
+ }
130
+
131
+ export function registerJittorVehicleOperations(registry: VehicleRegistry, operations: OperationHandlerMap): void {
132
+ for (const [name, meta] of Object.entries(OPERATION_META) as Array<[OperationName, OperationMeta]>) {
133
+ const handler = operations[name];
134
+ if (!handler) throw new Error(`jittor Vehicle registration: no handler configured for operation "${name}"`);
135
+ const operation = defineVehicleOperation({
136
+ name,
137
+ version: 1,
138
+ description: meta.description,
139
+ input: passthroughVehicleSchema,
140
+ output: passthroughVehicleSchema,
141
+ permissions: permissionsFor(meta.effect),
142
+ effect: meta.effect,
143
+ idempotency: meta.effect === "read" ? READ : WRITE,
144
+ limits: LIMITS,
145
+ });
146
+ registry.register(
147
+ OWNER,
148
+ bindVehicleOperation(
149
+ operation,
150
+ () => async (context) => withJittorErrorParity(() => handler(context.input as Record<string, unknown>)),
151
+ ),
152
+ );
153
+ }
154
+ }