pi2dsh 0.3.0 → 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 (104) hide show
  1. package/README.md +31 -11
  2. package/README.zh.md +31 -11
  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 +25 -3
  13. package/dist/compat/pi-ai.d.mts.map +1 -1
  14. package/dist/compat/pi-ai.mjs +3 -2919
  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.d.mts +27 -0
  19. package/dist/credentials-oauth.d.mts.map +1 -0
  20. package/dist/credentials-oauth.mjs +91 -0
  21. package/dist/credentials-oauth.mjs.map +1 -0
  22. package/dist/deferred-tools-BD4g5QUx.mjs +32 -0
  23. package/dist/deferred-tools-BD4g5QUx.mjs.map +1 -0
  24. package/dist/diagnostics-CJye1UFh.mjs +99 -0
  25. package/dist/diagnostics-CJye1UFh.mjs.map +1 -0
  26. package/dist/dist-IQKOgQyt.mjs +10035 -0
  27. package/dist/dist-IQKOgQyt.mjs.map +1 -0
  28. package/dist/dist-QBBA-dL_.mjs +1246 -0
  29. package/dist/dist-QBBA-dL_.mjs.map +1 -0
  30. package/dist/error-body-DKA3lknR.mjs +104 -0
  31. package/dist/error-body-DKA3lknR.mjs.map +1 -0
  32. package/dist/github-copilot-headers-CZo2QHrt.mjs +25 -0
  33. package/dist/github-copilot-headers-CZo2QHrt.mjs.map +1 -0
  34. package/dist/google-generative-ai-B7OZs5lu.mjs +358 -0
  35. package/dist/google-generative-ai-B7OZs5lu.mjs.map +1 -0
  36. package/dist/google-shared-CRsAUItF.mjs +29042 -0
  37. package/dist/google-shared-CRsAUItF.mjs.map +1 -0
  38. package/dist/google-vertex-DmdaHQtB.mjs +402 -0
  39. package/dist/google-vertex-DmdaHQtB.mjs.map +1 -0
  40. package/dist/hash-Dbggamac.mjs +19 -0
  41. package/dist/hash-Dbggamac.mjs.map +1 -0
  42. package/dist/headers-CPzZhJQn.mjs +17 -0
  43. package/dist/headers-CPzZhJQn.mjs.map +1 -0
  44. package/dist/host.d.mts.map +1 -1
  45. package/dist/host.mjs +15 -3
  46. package/dist/host.mjs.map +1 -1
  47. package/dist/images-models-DJtHo9Rp.mjs +1068 -0
  48. package/dist/images-models-DJtHo9Rp.mjs.map +1 -0
  49. package/dist/index.d.mts.map +1 -1
  50. package/dist/index.mjs +1 -1
  51. package/dist/json-parse-CFyFZSLJ.mjs +384 -0
  52. package/dist/json-parse-CFyFZSLJ.mjs.map +1 -0
  53. package/dist/{mcp-config-gqg7GJEE.mjs → mcp-config-D2B3kv-Z.mjs} +34 -16
  54. package/dist/mcp-config-D2B3kv-Z.mjs.map +1 -0
  55. package/dist/mistral-conversations-HHZCcFv_.mjs +42007 -0
  56. package/dist/mistral-conversations-HHZCcFv_.mjs.map +1 -0
  57. package/dist/multipart-parser-CIa5BbwO.mjs +301 -0
  58. package/dist/multipart-parser-CIa5BbwO.mjs.map +1 -0
  59. package/dist/oauth-bridge-C6fL1qSv.mjs +375 -0
  60. package/dist/oauth-bridge-C6fL1qSv.mjs.map +1 -0
  61. package/dist/openai-DkzRh5_G.mjs +6732 -0
  62. package/dist/openai-DkzRh5_G.mjs.map +1 -0
  63. package/dist/openai-codex-responses-vmovNu-q.mjs +1169 -0
  64. package/dist/openai-codex-responses-vmovNu-q.mjs.map +1 -0
  65. package/dist/openai-completions-fXHhRolF.mjs +947 -0
  66. package/dist/openai-completions-fXHhRolF.mjs.map +1 -0
  67. package/dist/openai-prompt-cache-BDWtZRKh.mjs +11 -0
  68. package/dist/openai-prompt-cache-BDWtZRKh.mjs.map +1 -0
  69. package/dist/openai-responses-DKdCYf3h.mjs +247 -0
  70. package/dist/openai-responses-DKdCYf3h.mjs.map +1 -0
  71. package/dist/openai-responses-shared-D0Jq7yVP.mjs +572 -0
  72. package/dist/openai-responses-shared-D0Jq7yVP.mjs.map +1 -0
  73. package/dist/openrouter-images-fG3Aj6M4.mjs +127 -0
  74. package/dist/openrouter-images-fG3Aj6M4.mjs.map +1 -0
  75. package/dist/otel-BQSQRBLF.mjs +24935 -0
  76. package/dist/otel-BQSQRBLF.mjs.map +1 -0
  77. package/dist/pi-ai-DhYGgq4Y.mjs +2984 -0
  78. package/dist/pi-ai-DhYGgq4Y.mjs.map +1 -0
  79. package/dist/{pi-coding-agent-Z1hTs61i.mjs → pi-coding-agent-fPHwKP4d.mjs} +27 -3
  80. package/dist/{pi-coding-agent-Z1hTs61i.mjs.map → pi-coding-agent-fPHwKP4d.mjs.map} +1 -1
  81. package/dist/pi-messages-CV-Ts8An.mjs +306 -0
  82. package/dist/pi-messages-CV-Ts8An.mjs.map +1 -0
  83. package/dist/provider-env-C72pF4vP.mjs +40 -0
  84. package/dist/provider-env-C72pF4vP.mjs.map +1 -0
  85. package/dist/provider-retry-Cw6YJ4cr.mjs +80 -0
  86. package/dist/provider-retry-Cw6YJ4cr.mjs.map +1 -0
  87. package/dist/rolldown-runtime-CTfmNlz1.mjs +44 -0
  88. package/dist/{runtime-oLd2EInK.mjs → runtime-B9R7bDWT.mjs} +838 -345
  89. package/dist/runtime-B9R7bDWT.mjs.map +1 -0
  90. package/dist/runtime.d.mts.map +1 -1
  91. package/dist/runtime.mjs +1 -1
  92. package/dist/sanitize-unicode-wLA11KvZ.mjs +29 -0
  93. package/dist/sanitize-unicode-wLA11KvZ.mjs.map +1 -0
  94. package/dist/src-C3WBfOks.mjs +5031 -0
  95. package/dist/src-C3WBfOks.mjs.map +1 -0
  96. package/dist/transform-messages-DGq9mvhA.mjs +379 -0
  97. package/dist/transform-messages-DGq9mvhA.mjs.map +1 -0
  98. package/dist/uuid-DtFT7tOE.mjs +65 -0
  99. package/dist/uuid-DtFT7tOE.mjs.map +1 -0
  100. package/package.json +11 -1
  101. package/dist/compat/pi-ai.mjs.map +0 -1
  102. package/dist/mcp-config-gqg7GJEE.mjs.map +0 -1
  103. package/dist/rolldown-runtime-D-uZhY3_.mjs +0 -16
  104. package/dist/runtime-oLd2EInK.mjs.map +0 -1
