Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion apps/agents/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"license": "MIT",
"private": true,
"scripts": {
"dev": "npx @langchain/langgraph-cli dev --port 54367 --config ../../langgraph.json",
"dev": "langgraphjs dev --port 54367 --config ../../langgraph.json --no-browser",
"build": "yarn clean && tsc",
"clean": "rm -rf ./dist .turbo || true",
"format": "prettier --config .prettierrc --write \"src\"",
Expand Down Expand Up @@ -41,6 +41,7 @@
"exa-js": "^1.4.10",
"framer-motion": "^11.11.9",
"groq-sdk": "^0.13.0",
"hono": "^4.7.6",
"langchain": "^0.3.14",
"langsmith": "^0.3.3",
"lodash": "^4.17.21",
Expand All @@ -52,6 +53,8 @@
"@eslint/js": "^9.12.0",
"@types/eslint__js": "^8.42.3",
"@types/lodash": "^4.17.12",
"@langchain/langgraph-api": "http://localhost:3123/18/@langchain/langgraph-api",
"@langchain/langgraph-cli": "http://localhost:3123/18/@langchain/langgraph-cli",
"@types/node": "^20",
"@types/pdf-parse": "^1.1.4",
"@types/uuid": "^10.0.0",
Expand All @@ -65,5 +68,8 @@
"typescript": "^5",
"typescript-eslint": "^8.8.1",
"vitest": "^3.0.4"
},
"resolutions": {
"@langchain/langgraph-sdk": "http://localhost:3123/18/@langchain/langgraph-sdk"
}
}
118 changes: 118 additions & 0 deletions apps/agents/src/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";
import { CONTEXT_DOCUMENTS_NAMESPACE } from "@opencanvas/shared/constants";
import { createClient, UserResponse } from "@supabase/supabase-js";

const USER_PERMISSIONS = [
"threads:create",
"threads:read",
"threads:update",
"threads:delete",
"threads:search",
"threads:create_run",
"assistants:create",
"assistants:read",
"assistants:update",
"assistants:delete",
"assistants:search",
"assistants:write",
"store:put",
"store:get",
"store:search",
"store:list_namespaces",
"store:delete",
];

const isContextDocumentNamespace = (namespace: string[]): boolean => {
return namespace.includes(CONTEXT_DOCUMENTS_NAMESPACE[0]);
};

