From 39db2c1a0da73507cca5c34cbefba55b5f6b3a6b Mon Sep 17 00:00:00 2001 From: Ankit Bhardwaj <97785108+Ankit6149@users.noreply.github.com> Date: Fri, 25 Sep 2026 04:17:25 -0700 Subject: [PATCH 1/8] Add bounded generation execution budget --- frontend/lib/ai/generationExecutionBudget.mjs | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 frontend/lib/ai/generationExecutionBudget.mjs diff --git a/frontend/lib/ai/generationExecutionBudget.mjs b/frontend/lib/ai/generationExecutionBudget.mjs new file mode 100644 index 00000000..496c5a5d --- /dev/null +++ b/frontend/lib/ai/generationExecutionBudget.mjs @@ -0,0 +1,114 @@ +export const DEFAULT_MAX_PROVIDER_REQUESTS = 40; +export const MAX_PROVIDER_REQUESTS = 40; + +function integer(value, fallback) { + const parsed = Number(value); + return Number.isInteger(parsed) ? parsed : fallback; +} + +export function resolveProviderRequestLimit(value) { + return Math.max(1, Math.min(MAX_PROVIDER_REQUESTS, integer(value, DEFAULT_MAX_PROVIDER_REQUESTS))); +} + +export function estimateGenerationRequestBudget(destinationCount, { maxRequests = DEFAULT_MAX_PROVIDER_REQUESTS } = {}) { + const destinations = Math.max(0, integer(destinationCount, 0)); + const plannedMaxRequests = 1 + (destinations * 3); + const hardMaxRequests = resolveProviderRequestLimit(maxRequests); + return Object.freeze({ + destinations, + plannedMaxRequests, + hardMaxRequests, + withinBudget: plannedMaxRequests <= hardMaxRequests, + }); +} + +export function generationRequestBudgetError({ plannedMaxRequests = null, maxRequests = DEFAULT_MAX_PROVIDER_REQUESTS } = {}) { + const error = new Error( + plannedMaxRequests + ? `This generation plan may require up to ${plannedMaxRequests} provider requests, above the hard limit of ${maxRequests}. Reduce destinations before generating.` + : `Generation reached the hard provider-request limit of ${maxRequests}. Reduce destinations or retry only the affected destination.`, + ); + error.code = "generation_request_budget_exceeded"; + error.status = 422; + return error; +} + +export function createGenerationExecutionBudget({ + maxRequests = DEFAULT_MAX_PROVIDER_REQUESTS, + now = () => Date.now(), +} = {}) { + const hardMaxRequests = resolveProviderRequestLimit(maxRequests); + const startedAt = now(); + let sequence = 0; + let completed = 0; + let failed = 0; + const entries = []; + + function begin({ provider = "", model = "", kind = "provider_request", destination = "", maxOutputTokens = 0 } = {}) { + if (entries.length >= hardMaxRequests) { + throw generationRequestBudgetError({ maxRequests: hardMaxRequests }); + } + const ticket = { + id: ++sequence, + provider: String(provider || ""), + model: String(model || ""), + kind: String(kind || "provider_request"), + destination: String(destination || ""), + maxOutputTokens: Math.max(0, Number(maxOutputTokens) || 0), + startedAt: now(), + finishedAt: null, + durationMs: null, + status: "running", + errorCode: "", + }; + entries.push(ticket); + return ticket; + } + + function finish(ticket, { ok = true, errorCode = "" } = {}) { + if (!ticket || ticket.finishedAt !== null) return; + ticket.finishedAt = now(); + ticket.durationMs = Math.max(0, ticket.finishedAt - ticket.startedAt); + ticket.status = ok ? "completed" : "failed"; + ticket.errorCode = ok ? "" : String(errorCode || "provider_request_failed"); + if (ok) completed += 1; + else failed += 1; + } + + function snapshot() { + const finishedAt = now(); + const byKind = {}; + let maxOutputTokens = 0; + for (const entry of entries) { + byKind[entry.kind] = (byKind[entry.kind] || 0) + 1; + maxOutputTokens += entry.maxOutputTokens; + } + return Object.freeze({ + requestCount: entries.length, + maxRequests: hardMaxRequests, + completedRequests: completed, + failedRequests: failed, + durationMs: Math.max(0, finishedAt - startedAt), + maxOutputTokens, + byKind: Object.freeze({ ...byKind }), + requests: Object.freeze(entries.map((entry) => Object.freeze({ + id: entry.id, + provider: entry.provider, + model: entry.model, + kind: entry.kind, + destination: entry.destination, + maxOutputTokens: entry.maxOutputTokens, + durationMs: entry.durationMs, + status: entry.status, + errorCode: entry.errorCode, + }))), + }); + } + + return Object.freeze({ + maxRequests: hardMaxRequests, + begin, + finish, + snapshot, + }); +} From ae5f9a55a0551b1c6bf1c6d86d5ea7da82fbd5cf Mon Sep 17 00:00:00 2001 From: Ankit Bhardwaj <97785108+Ankit6149@users.noreply.github.com> Date: Fri, 25 Sep 2026 04:17:46 -0700 Subject: [PATCH 2/8] Normalize provider request budget failures --- frontend/lib/ai/providerErrors.mjs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/lib/ai/providerErrors.mjs b/frontend/lib/ai/providerErrors.mjs index edb90f0d..e498ec96 100644 --- a/frontend/lib/ai/providerErrors.mjs +++ b/frontend/lib/ai/providerErrors.mjs @@ -7,6 +7,7 @@ const SAFE_CODES = Object.freeze({ PAYMENT_REQUIRED: "provider_payment_required", TIMEOUT: "provider_timeout", CANCELLED: "provider_request_cancelled", + REQUEST_BUDGET_EXCEEDED: "generation_request_budget_exceeded", UNAVAILABLE: "provider_unavailable", MALFORMED_RESPONSE: "provider_malformed_response", EMPTY_RESPONSE: "provider_empty_response", @@ -39,6 +40,9 @@ function classify(error) { if (error?.code === "provider_request_cancelled") { return { code: SAFE_CODES.CANCELLED, retryable: true, action: "retry_destination" }; } + if (error?.code === "generation_request_budget_exceeded") { + return { code: SAFE_CODES.REQUEST_BUDGET_EXCEEDED, retryable: false, action: "reduce_destinations" }; + } if (error?.name === "AbortError" || /timed?\s*out|timeout/.test(message)) { return { code: SAFE_CODES.TIMEOUT, retryable: true, action: "retry_destination" }; } @@ -86,6 +90,7 @@ function safeMessage(code, provider) { case SAFE_CODES.PAYMENT_REQUIRED: return `${label} requires billing or credits before generation can continue.`; case SAFE_CODES.TIMEOUT: return `${label} did not respond within the request limit.`; case SAFE_CODES.CANCELLED: return "Generation was cancelled before the provider request completed."; + case SAFE_CODES.REQUEST_BUDGET_EXCEEDED: return "Generation reached the configured provider-request budget. Reduce destinations or retry only the affected destination."; case SAFE_CODES.UNAVAILABLE: return `${label} is temporarily unavailable.`; case SAFE_CODES.MALFORMED_RESPONSE: return `${label} returned a response that could not be validated as the required JSON contract.`; case SAFE_CODES.EMPTY_RESPONSE: return `${label} returned no usable model output.`; From f9f49896ed39a0335e4a5d96f2c87ab435d8dfd5 Mon Sep 17 00:00:00 2001 From: Ankit Bhardwaj <97785108+Ankit6149@users.noreply.github.com> Date: Fri, 25 Sep 2026 04:17:49 -0700 Subject: [PATCH 3/8] Account every provider request --- frontend/lib/ai/generateText.js | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/frontend/lib/ai/generateText.js b/frontend/lib/ai/generateText.js index 83084f45..d40a567b 100644 --- a/frontend/lib/ai/generateText.js +++ b/frontend/lib/ai/generateText.js @@ -20,34 +20,47 @@ export async function generateText({ provider, prompt, modelOverride = null, con ...config, maxTokens: resolveOutputTokenBudget(prompt, config.maxTokens), }; + let budgetTicket = null; try { + budgetTicket = config.requestBudget?.begin?.({ + provider: p, + model: modelOverride || resolvedConfig.modelName || "", + kind: config.requestKind || "provider_request", + destination: config.destination || "", + maxOutputTokens: resolvedConfig.maxTokens, + }) || null; + let result; switch (p) { case "vercel_gateway": - return await generateVercelGateway(prompt, modelOverride, resolvedConfig); + result = await generateVercelGateway(prompt, modelOverride, resolvedConfig); break; case "openai": - return await generateOpenAI(prompt, modelOverride, resolvedConfig); + result = await generateOpenAI(prompt, modelOverride, resolvedConfig); break; case "claude": - return await generateClaude(prompt, modelOverride, resolvedConfig); + result = await generateClaude(prompt, modelOverride, resolvedConfig); break; case "gemini": - return await generateGemini(prompt, modelOverride, resolvedConfig); + result = await generateGemini(prompt, modelOverride, resolvedConfig); break; case "groq": - return await generateGroq(prompt, modelOverride, resolvedConfig); + result = await generateGroq(prompt, modelOverride, resolvedConfig); break; case "openrouter": - return await generateOpenRouter(prompt, modelOverride, resolvedConfig); + result = await generateOpenRouter(prompt, modelOverride, resolvedConfig); break; case "ollama": - return await generateOllama(prompt, modelOverride, resolvedConfig); + result = await generateOllama(prompt, modelOverride, resolvedConfig); break; case "lmstudio": - return await generateLMStudio(prompt, modelOverride, resolvedConfig); + result = await generateLMStudio(prompt, modelOverride, resolvedConfig); break; case "custom": - return await generateCustomOpenAI(prompt, modelOverride, resolvedConfig); + result = await generateCustomOpenAI(prompt, modelOverride, resolvedConfig); break; default: throw new Error(`Text generation not supported for provider mode: "${provider}"`); } + config.requestBudget?.finish?.(budgetTicket, { ok: true }); + return result; } catch (error) { - throw normalizeProviderError(error, { + const normalized = normalizeProviderError(error, { provider: p, model: modelOverride || "", }); + config.requestBudget?.finish?.(budgetTicket, { ok: false, errorCode: normalized.code }); + throw normalized; } } From 56976f218c7ec57fc24b5d20cd9c9f20f437c30c Mon Sep 17 00:00:00 2001 From: Ankit Bhardwaj <97785108+Ankit6149@users.noreply.github.com> Date: Fri, 25 Sep 2026 04:18:28 -0700 Subject: [PATCH 4/8] Record generation repair request count --- frontend/lib/ai/generationExecutionBudget.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/lib/ai/generationExecutionBudget.mjs b/frontend/lib/ai/generationExecutionBudget.mjs index 496c5a5d..9f0a56d3 100644 --- a/frontend/lib/ai/generationExecutionBudget.mjs +++ b/frontend/lib/ai/generationExecutionBudget.mjs @@ -83,9 +83,11 @@ export function createGenerationExecutionBudget({ byKind[entry.kind] = (byKind[entry.kind] || 0) + 1; maxOutputTokens += entry.maxOutputTokens; } + const retryRequests = (byKind.destination_revision || 0) + (byKind.duplicate_repair || 0); return Object.freeze({ requestCount: entries.length, maxRequests: hardMaxRequests, + retryRequests, completedRequests: completed, failedRequests: failed, durationMs: Math.max(0, finishedAt - startedAt), From 3487916222cf6708b374ef2d59864baf3e570fb1 Mon Sep 17 00:00:00 2001 From: Ankit Bhardwaj <97785108+Ankit6149@users.noreply.github.com> Date: Fri, 25 Sep 2026 04:18:45 -0700 Subject: [PATCH 5/8] Plan and record bounded generation execution --- frontend/lib/ai/generateStudioPackage.js | 51 +++++++++++++++++++----- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/frontend/lib/ai/generateStudioPackage.js b/frontend/lib/ai/generateStudioPackage.js index 83573cf5..a0e6ab63 100644 --- a/frontend/lib/ai/generateStudioPackage.js +++ b/frontend/lib/ai/generateStudioPackage.js @@ -10,6 +10,11 @@ import { evaluateStrategyQuality, STRATEGY_QUALITY_STATES } from "./strategyQual import { duplicateRevisionTargets } from "./crossChannelQuality.mjs"; import { normalizeProviderError, providerErrorPayload } from "./providerErrors.mjs"; import { mapWithConcurrency } from "./generationConcurrency.mjs"; +import { + createGenerationExecutionBudget, + estimateGenerationRequestBudget, + generationRequestBudgetError, +} from "./generationExecutionBudget.mjs"; import { CHANNEL_CONTRACTS, assessChannelDraft, @@ -181,7 +186,7 @@ async function generateDestination({ provider, prompt: buildChannelPrompt({ channel, context, campaignBrief }), modelOverride, - config, + config: { ...config, requestKind: "destination_initial", destination: channel }, }); firstDraft = normalizeDestinationDraft(firstRaw, channel, generationInputs); firstQuality = assessChannelDraft(channel, firstDraft, { @@ -215,7 +220,7 @@ async function generateDestination({ qualityIssues: firstQuality.issues, }), modelOverride, - config, + config: { ...config, requestKind: "destination_revision", destination: channel }, }); const revisedDraft = normalizeDestinationDraft(revisedRaw, channel, generationInputs); const revisedQuality = assessChannelDraft(channel, revisedDraft, { @@ -288,7 +293,7 @@ async function reviseDuplicateDestination({ qualityIssues: [target.guidance], }), modelOverride, - config, + config: { ...config, requestKind: "duplicate_repair", destination: target.channel }, }); const revisedDraft = normalizeDestinationDraft(revisedRaw, target.channel, generationInputs); const quality = assessChannelDraft(target.channel, revisedDraft, { @@ -409,13 +414,28 @@ export async function generateStudioPackage(inputs) { } const modelOverride = model_name || config?.modelName || providerMeta.defaultModel; + let requestBudget = config.requestBudget || null; + let requestBudgetPlan = null; try { + const hardRequestLimit = requestBudget?.maxRequests ?? config.maxProviderRequests; + requestBudgetPlan = estimateGenerationRequestBudget(channels.length, { maxRequests: hardRequestLimit }); + if (!requestBudgetPlan.withinBudget) { + throw generationRequestBudgetError({ + plannedMaxRequests: requestBudgetPlan.plannedMaxRequests, + maxRequests: requestBudgetPlan.hardMaxRequests, + }); + } + requestBudget = requestBudget || createGenerationExecutionBudget({ + maxRequests: requestBudgetPlan.hardMaxRequests, + }); + const executionConfig = { ...config, requestBudget }; + const rawBrief = await generateJSON({ provider: generator, prompt: campaignBriefPrompt, modelOverride, - config, + config: { ...executionConfig, requestKind: "strategy" }, }); const pkg = normalizePackage(rawBrief, generationInputs, { allowTemplateFallback: false }); @@ -433,6 +453,10 @@ export async function generateStudioPackage(inputs) { code: "strategy_quality_blocked", providerUsed: generator, fallbackUsed: false, + generation_execution: { + plan: requestBudgetPlan, + actual: requestBudget.snapshot(), + }, strategy_review: { status: strategyQuality.status, issues: strategyQuality.issues, @@ -459,10 +483,10 @@ export async function generateStudioPackage(inputs) { generationInputs, provider: generator, modelOverride, - config, - }), config.destinationConcurrency, { signal: config.signal }); + config: executionConfig, + }), executionConfig.destinationConcurrency, { signal: executionConfig.signal }); - if (config.signal?.aborted) { + if (executionConfig.signal?.aborted) { const cancelled = new Error("Generation request was cancelled."); cancelled.code = "provider_request_cancelled"; cancelled.status = 499; @@ -489,9 +513,9 @@ export async function generateStudioPackage(inputs) { generationInputs, provider: generator, modelOverride, - config, - }), config.destinationConcurrency, { signal: config.signal }); - if (config.signal?.aborted) { + config: executionConfig, + }), executionConfig.destinationConcurrency, { signal: executionConfig.signal }); + if (executionConfig.signal?.aborted) { const cancelled = new Error("Generation request was cancelled."); cancelled.code = "provider_request_cancelled"; cancelled.status = 499; @@ -547,6 +571,11 @@ export async function generateStudioPackage(inputs) { throw new Error(`Every selected destination failed: ${failedDestinations.map((item) => item.channel).join(", ")}.`); } + const generationExecution = { + plan: requestBudgetPlan, + actual: requestBudget.snapshot(), + }; + pkg.generation = { mode: "staged_agent", provider: generator, @@ -555,6 +584,7 @@ export async function generateStudioPackage(inputs) { strategyQuality, duplicateRevisionTargets: duplicateTargets, unresolvedDuplicateRevisionTargets: unresolvedDuplicateTargets, + execution: generationExecution, destinations: generationStatus, }; @@ -570,6 +600,7 @@ export async function generateStudioPackage(inputs) { providerUsed: generator, fallbackUsed: false, partialFailureUsed, + generation_execution: generationExecution, generation_status: generationStatus, chatbot_prompt: campaignBriefPrompt, warnings: Array.from(new Set([...contextWarnings, ...generationWarnings])), From 1058fb13d0985ccc3f19454e9eb665e4dfeac51b Mon Sep 17 00:00:00 2001 From: Ankit Bhardwaj <97785108+Ankit6149@users.noreply.github.com> Date: Fri, 25 Sep 2026 04:18:49 -0700 Subject: [PATCH 6/8] Add request-budget recovery guidance --- frontend/app/page.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/app/page.js b/frontend/app/page.js index 4d20e8cb..c62b8946 100644 --- a/frontend/app/page.js +++ b/frontend/app/page.js @@ -274,6 +274,7 @@ function providerRecoveryMessage(providerError) { wait_then_retry: "Wait for the provider rate limit to clear, then retry deliberately.", choose_model: "Choose a model that exists for this provider.", retry_destination: "Retry the affected destination; successful destinations remain unchanged.", + reduce_destinations: "Reduce the number of destinations or retry only the affected destination.", retry_or_choose_model: "Retry once, then choose another model if the response contract still fails.", choose_provider: "Choose a supported provider route.", retry_or_contact_owner: "Retry once. If it persists, inspect owner/server diagnostics using the correlation ID.", From 94be02f67c7a492f2be17d62bdd0e61b66e3949b Mon Sep 17 00:00:00 2001 From: Ankit Bhardwaj <97785108+Ankit6149@users.noreply.github.com> Date: Fri, 25 Sep 2026 04:19:26 -0700 Subject: [PATCH 7/8] Test bounded provider request execution metrics --- .../tests/generationExecutionBudget.test.mjs | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 frontend/tests/generationExecutionBudget.test.mjs diff --git a/frontend/tests/generationExecutionBudget.test.mjs b/frontend/tests/generationExecutionBudget.test.mjs new file mode 100644 index 00000000..1430e963 --- /dev/null +++ b/frontend/tests/generationExecutionBudget.test.mjs @@ -0,0 +1,126 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; + +import { + DEFAULT_MAX_PROVIDER_REQUESTS, + MAX_PROVIDER_REQUESTS, + createGenerationExecutionBudget, + estimateGenerationRequestBudget, + generationRequestBudgetError, + resolveProviderRequestLimit, +} from "../lib/ai/generationExecutionBudget.mjs"; +import { + PROVIDER_ERROR_CODES, + normalizeProviderError, + providerErrorPayload, +} from "../lib/ai/providerErrors.mjs"; + +test("twelve destinations fit the hard worst-case provider request budget", () => { + const plan = estimateGenerationRequestBudget(12); + assert.equal(plan.plannedMaxRequests, 37); + assert.equal(plan.hardMaxRequests, DEFAULT_MAX_PROVIDER_REQUESTS); + assert.equal(MAX_PROVIDER_REQUESTS, 40); + assert.equal(plan.withinBudget, true); +}); + +test("request budget cannot be raised above the server hard ceiling", () => { + assert.equal(resolveProviderRequestLimit(4000), MAX_PROVIDER_REQUESTS); + assert.equal(resolveProviderRequestLimit(0), 1); + const blocked = estimateGenerationRequestBudget(12, { maxRequests: 36 }); + assert.equal(blocked.withinBudget, false); + assert.equal(blocked.plannedMaxRequests, 37); +}); + +test("execution budget blocks another provider call after the hard request count", () => { + let now = 1_000; + const budget = createGenerationExecutionBudget({ + maxRequests: 2, + now: () => now, + }); + + const first = budget.begin({ + provider: "gemini", + model: "model-a", + kind: "strategy", + maxOutputTokens: 1000, + }); + now += 20; + budget.finish(first, { ok: true }); + + const second = budget.begin({ + provider: "gemini", + model: "model-a", + kind: "destination_revision", + destination: "linkedin", + maxOutputTokens: 2000, + }); + now += 30; + budget.finish(second, { ok: false, errorCode: "provider_timeout" }); + + assert.throws( + () => budget.begin({ provider: "gemini", model: "model-a" }), + (error) => error?.code === "generation_request_budget_exceeded" && error?.status === 422, + ); + + const snapshot = budget.snapshot(); + assert.equal(snapshot.requestCount, 2); + assert.equal(snapshot.completedRequests, 1); + assert.equal(snapshot.failedRequests, 1); + assert.equal(snapshot.retryRequests, 1); + assert.equal(snapshot.maxOutputTokens, 3000); + assert.equal(snapshot.durationMs, 50); + assert.deepEqual(snapshot.byKind, { + strategy: 1, + destination_revision: 1, + }); + assert.equal(snapshot.requests[1].destination, "linkedin"); + assert.equal(snapshot.requests[1].durationMs, 30); + assert.equal("prompt" in snapshot.requests[0], false); +}); + +test("request budget failure normalizes without leaking internal generation content", () => { + const raw = generationRequestBudgetError({ plannedMaxRequests: 41, maxRequests: 40 }); + const safe = providerErrorPayload(normalizeProviderError(raw, { + provider: "gemini", + model: "model-a", + })); + assert.equal(safe.code, PROVIDER_ERROR_CODES.REQUEST_BUDGET_EXCEEDED); + assert.equal(safe.retryable, false); + assert.equal(safe.recoveryAction, "reduce_destinations"); + assert.equal(safe.httpStatus, 422); + assert.doesNotMatch(safe.message, /prompt|source text|private/i); +}); + +test("campaign generation plans before spend and records safe execution telemetry", async () => { + const source = await readFile(new URL("../lib/ai/generateStudioPackage.js", import.meta.url), "utf8"); + const plan = source.indexOf("estimateGenerationRequestBudget(channels.length"); + const budget = source.indexOf("createGenerationExecutionBudget({"); + const firstProviderCall = source.indexOf("const rawBrief = await generateJSON({"); + assert.ok(plan >= 0); + assert.ok(budget > plan); + assert.ok(firstProviderCall > budget); + assert.match(source, /requestKind: "strategy"/); + assert.match(source, /requestKind: "destination_initial"/); + assert.match(source, /requestKind: "destination_revision"/); + assert.match(source, /requestKind: "duplicate_repair"/); + assert.match(source, /generation_execution: generationExecution/); + assert.match(source, /destinations: generationStatus/); +}); + +test("provider calls are accounted centrally before adapter invocation", async () => { + const source = await readFile(new URL("../lib/ai/generateText.js", import.meta.url), "utf8"); + const begin = source.indexOf("requestBudget?.begin"); + const firstAdapter = source.indexOf("generateVercelGateway("); + const success = source.indexOf("requestBudget?.finish?.(budgetTicket, { ok: true })"); + const failure = source.indexOf("requestBudget?.finish?.(budgetTicket, { ok: false"); + assert.ok(begin >= 0); + assert.ok(firstAdapter > begin); + assert.ok(success > firstAdapter); + assert.ok(failure > success); +}); + +test("Studio exposes a specific request-budget recovery action", async () => { + const page = await readFile(new URL("../app/page.js", import.meta.url), "utf8"); + assert.match(page, /reduce_destinations: "Reduce the number of destinations or retry only the affected destination\."/); +}); From 0d475e25fdba9f975d1e6886ac12f4595afd06c1 Mon Sep 17 00:00:00 2001 From: Ankit Bhardwaj <97785108+Ankit6149@users.noreply.github.com> Date: Fri, 25 Sep 2026 04:20:46 -0700 Subject: [PATCH 8/8] Update cancellation assertion for execution config --- frontend/tests/generationQualityContracts.test.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/tests/generationQualityContracts.test.mjs b/frontend/tests/generationQualityContracts.test.mjs index 26ae2d16..b1c9c604 100644 --- a/frontend/tests/generationQualityContracts.test.mjs +++ b/frontend/tests/generationQualityContracts.test.mjs @@ -403,7 +403,7 @@ test("generation cancellation is propagated through API, providers, UI, and camp ]; assert.match(routeSource, /signal: request\.signal/); - assert.match(packageSource, /\{ signal: config\.signal \}/); + assert.match(packageSource, /\{ signal: executionConfig\.signal \}/); assert.match(pageSource, /Cancel generation/); assert.match(pageSource, /MARK_CHANNELS_CANCELLED/);