@agentworkforce/sage 1.0.5 → 1.1.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/.env.example +5 -4
- package/dist/app.d.ts +0 -2
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +268 -190
- package/dist/integrations/cloud-proxy-provider.d.ts +42 -0
- package/dist/integrations/cloud-proxy-provider.d.ts.map +1 -0
- package/dist/integrations/cloud-proxy-provider.js +131 -0
- package/dist/integrations/github-context.d.ts +2 -1
- package/dist/integrations/github-context.d.ts.map +1 -1
- package/dist/integrations/github-context.js +4 -2
- package/dist/integrations/github.d.ts +4 -2
- package/dist/integrations/github.d.ts.map +1 -1
- package/dist/integrations/github.js +16 -11
- package/dist/integrations/slack-egress.d.ts +28 -0
- package/dist/integrations/slack-egress.d.ts.map +1 -0
- package/dist/integrations/slack-egress.js +181 -0
- package/dist/integrations/slack-ingress.d.ts +26 -0
- package/dist/integrations/slack-ingress.d.ts.map +1 -0
- package/dist/integrations/slack-ingress.js +31 -0
- package/dist/nango.d.ts +1 -7
- package/dist/nango.d.ts.map +1 -1
- package/dist/nango.js +9 -30
- package/dist/proactive/context-watcher.d.ts +2 -2
- package/dist/proactive/context-watcher.d.ts.map +1 -1
- package/dist/proactive/context-watcher.js +5 -3
- package/dist/proactive/engine.d.ts +5 -5
- package/dist/proactive/engine.d.ts.map +1 -1
- package/dist/proactive/engine.js +25 -19
- package/dist/proactive/evidence-sources/affirmative-reply-source.d.ts.map +1 -1
- package/dist/proactive/evidence-sources/affirmative-reply-source.js +4 -2
- package/dist/proactive/evidence-sources/explicit-close-source.d.ts.map +1 -1
- package/dist/proactive/evidence-sources/explicit-close-source.js +4 -2
- package/dist/proactive/evidence-sources/pr-merge-source.d.ts.map +1 -1
- package/dist/proactive/evidence-sources/pr-merge-source.js +12 -5
- package/dist/proactive/evidence-sources/reaction-source.d.ts.map +1 -1
- package/dist/proactive/evidence-sources/reaction-source.js +6 -16
- package/dist/proactive/follow-up-checker.d.ts +4 -4
- package/dist/proactive/follow-up-checker.d.ts.map +1 -1
- package/dist/proactive/follow-up-checker.js +40 -20
- package/dist/proactive/integrations/slack-egress.d.ts +2 -0
- package/dist/proactive/integrations/slack-egress.d.ts.map +1 -0
- package/dist/proactive/integrations/slack-egress.js +1 -0
- package/dist/proactive/pr-matcher.d.ts +2 -2
- package/dist/proactive/pr-matcher.d.ts.map +1 -1
- package/dist/proactive/pr-matcher.js +8 -6
- package/dist/proactive/stale-thread-detector.d.ts +2 -2
- package/dist/proactive/stale-thread-detector.d.ts.map +1 -1
- package/dist/proactive/stale-thread-detector.js +16 -23
- package/dist/proactive/types.d.ts +8 -5
- package/dist/proactive/types.d.ts.map +1 -1
- package/dist/slack.d.ts +3 -13
- package/dist/slack.d.ts.map +1 -1
- package/dist/slack.js +7 -108
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type ProxyMethod = "DELETE" | "GET" | "PATCH" | "POST" | "PUT";
|
|
2
|
+
export type ProxyHeaders = Record<string, string>;
|
|
3
|
+
export type ProxyQuery = Record<string, string>;
|
|
4
|
+
export interface ProxyRequest {
|
|
5
|
+
method: ProxyMethod;
|
|
6
|
+
endpoint: string;
|
|
7
|
+
workspaceId?: string;
|
|
8
|
+
data?: unknown;
|
|
9
|
+
params?: ProxyQuery;
|
|
10
|
+
}
|
|
11
|
+
export interface ProxyResponse<T = unknown> {
|
|
12
|
+
status: number;
|
|
13
|
+
headers: ProxyHeaders;
|
|
14
|
+
data: T;
|
|
15
|
+
}
|
|
16
|
+
export interface ConnectionProvider {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
proxy<T = unknown>(request: ProxyRequest): Promise<ProxyResponse<T>>;
|
|
19
|
+
healthCheck(): Promise<boolean>;
|
|
20
|
+
}
|
|
21
|
+
export declare class CloudProxyError extends Error {
|
|
22
|
+
readonly status?: number;
|
|
23
|
+
readonly code: string;
|
|
24
|
+
readonly retryAfterMs?: number;
|
|
25
|
+
constructor(message: string, options?: {
|
|
26
|
+
status?: number;
|
|
27
|
+
code?: string;
|
|
28
|
+
retryAfterMs?: number;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export declare class CloudProxyProvider implements ConnectionProvider {
|
|
32
|
+
readonly name = "cloud-proxy";
|
|
33
|
+
private readonly baseUrl;
|
|
34
|
+
private readonly cloudApiToken;
|
|
35
|
+
constructor(config: {
|
|
36
|
+
cloudApiUrl: string;
|
|
37
|
+
cloudApiToken: string;
|
|
38
|
+
});
|
|
39
|
+
proxy<T = unknown>(request: ProxyRequest): Promise<ProxyResponse<T>>;
|
|
40
|
+
healthCheck(): Promise<boolean>;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=cloud-proxy-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-proxy-provider.d.ts","sourceRoot":"","sources":["../../src/integrations/cloud-proxy-provider.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;AACtE,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAClD,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEhD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,YAAY,CAAC;IACtB,IAAI,EAAE,CAAC,CAAC;CACT;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC;AA8CD,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;gBAEnB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE;CAOjG;AAwBD,qBAAa,kBAAmB,YAAW,kBAAkB;IAC3D,QAAQ,CAAC,IAAI,iBAAiB;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE;IAK5D,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IA0DpE,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;CAWtC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
const HEALTH_ENDPOINT = "/api/health";
|
|
2
|
+
const SLACK_PROXY_ENDPOINT = "/api/v1/proxy/slack";
|
|
3
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function readString(value) {
|
|
8
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
9
|
+
}
|
|
10
|
+
function readNumber(value) {
|
|
11
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
12
|
+
}
|
|
13
|
+
function requireNonEmpty(value, field) {
|
|
14
|
+
const normalized = value.trim();
|
|
15
|
+
if (!normalized) {
|
|
16
|
+
throw new CloudProxyError(`Cloud proxy configuration is missing ${field}`);
|
|
17
|
+
}
|
|
18
|
+
return normalized;
|
|
19
|
+
}
|
|
20
|
+
function readWorkspaceId(request) {
|
|
21
|
+
const workspaceId = readString(request.workspaceId);
|
|
22
|
+
if (!workspaceId) {
|
|
23
|
+
throw new CloudProxyError("Cloud Slack proxy request is missing workspaceId", {
|
|
24
|
+
code: "invalid_request",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return workspaceId;
|
|
28
|
+
}
|
|
29
|
+
export class CloudProxyError extends Error {
|
|
30
|
+
status;
|
|
31
|
+
code;
|
|
32
|
+
retryAfterMs;
|
|
33
|
+
constructor(message, options) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "CloudProxyError";
|
|
36
|
+
this.status = options?.status;
|
|
37
|
+
this.code = options?.code ?? "cloud_proxy_error";
|
|
38
|
+
this.retryAfterMs = options?.retryAfterMs;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function parseEnvelope(payload) {
|
|
42
|
+
if (!isRecord(payload) || typeof payload.ok !== "boolean") {
|
|
43
|
+
throw new CloudProxyError("Cloud Slack proxy returned an invalid response");
|
|
44
|
+
}
|
|
45
|
+
if (payload.ok) {
|
|
46
|
+
return {
|
|
47
|
+
ok: true,
|
|
48
|
+
data: ("data" in payload ? payload.data : undefined),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
...(readString(payload.error) ? { error: readString(payload.error) } : {}),
|
|
54
|
+
...(readString(payload.code) ? { code: readString(payload.code) } : {}),
|
|
55
|
+
...(readNumber(payload.retryAfterMs) !== undefined
|
|
56
|
+
? { retryAfterMs: readNumber(payload.retryAfterMs) }
|
|
57
|
+
: {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export class CloudProxyProvider {
|
|
61
|
+
name = "cloud-proxy";
|
|
62
|
+
baseUrl;
|
|
63
|
+
cloudApiToken;
|
|
64
|
+
constructor(config) {
|
|
65
|
+
this.baseUrl = requireNonEmpty(config.cloudApiUrl, "cloudApiUrl").replace(/\/+$/, "");
|
|
66
|
+
this.cloudApiToken = requireNonEmpty(config.cloudApiToken, "cloudApiToken");
|
|
67
|
+
}
|
|
68
|
+
async proxy(request) {
|
|
69
|
+
let response;
|
|
70
|
+
const workspaceId = readWorkspaceId(request);
|
|
71
|
+
try {
|
|
72
|
+
response = await fetch(`${this.baseUrl}${SLACK_PROXY_ENDPOINT}`, {
|
|
73
|
+
method: "POST",
|
|
74
|
+
headers: {
|
|
75
|
+
Authorization: `Bearer ${this.cloudApiToken}`,
|
|
76
|
+
"Content-Type": "application/json",
|
|
77
|
+
},
|
|
78
|
+
body: JSON.stringify({
|
|
79
|
+
workspaceId,
|
|
80
|
+
endpoint: request.endpoint,
|
|
81
|
+
method: request.method,
|
|
82
|
+
...(request.data !== undefined ? { data: request.data } : {}),
|
|
83
|
+
...(request.params !== undefined ? { params: request.params } : {}),
|
|
84
|
+
}),
|
|
85
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
throw new CloudProxyError(error instanceof Error ? error.message : "Cloud Slack proxy request failed", { code: "network_error" });
|
|
90
|
+
}
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
throw new CloudProxyError(`Cloud Slack proxy request failed: ${response.status} ${response.statusText}`, {
|
|
93
|
+
code: "upstream_error",
|
|
94
|
+
status: response.status,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
let payload;
|
|
98
|
+
try {
|
|
99
|
+
payload = parseEnvelope(await response.json());
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error instanceof CloudProxyError) {
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
throw new CloudProxyError("Cloud Slack proxy returned invalid JSON");
|
|
106
|
+
}
|
|
107
|
+
if (!payload.ok) {
|
|
108
|
+
throw new CloudProxyError(payload.error ?? "Cloud Slack proxy returned an error", {
|
|
109
|
+
code: payload.code,
|
|
110
|
+
retryAfterMs: payload.retryAfterMs,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
status: response.status,
|
|
115
|
+
headers: {},
|
|
116
|
+
data: payload.data,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
async healthCheck() {
|
|
120
|
+
try {
|
|
121
|
+
const response = await fetch(`${this.baseUrl}${HEALTH_ENDPOINT}`, {
|
|
122
|
+
method: "GET",
|
|
123
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
124
|
+
});
|
|
125
|
+
return response.ok;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -6,10 +6,11 @@ export interface GitHubContextProviderOptions {
|
|
|
6
6
|
}
|
|
7
7
|
export declare class GitHubContextProvider {
|
|
8
8
|
private readonly nangoClient;
|
|
9
|
+
private readonly providerConfigKey;
|
|
9
10
|
private readonly cache;
|
|
10
11
|
private readonly cacheTtlMs;
|
|
11
12
|
private readonly logger;
|
|
12
|
-
constructor(nangoClient: NangoClient, options?: GitHubContextProviderOptions);
|
|
13
|
+
constructor(nangoClient: NangoClient, providerConfigKey: string, options?: GitHubContextProviderOptions);
|
|
13
14
|
getOrgsAndRepos(connectionId: string, relayfileReader?: SageRelayFileReader | null): Promise<GitHubOrgDiscovery>;
|
|
14
15
|
private setCache;
|
|
15
16
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-context.d.ts","sourceRoot":"","sources":["../../src/integrations/github-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AASjE,MAAM,WAAW,4BAA4B;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,qBAAa,qBAAqB;IAM9B,OAAO,CAAC,QAAQ,CAAC,WAAW;
|
|
1
|
+
{"version":3,"file":"github-context.d.ts","sourceRoot":"","sources":["../../src/integrations/github-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AASjE,MAAM,WAAW,4BAA4B;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,qBAAa,qBAAqB;IAM9B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IANpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IACvD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;gBAG5B,WAAW,EAAE,WAAW,EACxB,iBAAiB,EAAE,MAAM,EAC1C,OAAO,GAAE,4BAAiC;IAMtC,eAAe,CACnB,YAAY,EAAE,MAAM,EACpB,eAAe,CAAC,EAAE,mBAAmB,GAAG,IAAI,GAC3C,OAAO,CAAC,kBAAkB,CAAC;IAwB9B,OAAO,CAAC,QAAQ;CAMjB"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
const DEFAULT_CACHE_TTL_MS = 5 * 60_000;
|
|
2
2
|
export class GitHubContextProvider {
|
|
3
3
|
nangoClient;
|
|
4
|
+
providerConfigKey;
|
|
4
5
|
cache = new Map();
|
|
5
6
|
cacheTtlMs;
|
|
6
7
|
logger;
|
|
7
|
-
constructor(nangoClient, options = {}) {
|
|
8
|
+
constructor(nangoClient, providerConfigKey, options = {}) {
|
|
8
9
|
this.nangoClient = nangoClient;
|
|
10
|
+
this.providerConfigKey = providerConfigKey;
|
|
9
11
|
this.cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
10
12
|
this.logger = options.logger ?? console;
|
|
11
13
|
}
|
|
@@ -27,7 +29,7 @@ export class GitHubContextProvider {
|
|
|
27
29
|
this.logger.warn('[sage] RelayFile GitHub discovery failed, falling back to Nango', error);
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
|
-
const discovery = await this.nangoClient.discoverGitHubOrgs(connectionId);
|
|
32
|
+
const discovery = await this.nangoClient.discoverGitHubOrgs(connectionId, this.providerConfigKey);
|
|
31
33
|
this.setCache(connectionId, discovery);
|
|
32
34
|
return cloneGitHubOrgDiscovery(discovery);
|
|
33
35
|
}
|
|
@@ -3,16 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* All GitHub API calls are routed through Nango's proxy so that
|
|
5
5
|
* authentication is handled automatically via the stored connection.
|
|
6
|
-
* Uses the
|
|
6
|
+
* Uses the workspace-resolved provider config key for repo access.
|
|
7
7
|
*/
|
|
8
8
|
import { NangoClient } from '../nango.js';
|
|
9
9
|
import type { IntegrationResult, GitHubFile, GitHubIssue, GitHubPR, GitHubSearchResult } from './types.js';
|
|
10
10
|
export declare class GitHubIntegration {
|
|
11
11
|
private readonly nango;
|
|
12
12
|
private readonly connectionId;
|
|
13
|
-
|
|
13
|
+
private readonly providerConfigKey;
|
|
14
|
+
constructor({ nangoClient, connectionId, providerConfigKey, }: {
|
|
14
15
|
nangoClient: NangoClient;
|
|
15
16
|
connectionId: string;
|
|
17
|
+
providerConfigKey?: string | null;
|
|
16
18
|
});
|
|
17
19
|
private errorResult;
|
|
18
20
|
private execute;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/integrations/github.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/integrations/github.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAc,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EACV,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,kBAAkB,EACnB,MAAM,YAAY,CAAC;AA4EpB,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;gBAE/B,EACV,WAAW,EACX,YAAY,EACZ,iBAAiB,GAClB,EAAE;QACD,WAAW,EAAE,WAAW,CAAC;QACzB,YAAY,EAAE,MAAM,CAAC;QACrB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACnC;IAWD,OAAO,CAAC,WAAW;YAOL,OAAO;IAcf,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;IAmCxF,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,WAAW,EAAE,CAAC,CAAC;IAmCrF,QAAQ,CACZ,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAmCnC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAuC7F,aAAa,CACjB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,iBAAiB,CAAC,MAAM,EAAE,CAAC,CAAC;IAmBjC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;CA+BrG"}
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* All GitHub API calls are routed through Nango's proxy so that
|
|
5
5
|
* authentication is handled automatically via the stored connection.
|
|
6
|
-
* Uses the
|
|
6
|
+
* Uses the workspace-resolved provider config key for repo access.
|
|
7
7
|
*/
|
|
8
|
-
import { NangoError
|
|
8
|
+
import { NangoError } from '../nango.js';
|
|
9
9
|
const SOURCE = 'github';
|
|
10
|
-
const PROVIDER = NANGO_INTEGRATIONS.GITHUB;
|
|
11
10
|
function result(data, summary) {
|
|
12
11
|
return {
|
|
13
12
|
source: SOURCE,
|
|
@@ -64,9 +63,15 @@ function encodeRepoPath(path) {
|
|
|
64
63
|
export class GitHubIntegration {
|
|
65
64
|
nango;
|
|
66
65
|
connectionId;
|
|
67
|
-
|
|
66
|
+
providerConfigKey;
|
|
67
|
+
constructor({ nangoClient, connectionId, providerConfigKey, }) {
|
|
68
|
+
const resolvedProviderConfigKey = providerConfigKey?.trim();
|
|
69
|
+
if (!resolvedProviderConfigKey) {
|
|
70
|
+
throw new Error('GitHubIntegration requires a providerConfigKey');
|
|
71
|
+
}
|
|
68
72
|
this.nango = nangoClient;
|
|
69
73
|
this.connectionId = connectionId;
|
|
74
|
+
this.providerConfigKey = resolvedProviderConfigKey;
|
|
70
75
|
}
|
|
71
76
|
errorResult(action, fallback, error) {
|
|
72
77
|
const prefix = isRateLimited(error)
|
|
@@ -88,7 +93,7 @@ export class GitHubIntegration {
|
|
|
88
93
|
return this.execute(`searching code for "${query}"${repo ? ` in ${repo}` : ''}`, { items: [] }, async () => {
|
|
89
94
|
const data = await this.nango.proxy({
|
|
90
95
|
connectionId: this.connectionId,
|
|
91
|
-
providerConfigKey:
|
|
96
|
+
providerConfigKey: this.providerConfigKey,
|
|
92
97
|
method: 'GET',
|
|
93
98
|
endpoint: `/search/code?q=${encodeURIComponent(scopedQuery)}`,
|
|
94
99
|
headers: { Accept: 'application/vnd.github.text-match+json' },
|
|
@@ -108,7 +113,7 @@ export class GitHubIntegration {
|
|
|
108
113
|
return this.execute(`searching issues for "${query}"${repo ? ` in ${repo}` : ''}`, [], async () => {
|
|
109
114
|
const data = await this.nango.proxy({
|
|
110
115
|
connectionId: this.connectionId,
|
|
111
|
-
providerConfigKey:
|
|
116
|
+
providerConfigKey: this.providerConfigKey,
|
|
112
117
|
method: 'GET',
|
|
113
118
|
endpoint: `/search/issues?q=${encodeURIComponent(scopedQuery)}`,
|
|
114
119
|
});
|
|
@@ -130,7 +135,7 @@ export class GitHubIntegration {
|
|
|
130
135
|
return this.execute(`reading file ${owner}/${repo}/${path}${ref ? ` @ ${ref}` : ''}`, { path, content: '', sha: '' }, async () => {
|
|
131
136
|
const data = await this.nango.proxy({
|
|
132
137
|
connectionId: this.connectionId,
|
|
133
|
-
providerConfigKey:
|
|
138
|
+
providerConfigKey: this.providerConfigKey,
|
|
134
139
|
method: 'GET',
|
|
135
140
|
endpoint,
|
|
136
141
|
});
|
|
@@ -148,13 +153,13 @@ export class GitHubIntegration {
|
|
|
148
153
|
const [pr, files] = await Promise.all([
|
|
149
154
|
this.nango.proxy({
|
|
150
155
|
connectionId: this.connectionId,
|
|
151
|
-
providerConfigKey:
|
|
156
|
+
providerConfigKey: this.providerConfigKey,
|
|
152
157
|
method: 'GET',
|
|
153
158
|
endpoint: `/repos/${owner}/${repo}/pulls/${number}`,
|
|
154
159
|
}),
|
|
155
160
|
this.nango.proxy({
|
|
156
161
|
connectionId: this.connectionId,
|
|
157
|
-
providerConfigKey:
|
|
162
|
+
providerConfigKey: this.providerConfigKey,
|
|
158
163
|
method: 'GET',
|
|
159
164
|
endpoint: `/repos/${owner}/${repo}/pulls/${number}/files`,
|
|
160
165
|
}),
|
|
@@ -174,7 +179,7 @@ export class GitHubIntegration {
|
|
|
174
179
|
return this.execute(`listing repository contents for ${owner}/${repo}${path ? `/${path}` : ''}`, [], async () => {
|
|
175
180
|
const data = await this.nango.proxy({
|
|
176
181
|
connectionId: this.connectionId,
|
|
177
|
-
providerConfigKey:
|
|
182
|
+
providerConfigKey: this.providerConfigKey,
|
|
178
183
|
method: 'GET',
|
|
179
184
|
endpoint: `/repos/${owner}/${repo}/contents${encodedPath}`,
|
|
180
185
|
});
|
|
@@ -185,7 +190,7 @@ export class GitHubIntegration {
|
|
|
185
190
|
return this.execute(`loading issue #${number} in ${owner}/${repo}`, { number, title: '', body: '', state: 'error', labels: [], url: '' }, async () => {
|
|
186
191
|
const data = await this.nango.proxy({
|
|
187
192
|
connectionId: this.connectionId,
|
|
188
|
-
providerConfigKey:
|
|
193
|
+
providerConfigKey: this.providerConfigKey,
|
|
189
194
|
method: 'GET',
|
|
190
195
|
endpoint: `/repos/${owner}/${repo}/issues/${number}`,
|
|
191
196
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ConnectionProvider } from "./cloud-proxy-provider.js";
|
|
2
|
+
import { type ThreadMessage } from "../slack.js";
|
|
3
|
+
export interface SlackEgressResult {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
ts?: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class SlackEgressError extends Error {
|
|
9
|
+
readonly code: string;
|
|
10
|
+
readonly retryAfterMs?: number;
|
|
11
|
+
constructor(message: string, code?: string, retryAfterMs?: number);
|
|
12
|
+
}
|
|
13
|
+
export interface SlackReaction {
|
|
14
|
+
name: string;
|
|
15
|
+
count: number;
|
|
16
|
+
}
|
|
17
|
+
export interface SlackEgress {
|
|
18
|
+
postMessage(channel: string, text: string, threadTs?: string): Promise<SlackEgressResult>;
|
|
19
|
+
postMessageChunked(channel: string, text: string, threadTs?: string, maxChars?: number): Promise<SlackEgressResult>;
|
|
20
|
+
addReaction(channel: string, timestamp: string, emoji: string): Promise<void>;
|
|
21
|
+
fetchThreadHistory(channel: string, threadTs: string, botUserId?: string): Promise<ThreadMessage[]>;
|
|
22
|
+
getBotUserId(): Promise<string | undefined>;
|
|
23
|
+
getReactions(channel: string, timestamp: string): Promise<SlackReaction[]>;
|
|
24
|
+
}
|
|
25
|
+
export declare function createSlackEgress(provider: ConnectionProvider, workspaceId: string): SlackEgress;
|
|
26
|
+
export type { ConnectionProvider } from "./cloud-proxy-provider.js";
|
|
27
|
+
export type { ThreadMessage } from "../slack.js";
|
|
28
|
+
//# sourceMappingURL=slack-egress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-egress.d.ts","sourceRoot":"","sources":["../../src/integrations/slack-egress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAgB,MAAM,2BAA2B,CAAC;AAElF,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AA4BrB,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;gBAEnB,OAAO,EAAE,MAAM,EAAE,IAAI,SAAwB,EAAE,YAAY,CAAC,EAAE,MAAM;CAMjF;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC1F,kBAAkB,CAChB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC9B,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,kBAAkB,CAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAC5B,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC5C,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;CAC5E;AA+ED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,GAAG,WAAW,CAkKhG;AAED,YAAY,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { SLACK_MESSAGE_MAX_CHARS, chunkSlackMessage, cleanSlackText, } from "../slack.js";
|
|
2
|
+
export class SlackEgressError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
retryAfterMs;
|
|
5
|
+
constructor(message, code = "slack_egress_failed", retryAfterMs) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "SlackEgressError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.retryAfterMs = retryAfterMs;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function isRecord(value) {
|
|
13
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
function asString(value) {
|
|
16
|
+
return typeof value === "string" ? value : undefined;
|
|
17
|
+
}
|
|
18
|
+
function asNumber(value) {
|
|
19
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
20
|
+
}
|
|
21
|
+
function toSlackEgressError(error, fallbackCode = "slack_egress_failed") {
|
|
22
|
+
if (error instanceof SlackEgressError) {
|
|
23
|
+
return error;
|
|
24
|
+
}
|
|
25
|
+
if (isRecord(error)) {
|
|
26
|
+
const message = asString(error.message) ?? "Slack request failed";
|
|
27
|
+
const code = asString(error.code) ?? fallbackCode;
|
|
28
|
+
const retryAfterMs = asNumber(error.retryAfterMs);
|
|
29
|
+
return new SlackEgressError(message, code, retryAfterMs);
|
|
30
|
+
}
|
|
31
|
+
if (error instanceof Error) {
|
|
32
|
+
return new SlackEgressError(error.message, fallbackCode);
|
|
33
|
+
}
|
|
34
|
+
return new SlackEgressError("Slack request failed", fallbackCode);
|
|
35
|
+
}
|
|
36
|
+
function assertWorkspaceId(workspaceId) {
|
|
37
|
+
if (typeof workspaceId !== "string" || workspaceId.trim().length === 0) {
|
|
38
|
+
throw new Error("A workspaceId is required to create Slack egress");
|
|
39
|
+
}
|
|
40
|
+
return workspaceId.trim();
|
|
41
|
+
}
|
|
42
|
+
function createProxyRequest(workspaceId, endpoint, method, options) {
|
|
43
|
+
return {
|
|
44
|
+
method,
|
|
45
|
+
endpoint,
|
|
46
|
+
workspaceId,
|
|
47
|
+
...(options?.data !== undefined ? { data: options.data } : {}),
|
|
48
|
+
...(options?.params !== undefined ? { params: options.params } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async function executeSlackRequest(provider, request) {
|
|
52
|
+
try {
|
|
53
|
+
const response = await provider.proxy(request);
|
|
54
|
+
if (isRecord(response.data) && response.data.ok === false) {
|
|
55
|
+
throw new SlackEgressError(asString(response.data.error) ?? "Slack request failed", asString(response.data.code) ?? "slack_error", asNumber(response.data.retryAfterMs));
|
|
56
|
+
}
|
|
57
|
+
if (response.status >= 400) {
|
|
58
|
+
throw new SlackEgressError(`Slack request failed with status ${response.status}`, `http_${response.status}`);
|
|
59
|
+
}
|
|
60
|
+
return response.data;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
throw toSlackEgressError(error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function createSlackEgress(provider, workspaceId) {
|
|
67
|
+
const resolvedWorkspaceId = assertWorkspaceId(workspaceId);
|
|
68
|
+
let cachedBotUserId;
|
|
69
|
+
let botUserIdLoaded = false;
|
|
70
|
+
const postMessage = async (channel, text, threadTs) => {
|
|
71
|
+
const body = {
|
|
72
|
+
channel,
|
|
73
|
+
text,
|
|
74
|
+
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
75
|
+
};
|
|
76
|
+
const response = await executeSlackRequest(provider, createProxyRequest(resolvedWorkspaceId, "/chat.postMessage", "POST", { data: body }));
|
|
77
|
+
return {
|
|
78
|
+
ok: response.ok === true,
|
|
79
|
+
...(response.ts ? { ts: response.ts } : {}),
|
|
80
|
+
...(response.error ? { error: response.error } : {}),
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
postMessage,
|
|
85
|
+
async postMessageChunked(channel, text, threadTs, maxChars = SLACK_MESSAGE_MAX_CHARS) {
|
|
86
|
+
const effectiveMaxChars = Math.min(Math.max(1, maxChars), SLACK_MESSAGE_MAX_CHARS);
|
|
87
|
+
const chunks = chunkSlackMessage(text, effectiveMaxChars);
|
|
88
|
+
if (!chunks.length) {
|
|
89
|
+
return { ok: false, error: "Cannot post an empty Slack message" };
|
|
90
|
+
}
|
|
91
|
+
let firstTs;
|
|
92
|
+
let replyThreadTs = threadTs;
|
|
93
|
+
for (const chunk of chunks) {
|
|
94
|
+
const result = await postMessage(channel, chunk, replyThreadTs);
|
|
95
|
+
if (!result.ok) {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
...(firstTs ? { ts: firstTs } : {}),
|
|
99
|
+
...(result.error ? { error: result.error } : {}),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
firstTs ??= result.ts;
|
|
103
|
+
replyThreadTs ??= result.ts;
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
ok: true,
|
|
107
|
+
...(firstTs ? { ts: firstTs } : {}),
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
async addReaction(channel, timestamp, emoji) {
|
|
111
|
+
try {
|
|
112
|
+
await executeSlackRequest(provider, createProxyRequest(resolvedWorkspaceId, "/reactions.add", "POST", {
|
|
113
|
+
data: {
|
|
114
|
+
channel,
|
|
115
|
+
timestamp,
|
|
116
|
+
name: emoji,
|
|
117
|
+
},
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Reactions are best-effort and should not block callers.
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
async fetchThreadHistory(channel, threadTs, botUserId) {
|
|
125
|
+
const response = await executeSlackRequest(provider, createProxyRequest(resolvedWorkspaceId, "/conversations.replies", "POST", {
|
|
126
|
+
data: {
|
|
127
|
+
channel,
|
|
128
|
+
ts: threadTs,
|
|
129
|
+
},
|
|
130
|
+
}));
|
|
131
|
+
if (!Array.isArray(response.messages)) {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
return response.messages
|
|
135
|
+
.filter((message) => !message.subtype || message.subtype === "bot_message")
|
|
136
|
+
.sort((left, right) => Number(left.ts ?? "0") - Number(right.ts ?? "0"))
|
|
137
|
+
.map((message) => {
|
|
138
|
+
const content = cleanSlackText(message.text);
|
|
139
|
+
const isAssistant = Boolean(message.bot_id) || (!!botUserId && message.user === botUserId);
|
|
140
|
+
return {
|
|
141
|
+
role: isAssistant ? "assistant" : "user",
|
|
142
|
+
content,
|
|
143
|
+
...(message.ts ? { ts: message.ts } : {}),
|
|
144
|
+
};
|
|
145
|
+
})
|
|
146
|
+
.filter((message) => message.content.length > 0)
|
|
147
|
+
.slice(-20);
|
|
148
|
+
},
|
|
149
|
+
async getBotUserId() {
|
|
150
|
+
if (botUserIdLoaded) {
|
|
151
|
+
return cachedBotUserId;
|
|
152
|
+
}
|
|
153
|
+
const response = await executeSlackRequest(provider, createProxyRequest(resolvedWorkspaceId, "/auth.test", "POST"));
|
|
154
|
+
cachedBotUserId = asString(response.user_id);
|
|
155
|
+
botUserIdLoaded = true;
|
|
156
|
+
return cachedBotUserId;
|
|
157
|
+
},
|
|
158
|
+
async getReactions(channel, timestamp) {
|
|
159
|
+
const response = await executeSlackRequest(provider, createProxyRequest(resolvedWorkspaceId, "/reactions.get", "GET", {
|
|
160
|
+
params: {
|
|
161
|
+
channel,
|
|
162
|
+
timestamp,
|
|
163
|
+
},
|
|
164
|
+
}));
|
|
165
|
+
const reactions = response.message?.reactions;
|
|
166
|
+
if (!Array.isArray(reactions)) {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
return reactions
|
|
170
|
+
.map((reaction) => {
|
|
171
|
+
const name = asString(reaction.name);
|
|
172
|
+
const count = asNumber(reaction.count);
|
|
173
|
+
if (!name || count === undefined) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
return { name, count };
|
|
177
|
+
})
|
|
178
|
+
.filter((reaction) => reaction !== null);
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { normalizeSlackWebhook } from "@relayfile/adapter-slack";
|
|
2
|
+
import type { NormalizedWebhook, SlackEnvelope as AdapterSlackEventEnvelope, SlackEvent as AdapterSlackEvent } from "@relayfile/adapter-slack";
|
|
3
|
+
/**
|
|
4
|
+
* Parse a Slack webhook body that may have been forwarded through Nango.
|
|
5
|
+
*
|
|
6
|
+
* Real-world delivery path in production:
|
|
7
|
+
* Slack → Nango forward webhook → sage (this function)
|
|
8
|
+
*
|
|
9
|
+
* Nango wraps the underlying Slack event in its own envelope:
|
|
10
|
+
* { type: "forward", from: "slack", connectionId, providerConfigKey, payload: {...slack event...} }
|
|
11
|
+
*
|
|
12
|
+
* The raw Slack envelope (what the @relayfile/adapter-slack parser expects) is
|
|
13
|
+
* whatever is inside `.payload`. For direct webhook delivery paths (no Nango
|
|
14
|
+
* in front) the body IS the raw Slack envelope, no unwrap needed.
|
|
15
|
+
*
|
|
16
|
+
* This wrapper detects the envelope shape and hands the adapter parser the
|
|
17
|
+
* unwrapped inner payload in both cases.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseSlackWebhookEnvelope(rawBody: unknown): AdapterSlackEventEnvelope;
|
|
20
|
+
export { normalizeSlackWebhook };
|
|
21
|
+
export type SlackEvent = AdapterSlackEvent;
|
|
22
|
+
export type SlackEventEnvelope = AdapterSlackEventEnvelope;
|
|
23
|
+
export type NormalizedSlackWebhook = Omit<NormalizedWebhook, "payload"> & {
|
|
24
|
+
payload: SlackEventEnvelope;
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=slack-ingress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-ingress.d.ts","sourceRoot":"","sources":["../../src/integrations/slack-ingress.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,qBAAqB,EAEtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,IAAI,yBAAyB,EAC1C,UAAU,IAAI,iBAAiB,EAChC,MAAM,0BAA0B,CAAC;AAElC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,yBAAyB,CAerF;AAED,OAAO,EAAE,qBAAqB,EAAE,CAAC;AAEjC,MAAM,MAAM,UAAU,GAAG,iBAAiB,CAAC;AAC3C,MAAM,MAAM,kBAAkB,GAAG,yBAAyB,CAAC;AAE3D,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,iBAAiB,EAAE,SAAS,CAAC,GAAG;IACxE,OAAO,EAAE,kBAAkB,CAAC;CAC7B,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { parseNangoWebhookPayload, } from "@relayfile/provider-nango";
|
|
2
|
+
import { normalizeSlackWebhook, parseSlackWebhookEnvelope as parseRawSlackWebhookEnvelope, } from "@relayfile/adapter-slack";
|
|
3
|
+
/**
|
|
4
|
+
* Parse a Slack webhook body that may have been forwarded through Nango.
|
|
5
|
+
*
|
|
6
|
+
* Real-world delivery path in production:
|
|
7
|
+
* Slack → Nango forward webhook → sage (this function)
|
|
8
|
+
*
|
|
9
|
+
* Nango wraps the underlying Slack event in its own envelope:
|
|
10
|
+
* { type: "forward", from: "slack", connectionId, providerConfigKey, payload: {...slack event...} }
|
|
11
|
+
*
|
|
12
|
+
* The raw Slack envelope (what the @relayfile/adapter-slack parser expects) is
|
|
13
|
+
* whatever is inside `.payload`. For direct webhook delivery paths (no Nango
|
|
14
|
+
* in front) the body IS the raw Slack envelope, no unwrap needed.
|
|
15
|
+
*
|
|
16
|
+
* This wrapper detects the envelope shape and hands the adapter parser the
|
|
17
|
+
* unwrapped inner payload in both cases.
|
|
18
|
+
*/
|
|
19
|
+
export function parseSlackWebhookEnvelope(rawBody) {
|
|
20
|
+
const envelope = parseNangoWebhookPayload(rawBody);
|
|
21
|
+
if (envelope.type === "forward") {
|
|
22
|
+
const inner = envelope.payload;
|
|
23
|
+
if (!inner || typeof inner !== "object") {
|
|
24
|
+
throw new TypeError("Nango forward webhook payload is missing its inner object — cannot extract Slack envelope");
|
|
25
|
+
}
|
|
26
|
+
return parseRawSlackWebhookEnvelope(inner);
|
|
27
|
+
}
|
|
28
|
+
// Direct webhook delivery — the body itself is already a raw Slack envelope.
|
|
29
|
+
return parseRawSlackWebhookEnvelope(envelope);
|
|
30
|
+
}
|
|
31
|
+
export { normalizeSlackWebhook };
|
package/dist/nango.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
export declare const NANGO_INTEGRATIONS: {
|
|
2
|
-
readonly GITHUB: "github-app-oauth";
|
|
3
|
-
readonly SLACK: "slack";
|
|
4
|
-
};
|
|
5
1
|
export interface ProxyConfig {
|
|
6
2
|
connectionId: string;
|
|
7
3
|
providerConfigKey: string;
|
|
@@ -34,7 +30,6 @@ export declare class NangoError extends Error {
|
|
|
34
30
|
export declare class NangoClient {
|
|
35
31
|
private client;
|
|
36
32
|
private readonly githubOrgDiscoveryCache;
|
|
37
|
-
private readonly slackBotUserIdCache;
|
|
38
33
|
private readonly secretKey;
|
|
39
34
|
constructor({ secretKey }: {
|
|
40
35
|
secretKey: string;
|
|
@@ -42,8 +37,7 @@ export declare class NangoClient {
|
|
|
42
37
|
private getClient;
|
|
43
38
|
proxy<T>(config: ProxyConfig): Promise<T>;
|
|
44
39
|
getConnection(connectionId: string, providerConfigKey: string): Promise<NangoConnection | null>;
|
|
45
|
-
discoverGitHubOrgs(connectionId: string): Promise<GitHubOrgDiscovery>;
|
|
46
|
-
getSlackBotUserId(connectionId: string): Promise<string>;
|
|
40
|
+
discoverGitHubOrgs(connectionId: string, providerConfigKey: string): Promise<GitHubOrgDiscovery>;
|
|
47
41
|
private listGitHubInstallationRepos;
|
|
48
42
|
}
|
|
49
43
|
//# sourceMappingURL=nango.d.ts.map
|
package/dist/nango.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nango.d.ts","sourceRoot":"","sources":["../src/nango.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"nango.d.ts","sourceRoot":"","sources":["../src/nango.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAClE;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAC9B;AA4BD,qBAAa,UAAW,SAAQ,KAAK;aAEjB,SAAS,EAAE,MAAM;aACjB,MAAM,EAAE,MAAM;aACd,KAAK,EAAE,OAAO;gBAFd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO;CAKjC;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAqD;IAC7F,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,EAAE,SAAS,EAAE,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE;IAIhD,OAAO,CAAC,SAAS;IAIX,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;IAQzC,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAQ/F,kBAAkB,CACtB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,kBAAkB,CAAC;YA8DhB,2BAA2B;CAuD1C"}
|