diff --git a/indexers/agents/agents.js b/indexers/agents/agents.js
new file mode 100644
index 00000000..258d97ea
--- /dev/null
+++ b/indexers/agents/agents.js
@@ -0,0 +1,122 @@
+import { Block } from "@near-lake/primitives";
+/**
+ * Note: We only support javascript at the moment. We will support Rust, Typescript in a further release.
+ */
+
+/**
+ * getBlock(block, context) applies your custom logic to a Block on Near and commits the data to a database.
+ * context is a global variable that contains helper methods.
+ * context.db is a subfield which contains helper methods to interact with your database.
+ *
+ * Learn more about indexers here: https://docs.near.org/concepts/advanced/indexers
+ *
+ * @param {block} Block - A Near Protocol Block
+ */
+async function getBlock(block: Block) {
+ function base64decode(encodedValue) {
+ try {
+ const buff = Buffer.from(encodedValue, "base64");
+ const str = buff.toString("utf-8").replace(/\\xa0/g, " ");
+ return JSON.parse(str);
+ } catch (error) {
+ console.log(
+ 'Error parsing JSON - skipping data for "functionCallOperation.args"',
+ error
+ );
+ }
+ }
+
+ const getFunctionCallsFunction = (block, contract, method) => {
+ return block
+ .actions()
+ .filter((action) => action.receiverId === contract)
+ .flatMap((action) =>
+ action.operations
+ .map((operation) => operation["FunctionCall"])
+ .filter((operation) => operation?.methodName === method)
+ .map((functionCallOperation) => ({
+ ...functionCallOperation,
+ args: base64decode(functionCallOperation.args),
+ receiptId: action.receiptId,
+ }))
+ .filter((a) =>
+ block
+ .receipts()
+ .find((r) => r.receiptId === a.receiptId)
+ .status.hasOwnProperty("SuccessValue")
+ )
+ );
+ };
+
+ const getSocialOperations = (block, operation) => {
+ const contract = "social.near";
+ const method = "set";
+ return getFunctionCallsFunction(block, contract, method)
+ .filter((functionCall) => {
+ if (
+ !functionCall ||
+ !functionCall.args ||
+ !functionCall.args.data ||
+ !Object.keys(functionCall.args.data) ||
+ !Object.keys(functionCall.args.data)[0]
+ ) {
+ console.log("Set operation did not have arg data in expected format");
+ return;
+ }
+ const accountId = Object.keys(functionCall.args.data)[0];
+ const accountData = functionCall.args.data[accountId];
+ if (!accountData) {
+ console.log(
+ "Set operation did not have arg data for accountId in expected format"
+ );
+ return;
+ }
+ return accountData[operation];
+ })
+ .map((functionCall) => {
+ const accountId = Object.keys(functionCall.args.data)[0];
+ return {
+ accountId,
+ data: functionCall.args.data[accountId][operation],
+ };
+ });
+ };
+
+ const agentWrites = getSocialOperations(block, "agent");
+ agentWrites.map(async ({ accountId, data }) => {
+ try {
+ const agent = {
+ name: data.name,
+ account_id: accountId,
+ display_name: data.displayName,
+ preferred_provider: data.preferredProvider,
+ preferred_model: data.preferredModel,
+ prompt: data.prompt,
+ variables: data.variables,
+ component: data.component,
+ logo_url: data.logoUrl,
+ tools: data.tools,
+ properties: data.properties,
+ };
+ await context.db.Agents.upsert(
+ agent,
+ ["account_id", "name"],
+ [
+ "display_name",
+ "preferred_provider",
+ "preferred_model",
+ "prompt",
+ "variables",
+ "component",
+ "logo_url",
+ "tools",
+ "properties",
+ ]
+ );
+
+ console.log(`Agent from ${accountId} has been added to the database`);
+ } catch (e) {
+ console.log(`Failed to store agent from ${accountId} to the database`, e);
+ }
+ });
+}
diff --git a/indexers/agents/agents.sql b/indexers/agents/agents.sql
new file mode 100644
index 00000000..14cdcea1
--- /dev/null
+++ b/indexers/agents/agents.sql
@@ -0,0 +1,20 @@
+CREATE TABLE
+ "agents" (
+ "id" SERIAL NOT NULL,
+ "account_id" TEXT NOT NULL,
+ "name" TEXT NOT NULL,
+ "display_name" TEXT,
+ "preferred_provider" TEXT,
+ "preferred_model" TEXT,
+ "input_adaptor" TEXT,
+ "output_adaptor" TEXT,
+ "prompt" TEXT,
+ "variables" TEXT,
+ "component" TEXT,
+ "logo_url" TEXT,
+ "tools" JSONB,
+ "properties" JSONB,
+ PRIMARY KEY ("id")
+);
+
+ALTER TABLE "agents" ADD CONSTRAINT "unique_name" UNIQUE ("account_id", "name");
\ No newline at end of file
diff --git a/src/Entities/AgentFramework/AgentCard.jsx b/src/Entities/AgentFramework/AgentCard.jsx
new file mode 100644
index 00000000..2be95282
--- /dev/null
+++ b/src/Entities/AgentFramework/AgentCard.jsx
@@ -0,0 +1,98 @@
+const { href } = VM.require("devhub.near/widget/core.lib.url");
+
+if (!href) {
+ return <>>;
+}
+
+const Card = styled.div`
+ cursor: pointer;
+ background-color: white;
+ border-radius: 0.5rem;
+ padding: 1.5rem;
+ gap: 1rem;
+ height: 100%;
+ min-height: 12rem;
+
+ transition: all 300ms;
+ box-shadow:
+ 0 1px 3px 0 rgb(0 0 0 / 0.1),
+ 0 1px 2px -1px rgb(0 0 0 / 0.1);
+
+ &:hover {
+ box-shadow:
+ 0 4px 6px -1px rgb(0 0 0 / 0.1),
+ 0 2px 4px -2px rgb(0 0 0 / 0.1);
+ }
+
+ img.logo {
+ height: 6rem;
+ width: 6rem;
+ border-radius: 50%;
+
+ object-fit: cover;
+ }
+
+ h3,
+ p {
+ margin: 0;
+ }
+
+ h3 {
+ font-size: 1.25rem;
+ font-weight: 600;
+ }
+
+ p {
+ font-size: 1rem;
+ font-weight: 400;
+ }
+`;
+const CardBody = styled.div`
+ display: flex;
+ align-items: center;
+`;
+const Break = styled.div`
+ flex-basis: 100%;
+ height: 0;
+`;
+const Actions = styled.div`
+ padding-top: 16px;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+`;
+const AgentCard = ({ item }) => {
+ const { accountId, name, displayName, prompt, logoUrl } = item;
+ const imageUrl =
+ logoUrl ?? "https://ipfs.near.social/ipfs/bafkreibysr2mkwhb4j36h2t7mqwhynqdy4vzjfygfkfg65kuspd2bawauu";
+ const link = href({
+ widgetSrc: `${REPL_ACCOUNT}/widget/Entities.AgentFramework.AgentChat`,
+ params: { src: `${accountId}/agent/${name}` },
+ });
+
+ return (
+ by {accountId} {prompt ? prompt.substring(0, 20) : ""}...{displayName}
+ {data.prompt}
+
Loading modules...
; +} + +const { data, onSubmit, onCancel } = props; + +const onSubmitDefault = (formValues) => { + const { name, ...rest } = formValues; + const agent = { [name]: rest }; + Social.set({ agent: agent }); +}; +const onSubmitFunction = onSubmit ?? onSubmitDefault; + +const AgentInputsPartialSchema = { + name: { + inputProps: { + min: 2, + max: 80, + allowCommaAndSpace: false, + placeholder: "Choose a unique identifier for your agent. Example: travel-agent.", + required: true, + }, + + label: "Name", + order: 1, + }, + displayName: { + inputProps: { + min: 2, + max: 255, + placeholder: "The name that will be displayed to users.", + required: true, + }, + label: "Display Name", + order: 2, + }, + prompt: { + inputProps: { + min: 2, + max: 8192, + placeholder: "The prompt for the agent.", + required: true, + }, + label: "Prompt", + order: 3, + }, + // preferredProvider: { + // inputProps: { + // min: 2, + // max: 255, + // placeholder: "The preferred provider for the agent.", + // required: false, + // }, + // label: "Preferred Provider", + // order: 4, + // }, + // preferredModel: { + // inputProps: { + // min: 2, + // max: 255, + // placeholder: "The preferred model for the agent.", + // required: false, + // }, + // label: "Preferred Model", + // order: 5, + // }, + // variables: { + // inputProps: { + // min: 2, + // max: 255, + // placeholder: "The variables for the agent.", + // required: false, + // }, + // label: "A comma separated list of variables that should be passed into the prompt. Example: ['rfp', 'proposal'].", + // order: 6, + // }, + // component: { + // inputProps: { + // min: 2, + // max: 255, + // placeholder: "The component used to run the agent for the agent.", + // required: false, + // }, + // label: "Component", + // order: 7, + // }, + logoUrl: { + inputProps: { + min: 4, + max: 255, + placeholder: "The logo URL for the agent.", + required: false, + }, + + label: "Logo URL", + order: 8, + }, + // tools: { + // inputProps: { + // min: 2, + // max: 255, + // placeholder: "A JSON array of Tools or Functions the agent should have access to.", + // required: false, + // }, + // + // label: "Tools", + // order: 9, + // }, + // properties: { + // inputProps: { + // min: 2, + // max: 1024, + // placeholder: "JSON properties for the agent.", + // required: false, + // }, + // + // label: "Properties", + // order: 10, + // }, +}; + +const agentInputsValidator = (formValues) => + typeMatch(formValues) && Object.values(formValues).every((value) => typeof value === "string" && value.length > 0); + +const AgentInputsDefaults = { + handle: "", + name: "", + tag: "", + description: "", +}; + +return ( +