agentrust-py 0.0.3__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.
- agentrust/__init__.py +72 -0
- agentrust_py-0.0.3.dist-info/METADATA +193 -0
- agentrust_py-0.0.3.dist-info/RECORD +29 -0
- agentrust_py-0.0.3.dist-info/WHEEL +4 -0
- agentrust_py-0.0.3.dist-info/entry_points.txt +2 -0
- agentrust_py-0.0.3.dist-info/licenses/LICENSE +177 -0
- agentrust_sdk/__init__.py +124 -0
- agentrust_sdk/adapters/__init__.py +1 -0
- agentrust_sdk/adapters/autogen.py +235 -0
- agentrust_sdk/adapters/claude_agents.py +225 -0
- agentrust_sdk/adapters/crewai.py +98 -0
- agentrust_sdk/adapters/langgraph.py +109 -0
- agentrust_sdk/adapters/mcp.py +193 -0
- agentrust_sdk/adapters/openai_agents.py +263 -0
- agentrust_sdk/auth.py +192 -0
- agentrust_sdk/auto.py +397 -0
- agentrust_sdk/autoload.py +95 -0
- agentrust_sdk/cli.py +736 -0
- agentrust_sdk/client.py +790 -0
- agentrust_sdk/config.py +192 -0
- agentrust_sdk/decorator.py +276 -0
- agentrust_sdk/embedded.py +428 -0
- agentrust_sdk/hooks.py +461 -0
- agentrust_sdk/models.py +81 -0
- agentrust_sdk/py.typed +0 -0
- agentrust_sdk/queue_replay.py +204 -0
- agentrust_sdk/tiers.py +180 -0
- agentrust_sdk/version_negotiation.py +290 -0
- agentrust_sdk/webhooks.py +782 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgentTrust EmbeddedGateway — zero-dependency local governance.
|
|
3
|
+
|
|
4
|
+
Ships as part of agentrust[embedded]. Starts a minimal FastAPI app in a
|
|
5
|
+
background thread backed by SQLite — no PostgreSQL, no Redis, no Docker.
|
|
6
|
+
|
|
7
|
+
Usage::
|
|
8
|
+
|
|
9
|
+
from agentrust import embed_gateway, harness
|
|
10
|
+
|
|
11
|
+
embed_gateway() # starts in-process gateway on :8765
|
|
12
|
+
|
|
13
|
+
@harness
|
|
14
|
+
def my_agent(user: str, input: str) -> dict:
|
|
15
|
+
return {"answer": "42"}
|
|
16
|
+
|
|
17
|
+
The gateway runs only the fast deterministic checks (schema + basic policy).
|
|
18
|
+
For full risk scoring, LLM judge, and analytics, deploy the full gateway.
|
|
19
|
+
|
|
20
|
+
Stop it::
|
|
21
|
+
|
|
22
|
+
gw = embed_gateway() # returns EmbeddedGateway instance
|
|
23
|
+
gw.stop()
|
|
24
|
+
|
|
25
|
+
Or use as context manager::
|
|
26
|
+
|
|
27
|
+
with EmbeddedGateway() as gw:
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
Env vars respected
|
|
31
|
+
------------------
|
|
32
|
+
AGENTRUST_EMBED_PORT Port (default 8765)
|
|
33
|
+
AGENTRUST_EMBED_DB SQLite db path (default ~/.agentrust/embedded.db)
|
|
34
|
+
Use ":memory:" for ephemeral (lost on stop)
|
|
35
|
+
AGENTRUST_EMBED_TOKEN Bearer token required on all non-health endpoints.
|
|
36
|
+
When unset, a random token is generated at startup and
|
|
37
|
+
logged at INFO level. Set explicitly for reproducible auth.
|
|
38
|
+
AGENTRUST_ENABLED When false, embed_gateway() is a no-op
|
|
39
|
+
"""
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
|
|
42
|
+
import logging
|
|
43
|
+
import os
|
|
44
|
+
import secrets
|
|
45
|
+
import sqlite3
|
|
46
|
+
import threading
|
|
47
|
+
import time
|
|
48
|
+
from pathlib import Path
|
|
49
|
+
from typing import Any
|
|
50
|
+
from uuid import uuid4
|
|
51
|
+
|
|
52
|
+
logger = logging.getLogger(__name__)
|
|
53
|
+
|
|
54
|
+
_DEFAULT_PORT = int(os.environ.get("AGENTRUST_EMBED_PORT", "8765"))
|
|
55
|
+
_DEFAULT_DB = os.environ.get(
|
|
56
|
+
"AGENTRUST_EMBED_DB",
|
|
57
|
+
str(Path.home() / ".agentrust" / "embedded.db"),
|
|
58
|
+
)
|
|
59
|
+
_MAX_LIST_LIMIT = 200 # server-side cap on /v1/audit/executions?limit=
|
|
60
|
+
|
|
61
|
+
# Module-level singleton so embed_gateway() is idempotent
|
|
62
|
+
_INSTANCE: "EmbeddedGateway | None" = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class EmbeddedGateway:
|
|
66
|
+
"""
|
|
67
|
+
Minimal in-process governance gateway backed by SQLite.
|
|
68
|
+
|
|
69
|
+
Supports:
|
|
70
|
+
- Schema validation (output must be non-empty JSON object)
|
|
71
|
+
- Basic policy checks (output keys, type enforcement)
|
|
72
|
+
- Append-only SQLite audit log
|
|
73
|
+
- governance_disclosure + confidence_rationale fields
|
|
74
|
+
- All 5 decision outcomes: approve/retry/request_evidence/escalate/block
|
|
75
|
+
|
|
76
|
+
Does NOT support (requires full gateway):
|
|
77
|
+
- LLM judge slow-path
|
|
78
|
+
- Risk engine (scores all LOW)
|
|
79
|
+
- Human review queue
|
|
80
|
+
- Webhook notifications
|
|
81
|
+
- Multi-tenant isolation
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
port: int = _DEFAULT_PORT,
|
|
87
|
+
db_path: str = _DEFAULT_DB,
|
|
88
|
+
token: str | None = None,
|
|
89
|
+
) -> None:
|
|
90
|
+
self._port = port
|
|
91
|
+
self._db_path = db_path
|
|
92
|
+
self._server: Any = None
|
|
93
|
+
self._thread: threading.Thread | None = None
|
|
94
|
+
self._ready = threading.Event()
|
|
95
|
+
# A-3: bearer token for all non-health endpoints.
|
|
96
|
+
# Caller may pass an explicit token; otherwise a random one is generated
|
|
97
|
+
# and emitted to logs so the SDK client can pick it up.
|
|
98
|
+
env_token = os.environ.get("AGENTRUST_EMBED_TOKEN", "").strip()
|
|
99
|
+
self._token: str = token or env_token or secrets.token_hex(32)
|
|
100
|
+
if not (token or env_token):
|
|
101
|
+
logger.info(
|
|
102
|
+
"[AgentTrust] EmbeddedGateway auto-generated bearer token: %s "
|
|
103
|
+
"(set AGENTRUST_EMBED_TOKEN env var to pin this across restarts)",
|
|
104
|
+
self._token,
|
|
105
|
+
)
|
|
106
|
+
self._app = self._build_app()
|
|
107
|
+
|
|
108
|
+
# ------------------------------------------------------------------
|
|
109
|
+
# Lifecycle
|
|
110
|
+
# ------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
def start(self) -> "EmbeddedGateway":
|
|
113
|
+
if self._thread and self._thread.is_alive():
|
|
114
|
+
return self
|
|
115
|
+
self._init_db()
|
|
116
|
+
self._thread = threading.Thread(
|
|
117
|
+
target=self._run_server, daemon=True, name="agentrust-embedded"
|
|
118
|
+
)
|
|
119
|
+
self._thread.start()
|
|
120
|
+
if not self._ready.wait(timeout=10):
|
|
121
|
+
raise RuntimeError(
|
|
122
|
+
f"EmbeddedGateway did not start within 10s on port {self._port}"
|
|
123
|
+
)
|
|
124
|
+
logger.info(
|
|
125
|
+
"[AgentTrust] EmbeddedGateway started on http://127.0.0.1:%d "
|
|
126
|
+
"(SQLite: %s)", self._port, self._db_path
|
|
127
|
+
)
|
|
128
|
+
return self
|
|
129
|
+
|
|
130
|
+
def stop(self) -> None:
|
|
131
|
+
if self._server:
|
|
132
|
+
self._server.should_exit = True
|
|
133
|
+
if self._thread:
|
|
134
|
+
self._thread.join(timeout=5)
|
|
135
|
+
logger.info("[AgentTrust] EmbeddedGateway stopped.")
|
|
136
|
+
|
|
137
|
+
def __enter__(self) -> "EmbeddedGateway":
|
|
138
|
+
return self.start()
|
|
139
|
+
|
|
140
|
+
def __exit__(self, *args: Any) -> None:
|
|
141
|
+
self.stop()
|
|
142
|
+
|
|
143
|
+
# ------------------------------------------------------------------
|
|
144
|
+
# Internal: SQLite
|
|
145
|
+
# ------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
def _init_db(self) -> None:
|
|
148
|
+
if self._db_path == ":memory:":
|
|
149
|
+
# Will be created per-connection; not shareable across threads,
|
|
150
|
+
# but fine for unit tests. Use file path for persistence.
|
|
151
|
+
return
|
|
152
|
+
Path(self._db_path).parent.mkdir(parents=True, exist_ok=True)
|
|
153
|
+
con = sqlite3.connect(self._db_path)
|
|
154
|
+
con.execute("PRAGMA journal_mode=WAL")
|
|
155
|
+
con.execute("PRAGMA synchronous=NORMAL")
|
|
156
|
+
con.execute(
|
|
157
|
+
"""
|
|
158
|
+
CREATE TABLE IF NOT EXISTS executions (
|
|
159
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
160
|
+
envelope_id TEXT NOT NULL,
|
|
161
|
+
agent_id TEXT NOT NULL,
|
|
162
|
+
decision TEXT NOT NULL,
|
|
163
|
+
risk_tier TEXT NOT NULL DEFAULT 'low',
|
|
164
|
+
final_confidence REAL NOT NULL DEFAULT 100.0,
|
|
165
|
+
request_json TEXT,
|
|
166
|
+
output_json TEXT,
|
|
167
|
+
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
|
|
168
|
+
)
|
|
169
|
+
"""
|
|
170
|
+
)
|
|
171
|
+
con.commit()
|
|
172
|
+
con.close()
|
|
173
|
+
|
|
174
|
+
def _save_execution(self, envelope_id: str, agent_id: str,
|
|
175
|
+
decision: str, payload: dict[str, Any]) -> None:
|
|
176
|
+
import json
|
|
177
|
+
if self._db_path == ":memory:":
|
|
178
|
+
return
|
|
179
|
+
try:
|
|
180
|
+
con = sqlite3.connect(self._db_path)
|
|
181
|
+
con.execute(
|
|
182
|
+
"INSERT INTO executions "
|
|
183
|
+
"(envelope_id, agent_id, decision, request_json, output_json) "
|
|
184
|
+
"VALUES (?, ?, ?, ?, ?)",
|
|
185
|
+
(
|
|
186
|
+
envelope_id, agent_id, decision,
|
|
187
|
+
json.dumps(payload.get("request", {})),
|
|
188
|
+
json.dumps(payload.get("output", {})),
|
|
189
|
+
),
|
|
190
|
+
)
|
|
191
|
+
con.commit()
|
|
192
|
+
con.close()
|
|
193
|
+
except Exception as exc:
|
|
194
|
+
logger.warning("[AgentTrust] Embedded audit write failed: %s", exc)
|
|
195
|
+
|
|
196
|
+
# ------------------------------------------------------------------
|
|
197
|
+
# Internal: FastAPI app
|
|
198
|
+
# ------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
def _build_app(self) -> Any:
|
|
201
|
+
try:
|
|
202
|
+
from fastapi import FastAPI, Request
|
|
203
|
+
from fastapi.responses import JSONResponse
|
|
204
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
205
|
+
except ImportError:
|
|
206
|
+
raise ImportError(
|
|
207
|
+
"FastAPI is required for EmbeddedGateway. "
|
|
208
|
+
"Install it: pip install 'agentrust-sdk[embedded]'"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
app = FastAPI(
|
|
212
|
+
title="AgentTrust EmbeddedGateway",
|
|
213
|
+
version="embedded",
|
|
214
|
+
docs_url=None,
|
|
215
|
+
redoc_url=None,
|
|
216
|
+
openapi_url=None,
|
|
217
|
+
)
|
|
218
|
+
_gw = self # closure reference
|
|
219
|
+
|
|
220
|
+
# Reject oversized request bodies (default 1 MB) to prevent memory exhaustion
|
|
221
|
+
_MAX_BODY_BYTES = 1 * 1024 * 1024
|
|
222
|
+
|
|
223
|
+
class _BodySizeLimiter(BaseHTTPMiddleware):
|
|
224
|
+
async def dispatch(self, request: Request, call_next):
|
|
225
|
+
content_length = request.headers.get("content-length")
|
|
226
|
+
if content_length and int(content_length) > _MAX_BODY_BYTES:
|
|
227
|
+
return JSONResponse(
|
|
228
|
+
{"detail": f"Request body too large (max {_MAX_BODY_BYTES} bytes)"},
|
|
229
|
+
status_code=413,
|
|
230
|
+
)
|
|
231
|
+
return await call_next(request)
|
|
232
|
+
|
|
233
|
+
# A-3: bearer token enforcement on all non-health endpoints
|
|
234
|
+
_expected_token = _gw._token
|
|
235
|
+
|
|
236
|
+
class _TokenAuth(BaseHTTPMiddleware):
|
|
237
|
+
async def dispatch(self, request: Request, call_next):
|
|
238
|
+
if request.url.path == "/v1/health":
|
|
239
|
+
return await call_next(request)
|
|
240
|
+
auth = request.headers.get("authorization", "")
|
|
241
|
+
provided = auth.removeprefix("Bearer ").strip()
|
|
242
|
+
if not secrets.compare_digest(provided, _expected_token):
|
|
243
|
+
return JSONResponse(
|
|
244
|
+
{"detail": "Invalid or missing Bearer token"},
|
|
245
|
+
status_code=401,
|
|
246
|
+
)
|
|
247
|
+
return await call_next(request)
|
|
248
|
+
|
|
249
|
+
app.add_middleware(_BodySizeLimiter)
|
|
250
|
+
app.add_middleware(_TokenAuth)
|
|
251
|
+
|
|
252
|
+
@app.get("/v1/health")
|
|
253
|
+
async def health() -> dict:
|
|
254
|
+
return {"status": "ok", "mode": "embedded", "db": _gw._db_path}
|
|
255
|
+
|
|
256
|
+
@app.post("/v1/runtime/validate")
|
|
257
|
+
async def validate(body: dict) -> dict:
|
|
258
|
+
return await _gw._handle_validate(body)
|
|
259
|
+
|
|
260
|
+
@app.get("/v1/audit/executions")
|
|
261
|
+
async def list_executions(limit: int = 20) -> dict:
|
|
262
|
+
# Server-side cap to prevent runaway memory reads
|
|
263
|
+
return _gw._list_executions(min(limit, _MAX_LIST_LIMIT))
|
|
264
|
+
|
|
265
|
+
return app
|
|
266
|
+
|
|
267
|
+
async def _handle_validate(self, body: dict[str, Any]) -> dict[str, Any]:
|
|
268
|
+
envelope_id = str(uuid4())
|
|
269
|
+
agent_id = body.get("agent_id", "unknown")
|
|
270
|
+
output = body.get("output", {})
|
|
271
|
+
request = body.get("request", {})
|
|
272
|
+
|
|
273
|
+
failures: list[str] = []
|
|
274
|
+
schema_score = 100.0
|
|
275
|
+
|
|
276
|
+
# Schema check
|
|
277
|
+
if not isinstance(output, dict):
|
|
278
|
+
failures.append("schema: output must be a JSON object")
|
|
279
|
+
schema_score = 0.0
|
|
280
|
+
elif not output:
|
|
281
|
+
failures.append("schema: output is empty — agent produced no result")
|
|
282
|
+
schema_score = 40.0
|
|
283
|
+
|
|
284
|
+
# Basic policy check: no error keys in output
|
|
285
|
+
if isinstance(output, dict) and output.get("error"):
|
|
286
|
+
failures.append("policy: output contains error field")
|
|
287
|
+
schema_score = min(schema_score, 50.0)
|
|
288
|
+
|
|
289
|
+
final_confidence = schema_score
|
|
290
|
+
decision = "approve" if not failures else ("block" if schema_score == 0 else "retry")
|
|
291
|
+
|
|
292
|
+
governance_disclosure = (
|
|
293
|
+
f"Evaluated by AgentTrust EmbeddedGateway "
|
|
294
|
+
f"(envelope_id={envelope_id}, agent_id={agent_id}). "
|
|
295
|
+
f"Decision: {decision}. Confidence: {final_confidence:.1f}%. "
|
|
296
|
+
f"Mode: embedded (schema + basic policy checks only)."
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
self._save_execution(envelope_id, agent_id, decision, body)
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
"envelope_id": envelope_id,
|
|
303
|
+
"validation": {
|
|
304
|
+
"schema_score": schema_score,
|
|
305
|
+
"evidence_score": 0.0,
|
|
306
|
+
"tool_trust_score": 0.0,
|
|
307
|
+
"consistency_score": 0.0,
|
|
308
|
+
"policy_score": schema_score,
|
|
309
|
+
"final_confidence": final_confidence,
|
|
310
|
+
"failures": failures,
|
|
311
|
+
},
|
|
312
|
+
"risk": {
|
|
313
|
+
"tier": "low",
|
|
314
|
+
"score": 0.0,
|
|
315
|
+
"reason": "Embedded mode: risk engine not available",
|
|
316
|
+
},
|
|
317
|
+
"decision": {
|
|
318
|
+
"outcome": decision,
|
|
319
|
+
"reason": failures[0] if failures else "All embedded checks passed",
|
|
320
|
+
"policy_version": "embedded-v1",
|
|
321
|
+
},
|
|
322
|
+
"latency_ms": 0.0,
|
|
323
|
+
"governance_disclosure": governance_disclosure,
|
|
324
|
+
"confidence_rationale": (
|
|
325
|
+
f"Schema check: {schema_score:.0f}/100. "
|
|
326
|
+
"Full confidence engine requires full gateway deployment."
|
|
327
|
+
),
|
|
328
|
+
"trust_chain": None,
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
def _list_executions(self, limit: int) -> dict[str, Any]:
|
|
332
|
+
if self._db_path == ":memory:":
|
|
333
|
+
return {"items": [], "note": "In-memory mode: no persisted records"}
|
|
334
|
+
try:
|
|
335
|
+
con = sqlite3.connect(self._db_path)
|
|
336
|
+
con.row_factory = sqlite3.Row
|
|
337
|
+
rows = con.execute(
|
|
338
|
+
"SELECT * FROM executions ORDER BY timestamp DESC LIMIT ?", (limit,)
|
|
339
|
+
).fetchall()
|
|
340
|
+
con.close()
|
|
341
|
+
return {"items": [dict(r) for r in rows], "total": len(rows)}
|
|
342
|
+
except Exception as exc:
|
|
343
|
+
return {"items": [], "error": str(exc)}
|
|
344
|
+
|
|
345
|
+
# ------------------------------------------------------------------
|
|
346
|
+
# Internal: server runner
|
|
347
|
+
# ------------------------------------------------------------------
|
|
348
|
+
|
|
349
|
+
def _run_server(self) -> None:
|
|
350
|
+
try:
|
|
351
|
+
import uvicorn
|
|
352
|
+
except ImportError:
|
|
353
|
+
raise ImportError(
|
|
354
|
+
"uvicorn is required for EmbeddedGateway. "
|
|
355
|
+
"Install it: pip install 'agentrust-sdk[embedded]'"
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
config = uvicorn.Config(
|
|
359
|
+
app=self._app,
|
|
360
|
+
host="127.0.0.1",
|
|
361
|
+
port=self._port,
|
|
362
|
+
log_level="error",
|
|
363
|
+
access_log=False,
|
|
364
|
+
)
|
|
365
|
+
self._server = uvicorn.Server(config)
|
|
366
|
+
|
|
367
|
+
# Signal ready once the server loop is about to start
|
|
368
|
+
original_startup = self._server.startup
|
|
369
|
+
|
|
370
|
+
async def _startup_with_signal(*args: Any, **kw: Any) -> None:
|
|
371
|
+
await original_startup(*args, **kw)
|
|
372
|
+
self._ready.set()
|
|
373
|
+
|
|
374
|
+
self._server.startup = _startup_with_signal
|
|
375
|
+
self._server.run()
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
# ---------------------------------------------------------------------------
|
|
379
|
+
# Public helper
|
|
380
|
+
# ---------------------------------------------------------------------------
|
|
381
|
+
|
|
382
|
+
def embed_gateway(
|
|
383
|
+
port: int = _DEFAULT_PORT,
|
|
384
|
+
db_path: str = _DEFAULT_DB,
|
|
385
|
+
set_env: bool = True,
|
|
386
|
+
) -> "EmbeddedGateway":
|
|
387
|
+
"""
|
|
388
|
+
Start an in-process EmbeddedGateway and (optionally) set AGENTRUST_GATEWAY_URL
|
|
389
|
+
so all SDK clients automatically route to it.
|
|
390
|
+
|
|
391
|
+
Idempotent — calling twice returns the same instance.
|
|
392
|
+
|
|
393
|
+
Parameters
|
|
394
|
+
----------
|
|
395
|
+
port: Port to listen on (default 8765, configurable via AGENTRUST_EMBED_PORT)
|
|
396
|
+
db_path: SQLite database file path (default ~/.agentrust/embedded.db)
|
|
397
|
+
set_env: When True, sets AGENTRUST_GATEWAY_URL=http://127.0.0.1:{port}
|
|
398
|
+
so @harness picks it up automatically.
|
|
399
|
+
|
|
400
|
+
Returns the running EmbeddedGateway instance.
|
|
401
|
+
"""
|
|
402
|
+
from .config import SDK_CONFIG
|
|
403
|
+
|
|
404
|
+
if not SDK_CONFIG.enabled:
|
|
405
|
+
logger.info("[AgentTrust] AGENTRUST_ENABLED=false — embed_gateway() is a no-op")
|
|
406
|
+
|
|
407
|
+
class _NoOp:
|
|
408
|
+
def stop(self) -> None: ...
|
|
409
|
+
def __enter__(self): return self
|
|
410
|
+
def __exit__(self, *a: Any): ...
|
|
411
|
+
|
|
412
|
+
return _NoOp() # type: ignore[return-value]
|
|
413
|
+
|
|
414
|
+
global _INSTANCE
|
|
415
|
+
if _INSTANCE is not None:
|
|
416
|
+
return _INSTANCE
|
|
417
|
+
|
|
418
|
+
gw = EmbeddedGateway(port=port, db_path=db_path)
|
|
419
|
+
gw.start()
|
|
420
|
+
|
|
421
|
+
if set_env:
|
|
422
|
+
# SDK_CONFIG.gateway_url and api_key are properties that read from os.environ,
|
|
423
|
+
# so setting env vars here is sufficient for all clients created afterwards.
|
|
424
|
+
os.environ["AGENTRUST_GATEWAY_URL"] = f"http://127.0.0.1:{port}"
|
|
425
|
+
os.environ["AGENTRUST_KEY"] = gw._token
|
|
426
|
+
|
|
427
|
+
_INSTANCE = gw
|
|
428
|
+
return gw
|