modular-agent-examples 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/chunking-demo.ts +339 -0
  2. package/cleanup-duplicates.ts +142 -0
  3. package/data/flower.jpg +0 -0
  4. package/generative.ts +128 -0
  5. package/graph/context-example.ts +209 -0
  6. package/graph/data-pipeline/agents.ts +60 -0
  7. package/graph/data-pipeline/fetchers.ts +166 -0
  8. package/graph/data-pipeline/index.ts +282 -0
  9. package/graph/index.ts +154 -0
  10. package/graph/map-example.ts +227 -0
  11. package/graph/metrics-example.ts +238 -0
  12. package/graph/parallel-example.ts +167 -0
  13. package/graph/pipeline-example.ts +225 -0
  14. package/graph/planning-example.ts +406 -0
  15. package/graph/router-example.ts +226 -0
  16. package/graph/sequential-example.ts +141 -0
  17. package/graph/voting-example.ts +159 -0
  18. package/graph-rag/docker-compose.yaml +14 -0
  19. package/graph-rag/index.js +99 -0
  20. package/graph-rag/init-db.sh +7 -0
  21. package/graph-rag/package.json +15 -0
  22. package/history-compression-example.ts +163 -0
  23. package/history-persistence.ts +347 -0
  24. package/index.ts +175 -0
  25. package/ingestion-pipeline.ts +353 -0
  26. package/mcp-airbnb-example.ts +69 -0
  27. package/mcp-http-example.ts +70 -0
  28. package/mcp-stdio-example.ts +63 -0
  29. package/multimodal.ts +144 -0
  30. package/ollama.ts +148 -0
  31. package/openai-compatible.ts +141 -0
  32. package/opensearch-vector-store.ts +342 -0
  33. package/package.json +24 -0
  34. package/pubmed.ts +289 -0
  35. package/reasoning-with-sub-agent.ts +311 -0
  36. package/synchronous/index.ts +48 -0
  37. package/tsconfig.json +8 -0
  38. package/vector-store-filtering.ts +303 -0
  39. package/vector-store.ts +210 -0
  40. package/vectorstore/index.ts +0 -0
  41. package/vectorstore/store/dbService.ts +80 -0
  42. package/voyage-embeddings.ts +99 -0
  43. package/weather-with-sub-agent.ts +276 -0
  44. package/weather.ts +389 -0