@@ -1,16 +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";
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";
4
6
  import { createRequire } from "node:module";
5
- import { access, mkdir, readFile, rename, writeFile } from "node:fs/promises";
6
- import { dirname, join } from "node:path";
7
+ import { access, readFile } from "node:fs/promises";
8
+ import { join } from "node:path";
7
9
  import { fileURLToPath } from "node:url";
10
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
8
11
  import { EventEmitter } from "node:events";
9
12
  import { AsyncLocalStorage } from "node:async_hooks";
10
13
  import { createJiti } from "jiti";
11
14
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
12
15
  import { renderPrompt } from "@deepseek-ai/dsh-system-prompt";
13
- import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
14
16
  import { randomUUID } from "node:crypto";
15
17
  //#region src/session-bridge.ts
16
18
  function sidecarDir() {
@@ -433,11 +435,13 @@ var PiBridgedAgentSession = class {
433
435
  }
434
436
  abort() {
435
437
  this.#aborted = true;
436
- const cancel = this.#handle.agent.cancel;
437
- cancel?.({
438
- kind: "hook",
439
- reason: "pi2dsh subagent abort()"
440
- });
438
+ try {
439
+ const cancel = this.#handle.agent.cancel;
440
+ cancel?.({
441
+ kind: "hook",
442
+ reason: "pi2dsh subagent abort()"
443
+ });
444
+ } catch {}
441
445
  }
442
446
  setSessionName(name) {
443
447
  this.#sessionName = String(name);
@@ -477,14 +481,34 @@ async function createBridgedAgentSession(host, options) {
477
481
  const sessionId = `pi2dsh-sub-${Date.now().toString(36)}-${subagentSerial}`;
478
482
  let handle;
479
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;
480
489
  handle = await agents.create({
481
490
  sessionId,
482
491
  meta: {
483
492
  cwd: typeof options.cwd === "string" ? options.cwd : host.cwd(),
484
493
  origin: "subagent",
485
494
  ...host.parentSessionId() !== void 0 ? { parentSession: host.parentSessionId() } : {}
486
- }
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
+ } } : {}
487
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
+ }
488
512
  } catch (error) {
489
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)})`);
490
514
  }
@@ -492,356 +516,661 @@ async function createBridgedAgentSession(host, options) {
492
516
  return { session: new PiBridgedAgentSession(host, handle, tools) };
493
517
  }
494
518
  //#endregion
495
- //#region src/compat/vendor/pi-ai-abort.ts
496
- function abortReason(signal) {
497
- if (signal.reason !== void 0) return signal.reason;
498
- const error = /* @__PURE__ */ new Error("The operation was aborted");
499
- error.name = "AbortError";
500
- return error;
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
+ }
501
528
  }
502
- /** Create an operation-local signal for public APIs whose signal is optional. */
503
- function operationSignal(signal) {
504
- return signal ?? new AbortController().signal;
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
+ };
505
539
  }
506
540
  /**
507
- * Stop waiting for an operation when its signal aborts while continuing to
508
- * observe the abandoned promise so a later rejection is always handled.
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).
509
544
  */
510
- function raceWithAbortSignal(operation, signal) {
511
- if (signal.aborted) {
512
- operation.catch(() => {});
513
- return Promise.reject(abortReason(signal));
545
+ var ModelCatalog = class {
546
+ #llm;
547
+ #models = [];
548
+ #resolved = /* @__PURE__ */ new Map();
549
+ #refreshing;
550
+ constructor(llm) {
551
+ this.#llm = llm;
514
552
  }
515
- return new Promise((resolve, reject) => {
516
- let settled = false;
517
- const cleanup = () => signal.removeEventListener("abort", onAbort);
518
- const onAbort = () => {
519
- if (settled) return;
520
- settled = true;
521
- cleanup();
522
- reject(abortReason(signal));
523
- };
524
- signal.addEventListener("abort", onAbort, { once: true });
525
- operation.then((value) => {
526
- if (settled) return;
527
- settled = true;
528
- cleanup();
529
- resolve(value);
530
- }, (error) => {
531
- if (settled) return;
532
- settled = true;
533
- cleanup();
534
- reject(error);
535
- });
536
- if (signal.aborted) onAbort();
537
- });
538
- }
539
- //#endregion
540
- //#region src/compat/vendor/pi-ai-credential-store.ts
541
- /**
542
- * Default in-memory credential store. Apps inject persistent stores.
543
- * Keyed by `Provider.id`, one credential per provider; see `CredentialStore`.
544
- * Writes are serialized per provider through a promise chain.
545
- */
546
- var InMemoryCredentialStore = class {
547
- credentials = /* @__PURE__ */ new Map();
548
- chains = /* @__PURE__ */ new Map();
549
- /** Serialize tasks per provider id without releasing the chain before active work settles. */
550
- enqueue(providerId, task, options) {
551
- const signal = operationSignal(options?.signal);
552
- const previous = this.chains.get(providerId) ?? Promise.resolve();
553
- const queued = (async () => {
554
- await previous.catch(() => {});
555
- signal.throwIfAborted();
556
- return task();
557
- })();
558
- const tail = queued.catch(() => {});
559
- this.chains.set(providerId, tail);
560
- tail.then(() => {
561
- if (this.chains.get(providerId) === tail) this.chains.delete(providerId);
562
- });
563
- return raceWithAbortSignal(queued, signal);
553
+ get present() {
554
+ return this.#llm !== void 0;
564
555
  }
565
- async read(providerId, options) {
566
- options?.signal?.throwIfAborted();
567
- return this.credentials.get(providerId);
556
+ all() {
557
+ return [...this.#models];
568
558
  }
569
- async list(options) {
570
- options?.signal?.throwIfAborted();
571
- return [...this.credentials].map(([providerId, credential]) => ({
572
- providerId,
573
- type: credential.type
574
- }));
559
+ find(provider, modelId) {
560
+ return this.#resolved.get(`${provider}${modelId}`) ?? this.#models.find((model) => model.provider === provider && model.id === modelId);
575
561
  }
576
- modify(providerId, fn, options) {
577
- return this.enqueue(providerId, async () => {
578
- const current = this.credentials.get(providerId);
579
- const next = await fn(current);
580
- options?.signal?.throwIfAborted();
581
- if (next !== void 0) this.credentials.set(providerId, next);
582
- return next ?? current;
583
- }, options);
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;
584
568
  }
585
- delete(providerId, options) {
586
- return this.enqueue(providerId, async () => {
587
- this.credentials.delete(providerId);
588
- }, options);
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;
589
580
  }
590
- };
591
- //#endregion
592
- //#region src/compat/vendor/pi-ai-diagnostics.ts
593
- function formatThrownValue(value) {
594
- if (value instanceof Error) return value.message || value.name;
595
- if (typeof value === "string") return value;
596
- return String(value);
597
- }
598
- //#endregion
599
- //#region src/compat/vendor/pi-ai-auth-resolve.ts
600
- var ModelsError = class extends Error {
601
- code;
602
- constructor(code, message, options) {
603
- super(withCauseDetail(message, options?.cause), options);
604
- this.name = "ModelsError";
605
- this.code = code;
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
+ }
606
601
  }
607
602
  };
608
- /** Callers surface `error.message` only, so keep the underlying reason in it. */
609
- function withCauseDetail(message, cause) {
610
- if (cause === void 0 || cause === null) return message;
611
- const detail = formatThrownValue(cause).trim();
612
- if (!detail || message.includes(detail)) return message;
613
- return `${message}: ${detail}`;
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;
614
629
  }
615
630
  /**
616
- * Auth resolution shared by the `Models` and `ImagesModels` collections.
617
- * A stored credential owns the provider: ambient/env is consulted only when
618
- * nothing is stored. No silent env fallback after a failed refresh or for a
619
- * credential type without a matching handler.
631
+ * Pi request context DSH GenerateOptions parts. System prompt moves to the
632
+ * dedicated slot; toolResult messages become DSH tool-result user messages.
620
633
  */
621
- function resolveProviderAuth(provider, credentials, authContext, overrides) {
622
- const signal = operationSignal(overrides?.signal);
623
- return raceWithAbortSignal(resolveProviderAuthWithSignal(provider, credentials, authContext, overrides, signal), signal);
624
- }
625
- async function resolveProviderAuthWithSignal(provider, credentials, authContext, overrides, signal) {
626
- signal.throwIfAborted();
627
- const requestAuthContext = overrides?.env ? overlayEnvAuthContext(authContext, overrides.env) : authContext;
628
- if (overrides?.apiKey !== void 0 && provider.auth.apiKey) return resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, {
629
- type: "api_key",
630
- key: overrides.apiKey,
631
- env: overrides.env
632
- }, signal);
633
- const stored = await readCredential(credentials, provider.id, signal);
634
- if (stored) {
635
- if (stored.type === "oauth" && provider.auth.oauth) return resolveStoredOAuth(credentials, provider.id, provider.auth.oauth, stored, signal, overrides?.minOAuthValidityMs);
636
- if (stored.type === "api_key" && provider.auth.apiKey) {
637
- const credential = overrides?.env ? {
638
- ...stored,
639
- env: {
640
- ...stored.env,
641
- ...overrides.env
642
- }
643
- } : stored;
644
- return resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, credential, signal);
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")
645
649
  }
646
- return;
647
- }
648
- return provider.auth.apiKey ? resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, void 0, signal) : void 0;
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
+ };
649
669
  }
650
- function overlayEnvAuthContext(base, env) {
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);
651
675
  return {
652
- env: async (name) => env[name] || await base.env(name),
653
- fileExists: (path) => base.fileExists(path)
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
+ }
654
688
  };
655
689
  }
656
- const DEFAULT_OAUTH_MINIMUM_VALIDITY_MS = 3e5;
657
- const DEFAULT_OAUTH_REFRESH_TIMEOUT_MS = 15e3;
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
+ }
658
699
  /**
659
- * OAuth resolution with double-checked locking: tokens with less than five
660
- * minutes remaining lock, re-check expiry under the lock, refresh once
661
- * globally, and persist the rotated credential before release.
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.
662
702
  */
663
- async function resolveStoredOAuth(credentials, providerId, oauth, stored, signal, minOAuthValidityMs) {
664
- const minimumValidityMs = Math.max(DEFAULT_OAUTH_MINIMUM_VALIDITY_MS, minOAuthValidityMs ?? 0);
665
- const expiresSoon = (credential) => Date.now() + minimumValidityMs >= credential.expires;
666
- let credential = stored;
667
- if (expiresSoon(credential)) {
668
- let post;
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
+ });
669
737
  try {
670
- post = await credentials.modify(providerId, async (current) => {
671
- if (current?.type !== "oauth") return void 0;
672
- if (!expiresSoon(current)) return void 0;
673
- try {
674
- const refreshSignal = AbortSignal.any([signal, AbortSignal.timeout(DEFAULT_OAUTH_REFRESH_TIMEOUT_MS)]);
675
- return await oauth.refresh(current, refreshSignal);
676
- } catch (error) {
677
- throw new ModelsError("oauth", `OAuth refresh failed for ${providerId}`, { cause: error });
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;
678
896
  }
679
- }, { signal });
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
+ });
680
904
  } catch (error) {
681
- if (error instanceof ModelsError) throw error;
682
- throw new ModelsError("auth", `Credential store modify failed for ${providerId}`, { cause: error });
905
+ stream.push({
906
+ type: "error",
907
+ reason: "error",
908
+ error: error instanceof Error ? error : new Error(String(error)),
909
+ partial
910
+ });
683
911
  }
684
- if (post?.type !== "oauth") return void 0;
685
- credential = post;
686
- if (minOAuthValidityMs !== void 0 && expiresSoon(credential)) throw new ModelsError("oauth", `OAuth refresh returned a token that expires too soon for ${providerId}`);
687
- }
688
- try {
689
- return {
690
- auth: await oauth.toAuth(credential),
691
- source: "OAuth"
692
- };
693
- } catch (error) {
694
- throw new ModelsError("oauth", `OAuth auth derivation failed for ${providerId}`, { cause: error });
695
- }
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
+ }));
696
924
  }
697
- async function resolveApiKey(authContext, apiKey, providerId, credential, signal) {
698
- try {
699
- return await apiKey.resolve({
700
- ctx: authContext,
701
- credential,
702
- signal
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)
703
946
  });
704
- } catch (error) {
705
- throw new ModelsError("auth", `API key auth failed for provider ${providerId}`, { cause: error });
706
- }
707
- }
708
- async function readCredential(credentials, providerId, signal) {
709
- try {
710
- return await credentials.read(providerId, { signal });
711
- } catch (error) {
712
- throw new ModelsError("auth", `Credential store read failed for ${providerId}`, { cause: error });
713
947
  }
714
- }
715
- //#endregion
716
- //#region src/compat/vendor/pi-oauth-adapt.ts
717
- function adaptOAuth(config) {
718
948
  return {
719
- name: config.name,
720
- isSubscription: config.isSubscription,
721
- login: async (callbacks) => {
722
- return {
723
- ...await config.login({
724
- onAuth: (info) => callbacks.notify({
725
- type: "auth_url",
726
- ...info
727
- }),
728
- onDeviceCode: (info) => callbacks.notify({
729
- type: "device_code",
730
- ...info
731
- }),
732
- onPrompt: (prompt) => callbacks.prompt({
733
- type: "text",
734
- ...prompt
735
- }),
736
- onProgress: (message) => callbacks.notify({
737
- type: "progress",
738
- message
739
- }),
740
- onManualCodeInput: () => callbacks.prompt({
741
- type: "manual_code",
742
- message: "Paste the authorization code"
743
- }),
744
- onSelect: (prompt) => callbacks.prompt({
745
- type: "select",
746
- ...prompt
747
- }),
748
- signal: callbacks.signal
749
- }),
750
- type: "oauth"
751
- };
752
- },
753
- refresh: async (credential, signal) => ({
754
- ...await config.refreshToken(credential, signal),
755
- type: "oauth"
756
- }),
757
- toAuth: async (credential) => ({ apiKey: config.getApiKey(credential) })
949
+ ...typeof options.system === "string" && options.system.length > 0 ? { systemPrompt: options.system } : {},
950
+ messages: piMessages,
951
+ ...Array.isArray(options.tools) ? { tools: options.tools } : {}
758
952
  };
759
953
  }
760
- //#endregion
761
- //#region src/oauth-bridge.ts
762
- var FileCredentialStore = class extends InMemoryCredentialStore {
763
- #path;
764
- constructor(path) {
765
- super();
766
- this.#path = path;
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;
767
966
  try {
768
- const data = JSON.parse(readFileSync(path, "utf8"));
769
- for (const [providerId, credential] of Object.entries(data)) this.credentials.set(providerId, credential);
770
- } catch {}
771
- }
772
- get path() {
773
- return this.#path;
774
- }
775
- async #persist() {
776
- const credentials = this.credentials;
777
- const data = Object.fromEntries(credentials);
778
- await mkdir(dirname(this.#path), { recursive: true });
779
- const temp = `${this.#path}.tmp-${process.pid}-${Math.floor(Math.random() * 1e9).toString(36)}`;
780
- await writeFile(temp, `${JSON.stringify(data, null, 2)}\n`, { mode: 384 });
781
- await rename(temp, this.#path);
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
+ });
782
977
  }
