deepintshield 2.7.0__tar.gz → 2.7.2__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 (124) hide show
  1. {deepintshield-2.7.0/src/deepintshield.egg-info → deepintshield-2.7.2}/PKG-INFO +70 -6
  2. {deepintshield-2.7.0 → deepintshield-2.7.2}/README.md +69 -5
  3. {deepintshield-2.7.0 → deepintshield-2.7.2}/pyproject.toml +1 -1
  4. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/_prompt_cache.py +16 -7
  5. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/enforcement.py +11 -0
  6. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/registry.py +17 -0
  7. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/anthropic.py +19 -3
  8. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/client.py +35 -3
  9. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/anthropic.py +28 -12
  10. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/genai.py +32 -1
  11. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/openai.py +5 -3
  12. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/rag.py +149 -30
  13. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/transport.py +46 -8
  14. deepintshield-2.7.2/src/deepintshield/version.py +1 -0
  15. {deepintshield-2.7.0 → deepintshield-2.7.2/src/deepintshield.egg-info}/PKG-INFO +70 -6
  16. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield.egg-info/SOURCES.txt +8 -0
  17. deepintshield-2.7.2/tests/test_agentic_import_lifecycle.py +56 -0
  18. deepintshield-2.7.2/tests/test_anthropic_transport_compat.py +103 -0
  19. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_error_surfaces.py +34 -0
  20. deepintshield-2.7.2/tests/test_genai_contracts.py +227 -0
  21. deepintshield-2.7.2/tests/test_model_compatibility.py +188 -0
  22. deepintshield-2.7.2/tests/test_native_provider_headers.py +108 -0
  23. deepintshield-2.7.2/tests/test_platform_agentic_workflows.py +84 -0
  24. deepintshield-2.7.2/tests/test_platform_rag_workflows.py +177 -0
  25. deepintshield-2.7.2/tests/test_rag_redaction.py +127 -0
  26. deepintshield-2.7.0/src/deepintshield/version.py +0 -1
  27. {deepintshield-2.7.0 → deepintshield-2.7.2}/LICENSE +0 -0
  28. {deepintshield-2.7.0 → deepintshield-2.7.2}/NOTICE +0 -0
  29. {deepintshield-2.7.0 → deepintshield-2.7.2}/setup.cfg +0 -0
  30. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/__init__.py +0 -0
  31. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/_gemini_cache.py +0 -0
  32. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agent.py +0 -0
  33. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/__init__.py +0 -0
  34. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/actions.py +0 -0
  35. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/__init__.py +0 -0
  36. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/base.py +0 -0
  37. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/entra.py +0 -0
  38. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/oidc.py +0 -0
  39. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/zeroid.py +0 -0
  40. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/decorators.py +0 -0
  41. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/dpop.py +0 -0
  42. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/engine.py +0 -0
  43. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/errors.py +0 -0
  44. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/execution.py +0 -0
  45. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/gate.py +0 -0
  46. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/identity.py +0 -0
  47. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/__init__.py +0 -0
  48. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/_common.py +0 -0
  49. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/autogen.py +0 -0
  50. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/crewai.py +0 -0
  51. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/google_adk.py +0 -0
  52. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/hermes.py +0 -0
  53. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/langchain.py +0 -0
  54. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/langgraph.py +0 -0
  55. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/litellm.py +0 -0
  56. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/llamaindex.py +0 -0
  57. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/openai_agents.py +0 -0
  58. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/openclaw.py +0 -0
  59. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/pydanticai.py +0 -0
  60. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/strands.py +0 -0
  61. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/temporal.py +0 -0
  62. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/manifest.py +0 -0
  63. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/obligations.py +0 -0
  64. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/surface.py +0 -0
  65. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/types.py +0 -0
  66. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/client.py +0 -0
  67. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/config.py +0 -0
  68. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/errors.py +0 -0
  69. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/frameworks/__init__.py +0 -0
  70. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/frameworks/autogen.py +0 -0
  71. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/frameworks/crewai.py +0 -0
  72. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/frameworks/langgraph.py +0 -0
  73. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/frameworks/llamaindex.py +0 -0
  74. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/frameworks/openai_agents.py +0 -0
  75. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/frameworks/pydanticai.py +0 -0
  76. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/__init__.py +0 -0
  77. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/_errors.py +0 -0
  78. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/_native.py +0 -0
  79. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/__init__.py +0 -0
  80. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/_security.py +0 -0
  81. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/langchain.py +0 -0
  82. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/openai.py +0 -0
  83. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/tool.py +0 -0
  84. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/__init__.py +0 -0
  85. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/bedrock.py +0 -0
  86. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/langchain.py +0 -0
  87. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/langgraph.py +0 -0
  88. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/litellm.py +0 -0
  89. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/pydanticai.py +0 -0
  90. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/streaming.py +0 -0
  91. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/types.py +0 -0
  92. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield.egg-info/dependency_links.txt +0 -0
  93. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield.egg-info/requires.txt +0 -0
  94. {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield.egg-info/top_level.txt +0 -0
  95. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agent.py +0 -0
  96. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic.py +0 -0
  97. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_blueprint_contract.py +0 -0
  98. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_callback_inventory.py +0 -0
  99. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_credentials.py +0 -0
  100. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_error_contract.py +0 -0
  101. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_execution_lifecycle.py +0 -0
  102. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_fail_closed_integrations.py +0 -0
  103. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_framework_parity.py +0 -0
  104. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_langchain.py +0 -0
  105. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_new_decide.py +0 -0
  106. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_new_discovery.py +0 -0
  107. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_new_enforcement.py +0 -0
  108. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_workload_headers.py +0 -0
  109. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_chat_streaming.py +0 -0
  110. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_client.py +0 -0
  111. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_config.py +0 -0
  112. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_error_catalog.py +0 -0
  113. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_errors.py +0 -0
  114. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_gemini_cache.py +0 -0
  115. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_mcp_native_errors.py +0 -0
  116. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_mcp_native_session.py +0 -0
  117. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_mcp_preferred_api.py +0 -0
  118. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_obligation_contract.py +0 -0
  119. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_prompt_cache.py +0 -0
  120. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_providers.py +0 -0
  121. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_rag.py +0 -0
  122. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_rag_guard.py +0 -0
  123. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_transport.py +0 -0
  124. {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepintshield
3
- Version: 2.7.0
3
+ Version: 2.7.2
4
4
  Summary: Unified Python SDK for routing chat, RAG, agentic tool-gating, identity, and MCP traffic through DeepintShield - drop-in across the top agentic frameworks.
5
5
  Author: DeepintShield
6
6
  License-Expression: Apache-2.0
@@ -98,6 +98,8 @@ Dynamic: license-file
98
98
 
99
99
  Unified Python SDK for DeepIntShield - one import, any provider, any agent framework.
100
100
 
101
+ Current release: **2.7.2**, aligned with DeepIntShield Server **2.7.2**.
102
+
101
103
  `deepintshield` lets you keep writing idiomatic OpenAI / Anthropic / Bedrock /
102
104
  Google GenAI code **and** native agent-framework code (LangGraph, CrewAI,
103
105
  OpenAI Agents SDK, LlamaIndex, AutoGen, PydanticAI, Temporal, AWS Strands,
@@ -134,6 +136,9 @@ and you're done.
134
136
 
135
137
  ## Install
136
138
 
139
+ For a reproducible installation of this release, use `pip install "deepintshield==2.7.2"`.
140
+ Add the provider and framework extras your application needs:
141
+
137
142
  ```bash
138
143
  pip install deepintshield # core (chat, RAG, agentic)
139
144
  pip install 'deepintshield[openai]' # + OpenAI SDK
@@ -268,17 +273,40 @@ response = openai.chat.completions.create(
268
273
  )
269
274
  ```
270
275
 
276
+ The same client exposes the native Responses API:
277
+
278
+ ```python
279
+ response = openai.responses.create(
280
+ model="gpt-4o-mini",
281
+ input="Explain this design in one sentence.",
282
+ store=False,
283
+ )
284
+ print(response.output_text)
285
+ ```
286
+
287
+ Responses uses `max_output_tokens` and a `reasoning` object where supported;
288
+ Chat Completions uses its model's supported token-limit field and
289
+ `reasoning_effort`. For manual Responses continuations, retain the full output
290
+ items, including tool calls and opaque reasoning state, and replay them only
291
+ with the same provider and model. The Playground performs this mapping and
292
+ preserves compatible response state in saved sessions.
293
+
271
294
  ### Anthropic
272
295
 
273
296
  ```python
274
297
  anthropic = shield.anthropic()
275
298
  response = anthropic.messages.create(
276
- model="claude-3-sonnet-20240229",
299
+ model="claude-sonnet-5",
277
300
  max_tokens=256,
278
301
  messages=[{"role": "user", "content": "hello"}],
279
302
  )
280
303
  ```
281
304
 
305
+ `shield.anthropic()` uses the installed Anthropic SDK's default transport class,
306
+ including SDK releases backed by `httpx2`, and retains automatic prompt-cache
307
+ hooks. A caller-supplied `http_client` must be compatible with that installed SDK
308
+ and remains responsible for its own prompt-cache hooks.
309
+
282
310
  ### Bedrock
283
311
 
284
312
  ```python
@@ -294,11 +322,30 @@ response = bedrock.converse(
294
322
  ```python
295
323
  genai = shield.genai()
296
324
  response = genai.models.generate_content(
297
- model="gemini-1.5-flash",
325
+ model="gemini-3.5-flash",
298
326
  contents="hello",
327
+ config={"automatic_function_calling": {"disable": True}},
299
328
  )
329
+ print(response.text)
300
330
  ```
301
331
 
332
+ Disabling automatic function calling (AFC) is optional for this text-only call.
333
+ Recent Google SDK versions warn about direct AFC use even when no callable tools
334
+ are supplied; that warning alone does not mean the request failed. When using
335
+ Python callable tools, Google recommends the chat interface. Multi-turn tool
336
+ workflows also depend on the gateway preserving tool roles and thought signatures;
337
+ a successful text-only request does not verify those conversions.
338
+
339
+ The gateway's native Gemini conversion preserves model/user tool roles, per-call
340
+ thought signatures, and distinct IDs for parallel calls to the same function.
341
+ SDK regression tests cover direct and chat calls, sync/async streaming, and
342
+ callable-tool continuations. A timeout-only `http_options` override retains the
343
+ gateway destination in SDK 2.7.2 (fixed in 2.7.1); SDK 2.7.0 does not merge that override correctly.
344
+
345
+ For streaming, use `genai.models.generate_content_stream(...)` or
346
+ `chat.send_message_stream(...)` and read each chunk's `text`. The native async
347
+ interfaces remain available under `genai.aio`.
348
+
302
349
  ### LangChain
303
350
 
304
351
  ```python
@@ -370,6 +417,12 @@ retriever = shield.rag.guard_retriever(my_retriever) # mutates in place
370
417
  docs = retriever.invoke("what is the Q2 ledger?") # only allowed chunks
371
418
  ```
372
419
 
420
+ Async retrievers are supported too: use `await retriever.ainvoke(query)` or
421
+ the retriever's native async retrieval method. Filtering finishes before
422
+ documents are returned; delegated retrieval methods filter once per call.
423
+ Allowed documents keep their order and original framework objects. Redacted
424
+ content is returned in copies, leaving the retriever's source documents intact.
425
+
373
426
  ### Guard an embedder (pre-embedding, Portkey-parity "before request")
374
427
 
375
428
  Screen input text for PII / injection / toxicity **before** it is vectorised:
@@ -808,15 +861,21 @@ OpenAI/Anthropic/LangChain conversion-loop helpers remain deprecated 2.x
808
861
  compatibility shims. Their removal is planned for SDK 3.0; new code should use
809
862
  the official session or a maintained third-party adapter.
810
863
 
864
+ For an existing Anthropic conversion loop, use the same `shield.mcp` instance
865
+ for `to_anthropic(tools)` and `run_anthropic_tool_uses(response.content)`.
866
+ Provider-safe aliases distinguish qualified names that contain unsupported
867
+ characters or exceed Anthropic's length limit; that client retains the mapping
868
+ back to the original tool names for execution.
869
+
811
870
  ---
812
871
 
813
872
  ## Cost Optimization
814
873
 
815
- The SDK automatically participates in the gateway's two cost-reduction layers
874
+ The SDK participates in the gateway's caching mechanisms
816
875
  (both controlled by workspace switches under **Cost Optimization**):
817
876
 
818
- - **Provider prompt caching** - every chat client returned by `shield.openai()`,
819
- `shield.anthropic()`, etc. ships an `httpx` request hook that injects
877
+ - **Provider prompt caching** - SDK-created OpenAI and Anthropic transports
878
+ include request hooks that inject
820
879
  Anthropic `cache_control` markers and an OpenAI `prompt_cache_key` so the
821
880
  provider reuses KV state for the static prompt prefix. Eligible cached tokens
822
881
  use the provider's current cached-input rate; verify model-specific pricing
@@ -830,6 +889,11 @@ The SDK automatically participates in the gateway's two cost-reduction layers
830
889
  threshold. The SDK doesn't need any code change to benefit; results flow
831
890
  back through the normal API.
832
891
 
892
+ Cost dashboards show recorded numeric totals, using `$0.00` when no display
893
+ value is available. Missing prices remain nullable in API log records and can
894
+ still be found through the missing-cost filter. Estimated savings remain signed
895
+ and are separate from the actual recorded cost.
896
+
833
897
  ### Per-request cache overrides
834
898
 
835
899
  Workspace settings are the default, but any individual call can override
@@ -5,6 +5,8 @@
5
5
 
6
6
  Unified Python SDK for DeepIntShield - one import, any provider, any agent framework.
7
7
 
8
+ Current release: **2.7.2**, aligned with DeepIntShield Server **2.7.2**.
9
+
8
10
  `deepintshield` lets you keep writing idiomatic OpenAI / Anthropic / Bedrock /
9
11
  Google GenAI code **and** native agent-framework code (LangGraph, CrewAI,
10
12
  OpenAI Agents SDK, LlamaIndex, AutoGen, PydanticAI, Temporal, AWS Strands,
@@ -41,6 +43,9 @@ and you're done.
41
43
 
42
44
  ## Install
43
45
 
46
+ For a reproducible installation of this release, use `pip install "deepintshield==2.7.2"`.
47
+ Add the provider and framework extras your application needs:
48
+
44
49
  ```bash
45
50
  pip install deepintshield # core (chat, RAG, agentic)
46
51
  pip install 'deepintshield[openai]' # + OpenAI SDK
@@ -175,17 +180,40 @@ response = openai.chat.completions.create(
175
180
  )
176
181
  ```
177
182
 
183
+ The same client exposes the native Responses API:
184
+
185
+ ```python
186
+ response = openai.responses.create(
187
+ model="gpt-4o-mini",
188
+ input="Explain this design in one sentence.",
189
+ store=False,
190
+ )
191
+ print(response.output_text)
192
+ ```
193
+
194
+ Responses uses `max_output_tokens` and a `reasoning` object where supported;
195
+ Chat Completions uses its model's supported token-limit field and
196
+ `reasoning_effort`. For manual Responses continuations, retain the full output
197
+ items, including tool calls and opaque reasoning state, and replay them only
198
+ with the same provider and model. The Playground performs this mapping and
199
+ preserves compatible response state in saved sessions.
200
+
178
201
  ### Anthropic
179
202
 
180
203
  ```python
181
204
  anthropic = shield.anthropic()
182
205
  response = anthropic.messages.create(
183
- model="claude-3-sonnet-20240229",
206
+ model="claude-sonnet-5",
184
207
  max_tokens=256,
185
208
  messages=[{"role": "user", "content": "hello"}],
186
209
  )
187
210
  ```
188
211
 
212
+ `shield.anthropic()` uses the installed Anthropic SDK's default transport class,
213
+ including SDK releases backed by `httpx2`, and retains automatic prompt-cache
214
+ hooks. A caller-supplied `http_client` must be compatible with that installed SDK
215
+ and remains responsible for its own prompt-cache hooks.
216
+
189
217
  ### Bedrock
190
218
 
191
219
  ```python
@@ -201,11 +229,30 @@ response = bedrock.converse(
201
229
  ```python
202
230
  genai = shield.genai()
203
231
  response = genai.models.generate_content(
204
- model="gemini-1.5-flash",
232
+ model="gemini-3.5-flash",
205
233
  contents="hello",
234
+ config={"automatic_function_calling": {"disable": True}},
206
235
  )
236
+ print(response.text)
207
237
  ```
208
238
 
239
+ Disabling automatic function calling (AFC) is optional for this text-only call.
240
+ Recent Google SDK versions warn about direct AFC use even when no callable tools
241
+ are supplied; that warning alone does not mean the request failed. When using
242
+ Python callable tools, Google recommends the chat interface. Multi-turn tool
243
+ workflows also depend on the gateway preserving tool roles and thought signatures;
244
+ a successful text-only request does not verify those conversions.
245
+
246
+ The gateway's native Gemini conversion preserves model/user tool roles, per-call
247
+ thought signatures, and distinct IDs for parallel calls to the same function.
248
+ SDK regression tests cover direct and chat calls, sync/async streaming, and
249
+ callable-tool continuations. A timeout-only `http_options` override retains the
250
+ gateway destination in SDK 2.7.2 (fixed in 2.7.1); SDK 2.7.0 does not merge that override correctly.
251
+
252
+ For streaming, use `genai.models.generate_content_stream(...)` or
253
+ `chat.send_message_stream(...)` and read each chunk's `text`. The native async
254
+ interfaces remain available under `genai.aio`.
255
+
209
256
  ### LangChain
210
257
 
211
258
  ```python
@@ -277,6 +324,12 @@ retriever = shield.rag.guard_retriever(my_retriever) # mutates in place
277
324
  docs = retriever.invoke("what is the Q2 ledger?") # only allowed chunks
278
325
  ```
279
326
 
327
+ Async retrievers are supported too: use `await retriever.ainvoke(query)` or
328
+ the retriever's native async retrieval method. Filtering finishes before
329
+ documents are returned; delegated retrieval methods filter once per call.
330
+ Allowed documents keep their order and original framework objects. Redacted
331
+ content is returned in copies, leaving the retriever's source documents intact.
332
+
280
333
  ### Guard an embedder (pre-embedding, Portkey-parity "before request")
281
334
 
282
335
  Screen input text for PII / injection / toxicity **before** it is vectorised:
@@ -715,15 +768,21 @@ OpenAI/Anthropic/LangChain conversion-loop helpers remain deprecated 2.x
715
768
  compatibility shims. Their removal is planned for SDK 3.0; new code should use
716
769
  the official session or a maintained third-party adapter.
717
770
 
771
+ For an existing Anthropic conversion loop, use the same `shield.mcp` instance
772
+ for `to_anthropic(tools)` and `run_anthropic_tool_uses(response.content)`.
773
+ Provider-safe aliases distinguish qualified names that contain unsupported
774
+ characters or exceed Anthropic's length limit; that client retains the mapping
775
+ back to the original tool names for execution.
776
+
718
777
  ---
719
778
 
720
779
  ## Cost Optimization
721
780
 
722
- The SDK automatically participates in the gateway's two cost-reduction layers
781
+ The SDK participates in the gateway's caching mechanisms
723
782
  (both controlled by workspace switches under **Cost Optimization**):
724
783
 
725
- - **Provider prompt caching** - every chat client returned by `shield.openai()`,
726
- `shield.anthropic()`, etc. ships an `httpx` request hook that injects
784
+ - **Provider prompt caching** - SDK-created OpenAI and Anthropic transports
785
+ include request hooks that inject
727
786
  Anthropic `cache_control` markers and an OpenAI `prompt_cache_key` so the
728
787
  provider reuses KV state for the static prompt prefix. Eligible cached tokens
729
788
  use the provider's current cached-input rate; verify model-specific pricing
@@ -737,6 +796,11 @@ The SDK automatically participates in the gateway's two cost-reduction layers
737
796
  threshold. The SDK doesn't need any code change to benefit; results flow
738
797
  back through the normal API.
739
798
 
799
+ Cost dashboards show recorded numeric totals, using `$0.00` when no display
800
+ value is available. Missing prices remain nullable in API log records and can
801
+ still be found through the missing-cost filter. Estimated savings remain signed
802
+ and are separate from the actual recorded cost.
803
+
740
804
  ### Per-request cache overrides
741
805
 
742
806
  Workspace settings are the default, but any individual call can override
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deepintshield"
7
- version = "2.7.0"
7
+ version = "2.7.2"
8
8
  description = "Unified Python SDK for routing chat, RAG, agentic tool-gating, identity, and MCP traffic through DeepintShield - drop-in across the top agentic frameworks."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -25,6 +25,7 @@ from __future__ import annotations
25
25
 
26
26
  import hashlib
27
27
  import json
28
+ from functools import lru_cache
28
29
  from typing import Any, Callable, Iterable
29
30
 
30
31
  import httpx
@@ -33,6 +34,20 @@ import httpx
33
34
  PROVIDER_ANTHROPIC = "anthropic"
34
35
  PROVIDER_OPENAI = "openai"
35
36
 
37
+
38
+ @lru_cache(maxsize=8)
39
+ def _request_byte_stream_type(request_type: type) -> type:
40
+ """Keep rewritten request streams in the HTTP library that owns the request."""
41
+ for cls in request_type.__mro__:
42
+ package = cls.__module__.partition(".")[0]
43
+ if package == "httpx2":
44
+ # Optional: only imported for a request from an installed httpx2 SDK.
45
+ from httpx2 import ByteStream
46
+ return ByteStream
47
+ if package == "httpx":
48
+ return httpx.ByteStream
49
+ return httpx.ByteStream
50
+
36
51
  # Anthropic accepts up to 4 cache_control markers per request. We place them in
37
52
  # priority order: system → tools → last static user/assistant block.
38
53
  _DEFAULT_BREAKPOINTS: tuple[str, ...] = ("system", "tools")
@@ -209,13 +224,7 @@ def build_request_hook(
209
224
  # the announced byte count). Reattach the stream so the new body
210
225
  # is what actually gets sent.
211
226
  request._content = encoded # type: ignore[attr-defined]
212
- try:
213
- from httpx._content import ByteStream # type: ignore
214
- except ImportError: # httpx <0.24 fallback - module path drifted.
215
- from httpx import _content # type: ignore
216
- ByteStream = getattr(_content, "ByteStream", None)
217
- if ByteStream is not None:
218
- request.stream = ByteStream(encoded) # type: ignore[attr-defined]
227
+ request.stream = _request_byte_stream_type(type(request))(encoded)
219
228
 
220
229
  return hook
221
230
 
@@ -267,6 +267,17 @@ def install_all(*, client: Any = None) -> list[str]:
267
267
  with _install_lock:
268
268
  if mod_name in _installed:
269
269
  continue
270
+ # Nested imports return before the enclosing framework has
271
+ # defined its execution classes. Inspecting it at that point
272
+ # causes circular imports and falsely reports an unsupported
273
+ # version. The enclosing import's post-hook retries once all
274
+ # framework modules have finished initializing.
275
+ if any(
276
+ (name == mod_name or name.startswith(mod_name + "."))
277
+ and getattr(getattr(module, "__spec__", None), "_initializing", False)
278
+ for name, module in tuple(sys.modules.items())
279
+ ):
280
+ continue
270
281
  # Keep check → patch → mark atomic. Two clients initialized in
271
282
  # parallel must not wrap the same framework method twice and
272
283
  # therefore run two PDP decisions for one tool invocation.
@@ -3358,6 +3358,23 @@ def ensure_registration_capture(
3358
3358
  if time.monotonic() < _capture_retry_at(engine, tool_key):
3359
3359
  return False
3360
3360
  selector = str(getattr(engine, "_agent_subject_selector", "") or "")
3361
+ if not selector:
3362
+ # No explicit agent_name was given. The gateway's own
3363
+ # credential-info carries the subject this virtual key is bound to,
3364
+ # and ``AgenticEngine.agent_subject`` documents that value as
3365
+ # authoritative - it is already what /decide is told the principal
3366
+ # is. Registering under it keeps one identity across the decision
3367
+ # and the registry instead of refusing a VK that the server can
3368
+ # name perfectly well.
3369
+ #
3370
+ # This is NOT the invented shared name warned about below: it is
3371
+ # server-issued and VK-bound, so it cannot collide across keys.
3372
+ try:
3373
+ selector = str(getattr(engine, "agent_subject", "") or "").strip()
3374
+ except Exception:
3375
+ # Credential discovery is allowed to fail here; the selector
3376
+ # check below then reports the missing name as before.
3377
+ selector = ""
3361
3378
  agent_key = _key(selector.removeprefix("agent:"))
3362
3379
  if not selector.startswith("agent:") or not agent_key:
3363
3380
  # Proof-less registration is deliberately limited to an explicit
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import re
5
+ import hashlib
5
6
  from typing import TYPE_CHECKING, Any, Iterable, Mapping
6
7
 
7
8
  from ..tool import Tool
@@ -15,11 +16,23 @@ if TYPE_CHECKING:
15
16
  _ANTHROPIC_NAME_RE = re.compile(r"[^a-zA-Z0-9_-]")
16
17
 
17
18
 
18
- def to_anthropic(tools: Iterable[Tool]) -> list[dict[str, Any]]:
19
+ def to_anthropic(
20
+ tools: Iterable[Tool], *, name_map: dict[str, str] | None = None,
21
+ ) -> list[dict[str, Any]]:
19
22
  """Convert ``Tool`` objects to Anthropic's Messages API tools array."""
20
23
  out: list[dict[str, Any]] = []
24
+ aliases = dict(name_map or {})
21
25
  for tool in tools:
22
- sanitized = _ANTHROPIC_NAME_RE.sub("_", tool.qualified_name)[:64]
26
+ qualified = tool.qualified_name
27
+ sanitized = _ANTHROPIC_NAME_RE.sub("_", qualified)
28
+ if sanitized != qualified or len(sanitized) > 64:
29
+ # Truncation/replacement alone can send two different tools to
30
+ # the same server name. Keep a stable, provider-valid alias.
31
+ digest = hashlib.sha256(qualified.encode("utf-8")).hexdigest()[:16]
32
+ sanitized = f"{sanitized[:47]}_{digest}"
33
+ if sanitized in aliases and aliases[sanitized] != qualified:
34
+ raise ValueError("Anthropic MCP tool aliases collide")
35
+ aliases[sanitized] = qualified
23
36
  out.append(
24
37
  {
25
38
  "name": sanitized,
@@ -27,6 +40,8 @@ def to_anthropic(tools: Iterable[Tool]) -> list[dict[str, Any]]:
27
40
  "input_schema": tool.schema or {"type": "object", "properties": {}},
28
41
  }
29
42
  )
43
+ if name_map is not None:
44
+ name_map.update(aliases)
30
45
  return out
31
46
 
32
47
 
@@ -35,6 +50,7 @@ def run_tool_uses(
35
50
  content: Iterable[Any],
36
51
  *,
37
52
  extra_headers: Mapping[str, str] | None = None,
53
+ name_map: Mapping[str, str] | None = None,
38
54
  ) -> list[dict[str, Any]]:
39
55
  """Execute every ``tool_use`` block in an assistant content array.
40
56
 
@@ -50,7 +66,7 @@ def run_tool_uses(
50
66
  continue
51
67
  try:
52
68
  result = client.call_qualified(
53
- name,
69
+ (name_map or {}).get(name, name),
54
70
  args or {},
55
71
  call_id=tool_use_id,
56
72
  extra_headers=extra_headers,
@@ -41,6 +41,7 @@ class MCPClient:
41
41
 
42
42
  def __init__(self, shield: "DeepintShield") -> None:
43
43
  self._shield = shield
44
+ self._anthropic_tool_names: dict[str, str] = {}
44
45
 
45
46
  # ───────────────────── preferred native MCP boundary ───────────────────
46
47
 
@@ -193,7 +194,7 @@ class MCPClient:
193
194
  "POST",
194
195
  "/v1/mcp/tool/execute",
195
196
  json_body=payload,
196
- extra_headers=extra_headers,
197
+ extra_headers=self._agent_identity_headers(extra_headers),
197
198
  error_code=ErrorCode.MCP_EXECUTION_FAILED,
198
199
  require_object=True,
199
200
  )
@@ -258,6 +259,36 @@ class MCPClient:
258
259
  )
259
260
  return self.call(server=server, tool=tool, arguments=args_dict, **kwargs)
260
261
 
262
+ # ───────────────────────── agent identity headers ────────────────────────
263
+
264
+ def _agent_identity_headers(
265
+ self, extra_headers: Mapping[str, str] | None
266
+ ) -> dict[str, str]:
267
+ """Attach the client's agent selector to a gateway MCP call.
268
+
269
+ A DeepintShield client represents one agent identity. The PDP decide
270
+ path already sends ``X-Agent-Subject``; the brokered MCP path used to
271
+ send nothing, so a virtual key bound to more than one active agent was
272
+ refused with ``mcp_tool_authorization_unavailable`` on execute while
273
+ its decide calls succeeded. The selector is derived locally (no
274
+ discovery round-trip). Workload tokens stay request-scoped: pass
275
+ ``X-Agent-Token`` through ``extra_headers`` as before. Explicit caller
276
+ headers always win; nothing is added for a client without an
277
+ ``agent_name``.
278
+ """
279
+ headers = dict(extra_headers or {})
280
+ agent_name = str(getattr(self._shield, "agent_name", "") or "").strip()
281
+ if not agent_name:
282
+ return headers
283
+ if any(str(key).lower() == "x-agent-subject" for key in headers):
284
+ return headers
285
+ from ..agentic.registry import _registry_key
286
+
287
+ agent_key = _registry_key(agent_name)
288
+ if agent_key:
289
+ headers["X-Agent-Subject"] = f"agent:{agent_key}"
290
+ return headers
291
+
261
292
  # ─────────────────────────── discovery (optional) ────────────────────────
262
293
 
263
294
  def list_tools(
@@ -274,7 +305,7 @@ class MCPClient:
274
305
  unavailable in your environment, supply tool definitions manually.
275
306
  """
276
307
  self._warn_legacy("list_tools")
277
- headers: dict[str, str] = {}
308
+ headers: dict[str, str] = self._agent_identity_headers(None)
278
309
  if admin_token:
279
310
  headers["Authorization"] = f"Bearer {admin_token}"
280
311
  payload = self._shield.request(
@@ -338,7 +369,7 @@ class MCPClient:
338
369
  """Convert tools to Anthropic Messages API ``tools=`` array shape."""
339
370
  self._warn_legacy("to_anthropic")
340
371
  from .adapters import anthropic as _anthropic
341
- return _anthropic.to_anthropic(tools)
372
+ return _anthropic.to_anthropic(tools, name_map=self._anthropic_tool_names)
342
373
 
343
374
  def run_anthropic_tool_uses(
344
375
  self,
@@ -356,6 +387,7 @@ class MCPClient:
356
387
  self,
357
388
  content,
358
389
  extra_headers=extra_headers,
390
+ name_map=self._anthropic_tool_names,
359
391
  )
360
392
 
361
393
  def to_langchain(self, tools: Iterable[Tool]) -> list[Any]:
@@ -2,8 +2,9 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
- from .._prompt_cache import PROVIDER_ANTHROPIC, build_http_client
5
+ from .._prompt_cache import PROVIDER_ANTHROPIC, build_request_hook
6
6
  from ..errors import ErrorCode, _dependency_error
7
+ from ..transport import connection_headers, _install_agent_selector_header_hook
7
8
 
8
9
  if TYPE_CHECKING:
9
10
  from ..client import DeepintShield
@@ -19,8 +20,8 @@ def build_client(shield: "DeepintShield", *, passthrough: bool = False, **kwargs
19
20
  Provider Prompt Caching switch - disabled workspaces have the markers
20
21
  stripped at the gateway before they reach Anthropic.
21
22
 
22
- Pass a custom ``http_client`` to bypass injection entirely; the SDK trusts
23
- the caller's transport in that case.
23
+ Pass a custom ``http_client`` to manage caching yourself; only
24
+ case-insensitive agent selector override handling is added to that client.
24
25
  """
25
26
  try:
26
27
  import anthropic
@@ -33,13 +34,28 @@ def build_client(shield: "DeepintShield", *, passthrough: bool = False, **kwargs
33
34
 
34
35
  base_url = shield.anthropic_passthrough_base_url() if passthrough else shield.anthropic_base_url()
35
36
  http_client = kwargs.pop("http_client", None)
37
+ owns_http_client = http_client is None
36
38
  if http_client is None:
37
- http_client = build_http_client(PROVIDER_ANTHROPIC, timeout=shield.timeout)
38
-
39
- return anthropic.Anthropic(
40
- base_url=kwargs.pop("base_url", base_url),
41
- api_key=kwargs.pop("api_key", shield.api_key()),
42
- default_headers={**shield.headers(), **(kwargs.pop("default_headers", None) or {})},
43
- http_client=http_client,
44
- **kwargs,
45
- )
39
+ # Use the installed SDK's public transport class: older releases use
40
+ # httpx, while current releases require httpx2 and reject httpx.Client.
41
+ http_client = anthropic.DefaultHttpxClient(
42
+ timeout=shield.timeout,
43
+ follow_redirects=False,
44
+ event_hooks={"request": [build_request_hook(PROVIDER_ANTHROPIC)]},
45
+ )
46
+
47
+ try:
48
+ client = anthropic.Anthropic(
49
+ base_url=kwargs.pop("base_url", base_url),
50
+ api_key=kwargs.pop("api_key", shield.api_key()),
51
+ default_headers=connection_headers(shield, extra=kwargs.pop("default_headers", None)),
52
+ http_client=http_client,
53
+ **kwargs,
54
+ )
55
+ except Exception:
56
+ if owns_http_client:
57
+ http_client.close()
58
+ raise
59
+ # Let the SDK validate caller-supplied transports before mutating hooks.
60
+ _install_agent_selector_header_hook(http_client)
61
+ return client
@@ -4,6 +4,12 @@ from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from .._gemini_cache import GenaiCachedClient, GeminiCacheManager, env_ttl_seconds
6
6
  from ..errors import ErrorCode, _dependency_error
7
+ from ..transport import (
8
+ connection_headers,
9
+ _install_agent_selector_header_hook,
10
+ _normalize_agent_selector_headers,
11
+ _normalize_agent_selector_headers_async,
12
+ )
7
13
 
8
14
  if TYPE_CHECKING:
9
15
  from ..client import DeepintShield
@@ -22,9 +28,34 @@ def build_client(shield: "DeepintShield", *, passthrough: bool = False, **kwargs
22
28
  ) from exc
23
29
 
24
30
  base_url = shield.genai_passthrough_base_url() if passthrough else shield.genai_base_url()
31
+ supplied_options = kwargs.pop("http_options", None)
32
+ if isinstance(supplied_options, HttpOptions):
33
+ options = supplied_options.model_copy()
34
+ else:
35
+ options = HttpOptions(**(supplied_options or {}))
36
+ options.base_url = options.base_url or base_url
37
+ options.headers = connection_headers(shield, extra=options.headers)
38
+ # Preserve caller options, transports, and hooks. Native sync/async clients
39
+ # use the same stateless selector normalization, including stream requests.
40
+ for client_attr, args_attr, hook in (
41
+ ("httpx_client", "client_args", _normalize_agent_selector_headers),
42
+ ("httpx_async_client", "async_client_args", _normalize_agent_selector_headers_async),
43
+ ):
44
+ native_http_client = getattr(options, client_attr, None)
45
+ if native_http_client is not None:
46
+ _install_agent_selector_header_hook(native_http_client)
47
+ else:
48
+ client_args = dict(getattr(options, args_attr, None) or {})
49
+ event_hooks = dict(client_args.get("event_hooks") or {})
50
+ request_hooks = list(event_hooks.get("request") or [])
51
+ if hook not in request_hooks:
52
+ request_hooks.append(hook)
53
+ event_hooks["request"] = request_hooks
54
+ client_args["event_hooks"] = event_hooks
55
+ setattr(options, args_attr, client_args)
25
56
  return genai.Client(
26
57
  api_key=kwargs.pop("api_key", shield.api_key()),
27
- http_options=kwargs.pop("http_options", HttpOptions(base_url=base_url, headers=shield.headers())),
58
+ http_options=options,
28
59
  **kwargs,
29
60
  )
30
61
 
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from .._prompt_cache import PROVIDER_OPENAI, build_http_client
6
6
  from ..errors import ErrorCode, _dependency_error
7
+ from ..transport import connection_headers, _install_agent_selector_header_hook
7
8
 
8
9
  if TYPE_CHECKING:
9
10
  from ..client import DeepintShield
@@ -19,8 +20,8 @@ def build_client(shield: "DeepintShield", *, passthrough: bool = False, **kwargs
19
20
  on the gateway - if the workspace has it disabled the gateway strips the
20
21
  key before forwarding.
21
22
 
22
- Callers passing their own ``http_client`` are respected (no hook is
23
- attached); they're assumed to manage caching themselves.
23
+ Callers passing their own ``http_client`` manage caching themselves; only
24
+ case-insensitive agent selector override handling is added to that client.
24
25
  """
25
26
  try:
26
27
  from openai import OpenAI
@@ -35,11 +36,12 @@ def build_client(shield: "DeepintShield", *, passthrough: bool = False, **kwargs
35
36
  http_client = kwargs.pop("http_client", None)
36
37
  if http_client is None:
37
38
  http_client = build_http_client(PROVIDER_OPENAI, timeout=shield.timeout)
39
+ _install_agent_selector_header_hook(http_client)
38
40
 
39
41
  return OpenAI(
40
42
  base_url=kwargs.pop("base_url", base_url),
41
43
  api_key=kwargs.pop("api_key", shield.api_key()),
42
- default_headers={**shield.headers(), **(kwargs.pop("default_headers", None) or {})},
44
+ default_headers=connection_headers(shield, extra=kwargs.pop("default_headers", None)),
43
45
  http_client=http_client,
44
46
  **kwargs,
45
47
  )