openhands-agent-server 1.36.0__tar.gz → 1.37.0__tar.gz
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.
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/PKG-INFO +3 -1
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/api.py +85 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/config.py +87 -1
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/conversation_service.py +671 -194
- openhands_agent_server-1.37.0/openhands/agent_server/credential_binding.py +107 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/event_router.py +6 -2
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/event_service.py +216 -1
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/init_router.py +27 -1
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/mcp_oauth_store.py +7 -2
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/persistence/models.py +9 -2
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/persistence/store.py +118 -16
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/plugins_router.py +47 -11
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/plugins_service.py +115 -18
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/server_details_router.py +1 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/settings_router.py +7 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/skills_service.py +12 -2
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/sockets.py +1 -3
- openhands_agent_server-1.37.0/openhands/agent_server/telemetry/__init__.py +88 -0
- openhands_agent_server-1.37.0/openhands/agent_server/telemetry/factory.py +148 -0
- openhands_agent_server-1.37.0/openhands/agent_server/telemetry/http_exporter.py +83 -0
- openhands_agent_server-1.37.0/openhands/agent_server/telemetry/models.py +315 -0
- openhands_agent_server-1.37.0/openhands/agent_server/telemetry/policy.py +216 -0
- openhands_agent_server-1.37.0/openhands/agent_server/telemetry/posthog_exporter.py +93 -0
- openhands_agent_server-1.37.0/openhands/agent_server/telemetry/sanitizer.py +356 -0
- openhands_agent_server-1.37.0/openhands/agent_server/telemetry/service.py +243 -0
- openhands_agent_server-1.37.0/openhands/agent_server/telemetry/sink.py +304 -0
- openhands_agent_server-1.37.0/openhands/agent_server/telemetry/subscriber.py +305 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands_agent_server.egg-info/PKG-INFO +3 -1
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands_agent_server.egg-info/SOURCES.txt +22 -2
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands_agent_server.egg-info/requires.txt +3 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/pyproject.toml +8 -1
- openhands_agent_server-1.36.0/openhands/agent_server/event_compat.py +0 -50
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/__init__.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/__main__.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/_secrets_exposure.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/agent_profiles_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/auth_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/bash_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/bash_service.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/conversation_lease.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/conversation_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/dependencies.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/desktop_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/desktop_service.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/docker/Dockerfile +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/docker/build.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/docker/wallpaper.svg +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/env_parser.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/file_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/git_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/hooks_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/hooks_service.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/llm_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/logging_config.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/mcp_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/middleware.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/models.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/openai/__init__.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/openai/models.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/openai/router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/openai/service.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/openapi.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/persistence/__init__.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/profiles_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/pub_sub.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/py.typed +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/skills_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/sub_agents_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/tool_preload_service.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/tool_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/utils.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/vscode_extensions/openhands-settings/extension.js +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/vscode_extensions/openhands-settings/package.json +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/vscode_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/vscode_service.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/workspace_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/workspaces_router.py +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands_agent_server.egg-info/dependency_links.txt +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands_agent_server.egg-info/entry_points.txt +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands_agent_server.egg-info/top_level.txt +0 -0
- {openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-agent-server
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.37.0
|
|
4
4
|
Summary: OpenHands Agent Server - REST/WebSocket interface for OpenHands AI Agent
|
|
5
5
|
Project-URL: Source, https://github.com/OpenHands/software-agent-sdk
|
|
6
6
|
Project-URL: Homepage, https://github.com/OpenHands/software-agent-sdk
|
|
@@ -18,3 +18,5 @@ Requires-Dist: sqlalchemy>=2
|
|
|
18
18
|
Requires-Dist: uvicorn>=0.31.1
|
|
19
19
|
Requires-Dist: websockets>=12
|
|
20
20
|
Requires-Dist: wsproto>=1.2.0
|
|
21
|
+
Provides-Extra: posthog
|
|
22
|
+
Requires-Dist: posthog<7,>=6; extra == "posthog"
|
{openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/api.py
RENAMED
|
@@ -28,6 +28,9 @@ from openhands.agent_server.conversation_router import conversation_router
|
|
|
28
28
|
from openhands.agent_server.conversation_service import (
|
|
29
29
|
get_default_conversation_service,
|
|
30
30
|
)
|
|
31
|
+
from openhands.agent_server.credential_binding import (
|
|
32
|
+
router as credential_binding_router,
|
|
33
|
+
)
|
|
31
34
|
from openhands.agent_server.dependencies import (
|
|
32
35
|
check_session_api_key,
|
|
33
36
|
check_workspace_session,
|
|
@@ -61,6 +64,23 @@ from openhands.agent_server.settings_router import settings_router
|
|
|
61
64
|
from openhands.agent_server.skills_router import skills_router
|
|
62
65
|
from openhands.agent_server.sockets import sockets_router
|
|
63
66
|
from openhands.agent_server.sub_agents_router import sub_agents_router
|
|
67
|
+
from openhands.agent_server.telemetry import (
|
|
68
|
+
build_telemetry_sink,
|
|
69
|
+
emit_server_started,
|
|
70
|
+
emit_server_stopped,
|
|
71
|
+
get_event_factory,
|
|
72
|
+
get_telemetry_sink,
|
|
73
|
+
shutdown_telemetry_sink,
|
|
74
|
+
)
|
|
75
|
+
from openhands.agent_server.telemetry.factory import (
|
|
76
|
+
DISTINCT_ID_HEADER,
|
|
77
|
+
distinct_id_from_header,
|
|
78
|
+
)
|
|
79
|
+
from openhands.agent_server.telemetry.models import (
|
|
80
|
+
EventName,
|
|
81
|
+
RequestFailedProperties,
|
|
82
|
+
)
|
|
83
|
+
from openhands.agent_server.telemetry.sanitizer import normalize_exception
|
|
64
84
|
from openhands.agent_server.tool_preload_service import get_tool_preload_service
|
|
65
85
|
from openhands.agent_server.tool_router import tool_router
|
|
66
86
|
from openhands.agent_server.vscode_router import vscode_router
|
|
@@ -133,6 +153,13 @@ async def api_lifespan(api: FastAPI) -> AsyncIterator[None]:
|
|
|
133
153
|
|
|
134
154
|
config: Config = api.state.config
|
|
135
155
|
deferred = config.deferred_init
|
|
156
|
+
|
|
157
|
+
# Deferred pods boot with telemetry disabled and are rebuilt by
|
|
158
|
+
# InitService, so they emit `server_started` there instead.
|
|
159
|
+
api.state.telemetry_sink = await build_telemetry_sink(config)
|
|
160
|
+
if not deferred:
|
|
161
|
+
emit_server_started()
|
|
162
|
+
|
|
136
163
|
vscode_service = get_vscode_service()
|
|
137
164
|
desktop_service = get_desktop_service()
|
|
138
165
|
tool_preload_service = get_tool_preload_service()
|
|
@@ -265,12 +292,67 @@ async def api_lifespan(api: FastAPI) -> AsyncIterator[None]:
|
|
|
265
292
|
|
|
266
293
|
await stop_stateless_services()
|
|
267
294
|
finally:
|
|
295
|
+
# Outer finally so a startup failure cannot leak the drain task, and
|
|
296
|
+
# after `async with service` so terminal events are still accepted.
|
|
297
|
+
emit_server_stopped()
|
|
298
|
+
await shutdown_telemetry_sink()
|
|
299
|
+
|
|
268
300
|
if tmux_tmpdir_was_defaulted and os.environ.get("TMUX_TMPDIR") == str(
|
|
269
301
|
tmux_tmpdir
|
|
270
302
|
):
|
|
271
303
|
os.environ.pop("TMUX_TMPDIR", None)
|
|
272
304
|
|
|
273
305
|
|
|
306
|
+
def _emit_request_failed(request: Request, exc: Exception, error_id: str) -> None:
|
|
307
|
+
"""Report an unhandled 5xx as a sanitized diagnostic event.
|
|
308
|
+
|
|
309
|
+
Sends the *route template* (``/api/conversations/{conversation_id}``)
|
|
310
|
+
rather than ``request.url.path``, which embeds real identifiers. Fully
|
|
311
|
+
defensive: an error in the telemetry path must not replace the 500 the
|
|
312
|
+
caller is already getting with a different failure.
|
|
313
|
+
"""
|
|
314
|
+
try:
|
|
315
|
+
sink = get_telemetry_sink()
|
|
316
|
+
if not sink.enabled:
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
factory = get_event_factory()
|
|
320
|
+
if factory is None:
|
|
321
|
+
return
|
|
322
|
+
|
|
323
|
+
route = request.scope.get("route")
|
|
324
|
+
route_template = getattr(route, "path", None)
|
|
325
|
+
if not isinstance(route_template, str) or not route_template:
|
|
326
|
+
# Unmatched route: reporting the raw path could leak identifiers.
|
|
327
|
+
route_template = "/unmatched"
|
|
328
|
+
|
|
329
|
+
method = request.method.upper()
|
|
330
|
+
if method not in ("GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"):
|
|
331
|
+
return
|
|
332
|
+
|
|
333
|
+
fingerprint = normalize_exception(exc)
|
|
334
|
+
# Attribute to the frontend's analytics identity when it supplied one;
|
|
335
|
+
# request-scoped activity has no conversation user_id otherwise.
|
|
336
|
+
distinct_id = distinct_id_from_header(request.headers.get(DISTINCT_ID_HEADER))
|
|
337
|
+
sink.emit(
|
|
338
|
+
factory.build(
|
|
339
|
+
EventName.REQUEST_FAILED,
|
|
340
|
+
RequestFailedProperties(
|
|
341
|
+
route_template=route_template,
|
|
342
|
+
method=method, # type: ignore[arg-type]
|
|
343
|
+
status_code=500,
|
|
344
|
+
error_class=fingerprint.error_class,
|
|
345
|
+
error_category=fingerprint.error_category,
|
|
346
|
+
error_fingerprint=fingerprint.error_fingerprint,
|
|
347
|
+
error_id=error_id,
|
|
348
|
+
),
|
|
349
|
+
user_id=distinct_id,
|
|
350
|
+
)
|
|
351
|
+
)
|
|
352
|
+
except Exception:
|
|
353
|
+
logger.debug("Could not emit request_failed telemetry", exc_info=True)
|
|
354
|
+
|
|
355
|
+
|
|
274
356
|
def _get_root_path(config: Config) -> str:
|
|
275
357
|
root_path = ""
|
|
276
358
|
if config.web_url:
|
|
@@ -343,6 +425,7 @@ def _add_api_routes(app: FastAPI) -> None:
|
|
|
343
425
|
api_router = APIRouter(prefix="/api", dependencies=dependencies)
|
|
344
426
|
api_router.include_router(event_router)
|
|
345
427
|
api_router.include_router(conversation_router)
|
|
428
|
+
api_router.include_router(credential_binding_router)
|
|
346
429
|
api_router.include_router(tool_router)
|
|
347
430
|
api_router.include_router(bash_router)
|
|
348
431
|
api_router.include_router(git_router)
|
|
@@ -517,6 +600,7 @@ def _add_exception_handlers(api: FastAPI) -> None:
|
|
|
517
600
|
error_id,
|
|
518
601
|
exc_info=(type(exc), exc, exc.__traceback__),
|
|
519
602
|
)
|
|
603
|
+
_emit_request_failed(request, exc, error_id)
|
|
520
604
|
return JSONResponse(status_code=500, content=content)
|
|
521
605
|
|
|
522
606
|
# Logs full stack trace for any unhandled error that FastAPI would
|
|
@@ -528,6 +612,7 @@ def _add_exception_handlers(api: FastAPI) -> None:
|
|
|
528
612
|
error_id,
|
|
529
613
|
exc_info=(type(exc), exc, exc.__traceback__),
|
|
530
614
|
)
|
|
615
|
+
_emit_request_failed(request, exc, error_id)
|
|
531
616
|
return JSONResponse(status_code=500, content=content)
|
|
532
617
|
|
|
533
618
|
@api.exception_handler(HTTPException)
|
{openhands_agent_server-1.36.0 → openhands_agent_server-1.37.0}/openhands/agent_server/config.py
RENAMED
|
@@ -2,7 +2,7 @@ import json
|
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Any, ClassVar
|
|
5
|
+
from typing import Any, ClassVar, Literal
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel, ConfigDict, Field, SecretStr
|
|
8
8
|
|
|
@@ -111,6 +111,85 @@ class WebhookSpec(BaseModel):
|
|
|
111
111
|
)
|
|
112
112
|
|
|
113
113
|
|
|
114
|
+
TelemetryExporterKind = Literal["none", "posthog", "http"]
|
|
115
|
+
"""Which exporter ships diagnostic events, if any."""
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class TelemetrySpec(BaseModel):
|
|
119
|
+
"""Deployment-supplied product-analytics transport settings.
|
|
120
|
+
|
|
121
|
+
This carries *transport* only. Whether telemetry may be delivered is
|
|
122
|
+
resolved from consent (``misc_settings.telemetry.consent``, optionally
|
|
123
|
+
seeded or overridden by ``OH_TELEMETRY_CONSENT``) — there is no deployment
|
|
124
|
+
"mode" here, and nothing in the agent-server special-cases a hosted
|
|
125
|
+
deployment.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
exporter: TelemetryExporterKind = Field(
|
|
129
|
+
default="none",
|
|
130
|
+
description=(
|
|
131
|
+
"Exporter to use. 'none' (the default) never delivers, and is what "
|
|
132
|
+
"library and headless consumers get. 'posthog' requires the "
|
|
133
|
+
"[posthog] extra. 'http' POSTs sanitized batches to "
|
|
134
|
+
"telemetry_http_endpoint."
|
|
135
|
+
),
|
|
136
|
+
)
|
|
137
|
+
posthog_api_key: SecretStr | None = Field(
|
|
138
|
+
default=None,
|
|
139
|
+
description=(
|
|
140
|
+
"PostHog project API key. Required by the 'posthog' exporter; "
|
|
141
|
+
"without it telemetry stays inactive."
|
|
142
|
+
),
|
|
143
|
+
)
|
|
144
|
+
posthog_host: str = Field(
|
|
145
|
+
default="https://us.i.posthog.com",
|
|
146
|
+
description="PostHog ingestion host.",
|
|
147
|
+
)
|
|
148
|
+
http_endpoint: str | None = Field(
|
|
149
|
+
default=None,
|
|
150
|
+
description=(
|
|
151
|
+
"Endpoint the 'http' exporter POSTs sanitized event batches to. "
|
|
152
|
+
"Intended to front a backend that revalidates auth and consent "
|
|
153
|
+
"before forwarding onward."
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
http_token: SecretStr | None = Field(
|
|
157
|
+
default=None,
|
|
158
|
+
description="Bearer token sent by the 'http' exporter, if required.",
|
|
159
|
+
)
|
|
160
|
+
salt: SecretStr | None = Field(
|
|
161
|
+
default=None,
|
|
162
|
+
description=(
|
|
163
|
+
"Key used to pseudonymize conversation identifiers. Falls back to "
|
|
164
|
+
"a per-process random salt, which keeps pseudonyms stable within a "
|
|
165
|
+
"run but unlinkable across runs."
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
max_queue_size: int = Field(
|
|
169
|
+
default=1000,
|
|
170
|
+
ge=1,
|
|
171
|
+
description=(
|
|
172
|
+
"Upper bound on buffered diagnostic events. The queue is bounded "
|
|
173
|
+
"on ingest: past this many events the oldest are dropped, so a "
|
|
174
|
+
"failing exporter cannot grow memory without limit."
|
|
175
|
+
),
|
|
176
|
+
)
|
|
177
|
+
event_buffer_size: int = Field(
|
|
178
|
+
default=20, ge=1, description="Maximum events per delivery batch."
|
|
179
|
+
)
|
|
180
|
+
flush_delay: float = Field(
|
|
181
|
+
default=30.0,
|
|
182
|
+
gt=0,
|
|
183
|
+
description="Seconds to wait before flushing a partial batch.",
|
|
184
|
+
)
|
|
185
|
+
num_retries: int = Field(
|
|
186
|
+
default=2, ge=0, description="Retries before a batch is dropped."
|
|
187
|
+
)
|
|
188
|
+
retry_delay: float = Field(
|
|
189
|
+
default=5.0, ge=0, description="Base seconds between delivery retries."
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
114
193
|
class Config(BaseModel):
|
|
115
194
|
"""
|
|
116
195
|
Immutable configuration for a server running in local mode.
|
|
@@ -270,6 +349,13 @@ class Config(BaseModel):
|
|
|
270
349
|
"to expire before the first renewal, effectively making it one-shot."
|
|
271
350
|
),
|
|
272
351
|
)
|
|
352
|
+
telemetry: TelemetrySpec = Field(
|
|
353
|
+
default_factory=TelemetrySpec,
|
|
354
|
+
description=(
|
|
355
|
+
"Product-analytics policy. Disabled by default; see TelemetrySpec. "
|
|
356
|
+
"Distinct from LLM completion logging and from Laminar/OTel tracing."
|
|
357
|
+
),
|
|
358
|
+
)
|
|
273
359
|
model_config: ClassVar[ConfigDict] = {"frozen": True}
|
|
274
360
|
|
|
275
361
|
@property
|