@pi-unipi/unipi 2.3.0 → 2.4.1
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/CHANGELOG.md +32 -0
- package/package.json +21 -21
- package/packages/ask-user/package.json +2 -2
- package/packages/autocomplete/package.json +1 -1
- package/packages/btw/package.json +2 -2
- package/packages/cocoindex/package.json +2 -2
- package/packages/compactor/package.json +3 -3
- package/packages/compactor/src/compaction/auto-trigger.ts +0 -0
- package/packages/compactor/src/config/schema.ts +0 -0
- package/packages/core/package.json +1 -1
- package/packages/footer/package.json +2 -2
- package/packages/image/package.json +2 -2
- package/packages/image/src/generate.ts +34 -15
- package/packages/image/src/index.ts +8 -0
- package/packages/image/src/models.ts +28 -0
- package/packages/image/src/openai-images-api.ts +282 -0
- package/packages/image/src/register-providers.ts +220 -0
- package/packages/image/src/tools.ts +37 -6
- package/packages/image/src/tui/settings-dialog.ts +6 -1
- package/packages/info-screen/package.json +2 -2
- package/packages/input-shortcuts/package.json +2 -2
- package/packages/kanboard/package.json +2 -2
- package/packages/mcp/package.json +2 -2
- package/packages/memory/package.json +3 -3
- package/packages/milestone/hooks.ts +0 -0
- package/packages/milestone/package.json +2 -2
- package/packages/notify/package.json +2 -2
- package/packages/ralph/index.ts +10 -5
- package/packages/ralph/package.json +3 -3
- package/packages/subagents/package.json +4 -4
- package/packages/unipi/bundled.js +298 -21
- package/packages/updater/package.json +2 -2
- package/packages/utility/package.json +2 -2
- package/packages/web-api/package.json +2 -2
- package/packages/workflow/package.json +2 -2
|
@@ -7743,7 +7743,7 @@ function ralph_default(pi) {
|
|
|
7743
7743
|
);
|
|
7744
7744
|
}
|
|
7745
7745
|
});
|
|
7746
|
-
pi.on("before_agent_start", async (
|
|
7746
|
+
pi.on("before_agent_start", async (_event, ctx) => {
|
|
7747
7747
|
const mgr = getManager(ctx, pi);
|
|
7748
7748
|
const currentLoop = mgr.getCurrentLoop();
|
|
7749
7749
|
if (!currentLoop) return;
|
|
@@ -7762,10 +7762,13 @@ function ralph_default(pi) {
|
|
|
7762
7762
|
`;
|
|
7763
7763
|
instructions += `- Otherwise, call ralph_done tool to proceed to next iteration`;
|
|
7764
7764
|
return {
|
|
7765
|
-
|
|
7766
|
-
|
|
7765
|
+
message: {
|
|
7766
|
+
customType: "unipi-ralph-loop-reminder",
|
|
7767
|
+
content: `[RALPH LOOP - ${state.name} - Iteration ${iterStr}]
|
|
7767
7768
|
|
|
7768
|
-
${instructions}
|
|
7769
|
+
${instructions}`,
|
|
7770
|
+
display: false
|
|
7771
|
+
}
|
|
7769
7772
|
};
|
|
7770
7773
|
});
|
|
7771
7774
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
@@ -36307,11 +36310,25 @@ function listRegistryImageGenModels(registry3) {
|
|
|
36307
36310
|
provider: candidate.provider,
|
|
36308
36311
|
name: candidate.name,
|
|
36309
36312
|
api: candidate.api ?? "",
|
|
36313
|
+
// Carry the endpoint through. The generic images adapter POSTs to
|
|
36314
|
+
// `{baseUrl}/images/generations`, and this is the only place the
|
|
36315
|
+
// registry's baseUrl is available — dropping it here surfaces later as
|
|
36316
|
+
// "No baseUrl for image model ..." once generation is attempted.
|
|
36317
|
+
...candidate.baseUrl ? { baseUrl: candidate.baseUrl } : {},
|
|
36310
36318
|
...candidate.output ? { output: candidate.output } : {}
|
|
36311
36319
|
});
|
|
36312
36320
|
}
|
|
36313
36321
|
return out;
|
|
36314
36322
|
}
|
|
36323
|
+
function findProviderBaseUrl(registry3, provider) {
|
|
36324
|
+
if (!registry3) return void 0;
|
|
36325
|
+
try {
|
|
36326
|
+
const models = registry3.getAvailable?.() ?? registry3.getAll();
|
|
36327
|
+
return models.find((m) => m?.provider === provider && m.baseUrl)?.baseUrl;
|
|
36328
|
+
} catch {
|
|
36329
|
+
return void 0;
|
|
36330
|
+
}
|
|
36331
|
+
}
|
|
36315
36332
|
async function listAllImageGenModels(registry3) {
|
|
36316
36333
|
const builtin = await listImageGenModels();
|
|
36317
36334
|
const fromRegistry = registry3 ? listRegistryImageGenModels(registry3) : [];
|
|
@@ -36745,6 +36762,240 @@ var ImageModelSelectorOverlay = class {
|
|
|
36745
36762
|
}
|
|
36746
36763
|
};
|
|
36747
36764
|
|
|
36765
|
+
// packages/image/src/openai-images-api.ts
|
|
36766
|
+
var openai_images_api_exports = {};
|
|
36767
|
+
__export(openai_images_api_exports, {
|
|
36768
|
+
generateImages: () => generateImages,
|
|
36769
|
+
normalizeImageItem: () => normalizeImageItem,
|
|
36770
|
+
normalizeModelId: () => normalizeModelId
|
|
36771
|
+
});
|
|
36772
|
+
var DEFAULT_TIMEOUT_MS2 = 24e4;
|
|
36773
|
+
function asString(value) {
|
|
36774
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
36775
|
+
}
|
|
36776
|
+
function normalizeImageItem(item) {
|
|
36777
|
+
const declared = asString(item.media_type) ?? asString(item.mime_type) ?? asString(item.mimeType);
|
|
36778
|
+
const b64 = asString(item.b64_json);
|
|
36779
|
+
if (b64) return { data: b64, mimeType: declared ?? "image/png" };
|
|
36780
|
+
const url = asString(item.url);
|
|
36781
|
+
if (!url) return null;
|
|
36782
|
+
const dataUrl = /^data:([^;,]+)(?:;[^,]*)*,(.*)$/s.exec(url);
|
|
36783
|
+
if (dataUrl) {
|
|
36784
|
+
const [, mime, payload] = dataUrl;
|
|
36785
|
+
if (payload) return { data: payload, mimeType: declared ?? mime ?? "image/png" };
|
|
36786
|
+
return null;
|
|
36787
|
+
}
|
|
36788
|
+
return { text: `Image available at: ${url}` };
|
|
36789
|
+
}
|
|
36790
|
+
function joinUrl(baseUrl, suffix) {
|
|
36791
|
+
return `${baseUrl.replace(/\/+$/, "")}/${suffix.replace(/^\/+/, "")}`;
|
|
36792
|
+
}
|
|
36793
|
+
function normalizeModelId(id) {
|
|
36794
|
+
return id;
|
|
36795
|
+
}
|
|
36796
|
+
function buildHeaders(apiKey, extra) {
|
|
36797
|
+
const headers = {
|
|
36798
|
+
Authorization: `Bearer ${apiKey}`,
|
|
36799
|
+
"Content-Type": "application/json"
|
|
36800
|
+
};
|
|
36801
|
+
for (const [key, value] of Object.entries(extra ?? {})) {
|
|
36802
|
+
if (value === null) delete headers[key];
|
|
36803
|
+
else headers[key] = value;
|
|
36804
|
+
}
|
|
36805
|
+
return headers;
|
|
36806
|
+
}
|
|
36807
|
+
function describeError(status2, statusText, body) {
|
|
36808
|
+
let detail = body.slice(0, 300);
|
|
36809
|
+
try {
|
|
36810
|
+
const parsed = JSON.parse(body);
|
|
36811
|
+
if (typeof parsed.error === "string") detail = parsed.error;
|
|
36812
|
+
else if (parsed.error?.message) detail = parsed.error.message;
|
|
36813
|
+
} catch {
|
|
36814
|
+
}
|
|
36815
|
+
return `${status2} ${statusText}${detail ? `: ${detail}` : ""}`;
|
|
36816
|
+
}
|
|
36817
|
+
function annotateModelError(message, model) {
|
|
36818
|
+
if (/invalid image model|not found|unknown model/i.test(message)) {
|
|
36819
|
+
return `${message}
|
|
36820
|
+
\u2192 Model id sent: "${model.id}" (provider "${model.provider}").
|
|
36821
|
+
\u2192 Some gateways list aliases they cannot serve. Try the id exactly as it appears in the provider's own catalog, or pick another with /unipi:image-settings.`;
|
|
36822
|
+
}
|
|
36823
|
+
return message;
|
|
36824
|
+
}
|
|
36825
|
+
async function generateImages(model, context, options) {
|
|
36826
|
+
const result = {
|
|
36827
|
+
api: model.api || "openai-images",
|
|
36828
|
+
provider: model.provider,
|
|
36829
|
+
model: model.id,
|
|
36830
|
+
output: [],
|
|
36831
|
+
stopReason: "stop",
|
|
36832
|
+
timestamp: Date.now()
|
|
36833
|
+
};
|
|
36834
|
+
const fetchImpl = options?.fetchImpl ?? fetch;
|
|
36835
|
+
const controller = new AbortController();
|
|
36836
|
+
const timer = setTimeout(
|
|
36837
|
+
() => controller.abort(),
|
|
36838
|
+
options?.timeoutMs ?? DEFAULT_TIMEOUT_MS2
|
|
36839
|
+
);
|
|
36840
|
+
const onAbort = () => controller.abort();
|
|
36841
|
+
options?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
36842
|
+
try {
|
|
36843
|
+
const apiKey = options?.apiKey;
|
|
36844
|
+
if (!apiKey) throw new Error(`No API key for provider: ${model.provider}`);
|
|
36845
|
+
if (!model.baseUrl) {
|
|
36846
|
+
throw new Error(`No baseUrl for image model ${model.provider}/${model.id}`);
|
|
36847
|
+
}
|
|
36848
|
+
const prompt = context.input.filter((part) => part.type === "text" && part.text).map((part) => part.text).join("\n").trim();
|
|
36849
|
+
const images = context.input.filter((part) => part.type === "image" && part.data).map((part) => `data:${part.mimeType || "image/png"};base64,${part.data}`);
|
|
36850
|
+
if (!prompt) throw new Error("A non-empty prompt is required.");
|
|
36851
|
+
const body = {
|
|
36852
|
+
model: normalizeModelId(model.id),
|
|
36853
|
+
prompt
|
|
36854
|
+
};
|
|
36855
|
+
if (images.length > 0) body.image = images;
|
|
36856
|
+
const response = await fetchImpl(joinUrl(model.baseUrl, "images/generations"), {
|
|
36857
|
+
method: "POST",
|
|
36858
|
+
headers: buildHeaders(apiKey, options?.headers),
|
|
36859
|
+
body: JSON.stringify(body),
|
|
36860
|
+
signal: controller.signal
|
|
36861
|
+
});
|
|
36862
|
+
if (!response.ok) {
|
|
36863
|
+
throw new Error(
|
|
36864
|
+
describeError(response.status, response.statusText, await response.text())
|
|
36865
|
+
);
|
|
36866
|
+
}
|
|
36867
|
+
const payload = await response.json();
|
|
36868
|
+
if (payload.error?.message) throw new Error(payload.error.message);
|
|
36869
|
+
for (const item of payload.data ?? []) {
|
|
36870
|
+
const normalized = normalizeImageItem(item);
|
|
36871
|
+
if (!normalized) continue;
|
|
36872
|
+
if ("text" in normalized) {
|
|
36873
|
+
result.output.push({ type: "text", text: normalized.text });
|
|
36874
|
+
} else {
|
|
36875
|
+
result.output.push({
|
|
36876
|
+
type: "image",
|
|
36877
|
+
data: normalized.data,
|
|
36878
|
+
mimeType: normalized.mimeType
|
|
36879
|
+
});
|
|
36880
|
+
}
|
|
36881
|
+
const revised = asString(item.revised_prompt);
|
|
36882
|
+
if (revised && revised !== prompt) {
|
|
36883
|
+
result.output.push({ type: "text", text: `Revised prompt: ${revised}` });
|
|
36884
|
+
}
|
|
36885
|
+
}
|
|
36886
|
+
if (result.output.every((part) => part.type !== "image")) {
|
|
36887
|
+
throw new Error("The provider returned no image data.");
|
|
36888
|
+
}
|
|
36889
|
+
return result;
|
|
36890
|
+
} catch (error) {
|
|
36891
|
+
const aborted = options?.signal?.aborted || controller.signal.aborted;
|
|
36892
|
+
result.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
36893
|
+
result.errorMessage = aborted && !options?.signal?.aborted ? "Image request timed out." : annotateModelError(
|
|
36894
|
+
error instanceof Error ? error.message : String(error),
|
|
36895
|
+
model
|
|
36896
|
+
);
|
|
36897
|
+
return result;
|
|
36898
|
+
} finally {
|
|
36899
|
+
clearTimeout(timer);
|
|
36900
|
+
options?.signal?.removeEventListener("abort", onAbort);
|
|
36901
|
+
}
|
|
36902
|
+
}
|
|
36903
|
+
|
|
36904
|
+
// packages/image/src/register-providers.ts
|
|
36905
|
+
var registered = false;
|
|
36906
|
+
function groupModelsByProvider(models, baseUrlFor) {
|
|
36907
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
36908
|
+
for (const model of models) {
|
|
36909
|
+
const baseUrl = model.baseUrl ?? baseUrlFor(model.provider);
|
|
36910
|
+
if (!baseUrl) continue;
|
|
36911
|
+
let entry = grouped.get(model.provider);
|
|
36912
|
+
if (!entry) {
|
|
36913
|
+
entry = { baseUrl, models: [] };
|
|
36914
|
+
grouped.set(model.provider, entry);
|
|
36915
|
+
}
|
|
36916
|
+
entry.models.push({ ...model, baseUrl });
|
|
36917
|
+
}
|
|
36918
|
+
return grouped;
|
|
36919
|
+
}
|
|
36920
|
+
function providerBaseUrlLookup(registry3) {
|
|
36921
|
+
const cache = /* @__PURE__ */ new Map();
|
|
36922
|
+
return (provider) => {
|
|
36923
|
+
if (cache.has(provider)) return cache.get(provider);
|
|
36924
|
+
let baseUrl;
|
|
36925
|
+
try {
|
|
36926
|
+
const models = registry3.getAvailable?.() ?? registry3.getAll();
|
|
36927
|
+
baseUrl = models.find((m) => m?.provider === provider && m.baseUrl)?.baseUrl;
|
|
36928
|
+
} catch {
|
|
36929
|
+
baseUrl = void 0;
|
|
36930
|
+
}
|
|
36931
|
+
cache.set(provider, baseUrl);
|
|
36932
|
+
return baseUrl;
|
|
36933
|
+
};
|
|
36934
|
+
}
|
|
36935
|
+
async function registerRegistryImageProviders(registry3, options) {
|
|
36936
|
+
if (!registry3) return [];
|
|
36937
|
+
if (registered && !options?.force) return [];
|
|
36938
|
+
const images = await getImagesModels();
|
|
36939
|
+
if (!images) return [];
|
|
36940
|
+
const mutable = images;
|
|
36941
|
+
if (typeof mutable.setProvider !== "function") return [];
|
|
36942
|
+
let createImagesProvider;
|
|
36943
|
+
try {
|
|
36944
|
+
const mod = await import("@earendil-works/pi-ai");
|
|
36945
|
+
if (typeof mod.createImagesProvider !== "function") return [];
|
|
36946
|
+
createImagesProvider = mod.createImagesProvider;
|
|
36947
|
+
} catch {
|
|
36948
|
+
return [];
|
|
36949
|
+
}
|
|
36950
|
+
const discovered = listRegistryImageGenModels(registry3);
|
|
36951
|
+
if (discovered.length === 0) {
|
|
36952
|
+
registered = true;
|
|
36953
|
+
return [];
|
|
36954
|
+
}
|
|
36955
|
+
const grouped = groupModelsByProvider(discovered, providerBaseUrlLookup(registry3));
|
|
36956
|
+
const added = [];
|
|
36957
|
+
for (const [providerId, { models }] of grouped) {
|
|
36958
|
+
try {
|
|
36959
|
+
if (mutable.getProvider?.(providerId)) continue;
|
|
36960
|
+
} catch {
|
|
36961
|
+
}
|
|
36962
|
+
try {
|
|
36963
|
+
const provider = createImagesProvider({
|
|
36964
|
+
id: providerId,
|
|
36965
|
+
name: providerId,
|
|
36966
|
+
models,
|
|
36967
|
+
api: openai_images_api_exports,
|
|
36968
|
+
auth: {
|
|
36969
|
+
apiKey: {
|
|
36970
|
+
name: `${providerId} API key`,
|
|
36971
|
+
// Resolve through pi's own auth storage so the user never logs in
|
|
36972
|
+
// twice. `resolve` MUST return an AuthResult (`{ auth: {...} }`);
|
|
36973
|
+
// returning a bare key fails silently at request time.
|
|
36974
|
+
resolve: async () => {
|
|
36975
|
+
const key = await resolveProviderKey(registry3, providerId);
|
|
36976
|
+
return key ? { auth: { apiKey: key }, source: `pi:${providerId}` } : void 0;
|
|
36977
|
+
}
|
|
36978
|
+
}
|
|
36979
|
+
}
|
|
36980
|
+
});
|
|
36981
|
+
mutable.setProvider(provider);
|
|
36982
|
+
added.push(providerId);
|
|
36983
|
+
} catch {
|
|
36984
|
+
}
|
|
36985
|
+
}
|
|
36986
|
+
registered = true;
|
|
36987
|
+
return added;
|
|
36988
|
+
}
|
|
36989
|
+
async function resolveProviderKey(registry3, provider) {
|
|
36990
|
+
try {
|
|
36991
|
+
const key = await registry3.getApiKeyForProvider?.(provider);
|
|
36992
|
+
if (key) return key;
|
|
36993
|
+
} catch {
|
|
36994
|
+
}
|
|
36995
|
+
const envName = `${provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`;
|
|
36996
|
+
return process.env[envName] || void 0;
|
|
36997
|
+
}
|
|
36998
|
+
|
|
36748
36999
|
// packages/image/src/tui/settings-dialog.ts
|
|
36749
37000
|
var EXIT = "__exit__";
|
|
36750
37001
|
function describe(config) {
|
|
@@ -36911,6 +37162,7 @@ async function pickModel(ctx, kind, config) {
|
|
|
36911
37162
|
async function collectModels(ctx, kind) {
|
|
36912
37163
|
const registry3 = ctx.modelRegistry;
|
|
36913
37164
|
if (kind === "generate") {
|
|
37165
|
+
await registerRegistryImageProviders(registry3);
|
|
36914
37166
|
const models = await listAllImageGenModels(registry3);
|
|
36915
37167
|
const generating = await getGeneratingProviders();
|
|
36916
37168
|
return models.map((m) => ({
|
|
@@ -36920,7 +37172,7 @@ async function collectModels(ctx, kind) {
|
|
|
36920
37172
|
// pi-ai's images collection has its own provider set. A chat provider's
|
|
36921
37173
|
// image models are listed for reference but cannot actually generate,
|
|
36922
37174
|
// so flag them rather than letting the user pick a dead option.
|
|
36923
|
-
unavailable: generating.length > 0 && !generating.includes(m.provider) ? "
|
|
37175
|
+
unavailable: generating.length > 0 && !generating.includes(m.provider) ? "no image route" : void 0
|
|
36924
37176
|
}));
|
|
36925
37177
|
}
|
|
36926
37178
|
if (!registry3) return [];
|
|
@@ -36996,7 +37248,7 @@ function providerCanGenerate(imagesApi, provider) {
|
|
|
36996
37248
|
return providers.includes(provider);
|
|
36997
37249
|
}
|
|
36998
37250
|
async function generateImage(options) {
|
|
36999
|
-
const { prompt, model, signal, outputDir, now: now2 } = options;
|
|
37251
|
+
const { prompt, model, signal, outputDir, now: now2, inputImage } = options;
|
|
37000
37252
|
if (!prompt.trim()) {
|
|
37001
37253
|
throw new Error("A non-empty prompt is required.");
|
|
37002
37254
|
}
|
|
@@ -37009,28 +37261,37 @@ async function generateImage(options) {
|
|
|
37009
37261
|
if (!providerCanGenerate(imagesApi, model.provider)) {
|
|
37010
37262
|
const supported = supportedProviders(imagesApi);
|
|
37011
37263
|
throw new Error(
|
|
37012
|
-
`Provider "${model.provider}"
|
|
37013
|
-
\u2192
|
|
37014
|
-
\u2192
|
|
37015
|
-
\u2192 Pick
|
|
37264
|
+
`Provider "${model.provider}" has no image-generation route.
|
|
37265
|
+
\u2192 Available: ${supported.join(", ") || "openrouter"}.
|
|
37266
|
+
\u2192 Providers are bridged from pi automatically; one without a baseUrl or an API key cannot be used.
|
|
37267
|
+
\u2192 Pick another with /unipi:image-settings.`
|
|
37016
37268
|
);
|
|
37017
37269
|
}
|
|
37018
37270
|
let apiKey;
|
|
37019
37271
|
try {
|
|
37020
|
-
|
|
37272
|
+
const resolvedAuth = await imagesApi.getAuth(model);
|
|
37273
|
+
apiKey = resolvedAuth?.auth?.apiKey ?? resolvedAuth?.apiKey;
|
|
37021
37274
|
} catch {
|
|
37022
37275
|
}
|
|
37023
37276
|
apiKey ||= options.apiKey;
|
|
37024
37277
|
if (!apiKey) {
|
|
37025
37278
|
throw new Error(
|
|
37026
37279
|
`No API key for provider "${model.provider}".
|
|
37027
|
-
\u2192
|
|
37028
|
-
\u2192
|
|
37280
|
+
\u2192 Sign in with /login, or set the provider's API key environment variable.
|
|
37281
|
+
\u2192 Expected environment variable: ${model.provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`
|
|
37029
37282
|
);
|
|
37030
37283
|
}
|
|
37284
|
+
const input = [{ type: "text", text: prompt }];
|
|
37285
|
+
if (inputImage) {
|
|
37286
|
+
input.push({
|
|
37287
|
+
type: "image",
|
|
37288
|
+
data: inputImage.data,
|
|
37289
|
+
mimeType: inputImage.mimeType
|
|
37290
|
+
});
|
|
37291
|
+
}
|
|
37031
37292
|
const result = await imagesApi.generateImages(
|
|
37032
37293
|
model,
|
|
37033
|
-
{ input
|
|
37294
|
+
{ input },
|
|
37034
37295
|
{ apiKey, ...signal ? { signal } : {} }
|
|
37035
37296
|
);
|
|
37036
37297
|
if (result.stopReason === "error") {
|
|
@@ -37194,7 +37455,7 @@ function truncate(value, max = 60) {
|
|
|
37194
37455
|
}
|
|
37195
37456
|
|
|
37196
37457
|
// packages/image/src/recognize.ts
|
|
37197
|
-
var
|
|
37458
|
+
var DEFAULT_TIMEOUT_MS3 = 12e4;
|
|
37198
37459
|
var DEFAULT_MAX_TOKENS = 2048;
|
|
37199
37460
|
function createSignal(timeoutMs, external) {
|
|
37200
37461
|
const controller = new AbortController();
|
|
@@ -37233,7 +37494,7 @@ async function callAnthropic2(options) {
|
|
|
37233
37494
|
apiKey,
|
|
37234
37495
|
baseUrl,
|
|
37235
37496
|
modelId,
|
|
37236
|
-
timeoutMs =
|
|
37497
|
+
timeoutMs = DEFAULT_TIMEOUT_MS3,
|
|
37237
37498
|
maxTokens = DEFAULT_MAX_TOKENS,
|
|
37238
37499
|
signal: external,
|
|
37239
37500
|
fetchImpl = fetch
|
|
@@ -37340,7 +37601,7 @@ async function callOpenAICompatible2(options) {
|
|
|
37340
37601
|
apiKey,
|
|
37341
37602
|
baseUrl,
|
|
37342
37603
|
modelId,
|
|
37343
|
-
timeoutMs =
|
|
37604
|
+
timeoutMs = DEFAULT_TIMEOUT_MS3,
|
|
37344
37605
|
maxTokens = DEFAULT_MAX_TOKENS,
|
|
37345
37606
|
signal: external,
|
|
37346
37607
|
fetchImpl = fetch
|
|
@@ -37447,11 +37708,14 @@ function registerGenerateTool(pi) {
|
|
|
37447
37708
|
pi.registerTool({
|
|
37448
37709
|
name: IMAGE_TOOLS.GENERATE,
|
|
37449
37710
|
label: "Generate Image",
|
|
37450
|
-
description: "Generate an image from a text prompt
|
|
37711
|
+
description: "Generate an image from a text prompt, or edit an existing image by passing `image`. The result is returned inline and, when enabled, saved to disk.",
|
|
37451
37712
|
promptSnippet: "Generate an image from a text prompt.",
|
|
37452
37713
|
promptGuidelines: [
|
|
37453
37714
|
"Use image_generate to create images from a text description.",
|
|
37454
37715
|
"Write a detailed prompt \u2014 subject, style, composition and lighting all help.",
|
|
37716
|
+
"Pass `image` to edit an existing image instead of generating a new one.",
|
|
37717
|
+
"Editing regenerates the whole image, so unmentioned details may change.",
|
|
37718
|
+
"Describe what you DO want; negation is unreliable in image models.",
|
|
37455
37719
|
"Omit model to use the one configured in /unipi:image-settings.",
|
|
37456
37720
|
"Generated images cost money per call; do not regenerate without being asked."
|
|
37457
37721
|
],
|
|
@@ -37459,6 +37723,11 @@ function registerGenerateTool(pi) {
|
|
|
37459
37723
|
prompt: Type9.String({
|
|
37460
37724
|
description: "Description of the image to generate. Be specific."
|
|
37461
37725
|
}),
|
|
37726
|
+
image: Type9.Optional(
|
|
37727
|
+
Type9.String({
|
|
37728
|
+
description: "Source image to edit: a local file path, data: URL, or base64 data. When set, the model edits this image instead of generating from scratch."
|
|
37729
|
+
})
|
|
37730
|
+
),
|
|
37462
37731
|
model: Type9.Optional(
|
|
37463
37732
|
Type9.String({
|
|
37464
37733
|
description: 'Image model override, e.g. "flux.2-pro" or "openrouter/google/gemini-3-pro-image". Omit to use the configured default.'
|
|
@@ -37469,21 +37738,26 @@ function registerGenerateTool(pi) {
|
|
|
37469
37738
|
try {
|
|
37470
37739
|
const config = loadConfig7();
|
|
37471
37740
|
const registry3 = getRegistry2(ctx);
|
|
37741
|
+
await registerRegistryImageProviders(registry3);
|
|
37472
37742
|
const models = await listAllImageGenModels(registry3);
|
|
37473
37743
|
const requested = params.model?.trim() || config.generate.model;
|
|
37474
|
-
const
|
|
37475
|
-
if (typeof
|
|
37744
|
+
const maybeResolved = resolveImageGenModel(requested, models);
|
|
37745
|
+
if (typeof maybeResolved === "string") return errorResult(maybeResolved);
|
|
37746
|
+
const registryBaseUrl = maybeResolved.baseUrl ? void 0 : findProviderBaseUrl(registry3, maybeResolved.provider);
|
|
37747
|
+
const resolved = registryBaseUrl ? { ...maybeResolved, baseUrl: registryBaseUrl } : maybeResolved;
|
|
37476
37748
|
const fallbackKey = await resolveApiKey(registry3, resolved.provider);
|
|
37749
|
+
const sourceImage = params.image?.trim() ? loadImage(params.image, ctx.cwd ?? process.cwd()) : void 0;
|
|
37477
37750
|
const result = await generateImage({
|
|
37478
37751
|
prompt: params.prompt,
|
|
37479
37752
|
model: resolved,
|
|
37480
37753
|
...fallbackKey ? { apiKey: fallbackKey } : {},
|
|
37754
|
+
...sourceImage ? { inputImage: sourceImage } : {},
|
|
37481
37755
|
signal,
|
|
37482
37756
|
outputDir: config.generate.saveToDisk ? getOutputDir(config) : void 0
|
|
37483
37757
|
});
|
|
37484
37758
|
const saved = result.images.map((image) => image.path).filter((path28) => Boolean(path28));
|
|
37485
37759
|
const summary = [
|
|
37486
|
-
|
|
37760
|
+
`${sourceImage ? "Edited" : "Generated"} ${result.images.length} image${result.images.length === 1 ? "" : "s"} with ${formatModelRef(resolved)}.`,
|
|
37487
37761
|
saved.length > 0 ? `Saved to:
|
|
37488
37762
|
${saved.map((p) => ` ${p}`).join("\n")}` : "",
|
|
37489
37763
|
config.generate.saveToDisk && saved.length === 0 ? "Could not write to the output directory \u2014 returning the image inline only." : "",
|
|
@@ -37625,6 +37899,9 @@ function src_default5(pi) {
|
|
|
37625
37899
|
registerImageCommands(pi);
|
|
37626
37900
|
pi.on("session_start", async (_event, ctx) => {
|
|
37627
37901
|
const config = loadConfig7();
|
|
37902
|
+
void registerRegistryImageProviders(
|
|
37903
|
+
ctx.modelRegistry
|
|
37904
|
+
).catch(() => void 0);
|
|
37628
37905
|
const tools = [];
|
|
37629
37906
|
if (config.generate.enabled) tools.push(IMAGE_TOOLS.GENERATE);
|
|
37630
37907
|
if (config.recognize.enabled) tools.push(IMAGE_TOOLS.RECOGNIZE);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/updater",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Auto-updater, changelog browser, and readme browser for Unipi — checks npm registry, renders CHANGELOG.md and README.md files in TUI overlays",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@pi-unipi/core": "2.
|
|
36
|
+
"@pi-unipi/core": "2.4.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/utility",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Utility commands and tools for Pi coding agent — lifecycle, diagnostics, cache, analytics, display, batch execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@pi-unipi/core": "2.
|
|
37
|
+
"@pi-unipi/core": "2.4.0",
|
|
38
38
|
"diff": "^7.0.0",
|
|
39
39
|
"@shikijs/cli": "^4.0.2"
|
|
40
40
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/web-api",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Web search, read, and summarize tools with provider-based backend selection for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"README.md"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@pi-unipi/core": "2.
|
|
34
|
+
"@pi-unipi/core": "2.4.0",
|
|
35
35
|
"defuddle": "^0.18.1",
|
|
36
36
|
"linkedom": "^0.18.12",
|
|
37
37
|
"lodash": "^4.17.21",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/workflow",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Structured development workflow commands for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@pi-unipi/core": "2.
|
|
29
|
+
"@pi-unipi/core": "2.4.0"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"@earendil-works/pi-ai": "^0.80.0",
|