export const auth = new Auth()
.authenticate(async (request) => {
const authorization = request.headers.get("Authorization");

const exc = new HTTPException(401, {
message: "Could not validate credentials",
headers: { "WWW-Authenticate": "Bearer" },
});

if (!authorization?.toLocaleLowerCase().startsWith("bearer ")) {
console.log("authorization could not verify", authorization);
throw exc;
}

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE;
if (!supabaseUrl || !supabaseKey) {
throw new HTTPException(555, {
message:
"SUPABASE_URL or SUPABASE_SERVICE_ROLE environment variables are not set",
});
}
const supabaseClient = createClient(supabaseUrl, supabaseKey);

let authRes: UserResponse | undefined;

try {
authRes = await supabaseClient.auth.getUser(authorization?.split(" ")[1]);
} catch (error) {
throw new HTTPException(401, {
message: "Failed to verify JWT token",
cause: error,
});
}

if (!authRes.data.user) throw exc;

const user = authRes.data.user;
if (!user) throw exc;

return {
permissions: USER_PERMISSIONS,
is_authenticated: true,
display_name: user.user_metadata.display_name,
identity: user.id,
role: "user",
};
})
.on("*", ({ permissions }) => {
if (!permissions?.length) {
throw new HTTPException(403, { message: "Not authorized" });
}
})
.on("assistants:create", ({ value, user, permissions }) => {
if (!permissions?.includes("assistants:write")) {
throw new HTTPException(403, { message: "Not authorized" });
}

value.metadata ??= {};
value.metadata["user_id"] = user.identity;
})
.on("assistants:search", ({ user }) => {
return { user_id: user.identity };
})
.on(["threads", "assistants"], ({ action, value, user }) => {
const filters = { user_id: user.identity };
if (
action === "threads:create_run" ||
action === "threads:update" ||
action === "threads:create" ||
action === "assistants:create" ||
action === "assistants:update"
) {
value.metadata ??= {};
value.metadata["user_id"] = user.identity;
}
return filters;
})
.on("store", ({ value, user }) => {
const identity = user.identity;
// Throw an error if their identity is undefined, or if the namespace does not include their identity and is not a context document namespace
// this is due to the legacy namespacing of the context documents which do not include the user identity
if (
!identity ||
(!value.namespace?.includes(identity) &&
!isContextDocumentNamespace(value.namespace ?? []))
) {
throw new HTTPException(403, { message: "Not authorized" });
}
});
53 changes: 14 additions & 39 deletions apps/agents/src/open-canvas/nodes/customAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,8 @@ import {
ArtifactCodeV3,
ArtifactMarkdownV3,
ArtifactV3,
CustomQuickAction,
Reflections,
} from "@opencanvas/shared/types";
import {
ensureStoreInConfig,
formatReflections,
getModelFromConfig,
} from "../../utils.js";
import { getModelFromConfig } from "../../utils.js";
import {
CUSTOM_QUICK_ACTION_ARTIFACT_CONTENT_PROMPT,
CUSTOM_QUICK_ACTION_ARTIFACT_PROMPT_PREFIX,
Expand All @@ -26,6 +20,8 @@ import {
OpenCanvasGraphAnnotation,
OpenCanvasGraphReturnType,
} from "../state.js";
import { getReflections } from "../../stores/reflections.js";
import { getCustomActions } from "../../stores/custom-actions.js";

const formatMessages = (messages: BaseMessage[]): string =>
messages
Expand All @@ -47,47 +43,26 @@ export const customAction = async (
temperature: 0.5,
});

const store = ensureStoreInConfig(config);
const assistantId = config.configurable?.assistant_id;
const userId = config.configurable?.supabase_user_id;
if (!assistantId) {
throw new Error("`assistant_id` not found in configurable");
}
if (!userId) {
throw new Error("`user.id` not found in configurable");
}
const customActionsNamespace = ["custom_actions", userId];
const actionsKey = "actions";

const memoryNamespace = ["memories", assistantId];
const memoryKey = "reflection";

