@byokit/openclaw 0.25.0 → 0.26.0

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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.26.0 (2026-10-20)
6
+
7
+ - Dependency update: pins @byokit/accounts 0.30.0.
8
+
9
+ - FEAT: every key route now lists its own models: `kit.models(member, { authChoice })` answers the provider plugin's catalog joined by the vendor's own `/models` (not only OpenCode Go, OpenRouter and MiniMax), and `addKey({ ..., model })` takes any full `provider/model` id on any key route. An id the catalog lags is taken with the route's own capabilities instead of being refused; only a route with no catalog at all refuses a model.
10
+ - The bridge's Codex CLI route now names its two model rows itself instead of reading them from `@byokit/accounts`' catalogue, which no longer keeps model tiers; the rows the engine lists are unchanged.
11
+
5
12
  ## 0.25.0 (2026-10-20)
6
13
 
7
14
  - Dependency update: pins @byokit/accounts 0.29.0.
package/README.md CHANGED
@@ -159,10 +159,10 @@ other choice that takes no key (a subscription such as `openai`, a provider with
159
159
  throws its cause before anything is sent.
160
160
  `reason` is `auth` (provider refused the key) or `billing` (the provider's billing refused it; the kit words it and never passes
161
161
  provider text): both are `invalid`, and an unchanged key must not be retried. `empty` is a blank key, rejected locally before any
162
- provider call, also `invalid`. `model` is an unsupported route or unknown model (`not_included`), or a live model the engine did not
162
+ provider call, also `invalid`. `model` is an unsupported route or a model on a route with no catalog (`not_included`), or a model the engine did not
163
163
  register (`retry`). `network` is a timeout or transport failure; `inference` is a provider reply in an unexpected format, an unknown
164
164
  probe failure, or a typed request-validation refusal; `engine` is a control or preparation failure; `busy` is a rate limit or a
165
- restarting engine. `not_included` covers an unsupported route, an unknown chosen model and a probe reporting the route unavailable;
165
+ restarting engine. `not_included` covers an unsupported route, a chosen model on a route with no catalog and a probe reporting the route unavailable;
166
166
  every other failure is `retry`. Migrate from the old string result with `result.status === 'ok'`, not `result === 'ok'`.
167
167
  An OpenCode Go plan login can act for several workspaces, and the Go gateway refuses its every call until one is named
168
168
  (`403 Workspace selection is required`). Pass the workspace as `orgId` when you have it; it rides the probe and every run as
@@ -185,16 +185,19 @@ live catalog. Before a key exists it returns `[]` (a failed catalog probe with n
185
185
  empty; with one it is an error) and runs no engine discovery. Only available key-lane
186
186
  models can be saved; the ordinary member's sign-ins never supply the list. Pass a full catalog id, e.g.
187
187
  `model: 'opencode-go/muse-spark-1.3-contributor'`, to `addKey` to save it as the lane's default after activation.
188
- Model listing and selection belong to the live-catalog routes, OpenCode Go and OpenRouter. An OpenRouter key is
188
+ Every key route lists and takes models: its catalog is the provider plugin's own, joined by the vendor's own `/models`
189
+ list, and the kit keeps no provider or model list. An OpenRouter key is
189
190
  pay-as-you-go with no plan, so it takes any model its own live catalog lists, named `openrouter/<id>`, e.g.
190
191
  `model: 'openrouter/deepseek/deepseek-v4.1-flash-1017'`; an id already beginning with its own provider keeps one
191
192
  prefix (`openrouter/auto`). A Google preview id is named and saved as the canonical id the engine keeps
192
193
  (`openrouter/google/gemini-3.1-flash-lite-preview` becomes `openrouter/google/gemini-3.1-flash-lite`); the add
193
- registers that one model with the engine. Other key routes list no models, and a `model` on them returns
194
- `not_included`/`model` before activation.
195
- An unknown model returns `{ status: 'not_included', reason: 'model' }` and clears the replacement.
194
+ registers that one model with the engine (every provider but OpenCode Go and MiniMax, whose whole lists are registered).
195
+ A vendor's list can lag a model it already runs, so an id the catalog lacks is taken, not refused: it rides the route's
196
+ own models (the richest row's api, base URL and context), unpriced, and the app sees it missing from `models()` to warn.
197
+ A route with no catalog rows (its plugin has none) lists nothing, and a `model` there returns
198
+ `{ status: 'not_included', reason: 'model' }` and clears the replacement.
196
199
  Before success, `addKey` waits for catalog registration and prepares the new key agent's full-catalog owner once.
197
- For a live-catalog provider, a chosen model must also be in the engine's registered `models.providers[provider].models`
200
+ A chosen model must also be in the engine's registered `models.providers[provider].models`
198
201
  list before anything is sealed; otherwise the add clears the attempt and returns `{ status: 'retry', reason: 'model' }`.
199
202
  Preparation failure clears the attempted key and returns `{ status: 'retry', reason: 'engine' }`; ordinary runs stay unchanged.
200
203
  With no choice, today's starter stays.
package/dist/bridge.js CHANGED
@@ -3,7 +3,6 @@ import { randomBytes } from 'node:crypto';
3
3
  import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
4
  import { createServer } from 'node:net';
5
5
  import { rm } from 'node:fs/promises';
6
- import { provider } from '@byokit/accounts';
7
6
  import { words } from './words.js';
8
7
  import { HelperError } from './helpers.js';
9
8
  /** Newline-framed JSON, one request per connection; anything larger is not a gate request. */
@@ -40,8 +39,9 @@ export function writePlugin(dir, o) {
40
39
  name: 'BYOKit bridge',
41
40
  activation: { onStartup: true },
42
41
  cliBackends: ['openai-codex'],
43
- // The Codex CLI route lists the ChatGPT catalogue's models; the engine lists a plugin route's rows only from here.
44
- modelCatalog: { providers: { 'openai-codex': { models: [provider('chatgpt').models.strong, provider('chatgpt').models.fast].map((id) => ({ id, name: id })) } }, discovery: { 'openai-codex': 'static' } },
42
+ // The engine lists a plugin route's rows only from here. Named exception: the Codex CLI's own list (its app-server
43
+ // `model/list`) is not read yet, so the route names these two ids; a recorded real `model/list` reply lifts it.
44
+ modelCatalog: { providers: { 'openai-codex': { models: ['gpt-6-sol', 'gpt-6-luna'].map((id) => ({ id, name: id })) } }, discovery: { 'openai-codex': 'static' } },
45
45
  contracts: { tools: o.tools.map((t) => t.name), ...(o.browser ? { agentToolResultMiddleware: ['openclaw', 'codex'] } : {}) },
46
46
  configSchema: { type: 'object', additionalProperties: false },
47
47
  };
@@ -1,6 +1,4 @@
1
1
  export declare const ENGINE_VERSION = "2026.8.35";
2
2
  export declare const PROTOCOL_VERSION = 4;
3
- export declare const LIVE_CATALOGS: string[];
4
3
  export declare const REGISTERED_CATALOGS: string[];
5
- export declare const OPEN_CATALOGS: string[];
6
4
  export declare const OPERATOR_SCOPES: readonly ['operator.read', 'operator.write', 'operator.admin', 'operator.approvals', 'operator.questions', 'operator.pairing', 'operator.talk'];
package/dist/constants.js CHANGED
@@ -2,15 +2,9 @@
2
2
  // `needs-update`, never silently run.
3
3
  export const ENGINE_VERSION = '2026.8.35';
4
4
  export const PROTOCOL_VERSION = 4;
5
- // Providers whose live catalog lists and checks a key's chosen model (5.6), through the catalog hook. OpenRouter is
6
- // pay-as-you-go: its key takes any model its own catalog lists, with no plan to include it.
7
- export const LIVE_CATALOGS = ['opencode-go', 'openrouter', 'minimax'];
8
- // Live catalogs the kit registers whole. The engine canonicalizes Google preview ids on config write, so OpenRouter's
5
+ // Live catalogs (5.6) the kit registers whole; every other key route registers its chosen model alone. The engine canonicalizes Google preview ids on config write, so OpenRouter's
9
6
  // list collides with itself (`google/x-preview` and `google/x`); an OpenRouter add registers only the chosen model.
10
7
  export const REGISTERED_CATALOGS = ['opencode-go', 'minimax'];
11
- // Pay-as-you-go catalogs whose vendor list can lag a model that already runs: an id the list lacks is warned about, never
12
- // refused, and takes the capabilities of the route's own models.
13
- export const OPEN_CATALOGS = ['minimax'];
14
8
  export const OPERATOR_SCOPES = [
15
9
  'operator.read',
16
10
  'operator.write',
package/dist/keys.js CHANGED
@@ -5,7 +5,7 @@ import { activate } from './activation.js';
5
5
  import { keyAgentId, MEMBER_ID, KEY_PREFIX, untilListed } from './members.js';
6
6
  import { providerOf } from './pick.js';
7
7
  import { kitId, modelsOf } from './accounts.js';
8
- import { LIVE_CATALOGS, OPEN_CATALOGS, REGISTERED_CATALOGS } from './constants.js';
8
+ import { REGISTERED_CATALOGS } from './constants.js';
9
9
  export function createKeys(ctx) {
10
10
  // Serialize key replacement and key runs, so an in-flight run never sees a half-installed replacement.
11
11
  const tails = new Map();
@@ -96,7 +96,7 @@ export function createKeys(ctx) {
96
96
  // The vendor's list can lag a model that runs: an id it lacks rides the route's own models (the richest row's api,
97
97
  // base URL and context, every input modality any row takes), unpriced. Nothing is added to an empty list.
98
98
  const unlisted = (provider, rows, model) => {
99
- if (!rows.length || rows.some((m) => kitId(provider, String(m.id)) === model))
99
+ if (!rows.length || !model.startsWith(`${provider}/`) || rows.some((m) => kitId(provider, String(m.id)) === model))
100
100
  return [];
101
101
  const base = rows.reduce((a, b) => (Number(b.contextWindow) || 0) > (Number(a.contextWindow) || 0) ? b : a);
102
102
  const input = ['text', 'image'].filter((i) => rows.some((m) => Array.isArray(m.input) && m.input.includes(i)));
@@ -108,7 +108,7 @@ export function createKeys(ctx) {
108
108
  if (!check(member))
109
109
  throw new Error('Choose your own member.');
110
110
  const route = keyRoute(o.authChoice);
111
- if (route.readiness !== 'ready' || !LIVE_CATALOGS.includes(route.provider))
111
+ if (route.readiness !== 'ready')
112
112
  return [];
113
113
  try {
114
114
  return listed(route.provider, await catalog(member, route.provider));
@@ -138,7 +138,7 @@ export function createKeys(ctx) {
138
138
  if (!o.apiKey.trim())
139
139
  return failure('invalid', 'empty');
140
140
  const route = keyRoute(o.authChoice);
141
- if (route.readiness !== 'ready' || (o.model !== undefined && !LIVE_CATALOGS.includes(route.provider)))
141
+ if (route.readiness !== 'ready')
142
142
  return failure('not_included', 'model');
143
143
  const model = o.model === undefined ? undefined : canonicalModel(o.model);
144
144
  // A workspace names a Go login's own workspace only; another route takes no header, and a
@@ -227,7 +227,7 @@ export function createKeys(ctx) {
227
227
  if (result.late)
228
228
  return abandon(failure('retry', 'busy'));
229
229
  const rows = model === undefined ? [] : await catalog(member, route.provider);
230
- const lagging = model !== undefined && OPEN_CATALOGS.includes(route.provider) ? unlisted(route.provider, rows, model) : [];
230
+ const lagging = model !== undefined ? unlisted(route.provider, rows, model) : [];
231
231
  rows.push(...lagging);
232
232
  if (model !== undefined && !listed(route.provider, rows).some((m) => m.id === model && m.available))
233
233
  return abandon(failure('not_included', 'model'));
@@ -240,10 +240,10 @@ export function createKeys(ctx) {
240
240
  }
241
241
  if (ac.signal.aborted)
242
242
  return abandon(cancelled());
243
- // A live catalog the kit does not register whole (OpenRouter) registers the chosen model alone.
243
+ // A catalog the kit does not register whole (OpenRouter's, and any other provider's) registers the chosen model alone.
244
244
  if (model !== undefined && (!REGISTERED_CATALOGS.includes(route.provider) || lagging.length))
245
245
  await ctx.registerModels(route.provider, rows.filter((m) => kitId(route.provider, String(m.id)) === model)).catch(() => { });
246
- if (model !== undefined && LIVE_CATALOGS.includes(route.provider) && !(await registered(route.provider, model)))
246
+ if (model !== undefined && !(await registered(route.provider, model)))
247
247
  return abandon(failure('retry', 'model'));
248
248
  if (!(await control(member, 'seal', undefined, model)).ok)
249
249
  return abandon(failure('retry', 'engine'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byokit/openclaw",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "The OpenClaw runtime kit: the pinned engine's full operator surface with plain-words helpers for members, sign-in, runs and approvals.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -60,7 +60,7 @@
60
60
  "prepack": "tsc -b && node ../../scripts/fix-words-dts.cjs"
61
61
  },
62
62
  "dependencies": {
63
- "@byokit/accounts": "0.29.0",
63
+ "@byokit/accounts": "0.30.0",
64
64
  "@byokit/pair": "0.10.4",
65
65
  "@byokit/relay": "0.8.2",
66
66
  "@byokit/discover": "0.8.4",
package/plugin/catalog.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // byokit.catalog (5.6 live provider catalogs): the provider plugin's own live model list, with each model's api and
2
- // baseUrl from its catalog data, so the kit can register models embedded runs cannot resolve otherwise. The engine's
3
- // catalog loader stops at a registered list; the provider's catalog hook does not.
2
+ // baseUrl from its catalog data, joined by the vendor's own model list, so the kit can register models embedded runs
3
+ // cannot resolve otherwise. The engine's catalog loader stops at a registered list; the provider's catalog hook does not.
4
4
  export function registerCatalog(api) {
5
5
  api.registerGatewayMethod('byokit.catalog', async ({ params, respond }) => {
6
6
  try {
@@ -11,7 +11,8 @@ export function registerCatalog(api) {
11
11
  const { resolveApiKeyForProvider } = await import('openclaw/plugin-sdk/provider-auth-runtime');
12
12
  const config = getRuntimeConfig();
13
13
  const hook = resolvePluginProviders({ config, env: process.env }).find((p) => p.id === provider)?.catalog;
14
- if (!provider || typeof hook?.run !== 'function') return respond(true, { ok: false, reason: 'failed' });
14
+ if (!provider) return respond(true, { ok: false, reason: 'failed' });
15
+ if (typeof hook?.run !== 'function') return respond(true, { ok: true, models: [] }); // a provider with no catalog lists nothing
15
16
  // The first agent holding the provider's key; every account of one provider sees the same catalog.
16
17
  let unreadable = false;
17
18
  for (const agentId of new Set([...Object.keys(config.agents?.entries ?? {}), 'main'])) {
@@ -38,13 +39,16 @@ export function registerCatalog(api) {
38
39
  const models = [...live.models, ...(pinned?.models ?? []).filter((m) => !liveIds.has(m.id))]
39
40
  .map(({ provider: _p, apiKey: _k, headers: _h, ...model }) =>
40
41
  (endpoint.has(model.api) ? { ...model, baseUrl: endpoint.get(model.api) } : model));
41
- // MiniMax's engine catalog is a fixed list: its vendor `/models` lists newer ids, which join it with the richest
42
- // row's api, base URL and capabilities (the vendor lists ids only). A failed fetch keeps the engine's list.
43
- if (provider === 'minimax' && models.length) {
44
- const base = models.reduce((a, b) => (b.contextWindow ?? 0) > (a.contextWindow ?? 0) ? b : a);
42
+ // A plugin catalog can be a fixed list (MiniMax's): the vendor's own `/models` lists newer ids, which join it with
43
+ // the richest row's api, base URL and capabilities (a vendor list carries ids only). Same rule as
44
+ // @byokit/accounts' live lists; another api, or a failed fetch, keeps the plugin's rows.
45
+ const base = models.reduce((a, b) => (b.contextWindow ?? 0) > (a.contextWindow ?? 0) ? b : a);
46
+ const openai = String(base.api).startsWith('openai-');
47
+ if (base.baseUrl && (openai || base.api === 'anthropic-messages')) {
45
48
  const input = ['text', 'image'].filter((i) => models.some((m) => m.input?.includes(i)));
46
- const listed = await fetch(`${String(base.baseUrl).replace(/\/$/, '')}/v1/models`,
47
- { headers: { authorization: `Bearer ${apiKey}`, 'anthropic-version': '2023-06-01' }, signal: AbortSignal.timeout(10_000) })
49
+ const bearer = openai || /^minimax/.test(provider);
50
+ const listed = await fetch(`${String(base.baseUrl).replace(/\/$/, '')}${openai ? '/models' : '/v1/models'}`, { signal: AbortSignal.timeout(10_000),
51
+ headers: bearer ? { authorization: `Bearer ${apiKey}`, 'anthropic-version': '2023-06-01' } : { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' } })
48
52
  .then((r) => r.ok ? r.json() : null, () => null);
49
53
  const have = new Set(models.map((m) => m.id));
50
54
  for (const m of Array.isArray(listed?.data) ? listed.data : [])