783
- modify(providerId, fn, options) {
784
- return super.modify(providerId, async (current) => {
785
- const next = await fn(current);
786
- if (next !== void 0) {
787
- this.credentials.set(providerId, next);
788
- await this.#persist();
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 ?? "")
789
1020
  }
790
- return next;
791
- }, options);
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
+ }
792
1086
  }
793
- delete(providerId, options) {
794
- return super.delete(providerId, options).then(() => this.#persist());
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 [];
795
1109
  }
796
- };
797
- function oauthInteraction(ui, signal) {
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) {
798
1116
  return {
799
- signal: signal ?? new AbortController().signal,
800
- async prompt(prompt) {
801
- if (prompt.type === "select") {
802
- const options = prompt.options ?? [];
803
- const picked = await ui.select(prompt.message, options.map((option) => option.label));
804
- return options.find((option) => option.label === picked)?.id ?? picked;
805
- }
806
- return ui.input(prompt.message, prompt.placeholder);
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
+ };
807
1137
  },
808
- notify(event) {
809
- if (event.type === "auth_url") ui.notify(`Open this URL to authorize: ${event.url}${event.instructions !== void 0 ? `\n${event.instructions}` : ""}`);
810
- else if (event.type === "device_code") ui.notify(`Visit ${event.verificationUri} and enter code ${event.userCode}`);
811
- else if (event.message !== void 0) ui.notify(event.message);
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));
812
1155
  }
