Skip to content

Commit 2710913

Browse files
feat(telemetry): implement rich multi-turn telemetry collection
- Update integration to properly record user messages with content - Record assistant responses with model ID and token counts - Track tool calls with status, duration, and input/output sizes - Finalize turns and upload to telemetry service - Deduplicate user message recording - Get user info (userId, organizationId) from consent endpoint - Read qBraid API key from config (provider.qbraid.options.apiKey) - Update default telemetry endpoint to production Cloud Run URL The telemetry now captures: - Full user message content (sanitized) - Full assistant response content - Tool call metadata (name, status, duration, sizes) - File changes with path hashes - Session metrics (turn count, token counts, tool counts) - Implicit signals (retries, compactions, abandonment)
1 parent 221c6dc commit 2710913

5 files changed

Lines changed: 211 additions & 56 deletions

File tree

packages/opencode/src/config/config.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -77,18 +77,18 @@ export namespace Config {
7777
for (const [key, value] of Object.entries(auth)) {
7878
if (value.type === "wellknown") {
7979
process.env[value.key] = value.token
80-
log.debug("fetching remote config", { url: `${key}/.well-known/opencode` })
81-
const response = await fetch(`${key}/.well-known/opencode`)
80+
log.debug("fetching remote config", { url: `${key}/.well-known/codeq` })
81+
const response = await fetch(`${key}/.well-known/codeq`)
8282
if (!response.ok) {
8383
throw new Error(`failed to fetch remote config from ${key}: ${response.status}`)
8484
}
8585
const wellknown = (await response.json()) as any
8686
const remoteConfig = wellknown.config ?? {}
8787
// Add $schema to prevent load() from trying to write back to a non-existent file
88-
if (!remoteConfig.$schema) remoteConfig.$schema = "https://opencode.ai/config.json"
88+
if (!remoteConfig.$schema) remoteConfig.$schema = "https://codeq.ai/config.json"
8989
result = mergeConfigConcatArrays(
9090
result,
91-
await load(JSON.stringify(remoteConfig), `${key}/.well-known/opencode`),
91+
await load(JSON.stringify(remoteConfig), `${key}/.well-known/codeq`),
9292
)
9393
log.debug("loaded remote config from well-known", { url: key })
9494
}
@@ -132,7 +132,7 @@ export namespace Config {
132132
// Always scan ~/.opencode/ (user home directory)
133133
...(await Array.fromAsync(
134134
Filesystem.up({
135-
targets: [".opencode"],
135+
targets: [".codeq"],
136136
start: Global.Path.home,
137137
stop: Global.Path.home,
138138
}),
@@ -148,7 +148,7 @@ export namespace Config {
148148
const deps = []
149149

150150
for (const dir of unique(directories)) {
151-
if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) {
151+
if (dir.endsWith(".codeq") || dir === Flag.OPENCODE_CONFIG_DIR) {
152152
for (const file of ["opencode.jsonc", "opencode.json"]) {
153153
log.debug(`loading config from ${path.join(dir, file)}`)
154154
result = mergeConfigConcatArrays(result, await loadFile(path.join(dir, file)))
@@ -473,7 +473,7 @@ export namespace Config {
473473
*
474474
* @example
475475
* getPluginName("file:///path/to/plugin/foo.js") // "foo"
476-
* getPluginName("oh-my-opencode@2.4.3") // "oh-my-opencode"
476+
* getPluginName("oh-my-codeq@2.4.3") // "oh-my-codeq"
477477
* getPluginName("@scope/pkg@1.0.0") // "@scope/pkg"
478478
*/
479479
export function getPluginName(plugin: string): string {
@@ -500,11 +500,11 @@ export namespace Config {
500500
*/
501501
export function deduplicatePlugins(plugins: string[]): string[] {
502502
// seenNames: canonical plugin names for duplicate detection
503-
// e.g., "oh-my-opencode", "@scope/pkg"
503+
// e.g., "oh-my-codeq", "@scope/pkg"
504504
const seenNames = new Set<string>()
505505

506506
// uniqueSpecifiers: full plugin specifiers to return
507-
// e.g., "oh-my-opencode@2.4.3", "file:///path/to/plugin.js"
507+
// e.g., "oh-my-codeq@2.4.3", "file:///path/to/plugin.js"
508508
const uniqueSpecifiers: string[] = []
509509

510510
for (const specifier of plugins.toReversed()) {
@@ -1006,7 +1006,7 @@ export namespace Config {
10061006
keybinds: Keybinds.optional().describe("Custom keybind configurations"),
10071007
logLevel: Log.Level.optional().describe("Log level"),
10081008
tui: TUI.optional().describe("TUI specific settings"),
1009-
server: Server.optional().describe("Server configuration for opencode serve and web commands"),
1009+
server: Server.optional().describe("Server configuration for codeq serve and web commands"),
10101010
command: z
10111011
.record(z.string(), Command)
10121012
.optional()
@@ -1078,7 +1078,7 @@ export namespace Config {
10781078
})
10791079
.catchall(Agent)
10801080
.optional()
1081-
.describe("Agent configuration, see https://opencode.ai/docs/agents"),
1081+
.describe("Agent configuration, see https://codeq.ai/docs/agents"),
10821082
provider: z
10831083
.record(z.string(), Provider)
10841084
.optional()
@@ -1184,7 +1184,7 @@ export namespace Config {
11841184
})
11851185
.optional(),
11861186
// qBraid-specific configuration (CodeQ customizations)
1187-
// This section is ignored by upstream opencode and contains qBraid-specific features
1187+
// This section is ignored by upstream codeq and contains qBraid-specific features
11881188
qbraid: z
11891189
.object({
11901190
telemetry: z
@@ -1350,9 +1350,9 @@ export namespace Config {
13501350
const parsed = Info.safeParse(data)
13511351
if (parsed.success) {
13521352
if (!parsed.data.$schema) {
1353-
parsed.data.$schema = "https://opencode.ai/config.json"
1353+
parsed.data.$schema = "https://codeq.ai/config.json"
13541354
// Write the $schema to the original text to preserve variables like {env:VAR}
1355-
const updated = original.replace(/^\s*\{/, '{\n "$schema": "https://opencode.ai/config.json",')
1355+
const updated = original.replace(/^\s*\{/, '{\n "$schema": "https://codeq.ai/config.json",')
13561356
await Bun.write(configFilepath, updated).catch(() => {})
13571357
}
13581358
const data = parsed.data

packages/opencode/src/telemetry/collector.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ export class TelemetryCollector {
343343
/**
344344
* Finalize the current turn and queue for upload
345345
*/
346-
private finalizeTurn(): void {
346+
finalizeTurn(): void {
347347
if (!this.sessionState?.currentTurn) return
348348

349349
const turn = this.sessionState.currentTurn as TelemetryTurn

packages/opencode/src/telemetry/consent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type { ConsentStatus, DataLevel, UserTier } from "./types"
1111
const log = Log.create({ service: "telemetry:consent" })
1212

1313
// Default telemetry endpoint
14-
const DEFAULT_TELEMETRY_ENDPOINT = "https://telemetry.qbraid.com"
14+
const DEFAULT_TELEMETRY_ENDPOINT = "https://qbraid-telemetry-314301605548.us-central1.run.app"
1515

1616
// Cache consent status to avoid repeated API calls
1717
let cachedConsent: ConsentStatus | null = null

packages/opencode/src/telemetry/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* CodeQ Telemetry Module
33
*
44
* Collects session telemetry for analysis and model improvement.
5-
* This module is qBraid-specific and not part of upstream opencode.
5+
* This module is qBraid-specific and not part of upstream codeq.
66
*
77
* Usage:
88
* import { Telemetry } from "./telemetry"
@@ -113,7 +113,7 @@ export namespace Telemetry {
113113
/**
114114
* Start collecting for a new session
115115
*
116-
* @param sessionId - OpenCode session ID
116+
* @param sessionId - CodeQ session ID
117117
* @param userId - qBraid user ID
118118
* @param organizationId - Organization ID
119119
*/

0 commit comments

Comments
 (0)