diff --git a/e2e-tests/engine.spec.ts b/e2e-tests/engine.spec.ts index 8fbab9b..1159164 100644 --- a/e2e-tests/engine.spec.ts +++ b/e2e-tests/engine.spec.ts @@ -11,15 +11,29 @@ testSkipIfWindows("send message to engine", async ({ po }) => { await po.snapshotMessages({ replaceDumpPath: true }); }); -testSkipIfWindows("send message to gateway", async ({ po }) => { +testSkipIfWindows("send message to engine - openai gpt-4.1", async ({ po }) => { await po.setUpDyadPro(); - await po.selectModel({ provider: "Google", model: "Gemini 2.5 Flash" }); - await po.sendPrompt("[dump] tc=gateway-simple"); + // By default, it's using auto which points to Flash 2.5 and doesn't + // use engine. + await po.selectModel({ provider: "OpenAI", model: "GPT 4.1" }); + await po.sendPrompt("[dump] tc=turbo-edits"); await po.snapshotServerDump("request"); - await po.snapshotMessages({ replaceDumpPath: true }); }); +testSkipIfWindows( + "send message to engine - anthropic claude sonnet 4", + async ({ po }) => { + await po.setUpDyadPro(); + // By default, it's using auto which points to Flash 2.5 and doesn't + // use engine. + await po.selectModel({ provider: "Anthropic", model: "Claude 4 Sonnet" }); + await po.sendPrompt("[dump] tc=turbo-edits"); + + await po.snapshotServerDump("request"); + }, +); + // auto (defaults to Gemini 2.5 Flash) testSkipIfWindows("auto should send message to gateway", async ({ po }) => { await po.setUpDyadPro(); diff --git a/e2e-tests/gateway.spec.ts b/e2e-tests/gateway.spec.ts new file mode 100644 index 0000000..4973bef --- /dev/null +++ b/e2e-tests/gateway.spec.ts @@ -0,0 +1,29 @@ +import { testSkipIfWindows } from "./helpers/test_helper"; + +testSkipIfWindows("gemini 2.5 flash", async ({ po }) => { + // Note: we do not need to disable pro modes because 2.5 flash doesn't + // use engine. + await po.setUpDyadPro(); + await po.selectModel({ provider: "Google", model: "Gemini 2.5 Flash" }); + await po.sendPrompt("[dump] tc=gateway-simple"); + + await po.snapshotServerDump("request"); + await po.snapshotMessages({ replaceDumpPath: true }); +}); + +testSkipIfWindows("claude 4 sonnet", async ({ po }) => { + await po.setUpDyadPro(); + // Disable the pro modes so it routes to gateway. + const proModesDialog = await po.openProModesDialog({ + location: "home-chat-input-container", + }); + await proModesDialog.toggleTurboEdits(); + await proModesDialog.toggleSmartContext(); + await proModesDialog.close(); + + await po.selectModel({ provider: "Anthropic", model: "Claude 4 Sonnet" }); + + await po.sendPrompt("[dump] tc=gateway-simple"); + + await po.snapshotServerDump("request"); +}); diff --git a/e2e-tests/helpers/test_helper.ts b/e2e-tests/helpers/test_helper.ts index db8b6d8..ba5b62c 100644 --- a/e2e-tests/helpers/test_helper.ts +++ b/e2e-tests/helpers/test_helper.ts @@ -139,10 +139,14 @@ export class PageObject { }); } - async openProModesDialog(): Promise { + async openProModesDialog({ + location = "chat-input-container", + }: { + location?: "chat-input-container" | "home-chat-input-container"; + } = {}): Promise { const proButton = this.page // Assumes you're on the chat page. - .getByTestId("chat-input-container") + .getByTestId(location) .getByRole("button", { name: "Pro", exact: true }); await proButton.click(); return new ProModesDialog(this.page, async () => { @@ -396,7 +400,7 @@ export class PageObject { async selectModel({ provider, model }: { provider: string; model: string }) { await this.page.getByRole("button", { name: "Model: Auto" }).click(); await this.page.getByText(provider).click(); - await this.page.getByText(model).click(); + await this.page.getByText(model, { exact: true }).click(); } async selectTestModel() { diff --git a/e2e-tests/snapshots/attach_image.spec.ts_attach-image---chat-1.aria.yml b/e2e-tests/snapshots/attach_image.spec.ts_attach-image---chat-1.aria.yml index 7b84ebe..3ecbcaf 100644 --- a/e2e-tests/snapshots/attach_image.spec.ts_attach-image---chat-1.aria.yml +++ b/e2e-tests/snapshots/attach_image.spec.ts_attach-image---chat-1.aria.yml @@ -1,12 +1,4 @@ - paragraph: basic -- 'button "Thinking `<dyad-write>`: I''ll think about the problem and write a bug report. <dyad-write> <dyad-write path=\"file1.txt\"> Fake dyad write </dyad-write>"': - - img - - img - - paragraph: - - code: "`<dyad-write>`" - - text: ": I'll think about the problem and write a bug report." - - paragraph: <dyad-write> - - paragraph: <dyad-write path="file1.txt"> Fake dyad write </dyad-write> - img - text: file1.txt - img diff --git a/e2e-tests/snapshots/attach_image.spec.ts_attach-image-via-drag---chat-1.aria.yml b/e2e-tests/snapshots/attach_image.spec.ts_attach-image-via-drag---chat-1.aria.yml index 7b84ebe..3ecbcaf 100644 --- a/e2e-tests/snapshots/attach_image.spec.ts_attach-image-via-drag---chat-1.aria.yml +++ b/e2e-tests/snapshots/attach_image.spec.ts_attach-image-via-drag---chat-1.aria.yml @@ -1,12 +1,4 @@ - paragraph: basic -- 'button "Thinking `<dyad-write>`: I''ll think about the problem and write a bug report. <dyad-write> <dyad-write path=\"file1.txt\"> Fake dyad write </dyad-write>"': - - img - - img - - paragraph: - - code: "`<dyad-write>`" - - text: ": I'll think about the problem and write a bug report." - - paragraph: <dyad-write> - - paragraph: <dyad-write path="file1.txt"> Fake dyad write </dyad-write> - img - text: file1.txt - img diff --git a/e2e-tests/snapshots/context_manage.spec.ts_manage-context---default-1.txt b/e2e-tests/snapshots/context_manage.spec.ts_manage-context---default-1.txt index b8770cc..d6ea597 100644 --- a/e2e-tests/snapshots/context_manage.spec.ts_manage-context---default-1.txt +++ b/e2e-tests/snapshots/context_manage.spec.ts_manage-context---default-1.txt @@ -282,58 +282,6 @@ Do not hesitate to extensively use console logs to follow the flow of the code. DO NOT OVERENGINEER THE CODE. You take great pride in keeping things simple and elegant. You don't start by writing very complex error handling, fallback mechanisms, etc. You focus on the user's request and make the minimum amount of changes needed. DON'T DO MORE THAN WHAT THE USER ASKS FOR. - -# Thinking Process - -Before responding to user requests, ALWAYS use tags to carefully plan your approach. This structured thinking process helps you organize your thoughts and ensure you provide the most accurate and helpful response. Your thinking should: - -- Use **bullet points** to break down the steps -- **Bold key insights** and important considerations -- Follow a clear analytical framework - -Example of proper thinking structure for a debugging request: - - -• **Identify the specific UI/FE bug described by the user** - - "Form submission button doesn't work when clicked" - - User reports clicking the button has no effect - - This appears to be a **functional issue**, not just styling - -• **Examine relevant components in the codebase** - - Form component at `src/components/ContactForm.jsx` - - Button component at `src/components/Button.jsx` - - Form submission logic in `src/utils/formHandlers.js` - - **Key observation**: onClick handler in Button component doesn't appear to be triggered - -• **Diagnose potential causes** - - Event handler might not be properly attached to the button - - **State management issue**: form validation state might be blocking submission - - Button could be disabled by a condition we're missing - - Event propagation might be stopped elsewhere - - Possible React synthetic event issues - -• **Plan debugging approach** - - Add console.logs to track execution flow - - **Fix #1**: Ensure onClick prop is properly passed through Button component - - **Fix #2**: Check form validation state before submission - - **Fix #3**: Verify event handler is properly bound in the component - - Add error handling to catch and display submission issues - -• **Consider improvements beyond the fix** - - Add visual feedback when button is clicked (loading state) - - Implement better error handling for form submissions - - Add logging to help debug edge cases - - -After completing your thinking process, proceed with your response following the guidelines above. Remember to be concise in your explanations to the user while being thorough in your thinking process. - -This structured thinking ensures you: -1. Don't miss important aspects of the request -2. Consider all relevant factors before making changes -3. Deliver more accurate and helpful responses -4. Maintain a consistent approach to problem-solving - - # AI_RULES.md diff --git a/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context---auto-includes-only-1.txt b/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context---auto-includes-only-1.txt index 75e9443..53ee547 100644 --- a/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context---auto-includes-only-1.txt +++ b/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context---auto-includes-only-1.txt @@ -14,6 +14,10 @@ } ], "stream": true, + "thinking": { + "type": "enabled", + "include_thoughts": true + }, "dyad_options": { "files": [ { diff --git a/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context-1.txt b/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context-1.txt index c38869a..9a4be9b 100644 --- a/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context-1.txt +++ b/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context-1.txt @@ -14,6 +14,10 @@ } ], "stream": true, + "thinking": { + "type": "enabled", + "include_thoughts": true + }, "dyad_options": { "files": [ { diff --git a/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context-2.txt b/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context-2.txt index fc5a281..84379b7 100644 --- a/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context-2.txt +++ b/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context-2.txt @@ -282,58 +282,6 @@ Do not hesitate to extensively use console logs to follow the flow of the code. DO NOT OVERENGINEER THE CODE. You take great pride in keeping things simple and elegant. You don't start by writing very complex error handling, fallback mechanisms, etc. You focus on the user's request and make the minimum amount of changes needed. DON'T DO MORE THAN WHAT THE USER ASKS FOR. - -# Thinking Process - -Before responding to user requests, ALWAYS use tags to carefully plan your approach. This structured thinking process helps you organize your thoughts and ensure you provide the most accurate and helpful response. Your thinking should: - -- Use **bullet points** to break down the steps -- **Bold key insights** and important considerations -- Follow a clear analytical framework - -Example of proper thinking structure for a debugging request: - - -• **Identify the specific UI/FE bug described by the user** - - "Form submission button doesn't work when clicked" - - User reports clicking the button has no effect - - This appears to be a **functional issue**, not just styling - -• **Examine relevant components in the codebase** - - Form component at `src/components/ContactForm.jsx` - - Button component at `src/components/Button.jsx` - - Form submission logic in `src/utils/formHandlers.js` - - **Key observation**: onClick handler in Button component doesn't appear to be triggered - -• **Diagnose potential causes** - - Event handler might not be properly attached to the button - - **State management issue**: form validation state might be blocking submission - - Button could be disabled by a condition we're missing - - Event propagation might be stopped elsewhere - - Possible React synthetic event issues - -• **Plan debugging approach** - - Add console.logs to track execution flow - - **Fix #1**: Ensure onClick prop is properly passed through Button component - - **Fix #2**: Check form validation state before submission - - **Fix #3**: Verify event handler is properly bound in the component - - Add error handling to catch and display submission issues - -• **Consider improvements beyond the fix** - - Add visual feedback when button is clicked (loading state) - - Implement better error handling for form submissions - - Add logging to help debug edge cases - - -After completing your thinking process, proceed with your response following the guidelines above. Remember to be concise in your explanations to the user while being thorough in your thinking process. - -This structured thinking ensures you: -1. Don't miss important aspects of the request -2. Consider all relevant factors before making changes -3. Deliver more accurate and helpful responses -4. Maintain a consistent approach to problem-solving - - # AI_RULES.md diff --git a/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context-3.txt b/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context-3.txt index ca36066..174f140 100644 --- a/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context-3.txt +++ b/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context-3.txt @@ -22,6 +22,10 @@ } ], "stream": true, + "thinking": { + "type": "enabled", + "include_thoughts": true + }, "dyad_options": { "files": [ { diff --git a/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context-4.txt b/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context-4.txt index 5f145ed..0e8d295 100644 --- a/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context-4.txt +++ b/e2e-tests/snapshots/context_manage.spec.ts_manage-context---smart-context-4.txt @@ -30,6 +30,10 @@ } ], "stream": true, + "thinking": { + "type": "enabled", + "include_thoughts": true + }, "dyad_options": { "files": [ { diff --git a/e2e-tests/snapshots/context_window.spec.ts_context-window-1.txt b/e2e-tests/snapshots/context_window.spec.ts_context-window-1.txt index 639e311..6d51180 100644 --- a/e2e-tests/snapshots/context_window.spec.ts_context-window-1.txt +++ b/e2e-tests/snapshots/context_window.spec.ts_context-window-1.txt @@ -282,58 +282,6 @@ Do not hesitate to extensively use console logs to follow the flow of the code. DO NOT OVERENGINEER THE CODE. You take great pride in keeping things simple and elegant. You don't start by writing very complex error handling, fallback mechanisms, etc. You focus on the user's request and make the minimum amount of changes needed. DON'T DO MORE THAN WHAT THE USER ASKS FOR. - -# Thinking Process - -Before responding to user requests, ALWAYS use tags to carefully plan your approach. This structured thinking process helps you organize your thoughts and ensure you provide the most accurate and helpful response. Your thinking should: - -- Use **bullet points** to break down the steps -- **Bold key insights** and important considerations -- Follow a clear analytical framework - -Example of proper thinking structure for a debugging request: - - -• **Identify the specific UI/FE bug described by the user** - - "Form submission button doesn't work when clicked" - - User reports clicking the button has no effect - - This appears to be a **functional issue**, not just styling - -• **Examine relevant components in the codebase** - - Form component at `src/components/ContactForm.jsx` - - Button component at `src/components/Button.jsx` - - Form submission logic in `src/utils/formHandlers.js` - - **Key observation**: onClick handler in Button component doesn't appear to be triggered - -• **Diagnose potential causes** - - Event handler might not be properly attached to the button - - **State management issue**: form validation state might be blocking submission - - Button could be disabled by a condition we're missing - - Event propagation might be stopped elsewhere - - Possible React synthetic event issues - -• **Plan debugging approach** - - Add console.logs to track execution flow - - **Fix #1**: Ensure onClick prop is properly passed through Button component - - **Fix #2**: Check form validation state before submission - - **Fix #3**: Verify event handler is properly bound in the component - - Add error handling to catch and display submission issues - -• **Consider improvements beyond the fix** - - Add visual feedback when button is clicked (loading state) - - Implement better error handling for form submissions - - Add logging to help debug edge cases - - -After completing your thinking process, proceed with your response following the guidelines above. Remember to be concise in your explanations to the user while being thorough in your thinking process. - -This structured thinking ensures you: -1. Don't miss important aspects of the request -2. Consider all relevant factors before making changes -3. Deliver more accurate and helpful responses -4. Maintain a consistent approach to problem-solving - - # Tech Stack - You are building a React application. diff --git a/e2e-tests/snapshots/context_window.spec.ts_context-window-2.txt b/e2e-tests/snapshots/context_window.spec.ts_context-window-2.txt index b8029ff..77ae4db 100644 --- a/e2e-tests/snapshots/context_window.spec.ts_context-window-2.txt +++ b/e2e-tests/snapshots/context_window.spec.ts_context-window-2.txt @@ -282,58 +282,6 @@ Do not hesitate to extensively use console logs to follow the flow of the code. DO NOT OVERENGINEER THE CODE. You take great pride in keeping things simple and elegant. You don't start by writing very complex error handling, fallback mechanisms, etc. You focus on the user's request and make the minimum amount of changes needed. DON'T DO MORE THAN WHAT THE USER ASKS FOR. - -# Thinking Process - -Before responding to user requests, ALWAYS use tags to carefully plan your approach. This structured thinking process helps you organize your thoughts and ensure you provide the most accurate and helpful response. Your thinking should: - -- Use **bullet points** to break down the steps -- **Bold key insights** and important considerations -- Follow a clear analytical framework - -Example of proper thinking structure for a debugging request: - - -• **Identify the specific UI/FE bug described by the user** - - "Form submission button doesn't work when clicked" - - User reports clicking the button has no effect - - This appears to be a **functional issue**, not just styling - -• **Examine relevant components in the codebase** - - Form component at `src/components/ContactForm.jsx` - - Button component at `src/components/Button.jsx` - - Form submission logic in `src/utils/formHandlers.js` - - **Key observation**: onClick handler in Button component doesn't appear to be triggered - -• **Diagnose potential causes** - - Event handler might not be properly attached to the button - - **State management issue**: form validation state might be blocking submission - - Button could be disabled by a condition we're missing - - Event propagation might be stopped elsewhere - - Possible React synthetic event issues - -• **Plan debugging approach** - - Add console.logs to track execution flow - - **Fix #1**: Ensure onClick prop is properly passed through Button component - - **Fix #2**: Check form validation state before submission - - **Fix #3**: Verify event handler is properly bound in the component - - Add error handling to catch and display submission issues - -• **Consider improvements beyond the fix** - - Add visual feedback when button is clicked (loading state) - - Implement better error handling for form submissions - - Add logging to help debug edge cases - - -After completing your thinking process, proceed with your response following the guidelines above. Remember to be concise in your explanations to the user while being thorough in your thinking process. - -This structured thinking ensures you: -1. Don't miss important aspects of the request -2. Consider all relevant factors before making changes -3. Deliver more accurate and helpful responses -4. Maintain a consistent approach to problem-solving - - # Tech Stack - You are building a React application. diff --git a/e2e-tests/snapshots/context_window.spec.ts_context-window-3.txt b/e2e-tests/snapshots/context_window.spec.ts_context-window-3.txt index a69808d..ad8d707 100644 --- a/e2e-tests/snapshots/context_window.spec.ts_context-window-3.txt +++ b/e2e-tests/snapshots/context_window.spec.ts_context-window-3.txt @@ -282,58 +282,6 @@ Do not hesitate to extensively use console logs to follow the flow of the code. DO NOT OVERENGINEER THE CODE. You take great pride in keeping things simple and elegant. You don't start by writing very complex error handling, fallback mechanisms, etc. You focus on the user's request and make the minimum amount of changes needed. DON'T DO MORE THAN WHAT THE USER ASKS FOR. - -# Thinking Process - -Before responding to user requests, ALWAYS use tags to carefully plan your approach. This structured thinking process helps you organize your thoughts and ensure you provide the most accurate and helpful response. Your thinking should: - -- Use **bullet points** to break down the steps -- **Bold key insights** and important considerations -- Follow a clear analytical framework - -Example of proper thinking structure for a debugging request: - - -• **Identify the specific UI/FE bug described by the user** - - "Form submission button doesn't work when clicked" - - User reports clicking the button has no effect - - This appears to be a **functional issue**, not just styling - -• **Examine relevant components in the codebase** - - Form component at `src/components/ContactForm.jsx` - - Button component at `src/components/Button.jsx` - - Form submission logic in `src/utils/formHandlers.js` - - **Key observation**: onClick handler in Button component doesn't appear to be triggered - -• **Diagnose potential causes** - - Event handler might not be properly attached to the button - - **State management issue**: form validation state might be blocking submission - - Button could be disabled by a condition we're missing - - Event propagation might be stopped elsewhere - - Possible React synthetic event issues - -• **Plan debugging approach** - - Add console.logs to track execution flow - - **Fix #1**: Ensure onClick prop is properly passed through Button component - - **Fix #2**: Check form validation state before submission - - **Fix #3**: Verify event handler is properly bound in the component - - Add error handling to catch and display submission issues - -• **Consider improvements beyond the fix** - - Add visual feedback when button is clicked (loading state) - - Implement better error handling for form submissions - - Add logging to help debug edge cases - - -After completing your thinking process, proceed with your response following the guidelines above. Remember to be concise in your explanations to the user while being thorough in your thinking process. - -This structured thinking ensures you: -1. Don't miss important aspects of the request -2. Consider all relevant factors before making changes -3. Deliver more accurate and helpful responses -4. Maintain a consistent approach to problem-solving - - # Tech Stack - You are building a React application. diff --git a/e2e-tests/snapshots/context_window.spec.ts_context-window-5.txt b/e2e-tests/snapshots/context_window.spec.ts_context-window-5.txt index 32017ce..239139e 100644 --- a/e2e-tests/snapshots/context_window.spec.ts_context-window-5.txt +++ b/e2e-tests/snapshots/context_window.spec.ts_context-window-5.txt @@ -282,58 +282,6 @@ Do not hesitate to extensively use console logs to follow the flow of the code. DO NOT OVERENGINEER THE CODE. You take great pride in keeping things simple and elegant. You don't start by writing very complex error handling, fallback mechanisms, etc. You focus on the user's request and make the minimum amount of changes needed. DON'T DO MORE THAN WHAT THE USER ASKS FOR. - -# Thinking Process - -Before responding to user requests, ALWAYS use tags to carefully plan your approach. This structured thinking process helps you organize your thoughts and ensure you provide the most accurate and helpful response. Your thinking should: - -- Use **bullet points** to break down the steps -- **Bold key insights** and important considerations -- Follow a clear analytical framework - -Example of proper thinking structure for a debugging request: - - -• **Identify the specific UI/FE bug described by the user** - - "Form submission button doesn't work when clicked" - - User reports clicking the button has no effect - - This appears to be a **functional issue**, not just styling - -• **Examine relevant components in the codebase** - - Form component at `src/components/ContactForm.jsx` - - Button component at `src/components/Button.jsx` - - Form submission logic in `src/utils/formHandlers.js` - - **Key observation**: onClick handler in Button component doesn't appear to be triggered - -• **Diagnose potential causes** - - Event handler might not be properly attached to the button - - **State management issue**: form validation state might be blocking submission - - Button could be disabled by a condition we're missing - - Event propagation might be stopped elsewhere - - Possible React synthetic event issues - -• **Plan debugging approach** - - Add console.logs to track execution flow - - **Fix #1**: Ensure onClick prop is properly passed through Button component - - **Fix #2**: Check form validation state before submission - - **Fix #3**: Verify event handler is properly bound in the component - - Add error handling to catch and display submission issues - -• **Consider improvements beyond the fix** - - Add visual feedback when button is clicked (loading state) - - Implement better error handling for form submissions - - Add logging to help debug edge cases - - -After completing your thinking process, proceed with your response following the guidelines above. Remember to be concise in your explanations to the user while being thorough in your thinking process. - -This structured thinking ensures you: -1. Don't miss important aspects of the request -2. Consider all relevant factors before making changes -3. Deliver more accurate and helpful responses -4. Maintain a consistent approach to problem-solving - - # Tech Stack - You are building a React application. diff --git a/e2e-tests/snapshots/dump_messages.spec.ts_dump-messages-1.txt b/e2e-tests/snapshots/dump_messages.spec.ts_dump-messages-1.txt index d108970..533b812 100644 --- a/e2e-tests/snapshots/dump_messages.spec.ts_dump-messages-1.txt +++ b/e2e-tests/snapshots/dump_messages.spec.ts_dump-messages-1.txt @@ -282,58 +282,6 @@ Do not hesitate to extensively use console logs to follow the flow of the code. DO NOT OVERENGINEER THE CODE. You take great pride in keeping things simple and elegant. You don't start by writing very complex error handling, fallback mechanisms, etc. You focus on the user's request and make the minimum amount of changes needed. DON'T DO MORE THAN WHAT THE USER ASKS FOR. - -# Thinking Process - -Before responding to user requests, ALWAYS use tags to carefully plan your approach. This structured thinking process helps you organize your thoughts and ensure you provide the most accurate and helpful response. Your thinking should: - -- Use **bullet points** to break down the steps -- **Bold key insights** and important considerations -- Follow a clear analytical framework - -Example of proper thinking structure for a debugging request: - - -• **Identify the specific UI/FE bug described by the user** - - "Form submission button doesn't work when clicked" - - User reports clicking the button has no effect - - This appears to be a **functional issue**, not just styling - -• **Examine relevant components in the codebase** - - Form component at `src/components/ContactForm.jsx` - - Button component at `src/components/Button.jsx` - - Form submission logic in `src/utils/formHandlers.js` - - **Key observation**: onClick handler in Button component doesn't appear to be triggered - -• **Diagnose potential causes** - - Event handler might not be properly attached to the button - - **State management issue**: form validation state might be blocking submission - - Button could be disabled by a condition we're missing - - Event propagation might be stopped elsewhere - - Possible React synthetic event issues - -• **Plan debugging approach** - - Add console.logs to track execution flow - - **Fix #1**: Ensure onClick prop is properly passed through Button component - - **Fix #2**: Check form validation state before submission - - **Fix #3**: Verify event handler is properly bound in the component - - Add error handling to catch and display submission issues - -• **Consider improvements beyond the fix** - - Add visual feedback when button is clicked (loading state) - - Implement better error handling for form submissions - - Add logging to help debug edge cases - - -After completing your thinking process, proceed with your response following the guidelines above. Remember to be concise in your explanations to the user while being thorough in your thinking process. - -This structured thinking ensures you: -1. Don't miss important aspects of the request -2. Consider all relevant factors before making changes -3. Deliver more accurate and helpful responses -4. Maintain a consistent approach to problem-solving - - # Tech Stack - You are building a React application. diff --git a/e2e-tests/snapshots/engine.spec.ts_auto-should-send-message-to-gateway-1.txt b/e2e-tests/snapshots/engine.spec.ts_auto-should-send-message-to-gateway-1.txt index 018ffaf..cd3f45e 100644 --- a/e2e-tests/snapshots/engine.spec.ts_auto-should-send-message-to-gateway-1.txt +++ b/e2e-tests/snapshots/engine.spec.ts_auto-should-send-message-to-gateway-1.txt @@ -3,6 +3,10 @@ "model": "gemini/gemini-2.5-flash-preview-05-20", "max_tokens": 8000, "temperature": 0, + "thinking": { + "type": "enabled", + "include_thoughts": true + }, "messages": [ { "role": "system", diff --git a/e2e-tests/snapshots/engine.spec.ts_send-message-to-engine---anthropic-claude-sonnet-4-1.txt b/e2e-tests/snapshots/engine.spec.ts_send-message-to-engine---anthropic-claude-sonnet-4-1.txt new file mode 100644 index 0000000..8a4426e --- /dev/null +++ b/e2e-tests/snapshots/engine.spec.ts_send-message-to-engine---anthropic-claude-sonnet-4-1.txt @@ -0,0 +1,372 @@ +{ + "body": { + "model": "anthropic/claude-sonnet-4-20250514", + "max_tokens": 16000, + "temperature": 0, + "messages": [ + { + "role": "system", + "content": "[[SYSTEM_MESSAGE]]" + }, + { + "role": "user", + "content": "[dump] tc=turbo-edits" + } + ], + "stream": true, + "dyad_options": { + "files": [ + { + "path": "AI_RULES.md", + "content": "# Tech Stack\n\n- You are building a React application.\n- Use TypeScript.\n- Use React Router. KEEP the routes in src/App.tsx\n- Always put source code in the src folder.\n- Put pages into src/pages/\n- Put components into src/components/\n- The main page (default page) is src/pages/Index.tsx\n- UPDATE the main page to include the new components. OTHERWISE, the user can NOT see any components!\n- ALWAYS try to use the shadcn/ui library.\n- Tailwind CSS: always use Tailwind CSS for styling components. Utilize Tailwind classes extensively for layout, spacing, colors, and other design aspects.\n\nAvailable packages and libraries:\n\n- The lucide-react package is installed for icons.\n- You ALREADY have ALL the shadcn/ui components and their dependencies installed. So you don't need to install them again.\n- You have ALL the necessary Radix UI components installed.\n- Use prebuilt components from the shadcn/ui library after importing them. Note that these files shouldn't be edited, so make new components if you need to change them.\n", + "force": false + }, + { + "path": "eslint.config.js", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "index.html", + "content": "\n\n \n \n \n dyad-generated-app\n \n\n \n
\n \n \n\n", + "force": false + }, + { + "path": "package.json", + "content": "{\n \"dependencies\": {\n \"@hookform/resolvers\": \"^3.9.0\",\n \"@radix-ui/react-accordion\": \"^1.2.0\",\n \"@radix-ui/react-alert-dialog\": \"^1.1.1\",\n \"@radix-ui/react-aspect-ratio\": \"^1.1.0\",\n \"@radix-ui/react-avatar\": \"^1.1.0\",\n \"@radix-ui/react-checkbox\": \"^1.1.1\",\n \"@radix-ui/react-collapsible\": \"^1.1.0\",\n \"@radix-ui/react-context-menu\": \"^2.2.1\",\n \"@radix-ui/react-dialog\": \"^1.1.2\",\n \"@radix-ui/react-dropdown-menu\": \"^2.1.1\",\n \"@radix-ui/react-hover-card\": \"^1.1.1\",\n \"@radix-ui/react-label\": \"^2.1.0\",\n \"@radix-ui/react-menubar\": \"^1.1.1\",\n \"@radix-ui/react-navigation-menu\": \"^1.2.0\",\n \"@radix-ui/react-popover\": \"^1.1.1\",\n \"@radix-ui/react-progress\": \"^1.1.0\",\n \"@radix-ui/react-radio-group\": \"^1.2.0\",\n \"@radix-ui/react-scroll-area\": \"^1.1.0\",\n \"@radix-ui/react-select\": \"^2.1.1\",\n \"@radix-ui/react-separator\": \"^1.1.0\",\n \"@radix-ui/react-slider\": \"^1.2.0\",\n \"@radix-ui/react-slot\": \"^1.1.0\",\n \"@radix-ui/react-switch\": \"^1.1.0\",\n \"@radix-ui/react-tabs\": \"^1.1.0\",\n \"@radix-ui/react-toast\": \"^1.2.1\",\n \"@radix-ui/react-toggle\": \"^1.1.0\",\n \"@radix-ui/react-toggle-group\": \"^1.1.0\",\n \"@radix-ui/react-tooltip\": \"^1.1.4\",\n \"@tanstack/react-query\": \"^5.56.2\",\n \"class-variance-authority\": \"^0.7.1\",\n \"clsx\": \"^2.1.1\",\n \"cmdk\": \"^1.0.0\",\n \"date-fns\": \"^3.6.0\",\n \"embla-carousel-react\": \"^8.3.0\",\n \"input-otp\": \"^1.2.4\",\n \"lucide-react\": \"^0.462.0\",\n \"next-themes\": \"^0.3.0\",\n \"react\": \"^18.3.1\",\n \"react-day-picker\": \"^8.10.1\",\n \"react-dom\": \"^18.3.1\",\n \"react-hook-form\": \"^7.53.0\",\n \"react-resizable-panels\": \"^2.1.3\",\n \"react-router-dom\": \"^6.26.2\",\n \"recharts\": \"^2.12.7\",\n \"sonner\": \"^1.5.0\",\n \"tailwind-merge\": \"^2.5.2\",\n \"tailwindcss-animate\": \"^1.0.7\",\n \"vaul\": \"^0.9.3\",\n \"zod\": \"^3.23.8\"\n },\n \"devDependencies\": {\n \"@dyad-sh/react-vite-component-tagger\": \"^0.8.0\",\n \"@eslint/js\": \"^9.9.0\",\n \"@tailwindcss/typography\": \"^0.5.15\",\n \"@types/node\": \"^22.5.5\",\n \"@types/react\": \"^18.3.3\",\n \"@types/react-dom\": \"^18.3.0\",\n \"@vitejs/plugin-react-swc\": \"^3.9.0\",\n \"autoprefixer\": \"^10.4.20\",\n \"eslint\": \"^9.9.0\",\n \"eslint-plugin-react-hooks\": \"^5.1.0-rc.0\",\n \"eslint-plugin-react-refresh\": \"^0.4.9\",\n \"globals\": \"^15.9.0\",\n \"postcss\": \"^8.4.47\",\n \"tailwindcss\": \"^3.4.11\",\n \"typescript\": \"^5.5.3\",\n \"typescript-eslint\": \"^8.0.1\",\n \"vite\": \"^6.3.4\"\n }\n}", + "force": false + }, + { + "path": "postcss.config.js", + "content": "export default {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};\n", + "force": false + }, + { + "path": "README.md", + "content": "# Welcome to your Dyad app\n", + "force": false + }, + { + "path": "src/App.css", + "content": "#root {\n max-width: 1280px;\n margin: 0 auto;\n padding: 2rem;\n text-align: center;\n}\n\n.logo {\n height: 6em;\n padding: 1.5em;\n will-change: filter;\n transition: filter 300ms;\n}\n.logo:hover {\n filter: drop-shadow(0 0 2em #646cffaa);\n}\n.logo.react:hover {\n filter: drop-shadow(0 0 2em #61dafbaa);\n}\n\n@keyframes logo-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n}\n\n@media (prefers-reduced-motion: no-preference) {\n a:nth-of-type(2) .logo {\n animation: logo-spin infinite 20s linear;\n }\n}\n\n.card {\n padding: 2em;\n}\n\n.read-the-docs {\n color: #888;\n}\n", + "force": false + }, + { + "path": "src/App.tsx", + "content": "import { Toaster } from \"@/components/ui/toaster\";\nimport { Toaster as Sonner } from \"@/components/ui/sonner\";\nimport { TooltipProvider } from \"@/components/ui/tooltip\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { BrowserRouter, Routes, Route } from \"react-router-dom\";\nimport Index from \"./pages/Index\";\nimport NotFound from \"./pages/NotFound\";\n\nconst queryClient = new QueryClient();\n\nconst App = () => (\n \n \n \n \n \n \n } />\n {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL \"*\" ROUTE */}\n } />\n \n \n \n \n);\n\nexport default App;\n", + "force": false + }, + { + "path": "src/components/made-with-dyad.tsx", + "content": "export const MadeWithDyad = () => {\n return (\n
\n \n Made with Dyad\n \n
\n );\n};\n", + "force": false + }, + { + "path": "src/components/ui/accordion.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/alert-dialog.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/alert.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/aspect-ratio.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/avatar.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/badge.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/breadcrumb.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/button.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/calendar.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/card.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/carousel.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/chart.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/checkbox.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/collapsible.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/command.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/context-menu.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/dialog.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/drawer.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/dropdown-menu.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/form.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/hover-card.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/input-otp.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/input.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/label.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/menubar.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/navigation-menu.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/pagination.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/popover.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/progress.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/radio-group.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/resizable.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/scroll-area.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/select.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/separator.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/sheet.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/sidebar.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/skeleton.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/slider.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/sonner.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/switch.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/table.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/tabs.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/textarea.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/toast.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/toaster.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/toggle-group.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/toggle.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/tooltip.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/use-toast.ts", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/globals.css", + "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n :root {\n --background: 0 0% 100%;\n --foreground: 222.2 84% 4.9%;\n\n --card: 0 0% 100%;\n --card-foreground: 222.2 84% 4.9%;\n\n --popover: 0 0% 100%;\n --popover-foreground: 222.2 84% 4.9%;\n\n --primary: 222.2 47.4% 11.2%;\n --primary-foreground: 210 40% 98%;\n\n --secondary: 210 40% 96.1%;\n --secondary-foreground: 222.2 47.4% 11.2%;\n\n --muted: 210 40% 96.1%;\n --muted-foreground: 215.4 16.3% 46.9%;\n\n --accent: 210 40% 96.1%;\n --accent-foreground: 222.2 47.4% 11.2%;\n\n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 210 40% 98%;\n\n --border: 214.3 31.8% 91.4%;\n --input: 214.3 31.8% 91.4%;\n --ring: 222.2 84% 4.9%;\n\n --radius: 0.5rem;\n\n --sidebar-background: 0 0% 98%;\n\n --sidebar-foreground: 240 5.3% 26.1%;\n\n --sidebar-primary: 240 5.9% 10%;\n\n --sidebar-primary-foreground: 0 0% 98%;\n\n --sidebar-accent: 240 4.8% 95.9%;\n\n --sidebar-accent-foreground: 240 5.9% 10%;\n\n --sidebar-border: 220 13% 91%;\n\n --sidebar-ring: 217.2 91.2% 59.8%;\n }\n\n .dark {\n --background: 222.2 84% 4.9%;\n --foreground: 210 40% 98%;\n\n --card: 222.2 84% 4.9%;\n --card-foreground: 210 40% 98%;\n\n --popover: 222.2 84% 4.9%;\n --popover-foreground: 210 40% 98%;\n\n --primary: 210 40% 98%;\n --primary-foreground: 222.2 47.4% 11.2%;\n\n --secondary: 217.2 32.6% 17.5%;\n --secondary-foreground: 210 40% 98%;\n\n --muted: 217.2 32.6% 17.5%;\n --muted-foreground: 215 20.2% 65.1%;\n\n --accent: 217.2 32.6% 17.5%;\n --accent-foreground: 210 40% 98%;\n\n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 210 40% 98%;\n\n --border: 217.2 32.6% 17.5%;\n --input: 217.2 32.6% 17.5%;\n --ring: 212.7 26.8% 83.9%;\n --sidebar-background: 240 5.9% 10%;\n --sidebar-foreground: 240 4.8% 95.9%;\n --sidebar-primary: 224.3 76.3% 48%;\n --sidebar-primary-foreground: 0 0% 100%;\n --sidebar-accent: 240 3.7% 15.9%;\n --sidebar-accent-foreground: 240 4.8% 95.9%;\n --sidebar-border: 240 3.7% 15.9%;\n --sidebar-ring: 217.2 91.2% 59.8%;\n }\n}\n\n@layer base {\n * {\n @apply border-border;\n }\n\n body {\n @apply bg-background text-foreground;\n }\n}\n", + "force": false + }, + { + "path": "src/hooks/use-mobile.tsx", + "content": "import * as React from \"react\";\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n const [isMobile, setIsMobile] = React.useState(\n undefined,\n );\n\n React.useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n const onChange = () => {\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n };\n mql.addEventListener(\"change\", onChange);\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n return () => mql.removeEventListener(\"change\", onChange);\n }, []);\n\n return !!isMobile;\n}\n", + "force": false + }, + { + "path": "src/hooks/use-toast.ts", + "content": "import * as React from \"react\";\n\nimport type { ToastActionElement, ToastProps } from \"@/components/ui/toast\";\n\nconst TOAST_LIMIT = 1;\nconst TOAST_REMOVE_DELAY = 1000000;\n\ntype ToasterToast = ToastProps & {\n id: string;\n title?: React.ReactNode;\n description?: React.ReactNode;\n action?: ToastActionElement;\n};\n\nconst _actionTypes = {\n ADD_TOAST: \"ADD_TOAST\",\n UPDATE_TOAST: \"UPDATE_TOAST\",\n DISMISS_TOAST: \"DISMISS_TOAST\",\n REMOVE_TOAST: \"REMOVE_TOAST\",\n} as const;\n\nlet count = 0;\n\nfunction genId() {\n count = (count + 1) % Number.MAX_SAFE_INTEGER;\n return count.toString();\n}\n\ntype ActionType = typeof _actionTypes;\n\ntype Action =\n | {\n type: ActionType[\"ADD_TOAST\"];\n toast: ToasterToast;\n }\n | {\n type: ActionType[\"UPDATE_TOAST\"];\n toast: Partial;\n }\n | {\n type: ActionType[\"DISMISS_TOAST\"];\n toastId?: ToasterToast[\"id\"];\n }\n | {\n type: ActionType[\"REMOVE_TOAST\"];\n toastId?: ToasterToast[\"id\"];\n };\n\ninterface State {\n toasts: ToasterToast[];\n}\n\nconst toastTimeouts = new Map>();\n\nconst addToRemoveQueue = (toastId: string) => {\n if (toastTimeouts.has(toastId)) {\n return;\n }\n\n const timeout = setTimeout(() => {\n toastTimeouts.delete(toastId);\n dispatch({\n type: \"REMOVE_TOAST\",\n toastId: toastId,\n });\n }, TOAST_REMOVE_DELAY);\n\n toastTimeouts.set(toastId, timeout);\n};\n\nexport const reducer = (state: State, action: Action): State => {\n switch (action.type) {\n case \"ADD_TOAST\":\n return {\n ...state,\n toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),\n };\n\n case \"UPDATE_TOAST\":\n return {\n ...state,\n toasts: state.toasts.map((t) =>\n t.id === action.toast.id ? { ...t, ...action.toast } : t,\n ),\n };\n\n case \"DISMISS_TOAST\": {\n const { toastId } = action;\n\n // ! Side effects ! - This could be extracted into a dismissToast() action,\n // but I'll keep it here for simplicity\n if (toastId) {\n addToRemoveQueue(toastId);\n } else {\n state.toasts.forEach((toast) => {\n addToRemoveQueue(toast.id);\n });\n }\n\n return {\n ...state,\n toasts: state.toasts.map((t) =>\n t.id === toastId || toastId === undefined\n ? {\n ...t,\n open: false,\n }\n : t,\n ),\n };\n }\n case \"REMOVE_TOAST\":\n if (action.toastId === undefined) {\n return {\n ...state,\n toasts: [],\n };\n }\n return {\n ...state,\n toasts: state.toasts.filter((t) => t.id !== action.toastId),\n };\n }\n};\n\nconst listeners: Array<(state: State) => void> = [];\n\nlet memoryState: State = { toasts: [] };\n\nfunction dispatch(action: Action) {\n memoryState = reducer(memoryState, action);\n listeners.forEach((listener) => {\n listener(memoryState);\n });\n}\n\ntype Toast = Omit;\n\nfunction toast({ ...props }: Toast) {\n const id = genId();\n\n const update = (props: ToasterToast) =>\n dispatch({\n type: \"UPDATE_TOAST\",\n toast: { ...props, id },\n });\n const dismiss = () => dispatch({ type: \"DISMISS_TOAST\", toastId: id });\n\n dispatch({\n type: \"ADD_TOAST\",\n toast: {\n ...props,\n id,\n open: true,\n onOpenChange: (open) => {\n if (!open) dismiss();\n },\n },\n });\n\n return {\n id: id,\n dismiss,\n update,\n };\n}\n\nfunction useToast() {\n const [state, setState] = React.useState(memoryState);\n\n React.useEffect(() => {\n listeners.push(setState);\n return () => {\n const index = listeners.indexOf(setState);\n if (index > -1) {\n listeners.splice(index, 1);\n }\n };\n }, [state]);\n\n return {\n ...state,\n toast,\n dismiss: (toastId?: string) => dispatch({ type: \"DISMISS_TOAST\", toastId }),\n };\n}\n\nexport { useToast, toast };\n", + "force": false + }, + { + "path": "src/lib/utils.ts", + "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n", + "force": false + }, + { + "path": "src/main.tsx", + "content": "import { createRoot } from \"react-dom/client\";\nimport App from \"./App.tsx\";\nimport \"./globals.css\";\n\ncreateRoot(document.getElementById(\"root\")!).render();\n", + "force": false + }, + { + "path": "src/pages/Index.tsx", + "content": "// Update this page (the content is just a fallback if you fail to update the page)\n\nimport { MadeWithDyad } from \"@/components/made-with-dyad\";\n\nconst Index = () => {\n return (\n
\n
\n

Welcome to Your Blank App

\n

\n Start building your amazing project here!\n

\n
\n \n
\n );\n};\n\nexport default Index;\n", + "force": false + }, + { + "path": "src/pages/NotFound.tsx", + "content": "import { useLocation } from \"react-router-dom\";\nimport { useEffect } from \"react\";\n\nconst NotFound = () => {\n const location = useLocation();\n\n useEffect(() => {\n console.error(\n \"404 Error: User attempted to access non-existent route:\",\n location.pathname,\n );\n }, [location.pathname]);\n\n return (\n
\n
\n

404

\n

Oops! Page not found

\n \n Return to Home\n \n
\n
\n );\n};\n\nexport default NotFound;\n", + "force": false + }, + { + "path": "src/utils/toast.ts", + "content": "import { toast } from \"sonner\";\n\nexport const showSuccess = (message: string) => {\n toast.success(message);\n};\n\nexport const showError = (message: string) => {\n toast.error(message);\n};\n\nexport const showLoading = (message: string) => {\n return toast.loading(message);\n};\n\nexport const dismissToast = (toastId: string) => {\n toast.dismiss(toastId);\n};\n", + "force": false + }, + { + "path": "src/vite-env.d.ts", + "content": "/// \n", + "force": false + }, + { + "path": "tailwind.config.ts", + "content": "import type { Config } from \"tailwindcss\";\n\nexport default {\n darkMode: [\"class\"],\n content: [\n \"./pages/**/*.{ts,tsx}\",\n \"./components/**/*.{ts,tsx}\",\n \"./app/**/*.{ts,tsx}\",\n \"./src/**/*.{ts,tsx}\",\n ],\n prefix: \"\",\n theme: {\n container: {\n center: true,\n padding: \"2rem\",\n screens: {\n \"2xl\": \"1400px\",\n },\n },\n extend: {\n colors: {\n border: \"hsl(var(--border))\",\n input: \"hsl(var(--input))\",\n ring: \"hsl(var(--ring))\",\n background: \"hsl(var(--background))\",\n foreground: \"hsl(var(--foreground))\",\n primary: {\n DEFAULT: \"hsl(var(--primary))\",\n foreground: \"hsl(var(--primary-foreground))\",\n },\n secondary: {\n DEFAULT: \"hsl(var(--secondary))\",\n foreground: \"hsl(var(--secondary-foreground))\",\n },\n destructive: {\n DEFAULT: \"hsl(var(--destructive))\",\n foreground: \"hsl(var(--destructive-foreground))\",\n },\n muted: {\n DEFAULT: \"hsl(var(--muted))\",\n foreground: \"hsl(var(--muted-foreground))\",\n },\n accent: {\n DEFAULT: \"hsl(var(--accent))\",\n foreground: \"hsl(var(--accent-foreground))\",\n },\n popover: {\n DEFAULT: \"hsl(var(--popover))\",\n foreground: \"hsl(var(--popover-foreground))\",\n },\n card: {\n DEFAULT: \"hsl(var(--card))\",\n foreground: \"hsl(var(--card-foreground))\",\n },\n sidebar: {\n DEFAULT: \"hsl(var(--sidebar-background))\",\n foreground: \"hsl(var(--sidebar-foreground))\",\n primary: \"hsl(var(--sidebar-primary))\",\n \"primary-foreground\": \"hsl(var(--sidebar-primary-foreground))\",\n accent: \"hsl(var(--sidebar-accent))\",\n \"accent-foreground\": \"hsl(var(--sidebar-accent-foreground))\",\n border: \"hsl(var(--sidebar-border))\",\n ring: \"hsl(var(--sidebar-ring))\",\n },\n },\n borderRadius: {\n lg: \"var(--radius)\",\n md: \"calc(var(--radius) - 2px)\",\n sm: \"calc(var(--radius) - 4px)\",\n },\n keyframes: {\n \"accordion-down\": {\n from: {\n height: \"0\",\n },\n to: {\n height: \"var(--radix-accordion-content-height)\",\n },\n },\n \"accordion-up\": {\n from: {\n height: \"var(--radix-accordion-content-height)\",\n },\n to: {\n height: \"0\",\n },\n },\n },\n animation: {\n \"accordion-down\": \"accordion-down 0.2s ease-out\",\n \"accordion-up\": \"accordion-up 0.2s ease-out\",\n },\n },\n },\n plugins: [require(\"tailwindcss-animate\")],\n} satisfies Config;\n", + "force": false + }, + { + "path": "vite.config.ts", + "content": "import { defineConfig } from \"vite\";\nimport dyadComponentTagger from \"@dyad-sh/react-vite-component-tagger\";\nimport react from \"@vitejs/plugin-react-swc\";\nimport path from \"path\";\n\nexport default defineConfig(() => ({\n server: {\n host: \"::\",\n port: 8080,\n },\n plugins: [dyadComponentTagger(), react()],\n resolve: {\n alias: {\n \"@\": path.resolve(__dirname, \"./src\"),\n },\n },\n}));\n", + "force": false + } + ], + "enable_lazy_edits": true, + "enable_smart_files_context": true + } + }, + "headers": { + "authorization": "Bearer testdyadkey" + } +} \ No newline at end of file diff --git a/e2e-tests/snapshots/engine.spec.ts_send-message-to-engine---openai-gpt-4-1-1.txt b/e2e-tests/snapshots/engine.spec.ts_send-message-to-engine---openai-gpt-4-1-1.txt new file mode 100644 index 0000000..b309c4a --- /dev/null +++ b/e2e-tests/snapshots/engine.spec.ts_send-message-to-engine---openai-gpt-4-1-1.txt @@ -0,0 +1,372 @@ +{ + "body": { + "model": "gpt-4.1", + "max_tokens": 32768, + "temperature": 0, + "messages": [ + { + "role": "system", + "content": "[[SYSTEM_MESSAGE]]" + }, + { + "role": "user", + "content": "[dump] tc=turbo-edits" + } + ], + "stream": true, + "dyad_options": { + "files": [ + { + "path": "AI_RULES.md", + "content": "# Tech Stack\n\n- You are building a React application.\n- Use TypeScript.\n- Use React Router. KEEP the routes in src/App.tsx\n- Always put source code in the src folder.\n- Put pages into src/pages/\n- Put components into src/components/\n- The main page (default page) is src/pages/Index.tsx\n- UPDATE the main page to include the new components. OTHERWISE, the user can NOT see any components!\n- ALWAYS try to use the shadcn/ui library.\n- Tailwind CSS: always use Tailwind CSS for styling components. Utilize Tailwind classes extensively for layout, spacing, colors, and other design aspects.\n\nAvailable packages and libraries:\n\n- The lucide-react package is installed for icons.\n- You ALREADY have ALL the shadcn/ui components and their dependencies installed. So you don't need to install them again.\n- You have ALL the necessary Radix UI components installed.\n- Use prebuilt components from the shadcn/ui library after importing them. Note that these files shouldn't be edited, so make new components if you need to change them.\n", + "force": false + }, + { + "path": "eslint.config.js", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "index.html", + "content": "\n\n \n \n \n dyad-generated-app\n \n\n \n
\n \n \n\n", + "force": false + }, + { + "path": "package.json", + "content": "{\n \"dependencies\": {\n \"@hookform/resolvers\": \"^3.9.0\",\n \"@radix-ui/react-accordion\": \"^1.2.0\",\n \"@radix-ui/react-alert-dialog\": \"^1.1.1\",\n \"@radix-ui/react-aspect-ratio\": \"^1.1.0\",\n \"@radix-ui/react-avatar\": \"^1.1.0\",\n \"@radix-ui/react-checkbox\": \"^1.1.1\",\n \"@radix-ui/react-collapsible\": \"^1.1.0\",\n \"@radix-ui/react-context-menu\": \"^2.2.1\",\n \"@radix-ui/react-dialog\": \"^1.1.2\",\n \"@radix-ui/react-dropdown-menu\": \"^2.1.1\",\n \"@radix-ui/react-hover-card\": \"^1.1.1\",\n \"@radix-ui/react-label\": \"^2.1.0\",\n \"@radix-ui/react-menubar\": \"^1.1.1\",\n \"@radix-ui/react-navigation-menu\": \"^1.2.0\",\n \"@radix-ui/react-popover\": \"^1.1.1\",\n \"@radix-ui/react-progress\": \"^1.1.0\",\n \"@radix-ui/react-radio-group\": \"^1.2.0\",\n \"@radix-ui/react-scroll-area\": \"^1.1.0\",\n \"@radix-ui/react-select\": \"^2.1.1\",\n \"@radix-ui/react-separator\": \"^1.1.0\",\n \"@radix-ui/react-slider\": \"^1.2.0\",\n \"@radix-ui/react-slot\": \"^1.1.0\",\n \"@radix-ui/react-switch\": \"^1.1.0\",\n \"@radix-ui/react-tabs\": \"^1.1.0\",\n \"@radix-ui/react-toast\": \"^1.2.1\",\n \"@radix-ui/react-toggle\": \"^1.1.0\",\n \"@radix-ui/react-toggle-group\": \"^1.1.0\",\n \"@radix-ui/react-tooltip\": \"^1.1.4\",\n \"@tanstack/react-query\": \"^5.56.2\",\n \"class-variance-authority\": \"^0.7.1\",\n \"clsx\": \"^2.1.1\",\n \"cmdk\": \"^1.0.0\",\n \"date-fns\": \"^3.6.0\",\n \"embla-carousel-react\": \"^8.3.0\",\n \"input-otp\": \"^1.2.4\",\n \"lucide-react\": \"^0.462.0\",\n \"next-themes\": \"^0.3.0\",\n \"react\": \"^18.3.1\",\n \"react-day-picker\": \"^8.10.1\",\n \"react-dom\": \"^18.3.1\",\n \"react-hook-form\": \"^7.53.0\",\n \"react-resizable-panels\": \"^2.1.3\",\n \"react-router-dom\": \"^6.26.2\",\n \"recharts\": \"^2.12.7\",\n \"sonner\": \"^1.5.0\",\n \"tailwind-merge\": \"^2.5.2\",\n \"tailwindcss-animate\": \"^1.0.7\",\n \"vaul\": \"^0.9.3\",\n \"zod\": \"^3.23.8\"\n },\n \"devDependencies\": {\n \"@dyad-sh/react-vite-component-tagger\": \"^0.8.0\",\n \"@eslint/js\": \"^9.9.0\",\n \"@tailwindcss/typography\": \"^0.5.15\",\n \"@types/node\": \"^22.5.5\",\n \"@types/react\": \"^18.3.3\",\n \"@types/react-dom\": \"^18.3.0\",\n \"@vitejs/plugin-react-swc\": \"^3.9.0\",\n \"autoprefixer\": \"^10.4.20\",\n \"eslint\": \"^9.9.0\",\n \"eslint-plugin-react-hooks\": \"^5.1.0-rc.0\",\n \"eslint-plugin-react-refresh\": \"^0.4.9\",\n \"globals\": \"^15.9.0\",\n \"postcss\": \"^8.4.47\",\n \"tailwindcss\": \"^3.4.11\",\n \"typescript\": \"^5.5.3\",\n \"typescript-eslint\": \"^8.0.1\",\n \"vite\": \"^6.3.4\"\n }\n}", + "force": false + }, + { + "path": "postcss.config.js", + "content": "export default {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};\n", + "force": false + }, + { + "path": "README.md", + "content": "# Welcome to your Dyad app\n", + "force": false + }, + { + "path": "src/App.css", + "content": "#root {\n max-width: 1280px;\n margin: 0 auto;\n padding: 2rem;\n text-align: center;\n}\n\n.logo {\n height: 6em;\n padding: 1.5em;\n will-change: filter;\n transition: filter 300ms;\n}\n.logo:hover {\n filter: drop-shadow(0 0 2em #646cffaa);\n}\n.logo.react:hover {\n filter: drop-shadow(0 0 2em #61dafbaa);\n}\n\n@keyframes logo-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n}\n\n@media (prefers-reduced-motion: no-preference) {\n a:nth-of-type(2) .logo {\n animation: logo-spin infinite 20s linear;\n }\n}\n\n.card {\n padding: 2em;\n}\n\n.read-the-docs {\n color: #888;\n}\n", + "force": false + }, + { + "path": "src/App.tsx", + "content": "import { Toaster } from \"@/components/ui/toaster\";\nimport { Toaster as Sonner } from \"@/components/ui/sonner\";\nimport { TooltipProvider } from \"@/components/ui/tooltip\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { BrowserRouter, Routes, Route } from \"react-router-dom\";\nimport Index from \"./pages/Index\";\nimport NotFound from \"./pages/NotFound\";\n\nconst queryClient = new QueryClient();\n\nconst App = () => (\n \n \n \n \n \n \n } />\n {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL \"*\" ROUTE */}\n } />\n \n \n \n \n);\n\nexport default App;\n", + "force": false + }, + { + "path": "src/components/made-with-dyad.tsx", + "content": "export const MadeWithDyad = () => {\n return (\n
\n \n Made with Dyad\n \n
\n );\n};\n", + "force": false + }, + { + "path": "src/components/ui/accordion.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/alert-dialog.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/alert.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/aspect-ratio.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/avatar.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/badge.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/breadcrumb.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/button.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/calendar.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/card.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/carousel.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/chart.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/checkbox.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/collapsible.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/command.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/context-menu.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/dialog.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/drawer.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/dropdown-menu.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/form.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/hover-card.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/input-otp.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/input.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/label.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/menubar.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/navigation-menu.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/pagination.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/popover.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/progress.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/radio-group.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/resizable.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/scroll-area.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/select.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/separator.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/sheet.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/sidebar.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/skeleton.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/slider.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/sonner.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/switch.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/table.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/tabs.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/textarea.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/toast.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/toaster.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/toggle-group.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/toggle.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/tooltip.tsx", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/components/ui/use-toast.ts", + "content": "// Contents omitted for brevity", + "force": false + }, + { + "path": "src/globals.css", + "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n :root {\n --background: 0 0% 100%;\n --foreground: 222.2 84% 4.9%;\n\n --card: 0 0% 100%;\n --card-foreground: 222.2 84% 4.9%;\n\n --popover: 0 0% 100%;\n --popover-foreground: 222.2 84% 4.9%;\n\n --primary: 222.2 47.4% 11.2%;\n --primary-foreground: 210 40% 98%;\n\n --secondary: 210 40% 96.1%;\n --secondary-foreground: 222.2 47.4% 11.2%;\n\n --muted: 210 40% 96.1%;\n --muted-foreground: 215.4 16.3% 46.9%;\n\n --accent: 210 40% 96.1%;\n --accent-foreground: 222.2 47.4% 11.2%;\n\n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 210 40% 98%;\n\n --border: 214.3 31.8% 91.4%;\n --input: 214.3 31.8% 91.4%;\n --ring: 222.2 84% 4.9%;\n\n --radius: 0.5rem;\n\n --sidebar-background: 0 0% 98%;\n\n --sidebar-foreground: 240 5.3% 26.1%;\n\n --sidebar-primary: 240 5.9% 10%;\n\n --sidebar-primary-foreground: 0 0% 98%;\n\n --sidebar-accent: 240 4.8% 95.9%;\n\n --sidebar-accent-foreground: 240 5.9% 10%;\n\n --sidebar-border: 220 13% 91%;\n\n --sidebar-ring: 217.2 91.2% 59.8%;\n }\n\n .dark {\n --background: 222.2 84% 4.9%;\n --foreground: 210 40% 98%;\n\n --card: 222.2 84% 4.9%;\n --card-foreground: 210 40% 98%;\n\n --popover: 222.2 84% 4.9%;\n --popover-foreground: 210 40% 98%;\n\n --primary: 210 40% 98%;\n --primary-foreground: 222.2 47.4% 11.2%;\n\n --secondary: 217.2 32.6% 17.5%;\n --secondary-foreground: 210 40% 98%;\n\n --muted: 217.2 32.6% 17.5%;\n --muted-foreground: 215 20.2% 65.1%;\n\n --accent: 217.2 32.6% 17.5%;\n --accent-foreground: 210 40% 98%;\n\n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 210 40% 98%;\n\n --border: 217.2 32.6% 17.5%;\n --input: 217.2 32.6% 17.5%;\n --ring: 212.7 26.8% 83.9%;\n --sidebar-background: 240 5.9% 10%;\n --sidebar-foreground: 240 4.8% 95.9%;\n --sidebar-primary: 224.3 76.3% 48%;\n --sidebar-primary-foreground: 0 0% 100%;\n --sidebar-accent: 240 3.7% 15.9%;\n --sidebar-accent-foreground: 240 4.8% 95.9%;\n --sidebar-border: 240 3.7% 15.9%;\n --sidebar-ring: 217.2 91.2% 59.8%;\n }\n}\n\n@layer base {\n * {\n @apply border-border;\n }\n\n body {\n @apply bg-background text-foreground;\n }\n}\n", + "force": false + }, + { + "path": "src/hooks/use-mobile.tsx", + "content": "import * as React from \"react\";\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n const [isMobile, setIsMobile] = React.useState(\n undefined,\n );\n\n React.useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n const onChange = () => {\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n };\n mql.addEventListener(\"change\", onChange);\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n return () => mql.removeEventListener(\"change\", onChange);\n }, []);\n\n return !!isMobile;\n}\n", + "force": false + }, + { + "path": "src/hooks/use-toast.ts", + "content": "import * as React from \"react\";\n\nimport type { ToastActionElement, ToastProps } from \"@/components/ui/toast\";\n\nconst TOAST_LIMIT = 1;\nconst TOAST_REMOVE_DELAY = 1000000;\n\ntype ToasterToast = ToastProps & {\n id: string;\n title?: React.ReactNode;\n description?: React.ReactNode;\n action?: ToastActionElement;\n};\n\nconst _actionTypes = {\n ADD_TOAST: \"ADD_TOAST\",\n UPDATE_TOAST: \"UPDATE_TOAST\",\n DISMISS_TOAST: \"DISMISS_TOAST\",\n REMOVE_TOAST: \"REMOVE_TOAST\",\n} as const;\n\nlet count = 0;\n\nfunction genId() {\n count = (count + 1) % Number.MAX_SAFE_INTEGER;\n return count.toString();\n}\n\ntype ActionType = typeof _actionTypes;\n\ntype Action =\n | {\n type: ActionType[\"ADD_TOAST\"];\n toast: ToasterToast;\n }\n | {\n type: ActionType[\"UPDATE_TOAST\"];\n toast: Partial;\n }\n | {\n type: ActionType[\"DISMISS_TOAST\"];\n toastId?: ToasterToast[\"id\"];\n }\n | {\n type: ActionType[\"REMOVE_TOAST\"];\n toastId?: ToasterToast[\"id\"];\n };\n\ninterface State {\n toasts: ToasterToast[];\n}\n\nconst toastTimeouts = new Map>();\n\nconst addToRemoveQueue = (toastId: string) => {\n if (toastTimeouts.has(toastId)) {\n return;\n }\n\n const timeout = setTimeout(() => {\n toastTimeouts.delete(toastId);\n dispatch({\n type: \"REMOVE_TOAST\",\n toastId: toastId,\n });\n }, TOAST_REMOVE_DELAY);\n\n toastTimeouts.set(toastId, timeout);\n};\n\nexport const reducer = (state: State, action: Action): State => {\n switch (action.type) {\n case \"ADD_TOAST\":\n return {\n ...state,\n toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),\n };\n\n case \"UPDATE_TOAST\":\n return {\n ...state,\n toasts: state.toasts.map((t) =>\n t.id === action.toast.id ? { ...t, ...action.toast } : t,\n ),\n };\n\n case \"DISMISS_TOAST\": {\n const { toastId } = action;\n\n // ! Side effects ! - This could be extracted into a dismissToast() action,\n // but I'll keep it here for simplicity\n if (toastId) {\n addToRemoveQueue(toastId);\n } else {\n state.toasts.forEach((toast) => {\n addToRemoveQueue(toast.id);\n });\n }\n\n return {\n ...state,\n toasts: state.toasts.map((t) =>\n t.id === toastId || toastId === undefined\n ? {\n ...t,\n open: false,\n }\n : t,\n ),\n };\n }\n case \"REMOVE_TOAST\":\n if (action.toastId === undefined) {\n return {\n ...state,\n toasts: [],\n };\n }\n return {\n ...state,\n toasts: state.toasts.filter((t) => t.id !== action.toastId),\n };\n }\n};\n\nconst listeners: Array<(state: State) => void> = [];\n\nlet memoryState: State = { toasts: [] };\n\nfunction dispatch(action: Action) {\n memoryState = reducer(memoryState, action);\n listeners.forEach((listener) => {\n listener(memoryState);\n });\n}\n\ntype Toast = Omit;\n\nfunction toast({ ...props }: Toast) {\n const id = genId();\n\n const update = (props: ToasterToast) =>\n dispatch({\n type: \"UPDATE_TOAST\",\n toast: { ...props, id },\n });\n const dismiss = () => dispatch({ type: \"DISMISS_TOAST\", toastId: id });\n\n dispatch({\n type: \"ADD_TOAST\",\n toast: {\n ...props,\n id,\n open: true,\n onOpenChange: (open) => {\n if (!open) dismiss();\n },\n },\n });\n\n return {\n id: id,\n dismiss,\n update,\n };\n}\n\nfunction useToast() {\n const [state, setState] = React.useState(memoryState);\n\n React.useEffect(() => {\n listeners.push(setState);\n return () => {\n const index = listeners.indexOf(setState);\n if (index > -1) {\n listeners.splice(index, 1);\n }\n };\n }, [state]);\n\n return {\n ...state,\n toast,\n dismiss: (toastId?: string) => dispatch({ type: \"DISMISS_TOAST\", toastId }),\n };\n}\n\nexport { useToast, toast };\n", + "force": false + }, + { + "path": "src/lib/utils.ts", + "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n", + "force": false + }, + { + "path": "src/main.tsx", + "content": "import { createRoot } from \"react-dom/client\";\nimport App from \"./App.tsx\";\nimport \"./globals.css\";\n\ncreateRoot(document.getElementById(\"root\")!).render();\n", + "force": false + }, + { + "path": "src/pages/Index.tsx", + "content": "// Update this page (the content is just a fallback if you fail to update the page)\n\nimport { MadeWithDyad } from \"@/components/made-with-dyad\";\n\nconst Index = () => {\n return (\n
\n
\n

Welcome to Your Blank App

\n

\n Start building your amazing project here!\n

\n
\n \n
\n );\n};\n\nexport default Index;\n", + "force": false + }, + { + "path": "src/pages/NotFound.tsx", + "content": "import { useLocation } from \"react-router-dom\";\nimport { useEffect } from \"react\";\n\nconst NotFound = () => {\n const location = useLocation();\n\n useEffect(() => {\n console.error(\n \"404 Error: User attempted to access non-existent route:\",\n location.pathname,\n );\n }, [location.pathname]);\n\n return (\n
\n
\n

404

\n

Oops! Page not found

\n \n Return to Home\n \n
\n
\n );\n};\n\nexport default NotFound;\n", + "force": false + }, + { + "path": "src/utils/toast.ts", + "content": "import { toast } from \"sonner\";\n\nexport const showSuccess = (message: string) => {\n toast.success(message);\n};\n\nexport const showError = (message: string) => {\n toast.error(message);\n};\n\nexport const showLoading = (message: string) => {\n return toast.loading(message);\n};\n\nexport const dismissToast = (toastId: string) => {\n toast.dismiss(toastId);\n};\n", + "force": false + }, + { + "path": "src/vite-env.d.ts", + "content": "/// \n", + "force": false + }, + { + "path": "tailwind.config.ts", + "content": "import type { Config } from \"tailwindcss\";\n\nexport default {\n darkMode: [\"class\"],\n content: [\n \"./pages/**/*.{ts,tsx}\",\n \"./components/**/*.{ts,tsx}\",\n \"./app/**/*.{ts,tsx}\",\n \"./src/**/*.{ts,tsx}\",\n ],\n prefix: \"\",\n theme: {\n container: {\n center: true,\n padding: \"2rem\",\n screens: {\n \"2xl\": \"1400px\",\n },\n },\n extend: {\n colors: {\n border: \"hsl(var(--border))\",\n input: \"hsl(var(--input))\",\n ring: \"hsl(var(--ring))\",\n background: \"hsl(var(--background))\",\n foreground: \"hsl(var(--foreground))\",\n primary: {\n DEFAULT: \"hsl(var(--primary))\",\n foreground: \"hsl(var(--primary-foreground))\",\n },\n secondary: {\n DEFAULT: \"hsl(var(--secondary))\",\n foreground: \"hsl(var(--secondary-foreground))\",\n },\n destructive: {\n DEFAULT: \"hsl(var(--destructive))\",\n foreground: \"hsl(var(--destructive-foreground))\",\n },\n muted: {\n DEFAULT: \"hsl(var(--muted))\",\n foreground: \"hsl(var(--muted-foreground))\",\n },\n accent: {\n DEFAULT: \"hsl(var(--accent))\",\n foreground: \"hsl(var(--accent-foreground))\",\n },\n popover: {\n DEFAULT: \"hsl(var(--popover))\",\n foreground: \"hsl(var(--popover-foreground))\",\n },\n card: {\n DEFAULT: \"hsl(var(--card))\",\n foreground: \"hsl(var(--card-foreground))\",\n },\n sidebar: {\n DEFAULT: \"hsl(var(--sidebar-background))\",\n foreground: \"hsl(var(--sidebar-foreground))\",\n primary: \"hsl(var(--sidebar-primary))\",\n \"primary-foreground\": \"hsl(var(--sidebar-primary-foreground))\",\n accent: \"hsl(var(--sidebar-accent))\",\n \"accent-foreground\": \"hsl(var(--sidebar-accent-foreground))\",\n border: \"hsl(var(--sidebar-border))\",\n ring: \"hsl(var(--sidebar-ring))\",\n },\n },\n borderRadius: {\n lg: \"var(--radius)\",\n md: \"calc(var(--radius) - 2px)\",\n sm: \"calc(var(--radius) - 4px)\",\n },\n keyframes: {\n \"accordion-down\": {\n from: {\n height: \"0\",\n },\n to: {\n height: \"var(--radix-accordion-content-height)\",\n },\n },\n \"accordion-up\": {\n from: {\n height: \"var(--radix-accordion-content-height)\",\n },\n to: {\n height: \"0\",\n },\n },\n },\n animation: {\n \"accordion-down\": \"accordion-down 0.2s ease-out\",\n \"accordion-up\": \"accordion-up 0.2s ease-out\",\n },\n },\n },\n plugins: [require(\"tailwindcss-animate\")],\n} satisfies Config;\n", + "force": false + }, + { + "path": "vite.config.ts", + "content": "import { defineConfig } from \"vite\";\nimport dyadComponentTagger from \"@dyad-sh/react-vite-component-tagger\";\nimport react from \"@vitejs/plugin-react-swc\";\nimport path from \"path\";\n\nexport default defineConfig(() => ({\n server: {\n host: \"::\",\n port: 8080,\n },\n plugins: [dyadComponentTagger(), react()],\n resolve: {\n alias: {\n \"@\": path.resolve(__dirname, \"./src\"),\n },\n },\n}));\n", + "force": false + } + ], + "enable_lazy_edits": true, + "enable_smart_files_context": true + } + }, + "headers": { + "authorization": "Bearer testdyadkey" + } +} \ No newline at end of file diff --git a/e2e-tests/snapshots/engine.spec.ts_send-message-to-engine-1.txt b/e2e-tests/snapshots/engine.spec.ts_send-message-to-engine-1.txt index aa1e4ce..40ab6a9 100644 --- a/e2e-tests/snapshots/engine.spec.ts_send-message-to-engine-1.txt +++ b/e2e-tests/snapshots/engine.spec.ts_send-message-to-engine-1.txt @@ -14,6 +14,10 @@ } ], "stream": true, + "thinking": { + "type": "enabled", + "include_thoughts": true + }, "dyad_options": { "files": [ { diff --git a/e2e-tests/snapshots/engine.spec.ts_send-message-to-gateway-1.txt b/e2e-tests/snapshots/gateway.spec.ts_claude-4-sonnet-1.txt similarity index 99% rename from e2e-tests/snapshots/engine.spec.ts_send-message-to-gateway-1.txt rename to e2e-tests/snapshots/gateway.spec.ts_claude-4-sonnet-1.txt index 343e495..37fd4b7 100644 --- a/e2e-tests/snapshots/engine.spec.ts_send-message-to-gateway-1.txt +++ b/e2e-tests/snapshots/gateway.spec.ts_claude-4-sonnet-1.txt @@ -1,7 +1,7 @@ { "body": { - "model": "gemini/gemini-2.5-flash-preview-05-20", - "max_tokens": 65535, + "model": "anthropic/claude-sonnet-4-20250514", + "max_tokens": 16000, "temperature": 0, "messages": [ { diff --git a/e2e-tests/snapshots/engine.spec.ts_send-message-to-gateway-1.aria.yml b/e2e-tests/snapshots/gateway.spec.ts_gemini-2-5-flash-1.aria.yml similarity index 100% rename from e2e-tests/snapshots/engine.spec.ts_send-message-to-gateway-1.aria.yml rename to e2e-tests/snapshots/gateway.spec.ts_gemini-2-5-flash-1.aria.yml diff --git a/e2e-tests/snapshots/gateway.spec.ts_gemini-2-5-flash-1.txt b/e2e-tests/snapshots/gateway.spec.ts_gemini-2-5-flash-1.txt new file mode 100644 index 0000000..5bcff44 --- /dev/null +++ b/e2e-tests/snapshots/gateway.spec.ts_gemini-2-5-flash-1.txt @@ -0,0 +1,33 @@ +{ + "body": { + "model": "gemini/gemini-2.5-flash-preview-05-20", + "max_tokens": 65535, + "temperature": 0, + "thinking": { + "type": "enabled", + "include_thoughts": true + }, + "messages": [ + { + "role": "system", + "content": "[[SYSTEM_MESSAGE]]" + }, + { + "role": "user", + "content": "This is my codebase. \n# Tech Stack\n\n- You are building a React application.\n- Use TypeScript.\n- Use React Router. KEEP the routes in src/App.tsx\n- Always put source code in the src folder.\n- Put pages into src/pages/\n- Put components into src/components/\n- The main page (default page) is src/pages/Index.tsx\n- UPDATE the main page to include the new components. OTHERWISE, the user can NOT see any components!\n- ALWAYS try to use the shadcn/ui library.\n- Tailwind CSS: always use Tailwind CSS for styling components. Utilize Tailwind classes extensively for layout, spacing, colors, and other design aspects.\n\nAvailable packages and libraries:\n\n- The lucide-react package is installed for icons.\n- You ALREADY have ALL the shadcn/ui components and their dependencies installed. So you don't need to install them again.\n- You have ALL the necessary Radix UI components installed.\n- Use prebuilt components from the shadcn/ui library after importing them. Note that these files shouldn't be edited, so make new components if you need to change them.\n\n\n\n\n// Contents omitted for brevity\n\n\n\n\n\n \n \n \n dyad-generated-app\n \n\n \n
\n \n \n\n\n
\n\n\n{\n \"dependencies\": {\n \"@hookform/resolvers\": \"^3.9.0\",\n \"@radix-ui/react-accordion\": \"^1.2.0\",\n \"@radix-ui/react-alert-dialog\": \"^1.1.1\",\n \"@radix-ui/react-aspect-ratio\": \"^1.1.0\",\n \"@radix-ui/react-avatar\": \"^1.1.0\",\n \"@radix-ui/react-checkbox\": \"^1.1.1\",\n \"@radix-ui/react-collapsible\": \"^1.1.0\",\n \"@radix-ui/react-context-menu\": \"^2.2.1\",\n \"@radix-ui/react-dialog\": \"^1.1.2\",\n \"@radix-ui/react-dropdown-menu\": \"^2.1.1\",\n \"@radix-ui/react-hover-card\": \"^1.1.1\",\n \"@radix-ui/react-label\": \"^2.1.0\",\n \"@radix-ui/react-menubar\": \"^1.1.1\",\n \"@radix-ui/react-navigation-menu\": \"^1.2.0\",\n \"@radix-ui/react-popover\": \"^1.1.1\",\n \"@radix-ui/react-progress\": \"^1.1.0\",\n \"@radix-ui/react-radio-group\": \"^1.2.0\",\n \"@radix-ui/react-scroll-area\": \"^1.1.0\",\n \"@radix-ui/react-select\": \"^2.1.1\",\n \"@radix-ui/react-separator\": \"^1.1.0\",\n \"@radix-ui/react-slider\": \"^1.2.0\",\n \"@radix-ui/react-slot\": \"^1.1.0\",\n \"@radix-ui/react-switch\": \"^1.1.0\",\n \"@radix-ui/react-tabs\": \"^1.1.0\",\n \"@radix-ui/react-toast\": \"^1.2.1\",\n \"@radix-ui/react-toggle\": \"^1.1.0\",\n \"@radix-ui/react-toggle-group\": \"^1.1.0\",\n \"@radix-ui/react-tooltip\": \"^1.1.4\",\n \"@tanstack/react-query\": \"^5.56.2\",\n \"class-variance-authority\": \"^0.7.1\",\n \"clsx\": \"^2.1.1\",\n \"cmdk\": \"^1.0.0\",\n \"date-fns\": \"^3.6.0\",\n \"embla-carousel-react\": \"^8.3.0\",\n \"input-otp\": \"^1.2.4\",\n \"lucide-react\": \"^0.462.0\",\n \"next-themes\": \"^0.3.0\",\n \"react\": \"^18.3.1\",\n \"react-day-picker\": \"^8.10.1\",\n \"react-dom\": \"^18.3.1\",\n \"react-hook-form\": \"^7.53.0\",\n \"react-resizable-panels\": \"^2.1.3\",\n \"react-router-dom\": \"^6.26.2\",\n \"recharts\": \"^2.12.7\",\n \"sonner\": \"^1.5.0\",\n \"tailwind-merge\": \"^2.5.2\",\n \"tailwindcss-animate\": \"^1.0.7\",\n \"vaul\": \"^0.9.3\",\n \"zod\": \"^3.23.8\"\n },\n \"devDependencies\": {\n \"@dyad-sh/react-vite-component-tagger\": \"^0.8.0\",\n \"@eslint/js\": \"^9.9.0\",\n \"@tailwindcss/typography\": \"^0.5.15\",\n \"@types/node\": \"^22.5.5\",\n \"@types/react\": \"^18.3.3\",\n \"@types/react-dom\": \"^18.3.0\",\n \"@vitejs/plugin-react-swc\": \"^3.9.0\",\n \"autoprefixer\": \"^10.4.20\",\n \"eslint\": \"^9.9.0\",\n \"eslint-plugin-react-hooks\": \"^5.1.0-rc.0\",\n \"eslint-plugin-react-refresh\": \"^0.4.9\",\n \"globals\": \"^15.9.0\",\n \"postcss\": \"^8.4.47\",\n \"tailwindcss\": \"^3.4.11\",\n \"typescript\": \"^5.5.3\",\n \"typescript-eslint\": \"^8.0.1\",\n \"vite\": \"^6.3.4\"\n }\n}\n\n\n\nexport default {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};\n\n\n\n\n# Welcome to your Dyad app\n\n\n\n\n#root {\n max-width: 1280px;\n margin: 0 auto;\n padding: 2rem;\n text-align: center;\n}\n\n.logo {\n height: 6em;\n padding: 1.5em;\n will-change: filter;\n transition: filter 300ms;\n}\n.logo:hover {\n filter: drop-shadow(0 0 2em #646cffaa);\n}\n.logo.react:hover {\n filter: drop-shadow(0 0 2em #61dafbaa);\n}\n\n@keyframes logo-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n}\n\n@media (prefers-reduced-motion: no-preference) {\n a:nth-of-type(2) .logo {\n animation: logo-spin infinite 20s linear;\n }\n}\n\n.card {\n padding: 2em;\n}\n\n.read-the-docs {\n color: #888;\n}\n\n\n\n\nimport { Toaster } from \"@/components/ui/toaster\";\nimport { Toaster as Sonner } from \"@/components/ui/sonner\";\nimport { TooltipProvider } from \"@/components/ui/tooltip\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { BrowserRouter, Routes, Route } from \"react-router-dom\";\nimport Index from \"./pages/Index\";\nimport NotFound from \"./pages/NotFound\";\n\nconst queryClient = new QueryClient();\n\nconst App = () => (\n \n \n \n \n \n \n } />\n {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL \"*\" ROUTE */}\n } />\n \n \n \n \n);\n\nexport default App;\n\n\n\n\nexport const MadeWithDyad = () => {\n return (\n
\n \n Made with Dyad\n \n
\n );\n};\n\n
\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n :root {\n --background: 0 0% 100%;\n --foreground: 222.2 84% 4.9%;\n\n --card: 0 0% 100%;\n --card-foreground: 222.2 84% 4.9%;\n\n --popover: 0 0% 100%;\n --popover-foreground: 222.2 84% 4.9%;\n\n --primary: 222.2 47.4% 11.2%;\n --primary-foreground: 210 40% 98%;\n\n --secondary: 210 40% 96.1%;\n --secondary-foreground: 222.2 47.4% 11.2%;\n\n --muted: 210 40% 96.1%;\n --muted-foreground: 215.4 16.3% 46.9%;\n\n --accent: 210 40% 96.1%;\n --accent-foreground: 222.2 47.4% 11.2%;\n\n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 210 40% 98%;\n\n --border: 214.3 31.8% 91.4%;\n --input: 214.3 31.8% 91.4%;\n --ring: 222.2 84% 4.9%;\n\n --radius: 0.5rem;\n\n --sidebar-background: 0 0% 98%;\n\n --sidebar-foreground: 240 5.3% 26.1%;\n\n --sidebar-primary: 240 5.9% 10%;\n\n --sidebar-primary-foreground: 0 0% 98%;\n\n --sidebar-accent: 240 4.8% 95.9%;\n\n --sidebar-accent-foreground: 240 5.9% 10%;\n\n --sidebar-border: 220 13% 91%;\n\n --sidebar-ring: 217.2 91.2% 59.8%;\n }\n\n .dark {\n --background: 222.2 84% 4.9%;\n --foreground: 210 40% 98%;\n\n --card: 222.2 84% 4.9%;\n --card-foreground: 210 40% 98%;\n\n --popover: 222.2 84% 4.9%;\n --popover-foreground: 210 40% 98%;\n\n --primary: 210 40% 98%;\n --primary-foreground: 222.2 47.4% 11.2%;\n\n --secondary: 217.2 32.6% 17.5%;\n --secondary-foreground: 210 40% 98%;\n\n --muted: 217.2 32.6% 17.5%;\n --muted-foreground: 215 20.2% 65.1%;\n\n --accent: 217.2 32.6% 17.5%;\n --accent-foreground: 210 40% 98%;\n\n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 210 40% 98%;\n\n --border: 217.2 32.6% 17.5%;\n --input: 217.2 32.6% 17.5%;\n --ring: 212.7 26.8% 83.9%;\n --sidebar-background: 240 5.9% 10%;\n --sidebar-foreground: 240 4.8% 95.9%;\n --sidebar-primary: 224.3 76.3% 48%;\n --sidebar-primary-foreground: 0 0% 100%;\n --sidebar-accent: 240 3.7% 15.9%;\n --sidebar-accent-foreground: 240 4.8% 95.9%;\n --sidebar-border: 240 3.7% 15.9%;\n --sidebar-ring: 217.2 91.2% 59.8%;\n }\n}\n\n@layer base {\n * {\n @apply border-border;\n }\n\n body {\n @apply bg-background text-foreground;\n }\n}\n\n\n\n\nimport * as React from \"react\";\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n const [isMobile, setIsMobile] = React.useState(\n undefined,\n );\n\n React.useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n const onChange = () => {\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n };\n mql.addEventListener(\"change\", onChange);\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n return () => mql.removeEventListener(\"change\", onChange);\n }, []);\n\n return !!isMobile;\n}\n\n\n\n\nimport * as React from \"react\";\n\nimport type { ToastActionElement, ToastProps } from \"@/components/ui/toast\";\n\nconst TOAST_LIMIT = 1;\nconst TOAST_REMOVE_DELAY = 1000000;\n\ntype ToasterToast = ToastProps & {\n id: string;\n title?: React.ReactNode;\n description?: React.ReactNode;\n action?: ToastActionElement;\n};\n\nconst _actionTypes = {\n ADD_TOAST: \"ADD_TOAST\",\n UPDATE_TOAST: \"UPDATE_TOAST\",\n DISMISS_TOAST: \"DISMISS_TOAST\",\n REMOVE_TOAST: \"REMOVE_TOAST\",\n} as const;\n\nlet count = 0;\n\nfunction genId() {\n count = (count + 1) % Number.MAX_SAFE_INTEGER;\n return count.toString();\n}\n\ntype ActionType = typeof _actionTypes;\n\ntype Action =\n | {\n type: ActionType[\"ADD_TOAST\"];\n toast: ToasterToast;\n }\n | {\n type: ActionType[\"UPDATE_TOAST\"];\n toast: Partial;\n }\n | {\n type: ActionType[\"DISMISS_TOAST\"];\n toastId?: ToasterToast[\"id\"];\n }\n | {\n type: ActionType[\"REMOVE_TOAST\"];\n toastId?: ToasterToast[\"id\"];\n };\n\ninterface State {\n toasts: ToasterToast[];\n}\n\nconst toastTimeouts = new Map>();\n\nconst addToRemoveQueue = (toastId: string) => {\n if (toastTimeouts.has(toastId)) {\n return;\n }\n\n const timeout = setTimeout(() => {\n toastTimeouts.delete(toastId);\n dispatch({\n type: \"REMOVE_TOAST\",\n toastId: toastId,\n });\n }, TOAST_REMOVE_DELAY);\n\n toastTimeouts.set(toastId, timeout);\n};\n\nexport const reducer = (state: State, action: Action): State => {\n switch (action.type) {\n case \"ADD_TOAST\":\n return {\n ...state,\n toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),\n };\n\n case \"UPDATE_TOAST\":\n return {\n ...state,\n toasts: state.toasts.map((t) =>\n t.id === action.toast.id ? { ...t, ...action.toast } : t,\n ),\n };\n\n case \"DISMISS_TOAST\": {\n const { toastId } = action;\n\n // ! Side effects ! - This could be extracted into a dismissToast() action,\n // but I'll keep it here for simplicity\n if (toastId) {\n addToRemoveQueue(toastId);\n } else {\n state.toasts.forEach((toast) => {\n addToRemoveQueue(toast.id);\n });\n }\n\n return {\n ...state,\n toasts: state.toasts.map((t) =>\n t.id === toastId || toastId === undefined\n ? {\n ...t,\n open: false,\n }\n : t,\n ),\n };\n }\n case \"REMOVE_TOAST\":\n if (action.toastId === undefined) {\n return {\n ...state,\n toasts: [],\n };\n }\n return {\n ...state,\n toasts: state.toasts.filter((t) => t.id !== action.toastId),\n };\n }\n};\n\nconst listeners: Array<(state: State) => void> = [];\n\nlet memoryState: State = { toasts: [] };\n\nfunction dispatch(action: Action) {\n memoryState = reducer(memoryState, action);\n listeners.forEach((listener) => {\n listener(memoryState);\n });\n}\n\ntype Toast = Omit;\n\nfunction toast({ ...props }: Toast) {\n const id = genId();\n\n const update = (props: ToasterToast) =>\n dispatch({\n type: \"UPDATE_TOAST\",\n toast: { ...props, id },\n });\n const dismiss = () => dispatch({ type: \"DISMISS_TOAST\", toastId: id });\n\n dispatch({\n type: \"ADD_TOAST\",\n toast: {\n ...props,\n id,\n open: true,\n onOpenChange: (open) => {\n if (!open) dismiss();\n },\n },\n });\n\n return {\n id: id,\n dismiss,\n update,\n };\n}\n\nfunction useToast() {\n const [state, setState] = React.useState(memoryState);\n\n React.useEffect(() => {\n listeners.push(setState);\n return () => {\n const index = listeners.indexOf(setState);\n if (index > -1) {\n listeners.splice(index, 1);\n }\n };\n }, [state]);\n\n return {\n ...state,\n toast,\n dismiss: (toastId?: string) => dispatch({ type: \"DISMISS_TOAST\", toastId }),\n };\n}\n\nexport { useToast, toast };\n\n\n\n\nimport { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n\n\n\nimport { createRoot } from \"react-dom/client\";\nimport App from \"./App.tsx\";\nimport \"./globals.css\";\n\ncreateRoot(document.getElementById(\"root\")!).render();\n\n\n\n\n// Update this page (the content is just a fallback if you fail to update the page)\n\nimport { MadeWithDyad } from \"@/components/made-with-dyad\";\n\nconst Index = () => {\n return (\n
\n
\n

Welcome to Your Blank App

\n

\n Start building your amazing project here!\n

\n
\n \n
\n );\n};\n\nexport default Index;\n\n
\n\n\nimport { useLocation } from \"react-router-dom\";\nimport { useEffect } from \"react\";\n\nconst NotFound = () => {\n const location = useLocation();\n\n useEffect(() => {\n console.error(\n \"404 Error: User attempted to access non-existent route:\",\n location.pathname,\n );\n }, [location.pathname]);\n\n return (\n
\n
\n

404

\n

Oops! Page not found

\n \n Return to Home\n \n
\n
\n );\n};\n\nexport default NotFound;\n\n
\n\n\nimport { toast } from \"sonner\";\n\nexport const showSuccess = (message: string) => {\n toast.success(message);\n};\n\nexport const showError = (message: string) => {\n toast.error(message);\n};\n\nexport const showLoading = (message: string) => {\n return toast.loading(message);\n};\n\nexport const dismissToast = (toastId: string) => {\n toast.dismiss(toastId);\n};\n\n\n\n\n/// \n\n\n\n\nimport type { Config } from \"tailwindcss\";\n\nexport default {\n darkMode: [\"class\"],\n content: [\n \"./pages/**/*.{ts,tsx}\",\n \"./components/**/*.{ts,tsx}\",\n \"./app/**/*.{ts,tsx}\",\n \"./src/**/*.{ts,tsx}\",\n ],\n prefix: \"\",\n theme: {\n container: {\n center: true,\n padding: \"2rem\",\n screens: {\n \"2xl\": \"1400px\",\n },\n },\n extend: {\n colors: {\n border: \"hsl(var(--border))\",\n input: \"hsl(var(--input))\",\n ring: \"hsl(var(--ring))\",\n background: \"hsl(var(--background))\",\n foreground: \"hsl(var(--foreground))\",\n primary: {\n DEFAULT: \"hsl(var(--primary))\",\n foreground: \"hsl(var(--primary-foreground))\",\n },\n secondary: {\n DEFAULT: \"hsl(var(--secondary))\",\n foreground: \"hsl(var(--secondary-foreground))\",\n },\n destructive: {\n DEFAULT: \"hsl(var(--destructive))\",\n foreground: \"hsl(var(--destructive-foreground))\",\n },\n muted: {\n DEFAULT: \"hsl(var(--muted))\",\n foreground: \"hsl(var(--muted-foreground))\",\n },\n accent: {\n DEFAULT: \"hsl(var(--accent))\",\n foreground: \"hsl(var(--accent-foreground))\",\n },\n popover: {\n DEFAULT: \"hsl(var(--popover))\",\n foreground: \"hsl(var(--popover-foreground))\",\n },\n card: {\n DEFAULT: \"hsl(var(--card))\",\n foreground: \"hsl(var(--card-foreground))\",\n },\n sidebar: {\n DEFAULT: \"hsl(var(--sidebar-background))\",\n foreground: \"hsl(var(--sidebar-foreground))\",\n primary: \"hsl(var(--sidebar-primary))\",\n \"primary-foreground\": \"hsl(var(--sidebar-primary-foreground))\",\n accent: \"hsl(var(--sidebar-accent))\",\n \"accent-foreground\": \"hsl(var(--sidebar-accent-foreground))\",\n border: \"hsl(var(--sidebar-border))\",\n ring: \"hsl(var(--sidebar-ring))\",\n },\n },\n borderRadius: {\n lg: \"var(--radius)\",\n md: \"calc(var(--radius) - 2px)\",\n sm: \"calc(var(--radius) - 4px)\",\n },\n keyframes: {\n \"accordion-down\": {\n from: {\n height: \"0\",\n },\n to: {\n height: \"var(--radix-accordion-content-height)\",\n },\n },\n \"accordion-up\": {\n from: {\n height: \"var(--radix-accordion-content-height)\",\n },\n to: {\n height: \"0\",\n },\n },\n },\n animation: {\n \"accordion-down\": \"accordion-down 0.2s ease-out\",\n \"accordion-up\": \"accordion-up 0.2s ease-out\",\n },\n },\n },\n plugins: [require(\"tailwindcss-animate\")],\n} satisfies Config;\n\n\n\n\nimport { defineConfig } from \"vite\";\nimport dyadComponentTagger from \"@dyad-sh/react-vite-component-tagger\";\nimport react from \"@vitejs/plugin-react-swc\";\nimport path from \"path\";\n\nexport default defineConfig(() => ({\n server: {\n host: \"::\",\n port: 8080,\n },\n plugins: [dyadComponentTagger(), react()],\n resolve: {\n alias: {\n \"@\": path.resolve(__dirname, \"./src\"),\n },\n },\n}));\n\n\n\n" + }, + { + "role": "assistant", + "content": "OK, got it. I'm ready to help" + }, + { + "role": "user", + "content": "[dump] tc=gateway-simple" + } + ], + "stream": true + }, + "headers": { + "authorization": "Bearer testdyadkey" + } +} \ No newline at end of file diff --git a/e2e-tests/snapshots/gateway.spec.ts_send-message-to-gateway---claude-4-sonnet-1.txt b/e2e-tests/snapshots/gateway.spec.ts_send-message-to-gateway---claude-4-sonnet-1.txt new file mode 100644 index 0000000..37fd4b7 --- /dev/null +++ b/e2e-tests/snapshots/gateway.spec.ts_send-message-to-gateway---claude-4-sonnet-1.txt @@ -0,0 +1,29 @@ +{ + "body": { + "model": "anthropic/claude-sonnet-4-20250514", + "max_tokens": 16000, + "temperature": 0, + "messages": [ + { + "role": "system", + "content": "[[SYSTEM_MESSAGE]]" + }, + { + "role": "user", + "content": "This is my codebase. \n# Tech Stack\n\n- You are building a React application.\n- Use TypeScript.\n- Use React Router. KEEP the routes in src/App.tsx\n- Always put source code in the src folder.\n- Put pages into src/pages/\n- Put components into src/components/\n- The main page (default page) is src/pages/Index.tsx\n- UPDATE the main page to include the new components. OTHERWISE, the user can NOT see any components!\n- ALWAYS try to use the shadcn/ui library.\n- Tailwind CSS: always use Tailwind CSS for styling components. Utilize Tailwind classes extensively for layout, spacing, colors, and other design aspects.\n\nAvailable packages and libraries:\n\n- The lucide-react package is installed for icons.\n- You ALREADY have ALL the shadcn/ui components and their dependencies installed. So you don't need to install them again.\n- You have ALL the necessary Radix UI components installed.\n- Use prebuilt components from the shadcn/ui library after importing them. Note that these files shouldn't be edited, so make new components if you need to change them.\n\n\n\n\n// Contents omitted for brevity\n\n\n\n\n\n \n \n \n dyad-generated-app\n \n\n \n
\n \n \n\n\n
\n\n\n{\n \"dependencies\": {\n \"@hookform/resolvers\": \"^3.9.0\",\n \"@radix-ui/react-accordion\": \"^1.2.0\",\n \"@radix-ui/react-alert-dialog\": \"^1.1.1\",\n \"@radix-ui/react-aspect-ratio\": \"^1.1.0\",\n \"@radix-ui/react-avatar\": \"^1.1.0\",\n \"@radix-ui/react-checkbox\": \"^1.1.1\",\n \"@radix-ui/react-collapsible\": \"^1.1.0\",\n \"@radix-ui/react-context-menu\": \"^2.2.1\",\n \"@radix-ui/react-dialog\": \"^1.1.2\",\n \"@radix-ui/react-dropdown-menu\": \"^2.1.1\",\n \"@radix-ui/react-hover-card\": \"^1.1.1\",\n \"@radix-ui/react-label\": \"^2.1.0\",\n \"@radix-ui/react-menubar\": \"^1.1.1\",\n \"@radix-ui/react-navigation-menu\": \"^1.2.0\",\n \"@radix-ui/react-popover\": \"^1.1.1\",\n \"@radix-ui/react-progress\": \"^1.1.0\",\n \"@radix-ui/react-radio-group\": \"^1.2.0\",\n \"@radix-ui/react-scroll-area\": \"^1.1.0\",\n \"@radix-ui/react-select\": \"^2.1.1\",\n \"@radix-ui/react-separator\": \"^1.1.0\",\n \"@radix-ui/react-slider\": \"^1.2.0\",\n \"@radix-ui/react-slot\": \"^1.1.0\",\n \"@radix-ui/react-switch\": \"^1.1.0\",\n \"@radix-ui/react-tabs\": \"^1.1.0\",\n \"@radix-ui/react-toast\": \"^1.2.1\",\n \"@radix-ui/react-toggle\": \"^1.1.0\",\n \"@radix-ui/react-toggle-group\": \"^1.1.0\",\n \"@radix-ui/react-tooltip\": \"^1.1.4\",\n \"@tanstack/react-query\": \"^5.56.2\",\n \"class-variance-authority\": \"^0.7.1\",\n \"clsx\": \"^2.1.1\",\n \"cmdk\": \"^1.0.0\",\n \"date-fns\": \"^3.6.0\",\n \"embla-carousel-react\": \"^8.3.0\",\n \"input-otp\": \"^1.2.4\",\n \"lucide-react\": \"^0.462.0\",\n \"next-themes\": \"^0.3.0\",\n \"react\": \"^18.3.1\",\n \"react-day-picker\": \"^8.10.1\",\n \"react-dom\": \"^18.3.1\",\n \"react-hook-form\": \"^7.53.0\",\n \"react-resizable-panels\": \"^2.1.3\",\n \"react-router-dom\": \"^6.26.2\",\n \"recharts\": \"^2.12.7\",\n \"sonner\": \"^1.5.0\",\n \"tailwind-merge\": \"^2.5.2\",\n \"tailwindcss-animate\": \"^1.0.7\",\n \"vaul\": \"^0.9.3\",\n \"zod\": \"^3.23.8\"\n },\n \"devDependencies\": {\n \"@dyad-sh/react-vite-component-tagger\": \"^0.8.0\",\n \"@eslint/js\": \"^9.9.0\",\n \"@tailwindcss/typography\": \"^0.5.15\",\n \"@types/node\": \"^22.5.5\",\n \"@types/react\": \"^18.3.3\",\n \"@types/react-dom\": \"^18.3.0\",\n \"@vitejs/plugin-react-swc\": \"^3.9.0\",\n \"autoprefixer\": \"^10.4.20\",\n \"eslint\": \"^9.9.0\",\n \"eslint-plugin-react-hooks\": \"^5.1.0-rc.0\",\n \"eslint-plugin-react-refresh\": \"^0.4.9\",\n \"globals\": \"^15.9.0\",\n \"postcss\": \"^8.4.47\",\n \"tailwindcss\": \"^3.4.11\",\n \"typescript\": \"^5.5.3\",\n \"typescript-eslint\": \"^8.0.1\",\n \"vite\": \"^6.3.4\"\n }\n}\n\n\n\nexport default {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};\n\n\n\n\n# Welcome to your Dyad app\n\n\n\n\n#root {\n max-width: 1280px;\n margin: 0 auto;\n padding: 2rem;\n text-align: center;\n}\n\n.logo {\n height: 6em;\n padding: 1.5em;\n will-change: filter;\n transition: filter 300ms;\n}\n.logo:hover {\n filter: drop-shadow(0 0 2em #646cffaa);\n}\n.logo.react:hover {\n filter: drop-shadow(0 0 2em #61dafbaa);\n}\n\n@keyframes logo-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n}\n\n@media (prefers-reduced-motion: no-preference) {\n a:nth-of-type(2) .logo {\n animation: logo-spin infinite 20s linear;\n }\n}\n\n.card {\n padding: 2em;\n}\n\n.read-the-docs {\n color: #888;\n}\n\n\n\n\nimport { Toaster } from \"@/components/ui/toaster\";\nimport { Toaster as Sonner } from \"@/components/ui/sonner\";\nimport { TooltipProvider } from \"@/components/ui/tooltip\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { BrowserRouter, Routes, Route } from \"react-router-dom\";\nimport Index from \"./pages/Index\";\nimport NotFound from \"./pages/NotFound\";\n\nconst queryClient = new QueryClient();\n\nconst App = () => (\n \n \n \n \n \n \n } />\n {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL \"*\" ROUTE */}\n } />\n \n \n \n \n);\n\nexport default App;\n\n\n\n\nexport const MadeWithDyad = () => {\n return (\n
\n \n Made with Dyad\n \n
\n );\n};\n\n
\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n// Contents omitted for brevity\n\n\n\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n :root {\n --background: 0 0% 100%;\n --foreground: 222.2 84% 4.9%;\n\n --card: 0 0% 100%;\n --card-foreground: 222.2 84% 4.9%;\n\n --popover: 0 0% 100%;\n --popover-foreground: 222.2 84% 4.9%;\n\n --primary: 222.2 47.4% 11.2%;\n --primary-foreground: 210 40% 98%;\n\n --secondary: 210 40% 96.1%;\n --secondary-foreground: 222.2 47.4% 11.2%;\n\n --muted: 210 40% 96.1%;\n --muted-foreground: 215.4 16.3% 46.9%;\n\n --accent: 210 40% 96.1%;\n --accent-foreground: 222.2 47.4% 11.2%;\n\n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 210 40% 98%;\n\n --border: 214.3 31.8% 91.4%;\n --input: 214.3 31.8% 91.4%;\n --ring: 222.2 84% 4.9%;\n\n --radius: 0.5rem;\n\n --sidebar-background: 0 0% 98%;\n\n --sidebar-foreground: 240 5.3% 26.1%;\n\n --sidebar-primary: 240 5.9% 10%;\n\n --sidebar-primary-foreground: 0 0% 98%;\n\n --sidebar-accent: 240 4.8% 95.9%;\n\n --sidebar-accent-foreground: 240 5.9% 10%;\n\n --sidebar-border: 220 13% 91%;\n\n --sidebar-ring: 217.2 91.2% 59.8%;\n }\n\n .dark {\n --background: 222.2 84% 4.9%;\n --foreground: 210 40% 98%;\n\n --card: 222.2 84% 4.9%;\n --card-foreground: 210 40% 98%;\n\n --popover: 222.2 84% 4.9%;\n --popover-foreground: 210 40% 98%;\n\n --primary: 210 40% 98%;\n --primary-foreground: 222.2 47.4% 11.2%;\n\n --secondary: 217.2 32.6% 17.5%;\n --secondary-foreground: 210 40% 98%;\n\n --muted: 217.2 32.6% 17.5%;\n --muted-foreground: 215 20.2% 65.1%;\n\n --accent: 217.2 32.6% 17.5%;\n --accent-foreground: 210 40% 98%;\n\n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 210 40% 98%;\n\n --border: 217.2 32.6% 17.5%;\n --input: 217.2 32.6% 17.5%;\n --ring: 212.7 26.8% 83.9%;\n --sidebar-background: 240 5.9% 10%;\n --sidebar-foreground: 240 4.8% 95.9%;\n --sidebar-primary: 224.3 76.3% 48%;\n --sidebar-primary-foreground: 0 0% 100%;\n --sidebar-accent: 240 3.7% 15.9%;\n --sidebar-accent-foreground: 240 4.8% 95.9%;\n --sidebar-border: 240 3.7% 15.9%;\n --sidebar-ring: 217.2 91.2% 59.8%;\n }\n}\n\n@layer base {\n * {\n @apply border-border;\n }\n\n body {\n @apply bg-background text-foreground;\n }\n}\n\n\n\n\nimport * as React from \"react\";\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n const [isMobile, setIsMobile] = React.useState(\n undefined,\n );\n\n React.useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n const onChange = () => {\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n };\n mql.addEventListener(\"change\", onChange);\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n return () => mql.removeEventListener(\"change\", onChange);\n }, []);\n\n return !!isMobile;\n}\n\n\n\n\nimport * as React from \"react\";\n\nimport type { ToastActionElement, ToastProps } from \"@/components/ui/toast\";\n\nconst TOAST_LIMIT = 1;\nconst TOAST_REMOVE_DELAY = 1000000;\n\ntype ToasterToast = ToastProps & {\n id: string;\n title?: React.ReactNode;\n description?: React.ReactNode;\n action?: ToastActionElement;\n};\n\nconst _actionTypes = {\n ADD_TOAST: \"ADD_TOAST\",\n UPDATE_TOAST: \"UPDATE_TOAST\",\n DISMISS_TOAST: \"DISMISS_TOAST\",\n REMOVE_TOAST: \"REMOVE_TOAST\",\n} as const;\n\nlet count = 0;\n\nfunction genId() {\n count = (count + 1) % Number.MAX_SAFE_INTEGER;\n return count.toString();\n}\n\ntype ActionType = typeof _actionTypes;\n\ntype Action =\n | {\n type: ActionType[\"ADD_TOAST\"];\n toast: ToasterToast;\n }\n | {\n type: ActionType[\"UPDATE_TOAST\"];\n toast: Partial;\n }\n | {\n type: ActionType[\"DISMISS_TOAST\"];\n toastId?: ToasterToast[\"id\"];\n }\n | {\n type: ActionType[\"REMOVE_TOAST\"];\n toastId?: ToasterToast[\"id\"];\n };\n\ninterface State {\n toasts: ToasterToast[];\n}\n\nconst toastTimeouts = new Map>();\n\nconst addToRemoveQueue = (toastId: string) => {\n if (toastTimeouts.has(toastId)) {\n return;\n }\n\n const timeout = setTimeout(() => {\n toastTimeouts.delete(toastId);\n dispatch({\n type: \"REMOVE_TOAST\",\n toastId: toastId,\n });\n }, TOAST_REMOVE_DELAY);\n\n toastTimeouts.set(toastId, timeout);\n};\n\nexport const reducer = (state: State, action: Action): State => {\n switch (action.type) {\n case \"ADD_TOAST\":\n return {\n ...state,\n toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),\n };\n\n case \"UPDATE_TOAST\":\n return {\n ...state,\n toasts: state.toasts.map((t) =>\n t.id === action.toast.id ? { ...t, ...action.toast } : t,\n ),\n };\n\n case \"DISMISS_TOAST\": {\n const { toastId } = action;\n\n // ! Side effects ! - This could be extracted into a dismissToast() action,\n // but I'll keep it here for simplicity\n if (toastId) {\n addToRemoveQueue(toastId);\n } else {\n state.toasts.forEach((toast) => {\n addToRemoveQueue(toast.id);\n });\n }\n\n return {\n ...state,\n toasts: state.toasts.map((t) =>\n t.id === toastId || toastId === undefined\n ? {\n ...t,\n open: false,\n }\n : t,\n ),\n };\n }\n case \"REMOVE_TOAST\":\n if (action.toastId === undefined) {\n return {\n ...state,\n toasts: [],\n };\n }\n return {\n ...state,\n toasts: state.toasts.filter((t) => t.id !== action.toastId),\n };\n }\n};\n\nconst listeners: Array<(state: State) => void> = [];\n\nlet memoryState: State = { toasts: [] };\n\nfunction dispatch(action: Action) {\n memoryState = reducer(memoryState, action);\n listeners.forEach((listener) => {\n listener(memoryState);\n });\n}\n\ntype Toast = Omit;\n\nfunction toast({ ...props }: Toast) {\n const id = genId();\n\n const update = (props: ToasterToast) =>\n dispatch({\n type: \"UPDATE_TOAST\",\n toast: { ...props, id },\n });\n const dismiss = () => dispatch({ type: \"DISMISS_TOAST\", toastId: id });\n\n dispatch({\n type: \"ADD_TOAST\",\n toast: {\n ...props,\n id,\n open: true,\n onOpenChange: (open) => {\n if (!open) dismiss();\n },\n },\n });\n\n return {\n id: id,\n dismiss,\n update,\n };\n}\n\nfunction useToast() {\n const [state, setState] = React.useState(memoryState);\n\n React.useEffect(() => {\n listeners.push(setState);\n return () => {\n const index = listeners.indexOf(setState);\n if (index > -1) {\n listeners.splice(index, 1);\n }\n };\n }, [state]);\n\n return {\n ...state,\n toast,\n dismiss: (toastId?: string) => dispatch({ type: \"DISMISS_TOAST\", toastId }),\n };\n}\n\nexport { useToast, toast };\n\n\n\n\nimport { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n\n\n\nimport { createRoot } from \"react-dom/client\";\nimport App from \"./App.tsx\";\nimport \"./globals.css\";\n\ncreateRoot(document.getElementById(\"root\")!).render();\n\n\n\n\n// Update this page (the content is just a fallback if you fail to update the page)\n\nimport { MadeWithDyad } from \"@/components/made-with-dyad\";\n\nconst Index = () => {\n return (\n
\n
\n

Welcome to Your Blank App

\n

\n Start building your amazing project here!\n

\n
\n \n
\n );\n};\n\nexport default Index;\n\n
\n\n\nimport { useLocation } from \"react-router-dom\";\nimport { useEffect } from \"react\";\n\nconst NotFound = () => {\n const location = useLocation();\n\n useEffect(() => {\n console.error(\n \"404 Error: User attempted to access non-existent route:\",\n location.pathname,\n );\n }, [location.pathname]);\n\n return (\n
\n
\n

404

\n

Oops! Page not found

\n \n Return to Home\n \n
\n
\n );\n};\n\nexport default NotFound;\n\n
\n\n\nimport { toast } from \"sonner\";\n\nexport const showSuccess = (message: string) => {\n toast.success(message);\n};\n\nexport const showError = (message: string) => {\n toast.error(message);\n};\n\nexport const showLoading = (message: string) => {\n return toast.loading(message);\n};\n\nexport const dismissToast = (toastId: string) => {\n toast.dismiss(toastId);\n};\n\n\n\n\n/// \n\n\n\n\nimport type { Config } from \"tailwindcss\";\n\nexport default {\n darkMode: [\"class\"],\n content: [\n \"./pages/**/*.{ts,tsx}\",\n \"./components/**/*.{ts,tsx}\",\n \"./app/**/*.{ts,tsx}\",\n \"./src/**/*.{ts,tsx}\",\n ],\n prefix: \"\",\n theme: {\n container: {\n center: true,\n padding: \"2rem\",\n screens: {\n \"2xl\": \"1400px\",\n },\n },\n extend: {\n colors: {\n border: \"hsl(var(--border))\",\n input: \"hsl(var(--input))\",\n ring: \"hsl(var(--ring))\",\n background: \"hsl(var(--background))\",\n foreground: \"hsl(var(--foreground))\",\n primary: {\n DEFAULT: \"hsl(var(--primary))\",\n foreground: \"hsl(var(--primary-foreground))\",\n },\n secondary: {\n DEFAULT: \"hsl(var(--secondary))\",\n foreground: \"hsl(var(--secondary-foreground))\",\n },\n destructive: {\n DEFAULT: \"hsl(var(--destructive))\",\n foreground: \"hsl(var(--destructive-foreground))\",\n },\n muted: {\n DEFAULT: \"hsl(var(--muted))\",\n foreground: \"hsl(var(--muted-foreground))\",\n },\n accent: {\n DEFAULT: \"hsl(var(--accent))\",\n foreground: \"hsl(var(--accent-foreground))\",\n },\n popover: {\n DEFAULT: \"hsl(var(--popover))\",\n foreground: \"hsl(var(--popover-foreground))\",\n },\n card: {\n DEFAULT: \"hsl(var(--card))\",\n foreground: \"hsl(var(--card-foreground))\",\n },\n sidebar: {\n DEFAULT: \"hsl(var(--sidebar-background))\",\n foreground: \"hsl(var(--sidebar-foreground))\",\n primary: \"hsl(var(--sidebar-primary))\",\n \"primary-foreground\": \"hsl(var(--sidebar-primary-foreground))\",\n accent: \"hsl(var(--sidebar-accent))\",\n \"accent-foreground\": \"hsl(var(--sidebar-accent-foreground))\",\n border: \"hsl(var(--sidebar-border))\",\n ring: \"hsl(var(--sidebar-ring))\",\n },\n },\n borderRadius: {\n lg: \"var(--radius)\",\n md: \"calc(var(--radius) - 2px)\",\n sm: \"calc(var(--radius) - 4px)\",\n },\n keyframes: {\n \"accordion-down\": {\n from: {\n height: \"0\",\n },\n to: {\n height: \"var(--radix-accordion-content-height)\",\n },\n },\n \"accordion-up\": {\n from: {\n height: \"var(--radix-accordion-content-height)\",\n },\n to: {\n height: \"0\",\n },\n },\n },\n animation: {\n \"accordion-down\": \"accordion-down 0.2s ease-out\",\n \"accordion-up\": \"accordion-up 0.2s ease-out\",\n },\n },\n },\n plugins: [require(\"tailwindcss-animate\")],\n} satisfies Config;\n\n\n\n\nimport { defineConfig } from \"vite\";\nimport dyadComponentTagger from \"@dyad-sh/react-vite-component-tagger\";\nimport react from \"@vitejs/plugin-react-swc\";\nimport path from \"path\";\n\nexport default defineConfig(() => ({\n server: {\n host: \"::\",\n port: 8080,\n },\n plugins: [dyadComponentTagger(), react()],\n resolve: {\n alias: {\n \"@\": path.resolve(__dirname, \"./src\"),\n },\n },\n}));\n\n\n\n" + }, + { + "role": "assistant", + "content": "OK, got it. I'm ready to help" + }, + { + "role": "user", + "content": "[dump] tc=gateway-simple" + } + ], + "stream": true + }, + "headers": { + "authorization": "Bearer testdyadkey" + } +} \ No newline at end of file diff --git a/e2e-tests/snapshots/import.spec.ts_import-app-2.aria.yml b/e2e-tests/snapshots/import.spec.ts_import-app-2.aria.yml index 49e3bc0..55c1c7b 100644 --- a/e2e-tests/snapshots/import.spec.ts_import-app-2.aria.yml +++ b/e2e-tests/snapshots/import.spec.ts_import-app-2.aria.yml @@ -1,12 +1,4 @@ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ -- 'button "Thinking `<dyad-write>`: I''ll think about the problem and write a bug report. <dyad-write> <dyad-write path=\"file1.txt\"> Fake dyad write </dyad-write>"': - - img - - img - - paragraph: - - code: "`<dyad-write>`" - - text: ": I'll think about the problem and write a bug report." - - paragraph: <dyad-write> - - paragraph: <dyad-write path="file1.txt"> Fake dyad write </dyad-write> - img - text: file1.txt - img diff --git a/e2e-tests/snapshots/import.spec.ts_import-app-with-AI-rules-1.txt b/e2e-tests/snapshots/import.spec.ts_import-app-with-AI-rules-1.txt index db1e496..de8f2e6 100644 --- a/e2e-tests/snapshots/import.spec.ts_import-app-with-AI-rules-1.txt +++ b/e2e-tests/snapshots/import.spec.ts_import-app-with-AI-rules-1.txt @@ -282,58 +282,6 @@ Do not hesitate to extensively use console logs to follow the flow of the code. DO NOT OVERENGINEER THE CODE. You take great pride in keeping things simple and elegant. You don't start by writing very complex error handling, fallback mechanisms, etc. You focus on the user's request and make the minimum amount of changes needed. DON'T DO MORE THAN WHAT THE USER ASKS FOR. - -# Thinking Process - -Before responding to user requests, ALWAYS use tags to carefully plan your approach. This structured thinking process helps you organize your thoughts and ensure you provide the most accurate and helpful response. Your thinking should: - -- Use **bullet points** to break down the steps -- **Bold key insights** and important considerations -- Follow a clear analytical framework - -Example of proper thinking structure for a debugging request: - - -• **Identify the specific UI/FE bug described by the user** - - "Form submission button doesn't work when clicked" - - User reports clicking the button has no effect - - This appears to be a **functional issue**, not just styling - -• **Examine relevant components in the codebase** - - Form component at `src/components/ContactForm.jsx` - - Button component at `src/components/Button.jsx` - - Form submission logic in `src/utils/formHandlers.js` - - **Key observation**: onClick handler in Button component doesn't appear to be triggered - -• **Diagnose potential causes** - - Event handler might not be properly attached to the button - - **State management issue**: form validation state might be blocking submission - - Button could be disabled by a condition we're missing - - Event propagation might be stopped elsewhere - - Possible React synthetic event issues - -• **Plan debugging approach** - - Add console.logs to track execution flow - - **Fix #1**: Ensure onClick prop is properly passed through Button component - - **Fix #2**: Check form validation state before submission - - **Fix #3**: Verify event handler is properly bound in the component - - Add error handling to catch and display submission issues - -• **Consider improvements beyond the fix** - - Add visual feedback when button is clicked (loading state) - - Implement better error handling for form submissions - - Add logging to help debug edge cases - - -After completing your thinking process, proceed with your response following the guidelines above. Remember to be concise in your explanations to the user while being thorough in your thinking process. - -This structured thinking ensures you: -1. Don't miss important aspects of the request -2. Consider all relevant factors before making changes -3. Deliver more accurate and helpful responses -4. Maintain a consistent approach to problem-solving - - [[beginning of AI_RULES.md]] There's already AI rules... [[end of AI_RULES.md]] diff --git a/e2e-tests/snapshots/lm_studio.spec.ts_send-message-to-LM-studio-1.aria.yml b/e2e-tests/snapshots/lm_studio.spec.ts_send-message-to-LM-studio-1.aria.yml index 777e2f1..6e76411 100644 --- a/e2e-tests/snapshots/lm_studio.spec.ts_send-message-to-LM-studio-1.aria.yml +++ b/e2e-tests/snapshots/lm_studio.spec.ts_send-message-to-LM-studio-1.aria.yml @@ -1,12 +1,4 @@ - paragraph: hi -- 'button "Thinking `<dyad-write>`: I''ll think about the problem and write a bug report. <dyad-write> <dyad-write path=\"file1.txt\"> Fake dyad write </dyad-write>"': - - img - - img - - paragraph: - - code: "`<dyad-write>`" - - text: ": I'll think about the problem and write a bug report." - - paragraph: <dyad-write> - - paragraph: <dyad-write path="file1.txt"> Fake dyad write </dyad-write> - img - text: file1.txt - img diff --git a/e2e-tests/snapshots/main.spec.ts_simple-message-to-custom-test-model-1.aria.yml b/e2e-tests/snapshots/main.spec.ts_simple-message-to-custom-test-model-1.aria.yml index 777e2f1..6e76411 100644 --- a/e2e-tests/snapshots/main.spec.ts_simple-message-to-custom-test-model-1.aria.yml +++ b/e2e-tests/snapshots/main.spec.ts_simple-message-to-custom-test-model-1.aria.yml @@ -1,12 +1,4 @@ - paragraph: hi -- 'button "Thinking `<dyad-write>`: I''ll think about the problem and write a bug report. <dyad-write> <dyad-write path=\"file1.txt\"> Fake dyad write </dyad-write>"': - - img - - img - - paragraph: - - code: "`<dyad-write>`" - - text: ": I'll think about the problem and write a bug report." - - paragraph: <dyad-write> - - paragraph: <dyad-write path="file1.txt"> Fake dyad write </dyad-write> - img - text: file1.txt - img diff --git a/e2e-tests/snapshots/select_component.spec.ts_select-component-1.txt b/e2e-tests/snapshots/select_component.spec.ts_select-component-1.txt index eba33c7..4a4e7a0 100644 --- a/e2e-tests/snapshots/select_component.spec.ts_select-component-1.txt +++ b/e2e-tests/snapshots/select_component.spec.ts_select-component-1.txt @@ -282,58 +282,6 @@ Do not hesitate to extensively use console logs to follow the flow of the code. DO NOT OVERENGINEER THE CODE. You take great pride in keeping things simple and elegant. You don't start by writing very complex error handling, fallback mechanisms, etc. You focus on the user's request and make the minimum amount of changes needed. DON'T DO MORE THAN WHAT THE USER ASKS FOR. - -# Thinking Process - -Before responding to user requests, ALWAYS use tags to carefully plan your approach. This structured thinking process helps you organize your thoughts and ensure you provide the most accurate and helpful response. Your thinking should: - -- Use **bullet points** to break down the steps -- **Bold key insights** and important considerations -- Follow a clear analytical framework - -Example of proper thinking structure for a debugging request: - - -• **Identify the specific UI/FE bug described by the user** - - "Form submission button doesn't work when clicked" - - User reports clicking the button has no effect - - This appears to be a **functional issue**, not just styling - -• **Examine relevant components in the codebase** - - Form component at `src/components/ContactForm.jsx` - - Button component at `src/components/Button.jsx` - - Form submission logic in `src/utils/formHandlers.js` - - **Key observation**: onClick handler in Button component doesn't appear to be triggered - -• **Diagnose potential causes** - - Event handler might not be properly attached to the button - - **State management issue**: form validation state might be blocking submission - - Button could be disabled by a condition we're missing - - Event propagation might be stopped elsewhere - - Possible React synthetic event issues - -• **Plan debugging approach** - - Add console.logs to track execution flow - - **Fix #1**: Ensure onClick prop is properly passed through Button component - - **Fix #2**: Check form validation state before submission - - **Fix #3**: Verify event handler is properly bound in the component - - Add error handling to catch and display submission issues - -• **Consider improvements beyond the fix** - - Add visual feedback when button is clicked (loading state) - - Implement better error handling for form submissions - - Add logging to help debug edge cases - - -After completing your thinking process, proceed with your response following the guidelines above. Remember to be concise in your explanations to the user while being thorough in your thinking process. - -This structured thinking ensures you: -1. Don't miss important aspects of the request -2. Consider all relevant factors before making changes -3. Deliver more accurate and helpful responses -4. Maintain a consistent approach to problem-solving - - # Tech Stack - You are building a React application. diff --git a/e2e-tests/snapshots/select_component.spec.ts_select-component-next-js-1.txt b/e2e-tests/snapshots/select_component.spec.ts_select-component-next-js-1.txt index 1d19a63..84590fe 100644 --- a/e2e-tests/snapshots/select_component.spec.ts_select-component-next-js-1.txt +++ b/e2e-tests/snapshots/select_component.spec.ts_select-component-next-js-1.txt @@ -282,58 +282,6 @@ Do not hesitate to extensively use console logs to follow the flow of the code. DO NOT OVERENGINEER THE CODE. You take great pride in keeping things simple and elegant. You don't start by writing very complex error handling, fallback mechanisms, etc. You focus on the user's request and make the minimum amount of changes needed. DON'T DO MORE THAN WHAT THE USER ASKS FOR. - -# Thinking Process - -Before responding to user requests, ALWAYS use tags to carefully plan your approach. This structured thinking process helps you organize your thoughts and ensure you provide the most accurate and helpful response. Your thinking should: - -- Use **bullet points** to break down the steps -- **Bold key insights** and important considerations -- Follow a clear analytical framework - -Example of proper thinking structure for a debugging request: - - -• **Identify the specific UI/FE bug described by the user** - - "Form submission button doesn't work when clicked" - - User reports clicking the button has no effect - - This appears to be a **functional issue**, not just styling - -• **Examine relevant components in the codebase** - - Form component at `src/components/ContactForm.jsx` - - Button component at `src/components/Button.jsx` - - Form submission logic in `src/utils/formHandlers.js` - - **Key observation**: onClick handler in Button component doesn't appear to be triggered - -• **Diagnose potential causes** - - Event handler might not be properly attached to the button - - **State management issue**: form validation state might be blocking submission - - Button could be disabled by a condition we're missing - - Event propagation might be stopped elsewhere - - Possible React synthetic event issues - -• **Plan debugging approach** - - Add console.logs to track execution flow - - **Fix #1**: Ensure onClick prop is properly passed through Button component - - **Fix #2**: Check form validation state before submission - - **Fix #3**: Verify event handler is properly bound in the component - - Add error handling to catch and display submission issues - -• **Consider improvements beyond the fix** - - Add visual feedback when button is clicked (loading state) - - Implement better error handling for form submissions - - Add logging to help debug edge cases - - -After completing your thinking process, proceed with your response following the guidelines above. Remember to be concise in your explanations to the user while being thorough in your thinking process. - -This structured thinking ensures you: -1. Don't miss important aspects of the request -2. Consider all relevant factors before making changes -3. Deliver more accurate and helpful responses -4. Maintain a consistent approach to problem-solving - - # AI Development Rules This document outlines the technology stack and specific library usage guidelines for this Next.js application. Adhering to these rules will help maintain consistency, improve collaboration, and ensure the AI assistant can effectively understand and modify the codebase. diff --git a/package-lock.json b/package-lock.json index 06693cb..a1e46a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@ai-sdk/anthropic": "^1.2.8", - "@ai-sdk/google": "^1.2.10", + "@ai-sdk/google": "^1.2.19", "@ai-sdk/openai": "^1.3.7", "@ai-sdk/openai-compatible": "^0.2.13", "@biomejs/biome": "^1.9.4", @@ -135,13 +135,13 @@ } }, "node_modules/@ai-sdk/google": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-1.2.14.tgz", - "integrity": "sha512-r3FSyyWl0KVjUlKn5o+vMl+Nk8Z/mV6xrqW+49g7fMoRVr/wkRxJZtHorrdDGRreCJubZyAk8ziSQSLpgv2H6w==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-1.2.19.tgz", + "integrity": "sha512-Xgl6eftIRQ4srUdCzxM112JuewVMij5q4JLcNmHcB68Bxn9dpr3MVUSPlJwmameuiQuISIA8lMB+iRiRbFsaqA==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "1.1.3", - "@ai-sdk/provider-utils": "2.2.7" + "@ai-sdk/provider-utils": "2.2.8" }, "engines": { "node": ">=18" @@ -150,6 +150,23 @@ "zod": "^3.0.0" } }, + "node_modules/@ai-sdk/google/node_modules/@ai-sdk/provider-utils": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz", + "integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "nanoid": "^3.3.8", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.23.8" + } + }, "node_modules/@ai-sdk/openai": { "version": "1.3.21", "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-1.3.21.tgz", diff --git a/package.json b/package.json index 92e8e4d..a21c762 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ }, "dependencies": { "@ai-sdk/anthropic": "^1.2.8", - "@ai-sdk/google": "^1.2.10", + "@ai-sdk/google": "^1.2.19", "@ai-sdk/openai": "^1.3.7", "@ai-sdk/openai-compatible": "^0.2.13", "@biomejs/biome": "^1.9.4", diff --git a/src/ipc/handlers/chat_stream_handlers.ts b/src/ipc/handlers/chat_stream_handlers.ts index 39f91cd..db8ee62 100644 --- a/src/ipc/handlers/chat_stream_handlers.ts +++ b/src/ipc/handlers/chat_stream_handlers.ts @@ -33,6 +33,9 @@ import { readFile, writeFile, unlink } from "fs/promises"; import { getMaxTokens } from "../utils/token_utils"; import { MAX_CHAT_TURNS_IN_CONTEXT } from "@/constants/settings_constants"; import { validateChatContext } from "../utils/context_paths_utils"; +import { GoogleGenerativeAIProviderOptions } from "@ai-sdk/google"; + +import { getExtraProviderOptions } from "../utils/thinking_utils"; const logger = log.scope("chat_stream_handlers"); @@ -443,17 +446,31 @@ This conversation includes one or more image attachments. When the user uploads } // When calling streamText, the messages need to be properly formatted for mixed content - const { textStream } = streamText({ + const { fullStream } = streamText({ maxTokens: await getMaxTokens(settings.selectedModel), temperature: 0, maxRetries: 2, model: modelClient.model, + providerOptions: { + "dyad-gateway": getExtraProviderOptions( + modelClient.builtinProviderId, + ), + google: { + thinkingConfig: { + includeThoughts: true, + }, + } satisfies GoogleGenerativeAIProviderOptions, + }, system: systemPrompt, messages: chatMessages.filter((m) => m.content), onError: (error: any) => { logger.error("Error streaming text:", error); - const message = - (error as any)?.error?.message || JSON.stringify(error); + let errorMessage = (error as any)?.error?.message; + const responseBody = error?.error?.responseBody; + if (errorMessage && responseBody) { + errorMessage += "\n\nDetails: " + responseBody; + } + const message = errorMessage || JSON.stringify(error); event.sender.send( "chat:response:error", `Sorry, there was an error from the AI: ${message}`, @@ -465,10 +482,38 @@ This conversation includes one or more image attachments. When the user uploads }); // Process the stream as before + let inThinkingBlock = false; try { - for await (const textPart of textStream) { - fullResponse += textPart; - fullResponse = cleanThinkingByEscapingDyadTags(fullResponse); + for await (const part of fullStream) { + let chunk = ""; + if (part.type === "text-delta") { + if (inThinkingBlock) { + chunk = ""; + inThinkingBlock = false; + } + chunk += part.textDelta; + } else if (part.type === "reasoning") { + if (!inThinkingBlock) { + chunk = ""; + inThinkingBlock = true; + } + // Escape dyad tags in reasoning content + // We are replacing the opening tag with a look-alike character + // to avoid issues where thinking content includes dyad tags + // and are mishandled by: + // 1. FE markdown parser + // 2. Main process response processor + chunk += part.textDelta + .replace(/ { - // Use default fetch if no init or body - if (!init || !init.body || typeof init.body !== "string") { - return (options.fetch || fetch)(input, init); - } + fetch: (input: RequestInfo | URL, init?: RequestInit) => { + // Use default fetch if no init or body + if (!init || !init.body || typeof init.body !== "string") { + return (options.fetch || fetch)(input, init); + } - try { - // Parse the request body to manipulate it - const parsedBody = JSON.parse(init.body); + try { + // Parse the request body to manipulate it + const parsedBody = { + ...JSON.parse(init.body), + ...getExtraProviderOptions(options.originalProviderId), + }; - // Add files to the request if they exist - if (files?.length) { - parsedBody.dyad_options = { - files, - enable_lazy_edits: options.dyadOptions.enableLazyEdits, - enable_smart_files_context: - options.dyadOptions.enableSmartFilesContext, - }; - } - - // Return modified request with files included - const modifiedInit = { - ...init, - body: JSON.stringify(parsedBody), - }; - - // Use the provided fetch or default fetch - return (options.fetch || fetch)(input, modifiedInit); - } catch (e) { - logger.error("Error parsing request body", e); - // If parsing fails, use original request - return (options.fetch || fetch)(input, init); - } + // Add files to the request if they exist + if (files?.length) { + parsedBody.dyad_options = { + files, + enable_lazy_edits: options.dyadOptions.enableLazyEdits, + enable_smart_files_context: + options.dyadOptions.enableSmartFilesContext, + }; } - : options.fetch, + + // Return modified request with files included + const modifiedInit = { + ...init, + body: JSON.stringify(parsedBody), + }; + + // Use the provided fetch or default fetch + return (options.fetch || fetch)(input, modifiedInit); + } catch (e) { + logger.error("Error parsing request body", e); + // If parsing fails, use original request + return (options.fetch || fetch)(input, init); + } + }, }; return new OpenAICompatibleChatLanguageModel(modelId, restSettings, config); diff --git a/src/ipc/utils/thinking_utils.ts b/src/ipc/utils/thinking_utils.ts new file mode 100644 index 0000000..b5ee058 --- /dev/null +++ b/src/ipc/utils/thinking_utils.ts @@ -0,0 +1,18 @@ +import { PROVIDERS_THAT_SUPPORT_THINKING } from "../shared/language_model_helpers"; + +export function getExtraProviderOptions( + providerId: string | undefined, +): Record { + if (!providerId) { + return {}; + } + if (PROVIDERS_THAT_SUPPORT_THINKING.includes(providerId)) { + return { + thinking: { + type: "enabled", + include_thoughts: true, + }, + }; + } + return {}; +} diff --git a/src/prompts/system_prompt.ts b/src/prompts/system_prompt.ts index e2e9a4f..76de903 100644 --- a/src/prompts/system_prompt.ts +++ b/src/prompts/system_prompt.ts @@ -338,8 +338,6 @@ Do not hesitate to extensively use console logs to follow the flow of the code. DO NOT OVERENGINEER THE CODE. You take great pride in keeping things simple and elegant. You don't start by writing very complex error handling, fallback mechanisms, etc. You focus on the user's request and make the minimum amount of changes needed. DON'T DO MORE THAN WHAT THE USER ASKS FOR. -${THINKING_PROMPT} - [[AI_RULES]] Directory names MUST be all lower-case (src/pages, src/components, etc.). File names may use mixed-case if you like. diff --git a/testing/fake-llm-server/index.ts b/testing/fake-llm-server/index.ts index afbfe53..143fe2a 100644 --- a/testing/fake-llm-server/index.ts +++ b/testing/fake-llm-server/index.ts @@ -35,17 +35,6 @@ export function createStreamChunk( } export const CANNED_MESSAGE = ` - - \`\`: - I'll think about the problem and write a bug report. - - - - - Fake dyad write - - - A file (2)