@eddyskywalker/dsh-chatgpt-subscription 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +2 -0
- package/lib/client.js +190 -11
- package/lib/client.js.map +1 -1
- package/lib/index.js +570 -19
- package/lib/types/client/antigravity/AntigravitySection.d.ts.map +1 -1
- package/lib/types/client/antigravity/locales.d.ts +47 -2
- package/lib/types/client/antigravity/locales.d.ts.map +1 -1
- package/lib/types/client/kimi-code/locales.d.ts +2 -2
- package/lib/types/client/workbuddy/WorkBuddySection.d.ts +36 -0
- package/lib/types/client/workbuddy/WorkBuddySection.d.ts.map +1 -1
- package/lib/types/client/workbuddy/locales.d.ts +18 -0
- package/lib/types/client/workbuddy/locales.d.ts.map +1 -1
- package/lib/types/host/antigravity/account-pool.d.ts +24 -0
- package/lib/types/host/antigravity/account-pool.d.ts.map +1 -1
- package/lib/types/host/antigravity/adapter.d.ts.map +1 -1
- package/lib/types/host/antigravity/cache-stats.d.ts +166 -0
- package/lib/types/host/antigravity/cache-stats.d.ts.map +1 -0
- package/lib/types/host/antigravity/mapper.d.ts +75 -3
- package/lib/types/host/antigravity/mapper.d.ts.map +1 -1
- package/lib/types/host/antigravity/routes.d.ts +1 -1
- package/lib/types/host/antigravity/routes.d.ts.map +1 -1
- package/lib/types/host/workbuddy/client.d.ts +8 -0
- package/lib/types/host/workbuddy/client.d.ts.map +1 -1
- package/lib/types/host/workbuddy/model-catalog.d.ts +30 -1
- package/lib/types/host/workbuddy/model-catalog.d.ts.map +1 -1
- package/lib/types/host/workbuddy/routes.d.ts +7 -0
- package/lib/types/host/workbuddy/routes.d.ts.map +1 -1
- package/lib/types/shared/antigravity-contracts.d.ts +76 -0
- package/lib/types/shared/antigravity-contracts.d.ts.map +1 -1
- package/lib/types/shared/workbuddy-contracts.d.ts +42 -0
- package/lib/types/shared/workbuddy-contracts.d.ts.map +1 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -9812,14 +9812,80 @@ const OMITTED_IMAGE_TEXT$4 = "[image omitted to keep the request within its imag
|
|
|
9812
9812
|
* instruction, the conversation text, and the tool declarations.
|
|
9813
9813
|
*/
|
|
9814
9814
|
const MAX_REQUEST_IMAGE_BYTES$4 = 12 * 1024 * 1024;
|
|
9815
|
+
/**
|
|
9816
|
+
* Longest edge, in pixels, one image of this request may keep.
|
|
9817
|
+
*
|
|
9818
|
+
* CHOICE: 1024 px matches the long edge the other scaled routes send, and it is
|
|
9819
|
+
* what makes the scaling in {@link resolveRequestImages} effective rather than
|
|
9820
|
+
* nominal: the target is computed from the stored image's own dimensions, so the
|
|
9821
|
+
* same image yields the same request bytes in every later turn and the prefix
|
|
9822
|
+
* Google's implicit cache matches stays byte-stable.
|
|
9823
|
+
*
|
|
9824
|
+
* The budget stays Antigravity's own 12 MB. The 1.5 MB in
|
|
9825
|
+
* host/common/request-images.ts is Kimi's, and that file warns in the same
|
|
9826
|
+
* breath that importing one route's number into another refuses the other
|
|
9827
|
+
* route's own legal images; Google accepts a request of up to 20 MB.
|
|
9828
|
+
*/
|
|
9829
|
+
const REQUEST_IMAGE_MAX_EDGE$1 = 1024;
|
|
9830
|
+
/**
|
|
9831
|
+
* Encoded-byte target for one downscaled request version.
|
|
9832
|
+
*
|
|
9833
|
+
* 256 KiB of raw bytes is about 350 KiB of base64, so twenty of them still fit
|
|
9834
|
+
* the 12 MB budget with room left for the system instruction, the conversation
|
|
9835
|
+
* text, and the tool declarations that share the body. The harness keeps its
|
|
9836
|
+
* smallest output when no quality level meets the target.
|
|
9837
|
+
*/
|
|
9838
|
+
const REQUEST_IMAGE_VERSION_MAX_BYTES$2 = 256 * 1024;
|
|
9839
|
+
/**
|
|
9840
|
+
* Request-version target for one stored image whose long edge is over the
|
|
9841
|
+
* ceiling, or undefined when it already fits and is sent as stored.
|
|
9842
|
+
*
|
|
9843
|
+
* A reference without usable dimensions is sent as stored: there is no basis for
|
|
9844
|
+
* choosing a target, and the budget fallback in
|
|
9845
|
+
* {@link offloadOldestRequestImages} still applies to it.
|
|
9846
|
+
*/
|
|
9847
|
+
function requestImageTarget$1(ref, edge = REQUEST_IMAGE_MAX_EDGE$1) {
|
|
9848
|
+
const { width, height } = ref;
|
|
9849
|
+
if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height) || width <= 0 || height <= 0) return void 0;
|
|
9850
|
+
if (Math.max(width, height) <= edge) return void 0;
|
|
9851
|
+
return width >= height ? {
|
|
9852
|
+
width: edge,
|
|
9853
|
+
height: Math.max(1, Math.round(edge * height / width)),
|
|
9854
|
+
maxBytes: REQUEST_IMAGE_VERSION_MAX_BYTES$2
|
|
9855
|
+
} : {
|
|
9856
|
+
width: Math.max(1, Math.round(edge * width / height)),
|
|
9857
|
+
height: edge,
|
|
9858
|
+
maxBytes: REQUEST_IMAGE_VERSION_MAX_BYTES$2
|
|
9859
|
+
};
|
|
9860
|
+
}
|
|
9815
9861
|
/** Base64 length of raw image bytes, including padding. */
|
|
9816
9862
|
function base64Length$4(bytes) {
|
|
9817
9863
|
return Math.ceil(bytes / 3) * 4;
|
|
9818
9864
|
}
|
|
9819
|
-
/**
|
|
9865
|
+
/**
|
|
9866
|
+
* Base64 payload one image block will actually contribute to the wire, or
|
|
9867
|
+
* undefined when it contributes none.
|
|
9868
|
+
*
|
|
9869
|
+
* This is the length of what is SENT, which is not the length of what is
|
|
9870
|
+
* STORED. An image whose long edge is over {@link REQUEST_IMAGE_MAX_EDGE} is
|
|
9871
|
+
* sent as its downscaled request version (see {@link resolveRequestImages}), so
|
|
9872
|
+
* measuring `attachment.bytes` counts a 5 MB screenshot as 5 MB when it reaches
|
|
9873
|
+
* the wire as a few hundred kilobytes. That over-count is not harmless: it makes
|
|
9874
|
+
* {@link offloadOldestRequestImages} replace an image with placeholder text that
|
|
9875
|
+
* would have fitted once scaled, and every replacement rewrites the prefix
|
|
9876
|
+
* Google's implicit cache matches.
|
|
9877
|
+
*
|
|
9878
|
+
* The exact size is only known once the attachment service has derived the
|
|
9879
|
+
* version, which happens after this budget runs, so an image with a scale target
|
|
9880
|
+
* is measured at its target ceiling. An image without one travels as stored, and
|
|
9881
|
+
* there the stored size is exact.
|
|
9882
|
+
*/
|
|
9820
9883
|
function requestImageBytes$3(block) {
|
|
9821
9884
|
const attachment = attachmentOf$5(block);
|
|
9822
|
-
if (attachment)
|
|
9885
|
+
if (attachment) {
|
|
9886
|
+
const target = requestImageTarget$1(attachment);
|
|
9887
|
+
return target ? base64Length$4(Math.min(attachment.bytes, target.maxBytes)) : base64Length$4(attachment.bytes);
|
|
9888
|
+
}
|
|
9823
9889
|
const inline = asString$17(block.data) || asString$17(block.base64);
|
|
9824
9890
|
return inline ? inline.length : void 0;
|
|
9825
9891
|
}
|
|
@@ -9841,6 +9907,12 @@ function collectRequestImageBytes$3(content, lengths) {
|
|
|
9841
9907
|
* order them, and the placeholder tells the model the image is missing instead
|
|
9842
9908
|
* of letting it answer as though the picture were simply blank.
|
|
9843
9909
|
*
|
|
9910
|
+
* Each occurrence is measured at the size it will actually be sent at, so an
|
|
9911
|
+
* image that only fits after scaling is not replaced (see
|
|
9912
|
+
* {@link requestImageBytes}). Replacing an image is the LAST resort: it rewrites
|
|
9913
|
+
* the retained prefix and costs the next turn its whole implicit cache hit, so
|
|
9914
|
+
* it is worth keeping the image count stable for as long as the bytes allow.
|
|
9915
|
+
*
|
|
9844
9916
|
* @param options - the request about to be built; durable history stays untouched.
|
|
9845
9917
|
* @returns the original options when they already fit, otherwise shallow copies.
|
|
9846
9918
|
*/
|
|
@@ -9890,6 +9962,17 @@ function offloadOldestRequestImages$4(options) {
|
|
|
9890
9962
|
* model a text marker, because a turn that silently loses its image is far
|
|
9891
9963
|
* harder to diagnose than one that says so.
|
|
9892
9964
|
*
|
|
9965
|
+
* WHY SCALE INSTEAD OF DROP. `offloadOldestRequestImages` has to replace an
|
|
9966
|
+
* image with placeholder text once the request exceeds
|
|
9967
|
+
* {@link MAX_REQUEST_IMAGE_BYTES}, and the count it replaces grows with every
|
|
9968
|
+
* image added afterwards. Each change rewrites the retained prefix, so Google's
|
|
9969
|
+
* implicit cache misses again on the very next turn. An image whose long edge is
|
|
9970
|
+
* over {@link REQUEST_IMAGE_MAX_EDGE} is therefore sent as the harness's
|
|
9971
|
+
* downscaled request version, which keeps the image COUNT steady and with it the
|
|
9972
|
+
* prefix. The stored image and durable history do not change, and an image that
|
|
9973
|
+
* already fits is sent byte-for-byte as stored. The drop path stays as the last
|
|
9974
|
+
* resort for a request that is over budget even after scaling.
|
|
9975
|
+
*
|
|
9893
9976
|
* @param options - the exact request about to be built.
|
|
9894
9977
|
* @param attachments - durable attachment store; absent when the host wired none.
|
|
9895
9978
|
* @param signal - cancellation, forwarded to every attachment read.
|
|
@@ -9906,6 +9989,16 @@ async function resolveRequestImages$4(options, attachments, signal) {
|
|
|
9906
9989
|
return;
|
|
9907
9990
|
}
|
|
9908
9991
|
try {
|
|
9992
|
+
const target = requestImageTarget$1(ref);
|
|
9993
|
+
if (target !== void 0) {
|
|
9994
|
+
const version = await attachments.readImageRequest(ref, target, signal);
|
|
9995
|
+
resolved.set(attachmentId, Math.max(version.width, version.height) > 1024 ? { kind: "unavailable" } : {
|
|
9996
|
+
kind: "inline",
|
|
9997
|
+
mediaType: version.mediaType,
|
|
9998
|
+
data: Buffer.from(version.data).toString("base64")
|
|
9999
|
+
});
|
|
10000
|
+
return;
|
|
10001
|
+
}
|
|
9909
10002
|
const stored = await attachments.readImage(ref, signal);
|
|
9910
10003
|
resolved.set(attachmentId, {
|
|
9911
10004
|
kind: "inline",
|
|
@@ -10012,7 +10105,54 @@ function replayPart(part) {
|
|
|
10012
10105
|
if (typeof copy.text === "string") copy.text = sanitizeText$5(copy.text);
|
|
10013
10106
|
return copy;
|
|
10014
10107
|
}
|
|
10015
|
-
/**
|
|
10108
|
+
/**
|
|
10109
|
+
* Whether one part is nothing but visible text.
|
|
10110
|
+
*
|
|
10111
|
+
* A part carrying any second key keeps its own boundary. A signature, an opaque
|
|
10112
|
+
* provider field, `thought: true`, and a tool call all make the boundary around
|
|
10113
|
+
* them meaningful, and Google's implicit cache matches a byte-stable prefix, so
|
|
10114
|
+
* none of them may be crossed by a merge.
|
|
10115
|
+
*/
|
|
10116
|
+
function isPureTextPart(part) {
|
|
10117
|
+
return part !== void 0 && typeof part.text === "string" && Object.keys(part).length === 1;
|
|
10118
|
+
}
|
|
10119
|
+
/**
|
|
10120
|
+
* Collapse each run of adjacent pure-text parts into a single part.
|
|
10121
|
+
*
|
|
10122
|
+
* SSE transport chunks are not semantic content blocks. One answer arrives as
|
|
10123
|
+
* hundreds of text deltas, and replaying each delta as its own part adds one
|
|
10124
|
+
* part per delta to the request prefix on every later turn of the conversation.
|
|
10125
|
+
* Antigravity relies on Google's implicit cache, which matches a byte-stable
|
|
10126
|
+
* prefix: the moment the prefix gains a part the previous request did not have,
|
|
10127
|
+
* everything after it misses. The Claude route lost 68.84% cache hits to exactly
|
|
10128
|
+
* that growth before its deltas were merged; the Gemini route sends the same
|
|
10129
|
+
* deltas and needs the same treatment.
|
|
10130
|
+
*
|
|
10131
|
+
* Merging is restricted to pure text, so signed, opaque, thinking, and tool
|
|
10132
|
+
* parts keep their boundaries exactly where the model produced them.
|
|
10133
|
+
*
|
|
10134
|
+
* @param parts - replay parts in model order; the input is never mutated.
|
|
10135
|
+
* @returns parts with every adjacent pure-text run merged into its first member.
|
|
10136
|
+
*/
|
|
10137
|
+
function mergeAdjacentTextParts(parts) {
|
|
10138
|
+
const merged = [];
|
|
10139
|
+
for (const part of parts) {
|
|
10140
|
+
const previous = merged[merged.length - 1];
|
|
10141
|
+
if (isPureTextPart(part) && isPureTextPart(previous)) {
|
|
10142
|
+
previous.text = String(previous.text) + part.text;
|
|
10143
|
+
continue;
|
|
10144
|
+
}
|
|
10145
|
+
merged.push({ ...part });
|
|
10146
|
+
}
|
|
10147
|
+
return merged;
|
|
10148
|
+
}
|
|
10149
|
+
/**
|
|
10150
|
+
* Claude requires a signature on each thinking block, not a separate SSE part.
|
|
10151
|
+
*
|
|
10152
|
+
* The thinking/signature pairing below is Claude's wire contract alone: Vertex
|
|
10153
|
+
* Anthropic rejects a thinking block without its signature, while Gemini has no
|
|
10154
|
+
* such rule and must receive the thinking parts the model actually produced.
|
|
10155
|
+
*/
|
|
10016
10156
|
function claudeReplayParts(parts) {
|
|
10017
10157
|
const result = [];
|
|
10018
10158
|
let pending = "";
|
|
@@ -10040,14 +10180,9 @@ function claudeReplayParts(parts) {
|
|
|
10040
10180
|
continue;
|
|
10041
10181
|
}
|
|
10042
10182
|
pending = "";
|
|
10043
|
-
if (part.thought !== true) {
|
|
10044
|
-
const previous = result[result.length - 1];
|
|
10045
|
-
const pureText = (value) => value !== void 0 && typeof value.text === "string" && Object.keys(value).length === 1;
|
|
10046
|
-
if (pureText(part) && pureText(previous)) previous.text = String(previous.text) + part.text;
|
|
10047
|
-
else result.push({ ...part });
|
|
10048
|
-
}
|
|
10183
|
+
if (part.thought !== true) result.push({ ...part });
|
|
10049
10184
|
}
|
|
10050
|
-
return result;
|
|
10185
|
+
return mergeAdjacentTextParts(result);
|
|
10051
10186
|
}
|
|
10052
10187
|
function assistantParts(message, model, runtimeModel, toolCalls) {
|
|
10053
10188
|
const parts = [];
|
|
@@ -10096,7 +10231,7 @@ function assistantParts(message, model, runtimeModel, toolCalls) {
|
|
|
10096
10231
|
parts.push(...originalParts.filter((part) => !part.functionCall).map(replayPart));
|
|
10097
10232
|
}
|
|
10098
10233
|
}
|
|
10099
|
-
return runtimeModel.startsWith("claude-") ? claudeReplayParts(parts) : parts;
|
|
10234
|
+
return runtimeModel.startsWith("claude-") ? claudeReplayParts(parts) : mergeAdjacentTextParts(parts);
|
|
10100
10235
|
}
|
|
10101
10236
|
function pushToolResult(contents, result, toolCalls, model, runtimeModel, images = NO_RESOLVED_IMAGES$5) {
|
|
10102
10237
|
const toolCallId = String(result.toolCallId || "");
|
|
@@ -10444,14 +10579,276 @@ function closeStream$5(state) {
|
|
|
10444
10579
|
return out;
|
|
10445
10580
|
}
|
|
10446
10581
|
//#endregion
|
|
10582
|
+
//#region src/host/antigravity/cache-stats.ts
|
|
10583
|
+
const EMPTY_TOTALS = {
|
|
10584
|
+
requests: 0,
|
|
10585
|
+
cachedTokens: 0,
|
|
10586
|
+
freshTokens: 0
|
|
10587
|
+
};
|
|
10588
|
+
const totalsByScope = /* @__PURE__ */ new Map();
|
|
10589
|
+
const prefixesBySession = /* @__PURE__ */ new Map();
|
|
10590
|
+
const pendingBySession = /* @__PURE__ */ new Map();
|
|
10591
|
+
const missBySession = /* @__PURE__ */ new Map();
|
|
10592
|
+
/** When each session last SENT a request, for the idle reading a miss reports. */
|
|
10593
|
+
const requestAtBySession = /* @__PURE__ */ new Map();
|
|
10594
|
+
/**
|
|
10595
|
+
* Sessions the process is willing to remember before evicting the oldest.
|
|
10596
|
+
*
|
|
10597
|
+
* DSH runs several sessions and subagents through one host, but not hundreds at
|
|
10598
|
+
* once: eviction only begins once a host has served that many, and it drops the
|
|
10599
|
+
* least recently written key, which is a stale session by then. A purely
|
|
10600
|
+
* advisory feature must not be a leak.
|
|
10601
|
+
*/
|
|
10602
|
+
const MAX_TRACKED_SCOPES = 256;
|
|
10603
|
+
/**
|
|
10604
|
+
* Bucket key for a request that names no session.
|
|
10605
|
+
*
|
|
10606
|
+
* A one-shot request — a probe, a test, a caller that never had a conversation —
|
|
10607
|
+
* is still counted, but kept apart from every real session so it can never
|
|
10608
|
+
* inflate, or be mistaken for, one of their ratios.
|
|
10609
|
+
*/
|
|
10610
|
+
const UNSCOPED_KEY = "";
|
|
10611
|
+
/** The session a request belongs to, or the unscoped bucket when it names none. */
|
|
10612
|
+
function sessionScopeKey(sessionId) {
|
|
10613
|
+
return typeof sessionId === "string" && sessionId.trim() !== "" ? sessionId.trim() : UNSCOPED_KEY;
|
|
10614
|
+
}
|
|
10615
|
+
/**
|
|
10616
|
+
* Composite key separating one session's numbers from another's, and one
|
|
10617
|
+
* account's share of a session from another's.
|
|
10618
|
+
*
|
|
10619
|
+
* The account is part of the key because the pool can answer the same session
|
|
10620
|
+
* from different accounts, and each of those probably keeps its own cache
|
|
10621
|
+
* server-side. A shared entry would blend a cold account's misses into a warm
|
|
10622
|
+
* account's ratio, leaving a number that means nothing.
|
|
10623
|
+
*
|
|
10624
|
+
* That each account is its own cache namespace is INFERRED, not measured: no
|
|
10625
|
+
* account-scoped cache metric exists, and an account-scoped read is unobservable
|
|
10626
|
+
* from here. Splitting by account is the conservative reading of that assumption
|
|
10627
|
+
* — if it is wrong the two entries are merely reported apart, whereas merging
|
|
10628
|
+
* them would silently average a claim this line cannot check.
|
|
10629
|
+
*/
|
|
10630
|
+
function statsScopeKey(sessionId, accountId) {
|
|
10631
|
+
const session = sessionScopeKey(sessionId);
|
|
10632
|
+
if (session === UNSCOPED_KEY) return UNSCOPED_KEY;
|
|
10633
|
+
return accountId === void 0 || accountId === "" ? session : `${session} ${accountId}`;
|
|
10634
|
+
}
|
|
10635
|
+
/** Write a key, evicting the oldest entry once the bound is reached. */
|
|
10636
|
+
function rememberScoped$1(store, key, value) {
|
|
10637
|
+
store.delete(key);
|
|
10638
|
+
store.set(key, value);
|
|
10639
|
+
if (store.size <= MAX_TRACKED_SCOPES) return;
|
|
10640
|
+
const oldest = store.keys().next();
|
|
10641
|
+
if (oldest.done !== true) store.delete(oldest.value);
|
|
10642
|
+
}
|
|
10643
|
+
/**
|
|
10644
|
+
* A short, stable fingerprint of one prefix segment.
|
|
10645
|
+
*
|
|
10646
|
+
* Hashed rather than stored verbatim: these strings are the whole conversation
|
|
10647
|
+
* on a long turn, and a 32-bit FNV-1a keeps the diagnostic dependency-free.
|
|
10648
|
+
* Collisions only ever cost a missed attribution, never a wrong request.
|
|
10649
|
+
*/
|
|
10650
|
+
function fingerprint$1(text) {
|
|
10651
|
+
let hash = 2166136261;
|
|
10652
|
+
for (let index = 0; index < text.length; index++) {
|
|
10653
|
+
hash ^= text.charCodeAt(index);
|
|
10654
|
+
hash = Math.imul(hash, 16777619);
|
|
10655
|
+
}
|
|
10656
|
+
return (hash >>> 0).toString(16);
|
|
10657
|
+
}
|
|
10658
|
+
/**
|
|
10659
|
+
* One value's canonical text, with object keys in a fixed order.
|
|
10660
|
+
*
|
|
10661
|
+
* Key order is an artifact of how a value was assembled, not of what it means:
|
|
10662
|
+
* hashing `JSON.stringify` output verbatim would report a drift whenever two
|
|
10663
|
+
* structurally equal requests happened to build their objects in a different
|
|
10664
|
+
* order, and a reader cannot tell that false positive from a real one.
|
|
10665
|
+
*/
|
|
10666
|
+
function stableSerialize(value) {
|
|
10667
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
|
|
10668
|
+
if (Array.isArray(value)) return `[${value.map(stableSerialize).join(",")}]`;
|
|
10669
|
+
const record = value;
|
|
10670
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableSerialize(record[key])}`).join(",")}}`;
|
|
10671
|
+
}
|
|
10672
|
+
/** Hash one segment, or null when the request does not carry it at all. */
|
|
10673
|
+
function segmentHash(value) {
|
|
10674
|
+
return value === void 0 ? null : fingerprint$1(stableSerialize(value));
|
|
10675
|
+
}
|
|
10676
|
+
/** The `request` object of a serialized Antigravity body, when it has one. */
|
|
10677
|
+
function requestSegment(body) {
|
|
10678
|
+
try {
|
|
10679
|
+
const parsed = JSON.parse(body);
|
|
10680
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
10681
|
+
const request = parsed.request;
|
|
10682
|
+
return typeof request === "object" && request !== null ? request : void 0;
|
|
10683
|
+
} catch {
|
|
10684
|
+
return;
|
|
10685
|
+
}
|
|
10686
|
+
}
|
|
10687
|
+
/** Which segment changed, or that there was nothing to compare against. */
|
|
10688
|
+
function driftCause(previous, current) {
|
|
10689
|
+
if (previous === void 0) return "new-session";
|
|
10690
|
+
if (previous.sessionId !== current.sessionId) return "session-id";
|
|
10691
|
+
if (previous.contents !== current.contents) return "contents";
|
|
10692
|
+
if (previous.systemInstruction !== current.systemInstruction) return "systemInstruction";
|
|
10693
|
+
if (previous.tools !== current.tools) return "tools";
|
|
10694
|
+
return "none";
|
|
10695
|
+
}
|
|
10696
|
+
/**
|
|
10697
|
+
* Fingerprint the prefix of a request about to be sent, and attribute whatever
|
|
10698
|
+
* changed since this session's previous request.
|
|
10699
|
+
*
|
|
10700
|
+
* Called once per turn, before the request goes out, so the answer describes the
|
|
10701
|
+
* prefix the service will actually see. The reason is remembered for the session
|
|
10702
|
+
* as well, so the route can publish the attribution beside the ratio it explains.
|
|
10703
|
+
*
|
|
10704
|
+
* @param body - The serialized request body, exactly as it will be sent.
|
|
10705
|
+
* @param sessionId - Conversation the request belongs to, when it has one.
|
|
10706
|
+
* @returns The change attributed to this request.
|
|
10707
|
+
*/
|
|
10708
|
+
function recordCacheRequest(body, sessionId) {
|
|
10709
|
+
const scope = sessionScopeKey(sessionId);
|
|
10710
|
+
const request = requestSegment(body);
|
|
10711
|
+
const snapshot = {
|
|
10712
|
+
sessionId: typeof sessionId === "string" ? sessionId : "",
|
|
10713
|
+
contents: segmentHash(request?.contents),
|
|
10714
|
+
systemInstruction: segmentHash(request?.systemInstruction),
|
|
10715
|
+
tools: segmentHash(request?.tools)
|
|
10716
|
+
};
|
|
10717
|
+
const previous = prefixesBySession.get(scope);
|
|
10718
|
+
rememberScoped$1(prefixesBySession, scope, snapshot);
|
|
10719
|
+
const cause = driftCause(previous, snapshot);
|
|
10720
|
+
const previousRequestAt = requestAtBySession.get(scope);
|
|
10721
|
+
rememberScoped$1(requestAtBySession, scope, Date.now());
|
|
10722
|
+
const idleMs = previousRequestAt === void 0 ? void 0 : Math.max(0, Date.now() - previousRequestAt);
|
|
10723
|
+
rememberScoped$1(pendingBySession, scope, {
|
|
10724
|
+
cause,
|
|
10725
|
+
...idleMs === void 0 ? {} : { idleMs }
|
|
10726
|
+
});
|
|
10727
|
+
return cause;
|
|
10728
|
+
}
|
|
10729
|
+
/** A token count worth recording, or undefined when it is not a count at all. */
|
|
10730
|
+
function usableCount(value) {
|
|
10731
|
+
return Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
10732
|
+
}
|
|
10733
|
+
/**
|
|
10734
|
+
* Add one request's reported counts to its session's totals.
|
|
10735
|
+
*
|
|
10736
|
+
* The scope carries the account as well as the session, because a pooled line
|
|
10737
|
+
* can answer one conversation from several accounts and each of those keeps its
|
|
10738
|
+
* own cache server-side: one account's cold misses must not be averaged into
|
|
10739
|
+
* another's warm hits.
|
|
10740
|
+
*
|
|
10741
|
+
* @param sample - Token counts the response reported.
|
|
10742
|
+
* @param sessionId - Conversation the request belonged to.
|
|
10743
|
+
* @param accountId - Pooled account that served it, when the pool is in use.
|
|
10744
|
+
*/
|
|
10745
|
+
function recordCacheUsage(sample, sessionId, accountId) {
|
|
10746
|
+
const cached = usableCount(sample.cachedTokens);
|
|
10747
|
+
const fresh = usableCount(sample.freshTokens);
|
|
10748
|
+
if (cached === void 0 || fresh === void 0) return;
|
|
10749
|
+
const scope = statsScopeKey(sessionId, accountId);
|
|
10750
|
+
const previous = totalsByScope.get(scope) ?? EMPTY_TOTALS;
|
|
10751
|
+
rememberScoped$1(totalsByScope, scope, {
|
|
10752
|
+
requests: previous.requests + 1,
|
|
10753
|
+
cachedTokens: previous.cachedTokens + cached,
|
|
10754
|
+
freshTokens: previous.freshTokens + fresh
|
|
10755
|
+
});
|
|
10756
|
+
const session = sessionScopeKey(sessionId);
|
|
10757
|
+
const pending = pendingBySession.get(session);
|
|
10758
|
+
if (cached !== 0 || pending === void 0) return;
|
|
10759
|
+
rememberScoped$1(missBySession, session, {
|
|
10760
|
+
cause: pending.cause,
|
|
10761
|
+
...pending.idleMs === void 0 ? {} : { idleMs: pending.idleMs },
|
|
10762
|
+
at: Date.now()
|
|
10763
|
+
});
|
|
10764
|
+
}
|
|
10765
|
+
/** Add one scope's totals into an accumulator. */
|
|
10766
|
+
function accumulate$1(totals, stats) {
|
|
10767
|
+
totals.requests += stats.requests;
|
|
10768
|
+
totals.cachedTokens += stats.cachedTokens;
|
|
10769
|
+
totals.freshTokens += stats.freshTokens;
|
|
10770
|
+
}
|
|
10771
|
+
/**
|
|
10772
|
+
* Sum the scopes one question covers.
|
|
10773
|
+
*
|
|
10774
|
+
* The three cases are not a convenience: naming an account answers "is this
|
|
10775
|
+
* account's cache warm", naming only a session answers "how is this
|
|
10776
|
+
* conversation doing overall", and naming neither answers "how is this install
|
|
10777
|
+
* doing". Summing the stored scopes rather than keeping a second running total
|
|
10778
|
+
* means the aggregate can never drift out of step with the parts it summarises.
|
|
10779
|
+
*/
|
|
10780
|
+
function sumTotals(sessionId, accountId) {
|
|
10781
|
+
const totals = { ...EMPTY_TOTALS };
|
|
10782
|
+
if (sessionId === void 0) for (const stats of totalsByScope.values()) accumulate$1(totals, stats);
|
|
10783
|
+
else if (accountId !== void 0) accumulate$1(totals, totalsByScope.get(statsScopeKey(sessionId, accountId)) ?? EMPTY_TOTALS);
|
|
10784
|
+
else {
|
|
10785
|
+
const scope = sessionScopeKey(sessionId);
|
|
10786
|
+
const prefix = scope + " ";
|
|
10787
|
+
for (const [key, stats] of totalsByScope) if (key === scope || key.startsWith(prefix)) accumulate$1(totals, stats);
|
|
10788
|
+
}
|
|
10789
|
+
return totals;
|
|
10790
|
+
}
|
|
10791
|
+
/** The most recent miss for the requested scope, when one was recorded. */
|
|
10792
|
+
function missFor(sessionId) {
|
|
10793
|
+
if (sessionId === void 0) {
|
|
10794
|
+
let latest;
|
|
10795
|
+
for (const record of missBySession.values()) if (latest === void 0 || record.at >= latest.at) latest = record;
|
|
10796
|
+
return latest;
|
|
10797
|
+
}
|
|
10798
|
+
return missBySession.get(sessionScopeKey(sessionId));
|
|
10799
|
+
}
|
|
10800
|
+
/**
|
|
10801
|
+
* Cache totals for one account's share of a session, one whole session, or every
|
|
10802
|
+
* tracked session.
|
|
10803
|
+
*
|
|
10804
|
+
* A missing session is not an error: the settings card has no conversation in
|
|
10805
|
+
* hand, and the process aggregate is the honest answer there.
|
|
10806
|
+
*/
|
|
10807
|
+
function getCacheStats$1(sessionId, accountId) {
|
|
10808
|
+
const totals = sumTotals(sessionId, accountId);
|
|
10809
|
+
const prompt = totals.cachedTokens + totals.freshTokens;
|
|
10810
|
+
const miss = missFor(sessionId);
|
|
10811
|
+
return {
|
|
10812
|
+
...totals,
|
|
10813
|
+
hitRatio: prompt === 0 ? null : totals.cachedTokens / prompt,
|
|
10814
|
+
...miss === void 0 ? {} : { lastMiss: miss }
|
|
10815
|
+
};
|
|
10816
|
+
}
|
|
10817
|
+
//#endregion
|
|
10447
10818
|
//#region src/host/antigravity/account-pool.ts
|
|
10448
10819
|
function poolPath() {
|
|
10449
10820
|
return path.join(dshHomeDir(), "storages", "antigravity-pool.json");
|
|
10450
10821
|
}
|
|
10822
|
+
/**
|
|
10823
|
+
* The rotation strategy this line defaults to, and the reader for it.
|
|
10824
|
+
*
|
|
10825
|
+
* Sticky rather than the shared core's sequential default, because the account a
|
|
10826
|
+
* request is served by may also decide which server-side prefix cache it can
|
|
10827
|
+
* reach: each account is a separate quota domain, and an account that changes mid
|
|
10828
|
+
* conversation plausibly lands on a cache the previous one had not warmed, so the
|
|
10829
|
+
* next turn may reprocess a prefix that was already paid for. Holding the account
|
|
10830
|
+
* that served the previous request is the cheap way to not find out the hard way,
|
|
10831
|
+
* and a request that is rate limited still falls through to another account.
|
|
10832
|
+
*
|
|
10833
|
+
* That account identity selects a cache namespace is INFERRED, not measured.
|
|
10834
|
+
* Nothing here observes an account-scoped cache metric, no live capture has been
|
|
10835
|
+
* taken, and docs/antigravity-claude-cache-audit.md records the upstream position
|
|
10836
|
+
* — on-wire session affinity still needs transport diagnostics — against a run
|
|
10837
|
+
* that had a single account, so cross-account behaviour was never exercised.
|
|
10838
|
+
* The default stands as a design assumption until one of those is done.
|
|
10839
|
+
*
|
|
10840
|
+
* Only the DEFAULT differs. An explicit stored choice is returned unchanged, and
|
|
10841
|
+
* a pool file written before this change carries an explicit 'sequential' — so
|
|
10842
|
+
* upgrading does not silently move an existing user onto a different strategy.
|
|
10843
|
+
* The shared core keeps its own sequential default for every other line.
|
|
10844
|
+
*/
|
|
10845
|
+
function poolRotationStrategy(value) {
|
|
10846
|
+
return value === "sequential" || value === "round-robin" || value === "sticky" ? value : "sticky";
|
|
10847
|
+
}
|
|
10451
10848
|
function parseAntigravityPoolData(value) {
|
|
10452
10849
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("Antigravity pool payload is invalid");
|
|
10453
10850
|
const record = value;
|
|
10454
|
-
const strategy =
|
|
10851
|
+
const strategy = poolRotationStrategy(record.rotationStrategy);
|
|
10455
10852
|
const activeAccountId = typeof record.activeAccountId === "string" ? record.activeAccountId : void 0;
|
|
10456
10853
|
const rawAccounts = Array.isArray(record.accounts) ? record.accounts : [];
|
|
10457
10854
|
const accounts = [];
|
|
@@ -10680,6 +11077,25 @@ function resolveDefaultReasoningEffort$4(efforts, configuredEffort) {
|
|
|
10680
11077
|
if (efforts.includes("low")) return ReasoningEffortId("low");
|
|
10681
11078
|
if (efforts.length > 0) return ReasoningEffortId(efforts[0]);
|
|
10682
11079
|
}
|
|
11080
|
+
/**
|
|
11081
|
+
* Land one stream chunk's usage on its session's cache totals.
|
|
11082
|
+
*
|
|
11083
|
+
* The adapter is the only place that still knows the request's session and the
|
|
11084
|
+
* account that served it by the time the terminal chunk arrives: usage is
|
|
11085
|
+
* emitted from the mapper's stream state, which carries neither. Chunks of every
|
|
11086
|
+
* other type pass through untouched, and the mapper is left alone.
|
|
11087
|
+
*
|
|
11088
|
+
* The mapper counts cached and fresh prompt tokens separately
|
|
11089
|
+
* (`cacheReadTokens` / `inputTokens`), which is exactly the split these totals
|
|
11090
|
+
* are built from, so the numbers here are the ones the harness already reported.
|
|
11091
|
+
*/
|
|
11092
|
+
function recordUsageChunk(chunk, sessionId, accountId) {
|
|
11093
|
+
if (chunk.type !== "usage") return;
|
|
11094
|
+
recordCacheUsage({
|
|
11095
|
+
cachedTokens: chunk.usage.cacheReadTokens ?? 0,
|
|
11096
|
+
freshTokens: chunk.usage.inputTokens ?? 0
|
|
11097
|
+
}, sessionId, accountId);
|
|
11098
|
+
}
|
|
10683
11099
|
var AntigravityAdapter = class extends LlmAdapter {
|
|
10684
11100
|
store;
|
|
10685
11101
|
modelSettings;
|
|
@@ -10793,6 +11209,8 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
10793
11209
|
let recoveredMissingThinkingSignature = false;
|
|
10794
11210
|
const triedAccountIds = /* @__PURE__ */ new Set();
|
|
10795
11211
|
let response;
|
|
11212
|
+
let statsSessionId = options.sessionId === void 0 ? void 0 : String(options.sessionId);
|
|
11213
|
+
let statsAccountId;
|
|
10796
11214
|
while (true) {
|
|
10797
11215
|
let eff;
|
|
10798
11216
|
try {
|
|
@@ -10813,6 +11231,9 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
10813
11231
|
triedAccountIds.add(account.id);
|
|
10814
11232
|
for (const runtimeModel of candidates) {
|
|
10815
11233
|
const body = JSON.stringify(buildRequest$2(requestOptions, model, projectId, runtimeModel, effort, images));
|
|
11234
|
+
recordCacheRequest(body, requestOptions.sessionId);
|
|
11235
|
+
statsSessionId = requestOptions.sessionId === void 0 ? void 0 : String(requestOptions.sessionId);
|
|
11236
|
+
statsAccountId = account.id;
|
|
10816
11237
|
const headers = {
|
|
10817
11238
|
...antigravityHeaders(token),
|
|
10818
11239
|
...model.id.startsWith("claude-") ? { "anthropic-beta": "interleaved-thinking-2025-05-14" } : {}
|
|
@@ -10903,16 +11324,25 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
10903
11324
|
const trimmed = line.trim();
|
|
10904
11325
|
if (!trimmed) continue;
|
|
10905
11326
|
const chunks = processStreamLine$2(trimmed, state);
|
|
10906
|
-
for (const chunk of chunks)
|
|
11327
|
+
for (const chunk of chunks) {
|
|
11328
|
+
recordUsageChunk(chunk, statsSessionId, statsAccountId);
|
|
11329
|
+
yield chunk;
|
|
11330
|
+
}
|
|
10907
11331
|
if (state.finished) return;
|
|
10908
11332
|
}
|
|
10909
11333
|
}
|
|
10910
11334
|
buffer += decoder.decode();
|
|
10911
11335
|
if (buffer.trim()) {
|
|
10912
11336
|
const chunks = processStreamLine$2(buffer.trim(), state);
|
|
10913
|
-
for (const chunk of chunks)
|
|
11337
|
+
for (const chunk of chunks) {
|
|
11338
|
+
recordUsageChunk(chunk, statsSessionId, statsAccountId);
|
|
11339
|
+
yield chunk;
|
|
11340
|
+
}
|
|
11341
|
+
}
|
|
11342
|
+
for (const chunk of closeStream$5(state)) {
|
|
11343
|
+
recordUsageChunk(chunk, statsSessionId, statsAccountId);
|
|
11344
|
+
yield chunk;
|
|
10914
11345
|
}
|
|
10915
|
-
for (const chunk of closeStream$5(state)) yield chunk;
|
|
10916
11346
|
} finally {
|
|
10917
11347
|
reader.cancel().catch(() => void 0);
|
|
10918
11348
|
}
|
|
@@ -11004,7 +11434,7 @@ async function readRequestJson$7(request) {
|
|
|
11004
11434
|
request.on("error", reject);
|
|
11005
11435
|
});
|
|
11006
11436
|
}
|
|
11007
|
-
async function getAntigravityWebStatus(store, modelSettings, preferences, accountPool = new AccountPoolStore(void 0, void 0, store)) {
|
|
11437
|
+
async function getAntigravityWebStatus(store, modelSettings, preferences, accountPool = new AccountPoolStore(void 0, void 0, store), sessionId) {
|
|
11008
11438
|
const credentials = await store.read();
|
|
11009
11439
|
const settings = preferences ? preferences.status() : await modelSettings.read();
|
|
11010
11440
|
const quota = getCachedQuota();
|
|
@@ -11035,16 +11465,32 @@ async function getAntigravityWebStatus(store, modelSettings, preferences, accoun
|
|
|
11035
11465
|
defaultReasoningEffort: settings.defaultReasoningEffort || null,
|
|
11036
11466
|
accounts,
|
|
11037
11467
|
activeAccountId: poolData?.activeAccountId || activeAccount?.id,
|
|
11038
|
-
rotationStrategy: poolData?.rotationStrategy ||
|
|
11468
|
+
rotationStrategy: poolData?.rotationStrategy || poolRotationStrategy(void 0),
|
|
11469
|
+
cache: cacheStatsOrNull$1(sessionId)
|
|
11039
11470
|
};
|
|
11040
11471
|
}
|
|
11472
|
+
/**
|
|
11473
|
+
* Rolling cache totals for one session, or null while nothing has reported usage.
|
|
11474
|
+
*
|
|
11475
|
+
* The session is a query parameter because the numbers are per session: a host
|
|
11476
|
+
* running several conversations at once cannot present one merged hit ratio as
|
|
11477
|
+
* if it described any of them. With no session named the route still answers
|
|
11478
|
+
* with the process aggregate, which is the right answer for a settings card
|
|
11479
|
+
* that is not showing a specific conversation.
|
|
11480
|
+
*/
|
|
11481
|
+
function cacheStatsOrNull$1(sessionId) {
|
|
11482
|
+
const stats = getCacheStats$1(sessionId);
|
|
11483
|
+
if (stats.requests === 0) return null;
|
|
11484
|
+
return stats;
|
|
11485
|
+
}
|
|
11041
11486
|
function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetchFn = fetch, accountPool = new AccountPoolStore(void 0, void 0, store)) {
|
|
11042
11487
|
const quotaRefresh = new QuotaRefresh();
|
|
11043
11488
|
return ctx.webServer.register({
|
|
11044
11489
|
kind: "prefix",
|
|
11045
11490
|
path: "/antigravity/api",
|
|
11046
11491
|
handler: async (request, response) => {
|
|
11047
|
-
const
|
|
11492
|
+
const url = new URL(request.url || "/", "http://dsh.local");
|
|
11493
|
+
const path = url.pathname.replace(/^\/antigravity\/api\/?/, "");
|
|
11048
11494
|
try {
|
|
11049
11495
|
if (path === "status" || path === "") {
|
|
11050
11496
|
if (request.method !== "GET") return sendMethodNotAllowed$6(response);
|
|
@@ -11059,7 +11505,7 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetch
|
|
|
11059
11505
|
return sendJson$8(response, 200, {
|
|
11060
11506
|
ok: true,
|
|
11061
11507
|
value: {
|
|
11062
|
-
...await getAntigravityWebStatus(store, modelSettings, preferences, accountPool),
|
|
11508
|
+
...await getAntigravityWebStatus(store, modelSettings, preferences, accountPool, url.searchParams.get("sessionId") ?? void 0),
|
|
11063
11509
|
quotaRefreshing: quotaRefresh.refreshing
|
|
11064
11510
|
}
|
|
11065
11511
|
});
|
|
@@ -30217,6 +30663,25 @@ function refreshSourceForDomain(domain) {
|
|
|
30217
30663
|
}
|
|
30218
30664
|
//#endregion
|
|
30219
30665
|
//#region src/shared/workbuddy-contracts.ts
|
|
30666
|
+
/**
|
|
30667
|
+
* Parse one gateway \`credits\` string into the bare multiplier it names.
|
|
30668
|
+
*
|
|
30669
|
+
* Measured shapes: \`x6.67\` (intl), \`x0.79 credits\` (cn), and \`''\` for a model
|
|
30670
|
+
* whose publisher declares no rate. \`x0.00\` is deliberately kept — it is a real
|
|
30671
|
+
* declaration ("this model does not consume allowance"), not a missing value,
|
|
30672
|
+
* so it must not be confused with the empty string that means nothing is known.
|
|
30673
|
+
*
|
|
30674
|
+
* The return is \`null\` rather than \`0\` for anything unparseable, so a caller can
|
|
30675
|
+
* tell "no rate published" from "a rate of zero".
|
|
30676
|
+
*/
|
|
30677
|
+
function parseWorkBuddyCreditMultiplier(raw) {
|
|
30678
|
+
if (typeof raw !== "string") return null;
|
|
30679
|
+
const stripped = raw.replace(/\s*credits\s*$/i, "").trim().replace(/^[x×]/i, "").trim();
|
|
30680
|
+
if (stripped === "") return null;
|
|
30681
|
+
const value = Number(stripped);
|
|
30682
|
+
if (!Number.isFinite(value) || value < 0) return null;
|
|
30683
|
+
return stripped;
|
|
30684
|
+
}
|
|
30220
30685
|
/** Every level this route can name, in escalating order (the card's sort key). */
|
|
30221
30686
|
const WORKBUDDY_REASONING_EFFORTS = [
|
|
30222
30687
|
"minimal",
|
|
@@ -30390,6 +30855,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30390
30855
|
maxContextWindow: 56e3,
|
|
30391
30856
|
maxTokens: 24e3,
|
|
30392
30857
|
regions: ["cn"],
|
|
30858
|
+
credits: { cn: "2.00" },
|
|
30393
30859
|
supportsImage: false,
|
|
30394
30860
|
reasoningEfforts: [],
|
|
30395
30861
|
defaultReasoningEffort: null,
|
|
@@ -30403,6 +30869,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30403
30869
|
maxContextWindow: 2e5,
|
|
30404
30870
|
maxTokens: 32e3,
|
|
30405
30871
|
regions: ["intl"],
|
|
30872
|
+
credits: { intl: "0.34" },
|
|
30406
30873
|
supportsImage: true,
|
|
30407
30874
|
reasoningEfforts: [
|
|
30408
30875
|
"low",
|
|
@@ -30420,6 +30887,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30420
30887
|
maxContextWindow: 256e3,
|
|
30421
30888
|
maxTokens: 32e3,
|
|
30422
30889
|
regions: ["intl"],
|
|
30890
|
+
credits: { intl: "0.59" },
|
|
30423
30891
|
supportsImage: true,
|
|
30424
30892
|
reasoningEfforts: [
|
|
30425
30893
|
"low",
|
|
@@ -30437,6 +30905,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30437
30905
|
maxContextWindow: 272e3,
|
|
30438
30906
|
maxTokens: 72e3,
|
|
30439
30907
|
regions: ["intl"],
|
|
30908
|
+
credits: { intl: "3.31" },
|
|
30440
30909
|
supportsImage: true,
|
|
30441
30910
|
reasoningEfforts: [
|
|
30442
30911
|
"low",
|
|
@@ -30454,6 +30923,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30454
30923
|
maxContextWindow: 2e5,
|
|
30455
30924
|
maxTokens: 24e3,
|
|
30456
30925
|
regions: ["intl"],
|
|
30926
|
+
credits: { intl: "3.33" },
|
|
30457
30927
|
supportsImage: true,
|
|
30458
30928
|
reasoningEfforts: [],
|
|
30459
30929
|
defaultReasoningEffort: null,
|
|
@@ -30468,6 +30938,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30468
30938
|
maxContextWindow: 1e6,
|
|
30469
30939
|
maxTokens: 128e3,
|
|
30470
30940
|
regions: ["intl"],
|
|
30941
|
+
credits: { intl: "6.67" },
|
|
30471
30942
|
supportsImage: true,
|
|
30472
30943
|
reasoningEfforts: [
|
|
30473
30944
|
"low",
|
|
@@ -30487,6 +30958,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30487
30958
|
maxContextWindow: 1e6,
|
|
30488
30959
|
maxTokens: 128e3,
|
|
30489
30960
|
regions: ["intl"],
|
|
30961
|
+
credits: { intl: "3.47" },
|
|
30490
30962
|
supportsImage: true,
|
|
30491
30963
|
reasoningEfforts: [
|
|
30492
30964
|
"low",
|
|
@@ -30506,6 +30978,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30506
30978
|
maxContextWindow: 1e6,
|
|
30507
30979
|
maxTokens: 128e3,
|
|
30508
30980
|
regions: ["intl"],
|
|
30981
|
+
credits: { intl: "1.39" },
|
|
30509
30982
|
supportsImage: true,
|
|
30510
30983
|
reasoningEfforts: [
|
|
30511
30984
|
"low",
|
|
@@ -30525,6 +30998,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30525
30998
|
maxContextWindow: 1e6,
|
|
30526
30999
|
maxTokens: 128e3,
|
|
30527
31000
|
regions: ["intl"],
|
|
31001
|
+
credits: { intl: "0.14" },
|
|
30528
31002
|
supportsImage: true,
|
|
30529
31003
|
reasoningEfforts: [
|
|
30530
31004
|
"low",
|
|
@@ -30544,6 +31018,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30544
31018
|
maxContextWindow: 1e6,
|
|
30545
31019
|
maxTokens: 128e3,
|
|
30546
31020
|
regions: ["intl"],
|
|
31021
|
+
credits: { intl: "3.31" },
|
|
30547
31022
|
supportsImage: true,
|
|
30548
31023
|
reasoningEfforts: [
|
|
30549
31024
|
"low",
|
|
@@ -30562,6 +31037,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30562
31037
|
maxContextWindow: 272e3,
|
|
30563
31038
|
maxTokens: 72e3,
|
|
30564
31039
|
regions: ["intl"],
|
|
31040
|
+
credits: { intl: "1.65" },
|
|
30565
31041
|
supportsImage: true,
|
|
30566
31042
|
reasoningEfforts: [
|
|
30567
31043
|
"low",
|
|
@@ -30580,6 +31056,10 @@ const FALLBACK_MODELS$1 = [
|
|
|
30580
31056
|
maxContextWindow: 1e6,
|
|
30581
31057
|
maxTokens: 48e3,
|
|
30582
31058
|
regions: ["intl", "cn"],
|
|
31059
|
+
credits: {
|
|
31060
|
+
cn: "0.79",
|
|
31061
|
+
intl: "0.79"
|
|
31062
|
+
},
|
|
30583
31063
|
supportsImage: true,
|
|
30584
31064
|
reasoningEfforts: [
|
|
30585
31065
|
"low",
|
|
@@ -30597,6 +31077,10 @@ const FALLBACK_MODELS$1 = [
|
|
|
30597
31077
|
maxContextWindow: 1e6,
|
|
30598
31078
|
maxTokens: 32e3,
|
|
30599
31079
|
regions: ["intl", "cn"],
|
|
31080
|
+
credits: {
|
|
31081
|
+
cn: "0.06",
|
|
31082
|
+
intl: "0.06"
|
|
31083
|
+
},
|
|
30600
31084
|
supportsImage: true,
|
|
30601
31085
|
reasoningEfforts: [
|
|
30602
31086
|
"low",
|
|
@@ -30614,6 +31098,10 @@ const FALLBACK_MODELS$1 = [
|
|
|
30614
31098
|
maxContextWindow: 1e6,
|
|
30615
31099
|
maxTokens: 48e3,
|
|
30616
31100
|
regions: ["intl", "cn"],
|
|
31101
|
+
credits: {
|
|
31102
|
+
cn: "0.79",
|
|
31103
|
+
intl: "0.79"
|
|
31104
|
+
},
|
|
30617
31105
|
supportsImage: true,
|
|
30618
31106
|
reasoningEfforts: ["high", "xhigh"],
|
|
30619
31107
|
defaultReasoningEffort: "high",
|
|
@@ -30627,6 +31115,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30627
31115
|
maxContextWindow: 2e5,
|
|
30628
31116
|
maxTokens: 48e3,
|
|
30629
31117
|
regions: ["cn"],
|
|
31118
|
+
credits: { cn: "0.79" },
|
|
30630
31119
|
supportsImage: true,
|
|
30631
31120
|
reasoningEfforts: [
|
|
30632
31121
|
"low",
|
|
@@ -30644,6 +31133,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30644
31133
|
maxContextWindow: 2e5,
|
|
30645
31134
|
maxTokens: 64e3,
|
|
30646
31135
|
regions: ["cn"],
|
|
31136
|
+
credits: { cn: "0.71" },
|
|
30647
31137
|
supportsImage: true,
|
|
30648
31138
|
reasoningEfforts: [
|
|
30649
31139
|
"low",
|
|
@@ -30661,6 +31151,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30661
31151
|
maxContextWindow: 2e5,
|
|
30662
31152
|
maxTokens: 48e3,
|
|
30663
31153
|
regions: ["cn"],
|
|
31154
|
+
credits: { cn: "0.80" },
|
|
30664
31155
|
supportsImage: false,
|
|
30665
31156
|
reasoningEfforts: [],
|
|
30666
31157
|
defaultReasoningEffort: null,
|
|
@@ -30674,6 +31165,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30674
31165
|
maxContextWindow: 2e5,
|
|
30675
31166
|
maxTokens: 48e3,
|
|
30676
31167
|
regions: ["cn"],
|
|
31168
|
+
credits: { cn: "0.95" },
|
|
30677
31169
|
supportsImage: false,
|
|
30678
31170
|
reasoningEfforts: [
|
|
30679
31171
|
"low",
|
|
@@ -30691,6 +31183,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30691
31183
|
maxContextWindow: 2e5,
|
|
30692
31184
|
maxTokens: 48e3,
|
|
30693
31185
|
regions: ["cn"],
|
|
31186
|
+
credits: { cn: "0.23" },
|
|
30694
31187
|
supportsImage: false,
|
|
30695
31188
|
reasoningEfforts: [],
|
|
30696
31189
|
defaultReasoningEffort: null,
|
|
@@ -30704,6 +31197,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30704
31197
|
maxContextWindow: 128e3,
|
|
30705
31198
|
maxTokens: 32e3,
|
|
30706
31199
|
regions: ["cn"],
|
|
31200
|
+
credits: { cn: "0.11" },
|
|
30707
31201
|
supportsImage: true,
|
|
30708
31202
|
reasoningEfforts: [
|
|
30709
31203
|
"low",
|
|
@@ -30721,6 +31215,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30721
31215
|
maxContextWindow: 168e3,
|
|
30722
31216
|
maxTokens: 32e3,
|
|
30723
31217
|
regions: ["cn"],
|
|
31218
|
+
credits: { cn: "0.23" },
|
|
30724
31219
|
supportsImage: false,
|
|
30725
31220
|
reasoningEfforts: [],
|
|
30726
31221
|
defaultReasoningEffort: null,
|
|
@@ -30734,6 +31229,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30734
31229
|
maxContextWindow: 1e6,
|
|
30735
31230
|
maxTokens: 32e3,
|
|
30736
31231
|
regions: ["intl"],
|
|
31232
|
+
credits: { intl: "1.62" },
|
|
30737
31233
|
supportsImage: true,
|
|
30738
31234
|
reasoningEfforts: [
|
|
30739
31235
|
"low",
|
|
@@ -30751,6 +31247,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30751
31247
|
maxContextWindow: 1e6,
|
|
30752
31248
|
maxTokens: 32e3,
|
|
30753
31249
|
regions: ["cn"],
|
|
31250
|
+
credits: { cn: "1.62" },
|
|
30754
31251
|
supportsImage: true,
|
|
30755
31252
|
reasoningEfforts: [
|
|
30756
31253
|
"low",
|
|
@@ -30768,6 +31265,10 @@ const FALLBACK_MODELS$1 = [
|
|
|
30768
31265
|
maxContextWindow: 1e6,
|
|
30769
31266
|
maxTokens: 32e3,
|
|
30770
31267
|
regions: ["intl", "cn"],
|
|
31268
|
+
credits: {
|
|
31269
|
+
cn: "0.77",
|
|
31270
|
+
intl: "0.77"
|
|
31271
|
+
},
|
|
30771
31272
|
supportsImage: true,
|
|
30772
31273
|
reasoningEfforts: [
|
|
30773
31274
|
"low",
|
|
@@ -30785,6 +31286,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30785
31286
|
maxContextWindow: 256e3,
|
|
30786
31287
|
maxTokens: 32e3,
|
|
30787
31288
|
regions: ["cn"],
|
|
31289
|
+
credits: { cn: "0.57" },
|
|
30788
31290
|
supportsImage: true,
|
|
30789
31291
|
reasoningEfforts: [
|
|
30790
31292
|
"low",
|
|
@@ -30802,6 +31304,10 @@ const FALLBACK_MODELS$1 = [
|
|
|
30802
31304
|
maxContextWindow: 256e3,
|
|
30803
31305
|
maxTokens: 32e3,
|
|
30804
31306
|
regions: ["intl", "cn"],
|
|
31307
|
+
credits: {
|
|
31308
|
+
cn: "0.52",
|
|
31309
|
+
intl: "0.52"
|
|
31310
|
+
},
|
|
30805
31311
|
supportsImage: true,
|
|
30806
31312
|
reasoningEfforts: [
|
|
30807
31313
|
"low",
|
|
@@ -30819,6 +31325,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30819
31325
|
maxContextWindow: 164e3,
|
|
30820
31326
|
maxTokens: 32e3,
|
|
30821
31327
|
regions: ["cn"],
|
|
31328
|
+
credits: { cn: "0.45" },
|
|
30822
31329
|
supportsImage: true,
|
|
30823
31330
|
reasoningEfforts: [
|
|
30824
31331
|
"low",
|
|
@@ -30836,6 +31343,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30836
31343
|
maxContextWindow: 164e3,
|
|
30837
31344
|
maxTokens: 32e3,
|
|
30838
31345
|
regions: ["cn"],
|
|
31346
|
+
credits: { cn: "0.54" },
|
|
30839
31347
|
supportsImage: false,
|
|
30840
31348
|
reasoningEfforts: [
|
|
30841
31349
|
"low",
|
|
@@ -30853,6 +31361,10 @@ const FALLBACK_MODELS$1 = [
|
|
|
30853
31361
|
maxContextWindow: 1e6,
|
|
30854
31362
|
maxTokens: 128e3,
|
|
30855
31363
|
regions: ["intl", "cn"],
|
|
31364
|
+
credits: {
|
|
31365
|
+
cn: "0.11",
|
|
31366
|
+
intl: "0.00"
|
|
31367
|
+
},
|
|
30856
31368
|
supportsImage: true,
|
|
30857
31369
|
reasoningEfforts: [
|
|
30858
31370
|
"low",
|
|
@@ -30870,6 +31382,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30870
31382
|
maxContextWindow: 1e6,
|
|
30871
31383
|
maxTokens: 128e3,
|
|
30872
31384
|
regions: ["intl"],
|
|
31385
|
+
credits: { intl: "0.03" },
|
|
30873
31386
|
supportsImage: true,
|
|
30874
31387
|
reasoningEfforts: [
|
|
30875
31388
|
"low",
|
|
@@ -30887,6 +31400,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30887
31400
|
maxContextWindow: 1e6,
|
|
30888
31401
|
maxTokens: 128e3,
|
|
30889
31402
|
regions: ["cn"],
|
|
31403
|
+
credits: { cn: "0.51" },
|
|
30890
31404
|
supportsImage: true,
|
|
30891
31405
|
reasoningEfforts: [
|
|
30892
31406
|
"low",
|
|
@@ -30904,6 +31418,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30904
31418
|
maxContextWindow: 1e6,
|
|
30905
31419
|
maxTokens: 5e4,
|
|
30906
31420
|
regions: ["cn"],
|
|
31421
|
+
credits: { cn: "0.17" },
|
|
30907
31422
|
supportsImage: true,
|
|
30908
31423
|
reasoningEfforts: [
|
|
30909
31424
|
"low",
|
|
@@ -30921,6 +31436,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30921
31436
|
maxContextWindow: 96e3,
|
|
30922
31437
|
maxTokens: 32e3,
|
|
30923
31438
|
regions: ["cn"],
|
|
31439
|
+
credits: { cn: "0.29" },
|
|
30924
31440
|
supportsImage: false,
|
|
30925
31441
|
reasoningEfforts: [
|
|
30926
31442
|
"low",
|
|
@@ -30938,6 +31454,10 @@ const FALLBACK_MODELS$1 = [
|
|
|
30938
31454
|
maxContextWindow: 1e6,
|
|
30939
31455
|
maxTokens: 64e3,
|
|
30940
31456
|
regions: ["intl", "cn"],
|
|
31457
|
+
credits: {
|
|
31458
|
+
cn: "0.29",
|
|
31459
|
+
intl: "0.29"
|
|
31460
|
+
},
|
|
30941
31461
|
supportsImage: true,
|
|
30942
31462
|
reasoningEfforts: ["high"],
|
|
30943
31463
|
defaultReasoningEffort: "high",
|
|
@@ -30951,6 +31471,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30951
31471
|
maxContextWindow: 1e6,
|
|
30952
31472
|
maxTokens: 64e3,
|
|
30953
31473
|
regions: ["cn"],
|
|
31474
|
+
credits: { cn: "0.00" },
|
|
30954
31475
|
supportsImage: true,
|
|
30955
31476
|
reasoningEfforts: ["high"],
|
|
30956
31477
|
defaultReasoningEffort: "high",
|
|
@@ -30964,6 +31485,10 @@ const FALLBACK_MODELS$1 = [
|
|
|
30964
31485
|
maxContextWindow: 192e3,
|
|
30965
31486
|
maxTokens: 64e3,
|
|
30966
31487
|
regions: ["intl", "cn"],
|
|
31488
|
+
credits: {
|
|
31489
|
+
cn: "0.00",
|
|
31490
|
+
intl: "0.00"
|
|
31491
|
+
},
|
|
30967
31492
|
supportsImage: true,
|
|
30968
31493
|
reasoningEfforts: ["low", "high"],
|
|
30969
31494
|
defaultReasoningEffort: "high",
|
|
@@ -30977,6 +31502,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30977
31502
|
maxContextWindow: 192e3,
|
|
30978
31503
|
maxTokens: 64e3,
|
|
30979
31504
|
regions: ["cn"],
|
|
31505
|
+
credits: { cn: "0.05" },
|
|
30980
31506
|
supportsImage: true,
|
|
30981
31507
|
reasoningEfforts: [
|
|
30982
31508
|
"low",
|
|
@@ -30994,6 +31520,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30994
31520
|
maxContextWindow: 2e5,
|
|
30995
31521
|
maxTokens: 8192,
|
|
30996
31522
|
regions: ["cn"],
|
|
31523
|
+
credits: { cn: "0.10" },
|
|
30997
31524
|
supportsImage: false,
|
|
30998
31525
|
reasoningEfforts: [],
|
|
30999
31526
|
defaultReasoningEffort: null,
|
|
@@ -31007,6 +31534,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
31007
31534
|
maxContextWindow: 1e6,
|
|
31008
31535
|
maxTokens: 128e3,
|
|
31009
31536
|
regions: ["cn"],
|
|
31537
|
+
credits: { cn: "0.08" },
|
|
31010
31538
|
supportsImage: true,
|
|
31011
31539
|
reasoningEfforts: [
|
|
31012
31540
|
"low",
|
|
@@ -31026,6 +31554,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
31026
31554
|
maxContextWindow: 512e3,
|
|
31027
31555
|
maxTokens: 64e3,
|
|
31028
31556
|
regions: ["cn"],
|
|
31557
|
+
credits: { cn: "0.25" },
|
|
31029
31558
|
supportsImage: true,
|
|
31030
31559
|
reasoningEfforts: [
|
|
31031
31560
|
"low",
|
|
@@ -31043,6 +31572,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
31043
31572
|
maxContextWindow: 2e5,
|
|
31044
31573
|
maxTokens: 48e3,
|
|
31045
31574
|
regions: ["cn"],
|
|
31575
|
+
credits: { cn: "0.19" },
|
|
31046
31576
|
supportsImage: true,
|
|
31047
31577
|
reasoningEfforts: [
|
|
31048
31578
|
"low",
|
|
@@ -31060,6 +31590,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
31060
31590
|
maxContextWindow: 2e5,
|
|
31061
31591
|
maxTokens: 48e3,
|
|
31062
31592
|
regions: ["cn"],
|
|
31593
|
+
credits: { cn: "0.18" },
|
|
31063
31594
|
supportsImage: false,
|
|
31064
31595
|
reasoningEfforts: [
|
|
31065
31596
|
"low",
|
|
@@ -31078,6 +31609,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
31078
31609
|
maxContextWindow: 1e6,
|
|
31079
31610
|
maxTokens: 65536,
|
|
31080
31611
|
regions: ["intl"],
|
|
31612
|
+
credits: { intl: "0.99" },
|
|
31081
31613
|
supportsImage: true,
|
|
31082
31614
|
reasoningEfforts: [
|
|
31083
31615
|
"low",
|
|
@@ -31145,6 +31677,7 @@ function resolveWorkBuddyModel(id, catalog = FALLBACK_MODELS$1) {
|
|
|
31145
31677
|
maxContextWindow: DEFAULT_CONTEXT_WINDOW$1,
|
|
31146
31678
|
maxTokens: FALLBACK_MAX_TOKENS,
|
|
31147
31679
|
regions: ["cn", "intl"],
|
|
31680
|
+
credits: {},
|
|
31148
31681
|
supportsImage: false,
|
|
31149
31682
|
reasoningEfforts: [],
|
|
31150
31683
|
defaultReasoningEffort: null,
|
|
@@ -32151,6 +32684,14 @@ async function fetchAccountIdentity$1(credentials, options = {}) {
|
|
|
32151
32684
|
* inferred from the model's name. Image-generation tools (`tags` naming
|
|
32152
32685
|
* `text-to-image`/`image-to-image`) are skipped: they are not chat models and
|
|
32153
32686
|
* carry no context window.
|
|
32687
|
+
*
|
|
32688
|
+
* `credits` is read here rather than left to the shipped table because a
|
|
32689
|
+
* reachable gateway **replaces** that table entirely: a multiplier transcribed
|
|
32690
|
+
* offline would be the only one a signed-in user ever saw, and it would stay
|
|
32691
|
+
* wrong the moment the subscription repriced a model. A string this route cannot
|
|
32692
|
+
* turn into a number (`''`, which `default-model` publishes, or a value a
|
|
32693
|
+
* future gateway invents) leaves the field off the entry altogether instead of
|
|
32694
|
+
* writing a plausible-looking `0` — see `parseWorkBuddyCreditMultiplier`.
|
|
32154
32695
|
*/
|
|
32155
32696
|
function parseConfigModels(payload, region) {
|
|
32156
32697
|
const root = asRecord$4(payload) ?? {};
|
|
@@ -32172,6 +32713,7 @@ function parseConfigModels(payload, region) {
|
|
|
32172
32713
|
const declaredDefault = asString$5(reasoning.defaultEffort) ?? single ?? null;
|
|
32173
32714
|
const modelDefault = declaredDefault === null || efforts.includes(declaredDefault) ? declaredDefault : convergeWorkBuddyEffort(declaredDefault, efforts);
|
|
32174
32715
|
const servedDefault = asNumber$1(asRecord$4(record.contextWindow)?.defaultLength);
|
|
32716
|
+
const multiplier = parseWorkBuddyCreditMultiplier(record.credits);
|
|
32175
32717
|
models.push({
|
|
32176
32718
|
id,
|
|
32177
32719
|
name: asString$5(record.name) ?? id,
|
|
@@ -32179,6 +32721,7 @@ function parseConfigModels(payload, region) {
|
|
|
32179
32721
|
maxContextWindow,
|
|
32180
32722
|
maxTokens: asNumber$1(record.maxOutputTokens) ?? 32768,
|
|
32181
32723
|
regions: [region],
|
|
32724
|
+
...multiplier === null ? {} : { credits: { [region]: multiplier } },
|
|
32182
32725
|
supportsImage: record.supportsImages === true,
|
|
32183
32726
|
reasoningEfforts: efforts,
|
|
32184
32727
|
defaultReasoningEffort: modelDefault,
|
|
@@ -32792,6 +33335,13 @@ function resolveEnabledModelIds$2(stored, available, enabled = true) {
|
|
|
32792
33335
|
* Order comes from the caller, which passes a vendor-grouped list so a family
|
|
32793
33336
|
* is not interleaved with the next one; this preserves whatever order it is
|
|
32794
33337
|
* given rather than imposing a second one.
|
|
33338
|
+
*
|
|
33339
|
+
* `credits` is passed through whole, every region the entry carries. Trimming
|
|
33340
|
+
* it to one account's region is tempting and wrong: this DTO is also read with
|
|
33341
|
+
* no account in hand (the harness resolves a model list before a credential is
|
|
33342
|
+
* chosen), and such a caller would be told nothing rather than everything. The
|
|
33343
|
+
* region key the reader actually has is already on each entry as `regions`, so
|
|
33344
|
+
* picking a value is the renderer's job, not this one's.
|
|
32795
33345
|
*/
|
|
32796
33346
|
function buildModelOptions(catalog, available, enabledModelIds, overrides) {
|
|
32797
33347
|
const enabled = new Set(enabledModelIds);
|
|
@@ -32809,6 +33359,7 @@ function buildModelOptions(catalog, available, enabledModelIds, overrides) {
|
|
|
32809
33359
|
reasoningEfforts: [...workBuddyReasoningEfforts(id, catalog)],
|
|
32810
33360
|
supportsImage: workBuddyModelSupportsImage(id, catalog),
|
|
32811
33361
|
regions: [...entry.regions],
|
|
33362
|
+
...entry.credits === void 0 || Object.keys(entry.credits).length === 0 ? {} : { credits: { ...entry.credits } },
|
|
32812
33363
|
description: entry.description
|
|
32813
33364
|
};
|
|
32814
33365
|
});
|