813
1156
  };
814
1157
  }
815
- function oauthConfigOf(providerConfig) {
816
- const oauth = providerConfig?.oauth;
817
- return typeof oauth === "object" && oauth !== null && typeof oauth.login === "function" ? oauth : void 0;
818
- }
819
- function oauthAdapterOf(oauthConfig) {
820
- if (typeof oauthConfig.toAuth === "function") return oauthConfig;
821
- return adaptOAuth(oauthConfig);
822
- }
823
- function providerSupportsOAuth(providerConfig) {
824
- return oauthConfigOf(providerConfig) !== void 0;
825
- }
826
- async function loginPiProvider(options) {
827
- const oauthConfig = oauthConfigOf(options.providerConfig);
828
- if (oauthConfig === void 0) throw new Error(`${options.providerName ?? options.providerId} does not support oauth login`);
829
- const credential = await oauthAdapterOf(oauthConfig).login(oauthInteraction(options.ui, options.signal));
830
- await options.store.modify(options.providerId, async () => credential);
831
- return credential;
832
- }
833
- async function resolveOAuthApiKey(options) {
834
- const oauthConfig = oauthConfigOf(options.providerConfig);
835
- if (oauthConfig === void 0) return void 0;
836
- return (await resolveProviderAuth({
837
- id: options.providerId,
838
- name: options.providerName ?? options.providerId,
839
- auth: { oauth: oauthAdapterOf(oauthConfig) }
840
- }, options.store, { env: async () => void 0 }, options.signal !== void 0 ? { signal: options.signal } : void 0))?.auth?.apiKey;
841
- }
842
- async function storedOAuthCredential(store, providerId) {
843
- const stored = await store.read(providerId, void 0);
844
- return stored?.type === "oauth" ? stored : void 0;
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
+ }
845
1174
  }
