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.

130 lines
4.6 KiB

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