@superblocksteam/sdk-api 2.0.105 → 2.0.106-next.0

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 (181) hide show
  1. package/README.md +439 -89
  2. package/dist/api/definition.d.ts +11 -6
  3. package/dist/api/definition.d.ts.map +1 -1
  4. package/dist/api/definition.js +19 -12
  5. package/dist/api/definition.js.map +1 -1
  6. package/dist/api/definition.test.js +39 -15
  7. package/dist/api/definition.test.js.map +1 -1
  8. package/dist/errors.d.ts +1 -1
  9. package/dist/errors.js +1 -1
  10. package/dist/index.d.ts +10 -11
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +9 -5
  13. package/dist/index.js.map +1 -1
  14. package/dist/integrations/base/index.d.ts +2 -1
  15. package/dist/integrations/base/index.d.ts.map +1 -1
  16. package/dist/integrations/base/index.js +1 -0
  17. package/dist/integrations/base/index.js.map +1 -1
  18. package/dist/integrations/base/rest-api-client-base.d.ts +48 -0
  19. package/dist/integrations/base/rest-api-client-base.d.ts.map +1 -0
  20. package/dist/integrations/base/rest-api-client-base.js +98 -0
  21. package/dist/integrations/base/rest-api-client-base.js.map +1 -0
  22. package/dist/integrations/base/rest-api-integration-client.d.ts +10 -20
  23. package/dist/integrations/base/rest-api-integration-client.d.ts.map +1 -1
  24. package/dist/integrations/base/rest-api-integration-client.js +10 -65
  25. package/dist/integrations/base/rest-api-integration-client.js.map +1 -1
  26. package/dist/integrations/box/types.d.ts +1 -1
  27. package/dist/integrations/declarations.d.ts +5 -73
  28. package/dist/integrations/declarations.d.ts.map +1 -1
  29. package/dist/integrations/declarations.js +5 -68
  30. package/dist/integrations/declarations.js.map +1 -1
  31. package/dist/integrations/documentation.test.js +0 -2
  32. package/dist/integrations/documentation.test.js.map +1 -1
  33. package/dist/integrations/googledrive/types.d.ts +1 -1
  34. package/dist/integrations/index.d.ts +1 -11
  35. package/dist/integrations/index.d.ts.map +1 -1
  36. package/dist/integrations/index.js +1 -7
  37. package/dist/integrations/index.js.map +1 -1
  38. package/dist/integrations/registry.d.ts +1 -11
  39. package/dist/integrations/registry.d.ts.map +1 -1
  40. package/dist/integrations/registry.js +0 -29
  41. package/dist/integrations/registry.js.map +1 -1
  42. package/dist/integrations/slack/client.d.ts +13 -9
  43. package/dist/integrations/slack/client.d.ts.map +1 -1
  44. package/dist/integrations/slack/client.js +60 -8
  45. package/dist/integrations/slack/client.js.map +1 -1
  46. package/dist/integrations/slack/client.test.d.ts +11 -0
  47. package/dist/integrations/slack/client.test.d.ts.map +1 -0
  48. package/dist/integrations/slack/client.test.js +368 -0
  49. package/dist/integrations/slack/client.test.js.map +1 -0
  50. package/dist/integrations/slack/index.d.ts +2 -1
  51. package/dist/integrations/slack/index.d.ts.map +1 -1
  52. package/dist/integrations/slack/index.js +1 -0
  53. package/dist/integrations/slack/index.js.map +1 -1
  54. package/dist/integrations/slack/types.d.ts +127 -28
  55. package/dist/integrations/slack/types.d.ts.map +1 -1
  56. package/dist/integrations/slack/types.js +27 -1
  57. package/dist/integrations/slack/types.js.map +1 -1
  58. package/dist/integrations/snowflake/client.d.ts +2 -2
  59. package/dist/integrations/snowflake/client.js +2 -2
  60. package/dist/runtime/context.d.ts +1 -1
  61. package/dist/runtime/executor.d.ts +2 -2
  62. package/dist/types.d.ts +15 -6
  63. package/dist/types.d.ts.map +1 -1
  64. package/package.json +2 -2
  65. package/src/api/definition.test.ts +40 -15
  66. package/src/api/definition.ts +19 -12
  67. package/src/errors.ts +1 -1
  68. package/src/index.ts +13 -33
  69. package/src/integrations/asana/README.md +12 -12
  70. package/src/integrations/base/index.ts +2 -1
  71. package/src/integrations/base/rest-api-client-base.ts +134 -0
  72. package/src/integrations/base/rest-api-integration-client.ts +12 -89
  73. package/src/integrations/bitbucket/README.md +19 -19
  74. package/src/integrations/box/README.md +24 -24
  75. package/src/integrations/box/types.ts +1 -1
  76. package/src/integrations/circleci/README.md +18 -18
  77. package/src/integrations/declarations.ts +5 -105
  78. package/src/integrations/documentation.test.ts +0 -2
  79. package/src/integrations/googledrive/README.md +25 -22
  80. package/src/integrations/googledrive/types.ts +1 -1
  81. package/src/integrations/graphql/README.md +2 -2
  82. package/src/integrations/groq/README.md +8 -8
  83. package/src/integrations/index.ts +0 -51
  84. package/src/integrations/perplexity/README.md +39 -48
  85. package/src/integrations/registry.ts +1 -39
  86. package/src/integrations/salesforce/README.md +11 -9
  87. package/src/integrations/slack/README.md +62 -19
  88. package/src/integrations/slack/client.test.ts +553 -0
  89. package/src/integrations/slack/client.ts +92 -12
  90. package/src/integrations/slack/index.ts +6 -1
  91. package/src/integrations/slack/types.ts +142 -29
  92. package/src/integrations/snowflake/client.ts +2 -2
  93. package/src/integrations/zoom/README.md +15 -15
  94. package/src/runtime/context.ts +1 -1
  95. package/src/runtime/executor.ts +2 -2
  96. package/src/types.ts +15 -6
  97. package/dist/integrations/couchbase/client.d.ts +0 -36
  98. package/dist/integrations/couchbase/client.d.ts.map +0 -1
  99. package/dist/integrations/couchbase/client.js +0 -148
  100. package/dist/integrations/couchbase/client.js.map +0 -1
  101. package/dist/integrations/couchbase/index.d.ts +0 -8
  102. package/dist/integrations/couchbase/index.d.ts.map +0 -1
  103. package/dist/integrations/couchbase/index.js +0 -7
  104. package/dist/integrations/couchbase/index.js.map +0 -1
  105. package/dist/integrations/couchbase/types.d.ts +0 -100
  106. package/dist/integrations/couchbase/types.d.ts.map +0 -1
  107. package/dist/integrations/couchbase/types.js +0 -5
  108. package/dist/integrations/couchbase/types.js.map +0 -1
  109. package/dist/integrations/kafka/client.d.ts +0 -25
  110. package/dist/integrations/kafka/client.d.ts.map +0 -1
  111. package/dist/integrations/kafka/client.js +0 -124
  112. package/dist/integrations/kafka/client.js.map +0 -1
  113. package/dist/integrations/kafka/index.d.ts +0 -8
  114. package/dist/integrations/kafka/index.d.ts.map +0 -1
  115. package/dist/integrations/kafka/index.js +0 -7
  116. package/dist/integrations/kafka/index.js.map +0 -1
  117. package/dist/integrations/kafka/types.d.ts +0 -113
  118. package/dist/integrations/kafka/types.d.ts.map +0 -1
  119. package/dist/integrations/kafka/types.js +0 -5
  120. package/dist/integrations/kafka/types.js.map +0 -1
  121. package/dist/integrations/kinesis/client.d.ts +0 -31
  122. package/dist/integrations/kinesis/client.d.ts.map +0 -1
  123. package/dist/integrations/kinesis/client.js +0 -101
  124. package/dist/integrations/kinesis/client.js.map +0 -1
  125. package/dist/integrations/kinesis/index.d.ts +0 -8
  126. package/dist/integrations/kinesis/index.d.ts.map +0 -1
  127. package/dist/integrations/kinesis/index.js +0 -7
  128. package/dist/integrations/kinesis/index.js.map +0 -1
  129. package/dist/integrations/kinesis/types.d.ts +0 -97
  130. package/dist/integrations/kinesis/types.d.ts.map +0 -1
  131. package/dist/integrations/kinesis/types.js +0 -7
  132. package/dist/integrations/kinesis/types.js.map +0 -1
  133. package/dist/integrations/python/client.d.ts +0 -42
  134. package/dist/integrations/python/client.d.ts.map +0 -1
  135. package/dist/integrations/python/client.js +0 -89
  136. package/dist/integrations/python/client.js.map +0 -1
  137. package/dist/integrations/python/client.test.d.ts +0 -5
  138. package/dist/integrations/python/client.test.d.ts.map +0 -1
  139. package/dist/integrations/python/client.test.js +0 -214
  140. package/dist/integrations/python/client.test.js.map +0 -1
  141. package/dist/integrations/python/index.d.ts +0 -6
  142. package/dist/integrations/python/index.d.ts.map +0 -1
  143. package/dist/integrations/python/index.js +0 -5
  144. package/dist/integrations/python/index.js.map +0 -1
  145. package/dist/integrations/python/types.d.ts +0 -85
  146. package/dist/integrations/python/types.d.ts.map +0 -1
  147. package/dist/integrations/python/types.js +0 -5
  148. package/dist/integrations/python/types.js.map +0 -1
  149. package/dist/integrations/redis/client.d.ts +0 -43
  150. package/dist/integrations/redis/client.d.ts.map +0 -1
  151. package/dist/integrations/redis/client.js +0 -142
  152. package/dist/integrations/redis/client.js.map +0 -1
  153. package/dist/integrations/redis/index.d.ts +0 -8
  154. package/dist/integrations/redis/index.d.ts.map +0 -1
  155. package/dist/integrations/redis/index.js +0 -7
  156. package/dist/integrations/redis/index.js.map +0 -1
  157. package/dist/integrations/redis/types.d.ts +0 -137
  158. package/dist/integrations/redis/types.d.ts.map +0 -1
  159. package/dist/integrations/redis/types.js +0 -5
  160. package/dist/integrations/redis/types.js.map +0 -1
  161. package/src/integrations/couchbase/README.md +0 -138
  162. package/src/integrations/couchbase/client.ts +0 -225
  163. package/src/integrations/couchbase/index.ts +0 -8
  164. package/src/integrations/couchbase/types.ts +0 -126
  165. package/src/integrations/kafka/README.md +0 -144
  166. package/src/integrations/kafka/client.ts +0 -216
  167. package/src/integrations/kafka/index.ts +0 -14
  168. package/src/integrations/kafka/types.ts +0 -128
  169. package/src/integrations/kinesis/README.md +0 -153
  170. package/src/integrations/kinesis/client.ts +0 -146
  171. package/src/integrations/kinesis/index.ts +0 -14
  172. package/src/integrations/kinesis/types.ts +0 -114
  173. package/src/integrations/python/README.md +0 -566
  174. package/src/integrations/python/client.test.ts +0 -341
  175. package/src/integrations/python/client.ts +0 -136
  176. package/src/integrations/python/index.ts +0 -6
  177. package/src/integrations/python/types.ts +0 -92
  178. package/src/integrations/redis/README.md +0 -200
  179. package/src/integrations/redis/client.ts +0 -208
  180. package/src/integrations/redis/index.ts +0 -8
  181. package/src/integrations/redis/types.ts +0 -167
