@victor-software-house/pi-acp 0.15.0 → 0.17.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.
@@ -8,7 +8,7 @@ import { isAbsolute, join, resolve } from "node:path";
8
8
  import { Hono } from "hono";
9
9
  import { AgentSideConnection, RequestError, ndJsonStream } from "@agentclientprotocol/sdk";
10
10
  import { randomUUID } from "node:crypto";
11
- import { DefaultResourceLoader, SessionManager, createAgentSession, createBashToolDefinition, createReadToolDefinition, getAgentDir } from "@earendil-works/pi-coding-agent";
11
+ import { AuthStorage, DefaultResourceLoader, SessionManager, createAgentSession, createBashToolDefinition, createReadToolDefinition, getAgentDir } from "@earendil-works/pi-coding-agent";
12
12
  import * as z from "zod";
13
13
  import { parse } from "yaml";
14
14
  import { $ } from "bun";
@@ -544,6 +544,86 @@ function skillCommandsEnabled(cwd) {
544
544
  return true;
545
545
  }
546
546
  //#endregion
547
+ //#region src/acp/providers.ts
548
+ /**
549
+ * Map an ACP `LlmProtocol` to a pi `Api` identifier. Used when
550
+ * `unstable_setProvider` injects a provider whose `apiType` is one of the
551
+ * spec's enumerated protocols. Unknown protocols pass through verbatim
552
+ * (LlmProtocol is `KnownProtocol | string`).
553
+ */
554
+ function acpProtocolToPiApi(protocol) {
555
+ switch (protocol) {
556
+ case "anthropic": return "anthropic-messages";
557
+ case "openai": return "openai-responses";
558
+ case "azure": return "azure-openai-responses";
559
+ case "vertex": return "google-vertex";
560
+ case "bedrock": return "bedrock-converse-stream";
561
+ default: return protocol;
562
+ }
563
+ }
564
+ /**
565
+ * Reverse map: pi `Api` → ACP `LlmProtocol`. Used by `listProviders` to
566
+ * describe each pi-known model's transport. Falls back to the raw `api`
567
+ * string when no canonical bucket matches.
568
+ */
569
+ function piApiToAcpProtocol(api) {
570
+ if (api.startsWith("anthropic")) return "anthropic";
571
+ if (api.startsWith("openai")) return "openai";
572
+ if (api.startsWith("azure")) return "azure";
573
+ if (api.includes("vertex")) return "vertex";
574
+ if (api.startsWith("bedrock")) return "bedrock";
575
+ if (api.startsWith("google")) return "vertex";
576
+ return api;
577
+ }
578
+ function buildListProvidersResponse(deps) {
579
+ const first = firstRegistry(deps.registries());
580
+ if (first === void 0) return { providers: [] };
581
+ const byProvider = /* @__PURE__ */ new Map();
582
+ for (const model of first.getAll()) {
583
+ const entry = byProvider.get(model.provider) ?? {
584
+ baseUrls: /* @__PURE__ */ new Set(),
585
+ protocols: /* @__PURE__ */ new Set()
586
+ };
587
+ entry.baseUrls.add(model.baseUrl);
588
+ entry.protocols.add(piApiToAcpProtocol(model.api));
589
+ byProvider.set(model.provider, entry);
590
+ }
591
+ return { providers: Array.from(byProvider.entries()).map(([id, entry]) => {
592
+ const supported = Array.from(entry.protocols);
593
+ const primaryBaseUrl = Array.from(entry.baseUrls)[0] ?? "";
594
+ const primaryProtocol = supported[0] ?? "openai";
595
+ return {
596
+ id,
597
+ supported,
598
+ required: false,
599
+ current: deps.disabled.has(id) ? null : {
600
+ apiType: primaryProtocol,
601
+ baseUrl: primaryBaseUrl
602
+ }
603
+ };
604
+ }) };
605
+ }
606
+ function applySetProvider(deps, params) {
607
+ const config = {
608
+ baseUrl: params.baseUrl,
609
+ api: acpProtocolToPiApi(params.apiType),
610
+ ...params.headers !== void 0 ? { headers: params.headers } : {}
611
+ };
612
+ for (const reg of deps.registries()) reg.registerProvider(params.id, config);
613
+ deps.disabled.delete(params.id);
614
+ return {};
615
+ }
616
+ function applyDisableProvider(deps, params) {
617
+ deps.disabled.add(params.id);
618
+ for (const reg of deps.registries()) try {
619
+ reg.unregisterProvider(params.id);
620
+ } catch {}
621
+ return {};
622
+ }
623
+ function firstRegistry(it) {
624
+ for (const reg of it) return reg;
625
+ }
626
+ //#endregion
547
627
  //#region src/acp/translate/tool-content.ts
