pi2dsh 0.4.0 → 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/cli.mjs +1 -1
- package/dist/compat/pi-coding-agent.mjs +1 -1
- package/dist/credentials-oauth.mjs +1 -1
- package/dist/host.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{mcp-config-B3h5jTBb.mjs → mcp-config-ByUc0jmD.mjs} +3 -3
- package/dist/mcp-config-ByUc0jmD.mjs.map +1 -0
- package/dist/{pi-coding-agent-COMX0KHL.mjs → pi-coding-agent-C9Y9ql5E.mjs} +2 -2
- package/dist/{pi-coding-agent-COMX0KHL.mjs.map → pi-coding-agent-C9Y9ql5E.mjs.map} +1 -1
- package/dist/{runtime-B7mEIRf-.mjs → runtime-VK4eVnYa.mjs} +153 -5
- package/dist/runtime-VK4eVnYa.mjs.map +1 -0
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +1 -1
- package/package.json +1 -1
- package/dist/mcp-config-B3h5jTBb.mjs.map +0 -1
- package/dist/runtime-B7mEIRf-.mjs.map +0 -1
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
|
|
2
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
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-
|
|
4
|
+
import { E as __setSubagentSessionFactory, S as Theme, f as ExtensionRunner } from "./pi-coding-agent-C9Y9ql5E.mjs";
|
|
5
5
|
import { a as resolvePiProviderAuth, n as loginPiProvider, r as providerSupportsOAuth, t as FileCredentialStore } from "./oauth-bridge-C6fL1qSv.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";
|
|
@@ -494,6 +495,7 @@ async function createBridgedAgentSession(host, options) {
|
|
|
494
495
|
meta: {
|
|
495
496
|
cwd: typeof options.cwd === "string" ? options.cwd : host.cwd(),
|
|
496
497
|
origin: "subagent",
|
|
498
|
+
delegationDepth: host.parentDelegationDepth() + 1,
|
|
497
499
|
...host.parentSessionId() !== void 0 ? { parentSession: host.parentSessionId() } : {}
|
|
498
500
|
},
|
|
499
501
|
...typeof requestedModel?.id === "string" && requestedModel.id.length > 0 ? { agentOptions: {
|
|
@@ -1750,8 +1752,17 @@ function modelsJsonPath() {
|
|
|
1750
1752
|
const emptySnapshot = () => ({
|
|
1751
1753
|
models: [],
|
|
1752
1754
|
providers: /* @__PURE__ */ new Map(),
|
|
1753
|
-
errors: []
|
|
1755
|
+
errors: [],
|
|
1756
|
+
companions: /* @__PURE__ */ new Map()
|
|
1754
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
|
+
}
|
|
1755
1766
|
/**
|
|
1756
1767
|
* One load of models.json projected to Pi Model objects. Mirrors Pi's
|
|
1757
1768
|
* composeModelProvider composition order: base (builtin) models →
|
|
@@ -1770,6 +1781,11 @@ async function loadModelsJsonSnapshot(baseProviderOf) {
|
|
|
1770
1781
|
for (const providerId of config.getProviderIds()) {
|
|
1771
1782
|
const providerConfig = config.getProvider(providerId);
|
|
1772
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
|
+
}
|
|
1773
1789
|
try {
|
|
1774
1790
|
const base = await baseProviderOf?.(providerId);
|
|
1775
1791
|
const composed = applyModelsJson(providerId, typeof base?.getModels === "function" ? base.getModels() : [], providerConfig).map((model) => {
|
|
@@ -2002,6 +2018,77 @@ function piProviderDshAdapter(providerId, provider, host) {
|
|
|
2002
2018
|
}
|
|
2003
2019
|
};
|
|
2004
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
|
+
}
|
|
2045
|
+
/**
|
|
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
|
+
}
|
|
2005
2092
|
/**
|
|
2006
2093
|
* Register the provider as a live DSH route when an llm service is mounted.
|
|
2007
2094
|
* A provider carrying its own transport streams through it; a config-only
|
|
@@ -2271,7 +2358,8 @@ function isSubagentOrigin(subject) {
|
|
|
2271
2358
|
function currentPiModel(state, agent) {
|
|
2272
2359
|
const override = state.modelOverrides.get(agent);
|
|
2273
2360
|
const options = agent.options;
|
|
2274
|
-
const
|
|
2361
|
+
const selectedProvider = String(override?.provider ?? options?.provider ?? "");
|
|
2362
|
+
const provider = state.companionRoutes.get(selectedProvider) ?? selectedProvider;
|
|
2275
2363
|
const id = String(override?.model ?? options?.model ?? "");
|
|
2276
2364
|
if (id.length === 0) return override;
|
|
2277
2365
|
return (provider.length > 0 ? state.modelCatalog?.find(provider, id) : void 0) ?? {
|
|
@@ -3090,6 +3178,7 @@ async function reloadModelsJson(ctx, state) {
|
|
|
3090
3178
|
dispose();
|
|
3091
3179
|
}
|
|
3092
3180
|
state.modelsJsonProviders.clear();
|
|
3181
|
+
state.companionRoutes.clear();
|
|
3093
3182
|
state.modelsJson = await loadModelsJsonSnapshot(realBuiltinProvider);
|
|
3094
3183
|
for (const problem of state.modelsJson.errors) logger(ctx).warn(`[pi2dsh] models.json: ${problem}`);
|
|
3095
3184
|
for (const [providerId, providerConfig] of state.modelsJson.providers) {
|
|
@@ -3118,6 +3207,57 @@ async function reloadModelsJson(ctx, state) {
|
|
|
3118
3207
|
logger(ctx).info(`[pi2dsh] models.json provider ${JSON.stringify(providerId)} registered as a native DSH llm route (${models.length} models)`);
|
|
3119
3208
|
}
|
|
3120
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
|
+
}
|
|
3121
3261
|
}
|
|
3122
3262
|
function ensureLoginCommand(ctx, state) {
|
|
3123
3263
|
if (state.loginCommandRegistered === true) return;
|
|
@@ -3698,6 +3838,7 @@ async function applyPiPackage(ctx, options) {
|
|
|
3698
3838
|
commands: /* @__PURE__ */ new Map(),
|
|
3699
3839
|
commandDisposers: /* @__PURE__ */ new Map(),
|
|
3700
3840
|
modelsJsonProviders: /* @__PURE__ */ new Map(),
|
|
3841
|
+
companionRoutes: /* @__PURE__ */ new Map(),
|
|
3701
3842
|
flags: /* @__PURE__ */ new Map(),
|
|
3702
3843
|
notifications: [],
|
|
3703
3844
|
activeAgents: /* @__PURE__ */ new Set(),
|
|
@@ -3766,6 +3907,13 @@ async function applyPiPackage(ctx, options) {
|
|
|
3766
3907
|
const session = agentSession(currentAgent(state));
|
|
3767
3908
|
return session === void 0 ? void 0 : String(session.id ?? "") || void 0;
|
|
3768
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
|
+
},
|
|
3769
3917
|
piContentToDsh: (content) => piToDshContent(ctx, content),
|
|
3770
3918
|
deliver: (agent, message, mode) => deliverAgentMessage(agent, message, mode),
|
|
3771
3919
|
messageFromSessionEvent,
|
|
@@ -3785,4 +3933,4 @@ const runtimeInternals = {
|
|
|
3785
3933
|
//#endregion
|
|
3786
3934
|
export { normalizeToolSchema as n, runtimeInternals as r, applyPiPackage as t };
|
|
3787
3935
|
|
|
3788
|
-
//# sourceMappingURL=runtime-
|
|
3936
|
+
//# sourceMappingURL=runtime-VK4eVnYa.mjs.map
|