diff --git a/python-examples/computer-use/api/gemini-computer-use.py b/python-examples/computer-use/api/gemini-computer-use.py index 541aa9a8..5248fd58 100644 --- a/python-examples/computer-use/api/gemini-computer-use.py +++ b/python-examples/computer-use/api/gemini-computer-use.py @@ -37,9 +37,29 @@ async def automation(page: StagehandPage, params: Params, *args: ..., **kwargs: ) # Execute the autonomous task with the Computer Use Agent - result = await agent.execute( - instruction=params["query"], max_steps=30, auto_screenshot=True - ) + try: + result = await agent.execute( + instruction=params["query"], max_steps=30, auto_screenshot=True + ) + except Exception as e: + error_str = str(e).lower() + if any( + kw in error_str + for kw in [ + "credit", + "quota", + "rate limit", + "rate_limit", + "insufficient", + "payment", + "402", + ] + ): + raise RuntimeError( + "❌ AI credits exceeded or rate limit reached. " + "Please check your API key quota or Intuned account credit balance." + ) from e + raise print("Task completed!") print(f"Result: {result}") diff --git a/python-examples/hybrid-automation/api/rpa/fill-form.py b/python-examples/hybrid-automation/api/rpa/fill-form.py index a7d5a32b..d8cd9652 100644 --- a/python-examples/hybrid-automation/api/rpa/fill-form.py +++ b/python-examples/hybrid-automation/api/rpa/fill-form.py @@ -8,6 +8,28 @@ from stagehand.types.session_start_params import Browser, BrowserLaunchOptions +def _raise_clear_ai_error(e: Exception) -> None: + """Re-raise with a clear message if the error is related to AI credits or quota.""" + error_str = str(e).lower() + if any( + kw in error_str + for kw in [ + "credit", + "quota", + "rate limit", + "rate_limit", + "insufficient", + "payment", + "402", + ] + ): + raise RuntimeError( + "❌ AI credits exceeded or rate limit reached. " + "Please check your Intuned account credit balance." + ) from e + raise e + + class BookConsultationSchema(BaseModel): name: str email: str @@ -98,12 +120,15 @@ async def automation( print("✓ Filled name with Playwright") except Exception as e: print(f"Playwright failed for name, using Stagehand act: {e}") - await client.sessions.act( - id=session_id, - input=f'Type "{name}" in the name input field', - options={"model": model_config}, - ) - print("✓ Filled name with Stagehand act") + try: + await client.sessions.act( + id=session_id, + input=f'Type "{name}" in the name input field', + options={"model": model_config}, + ) + print("✓ Filled name with Stagehand act") + except Exception as stagehand_error: + _raise_clear_ai_error(stagehand_error) # Step 2: Fill email field try: @@ -111,12 +136,15 @@ async def automation( print("✓ Filled email with Playwright") except Exception as e: print(f"Playwright failed for email, using Stagehand act: {e}") - await client.sessions.act( - id=session_id, - input=f'Type "{email}" in the email input field', - options={"model": model_config}, - ) - print("✓ Filled email with Stagehand act") + try: + await client.sessions.act( + id=session_id, + input=f'Type "{email}" in the email input field', + options={"model": model_config}, + ) + print("✓ Filled email with Stagehand act") + except Exception as stagehand_error: + _raise_clear_ai_error(stagehand_error) # Step 3: Fill phone field try: @@ -124,12 +152,15 @@ async def automation( print("✓ Filled phone with Playwright") except Exception as e: print(f"Playwright failed for phone, using Stagehand act: {e}") - await client.sessions.act( - id=session_id, - input=f'Type "{phone}" in the phone input field', - options={"model": model_config}, - ) - print("✓ Filled phone with Stagehand act") + try: + await client.sessions.act( + id=session_id, + input=f'Type "{phone}" in the phone input field', + options={"model": model_config}, + ) + print("✓ Filled phone with Stagehand act") + except Exception as stagehand_error: + _raise_clear_ai_error(stagehand_error) # Step 4: Fill date field try: @@ -137,12 +168,15 @@ async def automation( print("✓ Filled date with Playwright") except Exception as e: print(f"Playwright failed for date, using Stagehand act: {e}") - await client.sessions.act( - id=session_id, - input=f'Type "{date}" in the date input field', - options={"model": model_config}, - ) - print("✓ Filled date with Stagehand act") + try: + await client.sessions.act( + id=session_id, + input=f'Type "{date}" in the date input field', + options={"model": model_config}, + ) + print("✓ Filled date with Stagehand act") + except Exception as stagehand_error: + _raise_clear_ai_error(stagehand_error) # Step 5: Fill time field try: @@ -150,12 +184,15 @@ async def automation( print("✓ Filled time with Playwright") except Exception as e: print(f"Playwright failed for time, using Stagehand act: {e}") - await client.sessions.act( - id=session_id, - input=f'Type "{time}" in the time input field', - options={"model": model_config}, - ) - print("✓ Filled time with Stagehand act") + try: + await client.sessions.act( + id=session_id, + input=f'Type "{time}" in the time input field', + options={"model": model_config}, + ) + print("✓ Filled time with Stagehand act") + except Exception as stagehand_error: + _raise_clear_ai_error(stagehand_error) # Step 6: Select the consultation topic from dropdown try: @@ -163,12 +200,15 @@ async def automation( print("✓ Selected topic with Playwright") except Exception as e: print(f"Playwright failed for topic selection, using Stagehand act: {e}") - await client.sessions.act( - id=session_id, - input=f'Select "{topic}" from the topic dropdown', - options={"model": model_config}, - ) - print("✓ Selected topic with Stagehand act") + try: + await client.sessions.act( + id=session_id, + input=f'Select "{topic}" from the topic dropdown', + options={"model": model_config}, + ) + print("✓ Selected topic with Stagehand act") + except Exception as stagehand_error: + _raise_clear_ai_error(stagehand_error) # Step 7: Submit the booking form try: @@ -176,12 +216,15 @@ async def automation( print("✓ Submitted form with Playwright") except Exception as e: print(f"Playwright failed for submit, using Stagehand act: {e}") - await client.sessions.act( - id=session_id, - input="Click the submit button to submit the booking form", - options={"model": model_config}, - ) - print("✓ Submitted form with Stagehand act") + try: + await client.sessions.act( + id=session_id, + input="Click the submit button to submit the booking form", + options={"model": model_config}, + ) + print("✓ Submitted form with Stagehand act") + except Exception as stagehand_error: + _raise_clear_ai_error(stagehand_error) # Step 8: Wait for and verify the success modal try: @@ -191,32 +234,35 @@ async def automation( print("✓ Verified success with Playwright") except Exception as e: print(f"Playwright failed for verification, using Stagehand extract: {e}") - result = await client.sessions.extract( - id=session_id, - instruction="Check if the booking was successful. Look for a success modal or confirmation message.", - options={"model": model_config}, - schema={ - "type": "object", - "properties": { - "success": { - "type": "boolean", - "description": "Whether the booking was successful", - }, - "message": { - "type": "string", - "description": "The success or error message displayed", + try: + result = await client.sessions.extract( + id=session_id, + instruction="Check if the booking was successful. Look for a success modal or confirmation message.", + options={"model": model_config}, + schema={ + "type": "object", + "properties": { + "success": { + "type": "boolean", + "description": "Whether the booking was successful", + }, + "message": { + "type": "string", + "description": "The success or error message displayed", + }, }, + "required": ["success", "message"], }, - "required": ["success", "message"], - }, - ) - result_data = ( - SuccessCheck.model_validate(result.data.result) - if result.data.result - else None - ) - is_success = result_data.success if result_data else False - print(f"✓ Verified with Stagehand extract: {result_data}") + ) + result_data = ( + SuccessCheck.model_validate(result.data.result) + if result.data.result + else None + ) + is_success = result_data.success if result_data else False + print(f"✓ Verified with Stagehand extract: {result_data}") + except Exception as stagehand_error: + _raise_clear_ai_error(stagehand_error) finally: # Cleanup Stagehand diff --git a/python-examples/rpa-forms-example/api/insurance-form-filler.py b/python-examples/rpa-forms-example/api/insurance-form-filler.py index 16838953..1d2f3926 100644 --- a/python-examples/rpa-forms-example/api/insurance-form-filler.py +++ b/python-examples/rpa-forms-example/api/insurance-form-filler.py @@ -10,6 +10,28 @@ class InvalidActionError(Exception): pass +AI_CREDIT_KEYWORDS = [ + "credit", + "quota", + "rate limit", + "rate_limit", + "insufficient", + "payment", + "402", +] + + +def _raise_clear_ai_error(e: Exception) -> None: + """Re-raise with a clear message if the error is related to AI credits or quota.""" + error_str = str(e).lower() + if any(kw in error_str for kw in AI_CREDIT_KEYWORDS): + raise RuntimeError( + "❌ AI credits exceeded or rate limit reached. " + "Please check your Intuned account credit balance." + ) from e + raise e + + async def automation(page: Page, params: ListParameters, *args: ..., **kwargs: ...): base_url, api_key = get_ai_gateway_config() cdp_url = attempt_store.get("cdp_url") @@ -44,22 +66,27 @@ async def automation(page: Page, params: ListParameters, *args: ..., **kwargs: . async def perform_action(page: Page, instruction: str) -> None: for _ in range(3): - observed = await client.sessions.observe( - id=session_id, - instruction=instruction, - options={"model": model_config}, - ) - if observed.data.result: - await client.sessions.act( + try: + observed = await client.sessions.observe( id=session_id, - input=instruction, + instruction=instruction, options={"model": model_config}, ) - await page.wait_for_load_state("domcontentloaded") - await page.wait_for_timeout(2000) - return - else: - await page.wait_for_timeout(2000) + if observed.data.result: + await client.sessions.act( + id=session_id, + input=instruction, + options={"model": model_config}, + ) + await page.wait_for_load_state("domcontentloaded") + await page.wait_for_timeout(2000) + return + else: + await page.wait_for_timeout(2000) + except Exception as e: + _raise_clear_ai_error( + e + ) # clear message for credit errors, re-raises original otherwise raise InvalidActionError( f"Could not find action for instruction: {instruction}" ) diff --git a/python-examples/stagehand/api/get-books.py b/python-examples/stagehand/api/get-books.py index d3c2cdd3..7d13841c 100644 --- a/python-examples/stagehand/api/get-books.py +++ b/python-examples/stagehand/api/get-books.py @@ -24,6 +24,27 @@ class BooksResponse(BaseModel): MAX_PAGES = 10 +AI_CREDIT_KEYWORDS = [ + "credit", + "quota", + "rate limit", + "rate_limit", + "insufficient", + "payment", + "402", +] + + +def _raise_clear_ai_error(e: Exception) -> None: + """Re-raise with a clear message if the error is related to AI credits or quota.""" + error_str = str(e).lower() + if any(kw in error_str for kw in AI_CREDIT_KEYWORDS): + raise RuntimeError( + "❌ AI credits exceeded or rate limit reached. " + "Please check your Intuned account credit balance." + ) from e + raise e + async def automation(page: Page, params: Params, **_kwargs): base_url, api_key = get_ai_gateway_config() @@ -145,6 +166,10 @@ async def automation(page: Page, params: Params, **_kwargs): ) print(f"Navigated to page {page_num + 1}") except Exception as e: + # Re-raise AI credit errors immediately; break for "no more pages" errors + error_str = str(e).lower() + if any(kw in error_str for kw in AI_CREDIT_KEYWORDS): + _raise_clear_ai_error(e) print(f"No more pages or navigation failed: {e}") break finally: diff --git a/typescript-examples/computer-use/api/stagehand.ts b/typescript-examples/computer-use/api/stagehand.ts index b57a620f..dc1d98a7 100644 --- a/typescript-examples/computer-use/api/stagehand.ts +++ b/typescript-examples/computer-use/api/stagehand.ts @@ -7,6 +7,17 @@ interface Params { query: string; // The task you want the AI to perform } +function raiseClearAiError(e: unknown): never { + const msg = e instanceof Error ? e.message : String(e); + if (/credits?|quota|rate.?limit|insufficient|payment.?required|402/i.test(msg)) { + throw new Error( + `❌ AI credits exceeded or rate limit reached. Please check your Intuned account credit balance. (${msg})` + ); + } + if (e instanceof Error) throw e; + throw new Error(String(e)); +} + async function getWebSocketUrl(cdpUrl: string): Promise { if (!cdpUrl) { throw new Error("CDP URL is not available. Make sure the browser is running and the setupContext hook is configured."); @@ -80,15 +91,20 @@ export default async function handler( }); // Agent runs on current page - const result = await agent.execute({ - instruction: query, - maxSteps: 50, - }); - - return { - result: result.success ? 'Task completed successfully' : 'Task failed', - success: result.success, - }; + try { + const execResult = await agent.execute({ + instruction: query, + maxSteps: 50, + }); + return { + result: execResult.success + ? "Task completed successfully" + : "Task failed", + success: execResult.success, + }; + } catch (e) { + raiseClearAiError(e); + } } finally { // Cleanup Stagehand console.log("\nClosing 🤘 Stagehand..."); diff --git a/typescript-examples/hybrid-automation/api/rpa/fill-form.ts b/typescript-examples/hybrid-automation/api/rpa/fill-form.ts index ce20d6bf..e2d58eea 100644 --- a/typescript-examples/hybrid-automation/api/rpa/fill-form.ts +++ b/typescript-examples/hybrid-automation/api/rpa/fill-form.ts @@ -32,6 +32,17 @@ const successCheckSchema = z.object({ message: z.string().describe("The success or error message displayed"), }); +function raiseClearAiError(e: unknown): never { + const msg = e instanceof Error ? e.message : String(e); + if (/credits?|quota|rate.?limit|insufficient|payment.?required|402/i.test(msg)) { + throw new Error( + `❌ AI credits exceeded or rate limit reached. Please check your Intuned account credit balance. (${msg})` + ); + } + if (e instanceof Error) throw e; + throw new Error(String(e)); +} + async function getWebSocketUrl(cdpUrl: string): Promise { if (cdpUrl.includes("ws://") || cdpUrl.includes("wss://")) { return cdpUrl; @@ -93,8 +104,12 @@ export default async function handler( console.log("✓ Filled name with Playwright"); } catch (e) { console.log(`Playwright failed for name, using Stagehand act: ${e}`); - await stagehand.act(`Type "${name}" in the name input field`); - console.log("✓ Filled name with Stagehand act"); + try { + await stagehand.act(`Type "${name}" in the name input field`); + console.log("✓ Filled name with Stagehand act"); + } catch (stagehandError) { + raiseClearAiError(stagehandError); + } } // Step 2: Fill email field @@ -103,8 +118,12 @@ export default async function handler( console.log("✓ Filled email with Playwright"); } catch (e) { console.log(`Playwright failed for email, using Stagehand act: ${e}`); - await stagehand.act(`Type "${email}" in the email input field`); - console.log("✓ Filled email with Stagehand act"); + try { + await stagehand.act(`Type "${email}" in the email input field`); + console.log("✓ Filled email with Stagehand act"); + } catch (stagehandError) { + raiseClearAiError(stagehandError); + } } // Step 3: Fill phone field @@ -113,8 +132,12 @@ export default async function handler( console.log("✓ Filled phone with Playwright"); } catch (e) { console.log(`Playwright failed for phone, using Stagehand act: ${e}`); - await stagehand.act(`Type "${phone}" in the phone input field`); - console.log("✓ Filled phone with Stagehand act"); + try { + await stagehand.act(`Type "${phone}" in the phone input field`); + console.log("✓ Filled phone with Stagehand act"); + } catch (stagehandError) { + raiseClearAiError(stagehandError); + } } // Step 4: Fill date field @@ -123,8 +146,12 @@ export default async function handler( console.log("✓ Filled date with Playwright"); } catch (e) { console.log(`Playwright failed for date, using Stagehand act: ${e}`); - await stagehand.act(`Type "${date}" in the date input field`); - console.log("✓ Filled date with Stagehand act"); + try { + await stagehand.act(`Type "${date}" in the date input field`); + console.log("✓ Filled date with Stagehand act"); + } catch (stagehandError) { + raiseClearAiError(stagehandError); + } } // Step 5: Fill time field @@ -133,8 +160,12 @@ export default async function handler( console.log("✓ Filled time with Playwright"); } catch (e) { console.log(`Playwright failed for time, using Stagehand act: ${e}`); - await stagehand.act(`Type "${time}" in the time input field`); - console.log("✓ Filled time with Stagehand act"); + try { + await stagehand.act(`Type "${time}" in the time input field`); + console.log("✓ Filled time with Stagehand act"); + } catch (stagehandError) { + raiseClearAiError(stagehandError); + } } // Step 6: Select the consultation topic from dropdown @@ -145,8 +176,12 @@ export default async function handler( console.log( `Playwright failed for topic selection, using Stagehand act: ${e}` ); - await stagehand.act(`Select "${topic}" from the topic dropdown`); - console.log("✓ Selected topic with Stagehand act"); + try { + await stagehand.act(`Select "${topic}" from the topic dropdown`); + console.log("✓ Selected topic with Stagehand act"); + } catch (stagehandError) { + raiseClearAiError(stagehandError); + } } // Step 7: Submit the booking form @@ -155,10 +190,14 @@ export default async function handler( console.log("✓ Submitted form with Playwright"); } catch (e) { console.log(`Playwright failed for submit, using Stagehand act: ${e}`); - await stagehand.act( - "Click the submit button to submit the booking form" - ); - console.log("✓ Submitted form with Stagehand act"); + try { + await stagehand.act( + "Click the submit button to submit the booking form" + ); + console.log("✓ Submitted form with Stagehand act"); + } catch (stagehandError) { + raiseClearAiError(stagehandError); + } } // Step 8: Wait for and verify the success modal @@ -174,12 +213,16 @@ export default async function handler( console.log( `Playwright failed for verification, using Stagehand extract: ${e}` ); - const result = await stagehand.extract( - "Check if the booking was successful. Look for a success modal or confirmation message.", - successCheckSchema - ); - isSuccess = result?.success || false; - console.log(`✓ Verified with Stagehand extract: ${JSON.stringify(result)}`); + try { + const result = await stagehand.extract( + "Check if the booking was successful. Look for a success modal or confirmation message.", + successCheckSchema + ); + isSuccess = result?.success || false; + console.log(`✓ Verified with Stagehand extract: ${JSON.stringify(result)}`); + } catch (stagehandError) { + raiseClearAiError(stagehandError); + } } } finally { // Cleanup Stagehand diff --git a/typescript-examples/rpa-forms-example/api/insurance-form-filler.ts b/typescript-examples/rpa-forms-example/api/insurance-form-filler.ts index edd36c7b..ae850e4c 100644 --- a/typescript-examples/rpa-forms-example/api/insurance-form-filler.ts +++ b/typescript-examples/rpa-forms-example/api/insurance-form-filler.ts @@ -10,6 +10,17 @@ class InvalidActionError extends Error { } } +function raiseClearAiError(e: unknown): never { + const msg = e instanceof Error ? e.message : String(e); + if (/credits?|quota|rate.?limit|insufficient|payment.?required|402/i.test(msg)) { + throw new Error( + `❌ AI credits exceeded or rate limit reached. Please check your Intuned account credit balance. (${msg})` + ); + } + if (e instanceof Error) throw e; + throw new Error(String(e)); +} + async function getWebSocketUrl(cdpUrl: string): Promise { if (cdpUrl.includes("ws://") || cdpUrl.includes("wss://")) { return cdpUrl; @@ -28,14 +39,18 @@ async function performAction( instruction: string ): Promise { for (let i = 0; i < 3; i++) { - const action = await stagehand.observe(instruction); - if (action && action.length > 0) { - await stagehand.act(action[0]); - await page.waitForLoadState("domcontentloaded"); - await page.waitForTimeout(2000); - return; - } else { - await page.waitForTimeout(2000); + try { + const action = await stagehand.observe(instruction); + if (action && action.length > 0) { + await stagehand.act(action[0]); + await page.waitForLoadState("domcontentloaded"); + await page.waitForTimeout(2000); + return; + } else { + await page.waitForTimeout(2000); + } + } catch (e) { + raiseClearAiError(e); // re-raises with clear message for credit errors, re-throws original otherwise } } throw new InvalidActionError( diff --git a/typescript-examples/stagehand/api/get-books.ts b/typescript-examples/stagehand/api/get-books.ts index defb29ff..b2b9c284 100644 --- a/typescript-examples/stagehand/api/get-books.ts +++ b/typescript-examples/stagehand/api/get-books.ts @@ -24,6 +24,17 @@ type BooksResponse = z.infer; const MAX_PAGES = 10; +function raiseClearAiError(e: unknown): never { + const msg = e instanceof Error ? e.message : String(e); + if (/credits?|quota|rate.?limit|insufficient|payment.?required|402/i.test(msg)) { + throw new Error( + `❌ AI credits exceeded or rate limit reached. Please check your Intuned account credit balance. (${msg})` + ); + } + if (e instanceof Error) throw e; + throw new Error(String(e)); +} + async function getWebSocketUrl(cdpUrl: string): Promise { if (cdpUrl.includes("ws://") || cdpUrl.includes("wss://")) { return cdpUrl; @@ -116,6 +127,11 @@ export default async function handler( ); console.log(`Navigated to page ${pageNum + 1}`); } catch (e) { + // Re-raise AI credit errors immediately; break for "no more pages" errors + const msg = e instanceof Error ? e.message : String(e); + if (/credits?|quota|rate.?limit|insufficient|payment.?required|402/i.test(msg)) { + raiseClearAiError(e); + } console.log(`No more pages or navigation failed: ${e}`); break; }