@burdenoff/fe-libs 2026.1027.14 → 2026.1028.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,7 +28,7 @@ function D(e, t) {
28
28
  }
29
29
  function O(O, k) {
30
30
  let A = O.mode ?? "assistant", j = O.streamBudgetMs ?? S, M = { accumulateNarration: O.accumulateNarration === !0 }, N = O.ttlSeconds ?? 3600, P = O.isProdRuntime ?? (() => !1), F = O.enableCredits ?? !1, I = _("transport", O.product), L = (e) => async (t) => {
31
- t.messageId && await f(t.workspaceId, t.sessionId, t.messageId, t.model, t.tokens, e);
31
+ t.messageId && await f(t.workspaceId, t.sessionId, t.messageId, t.model, t.tokens, e, O.product, O.aiCreditLedger);
32
32
  }, R = new h({
33
33
  product: O.product,
34
34
  mode: A,
@@ -141,7 +141,7 @@ function O(O, k) {
141
141
  let e = await b(I, n, L(r));
142
142
  if (!e.ok) throw x(e.kind);
143
143
  }
144
- return F && l(n, r).catch((e) => {
144
+ return F && l(n, r, O.product, O.aiCreditLedger).catch((e) => {
145
145
  t(e) && O.onQuotaExhausted?.(e);
146
146
  }), B(e, n, r, 1);
147
147
  },
@@ -302,6 +302,7 @@ export * from './appControls/languageReply';
302
302
  export * from './appControls/text';
303
303
  export * from './turnOutcomeCopy';
304
304
  export * from './aiCredits';
305
+ export * from './productAiCredits';
305
306
  export * from './usageReporting';
306
307
  export * from './writeProposals';
307
308
  export * from './writeReplyBinder';
@@ -40,6 +40,14 @@ export declare const STOP_MY_ASSISTANT_SANDBOX = "\n mutation StopMyAssistantSa
40
40
  export declare const EXTEND_MY_ASSISTANT_SANDBOX_TTL = "\n mutation ExtendMyAssistantSandboxTtl(\n $id: ID!\n $additionalSeconds: Int!\n $context: ContextInput!\n ) {\n extendMyAssistantSandboxTTL(id: $id, additionalSeconds: $additionalSeconds, context: $context) {\n id\n expiresAt\n }\n }\n";
41
41
  export declare const CHECK_AI_CREDITS = "\n mutation CheckAiCredits {\n checkAiCredits\n }\n";
42
42
  export declare const REPORT_AI_TOKEN_USAGE = "\n mutation ReportAiTokenUsage($input: ReportAiTokenUsageInput!) {\n reportAiTokenUsage(input: $input)\n }\n";
43
+ /**
44
+ * BOFF-3732: the shared, product-keyed AI-credit gate and charge in
45
+ * wspace-prompts-svc. Each works on `<productKey>.ai.credits`, so a product's
46
+ * assistant turns are billed to its own quota, never VibeControls'.
47
+ */
48
+ export declare const CHECK_AI_CREDITS_FOR = "\n mutation CheckAiCreditsFor($productKey: String!) {\n checkAiCreditsFor(productKey: $productKey) {\n provisioned\n exhausted\n remaining\n }\n }\n";
49
+ export declare const CHARGE_AI_INVOCATION = "\n mutation ChargeAiInvocation($input: PromptChargeAiInvocationInput!) {\n chargeAiInvocation(input: $input) {\n status\n reason\n credits\n }\n }\n";
50
+ export declare const AI_CREDIT_STATUS = "\n query AiCreditStatus($productKey: String!) {\n aiCreditStatus(productKey: $productKey) {\n productKey\n quotaName\n provisioned\n noLimit\n used\n limit\n remaining\n exhausted\n }\n }\n";
43
51
  /**
44
52
  * BOFF-7801: finished turns' timings for the production latency histograms
45
53
  * (wspace-sandbox-svc). Self-scoped — the actor comes from the gateway, and
@@ -1,4 +1,4 @@
1
1
  //#region src/shared/assistant/operations.ts
2
- var e = "\n mutation CreateSandbox($input: CreateSandboxInput!) {\n createSandbox(input: $input) {\n id\n name\n status\n network {\n internalUrl\n }\n }\n }\n", t = "\n query GetSandbox($id: ID!, $context: ContextInput!) {\n getSandbox(id: $id, context: $context) {\n id\n status\n errorMessage\n network {\n internalUrl\n }\n }\n }\n", n = "\n query WaitForSandboxRunning($id: ID!, $timeoutSeconds: Int, $context: ContextInput!) {\n waitForSandboxRunning(id: $id, timeoutSeconds: $timeoutSeconds, context: $context) {\n id\n status\n errorMessage\n }\n }\n", r = "\n query WaitForAssistantReady($id: ID!, $timeoutSeconds: Int, $context: ContextInput!) {\n waitForAssistantReady(id: $id, timeoutSeconds: $timeoutSeconds, context: $context) {\n ready\n health\n waited\n }\n }\n", i = "\n query GetActiveSandboxes($context: ContextInput!) {\n getActiveSandboxes(context: $context) {\n id\n name\n status\n metadata\n template\n createdBy\n }\n }\n", a = "\n mutation ProxySandboxRequest($input: ProxySandboxRequestInput!) {\n proxySandboxRequest(input: $input) {\n status\n body\n }\n }\n", o = "\n mutation StopAssistantSandbox($id: ID!, $context: ContextInput!) {\n stopSandbox(id: $id, context: $context) {\n id\n status\n }\n }\n", s = "\n mutation ExtendSandboxTtl($id: ID!, $additionalSeconds: Int!, $context: ContextInput!) {\n extendSandboxTTL(id: $id, additionalSeconds: $additionalSeconds, context: $context) {\n id\n expiresAt\n }\n }\n", c = "\n mutation ProvisionAssistantSandbox($input: ProvisionAssistantSandboxInput!) {\n provisionAssistantSandbox(input: $input) {\n id\n name\n status\n }\n }\n", l = "\n mutation StopMyAssistantSandbox($id: ID!, $context: ContextInput!) {\n stopMyAssistantSandbox(id: $id, context: $context) {\n id\n status\n }\n }\n", u = "\n mutation ExtendMyAssistantSandboxTtl(\n $id: ID!\n $additionalSeconds: Int!\n $context: ContextInput!\n ) {\n extendMyAssistantSandboxTTL(id: $id, additionalSeconds: $additionalSeconds, context: $context) {\n id\n expiresAt\n }\n }\n", d = "\n mutation CheckAiCredits {\n checkAiCredits\n }\n", f = "\n mutation ReportAiTokenUsage($input: ReportAiTokenUsageInput!) {\n reportAiTokenUsage(input: $input)\n }\n", p = "\n mutation ReportMyAssistantTurnTimings($input: ReportAssistantTurnTimingsInput!) {\n reportMyAssistantTurnTimings(input: $input) {\n enabled\n accepted\n rejected\n duplicate\n rateLimited\n }\n }\n";
2
+ var e = "\n mutation CreateSandbox($input: CreateSandboxInput!) {\n createSandbox(input: $input) {\n id\n name\n status\n network {\n internalUrl\n }\n }\n }\n", t = "\n query GetSandbox($id: ID!, $context: ContextInput!) {\n getSandbox(id: $id, context: $context) {\n id\n status\n errorMessage\n network {\n internalUrl\n }\n }\n }\n", n = "\n query WaitForSandboxRunning($id: ID!, $timeoutSeconds: Int, $context: ContextInput!) {\n waitForSandboxRunning(id: $id, timeoutSeconds: $timeoutSeconds, context: $context) {\n id\n status\n errorMessage\n }\n }\n", r = "\n query WaitForAssistantReady($id: ID!, $timeoutSeconds: Int, $context: ContextInput!) {\n waitForAssistantReady(id: $id, timeoutSeconds: $timeoutSeconds, context: $context) {\n ready\n health\n waited\n }\n }\n", i = "\n query GetActiveSandboxes($context: ContextInput!) {\n getActiveSandboxes(context: $context) {\n id\n name\n status\n metadata\n template\n createdBy\n }\n }\n", a = "\n mutation ProxySandboxRequest($input: ProxySandboxRequestInput!) {\n proxySandboxRequest(input: $input) {\n status\n body\n }\n }\n", o = "\n mutation StopAssistantSandbox($id: ID!, $context: ContextInput!) {\n stopSandbox(id: $id, context: $context) {\n id\n status\n }\n }\n", s = "\n mutation ExtendSandboxTtl($id: ID!, $additionalSeconds: Int!, $context: ContextInput!) {\n extendSandboxTTL(id: $id, additionalSeconds: $additionalSeconds, context: $context) {\n id\n expiresAt\n }\n }\n", c = "\n mutation ProvisionAssistantSandbox($input: ProvisionAssistantSandboxInput!) {\n provisionAssistantSandbox(input: $input) {\n id\n name\n status\n }\n }\n", l = "\n mutation StopMyAssistantSandbox($id: ID!, $context: ContextInput!) {\n stopMyAssistantSandbox(id: $id, context: $context) {\n id\n status\n }\n }\n", u = "\n mutation ExtendMyAssistantSandboxTtl(\n $id: ID!\n $additionalSeconds: Int!\n $context: ContextInput!\n ) {\n extendMyAssistantSandboxTTL(id: $id, additionalSeconds: $additionalSeconds, context: $context) {\n id\n expiresAt\n }\n }\n", d = "\n mutation CheckAiCredits {\n checkAiCredits\n }\n", f = "\n mutation ReportAiTokenUsage($input: ReportAiTokenUsageInput!) {\n reportAiTokenUsage(input: $input)\n }\n", p = "\n mutation CheckAiCreditsFor($productKey: String!) {\n checkAiCreditsFor(productKey: $productKey) {\n provisioned\n exhausted\n remaining\n }\n }\n", m = "\n mutation ChargeAiInvocation($input: PromptChargeAiInvocationInput!) {\n chargeAiInvocation(input: $input) {\n status\n reason\n credits\n }\n }\n", h = "\n query AiCreditStatus($productKey: String!) {\n aiCreditStatus(productKey: $productKey) {\n productKey\n quotaName\n provisioned\n noLimit\n used\n limit\n remaining\n exhausted\n }\n }\n", g = "\n mutation ReportMyAssistantTurnTimings($input: ReportAssistantTurnTimingsInput!) {\n reportMyAssistantTurnTimings(input: $input) {\n enabled\n accepted\n rejected\n duplicate\n rateLimited\n }\n }\n";
3
3
  //#endregion
4
- export { d as CHECK_AI_CREDITS, e as CREATE_SANDBOX, u as EXTEND_MY_ASSISTANT_SANDBOX_TTL, s as EXTEND_SANDBOX_TTL, i as GET_ACTIVE_SANDBOXES, t as GET_SANDBOX, c as PROVISION_ASSISTANT_SANDBOX, a as PROXY_SANDBOX_REQUEST, f as REPORT_AI_TOKEN_USAGE, p as REPORT_MY_ASSISTANT_TURN_TIMINGS, o as STOP_ASSISTANT_SANDBOX, l as STOP_MY_ASSISTANT_SANDBOX, r as WAIT_FOR_ASSISTANT_READY, n as WAIT_FOR_SANDBOX_RUNNING };
4
+ export { h as AI_CREDIT_STATUS, m as CHARGE_AI_INVOCATION, d as CHECK_AI_CREDITS, p as CHECK_AI_CREDITS_FOR, e as CREATE_SANDBOX, u as EXTEND_MY_ASSISTANT_SANDBOX_TTL, s as EXTEND_SANDBOX_TTL, i as GET_ACTIVE_SANDBOXES, t as GET_SANDBOX, c as PROVISION_ASSISTANT_SANDBOX, a as PROXY_SANDBOX_REQUEST, f as REPORT_AI_TOKEN_USAGE, g as REPORT_MY_ASSISTANT_TURN_TIMINGS, o as STOP_ASSISTANT_SANDBOX, l as STOP_MY_ASSISTANT_SANDBOX, r as WAIT_FOR_ASSISTANT_READY, n as WAIT_FOR_SANDBOX_RUNNING };
@@ -0,0 +1,28 @@
1
+ import { UsageReport } from './usageReporting';
2
+ /** How a workspace stands against `<productKey>.ai.credits`. */
3
+ export interface AiCreditStatus {
4
+ productKey: string;
5
+ quotaName: string;
6
+ /** False when the workspace has no assignment for the quota: AI is not metered there. */
7
+ provisioned: boolean;
8
+ noLimit: boolean;
9
+ used: number | null;
10
+ limit: number | null;
11
+ remaining: number | null;
12
+ exhausted: boolean;
13
+ }
14
+ /** The operation key the shared ledger stores for an assistant turn. */
15
+ export declare const ASSISTANT_TURN_OPERATION = "assistant.turn";
16
+ /**
17
+ * A `reportTokenUsage` for `AssistantProductProvider` that charges the
18
+ * product's own AI credits. A turn without a message id or model is skipped:
19
+ * the server needs the first for idempotency and the second for the price.
20
+ */
21
+ export declare function createAiCreditReporter(productKey: string): (report: UsageReport) => Promise<void>;
22
+ /** Parse an `aiCreditStatus` payload; anything malformed reads as null. */
23
+ export declare function parseAiCreditStatus(value: unknown): AiCreditStatus | null;
24
+ /**
25
+ * The workspace's remaining AI credits for a product, for showing the balance
26
+ * before an AI action. Resolves null when it cannot be read; never throws.
27
+ */
28
+ export declare function fetchAiCreditStatus(productKey: string, workspaceId?: string): Promise<AiCreditStatus | null>;
@@ -0,0 +1,64 @@
1
+ import { graphqlFetch as e } from "../graphql/fetch.js";
2
+ import { AI_CREDIT_STATUS as t, CHARGE_AI_INVOCATION as n } from "./operations.js";
3
+ //#region src/shared/assistant/productAiCredits.ts
4
+ var r = "assistant.turn";
5
+ function i(t) {
6
+ return async (i) => {
7
+ let { workspaceId: a, sessionId: o, messageId: s, model: c, tokens: l } = i;
8
+ if (!s || !c) return;
9
+ let u = Math.max(0, Math.round(l.input ?? 0)), d = Math.max(0, Math.round(l.output ?? 0)), f = Math.max(0, Math.round(l.cache?.read ?? 0)), p = Math.max(0, Math.round(l.cache?.write ?? 0));
10
+ u + d + f + p !== 0 && await e({
11
+ gateway: "workspace",
12
+ query: n,
13
+ operationName: "ChargeAiInvocation",
14
+ variables: { input: {
15
+ productKey: t,
16
+ sessionId: o,
17
+ messageId: s,
18
+ model: c,
19
+ inputTokens: u,
20
+ outputTokens: d,
21
+ cacheReadTokens: f,
22
+ cacheWriteTokens: p,
23
+ operationKey: r
24
+ } },
25
+ workspaceId: a,
26
+ suppressGlobalErrorEvent: !0
27
+ });
28
+ };
29
+ }
30
+ function a(e) {
31
+ return typeof e == "number" && Number.isFinite(e) ? e : null;
32
+ }
33
+ function o(e) {
34
+ if (!e || typeof e != "object") return null;
35
+ let t = e.aiCreditStatus;
36
+ if (!t || typeof t != "object") return null;
37
+ let n = t;
38
+ return typeof n.productKey != "string" || typeof n.quotaName != "string" || typeof n.provisioned != "boolean" || typeof n.noLimit != "boolean" || typeof n.exhausted != "boolean" ? null : {
39
+ productKey: n.productKey,
40
+ quotaName: n.quotaName,
41
+ provisioned: n.provisioned,
42
+ noLimit: n.noLimit,
43
+ used: a(n.used),
44
+ limit: a(n.limit),
45
+ remaining: a(n.remaining),
46
+ exhausted: n.exhausted
47
+ };
48
+ }
49
+ async function s(n, r) {
50
+ try {
51
+ return o(await e({
52
+ gateway: "workspace",
53
+ query: t,
54
+ operationName: "AiCreditStatus",
55
+ variables: { productKey: n },
56
+ workspaceId: r ?? !0,
57
+ suppressGlobalErrorEvent: !0
58
+ }));
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+ //#endregion
64
+ export { r as ASSISTANT_TURN_OPERATION, i as createAiCreditReporter, s as fetchAiCreditStatus, o as parseAiCreditStatus };
@@ -30,6 +30,8 @@ export type ProductAssistantMode = string & {
30
30
  };
31
31
  export type AssistantMode = BuiltInAssistantMode | ProductAssistantMode;
32
32
  /** Local mirror of a product's PageContext; the transport forwards a subset. */
33
+ /** Where a product's assistant AI credits are gated and charged (see `aiCreditLedger`). */
34
+ export type AiCreditLedger = 'shared' | 'product-service';
33
35
  export interface PageContext {
34
36
  currentUrl?: string;
35
37
  currentPage?: string;
@@ -164,6 +166,19 @@ export interface SandboxAssistantConfig {
164
166
  * pauses the next send until it is (`usageReporting.ts`).
165
167
  */
166
168
  enableCredits?: boolean;
169
+ /**
170
+ * Which service gates and charges this product's AI credits (BOFF-3732).
171
+ *
172
+ * - `'shared'` (default): wspace-prompts-svc's product-keyed
173
+ * `checkAiCreditsFor` / `chargeAiInvocation`, against
174
+ * `<product>.ai.credits`.
175
+ * - `'product-service'`: the product's own service declares
176
+ * `checkAiCredits` / `reportAiTokenUsage` and charges its own quota. Only a
177
+ * product whose service really owns those mutations may set it: they are
178
+ * declared once in the supergraph, so any other product setting it bills
179
+ * that service's quota, not its own.
180
+ */
181
+ aiCreditLedger?: AiCreditLedger;
167
182
  /** Overall per-turn deadline. Default 420_000ms. */
168
183
  streamBudgetMs?: number;
169
184
  /**
@@ -4,47 +4,49 @@ import { useAuth as n } from "../providers/shell/AuthProvider.js";
4
4
  import { useActiveContext as r } from "../providers/shell/ActiveContextProvider.js";
5
5
  import { MainLandmark as i } from "../components/SkipToContent.js";
6
6
  import { PermissionProvider as a, usePermissions as o, useSafePermissions as s } from "../providers/shell/PermissionProvider.js";
7
- import { MODELS as c, PROVIDERS as l, SUMMARY as u, catalog as d, sevenDays as f, summary as p } from "./ai/contracts.js";
8
- import { useAiTr as m } from "./ai/i18n.js";
9
- import { CatalogPanel as h } from "./ai/CatalogPanel.js";
10
- import { SummaryPanel as g } from "./ai/SummaryPanel.js";
11
- import { useAiRead as _ } from "./ai/useAiRead.js";
12
- import { useCallback as v, useMemo as y, useState as b } from "react";
13
- import { Fragment as x, jsx as S, jsxs as C } from "react/jsx-runtime";
7
+ import { BREAKDOWN as c, CREDITS as l, MODELS as u, PROVIDERS as d, SUMMARY as f, breakdown as p, catalog as m, credits as h, sevenDays as g, summary as _ } from "./ai/contracts.js";
8
+ import { useAiTr as v } from "./ai/i18n.js";
9
+ import { CatalogPanel as y } from "./ai/CatalogPanel.js";
10
+ import { SummaryPanel as b } from "./ai/SummaryPanel.js";
11
+ import { BreakdownPanel as x } from "./ai/BreakdownPanel.js";
12
+ import { CreditsPanel as S } from "./ai/CreditsPanel.js";
13
+ import { useAiRead as C } from "./ai/useAiRead.js";
14
+ import { useCallback as w, useMemo as T, useState as E } from "react";
15
+ import { Fragment as D, jsx as O, jsxs as k } from "react/jsx-runtime";
14
16
  //#region src/shared/pages/AiPage.tsx
15
- function w({ productName: t, productId: n }) {
16
- let r = m(), a = e("workspace");
17
- return /* @__PURE__ */ C(i, {
17
+ function A({ productName: t, productId: n }) {
18
+ let r = v(), a = e("workspace");
19
+ return /* @__PURE__ */ k(i, {
18
20
  className: "mx-auto w-full max-w-6xl space-y-6 px-4 py-6 sm:px-6",
19
21
  "data-product-id": n,
20
- children: [/* @__PURE__ */ C("header", {
22
+ children: [/* @__PURE__ */ k("header", {
21
23
  className: "space-y-2",
22
- children: [/* @__PURE__ */ S("h1", {
24
+ children: [/* @__PURE__ */ O("h1", {
23
25
  className: "text-2xl font-semibold text-foreground",
24
26
  children: r("title", "{product} AI operations", { product: t })
25
- }), /* @__PURE__ */ S("p", {
27
+ }), /* @__PURE__ */ O("p", {
26
28
  className: "max-w-3xl text-sm text-muted-foreground",
27
29
  children: r("intro", "Review the workspace model catalog and recorded AI usage.")
28
30
  })]
29
- }), a ? /* @__PURE__ */ S(T, { children: /* @__PURE__ */ S(E, {
31
+ }), a ? /* @__PURE__ */ O(j, { children: /* @__PURE__ */ O(M, {
30
32
  productId: n,
31
33
  client: a
32
- }) }) : /* @__PURE__ */ S("p", {
34
+ }) }) : /* @__PURE__ */ O("p", {
33
35
  role: "status",
34
36
  children: r("unavailable", "AI operations are unavailable in this context.")
35
37
  })]
36
38
  });
37
39
  }
38
- function T({ children: e }) {
40
+ function j({ children: e }) {
39
41
  let t = s(), { workspaceId: n } = r();
40
- return t ? e : /* @__PURE__ */ S(a, {
42
+ return t ? e : /* @__PURE__ */ O(a, {
41
43
  scopeId: n ?? null,
42
44
  gateway: "workspace",
43
45
  children: e
44
46
  });
45
47
  }
46
- function E({ productId: e, client: i }) {
47
- let a = n(), { workspaceId: s } = r(), w = o(), { locale: T } = t(), E = m(), D = y(() => ({
48
+ function M({ productId: e, client: i }) {
49
+ let a = n(), { workspaceId: s } = r(), A = o(), { locale: j } = t(), M = v(), N = T(() => ({
48
50
  workspaceId: s ?? "",
49
51
  actorId: a.user?.id ?? "",
50
52
  profileId: a.activeProfileId,
@@ -56,78 +58,102 @@ function E({ productId: e, client: i }) {
56
58
  a.activeProfileId,
57
59
  a.tokens?.accessToken,
58
60
  a.workspaceToken
59
- ]), [O, k] = b(f), [A, j] = b({
60
- scope: D,
61
+ ]), [P, F] = E(g), [I, L] = E({
62
+ scope: N,
61
63
  models: 0,
62
64
  providers: 0,
63
65
  revision: 0
64
- }), M = A.scope === D ? A.models : 0, N = A.scope === D ? A.providers : 0, P = A.scope === D ? A.revision : 0, F = (e) => a.isLoading ? "loading" : !a.isAuthenticated || !D.workspaceId || !D.actorId || !D.accessToken ? "unavailable" : w.isLoading || w.scopeId !== D.workspaceId ? "loading" : w.error ? "unavailable" : !w.isSuperAdmin && w.permissions?.actorId !== D.actorId ? "loading" : w.hasPermission(e) ? "ready" : "denied", I = y(() => ({ pagination: {
66
+ }), R = I.scope === N ? I.models : 0, z = I.scope === N ? I.providers : 0, B = I.scope === N ? I.revision : 0, V = (e) => a.isLoading ? "loading" : !a.isAuthenticated || !N.workspaceId || !N.actorId || !N.accessToken ? "unavailable" : A.isLoading || A.scopeId !== N.workspaceId ? "loading" : A.error ? "unavailable" : !A.isSuperAdmin && A.permissions?.actorId !== N.actorId ? "loading" : A.hasPermission(e) ? "ready" : "denied", H = T(() => ({ pagination: {
65
67
  limit: 20,
66
- offset: M
67
- } }), [M]), L = y(() => ({ pagination: {
68
+ offset: R
69
+ } }), [R]), U = T(() => ({ pagination: {
68
70
  limit: 20,
69
- offset: N
70
- } }), [N]), R = y(() => ({
71
- ...O,
71
+ offset: z
72
+ } }), [z]), W = T(() => ({
73
+ ...P,
72
74
  productKey: e
73
- }), [O, e]), z = v((e) => d(e, "models"), []), B = v((e) => d(e, "providers"), []), V = v((t) => p(t, D.workspaceId, e, O), [
74
- D.workspaceId,
75
+ }), [P, e]), G = w((e) => m(e, "models"), []), K = w((e) => m(e, "providers"), []), ee = w((t) => _(t, N.workspaceId, e, P), [
76
+ N.workspaceId,
75
77
  e,
76
- O
77
- ]), H = _(i, D, F("prompt-model:read"), c, I, z, P), U = _(i, D, F("prompt-provider:read"), l, L, B, P), W = _(i, D, F("prompt-invocation:read"), u, R, V, P);
78
- function G() {
79
- k(f()), j({
80
- scope: D,
81
- models: M,
82
- providers: N,
83
- revision: P + 1
78
+ P
79
+ ]), q = C(i, N, V("prompt-model:read"), u, H, G, B), J = C(i, N, V("prompt-provider:read"), d, U, K, B), Y = C(i, N, V("prompt-invocation:read"), f, W, ee, B), X = T(() => ({
80
+ ...P,
81
+ productKey: e,
82
+ groupBy: ["MODEL"]
83
+ }), [P, e]), Z = T(() => ({
84
+ ...P,
85
+ productKey: e,
86
+ groupBy: ["OPERATION"]
87
+ }), [P, e]), Q = T(() => ({
88
+ ...P,
89
+ productKey: e,
90
+ groupBy: ["TRIGGER"]
91
+ }), [P, e]), $ = w((t) => p(t, N.workspaceId, e, "MODEL"), [N.workspaceId, e]), te = w((t) => p(t, N.workspaceId, e, "OPERATION"), [N.workspaceId, e]), ne = w((t) => p(t, N.workspaceId, e, "TRIGGER"), [N.workspaceId, e]), re = T(() => ({ productKey: e }), [e]), ie = w((t) => h(t, e), [e]), ae = C(i, N, V("prompt-invocation:read"), c, X, $, B), oe = C(i, N, V("prompt-invocation:read"), c, Z, te, B), se = C(i, N, V("prompt-invocation:read"), c, Q, ne, B), ce = C(i, N, V("assistant:use"), l, re, ie, B);
92
+ function le() {
93
+ F(g()), L({
94
+ scope: N,
95
+ models: R,
96
+ providers: z,
97
+ revision: B + 1
84
98
  });
85
99
  }
86
- return /* @__PURE__ */ C(x, { children: [
87
- /* @__PURE__ */ C("div", {
100
+ return /* @__PURE__ */ k(D, { children: [
101
+ /* @__PURE__ */ k("div", {
88
102
  className: "flex flex-wrap items-center justify-between gap-3",
89
- children: [/* @__PURE__ */ S("p", {
103
+ children: [/* @__PURE__ */ O("p", {
90
104
  className: "text-sm text-muted-foreground",
91
- children: E("readonly", "Read-only workspace view")
92
- }), /* @__PURE__ */ S("button", {
105
+ children: M("readonly", "Read-only workspace view")
106
+ }), /* @__PURE__ */ O("button", {
93
107
  type: "button",
94
- onClick: G,
108
+ onClick: le,
95
109
  className: "rounded-md border border-border px-3 py-2 text-sm font-medium focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
96
- children: E("refresh", "Refresh")
110
+ children: M("refresh", "Refresh")
97
111
  })]
98
112
  }),
99
- /* @__PURE__ */ S(g, {
100
- state: W,
101
- window: O,
102
- locale: T
113
+ /* @__PURE__ */ O(S, {
114
+ state: ce,
115
+ locale: j
103
116
  }),
104
- /* @__PURE__ */ C("div", {
117
+ /* @__PURE__ */ O(b, {
118
+ state: Y,
119
+ window: P,
120
+ locale: j
121
+ }),
122
+ /* @__PURE__ */ O(x, {
123
+ states: {
124
+ MODEL: ae,
125
+ OPERATION: oe,
126
+ TRIGGER: se
127
+ },
128
+ locale: j
129
+ }),
130
+ /* @__PURE__ */ k("div", {
105
131
  className: "grid gap-6 lg:grid-cols-2",
106
- children: [/* @__PURE__ */ S(h, {
132
+ children: [/* @__PURE__ */ O(y, {
107
133
  kind: "models",
108
- state: H,
109
- offset: M,
110
- locale: T,
111
- onPage: (e) => j({
112
- scope: D,
134
+ state: q,
135
+ offset: R,
136
+ locale: j,
137
+ onPage: (e) => L({
138
+ scope: N,
113
139
  models: e,
114
- providers: N,
115
- revision: P
140
+ providers: z,
141
+ revision: B
116
142
  })
117
- }), /* @__PURE__ */ S(h, {
143
+ }), /* @__PURE__ */ O(y, {
118
144
  kind: "providers",
119
- state: U,
120
- offset: N,
121
- locale: T,
122
- onPage: (e) => j({
123
- scope: D,
124
- models: M,
145
+ state: J,
146
+ offset: z,
147
+ locale: j,
148
+ onPage: (e) => L({
149
+ scope: N,
150
+ models: R,
125
151
  providers: e,
126
- revision: P
152
+ revision: B
127
153
  })
128
154
  })]
129
155
  })
130
156
  ] });
131
157
  }
132
158
  //#endregion
133
- export { w as AiPage };
159
+ export { A as AiPage };
@@ -0,0 +1,6 @@
1
+ import { Breakdown, BreakdownDimension } from './contracts';
2
+ import { ReadState } from './useAiRead';
3
+ export declare function BreakdownPanel({ states, locale, }: {
4
+ states: Record<BreakdownDimension, ReadState<Breakdown>>;
5
+ locale: string;
6
+ }): import('react/jsx-runtime').JSX.Element;
@@ -0,0 +1,117 @@
1
+ import { exactInteger as e, exactUsd as t } from "./contracts.js";
2
+ import { useAiTr as n } from "./i18n.js";
3
+ import { ReadNotice as r } from "./ReadNotice.js";
4
+ import { jsx as i, jsxs as a } from "react/jsx-runtime";
5
+ //#region src/shared/pages/ai/BreakdownPanel.tsx
6
+ function o({ title: o, state: s, locale: c }) {
7
+ let l = n();
8
+ return /* @__PURE__ */ a("div", {
9
+ className: "min-w-0",
10
+ children: [/* @__PURE__ */ i("h3", {
11
+ className: "text-sm font-semibold",
12
+ children: o
13
+ }), /* @__PURE__ */ i("div", {
14
+ className: "mt-2",
15
+ "aria-live": "polite",
16
+ children: s.status === "ready" ? s.data.rows.length === 0 ? /* @__PURE__ */ i("p", {
17
+ className: "text-sm text-muted-foreground",
18
+ children: l("breakdownEmpty", "No recorded attempts in this window.")
19
+ }) : /* @__PURE__ */ a("div", {
20
+ className: "overflow-x-auto",
21
+ children: [/* @__PURE__ */ a("table", {
22
+ className: "w-full min-w-0 text-left text-sm",
23
+ children: [/* @__PURE__ */ i("thead", {
24
+ className: "text-xs text-muted-foreground",
25
+ children: /* @__PURE__ */ a("tr", { children: [
26
+ /* @__PURE__ */ i("th", {
27
+ scope: "col",
28
+ className: "py-1 pr-3 font-medium",
29
+ children: o
30
+ }),
31
+ /* @__PURE__ */ i("th", {
32
+ scope: "col",
33
+ className: "py-1 pr-3 text-right font-medium",
34
+ children: l("breakdownAttempts", "Attempts")
35
+ }),
36
+ /* @__PURE__ */ i("th", {
37
+ scope: "col",
38
+ className: "py-1 pr-3 text-right font-medium",
39
+ children: l("breakdownTokens", "Tokens")
40
+ }),
41
+ /* @__PURE__ */ i("th", {
42
+ scope: "col",
43
+ className: "py-1 text-right font-medium",
44
+ children: l("breakdownCost", "Est. cost")
45
+ })
46
+ ] })
47
+ }), /* @__PURE__ */ i("tbody", { children: s.data.rows.map((n, r) => /* @__PURE__ */ a("tr", {
48
+ className: "border-t border-border",
49
+ children: [
50
+ /* @__PURE__ */ i("td", {
51
+ className: "max-w-[16rem] break-all py-1 pr-3",
52
+ children: n.key ?? /* @__PURE__ */ i("span", {
53
+ className: "text-muted-foreground",
54
+ children: l("breakdownUnrecorded", "Not recorded")
55
+ })
56
+ }),
57
+ /* @__PURE__ */ i("td", {
58
+ className: "py-1 pr-3 text-right tabular-nums",
59
+ children: e(n.attempts, c)
60
+ }),
61
+ /* @__PURE__ */ i("td", {
62
+ className: "py-1 pr-3 text-right tabular-nums",
63
+ children: n.totalTokens === null ? l("unknown", "Unknown") : e(n.totalTokens, c)
64
+ }),
65
+ /* @__PURE__ */ i("td", {
66
+ className: "py-1 text-right tabular-nums",
67
+ children: n.costMicroUsd === null ? l("unknown", "Unknown") : t(n.costMicroUsd, c)
68
+ })
69
+ ]
70
+ }, `${n.key ?? ""}-${r}`)) })]
71
+ }), s.data.more && /* @__PURE__ */ i("p", {
72
+ className: "mt-1 text-xs text-muted-foreground",
73
+ children: l("breakdownMore", "Showing the highest-cost groups only.")
74
+ })]
75
+ }) : /* @__PURE__ */ i(r, { status: s.status })
76
+ })]
77
+ });
78
+ }
79
+ function s({ states: e, locale: t }) {
80
+ let r = n(), s = r("breakdown", "Where the spend went");
81
+ return /* @__PURE__ */ a("section", {
82
+ "aria-label": s,
83
+ className: "min-w-0 rounded-xl border border-border bg-card p-4 sm:p-5",
84
+ children: [
85
+ /* @__PURE__ */ i("h2", {
86
+ className: "text-lg font-semibold",
87
+ children: s
88
+ }),
89
+ /* @__PURE__ */ i("p", {
90
+ className: "mt-2 text-sm text-muted-foreground",
91
+ children: r("breakdownNotice", "Recorded attempts in the same window, grouped by model, AI surface and what started them. Costs are estimates.")
92
+ }),
93
+ /* @__PURE__ */ a("div", {
94
+ className: "mt-4 grid gap-6 xl:grid-cols-3",
95
+ children: [
96
+ /* @__PURE__ */ i(o, {
97
+ title: r("byModel", "Model"),
98
+ state: e.MODEL,
99
+ locale: t
100
+ }),
101
+ /* @__PURE__ */ i(o, {
102
+ title: r("bySurface", "AI surface"),
103
+ state: e.OPERATION,
104
+ locale: t
105
+ }),
106
+ /* @__PURE__ */ i(o, {
107
+ title: r("byTrigger", "Trigger"),
108
+ state: e.TRIGGER,
109
+ locale: t
110
+ })
111
+ ]
112
+ })
113
+ ]
114
+ });
115
+ }
116
+ //#endregion
117
+ export { s as BreakdownPanel };
@@ -0,0 +1,10 @@
1
+ import { CreditStatus } from './contracts';
2
+ import { ReadState } from './useAiRead';
3
+ /**
4
+ * The workspace's remaining AI credits for this product (BOFF-3732), so the
5
+ * balance is visible before an AI action rather than after a refusal.
6
+ */
7
+ export declare function CreditsPanel({ state, locale, }: {
8
+ state: ReadState<CreditStatus>;
9
+ locale: string;
10
+ }): import('react/jsx-runtime').JSX.Element;
@@ -0,0 +1,64 @@
1
+ import { useAiTr as e } from "./i18n.js";
2
+ import { ReadNotice as t } from "./ReadNotice.js";
3
+ import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
+ //#region src/shared/pages/ai/CreditsPanel.tsx
5
+ function i({ state: i, locale: a }) {
6
+ let o = e(), s = o("credits", "AI credits this cycle"), c = new Intl.NumberFormat(a, { maximumFractionDigits: 0 });
7
+ return /* @__PURE__ */ r("section", {
8
+ "aria-label": s,
9
+ className: "min-w-0 rounded-xl border border-border bg-card p-4 sm:p-5",
10
+ children: [/* @__PURE__ */ n("h2", {
11
+ className: "text-lg font-semibold",
12
+ children: s
13
+ }), /* @__PURE__ */ r("div", {
14
+ className: "mt-3",
15
+ "aria-live": "polite",
16
+ children: [i.status === "ready" ? i.data.provisioned ? i.data.noLimit ? /* @__PURE__ */ n("p", {
17
+ className: "text-sm",
18
+ children: o("creditsUnlimited", "This plan has no AI credit limit.")
19
+ }) : /* @__PURE__ */ r("dl", {
20
+ className: "grid grid-cols-3 gap-3",
21
+ children: [
22
+ /* @__PURE__ */ r("div", {
23
+ className: "min-w-0",
24
+ children: [/* @__PURE__ */ n("dt", {
25
+ className: "text-xs text-muted-foreground",
26
+ children: o("creditsRemaining", "Remaining")
27
+ }), /* @__PURE__ */ n("dd", {
28
+ className: `break-all text-xl font-semibold tabular-nums ${i.data.exhausted ? "text-destructive" : ""}`,
29
+ children: c.format(i.data.remaining ?? 0)
30
+ })]
31
+ }),
32
+ /* @__PURE__ */ r("div", {
33
+ className: "min-w-0",
34
+ children: [/* @__PURE__ */ n("dt", {
35
+ className: "text-xs text-muted-foreground",
36
+ children: o("creditsUsed", "Used")
37
+ }), /* @__PURE__ */ n("dd", {
38
+ className: "break-all text-xl font-semibold tabular-nums",
39
+ children: c.format(i.data.used ?? 0)
40
+ })]
41
+ }),
42
+ /* @__PURE__ */ r("div", {
43
+ className: "min-w-0",
44
+ children: [/* @__PURE__ */ n("dt", {
45
+ className: "text-xs text-muted-foreground",
46
+ children: o("creditsLimit", "Limit")
47
+ }), /* @__PURE__ */ n("dd", {
48
+ className: "break-all text-xl font-semibold tabular-nums",
49
+ children: c.format(i.data.limit ?? 0)
50
+ })]
51
+ })
52
+ ]
53
+ }) : /* @__PURE__ */ n("p", {
54
+ className: "text-sm text-muted-foreground",
55
+ children: o("creditsNotMetered", "AI use is not metered against a credit budget in this workspace.")
56
+ }) : /* @__PURE__ */ n(t, { status: i.status }), i.status === "ready" && i.data.exhausted && /* @__PURE__ */ n("p", {
57
+ className: "mt-3 text-sm text-destructive",
58
+ children: o("creditsExhausted", "AI credits are used up. AI actions resume next cycle or after an upgrade.")
59
+ })]
60
+ })]
61
+ });
62
+ }
63
+ //#endregion
64
+ export { i as CreditsPanel };
@@ -43,3 +43,38 @@ export declare function summary(value: unknown, workspaceId: string, productKey:
43
43
  export declare function exactInteger(value: string, locale: string): string;
44
44
  export declare function exactUsd(value: string, locale: string): string;
45
45
  export declare function sevenDays(now?: Date): UsageWindow;
46
+ /**
47
+ * BOFF-3732: spend grouped by one dimension (BOFF-9935's promptUsageBreakdownV2),
48
+ * so an admin can tell a runaway workflow from the team asking the assistant.
49
+ */
50
+ export declare const BREAKDOWN: import('graphql').DocumentNode;
51
+ /** BOFF-3732: the workspace's remaining `<productKey>.ai.credits`. */
52
+ export declare const CREDITS: import('graphql').DocumentNode;
53
+ export type BreakdownDimension = 'MODEL' | 'OPERATION' | 'TRIGGER';
54
+ export declare const BREAKDOWN_DIMENSIONS: readonly BreakdownDimension[];
55
+ /** Rows shown per breakdown; the service orders by known cost, highest first. */
56
+ export declare const BREAKDOWN_ROWS = 10;
57
+ export interface BreakdownRow {
58
+ /** The group's value; null when the ledger rows did not record it (older rows have no trigger). */
59
+ key: string | null;
60
+ attempts: string;
61
+ totalTokens: string | null;
62
+ costMicroUsd: string | null;
63
+ costKnownAttempts: string;
64
+ }
65
+ export interface Breakdown {
66
+ dimension: BreakdownDimension;
67
+ rows: BreakdownRow[];
68
+ /** More groups exist than are shown. */
69
+ more: boolean;
70
+ }
71
+ export declare function breakdown(value: unknown, workspaceId: string, productKey: string, dimension: BreakdownDimension): Breakdown;
72
+ export interface CreditStatus {
73
+ provisioned: boolean;
74
+ noLimit: boolean;
75
+ exhausted: boolean;
76
+ used: number | null;
77
+ limit: number | null;
78
+ remaining: number | null;
79
+ }
80
+ export declare function credits(value: unknown, productKey: string): CreditStatus;