edgeone 1.6.4 → 1.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,25 +13,16 @@ from typing import Any, Callable
13
13
  from urllib.parse import parse_qs
14
14
  import httpx
15
15
 
16
- # --- Observability bootstrap (deferred for fast cold start) ---
17
- # Moved to _ensure_observability() called by background preload thread
18
- # or lazily on first request. This avoids blocking startup with OTel imports.
19
- _obs_initialized = False
20
-
21
-
22
- def _ensure_observability():
23
- """Initialize observability (idempotent). Called by preload thread or first request."""
24
- global _obs_initialized
25
- if _obs_initialized:
26
- return
27
- _obs_initialized = True
28
- _obs_entries_raw = os.environ.get("AGENT_OBSERVABILITY_ENTRIES", "")
29
- if _obs_entries_raw:
30
- try:
31
- from _platform._observability import setup as _obs_setup
32
- _obs_setup(json.loads(_obs_entries_raw))
33
- except Exception as _obs_err:
34
- print(f"[observability] setup failed: {_obs_err}", file=sys.stderr, flush=True)
16
+ # --- Observability bootstrap (must run before user code imports) ---
17
+ # Reads entry names from AGENT_OBSERVABILITY_ENTRIES env var (set by main.py
18
+ # or the dev runner). Idempotent safe to call multiple times.
19
+ _obs_entries_raw = os.environ.get("AGENT_OBSERVABILITY_ENTRIES", "")
20
+ if _obs_entries_raw:
21
+ try:
22
+ from _platform._observability import setup as _obs_setup
23
+ _obs_setup(json.loads(_obs_entries_raw))
24
+ except Exception as _obs_err:
25
+ print(f"[observability] setup failed: {_obs_err}", file=sys.stderr, flush=True)
35
26
 
36
27
  from .store import InMemoryStore, BlobBackedStore
37
28
  from .runtime import AgentRuntime, RouteEntry
@@ -464,58 +455,22 @@ _registry: dict[str, RouteEntry] = {}
464
455
  # 加载失败的路由:route_path → 失败原因。404 响应时回显,避免静默 404 难以排查。
465
456
  _failed_routes: dict[str, str] = {}
466
457
 
467
-
468
- # --- Lazy route loading for fast cold start ---
469
- # Routes are NOT imported at startup. Instead, each route module is loaded
470
- # on first access (or by the background preload thread). This avoids blocking
471
- # the health check with heavy imports (langchain, langgraph, pydantic, etc.).
472
-
473
- class _LazyRouteEntry:
474
- """Lazy-loading wrapper for RouteEntry. Defers importlib.import_module() to first access."""
475
-
476
- __slots__ = ('_module_path', '_is_index', '_handler', '_loaded', '_load_error')
477
-
478
- def __init__(self, module_path: str, is_index: bool):
479
- self._module_path = module_path
480
- self._is_index = is_index
481
- self._handler = None
482
- self._loaded = False
483
- self._load_error: str | None = None
484
-
485
- @property
486
- def handler(self):
487
- if not self._loaded:
488
- self._do_load()
489
- if self._load_error:
490
- raise ImportError(self._load_error)
491
- return self._handler
492
-
493
- @property
494
- def is_index(self):
495
- return self._is_index
496
-
497
- @property
498
- def module_path(self):
499
- return self._module_path
500
-
501
- def _do_load(self):
502
- """Actually import the module and resolve the handler."""
503
- try:
504
- mod = importlib.import_module(self._module_path)
505
- handler = getattr(mod, "handler", None)
506
- if handler is None:
507
- self._load_error = f"module '{self._module_path}' has no 'handler' function"
508
- else:
509
- self._handler = handler
510
- except Exception as e:
511
- self._load_error = f"failed to import '{self._module_path}': {e}"
512
- self._loaded = True
513
-
514
-
515
458
  for route_path, info in _route_table_raw.items():
516
459
  module_path = info["module"]
517
460
  is_index = info.get("isIndex", False)
