Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions actions/setup/js/handle_agent_failure.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -1626,10 +1626,11 @@ function buildTimeoutContext(isTimedOut, timeoutMinutes) {
* @param {string} agentConclusion
* @param {boolean} hasToolDenialsExceeded
* @param {boolean} isTimedOut
* @param {boolean} hasMissingModelPricingError
* @returns {boolean}
*/
function shouldBuildEngineFailureContext(agentConclusion, hasToolDenialsExceeded, isTimedOut) {
return agentConclusion === "failure" && !hasToolDenialsExceeded && !isTimedOut;
function shouldBuildEngineFailureContext(agentConclusion, hasToolDenialsExceeded, isTimedOut, hasMissingModelPricingError = false) {
return agentConclusion === "failure" && !hasToolDenialsExceeded && !isTimedOut && !hasMissingModelPricingError;
}

/**
Expand Down Expand Up @@ -1836,13 +1837,17 @@ function quoteYAMLKey(value) {
* @param {{input: number, output: number, cacheRead?: number, cacheWrite?: number}|null} pricing Per-million-token values from models.dev
* @returns {string|null}
*/
function buildModelPricingFrontmatterSnippet(modelName, engineId, pricing) {
function buildModelPricingFrontmatterSnippet(modelName, engineId, pricing, isPlaceholderPricing = false) {
if (!modelName || !pricing) return null;
const provider = inferProviderKeyFromEngineId(engineId);
const inputStr = formatPerTokenPrice(pricing.input);
const outputStr = formatPerTokenPrice(pricing.output);
const quotedModelName = quoteYAMLKey(modelName);
let costBlock = ` input: "${inputStr}" # $${pricing.input.toFixed(2)} per million input tokens\n`;
let costBlock = "";
if (isPlaceholderPricing) {
costBlock += " # Placeholder values — replace with actual pricing for this model\n";
}
costBlock += ` input: "${inputStr}" # $${pricing.input.toFixed(2)} per million input tokens\n`;
costBlock += ` output: "${outputStr}" # $${pricing.output.toFixed(2)} per million output tokens\n`;
if (pricing.cacheRead !== undefined) {
costBlock += ` cache_read: "${formatPerTokenPrice(pricing.cacheRead)}" # $${pricing.cacheRead.toFixed(2)} per million cache-read tokens\n`;
Expand All @@ -1855,7 +1860,7 @@ models:
providers:
${provider}:
models:
${quotedModelName}:
${quotedModelName}:
cost:
${costBlock.trimEnd()}
\`\`\``;
Expand All @@ -1868,7 +1873,7 @@ ${costBlock.trimEnd()}
* @returns {string|null}
*/
function buildManualModelPricingFrontmatterSnippet(modelName, engineId) {
return buildModelPricingFrontmatterSnippet(modelName, engineId, { input: 0, output: 0 });
return buildModelPricingFrontmatterSnippet(modelName, engineId, { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, true);
}

/**
Expand Down Expand Up @@ -3657,7 +3662,9 @@ async function main() {
// Suppress when tool-denials-exceeded is present: the engine termination is a
// direct consequence of the SDK hitting the denial threshold, so the tool-denials
// context is the more actionable signal.
const engineFailureContext = shouldBuildEngineFailureContext(agentConclusion, hasToolDenialsExceeded, isTimedOut) ? buildEngineFailureContext({ suppressEngineRateLimit429: maxAICreditsExceeded }) : "";
// Also suppress when missing-model-pricing is detected: the pricing error is the
// root cause and the engine error block would be redundant noise.
const engineFailureContext = shouldBuildEngineFailureContext(agentConclusion, hasToolDenialsExceeded, isTimedOut, missingModelPricingError) ? buildEngineFailureContext({ suppressEngineRateLimit429: maxAICreditsExceeded }) : "";
// Build timeout context
const timeoutContext = buildTimeoutContext(isTimedOut, timeoutMinutes);

Expand Down Expand Up @@ -3876,7 +3883,9 @@ async function main() {
// Suppress when tool-denials-exceeded is present: the engine termination is a
// direct consequence of the SDK hitting the denial threshold, so the tool-denials
// context is the more actionable signal.
const engineFailureContext = shouldBuildEngineFailureContext(agentConclusion, hasToolDenialsExceeded, isTimedOut) ? buildEngineFailureContext({ suppressEngineRateLimit429: maxAICreditsExceeded }) : "";
// Also suppress when missing-model-pricing is detected: the pricing error is the
// root cause and the engine error block would be redundant noise.
const engineFailureContext = shouldBuildEngineFailureContext(agentConclusion, hasToolDenialsExceeded, isTimedOut, missingModelPricingError) ? buildEngineFailureContext({ suppressEngineRateLimit429: maxAICreditsExceeded }) : "";

// Build timeout context
const timeoutContext = buildTimeoutContext(isTimedOut, timeoutMinutes);
Expand Down
7 changes: 7 additions & 0 deletions actions/setup/js/handle_agent_failure.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2229,6 +2229,10 @@ describe("handle_agent_failure", () => {
expect(shouldBuildEngineFailureContext("failure", true, false)).toBe(false);
});

it("returns false when missing model pricing error is present", () => {
expect(shouldBuildEngineFailureContext("failure", false, false, true)).toBe(false);
});

it("returns false for non-failure conclusions", () => {
expect(shouldBuildEngineFailureContext("timed_out", false, true)).toBe(false);
expect(shouldBuildEngineFailureContext("success", false, false)).toBe(false);
Expand Down Expand Up @@ -3211,6 +3215,9 @@ describe("handle_agent_failure", () => {
expect(result).toContain("anthropic:");
expect(result).toContain("'model: alias':");
expect(result).toContain('input: "0e0"');
expect(result).toContain('cache_read: "0e0"');
expect(result).toContain('cache_write: "0e0"');
expect(result).toContain("Placeholder values");
});

it("throws when template is missing and error is true", async () => {
Expand Down
2 changes: 2 additions & 0 deletions actions/setup/js/safe_output_type_validator.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ function validateIssueIntentLabels(value, lineNum, itemType, fieldName, options)
* @property {number} defaultMax - Default max count for this type
* @property {Object.<string, FieldValidation>} fields - Field validation rules
* @property {string} [customValidation] - Custom validation rule identifier
* @property {boolean} [dataEnabled] - Whether structured data is enabled for this type
* @property {any} [dataSchema] - Optional schema used to validate structured data
*/

/** @type {Object.<string, TypeValidationConfig>|null} */
Expand Down
2 changes: 1 addition & 1 deletion actions/setup/js/safe_outputs_handlers.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ function createHandlers(server, appendSafeOutput, config = {}) {
if (!dataEnabled) {
return buildIntentErrorResponse(`${type} data is not enabled (set safe-outputs.data in workflow frontmatter)`);
}
let dataSchema = null;
let dataSchema;
try {
if (toolConfig?.data_schema !== undefined) {
dataSchema = resolveDataSchema(toolConfig.data_schema, `safe-outputs.${type}.data`);
Expand Down
Loading