codacus commited on
Commit
3c56346
·
unverified ·
1 Parent(s): 2ae897a

feat: enhance context handling by adding code context selection and implementing summary generation (#1091) #release

Browse files

* feat: add context annotation types and enhance file handling in LLM processing

* feat: enhance context handling by adding chatId to annotations and implementing summary generation

* removed useless changes

* feat: updated token counts to include optimization requests

* prompt fix

* logging added

* useless logs removed

app/components/chat/AssistantMessage.tsx CHANGED
@@ -1,6 +1,8 @@
1
  import { memo } from 'react';
2
  import { Markdown } from './Markdown';
3
  import type { JSONValue } from 'ai';
 
 
4
 
5
  interface AssistantMessageProps {
6
  content: string;
@@ -10,7 +12,12 @@ interface AssistantMessageProps {
10
  export const AssistantMessage = memo(({ content, annotations }: AssistantMessageProps) => {
11
  const filteredAnnotations = (annotations?.filter(
12
  (annotation: JSONValue) => annotation && typeof annotation === 'object' && Object.keys(annotation).includes('type'),
13
- ) || []) as { type: string; value: any }[];
 
 
 
 
 
14
 
15
  const usage: {
16
  completionTokens: number;
@@ -20,11 +27,18 @@ export const AssistantMessage = memo(({ content, annotations }: AssistantMessage
20
 
21
  return (
22
  <div className="overflow-hidden w-full">
23
- {usage && (
24
- <div className="text-sm text-bolt-elements-textSecondary mb-2">
25
- Tokens: {usage.totalTokens} (prompt: {usage.promptTokens}, completion: {usage.completionTokens})
 
 
 
 
 
 
 
26
  </div>
27
- )}
28
  <Markdown html>{content}</Markdown>
29
  </div>
30
  );
 
1
  import { memo } from 'react';
2
  import { Markdown } from './Markdown';
3
  import type { JSONValue } from 'ai';
4
+ import type { ProgressAnnotation } from '~/types/context';
5
+ import Popover from '~/components/ui/Popover';
6
 
7
  interface AssistantMessageProps {
8
  content: string;
 
12
  export const AssistantMessage = memo(({ content, annotations }: AssistantMessageProps) => {
13
  const filteredAnnotations = (annotations?.filter(
14
  (annotation: JSONValue) => annotation && typeof annotation === 'object' && Object.keys(annotation).includes('type'),
15
+ ) || []) as { type: string; value: any } & { [key: string]: any }[];
16
+
17
+ let progressAnnotation: ProgressAnnotation[] = filteredAnnotations.filter(
18
+ (annotation) => annotation.type === 'progress',
19
+ ) as ProgressAnnotation[];
20
+ progressAnnotation = progressAnnotation.sort((a, b) => b.value - a.value);
21
 
22
  const usage: {
23
  completionTokens: number;
 
27
 
28
  return (
29
  <div className="overflow-hidden w-full">
30
+ <>
31
+ <div className=" flex gap-2 items-center text-sm text-bolt-elements-textSecondary mb-2">
32
+ {progressAnnotation.length > 0 && (
33
+ <Popover trigger={<div className="i-ph:info" />}>{progressAnnotation[0].message}</Popover>
34
+ )}
35
+ {usage && (
36
+ <div>
37
+ Tokens: {usage.totalTokens} (prompt: {usage.promptTokens}, completion: {usage.completionTokens})
38
+ </div>
39
+ )}
40
  </div>
41
+ </>
42
  <Markdown html>{content}</Markdown>
43
  </div>
44
  );
app/components/ui/Popover.tsx ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as Popover from '@radix-ui/react-popover';
2
+ import type { PropsWithChildren, ReactNode } from 'react';
3
+
4
+ export default ({ children, trigger }: PropsWithChildren<{ trigger: ReactNode }>) => (
5
+ <Popover.Root>
6
+ <Popover.Trigger asChild>{trigger}</Popover.Trigger>
7
+ <Popover.Anchor />
8
+ <Popover.Portal>
9
+ <Popover.Content
10
+ sideOffset={10}
11
+ side="top"
12
+ align="center"
13
+ className="bg-bolt-elements-background-depth-2 text-bolt-elements-item-contentAccent p-2 rounded-md shadow-xl z-workbench"
14
+ >
15
+ {children}
16
+ <Popover.Arrow className="bg-bolt-elements-item-background-depth-2" />
17
+ </Popover.Content>
18
+ </Popover.Portal>
19
+ </Popover.Root>
20
+ );
app/entry.server.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import type { AppLoadContext, EntryContext } from '@remix-run/cloudflare';
2
  import { RemixServer } from '@remix-run/react';
3
  import { isbot } from 'isbot';
4
  import { renderToReadableStream } from 'react-dom/server';
@@ -10,7 +10,7 @@ export default async function handleRequest(
10
  request: Request,
11
  responseStatusCode: number,
12
  responseHeaders: Headers,
13
- remixContext: EntryContext,
14
  _loadContext: AppLoadContext,
15
  ) {
16
  // await initializeModelList({});
 
1
+ import type { AppLoadContext } from '@remix-run/cloudflare';
2
  import { RemixServer } from '@remix-run/react';
3
  import { isbot } from 'isbot';
4
  import { renderToReadableStream } from 'react-dom/server';
 
10
  request: Request,
11
  responseStatusCode: number,
12
  responseHeaders: Headers,
13
+ remixContext: any,
14
  _loadContext: AppLoadContext,
15
  ) {
16
  // await initializeModelList({});
app/lib/.server/llm/constants.ts CHANGED
@@ -3,3 +3,36 @@ export const MAX_TOKENS = 8000;
3
 
4
  // limits the number of model responses that can be returned in a single request
5
  export const MAX_RESPONSE_SEGMENTS = 2;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  // limits the number of model responses that can be returned in a single request
5
  export const MAX_RESPONSE_SEGMENTS = 2;
6
+
7
+ export interface File {
8
+ type: 'file';
9
+ content: string;
10
+ isBinary: boolean;
11
+ }
12
+
13
+ export interface Folder {
14
+ type: 'folder';
15
+ }
16
+
17
+ type Dirent = File | Folder;
18
+
19
+ export type FileMap = Record<string, Dirent | undefined>;
20
+
21
+ export const IGNORE_PATTERNS = [
22
+ 'node_modules/**',
23
+ '.git/**',
24
+ 'dist/**',
25
+ 'build/**',
26
+ '.next/**',
27
+ 'coverage/**',
28
+ '.cache/**',
29
+ '.vscode/**',
30
+ '.idea/**',
31
+ '**/*.log',
32
+ '**/.DS_Store',
33
+ '**/npm-debug.log*',
34
+ '**/yarn-debug.log*',
35
+ '**/yarn-error.log*',
36
+ '**/*lock.json',
37
+ '**/*lock.yml',
38
+ ];
app/lib/.server/llm/create-summary.ts ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { generateText, type CoreTool, type GenerateTextResult, type Message } from 'ai';
2
+ import type { IProviderSetting } from '~/types/model';
3
+ import { DEFAULT_MODEL, DEFAULT_PROVIDER, PROVIDER_LIST } from '~/utils/constants';
4
+ import { extractCurrentContext, extractPropertiesFromMessage, simplifyBoltActions } from './utils';
5
+ import { createScopedLogger } from '~/utils/logger';
6
+ import { LLMManager } from '~/lib/modules/llm/manager';
7
+
8
+ const logger = createScopedLogger('create-summary');
9
+
10
+ export async function createSummary(props: {
11
+ messages: Message[];
12
+ env?: Env;
13
+ apiKeys?: Record<string, string>;
14
+ providerSettings?: Record<string, IProviderSetting>;
15
+ promptId?: string;
16
+ contextOptimization?: boolean;
17
+ onFinish?: (resp: GenerateTextResult<Record<string, CoreTool<any, any>>, never>) => void;
18
+ }) {
19
+ const { messages, env: serverEnv, apiKeys, providerSettings, contextOptimization, onFinish } = props;
20
+ let currentModel = DEFAULT_MODEL;
21
+ let currentProvider = DEFAULT_PROVIDER.name;
22
+ const processedMessages = messages.map((message) => {
23
+ if (message.role === 'user') {
24
+ const { model, provider, content } = extractPropertiesFromMessage(message);
25
+ currentModel = model;
26
+ currentProvider = provider;
27
+
28
+ return { ...message, content };
29
+ } else if (message.role == 'assistant') {
30
+ let content = message.content;
31
+
32
+ if (contextOptimization) {
33
+ content = simplifyBoltActions(content);
34
+ }
35
+
36
+ return { ...message, content };
37
+ }
38
+
39
+ return message;
40
+ });
41
+
42
+ const provider = PROVIDER_LIST.find((p) => p.name === currentProvider) || DEFAULT_PROVIDER;
43
+ const staticModels = LLMManager.getInstance().getStaticModelListFromProvider(provider);
44
+ let modelDetails = staticModels.find((m) => m.name === currentModel);
45
+
46
+ if (!modelDetails) {
47
+ const modelsList = [
48
+ ...(provider.staticModels || []),
49
+ ...(await LLMManager.getInstance().getModelListFromProvider(provider, {
50
+ apiKeys,
51
+ providerSettings,
52
+ serverEnv: serverEnv as any,
53
+ })),
54
+ ];
55
+
56
+ if (!modelsList.length) {
57
+ throw new Error(`No models found for provider ${provider.name}`);
58
+ }
59
+
60
+ modelDetails = modelsList.find((m) => m.name === currentModel);
61
+
62
+ if (!modelDetails) {
63
+ // Fallback to first model
64
+ logger.warn(
65
+ `MODEL [${currentModel}] not found in provider [${provider.name}]. Falling back to first model. ${modelsList[0].name}`,
66
+ );
67
+ modelDetails = modelsList[0];
68
+ }
69
+ }
70
+
71
+ let slicedMessages = processedMessages;
72
+ const { summary } = extractCurrentContext(processedMessages);
73
+ let summaryText: string | undefined = undefined;
74
+ let chatId: string | undefined = undefined;
75
+
76
+ if (summary && summary.type === 'chatSummary') {
77
+ chatId = summary.chatId;
78
+ summaryText = `Below is the Chat Summary till now, this is chat summary before the conversation provided by the user
79
+ you should also use this as historical message while providing the response to the user.
80
+ ${summary.summary}`;
81
+
82
+ if (chatId) {
83
+ let index = 0;
84
+
85
+ for (let i = 0; i < processedMessages.length; i++) {
86
+ if (processedMessages[i].id === chatId) {
87
+ index = i;
88
+ break;
89
+ }
90
+ }
91
+ slicedMessages = processedMessages.slice(index + 1);
92
+ }
93
+ }
94
+
95
+ const extractTextContent = (message: Message) =>
96
+ Array.isArray(message.content)
97
+ ? (message.content.find((item) => item.type === 'text')?.text as string) || ''
98
+ : message.content;
99
+
100
+ // select files from the list of code file from the project that might be useful for the current request from the user
101
+ const resp = await generateText({
102
+ system: `
103
+ You are a software engineer. You are working on a project. tou need to summarize the work till now and provide a summary of the chat till now.
104
+
105
+ ${summaryText}
106
+
107
+ RULES:
108
+ * Only provide the summary of the chat till now.
109
+ * Do not provide any new information.
110
+ `,
111
+ prompt: `
112
+ please provide a summary of the chat till now.
113
+ below is the latest chat:
114
+
115
+ ---
116
+ ${slicedMessages
117
+ .map((x) => {
118
+ return `---\n[${x.role}] ${extractTextContent(x)}\n---`;
119
+ })
120
+ .join('\n')}
121
+ ---
122
+ `,
123
+ model: provider.getModelInstance({
124
+ model: currentModel,
125
+ serverEnv,
126
+ apiKeys,
127
+ providerSettings,
128
+ }),
129
+ });
130
+
131
+ const response = resp.text;
132
+
133
+ if (onFinish) {
134
+ onFinish(resp);
135
+ }
136
+
137
+ return response;
138
+ }
app/lib/.server/llm/select-context.ts ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { generateText, type CoreTool, type GenerateTextResult, type Message } from 'ai';
2
+ import ignore from 'ignore';
3
+ import type { IProviderSetting } from '~/types/model';
4
+ import { IGNORE_PATTERNS, type FileMap } from './constants';
5
+ import { DEFAULT_MODEL, DEFAULT_PROVIDER, PROVIDER_LIST } from '~/utils/constants';
6
+ import { createFilesContext, extractCurrentContext, extractPropertiesFromMessage, simplifyBoltActions } from './utils';
7
+ import { createScopedLogger } from '~/utils/logger';
8
+ import { LLMManager } from '~/lib/modules/llm/manager';
9
+
10
+ // Common patterns to ignore, similar to .gitignore
11
+
12
+ const ig = ignore().add(IGNORE_PATTERNS);
13
+ const logger = createScopedLogger('select-context');
14
+
15
+ export async function selectContext(props: {
16
+ messages: Message[];
17
+ env?: Env;
18
+ apiKeys?: Record<string, string>;
19
+ files: FileMap;
20
+ providerSettings?: Record<string, IProviderSetting>;
21
+ promptId?: string;
22
+ contextOptimization?: boolean;
23
+ summary: string;
24
+ onFinish?: (resp: GenerateTextResult<Record<string, CoreTool<any, any>>, never>) => void;
25
+ }) {
26
+ const { messages, env: serverEnv, apiKeys, files, providerSettings, contextOptimization, summary, onFinish } = props;
27
+ let currentModel = DEFAULT_MODEL;
28
+ let currentProvider = DEFAULT_PROVIDER.name;
29
+ const processedMessages = messages.map((message) => {
30
+ if (message.role === 'user') {
31
+ const { model, provider, content } = extractPropertiesFromMessage(message);
32
+ currentModel = model;
33
+ currentProvider = provider;
34
+
35
+ return { ...message, content };
36
+ } else if (message.role == 'assistant') {
37
+ let content = message.content;
38
+
39
+ if (contextOptimization) {
40
+ content = simplifyBoltActions(content);
41
+ }
42
+
43
+ return { ...message, content };
44
+ }
45
+
46
+ return message;
47
+ });
48
+
49
+ const provider = PROVIDER_LIST.find((p) => p.name === currentProvider) || DEFAULT_PROVIDER;
50
+ const staticModels = LLMManager.getInstance().getStaticModelListFromProvider(provider);
51
+ let modelDetails = staticModels.find((m) => m.name === currentModel);
52
+
53
+ if (!modelDetails) {
54
+ const modelsList = [
55
+ ...(provider.staticModels || []),
56
+ ...(await LLMManager.getInstance().getModelListFromProvider(provider, {
57
+ apiKeys,
58
+ providerSettings,
59
+ serverEnv: serverEnv as any,
60
+ })),
61
+ ];
62
+
63
+ if (!modelsList.length) {
64
+ throw new Error(`No models found for provider ${provider.name}`);
65
+ }
66
+
67
+ modelDetails = modelsList.find((m) => m.name === currentModel);
68
+
69
+ if (!modelDetails) {
70
+ // Fallback to first model
71
+ logger.warn(
72
+ `MODEL [${currentModel}] not found in provider [${provider.name}]. Falling back to first model. ${modelsList[0].name}`,
73
+ );
74
+ modelDetails = modelsList[0];
75
+ }
76
+ }
77
+
78
+ const { codeContext } = extractCurrentContext(processedMessages);
79
+
80
+ let filePaths = getFilePaths(files || {});
81
+ filePaths = filePaths.filter((x) => {
82
+ const relPath = x.replace('/home/project/', '');
83
+ return !ig.ignores(relPath);
84
+ });
85
+
86
+ let context = '';
87
+ const currrentFiles: string[] = [];
88
+ const contextFiles: FileMap = {};
89
+
90
+ if (codeContext?.type === 'codeContext') {
91
+ const codeContextFiles: string[] = codeContext.files;
92
+ Object.keys(files || {}).forEach((path) => {
93
+ let relativePath = path;
94
+
95
+ if (path.startsWith('/home/project/')) {
96
+ relativePath = path.replace('/home/project/', '');
97
+ }
98
+
99
+ if (codeContextFiles.includes(relativePath)) {
100
+ contextFiles[relativePath] = files[path];
101
+ currrentFiles.push(relativePath);
102
+ }
103
+ });
104
+ context = createFilesContext(contextFiles);
105
+ }
106
+
107
+ const summaryText = `Here is the summary of the chat till now: ${summary}`;
108
+
109
+ const extractTextContent = (message: Message) =>
110
+ Array.isArray(message.content)
111
+ ? (message.content.find((item) => item.type === 'text')?.text as string) || ''
112
+ : message.content;
113
+
114
+ const lastUserMessage = processedMessages.filter((x) => x.role == 'user').pop();
115
+
116
+ if (!lastUserMessage) {
117
+ throw new Error('No user message found');
118
+ }
119
+
120
+ // select files from the list of code file from the project that might be useful for the current request from the user
121
+ const resp = await generateText({
122
+ system: `
123
+ You are a software engineer. You are working on a project. You have access to the following files:
124
+
125
+ AVAILABLE FILES PATHS
126
+ ---
127
+ ${filePaths.map((path) => `- ${path}`).join('\n')}
128
+ ---
129
+
130
+ You have following code loaded in the context buffer that you can refer to:
131
+
132
+ CURRENT CONTEXT BUFFER
133
+ ---
134
+ ${context}
135
+ ---
136
+
137
+ Now, you are given a task. You need to select the files that are relevant to the task from the list of files above.
138
+
139
+ RESPONSE FORMAT:
140
+ your response shoudl be in following format:
141
+ ---
142
+ <updateContextBuffer>
143
+ <includeFile path="path/to/file"/>
144
+ <excludeFile path="path/to/file"/>
145
+ </updateContextBuffer>
146
+ ---
147
+ * Your should start with <updateContextBuffer> and end with </updateContextBuffer>.
148
+ * You can include multiple <includeFile> and <excludeFile> tags in the response.
149
+ * You should not include any other text in the response.
150
+ * You should not include any file that is not in the list of files above.
151
+ * You should not include any file that is already in the context buffer.
152
+ * If no changes are needed, you can leave the response empty updateContextBuffer tag.
153
+ `,
154
+ prompt: `
155
+ ${summaryText}
156
+
157
+ Users Question: ${extractTextContent(lastUserMessage)}
158
+
159
+ update the context buffer with the files that are relevant to the task from the list of files above.
160
+
161
+ CRITICAL RULES:
162
+ * Only include relevant files in the context buffer.
163
+ * context buffer should not include any file that is not in the list of files above.
164
+ * context buffer is extremlly expensive, so only include files that are absolutely necessary.
165
+ * If no changes are needed, you can leave the response empty updateContextBuffer tag.
166
+ * Only 5 files can be placed in the context buffer at a time.
167
+ * if the buffer is full, you need to exclude files that is not needed and include files that is relevent.
168
+
169
+ `,
170
+ model: provider.getModelInstance({
171
+ model: currentModel,
172
+ serverEnv,
173
+ apiKeys,
174
+ providerSettings,
175
+ }),
176
+ });
177
+
178
+ const response = resp.text;
179
+ const updateContextBuffer = response.match(/<updateContextBuffer>([\s\S]*?)<\/updateContextBuffer>/);
180
+
181
+ if (!updateContextBuffer) {
182
+ throw new Error('Invalid response. Please follow the response format');
183
+ }
184
+
185
+ const includeFiles =
186
+ updateContextBuffer[1]
187
+ .match(/<includeFile path="(.*?)"/gm)
188
+ ?.map((x) => x.replace('<includeFile path="', '').replace('"', '')) || [];
189
+ const excludeFiles =
190
+ updateContextBuffer[1]
191
+ .match(/<excludeFile path="(.*?)"/gm)
192
+ ?.map((x) => x.replace('<excludeFile path="', '').replace('"', '')) || [];
193
+
194
+ const filteredFiles: FileMap = {};
195
+ excludeFiles.forEach((path) => {
196
+ delete contextFiles[path];
197
+ });
198
+ includeFiles.forEach((path) => {
199
+ let fullPath = path;
200
+
201
+ if (!path.startsWith('/home/project/')) {
202
+ fullPath = `/home/project/${path}`;
203
+ }
204
+
205
+ if (!filePaths.includes(fullPath)) {
206
+ throw new Error(`File ${path} is not in the list of files above.`);
207
+ }
208
+
209
+ if (currrentFiles.includes(path)) {
210
+ return;
211
+ }
212
+
213
+ filteredFiles[path] = files[fullPath];
214
+ });
215
+
216
+ if (onFinish) {
217
+ onFinish(resp);
218
+ }
219
+
220
+ return filteredFiles;
221
+
222
+ // generateText({
223
+ }
224
+
225
+ export function getFilePaths(files: FileMap) {
226
+ let filePaths = Object.keys(files);
227
+ filePaths = filePaths.filter((x) => {
228
+ const relPath = x.replace('/home/project/', '');
229
+ return !ig.ignores(relPath);
230
+ });
231
+
232
+ return filePaths;
233
+ }
app/lib/.server/llm/stream-text.ts CHANGED
@@ -1,162 +1,48 @@
1
- import { convertToCoreMessages, streamText as _streamText } from 'ai';
2
- import { MAX_TOKENS } from './constants';
3
  import { getSystemPrompt } from '~/lib/common/prompts/prompts';
4
- import {
5
- DEFAULT_MODEL,
6
- DEFAULT_PROVIDER,
7
- MODEL_REGEX,
8
- MODIFICATIONS_TAG_NAME,
9
- PROVIDER_LIST,
10
- PROVIDER_REGEX,
11
- WORK_DIR,
12
- } from '~/utils/constants';
13
- import ignore from 'ignore';
14
  import type { IProviderSetting } from '~/types/model';
15
  import { PromptLibrary } from '~/lib/common/prompt-library';
16
  import { allowedHTMLElements } from '~/utils/markdown';
17
  import { LLMManager } from '~/lib/modules/llm/manager';
18
  import { createScopedLogger } from '~/utils/logger';
19
-
20
- interface ToolResult<Name extends string, Args, Result> {
21
- toolCallId: string;
22
- toolName: Name;
23
- args: Args;
24
- result: Result;
25
- }
26
-
27
- interface Message {
28
- role: 'user' | 'assistant';
29
- content: string;
30
- toolInvocations?: ToolResult<string, unknown, unknown>[];
31
- model?: string;
32
- }
33
 
34
  export type Messages = Message[];
35
 
36
  export type StreamingOptions = Omit<Parameters<typeof _streamText>[0], 'model'>;
37
 
38
- export interface File {
39
- type: 'file';
40
- content: string;
41
- isBinary: boolean;
42
- }
43
-
44
- export interface Folder {
45
- type: 'folder';
46
- }
47
-
48
- type Dirent = File | Folder;
49
-
50
- export type FileMap = Record<string, Dirent | undefined>;
51
-
52
- export function simplifyBoltActions(input: string): string {
53
- // Using regex to match boltAction tags that have type="file"
54
- const regex = /(<boltAction[^>]*type="file"[^>]*>)([\s\S]*?)(<\/boltAction>)/g;
55
-
56
- // Replace each matching occurrence
57
- return input.replace(regex, (_0, openingTag, _2, closingTag) => {
58
- return `${openingTag}\n ...\n ${closingTag}`;
59
- });
60
- }
61
-
62
- // Common patterns to ignore, similar to .gitignore
63
- const IGNORE_PATTERNS = [
64
- 'node_modules/**',
65
- '.git/**',
66
- 'dist/**',
67
- 'build/**',
68
- '.next/**',
69
- 'coverage/**',
70
- '.cache/**',
71
- '.vscode/**',
72
- '.idea/**',
73
- '**/*.log',
74
- '**/.DS_Store',
75
- '**/npm-debug.log*',
76
- '**/yarn-debug.log*',
77
- '**/yarn-error.log*',
78
- '**/*lock.json',
79
- '**/*lock.yml',
80
- ];
81
- const ig = ignore().add(IGNORE_PATTERNS);
82
-
83
- function createFilesContext(files: FileMap) {
84
- let filePaths = Object.keys(files);
85
- filePaths = filePaths.filter((x) => {
86
- const relPath = x.replace('/home/project/', '');
87
- return !ig.ignores(relPath);
88
- });
89
-
90
- const fileContexts = filePaths
91
- .filter((x) => files[x] && files[x].type == 'file')
92
- .map((path) => {
93
- const dirent = files[path];
94
-
95
- if (!dirent || dirent.type == 'folder') {
96
- return '';
97
- }
98
-
99
- return `<file path="${path}">\n${dirent.content}\n</file>`;
100
- });
101
-
102
- return `Below are the code files present in the webcontainer:\n <codebase>\n${fileContexts.join('\n\n')}\n</codebase>`;
103
- }
104
-
105
- function extractPropertiesFromMessage(message: Message): { model: string; provider: string; content: string } {
106
- const textContent = Array.isArray(message.content)
107
- ? message.content.find((item) => item.type === 'text')?.text || ''
108
- : message.content;
109
-
110
- const modelMatch = textContent.match(MODEL_REGEX);
111
- const providerMatch = textContent.match(PROVIDER_REGEX);
112
-
113
- /*
114
- * Extract model
115
- * const modelMatch = message.content.match(MODEL_REGEX);
116
- */
117
- const model = modelMatch ? modelMatch[1] : DEFAULT_MODEL;
118
-
119
- /*
120
- * Extract provider
121
- * const providerMatch = message.content.match(PROVIDER_REGEX);
122
- */
123
- const provider = providerMatch ? providerMatch[1] : DEFAULT_PROVIDER.name;
124
-
125
- const cleanedContent = Array.isArray(message.content)
126
- ? message.content.map((item) => {
127
- if (item.type === 'text') {
128
- return {
129
- type: 'text',
130
- text: item.text?.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, ''),
131
- };
132
- }
133
-
134
- return item; // Preserve image_url and other types as is
135
- })
136
- : textContent.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, '');
137
-
138
- return { model, provider, content: cleanedContent };
139
- }
140
-
141
  const logger = createScopedLogger('stream-text');
142
 
143
  export async function streamText(props: {
144
- messages: Messages;
145
- env: Env;
146
  options?: StreamingOptions;
147
  apiKeys?: Record<string, string>;
148
  files?: FileMap;
149
  providerSettings?: Record<string, IProviderSetting>;
150
  promptId?: string;
151
  contextOptimization?: boolean;
 
 
152
  }) {
153
- const { messages, env: serverEnv, options, apiKeys, files, providerSettings, promptId, contextOptimization } = props;
154
-
155
- // console.log({serverEnv});
156
-
 
 
 
 
 
 
 
 
157
  let currentModel = DEFAULT_MODEL;
158
  let currentProvider = DEFAULT_PROVIDER.name;
159
- const processedMessages = messages.map((message) => {
160
  if (message.role === 'user') {
161
  const { model, provider, content } = extractPropertiesFromMessage(message);
162
  currentModel = model;
@@ -214,13 +100,44 @@ export async function streamText(props: {
214
  modificationTagName: MODIFICATIONS_TAG_NAME,
215
  }) ?? getSystemPrompt();
216
 
217
- if (files && contextOptimization) {
218
- const codeContext = createFilesContext(files);
219
- systemPrompt = `${systemPrompt}\n\n ${codeContext}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  }
221
 
222
  logger.info(`Sending llm call to ${provider.name} with model ${modelDetails.name}`);
223
 
 
 
224
  return await _streamText({
225
  model: provider.getModelInstance({
226
  model: modelDetails.name,
 
1
+ import { convertToCoreMessages, streamText as _streamText, type Message } from 'ai';
2
+ import { MAX_TOKENS, type FileMap } from './constants';
3
  import { getSystemPrompt } from '~/lib/common/prompts/prompts';
4
+ import { DEFAULT_MODEL, DEFAULT_PROVIDER, MODIFICATIONS_TAG_NAME, PROVIDER_LIST, WORK_DIR } from '~/utils/constants';
 
 
 
 
 
 
 
 
 
5
  import type { IProviderSetting } from '~/types/model';
6
  import { PromptLibrary } from '~/lib/common/prompt-library';
7
  import { allowedHTMLElements } from '~/utils/markdown';
8
  import { LLMManager } from '~/lib/modules/llm/manager';
9
  import { createScopedLogger } from '~/utils/logger';
10
+ import { createFilesContext, extractPropertiesFromMessage, simplifyBoltActions } from './utils';
11
+ import { getFilePaths } from './select-context';
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  export type Messages = Message[];
14
 
15
  export type StreamingOptions = Omit<Parameters<typeof _streamText>[0], 'model'>;
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  const logger = createScopedLogger('stream-text');
18
 
19
  export async function streamText(props: {
20
+ messages: Omit<Message, 'id'>[];
21
+ env?: Env;
22
  options?: StreamingOptions;
23
  apiKeys?: Record<string, string>;
24
  files?: FileMap;
25
  providerSettings?: Record<string, IProviderSetting>;
26
  promptId?: string;
27
  contextOptimization?: boolean;
28
+ contextFiles?: FileMap;
29
+ summary?: string;
30
  }) {
31
+ const {
32
+ messages,
33
+ env: serverEnv,
34
+ options,
35
+ apiKeys,
36
+ files,
37
+ providerSettings,
38
+ promptId,
39
+ contextOptimization,
40
+ contextFiles,
41
+ summary,
42
+ } = props;
43
  let currentModel = DEFAULT_MODEL;
44
  let currentProvider = DEFAULT_PROVIDER.name;
45
+ let processedMessages = messages.map((message) => {
46
  if (message.role === 'user') {
47
  const { model, provider, content } = extractPropertiesFromMessage(message);
48
  currentModel = model;
 
100
  modificationTagName: MODIFICATIONS_TAG_NAME,
101
  }) ?? getSystemPrompt();
102
 
103
+ if (files && contextFiles && contextOptimization) {
104
+ const codeContext = createFilesContext(contextFiles, true);
105
+ const filePaths = getFilePaths(files);
106
+
107
+ systemPrompt = `${systemPrompt}
108
+ Below are all the files present in the project:
109
+ ---
110
+ ${filePaths.join('\n')}
111
+ ---
112
+
113
+ Below is the context loaded into context buffer for you to have knowledge of and might need changes to fullfill current user request.
114
+ CONTEXT BUFFER:
115
+ ---
116
+ ${codeContext}
117
+ ---
118
+ `;
119
+
120
+ if (summary) {
121
+ systemPrompt = `${systemPrompt}
122
+ below is the chat history till now
123
+ CHAT SUMMARY:
124
+ ---
125
+ ${props.summary}
126
+ ---
127
+ `;
128
+
129
+ const lastMessage = processedMessages.pop();
130
+
131
+ if (lastMessage) {
132
+ processedMessages = [lastMessage];
133
+ }
134
+ }
135
  }
136
 
137
  logger.info(`Sending llm call to ${provider.name} with model ${modelDetails.name}`);
138
 
139
+ // console.log(systemPrompt,processedMessages);
140
+
141
  return await _streamText({
142
  model: provider.getModelInstance({
143
  model: modelDetails.name,
app/lib/.server/llm/utils.ts ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { type Message } from 'ai';
2
+ import { DEFAULT_MODEL, DEFAULT_PROVIDER, MODEL_REGEX, PROVIDER_REGEX } from '~/utils/constants';
3
+ import { IGNORE_PATTERNS, type FileMap } from './constants';
4
+ import ignore from 'ignore';
5
+ import type { ContextAnnotation } from '~/types/context';
6
+
7
+ export function extractPropertiesFromMessage(message: Omit<Message, 'id'>): {
8
+ model: string;
9
+ provider: string;
10
+ content: string;
11
+ } {
12
+ const textContent = Array.isArray(message.content)
13
+ ? message.content.find((item) => item.type === 'text')?.text || ''
14
+ : message.content;
15
+
16
+ const modelMatch = textContent.match(MODEL_REGEX);
17
+ const providerMatch = textContent.match(PROVIDER_REGEX);
18
+
19
+ /*
20
+ * Extract model
21
+ * const modelMatch = message.content.match(MODEL_REGEX);
22
+ */
23
+ const model = modelMatch ? modelMatch[1] : DEFAULT_MODEL;
24
+
25
+ /*
26
+ * Extract provider
27
+ * const providerMatch = message.content.match(PROVIDER_REGEX);
28
+ */
29
+ const provider = providerMatch ? providerMatch[1] : DEFAULT_PROVIDER.name;
30
+
31
+ const cleanedContent = Array.isArray(message.content)
32
+ ? message.content.map((item) => {
33
+ if (item.type === 'text') {
34
+ return {
35
+ type: 'text',
36
+ text: item.text?.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, ''),
37
+ };
38
+ }
39
+
40
+ return item; // Preserve image_url and other types as is
41
+ })
42
+ : textContent.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, '');
43
+
44
+ return { model, provider, content: cleanedContent };
45
+ }
46
+
47
+ export function simplifyBoltActions(input: string): string {
48
+ // Using regex to match boltAction tags that have type="file"
49
+ const regex = /(<boltAction[^>]*type="file"[^>]*>)([\s\S]*?)(<\/boltAction>)/g;
50
+
51
+ // Replace each matching occurrence
52
+ return input.replace(regex, (_0, openingTag, _2, closingTag) => {
53
+ return `${openingTag}\n ...\n ${closingTag}`;
54
+ });
55
+ }
56
+
57
+ export function createFilesContext(files: FileMap, useRelativePath?: boolean) {
58
+ const ig = ignore().add(IGNORE_PATTERNS);
59
+ let filePaths = Object.keys(files);
60
+ filePaths = filePaths.filter((x) => {
61
+ const relPath = x.replace('/home/project/', '');
62
+ return !ig.ignores(relPath);
63
+ });
64
+
65
+ const fileContexts = filePaths
66
+ .filter((x) => files[x] && files[x].type == 'file')
67
+ .map((path) => {
68
+ const dirent = files[path];
69
+
70
+ if (!dirent || dirent.type == 'folder') {
71
+ return '';
72
+ }
73
+
74
+ const codeWithLinesNumbers = dirent.content
75
+ .split('\n')
76
+ // .map((v, i) => `${i + 1}|${v}`)
77
+ .join('\n');
78
+
79
+ let filePath = path;
80
+
81
+ if (useRelativePath) {
82
+ filePath = path.replace('/home/project/', '');
83
+ }
84
+
85
+ return `<file path="${filePath}">\n${codeWithLinesNumbers}\n</file>`;
86
+ });
87
+
88
+ return `<codebase>${fileContexts.join('\n\n')}\n\n</codebase>`;
89
+ }
90
+
91
+ export function extractCurrentContext(messages: Message[]) {
92
+ const lastAssistantMessage = messages.filter((x) => x.role == 'assistant').slice(-1)[0];
93
+
94
+ if (!lastAssistantMessage) {
95
+ return { summary: undefined, codeContext: undefined };
96
+ }
97
+
98
+ let summary: ContextAnnotation | undefined;
99
+ let codeContext: ContextAnnotation | undefined;
100
+
101
+ if (!lastAssistantMessage.annotations?.length) {
102
+ return { summary: undefined, codeContext: undefined };
103
+ }
104
+
105
+ for (let i = 0; i < lastAssistantMessage.annotations.length; i++) {
106
+ const annotation = lastAssistantMessage.annotations[i];
107
+
108
+ if (!annotation || typeof annotation !== 'object') {
109
+ continue;
110
+ }
111
+
112
+ if (!(annotation as any).type) {
113
+ continue;
114
+ }
115
+
116
+ const annotationObject = annotation as any;
117
+
118
+ if (annotationObject.type === 'codeContext') {
119
+ codeContext = annotationObject;
120
+ break;
121
+ } else if (annotationObject.type === 'chatSummary') {
122
+ summary = annotationObject;
123
+ break;
124
+ }
125
+ }
126
+
127
+ return { summary, codeContext };
128
+ }
app/lib/modules/llm/base-provider.ts CHANGED
@@ -111,7 +111,7 @@ export abstract class BaseProvider implements ProviderInfo {
111
 
112
  abstract getModelInstance(options: {
113
  model: string;
114
- serverEnv: Env;
115
  apiKeys?: Record<string, string>;
116
  providerSettings?: Record<string, IProviderSetting>;
117
  }): LanguageModelV1;
 
111
 
112
  abstract getModelInstance(options: {
113
  model: string;
114
+ serverEnv?: Env;
115
  apiKeys?: Record<string, string>;
116
  providerSettings?: Record<string, IProviderSetting>;
117
  }): LanguageModelV1;
app/routes/api.chat.ts CHANGED
@@ -1,11 +1,15 @@
1
  import { type ActionFunctionArgs } from '@remix-run/cloudflare';
2
- import { createDataStream } from 'ai';
3
- import { MAX_RESPONSE_SEGMENTS, MAX_TOKENS } from '~/lib/.server/llm/constants';
4
  import { CONTINUE_PROMPT } from '~/lib/common/prompts/prompts';
5
  import { streamText, type Messages, type StreamingOptions } from '~/lib/.server/llm/stream-text';
6
  import SwitchableStream from '~/lib/.server/llm/switchable-stream';
7
  import type { IProviderSetting } from '~/types/model';
8
  import { createScopedLogger } from '~/utils/logger';
 
 
 
 
9
 
10
  export async function action(args: ActionFunctionArgs) {
11
  return chatAction(args);
@@ -52,23 +56,121 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
52
  promptTokens: 0,
53
  totalTokens: 0,
54
  };
 
 
55
 
56
  try {
57
- const options: StreamingOptions = {
58
- toolChoice: 'none',
59
- onFinish: async ({ text: content, finishReason, usage }) => {
60
- logger.debug('usage', JSON.stringify(usage));
61
-
62
- if (usage) {
63
- cumulativeUsage.completionTokens += usage.completionTokens || 0;
64
- cumulativeUsage.promptTokens += usage.promptTokens || 0;
65
- cumulativeUsage.totalTokens += usage.totalTokens || 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  }
67
 
68
- if (finishReason !== 'length') {
69
- const encoder = new TextEncoder();
70
- const usageStream = createDataStream({
71
- async execute(dataStream) {
 
 
 
 
 
 
 
 
 
72
  dataStream.writeMessageAnnotation({
73
  type: 'usage',
74
  value: {
@@ -77,80 +179,89 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
77
  totalTokens: cumulativeUsage.totalTokens,
78
  },
79
  });
80
- },
81
- onError: (error: any) => `Custom error: ${error.message}`,
82
- }).pipeThrough(
83
- new TransformStream({
84
- transform: (chunk, controller) => {
85
- // Convert the string stream to a byte stream
86
- const str = typeof chunk === 'string' ? chunk : JSON.stringify(chunk);
87
- controller.enqueue(encoder.encode(str));
88
- },
89
- }),
90
- );
91
- await stream.switchSource(usageStream);
92
- await new Promise((resolve) => setTimeout(resolve, 0));
93
- stream.close();
94
 
95
- return;
96
- }
 
97
 
98
- if (stream.switches >= MAX_RESPONSE_SEGMENTS) {
99
- throw Error('Cannot continue message: Maximum segments reached');
100
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
- const switchesLeft = MAX_RESPONSE_SEGMENTS - stream.switches;
 
 
 
 
103
 
104
- logger.info(`Reached max token limit (${MAX_TOKENS}): Continuing message (${switchesLeft} switches left)`);
 
 
 
105
 
106
- messages.push({ role: 'assistant', content });
107
- messages.push({ role: 'user', content: CONTINUE_PROMPT });
 
108
 
109
  const result = await streamText({
110
  messages,
111
- env: context.cloudflare.env,
112
  options,
113
  apiKeys,
114
  files,
115
  providerSettings,
116
  promptId,
117
  contextOptimization,
 
 
118
  });
119
 
120
- stream.switchSource(result.toDataStream());
 
 
 
 
121
 
122
- return;
123
- },
124
- };
125
- const totalMessageContent = messages.reduce((acc, message) => acc + message.content, '');
126
- logger.debug(`Total message length: ${totalMessageContent.split(' ').length}, words`);
127
-
128
- const result = await streamText({
129
- messages,
130
- env: context.cloudflare.env,
131
- options,
132
- apiKeys,
133
- files,
134
- providerSettings,
135
- promptId,
136
- contextOptimization,
137
- });
138
 
139
- (async () => {
140
- for await (const part of result.fullStream) {
141
- if (part.type === 'error') {
142
- const error: any = part.error;
143
- logger.error(`${error}`);
144
-
145
- return;
146
- }
147
- }
148
- })();
149
-
150
- stream.switchSource(result.toDataStream());
151
 
152
- // return createrespo
153
- return new Response(stream.readable, {
154
  status: 200,
155
  headers: {
156
  'Content-Type': 'text/event-stream; charset=utf-8',
 
1
  import { type ActionFunctionArgs } from '@remix-run/cloudflare';
2
+ import { createDataStream, generateId } from 'ai';
3
+ import { MAX_RESPONSE_SEGMENTS, MAX_TOKENS, type FileMap } from '~/lib/.server/llm/constants';
4
  import { CONTINUE_PROMPT } from '~/lib/common/prompts/prompts';
5
  import { streamText, type Messages, type StreamingOptions } from '~/lib/.server/llm/stream-text';
6
  import SwitchableStream from '~/lib/.server/llm/switchable-stream';
7
  import type { IProviderSetting } from '~/types/model';
8
  import { createScopedLogger } from '~/utils/logger';
9
+ import { getFilePaths, selectContext } from '~/lib/.server/llm/select-context';
10
+ import type { ContextAnnotation, ProgressAnnotation } from '~/types/context';
11
+ import { WORK_DIR } from '~/utils/constants';
12
+ import { createSummary } from '~/lib/.server/llm/create-summary';
13
 
14
  export async function action(args: ActionFunctionArgs) {
15
  return chatAction(args);
 
56
  promptTokens: 0,
57
  totalTokens: 0,
58
  };
59
+ const encoder: TextEncoder = new TextEncoder();
60
+ let progressCounter: number = 1;
61
 
62
  try {
63
+ const totalMessageContent = messages.reduce((acc, message) => acc + message.content, '');
64
+ logger.debug(`Total message length: ${totalMessageContent.split(' ').length}, words`);
65
+
66
+ const dataStream = createDataStream({
67
+ async execute(dataStream) {
68
+ const filePaths = getFilePaths(files || {});
69
+ let filteredFiles: FileMap | undefined = undefined;
70
+ let summary: string | undefined = undefined;
71
+
72
+ if (filePaths.length > 0 && contextOptimization) {
73
+ dataStream.writeData('HI ');
74
+ logger.debug('Generating Chat Summary');
75
+ dataStream.writeMessageAnnotation({
76
+ type: 'progress',
77
+ value: progressCounter++,
78
+ message: 'Generating Chat Summary',
79
+ } as ProgressAnnotation);
80
+
81
+ // Create a summary of the chat
82
+ console.log(`Messages count: ${messages.length}`);
83
+
84
+ summary = await createSummary({
85
+ messages: [...messages],
86
+ env: context.cloudflare?.env,
87
+ apiKeys,
88
+ providerSettings,
89
+ promptId,
90
+ contextOptimization,
91
+ onFinish(resp) {
92
+ if (resp.usage) {
93
+ logger.debug('createSummary token usage', JSON.stringify(resp.usage));
94
+ cumulativeUsage.completionTokens += resp.usage.completionTokens || 0;
95
+ cumulativeUsage.promptTokens += resp.usage.promptTokens || 0;
96
+ cumulativeUsage.totalTokens += resp.usage.totalTokens || 0;
97
+ }
98
+ },
99
+ });
100
+
101
+ dataStream.writeMessageAnnotation({
102
+ type: 'chatSummary',
103
+ summary,
104
+ chatId: messages.slice(-1)?.[0]?.id,
105
+ } as ContextAnnotation);
106
+
107
+ // Update context buffer
108
+ logger.debug('Updating Context Buffer');
109
+ dataStream.writeMessageAnnotation({
110
+ type: 'progress',
111
+ value: progressCounter++,
112
+ message: 'Updating Context Buffer',
113
+ } as ProgressAnnotation);
114
+
115
+ // Select context files
116
+ console.log(`Messages count: ${messages.length}`);
117
+ filteredFiles = await selectContext({
118
+ messages: [...messages],
119
+ env: context.cloudflare?.env,
120
+ apiKeys,
121
+ files,
122
+ providerSettings,
123
+ promptId,
124
+ contextOptimization,
125
+ summary,
126
+ onFinish(resp) {
127
+ if (resp.usage) {
128
+ logger.debug('selectContext token usage', JSON.stringify(resp.usage));
129
+ cumulativeUsage.completionTokens += resp.usage.completionTokens || 0;
130
+ cumulativeUsage.promptTokens += resp.usage.promptTokens || 0;
131
+ cumulativeUsage.totalTokens += resp.usage.totalTokens || 0;
132
+ }
133
+ },
134
+ });
135
+
136
+ if (filteredFiles) {
137
+ logger.debug(`files in context : ${JSON.stringify(Object.keys(filteredFiles))}`);
138
+ }
139
+
140
+ dataStream.writeMessageAnnotation({
141
+ type: 'codeContext',
142
+ files: Object.keys(filteredFiles).map((key) => {
143
+ let path = key;
144
+
145
+ if (path.startsWith(WORK_DIR)) {
146
+ path = path.replace(WORK_DIR, '');
147
+ }
148
+
149
+ return path;
150
+ }),
151
+ } as ContextAnnotation);
152
+
153
+ dataStream.writeMessageAnnotation({
154
+ type: 'progress',
155
+ value: progressCounter++,
156
+ message: 'Context Buffer Updated',
157
+ } as ProgressAnnotation);
158
+ logger.debug('Context Buffer Updated');
159
  }
160
 
161
+ // Stream the text
162
+ const options: StreamingOptions = {
163
+ toolChoice: 'none',
164
+ onFinish: async ({ text: content, finishReason, usage }) => {
165
+ logger.debug('usage', JSON.stringify(usage));
166
+
167
+ if (usage) {
168
+ cumulativeUsage.completionTokens += usage.completionTokens || 0;
169
+ cumulativeUsage.promptTokens += usage.promptTokens || 0;
170
+ cumulativeUsage.totalTokens += usage.totalTokens || 0;
171
+ }
172
+
173
+ if (finishReason !== 'length') {
174
  dataStream.writeMessageAnnotation({
175
  type: 'usage',
176
  value: {
 
179
  totalTokens: cumulativeUsage.totalTokens,
180
  },
181
  });
182
+ await new Promise((resolve) => setTimeout(resolve, 0));
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
+ // stream.close();
185
+ return;
186
+ }
187
 
188
+ if (stream.switches >= MAX_RESPONSE_SEGMENTS) {
189
+ throw Error('Cannot continue message: Maximum segments reached');
190
+ }
191
+
192
+ const switchesLeft = MAX_RESPONSE_SEGMENTS - stream.switches;
193
+
194
+ logger.info(`Reached max token limit (${MAX_TOKENS}): Continuing message (${switchesLeft} switches left)`);
195
+
196
+ messages.push({ id: generateId(), role: 'assistant', content });
197
+ messages.push({ id: generateId(), role: 'user', content: CONTINUE_PROMPT });
198
+
199
+ const result = await streamText({
200
+ messages,
201
+ env: context.cloudflare?.env,
202
+ options,
203
+ apiKeys,
204
+ files,
205
+ providerSettings,
206
+ promptId,
207
+ contextOptimization,
208
+ });
209
+
210
+ result.mergeIntoDataStream(dataStream);
211
 
212
+ (async () => {
213
+ for await (const part of result.fullStream) {
214
+ if (part.type === 'error') {
215
+ const error: any = part.error;
216
+ logger.error(`${error}`);
217
 
218
+ return;
219
+ }
220
+ }
221
+ })();
222
 
223
+ return;
224
+ },
225
+ };
226
 
227
  const result = await streamText({
228
  messages,
229
+ env: context.cloudflare?.env,
230
  options,
231
  apiKeys,
232
  files,
233
  providerSettings,
234
  promptId,
235
  contextOptimization,
236
+ contextFiles: filteredFiles,
237
+ summary,
238
  });
239
 
240
+ (async () => {
241
+ for await (const part of result.fullStream) {
242
+ if (part.type === 'error') {
243
+ const error: any = part.error;
244
+ logger.error(`${error}`);
245
 
246
+ return;
247
+ }
248
+ }
249
+ })();
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
+ result.mergeIntoDataStream(dataStream);
252
+ },
253
+ onError: (error: any) => `Custom error: ${error.message}`,
254
+ }).pipeThrough(
255
+ new TransformStream({
256
+ transform: (chunk, controller) => {
257
+ // Convert the string stream to a byte stream
258
+ const str = typeof chunk === 'string' ? chunk : JSON.stringify(chunk);
259
+ controller.enqueue(encoder.encode(str));
260
+ },
261
+ }),
262
+ );
263
 
264
+ return new Response(dataStream, {
 
265
  status: 200,
266
  headers: {
267
  'Content-Type': 'text/event-stream; charset=utf-8',
app/routes/api.enhancer.ts CHANGED
@@ -74,7 +74,7 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) {
74
  `,
75
  },
76
  ],
77
- env: context.cloudflare.env,
78
  apiKeys,
79
  providerSettings,
80
  });
 
74
  `,
75
  },
76
  ],
77
+ env: context.cloudflare?.env as any,
78
  apiKeys,
79
  providerSettings,
80
  });
app/routes/api.llmcall.ts CHANGED
@@ -63,7 +63,7 @@ async function llmCallAction({ context, request }: ActionFunctionArgs) {
63
  content: `${message}`,
64
  },
65
  ],
66
- env: context.cloudflare.env,
67
  apiKeys,
68
  providerSettings,
69
  });
@@ -91,7 +91,7 @@ async function llmCallAction({ context, request }: ActionFunctionArgs) {
91
  }
92
  } else {
93
  try {
94
- const models = await getModelList({ apiKeys, providerSettings, serverEnv: context.cloudflare.env as any });
95
  const modelDetails = models.find((m: ModelInfo) => m.name === model);
96
 
97
  if (!modelDetails) {
@@ -116,7 +116,7 @@ async function llmCallAction({ context, request }: ActionFunctionArgs) {
116
  ],
117
  model: providerInfo.getModelInstance({
118
  model: modelDetails.name,
119
- serverEnv: context.cloudflare.env as any,
120
  apiKeys,
121
  providerSettings,
122
  }),
 
63
  content: `${message}`,
64
  },
65
  ],
66
+ env: context.cloudflare?.env as any,
67
  apiKeys,
68
  providerSettings,
69
  });
 
91
  }
92
  } else {
93
  try {
94
+ const models = await getModelList({ apiKeys, providerSettings, serverEnv: context.cloudflare?.env as any });
95
  const modelDetails = models.find((m: ModelInfo) => m.name === model);
96
 
97
  if (!modelDetails) {
 
116
  ],
117
  model: providerInfo.getModelInstance({
118
  model: modelDetails.name,
119
+ serverEnv: context.cloudflare?.env as any,
120
  apiKeys,
121
  providerSettings,
122
  }),
app/styles/variables.scss CHANGED
@@ -219,7 +219,7 @@
219
  --header-height: 54px;
220
  --chat-max-width: 37rem;
221
  --chat-min-width: 640px;
222
- --workbench-width: min(calc(100% - var(--chat-min-width)), 1536px);
223
  --workbench-inner-width: var(--workbench-width);
224
  --workbench-left: calc(100% - var(--workbench-width));
225
 
 
219
  --header-height: 54px;
220
  --chat-max-width: 37rem;
221
  --chat-min-width: 640px;
222
+ --workbench-width: min(calc(100% - var(--chat-min-width)), 2536px);
223
  --workbench-inner-width: var(--workbench-width);
224
  --workbench-left: calc(100% - var(--workbench-width));
225
 
app/types/context.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export type ContextAnnotation =
2
+ | {
3
+ type: 'codeContext';
4
+ files: string[];
5
+ }
6
+ | {
7
+ type: 'chatSummary';
8
+ summary: string;
9
+ chatId: string;
10
+ };
11
+
12
+ export type ProgressAnnotation = {
13
+ type: 'progress';
14
+ value: number;
15
+ message: string;
16
+ };
package.json CHANGED
@@ -30,12 +30,12 @@
30
  "node": ">=18.18.0"
31
  },
32
  "dependencies": {
 
33
  "@ai-sdk/anthropic": "^0.0.39",
34
  "@ai-sdk/cohere": "^1.0.3",
35
  "@ai-sdk/google": "^0.0.52",
36
  "@ai-sdk/mistral": "^0.0.43",
37
  "@ai-sdk/openai": "^0.0.66",
38
- "@ai-sdk/amazon-bedrock": "1.0.6",
39
  "@codemirror/autocomplete": "^6.18.3",
40
  "@codemirror/commands": "^6.7.1",
41
  "@codemirror/lang-cpp": "^6.0.2",
@@ -62,6 +62,7 @@
62
  "@radix-ui/react-context-menu": "^2.2.2",
63
  "@radix-ui/react-dialog": "^1.1.2",
64
  "@radix-ui/react-dropdown-menu": "^2.1.2",
 
65
  "@radix-ui/react-separator": "^1.1.0",
66
  "@radix-ui/react-switch": "^1.1.1",
67
  "@radix-ui/react-tooltip": "^1.1.4",
 
30
  "node": ">=18.18.0"
31
  },
32
  "dependencies": {
33
+ "@ai-sdk/amazon-bedrock": "1.0.6",
34
  "@ai-sdk/anthropic": "^0.0.39",
35
  "@ai-sdk/cohere": "^1.0.3",
36
  "@ai-sdk/google": "^0.0.52",
37
  "@ai-sdk/mistral": "^0.0.43",
38
  "@ai-sdk/openai": "^0.0.66",
 
39
  "@codemirror/autocomplete": "^6.18.3",
40
  "@codemirror/commands": "^6.7.1",
41
  "@codemirror/lang-cpp": "^6.0.2",
 
62
  "@radix-ui/react-context-menu": "^2.2.2",
63
  "@radix-ui/react-dialog": "^1.1.2",
64
  "@radix-ui/react-dropdown-menu": "^2.1.2",
65
+ "@radix-ui/react-popover": "^1.1.4",
66
  "@radix-ui/react-separator": "^1.1.0",
67
  "@radix-ui/react-switch": "^1.1.1",
68
  "@radix-ui/react-tooltip": "^1.1.4",
pnpm-lock.yaml CHANGED
@@ -107,6 +107,9 @@ importers:
107
  '@radix-ui/react-dropdown-menu':
108
  specifier: ^2.1.2
109
 
 
 
110
  '@radix-ui/react-separator':
111
  specifier: ^1.1.0
112
@@ -1647,6 +1650,9 @@ packages:
1647
  '@radix-ui/[email protected]':
1648
  resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==}
1649
 
 
 
 
1650
  '@radix-ui/[email protected]':
1651
  resolution: {integrity: sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==}
1652
  peerDependencies:
@@ -1660,6 +1666,19 @@ packages:
1660
  '@types/react-dom':
1661
  optional: true
1662
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1663
  '@radix-ui/[email protected]':
1664
  resolution: {integrity: sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==}
1665
  peerDependencies:
@@ -1682,6 +1701,15 @@ packages:
1682
  '@types/react':
1683
  optional: true
1684
 
 
 
 
 
 
 
 
 
 
1685
  '@radix-ui/[email protected]':
1686
  resolution: {integrity: sha512-99EatSTpW+hRYHt7m8wdDlLtkmTovEe8Z/hnxUPV+SKuuNL5HWNhQI4QSdjZqNSgXHay2z4M3Dym73j9p2Gx5Q==}
1687
  peerDependencies:
@@ -1748,6 +1776,19 @@ packages:
1748
  '@types/react-dom':
1749
  optional: true
1750
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1751
  '@radix-ui/[email protected]':
1752
  resolution: {integrity: sha512-GVZMR+eqK8/Kes0a36Qrv+i20bAPXSn8rCBTHx30w+3ECnR5o3xixAlqcVaYvLeyKUsm0aqyhWfmUcqufM8nYA==}
1753
  peerDependencies:
@@ -1783,6 +1824,19 @@ packages:
1783
  '@types/react-dom':
1784
  optional: true
1785
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1786
  '@radix-ui/[email protected]':
1787
  resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==}
1788
  peerDependencies:
@@ -1805,6 +1859,19 @@ packages:
1805
  '@types/react-dom':
1806
  optional: true
1807
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1808
  '@radix-ui/[email protected]':
1809
  resolution: {integrity: sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==}
1810
  peerDependencies:
@@ -1818,6 +1885,19 @@ packages:
1818
  '@types/react-dom':
1819
  optional: true
1820
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1821
  '@radix-ui/[email protected]':
1822
  resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==}
1823
  peerDependencies:
@@ -1831,6 +1911,19 @@ packages:
1831
  '@types/react-dom':
1832
  optional: true
1833
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1834
  '@radix-ui/[email protected]':
1835
  resolution: {integrity: sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==}
1836
  peerDependencies:
@@ -1844,6 +1937,19 @@ packages:
1844
  '@types/react-dom':
1845
  optional: true
1846
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1847
  '@radix-ui/[email protected]':
1848
  resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==}
1849
  peerDependencies:
@@ -1857,6 +1963,19 @@ packages:
1857
  '@types/react-dom':
1858
  optional: true
1859
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1860
  '@radix-ui/[email protected]':
1861
  resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==}
1862
  peerDependencies:
@@ -1892,6 +2011,15 @@ packages:
1892
  '@types/react':
1893
  optional: true
1894
 
 
 
 
 
 
 
 
 
 
1895
  '@radix-ui/[email protected]':
1896
  resolution: {integrity: sha512-diPqDDoBcZPSicYoMWdWx+bCPuTRH4QSp9J+65IvtdS0Kuzt67bI6n32vCj8q6NZmYW/ah+2orOtMwcX5eQwIg==}
1897
  peerDependencies:
@@ -4985,6 +5113,16 @@ packages:
4985
  '@types/react':
4986
  optional: true
4987
 
 
 
 
 
 
 
 
 
 
 
4988
4989
  resolution: {integrity: sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==}
4990
  engines: {node: '>=10'}
@@ -4995,6 +5133,16 @@ packages:
4995
  '@types/react':
4996
  optional: true
4997
 
 
 
 
 
 
 
 
 
 
 
4998
4999
  resolution: {integrity: sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA==}
5000
  peerDependencies:
@@ -5024,6 +5172,16 @@ packages:
5024
  '@types/react':
5025
  optional: true
5026
 
 
 
 
 
 
 
 
 
 
 
5027
5028
  resolution: {integrity: sha512-yYjp+omCDf9lhZcrZHKbSq7YMuK0zcYkDFTzfRFgTXkTFHZ1ToxwAonzA4JI5CxA91JpjFLmwEsZEgfYfOqI1A==}
5029
  peerDependencies:
@@ -5804,6 +5962,16 @@ packages:
5804
  '@types/react':
5805
  optional: true
5806
 
 
 
 
 
 
 
 
 
 
 
5807
5808
  resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
5809
  engines: {node: '>=10'}
@@ -7649,6 +7817,8 @@ snapshots:
7649
 
7650
  '@radix-ui/[email protected]': {}
7651
 
 
 
7652
7653
  dependencies:
7654
  '@radix-ui/react-primitive': 2.0.0(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
@@ -7658,6 +7828,15 @@ snapshots:
7658
  '@types/react': 18.3.12
7659
  '@types/react-dom': 18.3.1
7660
 
 
 
 
 
 
 
 
 
 
7661
7662
  dependencies:
7663
  '@radix-ui/react-compose-refs': 1.1.0(@types/[email protected])([email protected])
@@ -7676,6 +7855,12 @@ snapshots:
7676
  optionalDependencies:
7677
  '@types/react': 18.3.12
7678
 
 
 
 
 
 
 
7679
7680
  dependencies:
7681
  '@radix-ui/primitive': 1.1.0
@@ -7743,6 +7928,19 @@ snapshots:
7743
  '@types/react': 18.3.12
7744
  '@types/react-dom': 18.3.1
7745
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7746
7747
  dependencies:
7748
  '@radix-ui/primitive': 1.1.0
@@ -7775,6 +7973,17 @@ snapshots:
7775
  '@types/react': 18.3.12
7776
  '@types/react-dom': 18.3.1
7777
 
 
 
 
 
 
 
 
 
 
 
 
7778
7779
  dependencies:
7780
  '@radix-ui/react-use-layout-effect': 1.1.0(@types/[email protected])([email protected])
@@ -7808,6 +8017,29 @@ snapshots:
7808
  '@types/react': 18.3.12
7809
  '@types/react-dom': 18.3.1
7810
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7811
7812
  dependencies:
7813
  '@floating-ui/react-dom': 2.1.2([email protected]([email protected]))([email protected])
@@ -7826,6 +8058,24 @@ snapshots:
7826
  '@types/react': 18.3.12
7827
  '@types/react-dom': 18.3.1
7828
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7829
7830
  dependencies:
7831
  '@radix-ui/react-primitive': 2.0.0(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
@@ -7836,6 +8086,16 @@ snapshots:
7836
  '@types/react': 18.3.12
7837
  '@types/react-dom': 18.3.1
7838
 
 
 
 
 
 
 
 
 
 
 
7839
7840
  dependencies:
7841
  '@radix-ui/react-compose-refs': 1.1.0(@types/[email protected])([email protected])
@@ -7846,6 +8106,16 @@ snapshots:
7846
  '@types/react': 18.3.12
7847
  '@types/react-dom': 18.3.1
7848
 
 
 
 
 
 
 
 
 
 
 
7849
7850
  dependencies:
7851
  '@radix-ui/react-slot': 1.1.0(@types/[email protected])([email protected])
@@ -7855,6 +8125,15 @@ snapshots:
7855
  '@types/react': 18.3.12
7856
  '@types/react-dom': 18.3.1
7857
 
 
 
 
 
 
 
 
 
 
7858
7859
  dependencies:
7860
  '@radix-ui/primitive': 1.1.0
@@ -7888,6 +8167,13 @@ snapshots:
7888
  optionalDependencies:
7889
  '@types/react': 18.3.12
7890
 
 
 
 
 
 
 
 
7891
7892
  dependencies:
7893
  '@radix-ui/primitive': 1.1.0
@@ -11850,6 +12136,14 @@ snapshots:
11850
  optionalDependencies:
11851
  '@types/react': 18.3.12
11852
 
 
 
 
 
 
 
 
 
11853
11854
  dependencies:
11855
  react: 18.3.1
@@ -11861,6 +12155,17 @@ snapshots:
11861
  optionalDependencies:
11862
  '@types/react': 18.3.12
11863
 
 
 
 
 
 
 
 
 
 
 
 
11864
11865
  dependencies:
11866
  react: 18.3.1
@@ -11887,6 +12192,14 @@ snapshots:
11887
  optionalDependencies:
11888
  '@types/react': 18.3.12
11889
 
 
 
 
 
 
 
 
 
11890
11891
  dependencies:
11892
  clsx: 2.1.1
@@ -12737,6 +13050,13 @@ snapshots:
12737
  optionalDependencies:
12738
  '@types/react': 18.3.12
12739
 
 
 
 
 
 
 
 
12740
12741
  dependencies:
12742
  detect-node-es: 1.1.0
 
107
  '@radix-ui/react-dropdown-menu':
108
  specifier: ^2.1.2
109
110
+ '@radix-ui/react-popover':
111
+ specifier: ^1.1.4
112
113
  '@radix-ui/react-separator':
114
  specifier: ^1.1.0
115
 
1650
  '@radix-ui/[email protected]':
1651
  resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==}
1652
 
1653
+ '@radix-ui/[email protected]':
1654
+ resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==}
1655
+
1656
  '@radix-ui/[email protected]':
1657
  resolution: {integrity: sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==}
1658
  peerDependencies:
 
1666
  '@types/react-dom':
1667
  optional: true
1668
 
1669
+ '@radix-ui/[email protected]':
1670
+ resolution: {integrity: sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==}
1671
+ peerDependencies:
1672
+ '@types/react': '*'
1673
+ '@types/react-dom': '*'
1674
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1675
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1676
+ peerDependenciesMeta:
1677
+ '@types/react':
1678
+ optional: true
1679
+ '@types/react-dom':
1680
+ optional: true
1681
+
1682
  '@radix-ui/[email protected]':
1683
  resolution: {integrity: sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==}
1684
  peerDependencies:
 
1701
  '@types/react':
1702
  optional: true
1703
 
1704
+ '@radix-ui/[email protected]':
1705
+ resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==}
1706
+ peerDependencies:
1707
+ '@types/react': '*'
1708
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1709
+ peerDependenciesMeta:
1710
+ '@types/react':
1711
+ optional: true
1712
+
1713
  '@radix-ui/[email protected]':
1714
  resolution: {integrity: sha512-99EatSTpW+hRYHt7m8wdDlLtkmTovEe8Z/hnxUPV+SKuuNL5HWNhQI4QSdjZqNSgXHay2z4M3Dym73j9p2Gx5Q==}
1715
  peerDependencies:
 
1776
  '@types/react-dom':
1777
  optional: true
1778
 
1779
+ '@radix-ui/[email protected]':
1780
+ resolution: {integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==}
1781
+ peerDependencies:
1782
+ '@types/react': '*'
1783
+ '@types/react-dom': '*'
1784
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1785
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1786
+ peerDependenciesMeta:
1787
+ '@types/react':
1788
+ optional: true
1789
+ '@types/react-dom':
1790
+ optional: true
1791
+
1792
  '@radix-ui/[email protected]':
1793
  resolution: {integrity: sha512-GVZMR+eqK8/Kes0a36Qrv+i20bAPXSn8rCBTHx30w+3ECnR5o3xixAlqcVaYvLeyKUsm0aqyhWfmUcqufM8nYA==}
1794
  peerDependencies:
 
1824
  '@types/react-dom':
1825
  optional: true
1826
 
1827
+ '@radix-ui/[email protected]':
1828
+ resolution: {integrity: sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==}
1829
+ peerDependencies:
1830
+ '@types/react': '*'
1831
+ '@types/react-dom': '*'
1832
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1833
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1834
+ peerDependenciesMeta:
1835
+ '@types/react':
1836
+ optional: true
1837
+ '@types/react-dom':
1838
+ optional: true
1839
+
1840
  '@radix-ui/[email protected]':
1841
  resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==}
1842
  peerDependencies:
 
1859
  '@types/react-dom':
1860
  optional: true
1861
 
1862
+ '@radix-ui/[email protected]':
1863
+ resolution: {integrity: sha512-aUACAkXx8LaFymDma+HQVji7WhvEhpFJ7+qPz17Nf4lLZqtreGOFRiNQWQmhzp7kEWg9cOyyQJpdIMUMPc/CPw==}
1864
+ peerDependencies:
1865
+ '@types/react': '*'
1866
+ '@types/react-dom': '*'
1867
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1868
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1869
+ peerDependenciesMeta:
1870
+ '@types/react':
1871
+ optional: true
1872
+ '@types/react-dom':
1873
+ optional: true
1874
+
1875
  '@radix-ui/[email protected]':
1876
  resolution: {integrity: sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==}
1877
  peerDependencies:
 
1885
  '@types/react-dom':
1886
  optional: true
1887
 
1888
+ '@radix-ui/[email protected]':
1889
+ resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==}
1890
+ peerDependencies:
1891
+ '@types/react': '*'
1892
+ '@types/react-dom': '*'
1893
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1894
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1895
+ peerDependenciesMeta:
1896
+ '@types/react':
1897
+ optional: true
1898
+ '@types/react-dom':
1899
+ optional: true
1900
+
1901
  '@radix-ui/[email protected]':
1902
  resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==}
1903
  peerDependencies:
 
1911
  '@types/react-dom':
1912
  optional: true
1913
 
1914
+ '@radix-ui/[email protected]':
1915
+ resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==}
1916
+ peerDependencies:
1917
+ '@types/react': '*'
1918
+ '@types/react-dom': '*'
1919
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1920
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1921
+ peerDependenciesMeta:
1922
+ '@types/react':
1923
+ optional: true
1924
+ '@types/react-dom':
1925
+ optional: true
1926
+
1927
  '@radix-ui/[email protected]':
1928
  resolution: {integrity: sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==}
1929
  peerDependencies:
 
1937
  '@types/react-dom':
1938
  optional: true
1939
 
1940
+ '@radix-ui/[email protected]':
1941
+ resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==}
1942
+ peerDependencies:
1943
+ '@types/react': '*'
1944
+ '@types/react-dom': '*'
1945
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1946
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1947
+ peerDependenciesMeta:
1948
+ '@types/react':
1949
+ optional: true
1950
+ '@types/react-dom':
1951
+ optional: true
1952
+
1953
  '@radix-ui/[email protected]':
1954
  resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==}
1955
  peerDependencies:
 
1963
  '@types/react-dom':
1964
  optional: true
1965
 
1966
+ '@radix-ui/[email protected]':
1967
+ resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==}
1968
+ peerDependencies:
1969
+ '@types/react': '*'
1970
+ '@types/react-dom': '*'
1971
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1972
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1973
+ peerDependenciesMeta:
1974
+ '@types/react':
1975
+ optional: true
1976
+ '@types/react-dom':
1977
+ optional: true
1978
+
1979
  '@radix-ui/[email protected]':
1980
  resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==}
1981
  peerDependencies:
 
2011
  '@types/react':
2012
  optional: true
2013
 
2014
+ '@radix-ui/[email protected]':
2015
+ resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==}
2016
+ peerDependencies:
2017
+ '@types/react': '*'
2018
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
2019
+ peerDependenciesMeta:
2020
+ '@types/react':
2021
+ optional: true
2022
+
2023
  '@radix-ui/[email protected]':
2024
  resolution: {integrity: sha512-diPqDDoBcZPSicYoMWdWx+bCPuTRH4QSp9J+65IvtdS0Kuzt67bI6n32vCj8q6NZmYW/ah+2orOtMwcX5eQwIg==}
2025
  peerDependencies:
 
5113
  '@types/react':
5114
  optional: true
5115
 
5116
5117
+ resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
5118
+ engines: {node: '>=10'}
5119
+ peerDependencies:
5120
+ '@types/react': '*'
5121
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
5122
+ peerDependenciesMeta:
5123
+ '@types/react':
5124
+ optional: true
5125
+
5126
5127
  resolution: {integrity: sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==}
5128
  engines: {node: '>=10'}
 
5133
  '@types/react':
5134
  optional: true
5135
 
5136
5137
+ resolution: {integrity: sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==}
5138
+ engines: {node: '>=10'}
5139
+ peerDependencies:
5140
+ '@types/react': '*'
5141
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
5142
+ peerDependenciesMeta:
5143
+ '@types/react':
5144
+ optional: true
5145
+
5146
5147
  resolution: {integrity: sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA==}
5148
  peerDependencies:
 
5172
  '@types/react':
5173
  optional: true
5174
 
5175
5176
+ resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
5177
+ engines: {node: '>=10'}
5178
+ peerDependencies:
5179
+ '@types/react': '*'
5180
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
5181
+ peerDependenciesMeta:
5182
+ '@types/react':
5183
+ optional: true
5184
+
5185
5186
  resolution: {integrity: sha512-yYjp+omCDf9lhZcrZHKbSq7YMuK0zcYkDFTzfRFgTXkTFHZ1ToxwAonzA4JI5CxA91JpjFLmwEsZEgfYfOqI1A==}
5187
  peerDependencies:
 
5962
  '@types/react':
5963
  optional: true
5964
 
5965
5966
+ resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
5967
+ engines: {node: '>=10'}
5968
+ peerDependencies:
5969
+ '@types/react': '*'
5970
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
5971
+ peerDependenciesMeta:
5972
+ '@types/react':
5973
+ optional: true
5974
+
5975
5976
  resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
5977
  engines: {node: '>=10'}
 
7817
 
7818
  '@radix-ui/[email protected]': {}
7819
 
7820
+ '@radix-ui/[email protected]': {}
7821
+
7822
7823
  dependencies:
7824
  '@radix-ui/react-primitive': 2.0.0(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
 
7828
  '@types/react': 18.3.12
7829
  '@types/react-dom': 18.3.1
7830
 
7831
7832
+ dependencies:
7833
+ '@radix-ui/react-primitive': 2.0.1(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
7834
+ react: 18.3.1
7835
+ react-dom: 18.3.1([email protected])
7836
+ optionalDependencies:
7837
+ '@types/react': 18.3.12
7838
+ '@types/react-dom': 18.3.1
7839
+
7840
7841
  dependencies:
7842
  '@radix-ui/react-compose-refs': 1.1.0(@types/[email protected])([email protected])
 
7855
  optionalDependencies:
7856
  '@types/react': 18.3.12
7857
 
7858
7859
+ dependencies:
7860
+ react: 18.3.1
7861
+ optionalDependencies:
7862
+ '@types/react': 18.3.12
7863
+
7864
7865
  dependencies:
7866
  '@radix-ui/primitive': 1.1.0
 
7928
  '@types/react': 18.3.12
7929
  '@types/react-dom': 18.3.1
7930
 
7931
7932
+ dependencies:
7933
+ '@radix-ui/primitive': 1.1.1
7934
+ '@radix-ui/react-compose-refs': 1.1.1(@types/[email protected])([email protected])
7935
+ '@radix-ui/react-primitive': 2.0.1(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
7936
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/[email protected])([email protected])
7937
+ '@radix-ui/react-use-escape-keydown': 1.1.0(@types/[email protected])([email protected])
7938
+ react: 18.3.1
7939
+ react-dom: 18.3.1([email protected])
7940
+ optionalDependencies:
7941
+ '@types/react': 18.3.12
7942
+ '@types/react-dom': 18.3.1
7943
+
7944
7945
  dependencies:
7946
  '@radix-ui/primitive': 1.1.0
 
7973
  '@types/react': 18.3.12
7974
  '@types/react-dom': 18.3.1
7975
 
7976
7977
+ dependencies:
7978
+ '@radix-ui/react-compose-refs': 1.1.1(@types/[email protected])([email protected])
7979
+ '@radix-ui/react-primitive': 2.0.1(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
7980
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/[email protected])([email protected])
7981
+ react: 18.3.1
7982
+ react-dom: 18.3.1([email protected])
7983
+ optionalDependencies:
7984
+ '@types/react': 18.3.12
7985
+ '@types/react-dom': 18.3.1
7986
+
7987
7988
  dependencies:
7989
  '@radix-ui/react-use-layout-effect': 1.1.0(@types/[email protected])([email protected])
 
8017
  '@types/react': 18.3.12
8018
  '@types/react-dom': 18.3.1
8019
 
8020
8021
+ dependencies:
8022
+ '@radix-ui/primitive': 1.1.1
8023
+ '@radix-ui/react-compose-refs': 1.1.1(@types/[email protected])([email protected])
8024
+ '@radix-ui/react-context': 1.1.1(@types/[email protected])([email protected])
8025
+ '@radix-ui/react-dismissable-layer': 1.1.3(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
8026
+ '@radix-ui/react-focus-guards': 1.1.1(@types/[email protected])([email protected])
8027
+ '@radix-ui/react-focus-scope': 1.1.1(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
8028
+ '@radix-ui/react-id': 1.1.0(@types/[email protected])([email protected])
8029
8030
8031
+ '@radix-ui/react-presence': 1.1.2(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
8032
+ '@radix-ui/react-primitive': 2.0.1(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
8033
+ '@radix-ui/react-slot': 1.1.1(@types/[email protected])([email protected])
8034
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/[email protected])([email protected])
8035
+ aria-hidden: 1.2.4
8036
+ react: 18.3.1
8037
+ react-dom: 18.3.1([email protected])
8038
+ react-remove-scroll: 2.6.2(@types/[email protected])([email protected])
8039
+ optionalDependencies:
8040
+ '@types/react': 18.3.12
8041
+ '@types/react-dom': 18.3.1
8042
+
8043
8044
  dependencies:
8045
  '@floating-ui/react-dom': 2.1.2([email protected]([email protected]))([email protected])
 
8058
  '@types/react': 18.3.12
8059
  '@types/react-dom': 18.3.1
8060
 
8061
8062
+ dependencies:
8063
+ '@floating-ui/react-dom': 2.1.2([email protected]([email protected]))([email protected])
8064
8065
+ '@radix-ui/react-compose-refs': 1.1.1(@types/[email protected])([email protected])
8066
+ '@radix-ui/react-context': 1.1.1(@types/[email protected])([email protected])
8067
+ '@radix-ui/react-primitive': 2.0.1(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
8068
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/[email protected])([email protected])
8069
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/[email protected])([email protected])
8070
+ '@radix-ui/react-use-rect': 1.1.0(@types/[email protected])([email protected])
8071
+ '@radix-ui/react-use-size': 1.1.0(@types/[email protected])([email protected])
8072
+ '@radix-ui/rect': 1.1.0
8073
+ react: 18.3.1
8074
+ react-dom: 18.3.1([email protected])
8075
+ optionalDependencies:
8076
+ '@types/react': 18.3.12
8077
+ '@types/react-dom': 18.3.1
8078
+
8079
8080
  dependencies:
8081
  '@radix-ui/react-primitive': 2.0.0(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
 
8086
  '@types/react': 18.3.12
8087
  '@types/react-dom': 18.3.1
8088
 
8089
8090
+ dependencies:
8091
+ '@radix-ui/react-primitive': 2.0.1(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
8092
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/[email protected])([email protected])
8093
+ react: 18.3.1
8094
+ react-dom: 18.3.1([email protected])
8095
+ optionalDependencies:
8096
+ '@types/react': 18.3.12
8097
+ '@types/react-dom': 18.3.1
8098
+
8099
8100
  dependencies:
8101
  '@radix-ui/react-compose-refs': 1.1.0(@types/[email protected])([email protected])
 
8106
  '@types/react': 18.3.12
8107
  '@types/react-dom': 18.3.1
8108
 
8109
8110
+ dependencies:
8111
+ '@radix-ui/react-compose-refs': 1.1.1(@types/[email protected])([email protected])
8112
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/[email protected])([email protected])
8113
+ react: 18.3.1
8114
+ react-dom: 18.3.1([email protected])
8115
+ optionalDependencies:
8116
+ '@types/react': 18.3.12
8117
+ '@types/react-dom': 18.3.1
8118
+
8119
8120
  dependencies:
8121
  '@radix-ui/react-slot': 1.1.0(@types/[email protected])([email protected])
 
8125
  '@types/react': 18.3.12
8126
  '@types/react-dom': 18.3.1
8127
 
8128
8129
+ dependencies:
8130
+ '@radix-ui/react-slot': 1.1.1(@types/[email protected])([email protected])
8131
+ react: 18.3.1
8132
+ react-dom: 18.3.1([email protected])
8133
+ optionalDependencies:
8134
+ '@types/react': 18.3.12
8135
+ '@types/react-dom': 18.3.1
8136
+
8137
8138
  dependencies:
8139
  '@radix-ui/primitive': 1.1.0
 
8167
  optionalDependencies:
8168
  '@types/react': 18.3.12
8169
 
8170
8171
+ dependencies:
8172
+ '@radix-ui/react-compose-refs': 1.1.1(@types/[email protected])([email protected])
8173
+ react: 18.3.1
8174
+ optionalDependencies:
8175
+ '@types/react': 18.3.12
8176
+
8177
8178
  dependencies:
8179
  '@radix-ui/primitive': 1.1.0
 
12136
  optionalDependencies:
12137
  '@types/react': 18.3.12
12138
 
12139
12140
+ dependencies:
12141
+ react: 18.3.1
12142
+ react-style-singleton: 2.2.3(@types/[email protected])([email protected])
12143
+ tslib: 2.8.1
12144
+ optionalDependencies:
12145
+ '@types/react': 18.3.12
12146
+
12147
12148
  dependencies:
12149
  react: 18.3.1
 
12155
  optionalDependencies:
12156
  '@types/react': 18.3.12
12157
 
12158
12159
+ dependencies:
12160
+ react: 18.3.1
12161
+ react-remove-scroll-bar: 2.3.8(@types/[email protected])([email protected])
12162
+ react-style-singleton: 2.2.1(@types/[email protected])([email protected])
12163
+ tslib: 2.8.1
12164
+ use-callback-ref: 1.3.3(@types/[email protected])([email protected])
12165
+ use-sidecar: 1.1.2(@types/[email protected])([email protected])
12166
+ optionalDependencies:
12167
+ '@types/react': 18.3.12
12168
+
12169
12170
  dependencies:
12171
  react: 18.3.1
 
12192
  optionalDependencies:
12193
  '@types/react': 18.3.12
12194
 
12195
12196
+ dependencies:
12197
+ get-nonce: 1.0.1
12198
+ react: 18.3.1
12199
+ tslib: 2.8.1
12200
+ optionalDependencies:
12201
+ '@types/react': 18.3.12
12202
+
12203
12204
  dependencies:
12205
  clsx: 2.1.1
 
13050
  optionalDependencies:
13051
  '@types/react': 18.3.12
13052
 
13053
13054
+ dependencies:
13055
+ react: 18.3.1
13056
+ tslib: 2.8.1
13057
+ optionalDependencies:
13058
+ '@types/react': 18.3.12
13059
+
13060
13061
  dependencies:
13062
  detect-node-es: 1.1.0