518
- _registry[route_path] = _LazyRouteEntry(module_path, is_index)
461
+ try:
462
+ mod = importlib.import_module(module_path)
463
+ handler = getattr(mod, "handler", None)
464
+ if handler is None:
465
+ reason = f"module '{module_path}' has no 'handler' function"
466
+ print(f"[agent-python] WARNING: {reason}, skipping", file=sys.stderr, flush=True)
467
+ _failed_routes[route_path] = reason
468
+ continue
469
+ _registry[route_path] = RouteEntry(handler=handler, is_index=is_index, module_path=module_path)
470
+ except Exception as e:
471
+ reason = f"failed to import '{module_path}': {e}"
472
+ print(f"[agent-python] ERROR loading {module_path}: {e}", file=sys.stderr, flush=True)
473
+ _failed_routes[route_path] = reason
519
474
 
520
475
  _runtime = AgentRuntime(
521
476
  registry=_registry,
@@ -525,46 +480,13 @@ _runtime = AgentRuntime(
525
480
  failed_routes=_failed_routes,
526
481
  )
527
482
 
528
- print(f"[agent-python] Registered {len(_registry)} routes (lazy), timeout={_timeout}s", file=sys.stderr, flush=True)
529
-
530
-
531
- # --- Background preload thread ---
532
- # After uvicorn binds the port and health check passes, this thread
533
- # pre-imports all route modules in the background. If a request arrives
534
- # before preload completes, the lazy handler property will block and
535
- # import synchronously (no failure, just slower first request).
536
-
537
- import threading
538
-
539
-
540
- def _background_preload():
541
- """Preload routes + observability in background after startup."""
542
- import time
543
- time.sleep(0.05) # yield to let uvicorn finish port binding
544
-
545
- # 1. Initialize observability first (lighter, needed for spans)
546
- _ensure_observability()
547
-
548
- # 2. Trigger lazy loading for all routes
549
- for route_path, entry in _registry.items():
550
- try:
551
- _ = entry.handler
552
- except Exception as e:
553
- _failed_routes[route_path] = str(e)
554
- print(f"[agent-python] ERROR loading {entry.module_path}: {e}", file=sys.stderr, flush=True)
555
-
556
- loaded_count = sum(1 for e in _registry.values() if e._loaded and e._load_error is None)
557
- if _failed_routes:
558
- print(
559
- f"[agent-python] Preload done: {loaded_count}/{len(_registry)} routes loaded, "
560
- f"{len(_failed_routes)} failed: {', '.join(_failed_routes.keys())}",
561
- file=sys.stderr, flush=True,
562
- )
563
- else:
564
- print(f"[agent-python] Preload done: {loaded_count}/{len(_registry)} routes loaded", file=sys.stderr, flush=True)
565
-
566
-
567
- threading.Thread(target=_background_preload, daemon=True, name="agent-preload").start()
483
+ print(f"[agent-python] Loaded {len(_registry)} routes, timeout={_timeout}s", file=sys.stderr, flush=True)
484
+ if _failed_routes:
485
+ print(
486
+ f"[agent-python] WARNING: {len(_failed_routes)} route(s) failed to load: "
487
+ f"{', '.join(_failed_routes.keys())}",
488
+ file=sys.stderr, flush=True,
489
+ )
568
490
 
569
491
 
570
492
  # --- ASGI app ---
@@ -589,11 +511,6 @@ async def app(scope: dict, receive, send) -> None:
589
511
  if scope["type"] != "http":
590
512
  return
591
513
 
592
- # Ensure observability is initialized before processing requests.
593
- # Usually already done by background preload, but if a request arrives
594
- # before preload completes, this ensures instrumentation is active.
595
- _ensure_observability()
596
-
597
514
  # Read body
598
515
  body_parts = []
599
516
  while True:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edgeone",
3
- "version": "1.6.4",
3
+ "version": "1.6.5",
4
4
  "description": "Command-line interface for TencentCloud Pages Functions",
5
5
  "bin": {
6
6
  "edgeone": "./edgeone-bin/edgeone.js"