Files
simstudioai--sim/scripts/sync-metrics-contract.ts
WeHub Mirror 6bf8bebf51
CI / Test and Build (push) Failing after 1s
CI / Migrate Dev DB (push) Has been skipped
CI / Migrate DB (push) Has been skipped
CodeQL / Analyze actions (push) Has been cancelled
CodeQL / Analyze javascript-typescript (push) Has been cancelled
CI / Detect Version (push) Has been cancelled
CI / Detect Desktop Changes (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/cron.Dockerfile, ubuntu-latest, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build AMD64 (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/cron.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/db.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/pii.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/realtime.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-8vcpu-ubuntu-2404-arm, ./docker/app.Dockerfile, linux-arm64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
Helm Chart / Lint, test, and validate chart (push) Has been cancelled
Helm Chart / Chart version bumped (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Build Dev ECR (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core) (push) Has been cancelled
CI / Promote Images (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Check Desktop Signing Secrets (push) Has been cancelled
CI / Desktop Release (push) Has been cancelled
CI / Create Desktop Prerelease (push) Has been cancelled
CI / Desktop Prerelease Build (push) Has been cancelled
CI / Publish Desktop Prerelease (push) Has been cancelled
CI / Prune Desktop Prereleases (push) Has been cancelled
Helm Chart / Install on kind and run helm test (push) Has been cancelled
WeHub snapshot of cb28d14c6f2c081de7a0d8729a8c816c9adef67a
2026-08-10 11:17:50 +08:00

143 lines
5.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { formatGeneratedSource } from './format-generated-source'
/**
* Generate `apps/sim/lib/copilot/generated/metrics-v1.ts` from the Go-side
* `contracts/metrics-v1.schema.json` contract.
*
* The contract is a single-enum JSON Schema listing every canonical mothership
* OTel METRIC name. Go and Sim BOTH emit mothership metrics (the agent loop in
* Go; server-side tool/VFS/file instrumentation in Sim), so both sides MUST
* emit identical metric names for `histogram_quantile(sum by (le) …)` over the
* GoSim union to be valid. We emit:
* - A `Metric` const object keyed by PascalCase identifier whose values are
* the exact wire names, so call sites read `meter.createHistogram(
* Metric.CopilotToolDuration)` instead of a raw string literal.
* - A `MetricKey` / `MetricValue` union pair.
* - A sorted `MetricValues` readonly array for tests/enumeration.
*
* Label allowlists and histogram bucket boundaries are NOT encoded in the
* schema (name-only). The Go side owns the label-cardinality allowlist
* (contracts/metrics_v1.go) and the shared bucket constant
* (internal/telemetry/metrics.go); the Sim emitter MUST use the identical
* label keys and bucket boundaries by hand.
*
* This is the metric-name twin of `sync-trace-attributes-contract.ts`; the two
* share the enum-extraction + PascalCase + collision-detection pattern.
*/
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url))
const ROOT = resolve(SCRIPT_DIR, '..')
const DEFAULT_CONTRACT_PATH = resolve(ROOT, '../copilot/copilot/contracts/metrics-v1.schema.json')
const OUTPUT_PATH = resolve(ROOT, 'apps/sim/lib/copilot/generated/metrics-v1.ts')
function extractMetricNames(schema: Record<string, unknown>): string[] {
const defs = (schema.$defs ?? {}) as Record<string, unknown>
const nameDef = defs.MetricsV1Name
if (
!nameDef ||
typeof nameDef !== 'object' ||
!Array.isArray((nameDef as Record<string, unknown>).enum)
) {
throw new Error('metrics-v1.schema.json is missing $defs.MetricsV1Name.enum')
}
const enumValues = (nameDef as Record<string, unknown>).enum as unknown[]
if (!enumValues.every((v) => typeof v === 'string')) {
throw new Error('MetricsV1Name enum must be string-only')
}
return (enumValues as string[]).slice().sort()
}
/**
* Convert a wire metric name like `copilot.request.duration` into an
* identifier-safe PascalCase key like `CopilotRequestDuration`. Same algorithm
* as the trace-attributes sync script so readers learn one and reuse it.
*/
function toIdentifier(name: string): string {
const parts = name.split(/[^A-Za-z0-9]+/).filter(Boolean)
if (parts.length === 0) {
throw new Error(`Cannot derive identifier for metric name: ${name}`)
}
const ident = parts.map((p) => p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()).join('')
if (/^[0-9]/.test(ident)) {
throw new Error(`Derived identifier "${ident}" for metric "${name}" starts with a digit`)
}
return ident
}
function render(metricNames: string[]): string {
const pairs = metricNames.map((name) => ({ name, ident: toIdentifier(name) }))
const seen = new Map<string, string>()
for (const p of pairs) {
const prev = seen.get(p.ident)
if (prev && prev !== p.name) {
throw new Error(`Identifier collision: "${prev}" and "${p.name}" both map to "${p.ident}"`)
}
seen.set(p.ident, p.name)
}
const constLines = pairs.map((p) => ` ${p.ident}: ${JSON.stringify(p.name)},`).join('\n')
const arrayEntries = metricNames.map((n) => ` ${JSON.stringify(n)},`).join('\n')
return `// AUTO-GENERATED FILE. DO NOT EDIT.
//
// Source: copilot/copilot/contracts/metrics-v1.schema.json
// Regenerate with: bun run metrics-contract:generate
//
// Canonical mothership OTel metric names. Call sites should reference
// \`Metric.<Identifier>\` (e.g. \`Metric.CopilotToolDuration\`) rather than raw
// string literals, so the Go-side contract is the single source of truth and
// typos become compile errors.
//
// NAMES ONLY. Label keys and histogram bucket boundaries are NOT in this
// contract — Go owns the label-cardinality allowlist and the shared bucket
// constant, and the Sim emitter MUST mirror those by hand so the GoSim metric
// union is queryable as one series set.
export const Metric = {
${constLines}
} as const;
export type MetricKey = keyof typeof Metric;
export type MetricValue = (typeof Metric)[MetricKey];
/** Readonly sorted list of every canonical mothership metric name. */
export const MetricValues: readonly MetricValue[] = [
${arrayEntries}
] as const;
`
}
async function main() {
const checkOnly = process.argv.includes('--check')
const inputArg = process.argv.find((a) => a.startsWith('--input='))
const inputPath = inputArg
? resolve(ROOT, inputArg.slice('--input='.length))
: DEFAULT_CONTRACT_PATH
const raw = await readFile(inputPath, 'utf8')
const schema = JSON.parse(raw)
const metricNames = extractMetricNames(schema)
const rendered = formatGeneratedSource(render(metricNames), OUTPUT_PATH, ROOT)
if (checkOnly) {
const existing = await readFile(OUTPUT_PATH, 'utf8').catch(() => null)
if (existing !== rendered) {
throw new Error('Generated metrics contract is stale. Run: bun run metrics-contract:generate')
}
console.log('Metrics contract is up to date.')
return
}
await mkdir(dirname(OUTPUT_PATH), { recursive: true })
await writeFile(OUTPUT_PATH, rendered, 'utf8')
console.log(`Generated metrics types -> ${OUTPUT_PATH}`)
}
main().catch((err) => {
console.error(err)
process.exit(1)
})