This commit is contained in:
johnyeo
2026-06-15 10:19:57 +01:00
5 changed files with 369 additions and 0 deletions

View File

@@ -20,6 +20,16 @@ import { createBraintrustLogger } from "../../../providers/braintrust/index.js";
import type { AgentEngine } from "../types.js";
const client = new Anthropic();
const AUTH_FAILURE_PATTERN =
/invalid or expired access token|request failed \(401\)/i;
const isAutumnAuthFailure = (output: unknown) => {
try {
return AUTH_FAILURE_PATTERN.test(JSON.stringify(output) ?? "");
} catch {
return false;
}
};
// initLogger sets Braintrust's ambient logger so traced()/spans are recorded.
const braintrustLogger = createBraintrustLogger();
const braintrustEnabled = Boolean(braintrustLogger);

View File

@@ -113,6 +113,22 @@ export const cmaRepo = {
return row;
},
// Forces the next ensureAutumnVault to resync tokens into the vault.
markVaultStale: async ({
db,
env,
orgId,
}: {
db: ChatDb;
env: AppEnv;
orgId: string;
}) => {
await db
.update(cmaVaults)
.set({ updated_at: 0 })
.where(and(eq(cmaVaults.org_id, orgId), eq(cmaVaults.env, env)));
},
upsertVault: async ({
credentialId,
db,

View File

@@ -824,6 +824,7 @@ export const approvalStatusCard = ({
),
]
: []),
...(config ? [CardText(config, { style: "muted" })] : []),
],
});
};

View File

@@ -0,0 +1,185 @@
import type { CardChild } from "chat";
import { CardText, Table } from "chat";
import { format } from "date-fns";
type LooseRecord = Record<string, unknown>;
const MAX_LINE_ITEM_ROWS = 10;
const asRecord = (value: unknown): LooseRecord | null =>
value && typeof value === "object" && !Array.isArray(value)
? (value as LooseRecord)
: null;
const parseJson = (text: string): unknown => {
try {
return JSON.parse(text);
} catch {
return null;
}
};
export const formatEpochDate = (epochMs: number) =>
format(epochMs, "MMM d, yyyy");
// Amounts are major currency units (the schema's "in cents" wording is stale —
// the dashboard renders these values directly).
const formatMoney = (amount: number, currency: string) => {
try {
return new Intl.NumberFormat("en-US", {
currency: currency.toUpperCase(),
currencyDisplay: "narrowSymbol",
style: "currency",
}).format(amount);
} catch {
return `$${amount.toFixed(2)}`;
}
};
// Unwraps MCP transport shapes around the preview payload: JSON strings,
// [{text}] content arrays, {content} results, and the {preview, pending} wrapper.
export const parsePreviewPayload = (preview: unknown): LooseRecord | null => {
if (typeof preview === "string") {
const parsed = parseJson(preview.trim());
return parsed ? parsePreviewPayload(parsed) : null;
}
if (Array.isArray(preview)) {
for (const entry of preview) {
const record = asRecord(entry);
if (typeof record?.text !== "string") continue;
const parsed = parsePreviewPayload(record.text);
if (parsed) return parsed;
}
return null;
}
const record = asRecord(preview);
if (!record) return null;
if (Array.isArray(record.content)) return parsePreviewPayload(record.content);
if ("preview" in record) return parsePreviewPayload(record.preview);
return record;
};
const UPDATE_INTENT_LABELS: Record<string, string> = {
cancel_end_of_cycle: "Cancel at end of cycle",
cancel_immediately: "Cancel immediately",
uncancel: "Uncancel",
update_plan: "Update plan",
update_quantity: "Update quantity",
};
const lineItemRows = ({
lineItems,
currency,
}: {
lineItems: unknown[];
currency: string;
}) => {
const items = lineItems.flatMap((item) => {
const record = asRecord(item);
return typeof record?.display_name === "string" &&
typeof record.total === "number"
? [{ name: record.display_name, total: record.total }]
: [];
});
const rows = items
.slice(0, MAX_LINE_ITEM_ROWS)
.map((item) => [item.name, formatMoney(item.total, currency)]);
if (items.length > MAX_LINE_ITEM_ROWS) {
rows.push([`+${items.length - MAX_LINE_ITEM_ROWS} more items`, ""]);
}
return rows;
};
// attach / createSchedule / updateSubscription previews all share the
// BillingPreviewResponse shape (line_items, total, currency, next_cycle).
// Rendered receipt-style: one table holding line items AND total rows.
const billingPreviewElements = (payload: LooseRecord): CardChild[] => {
const currency =
typeof payload.currency === "string" ? payload.currency : "usd";
const rows = lineItemRows({
lineItems: payload.line_items as unknown[],
currency,
});
const nextCycle = asRecord(payload.next_cycle);
const intentLabel =
typeof payload.intent === "string"
? UPDATE_INTENT_LABELS[payload.intent]
: undefined;
rows.push(["Due now", formatMoney(payload.total as number, currency)]);
if (
typeof nextCycle?.total === "number" &&
typeof nextCycle.starts_at === "number"
) {
rows.push([
`Next cycle · ${formatEpochDate(nextCycle.starts_at)}`,
formatMoney(nextCycle.total, currency),
]);
}
const notes = [
intentLabel ? `Change: ${intentLabel}` : null,
payload.redirect_to_checkout === true
? "Customer pays via checkout link"
: null,
].filter((note): note is string => Boolean(note));
return [
Table({ align: ["left", "right"], headers: ["Item", "Amount"], rows }),
...(notes.length
? [CardText(notes.join(" · "), { style: "muted" })]
: []),
];
};
const balancePreviewElements = (payload: LooseRecord): CardChild[] | null => {
const request = asRecord(payload.request);
if (!request) return null;
const reset = asRecord(request.reset);
const fields = [
["Feature", request.feature_id],
[
"Grant",
request.unlimited === true ? "Unlimited" : request.included_grant,
],
[
"Expires",
typeof request.expires_at === "number"
? formatEpochDate(request.expires_at)
: null,
],
[
"Resets",
typeof reset?.interval === "string"
? `Every ${typeof reset.interval_count === "number" && reset.interval_count > 1 ? `${reset.interval_count} ${reset.interval}s` : reset.interval}`
: null,
],
].flatMap(([label, value]) =>
typeof value === "string" || typeof value === "number"
? [`**${label}** ${value}`]
: [],
);
return [
...(fields.length ? [CardText(fields.join("\n"))] : []),
...(typeof payload.impact === "string"
? [CardText(payload.impact, { style: "muted" })]
: []),
];
};
/** Structured card body for a preview payload, or null to fall back to text. */
export const previewElements = (preview: unknown): CardChild[] | null => {
const payload = parsePreviewPayload(preview);
if (!payload) return null;
if (Array.isArray(payload.line_items) && typeof payload.total === "number") {
return billingPreviewElements(payload);
}
if (payload.action === "createBalance") {
return balancePreviewElements(payload);
}
return null;
};

