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

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/README.md CHANGED
@@ -5,22 +5,27 @@ usage, and use the route_33 models — no API keys to paste, ever.
5
5
 
6
6
  ## Install
7
7
 
8
- **OpenCode v2 (recommended)** uses the `add` subcommand and installs into
9
- the global configuration (`~/.config/opencode/opencode.json` + `tui.json`):
8
+ **OpenCode v2 (recommended):**
10
9
 
11
10
  ```sh
12
11
  opencode plugin add @route_33/opencode
13
12
  ```
14
13
 
15
- **OpenCode v1** installs the package and registers the plugin at the nearest
16
- definition — the project's `.opencode/opencode.json` (plus the TUI part in
17
- `tui.json`) when run inside a project, the global config with `--global`:
14
+ Installs globally — one `plugins` entry in the config's `opencode.json`
15
+ loads every part (the server and TUI parts are auto-discovered from the
16
+ package exports; no `tui.json` exists on v2).
17
+
18
+ **OpenCode v1:**
18
19
 
19
20
  ```sh
20
- opencode plugin @route_33/opencode # project scope
21
+ opencode plugin @route_33/opencode # project scope (default)
21
22
  opencode plugin @route_33/opencode --global # global scope
22
23
  ```
23
24
 
25
+ Installs at the nearest definition and registers the plugin twice: the
26
+ server part in `opencode.json` and the TUI part in `tui.json` — both files
27
+ are load-bearing on v1, which is why the installer writes both.
28
+
24
29
  Either way, restart OpenCode afterwards — plugins load at startup. Prefer
25
30
  config? Declare the plugin directly in `opencode.json(c)`:
26
31
 
@@ -30,15 +35,19 @@ config? Declare the plugin directly in `opencode.json(c)`:
30
35
 
31
36
  ## First run
32
37
 
33
- 1. Pick any route_33 model (or run `/connect` and choose **Route 33**).
34
- 2. Open the sign-in link — your browser finishes at a Route 33 confirmation
35
- page.
36
- 3. Connect again and assign **which budget covers usage in this folder**: one of your
37
- Route 33 projects, or your personal key.
38
- 4. The route_33 models appear. Done.
38
+ With `ROUTE33_API_KEY` unset and `ROUTE33_AUTOCONNECT` not `0`, the wizard
39
+ auto-starts at launch and completes **before the first prompt** on OpenCode
40
+ v2 — the setup awaits the whole chain:
41
+
42
+ 1. The browser sign-in opens; finish it there.
43
+ 2. The budget picker follows in OpenCode — one of your Route 33 projects,
44
+ or your personal key.
45
+ 3. The route_33 models appear. Pick one and prompt away.
39
46
 
40
- To change the budget later, connect again and pick another option. `/r33` shows
41
- the current state at any time.
47
+ On OpenCode v1 the wizard also auto-starts, but the 1.x TUI API has no
48
+ pre-prompt hook, so it runs in parallel with the prompt — the models appear
49
+ after the wizard finishes and OpenCode restarts. Use `/r33` any time to
50
+ re-run connect, change the budget, or inspect the session.
42
51
 
43
52
  ## Commands
44
53
 
package/dist/index.js CHANGED
@@ -3245,6 +3245,26 @@ var Route33Runtime = class {
3245
3245
  }
3246
3246
  };
3247
3247
 
3248
+ // src/v1-entry/unattributed-prompt-gate/unattributed-prompt-gate.ts
3249
+ var createUnattributedPromptGate = ({ directory, outsideWorktree }) => (input) => {
3250
+ if (input.model?.providerID !== "route_33") return Promise.resolve();
3251
+ const attribution = readAttribution({ directory, outsideWorktree });
3252
+ if (attribution !== void 0) return Promise.resolve();
3253
+ return Promise.reject(
3254
+ new Error(
3255
+ "Route 33 needs a budget for this folder first \u2014 run /r33 in OpenCode, choose Route 33, and assign a budget."
3256
+ )
3257
+ );
3258
+ };
3259
+
3260
+ // src/v1-entry/workspace-key-headers/workspace-key-headers.ts
3261
+ var createWorkspaceKeyHeaders = ({ resolveKey, session }) => async (input, output) => {
3262
+ const workspace = await session.get({ sessionID: input.sessionID });
3263
+ const key = await resolveKey(workspace.location.directory);
3264
+ if (key === void 0) return;
3265
+ output.headers["Authorization"] = `Bearer ${key.value}`;
3266
+ };
3267
+
3248
3268
  // src/create-route33-plugin/create-route33-plugin.ts