846
1175
  //#endregion
847
1176
  //#region src/runtime.ts
@@ -1084,6 +1413,26 @@ function agentSession(agent) {
1084
1413
  if (typeof record.id !== "string") return void 0;
1085
1414
  return record;
1086
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
+ }
1087
1436
  function thinkingLevelOf(state, agent) {
1088
1437
  if (agent !== void 0) {
1089
1438
  const scoped = state.thinkingLevels.get(agent);
@@ -1168,6 +1517,29 @@ function contextFor(ctx, state, agent, signal, command = false) {
1168
1517
  };
1169
1518
  const session = agentSession(agent);
1170
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
+ };
1171
1543
  const base = {
1172
1544
  ui,
1173
1545
  mode: "rpc",
@@ -1178,11 +1550,47 @@ function contextFor(ctx, state, agent, signal, command = false) {
1178
1550
  events: []
1179
1551
  }, cwdOf(agent)) : state.bridge.readonlySessionManager(session, cwdOf(agent)),
1180
1552
  modelRegistry: {
1181
- getAll: () => [],
1182
- getAvailable: () => [],
1183
- 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),
1184
1556
  getError: () => void 0,
1185
- 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
+ },
1186
1594
  getProviderAuthStatus: (provider) => providerSupportsOAuth(providerConfig(provider)) ? "oauth" : "none",