548
628
  const textBlockSchema = z.object({
549
629
  type: z.literal("text"),
@@ -2115,7 +2195,7 @@ var SshBackend = class {
2115
2195
  //#endregion
2116
2196
  //#region package.json
2117
2197
  var name = "@victor-software-house/pi-acp";
2118
- var version = "0.15.0";
2198
+ var version = "0.17.0";
2119
2199
  //#endregion
2120
2200
  //#region src/acp/agent.ts
2121
2201
  /** Builtin ACP slash commands handled directly by the adapter. */
@@ -2213,6 +2293,12 @@ var PiAcpAgent = class {
2213
2293
  connectionId = randomUUID();
2214
2294
  extMethods;
2215
2295
  startedAt = Date.now();
2296
+ /**
2297
+ * pi-acp-side soft-disable set for providers. Pi has only `unregister`
2298
+ * (destructive); we layer a disabled-set on top so `listProviders` can
2299
+ * report `current: null` per ACP spec even after disable.
2300
+ */
2301
+ disabledProviders = /* @__PURE__ */ new Set();
2216
2302
  dispose() {
2217
2303
  if (this.daemonContext !== void 0) {
2218
2304
  const registry = this.daemonContext.sessionRegistry;
@@ -2237,6 +2323,72 @@ var PiAcpAgent = class {
2237
2323
  async extNotification(method, params) {
2238
2324
  await this.extMethods.handleNotification(method, params);
2239
2325
  }
2326
+ /**
2327
+ * Iterable of every live `ModelRegistry` instance — local SessionManager
2328
+ * plus daemon SessionRegistry. Used by the providers/* methods to apply
2329
+ * mutations across all live sessions.
2330
+ */
2331
+ liveModelRegistries() {
2332
+ const out = [];
2333
+ for (const s of this.sessions.values()) out.push(s.piSession.modelRegistry);
2334
+ if (this.daemonContext !== void 0) for (const e of this.daemonContext.sessionRegistry.listAll()) out.push(e.piSession.modelRegistry);
2335
+ return out;
2336
+ }
2337
+ async unstable_listProviders(_params) {
2338
+ return buildListProvidersResponse({
2339
+ registries: () => this.liveModelRegistries(),
2340
+ disabled: this.disabledProviders
2341
+ });
2342
+ }
2343
+ async unstable_setProvider(params) {
2344
+ if (params.id === "") throw RequestError.invalidParams("provider id must be non-empty");
2345
+ return applySetProvider({
2346
+ registries: () => this.liveModelRegistries(),
2347
+ disabled: this.disabledProviders
2348
+ }, params);
2349
+ }
2350
+ async unstable_disableProvider(params) {
2351
+ if (params.id === "") throw RequestError.invalidParams("provider id must be non-empty");
2352
+ return applyDisableProvider({
2353
+ registries: () => this.liveModelRegistries(),
2354
+ disabled: this.disabledProviders
2355
+ }, params);
2356
+ }
2357
+ /**
2358
+ * Clears every provider's stored credentials from the shared AuthStorage.
2359
+ *
2360
+ * ACP `LogoutRequest` carries only `_meta` — no per-provider selector —
2361
+ * so this is correctly GLOBAL. Pi has no AuthStorage.clearAll(); we
2362
+ * loop `list()` + `remove()` per provider.
2363
+ *
2364
+ * Sessions stay live. Subsequent prompts may surface auth_required which
2365
+ * is the correct UX. Best-effort fanout: post an agent_message_chunk to
2366
+ * every live PiAcpSession announcing the logout for client visibility.
2367
+ *
2368
+ * Strategy: reuse an active session's AuthStorage instance when one
2369
+ * exists (every live session shares the same on-disk auth.json). When
2370
+ * no session is live, mint an ad-hoc `AuthStorage.create()` to operate
2371
+ * directly on the on-disk store.
2372
+ *
2373
+ * Gated by `agentCapabilities.auth.logout = {}`.
2374
+ */
2375
+ async unstable_logout(_params) {
2376
+ const live = this.sessions.first();
2377
+ const authStorage = live !== void 0 ? live.piSession.modelRegistry.authStorage : AuthStorage.create();
2378
+ const providers = authStorage.list();
2379
+ for (const p of providers) authStorage.remove(p);
2380
+ for (const s of this.sessions.values()) this.conn.sessionUpdate({
2381
+ sessionId: s.sessionId,
2382
+ update: {
2383
+ sessionUpdate: "agent_message_chunk",
2384
+ content: {
2385
+ type: "text",
2386
+ text: "[pi-acp] Logged out from all providers.\n"
2387
+ }
2388
+ }
2389
+ }).catch(() => {});
2390
+ return { _meta: { piAcp: { clearedProviders: providers } } };
2391
+ }
2240
2392
  registerWithDaemon(input) {
2241
2393
  if (this.daemonContext === void 0) return;
2242
2394
  this.daemonContext.sessionRegistry.register({
@@ -2399,6 +2551,7 @@ var PiAcpAgent = class {
2399
2551
  },
2400
2552
  authMethods: buildAuthMethods({ supportsTerminalAuthMeta: this.clientCapabilities.terminalAuth }),
2401
2553
  agentCapabilities: {
2554
+ auth: { logout: {} },
2402
2555
  loadSession: true,
2403
2556
  mcpCapabilities: {
2404
2557
  http: false,
@@ -2415,7 +2568,8 @@ var PiAcpAgent = class {
2415
2568
  resume: {},
2416
2569
  fork: {},
2417
2570
  delete: {}
2418
- }
2571
+ },
2572
+ providers: {}
2419
2573
  }
2420
2574
  };
2421
2575
  }
@@ -3563,4 +3717,4 @@ async function runDaemon() {
3563
3717
  //#endregion
3564
3718
  export { runDaemon };
3565
3719
 
3566
- //# sourceMappingURL=daemon-C-rb-cf5.mjs.map
3720
+ //# sourceMappingURL=daemon-CTINLJUp.mjs.map