spendguard-sdk 0.1.0a1__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.
Files changed (34) hide show
  1. spendguard_sdk-0.1.0a1/.gitignore +25 -0
  2. spendguard_sdk-0.1.0a1/CHANGELOG.md +44 -0
  3. spendguard_sdk-0.1.0a1/Makefile +61 -0
  4. spendguard_sdk-0.1.0a1/PKG-INFO +152 -0
  5. spendguard_sdk-0.1.0a1/README.md +107 -0
  6. spendguard_sdk-0.1.0a1/examples_pydantic_ai/basic_agent.py +109 -0
  7. spendguard_sdk-0.1.0a1/pyproject.toml +87 -0
  8. spendguard_sdk-0.1.0a1/src/spendguard/__init__.py +70 -0
  9. spendguard_sdk-0.1.0a1/src/spendguard/_proto/__init__.py +8 -0
  10. spendguard_sdk-0.1.0a1/src/spendguard/_proto/spendguard/__init__.py +0 -0
  11. spendguard_sdk-0.1.0a1/src/spendguard/_proto/spendguard/common/__init__.py +0 -0
  12. spendguard_sdk-0.1.0a1/src/spendguard/_proto/spendguard/common/v1/__init__.py +0 -0
  13. spendguard_sdk-0.1.0a1/src/spendguard/_proto/spendguard/common/v1/common_pb2.py +77 -0
  14. spendguard_sdk-0.1.0a1/src/spendguard/_proto/spendguard/common/v1/common_pb2.pyi +286 -0
  15. spendguard_sdk-0.1.0a1/src/spendguard/_proto/spendguard/common/v1/common_pb2_grpc.py +24 -0
  16. spendguard_sdk-0.1.0a1/src/spendguard/_proto/spendguard/ledger/__init__.py +0 -0
  17. spendguard_sdk-0.1.0a1/src/spendguard/_proto/spendguard/ledger/v1/__init__.py +0 -0
  18. spendguard_sdk-0.1.0a1/src/spendguard/_proto/spendguard/ledger/v1/ledger_pb2.py +120 -0
  19. spendguard_sdk-0.1.0a1/src/spendguard/_proto/spendguard/ledger/v1/ledger_pb2.pyi +645 -0
  20. spendguard_sdk-0.1.0a1/src/spendguard/_proto/spendguard/ledger/v1/ledger_pb2_grpc.py +656 -0
  21. spendguard_sdk-0.1.0a1/src/spendguard/_proto/spendguard/sidecar_adapter/__init__.py +0 -0
  22. spendguard_sdk-0.1.0a1/src/spendguard/_proto/spendguard/sidecar_adapter/v1/__init__.py +0 -0
  23. spendguard_sdk-0.1.0a1/src/spendguard/_proto/spendguard/sidecar_adapter/v1/adapter_pb2.py +101 -0
  24. spendguard_sdk-0.1.0a1/src/spendguard/_proto/spendguard/sidecar_adapter/v1/adapter_pb2.pyi +446 -0
  25. spendguard_sdk-0.1.0a1/src/spendguard/_proto/spendguard/sidecar_adapter/v1/adapter_pb2_grpc.py +436 -0
  26. spendguard_sdk-0.1.0a1/src/spendguard/client.py +721 -0
  27. spendguard_sdk-0.1.0a1/src/spendguard/errors.py +95 -0
  28. spendguard_sdk-0.1.0a1/src/spendguard/ids.py +184 -0
  29. spendguard_sdk-0.1.0a1/src/spendguard/integrations/__init__.py +12 -0
  30. spendguard_sdk-0.1.0a1/src/spendguard/integrations/agt.py +278 -0
  31. spendguard_sdk-0.1.0a1/src/spendguard/integrations/langchain.py +342 -0
  32. spendguard_sdk-0.1.0a1/src/spendguard/integrations/openai_agents.py +302 -0
  33. spendguard_sdk-0.1.0a1/src/spendguard/integrations/pydantic_ai.py +657 -0
  34. spendguard_sdk-0.1.0a1/src/spendguard/pricing.py +97 -0
