@stage-labs/metro 0.1.0-beta.307 → 0.1.0-beta.309
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/dist/agent.js +23 -28
- package/dist/child.js +33 -0
- package/dist/claude-env.js +104 -0
- package/dist/cli.js +7 -21
- package/dist/lock.js +62 -0
- package/dist/memory.js +12 -27
- package/dist/root-release-io.js +1 -2
- package/dist/root-release.js +1 -1
- package/dist/route.js +0 -1
- package/hooks/guard.mjs +1 -5
- package/hooks/session-start.mjs +1 -1
- package/package.json +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/provision.ts +13 -38
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +21 -1
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +3 -9
- package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +14 -12
- package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +6 -18
- package/runtime/node_modules/@metro-labs/daemon/src/claude/github-skills.ts +3 -7
- package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +6 -5
- package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +1 -23
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +39 -118
- package/runtime/node_modules/@metro-labs/daemon/src/claude/setup-api.ts +14 -61
- package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +10 -42
- package/runtime/node_modules/@metro-labs/daemon/src/claude/usage-probe.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +1 -6
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/claude-logins.ts +53 -29
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/claude-store.ts +55 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +40 -62
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/delivered.ts +82 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/effective.ts +47 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/fallback.ts +18 -19
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +0 -1
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +37 -77
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +86 -220
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +80 -283
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-import.ts +125 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-lists.ts +0 -17
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-v2.ts +79 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +0 -79
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/org-models.ts +231 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +4 -44
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/text.ts +0 -2
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage-refresh.ts +37 -57
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +0 -13
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +5 -15
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/instructions.ts +0 -11
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-slot.ts +3 -7
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +12 -24
- package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +1 -4
- package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +1 -2
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +3 -4
- package/runtime/node_modules/@metro-labs/daemon/src/voice/call.ts +9 -92
- package/runtime/node_modules/@metro-labs/daemon/src/voice/calls.ts +1 -4
- package/runtime/node_modules/@metro-labs/daemon/src/voice/scribe.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/voice/shared.ts +0 -2
- package/runtime/node_modules/@metro-labs/daemon/src/voice/speech.ts +0 -4
- package/runtime/node_modules/@metro-labs/daemon/src/voice/store.ts +1 -14
- package/runtime/node_modules/@metro-labs/http/src/api-http.ts +0 -1
- package/runtime/runtime.json +1 -1
- package/runtime/sdk-runner/runner.json +1 -1
- package/dist/background.js +0 -141
- package/dist/claude.js +0 -204
- package/dist/mcp-config.js +0 -27
- package/dist/native-recovery-scan.js +0 -246
- package/dist/native-recovery-state.js +0 -154
- package/dist/native-recovery.js +0 -164
- package/dist/onboarding.js +0 -44
- package/dist/voice.js +0 -56
- package/runtime/node_modules/@metro-labs/daemon/src/claude/channel-check.ts +0 -56
- package/runtime/node_modules/@metro-labs/daemon/src/claude/pinned.ts +0 -16
- package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-cleanup.ts +0 -121
- package/runtime/node_modules/@metro-labs/daemon/src/claude/runner.ts +0 -45
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session-continuity.ts +0 -57
- package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +0 -76
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +0 -219
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +0 -119
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +0 -160
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +0 -46
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +0 -150
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +0 -139
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +0 -236
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +0 -208
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-signin.ts +0 -157
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-store.ts +0 -83
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter-login.ts +0 -192
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter-signin.ts +0 -64
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/pkce.ts +0 -36
- package/runtime/node_modules/@metro-labs/daemon/src/voice/approvals.ts +0 -38
- package/runtime/node_modules/@metro-labs/daemon/src/voice/brain.ts +0 -146
- package/runtime/node_modules/@metro-labs/daemon/src/voice/speakable.ts +0 -54
- package/runtime/node_modules/@metro-labs/daemon/src/voice/talk.ts +0 -264
|
@@ -1,231 +1,105 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
2
|
import { ApiError } from '@metro-labs/http/api-error';
|
|
3
|
-
import { apiFailure, apiSession, cors,
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
parseModelConfig,
|
|
16
|
-
readModelConfig,
|
|
17
|
-
readModelSnapshot,
|
|
18
|
-
removeConnection,
|
|
19
|
-
setFallbacks,
|
|
20
|
-
setRoute,
|
|
21
|
-
updateConnection,
|
|
22
|
-
writeModelConfig,
|
|
23
|
-
type ModelConfig,
|
|
24
|
-
} from './model-config.js';
|
|
25
|
-
import { asApiError, BODY_MAX, claudeLoginDeps, connectionFor, settingsBody, type ModelApiDeps, type Route, type Store } from './model-store.js';
|
|
26
|
-
import { dropUnusedClaudeLogins } from './claude-logins.js';
|
|
27
|
-
import { CODEX_ROUTES, codexDeviceRoute, GEMINI_ROUTES } from './model-signin.js';
|
|
28
|
-
import { refreshUsage } from './usage-refresh.js';
|
|
29
|
-
import { handleOpenRouterSignIn } from './openrouter-signin.js';
|
|
3
|
+
import { apiFailure, apiSession, cors, requireAdmin, sendJson, type ApiSession } from '@metro-labs/http/api-http';
|
|
4
|
+
import { agentsDir } from '../agents/files.js';
|
|
5
|
+
import { removeClaudeLogin } from './claude-logins.js';
|
|
6
|
+
import { readClaudeLogins, type ClaudeLogin } from './claude-store.js';
|
|
7
|
+
import { effectiveSetup } from './effective.js';
|
|
8
|
+
import { chainStatus } from './fallback.js';
|
|
9
|
+
import { notReady, primaryRoute, type Connection, type ModelSetup } from './model-config.js';
|
|
10
|
+
import { orgModels, type SyncStatus } from './org-models.js';
|
|
11
|
+
import { claudeModels } from './provider-models.js';
|
|
12
|
+
import { lastServed } from './served.js';
|
|
13
|
+
import { refreshUsage, type UsageDeps } from './usage-refresh.js';
|
|
14
|
+
import { usageSeen } from './usage.js';
|
|
30
15
|
|
|
31
16
|
const PATH = '/api/model';
|
|
32
17
|
const SNAPSHOT = '/api/model/snapshot';
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const agents = deps.setup?.agents;
|
|
64
|
-
if (harnessRunner(agents) === 'sdk') {
|
|
65
|
-
if (sdkAllowed(agents, next)) switchLive(before, next, deps);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
if (routeOf(next) !== routeOf(before) && restart())
|
|
69
|
-
log.info({ was: routeOf(before), now: routeOf(next) }, 'model-api: the model changed, so the Claude session restarts on it');
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function kept(store: Store, next: ModelConfig, deps: ModelApiDeps, note: string, fields: Record<string, unknown>): unknown {
|
|
73
|
-
const before = store.read();
|
|
74
|
-
store.write(next);
|
|
75
|
-
try {
|
|
76
|
-
followModel(before, next, deps);
|
|
77
|
-
} catch (err) {
|
|
78
|
-
store.write(before);
|
|
79
|
-
throw err;
|
|
80
|
-
}
|
|
81
|
-
syncAvailableModelsQuietly(deps.setup ?? {}, next);
|
|
82
|
-
log.info(fields, note);
|
|
83
|
-
return settingsBody(next);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function withBody(req: IncomingMessage, run: (body: Record<string, unknown>) => ModelConfig): Promise<ModelConfig> {
|
|
87
|
-
const body = await readJsonBody(req, BODY_MAX);
|
|
88
|
-
if (!isRecord(body)) throw new ApiError('body must be a JSON object', 400);
|
|
89
|
-
try {
|
|
90
|
-
return run(body);
|
|
91
|
-
} catch (err) {
|
|
92
|
-
return asApiError(err);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async function chooseRoute(req: IncomingMessage, deps: ModelApiDeps, store: Store): Promise<unknown> {
|
|
97
|
-
const next = await withBody(req, (body) => setRoute(store.read(), typeof body.route === 'string' ? body.route : ''));
|
|
98
|
-
return kept(store, next, deps, 'model-api: route changed', { route: next.route });
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function chooseFallbacks(req: IncomingMessage, deps: ModelApiDeps, store: Store): Promise<unknown> {
|
|
102
|
-
const next = await withBody(req, (body) => setFallbacks(store.read(), body.fallbacks));
|
|
103
|
-
return kept(store, next, deps, 'model-api: fallback models changed', { fallbacks: (next.fallbacks ?? []).map((f) => `${f.connection}:${f.model}`) });
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function create(req: IncomingMessage, deps: ModelApiDeps, store: Store): Promise<unknown> {
|
|
107
|
-
const next = await withBody(req, (body) => addConnection(store.read(), body));
|
|
108
|
-
return kept(store, next, deps, 'model-api: connection added', { connection: next.connections.at(-1)?.id ?? '' });
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async function change(req: IncomingMessage, deps: ModelApiDeps, store: Store, id: string): Promise<unknown> {
|
|
112
|
-
const before = connectionOf(store.read(), id)?.apiKey;
|
|
113
|
-
const next = await withBody(req, (body) => updateConnection(store.read(), id, body));
|
|
114
|
-
if (connectionOf(next, id)?.apiKey !== before) forgetReported(id);
|
|
115
|
-
return kept(store, next, deps, 'model-api: connection changed', { connection: id });
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function drop(deps: ModelApiDeps, store: Store, id: string): unknown {
|
|
119
|
-
const before = store.read();
|
|
120
|
-
let next: ModelConfig;
|
|
121
|
-
try {
|
|
122
|
-
next = removeConnection(before, id);
|
|
123
|
-
} catch (err) {
|
|
124
|
-
asApiError(err);
|
|
125
|
-
}
|
|
126
|
-
forgetOne(id);
|
|
127
|
-
const body = kept(store, next, deps, 'model-api: connection removed', { connection: id });
|
|
128
|
-
dropUnusedClaudeLogins(before, next, claudeLoginDeps(deps));
|
|
129
|
-
return body;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async function settingsWithUsage(deps: ModelApiDeps, store: Store): Promise<Record<string, unknown>> {
|
|
133
|
-
await refreshUsage(deps, store);
|
|
134
|
-
return settingsBody(store.read());
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const OPENROUTER_ROUTES: Record<string, Route> = {
|
|
138
|
-
models: { method: 'GET', run: async (_req, deps) => ({ models: await openrouterModels(deps.openrouterBase, deps.fetchImpl).catch(asApiError) }) },
|
|
139
|
-
zdr: { method: 'GET', run: async (_req, deps) => ({ models: await openrouterZdrModels(deps.openrouterBase, deps.fetchImpl).catch(asApiError) }) },
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const ANTHROPIC_ROUTES: Record<string, Route> = {
|
|
143
|
-
models: {
|
|
144
|
-
method: 'GET',
|
|
145
|
-
run: async (req, deps, store) => ({ models: await anthropicModels(connectionFor(store.read(), req, 'anthropic'), deps.anthropicBase, deps.fetchImpl).catch(asApiError) }),
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const BEDROCK_ROUTES: Record<string, Route> = {
|
|
150
|
-
models: {
|
|
151
|
-
method: 'GET',
|
|
152
|
-
run: async (req, deps, store) => ({ models: await bedrockModels(connectionFor(store.read(), req, 'bedrock'), deps.bedrockControlBase, deps.fetchImpl).catch(asApiError) }),
|
|
153
|
-
},
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const named = (table: Record<string, Route>, name: string, method: string | undefined): Route | number => {
|
|
157
|
-
const route = table[name];
|
|
158
|
-
if (route === undefined) return 404;
|
|
159
|
-
return route.method === method ? route : 405;
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
async function restore(req: IncomingMessage, store: Store, deps: ModelApiDeps): Promise<unknown> {
|
|
163
|
-
const body = await readJsonBody(req, RESTORE_MAX);
|
|
164
|
-
if (!isRecord(body)) throw new ApiError('body must be a JSON object', 400);
|
|
165
|
-
const before = store.read();
|
|
166
|
-
const next = parseModelConfig(body);
|
|
167
|
-
const answer = kept(store, next, deps, 'model-api: model setup restored from a file', {});
|
|
168
|
-
dropUnusedClaudeLogins(before, next, claudeLoginDeps(deps));
|
|
169
|
-
return answer;
|
|
18
|
+
const SYNC = '/api/model/sync';
|
|
19
|
+
const CLAUDE_MODELS = '/api/model/claude-models';
|
|
20
|
+
const CLAUDE_LOGIN = '/api/model/claude/';
|
|
21
|
+
const LOGIN_RE = /^[A-Za-z0-9_-]{11}$/;
|
|
22
|
+
|
|
23
|
+
export interface ModelApiDeps extends UsageDeps {
|
|
24
|
+
dir?: () => string;
|
|
25
|
+
setup?: () => ModelSetup;
|
|
26
|
+
sync?: () => Promise<void>;
|
|
27
|
+
syncStatus?: () => SyncStatus;
|
|
28
|
+
anthropicBase?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const NO_SYNC: SyncStatus = { enrolled: false, at: null, error: null };
|
|
32
|
+
|
|
33
|
+
function publicConnection(c: Connection): Record<string, unknown> {
|
|
34
|
+
return {
|
|
35
|
+
id: c.id,
|
|
36
|
+
provider: c.provider,
|
|
37
|
+
label: c.label,
|
|
38
|
+
model: c.model,
|
|
39
|
+
hasKey: c.apiKey !== '',
|
|
40
|
+
region: c.region,
|
|
41
|
+
zdr: c.zdr,
|
|
42
|
+
signedIn: c.codex !== null || (c.claude !== null && !c.signedOut),
|
|
43
|
+
signedOut: c.signedOut,
|
|
44
|
+
account: c.account.email,
|
|
45
|
+
plan: c.account.plan,
|
|
46
|
+
method: c.account.method,
|
|
47
|
+
};
|
|
170
48
|
}
|
|
171
49
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
50
|
+
const loginsOf = (deps: ModelApiDeps): ClaudeLogin[] => readClaudeLogins(deps.dir?.() ?? agentsDir());
|
|
51
|
+
const setupOf = (deps: ModelApiDeps): ModelSetup => (deps.setup ?? (() => effectiveSetup(deps.dir?.())))();
|
|
52
|
+
|
|
53
|
+
export function settingsBody(deps: ModelApiDeps): Record<string, unknown> {
|
|
54
|
+
const setup = setupOf(deps);
|
|
55
|
+
const primary = primaryRoute(setup);
|
|
56
|
+
const reason = primary === null ? null : notReady(primary);
|
|
57
|
+
return {
|
|
58
|
+
source: setup.source,
|
|
59
|
+
sync: (deps.syncStatus ?? (() => orgModels()?.syncStatus() ?? NO_SYNC))(),
|
|
60
|
+
route: primary?.connection.id ?? '',
|
|
61
|
+
model: primary?.model ?? '',
|
|
62
|
+
ready: reason === null,
|
|
63
|
+
reason,
|
|
64
|
+
connections: setup.connections.map(publicConnection),
|
|
65
|
+
fallbacks: setup.fallbacks,
|
|
66
|
+
chain: chainStatus(setup),
|
|
67
|
+
claude: loginsOf(deps),
|
|
68
|
+
lastServed: lastServed(),
|
|
69
|
+
usage: usageSeen(),
|
|
70
|
+
};
|
|
175
71
|
}
|
|
176
72
|
|
|
177
|
-
function
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (method === 'PUT') return { method: 'POST', run: chooseRoute };
|
|
181
|
-
return 405;
|
|
73
|
+
async function withUsage(deps: ModelApiDeps): Promise<Record<string, unknown>> {
|
|
74
|
+
await refreshUsage(setupOf(deps), loginsOf(deps), deps);
|
|
75
|
+
return settingsBody(deps);
|
|
182
76
|
}
|
|
183
77
|
|
|
184
|
-
function
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if (id === '' || id.includes('/')) return 404;
|
|
188
|
-
if (method === 'PUT') return { method: 'POST', run: (req, deps, store) => change(req, deps, store, id) };
|
|
189
|
-
if (method === 'DELETE') return { method: 'GET', run: (_req, deps, store) => Promise.resolve(drop(deps, store, id)) };
|
|
190
|
-
return 405;
|
|
78
|
+
async function synced(deps: ModelApiDeps): Promise<Record<string, unknown>> {
|
|
79
|
+
await (deps.sync ?? (() => orgModels()?.requested() ?? Promise.resolve()))();
|
|
80
|
+
return settingsBody(deps);
|
|
191
81
|
}
|
|
192
82
|
|
|
193
|
-
function
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return codexDeviceRoute(id, method);
|
|
83
|
+
function dropLogin(session: ApiSession, login: string, deps: ModelApiDeps): Record<string, unknown> {
|
|
84
|
+
requireAdmin(session);
|
|
85
|
+
removeClaudeLogin(login, { ...deps.claudeLogins, dir: deps.dir?.() ?? deps.claudeLogins?.dir });
|
|
86
|
+
return settingsBody(deps);
|
|
198
87
|
}
|
|
199
88
|
|
|
200
|
-
|
|
201
|
-
[OPENROUTER, OPENROUTER_ROUTES],
|
|
202
|
-
[ANTHROPIC, ANTHROPIC_ROUTES],
|
|
203
|
-
[BEDROCK, BEDROCK_ROUTES],
|
|
204
|
-
[GEMINI, GEMINI_ROUTES],
|
|
205
|
-
];
|
|
206
|
-
const EXACT = new Set([PATH, SNAPSHOT, FALLBACKS, BUNDLE, RESTORE, CONNECTIONS]);
|
|
207
|
-
const PREFIXES = [`${CONNECTIONS}/`, CODEX, ...TABLES.map(([prefix]) => prefix)];
|
|
89
|
+
type Run = (session: ApiSession) => Promise<unknown>;
|
|
208
90
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (path === SNAPSHOT) return
|
|
213
|
-
if (path ===
|
|
214
|
-
if (path ===
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
return codexRoute(path.slice(CODEX.length), method);
|
|
91
|
+
function routeFor(path: string, method: string | undefined, deps: ModelApiDeps): Run | number {
|
|
92
|
+
const only = (wanted: string, run: Run): Run | number => (method === wanted ? run : 405);
|
|
93
|
+
if (path === PATH) return only('GET', () => withUsage(deps));
|
|
94
|
+
if (path === SNAPSHOT) return only('GET', () => Promise.resolve(settingsBody(deps)));
|
|
95
|
+
if (path === SYNC) return only('POST', () => synced(deps));
|
|
96
|
+
if (path === CLAUDE_MODELS) return only('GET', () => Promise.resolve({ models: claudeModels(deps.anthropicBase) }));
|
|
97
|
+
const login = path.slice(CLAUDE_LOGIN.length);
|
|
98
|
+
if (!LOGIN_RE.test(login)) return 404;
|
|
99
|
+
return only('DELETE', (session) => Promise.resolve(dropLogin(session, login, deps)));
|
|
219
100
|
}
|
|
220
101
|
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
function checkRunnerRoute(before: ModelConfig, next: ModelConfig, deps: ModelApiDeps): void {
|
|
224
|
-
const agents = deps.setup?.agents;
|
|
225
|
-
if (harnessRunner(agents) !== 'sdk' || !sdkAllowed(agents, before) || sdkAllowed(agents, next)) return;
|
|
226
|
-
if (runnerIsLive(deps))
|
|
227
|
-
throw new ApiError('The Agent SDK session is running, and this change would leave a model or fallback it may not use. Let it finish its work, stop it, then save the change.', 409);
|
|
228
|
-
}
|
|
102
|
+
const mine = (path: string): boolean => path === PATH || path.startsWith(`${PATH}/`);
|
|
229
103
|
|
|
230
104
|
export function handleModelRequest(req: IncomingMessage, res: ServerResponse, deps: ModelApiDeps): boolean {
|
|
231
105
|
const path = (req.url ?? '').split('?')[0] ?? '';
|
|
@@ -234,15 +108,7 @@ export function handleModelRequest(req: IncomingMessage, res: ServerResponse, de
|
|
|
234
108
|
res.writeHead(204, cors(req)).end();
|
|
235
109
|
return true;
|
|
236
110
|
}
|
|
237
|
-
const
|
|
238
|
-
read: deps.read ?? (path === SNAPSHOT ? readModelSnapshot : readModelConfig),
|
|
239
|
-
write: (cfg) => {
|
|
240
|
-
checkRunnerRoute(store.read(), cfg, deps);
|
|
241
|
-
(deps.write ?? writeModelConfig)(cfg);
|
|
242
|
-
},
|
|
243
|
-
};
|
|
244
|
-
if (handleOpenRouterSignIn(req, res, deps, store)) return true;
|
|
245
|
-
const route = routeFor(path, req.method);
|
|
111
|
+
const route = path === PATH || path.startsWith(CLAUDE_LOGIN) || [SNAPSHOT, SYNC, CLAUDE_MODELS].includes(path) ? routeFor(path, req.method, deps) : 404;
|
|
246
112
|
if (typeof route === 'number') {
|
|
247
113
|
sendJson(req, res, route, { error: route === 404 ? 'not found' : 'method not allowed' });
|
|
248
114
|
return true;
|
|
@@ -250,7 +116,7 @@ export function handleModelRequest(req: IncomingMessage, res: ServerResponse, de
|
|
|
250
116
|
apiSession(req)
|
|
251
117
|
.then(async (session) => {
|
|
252
118
|
if (!session) throw new ApiError('unauthorized', 401);
|
|
253
|
-
sendJson(req, res, 200, await route
|
|
119
|
+
sendJson(req, res, 200, await route(session));
|
|
254
120
|
})
|
|
255
121
|
.catch((err: unknown) => {
|
|
256
122
|
apiFailure(req, res, err, 'model-api');
|