feat: Support .ducklake() and .datatable() in agent workers (#8697)

* Update clients to check for agent workers

* fixes

* typescript uses 127.0.0.1

* Refresh system prompts

* fix: check both localhost and 127.0.0.1 in workerHasInternalServer detection

Both Python and TypeScript clients now check for both hostnames to avoid
silent breakage if BASE_INTERNAL_URL uses one or the other. Also adds
return type annotation to the Python method.

Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>

* refresh system prompts

* nit localhost regex boundary

* fix: use provider.language instead of undefined bare language in sqlUtils

The language variable was referenced as a bare identifier in the fetch
calls, resolving to undefined at runtime instead of reading from
provider.language.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Diego Imbert
2026-04-04 00:34:34 +02:00
committed by GitHub
parent ef74a1bb4d
commit fda68a72e5
13 changed files with 83 additions and 12 deletions
+14 -1
View File
@@ -262,6 +262,8 @@ const result: S3Object = await wmill.writeS3File(
Import: import * as wmill from 'windmill-client'
workerHasInternalServer(): boolean
/**
* Initialize the Windmill client with authentication token and base URL
* @param token - Authentication token (defaults to WM_TOKEN env variable)
@@ -925,6 +927,8 @@ const result: S3Object = await wmill.writeS3File(
Import: import * as wmill from 'windmill-client'
workerHasInternalServer(): boolean
/**
* Initialize the Windmill client with authentication token and base URL
* @param token - Authentication token (defaults to WM_TOKEN env variable)
@@ -1651,6 +1655,8 @@ const result: S3Object = await wmill.writeS3File(
Import: import * as wmill from 'windmill-client'
workerHasInternalServer(): boolean
/**
* Initialize the Windmill client with authentication token and base URL
* @param token - Authentication token (defaults to WM_TOKEN env variable)
@@ -2591,6 +2597,8 @@ export async function preprocessor(event: Event) {
Import: import * as wmill from 'windmill-client'
workerHasInternalServer(): boolean
/**
* Initialize the Windmill client with authentication token and base URL
* @param token - Authentication token (defaults to WM_TOKEN env variable)
@@ -3433,6 +3441,8 @@ result: S3Object = wmill.write_s3_file(
Import: import wmill
def worker_has_internal_server() -> bool
def get_mocked_api() -> Optional[dict]
# Get the HTTP client instance.
@@ -3497,7 +3507,10 @@ def run_script_by_path(path: str, args: dict = None, timeout: dt.timedelta | int
# Run script by hash synchronously and return its result.
def run_script_by_hash(hash_: str, args: dict = None, timeout: dt.timedelta | int | float | None = None, verbose: bool = False, cleanup: bool = True, assert_result_is_not_none: bool = False) -> Any
# Run a script on the current worker without creating a job
# Run a script on the current worker without creating a job.
#
# On agent workers (no internal server), falls back to running a normal
# preview job and waiting for the result.
def run_inline_script_preview(content: str, language: str, args: dict = None) -> Any
# Wait for a job to complete and return its result.
+14 -2
View File
@@ -66,6 +66,11 @@ class Windmill:
f"workspace required as an argument or as WM_WORKSPACE environment variable"
)
def worker_has_internal_server(self) -> bool:
return bool(
re.match(r"^https?://(localhost|127\.0\.0\.1)(:|/|$)", self.base_url or "")
)
def get_mocked_api(self) -> Optional[dict]:
mocked_path = os.environ.get("WM_MOCKED_API_FILE")
if not mocked_path:
@@ -326,8 +331,15 @@ class Windmill:
language: str,
args: dict = None,
) -> Any:
"""Run a script on the current worker without creating a job"""
endpoint = f"/w/{self.workspace}/jobs/run_inline/preview"
"""Run a script on the current worker without creating a job.
On agent workers (no internal server), falls back to running a normal
preview job and waiting for the result.
"""
if self.worker_has_internal_server():
endpoint = f"/w/{self.workspace}/jobs/run_inline/preview"
else:
endpoint = f"/w/{self.workspace}/jobs/run_wait_result/preview"
body = {
"content": content,
"language": language,
+8 -1
View File
@@ -153,6 +153,8 @@ export const SDK_TYPESCRIPT = `# TypeScript SDK (windmill-client)
Import: import * as wmill from 'windmill-client'
workerHasInternalServer(): boolean
/**
* Initialize the Windmill client with authentication token and base URL
* @param token - Authentication token (defaults to WM_TOKEN env variable)
@@ -690,6 +692,8 @@ export const SDK_PYTHON = `# Python SDK (wmill)
Import: import wmill
def worker_has_internal_server() -> bool
def get_mocked_api() -> Optional[dict]
# Get the HTTP client instance.
@@ -754,7 +758,10 @@ def run_script_by_path(path: str, args: dict = None, timeout: dt.timedelta | int
# Run script by hash synchronously and return its result.
def run_script_by_hash(hash_: str, args: dict = None, timeout: dt.timedelta | int | float | None = None, verbose: bool = False, cleanup: bool = True, assert_result_is_not_none: bool = False) -> Any
# Run a script on the current worker without creating a job
# Run a script on the current worker without creating a job.
#
# On agent workers (no internal server), falls back to running a normal
# preview job and waiting for the result.
def run_inline_script_preview(content: str, language: str, args: dict = None) -> Any
# Wait for a job to complete and return its result.
+8 -1
View File
@@ -1213,6 +1213,8 @@ SELECT * FROM users WHERE name = ? AND age > ?;
Import: import * as wmill from 'windmill-client'
workerHasInternalServer(): boolean
/**
* Initialize the Windmill client with authentication token and base URL
* @param token - Authentication token (defaults to WM_TOKEN env variable)
@@ -1750,6 +1752,8 @@ ducklake(name: string = "main"): SqlTemplateFunction
Import: import wmill
def worker_has_internal_server() -> bool
def get_mocked_api() -> Optional[dict]
# Get the HTTP client instance.
@@ -1814,7 +1818,10 @@ def run_script_by_path(path: str, args: dict = None, timeout: dt.timedelta | int
# Run script by hash synchronously and return its result.
def run_script_by_hash(hash_: str, args: dict = None, timeout: dt.timedelta | int | float | None = None, verbose: bool = False, cleanup: bool = True, assert_result_is_not_none: bool = False) -> Any
# Run a script on the current worker without creating a job
# Run a script on the current worker without creating a job.
#
# On agent workers (no internal server), falls back to running a normal
# preview job and waiting for the result.
def run_inline_script_preview(content: str, language: str, args: dict = None) -> Any
# Wait for a job to complete and return its result.
+6 -1
View File
@@ -2,6 +2,8 @@
Import: import wmill
def worker_has_internal_server() -> bool
def get_mocked_api() -> Optional[dict]
# Get the HTTP client instance.
@@ -66,7 +68,10 @@ def run_script_by_path(path: str, args: dict = None, timeout: dt.timedelta | int
# Run script by hash synchronously and return its result.
def run_script_by_hash(hash_: str, args: dict = None, timeout: dt.timedelta | int | float | None = None, verbose: bool = False, cleanup: bool = True, assert_result_is_not_none: bool = False) -> Any
# Run a script on the current worker without creating a job
# Run a script on the current worker without creating a job.
#
# On agent workers (no internal server), falls back to running a normal
# preview job and waiting for the result.
def run_inline_script_preview(content: str, language: str, args: dict = None) -> Any
# Wait for a job to complete and return its result.
@@ -2,6 +2,8 @@
Import: import * as wmill from 'windmill-client'
workerHasInternalServer(): boolean
/**
* Initialize the Windmill client with authentication token and base URL
* @param token - Authentication token (defaults to WM_TOKEN env variable)
@@ -131,6 +131,8 @@ const result: S3Object = await wmill.writeS3File(
Import: import * as wmill from 'windmill-client'
workerHasInternalServer(): boolean
/**
* Initialize the Windmill client with authentication token and base URL
* @param token - Authentication token (defaults to WM_TOKEN env variable)
@@ -129,6 +129,8 @@ const result: S3Object = await wmill.writeS3File(
Import: import * as wmill from 'windmill-client'
workerHasInternalServer(): boolean
/**
* Initialize the Windmill client with authentication token and base URL
* @param token - Authentication token (defaults to WM_TOKEN env variable)
@@ -135,6 +135,8 @@ const result: S3Object = await wmill.writeS3File(
Import: import * as wmill from 'windmill-client'
workerHasInternalServer(): boolean
/**
* Initialize the Windmill client with authentication token and base URL
* @param token - Authentication token (defaults to WM_TOKEN env variable)
@@ -96,6 +96,8 @@ export async function preprocessor(event: Event) {
Import: import * as wmill from 'windmill-client'
workerHasInternalServer(): boolean
/**
* Initialize the Windmill client with authentication token and base URL
* @param token - Authentication token (defaults to WM_TOKEN env variable)
@@ -137,6 +137,8 @@ result: S3Object = wmill.write_s3_file(
Import: import wmill
def worker_has_internal_server() -> bool
def get_mocked_api() -> Optional[dict]
# Get the HTTP client instance.
@@ -201,7 +203,10 @@ def run_script_by_path(path: str, args: dict = None, timeout: dt.timedelta | int
# Run script by hash synchronously and return its result.
def run_script_by_hash(hash_: str, args: dict = None, timeout: dt.timedelta | int | float | None = None, verbose: bool = False, cleanup: bool = True, assert_result_is_not_none: bool = False) -> Any
# Run a script on the current worker without creating a job
# Run a script on the current worker without creating a job.
#
# On agent workers (no internal server), falls back to running a normal
# preview job and waiting for the result.
def run_inline_script_preview(content: str, language: str, args: dict = None) -> Any
# Wait for a job to complete and return its result.
+4
View File
@@ -43,6 +43,10 @@ export const SHARED_FOLDER = "/shared";
let mockedApi: MockedApi | undefined = undefined;
export function workerHasInternalServer(): boolean {
return /^https?:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/.test(OpenAPI.BASE ?? "");
}
/**
* Initialize the Windmill client with authentication token and base URL
* @param token - Authentication token (defaults to WM_TOKEN env variable)
+13 -5
View File
@@ -1,4 +1,4 @@
import { getWorkspace } from "./client";
import { getWorkspace, workerHasInternalServer } from "./client";
import { JobService } from "./services.gen";
type ResultCollection =
@@ -223,10 +223,18 @@ function buildSqlTemplateFunction(provider: SqlProvider): SqlTemplateFunction {
if (resultCollection)
content = `-- result_collection=${resultCollection}\n${content}`;
try {
let result = await JobService.runScriptPreviewInline({
workspace: getWorkspace(),
requestBody: { args, content, language: provider.language },
});
let result;
if (workerHasInternalServer()) {
result = await JobService.runScriptPreviewInline({
workspace: getWorkspace(),
requestBody: { args, content, language: provider.language },
});
} else {
result = await JobService.runScriptPreviewAndWaitResult({
workspace: getWorkspace(),
requestBody: { args, content, language: provider.language },
});
}
return result as SqlResult<any, ResultCollectionT>;
} catch (e: any) {
let err = e;