@radaros/core 0.3.5 → 0.3.7

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 (60) hide show
  1. package/dist/index.d.ts +1711 -0
  2. package/dist/index.js +6341 -0
  3. package/package.json +6 -2
  4. package/src/a2a/a2a-remote-agent.ts +0 -270
  5. package/src/a2a/types.ts +0 -142
  6. package/src/agent/agent.ts +0 -417
  7. package/src/agent/llm-loop.ts +0 -290
  8. package/src/agent/run-context.ts +0 -35
  9. package/src/agent/types.ts +0 -89
  10. package/src/events/event-bus.ts +0 -45
  11. package/src/events/types.ts +0 -16
  12. package/src/guardrails/types.ts +0 -5
  13. package/src/hooks/types.ts +0 -6
  14. package/src/index.ts +0 -157
  15. package/src/knowledge/knowledge-base.ts +0 -146
  16. package/src/logger/logger.ts +0 -249
  17. package/src/mcp/mcp-client.ts +0 -264
  18. package/src/memory/memory.ts +0 -87
  19. package/src/memory/types.ts +0 -13
  20. package/src/memory/user-memory.ts +0 -211
  21. package/src/models/provider.ts +0 -22
  22. package/src/models/providers/anthropic.ts +0 -360
  23. package/src/models/providers/google.ts +0 -386
  24. package/src/models/providers/ollama.ts +0 -211
  25. package/src/models/providers/openai.ts +0 -345
  26. package/src/models/providers/vertex.ts +0 -427
  27. package/src/models/registry.ts +0 -107
  28. package/src/models/types.ts +0 -124
  29. package/src/session/session-manager.ts +0 -75
  30. package/src/session/types.ts +0 -10
  31. package/src/storage/driver.ts +0 -10
  32. package/src/storage/in-memory.ts +0 -44
  33. package/src/storage/mongodb.ts +0 -70
  34. package/src/storage/postgres.ts +0 -81
  35. package/src/storage/sqlite.ts +0 -81
  36. package/src/team/modes.ts +0 -1
  37. package/src/team/team.ts +0 -323
  38. package/src/team/types.ts +0 -26
  39. package/src/toolkits/base.ts +0 -15
  40. package/src/toolkits/duckduckgo.ts +0 -256
  41. package/src/toolkits/gmail.ts +0 -226
  42. package/src/toolkits/hackernews.ts +0 -121
  43. package/src/toolkits/websearch.ts +0 -158
  44. package/src/toolkits/whatsapp.ts +0 -209
  45. package/src/tools/define-tool.ts +0 -22
  46. package/src/tools/tool-executor.ts +0 -221
  47. package/src/tools/types.ts +0 -36
  48. package/src/utils/retry.ts +0 -56
  49. package/src/vector/base.ts +0 -44
  50. package/src/vector/embeddings/google.ts +0 -64
  51. package/src/vector/embeddings/openai.ts +0 -66
  52. package/src/vector/in-memory.ts +0 -115
  53. package/src/vector/mongodb.ts +0 -241
  54. package/src/vector/pgvector.ts +0 -169
  55. package/src/vector/qdrant.ts +0 -203
  56. package/src/vector/types.ts +0 -55
  57. package/src/workflow/step-runner.ts +0 -303
  58. package/src/workflow/types.ts +0 -55
  59. package/src/workflow/workflow.ts +0 -68
  60. package/tsconfig.json +0 -8