View File

@@ -0,0 +1,157 @@
import { describe, expect, test } from "bun:test";
import { approvalCard } from "../../../src/ui/blocks.js";
import {
parsePreviewPayload,
previewElements,
} from "../../../src/ui/previewContent.js";
const attachPreview = {
object: "attach_preview",
customer_id: "cus_1",
currency: "usd",
line_items: [
{
display_name: "Pro Checkout",
description: "Pro Checkout - Base Price",
subtotal: 20,
total: 20,
discounts: [],
},
{
display_name: "Messages",
description: "Prepaid usage",
subtotal: 5.5,
total: 5.5,
discounts: [],
},
],
subtotal: 25.5,
total: 25.5,
next_cycle: { starts_at: 1812731225000, subtotal: 40, total: 40 },
redirect_to_checkout: false,
};
describe("parsePreviewPayload", () => {
test("unwraps MCP content arrays of JSON text", () => {
const payload = parsePreviewPayload([
{ type: "text", text: JSON.stringify(attachPreview) },
]);
expect(payload?.customer_id).toBe("cus_1");
});
test("unwraps the agent {preview, pending} wrapper", () => {
const payload = parsePreviewPayload({
preview: attachPreview,
pending: true,
message: "Preview ready",
});
expect(payload?.object).toBe("attach_preview");
});
test("returns null for model prose", () => {
expect(parsePreviewPayload("I'll preview this now!")).toBeNull();
});
});
describe("previewElements", () => {
test("renders billing previews as a line item table with totals", () => {
const elements = previewElements(attachPreview);
const json = JSON.stringify(elements);
expect(elements?.[0]?.type).toBe("table");
expect(json).toContain("Pro Checkout");
expect(json).toContain("$20.00");
expect(json).toContain("$5.50");
expect(json).toContain("Due now");
expect(json).toContain("Next cycle");
expect(json).toContain("$40.00");
});
test("labels update subscription intents", () => {
const json = JSON.stringify(
previewElements({
...attachPreview,
object: "update_subscription_preview",
intent: "cancel_end_of_cycle",
}),
);
expect(json).toContain("Cancel at end of cycle");
});
test("renders createBalance local previews as fields", () => {
const json = JSON.stringify(
previewElements({
action: "createBalance",
request: {
customer_id: "cus_1",
feature_id: "credits",
included_grant: 500,
expires_at: 1812731225000,
},
impact: "Creates a standalone balance grant.",
}),
);
expect(json).toContain("credits");
expect(json).toContain("500");
expect(json).toContain("Expires");
expect(json).toContain("standalone balance grant");
});
});
describe("approvalCard with structured previews", () => {
test("uses the structured renderer instead of text scraping", () => {
const card = approvalCard({
id: "approval_1",
toolName: "attach",
toolArgs: { request: { customer_id: "cus_1", plan_id: "pro" } },
preview: attachPreview,
});
const json = JSON.stringify(card);
expect(json).toContain("table");
expect(json).toContain("Due now");
expect(card.children.at(-1)?.type).toBe("actions");
});
test("moves environment to subtitle and config to a muted line", () => {
const card = approvalCard({
id: "approval_1",
env: "sandbox" as never,
toolName: "attach",
toolArgs: {
request: {
customer_id: "cus_1",
plan_id: "pro",
redirect_mode: "if_required",
invoice_mode: { enabled: true, finalize: false },
},
},
preview: attachPreview,
});
expect(card.subtitle).toContain("Sandbox");
const muted = card.children.filter(
(child) => child.type === "text" && child.style === "muted",
);
const mutedJson = JSON.stringify(muted);
expect(mutedJson).toContain("Redirect: if_required");
expect(mutedJson).toContain("Invoice: enabled, draft invoice");
expect(JSON.stringify(card.children)).not.toContain("Environment");
});
test("shows schedule start dates from tool args", () => {
const card = approvalCard({
id: "approval_2",
toolName: "createSchedule",
toolArgs: {
request: {
customer_id: "cus_1",
plan_id: "pro",
starts_at: 1812731225000,
},
},
preview: attachPreview,
});
expect(JSON.stringify(card)).toContain("Starts");
});
});