attribution.ts 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. /**
  2. * Centralize the non-secret product identity every provider request sends as `User-Agent`, keeping
  3. * adapters from drifting. See
  4. * `.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md`.
  5. *
  6. * App-attribution vocabulary for provider requests.
  7. * @module @deepseek-ai/dsh-llm/attribution
  8. */
  9. import { createRequire } from 'node:module'
  10. // The package's own manifest is the single source of the version so the
  11. // User-Agent cannot drift from what is published (`./package.json` is an
  12. // export of this package; the relative path resolves from both `src/` and
  13. // the bundled `lib/`).
  14. const { version } = createRequire(import.meta.url)('../package.json') as { version: string }
  15. /**
  16. * Static public application identity sent to LLM providers.
  17. *
  18. * Every field is a public product fact, safe on every request: no secrets,
  19. * local paths, session ids, prompt text, or per-user identifiers belong here,
  20. * and nothing per-request may influence the values.
  21. */
  22. export interface AppIdentity {
  23. /** `User-Agent` product token (lowercase, hyphenated). */
  24. product: string
  25. /** Product version; sourced from package metadata, never hand-copied. */
  26. version: string
  27. /** Repository home URL of the app, used as the `User-Agent` comment. */
  28. url: string
  29. }
  30. /**
  31. * The harness's own identity: the default every adapter sends. Deployments
  32. * that need a white-label identity pass their own {@link AppIdentity} to
  33. * {@link attributionHeaders} — omission falls back to this default; nothing
  34. * can suppress attribution entirely.
  35. */
  36. export const APP_IDENTITY: AppIdentity = {
  37. product: 'deepseek-harness',
  38. version,
  39. url: 'https://github.com/deepseek-ai/deepseek-harness',
  40. }
  41. /**
  42. * The standard `User-Agent` value: `product/version (+url)`. The
  43. * parenthesized `+url` comment is the conventional self-identification form
  44. * (RFC 9110 §10.1.5 product + comment syntax).
  45. * @param identity - the identity to render; defaults to {@link APP_IDENTITY}.
  46. * @returns the ready-to-send header value.
  47. */
  48. export function userAgent(identity: AppIdentity = APP_IDENTITY): string {
  49. return `${identity.product}/${identity.version} (+${identity.url})`
  50. }
  51. /**
  52. * Build the attribution headers an adapter must send on every provider
  53. * request. Header names are lowercase (HTTP field names are case-insensitive
  54. * on the wire).
  55. * @param identity - the identity to send; defaults to {@link APP_IDENTITY} — omission cannot suppress attribution.
  56. * @returns headers to merge into the provider request (currently just `user-agent`).
  57. */
  58. export function attributionHeaders(
  59. identity: AppIdentity = APP_IDENTITY,
  60. ): Record<string, string> {
  61. return { 'user-agent': userAgent(identity) }
  62. }