stacklink-sdk 0.3.13__tar.gz → 0.3.14__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 (43) hide show
  1. {stacklink_sdk-0.3.13/stacklink_sdk.egg-info → stacklink_sdk-0.3.14}/PKG-INFO +1 -1
  2. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/pyproject.toml +1 -1
  3. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/bots.py +49 -1
  4. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/client.py +1 -1
  5. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14/stacklink_sdk.egg-info}/PKG-INFO +1 -1
  6. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/tests/test_client.py +31 -0
  7. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/LICENSE +0 -0
  8. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/README.md +0 -0
  9. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/setup.cfg +0 -0
  10. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/__init__.py +0 -0
  11. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/api_keys.py +0 -0
  12. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/approvals.py +0 -0
  13. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/artifacts.py +0 -0
  14. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/auth_configs.py +0 -0
  15. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/billing.py +0 -0
  16. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/claude_agent.py +0 -0
  17. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/connected_accounts.py +0 -0
  18. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/errors.py +0 -0
  19. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/executions.py +0 -0
  20. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/knowledge.py +0 -0
  21. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/logs.py +0 -0
  22. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/mcp.py +0 -0
  23. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/memory.py +0 -0
  24. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/openai_agents.py +0 -0
  25. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/openai_memory.py +0 -0
  26. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/projects.py +0 -0
  27. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/providers.py +0 -0
  28. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/sessions.py +0 -0
  29. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/tools.py +0 -0
  30. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/triggers.py +0 -0
  31. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/types.py +0 -0
  32. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/webhooks.py +0 -0
  33. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/workbench.py +0 -0
  34. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink/x_chat.py +0 -0
  35. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink_sdk.egg-info/SOURCES.txt +0 -0
  36. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink_sdk.egg-info/dependency_links.txt +0 -0
  37. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink_sdk.egg-info/requires.txt +0 -0
  38. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/stacklink_sdk.egg-info/top_level.txt +0 -0
  39. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/tests/test_google_docs_provider.py +0 -0
  40. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/tests/test_google_drive_provider.py +0 -0
  41. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/tests/test_google_sheets_provider.py +0 -0
  42. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/tests/test_openai_memory.py +0 -0
  43. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.14}/tests/test_zoho_books_provider.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stacklink-sdk
3
- Version: 0.3.13
3
+ Version: 0.3.14
4
4
  Summary: Python SDK for Stacklink sessions, tools, MCP, auth, workbench, logs, knowledge, and billing.
5
5
  Author: Stacklink
6
6
  License-Expression: LicenseRef-Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "stacklink-sdk"
7
- version = "0.3.13"
7
+ version = "0.3.14"
8
8
  description = "Python SDK for Stacklink sessions, tools, MCP, auth, workbench, logs, knowledge, and billing."
9
9
  readme = "README.md"
10
10
  license = "LicenseRef-Proprietary"
@@ -54,7 +54,52 @@ class BotRequirement(TypedDict):
54
54
  nextAction: Literal["configure_bot", "repair_connection", "use_live_data", "contact_administrator", "unavailable"]
55
55
 
56
56
 
57
+ class BotAttachment(TypedDict, total=False):
58
+ id: str
59
+ name: str
60
+ mediaType: str
61
+ sizeBytes: int
62
+ sha256: str | None
63
+ status: Literal["uploading", "uploaded", "preparing", "ready", "failed", "gone"]
64
+ facts: dict[str, Any]
65
+ warnings: list[dict[str, str]]
66
+ error: dict[str, str] | None
67
+
68
+
69
+ class BotFileUpload(TypedDict):
70
+ fileId: str
71
+ uploadUrl: str
72
+ requiredHeaders: dict[str, str]
73
+ expiresAt: str
74
+
75
+
76
+ class BotFiles:
77
+ """Sync and async clients share this contract; upload bytes with the returned headers."""
78
+ def __init__(self, client):
79
+ self.client = client
80
+
81
+ def _path(self, bot_id: str, file_id: str | None = None):
82
+ return "/v1/bots/" + quote(bot_id, safe="") + "/files" + ("/" + quote(file_id, safe="") if file_id else "")
83
+
84
+ def create_upload(self, bot_id: str, *, session_id: str, name: str, media_type: str, size_bytes: int):
85
+ return self.client.request("POST", self._path(bot_id), json_body={"sessionId": session_id, "name": name, "mediaType": media_type, "sizeBytes": size_bytes})
86
+
87
+ def finalize(self, bot_id: str, file_id: str, *, session_id: str):
88
+ return self.client.request("POST", self._path(bot_id, file_id) + "/finalize", json_body={"sessionId": session_id})
89
+
90
+ def get(self, bot_id: str, file_id: str, *, session_id: str):
91
+ return self.client.request("GET", self._path(bot_id, file_id), query={"sessionId": session_id})
92
+
93
+ def link(self, bot_id: str, file_id: str, *, session_id: str):
94
+ return self.client.request("POST", self._path(bot_id, file_id) + "/link", json_body={"sessionId": session_id})
95
+
96
+
57
97
  class BotRun(TypedDict, total=False):
98
+ attachments: list[BotAttachment]
99
+ citations: list[dict[str, Any]]
100
+ outputs: list[dict[str, Any]]
101
+ coverage: list[dict[str, Any]]
102
+ updates: list[dict[str, str]]
58
103
  id: str
