forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig.py
More file actions
426 lines (363 loc) · 16.4 KB
/
Copy pathconfig.py
File metadata and controls
426 lines (363 loc) · 16.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
"""Configuration primitives for the local dev harness."""
from __future__ import annotations
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Mapping
from dotenv import dotenv_values
from . import providers, safety
FIRESTORE_PORT = 8085
AUTH_PORT = 9099
BACKEND_PORT = 8000
LLM_GATEWAY_PORT = 9080
DESKTOP_BACKEND_PORT = 10201
REDIS_PORT = 6380
TYPESENSE_PORT = 8108
# The official container listens on its internal default unless an explicit
# --api-port is supplied. Harness instances vary only the loopback host port.
TYPESENSE_CONTAINER_PORT = 8108
TYPESENSE_PINNED_VERSION = "27.1"
LOCAL_TYPESENSE_API_KEY = "local-typesense-api-key-not-real"
LOCAL_FIREBASE_API_KEY = "local-firebase-auth-emulator-api-key"
LOCAL_LLM_GATEWAY_SERVICE_TOKEN = "local-dev-llm-gateway-service-token-not-real"
# Signs the internal screen-frame approval token. Production sets either this or
# SCREEN_FRAME_KMS_KEY; without one the egress routes fail closed, so the harness
# supplies a throwaway value rather than leaving meeting screenshots unrunnable locally.
LOCAL_SCREEN_FRAME_SIGNING_SECRET = "local-dev-screen-frame-signing-secret-not-real"
LOCAL_STORAGE_BUCKET_ENV = {
"BUCKET_SPEECH_PROFILES": "speech-profiles",
"BUCKET_POSTPROCESSING": "postprocessing",
"BUCKET_PRIVATE_CLOUD_SYNC": "omi-private-cloud-sync",
"BUCKET_TEMPORAL_SYNC_LOCAL": "sync-temporal",
"BUCKET_MEMORIES_RECORDINGS": "memories-recordings",
"BUCKET_PLUGINS_LOGOS": "plugins-logos",
"BUCKET_APP_THUMBNAILS": "app-thumbnails",
"BUCKET_CHAT_FILES": "chat-files",
"BUCKET_DESKTOP_UPDATES": "desktop-updates",
"BUCKET_SCREEN_FRAMES": "screen-frames",
}
PORT_OFFSET_ENV = "OMI_HARNESS_PORT_OFFSET"
PORT_OVERRIDE_ENVS = {
"firestore": "OMI_HARNESS_FIRESTORE_PORT",
"auth": "OMI_HARNESS_AUTH_PORT",
"backend": "OMI_HARNESS_BACKEND_PORT",
"desktop_backend": "OMI_HARNESS_DESKTOP_BACKEND_PORT",
"redis": "OMI_HARNESS_REDIS_PORT",
"typesense": "OMI_HARNESS_TYPESENSE_PORT",
"llm_gateway": "OMI_HARNESS_LLM_GATEWAY_PORT",
}
PROVIDER_MODES = providers.PROVIDER_MODES
CORE_PROVIDER_ENV = (
"OPENAI_API_KEY",
"DEEPGRAM_API_KEY",
"GEMINI_API_KEY",
"ANTHROPIC_API_KEY",
)
SECRETS_FILE_ALLOWED_KEYS = frozenset({"PROVIDER_MODE", *CORE_PROVIDER_ENV})
@dataclass(frozen=True)
class SecretsFileParseResult:
secrets: dict[str, str]
ignored_keys: tuple[str, ...]
sources: dict[str, str]
@dataclass(frozen=True)
class HarnessConfig:
repo_root: Path
instance: str
provider_mode: str
layout: safety.HarnessLayout
project_id: str = safety.DEFAULT_LOCAL_FIREBASE_PROJECT_ID
database_id: str = safety.DEFAULT_FIRESTORE_DATABASE_ID
firestore_port: int = FIRESTORE_PORT
auth_port: int = AUTH_PORT
backend_port: int = BACKEND_PORT
desktop_backend_port: int = DESKTOP_BACKEND_PORT
redis_host: str = "127.0.0.1"
redis_port: int = REDIS_PORT
typesense_port: int = TYPESENSE_PORT
dev_bind_host: str = "127.0.0.1"
# Address clients on other machines (a phone) reach the backend at; only
# used for URLs the backend hands out, such as local-storage file links.
dev_advertise_host: str = "127.0.0.1"
llm_gateway_port: int = LLM_GATEWAY_PORT
@property
def firestore_host(self) -> str:
return f"127.0.0.1:{self.firestore_port}"
@property
def auth_host(self) -> str:
return f"127.0.0.1:{self.auth_port}"
@property
def backend_host(self) -> str:
return f"127.0.0.1:{self.backend_port}"
@property
def desktop_backend_host(self) -> str:
return f"127.0.0.1:{self.desktop_backend_port}"
@property
def redis_url(self) -> str:
return f"redis://{self.redis_host}:{self.redis_port}/0?omi_instance={self.instance}"
@property
def backend_url(self) -> str:
return f"http://{self.backend_host}"
@property
def backend_public_url(self) -> str:
"""Backend URL as reachable from a physical device (see OMI_DEV_HOST)."""
return f"http://{self.dev_advertise_host}:{self.backend_port}"
@property
def desktop_backend_url(self) -> str:
return f"http://{self.desktop_backend_host}"
@property
def llm_gateway_url(self) -> str:
return f"http://127.0.0.1:{self.llm_gateway_port}"
@property
def llm_gateway_service_token(self) -> str:
# Isolate tokens per harness instance so parallel offsets cannot reuse
# the shared local default and accidentally authorize each other.
return f"{LOCAL_LLM_GATEWAY_SERVICE_TOKEN}:{self.instance}"
def repo_root_from(path: Path) -> Path:
current = path.resolve()
for candidate in (current, *current.parents):
if (candidate / "AGENTS.md").is_file() and (candidate / ".git").exists():
return candidate
return Path(__file__).resolve().parents[3]
def provider_mode_from_env(env: Mapping[str, str] | None = None) -> str:
return providers.provider_mode_from_env(env)
DEV_BIND_HOST_ENV = "OMI_DEV_BIND_HOST"
# Falls back to OMI_DEV_HOST (app/setup.sh:53) so a contributor who follows the
# documented physical-device setup — set OMI_DEV_HOST to the Mac's reachable
# address — gets a harness that actually listens there too, not just an app
# built to expect it (#11774). OMI_DEV_BIND_HOST exists for the narrower case
# of wanting an asymmetric bind (e.g. a different advertise vs. listen address).
APP_DEV_HOST_ENV = "OMI_DEV_HOST"
def dev_bind_host_from_env(env: Mapping[str, str] | None = None) -> str:
"""Resolve what the harness's HTTP services and Firebase emulators bind to.
Defaults to loopback-only, preserving today's behavior. Binds all
interfaces (0.0.0.0) when a private LAN/CGNAT address is requested, rather
than that literal address, so loopback callers (health checks, a booted
simulator) keep working alongside the newly reachable LAN/tailnet address.
Fails closed (SafetyError) on a publicly routable address.
"""
source = os.environ if env is None else env
requested = source.get(DEV_BIND_HOST_ENV, "").strip() or source.get(APP_DEV_HOST_ENV, "").strip()
if not requested:
return "127.0.0.1"
safety.validate_dev_bind_host(requested, name=DEV_BIND_HOST_ENV)
if safety.is_loopback_host(requested):
return "127.0.0.1"
return "0.0.0.0"
def dev_advertise_host_from_env(env: Mapping[str, str] | None = None) -> str:
"""Resolve the address the backend advertises itself at in generated URLs.
A phone built against OMI_DEV_HOST cannot fetch a local-storage file (the
saved speech profile, say) from a 127.0.0.1 link, so when OMI_DEV_HOST names
a LAN/tailnet address the backend hands out links on that address instead.
Loopback stays the default for simulator-only setups.
"""
source = os.environ if env is None else env
requested = source.get(APP_DEV_HOST_ENV, "").strip()
if not requested or safety.is_loopback_host(requested):
return "127.0.0.1"
safety.validate_dev_bind_host(requested, name=APP_DEV_HOST_ENV)
return requested
def _port_from_env(source: Mapping[str, str], name: str, default: int, offset: int) -> int:
raw = source.get(PORT_OVERRIDE_ENVS[name], "").strip()
try:
value = int(raw) if raw else default + offset
except ValueError as exc:
raise safety.SafetyError(f"{PORT_OVERRIDE_ENVS[name]} must be an integer, got {raw!r}") from exc
if not 1 <= value <= 65535:
raise safety.SafetyError(f"{PORT_OVERRIDE_ENVS[name]} resolved outside valid port range: {value}")
return value
def harness_ports_from_env(env: Mapping[str, str] | None = None) -> dict[str, int]:
"""Resolve one isolated loopback port set while preserving dev defaults."""
source = os.environ if env is None else env
raw_offset = source.get(PORT_OFFSET_ENV, "0").strip()
try:
offset = int(raw_offset)
except ValueError as exc:
raise safety.SafetyError(f"{PORT_OFFSET_ENV} must be an integer, got {raw_offset!r}") from exc
if not 0 <= offset <= 50000:
raise safety.SafetyError(f"{PORT_OFFSET_ENV} must be between 0 and 50000, got {offset}")
defaults = {
"firestore": FIRESTORE_PORT,
"auth": AUTH_PORT,
"backend": BACKEND_PORT,
"desktop_backend": DESKTOP_BACKEND_PORT,
"redis": REDIS_PORT,
"typesense": TYPESENSE_PORT,
"llm_gateway": LLM_GATEWAY_PORT,
}
ports = {name: _port_from_env(source, name, default, offset) for name, default in defaults.items()}
duplicates = sorted(port for port in set(ports.values()) if list(ports.values()).count(port) > 1)
if duplicates:
raise safety.SafetyError(f"Harness ports must be distinct; duplicate values: {duplicates}")
return ports
def secrets_file_path(cfg: HarnessConfig) -> Path:
filename = ".env.offline" if cfg.provider_mode == "offline" else ".env.local-dev"
return cfg.repo_root / "backend" / filename
def _credential_env_names(cfg: HarnessConfig) -> tuple[str, ...]:
names: list[str] = []
for spec in providers.default_provider_specs(cfg.repo_root):
if spec.credential_env:
names.append(spec.credential_env)
return tuple(names)
def parse_secrets_file(cfg: HarnessConfig) -> SecretsFileParseResult:
"""Read provider secrets from the stage secrets file (file-first, ambient fallback)."""
path = secrets_file_path(cfg)
file_values = dotenv_values(path) if path.is_file() else {}
ignored: list[str] = []
secrets: dict[str, str] = {}
sources: dict[str, str] = {}
for key, raw in file_values.items():
if key is None or raw is None:
continue
if key not in SECRETS_FILE_ALLOWED_KEYS:
ignored.append(key)
continue
value = str(raw).strip()
if value:
secrets[key] = value
sources[key] = "file"
for key in _credential_env_names(cfg):
if key in secrets:
continue
ambient = os.environ.get(key, "").strip()
if ambient:
secrets[key] = ambient
sources[key] = "ambient"
provider_mode = secrets.get("PROVIDER_MODE") or os.environ.get("PROVIDER_MODE", "").strip()
if provider_mode:
secrets["PROVIDER_MODE"] = provider_mode
if "PROVIDER_MODE" not in sources:
sources["PROVIDER_MODE"] = (
"ambient" if provider_mode == os.environ.get("PROVIDER_MODE", "").strip() else "file"
)
return SecretsFileParseResult(
secrets=secrets,
ignored_keys=tuple(sorted(set(ignored))),
sources=sources,
)
def provider_secrets_from_file(cfg: HarnessConfig) -> dict[str, str]:
"""Return non-empty provider credential env vars (file-first, ambient fallback)."""
parsed = parse_secrets_file(cfg)
credential_names = set(_credential_env_names(cfg))
return {key: value for key, value in parsed.secrets.items() if key in credential_names}
def preflight_env(cfg: HarnessConfig) -> dict[str, str]:
"""Merged ambient + secrets-file env used for provider preflight and dev-status."""
merged = dict(os.environ)
merged.update(parse_secrets_file(cfg).secrets)
return merged
def load_config(repo_root: Path, env: Mapping[str, str] | None = None, *, create_layout: bool = False) -> HarnessConfig:
source = os.environ if env is None else env
instance = safety.validate_instance_name(source.get("OMI_LOCAL_INSTANCE", safety.DEFAULT_INSTANCE_NAME))
provider_mode = provider_mode_from_env(source)
layout = (
safety.create_state_layout(repo_root, instance, source)
if create_layout
else safety.layout_for_instance(repo_root, instance, source)
)
ports = harness_ports_from_env(source)
dev_bind_host = dev_bind_host_from_env(source)
dev_advertise_host = dev_advertise_host_from_env(source)
cfg = HarnessConfig(
repo_root=repo_root.resolve(),
instance=instance,
provider_mode=provider_mode,
layout=layout,
firestore_port=ports["firestore"],
auth_port=ports["auth"],
backend_port=ports["backend"],
desktop_backend_port=ports["desktop_backend"],
redis_port=ports["redis"],
typesense_port=ports["typesense"],
llm_gateway_port=ports["llm_gateway"],
dev_bind_host=dev_bind_host,
dev_advertise_host=dev_advertise_host,
)
parsed = parse_secrets_file(cfg)
if parsed.secrets.get("PROVIDER_MODE"):
provider_mode = provider_mode_from_env({**dict(source), **parsed.secrets})
cfg = HarnessConfig(
repo_root=cfg.repo_root,
instance=cfg.instance,
provider_mode=provider_mode,
layout=cfg.layout,
firestore_port=cfg.firestore_port,
auth_port=cfg.auth_port,
backend_port=cfg.backend_port,
desktop_backend_port=cfg.desktop_backend_port,
redis_port=cfg.redis_port,
typesense_port=cfg.typesense_port,
llm_gateway_port=cfg.llm_gateway_port,
dev_bind_host=cfg.dev_bind_host,
dev_advertise_host=cfg.dev_advertise_host,
)
safety.validate_harness_runtime_config(
project_id=cfg.project_id,
database_id=cfg.database_id,
emulator_hosts={"Firestore emulator": cfg.firestore_host, "Firebase Auth emulator": cfg.auth_host},
)
return cfg
def _harness_service_extra(cfg: HarnessConfig) -> dict[str, str]:
# Offline harness uses direct/stub LLM paths. OMI_ENV_STAGE=offline is not a
# gateway-local stage, so FEATURE_MODE=gateway would make gateway_client reject
# startup while still advertising gateway routing.
gateway_feature_mode = "off" if cfg.provider_mode == "offline" else "gateway"
return {
"OMI_HARNESS_INSTANCE": cfg.instance,
"OMI_HARNESS_STATE_ROOT": str(cfg.layout.state_root),
"OMI_LOCAL_STORAGE_ROOT": str(cfg.layout.services_dir / "storage"),
"OMI_LOCAL_STORAGE_BASE_URL": f"{cfg.backend_public_url}/_local/storage",
"FIRESTORE_EMULATOR_HOST": cfg.firestore_host,
"FIREBASE_AUTH_EMULATOR_HOST": cfg.auth_host,
"FIREBASE_AUTH_PROJECT_ID": cfg.project_id,
"FIREBASE_PROJECT_ID": cfg.project_id,
"FIRESTORE_DATABASE_ID": cfg.database_id,
"FIREBASE_API_KEY": LOCAL_FIREBASE_API_KEY,
"MEMORY_MODE": "read",
"MEMORY_CANONICAL_CONSOLIDATION_ENABLED": "true",
"REDIS_DB_HOST": cfg.redis_host,
"REDIS_DB_PORT": str(cfg.redis_port),
"REDIS_DB_PASSWORD": "",
"ENVIRONMENT": "local-dev-harness",
"ENCRYPTION_SECRET": "omi_local_dev_harness_32_byte_test_secret_not_prod",
"ADMIN_KEY": "local-dev-admin-key-",
"TYPESENSE_HOST": "127.0.0.1",
"TYPESENSE_HOST_PORT": str(cfg.typesense_port),
"TYPESENSE_API_KEY": LOCAL_TYPESENSE_API_KEY,
"TYPESENSE_PROTOCOL": "http",
"BASE_API_URL": cfg.backend_url,
"API_BASE_URL": cfg.backend_url,
"OMI_LLM_GATEWAY_URL": cfg.llm_gateway_url,
"OMI_LLM_GATEWAY_SERVICE_TOKEN": cfg.llm_gateway_service_token,
"OMI_LLM_GATEWAY_FEATURE_MODE": gateway_feature_mode,
# Production ships default-off and turns this on deliberately; the harness is
# the one place the feature must be on, since running it is the whole point.
"SCREEN_FRAME_EGRESS_ENABLED": "true",
"SCREEN_FRAME_SIGNING_SECRET": LOCAL_SCREEN_FRAME_SIGNING_SECRET,
**LOCAL_STORAGE_BUCKET_ENV,
}
def child_env_for(cfg: HarnessConfig) -> dict[str, str]:
extra = {
**_harness_service_extra(cfg),
"PORT": str(cfg.backend_port),
"PYTHONUNBUFFERED": "1",
"OMI_ENV_STAGE": "offline" if cfg.provider_mode == "offline" else "local",
}
if cfg.provider_mode != "offline":
extra.update(provider_secrets_from_file(cfg))
env = safety.build_child_env(provider_mode=cfg.provider_mode, extra=extra)
if cfg.provider_mode == "offline":
env.update(safety.offline_provider_placeholders())
return env
def desktop_backend_child_env_for(cfg: HarnessConfig) -> dict[str, str]:
extra = {
**_harness_service_extra(cfg),
"PORT": str(cfg.desktop_backend_port),
"USE_VERTEX_AI": "false",
"OMI_ENV_STAGE": "offline" if cfg.provider_mode == "offline" else "local",
}
if cfg.provider_mode != "offline":
extra.update(provider_secrets_from_file(cfg))
env = safety.build_child_env(provider_mode=cfg.provider_mode, extra=extra)
if cfg.provider_mode == "offline":
env.update(safety.offline_provider_placeholders())
env["OMI_LLM_STUB"] = "1"
return env