pi2dsh 0.3.5 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +17 -0
  2. package/README.zh.md +11 -0
  3. package/dist/{all-Rt4K68QA.mjs → all-DSpxKvHa.mjs} +10 -10
  4. package/dist/{all-Rt4K68QA.mjs.map → all-DSpxKvHa.mjs.map} +1 -1
  5. package/dist/{anthropic-messages-Beym0_aC.mjs → anthropic-messages-BL9N1Idv.mjs} +3 -3
  6. package/dist/{anthropic-messages-Beym0_aC.mjs.map → anthropic-messages-BL9N1Idv.mjs.map} +1 -1
  7. package/dist/{azure-openai-responses-cHqZTgLD.mjs → azure-openai-responses-A67VbaZ1.mjs} +3 -3
  8. package/dist/{azure-openai-responses-cHqZTgLD.mjs.map → azure-openai-responses-A67VbaZ1.mjs.map} +1 -1
  9. package/dist/cli.mjs +1 -1
  10. package/dist/compat/pi-ai.d.mts +3 -38
  11. package/dist/compat/pi-ai.d.mts.map +1 -1
  12. package/dist/compat/pi-ai.mjs +3 -3
  13. package/dist/compat/pi-coding-agent.d.mts +32 -31
  14. package/dist/compat/pi-coding-agent.d.mts.map +1 -1
  15. package/dist/compat/pi-coding-agent.mjs +1 -1
  16. package/dist/credentials-oauth.mjs +2 -2
  17. package/dist/{dist-QBBA-dL_.mjs → dist-BYA74yh_.mjs} +2 -2
  18. package/dist/{dist-QBBA-dL_.mjs.map → dist-BYA74yh_.mjs.map} +1 -1
  19. package/dist/dist-DeonWo7m.mjs +1089 -0
  20. package/dist/dist-DeonWo7m.mjs.map +1 -0
  21. package/dist/{google-generative-ai-B7OZs5lu.mjs → google-generative-ai-DT5S8D8y.mjs} +2 -2
  22. package/dist/{google-generative-ai-B7OZs5lu.mjs.map → google-generative-ai-DT5S8D8y.mjs.map} +1 -1
  23. package/dist/{google-shared-CRsAUItF.mjs → google-shared-DYP_9cnK.mjs} +4 -4
  24. package/dist/{google-shared-CRsAUItF.mjs.map → google-shared-DYP_9cnK.mjs.map} +1 -1
  25. package/dist/{google-vertex-DmdaHQtB.mjs → google-vertex-Dpq7O7Be.mjs} +3 -3
  26. package/dist/{google-vertex-DmdaHQtB.mjs.map → google-vertex-Dpq7O7Be.mjs.map} +1 -1
  27. package/dist/host.mjs +1 -1
  28. package/dist/index.d.mts.map +1 -1
  29. package/dist/index.mjs +1 -1
  30. package/dist/{json-parse-CFyFZSLJ.mjs → json-parse-Dz5sKMTv.mjs} +2 -2
  31. package/dist/{json-parse-CFyFZSLJ.mjs.map → json-parse-Dz5sKMTv.mjs.map} +1 -1
  32. package/dist/{mcp-config-D2B3kv-Z.mjs → mcp-config-ByUc0jmD.mjs} +10 -9
  33. package/dist/mcp-config-ByUc0jmD.mjs.map +1 -0
  34. package/dist/{mistral-conversations-HHZCcFv_.mjs → mistral-conversations-BsxbCCWx.mjs} +4 -4
  35. package/dist/{mistral-conversations-HHZCcFv_.mjs.map → mistral-conversations-BsxbCCWx.mjs.map} +1 -1
  36. package/dist/{multipart-parser-CIa5BbwO.mjs → multipart-parser-woKCuALW.mjs} +3 -3
  37. package/dist/{multipart-parser-CIa5BbwO.mjs.map → multipart-parser-woKCuALW.mjs.map} +1 -1
  38. package/dist/{openai-codex-responses-vmovNu-q.mjs → openai-codex-responses-C7vviVT2.mjs} +3 -3
  39. package/dist/{openai-codex-responses-vmovNu-q.mjs.map → openai-codex-responses-C7vviVT2.mjs.map} +1 -1
  40. package/dist/{openai-completions-fXHhRolF.mjs → openai-completions-By8QNX1P.mjs} +3 -3
  41. package/dist/{openai-completions-fXHhRolF.mjs.map → openai-completions-By8QNX1P.mjs.map} +1 -1
  42. package/dist/{openai-responses-DKdCYf3h.mjs → openai-responses-DboJUcFN.mjs} +3 -3
  43. package/dist/{openai-responses-DKdCYf3h.mjs.map → openai-responses-DboJUcFN.mjs.map} +1 -1
  44. package/dist/{openai-responses-shared-D0Jq7yVP.mjs → openai-responses-shared-BDzn7nJY.mjs} +2 -2
  45. package/dist/{openai-responses-shared-D0Jq7yVP.mjs.map → openai-responses-shared-BDzn7nJY.mjs.map} +1 -1
  46. package/dist/{otel-BQSQRBLF.mjs → otel-PimLMoiV.mjs} +3 -3
  47. package/dist/{otel-BQSQRBLF.mjs.map → otel-PimLMoiV.mjs.map} +1 -1
  48. package/dist/{pi-ai-DhYGgq4Y.mjs → pi-ai-B_88Wb0r.mjs} +49 -6
  49. package/dist/{pi-ai-DhYGgq4Y.mjs.map → pi-ai-B_88Wb0r.mjs.map} +1 -1
  50. package/dist/{pi-coding-agent-DuqSaquA.mjs → pi-coding-agent-C9Y9ql5E.mjs} +35 -34
  51. package/dist/pi-coding-agent-C9Y9ql5E.mjs.map +1 -0
  52. package/dist/{pi-messages-CV-Ts8An.mjs → pi-messages-B-7Xo_I0.mjs} +3 -3
  53. package/dist/{pi-messages-CV-Ts8An.mjs.map → pi-messages-B-7Xo_I0.mjs.map} +1 -1
  54. package/dist/pi-types-KazmR2O5.d.mts +62 -0
  55. package/dist/pi-types-KazmR2O5.d.mts.map +1 -0
  56. package/dist/pi-uuid-Db8ShZsK.mjs +47 -0
  57. package/dist/pi-uuid-Db8ShZsK.mjs.map +1 -0
  58. package/dist/{provider-env-C72pF4vP.mjs → provider-env-D-cCRWzv.mjs} +2 -2
  59. package/dist/{provider-env-C72pF4vP.mjs.map → provider-env-D-cCRWzv.mjs.map} +1 -1
  60. package/dist/{runtime-D1oXtRIx.mjs → runtime-VK4eVnYa.mjs} +1333 -54
  61. package/dist/runtime-VK4eVnYa.mjs.map +1 -0
  62. package/dist/runtime.d.mts.map +1 -1
  63. package/dist/runtime.mjs +1 -1
  64. package/dist/{src-C3WBfOks.mjs → src-BEl3QrGL.mjs} +3 -3
  65. package/dist/{src-C3WBfOks.mjs.map → src-BEl3QrGL.mjs.map} +1 -1
  66. package/package.json +2 -2
  67. package/dist/build-BG7p2Lbr.mjs +0 -4416
  68. package/dist/build-BG7p2Lbr.mjs.map +0 -1
  69. package/dist/dist-IQKOgQyt.mjs +0 -10035
  70. package/dist/dist-IQKOgQyt.mjs.map +0 -1
  71. package/dist/mcp-config-D2B3kv-Z.mjs.map +0 -1
  72. package/dist/pi-coding-agent-DuqSaquA.mjs.map +0 -1
  73. package/dist/pi-types-BLt46jaf.d.mts +0 -2494
  74. package/dist/pi-types-BLt46jaf.d.mts.map +0 -1
  75. package/dist/rolldown-runtime-CTfmNlz1.mjs +0 -44
  76. package/dist/runtime-D1oXtRIx.mjs.map +0 -1
@@ -1,19 +1,23 @@
1
1
 
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-DuqSaquA.mjs";
2
+ import { C as realBuiltinProvider, R as AssistantMessageEventStream, S as openAIResponsesApi, a as builtinProviders, i as anthropicMessagesApi, r as __setPiAiLlmBridge, x as openAICompletionsApi } from "./pi-ai-B_88Wb0r.mjs";
3
+ import { r as normalizePath, t as getAgentDir } from "./pi-config-shim-CZ1wFzqM.mjs";
4
+ import { E as __setSubagentSessionFactory, S as Theme, f as ExtensionRunner } from "./pi-coding-agent-C9Y9ql5E.mjs";
4
5
  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
- import { access, readFile } from "node:fs/promises";
7
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
8
8
  import { join } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
11
+ import { tmpdir } from "node:os";
11
12
  import { EventEmitter } from "node:events";
12
13
  import { AsyncLocalStorage } from "node:async_hooks";
13
14
  import { createJiti } from "jiti";
14
15
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
15
16
  import { renderPrompt } from "@deepseek-ai/dsh-system-prompt";
16
17
  import { randomUUID } from "node:crypto";
18
+ import { execSync, spawnSync } from "child_process";
19
+ import { Type } from "typebox";
20
+ import { Compile } from "typebox/compile";
17
21
  //#region src/session-bridge.ts
