tool-sanitizer.test.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import type { ToolUIPart } from 'ai'
  2. import { describe, expect, test } from 'vitest'
  3. // End of third-party imports
  4. import { prepareMessagesForAPI } from '../message-utils'
  5. import {
  6. createAssistantMessageWithExecuteSqlTool,
  7. createAssistantMessageWithMultipleTools,
  8. createLongConversation,
  9. } from '../test-fixtures'
  10. import { NO_DATA_PERMISSIONS, sanitizeMessagePart } from './tool-sanitizer'
  11. describe('messages are sanitized based on opt-in level', () => {
  12. test('messages are sanitized at disabled level', () => {
  13. const messages = [
  14. createAssistantMessageWithExecuteSqlTool('SELECT email FROM users', [
  15. { email: 'test@example.com' },
  16. ]),
  17. ]
  18. // Prepare messages as frontend would
  19. const preparedMessages = prepareMessagesForAPI(messages)
  20. // Sanitize messages as API endpoint would
  21. const processedMessages = preparedMessages.map((msg) => {
  22. if (msg.role === 'assistant' && msg.parts) {
  23. const processedParts = msg.parts.map((part) => {
  24. return sanitizeMessagePart(part, 'disabled')
  25. })
  26. return { ...msg, parts: processedParts }
  27. }
  28. return msg
  29. })
  30. const output = (processedMessages[0].parts[1] as ToolUIPart).output
  31. expect(output).toMatch(NO_DATA_PERMISSIONS)
  32. })
  33. test('messages are sanitized at schema level', () => {
  34. const messages = [
  35. createAssistantMessageWithExecuteSqlTool('SELECT email FROM users', [
  36. { email: 'test@example.com' },
  37. ]),
  38. ]
  39. // Prepare messages as frontend would
  40. const preparedMessages = prepareMessagesForAPI(messages)
  41. // Sanitize messages as API endpoint would
  42. const processedMessages = preparedMessages.map((msg) => {
  43. if (msg.role === 'assistant' && msg.parts) {
  44. const processedParts = msg.parts.map((part) => {
  45. return sanitizeMessagePart(part, 'schema')
  46. })
  47. return { ...msg, parts: processedParts }
  48. }
  49. return msg
  50. })
  51. const output = (processedMessages[0].parts[1] as ToolUIPart).output
  52. expect(output).toMatch(NO_DATA_PERMISSIONS)
  53. })
  54. test('messages are sanitized at schema and log level', () => {
  55. const messages = [
  56. createAssistantMessageWithExecuteSqlTool('SELECT email FROM users', [
  57. { email: 'test@example.com' },
  58. ]),
  59. ]
  60. // Prepare messages as frontend would
  61. const preparedMessages = prepareMessagesForAPI(messages)
  62. // Sanitize messages as API endpoint would
  63. const processedMessages = preparedMessages.map((msg) => {
  64. if (msg.role === 'assistant' && msg.parts) {
  65. const processedParts = msg.parts.map((part) => {
  66. return sanitizeMessagePart(part, 'schema_and_log')
  67. })
  68. return { ...msg, parts: processedParts }
  69. }
  70. return msg
  71. })
  72. const output = (processedMessages[0].parts[1] as ToolUIPart).output
  73. expect(output).toMatch(NO_DATA_PERMISSIONS)
  74. })
  75. test('messages are not sanitized at data level', () => {
  76. const messages = [
  77. createAssistantMessageWithExecuteSqlTool('SELECT email FROM users', [
  78. { email: 'test@example.com' },
  79. ]),
  80. ]
  81. // Prepare messages as frontend would
  82. const preparedMessages = prepareMessagesForAPI(messages)
  83. // Sanitize messages as API endpoint would
  84. const processedMessages = preparedMessages.map((msg) => {
  85. if (msg.role === 'assistant' && msg.parts) {
  86. const processedParts = msg.parts.map((part) => {
  87. return sanitizeMessagePart(part, 'schema_and_log_and_data')
  88. })
  89. return { ...msg, parts: processedParts }
  90. }
  91. return msg
  92. })
  93. const output = (processedMessages[0].parts[1] as ToolUIPart).output
  94. expect(output).toEqual([{ email: 'test@example.com' }])
  95. })
  96. test('multiple tool parts in message are sanitized', () => {
  97. const messages = [createAssistantMessageWithMultipleTools()]
  98. // Prepare messages as frontend would
  99. const preparedMessages = prepareMessagesForAPI(messages)
  100. // Sanitize messages as API endpoint would
  101. const processedMessages = preparedMessages.map((msg) => {
  102. if (msg.role === 'assistant' && msg.parts) {
  103. const processedParts = msg.parts.map((part) => {
  104. return sanitizeMessagePart(part, 'schema')
  105. })
  106. return { ...msg, parts: processedParts }
  107. }
  108. return msg
  109. })
  110. const parts = processedMessages[0].parts
  111. parts.forEach((part) => {
  112. if (part.type.startsWith('tool')) {
  113. const tool = part as ToolUIPart
  114. expect(tool.output).toMatch(NO_DATA_PERMISSIONS)
  115. }
  116. })
  117. })
  118. test('long message chain is sanitized', () => {
  119. const messages = createLongConversation()
  120. // Prepare messages as frontend would
  121. const preparedMessages = prepareMessagesForAPI(messages)
  122. // Sanitize messages as API endpoint would
  123. const processedMessages = preparedMessages.map((msg) => {
  124. if (msg.role === 'assistant' && msg.parts) {
  125. const processedParts = msg.parts.map((part) => {
  126. return sanitizeMessagePart(part, 'schema')
  127. })
  128. return { ...msg, parts: processedParts }
  129. }
  130. return msg
  131. })
  132. processedMessages.forEach((msg) => {
  133. if (msg.role === 'assistant' && msg.parts) {
  134. const parts = msg.parts
  135. parts.forEach((part) => {
  136. if (part.type.startsWith('tool')) {
  137. const tool = part as ToolUIPart
  138. expect(tool.output).toMatch(NO_DATA_PERMISSIONS)
  139. }
  140. })
  141. }
  142. })
  143. })
  144. })