pi-codebuddy-sdk 0.4.36 → 0.4.37

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
@@ -103,6 +103,8 @@ File: `~/.pi/agent/codebuddy-sdk.json` or `.pi/codebuddy-sdk.json` in a project.
103
103
  | `provider.strictMcpConfig` | `true` | Use only Pi-bridged MCP tools |
104
104
  | `provider.pathToCodebuddyCode` | auto | Path to the `codebuddy` **CLI entry point** when it is **not** on `PATH`; does not change the JS runtime |
105
105
  | `provider.executable` | host runtime | Runtime used to spawn the CLI (e.g. `"node"`). Set it when the host is Bun-compiled (no `bun` on `PATH`); auto-detected otherwise |
106
+ | `provider.isolatedEnv` | `false` | Opt in to a private CodeBuddy config dir with user, auto, and team memory disabled |
107
+ | `provider.codebuddyConfigDir` | `~/.codebuddy-cli-isolated` | Config dir used when `isolatedEnv` is enabled |
106
108
  | `provider.contextWindow` | estimated | Override context window (tokens) for **all** models (e.g. `300000`) |
107
109
  | `provider.maxTokens` | estimated | Override max output tokens for **all** models (e.g. `32768`) |
108
110
  | `provider.modelOverrides` | — | Per-model overrides, keyed by model id or substring (see below) |
@@ -121,7 +123,42 @@ key wins and beats the global `contextWindow` / `maxTokens`:
121
123
  }
