@aprovan/chat-backend 0.1.0-dev.4d82df8 → 0.1.0-dev.879ed82
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/.turbo/turbo-build.log +7 -7
- package/dist/index.d.ts +0 -3
- package/dist/index.js +116 -259
- package/dist/index.js.map +1 -1
- package/dist/lambda.js +116 -259
- package/dist/lambda.js.map +1 -1
- package/package.json +1 -1
- package/src/app.ts +3 -5
- package/src/env.ts +0 -1
- package/src/middleware/workspace.ts +19 -56
- package/src/routes/chat.ts +2 -10
- package/test/app.test.ts +0 -3
- package/test/middleware/workspace.test.ts +45 -66
- package/test/routes/chat.test.ts +0 -49
- package/src/routes/workspaces.ts +0 -89
- package/src/session.ts +0 -80
- package/test/routes/workspaces.test.ts +0 -164
- package/test/session.test.ts +0 -56
package/.turbo/turbo-build.log
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
[34mCLI[39m Target: node20
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
13
|
-
[32mESM[39m [1mdist/lambda.js [22m[
|
|
14
|
-
[32mESM[39m [1mdist/index.js.map [22m[
|
|
15
|
-
[32mESM[39m [1mdist/lambda.js.map [22m[
|
|
16
|
-
[32mESM[39m ⚡️ Build success in
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m33.72 KB[39m
|
|
13
|
+
[32mESM[39m [1mdist/lambda.js [22m[32m33.40 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m61.40 KB[39m
|
|
15
|
+
[32mESM[39m [1mdist/lambda.js.map [22m[32m60.55 KB[39m
|
|
16
|
+
[32mESM[39m ⚡️ Build success in 133ms
|
|
17
17
|
[34mDTS[39m Build start
|
|
18
|
-
[32mDTS[39m ⚡️ Build success in
|
|
19
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m2.
|
|
18
|
+
[32mDTS[39m ⚡️ Build success in 13582ms
|
|
19
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m2.55 KB[39m
|
|
20
20
|
[32mDTS[39m [1mdist/lambda.d.ts [22m[32m171.00 B[39m
|
package/dist/index.d.ts
CHANGED
|
@@ -38,7 +38,6 @@ declare const envSchema: z.ZodObject<{
|
|
|
38
38
|
AWS_REGION: z.ZodDefault<z.ZodString>;
|
|
39
39
|
WORKSPACE_TABLE_NAME: z.ZodString;
|
|
40
40
|
MEMBERSHIPS_TABLE_NAME: z.ZodString;
|
|
41
|
-
USER_SESSIONS_TABLE_NAME: z.ZodString;
|
|
42
41
|
OPENROUTER_SECRET_ARN: z.ZodString;
|
|
43
42
|
GATEWAY_URL: z.ZodString;
|
|
44
43
|
POSTHOG_PROJECT_API_KEY: z.ZodOptional<z.ZodString>;
|
|
@@ -52,7 +51,6 @@ declare const envSchema: z.ZodObject<{
|
|
|
52
51
|
AWS_REGION: string;
|
|
53
52
|
WORKSPACE_TABLE_NAME: string;
|
|
54
53
|
MEMBERSHIPS_TABLE_NAME: string;
|
|
55
|
-
USER_SESSIONS_TABLE_NAME: string;
|
|
56
54
|
OPENROUTER_SECRET_ARN: string;
|
|
57
55
|
GATEWAY_URL: string;
|
|
58
56
|
POSTHOG_HOST: string;
|
|
@@ -63,7 +61,6 @@ declare const envSchema: z.ZodObject<{
|
|
|
63
61
|
COGNITO_CLIENT_ID: string;
|
|
64
62
|
WORKSPACE_TABLE_NAME: string;
|
|
65
63
|
MEMBERSHIPS_TABLE_NAME: string;
|
|
66
|
-
USER_SESSIONS_TABLE_NAME: string;
|
|
67
64
|
OPENROUTER_SECRET_ARN: string;
|
|
68
65
|
GATEWAY_URL: string;
|
|
69
66
|
NODE_ENV?: "development" | "production" | "test" | undefined;
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { serve } from "@hono/node-server";
|
|
3
3
|
|
|
4
4
|
// src/app.ts
|
|
5
|
-
import { Hono as
|
|
5
|
+
import { Hono as Hono6 } from "hono";
|
|
6
6
|
|
|
7
7
|
// src/middleware/auth.ts
|
|
8
8
|
import { CognitoJwtVerifier } from "aws-jwt-verify";
|
|
@@ -32,11 +32,11 @@ var authMiddleware = async (c, next) => {
|
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
// src/middleware/
|
|
35
|
+
// src/middleware/workspace.ts
|
|
36
36
|
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
37
|
-
import { DynamoDBDocumentClient,
|
|
38
|
-
var
|
|
39
|
-
var
|
|
37
|
+
import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb";
|
|
38
|
+
var MEMBERSHIP_CACHE_TTL_MS = 3e5;
|
|
39
|
+
var membershipCache = /* @__PURE__ */ new Map();
|
|
40
40
|
var ddbClient = null;
|
|
41
41
|
function getDdb() {
|
|
42
42
|
if (!ddbClient) {
|
|
@@ -46,13 +46,57 @@ function getDdb() {
|
|
|
46
46
|
}
|
|
47
47
|
return ddbClient;
|
|
48
48
|
}
|
|
49
|
+
async function resolveWorkspaceId(userSub) {
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
const cached = membershipCache.get(userSub);
|
|
52
|
+
if (cached && now - cached.fetchedAt < MEMBERSHIP_CACHE_TTL_MS) {
|
|
53
|
+
return cached.workspaceId;
|
|
54
|
+
}
|
|
55
|
+
const result = await getDdb().send(
|
|
56
|
+
new QueryCommand({
|
|
57
|
+
TableName: process.env["MEMBERSHIPS_TABLE_NAME"],
|
|
58
|
+
IndexName: "ByUserSub",
|
|
59
|
+
KeyConditionExpression: "userSub = :sub",
|
|
60
|
+
ExpressionAttributeValues: { ":sub": userSub },
|
|
61
|
+
Limit: 1
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
const item = result.Items?.[0];
|
|
65
|
+
if (!item) return null;
|
|
66
|
+
membershipCache.set(userSub, { workspaceId: item.workspaceId, fetchedAt: now });
|
|
67
|
+
return item.workspaceId;
|
|
68
|
+
}
|
|
69
|
+
var workspaceMiddleware = async (c, next) => {
|
|
70
|
+
const claims = c.get("claims");
|
|
71
|
+
const workspaceId = await resolveWorkspaceId(claims.sub);
|
|
72
|
+
if (!workspaceId) {
|
|
73
|
+
return c.json({ error: "No workspace membership" }, 403);
|
|
74
|
+
}
|
|
75
|
+
c.set("workspaceId", workspaceId);
|
|
76
|
+
return next();
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// src/middleware/plan.ts
|
|
80
|
+
import { DynamoDBClient as DynamoDBClient2 } from "@aws-sdk/client-dynamodb";
|
|
81
|
+
import { DynamoDBDocumentClient as DynamoDBDocumentClient2, GetCommand } from "@aws-sdk/lib-dynamodb";
|
|
82
|
+
var WORKSPACE_CACHE_TTL_MS = 6e4;
|
|
83
|
+
var workspaceCache = /* @__PURE__ */ new Map();
|
|
84
|
+
var ddbClient2 = null;
|
|
85
|
+
function getDdb2() {
|
|
86
|
+
if (!ddbClient2) {
|
|
87
|
+
ddbClient2 = DynamoDBDocumentClient2.from(
|
|
88
|
+
new DynamoDBClient2({ region: process.env["AWS_REGION"] })
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return ddbClient2;
|
|
92
|
+
}
|
|
49
93
|
async function getWorkspace(workspaceId) {
|
|
50
94
|
const now = Date.now();
|
|
51
95
|
const cached = workspaceCache.get(workspaceId);
|
|
52
96
|
if (cached && now - cached.fetchedAt < WORKSPACE_CACHE_TTL_MS) {
|
|
53
97
|
return cached.workspace;
|
|
54
98
|
}
|
|
55
|
-
const result = await
|
|
99
|
+
const result = await getDdb2().send(
|
|
56
100
|
new GetCommand({
|
|
57
101
|
TableName: process.env["WORKSPACE_TABLE_NAME"],
|
|
58
102
|
Key: { workspaceId }
|
|
@@ -79,116 +123,10 @@ var planMiddleware = async (c, next) => {
|
|
|
79
123
|
return next();
|
|
80
124
|
};
|
|
81
125
|
|
|
82
|
-
// src/
|
|
83
|
-
import {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// src/session.ts
|
|
87
|
-
import { DynamoDBClient as DynamoDBClient2 } from "@aws-sdk/client-dynamodb";
|
|
88
|
-
import {
|
|
89
|
-
DynamoDBDocumentClient as DynamoDBDocumentClient2,
|
|
90
|
-
GetCommand as GetCommand2,
|
|
91
|
-
PutCommand
|
|
92
|
-
} from "@aws-sdk/lib-dynamodb";
|
|
93
|
-
var SESSION_CACHE_TTL_MS = 15e3;
|
|
94
|
-
var sessionCache = /* @__PURE__ */ new Map();
|
|
95
|
-
var ddbClient2 = null;
|
|
96
|
-
function getDdb2() {
|
|
97
|
-
if (!ddbClient2) {
|
|
98
|
-
ddbClient2 = DynamoDBDocumentClient2.from(
|
|
99
|
-
new DynamoDBClient2({ region: process.env["AWS_REGION"] })
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
return ddbClient2;
|
|
103
|
-
}
|
|
104
|
-
function tableName() {
|
|
105
|
-
return process.env["USER_SESSIONS_TABLE_NAME"];
|
|
106
|
-
}
|
|
107
|
-
async function getSessionWorkspaceId(userSub) {
|
|
108
|
-
const now = Date.now();
|
|
109
|
-
const cached = sessionCache.get(userSub);
|
|
110
|
-
if (cached && now - cached.fetchedAt < SESSION_CACHE_TTL_MS) {
|
|
111
|
-
return cached.activeWorkspaceId;
|
|
112
|
-
}
|
|
113
|
-
const result = await getDdb2().send(
|
|
114
|
-
new GetCommand2({
|
|
115
|
-
TableName: tableName(),
|
|
116
|
-
Key: { userSub }
|
|
117
|
-
})
|
|
118
|
-
);
|
|
119
|
-
const activeWorkspaceId = result.Item?.activeWorkspaceId ?? null;
|
|
120
|
-
sessionCache.set(userSub, { activeWorkspaceId, fetchedAt: now });
|
|
121
|
-
return activeWorkspaceId;
|
|
122
|
-
}
|
|
123
|
-
async function setSessionWorkspaceId(userSub, workspaceId) {
|
|
124
|
-
await getDdb2().send(
|
|
125
|
-
new PutCommand({
|
|
126
|
-
TableName: tableName(),
|
|
127
|
-
Item: {
|
|
128
|
-
userSub,
|
|
129
|
-
activeWorkspaceId: workspaceId,
|
|
130
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
131
|
-
}
|
|
132
|
-
})
|
|
133
|
-
);
|
|
134
|
-
sessionCache.set(userSub, {
|
|
135
|
-
activeWorkspaceId: workspaceId,
|
|
136
|
-
fetchedAt: Date.now()
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// src/middleware/workspace.ts
|
|
141
|
-
var MEMBERSHIP_CACHE_TTL_MS = 3e5;
|
|
142
|
-
var membershipCache = /* @__PURE__ */ new Map();
|
|
143
|
-
var ddbClient3 = null;
|
|
144
|
-
function getDdb3() {
|
|
145
|
-
if (!ddbClient3) {
|
|
146
|
-
ddbClient3 = DynamoDBDocumentClient3.from(
|
|
147
|
-
new DynamoDBClient3({ region: process.env["AWS_REGION"] })
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
return ddbClient3;
|
|
151
|
-
}
|
|
152
|
-
async function listWorkspaceMemberships(userSub) {
|
|
153
|
-
const now = Date.now();
|
|
154
|
-
const cached = membershipCache.get(userSub);
|
|
155
|
-
if (cached && now - cached.fetchedAt < MEMBERSHIP_CACHE_TTL_MS) {
|
|
156
|
-
return cached.workspaceIds;
|
|
157
|
-
}
|
|
158
|
-
const result = await getDdb3().send(
|
|
159
|
-
new QueryCommand({
|
|
160
|
-
TableName: process.env["MEMBERSHIPS_TABLE_NAME"],
|
|
161
|
-
IndexName: "ByUserSub",
|
|
162
|
-
KeyConditionExpression: "userSub = :sub",
|
|
163
|
-
ExpressionAttributeValues: { ":sub": userSub }
|
|
164
|
-
})
|
|
165
|
-
);
|
|
166
|
-
const workspaceIds = (result.Items ?? []).map(
|
|
167
|
-
(item) => item.workspaceId
|
|
168
|
-
);
|
|
169
|
-
membershipCache.set(userSub, { workspaceIds, fetchedAt: now });
|
|
170
|
-
return workspaceIds;
|
|
171
|
-
}
|
|
172
|
-
async function resolveWorkspaceId(userSub) {
|
|
173
|
-
const [sessionWsId, workspaceIds] = await Promise.all([
|
|
174
|
-
getSessionWorkspaceId(userSub),
|
|
175
|
-
listWorkspaceMemberships(userSub)
|
|
176
|
-
]);
|
|
177
|
-
if (workspaceIds.length === 0) return null;
|
|
178
|
-
if (sessionWsId && workspaceIds.includes(sessionWsId)) {
|
|
179
|
-
return sessionWsId;
|
|
180
|
-
}
|
|
181
|
-
return workspaceIds[0] ?? null;
|
|
182
|
-
}
|
|
183
|
-
var workspaceMiddleware = async (c, next) => {
|
|
184
|
-
const claims = c.get("claims");
|
|
185
|
-
const workspaceId = await resolveWorkspaceId(claims.sub);
|
|
186
|
-
if (!workspaceId) {
|
|
187
|
-
return c.json({ error: "No workspace membership" }, 403);
|
|
188
|
-
}
|
|
189
|
-
c.set("workspaceId", workspaceId);
|
|
190
|
-
return next();
|
|
191
|
-
};
|
|
126
|
+
// src/routes/health.ts
|
|
127
|
+
import { Hono } from "hono";
|
|
128
|
+
var health = new Hono();
|
|
129
|
+
health.get("/health", (c) => c.json({ status: "ok" }));
|
|
192
130
|
|
|
193
131
|
// src/routes/chat.ts
|
|
194
132
|
import { withTracing } from "@posthog/ai";
|
|
@@ -199,7 +137,7 @@ import {
|
|
|
199
137
|
stepCountIs,
|
|
200
138
|
jsonSchema
|
|
201
139
|
} from "ai";
|
|
202
|
-
import { Hono } from "hono";
|
|
140
|
+
import { Hono as Hono2 } from "hono";
|
|
203
141
|
import { z } from "zod";
|
|
204
142
|
|
|
205
143
|
// src/fallback-prompts.ts
|
|
@@ -553,7 +491,7 @@ var CHAT_PROMPT_ALLOWLIST = /* @__PURE__ */ new Set(["chat-patchwork-widget", "c
|
|
|
553
491
|
var EDIT_PROMPT_ID = "edit-patchwork-widget";
|
|
554
492
|
|
|
555
493
|
// src/gateway-session.ts
|
|
556
|
-
var
|
|
494
|
+
var sessionCache = /* @__PURE__ */ new Map();
|
|
557
495
|
var toolsCache = /* @__PURE__ */ new Map();
|
|
558
496
|
function getCachedTools(sub) {
|
|
559
497
|
return toolsCache.get(sub);
|
|
@@ -569,12 +507,12 @@ function gatewayUrl() {
|
|
|
569
507
|
async function getGatewaySession(claims, workspaceId, cognitoToken) {
|
|
570
508
|
const sub = claims.sub;
|
|
571
509
|
const now = Math.floor(Date.now() / 1e3);
|
|
572
|
-
const cached =
|
|
510
|
+
const cached = sessionCache.get(sub);
|
|
573
511
|
if (cached && cached.expires_at > now + 60) {
|
|
574
512
|
return cached;
|
|
575
513
|
}
|
|
576
514
|
const entry = await exchangeSession(cognitoToken, workspaceId, claims.exp);
|
|
577
|
-
|
|
515
|
+
sessionCache.set(sub, entry);
|
|
578
516
|
return entry;
|
|
579
517
|
}
|
|
580
518
|
async function exchangeSession(cognitoToken, workspaceId, exp) {
|
|
@@ -616,7 +554,7 @@ async function callAuthSessions(cognitoToken, workspaceId) {
|
|
|
616
554
|
});
|
|
617
555
|
}
|
|
618
556
|
function evictGatewaySession(sub) {
|
|
619
|
-
|
|
557
|
+
sessionCache.delete(sub);
|
|
620
558
|
toolsCache.delete(sub);
|
|
621
559
|
}
|
|
622
560
|
var GatewaySessionError = class extends Error {
|
|
@@ -758,7 +696,6 @@ var chatBodySchema = z.object({
|
|
|
758
696
|
id: z.string(),
|
|
759
697
|
messages: z.array(z.any()),
|
|
760
698
|
trigger: z.string(),
|
|
761
|
-
model: z.string().optional(),
|
|
762
699
|
metadata: z.unknown().optional(),
|
|
763
700
|
prompt: z.object({
|
|
764
701
|
id: z.string(),
|
|
@@ -778,7 +715,7 @@ var retryAtStartMiddleware = {
|
|
|
778
715
|
}
|
|
779
716
|
}
|
|
780
717
|
};
|
|
781
|
-
var chatRoute = new
|
|
718
|
+
var chatRoute = new Hono2();
|
|
782
719
|
async function fetchGatewayTools(gatewayUrl2, bearerToken) {
|
|
783
720
|
const res = await fetch(`${gatewayUrl2}/tools`, {
|
|
784
721
|
headers: { Authorization: `Bearer ${bearerToken}` }
|
|
@@ -824,16 +761,10 @@ chatRoute.post("/", async (c) => {
|
|
|
824
761
|
if (!parsed.success) {
|
|
825
762
|
return c.json({ error: "Invalid request body" }, 400);
|
|
826
763
|
}
|
|
827
|
-
const { messages,
|
|
764
|
+
const { messages, prompt: promptBody } = parsed.data;
|
|
828
765
|
const claims = c.get("claims");
|
|
829
766
|
const workspaceId = c.get("workspaceId");
|
|
830
767
|
const workspace = c.get("workspace");
|
|
831
|
-
if (requestedModel !== void 0 && !workspace.limits.maxModels.includes(requestedModel)) {
|
|
832
|
-
return c.json(
|
|
833
|
-
{ error: "Model not allowed on your current plan", model: requestedModel },
|
|
834
|
-
403
|
|
835
|
-
);
|
|
836
|
-
}
|
|
837
768
|
const promptId = promptBody?.id ?? "chat-patchwork-widget";
|
|
838
769
|
if (!CHAT_PROMPT_ALLOWLIST.has(promptId)) {
|
|
839
770
|
return c.json({ error: "Unknown prompt id" }, 400);
|
|
@@ -878,7 +809,7 @@ chatRoute.post("/", async (c) => {
|
|
|
878
809
|
});
|
|
879
810
|
const apiKey = await getOpenRouterKey();
|
|
880
811
|
const provider = createOpenRouterProvider(apiKey);
|
|
881
|
-
const modelId =
|
|
812
|
+
const modelId = workspace.limits.maxModels[0] ?? "openrouter/auto";
|
|
882
813
|
const baseModel = provider(modelId);
|
|
883
814
|
const phClient = getPostHogClient();
|
|
884
815
|
const tracedModel = phClient && promptResult.source !== "code_fallback" ? withTracing(baseModel, phClient, {
|
|
@@ -906,14 +837,14 @@ chatRoute.post("/", async (c) => {
|
|
|
906
837
|
// src/routes/edit.ts
|
|
907
838
|
import { withTracing as withTracing2 } from "@posthog/ai";
|
|
908
839
|
import { streamText as streamText2 } from "ai";
|
|
909
|
-
import { Hono as
|
|
840
|
+
import { Hono as Hono3 } from "hono";
|
|
910
841
|
import { z as z2 } from "zod";
|
|
911
842
|
var editBodySchema = z2.object({
|
|
912
843
|
code: z2.string(),
|
|
913
844
|
prompt: z2.string()
|
|
914
845
|
});
|
|
915
846
|
var MODEL_ID = "openrouter/auto";
|
|
916
|
-
var editRoute = new
|
|
847
|
+
var editRoute = new Hono3();
|
|
917
848
|
editRoute.post("/", async (c) => {
|
|
918
849
|
const body = await c.req.json().catch(() => null);
|
|
919
850
|
const parsed = editBodySchema.safeParse(body);
|
|
@@ -943,19 +874,12 @@ editRoute.post("/", async (c) => {
|
|
|
943
874
|
return result.toTextStreamResponse();
|
|
944
875
|
});
|
|
945
876
|
|
|
946
|
-
// src/routes/
|
|
947
|
-
import { Hono as Hono3 } from "hono";
|
|
948
|
-
var health = new Hono3();
|
|
949
|
-
health.get("/health", (c) => c.json({ status: "ok" }));
|
|
950
|
-
|
|
951
|
-
// src/routes/proxy.ts
|
|
877
|
+
// src/routes/services.ts
|
|
952
878
|
import { Hono as Hono4 } from "hono";
|
|
953
|
-
var
|
|
954
|
-
|
|
879
|
+
var services = new Hono4();
|
|
880
|
+
services.get("/", async (c) => {
|
|
955
881
|
const claims = c.get("claims");
|
|
956
882
|
const workspaceId = c.get("workspaceId");
|
|
957
|
-
const ns = c.req.param("ns");
|
|
958
|
-
const proc = c.req.param("proc");
|
|
959
883
|
const authHeader = c.req.header("Authorization");
|
|
960
884
|
const cognitoToken = authHeader.slice("Bearer ".length);
|
|
961
885
|
let sessionToken;
|
|
@@ -968,34 +892,37 @@ proxy.post("/:ns/:proc{.*}", async (c) => {
|
|
|
968
892
|
}
|
|
969
893
|
return c.json({ error: "Failed to connect to gateway" }, 502);
|
|
970
894
|
}
|
|
971
|
-
let body = {};
|
|
972
|
-
try {
|
|
973
|
-
body = await c.req.json();
|
|
974
|
-
} catch {
|
|
975
|
-
}
|
|
976
895
|
const gatewayUrl2 = process.env["GATEWAY_URL"].replace(/\/$/, "");
|
|
977
|
-
const res = await fetch(`${gatewayUrl2}/tools
|
|
978
|
-
|
|
979
|
-
headers: {
|
|
980
|
-
"Content-Type": "application/json",
|
|
981
|
-
Authorization: `Bearer ${sessionToken}`
|
|
982
|
-
},
|
|
983
|
-
body: JSON.stringify(body)
|
|
896
|
+
const res = await fetch(`${gatewayUrl2}/tools`, {
|
|
897
|
+
headers: { Authorization: `Bearer ${sessionToken}` }
|
|
984
898
|
});
|
|
985
899
|
if (res.status === 401) {
|
|
986
900
|
evictGatewaySession(claims.sub);
|
|
987
901
|
return c.json({ error: "Gateway authentication failed" }, 502);
|
|
988
902
|
}
|
|
989
|
-
|
|
990
|
-
|
|
903
|
+
if (!res.ok) {
|
|
904
|
+
return c.json({ error: "Gateway tools fetch failed" }, 502);
|
|
905
|
+
}
|
|
906
|
+
const data = await res.json();
|
|
907
|
+
const serviceList = data.tools.map((t) => ({
|
|
908
|
+
namespace: t.provider,
|
|
909
|
+
name: t.name,
|
|
910
|
+
procedure: t.operation,
|
|
911
|
+
description: t.description ?? "",
|
|
912
|
+
parameters: t.inputSchema
|
|
913
|
+
}));
|
|
914
|
+
const namespaces = Array.from(new Set(data.tools.map((t) => t.provider)));
|
|
915
|
+
return c.json({ namespaces, services: serviceList });
|
|
991
916
|
});
|
|
992
917
|
|
|
993
|
-
// src/routes/
|
|
918
|
+
// src/routes/proxy.ts
|
|
994
919
|
import { Hono as Hono5 } from "hono";
|
|
995
|
-
var
|
|
996
|
-
|
|
920
|
+
var proxy = new Hono5();
|
|
921
|
+
proxy.post("/:ns/:proc{.*}", async (c) => {
|
|
997
922
|
const claims = c.get("claims");
|
|
998
923
|
const workspaceId = c.get("workspaceId");
|
|
924
|
+
const ns = c.req.param("ns");
|
|
925
|
+
const proc = c.req.param("proc");
|
|
999
926
|
const authHeader = c.req.header("Authorization");
|
|
1000
927
|
const cognitoToken = authHeader.slice("Bearer ".length);
|
|
1001
928
|
let sessionToken;
|
|
@@ -1008,99 +935,31 @@ services.get("/", async (c) => {
|
|
|
1008
935
|
}
|
|
1009
936
|
return c.json({ error: "Failed to connect to gateway" }, 502);
|
|
1010
937
|
}
|
|
938
|
+
let body = {};
|
|
939
|
+
try {
|
|
940
|
+
body = await c.req.json();
|
|
941
|
+
} catch {
|
|
942
|
+
}
|
|
1011
943
|
const gatewayUrl2 = process.env["GATEWAY_URL"].replace(/\/$/, "");
|
|
1012
|
-
const res = await fetch(`${gatewayUrl2}/tools`, {
|
|
1013
|
-
|
|
944
|
+
const res = await fetch(`${gatewayUrl2}/tools/${ns}/${proc}`, {
|
|
945
|
+
method: "POST",
|
|
946
|
+
headers: {
|
|
947
|
+
"Content-Type": "application/json",
|
|
948
|
+
Authorization: `Bearer ${sessionToken}`
|
|
949
|
+
},
|
|
950
|
+
body: JSON.stringify(body)
|
|
1014
951
|
});
|
|
1015
952
|
if (res.status === 401) {
|
|
1016
953
|
evictGatewaySession(claims.sub);
|
|
1017
954
|
return c.json({ error: "Gateway authentication failed" }, 502);
|
|
1018
955
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
}
|
|
1022
|
-
const data = await res.json();
|
|
1023
|
-
const serviceList = data.tools.map((t) => ({
|
|
1024
|
-
namespace: t.provider,
|
|
1025
|
-
name: t.name,
|
|
1026
|
-
procedure: t.operation,
|
|
1027
|
-
description: t.description ?? "",
|
|
1028
|
-
parameters: t.inputSchema
|
|
1029
|
-
}));
|
|
1030
|
-
const namespaces = Array.from(new Set(data.tools.map((t) => t.provider)));
|
|
1031
|
-
return c.json({ namespaces, services: serviceList });
|
|
1032
|
-
});
|
|
1033
|
-
|
|
1034
|
-
// src/routes/workspaces.ts
|
|
1035
|
-
import { DynamoDBClient as DynamoDBClient4 } from "@aws-sdk/client-dynamodb";
|
|
1036
|
-
import { BatchGetCommand, DynamoDBDocumentClient as DynamoDBDocumentClient4 } from "@aws-sdk/lib-dynamodb";
|
|
1037
|
-
import { zValidator } from "@hono/zod-validator";
|
|
1038
|
-
import { Hono as Hono6 } from "hono";
|
|
1039
|
-
import { z as z3 } from "zod";
|
|
1040
|
-
var ddbClient4 = null;
|
|
1041
|
-
function getDdb4() {
|
|
1042
|
-
if (!ddbClient4) {
|
|
1043
|
-
ddbClient4 = DynamoDBDocumentClient4.from(
|
|
1044
|
-
new DynamoDBClient4({ region: process.env["AWS_REGION"] })
|
|
1045
|
-
);
|
|
1046
|
-
}
|
|
1047
|
-
return ddbClient4;
|
|
1048
|
-
}
|
|
1049
|
-
async function batchGetWorkspaces(workspaceIds) {
|
|
1050
|
-
if (workspaceIds.length === 0) return [];
|
|
1051
|
-
const tableName2 = process.env["WORKSPACE_TABLE_NAME"];
|
|
1052
|
-
const result = await getDdb4().send(
|
|
1053
|
-
new BatchGetCommand({
|
|
1054
|
-
RequestItems: {
|
|
1055
|
-
[tableName2]: { Keys: workspaceIds.map((id) => ({ workspaceId: id })) }
|
|
1056
|
-
}
|
|
1057
|
-
})
|
|
1058
|
-
);
|
|
1059
|
-
return result.Responses?.[tableName2] ?? [];
|
|
1060
|
-
}
|
|
1061
|
-
var workspacesRoute = new Hono6();
|
|
1062
|
-
workspacesRoute.get("/", async (c) => {
|
|
1063
|
-
const claims = c.get("claims");
|
|
1064
|
-
const userSub = claims.sub;
|
|
1065
|
-
const [workspaceIds, activeWorkspaceId] = await Promise.all([
|
|
1066
|
-
listWorkspaceMemberships(userSub),
|
|
1067
|
-
getSessionWorkspaceId(userSub)
|
|
1068
|
-
]);
|
|
1069
|
-
const workspaces = await batchGetWorkspaces(workspaceIds);
|
|
1070
|
-
const effectiveActiveId = activeWorkspaceId && workspaceIds.includes(activeWorkspaceId) ? activeWorkspaceId : workspaceIds[0] ?? null;
|
|
1071
|
-
const sorted = workspaceIds.map((id) => workspaces.find((w) => w.workspaceId === id)).filter((w) => w !== void 0);
|
|
1072
|
-
return c.json({
|
|
1073
|
-
workspaces: sorted.map((w) => ({
|
|
1074
|
-
workspaceId: w.workspaceId,
|
|
1075
|
-
name: w.name,
|
|
1076
|
-
plan: w.plan,
|
|
1077
|
-
active: w.workspaceId === effectiveActiveId
|
|
1078
|
-
})),
|
|
1079
|
-
activeWorkspaceId: effectiveActiveId
|
|
1080
|
-
});
|
|
1081
|
-
});
|
|
1082
|
-
var setActiveSchema = z3.object({
|
|
1083
|
-
workspaceId: z3.string().min(1)
|
|
956
|
+
const responseData = await res.json();
|
|
957
|
+
return c.json(responseData, res.status);
|
|
1084
958
|
});
|
|
1085
|
-
workspacesRoute.put(
|
|
1086
|
-
"/active",
|
|
1087
|
-
zValidator("json", setActiveSchema),
|
|
1088
|
-
async (c) => {
|
|
1089
|
-
const claims = c.get("claims");
|
|
1090
|
-
const userSub = claims.sub;
|
|
1091
|
-
const { workspaceId } = c.req.valid("json");
|
|
1092
|
-
const workspaceIds = await listWorkspaceMemberships(userSub);
|
|
1093
|
-
if (!workspaceIds.includes(workspaceId)) {
|
|
1094
|
-
return c.json({ error: "Not a member of this workspace" }, 403);
|
|
1095
|
-
}
|
|
1096
|
-
await setSessionWorkspaceId(userSub, workspaceId);
|
|
1097
|
-
return c.json({ activeWorkspaceId: workspaceId });
|
|
1098
|
-
}
|
|
1099
|
-
);
|
|
1100
959
|
|
|
1101
960
|
// src/app.ts
|
|
1102
961
|
function createChatApp() {
|
|
1103
|
-
const app2 = new
|
|
962
|
+
const app2 = new Hono6();
|
|
1104
963
|
app2.route("/", health);
|
|
1105
964
|
const api = app2.basePath("/api");
|
|
1106
965
|
api.use(authMiddleware, workspaceMiddleware, planMiddleware);
|
|
@@ -1108,27 +967,25 @@ function createChatApp() {
|
|
|
1108
967
|
api.route("/edit", editRoute);
|
|
1109
968
|
api.route("/services", services);
|
|
1110
969
|
api.route("/proxy", proxy);
|
|
1111
|
-
api.route("/workspaces", workspacesRoute);
|
|
1112
970
|
return app2;
|
|
1113
971
|
}
|
|
1114
972
|
|
|
1115
973
|
// src/env.ts
|
|
1116
|
-
import { z as
|
|
1117
|
-
var envSchema =
|
|
1118
|
-
NODE_ENV:
|
|
1119
|
-
PORT:
|
|
1120
|
-
COGNITO_USER_POOL_ID:
|
|
1121
|
-
COGNITO_CLIENT_ID:
|
|
1122
|
-
AWS_REGION:
|
|
1123
|
-
WORKSPACE_TABLE_NAME:
|
|
1124
|
-
MEMBERSHIPS_TABLE_NAME:
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
GATEWAY_URL: z4.string().url(),
|
|
974
|
+
import { z as z3 } from "zod";
|
|
975
|
+
var envSchema = z3.object({
|
|
976
|
+
NODE_ENV: z3.enum(["development", "production", "test"]).default("development"),
|
|
977
|
+
PORT: z3.coerce.number().default(3001),
|
|
978
|
+
COGNITO_USER_POOL_ID: z3.string().min(1),
|
|
979
|
+
COGNITO_CLIENT_ID: z3.string().min(1),
|
|
980
|
+
AWS_REGION: z3.string().default("us-east-1"),
|
|
981
|
+
WORKSPACE_TABLE_NAME: z3.string().min(1),
|
|
982
|
+
MEMBERSHIPS_TABLE_NAME: z3.string().min(1),
|
|
983
|
+
OPENROUTER_SECRET_ARN: z3.string().min(1),
|
|
984
|
+
GATEWAY_URL: z3.string().url(),
|
|
1128
985
|
// PostHog prompt management (optional — falls back to code prompts when absent)
|
|
1129
|
-
POSTHOG_PROJECT_API_KEY:
|
|
1130
|
-
POSTHOG_PERSONAL_API_KEY:
|
|
1131
|
-
POSTHOG_HOST:
|
|
986
|
+
POSTHOG_PROJECT_API_KEY: z3.string().optional(),
|
|
987
|
+
POSTHOG_PERSONAL_API_KEY: z3.string().optional(),
|
|
988
|
+
POSTHOG_HOST: z3.string().default("https://us.posthog.com")
|
|
1132
989
|
});
|
|
1133
990
|
function parseEnv(raw) {
|
|
1134
991
|
return envSchema.parse(raw);
|