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 +26 -8
- package/extensions/bob.ts +12 -7
- package/package.json +1 -1
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
|
-
|
|
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.
|
|
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
|
|
162
|
+
### Verified against Bob Shell 2.0.1 (2026-08-31)
|
|
149
163
|
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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)
|
|
345
|
+
if (!isRecord(entry) || !isRecord(entry.model_info)) continue;
|
|
346
346
|
const id = safeCatalogLabel(entry.model_name);
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
});
|