pi-bob 0.2.1 → 0.2.2

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
@@ -4,9 +4,21 @@ Pi provider package for IBM Bob / IBM-approved enterprise model endpoints.
4
4
 
5
5
  The package registers Bob through Pi's built-in compatible provider APIs and discovers the currently exposed model catalog from Bob's authenticated `/inference/v1/model/info` endpoint. It does not scrape Bob, extract browser/session credentials, or bypass IBM-approved access paths.
6
6
 
7
+ > **✅ Works with IBM Bob Shell / Bob CLI 2.x (verified against `bobshell@2.0.1`, 2026-08-31).**
8
+ > SSO login, token refresh, and live model discovery all work against Bob 2.0 — 13 models discovered, including `premium`, `premium-ide`, `ultra`, `fast`, and `wxO-model` (1M ctx). One caveat: under 2.x, Bob only accepts the default **`openai-completions`** adapter; `anthropic-messages` and `openai-responses` are rejected with 403 by Bob-side entitlement. 1.x Bob Shell installations keep working — the parser accepts both payload shapes. See [Discovered local Bob Shell settings](#discovered-local-bob-shell-settings) for the full 2.x change list.
9
+
7
10
  ## Discovered local Bob Shell settings
8
11
 
9
- From the installed `bobshell@1.0.6` package and the local redacted Bob configuration:
12
+ Updated for the installed `bobshell@2.0.1` package (upgraded from 1.0.6; findings verified against the live endpoint on 2026-08-31):
13
+
14
+ - Bob Shell 2.x moved stored SSO tokens to `~/.bob/settings/auth-secrets.json` (key `bob.auth.tokens-<gateway>`); `~/.bob/settings.json` keeps only non-secret settings. This extension still reads only `ibm.instanceId`/`ibm.teamId` from there and still ignores Bob's secrets.
15
+ - Token endpoints are unchanged in practice: 2.x builds `${gateway}/authn` + `v1/auth/token|refresh`, which resolves to the same `/authn/v1/auth/token` and `/authn/v1/auth/refresh` routes used here. The web login URL is now assembled as `https://bob.ibm.com` + `/login`; the final URL is the same as the 1.x constant.
16
+ - **Breaking:** 2.x `model/info` entries dropped `litellm_params` (the backend identifier) and now carry `max_output_tokens` and `supports_prompt_caching` alongside `max_tokens`. The parser accepts both shapes and falls back to the route name when the backend is missing.
17
+ - **Breaking:** 2.x `model/info` requires `x-instance-id` (HTTP 400 without it); `x-team-id` stays optional. The extension already sends both from Bob settings or the JWT `instances` claim.
18
+ - Model tiers were renamed: 1.x `premium`/`pro`/`flash`/`flash-lite` became 2.x tiers `fast`/`premium`/`ultra` (`explorer` exists but is hidden). Live catalog also exposes `premium-ide`, `premium-shell`, `sonnet-4.5`, `wxO-model` (1M ctx / 64k out), `gpt-oss-20b`, and granite/rnj code models. The `premium` alias still exists, so the fallback catalog stays valid.
19
+ - **Breaking (entitlement):** under 2.0, the `anthropic-messages` and `openai-responses` routes return `403 Access denied` with a valid token (routes exist; access is denied for this instance). Only the default `openai-completions` adapter works; treat the alternates as unavailable until IBM grants access.
20
+
21
+ From the installed `bobshell@1.0.6` package and the local redacted Bob configuration (historical):
10
22
 
11
23
  - Bob Shell CLI: `bob`
12
24
  - Bob Shell package: `bobshell`
@@ -36,6 +48,8 @@ Set `IBM_BOB_API` to one of Pi's compatible API adapters:
36
48
  - `openai-responses` — OpenAI Responses-compatible routes.
37
49
  - `anthropic-messages` — Anthropic Messages-compatible routes.
38
50
 
51
+ Under Bob Shell 2.x, Bob currently accepts only the default `openai-completions` adapter; the other two return `403 Access denied` regardless of extension settings (verified 2026-08-31).
52
+
39
53
  The extension registers provider id `ibm-bob`. It uses an isolated `ibm-bob-compatible` Pi API adapter internally, then delegates serialization and streaming to the selected built-in adapter. This prevents Bob-specific authentication rules from affecting other providers.
40
54
 
41
55
  ## Dynamic model discovery
@@ -109,7 +123,7 @@ For SSO, do **not** copy a token out of Bob's local credential store unless IBM
109
123
  | `IBM_BOB_READ_BOBSHELL_SETTINGS` | `true` | Read non-secret `instanceId`/`teamId` from `~/.bob/settings.json`. |
110
124
  | `IBM_BOB_INSTANCE_ID` | Bob setting | Override `x-instance-id`. |
111
125
  | `IBM_BOB_TEAM_ID` | Bob setting | Override `x-team-id`. |
112
- | `IBM_BOB_USER_AGENT` | `pi-bob/0.2.0` | User-Agent header sent to Bob endpoint. |
126
+ | `IBM_BOB_USER_AGENT` | `pi-bob/0.2.2` | User-Agent header sent to Bob endpoint. |
113
127
 
114
128
  ### Auth headers
115
129
 
@@ -145,18 +159,20 @@ It also rejects OpenAI-specific `store` and `prompt_cache_key` request propertie
145
159
 
146
160
  ## Validation performed
147
161
 
148
- Bob Shell SSO itself works locally:
162
+ ### Verified against Bob Shell 2.0.1 (2026-08-31)
149
163
 
150
- ```bash
151
- bob --auth-method sso -m premium -p 'Reply with exactly: bob-ok' --hide-intermediary-output --output-format json
152
- ```
164
+ - Live discovery through Pi: `pi --list-models` shows all 13 catalog models (`premium`, `premium-ide`, `premium-shell`, `ultra`, `fast`, `explorer`, `sonnet-4.5`, `wxO-model`, `gpt-oss-20b`, granite/rnj) from any directory, using stored SSO credentials.
165
+ - End-to-end inference through `openai-completions`: `premium`, `ultra`, and `premium-shell` all responded correctly to exact-reply smokes.
166
+ - SSO refresh on `/authn/v1/auth/refresh` returned `200` with a rotated refresh token; the refreshed token immediately passed `model/info`.
167
+ - `anthropic-messages` and `openai-responses` routes: `403 Access denied` with a valid token — Bob-side entitlement change, not an extension defect.
168
+ - `bun test`: 33 tests pass, including a fixture built from an observed 2.x `model/info` payload (no `litellm_params`, `max_output_tokens`, `supports_prompt_caching`).
153
169
 
154
- The response included `bob-ok` and successful usage stats for model `premium`.
170
+ ### Earlier validation (Bob Shell 1.x era)
155
171
 
156
172
  The unauthenticated Bob model-info endpoint responds as expected when called with a normal User-Agent:
157
173
 
158
174
  ```bash
159
- curl -H 'User-Agent: pi-bob/0.1.0' \
175
+ curl -H 'User-Agent: pi-bob/0.2.2' \
160
176
  https://api.us-east.bob.ibm.com/inference/v1/model/info
161
177
  ```
162
178
 
@@ -209,6 +225,8 @@ Run Pi with `PI_OFFLINE=1` to skip Pi's network catalog refresh entirely; the
209
225
  login then returns to the prompt immediately after SSO, and ibm-bob's own
210
226
  model discovery still runs.
211
227
 
228
+ **`/model` shows only `premium` under `ibm-bob`.** The stored credentials carry no cached model catalog — typical for credentials created before this version, or after Bob changes its catalog shape. Run `/login ibm-bob` once: login always rediscovers the catalog and re-attaches it to the stored credentials, and every later startup reuses it. Do **not** hand-edit `~/.pi/agent/auth.json` to add the catalog; Pi's auth store manages that file and may silently drop hand-written fields.
229
+
212
230
  **Debugging the login flow.** Run with `IBM_BOB_DEBUG=1` and stderr
213
231
  redirected to a file to see per-step timing for the SSO callback, token
214
232
  exchange, and model discovery:
package/extensions/bob.ts CHANGED
@@ -239,7 +239,7 @@ function parseJsonHeaders(): Record<string, string> | undefined {
239
239
 
240
240
  function buildHeaders(settings?: BobShellSettings): Record<string, string> | undefined {
241
241
  const headers: Record<string, string> = {
242
- "User-Agent": env("IBM_BOB_USER_AGENT") ?? "pi-bob/0.2.0",
242
+ "User-Agent": env("IBM_BOB_USER_AGENT") ?? "pi-bob/0.2.2",
243
243
  };
244
244
 
245
245
  const instanceId = env("IBM_BOB_INSTANCE_ID") ?? settings?.ibm?.instanceId;
@@ -342,10 +342,12 @@ export function parseBobModelCatalog(payload: unknown): BobDiscoveredModel[] {
342
342
  const seen = new Set<string>();
343
343
  let structurallyValidEntries = 0;
344
344
  for (const entry of payload.data) {
345
- if (!isRecord(entry) || !isRecord(entry.model_info) || !isRecord(entry.litellm_params)) continue;
345
+ if (!isRecord(entry) || !isRecord(entry.model_info)) continue;
346
346
  const id = safeCatalogLabel(entry.model_name);
347
- const backend = safeCatalogLabel(entry.litellm_params.model);
348
- if (!id || !backend) continue;
347
+ if (!id) continue;
348
+ // bobshell 2.x model/info entries dropped litellm_params; fall back to the
349
+ // route name itself so discovery still works against 2.x payloads.
350
+ const backend = safeCatalogLabel((entry.litellm_params as Record<string, unknown> | undefined)?.model) || id;
349
351
  const exposed = entry.model_info.exposed;
350
352
  if (exposed !== undefined && typeof exposed !== "boolean") continue;
351
353
  structurallyValidEntries++;
@@ -363,7 +365,10 @@ export function parseBobModelCatalog(payload: unknown): BobDiscoveredModel[] {
363
365
  entry.model_info.supports_thinking === true,
364
366
  supportsVision: entry.model_info.supports_vision === true,
365
367
  contextWindow: positiveNumber(entry.model_info.max_input_tokens),
366
- maxTokens: positiveNumber(entry.model_info.max_tokens),
368
+ // bobshell 2.x adds max_output_tokens; keep the conservative 1.x
369
+ // max_tokens precedence and only use it as a fallback.
370
+ maxTokens:
371
+ positiveNumber(entry.model_info.max_tokens) ?? positiveNumber(entry.model_info.max_output_tokens),
367
372
  cost: {
368
373
  input: perMillionCost(entry.model_info.input_cost_per_token),
369
374
  output: perMillionCost(entry.model_info.output_cost_per_token),
@@ -508,7 +513,7 @@ async function fetchBobModelCatalog(
508
513
  const headers: Record<string, string> = {
509
514
  Accept: "application/json",
510
515
  Authorization: `${authScheme} ${accessToken}`,
511
- "User-Agent": env("IBM_BOB_USER_AGENT") ?? "pi-bob/0.2.0",
516
+ "User-Agent": env("IBM_BOB_USER_AGENT") ?? "pi-bob/0.2.2",
512
517
  };
513
518
  const instanceId =
514
519
  env("IBM_BOB_INSTANCE_ID") ?? settings?.ibm?.instanceId ?? readInstanceFromJwt(accessToken);
@@ -777,7 +782,7 @@ async function postToken(path: "/authn/v1/auth/token" | "/authn/v1/auth/refresh"
777
782
  try {
778
783
  const response = await fetch(`${bobOrigin()}${path}`, {
779
784
  method: "POST",
780
- headers: { "Content-Type": "application/json", "User-Agent": env("IBM_BOB_USER_AGENT") ?? "pi-bob/0.2.0" },
785
+ headers: { "Content-Type": "application/json", "User-Agent": env("IBM_BOB_USER_AGENT") ?? "pi-bob/0.2.2" },
781
786
  body: JSON.stringify(body),
782
787
  signal: controller.signal,
783
788
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-bob",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "description": "Pi provider extension for IBM Bob / IBM-approved compatible model endpoints.",
6
6
  "keywords": [