59
104
  botId: str
60
105
  conversationId: str
@@ -115,6 +160,7 @@ def verify_bot_api_request(*, method: str, url: str, body: str, headers: dict[st
115
160
  class BotsResource:
116
161
  def __init__(self, client, *, async_mode: bool = False):
117
162
  self.client = client
163
+ self.files = BotFiles(client)
118
164
  self.async_mode = async_mode
119
165
 
120
166
  def _path(self, bot_id: str, run_id: str | None = None) -> str:
@@ -187,8 +233,10 @@ class BotsResource:
187
233
  def publish(self, bot_id: str, revision: int, channels: dict[str, bool]):
188
234
  return self.client.request("POST", self._path(bot_id) + "/publish", json_body={"revision": revision, "channels": channels})
189
235
 
190
- def run(self, bot_id: str, *, session_id: str, message: str, variables: dict[str, Any] | None = None, conversation_id: str | None = None, run_id: str | None = None, connection_return_url: str | None = None):
236
+ def run(self, bot_id: str, *, session_id: str, message: str, variables: dict[str, Any] | None = None, conversation_id: str | None = None, run_id: str | None = None, connection_return_url: str | None = None, attachments: list[str] | None = None):
191
237
  body: dict[str, Any] = {"sessionId": session_id, "message": message, "variables": variables or {}, "runId": run_id or str(uuid4())}
238
+ if attachments is not None:
239
+ body["attachments"] = attachments
192
240
  if conversation_id:
193
241
  body["conversationId"] = conversation_id
194
242
  if connection_return_url is not None:
@@ -37,7 +37,7 @@ from .types import Json, Transport
37
37
  from .webhooks import WebhooksResource
38
38
  from .workbench import WorkbenchResource
39
39
 
40
- STACKLINK_PYTHON_SDK_VERSION = "0.3.13"
40
+ STACKLINK_PYTHON_SDK_VERSION = "0.3.14"
41
41
  STACKLINK_PYTHON_USER_AGENT = f"stacklink-python/{STACKLINK_PYTHON_SDK_VERSION}"
42
42
 
43
43
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stacklink-sdk
3
- Version: 0.3.13
3
+ Version: 0.3.14
4
4
  Summary: Python SDK for Stacklink sessions, tools, MCP, auth, workbench, logs, knowledge, and billing.
5
5
  Author: Stacklink
6
6
  License-Expression: LicenseRef-Proprietary
@@ -159,6 +159,37 @@ class StacklinkClientTests(unittest.TestCase):
159
159
  ("POST", "/v1/bots/bot-one/apis/api-one/signing-key", {"action": "activate", "keyId": "key-two"}),
160
160
  ])
161
161
 
162
+ def test_bot_files_preserve_customer_identity_for_sync_and_async_clients(self):
163
+ expected = [
164
+ ("POST", "/v1/bots/bot-one/files", {"sessionId": "customer-a", "name": "notes.sql", "mediaType": "text/plain", "sizeBytes": 42}, {}),
165
+ ("POST", "/v1/bots/bot-one/files/file-one/finalize", {"sessionId": "customer-a"}, {}),
166
+ ("GET", "/v1/bots/bot-one/files/file-one", None, {"sessionId": "customer-a"}),
167
+ ("POST", "/v1/bots/bot-one/files/file-one/link", {"sessionId": "customer-a"}, {}),
168
+ ]
169
+ sync_calls = []
170
+ def transport(method, path, headers, body, query, timeout):
171
+ sync_calls.append((method, path, body, query))
172
+ return 200, {}, {"ok": True}
173
+ client = Stacklink(api_key="test", base_url="https://stacklink.test", transport=transport)
174
+ client.bots.files.create_upload("bot-one", session_id="customer-a", name="notes.sql", media_type="text/plain", size_bytes=42)
175
+ client.bots.files.finalize("bot-one", "file-one", session_id="customer-a")
176
+ client.bots.files.get("bot-one", "file-one", session_id="customer-a")
177
+ client.bots.files.link("bot-one", "file-one", session_id="customer-a")
178
+ self.assertEqual(sync_calls, expected)
179
+ async_calls = []
180
+ async def handler(request):
181
+ async_calls.append((request.method, request.url.path, json.loads(request.content) if request.content else None, dict(request.url.params)))
182
+ return httpx.Response(200, json={"ok": True})
183
+ async def run():
184
+ async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as http:
185
+ client = AsyncStacklink(api_key="test", base_url="https://stacklink.test", http_client=http)
186
+ await client.bots.files.create_upload("bot-one", session_id="customer-a", name="notes.sql", media_type="text/plain", size_bytes=42)
187
+ await client.bots.files.finalize("bot-one", "file-one", session_id="customer-a")
188
+ await client.bots.files.get("bot-one", "file-one", session_id="customer-a")
189
+ await client.bots.files.link("bot-one", "file-one", session_id="customer-a")
190
+ asyncio.run(run())
191
+ self.assertEqual(async_calls, expected)
192
+
162
193
  def test_callback_signature_uses_original_body_and_expiry(self):
163
194
  import base64, hashlib, hmac
164
195
  from stacklink import verify_webhook_signature
File without changes
File without changes
File without changes