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