(
request: Pick<StreamRequestConfig, "hasQueuedMessages" | "toolPolicy">
)
| 1457 | } |
| 1458 | |
| 1459 | private createStopWhenCondition( |
| 1460 | request: Pick<StreamRequestConfig, "hasQueuedMessages" | "toolPolicy"> |
| 1461 | ): Array<ReturnType<typeof stepCountIs>> { |
| 1462 | // Completion-tool stop check: completion/routing tools use explicit |
| 1463 | // success/ok markers (agent_report, propose_plan). |
| 1464 | // When a marker is present, respect it — success:false means the tool |
| 1465 | // should be retried, so don't stop. When no marker is present (e.g., |
| 1466 | // MCP tools, arbitrary required tools), treat non-null object results |
| 1467 | // as successful completion unless the result is error-shaped. |
| 1468 | const isSuccessfulOutput = (output: unknown): boolean => { |
| 1469 | if (typeof output !== "object" || output === null) { |
| 1470 | return false; |
| 1471 | } |
| 1472 | const parsedOutput = output as Record<string, unknown>; |
| 1473 | if ("success" in parsedOutput) { |
| 1474 | return parsedOutput.success === true; |
| 1475 | } |
| 1476 | if ("ok" in parsedOutput) { |
| 1477 | return parsedOutput.ok === true; |
| 1478 | } |
| 1479 | if (parsedOutput.error != null || parsedOutput.isError === true) { |
| 1480 | return false; |
| 1481 | } |
| 1482 | return true; |
| 1483 | }; |
| 1484 | |
| 1485 | const requiredPatterns = (request.toolPolicy ?? []) |
| 1486 | .filter((filter) => filter.action === "require") |
| 1487 | .map((filter) => { |
| 1488 | // Strip existing anchors to avoid double-anchoring recovery policies |
| 1489 | // (e.g. "^agent_report$" would otherwise become "^^agent_report$$"). |
| 1490 | const rawPattern = filter.regex_match.replace(/^\^/, "").replace(/\$$/, ""); |
| 1491 | return new RegExp(`^${rawPattern}$`); |
| 1492 | }); |
| 1493 | |
| 1494 | const hasSuccessfulRequiredToolResult: ReturnType<typeof stepCountIs> = ({ steps }) => { |
| 1495 | if (requiredPatterns.length === 0) { |
| 1496 | return false; |
| 1497 | } |
| 1498 | const lastStep = steps[steps.length - 1]; |
| 1499 | return ( |
| 1500 | lastStep?.toolResults?.some( |
| 1501 | (toolResult) => |
| 1502 | requiredPatterns.some((pattern) => pattern.test(toolResult.toolName)) && |
| 1503 | isSuccessfulOutput(toolResult.output) |
| 1504 | ) ?? false |
| 1505 | ); |
| 1506 | }; |
| 1507 | |
| 1508 | return [ |
| 1509 | stepCountIs(100000), |
| 1510 | () => request.hasQueuedMessages?.("tool-end") ?? false, |
| 1511 | hasSuccessfulRequiredToolResult, |
| 1512 | ]; |
| 1513 | } |
| 1514 | |
| 1515 | private createStreamResult( |
| 1516 | request: StreamRequestConfig, |
no test coverage detected