const [customActionsItem, memories] = await Promise.all([
store.get(customActionsNamespace, actionsKey),
store.get(memoryNamespace, memoryKey),
const [customQuickAction, reflections] = await Promise.all([
getCustomActions(config.store, {
userId: config.configurable?.user_id,
customQuickActionId: state.customQuickActionId,
}),
getReflections(config.store, {
assistantId: config.configurable?.assistant_id,
userId: config.configurable?.user_id,
}),
]);
if (!customActionsItem?.value) {
throw new Error("No custom actions found.");
}
const customQuickAction = customActionsItem.value[
state.customQuickActionId
] as CustomQuickAction | undefined;
if (!customQuickAction) {
throw new Error(
`No custom quick action found from ID ${state.customQuickActionId}`
);
}

const currentArtifactContent = state.artifact
? getArtifactContent(state.artifact)
: undefined;

let formattedPrompt = `<custom-instructions>\n${customQuickAction.prompt}\n</custom-instructions>`;
if (customQuickAction.includeReflections && memories?.value) {
const memoriesAsString = formatReflections(memories.value as Reflections);
if (customQuickAction.includeReflections && reflections) {
const reflectionsPrompt = REFLECTIONS_QUICK_ACTION_PROMPT.replace(
"{reflections}",
memoriesAsString
reflections
);
formattedPrompt += `\n\n${reflectionsPrompt}`;
}
Expand Down
10 changes: 7 additions & 3 deletions apps/agents/src/open-canvas/nodes/generate-artifact/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
createContextDocumentMessages,
getFormattedReflections,
getModelConfig,
getModelFromConfig,
isUsingO1MiniModel,
Expand All @@ -15,6 +14,7 @@ import {
import { ARTIFACT_TOOL_SCHEMA } from "./schemas.js";
import { createArtifactContent, formatNewArtifactPrompt } from "./utils.js";
import { z } from "zod";
import { getReflections } from "../../../stores/reflections.js";

/**
* Generate a new artifact based on the user's query.
Expand Down Expand Up @@ -44,9 +44,13 @@ export const generateArtifact = async (
}
);

const memoriesAsString = await getFormattedReflections(config);
const reflections = await getReflections(config.store, {
assistantId: config.configurable?.assistant_id,
userId: config.configurable?.user_id,
});

const formattedNewArtifactPrompt = formatNewArtifactPrompt(
memoriesAsString,
reflections,
modelName
);

Expand Down
22 changes: 6 additions & 16 deletions apps/agents/src/open-canvas/nodes/generateFollowup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import {
getArtifactContent,
isArtifactMarkdownContent,
} from "@opencanvas/shared/utils/artifacts";
import { Reflections } from "@opencanvas/shared/types";
import { ensureStoreInConfig, formatReflections } from "../../utils.js";
import { FOLLOWUP_ARTIFACT_PROMPT } from "../prompts.js";
import {
OpenCanvasGraphAnnotation,
OpenCanvasGraphReturnType,
} from "../state.js";
import { getReflections } from "../../stores/reflections.js";

/**
* Generate a followup message after generating or updating an artifact.
Expand All @@ -25,19 +24,10 @@ export const generateFollowup = async (
isToolCalling: true,
});

const store = ensureStoreInConfig(config);
const assistantId = config.configurable?.assistant_id;
if (!assistantId) {
throw new Error("`assistant_id` not found in configurable");
}
const memoryNamespace = ["memories", assistantId];
const memoryKey = "reflection";
const memories = await store.get(memoryNamespace, memoryKey);
const memoriesAsString = memories?.value
? formatReflections(memories.value as Reflections, {
onlyContent: true,
})
: "No reflections found.";
const reflections = await getReflections(config.store, {
assistantId: config.configurable?.assistant_id,
userId: config.configurable?.user_id,
});

const currentArtifactContent = state.artifact
? getArtifactContent(state.artifact)
Expand All @@ -53,7 +43,7 @@ export const generateFollowup = async (
"{artifactContent}",
artifactContent || "No artifacts generated yet."
)
.replace("{reflections}", memoriesAsString)
.replace("{reflections}", reflections)
.replace(
"{conversation}",
state._messages
Expand Down
21 changes: 6 additions & 15 deletions apps/agents/src/open-canvas/nodes/replyToGeneralInput.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { LangGraphRunnableConfig } from "@langchain/langgraph";
import { getArtifactContent } from "@opencanvas/shared/utils/artifacts";
import { Reflections } from "@opencanvas/shared/types";
import {
createContextDocumentMessages,
ensureStoreInConfig,
formatArtifactContentWithTemplate,
formatReflections,
getModelFromConfig,
isUsingO1MiniModel,
} from "../../utils.js";
Expand All @@ -14,6 +11,7 @@ import {
OpenCanvasGraphAnnotation,
OpenCanvasGraphReturnType,
} from "../state.js";
import { getReflections } from "../../stores/reflections.js";

/**
* Generate responses to questions. Does not generate artifacts.
Expand All @@ -39,20 +37,13 @@ You also have the following reflections on style guidelines and general memories
? getArtifactContent(state.artifact)
: undefined;

const store = ensureStoreInConfig(config);
const assistantId = config.configurable?.assistant_id;
if (!assistantId) {
throw new Error("`assistant_id` not found in configurable");
}
const memoryNamespace = ["memories", assistantId];
const memoryKey = "reflection";
const memories = await store.get(memoryNamespace, memoryKey);
const memoriesAsString = memories?.value
? formatReflections(memories.value as Reflections)
: "No reflections found.";
const reflections = await getReflections(config.store, {
assistantId: config.configurable?.assistant_id,
userId: config.configurable?.user_id,
});

const formattedPrompt = prompt
.replace("{reflections}", memoriesAsString)
.replace("{reflections}", reflections)
.replace(
"{currentArtifactPrompt}",
currentArtifactContent
Expand Down
Loading