cred-autogen 1.0.0__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.
@@ -0,0 +1,50 @@
1
+ node_modules/
2
+ dist/
3
+ build/
4
+
5
+ # Sprint artifacts
6
+ .factory/
7
+ .sprint-*
8
+ .sprint-pid
9
+ .rewire-done
10
+ .content-rewrite-done
11
+ .hubspot-done
12
+ .integrations-done
13
+ .done
14
+ .validation-report.md
15
+ .sprint-logs/
16
+ .sprint-prompts/
17
+ SESSION_PACKAGE.md
18
+ run-sprint.sh
19
+ validate.sh
20
+
21
+ # Internal planning docs (not for public repo)
22
+ CLAUDE.md
23
+ PLAN.md
24
+ HANDOFF.md
25
+ VALIDATION.md
26
+ MARKETING-VALIDATION.md
27
+ FINAL-SUBMISSION-VALIDATION.md
28
+ .next/
29
+ out/
30
+ coverage/
31
+ *.tsbuildinfo
32
+ .turbo/
33
+ .DS_Store
34
+ *.pem
35
+ .env
36
+ .env.local
37
+ .env*.local
38
+ !.env.example
39
+
40
+ # Python
41
+ __pycache__/
42
+ *.py[cod]
43
+ *.pyo
44
+ *.egg-info/
45
+ .pytest_cache/
46
+ .venv/
47
+ venv/
48
+ dist/
49
+ *.dist-info/
50
+ packages/sdk-python/.venv/
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: cred-autogen
3
+ Version: 1.0.0
4
+ Summary: Cred AutoGen integration — credential delegation FunctionTool for Microsoft AutoGen agents
5
+ Author-email: Cred <hello@cred.ninja>
6
+ License: Apache-2.0
7
+ Keywords: ai-agents,autogen,cred,delegation,microsoft,oauth
8
+ Requires-Python: >=3.9
9
+ Requires-Dist: autogen-core>=0.4
10
+ Requires-Dist: cred-auth>=0.1.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: hatchling; extra == 'dev'
13
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
14
+ Requires-Dist: pytest>=7.0; extra == 'dev'
15
+ Description-Content-Type: text/markdown
16
+
17
+ # cred-autogen
18
+
19
+ Microsoft AutoGen integration for Cred. OAuth2 credential delegation for AI agents.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pip install cred-autogen
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ```python
30
+ import os
31
+ from cred_autogen import cred_delegate_tool
32
+
33
+ tool = cred_delegate_tool(
34
+ agent_token=os.environ["CRED_AGENT_TOKEN"],
35
+ user_id="user_123",
36
+ app_client_id="my_app_client_id",
37
+ )
38
+
39
+ # Register with an AutoGen AssistantAgent
40
+ from autogen_agentchat.agents import AssistantAgent
41
+
42
+ agent = AssistantAgent(
43
+ name="assistant",
44
+ tools=[tool],
45
+ )
46
+ ```
47
+
48
+ ## Tool Schema
49
+
50
+ The `cred_delegate` tool accepts:
51
+
52
+ | Parameter | Type | Description |
53
+ |-----------|------|-------------|
54
+ | `service` | `string` | Service slug (e.g. `google`, `github`) |
55
+ | `scopes` | `string[]` | OAuth scopes to request |
56
+
57
+ `user_id` and `app_client_id` are pre-configured at factory time, not agent-controlled.
58
+
59
+ ## Handling Consent
60
+
61
+ When the user hasn't connected the service, the tool raises `ConsentRequiredError`.
62
+ The error's `consent_url` attribute contains the URL to redirect the user.
63
+
64
+ ```python
65
+ from cred import ConsentRequiredError
66
+
67
+ try:
68
+ result = await tool.run_json(
69
+ {"service": "google", "scopes": ["calendar.readonly"]},
70
+ cancellation_token,
71
+ )
72
+ except ConsentRequiredError as e:
73
+ print(f"Redirect user to: {e.consent_url}")
74
+ ```
75
+
76
+ ## Cred Cloud (Coming Soon)
77
+
78
+ Managed cloud delegation is coming. [Join the waitlist](https://cred.ninja/waitlist).
@@ -0,0 +1,62 @@
1
+ # cred-autogen
2
+
3
+ Microsoft AutoGen integration for Cred. OAuth2 credential delegation for AI agents.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install cred-autogen
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ import os
15
+ from cred_autogen import cred_delegate_tool
16
+
17
+ tool = cred_delegate_tool(
18
+ agent_token=os.environ["CRED_AGENT_TOKEN"],
19
+ user_id="user_123",
20
+ app_client_id="my_app_client_id",
21
+ )
22
+
23
+ # Register with an AutoGen AssistantAgent
24
+ from autogen_agentchat.agents import AssistantAgent
25
+
26
+ agent = AssistantAgent(
27
+ name="assistant",
28
+ tools=[tool],
29
+ )
30
+ ```
31
+
32
+ ## Tool Schema
33
+
34
+ The `cred_delegate` tool accepts:
35
+
36
+ | Parameter | Type | Description |
37
+ |-----------|------|-------------|
38
+ | `service` | `string` | Service slug (e.g. `google`, `github`) |
39
+ | `scopes` | `string[]` | OAuth scopes to request |
40
+
41
+ `user_id` and `app_client_id` are pre-configured at factory time, not agent-controlled.
42
+
43
+ ## Handling Consent
44
+
45
+ When the user hasn't connected the service, the tool raises `ConsentRequiredError`.
46
+ The error's `consent_url` attribute contains the URL to redirect the user.
47
+
48
+ ```python
49
+ from cred import ConsentRequiredError
50
+
51
+ try:
52
+ result = await tool.run_json(
53
+ {"service": "google", "scopes": ["calendar.readonly"]},
54
+ cancellation_token,
55
+ )
56
+ except ConsentRequiredError as e:
57
+ print(f"Redirect user to: {e.consent_url}")
58
+ ```
59
+
60
+ ## Cred Cloud (Coming Soon)
61
+
62
+ Managed cloud delegation is coming. [Join the waitlist](https://cred.ninja/waitlist).
@@ -0,0 +1,7 @@
1
+ """Cred AutoGen integration."""
2
+
3
+ from .tool import cred_delegate_tool
4
+
5
+ CredTool = cred_delegate_tool
6
+
7
+ __all__ = ["cred_delegate_tool", "CredTool"]
@@ -0,0 +1,94 @@
1
+ """Cred AutoGen integration.
2
+
3
+ Returns a FunctionTool that AutoGen agents can call to get delegated OAuth tokens.
4
+ The tool schema matches the Cred MCP tool spec:
5
+
6
+ name: cred_delegate
7
+ params: { service: str, scopes: list[str] }
8
+
9
+ user_id and app_client_id are pre-configured at factory time and are not
10
+ agent-controlled inputs.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ from typing import Annotated, Optional
17
+
18
+ from autogen_core.tools import FunctionTool
19
+
20
+ from cred import Cred
21
+
22
+
23
+ def cred_delegate_tool(
24
+ agent_token: str,
25
+ user_id: str,
26
+ app_client_id: str,
27
+ base_url: str = "https://api.cred.ninja",
28
+ ) -> FunctionTool:
29
+ """Create an AutoGen FunctionTool for credential delegation.
30
+
31
+ The returned tool is pre-configured with the user's identity and app context.
32
+ At runtime, the agent supplies ``service`` and ``scopes``.
33
+
34
+ Args:
35
+ agent_token: Agent token issued by Cred (starts with ``cred_at_``).
36
+ user_id: The user whose credentials to delegate.
37
+ app_client_id: The Cred app client ID to delegate for.
38
+ base_url: Override the API base URL.
39
+
40
+ Returns:
41
+ A ``FunctionTool`` ready to use with AutoGen agents.
42
+
43
+ Raises:
44
+ ConsentRequiredError: (from the tool at runtime) when the user hasn't
45
+ connected the requested service. The error message includes
46
+ ``consent_url`` so the caller can redirect the user.
47
+
48
+ Example::
49
+
50
+ from cred_autogen import cred_delegate_tool
51
+
52
+ tool = cred_delegate_tool(
53
+ agent_token=os.environ["CRED_AGENT_TOKEN"],
54
+ user_id="user_123",
55
+ app_client_id="my_app",
56
+ )
57
+ # Register with an AutoGen agent
58
+ """
59
+ cred = Cred(agent_token=agent_token, base_url=base_url)
60
+
61
+ def delegate(
62
+ service: Annotated[str, "Service slug to get a token for (e.g. 'google', 'github', 'google-calendar')."],
63
+ scopes: Annotated[list[str], "OAuth scopes to request (e.g. ['calendar.readonly']). Pass an empty list to use all consented scopes."],
64
+ ) -> str:
65
+ """Get a delegated OAuth access token for a third-party service on behalf of the current user.
66
+
67
+ Returns JSON with access_token, token_type, expires_in, service, scopes,
68
+ and delegation_id. Raises an error with consent_url if the user hasn't
69
+ connected the service.
70
+ """
71
+ resolved_scopes: Optional[list[str]] = scopes if scopes else None
72
+
73
+ result = cred.delegate(
74
+ service=service,
75
+ user_id=user_id,
76
+ app_client_id=app_client_id,
77
+ scopes=resolved_scopes,
78
+ )
79
+
80
+ return json.dumps({
81
+ "access_token": result.access_token,
82
+ "token_type": result.token_type,
83
+ "expires_in": result.expires_in,
84
+ "service": result.service,
85
+ "scopes": result.scopes,
86
+ "delegation_id": result.delegation_id,
87
+ })
88
+
89
+ return FunctionTool(delegate, name="cred_delegate", description=(
90
+ "Get a delegated OAuth access token for a third-party service "
91
+ "on behalf of the current user. "
92
+ "Returns access_token, token_type, expires_in, service, and scopes. "
93
+ "Raises an error with consent_url if the user hasn't connected the service."
94
+ ))
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "cred-autogen"
7
+ version = "1.0.0"
8
+ description = "Cred AutoGen integration — credential delegation FunctionTool for Microsoft AutoGen agents"
9
+ readme = "README.md"
10
+ license = { text = "Apache-2.0" }
11
+ requires-python = ">=3.9"
12
+ authors = [{ name = "Cred", email = "hello@cred.ninja" }]
13
+ keywords = ["cred", "autogen", "microsoft", "oauth", "delegation", "ai-agents"]
14
+ dependencies = [
15
+ "cred-auth>=0.1.0",
16
+ "autogen-core>=0.4",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ dev = ["pytest>=7.0", "pytest-asyncio>=0.21", "hatchling"]
21
+
22
+ [tool.hatch.build.targets.wheel]
23
+ packages = ["cred_autogen"]
24
+
25
+ [tool.pytest.ini_options]
26
+ testpaths = ["tests"]
File without changes
@@ -0,0 +1,206 @@
1
+ """Cred AutoGen integration tests.
2
+
3
+ Tests focus on: FunctionTool shape, invocation, output JSON,
4
+ and error propagation. The Cred client is mocked -- no real HTTP.
5
+ """
6
+
7
+ import asyncio
8
+ import json
9
+ from datetime import datetime, timezone
10
+ from typing import Optional
11
+ import pytest
12
+ import httpx
13
+ from unittest.mock import MagicMock, patch
14
+
15
+ from autogen_core.tools import FunctionTool
16
+ from autogen_core import CancellationToken
17
+
18
+ from cred import DelegationResult, ConsentRequiredError, CredError
19
+ from cred_autogen import cred_delegate_tool, CredTool
20
+
21
+ TOKEN = "cred_at_test"
22
+ USER_ID = "user_123"
23
+ APP_CLIENT_ID = "app_1"
24
+
25
+
26
+ def make_delegation_result(
27
+ *,
28
+ access_token: str = "at",
29
+ token_type: str = "Bearer",
30
+ expires_in: int = 3600,
31
+ service: str = "google",
32
+ scopes: Optional[list[str]] = None,
33
+ delegation_id: str = "del_1",
34
+ ) -> DelegationResult:
35
+ return DelegationResult(
36
+ access_token=access_token,
37
+ token_type=token_type,
38
+ expires_in=expires_in,
39
+ expires_at=datetime(2026, 3, 1, tzinfo=timezone.utc),
40
+ service=service,
41
+ scopes=scopes or [],
42
+ delegation_id=delegation_id,
43
+ )
44
+
45
+
46
+ @pytest.fixture
47
+ def mock_cred():
48
+ return MagicMock()
49
+
50
+
51
+ @pytest.fixture
52
+ def tool(mock_cred):
53
+ with patch("cred_autogen.tool.Cred", return_value=mock_cred):
54
+ return cred_delegate_tool(
55
+ agent_token=TOKEN,
56
+ user_id=USER_ID,
57
+ app_client_id=APP_CLIENT_ID,
58
+ )
59
+
60
+
61
+ def run(coro):
62
+ """Run a coroutine synchronously (test helper)."""
63
+ return asyncio.run(coro)
64
+
65
+
66
+ # -- factory function ---------------------------------------------------------
67
+
68
+ class TestFactory:
69
+ def test_returns_function_tool(self):
70
+ with patch("cred_autogen.tool.Cred"):
71
+ t = cred_delegate_tool(
72
+ agent_token=TOKEN,
73
+ user_id=USER_ID,
74
+ app_client_id=APP_CLIENT_ID,
75
+ )
76
+ assert isinstance(t, FunctionTool)
77
+
78
+ def test_tool_name_is_cred_delegate(self, tool):
79
+ assert tool.name == "cred_delegate"
80
+
81
+ def test_tool_description_mentions_oauth(self, tool):
82
+ assert "access token" in tool.description.lower()
83
+ assert "service" in tool.description.lower()
84
+
85
+ def test_creates_cred_client_with_token_and_base_url(self):
86
+ with patch("cred_autogen.tool.Cred") as mock_cred_class:
87
+ cred_delegate_tool(
88
+ agent_token=TOKEN,
89
+ user_id=USER_ID,
90
+ app_client_id=APP_CLIENT_ID,
91
+ base_url="http://localhost:3001",
92
+ )
93
+ mock_cred_class.assert_called_once_with(
94
+ agent_token=TOKEN,
95
+ base_url="http://localhost:3001",
96
+ )
97
+
98
+ def test_exports_cred_tool_alias(self):
99
+ assert CredTool is cred_delegate_tool
100
+
101
+
102
+ # -- invocation ---------------------------------------------------------------
103
+
104
+ class TestInvoke:
105
+ def test_returns_json_with_access_token(self, tool, mock_cred):
106
+ mock_cred.delegate.return_value = make_delegation_result(
107
+ access_token="ya29.mock",
108
+ expires_in=3600,
109
+ service="google",
110
+ scopes=["calendar.readonly"],
111
+ delegation_id="del_abc",
112
+ )
113
+
114
+ result_str = run(tool.run_json(
115
+ {"service": "google", "scopes": ["calendar.readonly"]},
116
+ CancellationToken(),
117
+ ))
118
+ result = json.loads(result_str)
119
+
120
+ assert result["access_token"] == "ya29.mock"
121
+ assert result["token_type"] == "Bearer"
122
+ assert result["expires_in"] == 3600
123
+ assert result["service"] == "google"
124
+ assert result["scopes"] == ["calendar.readonly"]
125
+ assert result["delegation_id"] == "del_abc"
126
+
127
+ def test_passes_service_user_app_to_cred(self, tool, mock_cred):
128
+ mock_cred.delegate.return_value = make_delegation_result(service="github")
129
+
130
+ run(tool.run_json(
131
+ {"service": "github", "scopes": ["repo"]},
132
+ CancellationToken(),
133
+ ))
134
+
135
+ mock_cred.delegate.assert_called_once_with(
136
+ service="github",
137
+ user_id=USER_ID,
138
+ app_client_id=APP_CLIENT_ID,
139
+ scopes=["repo"],
140
+ )
141
+
142
+ def test_passes_none_scopes_when_empty_list(self, tool, mock_cred):
143
+ mock_cred.delegate.return_value = make_delegation_result()
144
+
145
+ run(tool.run_json(
146
+ {"service": "google", "scopes": []},
147
+ CancellationToken(),
148
+ ))
149
+
150
+ call_kwargs = mock_cred.delegate.call_args.kwargs
151
+ assert call_kwargs["scopes"] is None
152
+
153
+ def test_propagates_consent_required_error(self, tool, mock_cred):
154
+ mock_cred.delegate.side_effect = ConsentRequiredError(
155
+ "User has not consented",
156
+ "https://api.cred.ninja/api/connect/google/authorize?app_client_id=app_1",
157
+ )
158
+
159
+ with pytest.raises(ConsentRequiredError) as exc_info:
160
+ run(tool.run_json(
161
+ {"service": "google", "scopes": ["calendar.readonly"]},
162
+ CancellationToken(),
163
+ ))
164
+
165
+ assert "/api/connect/google/authorize" in exc_info.value.consent_url
166
+
167
+ def test_propagates_cred_error_on_401(self, tool, mock_cred):
168
+ mock_cred.delegate.side_effect = CredError(
169
+ "Invalid agent token", "unauthorized", 401
170
+ )
171
+
172
+ with pytest.raises(CredError) as exc_info:
173
+ run(tool.run_json(
174
+ {"service": "google", "scopes": []},
175
+ CancellationToken(),
176
+ ))
177
+
178
+ assert exc_info.value.status_code == 401
179
+
180
+ def test_real_cred_client_delegates_against_mock_server(self):
181
+ tool = cred_delegate_tool(
182
+ agent_token=TOKEN,
183
+ user_id=USER_ID,
184
+ app_client_id=APP_CLIENT_ID,
185
+ base_url="https://cred.example.com",
186
+ )
187
+
188
+ with patch("httpx.Client.request", return_value=httpx.Response(
189
+ 200,
190
+ json={
191
+ "access_token": "ya29.real",
192
+ "token_type": "Bearer",
193
+ "expires_in": 3600,
194
+ "service": "google",
195
+ "scopes": ["calendar.readonly"],
196
+ "delegation_id": "del_real",
197
+ },
198
+ )) as request_mock:
199
+ result_str = run(tool.run_json(
200
+ {"service": "google", "scopes": ["calendar.readonly"]},
201
+ CancellationToken(),
202
+ ))
203
+
204
+ payload = json.loads(result_str)
205
+ assert payload["access_token"] == "ya29.real"
206
+ assert request_mock.called