Skip to content

Commit c3f23ba

Browse files
committed
add custom code
1 parent 637ffe3 commit c3f23ba

File tree

2 files changed

+181
-0
lines changed

2 files changed

+181
-0
lines changed

src/kernel/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@
2626
)
2727
from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient
2828
from ._utils._logs import setup_logging as _setup_logging
29+
from .app_framework import (
30+
App,
31+
Browser,
32+
Browsers,
33+
KernelApp,
34+
KernelAction,
35+
KernelContext,
36+
KernelAppRegistry,
37+
browsers,
38+
app_registry,
39+
export_registry,
40+
)
2941

3042
__all__ = [
3143
"types",
@@ -66,6 +78,16 @@
6678
"DEFAULT_CONNECTION_LIMITS",
6779
"DefaultHttpxClient",
6880
"DefaultAsyncHttpxClient",
81+
"KernelContext",
82+
"Browser",
83+
"KernelAction",
84+
"KernelApp",
85+
"KernelAppRegistry",
86+
"Browsers",
87+
"browsers",
88+
"App",
89+
"app_registry",
90+
"export_registry",
6991
]
7092

7193
_setup_logging()

src/kernel/app_framework.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import json
2+
import inspect
3+
import functools
4+
from typing import Any, Dict, List, Union, TypeVar, Callable, Optional
5+
from dataclasses import dataclass
6+
7+
T = TypeVar("T")
8+
9+
10+
# Context definition
11+
@dataclass
12+
class KernelContext:
13+
"""Context object passed to action handlers"""
14+
15+
invocation_id: str
16+
17+
18+
# Browser definition
19+
class Browser:
20+
def __init__(self, cdp_ws_url: str):
21+
self.cdp_ws_url = cdp_ws_url
22+
23+
24+
# Action definition
25+
class KernelAction:
26+
def __init__(self, name: str, handler: Callable[..., Any]):
27+
self.name = name
28+
self.handler = handler
29+
30+
31+
# App class
32+
class KernelApp:
33+
def __init__(self, name: str):
34+
self.name = name
35+
self.actions: Dict[str, KernelAction] = {}
36+
# Register this app in the global registry
37+
_app_registry.register_app(self)
38+
39+
def action(self, name_or_handler: Optional[Union[str, Callable[..., Any]]] = None):
40+
"""Decorator to register an action with the app"""
41+
if name_or_handler is None:
42+
# This is the @app.action() case, which should return the decorator
43+
def decorator(f: Callable[..., Any]):
44+
return self._register_action(f.__name__, f)
45+
46+
return decorator
47+
elif callable(name_or_handler):
48+
# This is the @app.action case (handler passed directly)
49+
return self._register_action(name_or_handler.__name__, name_or_handler)
50+
else:
51+
# This is the @app.action("name") case (name_or_handler is a string)
52+
def decorator(f: Callable[..., Any]):
53+
return self._register_action(name_or_handler, f) # name_or_handler is the name string here
54+
55+
return decorator
56+
57+
def _register_action(self, name: str, handler: Callable[..., Any]) -> Callable[..., Any]:
58+
"""Internal method to register an action"""
59+
60+
@functools.wraps(handler)
61+
def wrapper(*args: Any, **kwargs: Any) -> Any:
62+
# Determine if the original handler accepts context as first argument
63+
sig = inspect.signature(handler)
64+
param_names = list(sig.parameters.keys())
65+
param_count = len(param_names)
66+
67+
if param_count == 1:
68+
actual_input = None
69+
# The handler only takes input
70+
if len(args) > 0: # Prioritize args if context was implicitly passed
71+
# If context (args[0]) and input (args[1]) were provided, or just input (args[0])
72+
actual_input = args[1] if len(args) > 1 else args[0]
73+
elif kwargs:
74+
# Attempt to find the single expected kwarg
75+
if param_names: # Should always be true if param_count == 1
76+
param_name = param_names[0]
77+
if param_name in kwargs:
78+
actual_input = kwargs[param_name]
79+
elif kwargs: # Fallback if name doesn't match but kwargs exist
80+
actual_input = next(iter(kwargs.values()))
81+
elif kwargs: # param_names is empty but kwargs exist (unlikely for param_count==1)
82+
actual_input = next(iter(kwargs.values()))
83+
# If no args/kwargs, actual_input remains None, handler might raise error or accept None
84+
return handler(actual_input)
85+
else: # param_count == 0 or param_count > 1
86+
# Handler takes context and input (or more), or no args
87+
return handler(*args, **kwargs)
88+
89+
action = KernelAction(name=name, handler=wrapper)
90+
self.actions[name] = action
91+
return wrapper
92+
93+
def get_actions(self) -> List[KernelAction]:
94+
"""Get all actions for this app"""
95+
return list(self.actions.values())
96+
97+
def get_action(self, name: str) -> Optional[KernelAction]:
98+
"""Get an action by name"""
99+
return self.actions.get(name)
100+
101+
def to_dict(self) -> Dict[str, Any]:
102+
"""Export app information without handlers"""
103+
return {"name": self.name, "actions": [{"name": action.name} for action in self.get_actions()]}
104+
105+
106+
# Registry for storing Kernel apps
107+
class KernelAppRegistry:
108+
def __init__(self):
109+
self.apps: Dict[str, KernelApp] = {}
110+
111+
def register_app(self, app: KernelApp) -> None:
112+
self.apps[app.name] = app
113+
114+
def get_apps(self) -> List[KernelApp]:
115+
return list(self.apps.values())
116+
117+
def get_app_by_name(self, name: str) -> Optional[KernelApp]:
118+
return self.apps.get(name)
119+
120+
def export_json(self, entrypoint_relpath: str) -> str:
121+
"""Export the registry as JSON"""
122+
apps = [app.to_dict() for app in self.get_apps()]
123+
return json.dumps({"apps": apps, "entrypoint": entrypoint_relpath}, indent=2)
124+
125+
126+
# Create singleton registry for apps
127+
_app_registry = KernelAppRegistry()
128+
129+
130+
# Browser management
131+
class Browsers:
132+
@staticmethod
133+
def create(invocation_id: str) -> Browser:
134+
"""Create a new browser instance"""
135+
# TODO: The cdp_ws_url is hardcoded here. This might need to be
136+
# dynamically configured or provided by the Kernel platform.
137+
return Browser(
138+
cdp_ws_url="wss://floral-morning-3s0r8t7x.iad-prod-apiukp-0.onkernel.app:9222/devtools/browser/4dc1cbf6-be0e-4612-bba3-8e399d9638c7"
139+
)
140+
141+
142+
# Create browser management instance
143+
browsers = Browsers()
144+
145+
146+
# Create a simple function for creating apps
147+
def App(name: str) -> KernelApp:
148+
"""Create a new Kernel app"""
149+
return KernelApp(name)
150+
151+
152+
# Export the app registry for boot loader
153+
app_registry = _app_registry
154+
155+
156+
# Function to export registry as JSON
157+
def export_registry(entrypoint_relpath: str) -> str:
158+
"""Export the registry as JSON"""
159+
return _app_registry.export_json(entrypoint_relpath)

0 commit comments

Comments
 (0)