@almadar/integrations 2.18.0 → 2.20.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.
- package/dist/{BaseIntegration-B9lSTRQm.d.ts → BaseIntegration-MA-b4fh8.d.ts} +1 -1
- package/dist/{contracts-DMzkrcLB.d.ts → contracts-DaVfyPnd.d.ts} +55 -3
- package/dist/{factory-TNgVDujm.d.ts → factory-DTdVeyAi.d.ts} +1 -1
- package/dist/index.d.ts +54 -5
- package/dist/index.js +417 -24
- package/dist/index.js.map +1 -1
- package/dist/integrations/github/index.d.ts +1 -1
- package/dist/mocks/index.d.ts +2 -2
- package/dist/runtime/index.d.ts +3 -3
- package/dist/runtime/index.js +9 -0
- package/dist/runtime/index.js.map +1 -1
- package/package.json +5 -3
|
@@ -146,4 +146,4 @@ declare abstract class BaseIntegration {
|
|
|
146
146
|
protected executeWithRetry<T>(fn: () => Promise<T>): Promise<T>;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
export { BaseIntegration as B, type IntegrationConfig as I, type ValidationError as V, IntegrationError as a, type IntegrationErrorCode as b, type IntegrationLogger as c, type
|
|
149
|
+
export { BaseIntegration as B, type IntegrationConfig as I, type ValidationError as V, IntegrationError as a, type IntegrationErrorCode as b, type IntegrationLogger as c, type IntegrationParamValue as d, type IntegrationParams as e, type IntegrationResult as f, type ValidationResult as g, validateParams as v };
|
|
@@ -1,5 +1,29 @@
|
|
|
1
|
-
import { ServiceParams } from '@almadar/core';
|
|
2
|
-
import { d as IntegrationParams } from './BaseIntegration-
|
|
1
|
+
import { ServiceParamsValue, ServiceParams } from '@almadar/core';
|
|
2
|
+
import { d as IntegrationParamValue, e as IntegrationParams } from './BaseIntegration-MA-b4fh8.js';
|
|
3
|
+
|
|
4
|
+
/** Scalar value admissible as a SQL bind parameter. */
|
|
5
|
+
type DatabaseQueryParamValue = string | number | boolean | Date | null;
|
|
6
|
+
/** Params for the `query` action. */
|
|
7
|
+
type DatabaseQueryParams = {
|
|
8
|
+
connectionRef: string;
|
|
9
|
+
sql: string;
|
|
10
|
+
params?: DatabaseQueryParamValue[];
|
|
11
|
+
};
|
|
12
|
+
/** One result row, keyed by column name. */
|
|
13
|
+
type DatabaseRow = Record<string, IntegrationParamValue>;
|
|
14
|
+
/** Result of a read-only query. */
|
|
15
|
+
interface DatabaseQueryResult {
|
|
16
|
+
rows: DatabaseRow[];
|
|
17
|
+
rowCount: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Driver seam behind the `query` action. Postgres is the first
|
|
21
|
+
* implementation; MySQL/SQLite slot behind this interface later.
|
|
22
|
+
*/
|
|
23
|
+
interface DatabaseDriver {
|
|
24
|
+
query(sql: string, params: readonly DatabaseQueryParamValue[]): Promise<DatabaseQueryResult>;
|
|
25
|
+
end(): Promise<void>;
|
|
26
|
+
}
|
|
3
27
|
|
|
4
28
|
/**
|
|
5
29
|
* ServiceContract type definitions for all @almadar/integrations.
|
|
@@ -235,6 +259,19 @@ type LLMIntegrationActions = {
|
|
|
235
259
|
};
|
|
236
260
|
};
|
|
237
261
|
};
|
|
262
|
+
type MLActions = {
|
|
263
|
+
infer: {
|
|
264
|
+
params: {
|
|
265
|
+
model: string;
|
|
266
|
+
input: ServiceParamsValue;
|
|
267
|
+
};
|
|
268
|
+
result: {
|
|
269
|
+
output: ServiceParamsValue;
|
|
270
|
+
confidence: number;
|
|
271
|
+
violations: ServiceParamsValue[];
|
|
272
|
+
};
|
|
273
|
+
};
|
|
274
|
+
};
|
|
238
275
|
type YouTubeActions = {
|
|
239
276
|
search: {
|
|
240
277
|
params: {
|
|
@@ -835,6 +872,19 @@ type DeepAgentActions = {
|
|
|
835
872
|
};
|
|
836
873
|
};
|
|
837
874
|
};
|
|
875
|
+
type DatabaseActions = {
|
|
876
|
+
query: {
|
|
877
|
+
params: {
|
|
878
|
+
connectionRef: string;
|
|
879
|
+
sql: string;
|
|
880
|
+
params?: DatabaseQueryParamValue[];
|
|
881
|
+
};
|
|
882
|
+
result: {
|
|
883
|
+
rows: DatabaseRow[];
|
|
884
|
+
rowCount: number;
|
|
885
|
+
};
|
|
886
|
+
};
|
|
887
|
+
};
|
|
838
888
|
/**
|
|
839
889
|
* Maps each integration name (as used in `registerIntegration`) to its action
|
|
840
890
|
* map type. Use with `ServiceContract<IntegrationContracts[K]>` to get a fully
|
|
@@ -852,6 +902,7 @@ type IntegrationContracts = {
|
|
|
852
902
|
github: GitHubActions;
|
|
853
903
|
stripe: StripeActions;
|
|
854
904
|
llm: LLMIntegrationActions;
|
|
905
|
+
ml: MLActions;
|
|
855
906
|
youtube: YouTubeActions;
|
|
856
907
|
twilio: TwilioActions;
|
|
857
908
|
email: EmailActions;
|
|
@@ -863,6 +914,7 @@ type IntegrationContracts = {
|
|
|
863
914
|
otel: OtelActions;
|
|
864
915
|
cli: CLIActions;
|
|
865
916
|
deepagent: DeepAgentActions;
|
|
917
|
+
database: DatabaseActions;
|
|
866
918
|
};
|
|
867
919
|
/** Integration name literal union. */
|
|
868
920
|
type IntegrationName = keyof IntegrationContracts;
|
|
@@ -877,4 +929,4 @@ type IntegrationName = keyof IntegrationContracts;
|
|
|
877
929
|
*/
|
|
878
930
|
type IntegrationActionName<K extends IntegrationName> = keyof IntegrationContracts[K] & string;
|
|
879
931
|
|
|
880
|
-
export type { CLIActions as C,
|
|
932
|
+
export type { CLIActions as C, DatabaseActions as D, EmailActions as E, GitHubActions as G, IntegrationActionName as I, LLMIntegrationActions as L, MLActions as M, OAuthActions as O, QueueActions as Q, RedisActions as R, StorageActions as S, TwilioActions as T, YouTubeActions as Y, DatabaseDriver as a, DatabaseQueryParamValue as b, DatabaseQueryParams as c, DatabaseQueryResult as d, DatabaseRow as e, DeepAgentActions as f, DockerActions as g, IntegrationContracts as h, IntegrationName as i, OtelActions as j, StripeActions as k };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { I as IntegrationConfig, B as BaseIntegration,
|
|
1
|
+
import { I as IntegrationConfig, B as BaseIntegration, e as IntegrationParams, f as IntegrationResult } from './BaseIntegration-MA-b4fh8.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Factory for creating and managing integration instances
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { c as IntegrationLogger, b as IntegrationErrorCode, I as IntegrationConfig, B as BaseIntegration,
|
|
2
|
-
export { a as IntegrationError, V as ValidationError,
|
|
3
|
-
export { C as CLIActions, D as
|
|
1
|
+
import { c as IntegrationLogger, b as IntegrationErrorCode, I as IntegrationConfig, B as BaseIntegration, e as IntegrationParams, f as IntegrationResult } from './BaseIntegration-MA-b4fh8.js';
|
|
2
|
+
export { a as IntegrationError, d as IntegrationParamValue, V as ValidationError, g as ValidationResult, v as validateParams } from './BaseIntegration-MA-b4fh8.js';
|
|
3
|
+
export { C as CLIActions, D as DatabaseActions, a as DatabaseDriver, b as DatabaseQueryParamValue, c as DatabaseQueryParams, d as DatabaseQueryResult, e as DatabaseRow, f as DeepAgentActions, g as DockerActions, E as EmailActions, G as GitHubActions, I as IntegrationActionName, h as IntegrationContracts, i as IntegrationName, L as LLMIntegrationActions, M as MLActions, O as OAuthActions, j as OtelActions, Q as QueueActions, R as RedisActions, S as StorageActions, k as StripeActions, T as TwilioActions, Y as YouTubeActions } from './contracts-DaVfyPnd.js';
|
|
4
4
|
import { LogMeta } from '@almadar/core';
|
|
5
|
-
export { I as IntegrationFactory, g as getIntegrationFactory, r as resetIntegrationFactory } from './factory-
|
|
5
|
+
export { I as IntegrationFactory, g as getIntegrationFactory, r as resetIntegrationFactory } from './factory-DTdVeyAi.js';
|
|
6
6
|
export { GitHubIntegration } from './integrations/github/index.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -366,6 +366,15 @@ declare class LLMIntegration extends BaseIntegration {
|
|
|
366
366
|
private summarize;
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
declare class MLIntegration extends BaseIntegration {
|
|
370
|
+
private readonly eventsUrl;
|
|
371
|
+
private readonly timeoutMs;
|
|
372
|
+
constructor(config: IntegrationConfig);
|
|
373
|
+
execute(action: string, params: IntegrationParams): Promise<IntegrationResult>;
|
|
374
|
+
private infer;
|
|
375
|
+
private postEvent;
|
|
376
|
+
}
|
|
377
|
+
|
|
369
378
|
/**
|
|
370
379
|
* DeepAgent integration for AI code generation
|
|
371
380
|
*/
|
|
@@ -551,4 +560,44 @@ declare class DockerIntegration extends BaseIntegration {
|
|
|
551
560
|
private list;
|
|
552
561
|
}
|
|
553
562
|
|
|
554
|
-
|
|
563
|
+
/**
|
|
564
|
+
* Read-only SQL enforcement for the database integration.
|
|
565
|
+
*
|
|
566
|
+
* The guard accepts exactly one SELECT statement (optionally CTE-led) and
|
|
567
|
+
* nothing else. Comments are stripped and string/identifier contents are
|
|
568
|
+
* blanked before keyword checks, so denied keywords inside literals (e.g.
|
|
569
|
+
* `WHERE note = 'delete this'`) never false-positive, and comment/quote
|
|
570
|
+
* tricks cannot smuggle a second statement past the check.
|
|
571
|
+
*/
|
|
572
|
+
interface SqlGuardResult {
|
|
573
|
+
ok: boolean;
|
|
574
|
+
reason?: string;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Verify `sql` is a single read-only SELECT statement.
|
|
578
|
+
* Returns `{ ok: false, reason }` for anything else.
|
|
579
|
+
*/
|
|
580
|
+
declare function assertReadOnlySelect(sql: string): SqlGuardResult;
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Database Integration for Almadar
|
|
584
|
+
* Read-only SQL queries over external connections (migration fetch seam).
|
|
585
|
+
*/
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Generic database integration. One action — `query` — with read-only
|
|
589
|
+
* enforced in the integration itself (single SELECT only, statement
|
|
590
|
+
* timeout). `connectionRef` is an environment variable NAME, never the
|
|
591
|
+
* secret; the connection string is resolved from `process.env` at run time.
|
|
592
|
+
*/
|
|
593
|
+
declare class DatabaseIntegration extends BaseIntegration {
|
|
594
|
+
private drivers;
|
|
595
|
+
private statementTimeoutMs;
|
|
596
|
+
constructor(config: IntegrationConfig);
|
|
597
|
+
execute(action: string, params: IntegrationParams): Promise<IntegrationResult>;
|
|
598
|
+
private runQuery;
|
|
599
|
+
/** Resolve (and cache) the driver for a connection reference. */
|
|
600
|
+
private driverFor;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
export { type AlmadarCheckoutSession, type AlmadarCustomer, type AlmadarInvoiceFailure, type AlmadarInvoicePayment, type AlmadarPortalSession, type AlmadarStripeEvent, type AlmadarStripeEventOk, type AlmadarSubscription, type AlmadarSubscriptionStatus, type AlmadarTier, BaseIntegration, CLIIntegration, type CancelSubscriptionInput, ConsoleLogger, type CreateCheckoutInput, type CreateCustomerInput, type CreatePortalInput, DatabaseIntegration, DeepAgentIntegration, DockerIntegration, EmailIntegration, IntegrationConfig, type IntegrationConstructor, IntegrationErrorCode, IntegrationLogger, IntegrationParams, IntegrationResult, LLMIntegration, MLIntegration, OAuthIntegration, OtelIntegration, QueueIntegration, RedisIntegration, type RetryConfig, type SqlGuardResult, StorageIntegration, StripeIntegration, type StripePriceMap, TwilioIntegration, type UpdateSubscriptionInput, type VerifyAndParseInput, YouTubeIntegration, assertReadOnlySelect, getIntegration, getRegisteredIntegrations, isKnownIntegration, registerIntegration, verifyAndParseStripeEvent, withRetry };
|
package/dist/index.js
CHANGED
|
@@ -11,8 +11,27 @@ import { execSync, spawn } from 'child_process';
|
|
|
11
11
|
import { mkdtempSync, writeFileSync, rmSync, promises } from 'fs';
|
|
12
12
|
import { join } from 'path';
|
|
13
13
|
import { tmpdir } from 'os';
|
|
14
|
+
import { Pool } from 'pg';
|
|
14
15
|
|
|
15
|
-
// src/
|
|
16
|
+
// src/types.ts
|
|
17
|
+
var IntegrationError = class extends Error {
|
|
18
|
+
constructor(message, code = "UNKNOWN_ERROR", details) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "IntegrationError";
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.details = details;
|
|
23
|
+
}
|
|
24
|
+
toJSON() {
|
|
25
|
+
return {
|
|
26
|
+
name: this.name,
|
|
27
|
+
message: this.message,
|
|
28
|
+
code: this.code,
|
|
29
|
+
integration: this.integration,
|
|
30
|
+
action: this.action,
|
|
31
|
+
details: this.details
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
};
|
|
16
35
|
var ConsoleLogger = class {
|
|
17
36
|
constructor(_level = "info") {
|
|
18
37
|
this.log = createLogger("almadar:integrations");
|
|
@@ -1121,6 +1140,159 @@ Return ONLY valid JSON matching the schema.`,
|
|
|
1121
1140
|
};
|
|
1122
1141
|
registerIntegration("llm", LLMIntegration);
|
|
1123
1142
|
|
|
1143
|
+
// src/integrations/ml/index.ts
|
|
1144
|
+
var INFERRED_EVENT = "INFERRED";
|
|
1145
|
+
var INFER_FAILED_EVENT = "INFER_FAILED";
|
|
1146
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
1147
|
+
function isServiceParams(value) {
|
|
1148
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
1149
|
+
}
|
|
1150
|
+
function extractInferResult(response) {
|
|
1151
|
+
const emitted = (response.effectResults ?? []).find(
|
|
1152
|
+
(result) => result.effect === "emit" && result.success && isServiceParams(result.data) && result.data.event === INFERRED_EVENT
|
|
1153
|
+
);
|
|
1154
|
+
if (!emitted || !isServiceParams(emitted.data)) {
|
|
1155
|
+
return null;
|
|
1156
|
+
}
|
|
1157
|
+
const payload = emitted.data.payload;
|
|
1158
|
+
if (!isServiceParams(payload)) {
|
|
1159
|
+
return null;
|
|
1160
|
+
}
|
|
1161
|
+
if (!("output" in payload) || typeof payload.confidence !== "number" || !Array.isArray(payload.violations)) {
|
|
1162
|
+
return null;
|
|
1163
|
+
}
|
|
1164
|
+
return {
|
|
1165
|
+
output: payload.output,
|
|
1166
|
+
confidence: payload.confidence,
|
|
1167
|
+
violations: payload.violations
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
var MLIntegration = class extends BaseIntegration {
|
|
1171
|
+
constructor(config) {
|
|
1172
|
+
super(config);
|
|
1173
|
+
const baseUrl = config.env.MASAR_URL;
|
|
1174
|
+
const traitPrefix = config.env.MASAR_ML_TRAIT;
|
|
1175
|
+
if (!baseUrl || !traitPrefix) {
|
|
1176
|
+
throw new Error(
|
|
1177
|
+
"ML integration requires MASAR_URL (serving orbital host) and MASAR_ML_TRAIT (kebab-case trait name of its /events route) to be configured"
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
this.eventsUrl = `${baseUrl.replace(/\/+$/, "")}/api/${traitPrefix}/events`;
|
|
1181
|
+
this.timeoutMs = config.env.MASAR_ML_TIMEOUT_MS ? Number(config.env.MASAR_ML_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
|
|
1182
|
+
this.logger.info("ML integration initialized", { eventsUrl: this.eventsUrl });
|
|
1183
|
+
}
|
|
1184
|
+
async execute(action, params) {
|
|
1185
|
+
const validation = this.validateParams(action, params);
|
|
1186
|
+
if (!validation.valid) {
|
|
1187
|
+
return {
|
|
1188
|
+
success: false,
|
|
1189
|
+
error: {
|
|
1190
|
+
name: "IntegrationError",
|
|
1191
|
+
message: "Validation failed",
|
|
1192
|
+
code: "VALIDATION_ERROR",
|
|
1193
|
+
details: validation.errors
|
|
1194
|
+
},
|
|
1195
|
+
metadata: this.createMetadata(action, 0)
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
const startTime = Date.now();
|
|
1199
|
+
try {
|
|
1200
|
+
let data;
|
|
1201
|
+
switch (action) {
|
|
1202
|
+
case "infer":
|
|
1203
|
+
data = await this.executeWithRetry(() => this.infer(params));
|
|
1204
|
+
break;
|
|
1205
|
+
default:
|
|
1206
|
+
throw new Error(`Unknown action: ${action}`);
|
|
1207
|
+
}
|
|
1208
|
+
return {
|
|
1209
|
+
success: true,
|
|
1210
|
+
data,
|
|
1211
|
+
metadata: this.createMetadata(action, Date.now() - startTime)
|
|
1212
|
+
};
|
|
1213
|
+
} catch (error) {
|
|
1214
|
+
return this.handleError(action, error);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
async infer(params) {
|
|
1218
|
+
const { model, input } = params;
|
|
1219
|
+
this.logger.debug("Running ML inference", { model });
|
|
1220
|
+
const response = await this.postEvent(model, input);
|
|
1221
|
+
if (!response.success) {
|
|
1222
|
+
throw new IntegrationError(
|
|
1223
|
+
`ML service reported failure: ${response.error ?? "unknown error"}`,
|
|
1224
|
+
"SERVICE_ERROR",
|
|
1225
|
+
{ model, response }
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
const failed = (response.effectResults ?? []).find(
|
|
1229
|
+
(result) => result.effect === "emit" && isServiceParams(result.data) && result.data.event === INFER_FAILED_EVENT
|
|
1230
|
+
);
|
|
1231
|
+
if (failed) {
|
|
1232
|
+
throw new IntegrationError(
|
|
1233
|
+
`ML service could not infer: ${failed.error ?? "model unavailable"}`,
|
|
1234
|
+
"SERVICE_ERROR",
|
|
1235
|
+
{ model, response }
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
const inferred = extractInferResult(response);
|
|
1239
|
+
if (!inferred) {
|
|
1240
|
+
throw new IntegrationError(
|
|
1241
|
+
"ML service response did not include a valid inference result",
|
|
1242
|
+
"SERVICE_ERROR",
|
|
1243
|
+
{ model, response }
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
return inferred;
|
|
1247
|
+
}
|
|
1248
|
+
async postEvent(model, input) {
|
|
1249
|
+
const controller = new AbortController();
|
|
1250
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
1251
|
+
let httpResponse;
|
|
1252
|
+
try {
|
|
1253
|
+
httpResponse = await fetch(this.eventsUrl, {
|
|
1254
|
+
method: "POST",
|
|
1255
|
+
headers: { "Content-Type": "application/json" },
|
|
1256
|
+
body: JSON.stringify({ event: "INFER", payload: { model, input } }),
|
|
1257
|
+
signal: controller.signal
|
|
1258
|
+
});
|
|
1259
|
+
} catch (error) {
|
|
1260
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
1261
|
+
throw new IntegrationError(
|
|
1262
|
+
`ML service request timed out after ${this.timeoutMs}ms (model may be cold-starting)`,
|
|
1263
|
+
"TIMEOUT_ERROR",
|
|
1264
|
+
{ model, eventsUrl: this.eventsUrl }
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
throw new IntegrationError(
|
|
1268
|
+
`ML service request failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1269
|
+
"NETWORK_ERROR",
|
|
1270
|
+
{ model, eventsUrl: this.eventsUrl }
|
|
1271
|
+
);
|
|
1272
|
+
} finally {
|
|
1273
|
+
clearTimeout(timer);
|
|
1274
|
+
}
|
|
1275
|
+
if (!httpResponse.ok) {
|
|
1276
|
+
const body = await httpResponse.text().catch(() => "");
|
|
1277
|
+
throw new IntegrationError(
|
|
1278
|
+
`ML service responded with status ${httpResponse.status}`,
|
|
1279
|
+
"SERVICE_ERROR",
|
|
1280
|
+
{ model, status: httpResponse.status, body }
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
try {
|
|
1284
|
+
return await httpResponse.json();
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
throw new IntegrationError(
|
|
1287
|
+
`ML service returned a malformed response body: ${error instanceof Error ? error.message : String(error)}`,
|
|
1288
|
+
"SERVICE_ERROR",
|
|
1289
|
+
{ model }
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
registerIntegration("ml", MLIntegration);
|
|
1295
|
+
|
|
1124
1296
|
// src/integrations/deepagent/index.ts
|
|
1125
1297
|
var DeepAgentIntegration = class extends BaseIntegration {
|
|
1126
1298
|
constructor(config) {
|
|
@@ -1242,28 +1414,6 @@ var DeepAgentIntegration = class extends BaseIntegration {
|
|
|
1242
1414
|
}
|
|
1243
1415
|
};
|
|
1244
1416
|
registerIntegration("deepagent", DeepAgentIntegration);
|
|
1245
|
-
|
|
1246
|
-
// src/types.ts
|
|
1247
|
-
var IntegrationError = class extends Error {
|
|
1248
|
-
constructor(message, code = "UNKNOWN_ERROR", details) {
|
|
1249
|
-
super(message);
|
|
1250
|
-
this.name = "IntegrationError";
|
|
1251
|
-
this.code = code;
|
|
1252
|
-
this.details = details;
|
|
1253
|
-
}
|
|
1254
|
-
toJSON() {
|
|
1255
|
-
return {
|
|
1256
|
-
name: this.name,
|
|
1257
|
-
message: this.message,
|
|
1258
|
-
code: this.code,
|
|
1259
|
-
integration: this.integration,
|
|
1260
|
-
action: this.action,
|
|
1261
|
-
details: this.details
|
|
1262
|
-
};
|
|
1263
|
-
}
|
|
1264
|
-
};
|
|
1265
|
-
|
|
1266
|
-
// src/integrations/github/github-git.ts
|
|
1267
1417
|
async function execGit(args, cwd, env) {
|
|
1268
1418
|
return new Promise((resolve, reject) => {
|
|
1269
1419
|
const proc = spawn("git", args, {
|
|
@@ -3021,6 +3171,249 @@ var DockerIntegration = class extends BaseIntegration {
|
|
|
3021
3171
|
};
|
|
3022
3172
|
registerIntegration("docker", DockerIntegration);
|
|
3023
3173
|
|
|
3024
|
-
|
|
3174
|
+
// src/integrations/database/sql-guard.ts
|
|
3175
|
+
var DENIED_KEYWORDS = /* @__PURE__ */ new Set(["INSERT", "UPDATE", "DELETE", "MERGE", "INTO", "FOR"]);
|
|
3176
|
+
function isWordChar(ch) {
|
|
3177
|
+
return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "_" || ch === "$";
|
|
3178
|
+
}
|
|
3179
|
+
function skipSingleQuoted(sql, start, eString) {
|
|
3180
|
+
let i = start + 1;
|
|
3181
|
+
while (i < sql.length) {
|
|
3182
|
+
if (eString && sql[i] === "\\") {
|
|
3183
|
+
i += 2;
|
|
3184
|
+
continue;
|
|
3185
|
+
}
|
|
3186
|
+
if (sql[i] === "'") {
|
|
3187
|
+
if (sql[i + 1] === "'") {
|
|
3188
|
+
i += 2;
|
|
3189
|
+
continue;
|
|
3190
|
+
}
|
|
3191
|
+
return i + 1;
|
|
3192
|
+
}
|
|
3193
|
+
i++;
|
|
3194
|
+
}
|
|
3195
|
+
return sql.length;
|
|
3196
|
+
}
|
|
3197
|
+
function skipDoubleQuoted(sql, start) {
|
|
3198
|
+
let i = start + 1;
|
|
3199
|
+
while (i < sql.length) {
|
|
3200
|
+
if (sql[i] === '"') {
|
|
3201
|
+
if (sql[i + 1] === '"') {
|
|
3202
|
+
i += 2;
|
|
3203
|
+
continue;
|
|
3204
|
+
}
|
|
3205
|
+
return i + 1;
|
|
3206
|
+
}
|
|
3207
|
+
i++;
|
|
3208
|
+
}
|
|
3209
|
+
return sql.length;
|
|
3210
|
+
}
|
|
3211
|
+
function matchDollarTag(sql, start) {
|
|
3212
|
+
let i = start + 1;
|
|
3213
|
+
if (sql[i] === "$") return "$$";
|
|
3214
|
+
const first = sql[i];
|
|
3215
|
+
if (!first || !(first >= "a" && first <= "z" || first >= "A" && first <= "Z" || first === "_")) {
|
|
3216
|
+
return void 0;
|
|
3217
|
+
}
|
|
3218
|
+
i++;
|
|
3219
|
+
while (i < sql.length && isWordChar(sql[i]) && sql[i] !== "$") i++;
|
|
3220
|
+
if (sql[i] !== "$") return void 0;
|
|
3221
|
+
return sql.slice(start, i + 1);
|
|
3222
|
+
}
|
|
3223
|
+
function sanitizeSql(sql) {
|
|
3224
|
+
const out = [];
|
|
3225
|
+
let i = 0;
|
|
3226
|
+
while (i < sql.length) {
|
|
3227
|
+
const ch = sql[i];
|
|
3228
|
+
if (ch === "-" && sql[i + 1] === "-") {
|
|
3229
|
+
while (i < sql.length && sql[i] !== "\n") i++;
|
|
3230
|
+
out.push(" ");
|
|
3231
|
+
continue;
|
|
3232
|
+
}
|
|
3233
|
+
if (ch === "/" && sql[i + 1] === "*") {
|
|
3234
|
+
let depth = 1;
|
|
3235
|
+
i += 2;
|
|
3236
|
+
while (i < sql.length && depth > 0) {
|
|
3237
|
+
if (sql[i] === "/" && sql[i + 1] === "*") {
|
|
3238
|
+
depth++;
|
|
3239
|
+
i += 2;
|
|
3240
|
+
} else if (sql[i] === "*" && sql[i + 1] === "/") {
|
|
3241
|
+
depth--;
|
|
3242
|
+
i += 2;
|
|
3243
|
+
} else {
|
|
3244
|
+
i++;
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
out.push(" ");
|
|
3248
|
+
continue;
|
|
3249
|
+
}
|
|
3250
|
+
if (ch === "'") {
|
|
3251
|
+
let j = i - 1;
|
|
3252
|
+
while (j >= 0 && (sql[j] === " " || sql[j] === " " || sql[j] === "\n" || sql[j] === "\r")) j--;
|
|
3253
|
+
const prev = j >= 0 ? sql[j] : "";
|
|
3254
|
+
const eString = (prev === "e" || prev === "E") && (j === 0 || !isWordChar(sql[j - 1]));
|
|
3255
|
+
i = skipSingleQuoted(sql, i, eString);
|
|
3256
|
+
out.push("''");
|
|
3257
|
+
continue;
|
|
3258
|
+
}
|
|
3259
|
+
if (ch === '"') {
|
|
3260
|
+
i = skipDoubleQuoted(sql, i);
|
|
3261
|
+
out.push('""');
|
|
3262
|
+
continue;
|
|
3263
|
+
}
|
|
3264
|
+
if (ch === "$") {
|
|
3265
|
+
const tag = matchDollarTag(sql, i);
|
|
3266
|
+
if (tag) {
|
|
3267
|
+
const close = sql.indexOf(tag, i + tag.length);
|
|
3268
|
+
i = close === -1 ? sql.length : close + tag.length;
|
|
3269
|
+
out.push("$$");
|
|
3270
|
+
continue;
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
out.push(ch);
|
|
3274
|
+
i++;
|
|
3275
|
+
}
|
|
3276
|
+
return out.join("");
|
|
3277
|
+
}
|
|
3278
|
+
function assertReadOnlySelect(sql) {
|
|
3279
|
+
const sanitized = sanitizeSql(sql);
|
|
3280
|
+
const statements = sanitized.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
3281
|
+
if (statements.length === 0) {
|
|
3282
|
+
return { ok: false, reason: "Empty statement" };
|
|
3283
|
+
}
|
|
3284
|
+
if (statements.length > 1) {
|
|
3285
|
+
return { ok: false, reason: "Only a single statement is allowed" };
|
|
3286
|
+
}
|
|
3287
|
+
const words = (statements[0].match(/\b[A-Za-z_][A-Za-z0-9_]*\b/g) ?? []).map(
|
|
3288
|
+
(w) => w.toUpperCase()
|
|
3289
|
+
);
|
|
3290
|
+
const first = words[0];
|
|
3291
|
+
if (first !== "SELECT" && first !== "WITH") {
|
|
3292
|
+
return { ok: false, reason: "Only SELECT statements are allowed" };
|
|
3293
|
+
}
|
|
3294
|
+
const denied = words.find((w) => DENIED_KEYWORDS.has(w));
|
|
3295
|
+
if (denied) {
|
|
3296
|
+
return { ok: false, reason: `Keyword not allowed in a read-only query: ${denied}` };
|
|
3297
|
+
}
|
|
3298
|
+
return { ok: true };
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
// src/integrations/database/index.ts
|
|
3302
|
+
var DEFAULT_STATEMENT_TIMEOUT_MS = 1e4;
|
|
3303
|
+
var PG_QUERY_CANCELED = "57014";
|
|
3304
|
+
var PostgresDriver = class {
|
|
3305
|
+
constructor(connectionString, statementTimeoutMs) {
|
|
3306
|
+
this.pool = new Pool({
|
|
3307
|
+
connectionString,
|
|
3308
|
+
statement_timeout: statementTimeoutMs,
|
|
3309
|
+
query_timeout: statementTimeoutMs
|
|
3310
|
+
});
|
|
3311
|
+
}
|
|
3312
|
+
async query(sql, params) {
|
|
3313
|
+
const result = await this.pool.query(sql, [...params]);
|
|
3314
|
+
return {
|
|
3315
|
+
rows: result.rows,
|
|
3316
|
+
rowCount: result.rowCount ?? result.rows.length
|
|
3317
|
+
};
|
|
3318
|
+
}
|
|
3319
|
+
async end() {
|
|
3320
|
+
await this.pool.end();
|
|
3321
|
+
}
|
|
3322
|
+
};
|
|
3323
|
+
function mapDriverError(error) {
|
|
3324
|
+
if (error instanceof IntegrationError) return error;
|
|
3325
|
+
const code = error.code;
|
|
3326
|
+
if (code === PG_QUERY_CANCELED) {
|
|
3327
|
+
return new IntegrationError("Statement timeout exceeded", "TIMEOUT_ERROR");
|
|
3328
|
+
}
|
|
3329
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3330
|
+
return new IntegrationError(message, "SERVICE_ERROR");
|
|
3331
|
+
}
|
|
3332
|
+
var DatabaseIntegration = class extends BaseIntegration {
|
|
3333
|
+
constructor(config) {
|
|
3334
|
+
super(config);
|
|
3335
|
+
this.drivers = /* @__PURE__ */ new Map();
|
|
3336
|
+
const fromEnv = config.env.DATABASE_STATEMENT_TIMEOUT_MS;
|
|
3337
|
+
if (fromEnv) {
|
|
3338
|
+
const parsed = Number(fromEnv);
|
|
3339
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
3340
|
+
throw new Error(`Invalid DATABASE_STATEMENT_TIMEOUT_MS: ${fromEnv}`);
|
|
3341
|
+
}
|
|
3342
|
+
this.statementTimeoutMs = parsed;
|
|
3343
|
+
} else {
|
|
3344
|
+
this.statementTimeoutMs = config.timeout ?? DEFAULT_STATEMENT_TIMEOUT_MS;
|
|
3345
|
+
}
|
|
3346
|
+
this.logger.info("Database integration initialized", {
|
|
3347
|
+
statementTimeoutMs: this.statementTimeoutMs
|
|
3348
|
+
});
|
|
3349
|
+
}
|
|
3350
|
+
async execute(action, params) {
|
|
3351
|
+
const validation = this.validateParams(action, params);
|
|
3352
|
+
if (!validation.valid) {
|
|
3353
|
+
return {
|
|
3354
|
+
success: false,
|
|
3355
|
+
error: {
|
|
3356
|
+
name: "IntegrationError",
|
|
3357
|
+
message: "Validation failed",
|
|
3358
|
+
code: "VALIDATION_ERROR",
|
|
3359
|
+
details: validation.errors
|
|
3360
|
+
},
|
|
3361
|
+
metadata: this.createMetadata(action, 0)
|
|
3362
|
+
};
|
|
3363
|
+
}
|
|
3364
|
+
const startTime = Date.now();
|
|
3365
|
+
try {
|
|
3366
|
+
let data;
|
|
3367
|
+
switch (action) {
|
|
3368
|
+
case "query":
|
|
3369
|
+
data = await this.executeWithRetry(() => this.runQuery(params));
|
|
3370
|
+
break;
|
|
3371
|
+
default:
|
|
3372
|
+
throw new Error(`Unknown action: ${action}`);
|
|
3373
|
+
}
|
|
3374
|
+
return {
|
|
3375
|
+
success: true,
|
|
3376
|
+
data,
|
|
3377
|
+
metadata: this.createMetadata(action, Date.now() - startTime)
|
|
3378
|
+
};
|
|
3379
|
+
} catch (error) {
|
|
3380
|
+
return this.handleError(action, error);
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
async runQuery(params) {
|
|
3384
|
+
const guard = assertReadOnlySelect(params.sql);
|
|
3385
|
+
if (!guard.ok) {
|
|
3386
|
+
throw new IntegrationError(
|
|
3387
|
+
`Read-only violation: ${guard.reason ?? "not a SELECT statement"}`,
|
|
3388
|
+
"VALIDATION_ERROR"
|
|
3389
|
+
);
|
|
3390
|
+
}
|
|
3391
|
+
const driver = this.driverFor(params.connectionRef);
|
|
3392
|
+
this.logger.debug("Database QUERY", { connectionRef: params.connectionRef });
|
|
3393
|
+
try {
|
|
3394
|
+
return await driver.query(params.sql, params.params ?? []);
|
|
3395
|
+
} catch (error) {
|
|
3396
|
+
throw mapDriverError(error);
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
/** Resolve (and cache) the driver for a connection reference. */
|
|
3400
|
+
driverFor(connectionRef) {
|
|
3401
|
+
const connectionString = process.env[connectionRef];
|
|
3402
|
+
if (!connectionString) {
|
|
3403
|
+
throw new IntegrationError(
|
|
3404
|
+
`Connection reference "${connectionRef}" is not set in the environment`,
|
|
3405
|
+
"AUTH_ERROR"
|
|
3406
|
+
);
|
|
3407
|
+
}
|
|
3408
|
+
const cached = this.drivers.get(connectionString);
|
|
3409
|
+
if (cached) return cached;
|
|
3410
|
+
const driver = new PostgresDriver(connectionString, this.statementTimeoutMs);
|
|
3411
|
+
this.drivers.set(connectionString, driver);
|
|
3412
|
+
return driver;
|
|
3413
|
+
}
|
|
3414
|
+
};
|
|
3415
|
+
registerIntegration("database", DatabaseIntegration);
|
|
3416
|
+
|
|
3417
|
+
export { BaseIntegration, CLIIntegration, ConsoleLogger, DatabaseIntegration, DeepAgentIntegration, DockerIntegration, EmailIntegration, GitHubIntegration, IntegrationError, IntegrationFactory, LLMIntegration, MLIntegration, OAuthIntegration, OtelIntegration, QueueIntegration, RedisIntegration, StorageIntegration, StripeIntegration, TwilioIntegration, YouTubeIntegration, assertReadOnlySelect, getIntegration, getIntegrationFactory, getRegisteredIntegrations, isKnownIntegration, registerIntegration, resetIntegrationFactory, validateParams, verifyAndParseStripeEvent, withRetry };
|
|
3025
3418
|
//# sourceMappingURL=index.js.map
|
|
3026
3419
|
//# sourceMappingURL=index.js.map
|