chore: simplify honeycomb alerts (#26142)

This commit is contained in:
Victor Navarro 2026-05-07 09:56:10 +02:00 committed by GitHub
parent 293bb422fa
commit f8aa4a3be0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 186 additions and 409 deletions

View File

@ -107,7 +107,6 @@
"solid-js": "catalog:", "solid-js": "catalog:",
"solid-list": "0.3.0", "solid-list": "0.3.0",
"solid-stripe": "0.8.1", "solid-stripe": "0.8.1",
"svix": "1.92.2",
"vite": "catalog:", "vite": "catalog:",
"zod": "catalog:", "zod": "catalog:",
}, },
@ -2168,8 +2167,6 @@
"@speed-highlight/core": ["@speed-highlight/core@1.2.15", "", {}, "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw=="], "@speed-highlight/core": ["@speed-highlight/core@1.2.15", "", {}, "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw=="],
"@stablelib/base64": ["@stablelib/base64@1.0.1", "", {}, "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="],
"@standard-community/standard-json": ["@standard-community/standard-json@0.3.5", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "@types/json-schema": "^7.0.15", "@valibot/to-json-schema": "^1.3.0", "arktype": "^2.1.20", "effect": "^3.16.8", "quansync": "^0.2.11", "sury": "^10.0.0", "typebox": "^1.0.17", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.5" }, "optionalPeers": ["@valibot/to-json-schema", "arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-to-json-schema"] }, "sha512-4+ZPorwDRt47i+O7RjyuaxHRK/37QY/LmgxlGrRrSTLYoFatEOzvqIc85GTlM18SFZ5E91C+v0o/M37wZPpUHA=="], "@standard-community/standard-json": ["@standard-community/standard-json@0.3.5", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "@types/json-schema": "^7.0.15", "@valibot/to-json-schema": "^1.3.0", "arktype": "^2.1.20", "effect": "^3.16.8", "quansync": "^0.2.11", "sury": "^10.0.0", "typebox": "^1.0.17", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.5" }, "optionalPeers": ["@valibot/to-json-schema", "arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-to-json-schema"] }, "sha512-4+ZPorwDRt47i+O7RjyuaxHRK/37QY/LmgxlGrRrSTLYoFatEOzvqIc85GTlM18SFZ5E91C+v0o/M37wZPpUHA=="],
"@standard-community/standard-openapi": ["@standard-community/standard-openapi@0.2.9", "", { "peerDependencies": { "@standard-community/standard-json": "^0.3.5", "@standard-schema/spec": "^1.0.0", "arktype": "^2.1.20", "effect": "^3.17.14", "openapi-types": "^12.1.3", "sury": "^10.0.0", "typebox": "^1.0.0", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-openapi": "^4" }, "optionalPeers": ["arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-openapi"] }, "sha512-htj+yldvN1XncyZi4rehbf9kLbu8os2Ke/rfqoZHCMHuw34kiF3LP/yQPdA0tQ940y8nDq3Iou8R3wG+AGGyvg=="], "@standard-community/standard-openapi": ["@standard-community/standard-openapi@0.2.9", "", { "peerDependencies": { "@standard-community/standard-json": "^0.3.5", "@standard-schema/spec": "^1.0.0", "arktype": "^2.1.20", "effect": "^3.17.14", "openapi-types": "^12.1.3", "sury": "^10.0.0", "typebox": "^1.0.0", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-openapi": "^4" }, "optionalPeers": ["arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-openapi"] }, "sha512-htj+yldvN1XncyZi4rehbf9kLbu8os2Ke/rfqoZHCMHuw34kiF3LP/yQPdA0tQ940y8nDq3Iou8R3wG+AGGyvg=="],
@ -3180,8 +3177,6 @@
"fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="], "fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="],
"fast-sha256": ["fast-sha256@1.3.0", "", {}, "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="],
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
"fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="], "fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="],
@ -4656,8 +4651,6 @@
"standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="], "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
"standardwebhooks": ["standardwebhooks@1.0.0", "", { "dependencies": { "@stablelib/base64": "^1.0.0", "fast-sha256": "^1.3.0" } }, "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg=="],
"stat-mode": ["stat-mode@1.0.0", "", {}, "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg=="], "stat-mode": ["stat-mode@1.0.0", "", {}, "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg=="],
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
@ -4726,8 +4719,6 @@
"sury": ["sury@11.0.0-alpha.4", "", { "peerDependencies": { "rescript": "12.x" }, "optionalPeers": ["rescript"] }, "sha512-oeG/GJWZvQCKtGPpLbu0yCZudfr5LxycDo5kh7SJmKHDPCsEPJssIZL2Eb4Tl7g9aPEvIDuRrkS+L0pybsMEMA=="], "sury": ["sury@11.0.0-alpha.4", "", { "peerDependencies": { "rescript": "12.x" }, "optionalPeers": ["rescript"] }, "sha512-oeG/GJWZvQCKtGPpLbu0yCZudfr5LxycDo5kh7SJmKHDPCsEPJssIZL2Eb4Tl7g9aPEvIDuRrkS+L0pybsMEMA=="],
"svix": ["svix@1.92.2", "", { "dependencies": { "standardwebhooks": "1.0.0" } }, "sha512-ZmuA3UVvlnF9EgxlzmPtF7CKjQb64Z6OFlyfdDfU0sdcC7dJa+3aOYX5B9mA+RS6ch1AxBa4UP/l6KmqfGtWBQ=="],
"system-architecture": ["system-architecture@0.1.0", "", {}, "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA=="], "system-architecture": ["system-architecture@0.1.0", "", {}, "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA=="],
"tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="], "tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="],