122
124
  ```
123
125
 
124
- Supported per-model keys: `contextWindow`, `maxTokens`, `reasoning`, `images`.
126
+ Supported per-model keys: `contextWindow`, `maxTokens`, `reasoning`, `images`,
127
+ `thinkingLevelMap`.
128
+
129
+ `thinkingLevelMap` maps a pi thinking level to the effort string sent to the
130
+ CodeBuddy CLI. pi only offers `xhigh` and `max` when a model declares them here
131
+ (pi-ai `getSupportedThinkingLevels`), so a model that supports `max` upstream
132
+ stays unreachable until the map names it:
133
+
134
+ ```json
135
+ {
136
+ "provider": {
137
+ "modelOverrides": {
138
+ "deepseek-v4.1-flash": { "thinkingLevelMap": { "max": "max" } },
139
+ "space-bunny": { "thinkingLevelMap": { "xhigh": "xhigh", "max": "max" } }
140
+ }
141
+ }
142
+ }
143
+ ```
144
+
145
+ Declare only the habits the model actually serves — an effort the backend
146
+ rejects fails the turn.
147
+
148
+ Set `provider.isolatedEnv` to keep Pi turns away from the CodeBuddy IDE's rules,
149
+ projects, and memory. The extension sends both the CLI subprocess and its own
150
+ session files to one config dir:
151
+
152
+ ```json
153
+ {
154
+ "provider": {
155
+ "isolatedEnv": true,
156
+ "codebuddyConfigDir": "/path/to/private-codebuddy-config"
157
+ }
158
+ }
159
+ ```
160
+
161
+ The first run against a new dir needs the CodeBuddy CLI login for that dir.
125
162
 
126
163
  Window values the backend reports for a model are remembered per model id in
127
164
  `~/.pi/agent/codebuddy-sdk-learned-windows.json`, so the next start registers the
@@ -134,7 +171,7 @@ again (ids the backend no longer serves are dropped automatically).
134
171
  - The extension does **not** send conversation data to this repository or any third-party telemetry endpoint.
135
172
  - Credentials are handled entirely by the CodeBuddy CLI on your machine.
136
173
  - Optional debug mode (`CODEBUDDY_SDK_DEBUG=1`) writes **local** logs under `~/.pi/agent/`; paths are redacted, prompts and tool payloads are not logged. Delete logs when finished.
137
- - CodeBuddy loads project instructions, `~/.codebuddy/rules/`, and project memory independently of `--setting-sources`; those files can add context that is not part of the Pi session.
174
+ - CodeBuddy loads project instructions, rules, and project memory independently of `--setting-sources`; those files can add context that is not part of the Pi session. Set `provider.isolatedEnv` to use a private config dir with memory disabled.
138
175
 
139
176
  ## Troubleshooting
140
177
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-codebuddy-sdk",
3
- "version": "0.4.36",
3
+ "version": "0.4.37",
4
4
  "description": "Pi extension that uses CodeBuddy (via Agent SDK) as a model provider and adds an AskCodebuddy tool.",
5
5
  "author": "RealAlexandreAI",
6
6
  "license": "MIT",
@@ -34,7 +34,7 @@
34
34
  "typecheck": "tsc --noEmit"
35
35
  },
36
36
  "dependencies": {
37
- "@tencent-ai/agent-sdk": "^0.3.279",
37
+ "@tencent-ai/agent-sdk": "^0.3.291",
38
38
  "change-case": "^5.4.4"
39
39
  },
40
40
  "peerDependencies": {
@@ -52,6 +52,9 @@
52
52
  "typebox": "1.4.3",
53
53
  "typescript": "^7.0.2"
54
54
  },
55
+ "overrides": {
56
+ "undici": "^8.12.3"
57
+ },
55
58
  "pi": {
56
59
  "extensions": [
57
60
  "./src/index.ts"
package/src/config.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { SettingSource } from "./index-types.js";
2
+ import type { ModelOverrides } from "./models.js";
2
3
  import { CONFIG_DIR_NAME } from "@earendil-works/pi-coding-agent";
3
4
  import { existsSync, readFileSync } from "fs";
4
5
  import { homedir } from "os";
@@ -32,17 +33,37 @@ export interface Config {
32
33
  * host runtime; set this when a Bun-compiled host has no `bun` on `PATH`.
33
34
  */
34
35
  executable?: string;
36
+ /** Run the CLI against a private CodeBuddy config dir and disable its memory. */
37
+ isolatedEnv?: boolean;
38
+ /** Config dir used when `isolatedEnv` is on. Defaults to `~/.codebuddy-cli-isolated`. */
39
+ codebuddyConfigDir?: string;
35
40
  /** Override the estimated context window (tokens) for ALL models. */
36
41
  contextWindow?: number;
37
42
  /** Override the estimated max output tokens for ALL models. */
38
43
  maxTokens?: number;
39
44
  /** Per-model overrides keyed by model id (or id substring). */
40
- modelOverrides?: Record<string, {
41
- contextWindow?: number;
42
- maxTokens?: number;
43
- reasoning?: boolean;
44
- images?: boolean;
45
- }>;
45
+ modelOverrides?: Record<string, ModelOverrides>;
46
+ };
47
+ }
48
+
49
+ export function resolveCodebuddyConfigDir(provider?: Config["provider"]): string | undefined {
50
+ if (provider?.isolatedEnv !== true) return undefined;
51
+ return provider.codebuddyConfigDir ?? join(homedir(), ".codebuddy-cli-isolated");
52
+ }
53
+
54
+ export function buildCodebuddyEnv(
55
+ provider?: Config["provider"],
56
+ base: NodeJS.ProcessEnv = process.env,
57
+ ): NodeJS.ProcessEnv {
58
+ const env = { ...base, DISABLE_AUTO_COMPACT: "1" };
59
+ const dir = resolveCodebuddyConfigDir(provider);
60
+ if (!dir) return env;
61
+ return {
62
+ ...env,
63
+ CODEBUDDY_CONFIG_DIR: dir,
64
+ CODEBUDDY_MEMORY_ENABLED: "0",
65
+ CODEBUDDY_DISABLE_AUTO_MEMORY: "1",
66
+ CODEBUDDY_TEAM_MEMORY_ENABLED: "0",
46
67
  };
47
68
  }
48
69
 
package/src/index.ts CHANGED
@@ -20,7 +20,7 @@ import { MCP_SERVER_NAME, MCP_TOOL_PREFIX, buildCodebuddySystemPrompt } from "./
20
20
  import { verifyWrittenSession as _verifyWrittenSession } from "./session-verify.js";
21
21
  import { extractAllToolResults as _extractAllToolResults, type McpResult } from "./extract-tool-results.js";
22
22
  import { QueryContext, ctx } from "./query-state.js";
23
- import { loadConfig, type Config } from "./config.js";
23
+ import { buildCodebuddyEnv, loadConfig, resolveCodebuddyConfigDir, type Config } from "./config.js";
24
24
  import { jsonSchemaToZodShape } from "./typebox-to-zod.js";
25
25
  import { buildActionSummary, type ToolCallState } from "./askcodebuddy-ui.js";
26
26
  import { withSdkGate } from "./sdk-gate.js";
@@ -207,10 +207,6 @@ type ContentBlockParam =
207
207
  | { type: "image"; source: { type: "base64"; media_type: string; data: string } };
208
208
  type SettingSource = "user" | "project" | "local";
209
209
 
210
- function resolveModel(input: string) {
211
- return _resolveModel(MODELS, input);
212
- }
213
-
214
210
  function registerCodebuddyProvider(pi: ExtensionAPI, globals: Record<symbol, any> = globalThis as Record<symbol, any>): void {
215
211
  const { streamFn, claimStream } = providerRegistration(globals, streamCodebuddySdk, boundRuntime);
216
212
  pi.registerProvider(PROVIDER_ID, {
@@ -447,7 +443,7 @@ async function runIsolatedSummary(
447
443
  prompt: promptText,
448
444
  options: {
449
445
  cwd,
450
- env: { ...process.env, DISABLE_AUTO_COMPACT: "1" },
446
+ env: buildCodebuddyEnv(loadConfig(cwd).provider),
451
447
  tools: [],
452
448
  strictMcpConfig: true,
453
449
  settingSources: [] as SettingSource[],
@@ -562,7 +558,7 @@ function verifyWrittenSession(
562
558
  `. Report: ${ISSUES_URL}`,
563
559
  "warning",
564
560
  );
565
- diagDump("session_verify_fail", { msg, jsonlPath, cwd, realpath: safeRealpath(cwd), codebuddyConfigDir: process.env.CODEBUDDY_CONFIG_DIR ?? null });
561
+ diagDump("session_verify_fail", { msg, jsonlPath, cwd, realpath: safeRealpath(cwd), codebuddyConfigDir: resolveCodebuddyConfigDir(providerSettings) ?? null });
566
562
  }
567
563
  }
568
564
 
@@ -691,11 +687,11 @@ function syncSharedSession(
691
687
  const preserveId = previousSessionId !== undefined && !sharedSession?.forceRotate;
692
688
  if (preserveId) {
693
689
  // Wipe prior jsonl + companion dir (no-op if nothing to wipe).
694
- deleteSession(previousSessionId!, cwd, process.env.CODEBUDDY_CONFIG_DIR);
690
+ deleteSession(previousSessionId!, cwd, resolveCodebuddyConfigDir(providerSettings));
695
691
  }
696
692
  const session = createSession({
697
693
  projectPath: cwd,
698
- codebuddyDir: process.env.CODEBUDDY_CONFIG_DIR,
694
+ codebuddyDir: resolveCodebuddyConfigDir(providerSettings),
699
695
  ...(preserveId ? { sessionId: previousSessionId } : {}),
700
696
  ...(modelId ? { model: modelId } : {}),
701
697
  });
@@ -727,7 +723,11 @@ export const __test = {
727
723
  getSharedSession() {
728
724
  return sharedSession;
729
725
  },
726
+ setProviderSettings(settings: NonNullable<Config["provider"]>) {
727
+ providerSettings = settings;
728
+ },
730
729
  syncSharedSession,
730
+ promptAndWait,
731
731
  };
732
732
 
733
733
  // --- Provider helpers: tool name mapping ---
@@ -910,7 +910,7 @@ function syncServedContextWindow(label: string, message: CbMessage, model: Model
910
910
  // Pi reasoning levels → CC SDK effort levels
911
911
 
912
912
  const REASONING_TO_EFFORT: Record<string, Effort> = {
913
- minimal: "low", low: "low", medium: "medium", high: "high", xhigh: "xhigh",
913
+ minimal: "low", low: "low", medium: "medium", high: "high", xhigh: "xhigh", max: "max",
914
914
  };
915
915
 
916
916
  // --- Provider helpers: misc ---
@@ -1399,7 +1399,7 @@ function streamCodebuddySdk(model: Model<any>, context: Context, options?: Simpl
1399
1399
  const extraArgs: Record<string, string | null> = { model: cliModel };
1400
1400
  if (strictMcpConfigEnabled) extraArgs["strict-mcp-config"] = null;
1401
1401
 
1402
- const childEnv = { ...process.env, DISABLE_AUTO_COMPACT: "1" };
1402
+ const childEnv = buildCodebuddyEnv(providerSettings);
1403
1403
  const queryOptions: NonNullable<Parameters<typeof query>[0]["options"]> = {
1404
1404
  cwd,
1405
1405
  env: childEnv,
@@ -1472,7 +1472,7 @@ function streamCodebuddySdk(model: Model<any>, context: Context, options?: Simpl
1472
1472
  const sessionId = capturedSessionId ?? sharedSession?.sessionId;
1473
1473
  if (syncResult.preserveSharedSession) {
1474
1474
  if (capturedSessionId && capturedSessionId !== sharedSession?.sessionId) {
1475
- deleteSession(capturedSessionId, cwd, process.env.CODEBUDDY_CONFIG_DIR);
1475
+ deleteSession(capturedSessionId, cwd, resolveCodebuddyConfigDir(providerSettings));
1476
1476
  debug(`provider: query done, deleted ephemeral session ${capturedSessionId.slice(0, 8)} to preserve shared session`);
1477
1477
  }
1478
1478
  debug(`provider: query done, ignoring captured session ${capturedSessionId?.slice(0, 8) ?? "none"} to preserve shared session`);
@@ -1571,12 +1571,12 @@ async function promptAndWait(
1571
1571
  isolated?: boolean;
1572
1572
  context?: Context["messages"];
1573
1573
  },
1574
+ runQuery: typeof startQuery = startQuery,
1574
1575
  ): Promise<{ responseText: string; stopReason: string }> {
1575
1576
  const cwd = process.cwd();
1576
- const requestedModel = options?.model ?? "opus";
1577
- const model = resolveModel(requestedModel);
1577
+ const requestedModel = options?.model;
1578
+ const model = requestedModel === undefined ? undefined : _resolveModel(MODELS, requestedModel);
1578
1579
  const modelId = model?.id ?? requestedModel;
1579
- const cliModel = model ? codebuddyModelId(model) : modelId;
1580
1580
 
1581
1581
  // Session resume for shared mode — reuse provider's session if it exists,
1582
1582
  // otherwise create one from pi's context.
@@ -1613,19 +1613,19 @@ async function promptAndWait(
1613
1613
 
1614
1614
  const extraArgs: Record<string, string | null> = {
1615
1615
  "strict-mcp-config": null,
1616
- model: cliModel,
1616
+ ...(modelId !== undefined ? { model: modelId } : {}),
1617
1617
  };
1618
1618
 
1619
1619
  debug("askCodebuddy:",
1620
- `mode=${mode} model=${modelId} cliModel=${cliModel} effort=${effort ?? "default"}`,
1620
+ `mode=${mode} model=${modelId ?? "CLI default"} effort=${effort ?? "default"}`,
1621
1621
  `isolated=${options?.isolated ?? false} resume=${resumeSessionId?.slice(0, 8) ?? "none"}`,
1622
1622
  `sysPrompt=${Boolean(askSystemPrompt)} promptLen=${prompt.length}`);
1623
1623
 
1624
- const sdkQuery = startQuery({
1624
+ const sdkQuery = runQuery({
1625
1625
  prompt,
1626
1626
  options: {
1627
1627
  cwd,
1628
- env: { ...process.env, DISABLE_AUTO_COMPACT: "1" },
1628
+ env: buildCodebuddyEnv(providerSettings),
1629
1629
  permissionMode: "bypassPermissions",
1630
1630
  ...(disallowedTools.length ? { disallowedTools } : {}),
1631
1631
  ...(effort ? { effort } : {}),
@@ -1699,6 +1699,10 @@ async function promptAndWait(
1699
1699
  if (!responseText && message.subtype === "success" && message.result) {
1700
1700
  responseText = message.result;
1701
1701
  }
1702
+ if (r.is_error || message.subtype !== "success") {
1703
+ const failure = [responseText, r.result, ...(r.errors ?? [])].filter(Boolean).join("\n");
1704
+ throw new Error(failure || `CodeBuddy query failed (${message.subtype}).`);
1705
+ }
1702
1706
  break;
1703
1707
  }
1704
1708
  }
@@ -1710,6 +1714,13 @@ async function promptAndWait(
1710
1714
  `sdkMessages=${sdkMessageCount} textDeltas=${textDeltaCount} responseLen=${responseText.length}`,
1711
1715
  `toolCalls=${toolCalls.size}`);
1712
1716
  return { responseText, stopReason };
1717
+ } catch (err) {
1718
+ const message = errorMessage(err);
1719
+ const unavailableModel = /model\s*\[([^\]]+)\]\s*service info not found/i.exec(message);
1720
+ if (unavailableModel) {
1721
+ throw new Error(`CodeBuddy model "${unavailableModel[1]}" is unavailable for this account. Choose a model ID supported by your CodeBuddy account, or omit model to use the CLI's account default.\n${message}`);
1722
+ }
1723
+ throw err;
1713
1724
  } finally {
1714
1725
  signal?.removeEventListener("abort", onAbort);
1715
1726
  endQuery(sdkQuery, "askCodebuddy", { log: debug });
@@ -1734,7 +1745,16 @@ async function discoverModels(pi: ExtensionAPI): Promise<void> {
1734
1745
  await withSdkGate(async () => {
1735
1746
  let q: ReturnType<typeof startQuery> | undefined;
1736
1747
  try {
1737
- q = startQuery({ prompt: " ", options: { maxTurns: 0, permissionMode: "bypassPermissions", tools: [] } });
1748
+ q = startQuery({
1749
+ prompt: " ",
1750
+ options: {
1751
+ maxTurns: 0,
1752
+ permissionMode: "bypassPermissions",
1753
+ tools: [],
1754
+ env: buildCodebuddyEnv(providerSettings),
1755
+ ...(providerSettings.pathToCodebuddyCode ? { pathToCodebuddyCode: providerSettings.pathToCodebuddyCode } : {}),
1756
+ },
1757
+ });
1738
1758
  const supported = await q.supportedModels();
1739
1759
  await q.return().catch(() => {});
1740
1760
  if (!supported.length) return;
@@ -1903,11 +1923,11 @@ export default async function (pi: ExtensionAPI) {
1903
1923
 
1904
1924
  if (askConf?.enabled !== false) {
1905
1925
  const askCodebuddyParams = Type.Object({
1906
- prompt: Type.String({ description: "The question or task for CodeBuddy. By default Claude sees the full conversation history. Don't research up front, let Claude explore." }),
1926
+ prompt: Type.String({ description: "The question or task for CodeBuddy. By default CodeBuddy sees the full conversation history. Don't research up front, let CodeBuddy explore." }),
1907
1927
  mode: Type.Optional(StringEnum(modeValues, { description: modeDesc })),
1908
- model: Type.Optional(Type.String({ description: 'Claude model (e.g. "opus", "sonnet", "haiku", or full ID). Defaults to "opus".' })),
1909
- thinking: Type.Optional(StringEnum(["off", "minimal", "low", "medium", "high", "xhigh"] as const, { description: "Thinking effort level. Omit to use CodeBuddy's default." })),
1910
- isolated: Type.Optional(Type.Boolean({ description: "When true, Claude sees only this prompt (clean session). When false (default), Claude sees the full conversation history." })),
1928
+ model: Type.Optional(Type.String({ description: "CodeBuddy model ID supported by your account. Omit to use the CLI's account default." })),
1929
+ thinking: Type.Optional(StringEnum(["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const, { description: "Thinking effort level. Omit to use CodeBuddy's default." })),
1930
+ isolated: Type.Optional(Type.Boolean({ description: "When true, CodeBuddy sees only this prompt (clean session). When false (default), CodeBuddy sees the full conversation history." })),
1911
1931
  });
1912
1932
  pi.registerTool<typeof askCodebuddyParams>({
1913
1933
  name: askConf?.name ?? "AskCodebuddy",
package/src/models.ts CHANGED
@@ -95,6 +95,9 @@ export type ModelOverrides = {
95
95
  maxTokens?: number;
96
96
  reasoning?: boolean;
97
97
  images?: boolean;
98
+ // Pi thinking level -> provider-native effort. Pi only offers `xhigh`/`max`
99
+ // when the key is present here (pi-ai `getSupportedThinkingLevels`).
100
+ thinkingLevelMap?: Record<string, string>;
98
101
  };
99
102
 
100
103
  /**
@@ -137,12 +140,16 @@ export function buildModels(
137
140
  const input: ("text" | "image")[] = o.images === undefined
138
141
  ? m.input
139
142
  : (o.images ? ["text", "image"] as const : ["text"] as const);
143
+ const thinkingLevelMap = o.thinkingLevelMap
144
+ ? { ...(m.thinkingLevelMap ?? {}), ...o.thinkingLevelMap }
145
+ : m.thinkingLevelMap;
140
146
  return {
141
147
  ...m,
142
148
  contextWindow,
143
149
  maxTokens,
144
150
  reasoning: o.reasoning ?? m.reasoning,
145
151
  input,
152
+ ...(thinkingLevelMap ? { thinkingLevelMap } : {}),
146
153
  cost: m.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
147
154
  };
148
155
  });