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.

115 lines
3.6 KiB

  1. import fetchPonyfill from 'fetch-ponyfill';
  2. import { EventEmitter } from 'events';
  3. import { PassThrough } from 'stream';
  4. import { PlatformEventEmitter } from './events';
  5. import { createTextCompletion, TextCompletion } from './features/text-completion';
  6. import { createImage } from './features/image';
  7. import { createChatCompletion, ChatCompletion } from './features/chat-completion';
  8. import { createEdit } from './features/edit';
  9. export * from './message';
  10. export * from './models';
  11. export { PlatformEventEmitter, ChatCompletion, TextCompletion };
  12. export {
  13. ChatCompletionChunkDataEvent,
  14. DataEventObjectType as ChatCompletionDataEventObjectType,
  15. } from './features/chat-completion';
  16. export {
  17. TextCompletionChunkDataEvent,
  18. DataEventObjectType as TextCompletionDataEventObjectType,
  19. } from './features/text-completion';
  20. export {
  21. CreateEditDataEvent,
  22. DataEventObjectType as EditDataEventObjectType,
  23. } from './features/edit';
  24. export { CreateImageDataEvent, CreateImageSize } from './features/image';
  25. export * from './common';
  26. export enum ApiVersion {
  27. V1 = 'v1',
  28. }
  29. export const PLATFORM_ID = 'openai' as const;
  30. export interface PlatformConfig {
  31. platform: typeof PLATFORM_ID;
  32. platformConfiguration: Configuration;
  33. }
  34. export interface Configuration {
  35. organizationId?: string;
  36. apiVersion: ApiVersion;
  37. apiKey: string;
  38. baseUrl?: string;
  39. }
  40. export class PlatformEventEmitterImpl extends EventEmitter implements PlatformEventEmitter {
  41. readonly createCompletion: PlatformEventEmitter['createCompletion'];
  42. readonly createImage: PlatformEventEmitter['createImage'];
  43. readonly createChatCompletion: PlatformEventEmitter['createChatCompletion'];
  44. readonly createEdit: PlatformEventEmitter['createEdit'];
  45. constructor(configParams: Configuration) {
  46. super();
  47. const headers: Record<string, string> = {
  48. Authorization: `Bearer ${configParams.apiKey}`,
  49. };
  50. if (configParams.organizationId) {
  51. headers['OpenAI-Organization'] = configParams.organizationId;
  52. }
  53. const { fetch: fetchInstance } = fetchPonyfill();
  54. const doFetch = (method: string, path: string, body: Record<string, unknown>) => {
  55. const theFetchParams = {
  56. method,
  57. headers: {
  58. ...headers,
  59. 'Content-Type': 'application/json',
  60. },
  61. body: JSON.stringify(body),
  62. };
  63. const url = new URL(
  64. `/${configParams.apiVersion}${path}`,
  65. configParams.baseUrl ?? 'https://api.openai.com',
  66. ).toString();
  67. this.emit('start', {
  68. ...theFetchParams,
  69. url,
  70. });
  71. return fetchInstance(url, theFetchParams);
  72. };
  73. const consumeStream = async (response: Response) => {
  74. // eslint-disable-next-line no-restricted-syntax
  75. for await (const chunk of response.body as unknown as PassThrough) {
  76. const chunkStringMaybeMultiple = chunk.toString();
  77. const chunkStrings = chunkStringMaybeMultiple
  78. .split('\n')
  79. .filter((chunkString: string) => chunkString.length > 0);
  80. chunkStrings.forEach((chunkString: string) => {
  81. const dataRaw = chunkString.split('data: ').at(1);
  82. if (!dataRaw) {
  83. return;
  84. }
  85. if (dataRaw === '[DONE]') {
  86. return;
  87. }
  88. const data = JSON.parse(dataRaw);
  89. this.emit('data', data);
  90. });
  91. }
  92. };
  93. this.createImage = createImage.bind(this, doFetch);
  94. this.createCompletion = createTextCompletion.bind(this, doFetch, consumeStream);
  95. this.createChatCompletion = createChatCompletion.bind(this, doFetch, consumeStream);
  96. this.createEdit = createEdit.bind(this, doFetch);
  97. }
  98. }