View File

@ -1,5 +1,6 @@
import { domain } from "./stage" import { domain } from "./stage"
import { EMAILOCTOPUS_API_KEY } from "./app" import { EMAILOCTOPUS_API_KEY } from "./app"
import { SECRET } from "./secret"
//////////////// ////////////////
// DATABASE // DATABASE
@ -221,8 +222,6 @@ const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
const STRIPE_WEBHOOK_SECRET = new sst.Linkable("STRIPE_WEBHOOK_SECRET", { const STRIPE_WEBHOOK_SECRET = new sst.Linkable("STRIPE_WEBHOOK_SECRET", {
properties: { value: stripeWebhook.secret }, properties: { value: stripeWebhook.secret },
}) })
const INCIDENT_WEBHOOK_SIGNING_SECRET = new sst.Secret("INCIDENT_WEBHOOK_SIGNING_SECRET")
const DISCORD_INCIDENT_WEBHOOK_URL = new sst.Secret("DISCORD_INCIDENT_WEBHOOK_URL")
const gatewayKv = new sst.cloudflare.Kv("GatewayKv") const gatewayKv = new sst.cloudflare.Kv("GatewayKv")
@ -233,6 +232,7 @@ const gatewayKv = new sst.cloudflare.Kv("GatewayKv")
const bucket = new sst.cloudflare.Bucket("ZenData") const bucket = new sst.cloudflare.Bucket("ZenData")
const bucketNew = new sst.cloudflare.Bucket("ZenDataNew") const bucketNew = new sst.cloudflare.Bucket("ZenDataNew")
const DISCORD_INCIDENT_WEBHOOK_URL = new sst.Secret("DISCORD_INCIDENT_WEBHOOK_URL")
const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID") const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID")
const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY") const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY")
@ -254,8 +254,8 @@ new sst.cloudflare.x.SolidStart("Console", {
database, database,
AUTH_API_URL, AUTH_API_URL,
STRIPE_WEBHOOK_SECRET, STRIPE_WEBHOOK_SECRET,
INCIDENT_WEBHOOK_SIGNING_SECRET,
DISCORD_INCIDENT_WEBHOOK_URL, DISCORD_INCIDENT_WEBHOOK_URL,
SECRET.HoneycombWebhookSecret,
STRIPE_SECRET_KEY, STRIPE_SECRET_KEY,
EMAILOCTOPUS_API_KEY, EMAILOCTOPUS_API_KEY,
AWS_SES_ACCESS_KEY_ID, AWS_SES_ACCESS_KEY_ID,

View File

@ -1,318 +1,91 @@
const displayName = (s: string) => import { SECRET } from "./secret"
s import { domain } from "./stage"
.split("-")
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(" ")
.replace(/(?<=\d) (?=\d)/g, ".")
const resourceName = (s: string) => displayName(s).replace(/[^a-zA-Z0-9]/g, "") const webhookRecipient = new honeycomb.WebhookRecipient("DiscordAlerts", {
name: $app.stage === "production" ? "Discord Alerts" : `Discord Alerts (${$app.stage})`,
const varSpec = (label: string, name: string) => url: `https://${domain}/honeycomb/webhook`,
$jsonStringify({ secret: SECRET.HoneycombWebhookSecret.result,
content: [
{
content: [
{
attrs: {
name,
label,
missing: false,
},
type: "varSpec",
},
],
type: "paragraph",
},
],
type: "doc",
})
const fields = {
model: incident.getAlertAttributeOutput({ name: "Model" }),
product: incident.getAlertAttributeOutput({ name: "Product" }),
}
const alertSource = new incident.AlertSource("HoneycombAlertSource", {
name: $app.stage === "production" ? "Honeycomb" : `Honeycomb (${$app.stage})`,
sourceType: "honeycomb",
template: {
title: {
literal: varSpec("Payload -> Title", "title"),
},
description: {
literal: varSpec("Payload -> Description", "description"),
},
attributes: [
{
alertAttributeId: fields.model.id,
binding: {
value: {
reference: 'expressions["model"]',
},
mergeStrategy: "first_wins",
},
},
{
alertAttributeId: fields.product.id,
binding: {
value: {
reference: 'expressions["product"]',
},
mergeStrategy: "first_wins",
},
},
],
expressions: [
{
label: "Model",
operations: [
{
operationType: "parse",
parse: {
returns: {
array: false,
type: fields.model.type,
},
source: "$['model']",
},
},
],
reference: "model",
rootReference: "payload",
},
{
label: "Product",
operations: [
{
operationType: "parse",
parse: {
returns: {
array: false,
type: fields.product.type,
},
source: "$['product']",
},
},
],
reference: "product",
rootReference: "payload",
},
],
},
})
const webhookRecipient = new honeycomb.WebhookRecipient(`IncidentWebhook`, {
name: $app.stage === "production" ? "Incident.io" : `Incident.io (${$app.stage})`,
url: alertSource.alertEventsUrl,
secret: alertSource.secretToken,
templates: [ templates: [
{ {
type: "trigger", type: "trigger",
body: $jsonStringify({ body: `{
title: "{{ .Name }}", "url": {{ .Result.URL | quote }},
description: "{{ .Description }}", "type": {{ .Vars.type | quote }},
status: "{{ .Alert.Status }}", "name": {{ .Name | quote }},
deduplication_key: "{{ .Alert.InstanceID }}", "status": {{ .Alert.Status | quote }},
source_url: "{{ .Result.URL }}", "isTest": {{ .Alert.IsTest }},
model: "{{ .Vars.model }}", "groups": {{ .Result.GroupsTriggered | toJson }}
product: "{{ .Vars.product }}", }`,
}),
}, },
], ],
variables: [ variables: [
{ {
name: "model", name: "type",
},
{
name: "product",
}, },
], ],
}) })
new incident.AlertRoute("HoneycombAlertRoute", { const modelHttpErrorsQuery = (product: "go" | "zen") => {
name: $app.stage === "production" ? "Honeycomb" : `Honeycomb (${$app.stage})`, const filters = [
enabled: true, { column: "model", op: "exists" },
isPrivate: false, { column: "event_type", op: "=", value: "completions" },
alertSources: [ { column: "user_agent", op: "contains", value: "opencode" },
{ { column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" },
alertSourceId: alertSource.id, ]
conditionGroups: [
{ return honeycomb.getQuerySpecificationOutput({
conditions: [ breakdowns: ["model"],
{ calculatedFields: [
subject: "alert.title",
operation: "is_set",
paramBindings: [],
},
],
},
],
},
],
conditionGroups: [
{
conditions: [
{
subject: "alert.title",
operation: "is_set",
paramBindings: [],
},
],
},
],
expressions: [],
escalationConfig: {
autoCancelEscalations: true,
escalationTargets: [],
},
incidentConfig: {
autoDeclineEnabled: true,
enabled: true,
conditionGroups: [],
deferTimeSeconds: 0,
groupingKeys: [
{ {
reference: $interpolate`alert.attributes.${fields.model.id}`, name: "is_failed_http_status",
}, expression: `IF(AND(GTE($status, "400"), NOT(EQUALS($status, "401"))), 1, 0)`,
{
reference: $interpolate`alert.attributes.${fields.product.id}`,
}, },
], ],
groupingWindowSeconds: 3600,
},
incidentTemplate: {
name: {
value: {
literal: varSpec("Alert -> Title", "alert.title"),
},
},
summary: {
value: {
literal: varSpec("Alert -> Description", "alert.description"),
},
},
startInTriage: {
value: {
literal: "true",
},
},
severity: {
mergeStrategy: "first-wins",
},
incidentMode: {
value: {
literal: $app.stage === "production" ? "standard" : "test",
},
},
},
})
type Product = "go" | "zen"
type Trigger = (opts: { model: string; product: Product }) => {
id: string
title: string
description: string
json: honeycomb.GetQuerySpecificationOutputArgs
threshold: { op: ">=" | "<="; value: number }
}
type Model = { id: string; products: Product[]; triggers: Trigger[] }
const httpErrors: Trigger = ({ model, product }) => ({
id: "increased-http-errors",
title: `Increased HTTP Errors for ${displayName(model)} on ${displayName(product)}`,
description: `Detected increased rate of HTTP errors for ${displayName(model)} on OpenCode ${displayName(product)}`,
json: {
calculations: [ calculations: [
{ { op: "COUNT", name: "TOTAL", filterCombination: "AND", filters },
op: "COUNT", { op: "SUM", name: "FAILED", column: "is_failed_http_status", filterCombination: "AND", filters },
name: "TOTAL",
filterCombination: "AND",
filters: [
{ column: "model", op: "=", value: model },
{ column: "event_type", op: "=", value: "completions" },
{ column: "user_agent", op: "contains", value: "opencode" },
{ column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" },
],
},
{
op: "COUNT",
name: "FAILED",
filterCombination: "AND",
filters: [
{ column: "model", op: "=", value: model },
{ column: "event_type", op: "=", value: "completions" },
{ column: "user_agent", op: "contains", value: "opencode" },
{ column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" },
{ column: "status", op: ">=", value: "400" },
{ column: "status", op: "!=", value: "401" },
],
},
], ],
formulas: [{ name: "ERROR", expression: "$FAILED / $TOTAL" }], formulas: [{ name: "ERROR", expression: "IF(GTE($TOTAL, 2500), DIV($FAILED, $TOTAL), 0)" }],
timeRange: 900, timeRange: 900,
}, }).json
threshold: { op: ">=", value: 0.8 }, }
const description = "Managed by SST (Don't edit in Honeycomb UI)"
new honeycomb.Trigger("IncreasedModelHttpErrorsGo", {
name: "Increased Model HTTP Errors [Go]",
description,
queryJson: modelHttpErrorsQuery("go"),
alertType: "on_change",
frequency: 300,
thresholds: [{ op: ">=", value: 0.8, exceededLimit: 1 }],
recipients: [
// {
// id: webhookRecipient.id,
// notificationDetails: [
// {
// variables: [{ name: "type", value: "model_http_errors" }],
// },
// ],
// },
],
}) })
const models: Model[] = [ new honeycomb.Trigger("IncreasedModelHttpErrorsZen", {
{ id: "kimi-k2.6", products: ["go", "zen"], triggers: [httpErrors] }, name: "Increased Model HTTP Errors [Zen]",
{ id: "kimi-k2.5", products: ["go", "zen"], triggers: [httpErrors] }, description,
{ id: "deepseek-v4-flash", products: ["go", "zen"], triggers: [httpErrors] }, queryJson: modelHttpErrorsQuery("zen"),
{ id: "deepseek-v4-pro", products: ["go", "zen"], triggers: [httpErrors] }, alertType: "on_change",
{ id: "glm-5.1", products: ["go", "zen"], triggers: [httpErrors] }, frequency: 300,
// { id: "glm-5", products: ["go"], triggers: [httpErrors] }, thresholds: [{ op: ">=", value: 0.8, exceededLimit: 1 }],
{ id: "qwen3.6-plus", products: ["go", "zen"], triggers: [httpErrors] }, recipients: [
{ id: "qwen3.5-plus", products: ["go"], triggers: [httpErrors] }, // {
{ id: "minimax-m2.7", products: ["go", "zen"], triggers: [httpErrors] }, // id: webhookRecipient.id,
// { id: "minimax-m2.5", products: ["go", "zen"], triggers: [httpErrors] }, // notificationDetails: [
{ id: "mimo-v2.5-pro", products: ["go"], triggers: [httpErrors] }, // {
// { id: "mimo-v2.5", products: ["go"], triggers: [httpErrors] }, // variables: [{ name: "type", value: "model_http_errors" }],
// { id: "mimo-v2-omni", products: ["go"], triggers: [httpErrors] }, // },
// { id: "mimo-v2-pro", products: ["go"], triggers: [httpErrors] }, // ],
{ id: "claude-opus-4-7", products: ["zen"], triggers: [httpErrors] }, // },
// { id: "claude-opus-4-6", products: ["zen"], triggers: [httpErrors] }, ],
// { id: "claude-sonnet-4-6", products: ["zen"], triggers: [httpErrors] }, })
{ id: "gpt-5.5", products: ["zen"], triggers: [httpErrors] },
{ id: "big-pickle", products: ["zen"], triggers: [httpErrors] },
// { id: "minimax-m2.5-free", products: ["zen"], triggers: [httpErrors] },
// { id: "hy3-preview-free", products: ["zen"], triggers: [httpErrors] },
// { id: "nemotron-3-super-free", products: ["zen"], triggers: [httpErrors] },
// { id: "trinity-large-preview-free", products: ["zen"], triggers: [httpErrors] },
// { id: "ling-2.6-flash-free", products: ["zen"], triggers: [httpErrors] },
]
if ($app.stage !== "production") {
models.splice(1)
}
for (const model of models) {
for (const product of model.products) {
for (const trigger of model.triggers) {
const spec = trigger({ model: model.id, product })
new honeycomb.Trigger(resourceName(`${spec.id}-${product}-${model.id}`), {
name: spec.title,
description: spec.description,
queryJson: honeycomb.getQuerySpecificationOutput(spec.json).json,
alertType: "on_change",
frequency: 300,
thresholds: [{ ...spec.threshold, exceededLimit: 1 }],
recipients: [
{
id: webhookRecipient.id,
notificationDetails: [
{
variables: [
{ name: "model", value: model.id },
{ name: "product", value: product },
],
},
],
},
],
})
}
}
}

View File

@ -1,4 +1,11 @@
sst.Linkable.wrap(random.RandomPassword, (resource) => ({
properties: {
value: resource.result,
},
}))
export const SECRET = { export const SECRET = {
R2AccessKey: new sst.Secret("R2AccessKey", "unknown"), R2AccessKey: new sst.Secret("R2AccessKey", "unknown"),
R2SecretKey: new sst.Secret("R2SecretKey", "unknown"), R2SecretKey: new sst.Secret("R2SecretKey", "unknown"),
HoneycombWebhookSecret: new random.RandomPassword("HoneycombWebhookSecret", { length: 24 }),
} }

View File

@ -31,7 +31,6 @@
"solid-js": "catalog:", "solid-js": "catalog:",
"solid-list": "0.3.0", "solid-list": "0.3.0",
"solid-stripe": "0.8.1", "solid-stripe": "0.8.1",
"svix": "1.92.2",
"vite": "catalog:", "vite": "catalog:",
"zod": "catalog:" "zod": "catalog:"
}, },

View File

@ -0,0 +1,81 @@
import type { APIEvent } from "@solidjs/start/server"
import { z } from "zod"
import { Resource } from "@opencode-ai/console-resource"
import { safeEqual } from "@opencode-ai/console-core/util/crypto.js"
const DISCORD_ALERT_ROLE_ID = "1501447160175136838"
const basePayload = z.object({
name: z.string().optional(),
status: z.string().optional(),
isTest: z.boolean().optional(),
url: z.string(),
})
const groups = z.object({ group: z.object({ key: z.string(), value: z.string() }).array() }).array()
const honeycombWebhookPayload = z.discriminatedUnion("type", [
basePayload.extend({
type: z.literal("model_http_errors"),
groups,
}),
basePayload.extend({
type: z.literal("provider_http_errors"),
groups,
}),
])
const postDiscordMessage = async (payload: z.infer<typeof honeycombWebhookPayload>) => {
const group = payload.type === "model_http_errors" ? "model" : "provider"
const names = (payload.groups ?? []).flatMap((item) => item.group.map((g) => g.value))
const content = [
`[**${payload.isTest ? "[TEST] " : ""}${payload.name ?? "Honeycomb alert"}**](${payload.url})`,
names.length > 0 ? `Affected ${group}s:` : undefined,
...names.map((name) => `- ${name}`),
"",
`<@&${DISCORD_ALERT_ROLE_ID}>`,
]
.filter((line) => line !== undefined)
.join("\n")
return fetch(Resource.DISCORD_INCIDENT_WEBHOOK_URL.value, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
content,
allowed_mentions: { roles: [DISCORD_ALERT_ROLE_ID] },
flags: 4,
}),
})
}
export async function POST(input: APIEvent) {
const token = input.request.headers.get("X-Honeycomb-Webhook-Token")
if (!safeEqual(token ?? "", Resource.HoneycombWebhookSecret.value)) {
console.debug("Invalid Honeycomb webhook token")
return Response.json({ message: "invalid token" }, { status: 401 })
}
const body = await input.request.json()
console.log(body, JSON.stringify(body, null, 2))
const parsed = honeycombWebhookPayload.safeParse(body)
if (!parsed.success) {
console.error(parsed.error)
return Response.json({ message: "invalid payload" }, { status: 400 })
}
if (parsed.data.status !== "TRIGGERED") {
console.debug("Skipping resolved alert Honeycomb webhook")
return Response.json({ message: "ignored" }, { status: 200 })
}
const response = await postDiscordMessage(parsed.data)
if (!response.ok) {
return Response.json({ message: "discord webhook failed" }, { status: 502 })
}
return Response.json({ message: "sent" }, { status: 200 })
}

View File

@ -1,77 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { Resource } from "@opencode-ai/console-resource"
import { Webhook } from "svix"
const DISCORD_INCIDENT_ROLE_ID = "1501447160175136838"
type Incident = {
mode?: "test" | "standard"
name?: string
permalink?: string
summary?: string
}
type IncidentWebhookPayload = {
event_type?: string
"public_incident.incident_created_v2"?: Incident
}
const verifyWebhook = async (request: Request) => {
const body = await request.text()
try {
return new Webhook(Resource.INCIDENT_WEBHOOK_SIGNING_SECRET.value).verify(
body,
Object.fromEntries(request.headers.entries()),
) as IncidentWebhookPayload
} catch {
return undefined
}
}
const postDiscordMessage = async (incident: Incident) => {
return fetch(Resource.DISCORD_INCIDENT_WEBHOOK_URL.value, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
content: [
`**${incident.mode === "test" ? "[TEST] " : ""}${incident.name ?? "Incident has been created"}**`,
incident.summary,
"",
`<@&${DISCORD_INCIDENT_ROLE_ID}>`,
"",
incident.permalink,
]
.filter((line) => line !== undefined)
.join("\n"),
allowed_mentions: {
roles: [DISCORD_INCIDENT_ROLE_ID],
},
flags: 4,
}),
})
}
export async function POST(input: APIEvent) {
const payload = await verifyWebhook(input.request)
if (!payload) {
return Response.json({ message: "invalid signature" }, { status: 401 })
}
if (payload.event_type !== "public_incident.incident_created_v2") {
return Response.json({ message: "ignored event" }, { status: 200 })
}
const incident = payload["public_incident.incident_created_v2"]
if (!incident) {
return Response.json({ message: "missing incident" }, { status: 400 })
}
const response = await postDiscordMessage(incident)
if (!response.ok) {
return Response.json({ message: "discord webhook failed" }, { status: 502 })
}
return Response.json({ message: "sent" }, { status: 200 })
}

