@macaw-ai/client 0.0.0-stage → 0.9.11

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 (142) hide show
  1. package/LICENSE.md +339 -0
  2. package/bin/install.js +422 -0
  3. package/package.json +37 -4
  4. package/payload/README.md +56 -0
  5. package/payload/secureAI/LICENSE +190 -0
  6. package/payload/secureAI/NOTICE +5 -0
  7. package/payload/secureAI/demos/policy-controls/1_uncontrolled.py +139 -0
  8. package/payload/secureAI/demos/policy-controls/2_with_macaw.py +139 -0
  9. package/payload/secureAI/demos/policy-controls/3_with_user_policies.py +162 -0
  10. package/payload/secureAI/demos/policy-controls/credentials.txt +12 -0
  11. package/payload/secureAI/demos/policy-controls/malicious_report.txt +21 -0
  12. package/payload/secureAI/demos/policy-controls/sales_report.txt +20 -0
  13. package/payload/secureAI/demos/tutorial-1-rbac/README.md +143 -0
  14. package/payload/secureAI/demos/tutorial-1-rbac/app.py +147 -0
  15. package/payload/secureAI/demos/tutorial-1-rbac/authprompts_demo.py +306 -0
  16. package/payload/secureAI/demos/tutorial-1-rbac/config/claims-config.yaml +152 -0
  17. package/payload/secureAI/demos/tutorial-1-rbac/demo_secureopenai_with_policies.py +150 -0
  18. package/payload/secureAI/demos/tutorial-1-rbac/policies/bu_Analytics.json +8 -0
  19. package/payload/secureAI/demos/tutorial-1-rbac/policies/company_FinTech_Corp.json +70 -0
  20. package/payload/secureAI/demos/tutorial-1-rbac/policies/llm-base.json +69 -0
  21. package/payload/secureAI/demos/tutorial-1-rbac/policies/team_Reporting.json +8 -0
  22. package/payload/secureAI/demos/tutorial-1-rbac/policies/user_alice.json +32 -0
  23. package/payload/secureAI/demos/tutorial-1-rbac/policies/user_bob.json +28 -0
  24. package/payload/secureAI/demos/tutorial-1-rbac/policies/user_carol.json +34 -0
  25. package/payload/secureAI/demos/tutorial-1-rbac/requirements.txt +6 -0
  26. package/payload/secureAI/demos/tutorial-1-rbac/setup/auth0_complete_setup.sh +392 -0
  27. package/payload/secureAI/demos/tutorial-1-rbac/setup/keycloak_complete_setup.sh +320 -0
  28. package/payload/secureAI/demos/tutorial-2-intent/Policies/alation-remote-proxy.json +155 -0
  29. package/payload/secureAI/demos/tutorial-2-intent/Policies/bu.json +22 -0
  30. package/payload/secureAI/demos/tutorial-2-intent/Policies/company.json +17 -0
  31. package/payload/secureAI/demos/tutorial-2-intent/Policies/user_alice.json +48 -0
  32. package/payload/secureAI/demos/tutorial-2-intent/Policies/user_bob.json +83 -0
  33. package/payload/secureAI/demos/tutorial-2-intent/README.md +213 -0
  34. package/payload/secureAI/demos/tutorial-2-intent/script.py +188 -0
  35. package/payload/secureAI/demos/tutorial-2-intent/setup/alation_verifier.py +177 -0
  36. package/payload/secureAI/demos/tutorial-2-intent/setup/get_alation_user_token.py +180 -0
  37. package/payload/secureAI/examples/README.md +244 -0
  38. package/payload/secureAI/examples/anthropic/anthropic_1a_dropin_simple.py +103 -0
  39. package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind.py +190 -0
  40. package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind_streaming.py +190 -0
  41. package/payload/secureAI/examples/anthropic/anthropic_1c_a2a_invoke.py +156 -0
  42. package/payload/secureAI/examples/attestations/1a_trade_alice.py +215 -0
  43. package/payload/secureAI/examples/attestations/1a_trade_bob.py +164 -0
  44. package/payload/secureAI/examples/attestations/1b_grant_admin.py +170 -0
  45. package/payload/secureAI/examples/attestations/1b_grant_researcher.py +231 -0
  46. package/payload/secureAI/examples/attestations/README.md +128 -0
  47. package/payload/secureAI/examples/langchain/langchain_1a_dropin_simple.py +199 -0
  48. package/payload/secureAI/examples/langchain/langchain_1b_multiuser.py +262 -0
  49. package/payload/secureAI/examples/langchain/langchain_1c_orchestration.py +332 -0
  50. package/payload/secureAI/examples/langchain/langchain_1d_llm_openai.py +130 -0
  51. package/payload/secureAI/examples/langchain/langchain_1e_llm_anthropic.py +130 -0
  52. package/payload/secureAI/examples/langchain/langchain_1f_memory.py +214 -0
  53. package/payload/secureAI/examples/litellm/litellm_1a_dropin_simple.py +131 -0
  54. package/payload/secureAI/examples/mcp/1a_simple_invocation.py +115 -0
  55. package/payload/secureAI/examples/mcp/1b_discovery_and_resources.py +142 -0
  56. package/payload/secureAI/examples/mcp/1c_logging_client.py +100 -0
  57. package/payload/secureAI/examples/mcp/1d_progress_client.py +100 -0
  58. package/payload/secureAI/examples/mcp/1e_sampling_client.py +150 -0
  59. package/payload/secureAI/examples/mcp/1e_sampling_server.py +90 -0
  60. package/payload/secureAI/examples/mcp/1f_elicitation_client.py +127 -0
  61. package/payload/secureAI/examples/mcp/1f_elicitation_server.py +73 -0
  62. package/payload/secureAI/examples/mcp/1g_roots_client.py +122 -0
  63. package/payload/secureAI/examples/mcp/1g_roots_server.py +85 -0
  64. package/payload/secureAI/examples/mcp/2a_proxy_snowflake.py +212 -0
  65. package/payload/secureAI/examples/mcp/2b_proxy_databricks.py +299 -0
  66. package/payload/secureAI/examples/mcp/2c_proxy_stdio_fetch.py +91 -0
  67. package/payload/secureAI/examples/mcp/2d_proxy_stdio_salesforce.py +119 -0
  68. package/payload/secureAI/examples/mcp/2e_proxy_multiuser.py +136 -0
  69. package/payload/secureAI/examples/mcp/2f_proxy_stdio_databricks.py +144 -0
  70. package/payload/secureAI/examples/mcp/3a_native_mcp_client.py +104 -0
  71. package/payload/secureAI/examples/mcp/3b_native_mcp_proxy.py +114 -0
  72. package/payload/secureAI/examples/mcp/3c_native_mcp_sampling.py +180 -0
  73. package/payload/secureAI/examples/mcp/3d_native_mcp_2026.py +135 -0
  74. package/payload/secureAI/examples/mcp/securemcp_calculator.py +248 -0
  75. package/payload/secureAI/examples/mcp/securemcp_server.py +75 -0
  76. package/payload/secureAI/examples/openai/openai_1a_dropin_simple.py +102 -0
  77. package/payload/secureAI/examples/openai/openai_1b_multiuser_bind.py +193 -0
  78. package/payload/secureAI/examples/openai/openai_1b_multiuser_bind_streaming.py +199 -0
  79. package/payload/secureAI/examples/openai/openai_1c_a2a_invoke.py +156 -0
  80. package/payload/secureAI/examples/openai/openai_1d_invocation_patterns.py +337 -0
  81. package/payload/secureAI/examples/phishing-resistance/1a_login_alice.py +180 -0
  82. package/payload/secureAI/examples/phishing-resistance/1b_trade_alice.py +241 -0
  83. package/payload/secureAI/examples/phishing-resistance/1b_trade_bob.py +164 -0
  84. package/payload/secureAI/examples/phishing-resistance/README.md +124 -0
  85. package/payload/secureAI/examples/phishing-resistance/get_token.py +114 -0
  86. package/payload/secureAI/examples/pydantic_ai/README.md +88 -0
  87. package/payload/secureAI/examples/pydantic_ai/policies/app_pydantic-agent.json +10 -0
  88. package/payload/secureAI/examples/pydantic_ai/pydanticai_1a_dropin_simple.py +90 -0
  89. package/payload/secureAI/examples/pydantic_ai/pydanticai_1b_model_routing.py +85 -0
  90. package/payload/secureAI/examples/pydantic_ai/pydanticai_1c_native_tools.py +83 -0
  91. package/payload/secureAI/examples/pydantic_ai/pydanticai_1d_multiuser_bind.py +104 -0
  92. package/payload/secureAI/examples/pydantic_ai/pydanticai_1e_mcp_compose.py +92 -0
  93. package/payload/secureAI/macaw_adapters/__init__.py +55 -0
  94. package/payload/secureAI/macaw_adapters/anthropic/REFERENCE.md +374 -0
  95. package/payload/secureAI/macaw_adapters/anthropic/__init__.py +22 -0
  96. package/payload/secureAI/macaw_adapters/anthropic/secure_anthropic.py +792 -0
  97. package/payload/secureAI/macaw_adapters/anthropic_ext_tools/__init__.py +25 -0
  98. package/payload/secureAI/macaw_adapters/anthropic_ext_tools/secure_anthropic.py +840 -0
  99. package/payload/secureAI/macaw_adapters/langchain/REFERENCE.md +234 -0
  100. package/payload/secureAI/macaw_adapters/langchain/__init__.py +100 -0
  101. package/payload/secureAI/macaw_adapters/langchain/_utils.py +83 -0
  102. package/payload/secureAI/macaw_adapters/langchain/agents.py +296 -0
  103. package/payload/secureAI/macaw_adapters/langchain/anthropic.py +456 -0
  104. package/payload/secureAI/macaw_adapters/langchain/callbacks.py +306 -0
  105. package/payload/secureAI/macaw_adapters/langchain/memory.py +352 -0
  106. package/payload/secureAI/macaw_adapters/langchain/openai.py +410 -0
  107. package/payload/secureAI/macaw_adapters/langchain/tools.py +174 -0
  108. package/payload/secureAI/macaw_adapters/litellm/README.md +216 -0
  109. package/payload/secureAI/macaw_adapters/litellm/__init__.py +143 -0
  110. package/payload/secureAI/macaw_adapters/litellm/secure_litellm.py +686 -0
  111. package/payload/secureAI/macaw_adapters/mcp/REFERENCE.md +460 -0
  112. package/payload/secureAI/macaw_adapters/mcp/__init__.py +65 -0
  113. package/payload/secureAI/macaw_adapters/mcp/_endpoint.py +193 -0
  114. package/payload/secureAI/macaw_adapters/mcp/_endpoint2.py +225 -0
  115. package/payload/secureAI/macaw_adapters/mcp/_serve.py +25 -0
  116. package/payload/secureAI/macaw_adapters/mcp/client.py +721 -0
  117. package/payload/secureAI/macaw_adapters/mcp/decorators.py +228 -0
  118. package/payload/secureAI/macaw_adapters/mcp/fastmcp_compat.py +353 -0
  119. package/payload/secureAI/macaw_adapters/mcp/mcp.py +785 -0
  120. package/payload/secureAI/macaw_adapters/mcp/proxy.py +626 -0
  121. package/payload/secureAI/macaw_adapters/mcp/server.py +317 -0
  122. package/payload/secureAI/macaw_adapters/mcp/subscriptions.py +450 -0
  123. package/payload/secureAI/macaw_adapters/openai/REFERENCE.md +368 -0
  124. package/payload/secureAI/macaw_adapters/openai/__init__.py +22 -0
  125. package/payload/secureAI/macaw_adapters/openai/secure_openai.py +833 -0
  126. package/payload/secureAI/macaw_adapters/openai_ext_tools/REFERENCE.md +368 -0
  127. package/payload/secureAI/macaw_adapters/openai_ext_tools/__init__.py +25 -0
  128. package/payload/secureAI/macaw_adapters/openai_ext_tools/secure_openai.py +876 -0
  129. package/payload/secureAI/macaw_adapters/pydantic_ai/__init__.py +36 -0
  130. package/payload/secureAI/macaw_adapters/pydantic_ai/_core.py +257 -0
  131. package/payload/secureAI/macaw_adapters/pydantic_ai/agent.py +218 -0
  132. package/payload/secureAI/macaw_adapters/pydantic_ai/model.py +99 -0
  133. package/payload/secureAI/macaw_adapters/pydantic_ai/toolset.py +86 -0
  134. package/payload/secureAI/test_harness_quick.py +296 -0
  135. package/wheels/macaw_adapters-0.9.10.1-py3-none-any.whl +0 -0
  136. package/wheels/macaw_client-0.9.10.1-cp312-cp312-macosx_11_0_arm64.whl +0 -0
  137. package/wheels/macaw_client-0.9.10.1-cp312-cp312-manylinux_2_17_x86_64.whl +0 -0
  138. package/wheels/macaw_client-0.9.10.1-cp314-cp314-macosx_11_0_arm64.whl +0 -0
  139. package/wheels/macaw_client-0.9.10.1-cp314-cp314-manylinux_2_17_x86_64.whl +0 -0
  140. package/wheels/macaw_client-0.9.10.1-cp314-cp314-win_amd64.whl +0 -0
  141. package/wheels/macaw_client-0.9.10.1-cp39-cp39-macosx_11_0_arm64.whl +0 -0
  142. package/README.md +0 -3
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ openai_1b_multiuser_bind_streaming.py - Multi-user OpenAI with streaming
4
+
5
+ Same as 1b but demonstrates streaming responses with bind_to_user pattern.
6
+
7
+ Prerequisites:
8
+ - MACAW SDK installed (pip install macaw-client macaw-adapters)
9
+ - OPENAI_API_KEY environment variable
10
+ - Identity Provider configured (Console -> Settings -> Identity Bridge)
11
+ - Test users: alice/Alice123!, bob/Bob@123!
12
+
13
+ Run:
14
+ export OPENAI_API_KEY=sk-...
15
+ python openai_1b_multiuser_bind_streaming.py
16
+
17
+ No IdP configured? Run simpler example first:
18
+ python openai_1a_dropin_simple.py
19
+ """
20
+
21
+ import os
22
+ import sys
23
+
24
+ from macaw_adapters.openai import SecureOpenAI
25
+ from macaw_client import MACAWClient, RemoteIdentityProvider
26
+
27
+
28
+ # Test configurations based on user policies
29
+ # Alice: GPT-3.5 only, max 500 tokens
30
+ # Bob: GPT-3.5/4, max 2000 tokens
31
+ USER_TESTS = {
32
+ "alice": {
33
+ "password": "Alice123!",
34
+ "policy_desc": "GPT-3.5 only, max 500 tokens",
35
+ "tests": [
36
+ # (model, max_tokens, should_succeed)
37
+ ("gpt-3.5-turbo", 400, True), # ALLOWED - correct model, within limit
38
+ ("gpt-4", 400, False), # BLOCKED - alice can't use GPT-4
39
+ ("gpt-3.5-turbo", 600, False), # BLOCKED - exceeds max_tokens
40
+ ]
41
+ },
42
+ "bob": {
43
+ "password": "Bob@123!",
44
+ "policy_desc": "GPT-3.5/4, max 2000 tokens",
45
+ "tests": [
46
+ ("gpt-3.5-turbo", 400, True), # ALLOWED
47
+ ("gpt-4", 400, True), # ALLOWED - bob CAN use GPT-4
48
+ ("gpt-3.5-turbo", 600, True), # ALLOWED - within bob's 2000 limit
49
+ ("gpt-4", 2500, False), # BLOCKED - exceeds max_tokens
50
+ ]
51
+ }
52
+ }
53
+
54
+
55
+ def create_user_client(username: str, password: str) -> MACAWClient:
56
+ """Create authenticated user client."""
57
+ print(f" Authenticating {username}...")
58
+ jwt_token, _ = RemoteIdentityProvider().login(username, password)
59
+
60
+ user = MACAWClient(
61
+ user_name=username,
62
+ iam_token=jwt_token,
63
+ agent_type="user",
64
+ app_name="financial-app"
65
+ )
66
+
67
+ if not user.register():
68
+ raise RuntimeError(f"Failed to register user {username}")
69
+
70
+ print(f" User agent: {user.agent_id}")
71
+ return user
72
+
73
+
74
+ def test_user(username: str, openai_service: SecureOpenAI):
75
+ """Test a user with bind_to_user pattern."""
76
+ config = USER_TESTS[username]
77
+
78
+ print(f"\n{'=' * 60}")
79
+ print(f"Testing {username.upper()} via bind_to_user")
80
+ print(f"Policy: {config['policy_desc']}")
81
+ print("=" * 60)
82
+
83
+ # 1. Create user client with JWT
84
+ user_client = create_user_client(username, config["password"])
85
+
86
+ # 2. Bind user to service
87
+ print(f"\n Binding to service: {openai_service.server_id}")
88
+ user_openai = openai_service.bind_to_user(user_client)
89
+
90
+ # 3. Test with different parameters
91
+ print(f"\n Running tests:")
92
+ for model, max_tokens, should_succeed in config["tests"]:
93
+ expected = "SHOULD SUCCEED" if should_succeed else "SHOULD BE BLOCKED"
94
+ print(f"\n -> {model}, {max_tokens} tokens ({expected})")
95
+
96
+ try:
97
+ response = user_openai.chat.completions.create(
98
+ model=model,
99
+ max_tokens=max_tokens,
100
+ messages=[
101
+ {"role": "system", "content": "You are a financial analyst."},
102
+ {"role": "user", "content": "What is revenue growth? Brief answer."}
103
+ ],
104
+ stream=True
105
+ )
106
+
107
+ if should_succeed:
108
+ print(f" SUCCESS (as expected)")
109
+ # Collect streaming chunks
110
+ collected_content = []
111
+ chunk_count = 0
112
+ for chunk in response:
113
+ chunk_count += 1
114
+ if chunk.choices and chunk.choices[0].delta.content:
115
+ collected_content.append(chunk.choices[0].delta.content)
116
+ full_content = "".join(collected_content)
117
+ print(f" Chunks received: {chunk_count}")
118
+ print(f" Response: {full_content[:60]}...")
119
+ else:
120
+ print(f" UNEXPECTED SUCCESS - should have been blocked!")
121
+
122
+ except Exception as e:
123
+ error = str(e)
124
+ is_policy_block = any(word in error.lower() for word in
125
+ ["not in allowed", "not permitted", "policy",
126
+ "max_tokens", "model", "blocked", "denied"])
127
+ if is_policy_block:
128
+ if should_succeed:
129
+ print(f" UNEXPECTED BLOCK: {error[:60]}...")
130
+ else:
131
+ print(f" CORRECTLY BLOCKED by policy")
132
+ else:
133
+ print(f" ERROR: {error[:60]}...")
134
+
135
+ # 4. Cleanup
136
+ print(f"\n Unbinding user...")
137
+ user_openai.unbind()
138
+ print(f" is_bound: {user_openai.is_bound}")
139
+
140
+
141
+ def main():
142
+ # Check for API key
143
+ if not os.environ.get("OPENAI_API_KEY"):
144
+ print("Set OPENAI_API_KEY environment variable")
145
+ print(" export OPENAI_API_KEY=sk-...")
146
+ return
147
+
148
+ print("=" * 60)
149
+ print("Example 1b: Multi-user with bind_to_user (OpenAI)")
150
+ print("=" * 60)
151
+ print("\nPath: bind_to_user (per-user identity)")
152
+ print("Use when: SaaS app, different users need different permissions")
153
+ print("\nArchitecture:")
154
+ print(" - Single SecureOpenAI service (shared)")
155
+ print(" - Per-user MACAWClient with JWT identity")
156
+ print(" - bind_to_user() connects user to service")
157
+ print(" - User's identity flows through for policy evaluation")
158
+
159
+ # Create SINGLE service (shared across all users)
160
+ print("\n--- Creating SecureOpenAI service ---")
161
+ openai_service = SecureOpenAI(app_name="openai-service")
162
+ print(f"Service registered: {openai_service.server_id}")
163
+ print(f"Mode: {openai_service._mode}")
164
+
165
+ # Test alice (restricted)
166
+ try:
167
+ test_user("alice", openai_service)
168
+ except Exception as e:
169
+ print(f"\nFailed to test alice: {e}")
170
+
171
+ # Test bob (enhanced)
172
+ try:
173
+ test_user("bob", openai_service)
174
+ except Exception as e:
175
+ print(f"\nFailed to test bob: {e}")
176
+
177
+ print("\n" + "=" * 60)
178
+ print("Example complete!")
179
+ print("=" * 60)
180
+
181
+
182
+ if __name__ == "__main__":
183
+ try:
184
+ main()
185
+ except Exception as e:
186
+ err = str(e)
187
+ print("\n" + "=" * 60)
188
+ if "OPENAI_API_KEY" in err or "api_key" in err.lower():
189
+ print("ERROR: OpenAI API key not configured")
190
+ print("Fix: export OPENAI_API_KEY=sk-...")
191
+ elif "Local provider does not support" in err:
192
+ print("ERROR: Identity Provider not configured")
193
+ print("Fix: Console -> Settings -> Identity Bridge")
194
+ print("\nOr run simpler example first:")
195
+ print(" python openai_1a_dropin_simple.py")
196
+ else:
197
+ print(f"ERROR: {e}")
198
+ print("=" * 60)
199
+ sys.exit(1)
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ openai_1c_a2a_invoke.py - Agent-to-Agent invocation with invoke_tool
4
+
5
+ Demonstrates explicit tool invocation for agent systems and cross-service calls.
6
+ Use when you need fine-grained control over routing and tool invocation.
7
+
8
+ Prerequisites:
9
+ - MACAW SDK installed (pip install macaw-client macaw-adapters)
10
+ - OPENAI_API_KEY environment variable
11
+ - Identity Provider configured (Console -> Settings -> Identity Bridge)
12
+ - Test users: alice/Alice123!
13
+
14
+ Run:
15
+ export OPENAI_API_KEY=sk-...
16
+ python openai_1c_a2a_invoke.py
17
+
18
+ No IdP configured? Run simpler example first:
19
+ python openai_1a_dropin_simple.py
20
+ """
21
+
22
+ import os
23
+ import sys
24
+
25
+ from macaw_adapters.openai import SecureOpenAI
26
+ from macaw_client import MACAWClient, RemoteIdentityProvider
27
+
28
+
29
+ def main():
30
+ # Check for API key
31
+ if not os.environ.get("OPENAI_API_KEY"):
32
+ print("Set OPENAI_API_KEY environment variable")
33
+ print(" export OPENAI_API_KEY=sk-...")
34
+ return
35
+
36
+ print("=" * 60)
37
+ print("Example 1c: A2A with invoke_tool (OpenAI)")
38
+ print("=" * 60)
39
+ print("\nPath: invoke_tool (explicit A2A control)")
40
+ print("Use when: Agent systems, explicit control, cross-service calls")
41
+ print("\nArchitecture:")
42
+ print(" - SecureOpenAI service registers tools")
43
+ print(" - User agent calls invoke_tool() directly")
44
+ print(" - Must know MAPL tool name: tool:<app>/generate")
45
+ print(" - Returns raw dict (not SDK types)")
46
+
47
+ # 1. Create OpenAI service
48
+ print("\n--- Creating SecureOpenAI service ---")
49
+ openai_service = SecureOpenAI(app_name="openai-service")
50
+ print(f"Service registered: {openai_service.server_id}")
51
+ print(f"Tools registered:")
52
+ for tool_name in openai_service.tools.keys():
53
+ print(f" - {tool_name}")
54
+
55
+ # 2. Create user agent with JWT
56
+ print("\n--- Creating user agent ---")
57
+ print(" Authenticating alice...")
58
+ jwt_token, _ = RemoteIdentityProvider().login("alice", "Alice123!")
59
+
60
+ user = MACAWClient(
61
+ user_name="alice",
62
+ iam_token=jwt_token,
63
+ agent_type="user",
64
+ app_name="my-agent"
65
+ )
66
+
67
+ if not user.register():
68
+ print(" Failed to register user agent")
69
+ return
70
+
71
+ print(f" User agent: {user.agent_id}")
72
+
73
+ # 3. Explicit invoke_tool
74
+ print("\n--- Test 1: Direct invoke_tool ---")
75
+ print(f" Tool name: tool:{openai_service.app_name}/generate")
76
+ print(f" Target: {openai_service.server_id}")
77
+
78
+ result = user.invoke_tool(
79
+ tool_name=f"tool:{openai_service.app_name}/generate",
80
+ parameters={
81
+ "model": "gpt-3.5-turbo",
82
+ "max_tokens": 100,
83
+ "messages": [
84
+ {"role": "system", "content": "You are a financial analyst."},
85
+ {"role": "user", "content": "What is compound interest? Brief answer."}
86
+ ]
87
+ },
88
+ target_agent=openai_service.server_id
89
+ )
90
+
91
+ # Result is raw dict - you handle parsing
92
+ if isinstance(result, dict):
93
+ if "error" in result:
94
+ print(f" Error: {result['error']}")
95
+ elif "choices" in result:
96
+ content = result["choices"][0]["message"]["content"]
97
+ print(f" Success!")
98
+ print(f" Model: {result.get('model')}")
99
+ print(f" Response: {content[:80]}...")
100
+ else:
101
+ print(f" Unexpected result: {result}")
102
+
103
+ # 4. Test policy enforcement
104
+ print("\n--- Test 2: Policy enforcement (GPT-4 blocked for alice) ---")
105
+ try:
106
+ result = user.invoke_tool(
107
+ tool_name=f"tool:{openai_service.app_name}/generate",
108
+ parameters={
109
+ "model": "gpt-4", # Alice can't use GPT-4!
110
+ "max_tokens": 100,
111
+ "messages": [{"role": "user", "content": "Hello"}]
112
+ },
113
+ target_agent=openai_service.server_id
114
+ )
115
+
116
+ if isinstance(result, dict) and "error" in result:
117
+ print(f" Correctly blocked: {result['error'][:60]}...")
118
+ else:
119
+ print(f" Unexpected - should have been blocked!")
120
+ except Exception as e:
121
+ print(f" Correctly blocked: {str(e)[:60]}...")
122
+
123
+ # 5. Service discovery (optional advanced feature)
124
+ print("\n--- Service Discovery ---")
125
+ print(" (Agents can discover services dynamically via registry)")
126
+ print(f" Known service: {openai_service.server_id}")
127
+ print(f" Tool pattern: tool:<app_name>/generate")
128
+
129
+ print("\n" + "=" * 60)
130
+ print("Example complete!")
131
+ print("=" * 60)
132
+ print("\nKey differences from bind_to_user:")
133
+ print(" - Must know MAPL tool names (tool:xxx/generate)")
134
+ print(" - Must know target agent server_id")
135
+ print(" - Returns raw dict (not ChatCompletion)")
136
+ print(" - More control, but more responsibility")
137
+
138
+
139
+ if __name__ == "__main__":
140
+ try:
141
+ main()
142
+ except Exception as e:
143
+ err = str(e)
144
+ print("\n" + "=" * 60)
145
+ if "OPENAI_API_KEY" in err or "api_key" in err.lower():
146
+ print("ERROR: OpenAI API key not configured")
147
+ print("Fix: export OPENAI_API_KEY=sk-...")
148
+ elif "Local provider does not support" in err:
149
+ print("ERROR: Identity Provider not configured")
150
+ print("Fix: Console -> Settings -> Identity Bridge")
151
+ print("\nOr run simpler example first:")
152
+ print(" python openai_1a_dropin_simple.py")
153
+ else:
154
+ print(f"ERROR: {e}")
155
+ print("=" * 60)
156
+ sys.exit(1)
@@ -0,0 +1,337 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ openai_1d_invocation_patterns.py - Compare invoke_tool vs bind_to_user
4
+
5
+ Compares two ways to invoke SecureOpenAI with the same security guarantees:
6
+
7
+ PATH 1: Direct invoke_tool
8
+ - Explicit A2A (agent-to-agent) control
9
+ - Must know MAPL tool name: tool:<app>/generate
10
+ - Returns raw dict
11
+ - Use when: Agent systems, cross-service calls, custom routing
12
+
13
+ PATH 2: bind_to_user wrapper
14
+ - OpenAI-compatible API (chat.completions.create)
15
+ - Familiar SDK experience
16
+ - Internally calls invoke_tool
17
+ - Use when: Drop-in replacement, developer ergonomics
18
+
19
+ Both paths enforce per-user policies and propagate JWT identity.
20
+
21
+ Prerequisites:
22
+ - MACAW SDK installed (pip install macaw-client macaw-adapters)
23
+ - OPENAI_API_KEY environment variable
24
+ - Identity Provider configured (Console -> Settings -> Identity Bridge)
25
+ - Test users: alice/Alice123!, bob/Bob@123!
26
+
27
+ Run:
28
+ export OPENAI_API_KEY=sk-...
29
+ python openai_1d_invocation_patterns.py
30
+
31
+ No IdP configured? Run simpler example first:
32
+ python openai_1a_dropin_simple.py
33
+ """
34
+
35
+ import os
36
+ import sys
37
+
38
+ from macaw_adapters.openai import SecureOpenAI
39
+ from macaw_client import MACAWClient, RemoteIdentityProvider
40
+
41
+
42
+ # User-specific test configurations based on their policies
43
+ # Alice: GPT-3.5 only, max 500 tokens
44
+ # Bob: GPT-3.5/4, max 2000 tokens
45
+ USER_TESTS = {
46
+ "alice": {
47
+ "password": "Alice123!",
48
+ "policy_desc": "gpt-3.5-turbo only, max_tokens <= 500",
49
+ "tests": [
50
+ # (model, max_tokens, query, should_succeed)
51
+ ("gpt-3.5-turbo", 400, "What is revenue growth?", True), # ALLOWED
52
+ ("gpt-4", 400, "What is revenue growth?", False), # BLOCKED - wrong model
53
+ ("gpt-3.5-turbo", 600, "What is compound interest?", False), # BLOCKED - exceeds max_tokens
54
+ ]
55
+ },
56
+ "bob": {
57
+ "password": "Bob@123!",
58
+ "policy_desc": "gpt-3.5-turbo/gpt-4, max_tokens <= 2000",
59
+ "tests": [
60
+ # (model, max_tokens, query, should_succeed)
61
+ ("gpt-3.5-turbo", 400, "What is revenue growth?", True), # ALLOWED
62
+ ("gpt-4", 400, "What is market cap?", True), # ALLOWED - Bob CAN use gpt-4
63
+ ("gpt-3.5-turbo", 600, "What is compound interest?", True), # ALLOWED - Bob's limit is 2000
64
+ ("gpt-4", 2500, "Deep financial analysis", False), # BLOCKED - exceeds max_tokens
65
+ ]
66
+ }
67
+ }
68
+
69
+
70
+ def test_user_path1(username: str, openai_service: SecureOpenAI):
71
+ """
72
+ PATH 1: Direct invoke_tool (explicit A2A control).
73
+
74
+ invoke_tool auto-creates authenticated prompts based on registry lookup.
75
+ """
76
+ print(f"\n{'='*60}")
77
+ print(f"PATH 1: {username.upper()} via invoke_tool")
78
+ print("="*60)
79
+
80
+ user_config = USER_TESTS[username]
81
+
82
+ # 1. Get JWT
83
+ print(f"1. Authenticating...")
84
+ jwt_token, _ = RemoteIdentityProvider().login(username, user_config["password"])
85
+ print(" OK Got JWT token")
86
+
87
+ # 2. Create user agent with JWT
88
+ print("2. Creating user agent...")
89
+ user = MACAWClient(
90
+ user_name=username,
91
+ iam_token=jwt_token, # Converted to embedded_context automatically!
92
+ agent_type="user",
93
+ app_name="financial-analyzer"
94
+ )
95
+
96
+ if not user.register():
97
+ print(" FAIL Failed to create user agent")
98
+ return None
99
+ print(f" OK User agent: {user.agent_id}")
100
+
101
+ # 3. Test with invoke_tool (auto-creates authenticated prompts!)
102
+ tests = user_config["tests"]
103
+
104
+ print(f"\n3. Testing via invoke_tool to service {openai_service.server_id}:")
105
+ print(" (invoke_tool will auto-create authenticated prompts for 'messages')")
106
+ print(f" Policy: {username} -> {user_config['policy_desc']}")
107
+
108
+ for model, max_tokens, query, should_succeed in tests:
109
+ expected = "SHOULD SUCCEED" if should_succeed else "SHOULD BE BLOCKED"
110
+ print(f"\n -> {model} with {max_tokens} tokens ({expected})")
111
+
112
+ try:
113
+ # A2A call to single service agent
114
+ # invoke_tool auto-creates authenticated prompts!
115
+ result = user.invoke_tool(
116
+ tool_name=f"tool:{openai_service.app_name}/generate",
117
+ parameters={
118
+ "model": model,
119
+ "max_tokens": max_tokens,
120
+ "messages": [
121
+ {"role": "system", "content": "You are a financial analyst."},
122
+ {"role": "user", "content": query}
123
+ ]
124
+ },
125
+ target_agent=openai_service.server_id
126
+ )
127
+
128
+ # Check if we got a valid response
129
+ if isinstance(result, dict):
130
+ if "error" in result:
131
+ if should_succeed:
132
+ print(f" FAIL UNEXPECTED FAILURE: {result['error']}")
133
+ else:
134
+ print(f" PASS CORRECTLY BLOCKED: {result['error'][:60]}...")
135
+ elif "choices" in result:
136
+ if should_succeed:
137
+ print(f" PASS SUCCESS (as expected)")
138
+ print(f" Model: {result.get('model')}")
139
+ content = result.get('choices', [{}])[0].get('message', {}).get('content', '')
140
+ print(f" Response: {content[:80]}...")
141
+ else:
142
+ print(f" FAIL UNEXPECTED SUCCESS - should have been blocked!")
143
+ else:
144
+ print(f" FAIL Unexpected response: {result}")
145
+ else:
146
+ print(f" FAIL Invalid response type: {type(result)}")
147
+
148
+ except Exception as e:
149
+ error = str(e)
150
+ is_policy_block = ("not in allowed" in error or "not permitted" in error or
151
+ "policy" in error.lower() or "max_tokens" in error or
152
+ "model" in error or "blocked" in error.lower())
153
+ if is_policy_block:
154
+ if should_succeed:
155
+ print(f" FAIL UNEXPECTED BLOCK: {error}")
156
+ else:
157
+ print(f" PASS CORRECTLY BLOCKED by policy")
158
+ else:
159
+ print(f" FAIL Error: {error}")
160
+
161
+ return user
162
+
163
+
164
+ def test_user_path2(username: str, openai_service: SecureOpenAI):
165
+ """
166
+ PATH 2: bind_to_user wrapper (OpenAI-compatible API).
167
+
168
+ Same underlying invoke_tool, but wrapped in OpenAI-compatible API.
169
+ """
170
+ print(f"\n{'='*60}")
171
+ print(f"PATH 2: {username.upper()} via bind_to_user")
172
+ print("="*60)
173
+
174
+ user_config = USER_TESTS[username]
175
+
176
+ # 1. Get JWT
177
+ print(f"1. Authenticating...")
178
+ jwt_token, _ = RemoteIdentityProvider().login(username, user_config["password"])
179
+ print(" OK Got JWT token")
180
+
181
+ # 2. Create user agent with JWT
182
+ print("2. Creating user agent...")
183
+ user = MACAWClient(
184
+ user_name=username,
185
+ iam_token=jwt_token,
186
+ agent_type="user",
187
+ app_name="financial-analyzer"
188
+ )
189
+
190
+ if not user.register():
191
+ print(" FAIL Failed to create user agent")
192
+ return
193
+ print(f" OK User agent: {user.agent_id}")
194
+
195
+ # 3. Bind user to service
196
+ print("3. Binding user to SecureOpenAI service...")
197
+ user_openai = openai_service.bind_to_user(user)
198
+ print(f" OK Bound to: {openai_service.server_id}")
199
+
200
+ # 4. Test with OpenAI-style API
201
+ tests = user_config["tests"]
202
+
203
+ print(f"\n4. Testing via bind_to_user wrapper:")
204
+ print(" (internally calls invoke_tool which auto-creates auth prompts)")
205
+ print(f" Policy: {username} -> {user_config['policy_desc']}")
206
+
207
+ for model, max_tokens, query, should_succeed in tests:
208
+ expected = "SHOULD SUCCEED" if should_succeed else "SHOULD BE BLOCKED"
209
+ print(f"\n -> {model} with {max_tokens} tokens ({expected})")
210
+
211
+ try:
212
+ response = user_openai.chat.completions.create(
213
+ model=model,
214
+ max_tokens=max_tokens,
215
+ messages=[
216
+ {"role": "system", "content": "You are a financial analyst."},
217
+ {"role": "user", "content": query}
218
+ ]
219
+ )
220
+
221
+ if should_succeed:
222
+ print(f" PASS SUCCESS (as expected)")
223
+ print(f" Model: {response.model}")
224
+ print(f" Response: {response.choices[0].message.content[:80]}...")
225
+ else:
226
+ print(f" FAIL UNEXPECTED SUCCESS - should have been blocked!")
227
+
228
+ except Exception as e:
229
+ error = str(e)
230
+ is_policy_block = ("not in allowed" in error or "not permitted" in error or
231
+ "policy" in error.lower() or "max_tokens" in error or
232
+ "model" in error or "blocked" in error.lower())
233
+ if is_policy_block:
234
+ if should_succeed:
235
+ print(f" FAIL UNEXPECTED BLOCK: {error}")
236
+ else:
237
+ print(f" PASS CORRECTLY BLOCKED by policy")
238
+ else:
239
+ print(f" FAIL Error: {error}")
240
+
241
+ # 5. Demonstrate unbind
242
+ print(f"\n5. Testing unbind()...")
243
+ print(f" is_bound before: {user_openai.is_bound}")
244
+ user_openai.unbind()
245
+ print(f" is_bound after: {user_openai.is_bound}")
246
+ try:
247
+ user_openai.chat.completions.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "test"}])
248
+ print(f" FAIL Call should have failed after unbind!")
249
+ except RuntimeError as e:
250
+ print(f" PASS Correctly rejected: {str(e)[:50]}...")
251
+
252
+
253
+ def main():
254
+ """Compare both invocation patterns with multi-user policy enforcement."""
255
+ print("\n" + "="*70)
256
+ print("Invocation Patterns Demo + MACAW Policy Enforcement")
257
+ print("="*70)
258
+ print("\nArchitecture:")
259
+ print(" - Single SecureOpenAI service agent (created once)")
260
+ print(" - User agents with JWT -> embedded_context (automatic)")
261
+ print(" - A2A calls: User -> SecureOpenAI service")
262
+ print(" - Policy enforcement at ToolAgent PEP level")
263
+ print(" - Authenticated prompts auto-created by invoke_tool")
264
+
265
+ print("\nTwo invocation patterns tested:")
266
+ print(" PATH 1: Direct invoke_tool (explicit A2A control)")
267
+ print(" PATH 2: bind_to_user wrapper (OpenAI-compatible API)")
268
+
269
+ print("\nExpected policies (enforced by ToolAgent PEP):")
270
+ print(" - Alice: GPT-3.5 only, max 500 tokens")
271
+ print(" - Bob: GPT-3.5/4, max 2000 tokens")
272
+
273
+ if not os.environ.get("OPENAI_API_KEY"):
274
+ print("\nWARNING: No OPENAI_API_KEY - demo will fail")
275
+ print(" Set with: export OPENAI_API_KEY=sk-...")
276
+ return
277
+
278
+ # Create SINGLE SecureOpenAI service (ONCE!)
279
+ print("\n" + "="*70)
280
+ print("Creating single SecureOpenAI service agent...")
281
+ print("="*70)
282
+
283
+ openai_service = SecureOpenAI(
284
+ api_key=os.environ.get("OPENAI_API_KEY"),
285
+ app_name="openai-service"
286
+ )
287
+
288
+ print(f"OK SecureOpenAI service: {openai_service.server_id}")
289
+
290
+ # Test Alice with PATH 1 (invoke_tool)
291
+ try:
292
+ test_user_path1("alice", openai_service)
293
+ except Exception as e:
294
+ print(f"\nFAIL Failed PATH 1 for alice: {e}")
295
+
296
+ # Test Alice with PATH 2 (bind_to_user)
297
+ try:
298
+ test_user_path2("alice", openai_service)
299
+ except Exception as e:
300
+ print(f"\nFAIL Failed PATH 2 for alice: {e}")
301
+
302
+ # Test Bob with PATH 1 (invoke_tool)
303
+ try:
304
+ test_user_path1("bob", openai_service)
305
+ except Exception as e:
306
+ print(f"\nFAIL Failed PATH 1 for bob: {e}")
307
+
308
+ # Test Bob with PATH 2 (bind_to_user)
309
+ try:
310
+ test_user_path2("bob", openai_service)
311
+ except Exception as e:
312
+ print(f"\nFAIL Failed PATH 2 for bob: {e}")
313
+
314
+ # Summary
315
+ print("\n" + "="*70)
316
+ print("Demo complete!")
317
+ print("="*70)
318
+
319
+
320
+ if __name__ == "__main__":
321
+ try:
322
+ main()
323
+ except Exception as e:
324
+ err = str(e)
325
+ print("\n" + "=" * 60)
326
+ if "OPENAI_API_KEY" in err or "api_key" in err.lower():
327
+ print("ERROR: OpenAI API key not configured")
328
+ print("Fix: export OPENAI_API_KEY=sk-...")
329
+ elif "Local provider does not support" in err:
330
+ print("ERROR: Identity Provider not configured")
331
+ print("Fix: Console -> Settings -> Identity Bridge")
332
+ print("\nOr run simpler example first:")
333
+ print(" python openai_1a_dropin_simple.py")
334
+ else:
335
+ print(f"ERROR: {e}")
336
+ print("=" * 60)
337
+ sys.exit(1)