feat: initial datalake and stats site (#28666)

This commit is contained in:
Adam 2026-05-25 17:34:04 -05:00 committed by GitHub
parent 633b5d6208
commit 5b02ac4d33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
68 changed files with 8967 additions and 42 deletions

12
.dockerignore Normal file
View file

@ -0,0 +1,12 @@
.git
.opencode
.sst
.turbo
.wrangler
node_modules
**/node_modules
**/.output
**/dist
**/.turbo
**/.vite
**/coverage

View file

@ -9,9 +9,15 @@ on:
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: read
id-token: write
jobs:
deploy:
if: github.repository == 'anomalyco/opencode' && (github.ref_name == 'dev' || github.ref_name == 'production')
runs-on: ubuntu-latest
environment: ${{ github.ref_name }}
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
@ -21,6 +27,12 @@ jobs:
with:
node-version: "24"
- uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4.3.1
with:
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_ARN }}
role-session-name: opencode-${{ github.run_id }}
aws-region: us-east-1
- run: bun sst deploy --stage=${{ github.ref_name }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

113
bun.lock
View file

@ -23,7 +23,7 @@
"oxlint-tsgolint": "0.21.0",
"prettier": "3.6.2",
"semver": "^7.6.0",
"sst": "4.13.1",
"sst": "catalog:",
"turbo": "2.8.13",
},
},
@ -603,6 +603,66 @@
"typescript": "catalog:",
},
},
"packages/stats/app": {
"name": "@opencode-ai/stats-app",
"version": "1.14.50",
"dependencies": {
"@opencode-ai/stats-core": "workspace:*",
"@opencode-ai/ui": "workspace:*",
"@solidjs/meta": "catalog:",
"@solidjs/router": "catalog:",
"@solidjs/start": "catalog:",
"d3-scale": "4.0.2",
"effect": "catalog:",
"nitro": "3.0.1-alpha.1",
"solid-js": "catalog:",
"vite": "catalog:",
},
"devDependencies": {
"@cloudflare/workers-types": "catalog:",
"@types/bun": "catalog:",
"@types/d3-scale": "4.0.9",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:",
},
},
"packages/stats/core": {
"name": "@opencode-ai/stats-core",
"version": "1.14.50",
"dependencies": {
"@aws-sdk/client-athena": "3.933.0",
"@planetscale/database": "1.19.0",
"drizzle-orm": "catalog:",
"effect": "catalog:",
"sst": "catalog:",
},
"devDependencies": {
"@tsconfig/node22": "catalog:",
"@types/bun": "catalog:",
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"drizzle-kit": "catalog:",
"typescript": "catalog:",
},
},
"packages/stats/server": {
"name": "@opencode-ai/stats-server",
"version": "1.14.50",
"dependencies": {
"@aws-sdk/client-firehose": "3.933.0",
"@effect/platform-node": "catalog:",
"@opencode-ai/stats-core": "workspace:*",
"effect": "catalog:",
"sst": "catalog:",
},
"devDependencies": {
"@tsconfig/node22": "catalog:",
"@types/bun": "catalog:",
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:",
},
},
"packages/storybook": {
"name": "@opencode-ai/storybook",
"devDependencies": {
@ -783,6 +843,7 @@
"shiki": "3.20.0",
"solid-js": "1.9.10",
"solid-list": "0.3.0",
"sst": "4.13.1",
"tailwindcss": "4.1.11",
"typescript": "5.8.2",
"ulid": "3.0.1",
@ -908,8 +969,12 @@
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
"@aws-sdk/client-athena": ["@aws-sdk/client-athena@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/credential-provider-node": "3.933.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-9eMUCu1Ay3C9ojo+dJcynSdpbxuwDVtZUt/Xhce+c2+mgDsmvRzjww+wfLpZwRNWxBWmeauQQAZk52tCwQgXsQ=="],
"@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/credential-provider-node": "^3.972.10", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7Ne3Yk/bgQPVebAkv7W+RfhiwTRSbfER9BtbhOa2w/+dIr902LrJf6vrZlxiqaJbGj2ALx8M+ZK1YIHVxSwu9A=="],
"@aws-sdk/client-firehose": ["@aws-sdk/client-firehose@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/credential-provider-node": "3.933.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-tDrtgczN2lQsflLDPYu/wdOoyCZLVYtgzmWnYzSEOBWd/cp2AbuQ7D+FemSwUTzyoMTuhhIevyEJKzqsF+QYxA=="],
"@aws-sdk/client-lambda": ["@aws-sdk/client-lambda@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-ryEYNVdilyWkKsOs/7Xy/l7+qjtSz4sll8NpcWD6AtONxjG/5OMaAhxxDkQb4iBoNMKnISxsARzQAp/Wa8pXIg=="],
"@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.933.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/credential-provider-node": "3.933.0", "@aws-sdk/middleware-bucket-endpoint": "3.930.0", "@aws-sdk/middleware-expect-continue": "3.930.0", "@aws-sdk/middleware-flexible-checksums": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-location-constraint": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-sdk-s3": "3.932.0", "@aws-sdk/middleware-ssec": "3.930.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/signature-v4-multi-region": "3.932.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/eventstream-serde-browser": "^4.2.5", "@smithy/eventstream-serde-config-resolver": "^4.3.5", "@smithy/eventstream-serde-node": "^4.2.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-blob-browser": "^4.2.6", "@smithy/hash-node": "^4.2.5", "@smithy/hash-stream-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/md5-js": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-KxwZvdxdCeWK6o8mpnb+kk7Kgb8V+8AjTwSXUWH1UAD85B0tjdo1cSfE5zoR5fWGol4Ml5RLez12a6LPhsoTqA=="],
@ -1588,6 +1653,12 @@
"@opencode-ai/slack": ["@opencode-ai/slack@workspace:packages/slack"],
"@opencode-ai/stats-app": ["@opencode-ai/stats-app@workspace:packages/stats/app"],
"@opencode-ai/stats-core": ["@opencode-ai/stats-core@workspace:packages/stats/core"],
"@opencode-ai/stats-server": ["@opencode-ai/stats-server@workspace:packages/stats/server"],
"@opencode-ai/storybook": ["@opencode-ai/storybook@workspace:packages/storybook"],
"@opencode-ai/ui": ["@opencode-ai/ui@workspace:packages/ui"],
@ -2326,6 +2397,10 @@
"@types/cross-spawn": ["@types/cross-spawn@6.0.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA=="],
"@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="],
"@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="],
"@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="],
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
@ -2898,6 +2973,20 @@
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
"d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
"d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="],
"d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
"d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
"d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
"d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="],
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
@ -3472,6 +3561,8 @@
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
"ioredis": ["ioredis@5.10.1", "", { "dependencies": { "@ioredis/commands": "1.5.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA=="],
"ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
@ -5180,6 +5271,16 @@
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
"@aws-sdk/client-athena/@smithy/core": ["@smithy/core@3.24.3", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg=="],
"@aws-sdk/client-athena/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A=="],
"@aws-sdk/client-athena/@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA=="],
"@aws-sdk/client-athena/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="],
"@aws-sdk/client-athena/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/core": ["@aws-sdk/core@3.973.27", "", { "dependencies": { "@aws-sdk/types": "^3.973.7", "@aws-sdk/xml-builder": "^3.972.17", "@smithy/core": "^3.23.14", "@smithy/node-config-provider": "^4.3.13", "@smithy/property-provider": "^4.2.13", "@smithy/protocol-http": "^5.3.13", "@smithy/signature-v4": "^5.3.13", "@smithy/smithy-client": "^4.12.9", "@smithy/types": "^4.14.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.13", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-CUZ5m8hwMCH6OYI4Li/WgMfIEx10Q2PLI9Y3XOUTPGZJ53aZ0007jCv+X/ywsaERyKPdw5MRZWk877roQksQ4A=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.30", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.25", "@aws-sdk/credential-provider-http": "^3.972.27", "@aws-sdk/credential-provider-ini": "^3.972.29", "@aws-sdk/credential-provider-process": "^3.972.25", "@aws-sdk/credential-provider-sso": "^3.972.29", "@aws-sdk/credential-provider-web-identity": "^3.972.29", "@aws-sdk/types": "^3.973.7", "@smithy/credential-provider-imds": "^4.2.13", "@smithy/property-provider": "^4.2.13", "@smithy/shared-ini-file-loader": "^4.4.8", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-FMnAnWxc8PG+ZrZ2OBKzY4luCUJhe9CG0B9YwYr4pzrYGLXBS2rl+UoUvjGbAwiptxRL6hyA3lFn03Bv1TLqTw=="],
@ -5204,6 +5305,16 @@
"@aws-sdk/client-cognito-identity/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
"@aws-sdk/client-firehose/@smithy/core": ["@smithy/core@3.24.3", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg=="],
"@aws-sdk/client-firehose/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A=="],
"@aws-sdk/client-firehose/@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA=="],
"@aws-sdk/client-firehose/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="],
"@aws-sdk/client-firehose/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
"@aws-sdk/client-lambda/@aws-sdk/core": ["@aws-sdk/core@3.974.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws-sdk/xml-builder": "^3.972.24", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.2", "@smithy/signature-v4": "^5.4.2", "@smithy/types": "^4.14.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-QpnINq5FZH6EOaDEkmHdT7eUunbvD27pDNQypaWjFyYz7Zl1q3UCMQErBZxpmfGfI7MvI2TlK8KTkgNpv8b1ug=="],
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.42", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.37", "@aws-sdk/credential-provider-http": "^3.972.39", "@aws-sdk/credential-provider-ini": "^3.972.41", "@aws-sdk/credential-provider-process": "^3.972.37", "@aws-sdk/credential-provider-sso": "^3.972.41", "@aws-sdk/credential-provider-web-identity": "^3.972.41", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/credential-provider-imds": "^4.3.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-D4oon2zbqqsWOJUM99Gm3/ZyJ0IJvTXVN3PyloGb3kQEyI36fjCZheZj422lAgTWWd6TSHgiImLt3RIaLdv3dQ=="],

View file

@ -30,7 +30,7 @@ export const api = new sst.cloudflare.Worker("Api", {
transform: {
worker: (args) => {
args.logpush = true
if ($app.stage === "vimtor") return
if ($app.stage === "vimtor" || $app.stage === "adam") return
args.bindings = $resolve(args.bindings).apply((bindings) => [
...bindings,
{

View file

@ -1,7 +1,9 @@
import { domain } from "./stage"
import { deployAws, domain } from "./stage"
import { EMAILOCTOPUS_API_KEY } from "./app"
import { SECRET } from "./secret"
const lake = deployAws ? await import("./lake") : undefined
////////////////
// DATABASE
////////////////
@ -240,7 +242,7 @@ const SALESFORCE_INSTANCE_URL = new sst.Secret("SALESFORCE_INSTANCE_URL")
const logProcessor = new sst.cloudflare.Worker("LogProcessor", {
handler: "packages/console/function/src/log-processor.ts",
link: [new sst.Secret("HONEYCOMB_API_KEY")],
link: [SECRET.HoneycombApiKey, ...(lake?.lakeIngest ? [lake.lakeIngest] : [])],
})
new sst.cloudflare.x.SolidStart("Console", {

340
infra/lake.ts Normal file
View file

@ -0,0 +1,340 @@
import { domain } from "./stage"
const current = aws.getCallerIdentityOutput({})
const partition = aws.getPartitionOutput({})
const region = aws.getRegionOutput({})
const tableBucketName = `opencode-${$app.stage}-lake`
const glueCatalogName = "s3tablescatalog"
const glueCatalogArn = $interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:catalog`
const glueS3TablesCatalogArn = $interpolate`${glueCatalogArn}/${glueCatalogName}`
const glueS3TablesChildCatalogArn = $interpolate`${glueS3TablesCatalogArn}/${tableBucketName}`
const glueS3TablesDatabaseWildcardArn = $interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:database/${glueCatalogName}/${tableBucketName}/*`
const glueS3TablesTableWildcardArn = $interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:table/${glueCatalogName}/${tableBucketName}/*/*`
const s3TablesBucketWildcardArn = $interpolate`arn:${partition.partition}:s3tables:${region.region}:${current.accountId}:bucket/*`
export const tableBucket = new aws.s3tables.TableBucket(
"LakeTableBucket",
{
name: tableBucketName,
forceDestroy: $app.stage !== "production",
},
)
const s3TablesCatalog = new aws.cloudcontrol.Resource(
"LakeS3TablesCatalog",
{
typeName: "AWS::Glue::Catalog",
desiredState: $jsonStringify({
Name: glueCatalogName,
Description: "Federated catalog for S3 Tables",
FederatedCatalog: {
Identifier: s3TablesBucketWildcardArn,
ConnectionName: "aws:s3tables",
},
CreateDatabaseDefaultPermissions: [
{
Principal: {
DataLakePrincipalIdentifier: "IAM_ALLOWED_PRINCIPALS",
},
Permissions: ["ALL"],
},
],
CreateTableDefaultPermissions: [
{
Principal: {
DataLakePrincipalIdentifier: "IAM_ALLOWED_PRINCIPALS",
},
Permissions: ["ALL"],
},
],
AllowFullTableExternalDataAccess: "True",
}),
},
{ dependsOn: [tableBucket] },
)
const athenaResultsBucket = new aws.s3.Bucket(
"LakeAthenaResults",
{
bucket: `opencode-${$app.stage}-lake-athena-results`,
forceDestroy: $app.stage !== "production",
},
)
const firehoseErrorBucket = new aws.s3.Bucket(
"LakeFirehoseErrors",
{
bucket: `opencode-${$app.stage}-lake-firehose-errors`,
forceDestroy: $app.stage !== "production",
},
)
const athenaWorkgroup = new aws.athena.Workgroup(
"LakeAthenaWorkgroup",
{
name: `opencode-${$app.stage}-lake-workgroup`,
forceDestroy: $app.stage !== "production",
configuration: {
enforceWorkgroupConfiguration: true,
publishCloudwatchMetricsEnabled: true,
resultConfiguration: {
outputLocation: $interpolate`s3://${athenaResultsBucket.bucket}/`,
},
},
},
)
const firehoseRole = new aws.iam.Role(
"LakeFirehoseRole",
{
assumeRolePolicy: aws.iam.getPolicyDocumentOutput({
statements: [
{
effect: "Allow",
actions: ["sts:AssumeRole"],
principals: [
{
type: "Service",
identifiers: ["firehose.amazonaws.com"],
},
],
},
],
}).json,
},
)
const firehosePolicy = new aws.iam.RolePolicy(
"LakeFirehosePolicy",
{
role: firehoseRole.id,
policy: aws.iam.getPolicyDocumentOutput({
statements: [
{
effect: "Allow",
actions: [
"s3tables:ListTableBuckets",
"s3tables:GetTableBucket",
"s3tables:GetNamespace",
"s3tables:GetTable",
"s3tables:GetTableData",
"s3tables:GetTableMetadataLocation",
"s3tables:ListNamespaces",
"s3tables:ListTables",
"s3tables:PutTableData",
"s3tables:UpdateTableMetadataLocation",
],
resources: ["*"],
},
{
effect: "Allow",
actions: [
"glue:GetCatalog",
"glue:GetCatalogs",
"glue:GetDatabase",
"glue:GetDatabases",
"glue:GetTable",
"glue:GetTables",
"glue:UpdateTable",
],
resources: [
glueCatalogArn,
glueS3TablesCatalogArn,
$interpolate`${glueS3TablesCatalogArn}/*`,
glueS3TablesDatabaseWildcardArn,
glueS3TablesTableWildcardArn,
$interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:database/*`,
$interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:table/*/*`,
$interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:table/${glueCatalogName}/*`,
],
},
{
effect: "Allow",
actions: [
"s3:AbortMultipartUpload",
"s3:GetBucketLocation",
"s3:GetObject",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
"s3:PutObject",
],
resources: [firehoseErrorBucket.arn, $interpolate`${firehoseErrorBucket.arn}/*`],
},
{
effect: "Allow",
actions: ["lakeformation:GetDataAccess"],
resources: ["*"],
},
],
}).json,
},
)
const firehose = new aws.kinesis.FirehoseDeliveryStream(
"LakeFirehose",
{
name: `opencode-${$app.stage}-lake-ingest`,
destination: "iceberg",
icebergConfiguration: {
appendOnly: true,
bufferingInterval: 60,
bufferingSize: 1,
catalogArn: glueS3TablesChildCatalogArn,
processingConfiguration: {
enabled: true,
processors: [
{
type: "MetadataExtraction",
parameters: [
{ parameterName: "JsonParsingEngine", parameterValue: "JQ-1.6" },
{
parameterName: "MetadataExtractionQuery",
parameterValue:
'{destinationDatabaseName:._lake_database,destinationTableName:._lake_table,operation:(._lake_operation // "insert")}',
},
],
},
],
},
roleArn: firehoseRole.arn,
s3BackupMode: "FailedDataOnly",
s3Configuration: {
roleArn: firehoseRole.arn,
bucketArn: firehoseErrorBucket.arn,
errorOutputPrefix: "errors/!{firehose:error-output-type}/",
},
},
},
{ dependsOn: [s3TablesCatalog, firehosePolicy] },
)
export const lakeVpc = new sst.aws.Vpc("LakeVpc")
export const lakeCluster = new sst.aws.Cluster("LakeCluster", { vpc: lakeVpc })
export const lakeRegion = region.region
export const lakeCatalog = $interpolate`${glueCatalogName}/${tableBucket.name}`
export const lakeAthenaWorkgroup = athenaWorkgroup
const ingestSecret = new random.RandomPassword("LakeIngestSecret", { length: 32 })
const ingestConfig = new sst.Linkable("LakeIngestConfig", {
properties: {
streamName: firehose.name,
secret: ingestSecret.result,
},
})
const ingestService = new sst.aws.Service("LakeIngestService", {
cluster: lakeCluster,
architecture: "arm64",
cpu: "0.5 vCPU",
memory: "1 GB",
image: {
context: ".",
dockerfile: "packages/stats/server/Dockerfile",
},
link: [ingestConfig],
permissions: [
{
actions: ["firehose:PutRecord", "firehose:PutRecordBatch"],
resources: [firehose.arn],
},
],
scaling: {
min: $app.stage === "production" ? 2 : 1,
max: $app.stage === "production" ? 32 : 4,
cpuUtilization: 60,
memoryUtilization: 70,
},
loadBalancer: {
domain: {
name: `lake.${domain}`,
dns: sst.cloudflare.dns(),
},
rules: [
{ listen: "80/http", redirect: "443/https" },
{ listen: "443/https", forward: "3000/http" },
],
health: {
"3000/http": {
path: "/ready",
successCodes: "200-299",
},
},
},
health: {
command: [
"CMD-SHELL",
"bun --eval \"fetch('http://localhost:3000/health').then((r) => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))\"",
],
interval: "30 seconds",
retries: 3,
startPeriod: "30 seconds",
timeout: "5 seconds",
},
dev: {
command: "bun run start",
directory: "packages/stats/server",
url: "http://localhost:3000",
},
wait: $app.stage === "production",
})
export const lakeIngest = new sst.Linkable("LakeIngest", {
properties: {
url: ingestService.url,
secret: ingestSecret.result,
},
})
export const lakeQueryPermissions = [
{
actions: ["athena:StartQueryExecution", "athena:GetQueryExecution", "athena:GetQueryResults"],
resources: [athenaWorkgroup.arn],
},
{
actions: [
"glue:GetCatalog",
"glue:GetCatalogs",
"glue:GetDatabase",
"glue:GetDatabases",
"glue:GetTable",
"glue:GetTables",
"glue:GetPartitions",
],
resources: [
glueCatalogArn,
glueS3TablesCatalogArn,
$interpolate`${glueS3TablesCatalogArn}/*`,
glueS3TablesDatabaseWildcardArn,
glueS3TablesTableWildcardArn,
$interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:database/*`,
$interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:table/*/*`,
$interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:table/${glueCatalogName}/*`,
],
},
{
actions: ["s3:GetBucketLocation", "s3:ListBucket"],
resources: [athenaResultsBucket.arn],
},
{
actions: ["s3:GetObject", "s3:PutObject", "s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads"],
resources: [$interpolate`${athenaResultsBucket.arn}/*`],
},
{
actions: [
"s3tables:GetTableBucket",
"s3tables:GetNamespace",
"s3tables:GetTable",
"s3tables:GetTableData",
"s3tables:GetTableMetadataLocation",
"s3tables:ListNamespaces",
"s3tables:ListTables",
],
resources: ["*"],
},
{
actions: ["lakeformation:GetDataAccess"],
resources: ["*"],
},
]

View file

@ -7,6 +7,7 @@ sst.Linkable.wrap(random.RandomPassword, (resource) => ({
export const SECRET = {
R2AccessKey: new sst.Secret("R2AccessKey", "unknown"),
R2SecretKey: new sst.Secret("R2SecretKey", "unknown"),
HoneycombApiKey: new sst.Secret("HONEYCOMB_API_KEY"),
HoneycombWebhookSecret: new random.RandomPassword("HoneycombWebhookSecret", { length: 24 }),
UpstashRedisRestUrl: new sst.Secret("UpstashRedisRestUrl"),
UpstashRedisRestToken: new sst.Secret("UpstashRedisRestToken"),

View file

@ -5,6 +5,50 @@ export const domain = (() => {
})()
export const zoneID = "430ba34c138cfb5360826c4909f99be8"
export const deployAws = $app.stage === "production" || $app.stage === "dev" || $app.stage === "adam"
const githubActionsDeployRole = (() => {
if ($app.stage !== "dev" && $app.stage !== "production") return
const provider = new aws.iam.OpenIdConnectProvider("GithubActionsOidcProvider", {
url: "https://token.actions.githubusercontent.com",
clientIdLists: ["sts.amazonaws.com"],
})
const role = new aws.iam.Role("GithubActionsDeployRole", {
name: `opencode-${$app.stage}-github-actions-deploy`,
maxSessionDuration: 3600,
assumeRolePolicy: aws.iam.getPolicyDocumentOutput({
statements: [
{
effect: "Allow",
actions: ["sts:AssumeRoleWithWebIdentity"],
principals: [{ type: "Federated", identifiers: [provider.arn] }],
conditions: [
{
test: "StringEquals",
variable: "token.actions.githubusercontent.com:aud",
values: ["sts.amazonaws.com"],
},
{
test: "StringEquals",
variable: "token.actions.githubusercontent.com:sub",
values: [`repo:anomalyco/opencode:environment:${$app.stage}`],
},
],
},
],
}).json,
})
new aws.iam.RolePolicyAttachment("GithubActionsDeployRoleAdmin", {
role: role.name,
policyArn: "arn:aws:iam::aws:policy/AdministratorAccess",
})
return role
})()
export const githubActionsDeployRoleArn = githubActionsDeployRole?.arn
new cloudflare.RegionalHostname("RegionalHostname", {
hostname: domain,

207
infra/stats.ts Normal file
View file

@ -0,0 +1,207 @@
import { lakeAthenaWorkgroup, lakeCatalog, lakeCluster, lakeQueryPermissions, lakeRegion, tableBucket } from "./lake"
const domain = (() => {
if ($app.stage === "production") return "stats.opencode.ai"
if ($app.stage === "dev") return "stats.dev.opencode.ai"
return `stats.${$app.stage}.dev.opencode.ai`
})()
////////////////
// LAKE
////////////////
const inferenceNamespace = new aws.s3tables.Namespace("LakeInferenceNamespace", {
namespace: "inference",
tableBucketArn: tableBucket.arn,
})
const inferenceEventTable = new aws.s3tables.Table(
"LakeInferenceEventTable",
{
name: "event",
namespace: inferenceNamespace.namespace,
tableBucketArn: inferenceNamespace.tableBucketArn,
format: "ICEBERG",
metadata: {
iceberg: {
schema: {
fields: [
{ name: "event_timestamp", type: "string", required: false },
{ name: "event_date", type: "string", required: false },
{ name: "event_type", type: "string", required: false },
{ name: "dataset", type: "string", required: false },
{ name: "cf_continent", type: "string", required: false },
{ name: "cf_country", type: "string", required: false },
{ name: "cf_city", type: "string", required: false },
{ name: "cf_region", type: "string", required: false },
{ name: "cf_latitude", type: "double", required: false },
{ name: "cf_longitude", type: "double", required: false },
{ name: "cf_timezone", type: "string", required: false },
{ name: "duration", type: "double", required: false },
{ name: "request_length", type: "long", required: false },
{ name: "status", type: "int", required: false },
{ name: "ip", type: "string", required: false },
{ name: "is_stream", type: "boolean", required: false },
{ name: "session", type: "string", required: false },
{ name: "request", type: "string", required: false },
{ name: "client", type: "string", required: false },
{ name: "user_agent", type: "string", required: false },
{ name: "model_variant", type: "string", required: false },
{ name: "source", type: "string", required: false },
{ name: "provider", type: "string", required: false },
{ name: "provider_model", type: "string", required: false },
{ name: "model", type: "string", required: false },
{ name: "llm_error_code", type: "int", required: false },
{ name: "llm_error_message", type: "string", required: false },
{ name: "error_response", type: "string", required: false },
{ name: "error_type", type: "string", required: false },
{ name: "error_message", type: "string", required: false },
{ name: "error_cause", type: "string", required: false },
{ name: "error_cause2", type: "string", required: false },
{ name: "api_key", type: "string", required: false },
{ name: "workspace", type: "string", required: false },
{ name: "is_subscription", type: "boolean", required: false },
{ name: "subscription", type: "string", required: false },
{ name: "response_length", type: "long", required: false },
{ name: "time_to_first_byte", type: "long", required: false },
{ name: "timestamp_first_byte", type: "long", required: false },
{ name: "timestamp_last_byte", type: "long", required: false },
{ name: "tokens_input", type: "long", required: false },
{ name: "tokens_output", type: "long", required: false },
{ name: "tokens_reasoning", type: "long", required: false },
{ name: "tokens_cache_read", type: "long", required: false },
{ name: "tokens_cache_write_5m", type: "long", required: false },
{ name: "tokens_cache_write_1h", type: "long", required: false },
{ name: "cost_input_microcents", type: "long", required: false },
{ name: "cost_output_microcents", type: "long", required: false },
{ name: "cost_cache_read_microcents", type: "long", required: false },
{ name: "cost_cache_write_microcents", type: "long", required: false },
{ name: "cost_total_microcents", type: "long", required: false },
{ name: "cost_input", type: "long", required: false },
{ name: "cost_output", type: "long", required: false },
{ name: "cost_cache_read", type: "long", required: false },
{ name: "cost_cache_write_5m", type: "long", required: false },
{ name: "cost_cache_write_1h", type: "long", required: false },
{ name: "cost_total", type: "long", required: false },
],
},
},
},
},
{ deleteBeforeReplace: $app.stage !== "production" },
)
export const inferenceEvent = new sst.Linkable("InferenceEvent", {
properties: {
region: lakeRegion,
catalog: lakeCatalog,
database: inferenceNamespace.namespace,
table: inferenceEventTable.name,
tableBucket: tableBucket.name,
workgroup: lakeAthenaWorkgroup.name,
},
})
////////////////
// DATABASE
////////////////
const cluster = planetscale.getDatabaseOutput({
name: "opencode-stats",
organization: "anomalyco",
})
const branch =
$app.stage === "production"
? planetscale.getBranchOutput({
name: "production",
organization: cluster.organization,
database: cluster.name,
})
: new planetscale.Branch("StatsDatabaseBranch", {
database: cluster.name,
organization: cluster.organization,
name: $app.stage,
parentBranch: "production",
})
const password = new planetscale.Password("StatsDatabasePassword", {
name: $app.stage,
database: cluster.name,
organization: cluster.organization,
branch: branch.name,
})
const databaseUrl = $interpolate`mysql://${password.username.apply(encodeURIComponent)}:${password.plaintext.apply(
encodeURIComponent,
)}@${password.accessHostUrl}/${cluster.name}`
export const database = new sst.Linkable("StatsDatabase", {
properties: {
host: password.accessHostUrl,
database: cluster.name,
username: password.username,
password: password.plaintext,
port: 3306,
url: databaseUrl,
},
})
new sst.x.DevCommand("StatsStudio", {
link: [database],
environment: {
DATABASE_URL: databaseUrl,
},
dev: {
command: "bun db:studio",
directory: "packages/stats/core",
autostart: false,
},
})
////////////////
// APP
////////////////
// export const app = new sst.cloudflare.x.SolidStart("Stats", {
// path: "packages/stats/app",
// buildCommand: "bun run build",
// domain,
// link: [database],
// environment: {
// PUBLIC_URL: `https://${domain}`,
// },
// })
////////////////
// SERVICES
////////////////
const statsSyncConfig = new sst.Linkable("StatsSyncConfig", {
properties: {
dataset: "zen",
},
})
export const statSync = new sst.aws.Service("StatsSyncService", {
cluster: lakeCluster,
architecture: "arm64",
cpu: "0.25 vCPU",
memory: "0.5 GB",
image: {
context: ".",
dockerfile: "packages/stats/server/Dockerfile",
},
command: ["bun", "src/stat-sync.ts"],
link: [database, inferenceEvent, statsSyncConfig],
permissions: lakeQueryPermissions,
scaling: {
min: 1,
max: 1,
},
dev: {
command: "bun src/stat-sync.ts",
directory: "packages/stats/server",
autostart: false,
},
})

View file

@ -10,6 +10,7 @@
"dev:desktop": "bun --cwd packages/desktop dev",
"dev:web": "bun --cwd packages/app dev",
"dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
"dev:stats": "bun run --cwd packages/stats/app dev",
"dev:storybook": "bun --cwd packages/storybook storybook",
"lint": "oxlint",
"typecheck": "bun turbo typecheck",
@ -17,13 +18,14 @@
"postinstall": "bun run --cwd packages/opencode fix-node-pty",
"prepare": "husky",
"random": "echo 'Random script'",
"hello": "echo 'Hello World!'",
"sso": "aws sso login --sso-session=opencode --no-browser",
"test": "echo 'do not run tests from root' && exit 1"
},
"workspaces": {
"packages": [
"packages/*",
"packages/console/*",
"packages/stats/*",
"packages/sdk/js",
"packages/slack"
],
@ -72,6 +74,7 @@
"@typescript/native-preview": "7.0.0-dev.20251207.1",
"zod": "4.1.8",
"remeda": "2.26.0",
"sst": "4.13.1",
"shiki": "3.20.0",
"solid-list": "0.3.0",
"tailwindcss": "4.1.11",
@ -98,7 +101,7 @@
"oxlint-tsgolint": "0.21.0",
"prettier": "3.6.2",
"semver": "^7.6.0",
"sst": "4.13.1",
"sst": "catalog:",
"turbo": "2.8.13"
},
"dependencies": {

View file

@ -6,7 +6,7 @@ export const logger = {
},
log: console.log,
debug: (message: string) => {
if (Resource.App.stage === "production") return
if (Resource.App.stage === "production" || Resource.App.stage === "adam") return
console.debug(message)
},
}

View file

@ -9,7 +9,7 @@ export default defineConfig({
}) as PluginOption,
nitro({
compatibilityDate: "2024-09-19",
preset: "cloudflare_module",
preset: "cloudflare-module",
cloudflare: {
nodeCompat: true,
},

View file

@ -0,0 +1,146 @@
import { Resource } from "@opencode-ai/console-resource"
import { and, Database, eq, isNull } from "../src/drizzle/index.js"
import { Identifier } from "../src/identifier.js"
import { AccountTable } from "../src/schema/account.sql.js"
import { AuthTable } from "../src/schema/auth.sql.js"
import { BillingTable } from "../src/schema/billing.sql.js"
import { KeyTable } from "../src/schema/key.sql.js"
import { UserTable } from "../src/schema/user.sql.js"
import { WorkspaceTable } from "../src/schema/workspace.sql.js"
import { centsToMicroCents } from "../src/util/price.js"
const args = parseArgs(process.argv.slice(2))
if (!args.email) {
console.error(
"Usage: bun script/create-api-key.ts --email <email> [--workspace-id <wrk_...>] [--workspace-name <name>] [--key-name <name>] [--balance-dollars <amount>] [--allow-production]",
)
process.exit(1)
}
if (Resource.App.stage === "production" && !args.allowProduction) {
throw new Error("Refusing to create a production API key without --allow-production")
}
const result = await Database.transaction(async (tx) => {
const auth = await tx
.select()
.from(AuthTable)
.where(and(eq(AuthTable.provider, "email"), eq(AuthTable.subject, args.email)))
.then((rows) => rows[0])
const accountID = auth?.accountID ?? Identifier.create("account")
if (!auth) {
await tx.insert(AccountTable).values({ id: accountID })
await tx.insert(AuthTable).values({
id: Identifier.create("auth"),
provider: "email",
subject: args.email,
accountID,
})
}
const workspace = args.workspaceID
? await tx
.select()
.from(WorkspaceTable)
.where(eq(WorkspaceTable.id, args.workspaceID))
.then((rows) => rows[0])
: await tx
.select({ workspace: WorkspaceTable })
.from(UserTable)
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID))
.where(and(eq(UserTable.accountID, accountID), isNull(UserTable.timeDeleted)))
.then((rows) => rows[0]?.workspace)
if (args.workspaceID && !workspace) throw new Error(`Workspace not found: ${args.workspaceID}`)
const workspaceID = workspace?.id ?? Identifier.create("workspace")
if (!workspace) {
await tx.insert(WorkspaceTable).values({
id: workspaceID,
slug: null,
name: args.workspaceName ?? `${args.email} manual`,
})
}
const user = await tx
.select()
.from(UserTable)
.where(
and(eq(UserTable.workspaceID, workspaceID), eq(UserTable.accountID, accountID), isNull(UserTable.timeDeleted)),
)
.then((rows) => rows[0])
const userID = user?.id ?? Identifier.create("user")
if (!user) {
await tx.insert(UserTable).values({
id: userID,
workspaceID,
accountID,
email: args.email,
name: args.email,
role: "admin",
})
}
const balance = centsToMicroCents(args.balanceDollars * 100)
const billing = await tx
.select()
.from(BillingTable)
.where(eq(BillingTable.workspaceID, workspaceID))
.then((rows) => rows[0])
if (!billing) {
await tx.insert(BillingTable).values({
id: Identifier.create("billing"),
workspaceID,
balance,
})
} else if (billing.balance < balance) {
await tx.update(BillingTable).set({ balance }).where(eq(BillingTable.workspaceID, workspaceID))
}
const secretKey = createSecretKey()
const keyID = Identifier.create("key")
await tx.insert(KeyTable).values({
id: keyID,
workspaceID,
userID,
name: args.keyName ?? "Manual API Key",
key: secretKey,
timeUsed: null,
})
return { accountID, workspaceID, userID, keyID, secretKey }
})
console.log(JSON.stringify({ stage: Resource.App.stage, ...result }, null, 2))
function createSecretKey() {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
const values = new Uint32Array(64)
crypto.getRandomValues(values)
return `sk-${Array.from(values, (value) => chars[value % chars.length]).join("")}`
}
function parseArgs(argv: string[]) {
const parsed = {
email: "",
workspaceID: "",
workspaceName: "",
keyName: "",
balanceDollars: 100,
allowProduction: false,
}
for (let index = 0; index < argv.length; index++) {
const arg = argv[index]
if (arg === "--email") parsed.email = requiredValue(argv, ++index, arg)
if (arg === "--workspace-id") parsed.workspaceID = requiredValue(argv, ++index, arg)
if (arg === "--workspace-name") parsed.workspaceName = requiredValue(argv, ++index, arg)
if (arg === "--key-name") parsed.keyName = requiredValue(argv, ++index, arg)
if (arg === "--balance-dollars") parsed.balanceDollars = Number(requiredValue(argv, ++index, arg))
if (arg === "--allow-production") parsed.allowProduction = true
}
if (!Number.isFinite(parsed.balanceDollars) || parsed.balanceDollars < 0) throw new Error("Invalid --balance-dollars")
return parsed
}
function requiredValue(argv: string[], index: number, arg: string) {
const value = argv[index]
if (!value || value.startsWith("--")) throw new Error(`Missing value for ${arg}`)
return value
}

View file

@ -21,7 +21,7 @@ export default {
)
continue
let data = {
let data: Record<string, unknown> = {
"cf.continent": event.event.request.cf?.continent,
"cf.country": event.event.request.cf?.country,
"cf.city": event.event.request.cf?.city,
@ -35,30 +35,152 @@ export default {
ip: event.event.request.headers["x-real-ip"],
}
const time = new Date(event.eventTimestamp ?? Date.now()).toISOString()
const events = []
for (const log of event.logs) {
for (const message of log.message) {
if (!message.startsWith("_metric:")) continue
const json = JSON.parse(message.slice(8))
data = { ...data, ...json }
if ("llm.error.code" in json) {
events.push({ time, data: { ...data, event_type: "llm.error" } })
}
}
}
events.push({ time, data: { ...data, event_type: "completions" } })
const events = [
...event.logs.flatMap((log) =>
log.message.flatMap((message: string) => {
if (!message.startsWith("_metric:")) return []
const json = JSON.parse(message.slice(8)) as Record<string, unknown>
data = { ...data, ...json }
if ("llm.error.code" in json) {
return [{ time, data: { ...data, event_type: "llm.error" } }]
}
return []
}),
),
{ time, data: { ...data, event_type: "completions" } },
]
console.log(JSON.stringify(data, null, 2))
const ret = await fetch("https://api.honeycomb.io/1/batch/zen", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Honeycomb-Team": Resource.HONEYCOMB_API_KEY.value,
},
body: JSON.stringify(events),
})
console.log(ret.status)
console.log(await ret.text())
const lakeIngest = getLakeIngest()
const [honeycomb, lake] = await Promise.all([
fetch("https://api.honeycomb.io/1/batch/zen", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Honeycomb-Team": Resource.HONEYCOMB_API_KEY.value,
},
body: JSON.stringify(events),
}),
...(lakeIngest
? [
fetch(lakeIngest.url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${lakeIngest.secret}`,
},
body: JSON.stringify({ events: events.map((event) => toLakeEvent(event.time, event.data)) }),
}),
]
: []),
])
console.log(honeycomb.status)
console.log(await honeycomb.text())
if (lake) {
console.log(lake.status)
console.log(await lake.text())
}
}
},
}
function getLakeIngest(): { url: string; secret: string } | undefined {
try {
return Resource.LakeIngest
} catch {
return undefined
}
}
function toLakeEvent(time: string, data: Record<string, unknown>) {
return {
_datalake_key: "inference.event",
event_timestamp: time,
event_date: time.slice(0, 10),
event_type: string(data, "event_type"),
dataset: "zen",
cf_continent: string(data, "cf.continent"),
cf_country: string(data, "cf.country"),
cf_city: string(data, "cf.city"),
cf_region: string(data, "cf.region"),
cf_latitude: number(data, "cf.latitude"),
cf_longitude: number(data, "cf.longitude"),
cf_timezone: string(data, "cf.timezone"),
duration: number(data, "duration"),
request_length: integer(data, "request_length"),
status: integer(data, "status"),
ip: string(data, "ip"),
is_stream: boolean(data, "is_stream"),
session: string(data, "session"),
request: string(data, "request"),
client: string(data, "client"),
user_agent: string(data, "user_agent"),
model_variant: string(data, "model.variant"),
source: string(data, "source"),
provider: string(data, "provider"),
provider_model: string(data, "provider.model"),
model: string(data, "model"),
llm_error_code: integer(data, "llm.error.code"),
llm_error_message: string(data, "llm.error.message"),
error_response: string(data, "error.response"),
error_type: string(data, "error.type"),
error_message: string(data, "error.message"),
error_cause: string(data, "error.cause"),
error_cause2: string(data, "error.cause2"),
api_key: string(data, "api_key"),
workspace: string(data, "workspace"),
is_subscription: boolean(data, "isSubscription"),
subscription: string(data, "subscription"),
response_length: integer(data, "response_length"),
time_to_first_byte: integer(data, "time_to_first_byte"),
timestamp_first_byte: integer(data, "timestamp.first_byte"),
timestamp_last_byte: integer(data, "timestamp.last_byte"),
tokens_input: integer(data, "tokens.input"),
tokens_output: integer(data, "tokens.output"),
tokens_reasoning: integer(data, "tokens.reasoning"),
tokens_cache_read: integer(data, "tokens.cache_read"),
tokens_cache_write_5m: integer(data, "tokens.cache_write_5m"),
tokens_cache_write_1h: integer(data, "tokens.cache_write_1h"),
cost_input_microcents: integer(data, "cost.input.microcents"),
cost_output_microcents: integer(data, "cost.output.microcents"),
cost_cache_read_microcents: integer(data, "cost.cache_read.microcents"),
cost_cache_write_microcents: integer(data, "cost.cache_write.microcents"),
cost_total_microcents: integer(data, "cost.total.microcents"),
cost_input: integer(data, "cost.input"),
cost_output: integer(data, "cost.output"),
cost_cache_read: integer(data, "cost.cache_read"),
cost_cache_write_5m: integer(data, "cost.cache_write_5m"),
cost_cache_write_1h: integer(data, "cost.cache_write_1h"),
cost_total: integer(data, "cost.total"),
}
}
function string(data: Record<string, unknown>, key: string) {
const value = data[key]
if (typeof value === "string") return value
if (typeof value === "number" || typeof value === "boolean") return String(value)
return undefined
}
function boolean(data: Record<string, unknown>, key: string) {
const value = data[key]
if (typeof value === "boolean") return value
if (typeof value === "string") return value === "true" ? true : value === "false" ? false : undefined
return undefined
}
function integer(data: Record<string, unknown>, key: string) {
const value = number(data, key)
if (value === undefined) return undefined
return Math.round(value)
}
function number(data: Record<string, unknown>, key: string) {
const value = data[key]
if (typeof value === "number") return Number.isFinite(value) ? value : undefined
if (typeof value === "string") {
const parsed = Number(value)
return Number.isFinite(parsed) ? parsed : undefined
}
return undefined
}

View file

@ -11,6 +11,7 @@ export const Resource = new Proxy(
{
get(_target, prop: keyof typeof ResourceBase) {
const value = ResourceBase[prop]
const secrets = ResourceBase as unknown as Record<string, { value: string }>
if ("type" in value) {
// @ts-ignore
if (value.type === "sst.cloudflare.Bucket") {
@ -21,11 +22,11 @@ export const Resource = new Proxy(
// @ts-ignore
if (value.type === "sst.cloudflare.Kv") {
const client = new Cloudflare({
apiToken: ResourceBase.CLOUDFLARE_API_TOKEN.value,
apiToken: secrets.CLOUDFLARE_API_TOKEN.value,
})
// @ts-ignore
const namespaceId = value.namespaceId
const accountId = ResourceBase.CLOUDFLARE_DEFAULT_ACCOUNT_ID.value
const accountId = secrets.CLOUDFLARE_DEFAULT_ACCOUNT_ID.value
return {
get: (k: string | string[]) => {
const isMulti = Array.isArray(k)

View file

@ -8,7 +8,7 @@ const nitroConfig: any = (() => {
if (target === "cloudflare") {
return {
compatibilityDate: "2024-09-19",
preset: "cloudflare_module",
preset: "cloudflare-module",
cloudflare: {
nodeCompat: true,
},

16
packages/stats/README.md Normal file
View file

@ -0,0 +1,16 @@
# OpenCode Stats
Stats is a separate site from the console. Runtime, database, and domain services live in `core`; the SolidStart website lives in `app`; deployable Lambda entrypoints live in `function`.
## Packages
- `app`: SolidStart frontend/site.
- `core`: Effect services, app config, Drizzle schema/migrations, and stats domains.
- `function`: Lambda handlers that call into `core` services.
## Commands
- `bun run dev:stats` from the repo root starts the SolidStart app.
- `bun run --cwd packages/stats/app typecheck` typechecks the site.
- `bun run --cwd packages/stats/core typecheck` typechecks the Effect/database package.
- `bun run --cwd packages/stats/function typecheck` typechecks Lambda entrypoints.

17
packages/stats/app/.gitignore vendored Normal file
View file

@ -0,0 +1,17 @@
dist
.wrangler
.output
.vercel
.netlify
app.config.timestamp_*.js
# Environment
.env
.env*.local
# dependencies
/node_modules
# System Files
.DS_Store
Thumbs.db

View file

@ -0,0 +1,5 @@
export default {
server: {
preset: "cloudflare-module",
},
}

View file

@ -0,0 +1,36 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/stats-app",
"version": "1.14.50",
"private": true,
"type": "module",
"license": "MIT",
"scripts": {
"typecheck": "tsgo --noEmit",
"dev": "vite dev --host 0.0.0.0",
"build": "vite build",
"start": "vite start"
},
"dependencies": {
"@opencode-ai/stats-core": "workspace:*",
"@opencode-ai/ui": "workspace:*",
"@solidjs/meta": "catalog:",
"@solidjs/router": "catalog:",
"@solidjs/start": "catalog:",
"d3-scale": "4.0.2",
"effect": "catalog:",
"nitro": "3.0.1-alpha.1",
"solid-js": "catalog:",
"vite": "catalog:"
},
"devDependencies": {
"@cloudflare/workers-types": "catalog:",
"@types/bun": "catalog:",
"@types/d3-scale": "4.0.9",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:"
},
"engines": {
"node": ">=22"
}
}

View file

@ -0,0 +1,123 @@
:root {
color-scheme: light dark;
--stats-bg: #f8f5ee;
--stats-ink: #16110d;
--stats-muted: #6d6257;
--stats-line: #ded5c9;
--stats-panel: #fffaf1;
--stats-accent: #2357ff;
}
@media (prefers-color-scheme: dark) {
:root {
--stats-bg: #11100e;
--stats-ink: #f7efe4;
--stats-muted: #b8aa99;
--stats-line: #322d27;
--stats-panel: #1a1714;
--stats-accent: #86a2ff;
}
}
html {
line-height: 1;
background: var(--stats-bg);
}
body {
margin: 0;
min-width: 320px;
background:
radial-gradient(circle at top left, color-mix(in srgb, var(--stats-accent) 16%, transparent), transparent 32rem),
var(--stats-bg);
color: var(--stats-ink);
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
-webkit-font-smoothing: antialiased;
}
a {
color: inherit;
}
.shell {
box-sizing: border-box;
min-height: 100vh;
padding: 2rem clamp(1rem, 4vw, 4rem);
}
.panel {
display: grid;
gap: clamp(2rem, 8vw, 5rem);
box-sizing: border-box;
width: min(100%, 72rem);
margin: 0 auto;
padding: clamp(1.25rem, 4vw, 3rem);
border: 1px solid var(--stats-line);
border-radius: 1.5rem;
background: color-mix(in srgb, var(--stats-panel) 88%, transparent);
}
.eyebrow {
margin: 0 0 1rem;
color: var(--stats-muted);
font-size: 0.75rem;
letter-spacing: 0.14em;
text-transform: uppercase;
}
h1 {
max-width: 11ch;
margin: 0;
font-size: clamp(3rem, 14vw, 9rem);
line-height: 0.85;
letter-spacing: -0.08em;
}
.summary {
max-width: 42rem;
margin: 1.5rem 0 0;
color: var(--stats-muted);
font-size: clamp(1rem, 2vw, 1.25rem);
line-height: 1.6;
}
.grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1px;
overflow: hidden;
border: 1px solid var(--stats-line);
border-radius: 1rem;
background: var(--stats-line);
}
.metric {
padding: 1rem;
background: var(--stats-panel);
}
.metric b {
display: block;
margin-bottom: 0.5rem;
font-size: clamp(1.5rem, 4vw, 3rem);
letter-spacing: -0.05em;
}
.metric span {
color: var(--stats-muted);
font-size: 0.8125rem;
}
.link {
display: inline-flex;
width: fit-content;
margin-top: 1.5rem;
color: var(--stats-accent);
text-decoration: none;
}
@media (max-width: 720px) {
.grid {
grid-template-columns: 1fr;
}
}

View file

@ -0,0 +1,30 @@
import { MetaProvider, Meta, Title } from "@solidjs/meta"
import { Router } from "@solidjs/router"
import { FileRoutes } from "@solidjs/start/router"
import { Suspense } from "solid-js"
import "./app.css"
function AppMeta() {
return (
<>
<Title>opencode stats</Title>
<Meta name="description" content="OpenCode usage and stats." />
</>
)
}
export default function App() {
return (
<Router
explicitLinks={true}
root={(props) => (
<MetaProvider>
<AppMeta />
<Suspense>{props.children}</Suspense>
</MetaProvider>
)}
>
<FileRoutes />
</Router>
)
}

View file

@ -0,0 +1,18 @@
<svg width="234" height="42" viewBox="0 0 234 42" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 30H6V18H18V30Z" fill="#4B4646"/>
<path d="M18 12H6V30H18V12ZM24 36H0V6H24V36Z" fill="#B7B1B1"/>
<path d="M48 30H36V18H48V30Z" fill="#4B4646"/>
<path d="M36 30H48V12H36V30ZM54 36H36V42H30V6H54V36Z" fill="#B7B1B1"/>
<path d="M84 24V30H66V24H84Z" fill="#4B4646"/>
<path d="M84 24H66V30H84V36H60V6H84V24ZM66 18H78V12H66V18Z" fill="#B7B1B1"/>
<path d="M108 36H96V18H108V36Z" fill="#4B4646"/>
<path d="M108 12H96V36H90V6H108V12ZM114 36H108V12H114V36Z" fill="#B7B1B1"/>
<path d="M144 30H126V18H144V30Z" fill="#4B4646"/>
<path d="M144 12H126V30H144V36H120V6H144V12Z" fill="#F1ECEC"/>
<path d="M168 30H156V18H168V30Z" fill="#4B4646"/>
<path d="M168 12H156V30H168V12ZM174 36H150V6H174V36Z" fill="#F1ECEC"/>
<path d="M198 30H186V18H198V30Z" fill="#4B4646"/>
<path d="M198 12H186V30H198V12ZM204 36H180V6H198V0H204V36Z" fill="#F1ECEC"/>
<path d="M234 24V30H216V24H234Z" fill="#4B4646"/>
<path d="M216 12V18H228V12H216ZM234 24H216V30H234V36H210V6H234V24Z" fill="#F1ECEC"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,18 @@
<svg width="234" height="42" viewBox="0 0 234 42" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 30H6V18H18V30Z" fill="#CFCECD"/>
<path d="M18 12H6V30H18V12ZM24 36H0V6H24V36Z" fill="#656363"/>
<path d="M48 30H36V18H48V30Z" fill="#CFCECD"/>
<path d="M36 30H48V12H36V30ZM54 36H36V42H30V6H54V36Z" fill="#656363"/>
<path d="M84 24V30H66V24H84Z" fill="#CFCECD"/>
<path d="M84 24H66V30H84V36H60V6H84V24ZM66 18H78V12H66V18Z" fill="#656363"/>
<path d="M108 36H96V18H108V36Z" fill="#CFCECD"/>
<path d="M108 12H96V36H90V6H108V12ZM114 36H108V12H114V36Z" fill="#656363"/>
<path d="M144 30H126V18H144V30Z" fill="#CFCECD"/>
<path d="M144 12H126V30H144V36H120V6H144V12Z" fill="#211E1E"/>
<path d="M168 30H156V18H168V30Z" fill="#CFCECD"/>
<path d="M168 12H156V30H168V12ZM174 36H150V6H174V36Z" fill="#211E1E"/>
<path d="M198 30H186V18H198V30Z" fill="#CFCECD"/>
<path d="M198 12H186V30H198V12ZM204 36H180V6H198V0H204V36Z" fill="#211E1E"/>
<path d="M234 24V30H216V24H234Z" fill="#CFCECD"/>
<path d="M216 12V18H228V12H216ZM234 24H216V30H234V36H210V6H234V24Z" fill="#211E1E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,7 @@
// @refresh reload
import { mount, StartClient } from "@solidjs/start/client"
const root = document.getElementById("app")
if (!root) throw new Error("Root element #app not found")
mount(() => <StartClient />, root)

View file

@ -0,0 +1,25 @@
// @refresh reload
import { createHandler, StartServer } from "@solidjs/start/server"
export default createHandler(
() => (
<StartServer
document={({ assets, children, scripts }) => (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{assets}
</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
),
{
mode: "async",
},
)

1
packages/stats/app/src/global.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="@solidjs/start/env" />

View file

@ -0,0 +1,19 @@
import { AppConfig } from "@opencode-ai/stats-core/config"
import { runtime } from "@opencode-ai/stats-core/runtime"
import { Effect } from "effect"
export async function GET() {
return Response.json(
await runtime.runPromise(
Effect.gen(function* () {
const config = yield* AppConfig
return {
ok: true,
app: "stats",
stage: config.stage,
publicUrl: config.publicUrl,
}
}),
),
)
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,971 @@
import "./index.css"
import { Meta, Title } from "@solidjs/meta"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import {
type CountryEntry,
getStatsHomeData,
type LeaderboardEntry,
type MarketDay,
type StatsHomeData,
type SessionCostEntry,
type TokenCostEntry,
type UsagePoint,
} from "@opencode-ai/stats-core/domain/home"
import { runtime } from "@opencode-ai/stats-core/runtime"
import { createAsync, query } from "@solidjs/router"
import { scaleBand, scaleLinear } from "d3-scale"
import { createMemo, createSignal, For, Show, type JSX } from "solid-js"
import { getRequestEvent } from "solid-js/web"
import logoDark from "../asset/logo-ornate-dark.svg"
import logoLight from "../asset/logo-ornate-light.svg"
const products = ["All Users", "Zen", "Go", "Enterprise"] as const
const tokenProducts = ["Zen", "Go", "Enterprise"] as const
const ranges = ["1D", "1W", "1M", "3M", "YTD", "ALL"] as const
const usageColors = ["#ff5d64", "#ff8a00", "#8bef00", "#12c8b3", "#18c7dc", "#6c7dff", "#9d73f7"]
const marketColors = ["#ed6aff", "#a684ff", "#7c86ff", "#51a2ff", "#00d3f2", "#00d5be", "#00bc7d", "#9ae600", "#ffb900"]
const countryPositions = [
{ x: 112, y: 96 },
{ x: 284, y: 144 },
{ x: 472, y: 92 },
{ x: 642, y: 154 },
{ x: 800, y: 96 },
{ x: 172, y: 234 },
{ x: 362, y: 250 },
{ x: 552, y: 236 },
{ x: 744, y: 252 },
{ x: 48, y: 184 },
{ x: 892, y: 198 },
{ x: 456, y: 176 },
] as const
type UsageProduct = (typeof products)[number]
type TokenProduct = (typeof tokenProducts)[number]
type UsageRange = (typeof ranges)[number]
const getData = query(async () => {
"use server"
return runtime.runPromise(getStatsHomeData())
}, "getStatsHomeData")
export default function StatsHome() {
getRequestEvent()?.response.headers.set(
"Cache-Control",
"public, max-age=60, s-maxage=300, stale-while-revalidate=86400",
)
const data = createAsync(() => getData())
return (
<main data-page="stats">
<Title>OpenCode Stats</Title>
<Meta name="description" content="OpenCode usage, market share, token cost, and session cost stats." />
<div data-component="container">
<Header />
<div data-component="content">
<Show when={data()} fallback={<StatsLoading />}>
{(stats) => (
<>
<Hero updatedAt={stats().updatedAt} />
<UsageSection data={stats().usage} />
<LeaderboardSection data={stats().leaderboard} />
<MarketShareSection data={stats().market} />
<TokenCostSection data={stats().tokenCost} />
<SessionCostSection data={stats().sessionCost} />
<CountrySection data={stats().country} />
<Newsletter />
</>
)}
</Show>
</div>
<Footer />
</div>
<Legal />
</main>
)
}
function Hero(props: { updatedAt: string | null }) {
return (
<section data-section="hero">
<div>
<h1>OpenCode Stats</h1>
<p data-slot="meta">
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 16 16">
<rect x="3" y="3" width="10" height="10" fill="currentColor" />
<rect x="7" y="6.5" width="2" height="4.5" fill="var(--stats-layer-2)" />
<rect x="7" y="5" width="2" height="1" fill="var(--stats-layer-2)" />
</svg>
<span>OpenCode data</span> <b>·</b>{" "}
<em>{props.updatedAt ? `Updated ${formatUpdatedAt(props.updatedAt)}` : "No rows yet"}</em>
</p>
</div>
<p>See how model usage, provider share, cost, and geography move across OpenCode traffic.</p>
</section>
)
}
function StatsLoading() {
return (
<>
<Hero updatedAt={null} />
<ChartSection title="Usage">
<EmptyState title="Loading stats" description="Reading model aggregates from model_stat." />
</ChartSection>
</>
)
}
function ChartSection(props: { title: string; description?: string; controls?: JSX.Element; children: JSX.Element }) {
return (
<section data-section="chart">
<div data-slot="section-header">
<div>
<h2>{props.title}</h2>
{props.description && <p>{props.description}</p>}
</div>
{props.controls}
</div>
{props.children}
</section>
)
}
function EmptyState(props: { title: string; description: string }) {
return (
<div data-component="empty-state">
<strong>{props.title}</strong>
<p>{props.description}</p>
</div>
)
}
function formatUpdatedAt(value: string) {
const date = new Date(value)
if (Number.isNaN(date.getTime())) return "just now"
return new Intl.DateTimeFormat("en", {
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
timeZone: "UTC",
timeZoneName: "short",
}).format(date)
}
function UsageSection(props: { data: StatsHomeData["usage"] }) {
const [product, setProduct] = createSignal<UsageProduct>("All Users")
const [range, setRange] = createSignal<UsageRange>("1W")
const data = createMemo(() => props.data[product()][range()])
return (
<ChartSection title="Usage">
<Show
when={data().some((item) => usageTotal(item) > 0)}
fallback={<EmptyState title="No usage data" description="No model_stat rows matched this product and range." />}
>
<UsageChart data={data()} />
</Show>
<div data-slot="chart-footer">
<StatsFilters product={product()} range={range()} onProductSelect={setProduct} onRangeSelect={setRange} />
</div>
</ChartSection>
)
}
function StatsFilters(props: {
product: UsageProduct
range: UsageRange
onProductSelect: (product: UsageProduct) => void
onRangeSelect: (range: UsageRange) => void
}) {
return (
<>
<FilterPills
items={products}
selected={props.product}
label="Product filter"
variant="product"
onSelect={props.onProductSelect}
/>
<FilterPills
items={ranges}
selected={props.range}
label="Date range"
variant="range"
onSelect={props.onRangeSelect}
/>
</>
)
}
function FilterPills<T extends string>(props: {
items: readonly T[]
selected: T
label: string
variant: "product" | "range"
onSelect: (item: T) => void
}) {
return (
<div data-component="usage-filter" data-variant={props.variant} role="radiogroup" aria-label={props.label}>
<For each={props.items}>
{(item) => (
<button
type="button"
role="radio"
aria-checked={props.selected === item}
data-active={props.selected === item ? "true" : undefined}
onClick={() => props.onSelect(item)}
>
{item}
</button>
)}
</For>
</div>
)
}
function UsageChart(props: { data: UsagePoint[] }) {
const [activeIndex, setActiveIndex] = createSignal<number>()
const [activeSegment, setActiveSegment] = createSignal<number>()
const height = 434
const width = 920
const headerOffset = 46
const segmentGap = 2
const maxTotal = createMemo(() => Math.max(1, Math.max(...props.data.map((item) => usageTotal(item))) * 1.02))
const activePoint = createMemo(() => props.data[activeIndex() ?? -1])
const y = createMemo(() => scaleLinear([0, maxTotal()], [height, 0]))
const x = createMemo(() =>
scaleBand(
props.data.map((_, index) => String(index)),
[0, width],
).paddingInner(0.08),
)
const activeBar = createMemo(() => {
const index = activeIndex()
const point = activePoint()
if (index === undefined) return
if (!point) return
return {
point,
x: x()(String(index)) ?? 0,
width: x().bandwidth(),
}
})
return (
<div data-component="usage-chart">
<svg viewBox={`0 0 ${width} ${height + headerOffset}`} role="img" aria-label="Stacked usage chart">
<defs>
<pattern id="stats-usage-dot-grid" width="6" height="6" patternUnits="userSpaceOnUse">
<rect x="1" y="1" width="2" height="2" fill="var(--stats-dot)" />
</pattern>
</defs>
<For each={props.data}>
{(day, dayIndex) => {
const barX = x()(String(dayIndex())) ?? 0
const barWidth = x().bandwidth()
const stackTop = y()(usageTotal(day))
return (
<g
role="button"
tabIndex={0}
aria-label={`${day.date} ${formatTokens(usageTotal(day))}`}
data-active={activeIndex() === dayIndex() ? "true" : undefined}
onPointerEnter={() => {
setActiveIndex(dayIndex())
setActiveSegment(undefined)
}}
onPointerLeave={(event) => {
if (event.pointerType === "touch") return
setActiveIndex(undefined)
setActiveSegment(undefined)
}}
onClick={() => setActiveIndex(dayIndex())}
onFocus={() => {
setActiveIndex(dayIndex())
setActiveSegment(undefined)
}}
onBlur={() => {
setActiveIndex(undefined)
setActiveSegment(undefined)
}}
onKeyDown={(event) => {
if (event.key !== "Enter" && event.key !== " ") return
event.preventDefault()
setActiveIndex(dayIndex())
}}
>
<rect
x={barX}
y="0"
width={barWidth}
height={height + headerOffset}
fill="transparent"
pointer-events="all"
/>
<text x={barX} y="17" class="chart-total">
{formatTokens(usageTotal(day))}
</text>
<text x={barX} y="34" class="chart-date">
{day.date}
</text>
<rect x={barX} y={headerOffset} width={barWidth} height={stackTop} fill="url(#stats-usage-dot-grid)" />
<For each={day.segments}>
{(segment, index) => {
const previous = day.segments.slice(0, index()).reduce((sum, item) => sum + item.value, 0)
const segmentHeight = y()(previous) - y()(previous + segment.value)
const segmentInset = index() === day.segments.length - 1 ? 0 : segmentGap
return (
<rect
x={barX}
y={headerOffset + y()(previous + segment.value) + segmentInset}
width={barWidth}
height={Math.max(segmentHeight - segmentInset, 0)}
data-segment-active={
activeIndex() === dayIndex() && activeSegment() === index() ? "true" : undefined
}
opacity={getUsageSegmentOpacity(activeIndex() === dayIndex(), activeSegment(), index())}
fill={activeIndex() === dayIndex() ? usageColors[index()] : "var(--stats-bar-idle)"}
onPointerEnter={(event) => {
event.stopPropagation()
setActiveIndex(dayIndex())
setActiveSegment(index())
}}
/>
)
}}
</For>
</g>
)
}}
</For>
</svg>
<Show when={activeBar()}>
{(bar) => (
<div
data-component="chart-tooltip"
data-placement={bar().x > width * 0.62 ? "left" : "right"}
style={getUsageTooltipStyle(bar().x, bar().width, width)}
>
<strong>{bar().point.date}</strong>
<span>{formatTokens(usageTotal(bar().point))} total</span>
<div data-slot="tooltip-divider" />
<For each={bar().point.segments}>
{(segment, index) => (
<p data-active={activeSegment() === index() ? "true" : undefined}>
<span data-slot="tooltip-label">
<i style={{ background: usageColors[index()] }} /> {segment.model}
</span>
<b>{formatTokens(segment.value)}</b>
</p>
)}
</For>
</div>
)}
</Show>
</div>
)
}
function getUsageTooltipStyle(barX: number, barWidth: number, width: number) {
if (barX > width * 0.62) return { left: "auto", right: `${((width - barX + 12) / width) * 100}%` }
return { left: `${((barX + barWidth + 12) / width) * 100}%`, right: "auto" }
}
function getUsageSegmentOpacity(isActiveBar: boolean, activeSegment: number | undefined, index: number) {
if (!isActiveBar) return 1
if (activeSegment === undefined) return 1
return activeSegment === index ? 1 : 0.38
}
function usageTotal(point: UsagePoint) {
return point.segments.reduce((sum, item) => sum + item.value, 0)
}
function formatTokens(value: number) {
if (value >= 1) return `${value.toFixed(value >= 10 ? 0 : 1)}T`
return `${Math.round(value * 1000)}B`
}
function LeaderboardSection(props: { data: StatsHomeData["leaderboard"] }) {
const [product, setProduct] = createSignal<UsageProduct>("All Users")
const [range, setRange] = createSignal<UsageRange>("1W")
const data = createMemo(() => props.data[product()][range()])
return (
<ChartSection
title="Leaderboard"
description="Shown are the sum of prompt and completion tokens per model, including reasoning tokens."
>
<Show
when={data().length > 0}
fallback={
<EmptyState title="No leaderboard data" description="No model_stat rows matched this product and range." />
}
>
<Leaderboard data={data()} />
</Show>
<div data-slot="chart-footer">
<StatsFilters product={product()} range={range()} onProductSelect={setProduct} onRangeSelect={setRange} />
</div>
</ChartSection>
)
}
function Leaderboard(props: { data: LeaderboardEntry[] }) {
return (
<div data-component="leaderboard" aria-label="Model token leaderboard">
<div data-slot="leaderboard-grid">
<div data-slot="leaderboard-featured">
<For each={props.data.slice(0, 3)}>{(entry) => <LeaderboardCard entry={entry} size="featured" />}</For>
</div>
<div data-slot="leaderboard-compact">
<For each={props.data.slice(3)}>{(entry) => <LeaderboardCard entry={entry} size="compact" />}</For>
</div>
</div>
</div>
)
}
function LeaderboardCard(props: { entry: LeaderboardEntry; size: "featured" | "compact" }) {
return (
<article data-component="leader-card" data-size={props.size}>
<span data-slot="rank">{String(props.entry.rank).padStart(2, "0")}</span>
<ProviderIcon data-slot="leader-watermark" aria-hidden="true" id={getProviderIconId(props.entry.author)} />
<div data-slot="leader-body">
<ProviderIcon data-slot="leader-avatar" aria-hidden="true" id={getProviderIconId(props.entry.author)} />
<div data-slot="leader-copy">
<div>
<strong>{props.entry.model}</strong>
<span>{formatBillions(props.entry.tokens)}</span>
</div>
<div>
<span>{props.entry.author}</span>
<span data-slot="delta" data-negative={props.entry.change < 0 ? "true" : undefined}>
{formatChange(props.entry.change)}
</span>
</div>
</div>
</div>
</article>
)
}
function getProviderIconId(author: string) {
if (author === "MiniMax") return "minimax"
if (author === "Moonshot") return "moonshotai"
if (author === "Zhipu") return "zhipuai"
return author.toLowerCase()
}
function formatBillions(value: number) {
if (value >= 1000) return `${(value / 1000).toFixed(value >= 10000 ? 0 : 1)}T`
return `${value}B`
}
function formatChange(value: number) {
if (value > 0) return `+${value}%`
return `${value}%`
}
function MarketShareSection(props: { data: StatsHomeData["market"] }) {
const [range, setRange] = createSignal<UsageRange>("1W")
const [activeIndex, setActiveIndex] = createSignal(2)
const data = createMemo(() => props.data[range()])
const selectedIndex = createMemo(() => Math.min(activeIndex(), Math.max(data().length - 1, 0)))
const activeDay = createMemo(() => data()[selectedIndex()])
return (
<ChartSection title="Market Share" description="Compare token share by model author.">
<Show
when={activeDay()}
fallback={<EmptyState title="No market data" description="No model_stat rows matched this range." />}
>
{(day) => (
<>
<MarketShare data={data()} activeIndex={selectedIndex()} onActiveIndexChange={setActiveIndex} />
<MarketShareList data={day().authors} />
</>
)}
</Show>
<div data-slot="market-footer">
<p>
<span>[*]</span>
<strong>{activeDay()?.date ?? "No data"}</strong>
</p>
<FilterPills items={ranges} selected={range()} label="Date range" variant="range" onSelect={setRange} />
</div>
</ChartSection>
)
}
function MarketShare(props: { data: MarketDay[]; activeIndex: number; onActiveIndexChange: (index: number) => void }) {
return (
<div data-component="market-share" role="img" aria-label="Market share by model author">
<div data-slot="market-labels">
<For each={props.data}>
{(day, index) => (
<button
type="button"
data-active={props.activeIndex === index() ? "true" : undefined}
onClick={() => props.onActiveIndexChange(index())}
>
<span>{formatTrillions(day.total)}</span>
<span>{day.date}</span>
</button>
)}
</For>
</div>
<div data-slot="market-bars">
<For each={props.data}>
{(day, index) => (
<button
type="button"
aria-label={`${day.date} ${formatTrillions(day.total)}`}
data-active={props.activeIndex === index() ? "true" : undefined}
onClick={() => props.onActiveIndexChange(index())}
>
<For each={day.authors}>
{(author, authorIndex) => (
<span
style={{
"background-color": props.activeIndex === index() ? marketColors[authorIndex()] : undefined,
"flex-grow": author.share,
}}
/>
)}
</For>
</button>
)}
</For>
</div>
</div>
)
}
function MarketShareList(props: { data: MarketDay["authors"] }) {
return (
<ol data-component="market-share-list">
<For each={props.data}>
{(item, index) => (
<li>
<span>{String(index() + 1).padStart(2, "0")}</span>
<i style={{ background: marketColors[index()] }} />
<strong>{item.author}</strong>
<em>{formatTrillions(item.tokens)}</em>
<b>{item.share.toFixed(1)}%</b>
</li>
)}
</For>
</ol>
)
}
function formatTrillions(value: number) {
return `${value.toFixed(value >= 10 ? 0 : 1)}T`
}
function TokenCostSection(props: { data: StatsHomeData["tokenCost"] }) {
const [product, setProduct] = createSignal<TokenProduct>("Zen")
const [activeIndex, setActiveIndex] = createSignal(2)
const data = createMemo(() => props.data[product()])
const selectedIndex = createMemo(() => Math.min(activeIndex(), Math.max(data().length - 1, 0)))
return (
<ChartSection title="Token Cost" description="Price per 1M tokens.">
<Show
when={data().length > 0}
fallback={
<EmptyState title="No token cost data" description="No cost-bearing model_stat rows matched this product." />
}
>
<TokenCostChart data={data()} activeIndex={selectedIndex()} onActiveIndexChange={setActiveIndex} />
</Show>
<div data-slot="token-footer">
<FilterPills
items={tokenProducts}
selected={product()}
label="Product filter"
variant="product"
onSelect={setProduct}
/>
</div>
</ChartSection>
)
}
function TokenCostChart(props: {
data: TokenCostEntry[]
activeIndex: number
onActiveIndexChange: (index: number) => void
}) {
const max = createMemo(() => Math.max(1, ...props.data.map((item) => item.total)))
const active = createMemo(() => props.data[props.activeIndex] ?? props.data[0])
return (
<div data-component="token-cost">
<For each={props.data}>
{(item, index) => (
<button
type="button"
data-component="token-row"
data-active={props.activeIndex === index() ? "true" : undefined}
onClick={() => props.onActiveIndexChange(index())}
onPointerEnter={() => props.onActiveIndexChange(index())}
>
<strong>{formatDollars(item.total)}</strong>
<span>{item.model}</span>
<MetricBar value={item.total} max={max()} active={props.activeIndex === index()} />
</button>
)}
</For>
<Show when={active()}>
{(item) => (
<div data-component="token-tooltip" style={{ top: `${props.activeIndex * 28 + 2}px` }}>
<p>
<span>Input</span>
<strong>{formatDollars(item().input)}</strong>
</p>
<p>
<span>Output</span>
<strong>{formatDollars(item().output)}</strong>
</p>
<p>
<span>Cached</span>
<strong>{formatDollars(item().cached)}</strong>
</p>
</div>
)}
</Show>
</div>
)
}
function formatDollars(value: number) {
return `$${value.toFixed(2)}`
}
function MetricBar(props: { value: number; max: number; active: boolean }) {
return (
<i data-component="metric-bar" data-active={props.active ? "true" : undefined}>
<b style={{ "flex-grow": Math.max(props.value / Math.max(props.max, 1), 0.05) }} />
<em />
</i>
)
}
function SessionCostSection(props: { data: StatsHomeData["sessionCost"] }) {
const [product, setProduct] = createSignal<TokenProduct>("Zen")
const [activeIndex, setActiveIndex] = createSignal(2)
const data = createMemo(() => props.data[product()])
const selectedIndex = createMemo(() => Math.min(activeIndex(), Math.max(data().length - 1, 0)))
return (
<ChartSection title="Session Cost" description="Average cost per session.">
<Show
when={data().length > 0}
fallback={
<EmptyState
title="No session cost data"
description="No session-bearing model_stat rows matched this product."
/>
}
>
<SessionCostChart data={data()} activeIndex={selectedIndex()} onActiveIndexChange={setActiveIndex} />
</Show>
<div data-slot="token-footer">
<FilterPills
items={tokenProducts}
selected={product()}
label="Product filter"
variant="product"
onSelect={setProduct}
/>
</div>
</ChartSection>
)
}
function SessionCostChart(props: {
data: SessionCostEntry[]
activeIndex: number
onActiveIndexChange: (index: number) => void
}) {
const maxCost = createMemo(() => Math.max(1, ...props.data.map((item) => item.cost)))
const maxTokens = createMemo(() => Math.max(1, ...props.data.map((item) => item.tokens)))
const active = createMemo(() => props.data[props.activeIndex] ?? props.data[0])
return (
<div data-component="session-cost">
<div data-slot="session-heading">
<span />
<p>COST / SESSION</p>
<p>TOKENS / SESSIONS</p>
</div>
<For each={props.data}>
{(item, index) => (
<button
type="button"
data-component="token-row"
data-variant="session"
data-active={props.activeIndex === index() ? "true" : undefined}
onClick={() => props.onActiveIndexChange(index())}
onPointerEnter={() => props.onActiveIndexChange(index())}
>
<strong>{formatSessionCost(item.cost)}</strong>
<span>{item.model}</span>
<MetricBar value={item.cost} max={maxCost()} active={props.activeIndex === index()} />
<MetricBar value={item.tokens} max={maxTokens()} active={props.activeIndex === index()} />
</button>
)}
</For>
<Show when={active()}>
{(item) => (
<div
data-component="token-tooltip"
data-variant="session"
style={{ top: `${props.activeIndex * 28 + 21}px` }}
>
<p>
<span>Cost/Session</span>
<strong>{formatSessionCost(item().cost)}</strong>
</p>
<p>
<span>Tokens/Session</span>
<strong>{formatTokenCount(item().tokens)}</strong>
</p>
</div>
)}
</Show>
</div>
)
}
function formatTokenCount(value: number) {
if (value >= 1_000_000) return `${Number((value / 1_000_000).toFixed(1))}M`
return `${Math.round(value / 1_000)}K`
}
function formatSessionCost(value: number) {
return `$${value.toFixed(4)}`
}
function CountrySection(props: { data: StatsHomeData["country"] }) {
const [range, setRange] = createSignal<UsageRange>("1W")
const data = createMemo(() => props.data[range()])
return (
<ChartSection title="Token by Country" description="Country-level token totals from geo_stat.">
<Show
when={data().length > 0}
fallback={<EmptyState title="No country data" description="No geo_stat rows matched this range." />}
>
<CountryChart data={data()} />
</Show>
<div data-slot="country-footer">
<p>
<span>[*]</span>
<strong>Top countries by tokens</strong>
</p>
<FilterPills items={ranges} selected={range()} label="Date range" variant="range" onSelect={setRange} />
</div>
</ChartSection>
)
}
function CountryChart(props: { data: CountryEntry[] }) {
const [activeIndex, setActiveIndex] = createSignal(0)
const selectedIndex = createMemo(() => Math.min(activeIndex(), Math.max(props.data.length - 1, 0)))
const active = createMemo(() => props.data[selectedIndex()])
const max = createMemo(() => Math.max(0.0001, ...props.data.map((item) => item.tokens)))
return (
<div data-component="country-map">
<svg viewBox="0 0 920 320" role="img" aria-label="Country token share bubble chart">
<For each={props.data.slice(0, countryPositions.length)}>
{(item, index) => {
const position = countryPositions[index()]
const radius = 18 + Math.sqrt(item.tokens / max()) * 58
return (
<g
role="button"
tabIndex={0}
aria-label={`${formatCountry(item.country)} ${formatTokens(item.tokens)}`}
data-active={selectedIndex() === index() ? "true" : undefined}
onPointerEnter={() => setActiveIndex(index())}
onClick={() => setActiveIndex(index())}
onFocus={() => setActiveIndex(index())}
>
<circle cx={position.x} cy={position.y} r={radius} />
<text x={position.x} y={position.y + 4} text-anchor="middle">
{item.country}
</text>
</g>
)
}}
</For>
</svg>
<Show when={active()}>
{(item) => (
<div data-component="map-tooltip">
<strong>{formatCountry(item().country)}</strong>
<span>{item().continent || "Unknown region"}</span>
<p>
<b>{formatTokens(item().tokens)}</b>
<em>{item().share.toFixed(1)}%</em>
</p>
</div>
)}
</Show>
<CountryList data={props.data.slice(0, 8)} activeIndex={selectedIndex()} onActiveIndexChange={setActiveIndex} />
</div>
)
}
function CountryList(props: {
data: CountryEntry[]
activeIndex: number
onActiveIndexChange: (index: number) => void
}) {
return (
<ol data-component="country-list">
<For each={props.data}>
{(item, index) => (
<li>
<button
type="button"
data-active={props.activeIndex === index() ? "true" : undefined}
onClick={() => props.onActiveIndexChange(index())}
onPointerEnter={() => props.onActiveIndexChange(index())}
>
<span>{String(item.rank).padStart(2, "0")}</span>
<strong>{formatCountry(item.country)}</strong>
<em>{formatTokens(item.tokens)}</em>
<b>{item.share.toFixed(1)}%</b>
</button>
</li>
)}
</For>
</ol>
)
}
function formatCountry(country: string) {
const known: Record<string, string> = {
AU: "Australia",
BR: "Brazil",
CA: "Canada",
CN: "China",
DE: "Germany",
FR: "France",
GB: "United Kingdom",
IN: "India",
JP: "Japan",
KR: "South Korea",
NL: "Netherlands",
SG: "Singapore",
US: "United States",
ZZ: "Unknown",
}
return known[country] ?? country
}
function Newsletter() {
return (
<section data-section="newsletter">
<div>
<h2>Be the first to know when we release new products</h2>
<p>Join the waitlist for early access.</p>
</div>
<form>
<input type="email" placeholder="Email address" />
<button>Subscribe</button>
</form>
</section>
)
}
function Header() {
return (
<section data-component="top">
<a data-slot="brand" href="https://opencode.ai/" aria-label="OpenCode home">
<img data-slot="logo light" src={logoLight} alt="OpenCode" width="234" height="42" />
<img data-slot="logo dark" src={logoDark} alt="OpenCode" width="234" height="42" />
</a>
<nav data-component="nav-desktop" aria-label="Main navigation">
<ul>
<li>
<a href="https://github.com/sst/opencode" target="_blank" rel="noreferrer">
GitHub
</a>
</li>
<li>
<a href="https://opencode.ai/docs">Docs</a>
</li>
<li>
<a href="https://opencode.ai/zen">Zen</a>
</li>
<li>
<a href="https://opencode.ai/go">Go</a>
</li>
<li>
<a href="https://opencode.ai/enterprise">Enterprise</a>
</li>
<li>
<a href="https://opencode.ai/download" data-slot="cta-button">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true">
<path
d="M12.1875 9.75L9.00001 12.9375L5.8125 9.75M9.00001 2.0625L9 12.375M14.4375 15.9375H3.5625"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="square"
/>
</svg>
Download
</a>
</li>
</ul>
</nav>
</section>
)
}
function Footer() {
return (
<footer data-component="footer">
<div data-slot="cell">
<a href="https://github.com/sst/opencode" target="_blank" rel="noreferrer">
GitHub
</a>
</div>
<div data-slot="cell">
<a href="https://opencode.ai/docs">Docs</a>
</div>
<div data-slot="cell">
<a href="https://opencode.ai/changelog">Changelog</a>
</div>
<div data-slot="cell">
<a href="https://x.com/opencode_ai">X</a>
</div>
</footer>
)
}
function Legal() {
return (
<div data-component="legal">
<span>
©{new Date().getFullYear()} <a href="https://anoma.ly">Anomaly</a>
</span>
<span>
<a href="https://opencode.ai/brand">Brand</a>
</span>
<span>
<a href="https://opencode.ai/legal/privacy-policy">Privacy</a>
</span>
<span>
<a href="https://opencode.ai/legal/terms-of-service">Terms</a>
</span>
</div>
)
}

10
packages/stats/app/sst-env.d.ts vendored Normal file
View file

@ -0,0 +1,10 @@
/* This file is auto-generated by SST. Do not edit. */
/* tslint:disable */
/* eslint-disable */
/* deno-fmt-ignore-file */
/* biome-ignore-all lint: auto-generated */
/// <reference path="../../../sst-env.d.ts" />
import "sst"
export {}

View file

@ -0,0 +1,21 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"allowJs": true,
"strict": true,
"noEmit": true,
"types": ["vite/client", "bun"],
"isolatedModules": true,
"paths": {
"~/*": ["./src/*"]
}
}
}

View file

@ -0,0 +1,22 @@
import { solidStart } from "@solidjs/start/config"
import { nitro } from "nitro/vite"
import { defineConfig, type PluginOption } from "vite"
export default defineConfig({
plugins: [
solidStart() as PluginOption,
nitro({
compatibilityDate: "2024-09-19",
preset: "cloudflare-module",
cloudflare: {
nodeCompat: true,
},
}),
],
server: {
allowedHosts: true,
},
build: {
minify: false,
},
})

View file

@ -0,0 +1,21 @@
import { Resource } from "sst/resource"
import { defineConfig } from "drizzle-kit"
export default defineConfig({
dialect: "mysql",
schema: ["./src/database/schema.ts"],
// schema: ["./src/**/*.sql.ts"],
out: "./migrations/",
strict: true,
verbose: true,
dbCredentials: {
database: Resource.StatsDatabase.database,
host: Resource.StatsDatabase.host,
user: Resource.StatsDatabase.username,
password: Resource.StatsDatabase.password,
port: Resource.StatsDatabase.port,
ssl: {
rejectUnauthorized: false,
},
},
})

View file

@ -0,0 +1,42 @@
CREATE TABLE `stat` (
`id` bigint AUTO_INCREMENT PRIMARY KEY,
`grain` varchar(16) NOT NULL,
`period_start` datetime NOT NULL,
`period_end` datetime NOT NULL,
`dataset` varchar(64) NOT NULL DEFAULT 'all',
`tier` varchar(64) NOT NULL DEFAULT 'all',
`client` varchar(64) NOT NULL DEFAULT 'all',
`source` varchar(64) NOT NULL DEFAULT 'all',
`provider` varchar(128) NOT NULL,
`model` varchar(256) NOT NULL,
`provider_model` varchar(256) NOT NULL DEFAULT '',
`sessions` bigint NOT NULL DEFAULT 0,
`requests` bigint NOT NULL DEFAULT 0,
`input_tokens` bigint NOT NULL DEFAULT 0,
`output_tokens` bigint NOT NULL DEFAULT 0,
`reasoning_tokens` bigint NOT NULL DEFAULT 0,
`cache_read_tokens` bigint NOT NULL DEFAULT 0,
`total_tokens` bigint NOT NULL DEFAULT 0,
`input_cost_microcents` bigint NOT NULL DEFAULT 0,
`output_cost_microcents` bigint NOT NULL DEFAULT 0,
`total_cost_microcents` bigint NOT NULL DEFAULT 0,
`avg_duration_ms` decimal(12,2),
`p50_duration_ms` int,
`p95_duration_ms` int,
`avg_ttfb_ms` decimal(12,2),
`p50_ttfb_ms` int,
`p95_ttfb_ms` int,
`avg_output_tps` decimal(12,4),
`success_count` bigint NOT NULL DEFAULT 0,
`error_count` bigint NOT NULL DEFAULT 0,
`sample_count` bigint NOT NULL DEFAULT 0,
`rank_by_tokens` int,
`rank_by_requests` int,
`rank_by_cost` int,
`created_at` datetime NOT NULL DEFAULT (now()),
`updated_at` datetime NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT `uniq_model_period` UNIQUE INDEX(`grain`,`period_start`,`dataset`,`tier`,`client`,`source`,`provider`,`model`)
);
--> statement-breakpoint
CREATE INDEX `idx_leaderboard_tokens` ON `stat` (`grain`,`period_start`,`dataset`,`tier`,`total_tokens`);--> statement-breakpoint
CREATE INDEX `idx_model` ON `stat` (`model`,`grain`,`period_start`);

View file

@ -0,0 +1,627 @@
{
"version": "6",
"dialect": "mysql",
"id": "72655266-65da-408e-bfd8-9f3a4ad817a5",
"prevIds": [
"00000000-0000-0000-0000-000000000000"
],
"ddl": [
{
"name": "stat",
"entityType": "tables"
},
{
"type": "bigint",
"notNull": true,
"autoIncrement": true,
"default": null,
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "id",
"entityType": "columns",
"table": "stat"
},
{
"type": "varchar(16)",
"notNull": true,
"autoIncrement": false,
"default": null,
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "grain",
"entityType": "columns",
"table": "stat"
},
{
"type": "datetime",
"notNull": true,
"autoIncrement": false,
"default": null,
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "period_start",
"entityType": "columns",
"table": "stat"
},
{
"type": "datetime",
"notNull": true,
"autoIncrement": false,
"default": null,
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "period_end",
"entityType": "columns",
"table": "stat"
},
{
"type": "varchar(64)",
"notNull": true,
"autoIncrement": false,
"default": "'all'",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "dataset",
"entityType": "columns",
"table": "stat"
},
{
"type": "varchar(64)",
"notNull": true,
"autoIncrement": false,
"default": "'all'",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "tier",
"entityType": "columns",
"table": "stat"
},
{
"type": "varchar(64)",
"notNull": true,
"autoIncrement": false,
"default": "'all'",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "client",
"entityType": "columns",
"table": "stat"
},
{
"type": "varchar(64)",
"notNull": true,
"autoIncrement": false,
"default": "'all'",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "source",
"entityType": "columns",
"table": "stat"
},
{
"type": "varchar(128)",
"notNull": true,
"autoIncrement": false,
"default": null,
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "provider",
"entityType": "columns",
"table": "stat"
},
{
"type": "varchar(256)",
"notNull": true,
"autoIncrement": false,
"default": null,
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "model",
"entityType": "columns",
"table": "stat"
},
{
"type": "varchar(256)",
"notNull": true,
"autoIncrement": false,
"default": "''",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "provider_model",
"entityType": "columns",
"table": "stat"
},
{
"type": "bigint",
"notNull": true,
"autoIncrement": false,
"default": "0",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "sessions",
"entityType": "columns",
"table": "stat"
},
{
"type": "bigint",
"notNull": true,
"autoIncrement": false,
"default": "0",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "requests",
"entityType": "columns",
"table": "stat"
},
{
"type": "bigint",
"notNull": true,
"autoIncrement": false,
"default": "0",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "input_tokens",
"entityType": "columns",
"table": "stat"
},
{
"type": "bigint",
"notNull": true,
"autoIncrement": false,
"default": "0",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "output_tokens",
"entityType": "columns",
"table": "stat"
},
{
"type": "bigint",
"notNull": true,
"autoIncrement": false,
"default": "0",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "reasoning_tokens",
"entityType": "columns",
"table": "stat"
},
{
"type": "bigint",
"notNull": true,
"autoIncrement": false,
"default": "0",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "cache_read_tokens",
"entityType": "columns",
"table": "stat"
},
{
"type": "bigint",
"notNull": true,
"autoIncrement": false,
"default": "0",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "total_tokens",
"entityType": "columns",
"table": "stat"
},
{
"type": "bigint",
"notNull": true,
"autoIncrement": false,
"default": "0",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "input_cost_microcents",
"entityType": "columns",
"table": "stat"
},
{
"type": "bigint",
"notNull": true,
"autoIncrement": false,
"default": "0",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "output_cost_microcents",
"entityType": "columns",
"table": "stat"
},
{
"type": "bigint",
"notNull": true,
"autoIncrement": false,
"default": "0",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "total_cost_microcents",
"entityType": "columns",
"table": "stat"
},
{
"type": "decimal(12,2)",
"notNull": false,
"autoIncrement": false,
"default": null,
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "avg_duration_ms",
"entityType": "columns",
"table": "stat"
},
{
"type": "int",
"notNull": false,
"autoIncrement": false,
"default": null,
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "p50_duration_ms",
"entityType": "columns",
"table": "stat"
},
{
"type": "int",
"notNull": false,
"autoIncrement": false,
"default": null,
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "p95_duration_ms",
"entityType": "columns",
"table": "stat"
},
{
"type": "decimal(12,2)",
"notNull": false,
"autoIncrement": false,
"default": null,
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "avg_ttfb_ms",
"entityType": "columns",
"table": "stat"
},
{
"type": "int",
"notNull": false,
"autoIncrement": false,
"default": null,
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "p50_ttfb_ms",
"entityType": "columns",
"table": "stat"
},
{
"type": "int",
"notNull": false,
"autoIncrement": false,
"default": null,
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "p95_ttfb_ms",
"entityType": "columns",
"table": "stat"
},
{
"type": "decimal(12,4)",
"notNull": false,
"autoIncrement": false,
"default": null,
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "avg_output_tps",
"entityType": "columns",
"table": "stat"
},
{
"type": "bigint",
"notNull": true,
"autoIncrement": false,
"default": "0",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "success_count",
"entityType": "columns",
"table": "stat"
},
{
"type": "bigint",
"notNull": true,
"autoIncrement": false,
"default": "0",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "error_count",
"entityType": "columns",
"table": "stat"
},
{
"type": "bigint",
"notNull": true,
"autoIncrement": false,
"default": "0",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "sample_count",
"entityType": "columns",
"table": "stat"
},
{
"type": "int",
"notNull": false,
"autoIncrement": false,
"default": null,
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "rank_by_tokens",
"entityType": "columns",
"table": "stat"
},
{
"type": "int",
"notNull": false,
"autoIncrement": false,
"default": null,
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "rank_by_requests",
"entityType": "columns",
"table": "stat"
},
{
"type": "int",
"notNull": false,
"autoIncrement": false,
"default": null,
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "rank_by_cost",
"entityType": "columns",
"table": "stat"
},
{
"type": "datetime",
"notNull": true,
"autoIncrement": false,
"default": "(now())",
"onUpdateNow": false,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "created_at",
"entityType": "columns",
"table": "stat"
},
{
"type": "datetime",
"notNull": true,
"autoIncrement": false,
"default": "(now())",
"onUpdateNow": true,
"onUpdateNowFsp": null,
"charSet": null,
"collation": null,
"generated": null,
"name": "updated_at",
"entityType": "columns",
"table": "stat"
},
{
"columns": [
"id"
],
"name": "PRIMARY",
"table": "stat",
"entityType": "pks"
},
{
"columns": [
{
"value": "grain",
"isExpression": false
},
{
"value": "period_start",
"isExpression": false
},
{
"value": "dataset",
"isExpression": false
},
{
"value": "tier",
"isExpression": false
},
{
"value": "client",
"isExpression": false
},
{
"value": "source",
"isExpression": false
},
{
"value": "provider",
"isExpression": false
},
{
"value": "model",
"isExpression": false
}
],
"isUnique": true,
"using": null,
"algorithm": null,
"lock": null,
"nameExplicit": true,
"name": "uniq_model_period",
"entityType": "indexes",
"table": "stat"
},
{
"columns": [
{
"value": "grain",
"isExpression": false
},
{
"value": "period_start",
"isExpression": false
},
{
"value": "dataset",
"isExpression": false
},
{
"value": "tier",
"isExpression": false
},
{
"value": "total_tokens",
"isExpression": false
}
],
"isUnique": false,
"using": null,
"algorithm": null,
"lock": null,
"nameExplicit": true,
"name": "idx_leaderboard_tokens",
"entityType": "indexes",
"table": "stat"
},
{
"columns": [
{
"value": "model",
"isExpression": false
},
{
"value": "grain",
"isExpression": false
},
{
"value": "period_start",
"isExpression": false
}
],
"isUnique": false,
"using": null,
"algorithm": null,
"lock": null,
"nameExplicit": true,
"name": "idx_model",
"entityType": "indexes",
"table": "stat"
}
],
"renames": []
}

View file

@ -0,0 +1,94 @@
CREATE TABLE `geo_stat` (
`id` bigint AUTO_INCREMENT PRIMARY KEY,
`grain` varchar(16) NOT NULL,
`period_start` datetime NOT NULL,
`period_end` datetime NOT NULL,
`dataset` varchar(64) NOT NULL DEFAULT 'all',
`tier` varchar(64) NOT NULL DEFAULT 'all',
`client` varchar(64) NOT NULL DEFAULT 'all',
`source` varchar(64) NOT NULL DEFAULT 'all',
`country` char(2) NOT NULL,
`continent` varchar(8) NOT NULL DEFAULT '',
`sessions` bigint NOT NULL DEFAULT 0,
`requests` bigint NOT NULL DEFAULT 0,
`input_tokens` bigint NOT NULL DEFAULT 0,
`output_tokens` bigint NOT NULL DEFAULT 0,
`reasoning_tokens` bigint NOT NULL DEFAULT 0,
`cache_read_tokens` bigint NOT NULL DEFAULT 0,
`total_tokens` bigint NOT NULL DEFAULT 0,
`input_cost_microcents` bigint NOT NULL DEFAULT 0,
`output_cost_microcents` bigint NOT NULL DEFAULT 0,
`total_cost_microcents` bigint NOT NULL DEFAULT 0,
`avg_duration_ms` decimal(12,2),
`p50_duration_ms` int,
`p95_duration_ms` int,
`avg_ttfb_ms` decimal(12,2),
`p50_ttfb_ms` int,
`p95_ttfb_ms` int,
`avg_output_tps` decimal(12,4),
`success_count` bigint NOT NULL DEFAULT 0,
`error_count` bigint NOT NULL DEFAULT 0,
`sample_count` bigint NOT NULL DEFAULT 0,
`market_share_tokens` decimal(10,6),
`market_share_requests` decimal(10,6),
`market_share_sessions` decimal(10,6),
`rank_by_tokens` int,
`rank_by_requests` int,
`rank_by_sessions` int,
`rank_by_cost` int,
`created_at` datetime NOT NULL DEFAULT (now()),
`updated_at` datetime NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT `uniq_country_period` UNIQUE INDEX(`grain`,`period_start`,`dataset`,`tier`,`client`,`source`,`country`)
);
--> statement-breakpoint
CREATE TABLE `provider_stat` (
`id` bigint AUTO_INCREMENT PRIMARY KEY,
`grain` varchar(16) NOT NULL,
`period_start` datetime NOT NULL,
`period_end` datetime NOT NULL,
`dataset` varchar(64) NOT NULL DEFAULT 'all',
`tier` varchar(64) NOT NULL DEFAULT 'all',
`client` varchar(64) NOT NULL DEFAULT 'all',
`source` varchar(64) NOT NULL DEFAULT 'all',
`provider` varchar(128) NOT NULL,
`sessions` bigint NOT NULL DEFAULT 0,
`requests` bigint NOT NULL DEFAULT 0,
`input_tokens` bigint NOT NULL DEFAULT 0,
`output_tokens` bigint NOT NULL DEFAULT 0,
`reasoning_tokens` bigint NOT NULL DEFAULT 0,
`cache_read_tokens` bigint NOT NULL DEFAULT 0,
`total_tokens` bigint NOT NULL DEFAULT 0,
`input_cost_microcents` bigint NOT NULL DEFAULT 0,
`output_cost_microcents` bigint NOT NULL DEFAULT 0,
`total_cost_microcents` bigint NOT NULL DEFAULT 0,
`avg_duration_ms` decimal(12,2),
`p50_duration_ms` int,
`p95_duration_ms` int,
`avg_ttfb_ms` decimal(12,2),
`p50_ttfb_ms` int,
`p95_ttfb_ms` int,
`avg_output_tps` decimal(12,4),
`success_count` bigint NOT NULL DEFAULT 0,
`error_count` bigint NOT NULL DEFAULT 0,
`sample_count` bigint NOT NULL DEFAULT 0,
`market_share_tokens` decimal(10,6),
`market_share_requests` decimal(10,6),
`market_share_sessions` decimal(10,6),
`rank_by_tokens` int,
`rank_by_requests` int,
`rank_by_sessions` int,
`rank_by_cost` int,
`created_at` datetime NOT NULL DEFAULT (now()),
`updated_at` datetime NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT `uniq_provider_period` UNIQUE INDEX(`grain`,`period_start`,`dataset`,`tier`,`client`,`source`,`provider`)
);
--> statement-breakpoint
RENAME TABLE `stat` TO `model_stat`;--> statement-breakpoint
CREATE INDEX `idx_country_map_tokens` ON `geo_stat` (`grain`,`period_start`,`dataset`,`tier`,`total_tokens`);--> statement-breakpoint
CREATE INDEX `idx_country_rank` ON `geo_stat` (`grain`,`period_start`,`dataset`,`tier`,`rank_by_tokens`);--> statement-breakpoint
CREATE INDEX `idx_country` ON `geo_stat` (`country`,`grain`,`period_start`);--> statement-breakpoint
CREATE INDEX `idx_continent` ON `geo_stat` (`continent`,`grain`,`period_start`);--> statement-breakpoint
CREATE INDEX `idx_provider_leaderboard_tokens` ON `provider_stat` (`grain`,`period_start`,`dataset`,`tier`,`total_tokens`);--> statement-breakpoint
CREATE INDEX `idx_provider_market_share` ON `provider_stat` (`grain`,`period_start`,`dataset`,`tier`,`market_share_tokens`);--> statement-breakpoint
CREATE INDEX `idx_provider_rank` ON `provider_stat` (`grain`,`period_start`,`dataset`,`tier`,`rank_by_tokens`);--> statement-breakpoint
CREATE INDEX `idx_provider` ON `provider_stat` (`provider`,`grain`,`period_start`);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,43 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/stats-core",
"version": "1.14.50",
"private": true,
"type": "module",
"license": "MIT",
"exports": {
".": "./src/index.ts",
"./athena": "./src/athena.ts",
"./config": "./src/config.ts",
"./database": "./src/database.ts",
"./database/*": "./src/database/*.ts",
"./domain/*": "./src/domain/*.ts",
"./runtime": "./src/runtime.ts",
"./stat-sync": "./src/stat-sync.ts"
},
"scripts": {
"db:generate": "drizzle-kit generate --config=drizzle.config.ts",
"db:migrate": "bun src/migrate.ts",
"db:push": "drizzle-kit push --config=drizzle.config.ts",
"db:studio": "drizzle-kit studio --config=drizzle.config.ts",
"typecheck": "tsgo --noEmit"
},
"dependencies": {
"@aws-sdk/client-athena": "3.933.0",
"@planetscale/database": "1.19.0",
"drizzle-orm": "catalog:",
"effect": "catalog:",
"sst": "catalog:"
},
"devDependencies": {
"@tsconfig/node22": "catalog:",
"@types/bun": "catalog:",
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"drizzle-kit": "catalog:",
"typescript": "catalog:"
},
"engines": {
"node": ">=22"
}
}

View file

@ -0,0 +1,139 @@
import {
AthenaClient as AwsAthenaClient,
GetQueryExecutionCommand,
GetQueryResultsCommand,
StartQueryExecutionCommand,
type Row,
} from "@aws-sdk/client-athena"
import { Effect, Layer, Schema } from "effect"
import * as Context from "effect/Context"
import { Resource } from "sst/resource"
const ATHENA_MAX_POLL_ATTEMPTS = 60
const ATHENA_PAGE_SIZE = 1000
export type AthenaData = Record<string, string>
export class AthenaQueryError extends Schema.TaggedErrorClass<AthenaQueryError>()("AthenaQueryError", {
message: Schema.String,
queryExecutionId: Schema.optional(Schema.String),
cause: Schema.optional(Schema.Defect),
}) {}
export class AthenaQueryTimeoutError extends Schema.TaggedErrorClass<AthenaQueryTimeoutError>()(
"AthenaQueryTimeoutError",
{
message: Schema.String,
queryExecutionId: Schema.String,
},
) {}
export declare namespace Athena {
export interface Service {
readonly query: (query: string) => Effect.Effect<AthenaData[], AthenaQueryError | AthenaQueryTimeoutError>
}
}
export class Athena extends Context.Service<Athena, Athena.Service>()("@opencode/stats/Athena") {
static readonly layer: Layer.Layer<Athena> = Layer.effect(
Athena,
Effect.sync(() => {
const client = new AwsAthenaClient({ region: Resource.InferenceEvent.region })
const query = Effect.fn("Athena.query")(function* (query: string) {
const started = yield* Effect.tryPromise({
try: () =>
client.send(
new StartQueryExecutionCommand({
QueryString: query,
WorkGroup: Resource.InferenceEvent.workgroup,
QueryExecutionContext: {
Catalog: Resource.InferenceEvent.catalog,
Database: Resource.InferenceEvent.database,
},
}),
),
catch: (cause) => new AthenaQueryError({ message: "Failed to start Athena stats query", cause }),
})
const queryExecutionId = started.QueryExecutionId
if (!queryExecutionId)
return yield* new AthenaQueryError({ message: "Athena did not return a query execution id" })
yield* poll(client, queryExecutionId)
return yield* results(client, queryExecutionId)
})
return Athena.of({ query })
}),
)
}
const poll: (
client: AwsAthenaClient,
queryExecutionId: string,
attempt?: number,
) => Effect.Effect<void, AthenaQueryError | AthenaQueryTimeoutError> = Effect.fn("Athena.poll")(function* (
client: AwsAthenaClient,
queryExecutionId: string,
attempt = 0,
) {
if (attempt > 0) yield* Effect.sleep("2 seconds")
const result = yield* Effect.tryPromise({
try: () => client.send(new GetQueryExecutionCommand({ QueryExecutionId: queryExecutionId })),
catch: (cause) => new AthenaQueryError({ message: "Failed to poll Athena stats query", queryExecutionId, cause }),
})
const status = result.QueryExecution?.Status
if (status?.State === "SUCCEEDED") return
if (status?.State === "FAILED" || status?.State === "CANCELLED")
return yield* new AthenaQueryError({
message: `Athena stats query ${status.State.toLowerCase()}: ${status.StateChangeReason ?? "unknown reason"}`,
queryExecutionId,
})
if (attempt >= ATHENA_MAX_POLL_ATTEMPTS - 1)
return yield* new AthenaQueryTimeoutError({
message: `Athena stats query ${queryExecutionId} did not complete`,
queryExecutionId,
})
return yield* poll(client, queryExecutionId, attempt + 1)
})
const results: (
client: AwsAthenaClient,
queryExecutionId: string,
nextToken?: string,
) => Effect.Effect<AthenaData[], AthenaQueryError> = Effect.fn("Athena.results")(function* (
client: AwsAthenaClient,
queryExecutionId: string,
nextToken?: string,
) {
const result = yield* Effect.tryPromise({
try: () =>
client.send(
new GetQueryResultsCommand({
QueryExecutionId: queryExecutionId,
NextToken: nextToken,
MaxResults: ATHENA_PAGE_SIZE,
}),
),
catch: (cause) => new AthenaQueryError({ message: "Failed to read Athena stats results", queryExecutionId, cause }),
})
const columns = result.ResultSet?.ResultSetMetadata?.ColumnInfo?.map((item) => item.Name ?? "") ?? []
const rows = (result.ResultSet?.Rows ?? []).slice(nextToken ? 0 : 1).map((row) => rowData(columns, row))
if (!result.NextToken) return rows
return [...rows, ...(yield* results(client, queryExecutionId, result.NextToken))]
})
function rowData(columns: string[], row: Row): AthenaData {
return Object.fromEntries(
columns.flatMap((column, index) => {
const value = row.Data?.[index]?.VarCharValue
if (!column || value === undefined) return []
return [[column, value]]
}),
)
}

View file

@ -0,0 +1,23 @@
import { Config, ConfigProvider, Effect, Layer, Schema } from "effect"
import * as Context from "effect/Context"
import { Resource } from "sst/resource"
export class AppConfigValue extends Schema.Class<AppConfigValue>("AppConfigValue")({
stage: Schema.NonEmptyString,
publicUrl: Schema.NonEmptyString,
}) {}
const decodeAppConfigValue = Schema.decodeUnknownSync(AppConfigValue)
const config = Config.all({
stage: Config.succeed(Resource.App.stage),
publicUrl: Config.string("PUBLIC_URL").pipe(Config.withDefault("http://localhost:3000")),
}).pipe(Config.map(decodeAppConfigValue))
export class AppConfig extends Context.Service<AppConfig, AppConfigValue>()("@opencode/stats/AppConfig") {
static readonly config = config
static readonly layer: Layer.Layer<AppConfig, never, never> = Layer.effect(
AppConfig,
config.parse(ConfigProvider.fromEnv()).pipe(Effect.orDie),
)
}

View file

@ -0,0 +1,79 @@
import { Client } from "@planetscale/database"
import { drizzle } from "drizzle-orm/planetscale-serverless"
import { migrate as drizzleMigrate } from "drizzle-orm/planetscale-serverless/migrator"
import { Config, ConfigProvider, Effect, Layer, Schema } from "effect"
import * as Context from "effect/Context"
import * as schema from "./database/schema"
import { Resource } from "sst/resource"
export const DatabaseUrl = Schema.NonEmptyString.pipe(Schema.brand("DatabaseUrl"))
export type DatabaseUrl = typeof DatabaseUrl.Type
export class DatabaseSettings extends Schema.Class<DatabaseSettings>("DatabaseSettings")({
url: DatabaseUrl,
migrationsDir: Schema.NonEmptyString,
}) {}
const decodeDatabaseSettings = Schema.decodeUnknownSync(DatabaseSettings)
const config = Config.all({
url: Config.nonEmptyString("DATABASE_URL").pipe(Config.withDefault(Resource.StatsDatabase.url)),
migrationsDir: Config.nonEmptyString("DATABASE_MIGRATIONS_DIR").pipe(Config.withDefault("./migrations")),
}).pipe(Config.map(decodeDatabaseSettings))
export class DatabaseConfig extends Context.Service<DatabaseConfig, DatabaseSettings>()(
"@opencode/stats/DatabaseConfig",
) {
static readonly config = config
static readonly layer: Layer.Layer<DatabaseConfig, never, never> = Layer.effect(
DatabaseConfig,
config.parse(ConfigProvider.fromEnv()).pipe(Effect.orDie),
)
}
function makeDrizzle(settings: DatabaseSettings) {
return drizzle({ client: new Client({ url: settings.url }), schema })
}
export type Drizzle = ReturnType<typeof makeDrizzle>
export class DrizzleClient extends Context.Service<DrizzleClient, Drizzle>()("@opencode/stats/DrizzleClient") {
static readonly layer: Layer.Layer<DrizzleClient, never, DatabaseConfig> = Layer.effect(
DrizzleClient,
Effect.map(DatabaseConfig, makeDrizzle),
)
}
export class DatabaseError extends Schema.TaggedErrorClass<DatabaseError>()("DatabaseError", {
cause: Schema.Defect,
}) {}
export const catchDbError = Effect.mapError((cause) => DatabaseError.make({ cause }))
export class MigrationError extends Schema.TaggedErrorClass<MigrationError>()("MigrationError", {
message: Schema.String,
cause: Schema.optional(Schema.Defect),
}) {}
export const migrate = Effect.fn("Database.migrate")(function* () {
const settings = yield* DatabaseConfig
yield* Effect.logInfo("applying database migrations").pipe(
Effect.annotateLogs({ migrationsDir: settings.migrationsDir }),
)
const result = yield* Effect.tryPromise({
try: () =>
drizzleMigrate(drizzle({ client: new Client({ url: settings.url }) }), {
migrationsFolder: settings.migrationsDir,
}),
catch: (cause) => new MigrationError({ message: "Failed to apply database migrations", cause }),
})
if (result)
return yield* new MigrationError({
message: `Failed to initialize database migrations: ${result.exitCode}`,
})
yield* Effect.logInfo("database migrations complete").pipe(
Effect.annotateLogs({ migrationsDir: settings.migrationsDir }),
)
})
export const layer = Layer.mergeAll(DatabaseConfig.layer, DrizzleClient.layer.pipe(Layer.provide(DatabaseConfig.layer)))

View file

@ -0,0 +1,156 @@
import { bigint, char, datetime, decimal, index, int, mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core"
export const modelStat = mysqlTable(
"model_stat",
{
...periodColumns(),
provider: varchar({ length: 128 }).notNull(),
model: varchar({ length: 256 }).notNull(),
provider_model: varchar({ length: 256 }).notNull().default(""),
...metricColumns(),
rank_by_tokens: int(),
rank_by_requests: int(),
rank_by_cost: int(),
...timestampColumns(),
},
(table) => [
uniqueIndex("uniq_model_period").on(
table.grain,
table.period_start,
table.dataset,
table.tier,
table.client,
table.source,
table.provider,
table.model,
),
index("idx_leaderboard_tokens").on(table.grain, table.period_start, table.dataset, table.tier, table.total_tokens),
index("idx_model").on(table.model, table.grain, table.period_start),
],
)
export const providerStat = mysqlTable(
"provider_stat",
{
...periodColumns(),
provider: varchar({ length: 128 }).notNull(),
...metricColumns(),
...marketShareColumns(),
rank_by_tokens: int(),
rank_by_requests: int(),
rank_by_sessions: int(),
rank_by_cost: int(),
...timestampColumns(),
},
(table) => [
uniqueIndex("uniq_provider_period").on(
table.grain,
table.period_start,
table.dataset,
table.tier,
table.client,
table.source,
table.provider,
),
index("idx_provider_leaderboard_tokens").on(
table.grain,
table.period_start,
table.dataset,
table.tier,
table.total_tokens,
),
index("idx_provider_market_share").on(
table.grain,
table.period_start,
table.dataset,
table.tier,
table.market_share_tokens,
),
index("idx_provider_rank").on(table.grain, table.period_start, table.dataset, table.tier, table.rank_by_tokens),
index("idx_provider").on(table.provider, table.grain, table.period_start),
],
)
export const geoStat = mysqlTable(
"geo_stat",
{
...periodColumns(),
country: char({ length: 2 }).notNull(),
continent: varchar({ length: 8 }).notNull().default(""),
...metricColumns(),
...marketShareColumns(),
rank_by_tokens: int(),
rank_by_requests: int(),
rank_by_sessions: int(),
rank_by_cost: int(),
...timestampColumns(),
},
(table) => [
uniqueIndex("uniq_country_period").on(
table.grain,
table.period_start,
table.dataset,
table.tier,
table.client,
table.source,
table.country,
),
index("idx_country_map_tokens").on(table.grain, table.period_start, table.dataset, table.tier, table.total_tokens),
index("idx_country_rank").on(table.grain, table.period_start, table.dataset, table.tier, table.rank_by_tokens),
index("idx_country").on(table.country, table.grain, table.period_start),
index("idx_continent").on(table.continent, table.grain, table.period_start),
],
)
function periodColumns() {
return {
id: bigint({ mode: "number" }).autoincrement().primaryKey(),
grain: varchar({ length: 16 }).notNull(),
period_start: datetime({ mode: "date" }).notNull(),
period_end: datetime({ mode: "date" }).notNull(),
dataset: varchar({ length: 64 }).notNull().default("all"),
tier: varchar({ length: 64 }).notNull().default("all"),
client: varchar({ length: 64 }).notNull().default("all"),
source: varchar({ length: 64 }).notNull().default("all"),
}
}
function metricColumns() {
return {
sessions: bigint({ mode: "number" }).notNull().default(0),
requests: bigint({ mode: "number" }).notNull().default(0),
input_tokens: bigint({ mode: "number" }).notNull().default(0),
output_tokens: bigint({ mode: "number" }).notNull().default(0),
reasoning_tokens: bigint({ mode: "number" }).notNull().default(0),
cache_read_tokens: bigint({ mode: "number" }).notNull().default(0),
total_tokens: bigint({ mode: "number" }).notNull().default(0),
input_cost_microcents: bigint({ mode: "number" }).notNull().default(0),
output_cost_microcents: bigint({ mode: "number" }).notNull().default(0),
total_cost_microcents: bigint({ mode: "number" }).notNull().default(0),
avg_duration_ms: decimal({ precision: 12, scale: 2, mode: "number" }),
p50_duration_ms: int(),
p95_duration_ms: int(),
avg_ttfb_ms: decimal({ precision: 12, scale: 2, mode: "number" }),
p50_ttfb_ms: int(),
p95_ttfb_ms: int(),
avg_output_tps: decimal({ precision: 12, scale: 4, mode: "number" }),
success_count: bigint({ mode: "number" }).notNull().default(0),
error_count: bigint({ mode: "number" }).notNull().default(0),
sample_count: bigint({ mode: "number" }).notNull().default(0),
}
}
function marketShareColumns() {
return {
market_share_tokens: decimal({ precision: 10, scale: 6, mode: "number" }),
market_share_requests: decimal({ precision: 10, scale: 6, mode: "number" }),
market_share_sessions: decimal({ precision: 10, scale: 6, mode: "number" }),
}
}
function timestampColumns() {
return {
created_at: datetime({ mode: "date" }).notNull().defaultNow(),
updated_at: datetime({ mode: "date" }).notNull().defaultNow().onUpdateNow(),
}
}

View file

@ -0,0 +1,171 @@
import { and, asc, eq } from "drizzle-orm"
import { Effect, Layer } from "effect"
import * as Context from "effect/Context"
import { DatabaseError, DrizzleClient } from "../database"
import { geoStat } from "../database/schema"
import {
chunks,
collapseRows,
inserted,
rankRowsWithMarketShare,
synthesizeAllTierRows,
toStatBaseRow,
UPSERT_CHUNK_SIZE,
type StatBaseAggregate,
} from "./stat"
export type GeoStatRow = typeof geoStat.$inferInsert
export type GeoStatAggregate = StatBaseAggregate & { country: string; continent: string }
export type GeoStatMetric = {
periodStart: Date
periodEnd: Date
tier: string
country: string
continent: string
totalTokens: number
}
export declare namespace GeoStatRepo {
export interface Service {
readonly listDaily: () => Effect.Effect<GeoStatMetric[], DatabaseError>
readonly listByPeriod: (opts: {
readonly grain: string
readonly periodStart: Date
readonly dataset?: string
readonly tier?: string
readonly client?: string
readonly source?: string
}) => Effect.Effect<GeoStatRow[], DatabaseError>
readonly upsert: (rows: GeoStatRow[]) => Effect.Effect<void, DatabaseError>
}
}
export class GeoStatRepo extends Context.Service<GeoStatRepo, GeoStatRepo.Service>()("@opencode/stats/GeoStatRepo") {
static readonly layer: Layer.Layer<GeoStatRepo, never, DrizzleClient> = Layer.effect(
GeoStatRepo,
Effect.gen(function* () {
const db = yield* DrizzleClient
const listDaily = Effect.fn("GeoStatRepo.listDaily")(function* () {
return yield* Effect.tryPromise({
try: () =>
db
.select({
periodStart: geoStat.period_start,
periodEnd: geoStat.period_end,
tier: geoStat.tier,
country: geoStat.country,
continent: geoStat.continent,
totalTokens: geoStat.total_tokens,
})
.from(geoStat)
.where(and(eq(geoStat.grain, "day"), eq(geoStat.client, "all"), eq(geoStat.source, "all")))
.orderBy(asc(geoStat.period_start)),
catch: (cause) => DatabaseError.make({ cause }),
})
})
const listByPeriod = Effect.fn("GeoStatRepo.listByPeriod")(function* (opts: {
readonly grain: string
readonly periodStart: Date
readonly dataset?: string
readonly tier?: string
readonly client?: string
readonly source?: string
}) {
return yield* Effect.tryPromise({
try: () =>
db
.select()
.from(geoStat)
.where(
and(
eq(geoStat.grain, opts.grain),
eq(geoStat.period_start, opts.periodStart),
eq(geoStat.dataset, opts.dataset ?? "zen"),
eq(geoStat.tier, opts.tier ?? "all"),
eq(geoStat.client, opts.client ?? "all"),
eq(geoStat.source, opts.source ?? "all"),
),
),
catch: (cause) => DatabaseError.make({ cause }),
})
})
const upsert = Effect.fn("GeoStatRepo.upsert")(function* (rows: GeoStatRow[]) {
yield* Effect.forEach(
chunks(rows, UPSERT_CHUNK_SIZE),
(chunk) =>
Effect.tryPromise({
try: () =>
db
.insert(geoStat)
.values(chunk)
.onDuplicateKeyUpdate({
set: {
period_end: inserted("period_end"),
continent: inserted("continent"),
sessions: inserted("sessions"),
requests: inserted("requests"),
input_tokens: inserted("input_tokens"),
output_tokens: inserted("output_tokens"),
reasoning_tokens: inserted("reasoning_tokens"),
cache_read_tokens: inserted("cache_read_tokens"),
total_tokens: inserted("total_tokens"),
input_cost_microcents: inserted("input_cost_microcents"),
output_cost_microcents: inserted("output_cost_microcents"),
total_cost_microcents: inserted("total_cost_microcents"),
avg_duration_ms: inserted("avg_duration_ms"),
p50_duration_ms: inserted("p50_duration_ms"),
p95_duration_ms: inserted("p95_duration_ms"),
avg_ttfb_ms: inserted("avg_ttfb_ms"),
p50_ttfb_ms: inserted("p50_ttfb_ms"),
p95_ttfb_ms: inserted("p95_ttfb_ms"),
avg_output_tps: inserted("avg_output_tps"),
success_count: inserted("success_count"),
error_count: inserted("error_count"),
sample_count: inserted("sample_count"),
market_share_tokens: inserted("market_share_tokens"),
market_share_requests: inserted("market_share_requests"),
market_share_sessions: inserted("market_share_sessions"),
rank_by_tokens: inserted("rank_by_tokens"),
rank_by_requests: inserted("rank_by_requests"),
rank_by_sessions: inserted("rank_by_sessions"),
rank_by_cost: inserted("rank_by_cost"),
},
}),
catch: (cause) => DatabaseError.make({ cause }),
}),
{ discard: true },
)
})
return GeoStatRepo.of({ listDaily, listByPeriod, upsert })
}),
)
}
export function rowsFromAggregates(aggregates: GeoStatAggregate[]) {
return rankRowsWithMarketShare([
...synthesizeAllTierRows(
collapseRows(aggregates.filter((item) => item.grain === "week").map(toRow), dimensionKey),
dimensionKey,
),
...synthesizeAllTierRows(
collapseRows(aggregates.filter((item) => item.grain === "day").map(toRow), dimensionKey),
dimensionKey,
),
])
}
function toRow(data: GeoStatAggregate): GeoStatRow {
return {
...toStatBaseRow(data),
country: data.country,
continent: data.continent,
}
}
function dimensionKey(row: GeoStatRow) {
return row.country
}

View file

@ -0,0 +1,467 @@
import { Effect } from "effect"
import { DatabaseError } from "../database"
import { GeoStatRepo, type GeoStatMetric } from "./geo"
import { ModelStatRepo, type ModelStatMetric } from "./model"
import { ProviderStatRepo, type ProviderStatMetric } from "./provider"
export type UsageProduct = "All Users" | "Zen" | "Go" | "Enterprise"
export type TokenProduct = "Zen" | "Go" | "Enterprise"
export type UsageRange = "1D" | "1W" | "1M" | "3M" | "YTD" | "ALL"
export type UsagePoint = { date: string; segments: { model: string; value: number }[] }
export type MarketDay = { date: string; total: number; authors: { author: string; share: number; tokens: number }[] }
export type LeaderboardEntry = { model: string; author: string; tokens: number; change: number; rank: number }
export type TokenCostEntry = { model: string; total: number; input: number; output: number; cached: number }
export type SessionCostEntry = { model: string; cost: number; tokens: number }
export type CountryEntry = { country: string; continent: string; tokens: number; share: number; rank: number }
export type StatsHomeData = {
updatedAt: string | null
usage: Record<UsageProduct, Record<UsageRange, UsagePoint[]>>
leaderboard: Record<UsageProduct, Record<UsageRange, LeaderboardEntry[]>>
market: Record<UsageRange, MarketDay[]>
tokenCost: Record<TokenProduct, TokenCostEntry[]>
sessionCost: Record<TokenProduct, SessionCostEntry[]>
country: Record<UsageRange, CountryEntry[]>
}
const DAY_MS = 86_400_000
const TOKEN_SCALE = 1_000_000
const DOLLARS_PER_MICROCENT = 1 / 100_000_000
const months = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"] as const
type StatMetricRow = Omit<ModelStatMetric, "periodStart" | "periodEnd"> & {
periodStart: number
periodEnd: number
}
type ProviderMetricRow = Omit<ProviderStatMetric, "periodStart" | "periodEnd"> & {
periodStart: number
periodEnd: number
}
type GeoMetricRow = Omit<GeoStatMetric, "periodStart" | "periodEnd"> & {
periodStart: number
periodEnd: number
}
type DateWindow = { start: number; end: number; previousStart: number; previousEnd: number }
type Bucket = { start: number; end: number; label: string }
type ModelAggregate = {
model: string
provider: string
sessions: number
inputTokens: number
outputTokens: number
reasoningTokens: number
cacheReadTokens: number
totalTokens: number
inputCostMicrocents: number
outputCostMicrocents: number
totalCostMicrocents: number
}
export const getStatsHomeData: () => Effect.Effect<
StatsHomeData,
DatabaseError,
ModelStatRepo | ProviderStatRepo | GeoStatRepo
> = Effect.fn("StatsHome.getData")(function* () {
const modelStats = yield* ModelStatRepo
const providerStats = yield* ProviderStatRepo
const geoStats = yield* GeoStatRepo
const [modelRows, providerRows, geoRows] = yield* Effect.all(
[modelStats.listDaily(), providerStats.listDaily(), geoStats.listDaily()],
{ concurrency: "unbounded" },
)
return buildStatsHomeData(modelRows, providerRows, geoRows)
})
function buildStatsHomeData(
modelRows: ModelStatMetric[],
providerRows: ProviderStatMetric[],
geoRows: GeoStatMetric[],
): StatsHomeData {
const normalized = modelRows.flatMap(normalizeStatRow)
const providers = providerRows.flatMap(normalizeProviderRow)
const geo = geoRows.flatMap(normalizeGeoRow)
const periods = [...normalized, ...providers, ...geo]
if (periods.length === 0) return emptyStatsHomeData()
const earliest = Math.min(...periods.map((row) => row.periodStart))
const latest = Math.max(...periods.map((row) => row.periodStart))
const latestEnd = Math.max(...periods.map((row) => row.periodEnd))
return {
updatedAt: new Date(latestEnd).toISOString(),
usage: createUsageProductRecord((product) =>
createRangeRecord((range) => buildUsagePoints(normalized, product, range, getWindow(range, earliest, latest))),
),
leaderboard: createUsageProductRecord((product) =>
createRangeRecord((range) => buildLeaderboard(normalized, product, getWindow(range, earliest, latest))),
),
market: createRangeRecord((range) => buildMarketShare(providers, range, getWindow(range, earliest, latest))),
tokenCost: createTokenProductRecord((product) =>
buildTokenCost(normalized, product, getWindow("1W", earliest, latest)),
),
sessionCost: createTokenProductRecord((product) =>
buildSessionCost(normalized, product, getWindow("1W", earliest, latest)),
),
country: createRangeRecord((range) => buildCountryStats(geo, getWindow(range, earliest, latest))),
}
}
function emptyStatsHomeData(): StatsHomeData {
return {
updatedAt: null,
usage: createUsageProductRecord(() => createRangeRecord(() => [])),
leaderboard: createUsageProductRecord(() => createRangeRecord(() => [])),
market: createRangeRecord(() => []),
tokenCost: createTokenProductRecord(() => []),
sessionCost: createTokenProductRecord(() => []),
country: createRangeRecord(() => []),
}
}
function buildUsagePoints(rows: StatMetricRow[], product: UsageProduct, range: UsageRange, window: DateWindow) {
const windowRows = rowsForProduct(rows, product, window.start, window.end)
const modelOrder = aggregateByModel(windowRows)
.toSorted((a, b) => b.totalTokens - a.totalTokens)
.slice(0, 6)
.map((item) => ({ key: modelKey(item.provider, item.model), model: item.model }))
return createBuckets(window, range).map((bucket) => {
const bucketRows = aggregateByModel(rowsForProduct(rows, product, bucket.start, bucket.end))
const byModel = new Map(bucketRows.map((item) => [modelKey(item.provider, item.model), item.totalTokens]))
const segmentTokens = modelOrder.map((model) => ({ model: model.model, tokens: byModel.get(model.key) ?? 0 }))
const knownTokens = segmentTokens.reduce((sum, item) => sum + item.tokens, 0)
const totalTokens = bucketRows.reduce((sum, item) => sum + item.totalTokens, 0)
return {
date: bucket.label,
segments: [
...segmentTokens.map((item) => ({ model: item.model, value: round(item.tokens / 1_000_000_000_000, 2) })),
{ model: "Other", value: round(Math.max(totalTokens - knownTokens, 0) / 1_000_000_000_000, 2) },
].filter((item) => item.value > 0),
}
})
}
function buildLeaderboard(rows: StatMetricRow[], product: UsageProduct, window: DateWindow) {
const previous = new Map(
aggregateByModel(rowsForProduct(rows, product, window.previousStart, window.previousEnd)).map((item) => [
modelKey(item.provider, item.model),
item.totalTokens,
]),
)
return aggregateByModel(rowsForProduct(rows, product, window.start, window.end))
.toSorted((a, b) => b.totalTokens - a.totalTokens)
.slice(0, 13)
.map((item, index) => ({
model: item.model,
author: formatProvider(item.provider),
tokens: Math.round(item.totalTokens / 1_000_000_000),
change: percentChange(item.totalTokens, previous.get(modelKey(item.provider, item.model)) ?? 0),
rank: index + 1,
}))
}
function buildMarketShare(rows: ProviderMetricRow[], range: UsageRange, window: DateWindow) {
return createBuckets(window, range).flatMap((bucket) => {
const total = aggregateByProvider(rowsForProduct(rows, "All Users", bucket.start, bucket.end)).toSorted(
(a, b) => b.tokens - a.tokens,
)
const totalTokens = total.reduce((sum, item) => sum + item.tokens, 0)
if (totalTokens === 0) return []
const authors = total.slice(0, 8)
const knownTokens = authors.reduce((sum, item) => sum + item.tokens, 0)
const withOther = [...authors, { provider: "Other", tokens: Math.max(totalTokens - knownTokens, 0) }].filter(
(item) => item.tokens > 0,
)
return [
{
date: bucket.label,
total: round(totalTokens / 1_000_000_000_000, 2),
authors: withOther.map((item) => ({
author: item.provider === "Other" ? "Other" : formatProvider(item.provider),
share: round((item.tokens / totalTokens) * 100, 1),
tokens: round(item.tokens / 1_000_000_000_000, 2),
})),
},
]
})
}
function buildCountryStats(rows: GeoMetricRow[], window: DateWindow) {
const countries = aggregateByCountry(rowsForProduct(rows, "All Users", window.start, window.end))
.filter((item) => item.tokens > 0)
.toSorted((a, b) => b.tokens - a.tokens)
const totalTokens = countries.reduce((sum, item) => sum + item.tokens, 0)
if (totalTokens === 0) return []
return countries.slice(0, 16).map((item, index) => ({
country: item.country,
continent: item.continent,
tokens: round(item.tokens / 1_000_000_000_000, 4),
share: round((item.tokens / totalTokens) * 100, 1),
rank: index + 1,
}))
}
function buildTokenCost(rows: StatMetricRow[], product: TokenProduct, window: DateWindow) {
return aggregateByModel(rowsForProduct(rows, product, window.start, window.end))
.flatMap((item) => {
const total = costPerMillion(item.totalCostMicrocents, item.totalTokens)
if (total === 0) return []
return [
{
model: item.model,
total,
input: costPerMillion(item.inputCostMicrocents, item.inputTokens),
output: costPerMillion(item.outputCostMicrocents, item.outputTokens + item.reasoningTokens),
cached: costPerMillion(item.inputCostMicrocents, item.inputTokens + item.cacheReadTokens),
},
]
})
.toSorted((a, b) => a.total - b.total)
.slice(0, 17)
}
function buildSessionCost(rows: StatMetricRow[], product: TokenProduct, window: DateWindow) {
return aggregateByModel(rowsForProduct(rows, product, window.start, window.end))
.flatMap((item) => {
if (item.sessions === 0) return []
const cost = round(microcentsToDollars(item.totalCostMicrocents) / item.sessions, 4)
if (cost === 0) return []
return [{ model: item.model, cost, tokens: Math.round(item.totalTokens / item.sessions) }]
})
.toSorted((a, b) => a.cost - b.cost)
.slice(0, 17)
}
function rowsForProduct<T extends { periodStart: number; tier: string }>(
rows: T[],
product: UsageProduct,
start: number,
end: number,
) {
const windowRows = rows.filter((row) => row.periodStart >= start && row.periodStart < end)
if (product !== "All Users") return windowRows.filter((row) => row.tier === product)
const allRows = windowRows.filter((row) => row.tier === "all")
if (allRows.length > 0) return allRows
return windowRows.filter((row) => row.tier !== "all")
}
function aggregateByModel(rows: StatMetricRow[]) {
return Object.values(
rows.reduce<Record<string, ModelAggregate>>((result, row) => {
const key = modelKey(row.provider, row.model)
result[key] = combineModelAggregate(result[key], row)
return result
}, {}),
)
}
function aggregateByProvider(rows: ProviderMetricRow[]) {
return Object.values(
rows.reduce<Record<string, { provider: string; tokens: number }>>((result, row) => {
result[row.provider] = {
provider: row.provider,
tokens: (result[row.provider]?.tokens ?? 0) + row.totalTokens,
}
return result
}, {}),
)
}
function aggregateByCountry(rows: GeoMetricRow[]) {
return Object.values(
rows.reduce<Record<string, { country: string; continent: string; tokens: number }>>((result, row) => {
result[row.country] = {
country: row.country,
continent: result[row.country]?.continent || row.continent,
tokens: (result[row.country]?.tokens ?? 0) + row.totalTokens,
}
return result
}, {}),
)
}
function combineModelAggregate(current: ModelAggregate | undefined, row: StatMetricRow): ModelAggregate {
return {
model: row.model,
provider: row.provider,
sessions: (current?.sessions ?? 0) + row.sessions,
inputTokens: (current?.inputTokens ?? 0) + row.inputTokens,
outputTokens: (current?.outputTokens ?? 0) + row.outputTokens,
reasoningTokens: (current?.reasoningTokens ?? 0) + row.reasoningTokens,
cacheReadTokens: (current?.cacheReadTokens ?? 0) + row.cacheReadTokens,
totalTokens: (current?.totalTokens ?? 0) + row.totalTokens,
inputCostMicrocents: (current?.inputCostMicrocents ?? 0) + row.inputCostMicrocents,
outputCostMicrocents: (current?.outputCostMicrocents ?? 0) + row.outputCostMicrocents,
totalCostMicrocents: (current?.totalCostMicrocents ?? 0) + row.totalCostMicrocents,
}
}
function getWindow(range: UsageRange, earliest: number, latest: number): DateWindow {
const end = latest + DAY_MS
const start = Math.max(
earliest,
range === "1D"
? latest
: range === "1W"
? latest - 6 * DAY_MS
: range === "1M"
? latest - 29 * DAY_MS
: range === "3M"
? latest - 89 * DAY_MS
: range === "YTD"
? Date.UTC(new Date(latest).getUTCFullYear(), 0, 1)
: earliest,
)
const duration = end - start
return { start, end, previousStart: start - duration, previousEnd: start }
}
function createBuckets(window: DateWindow, range: UsageRange): Bucket[] {
const span = Math.max(window.end - window.start, DAY_MS)
const count = Math.max(1, Math.min(7, Math.ceil(span / DAY_MS)))
const size = span / count
return Array.from({ length: count }, (_, index) => {
const start = window.start + index * size
const end = index === count - 1 ? window.end : window.start + (index + 1) * size
return { start, end, label: formatBucketLabel(start, range) }
})
}
function createUsageProductRecord<T>(value: (product: UsageProduct) => T): Record<UsageProduct, T> {
return {
"All Users": value("All Users"),
Zen: value("Zen"),
Go: value("Go"),
Enterprise: value("Enterprise"),
}
}
function createTokenProductRecord<T>(value: (product: TokenProduct) => T): Record<TokenProduct, T> {
return {
Zen: value("Zen"),
Go: value("Go"),
Enterprise: value("Enterprise"),
}
}
function createRangeRecord<T>(value: (range: UsageRange) => T): Record<UsageRange, T> {
return {
"1D": value("1D"),
"1W": value("1W"),
"1M": value("1M"),
"3M": value("3M"),
YTD: value("YTD"),
ALL: value("ALL"),
}
}
function normalizeStatRow(row: ModelStatMetric): StatMetricRow[] {
const periodStart = dateTime(row.periodStart)
const periodEnd = dateTime(row.periodEnd)
if (!Number.isFinite(periodStart) || !Number.isFinite(periodEnd)) return []
return [
{
...row,
periodStart,
periodEnd,
tier: normalizeTier(row.tier),
provider: row.provider || "unknown",
model: row.model || "unknown",
},
]
}
function normalizeProviderRow(row: ProviderStatMetric): ProviderMetricRow[] {
const periodStart = dateTime(row.periodStart)
const periodEnd = dateTime(row.periodEnd)
if (!Number.isFinite(periodStart) || !Number.isFinite(periodEnd)) return []
return [
{
...row,
periodStart,
periodEnd,
tier: normalizeTier(row.tier),
provider: row.provider || "unknown",
},
]
}
function normalizeGeoRow(row: GeoStatMetric): GeoMetricRow[] {
const periodStart = dateTime(row.periodStart)
const periodEnd = dateTime(row.periodEnd)
if (!Number.isFinite(periodStart) || !Number.isFinite(periodEnd)) return []
return [
{
...row,
periodStart,
periodEnd,
tier: normalizeTier(row.tier),
country: row.country || "ZZ",
continent: row.continent || "",
},
]
}
function normalizeTier(value: string) {
const normalized = value.toLowerCase()
if (normalized === "paid" || normalized === "zen") return "Zen"
if (normalized === "go") return "Go"
if (normalized === "enterprise") return "Enterprise"
if (normalized === "all") return "all"
return value
}
function dateTime(value: Date | string) {
return (value instanceof Date ? value : new Date(value)).getTime()
}
function formatBucketLabel(value: number, range: UsageRange) {
const date = new Date(value)
if (range === "YTD") return months[date.getUTCMonth()]
if (range === "ALL")
return date.getUTCFullYear() === new Date().getUTCFullYear()
? months[date.getUTCMonth()]
: String(date.getUTCFullYear())
return `${months[date.getUTCMonth()]} ${date.getUTCDate()}`
}
function formatProvider(provider: string) {
const known: Record<string, string> = {
anthropic: "Anthropic",
google: "Google",
minimax: "MiniMax",
moonshotai: "Moonshot",
nvidia: "Nvidia",
openai: "OpenAI",
zhipuai: "Zhipu",
}
const normalized = provider.toLowerCase().replace(/[^a-z0-9]/g, "")
return known[normalized] ?? provider.replace(/[-_]/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase())
}
function modelKey(provider: string, model: string) {
return `${provider}\u0000${model}`
}
function costPerMillion(costMicrocents: number, tokens: number) {
if (tokens <= 0 || costMicrocents <= 0) return 0
return round((microcentsToDollars(costMicrocents) / tokens) * TOKEN_SCALE, 2)
}
function microcentsToDollars(value: number) {
return value * DOLLARS_PER_MICROCENT
}
function percentChange(current: number, previous: number) {
if (previous <= 0) return current > 0 ? 100 : 0
return Math.round(((current - previous) / previous) * 100)
}
function round(value: number, digits: number) {
return Number(value.toFixed(digits))
}

View file

@ -0,0 +1,216 @@
import { Resource } from "sst/resource"
import type { AthenaData } from "../athena"
import type { GeoStatAggregate } from "./geo"
import type { ModelStatAggregate } from "./model"
import type { ProviderStatAggregate } from "./provider"
import { normalizeCountry, normalizeTier, type StatBaseAggregate } from "./stat"
export type StatDimension = "model" | "provider" | "geo"
export function buildStatsQuery(periodStart: Date, periodEnd: Date, dimension: StatDimension) {
const periodStartValue = sqlString(periodStart.toISOString())
const periodEndValue = sqlString(periodEnd.toISOString())
const sourceTable = [
Resource.InferenceEvent.catalog,
Resource.InferenceEvent.database,
Resource.InferenceEvent.table,
]
.map(sqlIdentifier)
.join(".")
const dimensionSql = (() => {
if (dimension === "model")
return {
select: "provider, model, COALESCE(MAX(NULLIF(provider_model, '')), '') AS provider_model",
groupBy: "provider, model",
}
if (dimension === "provider") return { select: "provider", groupBy: "provider" }
return {
select: "country, COALESCE(MAX(NULLIF(continent, '')), '') AS continent",
groupBy: "country",
}
})()
const aggregateColumns = `
COUNT(DISTINCT session) AS sessions,
COUNT(*) AS requests,
COALESCE(SUM(tokens_input), 0) AS input_tokens,
COALESCE(SUM(tokens_output), 0) AS output_tokens,
COALESCE(SUM(tokens_reasoning), 0) AS reasoning_tokens,
COALESCE(SUM(tokens_cache_read), 0) AS cache_read_tokens,
COALESCE(SUM(tokens_total), 0) AS total_tokens,
COALESCE(SUM(cost_input_microcents), 0) AS input_cost_microcents,
COALESCE(SUM(cost_output_microcents), 0) AS output_cost_microcents,
COALESCE(SUM(cost_total_microcents), 0) AS total_cost_microcents,
AVG(duration_ms) AS avg_duration_ms,
approx_percentile(CAST(duration_ms AS double), 0.5) AS p50_duration_ms,
approx_percentile(CAST(duration_ms AS double), 0.95) AS p95_duration_ms,
AVG(ttfb_ms) AS avg_ttfb_ms,
approx_percentile(CAST(ttfb_ms AS double), 0.5) AS p50_ttfb_ms,
approx_percentile(CAST(ttfb_ms AS double), 0.95) AS p95_ttfb_ms,
AVG(output_tps) AS avg_output_tps,
SUM(CASE WHEN status >= 200 AND status < 400 THEN 1 ELSE 0 END) AS success_count,
SUM(CASE WHEN status >= 400 THEN 1 ELSE 0 END) AS error_count,
COUNT(*) AS sample_count`
return `
WITH filtered AS (
SELECT
from_iso8601_timestamp(event_timestamp) AS event_time,
CASE
WHEN source = 'lite' THEN 'Go'
WHEN model IN ('gpt-5-nano', 'grok-code', 'big-pickle') OR model LIKE '%-free' THEN 'Free'
ELSE 'Paid'
END AS tier,
COALESCE(NULLIF(
CASE
WHEN starts_with(provider, 'minimax-plan') THEN 'minimax-plan'
WHEN starts_with(provider, 'zai-plan') THEN 'zai-plan'
WHEN starts_with(provider, 'azure-databricks') THEN 'azure-databricks'
WHEN regexp_like(provider, '^azure[0-9]+') THEN 'azure-openai'
ELSE provider
END,
''
), 'unknown') AS provider,
COALESCE(NULLIF(provider_model, ''), '') AS provider_model,
COALESCE(NULLIF(model, ''), 'unknown') AS model,
UPPER(COALESCE(NULLIF(cf_country, ''), 'ZZ')) AS country,
COALESCE(NULLIF(cf_continent, ''), '') AS continent,
session,
status,
duration AS duration_ms,
time_to_first_byte AS ttfb_ms,
CASE
WHEN timestamp_last_byte - timestamp_first_byte < 100 THEN null
ELSE CAST(tokens_output AS double) / (timestamp_last_byte - timestamp_first_byte) * 1000
END AS output_tps,
tokens_input,
tokens_output,
tokens_reasoning,
tokens_cache_read,
COALESCE(tokens_cache_read, 0) + COALESCE(tokens_cache_write_5m, 0) + COALESCE(tokens_input, 0) + COALESCE(tokens_output, 0) AS tokens_total,
COALESCE(cost_input_microcents, cost_input * 1000000) AS cost_input_microcents,
COALESCE(cost_output_microcents, cost_output * 1000000) AS cost_output_microcents,
COALESCE(cost_total_microcents, cost_total * 1000000) AS cost_total_microcents
FROM ${sourceTable}
WHERE event_type = 'completions'
AND model IS NOT NULL
AND model <> ''
AND (strpos(COALESCE(user_agent, ''), 'ai-sdk') > 0 OR strpos(COALESCE(user_agent, ''), 'opencode') > 0)
AND event_timestamp >= ${periodStartValue}
AND event_timestamp < ${periodEndValue}
), daily AS (
SELECT date_trunc('day', event_time) AS day, *
FROM filtered
)
SELECT
'week' AS grain,
${periodStartValue} AS period_start,
${periodEndValue} AS period_end,
${sqlString(Resource.StatsSyncConfig.dataset)} AS dataset,
tier,
${dimensionSql.select},
${aggregateColumns}
FROM filtered
GROUP BY tier, ${dimensionSql.groupBy}
UNION ALL
SELECT
'day' AS grain,
to_iso8601(day) AS period_start,
to_iso8601(least(day + INTERVAL '1' DAY, from_iso8601_timestamp(${periodEndValue}))) AS period_end,
${sqlString(Resource.StatsSyncConfig.dataset)} AS dataset,
tier,
${dimensionSql.select},
${aggregateColumns}
FROM daily
GROUP BY day, tier, ${dimensionSql.groupBy}
ORDER BY grain, period_start, total_tokens DESC
`
}
export function toModelAggregate(data: AthenaData): ModelStatAggregate[] {
return toStatBaseAggregate(data).flatMap((base) => [
{
...base,
provider: data.provider || "unknown",
model: data.model || "unknown",
provider_model: data.provider_model || "",
},
])
}
export function toProviderAggregate(data: AthenaData): ProviderStatAggregate[] {
return toStatBaseAggregate(data).flatMap((base) => [{ ...base, provider: data.provider || "unknown" }])
}
export function toGeoAggregate(data: AthenaData): GeoStatAggregate[] {
return toStatBaseAggregate(data).flatMap((base) => [
{
...base,
country: normalizeCountry(data.country),
continent: data.continent || "",
},
])
}
function toStatBaseAggregate(data: AthenaData): StatBaseAggregate[] {
const grain = data.grain === "day" || data.grain === "week" ? data.grain : undefined
const periodStart = new Date(data.period_start ?? "")
const periodEnd = new Date(data.period_end ?? "")
if (!grain || Number.isNaN(periodStart.getTime()) || Number.isNaN(periodEnd.getTime())) return []
return [
{
grain,
period_start: periodStart,
period_end: periodEnd,
dataset: data.dataset || Resource.StatsSyncConfig.dataset,
tier: normalizeTier(data.tier || "unknown"),
sessions: integer(data, "sessions"),
requests: integer(data, "requests"),
input_tokens: integer(data, "input_tokens"),
output_tokens: integer(data, "output_tokens"),
reasoning_tokens: integer(data, "reasoning_tokens"),
cache_read_tokens: integer(data, "cache_read_tokens"),
total_tokens: integer(data, "total_tokens"),
input_cost_microcents: integer(data, "input_cost_microcents"),
output_cost_microcents: integer(data, "output_cost_microcents"),
total_cost_microcents: integer(data, "total_cost_microcents"),
avg_duration_ms: nullableNumber(data, "avg_duration_ms"),
p50_duration_ms: nullableInteger(data, "p50_duration_ms"),
p95_duration_ms: nullableInteger(data, "p95_duration_ms"),
avg_ttfb_ms: nullableNumber(data, "avg_ttfb_ms"),
p50_ttfb_ms: nullableInteger(data, "p50_ttfb_ms"),
p95_ttfb_ms: nullableInteger(data, "p95_ttfb_ms"),
avg_output_tps: nullableNumber(data, "avg_output_tps"),
success_count: integer(data, "success_count"),
error_count: integer(data, "error_count"),
sample_count: integer(data, "sample_count"),
},
]
}
function integer(data: AthenaData, key: string) {
return Math.round(number(data, key))
}
function nullableNumber(data: AthenaData, key: string) {
if (data[key] === undefined || data[key] === "") return null
return Number(number(data, key).toFixed(2))
}
function nullableInteger(data: AthenaData, key: string) {
if (data[key] === undefined || data[key] === "") return null
return Math.round(number(data, key))
}
function number(data: AthenaData, key: string) {
const value = Number(data[key])
return Number.isFinite(value) ? value : 0
}
function sqlIdentifier(value: string) {
return `"${value.replace(/"/g, '""')}"`
}
function sqlString(value: string) {
return `'${value.replace(/'/g, "''")}'`
}

View file

@ -0,0 +1,173 @@
import { and, asc, eq } from "drizzle-orm"
import { Effect, Layer } from "effect"
import * as Context from "effect/Context"
import { DatabaseError, DrizzleClient } from "../database"
import { modelStat } from "../database/schema"
import {
chunks,
collapseRows,
inserted,
rankBy,
statPeriodKey,
synthesizeAllTierRows,
toStatBaseRow,
UPSERT_CHUNK_SIZE,
type StatBaseAggregate,
} from "./stat"
export type ModelStatRow = typeof modelStat.$inferInsert
export type ModelStatAggregate = StatBaseAggregate & { provider: string; model: string; provider_model: string }
export type ModelStatMetric = {
periodStart: Date
periodEnd: Date
tier: string
provider: string
model: string
sessions: number
inputTokens: number
outputTokens: number
reasoningTokens: number
cacheReadTokens: number
totalTokens: number
inputCostMicrocents: number
outputCostMicrocents: number
totalCostMicrocents: number
}
export declare namespace ModelStatRepo {
export interface Service {
readonly listDaily: () => Effect.Effect<ModelStatMetric[], DatabaseError>
readonly upsert: (rows: ModelStatRow[]) => Effect.Effect<void, DatabaseError>
}
}
export class ModelStatRepo extends Context.Service<ModelStatRepo, ModelStatRepo.Service>()(
"@opencode/stats/ModelStatRepo",
) {
static readonly layer: Layer.Layer<ModelStatRepo, never, DrizzleClient> = Layer.effect(
ModelStatRepo,
Effect.gen(function* () {
const db = yield* DrizzleClient
const listDaily = Effect.fn("ModelStatRepo.listDaily")(function* () {
return yield* Effect.tryPromise({
try: () =>
db
.select({
periodStart: modelStat.period_start,
periodEnd: modelStat.period_end,
tier: modelStat.tier,
provider: modelStat.provider,
model: modelStat.model,
sessions: modelStat.sessions,
inputTokens: modelStat.input_tokens,
outputTokens: modelStat.output_tokens,
reasoningTokens: modelStat.reasoning_tokens,
cacheReadTokens: modelStat.cache_read_tokens,
totalTokens: modelStat.total_tokens,
inputCostMicrocents: modelStat.input_cost_microcents,
outputCostMicrocents: modelStat.output_cost_microcents,
totalCostMicrocents: modelStat.total_cost_microcents,
})
.from(modelStat)
.where(and(eq(modelStat.grain, "day"), eq(modelStat.client, "all"), eq(modelStat.source, "all")))
.orderBy(asc(modelStat.period_start)),
catch: (cause) => DatabaseError.make({ cause }),
})
})
const upsert = Effect.fn("ModelStatRepo.upsert")(function* (rows: ModelStatRow[]) {
yield* Effect.forEach(
chunks(rows, UPSERT_CHUNK_SIZE),
(chunk) =>
Effect.tryPromise({
try: () =>
db
.insert(modelStat)
.values(chunk)
.onDuplicateKeyUpdate({
set: {
period_end: inserted("period_end"),
provider_model: inserted("provider_model"),
sessions: inserted("sessions"),
requests: inserted("requests"),
input_tokens: inserted("input_tokens"),
output_tokens: inserted("output_tokens"),
reasoning_tokens: inserted("reasoning_tokens"),
cache_read_tokens: inserted("cache_read_tokens"),
total_tokens: inserted("total_tokens"),
input_cost_microcents: inserted("input_cost_microcents"),
output_cost_microcents: inserted("output_cost_microcents"),
total_cost_microcents: inserted("total_cost_microcents"),
avg_duration_ms: inserted("avg_duration_ms"),
p50_duration_ms: inserted("p50_duration_ms"),
p95_duration_ms: inserted("p95_duration_ms"),
avg_ttfb_ms: inserted("avg_ttfb_ms"),
p50_ttfb_ms: inserted("p50_ttfb_ms"),
p95_ttfb_ms: inserted("p95_ttfb_ms"),
avg_output_tps: inserted("avg_output_tps"),
success_count: inserted("success_count"),
error_count: inserted("error_count"),
sample_count: inserted("sample_count"),
rank_by_tokens: inserted("rank_by_tokens"),
rank_by_requests: inserted("rank_by_requests"),
rank_by_cost: inserted("rank_by_cost"),
},
}),
catch: (cause) => DatabaseError.make({ cause }),
}),
{ discard: true },
)
})
return ModelStatRepo.of({ listDaily, upsert })
}),
)
}
export function rowsFromAggregates(aggregates: ModelStatAggregate[]) {
return rankRows([
...synthesizeAllTierRows(
collapseRows(aggregates.filter((item) => item.grain === "week").map(toRow), dimensionKey),
dimensionKey,
),
...synthesizeAllTierRows(
collapseRows(aggregates.filter((item) => item.grain === "day").map(toRow), dimensionKey),
dimensionKey,
),
])
}
function toRow(data: ModelStatAggregate): ModelStatRow {
return {
...toStatBaseRow(data),
provider: data.provider,
model: data.model,
provider_model: data.provider_model,
}
}
function rankRows(rows: ModelStatRow[]) {
return Object.values(
rows.reduce<Record<string, ModelStatRow[]>>((result, row) => {
const key = statPeriodKey(row)
result[key] = [...(result[key] ?? []), row]
return result
}, {}),
).flatMap((group) => {
const tokenRanks = rankBy(group, (row) => row.total_tokens ?? 0)
const requestRanks = rankBy(group, (row) => row.requests ?? 0)
const costRanks = rankBy(group, (row) => row.total_cost_microcents ?? 0)
return group.map((row) => ({
...row,
rank_by_tokens: tokenRanks.get(row) ?? null,
rank_by_requests: requestRanks.get(row) ?? null,
rank_by_cost: costRanks.get(row) ?? null,
}))
})
}
function dimensionKey(row: ModelStatRow) {
return [row.provider, row.model].join("\u0000")
}

View file

@ -0,0 +1,169 @@
import { and, asc, eq } from "drizzle-orm"
import { Effect, Layer } from "effect"
import * as Context from "effect/Context"
import { DatabaseError, DrizzleClient } from "../database"
import { providerStat } from "../database/schema"
import {
chunks,
collapseRows,
inserted,
rankRowsWithMarketShare,
synthesizeAllTierRows,
toStatBaseRow,
UPSERT_CHUNK_SIZE,
type StatBaseAggregate,
} from "./stat"
export type ProviderStatRow = typeof providerStat.$inferInsert
export type ProviderStatAggregate = StatBaseAggregate & { provider: string }
export type ProviderStatMetric = {
periodStart: Date
periodEnd: Date
tier: string
provider: string
totalTokens: number
}
export declare namespace ProviderStatRepo {
export interface Service {
readonly listDaily: () => Effect.Effect<ProviderStatMetric[], DatabaseError>
readonly listByPeriod: (opts: {
readonly grain: string
readonly periodStart: Date
readonly dataset?: string
readonly tier?: string
readonly client?: string
readonly source?: string
}) => Effect.Effect<ProviderStatRow[], DatabaseError>
readonly upsert: (rows: ProviderStatRow[]) => Effect.Effect<void, DatabaseError>
}
}
export class ProviderStatRepo extends Context.Service<ProviderStatRepo, ProviderStatRepo.Service>()(
"@opencode/stats/ProviderStatRepo",
) {
static readonly layer: Layer.Layer<ProviderStatRepo, never, DrizzleClient> = Layer.effect(
ProviderStatRepo,
Effect.gen(function* () {
const db = yield* DrizzleClient
const listDaily = Effect.fn("ProviderStatRepo.listDaily")(function* () {
return yield* Effect.tryPromise({
try: () =>
db
.select({
periodStart: providerStat.period_start,
periodEnd: providerStat.period_end,
tier: providerStat.tier,
provider: providerStat.provider,
totalTokens: providerStat.total_tokens,
})
.from(providerStat)
.where(and(eq(providerStat.grain, "day"), eq(providerStat.client, "all"), eq(providerStat.source, "all")))
.orderBy(asc(providerStat.period_start)),
catch: (cause) => DatabaseError.make({ cause }),
})
})
const listByPeriod = Effect.fn("ProviderStatRepo.listByPeriod")(function* (opts: {
readonly grain: string
readonly periodStart: Date
readonly dataset?: string
readonly tier?: string
readonly client?: string
readonly source?: string
}) {
return yield* Effect.tryPromise({
try: () =>
db
.select()
.from(providerStat)
.where(
and(
eq(providerStat.grain, opts.grain),
eq(providerStat.period_start, opts.periodStart),
eq(providerStat.dataset, opts.dataset ?? "zen"),
eq(providerStat.tier, opts.tier ?? "all"),
eq(providerStat.client, opts.client ?? "all"),
eq(providerStat.source, opts.source ?? "all"),
),
),
catch: (cause) => DatabaseError.make({ cause }),
})
})
const upsert = Effect.fn("ProviderStatRepo.upsert")(function* (rows: ProviderStatRow[]) {
yield* Effect.forEach(
chunks(rows, UPSERT_CHUNK_SIZE),
(chunk) =>
Effect.tryPromise({
try: () =>
db
.insert(providerStat)
.values(chunk)
.onDuplicateKeyUpdate({
set: {
period_end: inserted("period_end"),
sessions: inserted("sessions"),
requests: inserted("requests"),
input_tokens: inserted("input_tokens"),
output_tokens: inserted("output_tokens"),
reasoning_tokens: inserted("reasoning_tokens"),
cache_read_tokens: inserted("cache_read_tokens"),
total_tokens: inserted("total_tokens"),
input_cost_microcents: inserted("input_cost_microcents"),
output_cost_microcents: inserted("output_cost_microcents"),
total_cost_microcents: inserted("total_cost_microcents"),
avg_duration_ms: inserted("avg_duration_ms"),
p50_duration_ms: inserted("p50_duration_ms"),
p95_duration_ms: inserted("p95_duration_ms"),
avg_ttfb_ms: inserted("avg_ttfb_ms"),
p50_ttfb_ms: inserted("p50_ttfb_ms"),
p95_ttfb_ms: inserted("p95_ttfb_ms"),
avg_output_tps: inserted("avg_output_tps"),
success_count: inserted("success_count"),
error_count: inserted("error_count"),
sample_count: inserted("sample_count"),
market_share_tokens: inserted("market_share_tokens"),
market_share_requests: inserted("market_share_requests"),
market_share_sessions: inserted("market_share_sessions"),
rank_by_tokens: inserted("rank_by_tokens"),
rank_by_requests: inserted("rank_by_requests"),
rank_by_sessions: inserted("rank_by_sessions"),
rank_by_cost: inserted("rank_by_cost"),
},
}),
catch: (cause) => DatabaseError.make({ cause }),
}),
{ discard: true },
)
})
return ProviderStatRepo.of({ listDaily, listByPeriod, upsert })
}),
)
}
export function rowsFromAggregates(aggregates: ProviderStatAggregate[]) {
return rankRowsWithMarketShare([
...synthesizeAllTierRows(
collapseRows(aggregates.filter((item) => item.grain === "week").map(toRow), dimensionKey),
dimensionKey,
),
...synthesizeAllTierRows(
collapseRows(aggregates.filter((item) => item.grain === "day").map(toRow), dimensionKey),
dimensionKey,
),
])
}
function toRow(data: ProviderStatAggregate): ProviderStatRow {
return {
...toStatBaseRow(data),
provider: data.provider,
}
}
function dimensionKey(row: ProviderStatRow) {
return row.provider
}

View file

@ -0,0 +1,233 @@
import { sql } from "drizzle-orm"
export const UPSERT_CHUNK_SIZE = 500
export type StatGrain = "day" | "week"
export type StatBaseAggregate = {
grain: StatGrain
period_start: Date
period_end: Date
dataset: string
tier: string
sessions: number
requests: number
input_tokens: number
output_tokens: number
reasoning_tokens: number
cache_read_tokens: number
total_tokens: number
input_cost_microcents: number
output_cost_microcents: number
total_cost_microcents: number
avg_duration_ms: number | null
p50_duration_ms: number | null
p95_duration_ms: number | null
avg_ttfb_ms: number | null
p50_ttfb_ms: number | null
p95_ttfb_ms: number | null
avg_output_tps: number | null
success_count: number
error_count: number
sample_count: number
}
export type StatBaseRow = {
grain: string
period_start: Date
period_end: Date
dataset?: string
tier?: string
client?: string
source?: string
sessions?: number
requests?: number
input_tokens?: number
output_tokens?: number
reasoning_tokens?: number
cache_read_tokens?: number
total_tokens?: number
input_cost_microcents?: number
output_cost_microcents?: number
total_cost_microcents?: number
avg_duration_ms?: number | null
p50_duration_ms?: number | null
p95_duration_ms?: number | null
avg_ttfb_ms?: number | null
p50_ttfb_ms?: number | null
p95_ttfb_ms?: number | null
avg_output_tps?: number | null
success_count?: number
error_count?: number
sample_count?: number
}
export function toStatBaseRow(data: StatBaseAggregate) {
return {
grain: data.grain,
period_start: data.period_start,
period_end: data.period_end,
dataset: data.dataset,
tier: data.tier,
client: "all",
source: "all",
sessions: data.sessions,
requests: data.requests,
input_tokens: data.input_tokens,
output_tokens: data.output_tokens,
reasoning_tokens: data.reasoning_tokens,
cache_read_tokens: data.cache_read_tokens,
total_tokens: data.total_tokens,
input_cost_microcents: data.input_cost_microcents,
output_cost_microcents: data.output_cost_microcents,
total_cost_microcents: data.total_cost_microcents,
avg_duration_ms: data.avg_duration_ms,
p50_duration_ms: data.p50_duration_ms,
p95_duration_ms: data.p95_duration_ms,
avg_ttfb_ms: data.avg_ttfb_ms,
p50_ttfb_ms: data.p50_ttfb_ms,
p95_ttfb_ms: data.p95_ttfb_ms,
avg_output_tps: data.avg_output_tps,
success_count: data.success_count,
error_count: data.error_count,
sample_count: data.sample_count,
}
}
export function synthesizeAllTierRows<T extends StatBaseRow>(rows: T[], dimensionKey: (row: T) => string) {
return [
...rows,
...Object.values(
rows.reduce<Record<string, T>>((result, row) => {
const key = [
row.grain,
row.period_start.toISOString(),
row.dataset,
row.client,
row.source,
dimensionKey(row),
].join("\u0000")
result[key] = result[key] ? combineRows(result[key], row) : { ...row, tier: "all" }
return result
}, {}),
),
]
}
export function collapseRows<T extends StatBaseRow>(rows: T[], dimensionKey: (row: T) => string) {
return Object.values(
rows.reduce<Record<string, T>>((result, row) => {
const key = [
row.grain,
row.period_start.toISOString(),
row.dataset,
row.tier,
row.client,
row.source,
dimensionKey(row),
].join("\u0000")
result[key] = result[key] ? combineRows(result[key], row) : row
return result
}, {}),
)
}
export function combineRows<T extends StatBaseRow>(left: T, right: T): T {
return {
...left,
period_end: right.period_end > left.period_end ? right.period_end : left.period_end,
sessions: (left.sessions ?? 0) + (right.sessions ?? 0),
requests: (left.requests ?? 0) + (right.requests ?? 0),
input_tokens: (left.input_tokens ?? 0) + (right.input_tokens ?? 0),
output_tokens: (left.output_tokens ?? 0) + (right.output_tokens ?? 0),
reasoning_tokens: (left.reasoning_tokens ?? 0) + (right.reasoning_tokens ?? 0),
cache_read_tokens: (left.cache_read_tokens ?? 0) + (right.cache_read_tokens ?? 0),
total_tokens: (left.total_tokens ?? 0) + (right.total_tokens ?? 0),
input_cost_microcents: (left.input_cost_microcents ?? 0) + (right.input_cost_microcents ?? 0),
output_cost_microcents: (left.output_cost_microcents ?? 0) + (right.output_cost_microcents ?? 0),
total_cost_microcents: (left.total_cost_microcents ?? 0) + (right.total_cost_microcents ?? 0),
avg_duration_ms: weightedAverage(left.avg_duration_ms, left.requests, right.avg_duration_ms, right.requests),
p50_duration_ms: null,
p95_duration_ms: null,
avg_ttfb_ms: weightedAverage(left.avg_ttfb_ms, left.requests, right.avg_ttfb_ms, right.requests),
p50_ttfb_ms: null,
p95_ttfb_ms: null,
avg_output_tps: weightedAverage(left.avg_output_tps, left.requests, right.avg_output_tps, right.requests),
success_count: (left.success_count ?? 0) + (right.success_count ?? 0),
error_count: (left.error_count ?? 0) + (right.error_count ?? 0),
sample_count: (left.sample_count ?? 0) + (right.sample_count ?? 0),
}
}
export function statPeriodKey(row: StatBaseRow) {
return [row.grain, row.period_start.toISOString(), row.dataset, row.tier, row.client, row.source].join("\u0000")
}
export function rankBy<T extends StatBaseRow>(rows: T[], value: (row: T) => number) {
return new Map(rows.toSorted((a, b) => value(b) - value(a)).map((row, index) => [row, index + 1]))
}
export function rankRowsWithMarketShare<T extends StatBaseRow>(rows: T[]) {
return Object.values(
rows.reduce<Record<string, T[]>>((result, row) => {
const key = statPeriodKey(row)
result[key] = [...(result[key] ?? []), row]
return result
}, {}),
).flatMap((group) => {
const tokens = group.reduce((sum, row) => sum + (row.total_tokens ?? 0), 0)
const requests = group.reduce((sum, row) => sum + (row.requests ?? 0), 0)
const sessions = group.reduce((sum, row) => sum + (row.sessions ?? 0), 0)
const tokenRanks = rankBy(group, (row) => row.total_tokens ?? 0)
const requestRanks = rankBy(group, (row) => row.requests ?? 0)
const sessionRanks = rankBy(group, (row) => row.sessions ?? 0)
const costRanks = rankBy(group, (row) => row.total_cost_microcents ?? 0)
return group.map((row) => ({
...row,
market_share_tokens: share(row.total_tokens, tokens),
market_share_requests: share(row.requests, requests),
market_share_sessions: share(row.sessions, sessions),
rank_by_tokens: tokenRanks.get(row) ?? null,
rank_by_requests: requestRanks.get(row) ?? null,
rank_by_sessions: sessionRanks.get(row) ?? null,
rank_by_cost: costRanks.get(row) ?? null,
}))
})
}
export function share(value: number | null | undefined, total: number) {
if (total <= 0) return null
return Number(((value ?? 0) / total).toFixed(6))
}
export function chunks<T>(items: T[], size: number) {
return Array.from({ length: Math.ceil(items.length / size) }, (_, index) =>
items.slice(index * size, (index + 1) * size),
)
}
export function inserted(column: string) {
return sql.raw(`values(\`${column}\`)`)
}
export function weightedAverage(
left: number | null | undefined,
leftWeight = 0,
right: number | null | undefined,
rightWeight = 0,
) {
const totalWeight =
(left === null || left === undefined ? 0 : leftWeight) + (right === null || right === undefined ? 0 : rightWeight)
if (totalWeight === 0) return null
return Number((((left ?? 0) * leftWeight + (right ?? 0) * rightWeight) / totalWeight).toFixed(2))
}
export function normalizeTier(value: string) {
if (value === "Paid") return "Zen"
return value
}
export function normalizeCountry(value: string | undefined) {
if (!value || value.length !== 2) return "ZZ"
return value.toUpperCase()
}

View file

@ -0,0 +1,11 @@
export * as Athena from "./athena"
export * as AppConfig from "./config"
export * as Database from "./database"
export * as GeoStat from "./domain/geo"
export * as StatsHome from "./domain/home"
export * as Inference from "./domain/inference"
export * as ModelStat from "./domain/model"
export * as ProviderStat from "./domain/provider"
export * as Stat from "./domain/stat"
export * as Runtime from "./runtime"
export * as StatSync from "./stat-sync"

View file

@ -0,0 +1,4 @@
import { Effect } from "effect"
import { layer, migrate } from "./database"
await Effect.runPromise(migrate().pipe(Effect.provide(layer)))

28
packages/stats/core/src/resource.d.ts vendored Normal file
View file

@ -0,0 +1,28 @@
import "sst/resource"
declare module "sst/resource" {
export interface Resource {
InferenceEvent: {
catalog: string
database: string
region: string
table: string
tableBucket: string
type: "sst.sst.Linkable"
workgroup: string
}
StatsSyncConfig: {
dataset: string
type: "sst.sst.Linkable"
}
StatsDatabase: {
database: string
host: string
password: string
port: number
type: "sst.sst.Linkable"
url: string
username: string
}
}
}

View file

@ -0,0 +1,14 @@
import { Layer, ManagedRuntime } from "effect"
import { AppConfig } from "./config"
import { layer as databaseLayer } from "./database"
import { GeoStatRepo } from "./domain/geo"
import { ModelStatRepo } from "./domain/model"
import { ProviderStatRepo } from "./domain/provider"
const repoLayer = Layer.mergeAll(ModelStatRepo.layer, ProviderStatRepo.layer, GeoStatRepo.layer).pipe(
Layer.provide(databaseLayer),
)
export const layer = Layer.mergeAll(AppConfig.layer, databaseLayer, repoLayer)
export const runtime = ManagedRuntime.make(layer)
export type RuntimeServices = ManagedRuntime.ManagedRuntime.Services<typeof runtime>

View file

@ -0,0 +1,88 @@
import { DateTime, Effect } from "effect"
import { Resource } from "sst/resource"
import { Athena, AthenaQueryError, AthenaQueryTimeoutError } from "./athena"
import { DatabaseError } from "./database"
import { GeoStatRepo, rowsFromAggregates as geoRowsFromAggregates } from "./domain/geo"
import { buildStatsQuery, toGeoAggregate, toModelAggregate, toProviderAggregate } from "./domain/inference"
import { ModelStatRepo, rowsFromAggregates as modelRowsFromAggregates } from "./domain/model"
import { ProviderStatRepo, rowsFromAggregates as providerRowsFromAggregates } from "./domain/provider"
const DATALAKE_INGESTION_LAG_MS = 5 * 60_000
export type SyncStatsResult = { ok: true; rows: number; startedAt: string; periodStart: string; periodEnd: string }
export type SyncStatsError = AthenaQueryError | AthenaQueryTimeoutError | DatabaseError
export const syncStats: () => Effect.Effect<
SyncStatsResult,
SyncStatsError,
Athena | ModelStatRepo | ProviderStatRepo | GeoStatRepo
> = Effect.fn("StatSync.sync")(function* () {
const startedAt = yield* DateTime.nowAsDate
const periodEnd = new Date(Math.floor((startedAt.getTime() - DATALAKE_INGESTION_LAG_MS) / 60_000) * 60_000)
const periodStart = new Date(
Date.UTC(periodEnd.getUTCFullYear(), periodEnd.getUTCMonth(), periodEnd.getUTCDate() - 6),
)
const athena = yield* Athena
const modelStats = yield* ModelStatRepo
const providerStats = yield* ProviderStatRepo
const geoStats = yield* GeoStatRepo
yield* logRuntimeCheck()
const [modelAggregates, providerAggregates, geoAggregates] = yield* Effect.all(
[
athena
.query(buildStatsQuery(periodStart, periodEnd, "model"))
.pipe(Effect.map((rows) => rows.flatMap(toModelAggregate))),
athena
.query(buildStatsQuery(periodStart, periodEnd, "provider"))
.pipe(Effect.map((rows) => rows.flatMap(toProviderAggregate))),
athena
.query(buildStatsQuery(periodStart, periodEnd, "geo"))
.pipe(Effect.map((rows) => rows.flatMap(toGeoAggregate))),
],
{ concurrency: "unbounded" },
)
const modelRows = modelRowsFromAggregates(modelAggregates)
const providerRows = providerRowsFromAggregates(providerAggregates)
const geoRows = geoRowsFromAggregates(geoAggregates)
yield* Effect.all([modelStats.upsert(modelRows), providerStats.upsert(providerRows), geoStats.upsert(geoRows)], {
concurrency: "unbounded",
discard: true,
})
yield* Effect.logInfo("stats sync complete").pipe(
Effect.annotateLogs({
startedAt: startedAt.toISOString(),
periodStart: periodStart.toISOString(),
periodEnd: periodEnd.toISOString(),
rows: modelRows.length,
providerRows: providerRows.length,
geoRows: geoRows.length,
stage: Resource.App.stage,
}),
)
return {
ok: true,
rows: modelRows.length,
startedAt: startedAt.toISOString(),
periodStart: periodStart.toISOString(),
periodEnd: periodEnd.toISOString(),
}
})
function logRuntimeCheck() {
return Effect.logInfo("athena stats runtime check").pipe(
Effect.annotateLogs({
catalog: Resource.InferenceEvent.catalog,
database: Resource.InferenceEvent.database,
dataset: Resource.StatsSyncConfig.dataset,
table: Resource.InferenceEvent.table,
workgroup: Resource.InferenceEvent.workgroup,
region: Resource.InferenceEvent.region,
stage: Resource.App.stage,
}),
)
}

10
packages/stats/core/sst-env.d.ts vendored Normal file
View file

@ -0,0 +1,10 @@
/* This file is auto-generated by SST. Do not edit. */
/* tslint:disable */
/* eslint-disable */
/* deno-fmt-ignore-file */
/* biome-ignore-all lint: auto-generated */
/// <reference path="../../../sst-env.d.ts" />
import "sst"
export {}

View file

@ -0,0 +1,11 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/node22/tsconfig.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true,
"types": ["bun", "node"]
}
}

View file

@ -0,0 +1,43 @@
FROM oven/bun:1.3.14-alpine
WORKDIR /app
ENV NODE_ENV=production
ENV BUN_RUNTIME_TRANSPILER_CACHE_PATH=0
COPY package.json bun.lock ./
COPY patches ./patches
COPY packages/app/package.json ./packages/app/package.json
COPY packages/console/app/package.json ./packages/console/app/package.json
COPY packages/console/core/package.json ./packages/console/core/package.json
COPY packages/console/function/package.json ./packages/console/function/package.json
COPY packages/console/mail/package.json ./packages/console/mail/package.json
COPY packages/console/resource/package.json ./packages/console/resource/package.json
COPY packages/core/package.json ./packages/core/package.json
COPY packages/desktop/package.json ./packages/desktop/package.json
COPY packages/effect-drizzle-sqlite/package.json ./packages/effect-drizzle-sqlite/package.json
COPY packages/enterprise/package.json ./packages/enterprise/package.json
COPY packages/function/package.json ./packages/function/package.json
COPY packages/http-recorder/package.json ./packages/http-recorder/package.json
COPY packages/llm/package.json ./packages/llm/package.json
COPY packages/opencode/package.json ./packages/opencode/package.json
COPY packages/plugin/package.json ./packages/plugin/package.json
COPY packages/script/package.json ./packages/script/package.json
COPY packages/sdk/js/package.json ./packages/sdk/js/package.json
COPY packages/slack/package.json ./packages/slack/package.json
COPY packages/stats/app/package.json ./packages/stats/app/package.json
COPY packages/stats/core/package.json ./packages/stats/core/package.json
COPY packages/stats/server/package.json ./packages/stats/server/package.json
COPY packages/storybook/package.json ./packages/storybook/package.json
COPY packages/ui/package.json ./packages/ui/package.json
COPY packages/web/package.json ./packages/web/package.json
RUN bun install --frozen-lockfile --production --ignore-scripts
COPY packages ./packages
WORKDIR /app/packages/stats/server
EXPOSE 3000
CMD ["bun", "src/server.ts"]

View file

@ -0,0 +1,33 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/stats-server",
"version": "1.14.50",
"private": true,
"type": "module",
"license": "MIT",
"main": "./src/server.ts",
"exports": {
".": "./src/server.ts"
},
"scripts": {
"start": "bun src/server.ts",
"typecheck": "tsgo --noEmit"
},
"dependencies": {
"@aws-sdk/client-firehose": "3.933.0",
"@effect/platform-node": "catalog:",
"@opencode-ai/stats-core": "workspace:*",
"effect": "catalog:",
"sst": "catalog:"
},
"devDependencies": {
"@tsconfig/node22": "catalog:",
"@types/bun": "catalog:",
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:"
},
"engines": {
"node": ">=22"
}
}

View file

@ -0,0 +1,110 @@
import { Buffer } from "node:buffer"
import { FirehoseClient, PutRecordBatchCommand } from "@aws-sdk/client-firehose"
import { Effect, Layer, Schema } from "effect"
import * as Context from "effect/Context"
import { Resource } from "sst/resource"
const MAX_FIREHOSE_BATCH_SIZE = 500
const MAX_FIREHOSE_ATTEMPTS = 3
const LAKE_TYPE = /^([A-Za-z0-9_]+)\.([A-Za-z0-9_]+)$/
type IngestEvent = Record<string, unknown>
type RoutedEvent = IngestEvent & { _lake_database: string; _lake_table: string; _lake_operation: "insert" }
type FirehoseRecord = { Data: Uint8Array }
export class IngestError extends Schema.TaggedErrorClass<IngestError>()("IngestError", {
message: Schema.String,
failed: Schema.Number,
cause: Schema.optional(Schema.Defect),
}) {}
export declare namespace Ingest {
export interface Service {
readonly write: (events: IngestEvent[]) => Effect.Effect<{ records: number }, IngestError>
}
}
export class Ingest extends Context.Service<Ingest, Ingest.Service>()("@opencode/stats/Ingest") {
static readonly layer: Layer.Layer<Ingest> = Layer.effect(
Ingest,
Effect.sync(() => {
const client = new FirehoseClient({})
const write = Effect.fn("Ingest.write")(function* (events: IngestEvent[]) {
if (events.length === 0) return { records: 0 }
const records = events.map(routeEvent).filter((event): event is RoutedEvent => Boolean(event))
if (records.length !== events.length) {
return yield* new IngestError({
message: "Unsupported lake event type",
failed: events.length - records.length,
})
}
const failed = (
yield* Effect.all(
chunks(
records.map((event) => ({ Data: Buffer.from(JSON.stringify(event)) })),
MAX_FIREHOSE_BATCH_SIZE,
).map((batch) => putRecords(client, Resource.LakeIngestConfig.streamName, batch)),
{ concurrency: 8 },
)
).reduce((sum, item) => sum + item, 0)
if (failed > 0) {
return yield* new IngestError({ message: "Failed to ingest all lake records", failed })
}
return { records: records.length }
})
return Ingest.of({ write })
}),
)
}
const putRecords: (
client: FirehoseClient,
streamName: string,
records: FirehoseRecord[],
attempt?: number,
) => Effect.Effect<number, IngestError> = Effect.fn("Ingest.putRecords")(function* (
client,
streamName,
records,
attempt = 1,
) {
const result = yield* Effect.tryPromise({
try: () => client.send(new PutRecordBatchCommand({ DeliveryStreamName: streamName, Records: records })),
catch: (cause) => new IngestError({ message: "Failed to write lake records to Firehose", failed: records.length, cause }),
})
const failed =
result.RequestResponses?.flatMap((item, index) => {
const record = records[index]
if (!item.ErrorCode || !record) return []
return [record]
}) ?? []
if (failed.length === 0) return 0
if (attempt >= MAX_FIREHOSE_ATTEMPTS) return failed.length
yield* Effect.sleep(`${250 * 2 ** (attempt - 1)} millis`)
return yield* putRecords(client, streamName, failed, attempt + 1)
})
function routeEvent(event: IngestEvent): RoutedEvent | undefined {
if (typeof event._datalake_key !== "string") return
const match = event._datalake_key.match(LAKE_TYPE)
if (!match?.[1] || !match[2]) return
return {
...Object.fromEntries(Object.entries(event).filter(([key]) => key !== "_datalake_key")),
_lake_database: match[1],
_lake_table: match[2],
_lake_operation: "insert" as const,
}
}
function chunks<T>(items: T[], size: number) {
return Array.from({ length: Math.ceil(items.length / size) }, (_, index) =>
items.slice(index * size, (index + 1) * size),
)
}

11
packages/stats/server/src/resource.d.ts vendored Normal file
View file

@ -0,0 +1,11 @@
import "sst/resource"
declare module "sst/resource" {
export interface Resource {
LakeIngestConfig: {
secret: string
streamName: string
type: "sst.sst.Linkable"
}
}
}

View file

@ -0,0 +1,62 @@
import { Buffer } from "node:buffer"
import { timingSafeEqual } from "node:crypto"
import { Effect, Schema } from "effect"
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
import { Resource } from "sst/resource"
import { Ingest } from "./ingest"
import { isShuttingDown } from "./shutdown"
const IngestPayload = Schema.Struct({
events: Schema.optional(Schema.Unknown),
})
export const Routes = HttpRouter.use((router) =>
Effect.gen(function* () {
const ingestService = yield* Ingest
yield* Effect.all(
[
router.add("GET", "/health", () => json(200, { ok: true })),
router.add("GET", "/ready", () => json(isShuttingDown() ? 503 : 200, { ok: !isShuttingDown() })),
router.add("POST", "/", ingest(ingestService)),
],
{ discard: true },
)
}),
)
const ingest = (ingestService: Ingest.Service) => Effect.gen(function* () {
const request = yield* HttpServerRequest.HttpServerRequest
if (!isAuthorized(request.headers)) return yield* json(401, { ok: false, error: "Unauthorized" })
const payload = yield* HttpServerRequest.schemaBodyJson(IngestPayload).pipe(
Effect.match({
onFailure: () => undefined,
onSuccess: (value) => value,
}),
)
if (!payload) return yield* json(400, { ok: false, error: "Invalid JSON body" })
const events = Array.isArray(payload.events) ? payload.events.filter(isRecord) : []
if (events.length === 0) return yield* json(202, { ok: true, records: 0 })
return yield* ingestService.write(events).pipe(
Effect.flatMap((result) => json(202, { ok: true, records: result.records })),
Effect.catchTag("IngestError", (error) => json(502, { ok: false, records: events.length, failed: error.failed })),
)
})
function isAuthorized(headers: Record<string, string | undefined>) {
const actual = Buffer.from(headers.authorization ?? headers.Authorization ?? "")
const expected = Buffer.from(`Bearer ${Resource.LakeIngestConfig.secret}`)
if (actual.length !== expected.length) return false
return timingSafeEqual(actual, expected)
}
function isRecord(item: unknown): item is Record<string, unknown> {
return Boolean(item) && typeof item === "object" && !Array.isArray(item)
}
function json(status: number, body: Record<string, unknown>) {
return HttpServerResponse.json(body, { status }).pipe(Effect.orDie)
}

View file

@ -0,0 +1,28 @@
import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"
import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
import { Config, Layer } from "effect"
import { HttpRouter } from "effect/unstable/http"
import { createServer } from "node:http"
import { Ingest } from "./ingest"
import { Routes } from "./router"
import { registerShutdownSignalHandlers } from "./shutdown"
registerShutdownSignalHandlers()
const ServerLive = NodeHttpServer.layerConfig(
() => createServer(),
Config.all({
port: Config.number("PORT").pipe(Config.withDefault(3000)),
host: Config.string("HOST").pipe(Config.withDefault("0.0.0.0")),
}),
)
const runtimeLayer = Ingest.layer
const programLayer = Routes.pipe(Layer.provide(runtimeLayer))
const main = Layer.launch(
HttpRouter.serve(programLayer, {
disableLogger: true,
}).pipe(Layer.provideMerge(ServerLive)),
)
NodeRuntime.runMain(main, { disableErrorReporting: true })

View file

@ -0,0 +1,17 @@
let shuttingDown = false
let signalHandlersRegistered = false
export function isShuttingDown() {
return shuttingDown
}
export function registerShutdownSignalHandlers() {
if (signalHandlersRegistered) return
signalHandlersRegistered = true
process.once("SIGTERM", markShuttingDown)
process.once("SIGINT", markShuttingDown)
}
function markShuttingDown() {
shuttingDown = true
}

View file

@ -0,0 +1,22 @@
import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
import { Athena } from "@opencode-ai/stats-core/athena"
import { layer as statsLayer } from "@opencode-ai/stats-core/runtime"
import { syncStats } from "@opencode-ai/stats-core/stat-sync"
import { Cause, Effect, Layer, Schedule } from "effect"
const SYNC_INTERVAL = "1 hour"
const runtimeLayer = Layer.mergeAll(statsLayer, Athena.layer)
const syncPass = syncStats().pipe(
Effect.catchCause((cause) =>
Effect.logWarning("stats sync failed").pipe(Effect.annotateLogs({ cause: Cause.pretty(cause) })),
),
)
const daemon = Effect.logInfo("stats sync daemon started").pipe(
Effect.andThen(syncPass.pipe(Effect.repeat(Schedule.fixed(SYNC_INTERVAL)))),
Effect.forkScoped,
)
NodeRuntime.runMain(Layer.launch(Layer.effectDiscard(daemon).pipe(Layer.provide(runtimeLayer))), {
disableErrorReporting: true,
})

10
packages/stats/server/sst-env.d.ts vendored Normal file
View file

@ -0,0 +1,10 @@
/* This file is auto-generated by SST. Do not edit. */
/* tslint:disable */
/* eslint-disable */
/* deno-fmt-ignore-file */
/* biome-ignore-all lint: auto-generated */
/// <reference path="../../../sst-env.d.ts" />
import "sst"
export {}

View file

@ -0,0 +1,12 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/node22/tsconfig.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true,
"types": ["bun", "node"]
},
"include": ["src", "../core/src/resource.d.ts"]
}

56
sst-env.d.ts vendored
View file

@ -26,14 +26,6 @@ declare module "sst" {
"AuthApi": import("@cloudflare/workers-types").Service
"AuthStorage": import("@cloudflare/workers-types").KVNamespace
"Bucket": import("@cloudflare/workers-types").R2Bucket
"CLOUDFLARE_API_TOKEN": {
"type": "sst.sst.Secret"
"value": string
}
"CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
"type": "sst.sst.Secret"
"value": string
}
"Console": {
"type": "sst.cloudflare.SolidStart"
"url": string
@ -99,6 +91,37 @@ declare module "sst" {
"type": "random.index/randomPassword.RandomPassword"
"value": string
}
"InferenceEvent": {
"catalog": string
"database": string
"region": string
"table": string
"tableBucket": string
"type": "sst.sst.Linkable"
"workgroup": string
}
"LakeIngest": {
"secret": string
"type": "sst.sst.Linkable"
"url": string
}
"LakeIngestConfig": {
"secret": string
"streamName": string
"type": "sst.sst.Linkable"
}
"LakeIngestSecret": {
"type": "random.index/randomPassword.RandomPassword"
"value": string
}
"LakeIngestService": {
"service": string
"type": "sst.aws.Service"
"url": string
}
"LakeVpc": {
"type": "sst.aws.Vpc"
}
"LogProcessor": import("@cloudflare/workers-types").Service
"R2AccessKey": {
"type": "sst.sst.Secret"
@ -133,6 +156,23 @@ declare module "sst" {
"value": string
}
"Stat": import("@cloudflare/workers-types").Service
"StatsDatabase": {
"database": string
"host": string
"password": string
"port": number
"type": "sst.sst.Linkable"
"url": string
"username": string
}
"StatsSyncConfig": {
"dataset": string
"type": "sst.sst.Linkable"
}
"StatsSyncService": {
"service": string
"type": "sst.aws.Service"
}
"Teams": {
"type": "sst.cloudflare.SolidStart"
"url": string

View file

@ -2,12 +2,26 @@
export default $config({
app(input) {
const deployAws = input.stage === "production" || input.stage === "dev" || input.stage === "adam"
return {
name: "opencode",
removal: input?.stage === "production" ? "retain" : "remove",
protect: ["production"].includes(input?.stage),
home: "cloudflare",
providers: {
...(deployAws
? {
aws: {
version: "7.30.0",
region: "us-east-1",
profile: process.env.GITHUB_ACTIONS
? undefined
: input.stage === "production"
? "opencode-production"
: "opencode-dev",
},
}
: {}),
stripe: {
version: "0.0.28",
apiKey: process.env.STRIPE_SECRET_KEY!,
@ -19,7 +33,12 @@ export default $config({
}
},
async run() {
const stage = await import("./infra/stage.js")
await import("./infra/app.js")
if (stage.deployAws) {
await import("./infra/lake.js")
await import("./infra/stats.js")
}
const { stat } = await import("./infra/console.js")
await import("./infra/enterprise.js")
if ($app.stage === "production" || $app.stage === "vimtor") {
@ -28,6 +47,8 @@ export default $config({
return {
StatWorkerUrl: stat.url,
// StatsUrl: stats.app.url,
...(stage.githubActionsDeployRoleArn ? { GithubActionsDeployRoleArn: stage.githubActionsDeployRoleArn } : {}),
}
},
})