@stage-labs/metro 0.1.0-beta.73 → 0.1.0-beta.74

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage-labs/metro",
3
- "version": "0.1.0-beta.73",
3
+ "version": "0.1.0-beta.74",
4
4
  "description": "The metro command line. Sign in once per machine, then hand your MCP connector list to Claude Code without the credentials touching disk, argv or shell history.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -7,6 +7,7 @@ import { beginLogin, CodexAuthError, finishLogin, readCodexCliAuth } from '../ga
7
7
  import { beginDeviceLogin, pollDeviceLogin } from '../gateway/codex-device.js';
8
8
  import { codexModels, currentTokens, freshCodexState } from '../gateway/codex.js';
9
9
  import { openrouterModels } from '../gateway/openrouter.js';
10
+ import { lastServed } from '../gateway/served.js';
10
11
  import type { CodexTokens } from '../gateway/codex-auth.js';
11
12
  import { GatewayError } from '../gateway/forward.js';
12
13
  import {
@@ -49,6 +50,8 @@ interface Route {
49
50
  run: Handler;
50
51
  }
51
52
 
53
+ const settingsBody = (cfg: ModelConfig): Record<string, unknown> => ({ ...publicModelConfig(cfg), lastServed: lastServed() });
54
+
52
55
  function asApiError(err: unknown): never {
53
56
  if (err instanceof ModelConfigError || err instanceof CodexAuthError) throw new ApiError(err.message, 400);
54
57
  if (err instanceof GatewayError) throw new ApiError(err.message, 502);
@@ -65,13 +68,13 @@ async function update(req: IncomingMessage, store: Store): Promise<unknown> {
65
68
  }
66
69
  store.write(next);
67
70
  log.info({ provider: next.provider }, 'model-api: route updated');
68
- return publicModelConfig(next);
71
+ return settingsBody(next);
69
72
  }
70
73
 
71
74
  function saveCodex(store: Store, cfg: ModelConfig, note: string): unknown {
72
75
  store.write(cfg);
73
76
  log.info({ signedIn: cfg.codex.auth !== null, plan: cfg.codex.auth?.plan ?? null }, note);
74
- return publicModelConfig(cfg);
77
+ return settingsBody(cfg);
75
78
  }
76
79
 
77
80
  const modelApiState = freshCodexState();
@@ -146,7 +149,7 @@ const named = (table: Record<string, Route>, name: string, method: string | unde
146
149
  };
147
150
 
148
151
  function settingsRoute(method: string | undefined): Route | number {
149
- if (method === 'GET') return { method: 'GET', run: (_req, _deps, store) => Promise.resolve(publicModelConfig(store.read())) };
152
+ if (method === 'GET') return { method: 'GET', run: (_req, _deps, store) => Promise.resolve(settingsBody(store.read())) };
150
153
  if (method === 'PUT') return { method: 'POST', run: (req, _deps, store) => update(req, store) };
151
154
  return 405;
152
155
  }
@@ -13,6 +13,7 @@ import { forwardedHeaders, GatewayError, parseJson, pipeResponse, readBody, send
13
13
  import { notReady, readModelConfig, resolveRoute, routeLabel, setCodexAuth, writeModelConfig, type ModelConfig, type Route } from './model-config.js';
14
14
  import { codexCount, codexMessages, freshCodexState, type CodexDeps } from './codex.js';
15
15
  import { OPENROUTER_BASE } from './openrouter.js';
16
+ import { forgetServed, noteServed } from './served.js';
16
17
  import type { CodexTokens } from './codex-auth.js';
17
18
 
18
19
  export const GATEWAY_PREFIX = '/gateway';
@@ -35,6 +36,7 @@ const learned: Adaptations = freshAdaptations();
35
36
  const codexState = freshCodexState();
36
37
 
37
38
  export function resetGatewayState(): void {
39
+ forgetServed();
38
40
  learned.fields.clear();
39
41
  learned.dropBetas = false;
40
42
  Object.assign(codexState, freshCodexState());
@@ -127,6 +129,7 @@ async function dispatch(req: IncomingMessage, res: ServerResponse, path: string,
127
129
  const body = parseJson(raw);
128
130
  const route = resolveRoute(requestedModel(body), cfg);
129
131
  log.info({ route: routeLabel(route), path }, 'gateway: routing');
132
+ if (path === MESSAGES) noteServed({ provider: route.provider, model: route.model, at: new Date().toISOString() });
130
133
  if (route.provider === 'bedrock') {
131
134
  assertBedrockReady(cfg.bedrock);
132
135
  const up = { settings: cfg.bedrock, base: deps.bedrockBase ?? bedrockBase(cfg.bedrock.region), learned, watch: watchUpstream(res) };
@@ -1,3 +1,4 @@
1
+ import { isRecord } from '../daemon/is-record.js';
1
2
  import { GatewayError } from './forward.js';
2
3
 
3
4
  export const OPENROUTER_BASE = 'https://openrouter.ai/api';
@@ -6,15 +7,23 @@ const MODELS_MAX = 2000;
6
7
  export interface OpenRouterModel {
7
8
  id: string;
8
9
  name: string;
10
+ prompt: number | null;
11
+ completion: number | null;
9
12
  }
10
13
 
11
14
  const str = (value: unknown): string => (typeof value === 'string' ? value : '');
12
15
 
16
+ function price(raw: unknown): number | null {
17
+ const value = typeof raw === 'string' ? Number(raw) : typeof raw === 'number' ? raw : Number.NaN;
18
+ return Number.isFinite(value) && value >= 0 ? value : null;
19
+ }
20
+
13
21
  function modelOf(entry: unknown): OpenRouterModel | null {
14
- if (typeof entry !== 'object' || entry === null) return null;
15
- const row = entry as { id?: unknown; name?: unknown };
16
- const id = str(row.id);
17
- return id === '' ? null : { id, name: str(row.name) || id };
22
+ if (!isRecord(entry)) return null;
23
+ const id = str(entry.id);
24
+ if (id === '') return null;
25
+ const pricing = isRecord(entry.pricing) ? entry.pricing : {};
26
+ return { id, name: str(entry.name) || id, prompt: price(pricing.prompt), completion: price(pricing.completion) };
18
27
  }
19
28
 
20
29
  export async function openrouterModels(base = OPENROUTER_BASE, fetchImpl: typeof fetch = fetch): Promise<OpenRouterModel[]> {
@@ -0,0 +1,17 @@
1
+ export interface Served {
2
+ provider: string;
3
+ model: string;
4
+ at: string;
5
+ }
6
+
7
+ let last: Served | null = null;
8
+
9
+ export const noteServed = (served: Served): void => {
10
+ last = served;
11
+ };
12
+
13
+ export const lastServed = (): Served | null => last;
14
+
15
+ export const forgetServed = (): void => {
16
+ last = null;
17
+ };
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.1.0-beta.73"
2
+ "version": "0.1.0-beta.74"
3
3
  }