@@ -0,0 +1,353 @@
1
+ /**
2
+ * Ingestion Pipeline Example
3
+ *
4
+ * Demonstrates how to use the chunkers and ingestion pipeline
5
+ * to process documents and store them in a vector database.
6
+ *
7
+ * Prerequisites:
8
+ * - Set OPENAI_API_KEY environment variable for full pipeline example
9
+ * - Run with: npm run example -- examples/ingestion-pipeline.ts
10
+ */
11
+ import "dotenv/config";
12
+ import {
13
+ TextChunker,
14
+ RecursiveChunker,
15
+ TokenChunker,
16
+ IngestionPipeline,
17
+ } from "../lib";
18
+ import { OpenAIEmbeddings } from "../lib/embeddings/OpenAIEmbeddings";
19
+ import { LanceDBVectorStore } from "../lib/vectorstore/LanceDBVectorStore";
20
+
21
+ async function main() {
22
+ console.log("=== Text Chunker Examples ===\n");
23
+
24
+ // Sample document
25
+ const sampleDoc = `# Introduction
26
+
27
+ This is a sample document that demonstrates the text chunking capabilities.
28
+ The chunker will split this document into smaller pieces for vector storage.
29
+
30
+ ## Features
31
+
32
+ The chunking system supports multiple strategies:
33
+
34
+ 1. TextChunker - Simple character-based splitting with overlap
35
+ 2. RecursiveChunker - Smart splitting on semantic boundaries
36
+ 3. TokenChunker - Token-aware splitting for LLM compatibility
37
+
38
+ ## Usage
39
+
40
+ To use a chunker, simply create an instance with your desired configuration
41
+ and call the chunk method with your text.
42
+
43
+ ### Example Code
44
+
45
+ Here's how you might use the RecursiveChunker:
46
+
47
+ \`\`\`typescript
48
+ const chunker = new RecursiveChunker({
49
+ chunkSize: 500,
50
+ chunkOverlap: 50,
51
+ });
52
+
53
+ const chunks = await chunker.chunk(document);
54
+ \`\`\`
55
+
56
+ ## Conclusion
57
+
58
+ Text chunking is an essential step in building RAG applications.
59
+ Choose the right chunker based on your use case and document types.`;
60
+
61
+ // Example 1: TextChunker
62
+ console.log("1. TextChunker (character-based)");
63
+ console.log("-".repeat(40));
64
+
65
+ const textChunker = new TextChunker({
66
+ chunkSize: 200,
67
+ chunkOverlap: 20,
68
+ });
69
+
70
+ const textChunks = await textChunker.chunk(sampleDoc, {
71
+ sourceId: "sample-doc",
72
+ sourcePath: "/docs/sample.md",
73
+ });
74
+
75
+ console.log(`Created ${textChunks.length} chunks`);
76
+ console.log(`First chunk (${textChunks[0].metadata.char_count} chars):`);
77
+ console.log(textChunks[0].content.substring(0, 100) + "...\n");
78
+
79
+ // Example 2: RecursiveChunker
80
+ console.log("2. RecursiveChunker (semantic boundaries)");
81
+ console.log("-".repeat(40));
82
+
83
+ const recursiveChunker = new RecursiveChunker({
84
+ chunkSize: 300,
85
+ chunkOverlap: 30,
86
+ separators: ["\n\n", "\n", ". ", " "],
87
+ });
88
+
89
+ const recursiveChunks = await recursiveChunker.chunk(sampleDoc, {
90
+ sourceId: "sample-doc",
91
+ metadata: { type: "documentation" },
92
+ });
93
+
94
+ console.log(`Created ${recursiveChunks.length} chunks`);
95
+ for (let i = 0; i < Math.min(3, recursiveChunks.length); i++) {
96
+ const chunk = recursiveChunks[i];
97
+ console.log(
98
+ ` Chunk ${i}: ${chunk.metadata.char_count} chars, section: ${chunk.metadata.section ?? "N/A"}`
99
+ );
100
+ }
101
+ console.log();
102
+
103
+ // Example 3: TokenChunker
104
+ console.log("3. TokenChunker (token-aware)");
105
+ console.log("-".repeat(40));
106
+
107
+ const tokenChunker = new TokenChunker({
108
+ chunkSize: 100, // 100 tokens
109
+ chunkOverlap: 10,
110
+ });
111
+
112
+ const tokenChunks = await tokenChunker.chunk(sampleDoc, {
113
+ sourceId: "sample-doc",
114
+ });
115
+
116
+ console.log(`Created ${tokenChunks.length} chunks`);
117
+ for (let i = 0; i < Math.min(3, tokenChunks.length); i++) {
118
+ const chunk = tokenChunks[i];
119
+ console.log(
120
+ ` Chunk ${i}: ~${chunk.metadata.token_count} tokens, ${chunk.metadata.char_count} chars`
121
+ );
122
+ }
123
+ console.log();
124
+
125
+ // Example 4: Chunk with custom processor
126
+ console.log("4. Chunker with custom processor (filtering)");
127
+ console.log("-".repeat(40));
128
+
129
+ const filteringChunker = new TextChunker({
130
+ chunkSize: 150,
131
+ chunkProcessor: (chunk) => {
132
+ // Filter out very short chunks
133
+ if (chunk.content.trim().length < 50) {
134
+ console.log(
135
+ ` Filtered out short chunk: "${chunk.content
136
+ .trim()
137
+ .substring(0, 30)}..."`
138
+ );
139
+ return null;
140
+ }
141
+ // Add custom metadata
142
+ return {
143
+ ...chunk,
144
+ metadata: {
145
+ ...chunk.metadata,
146
+ processedAt: new Date().toISOString(),
147
+ },
148
+ };
149
+ },
150
+ });
151
+
152
+ const filteredChunks = await filteringChunker.chunk(sampleDoc);
153
+ console.log(`Created ${filteredChunks.length} chunks after filtering\n`);
154
+
155
+ // Example 5: Chunk linking
156
+ console.log("5. Chunk linking (previous/next)");
157
+ console.log("-".repeat(40));
158
+
159
+ const linkingChunker = new TextChunker({ chunkSize: 15 });
160
+ const linkedChunks = await linkingChunker.chunk(
161
+ "First part. Second part. Third part. Fourth part.",
162
+ {
163
+ sourceId: "linked-doc",
164
+ }
165
+ );
166
+
167
+ for (const chunk of linkedChunks) {
168
+ console.log(` ${chunk.id}:`);
169
+ console.log(` prev: ${chunk.metadata.prev_id ?? "null"}`);
170
+ console.log(` next: ${chunk.metadata.next_id ?? "null"}`);
171
+ }
172
+ console.log();
173
+
174
+ // Example 6: Full ingestion pipeline (requires API keys)
175
+ console.log("6. Full Ingestion Pipeline");
176
+ console.log("-".repeat(40));
177
+
178
+ if (!process.env.OPENAI_API_KEY) {
179
+ console.log("Skipping ingestion pipeline (OPENAI_API_KEY not set)");
180
+ console.log("Set OPENAI_API_KEY to run the full pipeline example.\n");
181
+ } else {
182
+ try {
183
+ // Create components
184
+ const embeddings = new OpenAIEmbeddings({
185
+ model: "text-embedding-3-small",
186
+ });
187
+
188
+ const store = await LanceDBVectorStore.create({
189
+ name: "ingestion-demo",
190
+ uri: "./data/ingestion-demo",
191
+ tableName: "documents",
192
+ embeddings,
193
+ metadataFields: [
194
+ { name: "author", type: "string" as const, nullable: true },
195
+ ],
196
+ });
197
+
198
+ const pipeline = new IngestionPipeline(
199
+ new RecursiveChunker({ chunkSize: 500, chunkOverlap: 50 }),
200
+ embeddings,
201
+ store
202
+ );
203
+
204
+ // Ingest the sample document — table is created here on first insert
205
+ const result = await pipeline.ingest(sampleDoc, {
206
+ sourceId: "sample-doc",
207
+ sourcePath: "/docs/sample.md",
208
+ metadata: {
209
+ author: "demo",
210
+ createdAt: new Date().toISOString(),
211
+ },
212
+ batchSize: 10,
213
+ onProgress: ({ phase, processed, total }) => {
214
+ console.log(` ${phase}: ${processed}/${total}`);
215
+ },
216
+ });
217
+
218
+ console.log("\nIngestion complete:");
219
+ console.log(` Chunks processed: ${result.chunksProcessed}`);
220
+ console.log(` Chunks stored: ${result.chunksStored}`);
221
+ console.log(` Duration: ${result.duration}ms`);
222
+ console.log(` Errors: ${result.errors.length}`);
223
+ for (const e of result.errors) {
224
+ console.error(" Error:", e.error);
225
+ }
226
+
227
+ // Schema is now available after first insert
228
+ const tableSchema = await store.getTable()!.schema();
229
+ console.log(
230
+ "Table schema fields:",
231
+ tableSchema.fields.map((f) => `${f.name}:${f.type}`).join(", ")
232
+ );
233
+
234
+ // Inspect a raw stored row
235
+ const rawRows = await store.getTable()!.query().limit(1).toArray();
236
+ console.log(
237
+ "Raw row sample:",
238
+ JSON.stringify(rawRows[0], (_, v) =>
239
+ v instanceof Float32Array ? "<vector>" : v
240
+ )
241
+ );
242
+
243
+ // Test search
244
+ console.log("\nTesting search...");
245
+ const searchResults = await store.search("chunking strategies", {
246
+ limit: 3,
247
+ });
248
+
249
+ console.log(`Found ${searchResults.length} results:`);
250
+ for (const result of searchResults) {
251
+ console.log(` - Score: ${result.score.toFixed(3)}`);
252
+ console.log(
253
+ ` Content: ${result.document.content.substring(0, 80)}...`
254
+ );
255
+ // User metadata (author) is a top-level column; chunk metadata is in chunk_metadata struct
256
+ console.log(` author: ${result.document.metadata?.author}`);
257
+ }
258
+
259
+ // Search with metadata filter on user-defined columns
260
+ console.log("\nTesting search with metadata filter...");
261
+ const filtered = await store.search("chunking", {
262
+ limit: 3,
263
+ filter: { author: "demo" },
264
+ });
265
+ console.log(`Found ${filtered.length} results filtered by author='demo'`);
266
+ } catch (error) {
267
+ console.error("Pipeline error:", error);
268
+ }
269
+ }
270
+
271
+ // Example 7: Batch ingestion
272
+ console.log("\n7. Batch Document Ingestion");
273
+ console.log("-".repeat(40));
274
+
275
+ if (!process.env.OPENAI_API_KEY) {
276
+ console.log("Skipping batch ingestion (OPENAI_API_KEY not set)\n");
277
+ } else {
278
+ try {
279
+ const embeddings = new OpenAIEmbeddings({
280
+ model: "text-embedding-3-small",
281
+ });
282
+
283
+ const store = await LanceDBVectorStore.create({
284
+ name: "batch-demo",
285
+ uri: "./data/batch-demo",
286
+ tableName: "documents",
287
+ embeddings,
288
+ metadataFields: [
289
+ { name: "category", type: "string" as const, nullable: true },
290
+ ],
291
+ });
292
+
293
+ const pipeline = new IngestionPipeline(
294
+ new RecursiveChunker({ chunkSize: 300 }),
295
+ embeddings,
296
+ store
297
+ );
298
+
299
+ // Simulate multiple documents
300
+ const documents = [
301
+ {
302
+ text: "Document 1: Introduction to machine learning concepts and algorithms.",
303
+ options: { sourceId: "doc-1", metadata: { category: "ml" } },
304
+ },
305
+ {
306
+ text: "Document 2: Deep learning fundamentals including neural networks and backpropagation.",
307
+ options: { sourceId: "doc-2", metadata: { category: "dl" } },
308
+ },
309
+ {
310
+ text: "Document 3: Natural language processing techniques for text analysis.",
311
+ options: { sourceId: "doc-3", metadata: { category: "nlp" } },
312
+ },
313
+ ];
314
+
315
+ const result = await pipeline.ingestMany(documents, {
316
+ batchSize: 5,
317
+ onProgress: ({ phase, processed, total }) => {
318
+ console.log(` ${phase}: ${processed}/${total}`);
319
+ },
320
+ });
321
+
322
+ console.log("\nBatch ingestion complete:");
323
+ console.log(` Total chunks: ${result.chunksStored}`);
324
+ console.log(` Duration: ${result.duration}ms`);
325
+
326
+ const batchTableSchema = await store.getTable()!.schema();
327
+ console.log(
328
+ "Table schema fields:",
329
+ batchTableSchema.fields.map((f) => `${f.name}:${f.type}`).join(", ")
330
+ );
331
+
332
+ // The 'category' user metadata is a top-level column — filterable directly
333
+ console.log("\nTesting search with category filter...");
334
+ const mlResults = await store.search("neural networks", {
335
+ limit: 3,
336
+ filter: { category: "dl" },
337
+ });
338
+ console.log(
339
+ `Found ${mlResults.length} results filtered by category='dl'`
340
+ );
341
+ for (const r of mlResults) {
342
+ console.log(` - category: ${r.document.metadata?.category}`);
343
+ console.log(` ${r.document.content.substring(0, 60)}...`);
344
+ }
345
+ } catch (error) {
346
+ console.error("Batch ingestion error:", error);
347
+ }
348
+ }
349
+
350
+ console.log("\n=== Examples Complete ===");
351
+ }
352
+
353
+ main().catch(console.error);
@@ -0,0 +1,69 @@
1
+ import "dotenv/config";
2
+
3
+ /**
4
+ * MCP Airbnb Example
5
+ *
6
+ * Demonstrates using the Airbnb MCP server to search listings and retrieve
7
+ * property details via an AI agent.
8
+ *
9
+ * The @openbnb/mcp-server-airbnb server exposes two tools:
10
+ * - airbnb_search — search listings by location, dates, guests, price
11
+ * - airbnb_listing_details — get full details for a specific listing ID
12
+ *
13
+ * No API key required. Respects Airbnb's robots.txt by default.
14
+ *
15
+ * Prerequisites:
16
+ * npm install @modelcontextprotocol/sdk @anthropic-ai/sdk
17
+ *
18
+ * Usage:
19
+ * ANTHROPIC_API_KEY=sk-ant-... ts-node examples/mcp-airbnb-example.ts
20
+ */
21
+
22
+ import { MCPClient } from "../lib/mcp";
23
+ import { ClaudeAgent } from "../lib/agents/anthropic/ClaudeAgent";
24
+
25
+ async function main() {
26
+ const apiKey = process.env.ANTHROPIC_API_KEY;
27
+ if (!apiKey) {
28
+ throw new Error("ANTHROPIC_API_KEY environment variable is required");
29
+ }
30
+
31
+ // Spawn the Airbnb MCP server as a local process via npx
32
+ const mcp = MCPClient.fromStdio({
33
+ command: "npx",
34
+ args: ["-y", "@openbnb/mcp-server-airbnb", "--ignore-robots-txt"],
35
+ });
36
+
37
+ console.log("Connecting to Airbnb MCP server...");
38
+ await mcp.connect();
39
+
40
+ const tools = mcp.getTools();
41
+ console.log(
42
+ `Discovered ${tools.length} tools: ${tools.map((t) => t.name).join(", ")}\n`
43
+ );
44
+
45
+ const agent = new ClaudeAgent({
46
+ id: "airbnb-agent",
47
+ name: "Airbnb Agent",
48
+ description:
49
+ "You are a helpful travel assistant. Use the available tools to search Airbnb listings and provide detailed recommendations based on the user's needs.",
50
+ apiKey,
51
+ model: "claude-sonnet-4-6",
52
+ maxTokens: 10000,
53
+ tools,
54
+ });
55
+
56
+ const result = await agent.execute(
57
+ "Find me a nice place to stay Near Weymouth for 2 adults " +
58
+ "checking in on 2026-03-15 and checking out on 2026-03-20. " +
59
+ "Give me the top 3 options with prices and a brief description of each."
60
+ );
61
+
62
+ console.log("Agent response:\n");
63
+ console.log(result);
64
+
65
+ await mcp.disconnect();
66
+ console.log("\nDisconnected from Airbnb MCP server.");
67
+ }
68
+
69
+ main().catch(console.error);
@@ -0,0 +1,70 @@
1
+ import "dotenv/config";
2
+ /**
3
+ * MCP HTTP Example
4
+ *
5
+ * Demonstrates connecting to a remote MCP server via Streamable HTTP transport.
6
+ * Shows both static API key auth (via headers) and the OAuth provider pattern.
7
+ *
8
+ * Prerequisites:
9
+ * npm install @modelcontextprotocol/sdk @anthropic-ai/sdk
10
+ *
11
+ * Usage:
12
+ * MCP_SERVER_URL=http://localhost:3000/mcp \
13
+ * MCP_API_KEY=my-key \
14
+ * ANTHROPIC_API_KEY=sk-ant-... \
15
+ * ts-node examples/mcp-http-example.ts
16
+ */
17
+
18
+ import { MCPClient } from "../lib/mcp";
19
+ import { ClaudeAgent } from "../lib/agents/anthropic/ClaudeAgent";
20
+
21
+ async function main() {
22
+ const apiKey = process.env.ANTHROPIC_API_KEY;
23
+ const mcpServerUrl =
24
+ process.env.MCP_SERVER_URL ?? "http://localhost:3000/mcp";
25
+ const mcpApiKey = process.env.MCP_API_KEY;
26
+
27
+ if (!apiKey) {
28
+ throw new Error("ANTHROPIC_API_KEY environment variable is required");
29
+ }
30
+
31
+ // Connect to a remote MCP server with a static API key in the Authorization header.
32
+ //
33
+ // For OAuth-based servers, use the authProvider option instead:
34
+ //
35
+ // import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
36
+ // const mcp = MCPClient.fromUrl(mcpServerUrl, { authProvider: myOAuthProvider });
37
+ //
38
+ const mcp = MCPClient.fromUrl(mcpServerUrl, {
39
+ ...(mcpApiKey ? { headers: { Authorization: `Bearer ${mcpApiKey}` } } : {}),
40
+ });
41
+
42
+ console.log(`Connecting to MCP server at ${mcpServerUrl}...`);
43
+ await mcp.connect();
44
+
45
+ const tools = mcp.getTools();
46
+ console.log(
47
+ `Discovered ${tools.length} MCP tools: ${tools
48
+ .map((t) => t.name)
49
+ .join(", ")}\n`
50
+ );
51
+
52
+ const agent = new ClaudeAgent({
53
+ id: "mcp-http-agent",
54
+ name: "Remote MCP Agent",
55
+ description: "An agent that uses tools from a remote MCP server.",
56
+ apiKey,
57
+ model: "claude-haiku-4-5-20251001",
58
+ tools,
59
+ });
60
+
61
+ const result = await agent.execute(
62
+ "What tools do you have available? Briefly describe each one."
63
+ );
64
+ console.log("Agent response:\n", result);
65
+
66
+ await mcp.disconnect();
67
+ console.log("\nDisconnected from MCP server.");
68
+ }
69
+
70
+ main().catch(console.error);
@@ -0,0 +1,63 @@
1
+ import "dotenv/config";
2
+
3
+ /**
4
+ * MCP Stdio Example
5
+ *
6
+ * Demonstrates connecting to a local MCP server process via stdio and using its
7
+ * tools with a ClaudeAgent.
8
+ *
9
+ * This example uses the @modelcontextprotocol/server-filesystem MCP server, which
10
+ * exposes filesystem operations (read_file, write_file, list_directory, etc.) as tools.
11
+ *
12
+ * Prerequisites:
13
+ * npm install @modelcontextprotocol/sdk @anthropic-ai/sdk
14
+ *
15
+ * Usage:
16
+ * ANTHROPIC_API_KEY=sk-ant-... ts-node examples/mcp-stdio-example.ts
17
+ */
18
+
19
+ import { MCPClient } from "../lib/mcp";
20
+ import { ClaudeAgent } from "../lib/agents/anthropic/ClaudeAgent";
21
+
22
+ async function main() {
23
+ const apiKey = process.env.ANTHROPIC_API_KEY;
24
+ if (!apiKey) {
25
+ throw new Error("ANTHROPIC_API_KEY environment variable is required");
26
+ }
27
+
28
+ // Connect to the filesystem MCP server via stdio (spawned as a child process)
29
+ const mcp = MCPClient.fromStdio({
30
+ command: "npx",
31
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
32
+ });
33
+
34
+ console.log("Connecting to MCP filesystem server...");
35
+ await mcp.connect();
36
+
37
+ const tools = mcp.getTools();
38
+ console.log(
39
+ `Discovered ${tools.length} MCP tools: ${tools
40
+ .map((t) => t.name)
41
+ .join(", ")}\n`
42
+ );
43
+
44
+ const agent = new ClaudeAgent({
45
+ id: "mcp-file-agent",
46
+ name: "File Agent",
47
+ description:
48
+ "An agent that can explore and read files using MCP filesystem tools.",
49
+ apiKey,
50
+ model: "claude-haiku-4-5-20251001",
51
+ tools,
52
+ });
53
+
54
+ const result = await agent.execute(
55
+ "List the contents of /tmp and describe what you find."
56
+ );
57
+ console.log("Agent response:\n", result);
58
+
59
+ await mcp.disconnect();
60
+ console.log("\nDisconnected from MCP server.");
61
+ }
62
+
63
+ main().catch(console.error);
package/multimodal.ts ADDED
@@ -0,0 +1,144 @@
1
+ import "dotenv/config";
2
+ /**
3
+ * Multimodal example — sending images to Claude and OpenAI
4
+ *
5
+ * Run: ANTHROPIC_API_KEY=... npx ts-node examples/multimodal.ts
6
+ *
7
+ * Images are always passed directly to execute() as a MessageContent array.
8
+ * Use imageUrl() for remote URLs and imageBase64() for local files.
9
+ */
10
+
11
+ import * as fs from "fs";
12
+ import * as path from "path";
13
+ import { ClaudeAgent } from "../lib/agents/anthropic/ClaudeAgent";
14
+ import { OpenAiAgent } from "../lib/agents/openai/OpenAiAgent";
15
+ import { imageUrl, imageBase64 } from "../lib/history/History";
16
+ const plantPath = path.join(__dirname, "data", "flower.jpg");
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // 1. URL image via execute(MessageContent[])
20
+ // ---------------------------------------------------------------------------
21
+
22
+ async function exampleUrl() {
23
+ const agent = new ClaudeAgent({
24
+ id: "vision-url",
25
+ name: "VisionAgent",
26
+ description: "An agent that can analyse images",
27
+ apiKey: process.env.ANTHROPIC_API_KEY!,
28
+ model: "claude-opus-4-6",
29
+ maxTokens: 512,
30
+ });
31
+
32
+ const response = await agent.execute([
33
+ imageUrl(
34
+ "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png"
35
+ ),
36
+ { type: "text", text: "What do you see in this image? One sentence." },
37
+ ]);
38
+
39
+ console.log("[URL example]\n", response, "\n");
40
+ }
41
+ // ---------------------------------------------------------------------------
42
+ // 3. Multiple images — compare two images in one turn
43
+ // ---------------------------------------------------------------------------
44
+
45
+ async function exampleMultiImage() {
46
+ const agent = new ClaudeAgent({
47
+ id: "vision-multi",
48
+ name: "VisionAgent",
49
+ description: "An agent that can analyse images",
50
+ apiKey: process.env.ANTHROPIC_API_KEY!,
51
+ model: "claude-opus-4-6",
52
+ maxTokens: 512,
53
+ });
54
+
55
+ const response = await agent.execute([
56
+ { type: "text", text: "Compare these two images in one sentence:" },
57
+ imageUrl(
58
+ "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png"
59
+ ),
60
+ imageUrl(
61
+ "https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/Gatto_europeo4.jpg/320px-Gatto_europeo4.jpg"
62
+ ),
63
+ ]);
64
+
65
+ console.log("[Multi-image example]\n", response, "\n");
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // 4. Plant identification — local JPEG via base64
70
+ // Drop a photo at examples/data/plant.jpg to run this example.
71
+ // ---------------------------------------------------------------------------
72
+
73
+ async function examplePlantIdentification() {
74
+ if (!fs.existsSync(plantPath)) {
75
+ console.log(
76
+ "[Plant example] skipped — add a photo at examples/data/plant.jpg\n"
77
+ );
78
+ return;
79
+ }
80
+
81
+ const agent = new ClaudeAgent({
82
+ id: "vision-plant",
83
+ name: "PlantAgent",
84
+ description: "An agent that identifies plants from photos",
85
+ apiKey: process.env.ANTHROPIC_API_KEY!,
86
+ model: "claude-opus-4-6",
87
+ maxTokens: 512,
88
+ });
89
+
90
+ const data = fs.readFileSync(plantPath).toString("base64");
91
+
92
+ const response = await agent.execute([
93
+ imageBase64(data, "image/jpeg"),
94
+ {
95
+ type: "text",
96
+ text: "What plant is this? Give the common name, scientific name, and one care tip.",
97
+ },
98
+ ]);
99
+
100
+ console.log("[Plant example]\n", response, "\n");
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // 5. OpenAI — same MessageContent[] interface works across providers
105
+ // ---------------------------------------------------------------------------
106
+
107
+ async function exampleOpenAi() {
108
+ if (!process.env.OPENAI_API_KEY) {
109
+ console.log("[OpenAI example] skipped — OPENAI_API_KEY not set\n");
110
+ return;
111
+ }
112
+
113
+ const agent = new OpenAiAgent({
114
+ id: "vision-openai",
115
+ name: "VisionAgent",
116
+ description: "An agent that can analyse images",
117
+ apiKey: process.env.OPENAI_API_KEY!,
118
+ model: "gpt-4o-mini",
119
+ maxTokens: 512,
120
+ });
121
+
122
+ const data = fs.readFileSync(plantPath).toString("base64");
123
+
124
+ const response = await agent.execute([
125
+ imageBase64(data, "image/jpeg"),
126
+ {
127
+ type: "text",
128
+ text: "What plant is this? Give the common name, scientific name, and one care tip.",
129
+ },
130
+ ]);
131
+
132
+ console.log("[OpenAI example]\n", response, "\n");
133
+ }
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // Run
137
+ // ---------------------------------------------------------------------------
138
+
139
+ (async () => {
140
+ await exampleUrl();
141
+ // await exampleMultiImage();
142
+ await examplePlantIdentification();
143
+ await exampleOpenAi();
144
+ })().catch(console.error);