uri.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. /** Canonical session URI and inline mention encoding. */
  2. import { brandString } from '@deepseek-ai/dsh-brand'
  3. import type { SessionId as SessionIdType } from '@deepseek-ai/dsh-session'
  4. import { SessionReferenceError } from './config.ts'
  5. import type { SessionReferenceInput } from './types.ts'
  6. /** URI scheme reserved for DeepSeek Harness session snapshots. */
  7. export const SESSION_REFERENCE_SCHEME = 'dsh-session:'
  8. /**
  9. * Encode any JavaScript session-id string as a canonical lossless URI.
  10. * @param sessionId - opaque session id to serialize.
  11. * @returns canonical `dsh-session:` URI.
  12. */
  13. export function encodeSessionReferenceUri(sessionId: SessionIdType): string {
  14. const payload = Buffer.from(JSON.stringify(sessionId), 'utf8').toString('base64url')
  15. return `${SESSION_REFERENCE_SCHEME}${payload}`
  16. }
  17. /**
  18. * Decode and canonicalize one session-reference URI.
  19. * @param uri - complete canonical URI.
  20. * @returns decoded session id.
  21. */
  22. export function decodeSessionReferenceUri(uri: string): SessionIdType {
  23. if (!uri.startsWith(SESSION_REFERENCE_SCHEME)) {
  24. throw invalidUri(uri)
  25. }
  26. const payload = uri.slice(SESSION_REFERENCE_SCHEME.length)
  27. if (!/^[A-Za-z0-9_-]+$/.test(payload)) throw invalidUri(uri)
  28. try {
  29. const parsed: unknown = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'))
  30. if (typeof parsed !== 'string') throw new TypeError('decoded session id is not a string')
  31. const sessionId = brandString<SessionIdType>(parsed)
  32. if (encodeSessionReferenceUri(sessionId) !== uri) throw new TypeError('URI is not canonical')
  33. return sessionId
  34. } catch (error: unknown) {
  35. throw invalidUri(uri, error)
  36. }
  37. }
  38. /**
  39. * Render a host-neutral Markdown mention carrying the canonical URI.
  40. * @param reference - structured id and optional display label.
  41. * @returns escaped `@[label](uri)` mention.
  42. */
  43. export function formatSessionReferenceMention(reference: SessionReferenceInput): string {
  44. const label = escapeLabel(reference.label ?? reference.sessionId)
  45. return `@[${label}](${encodeSessionReferenceUri(reference.sessionId)})`
  46. }
  47. /** Result of extracting canonical mentions from plain text. */
  48. export interface ParsedSessionReferenceText {
  49. /** Text with opaque tokens replaced by readable `@label` spans. */
  50. text: string
  51. /** Structured references in first-appearance order, before service deduplication. */
  52. references: SessionReferenceInput[]
  53. }
  54. /**
  55. * Extract Markdown mentions and bare canonical URIs from one text value.
  56. * Explicit Markdown mentions fail on any malformed URI. Bare text is treated
  57. * as a reference only when it has a non-empty base64url-shaped payload, then
  58. * still fails if that candidate is not canonical.
  59. * @param text - host text to normalize.
  60. * @returns readable text and structured references in appearance order.
  61. */
  62. export function parseSessionReferenceText(text: string): ParsedSessionReferenceText {
  63. const references: SessionReferenceInput[] = []
  64. const pattern = /@\[((?:\\.|[^\\\]])*)\]\((dsh-session:[^\s)]*)\)|(dsh-session:[A-Za-z0-9_-]+)/gu
  65. const rendered = text.replace(pattern, (
  66. _match,
  67. rawLabel: string | undefined,
  68. markdownUri: string | undefined,
  69. bareUri: string | undefined,
  70. ) => {
  71. const uri = markdownUri ?? bareUri
  72. /* v8 ignore next -- the two-alternative regex always captures exactly one URI group. */
  73. if (uri === undefined) throw new SessionReferenceError('session reference URI is missing', 'SESSION_REFERENCE_INVALID_REFERENCE')
  74. const sessionId = decodeSessionReferenceUri(uri)
  75. const label = rawLabel === undefined ? sessionId : unescapeLabel(rawLabel)
  76. references.push({ sessionId, label })
  77. return `@${label}`
  78. })
  79. return { text: rendered, references }
  80. }
  81. function escapeLabel(label: string): string {
  82. return label.replace(/[\\\]]/gu, match => `\\${match}`)
  83. }
  84. function unescapeLabel(label: string): string {
  85. return label.replace(/\\(.)/gu, '$1')
  86. }
  87. function invalidUri(uri: string, cause?: unknown): SessionReferenceError {
  88. return new SessionReferenceError(
  89. `invalid session reference URI ${JSON.stringify(uri)}`,
  90. 'SESSION_REFERENCE_INVALID_REFERENCE',
  91. cause === undefined ? undefined : { cause },
  92. )
  93. }