mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
c3b6284955
Add cost tracking for LLM chat interactions with microdollar precision. ## Changes - Add `chatcost` package for per-message cost calculation using `shopspring/decimal` for intermediate arithmetic - **Ceil rounding policy**: fractional micros round UP to next whole micro (applied once after summing all components) - Database migration: `total_cost_micros` BIGINT column with historical backfill and `created_at` index - API endpoints: per-user cost summary and admin rollup under `/api/experimental/chats/cost/` - SDK types: `ChatCostSummary`, `ChatCostModelBreakdown`, `ChatCostUserRollup` - Fix `modeloptionsgen` to handle `decimal.Decimal` as opaque numeric type - Update frontend pricing test fixtures for string decimal types ## Design decisions - `NULL` = unpriced (no matching model config), `0` = free - Reasoning tokens included in output tokens (no double-counting) - Integer microdollars (BIGINT) for storage and API responses - Price config uses `decimal.Decimal` for exact parsing; totals use `int64` Frontend: #23037
244 lines
9.4 KiB
YAML
244 lines
9.4 KiB
YAML
# sqlc is used to generate types from sql schema language.
|
|
# It was chosen to ensure type-safety when interacting with
|
|
# the database.
|
|
version: "2"
|
|
cloud:
|
|
# This is the static ID for the coder project.
|
|
project: "01HEP08N3WKWRFZT3ZZ9Q37J8X"
|
|
sql:
|
|
- schema: "./dump.sql"
|
|
queries: "./queries"
|
|
engine: "postgresql"
|
|
# This only works if you are running a local postgres database with the
|
|
# schema loaded and migrations run. Run `make sqlc-vet` to run the linter.
|
|
database:
|
|
uri: "${SQLC_DATABASE_URL}"
|
|
analyzer:
|
|
database: false
|
|
rules:
|
|
- sqlc/db-prepare
|
|
- do-not-use-public-schema-in-queries
|
|
gen:
|
|
go:
|
|
package: "database"
|
|
out: "./queries"
|
|
emit_interface: true
|
|
emit_json_tags: true
|
|
emit_db_tags: true
|
|
emit_enum_valid_method: true
|
|
emit_all_enum_values: true
|
|
overrides:
|
|
- column: "api_keys.scopes"
|
|
go_type:
|
|
type: "APIKeyScopes"
|
|
- column: "api_keys.allow_list"
|
|
go_type:
|
|
type: "AllowList"
|
|
- db_type: "agent_id_name_pair"
|
|
go_type:
|
|
type: "AgentIDNamePair"
|
|
# Used in 'CustomRoles' query to filter by (name,organization_id)
|
|
- db_type: "name_organization_pair"
|
|
go_type:
|
|
type: "NameOrganizationPair"
|
|
- db_type: "tagset"
|
|
go_type:
|
|
type: "StringMap"
|
|
- column: "custom_roles.site_permissions"
|
|
go_type:
|
|
type: "CustomRolePermissions"
|
|
- column: "custom_roles.org_permissions"
|
|
go_type:
|
|
type: "CustomRolePermissions"
|
|
- column: "custom_roles.user_permissions"
|
|
go_type:
|
|
type: "CustomRolePermissions"
|
|
- column: "custom_roles.member_permissions"
|
|
go_type:
|
|
type: "CustomRolePermissions"
|
|
- column: "provisioner_daemons.tags"
|
|
go_type:
|
|
type: "StringMap"
|
|
- column: "provisioner_keys.tags"
|
|
go_type:
|
|
type: "StringMap"
|
|
- column: "provisioner_jobs.tags"
|
|
go_type:
|
|
type: "StringMap"
|
|
- column: "users.rbac_roles"
|
|
go_type: "github.com/lib/pq.StringArray"
|
|
- column: "templates.user_acl"
|
|
go_type:
|
|
type: "TemplateACL"
|
|
- column: "templates.group_acl"
|
|
go_type:
|
|
type: "TemplateACL"
|
|
- column: "template_with_names.user_acl"
|
|
go_type:
|
|
type: "TemplateACL"
|
|
- column: "template_with_names.group_acl"
|
|
go_type:
|
|
type: "TemplateACL"
|
|
- column: "template_usage_stats.app_usage_mins"
|
|
go_type:
|
|
type: "StringMapOfInt"
|
|
- column: "tasks_with_status.workspace_user_acl"
|
|
go_type:
|
|
type: "WorkspaceACL"
|
|
- column: "tasks_with_status.workspace_group_acl"
|
|
go_type:
|
|
type: "WorkspaceACL"
|
|
- column: "workspaces.user_acl"
|
|
go_type:
|
|
type: "WorkspaceACL"
|
|
- column: "workspaces.group_acl"
|
|
go_type:
|
|
type: "WorkspaceACL"
|
|
- column: "workspaces_expanded.user_acl"
|
|
go_type:
|
|
type: "WorkspaceACL"
|
|
- column: "workspaces_expanded.group_acl"
|
|
go_type:
|
|
type: "WorkspaceACL"
|
|
- column: "workspaces_expanded.user_acl_display_info"
|
|
go_type:
|
|
type: "WorkspaceACLDisplayInfo"
|
|
- column: "workspaces_expanded.group_acl_display_info"
|
|
go_type:
|
|
type: "WorkspaceACLDisplayInfo"
|
|
- column: "notification_templates.actions"
|
|
go_type:
|
|
type: "[]byte"
|
|
- column: "notification_messages.payload"
|
|
go_type:
|
|
type: "[]byte"
|
|
- column: "provisioner_job_stats.*_secs"
|
|
go_type:
|
|
type: "float64"
|
|
- column: "user_links.claims"
|
|
go_type:
|
|
type: "UserLinkClaims"
|
|
# Workaround for sqlc not interpreting the left join correctly.
|
|
- column: "tasks_with_status.workspace_build_number"
|
|
go_type: "database/sql.NullInt32"
|
|
- column: "tasks_with_status.status"
|
|
go_type:
|
|
type: "TaskStatus"
|
|
- column: "tasks_with_status.workspace_agent_lifecycle_state"
|
|
go_type:
|
|
type: "NullWorkspaceAgentLifecycleState"
|
|
- column: "tasks_with_status.workspace_app_health"
|
|
go_type:
|
|
type: "NullWorkspaceAppHealth"
|
|
# Workaround for sqlc not interpreting the left join correctly
|
|
# in the combined telemetry query.
|
|
- column: "task_event_data.start_build_number"
|
|
go_type: "database/sql.NullInt32"
|
|
- column: "task_event_data.stop_build_created_at"
|
|
go_type: "database/sql.NullTime"
|
|
- column: "task_event_data.stop_build_reason"
|
|
go_type:
|
|
type: "NullBuildReason"
|
|
- column: "task_event_data.start_build_created_at"
|
|
go_type: "database/sql.NullTime"
|
|
- column: "task_event_data.start_build_reason"
|
|
go_type:
|
|
type: "NullBuildReason"
|
|
- column: "task_event_data.last_working_status_at"
|
|
go_type: "database/sql.NullTime"
|
|
- column: "task_event_data.first_status_after_resume_at"
|
|
go_type: "database/sql.NullTime"
|
|
- db_type: "pg_catalog.numeric"
|
|
go_type:
|
|
import: "github.com/shopspring/decimal"
|
|
type: "Decimal"
|
|
package: "decimal"
|
|
- db_type: "pg_catalog.numeric"
|
|
nullable: true
|
|
go_type:
|
|
import: "github.com/shopspring/decimal"
|
|
type: "NullDecimal"
|
|
package: "decimal"
|
|
rename:
|
|
group_member: GroupMemberTable
|
|
group_members_expanded: GroupMember
|
|
template: TemplateTable
|
|
template_with_name: Template
|
|
workspace_build: WorkspaceBuildTable
|
|
workspace_build_with_user: WorkspaceBuild
|
|
workspace: WorkspaceTable
|
|
workspaces_expanded: Workspace
|
|
task: TaskTable
|
|
tasks_with_status: Task
|
|
template_version: TemplateVersionTable
|
|
template_version_with_user: TemplateVersion
|
|
api_key: APIKey
|
|
api_key_scope: APIKeyScope
|
|
api_key_scope_all: APIKeyScopeAll
|
|
api_key_scope_application_connect: APIKeyScopeApplicationConnect
|
|
api_version: APIVersion
|
|
avatar_url: AvatarURL
|
|
created_by_avatar_url: CreatedByAvatarURL
|
|
dbcrypt_key: DBCryptKey
|
|
session_count_vscode: SessionCountVSCode
|
|
session_count_jetbrains: SessionCountJetBrains
|
|
session_count_reconnecting_pty: SessionCountReconnectingPTY
|
|
session_count_ssh: SessionCountSSH
|
|
connection_median_latency_ms: ConnectionMedianLatencyMS
|
|
login_type_oidc: LoginTypeOIDC
|
|
oauth_access_token: OAuthAccessToken
|
|
oauth_access_token_key_id: OAuthAccessTokenKeyID
|
|
oauth_expiry: OAuthExpiry
|
|
oauth_id_token: OAuthIDToken
|
|
oauth_refresh_token: OAuthRefreshToken
|
|
oauth_refresh_token_key_id: OAuthRefreshTokenKeyID
|
|
oauth_extra: OAuthExtra
|
|
parameter_type_system_hcl: ParameterTypeSystemHCL
|
|
userstatus: UserStatus
|
|
gitsshkey: GitSSHKey
|
|
rbac_roles: RBACRoles
|
|
ip_address: IPAddress
|
|
ip_addresses: IPAddresses
|
|
ids: IDs
|
|
jwt: JWT
|
|
user_acl: UserACL
|
|
group_acl: GroupACL
|
|
workspace_user_acl: WorkspaceUserACL
|
|
workspace_group_acl: WorkspaceGroupACL
|
|
user_acl_display_info: UserACLDisplayInfo
|
|
group_acl_display_info: GroupACLDisplayInfo
|
|
troubleshooting_url: TroubleshootingURL
|
|
default_ttl: DefaultTTL
|
|
motd_file: MOTDFile
|
|
uuid: UUID
|
|
failure_ttl: FailureTTL
|
|
time_til_dormant_autodelete: TimeTilDormantAutoDelete
|
|
eof: EOF
|
|
template_ids: TemplateIDs
|
|
active_user_ids: ActiveUserIDs
|
|
display_app_ssh_helper: DisplayAppSSHHelper
|
|
oauth2_provider_app: OAuth2ProviderApp
|
|
oauth2_provider_app_secret: OAuth2ProviderAppSecret
|
|
oauth2_provider_app_code: OAuth2ProviderAppCode
|
|
oauth2_provider_app_token: OAuth2ProviderAppToken
|
|
api_key_id: APIKeyID
|
|
callback_url: CallbackURL
|
|
login_type_oauth2_provider_app: LoginTypeOAuth2ProviderApp
|
|
crypto_key_feature_workspace_apps_api_key: CryptoKeyFeatureWorkspaceAppsAPIKey
|
|
crypto_key_feature_oidc_convert: CryptoKeyFeatureOIDCConvert
|
|
stale_interval_ms: StaleIntervalMS
|
|
has_ai_task: HasAITask
|
|
ai_task_sidebar_app_id: AITaskSidebarAppID
|
|
latest_build_has_ai_task: LatestBuildHasAITask
|
|
cors_behavior: CorsBehavior
|
|
aibridge_interception: AIBridgeInterception
|
|
aibridge_tool_usage: AIBridgeToolUsage
|
|
aibridge_token_usage: AIBridgeTokenUsage
|
|
aibridge_user_prompt: AIBridgeUserPrompt
|
|
rules:
|
|
- name: do-not-use-public-schema-in-queries
|
|
message: "do not use public schema in queries"
|
|
# FIXME: It would be great to run sqlc-vet against `migrations` directory and `dump.sql`.
|
|
rule: >
|
|
query.sql.matches(r'[^a-z]public\.')
|