3249
3269
  var createRoute33Plugin = (options = {}) => {
3250
3270
  const baseURL = readEnvBaseUrl();
@@ -3285,6 +3305,8 @@ var createRoute33Plugin = (options = {}) => {
3285
3305
  }
3286
3306
  }
3287
3307
  let credentialProvider;
3308
+ let workspaceHeaders;
3309
+ let promptGate;
3288
3310
  if (directKey !== void 0) {
3289
3311
  credentialProvider = () => Promise.resolve(parseGatewayKey(directKey));
3290
3312
  } else if (runtime === void 0) {
@@ -3293,13 +3315,28 @@ var createRoute33Plugin = (options = {}) => {
3293
3315
  );
3294
3316
  } else {
3295
3317
  credentialProvider = () => runtime.resolveCredential();
3318
+ workspaceHeaders = createWorkspaceKeyHeaders({
3319
+ resolveKey: (directory) => runtime.resolveWorkspaceCredential(directory),
3320
+ session: {
3321
+ get: async ({ sessionID }) => {
3322
+ const session = await options.readSession?.(sessionID);
3323
+ return session ?? { location: { directory: input.worktree } };
3324
+ }
3325
+ }
3326
+ });
3327
+ promptGate = createUnattributedPromptGate({
3328
+ directory: input.worktree,
3329
+ outsideWorktree: "return-undefined"
3330
+ });
3296
3331
  }
3297
3332
  return Promise.resolve({
3298
3333
  config: createConfigHook({
3299
3334
  ...resolvedConfig,
3300
3335
  credentialProvider,
3301
3336
  cwd: input.worktree
3302
- })
3337
+ }),
3338
+ ...workspaceHeaders === void 0 ? {} : { "chat.headers": workspaceHeaders },
3339
+ ...promptGate === void 0 ? {} : { "chat.message": promptGate }
3303
3340
  });
3304
3341
  };
3305
3342
  };
@@ -6226,6 +6226,64 @@ var init_route33_runtime2 = __esm({
6226
6226
  }
6227
6227
  });
6228
6228
 
6229
+ // src/v1-entry/unattributed-prompt-gate/unattributed-prompt-gate.ts
6230
+ var createUnattributedPromptGate;
6231
+ var init_unattributed_prompt_gate = __esm({
6232
+ "src/v1-entry/unattributed-prompt-gate/unattributed-prompt-gate.ts"() {
6233
+ "use strict";
6234
+ init_attribution();
6235
+ createUnattributedPromptGate = ({ directory, outsideWorktree }) => (input) => {
6236
+ if (input.model?.providerID !== "route_33") return Promise.resolve();
6237
+ const attribution = readAttribution({ directory, outsideWorktree });
6238
+ if (attribution !== void 0) return Promise.resolve();
6239
+ return Promise.reject(
6240
+ new Error(
6241
+ "Route 33 needs a budget for this folder first \u2014 run /r33 in OpenCode, choose Route 33, and assign a budget."
6242
+ )
6243
+ );
6244
+ };
6245
+ }
6246
+ });
6247
+
6248
+ // src/v1-entry/unattributed-prompt-gate/index.ts
6249
+ var init_unattributed_prompt_gate2 = __esm({
6250
+ "src/v1-entry/unattributed-prompt-gate/index.ts"() {
6251
+ "use strict";
6252
+ init_unattributed_prompt_gate();
6253
+ }
6254
+ });
6255
+
6256
+ // src/v1-entry/workspace-key-headers/workspace-key-headers.ts
6257
+ var createWorkspaceKeyHeaders;
6258
+ var init_workspace_key_headers = __esm({
6259
+ "src/v1-entry/workspace-key-headers/workspace-key-headers.ts"() {
6260
+ "use strict";
6261
+ createWorkspaceKeyHeaders = ({ resolveKey, session }) => async (input, output) => {
6262
+ const workspace = await session.get({ sessionID: input.sessionID });
6263
+ const key = await resolveKey(workspace.location.directory);
6264
+ if (key === void 0) return;
6265
+ output.headers["Authorization"] = `Bearer ${key.value}`;
6266
+ };
6267
+ }
6268
+ });
6269
+
6270
+ // src/v1-entry/workspace-key-headers/index.ts
6271
+ var init_workspace_key_headers2 = __esm({
6272
+ "src/v1-entry/workspace-key-headers/index.ts"() {
6273
+ "use strict";
6274
+ init_workspace_key_headers();
6275
+ }
6276
+ });
6277
+
6278
+ // src/v1-entry/index.ts
6279
+ var init_v1_entry = __esm({
6280
+ "src/v1-entry/index.ts"() {
6281
+ "use strict";
6282
+ init_unattributed_prompt_gate2();
6283
+ init_workspace_key_headers2();
6284
+ }
6285
+ });
6286
+
6229
6287
  // src/create-route33-plugin/create-route33-plugin.ts
