@squadbase/vite-server 0.1.12-dev.8860f37 → 0.1.12-dev.a9ac647

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.
@@ -1,869 +0,0 @@
1
- // ../connectors/src/parameter-definition.ts
2
- var ParameterDefinition = class {
3
- slug;
4
- name;
5
- description;
6
- envVarBaseKey;
7
- type;
8
- secret;
9
- required;
10
- constructor(config) {
11
- this.slug = config.slug;
12
- this.name = config.name;
13
- this.description = config.description;
14
- this.envVarBaseKey = config.envVarBaseKey;
15
- this.type = config.type;
16
- this.secret = config.secret;
17
- this.required = config.required;
18
- }
19
- /**
20
- * Get the parameter value from a ConnectorConnectionObject.
21
- */
22
- getValue(connection2) {
23
- const param = connection2.parameters.find(
24
- (p) => p.parameterSlug === this.slug
25
- );
26
- if (!param || param.value == null) {
27
- throw new Error(
28
- `Parameter "${this.slug}" not found or has no value in connection "${connection2.id}"`
29
- );
30
- }
31
- return param.value;
32
- }
33
- /**
34
- * Try to get the parameter value. Returns undefined if not found (for optional params).
35
- */
36
- tryGetValue(connection2) {
37
- const param = connection2.parameters.find(
38
- (p) => p.parameterSlug === this.slug
39
- );
40
- if (!param || param.value == null) return void 0;
41
- return param.value;
42
- }
43
- };
44
-
45
- // ../connectors/src/connectors/powerbi/parameters.ts
46
- var parameters = {
47
- tenantId: new ParameterDefinition({
48
- slug: "tenant-id",
49
- name: "Azure Tenant ID",
50
- description: "The Microsoft Entra ID (Azure AD) tenant ID that owns the Service Principal. Found in Azure Portal > Microsoft Entra ID > Overview > Tenant ID.",
51
- envVarBaseKey: "POWERBI_TENANT_ID",
52
- type: "text",
53
- secret: false,
54
- required: true
55
- }),
56
- clientId: new ParameterDefinition({
57
- slug: "client-id",
58
- name: "Application (Client) ID",
59
- description: "The Application (client) ID of the Microsoft Entra app registration used as the Power BI Service Principal. Found in Azure Portal > App registrations > Overview.",
60
- envVarBaseKey: "POWERBI_CLIENT_ID",
61
- type: "text",
62
- secret: false,
63
- required: true
64
- }),
65
- clientSecret: new ParameterDefinition({
66
- slug: "client-secret",
67
- name: "Client Secret",
68
- description: "A client secret value for the app registration. Generated under App registrations > Certificates & secrets > Client secrets. The Service Principal must be granted Power BI tenant access (via 'Allow service principals to use Power BI APIs' admin setting) and added to each workspace as a Member/Admin.",
69
- envVarBaseKey: "POWERBI_CLIENT_SECRET",
70
- type: "text",
71
- secret: true,
72
- required: true
73
- })
74
- };
75
-
76
- // ../connectors/src/connectors/powerbi/sdk/index.ts
77
- var BASE_URL = "https://api.powerbi.com/v1.0/myorg";
78
- var TOKEN_SCOPE = "https://analysis.windows.net/powerbi/api/.default";
79
- function createClient(params) {
80
- const tenantId = params[parameters.tenantId.slug];
81
- const clientId = params[parameters.clientId.slug];
82
- const clientSecret = params[parameters.clientSecret.slug];
83
- if (!tenantId || !clientId || !clientSecret) {
84
- throw new Error(
85
- `powerbi: missing required parameters: ${parameters.tenantId.slug}, ${parameters.clientId.slug}, ${parameters.clientSecret.slug}`
86
- );
87
- }
88
- let cachedToken = null;
89
- async function getAccessToken2() {
90
- if (cachedToken && cachedToken.expiresAt > Date.now() + 6e4) {
91
- return cachedToken.token;
92
- }
93
- const tokenUrl = `https://login.microsoftonline.com/${encodeURIComponent(tenantId)}/oauth2/v2.0/token`;
94
- const body = new URLSearchParams({
95
- grant_type: "client_credentials",
96
- client_id: clientId,
97
- client_secret: clientSecret,
98
- scope: TOKEN_SCOPE
99
- });
100
- const res = await fetch(tokenUrl, {
101
- method: "POST",
102
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
103
- body: body.toString()
104
- });
105
- if (!res.ok) {
106
- const errorText = await res.text().catch(() => res.statusText);
107
- throw new Error(
108
- `powerbi: token request failed (${res.status}): ${errorText}`
109
- );
110
- }
111
- const data = await res.json();
112
- cachedToken = {
113
- token: data.access_token,
114
- expiresAt: Date.now() + data.expires_in * 1e3
115
- };
116
- return data.access_token;
117
- }
118
- async function request(path2, init) {
119
- const token = await getAccessToken2();
120
- const headers = new Headers(init?.headers);
121
- headers.set("Authorization", `Bearer ${token}`);
122
- if (!headers.has("Content-Type") && init?.body) {
123
- headers.set("Content-Type", "application/json");
124
- }
125
- if (!headers.has("Accept")) {
126
- headers.set("Accept", "application/json");
127
- }
128
- const url = `${BASE_URL}${path2.startsWith("/") ? "" : "/"}${path2}`;
129
- return fetch(url, { ...init, headers });
130
- }
131
- async function getJson(path2) {
132
- const res = await request(path2);
133
- if (!res.ok) {
134
- const body = await res.text();
135
- throw new Error(`powerbi: GET ${path2} failed (${res.status}): ${body}`);
136
- }
137
- return await res.json();
138
- }
139
- return {
140
- request,
141
- async listGroups(options) {
142
- const qs = new URLSearchParams();
143
- if (options?.top != null) qs.set("$top", String(options.top));
144
- if (options?.skip != null) qs.set("$skip", String(options.skip));
145
- if (options?.filter) qs.set("$filter", options.filter);
146
- const query = qs.toString();
147
- return getJson(
148
- `/groups${query ? `?${query}` : ""}`
149
- );
150
- },
151
- async listDatasets(groupId) {
152
- return getJson(
153
- `/groups/${encodeURIComponent(groupId)}/datasets`
154
- );
155
- },
156
- async listReports(groupId) {
157
- return getJson(
158
- `/groups/${encodeURIComponent(groupId)}/reports`
159
- );
160
- },
161
- async getDataset(groupId, datasetId) {
162
- return getJson(
163
- `/groups/${encodeURIComponent(groupId)}/datasets/${encodeURIComponent(datasetId)}`
164
- );
165
- },
166
- async executeQueries(groupId, datasetId, queries, options) {
167
- const res = await request(
168
- `/groups/${encodeURIComponent(groupId)}/datasets/${encodeURIComponent(datasetId)}/executeQueries`,
169
- {
170
- method: "POST",
171
- body: JSON.stringify({
172
- queries: queries.map((q) => ({ query: q })),
173
- serializerSettings: { includeNulls: options?.includeNulls ?? true },
174
- ...options?.impersonatedUserName ? {
175
- impersonatedUserName: options.impersonatedUserName
176
- } : {}
177
- })
178
- }
179
- );
180
- if (!res.ok) {
181
- const body = await res.text();
182
- throw new Error(
183
- `powerbi: executeQueries failed (${res.status}): ${body}`
184
- );
185
- }
186
- return await res.json();
187
- },
188
- async refreshDataset(groupId, datasetId, options) {
189
- const res = await request(
190
- `/groups/${encodeURIComponent(groupId)}/datasets/${encodeURIComponent(datasetId)}/refreshes`,
191
- {
192
- method: "POST",
193
- body: JSON.stringify({
194
- notifyOption: options?.notifyOption ?? "NoNotification"
195
- })
196
- }
197
- );
198
- if (!res.ok) {
199
- const body = await res.text();
200
- throw new Error(
201
- `powerbi: refreshDataset failed (${res.status}): ${body}`
202
- );
203
- }
204
- }
205
- };
206
- }
207
-
208
- // ../connectors/src/connector-onboarding.ts
209
- var ConnectorOnboarding = class {
210
- /** Phase 1: Connection setup instructions (optional — some connectors don't need this) */
211
- connectionSetupInstructions;
212
- /** Phase 2: Data overview instructions */
213
- dataOverviewInstructions;
214
- constructor(config) {
215
- this.connectionSetupInstructions = config.connectionSetupInstructions;
216
- this.dataOverviewInstructions = config.dataOverviewInstructions;
217
- }
218
- getConnectionSetupPrompt(language) {
219
- return this.connectionSetupInstructions?.[language] ?? null;
220
- }
221
- getDataOverviewInstructions(language) {
222
- return this.dataOverviewInstructions[language];
223
- }
224
- };
225
-
226
- // ../connectors/src/connector-tool.ts
227
- var ConnectorTool = class {
228
- name;
229
- description;
230
- inputSchema;
231
- outputSchema;
232
- _execute;
233
- constructor(config) {
234
- this.name = config.name;
235
- this.description = config.description;
236
- this.inputSchema = config.inputSchema;
237
- this.outputSchema = config.outputSchema;
238
- this._execute = config.execute;
239
- }
240
- createTool(connections, config) {
241
- return {
242
- description: this.description,
243
- inputSchema: this.inputSchema,
244
- outputSchema: this.outputSchema,
245
- execute: (input) => this._execute(input, connections, config)
246
- };
247
- }
248
- };
249
-
250
- // ../connectors/src/connector-plugin.ts
251
- var ConnectorPlugin = class _ConnectorPlugin {
252
- slug;
253
- authType;
254
- name;
255
- description;
256
- iconUrl;
257
- parameters;
258
- releaseFlag;
259
- proxyPolicy;
260
- experimentalAttributes;
261
- categories;
262
- onboarding;
263
- systemPrompt;
264
- tools;
265
- query;
266
- checkConnection;
267
- constructor(config) {
268
- this.slug = config.slug;
269
- this.authType = config.authType;
270
- this.name = config.name;
271
- this.description = config.description;
272
- this.iconUrl = config.iconUrl;
273
- this.parameters = config.parameters;
274
- this.releaseFlag = config.releaseFlag;
275
- this.proxyPolicy = config.proxyPolicy;
276
- this.experimentalAttributes = config.experimentalAttributes;
277
- this.categories = config.categories ?? [];
278
- this.onboarding = config.onboarding;
279
- this.systemPrompt = config.systemPrompt;
280
- this.tools = config.tools;
281
- this.query = config.query;
282
- this.checkConnection = config.checkConnection;
283
- }
284
- get connectorKey() {
285
- return _ConnectorPlugin.deriveKey(this.slug, this.authType);
286
- }
287
- /**
288
- * Create tools for connections that belong to this connector.
289
- * Filters connections by connectorKey internally.
290
- * Returns tools keyed as `${connectorKey}_${toolName}`.
291
- */
292
- createTools(connections, config, opts) {
293
- const myConnections = connections.filter(
294
- (c) => _ConnectorPlugin.deriveKey(c.connector.slug, c.connector.authType) === this.connectorKey
295
- );
296
- const result = {};
297
- for (const t of Object.values(this.tools)) {
298
- const tool = t.createTool(myConnections, config);
299
- const originalToModelOutput = tool.toModelOutput;
300
- result[`${this.connectorKey}_${t.name}`] = {
301
- ...tool,
302
- toModelOutput: async (options) => {
303
- if (!originalToModelOutput) {
304
- return opts.truncateOutput(options.output);
305
- }
306
- const modelOutput = await originalToModelOutput(options);
307
- if (modelOutput.type === "text" || modelOutput.type === "json") {
308
- return opts.truncateOutput(modelOutput.value);
309
- }
310
- return modelOutput;
311
- }
312
- };
313
- }
314
- return result;
315
- }
316
- static deriveKey(slug, authType) {
317
- if (authType) return `${slug}-${authType}`;
318
- const LEGACY_NULL_AUTH_TYPE_MAP = {
319
- // user-password
320
- "postgresql": "user-password",
321
- "mysql": "user-password",
322
- "clickhouse": "user-password",
323
- "kintone": "user-password",
324
- "squadbase-db": "user-password",
325
- // service-account
326
- "snowflake": "service-account",
327
- "bigquery": "service-account",
328
- "google-analytics": "service-account",
329
- "google-calendar": "service-account",
330
- "aws-athena": "service-account",
331
- "redshift": "service-account",
332
- // api-key
333
- "databricks": "api-key",
334
- "dbt": "api-key",
335
- "airtable": "api-key",
336
- "openai": "api-key",
337
- "gemini": "api-key",
338
- "anthropic": "api-key",
339
- "wix-store": "api-key"
340
- };
341
- const fallbackAuthType = LEGACY_NULL_AUTH_TYPE_MAP[slug];
342
- if (fallbackAuthType) return `${slug}-${fallbackAuthType}`;
343
- return slug;
344
- }
345
- };
346
-
347
- // ../connectors/src/auth-types.ts
348
- var AUTH_TYPES = {
349
- OAUTH: "oauth",
350
- API_KEY: "api-key",
351
- JWT: "jwt",
352
- SERVICE_ACCOUNT: "service-account",
353
- PAT: "pat",
354
- USER_PASSWORD: "user-password"
355
- };
356
-
357
- // ../connectors/src/lib/normalize-path.ts
358
- function normalizeRequestPath(path2, basePathSegment) {
359
- let p = path2.trim();
360
- if (!p.startsWith("/")) p = "/" + p;
361
- if (p === basePathSegment || p.startsWith(basePathSegment + "/")) {
362
- p = p.slice(basePathSegment.length) || "/";
363
- }
364
- return p;
365
- }
366
-
367
- // ../connectors/src/connectors/powerbi/setup.ts
368
- var powerbiOnboarding = new ConnectorOnboarding({
369
- connectionSetupInstructions: {
370
- en: `Follow these steps to verify the Power BI Service Principal connection.
371
-
372
- 1. Call \`powerbi_request\` with \`method: "GET"\` and \`path: "/groups"\` to list workspaces the Service Principal can access
373
- 2. If the response is an empty \`value\` array, ask the user to add the Service Principal as Member/Admin to at least one workspace (Power BI Service > Workspace > Access). The 'Allow service principals to use Power BI APIs' tenant setting must also be enabled
374
-
375
- #### Constraints
376
- - **Do NOT execute DAX queries or refresh datasets during setup**. Only the workspace listing above is allowed
377
- - Service Principals cannot access "My workspace" \u2014 always work through workspace (group) IDs`,
378
- ja: `Power BI Service Principal \u30B3\u30CD\u30AF\u30B7\u30E7\u30F3\u306E\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u306F\u4EE5\u4E0B\u306E\u624B\u9806\u3067\u884C\u3063\u3066\u304F\u3060\u3055\u3044\u3002
379
-
380
- 1. \`powerbi_request\` \u3092 \`method: "GET"\`\u3001\`path: "/groups"\` \u3067\u547C\u3073\u51FA\u3057\u3001Service Principal \u304C\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u4E00\u89A7\u3092\u53D6\u5F97\u3059\u308B
381
- 2. \`value\` \u304C\u7A7A\u914D\u5217\u3067\u8FD4\u3063\u3066\u304D\u305F\u5834\u5408\u306F\u3001Power BI Service > \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9 > \u30A2\u30AF\u30BB\u30B9 \u304B\u3089\u8A72\u5F53\u306E Service Principal \u3092 Member/Admin \u3068\u3057\u3066\u8FFD\u52A0\u3059\u308B\u3088\u3046\u30E6\u30FC\u30B6\u30FC\u306B\u4F1D\u3048\u308B\u3002\u30C6\u30CA\u30F3\u30C8\u7BA1\u7406\u8A2D\u5B9A\u3067\u300C\u30B5\u30FC\u30D3\u30B9 \u30D7\u30EA\u30F3\u30B7\u30D1\u30EB\u306B Power BI API \u306E\u4F7F\u7528\u3092\u8A31\u53EF\u3059\u308B\u300D\u3082\u6709\u52B9\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308B
382
-
383
- #### \u5236\u7D04
384
- - **\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u4E2D\u306B DAX \u30AF\u30A8\u30EA\u306E\u5B9F\u884C\u3084\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u306E\u66F4\u65B0\u3092\u884C\u308F\u306A\u3044\u3053\u3068**\u3002\u4E0A\u8A18\u306E\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u4E00\u89A7\u53D6\u5F97\u306E\u307F\u8A31\u53EF
385
- - Service Principal \u306F\u300C\u30DE\u30A4 \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u300D\u306B\u30A2\u30AF\u30BB\u30B9\u3067\u304D\u306A\u3044 \u2014 \u5FC5\u305A\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9 (group) ID \u7D4C\u7531\u3067\u64CD\u4F5C\u3059\u308B\u3053\u3068`
386
- },
387
- dataOverviewInstructions: {
388
- en: `1. Call powerbi_request with GET /groups to list accessible workspaces
389
- 2. For a target workspace, call powerbi_request with GET /groups/{groupId}/datasets to list datasets
390
- 3. Call powerbi_request with GET /groups/{groupId}/reports to list reports
391
- 4. For each interesting dataset call powerbi_request with GET /groups/{groupId}/datasets/{datasetId}/tables to inspect tables (requires the dataset to allow XMLA / metadata reads)`,
392
- ja: `1. powerbi_request \u3067 GET /groups \u3092\u547C\u3073\u51FA\u3057\u3001\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u4E00\u89A7\u3092\u53D6\u5F97
393
- 2. \u5BFE\u8C61\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u306B\u5BFE\u3057\u3066 powerbi_request \u3067 GET /groups/{groupId}/datasets \u3092\u547C\u3073\u51FA\u3057\u3001\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u4E00\u89A7\u3092\u53D6\u5F97
394
- 3. powerbi_request \u3067 GET /groups/{groupId}/reports \u3092\u547C\u3073\u51FA\u3057\u3001\u30EC\u30DD\u30FC\u30C8\u4E00\u89A7\u3092\u53D6\u5F97
395
- 4. \u8208\u5473\u306E\u3042\u308B\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u306B\u3064\u3044\u3066 powerbi_request \u3067 GET /groups/{groupId}/datasets/{datasetId}/tables \u3092\u547C\u3073\u51FA\u3057\u3001\u30C6\u30FC\u30D6\u30EB\u69CB\u9020\u3092\u78BA\u8A8D\uFF08XMLA / \u30E1\u30BF\u30C7\u30FC\u30BF\u8AAD\u307F\u53D6\u308A\u304C\u8A31\u53EF\u3055\u308C\u3066\u3044\u308B\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u306B\u9650\u308B\uFF09`
396
- }
397
- });
398
-
399
- // ../connectors/src/connectors/powerbi/tools/request.ts
400
- import { z } from "zod";
401
- var BASE_HOST = "https://api.powerbi.com";
402
- var BASE_PATH_SEGMENT = "/v1.0/myorg";
403
- var BASE_URL2 = `${BASE_HOST}${BASE_PATH_SEGMENT}`;
404
- var TOKEN_SCOPE2 = "https://analysis.windows.net/powerbi/api/.default";
405
- var REQUEST_TIMEOUT_MS = 6e4;
406
- var tokenCache = /* @__PURE__ */ new Map();
407
- async function getAccessToken(tenantId, clientId, clientSecret) {
408
- const cacheKey = `${tenantId}:${clientId}`;
409
- const cached = tokenCache.get(cacheKey);
410
- if (cached && cached.expiresAt > Date.now() + 6e4) {
411
- return cached.token;
412
- }
413
- const tokenUrl = `https://login.microsoftonline.com/${encodeURIComponent(tenantId)}/oauth2/v2.0/token`;
414
- const body = new URLSearchParams({
415
- grant_type: "client_credentials",
416
- client_id: clientId,
417
- client_secret: clientSecret,
418
- scope: TOKEN_SCOPE2
419
- });
420
- const res = await fetch(tokenUrl, {
421
- method: "POST",
422
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
423
- body: body.toString()
424
- });
425
- if (!res.ok) {
426
- const errorText = await res.text().catch(() => res.statusText);
427
- throw new Error(
428
- `Power BI token request failed: HTTP ${res.status} ${errorText}`
429
- );
430
- }
431
- const data = await res.json();
432
- tokenCache.set(cacheKey, {
433
- token: data.access_token,
434
- expiresAt: Date.now() + data.expires_in * 1e3
435
- });
436
- return data.access_token;
437
- }
438
- var inputSchema = z.object({
439
- toolUseIntent: z.string().optional().describe(
440
- "Brief description of what you intend to accomplish with this tool call"
441
- ),
442
- connectionId: z.string().describe("ID of the Power BI connection to use"),
443
- method: z.enum(["GET", "POST", "PATCH", "PUT", "DELETE"]).describe(
444
- "HTTP method. Use GET for reading workspaces/datasets/reports, POST for execute queries / refresh, PATCH/PUT for updates, DELETE for removal."
445
- ),
446
- path: z.string().describe(
447
- "API path appended to https://api.powerbi.com/v1.0/myorg (e.g., '/groups', '/groups/{groupId}/datasets', '/groups/{groupId}/reports/{reportId}'). Service Principal cannot access 'My workspace' \u2014 always work through workspace (group) IDs."
448
- ),
449
- queryParams: z.record(z.string(), z.string()).optional().describe(
450
- `Query parameters to append to the URL (e.g., { $top: '50', $filter: "name eq 'Sales'" }). OData operators ($filter, $top, $skip, $expand) are supported on many list endpoints.`
451
- ),
452
- body: z.record(z.string(), z.unknown()).optional().describe(
453
- "JSON request body for POST/PATCH/PUT/DELETE. Example: executeQueries body { queries: [{ query: 'EVALUATE TOPN(10, Sales)' }], serializerSettings: { includeNulls: true } }."
454
- )
455
- });
456
- var outputSchema = z.discriminatedUnion("success", [
457
- z.object({
458
- success: z.literal(true),
459
- status: z.number(),
460
- data: z.unknown()
461
- }),
462
- z.object({
463
- success: z.literal(false),
464
- error: z.string()
465
- })
466
- ]);
467
- var requestTool = new ConnectorTool({
468
- name: "request",
469
- description: `Send authenticated requests to the Power BI REST API v1.0.
470
- Authentication uses a Microsoft Entra Service Principal (client_credentials) \u2014 the token is acquired and cached automatically.
471
- All paths are relative to https://api.powerbi.com/v1.0/myorg. Service Principals cannot access 'My workspace'; always operate on workspaces ('groups') the SP has been added to.
472
- Use the executeQueries endpoint (POST /groups/{groupId}/datasets/{datasetId}/executeQueries) to run DAX against a dataset.`,
473
- inputSchema,
474
- outputSchema,
475
- async execute({ connectionId, method, path: path2, queryParams, body }, connections) {
476
- const connection2 = connections.find((c) => c.id === connectionId);
477
- if (!connection2) {
478
- return {
479
- success: false,
480
- error: `Connection ${connectionId} not found`
481
- };
482
- }
483
- console.log(
484
- `[connector-request] powerbi/${connection2.name}: ${method} ${path2}`
485
- );
486
- try {
487
- const tenantId = parameters.tenantId.getValue(connection2);
488
- const clientId = parameters.clientId.getValue(connection2);
489
- const clientSecret = parameters.clientSecret.getValue(connection2);
490
- const normalizedPath = normalizeRequestPath(path2, BASE_PATH_SEGMENT);
491
- let url = `${BASE_URL2}${normalizedPath}`;
492
- if (queryParams) {
493
- const searchParams = new URLSearchParams(queryParams);
494
- url += `?${searchParams.toString()}`;
495
- }
496
- const token = await getAccessToken(tenantId, clientId, clientSecret);
497
- const controller = new AbortController();
498
- const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
499
- try {
500
- const init = {
501
- method,
502
- headers: {
503
- Authorization: `Bearer ${token}`,
504
- "Content-Type": "application/json",
505
- Accept: "application/json"
506
- },
507
- signal: controller.signal
508
- };
509
- if (body !== void 0) {
510
- init.body = JSON.stringify(body);
511
- }
512
- const response = await fetch(url, init);
513
- const text = await response.text();
514
- const data = text ? (() => {
515
- try {
516
- return JSON.parse(text);
517
- } catch {
518
- return text;
519
- }
520
- })() : null;
521
- if (!response.ok) {
522
- const errorMessage = data && typeof data === "object" && "error" in data ? JSON.stringify(data.error) : typeof data === "string" && data ? data : `HTTP ${response.status} ${response.statusText}`;
523
- return { success: false, error: errorMessage };
524
- }
525
- return { success: true, status: response.status, data };
526
- } finally {
527
- clearTimeout(timeout);
528
- }
529
- } catch (err) {
530
- const msg = err instanceof Error ? err.message : String(err);
531
- return { success: false, error: msg };
532
- }
533
- }
534
- });
535
-
536
- // ../connectors/src/connectors/powerbi/index.ts
537
- var tools = { request: requestTool };
538
- var powerbiConnector = new ConnectorPlugin({
539
- slug: "powerbi",
540
- authType: AUTH_TYPES.API_KEY,
541
- name: "Power BI",
542
- description: "Connect to Microsoft Power BI via a Service Principal (Microsoft Entra ID app + tenant ID / client ID / client secret). Use it to enumerate workspaces, datasets, and reports, and to run DAX queries.",
543
- iconUrl: "https://upload.wikimedia.org/wikipedia/commons/c/cf/New_Power_BI_Logo.svg",
544
- parameters,
545
- releaseFlag: { dev1: true, dev2: false, prod: false },
546
- categories: ["other"],
547
- onboarding: powerbiOnboarding,
548
- systemPrompt: {
549
- en: `### Tools
550
-
551
- - \`powerbi_request\`: The only way to call the Power BI REST API v1.0. Use it to list workspaces (\`/groups\`), datasets, reports, dashboards, and to run DAX via the \`executeQueries\` endpoint. Service Principal authentication (\`client_credentials\`) is handled automatically and the bearer token is cached.
552
-
553
- ### Business Logic
554
-
555
- The business logic type for this connector is "typescript". Use the connector SDK in your handler. Do NOT read credentials from environment variables.
556
-
557
- SDK methods (client created via \`connection(connectionId)\`):
558
- - \`client.request(path, init?)\` \u2014 low-level authenticated fetch (path appended to \`https://api.powerbi.com/v1.0/myorg\`)
559
- - \`client.listGroups(options?)\` \u2014 list workspaces (\`$top\`, \`$skip\`, \`$filter\`)
560
- - \`client.listDatasets(groupId)\` / \`client.listReports(groupId)\` \u2014 list datasets / reports in a workspace
561
- - \`client.getDataset(groupId, datasetId)\` \u2014 fetch a single dataset
562
- - \`client.executeQueries(groupId, datasetId, queries, options?)\` \u2014 run DAX queries against a dataset
563
- - \`client.refreshDataset(groupId, datasetId, options?)\` \u2014 trigger an async refresh
564
-
565
- \`\`\`ts
566
- import type { Context } from "hono";
567
- import { connection } from "@squadbase/vite-server/connectors/powerbi";
568
-
569
- const powerbi = connection("<connectionId>");
570
-
571
- export default async function handler(c: Context) {
572
- const { groupId, datasetId, dax } = await c.req.json<{
573
- groupId: string;
574
- datasetId: string;
575
- dax: string;
576
- }>();
577
-
578
- const result = await powerbi.executeQueries(groupId, datasetId, [dax]);
579
- const rows = result.results[0]?.tables[0]?.rows ?? [];
580
- return c.json({ rows });
581
- }
582
- \`\`\`
583
-
584
- ### Power BI REST API Reference
585
-
586
- - Base URL: \`https://api.powerbi.com/v1.0/myorg\`
587
- - Authentication: Microsoft Entra Service Principal via OAuth2 \`client_credentials\` grant against \`https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token\` with scope \`https://analysis.windows.net/powerbi/api/.default\` (handled automatically)
588
- - Service Principals cannot access "My workspace"; the SP must be added as Member/Admin to each workspace
589
- - The tenant admin must enable "Allow service principals to use Power BI APIs" in the Power BI admin portal
590
-
591
- #### Common Endpoints
592
- - GET \`/groups\` \u2014 List workspaces accessible to the Service Principal
593
- - GET \`/groups/{groupId}/datasets\` \u2014 List datasets in a workspace
594
- - GET \`/groups/{groupId}/datasets/{datasetId}\` \u2014 Get dataset metadata
595
- - GET \`/groups/{groupId}/datasets/{datasetId}/tables\` \u2014 List tables (requires XMLA / metadata reads enabled)
596
- - GET \`/groups/{groupId}/reports\` \u2014 List reports
597
- - GET \`/groups/{groupId}/dashboards\` \u2014 List dashboards
598
- - POST \`/groups/{groupId}/datasets/{datasetId}/executeQueries\` \u2014 Run DAX (body: \`{ queries: [{ query: "EVALUATE ..." }], serializerSettings: { includeNulls: true } }\`)
599
- - POST \`/groups/{groupId}/datasets/{datasetId}/refreshes\` \u2014 Trigger a dataset refresh
600
- - GET \`/groups/{groupId}/datasets/{datasetId}/refreshes\` \u2014 List recent refresh history
601
-
602
- #### DAX Tips
603
- - \`EVALUATE TOPN(10, 'TableName')\` \u2014 sample 10 rows from a table
604
- - \`EVALUATE SUMMARIZECOLUMNS('Date'[Year], "Sales", SUM('Sales'[Amount]))\` \u2014 aggregate
605
- - Each \`executeQueries\` call accepts one query in the \`queries\` array (per current Power BI API limits); large result sets are truncated server-side (100k rows max)`,
606
- ja: `### \u30C4\u30FC\u30EB
607
-
608
- - \`powerbi_request\`: Power BI REST API v1.0 \u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9 (\`/groups\`)\u3001\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u3001\u30EC\u30DD\u30FC\u30C8\u3001\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u306E\u4E00\u89A7\u53D6\u5F97\u3084\u3001\`executeQueries\` \u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u306B\u3088\u308B DAX \u306E\u5B9F\u884C\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002Service Principal \u8A8D\u8A3C (\`client_credentials\`) \u306F\u81EA\u52D5\u3067\u51E6\u7406\u3055\u308C\u3001Bearer \u30C8\u30FC\u30AF\u30F3\u306F\u30AD\u30E3\u30C3\u30B7\u30E5\u3055\u308C\u307E\u3059\u3002
609
-
610
- ### Business Logic
611
-
612
- \u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\u30CF\u30F3\u30C9\u30E9\u5185\u3067\u306F\u30B3\u30CD\u30AF\u30BF SDK \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089\u8A8D\u8A3C\u60C5\u5831\u3092\u8AAD\u307F\u53D6\u3089\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
613
-
614
- SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
615
- - \`client.request(path, init?)\` \u2014 \u4F4E\u30EC\u30D9\u30EB\u8A8D\u8A3C\u4ED8\u304D fetch (path \u306F \`https://api.powerbi.com/v1.0/myorg\` \u306B\u8FFD\u52A0\u3055\u308C\u307E\u3059)
616
- - \`client.listGroups(options?)\` \u2014 \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u4E00\u89A7 (\`$top\`, \`$skip\`, \`$filter\`)
617
- - \`client.listDatasets(groupId)\` / \`client.listReports(groupId)\` \u2014 \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u5185\u306E\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8/\u30EC\u30DD\u30FC\u30C8\u4E00\u89A7
618
- - \`client.getDataset(groupId, datasetId)\` \u2014 \u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u5358\u4F53\u53D6\u5F97
619
- - \`client.executeQueries(groupId, datasetId, queries, options?)\` \u2014 DAX \u30AF\u30A8\u30EA\u5B9F\u884C
620
- - \`client.refreshDataset(groupId, datasetId, options?)\` \u2014 \u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u66F4\u65B0\u3092\u975E\u540C\u671F\u30C8\u30EA\u30AC\u30FC
621
-
622
- \`\`\`ts
623
- import type { Context } from "hono";
624
- import { connection } from "@squadbase/vite-server/connectors/powerbi";
625
-
626
- const powerbi = connection("<connectionId>");
627
-
628
- export default async function handler(c: Context) {
629
- const { groupId, datasetId, dax } = await c.req.json<{
630
- groupId: string;
631
- datasetId: string;
632
- dax: string;
633
- }>();
634
-
635
- const result = await powerbi.executeQueries(groupId, datasetId, [dax]);
636
- const rows = result.results[0]?.tables[0]?.rows ?? [];
637
- return c.json({ rows });
638
- }
639
- \`\`\`
640
-
641
- ### Power BI REST API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
642
-
643
- - \u30D9\u30FC\u30B9 URL: \`https://api.powerbi.com/v1.0/myorg\`
644
- - \u8A8D\u8A3C: Microsoft Entra Service Principal \u306B\u3088\u308B OAuth2 \`client_credentials\` \u30B0\u30E9\u30F3\u30C8\u3002\`https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token\` \u306B\u5BFE\u3057\u30B9\u30B3\u30FC\u30D7 \`https://analysis.windows.net/powerbi/api/.default\` \u3067\u30C8\u30FC\u30AF\u30F3\u53D6\u5F97 (\u81EA\u52D5)
645
- - Service Principal \u306F\u300C\u30DE\u30A4 \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u300D\u306B\u30A2\u30AF\u30BB\u30B9\u4E0D\u53EF\u3002\u5404\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u306B Member/Admin \u3068\u3057\u3066\u8FFD\u52A0\u304C\u5FC5\u8981
646
- - \u30C6\u30CA\u30F3\u30C8\u7BA1\u7406\u8005\u304C Power BI \u7BA1\u7406\u30DD\u30FC\u30BF\u30EB\u3067\u300C\u30B5\u30FC\u30D3\u30B9 \u30D7\u30EA\u30F3\u30B7\u30D1\u30EB\u306B Power BI API \u306E\u4F7F\u7528\u3092\u8A31\u53EF\u3059\u308B\u300D\u3092\u6709\u52B9\u306B\u3057\u3066\u3044\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059
647
-
648
- #### \u4E3B\u8981\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8
649
- - GET \`/groups\` \u2014 Service Principal \u304C\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u4E00\u89A7
650
- - GET \`/groups/{groupId}/datasets\` \u2014 \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u5185\u306E\u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u4E00\u89A7
651
- - GET \`/groups/{groupId}/datasets/{datasetId}\` \u2014 \u30C7\u30FC\u30BF\u30BB\u30C3\u30C8 \u30E1\u30BF\u30C7\u30FC\u30BF
652
- - GET \`/groups/{groupId}/datasets/{datasetId}/tables\` \u2014 \u30C6\u30FC\u30D6\u30EB\u4E00\u89A7 (XMLA / \u30E1\u30BF\u30C7\u30FC\u30BF\u8AAD\u307F\u53D6\u308A\u304C\u6709\u52B9\u3067\u3042\u308B\u5FC5\u8981\u3042\u308A)
653
- - GET \`/groups/{groupId}/reports\` \u2014 \u30EC\u30DD\u30FC\u30C8\u4E00\u89A7
654
- - GET \`/groups/{groupId}/dashboards\` \u2014 \u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u4E00\u89A7
655
- - POST \`/groups/{groupId}/datasets/{datasetId}/executeQueries\` \u2014 DAX \u5B9F\u884C (body: \`{ queries: [{ query: "EVALUATE ..." }], serializerSettings: { includeNulls: true } }\`)
656
- - POST \`/groups/{groupId}/datasets/{datasetId}/refreshes\` \u2014 \u30C7\u30FC\u30BF\u30BB\u30C3\u30C8\u66F4\u65B0\u30C8\u30EA\u30AC\u30FC
657
- - GET \`/groups/{groupId}/datasets/{datasetId}/refreshes\` \u2014 \u76F4\u8FD1\u306E\u66F4\u65B0\u5C65\u6B74
658
-
659
- #### DAX Tips
660
- - \`EVALUATE TOPN(10, 'TableName')\` \u2014 \u30C6\u30FC\u30D6\u30EB\u304B\u3089 10 \u884C\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0
661
- - \`EVALUATE SUMMARIZECOLUMNS('Date'[Year], "Sales", SUM('Sales'[Amount]))\` \u2014 \u96C6\u8A08
662
- - \`executeQueries\` \u306F\u73FE\u72B6 1 \u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u3064\u304D 1 \u30AF\u30A8\u30EA\u306E\u307F\u3002\u7D50\u679C\u306F\u6700\u5927 10 \u4E07\u884C\u307E\u3067\u30B5\u30FC\u30D0\u30FC\u5074\u3067\u5207\u308A\u8A70\u3081\u3089\u308C\u307E\u3059`
663
- },
664
- tools,
665
- async checkConnection(params) {
666
- const tenantId = params[parameters.tenantId.slug];
667
- const clientId = params[parameters.clientId.slug];
668
- const clientSecret = params[parameters.clientSecret.slug];
669
- if (!tenantId || !clientId || !clientSecret) {
670
- return {
671
- success: false,
672
- error: "Missing required parameters: tenant-id, client-id, client-secret"
673
- };
674
- }
675
- try {
676
- const tokenUrl = `https://login.microsoftonline.com/${encodeURIComponent(tenantId)}/oauth2/v2.0/token`;
677
- const body = new URLSearchParams({
678
- grant_type: "client_credentials",
679
- client_id: clientId,
680
- client_secret: clientSecret,
681
- scope: "https://analysis.windows.net/powerbi/api/.default"
682
- });
683
- const tokenRes = await fetch(tokenUrl, {
684
- method: "POST",
685
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
686
- body: body.toString()
687
- });
688
- if (!tokenRes.ok) {
689
- const errorText = await tokenRes.text().catch(() => tokenRes.statusText);
690
- return {
691
- success: false,
692
- error: `Power BI token request failed: HTTP ${tokenRes.status} ${errorText}`
693
- };
694
- }
695
- const { access_token } = await tokenRes.json();
696
- const apiRes = await fetch(
697
- "https://api.powerbi.com/v1.0/myorg/groups?$top=1",
698
- {
699
- method: "GET",
700
- headers: {
701
- Authorization: `Bearer ${access_token}`,
702
- Accept: "application/json"
703
- }
704
- }
705
- );
706
- if (!apiRes.ok) {
707
- const errorText = await apiRes.text().catch(() => apiRes.statusText);
708
- return {
709
- success: false,
710
- error: `Power BI API failed: HTTP ${apiRes.status} ${errorText}`
711
- };
712
- }
713
- return { success: true };
714
- } catch (error) {
715
- return {
716
- success: false,
717
- error: error instanceof Error ? error.message : String(error)
718
- };
719
- }
720
- }
721
- });
722
-
723
- // src/connectors/create-connector-sdk.ts
724
- import { readFileSync } from "fs";
725
- import path from "path";
726
-
727
- // src/connector-client/env.ts
728
- function resolveEnvVar(entry, key, connectionId) {
729
- const envVarName = entry.envVars[key];
730
- if (!envVarName) {
731
- throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
732
- }
733
- const value = process.env[envVarName];
734
- if (!value) {
735
- throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
736
- }
737
- return value;
738
- }
739
- function resolveEnvVarOptional(entry, key) {
740
- const envVarName = entry.envVars[key];
741
- if (!envVarName) return void 0;
742
- return process.env[envVarName] || void 0;
743
- }
744
-
745
- // src/connector-client/proxy-fetch.ts
746
- import { getContext } from "hono/context-storage";
747
- import { getCookie } from "hono/cookie";
748
- var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
749
- function normalizeHeaders(input) {
750
- const out = {};
751
- if (!input) return out;
752
- new Headers(input).forEach((value, key) => {
753
- out[key] = value;
754
- });
755
- return out;
756
- }
757
- function createSandboxProxyFetch(connectionId) {
758
- return async (input, init) => {
759
- const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
760
- const sandboxId = process.env.INTERNAL_SQUADBASE_SANDBOX_ID;
761
- if (!token || !sandboxId) {
762
- throw new Error(
763
- "Connection proxy is not configured. Please check your deployment settings."
764
- );
765
- }
766
- const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
767
- const originalMethod = init?.method ?? "GET";
768
- const originalBody = init?.body ? JSON.parse(init.body) : void 0;
769
- const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
770
- const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
771
- return fetch(proxyUrl, {
772
- method: "POST",
773
- headers: {
774
- "Content-Type": "application/json",
775
- Authorization: `Bearer ${token}`
776
- },
777
- body: JSON.stringify({
778
- url: originalUrl,
779
- method: originalMethod,
780
- headers: normalizeHeaders(init?.headers),
781
- body: originalBody
782
- })
783
- });
784
- };
785
- }
786
- function createDeployedAppProxyFetch(connectionId) {
787
- const projectId = process.env["SQUADBASE_PROJECT_ID"];
788
- if (!projectId) {
789
- throw new Error(
790
- "Connection proxy is not configured. Please check your deployment settings."
791
- );
792
- }
793
- const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
794
- const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
795
- return async (input, init) => {
796
- const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
797
- const originalMethod = init?.method ?? "GET";
798
- const originalBody = init?.body ? JSON.parse(init.body) : void 0;
799
- const c = getContext();
800
- const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
801
- if (!appSession) {
802
- throw new Error(
803
- "No authentication method available for connection proxy."
804
- );
805
- }
806
- return fetch(proxyUrl, {
807
- method: "POST",
808
- headers: {
809
- "Content-Type": "application/json",
810
- Authorization: `Bearer ${appSession}`
811
- },
812
- body: JSON.stringify({
813
- url: originalUrl,
814
- method: originalMethod,
815
- headers: normalizeHeaders(init?.headers),
816
- body: originalBody
817
- })
818
- });
819
- };
820
- }
821
- function createProxyFetch(connectionId) {
822
- if (process.env.INTERNAL_SQUADBASE_SANDBOX_ID) {
823
- return createSandboxProxyFetch(connectionId);
824
- }
825
- return createDeployedAppProxyFetch(connectionId);
826
- }
827
-
828
- // src/connectors/create-connector-sdk.ts
829
- function loadConnectionsSync() {
830
- const filePath = process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
831
- try {
832
- const raw = readFileSync(filePath, "utf-8");
833
- return JSON.parse(raw);
834
- } catch {
835
- return {};
836
- }
837
- }
838
- function createConnectorSdk(plugin, createClient2) {
839
- return (connectionId) => {
840
- const connections = loadConnectionsSync();
841
- const entry = connections[connectionId];
842
- if (!entry) {
843
- throw new Error(
844
- `Connection "${connectionId}" not found in .squadbase/connections.json`
845
- );
846
- }
847
- if (entry.connector.slug !== plugin.slug) {
848
- throw new Error(
849
- `Connection "${connectionId}" is not a ${plugin.slug} connection (got "${entry.connector.slug}")`
850
- );
851
- }
852
- const params = {};
853
- for (const param of Object.values(plugin.parameters)) {
854
- if (param.required) {
855
- params[param.slug] = resolveEnvVar(entry, param.slug, connectionId);
856
- } else {
857
- const val = resolveEnvVarOptional(entry, param.slug);
858
- if (val !== void 0) params[param.slug] = val;
859
- }
860
- }
861
- return createClient2(params, createProxyFetch(connectionId));
862
- };
863
- }
864
-
865
- // src/connectors/entries/powerbi.ts
866
- var connection = createConnectorSdk(powerbiConnector, createClient);
867
- export {
868
- connection
869
- };