View File

@ -0,0 +1,8 @@
import { timingSafeEqual } from "node:crypto"
export function safeEqual(a: string, b: string): boolean {
const encoder = new TextEncoder()
const aBytes = encoder.encode(a)
const bBytes = encoder.encode(b)
return aBytes.length === bBytes.length && timingSafeEqual(aBytes, bBytes)
}

View File

@ -91,8 +91,8 @@ declare module "sst" {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string
} }
"INCIDENT_WEBHOOK_SIGNING_SECRET": { "HoneycombWebhookSecret": {
"type": "sst.sst.Secret" "type": "random.index/randomPassword.RandomPassword"
"value": string "value": string
} }
"R2AccessKey": { "R2AccessKey": {

View File

@ -91,8 +91,8 @@ declare module "sst" {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string
} }
"INCIDENT_WEBHOOK_SIGNING_SECRET": { "HoneycombWebhookSecret": {
"type": "sst.sst.Secret" "type": "random.index/randomPassword.RandomPassword"
"value": string "value": string
} }
"R2AccessKey": { "R2AccessKey": {

View File

@ -91,8 +91,8 @@ declare module "sst" {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string
} }
"INCIDENT_WEBHOOK_SIGNING_SECRET": { "HoneycombWebhookSecret": {
"type": "sst.sst.Secret" "type": "random.index/randomPassword.RandomPassword"
"value": string "value": string
} }
"R2AccessKey": { "R2AccessKey": {

View File

@ -91,8 +91,8 @@ declare module "sst" {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string
} }
"INCIDENT_WEBHOOK_SIGNING_SECRET": { "HoneycombWebhookSecret": {
"type": "sst.sst.Secret" "type": "random.index/randomPassword.RandomPassword"
"value": string "value": string
} }
"R2AccessKey": { "R2AccessKey": {

View File

@ -91,8 +91,8 @@ declare module "sst" {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string
} }
"INCIDENT_WEBHOOK_SIGNING_SECRET": { "HoneycombWebhookSecret": {
"type": "sst.sst.Secret" "type": "random.index/randomPassword.RandomPassword"
"value": string "value": string
} }
"R2AccessKey": { "R2AccessKey": {

4
sst-env.d.ts vendored
View File

@ -114,8 +114,8 @@ declare module "sst" {
"type": "sst.sst.Secret" "type": "sst.sst.Secret"
"value": string "value": string
} }
"INCIDENT_WEBHOOK_SIGNING_SECRET": { "HoneycombWebhookSecret": {
"type": "sst.sst.Secret" "type": "random.index/randomPassword.RandomPassword"
"value": string "value": string
} }
"LogProcessor": { "LogProcessor": {

View File

@ -11,15 +11,10 @@ export default $config({
stripe: { stripe: {
apiKey: process.env.STRIPE_SECRET_KEY!, apiKey: process.env.STRIPE_SECRET_KEY!,
}, },
random: "4.19.2",
planetscale: "0.4.1", planetscale: "0.4.1",
honeycomb: { honeycomb: "0.49.0",
version: "0.49.0", incident: "5.35.0",
apiKey: process.env.HONEYCOMB_API_KEY!,
},
incident: {
version: "5.35.0",
apiKey: process.env.INCIDENT_API_KEY!,
},
}, },
} }
}, },
@ -27,7 +22,7 @@ export default $config({
await import("./infra/app.js") await import("./infra/app.js")
await import("./infra/console.js") await import("./infra/console.js")
await import("./infra/enterprise.js") await import("./infra/enterprise.js")
if ($app.stage === "production") { if ($app.stage === "production" || $app.stage === "vimtor") {
await import("./infra/monitoring.js") await import("./infra/monitoring.js")
} }
}, },