Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion packages/narada-core/src/narada_core/actions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,35 @@ class GetUrlResponse(BaseModel):
url: str


class PromptForUserInputVariable(BaseModel):
name: str
type: Literal["string", "number", "boolean", "enum", "dataTable", "object", "array"]
required: bool
enum_values: list[str] | None = None


class PromptForUserInputRequest(BaseModel):
name: Literal["prompt_for_user_input"] = "prompt_for_user_input"
step_id: str
variables: list[PromptForUserInputVariable]


class PromptForUserInputResponse(BaseModel):
values_by_name: dict[str, Any]


class UserApprovalRequest(BaseModel):
name: Literal["user_approval"] = "user_approval"
step_id: str
prompt_message: str
approve_label: str
reject_label: str


class UserApprovalResponse(BaseModel):
approved: bool


type ExtensionActionRequest = (
AgenticSelectorRequest
| AgenticMouseActionRequest
Expand All @@ -590,10 +619,12 @@ class GetUrlResponse(BaseModel):
| GetSimplifiedHtmlRequest
| GetScreenshotRequest
| GetUrlRequest
| PromptForUserInputRequest
| UserApprovalRequest
)


class ExtensionActionResponse(BaseModel):
status: Literal["success", "error"]
status: Literal["success", "error", "aborted"]
error: str | None = None
data: str | None = None
4 changes: 4 additions & 0 deletions packages/narada-core/src/narada_core/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,7 @@ class NaradaExtensionUnauthenticatedError(NaradaError):

class NaradaInitializationError(NaradaError):
pass


class UserAbortedError(Exception):
pass
45 changes: 45 additions & 0 deletions packages/narada-pyodide/src/narada/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,22 @@
GetUrlResponse,
GoToUrlRequest,
PrintMessageRequest,
PromptForUserInputRequest,
PromptForUserInputResponse,
PromptForUserInputVariable,
ReadGoogleSheetRequest,
ReadGoogleSheetResponse,
RecordedClick,
UserApprovalRequest,
UserApprovalResponse,
WriteGoogleSheetRequest,
parse_action_trace,
)
from narada_core.errors import (
NaradaAgentTimeoutError_INTERNAL_DO_NOT_USE,
NaradaError,
NaradaTimeoutError,
UserAbortedError,
)
from narada_core.models import (
Agent,
Expand Down Expand Up @@ -479,6 +485,43 @@ async def print_message(self, *, message: str, timeout: int | None = None) -> No
PrintMessageRequest(message=message), timeout=timeout
)

async def prompt_for_user_input(
self,
*,
step_id: str,
variables: list[PromptForUserInputVariable],
timeout: int | None = None,
) -> dict[str, Any]:
"""Prompts the user for one or more input values in the extension UI."""
result = await self._run_extension_action(
PromptForUserInputRequest(step_id=step_id, variables=variables),
PromptForUserInputResponse,
timeout=timeout,
)
return result.values_by_name

async def user_approval(
self,
*,
step_id: str,
prompt_message: str,
approve_label: str,
reject_label: str,
timeout: int | None = None,
) -> bool:
"""Prompts the user to approve or reject in the extension UI."""
result = await self._run_extension_action(
UserApprovalRequest(
step_id=step_id,
prompt_message=prompt_message,
approve_label=approve_label,
reject_label=reject_label,
),
UserApprovalResponse,
timeout=timeout,
)
return result.approved

async def read_google_sheet(
self,
*,
Expand Down Expand Up @@ -601,6 +644,8 @@ async def _run_extension_action(
response = ExtensionActionResponse.model_validate(resp_json)
if response.status == "error":
raise NaradaError(response.error)
if response.status == "aborted":
raise UserAbortedError

if response_model is None:
return None
Expand Down
2 changes: 2 additions & 0 deletions packages/narada/src/narada/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
NaradaInitializationError,
NaradaTimeoutError,
NaradaUnsupportedBrowserError,
UserAbortedError,
)
from narada_core.models import Agent, File, Response, ResponseContent

Expand Down Expand Up @@ -34,4 +35,5 @@
"render_html",
"Response",
"ResponseContent",
"UserAbortedError",
]
45 changes: 45 additions & 0 deletions packages/narada/src/narada/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,22 @@
GetUrlResponse,
GoToUrlRequest,
PrintMessageRequest,
PromptForUserInputRequest,
PromptForUserInputResponse,
PromptForUserInputVariable,
ReadGoogleSheetRequest,
ReadGoogleSheetResponse,
RecordedClick,
UserApprovalRequest,
UserApprovalResponse,
WriteGoogleSheetRequest,
parse_action_trace,
)
from narada_core.errors import (
NaradaAgentTimeoutError_INTERNAL_DO_NOT_USE,
NaradaError,
NaradaTimeoutError,
UserAbortedError,
)
from narada_core.models import (
Agent,
Expand Down Expand Up @@ -540,6 +546,43 @@ async def print_message(self, *, message: str, timeout: int | None = None) -> No
PrintMessageRequest(message=message), timeout=timeout
)

async def prompt_for_user_input(
self,
*,
step_id: str,
variables: list[PromptForUserInputVariable],
timeout: int | None = None,
) -> dict[str, Any]:
"""Prompts the user for one or more input values in the extension UI."""
result = await self._run_extension_action(
PromptForUserInputRequest(step_id=step_id, variables=variables),
PromptForUserInputResponse,
timeout=timeout,
)
return result.values_by_name

async def user_approval(
self,
*,
step_id: str,
prompt_message: str,
approve_label: str,
reject_label: str,
timeout: int | None = None,
) -> bool:
"""Prompts the user to approve or reject in the extension UI."""
result = await self._run_extension_action(
UserApprovalRequest(
step_id=step_id,
prompt_message=prompt_message,
approve_label=approve_label,
reject_label=reject_label,
),
UserApprovalResponse,
timeout=timeout,
)
return result.approved

async def read_google_sheet(
self,
*,
Expand Down Expand Up @@ -645,6 +688,8 @@ async def _run_extension_action(
response = ExtensionActionResponse.model_validate(resp_json)
if response.status == "error":
raise NaradaError(response.error)
if response.status == "aborted":
raise UserAbortedError

if response_model is None:
return None
Expand Down
Loading