diff --git a/coderd/aibridgedserver/aibridgedserver_test.go b/coderd/aibridgedserver/aibridgedserver_test.go index da8f341bdf5..1e54445b7ab 100644 --- a/coderd/aibridgedserver/aibridgedserver_test.go +++ b/coderd/aibridgedserver/aibridgedserver_test.go @@ -2454,6 +2454,10 @@ func TestRecordTokenUsageBudgetNotifications(t *testing.T) { require.Equal(t, wantThreshold[tmpl], sent[0].Labels["threshold"]) require.Equal(t, "$100.00", sent[0].Labels["limit"]) require.Equal(t, "Engineering", sent[0].Labels["effective_group_name"]) + // The interception is recorded at 2026-06-25, so its budget + // period runs June 1 - July 1, 2026. + require.Equal(t, "June 1, 2026", sent[0].Labels["period_start"]) + require.Equal(t, "July 1, 2026", sent[0].Labels["period_end"]) } }) } diff --git a/coderd/aibridgedserver/notifications.go b/coderd/aibridgedserver/notifications.go index 3e00c6d848d..4481401408f 100644 --- a/coderd/aibridgedserver/notifications.go +++ b/coderd/aibridgedserver/notifications.go @@ -49,6 +49,10 @@ type budgetThresholdCrossing struct { spendLimitMicros int64 thresholdPercent int notificationTemplate uuid.UUID + // periodStart and periodEnd bound the budget period [start, end) the + // crossing occurred in. + periodStart time.Time + periodEnd time.Time } // detectBudgetThresholdCrossings checks whether this interception's cost pushed @@ -94,6 +98,8 @@ func (s *Server) detectBudgetThresholdCrossings(ctx context.Context, tx database spendLimitMicros: limit, thresholdPercent: t.percent, notificationTemplate: t.notificationTemplate, + periodStart: period.Start, + periodEnd: period.End, }) } } @@ -113,6 +119,10 @@ func (s *Server) notifyBudgetThresholdCrossing(ctx context.Context, crossing bud "limit": formatSpendLimit(crossing.spendLimitMicros), "period": s.budgetPeriod.Adjective(), "effective_group_name": group.Name, + // Both bounds carry the year so a period straddling a year boundary + // (e.g. December 1, 2026 - January 1, 2027) is unambiguous. + "period_start": crossing.periodStart.UTC().Format("January 2, 2006"), + "period_end": crossing.periodEnd.UTC().Format("January 2, 2006"), } //nolint:gocritic // Enqueuing notifications requires the notifier actor. diff --git a/coderd/database/migrations/000552_ai_budget_notifications.up.sql b/coderd/database/migrations/000552_ai_budget_notifications.up.sql index 1816a5a2ae4..61535f4f72d 100644 --- a/coderd/database/migrations/000552_ai_budget_notifications.up.sql +++ b/coderd/database/migrations/000552_ai_budget_notifications.up.sql @@ -13,7 +13,11 @@ VALUES ( 'b5db9597-de2a-4dea-87e9-25cee6906b86', 'AI Budget Warning', E'You''re approaching your {{.Labels.period}} AI budget limit', - E'You have used more than {{.Labels.threshold}}% of your {{.Labels.period}} AI budget ({{.Labels.limit}}). Effective group: **{{.Labels.effective_group_name}}**.', + $$You have used more than {{.Labels.threshold}}% of your {{.Labels.period}} AI budget ({{.Labels.limit}}). + +Effective group: **{{.Labels.effective_group_name}}** + +AI budget period: {{.Labels.period_start}} - {{.Labels.period_end}}$$, '[]'::jsonb, 'AI Cost Control Events', NULL, @@ -36,7 +40,11 @@ VALUES ( 'cdcf2ecd-f003-4169-9800-abb2661ea522', 'AI Budget Limit Reached', E'You''ve reached your {{.Labels.period}} AI budget limit', - E'You have reached your {{.Labels.period}} AI budget limit ({{.Labels.limit}}). Subsequent requests will be blocked. Effective group: **{{.Labels.effective_group_name}}**.', + $$You have reached your {{.Labels.period}} AI budget limit ({{.Labels.limit}}). Subsequent requests will be blocked. + +Effective group: **{{.Labels.effective_group_name}}** + +AI budget period: {{.Labels.period_start}} - {{.Labels.period_end}}$$, '[]'::jsonb, 'AI Cost Control Events', NULL, diff --git a/coderd/notifications/notifications_test.go b/coderd/notifications/notifications_test.go index c72f1af4562..ac26ecca9fa 100644 --- a/coderd/notifications/notifications_test.go +++ b/coderd/notifications/notifications_test.go @@ -1473,6 +1473,8 @@ func TestNotificationTemplates_Golden(t *testing.T) { "limit": "$1000.00", "period": "monthly", "effective_group_name": "Engineering", + "period_start": "July 1, 2026", + "period_end": "August 1, 2026", }, Data: map[string]any{}, }, @@ -1489,6 +1491,8 @@ func TestNotificationTemplates_Golden(t *testing.T) { "limit": "$1000.00", "period": "monthly", "effective_group_name": "Engineering", + "period_start": "July 1, 2026", + "period_end": "August 1, 2026", }, Data: map[string]any{}, }, diff --git a/coderd/notifications/testdata/rendered-templates/smtp/TemplateAIBudgetLimitReachedUser.html.golden b/coderd/notifications/testdata/rendered-templates/smtp/TemplateAIBudgetLimitReachedUser.html.golden index 2e85771d84b..37db6f733cc 100644 --- a/coderd/notifications/testdata/rendered-templates/smtp/TemplateAIBudgetLimitReachedUser.html.golden +++ b/coderd/notifications/testdata/rendered-templates/smtp/TemplateAIBudgetLimitReachedUser.html.golden @@ -13,7 +13,11 @@ Content-Type: text/plain; charset=UTF-8 Hi Bobby, You have reached your monthly AI budget limit ($1000.00). Subsequent reques= -ts will be blocked. Effective group: Engineering. +ts will be blocked. + +Effective group: Engineering + +AI budget period: July 1, 2026 - August 1, 2026 --bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 @@ -46,8 +50,11 @@ argin: 8px 0 32px; line-height: 1.5;">

Hi Bobby,

You have reached your monthly AI budget limit ($1000.00). Subseq= -uent requests will be blocked. Effective group: Engineering.

+uent requests will be blocked.

+ +

Effective group: Engineering

+ +

AI budget period: July 1, 2026 - August 1, 2026

=20 diff --git a/coderd/notifications/testdata/rendered-templates/smtp/TemplateAIBudgetWarningUser.html.golden b/coderd/notifications/testdata/rendered-templates/smtp/TemplateAIBudgetWarningUser.html.golden index 7fb9cdac3ea..3927ab28e31 100644 --- a/coderd/notifications/testdata/rendered-templates/smtp/TemplateAIBudgetWarningUser.html.golden +++ b/coderd/notifications/testdata/rendered-templates/smtp/TemplateAIBudgetWarningUser.html.golden @@ -12,8 +12,11 @@ Content-Type: text/plain; charset=UTF-8 Hi Bobby, -You have used more than 85% of your monthly AI budget ($1000.00). Effective= - group: Engineering. +You have used more than 85% of your monthly AI budget ($1000.00). + +Effective group: Engineering + +AI budget period: July 1, 2026 - August 1, 2026 --bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 @@ -46,7 +49,11 @@ argin: 8px 0 32px; line-height: 1.5;">

Hi Bobby,

You have used more than 85% of your monthly AI budget ($1000.00)= -. Effective group: Engineering.

+.

+ +

Effective group: Engineering

+ +

AI budget period: July 1, 2026 - August 1, 2026

=20 diff --git a/coderd/notifications/testdata/rendered-templates/webhook/TemplateAIBudgetLimitReachedUser.json.golden b/coderd/notifications/testdata/rendered-templates/webhook/TemplateAIBudgetLimitReachedUser.json.golden index 87774ee95c8..4c3470574d0 100644 --- a/coderd/notifications/testdata/rendered-templates/webhook/TemplateAIBudgetLimitReachedUser.json.golden +++ b/coderd/notifications/testdata/rendered-templates/webhook/TemplateAIBudgetLimitReachedUser.json.golden @@ -14,6 +14,8 @@ "effective_group_name": "Engineering", "limit": "$1000.00", "period": "monthly", + "period_end": "August 1, 2026", + "period_start": "July 1, 2026", "threshold": "100" }, "data": {}, @@ -21,6 +23,6 @@ }, "title": "You've reached your monthly AI budget limit", "title_markdown": "You've reached your monthly AI budget limit", - "body": "You have reached your monthly AI budget limit ($1000.00). Subsequent requests will be blocked. Effective group: Engineering.", - "body_markdown": "You have reached your monthly AI budget limit ($1000.00). Subsequent requests will be blocked. Effective group: **Engineering**." + "body": "You have reached your monthly AI budget limit ($1000.00). Subsequent requests will be blocked.\n\nEffective group: Engineering\n\nAI budget period: July 1, 2026 - August 1, 2026", + "body_markdown": "You have reached your monthly AI budget limit ($1000.00). Subsequent requests will be blocked.\n\nEffective group: **Engineering**\n\nAI budget period: July 1, 2026 - August 1, 2026" } \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/webhook/TemplateAIBudgetWarningUser.json.golden b/coderd/notifications/testdata/rendered-templates/webhook/TemplateAIBudgetWarningUser.json.golden index 6624805af81..b9b082b9cbe 100644 --- a/coderd/notifications/testdata/rendered-templates/webhook/TemplateAIBudgetWarningUser.json.golden +++ b/coderd/notifications/testdata/rendered-templates/webhook/TemplateAIBudgetWarningUser.json.golden @@ -14,6 +14,8 @@ "effective_group_name": "Engineering", "limit": "$1000.00", "period": "monthly", + "period_end": "August 1, 2026", + "period_start": "July 1, 2026", "threshold": "85" }, "data": {}, @@ -21,6 +23,6 @@ }, "title": "You're approaching your monthly AI budget limit", "title_markdown": "You're approaching your monthly AI budget limit", - "body": "You have used more than 85% of your monthly AI budget ($1000.00). Effective group: Engineering.", - "body_markdown": "You have used more than 85% of your monthly AI budget ($1000.00). Effective group: **Engineering**." + "body": "You have used more than 85% of your monthly AI budget ($1000.00).\n\nEffective group: Engineering\n\nAI budget period: July 1, 2026 - August 1, 2026", + "body_markdown": "You have used more than 85% of your monthly AI budget ($1000.00).\n\nEffective group: **Engineering**\n\nAI budget period: July 1, 2026 - August 1, 2026" } \ No newline at end of file