From 423589af5a70d3144c78301189e0e0a1d4a8d2d6 Mon Sep 17 00:00:00 2001 From: kutay Date: Mon, 4 May 2026 18:15:54 +0000 Subject: [PATCH] Tag outgoing SDK calls with a lighter-agent-kit User-Agent Brand every HTTP request the kit issues through lighter-python with a `User-Agent: lighter-agent-kit/ lighter-python/` header so the server can attribute traffic to the agent kit (vs. web/mobile/ arbitrary SDK callers). Implementation lives in scripts/_sdk.py as a single helper applied at each ApiClient/SignerClient construction site. SDK version is read via importlib.metadata so it tracks the actually-installed package, with a fallback to lighter.__version__ and finally 'unknown'. --- scripts/_sdk.py | 42 +++++++++++++++++++++++++++++++++++ scripts/paper.py | 4 +++- scripts/query.py | 3 +++ scripts/trade.py | 3 ++- tests/test_trade.py | 1 + tests/test_trade_close_all.py | 1 + 6 files changed, 52 insertions(+), 2 deletions(-) diff --git a/scripts/_sdk.py b/scripts/_sdk.py index 7f271eb..621c0be 100644 --- a/scripts/_sdk.py +++ b/scripts/_sdk.py @@ -32,6 +32,7 @@ VENDOR_DIR = os.path.join(_SKILL_DIR, ".vendor", _PY_TAG) LOCKFILE = os.path.join(_SKILL_DIR, "requirements.lock") DEFAULT_HOST = "https://mainnet.zklighter.elliot.ai" +AGENT_KIT_VERSION = "0.1.0" _CREDENTIALS = None # Keys whose values must never appear in logs, tracebacks, or agent output. @@ -280,3 +281,44 @@ def ensure_lighter(): ) ) sys.exit(1) + + +def _resolve_sdk_version(): + """Return the installed `lighter-sdk` distribution version, falling back to + the in-module `lighter.__version__` (which can lag the package metadata) + and finally `unknown`. Pure resolution; never raises. + """ + try: + from importlib.metadata import version, PackageNotFoundError + except ImportError: + from importlib_metadata import version, PackageNotFoundError # type: ignore[no-redef] + try: + return version("lighter-sdk") + except PackageNotFoundError: + pass + try: + import lighter + return getattr(lighter, "__version__", "unknown") or "unknown" + except Exception: + return "unknown" + + +def user_agent(): + """`User-Agent` value identifying agent-kit traffic on the server side. + + Format follows RFC 7231: ``lighter-agent-kit/ lighter-python/``. + Computed lazily because resolving the SDK version requires the vendored + install to be on `sys.path`, which only happens after `ensure_lighter()`. + """ + return f"lighter-agent-kit/{AGENT_KIT_VERSION} lighter-python/{_resolve_sdk_version()}" + + +def tag_api_client(api_client): + """Brand outgoing HTTP requests so the server can attribute traffic to the + agent kit. Idempotent — safe to apply more than once. + + Pass the `lighter.ApiClient` instance directly, or `signer.api_client` for + a `SignerClient`. Returns the same client for fluent use. + """ + api_client.user_agent = user_agent() + return api_client diff --git a/scripts/paper.py b/scripts/paper.py index b35a793..f582e54 100644 --- a/scripts/paper.py +++ b/scripts/paper.py @@ -20,7 +20,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from _cli import JsonArgumentParser, error, output # noqa: E402 from _paths import paper_state_path # noqa: E402 -from _sdk import DEFAULT_HOST, ensure_lighter, get_config_value # noqa: E402 +from _sdk import DEFAULT_HOST, ensure_lighter, get_config_value, tag_api_client # noqa: E402 from _symbols import normalize_side, resolve_symbol # noqa: E402 ensure_lighter() @@ -375,6 +375,7 @@ async def _run_read(operation, *, refresh=True): async with lighter.ApiClient( configuration=lighter.Configuration(host=host), ) as api_client: + tag_api_client(api_client) paper = _hydrate_paper_client(api_client, tier_enum, account, configs) if refresh: _, failures = await _refresh_position_markets(paper) @@ -397,6 +398,7 @@ async def _run_with_paper_market(symbol, operation=None): async with lighter.ApiClient( configuration=lighter.Configuration(host=host), ) as api_client: + tag_api_client(api_client) try: market_id, market_type, _ = await resolve_symbol( symbol, diff --git a/scripts/query.py b/scripts/query.py index bd0e07b..f6226c5 100644 --- a/scripts/query.py +++ b/scripts/query.py @@ -18,6 +18,7 @@ ensure_lighter, get_config_value, resolve_with_source, + tag_api_client, ) from _symbols import resolve_symbol # noqa: E402 @@ -402,6 +403,7 @@ async def get_auth_token(host): account_index=account_index, api_private_keys={api_key_index: api_private_key.expose()}, ) + tag_api_client(signer.api_client) except Exception as e: error(f"failed to initialize signer for auth token: {e}") try: @@ -487,6 +489,7 @@ async def run(args): try: async with lighter.ApiClient(configuration=config) as client: + tag_api_client(client) group = args.group action = args.action diff --git a/scripts/trade.py b/scripts/trade.py index 0b909e0..a816f50 100644 --- a/scripts/trade.py +++ b/scripts/trade.py @@ -13,7 +13,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from _cli import JsonArgumentParser, error, output # noqa: E402 -from _sdk import DEFAULT_HOST, ensure_lighter, get_config_value # noqa: E402 +from _sdk import DEFAULT_HOST, ensure_lighter, get_config_value, tag_api_client # noqa: E402 from _symbols import normalize_side, resolve_symbol, side_to_is_ask # noqa: E402 ensure_lighter() @@ -323,6 +323,7 @@ async def build_signer_client(): account_index=account_index, api_private_keys={api_key_index: api_private_key.expose()}, ) + tag_api_client(client.api_client) except Exception as e: error(f"failed to initialize signer: {clean_sdk_error(str(e))}") diff --git a/tests/test_trade.py b/tests/test_trade.py index 88f5217..9ccf79c 100644 --- a/tests/test_trade.py +++ b/tests/test_trade.py @@ -40,6 +40,7 @@ class JsonArgumentParser: # pragma: no cover - import stub only sdk_mod.DEFAULT_HOST = "https://mainnet.zklighter.elliot.ai" sdk_mod.ensure_lighter = lambda: None sdk_mod.get_config_value = lambda *args, **kwargs: None + sdk_mod.tag_api_client = lambda api_client: api_client symbols_mod = types.ModuleType("_symbols") symbols_mod.normalize_side = lambda side, market_type: side diff --git a/tests/test_trade_close_all.py b/tests/test_trade_close_all.py index d955281..0753bfc 100644 --- a/tests/test_trade_close_all.py +++ b/tests/test_trade_close_all.py @@ -40,6 +40,7 @@ class JsonArgumentParser: # pragma: no cover - import stub only sdk_mod.DEFAULT_HOST = "https://mainnet.zklighter.elliot.ai" sdk_mod.ensure_lighter = lambda: None sdk_mod.get_config_value = lambda *args, **kwargs: None + sdk_mod.tag_api_client = lambda api_client: api_client symbols_mod = types.ModuleType("_symbols") symbols_mod.normalize_side = lambda side, market_type: side