fleet-memory/hindsight-integrations/chat
Nicolò Boschi 72c25c97e3
feat(typescript-client): Deno compatibility (#607)
* feat(typescript-client): add Deno compatibility

- Switch build from tsc to tsup for dual CJS + ESM output with proper exports field
- Add deno_setup.ts preload that injects Jest-compatible globals (describe/test/expect) via @std/testing/bdd and @std/expect
- Fix generated client.gen.ts: exclude hey-api internal `client` field from RequestInit spread to avoid conflict with Deno.HttpClient
- Add test:deno npm script using --unstable-sloppy-imports and --preload
- Add test-typescript-client-deno CI job using denoland/setup-deno@v2 (v2.x)
- Update docs: rename page to TypeScript / JavaScript Client, add Deno installation section

* feat: add Deno compatibility to ai-sdk and chat integrations

- Switch ai-sdk and chat builds from tsc to tsup (ESM bundle, eliminates
  extension-less import issues in Deno)
- Add deno.json import map to ai-sdk redirecting 'vitest' to a custom
  vitest-compat.ts shim and bare npm specifiers to npm: URLs
- Add vitest-compat.ts shim implementing vi.fn()/vi.spyOn()/vi.mocked()
  using @std/expect's Symbol.for("@MOCK") interface so toHaveBeenCalledWith
  and other mock matchers work under Deno
- Add test:deno script to ai-sdk (all 30 tests pass under Deno)

* ci: add Deno test job for ai-sdk integration

Adds a new test-ai-sdk-integration-deno CI job that runs the ai-sdk
unit tests under Deno LTS, verifying Deno compatibility of the package.

* fix: remove broken link to non-existent n8n blog post in streamlit post

* fix: patch client.gen.ts for Deno compatibility during generation

Add a post-generation patch step to generate-clients.sh that removes
the hey-api internal 'client' field from the RequestInit spread in
client.gen.ts. Deno's Request constructor rejects 'client' because it
conflicts with the Deno.HttpClient option name.
2026-03-18 14:25:35 +01:00
..
src feat: add Chat SDK integration for persistent chat bot memory (#442) 2026-02-26 17:07:47 +01:00
.gitignore feat: add Chat SDK integration for persistent chat bot memory (#442) 2026-02-26 17:07:47 +01:00
package-lock.json feat(typescript-client): Deno compatibility (#607) 2026-03-18 14:25:35 +01:00
package.json feat(typescript-client): Deno compatibility (#607) 2026-03-18 14:25:35 +01:00
README.md feat: add Chat SDK integration for persistent chat bot memory (#442) 2026-02-26 17:07:47 +01:00
tsconfig.json feat: add Chat SDK integration for persistent chat bot memory (#442) 2026-02-26 17:07:47 +01:00
tsup.config.ts feat(typescript-client): Deno compatibility (#607) 2026-03-18 14:25:35 +01:00
vitest.config.ts feat: add Chat SDK integration for persistent chat bot memory (#442) 2026-02-26 17:07:47 +01:00

@vectorize-io/hindsight-chat

Give your Vercel Chat SDK bots persistent, per-user memory with a single handler wrapper. Works with Slack, Discord, Teams, Google Chat, GitHub, and Linear.

Quick Start

npm install @vectorize-io/hindsight-chat
import { Chat } from 'chat';
import { HindsightClient } from '@vectorize-io/hindsight-client';
import { withHindsightChat } from '@vectorize-io/hindsight-chat';
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';

const chat = new Chat({ connectors: [/* your connectors */] });
const hindsight = new HindsightClient({ apiKey: process.env.HINDSIGHT_API_KEY });

chat.onNewMention(
  withHindsightChat(
    {
      client: hindsight,
      bankId: (msg) => msg.author.userId, // per-user memory
    },
    async (thread, message, ctx) => {
      await thread.subscribe();

      const result = await streamText({
        model: openai('gpt-4o'),
        system: ctx.memoriesAsSystemPrompt(),
        messages: [{ role: 'user', content: message.text }],
      });

      // Stream the response
      const chunks: string[] = [];
      for await (const chunk of result.textStream) {
        chunks.push(chunk);
      }
      const fullResponse = chunks.join('');
      await thread.post(fullResponse);

      // Store the conversation in memory
      await ctx.retain(
        `User: ${message.text}\nAssistant: ${fullResponse}`
      );
    }
  )
);

Configuration

withHindsightChat(options, handler)

Returns a standard Chat SDK handler (thread, message) => Promise<void>.

Options

Option Type Default Description
client HindsightClient required Hindsight client instance
bankId string | (msg) => string required Memory bank ID or resolver function
recall.enabled boolean true Auto-recall memories before handler
recall.budget 'low' | 'mid' | 'high' 'mid' Processing budget for recall
recall.maxTokens number API default Max tokens for recall results
recall.types FactType[] all Filter to specific fact types
recall.includeEntities boolean true Include entity observations
retain.enabled boolean false Auto-retain inbound messages
retain.async boolean true Fire-and-forget retain
retain.tags string[] Tags for retained memories
retain.metadata Record<string, string> Metadata for retained memories

Context (ctx)

The third argument passed to your handler:

Property/Method Description
ctx.bankId Resolved bank ID
ctx.memories Array of recalled memories
ctx.entities Entity observations (or null)
ctx.memoriesAsSystemPrompt(options?) Format memories for LLM system prompt
ctx.retain(content, options?) Store content in memory
ctx.recall(query, options?) Search memories
ctx.reflect(query, options?) Reason over memories

Examples

Subscribed Message Handler

chat.onSubscribedMessage(
  withHindsightChat(
    {
      client: hindsight,
      bankId: (msg) => msg.author.userId,
      recall: { budget: 'high', maxTokens: 1000 },
    },
    async (thread, message, ctx) => {
      const result = await generateText({
        model: openai('gpt-4o'),
        system: ctx.memoriesAsSystemPrompt(),
        messages: [{ role: 'user', content: message.text }],
      });
      await thread.post(result.text);
    }
  )
);

Auto-Retain Inbound Messages

chat.onNewMention(
  withHindsightChat(
    {
      client: hindsight,
      bankId: (msg) => msg.author.userId,
      retain: { enabled: true, tags: ['slack', 'inbound'] },
    },
    async (thread, message, ctx) => {
      // Inbound message is already being retained automatically
      const result = await generateText({
        model: openai('gpt-4o'),
        system: ctx.memoriesAsSystemPrompt(),
        messages: [{ role: 'user', content: message.text }],
      });
      await thread.post(result.text);

      // Retain the assistant response separately
      await ctx.retain(`Assistant: ${result.text}`, {
        tags: ['slack', 'outbound'],
      });
    }
  )
);

Static Bank ID (Shared Memory)

// All users share the same memory bank
chat.onNewMention(
  withHindsightChat(
    { client: hindsight, bankId: 'shared-team-memory' },
    async (thread, message, ctx) => {
      // ...
    }
  )
);

Error Handling

Memory failures never break your bot. Auto-recall and auto-retain errors are logged as warnings and the handler continues with empty memories. Manual ctx.retain(), ctx.recall(), and ctx.reflect() calls propagate errors normally so you can handle them as needed.

License

MIT