1187
1595
  getProvider: (provider) => providerConfig(provider),
1188
1596
  getRegisteredProviderConfig: (provider) => providerConfig(provider),
@@ -1193,42 +1601,44 @@ function contextFor(ctx, state, agent, signal, command = false) {
1193
1601
  },
1194
1602
  getProviderAuth: async (provider) => {
1195
1603
  const config = providerConfig(provider);
1196
- if (!providerSupportsOAuth(config)) return void 0;
1197
- if (await storedOAuthCredential(oauthStoreOf(state), provider) === void 0) return void 0;
1198
- const apiKey = await resolveOAuthApiKey({
1604
+ if (config === void 0) return void 0;
1605
+ const resolved = await resolvePiProviderAuth({
1199
1606
  providerId: provider,
1200
1607
  providerConfig: config,
1201
1608
  store: oauthStoreOf(state)
1202
1609
  });
1203
- if (apiKey === void 0) return void 0;
1204
- return {
1610
+ if (resolved?.auth === void 0) return resolved;
1611
+ return resolved.auth.baseUrl === void 0 && config.baseUrl !== void 0 ? {
1612
+ ...resolved,
1205
1613
  auth: {
1206
- apiKey,
1614
+ ...resolved.auth,
1207
1615
  baseUrl: config.baseUrl
1208
- },
1209
- source: "OAuth"
1210
- };
1616
+ }
1617
+ } : resolved;
1211
1618
  },
1212
1619
  getApiKeyForProvider: async (provider) => {
1213
1620
  const config = providerConfig(provider);
1214
- if (!providerSupportsOAuth(config)) return void 0;
1215
- return resolveOAuthApiKey({
1621
+ if (config === void 0) return void 0;
1622
+ return ((await resolvePiProviderAuth({
1216
1623
  providerId: provider,
1217
1624
  providerConfig: config,
1218
1625
  store: oauthStoreOf(state)
1219
- });
1626
+ }))?.auth)?.apiKey;
1220
1627
  },
1221
1628
  isUsingOAuth: (model) => {
1222
1629
  const provider = String(model?.provider ?? "");
1223
1630
  return provider.length > 0 && providerSupportsOAuth(providerConfig(provider));
1224
1631
  },
1225
- refresh: async () => ({
1226
- models: [],
1227
- errors: []
1228
- })
1632
+ refresh: async () => {
1633
+ await catalog?.refresh();
1634
+ return {
1635
+ models: catalog?.all() ?? [],
1636
+ errors: []
1637
+ };
1638
+ }
1229
1639
  },
