pi2dsh 0.3.1 → 0.3.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.
Files changed (101) hide show
  1. package/README.md +15 -10
  2. package/README.zh.md +15 -10
  3. package/dist/all-Rt4K68QA.mjs +28512 -0
  4. package/dist/all-Rt4K68QA.mjs.map +1 -0
  5. package/dist/anthropic-messages-Beym0_aC.mjs +6438 -0
  6. package/dist/anthropic-messages-Beym0_aC.mjs.map +1 -0
  7. package/dist/azure-openai-responses-cHqZTgLD.mjs +209 -0
  8. package/dist/azure-openai-responses-cHqZTgLD.mjs.map +1 -0
  9. package/dist/{build-DsGYAgiT.mjs → build-BG7p2Lbr.mjs} +2 -2
  10. package/dist/{build-DsGYAgiT.mjs.map → build-BG7p2Lbr.mjs.map} +1 -1
  11. package/dist/cli.mjs +1 -1
  12. package/dist/compat/pi-ai.d.mts +16 -3
  13. package/dist/compat/pi-ai.d.mts.map +1 -1
  14. package/dist/compat/pi-ai.mjs +3 -3
  15. package/dist/compat/pi-coding-agent.d.mts +12 -1
  16. package/dist/compat/pi-coding-agent.d.mts.map +1 -1
  17. package/dist/compat/pi-coding-agent.mjs +2 -2
  18. package/dist/credentials-oauth.mjs +3 -3
  19. package/dist/deferred-tools-BD4g5QUx.mjs +32 -0
  20. package/dist/deferred-tools-BD4g5QUx.mjs.map +1 -0
  21. package/dist/diagnostics-CJye1UFh.mjs +99 -0
  22. package/dist/diagnostics-CJye1UFh.mjs.map +1 -0
  23. package/dist/dist-IQKOgQyt.mjs +10035 -0
  24. package/dist/dist-IQKOgQyt.mjs.map +1 -0
  25. package/dist/dist-QBBA-dL_.mjs +1246 -0
  26. package/dist/dist-QBBA-dL_.mjs.map +1 -0
  27. package/dist/error-body-DKA3lknR.mjs +104 -0
  28. package/dist/error-body-DKA3lknR.mjs.map +1 -0
  29. package/dist/github-copilot-headers-CZo2QHrt.mjs +25 -0
  30. package/dist/github-copilot-headers-CZo2QHrt.mjs.map +1 -0
  31. package/dist/google-generative-ai-B7OZs5lu.mjs +358 -0
  32. package/dist/google-generative-ai-B7OZs5lu.mjs.map +1 -0
  33. package/dist/google-shared-CRsAUItF.mjs +29042 -0
  34. package/dist/google-shared-CRsAUItF.mjs.map +1 -0
  35. package/dist/google-vertex-DmdaHQtB.mjs +402 -0
  36. package/dist/google-vertex-DmdaHQtB.mjs.map +1 -0
  37. package/dist/hash-Dbggamac.mjs +19 -0
  38. package/dist/hash-Dbggamac.mjs.map +1 -0
  39. package/dist/headers-CPzZhJQn.mjs +17 -0
  40. package/dist/headers-CPzZhJQn.mjs.map +1 -0
  41. package/dist/host.d.mts.map +1 -1
  42. package/dist/host.mjs +13 -3
  43. package/dist/host.mjs.map +1 -1
  44. package/dist/images-models-DJtHo9Rp.mjs +1068 -0
  45. package/dist/images-models-DJtHo9Rp.mjs.map +1 -0
  46. package/dist/index.d.mts.map +1 -1
  47. package/dist/index.mjs +1 -1
  48. package/dist/json-parse-CFyFZSLJ.mjs +384 -0
  49. package/dist/json-parse-CFyFZSLJ.mjs.map +1 -0
  50. package/dist/{mcp-config-gqg7GJEE.mjs → mcp-config-D2B3kv-Z.mjs} +34 -16
  51. package/dist/mcp-config-D2B3kv-Z.mjs.map +1 -0
  52. package/dist/mistral-conversations-HHZCcFv_.mjs +42007 -0
  53. package/dist/mistral-conversations-HHZCcFv_.mjs.map +1 -0
  54. package/dist/multipart-parser-CIa5BbwO.mjs +301 -0
  55. package/dist/multipart-parser-CIa5BbwO.mjs.map +1 -0
  56. package/dist/{oauth-bridge-BpPrppjR.mjs → oauth-bridge-C6fL1qSv.mjs} +20 -4
  57. package/dist/oauth-bridge-C6fL1qSv.mjs.map +1 -0
  58. package/dist/openai-DkzRh5_G.mjs +6732 -0
  59. package/dist/openai-DkzRh5_G.mjs.map +1 -0
  60. package/dist/openai-codex-responses-vmovNu-q.mjs +1169 -0
  61. package/dist/openai-codex-responses-vmovNu-q.mjs.map +1 -0
  62. package/dist/openai-completions-fXHhRolF.mjs +947 -0
  63. package/dist/openai-completions-fXHhRolF.mjs.map +1 -0
  64. package/dist/openai-prompt-cache-BDWtZRKh.mjs +11 -0
  65. package/dist/openai-prompt-cache-BDWtZRKh.mjs.map +1 -0
  66. package/dist/openai-responses-DKdCYf3h.mjs +247 -0
  67. package/dist/openai-responses-DKdCYf3h.mjs.map +1 -0
  68. package/dist/openai-responses-shared-D0Jq7yVP.mjs +572 -0
  69. package/dist/openai-responses-shared-D0Jq7yVP.mjs.map +1 -0
  70. package/dist/openrouter-images-fG3Aj6M4.mjs +127 -0
  71. package/dist/openrouter-images-fG3Aj6M4.mjs.map +1 -0
  72. package/dist/otel-BQSQRBLF.mjs +24935 -0
  73. package/dist/otel-BQSQRBLF.mjs.map +1 -0
  74. package/dist/{pi-ai-CWFlgigJ.mjs → pi-ai-DhYGgq4Y.mjs} +49 -12
  75. package/dist/{pi-ai-CWFlgigJ.mjs.map → pi-ai-DhYGgq4Y.mjs.map} +1 -1
  76. package/dist/{pi-coding-agent-Z1hTs61i.mjs → pi-coding-agent-fPHwKP4d.mjs} +27 -3
  77. package/dist/{pi-coding-agent-Z1hTs61i.mjs.map → pi-coding-agent-fPHwKP4d.mjs.map} +1 -1
  78. package/dist/pi-messages-CV-Ts8An.mjs +306 -0
  79. package/dist/pi-messages-CV-Ts8An.mjs.map +1 -0
  80. package/dist/provider-env-C72pF4vP.mjs +40 -0
  81. package/dist/provider-env-C72pF4vP.mjs.map +1 -0
  82. package/dist/provider-retry-Cw6YJ4cr.mjs +80 -0
  83. package/dist/provider-retry-Cw6YJ4cr.mjs.map +1 -0
  84. package/dist/rolldown-runtime-CTfmNlz1.mjs +44 -0
  85. package/dist/{runtime-6DefpI6h.mjs → runtime-B9R7bDWT.mjs} +874 -37
  86. package/dist/runtime-B9R7bDWT.mjs.map +1 -0
  87. package/dist/runtime.d.mts.map +1 -1
  88. package/dist/runtime.mjs +1 -1
  89. package/dist/sanitize-unicode-wLA11KvZ.mjs +29 -0
  90. package/dist/sanitize-unicode-wLA11KvZ.mjs.map +1 -0
  91. package/dist/src-C3WBfOks.mjs +5031 -0
  92. package/dist/src-C3WBfOks.mjs.map +1 -0
  93. package/dist/transform-messages-DGq9mvhA.mjs +379 -0
  94. package/dist/transform-messages-DGq9mvhA.mjs.map +1 -0
  95. package/dist/uuid-DtFT7tOE.mjs +65 -0
  96. package/dist/uuid-DtFT7tOE.mjs.map +1 -0
  97. package/package.json +1 -1
  98. package/dist/mcp-config-gqg7GJEE.mjs.map +0 -1
  99. package/dist/oauth-bridge-BpPrppjR.mjs.map +0 -1
  100. package/dist/rolldown-runtime-D-uZhY3_.mjs +0 -16
  101. package/dist/runtime-6DefpI6h.mjs.map +0 -1