@@ -1,249 +0,0 @@
1
- export type LogLevel = "debug" | "info" | "warn" | "error" | "silent";
2
-
3
- const LEVEL_ORDER: Record<LogLevel, number> = {
4
- debug: 0,
5
- info: 1,
6
- warn: 2,
7
- error: 3,
8
- silent: 4,
9
- };
10
-
11
- const C = {
12
- reset: "\x1b[0m",
13
- bold: "\x1b[1m",
14
- dim: "\x1b[2m",
15
- italic: "\x1b[3m",
16
-
17
- black: "\x1b[30m",
18
- red: "\x1b[31m",
19
- green: "\x1b[32m",
20
- yellow: "\x1b[33m",
21
- blue: "\x1b[34m",
22
- magenta: "\x1b[35m",
23
- cyan: "\x1b[36m",
24
- white: "\x1b[37m",
25
-
26
- bgBlack: "\x1b[40m",
27
- bgRed: "\x1b[41m",
28
- bgGreen: "\x1b[42m",
29
- bgYellow: "\x1b[43m",
30
- bgBlue: "\x1b[44m",
31
- bgMagenta: "\x1b[45m",
32
- bgCyan: "\x1b[46m",
33
-
34
- gray: "\x1b[90m",
35
- brightGreen: "\x1b[92m",
36
- brightYellow: "\x1b[93m",
37
- brightBlue: "\x1b[94m",
38
- brightMagenta: "\x1b[95m",
39
- brightCyan: "\x1b[96m",
40
- };
41
-
42
- function noColor(str: string): string {
43
- return str.replace(/\x1b\[[0-9;]*m/g, "");
44
- }
45
-
46
- export interface LoggerConfig {
47
- level?: LogLevel;
48
- color?: boolean;
49
- prefix?: string;
50
- }
51
-
52
- export class Logger {
53
- private level: LogLevel;
54
- private color: boolean;
55
- private prefix: string;
56
-
57
- constructor(config: LoggerConfig = {}) {
58
- this.level = config.level ?? "info";
59
- this.color = config.color ?? process.stdout.isTTY !== false;
60
- this.prefix = config.prefix ?? "radaros";
61
- }
62
-
63
- private c(code: string, text: string): string {
64
- return this.color ? `${code}${text}${C.reset}` : text;
65
- }
66
-
67
- private shouldLog(level: LogLevel): boolean {
68
- return LEVEL_ORDER[level] >= LEVEL_ORDER[this.level];
69
- }
70
-
71
- private tag(level: LogLevel): string {
72
- switch (level) {
73
- case "debug":
74
- return this.c(C.gray, "DBG");
75
- case "info":
76
- return this.c(C.brightCyan, "INF");
77
- case "warn":
78
- return this.c(C.brightYellow, "WRN");
79
- case "error":
80
- return this.c(C.red, "ERR");
81
- default:
82
- return "";
83
- }
84
- }
85
-
86
- private timestamp(): string {
87
- const now = new Date();
88
- const ts = now.toISOString().slice(11, 23);
89
- return this.c(C.dim, ts);
90
- }
91
-
92
- private log(level: LogLevel, msg: string, data?: Record<string, unknown>): void {
93
- if (!this.shouldLog(level)) return;
94
- const parts = [
95
- this.timestamp(),
96
- this.tag(level),
97
- this.c(C.dim, `[${this.prefix}]`),
98
- msg,
99
- ];
100
- if (data && Object.keys(data).length > 0) {
101
- const formatted = Object.entries(data)
102
- .map(([k, v]) => `${this.c(C.dim, k + "=")}${this.formatValue(v)}`)
103
- .join(" ");
104
- parts.push(formatted);
105
- }
106
- console.log(parts.join(" "));
107
- }
108
-
109
- private formatValue(v: unknown): string {
110
- if (typeof v === "number") return this.c(C.brightGreen, String(v));
111
- if (typeof v === "string") return this.c(C.yellow, `"${v}"`);
112
- if (typeof v === "boolean") return this.c(C.magenta, String(v));
113
- return String(v);
114
- }
115
-
116
- debug(msg: string, data?: Record<string, unknown>) {
117
- this.log("debug", msg, data);
118
- }
119
-
120
- info(msg: string, data?: Record<string, unknown>) {
121
- this.log("info", msg, data);
122
- }
123
-
124
- warn(msg: string, data?: Record<string, unknown>) {
125
- this.log("warn", msg, data);
126
- }
127
-
128
- error(msg: string, data?: Record<string, unknown>) {
129
- this.log("error", msg, data);
130
- }
131
-
132
- // ── Formatted agent output helpers ────────────────────────────────────
133
-
134
- private readonly boxWidth = 80;
135
-
136
- private pipe(): string {
137
- return this.c(C.brightCyan, "│");
138
- }
139
-
140
- private printBoxLine(label: string, value: string, labelColor = C.dim, valueColor = C.white): void {
141
- const lines = value.split("\n");
142
- const prefix = `${this.pipe()} ${this.c(labelColor, label)}`;
143
- console.log(`${prefix}${this.c(valueColor, lines[0])}`);
144
- const pad = " ".repeat(noColor(label).length);
145
- for (let i = 1; i < lines.length; i++) {
146
- console.log(`${this.pipe()} ${pad}${this.c(valueColor, lines[i])}`);
147
- }
148
- }
149
-
150
- agentStart(agentName: string, input: string): void {
151
- if (!this.shouldLog("info")) return;
152
- const title = ` Agent: ${agentName} `;
153
- const lineLen = Math.max(0, this.boxWidth - title.length - 2);
154
- console.log("");
155
- console.log(
156
- this.c(C.bold + C.brightCyan, "┌─") +
157
- this.c(C.bold + C.brightCyan, title) +
158
- this.c(C.dim, "─".repeat(lineLen))
159
- );
160
- this.printBoxLine("Input: ", input);
161
- console.log(this.pipe());
162
- }
163
-
164
- toolCall(toolName: string, args: Record<string, unknown>): void {
165
- if (!this.shouldLog("debug")) return;
166
- const argsStr = JSON.stringify(args, null, 2);
167
- console.log(
168
- `${this.pipe()} ${this.c(C.brightMagenta, "⚡")} ${this.c(C.magenta, toolName)}`
169
- );
170
- if (argsStr !== "{}" && argsStr !== "[]") {
171
- const truncated = argsStr.length > 200 ? argsStr.slice(0, 200) + "…" : argsStr;
172
- for (const line of truncated.split("\n")) {
173
- console.log(`${this.pipe()} ${this.c(C.dim, line)}`);
174
- }
175
- }
176
- }
177
-
178
- toolResult(toolName: string, result: string): void {
179
- if (!this.shouldLog("debug")) return;
180
- const truncated = result.length > 300 ? result.slice(0, 300) + "…" : result;
181
- console.log(
182
- `${this.pipe()} ${this.c(C.green, "✓")} ${this.c(C.dim, toolName + " →")}`
183
- );
184
- for (const line of truncated.split("\n")) {
185
- console.log(`${this.pipe()} ${this.c(C.gray, line)}`);
186
- }
187
- console.log(this.pipe());
188
- }
189
-
190
- thinking(content: string): void {
191
- if (!this.shouldLog("info")) return;
192
- const truncated = content.length > 500 ? content.slice(0, 500) + "…" : content;
193
- const label = this.c(C.dim + C.italic, "Thinking: ");
194
- const lines = truncated.split("\n");
195
- console.log(`${this.pipe()} ${label}${this.c(C.dim + C.italic, lines[0])}`);
196
- const pad = " ".repeat(10);
197
- for (let i = 1; i < lines.length; i++) {
198
- console.log(`${this.pipe()} ${pad}${this.c(C.dim + C.italic, lines[i])}`);
199
- }
200
- console.log(this.pipe());
201
- }
202
-
203
- agentEnd(
204
- agentName: string,
205
- output: string,
206
- usage: { promptTokens: number; completionTokens: number; totalTokens: number; reasoningTokens?: number },
207
- durationMs: number
208
- ): void {
209
- if (!this.shouldLog("info")) return;
210
-
211
- console.log(this.pipe());
212
- this.printBoxLine("Output: ", output);
213
- console.log(this.pipe());
214
-
215
- let tokensLine =
216
- this.c(C.dim, "Tokens: ") +
217
- this.c(C.brightGreen, `↑ ${usage.promptTokens}`) +
218
- this.c(C.dim, " ") +
219
- this.c(C.brightGreen, `↓ ${usage.completionTokens}`) +
220
- this.c(C.dim, " ") +
221
- this.c(C.bold + C.brightGreen, `Σ ${usage.totalTokens}`);
222
-
223
- if (usage.reasoningTokens) {
224
- tokensLine += this.c(C.dim, " ") + this.c(C.brightMagenta, `🧠 ${usage.reasoningTokens}`);
225
- }
226
-
227
- const duration =
228
- this.c(C.dim, "Duration: ") +
229
- this.c(C.yellow, this.formatDuration(durationMs));
230
-
231
- console.log(`${this.pipe()} ${tokensLine}`);
232
- console.log(`${this.pipe()} ${duration}`);
233
- console.log(
234
- this.c(C.bold + C.brightCyan, "└") +
235
- this.c(C.dim, "─".repeat(this.boxWidth - 1))
236
- );
237
- }
238
-
239
- separator(): void {
240
- if (!this.shouldLog("info")) return;
241
- console.log(this.c(C.dim, "─".repeat(this.boxWidth)));
242
- }
243
-
244
- private formatDuration(ms: number): string {
245
- if (ms < 1000) return `${ms}ms`;
246
- const secs = (ms / 1000).toFixed(1);
247
- return `${secs}s`;
248
- }
249
- }
@@ -1,264 +0,0 @@
1
- import type { ToolDef, ToolResult } from "../tools/types.js";
2
- import type { RunContext } from "../agent/run-context.js";
3
-
4
- export interface MCPToolProviderConfig {
5
- name: string;
6
- transport: "stdio" | "http";
7
- /** For stdio transport: command to spawn */
8
- command?: string;
9
- /** For stdio transport: args for the command */
10
- args?: string[];
11
- /** For stdio transport: environment variables */
12
- env?: Record<string, string>;
13
- /** For http transport: server URL */
14
- url?: string;
15
- /** For http transport: custom headers */
16
- headers?: Record<string, string>;
17
- }
18
-
19
- /**
20
- * Connects to an MCP (Model Context Protocol) server and exposes its tools
21
- * as native RadarOS ToolDef[] that any Agent can use.
22
- *
23
- * Supports stdio and HTTP (Streamable HTTP) transports.
24
- * Requires: npm install @modelcontextprotocol/sdk
25
- */
26
- export class MCPToolProvider {
27
- readonly name: string;
28
- private config: MCPToolProviderConfig;
29
- private client: any = null;
30
- private transportInstance: any = null;
31
- private tools: ToolDef[] = [];
32
- private connected = false;
33
-
34
- constructor(config: MCPToolProviderConfig) {
35
- this.name = config.name;
36
- this.config = config;
37
- }
38
-
39
- async connect(): Promise<void> {
40
- if (this.connected) return;
41
-
42
- let ClientClass: any;
43
- try {
44
- const mod = await import("@modelcontextprotocol/sdk/client/index.js");
45
- ClientClass = mod.Client;
46
- } catch {
47
- throw new Error(
48
- "@modelcontextprotocol/sdk is required for MCPToolProvider. Install it: npm install @modelcontextprotocol/sdk"
49
- );
50
- }
51
-
52
- this.client = new ClientClass(
53
- { name: `radaros-${this.name}`, version: "1.0.0" },
54
- { capabilities: {} }
55
- );
56
-
57
- if (this.config.transport === "stdio") {
58
- if (!this.config.command) {
59
- throw new Error("MCPToolProvider: 'command' is required for stdio transport");
60
- }
61
- const { StdioClientTransport } = await import(
62
- "@modelcontextprotocol/sdk/client/stdio.js"
63
- );
64
-
65
- this.transportInstance = new StdioClientTransport({
66
- command: this.config.command,
67
- args: this.config.args ?? [],
68
- env: { ...process.env, ...(this.config.env ?? {}) } as Record<string, string>,
69
- });
70
- } else if (this.config.transport === "http") {
71
- if (!this.config.url) {
72
- throw new Error("MCPToolProvider: 'url' is required for http transport");
73
- }
74
-
75
- let TransportClass: any;
76
- try {
77
- const mod = await import(
78
- "@modelcontextprotocol/sdk/client/streamableHttp.js"
79
- );
80
- TransportClass = mod.StreamableHTTPClientTransport;
81
- } catch {
82
- const mod = await import("@modelcontextprotocol/sdk/client/sse.js");
83
- TransportClass = mod.SSEClientTransport;
84
- }
85
-
86
- this.transportInstance = new TransportClass(
87
- new URL(this.config.url),
88
- { requestInit: { headers: this.config.headers ?? {} } }
89
- );
90
- } else {
91
- throw new Error(`MCPToolProvider: unsupported transport '${this.config.transport}'`);
92
- }
93
-
94
- await this.client.connect(this.transportInstance);
95
- this.connected = true;
96
- await this.discoverTools();
97
- }
98
-
99
- private async discoverTools(): Promise<void> {
100
- const { z } = await import("zod");
101
- const result = await this.client.listTools();
102
- const mcpTools: any[] = result.tools ?? [];
103
-
104
- this.tools = mcpTools.map((mcpTool: any) => {
105
- const toolName = mcpTool.name;
106
- const description = mcpTool.description ?? "";
107
- const inputSchema = mcpTool.inputSchema ?? { type: "object", properties: {} };
108
-
109
- const parameters = this.jsonSchemaToZod(inputSchema, z);
110
-
111
- const execute = async (
112
- args: Record<string, unknown>,
113
- _ctx: RunContext
114
- ): Promise<string | ToolResult> => {
115
- const callResult = await this.client.callTool({
116
- name: toolName,
117
- arguments: args,
118
- });
119
-
120
- const contents: any[] = callResult.content ?? [];
121
- const textParts = contents
122
- .filter((c: any) => c.type === "text")
123
- .map((c: any) => c.text);
124
-
125
- const text = textParts.join("\n") || JSON.stringify(callResult);
126
-
127
- const artifacts = contents
128
- .filter((c: any) => c.type !== "text")
129
- .map((c: any) => ({
130
- type: c.type,
131
- data: c.data ?? c.blob ?? c.text,
132
- mimeType: c.mimeType,
133
- }));
134
-
135
- if (artifacts.length > 0) {
136
- return { content: text, artifacts };
137
- }
138
- return text;
139
- };
140
-
141
- return {
142
- name: `${this.name}__${toolName}`,
143
- description: `[${this.name}] ${description}`,
144
- parameters,
145
- execute,
146
- rawJsonSchema: inputSchema,
147
- } satisfies ToolDef;
148
- });
149
- }
150
-
151
- private jsonSchemaToZod(schema: any, z: any): any {
152
- if (!schema || !schema.properties) {
153
- return z.object({}).passthrough();
154
- }
155
-
156
- const shape: Record<string, any> = {};
157
- const required: string[] = schema.required ?? [];
158
-
159
- for (const [key, prop] of Object.entries(schema.properties) as [string, any][]) {
160
- let field: any;
161
-
162
- switch (prop.type) {
163
- case "string":
164
- field = z.string();
165
- if (prop.enum) field = z.enum(prop.enum);
166
- break;
167
- case "number":
168
- case "integer":
169
- field = z.number();
170
- break;
171
- case "boolean":
172
- field = z.boolean();
173
- break;
174
- case "array":
175
- field = z.array(z.any());
176
- break;
177
- case "object":
178
- field = z.record(z.any());
179
- break;
180
- default:
181
- field = z.any();
182
- }
183
-
184
- if (prop.description) {
185
- field = field.describe(prop.description);
186
- }
187
-
188
- if (!required.includes(key)) {
189
- field = field.optional();
190
- }
191
-
192
- shape[key] = field;
193
- }
194
-
195
- return z.object(shape);
196
- }
197
-
198
- /**
199
- * Returns tools from this MCP server as RadarOS ToolDef[].
200
- * Optionally filter by tool names to reduce token usage.
201
- *
202
- * @param filter - Tool names to include (without the server name prefix).
203
- * If omitted, returns all tools.
204
- *
205
- * @example
206
- * // All tools
207
- * await mcp.getTools()
208
- *
209
- * // Only specific tools (pass the original MCP tool names, not prefixed)
210
- * await mcp.getTools({ include: ["get_latest_release", "search_repositories"] })
211
- *
212
- * // Exclude specific tools
213
- * await mcp.getTools({ exclude: ["push_files", "create_repository"] })
214
- */
215
- async getTools(filter?: {
216
- include?: string[];
217
- exclude?: string[];
218
- }): Promise<ToolDef[]> {
219
- if (!this.connected) {
220
- await this.connect();
221
- }
222
-
223
- if (!filter) {
224
- return [...this.tools];
225
- }
226
-
227
- const prefix = `${this.name}__`;
228
-
229
- return this.tools.filter((tool) => {
230
- const shortName = tool.name.startsWith(prefix)
231
- ? tool.name.slice(prefix.length)
232
- : tool.name;
233
-
234
- if (filter.include) {
235
- return filter.include.includes(shortName);
236
- }
237
- if (filter.exclude) {
238
- return !filter.exclude.includes(shortName);
239
- }
240
- return true;
241
- });
242
- }
243
-
244
- /** Refresh the tool list from the MCP server. */
245
- async refresh(): Promise<void> {
246
- if (!this.connected) {
247
- throw new Error("MCPToolProvider: not connected. Call connect() first.");
248
- }
249
- await this.discoverTools();
250
- }
251
-
252
- /** Disconnect from the MCP server. */
253
- async close(): Promise<void> {
254
- if (this.client && this.connected) {
255
- try {
256
- await this.client.close();
257
- } catch {
258
- // ignore close errors
259
- }
260
- this.connected = false;
261
- this.tools = [];
262
- }
263
- }
264
- }
@@ -1,87 +0,0 @@
1
- import { InMemoryStorage } from "../storage/in-memory.js";
2
- import type { StorageDriver } from "../storage/driver.js";
3
- import { getTextContent, type ChatMessage } from "../models/types.js";
4
- import type { MemoryConfig, MemoryEntry } from "./types.js";
5
-
6
- const SHORT_TERM_NS = "memory:short";
7
- const LONG_TERM_NS = "memory:long";
8
-
9
- export class Memory {
10
- private storage: StorageDriver;
11
- private maxShortTermMessages: number;
12
- private enableLongTerm: boolean;
13
-
14
- constructor(config?: MemoryConfig) {
15
- this.storage = config?.storage ?? new InMemoryStorage();
16
- this.maxShortTermMessages = config?.maxShortTermMessages ?? 50;
17
- this.enableLongTerm = config?.enableLongTerm ?? false;
18
- }
19
-
20
- async addMessages(
21
- sessionId: string,
22
- messages: ChatMessage[]
23
- ): Promise<void> {
24
- const existing =
25
- (await this.storage.get<ChatMessage[]>(SHORT_TERM_NS, sessionId)) ?? [];
26
- const updated = [...existing, ...messages];
27
-
28
- if (updated.length > this.maxShortTermMessages) {
29
- const overflow = updated.splice(
30
- 0,
31
- updated.length - this.maxShortTermMessages
32
- );
33
-
34
- if (this.enableLongTerm && overflow.length > 0) {
35
- await this.summarizeAndStore(sessionId, overflow);
36
- }
37
- }
38
-
39
- await this.storage.set(SHORT_TERM_NS, sessionId, updated);
40
- }
41
-
42
- async getMessages(sessionId: string): Promise<ChatMessage[]> {
43
- return (
44
- (await this.storage.get<ChatMessage[]>(SHORT_TERM_NS, sessionId)) ?? []
45
- );
46
- }
47
-
48
- async getSummaries(sessionId: string): Promise<string[]> {
49
- if (!this.enableLongTerm) return [];
50
-
51
- const entries = await this.storage.list<MemoryEntry>(
52
- LONG_TERM_NS,
53
- sessionId
54
- );
55
- return entries.map((e) => e.value.summary);
56
- }
57
-
58
- async getContextString(sessionId: string): Promise<string> {
59
- const summaries = await this.getSummaries(sessionId);
60
- if (summaries.length === 0) return "";
61
- return `Previous context:\n${summaries.join("\n")}`;
62
- }
63
-
64
- private async summarizeAndStore(
65
- sessionId: string,
66
- messages: ChatMessage[]
67
- ): Promise<void> {
68
- const textParts = messages
69
- .filter((m) => m.content)
70
- .map((m) => `${m.role}: ${getTextContent(m.content)}`);
71
-
72
- if (textParts.length === 0) return;
73
-
74
- const summary = textParts.join(" | ").slice(0, 500);
75
- const entry: MemoryEntry = {
76
- key: `${sessionId}:${Date.now()}`,
77
- summary,
78
- createdAt: new Date(),
79
- };
80
-
81
- await this.storage.set(LONG_TERM_NS, entry.key, entry);
82
- }
83
-
84
- async clear(sessionId: string): Promise<void> {
85
- await this.storage.delete(SHORT_TERM_NS, sessionId);
86
- }
87
- }
@@ -1,13 +0,0 @@
1
- import type { StorageDriver } from "../storage/driver.js";
2
-
3
- export interface MemoryConfig {
4
- storage?: StorageDriver;
5
- maxShortTermMessages?: number;
6
- enableLongTerm?: boolean;
7
- }
8
-
9
- export interface MemoryEntry {
10
- key: string;
11
- summary: string;
12
- createdAt: Date;
13
- }