free-pi-cli 0.4.7 → 0.4.9
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/dist/index.js +1 -1
- package/dist/{main-ep5298x9.js → main-jaz36mav.js} +257 -41
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/main.ts
|
|
4
4
|
import { spawn as spawn2 } from "node:child_process";
|
|
5
5
|
import { realpathSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
6
|
-
import { dirname as dirname5, join as
|
|
6
|
+
import { dirname as dirname5, join as join7 } from "node:path";
|
|
7
7
|
|
|
8
8
|
// src/browser.ts
|
|
9
9
|
import { spawn } from "node:child_process";
|
|
@@ -46,7 +46,7 @@ function openBrowser(url, platform = process.platform, spawnImpl = spawn) {
|
|
|
46
46
|
// package.json
|
|
47
47
|
var package_default = {
|
|
48
48
|
name: "free-pi-cli",
|
|
49
|
-
version: "0.4.
|
|
49
|
+
version: "0.4.9",
|
|
50
50
|
description: "Free, ad-supported distribution of the pi coding agent. Inference paid for by ads.",
|
|
51
51
|
license: "MIT",
|
|
52
52
|
type: "module",
|
|
@@ -4906,7 +4906,8 @@ var ClientVersionResponseSchema = object({
|
|
|
4906
4906
|
latest: string2(),
|
|
4907
4907
|
model: string2().optional(),
|
|
4908
4908
|
notice: string2().optional(),
|
|
4909
|
-
models: array(object({ id: string2(), name: string2() })).optional()
|
|
4909
|
+
models: array(object({ id: string2(), name: string2() })).optional(),
|
|
4910
|
+
fusion: object({ planner: string2(), executor: string2() }).optional()
|
|
4910
4911
|
});
|
|
4911
4912
|
var SessionResetResponseSchema = object({
|
|
4912
4913
|
released: boolean2()
|
|
@@ -4970,6 +4971,7 @@ var ConfigSchema = object({
|
|
|
4970
4971
|
abuseRestrictedCapUsd: number2().default(0.5),
|
|
4971
4972
|
abuseRestrictedHourlyCap: number2().int().default(5),
|
|
4972
4973
|
abuseNormalHourlyCap: number2().int().default(30),
|
|
4974
|
+
abuseSignedInNormalHourlyCap: number2().int().default(0),
|
|
4973
4975
|
abuseFlipAlertThreshold: number2().int().default(10),
|
|
4974
4976
|
abuseAdBypassMinRequests: number2().int().default(20),
|
|
4975
4977
|
abuseAdBypassRatio: number2().int().default(10),
|
|
@@ -5042,6 +5044,7 @@ function loadConfig(env = process.env) {
|
|
|
5042
5044
|
abuseRestrictedCapUsd: envNumber(env.ABUSE_RESTRICTED_CAP_USD),
|
|
5043
5045
|
abuseRestrictedHourlyCap: envNumber(env.ABUSE_RESTRICTED_HOURLY_CAP),
|
|
5044
5046
|
abuseNormalHourlyCap: envNumber(env.ABUSE_NORMAL_HOURLY_CAP),
|
|
5047
|
+
abuseSignedInNormalHourlyCap: envNumber(env.ABUSE_SIGNED_IN_NORMAL_HOURLY_CAP),
|
|
5045
5048
|
abuseFlipAlertThreshold: envNumber(env.ABUSE_FLIP_ALERT_THRESHOLD),
|
|
5046
5049
|
abuseAdBypassMinRequests: envNumber(env.ABUSE_AD_BYPASS_MIN_REQUESTS),
|
|
5047
5050
|
abuseAdBypassRatio: envNumber(env.ABUSE_AD_BYPASS_RATIO),
|
|
@@ -5086,6 +5089,42 @@ function formatCreditsLeftLine(remaining, allowance, refillAt) {
|
|
|
5086
5089
|
return `${left} / ${total} credits left today · refills ${formatRefillEt(refillAt)}`;
|
|
5087
5090
|
}
|
|
5088
5091
|
var SLOW_LANE_METER_LINE = "Slow lane: cheapest model until refill";
|
|
5092
|
+
var LIMIT_NOTICE_LABELS = {
|
|
5093
|
+
hourly_ceiling: "hourly limit",
|
|
5094
|
+
daily_cap: "daily limit",
|
|
5095
|
+
rolling_cap: "rolling limit",
|
|
5096
|
+
global_cap: "service daily limit",
|
|
5097
|
+
queue_wait: "queue wait",
|
|
5098
|
+
concurrent: "one request at a time",
|
|
5099
|
+
concurrent_session: "another session is active"
|
|
5100
|
+
};
|
|
5101
|
+
var EXTRA_CREDITS_URL = "freepi.ai/extra-credits";
|
|
5102
|
+
var LIMIT_NOTICE_LOCAL_TIME_AFTER_SECONDS = 90 * 60;
|
|
5103
|
+
function formatLimitNotice(fields, opts = {}) {
|
|
5104
|
+
const slow = fields.lane === "slow" && fields.limit === "hourly_ceiling" && fields.hourly_ceiling != null;
|
|
5105
|
+
const label = slow ? `You've used today's free allowance, so you're on the slow lane (${fields.hourly_ceiling.toLocaleString("en-US")} requests an hour).` : LIMIT_NOTICE_LABELS[fields.limit] ?? fields.limit.replace(/_/g, " ");
|
|
5106
|
+
const parts = [label];
|
|
5107
|
+
const retry = fields.retry_after_seconds;
|
|
5108
|
+
if (retry != null && retry > 0) {
|
|
5109
|
+
const useClock = Boolean(opts.localTime) && Boolean(fields.clears_at) && retry >= LIMIT_NOTICE_LOCAL_TIME_AFTER_SECONDS;
|
|
5110
|
+
if (useClock) {
|
|
5111
|
+
const formatted = new Intl.DateTimeFormat("en-US", {
|
|
5112
|
+
timeZone: opts.timeZone,
|
|
5113
|
+
hour: "numeric",
|
|
5114
|
+
minute: "2-digit"
|
|
5115
|
+
}).format(new Date(fields.clears_at));
|
|
5116
|
+
parts.push(`back at ${formatted}`);
|
|
5117
|
+
} else {
|
|
5118
|
+
parts.push(`back in ${Math.max(1, Math.ceil(retry / 60))} min`);
|
|
5119
|
+
}
|
|
5120
|
+
}
|
|
5121
|
+
if (slow) {
|
|
5122
|
+
const wait = parts.length > 1 ? ` ${parts[1][0].toUpperCase()}${parts[1].slice(1)} — or get` : " Get";
|
|
5123
|
+
return `${label}${wait} credits at ${EXTRA_CREDITS_URL} to keep going at full speed.`;
|
|
5124
|
+
}
|
|
5125
|
+
parts.push(`${fields.remaining_credits_today.toLocaleString("en-US")} credits left today`);
|
|
5126
|
+
return parts.join(", ");
|
|
5127
|
+
}
|
|
5089
5128
|
// src/gravity-pixel.ts
|
|
5090
5129
|
var GRAVITY_PIXEL_ID = "6cd74804-0541-4d9b-899d-d7fceb465b6f";
|
|
5091
5130
|
function userIdFromJwt(jwt) {
|
|
@@ -8129,7 +8168,7 @@ function createAdsExtension(opts) {
|
|
|
8129
8168
|
}
|
|
8130
8169
|
|
|
8131
8170
|
// src/pi-launch.ts
|
|
8132
|
-
import { dirname as dirname3, join as
|
|
8171
|
+
import { dirname as dirname3, join as join5, resolve as resolve2 } from "node:path";
|
|
8133
8172
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
8134
8173
|
|
|
8135
8174
|
// src/provider.ts
|
|
@@ -8545,6 +8584,104 @@ function createUsageToolExtension(opts) {
|
|
|
8545
8584
|
};
|
|
8546
8585
|
}
|
|
8547
8586
|
|
|
8587
|
+
// src/image-tool.ts
|
|
8588
|
+
import { randomBytes } from "node:crypto";
|
|
8589
|
+
import { mkdir as mkdir2, open } from "node:fs/promises";
|
|
8590
|
+
import { join as join3 } from "node:path";
|
|
8591
|
+
var IMAGE_TOOL_NAME = "generate_image";
|
|
8592
|
+
var MAX_BYTES = 8 * 1024 * 1024;
|
|
8593
|
+
var TIMEOUT_MS = 120000;
|
|
8594
|
+
var EXT = { "image/png": "png", "image/jpeg": "jpg", "image/webp": "webp" };
|
|
8595
|
+
var IMAGE_GUIDELINES = [
|
|
8596
|
+
"Use generate_image only when the user asks for an image, mockup, icon, illustration or other visual asset. Do not call it for diagrams you can write as code, SVG or ASCII.",
|
|
8597
|
+
"Make one call per asset. Put the style, any exact text to render, and the composition in the prompt, and pick aspect_ratio to suit the use (16:9 for banners, 1:1 for icons).",
|
|
8598
|
+
"You cannot see the generated image. The tool saves it to a file and returns the relative path; refer to that path in code, docs or your answer, and tell the user you could not check how it looks.",
|
|
8599
|
+
"Each image uses part of the user's daily allowance, so do not regenerate unless the user asks."
|
|
8600
|
+
];
|
|
8601
|
+
var ERROR_TEXT = {
|
|
8602
|
+
daily_cap: "The user's free allowance for today is used up. Do not retry. Tell the user they can get credits at freepi.ai/extra-credits, and continue without this tool.",
|
|
8603
|
+
rolling_cap: "The user's short-term usage limit is reached. Do not retry; continue without this tool.",
|
|
8604
|
+
rate_limited: "Too many image requests right now. Do not call generate_image again in this turn unless the user asks.",
|
|
8605
|
+
not_configured: "Image generation is not available on this server. Do not call generate_image again in this session.",
|
|
8606
|
+
upstream_error: "The image provider failed. You may retry once.",
|
|
8607
|
+
account_review: "This account is under review, so image generation is disabled. Do not retry; tell the user and continue without it.",
|
|
8608
|
+
content_rejected: "The image provider rejected this prompt. Do not retry the same prompt; tell the user and offer to reword it."
|
|
8609
|
+
};
|
|
8610
|
+
async function post(opts, body, signal) {
|
|
8611
|
+
try {
|
|
8612
|
+
const timeout = AbortSignal.timeout(TIMEOUT_MS);
|
|
8613
|
+
const res = await (opts.fetchImpl ?? fetch)(`${opts.baseUrl}/tools/image`, {
|
|
8614
|
+
method: "POST",
|
|
8615
|
+
headers: { Authorization: `Bearer ${await opts.getToken()}`, "Content-Type": "application/json" },
|
|
8616
|
+
body: JSON.stringify(body),
|
|
8617
|
+
signal: signal ? AbortSignal.any([signal, timeout]) : timeout
|
|
8618
|
+
});
|
|
8619
|
+
if (res.ok)
|
|
8620
|
+
return { ok: true, data: await res.json() };
|
|
8621
|
+
const j = await res.json().catch(() => ({}));
|
|
8622
|
+
const code = j.error?.code ?? j.code ?? "";
|
|
8623
|
+
const mapped = ERROR_TEXT[code];
|
|
8624
|
+
if (mapped)
|
|
8625
|
+
return { ok: false, text: mapped };
|
|
8626
|
+
return { ok: false, text: `Request failed (HTTP ${res.status}): ${j.error?.message ?? j.message ?? "no message"}` };
|
|
8627
|
+
} catch (e) {
|
|
8628
|
+
if (e instanceof Error && e.name === "TimeoutError")
|
|
8629
|
+
return { ok: false, text: "Image generation timed out after 120 seconds." };
|
|
8630
|
+
return { ok: false, text: `Request failed: ${String(e)}` };
|
|
8631
|
+
}
|
|
8632
|
+
}
|
|
8633
|
+
function slug(s) {
|
|
8634
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+/, "").slice(0, 40).replace(/-+$/, "") || "image";
|
|
8635
|
+
}
|
|
8636
|
+
var done = (text, details) => ({ content: [{ type: "text", text }], details });
|
|
8637
|
+
var fail = (text) => done(text, { ok: false });
|
|
8638
|
+
function createImageToolExtension(opts) {
|
|
8639
|
+
return {
|
|
8640
|
+
name: "free-pi-image-tool",
|
|
8641
|
+
factory(pi) {
|
|
8642
|
+
pi.registerTool({
|
|
8643
|
+
name: IMAGE_TOOL_NAME,
|
|
8644
|
+
label: "Generate Image",
|
|
8645
|
+
description: "Generate one image from a text prompt and save it as a file under images/ in the project. Returns the file path; you cannot see the image. Use it only when the user asks for an image, mockup, icon or illustration. Each call uses part of the user's daily allowance.",
|
|
8646
|
+
promptSnippet: "Generate an image file from a prompt when the user asks for one (counts against the user's daily allowance)",
|
|
8647
|
+
promptGuidelines: IMAGE_GUIDELINES,
|
|
8648
|
+
parameters: _Object_({
|
|
8649
|
+
prompt: String2({ minLength: 1, maxLength: 1000, description: "What to draw: subject, style, any exact text to render, and composition" }),
|
|
8650
|
+
aspect_ratio: Optional(Union([Literal("1:1"), Literal("16:9"), Literal("9:16"), Literal("4:3"), Literal("3:2")], { description: "Default 1:1" })),
|
|
8651
|
+
name: Optional(String2({ maxLength: 60, description: "Short file name hint, e.g. hero-banner; used only for the saved file's name" }))
|
|
8652
|
+
}),
|
|
8653
|
+
async execute(_id, params, signal) {
|
|
8654
|
+
const r = await post(opts, { prompt: params.prompt, ...params.aspect_ratio ? { aspect_ratio: params.aspect_ratio } : {} }, signal);
|
|
8655
|
+
if (!r.ok)
|
|
8656
|
+
return fail(r.text);
|
|
8657
|
+
const d = r.data;
|
|
8658
|
+
const ext = EXT[d.media_type];
|
|
8659
|
+
if (!ext)
|
|
8660
|
+
return fail(`The server returned an unsupported image type (${d.media_type}). Nothing was saved.`);
|
|
8661
|
+
const buf = Buffer.from(d.image_b64 ?? "", "base64");
|
|
8662
|
+
if (buf.length === 0)
|
|
8663
|
+
return fail("The server returned an empty image. Nothing was saved.");
|
|
8664
|
+
if (buf.length > MAX_BYTES)
|
|
8665
|
+
return fail("The generated image was too large to save (over 8 MB). Nothing was saved.");
|
|
8666
|
+
const rel = `images/${slug(params.name ?? params.prompt)}-${randomBytes(3).toString("hex")}.${ext}`;
|
|
8667
|
+
try {
|
|
8668
|
+
await mkdir2(join3(opts.cwd ?? process.cwd(), "images"), { recursive: true });
|
|
8669
|
+
const fh = await open(join3(opts.cwd ?? process.cwd(), rel), "wx");
|
|
8670
|
+
try {
|
|
8671
|
+
await fh.writeFile(buf);
|
|
8672
|
+
} finally {
|
|
8673
|
+
await fh.close();
|
|
8674
|
+
}
|
|
8675
|
+
} catch (e) {
|
|
8676
|
+
return fail(`Could not save the image: ${String(e)}`);
|
|
8677
|
+
}
|
|
8678
|
+
return done(`Saved image to ${rel} (${ext}, ${buf.length} bytes). You cannot see the image; reference this path in your answer or code, and tell the user you could not check how it looks.`, { ok: true, path: rel, cost_usd: d.cost_usd, model: d.model });
|
|
8679
|
+
}
|
|
8680
|
+
});
|
|
8681
|
+
}
|
|
8682
|
+
};
|
|
8683
|
+
}
|
|
8684
|
+
|
|
8548
8685
|
// src/web-tools.ts
|
|
8549
8686
|
var WEB_SEARCH_TOOL_NAME = "web_search";
|
|
8550
8687
|
var DOCS_LOOKUP_TOOL_NAME = "docs_lookup";
|
|
@@ -8559,7 +8696,7 @@ var WEB_SEARCH_GUIDELINES = [
|
|
|
8559
8696
|
"Write a specific query the way you would type it into a search engine (include the product name, the version and the exact error text). Read the returned page text before searching again, and do not repeat a query that already returned results. Cite the URLs you relied on in your answer.",
|
|
8560
8697
|
COST_GUIDELINE
|
|
8561
8698
|
];
|
|
8562
|
-
var
|
|
8699
|
+
var ERROR_TEXT2 = {
|
|
8563
8700
|
daily_cap: "The user's daily free usage is used up. Do not retry; tell the user and continue without this tool.",
|
|
8564
8701
|
rolling_cap: "The user's short-term usage limit is reached. Do not retry; continue without this tool.",
|
|
8565
8702
|
rate_limited: "Too many calls right now. Wait for the current call to finish, and do not call again in this turn unless needed.",
|
|
@@ -8567,7 +8704,7 @@ var ERROR_TEXT = {
|
|
|
8567
8704
|
not_configured: "This tool is not available on this server. Do not call it again in this session.",
|
|
8568
8705
|
upstream_error: "The search provider failed. You may retry once."
|
|
8569
8706
|
};
|
|
8570
|
-
async function
|
|
8707
|
+
async function post2(opts, path, body, label, signal) {
|
|
8571
8708
|
try {
|
|
8572
8709
|
const timeout = AbortSignal.timeout(30000);
|
|
8573
8710
|
const res = await (opts.fetchImpl ?? fetch)(`${opts.baseUrl}${path}`, {
|
|
@@ -8579,7 +8716,7 @@ async function post(opts, path, body, label, signal) {
|
|
|
8579
8716
|
if (res.ok)
|
|
8580
8717
|
return { ok: true, data: await res.json() };
|
|
8581
8718
|
const err = await res.json().catch(() => ({}));
|
|
8582
|
-
const mapped =
|
|
8719
|
+
const mapped = ERROR_TEXT2[err.code ?? ""];
|
|
8583
8720
|
if (mapped)
|
|
8584
8721
|
return { ok: false, text: typeof mapped === "function" ? mapped(label) : mapped };
|
|
8585
8722
|
return { ok: false, text: `Request failed (HTTP ${res.status}): ${err.message ?? "no message"}` };
|
|
@@ -8589,7 +8726,7 @@ async function post(opts, path, body, label, signal) {
|
|
|
8589
8726
|
return { ok: false, text: `Request failed: ${String(e)}` };
|
|
8590
8727
|
}
|
|
8591
8728
|
}
|
|
8592
|
-
var
|
|
8729
|
+
var done2 = (o) => ({ content: [{ type: "text", text: o.text }], details: o.details });
|
|
8593
8730
|
function createWebToolsExtension(opts) {
|
|
8594
8731
|
return {
|
|
8595
8732
|
name: "free-pi-web-tools",
|
|
@@ -8605,9 +8742,9 @@ function createWebToolsExtension(opts) {
|
|
|
8605
8742
|
num_results: Optional(Integer({ minimum: 1, maximum: 10, description: "1-10, default 5; use 3 for a quick fact, 8-10 for a survey" }))
|
|
8606
8743
|
}),
|
|
8607
8744
|
async execute(_id, params, signal) {
|
|
8608
|
-
const r = await
|
|
8745
|
+
const r = await post2(opts, "/tools/web-search", params, "", signal);
|
|
8609
8746
|
if (!r.ok)
|
|
8610
|
-
return
|
|
8747
|
+
return done2({ text: r.text, details: { ok: false } });
|
|
8611
8748
|
const results = r.data.results ?? [];
|
|
8612
8749
|
const text = results.length ? `Web results for "${params.query}":
|
|
8613
8750
|
|
|
@@ -8617,7 +8754,7 @@ Published: ${x.published_date}` : ""}
|
|
|
8617
8754
|
${x.text}`).join(`
|
|
8618
8755
|
|
|
8619
8756
|
`) : `No results for "${params.query}". Try a broader or differently worded query.`;
|
|
8620
|
-
return
|
|
8757
|
+
return done2({ text, details: { ok: true, cost_usd: r.data.cost_usd } });
|
|
8621
8758
|
}
|
|
8622
8759
|
});
|
|
8623
8760
|
pi.registerTool({
|
|
@@ -8632,11 +8769,11 @@ ${x.text}`).join(`
|
|
|
8632
8769
|
library_id: Optional(String2({ description: "The library_id printed by an earlier docs_lookup, e.g. /vercel/next.js; skips the name search" }))
|
|
8633
8770
|
}),
|
|
8634
8771
|
async execute(_id, params, signal) {
|
|
8635
|
-
const r = await
|
|
8772
|
+
const r = await post2(opts, "/tools/docs", params, params.library, signal);
|
|
8636
8773
|
if (!r.ok)
|
|
8637
|
-
return
|
|
8774
|
+
return done2({ text: r.text, details: { ok: false } });
|
|
8638
8775
|
const d = r.data;
|
|
8639
|
-
return
|
|
8776
|
+
return done2({ text: `${d.title} (library_id: ${d.library_id})
|
|
8640
8777
|
|
|
8641
8778
|
${d.docs}`, details: { ok: true, cost_usd: d.cost_usd } });
|
|
8642
8779
|
}
|
|
@@ -9051,7 +9188,7 @@ function createTodoToolExtension(options = {}) {
|
|
|
9051
9188
|
|
|
9052
9189
|
// src/tool-aliases.ts
|
|
9053
9190
|
import { homedir as homedir2 } from "node:os";
|
|
9054
|
-
import { join as
|
|
9191
|
+
import { join as join4 } from "node:path";
|
|
9055
9192
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
9056
9193
|
import {
|
|
9057
9194
|
createBashToolDefinition,
|
|
@@ -9077,7 +9214,7 @@ function normalizeShellPath(input, platform = process.platform, home = homedir2(
|
|
|
9077
9214
|
if (p === "~")
|
|
9078
9215
|
return home;
|
|
9079
9216
|
if (p.startsWith("~/") || platform === "win32" && p.startsWith("~\\"))
|
|
9080
|
-
return
|
|
9217
|
+
return join4(home, p.slice(2));
|
|
9081
9218
|
if (/^file:\/\//.test(p))
|
|
9082
9219
|
return fileURLToPath2(p);
|
|
9083
9220
|
return p;
|
|
@@ -9109,6 +9246,12 @@ function createToolAliasesExtension() {
|
|
|
9109
9246
|
|
|
9110
9247
|
// src/error-notice.ts
|
|
9111
9248
|
var NOTICE_DEDUPE_MS = 15000;
|
|
9249
|
+
function optionalNumber(value) {
|
|
9250
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
9251
|
+
}
|
|
9252
|
+
function optionalString(value) {
|
|
9253
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
9254
|
+
}
|
|
9112
9255
|
function parseErrorEnvelope(errorMessage) {
|
|
9113
9256
|
if (typeof errorMessage !== "string")
|
|
9114
9257
|
return;
|
|
@@ -9121,14 +9264,53 @@ function parseErrorEnvelope(errorMessage) {
|
|
|
9121
9264
|
const code = body.code ?? body.error?.code;
|
|
9122
9265
|
if (typeof message !== "string" || message.trim() === "")
|
|
9123
9266
|
return;
|
|
9124
|
-
|
|
9267
|
+
const parsed = {
|
|
9268
|
+
status: Number(m[1]),
|
|
9269
|
+
code: typeof code === "string" ? code : "unknown",
|
|
9270
|
+
message
|
|
9271
|
+
};
|
|
9272
|
+
const limit = optionalString(body.limit);
|
|
9273
|
+
const retryAfter = optionalNumber(body.retry_after_seconds);
|
|
9274
|
+
const clearsAt = optionalString(body.clears_at);
|
|
9275
|
+
const remaining = optionalNumber(body.remaining_credits_today);
|
|
9276
|
+
const allowance = optionalNumber(body.allowance_credits);
|
|
9277
|
+
if (limit)
|
|
9278
|
+
parsed.limit = limit;
|
|
9279
|
+
if (retryAfter !== undefined)
|
|
9280
|
+
parsed.retry_after_seconds = retryAfter;
|
|
9281
|
+
if (clearsAt)
|
|
9282
|
+
parsed.clears_at = clearsAt;
|
|
9283
|
+
if (remaining !== undefined)
|
|
9284
|
+
parsed.remaining_credits_today = remaining;
|
|
9285
|
+
if (allowance !== undefined)
|
|
9286
|
+
parsed.allowance_credits = allowance;
|
|
9287
|
+
const ceiling = optionalNumber(body.hourly_ceiling);
|
|
9288
|
+
if (body.lane === "slow")
|
|
9289
|
+
parsed.lane = "slow";
|
|
9290
|
+
if (ceiling !== undefined)
|
|
9291
|
+
parsed.hourly_ceiling = ceiling;
|
|
9292
|
+
return parsed;
|
|
9125
9293
|
} catch {
|
|
9126
9294
|
return;
|
|
9127
9295
|
}
|
|
9128
9296
|
}
|
|
9297
|
+
function formatLimitEnvelope(env, opts) {
|
|
9298
|
+
if (env.code === "concurrent_session" || typeof env.remaining_credits_today !== "number")
|
|
9299
|
+
return env.message;
|
|
9300
|
+
return formatLimitNotice({
|
|
9301
|
+
limit: env.limit ?? env.code,
|
|
9302
|
+
retry_after_seconds: env.retry_after_seconds,
|
|
9303
|
+
clears_at: env.clears_at,
|
|
9304
|
+
remaining_credits_today: env.remaining_credits_today,
|
|
9305
|
+
allowance_credits: env.allowance_credits,
|
|
9306
|
+
lane: env.lane,
|
|
9307
|
+
hourly_ceiling: env.hourly_ceiling
|
|
9308
|
+
}, { localTime: true, timeZone: opts?.timeZone });
|
|
9309
|
+
}
|
|
9129
9310
|
function createErrorNoticeExtension(opts = {}) {
|
|
9130
9311
|
const lastAt = new Map;
|
|
9131
9312
|
const now = opts.now ?? Date.now;
|
|
9313
|
+
const timeZone = opts.timeZone;
|
|
9132
9314
|
return {
|
|
9133
9315
|
name: "free-pi-error-notice",
|
|
9134
9316
|
factory(pi) {
|
|
@@ -9143,7 +9325,7 @@ function createErrorNoticeExtension(opts = {}) {
|
|
|
9143
9325
|
if (t - (lastAt.get(env.code) ?? -Infinity) < NOTICE_DEDUPE_MS)
|
|
9144
9326
|
return;
|
|
9145
9327
|
lastAt.set(env.code, t);
|
|
9146
|
-
ctx.ui.notify(env
|
|
9328
|
+
ctx.ui.notify(formatLimitEnvelope(env, { timeZone }), env.status >= 500 ? "error" : "warning");
|
|
9147
9329
|
});
|
|
9148
9330
|
}
|
|
9149
9331
|
};
|
|
@@ -9277,6 +9459,19 @@ function installFooterRightAlign(mode) {
|
|
|
9277
9459
|
|
|
9278
9460
|
// src/changelog.ts
|
|
9279
9461
|
var CHANGELOG_HIGHLIGHTS = [
|
|
9462
|
+
{
|
|
9463
|
+
version: "0.4.9",
|
|
9464
|
+
highlights: [
|
|
9465
|
+
"\uD83E\uDDE8 Fusion is now much cheaper: DeepSeek V4 Pro plans and DeepSeek V4 Flash builds, so each Fusion turn uses a small fraction of the allowance it used to. Fusion is also listed first in /model."
|
|
9466
|
+
]
|
|
9467
|
+
},
|
|
9468
|
+
{
|
|
9469
|
+
version: "0.4.8",
|
|
9470
|
+
highlights: [
|
|
9471
|
+
"The agent can now make images: ask for a mockup, icon or illustration and generate_image saves it into your project's images/ folder. Each image counts toward your daily allowance.",
|
|
9472
|
+
"Clearer limit messages: you now see when a limit clears and how many credits you have left today."
|
|
9473
|
+
]
|
|
9474
|
+
},
|
|
9280
9475
|
{
|
|
9281
9476
|
version: "0.4.7",
|
|
9282
9477
|
highlights: [
|
|
@@ -9708,9 +9903,18 @@ function createHeaderExtension(opts) {
|
|
|
9708
9903
|
|
|
9709
9904
|
// src/fusion.ts
|
|
9710
9905
|
var FUSION_ID = "fusion";
|
|
9711
|
-
var
|
|
9712
|
-
var
|
|
9906
|
+
var FUSION_PICKER_ID = "\uD83E\uDDE8 fusion";
|
|
9907
|
+
var FUSION_NAME = "\uD83E\uDDE8 Fusion · DeepSeek Pro plans, Flash builds";
|
|
9908
|
+
var FUSION_FALLBACK_NAME = "\uD83E\uDDE8 Fusion · Argon plans, DeepSeek builds";
|
|
9909
|
+
var FUSION_PLANNER_ID = "deepseek-v4-pro";
|
|
9713
9910
|
var FUSION_EXECUTOR_ID = "deepseek-v4-flash";
|
|
9911
|
+
var FUSION_FALLBACK_PLANNER_ID = "gemini-4-argon-flex";
|
|
9912
|
+
function resolveFusionIds(catalogIds, server) {
|
|
9913
|
+
const has = (id) => catalogIds.includes(id);
|
|
9914
|
+
if (server && has(server.planner) && has(server.executor))
|
|
9915
|
+
return server;
|
|
9916
|
+
return { planner: has(FUSION_PLANNER_ID) ? FUSION_PLANNER_ID : FUSION_FALLBACK_PLANNER_ID, executor: FUSION_EXECUTOR_ID };
|
|
9917
|
+
}
|
|
9714
9918
|
function editedThisTurn(messages) {
|
|
9715
9919
|
for (let i = messages.length - 1;i >= 0; i--) {
|
|
9716
9920
|
const message = messages[i];
|
|
@@ -9755,7 +9959,7 @@ function routeFusion(request, models) {
|
|
|
9755
9959
|
if (restartPlan)
|
|
9756
9960
|
return route(models.planner, "plan");
|
|
9757
9961
|
const failed = request.failed;
|
|
9758
|
-
if (!failed || failed.model.provider === PROVIDER_NAME && failed.model.id ===
|
|
9962
|
+
if (!failed || failed.model.provider === PROVIDER_NAME && failed.model.id === models.planner?.id) {
|
|
9759
9963
|
return route(models.executor, "build");
|
|
9760
9964
|
}
|
|
9761
9965
|
return { ...route(failed.model), thinkingLevel: failed.thinkingLevel ?? request.thinkingLevel };
|
|
@@ -9764,21 +9968,24 @@ function routeFusion(request, models) {
|
|
|
9764
9968
|
return phase === "build" || editedThisTurn(request.messages) ? route(models.executor, "build") : route(models.planner, "plan");
|
|
9765
9969
|
}
|
|
9766
9970
|
}
|
|
9767
|
-
function createFusionExtension(defaultModelId) {
|
|
9971
|
+
function createFusionExtension(defaultModelId, ids = resolveFusionIds([])) {
|
|
9972
|
+
const name = ids.planner === FUSION_PLANNER_ID && ids.executor === FUSION_EXECUTOR_ID ? FUSION_NAME : ids.planner === FUSION_FALLBACK_PLANNER_ID ? FUSION_FALLBACK_NAME : "\uD83E\uDDE8 Fusion";
|
|
9768
9973
|
return {
|
|
9769
9974
|
name: "free-pi-fusion",
|
|
9770
9975
|
factory: (pi) => {
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9976
|
+
for (const id of [FUSION_PICKER_ID, FUSION_ID]) {
|
|
9977
|
+
pi.registerVirtualModel({
|
|
9978
|
+
provider: PROVIDER_NAME,
|
|
9979
|
+
id,
|
|
9980
|
+
name,
|
|
9981
|
+
input: ["text"],
|
|
9982
|
+
route: (request, ctx) => routeFusion(request, {
|
|
9983
|
+
planner: ctx.modelRegistry.find(PROVIDER_NAME, ids.planner),
|
|
9984
|
+
executor: ctx.modelRegistry.find(PROVIDER_NAME, ids.executor),
|
|
9985
|
+
defaultModel: ctx.modelRegistry.find(PROVIDER_NAME, defaultModelId)
|
|
9986
|
+
})
|
|
9987
|
+
});
|
|
9988
|
+
}
|
|
9782
9989
|
}
|
|
9783
9990
|
};
|
|
9784
9991
|
}
|
|
@@ -9788,7 +9995,7 @@ function resolveFreePiScope(modelRuntime, providerName, modelIds, env = process.
|
|
|
9788
9995
|
if (env.FREEPI_KEEP_ENV_PROVIDERS)
|
|
9789
9996
|
return [];
|
|
9790
9997
|
const scoped = [];
|
|
9791
|
-
for (const id of new Set([...modelIds
|
|
9998
|
+
for (const id of new Set([FUSION_PICKER_ID, ...modelIds])) {
|
|
9792
9999
|
const model = modelRuntime.getModel(providerName, id);
|
|
9793
10000
|
if (model)
|
|
9794
10001
|
scoped.push({ model });
|
|
@@ -9872,6 +10079,7 @@ var ALLOWED_TOOL_NAMES = [
|
|
|
9872
10079
|
DOCS_TOOL_NAME,
|
|
9873
10080
|
WEB_SEARCH_TOOL_NAME,
|
|
9874
10081
|
DOCS_LOOKUP_TOOL_NAME,
|
|
10082
|
+
IMAGE_TOOL_NAME,
|
|
9875
10083
|
TODO_TOOL_NAME,
|
|
9876
10084
|
"codemode",
|
|
9877
10085
|
"tool_search"
|
|
@@ -9934,6 +10142,10 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
9934
10142
|
baseUrl: opts.baseUrl,
|
|
9935
10143
|
getToken: () => opts.jwt
|
|
9936
10144
|
});
|
|
10145
|
+
const imageToolExtension = createImageToolExtension({
|
|
10146
|
+
baseUrl: opts.baseUrl,
|
|
10147
|
+
getToken: () => opts.jwt
|
|
10148
|
+
});
|
|
9937
10149
|
const errorNoticeExtension = createErrorNoticeExtension();
|
|
9938
10150
|
const toolGuardExtension = createToolGuardExtension({
|
|
9939
10151
|
baseUrl: opts.baseUrl,
|
|
@@ -9956,12 +10168,13 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
9956
10168
|
const toolSearchExtension = { name: "pi-tool-search", factory: createToolSearchExtension() };
|
|
9957
10169
|
const extensionFactories = [
|
|
9958
10170
|
providerExtension,
|
|
9959
|
-
createFusionExtension(models[0].id),
|
|
10171
|
+
createFusionExtension(models[0].id, resolveFusionIds(models.map((m) => m.id), opts.fusion)),
|
|
9960
10172
|
adsExtension,
|
|
9961
10173
|
usageToolExtension,
|
|
9962
10174
|
buyToolExtension,
|
|
9963
10175
|
docsToolExtension,
|
|
9964
10176
|
webToolsExtension,
|
|
10177
|
+
imageToolExtension,
|
|
9965
10178
|
errorNoticeExtension,
|
|
9966
10179
|
toolGuardExtension,
|
|
9967
10180
|
commandsExtension,
|
|
@@ -9987,7 +10200,7 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
9987
10200
|
function getSessionDir(cwd, agentDir) {
|
|
9988
10201
|
const resolvedCwd = resolve2(cwd);
|
|
9989
10202
|
const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
9990
|
-
return
|
|
10203
|
+
return join5(resolve2(agentDir), "sessions", safePath);
|
|
9991
10204
|
}
|
|
9992
10205
|
async function createSessionManager(cwd, sessionDir, sessionId, api = SessionManager) {
|
|
9993
10206
|
if (sessionId) {
|
|
@@ -10132,7 +10345,7 @@ function createRealSpawn(io = { platform: process.platform, spawnSyncImpl: spawn
|
|
|
10132
10345
|
// src/relaunch.ts
|
|
10133
10346
|
import { mkdtempSync, existsSync as existsSync2, rmSync } from "node:fs";
|
|
10134
10347
|
import { constants as osConstants, tmpdir } from "node:os";
|
|
10135
|
-
import { join as
|
|
10348
|
+
import { join as join6 } from "node:path";
|
|
10136
10349
|
var SEMVER = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
10137
10350
|
var WIN_SAFE_ARG = /^[A-Za-z0-9._:=\/@+,-]+$/;
|
|
10138
10351
|
async function relaunchViaNpx(latest, deps) {
|
|
@@ -10145,8 +10358,8 @@ async function relaunchViaNpx(latest, deps) {
|
|
|
10145
10358
|
if (win && !deps.argv.every((a) => WIN_SAFE_ARG.test(a)))
|
|
10146
10359
|
return;
|
|
10147
10360
|
const target = latest && SEMVER.test(latest) ? latest : "latest";
|
|
10148
|
-
const readyDir = mkdtempSync(
|
|
10149
|
-
const readyFile =
|
|
10361
|
+
const readyDir = mkdtempSync(join6(tmpdir(), "free-pi-relaunch-"));
|
|
10362
|
+
const readyFile = join6(readyDir, "started");
|
|
10150
10363
|
try {
|
|
10151
10364
|
deps.log(`Updating free-pi to ${target}…`);
|
|
10152
10365
|
return await new Promise((resolve) => {
|
|
@@ -10444,6 +10657,7 @@ async function run(deps, argv = process.argv) {
|
|
|
10444
10657
|
session: sessionArg.session,
|
|
10445
10658
|
model: versionCheck.model,
|
|
10446
10659
|
models: versionCheck.models,
|
|
10660
|
+
fusion: versionCheck.fusion,
|
|
10447
10661
|
updateLatest,
|
|
10448
10662
|
getGravityContext
|
|
10449
10663
|
});
|
|
@@ -10500,7 +10714,7 @@ async function checkClientVersion(baseUrl, cliVersion, fetchImpl = fetch) {
|
|
|
10500
10714
|
const parsed = ClientVersionResponseSchema.safeParse(await res.json());
|
|
10501
10715
|
if (!parsed.success)
|
|
10502
10716
|
return { action: "ok" };
|
|
10503
|
-
const { min, latest, model, models, notice } = parsed.data;
|
|
10717
|
+
const { min, latest, model, models, notice, fusion } = parsed.data;
|
|
10504
10718
|
const extra = {};
|
|
10505
10719
|
if (model !== undefined)
|
|
10506
10720
|
extra.model = model;
|
|
@@ -10508,6 +10722,8 @@ async function checkClientVersion(baseUrl, cliVersion, fetchImpl = fetch) {
|
|
|
10508
10722
|
extra.models = models;
|
|
10509
10723
|
if (notice !== undefined)
|
|
10510
10724
|
extra.notice = notice;
|
|
10725
|
+
if (fusion !== undefined)
|
|
10726
|
+
extra.fusion = fusion;
|
|
10511
10727
|
if (compareVersions(cliVersion, min) === -1)
|
|
10512
10728
|
return { action: "block", latest, ...extra };
|
|
10513
10729
|
if (compareVersions(cliVersion, latest) === -1)
|
|
@@ -10576,7 +10792,7 @@ ${HELP_TEXT}`);
|
|
|
10576
10792
|
launchPi,
|
|
10577
10793
|
log: (message) => console.log(message),
|
|
10578
10794
|
checkClientVersion,
|
|
10579
|
-
startGravityPixel: (jwt) => startGravityPixel(jwt,
|
|
10795
|
+
startGravityPixel: (jwt) => startGravityPixel(jwt, join7(dirname5(agentDir), "gravity")),
|
|
10580
10796
|
relaunchLatest: (latest) => relaunchViaNpx(latest, {
|
|
10581
10797
|
env: process.env,
|
|
10582
10798
|
argv: process.argv.slice(2),
|