coaiajs 0.1.2 → 0.2.0
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/.claude/settings.local.json +16 -0
- package/README.md +2 -0
- package/articles/academic/NOTES.md +1 -0
- package/articles/academic/README.md +1 -0
- package/articles/academic/creative-orientation-vs-problem-solving.md +177 -0
- package/articles/academic/jsonl-knowledge-graphs-agent-memory.md +142 -0
- package/articles/academic/langfuse-observability-llm-pipelines.md +144 -0
- package/articles/academic/medicine-wheel-software-architecture.md +163 -0
- package/articles/academic/mmot-autonomous-agents.md +156 -0
- package/articles/academic/model-context-protocol-interagent.md +161 -0
- package/articles/academic/pde-prompt-decomposition.md +186 -0
- package/articles/academic/structural-tension-in-ai-agents.md +134 -0
- package/articles/reviews/mcp-protocol-design-review.md +170 -0
- package/articles/reviews/observability-ai-systems-review.md +176 -0
- package/articles/reviews/prompt-engineering-decomposition-review.md +184 -0
- package/articles/surveys/agent-orchestration-survey.md +186 -0
- package/articles/surveys/knowledge-graph-storage-survey.md +204 -0
- package/articles/surveys/structural-tension-methodology-survey.md +154 -0
- package/articles/technical/aws-sdk-v3-polly.md +270 -0
- package/articles/technical/commander-cli-framework.md +262 -0
- package/articles/technical/dotenv-config-patterns.md +360 -0
- package/articles/technical/ioredis-vs-redis.md +142 -0
- package/articles/technical/langfuse-js-sdk-vs-rest.md +191 -0
- package/articles/technical/mcp-sdk-typescript.md +291 -0
- package/articles/technical/octokit-github-api.md +293 -0
- package/articles/technical/openai-sdk-modern.md +231 -0
- package/articles/technical/yaml-parsing-node.md +266 -0
- package/articles/technical/zod-runtime-validation.md +212 -0
- package/dist/mcp/server.js +5 -3
- package/dist/mcp/tools/coaiapy-tools.js +1 -0
- package/dist/src/cli.js +41 -5
- package/dist/src/langfuse/index.d.ts +1 -1
- package/dist/src/langfuse/index.js +1 -1
- package/dist/src/langfuse/traces.d.ts +1 -0
- package/dist/src/langfuse/traces.js +24 -0
- package/dist/src/redis.d.ts +1 -1
- package/dist/src/redis.js +2 -2
- package/mcp/config.ts +225 -0
- package/mcp/prompts.ts +131 -0
- package/mcp/resources.ts +84 -0
- package/mcp/server.ts +518 -0
- package/mcp/tools/coaiapy-tools.ts +366 -0
- package/mcp/tools/index.ts +4 -0
- package/package.json +2 -67
- package/src/audio.ts +76 -0
- package/src/cli-helpers.ts +86 -0
- package/src/cli.ts +1260 -0
- package/src/config.ts +207 -0
- package/src/environment.ts +171 -0
- package/src/github.ts +143 -0
- package/src/index.ts +82 -0
- package/src/langfuse/client.ts +105 -0
- package/src/langfuse/comments.ts +52 -0
- package/src/langfuse/datasets.ts +178 -0
- package/src/langfuse/index.ts +33 -0
- package/src/langfuse/media.ts +193 -0
- package/src/langfuse/observations.ts +131 -0
- package/src/langfuse/prompts.ts +157 -0
- package/src/langfuse/scores.ts +456 -0
- package/src/langfuse/traces.ts +302 -0
- package/src/llm.ts +106 -0
- package/src/narrative/graph-manager.ts +1358 -0
- package/src/narrative/index.ts +188 -0
- package/src/narrative/markdown-export.ts +535 -0
- package/src/narrative/tool-definitions.ts +635 -0
- package/src/narrative/tool-handlers.ts +528 -0
- package/src/narrative/types.ts +9 -0
- package/src/narrative/validation.ts +179 -0
- package/src/pde/index.ts +34 -0
- package/src/pde/mcp-handlers.ts +359 -0
- package/src/pde/mcp-tools.ts +201 -0
- package/src/pde/session-manager.ts +248 -0
- package/src/pde/stc-mapper.ts +298 -0
- package/src/pipeline/index.ts +7 -0
- package/src/pipeline/template-engine.ts +398 -0
- package/src/planning/index.ts +32 -0
- package/src/planning/mcp-handlers.ts +369 -0
- package/src/planning/mcp-tools.ts +155 -0
- package/src/planning/plan-parser.ts +587 -0
- package/src/redis.ts +111 -0
- package/src/types.ts +281 -0
- package/test/config.test.mjs +93 -0
- package/tsconfig.json +26 -0
- package/dist/mcp/config.d.ts.map +0 -1
- package/dist/mcp/config.js.map +0 -1
- package/dist/mcp/prompts.d.ts.map +0 -1
- package/dist/mcp/prompts.js.map +0 -1
- package/dist/mcp/resources.d.ts.map +0 -1
- package/dist/mcp/resources.js.map +0 -1
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js.map +0 -1
- package/dist/mcp/tools/coaiapy-tools.d.ts.map +0 -1
- package/dist/mcp/tools/coaiapy-tools.js.map +0 -1
- package/dist/mcp/tools/index.d.ts.map +0 -1
- package/dist/mcp/tools/index.js.map +0 -1
- package/dist/src/audio.d.ts.map +0 -1
- package/dist/src/audio.js.map +0 -1
- package/dist/src/cli-helpers.d.ts.map +0 -1
- package/dist/src/cli-helpers.js.map +0 -1
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/cli.js.map +0 -1
- package/dist/src/config.d.ts.map +0 -1
- package/dist/src/config.js.map +0 -1
- package/dist/src/environment.d.ts.map +0 -1
- package/dist/src/environment.js.map +0 -1
- package/dist/src/github.d.ts.map +0 -1
- package/dist/src/github.js.map +0 -1
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js.map +0 -1
- package/dist/src/langfuse/client.d.ts.map +0 -1
- package/dist/src/langfuse/client.js.map +0 -1
- package/dist/src/langfuse/comments.d.ts.map +0 -1
- package/dist/src/langfuse/comments.js.map +0 -1
- package/dist/src/langfuse/datasets.d.ts.map +0 -1
- package/dist/src/langfuse/datasets.js.map +0 -1
- package/dist/src/langfuse/index.d.ts.map +0 -1
- package/dist/src/langfuse/index.js.map +0 -1
- package/dist/src/langfuse/media.d.ts.map +0 -1
- package/dist/src/langfuse/media.js.map +0 -1
- package/dist/src/langfuse/observations.d.ts.map +0 -1
- package/dist/src/langfuse/observations.js.map +0 -1
- package/dist/src/langfuse/prompts.d.ts.map +0 -1
- package/dist/src/langfuse/prompts.js.map +0 -1
- package/dist/src/langfuse/scores.d.ts.map +0 -1
- package/dist/src/langfuse/scores.js.map +0 -1
- package/dist/src/langfuse/traces.d.ts.map +0 -1
- package/dist/src/langfuse/traces.js.map +0 -1
- package/dist/src/llm.d.ts.map +0 -1
- package/dist/src/llm.js.map +0 -1
- package/dist/src/narrative/graph-manager.d.ts.map +0 -1
- package/dist/src/narrative/graph-manager.js.map +0 -1
- package/dist/src/narrative/index.d.ts.map +0 -1
- package/dist/src/narrative/index.js.map +0 -1
- package/dist/src/narrative/markdown-export.d.ts.map +0 -1
- package/dist/src/narrative/markdown-export.js.map +0 -1
- package/dist/src/narrative/tool-definitions.d.ts.map +0 -1
- package/dist/src/narrative/tool-definitions.js.map +0 -1
- package/dist/src/narrative/tool-handlers.d.ts.map +0 -1
- package/dist/src/narrative/tool-handlers.js.map +0 -1
- package/dist/src/narrative/types.d.ts.map +0 -1
- package/dist/src/narrative/types.js.map +0 -1
- package/dist/src/narrative/validation.d.ts.map +0 -1
- package/dist/src/narrative/validation.js.map +0 -1
- package/dist/src/pde/index.d.ts.map +0 -1
- package/dist/src/pde/index.js.map +0 -1
- package/dist/src/pde/mcp-handlers.d.ts.map +0 -1
- package/dist/src/pde/mcp-handlers.js.map +0 -1
- package/dist/src/pde/mcp-tools.d.ts.map +0 -1
- package/dist/src/pde/mcp-tools.js.map +0 -1
- package/dist/src/pde/session-manager.d.ts.map +0 -1
- package/dist/src/pde/session-manager.js.map +0 -1
- package/dist/src/pde/stc-mapper.d.ts.map +0 -1
- package/dist/src/pde/stc-mapper.js.map +0 -1
- package/dist/src/pipeline/index.d.ts.map +0 -1
- package/dist/src/pipeline/index.js.map +0 -1
- package/dist/src/pipeline/template-engine.d.ts.map +0 -1
- package/dist/src/pipeline/template-engine.js.map +0 -1
- package/dist/src/planning/index.d.ts.map +0 -1
- package/dist/src/planning/index.js.map +0 -1
- package/dist/src/planning/mcp-handlers.d.ts.map +0 -1
- package/dist/src/planning/mcp-handlers.js.map +0 -1
- package/dist/src/planning/mcp-tools.d.ts.map +0 -1
- package/dist/src/planning/mcp-tools.js.map +0 -1
- package/dist/src/planning/plan-parser.d.ts.map +0 -1
- package/dist/src/planning/plan-parser.js.map +0 -1
- package/dist/src/redis.d.ts.map +0 -1
- package/dist/src/redis.js.map +0 -1
- package/dist/src/types.d.ts.map +0 -1
- package/dist/src/types.js.map +0 -1
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# YAML Parsing in Node.js: Technical Assessment for CoAiA.js
|
|
2
|
+
|
|
3
|
+
> Package selection brief — YAML parser for pipeline templates, session files, and config loading replacing coaiapy's PyYAML
|
|
4
|
+
|
|
5
|
+
## Summary & Recommendation
|
|
6
|
+
|
|
7
|
+
**Use `yaml` v2.x** (the npm package named simply `yaml`). Despite `js-yaml` having higher download numbers, the modern `yaml` package offers streaming support for large files, comment preservation (critical for human-edited pipeline templates), and a cleaner API. CoAiA pipeline templates are user-authored YAML that benefits from round-trip fidelity — comments survive parse→stringify cycles.
|
|
8
|
+
|
|
9
|
+
**Pin:** `"yaml": "^2.6.0"`
|
|
10
|
+
|
|
11
|
+
## What We're Replacing
|
|
12
|
+
|
|
13
|
+
Coaiapy uses PyYAML for pipeline template loading:
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
# coaiapy/pipeline.py — PyYAML pattern
|
|
17
|
+
import yaml
|
|
18
|
+
|
|
19
|
+
# Load pipeline template
|
|
20
|
+
for template_file in search_path.glob("*.yaml"):
|
|
21
|
+
with open(yaml_file, 'r') as f:
|
|
22
|
+
data = yaml.safe_load(f) # Parse YAML → dict
|
|
23
|
+
|
|
24
|
+
# Pipeline template format:
|
|
25
|
+
# name: "trace-session"
|
|
26
|
+
# version: "1.0"
|
|
27
|
+
# variables:
|
|
28
|
+
# - name: session_id
|
|
29
|
+
# type: string
|
|
30
|
+
# required: true
|
|
31
|
+
# steps:
|
|
32
|
+
# - name: create_trace
|
|
33
|
+
# observation_type: SPAN
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
PyYAML's `safe_load` works well; the JavaScript equivalent needs the same safety guarantees plus TypeScript types.
|
|
37
|
+
|
|
38
|
+
## Options Compared
|
|
39
|
+
|
|
40
|
+
| Feature | yaml v2.6 | js-yaml v4.1 |
|
|
41
|
+
|---------|-----------|-------------|
|
|
42
|
+
| Weekly npm downloads | ~80M | ~130M |
|
|
43
|
+
| GitHub stars | ~1,600 | ~6,500 |
|
|
44
|
+
| Parse speed (typical files) | Good | Faster (~10%) |
|
|
45
|
+
| Stringify speed | Faster | Slower |
|
|
46
|
+
| Streaming support | ✅ Parse & stringify | ❌ Memory only |
|
|
47
|
+
| Comment preservation | ✅ Round-trip fidelity | ❌ Comments lost |
|
|
48
|
+
| Anchor/alias support | ✅ Full | ✅ Full |
|
|
49
|
+
| Custom tags | ✅ Flexible API | ✅ Schema-based |
|
|
50
|
+
| TypeScript | Native types | `@types/js-yaml` |
|
|
51
|
+
| YAML 1.2 compliance | ✅ Full | ⚠️ Mostly |
|
|
52
|
+
| Security (CVEs) | None known | CVE-2025-64718 (fixed in 4.1.1) |
|
|
53
|
+
| API style | `parse(str)`, `stringify(obj)` | `load(str)`, `dump(obj)` |
|
|
54
|
+
| Bundle size | ~45KB | ~30KB |
|
|
55
|
+
| Dependencies | 0 | 0 |
|
|
56
|
+
|
|
57
|
+
## API Overview
|
|
58
|
+
|
|
59
|
+
### Basic Parse & Stringify
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { parse, stringify } from 'yaml';
|
|
63
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
64
|
+
|
|
65
|
+
// Parse YAML file
|
|
66
|
+
async function loadYaml<T>(filePath: string): Promise<T> {
|
|
67
|
+
const content = await readFile(filePath, 'utf-8');
|
|
68
|
+
return parse(content) as T;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Stringify to YAML
|
|
72
|
+
async function saveYaml(filePath: string, data: unknown): Promise<void> {
|
|
73
|
+
const yamlStr = stringify(data, {
|
|
74
|
+
indent: 2,
|
|
75
|
+
lineWidth: 120,
|
|
76
|
+
});
|
|
77
|
+
await writeFile(filePath, yamlStr, 'utf-8');
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Pipeline Template Loading (replacing pipeline.py)
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { parse } from 'yaml';
|
|
85
|
+
import { readFile } from 'fs/promises';
|
|
86
|
+
import { glob } from 'glob';
|
|
87
|
+
import { z } from 'zod';
|
|
88
|
+
|
|
89
|
+
// Schema (validated with Zod after YAML parse)
|
|
90
|
+
const PipelineTemplateSchema = z.object({
|
|
91
|
+
name: z.string().min(1),
|
|
92
|
+
version: z.string().default('1.0'),
|
|
93
|
+
variables: z.array(z.object({
|
|
94
|
+
name: z.string().min(1),
|
|
95
|
+
type: z.enum(['string', 'number', 'boolean', 'list']).default('string'),
|
|
96
|
+
required: z.boolean().default(true),
|
|
97
|
+
default: z.unknown().optional(),
|
|
98
|
+
choices: z.array(z.unknown()).optional(),
|
|
99
|
+
})).default([]),
|
|
100
|
+
steps: z.array(z.object({
|
|
101
|
+
name: z.string().min(1),
|
|
102
|
+
observation_type: z.enum(['EVENT', 'SPAN', 'GENERATION']).default('EVENT'),
|
|
103
|
+
conditional: z.string().optional(),
|
|
104
|
+
})).min(1),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
type PipelineTemplate = z.infer<typeof PipelineTemplateSchema>;
|
|
108
|
+
|
|
109
|
+
async function loadPipelineTemplates(templateDir: string): Promise<Map<string, PipelineTemplate>> {
|
|
110
|
+
const templates = new Map<string, PipelineTemplate>();
|
|
111
|
+
const files = await glob('*.yaml', { cwd: templateDir, absolute: true });
|
|
112
|
+
|
|
113
|
+
for (const file of files) {
|
|
114
|
+
const raw = parse(await readFile(file, 'utf-8'));
|
|
115
|
+
const template = PipelineTemplateSchema.parse(raw);
|
|
116
|
+
templates.set(template.name, template);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return templates;
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Comment-Preserving Round-Trip (unique to `yaml` package)
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { parseDocument, stringify } from 'yaml';
|
|
127
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
128
|
+
|
|
129
|
+
// Load, modify, save — preserving user comments
|
|
130
|
+
async function updateTemplateVersion(filePath: string, newVersion: string): Promise<void> {
|
|
131
|
+
const content = await readFile(filePath, 'utf-8');
|
|
132
|
+
const doc = parseDocument(content); // Preserves comments, anchors, formatting
|
|
133
|
+
|
|
134
|
+
doc.set('version', newVersion);
|
|
135
|
+
|
|
136
|
+
await writeFile(filePath, doc.toString(), 'utf-8');
|
|
137
|
+
// Original comments and formatting are preserved!
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Example input:
|
|
141
|
+
// # Pipeline for session tracing
|
|
142
|
+
// name: trace-session
|
|
143
|
+
// version: "1.0" # Bump this on changes
|
|
144
|
+
//
|
|
145
|
+
// After update, the comment "# Bump this on changes" survives
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Session File Persistence
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { parse, stringify } from 'yaml';
|
|
152
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
153
|
+
|
|
154
|
+
interface SessionFile {
|
|
155
|
+
id: string;
|
|
156
|
+
created: string;
|
|
157
|
+
traces: { id: string; name: string; status: string }[];
|
|
158
|
+
charts: { id: string; outcome: string }[];
|
|
159
|
+
metadata: Record<string, unknown>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function saveSession(filePath: string, session: SessionFile): Promise<void> {
|
|
163
|
+
await writeFile(filePath, stringify(session, {
|
|
164
|
+
indent: 2,
|
|
165
|
+
lineWidth: 120,
|
|
166
|
+
sortMapEntries: false, // Preserve insertion order
|
|
167
|
+
}), 'utf-8');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function loadSession(filePath: string): Promise<SessionFile> {
|
|
171
|
+
const content = await readFile(filePath, 'utf-8');
|
|
172
|
+
return parse(content) as SessionFile;
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Streaming for Large Files
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { parseAllDocuments } from 'yaml';
|
|
180
|
+
import { createReadStream } from 'fs';
|
|
181
|
+
|
|
182
|
+
// Stream-parse a multi-document YAML file
|
|
183
|
+
async function parseMultiDoc(filePath: string) {
|
|
184
|
+
const content = await readFile(filePath, 'utf-8');
|
|
185
|
+
const docs = parseAllDocuments(content);
|
|
186
|
+
|
|
187
|
+
for (const doc of docs) {
|
|
188
|
+
if (doc.errors.length > 0) {
|
|
189
|
+
console.error(`Parse errors in ${filePath}:`, doc.errors);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
yield doc.toJSON();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Config File Loading
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { parse } from 'yaml';
|
|
201
|
+
import { readFile } from 'fs/promises';
|
|
202
|
+
import { existsSync } from 'fs';
|
|
203
|
+
|
|
204
|
+
// Load coaia.json or coaia.yaml config
|
|
205
|
+
async function loadConfigFile(configPath: string): Promise<Record<string, unknown>> {
|
|
206
|
+
if (!existsSync(configPath)) return {};
|
|
207
|
+
|
|
208
|
+
const content = await readFile(configPath, 'utf-8');
|
|
209
|
+
|
|
210
|
+
if (configPath.endsWith('.yaml') || configPath.endsWith('.yml')) {
|
|
211
|
+
return parse(content) ?? {};
|
|
212
|
+
}
|
|
213
|
+
if (configPath.endsWith('.json')) {
|
|
214
|
+
return JSON.parse(content);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Try YAML first (superset of JSON)
|
|
218
|
+
try {
|
|
219
|
+
return parse(content) ?? {};
|
|
220
|
+
} catch {
|
|
221
|
+
return JSON.parse(content);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Integration Plan
|
|
227
|
+
|
|
228
|
+
1. **Core utility:** `src/yaml.ts` — typed `loadYaml<T>()` and `saveYaml()` wrappers
|
|
229
|
+
2. **Pipeline templates:** `src/pipeline/template-loader.ts` — YAML template discovery + Zod validation
|
|
230
|
+
3. **Session files:** `src/session/file.ts` — YAML session persistence
|
|
231
|
+
4. **Config loading:** `src/config.ts` — support `.yaml` config alongside `.json`
|
|
232
|
+
5. **Round-trip editing:** Use `parseDocument()` for user-facing template modifications
|
|
233
|
+
6. **Multi-doc:** Support `---` separated YAML documents for batch operations
|
|
234
|
+
|
|
235
|
+
## Why Not js-yaml?
|
|
236
|
+
|
|
237
|
+
1. **Comment preservation**: Pipeline templates are human-authored; losing comments on round-trip is unacceptable
|
|
238
|
+
2. **Security**: CVE-2025-64718 (prototype pollution) was a recent concern; `yaml` has a clean record
|
|
239
|
+
3. **YAML 1.2**: Full compliance matters for interop with other tools
|
|
240
|
+
4. **Streaming**: Future-proofing for large narrative JSONL files converted to/from YAML
|
|
241
|
+
5. **TypeScript**: Native types without DefinitelyTyped dependency
|
|
242
|
+
|
|
243
|
+
The ~10% parse speed advantage of js-yaml is irrelevant for config/template files (sub-millisecond either way).
|
|
244
|
+
|
|
245
|
+
## Version & Ecosystem
|
|
246
|
+
|
|
247
|
+
| Metric | Value |
|
|
248
|
+
|--------|-------|
|
|
249
|
+
| Current version | 2.6.x (2026) |
|
|
250
|
+
| Weekly downloads | ~80M |
|
|
251
|
+
| TypeScript | Native types |
|
|
252
|
+
| Dependencies | 0 |
|
|
253
|
+
| YAML spec | 1.2 compliant |
|
|
254
|
+
| Comment round-trip | ✅ Full support |
|
|
255
|
+
| Streaming | ✅ Parse & stringify |
|
|
256
|
+
| Node.js compat | ≥14 (we target ≥20) |
|
|
257
|
+
| License | ISC |
|
|
258
|
+
|
|
259
|
+
## References
|
|
260
|
+
|
|
261
|
+
- npm: https://www.npmjs.com/package/yaml
|
|
262
|
+
- GitHub: https://github.com/eemeli/yaml
|
|
263
|
+
- js-yaml: https://www.npmjs.com/package/js-yaml
|
|
264
|
+
- Comparison: https://npm-compare.com/js-yaml,yaml
|
|
265
|
+
- Performance: https://github.com/eemeli/yaml/discussions/358
|
|
266
|
+
- CVE-2025-64718: https://www.cvedetails.com/cve/CVE-2025-64718/
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Zod Runtime Validation: Technical Assessment for CoAiA.js
|
|
2
|
+
|
|
3
|
+
> Package selection brief — TypeScript-first schema validation replacing coaia-narrative's custom 181-line validation.ts
|
|
4
|
+
|
|
5
|
+
## Summary & Recommendation
|
|
6
|
+
|
|
7
|
+
**Use `zod` v4.x** as the unified validation layer for all coaiajs modules. Zod replaces coaia-narrative's custom `validation.ts` (181 lines of hand-rolled recursive validation) with a standard, composable, type-inferring schema library. The MCP SDK already requires Zod as a peer dependency for tool argument schemas — using it everywhere eliminates dual validation code.
|
|
8
|
+
|
|
9
|
+
**Pin:** `"zod": "^4.0.0"` (v4.3.6 stable, required by `@modelcontextprotocol/sdk`)
|
|
10
|
+
|
|
11
|
+
## What We're Replacing
|
|
12
|
+
|
|
13
|
+
coaia-narrative has a custom `validation.ts` implementing recursive schema validation:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// coaia-narrative/src/validation.ts — 181 lines of custom validation
|
|
17
|
+
type ValidationType = 'string' | 'number' | 'boolean' | 'array' | 'object' | 'date' | 'enum';
|
|
18
|
+
|
|
19
|
+
interface ValidationRule {
|
|
20
|
+
type: ValidationType;
|
|
21
|
+
required?: boolean;
|
|
22
|
+
minLength?: number;
|
|
23
|
+
maxLength?: number;
|
|
24
|
+
pattern?: RegExp;
|
|
25
|
+
minValue?: number;
|
|
26
|
+
maxValue?: number;
|
|
27
|
+
enumValues?: (string | number)[];
|
|
28
|
+
items?: ValidationRule; // array items schema
|
|
29
|
+
properties?: Record<string, ValidationRule>; // object properties
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function validate(args: any, schema: ValidationSchema): { valid: boolean; error?: string }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Pre-built schemas: `stringArray()`, `entityArray()`, `relationArray()`, `isoDate()`, `nonEmptyString()`.
|
|
36
|
+
|
|
37
|
+
This works but: no type inference, no composition, no transform/coerce, no JSON Schema export, and every new validation type requires manual code.
|
|
38
|
+
|
|
39
|
+
## Options Compared
|
|
40
|
+
|
|
41
|
+
| Feature | Zod v4 | Custom validation.ts | io-ts | Yup |
|
|
42
|
+
|---------|--------|---------------------|-------|-----|
|
|
43
|
+
| Type inference | ✅ Automatic `z.infer<>` | ❌ Manual types | ✅ (verbose) | ⚠️ Partial |
|
|
44
|
+
| Schema composition | ✅ `.merge()`, `.extend()`, `.pick()` | ❌ Manual nesting | ✅ | ✅ |
|
|
45
|
+
| MCP SDK compat | ✅ Required peer dep | ❌ N/A | ❌ | ❌ |
|
|
46
|
+
| JSON Schema export | ✅ `toJSONSchema()` | ❌ N/A | ❌ | ❌ |
|
|
47
|
+
| Transform/coerce | ✅ `.transform()`, `.coerce` | ❌ N/A | ❌ | ✅ |
|
|
48
|
+
| Error messages | ✅ Structured `ZodError` | ⚠️ Single string | ⚠️ Verbose | ✅ |
|
|
49
|
+
| Bundle size | ~2KB gzip (core) | ~3KB | ~15KB | ~12KB |
|
|
50
|
+
| Weekly downloads | ~100M+ | N/A | ~8M | ~15M |
|
|
51
|
+
| Performance (v4) | 14x faster strings, 7x faster arrays vs v3 | Adequate | Slower | Slower |
|
|
52
|
+
|
|
53
|
+
## API Overview
|
|
54
|
+
|
|
55
|
+
### Replacing Custom Schemas
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { z } from 'zod';
|
|
59
|
+
|
|
60
|
+
// Replaces: ValidationSchemas.nonEmptyString()
|
|
61
|
+
const NonEmptyString = z.string().min(1);
|
|
62
|
+
|
|
63
|
+
// Replaces: ValidationSchemas.isoDate()
|
|
64
|
+
const IsoDate = z.string().datetime();
|
|
65
|
+
|
|
66
|
+
// Replaces: ValidationSchemas.stringArray(minLength)
|
|
67
|
+
const StringArray = (min = 0) => z.array(z.string()).min(min);
|
|
68
|
+
|
|
69
|
+
// Replaces: ValidationSchemas.entityArray()
|
|
70
|
+
const Entity = z.object({
|
|
71
|
+
name: z.string().min(1),
|
|
72
|
+
entityType: z.string().min(1),
|
|
73
|
+
observations: z.array(z.string()).default([]),
|
|
74
|
+
});
|
|
75
|
+
const EntityArray = z.array(Entity).min(1);
|
|
76
|
+
|
|
77
|
+
// Replaces: ValidationSchemas.relationArray()
|
|
78
|
+
const Relation = z.object({
|
|
79
|
+
from: z.string().min(1),
|
|
80
|
+
to: z.string().min(1),
|
|
81
|
+
relationType: z.string().min(1),
|
|
82
|
+
});
|
|
83
|
+
const RelationArray = z.array(Relation);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Structural Tension Chart Schema
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const ChartSchema = z.object({
|
|
90
|
+
id: z.string().min(1),
|
|
91
|
+
desiredOutcome: z.string().min(1),
|
|
92
|
+
currentReality: z.string().min(1),
|
|
93
|
+
dueDate: z.string().datetime().optional(),
|
|
94
|
+
actionSteps: z.array(z.object({
|
|
95
|
+
title: z.string().min(1),
|
|
96
|
+
status: z.enum(['pending', 'in_progress', 'done', 'blocked']),
|
|
97
|
+
currentReality: z.string().optional(),
|
|
98
|
+
})).default([]),
|
|
99
|
+
createdAt: z.string().datetime(),
|
|
100
|
+
updatedAt: z.string().datetime(),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
type Chart = z.infer<typeof ChartSchema>; // TypeScript type auto-generated
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### MCP Tool Argument Validation
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
110
|
+
import { z } from 'zod';
|
|
111
|
+
|
|
112
|
+
const server = new McpServer({ name: 'coaiajs', version: '1.0.0' });
|
|
113
|
+
|
|
114
|
+
// Zod schemas ARE the MCP tool input schemas — zero duplication
|
|
115
|
+
server.tool(
|
|
116
|
+
'create_chart',
|
|
117
|
+
'Create a structural tension chart',
|
|
118
|
+
{
|
|
119
|
+
desiredOutcome: z.string().describe('What you want to CREATE'),
|
|
120
|
+
currentReality: z.string().describe('Factual assessment of current state'),
|
|
121
|
+
dueDate: z.string().datetime().optional().describe('Target date (ISO 8601)'),
|
|
122
|
+
actionSteps: z.array(z.string()).optional().describe('Initial action steps'),
|
|
123
|
+
},
|
|
124
|
+
async ({ desiredOutcome, currentReality, dueDate, actionSteps }) => {
|
|
125
|
+
// Arguments are already validated and typed
|
|
126
|
+
const chart = await createChart({ desiredOutcome, currentReality, dueDate, actionSteps });
|
|
127
|
+
return { content: [{ type: 'text', text: JSON.stringify(chart) }] };
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Pipeline Template Validation
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// Replaces coaiapy's pipeline.py validate_variables()
|
|
136
|
+
const PipelineVariable = z.object({
|
|
137
|
+
name: z.string().min(1),
|
|
138
|
+
type: z.enum(['string', 'number', 'boolean', 'list']).default('string'),
|
|
139
|
+
required: z.boolean().default(true),
|
|
140
|
+
default: z.unknown().optional(),
|
|
141
|
+
choices: z.array(z.unknown()).optional(),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const PipelineTemplate = z.object({
|
|
145
|
+
name: z.string().min(1),
|
|
146
|
+
version: z.string().default('1.0'),
|
|
147
|
+
variables: z.array(PipelineVariable).default([]),
|
|
148
|
+
steps: z.array(z.object({
|
|
149
|
+
name: z.string().min(1),
|
|
150
|
+
observation_type: z.enum(['EVENT', 'SPAN', 'GENERATION']).default('EVENT'),
|
|
151
|
+
conditional: z.string().optional(),
|
|
152
|
+
})).min(1),
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Validate and get typed result
|
|
156
|
+
function loadTemplate(raw: unknown): PipelineTemplate {
|
|
157
|
+
return PipelineTemplate.parse(raw); // throws ZodError on invalid
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Error Handling
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { z } from 'zod';
|
|
165
|
+
|
|
166
|
+
function validateSafe<T>(schema: z.ZodSchema<T>, data: unknown): { valid: true; data: T } | { valid: false; error: string } {
|
|
167
|
+
const result = schema.safeParse(data);
|
|
168
|
+
if (result.success) {
|
|
169
|
+
return { valid: true, data: result.data };
|
|
170
|
+
}
|
|
171
|
+
// Structured error messages
|
|
172
|
+
const messages = result.error.issues.map(i => `${i.path.join('.')}: ${i.message}`);
|
|
173
|
+
return { valid: false, error: messages.join('; ') };
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Integration Plan
|
|
178
|
+
|
|
179
|
+
1. **Shared schemas:** `src/schemas/` directory with reusable Zod schemas
|
|
180
|
+
- `chart.ts` — ChartSchema, ActionStepSchema
|
|
181
|
+
- `entity.ts` — Entity, Relation schemas (replaces validation.ts)
|
|
182
|
+
- `pipeline.ts` — PipelineTemplate, PipelineVariable
|
|
183
|
+
- `session.ts` — Session, Trace schemas
|
|
184
|
+
2. **MCP tools:** Direct Zod schemas in tool definitions (no separate validation layer)
|
|
185
|
+
3. **Config validation:** Zod for config file/env var validation at startup
|
|
186
|
+
4. **JSON Schema export:** `toJSONSchema()` for documentation and interop
|
|
187
|
+
5. **Migration:** Delete coaia-narrative's `validation.ts`, import from `@coaiajs/schemas`
|
|
188
|
+
|
|
189
|
+
### @zod/mini for Future Frontend
|
|
190
|
+
|
|
191
|
+
If coaiajs adds a web UI, use `@zod/mini` (~1.9KB) for client-side validation with the same schemas.
|
|
192
|
+
|
|
193
|
+
## Version & Ecosystem
|
|
194
|
+
|
|
195
|
+
| Metric | Value |
|
|
196
|
+
|--------|-------|
|
|
197
|
+
| Current version | 4.3.6 (Jan 2026) |
|
|
198
|
+
| Weekly downloads | ~100M+ |
|
|
199
|
+
| TypeScript | First-class, type inference is the core value |
|
|
200
|
+
| Bundle size | ~2KB gzip (core), ~1.9KB (@zod/mini) |
|
|
201
|
+
| MCP SDK | Required peer dependency |
|
|
202
|
+
| JSON Schema | `toJSONSchema()` built-in |
|
|
203
|
+
| Node.js compat | Any (zero native deps) |
|
|
204
|
+
| License | MIT |
|
|
205
|
+
|
|
206
|
+
## References
|
|
207
|
+
|
|
208
|
+
- npm: https://www.npmjs.com/package/zod
|
|
209
|
+
- GitHub: https://github.com/colinhacks/zod
|
|
210
|
+
- v4 announcement: https://www.infoq.com/news/2025/08/zod-v4-available/
|
|
211
|
+
- What's new in v4: https://basicutils.com/learn/zod/whats-new-in-zod-v4
|
|
212
|
+
- MCP SDK peer dep: https://www.npmjs.com/package/@modelcontextprotocol/sdk
|
package/dist/mcp/server.js
CHANGED
|
@@ -13,7 +13,7 @@ import { ALL_TOOL_DEFINITIONS as NARRATIVE_TOOL_DEFINITIONS, handleToolCall as h
|
|
|
13
13
|
import { PDE_MCP_TOOLS, handlePdeTool } from '../src/pde/index.js';
|
|
14
14
|
import { PLANNING_MCP_TOOLS, handlePlanningTool } from '../src/planning/index.js';
|
|
15
15
|
// Langfuse imports
|
|
16
|
-
import { addTrace, patchTraceOutput, listTraces, getTrace,
|
|
16
|
+
import { addTrace, patchTraceOutput, listTraces, getTrace, formatTracesMarkdown, formatTraceTree, } from '../src/langfuse/traces.js';
|
|
17
17
|
import { addObservation, getObservation, formatObservationDisplay } from '../src/langfuse/observations.js';
|
|
18
18
|
import { listPrompts, getPrompt, formatPromptsTable, formatPromptDisplay } from '../src/langfuse/prompts.js';
|
|
19
19
|
import { listDatasets, getDataset, formatDatasetsTable } from '../src/langfuse/datasets.js';
|
|
@@ -133,7 +133,9 @@ async function handleCoaiapyTool(name, args) {
|
|
|
133
133
|
page: args.page,
|
|
134
134
|
limit: args.limit,
|
|
135
135
|
});
|
|
136
|
-
|
|
136
|
+
if (args.json_output)
|
|
137
|
+
return textResult(result);
|
|
138
|
+
return textResult(formatTracesMarkdown(result));
|
|
137
139
|
}
|
|
138
140
|
case 'coaia_fuse_traces_session_view': {
|
|
139
141
|
const result = await listTraces({
|
|
@@ -141,7 +143,7 @@ async function handleCoaiapyTool(name, args) {
|
|
|
141
143
|
});
|
|
142
144
|
if (args.json_output)
|
|
143
145
|
return textResult(result);
|
|
144
|
-
return textResult(
|
|
146
|
+
return textResult(formatTracesMarkdown(result));
|
|
145
147
|
}
|
|
146
148
|
// ── Prompts ──
|
|
147
149
|
case 'coaia_fuse_prompts_list': {
|
|
@@ -145,6 +145,7 @@ export function getCoaiapyToolDefinitions(featureConfig) {
|
|
|
145
145
|
order_by: { type: 'string', description: 'Sort order' },
|
|
146
146
|
page: { type: 'integer', description: 'Page number', default: 1 },
|
|
147
147
|
limit: { type: 'integer', description: 'Items per page', default: 50 },
|
|
148
|
+
json_output: { type: 'boolean', description: 'Return raw JSON instead of markdown table' },
|
|
148
149
|
},
|
|
149
150
|
},
|
|
150
151
|
});
|
package/dist/src/cli.js
CHANGED
|
@@ -61,6 +61,9 @@ async function readStdin() {
|
|
|
61
61
|
}
|
|
62
62
|
return chunks.join('');
|
|
63
63
|
}
|
|
64
|
+
function parseInteger(value) {
|
|
65
|
+
return parseInt(value, 10);
|
|
66
|
+
}
|
|
64
67
|
function resolveText(positional, opts) {
|
|
65
68
|
if (opts.file)
|
|
66
69
|
return readFileSync(resolve(opts.file), 'utf-8');
|
|
@@ -91,7 +94,7 @@ function registerRootCommands(program) {
|
|
|
91
94
|
.argument('<key>', 'Redis key')
|
|
92
95
|
.argument('[value]', 'Value to store')
|
|
93
96
|
.option('-F, --file <path>', 'Read value from file')
|
|
94
|
-
.option('-T, --ttl <
|
|
97
|
+
.option('-T, --ttl <minutes>', 'Time to live in minutes', parseInteger, 5555)
|
|
95
98
|
.option('-v, --verbose', 'Verbose output')
|
|
96
99
|
.action(actionHandler(async (key, value, opts, cmd) => {
|
|
97
100
|
const val = resolveText(value, opts);
|
|
@@ -101,7 +104,7 @@ function registerRootCommands(program) {
|
|
|
101
104
|
}
|
|
102
105
|
await tash(key, val, opts.ttl);
|
|
103
106
|
if (opts.verbose) {
|
|
104
|
-
console.log(formatSuccess(`Stored ${key} (${val.length} bytes${opts.ttl ? `, ttl=${opts.ttl}
|
|
107
|
+
console.log(formatSuccess(`Stored ${key} (${val.length} bytes${opts.ttl ? `, ttl=${opts.ttl}m` : ''})`));
|
|
105
108
|
}
|
|
106
109
|
else {
|
|
107
110
|
console.log(formatSuccess(`Stored ${key}`));
|
|
@@ -112,8 +115,8 @@ function registerRootCommands(program) {
|
|
|
112
115
|
.command('fetch')
|
|
113
116
|
.description('Get a value from Redis')
|
|
114
117
|
.argument('<key>', 'Redis key')
|
|
115
|
-
.option('--output <path>', 'Write result to file')
|
|
116
|
-
.option('--verbose', 'Verbose output')
|
|
118
|
+
.option('-O, --output <path>', 'Write result to file')
|
|
119
|
+
.option('-v, --verbose', 'Verbose output')
|
|
117
120
|
.action(actionHandler(async (key, opts, cmd) => {
|
|
118
121
|
const result = await redisFetch(key);
|
|
119
122
|
if (result === null) {
|
|
@@ -458,6 +461,39 @@ function registerFuseCommands(program) {
|
|
|
458
461
|
}));
|
|
459
462
|
// ── traces ───────────────────────────────────────────────────────────
|
|
460
463
|
const traces = fuse.command('traces').description('Trace operations');
|
|
464
|
+
traces
|
|
465
|
+
.command('list')
|
|
466
|
+
.description('List traces with optional filters')
|
|
467
|
+
.option('--session-id <id>', 'Filter by session ID')
|
|
468
|
+
.option('--user-id <id>', 'Filter by user ID')
|
|
469
|
+
.option('--name <name>', 'Filter by trace name')
|
|
470
|
+
.option('--tags <tags>', 'Comma-separated tags to filter by')
|
|
471
|
+
.option('--from <timestamp>', 'From timestamp (ISO 8601)')
|
|
472
|
+
.option('--to <timestamp>', 'To timestamp (ISO 8601)')
|
|
473
|
+
.option('--order-by <field>', 'Order by field')
|
|
474
|
+
.option('--page <n>', 'Page number', parseInt)
|
|
475
|
+
.option('--limit <n>', 'Items per page (default 50)', parseInt)
|
|
476
|
+
.action(actionHandler(async (opts, cmd) => {
|
|
477
|
+
const filters = {
|
|
478
|
+
sessionId: opts.sessionId,
|
|
479
|
+
userId: opts.userId,
|
|
480
|
+
name: opts.name,
|
|
481
|
+
tags: opts.tags ? opts.tags.split(',').map((t) => t.trim()) : undefined,
|
|
482
|
+
fromTimestamp: opts.from,
|
|
483
|
+
toTimestamp: opts.to,
|
|
484
|
+
orderBy: opts.orderBy,
|
|
485
|
+
page: opts.page,
|
|
486
|
+
limit: opts.limit,
|
|
487
|
+
};
|
|
488
|
+
const raw = await callModule('langfuse', 'listTraces', filters);
|
|
489
|
+
if (globals(cmd).json) {
|
|
490
|
+
console.log(raw);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
const formatted = await callModule('langfuse', 'formatTracesMarkdown', raw);
|
|
494
|
+
console.log(formatted);
|
|
495
|
+
}
|
|
496
|
+
}));
|
|
461
497
|
traces
|
|
462
498
|
.command('create')
|
|
463
499
|
.description('Create a trace')
|
|
@@ -1048,7 +1084,7 @@ async function main() {
|
|
|
1048
1084
|
const program = new Command();
|
|
1049
1085
|
program
|
|
1050
1086
|
.name('coaia')
|
|
1051
|
-
.version('0.1.
|
|
1087
|
+
.version('0.1.3', '-V, --version')
|
|
1052
1088
|
.description('CoAIA unified CLI — structural tension, narrative, and DevOps tooling')
|
|
1053
1089
|
.option('--env <path>', 'Load environment file')
|
|
1054
1090
|
.option('-M, --memory-path <path>', 'JSONL memory file path')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { LangfuseClient, LangfuseApiError, getClient, resetClient, nowISO } from './client.js';
|
|
2
2
|
export type { IngestionEvent, LangfuseClientConfig } from './client.js';
|
|
3
|
-
export { addTrace, patchTraceOutput, listTraces, getTrace, formatTracesTable, formatTraceTree } from './traces.js';
|
|
3
|
+
export { addTrace, patchTraceOutput, listTraces, getTrace, formatTracesTable, formatTracesMarkdown, formatTraceTree } from './traces.js';
|
|
4
4
|
export type { TraceFilters } from './traces.js';
|
|
5
5
|
export { addObservation, getObservation, formatObservationDisplay } from './observations.js';
|
|
6
6
|
export { listPrompts, getPrompt, createPrompt, formatPromptsTable, formatPromptDisplay } from './prompts.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// coaiajs/src/langfuse/index.ts — Barrel export
|
|
2
2
|
export { LangfuseClient, LangfuseApiError, getClient, resetClient, nowISO } from './client.js';
|
|
3
|
-
export { addTrace, patchTraceOutput, listTraces, getTrace, formatTracesTable, formatTraceTree } from './traces.js';
|
|
3
|
+
export { addTrace, patchTraceOutput, listTraces, getTrace, formatTracesTable, formatTracesMarkdown, formatTraceTree } from './traces.js';
|
|
4
4
|
export { addObservation, getObservation, formatObservationDisplay } from './observations.js';
|
|
5
5
|
export { listPrompts, getPrompt, createPrompt, formatPromptsTable, formatPromptDisplay } from './prompts.js';
|
|
6
6
|
export { listDatasets, getDataset, createDataset, listDatasetItems, createDatasetItem, formatDatasetsTable, formatDatasetForFinetuning, } from './datasets.js';
|
|
@@ -26,4 +26,5 @@ export declare function listTraces(filters: TraceFilters): Promise<string>;
|
|
|
26
26
|
export declare function getTrace(traceId: string): Promise<string>;
|
|
27
27
|
export declare function formatTracesTable(json: unknown): string;
|
|
28
28
|
export declare function formatTraceTree(json: unknown): string;
|
|
29
|
+
export declare function formatTracesMarkdown(json: unknown): string;
|
|
29
30
|
//# sourceMappingURL=traces.d.ts.map
|
|
@@ -211,6 +211,30 @@ export function formatTraceTree(json) {
|
|
|
211
211
|
return `Error formatting trace tree: ${e}`;
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
|
+
export function formatTracesMarkdown(json) {
|
|
215
|
+
try {
|
|
216
|
+
const data = typeof json === 'string' ? JSON.parse(json) : json;
|
|
217
|
+
const traces = Array.isArray(data)
|
|
218
|
+
? data
|
|
219
|
+
: data.data ?? [];
|
|
220
|
+
if (!traces.length)
|
|
221
|
+
return '_No traces found._';
|
|
222
|
+
const header = '| ID | Name | Session | User | Timestamp |';
|
|
223
|
+
const sep = '|----|------|---------|------|-----------|';
|
|
224
|
+
const rows = traces.map((t) => {
|
|
225
|
+
const id = trunc(String(t.id ?? ''), 20);
|
|
226
|
+
const name = trunc(String(t.name ?? 'Unnamed'), 30);
|
|
227
|
+
const session = trunc(String(t.sessionId ?? ''), 24);
|
|
228
|
+
const user = trunc(String(t.userId ?? ''), 16);
|
|
229
|
+
const ts = String(t.timestamp ?? '').slice(0, 19);
|
|
230
|
+
return `| ${id} | ${name} | ${session} | ${user} | ${ts} |`;
|
|
231
|
+
});
|
|
232
|
+
return [header, sep, ...rows, '', `_Total: ${traces.length} trace(s)_`].join('\n');
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
return `Error formatting traces: ${e}`;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
214
238
|
// ─── Helpers ────────────────────────────────────────────────────────
|
|
215
239
|
function trunc(s, len) {
|
|
216
240
|
return s.length > len ? s.slice(0, len - 3) + '...' : s;
|
package/dist/src/redis.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ declare const Redis: typeof IORedis.Redis;
|
|
|
3
3
|
type RedisClient = InstanceType<typeof Redis>;
|
|
4
4
|
/** Get or create the lazy Redis client. */
|
|
5
5
|
export declare function getClient(): RedisClient;
|
|
6
|
-
/** Store a key-value pair with optional TTL
|
|
6
|
+
/** Store a key-value pair with optional TTL in minutes, matching coaiapy. */
|
|
7
7
|
export declare function tash(key: string, value: string, ttl?: number): Promise<void>;
|
|
8
8
|
/** Fetch a value by key. Returns null if not found. */
|
|
9
9
|
export declare function fetch(key: string): Promise<string | null>;
|