Many-in-one AI client.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

121 lines
4.2 KiB

  1. import { PassThrough } from 'stream';
  2. import { EventEmitter } from 'events';
  3. import fetchPonyfill from 'fetch-ponyfill';
  4. import * as AllPlatformsCommon from '../../common';
  5. import { Configuration, DEFAULT_BASE_URL } from './common';
  6. import { createTextCompletion, CreateTextCompletionParams } from './features/text-completion';
  7. import { CreateChatCompletionParams, createChatCompletion } from './features/chat-completion';
  8. import {
  9. CreateImageParams,
  10. createImage,
  11. CreateImageEditParams,
  12. createImageEdit,
  13. CreateImageVariationParams,
  14. createImageVariation,
  15. } from './features/image';
  16. import { CreateEditParams, createEdit } from './features/edit';
  17. import { listModels } from './features/model';
  18. import { DoFetchBody, processRequest } from '../../packages/request';
  19. export interface PlatformEventEmitter extends AllPlatformsCommon.PlatformEventEmitter {
  20. createChatCompletion(params: CreateChatCompletionParams): void;
  21. createImage(params: CreateImageParams): void;
  22. createImageEdit(params: CreateImageEditParams): void;
  23. createImageVariation(params: CreateImageVariationParams): void;
  24. createCompletion(params: CreateTextCompletionParams): void;
  25. createEdit(params: CreateEditParams): void;
  26. listModels(): void;
  27. }
  28. export class PlatformEventEmitterImpl extends EventEmitter implements PlatformEventEmitter {
  29. readonly createCompletion: PlatformEventEmitter['createCompletion'];
  30. readonly createImage: PlatformEventEmitter['createImage'];
  31. readonly createImageEdit: PlatformEventEmitter['createImageEdit'];
  32. readonly createImageVariation: PlatformEventEmitter['createImageVariation'];
  33. readonly createChatCompletion: PlatformEventEmitter['createChatCompletion'];
  34. readonly createEdit: PlatformEventEmitter['createEdit'];
  35. readonly listModels: PlatformEventEmitter['listModels'];
  36. constructor(configParams: Configuration) {
  37. super();
  38. const platformHeaders: Record<string, string> = {
  39. Authorization: `Bearer ${configParams.apiKey}`,
  40. };
  41. if (configParams.organizationId) {
  42. platformHeaders['OpenAI-Organization'] = configParams.organizationId;
  43. }
  44. const { fetch: fetchInstance } = fetchPonyfill();
  45. const doFetch = (method: string, path: string, body?: DoFetchBody) => {
  46. let finalBody: BodyInit | undefined;
  47. let finalHeaders = {
  48. ...platformHeaders,
  49. };
  50. if (body) {
  51. const finalRequest = processRequest(body, finalHeaders);
  52. finalBody = finalRequest.body;
  53. if (finalRequest.headers) {
  54. finalHeaders = finalRequest.headers;
  55. }
  56. }
  57. const theFetchParams: Record<string, unknown> = {
  58. method,
  59. headers: finalHeaders,
  60. };
  61. if (finalBody) {
  62. theFetchParams.body = finalBody;
  63. }
  64. const url = new URL(
  65. `/${configParams.apiVersion}${path}`,
  66. configParams.baseUrl ?? DEFAULT_BASE_URL,
  67. ).toString();
  68. this.emit('start', {
  69. ...theFetchParams,
  70. url,
  71. });
  72. return fetchInstance(url, theFetchParams);
  73. };
  74. const consumeStream = async (response: Response) => {
  75. // eslint-disable-next-line no-restricted-syntax
  76. for await (const chunk of response.body as unknown as PassThrough) {
  77. const chunkStringMaybeMultiple = chunk.toString();
  78. const chunkStrings = chunkStringMaybeMultiple
  79. .trim()
  80. .split('\n')
  81. .filter((chunkString: string) => chunkString.length > 0);
  82. chunkStrings.forEach((chunkString: string) => {
  83. const dataRaw = chunkString.split('data: ').at(1);
  84. if (!dataRaw) {
  85. return;
  86. }
  87. if (dataRaw === '[DONE]') {
  88. return;
  89. }
  90. const data = JSON.parse(dataRaw);
  91. this.emit('data', data);
  92. });
  93. }
  94. };
  95. this.listModels = listModels.bind(this, doFetch);
  96. this.createImage = createImage.bind(this, doFetch);
  97. this.createImageVariation = createImageVariation.bind(this, doFetch);
  98. this.createImageEdit = createImageEdit.bind(this, doFetch);
  99. this.createCompletion = createTextCompletion.bind(this, doFetch, consumeStream);
  100. this.createChatCompletion = createChatCompletion.bind(this, doFetch, consumeStream);
  101. this.createEdit = createEdit.bind(this, doFetch);
  102. }
  103. }