From b56d724ed85e4362c5393f26d20c00a96d69c18a Mon Sep 17 00:00:00 2001 From: Shuchang Zheng Date: Fri, 20 Feb 2026 00:09:43 -0800 Subject: [PATCH] v1.0.19: make env vars should always overrides api keys stored in the streamlit mount in skyvern image (#4824) --- Dockerfile.ui | 18 +++- entrypoint-skyvernui.sh | 39 ++++++- pyproject.toml | 2 +- skyvern-ts/client/package-lock.json | 4 +- skyvern-ts/client/package.json | 2 +- skyvern-ts/client/src/Client.ts | 81 +++++++++++++- .../src/api/types/ClearCacheResponse.ts | 11 ++ .../src/api/types/CredentialResponse.ts | 4 + .../client/src/api/types/WorkflowRun.ts | 3 + skyvern-ts/client/src/api/types/index.ts | 1 + skyvern-ts/client/src/version.ts | 2 +- skyvern-ts/client/tests/wire/main.test.ts | 60 +++++++++++ skyvern/client/__init__.py | 3 + skyvern/client/client.py | 75 +++++++++++++ skyvern/client/core/client_wrapper.py | 4 +- skyvern/client/raw_client.py | 101 ++++++++++++++++++ skyvern/client/types/__init__.py | 3 + skyvern/client/types/clear_cache_response.py | 31 ++++++ skyvern/client/types/credential_response.py | 10 ++ skyvern/client/types/workflow_run.py | 3 + skyvern/forge/sdk/routes/internal_auth.py | 14 ++- uv.lock | 2 +- 22 files changed, 454 insertions(+), 19 deletions(-) create mode 100644 skyvern-ts/client/src/api/types/ClearCacheResponse.ts create mode 100644 skyvern/client/types/clear_cache_response.py diff --git a/Dockerfile.ui b/Dockerfile.ui index e415829c..228bb1b8 100644 --- a/Dockerfile.ui +++ b/Dockerfile.ui @@ -1,13 +1,23 @@ FROM node:20.12-slim +# Install tini for proper signal handling and zombie reaping +RUN apt-get update && apt-get install -y --no-install-recommends tini && rm -rf /var/lib/apt/lists/* + WORKDIR /app COPY ./skyvern-frontend /app COPY ./entrypoint-skyvernui.sh /app/entrypoint-skyvernui.sh RUN npm install -ENV VITE_API_BASE_URL=http://localhost:8000/api/v1 -ENV VITE_WSS_BASE_URL=ws://localhost:8000/api/v1 -ENV VITE_ARTIFACT_API_BASE_URL=http://localhost:9090 +# Placeholders for runtime injection (will be replaced by entrypoint script) +ENV VITE_API_BASE_URL=__VITE_API_BASE_URL_PLACEHOLDER__ +ENV VITE_WSS_BASE_URL=__VITE_WSS_BASE_URL_PLACEHOLDER__ +ENV VITE_ARTIFACT_API_BASE_URL=__VITE_ARTIFACT_API_BASE_URL_PLACEHOLDER__ +ENV VITE_SKYVERN_API_KEY=__SKYVERN_API_KEY_PLACEHOLDER__ -CMD [ "/bin/bash", "/app/entrypoint-skyvernui.sh" ] +# Build at image time +RUN npm run build + +# Use tini as init for proper signal handling and zombie reaping +ENTRYPOINT ["/usr/bin/tini", "--"] +CMD ["/bin/bash", "/app/entrypoint-skyvernui.sh"] diff --git a/entrypoint-skyvernui.sh b/entrypoint-skyvernui.sh index d4225e39..600e243e 100644 --- a/entrypoint-skyvernui.sh +++ b/entrypoint-skyvernui.sh @@ -2,9 +2,40 @@ set -e -# setting api key -VITE_SKYVERN_API_KEY=$(sed -n 's/.*cred\s*=\s*"\([^"]*\)".*/\1/p' .streamlit/secrets.toml) -export VITE_SKYVERN_API_KEY -npm run start +# Default values for environment variables +VITE_API_BASE_URL="${VITE_API_BASE_URL:-http://localhost:8000/api/v1}" +VITE_WSS_BASE_URL="${VITE_WSS_BASE_URL:-ws://localhost:8000/api/v1}" +VITE_ARTIFACT_API_BASE_URL="${VITE_ARTIFACT_API_BASE_URL:-http://localhost:9090}" +# Priority for VITE_SKYVERN_API_KEY: +# 1. Environment variable (from .env file or docker-compose environment) +# 2. Fallback to .streamlit/secrets.toml +if [ -n "$VITE_SKYVERN_API_KEY" ]; then + # Trim whitespace and validate + VITE_SKYVERN_API_KEY=$(echo "$VITE_SKYVERN_API_KEY" | xargs) + echo "Using VITE_SKYVERN_API_KEY from environment variable" +else + # Fallback: Extract API key from secrets file + VITE_SKYVERN_API_KEY=$(sed -n 's/.*cred\s*=\s*"\([^"]*\)".*/\1/p' .streamlit/secrets.toml 2>/dev/null || echo "") + if [ -n "$VITE_SKYVERN_API_KEY" ]; then + echo "Using VITE_SKYVERN_API_KEY from .streamlit/secrets.toml" + else + echo "WARNING: No VITE_SKYVERN_API_KEY found in environment or .streamlit/secrets.toml" + fi +fi + +# Inject environment variables into pre-built JS files (replace placeholders) +# Using | as delimiter since URLs contain / +find /app/dist -name "*.js" -exec sed -i \ + -e "s|__VITE_API_BASE_URL_PLACEHOLDER__|${VITE_API_BASE_URL}|g" \ + -e "s|__VITE_WSS_BASE_URL_PLACEHOLDER__|${VITE_WSS_BASE_URL}|g" \ + -e "s|__VITE_ARTIFACT_API_BASE_URL_PLACEHOLDER__|${VITE_ARTIFACT_API_BASE_URL}|g" \ + -e "s|__SKYVERN_API_KEY_PLACEHOLDER__|${VITE_SKYVERN_API_KEY}|g" \ + {} \; + +# Start the servers (no rebuild needed) +# Tini (configured as ENTRYPOINT) handles signal forwarding and zombie reaping +node localServer.js & +node artifactServer.js & +wait diff --git a/pyproject.toml b/pyproject.toml index 43b9057a..18a23cf6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "skyvern" -version = "1.0.18" +version = "1.0.19" description = "" authors = [{ name = "Skyvern AI", email = "info@skyvern.com" }] requires-python = ">=3.11,<3.14" diff --git a/skyvern-ts/client/package-lock.json b/skyvern-ts/client/package-lock.json index fcaabdff..700f8db7 100644 --- a/skyvern-ts/client/package-lock.json +++ b/skyvern-ts/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "@skyvern/client", - "version": "1.0.18", + "version": "1.0.19", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@skyvern/client", - "version": "1.0.18", + "version": "1.0.19", "dependencies": { "playwright": "^1.48.0" }, diff --git a/skyvern-ts/client/package.json b/skyvern-ts/client/package.json index fcf23230..3d32d8c5 100644 --- a/skyvern-ts/client/package.json +++ b/skyvern-ts/client/package.json @@ -1,6 +1,6 @@ { "name": "@skyvern/client", - "version": "1.0.18", + "version": "1.0.19", "private": false, "repository": { "type": "git", diff --git a/skyvern-ts/client/src/Client.ts b/skyvern-ts/client/src/Client.ts index b098e4d0..0b052512 100644 --- a/skyvern-ts/client/src/Client.ts +++ b/skyvern-ts/client/src/Client.ts @@ -26,8 +26,8 @@ export class SkyvernClient { "x-api-key": _options?.apiKey, "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@skyvern/client", - "X-Fern-SDK-Version": "1.0.18", - "User-Agent": "@skyvern/client/1.0.18", + "X-Fern-SDK-Version": "1.0.19", + "User-Agent": "@skyvern/client/1.0.19", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -3139,6 +3139,83 @@ export class SkyvernClient { } } + /** + * Clear all cached scripts for a specific workflow. This will trigger script regeneration on subsequent runs. + * + * @param {string} workflowPermanentId - The workflow permanent ID to clear cache for + * @param {SkyvernClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link Skyvern.UnprocessableEntityError} + * + * @example + * await client.clearWorkflowCache("wpid_abc123") + */ + public clearWorkflowCache( + workflowPermanentId: string, + requestOptions?: SkyvernClient.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__clearWorkflowCache(workflowPermanentId, requestOptions)); + } + + private async __clearWorkflowCache( + workflowPermanentId: string, + requestOptions?: SkyvernClient.RequestOptions, + ): Promise> { + const _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ "x-api-key": requestOptions?.apiKey ?? this._options?.apiKey }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.SkyvernEnvironment.Cloud, + `v1/scripts/${core.url.encodePathParam(workflowPermanentId)}/cache`, + ), + method: "DELETE", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as Skyvern.ClearCacheResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + throw new Skyvern.UnprocessableEntityError(_response.error.body as unknown, _response.rawResponse); + default: + throw new errors.SkyvernError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.SkyvernError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.SkyvernTimeoutError( + "Timeout exceeded when calling DELETE /v1/scripts/{workflow_permanent_id}/cache.", + ); + case "unknown": + throw new errors.SkyvernError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + /** * Execute a single SDK action with the specified parameters * diff --git a/skyvern-ts/client/src/api/types/ClearCacheResponse.ts b/skyvern-ts/client/src/api/types/ClearCacheResponse.ts new file mode 100644 index 00000000..4a032560 --- /dev/null +++ b/skyvern-ts/client/src/api/types/ClearCacheResponse.ts @@ -0,0 +1,11 @@ +// This file was auto-generated by Fern from our API Definition. + +/** + * Response model for cache clearing operations. + */ +export interface ClearCacheResponse { + /** Number of cached entries deleted */ + deleted_count: number; + /** Status message */ + message: string; +} diff --git a/skyvern-ts/client/src/api/types/CredentialResponse.ts b/skyvern-ts/client/src/api/types/CredentialResponse.ts index e1432f9e..bef7a38f 100644 --- a/skyvern-ts/client/src/api/types/CredentialResponse.ts +++ b/skyvern-ts/client/src/api/types/CredentialResponse.ts @@ -14,6 +14,10 @@ export interface CredentialResponse { credential_type: Skyvern.CredentialTypeOutput; /** Name of the credential */ name: string; + /** Browser profile ID linked to this credential */ + browser_profile_id?: string; + /** Login page URL used during the credential test */ + tested_url?: string; } export namespace CredentialResponse { diff --git a/skyvern-ts/client/src/api/types/WorkflowRun.ts b/skyvern-ts/client/src/api/types/WorkflowRun.ts index 65ff8720..b048c8c0 100644 --- a/skyvern-ts/client/src/api/types/WorkflowRun.ts +++ b/skyvern-ts/client/src/api/types/WorkflowRun.ts @@ -29,6 +29,9 @@ export interface WorkflowRun { sequential_key?: string; ai_fallback?: boolean; code_gen?: boolean; + waiting_for_verification_code?: boolean; + verification_code_identifier?: string; + verification_code_polling_started_at?: string; queued_at?: string; started_at?: string; finished_at?: string; diff --git a/skyvern-ts/client/src/api/types/index.ts b/skyvern-ts/client/src/api/types/index.ts index ee4c0b04..fa0499c0 100644 --- a/skyvern-ts/client/src/api/types/index.ts +++ b/skyvern-ts/client/src/api/types/index.ts @@ -28,6 +28,7 @@ export * from "./BrowserProfile.js"; export * from "./BrowserSessionResponse.js"; export * from "./ChangeTierResponse.js"; export * from "./CheckoutSessionResponse.js"; +export * from "./ClearCacheResponse.js"; export * from "./ClickAction.js"; export * from "./ClickContext.js"; export * from "./CodeBlock.js"; diff --git a/skyvern-ts/client/src/version.ts b/skyvern-ts/client/src/version.ts index e3a611d3..1a65bbcd 100644 --- a/skyvern-ts/client/src/version.ts +++ b/skyvern-ts/client/src/version.ts @@ -1 +1 @@ -export const SDK_VERSION = "1.0.18"; +export const SDK_VERSION = "1.0.19"; diff --git a/skyvern-ts/client/tests/wire/main.test.ts b/skyvern-ts/client/tests/wire/main.test.ts index 7017efbb..377635b8 100644 --- a/skyvern-ts/client/tests/wire/main.test.ts +++ b/skyvern-ts/client/tests/wire/main.test.ts @@ -1484,6 +1484,9 @@ describe("SkyvernClient", () => { sequential_key: "sequential_key", ai_fallback: true, code_gen: true, + waiting_for_verification_code: true, + verification_code_identifier: "verification_code_identifier", + verification_code_polling_started_at: "2024-01-15T09:30:00Z", queued_at: "2024-01-15T09:30:00Z", started_at: "2024-01-15T09:30:00Z", finished_at: "2024-01-15T09:30:00Z", @@ -1531,6 +1534,9 @@ describe("SkyvernClient", () => { sequential_key: "sequential_key", ai_fallback: true, code_gen: true, + waiting_for_verification_code: true, + verification_code_identifier: "verification_code_identifier", + verification_code_polling_started_at: "2024-01-15T09:30:00Z", queued_at: "2024-01-15T09:30:00Z", started_at: "2024-01-15T09:30:00Z", finished_at: "2024-01-15T09:30:00Z", @@ -2627,6 +2633,8 @@ describe("SkyvernClient", () => { }, credential_type: "password", name: "Amazon Login", + browser_profile_id: "browser_profile_id", + tested_url: "tested_url", }, ]; server.mockEndpoint().get("/v1/credentials").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); @@ -2645,6 +2653,8 @@ describe("SkyvernClient", () => { }, credential_type: "password", name: "Amazon Login", + browser_profile_id: "browser_profile_id", + tested_url: "tested_url", }, ]); }); @@ -2678,6 +2688,8 @@ describe("SkyvernClient", () => { }, credential_type: "password", name: "Amazon Login", + browser_profile_id: "browser_profile_id", + tested_url: "tested_url", }; server .mockEndpoint() @@ -2706,6 +2718,8 @@ describe("SkyvernClient", () => { }, credential_type: "password", name: "Amazon Login", + browser_profile_id: "browser_profile_id", + tested_url: "tested_url", }); }); @@ -2756,6 +2770,8 @@ describe("SkyvernClient", () => { }, credential_type: "password", name: "Amazon Login", + browser_profile_id: "browser_profile_id", + tested_url: "tested_url", }; server .mockEndpoint() @@ -2783,6 +2799,8 @@ describe("SkyvernClient", () => { }, credential_type: "password", name: "Amazon Login", + browser_profile_id: "browser_profile_id", + tested_url: "tested_url", }); }); @@ -2857,6 +2875,8 @@ describe("SkyvernClient", () => { }, credential_type: "password", name: "Amazon Login", + browser_profile_id: "browser_profile_id", + tested_url: "tested_url", }; server .mockEndpoint() @@ -2876,6 +2896,8 @@ describe("SkyvernClient", () => { }, credential_type: "password", name: "Amazon Login", + browser_profile_id: "browser_profile_id", + tested_url: "tested_url", }); }); @@ -3435,6 +3457,44 @@ describe("SkyvernClient", () => { }).rejects.toThrow(Skyvern.UnprocessableEntityError); }); + test("clear_workflow_cache (1)", async () => { + const server = mockServerPool.createServer(); + const client = new SkyvernClient({ apiKey: "test", environment: server.baseUrl }); + + const rawResponseBody = { deleted_count: 1, message: "message" }; + server + .mockEndpoint() + .delete("/v1/scripts/wpid_abc123/cache") + .respondWith() + .statusCode(200) + .jsonBody(rawResponseBody) + .build(); + + const response = await client.clearWorkflowCache("wpid_abc123"); + expect(response).toEqual({ + deleted_count: 1, + message: "message", + }); + }); + + test("clear_workflow_cache (2)", async () => { + const server = mockServerPool.createServer(); + const client = new SkyvernClient({ apiKey: "test", environment: server.baseUrl }); + + const rawResponseBody = { key: "value" }; + server + .mockEndpoint() + .delete("/v1/scripts/workflow_permanent_id/cache") + .respondWith() + .statusCode(422) + .jsonBody(rawResponseBody) + .build(); + + await expect(async () => { + return await client.clearWorkflowCache("workflow_permanent_id"); + }).rejects.toThrow(Skyvern.UnprocessableEntityError); + }); + test("run_sdk_action (1)", async () => { const server = mockServerPool.createServer(); const client = new SkyvernClient({ apiKey: "test", environment: server.baseUrl }); diff --git a/skyvern/client/__init__.py b/skyvern/client/__init__.py index 41d5b0e5..b2b1299b 100644 --- a/skyvern/client/__init__.py +++ b/skyvern/client/__init__.py @@ -53,6 +53,7 @@ if typing.TYPE_CHECKING: BrowserSessionResponse, ChangeTierResponse, CheckoutSessionResponse, + ClearCacheResponse, ClickAction, ClickActionData, ClickContext, @@ -581,6 +582,7 @@ _dynamic_imports: typing.Dict[str, str] = { "BrowserSessionResponse": ".types", "ChangeTierResponse": ".types", "CheckoutSessionResponse": ".types", + "ClearCacheResponse": ".types", "ClickAction": ".types", "ClickActionData": ".types", "ClickContext": ".types", @@ -1135,6 +1137,7 @@ __all__ = [ "BrowserSessionResponse", "ChangeTierResponse", "CheckoutSessionResponse", + "ClearCacheResponse", "ClickAction", "ClickActionData", "ClickContext", diff --git a/skyvern/client/client.py b/skyvern/client/client.py index bf85876d..3a464e6b 100644 --- a/skyvern/client/client.py +++ b/skyvern/client/client.py @@ -18,6 +18,7 @@ from .types.browser_profile import BrowserProfile from .types.browser_session_response import BrowserSessionResponse from .types.change_tier_response import ChangeTierResponse from .types.checkout_session_response import CheckoutSessionResponse +from .types.clear_cache_response import ClearCacheResponse from .types.create_credential_request_credential import CreateCredentialRequestCredential from .types.create_script_response import CreateScriptResponse from .types.credential_response import CredentialResponse @@ -2101,6 +2102,39 @@ class Skyvern: _response = self._raw_client.deploy_script(script_id, files=files, request_options=request_options) return _response.data + def clear_workflow_cache( + self, workflow_permanent_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> ClearCacheResponse: + """ + Clear all cached scripts for a specific workflow. This will trigger script regeneration on subsequent runs. + + Parameters + ---------- + workflow_permanent_id : str + The workflow permanent ID to clear cache for + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ClearCacheResponse + Successful Response + + Examples + -------- + from skyvern import Skyvern + + client = Skyvern( + api_key="YOUR_API_KEY", + ) + client.clear_workflow_cache( + workflow_permanent_id="wpid_abc123", + ) + """ + _response = self._raw_client.clear_workflow_cache(workflow_permanent_id, request_options=request_options) + return _response.data + def run_sdk_action( self, *, @@ -4647,6 +4681,47 @@ class AsyncSkyvern: _response = await self._raw_client.deploy_script(script_id, files=files, request_options=request_options) return _response.data + async def clear_workflow_cache( + self, workflow_permanent_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> ClearCacheResponse: + """ + Clear all cached scripts for a specific workflow. This will trigger script regeneration on subsequent runs. + + Parameters + ---------- + workflow_permanent_id : str + The workflow permanent ID to clear cache for + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ClearCacheResponse + Successful Response + + Examples + -------- + import asyncio + + from skyvern import AsyncSkyvern + + client = AsyncSkyvern( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.clear_workflow_cache( + workflow_permanent_id="wpid_abc123", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.clear_workflow_cache(workflow_permanent_id, request_options=request_options) + return _response.data + async def run_sdk_action( self, *, diff --git a/skyvern/client/core/client_wrapper.py b/skyvern/client/core/client_wrapper.py index 80a1b205..baed6a24 100644 --- a/skyvern/client/core/client_wrapper.py +++ b/skyvern/client/core/client_wrapper.py @@ -22,10 +22,10 @@ class BaseClientWrapper: def get_headers(self) -> typing.Dict[str, str]: headers: typing.Dict[str, str] = { - "User-Agent": "skyvern/1.0.18", + "User-Agent": "skyvern/1.0.19", "X-Fern-Language": "Python", "X-Fern-SDK-Name": "skyvern", - "X-Fern-SDK-Version": "1.0.18", + "X-Fern-SDK-Version": "1.0.19", **(self.get_custom_headers() or {}), } if self._api_key is not None: diff --git a/skyvern/client/raw_client.py b/skyvern/client/raw_client.py index 3540b2f1..04230960 100644 --- a/skyvern/client/raw_client.py +++ b/skyvern/client/raw_client.py @@ -24,6 +24,7 @@ from .types.browser_profile import BrowserProfile from .types.browser_session_response import BrowserSessionResponse from .types.change_tier_response import ChangeTierResponse from .types.checkout_session_response import CheckoutSessionResponse +from .types.clear_cache_response import ClearCacheResponse from .types.create_credential_request_credential import CreateCredentialRequestCredential from .types.create_script_response import CreateScriptResponse from .types.credential_response import CredentialResponse @@ -2884,6 +2885,56 @@ class RawSkyvern: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + def clear_workflow_cache( + self, workflow_permanent_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[ClearCacheResponse]: + """ + Clear all cached scripts for a specific workflow. This will trigger script regeneration on subsequent runs. + + Parameters + ---------- + workflow_permanent_id : str + The workflow permanent ID to clear cache for + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ClearCacheResponse] + Successful Response + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/scripts/{jsonable_encoder(workflow_permanent_id)}/cache", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ClearCacheResponse, + parse_obj_as( + type_=ClearCacheResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + def run_sdk_action( self, *, @@ -6013,6 +6064,56 @@ class AsyncRawSkyvern: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + async def clear_workflow_cache( + self, workflow_permanent_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[ClearCacheResponse]: + """ + Clear all cached scripts for a specific workflow. This will trigger script regeneration on subsequent runs. + + Parameters + ---------- + workflow_permanent_id : str + The workflow permanent ID to clear cache for + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ClearCacheResponse] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/scripts/{jsonable_encoder(workflow_permanent_id)}/cache", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ClearCacheResponse, + parse_obj_as( + type_=ClearCacheResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + async def run_sdk_action( self, *, diff --git a/skyvern/client/types/__init__.py b/skyvern/client/types/__init__.py index 1475a88c..4d2d9e80 100644 --- a/skyvern/client/types/__init__.py +++ b/skyvern/client/types/__init__.py @@ -56,6 +56,7 @@ if typing.TYPE_CHECKING: from .browser_session_response import BrowserSessionResponse from .change_tier_response import ChangeTierResponse from .checkout_session_response import CheckoutSessionResponse + from .clear_cache_response import ClearCacheResponse from .click_action import ClickAction from .click_action_data import ClickActionData from .click_context import ClickContext @@ -622,6 +623,7 @@ _dynamic_imports: typing.Dict[str, str] = { "BrowserSessionResponse": ".browser_session_response", "ChangeTierResponse": ".change_tier_response", "CheckoutSessionResponse": ".checkout_session_response", + "ClearCacheResponse": ".clear_cache_response", "ClickAction": ".click_action", "ClickActionData": ".click_action_data", "ClickContext": ".click_context", @@ -1166,6 +1168,7 @@ __all__ = [ "BrowserSessionResponse", "ChangeTierResponse", "CheckoutSessionResponse", + "ClearCacheResponse", "ClickAction", "ClickActionData", "ClickContext", diff --git a/skyvern/client/types/clear_cache_response.py b/skyvern/client/types/clear_cache_response.py new file mode 100644 index 00000000..9d0acc67 --- /dev/null +++ b/skyvern/client/types/clear_cache_response.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ClearCacheResponse(UniversalBaseModel): + """ + Response model for cache clearing operations. + """ + + deleted_count: int = pydantic.Field() + """ + Number of cached entries deleted + """ + + message: str = pydantic.Field() + """ + Status message + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/skyvern/client/types/credential_response.py b/skyvern/client/types/credential_response.py index dc8c303b..5c8b2dbe 100644 --- a/skyvern/client/types/credential_response.py +++ b/skyvern/client/types/credential_response.py @@ -33,6 +33,16 @@ class CredentialResponse(UniversalBaseModel): Name of the credential """ + browser_profile_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Browser profile ID linked to this credential + """ + + tested_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Login page URL used during the credential test + """ + if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: diff --git a/skyvern/client/types/workflow_run.py b/skyvern/client/types/workflow_run.py index 806e57a4..e35d5623 100644 --- a/skyvern/client/types/workflow_run.py +++ b/skyvern/client/types/workflow_run.py @@ -37,6 +37,9 @@ class WorkflowRun(UniversalBaseModel): sequential_key: typing.Optional[str] = None ai_fallback: typing.Optional[bool] = None code_gen: typing.Optional[bool] = None + waiting_for_verification_code: typing.Optional[bool] = None + verification_code_identifier: typing.Optional[str] = None + verification_code_polling_started_at: typing.Optional[dt.datetime] = None queued_at: typing.Optional[dt.datetime] = None started_at: typing.Optional[dt.datetime] = None finished_at: typing.Optional[dt.datetime] = None diff --git a/skyvern/forge/sdk/routes/internal_auth.py b/skyvern/forge/sdk/routes/internal_auth.py index 4362d6de..131622f0 100644 --- a/skyvern/forge/sdk/routes/internal_auth.py +++ b/skyvern/forge/sdk/routes/internal_auth.py @@ -33,12 +33,24 @@ class DiagnosticsResult(NamedTuple): def _is_local_request(request: Request) -> bool: host = request.client.host if request.client else None if not host: + LOG.warning("No client host found in request", client=request.client) return False try: addr = ipaddress.ip_address(host) except ValueError: + LOG.warning("Invalid IP address in request", host=host) return False - return addr.is_loopback or addr.is_private + # Check if request is from Docker host (gateway IP) + # Docker typically uses 172.x.x.x or 192.168.x.x for bridge networks + is_local = addr.is_loopback or addr.is_private + LOG.info( + "Checking if request is local", + host=host, + is_loopback=addr.is_loopback, + is_private=addr.is_private, + is_local=is_local, + ) + return is_local def _require_local_access(request: Request) -> None: diff --git a/uv.lock b/uv.lock index 5f21176e..5bc20980 100644 --- a/uv.lock +++ b/uv.lock @@ -5581,7 +5581,7 @@ wheels = [ [[package]] name = "skyvern" -version = "1.0.18" +version = "1.0.19" source = { editable = "." } dependencies = [ { name = "aioboto3" },