honcho/harness-plugin-core/src/telemetry.ts

81 lines
2.9 KiB
TypeScript

/** Optional identity a host plugin knows at Honcho-client construction time. */
export interface TelemetryIdentity {
/** Host harness name, e.g. `harness`. */
host?: string
/** Host harness version, e.g. `2.1.3`. Omit when the harness does not expose it. */
hostVersion?: string
/** OS platform. Defaults to `process.platform`. */
platform?: string
/** Integration (plugin) name, e.g. `harness-honcho`. */
plugin?: string
/** Integration version, e.g. `0.2.11`. */
pluginVersion?: string
/** Agent completion model, e.g. `claude-sonnet-4-5`. Not a Honcho deriver/dialectic model. */
model?: string
}
export const HEADER_HOST = 'X-Honcho-Host'
export const HEADER_PLUGIN = 'X-Honcho-Plugin'
export const HEADER_AGENT_MODEL = 'X-Honcho-Agent-Model'
function sanitize(value: unknown): string | undefined {
if (typeof value !== 'string') return undefined
const s = value.replace(/[\r\n]+/g, ' ').trim()
return s || undefined
}
/** A `name/version` product token. Characters that would break parsing become `-`. */
function token(name: unknown, ver: unknown): string | undefined {
const clean = (v: unknown) => sanitize(v)?.replace(/[\s()/;]+/g, '-')
const n = clean(name)
const v = clean(ver)
if (n && v) return `${n}/${v}`
return n || v
}
/** `X-Honcho-Host` value: `harness/2.1.3 (darwin)`. Undefined when the host is unknown. */
export function hostHeaderValue(id: TelemetryIdentity = {}): string | undefined {
const host = token(id.host, id.hostVersion)
if (!host) return undefined
const platform = token(id.platform ?? process.platform, undefined)
return platform ? `${host} (${platform})` : host
}
/** `X-Honcho-Plugin` value: `harness-honcho/0.2.11`. Undefined when the plugin is unknown. */
export function pluginHeaderValue(id: TelemetryIdentity = {}): string | undefined {
return token(id.plugin, id.pluginVersion)
}
/**
* Headers to pass as the SDK's `defaultHeaders`. Fields are omitted when unknown, so a
* partial identity (e.g. just `model`) only touches the headers it names.
*/
export function telemetryHeaders(
id: TelemetryIdentity = {},
extra?: Record<string, string>
): Record<string, string> {
const headers: Record<string, string> = {}
const host = hostHeaderValue(id)
const plugin = pluginHeaderValue(id)
const model = sanitize(id.model)
if (host) headers[HEADER_HOST] = host
if (plugin) headers[HEADER_PLUGIN] = plugin
if (model) headers[HEADER_AGENT_MODEL] = model
if (extra) {
for (const [k, v] of Object.entries(extra)) {
const value = sanitize(v)
if (value) headers[k] = value
}
}
return headers
}
/** Merge identity onto a live header map (e.g. `honcho.http.defaultHeaders`). */
export function setTelemetryHeaders(
headers: Record<string, string>,
id: TelemetryIdentity = {},
extra?: Record<string, string>
): Record<string, string> {
return Object.assign(headers, telemetryHeaders(id, extra))
}