cloving 0.3.18 → 0.3.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/.DS_Store +0 -0
  2. package/.aider.chat.history.md +16978 -0
  3. package/.aider.input.history +15 -0
  4. package/.aider.tags.cache.v3/cache.db +0 -0
  5. package/.aider.tags.cache.v3/cache.db-shm +0 -0
  6. package/.aider.tags.cache.v3/cache.db-wal +0 -0
  7. package/.cursorrules +77 -0
  8. package/.github/workflows/auto-release.yml +83 -0
  9. package/.github/workflows/ci.yml +32 -0
  10. package/.github/workflows/npm-publish.yml +34 -0
  11. package/.gitignore +67 -0
  12. package/.husky/_/.gitignore +1 -0
  13. package/.husky/_/applypatch-msg +2 -0
  14. package/.husky/_/commit-msg +2 -0
  15. package/.husky/_/h +22 -0
  16. package/.husky/_/husky.sh +9 -0
  17. package/.husky/_/post-applypatch +2 -0
  18. package/.husky/_/post-checkout +2 -0
  19. package/.husky/_/post-commit +2 -0
  20. package/.husky/_/post-merge +2 -0
  21. package/.husky/_/post-rewrite +2 -0
  22. package/.husky/_/pre-applypatch +2 -0
  23. package/.husky/_/pre-auto-gc +2 -0
  24. package/.husky/_/pre-commit +2 -0
  25. package/.husky/_/pre-merge-commit +2 -0
  26. package/.husky/_/pre-push +2 -0
  27. package/.husky/_/pre-rebase +2 -0
  28. package/.husky/_/prepare-commit-msg +2 -0
  29. package/.husky/pre-commit +1 -0
  30. package/.husky/pre-push +1 -0
  31. package/.prettierrc.json +7 -0
  32. package/.tool-versions +2 -0
  33. package/SECURITY.md +15 -0
  34. package/cloving.json +67 -0
  35. package/dist/managers/CommitManager.js +8 -2
  36. package/dist/package.json +1 -1
  37. package/jest.config.js +11 -0
  38. package/package.json +1 -1
  39. package/src/cloving_gpt/adapters/azureopenai.ts +83 -0
  40. package/src/cloving_gpt/adapters/claude.ts +140 -0
  41. package/src/cloving_gpt/adapters/gemini.ts +146 -0
  42. package/src/cloving_gpt/adapters/index.ts +43 -0
  43. package/src/cloving_gpt/adapters/mistral.ts +142 -0
  44. package/src/cloving_gpt/adapters/ollama.ts +136 -0
  45. package/src/cloving_gpt/adapters/openai.ts +113 -0
  46. package/src/cloving_gpt/index.ts +229 -0
  47. package/src/commands/chat.ts +12 -0
  48. package/src/commands/config.ts +192 -0
  49. package/src/commands/generate/code.ts +18 -0
  50. package/src/commands/generate/commit.ts +9 -0
  51. package/src/commands/generate/context.ts +93 -0
  52. package/src/commands/generate/docs.ts +9 -0
  53. package/src/commands/generate/review.ts +9 -0
  54. package/src/commands/generate/shell.ts +9 -0
  55. package/src/commands/generate/unit_tests.ts +9 -0
  56. package/src/commands/init.ts +9 -0
  57. package/src/commands/models.ts +21 -0
  58. package/src/commands/proxy.ts +9 -0
  59. package/src/commands/tokens.ts +82 -0
  60. package/src/index.ts +197 -0
  61. package/src/managers/BlockManager.ts +126 -0
  62. package/src/managers/ChatManager.ts +755 -0
  63. package/src/managers/ChunkManager.ts +31 -0
  64. package/src/managers/CodeManager.ts +226 -0
  65. package/src/managers/CommitManager.ts +71 -0
  66. package/src/managers/DocsManager.ts +101 -0
  67. package/src/managers/InitManager.ts +263 -0
  68. package/src/managers/ProxyManager.ts +240 -0
  69. package/src/managers/ReviewManager.ts +154 -0
  70. package/src/managers/ShellManager.ts +172 -0
  71. package/src/managers/StreamManager.ts +353 -0
  72. package/src/managers/UnitTestManager.ts +293 -0
  73. package/src/utils/config_utils.ts +271 -0
  74. package/src/utils/git_utils.ts +230 -0
  75. package/src/utils/model_utils.ts +3 -0
  76. package/src/utils/prompt_utils.ts +375 -0
  77. package/src/utils/prompts/CODEGEN_COULDNT_APPLY.md +5 -0
  78. package/src/utils/prompts/CODEGEN_EXAMPLES.md +82 -0
  79. package/src/utils/prompts/CODEGEN_INSTRUCTIONS.md +43 -0
  80. package/src/utils/prompts/DOCS_INSTRUCTIONS.md +9 -0
  81. package/src/utils/prompts/INIT_INSTRUCTIONS.md +132 -0
  82. package/src/utils/prompts/REVIEW_INSTRUCTIONS.md +49 -0
  83. package/src/utils/prompts/SHELL_INSTRUCTIONS.md +15 -0
  84. package/src/utils/prompts/SPECIAL_FILES.md +66 -0
  85. package/src/utils/prompts/index.ts +37 -0
  86. package/src/utils/string_utils.ts +552 -0
  87. package/src/utils/types.ts +127 -0
  88. package/tests/commands/generate/unit_tests.test.ts +27 -0
  89. package/tests/utils/git_utils.test.ts +116 -0
  90. package/tests/utils/model_utils.test.ts +27 -0
  91. package/tests/utils/prompt_utils.test.ts +33 -0
  92. package/tests/utils/string_utils.test.ts +560 -0
  93. package/tsconfig.json +14 -0
  94. package/yarn.lock +5823 -0