@@ -0,0 +1,25 @@
1
+ .ait/
2
+
3
+ # AIT framework local hook config (absolute paths to user pipx venv).
4
+ .claude/
5
+ .codex/
6
+
7
+ # direnv local PATH setup (regenerated per checkout).
8
+ .envrc
9
+
10
+ # AIT per-session context file (regenerated each session).
11
+ .ait-context.md
12
+
13
+ # Python build / cache artifacts (Phase 4 O1 SDK + generated proto stubs).
14
+ __pycache__/
15
+ *.pyc
16
+ sdk/python/src/spendguard/_proto/spendguard/
17
+
18
+ # Terraform local state (Phase 4 O9).
19
+ terraform/**/.terraform/
20
+ terraform/**/.terraform.lock.hcl
21
+ terraform/**/terraform.tfstate
22
+ terraform/**/terraform.tfstate.backup
23
+
24
+ # mkdocs build output (Phase 4 O10).
25
+ docs/site/site/
@@ -0,0 +1,44 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0a1 (Phase 4 O1) — 2026-05-09
4
+
5
+ Initial SDK release. Restructured from `spendguard-pydantic-ai` to the
6
+ multi-framework `spendguard-sdk` with optional extras.
7
+
8
+ ### Added
9
+
10
+ - Top-level package `spendguard` with framework-agnostic core
11
+ (`SpendGuardClient`, `DecisionStopped`, etc.)
12
+ - `spendguard.integrations.pydantic_ai` (was `spendguard_pydantic_ai`
13
+ top-level) — gated behind `pip install 'spendguard-sdk[pydantic-ai]'`
14
+ - Slots reserved for `spendguard.integrations.langchain`,
15
+ `spendguard.integrations.langgraph`,
16
+ `spendguard.integrations.openai_agents` (Phase 4 O5).
17
+
18
+ ### Changed
19
+
20
+ - Package name: `spendguard-pydantic-ai` → `spendguard-sdk`
21
+ - Python module: `spendguard_pydantic_ai` → `spendguard`
22
+ - Pydantic-AI wrapper moved from top-level to
23
+ `spendguard.integrations.pydantic_ai`
24
+ - Internal contextvar renamed `spendguard_pydantic_ai_run_context` →
25
+ `spendguard_run_context`
26
+
27
+ ### Migration from `spendguard-pydantic-ai`
28
+
29
+ ```python
30
+ # Before
31
+ from spendguard_pydantic_ai import SpendGuardClient, SpendGuardModel, RunContext
32
+
33
+ # After
34
+ from spendguard import SpendGuardClient
35
+ from spendguard.integrations.pydantic_ai import SpendGuardModel, RunContext
36
+ ```
37
+
38
+ ```bash
39
+ # Before
40
+ pip install spendguard-pydantic-ai
41
+
42
+ # After
43
+ pip install 'spendguard-sdk[pydantic-ai]'
44
+ ```
@@ -0,0 +1,61 @@
1
+ PROTO_ROOT := $(realpath $(CURDIR)/../../proto)
2
+ OUT_DIR := $(CURDIR)/src/spendguard/_proto
3
+ # ledger.proto is included for the demo's webhook simulator path
4
+ # (talks directly to ledger gRPC for Step 8 dev convenience).
5
+ # Production adapters do NOT use ledger.proto; sidecar is the only
6
+ # ledger client per Stage 2 §3.1.
7
+ PROTOS := \
8
+ $(PROTO_ROOT)/spendguard/common/v1/common.proto \
9
+ $(PROTO_ROOT)/spendguard/sidecar_adapter/v1/adapter.proto \
10
+ $(PROTO_ROOT)/spendguard/ledger/v1/ledger.proto
11
+
12
+ .PHONY: proto clean lint typecheck test build publish-test publish
13
+
14
+ # Regenerate Python protobuf + gRPC stubs from the wire spec.
15
+ # Output lands under src/spendguard/_proto/spendguard/...
16
+ # Generated code uses absolute imports rooted at `spendguard.*`; we
17
+ # rewrite them to `spendguard._proto.spendguard.*` so the wheel ships
18
+ # a self-contained import tree.
19
+ proto:
20
+ @mkdir -p $(OUT_DIR)
21
+ python -m grpc_tools.protoc \
22
+ --proto_path=$(PROTO_ROOT) \
23
+ --python_out=$(OUT_DIR) \
24
+ --grpc_python_out=$(OUT_DIR) \
25
+ --pyi_out=$(OUT_DIR) \
26
+ $(PROTOS)
27
+ @find $(OUT_DIR) -type d -exec sh -c 'touch "$$1/__init__.py"' _ {} \;
28
+ @# Rewrite both `from spendguard.X import` and `import spendguard.X`
29
+ @# patterns to the namespaced location.
30
+ @find $(OUT_DIR) \( -name '*.py' -o -name '*.pyi' \) \
31
+ -exec sed -i.bak \
32
+ -e 's|^from spendguard\.|from spendguard._proto.spendguard.|g' \
33
+ -e 's|^import spendguard\.|import spendguard._proto.spendguard.|g' \
34
+ {} \;
35
+ @find $(OUT_DIR) -name '*.bak' -delete
36
+
37
+ clean:
38
+ rm -rf $(OUT_DIR) dist build *.egg-info
39
+
40
+ lint:
41
+ ruff check src
42
+
43
+ typecheck:
44
+ mypy src
45
+
46
+ test:
47
+ pytest -q
48
+
49
+ # Build sdist + wheel for publishing.
50
+ build: clean proto
51
+ python -m build
52
+
53
+ # Publish to test.pypi.org for verification.
54
+ publish-test: build
55
+ python -m twine check dist/*
56
+ python -m twine upload --repository testpypi dist/*
57
+
58
+ # Publish to PyPI (real).
59
+ publish: build
60
+ python -m twine check dist/*
61
+ python -m twine upload dist/*
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: spendguard-sdk
3
+ Version: 0.1.0a1
4
+ Summary: SpendGuard SDK — runtime safety layer client for AI agent frameworks (Pydantic-AI, LangChain, LangGraph, OpenAI Agents SDK).
5
+ Project-URL: Homepage, https://github.com/m24927605/agentic-spendguard
6
+ Project-URL: Repository, https://github.com/m24927605/agentic-spendguard
7
+ Project-URL: Issues, https://github.com/m24927605/agentic-spendguard/issues
8
+ Author-email: Michael Chen <m24927605@gmail.com>
9
+ License-Expression: Apache-2.0
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: License :: OSI Approved :: Apache Software License
12
+ Classifier: Operating System :: MacOS
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: grpcio>=1.62
21
+ Requires-Dist: protobuf<6,>=4.25
22
+ Requires-Dist: pydantic>=2.6
23
+ Provides-Extra: agt
24
+ Requires-Dist: agent-governance-toolkit>=3.4; extra == 'agt'
25
+ Requires-Dist: agent-os-kernel>=3.0; extra == 'agt'
26
+ Provides-Extra: dev
27
+ Requires-Dist: build>=1.2; extra == 'dev'
28
+ Requires-Dist: grpcio-tools>=1.62; extra == 'dev'
29
+ Requires-Dist: mypy>=1.10; extra == 'dev'
30
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
31
+ Requires-Dist: pytest>=8; extra == 'dev'
32
+ Requires-Dist: ruff>=0.5; extra == 'dev'
33
+ Requires-Dist: twine>=5; extra == 'dev'
34
+ Provides-Extra: langchain
35
+ Requires-Dist: langchain-core>=0.3; extra == 'langchain'
36
+ Requires-Dist: langchain>=0.3; extra == 'langchain'
37
+ Provides-Extra: langgraph
38
+ Requires-Dist: langchain-core>=0.3; extra == 'langgraph'
39
+ Requires-Dist: langgraph>=0.2; extra == 'langgraph'
40
+ Provides-Extra: openai-agents
41
+ Requires-Dist: openai-agents>=0.17; extra == 'openai-agents'
42
+ Provides-Extra: pydantic-ai
43
+ Requires-Dist: pydantic-ai<0.1.0,>=0.0.20; extra == 'pydantic-ai'
44
+ Description-Content-Type: text/markdown
45
+
46
+ # spendguard-sdk
47
+
48
+ Runtime safety layer client for AI agent frameworks. Talks to the
49
+ SpendGuard sidecar over Unix-domain-socket gRPC; gates each LLM /
50
+ tool-call boundary through a Contract DSL evaluator and an atomic
51
+ budget ledger with immutable audit chain.
52
+
53
+ ## Install
54
+
55
+ ```bash
56
+ # Core only (raw client, no framework integration)
57
+ pip install spendguard-sdk
58
+
59
+ # With the integration you need
60
+ pip install 'spendguard-sdk[pydantic-ai]'
61
+ pip install 'spendguard-sdk[langchain]'
62
+ pip install 'spendguard-sdk[langgraph]'
63
+ pip install 'spendguard-sdk[openai-agents]'
64
+ ```
65
+
66
+ ## Quickstart (Pydantic-AI)
67
+
68
+ ```python
69
+ import asyncio
70
+ from pydantic_ai import Agent
71
+ from pydantic_ai.models.openai import OpenAIModel
72
+
73
+ from spendguard import SpendGuardClient, new_uuid7
74
+ from spendguard.integrations.pydantic_ai import (
75
+ RunContext,
76
+ SpendGuardModel,
77
+ run_context,
78
+ )
79
+ from spendguard._proto.spendguard.common.v1 import common_pb2
80
+
81
+
82
+ async def main():
83
+ client = SpendGuardClient(
84
+ socket_path="/var/run/spendguard/adapter.sock",
85
+ tenant_id="00000000-0000-4000-8000-000000000001",
86
+ )
87
+ await client.connect()
88
+ await client.handshake()
89
+
90
+ guarded = SpendGuardModel(
91
+ inner=OpenAIModel("gpt-4o-mini"),
92
+ client=client,
93
+ budget_id="44444444-4444-4444-8444-444444444444",
94
+ window_instance_id="55555555-5555-4555-8555-555555555555",
95
+ unit=common_pb2.UnitRef(
96
+ unit_id="66666666-6666-4666-8666-666666666666",
97
+ token_kind="output_token",
98
+ model_family="gpt-4",
99
+ ),
100
+ pricing=common_pb2.PricingFreeze(
101
+ pricing_version="demo-pricing-v1",
102
+ price_snapshot_hash=b"<32 bytes>",
103
+ fx_rate_version="demo-fx-v1",
104
+ unit_conversion_version="demo-units-v1",
105
+ ),
106
+ claim_estimator=lambda messages, settings: [
107
+ common_pb2.BudgetClaim(
108
+ budget_id="44444444-4444-4444-8444-444444444444",
109
+ unit=common_pb2.UnitRef(
110
+ unit_id="66666666-6666-4666-8666-666666666666",
111
+ token_kind="output_token",
112
+ model_family="gpt-4",
113
+ ),
114
+ amount_atomic="500",
115
+ direction=common_pb2.BudgetClaim.DEBIT,
116
+ window_instance_id="55555555-5555-4555-8555-555555555555",
117
+ )
118
+ ],
119
+ )
120
+
121
+ agent = Agent(model=guarded)
122
+ async with run_context(RunContext(run_id=str(new_uuid7()))):
123
+ result = await agent.run("Say hello in three words.")
124
+ print(result.output)
125
+
126
+
127
+ asyncio.run(main())
128
+ ```
129
+
130
+ If a contract rule denies the call, `agent.run(...)` raises
131
+ `spendguard.DecisionStopped` carrying `reason_codes` and
132
+ `matched_rule_ids`.
133
+
134
+ ## API surface (core)
135
+
136
+ | Symbol | Purpose |
137
+ |---|---|
138
+ | `SpendGuardClient` | UDS gRPC client to the sidecar |
139
+ | `DecisionStopped`, `DecisionSkipped`, `ApprovalRequired` | per-decision exceptions |
140
+ | `derive_idempotency_key(...)` | deterministic key from (tenant, run, step, llm_call, trigger) |
141
+ | `new_uuid7()` | UUID v7 helper |
142
+
143
+ ## Wire-protocol compatibility
144
+
145
+ This SDK pins to a specific protobuf wire version. Check the
146
+ sidecar's published version against `spendguard.__version__`; minor
147
+ versions are wire-compatible, major bumps are breaking. (Not yet
148
+ enforced at handshake; planned for v0.2.)
149
+
150
+ ## License
151
+
152
+ Apache-2.0.
@@ -0,0 +1,107 @@
1
+ # spendguard-sdk
2
+
3
+ Runtime safety layer client for AI agent frameworks. Talks to the
4
+ SpendGuard sidecar over Unix-domain-socket gRPC; gates each LLM /
5
+ tool-call boundary through a Contract DSL evaluator and an atomic
6
+ budget ledger with immutable audit chain.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ # Core only (raw client, no framework integration)
12
+ pip install spendguard-sdk
13
+
14
+ # With the integration you need
15
+ pip install 'spendguard-sdk[pydantic-ai]'
16
+ pip install 'spendguard-sdk[langchain]'
17
+ pip install 'spendguard-sdk[langgraph]'
18
+ pip install 'spendguard-sdk[openai-agents]'
19
+ ```
20
+
21
+ ## Quickstart (Pydantic-AI)
22
+
23
+ ```python
24
+ import asyncio
25
+ from pydantic_ai import Agent
26
+ from pydantic_ai.models.openai import OpenAIModel
27
+
28
+ from spendguard import SpendGuardClient, new_uuid7
29
+ from spendguard.integrations.pydantic_ai import (
30
+ RunContext,
31
+ SpendGuardModel,
32
+ run_context,
33
+ )
34
+ from spendguard._proto.spendguard.common.v1 import common_pb2
35
+
36
+
37
+ async def main():
38
+ client = SpendGuardClient(
39
+ socket_path="/var/run/spendguard/adapter.sock",
40
+ tenant_id="00000000-0000-4000-8000-000000000001",
41
+ )
42
+ await client.connect()
43
+ await client.handshake()
44
+
45
+ guarded = SpendGuardModel(
46
+ inner=OpenAIModel("gpt-4o-mini"),
47
+ client=client,
48
+ budget_id="44444444-4444-4444-8444-444444444444",
49
+ window_instance_id="55555555-5555-4555-8555-555555555555",
50
+ unit=common_pb2.UnitRef(
51
+ unit_id="66666666-6666-4666-8666-666666666666",
52
+ token_kind="output_token",
53
+ model_family="gpt-4",
54
+ ),
55
+ pricing=common_pb2.PricingFreeze(
56
+ pricing_version="demo-pricing-v1",
57
+ price_snapshot_hash=b"<32 bytes>",
58
+ fx_rate_version="demo-fx-v1",
59
+ unit_conversion_version="demo-units-v1",
60
+ ),
61
+ claim_estimator=lambda messages, settings: [
62
+ common_pb2.BudgetClaim(
63
+ budget_id="44444444-4444-4444-8444-444444444444",
64
+ unit=common_pb2.UnitRef(
65
+ unit_id="66666666-6666-4666-8666-666666666666",
66
+ token_kind="output_token",
67
+ model_family="gpt-4",
68
+ ),
69
+ amount_atomic="500",
70
+ direction=common_pb2.BudgetClaim.DEBIT,
71
+ window_instance_id="55555555-5555-4555-8555-555555555555",
72
+ )
73
+ ],
74
+ )
75
+
76
+ agent = Agent(model=guarded)
77
+ async with run_context(RunContext(run_id=str(new_uuid7()))):
78
+ result = await agent.run("Say hello in three words.")
79
+ print(result.output)
80
+
81
+
82
+ asyncio.run(main())
83
+ ```
84
+
85
+ If a contract rule denies the call, `agent.run(...)` raises
86
+ `spendguard.DecisionStopped` carrying `reason_codes` and
87
+ `matched_rule_ids`.
88
+
89
+ ## API surface (core)
90
+
91
+ | Symbol | Purpose |
92
+ |---|---|
93
+ | `SpendGuardClient` | UDS gRPC client to the sidecar |
94
+ | `DecisionStopped`, `DecisionSkipped`, `ApprovalRequired` | per-decision exceptions |
95
+ | `derive_idempotency_key(...)` | deterministic key from (tenant, run, step, llm_call, trigger) |
96
+ | `new_uuid7()` | UUID v7 helper |
97
+
98
+ ## Wire-protocol compatibility
99
+
100
+ This SDK pins to a specific protobuf wire version. Check the
101
+ sidecar's published version against `spendguard.__version__`; minor
102
+ versions are wire-compatible, major bumps are breaking. (Not yet
103
+ enforced at handshake; planned for v0.2.)
104
+
105
+ ## License
106
+
107
+ Apache-2.0.
@@ -0,0 +1,109 @@
1
+ """Minimal end-to-end example: pydantic-ai Agent with SpendGuard gating.
2
+
3
+ Run with a sidecar listening on the configured UDS:
4
+
5
+ SPENDGUARD_SIDECAR_UDS=/run/spendguard.sock \
6
+ SPENDGUARD_TENANT_ID=11111111-1111-1111-1111-111111111111 \
7
+ SPENDGUARD_BUDGET_ID=22222222-2222-2222-2222-222222222222 \
8
+ SPENDGUARD_WINDOW_INSTANCE_ID=33333333-3333-3333-3333-333333333333 \
9
+ OPENAI_API_KEY=sk-... \
10
+ python examples/basic_agent.py
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import asyncio
16
+ import os
17
+ import uuid
18
+ from collections.abc import Sequence
19
+
20
+ from pydantic_ai import Agent
21
+ from pydantic_ai.models.openai import OpenAIModel
22
+
23
+ from spendguard_pydantic_ai import (
24
+ RunContext,
25
+ SpendGuardClient,
26
+ SpendGuardModel,
27
+ new_uuid7,
28
+ run_context,
29
+ )
30
+ from spendguard_pydantic_ai._proto.spendguard.common.v1 import common_pb2
31
+
32
+
33
+ def _env(name: str) -> str:
34
+ val = os.environ.get(name)
35
+ if not val:
36
+ raise RuntimeError(f"env var {name} is required")
37
+ return val
38
+
39
+
40
+ def estimate_claims(
41
+ messages: Sequence[object],
42
+ model_settings: object | None,
43
+ ) -> list[common_pb2.BudgetClaim]:
44
+ """Naive token estimator: 1 claim per call, 500 tokens projected.
45
+
46
+ Production code should use a real tokenizer (tiktoken /
47
+ anthropic-tokenizer) to count input tokens, then add the
48
+ `model_settings.max_tokens` ceiling for output.
49
+ """
50
+ return [
51
+ common_pb2.BudgetClaim(
52
+ budget_id=_env("SPENDGUARD_BUDGET_ID"),
53
+ unit=common_pb2.UnitRef(
54
+ unit_id=_env("SPENDGUARD_UNIT_ID"),
55
+ token_kind="output_token",
56
+ model_family="gpt-4",
57
+ ),
58
+ amount_atomic="500",
59
+ direction=common_pb2.BudgetClaim.DEBIT,
60
+ window_instance_id=_env("SPENDGUARD_WINDOW_INSTANCE_ID"),
61
+ ),
62
+ ]
63
+
64
+
65
+ async def main() -> None:
66
+ socket_path = _env("SPENDGUARD_SIDECAR_UDS")
67
+ tenant_id = _env("SPENDGUARD_TENANT_ID")
68
+ budget_id = _env("SPENDGUARD_BUDGET_ID")
69
+ window_id = _env("SPENDGUARD_WINDOW_INSTANCE_ID")
70
+
71
+ inner = OpenAIModel("gpt-4o-mini")
72
+
73
+ async with SpendGuardClient(
74
+ socket_path=socket_path,
75
+ tenant_id=tenant_id,
76
+ ) as client:
77
+ await client.handshake()
78
+
79
+ guarded = SpendGuardModel(
80
+ inner=inner,
81
+ client=client,
82
+ budget_id=budget_id,
83
+ window_instance_id=window_id,
84
+ unit=common_pb2.UnitRef(
85
+ unit_id=_env("SPENDGUARD_UNIT_ID"),
86
+ token_kind="output_token",
87
+ model_family="gpt-4",
88
+ ),
89
+ pricing=common_pb2.PricingFreeze(
90
+ pricing_version=_env("SPENDGUARD_PRICING_VERSION"),
91
+ price_snapshot_hash=bytes.fromhex(
92
+ _env("SPENDGUARD_PRICE_SNAPSHOT_HASH_HEX")
93
+ ),
94
+ fx_rate_version=_env("SPENDGUARD_FX_RATE_VERSION"),
95
+ unit_conversion_version=_env("SPENDGUARD_UNIT_CONVERSION_VERSION"),
96
+ ),
97
+ claim_estimator=estimate_claims,
98
+ )
99
+ agent = Agent(model=guarded)
100
+
101
+ run_id = str(new_uuid7())
102
+ async with run_context(RunContext(run_id=run_id)):
103
+ result = await agent.run("Say hello in three words.")
104
+ print(f"agent output: {result.output}")
105
+ print(f"run_id: {run_id}")
106
+
107
+
108
+ if __name__ == "__main__":
109
+ asyncio.run(main())
@@ -0,0 +1,87 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.21"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "spendguard-sdk"
7
+ version = "0.1.0a1"
8
+ description = "SpendGuard SDK — runtime safety layer client for AI agent frameworks (Pydantic-AI, LangChain, LangGraph, OpenAI Agents SDK)."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "Apache-2.0"
12
+ authors = [
13
+ { name = "Michael Chen", email = "m24927605@gmail.com" },
14
+ ]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "License :: OSI Approved :: Apache Software License",
21
+ "Topic :: Software Development :: Libraries",
22
+ "Development Status :: 3 - Alpha",
23
+ "Operating System :: POSIX :: Linux",
24
+ "Operating System :: MacOS",
25
+ ]
26
+
27
+ # Core deps: the SpendGuardClient + proto stubs only. Framework deps
28
+ # are gated behind extras so a user who only writes raw OpenAI SDK code
29
+ # doesn't pull in pydantic-ai / langchain / etc.
30
+ dependencies = [
31
+ "grpcio>=1.62",
32
+ "protobuf>=4.25,<6",
33
+ "pydantic>=2.6",
34
+ ]
35
+
36
+ [project.optional-dependencies]
37
+ pydantic-ai = [
38
+ "pydantic-ai>=0.0.20,<0.1.0",
39
+ ]
40
+ langchain = [
41
+ "langchain-core>=0.3",
42
+ "langchain>=0.3",
43
+ ]
44
+ langgraph = [
45
+ "langchain-core>=0.3",
46
+ "langgraph>=0.2",
47
+ ]
48
+ openai-agents = [
49
+ "openai-agents>=0.17",
50
+ ]
51
+ agt = [
52
+ "agent-governance-toolkit>=3.4",
53
+ "agent-os-kernel>=3.0",
54
+ ]
55
+ dev = [
56
+ "grpcio-tools>=1.62",
57
+ "pytest>=8",
58
+ "pytest-asyncio>=0.23",
59
+ "ruff>=0.5",
60
+ "mypy>=1.10",
61
+ "build>=1.2",
62
+ "twine>=5",
63
+ ]
64
+
65
+ [project.urls]
66
+ Homepage = "https://github.com/m24927605/agentic-spendguard"
67
+ Repository = "https://github.com/m24927605/agentic-spendguard"
68
+ Issues = "https://github.com/m24927605/agentic-spendguard/issues"
69
+
70
+ [tool.hatch.build.targets.wheel]
71
+ packages = ["src/spendguard"]
72
+
73
+ [tool.ruff]
74
+ line-length = 100
75
+ target-version = "py310"
76
+
77
+ [tool.ruff.lint]
78
+ select = ["E", "F", "W", "I", "B", "UP", "ANN", "ASYNC", "S"]
79
+ ignore = ["ANN101", "ANN102", "S101"]
80
+
81
+ [tool.mypy]
82
+ python_version = "3.10"
83
+ strict = true
84
+ warn_unused_configs = true
85
+
86
+ [tool.pytest.ini_options]
87
+ asyncio_mode = "auto"
@@ -0,0 +1,70 @@
1
+ """SpendGuard SDK — runtime safety layer client for AI agent frameworks.
2
+
3
+ Core surface (always available):
4
+
5
+ from spendguard import SpendGuardClient, DecisionStopped, derive_idempotency_key
6
+
7
+ Framework integrations are optional (install via extras):
8
+
9
+ pip install spendguard-sdk[pydantic-ai]
10
+ pip install spendguard-sdk[langchain]
11
+ pip install spendguard-sdk[langgraph]
12
+ pip install spendguard-sdk[openai-agents]
13
+
14
+ After installing the relevant extras::
15
+
16
+ from spendguard.integrations.pydantic_ai import SpendGuardModel
17
+ from spendguard.integrations.langchain import SpendGuardChatModel
18
+ # ...
19
+ """
20
+
21
+ from .client import (
22
+ DEFAULT_DECISION_TIMEOUT_S,
23
+ DEFAULT_HANDSHAKE_TIMEOUT_S,
24
+ DecisionOutcome,
25
+ HandshakeOutcome,
26
+ SpendGuardClient,
27
+ )
28
+ from .errors import (
29
+ ApprovalRequired,
30
+ DecisionDenied,
31
+ DecisionSkipped,
32
+ DecisionStopped,
33
+ HandshakeError,
34
+ MutationApplyFailed,
35
+ SidecarUnavailable,
36
+ SpendGuardError,
37
+ )
38
+ from .ids import (
39
+ default_call_signature,
40
+ derive_idempotency_key,
41
+ derive_uuid_from_signature,
42
+ new_uuid7,
43
+ workload_instance_id,
44
+ )
45
+
46
+ __all__ = [
47
+ # client
48
+ "DEFAULT_DECISION_TIMEOUT_S",
49
+ "DEFAULT_HANDSHAKE_TIMEOUT_S",
50
+ "DecisionOutcome",
51
+ "HandshakeOutcome",
52
+ "SpendGuardClient",
53
+ # errors
54
+ "ApprovalRequired",
55
+ "DecisionDenied",
56
+ "DecisionSkipped",
57
+ "DecisionStopped",
58
+ "HandshakeError",
59
+ "MutationApplyFailed",
60
+ "SidecarUnavailable",
61
+ "SpendGuardError",
62
+ # ids
63
+ "default_call_signature",
64
+ "derive_idempotency_key",
65
+ "derive_uuid_from_signature",
66
+ "new_uuid7",
67
+ "workload_instance_id",
68
+ ]
69
+
70
+ __version__ = "0.1.0a1"
@@ -0,0 +1,8 @@
1
+ """Generated protobuf + gRPC stubs.
2
+
3
+ Populated by `make proto` from the wire spec under `proto/spendguard/`.
4
+ The rest of the adapter imports specific generated modules from this
5
+ namespace (e.g. `spendguard._proto.spendguard.common.v1`);
6
+ attempting those imports before `make proto` raises a helpful
7
+ ImportError pointing the developer at the build step.
8
+ """