@genex-ai/cli-demo 1.38.0-dev.1083 → 1.38.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/dist/{blender-mcp-WSURPZET.js → blender-mcp-QVEGHE5L.js} +2 -2
- package/dist/{blender-serve-EH2MOW3U.js → blender-serve-66OHC4ML.js} +1 -1
- package/dist/{chunk-EABIVAGT.js → chunk-4BBTPRJW.js} +2 -2
- package/dist/{chunk-K4EHXOZK.js → chunk-QI7FIYBY.js} +1 -1
- package/dist/index.js +266 -771
- package/package.json +1 -1
- package/templates/skills/genex-game-director/SKILL.md +1 -1
- package/templates/skills/genex-getting-started/SKILL.md +2 -2
- package/templates/skills/genex-llm-in-games/SKILL.md +72 -209
- package/templates/skills/genex-llm-in-games/references/pricing.md +85 -126
- package/templates/skills/genex-monetization/SKILL.md +11 -2
- package/templates/skills/genex-threejs-embed-auth/SKILL.md +1 -4
- package/templates/skills/genex-threejs-embed-auth/references/shells.md +0 -104
- package/templates/skills/genex-tool-llm/SKILL.md +9 -11
- package/templates/skills/genex-tool-publish/SKILL.md +0 -7
- package/templates/skills/genex-updates/SKILL.md +1 -1
package/dist/index.js
CHANGED
|
@@ -57,7 +57,7 @@ import {
|
|
|
57
57
|
writeSecretFile,
|
|
58
58
|
writeUserToken,
|
|
59
59
|
writeWorkspace
|
|
60
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-4BBTPRJW.js";
|
|
61
61
|
import {
|
|
62
62
|
CLI_CHANNEL,
|
|
63
63
|
DEFAULT_API_URL,
|
|
@@ -78,7 +78,7 @@ import {
|
|
|
78
78
|
normalizeApiOrigin,
|
|
79
79
|
originKey,
|
|
80
80
|
resolveAgentTargets
|
|
81
|
-
} from "./chunk-
|
|
81
|
+
} from "./chunk-QI7FIYBY.js";
|
|
82
82
|
|
|
83
83
|
// src/instrument.ts
|
|
84
84
|
import { setTimeout as sleep } from "timers/promises";
|
|
@@ -1134,7 +1134,7 @@ Important note: put soul into your creations, with many details and love. Aim to
|
|
|
1134
1134
|
21. A turn ends in exactly one of two ways: on a question the player must answer before the next step can be chosen, or on a handoff \u2014 the draft link, what changed, what to try, and what is still open. Never close a turn on a promise. "I'll keep building", "while I keep working", "adding that now" are things you say and then DO before you stop; if you are stopping, say that you have stopped and what remains. While the Build plan's \`Now:\` line has open work and no answer is needed from the player, the turn is not over: preview, hand off, and start the next milestone in the same turn, until the requested outcome is reached or the platform's own budget ends the session. If a stop was forced on you anyway, the first line of your next turn is the promise you left and where it stands.
|
|
1135
1135
|
22. **What the player sees must read as the thing it is**, in this game's own style: a building as that building, a person as a person, a prop as that prop, a surface as its material \u2014 and a coloured box, a capsule, or a flat grey block standing in for one of them is never its finished version. Generation is the default route to that bar wherever code will not honestly reach it: the player's character, whatever the request names, whatever the player walks up to, enters, or interacts with, and the music and sound underneath \u2014 reach for the belt on your own judgment, without stopping to ask; a status line saying what you queued is enough, and \`--no-wait\` keeps you building while it lands. Procedural code is a first-class engine for what is structural, repeated, distant, or parametric \u2014 terrain, sky, fences, paving, modular kits, filler \u2014 and for anything else only when the result meets the same bar and you have looked at it in a capture before its row says \`landed\`. Start with one reusable implementation per distinct required gameplay or visual role. Background populations default to reusable procedural bodies and motion or compatible existing rigged assets. Catalog clips need a compatible skeleton and may be paid. Reserve paid custom bodies for the player and characters examined or interacted with closely, while honoring explicit user requests. Add variants only for an unmet requirement; unspent allowance is not unfinished work. Allowance questions use timeoutPolicy no-consent: silence does not raise the allowance. Record role, reuse and completion criteria in the existing Assets table. Decide per object, not per category, and write the route on each Assets row in \`DESIGN.md\` \u2014 the paid flow for generated pieces, the procedural flow with its local path for code-built ones. An Assets table with no generation in it is a decision, not a default: record it as \`Generation: none \u2014 <why code alone reaches the bar here>\` or generate.
|
|
1136
1136
|
23. **The screenshots you take to look at your own work go in \`.genex/scratch/\`.** Captures, render comparisons, before/after strips, traces and metrics dumps are how you SEE what you built \u2014 they are not part of what you built, and \`genex preview\` pushes the whole folder to the game's source, every time, forever. \`.genex/scratch/\` already exists for this and is already ignored, so there is nothing to set up: write the capture as \`.genex/scratch/arena-before.png\` and read it back from there. Never put them in a folder of your own at the top level: \`progress/\`, \`reports/\`, \`shots/\` and their kind are pushed like source and become permanent weight in the game and in every remix of it \u2014 one real game reached 11 GB and 10,332 committed screenshots exactly this way. If you find such a folder already there, add it to \`.gitignore\`; never delete the player's files (law 18). One file in \`.genex/scratch/\` IS read, by the platform: \`.genex/scratch/cover.png\` is the game's cover \u2014 the frame you would put on its poster: real gameplay at its signature moment, well lit, no menus, popups or busy HUD, landscape 16:9, and never any text (the page sets the title beside it). The next \`genex preview\` makes it the cover; save a better moment over it whenever one comes along, and if your browser tool can set the cover itself, use that instead. Before the first publish, and whenever the game's look changes, load \`$genex-cover\`: it chooses, stages, lights and judges that frame.
|
|
1137
|
-
24. **Benchmark before declaring an in-game LLM
|
|
1137
|
+
24. **Benchmark before declaring an in-game LLM price.** If the game calls a language model while the player plays \u2014 an NPC answering in its own words, a quest written for this save, a judge reading what the player typed \u2014 the PLAYER pays for it, and \`estimateCoins\` is the FIXED price of a started attempt: charged whether it succeeds, fails, is canceled, or stops at its own budget. Never pick that number by judgement or from a vendor's rate card. Run the real prompt on your own Nex \u2014 \`npx genex llm bench "<the prompt>" --schema <file> --samples 3 --max-coins <n> --user-approved\` \u2014 and declare the figure it recommends, recorded in \`DESIGN.md\` with its model and date; re-benchmark whenever the prompt, the schema or the model changes, because a prompt edit is a price change. Check the lane first with \`npx genex llm models\`: where it is off the routes 404, so build the feature behind a graceful unavailable state and say so in the handoff. A repeated call is a standing budget the player approves once (\`requestSpendGrant()\`, then \`generate({ grantId })\`), never a popup per turn, and its disclosed call rate is computed from the game's own loop. Model output is data the game validates against its own expectation \u2014 never executed, and never authority over Nex, items, entitlements or rewards. Load \`$genex-llm-in-games\` before writing any of it.
|
|
1138
1138
|
${CONTRACT_END}
|
|
1139
1139
|
`;
|
|
1140
1140
|
var TOOLS_CONTRACT_HEAD = `# Genex Tools (always in effect in this folder)
|
|
@@ -1148,7 +1148,7 @@ var TOOLS_CONTRACT_OFFER = `5. **When the game is built and playable \u2014 neve
|
|
|
1148
1148
|
var TOOLS_CONTRACT_HOSTED_LAWS = `5. **This game is hosted on Genex now, and publishing is glue \u2014 never a rebuild.** The game ships exactly as the user built it: \`initEmbed()\` first in the boot code (the \`genex-threejs-embed-auth\` card has the call; the renderer draws before any \`await waitForPlayer()\`), a static build into \`dist/\` with relative asset paths, then \`npx genex preview\`. Never restructure, reformat or "improve" the game to ship it, never scaffold a new app around it, and never start a design document or a build plan for it \u2014 the user's own process is theirs. Load the \`genex-tool-publish\` card before the first preview; it owns the vocabulary, the links and the limits.
|
|
1149
1149
|
6. **Ship first, then report.** \`preview\` prints a preflight \u2014 phone memory, a missing volume slider, an asset nobody wired, a viewport line. It never blocks a deploy, so push the build as it is, hand over the link, THEN relay each preflight line to the user in one plain sentence with an offer to fix it, and fix one only on their yes. The preflight is a report for the user, not a to-do list for you.
|
|
1150
1150
|
7. **The link is the game's page, and there are two versions.** After every preview give the user \`<dashboard>/draft/<slug>\` \u2014 \`dashboardOrigins[0]\` and \`slug\` from \`.genex/project.json\` \u2014 never localhost, a file path or the bare play origin. \`preview\` updates the draft and never touches what players are on; the first release is \`npx genex publish\` (it lists the game); after that "publish it", "update it" and "yes" all mean \`npx genex promote\` \u2014 the exact draft build, no rebuild. Ask once per round of work, in one line, and keep working while you wait.`;
|
|
1151
|
-
var TOOLS_CONTRACT_LLM_OFFER = `A model running while people play is built into the Genex platform \u2014 the player pays, with
|
|
1151
|
+
var TOOLS_CONTRACT_LLM_OFFER = `A model running while people play is built into the Genex platform \u2014 the player pays, with Nex or their own Claude/ChatGPT subscription, and approves it on a Genex sheet; your game just calls \`generate()\`. Want it that way?`;
|
|
1152
1152
|
function toolsContractLlmLaw(n, hosted) {
|
|
1153
1153
|
const onYes = hosted ? "On a yes, load `$genex-tool-llm` and then `$genex-llm-in-games`, which owns the build." : "On a yes, load `$genex-tool-llm`, which owns the rest: it checks the lane with `npx genex llm models` first, then runs `npx genex init --convert` (a hosted game is a static browser build \u2014 a local server holding a key can never ship).";
|
|
1154
1154
|
return `${n}. **A model running while people PLAY is a platform feature \u2014 never something you wire onto the user's own meter.** When a request implies one (NPCs that talk or decide in their own words, content written from what the player types, a prompt box in the game, "let the player pick a model"), recognise it, say in ONE line: "${TOOLS_CONTRACT_LLM_OFFER}" \u2014 then ASK and wait. ${onYes} On a no, build the authored version instead. Either way, never ship a game that calls a model on a key or an account of the user's own: every visitor would spend their money with nobody approving it, and the credential is readable in the bundle. Player-funded generation is text and JSON only \u2014 3D, images, video and audio stay the asset lanes above, on the user's meter.`;
|
|
@@ -8739,8 +8739,8 @@ function formatCoins(coins) {
|
|
|
8739
8739
|
function coinPrice(coins, displayUsdCents) {
|
|
8740
8740
|
if (coins === 0) return "Free";
|
|
8741
8741
|
if (displayUsdCents === void 0) return `${formatCoins(coins)} Nex`;
|
|
8742
|
-
const
|
|
8743
|
-
return `${formatCoins(coins)} Nex ($${
|
|
8742
|
+
const usd2 = (displayUsdCents / 100).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
8743
|
+
return `${formatCoins(coins)} Nex ($${usd2})`;
|
|
8744
8744
|
}
|
|
8745
8745
|
function priceGridLine(grid, freeAllowed) {
|
|
8746
8746
|
return `${[...freeAllowed ? ["Free"] : [], ...grid.map(formatCoins)].join(" \xB7 ")} Nex`;
|
|
@@ -10853,77 +10853,13 @@ async function ensureRealDir(dir, label) {
|
|
|
10853
10853
|
}
|
|
10854
10854
|
}
|
|
10855
10855
|
|
|
10856
|
-
// src/lib/web-version.ts
|
|
10857
|
-
var NATIVE_PAGE_WOULD_BE_REPLACED = "native_page_would_be_replaced";
|
|
10858
|
-
function isNativePageRefusal(body) {
|
|
10859
|
-
return body?.error === NATIVE_PAGE_WOULD_BE_REPLACED || body?.code === NATIVE_PAGE_WOULD_BE_REPLACED;
|
|
10860
|
-
}
|
|
10861
|
-
function explainNativePageRefusal(log, command, uploaded = false) {
|
|
10862
|
-
log.error("This game's page currently shows its Mac/Windows/Linux app \u2014 nothing was changed.");
|
|
10863
|
-
log.dim(" Making this web build live would replace that page: visitors would get this web");
|
|
10864
|
-
log.dim(" build instead of the app.");
|
|
10865
|
-
if (uploaded) log.dim(" The build was uploaded to the preview channel only; players never see it there.");
|
|
10866
|
-
log.dim(" Re-run with --replace-native-page ONLY if this folder really is the game's web version:");
|
|
10867
|
-
log.dim(` npx genex ${command} --replace-native-page`);
|
|
10868
|
-
}
|
|
10869
|
-
var REFUSALS = {
|
|
10870
|
-
no_native_build: [
|
|
10871
|
-
"This game has no Mac, Windows or Linux build players get, so removing its web version would leave nothing to play \u2014 nothing was changed.",
|
|
10872
|
-
" A native build waiting for review does not count yet.",
|
|
10873
|
-
" To take the game out of the gallery, unpublish it; to remove it entirely, delete it from the dashboard."
|
|
10874
|
-
],
|
|
10875
|
-
game_taken_down: [
|
|
10876
|
-
"This game was taken down by moderation; its build is kept for the review \u2014 nothing was changed."
|
|
10877
|
-
],
|
|
10878
|
-
web_removal_incomplete: [
|
|
10879
|
-
"The removal stopped partway: the web build no longer serves, but it is not fully gone yet.",
|
|
10880
|
-
" Run `npx genex web remove --yes` again to finish."
|
|
10881
|
-
],
|
|
10882
|
-
release_conflict: ["Another release of this game is in progress \u2014 nothing was changed. Try again in a moment."]
|
|
10883
|
-
};
|
|
10884
|
-
async function removeWebVersion(ctx, log) {
|
|
10885
|
-
let res;
|
|
10886
|
-
try {
|
|
10887
|
-
res = await apiFetch(`${ctx.apiUrl}/api/games/${ctx.projectId}/web`, {
|
|
10888
|
-
method: "DELETE",
|
|
10889
|
-
headers: { Authorization: `Bearer ${ctx.token}` }
|
|
10890
|
-
});
|
|
10891
|
-
} catch (err) {
|
|
10892
|
-
log.error(`Couldn't reach the API at ${ctx.apiUrl}: ${String(err)}`);
|
|
10893
|
-
return null;
|
|
10894
|
-
}
|
|
10895
|
-
const body = await res.json().catch(() => null);
|
|
10896
|
-
if (res.ok) return { removed: body?.removed === true, web: false };
|
|
10897
|
-
const words2 = body?.error ? REFUSALS[body.error] : void 0;
|
|
10898
|
-
if (words2 && body?.error) {
|
|
10899
|
-
const [first, ...rest] = words2;
|
|
10900
|
-
if (first) log.error(first);
|
|
10901
|
-
for (const line of rest) log.dim(line);
|
|
10902
|
-
return { refused: body.error };
|
|
10903
|
-
}
|
|
10904
|
-
if (res.status === 401) {
|
|
10905
|
-
log.error("Not authorized (HTTP 401) \u2014 your sign-in expired or was revoked. Run `genex link <your-game>`.");
|
|
10906
|
-
return null;
|
|
10907
|
-
}
|
|
10908
|
-
if (res.status === 404) {
|
|
10909
|
-
log.error("That game wasn't found on your account (HTTP 404), or this Genex server can't remove a web version yet.");
|
|
10910
|
-
return null;
|
|
10911
|
-
}
|
|
10912
|
-
log.error(`Couldn't remove the web version (HTTP ${res.status})${body?.error ? `: ${body.error}` : ""}.`);
|
|
10913
|
-
return null;
|
|
10914
|
-
}
|
|
10915
|
-
|
|
10916
10856
|
// src/lib/promote.ts
|
|
10917
|
-
async function promoteBuild(apiUrl, projectId, token, log
|
|
10857
|
+
async function promoteBuild(apiUrl, projectId, token, log) {
|
|
10918
10858
|
let res;
|
|
10919
10859
|
try {
|
|
10920
10860
|
res = await apiFetch(`${apiUrl}/api/games/${projectId}/promote`, {
|
|
10921
10861
|
method: "POST",
|
|
10922
|
-
headers: {
|
|
10923
|
-
Authorization: `Bearer ${token}`,
|
|
10924
|
-
...opts.replaceNativePage ? { "Content-Type": "application/json" } : {}
|
|
10925
|
-
},
|
|
10926
|
-
...opts.replaceNativePage ? { body: JSON.stringify({ replaceNativePage: true }) } : {}
|
|
10862
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
10927
10863
|
});
|
|
10928
10864
|
} catch (err) {
|
|
10929
10865
|
log.error(`Couldn't reach the API at ${apiUrl}: ${String(err)}`);
|
|
@@ -10931,11 +10867,6 @@ async function promoteBuild(apiUrl, projectId, token, log, opts = {}) {
|
|
|
10931
10867
|
}
|
|
10932
10868
|
if (res.status === 409) {
|
|
10933
10869
|
const body2 = await res.json().catch(() => null);
|
|
10934
|
-
if (isNativePageRefusal(body2)) {
|
|
10935
|
-
const command = opts.command ?? "promote";
|
|
10936
|
-
explainNativePageRefusal(log, command, command === "publish");
|
|
10937
|
-
return null;
|
|
10938
|
-
}
|
|
10939
10870
|
if (body2?.code === "live_is_newer") {
|
|
10940
10871
|
log.error("The live build is newer than your preview \u2014 nothing was changed.");
|
|
10941
10872
|
log.dim(" This happens after the live version was restored, or after a later publish.");
|
|
@@ -11068,10 +10999,7 @@ async function runPublish(opts) {
|
|
|
11068
10999
|
return;
|
|
11069
11000
|
}
|
|
11070
11001
|
log.step("Making it live\u2026");
|
|
11071
|
-
if (!await promoteBuild(apiUrl, meta.id, token, log
|
|
11072
|
-
replaceNativePage: opts.replaceNativePage,
|
|
11073
|
-
command: "publish"
|
|
11074
|
-
})) {
|
|
11002
|
+
if (!await promoteBuild(apiUrl, meta.id, token, log)) {
|
|
11075
11003
|
process.exitCode = 1;
|
|
11076
11004
|
return;
|
|
11077
11005
|
}
|
|
@@ -11098,7 +11026,6 @@ async function runPublish(opts) {
|
|
|
11098
11026
|
if (opts.license) body.license = opts.license;
|
|
11099
11027
|
if (opts.purchaseUrl) body.purchaseUrl = opts.purchaseUrl;
|
|
11100
11028
|
if (opts.productName) body.productName = opts.productName;
|
|
11101
|
-
if (opts.replaceNativePage) body.replaceNativePage = true;
|
|
11102
11029
|
body.matchmaking = detections.matchmaking ?? null;
|
|
11103
11030
|
body.gameStateUsed = detections.gameStateUsed;
|
|
11104
11031
|
body.mobileControls = detections.mobileControls;
|
|
@@ -11120,11 +11047,6 @@ async function runPublish(opts) {
|
|
|
11120
11047
|
const listedOnDisk = await readProject();
|
|
11121
11048
|
await writeProject({ ...listedOnDisk ?? meta, status: "published" });
|
|
11122
11049
|
const listed = await res.json().catch(() => null);
|
|
11123
|
-
if (listed?.project?.commitSha === null && listed.project.stagingCommitSha) {
|
|
11124
|
-
log.warn("Listed with its Mac/Windows/Linux app \u2014 your preview was not made live over the app's page.");
|
|
11125
|
-
log.dim(" Re-run with --replace-native-page ONLY if this folder really is the game's web version:");
|
|
11126
|
-
log.dim(" npx genex publish --no-push --replace-native-page");
|
|
11127
|
-
}
|
|
11128
11050
|
if (opts.regenerateCover && listed?.project?.coverAiEnabled === false) {
|
|
11129
11051
|
log.dim(" --regenerate-cover did nothing: this stand doesn't make AI covers.");
|
|
11130
11052
|
log.dim(" The cover is a real frame of the game \u2014 set one with `npx genex cover <file>`.");
|
|
@@ -11164,10 +11086,7 @@ async function runPromote(opts) {
|
|
|
11164
11086
|
return;
|
|
11165
11087
|
}
|
|
11166
11088
|
log.step("Making your previewed build live\u2026");
|
|
11167
|
-
const result = await promoteBuild(apiUrl, meta.id, token, log
|
|
11168
|
-
replaceNativePage: opts.replaceNativePage,
|
|
11169
|
-
command: "promote"
|
|
11170
|
-
});
|
|
11089
|
+
const result = await promoteBuild(apiUrl, meta.id, token, log);
|
|
11171
11090
|
if (!result) {
|
|
11172
11091
|
process.exitCode = 1;
|
|
11173
11092
|
return;
|
|
@@ -11642,7 +11561,6 @@ var BILLING_ERROR_MARKERS = [
|
|
|
11642
11561
|
/failed:\s*402\b/,
|
|
11643
11562
|
/insufficient funds/i,
|
|
11644
11563
|
/dunning decision is deny/i,
|
|
11645
|
-
/billing account has an outstanding balance/i,
|
|
11646
11564
|
/User is locked\. Reason:/
|
|
11647
11565
|
];
|
|
11648
11566
|
function isProviderBillingError(error) {
|
|
@@ -26093,7 +26011,7 @@ async function runBlender(opts) {
|
|
|
26093
26011
|
return 1;
|
|
26094
26012
|
}
|
|
26095
26013
|
if (sub === "serve") {
|
|
26096
|
-
const { serveLocalBlender } = await import("./blender-serve-
|
|
26014
|
+
const { serveLocalBlender } = await import("./blender-serve-66OHC4ML.js");
|
|
26097
26015
|
const port = Number(process.env.GENEX_BLENDER_PORT ?? 8088);
|
|
26098
26016
|
log.step(`Starting a local Blender service on port ${port}`);
|
|
26099
26017
|
log.plain(
|
|
@@ -26102,7 +26020,7 @@ async function runBlender(opts) {
|
|
|
26102
26020
|
return serveLocalBlender({ port, log });
|
|
26103
26021
|
}
|
|
26104
26022
|
if (sub === "mcp") {
|
|
26105
|
-
const { runBlenderMcp } = await import("./blender-mcp-
|
|
26023
|
+
const { runBlenderMcp } = await import("./blender-mcp-QVEGHE5L.js");
|
|
26106
26024
|
return runBlenderMcp();
|
|
26107
26025
|
}
|
|
26108
26026
|
if (sub === "seat") {
|
|
@@ -26610,12 +26528,7 @@ import fs39 from "fs/promises";
|
|
|
26610
26528
|
import path39 from "path";
|
|
26611
26529
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
26612
26530
|
var SUBS4 = ["models", "bench", "price", "status", "cancel"];
|
|
26613
|
-
var LLM_BENCH_APPROVAL_REQUIRED = "STOP: `genex llm bench` runs the real model and spends YOUR OWN
|
|
26614
|
-
var LLM_MAX_COINS_DEPRECATED = "--max-coins is deprecated: it is read as --max-credits, the same number \u2014 a bench spends credits now.";
|
|
26615
|
-
function benchBalanceShortLine(spendable, maxCredits) {
|
|
26616
|
-
return `Your spendable credits (${spendable}) cannot cover one attempt at --max-credits ${maxCredits} \u2014 nothing was sent, nothing was spent. Add credits, or re-run with a lower --max-credits once the person at the keyboard agrees to it.`;
|
|
26617
|
-
}
|
|
26618
|
-
var LLM_BENCH_UNVERIFIED_LINE = "Credits pay only for a verified email, and this account's is not verified yet (credits_unverified) \u2014 nothing was sent, nothing was spent. Verify it, then re-run.";
|
|
26531
|
+
var LLM_BENCH_APPROVAL_REQUIRED = "STOP: `genex llm bench` runs the real model and spends YOUR OWN Nex \u2014 your wallet, not a player's, and one this build's asset allowance cannot see. Re-run with --max-coins <n> --user-approved only after the person at the keyboard agreed to the number.";
|
|
26619
26532
|
var LLM_LANE_OFF_LINE = "The runtime LLM lane is off on this stand \u2014 in-game generate() answers 404 here, and nothing can be benchmarked.";
|
|
26620
26533
|
var LLM_TOOLS_CONVERT_HINT = [
|
|
26621
26534
|
"In-game model calls need a hosted game. This folder is a Genex Tools workspace, so nothing here",
|
|
@@ -26628,7 +26541,7 @@ function serverSlotCounts(body) {
|
|
|
26628
26541
|
return { held: body.slotsHeld, limit: body.slotLimit };
|
|
26629
26542
|
}
|
|
26630
26543
|
var PENDING_SLOT_RELEASE_MINUTES = 10;
|
|
26631
|
-
var LLM_STATUS_LEGEND = `"awaiting bill" = the call has stopped but the provider's bill is not final, so
|
|
26544
|
+
var LLM_STATUS_LEGEND = `"awaiting bill" = the call has stopped but the provider's bill is not final, so its Nex stays held until the bill resolves; such a row stops holding a slot ${PENDING_SLOT_RELEASE_MINUTES} minutes after dispatch.`;
|
|
26632
26545
|
var LLM_STATUS_NO_COUNT_LINE = "This stand does not report how many slots are held across your projects, so no count is shown.";
|
|
26633
26546
|
var LLM_CANCEL_AWAITING_BILL = "this call already stopped; its bill is awaiting the provider";
|
|
26634
26547
|
var LLM_CANCEL_ALREADY_FINAL = "this call is already final";
|
|
@@ -26642,33 +26555,11 @@ var DEFAULT_SAMPLE_TIMEOUT_SEC = 180;
|
|
|
26642
26555
|
function parseProviderKey(value) {
|
|
26643
26556
|
return value === "ok" || value === "rejected" || value === "unknown" ? value : null;
|
|
26644
26557
|
}
|
|
26645
|
-
function bpsOrNull(value) {
|
|
26646
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
26647
|
-
}
|
|
26648
|
-
function standingCreditGrantOrNull(value) {
|
|
26649
|
-
if (!value || typeof value !== "object") return null;
|
|
26650
|
-
const v = value;
|
|
26651
|
-
const whole = (x) => typeof x === "number" && Number.isSafeInteger(x) && x >= 0;
|
|
26652
|
-
if (!whole(v.minCredits) || !whole(v.maxCredits) || !whole(v.stepCredits) || !whole(v.defaultCredits)) return null;
|
|
26653
|
-
return {
|
|
26654
|
-
minCredits: v.minCredits,
|
|
26655
|
-
maxCredits: v.maxCredits,
|
|
26656
|
-
stepCredits: v.stepCredits,
|
|
26657
|
-
defaultCredits: v.defaultCredits,
|
|
26658
|
-
...whole(v.minUsdCents) ? { minUsdCents: v.minUsdCents } : {},
|
|
26659
|
-
...whole(v.maxUsdCents) ? { maxUsdCents: v.maxUsdCents } : {},
|
|
26660
|
-
...whole(v.stepUsdCents) ? { stepUsdCents: v.stepUsdCents } : {}
|
|
26661
|
-
};
|
|
26662
|
-
}
|
|
26663
26558
|
async function readRuntimeLane(apiUrl, token) {
|
|
26664
26559
|
const empty = (state) => ({
|
|
26665
26560
|
state,
|
|
26666
26561
|
models: null,
|
|
26667
26562
|
recommendedDeclaredHeadroomBps: null,
|
|
26668
|
-
currency: null,
|
|
26669
|
-
feeBps: null,
|
|
26670
|
-
usdCentsPerCredit: null,
|
|
26671
|
-
standingCreditGrant: null,
|
|
26672
26563
|
externalProviders: null,
|
|
26673
26564
|
total: null,
|
|
26674
26565
|
featuredCount: null,
|
|
@@ -26687,11 +26578,7 @@ async function readRuntimeLane(apiUrl, token) {
|
|
|
26687
26578
|
return {
|
|
26688
26579
|
state: "live",
|
|
26689
26580
|
models: body.models,
|
|
26690
|
-
recommendedDeclaredHeadroomBps:
|
|
26691
|
-
currency: body.currency === "credits" || body.currency === "coins" ? body.currency : null,
|
|
26692
|
-
feeBps: bpsOrNull(body.feeBps),
|
|
26693
|
-
usdCentsPerCredit: typeof body.usdCentsPerCredit === "number" && Number.isSafeInteger(body.usdCentsPerCredit) && body.usdCentsPerCredit >= 1 ? body.usdCentsPerCredit : null,
|
|
26694
|
-
standingCreditGrant: standingCreditGrantOrNull(body.standingCreditGrant),
|
|
26581
|
+
recommendedDeclaredHeadroomBps: typeof body.recommendedDeclaredHeadroomBps === "number" && Number.isFinite(body.recommendedDeclaredHeadroomBps) ? body.recommendedDeclaredHeadroomBps : null,
|
|
26695
26582
|
externalProviders: Array.isArray(body.externalProviders) ? body.externalProviders : null,
|
|
26696
26583
|
total: typeof body.total === "number" && Number.isFinite(body.total) ? body.total : null,
|
|
26697
26584
|
featuredCount: typeof body.featuredCount === "number" && Number.isFinite(body.featuredCount) ? body.featuredCount : null,
|
|
@@ -26707,45 +26594,18 @@ function percentile2(values, p) {
|
|
|
26707
26594
|
const rank2 = Math.ceil(p / 100 * sorted.length);
|
|
26708
26595
|
return sorted[Math.min(sorted.length - 1, Math.max(0, rank2 - 1))];
|
|
26709
26596
|
}
|
|
26710
|
-
function
|
|
26711
|
-
|
|
26712
|
-
const sorted = [...values].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
26713
|
-
const rank2 = Math.ceil(p / 100 * sorted.length);
|
|
26714
|
-
return sorted[Math.min(sorted.length - 1, Math.max(0, rank2 - 1))];
|
|
26597
|
+
function recommendEstimateCoins(p95, headroomBps) {
|
|
26598
|
+
return Math.max(1, Math.ceil(p95 * (1e4 + headroomBps) / 1e4));
|
|
26715
26599
|
}
|
|
26716
|
-
|
|
26717
|
-
|
|
26718
|
-
if (costPicos <= 0n) return 1;
|
|
26719
|
-
const num = costPicos * BigInt(1e4 + terms.feeBps) * BigInt(1e4 + terms.headroomBps);
|
|
26720
|
-
const den = 100000000n * BigInt(terms.usdCentsPerCredit) * PICOS_PER_CENT;
|
|
26721
|
-
return Math.max(1, Number((num + den - 1n) / den));
|
|
26722
|
-
}
|
|
26723
|
-
function averageCreditsPerCall(costsPicos, terms) {
|
|
26724
|
-
if (costsPicos.length === 0) return null;
|
|
26725
|
-
const sum = costsPicos.reduce((a, b) => a + b, 0n);
|
|
26726
|
-
return Number(sum * BigInt(1e4 + terms.feeBps)) / Number(BigInt(costsPicos.length) * 10000n * BigInt(terms.usdCentsPerCredit) * PICOS_PER_CENT);
|
|
26727
|
-
}
|
|
26728
|
-
function perCallEstimateCreditsFor(costsPicos, terms) {
|
|
26729
|
-
if (costsPicos.length === 0) return null;
|
|
26730
|
-
const sum = costsPicos.reduce((a, b) => a + b, 0n);
|
|
26731
|
-
const num = sum * BigInt(1e4 + terms.feeBps);
|
|
26732
|
-
const den = BigInt(costsPicos.length) * 10000n * BigInt(terms.usdCentsPerCredit) * PICOS_PER_CENT;
|
|
26733
|
-
return Math.max(1, Number((num + den - 1n) / den));
|
|
26734
|
-
}
|
|
26735
|
-
function formatCredits(value) {
|
|
26736
|
-
if (!Number.isFinite(value)) return "\u2014";
|
|
26737
|
-
if (value >= 100) return String(Math.round(value));
|
|
26738
|
-
return String(Number(value.toPrecision(value < 1 ? 2 : 3)));
|
|
26739
|
-
}
|
|
26740
|
-
function usdFromPicos(picos) {
|
|
26741
|
-
if (picos === null || picos === void 0) return "unknown";
|
|
26742
|
-
const value = typeof picos === "bigint" ? picos : /^\d+$/.test(picos) ? BigInt(picos) : null;
|
|
26743
|
-
if (value === null) return "unknown";
|
|
26744
|
-
return `$${(Number(value) / 1e12).toFixed(6)}`;
|
|
26600
|
+
function recommendPerCallMaxCoins(p95, max, headroomBps) {
|
|
26601
|
+
return Math.max(recommendEstimateCoins(p95, headroomBps), recommendEstimateCoins(max, headroomBps));
|
|
26745
26602
|
}
|
|
26746
26603
|
function usdPerMillion(value) {
|
|
26747
26604
|
return `$${value.toFixed(value < 1 ? 4 : 2)}`;
|
|
26748
26605
|
}
|
|
26606
|
+
function usd(value) {
|
|
26607
|
+
return value === null ? "unknown" : `$${value.toFixed(6)}`;
|
|
26608
|
+
}
|
|
26749
26609
|
async function runLlm(opts = {}) {
|
|
26750
26610
|
const log = createLogger({ quiet: opts.quiet || opts.json });
|
|
26751
26611
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -26771,23 +26631,16 @@ async function runLlm(opts = {}) {
|
|
|
26771
26631
|
return;
|
|
26772
26632
|
}
|
|
26773
26633
|
if (sub === "bench") {
|
|
26774
|
-
if (opts.maxCoins
|
|
26775
|
-
`);
|
|
26776
|
-
if (opts.maxCoins !== void 0 && opts.maxCredits !== void 0 && opts.maxCoins !== opts.maxCredits) {
|
|
26777
|
-
fail5("llm bench", `Pass --max-credits alone \u2014 --max-coins is its deprecated name, and the two disagree (${opts.maxCredits} vs ${opts.maxCoins}).`);
|
|
26778
|
-
return;
|
|
26779
|
-
}
|
|
26780
|
-
const approvedCredits = opts.maxCredits ?? opts.maxCoins;
|
|
26781
|
-
if (approvedCredits === void 0 || opts.userApproved !== true) {
|
|
26634
|
+
if (opts.maxCoins === void 0 || opts.userApproved !== true) {
|
|
26782
26635
|
fail5("llm bench", LLM_BENCH_APPROVAL_REQUIRED);
|
|
26783
26636
|
return;
|
|
26784
26637
|
}
|
|
26785
|
-
if (!Number.isInteger(
|
|
26786
|
-
fail5("llm bench", `--max-
|
|
26638
|
+
if (!Number.isInteger(opts.maxCoins) || opts.maxCoins < 1) {
|
|
26639
|
+
fail5("llm bench", `--max-coins takes a whole number of Nex, 1 or more (got ${String(opts.maxCoins)}).`);
|
|
26787
26640
|
return;
|
|
26788
26641
|
}
|
|
26789
26642
|
if (!opts.benchPrompt?.trim()) {
|
|
26790
|
-
fail5("llm bench", '`genex llm bench` needs the prompt your game would send, e.g. genex llm bench "<prompt>" --max-
|
|
26643
|
+
fail5("llm bench", '`genex llm bench` needs the prompt your game would send, e.g. genex llm bench "<prompt>" --max-coins <n> --user-approved.');
|
|
26791
26644
|
return;
|
|
26792
26645
|
}
|
|
26793
26646
|
if (opts.jsonOutput && opts.textOutput) {
|
|
@@ -26879,10 +26732,6 @@ async function reportModels(apiUrl, token, opts, log, toolsOnly = false) {
|
|
|
26879
26732
|
models: null,
|
|
26880
26733
|
externalProviders: null,
|
|
26881
26734
|
recommendedDeclaredHeadroomBps: null,
|
|
26882
|
-
currency: null,
|
|
26883
|
-
feeBps: null,
|
|
26884
|
-
usdCentsPerCredit: null,
|
|
26885
|
-
standingCreditGrant: null,
|
|
26886
26735
|
total: null,
|
|
26887
26736
|
featuredCount: null,
|
|
26888
26737
|
// The stand was never asked, so the verdict is unknown — but the key is
|
|
@@ -26908,12 +26757,6 @@ async function reportModels(apiUrl, token, opts, log, toolsOnly = false) {
|
|
|
26908
26757
|
models: lane.models,
|
|
26909
26758
|
externalProviders: lane.externalProviders,
|
|
26910
26759
|
recommendedDeclaredHeadroomBps: lane.recommendedDeclaredHeadroomBps,
|
|
26911
|
-
// The credit terms a call is billed on, the server's — null on a stand
|
|
26912
|
-
// that predates credit billing, never a number the CLI filled in.
|
|
26913
|
-
currency: lane.currency,
|
|
26914
|
-
feeBps: lane.feeBps,
|
|
26915
|
-
usdCentsPerCredit: lane.usdCentsPerCredit,
|
|
26916
|
-
standingCreditGrant: lane.standingCreditGrant,
|
|
26917
26760
|
total: lane.total ?? lane.models?.length ?? null,
|
|
26918
26761
|
featuredCount: lane.featuredCount ?? (lane.models ? lane.models.filter((m) => m.featured).length : null),
|
|
26919
26762
|
// `null` when the lane is not live or the server predates the probe.
|
|
@@ -26960,9 +26803,8 @@ async function reportModels(apiUrl, token, opts, log, toolsOnly = false) {
|
|
|
26960
26803
|
);
|
|
26961
26804
|
for (const m of shown) {
|
|
26962
26805
|
const plan2 = m.personalPlan ? ` \xB7 personal plan: ${m.personalPlan}` : "";
|
|
26963
|
-
const kind = m.kind === "typesafe" ? " \xB7 judge (classifier): judge() under a budget, not generate() or bench" : "";
|
|
26964
26806
|
log.plain(
|
|
26965
|
-
` ${c.cyan(m.id)} ${m.label} \u2014 ${usdPerMillion(m.inputUsdPerMillion)} in / ${usdPerMillion(m.outputUsdPerMillion)} out per million tokens${plan2}
|
|
26807
|
+
` ${c.cyan(m.id)} ${m.label} \u2014 ${usdPerMillion(m.inputUsdPerMillion)} in / ${usdPerMillion(m.outputUsdPerMillion)} out per million tokens${plan2}`
|
|
26966
26808
|
);
|
|
26967
26809
|
}
|
|
26968
26810
|
if (hiddenCount > 0) {
|
|
@@ -26978,104 +26820,74 @@ async function reportModels(apiUrl, token, opts, log, toolsOnly = false) {
|
|
|
26978
26820
|
}
|
|
26979
26821
|
log.plain("");
|
|
26980
26822
|
if (lane.recommendedDeclaredHeadroomBps === null) {
|
|
26981
|
-
log.dim(" This stand serves no recommended headroom, so no
|
|
26823
|
+
log.dim(" This stand serves no recommended headroom, so no price can be recommended from a bench.");
|
|
26982
26824
|
} else {
|
|
26983
|
-
log.dim(` Recommended headroom over
|
|
26984
|
-
}
|
|
26985
|
-
if (lane.feeBps === null) {
|
|
26986
|
-
log.dim(" This stand serves no platform fee \u2014 it predates credit billing, so no ceiling can be recommended from a bench.");
|
|
26987
|
-
} else {
|
|
26988
|
-
log.dim(` In-game calls are billed in the player's credits, as used: the provider's cost plus this stand's platform fee (${lane.feeBps} bps).`);
|
|
26825
|
+
log.dim(` Recommended headroom over benchmarked Nex on this stand: ${lane.recommendedDeclaredHeadroomBps} bps.`);
|
|
26989
26826
|
}
|
|
26990
26827
|
if (toolsOnly) {
|
|
26991
26828
|
printConvertHint();
|
|
26992
26829
|
return;
|
|
26993
26830
|
}
|
|
26994
|
-
log.dim(" Those per-million rates are the PROVIDER's
|
|
26995
|
-
log.dim(`
|
|
26831
|
+
log.dim(" Those per-million rates are the PROVIDER's; what your game is charged is the platform");
|
|
26832
|
+
log.dim(` tariff on top, which only a real call reveals: ${c.cyan('genex llm bench "<prompt>" --max-coins <n> --user-approved')}`);
|
|
26996
26833
|
}
|
|
26997
26834
|
function wholeOrNull(value) {
|
|
26998
26835
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
26999
26836
|
}
|
|
27000
|
-
function costPicosOf(usage) {
|
|
27001
|
-
if (typeof usage?.costUsdPicos === "string" && /^\d+$/.test(usage.costUsdPicos)) return usage.costUsdPicos;
|
|
27002
|
-
if (typeof usage?.costUsd === "number" && Number.isFinite(usage.costUsd) && usage.costUsd >= 0) {
|
|
27003
|
-
return String(BigInt(Math.round(usage.costUsd * 1e12)));
|
|
27004
|
-
}
|
|
27005
|
-
return null;
|
|
27006
|
-
}
|
|
27007
26837
|
function sampleFrom(id, settled) {
|
|
27008
26838
|
const usage = settled?.usage;
|
|
27009
|
-
const picos = costPicosOf(usage);
|
|
27010
26839
|
return {
|
|
27011
26840
|
id,
|
|
27012
26841
|
status: settled?.status ?? null,
|
|
27013
26842
|
billingStatus: settled?.billingStatus ?? null,
|
|
27014
|
-
|
|
27015
|
-
costUsd: typeof usage?.costUsd === "number" ? usage.costUsd :
|
|
27016
|
-
costUsdPicos: picos,
|
|
26843
|
+
chargedCoins: typeof settled?.chargedCoins === "number" ? settled.chargedCoins : null,
|
|
26844
|
+
costUsd: typeof usage?.costUsd === "number" ? usage.costUsd : null,
|
|
27017
26845
|
error: settled?.error ?? (settled ? null : "timed_out"),
|
|
27018
26846
|
providerMessage: typeof settled?.providerMessage === "string" && settled.providerMessage.trim() ? settled.providerMessage.trim() : null,
|
|
27019
26847
|
inputTokens: wholeOrNull(usage?.inputTokens),
|
|
27020
26848
|
outputTokens: wholeOrNull(usage?.outputTokens),
|
|
27021
26849
|
maxOutputTokens: wholeOrNull(usage?.maxOutputTokens),
|
|
27022
|
-
|
|
26850
|
+
fitCoins: typeof usage?.fitCoins === "number" && Number.isSafeInteger(usage.fitCoins) && usage.fitCoins >= 1 ? usage.fitCoins : null,
|
|
27023
26851
|
fitExceedsCap: typeof usage?.fitExceedsCap === "boolean" ? usage.fitExceedsCap : null,
|
|
27024
26852
|
truncated: typeof usage?.truncated === "boolean" ? usage.truncated : settled?.error === "provider_token_limit" ? true : null
|
|
27025
26853
|
};
|
|
27026
26854
|
}
|
|
27027
26855
|
var PROVIDER_TOKEN_LIMIT = "provider_token_limit";
|
|
27028
|
-
function benchRecommendation(results,
|
|
27029
|
-
const settled = results.filter(
|
|
27030
|
-
|
|
27031
|
-
);
|
|
27032
|
-
const
|
|
27033
|
-
const
|
|
27034
|
-
const fits = settled.map((r) => r.fitCredits).filter((v) => typeof v === "number");
|
|
27035
|
-
const costP95 = percentilePicos(costs, 95);
|
|
27036
|
-
const costMax = costs.length ? costs.reduce((a, b) => b > a ? b : a) : null;
|
|
26856
|
+
function benchRecommendation(results, headroomBps) {
|
|
26857
|
+
const settled = results.filter((r) => r.status === "succeeded" && r.billingStatus === "final" && typeof r.chargedCoins === "number");
|
|
26858
|
+
const charged = settled.map((r) => r.chargedCoins);
|
|
26859
|
+
const fits = settled.map((r) => r.fitCoins).filter((v) => typeof v === "number");
|
|
26860
|
+
const p95 = percentile2(charged, 95);
|
|
26861
|
+
const max = charged.length ? Math.max(...charged) : null;
|
|
27037
26862
|
const fitP95 = percentile2(fits, 95);
|
|
27038
26863
|
const fitMax = fits.length ? Math.max(...fits) : null;
|
|
27039
26864
|
const exceedsStandCap = results.some((r) => r.fitExceedsCap === true);
|
|
27040
26865
|
const cutOffSamples = results.filter((r) => r.error === PROVIDER_TOKEN_LIMIT && r.fitExceedsCap !== true).length;
|
|
27041
26866
|
const noRecommendationReason = exceedsStandCap ? "answer_exceeds_stand_cap" : cutOffSamples > 0 ? "answer_cut_off" : null;
|
|
27042
|
-
const
|
|
27043
|
-
const
|
|
27044
|
-
const
|
|
27045
|
-
const billing = feeBps !== null && usdCentsPerCredit !== null ? { feeBps, usdCentsPerCredit } : null;
|
|
27046
|
-
const costBased = full !== null && costP95 !== null ? ceilingCreditsFor(costP95, full) : null;
|
|
27047
|
-
const recommended = costBased === null || noRecommendationReason !== null ? null : Math.max(costBased, fitP95 ?? 0);
|
|
27048
|
-
const ceiling = recommended === null || costMax === null || full === null ? null : Math.max(ceilingCreditsFor(costMax, full), recommended, fitMax ?? 0);
|
|
27049
|
-
const average = billing !== null ? averageCreditsPerCall(costs, billing) : null;
|
|
27050
|
-
const estimate = ceiling === null || billing === null ? null : Math.min(ceiling, perCallEstimateCreditsFor(costs, billing) ?? 1);
|
|
26867
|
+
const chargedBased = p95 !== null && headroomBps !== null ? recommendEstimateCoins(p95, headroomBps) : null;
|
|
26868
|
+
const recommended = chargedBased === null || noRecommendationReason !== null ? null : Math.max(chargedBased, fitP95 ?? 0);
|
|
26869
|
+
const ceiling = recommended === null || p95 === null || max === null || headroomBps === null ? null : Math.max(recommendPerCallMaxCoins(p95, max, headroomBps), recommended, fitMax ?? 0);
|
|
27051
26870
|
return {
|
|
27052
|
-
costs,
|
|
27053
26871
|
charged,
|
|
27054
|
-
|
|
27055
|
-
|
|
27056
|
-
|
|
27057
|
-
chargedP50: percentile2(charged, 50),
|
|
27058
|
-
chargedP95: percentile2(charged, 95),
|
|
27059
|
-
chargedMax: charged.length ? Math.max(...charged) : null,
|
|
26872
|
+
p50: percentile2(charged, 50),
|
|
26873
|
+
p95,
|
|
26874
|
+
max,
|
|
27060
26875
|
fits,
|
|
27061
26876
|
fitP95,
|
|
27062
26877
|
fitMax,
|
|
27063
|
-
|
|
27064
|
-
|
|
27065
|
-
|
|
27066
|
-
|
|
27067
|
-
recommendedPerCallEstimateCredits: estimate,
|
|
27068
|
-
lengthRaisedCeiling: recommended !== null && costBased !== null && recommended > costBased,
|
|
26878
|
+
chargedBasedEstimateCoins: chargedBased,
|
|
26879
|
+
recommendedEstimateCoins: recommended,
|
|
26880
|
+
recommendedPerCallMaxCoins: ceiling,
|
|
26881
|
+
lengthRaisedPrice: recommended !== null && chargedBased !== null && recommended > chargedBased,
|
|
27069
26882
|
cutOffSamples,
|
|
27070
26883
|
exceedsStandCap,
|
|
27071
|
-
noRecommendationReason
|
|
27072
|
-
missingTerms
|
|
26884
|
+
noRecommendationReason
|
|
27073
26885
|
};
|
|
27074
26886
|
}
|
|
27075
|
-
var LLM_LENGTH_RAISED_LINE = "The
|
|
27076
|
-
var LLM_EXCEEDS_STAND_CAP_LINE = "No
|
|
27077
|
-
function benchCutOffLine(cutOff,
|
|
27078
|
-
return `No
|
|
26887
|
+
var LLM_LENGTH_RAISED_LINE = "The declared price also pays for how long the answer may be: at the charged-based price the answer would be cut off, so declare this one.";
|
|
26888
|
+
var LLM_EXCEEDS_STAND_CAP_LINE = "No price recommended: this answer is longer than one call on this stand may produce. Ask for a shorter answer \u2014 fewer fields, shorter strings, a length the prompt states \u2014 and benchmark again.";
|
|
26889
|
+
function benchCutOffLine(cutOff, maxCoins) {
|
|
26890
|
+
return `No price recommended: ${cutOff} sample${cutOff === 1 ? " was" : "s were"} cut off at this ceiling (--max-coins ${maxCoins}) before the answer was finished, so its real length is unknown. Re-run with a higher --max-coins.`;
|
|
27079
26891
|
}
|
|
27080
26892
|
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["requires_confirmation", "queued", "dispatching", "awaiting_external"]);
|
|
27081
26893
|
function rowState(row) {
|
|
@@ -27086,23 +26898,7 @@ function rowState(row) {
|
|
|
27086
26898
|
function rowHoldsSlot(row) {
|
|
27087
26899
|
if (typeof row.slotHeld === "boolean") return row.slotHeld;
|
|
27088
26900
|
const state = rowState(row);
|
|
27089
|
-
return state === "active" || state === "awaiting bill" && (
|
|
27090
|
-
}
|
|
27091
|
-
var UNIT_WORD = { credits: "credits", coin: "Nex" };
|
|
27092
|
-
function rowMoney(row) {
|
|
27093
|
-
const credits = row.currency === "credits" || row.currency === void 0 && typeof row.reservedCredits === "number" && typeof row.reservedCoins !== "number";
|
|
27094
|
-
if (credits) {
|
|
27095
|
-
return {
|
|
27096
|
-
unit: "credits",
|
|
27097
|
-
reserved: typeof row.reservedCredits === "number" ? row.reservedCredits : null,
|
|
27098
|
-
charged: typeof row.chargedCredits === "number" ? row.chargedCredits : null
|
|
27099
|
-
};
|
|
27100
|
-
}
|
|
27101
|
-
return {
|
|
27102
|
-
unit: "coin",
|
|
27103
|
-
reserved: typeof row.reservedCoins === "number" ? row.reservedCoins : null,
|
|
27104
|
-
charged: typeof row.chargedCoins === "number" ? row.chargedCoins : null
|
|
27105
|
-
};
|
|
26901
|
+
return state === "active" || state === "awaiting bill" && (row.reservedCoins ?? 0) > 0;
|
|
27106
26902
|
}
|
|
27107
26903
|
function providerRefusalStatus(error) {
|
|
27108
26904
|
const m = /^provider_http_(\d{3})$/.exec(error ?? "");
|
|
@@ -27121,7 +26917,7 @@ function formatAge(iso, now = Date.now()) {
|
|
|
27121
26917
|
}
|
|
27122
26918
|
async function runBench(args) {
|
|
27123
26919
|
const { apiUrl, token, projectId, cwd, opts, log } = args;
|
|
27124
|
-
const
|
|
26920
|
+
const maxCoins = opts.maxCoins;
|
|
27125
26921
|
const samples = opts.samples ?? DEFAULT_BENCH_SAMPLES;
|
|
27126
26922
|
const prompt = opts.benchPrompt.trim();
|
|
27127
26923
|
const auth = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
|
|
@@ -27143,7 +26939,7 @@ async function runBench(args) {
|
|
|
27143
26939
|
const lane = await readRuntimeLane(apiUrl, token);
|
|
27144
26940
|
if (lane.state !== "live" || !lane.models || lane.models.length === 0) {
|
|
27145
26941
|
if (lane.state === "off") {
|
|
27146
|
-
if (opts.json) writeJsonLine({ ...emptyBenchJson(apiUrl, projectId, prompt, outputFormat, samples,
|
|
26942
|
+
if (opts.json) writeJsonLine({ ...emptyBenchJson(apiUrl, projectId, prompt, outputFormat, samples, maxCoins, opts), status: "off", error: null });
|
|
27147
26943
|
else {
|
|
27148
26944
|
log.plain(LLM_LANE_OFF_LINE);
|
|
27149
26945
|
log.dim(" Nothing was spent.");
|
|
@@ -27170,27 +26966,17 @@ async function runBench(args) {
|
|
|
27170
26966
|
);
|
|
27171
26967
|
return;
|
|
27172
26968
|
}
|
|
27173
|
-
const
|
|
27174
|
-
if (walletBefore?.emailVerified === false) {
|
|
27175
|
-
benchFailed(opts, log, LLM_BENCH_UNVERIFIED_LINE);
|
|
27176
|
-
return;
|
|
27177
|
-
}
|
|
27178
|
-
if (walletBefore !== null && walletBefore.spendable < maxCredits) {
|
|
27179
|
-
benchFailed(opts, log, benchBalanceShortLine(walletBefore.spendable, maxCredits));
|
|
27180
|
-
return;
|
|
27181
|
-
}
|
|
27182
|
-
const balanceBefore = walletBefore?.spendable ?? null;
|
|
26969
|
+
const balanceBefore = await readCoinBalance(apiUrl, token);
|
|
27183
26970
|
if (!opts.json) {
|
|
27184
26971
|
log.plain(c.bold("genex llm bench"));
|
|
27185
26972
|
log.dim(` ${apiUrl}`);
|
|
27186
26973
|
log.plain("");
|
|
27187
26974
|
log.plain(` Model ${c.cyan(modelId)}`);
|
|
27188
26975
|
log.plain(` Samples ${samples} real attempt${samples === 1 ? "" : "s"}, ${outputFormat} output`);
|
|
27189
|
-
log.plain(` Approved ${
|
|
26976
|
+
log.plain(` Approved ${maxCoins} Nex per attempt \u2014 at worst ${maxCoins * samples} Nex for this run`);
|
|
27190
26977
|
log.plain(
|
|
27191
|
-
` Balance ${balanceBefore === null ? "couldn't be read" : `${balanceBefore}
|
|
26978
|
+
` Balance ${balanceBefore === null ? "couldn't be read" : `${balanceBefore} Nex spendable`}`
|
|
27192
26979
|
);
|
|
27193
|
-
log.dim(" Billed as used: each attempt is charged its real cost plus the platform fee, rounded up to a whole credit.");
|
|
27194
26980
|
log.plain("");
|
|
27195
26981
|
}
|
|
27196
26982
|
const base = `${apiUrl}/api/runtime/development/projects/${encodeURIComponent(projectId)}/generations`;
|
|
@@ -27216,7 +27002,7 @@ async function runBench(args) {
|
|
|
27216
27002
|
const res = await call3(base, {
|
|
27217
27003
|
method: "POST",
|
|
27218
27004
|
body: JSON.stringify({
|
|
27219
|
-
|
|
27005
|
+
maxCoins,
|
|
27220
27006
|
request: {
|
|
27221
27007
|
idempotencyKey: `bench-${randomUUID2()}`,
|
|
27222
27008
|
modelId,
|
|
@@ -27246,14 +27032,13 @@ async function runBench(args) {
|
|
|
27246
27032
|
if (refusedAt !== null) providerRefused++;
|
|
27247
27033
|
if (!opts.json) {
|
|
27248
27034
|
const length = row.outputTokens !== null ? ` \xB7 ${row.outputTokens}${row.maxOutputTokens !== null ? ` of ${row.maxOutputTokens}` : ""} tokens out` : "";
|
|
27249
|
-
const charged = row.chargedCredits === null ? "\u2014" : String(row.chargedCredits);
|
|
27250
27035
|
log.plain(
|
|
27251
|
-
` ${row.status === "succeeded" ? c.green("\u2713") : c.yellow("!")} sample ${i + 1} ${
|
|
27036
|
+
` ${row.status === "succeeded" ? c.green("\u2713") : c.yellow("!")} sample ${i + 1} ${String(row.chargedCoins ?? "\u2014").padStart(4)} Nex charged \xB7 provider ${usd(row.costUsd)}${length}${row.error ? ` \xB7 ${row.error}` : ""}`
|
|
27252
27037
|
);
|
|
27253
27038
|
if (refusedAt !== null) printProviderRefusal(log, refusedAt, row.providerMessage, row.status === "unknown");
|
|
27254
27039
|
if (row.error === PROVIDER_TOKEN_LIMIT) {
|
|
27255
27040
|
log.dim(
|
|
27256
|
-
row.fitExceedsCap === true ? " The answer was cut off at this stand's own output limit \u2014 no
|
|
27041
|
+
row.fitExceedsCap === true ? " The answer was cut off at this stand's own output limit \u2014 no price makes room for it; it is not a sample." : ` The answer was cut off at this ceiling (--max-coins ${maxCoins}) before it was finished \u2014 it was charged, and it is not a sample.`
|
|
27257
27042
|
);
|
|
27258
27043
|
}
|
|
27259
27044
|
}
|
|
@@ -27261,19 +27046,16 @@ async function runBench(args) {
|
|
|
27261
27046
|
} finally {
|
|
27262
27047
|
process.removeListener("SIGINT", onSigint);
|
|
27263
27048
|
}
|
|
27264
|
-
const
|
|
27265
|
-
|
|
27266
|
-
|
|
27267
|
-
|
|
27268
|
-
|
|
27269
|
-
const
|
|
27270
|
-
const settledCount = rec.charged.length;
|
|
27049
|
+
const headroomBps = lane.recommendedDeclaredHeadroomBps;
|
|
27050
|
+
const rec = benchRecommendation(results, headroomBps);
|
|
27051
|
+
const { charged, p50, p95, max } = rec;
|
|
27052
|
+
const settledCount = charged.length;
|
|
27053
|
+
const recommended = rec.recommendedEstimateCoins;
|
|
27054
|
+
const ceiling = rec.recommendedPerCallMaxCoins;
|
|
27271
27055
|
const lengthBlocked = rec.noRecommendationReason !== null;
|
|
27272
|
-
const balanceAfter =
|
|
27273
|
-
const picosOrNull = (v) => v === null ? null : String(v);
|
|
27056
|
+
const balanceAfter = await readCoinBalance(apiUrl, token);
|
|
27274
27057
|
const record = {
|
|
27275
|
-
v:
|
|
27276
|
-
currency: "credits",
|
|
27058
|
+
v: 1,
|
|
27277
27059
|
ranAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27278
27060
|
apiUrl,
|
|
27279
27061
|
projectId,
|
|
@@ -27282,34 +27064,28 @@ async function runBench(args) {
|
|
|
27282
27064
|
outputFormat,
|
|
27283
27065
|
samples,
|
|
27284
27066
|
completed: settledCount,
|
|
27285
|
-
|
|
27286
|
-
|
|
27287
|
-
|
|
27288
|
-
|
|
27289
|
-
|
|
27290
|
-
|
|
27067
|
+
chargedCoins: charged,
|
|
27068
|
+
p50,
|
|
27069
|
+
p95,
|
|
27070
|
+
max,
|
|
27071
|
+
recommendedDeclaredHeadroomBps: headroomBps,
|
|
27072
|
+
recommendedEstimateCoins: recommended,
|
|
27073
|
+
recommendedPerCallMaxCoins: ceiling,
|
|
27074
|
+
maxCoinsPerSample: maxCoins,
|
|
27075
|
+
fitCoins: rec.fits,
|
|
27291
27076
|
fitP95: rec.fitP95,
|
|
27292
|
-
|
|
27293
|
-
|
|
27294
|
-
feeBps: terms.feeBps,
|
|
27295
|
-
usdCentsPerCredit: terms.usdCentsPerCredit,
|
|
27296
|
-
costBasedMaxCredits: rec.costBasedMaxCredits,
|
|
27297
|
-
recommendedMaxCredits: rec.recommendedMaxCredits,
|
|
27298
|
-
recommendedPerCallMaxCredits: rec.recommendedPerCallMaxCredits,
|
|
27299
|
-
averageCreditsPerCall: rec.averageCreditsPerCall,
|
|
27300
|
-
recommendedPerCallEstimateCredits: rec.recommendedPerCallEstimateCredits,
|
|
27301
|
-
lengthRaisedCeiling: rec.lengthRaisedCeiling,
|
|
27077
|
+
chargedBasedEstimateCoins: rec.chargedBasedEstimateCoins,
|
|
27078
|
+
lengthRaisedPrice: rec.lengthRaisedPrice,
|
|
27302
27079
|
cutOffSamples: rec.cutOffSamples,
|
|
27303
27080
|
noRecommendationReason: rec.noRecommendationReason
|
|
27304
27081
|
};
|
|
27305
27082
|
const savedTo = await saveBench(cwd, record);
|
|
27306
27083
|
if (opts.json) {
|
|
27307
|
-
const ok =
|
|
27084
|
+
const ok = charged.length > 0 && !lengthBlocked;
|
|
27308
27085
|
writeJsonLine({
|
|
27309
27086
|
command: "llm bench",
|
|
27310
27087
|
status: ok ? "ok" : "failed",
|
|
27311
27088
|
error: ok ? null : rec.noRecommendationReason ?? "no_sample_settled",
|
|
27312
|
-
currency: "credits",
|
|
27313
27089
|
apiUrl,
|
|
27314
27090
|
projectId,
|
|
27315
27091
|
modelId,
|
|
@@ -27318,54 +27094,35 @@ async function runBench(args) {
|
|
|
27318
27094
|
schemaPath: opts.schemaPath ?? null,
|
|
27319
27095
|
samples,
|
|
27320
27096
|
completed: settledCount,
|
|
27321
|
-
|
|
27322
|
-
// Spendable credits before and after the run; null when unreadable.
|
|
27097
|
+
maxCoinsPerSample: maxCoins,
|
|
27323
27098
|
balanceBefore,
|
|
27324
27099
|
balanceAfter,
|
|
27325
27100
|
results,
|
|
27326
27101
|
// The create-time refusal that ended the run, or null. Beside the
|
|
27327
27102
|
// samples, never among them: no attempt existed and nothing was spent.
|
|
27328
27103
|
refusal: refusal2,
|
|
27329
|
-
|
|
27330
|
-
//
|
|
27331
|
-
costUsdPicos: record.cost,
|
|
27332
|
-
costUsd: {
|
|
27333
|
-
p50: rec.costP50 === null ? null : Number(rec.costP50) / 1e12,
|
|
27334
|
-
p95: rec.costP95 === null ? null : Number(rec.costP95) / 1e12,
|
|
27335
|
-
max: rec.costMax === null ? null : Number(rec.costMax) / 1e12
|
|
27336
|
-
},
|
|
27337
|
-
chargedCredits: record.charged,
|
|
27338
|
-
// The answer's LENGTH, priced by the server per sample (`fitCredits`,
|
|
27104
|
+
chargedCoins: { p50, p95, max },
|
|
27105
|
+
// The answer's LENGTH, priced by the server per sample (`fitCoins`,
|
|
27339
27106
|
// headroom already on the tokens), over the settled samples.
|
|
27340
|
-
|
|
27341
|
-
|
|
27342
|
-
|
|
27343
|
-
usdCentsPerCredit: terms.usdCentsPerCredit,
|
|
27344
|
-
missingTerms: rec.missingTerms,
|
|
27345
|
-
costBasedMaxCredits: rec.costBasedMaxCredits,
|
|
27346
|
-
lengthRaisedCeiling: rec.lengthRaisedCeiling,
|
|
27107
|
+
fitCoins: { p95: rec.fitP95, max: rec.fitMax },
|
|
27108
|
+
chargedBasedEstimateCoins: rec.chargedBasedEstimateCoins,
|
|
27109
|
+
lengthRaisedPrice: rec.lengthRaisedPrice,
|
|
27347
27110
|
cutOffSamples: rec.cutOffSamples,
|
|
27348
27111
|
exceedsStandCap: rec.exceedsStandCap,
|
|
27349
27112
|
noRecommendationReason: rec.noRecommendationReason,
|
|
27350
|
-
|
|
27351
|
-
|
|
27352
|
-
|
|
27353
|
-
recommendedPerCallEstimateCredits: rec.recommendedPerCallEstimateCredits,
|
|
27113
|
+
recommendedDeclaredHeadroomBps: headroomBps,
|
|
27114
|
+
recommendedEstimateCoins: recommended,
|
|
27115
|
+
recommendedPerCallMaxCoins: ceiling,
|
|
27354
27116
|
savedTo
|
|
27355
27117
|
});
|
|
27356
27118
|
if (!ok) process.exitCode = 1;
|
|
27357
27119
|
return;
|
|
27358
27120
|
}
|
|
27359
27121
|
log.plain("");
|
|
27360
|
-
const printMeasured = () => {
|
|
27361
|
-
log.plain(c.bold(" Real cost per call"));
|
|
27362
|
-
log.plain(` p50 ${usdFromPicos(rec.costP50)} p95 ${usdFromPicos(rec.costP95)} max ${usdFromPicos(rec.costMax)} (${settledCount} of ${samples} settled)`);
|
|
27363
|
-
log.plain(c.bold(" Charged credits"));
|
|
27364
|
-
log.plain(` p50 ${rec.chargedP50} p95 ${rec.chargedP95} max ${rec.chargedMax} (whole credits, rounded up per call)`);
|
|
27365
|
-
};
|
|
27366
27122
|
if (lengthBlocked) {
|
|
27367
|
-
if (
|
|
27368
|
-
|
|
27123
|
+
if (charged.length > 0) {
|
|
27124
|
+
log.plain(c.bold(" Charged Nex"));
|
|
27125
|
+
log.plain(` p50 ${p50} p95 ${p95} max ${max} (${charged.length} of ${samples} settled)`);
|
|
27369
27126
|
log.plain("");
|
|
27370
27127
|
}
|
|
27371
27128
|
printRecommendation(log, record);
|
|
@@ -27373,16 +27130,17 @@ async function runBench(args) {
|
|
|
27373
27130
|
process.exitCode = 1;
|
|
27374
27131
|
return;
|
|
27375
27132
|
}
|
|
27376
|
-
if (
|
|
27133
|
+
if (charged.length === 0) {
|
|
27377
27134
|
log.error(
|
|
27378
|
-
providerRefused > 0 && providerRefused === results.length ? " No sample ran \u2014 the provider refused every attempt at its door \u2014 so there is nothing to
|
|
27135
|
+
providerRefused > 0 && providerRefused === results.length ? " No sample ran \u2014 the provider refused every attempt at its door \u2014 so there is nothing to price from." : " No sample settled, so there is nothing to price from."
|
|
27379
27136
|
);
|
|
27380
27137
|
process.exitCode = 1;
|
|
27381
27138
|
return;
|
|
27382
27139
|
}
|
|
27383
|
-
|
|
27140
|
+
log.plain(c.bold(" Charged Nex"));
|
|
27141
|
+
log.plain(` p50 ${p50} p95 ${p95} max ${max} (${charged.length} of ${samples} settled)`);
|
|
27384
27142
|
if (balanceBefore !== null && balanceAfter !== null) {
|
|
27385
|
-
log.plain(` Balance ${balanceBefore} \u2192 ${balanceAfter}
|
|
27143
|
+
log.plain(` Balance ${balanceBefore} \u2192 ${balanceAfter} Nex spendable`);
|
|
27386
27144
|
}
|
|
27387
27145
|
log.plain("");
|
|
27388
27146
|
printRecommendation(log, record);
|
|
@@ -27394,58 +27152,43 @@ function printRecommendation(log, record) {
|
|
|
27394
27152
|
return;
|
|
27395
27153
|
}
|
|
27396
27154
|
if (record.noRecommendationReason === "answer_cut_off") {
|
|
27397
|
-
log.warn(` ${benchCutOffLine(record.cutOffSamples
|
|
27155
|
+
log.warn(` ${benchCutOffLine(record.cutOffSamples ?? 1, record.maxCoinsPerSample ?? 0)}`);
|
|
27398
27156
|
return;
|
|
27399
27157
|
}
|
|
27400
|
-
|
|
27401
|
-
|
|
27402
|
-
log.warn(" No ceiling recommended.");
|
|
27158
|
+
if (record.recommendedEstimateCoins === null || record.p95 === null) {
|
|
27159
|
+
log.warn(" No price recommended.");
|
|
27403
27160
|
log.dim(
|
|
27404
|
-
record.
|
|
27161
|
+
record.recommendedDeclaredHeadroomBps === null ? " This stand served no recommended headroom, and the multiplier is the server's to set \u2014" : " No sample settled, so there is no p95 to build a price on \u2014"
|
|
27405
27162
|
);
|
|
27406
27163
|
log.dim(" declaring a number from this run would be a guess dressed as a measurement.");
|
|
27407
|
-
printAverage(log, record, attempts);
|
|
27408
27164
|
return;
|
|
27409
27165
|
}
|
|
27410
|
-
log.plain(c.bold(` Declare
|
|
27411
|
-
if (record.
|
|
27166
|
+
log.plain(c.bold(` Declare estimateCoins: ${record.recommendedEstimateCoins}`));
|
|
27167
|
+
if (record.lengthRaisedPrice) {
|
|
27412
27168
|
log.plain(` ${LLM_LENGTH_RAISED_LINE}`);
|
|
27413
27169
|
log.dim(
|
|
27414
|
-
` = the smallest
|
|
27170
|
+
` = the smallest price whose answer allowance holds the answer plus this stand's headroom (p95 ${record.fitP95 ?? "\u2014"}), above the`
|
|
27415
27171
|
);
|
|
27416
27172
|
log.dim(
|
|
27417
|
-
`
|
|
27173
|
+
` charged-based ${record.chargedBasedEstimateCoins ?? "\u2014"} (p95 of charged Nex ${record.p95} plus ${record.recommendedDeclaredHeadroomBps} bps), over ${record.completed} settled attempt${record.completed === 1 ? "" : "s"} on ${record.modelId}.`
|
|
27418
27174
|
);
|
|
27419
27175
|
} else {
|
|
27420
27176
|
log.dim(
|
|
27421
|
-
` = p95
|
|
27177
|
+
` = p95 of charged Nex (${record.p95}) plus this stand's recommended headroom (${record.recommendedDeclaredHeadroomBps} bps),`
|
|
27422
27178
|
);
|
|
27423
|
-
log.dim(` measured over ${
|
|
27179
|
+
log.dim(` measured over ${record.completed} settled attempt${record.completed === 1 ? "" : "s"} on ${record.modelId}.`);
|
|
27424
27180
|
}
|
|
27425
|
-
log.dim(" That number is a
|
|
27426
|
-
|
|
27427
|
-
|
|
27428
|
-
log.plain(c.bold(` Grant perCallMaxCredits: ${record.recommendedPerCallMaxCredits}`));
|
|
27181
|
+
log.dim(" That number is a PRICE: a started attempt is charged in full, including one that fails.");
|
|
27182
|
+
if (record.recommendedPerCallMaxCoins != null) {
|
|
27183
|
+
log.plain(c.bold(` Grant perCallMaxCoins: ${record.recommendedPerCallMaxCoins}`));
|
|
27429
27184
|
log.dim(
|
|
27430
|
-
record.
|
|
27185
|
+
record.lengthRaisedPrice ? ` = the same headroom over the worst sample (${record.max}) or the longest answer's price, whichever is larger, and never below the price above it.` : ` = the same headroom over the worst sample (${record.max}), and never below the price above it.`
|
|
27431
27186
|
);
|
|
27432
|
-
|
|
27433
|
-
printAverage(log, record, attempts);
|
|
27434
|
-
}
|
|
27435
|
-
function printAverage(log, record, attempts) {
|
|
27436
|
-
if (record.averageCreditsPerCall === null) return;
|
|
27437
|
-
log.plain(` Per-call estimate: about ${formatCredits(record.averageCreditsPerCall)} credits per call`);
|
|
27438
|
-
log.dim(` = the average real cost with the platform fee, no headroom, over ${attempts}.`);
|
|
27439
|
-
if (record.recommendedPerCallEstimateCredits !== null) {
|
|
27440
|
-
log.plain(c.bold(` Grant perCallEstimateCredits: ${record.recommendedPerCallEstimateCredits}`));
|
|
27441
|
-
log.dim(` = that average rounded up to a whole credit. disclosure.estimatedCreditsPerPeriod = your calls per period \xD7 ${formatCredits(record.averageCreditsPerCall)}, rounded up.`);
|
|
27187
|
+
log.dim(" The ceiling is the price's room to be wrong; declaring one under the price is refused.");
|
|
27442
27188
|
}
|
|
27443
27189
|
}
|
|
27444
27190
|
async function explainBenchRefusal(res, call3, base, log, opts, index, settledSoFar) {
|
|
27445
|
-
if (printedStructuredError(res)) {
|
|
27446
|
-
const printed = await res.json().catch(() => ({}));
|
|
27447
|
-
return { stop: true, refusal: { status: res.status, error: typeof printed.error === "string" ? printed.error : null, slotsHeld: null, slotLimit: null } };
|
|
27448
|
-
}
|
|
27191
|
+
if (printedStructuredError(res)) return { stop: true, refusal: { status: res.status, error: null, slotsHeld: null, slotLimit: null } };
|
|
27449
27192
|
const body = await res.json().catch(() => ({}));
|
|
27450
27193
|
const refusal2 = { status: res.status, error: body.error ?? null, slotsHeld: null, slotLimit: null };
|
|
27451
27194
|
if (res.status === 429 && body.error === "generation_limit") {
|
|
@@ -27455,10 +27198,6 @@ async function explainBenchRefusal(res, call3, base, log, opts, index, settledSo
|
|
|
27455
27198
|
if (!opts.json) log.error(` Sample ${index + 1} ${benchSlotsHeldSentence(counts)}`);
|
|
27456
27199
|
return { stop: true, refusal: refusal2 };
|
|
27457
27200
|
}
|
|
27458
|
-
if (res.status === 403 && body.error === "credits_unverified") {
|
|
27459
|
-
if (!opts.json) log.error(` ${LLM_BENCH_UNVERIFIED_LINE}`);
|
|
27460
|
-
return { stop: true, refusal: refusal2 };
|
|
27461
|
-
}
|
|
27462
27201
|
if (opts.json) return { stop: res.status !== 429, refusal: refusal2 };
|
|
27463
27202
|
if (res.status === 404 && body.error === "not_found") {
|
|
27464
27203
|
log.error(` ${LLM_LANE_OFF_LINE}`);
|
|
@@ -27474,7 +27213,7 @@ async function explainBenchRefusal(res, call3, base, log, opts, index, settledSo
|
|
|
27474
27213
|
return { stop: true, refusal: refusal2 };
|
|
27475
27214
|
}
|
|
27476
27215
|
if (res.status === 402) {
|
|
27477
|
-
log.error(" Not enough
|
|
27216
|
+
log.error(" Not enough Nex to start the attempt.");
|
|
27478
27217
|
return { stop: true, refusal: refusal2 };
|
|
27479
27218
|
}
|
|
27480
27219
|
log.error(
|
|
@@ -27483,7 +27222,7 @@ async function explainBenchRefusal(res, call3, base, log, opts, index, settledSo
|
|
|
27483
27222
|
return { stop: res.status >= 500 || res.status === 401 || res.status === 403, refusal: refusal2 };
|
|
27484
27223
|
}
|
|
27485
27224
|
function printProviderRefusal(log, status2, message, awaitingBill = false) {
|
|
27486
|
-
if (awaitingBill) log.dim(` The provider answered ${status2} after routing \u2014 its bill is not final, so
|
|
27225
|
+
if (awaitingBill) log.dim(` The provider answered ${status2} after routing \u2014 its bill is not final, so this call's Nex stays held until it resolves (see \`genex llm status\`); it is not a sample.`);
|
|
27487
27226
|
else log.dim(" The provider refused this call at its door \u2014 no inference ran, it cost nothing, and it is not a sample.");
|
|
27488
27227
|
if (message) log.dim(` Provider said: ${message}`);
|
|
27489
27228
|
if (status2 === 401 || status2 === 403) {
|
|
@@ -27509,6 +27248,19 @@ async function pollSettled(call3, url, timeoutSec) {
|
|
|
27509
27248
|
}
|
|
27510
27249
|
return last;
|
|
27511
27250
|
}
|
|
27251
|
+
async function readCoinBalance(apiUrl, token) {
|
|
27252
|
+
try {
|
|
27253
|
+
const res = await apiFetch(`${apiUrl}/api/coin/balance`, {
|
|
27254
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
27255
|
+
signal: AbortSignal.timeout(6e3)
|
|
27256
|
+
});
|
|
27257
|
+
if (!res.ok) return null;
|
|
27258
|
+
const body = await res.json().catch(() => null);
|
|
27259
|
+
return typeof body?.spendable === "number" ? body.spendable : null;
|
|
27260
|
+
} catch {
|
|
27261
|
+
return null;
|
|
27262
|
+
}
|
|
27263
|
+
}
|
|
27512
27264
|
async function saveBench(cwd, record) {
|
|
27513
27265
|
try {
|
|
27514
27266
|
const file = path39.join(cwd, LLM_BENCH_FILE);
|
|
@@ -27524,10 +27276,9 @@ function benchFailed(opts, log, message) {
|
|
|
27524
27276
|
else log.error(message);
|
|
27525
27277
|
process.exitCode = 1;
|
|
27526
27278
|
}
|
|
27527
|
-
function emptyBenchJson(apiUrl, projectId, prompt, outputFormat, samples,
|
|
27279
|
+
function emptyBenchJson(apiUrl, projectId, prompt, outputFormat, samples, maxCoins, opts) {
|
|
27528
27280
|
return {
|
|
27529
27281
|
command: "llm bench",
|
|
27530
|
-
currency: "credits",
|
|
27531
27282
|
apiUrl,
|
|
27532
27283
|
projectId,
|
|
27533
27284
|
modelId: opts.modelId ?? null,
|
|
@@ -27536,28 +27287,21 @@ function emptyBenchJson(apiUrl, projectId, prompt, outputFormat, samples, maxCre
|
|
|
27536
27287
|
schemaPath: opts.schemaPath ?? null,
|
|
27537
27288
|
samples,
|
|
27538
27289
|
completed: 0,
|
|
27539
|
-
|
|
27290
|
+
maxCoinsPerSample: maxCoins,
|
|
27540
27291
|
balanceBefore: null,
|
|
27541
27292
|
balanceAfter: null,
|
|
27542
27293
|
results: [],
|
|
27543
27294
|
refusal: null,
|
|
27544
|
-
|
|
27545
|
-
|
|
27546
|
-
|
|
27547
|
-
|
|
27548
|
-
recommendedDeclaredHeadroomBps: null,
|
|
27549
|
-
feeBps: null,
|
|
27550
|
-
usdCentsPerCredit: null,
|
|
27551
|
-
missingTerms: null,
|
|
27552
|
-
costBasedMaxCredits: null,
|
|
27553
|
-
lengthRaisedCeiling: false,
|
|
27295
|
+
chargedCoins: { p50: null, p95: null, max: null },
|
|
27296
|
+
fitCoins: { p95: null, max: null },
|
|
27297
|
+
chargedBasedEstimateCoins: null,
|
|
27298
|
+
lengthRaisedPrice: false,
|
|
27554
27299
|
cutOffSamples: 0,
|
|
27555
27300
|
exceedsStandCap: false,
|
|
27556
27301
|
noRecommendationReason: null,
|
|
27557
|
-
|
|
27558
|
-
|
|
27559
|
-
|
|
27560
|
-
recommendedPerCallEstimateCredits: null,
|
|
27302
|
+
recommendedDeclaredHeadroomBps: null,
|
|
27303
|
+
recommendedEstimateCoins: null,
|
|
27304
|
+
recommendedPerCallMaxCoins: null,
|
|
27561
27305
|
savedTo: null
|
|
27562
27306
|
};
|
|
27563
27307
|
}
|
|
@@ -27620,8 +27364,7 @@ async function reportStatus(args) {
|
|
|
27620
27364
|
for (const row of rows) {
|
|
27621
27365
|
const state = rowState(row);
|
|
27622
27366
|
const mark = state === "active" ? c.cyan("\u25CF") : state === "awaiting bill" ? c.yellow("\u25D0") : c.green("\u25CB");
|
|
27623
|
-
const
|
|
27624
|
-
const reserved = money.reserved === null ? "reserved \u2014" : `${money.reserved} ${UNIT_WORD[money.unit]} reserved`;
|
|
27367
|
+
const reserved = typeof row.reservedCoins === "number" ? `${row.reservedCoins} Nex reserved` : "reserved \u2014";
|
|
27625
27368
|
log.plain(
|
|
27626
27369
|
` ${mark} ${row.id ?? "?"} ${row.modelId ?? "?"} ${state.padEnd(13)} ${formatAge(row.createdAt, now).padStart(4)} old ${reserved}${row.error ? ` ${row.error}` : ""}${rowHoldsSlot(row) ? "" : c.dim(" (no slot)")}`
|
|
27627
27370
|
);
|
|
@@ -27693,11 +27436,10 @@ async function cancelCall(args) {
|
|
|
27693
27436
|
if (!opts.json) {
|
|
27694
27437
|
log.warn(` ${row.id} (${row.status ?? "?"}) \u2014 ${sentence}.`);
|
|
27695
27438
|
if (state === "awaiting bill") {
|
|
27696
|
-
log.dim(` Nothing to cancel:
|
|
27439
|
+
log.dim(` Nothing to cancel: its Nex releases when the bill resolves, and it stops holding a slot ${PENDING_SLOT_RELEASE_MINUTES} minutes after dispatch.`);
|
|
27697
27440
|
if (row.providerMessage?.trim()) log.dim(` Provider said: ${row.providerMessage.trim()}`);
|
|
27698
27441
|
} else {
|
|
27699
|
-
|
|
27700
|
-
log.dim(` ${money.charged === null ? "charge unknown" : `${money.charged} ${UNIT_WORD[money.unit]} charged`}${row.error ? ` \xB7 ${row.error}` : ""}.`);
|
|
27442
|
+
log.dim(` ${typeof row.chargedCoins === "number" ? `${row.chargedCoins} Nex charged` : "charge unknown"}${row.error ? ` \xB7 ${row.error}` : ""}.`);
|
|
27701
27443
|
}
|
|
27702
27444
|
}
|
|
27703
27445
|
return done("ok", null, state === "awaiting bill" ? "awaiting_bill" : "already_final", row);
|
|
@@ -27719,126 +27461,78 @@ async function cancelCall(args) {
|
|
|
27719
27461
|
const after = await res.json().catch(() => null) ?? row;
|
|
27720
27462
|
if (!opts.json) {
|
|
27721
27463
|
log.success(` Cancelled ${row.id} (was ${row.status ?? "active"}).`);
|
|
27722
|
-
log.dim(`
|
|
27464
|
+
log.dim(` Its Nex releases once the bill settles; ${c.cyan("genex llm status")} shows it until then.`);
|
|
27723
27465
|
}
|
|
27724
27466
|
return done("ok", null, "cancelled", after);
|
|
27725
27467
|
}
|
|
27726
|
-
async function
|
|
27727
|
-
let
|
|
27468
|
+
async function reportSavedPrice(cwd, opts, log) {
|
|
27469
|
+
let record = null;
|
|
27728
27470
|
try {
|
|
27729
|
-
|
|
27471
|
+
record = JSON.parse(await fs39.readFile(path39.join(cwd, LLM_BENCH_FILE), "utf8"));
|
|
27730
27472
|
} catch {
|
|
27731
|
-
|
|
27473
|
+
record = null;
|
|
27732
27474
|
}
|
|
27733
|
-
if (!
|
|
27734
|
-
const v = parsed;
|
|
27735
|
-
if (v.v === 2 && v.currency === "credits" && v.cost && v.charged) return { kind: "credits", record: parsed };
|
|
27736
|
-
return { kind: "coins", record: parsed };
|
|
27737
|
-
}
|
|
27738
|
-
var LLM_PRICE_COIN_TERMS_LINE = "This bench was measured under the old Nex terms, when a game declared a fixed Nex price per call. In-game calls are now billed in the player's credits, as used, and a game declares a per-call CEILING (maxCredits) instead \u2014 a Nex price is not that number. Re-run the bench for the figures to declare.";
|
|
27739
|
-
async function reportSavedPrice(cwd, opts, log) {
|
|
27740
|
-
const saved = await readSavedBench(cwd);
|
|
27741
|
-
const rerun = 'genex llm bench "<prompt>" --max-credits <n> --user-approved';
|
|
27742
|
-
const creditKeys = (record2) => ({
|
|
27743
|
-
costUsdPicos: record2?.cost ?? { p50: null, p95: null, max: null },
|
|
27744
|
-
chargedCredits: record2?.charged ?? { p50: null, p95: null, max: null },
|
|
27745
|
-
fitP95: record2?.fitP95 ?? null,
|
|
27746
|
-
fitMax: record2?.fitMax ?? null,
|
|
27747
|
-
recommendedDeclaredHeadroomBps: record2?.recommendedDeclaredHeadroomBps ?? null,
|
|
27748
|
-
feeBps: record2?.feeBps ?? null,
|
|
27749
|
-
usdCentsPerCredit: record2?.usdCentsPerCredit ?? null,
|
|
27750
|
-
costBasedMaxCredits: record2?.costBasedMaxCredits ?? null,
|
|
27751
|
-
lengthRaisedCeiling: record2?.lengthRaisedCeiling ?? false,
|
|
27752
|
-
cutOffSamples: record2?.cutOffSamples ?? 0,
|
|
27753
|
-
noRecommendationReason: record2?.noRecommendationReason ?? null,
|
|
27754
|
-
recommendedMaxCredits: record2?.recommendedMaxCredits ?? null,
|
|
27755
|
-
recommendedPerCallMaxCredits: record2?.recommendedPerCallMaxCredits ?? null,
|
|
27756
|
-
averageCreditsPerCall: record2?.averageCreditsPerCall ?? null,
|
|
27757
|
-
recommendedPerCallEstimateCredits: record2?.recommendedPerCallEstimateCredits ?? null
|
|
27758
|
-
});
|
|
27759
|
-
if (!saved) {
|
|
27475
|
+
if (!record) {
|
|
27760
27476
|
if (opts.json) {
|
|
27761
27477
|
writeJsonLine({
|
|
27762
27478
|
command: "llm price",
|
|
27763
27479
|
status: "failed",
|
|
27764
27480
|
error: "no_bench",
|
|
27765
|
-
currency: null,
|
|
27766
27481
|
ranAt: null,
|
|
27767
27482
|
modelId: null,
|
|
27768
27483
|
samples: null,
|
|
27769
27484
|
completed: null,
|
|
27770
|
-
|
|
27771
|
-
|
|
27485
|
+
p50: null,
|
|
27486
|
+
p95: null,
|
|
27487
|
+
max: null,
|
|
27488
|
+
fitP95: null,
|
|
27489
|
+
chargedBasedEstimateCoins: null,
|
|
27490
|
+
lengthRaisedPrice: false,
|
|
27491
|
+
cutOffSamples: 0,
|
|
27492
|
+
noRecommendationReason: null,
|
|
27493
|
+
recommendedDeclaredHeadroomBps: null,
|
|
27494
|
+
recommendedEstimateCoins: null,
|
|
27495
|
+
recommendedPerCallMaxCoins: null,
|
|
27772
27496
|
savedTo: null
|
|
27773
27497
|
});
|
|
27774
27498
|
} else {
|
|
27775
27499
|
log.error("Nothing has been benchmarked in this folder yet.");
|
|
27776
|
-
log.dim(` Run ${c.cyan(
|
|
27500
|
+
log.dim(` Run ${c.cyan('genex llm bench "<prompt>" --max-coins <n> --user-approved')} first.`);
|
|
27777
27501
|
}
|
|
27778
27502
|
process.exitCode = 1;
|
|
27779
27503
|
return;
|
|
27780
27504
|
}
|
|
27781
|
-
if (saved.kind === "coins") {
|
|
27782
|
-
const old = saved.record;
|
|
27783
|
-
const legacy = {
|
|
27784
|
-
p50: old.p50 ?? null,
|
|
27785
|
-
p95: old.p95 ?? null,
|
|
27786
|
-
max: old.max ?? null,
|
|
27787
|
-
recommendedEstimateCoins: old.recommendedEstimateCoins ?? null,
|
|
27788
|
-
recommendedPerCallMaxCoins: old.recommendedPerCallMaxCoins ?? null,
|
|
27789
|
-
maxCoinsPerSample: old.maxCoinsPerSample ?? null,
|
|
27790
|
-
noRecommendationReason: old.noRecommendationReason ?? null
|
|
27791
|
-
};
|
|
27792
|
-
if (opts.json) {
|
|
27793
|
-
writeJsonLine({
|
|
27794
|
-
command: "llm price",
|
|
27795
|
-
status: "failed",
|
|
27796
|
-
error: "coin_terms",
|
|
27797
|
-
currency: "coins",
|
|
27798
|
-
ranAt: old.ranAt ?? null,
|
|
27799
|
-
modelId: old.modelId ?? null,
|
|
27800
|
-
samples: old.samples ?? null,
|
|
27801
|
-
completed: old.completed ?? null,
|
|
27802
|
-
...creditKeys(null),
|
|
27803
|
-
legacyCoinTerms: legacy,
|
|
27804
|
-
savedTo: LLM_BENCH_FILE
|
|
27805
|
-
});
|
|
27806
|
-
} else {
|
|
27807
|
-
log.plain(c.bold("genex llm price"));
|
|
27808
|
-
log.dim(` from ${LLM_BENCH_FILE}, benchmarked ${old.ranAt ?? "at an unknown time"}${old.modelId ? ` on ${old.modelId}` : ""}`);
|
|
27809
|
-
log.plain("");
|
|
27810
|
-
log.warn(` ${LLM_PRICE_COIN_TERMS_LINE}`);
|
|
27811
|
-
const measured = legacy.p95 === null ? "no settled sample" : `charged Nex p50 ${legacy.p50} \xB7 p95 ${legacy.p95} \xB7 max ${legacy.max}`;
|
|
27812
|
-
const recommended = legacy.recommendedEstimateCoins === null ? "no price recommended" : `it recommended estimateCoins ${legacy.recommendedEstimateCoins}${legacy.recommendedPerCallMaxCoins != null ? ` and perCallMaxCoins ${legacy.recommendedPerCallMaxCoins}` : ""}`;
|
|
27813
|
-
log.dim(` What it measured then: ${measured}; ${recommended}.`);
|
|
27814
|
-
log.dim(` Re-run: ${c.cyan(rerun)}`);
|
|
27815
|
-
}
|
|
27816
|
-
process.exitCode = 1;
|
|
27817
|
-
return;
|
|
27818
|
-
}
|
|
27819
|
-
const record = saved.record;
|
|
27820
27505
|
if (opts.json) {
|
|
27821
27506
|
writeJsonLine({
|
|
27822
27507
|
command: "llm price",
|
|
27823
|
-
status: record.
|
|
27824
|
-
error: record.
|
|
27825
|
-
currency: "credits",
|
|
27508
|
+
status: record.recommendedEstimateCoins === null ? "failed" : "ok",
|
|
27509
|
+
error: record.recommendedEstimateCoins === null ? record.noRecommendationReason ?? "no_recommendation" : null,
|
|
27826
27510
|
ranAt: record.ranAt ?? null,
|
|
27827
27511
|
modelId: record.modelId ?? null,
|
|
27828
27512
|
samples: record.samples ?? null,
|
|
27829
27513
|
completed: record.completed ?? null,
|
|
27830
|
-
|
|
27831
|
-
|
|
27514
|
+
p50: record.p50 ?? null,
|
|
27515
|
+
p95: record.p95 ?? null,
|
|
27516
|
+
max: record.max ?? null,
|
|
27517
|
+
// Absent from a file an older CLI wrote; null / false / 0 then, never missing.
|
|
27518
|
+
fitP95: record.fitP95 ?? null,
|
|
27519
|
+
chargedBasedEstimateCoins: record.chargedBasedEstimateCoins ?? null,
|
|
27520
|
+
lengthRaisedPrice: record.lengthRaisedPrice ?? false,
|
|
27521
|
+
cutOffSamples: record.cutOffSamples ?? 0,
|
|
27522
|
+
noRecommendationReason: record.noRecommendationReason ?? null,
|
|
27523
|
+
recommendedDeclaredHeadroomBps: record.recommendedDeclaredHeadroomBps ?? null,
|
|
27524
|
+
recommendedEstimateCoins: record.recommendedEstimateCoins ?? null,
|
|
27525
|
+
recommendedPerCallMaxCoins: record.recommendedPerCallMaxCoins ?? null,
|
|
27832
27526
|
savedTo: LLM_BENCH_FILE
|
|
27833
27527
|
});
|
|
27834
|
-
if (record.
|
|
27528
|
+
if (record.recommendedEstimateCoins === null) process.exitCode = 1;
|
|
27835
27529
|
return;
|
|
27836
27530
|
}
|
|
27837
27531
|
log.plain(c.bold("genex llm price"));
|
|
27838
27532
|
log.dim(` from ${LLM_BENCH_FILE}, benchmarked ${record.ranAt}`);
|
|
27839
27533
|
log.plain("");
|
|
27840
27534
|
printRecommendation(log, record);
|
|
27841
|
-
if (record.
|
|
27535
|
+
if (record.recommendedEstimateCoins === null) process.exitCode = 1;
|
|
27842
27536
|
}
|
|
27843
27537
|
function writeJsonLine(value) {
|
|
27844
27538
|
process.stdout.write(`${JSON.stringify(value)}
|
|
@@ -28098,7 +27792,7 @@ function runtimeRow(state, lane, toolsOnly = false) {
|
|
|
28098
27792
|
return {
|
|
28099
27793
|
label: "Runtime",
|
|
28100
27794
|
value: `in-game LLM lane live \xB7 ${n} model${n === 1 ? "" : "s"}${keyRefused ? ` \xB7 ${c.yellow("provider key refused")}` : ""}`,
|
|
28101
|
-
fix: keyRefused ? LLM_PROVIDER_KEY_REJECTED_LINE : toolsOnly ? convertFix : '
|
|
27795
|
+
fix: keyRefused ? LLM_PROVIDER_KEY_REJECTED_LINE : toolsOnly ? convertFix : 'Price a call before a game declares one: `npx genex llm bench "<prompt>" --max-coins <n> --user-approved`.'
|
|
28102
27796
|
};
|
|
28103
27797
|
}
|
|
28104
27798
|
case "off":
|
|
@@ -30022,14 +29716,6 @@ var INPUT_MIN = 64 * 1024;
|
|
|
30022
29716
|
var INPUT_MAX = 1024 * 1024;
|
|
30023
29717
|
var S_IFMT2 = 61440;
|
|
30024
29718
|
var S_IFLNK = 40960;
|
|
30025
|
-
var S_IFREG2 = 32768;
|
|
30026
|
-
var EXECUTE_BITS = 73;
|
|
30027
|
-
var PROGRAM_MODE = 33261;
|
|
30028
|
-
var FILE_MODE2 = 33188;
|
|
30029
|
-
var FOLDER_MODE = 16877;
|
|
30030
|
-
var PROGRAM_HEAD_BYTES = 4;
|
|
30031
|
-
var PROGRAM_MAGICS = /* @__PURE__ */ new Set([2135247942, 4277009102, 4277009103, 3472551422, 3489328638, 3405691582, 3199925962]);
|
|
30032
|
-
var SHEBANG = "#!";
|
|
30033
29719
|
var isDirectory = (entry) => entry.name.endsWith("/");
|
|
30034
29720
|
var isSymlink = (entry) => entry.mode !== null && (entry.mode & S_IFMT2) === S_IFLNK;
|
|
30035
29721
|
async function readAt2(file, position, length) {
|
|
@@ -30083,28 +29769,6 @@ function applyZip64(extra, entry, overflowed) {
|
|
|
30083
29769
|
at += 4 + length;
|
|
30084
29770
|
}
|
|
30085
29771
|
}
|
|
30086
|
-
function entryAt(data, at) {
|
|
30087
|
-
if (data.readUInt32LE(at) !== CENTRAL_SIGNATURE2) throw new Error("broken zip directory entry");
|
|
30088
|
-
const madeBy = data.readUInt16LE(at + 4);
|
|
30089
|
-
const nameLength = data.readUInt16LE(at + 28);
|
|
30090
|
-
const extraLength = data.readUInt16LE(at + 30);
|
|
30091
|
-
const commentLength = data.readUInt16LE(at + 32);
|
|
30092
|
-
const external = data.readUInt32LE(at + 38);
|
|
30093
|
-
const entry = {
|
|
30094
|
-
name: data.toString("utf8", at + 46, at + 46 + nameLength),
|
|
30095
|
-
method: data.readUInt16LE(at + 10),
|
|
30096
|
-
compressedSize: data.readUInt32LE(at + 20),
|
|
30097
|
-
uncompressedSize: data.readUInt32LE(at + 24),
|
|
30098
|
-
localOffset: data.readUInt32LE(at + 42),
|
|
30099
|
-
mode: madeBy >> 8 === UNIX_HOST2 ? external >>> 16 : null
|
|
30100
|
-
};
|
|
30101
|
-
applyZip64(data.subarray(at + 46 + nameLength, at + 46 + nameLength + extraLength), entry, {
|
|
30102
|
-
uncompressed: entry.uncompressedSize === U32_MAX,
|
|
30103
|
-
compressed: entry.compressedSize === U32_MAX,
|
|
30104
|
-
offset: entry.localOffset === U32_MAX
|
|
30105
|
-
});
|
|
30106
|
-
return { entry, madeBy, external, next: at + 46 + nameLength + extraLength + commentLength };
|
|
30107
|
-
}
|
|
30108
29772
|
async function readZipEntries(filePath) {
|
|
30109
29773
|
const file = await fs46.open(filePath, "r");
|
|
30110
29774
|
try {
|
|
@@ -30114,9 +29778,27 @@ async function readZipEntries(filePath) {
|
|
|
30114
29778
|
const entries = [];
|
|
30115
29779
|
let at = 0;
|
|
30116
29780
|
while (entries.length < directory.count && at + 46 <= data.length) {
|
|
30117
|
-
|
|
30118
|
-
|
|
30119
|
-
|
|
29781
|
+
if (data.readUInt32LE(at) !== CENTRAL_SIGNATURE2) throw new Error("broken zip directory entry");
|
|
29782
|
+
const madeBy = data.readUInt16LE(at + 4);
|
|
29783
|
+
const nameLength = data.readUInt16LE(at + 28);
|
|
29784
|
+
const extraLength = data.readUInt16LE(at + 30);
|
|
29785
|
+
const commentLength = data.readUInt16LE(at + 32);
|
|
29786
|
+
const external = data.readUInt32LE(at + 38);
|
|
29787
|
+
const entry = {
|
|
29788
|
+
name: data.toString("utf8", at + 46, at + 46 + nameLength),
|
|
29789
|
+
method: data.readUInt16LE(at + 10),
|
|
29790
|
+
compressedSize: data.readUInt32LE(at + 20),
|
|
29791
|
+
uncompressedSize: data.readUInt32LE(at + 24),
|
|
29792
|
+
localOffset: data.readUInt32LE(at + 42),
|
|
29793
|
+
mode: madeBy >> 8 === UNIX_HOST2 ? external >>> 16 : null
|
|
29794
|
+
};
|
|
29795
|
+
applyZip64(data.subarray(at + 46 + nameLength, at + 46 + nameLength + extraLength), entry, {
|
|
29796
|
+
uncompressed: entry.uncompressedSize === U32_MAX,
|
|
29797
|
+
compressed: entry.compressedSize === U32_MAX,
|
|
29798
|
+
offset: entry.localOffset === U32_MAX
|
|
29799
|
+
});
|
|
29800
|
+
entries.push(entry);
|
|
29801
|
+
at += 46 + nameLength + extraLength + commentLength;
|
|
30120
29802
|
}
|
|
30121
29803
|
if (entries.length !== directory.count) throw new Error("zip directory ends early");
|
|
30122
29804
|
return entries;
|
|
@@ -30127,58 +29809,13 @@ async function readZipEntries(filePath) {
|
|
|
30127
29809
|
async function readZipEntryHead(filePath, entry, maxBytes) {
|
|
30128
29810
|
const file = await fs46.open(filePath, "r");
|
|
30129
29811
|
try {
|
|
30130
|
-
|
|
30131
|
-
|
|
30132
|
-
|
|
30133
|
-
|
|
30134
|
-
}
|
|
30135
|
-
|
|
30136
|
-
|
|
30137
|
-
if (local.length < 30 || local.readUInt32LE(0) !== LOCAL_SIGNATURE2) throw new Error(`broken zip entry ${entry.name}`);
|
|
30138
|
-
const start = entry.localOffset + 30 + local.readUInt16LE(26) + local.readUInt16LE(28);
|
|
30139
|
-
if (entry.method === 0) return await readAt2(file, start, Math.min(maxBytes, entry.uncompressedSize));
|
|
30140
|
-
if (entry.method !== 8) throw new Error(`${entry.name} uses a compression this command cannot read`);
|
|
30141
|
-
const input = await readAt2(file, start, Math.min(entry.compressedSize, INPUT_MAX, Math.max(maxBytes, INPUT_MIN)));
|
|
30142
|
-
return await inflateHead(input, maxBytes);
|
|
30143
|
-
}
|
|
30144
|
-
function isProgramHead(head) {
|
|
30145
|
-
if (head.toString("latin1", 0, SHEBANG.length) === SHEBANG) return true;
|
|
30146
|
-
return head.length >= PROGRAM_HEAD_BYTES && PROGRAM_MAGICS.has(head.readUInt32BE(0));
|
|
30147
|
-
}
|
|
30148
|
-
function wantedMode(mode, directory, program) {
|
|
30149
|
-
if (directory) return mode ?? FOLDER_MODE;
|
|
30150
|
-
if (mode === null) return program ? PROGRAM_MODE : FILE_MODE2;
|
|
30151
|
-
const type = mode & S_IFMT2;
|
|
30152
|
-
const regular = type === S_IFREG2 || type === 0;
|
|
30153
|
-
if (!program || !regular) return mode;
|
|
30154
|
-
return (mode | S_IFREG2 | EXECUTE_BITS) >>> 0;
|
|
30155
|
-
}
|
|
30156
|
-
async function markPrograms(filePath) {
|
|
30157
|
-
const file = await fs46.open(filePath, "r+");
|
|
30158
|
-
try {
|
|
30159
|
-
const { size } = await file.stat();
|
|
30160
|
-
const directory = await findDirectory2(file, size);
|
|
30161
|
-
const data = await readAt2(file, directory.offset, directory.bytes);
|
|
30162
|
-
let marked = 0;
|
|
30163
|
-
let changed = false;
|
|
30164
|
-
let at = 0;
|
|
30165
|
-
for (let read = 0; read < directory.count && at + 46 <= data.length; read++) {
|
|
30166
|
-
const { entry, madeBy, external, next } = entryAt(data, at);
|
|
30167
|
-
const here = at;
|
|
30168
|
-
at = next;
|
|
30169
|
-
if (isSymlink(entry)) continue;
|
|
30170
|
-
const folder = isDirectory(entry);
|
|
30171
|
-
const head = folder ? Buffer.alloc(0) : await entryHead(file, entry, PROGRAM_HEAD_BYTES).catch(() => Buffer.alloc(0));
|
|
30172
|
-
const program = isProgramHead(head);
|
|
30173
|
-
const mode = wantedMode(entry.mode, folder, program);
|
|
30174
|
-
if (mode === entry.mode) continue;
|
|
30175
|
-
if (program) marked++;
|
|
30176
|
-
data.writeUInt16LE(UNIX_HOST2 << 8 | madeBy & 255, here + 4);
|
|
30177
|
-
data.writeUInt32LE((mode << 16 | external & 65535) >>> 0, here + 38);
|
|
30178
|
-
changed = true;
|
|
30179
|
-
}
|
|
30180
|
-
if (changed) await file.write(data, 0, data.length, directory.offset);
|
|
30181
|
-
return marked;
|
|
29812
|
+
const local = await readAt2(file, entry.localOffset, 30);
|
|
29813
|
+
if (local.length < 30 || local.readUInt32LE(0) !== LOCAL_SIGNATURE2) throw new Error(`broken zip entry ${entry.name}`);
|
|
29814
|
+
const start = entry.localOffset + 30 + local.readUInt16LE(26) + local.readUInt16LE(28);
|
|
29815
|
+
if (entry.method === 0) return await readAt2(file, start, Math.min(maxBytes, entry.uncompressedSize));
|
|
29816
|
+
if (entry.method !== 8) throw new Error(`${entry.name} uses a compression this command cannot read`);
|
|
29817
|
+
const input = await readAt2(file, start, Math.min(entry.compressedSize, INPUT_MAX, Math.max(maxBytes, INPUT_MIN)));
|
|
29818
|
+
return await inflateHead(input, maxBytes);
|
|
30182
29819
|
} finally {
|
|
30183
29820
|
await file.close();
|
|
30184
29821
|
}
|
|
@@ -30410,22 +30047,7 @@ async function packTarget(dir) {
|
|
|
30410
30047
|
if (apps.length === 1) return { target: path49.join(dir, apps[0].name), keepParent: true };
|
|
30411
30048
|
return { target: dir, keepParent: false };
|
|
30412
30049
|
}
|
|
30413
|
-
|
|
30414
|
-
async function zipFolder(target, keepParent, zipPath, opts) {
|
|
30415
|
-
const platform = opts.platform ?? process.platform;
|
|
30416
|
-
if (platform === "darwin") {
|
|
30417
|
-
await run4("ditto", ["-c", "-k", "--sequesterRsrc", ...keepParent ? ["--keepParent"] : [], target, zipPath]);
|
|
30418
|
-
return;
|
|
30419
|
-
}
|
|
30420
|
-
const cwd = keepParent ? path49.dirname(target) : target;
|
|
30421
|
-
if (platform === "win32") {
|
|
30422
|
-
const names2 = keepParent ? [path49.basename(target)] : await fs47.readdir(target);
|
|
30423
|
-
await run4(opts.tar ?? windowsTar(), ["-a", "-c", "-f", zipPath, ...names2], { cwd });
|
|
30424
|
-
return;
|
|
30425
|
-
}
|
|
30426
|
-
await run4("zip", ["-q", "-r", "-y", zipPath, keepParent ? path49.basename(target) : "."], { cwd });
|
|
30427
|
-
}
|
|
30428
|
-
async function packNativeBuild(input, opts = {}) {
|
|
30050
|
+
async function packNativeBuild(input) {
|
|
30429
30051
|
const stat = await fs47.stat(input).catch(() => null);
|
|
30430
30052
|
if (!stat) throw new NativeBuildError(`${input} does not exist.`);
|
|
30431
30053
|
if (stat.isFile()) {
|
|
@@ -30438,14 +30060,18 @@ async function packNativeBuild(input, opts = {}) {
|
|
|
30438
30060
|
const zipPath = path49.join(dir, `${path49.basename(target).replace(/[^A-Za-z0-9._-]+/g, "-") || "build"}.zip`);
|
|
30439
30061
|
const cleanup = () => fs47.rm(dir, { recursive: true, force: true });
|
|
30440
30062
|
try {
|
|
30441
|
-
|
|
30442
|
-
|
|
30063
|
+
if (process.platform === "darwin") {
|
|
30064
|
+
await run4("ditto", ["-c", "-k", "--sequesterRsrc", ...keepParent ? ["--keepParent"] : [], target, zipPath]);
|
|
30065
|
+
} else if (keepParent) {
|
|
30066
|
+
await run4("zip", ["-q", "-r", "-y", zipPath, path49.basename(target)], { cwd: path49.dirname(target) });
|
|
30067
|
+
} else {
|
|
30068
|
+
await run4("zip", ["-q", "-r", "-y", zipPath, "."], { cwd: target });
|
|
30069
|
+
}
|
|
30443
30070
|
} catch (err) {
|
|
30444
30071
|
await cleanup();
|
|
30445
30072
|
const missing = err.code === "ENOENT";
|
|
30446
|
-
const zipper = (opts.platform ?? process.platform) === "win32" ? "tar.exe" : "zip";
|
|
30447
30073
|
throw new NativeBuildError(
|
|
30448
|
-
missing ?
|
|
30074
|
+
missing ? "Couldn't zip the build: no `zip` on this machine. Zip it yourself (keeping links) and pass the .zip." : `Couldn't zip the build: ${err.message}`
|
|
30449
30075
|
);
|
|
30450
30076
|
}
|
|
30451
30077
|
return { zipPath, cleanup };
|
|
@@ -30568,8 +30194,7 @@ async function publishNativeBuild(ctx, body, log) {
|
|
|
30568
30194
|
async function listNativeBuilds(ctx, log) {
|
|
30569
30195
|
const res = await call(ctx, "/native-builds", { method: "GET" }, "list the builds", log);
|
|
30570
30196
|
if (!res) return null;
|
|
30571
|
-
|
|
30572
|
-
return { builds: body.builds, submissions: body.submissions ?? [] };
|
|
30197
|
+
return (await res.json()).builds;
|
|
30573
30198
|
}
|
|
30574
30199
|
async function removeNativeBuild(ctx, platform, arch, log) {
|
|
30575
30200
|
const route = `/native-builds/${encodeURIComponent(platform)}/${encodeURIComponent(arch)}`;
|
|
@@ -31031,17 +30656,6 @@ function describeLine(d) {
|
|
|
31031
30656
|
if (d.engine) parts.push(d.engine);
|
|
31032
30657
|
return parts.join(" \xB7 ");
|
|
31033
30658
|
}
|
|
31034
|
-
function reportPublished(build, log) {
|
|
31035
|
-
const pair = `${build.platform} ${build.arch}`;
|
|
31036
|
-
if (build.review === "pending") {
|
|
31037
|
-
log.success(`Sent for review \u2014 Genex desktop players on ${pair} get version ${build.version} once Genex approves it.`);
|
|
31038
|
-
} else if (build.review === "rejected") {
|
|
31039
|
-
const why = build.reviewReason ? `: ${build.reviewReason}` : ".";
|
|
31040
|
-
log.warn(`Version ${build.version} for ${pair} was turned down in review${why} Change the build and publish it again.`);
|
|
31041
|
-
} else {
|
|
31042
|
-
log.success(`Live \u2014 Genex desktop players on ${pair} now get version ${build.version}.`);
|
|
31043
|
-
}
|
|
31044
|
-
}
|
|
31045
30659
|
async function runPublish2(opts, log) {
|
|
31046
30660
|
if (refusedInHostedSession(log)) return false;
|
|
31047
30661
|
if (!opts.nativePath) {
|
|
@@ -31081,7 +30695,7 @@ async function runPublish2(opts, log) {
|
|
|
31081
30695
|
log
|
|
31082
30696
|
);
|
|
31083
30697
|
if (!build) return false;
|
|
31084
|
-
|
|
30698
|
+
log.success(`Live \u2014 Genex desktop players on ${platform} ${arch} now get version ${build.version}.`);
|
|
31085
30699
|
log.dim(` ${build.url}`);
|
|
31086
30700
|
const listing = opts.gallery ? await listGame(ctx, opts, log) : void 0;
|
|
31087
30701
|
if (opts.json) {
|
|
@@ -31120,25 +30734,20 @@ async function listGame(ctx, opts, log) {
|
|
|
31120
30734
|
function buildLine(b) {
|
|
31121
30735
|
return `${c.bold(`${b.platform} ${b.arch}`)} ${b.version} ${b.entry} ${sizeLine(b.sizeBytes)} ${b.publishedAt}`;
|
|
31122
30736
|
}
|
|
31123
|
-
function heldLine(b) {
|
|
31124
|
-
const state = b.review === "rejected" ? `turned down${b.reviewReason ? `: ${b.reviewReason}` : ""}` : "in review";
|
|
31125
|
-
return `${buildLine(b)} ${c.dim(state)}`;
|
|
31126
|
-
}
|
|
31127
30737
|
async function runList2(opts, log) {
|
|
31128
30738
|
const ctx = await context3(opts, log);
|
|
31129
30739
|
if (!ctx) return false;
|
|
31130
|
-
const
|
|
31131
|
-
if (!
|
|
30740
|
+
const builds = await listNativeBuilds(ctx, log);
|
|
30741
|
+
if (!builds) return false;
|
|
31132
30742
|
if (opts.json) {
|
|
31133
|
-
console.log(JSON.stringify(
|
|
30743
|
+
console.log(JSON.stringify({ builds }));
|
|
31134
30744
|
return true;
|
|
31135
30745
|
}
|
|
31136
|
-
if (
|
|
30746
|
+
if (builds.length === 0) {
|
|
31137
30747
|
log.plain("No native builds. Publish one with `genex native publish <path>`.");
|
|
31138
30748
|
return true;
|
|
31139
30749
|
}
|
|
31140
|
-
for (const b of
|
|
31141
|
-
for (const b of listed.submissions) log.plain(heldLine(b));
|
|
30750
|
+
for (const b of builds) log.plain(buildLine(b));
|
|
31142
30751
|
return true;
|
|
31143
30752
|
}
|
|
31144
30753
|
async function runRemove(opts, log) {
|
|
@@ -31342,75 +30951,6 @@ async function runNative(opts) {
|
|
|
31342
30951
|
if (!await run5(opts, log)) process.exitCode = 1;
|
|
31343
30952
|
}
|
|
31344
30953
|
|
|
31345
|
-
// src/commands/web.ts
|
|
31346
|
-
function loggerFor3(opts) {
|
|
31347
|
-
if (!opts.json) return createLogger({ quiet: opts.quiet });
|
|
31348
|
-
const err = (s) => {
|
|
31349
|
-
if (!opts.quiet) writeSafely(process.stderr, `${s}
|
|
31350
|
-
`);
|
|
31351
|
-
};
|
|
31352
|
-
return {
|
|
31353
|
-
info: (m) => err(`i ${m}`),
|
|
31354
|
-
success: (m) => err(`\u2713 ${m}`),
|
|
31355
|
-
warn: (m) => err(`! ${m}`),
|
|
31356
|
-
error: (m) => writeSafely(process.stderr, `\u2717 ${m}
|
|
31357
|
-
`),
|
|
31358
|
-
step: (m) => err(`\u203A ${m}`),
|
|
31359
|
-
dim: err,
|
|
31360
|
-
plain: err
|
|
31361
|
-
};
|
|
31362
|
-
}
|
|
31363
|
-
async function runRemove2(opts, log) {
|
|
31364
|
-
if (inHostedSession()) {
|
|
31365
|
-
log.error("`genex web remove` is not available inside a Genex chat session \u2014 it changes what players get.");
|
|
31366
|
-
return false;
|
|
31367
|
-
}
|
|
31368
|
-
if (!opts.yes) {
|
|
31369
|
-
log.warn("This removes the game's web version and keeps its Mac/Windows/Linux apps.");
|
|
31370
|
-
log.dim(" The game's page goes back to showing the apps, and its play address stops serving a web build.");
|
|
31371
|
-
log.dim(" Both web builds go: the live one and the preview. They cannot be restored; publish again to bring one back.");
|
|
31372
|
-
log.dim(" The apps, the gallery listing, likes and the cover are not touched.");
|
|
31373
|
-
log.dim(" Refused when the game has no app players get \u2014 unpublish or delete such a game instead.");
|
|
31374
|
-
log.dim(` Re-run with --yes to do it: ${c.cyan("npx genex web remove --yes")}`);
|
|
31375
|
-
return false;
|
|
31376
|
-
}
|
|
31377
|
-
const meta = await readProject();
|
|
31378
|
-
if (!meta) {
|
|
31379
|
-
log.error("No genex project here. Run `genex link <game>` in this directory first.");
|
|
31380
|
-
return false;
|
|
31381
|
-
}
|
|
31382
|
-
const apiUrl = opts.apiUrl ?? getApiUrl(meta.apiUrl);
|
|
31383
|
-
const token = opts.token ?? await readUserToken(opts.envPath, apiUrl);
|
|
31384
|
-
if (!token) {
|
|
31385
|
-
log.error("Not authorized. Run `genex link <your-game>` to sign in.");
|
|
31386
|
-
return false;
|
|
31387
|
-
}
|
|
31388
|
-
log.step("Removing the web version\u2026");
|
|
31389
|
-
const result = await removeWebVersion({ apiUrl, projectId: meta.id, token }, log);
|
|
31390
|
-
if (!result) return false;
|
|
31391
|
-
if ("refused" in result) {
|
|
31392
|
-
if (opts.json) console.log(JSON.stringify({ error: result.refused }));
|
|
31393
|
-
return false;
|
|
31394
|
-
}
|
|
31395
|
-
if (opts.json) console.log(JSON.stringify(result));
|
|
31396
|
-
if (result.removed) log.success("Removed the web version. The game's page shows its apps again.");
|
|
31397
|
-
else log.success("This game has no web version on Genex \u2014 nothing to remove.");
|
|
31398
|
-
return true;
|
|
31399
|
-
}
|
|
31400
|
-
var SUBCOMMANDS3 = {
|
|
31401
|
-
remove: runRemove2
|
|
31402
|
-
};
|
|
31403
|
-
async function runWeb(opts) {
|
|
31404
|
-
const log = loggerFor3(opts);
|
|
31405
|
-
const run5 = opts.name ? SUBCOMMANDS3[opts.name] : void 0;
|
|
31406
|
-
if (!run5) {
|
|
31407
|
-
log.error("Unknown web command. Use `genex web remove [--yes] [--json]`.");
|
|
31408
|
-
process.exitCode = 1;
|
|
31409
|
-
return;
|
|
31410
|
-
}
|
|
31411
|
-
if (!await run5(opts, log)) process.exitCode = 1;
|
|
31412
|
-
}
|
|
31413
|
-
|
|
31414
30954
|
// src/lib/costs.ts
|
|
31415
30955
|
var TYPICAL_CREDITS = {
|
|
31416
30956
|
model: 35,
|
|
@@ -31476,16 +31016,16 @@ ${c.bold("Usage")}
|
|
|
31476
31016
|
status | cancel. "models" says whether this
|
|
31477
31017
|
stand serves the runtime LLM lane at all, and
|
|
31478
31018
|
at what provider rates. "bench" runs the real
|
|
31479
|
-
model on YOUR OWN
|
|
31480
|
-
attempt
|
|
31019
|
+
model on YOUR OWN Nex and reports what each
|
|
31020
|
+
attempt was charged; it needs --max-coins <n>
|
|
31481
31021
|
--user-approved, like any spend the player has
|
|
31482
31022
|
to agree to. "price" reprints the last run's
|
|
31483
|
-
recommended
|
|
31023
|
+
recommended estimateCoins. "status" lists this
|
|
31484
31024
|
project's open calls and the slots they hold;
|
|
31485
31025
|
"cancel <id>" stops an active one. Declare a
|
|
31486
|
-
game's
|
|
31487
|
-
|
|
31488
|
-
|
|
31026
|
+
game's price from a bench, never from a guess:
|
|
31027
|
+
a started attempt is charged in full, including
|
|
31028
|
+
one that fails.
|
|
31489
31029
|
genex player <sub> [options] Answer the in-game model requests you approve, on your
|
|
31490
31030
|
OWN Claude or ChatGPT subscription: install | run |
|
|
31491
31031
|
status | stop | uninstall. "install" signs this machine
|
|
@@ -31523,9 +31063,6 @@ ${c.bold("Usage")}
|
|
|
31523
31063
|
genex promote [options] Make the previewed build live for players. No
|
|
31524
31064
|
rebuild \u2014 the exact build you previewed.
|
|
31525
31065
|
genex publish [options] Build + push + make live, then list it in the gallery.
|
|
31526
|
-
Both refuse to replace a page that shows only the
|
|
31527
|
-
game's Mac/Windows/Linux app unless given
|
|
31528
|
-
--replace-native-page.
|
|
31529
31066
|
genex accept Read the current Terms, Privacy and Content
|
|
31530
31067
|
policies and agree, so publishing is allowed.
|
|
31531
31068
|
Needs a real terminal: agreeing is a contract,
|
|
@@ -31541,9 +31078,6 @@ ${c.bold("Usage")}
|
|
|
31541
31078
|
for its platform and processor, live at once.
|
|
31542
31079
|
"build --platform windows --source <folder>" has
|
|
31543
31080
|
Genex build the Windows app of an Unreal project.
|
|
31544
|
-
genex web remove [--yes] [--json] Remove this game's web version and keep its
|
|
31545
|
-
Mac/Windows/Linux apps: the page shows the apps
|
|
31546
|
-
again. Refused for a game with no app.
|
|
31547
31081
|
genex cover [<file>] Show this game's cover and where it came from, or
|
|
31548
31082
|
make <file> (PNG/JPEG/WebP, \u22648 MB) the cover: one
|
|
31549
31083
|
real frame of the game \u2014 its signature moment,
|
|
@@ -31895,9 +31429,6 @@ ${c.bold("Options for `preview` / `publish`")}
|
|
|
31895
31429
|
--purchase-url <url> (operator) External purchase URL \u2014 marks the game a
|
|
31896
31430
|
paid-product demo ("Get \u2026" replaces Remix). Admin-only.
|
|
31897
31431
|
--product-name <name> (operator) Product name for the "Get {name}" button.
|
|
31898
|
-
--replace-native-page (publish, promote) Let this web build replace a page that
|
|
31899
|
-
shows only the game's Mac/Windows/Linux app. Use it ONLY when
|
|
31900
|
-
this folder really is the game's web version.
|
|
31901
31432
|
--api-url <url> (publish) Override the API base URL.
|
|
31902
31433
|
--env <path> Token env file (default: ~/.genex/env).
|
|
31903
31434
|
|
|
@@ -32000,7 +31531,7 @@ ${c.bold("Examples")}
|
|
|
32000
31531
|
genex items add sku_456
|
|
32001
31532
|
genex llm models
|
|
32002
31533
|
genex llm models --all
|
|
32003
|
-
genex llm bench "Reply with one short taunt." --samples 5 --max-
|
|
31534
|
+
genex llm bench "Reply with one short taunt." --samples 5 --max-coins 20 --user-approved
|
|
32004
31535
|
genex llm price
|
|
32005
31536
|
genex llm status
|
|
32006
31537
|
genex llm cancel <id>
|
|
@@ -32237,24 +31768,22 @@ ${c.bold("How the allowance works")}
|
|
|
32237
31768
|
|
|
32238
31769
|
Every number printed is live from your account; prices can change without a deploy.
|
|
32239
31770
|
`,
|
|
32240
|
-
llm: `${c.bold("genex llm")} \u2014 in-game model calls: is the lane live, what does one call cost, what
|
|
31771
|
+
llm: `${c.bold("genex llm")} \u2014 in-game model calls: is the lane live, what does one call cost, what should the game declare?
|
|
32241
31772
|
|
|
32242
31773
|
${c.bold("Usage")}
|
|
32243
31774
|
genex llm models [--all] [--json] Which models this stand serves, their provider rates per
|
|
32244
|
-
million tokens, the
|
|
32245
|
-
|
|
32246
|
-
|
|
32247
|
-
|
|
32248
|
-
|
|
32249
|
-
|
|
32250
|
-
|
|
32251
|
-
|
|
32252
|
-
|
|
32253
|
-
|
|
32254
|
-
|
|
32255
|
-
|
|
32256
|
-
allowance does not count. Needs a folder linked to a
|
|
32257
|
-
game you own, and a balance that covers one attempt.
|
|
31775
|
+
million tokens, and the headroom it recommends over a
|
|
31776
|
+
benchmark. The catalog is synced from OpenRouter, so the
|
|
31777
|
+
FEATURED short list is printed by default and --all
|
|
31778
|
+
prints every row (--json always carries every row). Off
|
|
31779
|
+
on this stand: it says so and exits 0 \u2014 build the feature
|
|
31780
|
+
without a model rather than promising a 404.
|
|
31781
|
+
genex llm bench "<prompt>" --max-coins <n> --user-approved [options]
|
|
31782
|
+
Run the real model <n> times through the development
|
|
31783
|
+
lane and report what each attempt was CHARGED. Refused
|
|
31784
|
+
without both flags, before any network call: this spends
|
|
31785
|
+
YOUR OWN COIN, a wallet the build's asset allowance
|
|
31786
|
+
cannot see. Needs a folder linked to a game you own.
|
|
32258
31787
|
genex llm price [--json] The recommendation from the last bench in this folder.
|
|
32259
31788
|
Reads a file; spends nothing.
|
|
32260
31789
|
genex llm status [--json] This project's open development calls: active, or
|
|
@@ -32271,32 +31800,29 @@ ${c.bold("Bench options")}
|
|
|
32271
31800
|
--samples <n> How many real attempts (default ${DEFAULT_BENCH_SAMPLES}). More samples, better p95.
|
|
32272
31801
|
--schema <file> A JSON file holding the output schema; implies JSON output.
|
|
32273
31802
|
--json-output Ask for JSON without a schema. --text Ask for text (the default).
|
|
32274
|
-
--max-
|
|
31803
|
+
--max-coins <n> The per-attempt ceiling YOU approved \u2014 a bench runs on your own Nex,
|
|
32275
31804
|
never a player's. The run's worst case is that number times --samples,
|
|
32276
|
-
and it is printed before anything starts.
|
|
32277
|
-
name, read as the same number.)
|
|
31805
|
+
and it is printed before anything starts.
|
|
32278
31806
|
--timeout <seconds> Bounds the wait for each attempt.
|
|
32279
31807
|
--json One machine-readable object; every key is always present, null when
|
|
32280
31808
|
the CLI could not learn it.
|
|
32281
31809
|
|
|
32282
31810
|
${c.bold("Why a benchmark and not an estimate")}
|
|
32283
|
-
|
|
32284
|
-
|
|
32285
|
-
|
|
32286
|
-
|
|
32287
|
-
|
|
32288
|
-
|
|
32289
|
-
|
|
32290
|
-
|
|
32291
|
-
|
|
32292
|
-
recommendation, because those multipliers are not the CLI's to invent.
|
|
31811
|
+
\`estimateCoins\` is a PRICE, not a guess: declaring it charges it, in full, even when the
|
|
31812
|
+
attempt fails, is cancelled, or stops at its budget. And three layers sit between the
|
|
31813
|
+
provider's rate card and that number \u2014 the provider's cost, the platform's tariff (already
|
|
31814
|
+
inside every charged Nex), and your headroom. The bench itself is billed to YOU, on the
|
|
31815
|
+
development lane; the player-funded lane is what the published game uses. Adding a margin
|
|
31816
|
+
to the provider's rate silently drops the tariff and under-prices every call, so the
|
|
31817
|
+
recommendation is built from CHARGED COINS and the headroom comes from the server. A stand
|
|
31818
|
+
that serves no headroom gets no recommendation, because the multiplier is not the CLI's to
|
|
31819
|
+
invent.
|
|
32293
31820
|
|
|
32294
|
-
|
|
32295
|
-
|
|
32296
|
-
A sample cut off at your --max-
|
|
32297
|
-
|
|
32298
|
-
|
|
32299
|
-
fee included, which is the honest estimate a budget's disclosure is built on.
|
|
31821
|
+
The declared price also decides how LONG the answer may be: it sizes each call's output
|
|
31822
|
+
allowance. So the recommendation is never below the smallest price that leaves room for
|
|
31823
|
+
the benchmarked answer, as the server computes it. A sample cut off at your --max-coins
|
|
31824
|
+
(provider_token_limit) is not a sample \u2014 re-run with a higher --max-coins; an answer longer
|
|
31825
|
+
than one call on this stand may produce gets no price at all \u2014 ask for a shorter one.
|
|
32300
31826
|
|
|
32301
31827
|
Results land in ${LLM_BENCH_FILE} \u2014 gitignored, machine-local, re-read by \`genex llm price\`.
|
|
32302
31828
|
`,
|
|
@@ -32385,24 +31911,6 @@ ${c.bold("Content filter")}
|
|
|
32385
31911
|
a body) as body horror \u2014 describe the object or machine instead: "a
|
|
32386
31912
|
statue-like figure threaded into the wall" passes where "cables entering her
|
|
32387
31913
|
spine" fails. Rejections are never retryable; reword and re-run.
|
|
32388
|
-
`,
|
|
32389
|
-
web: `${c.bold("genex web")} \u2014 this game's web version, beside its Mac/Windows/Linux apps.
|
|
32390
|
-
|
|
32391
|
-
${c.bold("Usage")}
|
|
32392
|
-
genex web remove Explain what removing the web version does, and stop.
|
|
32393
|
-
genex web remove --yes Remove it: both web builds (live and preview) go, and
|
|
32394
|
-
the game's page shows its apps again. The apps, the
|
|
32395
|
-
gallery listing, likes and the cover stay.
|
|
32396
|
-
--json One JSON object on stdout ({"removed": true, "web": false},
|
|
32397
|
-
or {"error": "<code>"} when refused).
|
|
32398
|
-
|
|
32399
|
-
${c.bold("Refused when")}
|
|
32400
|
-
The game has no app players get (a build waiting for review does not count): removing
|
|
32401
|
-
the web version would leave nothing to play \u2014 unpublish or delete the game instead.
|
|
32402
|
-
|
|
32403
|
-
${c.bold("Notes")}
|
|
32404
|
-
\`genex publish\` and \`genex promote\` never replace a page that shows only the apps
|
|
32405
|
-
unless given --replace-native-page.
|
|
32406
31914
|
`,
|
|
32407
31915
|
native: `${c.bold("genex native")} \u2014 a Unity, Godot or Unreal build players install from the Genex desktop app.
|
|
32408
31916
|
|
|
@@ -32584,13 +32092,11 @@ function parseArgs(argv) {
|
|
|
32584
32092
|
"--limit",
|
|
32585
32093
|
// `genex budget --assets <credits>` — the allowance the player approved.
|
|
32586
32094
|
"--assets",
|
|
32587
|
-
// `genex llm bench` value flags. `--max-
|
|
32588
|
-
// shape as `--assets
|
|
32589
|
-
// count a bench. `--max-coins` is its deprecated name (same number).
|
|
32095
|
+
// `genex llm bench` value flags. `--max-coins` is the COIN half of the
|
|
32096
|
+
// same approval shape as `--assets` (a different economy, its own gate).
|
|
32590
32097
|
"--model",
|
|
32591
32098
|
"--samples",
|
|
32592
32099
|
"--schema",
|
|
32593
|
-
"--max-credits",
|
|
32594
32100
|
"--max-coins",
|
|
32595
32101
|
// `genex player install --label <name>` — what this machine is called in
|
|
32596
32102
|
// the dashboard. Its own key rather than the generic 2nd positional,
|
|
@@ -32774,12 +32280,6 @@ function parseArgs(argv) {
|
|
|
32774
32280
|
case "--gallery":
|
|
32775
32281
|
parsed.options.gallery = true;
|
|
32776
32282
|
break;
|
|
32777
|
-
// `genex publish|promote --replace-native-page`: the owner's word that this web
|
|
32778
|
-
// build may replace a page that shows only the game's apps. Its own flag, never
|
|
32779
|
-
// a reuse of --yes/--force, because the flag must say what it consents to.
|
|
32780
|
-
case "--replace-native-page":
|
|
32781
|
-
parsed.options.replaceNativePage = true;
|
|
32782
|
-
break;
|
|
32783
32283
|
// A pair rather than one flag taking a value: `--resellable false` reads
|
|
32784
32284
|
// as a filename to every shell user who has ever mistyped it, and the two
|
|
32785
32285
|
// states have to be distinguishable from ABSENT so `shop set` can leave
|
|
@@ -33169,14 +32669,12 @@ function applyValueFlag(options, flag, value) {
|
|
|
33169
32669
|
options.samples = n;
|
|
33170
32670
|
break;
|
|
33171
32671
|
}
|
|
33172
|
-
case "--max-credits":
|
|
33173
32672
|
case "--max-coins": {
|
|
33174
32673
|
const n = Number(value);
|
|
33175
32674
|
if (!Number.isInteger(n) || n < 1) {
|
|
33176
|
-
throw new Error(`Invalid
|
|
32675
|
+
throw new Error(`Invalid --max-coins value: ${value} (whole coin, 1 or more)`);
|
|
33177
32676
|
}
|
|
33178
|
-
|
|
33179
|
-
else options.maxCoins = n;
|
|
32677
|
+
options.maxCoins = n;
|
|
33180
32678
|
break;
|
|
33181
32679
|
}
|
|
33182
32680
|
case "--timeout": {
|
|
@@ -33487,9 +32985,9 @@ async function main() {
|
|
|
33487
32985
|
await runBudget(parsed.options);
|
|
33488
32986
|
break;
|
|
33489
32987
|
// The in-game model lane: is it live here, what does one call really
|
|
33490
|
-
// cost, what
|
|
33491
|
-
//
|
|
33492
|
-
//
|
|
32988
|
+
// cost, what should the game declare. `bench` is the only CLI command
|
|
32989
|
+
// that spends COIN, so it carries its own --max-coins/--user-approved
|
|
32990
|
+
// gate — the credits allowance cannot see that wallet.
|
|
33493
32991
|
case "llm":
|
|
33494
32992
|
await runLlm(parsed.options);
|
|
33495
32993
|
break;
|
|
@@ -33605,9 +33103,6 @@ async function main() {
|
|
|
33605
33103
|
case "native":
|
|
33606
33104
|
await runNative(parsed.options);
|
|
33607
33105
|
break;
|
|
33608
|
-
case "web":
|
|
33609
|
-
await runWeb(parsed.options);
|
|
33610
|
-
break;
|
|
33611
33106
|
case "cover":
|
|
33612
33107
|
await runCover({ ...parsed.options, imagePath: parsed.options.name });
|
|
33613
33108
|
break;
|