stacklink-sdk 0.3.13__tar.gz → 0.3.15__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.15}/PKG-INFO +1 -1
  2. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/pyproject.toml +1 -1
  3. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/billing.py +42 -3
  4. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/bots.py +49 -1
  5. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/client.py +1 -1
  6. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/workbench.py +27 -0
  7. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15/stacklink_sdk.egg-info}/PKG-INFO +1 -1
  8. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/tests/test_client.py +31 -0
  9. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/LICENSE +0 -0
  10. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/README.md +0 -0
  11. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/setup.cfg +0 -0
  12. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/__init__.py +0 -0
  13. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/api_keys.py +0 -0
  14. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/approvals.py +0 -0
  15. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/artifacts.py +0 -0
  16. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/auth_configs.py +0 -0
  17. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/claude_agent.py +0 -0
  18. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/connected_accounts.py +0 -0
  19. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/errors.py +0 -0
  20. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/executions.py +0 -0
  21. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/knowledge.py +0 -0
  22. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/logs.py +0 -0
  23. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/mcp.py +0 -0
  24. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/memory.py +0 -0
  25. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/openai_agents.py +0 -0
  26. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/openai_memory.py +0 -0
  27. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/projects.py +0 -0
  28. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/providers.py +0 -0
  29. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/sessions.py +0 -0
  30. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/tools.py +0 -0
  31. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/triggers.py +0 -0
  32. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/types.py +0 -0
  33. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/webhooks.py +0 -0
  34. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink/x_chat.py +0 -0
  35. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink_sdk.egg-info/SOURCES.txt +0 -0
  36. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink_sdk.egg-info/dependency_links.txt +0 -0
  37. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink_sdk.egg-info/requires.txt +0 -0
  38. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/stacklink_sdk.egg-info/top_level.txt +0 -0
  39. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/tests/test_google_docs_provider.py +0 -0
  40. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/tests/test_google_drive_provider.py +0 -0
  41. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/tests/test_google_sheets_provider.py +0 -0
  42. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/tests/test_openai_memory.py +0 -0
  43. {stacklink_sdk-0.3.13 → stacklink_sdk-0.3.15}/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.15
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.15"
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"
@@ -1,6 +1,45 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any
3
+ from typing import Any, TypedDict, cast
4
+
5
+
6
+ class ResourceAllowanceAdjustment(TypedDict, total=False):
7
+ credited: int
8
+
9
+
10
+ class ResourceUsage(ResourceAllowanceAdjustment):
11
+ key: str
12
+ label: str
13
+ unit: str
14
+ included: int
15
+ used: int
16
+ reserved: int
17
+ remaining: int
18
+ additional: int
19
+ additionalCents: int
20
+ priceMicros: str
21
+ unitQuantity: int
22
+ rateVersion: str
23
+
24
+
25
+ class AdditionalSpending(TypedDict):
26
+ approvedCents: int
27
+ usedCents: int
28
+ reservedCents: int
29
+ availableCents: int
30
+ currency: str
31
+
32
+
33
+ class UsageSummary(TypedDict, total=False):
34
+ agreementVersion: str
35
+ resources: list[ResourceUsage]
36
+ spending: dict[str, AdditionalSpending]
37
+ workspace: dict[str, Any]
38
+ subscription: dict[str, Any]
39
+ plan: dict[str, Any]
40
+ period: dict[str, str]
41
+ usage: dict[str, Any]
42
+ overagePending: bool
4
43
 
5
44
 
6
45
  class BillingResource:
@@ -16,8 +55,8 @@ class BillingResource:
16
55
  def subscription(self):
17
56
  return self.client.request("GET", "/v1/subscription")
18
57
 
19
- def usage(self, **query):
20
- return self.client.request("GET", "/v1/usage", query=query)
58
+ def usage(self, **query) -> UsageSummary:
59
+ return cast(UsageSummary, self.client.request("GET", "/v1/usage", query=query))
21
60
 
22
61
  def usage_events(self, **query):
23
62
  return self.client.request("GET", "/v1/usage/events", query=query)
@@ -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.15"
41
41
  STACKLINK_PYTHON_USER_AGENT = f"stacklink-python/{STACKLINK_PYTHON_SDK_VERSION}"
42
42
 
43
43
 
@@ -68,6 +68,28 @@ class WorkbenchSession(TypedDict, total=False):
68
68
  updatedAt: str
69
69
 
70
70
 
71
+ class WorkbenchComputeState(TypedDict, total=False):
72
+ status: str
73
+ stopped: bool
74
+ fundedThrough: int | None
75
+ activeExecutionCount: int
76
+ generation: int | None
77
+
78
+
79
+ class WorkbenchRuntimeCapacity(TypedDict):
80
+ measurement: Literal["customer_active_ms", "legacy_operations"]
81
+ settlementStatus: Literal["pending", "acknowledgement_pending", "settled"]
82
+ reservedMilliseconds: int
83
+ workspace: dict[str, Any] | None
84
+ intervals: list[dict[str, Any]]
85
+
86
+
87
+ class WorkbenchComputeStatus(TypedDict):
88
+ workbenchSession: WorkbenchSession
89
+ compute: WorkbenchComputeState
90
+ billing: WorkbenchRuntimeCapacity
91
+
92
+
71
93
  class WorkbenchSessionUseResponse(TypedDict):
72
94
  workbenchSession: WorkbenchSession
73
95
  reused: bool
@@ -1084,6 +1106,11 @@ class WorkbenchResource:
1084
1106
  return _wait_for_workbench_response(self.client, body, response)
1085
1107
  return response
1086
1108
 
1109
+ def compute_status(self, session_id: str, workbench_session_id: str) -> WorkbenchComputeStatus:
1110
+ return cast(WorkbenchComputeStatus, self.client.request("POST", "/v1/workbench/run", json_body={
1111
+ "sessionId": session_id, "workbenchSessionId": workbench_session_id, "operation": "compute_status",
1112
+ }))
1113
+
1087
1114
  def destroy(self, session_id: str, workbench_session_id: str):
1088
1115
  return self.run({
1089
1116
  "sessionId": session_id,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stacklink-sdk
3
- Version: 0.3.13
3
+ Version: 0.3.15
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