@@ -1,18 +1,18 @@
1
1
 
2
2
  import { t as getAgentDir } from "./pi-config-shim-CZ1wFzqM.mjs";
3
- import { E as __setSubagentSessionFactory, S as Theme, f as ExtensionRunner } from "./pi-coding-agent-Z1hTs61i.mjs";
4
- import { a as storedOAuthCredential, i as resolveOAuthApiKey, n as loginPiProvider, r as providerSupportsOAuth, t as FileCredentialStore } from "./oauth-bridge-BpPrppjR.mjs";
5
- import { r as builtinProviders } from "./pi-ai-CWFlgigJ.mjs";
3
+ import { E as __setSubagentSessionFactory, S as Theme, f as ExtensionRunner } from "./pi-coding-agent-fPHwKP4d.mjs";
4
+ import { a as resolvePiProviderAuth, n as loginPiProvider, r as providerSupportsOAuth, t as FileCredentialStore } from "./oauth-bridge-C6fL1qSv.mjs";
5
+ import { L as AssistantMessageEventStream, S as realBuiltinProvider, i as builtinProviders, n as __setPiAiLlmBridge } from "./pi-ai-DhYGgq4Y.mjs";
6
6
  import { createRequire } from "node:module";
7
7
  import { access, readFile } from "node:fs/promises";
8
8
  import { join } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
10
11
  import { EventEmitter } from "node:events";
11
12
  import { AsyncLocalStorage } from "node:async_hooks";
12
13
  import { createJiti } from "jiti";
13
14
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
14
15
  import { renderPrompt } from "@deepseek-ai/dsh-system-prompt";
15
- import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
16
16
  import { randomUUID } from "node:crypto";
17
17
  //#region src/session-bridge.ts
