@route_33/opencode 0.4.7-rc.7 → 0.4.7-rc.8

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/index.js CHANGED
@@ -1,19 +1,219 @@
1
- // src/cli/read-env-base-url/read-env-base-url.ts
2
- var readEnvBaseUrl = (env = process.env) => {
3
- const value = env["ROUTE33_BASE_URL"]?.trim();
4
- return value === "" ? void 0 : value;
1
+ // src/auth/auth-error/auth-error.ts
2
+ var AuthError = class extends Error {
3
+ constructor(code, options) {
4
+ super(
5
+ "Route 33 authorization failed",
6
+ options?.cause === void 0 ? void 0 : { cause: options.cause }
7
+ );
8
+ this.code = code;
9
+ this.name = "AuthError";
10
+ this.statusCode = options?.statusCode;
11
+ }
12
+ code;
13
+ statusCode;
5
14
  };
6
15
 
7
- // src/cli/read-env-api-base/read-env-api-base.ts
8
- var readEnvApiBase = (env = process.env) => {
9
- const value = env["ROUTE33_API_BASE"]?.trim();
10
- return value === "" ? void 0 : value;
16
+ // src/auth/build-authorize-url/build-authorize-url.ts
17
+ var buildAuthorizeUrl = (issuerUrl2, request) => {
18
+ const url = new URL("/authorize", issuerUrl2);
19
+ url.search = new URLSearchParams({
20
+ client_id: request.clientId,
21
+ code_challenge: request.codeChallenge,
22
+ code_challenge_method: "S256",
23
+ redirect_uri: request.redirectUri,
24
+ response_type: "code",
25
+ scope: request.scope,
26
+ state: request.state
27
+ }).toString();
28
+ return url;
11
29
  };
12
30
 
13
- // src/cli/read-env-auth-issuer/read-env-auth-issuer.ts
14
- var readEnvAuthIssuer = (env = process.env) => {
15
- const value = env["ROUTE33_AUTH_ISSUER"]?.trim();
16
- return value === "" ? void 0 : value;
31
+ // src/contract/adapters/contract-parse-error/contract-parse-error.ts
32
+ var ContractParseError = class extends Error {
33
+ };
34
+
35
+ // src/contract/adapters/gateway-api-key/gateway-api-key.ts
36
+ var GatewayApiKey = class {
37
+ value;
38
+ constructor(value) {
39
+ this.value = value;
40
+ }
41
+ };
42
+
43
+ // src/contract/adapters/management-jwt/management-jwt.ts
44
+ var ManagementJwt = class {
45
+ value;
46
+ constructor(value) {
47
+ this.value = value;
48
+ }
49
+ };
50
+
51
+ // src/contract/adapters/parse-gateway-key/parse-gateway-key.ts
52
+ var parseGatewayKey = (value) => {
53
+ if (typeof value !== "string" || value.length === 0) {
54
+ throw new Error("gateway key must be non-empty");
55
+ }
56
+ return new GatewayApiKey(value);
57
+ };
58
+
59
+ // src/contract/adapters/parse-management-jwt/parse-management-jwt.ts
60
+ var parseManagementJwt = (value) => {
61
+ if (typeof value !== "string" || value.length === 0) {
62
+ throw new Error("management token must be non-empty");
63
+ }
64
+ return new ManagementJwt(value);
65
+ };
66
+
67
+ // src/contract/predicates/has-string-fields/has-string-fields.ts
68
+ var hasStringFields = (record, fields) => fields.every((field) => typeof record[field] === "string");
69
+
70
+ // src/contract/predicates/is-nullable-string/is-nullable-string.ts
71
+ var isNullableString = (value) => typeof value === "string" || value === null;
72
+
73
+ // src/contract/predicates/is-owner-type/is-owner-type.ts
74
+ var isOwnerType = (value) => value === "project" || value === "user_personal" || value === "user_project";
75
+
76
+ // src/contract/predicates/is-record/is-record.ts
77
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
78
+
79
+ // src/contract/adapters/parse-management-subject/parse-management-subject.ts
80
+ var parseManagementSubject = (credential) => {
81
+ const encodedPayload = credential.value.split(".")[1];
82
+ if (encodedPayload === void 0 || encodedPayload.length === 0) {
83
+ throw new ContractParseError("management token carries no usable subject");
84
+ }
85
+ let payload;
86
+ try {
87
+ payload = JSON.parse(
88
+ Buffer.from(encodedPayload, "base64url").toString("utf8")
89
+ );
90
+ } catch {
91
+ throw new ContractParseError("management token payload is not JSON");
92
+ }
93
+ if (!isRecord(payload)) {
94
+ throw new ContractParseError("management token payload is not an object");
95
+ }
96
+ const subject = payload["sub"];
97
+ if (typeof subject !== "string" || subject.length === 0) {
98
+ throw new ContractParseError("management token carries no usable subject");
99
+ }
100
+ return subject;
101
+ };
102
+
103
+ // src/contract/auth-session/parse-token-set/parse-token-set.ts
104
+ var parseTokenSet = (value) => {
105
+ if (!isRecord(value)) {
106
+ throw new ContractParseError("invalid token response");
107
+ }
108
+ if (typeof value["access_token"] !== "string" || typeof value["expires_in"] !== "number" || value["token_type"] !== "Bearer") {
109
+ throw new ContractParseError("invalid token response");
110
+ }
111
+ return {
112
+ access_token: value["access_token"],
113
+ expires_in: value["expires_in"],
114
+ token_type: "Bearer",
115
+ ...typeof value["id_token"] === "string" ? { id_token: value["id_token"] } : {},
116
+ ...typeof value["refresh_token"] === "string" ? { refresh_token: value["refresh_token"] } : {},
117
+ ...typeof value["scope"] === "string" ? { scope: value["scope"] } : {}
118
+ };
119
+ };
120
+
121
+ // src/auth/exchange-code-and-parse/exchange-code-and-parse.ts
122
+ var exchangeCodeAndParse = async (options) => {
123
+ const body = new URLSearchParams({
124
+ client_id: options.clientId,
125
+ code: options.input.code,
126
+ code_verifier: options.input.codeVerifier,
127
+ grant_type: "authorization_code",
128
+ redirect_uri: options.input.redirectUri
129
+ });
130
+ let response;
131
+ try {
132
+ response = await (options.fetchImpl ?? fetch)(options.tokenUrl, {
133
+ body,
134
+ headers: { "content-type": "application/x-www-form-urlencoded" },
135
+ method: "POST"
136
+ });
137
+ } catch (cause) {
138
+ throw new AuthError("AUTH_CODE_EXCHANGE_FAILED", { cause });
139
+ }
140
+ if (!response.ok) {
141
+ throw new AuthError("AUTH_CODE_EXCHANGE_FAILED", {
142
+ statusCode: response.status
143
+ });
144
+ }
145
+ let payload;
146
+ try {
147
+ payload = await response.json();
148
+ } catch (cause) {
149
+ throw new AuthError("AUTH_CODE_EXCHANGE_FAILED", { cause });
150
+ }
151
+ try {
152
+ return parseTokenSet(payload);
153
+ } catch (cause) {
154
+ if (cause instanceof ContractParseError) {
155
+ throw new AuthError("AUTH_CODE_EXCHANGE_FAILED", { cause });
156
+ }
157
+ throw cause;
158
+ }
159
+ };
160
+
161
+ // src/auth/refresh-session/refresh-session.ts
162
+ var refreshSession = async (options) => {
163
+ const body = new URLSearchParams({
164
+ client_id: options.clientId,
165
+ grant_type: "refresh_token",
166
+ refresh_token: options.refreshToken
167
+ });
168
+ let response;
169
+ try {
170
+ response = await (options.fetchImpl ?? fetch)(options.tokenUrl, {
171
+ body,
172
+ headers: { "content-type": "application/x-www-form-urlencoded" },
173
+ method: "POST"
174
+ });
175
+ } catch (cause) {
176
+ throw new AuthError("AUTH_REAUTHENTICATION_REQUIRED", { cause });
177
+ }
178
+ if (!response.ok) {
179
+ throw new AuthError("AUTH_REAUTHENTICATION_REQUIRED", {
180
+ statusCode: response.status
181
+ });
182
+ }
183
+ let payload;
184
+ try {
185
+ payload = await response.json();
186
+ } catch (cause) {
187
+ throw new AuthError("AUTH_REAUTHENTICATION_REQUIRED", { cause });
188
+ }
189
+ try {
190
+ return parseTokenSet(payload);
191
+ } catch (cause) {
192
+ if (cause instanceof ContractParseError) {
193
+ throw new AuthError("AUTH_REAUTHENTICATION_REQUIRED", { cause });
194
+ }
195
+ throw cause;
196
+ }
197
+ };
198
+
199
+ // src/auth/create-auth-session/create-auth-session.ts
200
+ var createAuthSession = (options) => {
201
+ const tokenUrl = new URL("/oauth/token", options.issuerUrl);
202
+ return {
203
+ authorizationUrl: (request) => buildAuthorizeUrl(options.issuerUrl, request),
204
+ exchangeCode: (input) => exchangeCodeAndParse({
205
+ clientId: options.clientId,
206
+ ...options.fetchImpl === void 0 ? {} : { fetchImpl: options.fetchImpl },
207
+ input,
208
+ tokenUrl
209
+ }),
210
+ refresh: (refreshToken) => refreshSession({
211
+ clientId: options.clientId,
212
+ ...options.fetchImpl === void 0 ? {} : { fetchImpl: options.fetchImpl },
213
+ refreshToken,
214
+ tokenUrl
215
+ })
216
+ };
17
217
  };
18
218
 
19
219
  // src/shared/default-auth-issuer-url/default-auth-issuer-url.ts
@@ -61,148 +261,382 @@ var resolveCacheDir = (env) => {
61
261
  );
62
262
  };
63
263
 
64
- // src/cli/read-env-cache-dir/read-env-cache-dir.ts
65
- var readEnvCacheDir = (env = process.env) => resolveCacheDir(env);
66
-
67
- // src/cli/read-env-catalog-ttl-ms/read-env-catalog-ttl-ms.ts
68
- var readEnvCatalogTtlMs = (env = process.env) => {
69
- const value = Number(env["ROUTE33_CATALOG_TTL_MS"]);
70
- return Number.isFinite(value) && value > 0 ? value : DEFAULT_CATALOG_TTL_MS;
71
- };
72
-
73
- // src/cli/read-env-client-id/read-env-client-id.ts
74
- var readEnvClientId = (env = process.env) => {
75
- const value = env["ROUTE33_CLIENT_ID"]?.trim();
76
- return value === "" ? void 0 : value;
77
- };
78
-
79
- // src/cli/read-env-direct-api-key/read-env-direct-api-key.ts
80
- var readEnvDirectApiKey = (env = process.env) => {
81
- const value = env["ROUTE33_API_KEY"]?.trim();
82
- return value === "" ? void 0 : value;
83
- };
84
-
85
- // src/cli/read-env-disable-id-shim/read-env-disable-id-shim.ts
86
- var readEnvDisableIdShim = (env = process.env) => env["ROUTE33_DISABLE_ID_SHIM"] === "1";
87
-
88
- // src/cli/read-env-discovery/read-env-discovery.ts
89
- var readEnvDiscovery = (env = process.env) => env["ROUTE33_DISCOVERY"] !== "0";
264
+ // src/auth/open-browser-and-capture/open-browser-and-capture.ts
265
+ import { spawn } from "node:child_process";
266
+ import { timingSafeEqual } from "node:crypto";
90
267
 
91
- // src/cli/read-env-strict/read-env-strict.ts
92
- var readEnvStrict = (env = process.env) => env["ROUTE33_STRICT"] !== "0";
268
+ // src/auth/loopback-callback/loopback-callback.ts
269
+ import { createServer } from "node:http";
93
270
 
94
- // src/cli/resolve-v1-secret-store-directory/resolve-v1-secret-store-directory.ts
95
- import * as os2 from "node:os";
96
- import * as path2 from "node:path";
97
- var resolveV1SecretStoreDirectory = (options = {}) => {
98
- const explicit = options.directory?.trim();
99
- if (explicit !== void 0 && explicit.length > 0) {
100
- return path2.resolve(explicit);
101
- }
102
- const hostConfig = (options.env ?? process.env)["OPENCODE_CONFIG_DIR"]?.trim();
103
- if (hostConfig !== void 0 && hostConfig.length > 0) {
104
- return path2.join(path2.resolve(hostConfig), "route33");
105
- }
106
- return path2.join(
107
- options.homeDirectory ?? os2.homedir(),
108
- ".config",
109
- "opencode",
110
- "route33"
111
- );
271
+ // src/auth/loopback-callback/callback-page/callback-page.ts
272
+ var MARK_PATHS = [
273
+ "M310.6,175.8c0-6.1-2.7-11.8-7.2-15.9l-127.6-114.4V7h-43.5v48.3c0,6.2,2.6,12.1,7.2,16.2l92.5,82.9H0v43.5h234.7l-95.6,91.4c-4.3,4.1-6.7,9.8-6.7,15.7v48.3h43.5v-39l128.1-122.4c4.4-4.2,6.8-10,6.7-16.1h0Z",
274
+ "M457.2,175.9v-2.6c18.4-4.9,33-14.1,44-27.7,11-13.6,16.5-30.3,16.5-50.3s-4.5-32.9-13.4-47.5c-9-14.5-21.6-26.1-38-34.9-16.4-8.7-35.8-13.1-58.3-13.1s-41.1,4-58.8,12.1-31.8,19.3-42.4,33.7-16.1,31-16.6,50h51.1c.3-10.8,3.6-20,9.8-27.7,6.2-7.6,14.2-13.5,24.1-17.5,9.9-4.1,20.6-6.1,32-6.1s22.3,2.2,31.1,6.7c8.8,4.5,15.6,10.8,20.5,18.8,4.9,8.1,7.3,17.5,7.3,28.4s-2.8,21.1-8.4,29.4-13.3,14.8-23.2,19.5-21.3,7.1-34.2,7.1h-29.9v43.3h29.9c16.2,0,30,2.5,41.5,7.5,11.5,5,20.2,12,26.1,21.1,6,9,9,19.4,9,31.2s-3,21.2-9.1,29.8-14.4,15.3-25.1,20.1c-10.7,4.9-22.9,7.3-36.7,7.3s-24.2-2.1-34.5-6.2-18.6-10-24.8-17.5c-6.3-7.5-9.8-16.5-10.6-26.8h-53.2c.6,19,6.3,35.7,17,50.2,10.7,14.5,25.2,25.7,43.6,33.8,18.4,8.1,39.4,12.1,63.1,12.1s45.1-4.4,63.7-13.2c18.6-8.8,33.3-20.8,44-36,10.8-15.2,16.1-32.5,16.1-51.8s-6.4-40.7-19.2-55.6c-12.8-14.9-30.8-24.2-54-27.8h0Z",
275
+ "M763,203.7c-12.8-14.9-30.8-24.2-54-27.8v-2.6c18.4-4.9,33-14.1,44-27.7,11-13.6,16.5-30.3,16.5-50.3s-4.5-32.9-13.4-47.5c-8.9-14.5-21.6-26.1-38-34.9-16.4-8.7-35.8-13.1-58.3-13.1s-41.1,4-58.8,12.1-31.8,19.3-42.4,33.7-16.1,31-16.6,50h51.1c.3-10.8,3.6-20,9.8-27.7,6.2-7.6,14.2-13.5,24.1-17.5,9.9-4.1,20.6-6.1,32-6.1s22.3,2.2,31.1,6.7c8.8,4.5,15.6,10.8,20.5,18.8,4.9,8.1,7.3,17.5,7.3,28.4s-2.8,21.1-8.4,29.4c-5.6,8.3-13.3,14.8-23.2,19.5s-21.3,7.1-34.2,7.1h-29.9v43.3h29.9c16.2,0,30,2.5,41.5,7.5,11.5,5,20.2,12,26.1,21.1,6,9,8.9,19.4,8.9,31.2s-3,21.2-9.1,29.8c-6.1,8.6-14.4,15.3-25.1,20.1-10.7,4.9-22.9,7.3-36.7,7.3s-24.2-2.1-34.5-6.2-18.6-10-24.8-17.5c-6.3-7.5-9.8-16.5-10.6-26.8h-53.2c.6,19,6.3,35.7,17,50.2,10.7,14.5,25.2,25.7,43.6,33.8,18.4,8.1,39.4,12.1,63.1,12.1s45.1-4.4,63.7-13.2,33.3-20.8,44-36c10.8-15.2,16.1-32.5,16.1-51.8s-6.4-40.7-19.2-55.6h0Z"
276
+ ];
277
+ var BRAND_MARK = `<svg class="r33-mark" viewBox="0 0 782.2 360.3" fill="currentColor" role="img" aria-label="route33.ai" xmlns="http://www.w3.org/2000/svg"><path d="${MARK_PATHS[0]}"/><path d="${MARK_PATHS[1]}"/><path d="${MARK_PATHS[2]}"/></svg>`;
278
+ var PAGE_STYLE = `:root{color-scheme:dark;--r33-euro-blue:#0000FF;--r33-off-white:#F1F1F1;--r33-off-black:#121212;--r33-grey-300:#C9CBCF;--r33-radius-fillet:4px;--r33-space-2:8px;--r33-space-3:16px;--r33-space-4:24px;--r33-space-6:64px;--r33-font-body:"Geist","Helvetica Neue",Arial,sans-serif;--r33-font-mono:"Geist Mono","SFMono-Regular",Menlo,monospace;--r33-surface:var(--r33-off-black);--r33-text-primary:var(--r33-off-white);--r33-text-secondary:var(--r33-grey-300);--r33-fill-accent:var(--r33-euro-blue);--r33-on-fill-accent:var(--r33-off-white)}
279
+ *{box-sizing:border-box}
280
+ html,body{margin:0}
281
+ .r33-page{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:var(--r33-space-6);background:var(--r33-surface);color:var(--r33-text-primary);font-family:var(--r33-font-body);-webkit-font-smoothing:antialiased}
282
+ .r33-panel{display:grid;gap:var(--r33-space-4);max-width:460px;width:100%}
283
+ .r33-mark{height:40px;width:auto;color:var(--r33-text-primary)}
284
+ .r33-status{display:inline-flex;align-items:center;gap:var(--r33-space-2);font-size:11px;letter-spacing:0.08em;text-transform:uppercase;color:var(--r33-text-secondary)}
285
+ .r33-status__dot{width:8px;height:8px;background:var(--r33-fill-accent)}
286
+ .r33-status__dot--error{background:transparent;border:1px solid var(--r33-text-primary);border-radius:50%}
287
+ .r33-headline{margin:0;font-size:48px;line-height:0.85;letter-spacing:-0.02em;font-weight:600;color:var(--r33-text-primary);text-wrap:balance}
288
+ .r33-subline{margin:0;font-size:16px;line-height:1.45;color:var(--r33-text-secondary);max-width:40ch;text-wrap:pretty}
289
+ .r33-code{margin:0;font-size:13px;letter-spacing:0.04em;color:var(--r33-text-secondary)}
290
+ .r33-code__value{font-family:var(--r33-font-mono);text-transform:uppercase;color:var(--r33-text-primary)}
291
+ @media (prefers-reduced-motion:reduce){*{animation-duration:1ms!important;transition-duration:1ms!important}}`;
292
+ var buildCallbackPage = (state, errorCode) => {
293
+ const isError = state === "error";
294
+ const safeCode = (errorCode ?? "").replace(/[^a-z0-9_-]/gi, "");
295
+ const title = isError ? "Sign-in failed" : "Signed in";
296
+ const headline = isError ? "Sign-in failed" : "Signed in to route33";
297
+ const subline = isError ? "Return to OpenCode and retry." : "Return to OpenCode \u2014 one more step may wait there: if it asks which budget covers usage, pick a project. Then this window can be closed.";
298
+ const statusLabel = isError ? "Authentication error" : "Authenticated";
299
+ const dotClass = isError ? "r33-status__dot r33-status__dot--error" : "r33-status__dot";
300
+ const codeLine = isError && safeCode.length > 0 ? `<p class="r33-code">Error code: <span class="r33-code__value">${safeCode}</span></p>` : "";
301
+ return `<!doctype html>
302
+ <html lang="en" data-theme="dark">
303
+ <head>
304
+ <meta charset="utf-8">
305
+ <meta name="viewport" content="width=device-width, initial-scale=1">
306
+ <title>${title} \xB7 route33.ai</title>
307
+ <style>${PAGE_STYLE}</style>
308
+ </head>
309
+ <body>
310
+ <main class="r33-page">
311
+ <div class="r33-panel">
312
+ ${BRAND_MARK}
313
+ <span class="r33-status"><span class="${dotClass}"></span>${statusLabel}</span>
314
+ <h1 class="r33-headline">${headline}</h1>
315
+ <p class="r33-subline">${subline}</p>
316
+ ${codeLine}
317
+ </div>
318
+ </main>
319
+ </body>
320
+ </html>`;
112
321
  };
113
322
 
114
- // src/config/abortable-sleep/abortable-sleep.ts
115
- var abortableSleep = (milliseconds, signal) => new Promise((resolve2, reject) => {
116
- if (signal === void 0) {
117
- setTimeout(resolve2, milliseconds);
118
- return;
119
- }
120
- const abortError = () => {
121
- const reason = signal.reason;
122
- if (reason instanceof Error) {
123
- return reason;
323
+ // src/auth/loopback-callback/loopback-callback.ts
324
+ var createAuthLoopbackCallback = async () => {
325
+ let redirectUri = "http://127.0.0.1/callback";
326
+ let resolveCapture;
327
+ const captured = new Promise((resolve2) => {
328
+ resolveCapture = resolve2;
329
+ });
330
+ const server = createServer((request, response) => {
331
+ const url = new URL(request.url ?? "/", redirectUri);
332
+ if (request.method !== "GET" || url.pathname !== "/callback") {
333
+ response.writeHead(404).end();
334
+ return;
124
335
  }
125
- return new Error(String(reason), { cause: reason });
126
- };
127
- if (signal.aborted) {
128
- reject(abortError());
129
- return;
336
+ const resolve2 = resolveCapture;
337
+ resolveCapture = void 0;
338
+ resolve2?.(url);
339
+ const error = url.searchParams.get("error");
340
+ const page = error === null || error.length === 0 ? buildCallbackPage("success") : buildCallbackPage("error", error);
341
+ response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
342
+ response.end(page);
343
+ });
344
+ await new Promise((resolve2, reject) => {
345
+ const onError = (cause) => {
346
+ reject(new AuthError("AUTH_CALLBACK_LISTEN_FAILED", { cause }));
347
+ };
348
+ server.once("error", onError);
349
+ server.listen(0, "127.0.0.1", () => {
350
+ server.off("error", onError);
351
+ resolve2();
352
+ });
353
+ });
354
+ const address = server.address();
355
+ if (address === null || typeof address === "string") {
356
+ server.close();
357
+ throw new AuthError("AUTH_CALLBACK_LISTEN_FAILED");
130
358
  }
131
- const onAbort = () => {
132
- clearTimeout(timer);
133
- reject(abortError());
359
+ redirectUri = `http://127.0.0.1:${String(address.port)}/callback`;
360
+ return {
361
+ redirectUri,
362
+ close: () => {
363
+ if (!server.listening) {
364
+ return Promise.resolve();
365
+ }
366
+ return new Promise((resolve2, reject) => {
367
+ server.close((cause) => {
368
+ if (cause === void 0) {
369
+ resolve2();
370
+ return;
371
+ }
372
+ reject(new AuthError("AUTH_CALLBACK_CLEANUP_FAILED", { cause }));
373
+ });
374
+ });
375
+ },
376
+ wait: () => captured
134
377
  };
135
- const timer = setTimeout(() => {
136
- signal.removeEventListener("abort", onAbort);
137
- resolve2();
138
- }, milliseconds);
139
- signal.addEventListener("abort", onAbort, { once: true });
140
- });
141
-
142
- // src/config/is-resendable-body/is-resendable-body.ts
143
- var isResendableBody = (body) => body === void 0 || typeof body === "string" || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof Blob || body instanceof URLSearchParams || body instanceof FormData;
144
-
145
- // src/contract/predicates/has-string-fields/has-string-fields.ts
146
- var hasStringFields = (record, fields) => fields.every((field) => typeof record[field] === "string");
147
-
148
- // src/contract/predicates/is-nullable-string/is-nullable-string.ts
149
- var isNullableString = (value) => typeof value === "string" || value === null;
150
-
151
- // src/contract/predicates/is-owner-type/is-owner-type.ts
152
- var isOwnerType = (value) => value === "project" || value === "user_personal" || value === "user_project";
153
-
154
- // src/contract/predicates/is-record/is-record.ts
155
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
156
-
157
- // src/config/parse-exhaustion-envelope/parse-exhaustion-envelope.ts
158
- var parseExhaustionEnvelope = async (response) => {
159
- let body;
160
- try {
161
- body = await response.clone().json();
162
- } catch (error) {
163
- if (error instanceof Error) {
164
- return void 0;
165
- }
166
- throw error;
167
- }
168
- if (!isRecord(body)) {
169
- return void 0;
170
- }
171
- const nestedError = body["error"];
172
- const nestedCode = isRecord(nestedError) ? nestedError["code"] : void 0;
173
- if (body["code"] !== "PROVIDER_LANES_EXHAUSTED" && nestedCode !== "PROVIDER_LANES_EXHAUSTED") {
174
- return void 0;
175
- }
176
- const nestedId = isRecord(nestedError) ? nestedError["correlationId"] : void 0;
177
- const directId = body["correlationId"];
178
- let correlationId;
179
- if (typeof directId === "string") {
180
- correlationId = directId;
181
- } else if (typeof nestedId === "string") {
182
- correlationId = nestedId;
183
- }
184
- return { correlationId };
185
378
  };
186
379
 
187
- // src/config/request-model/request-model.ts
188
- var requestModel = (body) => {
189
- if (typeof body !== "string") {
190
- return void 0;
191
- }
192
- let parsed;
380
+ // src/auth/open-browser-and-capture/open-browser-and-capture.ts
381
+ var openBrowserAndCapture = async (options) => {
382
+ const callback = await createAuthLoopbackCallback();
383
+ const authorizationUrl = options.authorizationUrl(callback.redirectUri);
384
+ const browserRunner = options.browserRunner ?? ((url) => new Promise((resolve2, reject) => {
385
+ let command = "xdg-open";
386
+ let args = [url.href];
387
+ if (process.platform === "darwin") {
388
+ command = "open";
389
+ } else if (process.platform === "win32") {
390
+ command = "cmd";
391
+ args = ["/c", "start", "", url.href];
392
+ }
393
+ const child = spawn(command, args, {
394
+ detached: true,
395
+ stdio: "ignore"
396
+ });
397
+ child.once("error", reject);
398
+ child.once("spawn", () => {
399
+ child.unref();
400
+ resolve2();
401
+ });
402
+ }));
403
+ let timer;
404
+ const timeout = new Promise((_resolve, reject) => {
405
+ timer = setTimeout(() => {
406
+ reject(new AuthError("AUTH_CALLBACK_TIMEOUT"));
407
+ }, options.timeoutMs ?? 12e4);
408
+ });
409
+ const launch = browserRunner(authorizationUrl).catch((cause) => {
410
+ throw new AuthError("AUTH_BROWSER_LAUNCH_FAILED", { cause });
411
+ });
193
412
  try {
194
- parsed = JSON.parse(body);
195
- } catch (error) {
196
- if (error instanceof SyntaxError) {
197
- return void 0;
413
+ const completed = await Promise.race([
414
+ Promise.all([launch, callback.wait()]),
415
+ timeout
416
+ ]);
417
+ const callbackUrl = completed[1];
418
+ const actualState = callbackUrl.searchParams.get("state");
419
+ if (actualState === null) {
420
+ throw new AuthError("AUTH_CALLBACK_RESPONSE_INVALID");
198
421
  }
199
- throw error;
422
+ const expectedState = Buffer.from(options.expectedState);
423
+ const receivedState = Buffer.from(actualState);
424
+ if (expectedState.length !== receivedState.length || !timingSafeEqual(expectedState, receivedState)) {
425
+ throw new AuthError("AUTH_CALLBACK_STATE_MISMATCH");
426
+ }
427
+ const code = callbackUrl.searchParams.get("code");
428
+ if (code === null || code.length === 0) {
429
+ throw new AuthError("AUTH_CALLBACK_RESPONSE_INVALID");
430
+ }
431
+ return { code, redirectUri: callback.redirectUri };
432
+ } finally {
433
+ if (timer !== void 0) {
434
+ clearTimeout(timer);
435
+ }
436
+ await callback.close();
200
437
  }
201
- return isRecord(parsed) && typeof parsed["model"] === "string" ? parsed["model"] : void 0;
202
438
  };
203
439
 
204
- // src/config/retry-wait-seconds/retry-wait-seconds.ts
205
- var retryWaitSeconds = (options) => {
440
+ // src/auth/pkce-verifier-and-challenge/pkce-verifier-and-challenge.ts
441
+ import { randomBytes } from "node:crypto";
442
+
443
+ // src/auth/pkce-challenge/pkce-challenge.ts
444
+ import { createHash } from "node:crypto";
445
+ var pkceChallenge = (verifier) => createHash("sha256").update(verifier).digest("base64url");
446
+
447
+ // src/auth/pkce-verifier-and-challenge/pkce-verifier-and-challenge.ts
448
+ var createPkceVerifierAndChallenge = () => {
449
+ const codeVerifier = randomBytes(32).toString("base64url");
450
+ return {
451
+ codeChallenge: pkceChallenge(codeVerifier),
452
+ codeVerifier,
453
+ state: randomBytes(32).toString("base64url")
454
+ };
455
+ };
456
+
457
+ // src/auth/requested-scopes/requested-scopes.ts
458
+ var REQUESTED_OAUTH_SCOPES = "openid offline_access projects:read keys:read keys:write";
459
+
460
+ // src/auth/login-flow/login-flow.ts
461
+ var loginFlow = async (options) => {
462
+ const log = options.logger ?? logger;
463
+ log.info("auth.login.started");
464
+ const pkce = createPkceVerifierAndChallenge();
465
+ const captured = await openBrowserAndCapture({
466
+ authorizationUrl: (redirectUri) => options.session.authorizationUrl({
467
+ clientId: options.clientId,
468
+ codeChallenge: pkce.codeChallenge,
469
+ redirectUri,
470
+ scope: REQUESTED_OAUTH_SCOPES,
471
+ state: pkce.state
472
+ }),
473
+ ...options.browserRunner === void 0 ? {} : { browserRunner: options.browserRunner },
474
+ expectedState: pkce.state,
475
+ ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
476
+ });
477
+ const tokenSet = await options.session.exchangeCode({
478
+ code: captured.code,
479
+ codeVerifier: pkce.codeVerifier,
480
+ redirectUri: captured.redirectUri
481
+ });
482
+ let credential;
483
+ try {
484
+ credential = parseManagementJwt(tokenSet.access_token);
485
+ } catch (cause) {
486
+ throw new AuthError("AUTH_TOKEN_RESPONSE_INVALID", { cause });
487
+ }
488
+ log.info("auth.login.completed");
489
+ return { credential, tokenSet };
490
+ };
491
+
492
+ // src/cli/read-env-base-url/read-env-base-url.ts
493
+ var readEnvBaseUrl = (env = process.env) => {
494
+ const value = env["ROUTE33_BASE_URL"]?.trim();
495
+ return value === "" ? void 0 : value;
496
+ };
497
+
498
+ // src/cli/read-env-api-base/read-env-api-base.ts
499
+ var readEnvApiBase = (env = process.env) => {
500
+ const value = env["ROUTE33_API_BASE"]?.trim();
501
+ return value === "" ? void 0 : value;
502
+ };
503
+
504
+ // src/cli/read-env-auth-issuer/read-env-auth-issuer.ts
505
+ var readEnvAuthIssuer = (env = process.env) => {
506
+ const value = env["ROUTE33_AUTH_ISSUER"]?.trim();
507
+ return value === "" ? void 0 : value;
508
+ };
509
+
510
+ // src/cli/read-env-cache-dir/read-env-cache-dir.ts
511
+ var readEnvCacheDir = (env = process.env) => resolveCacheDir(env);
512
+
513
+ // src/cli/read-env-catalog-ttl-ms/read-env-catalog-ttl-ms.ts
514
+ var readEnvCatalogTtlMs = (env = process.env) => {
515
+ const value = Number(env["ROUTE33_CATALOG_TTL_MS"]);
516
+ return Number.isFinite(value) && value > 0 ? value : DEFAULT_CATALOG_TTL_MS;
517
+ };
518
+
519
+ // src/cli/read-env-client-id/read-env-client-id.ts
520
+ var readEnvClientId = (env = process.env) => {
521
+ const value = env["ROUTE33_CLIENT_ID"]?.trim();
522
+ return value === "" ? void 0 : value;
523
+ };
524
+
525
+ // src/cli/read-env-direct-api-key/read-env-direct-api-key.ts
526
+ var readEnvDirectApiKey = (env = process.env) => {
527
+ const value = env["ROUTE33_API_KEY"]?.trim();
528
+ return value === "" ? void 0 : value;
529
+ };
530
+
531
+ // src/cli/read-env-disable-id-shim/read-env-disable-id-shim.ts
532
+ var readEnvDisableIdShim = (env = process.env) => env["ROUTE33_DISABLE_ID_SHIM"] === "1";
533
+
534
+ // src/cli/read-env-discovery/read-env-discovery.ts
535
+ var readEnvDiscovery = (env = process.env) => env["ROUTE33_DISCOVERY"] !== "0";
536
+
537
+ // src/cli/read-env-strict/read-env-strict.ts
538
+ var readEnvStrict = (env = process.env) => env["ROUTE33_STRICT"] !== "0";
539
+
540
+ // src/cli/resolve-v1-secret-store-directory/resolve-v1-secret-store-directory.ts
541
+ import * as os2 from "node:os";
542
+ import * as path2 from "node:path";
543
+ var resolveV1SecretStoreDirectory = (options = {}) => {
544
+ const explicit = options.directory?.trim();
545
+ if (explicit !== void 0 && explicit.length > 0) {
546
+ return path2.resolve(explicit);
547
+ }
548
+ const hostConfig = (options.env ?? process.env)["OPENCODE_CONFIG_DIR"]?.trim();
549
+ if (hostConfig !== void 0 && hostConfig.length > 0) {
550
+ return path2.join(path2.resolve(hostConfig), "route33");
551
+ }
552
+ return path2.join(
553
+ options.homeDirectory ?? os2.homedir(),
554
+ ".config",
555
+ "opencode",
556
+ "route33"
557
+ );
558
+ };
559
+
560
+ // src/config/abortable-sleep/abortable-sleep.ts
561
+ var abortableSleep = (milliseconds, signal) => new Promise((resolve2, reject) => {
562
+ if (signal === void 0) {
563
+ setTimeout(resolve2, milliseconds);
564
+ return;
565
+ }
566
+ const abortError = () => {
567
+ const reason = signal.reason;
568
+ if (reason instanceof Error) {
569
+ return reason;
570
+ }
571
+ return new Error(String(reason), { cause: reason });
572
+ };
573
+ if (signal.aborted) {
574
+ reject(abortError());
575
+ return;
576
+ }
577
+ const onAbort = () => {
578
+ clearTimeout(timer);
579
+ reject(abortError());
580
+ };
581
+ const timer = setTimeout(() => {
582
+ signal.removeEventListener("abort", onAbort);
583
+ resolve2();
584
+ }, milliseconds);
585
+ signal.addEventListener("abort", onAbort, { once: true });
586
+ });
587
+
588
+ // src/config/is-resendable-body/is-resendable-body.ts
589
+ var isResendableBody = (body) => body === void 0 || typeof body === "string" || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof Blob || body instanceof URLSearchParams || body instanceof FormData;
590
+
591
+ // src/config/parse-exhaustion-envelope/parse-exhaustion-envelope.ts
592
+ var parseExhaustionEnvelope = async (response) => {
593
+ let body;
594
+ try {
595
+ body = await response.clone().json();
596
+ } catch (error) {
597
+ if (error instanceof Error) {
598
+ return void 0;
599
+ }
600
+ throw error;
601
+ }
602
+ if (!isRecord(body)) {
603
+ return void 0;
604
+ }
605
+ const nestedError = body["error"];
606
+ const nestedCode = isRecord(nestedError) ? nestedError["code"] : void 0;
607
+ if (body["code"] !== "PROVIDER_LANES_EXHAUSTED" && nestedCode !== "PROVIDER_LANES_EXHAUSTED") {
608
+ return void 0;
609
+ }
610
+ const nestedId = isRecord(nestedError) ? nestedError["correlationId"] : void 0;
611
+ const directId = body["correlationId"];
612
+ let correlationId;
613
+ if (typeof directId === "string") {
614
+ correlationId = directId;
615
+ } else if (typeof nestedId === "string") {
616
+ correlationId = nestedId;
617
+ }
618
+ return { correlationId };
619
+ };
620
+
621
+ // src/config/request-model/request-model.ts
622
+ var requestModel = (body) => {
623
+ if (typeof body !== "string") {
624
+ return void 0;
625
+ }
626
+ let parsed;
627
+ try {
628
+ parsed = JSON.parse(body);
629
+ } catch (error) {
630
+ if (error instanceof SyntaxError) {
631
+ return void 0;
632
+ }
633
+ throw error;
634
+ }
635
+ return isRecord(parsed) && typeof parsed["model"] === "string" ? parsed["model"] : void 0;
636
+ };
637
+
638
+ // src/config/retry-wait-seconds/retry-wait-seconds.ts
639
+ var retryWaitSeconds = (options) => {
206
640
  const advised = options.retryAfter !== null && /^\d+$/.test(options.retryAfter) ? Number(options.retryAfter) : void 0;
207
641
  return advised !== void 0 && advised > 0 ? Math.min(advised, options.maxWaitSeconds) : Math.min(
208
642
  options.baseSeconds * 2 ** (options.attempt - 1),
@@ -443,8 +877,8 @@ var catalogMemory = /* @__PURE__ */ new Map();
443
877
  var CATALOG_CACHE_VERSION = 2;
444
878
 
445
879
  // src/catalog/catalog-cache/hash-key/hash-key.ts
446
- import { createHash } from "node:crypto";
447
- var hashKey = (baseURL, gatewayKey) => createHash("sha256").update(`${baseURL}
880
+ import { createHash as createHash2 } from "node:crypto";
881
+ var hashKey = (baseURL, gatewayKey) => createHash2("sha256").update(`${baseURL}
448
882
  ${gatewayKey.value}`).digest("hex").slice(0, 12);
449
883
 
450
884
  // src/catalog/catalog-cache/is-catalog-model-record/is-catalog-model-record.ts
@@ -548,103 +982,43 @@ var catalogModelsUrl = (baseURL) => {
548
982
  // src/catalog/default-fetch-timeout-ms/default-fetch-timeout-ms.ts
549
983
  var DEFAULT_FETCH_TIMEOUT_MS = 5e3;
550
984
 
551
- // src/contract/adapters/contract-parse-error/contract-parse-error.ts
552
- var ContractParseError = class extends Error {
553
- };
554
-
555
- // src/contract/adapters/gateway-api-key/gateway-api-key.ts
556
- var GatewayApiKey = class {
557
- value;
558
- constructor(value) {
559
- this.value = value;
985
+ // src/contract/gateway-catalog/parse-catalog-result/parse-catalog-result.ts
986
+ var parseCatalogResult = (value) => {
987
+ if (!isRecord(value) || !Array.isArray(value["data"])) {
988
+ throw new ContractParseError("invalid catalog response");
560
989
  }
990
+ return value;
561
991
  };
562
992
 
563
- // src/contract/adapters/management-jwt/management-jwt.ts
564
- var ManagementJwt = class {
565
- value;
566
- constructor(value) {
567
- this.value = value;
993
+ // src/catalog/parse-catalog-entries/parse-catalog-entries.ts
994
+ var parseCatalogEntries = (payload) => {
995
+ if (Array.isArray(payload)) {
996
+ return payload;
568
997
  }
569
- };
570
-
571
- // src/contract/adapters/parse-gateway-key/parse-gateway-key.ts
572
- var parseGatewayKey = (value) => {
573
- if (typeof value !== "string" || value.length === 0) {
574
- throw new Error("gateway key must be non-empty");
998
+ try {
999
+ return parseCatalogResult(payload).data;
1000
+ } catch (error) {
1001
+ if (error instanceof ContractParseError) {
1002
+ throw new CatalogFetchError("parse", error.message);
1003
+ }
1004
+ throw error;
575
1005
  }
576
- return new GatewayApiKey(value);
577
1006
  };
578
1007
 
579
- // src/contract/adapters/parse-management-jwt/parse-management-jwt.ts
580
- var parseManagementJwt = (value) => {
581
- if (typeof value !== "string" || value.length === 0) {
582
- throw new Error("management token must be non-empty");
1008
+ // src/catalog/parse-model-capabilities/parse-model-capabilities.ts
1009
+ var parseModelCapabilities = (raw) => {
1010
+ if (!isRecord(raw)) {
1011
+ return void 0;
583
1012
  }
584
- return new ManagementJwt(value);
585
- };
586
-
587
- // src/contract/adapters/parse-management-subject/parse-management-subject.ts
588
- var parseManagementSubject = (credential) => {
589
- const encodedPayload = credential.value.split(".")[1];
590
- if (encodedPayload === void 0 || encodedPayload.length === 0) {
591
- throw new ContractParseError("management token carries no usable subject");
592
- }
593
- let payload;
594
- try {
595
- payload = JSON.parse(
596
- Buffer.from(encodedPayload, "base64url").toString("utf8")
597
- );
598
- } catch {
599
- throw new ContractParseError("management token payload is not JSON");
600
- }
601
- if (!isRecord(payload)) {
602
- throw new ContractParseError("management token payload is not an object");
603
- }
604
- const subject = payload["sub"];
605
- if (typeof subject !== "string" || subject.length === 0) {
606
- throw new ContractParseError("management token carries no usable subject");
607
- }
608
- return subject;
609
- };
610
-
611
- // src/contract/gateway-catalog/parse-catalog-result/parse-catalog-result.ts
612
- var parseCatalogResult = (value) => {
613
- if (!isRecord(value) || !Array.isArray(value["data"])) {
614
- throw new ContractParseError("invalid catalog response");
615
- }
616
- return value;
617
- };
618
-
619
- // src/catalog/parse-catalog-entries/parse-catalog-entries.ts
620
- var parseCatalogEntries = (payload) => {
621
- if (Array.isArray(payload)) {
622
- return payload;
623
- }
624
- try {
625
- return parseCatalogResult(payload).data;
626
- } catch (error) {
627
- if (error instanceof ContractParseError) {
628
- throw new CatalogFetchError("parse", error.message);
629
- }
630
- throw error;
631
- }
632
- };
633
-
634
- // src/catalog/parse-model-capabilities/parse-model-capabilities.ts
635
- var parseModelCapabilities = (raw) => {
636
- if (!isRecord(raw)) {
637
- return void 0;
638
- }
639
- const reasoning = raw["reasoning"] === true;
640
- const toolCall = raw["tool_support"] === true;
641
- if (!reasoning && !toolCall) {
642
- return void 0;
643
- }
644
- return {
645
- ...reasoning ? { reasoning: true } : {},
646
- ...toolCall ? { tool_call: true } : {}
647
- };
1013
+ const reasoning = raw["reasoning"] === true;
1014
+ const toolCall = raw["tool_support"] === true;
1015
+ if (!reasoning && !toolCall) {
1016
+ return void 0;
1017
+ }
1018
+ return {
1019
+ ...reasoning ? { reasoning: true } : {},
1020
+ ...toolCall ? { tool_call: true } : {}
1021
+ };
648
1022
  };
649
1023
 
650
1024
  // src/catalog/parse-model-limit/parse-model-limit.ts
@@ -2417,544 +2791,170 @@ var LeaseState = class _LeaseState {
2417
2791
  /** Atomically swaps metadata when the expected snapshot is still current.
2418
2792
  * @param expected - Snapshot observed before asynchronous renewal.
2419
2793
  * @param next - Fresh metadata to commit.
2420
- * @returns Whether the swap committed.
2421
- */
2422
- compareAndSwap(expected, next) {
2423
- if (this.#current !== expected) {
2424
- return false;
2425
- }
2426
- this.#current = next;
2427
- return true;
2428
- }
2429
- /** Clears metadata only while the expected snapshot remains current.
2430
- * @param expected - Snapshot whose lease is being invalidated.
2431
- * @returns Whether the state was cleared.
2432
- */
2433
- clear(expected) {
2434
- if (this.#current !== expected) {
2435
- return false;
2436
- }
2437
- this.#current = void 0;
2438
- return true;
2439
- }
2440
- static #unexpectedAttribution(value) {
2441
- throw new TypeError(`Unexpected attribution: ${String(value)}`);
2442
- }
2443
- };
2444
-
2445
- // src/key-lease/lease-installation-factory/lease-installation-factory.ts
2446
- var createLeaseInstallationFactory = (options) => {
2447
- const installations = /* @__PURE__ */ new Map();
2448
- return (scopeId) => {
2449
- const existing = installations.get(scopeId);
2450
- if (existing !== void 0) {
2451
- return existing;
2452
- }
2453
- const installation = {
2454
- keyName: `route33:lease:${scopeId}`,
2455
- secretStore: options.createSecretStore(scopeId),
2456
- ...options.createStateStore === void 0 ? {} : { stateStore: options.createStateStore(scopeId) },
2457
- state: LeaseState.restore(void 0)
2458
- };
2459
- installations.set(scopeId, installation);
2460
- return installation;
2461
- };
2462
- };
2463
-
2464
- // src/key-lease/scope-id/scope-id.ts
2465
- import { createHash as createHash2 } from "node:crypto";
2466
- var deriveScopeId = (input) => {
2467
- let identity;
2468
- switch (input.attribution.kind) {
2469
- case "personal":
2470
- identity = ["route33-lease", "personal"];
2471
- break;
2472
- case "project":
2473
- if (input.folderRealpath === void 0 || input.folderRealpath.length === 0) {
2474
- throw new TypeError("Project lease scope requires a real folder path");
2475
- }
2476
- identity = [
2477
- "route33-lease",
2478
- "project",
2479
- input.attribution.projectId,
2480
- input.folderRealpath
2481
- ];
2482
- break;
2483
- default: {
2484
- const unexpected = input.attribution;
2485
- throw new TypeError(`Unexpected attribution: ${String(unexpected)}`);
2486
- }
2487
- }
2488
- return createHash2("sha256").update(JSON.stringify(identity)).digest("hex");
2489
- };
2490
-
2491
- // src/requested-client-id/requested-client-id.ts
2492
- var DEFAULT_ROUTE33_CLIENT_ID = false ? "" : "oa_platform_203623de";
2493
-
2494
- // src/commands/change-attribution/change-attribution.ts
2495
- var changeAttribution = async (options) => {
2496
- await options.clear();
2497
- return options.prompt();
2498
- };
2499
-
2500
- // src/commands/inspect-session/inspect-session.ts
2501
- var inspectSession = (options) => {
2502
- const attribution = options.readAttribution();
2503
- const lease = options.readLease();
2504
- return {
2505
- attribution,
2506
- ...lease === void 0 ? {} : { lease: { expiresAt: lease.expiresAt, keyId: lease.keyId } }
2507
- };
2508
- };
2509
-
2510
- // src/commands/reset-attribution/reset-attribution.ts
2511
- var resetAttribution = async (options) => {
2512
- await options.clear();
2513
- };
2514
-
2515
- // src/commands/rotate-now/rotate-now.ts
2516
- var rotateNow = async (options) => {
2517
- const rotated = await options.lease({ renewalRatio: Number.EPSILON });
2518
- return rotated.lease;
2519
- };
2520
-
2521
- // src/ux/attribution-prompt/attribution-prompt.ts
2522
- var createAttributionPrompt = (projects) => {
2523
- const choicesBySelection = {
2524
- personal: { kind: "personal" }
2525
- };
2526
- const projectOptions = projects.map((project) => {
2527
- const selection = `project:${project.id}`;
2528
- choicesBySelection[selection] = {
2529
- kind: "project",
2530
- projectId: project.id
2531
- };
2532
- return {
2533
- label: `${project.name} (${project.organisation.name})`,
2534
- selection
2535
- };
2536
- });
2537
- return {
2538
- choicesBySelection,
2539
- options: [
2540
- ...projectOptions,
2541
- { label: "Use my personal key", selection: "personal" }
2542
- ],
2543
- title: "Choose where Route 33 should manage this key"
2544
- };
2545
- };
2546
-
2547
- // src/ux/attribution-prompt-error/attribution-prompt-error.ts
2548
- var MESSAGES = {
2549
- ATTRIBUTION_PRESENTER_UNAVAILABLE: "Route 33 needs OpenCode v2 to assign a budget for this folder. Update to OpenCode v2, run /connect, choose Route 33, and follow the steps.",
2550
- ATTRIBUTION_SELECTION_INVALID: "That Route 33 option is no longer available. Connect again and pick another."
2551
- };
2552
- var AttributionPromptError = class extends GatewayCredentialError {
2553
- code;
2554
- constructor(code) {
2555
- super(MESSAGES[code]);
2556
- this.name = "AttributionPromptError";
2557
- this.code = code;
2558
- }
2559
- };
2560
-
2561
- // src/ux/prompt-flow/prompt-flow.ts
2562
- var resolveAttribution = async (options) => {
2563
- const remembered = readAttribution(options.state);
2564
- if (remembered !== void 0) {
2565
- return remembered;
2566
- }
2567
- if (options.presenter === void 0) {
2568
- throw new AttributionPromptError("ATTRIBUTION_PRESENTER_UNAVAILABLE");
2569
- }
2570
- const plan = createAttributionPrompt(await options.loadProjects());
2571
- const selection = await options.presenter.pick(plan);
2572
- const attribution = plan.choicesBySelection[selection];
2573
- if (attribution === void 0) {
2574
- throw new AttributionPromptError("ATTRIBUTION_SELECTION_INVALID");
2575
- }
2576
- writeAttribution(options.state, attribution);
2577
- return attribution;
2578
- };
2579
-
2580
- // src/ux/trusted-party-copy/trusted-party-copy.ts
2581
- var TRUSTED_PARTY_COPY = {
2582
- message: "Route 33 wants to manage your keys for this OpenCode installation.",
2583
- title: "Connect Route 33"
2584
- };
2585
-
2586
- // src/auth/auth-error/auth-error.ts
2587
- var AuthError = class extends Error {
2588
- constructor(code, options) {
2589
- super(
2590
- "Route 33 authorization failed",
2591
- options?.cause === void 0 ? void 0 : { cause: options.cause }
2592
- );
2593
- this.code = code;
2594
- this.name = "AuthError";
2595
- this.statusCode = options?.statusCode;
2596
- }
2597
- code;
2598
- statusCode;
2599
- };
2600
-
2601
- // src/auth/build-authorize-url/build-authorize-url.ts
2602
- var buildAuthorizeUrl = (issuerUrl, request) => {
2603
- const url = new URL("/authorize", issuerUrl);
2604
- url.search = new URLSearchParams({
2605
- client_id: request.clientId,
2606
- code_challenge: request.codeChallenge,
2607
- code_challenge_method: "S256",
2608
- redirect_uri: request.redirectUri,
2609
- response_type: "code",
2610
- scope: request.scope,
2611
- state: request.state
2612
- }).toString();
2613
- return url;
2614
- };
2615
-
2616
- // src/contract/auth-session/parse-token-set/parse-token-set.ts
2617
- var parseTokenSet = (value) => {
2618
- if (!isRecord(value)) {
2619
- throw new ContractParseError("invalid token response");
2620
- }
2621
- if (typeof value["access_token"] !== "string" || typeof value["expires_in"] !== "number" || value["token_type"] !== "Bearer") {
2622
- throw new ContractParseError("invalid token response");
2623
- }
2624
- return {
2625
- access_token: value["access_token"],
2626
- expires_in: value["expires_in"],
2627
- token_type: "Bearer",
2628
- ...typeof value["id_token"] === "string" ? { id_token: value["id_token"] } : {},
2629
- ...typeof value["refresh_token"] === "string" ? { refresh_token: value["refresh_token"] } : {},
2630
- ...typeof value["scope"] === "string" ? { scope: value["scope"] } : {}
2631
- };
2632
- };
2633
-
2634
- // src/auth/exchange-code-and-parse/exchange-code-and-parse.ts
2635
- var exchangeCodeAndParse = async (options) => {
2636
- const body = new URLSearchParams({
2637
- client_id: options.clientId,
2638
- code: options.input.code,
2639
- code_verifier: options.input.codeVerifier,
2640
- grant_type: "authorization_code",
2641
- redirect_uri: options.input.redirectUri
2642
- });
2643
- let response;
2644
- try {
2645
- response = await (options.fetchImpl ?? fetch)(options.tokenUrl, {
2646
- body,
2647
- headers: { "content-type": "application/x-www-form-urlencoded" },
2648
- method: "POST"
2649
- });
2650
- } catch (cause) {
2651
- throw new AuthError("AUTH_CODE_EXCHANGE_FAILED", { cause });
2652
- }
2653
- if (!response.ok) {
2654
- throw new AuthError("AUTH_CODE_EXCHANGE_FAILED", {
2655
- statusCode: response.status
2656
- });
2657
- }
2658
- let payload;
2659
- try {
2660
- payload = await response.json();
2661
- } catch (cause) {
2662
- throw new AuthError("AUTH_CODE_EXCHANGE_FAILED", { cause });
2663
- }
2664
- try {
2665
- return parseTokenSet(payload);
2666
- } catch (cause) {
2667
- if (cause instanceof ContractParseError) {
2668
- throw new AuthError("AUTH_CODE_EXCHANGE_FAILED", { cause });
2669
- }
2670
- throw cause;
2671
- }
2672
- };
2673
-
2674
- // src/auth/refresh-session/refresh-session.ts
2675
- var refreshSession = async (options) => {
2676
- const body = new URLSearchParams({
2677
- client_id: options.clientId,
2678
- grant_type: "refresh_token",
2679
- refresh_token: options.refreshToken
2680
- });
2681
- let response;
2682
- try {
2683
- response = await (options.fetchImpl ?? fetch)(options.tokenUrl, {
2684
- body,
2685
- headers: { "content-type": "application/x-www-form-urlencoded" },
2686
- method: "POST"
2687
- });
2688
- } catch (cause) {
2689
- throw new AuthError("AUTH_REAUTHENTICATION_REQUIRED", { cause });
2690
- }
2691
- if (!response.ok) {
2692
- throw new AuthError("AUTH_REAUTHENTICATION_REQUIRED", {
2693
- statusCode: response.status
2694
- });
2695
- }
2696
- let payload;
2697
- try {
2698
- payload = await response.json();
2699
- } catch (cause) {
2700
- throw new AuthError("AUTH_REAUTHENTICATION_REQUIRED", { cause });
2701
- }
2702
- try {
2703
- return parseTokenSet(payload);
2704
- } catch (cause) {
2705
- if (cause instanceof ContractParseError) {
2706
- throw new AuthError("AUTH_REAUTHENTICATION_REQUIRED", { cause });
2707
- }
2708
- throw cause;
2709
- }
2710
- };
2711
-
2712
- // src/auth/create-auth-session/create-auth-session.ts
2713
- var createAuthSession = (options) => {
2714
- const tokenUrl = new URL("/oauth/token", options.issuerUrl);
2715
- return {
2716
- authorizationUrl: (request) => buildAuthorizeUrl(options.issuerUrl, request),
2717
- exchangeCode: (input) => exchangeCodeAndParse({
2718
- clientId: options.clientId,
2719
- ...options.fetchImpl === void 0 ? {} : { fetchImpl: options.fetchImpl },
2720
- input,
2721
- tokenUrl
2722
- }),
2723
- refresh: (refreshToken) => refreshSession({
2724
- clientId: options.clientId,
2725
- ...options.fetchImpl === void 0 ? {} : { fetchImpl: options.fetchImpl },
2726
- refreshToken,
2727
- tokenUrl
2728
- })
2729
- };
2730
- };
2731
-
2732
- // src/auth/open-browser-and-capture/open-browser-and-capture.ts
2733
- import { spawn } from "node:child_process";
2734
- import { timingSafeEqual } from "node:crypto";
2735
-
2736
- // src/auth/loopback-callback/loopback-callback.ts
2737
- import { createServer } from "node:http";
2738
-
2739
- // src/auth/loopback-callback/callback-page/callback-page.ts
2740
- var MARK_PATHS = [
2741
- "M310.6,175.8c0-6.1-2.7-11.8-7.2-15.9l-127.6-114.4V7h-43.5v48.3c0,6.2,2.6,12.1,7.2,16.2l92.5,82.9H0v43.5h234.7l-95.6,91.4c-4.3,4.1-6.7,9.8-6.7,15.7v48.3h43.5v-39l128.1-122.4c4.4-4.2,6.8-10,6.7-16.1h0Z",
2742
- "M457.2,175.9v-2.6c18.4-4.9,33-14.1,44-27.7,11-13.6,16.5-30.3,16.5-50.3s-4.5-32.9-13.4-47.5c-9-14.5-21.6-26.1-38-34.9-16.4-8.7-35.8-13.1-58.3-13.1s-41.1,4-58.8,12.1-31.8,19.3-42.4,33.7-16.1,31-16.6,50h51.1c.3-10.8,3.6-20,9.8-27.7,6.2-7.6,14.2-13.5,24.1-17.5,9.9-4.1,20.6-6.1,32-6.1s22.3,2.2,31.1,6.7c8.8,4.5,15.6,10.8,20.5,18.8,4.9,8.1,7.3,17.5,7.3,28.4s-2.8,21.1-8.4,29.4-13.3,14.8-23.2,19.5-21.3,7.1-34.2,7.1h-29.9v43.3h29.9c16.2,0,30,2.5,41.5,7.5,11.5,5,20.2,12,26.1,21.1,6,9,9,19.4,9,31.2s-3,21.2-9.1,29.8-14.4,15.3-25.1,20.1c-10.7,4.9-22.9,7.3-36.7,7.3s-24.2-2.1-34.5-6.2-18.6-10-24.8-17.5c-6.3-7.5-9.8-16.5-10.6-26.8h-53.2c.6,19,6.3,35.7,17,50.2,10.7,14.5,25.2,25.7,43.6,33.8,18.4,8.1,39.4,12.1,63.1,12.1s45.1-4.4,63.7-13.2c18.6-8.8,33.3-20.8,44-36,10.8-15.2,16.1-32.5,16.1-51.8s-6.4-40.7-19.2-55.6c-12.8-14.9-30.8-24.2-54-27.8h0Z",
2743
- "M763,203.7c-12.8-14.9-30.8-24.2-54-27.8v-2.6c18.4-4.9,33-14.1,44-27.7,11-13.6,16.5-30.3,16.5-50.3s-4.5-32.9-13.4-47.5c-8.9-14.5-21.6-26.1-38-34.9-16.4-8.7-35.8-13.1-58.3-13.1s-41.1,4-58.8,12.1-31.8,19.3-42.4,33.7-16.1,31-16.6,50h51.1c.3-10.8,3.6-20,9.8-27.7,6.2-7.6,14.2-13.5,24.1-17.5,9.9-4.1,20.6-6.1,32-6.1s22.3,2.2,31.1,6.7c8.8,4.5,15.6,10.8,20.5,18.8,4.9,8.1,7.3,17.5,7.3,28.4s-2.8,21.1-8.4,29.4c-5.6,8.3-13.3,14.8-23.2,19.5s-21.3,7.1-34.2,7.1h-29.9v43.3h29.9c16.2,0,30,2.5,41.5,7.5,11.5,5,20.2,12,26.1,21.1,6,9,8.9,19.4,8.9,31.2s-3,21.2-9.1,29.8c-6.1,8.6-14.4,15.3-25.1,20.1-10.7,4.9-22.9,7.3-36.7,7.3s-24.2-2.1-34.5-6.2-18.6-10-24.8-17.5c-6.3-7.5-9.8-16.5-10.6-26.8h-53.2c.6,19,6.3,35.7,17,50.2,10.7,14.5,25.2,25.7,43.6,33.8,18.4,8.1,39.4,12.1,63.1,12.1s45.1-4.4,63.7-13.2,33.3-20.8,44-36c10.8-15.2,16.1-32.5,16.1-51.8s-6.4-40.7-19.2-55.6h0Z"
2744
- ];
2745
- var BRAND_MARK = `<svg class="r33-mark" viewBox="0 0 782.2 360.3" fill="currentColor" role="img" aria-label="route33.ai" xmlns="http://www.w3.org/2000/svg"><path d="${MARK_PATHS[0]}"/><path d="${MARK_PATHS[1]}"/><path d="${MARK_PATHS[2]}"/></svg>`;
2746
- var PAGE_STYLE = `:root{color-scheme:dark;--r33-euro-blue:#0000FF;--r33-off-white:#F1F1F1;--r33-off-black:#121212;--r33-grey-300:#C9CBCF;--r33-radius-fillet:4px;--r33-space-2:8px;--r33-space-3:16px;--r33-space-4:24px;--r33-space-6:64px;--r33-font-body:"Geist","Helvetica Neue",Arial,sans-serif;--r33-font-mono:"Geist Mono","SFMono-Regular",Menlo,monospace;--r33-surface:var(--r33-off-black);--r33-text-primary:var(--r33-off-white);--r33-text-secondary:var(--r33-grey-300);--r33-fill-accent:var(--r33-euro-blue);--r33-on-fill-accent:var(--r33-off-white)}
2747
- *{box-sizing:border-box}
2748
- html,body{margin:0}
2749
- .r33-page{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:var(--r33-space-6);background:var(--r33-surface);color:var(--r33-text-primary);font-family:var(--r33-font-body);-webkit-font-smoothing:antialiased}
2750
- .r33-panel{display:grid;gap:var(--r33-space-4);max-width:460px;width:100%}
2751
- .r33-mark{height:40px;width:auto;color:var(--r33-text-primary)}
2752
- .r33-status{display:inline-flex;align-items:center;gap:var(--r33-space-2);font-size:11px;letter-spacing:0.08em;text-transform:uppercase;color:var(--r33-text-secondary)}
2753
- .r33-status__dot{width:8px;height:8px;background:var(--r33-fill-accent)}
2754
- .r33-status__dot--error{background:transparent;border:1px solid var(--r33-text-primary);border-radius:50%}
2755
- .r33-headline{margin:0;font-size:48px;line-height:0.85;letter-spacing:-0.02em;font-weight:600;color:var(--r33-text-primary);text-wrap:balance}
2756
- .r33-subline{margin:0;font-size:16px;line-height:1.45;color:var(--r33-text-secondary);max-width:40ch;text-wrap:pretty}
2757
- .r33-code{margin:0;font-size:13px;letter-spacing:0.04em;color:var(--r33-text-secondary)}
2758
- .r33-code__value{font-family:var(--r33-font-mono);text-transform:uppercase;color:var(--r33-text-primary)}
2759
- @media (prefers-reduced-motion:reduce){*{animation-duration:1ms!important;transition-duration:1ms!important}}`;
2760
- var buildCallbackPage = (state, errorCode) => {
2761
- const isError = state === "error";
2762
- const safeCode = (errorCode ?? "").replace(/[^a-z0-9_-]/gi, "");
2763
- const title = isError ? "Sign-in failed" : "Signed in";
2764
- const headline = isError ? "Sign-in failed" : "Signed in to route33";
2765
- const subline = isError ? "Return to OpenCode and retry." : "Return to OpenCode \u2014 one more step may wait there: if it asks which budget covers usage, pick a project. Then this window can be closed.";
2766
- const statusLabel = isError ? "Authentication error" : "Authenticated";
2767
- const dotClass = isError ? "r33-status__dot r33-status__dot--error" : "r33-status__dot";
2768
- const codeLine = isError && safeCode.length > 0 ? `<p class="r33-code">Error code: <span class="r33-code__value">${safeCode}</span></p>` : "";
2769
- return `<!doctype html>
2770
- <html lang="en" data-theme="dark">
2771
- <head>
2772
- <meta charset="utf-8">
2773
- <meta name="viewport" content="width=device-width, initial-scale=1">
2774
- <title>${title} \xB7 route33.ai</title>
2775
- <style>${PAGE_STYLE}</style>
2776
- </head>
2777
- <body>
2778
- <main class="r33-page">
2779
- <div class="r33-panel">
2780
- ${BRAND_MARK}
2781
- <span class="r33-status"><span class="${dotClass}"></span>${statusLabel}</span>
2782
- <h1 class="r33-headline">${headline}</h1>
2783
- <p class="r33-subline">${subline}</p>
2784
- ${codeLine}
2785
- </div>
2786
- </main>
2787
- </body>
2788
- </html>`;
2789
- };
2790
-
2791
- // src/auth/loopback-callback/loopback-callback.ts
2792
- var createAuthLoopbackCallback = async () => {
2793
- let redirectUri = "http://127.0.0.1/callback";
2794
- let resolveCapture;
2795
- const captured = new Promise((resolve2) => {
2796
- resolveCapture = resolve2;
2797
- });
2798
- const server = createServer((request, response) => {
2799
- const url = new URL(request.url ?? "/", redirectUri);
2800
- if (request.method !== "GET" || url.pathname !== "/callback") {
2801
- response.writeHead(404).end();
2802
- return;
2803
- }
2804
- const resolve2 = resolveCapture;
2805
- resolveCapture = void 0;
2806
- resolve2?.(url);
2807
- const error = url.searchParams.get("error");
2808
- const page = error === null || error.length === 0 ? buildCallbackPage("success") : buildCallbackPage("error", error);
2809
- response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
2810
- response.end(page);
2811
- });
2812
- await new Promise((resolve2, reject) => {
2813
- const onError = (cause) => {
2814
- reject(new AuthError("AUTH_CALLBACK_LISTEN_FAILED", { cause }));
2815
- };
2816
- server.once("error", onError);
2817
- server.listen(0, "127.0.0.1", () => {
2818
- server.off("error", onError);
2819
- resolve2();
2820
- });
2821
- });
2822
- const address = server.address();
2823
- if (address === null || typeof address === "string") {
2824
- server.close();
2825
- throw new AuthError("AUTH_CALLBACK_LISTEN_FAILED");
2826
- }
2827
- redirectUri = `http://127.0.0.1:${String(address.port)}/callback`;
2828
- return {
2829
- redirectUri,
2830
- close: () => {
2831
- if (!server.listening) {
2832
- return Promise.resolve();
2833
- }
2834
- return new Promise((resolve2, reject) => {
2835
- server.close((cause) => {
2836
- if (cause === void 0) {
2837
- resolve2();
2838
- return;
2839
- }
2840
- reject(new AuthError("AUTH_CALLBACK_CLEANUP_FAILED", { cause }));
2841
- });
2842
- });
2843
- },
2844
- wait: () => captured
2845
- };
2846
- };
2847
-
2848
- // src/auth/open-browser-and-capture/open-browser-and-capture.ts
2849
- var openBrowserAndCapture = async (options) => {
2850
- const callback = await createAuthLoopbackCallback();
2851
- const authorizationUrl = options.authorizationUrl(callback.redirectUri);
2852
- const browserRunner = options.browserRunner ?? ((url) => new Promise((resolve2, reject) => {
2853
- let command = "xdg-open";
2854
- let args = [url.href];
2855
- if (process.platform === "darwin") {
2856
- command = "open";
2857
- } else if (process.platform === "win32") {
2858
- command = "cmd";
2859
- args = ["/c", "start", "", url.href];
2860
- }
2861
- const child = spawn(command, args, {
2862
- detached: true,
2863
- stdio: "ignore"
2864
- });
2865
- child.once("error", reject);
2866
- child.once("spawn", () => {
2867
- child.unref();
2868
- resolve2();
2869
- });
2870
- }));
2871
- let timer;
2872
- const timeout = new Promise((_resolve, reject) => {
2873
- timer = setTimeout(() => {
2874
- reject(new AuthError("AUTH_CALLBACK_TIMEOUT"));
2875
- }, options.timeoutMs ?? 12e4);
2876
- });
2877
- const launch = browserRunner(authorizationUrl).catch((cause) => {
2878
- throw new AuthError("AUTH_BROWSER_LAUNCH_FAILED", { cause });
2879
- });
2880
- try {
2881
- const completed = await Promise.race([
2882
- Promise.all([launch, callback.wait()]),
2883
- timeout
2884
- ]);
2885
- const callbackUrl = completed[1];
2886
- const actualState = callbackUrl.searchParams.get("state");
2887
- if (actualState === null) {
2888
- throw new AuthError("AUTH_CALLBACK_RESPONSE_INVALID");
2889
- }
2890
- const expectedState = Buffer.from(options.expectedState);
2891
- const receivedState = Buffer.from(actualState);
2892
- if (expectedState.length !== receivedState.length || !timingSafeEqual(expectedState, receivedState)) {
2893
- throw new AuthError("AUTH_CALLBACK_STATE_MISMATCH");
2894
- }
2895
- const code = callbackUrl.searchParams.get("code");
2896
- if (code === null || code.length === 0) {
2897
- throw new AuthError("AUTH_CALLBACK_RESPONSE_INVALID");
2794
+ * @returns Whether the swap committed.
2795
+ */
2796
+ compareAndSwap(expected, next) {
2797
+ if (this.#current !== expected) {
2798
+ return false;
2898
2799
  }
2899
- return { code, redirectUri: callback.redirectUri };
2900
- } finally {
2901
- if (timer !== void 0) {
2902
- clearTimeout(timer);
2800
+ this.#current = next;
2801
+ return true;
2802
+ }
2803
+ /** Clears metadata only while the expected snapshot remains current.
2804
+ * @param expected - Snapshot whose lease is being invalidated.
2805
+ * @returns Whether the state was cleared.
2806
+ */
2807
+ clear(expected) {
2808
+ if (this.#current !== expected) {
2809
+ return false;
2903
2810
  }
2904
- await callback.close();
2811
+ this.#current = void 0;
2812
+ return true;
2813
+ }
2814
+ static #unexpectedAttribution(value) {
2815
+ throw new TypeError(`Unexpected attribution: ${String(value)}`);
2905
2816
  }
2906
2817
  };
2907
2818
 
2908
- // src/auth/pkce-verifier-and-challenge/pkce-verifier-and-challenge.ts
2909
- import { randomBytes } from "node:crypto";
2819
+ // src/key-lease/lease-installation-factory/lease-installation-factory.ts
2820
+ var createLeaseInstallationFactory = (options) => {
2821
+ const installations = /* @__PURE__ */ new Map();
2822
+ return (scopeId) => {
2823
+ const existing = installations.get(scopeId);
2824
+ if (existing !== void 0) {
2825
+ return existing;
2826
+ }
2827
+ const installation = {
2828
+ keyName: `route33:lease:${scopeId}`,
2829
+ secretStore: options.createSecretStore(scopeId),
2830
+ ...options.createStateStore === void 0 ? {} : { stateStore: options.createStateStore(scopeId) },
2831
+ state: LeaseState.restore(void 0)
2832
+ };
2833
+ installations.set(scopeId, installation);
2834
+ return installation;
2835
+ };
2836
+ };
2910
2837
 
2911
- // src/auth/pkce-challenge/pkce-challenge.ts
2838
+ // src/key-lease/scope-id/scope-id.ts
2912
2839
  import { createHash as createHash3 } from "node:crypto";
2913
- var pkceChallenge = (verifier) => createHash3("sha256").update(verifier).digest("base64url");
2840
+ var deriveScopeId = (input) => {
2841
+ let identity;
2842
+ switch (input.attribution.kind) {
2843
+ case "personal":
2844
+ identity = ["route33-lease", "personal"];
2845
+ break;
2846
+ case "project":
2847
+ if (input.folderRealpath === void 0 || input.folderRealpath.length === 0) {
2848
+ throw new TypeError("Project lease scope requires a real folder path");
2849
+ }
2850
+ identity = [
2851
+ "route33-lease",
2852
+ "project",
2853
+ input.attribution.projectId,
2854
+ input.folderRealpath
2855
+ ];
2856
+ break;
2857
+ default: {
2858
+ const unexpected = input.attribution;
2859
+ throw new TypeError(`Unexpected attribution: ${String(unexpected)}`);
2860
+ }
2861
+ }
2862
+ return createHash3("sha256").update(JSON.stringify(identity)).digest("hex");
2863
+ };
2914
2864
 
2915
- // src/auth/pkce-verifier-and-challenge/pkce-verifier-and-challenge.ts
2916
- var createPkceVerifierAndChallenge = () => {
2917
- const codeVerifier = randomBytes(32).toString("base64url");
2865
+ // src/requested-client-id/requested-client-id.ts
2866
+ var DEFAULT_ROUTE33_CLIENT_ID = false ? "" : "oa_platform_203623de";
2867
+
2868
+ // src/commands/change-attribution/change-attribution.ts
2869
+ var changeAttribution = async (options) => {
2870
+ await options.clear();
2871
+ return options.prompt();
2872
+ };
2873
+
2874
+ // src/commands/inspect-session/inspect-session.ts
2875
+ var inspectSession = (options) => {
2876
+ const attribution = options.readAttribution();
2877
+ const lease = options.readLease();
2918
2878
  return {
2919
- codeChallenge: pkceChallenge(codeVerifier),
2920
- codeVerifier,
2921
- state: randomBytes(32).toString("base64url")
2879
+ attribution,
2880
+ ...lease === void 0 ? {} : { lease: { expiresAt: lease.expiresAt, keyId: lease.keyId } }
2922
2881
  };
2923
2882
  };
2924
2883
 
2925
- // src/auth/requested-scopes/requested-scopes.ts
2926
- var REQUESTED_OAUTH_SCOPES = "openid offline_access projects:read keys:read keys:write";
2884
+ // src/commands/reset-attribution/reset-attribution.ts
2885
+ var resetAttribution = async (options) => {
2886
+ await options.clear();
2887
+ };
2927
2888
 
2928
- // src/auth/login-flow/login-flow.ts
2929
- var loginFlow = async (options) => {
2930
- const log = options.logger ?? logger;
2931
- log.info("auth.login.started");
2932
- const pkce = createPkceVerifierAndChallenge();
2933
- const captured = await openBrowserAndCapture({
2934
- authorizationUrl: (redirectUri) => options.session.authorizationUrl({
2935
- clientId: options.clientId,
2936
- codeChallenge: pkce.codeChallenge,
2937
- redirectUri,
2938
- scope: REQUESTED_OAUTH_SCOPES,
2939
- state: pkce.state
2940
- }),
2941
- ...options.browserRunner === void 0 ? {} : { browserRunner: options.browserRunner },
2942
- expectedState: pkce.state,
2943
- ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
2944
- });
2945
- const tokenSet = await options.session.exchangeCode({
2946
- code: captured.code,
2947
- codeVerifier: pkce.codeVerifier,
2948
- redirectUri: captured.redirectUri
2889
+ // src/commands/rotate-now/rotate-now.ts
2890
+ var rotateNow = async (options) => {
2891
+ const rotated = await options.lease({ renewalRatio: Number.EPSILON });
2892
+ return rotated.lease;
2893
+ };
2894
+
2895
+ // src/ux/attribution-prompt/attribution-prompt.ts
2896
+ var createAttributionPrompt = (projects) => {
2897
+ const choicesBySelection = {
2898
+ personal: { kind: "personal" }
2899
+ };
2900
+ const projectOptions = projects.map((project) => {
2901
+ const selection = `project:${project.id}`;
2902
+ choicesBySelection[selection] = {
2903
+ kind: "project",
2904
+ projectId: project.id
2905
+ };
2906
+ return {
2907
+ label: `${project.name} (${project.organisation.name})`,
2908
+ selection
2909
+ };
2949
2910
  });
2950
- let credential;
2951
- try {
2952
- credential = parseManagementJwt(tokenSet.access_token);
2953
- } catch (cause) {
2954
- throw new AuthError("AUTH_TOKEN_RESPONSE_INVALID", { cause });
2911
+ return {
2912
+ choicesBySelection,
2913
+ options: [
2914
+ ...projectOptions,
2915
+ { label: "Use my personal key", selection: "personal" }
2916
+ ],
2917
+ title: "Choose where Route 33 should manage this key"
2918
+ };
2919
+ };
2920
+
2921
+ // src/ux/attribution-prompt-error/attribution-prompt-error.ts
2922
+ var MESSAGES = {
2923
+ ATTRIBUTION_PRESENTER_UNAVAILABLE: "Route 33 needs OpenCode v2 to assign a budget for this folder. Update to OpenCode v2, run /connect, choose Route 33, and follow the steps.",
2924
+ ATTRIBUTION_SELECTION_INVALID: "That Route 33 option is no longer available. Connect again and pick another."
2925
+ };
2926
+ var AttributionPromptError = class extends GatewayCredentialError {
2927
+ code;
2928
+ constructor(code) {
2929
+ super(MESSAGES[code]);
2930
+ this.name = "AttributionPromptError";
2931
+ this.code = code;
2955
2932
  }
2956
- log.info("auth.login.completed");
2957
- return { credential, tokenSet };
2933
+ };
2934
+
2935
+ // src/ux/prompt-flow/prompt-flow.ts
2936
+ var resolveAttribution = async (options) => {
2937
+ const remembered = readAttribution(options.state);
2938
+ if (remembered !== void 0) {
2939
+ return remembered;
2940
+ }
2941
+ if (options.presenter === void 0) {
2942
+ throw new AttributionPromptError("ATTRIBUTION_PRESENTER_UNAVAILABLE");
2943
+ }
2944
+ const plan = createAttributionPrompt(await options.loadProjects());
2945
+ const selection = await options.presenter.pick(plan);
2946
+ const attribution = plan.choicesBySelection[selection];
2947
+ if (attribution === void 0) {
2948
+ throw new AttributionPromptError("ATTRIBUTION_SELECTION_INVALID");
2949
+ }
2950
+ writeAttribution(options.state, attribution);
2951
+ return attribution;
2952
+ };
2953
+
2954
+ // src/ux/trusted-party-copy/trusted-party-copy.ts
2955
+ var TRUSTED_PARTY_COPY = {
2956
+ message: "Route 33 wants to manage your keys for this OpenCode installation.",
2957
+ title: "Connect Route 33"
2958
2958
  };
2959
2959
 
2960
2960
  // src/route33-runtime/login-session/login-session.ts
@@ -3268,7 +3268,7 @@ var createWorkspaceKeyHeaders = ({ resolveKey, session }) => async (input, outpu
3268
3268
  // src/create-route33-plugin/create-route33-plugin.ts
3269
3269
  var createRoute33Plugin = (options = {}) => {
3270
3270
  const baseURL = readEnvBaseUrl();
3271
- const clientId = options.clientId ?? readEnvClientId() ?? DEFAULT_ROUTE33_CLIENT_ID;
3271
+ const clientId2 = options.clientId ?? readEnvClientId() ?? DEFAULT_ROUTE33_CLIENT_ID;
3272
3272
  const directKey = readEnvDirectApiKey();
3273
3273
  const resolvedConfig = {
3274
3274
  ...baseURL === void 0 ? {} : { baseURL },
@@ -3289,19 +3289,19 @@ var createRoute33Plugin = (options = {}) => {
3289
3289
  })
3290
3290
  });
3291
3291
  return (input) => {
3292
- const runtime = runtimeOptions === void 0 || installation === void 0 ? void 0 : new Route33Runtime({
3292
+ const runtime2 = runtimeOptions === void 0 || installation === void 0 ? void 0 : new Route33Runtime({
3293
3293
  ...runtimeOptions,
3294
- auth: { ...runtimeOptions.auth, clientId },
3294
+ auth: { ...runtimeOptions.auth, clientId: clientId2 },
3295
3295
  installation,
3296
3296
  state: {
3297
3297
  directory: input.worktree,
3298
3298
  outsideWorktree: "return-undefined"
3299
3299
  }
3300
3300
  });
3301
- if (runtime !== void 0) {
3302
- options.bindRuntime?.(runtime);
3301
+ if (runtime2 !== void 0) {
3302
+ options.bindRuntime?.(runtime2);
3303
3303
  if (options.registerCommands !== void 0) {
3304
- options.registerCommands(runtime.commandHandlers());
3304
+ options.registerCommands(runtime2.commandHandlers());
3305
3305
  }
3306
3306
  }
3307
3307
  let credentialProvider;
@@ -3309,14 +3309,14 @@ var createRoute33Plugin = (options = {}) => {
3309
3309
  let promptGate;
3310
3310
  if (directKey !== void 0) {
3311
3311
  credentialProvider = () => Promise.resolve(parseGatewayKey(directKey));
3312
- } else if (runtime === void 0) {
3312
+ } else if (runtime2 === void 0) {
3313
3313
  credentialProvider = () => Promise.reject(
3314
3314
  new AttributionPromptError("ATTRIBUTION_PRESENTER_UNAVAILABLE")
3315
3315
  );
3316
3316
  } else {
3317
- credentialProvider = () => runtime.resolveCredential();
3317
+ credentialProvider = () => runtime2.resolveCredential();
3318
3318
  workspaceHeaders = createWorkspaceKeyHeaders({
3319
- resolveKey: (directory) => runtime.resolveWorkspaceCredential(directory),
3319
+ resolveKey: (directory) => runtime2.resolveWorkspaceCredential(directory),
3320
3320
  session: {
3321
3321
  get: async ({ sessionID }) => {
3322
3322
  const session = await options.readSession?.(sessionID);
@@ -3788,6 +3788,210 @@ var buildProductionRuntimeOptions = (input) => ({
3788
3788
  ...input.tokenStore === void 0 ? {} : { tokenStore: input.tokenStore }
3789
3789
  });
3790
3790
 
3791
+ // src/v1-entry/host-auth-bridge/host-auth-bridge.ts
3792
+ var createHostAuthBridge = () => {
3793
+ let refresh;
3794
+ return {
3795
+ hydrate: (auth) => {
3796
+ if (typeof auth.refresh === "string" && auth.refresh !== "") {
3797
+ refresh = auth.refresh;
3798
+ }
3799
+ },
3800
+ tokenStore: {
3801
+ get: () => Promise.resolve(refresh),
3802
+ set: (token) => {
3803
+ refresh = token;
3804
+ return Promise.resolve();
3805
+ }
3806
+ }
3807
+ };
3808
+ };
3809
+
3810
+ // src/auth/authorization-url-stash/authorization-url-stash.ts
3811
+ var FALLBACK_URL = "https://route33.ai";
3812
+ var SIGN_IN_INSTRUCTIONS = "Open the Route 33 sign-in link \u2014 your browser will finish at a confirmation page.";
3813
+ var FINISHING_INSTRUCTIONS = "Finishing Route 33 setup\u2026";
3814
+ var URL_WAIT_BUDGET_MS = 3e4;
3815
+ var createAuthorizationUrlStash = () => {
3816
+ let resolveUrl;
3817
+ const nextUrl = new Promise((resolve2) => {
3818
+ resolveUrl = resolve2;
3819
+ });
3820
+ const browserRunner = (url) => {
3821
+ resolveUrl?.(url);
3822
+ return Promise.resolve();
3823
+ };
3824
+ const urlForConnect = async (callback) => {
3825
+ let decide;
3826
+ const decided = new Promise((resolve2) => {
3827
+ decide = resolve2;
3828
+ });
3829
+ const timer = setTimeout(() => {
3830
+ decide({ instructions: SIGN_IN_INSTRUCTIONS, url: FALLBACK_URL });
3831
+ }, URL_WAIT_BUDGET_MS);
3832
+ void nextUrl.then((url) => {
3833
+ clearTimeout(timer);
3834
+ decide({ instructions: SIGN_IN_INSTRUCTIONS, url: url.toString() });
3835
+ });
3836
+ void Promise.resolve(callback).catch(() => void 0).then(() => {
3837
+ clearTimeout(timer);
3838
+ decide({ instructions: FINISHING_INSTRUCTIONS, url: FALLBACK_URL });
3839
+ });
3840
+ return decided;
3841
+ };
3842
+ return { browserRunner, urlForConnect };
3843
+ };
3844
+
3845
+ // src/v1-entry/route33-auth-hook/route33-auth-hook.ts
3846
+ var createRoute33AuthHook = (deps) => ({
3847
+ loader: async (auth) => {
3848
+ deps.bridge.hydrate(await auth());
3849
+ return buildOptions(deps.providerBaseURL);
3850
+ },
3851
+ methods: [
3852
+ {
3853
+ authorize: async () => {
3854
+ const stash = createAuthorizationUrlStash();
3855
+ const login = deps.startLogin(stash.browserRunner);
3856
+ const decision = await stash.urlForConnect(login.then(() => void 0));
3857
+ return {
3858
+ callback: async () => {
3859
+ try {
3860
+ const { tokenSet } = await login;
3861
+ deps.bridge.hydrate({
3862
+ refresh: tokenSet.refresh_token,
3863
+ type: "oauth"
3864
+ });
3865
+ return {
3866
+ access: tokenSet.access_token,
3867
+ expires: Date.now() + tokenSet.expires_in * 1e3,
3868
+ provider: "route_33",
3869
+ refresh: tokenSet.refresh_token ?? "",
3870
+ type: "success"
3871
+ };
3872
+ } catch {
3873
+ return { type: "failed" };
3874
+ }
3875
+ },
3876
+ instructions: decision.instructions,
3877
+ method: "auto",
3878
+ url: decision.url
3879
+ };
3880
+ },
3881
+ label: "Route 33 (browser sign-in)",
3882
+ type: "oauth"
3883
+ }
3884
+ ],
3885
+ provider: "route_33"
3886
+ });
3887
+
3888
+ // src/v1-entry/host-modality-flags/host-modality-flags.ts
3889
+ var hostModalityFlags = (modalities) => ({
3890
+ audio: modalities.includes("audio"),
3891
+ image: modalities.includes("image"),
3892
+ pdf: modalities.includes("pdf"),
3893
+ text: modalities.includes("text"),
3894
+ video: modalities.includes("video")
3895
+ });
3896
+
3897
+ // src/v1-entry/host-model-capabilities/host-model-capabilities.ts
3898
+ var hostModelCapabilities = (meta, forceReasoning) => {
3899
+ const modalities = meta.modalities;
3900
+ const capabilities = meta.capabilities;
3901
+ const attachment = meta.attachment ?? capabilities.attachment ?? false;
3902
+ return {
3903
+ attachment,
3904
+ input: hostModalityFlags(modalities?.input ?? ["text"]),
3905
+ interleaved: false,
3906
+ output: hostModalityFlags(modalities?.output ?? ["text"]),
3907
+ reasoning: capabilities.reasoning === true || forceReasoning,
3908
+ temperature: capabilities.temperature === true,
3909
+ toolcall: capabilities.tool_call === true
3910
+ };
3911
+ };
3912
+
3913
+ // src/v1-entry/host-model-quantities/host-model-quantities.ts
3914
+ var ZERO_COST = {
3915
+ cache: { read: 0, write: 0 },
3916
+ input: 0,
3917
+ output: 0
3918
+ };
3919
+ var DEFAULT_LIMIT = { context: 2e5, output: 64e3 };
3920
+ var hostModelQuantities = (meta) => {
3921
+ const cost = meta.cost;
3922
+ const limit = meta.limit;
3923
+ return {
3924
+ cost: cost === void 0 ? ZERO_COST : {
3925
+ cache: {
3926
+ read: cost.cache_read ?? 0,
3927
+ write: cost.cache_write ?? 0
3928
+ },
3929
+ input: cost.input,
3930
+ output: cost.output
3931
+ },
3932
+ limit: limit === void 0 ? DEFAULT_LIMIT : {
3933
+ context: limit.context,
3934
+ output: limit.output
3935
+ }
3936
+ };
3937
+ };
3938
+
3939
+ // src/v1-entry/host-model-vendor/host-model-vendor.ts
3940
+ var hostModelVendor = (slug) => {
3941
+ const separator = slug.indexOf("/");
3942
+ const vendor = separator === -1 ? slug : slug.slice(0, separator);
3943
+ return {
3944
+ forceReasoning: vendor === "anthropic",
3945
+ npm: NATIVE_SDK_BY_VENDOR[vendor] ?? "@ai-sdk/openai-compatible"
3946
+ };
3947
+ };
3948
+
3949
+ // src/v1-entry/host-models/host-models.ts
3950
+ var toHostModels = (input) => {
3951
+ const models = {};
3952
+ for (const [slug, meta] of Object.entries(input.enriched)) {
3953
+ const vendor = hostModelVendor(slug);
3954
+ const capabilities = hostModelCapabilities(meta, vendor.forceReasoning);
3955
+ const quantities = hostModelQuantities(meta);
3956
+ models[slug] = {
3957
+ api: { id: slug, npm: vendor.npm, url: input.baseURL },
3958
+ attachment: capabilities.attachment,
3959
+ capabilities,
3960
+ cost: quantities.cost,
3961
+ headers: {},
3962
+ id: slug,
3963
+ limit: quantities.limit,
3964
+ name: meta.name,
3965
+ options: {},
3966
+ providerID: "route_33",
3967
+ release_date: "",
3968
+ status: "active"
3969
+ };
3970
+ }
3971
+ return models;
3972
+ };
3973
+
3974
+ // src/v1-entry/route33-provider-models/route33-provider-models.ts
3975
+ var createRoute33ProviderModels = (deps) => async (provider) => {
3976
+ const fallback = provider.models;
3977
+ const key = await deps.resolveKey();
3978
+ if (key === void 0) return fallback;
3979
+ const [catalog, modelsDev] = await Promise.all([
3980
+ (deps.loadCatalog ?? getCatalog)({
3981
+ ...deps.catalog,
3982
+ cacheDir: deps.cacheDir,
3983
+ gatewayKey: key
3984
+ }),
3985
+ deps.loadModelsDev()
3986
+ ]);
3987
+ const enriched = enrichCatalog(catalog, modelsDev);
3988
+ const models = toHostModels({
3989
+ baseURL: deps.catalog.baseURL,
3990
+ enriched
3991
+ });
3992
+ return Object.keys(models).length === 0 ? fallback : models;
3993
+ };
3994
+
3791
3995
  // src/contract/fixtures/fixtures.ts
3792
3996
  var contractFixtures = {
3793
3997
  apiKey: {
@@ -3835,13 +4039,64 @@ var parseContractFixtures = (fixtures) => ({
3835
4039
  });
3836
4040
 
3837
4041
  // src/index.ts
3838
- var Route33Plugin = createRoute33Plugin({
4042
+ var clientId = readEnvClientId() ?? DEFAULT_ROUTE33_CLIENT_ID;
4043
+ var issuerUrl = readEnvAuthIssuer() ?? DEFAULT_AUTH_ISSUER_URL;
4044
+ var authBridge = createHostAuthBridge();
4045
+ var startNativeLogin = (browserRunner) => loginFlow({
4046
+ browserRunner,
4047
+ clientId,
4048
+ session: createAuthSession({ clientId, issuerUrl }),
4049
+ timeoutMs: LOGIN_BUDGET_MS
4050
+ });
4051
+ var route33Plugin = createRoute33Plugin({
4052
+ bindRuntime: (instance) => {
4053
+ runtime = instance;
4054
+ },
3839
4055
  runtime: buildProductionRuntimeOptions({
3840
4056
  apiBaseUrl: readEnvApiBase() ?? DEFAULT_MANAGEMENT_API_BASE_URL,
3841
- clientId: readEnvClientId() ?? DEFAULT_ROUTE33_CLIENT_ID,
3842
- issuerUrl: readEnvAuthIssuer() ?? DEFAULT_AUTH_ISSUER_URL
4057
+ clientId,
4058
+ issuerUrl,
4059
+ tokenStore: authBridge.tokenStore
3843
4060
  })
3844
4061
  });
4062
+ var runtime;
4063
+ var modelsDevOnce;
4064
+ var Route33Plugin = async (input) => {
4065
+ const hooks = await route33Plugin(input);
4066
+ const native = readEnvDirectApiKey();
4067
+ return {
4068
+ ...hooks,
4069
+ ...native !== void 0 ? {} : {
4070
+ auth: createRoute33AuthHook({
4071
+ bridge: authBridge,
4072
+ providerBaseURL: readEnvBaseUrl() ?? DEFAULT_BASE_URL,
4073
+ startLogin: startNativeLogin
4074
+ }),
4075
+ provider: {
4076
+ id: "route_33",
4077
+ models: createRoute33ProviderModels({
4078
+ cacheDir: readEnvCacheDir(),
4079
+ catalog: {
4080
+ baseURL: readEnvBaseUrl() ?? DEFAULT_BASE_URL,
4081
+ catalogTtlMs: readEnvCatalogTtlMs(),
4082
+ disableIdShim: readEnvDisableIdShim(),
4083
+ discoveryEnabled: readEnvDiscovery()
4084
+ },
4085
+ loadModelsDev: () => {
4086
+ modelsDevOnce ??= loadModelsDev({
4087
+ cacheDir: readEnvCacheDir()
4088
+ });
4089
+ return modelsDevOnce;
4090
+ },
4091
+ resolveKey: () => {
4092
+ const active = runtime;
4093
+ return active === void 0 ? Promise.resolve(void 0) : active.resolveWorkspaceCredential(input.worktree);
4094
+ }
4095
+ })
4096
+ }
4097
+ }
4098
+ };
4099
+ };
3845
4100
  export {
3846
4101
  AttributionPromptError,
3847
4102
  AuthError,