18
22
  function sidecarDir() {
19
23
  return join(getAgentDir(), "session-entries");
@@ -491,6 +495,7 @@ async function createBridgedAgentSession(host, options) {
491
495
  meta: {
492
496
  cwd: typeof options.cwd === "string" ? options.cwd : host.cwd(),
493
497
  origin: "subagent",
498
+ delegationDepth: host.parentDelegationDepth() + 1,
494
499
  ...host.parentSessionId() !== void 0 ? { parentSession: host.parentSessionId() } : {}
495
500
  },
496
501
  ...typeof requestedModel?.id === "string" && requestedModel.id.length > 0 ? { agentOptions: {
@@ -526,15 +531,28 @@ function llmOf(ctx) {
526
531
  return;
527
532
  }
528
533
  }
534
+ const PI_MODEL_PASSTHROUGH_FIELDS = [
535
+ "baseUrl",
536
+ "cost",
537
+ "contextWindow",
538
+ "maxTokens",
539
+ "samplingParams",
540
+ "thinkingLevelMap",
541
+ "compat",
542
+ "headers"
543
+ ];
529
544
  function piModelFromInfo(info) {
530
545
  const modalities = Array.isArray(info.inputModalities) ? info.inputModalities : void 0;
546
+ const carried = {};
547
+ for (const field of PI_MODEL_PASSTHROUGH_FIELDS) if (info[field] !== void 0) carried[field] = info[field];
531
548
  return {
549
+ ...carried,
532
550
  id: String(info.id ?? ""),
533
551
  name: String(info.name ?? info.id ?? ""),
534
552
  provider: String(info.provider ?? ""),
535
- api: "dsh-llm",
553
+ api: typeof info.api === "string" ? info.api : "faux",
536
554
  input: modalities === void 0 ? ["text"] : modalities.filter((m) => m === "text" || m === "image"),
537
- reasoning: info.reasoning !== void 0
555
+ reasoning: typeof info.reasoning === "boolean" ? info.reasoning : info.reasoning !== void 0
538
556
  };
539
557
  }
540
558
  /**
@@ -726,7 +744,7 @@ function streamViaDshLlm(llm, request) {
726
744
  content: [],
727
745
  provider,
728
746
  model: modelId,
729
- api: "dsh-llm"
747
+ api: "faux"
730
748
  };
731
749
  const content = partial.content;
732
750
  let usage;
@@ -882,7 +900,7 @@ function streamViaDshLlm(llm, request) {
882
900
  content: content.filter((block) => block !== void 0),
883
901
  provider,
884
902
  model: modelId,
885
- api: "dsh-llm",
903
+ api: "faux",
886
904
  ...usage === void 0 ? {} : { usage },
887
905
  stopReason,
888
906
  timestamp: Date.now()
@@ -1096,7 +1114,834 @@ async function* piEventsToDshChunks(events) {
1096
1114
  };
1097
1115
  }
1098
1116
  //#endregion
1117
+ //#region src/compat/vendor/pi-json.ts
1118
+ /** Strip `//` line comments and trailing commas from JSON, leaving string literals untouched. */
1119
+ function stripJsonComments(input) {
1120
+ return input.replace(/"(?:\\.|[^"\\])*"|\/\/[^\n]*/g, (m) => m[0] === "\"" ? m : "").replace(/"(?:\\.|[^"\\])*"|,(\s*[}\]])/g, (m, tail) => tail ?? (m[0] === "\"" ? m : ""));
1121
+ }
1122
+ //#endregion
1123
+ //#region src/compat/vendor/pi-model-config.ts
1124
+ /** Immutable, credential-blind models.json snapshot. */
1125
+ const PercentileCutoffsSchema = Type.Object({
1126
+ p50: Type.Optional(Type.Number()),
1127
+ p75: Type.Optional(Type.Number()),
1128
+ p90: Type.Optional(Type.Number()),
1129
+ p99: Type.Optional(Type.Number())
1130
+ });
1131
+ const OpenRouterRoutingSchema = Type.Object({
1132
+ allow_fallbacks: Type.Optional(Type.Boolean()),
1133
+ require_parameters: Type.Optional(Type.Boolean()),
1134
+ data_collection: Type.Optional(Type.Union([Type.Literal("deny"), Type.Literal("allow")])),
1135
+ zdr: Type.Optional(Type.Boolean()),
1136
+ enforce_distillable_text: Type.Optional(Type.Boolean()),
1137
+ order: Type.Optional(Type.Array(Type.String())),
1138
+ only: Type.Optional(Type.Array(Type.String())),
1139
+ ignore: Type.Optional(Type.Array(Type.String())),
1140
+ quantizations: Type.Optional(Type.Array(Type.String())),
1141
+ sort: Type.Optional(Type.Union([Type.String(), Type.Object({
1142
+ by: Type.Optional(Type.String()),
1143
+ partition: Type.Optional(Type.Union([Type.String(), Type.Null()]))
1144
+ })])),
1145
+ max_price: Type.Optional(Type.Object({
1146
+ prompt: Type.Optional(Type.Union([Type.Number(), Type.String()])),
1147
+ completion: Type.Optional(Type.Union([Type.Number(), Type.String()])),
1148
+ image: Type.Optional(Type.Union([Type.Number(), Type.String()])),
1149
+ audio: Type.Optional(Type.Union([Type.Number(), Type.String()])),
1150
+ request: Type.Optional(Type.Union([Type.Number(), Type.String()]))
1151
+ })),
1152
+ preferred_min_throughput: Type.Optional(Type.Union([Type.Number(), PercentileCutoffsSchema])),
1153
+ preferred_max_latency: Type.Optional(Type.Union([Type.Number(), PercentileCutoffsSchema]))
1154
+ });
1155
+ const VercelGatewayRoutingSchema = Type.Object({
1156
+ only: Type.Optional(Type.Array(Type.String())),
1157
+ order: Type.Optional(Type.Array(Type.String()))
1158
+ });
1159
+ const ThinkingLevelMapValueSchema = Type.Union([Type.String(), Type.Null()]);
1160
+ const ThinkingLevelMapSchema = Type.Object({
1161
+ off: Type.Optional(ThinkingLevelMapValueSchema),
1162
+ minimal: Type.Optional(ThinkingLevelMapValueSchema),
1163
+ low: Type.Optional(ThinkingLevelMapValueSchema),
1164
+ medium: Type.Optional(ThinkingLevelMapValueSchema),
1165
+ high: Type.Optional(ThinkingLevelMapValueSchema),
1166
+ xhigh: Type.Optional(ThinkingLevelMapValueSchema),
1167
+ max: Type.Optional(ThinkingLevelMapValueSchema)
1168
+ });
1169
+ const ChatTemplateKwargScalarSchema = Type.Union([
1170
+ Type.String(),
1171
+ Type.Number(),
1172
+ Type.Boolean(),
1173
+ Type.Null()
1174
+ ]);
1175
+ const ChatTemplateKwargVariableSchema = Type.Object({
1176
+ $var: Type.Union([Type.Literal("thinking.enabled"), Type.Literal("thinking.effort")]),
1177
+ omitWhenOff: Type.Optional(Type.Boolean())
1178
+ });
1179
+ const ChatTemplateKwargSchema = Type.Union([ChatTemplateKwargScalarSchema, ChatTemplateKwargVariableSchema]);
1180
+ const OpenAICompletionsCompatSchema = Type.Object({
1181
+ supportsStore: Type.Optional(Type.Boolean()),
1182
+ supportsDeveloperRole: Type.Optional(Type.Boolean()),
1183
+ supportsReasoningEffort: Type.Optional(Type.Boolean()),
1184
+ supportsUsageInStreaming: Type.Optional(Type.Boolean()),
1185
+ maxTokensField: Type.Optional(Type.Union([Type.Literal("max_completion_tokens"), Type.Literal("max_tokens")])),
1186
+ requiresToolResultName: Type.Optional(Type.Boolean()),
1187
+ requiresAssistantAfterToolResult: Type.Optional(Type.Boolean()),
1188
+ requiresThinkingAsText: Type.Optional(Type.Boolean()),
1189
+ requiresReasoningContentOnAssistantMessages: Type.Optional(Type.Boolean()),
1190
+ thinkingFormat: Type.Optional(Type.Union([
1191
+ Type.Literal("openai"),
1192
+ Type.Literal("openrouter"),
1193
+ Type.Literal("together"),
1194
+ Type.Literal("baseten"),
1195
+ Type.Literal("deepseek"),
1196
+ Type.Literal("zai"),
1197
+ Type.Literal("qwen"),
1198
+ Type.Literal("chat-template"),
1199
+ Type.Literal("qwen-chat-template"),
1200
+ Type.Literal("string-thinking"),
1201
+ Type.Literal("ant-ling")
1202
+ ])),
1203
+ chatTemplateKwargs: Type.Optional(Type.Record(Type.String(), ChatTemplateKwargSchema)),
1204
+ chatTemplateArgs: Type.Optional(Type.Record(Type.String(), ChatTemplateKwargSchema)),
1205
+ cacheControlFormat: Type.Optional(Type.Literal("anthropic")),
1206
+ openRouterRouting: Type.Optional(OpenRouterRoutingSchema),
1207
+ vercelGatewayRouting: Type.Optional(VercelGatewayRoutingSchema),
1208
+ supportsOpenAIGrammarTools: Type.Optional(Type.Boolean()),
1209
+ supportsStrictMode: Type.Optional(Type.Boolean()),
1210
+ sendSessionAffinityHeaders: Type.Optional(Type.Boolean()),
1211
+ deferredToolsMode: Type.Optional(Type.Literal("kimi")),
1212
+ sessionAffinityFormat: Type.Optional(Type.Union([
1213
+ Type.Literal("openai"),
1214
+ Type.Literal("openai-nosession"),
1215
+ Type.Literal("openrouter")
1216
+ ])),
1217
+ supportsLongCacheRetention: Type.Optional(Type.Boolean())
1218
+ });
1219
+ const OpenAIResponsesCompatSchema = Type.Object({
1220
+ supportsDeveloperRole: Type.Optional(Type.Boolean()),
1221
+ sessionAffinityFormat: Type.Optional(Type.Union([
1222
+ Type.Literal("openai"),
1223
+ Type.Literal("openai-nosession"),
1224
+ Type.Literal("openrouter")
1225
+ ])),
1226
+ supportsLongCacheRetention: Type.Optional(Type.Boolean()),
1227
+ supportsStrictMode: Type.Optional(Type.Boolean()),
1228
+ supportsOpenAIGrammarTools: Type.Optional(Type.Boolean()),
1229
+ supportsAdditionalTools: Type.Optional(Type.Boolean()),
1230
+ supportsToolSearch: Type.Optional(Type.Boolean())
1231
+ });
1232
+ const AnthropicMessagesCompatSchema = Type.Object({
1233
+ supportsEagerToolInputStreaming: Type.Optional(Type.Boolean()),
1234
+ supportsLongCacheRetention: Type.Optional(Type.Boolean()),
1235
+ sendSessionAffinityHeaders: Type.Optional(Type.Boolean()),
1236
+ supportsCacheControlOnTools: Type.Optional(Type.Boolean()),
1237
+ supportsTemperature: Type.Optional(Type.Boolean()),
1238
+ forceAdaptiveThinking: Type.Optional(Type.Boolean()),
1239
+ allowEmptySignature: Type.Optional(Type.Boolean()),
1240
+ supportsStrictTools: Type.Optional(Type.Boolean()),
1241
+ supportsToolReferences: Type.Optional(Type.Boolean())
1242
+ });
1243
+ const ProviderCompatSchema = Type.Union([
1244
+ OpenAICompletionsCompatSchema,
1245
+ OpenAIResponsesCompatSchema,
1246
+ AnthropicMessagesCompatSchema
1247
+ ]);
1248
+ const ModelCostRatesSchema = {
1249
+ input: Type.Number(),
1250
+ output: Type.Number(),
1251
+ cacheRead: Type.Number(),
1252
+ cacheWrite: Type.Number()
1253
+ };
1254
+ const ModelCostTierSchema = Type.Object({
1255
+ inputTokensAbove: Type.Number(),
1256
+ ...ModelCostRatesSchema
1257
+ });
1258
+ const ModelCostSchema = Type.Object({
1259
+ ...ModelCostRatesSchema,
1260
+ tiers: Type.Optional(Type.Array(ModelCostTierSchema))
1261
+ });
1262
+ const ModelDefinitionSchema = Type.Object({
1263
+ id: Type.String({ minLength: 1 }),
1264
+ name: Type.Optional(Type.String({ minLength: 1 })),
1265
+ api: Type.Optional(Type.String({ minLength: 1 })),
1266
+ baseUrl: Type.Optional(Type.String({ minLength: 1 })),
1267
+ reasoning: Type.Optional(Type.Boolean()),
1268
+ thinkingLevelMap: Type.Optional(ThinkingLevelMapSchema),
1269
+ input: Type.Optional(Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")]))),
1270
+ cost: Type.Optional(ModelCostSchema),
1271
+ contextWindow: Type.Optional(Type.Number()),
1272
+ maxTokens: Type.Optional(Type.Number()),
1273
+ samplingParams: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
1274
+ headers: Type.Optional(Type.Record(Type.String(), Type.String())),
1275
+ compat: Type.Optional(ProviderCompatSchema)
1276
+ });
1277
+ const ModelOverrideSchema = Type.Object({
1278
+ name: Type.Optional(Type.String({ minLength: 1 })),
1279
+ reasoning: Type.Optional(Type.Boolean()),
1280
+ thinkingLevelMap: Type.Optional(ThinkingLevelMapSchema),
1281
+ input: Type.Optional(Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")]))),
1282
+ cost: Type.Optional(Type.Object({
1283
+ input: Type.Optional(Type.Number()),
1284
+ output: Type.Optional(Type.Number()),
1285
+ cacheRead: Type.Optional(Type.Number()),
1286
+ cacheWrite: Type.Optional(Type.Number()),
1287
+ tiers: Type.Optional(Type.Array(ModelCostTierSchema))
1288
+ })),
1289
+ contextWindow: Type.Optional(Type.Number()),
1290
+ maxTokens: Type.Optional(Type.Number()),
1291
+ samplingParams: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
1292
+ headers: Type.Optional(Type.Record(Type.String(), Type.String())),
1293
+ compat: Type.Optional(ProviderCompatSchema)
1294
+ });
1295
+ const ProviderConfigSchema = Type.Object({
1296
+ name: Type.Optional(Type.String({ minLength: 1 })),
1297
+ baseUrl: Type.Optional(Type.String({ minLength: 1 })),
1298
+ apiKey: Type.Optional(Type.String({ minLength: 1 })),
1299
+ api: Type.Optional(Type.String({ minLength: 1 })),
1300
+ oauth: Type.Optional(Type.Literal("radius")),
1301
+ headers: Type.Optional(Type.Record(Type.String(), Type.String())),
1302
+ compat: Type.Optional(ProviderCompatSchema),
1303
+ authHeader: Type.Optional(Type.Boolean()),
1304
+ models: Type.Optional(Type.Array(ModelDefinitionSchema)),
1305
+ modelOverrides: Type.Optional(Type.Record(Type.String(), ModelOverrideSchema))
1306
+ });
1307
+ const ModelsConfigSchema = Type.Object({ providers: Type.Record(Type.String(), ProviderConfigSchema) });
1308
+ const validateModelsConfig = Compile(ModelsConfigSchema);
1309
+ function formatValidationPath(error) {
1310
+ if (error.keyword === "required") {
1311
+ const requiredProperty = error.params.requiredProperties?.[0];
1312
+ if (requiredProperty) {
1313
+ const basePath = error.instancePath.replace(/^\//, "").replace(/\//g, ".");
1314
+ return basePath ? `${basePath}.${requiredProperty}` : requiredProperty;
1315
+ }
1316
+ }
1317
+ return error.instancePath.replace(/^\//, "").replace(/\//g, ".") || "root";
1318
+ }
1319
+ function deepFreeze(value) {
1320
+ if (typeof value !== "object" || value === null || Object.isFrozen(value)) return value;
1321
+ for (const child of Object.values(value)) deepFreeze(child);
1322
+ return Object.freeze(value);
1323
+ }
1324
+ /** One immutable load of models.json. */
1325
+ var ModelConfig = class ModelConfig {
1326
+ providers;
1327
+ error;
1328
+ constructor(providers, error) {
1329
+ this.providers = providers;
1330
+ this.error = error;
1331
+ }
1332
+ static async load(modelsJsonPath) {
1333
+ if (!modelsJsonPath) return new ModelConfig(/* @__PURE__ */ new Map());
1334
+ const path = normalizePath(modelsJsonPath);
1335
+ let content;
1336
+ try {
1337
+ content = await readFile(path, "utf-8");
1338
+ } catch (error) {
1339
+ if (error.code === "ENOENT") return new ModelConfig(/* @__PURE__ */ new Map());
1340
+ return new ModelConfig(/* @__PURE__ */ new Map(), `Failed to load models.json: ${error instanceof Error ? error.message : error}\n\nFile: ${path}`);
1341
+ }
1342
+ let parsed;
1343
+ try {
1344
+ parsed = JSON.parse(stripJsonComments(content));
1345
+ } catch (error) {
1346
+ return new ModelConfig(/* @__PURE__ */ new Map(), `Failed to parse models.json: ${error instanceof Error ? error.message : error}\n\nFile: ${path}`);
1347
+ }
1348
+ if (!validateModelsConfig.Check(parsed)) {
1349
+ const errors = validateModelsConfig.Errors(parsed).map((error) => ` - ${formatValidationPath(error)}: ${error.message}`).join("\n") || "Unknown schema error";
1350
+ return new ModelConfig(/* @__PURE__ */ new Map(), `Invalid models.json schema:\n${errors}\n\nFile: ${path}`);
1351
+ }
1352
+ const config = parsed;
1353
+ const providers = /* @__PURE__ */ new Map();
1354
+ for (const [providerId, provider] of Object.entries(config.providers)) providers.set(providerId, deepFreeze(structuredClone(provider)));
1355
+ return new ModelConfig(providers);
1356
+ }
1357
+ getProvider(providerId) {
1358
+ return this.providers.get(providerId);
1359
+ }
1360
+ getProviderIds() {
1361
+ return [...this.providers.keys()];
1362
+ }
1363
+ getError() {
1364
+ return this.error;
1365
+ }
1366
+ };
1367
+ //#endregion
1368
+ //#region src/compat/vendor/pi-models-compose.ts
1369
+ function mergeCompat(base, override) {
1370
+ if (!override) return base;
1371
+ const merged = {
1372
+ ...base,
1373
+ ...override
1374
+ };
1375
+ const baseNested = base;
1376
+ const overrideNested = override;
1377
+ const mergedNested = merged;
1378
+ for (const key of [
1379
+ "openRouterRouting",
1380
+ "vercelGatewayRouting",
1381
+ "chatTemplateKwargs",
1382
+ "chatTemplateArgs"
1383
+ ]) {
1384
+ const baseValue = baseNested?.[key];
1385
+ const overrideValue = overrideNested[key];
1386
+ if (typeof baseValue === "object" && baseValue !== null || typeof overrideValue === "object" && overrideValue !== null) mergedNested[key] = {
1387
+ ...baseValue,
1388
+ ...overrideValue
1389
+ };
1390
+ }
1391
+ return merged;
1392
+ }
1393
+ function applyModelOverride(model, override) {
1394
+ return {
1395
+ ...model,
1396
+ name: override.name ?? model.name,
1397
+ reasoning: override.reasoning ?? model.reasoning,
1398
+ thinkingLevelMap: override.thinkingLevelMap ? {
1399
+ ...model.thinkingLevelMap,
1400
+ ...override.thinkingLevelMap
1401
+ } : model.thinkingLevelMap,
1402
+ input: override.input ?? model.input,
1403
+ cost: override.cost ? {
1404
+ input: override.cost.input ?? model.cost.input,
1405
+ output: override.cost.output ?? model.cost.output,
1406
+ cacheRead: override.cost.cacheRead ?? model.cost.cacheRead,
1407
+ cacheWrite: override.cost.cacheWrite ?? model.cost.cacheWrite,
1408
+ tiers: override.cost.tiers ?? model.cost.tiers
1409
+ } : model.cost,
1410
+ contextWindow: override.contextWindow ?? model.contextWindow,
1411
+ maxTokens: override.maxTokens ?? model.maxTokens,
1412
+ samplingParams: override.samplingParams ? {
1413
+ ...model.samplingParams,
1414
+ ...override.samplingParams
1415
+ } : model.samplingParams,
1416
+ compat: mergeCompat(model.compat, override.compat)
1417
+ };
1418
+ }
1419
+ function modelFromJson(providerId, definition, providerConfig, defaults) {
1420
+ const api = definition.api ?? providerConfig.api ?? defaults?.api;
1421
+ if (!api) throw new Error(`Provider ${providerId}, model ${definition.id}: no "api" specified. Set at provider or model level.`);
1422
+ const baseUrl = definition.baseUrl ?? providerConfig.baseUrl ?? defaults?.baseUrl;
1423
+ if (!baseUrl) throw new Error(`Provider ${providerId}: "baseUrl" is required when defining custom models.`);
1424
+ if (definition.contextWindow !== void 0 && definition.contextWindow <= 0) throw new Error(`Provider ${providerId}, model ${definition.id}: invalid contextWindow`);
1425
+ if (definition.maxTokens !== void 0 && definition.maxTokens <= 0) throw new Error(`Provider ${providerId}, model ${definition.id}: invalid maxTokens`);
1426
+ return {
1427
+ id: definition.id,
1428
+ name: definition.name ?? definition.id,
1429
+ api,
1430
+ provider: providerId,
1431
+ baseUrl,
1432
+ reasoning: definition.reasoning ?? false,
1433
+ thinkingLevelMap: definition.thinkingLevelMap,
1434
+ input: definition.input ?? ["text"],
1435
+ cost: definition.cost ?? {
1436
+ input: 0,
1437
+ output: 0,
1438
+ cacheRead: 0,
1439
+ cacheWrite: 0
1440
+ },
1441
+ contextWindow: definition.contextWindow ?? 128e3,
1442
+ maxTokens: definition.maxTokens ?? 16384,
1443
+ samplingParams: definition.samplingParams,
1444
+ headers: void 0,
1445
+ compat: mergeCompat(providerConfig.compat, definition.compat)
1446
+ };
1447
+ }
1448
+ function applyModelsJson(providerId, baseModels, config) {
1449
+ if (!config) return [...baseModels];
1450
+ if (config.oauth && !config.baseUrl) throw new Error(`Provider ${providerId}: "baseUrl" is required when "oauth" is set.`);
1451
+ const hasOverrides = config.modelOverrides && Object.keys(config.modelOverrides).length > 0;
1452
+ if (!config.models?.length && !config.baseUrl && !config.headers && !config.compat && !hasOverrides && !config.apiKey && !config.oauth && config.authHeader === void 0) throw new Error(`Provider ${providerId}: must specify "baseUrl", "headers", "compat", "modelOverrides", or "models".`);
1453
+ const models = baseModels.map((model) => ({
1454
+ ...model,
1455
+ baseUrl: config.oauth === "radius" ? model.baseUrl : config.baseUrl ?? model.baseUrl,
1456
+ compat: mergeCompat(model.compat, config.compat)
1457
+ }));
1458
+ for (const definition of config.models ?? []) {
1459
+ const existingIndex = models.findIndex((model) => model.id === definition.id);
1460
+ const model = modelFromJson(providerId, definition, config, existingIndex >= 0 ? models[existingIndex] : models[0]);
1461
+ if (existingIndex >= 0) models[existingIndex] = model;
1462
+ else models.push(model);
1463
+ }
1464
+ return models;
1465
+ }
1466
+ //#endregion
1467
+ //#region src/compat/vendor/pi-shell-config.ts
1468
+ function isLegacyWslBashPath(path) {
1469
+ const normalized = path.replace(/\//g, "\\").toLowerCase();
1470
+ return /^[a-z]:\\windows\\(?:system32|sysnative)\\bash\.exe$/.test(normalized);
1471
+ }
1472
+ function getBashShellConfig(shell) {
1473
+ return isLegacyWslBashPath(shell) ? {
1474
+ shell,
1475
+ args: ["-s"],
1476
+ commandTransport: "stdin"
1477
+ } : {
1478
+ shell,
1479
+ args: ["-c"]
1480
+ };
1481
+ }
1482
+ function findBashOnPath() {
1483
+ if (process.platform === "win32") {
1484
+ try {
1485
+ const result = spawnSync("where", ["bash.exe"], {
1486
+ encoding: "utf-8",
1487
+ timeout: 5e3,
1488
+ windowsHide: true
1489
+ });
1490
+ if (result.status === 0 && result.stdout) {
1491
+ const firstMatch = result.stdout.trim().split(/\r?\n/)[0];
1492
+ if (firstMatch && existsSync(firstMatch)) return firstMatch;
1493
+ }
1494
+ } catch {}
1495
+ return null;
1496
+ }
1497
+ try {
1498
+ const result = spawnSync("which", ["bash"], {
1499
+ encoding: "utf-8",
1500
+ timeout: 5e3
1501
+ });
1502
+ if (result.status === 0 && result.stdout) {
1503
+ const firstMatch = result.stdout.trim().split(/\r?\n/)[0];
1504
+ if (firstMatch) return firstMatch;
1505
+ }
1506
+ } catch {}
1507
+ return null;
1508
+ }
1509
+ /**
1510
+ * Resolve shell configuration based on platform and an optional explicit shell path.
1511
+ * Resolution order:
1512
+ * 1. User-specified shellPath
1513
+ * 2. On Windows: Git Bash in known locations, then bash on PATH
1514
+ * 3. On Unix: /bin/bash, then bash on PATH, then fallback to sh
1515
+ */
1516
+ function getShellConfig(customShellPath) {
1517
+ if (customShellPath) {
1518
+ if (existsSync(customShellPath)) return getBashShellConfig(customShellPath);
1519
+ throw new Error(`Custom shell path not found: ${customShellPath}`);
1520
+ }
1521
+ if (process.platform === "win32") {
1522
+ const paths = [];
1523
+ const programFiles = process.env.ProgramFiles;
1524
+ if (programFiles) paths.push(`${programFiles}\\Git\\bin\\bash.exe`);
1525
+ const programFilesX86 = process.env["ProgramFiles(x86)"];
1526
+ if (programFilesX86) paths.push(`${programFilesX86}\\Git\\bin\\bash.exe`);
1527
+ for (const path of paths) if (existsSync(path)) return getBashShellConfig(path);
1528
+ const bashOnPath = findBashOnPath();
1529
+ if (bashOnPath) return getBashShellConfig(bashOnPath);
1530
+ throw new Error(`No bash shell found. Options:
1531
+ 1. Install Git for Windows: https://git-scm.com/download/win
1532
+ 2. Add your bash to PATH (Cygwin, MSYS2, etc.)
1533
+ 3. Set shellPath in settings.json
1534
+
1535
+ Searched Git Bash in:\n${paths.map((p) => ` ${p}`).join("\n")}`);
1536
+ }
1537
+ if (existsSync("/bin/bash")) return getBashShellConfig("/bin/bash");
1538
+ const bashOnPath = findBashOnPath();
1539
+ if (bashOnPath) return getBashShellConfig(bashOnPath);
1540
+ return {
1541
+ shell: "sh",
1542
+ args: ["-c"]
1543
+ };
1544
+ }
1545
+ //#endregion
1546
+ //#region src/compat/vendor/pi-resolve-config-value.ts
1547
+ /**
1548
+ * Resolve configuration values that may be shell commands, environment variables, or literals.
1549
+ * Used by auth-storage.ts and model-registry.ts.
1550
+ */
1551
+ const ENV_VAR_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
1552
+ const ENV_VAR_NAME_PREFIX_RE = /^[A-Za-z_][A-Za-z0-9_]*/;
1553
+ function appendLiteral(parts, value) {
1554
+ if (!value) return;
1555
+ const previousPart = parts[parts.length - 1];
1556
+ if (previousPart?.type === "literal") {
1557
+ previousPart.value += value;
1558
+ return;
1559
+ }
1560
+ parts.push({
1561
+ type: "literal",
1562
+ value
1563
+ });
1564
+ }
1565
+ function parseConfigValueTemplate(config) {
1566
+ const parts = [];
1567
+ let index = 0;
1568
+ while (index < config.length) {
1569
+ const dollarIndex = config.indexOf("$", index);
1570
+ if (dollarIndex < 0) {
1571
+ appendLiteral(parts, config.slice(index));
1572
+ break;
1573
+ }
1574
+ appendLiteral(parts, config.slice(index, dollarIndex));
1575
+ const nextChar = config[dollarIndex + 1];
1576
+ if (nextChar === "$" || nextChar === "!") {
1577
+ appendLiteral(parts, nextChar);
1578
+ index = dollarIndex + 2;
1579
+ continue;
1580
+ }
1581
+ if (nextChar === "{") {
1582
+ const endIndex = config.indexOf("}", dollarIndex + 2);
1583
+ if (endIndex < 0) {
1584
+ appendLiteral(parts, "$");
1585
+ index = dollarIndex + 1;
1586
+ continue;
1587
+ }
1588
+ const name = config.slice(dollarIndex + 2, endIndex);
1589
+ if (ENV_VAR_NAME_RE.test(name)) parts.push({
1590
+ type: "env",
1591
+ name
1592
+ });
1593
+ else appendLiteral(parts, config.slice(dollarIndex, endIndex + 1));
1594
+ index = endIndex + 1;
1595
+ continue;
1596
+ }
1597
+ const match = config.slice(dollarIndex + 1).match(ENV_VAR_NAME_PREFIX_RE);
1598
+ if (match) {
1599
+ parts.push({
1600
+ type: "env",
1601
+ name: match[0]
1602
+ });
1603
+ index = dollarIndex + 1 + match[0].length;
1604
+ continue;
1605
+ }
1606
+ appendLiteral(parts, "$");
1607
+ index = dollarIndex + 1;
1608
+ }
1609
+ return parts;
1610
+ }
1611
+ function parseConfigValueReference(config) {
1612
+ if (config.startsWith("!")) return {
1613
+ type: "command",
1614
+ config
1615
+ };
1616
+ return {
1617
+ type: "template",
1618
+ parts: parseConfigValueTemplate(config)
1619
+ };
1620
+ }
1621
+ function resolveEnvConfigValue(name, env) {
1622
+ return env?.[name] || process.env[name] || void 0;
1623
+ }
1624
+ function getTemplateEnvVarNames(parts) {
1625
+ const names = [];
1626
+ for (const part of parts) {
1627
+ if (part.type !== "env" || names.includes(part.name)) continue;
1628
+ names.push(part.name);
1629
+ }
1630
+ return names;
1631
+ }
1632
+ function resolveTemplate(parts, env) {
1633
+ let resolved = "";
1634
+ for (const part of parts) {
1635
+ if (part.type === "literal") {
1636
+ resolved += part.value;
1637
+ continue;
1638
+ }
1639
+ const envValue = resolveEnvConfigValue(part.name, env);
1640
+ if (envValue === void 0) return void 0;
1641
+ resolved += envValue;
1642
+ }
1643
+ return resolved;
1644
+ }
1645
+ function getConfigValueEnvVarNames(config) {
1646
+ const reference = parseConfigValueReference(config);
1647
+ return reference.type === "template" ? getTemplateEnvVarNames(reference.parts) : [];
1648
+ }
1649
+ function getMissingConfigValueEnvVarNames(config, env) {
1650
+ return getConfigValueEnvVarNames(config).filter((name) => resolveEnvConfigValue(name, env) === void 0);
1651
+ }
1652
+ function isCommandConfigValue(config) {
1653
+ return parseConfigValueReference(config).type === "command";
1654
+ }
1655
+ function isConfigValueConfigured(config, env) {
1656
+ return getMissingConfigValueEnvVarNames(config, env).length === 0;
1657
+ }
1658
+ function executeWithConfiguredShell(command) {
1659
+ try {
1660
+ const { shell, args, commandTransport } = getShellConfig();
1661
+ const commandFromStdin = commandTransport === "stdin";
1662
+ const result = spawnSync(shell, commandFromStdin ? args : [...args, command], {
1663
+ encoding: "utf-8",
1664
+ input: commandFromStdin ? command : void 0,
1665
+ timeout: 1e4,
1666
+ stdio: [
1667
+ commandFromStdin ? "pipe" : "ignore",
1668
+ "pipe",
1669
+ "ignore"
1670
+ ],
1671
+ shell: false,
1672
+ windowsHide: true
1673
+ });
1674
+ if (result.error) {
1675
+ if (result.error.code === "ENOENT") return {
1676
+ executed: false,
1677
+ value: void 0
1678
+ };
1679
+ return {
1680
+ executed: true,
1681
+ value: void 0
1682
+ };
1683
+ }
1684
+ if (result.status !== 0) return {
1685
+ executed: true,
1686
+ value: void 0
1687
+ };
1688
+ return {
1689
+ executed: true,
1690
+ value: (result.stdout ?? "").trim() || void 0
1691
+ };
1692
+ } catch {
1693
+ return {
1694
+ executed: false,
1695
+ value: void 0
1696
+ };
1697
+ }
1698
+ }
1699
+ function executeWithDefaultShell(command) {
1700
+ try {
1701
+ return execSync(command, {
1702
+ encoding: "utf-8",
1703
+ timeout: 1e4,
1704
+ stdio: [
1705
+ "ignore",
1706
+ "pipe",
1707
+ "ignore"
1708
+ ]
1709
+ }).trim() || void 0;
1710
+ } catch {
1711
+ return;
1712
+ }
1713
+ }
1714
+ function executeCommandUncached(commandConfig) {
1715
+ const command = commandConfig.slice(1);
1716
+ return process.platform === "win32" ? (() => {
1717
+ const configuredResult = executeWithConfiguredShell(command);
1718
+ return configuredResult.executed ? configuredResult.value : executeWithDefaultShell(command);
1719
+ })() : executeWithDefaultShell(command);
1720
+ }
1721
+ /**
1722
+ * Resolve all header values using the same resolution logic as API keys.
1723
+ */
1724
+ function resolveConfigValueUncached(config, env) {
1725
+ const reference = parseConfigValueReference(config);
1726
+ if (reference.type === "command") return executeCommandUncached(reference.config);
1727
+ return resolveTemplate(reference.parts, env);
1728
+ }
1729
+ function resolveConfigValueOrThrow(config, description, env) {
1730
+ const resolvedValue = resolveConfigValueUncached(config, env);
1731
+ if (resolvedValue !== void 0) return resolvedValue;
1732
+ const reference = parseConfigValueReference(config);
1733
+ if (reference.type === "command") throw new Error(`Failed to resolve ${description} from shell command: ${reference.config.slice(1)}`);
1734
+ if (reference.type === "template") {
1735
+ const missingEnvVars = getMissingConfigValueEnvVarNames(config, env);
1736
+ if (missingEnvVars.length === 1) throw new Error(`Failed to resolve ${description} from environment variable: ${missingEnvVars[0]}`);
1737
+ if (missingEnvVars.length > 1) throw new Error(`Failed to resolve ${description} from environment variables: ${missingEnvVars.join(", ")}`);
1738
+ }
1739
+ throw new Error(`Failed to resolve ${description}`);
1740
+ }
1741
+ function resolveHeadersOrThrow(headers, description, env) {
1742
+ if (!headers) return void 0;
1743
+ const resolved = {};
1744
+ for (const [key, value] of Object.entries(headers)) resolved[key] = resolveConfigValueOrThrow(value, `${description} header "${key}"`, env);
1745
+ return Object.keys(resolved).length > 0 ? resolved : void 0;
1746
+ }
1747
+ //#endregion
1748
+ //#region src/models-json.ts
1749
+ function modelsJsonPath() {
1750
+ return join(getAgentDir(), "models.json");
1751
+ }
1752
+ const emptySnapshot = () => ({
1753
+ models: [],
1754
+ providers: /* @__PURE__ */ new Map(),
1755
+ errors: [],
1756
+ companions: /* @__PURE__ */ new Map()
1757
+ });
1758
+ function companionImageModels(provider) {
1759
+ if (provider.baseUrl !== void 0 || provider.models !== void 0 || provider.apiKey !== void 0) return void 0;
1760
+ const overrides = provider.modelOverrides;
1761
+ if (overrides === void 0) return void 0;
1762
+ const imageModels = /* @__PURE__ */ new Set();
1763
+ for (const [modelId, override] of Object.entries(overrides)) if (Array.isArray(override.input) && override.input.includes("image")) imageModels.add(modelId);
1764
+ return imageModels.size > 0 ? imageModels : void 0;
1765
+ }
1766
+ /**
1767
+ * One load of models.json projected to Pi Model objects. Mirrors Pi's
1768
+ * composeModelProvider composition order: base (builtin) models →
1769
+ * applyModelsJson upserts → modelOverrides as the topmost user-config layer.
1770
+ * A provider that fails composition contributes an error and is skipped —
1771
+ * the same per-entry isolation the rest of the bridge applies to extensions.
1772
+ */
1773
+ async function loadModelsJsonSnapshot(baseProviderOf) {
1774
+ const snapshot = emptySnapshot();
1775
+ const config = await ModelConfig.load(modelsJsonPath());
1776
+ const loadError = config.getError();
1777
+ if (loadError !== void 0) {
1778
+ snapshot.errors.push(loadError);
1779
+ return snapshot;
1780
+ }
1781
+ for (const providerId of config.getProviderIds()) {
1782
+ const providerConfig = config.getProvider(providerId);
1783
+ if (providerConfig === void 0) continue;
1784
+ const imageModels = companionImageModels(providerConfig);
1785
+ if (imageModels !== void 0) {
1786
+ snapshot.companions.set(providerId, imageModels);
1787
+ continue;
1788
+ }
1789
+ try {
1790
+ const base = await baseProviderOf?.(providerId);
1791
+ const composed = applyModelsJson(providerId, typeof base?.getModels === "function" ? base.getModels() : [], providerConfig).map((model) => {
1792
+ const override = providerConfig.modelOverrides?.[String(model.id)];
1793
+ return override ? applyModelOverride(model, override) : model;
1794
+ });
1795
+ snapshot.models.push(...composed);
1796
+ snapshot.providers.set(providerId, providerConfig);
1797
+ } catch (error) {
1798
+ snapshot.errors.push(error instanceof Error ? error.message : String(error));
1799
+ }
1800
+ }
1801
+ return snapshot;
1802
+ }
1803
+ /**
1804
+ * Pi's configured-auth answer for a models.json provider, following
1805
+ * composeApiKeyAuth.resolve + withConfiguredAuth and the registry's
1806
+ * translation of their failures:
1807
+ * - a configured apiKey resolves through $ENV/!command or fails loudly with
1808
+ * the resolver's message;
1809
+ * - authHeader folds the resolved key into an Authorization header and,
1810
+ * without a key, becomes Pi's "No API key found" answer;
1811
+ * - a provider with neither key nor authHeader answers with its resolved
1812
+ * headers only (the registry's compatibility path).
1813
+ */
1814
+ function resolveModelsJsonAuth(providerId, provider) {
1815
+ try {
1816
+ const headers = resolveHeadersOrThrow(provider.headers, `provider "${providerId}"`);
1817
+ const rawKey = provider.apiKey;
1818
+ if (rawKey === void 0) {
1819
+ if (provider.authHeader ?? false) return {
1820
+ ok: false,
1821
+ error: `No API key found for "${providerId}"`
1822
+ };
1823
+ return {
1824
+ ok: true,
1825
+ ...headers === void 0 ? {} : { headers }
1826
+ };
1827
+ }
1828
+ const apiKey = resolveConfigValueOrThrow(rawKey, `API key for provider "${providerId}"`);
1829
+ const merged = provider.authHeader ?? false ? {
1830
+ ...headers,
1831
+ Authorization: `Bearer ${apiKey}`
1832
+ } : headers;
1833
+ return {
1834
+ ok: true,
1835
+ apiKey,
1836
+ ...merged === void 0 ? {} : { headers: merged }
1837
+ };
1838
+ } catch (error) {
1839
+ return {
1840
+ ok: false,
1841
+ error: error instanceof Error ? error.message : String(error)
1842
+ };
1843
+ }
1844
+ }
1845
+ /**
1846
+ * Pi's withConfiguredAuth layered over an auth resolved by another family
1847
+ * (an extension-registered provider or a builtin): models.json headers
1848
+ * override the resolved ones, and authHeader folds the resolved key into
1849
+ * an Authorization header — failing loudly without one, as Pi does.
1850
+ */
1851
+ function applyModelsJsonConfiguredAuth(providerId, provider, auth) {
1852
+ try {
1853
+ const configured = resolveHeadersOrThrow(provider.headers, `provider "${providerId}"`);
1854
+ let headers = auth.headers || configured ? {
1855
+ ...auth.headers,
1856
+ ...configured
1857
+ } : void 0;
1858
+ if (provider.authHeader ?? false) {
1859
+ if (auth.apiKey === void 0) return {
1860
+ ok: false,
1861
+ error: `No API key found for "${providerId}"`
1862
+ };
1863
+ headers = {
1864
+ ...headers,
1865
+ Authorization: `Bearer ${auth.apiKey}`
1866
+ };
1867
+ }
1868
+ return {
1869
+ ok: true,
1870
+ ...auth.apiKey === void 0 ? {} : { apiKey: auth.apiKey },
1871
+ ...headers === void 0 ? {} : { headers }
1872
+ };
1873
+ } catch (error) {
1874
+ return {
1875
+ ok: false,
1876
+ error: error instanceof Error ? error.message : String(error)
1877
+ };
1878
+ }
1879
+ }
1880
+ /**
1881
+ * One models.json provider as a Pi Provider object: the composed Pi Models
1882
+ * as its catalog and Pi's per-api wire clients as its transport (attached by
1883
+ * the provider adapter's synthesis). Registered as a DSH llm route, this
1884
+ * makes the DSH directory the single model directory — the Pi registry
1885
+ * projects it back, and registry.getProvider() answers a genuine Pi
1886
+ * Provider, so packages that stream through the provider surface work
1887
+ * unchanged.
1888
+ */
1889
+ function modelsJsonPiProvider(providerId, provider, models) {
1890
+ return {
1891
+ id: providerId,
1892
+ name: provider.name ?? providerId,
1893
+ ...provider.baseUrl === void 0 ? {} : { baseUrl: provider.baseUrl },
1894
+ ...provider.api === void 0 ? {} : { api: provider.api },
1895
+ getModels: () => models
1896
+ };
1897
+ }
1898
+ /**
1899
+ * Pi's configured-check (not liveness) for a models.json provider — the
1900
+ * configuredRequestAuthStatus semantics: a literal or !command key counts as
1901
+ * configured, a templated key counts only when its env vars are present, and
1902
+ * a provider without an apiKey defers to the other credential families.
1903
+ */
1904
+ function modelsJsonAuthConfigured(provider) {
1905
+ const value = provider.apiKey;
1906
+ if (value === void 0) return void 0;
1907
+ if (isCommandConfigValue(value)) return true;
1908
+ if (getConfigValueEnvVarNames(value).length > 0) return isConfigValueConfigured(value);
1909
+ return true;
1910
+ }
1911
+ //#endregion
1099
1912
  //#region src/provider-adapter.ts
1913
+ const PI_API_FACTORIES = {
1914
+ "openai-completions": openAICompletionsApi,
1915
+ "openai-responses": openAIResponsesApi,
1916
+ "anthropic-messages": anthropicMessagesApi
1917
+ };
1918
+ function piApiClientFor(api) {
1919
+ const factory = typeof api === "string" ? PI_API_FACTORIES[api] : void 0;
1920
+ return factory === void 0 ? void 0 : factory();
1921
+ }
1922
+ /**
1923
+ * Give a config-only Pi provider (models.json entries, packages that declare
1924
+ * models without a stream) Pi's own transport: each call dispatches on the
1925
+ * model's declared `api` to the real pi-ai client, exactly as Pi's
1926
+ * provider composer does. Undispatchable providers (no models, or an api
1927
+ * with no client) answer undefined and stay off the directory.
1928
+ */
1929
+ function withSynthesizedTransport(provider) {
1930
+ const models = typeof provider.getModels === "function" ? provider.getModels() : Array.isArray(provider.models) ? provider.models : [];
1931
+ if (models.length === 0) return void 0;
1932
+ if (!models.some((model) => piApiClientFor(model.api) !== void 0)) return void 0;
1933
+ const dispatch = (simple) => (model, context, options) => {
1934
+ const client = piApiClientFor(model.api);
1935
+ if (client === void 0) throw new Error(`pi2dsh: no wire-protocol client for api ${JSON.stringify(String(model.api))} (model ${String(model.provider)}/${String(model.id)})`);
1936
+ return (simple ? client.streamSimple ?? client.stream : client.stream).call(client, model, context, options);
1937
+ };
1938
+ return {
1939
+ ...provider,
1940
+ getModels: () => models,
1941
+ stream: dispatch(false),
1942
+ streamSimple: dispatch(true)
1943
+ };
1944
+ }
1100
1945
  function providerCarriesTransport(value) {
1101
1946
  return typeof value?.stream === "function";
1102
1947
  }
@@ -1108,6 +1953,22 @@ function providerModels(provider) {
1108
1953
  return [];
1109
1954
  }
1110
1955
  }
1956
+ const PI_CARRIED_FIELDS = [
1957
+ "api",
1958
+ "baseUrl",
1959
+ "cost",
1960
+ "contextWindow",
1961
+ "maxTokens",
1962
+ "samplingParams",
1963
+ "thinkingLevelMap",
1964
+ "compat",
1965
+ "headers"
1966
+ ];
1967
+ function piCarriedFields(model) {
1968
+ const carried = {};
1969
+ for (const field of PI_CARRIED_FIELDS) if (model[field] !== void 0) carried[field] = model[field];
1970
+ return carried;
1971
+ }
1111
1972
  /**
1112
1973
  * Wrap one Pi provider object as a DSH LlmAdapter-shaped route handler.
1113
1974
  * Structural typing carries it through `llm.registerAdapter`.
@@ -1120,6 +1981,7 @@ function piProviderDshAdapter(providerId, provider, host) {
1120
1981
  }),
1121
1982
  providerRetryPolicy: () => void 0,
1122
1983
  listModels: async (id) => providerModels(provider).map((model) => ({
1984
+ ...piCarriedFields(model),
1123
1985
  provider: id,
1124
1986
  id: String(model.id ?? ""),
1125
1987
  name: String(model.name ?? model.id ?? ""),
@@ -1128,6 +1990,7 @@ function piProviderDshAdapter(providerId, provider, host) {
1128
1990
  resolveModel: async (id, modelId) => {
1129
1991
  const known = providerModels(provider).find((model) => model.id === modelId);
1130
1992
  return {
1993
+ ...known === void 0 ? {} : piCarriedFields(known),
1131
1994
  provider: id,
1132
1995
  id: modelId,
1133
1996
  name: String(known?.name ?? modelId),
@@ -1155,18 +2018,92 @@ function piProviderDshAdapter(providerId, provider, host) {
1155
2018
  }
1156
2019
  };
1157
2020
  }
2021
+ const IMAGE_OMITTED_TEXT = "[image attachment omitted: the selected model reads text only]";
2022
+ const imageNoticeText = (path) => path === void 0 ? IMAGE_OMITTED_TEXT : `[image attached at ${path} — the selected model reads text only; use an image-capable tool to view it]`;
2023
+ async function textOnlyBlocks(blocks, materialize) {
2024
+ const out = [];
2025
+ for (const block of blocks) if (block.type === "image") {
2026
+ const attachment = block.attachment;
2027
+ const path = typeof attachment === "object" && attachment !== null ? await materialize(attachment) : void 0;
2028
+ out.push({
2029
+ type: "text",
2030
+ text: imageNoticeText(path)
2031
+ });
2032
+ } else if (block.type === "tool-result" && Array.isArray(block.content)) out.push({
2033
+ ...block,
2034
+ content: await textOnlyBlocks(block.content, materialize)
2035
+ });
2036
+ else out.push(block);
2037
+ return out;
2038
+ }
2039
+ async function textOnlyMessages(messages, materialize) {
2040
+ return Promise.all(messages.map(async (message) => Array.isArray(message.content) ? {
2041
+ ...message,
2042
+ content: await textOnlyBlocks(message.content, materialize)
2043
+ } : message));
2044
+ }
1158
2045
  /**
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.
2046
+ * A DSH route that admits images on behalf of a text-only route — the
2047
+ * single-directory answer to models.json modelOverrides declaring
2048
+ * `input: ["text", "image"]` for models of a route this bridge does not own.
2049
+ * The companion honestly declares image input (so the host's admission and
2050
+ * model-switch checks pass), replaces image blocks with an explicit notice,
2051
+ * and forwards every call to the original route. It never carries a wire
2052
+ * transport of its own; the images themselves are served by whatever vision
2053
+ * extension handles the turn's entering messages.
2054
+ */
2055
+ function imageAdmissionCompanionAdapter(options) {
2056
+ const { originalId, imageModels, llm } = options;
2057
+ const materialize = options.materializeImage ?? (async () => void 0);
2058
+ const admitImage = (info, id) => {
2059
+ const modalities = Array.isArray(info.inputModalities) ? info.inputModalities.slice() : ["text"];
2060
+ if (!modalities.includes("image")) modalities.push("image");
2061
+ return {
2062
+ ...info,
2063
+ provider: id,
2064
+ inputModalities: modalities
2065
+ };
2066
+ };
2067
+ return {
2068
+ providerInfo: (id) => {
2069
+ return {
2070
+ id,
2071
+ name: `${llm.listProviders().find((provider) => provider.id === originalId)?.name ?? originalId} + Vision Bridge`
2072
+ };
2073
+ },
2074
+ providerRetryPolicy: () => void 0,
2075
+ listModels: async (id) => (await llm.listModels(originalId)).filter((model) => imageModels.has(String(model.id))).map((model) => admitImage(model, id)),
2076
+ resolveModel: async (id, modelId) => {
2077
+ const info = await llm.resolveModelInfo(originalId, modelId);
2078
+ return imageModels.has(modelId) ? admitImage(info, id) : {
2079
+ ...info,
2080
+ provider: id
2081
+ };
2082
+ },
2083
+ async *stream(streamOptions) {
2084
+ yield* llm.stream({
2085
+ ...streamOptions,
2086
+ provider: originalId,
2087
+ ...Array.isArray(streamOptions.messages) ? { messages: await textOnlyMessages(streamOptions.messages, materialize) } : {}
2088
+ });
2089
+ }
2090
+ };
2091
+ }
2092
+ /**
2093
+ * Register the provider as a live DSH route when an llm service is mounted.
2094
+ * A provider carrying its own transport streams through it; a config-only
2095
+ * provider gets Pi's synthesized per-api transport. A route conflict (an
2096
+ * adapter already owns the name — e.g. a deployment-configured `deepseek`)
2097
+ * keeps the existing route and reports the skip.
1163
2098
  * @returns the route disposer, or undefined when nothing was registered.
1164
2099
  */
1165
2100
  function registerPiProviderRoute(options) {
1166
2101
  const { llm, providerId, provider, host } = options;
1167
- if (llm === void 0 || !providerCarriesTransport(provider)) return void 0;
2102
+ if (llm === void 0) return void 0;
2103
+ const transport = providerCarriesTransport(provider) ? provider : withSynthesizedTransport(provider);
2104
+ if (transport === void 0) return void 0;
1168
2105
  try {
1169
- return llm.registerAdapter([providerId], piProviderDshAdapter(providerId, provider, host));
2106
+ return llm.registerAdapter([providerId], piProviderDshAdapter(providerId, transport, host));
1170
2107
  } catch (error) {
1171
2108
  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
2109
  return;
@@ -1421,14 +2358,15 @@ function isSubagentOrigin(subject) {
1421
2358
  function currentPiModel(state, agent) {
1422
2359
  const override = state.modelOverrides.get(agent);
1423
2360
  const options = agent.options;
1424
- const provider = String(override?.provider ?? options?.provider ?? "");
2361
+ const selectedProvider = String(override?.provider ?? options?.provider ?? "");
2362
+ const provider = state.companionRoutes.get(selectedProvider) ?? selectedProvider;
1425
2363
  const id = String(override?.model ?? options?.model ?? "");
1426
2364
  if (id.length === 0) return override;
1427
2365
  return (provider.length > 0 ? state.modelCatalog?.find(provider, id) : void 0) ?? {
1428
2366
  id,
1429
2367
  name: id,
1430
2368
  provider,
1431
- api: "dsh-llm",
2369
+ api: "faux",
1432
2370
  input: ["text"],
1433
2371
  reasoning: false
1434
2372
  };
@@ -1518,28 +2456,43 @@ function contextFor(ctx, state, agent, signal, command = false) {
1518
2456
  const session = agentSession(agent);
1519
2457
  const providerConfig = (name) => state.providers.get(name);
1520
2458
  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
- });
2459
+ const piNativeEntry = (provider, id) => {
2460
+ const source = state.modelsJsonProviders.get(provider) ?? (state.providerRouteDisposers.has(provider) ? state.providers.get(provider) : void 0);
2461
+ if (source === void 0) return void 0;
2462
+ let models;
2463
+ try {
2464
+ models = typeof source.getModels === "function" ? source.getModels() : source.models;
2465
+ } catch {
2466
+ return;
1535
2467
  }
1536
- return out;
2468
+ if (!Array.isArray(models)) return void 0;
2469
+ return models.find((model) => model.id === id);
1537
2470
  };
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)}`))];
2471
+ const restorePiShape = (entry) => {
2472
+ const native = piNativeEntry(String(entry.provider ?? ""), String(entry.id ?? ""));
2473
+ return native === void 0 ? entry : {
2474
+ ...entry,
2475
+ ...native,
2476
+ provider: entry.provider
2477
+ };
1542
2478
  };
2479
+ const allModels = () => (catalog?.all() ?? []).map(restorePiShape);
2480
+ const dshRoutedStream = (model, context, options) => {
2481
+ const llm = llmOf(ctx);
2482
+ if (llm === void 0) throw new Error("pi2dsh: model calls need a DSH llm service; this composition mounts none");
2483
+ return streamViaDshLlm(llm, {
2484
+ model,
2485
+ context,
2486
+ options
2487
+ });
2488
+ };
2489
+ const dshRoutedPiProvider = (base, providerId) => ({
2490
+ ...base,
2491
+ id: providerId,
2492
+ getModels: () => allModels().filter((model) => model.provider === providerId),
2493
+ stream: dshRoutedStream,
2494
+ streamSimple: dshRoutedStream
2495
+ });
1543
2496
  const base = {
1544
2497
  ui,
1545
2498
  mode: "rpc",
@@ -1552,15 +2505,28 @@ function contextFor(ctx, state, agent, signal, command = false) {
1552
2505
  modelRegistry: {
1553
2506
  getAll: () => allModels(),
1554
2507
  getAvailable: () => allModels(),
1555
- find: (provider, modelId) => catalog?.find(provider, modelId) ?? piProviderModels().find((model) => model.provider === provider && model.id === modelId),
1556
- getError: () => void 0,
2508
+ find: (provider, modelId) => {
2509
+ const entry = catalog?.find(provider, modelId);
2510
+ return entry === void 0 ? void 0 : restorePiShape(entry);
2511
+ },
2512
+ getError: () => {
2513
+ const problems = state.modelsJson?.errors ?? [];
2514
+ return problems.length > 0 ? problems.join("\n") : void 0;
2515
+ },
1557
2516
  hasConfiguredAuth: (model) => {
1558
2517
  const provider = String(model?.provider ?? "");
1559
- return provider.length > 0 && (state.providers.has(provider) || (catalog?.all() ?? []).some((entry) => entry.provider === provider));
2518
+ if (provider.length === 0) return false;
2519
+ const jsonProvider = state.modelsJson?.providers.get(provider);
2520
+ if (jsonProvider !== void 0) {
2521
+ const configured = modelsJsonAuthConfigured(jsonProvider);
2522
+ if (configured !== void 0) return configured;
2523
+ }
2524
+ return state.providers.has(provider) || (catalog?.all() ?? []).some((entry) => entry.provider === provider) || jsonProvider !== void 0;
1560
2525
  },
1561
2526
  getApiKeyAndHeaders: async (model) => {
1562
2527
  const provider = String(model?.provider ?? "");
1563
- const config = providerConfig(provider) ?? await realBuiltinProvider(provider);
2528
+ const jsonProvider = state.modelsJson?.providers.get(provider);
2529
+ const config = providerConfig(provider) ?? (jsonProvider?.apiKey !== void 0 ? void 0 : await realBuiltinProvider(provider));
1564
2530
  if (config !== void 0) {
1565
2531
  const auth = (await resolvePiProviderAuth({
1566
2532
  providerId: provider,
@@ -1568,13 +2534,34 @@ function contextFor(ctx, state, agent, signal, command = false) {
1568
2534
  store: oauthStoreOf(state)
1569
2535
  }))?.auth;
1570
2536
  if (auth?.apiKey === void 0) return { ok: false };
2537
+ const configured = jsonProvider === void 0 ? void 0 : applyModelsJsonConfiguredAuth(provider, jsonProvider, {
2538
+ apiKey: auth.apiKey,
2539
+ ...auth.headers === void 0 ? {} : { headers: auth.headers }
2540
+ });
2541
+ if (configured !== void 0 && !configured.ok) return {
2542
+ ok: false,
2543
+ error: configured.error
2544
+ };
2545
+ const headers = configured?.ok ? configured.headers : auth.headers;
1571
2546
  return {
1572
2547
  ok: true,
1573
2548
  apiKey: auth.apiKey,
1574
- ...auth.headers === void 0 ? {} : { headers: auth.headers },
2549
+ ...headers === void 0 ? {} : { headers },
1575
2550
  ...auth.baseUrl === void 0 && config.baseUrl === void 0 ? {} : { baseUrl: auth.baseUrl ?? config.baseUrl }
1576
2551
  };
1577
2552
  }
2553
+ if (jsonProvider !== void 0) {
2554
+ const resolved = resolveModelsJsonAuth(provider, jsonProvider);
2555
+ if (!resolved.ok) return {
2556
+ ok: false,
2557
+ error: resolved.error
2558
+ };
2559
+ return {
2560
+ ok: true,
2561
+ ...resolved.apiKey === void 0 ? {} : { apiKey: resolved.apiKey },
2562
+ ...resolved.headers === void 0 ? {} : { headers: resolved.headers }
2563
+ };
2564
+ }
1578
2565
  try {
1579
2566
  const entry = llmOf(ctx)?.listConfigurableProviders?.()?.find((candidate) => candidate.provider === provider);
1580
2567
  if (entry === void 0) return { ok: false };
@@ -1592,11 +2579,23 @@ function contextFor(ctx, state, agent, signal, command = false) {
1592
2579
  }
1593
2580
  },
1594
2581
  getProviderAuthStatus: (provider) => providerSupportsOAuth(providerConfig(provider)) ? "oauth" : "none",
1595
- getProvider: (provider) => providerConfig(provider),
2582
+ getProvider: (provider) => {
2583
+ const base = providerConfig(provider) ?? state.modelsJsonProviders.get(provider);
2584
+ if (base !== void 0) return state.providerRouteDisposers.has(provider) || state.providerRouteDisposers.has(`${MODELS_JSON_ROUTE_PREFIX}${provider}`) ? dshRoutedPiProvider(base, provider) : base;
2585
+ if ((catalog?.all() ?? []).some((entry) => entry.provider === provider)) return dshRoutedPiProvider({
2586
+ id: provider,
2587
+ name: provider
2588
+ }, provider);
2589
+ },
2590
+ complete: async (model, context, options) => {
2591
+ const result = await dshRoutedStream(model, context ?? {}, options).result();
2592
+ if (result instanceof Error) throw result;
2593
+ return result;
2594
+ },
1596
2595
  getRegisteredProviderConfig: (provider) => providerConfig(provider),
1597
2596
  getRegisteredProviderIds: () => [...state.providers.keys()],
1598
2597
  getProviderDisplayName: (provider) => {
1599
- const config = providerConfig(provider);
2598
+ const config = providerConfig(provider) ?? state.modelsJsonProviders.get(provider);
1600
2599
  return typeof config?.name === "string" ? config.name : provider;
1601
2600
  },
1602
2601
  getProviderAuth: async (provider) => {
@@ -1630,10 +2629,11 @@ function contextFor(ctx, state, agent, signal, command = false) {
1630
2629
  return provider.length > 0 && providerSupportsOAuth(providerConfig(provider));
1631
2630
  },
1632
2631
  refresh: async () => {
2632
+ await reloadModelsJson(ctx, state);
1633
2633
  await catalog?.refresh();
1634
2634
  return {
1635
- models: catalog?.all() ?? [],
1636
- errors: []
2635
+ models: allModels(),
2636
+ errors: [...state.modelsJson?.errors ?? []]
1637
2637
  };
1638
2638
  }
1639
2639
  },
@@ -1974,11 +2974,77 @@ function subscribeInterceptors(ctx, state) {
1974
2974
  };
1975
2975
  return downstream;
1976
2976
  });
2977
+ cordis.on("agent/pre-step", async (payload, next) => {
2978
+ const decision = await next();
2979
+ if (decision.kind !== "enter" || payload.step !== 1) return decision;
2980
+ if ((state.handlers.get("before_agent_start")?.length ?? 0) === 0) return decision;
2981
+ const agent = payload.agent;
2982
+ if (isSubagentOrigin(agent)) return decision;
2983
+ const signal = payload.signal;
2984
+ const messages = decision.messages ?? [];
2985
+ const userMessages = messages.filter((message) => (message.source?.kind ?? "user") === "user");
2986
+ const prompt = userMessages.flatMap((message) => textBlocks(message.content)).map((block) => block.text).join("\n");
2987
+ const images = await collectPiImages(ctx, userMessages);
2988
+ state.turnSystemPromptOverrides.delete(agent);
2989
+ const event = {
2990
+ type: "before_agent_start",
2991
+ prompt,
2992
+ ...images.length > 0 ? { images } : {},
2993
+ systemPrompt: state.currentSystemPrompt,
2994
+ systemPromptOptions: {}
2995
+ };
2996
+ const results = await dispatch(state, "before_agent_start", event, contextFor(ctx, state, agent, signal));
2997
+ const injected = [];
2998
+ for (const result of results) {
2999
+ if (typeof result !== "object" || result === null) continue;
3000
+ const record = result;
3001
+ if (typeof record.systemPrompt === "string") {
3002
+ state.turnSystemPromptOverrides.set(agent, record.systemPrompt);
3003
+ event.systemPrompt = record.systemPrompt;
3004
+ }
3005
+ const message = record.message;
3006
+ if (message !== void 0) {
3007
+ const content = message.content;
3008
+ const blocks = await piToDshContent(ctx, typeof content === "string" ? [{
3009
+ type: "text",
3010
+ text: content
3011
+ }] : content ?? []);
3012
+ injected.push(createUserMessage({
3013
+ content: blocks,
3014
+ source: {
3015
+ kind: "plugin",
3016
+ plugin: state.messageSource,
3017
+ ...typeof message.customType === "string" ? { piCustomType: message.customType } : {}
3018
+ }
3019
+ }));
3020
+ }
3021
+ }
3022
+ const stepMessages = injected.length === 0 ? messages : [...messages, ...injected];
3023
+ const transformed = await applyPiContextTransform(ctx, state, agent, signal, stepMessages);
3024
+ if (transformed === stepMessages && injected.length === 0) return decision;
3025
+ return {
3026
+ ...decision,
3027
+ messages: transformed
3028
+ };
3029
+ });
1977
3030
  cordis.on("system-prompt/assemble", async (assembly, assembleContext, next) => {
1978
3031
  const downstream = await next();
1979
3032
  if ((state.handlers.get("before_agent_start")?.length ?? 0) === 0) return downstream;
1980
3033
  const original = renderPrompt(downstream);
1981
3034
  state.currentSystemPrompt = original;
3035
+ const agent = assembleContext.agent ?? assembleContext.scope;
3036
+ if (agent !== void 0 && !isSubagentOrigin(agent)) {
3037
+ const override = state.turnSystemPromptOverrides.get(agent);
3038
+ if (override === void 0) return downstream;
3039
+ state.currentSystemPrompt = override;
3040
+ return {
3041
+ ...downstream,
3042
+ sections: [{
3043
+ name: "pi2dsh:system-prompt",
3044
+ text: override
3045
+ }]
3046
+ };
3047
+ }
1982
3048
  const event = {
1983
3049
  type: "before_agent_start",
1984
3050
  prompt: "",
@@ -1987,12 +3053,9 @@ function subscribeInterceptors(ctx, state) {
1987
3053
  };
1988
3054
  const results = await dispatch(state, "before_agent_start", event, contextFor(ctx, state, assembleContext.scope, assembleContext.signal));
1989
3055
  let replacement = original;
1990
- for (const result of results) {
1991
- if (typeof result === "object" && result !== null && typeof result.systemPrompt === "string") {
1992
- replacement = result.systemPrompt;
1993
- event.systemPrompt = replacement;
1994
- }
1995
- if (typeof result === "object" && result !== null && result.message !== void 0) logger(ctx).warn("[pi2dsh] before_agent_start custom-message injection is unsupported and was ignored");
3056
+ for (const result of results) if (typeof result === "object" && result !== null && typeof result.systemPrompt === "string") {
3057
+ replacement = result.systemPrompt;
3058
+ event.systemPrompt = replacement;
1996
3059
  }
1997
3060
  state.currentSystemPrompt = replacement;
1998
3061
  return {
@@ -2004,10 +3067,198 @@ function subscribeInterceptors(ctx, state) {
2004
3067
  };
2005
3068
  });
2006
3069
  }
3070
+ function piShapeOfPending(message) {
3071
+ const source = message.source;
3072
+ const content = dshToPiContent(message.content ?? []);
3073
+ return typeof source?.piCustomType === "string" ? {
3074
+ role: "custom",
3075
+ customType: source.piCustomType,
3076
+ content
3077
+ } : {
3078
+ role: "user",
3079
+ content
3080
+ };
3081
+ }
3082
+ async function applyPiContextTransform(ctx, state, agent, signal, pending) {
3083
+ if ((state.handlers.get("context")?.length ?? 0) === 0) return pending;
3084
+ const session = agentSession(agent);
3085
+ const history = [];
3086
+ for (const event of session?.events ?? []) {
3087
+ const projected = messageFromSessionEvent(event);
3088
+ if (projected === void 0) continue;
3089
+ if (event.type === "user/message") {
3090
+ const customType = (event.data?.source)?.piCustomType;
3091
+ if (typeof customType === "string") {
3092
+ history.push({
3093
+ ...projected,
3094
+ role: "custom",
3095
+ customType
3096
+ });
3097
+ continue;
3098
+ }
3099
+ }
3100
+ history.push(projected);
3101
+ }
3102
+ const projectedPending = pending.map(piShapeOfPending);
3103
+ const event = {
3104
+ type: "context",
3105
+ messages: [...history, ...projectedPending]
3106
+ };
3107
+ const results = await dispatch(state, "context", event, contextFor(ctx, state, agent, signal));
3108
+ let current = event.messages;
3109
+ for (const result of results) {
3110
+ if (typeof result !== "object" || result === null) continue;
3111
+ const returned = result.messages;
3112
+ if (!Array.isArray(returned)) continue;
3113
+ if (returned.length !== current.length) {
3114
+ logger(ctx).warn("[pi2dsh] a Pi context handler changed the message count; DSH durable history is append-only, so the transform was ignored");
3115
+ continue;
3116
+ }
3117
+ current = returned;
3118
+ }
3119
+ if (current === event.messages) return pending;
3120
+ for (let index = 0; index < history.length; index++) {
3121
+ if (current[index] === history[index]) continue;
3122
+ if (JSON.stringify(current[index]) === JSON.stringify(history[index])) continue;
3123
+ logger(ctx).warn("[pi2dsh] a Pi context handler edited already-entered history; DSH durable history is append-only, so those edits were ignored (only this step's not-yet-entered messages accept the transform)");
3124
+ break;
3125
+ }
3126
+ const tail = current.slice(history.length);
3127
+ const rebuilt = [];
3128
+ for (const [index, original] of pending.entries()) {
3129
+ const shape = tail[index];
3130
+ if (shape === void 0) {
3131
+ rebuilt.push(original);
3132
+ continue;
3133
+ }
3134
+ const blocks = await piToDshContent(ctx, typeof shape.content === "string" ? [{
3135
+ type: "text",
3136
+ text: shape.content
3137
+ }] : shape.content ?? []);
3138
+ rebuilt.push(createUserMessage({
3139
+ content: blocks,
3140
+ source: original.source ?? {
3141
+ kind: "plugin",
3142
+ plugin: state.messageSource
3143
+ }
3144
+ }));
3145
+ }
3146
+ return rebuilt;
3147
+ }
3148
+ async function collectPiImages(ctx, messages) {
3149
+ const attachments = ctx.get("attachments");
3150
+ if (attachments === void 0) return [];
3151
+ const images = [];
3152
+ for (const message of messages) {
3153
+ const content = message.content;
3154
+ if (!Array.isArray(content)) continue;
3155
+ for (const block of content) {
3156
+ if (block?.type !== "image" || block.attachment === void 0) continue;
3157
+ try {
3158
+ const stored = await attachments.readImage(block.attachment);
3159
+ images.push({
3160
+ type: "image",
3161
+ data: Buffer.from(stored.data).toString("base64"),
3162
+ mimeType: String(block.attachment.mediaType ?? "image/png")
3163
+ });
3164
+ } catch {}
3165
+ }
3166
+ }
3167
+ return images;
3168
+ }
2007
3169
  function oauthStoreOf(state) {
2008
3170
  state.oauthStore ??= new FileCredentialStore(join(getAgentDir(), "auth.json"));
2009
3171
  return state.oauthStore;
2010
3172
  }
3173
+ const MODELS_JSON_ROUTE_PREFIX = "models.json/";
3174
+ async function reloadModelsJson(ctx, state) {
3175
+ for (const [key, dispose] of state.providerRouteDisposers) {
3176
+ if (!key.startsWith(MODELS_JSON_ROUTE_PREFIX)) continue;
3177
+ state.providerRouteDisposers.delete(key);
3178
+ dispose();
3179
+ }
3180
+ state.modelsJsonProviders.clear();
3181
+ state.companionRoutes.clear();
3182
+ state.modelsJson = await loadModelsJsonSnapshot(realBuiltinProvider);
3183
+ for (const problem of state.modelsJson.errors) logger(ctx).warn(`[pi2dsh] models.json: ${problem}`);
3184
+ for (const [providerId, providerConfig] of state.modelsJson.providers) {
3185
+ const models = state.modelsJson.models.filter((model) => model.provider === providerId);
3186
+ const provider = modelsJsonPiProvider(providerId, providerConfig, models);
3187
+ const routeDisposer = registerPiProviderRoute({
3188
+ llm: llmOf(ctx),
3189
+ providerId,
3190
+ provider,
3191
+ host: {
3192
+ resolveAuth: async () => {
3193
+ const resolved = resolveModelsJsonAuth(providerId, providerConfig);
3194
+ if (!resolved.ok) return void 0;
3195
+ return { auth: {
3196
+ ...resolved.apiKey === void 0 ? {} : { apiKey: resolved.apiKey },
3197
+ ...resolved.headers === void 0 ? {} : { headers: resolved.headers },
3198
+ ...providerConfig.baseUrl === void 0 ? {} : { baseUrl: providerConfig.baseUrl }
3199
+ } };
3200
+ },
3201
+ warn: (message) => logger(ctx).warn(message)
3202
+ }
3203
+ });
3204
+ if (routeDisposer !== void 0) {
3205
+ state.modelsJsonProviders.set(providerId, provider);
3206
+ state.providerRouteDisposers.set(`${MODELS_JSON_ROUTE_PREFIX}${providerId}`, routeDisposer);
3207
+ logger(ctx).info(`[pi2dsh] models.json provider ${JSON.stringify(providerId)} registered as a native DSH llm route (${models.length} models)`);
3208
+ }
3209
+ }
3210
+ registerCompanionRoutes(ctx, state);
3211
+ }
3212
+ function registerCompanionRoutes(ctx, state) {
3213
+ const companions = state.modelsJson?.companions;
3214
+ if (companions === void 0 || companions.size === 0) return;
3215
+ const llm = llmOf(ctx);
3216
+ if (llm === void 0) return;
3217
+ for (const [originalId, imageModels] of companions) {
3218
+ if (!llm.listProviders().some((provider) => provider.id === originalId)) {
3219
+ logger(ctx).warn(`[pi2dsh] models.json: modelOverrides for ${JSON.stringify(originalId)} declare image input, but no such llm route exists; no companion route was registered`);
3220
+ continue;
3221
+ }
3222
+ const companionId = `${originalId}-vision`;
3223
+ state.companionRoutes.set(companionId, originalId);
3224
+ try {
3225
+ const dispose = llm.registerAdapter([companionId], imageAdmissionCompanionAdapter({
3226
+ originalId,
3227
+ imageModels,
3228
+ llm,
3229
+ materializeImage: (attachment) => materializeAttachmentImage(ctx, attachment)
3230
+ }));
3231
+ state.providerRouteDisposers.set(`${MODELS_JSON_ROUTE_PREFIX}${companionId}`, dispose);
3232
+ logger(ctx).info(`[pi2dsh] image-admission companion route ${JSON.stringify(companionId)} registered for ${JSON.stringify(originalId)} (${imageModels.size} models)`);
3233
+ } catch (error) {
3234
+ logger(ctx).warn(`[pi2dsh] companion route ${JSON.stringify(companionId)} already has a live adapter in this host (${error instanceof Error ? error.message : String(error)}); reusing it`);
3235
+ }
3236
+ }
3237
+ }
3238
+ const IMAGE_EXTENSIONS = {
3239
+ "image/png": "png",
3240
+ "image/jpeg": "jpg",
3241
+ "image/webp": "webp",
3242
+ "image/gif": "gif"
3243
+ };
3244
+ async function materializeAttachmentImage(ctx, attachment) {
3245
+ const attachments = ctx.get("attachments");
3246
+ const id = typeof attachment.attachmentId === "string" ? attachment.attachmentId : void 0;
3247
+ if (attachments === void 0 || id === void 0) return void 0;
3248
+ const extension = IMAGE_EXTENSIONS[String(attachment.mediaType)] ?? "png";
3249
+ const dir = join(tmpdir(), "pi2dsh-attached-images");
3250
+ const filePath = join(dir, `${id}.${extension}`);
3251
+ try {
3252
+ if (!existsSync(filePath)) {
3253
+ const stored = await attachments.readImage(attachment);
3254
+ await mkdir(dir, { recursive: true });
3255
+ await writeFile(filePath, Buffer.from(stored.data));
3256
+ }
3257
+ return filePath;
3258
+ } catch {
3259
+ return;
3260
+ }
3261
+ }
2011
3262
  function ensureLoginCommand(ctx, state) {
2012
3263
  if (state.loginCommandRegistered === true) return;
2013
3264
  state.loginCommandRegistered = true;
@@ -2206,15 +3457,19 @@ function dshCommandName(ctx, piName) {
2206
3457
  return name;
2207
3458
  }
2208
3459
  function registerCommand(ctx, state, command) {
2209
- if (state.commands.has(command.name)) throw new Error(`Pi command ${JSON.stringify(command.name)} is already registered`);
3460
+ if (state.commands.has(command.name)) {
3461
+ state.commandDisposers.get(command.name)?.();
3462
+ state.commandDisposers.delete(command.name);
3463
+ }
2210
3464
  state.commands.set(command.name, command);
2211
3465
  const commands = ctx.get("commands");
2212
3466
  if (commands === void 0) {
2213
3467
  logger(ctx).warn(`[pi2dsh] command /${command.name} was not registered because this DSH composition has no ctx.commands`);
2214
3468
  return;
2215
3469
  }
2216
- commands.register({
2217
- name: dshCommandName(ctx, command.name),
3470
+ const baseName = dshCommandName(ctx, command.name);
3471
+ const definitionFor = (dshName) => ({
3472
+ name: dshName,
2218
3473
  description: command.description || `Migrated Pi command /${command.name}`,
2219
3474
  ...command.argumentHint !== void 0 ? { input: { hint: command.argumentHint } } : {},
2220
3475
  async handler(invocation) {
@@ -2228,6 +3483,18 @@ function registerCommand(ctx, state, command) {
2228
3483
  };
2229
3484
  }
2230
3485
  });
3486
+ let lastError;
3487
+ for (let ordinal = 1; ordinal <= 9; ordinal++) {
3488
+ const dshName = ordinal === 1 ? baseName : `${baseName}-${ordinal}`;
3489
+ try {
3490
+ state.commandDisposers.set(command.name, commands.register(definitionFor(dshName)));
3491
+ if (ordinal > 1) logger(ctx).warn(`[pi2dsh] command /${command.name} collides with an earlier registration in this host; mounted as /${dshName} (Pi numbers colliding commands the same way)`);
3492
+ return;
3493
+ } catch (error) {
3494
+ lastError = error;
3495
+ }
3496
+ }
3497
+ logger(ctx).warn(`[pi2dsh] command /${command.name} was not registered: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
2231
3498
  }
2232
3499
  function requireSession(state, operation) {
2233
3500
  const session = agentSession(currentAgent(state));
@@ -2569,6 +3836,9 @@ async function applyPiPackage(ctx, options) {
2569
3836
  toolDisposers: /* @__PURE__ */ new Map(),
2570
3837
  toolRestrictions: /* @__PURE__ */ new WeakMap(),
2571
3838
  commands: /* @__PURE__ */ new Map(),
3839
+ commandDisposers: /* @__PURE__ */ new Map(),
3840
+ modelsJsonProviders: /* @__PURE__ */ new Map(),
3841
+ companionRoutes: /* @__PURE__ */ new Map(),
2572
3842
  flags: /* @__PURE__ */ new Map(),
2573
3843
  notifications: [],
2574
3844
  activeAgents: /* @__PURE__ */ new Set(),
@@ -2588,6 +3858,7 @@ async function applyPiPackage(ctx, options) {
2588
3858
  toolsExpanded: false,
2589
3859
  modelOverrides: /* @__PURE__ */ new WeakMap(),
2590
3860
  thinkingLevels: /* @__PURE__ */ new WeakMap(),
3861
+ turnSystemPromptOverrides: /* @__PURE__ */ new WeakMap(),
2591
3862
  globalThinkingLevel: "off",
2592
3863
  argMutations: /* @__PURE__ */ new WeakMap(),
2593
3864
  streamingTexts: /* @__PURE__ */ new Map(),
@@ -2616,6 +3887,7 @@ async function applyPiPackage(ctx, options) {
2616
3887
  }
2617
3888
  const llm = llmOf(ctx);
2618
3889
  state.modelCatalog = new ModelCatalog(llm);
3890
+ await reloadModelsJson(ctx, state);
2619
3891
  if (llm !== void 0) {
2620
3892
  ctx.on("llm/adapters-updated", () => {
2621
3893
  state.modelCatalog?.refresh();
@@ -2635,6 +3907,13 @@ async function applyPiPackage(ctx, options) {
2635
3907
  const session = agentSession(currentAgent(state));
2636
3908
  return session === void 0 ? void 0 : String(session.id ?? "") || void 0;
2637
3909
  },
3910
+ parentDelegationDepth: () => {
3911
+ const parent = currentAgent(state);
3912
+ const header = agentSession(parent)?.header;
3913
+ const fromHeader = typeof header?.delegationDepth === "number" ? header.delegationDepth : 0;
3914
+ const fromOptions = typeof parent?.options?.subagentDepth === "number" ? parent.options.subagentDepth : 0;
3915
+ return Math.max(fromHeader, fromOptions);
3916
+ },
2638
3917
  piContentToDsh: (content) => piToDshContent(ctx, content),
2639
3918
  deliver: (agent, message, mode) => deliverAgentMessage(agent, message, mode),
2640
3919
  messageFromSessionEvent,
@@ -2654,4 +3933,4 @@ const runtimeInternals = {
2654
3933
  //#endregion
2655
3934
  export { normalizeToolSchema as n, runtimeInternals as r, applyPiPackage as t };
2656
3935
 
2657
- //# sourceMappingURL=runtime-D1oXtRIx.mjs.map
3936
+ //# sourceMappingURL=runtime-VK4eVnYa.mjs.map