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.
- package/README.md +17 -0
- package/README.zh.md +11 -0
- package/dist/{all-Rt4K68QA.mjs → all-DSpxKvHa.mjs} +10 -10
- package/dist/{all-Rt4K68QA.mjs.map → all-DSpxKvHa.mjs.map} +1 -1
- package/dist/{anthropic-messages-Beym0_aC.mjs → anthropic-messages-BL9N1Idv.mjs} +3 -3
- package/dist/{anthropic-messages-Beym0_aC.mjs.map → anthropic-messages-BL9N1Idv.mjs.map} +1 -1
- package/dist/{azure-openai-responses-cHqZTgLD.mjs → azure-openai-responses-A67VbaZ1.mjs} +3 -3
- package/dist/{azure-openai-responses-cHqZTgLD.mjs.map → azure-openai-responses-A67VbaZ1.mjs.map} +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/compat/pi-ai.d.mts +3 -38
- package/dist/compat/pi-ai.d.mts.map +1 -1
- package/dist/compat/pi-ai.mjs +3 -3
- package/dist/compat/pi-coding-agent.d.mts +32 -31
- package/dist/compat/pi-coding-agent.d.mts.map +1 -1
- package/dist/compat/pi-coding-agent.mjs +1 -1
- package/dist/credentials-oauth.mjs +2 -2
- package/dist/{dist-QBBA-dL_.mjs → dist-BYA74yh_.mjs} +2 -2
- package/dist/{dist-QBBA-dL_.mjs.map → dist-BYA74yh_.mjs.map} +1 -1
- package/dist/dist-DeonWo7m.mjs +1089 -0
- package/dist/dist-DeonWo7m.mjs.map +1 -0
- package/dist/{google-generative-ai-B7OZs5lu.mjs → google-generative-ai-DT5S8D8y.mjs} +2 -2
- package/dist/{google-generative-ai-B7OZs5lu.mjs.map → google-generative-ai-DT5S8D8y.mjs.map} +1 -1
- package/dist/{google-shared-CRsAUItF.mjs → google-shared-DYP_9cnK.mjs} +4 -4
- package/dist/{google-shared-CRsAUItF.mjs.map → google-shared-DYP_9cnK.mjs.map} +1 -1
- package/dist/{google-vertex-DmdaHQtB.mjs → google-vertex-Dpq7O7Be.mjs} +3 -3
- package/dist/{google-vertex-DmdaHQtB.mjs.map → google-vertex-Dpq7O7Be.mjs.map} +1 -1
- package/dist/host.mjs +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{json-parse-CFyFZSLJ.mjs → json-parse-Dz5sKMTv.mjs} +2 -2
- package/dist/{json-parse-CFyFZSLJ.mjs.map → json-parse-Dz5sKMTv.mjs.map} +1 -1
- package/dist/{mcp-config-D2B3kv-Z.mjs → mcp-config-ByUc0jmD.mjs} +10 -9
- package/dist/mcp-config-ByUc0jmD.mjs.map +1 -0
- package/dist/{mistral-conversations-HHZCcFv_.mjs → mistral-conversations-BsxbCCWx.mjs} +4 -4
- package/dist/{mistral-conversations-HHZCcFv_.mjs.map → mistral-conversations-BsxbCCWx.mjs.map} +1 -1
- package/dist/{multipart-parser-CIa5BbwO.mjs → multipart-parser-woKCuALW.mjs} +3 -3
- package/dist/{multipart-parser-CIa5BbwO.mjs.map → multipart-parser-woKCuALW.mjs.map} +1 -1
- package/dist/{openai-codex-responses-vmovNu-q.mjs → openai-codex-responses-C7vviVT2.mjs} +3 -3
- package/dist/{openai-codex-responses-vmovNu-q.mjs.map → openai-codex-responses-C7vviVT2.mjs.map} +1 -1
- package/dist/{openai-completions-fXHhRolF.mjs → openai-completions-By8QNX1P.mjs} +3 -3
- package/dist/{openai-completions-fXHhRolF.mjs.map → openai-completions-By8QNX1P.mjs.map} +1 -1
- package/dist/{openai-responses-DKdCYf3h.mjs → openai-responses-DboJUcFN.mjs} +3 -3
- package/dist/{openai-responses-DKdCYf3h.mjs.map → openai-responses-DboJUcFN.mjs.map} +1 -1
- package/dist/{openai-responses-shared-D0Jq7yVP.mjs → openai-responses-shared-BDzn7nJY.mjs} +2 -2
- package/dist/{openai-responses-shared-D0Jq7yVP.mjs.map → openai-responses-shared-BDzn7nJY.mjs.map} +1 -1
- package/dist/{otel-BQSQRBLF.mjs → otel-PimLMoiV.mjs} +3 -3
- package/dist/{otel-BQSQRBLF.mjs.map → otel-PimLMoiV.mjs.map} +1 -1
- package/dist/{pi-ai-DhYGgq4Y.mjs → pi-ai-B_88Wb0r.mjs} +49 -6
- package/dist/{pi-ai-DhYGgq4Y.mjs.map → pi-ai-B_88Wb0r.mjs.map} +1 -1
- package/dist/{pi-coding-agent-DuqSaquA.mjs → pi-coding-agent-C9Y9ql5E.mjs} +35 -34
- package/dist/pi-coding-agent-C9Y9ql5E.mjs.map +1 -0
- package/dist/{pi-messages-CV-Ts8An.mjs → pi-messages-B-7Xo_I0.mjs} +3 -3
- package/dist/{pi-messages-CV-Ts8An.mjs.map → pi-messages-B-7Xo_I0.mjs.map} +1 -1
- package/dist/pi-types-KazmR2O5.d.mts +62 -0
- package/dist/pi-types-KazmR2O5.d.mts.map +1 -0
- package/dist/pi-uuid-Db8ShZsK.mjs +47 -0
- package/dist/pi-uuid-Db8ShZsK.mjs.map +1 -0
- package/dist/{provider-env-C72pF4vP.mjs → provider-env-D-cCRWzv.mjs} +2 -2
- package/dist/{provider-env-C72pF4vP.mjs.map → provider-env-D-cCRWzv.mjs.map} +1 -1
- package/dist/{runtime-D1oXtRIx.mjs → runtime-VK4eVnYa.mjs} +1333 -54
- package/dist/runtime-VK4eVnYa.mjs.map +1 -0
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +1 -1
- package/dist/{src-C3WBfOks.mjs → src-BEl3QrGL.mjs} +3 -3
- package/dist/{src-C3WBfOks.mjs.map → src-BEl3QrGL.mjs.map} +1 -1
- package/package.json +2 -2
- package/dist/build-BG7p2Lbr.mjs +0 -4416
- package/dist/build-BG7p2Lbr.mjs.map +0 -1
- package/dist/dist-IQKOgQyt.mjs +0 -10035
- package/dist/dist-IQKOgQyt.mjs.map +0 -1
- package/dist/mcp-config-D2B3kv-Z.mjs.map +0 -1
- package/dist/pi-coding-agent-DuqSaquA.mjs.map +0 -1
- package/dist/pi-types-BLt46jaf.d.mts +0 -2494
- package/dist/pi-types-BLt46jaf.d.mts.map +0 -1
- package/dist/rolldown-runtime-CTfmNlz1.mjs +0 -44
- package/dist/runtime-D1oXtRIx.mjs.map +0 -1
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
-
*
|
|
1160
|
-
*
|
|
1161
|
-
*
|
|
1162
|
-
*
|
|
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
|
|
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,
|
|
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
|
|
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: "
|
|
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
|
|
1522
|
-
const
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
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
|
|
2468
|
+
if (!Array.isArray(models)) return void 0;
|
|
2469
|
+
return models.find((model) => model.id === id);
|
|
1537
2470
|
};
|
|
1538
|
-
const
|
|
1539
|
-
const
|
|
1540
|
-
|
|
1541
|
-
|
|
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) =>
|
|
1556
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
...
|
|
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) =>
|
|
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:
|
|
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
|
-
|
|
1992
|
-
|
|
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))
|
|
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
|
-
|
|
2217
|
-
|
|
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-
|
|
3936
|
+
//# sourceMappingURL=runtime-VK4eVnYa.mjs.map
|