@@ -0,0 +1,140 @@
1
+ import { Adapter } from '.'
2
+ import type { GPTRequest, OpenAIStreamChunk } from '../../utils/types'
3
+
4
+ /**
5
+ * ClaudeAdapter class implements the Adapter interface for Claude AI models.
6
+ */
7
+ export class ClaudeAdapter implements Adapter {
8
+ private model: string
9
+ private anthropicVersion: string
10
+
11
+ /**
12
+ * Array of supported Claude model identifiers.
13
+ */
14
+ static supportedModels: string[] = [
15
+ 'claude:claude-3-5:sonnet-20240620',
16
+ 'claude:claude-3:opus-20240229',
17
+ 'claude:claude-3:sonnet-20240229',
18
+ 'claude:claude-3:haiku-20240307',
19
+ // Add more supported models here as needed
20
+ ]
21
+
22
+ /**
23
+ * Initializes a new ClaudeAdapter instance.
24
+ * @param model - The Claude model identifier.
25
+ */
26
+ constructor(model: string) {
27
+ this.model = model
28
+ this.anthropicVersion = '2023-06-01'
29
+ }
30
+
31
+ /**
32
+ * Logs all supported Claude models to the console.
33
+ */
34
+ static listSupportedModels(): void {
35
+ ClaudeAdapter.supportedModels.forEach((model) => {
36
+ console.log(model)
37
+ })
38
+ }
39
+
40
+ /**
41
+ * Returns the API endpoint for Claude.
42
+ * @returns The API endpoint URL.
43
+ */
44
+ getEndpoint(): string {
45
+ return `https://api.anthropic.com/v1/messages`
46
+ }
47
+
48
+ /**
49
+ * Generates headers for API requests.
50
+ * @param apiKey - The API key for authentication.
51
+ * @returns An object containing the required headers.
52
+ */
53
+ getHeaders(apiKey: string): Record<string, string> {
54
+ return {
55
+ 'x-api-key': apiKey,
56
+ 'Content-Type': 'application/json',
57
+ 'anthropic-version': this.anthropicVersion,
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Prepares the payload for API requests.
63
+ * @param request - The GPTRequest object containing request details.
64
+ * @param stream - Whether to enable streaming (default: false).
65
+ * @returns An object containing the prepared payload.
66
+ */
67
+ getPayload(request: GPTRequest, stream: boolean = false): Record<string, any> {
68
+ let system = 'You are a computer programmer giving advice on how to write better code.'
69
+ if (request.messages && request.messages.length > 0 && request.messages[0].role === 'system') {
70
+ system = request.messages[0].content
71
+ request = { ...request, messages: request.messages.slice(1) }
72
+ }
73
+ return {
74
+ model: this.model.replace(':', '-'),
75
+ system,
76
+ messages:
77
+ request.messages && request.messages.length > 0
78
+ ? request.messages
79
+ : [{ role: 'user', content: request.prompt }],
80
+ max_tokens: request.maxTokens || 4096,
81
+ temperature: request.temperature || 0.2,
82
+ stream,
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Extracts the response text from the API response data.
88
+ * @param data - The API response data.
89
+ * @returns The extracted response text.
90
+ * @throws Error if the response structure is invalid.
91
+ */
92
+ extractResponse(data: any): string {
93
+ try {
94
+ if (data && data.content && Array.isArray(data.content) && data.content.length > 0) {
95
+ return data.content.map((item: any) => item.text).join('\n')
96
+ } else {
97
+ console.error('Unexpected response structure:', data)
98
+ throw new Error('Invalid response structure')
99
+ }
100
+ } catch (error) {
101
+ console.error('Error extracting response:', error)
102
+ throw error
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Converts streaming data to OpenAIStreamChunk format.
108
+ * @param data - The streaming data string.
109
+ * @returns An OpenAIStreamChunk object or null if parsing fails.
110
+ */
111
+ convertStream(data: string): OpenAIStreamChunk | null {
112
+ let beginningChar = 0
113
+ let lastChar = 0
114
+
115
+ while (lastChar < data.length) {
116
+ try {
117
+ let remainingString = data.slice(beginningChar, lastChar)
118
+
119
+ // Strip any leading characters until we encounter the first '{' in this block
120
+ const firstBraceIndex = remainingString.indexOf('{')
121
+ if (firstBraceIndex > -1) {
122
+ remainingString = remainingString.slice(firstBraceIndex)
123
+ }
124
+
125
+ const parsedObject = JSON.parse(remainingString)
126
+ const output = parsedObject?.delta?.text || ''
127
+
128
+ return {
129
+ output,
130
+ lastChar,
131
+ }
132
+ } catch (error) {
133
+ // Incrementally increase the size of the JSON string to parse
134
+ lastChar += 1
135
+ }
136
+ }
137
+
138
+ return null
139
+ }
140
+ }
@@ -0,0 +1,146 @@
1
+ import { Adapter } from '.'
2
+ import type { OpenAIStreamChunk, GPTRequest } from '../../utils/types'
3
+
4
+ /**
5
+ * GeminiAdapter class implements the Adapter interface for Google's Gemini AI model.
6
+ */
7
+ export class GeminiAdapter implements Adapter {
8
+ private model: string
9
+
10
+ static supportedModels: string[] = [
11
+ 'gemini:gemini-1.5:pro-latest',
12
+ 'gemini:gemini-1.5:flash-latest',
13
+ 'gemini:gemini-1.0:pro-latest',
14
+ ]
15
+
16
+ /**
17
+ * Creates a new GeminiAdapter instance.
18
+ * @param model - The Gemini model to use.
19
+ */
20
+ constructor(model: string) {
21
+ this.model = model
22
+ }
23
+
24
+ /**
25
+ * Lists all supported Gemini models to the console.
26
+ */
27
+ static async listSupportedModels(): Promise<void> {
28
+ GeminiAdapter.supportedModels.forEach((model) => {
29
+ console.log(model)
30
+ })
31
+ }
32
+
33
+ /**
34
+ * Gets the appropriate API endpoint URL based on whether streaming is required.
35
+ * @param stream - Whether to use streaming endpoint.
36
+ * @returns The API endpoint URL.
37
+ */
38
+ getEndpoint(stream: boolean = false): string {
39
+ if (stream) {
40
+ return `https://generativelanguage.googleapis.com/v1beta/models/${this.model.replace(':', '-')}:streamGenerateContent`
41
+ } else {
42
+ return `https://generativelanguage.googleapis.com/v1beta/models/${this.model.replace(':', '-')}:generateContent`
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Generates headers for the API request.
48
+ * @param apiKey - The API key for authentication.
49
+ * @returns An object containing the necessary headers.
50
+ */
51
+ getHeaders(apiKey: string): Record<string, string> {
52
+ return {
53
+ 'Content-Type': 'application/json',
54
+ 'x-goog-api-key': apiKey,
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Prepares the payload for the API request.
60
+ * @param request - The GPTRequest object containing prompt and other parameters.
61
+ * @param stream - Whether the request is for streaming.
62
+ * @returns The formatted payload object.
63
+ */
64
+ getPayload(request: GPTRequest, stream: boolean = false): Record<string, any> {
65
+ return {
66
+ contents: [
67
+ {
68
+ role: 'user', // or 'model' if generated from the model
69
+ parts: [
70
+ {
71
+ text: request.prompt,
72
+ },
73
+ ],
74
+ },
75
+ ],
76
+ generationConfig: {
77
+ temperature: request.temperature || 0.2,
78
+ topK: 40,
79
+ topP: 0.95,
80
+ maxOutputTokens: 1024,
81
+ },
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Extracts the response text from the API response data.
87
+ * @param data - The raw API response data.
88
+ * @returns The extracted response text.
89
+ * @throws Error if the response structure is invalid.
90
+ */
91
+ extractResponse(data: any): string {
92
+ try {
93
+ if (
94
+ data &&
95
+ data.candidates &&
96
+ data.candidates[0] &&
97
+ data.candidates[0].content &&
98
+ data.candidates[0].content.parts &&
99
+ data.candidates[0].content.parts[0]
100
+ ) {
101
+ return data.candidates[0].content.parts[0].text
102
+ } else {
103
+ console.error('Unexpected response structure:', data)
104
+ throw new Error('Invalid response structure')
105
+ }
106
+ } catch (error) {
107
+ console.error('Error extracting response:', error)
108
+ throw error
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Converts a stream chunk to an OpenAIStreamChunk format.
114
+ * @param data - The raw stream chunk data.
115
+ * @returns An OpenAIStreamChunk object or null if parsing fails.
116
+ */
117
+ convertStream(data: string): OpenAIStreamChunk | null {
118
+ let beginningChar = 0
119
+ let lastChar = 0
120
+
121
+ while (lastChar < data.length) {
122
+ try {
123
+ let remainingString = data.slice(beginningChar, lastChar)
124
+
125
+ // Strip any leading characters until we encounter the first '{' in this block
126
+ const firstBraceIndex = remainingString.indexOf('{')
127
+ if (firstBraceIndex > -1) {
128
+ remainingString = remainingString.slice(firstBraceIndex)
129
+ }
130
+
131
+ const parsedObject = JSON.parse(remainingString)
132
+ const output = parsedObject?.candidates[0]?.content?.parts[0]?.text || ''
133
+
134
+ return {
135
+ output,
136
+ lastChar,
137
+ }
138
+ } catch (error) {
139
+ // Incrementally increase the size of the JSON string to parse
140
+ lastChar += 1
141
+ }
142
+ }
143
+
144
+ return null
145
+ }
146
+ }
@@ -0,0 +1,43 @@
1
+ import type { GPTRequest, OpenAIStreamChunk } from '../../utils/types'
2
+
3
+ /**
4
+ * Interface defining the structure for GPT adapters.
5
+ * This interface allows for different implementations of GPT API interactions.
6
+ */
7
+ export interface Adapter {
8
+ /**
9
+ * Determines the appropriate endpoint URL based on whether streaming is enabled.
10
+ * @param stream - Boolean indicating if streaming is enabled.
11
+ * @returns The endpoint URL as a string.
12
+ */
13
+ getEndpoint(stream: boolean): string
14
+
15
+ /**
16
+ * Generates the necessary headers for the API request.
17
+ * @param apiKey - The API key for authentication.
18
+ * @returns An object containing the required headers.
19
+ */
20
+ getHeaders(apiKey: string): Record<string, string>
21
+
22
+ /**
23
+ * Prepares the payload for the API request.
24
+ * @param request - The GPTRequest object containing the request details.
25
+ * @param stream - Boolean indicating if streaming is enabled.
26
+ * @returns An object representing the request payload.
27
+ */
28
+ getPayload(request: GPTRequest, stream: boolean): Record<string, any>
29
+
30
+ /**
31
+ * Extracts the response from the API data.
32
+ * @param data - The raw data received from the API.
33
+ * @returns The extracted response as a string.
34
+ */
35
+ extractResponse(data: any): string
36
+
37
+ /**
38
+ * Converts streaming data into an OpenAIStreamChunk object.
39
+ * @param data - The raw streaming data as a string.
40
+ * @returns An OpenAIStreamChunk object or null if conversion is not possible.
41
+ */
42
+ convertStream(data: string): OpenAIStreamChunk | null
43
+ }
@@ -0,0 +1,142 @@
1
+ import { Adapter } from '.'
2
+ import type { OpenAIStreamChunk, GPTRequest } from '../../utils/types'
3
+
4
+ /**
5
+ * MistralAdapter class
6
+ * Implements the Adapter interface for Mistral AI models.
7
+ */
8
+ export class MistralAdapter implements Adapter {
9
+ private model: string
10
+
11
+ /**
12
+ * List of supported Mistral AI models
13
+ */
14
+ static supportedModels: string[] = [
15
+ 'mistral:mistral:embed',
16
+ 'mistral:mistral:tiny-latest',
17
+ 'mistral:mistral:small-latest',
18
+ 'mistral:mistral:medium-latest',
19
+ 'mistral:mistral:large-latest',
20
+ 'mistral:open-mistral:7b',
21
+ 'mistral:open-mixtral:8x7b',
22
+ 'mistral:open-mixtral:8x22b',
23
+ 'mistral:codestral:latest',
24
+ ]
25
+
26
+ /**
27
+ * Prints all supported models to the console
28
+ */
29
+ static listSupportedModels(): void {
30
+ MistralAdapter.supportedModels.forEach((model) => {
31
+ console.log(model)
32
+ })
33
+ }
34
+
35
+ /**
36
+ * Constructor
37
+ * @param model - The Mistral AI model to use
38
+ */
39
+ constructor(model: string) {
40
+ this.model = model
41
+ }
42
+
43
+ /**
44
+ * @returns The Mistral AI API endpoint
45
+ */
46
+ getEndpoint(): string {
47
+ return 'https://api.mistral.ai/v1/chat/completions'
48
+ }
49
+
50
+ /**
51
+ * @param apiKey - The API key for authentication
52
+ * @returns Headers for the API request
53
+ */
54
+ getHeaders(apiKey: string): Record<string, string> {
55
+ return {
56
+ Authorization: `Bearer ${apiKey}`,
57
+ 'Content-Type': 'application/json',
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Prepares the payload for the API request
63
+ * @param request - The GPT request object
64
+ * @param stream - Whether to use streaming mode
65
+ * @returns The formatted payload
66
+ */
67
+ getPayload(request: GPTRequest, stream: boolean = false): Record<string, any> {
68
+ return {
69
+ model: this.model.replace(':', '-'),
70
+ messages:
71
+ request.messages && request.messages.length > 0
72
+ ? request.messages
73
+ : [{ role: 'user', content: request.prompt }],
74
+ max_tokens: request.maxTokens,
75
+ temperature: request.temperature || 0.2,
76
+ stream,
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Extracts the response content from the API data
82
+ * @param data - The API response data
83
+ * @returns The extracted response content
84
+ */
85
+ extractResponse(data: any): string {
86
+ return data.choices[0].message.content
87
+ }
88
+
89
+ // data example:
90
+ // {
91
+ // id: 'chatcmpl-9tL477A98d54qvVQqJT010bm8BLJl',
92
+ // object: 'chat.completion.chunk',
93
+ // created: 1722976043,
94
+ // model: 'gpt-4o-2024-05-13',
95
+ // system_fingerprint: 'fp_3aa7262c27',
96
+ // choices: [
97
+ // {
98
+ // "index": 0,
99
+ // "delta": {
100
+ // "content": " basic"
101
+ // },
102
+ // "logprobs": null,
103
+ // "finish_reason": null
104
+ // }
105
+ // ]
106
+ // }
107
+
108
+ /**
109
+ * Converts streaming data to OpenAIStreamChunk format
110
+ * @param data - The streaming data string
111
+ * @returns Parsed OpenAIStreamChunk or null if parsing fails
112
+ */
113
+ convertStream(data: string): OpenAIStreamChunk | null {
114
+ let beginningChar = 0
115
+ let lastChar = 0
116
+
117
+ while (lastChar < data.length) {
118
+ try {
119
+ let remainingString = data.slice(beginningChar, lastChar)
120
+
121
+ // Strip any leading characters until we encounter the first '{' in this block
122
+ const firstBraceIndex = remainingString.indexOf('{')
123
+ if (firstBraceIndex > -1) {
124
+ remainingString = remainingString.slice(firstBraceIndex)
125
+ }
126
+
127
+ const parsedObject = JSON.parse(remainingString)
128
+ const output = parsedObject?.choices[0]?.delta?.content || ''
129
+
130
+ return {
131
+ output,
132
+ lastChar,
133
+ }
134
+ } catch (error) {
135
+ // Incrementally increase the size of the JSON string to parse
136
+ lastChar += 1
137
+ }
138
+ }
139
+
140
+ return null
141
+ }
142
+ }
@@ -0,0 +1,136 @@
1
+ import axios from 'axios'
2
+
3
+ import { Adapter } from '.'
4
+ import type { OpenAIStreamChunk, GPTRequest } from '../../utils/types'
5
+
6
+ /**
7
+ * Adapter for interacting with the Ollama API.
8
+ */
9
+ export class OllamaAdapter implements Adapter {
10
+ private model: string
11
+
12
+ static supportedModels: string[] = []
13
+
14
+ /**
15
+ * Constructor to initialize the OllamaAdapter with a specific model.
16
+ * @param model - The model to be used by the adapter.
17
+ */
18
+ constructor(model: string) {
19
+ this.model = model
20
+ }
21
+
22
+ /**
23
+ * Fetches and lists supported models from the Ollama API.
24
+ */
25
+ static async listSupportedModels(): Promise<void> {
26
+ try {
27
+ const endpoint = 'http://localhost:11434/api/tags'
28
+ const headers = {
29
+ 'Content-Type': 'application/json',
30
+ }
31
+
32
+ const response = await axios.get(endpoint, { headers })
33
+ const data = response.data
34
+
35
+ if (data && Array.isArray(data.models)) {
36
+ OllamaAdapter.supportedModels = data.models.map((model: any) => `ollama:${model.name}`)
37
+ OllamaAdapter.supportedModels.forEach((model) => {
38
+ console.log(model)
39
+ })
40
+ } else {
41
+ console.error('Unexpected response structure:', data)
42
+ }
43
+ } catch (error) {
44
+ // do nothing, no ollama server running
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Returns the endpoint URL for generating responses.
50
+ * @returns The endpoint URL as a string.
51
+ */
52
+ getEndpoint(): string {
53
+ return `http://localhost:11434/api/generate`
54
+ }
55
+
56
+ /**
57
+ * Returns the headers required for making API requests.
58
+ * @param apiKey - The API key for authorization.
59
+ * @returns An object containing the headers.
60
+ */
61
+ getHeaders(apiKey: string): Record<string, string> {
62
+ return {
63
+ Authorization: `Bearer ${apiKey}`,
64
+ 'Content-Type': 'application/json',
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Constructs the payload for the API request.
70
+ * @param request - The GPT request object containing the prompt.
71
+ * @param stream - Boolean indicating if the response should be streamed.
72
+ * @returns An object containing the payload.
73
+ */
74
+ getPayload(request: GPTRequest, stream: boolean = false): Record<string, any> {
75
+ return {
76
+ model: this.model,
77
+ prompt: request.prompt,
78
+ stream,
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Extracts the response from the API response data.
84
+ * @param data - The response data from the API.
85
+ * @returns The extracted response as a string.
86
+ * @throws Will throw an error if the response structure is invalid.
87
+ */
88
+ extractResponse(data: any): string {
89
+ try {
90
+ if (data && typeof data === 'object' && 'response' in data) {
91
+ return data.response
92
+ } else {
93
+ console.error('Unexpected response structure:', data)
94
+ throw new Error('Invalid response structure')
95
+ }
96
+ } catch (error) {
97
+ console.error('Error extracting response:', error)
98
+ throw error
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Converts a stream of data into an OpenAIStreamChunk.
104
+ * @param data - The string data to be converted.
105
+ * @returns An OpenAIStreamChunk object or null if conversion fails.
106
+ */
107
+ convertStream(data: string): OpenAIStreamChunk | null {
108
+ let beginningChar = 0
109
+ let lastChar = 0
110
+
111
+ while (lastChar < data.length) {
112
+ try {
113
+ let remainingString = data.slice(beginningChar, lastChar)
114
+
115
+ // Strip any leading characters until we encounter the first '{' in this block
116
+ const firstBraceIndex = remainingString.indexOf('{')
117
+ if (firstBraceIndex > -1) {
118
+ remainingString = remainingString.slice(firstBraceIndex)
119
+ }
120
+
121
+ const parsedObject = JSON.parse(remainingString)
122
+ const output = parsedObject?.response || ''
123
+
124
+ return {
125
+ output,
126
+ lastChar,
127
+ }
128
+ } catch (error) {
129
+ // Incrementally increase the size of the JSON string to parse
130
+ lastChar += 1
131
+ }
132
+ }
133
+
134
+ return null
135
+ }
136
+ }