6230
6288
  var createRoute33Plugin;
6231
6289
  var init_create_route33_plugin = __esm({
@@ -6238,6 +6296,7 @@ var init_create_route33_plugin = __esm({
6238
6296
  init_requested_client_id2();
6239
6297
  init_route33_runtime2();
6240
6298
  init_ux();
6299
+ init_v1_entry();
6241
6300
  createRoute33Plugin = (options = {}) => {
6242
6301
  const baseURL = readEnvBaseUrl();
6243
6302
  const clientId = options.clientId ?? readEnvClientId() ?? DEFAULT_ROUTE33_CLIENT_ID;
@@ -6277,6 +6336,8 @@ var init_create_route33_plugin = __esm({
6277
6336
  }
6278
6337
  }
6279
6338
  let credentialProvider;
6339
+ let workspaceHeaders;
6340
+ let promptGate;
6280
6341
  if (directKey !== void 0) {
6281
6342
  credentialProvider = () => Promise.resolve(parseGatewayKey(directKey));
6282
6343
  } else if (runtime === void 0) {
@@ -6285,13 +6346,28 @@ var init_create_route33_plugin = __esm({
6285
6346
  );
6286
6347
  } else {
6287
6348
  credentialProvider = () => runtime.resolveCredential();
6349
+ workspaceHeaders = createWorkspaceKeyHeaders({
6350
+ resolveKey: (directory) => runtime.resolveWorkspaceCredential(directory),
6351
+ session: {
6352
+ get: async ({ sessionID }) => {
6353
+ const session = await options.readSession?.(sessionID);
6354
+ return session ?? { location: { directory: input.worktree } };
6355
+ }
6356
+ }
6357
+ });
6358
+ promptGate = createUnattributedPromptGate({
6359
+ directory: input.worktree,
6360
+ outsideWorktree: "return-undefined"
6361
+ });
6288
6362
  }
6289
6363
  return Promise.resolve({
6290
6364
  config: createConfigHook({
6291
6365
  ...resolvedConfig,
6292
6366
  credentialProvider,
6293
6367
  cwd: input.worktree
6294
- })
6368
+ }),
6369
+ ...workspaceHeaders === void 0 ? {} : { "chat.headers": workspaceHeaders },
6370
+ ...promptGate === void 0 ? {} : { "chat.message": promptGate }
6295
6371
  });
6296
6372
  };
6297
6373
  };
@@ -7093,21 +7169,43 @@ var showR33Menu = async (context) => {
7093
7169
  init_attribution();
7094
7170
  init_cli();
7095
7171
 
7172
+ // src/tui-entry/is-method-not-registered/is-method-not-registered.ts
7173
+ var isMethodNotRegistered = (error) => error instanceof Error && error.message.includes("OAuth method not found");
7174
+
7175
+ // src/tui-entry/v2-connect-surface/v2-connect-surface.ts
7176
+ var v2ConnectSurface = (context) => {
7177
+ const oauth = context.client?.integration?.oauth ?? context.client?.api?.integration?.oauth;
7178
+ return oauth?.connect;
7179
+ };
7180
+
7096
7181
  // src/tui-entry/v2-connect-runner/v2-connect-runner.ts
7097
7182
  var createV2ConnectRunner = ({
7098
7183
  context,
7184
+ maxAttempts = 20,
7185
+ retryDelayMs = 500,
7099
7186
  unattributed
7100
7187
  }) => {
7101
- const oauth = context.client?.api?.integration?.oauth;
7188
+ const connect = v2ConnectSurface(context);
7189
+ const sleep = (ms) => new Promise((resolve2) => {
7190
+ setTimeout(resolve2, ms);
7191
+ });
7102
7192
  return async (message) => {
7103
- const connect = oauth?.connect;
7104
7193
  if (connect === void 0) return;
7105
7194
  if (!unattributed()) return;
7106
7195
  context.ui.toast.show({ message, variant: "info" });
7107
- await connect({
7108
- integrationID: "route33",
7109
- methodID: "browser"
7110
- }).catch(() => void 0);
7196
+ for (let attempt = 1; ; attempt += 1) {
7197
+ if (!unattributed()) return;
7198
+ try {
7199
+ await connect({
7200
+ integrationID: "route33",
7201
+ methodID: "browser"
7202
+ });
7203
+ return;
7204
+ } catch (error) {
7205
+ if (attempt >= maxAttempts || !isMethodNotRegistered(error)) return;
7206
+ await sleep(retryDelayMs);
7207
+ }
7208
+ }
7111
7209
  };
7112
7210
  };
7113
7211
 
@@ -3248,6 +3248,26 @@ var Route33Runtime = class {
3248
3248
  }
3249
3249
  };
3250
3250
 
3251
+ // src/v1-entry/unattributed-prompt-gate/unattributed-prompt-gate.ts
3252
+ var createUnattributedPromptGate = ({ directory, outsideWorktree }) => (input) => {
3253
+ if (input.model?.providerID !== "route_33") return Promise.resolve();
3254
+ const attribution = readAttribution({ directory, outsideWorktree });
3255
+ if (attribution !== void 0) return Promise.resolve();
3256
+ return Promise.reject(
3257
+ new Error(
3258
+ "Route 33 needs a budget for this folder first \u2014 run /r33 in OpenCode, choose Route 33, and assign a budget."
3259
+ )
3260
+ );
3261
+ };
3262
+
3263
+ // src/v1-entry/workspace-key-headers/workspace-key-headers.ts
3264
+ var createWorkspaceKeyHeaders = ({ resolveKey, session }) => async (input, output) => {
3265
+ const workspace = await session.get({ sessionID: input.sessionID });
3266
+ const key = await resolveKey(workspace.location.directory);
3267
+ if (key === void 0) return;
3268
+ output.headers["Authorization"] = `Bearer ${key.value}`;
3269
+ };
3270
+
3251
3271
  // src/create-route33-plugin/create-route33-plugin.ts
3252
3272
  var createRoute33Plugin = (options = {}) => {
3253
3273
  const baseURL = readEnvBaseUrl();
@@ -3288,6 +3308,8 @@ var createRoute33Plugin = (options = {}) => {
3288
3308
  }
3289
3309
  }
3290
3310
  let credentialProvider;
3311
+ let workspaceHeaders;
3312
+ let promptGate;
3291
3313
  if (directKey !== void 0) {
3292
3314
  credentialProvider = () => Promise.resolve(parseGatewayKey(directKey));
3293
3315
  } else if (runtime === void 0) {
@@ -3296,13 +3318,28 @@ var createRoute33Plugin = (options = {}) => {
3296
3318
  );
3297
3319
  } else {
3298
3320
  credentialProvider = () => runtime.resolveCredential();
3321
+ workspaceHeaders = createWorkspaceKeyHeaders({
3322
+ resolveKey: (directory) => runtime.resolveWorkspaceCredential(directory),
3323
+ session: {
3324
+ get: async ({ sessionID }) => {
3325
+ const session = await options.readSession?.(sessionID);
3326
+ return session ?? { location: { directory: input.worktree } };
3327
+ }
3328
+ }
3329
+ });
3330
+ promptGate = createUnattributedPromptGate({
3331
+ directory: input.worktree,
3332
+ outsideWorktree: "return-undefined"
3333
+ });
3299
3334
  }
3300
3335
  return Promise.resolve({
3301
3336
  config: createConfigHook({
3302
3337
  ...resolvedConfig,
3303
3338
  credentialProvider,
3304
3339
  cwd: input.worktree
3305
- })
3340
+ }),
3341
+ ...workspaceHeaders === void 0 ? {} : { "chat.headers": workspaceHeaders },
3342
+ ...promptGate === void 0 ? {} : { "chat.message": promptGate }
3306
3343
  });
3307
3344
  };
3308
3345
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@route_33/opencode",
3
- "version": "0.4.7-rc.5",
3
+ "version": "0.4.7-rc.7",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",