catelog 1.0.0 → 1.0.2
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.
- package/DECISION.md +183 -183
- package/README.md +98 -74
- package/USAGE.md +958 -0
- package/dist/adapters.d.ts +84 -0
- package/dist/adapters.d.ts.map +1 -0
- package/dist/adapters.js +267 -0
- package/dist/adapters.js.map +1 -0
- package/dist/agent.d.ts +73 -13
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +373 -154
- package/dist/agent.js.map +1 -1
- package/dist/banner.d.ts +6 -3
- package/dist/banner.d.ts.map +1 -1
- package/dist/banner.js +29 -18
- package/dist/banner.js.map +1 -1
- package/dist/context.d.ts +18 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +82 -0
- package/dist/context.js.map +1 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -6
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +4 -4
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +46 -20
- package/dist/schema.js.map +1 -1
- package/dist/security.d.ts +25 -16
- package/dist/security.d.ts.map +1 -1
- package/dist/security.js +132 -55
- package/dist/security.js.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +8 -3
- package/dist/server.js.map +1 -1
- package/dist/sse.d.ts +31 -7
- package/dist/sse.d.ts.map +1 -1
- package/dist/sse.js +94 -26
- package/dist/sse.js.map +1 -1
- package/dist/telemetry.d.ts +41 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +93 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/types.d.ts +115 -27
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -1
- package/package.json +5 -2
package/USAGE.md
ADDED
|
@@ -0,0 +1,958 @@
|
|
|
1
|
+
# Catelog SDK — Usage Guide
|
|
2
|
+
|
|
3
|
+
> **Node.js 18+ · TypeScript 5+ · ESM**
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
1. [Installation](#1-installation)
|
|
10
|
+
2. [Environment Setup](#2-environment-setup)
|
|
11
|
+
3. [The Minimal Agent](#3-the-minimal-agent)
|
|
12
|
+
4. [Agent Types](#4-agent-types)
|
|
13
|
+
- [Sync Agent](#41-sync-agent--fast-direct-response)
|
|
14
|
+
- [Async Agent](#42-async-agent--long-running-background-jobs)
|
|
15
|
+
- [Conversational Agent](#43-conversational-agent--multi-turn--hitl)
|
|
16
|
+
5. [Framework Integrations](#5-framework-integrations)
|
|
17
|
+
- [OpenAI SDK](#51-openai-sdk)
|
|
18
|
+
- [Vercel AI SDK](#52-vercel-ai-sdk)
|
|
19
|
+
- [LangGraph / LangChain](#53-langgraph--langchain)
|
|
20
|
+
- [CrewAI / Custom Generators](#54-crewaicustom-async-generator)
|
|
21
|
+
- [Vanilla TypeScript](#55-vanilla-typescript-no-framework)
|
|
22
|
+
6. [Streaming Helpers](#6-streaming-helpers)
|
|
23
|
+
7. [Schema Definition](#7-schema-definition)
|
|
24
|
+
8. [API Keys](#8-api-keys)
|
|
25
|
+
9. [Telemetry & Logging](#9-telemetry--logging)
|
|
26
|
+
10. [What You Get — Endpoints](#10-what-you-get--endpoints)
|
|
27
|
+
11. [Testing Locally](#11-testing-locally)
|
|
28
|
+
12. [Registering on the Marketplace](#12-registering-on-the-marketplace)
|
|
29
|
+
13. [Error Handling](#13-error-handling)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 1. Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# pnpm (recommended)
|
|
37
|
+
pnpm add catelog zod
|
|
38
|
+
|
|
39
|
+
# npm
|
|
40
|
+
npm install catelog zod
|
|
41
|
+
|
|
42
|
+
# yarn
|
|
43
|
+
yarn add catelog zod
|
|
44
|
+
|
|
45
|
+
# bun
|
|
46
|
+
bun add catelog zod
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> **Note:** `zod` is optional. Only needed if you want typed input validation and auto-generated marketplace forms. If you skip it, the SDK accepts any JSON payload.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 2. Environment Setup
|
|
54
|
+
|
|
55
|
+
Create a `.env` file in your project root:
|
|
56
|
+
|
|
57
|
+
```env
|
|
58
|
+
# Required for production authentication
|
|
59
|
+
CATELOG_SECRET_KEY=cat_sec_live_your_secret_key_here
|
|
60
|
+
|
|
61
|
+
# Optional — used for public/read-only endpoint auth
|
|
62
|
+
CATELOG_PUBLIC_KEY=cat_pub_live_your_public_key_here
|
|
63
|
+
|
|
64
|
+
# Optional — telemetry reporting endpoint
|
|
65
|
+
CATELOG_TELEMETRY_URL=https://telemetry.catelog.io/ingest
|
|
66
|
+
|
|
67
|
+
# Optional — disable telemetry entirely
|
|
68
|
+
CATELOG_TELEMETRY_DISABLED=false
|
|
69
|
+
|
|
70
|
+
# Optional — silence the startup banner
|
|
71
|
+
CATELOG_SILENT=false
|
|
72
|
+
|
|
73
|
+
# Standard Node.js — controls dev/production behavior
|
|
74
|
+
NODE_ENV=development
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The SDK auto-reads all of these. You never need to pass them manually in code.
|
|
78
|
+
|
|
79
|
+
> **In development** (`NODE_ENV=development`), you can skip all keys entirely. The SDK runs in permissive dev mode and logs a warning.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 3. The Minimal Agent
|
|
84
|
+
|
|
85
|
+
The absolute minimum to publish a working agent:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { createAgent } from "catelog";
|
|
89
|
+
|
|
90
|
+
const agent = createAgent({
|
|
91
|
+
name: "Hello World",
|
|
92
|
+
async execute({ input }) {
|
|
93
|
+
return { message: `Hello, ${(input as any).name ?? "world"}!` };
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
agent.listen(3000);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
That's it. You get:
|
|
101
|
+
- ✅ `GET /health` — ping probe with capabilities
|
|
102
|
+
- ✅ `GET /schema` — auto-generated agent manifest
|
|
103
|
+
- ✅ `POST /execute` — authenticated execution endpoint
|
|
104
|
+
- ✅ SSE streaming with heartbeat keep-alives
|
|
105
|
+
- ✅ Dev mode auth (no keys required in development)
|
|
106
|
+
- ✅ Beautiful terminal startup banner
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 4. Agent Types
|
|
111
|
+
|
|
112
|
+
Choose the execution mode that matches your agent's behavior.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### 4.1 Sync Agent — Fast, Direct Response
|
|
117
|
+
|
|
118
|
+
**Best for:** Tool calls, Q&A, text generation, summarization, search — anything that completes within ~30 seconds.
|
|
119
|
+
|
|
120
|
+
**How it works:** The client opens a connection, the agent streams events, and the connection closes when the task completes.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { createAgent } from "catelog";
|
|
124
|
+
import { z } from "zod";
|
|
125
|
+
|
|
126
|
+
const agent = createAgent({
|
|
127
|
+
name: "Web Summarizer",
|
|
128
|
+
mode: "sync", // default — can be omitted
|
|
129
|
+
|
|
130
|
+
inputSchema: z.object({
|
|
131
|
+
url: z.string().url().describe("The URL to summarize"),
|
|
132
|
+
}),
|
|
133
|
+
|
|
134
|
+
outputSchema: z.object({
|
|
135
|
+
title: z.string(),
|
|
136
|
+
summary: z.string(),
|
|
137
|
+
readingTimeMinutes: z.number(),
|
|
138
|
+
}),
|
|
139
|
+
|
|
140
|
+
async execute({ input, stream, log, signal }) {
|
|
141
|
+
// Emit progress — does NOT complete the task
|
|
142
|
+
stream.progress(10, "Fetching page content...");
|
|
143
|
+
log.info("Fetching URL", { url: input.url });
|
|
144
|
+
|
|
145
|
+
const html = await fetch(input.url, { signal }).then((r) => r.text());
|
|
146
|
+
|
|
147
|
+
stream.progress(50, "Summarizing with AI...");
|
|
148
|
+
// stream.delta() pipes text live to the buyer UI
|
|
149
|
+
stream.delta("Analyzing page structure...\n");
|
|
150
|
+
|
|
151
|
+
const summary = await summarizeWithAI(html, { signal });
|
|
152
|
+
|
|
153
|
+
stream.progress(90, "Formatting output...");
|
|
154
|
+
stream.delta(summary);
|
|
155
|
+
|
|
156
|
+
// return completes the task automatically
|
|
157
|
+
return {
|
|
158
|
+
output: {
|
|
159
|
+
title: "Example Page",
|
|
160
|
+
summary,
|
|
161
|
+
readingTimeMinutes: Math.ceil(summary.split(" ").length / 200),
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
agent.listen(3000);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**What the marketplace receives:**
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
task.status → "working" (automatic on start)
|
|
174
|
+
task.progress → 10%, "Fetching page content..."
|
|
175
|
+
task.progress → 50%, "Summarizing with AI..."
|
|
176
|
+
task.delta → "Analyzing page structure..."
|
|
177
|
+
task.delta → "The page covers..."
|
|
178
|
+
task.progress → 90%, "Formatting output..."
|
|
179
|
+
task.completed → { title, summary, readingTimeMinutes } + telemetry
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### 4.2 Async Agent — Long-Running Background Jobs
|
|
185
|
+
|
|
186
|
+
**Best for:** Deep research, PDF generation, web scraping, multi-agent workflows — anything that takes more than 30 seconds or risks hitting HTTP gateway timeouts.
|
|
187
|
+
|
|
188
|
+
**How it works:** The SDK immediately returns `202 Accepted` with a job ID. The marketplace polls `GET /tasks/:id` until the job completes. You write normal `async/await` code — the SDK handles the polling infrastructure.
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { createAgent } from "catelog";
|
|
192
|
+
import { z } from "zod";
|
|
193
|
+
|
|
194
|
+
const agent = createAgent({
|
|
195
|
+
name: "Deep Research Agent",
|
|
196
|
+
mode: "async",
|
|
197
|
+
|
|
198
|
+
inputSchema: z.object({
|
|
199
|
+
topic: z.string().describe("Research topic or company name"),
|
|
200
|
+
depth: z.enum(["quick", "deep", "comprehensive"]).default("deep"),
|
|
201
|
+
}),
|
|
202
|
+
|
|
203
|
+
outputSchema: z.object({
|
|
204
|
+
report: z.string(),
|
|
205
|
+
sources: z.array(z.string()),
|
|
206
|
+
wordCount: z.number(),
|
|
207
|
+
}),
|
|
208
|
+
|
|
209
|
+
async execute({ input, log }) {
|
|
210
|
+
// The client already has the 202 + job ID
|
|
211
|
+
// Write your long-running logic normally here:
|
|
212
|
+
log.info("Starting deep research", { topic: input.topic, depth: input.depth });
|
|
213
|
+
|
|
214
|
+
const sources = await crawlAndScrape(input.topic); // May take minutes
|
|
215
|
+
const report = await generateReport(sources, input.depth);
|
|
216
|
+
|
|
217
|
+
log.info("Research complete", { sources: sources.length });
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
output: {
|
|
221
|
+
report,
|
|
222
|
+
sources: sources.map((s) => s.url),
|
|
223
|
+
wordCount: report.split(" ").length,
|
|
224
|
+
},
|
|
225
|
+
telemetry: {
|
|
226
|
+
model_used: "gpt-4o",
|
|
227
|
+
llm_tokens: { total: 12000 },
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
agent.listen(3000);
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**The request / response cycle:**
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
POST /execute
|
|
240
|
+
→ 202 Accepted
|
|
241
|
+
{ "run_id": "run_abc", "status": "working", "poll_url": "/tasks/run_abc" }
|
|
242
|
+
|
|
243
|
+
GET /tasks/run_abc (marketplace polls every few seconds)
|
|
244
|
+
→ 200 { "status": "working", "elapsed_ms": 14200 }
|
|
245
|
+
|
|
246
|
+
GET /tasks/run_abc (when done)
|
|
247
|
+
→ 200 {
|
|
248
|
+
"status": "completed",
|
|
249
|
+
"output": { "report": "...", "sources": [...], "wordCount": 4200 },
|
|
250
|
+
"telemetry": { ... }
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
### 4.3 Conversational Agent — Multi-Turn & HITL
|
|
257
|
+
|
|
258
|
+
**Best for:** Document assistants, workflow agents, code review bots, or any agent that needs to ask the user a clarifying question mid-run before proceeding.
|
|
259
|
+
|
|
260
|
+
**How it works:** The agent calls `ctx.needInput({...})` to suspend execution and emit a `task.input_required` SSE event with a resume token. The marketplace shows a form to the buyer. When the buyer submits, the agent is re-invoked with the resume token and the previous session state intact.
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
import { createAgent } from "catelog";
|
|
264
|
+
import { z } from "zod";
|
|
265
|
+
|
|
266
|
+
const agent = createAgent({
|
|
267
|
+
name: "SQL Query Assistant",
|
|
268
|
+
mode: "conversational",
|
|
269
|
+
|
|
270
|
+
inputSchema: z.object({
|
|
271
|
+
question: z.string().describe("What would you like to know?"),
|
|
272
|
+
// resume fields — optional, provided on second invocation
|
|
273
|
+
selectedTable: z.string().optional(),
|
|
274
|
+
}),
|
|
275
|
+
|
|
276
|
+
async execute({ input, ctx, stream, log }) {
|
|
277
|
+
// ── First invocation: user sends { question: "show me orders" } ──
|
|
278
|
+
if (!input.selectedTable) {
|
|
279
|
+
stream.progress(20, "Checking available tables...");
|
|
280
|
+
const tables = await getAvailableTables();
|
|
281
|
+
log.info("Tables discovered", { count: tables.length });
|
|
282
|
+
|
|
283
|
+
// Suspend and ask for input — does NOT complete the task
|
|
284
|
+
return ctx.needInput({
|
|
285
|
+
prompt: `I found ${tables.length} tables. Which one would you like to query?`,
|
|
286
|
+
schema: {
|
|
287
|
+
type: "object",
|
|
288
|
+
properties: {
|
|
289
|
+
selectedTable: {
|
|
290
|
+
type: "string",
|
|
291
|
+
enum: tables,
|
|
292
|
+
description: "Choose the table to query",
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
required: ["selectedTable"],
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ── Second invocation: user submits { question: "...", selectedTable: "orders" } ──
|
|
301
|
+
stream.progress(30, `Querying ${input.selectedTable}...`);
|
|
302
|
+
log.info("Querying table", { table: input.selectedTable });
|
|
303
|
+
|
|
304
|
+
const results = await runQuery(input.selectedTable, input.question);
|
|
305
|
+
|
|
306
|
+
// Session state is available across turns
|
|
307
|
+
const previousQueries = ctx.session.get<string[]>("queryHistory") ?? [];
|
|
308
|
+
ctx.session.set("queryHistory", [...previousQueries, input.question]);
|
|
309
|
+
|
|
310
|
+
stream.delta(`Found ${results.length} records in ${input.selectedTable}.\n`);
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
output: {
|
|
314
|
+
table: input.selectedTable,
|
|
315
|
+
rowCount: results.length,
|
|
316
|
+
preview: results.slice(0, 5),
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
agent.listen(3000);
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**The SSE event flow:**
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
First invocation:
|
|
329
|
+
task.status → "working"
|
|
330
|
+
task.progress → 20%, "Checking available tables..."
|
|
331
|
+
task.input_required → { prompt: "...", resume_token: "resume_abc", input_schema: {...} }
|
|
332
|
+
|
|
333
|
+
[Marketplace shows a form to the buyer]
|
|
334
|
+
[Buyer selects "orders" and submits]
|
|
335
|
+
|
|
336
|
+
Second invocation (resume):
|
|
337
|
+
POST /execute { input: { question: "...", selectedTable: "orders" }, resume_token: "resume_abc" }
|
|
338
|
+
task.status → "working"
|
|
339
|
+
task.progress → 30%, "Querying orders..."
|
|
340
|
+
task.delta → "Found 142 records..."
|
|
341
|
+
task.completed → { table: "orders", rowCount: 142, preview: [...] }
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## 5. Framework Integrations
|
|
347
|
+
|
|
348
|
+
The SDK provides one-line adapter functions that translate framework-specific streaming into Catelog's task event protocol.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
### 5.1 OpenAI SDK
|
|
353
|
+
|
|
354
|
+
Works with both streaming and non-streaming completions. Automatically extracts token usage.
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
import OpenAI from "openai";
|
|
358
|
+
import { createAgent, pipeOpenAiStream } from "catelog";
|
|
359
|
+
import { z } from "zod";
|
|
360
|
+
|
|
361
|
+
const openai = new OpenAI(); // Uses OPENAI_API_KEY from env
|
|
362
|
+
|
|
363
|
+
const agent = createAgent({
|
|
364
|
+
name: "OpenAI Analyst",
|
|
365
|
+
inputSchema: z.object({
|
|
366
|
+
prompt: z.string(),
|
|
367
|
+
model: z.enum(["gpt-4o", "gpt-4o-mini"]).default("gpt-4o-mini"),
|
|
368
|
+
}),
|
|
369
|
+
|
|
370
|
+
async execute({ input, stream, signal }) {
|
|
371
|
+
stream.progress(10, "Calling OpenAI...");
|
|
372
|
+
|
|
373
|
+
// Streaming chat completion
|
|
374
|
+
const openaiStream = await openai.chat.completions.create({
|
|
375
|
+
model: input.model,
|
|
376
|
+
messages: [{ role: "user", content: input.prompt }],
|
|
377
|
+
stream: true,
|
|
378
|
+
// Optional: get token counts in final chunk
|
|
379
|
+
stream_options: { include_usage: true },
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// One line: pipes chunks → task.delta, extracts token usage → telemetry
|
|
383
|
+
return pipeOpenAiStream(openaiStream, stream);
|
|
384
|
+
// Returns: { output: { text: "..." }, telemetry: { llm_tokens: {...}, model_used: "..." } }
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
agent.listen(3000);
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**What you get from the adapter:**
|
|
392
|
+
- Each `choices[0].delta.content` chunk → emitted as `task.delta`
|
|
393
|
+
- Tool calls in stream → emitted as `task.activity` events
|
|
394
|
+
- Final `usage` chunk → captured as `telemetry.llm_tokens`
|
|
395
|
+
- Model name → captured as `telemetry.model_used`
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
### 5.2 Vercel AI SDK
|
|
400
|
+
|
|
401
|
+
Works with `streamText`, `generateText`, `streamObject`, and any Vercel AI provider (OpenAI, Anthropic, Google, etc.).
|
|
402
|
+
|
|
403
|
+
```ts
|
|
404
|
+
import { streamText, generateText } from "ai";
|
|
405
|
+
import { openai } from "@ai-sdk/openai";
|
|
406
|
+
import { anthropic } from "@ai-sdk/anthropic";
|
|
407
|
+
import { createAgent, pipeAiSdkStream } from "catelog";
|
|
408
|
+
import { z } from "zod";
|
|
409
|
+
|
|
410
|
+
const agent = createAgent({
|
|
411
|
+
name: "Vercel AI Coding Assistant",
|
|
412
|
+
inputSchema: z.object({
|
|
413
|
+
prompt: z.string(),
|
|
414
|
+
provider: z.enum(["openai", "anthropic"]).default("openai"),
|
|
415
|
+
}),
|
|
416
|
+
|
|
417
|
+
async execute({ input, stream, signal }) {
|
|
418
|
+
stream.progress(10, "Generating response...");
|
|
419
|
+
|
|
420
|
+
// Works with any Vercel AI provider
|
|
421
|
+
const model = input.provider === "anthropic"
|
|
422
|
+
? anthropic("claude-3-5-sonnet-20241022")
|
|
423
|
+
: openai("gpt-4o");
|
|
424
|
+
|
|
425
|
+
// Streaming mode
|
|
426
|
+
const result = streamText({
|
|
427
|
+
model,
|
|
428
|
+
prompt: input.prompt,
|
|
429
|
+
abortSignal: signal, // Propagate cancellation
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// One line: pipes textStream → task.delta, extracts usage → telemetry
|
|
433
|
+
return pipeAiSdkStream(result, stream);
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
agent.listen(3000);
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**What you get from the adapter:**
|
|
441
|
+
- Each text chunk → emitted as `task.delta`
|
|
442
|
+
- Final `usage` object → captured as `telemetry.llm_tokens`
|
|
443
|
+
- Tool calls → captured as `telemetry.framework.tool_calls`
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
### 5.3 LangGraph / LangChain
|
|
448
|
+
|
|
449
|
+
Works with compiled LangGraph graphs. Translates node transitions to `task.activity` events and message content to `task.delta`.
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
import { createAgent, pipeLangGraphStream } from "catelog";
|
|
453
|
+
import { StateGraph, Annotation } from "@langchain/langgraph";
|
|
454
|
+
import { ChatOpenAI } from "@langchain/openai";
|
|
455
|
+
import { z } from "zod";
|
|
456
|
+
|
|
457
|
+
// Build your LangGraph normally
|
|
458
|
+
const GraphState = Annotation.Root({ messages: Annotation<string[]>() });
|
|
459
|
+
const llm = new ChatOpenAI({ model: "gpt-4o-mini" });
|
|
460
|
+
|
|
461
|
+
const graph = new StateGraph(GraphState)
|
|
462
|
+
.addNode("research", async (state) => { /* ... */ })
|
|
463
|
+
.addNode("synthesize", async (state) => { /* ... */ })
|
|
464
|
+
.addEdge("__start__", "research")
|
|
465
|
+
.addEdge("research", "synthesize")
|
|
466
|
+
.compile();
|
|
467
|
+
|
|
468
|
+
const agent = createAgent({
|
|
469
|
+
name: "LangGraph Research Pipeline",
|
|
470
|
+
inputSchema: z.object({
|
|
471
|
+
topic: z.string(),
|
|
472
|
+
}),
|
|
473
|
+
|
|
474
|
+
async execute({ input, stream, signal }) {
|
|
475
|
+
stream.progress(5, "Starting graph...");
|
|
476
|
+
|
|
477
|
+
// One line: runs graph, emits node transitions as task.activity
|
|
478
|
+
return pipeLangGraphStream(
|
|
479
|
+
graph,
|
|
480
|
+
{ messages: [input.topic] },
|
|
481
|
+
stream,
|
|
482
|
+
{ signal }
|
|
483
|
+
);
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
agent.listen(3000);
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**What you get from the adapter:**
|
|
491
|
+
- Each graph node entry → emitted as `task.activity` with `{ node: "research", step: 1 }`
|
|
492
|
+
- Message content from nodes → emitted as `task.delta`
|
|
493
|
+
- Visited nodes list → captured in `telemetry.framework.nodes_visited`
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
### 5.4 CrewAI / Custom Async Generator
|
|
498
|
+
|
|
499
|
+
For any framework that doesn't have a built-in adapter, use `pipeAsyncIterableStream`. This works with CrewAI agents, LlamaIndex, custom generators, or any `AsyncIterable<string>`.
|
|
500
|
+
|
|
501
|
+
```ts
|
|
502
|
+
import { createAgent, pipeAsyncIterableStream } from "catelog";
|
|
503
|
+
import { z } from "zod";
|
|
504
|
+
|
|
505
|
+
// Example: a custom async generator (replace with your framework)
|
|
506
|
+
async function* myCrewAiAgent(input: string): AsyncGenerator<string> {
|
|
507
|
+
yield "Agent 1 is researching...\n";
|
|
508
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
509
|
+
yield "Agent 2 is writing the report...\n";
|
|
510
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
511
|
+
yield `Final report on: ${input}\n`;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const agent = createAgent({
|
|
515
|
+
name: "CrewAI Research Crew",
|
|
516
|
+
inputSchema: z.object({
|
|
517
|
+
topic: z.string(),
|
|
518
|
+
}),
|
|
519
|
+
|
|
520
|
+
async execute({ input, stream }) {
|
|
521
|
+
stream.progress(10, "Spinning up the crew...");
|
|
522
|
+
|
|
523
|
+
// Pipe any async iterable directly — works with CrewAI, LlamaIndex, etc.
|
|
524
|
+
return pipeAsyncIterableStream(
|
|
525
|
+
myCrewAiAgent(input.topic),
|
|
526
|
+
stream
|
|
527
|
+
);
|
|
528
|
+
},
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
agent.listen(3000);
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
### 5.5 Vanilla TypeScript (No Framework)
|
|
537
|
+
|
|
538
|
+
No framework? No problem. Use the stream helpers directly.
|
|
539
|
+
|
|
540
|
+
```ts
|
|
541
|
+
import { createAgent } from "catelog";
|
|
542
|
+
import { z } from "zod";
|
|
543
|
+
|
|
544
|
+
const agent = createAgent({
|
|
545
|
+
name: "Custom Logic Agent",
|
|
546
|
+
inputSchema: z.object({
|
|
547
|
+
data: z.array(z.number()),
|
|
548
|
+
operation: z.enum(["sum", "avg", "max"]),
|
|
549
|
+
}),
|
|
550
|
+
|
|
551
|
+
async execute({ input, stream, log, metric, signal }) {
|
|
552
|
+
log.info("Running computation", { operation: input.operation });
|
|
553
|
+
stream.progress(10, `Computing ${input.operation}...`);
|
|
554
|
+
|
|
555
|
+
let result: number;
|
|
556
|
+
switch (input.operation) {
|
|
557
|
+
case "sum": result = input.data.reduce((a, b) => a + b, 0); break;
|
|
558
|
+
case "avg": result = input.data.reduce((a, b) => a + b, 0) / input.data.length; break;
|
|
559
|
+
case "max": result = Math.max(...input.data); break;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Track custom metrics
|
|
563
|
+
metric.count("numbers_processed", input.data.length);
|
|
564
|
+
metric.gauge("result_value", result);
|
|
565
|
+
|
|
566
|
+
stream.progress(100, "Done!");
|
|
567
|
+
stream.delta(`The ${input.operation} of ${input.data.length} numbers is: ${result}`);
|
|
568
|
+
|
|
569
|
+
return { output: { result, operation: input.operation } };
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
agent.listen(3000);
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
578
|
+
## 6. Streaming Helpers
|
|
579
|
+
|
|
580
|
+
All of these are available on the `stream` object inside `execute()`:
|
|
581
|
+
|
|
582
|
+
| Method | Signature | What It Does | Completes Task? |
|
|
583
|
+
| :--- | :--- | :--- | :---: |
|
|
584
|
+
| `stream.progress()` | `(percent: number, message?: string)` | Emits a `task.progress` event for the progress bar | No |
|
|
585
|
+
| `stream.delta()` | `(text: string, type?: "text" \| "json_patch")` | Emits a `task.delta` event — live text token | No |
|
|
586
|
+
| `stream.write()` | `(text: string)` | Alias for `stream.delta(text)` | No |
|
|
587
|
+
| `stream.activity()` | `(type: string, data?: object)` | Emits a `task.activity` event (tool calls, etc.) | No |
|
|
588
|
+
| `stream.step()` | `(label: string)` | Legacy alias — maps to `stream.progress(0, label)` | No |
|
|
589
|
+
| `stream.emit()` | `(event: string, data: unknown)` | Raw SSE event emitter | No |
|
|
590
|
+
| `stream.isClosed()` | `() => boolean` | Check if client disconnected | — |
|
|
591
|
+
| `return value` | any | Completes the task with output | **Yes** |
|
|
592
|
+
| `throw error` | Error | Fails the task | **Yes** |
|
|
593
|
+
| `ctx.needInput()` | `(opts)` | Suspends with input-required | **Yes** |
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
## 7. Schema Definition
|
|
598
|
+
|
|
599
|
+
Schemas are **optional** but recommended for marketplace listings. They generate the buyer input form and validate payloads at the gateway.
|
|
600
|
+
|
|
601
|
+
### With Zod (Recommended)
|
|
602
|
+
|
|
603
|
+
```ts
|
|
604
|
+
import { z } from "zod";
|
|
605
|
+
|
|
606
|
+
const agent = createAgent({
|
|
607
|
+
name: "Research Agent",
|
|
608
|
+
inputSchema: z.object({
|
|
609
|
+
query: z.string().min(3).describe("The research question"),
|
|
610
|
+
maxSources: z.number().min(1).max(20).default(5).describe("Max sources to cite"),
|
|
611
|
+
language: z.enum(["en", "es", "fr", "de"]).default("en"),
|
|
612
|
+
}),
|
|
613
|
+
outputSchema: z.object({
|
|
614
|
+
report: z.string(),
|
|
615
|
+
sources: z.array(z.string().url()),
|
|
616
|
+
language: z.string(),
|
|
617
|
+
}),
|
|
618
|
+
async execute({ input }) {
|
|
619
|
+
// input is fully typed: { query: string, maxSources: number, language: string }
|
|
620
|
+
return { output: { report: "...", sources: [], language: input.language } };
|
|
621
|
+
},
|
|
622
|
+
});
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
### With Raw JSON Schema
|
|
626
|
+
|
|
627
|
+
```ts
|
|
628
|
+
const agent = createAgent({
|
|
629
|
+
name: "Data Agent",
|
|
630
|
+
inputSchema: {
|
|
631
|
+
type: "object",
|
|
632
|
+
properties: {
|
|
633
|
+
query: { type: "string", minLength: 3, description: "The research question" },
|
|
634
|
+
maxSources: { type: "number", minimum: 1, maximum: 20, default: 5 },
|
|
635
|
+
},
|
|
636
|
+
required: ["query"],
|
|
637
|
+
},
|
|
638
|
+
async execute({ input }) { /* ... */ },
|
|
639
|
+
});
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### Without Schema (Accepts Any JSON)
|
|
643
|
+
|
|
644
|
+
```ts
|
|
645
|
+
const agent = createAgent({
|
|
646
|
+
name: "Flexible Agent",
|
|
647
|
+
// No inputSchema or outputSchema — accepts any JSON
|
|
648
|
+
async execute({ input }) {
|
|
649
|
+
return { output: { received: input } };
|
|
650
|
+
},
|
|
651
|
+
});
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
## 8. API Keys
|
|
657
|
+
|
|
658
|
+
### Where Keys Come From
|
|
659
|
+
|
|
660
|
+
Your Catelog dashboard generates two keys per workspace:
|
|
661
|
+
|
|
662
|
+
| Key | Format | Purpose |
|
|
663
|
+
| :--- | :--- | :--- |
|
|
664
|
+
| **Secret key** | `cat_sec_live_...` | Authenticates `/execute` calls |
|
|
665
|
+
| **Publishable key** | `cat_pub_live_...` | Safe to use in frontend code for `/health` and `/schema` |
|
|
666
|
+
|
|
667
|
+
### Setting Keys
|
|
668
|
+
|
|
669
|
+
Put them in your `.env` — the SDK auto-reads them:
|
|
670
|
+
|
|
671
|
+
```env
|
|
672
|
+
CATELOG_SECRET_KEY=cat_sec_live_your_key_here
|
|
673
|
+
CATELOG_PUBLIC_KEY=cat_pub_live_your_key_here
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
Or pass them explicitly in the constructor:
|
|
677
|
+
|
|
678
|
+
```ts
|
|
679
|
+
const agent = createAgent({
|
|
680
|
+
name: "My Agent",
|
|
681
|
+
secretKey: "cat_sec_live_your_key_here",
|
|
682
|
+
publicKey: "cat_pub_live_your_key_here",
|
|
683
|
+
async execute({ input }) { /* ... */ },
|
|
684
|
+
});
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
### How Authentication Works
|
|
688
|
+
|
|
689
|
+
| Caller | Auth Method | Header |
|
|
690
|
+
| :--- | :--- | :--- |
|
|
691
|
+
| **Marketplace Gateway** | HMAC-SHA256 (automatic) | `X-Marketplace-Signature: ...` |
|
|
692
|
+
| **Your curl / Postman** | Bearer secret key | `Authorization: Bearer cat_sec_live_...` |
|
|
693
|
+
| **Your frontend app** | Bearer public key | `Authorization: Bearer cat_pub_live_...` |
|
|
694
|
+
| **Local development** | None (dev mode) | *(no header required)* |
|
|
695
|
+
|
|
696
|
+
In development, you don't need any keys. The SDK runs in permissive mode and logs a warning.
|
|
697
|
+
|
|
698
|
+
---
|
|
699
|
+
|
|
700
|
+
## 9. Telemetry & Logging
|
|
701
|
+
|
|
702
|
+
### Structured Logging
|
|
703
|
+
|
|
704
|
+
A `log` helper is available inside every `execute()`:
|
|
705
|
+
|
|
706
|
+
```ts
|
|
707
|
+
async execute({ input, log }) {
|
|
708
|
+
log.info("Processing request", { query: input.query });
|
|
709
|
+
log.warn("Rate limit approaching", { remaining: 10 });
|
|
710
|
+
log.error("Failed to fetch", { url: "https://...", status: 429 });
|
|
711
|
+
log.debug("Cache hit", { key: "results:abc" }); // Only in development
|
|
712
|
+
}
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
Output is color-coded by level with run ID prefix. Debug logs are suppressed in production.
|
|
716
|
+
|
|
717
|
+
### Custom Metrics
|
|
718
|
+
|
|
719
|
+
A `metric` helper is available for tracking custom counters and gauges:
|
|
720
|
+
|
|
721
|
+
```ts
|
|
722
|
+
async execute({ input, metric }) {
|
|
723
|
+
metric.count("tokens_processed", 142);
|
|
724
|
+
metric.gauge("queue_depth", 5);
|
|
725
|
+
metric.timing("llm_latency_ms", 1240);
|
|
726
|
+
}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
All custom metrics appear in the `telemetry.framework.custom_metrics` field of the final response.
|
|
730
|
+
|
|
731
|
+
### Returning Telemetry
|
|
732
|
+
|
|
733
|
+
You can provide model usage data in the return value for accurate billing and analytics:
|
|
734
|
+
|
|
735
|
+
```ts
|
|
736
|
+
return {
|
|
737
|
+
output: { summary: "..." },
|
|
738
|
+
telemetry: {
|
|
739
|
+
model_used: "gpt-4o",
|
|
740
|
+
llm_tokens: {
|
|
741
|
+
prompt: 250,
|
|
742
|
+
completion: 400,
|
|
743
|
+
total: 650,
|
|
744
|
+
},
|
|
745
|
+
cost_usd: 0.0013,
|
|
746
|
+
},
|
|
747
|
+
};
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
If using a framework adapter (`pipeOpenAiStream`, `pipeAiSdkStream`), token counts are extracted automatically — no need to provide them manually.
|
|
751
|
+
|
|
752
|
+
---
|
|
753
|
+
|
|
754
|
+
## 10. What You Get — Endpoints
|
|
755
|
+
|
|
756
|
+
Once `agent.listen(3000)` is called, the SDK exposes these endpoints:
|
|
757
|
+
|
|
758
|
+
### `GET /health`
|
|
759
|
+
No auth required. Used by the marketplace registration probe.
|
|
760
|
+
|
|
761
|
+
```bash
|
|
762
|
+
curl http://localhost:3000/health
|
|
763
|
+
```
|
|
764
|
+
```json
|
|
765
|
+
{
|
|
766
|
+
"status": "ready",
|
|
767
|
+
"protocol_version": "1.0.0",
|
|
768
|
+
"uptime_seconds": 1284,
|
|
769
|
+
"environment_ready": true,
|
|
770
|
+
"capabilities": {
|
|
771
|
+
"streaming": true,
|
|
772
|
+
"delta_stream": true,
|
|
773
|
+
"max_timeout_seconds": 300,
|
|
774
|
+
"execution_modes": ["sync"]
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
### `GET /schema`
|
|
780
|
+
No auth required. Returns the agent manifest for marketplace listing.
|
|
781
|
+
|
|
782
|
+
```bash
|
|
783
|
+
curl http://localhost:3000/schema
|
|
784
|
+
```
|
|
785
|
+
```json
|
|
786
|
+
{
|
|
787
|
+
"id": "my-research-agent",
|
|
788
|
+
"name": "My Research Agent",
|
|
789
|
+
"version": "1.0.0",
|
|
790
|
+
"description": "...",
|
|
791
|
+
"protocol_version": "1.0.0",
|
|
792
|
+
"capabilities": { "streaming": true, "execution_modes": ["sync"] },
|
|
793
|
+
"input_schema": { "type": "object", "properties": { "query": { "type": "string" } } },
|
|
794
|
+
"output_schema": { "type": "object", "properties": { "report": { "type": "string" } } }
|
|
795
|
+
}
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
### `POST /execute`
|
|
799
|
+
Runs the agent. Accepts SSE (`Accept: text/event-stream`) or JSON (`Accept: application/json`).
|
|
800
|
+
|
|
801
|
+
```bash
|
|
802
|
+
# Dev mode — no auth required
|
|
803
|
+
curl -X POST http://localhost:3000/execute \
|
|
804
|
+
-H "Content-Type: application/json" \
|
|
805
|
+
-d '{"input": {"query": "AI trends 2025"}}'
|
|
806
|
+
|
|
807
|
+
# With Bearer auth
|
|
808
|
+
curl -X POST http://localhost:3000/execute \
|
|
809
|
+
-H "Authorization: Bearer cat_sec_live_your_key" \
|
|
810
|
+
-H "Content-Type: application/json" \
|
|
811
|
+
-d '{"input": {"query": "AI trends 2025"}}'
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
### `GET /tasks/:id` *(async mode only)*
|
|
815
|
+
Polls the status of a background async job.
|
|
816
|
+
|
|
817
|
+
```bash
|
|
818
|
+
curl http://localhost:3000/tasks/run_abc123
|
|
819
|
+
```
|
|
820
|
+
```json
|
|
821
|
+
{ "run_id": "run_abc123", "status": "working", "elapsed_ms": 4200 }
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
---
|
|
825
|
+
|
|
826
|
+
## 11. Testing Locally
|
|
827
|
+
|
|
828
|
+
### Basic curl test
|
|
829
|
+
|
|
830
|
+
```bash
|
|
831
|
+
# 1. Health check
|
|
832
|
+
curl http://localhost:3000/health
|
|
833
|
+
|
|
834
|
+
# 2. See the agent schema
|
|
835
|
+
curl http://localhost:3000/schema | jq .
|
|
836
|
+
|
|
837
|
+
# 3. Run the agent (SSE stream)
|
|
838
|
+
curl -N -X POST http://localhost:3000/execute \
|
|
839
|
+
-H "Content-Type: application/json" \
|
|
840
|
+
-H "Accept: text/event-stream" \
|
|
841
|
+
-d '{"input": {"query": "test query"}}'
|
|
842
|
+
|
|
843
|
+
# 4. Run the agent (JSON batch, no streaming)
|
|
844
|
+
curl -X POST http://localhost:3000/execute \
|
|
845
|
+
-H "Content-Type: application/json" \
|
|
846
|
+
-H "Accept: application/json" \
|
|
847
|
+
-d '{"input": {"query": "test query"}}'
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
### Running the test suite
|
|
851
|
+
|
|
852
|
+
```bash
|
|
853
|
+
pnpm test
|
|
854
|
+
# or
|
|
855
|
+
npx tsx test/index.test.ts
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
---
|
|
859
|
+
|
|
860
|
+
## 12. Registering on the Marketplace
|
|
861
|
+
|
|
862
|
+
1. Deploy your agent to a public URL (Fly.io, Railway, Render, AWS, etc.)
|
|
863
|
+
2. Go to **Catelog Dashboard → Creator Space → My Agents**
|
|
864
|
+
3. Click **Register New Agent**
|
|
865
|
+
4. Paste your public URL (e.g. `https://my-agent.fly.dev`)
|
|
866
|
+
5. The marketplace automatically:
|
|
867
|
+
- Pings `GET /health` — verifies reachability and protocol version
|
|
868
|
+
- Fetches `GET /schema` — populates agent name, description, and input form
|
|
869
|
+
- Checks `capabilities.execution_modes` — marks sync/async/conversational support
|
|
870
|
+
6. Review auto-populated fields, set your price, and publish
|
|
871
|
+
|
|
872
|
+
---
|
|
873
|
+
|
|
874
|
+
## 13. Error Handling
|
|
875
|
+
|
|
876
|
+
### Automatic Error Capture
|
|
877
|
+
|
|
878
|
+
You don't need to catch errors manually. Any uncaught exception in `execute()` is automatically converted to a `task.failed` event:
|
|
879
|
+
|
|
880
|
+
```ts
|
|
881
|
+
async execute({ input }) {
|
|
882
|
+
// If this throws, the SDK emits task.failed automatically
|
|
883
|
+
const result = await riskyOperation(input);
|
|
884
|
+
return { output: result };
|
|
885
|
+
}
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
### Custom Error Codes
|
|
889
|
+
|
|
890
|
+
For domain-specific errors, attach a `code` and `retryable` flag:
|
|
891
|
+
|
|
892
|
+
```ts
|
|
893
|
+
async execute({ input }) {
|
|
894
|
+
const balance = await checkCredits();
|
|
895
|
+
if (balance < 0.01) {
|
|
896
|
+
const err = new Error("Insufficient credits to run this model");
|
|
897
|
+
(err as any).code = "MODEL_RATE_LIMIT";
|
|
898
|
+
(err as any).retryable = true;
|
|
899
|
+
throw err;
|
|
900
|
+
}
|
|
901
|
+
// ...
|
|
902
|
+
}
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
### Cancellation via AbortSignal
|
|
906
|
+
|
|
907
|
+
When a buyer cancels or the gateway times out, `signal.aborted` becomes `true`. Pass the signal to your LLM or fetch calls to propagate cancellation and avoid wasted credits:
|
|
908
|
+
|
|
909
|
+
```ts
|
|
910
|
+
async execute({ input, stream, signal }) {
|
|
911
|
+
// signal propagates cancellation to fetch, OpenAI, LangGraph, etc.
|
|
912
|
+
const response = await fetch("https://api.example.com", { signal });
|
|
913
|
+
const result = await openai.chat.completions.create({ ..., stream: true });
|
|
914
|
+
|
|
915
|
+
// Check manually if needed
|
|
916
|
+
if (signal.aborted) {
|
|
917
|
+
return { output: { partial: true } };
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
---
|
|
923
|
+
|
|
924
|
+
## Quick Reference
|
|
925
|
+
|
|
926
|
+
```ts
|
|
927
|
+
import { createAgent, pipeOpenAiStream, pipeAiSdkStream, pipeLangGraphStream } from "catelog";
|
|
928
|
+
|
|
929
|
+
const agent = createAgent({
|
|
930
|
+
name: string, // Required
|
|
931
|
+
id?: string, // Auto-slugified from name
|
|
932
|
+
version?: string, // Default: "1.0.0"
|
|
933
|
+
description?: string,
|
|
934
|
+
mode?: "sync" | "async" | "conversational", // Default: "sync"
|
|
935
|
+
secretKey?: string, // Auto-read from CATELOG_SECRET_KEY
|
|
936
|
+
publicKey?: string, // Auto-read from CATELOG_PUBLIC_KEY
|
|
937
|
+
inputSchema?: ZodSchema | JSONSchema, // Optional
|
|
938
|
+
outputSchema?: ZodSchema | JSONSchema, // Optional
|
|
939
|
+
telemetry?: { endpoint?: string; disabled?: boolean },
|
|
940
|
+
capabilities?: { max_timeout_seconds?: number },
|
|
941
|
+
|
|
942
|
+
async execute(ctx) {
|
|
943
|
+
// ctx.runId string
|
|
944
|
+
// ctx.input typed input
|
|
945
|
+
// ctx.stream streaming helpers
|
|
946
|
+
// ctx.signal AbortSignal
|
|
947
|
+
// ctx.log structured logger
|
|
948
|
+
// ctx.metric metric recorder
|
|
949
|
+
// ctx.metadata buyer/session metadata
|
|
950
|
+
// ctx.session get/set for conversational state
|
|
951
|
+
// ctx.needInput() suspend for HITL input
|
|
952
|
+
|
|
953
|
+
return { output: { ... }, telemetry: { ... } }; // or just return { ... }
|
|
954
|
+
},
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
agent.listen(3000); // or agent.serve(3000)
|
|
958
|
+
```
|