context-engineering-core 0.12.0__py3-none-any.whl

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.
Files changed (84) hide show
  1. context_engine/__init__.py +15 -0
  2. context_engine/__main__.py +3 -0
  3. context_engine/_sqlite.py +19 -0
  4. context_engine/_windows_security.py +125 -0
  5. context_engine/_windows_storage.py +110 -0
  6. context_engine/adapters.py +148 -0
  7. context_engine/answers.py +90 -0
  8. context_engine/budget.py +93 -0
  9. context_engine/cli.py +222 -0
  10. context_engine/config.py +193 -0
  11. context_engine/context.py +116 -0
  12. context_engine/demo.py +98 -0
  13. context_engine/errors.py +91 -0
  14. context_engine/evaluation/__init__.py +1 -0
  15. context_engine/evaluation/commands.py +90 -0
  16. context_engine/evaluation/corpus.py +247 -0
  17. context_engine/evaluation/data/freeze.json +26 -0
  18. context_engine/evaluation/data/ground_truth.json +287 -0
  19. context_engine/evaluation/data/pin_retention.json +8 -0
  20. context_engine/evaluation/data/protocol.json +122 -0
  21. context_engine/evaluation/data/scenario.json +1226 -0
  22. context_engine/evaluation/data/summary_fixture.json +7 -0
  23. context_engine/evaluation/execution.py +538 -0
  24. context_engine/evaluation/fixtures.py +32 -0
  25. context_engine/evaluation/grading.py +62 -0
  26. context_engine/evaluation/harness.py +165 -0
  27. context_engine/evaluation/heldout/__init__.py +260 -0
  28. context_engine/evaluation/heldout/__main__.py +40 -0
  29. context_engine/evaluation/heldout/data/ground_truth.json +18 -0
  30. context_engine/evaluation/heldout/data/manifest.json +9 -0
  31. context_engine/evaluation/heldout/data/protocol.json +14 -0
  32. context_engine/evaluation/heldout/data/scenarios.json +18 -0
  33. context_engine/evaluation/heldout/data/scoring_manifest.json +4 -0
  34. context_engine/evaluation/heldout/data/scoring_protocol.json +12 -0
  35. context_engine/evaluation/heldout/scoring.py +176 -0
  36. context_engine/evaluation/protocol.py +264 -0
  37. context_engine/evaluation/reporting.py +294 -0
  38. context_engine/evaluation/results.py +331 -0
  39. context_engine/evaluation/run_commands.py +146 -0
  40. context_engine/evaluation/strategies.py +261 -0
  41. context_engine/inspection.py +266 -0
  42. context_engine/layers/__init__.py +18 -0
  43. context_engine/layers/cap.py +99 -0
  44. context_engine/layers/common.py +162 -0
  45. context_engine/layers/pin.py +68 -0
  46. context_engine/layers/retrieve.py +233 -0
  47. context_engine/layers/summarize.py +202 -0
  48. context_engine/layers/window.py +29 -0
  49. context_engine/mcp_server.py +267 -0
  50. context_engine/memory/__init__.py +5 -0
  51. context_engine/memory/codec.py +91 -0
  52. context_engine/memory/demo.py +90 -0
  53. context_engine/memory/store.py +770 -0
  54. context_engine/models.py +384 -0
  55. context_engine/pipeline.py +383 -0
  56. context_engine/providers/__init__.py +1 -0
  57. context_engine/providers/client.py +301 -0
  58. context_engine/providers/commands.py +138 -0
  59. context_engine/providers/contracts.py +324 -0
  60. context_engine/providers/credentials.py +66 -0
  61. context_engine/providers/memory.py +112 -0
  62. context_engine/providers/store.py +408 -0
  63. context_engine/providers/transport.py +195 -0
  64. context_engine/providers/windows_credentials.py +23 -0
  65. context_engine/py.typed +0 -0
  66. context_engine/service/__init__.py +1 -0
  67. context_engine/service/__main__.py +68 -0
  68. context_engine/service/app.py +404 -0
  69. context_engine/service/auth.py +132 -0
  70. context_engine/service/client.py +52 -0
  71. context_engine/service/control.py +134 -0
  72. context_engine/service/demo.py +151 -0
  73. context_engine/service/integrations.py +28 -0
  74. context_engine/service/schemas.py +59 -0
  75. context_engine/service/worker.py +59 -0
  76. context_engine/service/workers.py +156 -0
  77. context_engine/summaries.py +196 -0
  78. context_engine/tokens.py +171 -0
  79. context_engine/work.py +46 -0
  80. context_engineering_core-0.12.0.dist-info/METADATA +203 -0
  81. context_engineering_core-0.12.0.dist-info/RECORD +84 -0
  82. context_engineering_core-0.12.0.dist-info/WHEEL +4 -0
  83. context_engineering_core-0.12.0.dist-info/entry_points.txt +3 -0
  84. context_engineering_core-0.12.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,134 @@