18
18
  function sidecarDir() {
@@ -435,11 +435,13 @@ var PiBridgedAgentSession = class {
435
435
  }
436
436
  abort() {
437
437
  this.#aborted = true;
438
- const cancel = this.#handle.agent.cancel;
439
- cancel?.({
440
- kind: "hook",
441
- reason: "pi2dsh subagent abort()"
442
- });
438
+ try {
439
+ const cancel = this.#handle.agent.cancel;
440
+ cancel?.({
441
+ kind: "hook",
442
+ reason: "pi2dsh subagent abort()"
443
+ });
444
+ } catch {}
443
445
  }
444
446
  setSessionName(name) {
445
447
  this.#sessionName = String(name);
@@ -479,14 +481,34 @@ async function createBridgedAgentSession(host, options) {
479
481
  const sessionId = `pi2dsh-sub-${Date.now().toString(36)}-${subagentSerial}`;
480
482
  let handle;
481
483
  try {
484
+ const requestedModel = options.model;
485
+ const loader = options.resourceLoader;
486
+ const overrideText = typeof options.systemPrompt === "string" && options.systemPrompt.length > 0 ? options.systemPrompt : typeof loader?.getSystemPrompt === "function" ? loader.getSystemPrompt() : void 0;
487
+ const appendTexts = (typeof loader?.getAppendSystemPrompt === "function" ? loader.getAppendSystemPrompt() : []).filter((text) => typeof text === "string" && text.length > 0);
488
+ const systemPrompt = overrideText !== void 0 && overrideText.length > 0 ? [overrideText, ...appendTexts].join("\n\n") : void 0;
482
489
  handle = await agents.create({
483
490
  sessionId,
484
491
  meta: {
485
492
  cwd: typeof options.cwd === "string" ? options.cwd : host.cwd(),
486
493
  origin: "subagent",
487
494
  ...host.parentSessionId() !== void 0 ? { parentSession: host.parentSessionId() } : {}
488
- }
495
+ },
496
+ ...typeof requestedModel?.id === "string" && requestedModel.id.length > 0 ? { agentOptions: {
497
+ model: requestedModel.id,
498
+ ...typeof requestedModel.provider === "string" && requestedModel.provider.length > 0 ? { provider: requestedModel.provider } : {}
499
+ } } : {}
489
500
  });
501
+ if (systemPrompt !== void 0) {
502
+ const agentCtx = handle.agent.ctx;
503
+ const prompt = typeof agentCtx?.get === "function" ? agentCtx.get("systemPrompt") : void 0;
504
+ if (prompt === void 0) throw new Error("pi2dsh: createAgentSession() got a system prompt but the DSH composition has no systemPrompt service to carry it");
505
+ prompt.section({
506
+ name: "pi2dsh:subagent-system-prompt",
507
+ order: -1e6,
508
+ text: systemPrompt,
509
+ complete: true
510
+ });
511
+ }
490
512
  } catch (error) {
491
513
  throw new Error(`pi2dsh: subagent creation needs the DSH host loop (model runtime) to provide the agent factory; this composition cannot run one (${error instanceof Error ? error.message : String(error)})`);
492
514
  }
@@ -494,6 +516,663 @@ async function createBridgedAgentSession(host, options) {
494
516
  return { session: new PiBridgedAgentSession(host, handle, tools) };
495
517
  }
496
518
  //#endregion
519
+ //#region src/model-bridge.ts
520
+ function llmOf(ctx) {
521
+ try {
522
+ const service = ctx.get("llm");
523
+ if (typeof service?.stream !== "function") return void 0;
524
+ return service;
525
+ } catch {
526
+ return;
527
+ }
528
+ }
529
+ function piModelFromInfo(info) {
530
+ const modalities = Array.isArray(info.inputModalities) ? info.inputModalities : void 0;
531
+ return {
532
+ id: String(info.id ?? ""),
533
+ name: String(info.name ?? info.id ?? ""),
534
+ provider: String(info.provider ?? ""),
535
+ api: "dsh-llm",
536
+ input: modalities === void 0 ? ["text"] : modalities.filter((m) => m === "text" || m === "image"),
537
+ reasoning: info.reasoning !== void 0
538
+ };
539
+ }
540
+ /**
541
+ * Cached projection of DSH's advisory model catalog as Pi Model objects.
542
+ * `refresh()` re-reads providers and models; the runtime subscribes it to
543
+ * `llm/adapters-updated`. Reads are synchronous (Pi's getAll contract).
544
+ */
545
+ var ModelCatalog = class {
546
+ #llm;
547
+ #models = [];
548
+ #resolved = /* @__PURE__ */ new Map();
549
+ #refreshing;
550
+ constructor(llm) {
551
+ this.#llm = llm;
552
+ }
553
+ get present() {
554
+ return this.#llm !== void 0;
555
+ }
556
+ all() {
557
+ return [...this.#models];
558
+ }
559
+ find(provider, modelId) {
560
+ return this.#resolved.get(`${provider}${modelId}`) ?? this.#models.find((model) => model.provider === provider && model.id === modelId);
561
+ }
562
+ refresh() {
563
+ if (this.#llm === void 0) return Promise.resolve();
564
+ this.#refreshing ??= this.#refreshOnce().finally(() => {
565
+ this.#refreshing = void 0;
566
+ });
567
+ return this.#refreshing;
568
+ }
569
+ async #refreshOnce() {
570
+ const llm = this.#llm;
571
+ if (llm === void 0) return;
572
+ const next = [];
573
+ for (const provider of llm.listProviders()) try {
574
+ for (const info of await llm.listModels(provider.id)) next.push(piModelFromInfo({
575
+ ...info,
576
+ provider: provider.id
577
+ }));
578
+ } catch {}
579
+ this.#models = next;
580
+ }
581
+ /** Exact-route metadata (context window, reasoning efforts), cached per route. */
582
+ async resolve(provider, modelId) {
583
+ if (this.#llm === void 0) return void 0;
584
+ const key = `${provider}${modelId}`;
585
+ const cached = this.#resolved.get(key);
586
+ if (cached !== void 0) return cached;
587
+ try {
588
+ const info = await this.#llm.resolveModelInfo(provider, modelId);
589
+ const model = piModelFromInfo(info);
590
+ const context = info.context;
591
+ if (typeof context?.contextWindow === "number") model.contextWindow = context.contextWindow;
592
+ const defaultMaxTokens = info.defaultMaxTokens;
593
+ if (typeof defaultMaxTokens === "number") model.maxTokens = defaultMaxTokens;
594
+ const reasoning = info.reasoning;
595
+ model.reasoning = Array.isArray(reasoning?.efforts) && reasoning.efforts.length > 0;
596
+ this.#resolved.set(key, model);
597
+ return model;
598
+ } catch {
599
+ return;
600
+ }
601
+ }
602
+ };
603
+ function dshBlocksFromPiContent(content) {
604
+ if (typeof content === "string") return content.length > 0 ? [{
605
+ type: "text",
606
+ text: content
607
+ }] : [];
608
+ if (!Array.isArray(content)) return [];
609
+ const blocks = [];
610
+ for (const block of content) if (block.type === "text" && typeof block.text === "string") blocks.push({
611
+ type: "text",
612
+ text: block.text
613
+ });
614
+ else if (block.type === "thinking" && typeof block.thinking === "string") blocks.push({
615
+ type: "reasoning",
616
+ text: block.thinking
617
+ });
618
+ else if (block.type === "toolCall") blocks.push({
619
+ type: "tool-call",
620
+ id: String(block.id ?? ""),
621
+ name: String(block.name ?? ""),
622
+ arguments: typeof block.arguments === "string" ? block.arguments : JSON.stringify(block.arguments ?? {})
623
+ });
624
+ else if (block.type === "image") blocks.push({
625
+ type: "text",
626
+ text: `[image omitted by pi2dsh: ${String(block.mimeType ?? "unknown media type")}]`
627
+ });
628
+ return blocks;
629
+ }
630
+ /**
631
+ * Pi request context → DSH GenerateOptions parts. System prompt moves to the
632
+ * dedicated slot; toolResult messages become DSH tool-result user messages.
633
+ */
634
+ function piRequestToDshMessages(context) {
635
+ const messages = [];
636
+ const source = Array.isArray(context.messages) ? context.messages : [];
637
+ for (const message of source) if (message.role === "user") messages.push({
638
+ role: "user",
639
+ content: dshBlocksFromPiContent(message.content),
640
+ source: { kind: "user" }
641
+ });
642
+ else if (message.role === "assistant") messages.push({
643
+ role: "assistant",
644
+ content: dshBlocksFromPiContent(message.content),
645
+ source: {
646
+ kind: "model",
647
+ provider: String(message.provider ?? context.provider ?? "pi2dsh"),
648
+ model: String(message.model ?? context.model ?? "unknown")
649
+ }
650
+ });
651
+ else if (message.role === "toolResult") messages.push({
652
+ role: "user",
653
+ content: [{
654
+ type: "tool-result",
655
+ toolCallId: String(message.toolCallId ?? ""),
656
+ content: dshBlocksFromPiContent(message.content),
657
+ isError: message.isError === true
658
+ }],
659
+ source: {
660
+ kind: "tool",
661
+ callId: String(message.toolCallId ?? "")
662
+ }
663
+ });
664
+ const system = typeof context.systemPrompt === "string" && context.systemPrompt.length > 0 ? context.systemPrompt : void 0;
665
+ return {
666
+ ...system === void 0 ? {} : { system },
667
+ messages
668
+ };
669
+ }
670
+ function piUsageFromDsh(usage) {
671
+ const input = Number(usage?.inputTokens ?? 0);
672
+ const output = Number(usage?.outputTokens ?? 0);
673
+ const cacheRead = Number(usage?.cacheReadTokens ?? 0);
674
+ const cacheWrite = Number(usage?.cacheWriteTokens ?? 0);
675
+ return {
676
+ input,
677
+ output,
678
+ cacheRead,
679
+ cacheWrite,
680
+ totalTokens: input + output + cacheRead + cacheWrite,
681
+ cost: {
682
+ input: 0,
683
+ output: 0,
684
+ cacheRead: 0,
685
+ cacheWrite: 0,
686
+ total: 0
687
+ }
688
+ };
689
+ }
690
+ function piStopReason(finish) {
691
+ switch (finish?.kind) {
692
+ case "stop": return "stop";
693
+ case "max-tokens": return "length";
694
+ case "tool-calls": return "toolUse";
695
+ case "aborted": return "aborted";
696
+ default: return "error";
697
+ }
698
+ }
699
+ /**
700
+ * Run one hand-built pi-ai model call on DSH's native llm service, translating
701
+ * the chunk stream into Pi's AssistantMessageEventStream vocabulary.
702
+ */
703
+ function streamViaDshLlm(llm, request) {
704
+ const stream = new AssistantMessageEventStream();
705
+ const provider = String(request.model.provider ?? "");
706
+ const modelId = String(request.model.id ?? "");
707
+ const { system, messages } = piRequestToDshMessages({
708
+ ...request.context,
709
+ provider,
710
+ model: modelId
711
+ });
712
+ const options = request.options ?? {};
713
+ const generate = {
714
+ provider,
715
+ model: modelId,
716
+ messages,
717
+ ...system === void 0 ? {} : { system },
718
+ ...Array.isArray(request.context.tools) ? { tools: normalizeToolSchemas(request.context.tools) } : {},
719
+ ...typeof options.maxTokens === "number" ? { maxTokens: options.maxTokens } : {},
720
+ ...typeof options.temperature === "number" ? { temperature: options.temperature } : {},
721
+ ...options.signal instanceof AbortSignal ? { signal: options.signal } : {}
722
+ };
723
+ (async () => {
724
+ const partial = {
725
+ role: "assistant",
726
+ content: [],
727
+ provider,
728
+ model: modelId,
729
+ api: "dsh-llm"
730
+ };
731
+ const content = partial.content;
732
+ let usage;
733
+ stream.push({
734
+ type: "start",
735
+ partial
736
+ });
737
+ try {
738
+ for await (const chunk of llm.stream(generate)) {
739
+ const type = String(chunk.type ?? "");
740
+ const index = Number(chunk.index ?? 0);
741
+ if (type === "block-start") {
742
+ const blockType = String(chunk.blockType ?? "");
743
+ if (blockType === "text") {
744
+ content[index] = {
745
+ type: "text",
746
+ text: ""
747
+ };
748
+ stream.push({
749
+ type: "text_start",
750
+ contentIndex: index,
751
+ partial
752
+ });
753
+ } else if (blockType === "reasoning") {
754
+ content[index] = {
755
+ type: "thinking",
756
+ thinking: ""
757
+ };
758
+ stream.push({
759
+ type: "thinking_start",
760
+ contentIndex: index,
761
+ partial
762
+ });
763
+ } else if (blockType === "tool-call") {
764
+ content[index] = {
765
+ type: "toolCall",
766
+ id: "",
767
+ name: "",
768
+ arguments: void 0,
769
+ partialJson: ""
770
+ };
771
+ stream.push({
772
+ type: "toolcall_start",
773
+ contentIndex: index,
774
+ partial
775
+ });
776
+ }
777
+ } else if (type === "text-delta") {
778
+ const block = content[index] ??= {
779
+ type: "text",
780
+ text: ""
781
+ };
782
+ block.text = String(block.text ?? "") + String(chunk.text ?? "");
783
+ stream.push({
784
+ type: "text_delta",
785
+ contentIndex: index,
786
+ delta: String(chunk.text ?? ""),
787
+ partial
788
+ });
789
+ } else if (type === "reasoning-delta") {
790
+ const block = content[index] ??= {
791
+ type: "thinking",
792
+ thinking: ""
793
+ };
794
+ block.thinking = String(block.thinking ?? "") + String(chunk.text ?? "");
795
+ stream.push({
796
+ type: "thinking_delta",
797
+ contentIndex: index,
798
+ delta: String(chunk.text ?? ""),
799
+ partial
800
+ });
801
+ } else if (type === "tool-call-delta") {
802
+ const block = content[index] ??= {
803
+ type: "toolCall",
804
+ id: "",
805
+ name: "",
806
+ arguments: void 0,
807
+ partialJson: ""
808
+ };
809
+ if (chunk.id !== void 0) block.id = String(chunk.id);
810
+ if (chunk.name !== void 0) block.name = String(chunk.name);
811
+ block.partialJson = String(block.partialJson ?? "") + String(chunk.argumentsDelta ?? "");
812
+ stream.push({
813
+ type: "toolcall_delta",
814
+ contentIndex: index,
815
+ delta: String(chunk.argumentsDelta ?? ""),
816
+ partial
817
+ });
818
+ } else if (type === "block-end") {
819
+ const block = chunk.block;
820
+ const blockType = String(block?.type ?? "");
821
+ if (blockType === "text") {
822
+ content[index] = {
823
+ type: "text",
824
+ text: String(block?.text ?? "")
825
+ };
826
+ stream.push({
827
+ type: "text_end",
828
+ contentIndex: index,
829
+ content: String(block?.text ?? ""),
830
+ partial
831
+ });
832
+ } else if (blockType === "reasoning") {
833
+ content[index] = {
834
+ type: "thinking",
835
+ thinking: String(block?.text ?? "")
836
+ };
837
+ stream.push({
838
+ type: "thinking_end",
839
+ contentIndex: index,
840
+ content: String(block?.text ?? ""),
841
+ partial
842
+ });
843
+ } else if (blockType === "tool-call") {
844
+ let parsed;
845
+ try {
846
+ parsed = JSON.parse(String(block?.arguments ?? "{}"));
847
+ } catch {
848
+ parsed = {};
849
+ }
850
+ content[index] = {
851
+ type: "toolCall",
852
+ id: String(block?.id ?? ""),
853
+ name: String(block?.name ?? ""),
854
+ arguments: parsed
855
+ };
856
+ stream.push({
857
+ type: "toolcall_end",
858
+ contentIndex: index,
859
+ toolCall: content[index],
860
+ partial
861
+ });
862
+ }
863
+ } else if (type === "usage") usage = piUsageFromDsh(chunk.usage);
864
+ else if (type === "finish") {
865
+ const finish = chunk.reason;
866
+ const stopReason = piStopReason(finish);
867
+ if (stopReason === "error" || stopReason === "aborted") {
868
+ const failure = finish?.failure;
869
+ const error = {
870
+ type: "error",
871
+ reason: stopReason === "aborted" ? "aborted" : "error",
872
+ error: new Error(String(failure?.message ?? `model call failed (${stopReason})`))
873
+ };
874
+ stream.push({
875
+ ...error,
876
+ partial
877
+ });
878
+ return;
879
+ }
880
+ const message = {
881
+ role: "assistant",
882
+ content: content.filter((block) => block !== void 0),
883
+ provider,
884
+ model: modelId,
885
+ api: "dsh-llm",
886
+ ...usage === void 0 ? {} : { usage },
887
+ stopReason,
888
+ timestamp: Date.now()
889
+ };
890
+ stream.push({
891
+ type: "done",
892
+ reason: stopReason,
893
+ message
894
+ });
895
+ return;
896
+ }
897
+ }
898
+ stream.push({
899
+ type: "error",
900
+ reason: "error",
901
+ error: /* @__PURE__ */ new Error("pi2dsh: DSH llm stream ended without a finish chunk"),
902
+ partial
903
+ });
904
+ } catch (error) {
905
+ stream.push({
906
+ type: "error",
907
+ reason: "error",
908
+ error: error instanceof Error ? error : new Error(String(error)),
909
+ partial
910
+ });
911
+ }
912
+ })();
913
+ return stream;
914
+ }
915
+ function normalizeToolSchemas(tools) {
916
+ return tools.map((tool) => ({
917
+ name: String(tool.name ?? ""),
918
+ description: String(tool.description ?? ""),
919
+ parameters: tool.parameters ?? tool.inputSchema ?? {
920
+ type: "object",
921
+ properties: {}
922
+ }
923
+ }));
924
+ }
925
+ /** DSH GenerateOptions parts → the Pi request context a provider.stream expects. */
926
+ function dshRequestToPiContext(options) {
927
+ const piMessages = [];
928
+ for (const message of Array.isArray(options.messages) ? options.messages : []) {
929
+ const content = Array.isArray(message.content) ? message.content : [];
930
+ const source = message.source;
931
+ if (message.role === "user" && source?.kind === "tool") {
932
+ const result = content[0];
933
+ piMessages.push({
934
+ role: "toolResult",
935
+ toolCallId: String(result?.toolCallId ?? ""),
936
+ content: piContentFromDshBlocks(Array.isArray(result?.content) ? result.content : []),
937
+ isError: result?.isError === true
938
+ });
939
+ } else if (message.role === "user") piMessages.push({
940
+ role: "user",
941
+ content: piContentFromDshBlocks(content)
942
+ });
943
+ else if (message.role === "assistant") piMessages.push({
944
+ role: "assistant",
945
+ content: piContentFromDshBlocks(content)
946
+ });
947
+ }
948
+ return {
949
+ ...typeof options.system === "string" && options.system.length > 0 ? { systemPrompt: options.system } : {},
950
+ messages: piMessages,
951
+ ...Array.isArray(options.tools) ? { tools: options.tools } : {}
952
+ };
953
+ }
954
+ function piContentFromDshBlocks(blocks) {
955
+ const out = [];
956
+ for (const block of blocks) if (block.type === "text") out.push({
957
+ type: "text",
958
+ text: String(block.text ?? "")
959
+ });
960
+ else if (block.type === "reasoning") out.push({
961
+ type: "thinking",
962
+ thinking: String(block.text ?? "")
963
+ });
964
+ else if (block.type === "tool-call") {
965
+ let parsed;
966
+ try {
967
+ parsed = JSON.parse(String(block.arguments ?? "{}"));
968
+ } catch {
969
+ parsed = {};
970
+ }
971
+ out.push({
972
+ type: "toolCall",
973
+ id: String(block.id ?? ""),
974
+ name: String(block.name ?? ""),
975
+ arguments: parsed
976
+ });
977
+ }
978
+ return out;
979
+ }
980
+ /** A Pi AssistantMessageEventStream → DSH StreamChunk sequence. */
981
+ async function* piEventsToDshChunks(events) {
982
+ let usage;
983
+ for await (const event of events) {
984
+ const type = String(event.type ?? "");
985
+ const index = Number(event.contentIndex ?? 0);
986
+ if (type === "text_start") yield {
987
+ type: "block-start",
988
+ index,
989
+ blockType: "text"
990
+ };
991
+ else if (type === "text_delta") yield {
992
+ type: "text-delta",
993
+ index,
994
+ text: String(event.delta ?? "")
995
+ };
996
+ else if (type === "text_end") yield {
997
+ type: "block-end",
998
+ index,
999
+ block: {
1000
+ type: "text",
1001
+ text: String(event.content ?? "")
1002
+ }
1003
+ };
1004
+ else if (type === "thinking_start") yield {
1005
+ type: "block-start",
1006
+ index,
1007
+ blockType: "reasoning"
1008
+ };
1009
+ else if (type === "thinking_delta") yield {
1010
+ type: "reasoning-delta",
1011
+ index,
1012
+ text: String(event.delta ?? "")
1013
+ };
1014
+ else if (type === "thinking_end") yield {
1015
+ type: "block-end",
1016
+ index,
1017
+ block: {
1018
+ type: "reasoning",
1019
+ text: String(event.content ?? "")
1020
+ }
1021
+ };
1022
+ else if (type === "toolcall_start") yield {
1023
+ type: "block-start",
1024
+ index,
1025
+ blockType: "tool-call"
1026
+ };
1027
+ else if (type === "toolcall_delta") yield {
1028
+ type: "tool-call-delta",
1029
+ index,
1030
+ id: "",
1031
+ argumentsDelta: String(event.delta ?? "")
1032
+ };
1033
+ else if (type === "toolcall_end") {
1034
+ const call = event.toolCall;
1035
+ yield {
1036
+ type: "block-end",
1037
+ index,
1038
+ block: {
1039
+ type: "tool-call",
1040
+ id: String(call?.id ?? ""),
1041
+ name: String(call?.name ?? ""),
1042
+ arguments: typeof call?.arguments === "string" ? call.arguments : JSON.stringify(call?.arguments ?? {})
1043
+ }
1044
+ };
1045
+ } else if (type === "done") {
1046
+ const message = event.message;
1047
+ const piUsage = message?.usage;
1048
+ if (piUsage !== void 0) {
1049
+ usage = {
1050
+ inputTokens: Number(piUsage.input ?? 0),
1051
+ outputTokens: Number(piUsage.output ?? 0),
1052
+ ...piUsage.cacheRead === void 0 ? {} : { cacheReadTokens: Number(piUsage.cacheRead) },
1053
+ ...piUsage.cacheWrite === void 0 ? {} : { cacheWriteTokens: Number(piUsage.cacheWrite) }
1054
+ };
1055
+ yield {
1056
+ type: "usage",
1057
+ usage
1058
+ };
1059
+ }
1060
+ const stop = String(message?.stopReason ?? "stop");
1061
+ yield {
1062
+ type: "finish",
1063
+ reason: stop === "length" ? { kind: "max-tokens" } : stop === "toolUse" ? { kind: "tool-calls" } : stop === "aborted" ? {
1064
+ kind: "aborted",
1065
+ failure: {
1066
+ message: "aborted by caller",
1067
+ code: "ABORTED"
1068
+ }
1069
+ } : { kind: "stop" }
1070
+ };
1071
+ return;
1072
+ } else if (type === "error") {
1073
+ const error = event.error;
1074
+ yield {
1075
+ type: "finish",
1076
+ reason: {
1077
+ kind: String(event.reason ?? "") === "aborted" ? "aborted" : "error",
1078
+ failure: {
1079
+ message: error instanceof Error ? error.message : String(error ?? "provider stream error"),
1080
+ code: "PI_PROVIDER"
1081
+ }
1082
+ }
1083
+ };
1084
+ return;
1085
+ }
1086
+ }
1087
+ yield {
1088
+ type: "finish",
1089
+ reason: {
1090
+ kind: "error",
1091
+ failure: {
1092
+ message: "pi2dsh: Pi provider stream ended without done/error",
1093
+ code: "PI_PROVIDER"
1094
+ }
1095
+ }
1096
+ };
1097
+ }
1098
+ //#endregion
1099
+ //#region src/provider-adapter.ts
1100
+ function providerCarriesTransport(value) {
1101
+ return typeof value?.stream === "function";
1102
+ }
1103
+ function providerModels(provider) {
1104
+ try {
1105
+ const models = provider.getModels?.();
1106
+ return Array.isArray(models) ? models : [];
1107
+ } catch {
1108
+ return [];
1109
+ }
1110
+ }
1111
+ /**
1112
+ * Wrap one Pi provider object as a DSH LlmAdapter-shaped route handler.
1113
+ * Structural typing carries it through `llm.registerAdapter`.
1114
+ */
1115
+ function piProviderDshAdapter(providerId, provider, host) {
1116
+ return {
1117
+ providerInfo: (id) => ({
1118
+ id,
1119
+ name: typeof provider.name === "string" ? provider.name : id
1120
+ }),
1121
+ providerRetryPolicy: () => void 0,
1122
+ listModels: async (id) => providerModels(provider).map((model) => ({
1123
+ provider: id,
1124
+ id: String(model.id ?? ""),
1125
+ name: String(model.name ?? model.id ?? ""),
1126
+ ...Array.isArray(model.input) ? { inputModalities: model.input.filter((m) => m === "text" || m === "image") } : {}
1127
+ })),
1128
+ resolveModel: async (id, modelId) => {
1129
+ const known = providerModels(provider).find((model) => model.id === modelId);
1130
+ return {
1131
+ provider: id,
1132
+ id: modelId,
1133
+ name: String(known?.name ?? modelId),
1134
+ ...typeof known?.contextWindow === "number" ? { context: { contextWindow: known.contextWindow } } : {},
1135
+ ...typeof known?.maxTokens === "number" ? { defaultMaxTokens: known.maxTokens } : {}
1136
+ };
1137
+ },
1138
+ async *stream(options) {
1139
+ const auth = (await host.resolveAuth())?.auth;
1140
+ const modelId = String(options.model ?? "");
1141
+ const model = providerModels(provider).find((entry) => entry.id === modelId) ?? {
1142
+ id: modelId,
1143
+ provider: providerId
1144
+ };
1145
+ const piContext = dshRequestToPiContext(options);
1146
+ const piOptions = {
1147
+ ...auth?.apiKey === void 0 ? {} : { apiKey: auth.apiKey },
1148
+ ...auth?.baseUrl === void 0 ? {} : { baseUrl: auth.baseUrl },
1149
+ ...auth?.headers === void 0 ? {} : { headers: auth.headers },
1150
+ ...options.signal instanceof AbortSignal ? { signal: options.signal } : {},
1151
+ ...typeof options.maxTokens === "number" ? { maxTokens: options.maxTokens } : {},
1152
+ ...typeof options.temperature === "number" ? { temperature: options.temperature } : {}
1153
+ };
1154
+ yield* piEventsToDshChunks(provider.stream(model, piContext, piOptions));
1155
+ }
1156
+ };
1157
+ }
1158
+ /**
1159
+ * Register the provider as a live DSH route when it carries a transport and
1160
+ * an llm service is mounted. A route conflict (an adapter already owns the
1161
+ * name — e.g. a deployment-configured `deepseek`) keeps the existing route
1162
+ * and reports the skip; the package's registry entry still stands.
1163
+ * @returns the route disposer, or undefined when nothing was registered.
1164
+ */
1165
+ function registerPiProviderRoute(options) {
1166
+ const { llm, providerId, provider, host } = options;
1167
+ if (llm === void 0 || !providerCarriesTransport(provider)) return void 0;
1168
+ try {
1169
+ return llm.registerAdapter([providerId], piProviderDshAdapter(providerId, provider, host));
1170
+ } catch (error) {
1171
+ host.warn(`[pi2dsh] Pi provider ${JSON.stringify(providerId)} was not added as a DSH llm route (${error instanceof Error ? error.message : String(error)}); the existing route keeps the name`);
1172
+ return;
1173
+ }
1174
+ }
1175
+ //#endregion
497
1176
  //#region src/runtime.ts
498
1177
  function logger(ctx) {
499
1178
  const candidate = ctx.logger;
@@ -734,6 +1413,26 @@ function agentSession(agent) {
734
1413
  if (typeof record.id !== "string") return void 0;
735
1414
  return record;
736
1415
  }
1416
+ function isSubagentOrigin(subject) {
1417
+ const session = subject?.session ?? subject;
1418
+ const header = session?.header;
1419
+ return header?.origin === "subagent" || (header?.meta)?.origin === "subagent" || (session?.meta)?.origin === "subagent";
1420
+ }
1421
+ function currentPiModel(state, agent) {
1422
+ const override = state.modelOverrides.get(agent);
1423
+ const options = agent.options;
1424
+ const provider = String(override?.provider ?? options?.provider ?? "");
1425
+ const id = String(override?.model ?? options?.model ?? "");
1426
+ if (id.length === 0) return override;
1427
+ return (provider.length > 0 ? state.modelCatalog?.find(provider, id) : void 0) ?? {
1428
+ id,
1429
+ name: id,
1430
+ provider,
1431
+ api: "dsh-llm",
1432
+ input: ["text"],
1433
+ reasoning: false
1434
+ };
1435
+ }
737
1436
  function thinkingLevelOf(state, agent) {
738
1437
  if (agent !== void 0) {
739
1438
  const scoped = state.thinkingLevels.get(agent);
@@ -818,6 +1517,29 @@ function contextFor(ctx, state, agent, signal, command = false) {
818
1517
  };
819
1518
  const session = agentSession(agent);
820
1519
  const providerConfig = (name) => state.providers.get(name);
1520
+ const catalog = state.modelCatalog;
1521
+ const piProviderModels = () => {
1522
+ const out = [];
1523
+ for (const [key, value] of state.providers) {
1524
+ let models;
1525
+ try {
1526
+ models = typeof value.getModels === "function" ? value.getModels() : value.models;
1527
+ } catch {
1528
+ continue;
1529
+ }
1530
+ if (!Array.isArray(models)) continue;
1531
+ for (const model of models) out.push({
1532
+ ...model,
1533
+ provider: String(model.provider ?? key)
1534
+ });
1535
+ }
1536
+ return out;
1537
+ };
1538
+ const allModels = () => {
1539
+ const fromLlm = catalog?.all() ?? [];
1540
+ const seen = new Set(fromLlm.map((model) => `${model.provider} ${model.id}`));
1541
+ return [...fromLlm, ...piProviderModels().filter((model) => !seen.has(`${String(model.provider)} ${String(model.id)}`))];
1542
+ };
821
1543
  const base = {
822
1544
  ui,
823
1545
  mode: "rpc",
@@ -828,11 +1550,47 @@ function contextFor(ctx, state, agent, signal, command = false) {
828
1550
  events: []
829
1551
  }, cwdOf(agent)) : state.bridge.readonlySessionManager(session, cwdOf(agent)),
830
1552
  modelRegistry: {
831
- getAll: () => [],
832
- getAvailable: () => [],
833
- find: (_provider, _modelId) => void 0,
1553
+ getAll: () => allModels(),
1554
+ getAvailable: () => allModels(),
1555
+ find: (provider, modelId) => catalog?.find(provider, modelId) ?? piProviderModels().find((model) => model.provider === provider && model.id === modelId),
834
1556
  getError: () => void 0,
835
- hasConfiguredAuth: (_model) => false,
1557
+ hasConfiguredAuth: (model) => {
1558
+ const provider = String(model?.provider ?? "");
1559
+ return provider.length > 0 && (state.providers.has(provider) || (catalog?.all() ?? []).some((entry) => entry.provider === provider));
1560
+ },
1561
+ getApiKeyAndHeaders: async (model) => {
1562
+ const provider = String(model?.provider ?? "");
1563
+ const config = providerConfig(provider) ?? await realBuiltinProvider(provider);
1564
+ if (config !== void 0) {
1565
+ const auth = (await resolvePiProviderAuth({
1566
+ providerId: provider,
1567
+ providerConfig: config,
1568
+ store: oauthStoreOf(state)
1569
+ }))?.auth;
1570
+ if (auth?.apiKey === void 0) return { ok: false };
1571
+ return {
1572
+ ok: true,
1573
+ apiKey: auth.apiKey,
1574
+ ...auth.headers === void 0 ? {} : { headers: auth.headers },
1575
+ ...auth.baseUrl === void 0 && config.baseUrl === void 0 ? {} : { baseUrl: auth.baseUrl ?? config.baseUrl }
1576
+ };
1577
+ }
1578
+ try {
1579
+ const entry = llmOf(ctx)?.listConfigurableProviders?.()?.find((candidate) => candidate.provider === provider);
1580
+ if (entry === void 0) return { ok: false };
1581
+ const section = ctx.get("settings")?.get(entry.settingsNs);
1582
+ const ref = entry.settingsPath.reduce((node, key) => typeof node === "object" && node !== null ? node[key] : void 0, section)?.apiKeyEnv;
1583
+ if (typeof ref !== "string" || ref.length === 0) return { ok: false };
1584
+ const resolved = await ctx.get("credentials")?.resolve(ref);
1585
+ if (typeof resolved?.value !== "string" || resolved.value.length === 0) return { ok: false };
1586
+ return {
1587
+ ok: true,
1588
+ apiKey: resolved.value
1589
+ };
1590
+ } catch {
1591
+ return { ok: false };
1592
+ }
1593
+ },
836
1594
  getProviderAuthStatus: (provider) => providerSupportsOAuth(providerConfig(provider)) ? "oauth" : "none",
837
1595
  getProvider: (provider) => providerConfig(provider),
838
1596
  getRegisteredProviderConfig: (provider) => providerConfig(provider),
@@ -843,42 +1601,44 @@ function contextFor(ctx, state, agent, signal, command = false) {
843
1601
  },
844
1602
  getProviderAuth: async (provider) => {
845
1603
  const config = providerConfig(provider);
846
- if (!providerSupportsOAuth(config)) return void 0;
847
- if (await storedOAuthCredential(oauthStoreOf(state), provider) === void 0) return void 0;
848
- const apiKey = await resolveOAuthApiKey({
1604
+ if (config === void 0) return void 0;
1605
+ const resolved = await resolvePiProviderAuth({
849
1606
  providerId: provider,
850
1607
  providerConfig: config,
851
1608
  store: oauthStoreOf(state)
852
1609
  });
853
- if (apiKey === void 0) return void 0;
854
- return {
1610
+ if (resolved?.auth === void 0) return resolved;
1611
+ return resolved.auth.baseUrl === void 0 && config.baseUrl !== void 0 ? {
1612
+ ...resolved,
855
1613
  auth: {
856
- apiKey,
1614
+ ...resolved.auth,
857
1615
  baseUrl: config.baseUrl
858
- },
859
- source: "OAuth"
860
- };
1616
+ }
1617
+ } : resolved;
861
1618
  },
862
1619
  getApiKeyForProvider: async (provider) => {
863
1620
  const config = providerConfig(provider);
864
- if (!providerSupportsOAuth(config)) return void 0;
865
- return resolveOAuthApiKey({
1621
+ if (config === void 0) return void 0;
1622
+ return ((await resolvePiProviderAuth({
866
1623
  providerId: provider,
867
1624
  providerConfig: config,
868
1625
  store: oauthStoreOf(state)
869
- });
1626
+ }))?.auth)?.apiKey;
870
1627
  },
871
1628
  isUsingOAuth: (model) => {
872
1629
  const provider = String(model?.provider ?? "");
873
1630
  return provider.length > 0 && providerSupportsOAuth(providerConfig(provider));
874
1631
  },
875
- refresh: async () => ({
876
- models: [],
877
- errors: []
878
- })
1632
+ refresh: async () => {
1633
+ await catalog?.refresh();
1634
+ return {
1635
+ models: catalog?.all() ?? [],
1636
+ errors: []
1637
+ };
1638
+ }
879
1639
  },
880
- model: agent === void 0 ? void 0 : state.modelOverrides.get(agent),
881
- scopedModels: [],
1640
+ model: agent === void 0 ? void 0 : currentPiModel(state, agent),
1641
+ scopedModels: catalog?.all() ?? [],
882
1642
  thinkingLevel: thinkingLevelOf(state, agent),
883
1643
  isIdle: () => command,
884
1644
  isProjectTrusted: () => false,
@@ -972,6 +1732,10 @@ function subscribeLifecycle(ctx, state) {
972
1732
  const warn = (event, error) => logger(ctx).warn(`[pi2dsh] ${event} handler failed: ${String(error)}`);
973
1733
  cordis.on("agent/session-start", (payload) => {
974
1734
  const agent = payload.agent;
1735
+ if (isSubagentOrigin(agent)) {
1736
+ state.activeAgents.add(agent);
1737
+ return;
1738
+ }
975
1739
  state.activeAgents.add(agent);
976
1740
  const session = agentSession(agent);
977
1741
  if (session !== void 0) state.bridge.load(session.id);
@@ -990,13 +1754,14 @@ function subscribeLifecycle(ctx, state) {
990
1754
  }
991
1755
  if (typeof agent === "object" && agent !== null && !state.disposedAgents.has(agent)) {
992
1756
  state.disposedAgents.add(agent);
993
- dispatch(state, "session_shutdown", {
1757
+ if (!isSubagentOrigin(agent)) dispatch(state, "session_shutdown", {
994
1758
  type: "session_shutdown",
995
1759
  reason: "quit"
996
1760
  }, contextFor(ctx, state, agent, void 0)).catch((error) => warn("session_shutdown", error));
997
1761
  }
998
1762
  });
999
1763
  cordis.on("session/event", (session, event) => {
1764
+ if (isSubagentOrigin(session)) return;
1000
1765
  const agent = [...state.activeAgents].find((candidate) => candidate.session === session);
1001
1766
  const eventContext = contextFor(ctx, state, agent, void 0);
1002
1767
  const type = event.type;
@@ -1246,6 +2011,13 @@ function oauthStoreOf(state) {
1246
2011
  function ensureLoginCommand(ctx, state) {
1247
2012
  if (state.loginCommandRegistered === true) return;
1248
2013
  state.loginCommandRegistered = true;
2014
+ try {
2015
+ registerLoginCommand(ctx, state);
2016
+ } catch (error) {
2017
+ logger(ctx).warn(`[pi2dsh] /login is already registered by an earlier package in this host; this package's providers use that command (${error instanceof Error ? error.message : String(error)})`);
2018
+ }
2019
+ }
2020
+ function registerLoginCommand(ctx, state) {
1249
2021
  registerCommand(ctx, state, {
1250
2022
  name: "login",
1251
2023
  description: "Log in to a Pi provider through its own OAuth flow",
@@ -1488,16 +2260,66 @@ function createPiApi(ctx, state) {
1488
2260
  },
1489
2261
  getFlag: (name) => state.flags.get(name),
1490
2262
  registerProvider(providerOrName, config) {
1491
- const name = typeof providerOrName === "string" ? providerOrName : String(providerOrName?.name ?? "unnamed");
2263
+ const name = typeof providerOrName === "string" ? providerOrName : String(providerOrName?.id ?? providerOrName?.name ?? "unnamed");
1492
2264
  const value = typeof providerOrName === "string" ? config ?? {} : providerOrName;
1493
2265
  state.providers.set(name, value);
1494
2266
  if (providerSupportsOAuth(value)) {
1495
2267
  ensureLoginCommand(ctx, state);
1496
2268
  logger(ctx).info(`[pi2dsh] Pi provider ${JSON.stringify(name)} supports OAuth — log in with /login ${name}`);
1497
- } else logger(ctx).info(`[pi2dsh] recorded Pi provider ${JSON.stringify(name)}; model calls stay on DSH llm adapters`);
2269
+ }
2270
+ const routeDisposer = registerPiProviderRoute({
2271
+ llm: llmOf(ctx),
2272
+ providerId: name,
2273
+ provider: value,
2274
+ host: {
2275
+ resolveAuth: async () => resolvePiProviderAuth({
2276
+ providerId: name,
2277
+ providerConfig: value,
2278
+ store: oauthStoreOf(state)
2279
+ }),
2280
+ warn: (message) => logger(ctx).warn(message)
2281
+ }
2282
+ });
2283
+ if (routeDisposer !== void 0) {
2284
+ state.providerRouteDisposers.set(name, routeDisposer);
2285
+ state.modelCatalog?.refresh();
2286
+ logger(ctx).info(`[pi2dsh] Pi provider ${JSON.stringify(name)} registered as a native DSH llm route`);
2287
+ } else if (!providerSupportsOAuth(value)) logger(ctx).info(`[pi2dsh] recorded Pi provider ${JSON.stringify(name)}; model calls stay on DSH llm adapters`);
2288
+ const refreshModels = value.refreshModels;
2289
+ if (typeof refreshModels === "function") (async () => {
2290
+ const apiKey = ((await resolvePiProviderAuth({
2291
+ providerId: name,
2292
+ providerConfig: value,
2293
+ store: oauthStoreOf(state)
2294
+ }).catch(() => void 0))?.auth)?.apiKey;
2295
+ await Promise.resolve(refreshModels.call(value, {
2296
+ stored: void 0,
2297
+ ...apiKey === void 0 ? {} : { credential: {
2298
+ type: "api_key",
2299
+ key: apiKey
2300
+ } },
2301
+ store: {
2302
+ read: async () => void 0,
2303
+ write: async () => {},
2304
+ delete: async () => {}
2305
+ },
2306
+ allowNetwork: true,
2307
+ signal: new AbortController().signal,
2308
+ publish: async (publication) => {
2309
+ publication.update?.();
2310
+ return true;
2311
+ }
2312
+ }));
2313
+ state.modelCatalog?.refresh();
2314
+ })().catch((error) => logger(ctx).warn(`[pi2dsh] model catalog refresh for Pi provider ${JSON.stringify(name)} failed (its registry entries stay static): ${error instanceof Error ? error.message : String(error)}`));
1498
2315
  },
1499
2316
  unregisterProvider(name) {
1500
2317
  state.providers.delete(name);
2318
+ const routeDisposer = state.providerRouteDisposers.get(name);
2319
+ if (routeDisposer !== void 0) {
2320
+ state.providerRouteDisposers.delete(name);
2321
+ routeDisposer();
2322
+ }
1501
2323
  },
1502
2324
  registerMessageRenderer(customType, renderer) {
1503
2325
  state.messageRenderers.set(customType, renderer);
@@ -1769,7 +2591,8 @@ async function applyPiPackage(ctx, options) {
1769
2591
  globalThinkingLevel: "off",
1770
2592
  argMutations: /* @__PURE__ */ new WeakMap(),
1771
2593
  streamingTexts: /* @__PURE__ */ new Map(),
1772
- lastLoggedModels: /* @__PURE__ */ new WeakMap()
2594
+ lastLoggedModels: /* @__PURE__ */ new WeakMap(),
2595
+ providerRouteDisposers: /* @__PURE__ */ new Map()
1773
2596
  };
1774
2597
  subscribeLifecycle(ctx, state);
1775
2598
  subscribeInterceptors(ctx, state);
@@ -1791,6 +2614,20 @@ async function applyPiPackage(ctx, options) {
1791
2614
  });
1792
2615
  }
1793
2616
  }
2617
+ const llm = llmOf(ctx);
2618
+ state.modelCatalog = new ModelCatalog(llm);
2619
+ if (llm !== void 0) {
2620
+ ctx.on("llm/adapters-updated", () => {
2621
+ state.modelCatalog?.refresh();
2622
+ });
2623
+ await state.modelCatalog.refresh();
2624
+ __setPiAiLlmBridge((model, context, callOptions) => streamViaDshLlm(llm, {
2625
+ model,
2626
+ context,
2627
+ options: callOptions
2628
+ }));
2629
+ ctx.effect(() => () => __setPiAiLlmBridge(void 0));
2630
+ }
1794
2631
  __setSubagentSessionFactory((subagentOptions) => createBridgedAgentSession({
1795
2632
  cordis: ctx,
1796
2633
  cwd: () => cwdOf(currentAgent(state)),
@@ -1817,4 +2654,4 @@ const runtimeInternals = {
1817
2654
  //#endregion
1818
2655
  export { normalizeToolSchema as n, runtimeInternals as r, applyPiPackage as t };
1819
2656
 
1820
- //# sourceMappingURL=runtime-6DefpI6h.mjs.map
2657
+ //# sourceMappingURL=runtime-B9R7bDWT.mjs.map