chat-headers.ts 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import type { PluginInput, ProviderContext } from '@opencode-ai/plugin';
  2. import type { Model, UserMessage } from '@opencode-ai/sdk';
  3. import { hasInternalInitiatorMarker } from '../utils';
  4. interface ChatHeadersInput {
  5. sessionID: string;
  6. model: Model;
  7. provider: ProviderContext;
  8. message: UserMessage;
  9. }
  10. interface ChatHeadersOutput {
  11. headers: Record<string, string>;
  12. }
  13. const INTERNAL_MARKER_CACHE_LIMIT = 1000;
  14. const internalMarkerCache = new Map<string, boolean>();
  15. export function __resetInternalMarkerCacheForTesting(): void {
  16. internalMarkerCache.clear();
  17. }
  18. function getProviderID(input: ChatHeadersInput): string {
  19. return input.provider.info?.id || input.model.providerID;
  20. }
  21. function isCopilotProvider(providerID: string): boolean {
  22. return (
  23. providerID === 'github-copilot' ||
  24. providerID === 'github-copilot-enterprise'
  25. );
  26. }
  27. async function hasInternalMarker(
  28. client: PluginInput['client'],
  29. sessionID: string,
  30. messageID: string,
  31. ): Promise<boolean> {
  32. const cacheKey = `${sessionID}:${messageID}`;
  33. const cached = internalMarkerCache.get(cacheKey);
  34. if (cached !== undefined) {
  35. return cached;
  36. }
  37. try {
  38. const response = await client.session.message({
  39. path: { id: sessionID, messageID },
  40. });
  41. const hasMarker = (response.data?.parts ?? []).some(
  42. hasInternalInitiatorMarker,
  43. );
  44. if (hasMarker) {
  45. if (internalMarkerCache.size >= INTERNAL_MARKER_CACHE_LIMIT) {
  46. internalMarkerCache.clear();
  47. }
  48. internalMarkerCache.set(cacheKey, true);
  49. }
  50. return hasMarker;
  51. } catch {
  52. return false;
  53. }
  54. }
  55. export function createChatHeadersHook(ctx: PluginInput) {
  56. return {
  57. 'chat.headers': async (
  58. input: ChatHeadersInput,
  59. output: ChatHeadersOutput,
  60. ): Promise<void> => {
  61. if (!isCopilotProvider(getProviderID(input))) {
  62. return;
  63. }
  64. if (input.model.api.npm === '@ai-sdk/github-copilot') {
  65. return;
  66. }
  67. if (!input.message.id || input.message.role !== 'user') {
  68. return;
  69. }
  70. if (
  71. !(await hasInternalMarker(
  72. ctx.client,
  73. input.sessionID,
  74. input.message.id,
  75. ))
  76. ) {
  77. return;
  78. }
  79. output.headers['x-initiator'] = 'agent';
  80. },
  81. };
  82. }