
Security News
Open VSX Begins Implementing Pre-Publish Security Checks After Repeated Supply Chain Incidents
Following multiple malicious extension incidents, Open VSX outlines new safeguards designed to catch risky uploads earlier.
Keep your agent context small and cheap by zipping large tool results out of the conversation and into storage. ctx-zip automatically persists bulky tool outputs (JSON/text) to a storage backend and replaces them in the message list with short, human- and model-friendly references. You control when and how compaction happens, including a simple "last-N messages" strategy for long-running loops.
Works primarily with the AI SDK for agents and loop control. See: AI SDK – Loop Control: Context Management.
Written to storage: blob://prefix/abc.txt).readFile, grepAndSearchFile).prepareStep so you can also layer a simple, robust "last-N" message-retention strategy.npm i ctx-zip
# or
pnpm add ctx-zip
The example below shows how to keep only the last N messages while also compacting tool results to storage on each step. It follows the AI SDK prepareStep pattern for loop control.
After compaction, the model will see short references like Written to storage: blob://prefix/<key>. The agent can then retrieve or search that content using the built-in tools below. Add them to your tools map so the model can call them when it needs to re-open persisted outputs.
key from a storage URI (file://... or blob:).Usage:
import { generateText, stepCountIs } from "ai";
import { openai } from "@ai-sdk/openai";
import {
compactMessages,
createReadFileTool,
createGrepAndSearchFileTool,
} from "ctx-zip";
// Choose a storage backend (see Storage section below)
// - Local filesystem (default if omitted): file:///absolute/path
// - Vercel Blob: blob: (requires BLOB_READ_WRITE_TOKEN)
const storageUri = `file://${process.cwd()}`;
const result = await generateText({
model: openai("gpt-4.1-mini"),
tools: {
// Built-in tools so the model can read/search persisted outputs
readFile: createReadFileTool(),
grepAndSearchFile: createGrepAndSearchFileTool(),
// ... your other tools (zod-typed) ...
},
stopWhen: stepCountIs(6),
prompt: "Use tools to research, summarize, and cite sources.",
prepareStep: async ({ messages }) => {
// 1. Writes the tool results of the first 20 messages to a local file
// 2. Replaces those messages with a reference to that file
const compacted = await compactMessages(messages, {
storage: storageUri,
boundary: { type: "first-n-messages", count: 20 },
});
return { messages: compacted };
},
});
console.log(result.text);
Notes:
readFile and grepAndSearchFile so their outputs aren’t re-written; a friendly "Read from storage" reference is shown instead.storageReaderToolNames to extend this behavior for custom reader tools. If you provide additional reader tools, include them in the tools map and add their names to storageReaderToolNames so compaction treats their outputs as references rather than rewriting to storage.Tool inputs (model-provided):
{ key: string; storage: string }{ key: string; storage: string; pattern: string; flags?: string }By default, compactMessages treats readFile and grepAndSearchFile as reader tools and will not re-write their results back to storage; instead it replaces them with a short reference to the source so the context stays lean.
compactMessages(messages, options) accepts:
interface CompactOptions {
strategy?: "write-tool-results-to-storage" | string; // default
storage?: string | StorageAdapter | undefined; // e.g. "file:///..." | "blob:" | adapter instance
boundary?:
| "since-last-assistant-or-user-text"
| "entire-conversation"
| { type: "first-n-messages"; count: number }; // keep first N intact
serializeResult?: (value: unknown) => string; // default: JSON.stringify(v, null, 2)
storageReaderToolNames?: string[]; // tool names that read from storage
}
write-tool-results-to-storage is supported.file://..., blob:) or an adapter.since-last-assistant-or-user-text (default): Compact only the latest turn.entire-conversation: Re-compact the full history.{ type: "first-n-messages", count: N }: Preserve the first N messages (useful for system instructions) and compact the rest.ctx-zip supports local filesystem and Vercel Blob out of the box. Choose one via a URI in CompactOptions.storage or by passing a constructed adapter.
file:///absolute/output/dirprocess.cwd().Examples:
// Use a URI
await compactMessages(messages, { storage: "file:///var/tmp/ctx-zip" });
// Or construct an adapter
import { FileStorageAdapter } from "ctx-zip";
await compactMessages(messages, {
storage: new FileStorageAdapter({ baseDir: "/var/tmp/ctx-zip" }),
});
blob: (optionally blob://prefix)BLOB_READ_WRITE_TOKEN (this single token is sufficient)Examples:
// Use a URI (requires BLOB_READ_WRITE_TOKEN)
await compactMessages(messages, { storage: "blob:" });
// Or construct an adapter with a prefix
import { VercelBlobStorageAdapter } from "ctx-zip";
await compactMessages(messages, {
storage: new VercelBlobStorageAdapter({ prefix: "my-agent" }),
});
.env example:
# Required for Vercel Blob
BLOB_READ_WRITE_TOKEN=vcblt_rw_...
Adapters implement a minimal interface so you can persist anywhere (S3, Supabase, GCS, Azure Blob, databases, …):
export interface StorageAdapter {
write(params: { key: string; body: string | Uint8Array; contentType?: string }): Promise<{ key: string; url?: string }>;
readText?(params: { key: string }): Promise<string>;
openReadStream?(params: { key: string }): Promise<NodeJS.ReadableStream>;
resolveKey(name: string): string; // map a file name to a storage key/path
toString(): string; // human-readable URI (e.g., "blob://prefix")
}
Example: S3 (sketch):
import { S3Client, PutObjectCommand, HeadObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import type { StorageAdapter } from "ctx-zip";
class S3StorageAdapter implements StorageAdapter {
constructor(private bucket: string, private prefix = "") {}
resolveKey(name: string) {
const safe = name.replace(/\\/g, "/").replace(/\.+\//g, "");
return this.prefix ? `${this.prefix.replace(/\/$/, "")}/${safe}` : safe;
}
async write({ key, body, contentType }: { key: string; body: string | Uint8Array; contentType?: string }) {
const s3 = new S3Client({});
const Body = typeof body === "string" ? new TextEncoder().encode(body) : body;
await s3.send(new PutObjectCommand({ Bucket: this.bucket, Key: key, Body, ContentType: contentType }));
return { key, url: `s3://${this.bucket}/${key}` };
}
toString() {
return `s3://${this.bucket}${this.prefix ? "/" + this.prefix : ""}`;
}
}
// Usage
// await compactMessages(messages, { storage: new S3StorageAdapter("my-bucket", "agent-prefix") });
You can apply the same pattern to Supabase Storage, GCS, Azure Blob, or any other service.
boundary: { type: "first-n-messages", count: N }.file://...) to inspect outputs locally, then switch to blob: for production.From ctx-zip:
compactMessages(messages, options) and CompactOptionsdetectWindowStart, messageHasTextContent (advanced)FileStorageAdapter, VercelBlobStorageAdapter, createStorageAdapter(uriOrAdapter)resolveFileUriFromBaseDir, grepObject (advanced)createReadFileTool, createGrepAndSearchFileTool (recognized as reader tools by default)FAQs
Keep your AI agent context small and cheap by managing tool bloat and large outputs.
The npm package ctx-zip receives a total of 25 weekly downloads. As such, ctx-zip popularity was classified as not popular.
We found that ctx-zip demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Following multiple malicious extension incidents, Open VSX outlines new safeguards designed to catch risky uploads earlier.

Research
/Security News
Threat actors compromised four oorzc Open VSX extensions with more than 22,000 downloads, pushing malicious versions that install a staged loader, evade Russian-locale systems, pull C2 from Solana memos, and steal macOS credentials and wallets.

Security News
Lodash 4.17.23 marks a security reset, with maintainers rebuilding governance and infrastructure to support long-term, sustainable maintenance.