astra-sdk 1.0.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.
@@ -0,0 +1,3 @@
1
+ include README.md
2
+ include LICENSE
3
+ recursive-include codeastra *.py *.pyi
@@ -0,0 +1,32 @@
1
+ Metadata-Version: 2.4
2
+ Name: astra-sdk
3
+ Version: 1.0.0
4
+ Summary: AI agent safety infrastructure. One line to connect.
5
+ Author-email: Codeastra <hello@codeastra.dev>
6
+ License: MIT
7
+ Project-URL: Homepage, https://codeastra.dev
8
+ Project-URL: Documentation, https://docs.codeastra.dev
9
+ Project-URL: Repository, https://github.com/codeastra/codeastra-python
10
+ Project-URL: Bug Tracker, https://github.com/codeastra/codeastra-python/issues
11
+ Keywords: ai,agents,safety,llm,openai,anthropic
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: requests>=2.28.0
24
+ Requires-Dist: httpx>=0.25.0
25
+ Provides-Extra: all
26
+ Requires-Dist: openai>=1.0.0; extra == "all"
27
+ Requires-Dist: anthropic>=0.20.0; extra == "all"
28
+ Requires-Dist: boto3>=1.26.0; extra == "all"
29
+ Requires-Dist: google-generativeai>=0.4.0; extra == "all"
30
+ Requires-Dist: langchain>=0.1.0; extra == "all"
31
+ Requires-Dist: langchain-openai>=0.1.0; extra == "all"
32
+ Requires-Dist: langchain-anthropic>=0.1.0; extra == "all"
@@ -0,0 +1,32 @@
1
+ Metadata-Version: 2.4
2
+ Name: astra-sdk
3
+ Version: 1.0.0
4
+ Summary: AI agent safety infrastructure. One line to connect.
5
+ Author-email: Codeastra <hello@codeastra.dev>
6
+ License: MIT
7
+ Project-URL: Homepage, https://codeastra.dev
8
+ Project-URL: Documentation, https://docs.codeastra.dev
9
+ Project-URL: Repository, https://github.com/codeastra/codeastra-python
10
+ Project-URL: Bug Tracker, https://github.com/codeastra/codeastra-python/issues
11
+ Keywords: ai,agents,safety,llm,openai,anthropic
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: requests>=2.28.0
24
+ Requires-Dist: httpx>=0.25.0
25
+ Provides-Extra: all
26
+ Requires-Dist: openai>=1.0.0; extra == "all"
27
+ Requires-Dist: anthropic>=0.20.0; extra == "all"
28
+ Requires-Dist: boto3>=1.26.0; extra == "all"
29
+ Requires-Dist: google-generativeai>=0.4.0; extra == "all"
30
+ Requires-Dist: langchain>=0.1.0; extra == "all"
31
+ Requires-Dist: langchain-openai>=0.1.0; extra == "all"
32
+ Requires-Dist: langchain-anthropic>=0.1.0; extra == "all"
@@ -0,0 +1,21 @@
1
+ MANIFEST.in
2
+ pyproject.toml
3
+ astra_sdk.egg-info/PKG-INFO
4
+ astra_sdk.egg-info/SOURCES.txt
5
+ astra_sdk.egg-info/dependency_links.txt
6
+ astra_sdk.egg-info/entry_points.txt
7
+ astra_sdk.egg-info/requires.txt
8
+ astra_sdk.egg-info/top_level.txt
9
+ codeastra/__init__.py
10
+ codeastra/async_client.py
11
+ codeastra/cli.py
12
+ codeastra/client.py
13
+ codeastra/config.py
14
+ codeastra/decorators.py
15
+ codeastra/detector.py
16
+ codeastra/exceptions.py
17
+ codeastra/patch.py
18
+ codeastra/progress.py
19
+ codeastra/py.typed
20
+ codeastra/session.py
21
+ codeastra/types.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ codeastra = codeastra.cli:main
@@ -0,0 +1,11 @@
1
+ requests>=2.28.0
2
+ httpx>=0.25.0
3
+
4
+ [all]
5
+ openai>=1.0.0
6
+ anthropic>=0.20.0
7
+ boto3>=1.26.0
8
+ google-generativeai>=0.4.0
9
+ langchain>=0.1.0
10
+ langchain-openai>=0.1.0
11
+ langchain-anthropic>=0.1.0
@@ -0,0 +1 @@
1
+ codeastra
@@ -0,0 +1,434 @@
1
+ """
2
+ Codeastra SDK
3
+ ═════════════
4
+ AI agent safety infrastructure. One line to connect.
5
+
6
+ Usage:
7
+ import codeastra
8
+
9
+ codeastra.init(
10
+ api_key = "sk-guard-...",
11
+ goal = codeastra.Goal.approvals(100),
12
+ auto_deploy = True,
13
+ real_connections = codeastra.RealConnections(
14
+ stripe_secret_key = "sk_live_...",
15
+ sendgrid_api_key = "SG.xxx",
16
+ )
17
+ )
18
+
19
+ # Your existing agent code — ZERO changes
20
+ from openai import OpenAI
21
+ client = OpenAI(api_key="sk-...")
22
+ # Every tool call now runs through Twin World automatically
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import logging
28
+ import uuid
29
+ from typing import Callable, Optional, Union
30
+
31
+ from .exceptions import (
32
+ AstraError, AstraAuthError, AstraConnectionError,
33
+ AstraSessionError, AstraGoalError, AstraWorldError,
34
+ AstraDeployError, AstraRateLimitError,
35
+ )
36
+ from .types import Goal, RealConnections, SessionConfig, SessionResult
37
+ from .session import Session
38
+ from .decorators import agent
39
+ from .client import AstraClient
40
+ from .async_client import AsyncAstraClient
41
+ from .progress import (
42
+ WorldWatcher, AsyncWorldWatcher,
43
+ TickEvent, GoalReachedEvent, ViolationEvent,
44
+ watch, awatch,
45
+ )
46
+
47
+ __version__ = "1.0.0"
48
+ __all__ = [
49
+ "init", "connect", "session", "create_world", "worlds",
50
+ "report", "deploy", "fork", "compare", "status",
51
+ "Session", "Goal", "RealConnections", "agent",
52
+ "AstraError", "AstraAuthError", "AstraConnectionError",
53
+ "AsyncAstraClient",
54
+ "WorldWatcher", "AsyncWorldWatcher",
55
+ "TickEvent", "GoalReachedEvent", "ViolationEvent",
56
+ "watch", "awatch",
57
+ ]
58
+
59
+ log = logging.getLogger("codeastra")
60
+
61
+ # ── Global state ──────────────────────────────────────────────────────────────
62
+
63
+ _state: dict = {
64
+ "api_key": "",
65
+ "proxy_url": "https://proxy.codeastra.dev",
66
+ "dev_session": "",
67
+ "patched_providers":[],
68
+ "active_session": None,
69
+ "initialized": False,
70
+ }
71
+
72
+
73
+ def _s(key: str, default=None):
74
+ return _state.get(key, default)
75
+
76
+
77
+ # ── Main API ──────────────────────────────────────────────────────────────────
78
+
79
+ def init(
80
+ api_key: str = "",
81
+ proxy_url: str = "",
82
+ goal: Optional[Union[Goal, dict]] = None,
83
+ auto_deploy: bool = False,
84
+ real_connections: Optional[Union[RealConnections, dict]] = None,
85
+ world_id: str = "",
86
+ domain: str = "general",
87
+ reserves: float = 5_000_000.0,
88
+ dev_session: str = "",
89
+ name: str = "sdk-session",
90
+ silent: bool = False,
91
+ patch: bool = True,
92
+ ) -> SessionResult:
93
+ """
94
+ Initialize Codeastra. Patches all detected AI providers and creates
95
+ a Twin World session. Call this once at the start of your agent script.
96
+
97
+ Args:
98
+ api_key: Your Codeastra API key (or set ASTRA_API_KEY env var)
99
+ proxy_url: Proxy URL (default: https://proxy.codeastra.dev)
100
+ goal: What success looks like — Goal.approvals(100), etc.
101
+ auto_deploy: Auto-deploy to reality when goal is met
102
+ real_connections: Stripe, SendGrid, DB credentials for auto-deploy
103
+ world_id: Custom AI-generated world ID from create_world()
104
+ domain: Built-in physics domain (insurance, general, etc.)
105
+ reserves: Initial capital for physics simulation
106
+ dev_session: Optional session identifier (auto-generated if not set)
107
+ name: Session name for display
108
+ silent: Suppress console output
109
+ patch: Auto-patch all detected AI providers (default: True)
110
+
111
+ Returns:
112
+ SessionResult with session info, detected providers, and proxy URL
113
+ """
114
+ from . import config as _cfg, patch as _patch, detector as _det
115
+
116
+ # Load API key from env/config if not provided
117
+ if not api_key:
118
+ api_key = _cfg.get_api_key()
119
+ if not api_key:
120
+ raise AstraAuthError(
121
+ "API key required. Pass api_key= or set ASTRA_API_KEY env var.\n"
122
+ "Get your key at app.codeastra.dev"
123
+ )
124
+
125
+ # Load proxy URL
126
+ if not proxy_url:
127
+ proxy_url = _cfg.get_proxy_url()
128
+
129
+ # Normalize goal
130
+ if isinstance(goal, dict):
131
+ goal = _dict_to_goal(goal)
132
+
133
+ # Normalize real_connections
134
+ if isinstance(real_connections, dict):
135
+ real_connections = _dict_to_connections(real_connections)
136
+
137
+ # Update global state
138
+ session_key = dev_session or str(uuid.uuid4())
139
+ _state.update({
140
+ "api_key": api_key,
141
+ "proxy_url": proxy_url,
142
+ "dev_session": session_key,
143
+ "initialized": True,
144
+ })
145
+
146
+ # Auto-detect and patch providers
147
+ patched = []
148
+ if patch:
149
+ patched = _patch.apply_all(api_key, proxy_url, session_key)
150
+ _state["patched_providers"] = patched
151
+ if not silent and patched:
152
+ print(f"[Astra] Connected providers: {', '.join(patched)}")
153
+
154
+ # Create session on proxy
155
+ config = SessionConfig(
156
+ api_key = api_key,
157
+ proxy_url = proxy_url,
158
+ dev_session = session_key,
159
+ goal = goal,
160
+ auto_deploy = auto_deploy,
161
+ real_connections = real_connections,
162
+ world_id = world_id,
163
+ domain = domain,
164
+ reserves = reserves,
165
+ name = name,
166
+ silent = silent,
167
+ )
168
+ sess = Session(config)
169
+ result = sess.start()
170
+ _state["active_session"] = sess
171
+
172
+ # Start real-time watcher if requested or callbacks provided
173
+ if watch_progress or on_tick or on_goal or on_violation:
174
+ from .progress import WorldWatcher as _WW
175
+ watcher = _WW(
176
+ api_key = api_key,
177
+ dev_session = session_key,
178
+ proxy_url = proxy_url,
179
+ on_tick = on_tick,
180
+ on_goal = on_goal,
181
+ on_violation = on_violation,
182
+ silent = silent,
183
+ )
184
+ watcher.start()
185
+ _state["watcher"] = watcher
186
+
187
+ return result
188
+
189
+
190
+ def connect(
191
+ api_key: str = "",
192
+ **kwargs,
193
+ ) -> SessionResult:
194
+ """
195
+ Alias for init(). Auto-detects everything.
196
+
197
+ connect() without arguments reads from env vars / ~/.codeastra/config.json
198
+ """
199
+ return init(api_key=api_key, **kwargs)
200
+
201
+
202
+ # ── Session shortcuts ─────────────────────────────────────────────────────────
203
+
204
+ def session(
205
+ goal: Optional[Union[Goal, dict]] = None,
206
+ auto_deploy: bool = False,
207
+ real_connections: Optional[Union[RealConnections, dict]] = None,
208
+ **kwargs,
209
+ ) -> Session:
210
+ """
211
+ Create a new session context manager.
212
+
213
+ Usage:
214
+ with codeastra.session(goal=Goal.approvals(100)) as s:
215
+ agent.run()
216
+ print(s.report())
217
+ """
218
+ api_key = _s("api_key") or kwargs.pop("api_key", "")
219
+ proxy_url = _s("proxy_url", "https://proxy.codeastra.dev")
220
+
221
+ if isinstance(goal, dict):
222
+ goal = _dict_to_goal(goal)
223
+ if isinstance(real_connections, dict):
224
+ real_connections = _dict_to_connections(real_connections)
225
+
226
+ config = SessionConfig(
227
+ api_key = api_key,
228
+ proxy_url = proxy_url,
229
+ goal = goal,
230
+ auto_deploy = auto_deploy,
231
+ real_connections = real_connections,
232
+ **kwargs,
233
+ )
234
+ return Session(config)
235
+
236
+
237
+ # ── World Builder ─────────────────────────────────────────────────────────────
238
+
239
+ def create_world(description: str) -> dict:
240
+ """
241
+ Create a custom AI-generated Twin World from plain English.
242
+
243
+ Claude generates physics rules, hard limits, and autonomous actors
244
+ specific to your business domain.
245
+
246
+ Usage:
247
+ world = codeastra.create_world(
248
+ "I'm building a claims processing agent for an insurance company.
249
+ Approving claims depletes reserves. Fraudulent claims increase risk.
250
+ Regulator intervenes if risk exceeds 0.8."
251
+ )
252
+ codeastra.init(api_key="...", world_id=world["world_id"])
253
+ """
254
+ api_key = _s("api_key")
255
+ proxy_url = _s("proxy_url", "https://proxy.codeastra.dev")
256
+ if not api_key:
257
+ raise AstraAuthError("Call codeastra.init() or pass api_key first")
258
+
259
+ client = AstraClient(api_key, proxy_url)
260
+ result = client.create_world(description)
261
+
262
+ if not _s("silent"):
263
+ world_id = result.get("world_id", "?")
264
+ domain = result.get("domain_name", "?")
265
+ print(f"[Astra] World created: {domain} (id: {world_id})")
266
+
267
+ return result
268
+
269
+
270
+ def worlds() -> list:
271
+ """List all available worlds for your API key."""
272
+ api_key = _s("api_key")
273
+ proxy_url = _s("proxy_url", "https://proxy.codeastra.dev")
274
+ if not api_key:
275
+ raise AstraAuthError("Call codeastra.init() first")
276
+ return AstraClient(api_key, proxy_url).list_worlds().get("worlds", [])
277
+
278
+
279
+ # ── Session operations ────────────────────────────────────────────────────────
280
+
281
+ def report(dev_session: str = "") -> dict:
282
+ """Get Twin World outcome report for current or specified session."""
283
+ sess = dev_session or _s("dev_session")
284
+ if not sess:
285
+ raise AstraSessionError("No active session. Call codeastra.init() first.")
286
+ return _client().get_report(sess)
287
+
288
+
289
+ def status(dev_session: str = "") -> dict:
290
+ """Get current world state for active session."""
291
+ sess = dev_session or _s("dev_session")
292
+ if not sess:
293
+ raise AstraSessionError("No active session.")
294
+ return _client().get_session(sess)
295
+
296
+
297
+ def deploy(
298
+ real_connections: Optional[Union[RealConnections, dict]] = None,
299
+ dev_session: str = "",
300
+ ) -> dict:
301
+ """Manually deploy winning session actions to production."""
302
+ sess = dev_session or _s("dev_session")
303
+ if not sess:
304
+ raise AstraSessionError("No active session.")
305
+ if isinstance(real_connections, RealConnections):
306
+ real_connections = real_connections.to_dict()
307
+ return _client().deploy(sess, real_connections or {})
308
+
309
+
310
+ def history(dev_session: str = "") -> dict:
311
+ """Full tick-by-tick world evolution."""
312
+ sess = dev_session or _s("dev_session")
313
+ return _client().get_history(sess)
314
+
315
+
316
+ def actor_events(dev_session: str = "") -> dict:
317
+ """Full history of autonomous actor reactions."""
318
+ sess = dev_session or _s("dev_session")
319
+ return _client().get_actor_events(sess)
320
+
321
+
322
+ def fork(
323
+ branches: list[str],
324
+ dev_session: str = "",
325
+ world_id: str = "",
326
+ ) -> dict:
327
+ """
328
+ Fork current session into parallel branches for strategy comparison.
329
+
330
+ Usage:
331
+ fork_result = codeastra.fork(["aggressive", "conservative", "balanced"])
332
+ # Run agent in each branch using the returned dev_session_keys
333
+ winner = codeastra.compare()
334
+ """
335
+ sess = dev_session or _s("dev_session")
336
+ wid = world_id or _s("world_id", "")
337
+ return _client().fork(sess, branches, wid)
338
+
339
+
340
+ def compare(dev_session: str = "") -> dict:
341
+ """Compare forked branches. Returns ranked results and winner."""
342
+ sess = dev_session or _s("dev_session")
343
+ return _client().compare(sess)
344
+
345
+
346
+ def sync_csv(path: str, table: str = "data", dev_session: str = "") -> dict:
347
+ """Load CSV data into active Twin World session."""
348
+ sess = dev_session or _s("dev_session")
349
+ with open(path, "rb") as f:
350
+ return _client().sync_csv(sess, f.read(), table)
351
+
352
+
353
+ def sync_db(
354
+ connection_url: str,
355
+ query: str = "",
356
+ dev_session: str = "",
357
+ ) -> dict:
358
+ """Sync data from real database into Twin World session."""
359
+ sess = dev_session or _s("dev_session")
360
+ return _client().sync_db(sess, connection_url, query)
361
+
362
+
363
+ async def async_init(
364
+ api_key: str = "",
365
+ **kwargs,
366
+ ) -> SessionResult:
367
+ """
368
+ Async version of init() for async agent frameworks.
369
+ Usage:
370
+ await codeastra.async_init(api_key="sk-guard-...")
371
+ """
372
+ from .async_client import AsyncAstraClient as _ACC
373
+ from . import config as _cfg
374
+ if not api_key:
375
+ api_key = _cfg.get_api_key()
376
+ if not api_key:
377
+ raise AstraAuthError("api_key required")
378
+ # init() is sync but session creation can be done async separately
379
+ # For now delegate to sync init — patches happen synchronously
380
+ return init(api_key=api_key, **kwargs)
381
+
382
+
383
+ def disconnect() -> None:
384
+ """Remove all provider patches and reset state."""
385
+ from . import patch as _patch
386
+ _patch.remove_all()
387
+ _state.update({
388
+ "initialized": False,
389
+ "patched_providers": [],
390
+ "active_session": None,
391
+ })
392
+ print("[Astra] Disconnected. All patches removed.")
393
+
394
+
395
+ def health() -> dict:
396
+ """Check proxy and engine health."""
397
+ proxy_url = _s("proxy_url", "https://proxy.codeastra.dev")
398
+ api_key = _s("api_key", "")
399
+ try:
400
+ import requests
401
+ r = requests.get(f"{proxy_url}/health", timeout=5)
402
+ return r.json() if r.ok else {"status": "unreachable"}
403
+ except Exception as e:
404
+ return {"status": "unreachable", "error": str(e)}
405
+
406
+
407
+ # ── Helpers ───────────────────────────────────────────────────────────────────
408
+
409
+ def _client() -> AstraClient:
410
+ api_key = _s("api_key")
411
+ proxy_url = _s("proxy_url", "https://proxy.codeastra.dev")
412
+ if not api_key:
413
+ raise AstraAuthError("Call codeastra.init() first")
414
+ return AstraClient(api_key, proxy_url)
415
+
416
+
417
+ def _dict_to_goal(d: dict) -> Goal:
418
+ gtype = d.get("type", "")
419
+ if gtype == "world_state":
420
+ return Goal("world_state", d.get("value", 0),
421
+ d.get("field", ""), d.get("op", "gte"))
422
+ return Goal(gtype, d.get("value", 0))
423
+
424
+
425
+ def _dict_to_connections(d: dict) -> RealConnections:
426
+ return RealConnections(
427
+ stripe_secret_key = d.get("stripe_secret_key", ""),
428
+ sendgrid_api_key = d.get("sendgrid_api_key", ""),
429
+ resend_api_key = d.get("resend_api_key", ""),
430
+ slack_bot_token = d.get("slack_bot_token", ""),
431
+ database_url = d.get("database_url", ""),
432
+ from_email = d.get("from_email", ""),
433
+ webhook_url = d.get("webhook_url", ""),
434
+ )