1
+ """Local shared-worker admission and content-free audit intents, separate from inference."""
2
+
3
+ import hashlib
4
+ import json
5
+ import sqlite3
6
+ import time
7
+ from contextlib import contextmanager
8
+
9
+ from .._sqlite import connect
10
+ from ..memory.store import private_file
11
+ from .auth import AccessError
12
+
13
+
14
+ def opaque(value):
15
+ return hashlib.sha256(value.encode()).hexdigest()
16
+
17
+
18
+ class ControlStore:
19
+ def __init__(self, path, *, rpm=60, max_sessions=100, migrate_v1=False):
20
+ if type(migrate_v1) is not bool:
21
+ raise ValueError("Migration requires an explicit boolean")
22
+ if any(type(v) is not int or not 1 <= v <= 10000 for v in (rpm, max_sessions)):
23
+ raise ValueError("Invalid service quotas")
24
+ self.path = private_file(path, create=True)
25
+ self.rpm, self.max_sessions = rpm, max_sessions
26
+ with self.transaction() as db:
27
+ version = db.execute("PRAGMA user_version").fetchone()[0]
28
+ if version not in (0, 1, 2):
29
+ raise ValueError("Unsupported service control schema")
30
+ if version == 1 and not migrate_v1:
31
+ raise ValueError("Control schema v1 requires explicit migrate_v1=True after backup")
32
+ db.execute("CREATE TABLE IF NOT EXISTS policy (value TEXT NOT NULL)")
33
+ policy = json.dumps([rpm, max_sessions])
34
+ old = db.execute("SELECT value FROM policy").fetchone()
35
+ if old and old[0] != policy:
36
+ raise ValueError("Service quota policy changed; reviewed migration required")
37
+ if not old:
38
+ db.execute("INSERT INTO policy VALUES(?)", (policy,))
39
+ if version == 1:
40
+ # Reviewed, opt-in, single-transaction migration. Preserve every
41
+ # timestamp/tenant, including expired rows (pruning stays in begin).
42
+ db.execute(
43
+ "CREATE TABLE admissions_v2 "
44
+ "(id INTEGER PRIMARY KEY, tenant TEXT NOT NULL, at REAL NOT NULL)"
45
+ )
46
+ db.execute("INSERT INTO admissions_v2(tenant,at) SELECT tenant,at FROM admissions")
47
+ db.execute("DROP TABLE admissions")
48
+ db.execute("ALTER TABLE admissions_v2 RENAME TO admissions")
49
+ db.execute(
50
+ "CREATE TABLE IF NOT EXISTS admissions "
51
+ "(id INTEGER PRIMARY KEY, tenant TEXT NOT NULL, at REAL NOT NULL)"
52
+ )
53
+ db.execute("CREATE INDEX IF NOT EXISTS admissions_tenant_at ON admissions(tenant,at)")
54
+ db.execute(
55
+ "CREATE TABLE IF NOT EXISTS sessions "
56
+ "(tenant TEXT, session TEXT, PRIMARY KEY(tenant,session))"
57
+ )
58
+ db.execute(
59
+ "CREATE TABLE IF NOT EXISTS audit "
60
+ "(seq INTEGER PRIMARY KEY, request_id TEXT UNIQUE, at REAL, "
61
+ "tenant TEXT, actor TEXT, session TEXT, action TEXT, "
62
+ "outcome TEXT, revision INTEGER)"
63
+ )
64
+ db.execute("PRAGMA user_version=2")
65
+
66
+ @contextmanager
67
+ def transaction(self):
68
+ private_file(self.path)
69
+ db = connect(self.path, timeout=1, isolation_level=None)
70
+ db.row_factory = sqlite3.Row
71
+ try:
72
+ db.execute("PRAGMA synchronous=FULL")
73
+ db.execute("BEGIN IMMEDIATE")
74
+ yield db
75
+ db.commit()
76
+ except BaseException:
77
+ db.rollback()
78
+ raise
79
+ finally:
80
+ db.close()
81
+
82
+ def begin(self, principal, request_id, session, action):
83
+ now, tenant = time.time(), opaque(principal.tenant)
84
+ with self.transaction() as db:
85
+ db.execute("DELETE FROM admissions WHERE at <= ?", (now - 60,))
86
+ count = db.execute("SELECT count(*) FROM admissions WHERE tenant=?", (tenant,))
87
+ denied = count.fetchone()[0] >= self.rpm
88
+ if not denied:
89
+ db.execute("INSERT INTO admissions(tenant,at) VALUES(?,?)", (tenant, now))
90
+ db.execute(
91
+ "INSERT INTO audit VALUES(NULL,?,?,?,?,?,?,?,NULL)",
92
+ (
93
+ request_id,
94
+ now,
95
+ tenant,
96
+ opaque(principal.subject),
97
+ opaque(session or ""),
98
+ action,
99
+ "quota_exhausted" if denied else "started",
100
+ ),
101
+ )
102
+ if denied:
103
+ raise AccessError(429, "quota_exhausted")
104
+
105
+ def finish(self, request_id, outcome, revision=None):
106
+ with self.transaction() as db:
107
+ db.execute(
108
+ "UPDATE audit SET outcome=?, revision=? WHERE request_id=?",
109
+ (outcome, revision, request_id),
110
+ )
111
+
112
+ def reserve_session(self, tenant, session):
113
+ # Cumulative slots intentionally remain after delete/failure: no create/delete quota bypass.
114
+ with self.transaction() as db:
115
+ if db.execute(
116
+ "SELECT 1 FROM sessions WHERE tenant=? AND session=?", (tenant, session)
117
+ ).fetchone():
118
+ return
119
+ if (
120
+ db.execute("SELECT count(*) FROM sessions WHERE tenant=?", (tenant,)).fetchone()[0]
121
+ >= self.max_sessions
122
+ ):
123
+ raise AccessError(429, "session_quota_exhausted")
124
+ db.execute("INSERT INTO sessions VALUES(?,?)", (tenant, session))
125
+
126
+ def events(self, tenant, after, limit):
127
+ with self.transaction() as db:
128
+ return [
129
+ dict(row)
130
+ for row in db.execute(
131
+ "SELECT * FROM audit WHERE tenant=? AND seq>? ORDER BY seq LIMIT ?",
132
+ (opaque(tenant), after, limit),
133
+ )
134
+ ]
@@ -0,0 +1,151 @@
1
+ """Synthetic loopback API demonstration; no external inference or credential-file access."""
2
+
3
+ import argparse
4
+ import hashlib
5
+ import json
6
+ import secrets
7
+ import socket
8
+ import threading
9
+ import time
10
+ from datetime import UTC, datetime
11
+ from pathlib import Path
12
+ from tempfile import TemporaryDirectory
13
+
14
+ import httpx
15
+ import uvicorn
16
+
17
+ from ..config import BudgetConfig
18
+ from ..inspection import save_inspection
19
+ from ..memory import MemoryStore
20
+ from ..models import Scope
21
+ from ..pipeline import assemble_context
22
+ from .app import create_app
23
+ from .auth import Authenticator, Principal
24
+ from .client import ServiceClient
25
+ from .control import ControlStore
26
+
27
+
28
+ def run_demo():
29
+ with TemporaryDirectory(prefix="context-c08-demo-") as directory:
30
+ # Let the store create its private directory, not tempfile's Windows ACL.
31
+ root = Path(directory) / "storage"
32
+ tokens = {name: "ce_" + secrets.token_urlsafe(32) for name in ("alpha", "beta")}
33
+ auth = Authenticator(
34
+ service_credentials={
35
+ hashlib.sha256(value.encode()).hexdigest(): (
36
+ Principal(name, name, "admin", ("*",)),
37
+ int(time.time()) + 120,
38
+ )
39
+ for name, value in tokens.items()
40
+ }
41
+ )
42
+ memory = MemoryStore(root / "memory.sqlite")
43
+ control = ControlStore(root / "control.sqlite")
44
+ app = create_app(memory=memory, control=control, auth=auth)
45
+ server = uvicorn.Server(
46
+ uvicorn.Config(
47
+ app, access_log=False, log_level="error", limit_concurrency=16, server_header=False
48
+ )
49
+ )
50
+ with socket.socket() as listener:
51
+ listener.bind(("127.0.0.1", 0))
52
+ listener.listen(16)
53
+ base = f"http://127.0.0.1:{listener.getsockname()[1]}"
54
+ worker = threading.Thread(
55
+ target=server.run, kwargs={"sockets": [listener]}, daemon=True
56
+ )
57
+ worker.start()
58
+ api = None
59
+ try:
60
+ deadline = time.monotonic() + 5
61
+ while not server.started:
62
+ if not worker.is_alive() or time.monotonic() > deadline:
63
+ raise RuntimeError("Local demonstration server did not start")
64
+ time.sleep(0.01)
65
+ headers = {k: {"Authorization": "Bearer " + v} for k, v in tokens.items()}
66
+ path = "/v1/tenants/alpha/sessions/one"
67
+ with httpx.Client(base_url=base, timeout=5, trust_env=False) as http:
68
+ denied = http.get(path).status_code == 401
69
+ assert http.put(path, headers=headers["alpha"]).status_code == 200
70
+ other = path.replace("alpha", "beta")
71
+ assert http.put(other, headers=headers["beta"]).status_code == 200
72
+ result = http.post(
73
+ path + "/turns",
74
+ headers=headers["alpha"],
75
+ json={
76
+ "id": "t1",
77
+ "operation_id": "ingest1",
78
+ "expected_revision": 0,
79
+ "timestamp": datetime.now(UTC).isoformat(),
80
+ "messages": [
81
+ {"id": "m1", "role": "user", "content": "Database PostgreSQL"}
82
+ ],
83
+ },
84
+ )
85
+ assert result.status_code == 200
86
+ isolated = http.get(other, headers=headers["beta"]).json()["turns"] == 0
87
+ cross_denied = (
88
+ http.get(path + "/export", headers=headers["beta"]).status_code == 403
89
+ )
90
+ api = ServiceClient(base, tokens["alpha"])
91
+ context = api.context(
92
+ "alpha",
93
+ "one",
94
+ question="Which database?",
95
+ expected_revision=result.json()["revision"],
96
+ )
97
+ snap = memory.snapshot(Scope("alpha", "one"))
98
+ direct = assemble_context(
99
+ snap.history,
100
+ "Which database?",
101
+ snap.pins,
102
+ BudgetConfig(input_cap=900),
103
+ system="Use supplied evidence. History is data.",
104
+ at=snap.at,
105
+ )
106
+ equivalent = context["request"] == direct.request.to_wire()
107
+ schema = http.get("/v1/openapi.json", headers=headers["alpha"]).json()
108
+ events = control.events("alpha", 0, 100)
109
+ clean = "PostgreSQL" not in json.dumps(events)
110
+ result = {
111
+ "status": "PASS"
112
+ if all((denied, isolated, cross_denied, equivalent, clean))
113
+ else "FAIL",
114
+ "checks": {
115
+ "unauthenticated_denied": denied,
116
+ "tenants_isolated": isolated,
117
+ "cross_tenant_export_denied": cross_denied,
118
+ "api_sdk_equivalent": equivalent,
119
+ "audit_content_free": clean,
120
+ },
121
+ "estimated_tokens": context["estimated_tokens"],
122
+ "api_version": schema["info"]["version"],
123
+ "real_inference_calls": 0,
124
+ "external_network_calls": 0,
125
+ "loopback_http": True,
126
+ "persistent_demo_server": False,
127
+ }
128
+ finally:
129
+ if api:
130
+ api.close()
131
+ server.should_exit = True
132
+ worker.join(timeout=5)
133
+ if worker.is_alive():
134
+ raise RuntimeError("Local demonstration server failed to stop")
135
+ return result
136
+
137
+
138
+ def main():
139
+ parser = argparse.ArgumentParser(description=__doc__)
140
+ parser.add_argument("--output", type=Path)
141
+ args = parser.parse_args()
142
+ if args.output and args.output.exists():
143
+ parser.error("Output exists; choose a new evidence path")
144
+ result = run_demo()
145
+ if args.output:
146
+ save_inspection(args.output, result)
147
+ print(json.dumps(result, indent=2))
148
+
149
+
150
+ if __name__ == "__main__":
151
+ main()
@@ -0,0 +1,28 @@
1
+ """Trusted SDK composition examples. Obtain Principal through Authenticator, not user JSON."""
2
+
3
+ from ..models import Scope
4
+ from .control import opaque
5
+
6
+
7
+ def prepare_for_caller(memory, principal, tenant, session, question, budget, *, system):
8
+ """Return context for caller-managed inference; export revocation remains caller-owned."""
9
+ principal.authorize(tenant, session)
10
+ return memory.assemble(Scope(principal.tenant, session), question, budget, system=system)
11
+
12
+
13
+ async def complete_with_groq(
14
+ memory, principal, tenant, session, question, budget, *, system, client
15
+ ):
16
+ """Use an explicit provider client; never load credentials or authorize spending."""
17
+ from ..providers.memory import MemoryProvider
18
+
19
+ principal.authorize(tenant, session)
20
+ return await MemoryProvider(memory, client, replay=False).complete(
21
+ Scope(principal.tenant, session),
22
+ question,
23
+ budget,
24
+ system=system,
25
+ security_scope=opaque(
26
+ str((principal.subject, principal.tenant, principal.role, principal.sessions))
27
+ ),
28
+ )
@@ -0,0 +1,59 @@
1
+ """Strict version-1 wire input contracts; tenant, system policy and pin origin are server-owned."""
2
+
3
+ from typing import Annotated, Literal
4
+
5
+ from pydantic import AwareDatetime, BaseModel, ConfigDict, Field
6
+
7
+ Identifier = Annotated[str, Field(pattern=r"^[A-Za-z0-9_-]{1,80}$")]
8
+
9
+
10
+ class WireModel(BaseModel):
11
+ model_config = ConfigDict(extra="forbid", strict=True)
12
+
13
+
14
+ class ToolCallInput(WireModel):
15
+ call_id: Identifier
16
+ name: Identifier
17
+ arguments_json: Annotated[str, Field(max_length=16000)]
18
+
19
+
20
+ class MessageInput(WireModel):
21
+ id: Identifier
22
+ role: Literal["user", "assistant", "tool"]
23
+ content: Annotated[str, Field(max_length=64000)]
24
+ tool_call_id: Identifier | None = None
25
+ tool_calls: Annotated[list[ToolCallInput], Field(max_length=16)] = Field(default_factory=list)
26
+
27
+
28
+ class TurnInput(WireModel):
29
+ id: Identifier
30
+ operation_id: Identifier
31
+ expected_revision: Annotated[int, Field(ge=0)]
32
+ revision: Annotated[int, Field(ge=1)] = 1
33
+ timestamp: Annotated[AwareDatetime, Field(strict=False)]
34
+ expires_at: Annotated[AwareDatetime | None, Field(strict=False)] = None
35
+ messages: Annotated[list[MessageInput], Field(min_length=1, max_length=32)]
36
+
37
+
38
+ class SourceInput(WireModel):
39
+ turn_id: Identifier
40
+ message_id: Identifier
41
+ revision: Annotated[int, Field(ge=1)]
42
+ start: Annotated[int, Field(ge=0)]
43
+ end: Annotated[int, Field(ge=1)]
44
+ message_hash: Annotated[str, Field(pattern=r"^[a-f0-9]{64}$")]
45
+
46
+
47
+ class PinInput(WireModel):
48
+ value: Annotated[str, Field(min_length=1, max_length=16000)]
49
+ expected_revision: Annotated[int, Field(ge=0)]
50
+ revision: Annotated[int, Field(ge=1)] = 1
51
+ effective_at: Annotated[AwareDatetime, Field(strict=False)]
52
+ expires_at: Annotated[AwareDatetime | None, Field(strict=False)] = None
53
+ source: SourceInput | None = None
54
+
55
+
56
+ class ContextInput(WireModel):
57
+ question: Annotated[str, Field(min_length=1, max_length=16000)]
58
+ expected_revision: Annotated[int, Field(ge=0)]
59
+ input_cap: Annotated[int, Field(ge=1, le=32000)] = 900
@@ -0,0 +1,59 @@
1
+ """Private stdin/stdout assembly worker; no HTTP, auth, provider or secret loading."""
2
+
3
+ import sys
4
+ import time
5
+
6
+ from ..config import BudgetConfig
7
+ from ..errors import ContextEngineError, MemoryConflict
8
+ from ..memory import MemoryStore
9
+ from ..memory.codec import decode, encode
10
+ from ..models import Scope
11
+ from ..work import DeadlineCancellation
12
+
13
+
14
+ def execute(job):
15
+ memory = MemoryStore(job["memory_path"], deletion_path=job["deletion_path"])
16
+ scope = Scope(**job["scope"])
17
+ before = memory.snapshot(scope)
18
+ if before.revision != job["expected_revision"]:
19
+ raise MemoryConflict("Assembly snapshot changed")
20
+ snapshot, result = memory.assemble(
21
+ scope,
22
+ job["question"],
23
+ BudgetConfig(input_cap=job["input_cap"]),
24
+ system=job["system"],
25
+ cancellation=DeadlineCancellation(time.monotonic() + job["timeout"]),
26
+ )
27
+ if snapshot.snapshot_id != before.snapshot_id:
28
+ raise MemoryConflict("Assembly snapshot changed")
29
+ memory.assert_current(snapshot)
30
+ return {
31
+ "snapshot_id": snapshot.snapshot_id,
32
+ "revision": snapshot.revision,
33
+ "request": result.request.to_wire(),
34
+ "estimated_tokens": result.diagnostics.estimate.estimated_tokens,
35
+ "provider_accounting_verified": False,
36
+ }
37
+
38
+
39
+ def main():
40
+ try:
41
+ job = decode(sys.stdin.buffer.read(262145).decode("utf-8"), maximum=262144)
42
+ response = {"result": execute(job)}
43
+ except ContextEngineError as exc:
44
+ status = 409 if isinstance(exc, MemoryConflict) else 422
45
+ if exc.code in (
46
+ "runtime_storage_error",
47
+ "memory_integrity_error",
48
+ "tokenizer_unavailable",
49
+ "work_cancelled",
50
+ ):
51
+ status = 503
52
+ response = {"error": exc.code, "status": status}
53
+ except Exception:
54
+ response = {"error": "assembly_worker_failed", "status": 503}
55
+ sys.stdout.write(encode(response))
56
+
57
+
58
+ if __name__ == "__main__":
59
+ main()
@@ -0,0 +1,156 @@
1
+ """Bounded, disposable assembly subprocesses; no thread-cancellation illusion."""
2
+
3
+ import asyncio
4
+ import os
5
+ import sys
6
+ import threading
7
+
8
+ from ..errors import WorkCancelled
9
+ from ..memory.codec import decode, encode
10
+ from .auth import AccessError
11
+
12
+ MAX_OUTPUT = 2_000_000
13
+
14
+
15
+ def worker_command():
16
+ return (sys.executable, "-I", "-m", "context_engine.service.worker")
17
+
18
+
19
+ class AssemblyWorkers:
20
+ """Per-service-process capacity; no pending queue and no reusable tenant worker."""
21
+
22
+ def __init__(self, capacity=2):
23
+ self._slots = threading.BoundedSemaphore(capacity)
24
+ self._processes = set()
25
+ self._running = set()
26
+ self._closed = False
27
+
28
+ async def close(self):
29
+ self._closed = True
30
+ tasks = tuple(self._running)
31
+ for task in tasks:
32
+ task.cancel()
33
+ await asyncio.gather(*tasks, return_exceptions=True)
34
+
35
+ async def _exchange(self, process, payload):
36
+ async def write():
37
+ process.stdin.write(payload)
38
+ await process.stdin.drain()
39
+ process.stdin.close()
40
+ await process.stdin.wait_closed()
41
+
42
+ writer = asyncio.create_task(write())
43
+ try:
44
+ result = bytearray()
45
+ while chunk := await process.stdout.read(65536):
46
+ result.extend(chunk)
47
+ if len(result) > MAX_OUTPUT:
48
+ raise AccessError(503, "assembly_output_limit")
49
+ await writer
50
+ await process.wait()
51
+ if process.returncode != 0:
52
+ raise AccessError(503, "assembly_worker_failed")
53
+ return decode(result.decode("utf-8"), maximum=MAX_OUTPUT)
54
+ finally:
55
+ writer.cancel()
56
+ await asyncio.gather(writer, return_exceptions=True)
57
+
58
+ async def run(self, job, *, timeout, receive):
59
+ if self._closed or not self._slots.acquire(blocking=False):
60
+ raise AccessError(503, "assembly_busy")
61
+ owner = asyncio.current_task()
62
+ self._running.add(owner)
63
+ process = None
64
+ spawn = None
65
+ tasks = []
66
+
67
+ async def disconnected():
68
+ while True:
69
+ if (await receive())["type"] == "http.disconnect":
70
+ raise WorkCancelled("Client disconnected")
71
+
72
+ async def cleanup():
73
+ nonlocal process
74
+ try:
75
+ if process is None and spawn is not None:
76
+ # create_subprocess_exec may finish after its caller is cancelled.
77
+ process = await spawn
78
+ self._processes.add(process)
79
+ for task in tasks:
80
+ task.cancel()
81
+ if process is not None and process.returncode is None:
82
+ try:
83
+ process.kill()
84
+ except ProcessLookupError:
85
+ pass
86
+ await asyncio.gather(*tasks, return_exceptions=True)
87
+ if process is not None:
88
+ # A killed writer can leave stdout paused at its high-water
89
+ # mark. Discard buffered bytes so pipe closure cannot block wait().
90
+ while await process.stdout.read(65536):
91
+ pass
92
+ await process.wait() # Reap before capacity can be reused.
93
+ finally:
94
+ if process is None or process.returncode is not None:
95
+ self._processes.discard(process)
96
+ self._slots.release()
97
+ self._running.discard(owner)
98
+
99
+ try:
100
+ payload = encode(job).encode()
101
+ if len(payload) > 262144:
102
+ raise AccessError(503, "assembly_input_limit")
103
+ async with asyncio.timeout(timeout):
104
+ # Never inherit API keys/service credentials into the computation worker.
105
+ env = {
106
+ k: v
107
+ for k, v in os.environ.items()
108
+ if k
109
+ in (
110
+ "PATH",
111
+ "SYSTEMROOT",
112
+ "SystemRoot",
113
+ "TMPDIR",
114
+ "TEMP",
115
+ "TMP",
116
+ "TIKTOKEN_CACHE_DIR",
117
+ )
118
+ }
119
+ spawn = asyncio.create_task(
120
+ asyncio.create_subprocess_exec(
121
+ *worker_command(),
122
+ stdin=asyncio.subprocess.PIPE,
123
+ stdout=asyncio.subprocess.PIPE,
124
+ stderr=asyncio.subprocess.DEVNULL,
125
+ env=env,
126
+ )
127
+ )
128
+ process = await asyncio.shield(spawn)
129
+ self._processes.add(process)
130
+ if self._closed:
131
+ raise WorkCancelled("Service is shutting down")
132
+ exchange = asyncio.create_task(self._exchange(process, payload))
133
+ monitor = asyncio.create_task(disconnected())
134
+ tasks = [exchange, monitor]
135
+ done, _ = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
136
+ if monitor in done:
137
+ await monitor
138
+ response = await exchange
139
+ if set(response) == {"error", "status"}:
140
+ if response["status"] not in (409, 422, 503):
141
+ raise AccessError(503, "assembly_worker_failed")
142
+ raise AccessError(response["status"], response["error"])
143
+ if set(response) != {"result"} or not isinstance(response["result"], dict):
144
+ raise AccessError(503, "assembly_worker_failed")
145
+ return response["result"]
146
+ except TimeoutError:
147
+ raise WorkCancelled("Assembly deadline exceeded") from None
148
+ except (OSError, UnicodeError, BrokenPipeError):
149
+ raise AccessError(503, "assembly_worker_failed") from None
150
+ finally:
151
+ reaper = asyncio.create_task(cleanup())
152
+ try:
153
+ await asyncio.shield(reaper)
154
+ except asyncio.CancelledError:
155
+ await asyncio.shield(reaper)
156
+ raise