From 1ae945b4216e24d20b5aab3b6d5dcb73654c85c5 Mon Sep 17 00:00:00 2001 From: stack72 Date: Tue, 24 Feb 2026 23:35:03 +0000 Subject: [PATCH] docs: document external dependency support in extension models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The extension model skill only mentioned importing zod, but the bundler (introduced in PR #452) resolves all Deno-compatible imports — npm:, jsr:, and https:// URLs. This was undocumented, so users (and Claude) had no guidance on using external packages in their models. Add a 'Using External Dependencies' section to the examples reference with: - A verified lodash-es example showing npm: imports in action - How bundling works (deno bundle, mtime cache, zod externalization) - Import rules table covering all supported specifiers Update SKILL.md Key Rules to mention external imports are supported, linking to the reference for details. Keeps SKILL.md lean per skill-creator guidelines. --- .claude/skills/swamp-extension-model/SKILL.md | 5 +- .../references/examples.md | 88 +++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/.claude/skills/swamp-extension-model/SKILL.md b/.claude/skills/swamp-extension-model/SKILL.md index aa27a094..6bffe6a6 100644 --- a/.claude/skills/swamp-extension-model/SKILL.md +++ b/.claude/skills/swamp-extension-model/SKILL.md @@ -307,7 +307,10 @@ Files are classified by export name: `export const model` defines new types, 1. **Export**: Use `export const model = { ... }` for new types or `export const extension = { ... }` for extending existing types -2. **Import**: Only `import { z } from "npm:zod@4";` is needed +2. **Import**: `import { z } from "npm:zod@4";` is always required. Any + Deno-compatible import (`npm:`, `jsr:`, `https://`) can also be used — swamp + bundles all dependencies automatically (see + [references/examples.md](references/examples.md#using-external-dependencies)) 3. **Type naming**: Use `@/` format (e.g., `@user/my-model`) 4. **No type annotations**: Avoid TypeScript types in execute parameters 5. **File naming**: Use snake_case (`my_model.ts`) diff --git a/.claude/skills/swamp-extension-model/references/examples.md b/.claude/skills/swamp-extension-model/references/examples.md index 0360ac42..17f40e4f 100644 --- a/.claude/skills/swamp-extension-model/references/examples.md +++ b/.claude/skills/swamp-extension-model/references/examples.md @@ -9,8 +9,96 @@ - [Data Chaining Model](#data-chaining-model) - [Shell Command with Streamed Logging](#shell-command-with-streamed-logging) - [AWS Model with Pre-flight Credential Check](#aws-model-with-pre-flight-credential-check) +- [Using External Dependencies](#using-external-dependencies) - [Extending Existing Model Types](#extending-existing-model-types) +## Using External Dependencies + +Extension models are written in TypeScript and can import any package using +Deno's import specifiers: `npm:`, `jsr:`, or `https://` URLs. Swamp +automatically bundles the TypeScript source and all dependencies into a single +JavaScript file at startup — no install step required. + +The bundler resolves and inlines all dependencies except `zod`, which is shared +with swamp to preserve schema `instanceof` checks. + +```typescript +// extensions/models/text_analyzer.ts +import { z } from "npm:zod@4"; +import { countBy, sortBy, words } from "npm:lodash-es"; + +const GlobalArgsSchema = z.object({ + text: z.string().describe("Text to analyze"), +}); + +const AnalysisSchema = z.object({ + wordCount: z.number(), + topWords: z.array(z.object({ + word: z.string(), + count: z.number(), + })), + analyzedAt: z.string(), +}); + +export const model = { + type: "@user/text-analyzer", + version: "2026.02.24.1", + globalArguments: GlobalArgsSchema, + resources: { + "analysis": { + description: "Text analysis results", + schema: AnalysisSchema, + lifetime: "infinite", + garbageCollection: 10, + }, + }, + methods: { + analyze: { + description: "Analyze word frequency in the text", + arguments: z.object({ + topN: z.number().default(5), + }), + execute: async (args, context) => { + const allWords = words(context.globalArgs.text.toLowerCase()); + const counts = countBy(allWords); + const sorted = sortBy( + Object.entries(counts).map(([word, count]) => ({ word, count })), + (entry) => -entry.count, + ); + + const handle = await context.writeResource("analysis", "analysis", { + wordCount: allWords.length, + topWords: sorted.slice(0, args.topN), + analyzedAt: new Date().toISOString(), + }); + return { dataHandles: [handle] }; + }, + }, + }, +}; +``` + +**How bundling works:** + +- On first run (or after editing), swamp runs `deno bundle` to transpile your + TypeScript and resolve all `npm:` imports into a single `.js` file +- The bundle is cached to `.swamp/bundles/` — subsequent runs skip bundling + unless the source file's modification time is newer than the cached bundle +- `npm:zod` is externalized (not bundled) so your model shares the same zod + instance as swamp, which is required for schema validation to work +- All other npm imports are fully resolved and inlined into the bundle + +**Import rules:** + +| Import | Bundled? | Notes | +| ---------------------------------------- | -------- | -------------------------------- | +| `npm:zod@4` | No | Shared with swamp (externalized) | +| `npm:lodash-es` | Yes | Resolved and inlined | +| `npm:@aws-sdk/client-s3` | Yes | Resolved and inlined | +| `jsr:@std/path` | Yes | Resolved and inlined | +| `https://deno.land/[email protected]/async/..` | Yes | Resolved and inlined | +| Any other Deno-compatible import | Yes | Resolved and inlined | + ## CRUD Lifecycle Model (VPC) Models that manage real resources typically have `create`, `update`, and