@stage-labs/metro 0.1.0-beta.322 → 0.1.0-beta.323

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.322",
3
+ "version": "0.1.0-beta.323",
4
4
  "description": "The metro command line: run your agent on this machine, relaying its chat channels and connectors to Claude Code.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -11,11 +11,12 @@ import { isRecord } from '@metro-labs/core/is-record';
11
11
  import { readJson, writeJson } from '@metro-labs/core/secure-fs';
12
12
  import { METRO_VERSION } from '@metro-labs/core/version';
13
13
  import { agentsDir, listAgentFiles } from '../agents/files.js';
14
- import { notReady, primaryRoute, type ModelSetup } from '@metro-labs/inference/model-config';
14
+ import { primaryRoute, type ModelSetup } from '@metro-labs/inference/model-config';
15
15
  import { effectiveSetup } from '../gateway/effective.js';
16
16
  import { inSessionScope } from './memory.js';
17
17
  import { runningAsRoot } from '../metro-user/privilege.js';
18
18
  import { agentUser, agentUserExpected, agentViewDir, asAgent, claudeHome } from '../agent-user/user.js';
19
+ import { routeReadiness } from '../gateway/box-gateway.js';
19
20
 
20
21
  function sessionEnv(): Record<string, string> {
21
22
  const user = agentUser();
@@ -133,7 +134,7 @@ const NO_MODEL = "the organization routes this agent to no model yet: choose one
133
134
  function credentialReady(deps: SessionDeps): string | null {
134
135
  try {
135
136
  const primary = primaryRoute((deps.model ?? (() => effectiveSetup()))());
136
- return primary === null ? NO_MODEL : notReady(primary);
137
+ return primary === null ? NO_MODEL : routeReadiness(primary);
137
138
  } catch (err) {
138
139
  return `the model setup is not readable (${errMsg(err)})`;
139
140
  }
@@ -1,6 +1,7 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http';
2
2
  import { log } from '@metro-labs/core/log';
3
3
  import { handleGatewayRequest, type GatewayDeps } from '@metro-labs/inference/gateway';
4
+ import { notReady, setupNotReady, type Route } from '@metro-labs/inference/model-config';
4
5
  import { agentsDir } from '../agents/files.js';
5
6
  import { agentIdForKey } from '../agents/keys.js';
6
7
  import { reregisterMcpToken } from '../mcp-server/link.js';
@@ -13,6 +14,8 @@ type Handler = (req: IncomingMessage, res: ServerResponse) => boolean;
13
14
 
14
15
  const identify = (key: string): boolean => agentIdForKey(key) !== undefined;
15
16
 
17
+ export const routeReadiness = (route: Route): string | null => (gatewayUrl() === null ? notReady(route) : setupNotReady(route));
18
+
16
19
  export const boxGateway = (): GatewayDeps => ({
17
20
  setup: () => effectiveSetup(),
18
21
  identify,
@@ -4,7 +4,8 @@ import { ApiError } from '@metro-labs/http/api-error';
4
4
  import { apiFailure, apiSession, cors, sendJson } from '@metro-labs/http/api-http';
5
5
  import { effectiveSetup } from './effective.js';
6
6
  import { chainStatus } from '@metro-labs/inference/fallback';
7
- import { helperSetup, notReady, primaryRoute, type Connection, type ModelSetup } from '@metro-labs/inference/model-config';
7
+ import { helperSetup, primaryRoute, type Connection, type ModelSetup } from '@metro-labs/inference/model-config';
8
+ import { routeReadiness } from './box-gateway.js';
8
9
  import { orgModels, type SyncStatus } from './org-models.js';
9
10
  import { lastServed } from '@metro-labs/inference/served';
10
11
  import { refreshUsage, type UsageDeps } from '@metro-labs/inference/usage-refresh';
@@ -29,8 +30,8 @@ function publicConnection(c: Connection): Record<string, unknown> {
29
30
  provider: c.provider,
30
31
  label: c.label,
31
32
  model: c.model,
32
- hasKey: c.apiKey !== '',
33
- subscription: c.claudeToken !== '',
33
+ hasKey: c.apiKey !== '' || c.auth === 'key',
34
+ subscription: c.claudeToken !== '' || c.auth === 'subscription',
34
35
  region: c.region,
35
36
  zdr: c.zdr,
36
37
  signedOut: c.signedOut,
@@ -52,7 +53,7 @@ function helpersBody(setup: ModelSetup): Record<string, unknown> | null {
52
53
  export function settingsBody(deps: ModelApiDeps): Record<string, unknown> {
53
54
  const setup = setupOf(deps);
54
55
  const primary = primaryRoute(setup);
55
- const reason = primary === null ? NO_MODEL : notReady(primary);
56
+ const reason = primary === null ? NO_MODEL : routeReadiness(primary);
56
57
  return {
57
58
  source: setup.source,
58
59
  sync: (deps.syncStatus ?? (() => orgModels()?.syncStatus() ?? NO_SYNC))(),
@@ -1,6 +1,6 @@
1
1
  import { readEffort, type RouteEffort } from '@metro-labs/core/effort';
2
2
  import { isRecord } from '@metro-labs/core/is-record';
3
- import { emptySetup, isProvider, type ModelSetup, type Account, type Chain, type CodexAccess, type Connection, type Fallback, type Provider, type RouteSource } from './model-config.js';
3
+ import { emptySetup, isProvider, type AuthKind, type ModelSetup, type Account, type Chain, type CodexAccess, type Connection, type Fallback, type Provider, type RouteSource } from './model-config.js';
4
4
 
5
5
  export interface DeliveredRoute {
6
6
  primary: Fallback | null;
@@ -69,6 +69,10 @@ function credentialOf(provider: Provider, raw: unknown): Credential {
69
69
  return { apiKey: text(raw.apiKey), claudeToken: provider === 'anthropic' ? text(raw.claudeToken) : '', codex: null };
70
70
  }
71
71
 
72
+ const AUTH_KINDS: readonly AuthKind[] = ['key', 'subscription', 'codex'];
73
+
74
+ const authOf = (raw: unknown): AuthKind | null => AUTH_KINDS.find((kind) => kind === raw) ?? null;
75
+
72
76
  export function deliveredConnection(raw: unknown): Connection | null {
73
77
  if (!isRecord(raw) || !isProvider(raw.provider)) return null;
74
78
  const id = text(raw.id);
@@ -86,7 +90,8 @@ export function deliveredConnection(raw: unknown): Connection | null {
86
90
  codex,
87
91
  claudeToken,
88
92
  account: accountOf(raw.account),
89
- signedOut: codex === null && apiKey === '' && claudeToken === '',
93
+ signedOut: typeof raw.signedOut === 'boolean' ? raw.signedOut : codex === null && apiKey === '' && claudeToken === '',
94
+ auth: authOf(raw.auth),
90
95
  };
91
96
  }
92
97
 
@@ -31,8 +31,11 @@ export interface Connection {
31
31
  claudeToken: string;
32
32
  account: Account;
33
33
  signedOut: boolean;
34
+ auth?: AuthKind | null;
34
35
  }
35
36
 
37
+ export type AuthKind = 'key' | 'subscription' | 'codex';
38
+
36
39
  export interface Fallback {
37
40
  connection: string;
38
41
  model: string;
@@ -77,28 +80,36 @@ export function primaryRoute(setup: ModelSetup): Route | null {
77
80
  return null;
78
81
  }
79
82
 
80
- const CHECKS: Record<Provider, [(r: Route) => boolean, string][]> = {
81
- anthropic: [[(r) => r.connection.apiKey === '' && r.connection.claudeToken === '', "Anthropic has no API key or Claude subscription: add one on the organization's Gateway page."]],
83
+ type Check = [(r: Route) => boolean, string, 'credential' | 'setup'];
84
+
85
+ const CHECKS: Record<Provider, Check[]> = {
86
+ anthropic: [[(r) => r.connection.apiKey === '' && r.connection.claudeToken === '', "Anthropic has no API key or Claude subscription: add one on the organization's Gateway page.", 'credential']],
82
87
  bedrock: [
83
- [(r) => r.connection.apiKey === '', "Bedrock has no API key: add it on the organization's Gateway page."],
84
- [(r) => r.connection.region === '', "Bedrock needs a region: add it on the organization's Gateway page."],
88
+ [(r) => r.connection.apiKey === '', "Bedrock has no API key: add it on the organization's Gateway page.", 'credential'],
89
+ [(r) => r.connection.region === '', "Bedrock needs a region: add it on the organization's Gateway page.", 'setup'],
85
90
  ],
86
91
  openrouter: [
87
- [(r) => r.connection.apiKey === '', "OpenRouter has no API key: add it on the organization's Gateway page."],
88
- [(r) => r.model === '', "OpenRouter needs a model id: choose one on the organization's Gateway page."],
92
+ [(r) => r.connection.apiKey === '', "OpenRouter has no API key: add it on the organization's Gateway page.", 'credential'],
93
+ [(r) => r.model === '', "OpenRouter needs a model id: choose one on the organization's Gateway page.", 'setup'],
89
94
  ],
90
95
  codex: [
91
- [(r) => r.connection.codex === null, CODEX_SIGN_IN],
92
- [(r) => r.model === '', "Codex needs a model id: choose one on the organization's Gateway page."],
96
+ [(r) => r.connection.codex === null, CODEX_SIGN_IN, 'credential'],
97
+ [(r) => r.model === '', "Codex needs a model id: choose one on the organization's Gateway page.", 'setup'],
93
98
  ],
94
99
  routeway: [
95
- [(r) => r.connection.apiKey === '', "Routeway has no API key: add it on the organization's Gateway page."],
96
- [(r) => r.model === '', "Routeway needs a model id: choose one on the organization's Gateway page."],
100
+ [(r) => r.connection.apiKey === '', "Routeway has no API key: add it on the organization's Gateway page.", 'credential'],
101
+ [(r) => r.model === '', "Routeway needs a model id: choose one on the organization's Gateway page.", 'setup'],
97
102
  ],
98
103
  };
99
104
 
100
105
  export const notReady = (route: Route): string | null => CHECKS[route.connection.provider].find(([missing]) => missing(route))?.[1] ?? null;
101
106
 
107
+ export function setupNotReady(route: Route): string | null {
108
+ const checks = CHECKS[route.connection.provider];
109
+ if (route.connection.signedOut) return checks.find(([, , kind]) => kind === 'credential')?.[1] ?? null;
110
+ return checks.find(([missing, , kind]) => kind === 'setup' && missing(route))?.[1] ?? null;
111
+ }
112
+
102
113
  export const isSmallModel = (requested: string): boolean => SMALL_RE.test(requested);
103
114
 
104
115
  const DEFAULTS: Record<Provider, (requested: string, model: string) => string> = {
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.1.0-beta.322"
2
+ "version": "0.1.0-beta.323"
3
3
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.0-beta.322",
2
+ "version": "0.1.0-beta.323",
3
3
  "dependencies": {
4
4
  "@anthropic-ai/claude-agent-sdk": "0.3.287",
5
5
  "@modelcontextprotocol/sdk": "^1.29.0",