@@ -1,341 +0,0 @@
1
- /**
2
- * Tests for Python client binding injection.
3
- */
4
-
5
- import { describe, it, expect, vi } from "vitest";
6
- import { z } from "zod";
7
-
8
- import type { QueryExecutor } from "../registry.js";
9
- import type { IntegrationConfig } from "../types.js";
10
- import { PythonClientImpl } from "./client.js";
11
-
12
- /** Helper to compute expected base64-encoded json.loads preamble. */
13
- function expectedBindingsLine(bindings: Record<string, unknown>): string {
14
- const b64 = Buffer.from(JSON.stringify(bindings)).toString("base64");
15
- return `_bindings = json.loads(base64.b64decode('${b64}').decode())`;
16
- }
17
-
18
- describe("PythonClientImpl", () => {
19
- const createMockConfig = (): IntegrationConfig => ({
20
- id: "test-python-id",
21
- name: "Test Python",
22
- pluginId: "python",
23
- configuration: {},
24
- });
25
-
26
- const createMockExecutor = (mockResult: unknown): QueryExecutor => {
27
- return vi.fn().mockResolvedValue(mockResult);
28
- };
29
-
30
- describe("binding injection", () => {
31
- it("generates code without bindings when none provided", async () => {
32
- const mockExecutor = vi.fn().mockResolvedValue({ result: 42 });
33
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
34
-
35
- await client.run(
36
- 'return {"result": 42}',
37
- z.object({ result: z.number() }),
38
- );
39
-
40
- expect(mockExecutor).toHaveBeenCalledWith(
41
- { body: 'return {"result": 42}' },
42
- undefined,
43
- undefined,
44
- );
45
- });
46
-
47
- it("injects string bindings as Python variables", async () => {
48
- const mockExecutor = vi
49
- .fn()
50
- .mockResolvedValue({ message: "Hello World" });
51
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
52
-
53
- const bindings = { greeting: "Hello World" };
54
- await client.run(
55
- 'return {"message": greeting}',
56
- z.object({ message: z.string() }),
57
- bindings,
58
- );
59
-
60
- const request = mockExecutor.mock.calls[0][0];
61
- expect(request.body).toContain("import json, base64");
62
- expect(request.body).toContain(expectedBindingsLine(bindings));
63
- expect(request.body).toContain("greeting = _bindings['greeting']");
64
- expect(request.body).toContain('return {"message": greeting}');
65
- });
66
-
67
- it("injects number bindings as Python variables", async () => {
68
- const mockExecutor = vi.fn().mockResolvedValue({ result: 14 });
69
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
70
-
71
- const bindings = { number: 10 };
72
- await client.run(
73
- 'return {"result": number + 4}',
74
- z.object({ result: z.number() }),
75
- bindings,
76
- );
77
-
78
- const request = mockExecutor.mock.calls[0][0];
79
- expect(request.body).toContain("import json, base64");
80
- expect(request.body).toContain(expectedBindingsLine(bindings));
81
- expect(request.body).toContain("number = _bindings['number']");
82
- expect(request.body).toContain('return {"result": number + 4}');
83
- });
84
-
85
- it("injects boolean bindings as Python variables", async () => {
86
- const mockExecutor = vi.fn().mockResolvedValue({ active: true });
87
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
88
-
89
- const bindings = { isActive: true };
90
- await client.run(
91
- 'return {"active": isActive}',
92
- z.object({ active: z.boolean() }),
93
- bindings,
94
- );
95
-
96
- const request = mockExecutor.mock.calls[0][0];
97
- expect(request.body).toContain("import json, base64");
98
- expect(request.body).toContain(expectedBindingsLine(bindings));
99
- expect(request.body).toContain("isActive = _bindings['isActive']");
100
- expect(request.body).toContain('return {"active": isActive}');
101
- });
102
-
103
- it("injects null bindings as Python variables", async () => {
104
- const mockExecutor = vi.fn().mockResolvedValue({ value: null });
105
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
106
-
107
- const bindings = { nullValue: null };
108
- await client.run(
109
- 'return {"value": nullValue}',
110
- z.object({ value: z.null() }),
111
- bindings,
112
- );
113
-
114
- const request = mockExecutor.mock.calls[0][0];
115
- expect(request.body).toContain("import json, base64");
116
- expect(request.body).toContain(expectedBindingsLine(bindings));
117
- expect(request.body).toContain("nullValue = _bindings['nullValue']");
118
- expect(request.body).toContain('return {"value": nullValue}');
119
- });
120
-
121
- it("injects array bindings as Python variables", async () => {
122
- const mockExecutor = vi.fn().mockResolvedValue({ count: 3 });
123
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
124
-
125
- const bindings = { items: [1, 2, 3] };
126
- await client.run(
127
- 'return {"count": len(items)}',
128
- z.object({ count: z.number() }),
129
- bindings,
130
- );
131
-
132
- const request = mockExecutor.mock.calls[0][0];
133
- expect(request.body).toContain("import json, base64");
134
- expect(request.body).toContain(expectedBindingsLine(bindings));
135
- expect(request.body).toContain("items = _bindings['items']");
136
- expect(request.body).toContain('return {"count": len(items)}');
137
- });
138
-
139
- it("injects object bindings as Python variables", async () => {
140
- const mockExecutor = vi.fn().mockResolvedValue({ name: "John" });
141
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
142
-
143
- const bindings = { user: { name: "John", age: 30 } };
144
- await client.run(
145
- 'return {"name": user["name"]}',
146
- z.object({ name: z.string() }),
147
- bindings,
148
- );
149
-
150
- const request = mockExecutor.mock.calls[0][0];
151
- expect(request.body).toContain("import json, base64");
152
- expect(request.body).toContain(expectedBindingsLine(bindings));
153
- expect(request.body).toContain("user = _bindings['user']");
154
- expect(request.body).toContain('return {"name": user["name"]}');
155
- });
156
-
157
- it("injects multiple bindings as Python variables", async () => {
158
- const mockExecutor = vi
159
- .fn()
160
- .mockResolvedValue({ message: "Alice is 25 years old" });
161
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
162
-
163
- const bindings = { userName: "Alice", userAge: 25 };
164
- await client.run(
165
- 'return {"message": f"{userName} is {userAge} years old"}',
166
- z.object({ message: z.string() }),
167
- bindings,
168
- );
169
-
170
- const request = mockExecutor.mock.calls[0][0];
171
- expect(request.body).toContain("import json, base64");
172
- expect(request.body).toContain(expectedBindingsLine(bindings));
173
- expect(request.body).toContain("userName = _bindings['userName']");
174
- expect(request.body).toContain("userAge = _bindings['userAge']");
175
- expect(request.body).toContain(
176
- 'return {"message": f"{userName} is {userAge} years old"}',
177
- );
178
- });
179
-
180
- it("handles strings with special characters", async () => {
181
- const mockExecutor = vi
182
- .fn()
183
- .mockResolvedValue({ text: 'It\'s a "test"' });
184
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
185
-
186
- const bindings = { text: 'It\'s a "test"' };
187
- await client.run(
188
- 'return {"text": text}',
189
- z.object({ text: z.string() }),
190
- bindings,
191
- );
192
-
193
- const request = mockExecutor.mock.calls[0][0];
194
- expect(request.body).toContain("import json, base64");
195
- // Base64 encoding avoids all string escaping issues
196
- expect(request.body).toContain(expectedBindingsLine(bindings));
197
- expect(request.body).toContain("text = _bindings['text']");
198
- expect(request.body).toContain('return {"text": text}');
199
- });
200
-
201
- it("handles strings with newlines and tabs", async () => {
202
- const mockExecutor = vi
203
- .fn()
204
- .mockResolvedValue({ text: "line1\nline2\ttab" });
205
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
206
-
207
- const bindings = { text: "line1\nline2\ttab" };
208
- await client.run(
209
- 'return {"text": text}',
210
- z.object({ text: z.string() }),
211
- bindings,
212
- );
213
-
214
- const request = mockExecutor.mock.calls[0][0];
215
- // Base64 encoding handles newlines/tabs safely
216
- expect(request.body).toContain(expectedBindingsLine(bindings));
217
- expect(request.body).toContain("text = _bindings['text']");
218
- });
219
-
220
- it("handles strings with backslashes", async () => {
221
- const mockExecutor = vi
222
- .fn()
223
- .mockResolvedValue({ path: "C:\\Users\\test" });
224
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
225
-
226
- const bindings = { path: "C:\\Users\\test" };
227
- await client.run(
228
- 'return {"path": path}',
229
- z.object({ path: z.string() }),
230
- bindings,
231
- );
232
-
233
- const request = mockExecutor.mock.calls[0][0];
234
- // Base64 encoding handles backslashes safely
235
- expect(request.body).toContain(expectedBindingsLine(bindings));
236
- expect(request.body).toContain("path = _bindings['path']");
237
- });
238
-
239
- it("preserves code structure with blank line between preamble and user code", async () => {
240
- const mockExecutor = vi.fn().mockResolvedValue({ result: 42 });
241
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
242
-
243
- const bindings = { number: 21 };
244
- await client.run(
245
- 'result = number * 2\nreturn {"result": result}',
246
- z.object({ result: z.number() }),
247
- bindings,
248
- );
249
-
250
- const request = mockExecutor.mock.calls[0][0];
251
- const lines = (request.body as string).split("\n");
252
-
253
- // Should have: import, _bindings assignment, variable extraction, blank line, then user code
254
- expect(lines[0]).toBe("import json, base64");
255
- expect(lines[1]).toBe(expectedBindingsLine(bindings));
256
- expect(lines[2]).toBe("number = _bindings['number']");
257
- expect(lines[3]).toBe("");
258
- expect(lines[4]).toBe("result = number * 2");
259
- });
260
- });
261
-
262
- describe("validation", () => {
263
- it("validates result against schema", async () => {
264
- const mockExecutor = createMockExecutor({ result: 42 });
265
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
266
-
267
- const result = await client.run(
268
- 'return {"result": 42}',
269
- z.object({ result: z.number() }),
270
- );
271
-
272
- expect(result).toEqual({ result: 42 });
273
- });
274
-
275
- it("throws CodeExecutionError when validation fails", async () => {
276
- const mockExecutor = createMockExecutor({ result: "not a number" });
277
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
278
-
279
- await expect(
280
- client.run(
281
- 'return {"result": "not a number"}',
282
- z.object({ result: z.number() }),
283
- ),
284
- ).rejects.toThrow("Python execution result validation failed");
285
- });
286
-
287
- it("throws CodeExecutionError when execution fails", async () => {
288
- const mockExecutor = vi
289
- .fn()
290
- .mockRejectedValue(new Error("Execution error"));
291
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
292
-
293
- await expect(
294
- client.run('return {"result": 42}', z.object({ result: z.number() })),
295
- ).rejects.toThrow("Python execution failed");
296
- });
297
- });
298
-
299
- describe("complex bindings", () => {
300
- it("handles nested objects", async () => {
301
- const mockExecutor = vi.fn().mockResolvedValue({ name: "John Doe" });
302
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
303
-
304
- const bindings = {
305
- person: { first: "John", last: "Doe", address: { city: "NYC" } },
306
- };
307
- await client.run(
308
- "return {\"name\": f\"{person['first']} {person['last']}\"}",
309
- z.object({ name: z.string() }),
310
- bindings,
311
- );
312
-
313
- const request = mockExecutor.mock.calls[0][0];
314
- expect(request.body).toContain("import json, base64");
315
- expect(request.body).toContain(expectedBindingsLine(bindings));
316
- expect(request.body).toContain("person = _bindings['person']");
317
- });
318
-
319
- it("handles arrays of objects", async () => {
320
- const mockExecutor = vi.fn().mockResolvedValue({ count: 2 });
321
- const client = new PythonClientImpl(createMockConfig(), mockExecutor);
322
-
323
- const bindings = {
324
- users: [
325
- { id: 1, name: "Alice" },
326
- { id: 2, name: "Bob" },
327
- ],
328
- };
329
- await client.run(
330
- 'return {"count": len(users)}',
331
- z.object({ count: z.number() }),
332
- bindings,
333
- );
334
-
335
- const request = mockExecutor.mock.calls[0][0];
336
- expect(request.body).toContain("import json, base64");
337
- expect(request.body).toContain(expectedBindingsLine(bindings));
338
- expect(request.body).toContain("users = _bindings['users']");
339
- });
340
- });
341
- });
@@ -1,136 +0,0 @@
1
- /**
2
- * Python client implementation.
3
- *
4
- * Executes Python code via the orchestrator with type-safe bindings.
5
- */
6
-
7
- import type { PartialMessage } from "@bufbuild/protobuf";
8
- import type { z } from "zod";
9
-
10
- import type { Plugin as PythonPlugin } from "@superblocksteam/types/dist/src/plugins/python/v1/plugin_pb";
11
-
12
- import { CodeExecutionError } from "../../errors.js";
13
- import type { QueryExecutor, TraceMetadata } from "../registry.js";
14
- import type { IntegrationConfig, IntegrationClientImpl } from "../types.js";
15
- import type { PythonClient } from "./types.js";
16
-
17
- /**
18
- * Python plugin request type derived from proto definition.
19
- * Using PartialMessage allows optional fields.
20
- */
21
- export type PythonRequest = PartialMessage<PythonPlugin>;
22
-
23
- /**
24
- * Internal implementation of PythonClient.
25
- *
26
- * This implementation communicates with the orchestrator to execute Python code.
27
- * The orchestrator handles {{}} binding resolution and script execution.
28
- */
29
- export class PythonClientImpl implements PythonClient, IntegrationClientImpl {
30
- readonly name: string;
31
- readonly pluginId: string;
32
- readonly config: IntegrationConfig;
33
-
34
- private readonly executeQuery: QueryExecutor;
35
-
36
- constructor(config: IntegrationConfig, executeQuery: QueryExecutor) {
37
- this.name = config.name;
38
- this.pluginId = config.pluginId;
39
- this.config = config;
40
- this.executeQuery = executeQuery;
41
- }
42
-
43
- /**
44
- * Build Python plugin request with injected bindings.
45
- *
46
- * Bindings are injected as Python variables in a preamble before the user code.
47
- * This allows the user code to reference binding keys directly as Python variables.
48
- *
49
- * @param code - User's Python code string
50
- * @param bindings - Bindings to inject as Python variables
51
- * @returns Request object matching the proto schema with bindings injected
52
- */
53
- private buildRequest(
54
- code: string,
55
- bindings?: Record<string, unknown>,
56
- ): PythonRequest {
57
- // If no bindings, just return the code as-is
58
- if (!bindings || Object.keys(bindings).length === 0) {
59
- return {
60
- body: code,
61
- };
62
- }
63
-
64
- // Inject bindings as Python variables using base64 to avoid string escaping issues.
65
- // Format: import json,base64\n_bindings = json.loads(base64.b64decode('...').decode())\nvar1 = _bindings['var1']\n...\n\n<user code>
66
- const bindingLines: string[] = ["import json, base64"];
67
-
68
- // Base64-encode the JSON to avoid issues with special characters (\n, \t, \\, quotes)
69
- // that JSON.stringify produces and Python's string parser would interpret
70
- const bindingsB64 = Buffer.from(JSON.stringify(bindings)).toString(
71
- "base64",
72
- );
73
- bindingLines.push(
74
- `_bindings = json.loads(base64.b64decode('${bindingsB64}').decode())`,
75
- );
76
-
77
- // Extract each binding as a local variable
78
- for (const key of Object.keys(bindings)) {
79
- bindingLines.push(`${key} = _bindings['${key}']`);
80
- }
81
-
82
- // Combine binding preamble with user code
83
- const fullCode = `${bindingLines.join("\n")}\n\n${code}`;
84
-
85
- return {
86
- body: fullCode,
87
- };
88
- }
89
-
90
- async run<T>(
91
- code: string,
92
- schema: z.ZodSchema<T>,
93
- bindings?: Record<string, unknown>,
94
- metadata?: TraceMetadata,
95
- ): Promise<T> {
96
- // Build request with bindings injected as Python variables
97
- const request = this.buildRequest(code, bindings);
98
-
99
- try {
100
- // Execute the Python code with injected bindings
101
- // No need to pass bindings as input since they're already in the code
102
- const result = await this.executeQuery(
103
- request as unknown as Record<string, unknown>,
104
- undefined,
105
- metadata,
106
- );
107
-
108
- // Validate result against schema
109
- const parseResult = schema.safeParse(result);
110
-
111
- if (!parseResult.success) {
112
- throw new CodeExecutionError(
113
- `Python execution result validation failed: ${parseResult.error.message}`,
114
- {
115
- code,
116
- bindings,
117
- result,
118
- zodError: parseResult.error,
119
- },
120
- );
121
- }
122
-
123
- return parseResult.data;
124
- } catch (error) {
125
- if (error instanceof CodeExecutionError) {
126
- throw error;
127
- }
128
-
129
- throw new CodeExecutionError("Python execution failed", {
130
- code,
131
- bindings,
132
- originalError: error,
133
- });
134
- }
135
- }
136
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * Python code execution client.
3
- */
4
-
5
- export type { PythonClient } from "./types.js";
6
- export { PythonClientImpl } from "./client.js";
@@ -1,92 +0,0 @@
1
- /**
2
- * Python code execution client types.
3
- */
4
-
5
- import type { z } from "zod";
6
-
7
- import type { BaseIntegrationClient } from "../../types.js";
8
- import type { TraceMetadata } from "../registry.js";
9
-
10
- /**
11
- * Python code execution client.
12
- *
13
- * Executes Python scripts with type-safe inputs and outputs.
14
- * Bindings are passed as a map and referenced in code using {{key}} syntax.
15
- * The orchestrator resolves bindings before execution.
16
- *
17
- * @example
18
- * ```typescript
19
- * // Declare in api(): integrations: { runtime: python(INTEGRATION_ID) }
20
- * // In run(), access via: ctx.integrations.runtime
21
- * const result = await ctx.integrations.runtime.run(
22
- * `
23
- * import time
24
- *
25
- * user_id = {{userId}}
26
- * current_time = int(time.time())
27
- *
28
- * return {
29
- * "greeting": f"Hello user {user_id}!",
30
- * "timestamp": current_time
31
- * }
32
- * `,
33
- * z.object({
34
- * greeting: z.string(),
35
- * timestamp: z.number(),
36
- * }),
37
- * {
38
- * userId,
39
- * }
40
- * );
41
- * ```
42
- */
43
- export interface PythonClient extends BaseIntegrationClient {
44
- /**
45
- * Execute Python code with validated output.
46
- *
47
- * The Python code must return a value using a `return` statement.
48
- * Bindings are referenced using {{key}} syntax and resolved by the
49
- * orchestrator before execution.
50
- *
51
- * **Important:** The parameter order is `(code, schema, bindings?, metadata?)`.
52
- * Because `bindings` is `Record<string, unknown>`, it is structurally
53
- * compatible with `TraceMetadata`. Take care not to pass a metadata object
54
- * as the third argument -- if you only need metadata and no bindings, pass
55
- * `undefined` for `bindings` explicitly:
56
- *
57
- * ```typescript
58
- * // Correct: skip bindings, pass metadata
59
- * await python.run(code, schema, undefined, { label: "my label" });
60
- *
61
- * // Wrong: metadata is accidentally treated as bindings
62
- * await python.run(code, schema, { label: "my label" });
63
- * ```
64
- *
65
- * @param code - Python script to execute (use {{key}} for bindings)
66
- * @param schema - Zod schema for validating the return value
67
- * @param bindings - Optional key/value pairs accessible via {{key}} in code.
68
- * Do not pass `TraceMetadata` here; use the `metadata` parameter instead.
69
- * @param metadata - Optional trace metadata for diagnostics labeling
70
- * @returns Validated result matching the schema
71
- *
72
- * @throws {CodeExecutionError} If execution fails or validation fails
73
- *
74
- * @example
75
- * ```typescript
76
- * const result = await python.run(
77
- * `
78
- * user_id = {{userId}}
79
- * return {"message": f"Hello {user_id}"}
80
- * `,
81
- * z.object({ message: z.string() }),
82
- * { userId: "123" }
83
- * );
84
- * ```
85
- */
86
- run<T>(
87
- code: string,
88
- schema: z.ZodSchema<T>,
89
- bindings?: Record<string, unknown>,
90
- metadata?: TraceMetadata,
91
- ): Promise<T>;
92
- }