feat(cost): per-run cost governor — reserve-before-call enforcement
Pipeline v2 Phase 1, commit 3/4. Cost_Governor reserves each call's worst-case estimate (prompt chars/4 + max_tokens via the #18 pricing chain) against the run ledger row with one atomic conditional UPDATE (the #10 lock discipline) BEFORE dispatch, then settles to actuals after each response; failed calls release their hold. Integrated inside OpenRouter_Provider::send_chat_completion so EVERY text-LLM call path is guarded with zero call-site changes; the image curl_multi batch reserves its summed estimate in Image_Pipeline before generate_image_batch. Breach: run halted (sticky), overage recorded, Cost_Ceiling_Exception aborts the un-dispatched call, process_next_queued_article aborts the remaining queued articles of halted/failed runs, and the Review Queue shows a 'Halted runs' notice (runs halted pre-post have no draft to list). Ceiling = prautoblogger_per_run_cost_ceiling_usd setting (default constant $0.50, snapshotted at run start, 0 = disabled). Standalone no-run calls, the monthly cap, and the AI Gateway path are unchanged. Under-300: provider API-key fetch+validation moved verbatim to OpenRouter_Request_Builder::resolve_api_key(). Agent-Session: pipeline-v2-phase1-engineer-20260612
This commit is contained in:
56
templates/admin/review-queue.php
Executable file → Normal file
56
templates/admin/review-queue.php
Executable file → Normal file
@@ -18,6 +18,37 @@ $bulk_type = isset( $_GET['prautoblogger_bulk_type'] ) ? sanitize_text_field( wp
|
||||
<div class="wrap prautoblogger-review-queue">
|
||||
<h1><?php esc_html_e( 'PRAutoBlogger — Review Queue', 'prautoblogger' ); ?></h1>
|
||||
|
||||
<?php if ( ! empty( $halted_runs ) ) : ?>
|
||||
<div class="notice notice-error">
|
||||
<p>
|
||||
<strong><?php esc_html_e( 'Runs halted by the per-run cost ceiling', 'prautoblogger' ); ?></strong>
|
||||
<?php esc_html_e( '— these runs were stopped before exceeding their budget and need human review. Raise the ceiling in Settings or discard the run.', 'prautoblogger' ); ?>
|
||||
</p>
|
||||
<table class="widefat striped" style="margin-bottom: 8px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php esc_html_e( 'Run ID', 'prautoblogger' ); ?></th>
|
||||
<th><?php esc_html_e( 'Halted at', 'prautoblogger' ); ?></th>
|
||||
<th><?php esc_html_e( 'Spent + held (USD)', 'prautoblogger' ); ?></th>
|
||||
<th><?php esc_html_e( 'Ceiling (USD)', 'prautoblogger' ); ?></th>
|
||||
<th><?php esc_html_e( 'Blocked overage (USD)', 'prautoblogger' ); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ( $halted_runs as $halted_run ) : ?>
|
||||
<tr>
|
||||
<td><code><?php echo esc_html( (string) $halted_run['run_id'] ); ?></code></td>
|
||||
<td><?php echo esc_html( (string) ( $halted_run['finished_at'] ?? $halted_run['updated_at'] ) ); ?></td>
|
||||
<td><?php echo esc_html( number_format( (float) $halted_run['settled_usd'] + (float) $halted_run['reserved_usd'], 4 ) ); ?></td>
|
||||
<td><?php echo esc_html( number_format( (float) $halted_run['ceiling_usd'], 2 ) ); ?></td>
|
||||
<td><?php echo esc_html( number_format( (float) $halted_run['overage_usd'], 4 ) ); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( $bulk_done > 0 && '' !== $bulk_type ) : ?>
|
||||
<div class="notice notice-success is-dismissible">
|
||||
<p>
|
||||
@@ -86,7 +117,10 @@ $bulk_type = isset( $_GET['prautoblogger_bulk_type'] ) ? sanitize_text_field( wp
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php while ( $query->have_posts() ) : $query->the_post(); ?>
|
||||
<?php
|
||||
while ( $query->have_posts() ) :
|
||||
$query->the_post();
|
||||
?>
|
||||
<?php
|
||||
$post_id = get_the_ID();
|
||||
$verdict = get_post_meta( $post_id, '_prautoblogger_editor_verdict', true );
|
||||
@@ -139,14 +173,18 @@ $bulk_type = isset( $_GET['prautoblogger_bulk_type'] ) ? sanitize_text_field( wp
|
||||
if ( $total_pages > 1 ) :
|
||||
$current_page = isset( $_GET['paged'] ) ? absint( $_GET['paged'] ) : 1;
|
||||
echo '<div class="prautoblogger-pagination">';
|
||||
echo wp_kses_post( paginate_links( [
|
||||
'base' => add_query_arg( 'paged', '%#%' ),
|
||||
'format' => '',
|
||||
'current' => $current_page,
|
||||
'total' => $total_pages,
|
||||
'prev_text' => '«',
|
||||
'next_text' => '»',
|
||||
] ) );
|
||||
echo wp_kses_post(
|
||||
paginate_links(
|
||||
array(
|
||||
'base' => add_query_arg( 'paged', '%#%' ),
|
||||
'format' => '',
|
||||
'current' => $current_page,
|
||||
'total' => $total_pages,
|
||||
'prev_text' => '«',
|
||||
'next_text' => '»',
|
||||
)
|
||||
)
|
||||
);
|
||||
echo '</div>';
|
||||
endif;
|
||||
?>
|
||||
|
||||
Reference in New Issue
Block a user