Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f3b7d2b18 | ||
|
|
a8b27b0831 |
@@ -51,6 +51,7 @@ jobs:
|
||||
integrations-llamaindex: ${{ steps.filter.outputs.integrations-llamaindex }}
|
||||
integrations-paperclip: ${{ steps.filter.outputs.integrations-paperclip }}
|
||||
integrations-opencode: ${{ steps.filter.outputs.integrations-opencode }}
|
||||
integrations-eve: ${{ steps.filter.outputs.integrations-eve }}
|
||||
integrations-cursor: ${{ steps.filter.outputs.integrations-cursor }}
|
||||
integrations-zed: ${{ steps.filter.outputs.integrations-zed }}
|
||||
integrations-n8n: ${{ steps.filter.outputs.integrations-n8n }}
|
||||
@@ -173,6 +174,8 @@ jobs:
|
||||
- 'hindsight-integrations/paperclip/**'
|
||||
integrations-opencode:
|
||||
- 'hindsight-integrations/opencode/**'
|
||||
integrations-eve:
|
||||
- 'hindsight-integrations/eve/**'
|
||||
integrations-cursor:
|
||||
- 'hindsight-integrations/cursor/**'
|
||||
integrations-zed:
|
||||
@@ -793,6 +796,37 @@ jobs:
|
||||
working-directory: ./hindsight-integrations/opencode
|
||||
run: npm run build
|
||||
|
||||
test-eve-integration:
|
||||
needs: [detect-changes]
|
||||
if: >-
|
||||
(github.event_name == 'workflow_dispatch' ||
|
||||
needs.detect-changes.outputs.integrations-eve == 'true' ||
|
||||
needs.detect-changes.outputs.ci == 'true')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || '' }}
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./hindsight-integrations/eve
|
||||
run: npm ci
|
||||
|
||||
- name: Run tests
|
||||
working-directory: ./hindsight-integrations/eve
|
||||
run: npm test
|
||||
|
||||
- name: Build
|
||||
working-directory: ./hindsight-integrations/eve
|
||||
run: npm run build
|
||||
|
||||
test-n8n-integration:
|
||||
needs: [detect-changes]
|
||||
if: >-
|
||||
@@ -4878,6 +4912,7 @@ jobs:
|
||||
- build-ai-sdk-integration
|
||||
- test-ai-sdk-integration-deno
|
||||
- test-opencode-integration
|
||||
- test-eve-integration
|
||||
- test-omo-integration
|
||||
- test-cloudflare-oauth-proxy-integration
|
||||
- build-chat-integration
|
||||
|
||||
@@ -61,6 +61,7 @@ INTEGRATIONS: dict[str, IntegrationMeta] = {
|
||||
"aider": IntegrationMeta("hindsight-aider", "Aider"),
|
||||
"paperclip": IntegrationMeta("@vectorize-io/hindsight-paperclip", "Paperclip"),
|
||||
"opencode": IntegrationMeta("@vectorize-io/opencode-hindsight", "OpenCode"),
|
||||
"eve": IntegrationMeta("@vectorize-io/hindsight-eve", "Eve"),
|
||||
"cloudflare-oauth-proxy": IntegrationMeta("hindsight-cloudflare-oauth-proxy"),
|
||||
"openai-agents": IntegrationMeta("hindsight-openai-agents"),
|
||||
"pipecat": IntegrationMeta("hindsight-pipecat", "Pipecat"),
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
sidebar_position: 38
|
||||
title: "Eve Agent Memory with Hindsight | Integration"
|
||||
description: "Add long-term memory to Vercel Eve agents with Hindsight. A one-line MCP connection gives your agent retain, recall, and reflect across sessions."
|
||||
---
|
||||
|
||||
# Eve
|
||||
|
||||
Long-term memory for [Vercel Eve](https://github.com/vercel/eve) agents using [Hindsight](https://vectorize.io/hindsight). Eve is filesystem-first — an agent gains a capability by dropping a file under `agent/connections/`. The `@vectorize-io/hindsight-eve` package wraps Eve's `defineMcpClientConnection`, so one file gives your agent `retain`, `recall`, and `reflect` over Hindsight's MCP server and it remembers across sessions and deployments.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @vectorize-io/hindsight-eve
|
||||
```
|
||||
|
||||
`eve` is a peer dependency you already have in an Eve project.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Create `agent/connections/hindsight.ts`:
|
||||
|
||||
```ts
|
||||
import { defineHindsightConnection } from "@vectorize-io/hindsight-eve";
|
||||
|
||||
export default defineHindsightConnection();
|
||||
```
|
||||
|
||||
The connection reads its defaults from the environment:
|
||||
|
||||
| Env var | Purpose |
|
||||
| ----------------------- | ---------------------------------------------------------------- |
|
||||
| `HINDSIGHT_API_KEY` | Bearer token sent as `Authorization: Bearer <key>` |
|
||||
| `HINDSIGHT_MCP_URL` | MCP endpoint (defaults to Hindsight Cloud) |
|
||||
| `HINDSIGHT_MCP_BANK_ID` | Optional bank to scope memory to, sent as the `X-Bank-Id` header |
|
||||
|
||||
The model discovers the tools via Eve's `connection__search` and calls them as `connection__hindsight__recall`, `connection__hindsight__retain`, and `connection__hindsight__reflect`. The connection's URL and token never reach the model.
|
||||
|
||||
### Hindsight Cloud
|
||||
|
||||
Set `HINDSIGHT_API_KEY` from your [Hindsight Cloud](https://hindsight.vectorize.io) dashboard. The connection defaults to `https://api.hindsight.vectorize.io/mcp`, so no URL is needed.
|
||||
|
||||
### Self-hosted
|
||||
|
||||
Point at your own server, optionally scoping to a bank. Use `apiKey: null` for a no-auth local server:
|
||||
|
||||
```ts
|
||||
import { defineHindsightConnection } from "@vectorize-io/hindsight-eve";
|
||||
|
||||
export default defineHindsightConnection({
|
||||
url: "http://localhost:8000/mcp",
|
||||
apiKey: null,
|
||||
});
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```ts
|
||||
defineHindsightConnection({
|
||||
url, // MCP endpoint; defaults to HINDSIGHT_MCP_URL, then Cloud
|
||||
apiKey, // bearer token; null = no auth (local dev)
|
||||
bankId, // scope memory to a bank (X-Bank-Id header)
|
||||
description, // override the model-facing description
|
||||
tools, // { allow } | { block } — narrow which Hindsight tools the model sees
|
||||
approval, // human-in-the-loop policy, e.g. once() from "eve/tools/approval"
|
||||
});
|
||||
```
|
||||
|
||||
Restrict the agent to read-only recall and require approval the first time:
|
||||
|
||||
```ts
|
||||
import { defineHindsightConnection } from "@vectorize-io/hindsight-eve";
|
||||
import { once } from "eve/tools/approval";
|
||||
|
||||
export default defineHindsightConnection({
|
||||
tools: { allow: ["recall", "reflect"] },
|
||||
approval: once(),
|
||||
});
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
- [Hindsight docs](https://hindsight.vectorize.io)
|
||||
- [Eve connections](https://github.com/vercel/eve/blob/main/docs/connections.mdx)
|
||||
@@ -370,6 +370,16 @@
|
||||
"link": "/sdks/integrations/opencode",
|
||||
"icon": "/img/icons/opencode.png"
|
||||
},
|
||||
{
|
||||
"id": "eve",
|
||||
"name": "Eve",
|
||||
"description": "Long-term memory for Vercel Eve agents. A one-line MCP connection exposing retain, recall, and reflect.",
|
||||
"type": "official",
|
||||
"by": "hindsight",
|
||||
"category": "framework",
|
||||
"link": "/sdks/integrations/eve",
|
||||
"icon": "/img/icons/eve.svg"
|
||||
},
|
||||
{
|
||||
"id": "n8n",
|
||||
"name": "n8n",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64" role="img" aria-label="Eve" fill="none">
|
||||
<path d="M12 2 2 19.5h20L12 2Zm0 4.7 6.1 10.6H5.9L12 6.7Z" fill="#000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 206 B |
@@ -0,0 +1,30 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
|
||||
# Test coverage
|
||||
coverage/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
@@ -0,0 +1,104 @@
|
||||
# Hindsight for Eve
|
||||
|
||||
Long-term memory for [Vercel Eve](https://github.com/vercel/eve) agents, powered by
|
||||
[Hindsight](https://vectorize.io/hindsight). One file gives your agent `retain`, `recall`,
|
||||
and `reflect` over [Hindsight's MCP server](https://hindsight.vectorize.io) — so it
|
||||
remembers facts across sessions and deployments instead of starting cold every time.
|
||||
|
||||
## How it works
|
||||
|
||||
Eve is filesystem-first: an agent gains a capability by dropping a file under
|
||||
`agent/connections/`. This package wraps eve's `defineMcpClientConnection`, pre-filling the
|
||||
Hindsight MCP endpoint, a model-facing description, and bearer auth. The model discovers the
|
||||
tools through `connection__search` and calls them as `connection__hindsight__recall`,
|
||||
`connection__hindsight__retain`, and `connection__hindsight__reflect`. The connection's URL
|
||||
and token never reach the model.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @vectorize-io/hindsight-eve
|
||||
```
|
||||
|
||||
`eve` is a peer dependency — you already have it in an Eve project.
|
||||
|
||||
## Quick start
|
||||
|
||||
Create `agent/connections/hindsight.ts`:
|
||||
|
||||
```ts
|
||||
import { defineHindsightConnection } from "@vectorize-io/hindsight-eve";
|
||||
|
||||
export default defineHindsightConnection();
|
||||
```
|
||||
|
||||
That's it. By default the connection reads:
|
||||
|
||||
| Env var | Purpose |
|
||||
| ----------------------- | ---------------------------------------------------------------- |
|
||||
| `HINDSIGHT_API_KEY` | Bearer token sent as `Authorization: Bearer <key>` |
|
||||
| `HINDSIGHT_MCP_URL` | MCP endpoint (defaults to Hindsight Cloud) |
|
||||
| `HINDSIGHT_MCP_BANK_ID` | Optional bank to scope memory to, sent as the `X-Bank-Id` header |
|
||||
|
||||
### Hindsight Cloud
|
||||
|
||||
Set `HINDSIGHT_API_KEY` to a key from your [Hindsight Cloud](https://hindsight.vectorize.io)
|
||||
dashboard. The connection defaults to `https://api.hindsight.vectorize.io/mcp`, so no URL is
|
||||
needed.
|
||||
|
||||
### Self-hosted
|
||||
|
||||
Point at your own server and (optionally) pick a bank:
|
||||
|
||||
```bash
|
||||
export HINDSIGHT_MCP_URL="http://localhost:8000/mcp"
|
||||
export HINDSIGHT_MCP_BANK_ID="my-project"
|
||||
export HINDSIGHT_API_KEY="…" # or omit and pass apiKey: null below for a no-auth server
|
||||
```
|
||||
|
||||
```ts
|
||||
import { defineHindsightConnection } from "@vectorize-io/hindsight-eve";
|
||||
|
||||
// A local server with no auth:
|
||||
export default defineHindsightConnection({
|
||||
url: "http://localhost:8000/mcp",
|
||||
apiKey: null,
|
||||
});
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```ts
|
||||
defineHindsightConnection({
|
||||
url, // string — MCP endpoint; defaults to HINDSIGHT_MCP_URL, then Cloud
|
||||
apiKey, // string | null — bearer token; null = no auth (local dev)
|
||||
bankId, // string — scope memory to a bank (X-Bank-Id header)
|
||||
description, // string — override the model-facing description
|
||||
tools, // { allow } | { block } — narrow which Hindsight tools the model sees
|
||||
approval, // human-in-the-loop policy, e.g. once() from "eve/tools/approval"
|
||||
});
|
||||
```
|
||||
|
||||
Restrict the agent to read-only recall, and require approval the first time:
|
||||
|
||||
```ts
|
||||
import { defineHindsightConnection } from "@vectorize-io/hindsight-eve";
|
||||
import { once } from "eve/tools/approval";
|
||||
|
||||
export default defineHindsightConnection({
|
||||
tools: { allow: ["recall", "reflect"] },
|
||||
approval: once(),
|
||||
});
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
With the connection in place, run your agent and ask it something it would need to look up
|
||||
("what did we decide about X last week?"). Eve's `connection__search` surfaces the Hindsight
|
||||
tools and the model calls `connection__hindsight__recall`. To seed memory, have the agent
|
||||
`retain` a fact in one session and `recall` it in the next.
|
||||
|
||||
## Links
|
||||
|
||||
- [Hindsight docs](https://hindsight.vectorize.io)
|
||||
- [Eve connections](https://github.com/vercel/eve/blob/main/docs/connections.mdx)
|
||||
+3529
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "@vectorize-io/hindsight-eve",
|
||||
"version": "0.1.0",
|
||||
"description": "Hindsight long-term memory for Vercel Eve agents - a one-line MCP connection exposing retain, recall, and reflect",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"keywords": [
|
||||
"eve",
|
||||
"vercel",
|
||||
"agents",
|
||||
"mcp",
|
||||
"memory",
|
||||
"hindsight",
|
||||
"llm",
|
||||
"long-term-memory"
|
||||
],
|
||||
"author": "Vectorize <[email protected]>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vectorize-io/hindsight.git",
|
||||
"directory": "hindsight-integrations/eve"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsc --watch",
|
||||
"clean": "rm -rf dist",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"prepublishOnly": "npm run clean && npm run build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eve": ">=0.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"eve": "0.11.4",
|
||||
"tsup": "^8.5.1",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^4.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24"
|
||||
},
|
||||
"overrides": {
|
||||
"rollup": "^4.59.0",
|
||||
"picomatch": ">=2.3.2 <3.0.0 || >=4.0.4",
|
||||
"vite": ">=8.0.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { once } from "eve/tools/approval";
|
||||
import {
|
||||
resolveHindsightConnection,
|
||||
buildHindsightConnectionDefinition,
|
||||
defineHindsightConnection,
|
||||
HINDSIGHT_CLOUD_MCP_URL,
|
||||
DEFAULT_DESCRIPTION,
|
||||
} from "./index";
|
||||
|
||||
const EMPTY_ENV = {} as NodeJS.ProcessEnv;
|
||||
|
||||
describe("resolveHindsightConnection", () => {
|
||||
it("defaults to Hindsight Cloud with the default description", () => {
|
||||
const resolved = resolveHindsightConnection({ apiKey: "hsk_test" }, EMPTY_ENV);
|
||||
expect(resolved.url).toBe(HINDSIGHT_CLOUD_MCP_URL);
|
||||
expect(resolved.description).toBe(DEFAULT_DESCRIPTION);
|
||||
expect(resolved.apiKey).toBe("hsk_test");
|
||||
expect(resolved.bankId).toBeNull();
|
||||
});
|
||||
|
||||
it("reads url, key, and bank from the environment", () => {
|
||||
const resolved = resolveHindsightConnection({}, {
|
||||
HINDSIGHT_MCP_URL: "http://localhost:8000/mcp",
|
||||
HINDSIGHT_API_KEY: "env_key",
|
||||
HINDSIGHT_MCP_BANK_ID: "project-x",
|
||||
} as NodeJS.ProcessEnv);
|
||||
expect(resolved.url).toBe("http://localhost:8000/mcp");
|
||||
expect(resolved.apiKey).toBe("env_key");
|
||||
expect(resolved.bankId).toBe("project-x");
|
||||
});
|
||||
|
||||
it("prefers explicit options over the environment", () => {
|
||||
const resolved = resolveHindsightConnection(
|
||||
{ url: "http://opt/mcp", apiKey: "opt_key", bankId: "opt_bank" },
|
||||
{
|
||||
HINDSIGHT_MCP_URL: "http://env/mcp",
|
||||
HINDSIGHT_API_KEY: "env_key",
|
||||
HINDSIGHT_MCP_BANK_ID: "env_bank",
|
||||
} as NodeJS.ProcessEnv
|
||||
);
|
||||
expect(resolved.url).toBe("http://opt/mcp");
|
||||
expect(resolved.apiKey).toBe("opt_key");
|
||||
expect(resolved.bankId).toBe("opt_bank");
|
||||
});
|
||||
|
||||
it("treats apiKey: null as an explicit no-auth opt-out", () => {
|
||||
const resolved = resolveHindsightConnection(
|
||||
{ url: "http://localhost:8000/mcp", apiKey: null },
|
||||
{ HINDSIGHT_API_KEY: "env_key" } as NodeJS.ProcessEnv
|
||||
);
|
||||
expect(resolved.apiKey).toBeNull();
|
||||
});
|
||||
|
||||
it("throws when targeting Hindsight Cloud without a key", () => {
|
||||
expect(() => resolveHindsightConnection({}, EMPTY_ENV)).toThrow(/API key/);
|
||||
});
|
||||
|
||||
it("throws for a Cloud URL with a trailing slash and no key", () => {
|
||||
expect(() =>
|
||||
resolveHindsightConnection({ url: "https://api.hindsight.vectorize.io/mcp/" }, EMPTY_ENV)
|
||||
).toThrow(/API key/);
|
||||
});
|
||||
|
||||
it("throws for a regional Cloud subdomain with no key", () => {
|
||||
expect(() =>
|
||||
resolveHindsightConnection({ url: "https://api.eu.hindsight.vectorize.io/mcp" }, EMPTY_ENV)
|
||||
).toThrow(/API key/);
|
||||
});
|
||||
|
||||
it("does not treat a look-alike host as Cloud", () => {
|
||||
// `nothindsight.vectorize.io` must not match the Cloud guard, so a no-auth
|
||||
// self-hosted server on a similar domain is allowed.
|
||||
const resolved = resolveHindsightConnection(
|
||||
{ url: "https://nothindsight.vectorize.io/mcp", apiKey: null },
|
||||
EMPTY_ENV
|
||||
);
|
||||
expect(resolved.apiKey).toBeNull();
|
||||
});
|
||||
|
||||
it("allows a self-hosted url with no auth", () => {
|
||||
const resolved = resolveHindsightConnection(
|
||||
{ url: "http://localhost:8000/mcp", apiKey: null },
|
||||
EMPTY_ENV
|
||||
);
|
||||
expect(resolved.url).toBe("http://localhost:8000/mcp");
|
||||
expect(resolved.apiKey).toBeNull();
|
||||
});
|
||||
|
||||
it("ignores empty-string environment values", () => {
|
||||
const resolved = resolveHindsightConnection({ apiKey: "k" }, {
|
||||
HINDSIGHT_MCP_URL: "",
|
||||
} as NodeJS.ProcessEnv);
|
||||
expect(resolved.url).toBe(HINDSIGHT_CLOUD_MCP_URL);
|
||||
});
|
||||
|
||||
it("passes tool filters through unchanged", () => {
|
||||
const resolved = resolveHindsightConnection(
|
||||
{ apiKey: "k", tools: { allow: ["recall", "retain"] } },
|
||||
EMPTY_ENV
|
||||
);
|
||||
expect(resolved.tools).toEqual({ allow: ["recall", "retain"] });
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildHindsightConnectionDefinition", () => {
|
||||
it("wires bearer auth whose getToken returns the configured key", async () => {
|
||||
const definition = buildHindsightConnectionDefinition(
|
||||
resolveHindsightConnection({ url: "http://localhost:8000/mcp", apiKey: "k" }, EMPTY_ENV)
|
||||
);
|
||||
expect(definition.url).toBe("http://localhost:8000/mcp");
|
||||
const auth = definition.auth as { getToken: () => Promise<{ token: string }> };
|
||||
expect(await auth.getToken()).toEqual({ token: "k" });
|
||||
});
|
||||
|
||||
it("emits no auth when the key is null", () => {
|
||||
const definition = buildHindsightConnectionDefinition(
|
||||
resolveHindsightConnection({ url: "http://localhost:8000/mcp", apiKey: null }, EMPTY_ENV)
|
||||
);
|
||||
expect(definition.auth).toBeUndefined();
|
||||
});
|
||||
|
||||
it("sets the X-Bank-Id header when a bank is configured", () => {
|
||||
const definition = buildHindsightConnectionDefinition(
|
||||
resolveHindsightConnection({ apiKey: "k", bankId: "project-x" }, EMPTY_ENV)
|
||||
);
|
||||
expect(definition.headers).toEqual({ "X-Bank-Id": "project-x" });
|
||||
});
|
||||
|
||||
it("passes the approval policy through unchanged", () => {
|
||||
const approval = once();
|
||||
const definition = buildHindsightConnectionDefinition(
|
||||
resolveHindsightConnection({ apiKey: "k", approval }, EMPTY_ENV)
|
||||
);
|
||||
expect(definition.approval).toBe(approval);
|
||||
});
|
||||
|
||||
it("omits approval when none is configured", () => {
|
||||
const definition = buildHindsightConnectionDefinition(
|
||||
resolveHindsightConnection({ apiKey: "k" }, EMPTY_ENV)
|
||||
);
|
||||
expect(definition.approval).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("defineHindsightConnection", () => {
|
||||
it("builds a connection via the real eve framework without throwing", () => {
|
||||
const connection = defineHindsightConnection({
|
||||
url: "http://localhost:8000/mcp",
|
||||
apiKey: "k",
|
||||
});
|
||||
expect(connection).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Hindsight long-term memory for Vercel Eve agents.
|
||||
*
|
||||
* Wraps eve's `defineMcpClientConnection` so an agent gains persistent memory by
|
||||
* dropping a single file under `agent/connections/`. The helper fills in the
|
||||
* Hindsight MCP endpoint, a model-facing description, and bearer auth, reading
|
||||
* sensible defaults from the environment:
|
||||
*
|
||||
* ```ts
|
||||
* // agent/connections/hindsight.ts
|
||||
* import { defineHindsightConnection } from "@vectorize-io/hindsight-eve";
|
||||
* export default defineHindsightConnection(); // HINDSIGHT_MCP_URL + HINDSIGHT_API_KEY
|
||||
* ```
|
||||
*/
|
||||
import { defineMcpClientConnection } from "eve/connections";
|
||||
|
||||
/** The argument eve's connection factory accepts; options pass straight through. */
|
||||
type McpConnectionInput = Parameters<typeof defineMcpClientConnection>[0];
|
||||
|
||||
/** Hindsight Cloud MCP endpoint, used when no URL is configured. */
|
||||
export const HINDSIGHT_CLOUD_MCP_URL = "https://api.hindsight.vectorize.io/mcp";
|
||||
|
||||
/**
|
||||
* Default model-facing description written into the generated connection. Eve
|
||||
* surfaces it when the agent discovers this connection's tools
|
||||
* (`connection__hindsight__retain` / `recall` / `reflect`).
|
||||
*/
|
||||
export const DEFAULT_DESCRIPTION =
|
||||
"Hindsight long-term memory: retain facts from this session, recall relevant history " +
|
||||
"from past sessions, and reflect over consolidated mental models.";
|
||||
|
||||
export interface HindsightConnectionOptions {
|
||||
/** Hindsight MCP endpoint. Defaults to `HINDSIGHT_MCP_URL`, then Hindsight Cloud. */
|
||||
url?: string;
|
||||
/**
|
||||
* API key sent as `Authorization: Bearer <key>`. Defaults to `HINDSIGHT_API_KEY`.
|
||||
* Pass `null` to emit a no-auth connection (local/self-hosted dev only).
|
||||
*/
|
||||
apiKey?: string | null;
|
||||
/** Bank to scope memory to; sent as the `X-Bank-Id` header. Defaults to `HINDSIGHT_MCP_BANK_ID`. */
|
||||
bankId?: string;
|
||||
/** Override the model-facing description. */
|
||||
description?: string;
|
||||
/** Restrict which Hindsight tools the model can see. */
|
||||
tools?: McpConnectionInput["tools"];
|
||||
/** Human-in-the-loop approval policy (e.g. `once()` from `eve/tools/approval`). */
|
||||
approval?: McpConnectionInput["approval"];
|
||||
}
|
||||
|
||||
/** Fully-resolved connection settings, after applying options and environment defaults. */
|
||||
export interface ResolvedHindsightConnection {
|
||||
url: string;
|
||||
description: string;
|
||||
apiKey: string | null;
|
||||
bankId: string | null;
|
||||
tools?: McpConnectionInput["tools"];
|
||||
approval?: McpConnectionInput["approval"];
|
||||
}
|
||||
|
||||
/** First non-empty string among the candidates, or `null`. */
|
||||
function firstNonEmpty(...values: Array<string | null | undefined>): string | null {
|
||||
for (const value of values) {
|
||||
if (typeof value === "string" && value.length > 0) return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a URL points at Hindsight Cloud. Matched on host (not exact string)
|
||||
* so a trailing slash, `http`/`https`, or a regional subdomain still triggers
|
||||
* the missing-key guard below instead of letting the request fail with a raw
|
||||
* 401. The dot boundary keeps it from matching look-alike hosts like
|
||||
* `nothindsight.vectorize.io`.
|
||||
*/
|
||||
function isHindsightCloudUrl(url: string): boolean {
|
||||
try {
|
||||
const host = new URL(url).hostname;
|
||||
return host === "hindsight.vectorize.io" || host.endsWith(".hindsight.vectorize.io");
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve options against environment defaults. Pure and side-effect free so the
|
||||
* precedence rules can be unit-tested without constructing a live connection.
|
||||
*/
|
||||
export function resolveHindsightConnection(
|
||||
options: HindsightConnectionOptions = {},
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
): ResolvedHindsightConnection {
|
||||
const url = options.url ?? firstNonEmpty(env.HINDSIGHT_MCP_URL) ?? HINDSIGHT_CLOUD_MCP_URL;
|
||||
|
||||
// `apiKey: null` is an explicit no-auth opt-out; `undefined` falls back to the env var.
|
||||
const apiKey =
|
||||
options.apiKey === undefined ? firstNonEmpty(env.HINDSIGHT_API_KEY) : options.apiKey;
|
||||
|
||||
const bankId = options.bankId ?? firstNonEmpty(env.HINDSIGHT_MCP_BANK_ID);
|
||||
|
||||
if (isHindsightCloudUrl(url) && !apiKey) {
|
||||
throw new Error(
|
||||
"Hindsight Cloud requires an API key. Set HINDSIGHT_API_KEY, pass `apiKey`, or point " +
|
||||
"`url`/HINDSIGHT_MCP_URL at a self-hosted server (use `apiKey: null` for a no-auth server)."
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
url,
|
||||
description: options.description ?? DEFAULT_DESCRIPTION,
|
||||
apiKey,
|
||||
bankId,
|
||||
tools: options.tools,
|
||||
approval: options.approval,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the plain definition object handed to eve. Kept separate from
|
||||
* {@link defineHindsightConnection} so the auth/header wiring is testable without
|
||||
* depending on the shape of eve's returned connection.
|
||||
*/
|
||||
export function buildHindsightConnectionDefinition(
|
||||
resolved: ResolvedHindsightConnection
|
||||
): McpConnectionInput {
|
||||
return {
|
||||
url: resolved.url,
|
||||
description: resolved.description,
|
||||
// `{ token }` is eve's TokenResult shape (sent as `Authorization: Bearer`).
|
||||
// It rides on eve 0.11's auth contract, which the pinned peer/dev dep covers.
|
||||
...(resolved.apiKey
|
||||
? { auth: { getToken: async () => ({ token: resolved.apiKey as string }) } }
|
||||
: {}),
|
||||
...(resolved.bankId ? { headers: { "X-Bank-Id": resolved.bankId } } : {}),
|
||||
...(resolved.tools ? { tools: resolved.tools } : {}),
|
||||
...(resolved.approval ? { approval: resolved.approval } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Define an eve MCP connection to a Hindsight memory server. Export the result as
|
||||
* the default from `agent/connections/hindsight.ts`.
|
||||
*/
|
||||
export function defineHindsightConnection(options: HindsightConnectionOptions = {}) {
|
||||
return defineMcpClientConnection(
|
||||
buildHindsightConnectionDefinition(resolveHindsightConnection(options))
|
||||
);
|
||||
}
|
||||
|
||||
export default defineHindsightConnection;
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022"],
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["node"],
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "src/**/*.test.ts"]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig({
|
||||
entry: ["src/index.ts"],
|
||||
format: ["esm"],
|
||||
dts: true,
|
||||
outDir: "dist",
|
||||
clean: true,
|
||||
sourcemap: true,
|
||||
bundle: true,
|
||||
// `eve` is a peer dependency, supplied by the consuming agent project.
|
||||
external: ["eve"],
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "node",
|
||||
include: ["src/**/*.test.ts"],
|
||||
},
|
||||
});
|
||||
@@ -13,7 +13,7 @@ print_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||
print_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
print_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
VALID_INTEGRATIONS=("ag2" "agent-framework" "agentcore" "agno" "aider" "ai-sdk" "autogen" "chat" "claude-agent-sdk" "claude-code" "cline" "cloudflare-oauth-proxy" "codex" "composio" "continue" "crewai" "cursor" "cursor-cli" "dify" "flowise" "gemini-spark" "github-copilot" "google-adk" "haystack" "langgraph" "litellm" "llamaindex" "n8n" "nemoclaw" "obsidian" "omo" "openai-agents" "openclaw" "opencode" "openhands" "paperclip" "pipecat" "pydantic-ai" "roo-code" "smolagents" "strands" "superagent" "vapi" "windsurf" "zed")
|
||||
VALID_INTEGRATIONS=("ag2" "agent-framework" "agentcore" "agno" "aider" "ai-sdk" "autogen" "chat" "claude-agent-sdk" "claude-code" "cline" "cloudflare-oauth-proxy" "codex" "composio" "continue" "crewai" "cursor" "cursor-cli" "dify" "eve" "flowise" "gemini-spark" "github-copilot" "google-adk" "haystack" "langgraph" "litellm" "llamaindex" "n8n" "nemoclaw" "obsidian" "omo" "openai-agents" "openclaw" "opencode" "openhands" "paperclip" "pipecat" "pydantic-ai" "roo-code" "smolagents" "strands" "superagent" "vapi" "windsurf" "zed")
|
||||
|
||||
usage() {
|
||||
print_error "Usage: $0 <integration> <version>"
|
||||
|
||||
Reference in New Issue
Block a user