types.ts 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. /**
  2. * Shared message type shapes for the OpenCode plugin API's `messages` array.
  3. *
  4. * These types describe the structure of chat messages passed through
  5. * `experimental.chat.messages.transform` and related hooks. All fields
  6. * are unioned across the files that previously defined them privately -
  7. * optional extras are harmless under structural typing.
  8. */
  9. export type MessageInfo = {
  10. role: string;
  11. agent?: string;
  12. sessionID?: string;
  13. id?: string;
  14. };
  15. export type MessagePart = {
  16. type: string;
  17. text?: string;
  18. [key: string]: unknown;
  19. };
  20. export type MessageWithParts = {
  21. info: MessageInfo;
  22. parts: MessagePart[];
  23. };
  24. export function isMessageWithParts(
  25. message: unknown,
  26. ): message is MessageWithParts {
  27. if (!message || typeof message !== 'object') {
  28. return false;
  29. }
  30. const candidate = message as Partial<MessageWithParts>;
  31. return (
  32. !!candidate.info &&
  33. typeof candidate.info === 'object' &&
  34. typeof candidate.info.role === 'string' &&
  35. Array.isArray(candidate.parts)
  36. );
  37. }
  38. export function isUserMessageWithParts(
  39. message: unknown,
  40. ): message is MessageWithParts {
  41. return isMessageWithParts(message) && message.info.role === 'user';
  42. }
  43. export function findLatestUserMessage(
  44. messages: unknown[],
  45. ): MessageWithParts | undefined {
  46. for (let index = messages.length - 1; index >= 0; index--) {
  47. const message = messages[index];
  48. if (isUserMessageWithParts(message)) {
  49. return message;
  50. }
  51. }
  52. return undefined;
  53. }
  54. /**
  55. * A user message that can be replayed into a fallback prompt.
  56. *
  57. * Accepts both the v1 transform shape (`{ info: { role: 'user' }, parts }`)
  58. * and the v2 `session.messages()` shape (`{ type: 'user', text }`) returned
  59. * by the plugin SDK's HTTP API since OpenCode 1.18.
  60. */
  61. export type ReplayableUserMessage =
  62. | MessageWithParts
  63. | {
  64. type: 'user';
  65. text?: string;
  66. };
  67. export function isReplayableUserMessage(
  68. message: unknown,
  69. ): message is ReplayableUserMessage {
  70. if (isUserMessageWithParts(message)) {
  71. return true;
  72. }
  73. if (!message || typeof message !== 'object') {
  74. return false;
  75. }
  76. const candidate = message as { type?: unknown };
  77. return candidate.type === 'user';
  78. }
  79. export function partsFromReplayMessage(
  80. message: ReplayableUserMessage,
  81. ): MessagePart[] {
  82. const parts = (message as Partial<MessageWithParts>).parts;
  83. if (Array.isArray(parts)) {
  84. return parts;
  85. }
  86. const text = (message as { text?: string }).text;
  87. return typeof text === 'string' && text.length > 0
  88. ? [{ type: 'text', text }]
  89. : [];
  90. }