1230
- model: agent === void 0 ? void 0 : state.modelOverrides.get(agent),
1231
- scopedModels: [],
1640
+ model: agent === void 0 ? void 0 : currentPiModel(state, agent),
1641
+ scopedModels: catalog?.all() ?? [],
1232
1642
  thinkingLevel: thinkingLevelOf(state, agent),
1233
1643
  isIdle: () => command,
1234
1644
  isProjectTrusted: () => false,
@@ -1322,6 +1732,10 @@ function subscribeLifecycle(ctx, state) {
1322
1732
  const warn = (event, error) => logger(ctx).warn(`[pi2dsh] ${event} handler failed: ${String(error)}`);
1323
1733
  cordis.on("agent/session-start", (payload) => {
1324
1734
  const agent = payload.agent;
1735
+ if (isSubagentOrigin(agent)) {
1736
+ state.activeAgents.add(agent);
1737
+ return;
1738
+ }
1325
1739
  state.activeAgents.add(agent);
1326
1740
  const session = agentSession(agent);
1327
1741
  if (session !== void 0) state.bridge.load(session.id);
@@ -1340,13 +1754,14 @@ function subscribeLifecycle(ctx, state) {
1340
1754
  }
1341
1755
  if (typeof agent === "object" && agent !== null && !state.disposedAgents.has(agent)) {
1342
1756
  state.disposedAgents.add(agent);
1343
- dispatch(state, "session_shutdown", {
1757
+ if (!isSubagentOrigin(agent)) dispatch(state, "session_shutdown", {
1344
1758
  type: "session_shutdown",
1345
1759
  reason: "quit"
1346
1760
  }, contextFor(ctx, state, agent, void 0)).catch((error) => warn("session_shutdown", error));
1347
1761
  }
1348
1762
  });
1349
1763
  cordis.on("session/event", (session, event) => {
1764
+ if (isSubagentOrigin(session)) return;
1350
1765
  const agent = [...state.activeAgents].find((candidate) => candidate.session === session);
1351
1766
  const eventContext = contextFor(ctx, state, agent, void 0);
1352
1767
  const type = event.type;
@@ -1596,6 +2011,13 @@ function oauthStoreOf(state) {
1596
2011
  function ensureLoginCommand(ctx, state) {
1597
2012
  if (state.loginCommandRegistered === true) return;
1598
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) {
1599
2021
  registerCommand(ctx, state, {
1600
2022
  name: "login",
1601
2023
  description: "Log in to a Pi provider through its own OAuth flow",
@@ -1838,16 +2260,66 @@ function createPiApi(ctx, state) {
1838
2260
  },
1839
2261
  getFlag: (name) => state.flags.get(name),
1840
2262
  registerProvider(providerOrName, config) {
1841
- const name = typeof providerOrName === "string" ? providerOrName : String(providerOrName?.name ?? "unnamed");
2263
+ const name = typeof providerOrName === "string" ? providerOrName : String(providerOrName?.id ?? providerOrName?.name ?? "unnamed");
1842
2264
  const value = typeof providerOrName === "string" ? config ?? {} : providerOrName;
1843
2265
  state.providers.set(name, value);
1844
2266
  if (providerSupportsOAuth(value)) {
1845
2267
  ensureLoginCommand(ctx, state);
1846
2268
  logger(ctx).info(`[pi2dsh] Pi provider ${JSON.stringify(name)} supports OAuth — log in with /login ${name}`);
1847
- } 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)}`));
1848
2315
  },
1849
2316
  unregisterProvider(name) {
1850
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
+ }
1851
2323
  },
1852
2324
  registerMessageRenderer(customType, renderer) {
1853
2325
  state.messageRenderers.set(customType, renderer);
@@ -2119,10 +2591,17 @@ async function applyPiPackage(ctx, options) {
2119
2591
  globalThinkingLevel: "off",
2120
2592
  argMutations: /* @__PURE__ */ new WeakMap(),
2121
2593
  streamingTexts: /* @__PURE__ */ new Map(),
2122
- lastLoggedModels: /* @__PURE__ */ new WeakMap()
2594
+ lastLoggedModels: /* @__PURE__ */ new WeakMap(),
2595
+ providerRouteDisposers: /* @__PURE__ */ new Map()
2123
2596
  };
2124
2597
  subscribeLifecycle(ctx, state);
2125
2598
  subscribeInterceptors(ctx, state);
2599
+ for (const provider of builtinProviders()) state.providers.set(provider.id, {
2600
+ name: provider.name,
2601
+ baseUrl: provider.baseUrl,
2602
+ oauth: provider.auth.oauth
2603
+ });
2604
+ ensureLoginCommand(ctx, state);
2126
2605
  if (options.manifest.skillDirs.length > 0) {
2127
2606
  if (ctx.get("skills") === void 0) logger(ctx).warn("[pi2dsh] migrated skills were not mounted because this DSH composition has no ctx.skills");
2128
2607
  else {
@@ -2135,6 +2614,20 @@ async function applyPiPackage(ctx, options) {
2135
2614
  });
2136
2615
  }
2137
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
+ }
2138
2631
  __setSubagentSessionFactory((subagentOptions) => createBridgedAgentSession({
2139
2632
  cordis: ctx,
2140
2633
  cwd: () => cwdOf(currentAgent(state)),
@@ -2161,4 +2654,4 @@ const runtimeInternals = {
2161
2654
  //#endregion
2162
2655
  export { normalizeToolSchema as n, runtimeInternals as r, applyPiPackage as t };
2163
2656
 
2164
- //# sourceMappingURL=runtime-oLd2EInK.mjs.map
2657
+ //# sourceMappingURL=runtime-B9R7bDWT.mjs.map