index.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. /**
  2. * Session feedback event plus the human-facing `/feedback` producer. Recording
  3. * appends one authoritative log-only event and does not start model work. The
  4. * append is eager but unflushed, so acknowledgement reports that the entry is
  5. * logged, not that it reached disk.
  6. * @module @deepseek-ai/dsh-command-feedback
  7. */
  8. import type { Context } from '@deepseek-ai/cordis'
  9. import type { CommandInvocation, CommandResult } from '@deepseek-ai/dsh-commands'
  10. import type { Telemetry, TelemetrySharingStatus } from '@deepseek-ai/dsh-session-telemetry'
  11. import type { Session } from '@deepseek-ai/dsh-session'
  12. import { getOrCreateAnonymousUserId } from '@deepseek-ai/dsh-user-id'
  13. export const name = 'command-feedback'
  14. export const inject = ['commands']
  15. const USAGE = 'Usage: /feedback <text>'
  16. /** Fail closed when a future sharing status reaches the sentence switch. */
  17. /* v8 ignore next 3 -- only the ignored default arm calls this; the closed union cannot reach it via the public API. */
  18. function assertNever(value: never): never {
  19. throw new Error(`command-feedback: unsupported sharing status ${JSON.stringify(value)}`)
  20. }
  21. /** The acknowledgement's sharing sentence for a disclosed policy. */
  22. function sharingSentence(sharing: TelemetrySharingStatus): string {
  23. switch (sharing) {
  24. case 'full':
  25. return 'Session sharing is enabled.'
  26. case 'feedback-only':
  27. return 'Session sharing is feedback-gated; recording feedback releases the session prefix for sharing.'
  28. case 'disabled':
  29. return 'Session sharing is disabled.'
  30. /* v8 ignore next 2 -- the seam's closed union cannot reach the default; a future status must be given a sentence here. */
  31. default:
  32. return assertNever(sharing)
  33. }
  34. }
  35. /**
  36. * The sharing disclosure appended to the acknowledgement: the mounted
  37. * backend's disclosed policy, or a "not configured" notice when no backend
  38. * is mounted. Read through the plugin context so the command still works
  39. * when the telemetry service is absent.
  40. * @param telemetry - the mounted telemetry service, or undefined.
  41. * @returns one sentence describing this session's sharing policy.
  42. */
  43. function sharingDisclosure(telemetry: Telemetry | undefined): string {
  44. if (telemetry === undefined) {
  45. return 'Session sharing is not configured.'
  46. }
  47. return sharingSentence(telemetry.sharing)
  48. }
  49. declare module '@deepseek-ai/dsh-session/types' {
  50. interface SessionEventMap {
  51. /**
  52. * One recorded human remark about this session. Log-only and independent
  53. * of its trigger; it never enters the model surface or derived history.
  54. */
  55. 'feedback/record': { text: string }
  56. }
  57. }
  58. /**
  59. * Record feedback independently of any UI trigger.
  60. * @param session - session the feedback describes.
  61. * @param text - human-authored feedback; surrounding whitespace is discarded.
  62. * @throws {TypeError} when the normalized text is empty.
  63. */
  64. export function recordFeedback(session: Session, text: string): void {
  65. const normalized = text.trim()
  66. if (normalized.length === 0) throw new TypeError('feedback text must not be empty')
  67. session.append('feedback/record', { text: normalized })
  68. }
  69. /**
  70. * Validate, record, and acknowledge one feedback entry. Returning an error
  71. * leaves no `feedback/record` event.
  72. * @param invocation - receiving agent, raw command input, and UI cancellation.
  73. * @param ctx - plugin context used to read the optional telemetry service.
  74. * @returns an acknowledgement containing the receiving session and anonymous
  75. * user ids plus the session-sharing disclosure, or a usage error when no
  76. * feedback text was supplied.
  77. */
  78. function executeFeedbackCommand(invocation: CommandInvocation, ctx: Context): CommandResult {
  79. if (invocation.rawInput.trim().length === 0) {
  80. return { kind: 'error', text: `Feedback text is required. ${USAGE}` }
  81. }
  82. recordFeedback(invocation.agent.session, invocation.rawInput)
  83. const telemetry = ctx.get('telemetry')
  84. return {
  85. kind: 'success',
  86. text: `Feedback recorded for session ${invocation.agent.session.id}\nUser: ${getOrCreateAnonymousUserId()}. ${sharingDisclosure(telemetry)}`,
  87. }
  88. }
  89. /** Register the global `/feedback` command for every composed command adapter. */
  90. export function apply(ctx: Context): void {
  91. ctx.commands.register({
  92. name: 'feedback',
  93. description: 'record feedback about this session',
  94. input: { hint: '<text>' },
  95. recordInput: false,
  96. handler: invocation => executeFeedbackCommand(invocation, ctx),
  97. })
  98. }