@gugacoder/agentic-sdk 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.
Files changed (129) hide show
  1. package/dist/agent.d.ts +2 -0
  2. package/dist/agent.js +463 -0
  3. package/dist/context/compaction.d.ts +27 -0
  4. package/dist/context/compaction.js +219 -0
  5. package/dist/context/models.d.ts +6 -0
  6. package/dist/context/models.js +41 -0
  7. package/dist/context/tokenizer.d.ts +5 -0
  8. package/dist/context/tokenizer.js +11 -0
  9. package/dist/context/usage.d.ts +11 -0
  10. package/dist/context/usage.js +49 -0
  11. package/dist/display-schemas.d.ts +1865 -0
  12. package/dist/display-schemas.js +219 -0
  13. package/dist/index.d.ts +38 -0
  14. package/dist/index.js +28 -0
  15. package/dist/middleware/logging.d.ts +2 -0
  16. package/dist/middleware/logging.js +32 -0
  17. package/dist/prompts/assembly.d.ts +13 -0
  18. package/dist/prompts/assembly.js +229 -0
  19. package/dist/providers.d.ts +19 -0
  20. package/dist/providers.js +44 -0
  21. package/dist/proxy.d.ts +2 -0
  22. package/dist/proxy.js +103 -0
  23. package/dist/schemas.d.ts +228 -0
  24. package/dist/schemas.js +51 -0
  25. package/dist/session.d.ts +7 -0
  26. package/dist/session.js +102 -0
  27. package/dist/structured.d.ts +18 -0
  28. package/dist/structured.js +38 -0
  29. package/dist/tool-repair.d.ts +21 -0
  30. package/dist/tool-repair.js +72 -0
  31. package/dist/tools/api-spec.d.ts +4 -0
  32. package/dist/tools/api-spec.js +123 -0
  33. package/dist/tools/apply-patch.d.ts +484 -0
  34. package/dist/tools/apply-patch.js +157 -0
  35. package/dist/tools/ask-user.d.ts +14 -0
  36. package/dist/tools/ask-user.js +27 -0
  37. package/dist/tools/bash.d.ts +550 -0
  38. package/dist/tools/bash.js +43 -0
  39. package/dist/tools/batch.d.ts +13 -0
  40. package/dist/tools/batch.js +84 -0
  41. package/dist/tools/brave-search.d.ts +6 -0
  42. package/dist/tools/brave-search.js +19 -0
  43. package/dist/tools/code-search.d.ts +20 -0
  44. package/dist/tools/code-search.js +42 -0
  45. package/dist/tools/diagnostics.d.ts +4 -0
  46. package/dist/tools/diagnostics.js +69 -0
  47. package/dist/tools/display.d.ts +483 -0
  48. package/dist/tools/display.js +77 -0
  49. package/dist/tools/edit.d.ts +682 -0
  50. package/dist/tools/edit.js +47 -0
  51. package/dist/tools/glob.d.ts +4 -0
  52. package/dist/tools/glob.js +42 -0
  53. package/dist/tools/grep.d.ts +6 -0
  54. package/dist/tools/grep.js +69 -0
  55. package/dist/tools/http-request.d.ts +7 -0
  56. package/dist/tools/http-request.js +98 -0
  57. package/dist/tools/index.d.ts +1611 -0
  58. package/dist/tools/index.js +46 -0
  59. package/dist/tools/job-tools.d.ts +24 -0
  60. package/dist/tools/job-tools.js +67 -0
  61. package/dist/tools/list-dir.d.ts +5 -0
  62. package/dist/tools/list-dir.js +79 -0
  63. package/dist/tools/multi-edit.d.ts +814 -0
  64. package/dist/tools/multi-edit.js +57 -0
  65. package/dist/tools/read.d.ts +5 -0
  66. package/dist/tools/read.js +33 -0
  67. package/dist/tools/task.d.ts +21 -0
  68. package/dist/tools/task.js +51 -0
  69. package/dist/tools/todo.d.ts +14 -0
  70. package/dist/tools/todo.js +60 -0
  71. package/dist/tools/web-fetch.d.ts +4 -0
  72. package/dist/tools/web-fetch.js +126 -0
  73. package/dist/tools/web-search.d.ts +22 -0
  74. package/dist/tools/web-search.js +48 -0
  75. package/dist/tools/write.d.ts +550 -0
  76. package/dist/tools/write.js +30 -0
  77. package/dist/types.d.ts +201 -0
  78. package/dist/types.js +1 -0
  79. package/package.json +43 -0
  80. package/src/agent.ts +520 -0
  81. package/src/context/compaction.ts +265 -0
  82. package/src/context/models.ts +42 -0
  83. package/src/context/tokenizer.ts +12 -0
  84. package/src/context/usage.ts +65 -0
  85. package/src/display-schemas.ts +276 -0
  86. package/src/index.ts +43 -0
  87. package/src/middleware/logging.ts +37 -0
  88. package/src/prompts/assembly.ts +263 -0
  89. package/src/prompts/identity.md +10 -0
  90. package/src/prompts/patterns.md +7 -0
  91. package/src/prompts/safety.md +7 -0
  92. package/src/prompts/tool-guide.md +9 -0
  93. package/src/prompts/tools/bash.md +7 -0
  94. package/src/prompts/tools/edit.md +7 -0
  95. package/src/prompts/tools/glob.md +7 -0
  96. package/src/prompts/tools/grep.md +7 -0
  97. package/src/prompts/tools/read.md +7 -0
  98. package/src/prompts/tools/write.md +7 -0
  99. package/src/providers.ts +58 -0
  100. package/src/proxy.ts +101 -0
  101. package/src/schemas.ts +58 -0
  102. package/src/session.ts +110 -0
  103. package/src/structured.ts +65 -0
  104. package/src/tool-repair.ts +92 -0
  105. package/src/tools/api-spec.ts +158 -0
  106. package/src/tools/apply-patch.ts +188 -0
  107. package/src/tools/ask-user.ts +40 -0
  108. package/src/tools/bash.ts +51 -0
  109. package/src/tools/batch.ts +103 -0
  110. package/src/tools/brave-search.ts +24 -0
  111. package/src/tools/code-search.ts +69 -0
  112. package/src/tools/diagnostics.ts +93 -0
  113. package/src/tools/display.ts +105 -0
  114. package/src/tools/edit.ts +55 -0
  115. package/src/tools/glob.ts +46 -0
  116. package/src/tools/grep.ts +68 -0
  117. package/src/tools/http-request.ts +103 -0
  118. package/src/tools/index.ts +48 -0
  119. package/src/tools/job-tools.ts +84 -0
  120. package/src/tools/list-dir.ts +102 -0
  121. package/src/tools/multi-edit.ts +65 -0
  122. package/src/tools/read.ts +40 -0
  123. package/src/tools/task.ts +71 -0
  124. package/src/tools/todo.ts +82 -0
  125. package/src/tools/web-fetch.ts +155 -0
  126. package/src/tools/web-search.ts +75 -0
  127. package/src/tools/write.ts +34 -0
  128. package/src/types.ts +145 -0
  129. package/tsconfig.json +17 -0
@@ -0,0 +1,265 @@
1
+ import { generateText, Output, wrapLanguageModel, type ModelMessage, type LanguageModelMiddleware, type TelemetrySettings } from "ai";
2
+ import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
3
+ import { z } from "zod";
4
+ import { countTokens } from "./tokenizer.js";
5
+ import { getContextWindow } from "./models.js";
6
+ import type { AiTelemetryOptions } from "../types.js";
7
+ import type { createAiProviderRegistry } from "../providers.js";
8
+
9
+ const CompactionSchema = z.object({
10
+ summary: z.string().describe("Resumo conciso da conversa"),
11
+ decisions: z.array(z.string()).describe("Decisoes tomadas"),
12
+ filesModified: z.array(z.string()).describe("Arquivos modificados"),
13
+ currentState: z.string().describe("Estado atual do trabalho"),
14
+ nextSteps: z.array(z.string()).describe("Proximos passos"),
15
+ });
16
+
17
+ const TAIL_RATIO = 0.30;
18
+
19
+ const SUMMARIZATION_PROMPT = `Summarize the conversation below preserving: decisions made, files modified, current state of work, and next steps. Be concise.`;
20
+
21
+ export interface CompactOptions {
22
+ model: string;
23
+ apiKey: string;
24
+ contextWindow?: number;
25
+ systemPromptTokens: number;
26
+ toolDefinitionsTokens: number;
27
+ middleware?: LanguageModelMiddleware[];
28
+ telemetry?: AiTelemetryOptions;
29
+ /** Provider registry para reuso. Se nao fornecido, cria um internamente (backward compat). */
30
+ providers?: ReturnType<typeof createAiProviderRegistry>;
31
+ }
32
+
33
+ export interface CompactResult {
34
+ messages: ModelMessage[];
35
+ compacted: boolean;
36
+ warning?: string;
37
+ }
38
+
39
+ function countMessageTokens(msg: ModelMessage): number {
40
+ let tokens = 4; // overhead for role + framing
41
+ if (typeof msg.content === "string") {
42
+ tokens += countTokens(msg.content);
43
+ } else if (Array.isArray(msg.content)) {
44
+ for (const part of msg.content as any[]) {
45
+ if (part.type === "text" && typeof part.text === "string") {
46
+ tokens += countTokens(part.text);
47
+ } else if (part.type === "image") {
48
+ tokens += 300;
49
+ } else if (part.type === "file") {
50
+ const mime: string = part.mimeType ?? "";
51
+ if (mime === "application/pdf") {
52
+ tokens += 1500;
53
+ } else if (mime.startsWith("audio/")) {
54
+ tokens += 200;
55
+ } else {
56
+ tokens += 500;
57
+ }
58
+ } else {
59
+ tokens += 500; // unknown part fallback
60
+ }
61
+ }
62
+ }
63
+ return tokens;
64
+ }
65
+
66
+ function formatMessagesForSummary(messages: ModelMessage[]): string {
67
+ return messages
68
+ .map((msg) => {
69
+ const role = msg.role;
70
+ const content =
71
+ typeof msg.content === "string"
72
+ ? msg.content
73
+ : Array.isArray(msg.content)
74
+ ? msg.content
75
+ .filter((p) => "text" in p)
76
+ .map((p) => (p as any).text)
77
+ .join("\n")
78
+ : "[non-text content]";
79
+ return `[${role}]: ${content}`;
80
+ })
81
+ .join("\n\n");
82
+ }
83
+
84
+ /**
85
+ * Splits messages into head (to summarize) and tail (to keep intact).
86
+ * Tail = last messages that fit within 30% of the context window.
87
+ */
88
+ function splitMessages(
89
+ messages: ModelMessage[],
90
+ contextWindow: number
91
+ ): { head: ModelMessage[]; tail: ModelMessage[] } {
92
+ const tailBudget = Math.floor(contextWindow * TAIL_RATIO);
93
+ let tailTokens = 0;
94
+ let tailStart = messages.length;
95
+
96
+ // Walk backwards to find tail boundary
97
+ for (let i = messages.length - 1; i >= 0; i--) {
98
+ const msgTokens = countMessageTokens(messages[i]);
99
+ if (tailTokens + msgTokens > tailBudget) break;
100
+ tailTokens += msgTokens;
101
+ tailStart = i;
102
+ }
103
+
104
+ // Ensure at least one message in head (nothing to compact otherwise)
105
+ if (tailStart <= 0) {
106
+ tailStart = 1;
107
+ }
108
+
109
+ return {
110
+ head: messages.slice(0, tailStart),
111
+ tail: messages.slice(tailStart),
112
+ };
113
+ }
114
+
115
+ /**
116
+ * Compacts conversation messages by summarizing older messages (head)
117
+ * and keeping recent messages (tail) intact.
118
+ *
119
+ * Uses the same model and apiKey as the agent for summarization.
120
+ * If summarization fails, returns original messages with a warning.
121
+ */
122
+ export async function compactMessages(
123
+ messages: ModelMessage[],
124
+ options: CompactOptions
125
+ ): Promise<CompactResult> {
126
+ // Nothing to compact if 2 or fewer messages
127
+ if (messages.length <= 2) {
128
+ return { messages, compacted: false };
129
+ }
130
+
131
+ const contextWindow = getContextWindow(options.model, options.contextWindow);
132
+ const { head, tail } = splitMessages(messages, contextWindow);
133
+
134
+ // If head is empty or has only 1 message, nothing to compact
135
+ if (head.length <= 1) {
136
+ return { messages, compacted: false };
137
+ }
138
+
139
+ const conversationText = formatMessagesForSummary(head);
140
+
141
+ // Build telemetry config for compaction spans
142
+ const telemetryConfig: TelemetrySettings | undefined =
143
+ options.telemetry?.enabled
144
+ ? {
145
+ isEnabled: true,
146
+ functionId: options.telemetry.functionId
147
+ ? `${options.telemetry.functionId}-compaction`
148
+ : "ai-compaction",
149
+ recordInputs: false,
150
+ recordOutputs: false,
151
+ metadata: {
152
+ ...options.telemetry.metadata,
153
+ },
154
+ }
155
+ : undefined;
156
+
157
+ try {
158
+ const baseModel = options.providers
159
+ ? options.providers.model(options.model)
160
+ : createOpenAICompatible({
161
+ name: "openrouter",
162
+ baseURL: "https://openrouter.ai/api/v1",
163
+ apiKey: options.apiKey,
164
+ })(options.model);
165
+ const model =
166
+ options.middleware && options.middleware.length > 0
167
+ ? wrapLanguageModel({ model: baseModel, middleware: options.middleware })
168
+ : baseModel;
169
+
170
+ const result = await generateText({
171
+ model,
172
+ output: Output.object({ schema: CompactionSchema }),
173
+ system: SUMMARIZATION_PROMPT,
174
+ messages: [{ role: "user", content: conversationText }],
175
+ maxOutputTokens: 2000,
176
+ ...(telemetryConfig ? { experimental_telemetry: telemetryConfig } : {}),
177
+ });
178
+
179
+ const obj = result.output!;
180
+ if (!obj.summary || obj.summary.trim().length === 0) {
181
+ return {
182
+ messages,
183
+ compacted: false,
184
+ warning: "Compaction produced empty summary — keeping original messages",
185
+ };
186
+ }
187
+
188
+ const sections: string[] = [];
189
+ sections.push(`## Summary\n${obj.summary}`);
190
+ if (obj.decisions.length > 0) {
191
+ sections.push(`## Decisions\n${obj.decisions.map((d) => `- ${d}`).join("\n")}`);
192
+ }
193
+ if (obj.filesModified.length > 0) {
194
+ sections.push(`## Files Modified\n${obj.filesModified.map((f) => `- ${f}`).join("\n")}`);
195
+ }
196
+ sections.push(`## Current State\n${obj.currentState}`);
197
+ if (obj.nextSteps.length > 0) {
198
+ sections.push(`## Next Steps\n${obj.nextSteps.map((s) => `- ${s}`).join("\n")}`);
199
+ }
200
+
201
+ const summary = sections.join("\n\n");
202
+
203
+ const summaryMessage: ModelMessage = {
204
+ role: "user",
205
+ content: `<context_summary>\n${summary}\n</context_summary>`,
206
+ };
207
+
208
+ return {
209
+ messages: [summaryMessage, ...tail],
210
+ compacted: true,
211
+ };
212
+ } catch (structuredErr) {
213
+ // Fallback: generateObject() failed — try generateText() with free-text summarization
214
+ try {
215
+ const fallbackBaseModel = options.providers
216
+ ? options.providers.model(options.model)
217
+ : createOpenAICompatible({
218
+ name: "openrouter",
219
+ baseURL: "https://openrouter.ai/api/v1",
220
+ apiKey: options.apiKey,
221
+ })(options.model);
222
+ const fallbackModel =
223
+ options.middleware && options.middleware.length > 0
224
+ ? wrapLanguageModel({ model: fallbackBaseModel, middleware: options.middleware })
225
+ : fallbackBaseModel;
226
+
227
+ const result = await generateText({
228
+ model: fallbackModel,
229
+ system: SUMMARIZATION_PROMPT,
230
+ messages: [{ role: "user", content: conversationText }],
231
+ maxOutputTokens: 2000,
232
+ ...(telemetryConfig ? { experimental_telemetry: telemetryConfig } : {}),
233
+ });
234
+
235
+ const summary = result.text;
236
+ if (!summary || summary.trim().length === 0) {
237
+ return {
238
+ messages,
239
+ compacted: false,
240
+ warning: "Compaction fallback produced empty summary — keeping original messages",
241
+ };
242
+ }
243
+
244
+ const summaryMessage: ModelMessage = {
245
+ role: "user",
246
+ content: `<context_summary>\n${summary}\n</context_summary>`,
247
+ };
248
+
249
+ const structuredMsg = structuredErr instanceof Error ? structuredErr.message : String(structuredErr);
250
+ return {
251
+ messages: [summaryMessage, ...tail],
252
+ compacted: true,
253
+ warning: `Structured compaction failed (${structuredMsg}) — used text fallback`,
254
+ };
255
+ } catch (fallbackErr) {
256
+ const structuredMsg = structuredErr instanceof Error ? structuredErr.message : String(structuredErr);
257
+ const fallbackMsg = fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr);
258
+ return {
259
+ messages,
260
+ compacted: false,
261
+ warning: `Compaction failed: structured (${structuredMsg}), fallback (${fallbackMsg}) — keeping original messages`,
262
+ };
263
+ }
264
+ }
265
+ }
@@ -0,0 +1,42 @@
1
+ const MODEL_CONTEXT_WINDOWS: Record<string, number> = {
2
+ // Anthropic — Claude 4.x
3
+ "anthropic/claude-opus-4-6": 200000,
4
+ "anthropic/claude-sonnet-4-5": 200000,
5
+ "anthropic/claude-haiku-4-5": 200000,
6
+ // Anthropic — Claude 3.x
7
+ "anthropic/claude-3.5-sonnet": 200000,
8
+ "anthropic/claude-3.5-haiku": 200000,
9
+ "anthropic/claude-3-opus": 200000,
10
+ "anthropic/claude-3-sonnet": 200000,
11
+ "anthropic/claude-3-haiku": 200000,
12
+ // OpenAI
13
+ "openai/gpt-4o": 128000,
14
+ "openai/gpt-4o-mini": 128000,
15
+ "openai/gpt-4-turbo": 128000,
16
+ "openai/o1": 200000,
17
+ "openai/o1-mini": 128000,
18
+ "openai/o3": 200000,
19
+ "openai/o3-mini": 200000,
20
+ "openai/o4-mini": 200000,
21
+ // Google
22
+ "google/gemini-2.0-flash": 1048576,
23
+ "google/gemini-2.5-pro": 1048576,
24
+ // Meta
25
+ "meta-llama/llama-3.1-405b-instruct": 131072,
26
+ "meta-llama/llama-3.1-70b-instruct": 131072,
27
+ // DeepSeek
28
+ "deepseek/deepseek-chat-v3": 131072,
29
+ "deepseek/deepseek-r1": 131072,
30
+ };
31
+
32
+ const DEFAULT_CONTEXT_WINDOW = 128000;
33
+
34
+ /**
35
+ * Returns the context window size for a model ID.
36
+ * If `override` is provided, it takes precedence over the map.
37
+ * Unknown models default to 128000.
38
+ */
39
+ export function getContextWindow(modelId: string, override?: number): number {
40
+ if (override !== undefined) return override;
41
+ return MODEL_CONTEXT_WINDOWS[modelId] ?? DEFAULT_CONTEXT_WINDOW;
42
+ }
@@ -0,0 +1,12 @@
1
+ import { encodingForModel } from "js-tiktoken";
2
+
3
+ const encoder = encodingForModel("gpt-4o");
4
+
5
+ /**
6
+ * Returns an estimated token count for the given text using cl100k_base encoding.
7
+ * Returns 0 for empty strings.
8
+ */
9
+ export function countTokens(text: string): number {
10
+ if (!text) return 0;
11
+ return encoder.encode(text).length;
12
+ }
@@ -0,0 +1,65 @@
1
+ import type { ModelMessage } from "ai";
2
+ import { countTokens } from "./tokenizer.js";
3
+ import { getContextWindow } from "./models.js";
4
+ import type { ContextUsage } from "../types.js";
5
+
6
+ const DEFAULT_COMPACT_THRESHOLD = 0.65;
7
+
8
+ export interface GetContextUsageOptions {
9
+ model: string;
10
+ systemPrompt: string;
11
+ toolDefinitions: Record<string, unknown>;
12
+ messages: ModelMessage[];
13
+ contextWindow?: number;
14
+ compactThreshold?: number;
15
+ }
16
+
17
+ function countMessagesTokens(messages: ModelMessage[]): number {
18
+ let total = 0;
19
+ for (const msg of messages) {
20
+ if (typeof msg.content === "string") {
21
+ total += countTokens(msg.content);
22
+ } else if (Array.isArray(msg.content)) {
23
+ for (const part of msg.content) {
24
+ if ("text" in part && typeof part.text === "string") {
25
+ total += countTokens(part.text);
26
+ }
27
+ }
28
+ }
29
+ // Add overhead for role + message framing (~4 tokens per message)
30
+ total += 4;
31
+ }
32
+ return total;
33
+ }
34
+
35
+ function countToolDefinitionsTokens(tools: Record<string, unknown>): number {
36
+ if (!tools || Object.keys(tools).length === 0) return 0;
37
+ return countTokens(JSON.stringify(tools));
38
+ }
39
+
40
+ export function getContextUsage(options: GetContextUsageOptions): ContextUsage {
41
+ const contextWindow = getContextWindow(options.model, options.contextWindow);
42
+ const compactThreshold = options.compactThreshold ?? DEFAULT_COMPACT_THRESHOLD;
43
+
44
+ const systemPrompt = countTokens(options.systemPrompt);
45
+ const toolDefinitions = countToolDefinitionsTokens(options.toolDefinitions);
46
+ const messages = countMessagesTokens(options.messages);
47
+
48
+ const used = systemPrompt + toolDefinitions + messages;
49
+ const free = contextWindow - used;
50
+ const usagePercent = (used / contextWindow) * 100;
51
+ const willCompact = usagePercent >= compactThreshold * 100;
52
+
53
+ return {
54
+ model: options.model,
55
+ contextWindow,
56
+ systemPrompt,
57
+ toolDefinitions,
58
+ messages,
59
+ used,
60
+ free,
61
+ usagePercent,
62
+ compactThreshold: compactThreshold * 100,
63
+ willCompact,
64
+ };
65
+ }
@@ -0,0 +1,276 @@
1
+ import { z } from "zod";
2
+
3
+ // --- Primitivos reutilizaveis ---
4
+
5
+ const MoneySchema = z.object({
6
+ value: z.number(),
7
+ currency: z.string().default("BRL"),
8
+ });
9
+
10
+ const SourceRefSchema = z.object({
11
+ name: z.string(),
12
+ url: z.string().url(),
13
+ favicon: z.string().url().optional(),
14
+ });
15
+
16
+ const ImageItemSchema = z.object({
17
+ url: z.string().url(),
18
+ alt: z.string().optional(),
19
+ caption: z.string().optional(),
20
+ });
21
+
22
+ const BadgeSchema = z.object({
23
+ label: z.string(),
24
+ variant: z.enum(["default", "success", "warning", "error", "info"]).default("default"),
25
+ });
26
+
27
+ // --- Display Tools ---
28
+
29
+ // 1. METRICAS E DADOS
30
+
31
+ export const DisplayMetricSchema = z.object({
32
+ label: z.string(),
33
+ value: z.union([z.string(), z.number()]),
34
+ unit: z.string().optional(),
35
+ trend: z.object({
36
+ direction: z.enum(["up", "down", "neutral"]),
37
+ value: z.string(),
38
+ }).optional(),
39
+ icon: z.string().optional(),
40
+ });
41
+
42
+ export const DisplayChartSchema = z.object({
43
+ type: z.enum(["bar", "line", "pie", "area", "donut"]),
44
+ title: z.string(),
45
+ data: z.array(z.object({
46
+ label: z.string(),
47
+ value: z.number(),
48
+ color: z.string().optional(),
49
+ })),
50
+ format: z.object({
51
+ prefix: z.string().optional(),
52
+ suffix: z.string().optional(),
53
+ locale: z.string().default("pt-BR"),
54
+ }).optional(),
55
+ });
56
+
57
+ export const DisplayTableSchema = z.object({
58
+ title: z.string().optional(),
59
+ columns: z.array(z.object({
60
+ key: z.string(),
61
+ label: z.string(),
62
+ type: z.enum(["text", "number", "money", "image", "link", "badge"]).default("text"),
63
+ align: z.enum(["left", "center", "right"]).default("left"),
64
+ })),
65
+ rows: z.array(z.record(z.unknown())),
66
+ sortable: z.boolean().default(false),
67
+ });
68
+
69
+ export const DisplayProgressSchema = z.object({
70
+ title: z.string().optional(),
71
+ steps: z.array(z.object({
72
+ label: z.string(),
73
+ status: z.enum(["completed", "current", "pending"]),
74
+ description: z.string().optional(),
75
+ })),
76
+ });
77
+
78
+ // 2. PRODUTOS E COMERCIO
79
+
80
+ export const DisplayProductSchema = z.object({
81
+ title: z.string(),
82
+ image: z.string().url().optional(),
83
+ price: MoneySchema.optional(),
84
+ originalPrice: MoneySchema.optional(),
85
+ rating: z.object({
86
+ score: z.number().min(0).max(5),
87
+ count: z.number(),
88
+ }).optional(),
89
+ source: SourceRefSchema.optional(),
90
+ badges: z.array(BadgeSchema).optional(),
91
+ url: z.string().url().optional(),
92
+ description: z.string().optional(),
93
+ });
94
+
95
+ export const DisplayComparisonSchema = z.object({
96
+ title: z.string().optional(),
97
+ items: z.array(DisplayProductSchema),
98
+ attributes: z.array(z.object({
99
+ key: z.string(),
100
+ label: z.string(),
101
+ })).optional(),
102
+ });
103
+
104
+ export const DisplayPriceSchema = z.object({
105
+ value: MoneySchema,
106
+ label: z.string(),
107
+ context: z.string().optional(),
108
+ source: SourceRefSchema.optional(),
109
+ badge: BadgeSchema.optional(),
110
+ });
111
+
112
+ // 3. MIDIA
113
+
114
+ export const DisplayImageSchema = z.object({
115
+ url: z.string().url(),
116
+ alt: z.string().optional(),
117
+ caption: z.string().optional(),
118
+ width: z.number().optional(),
119
+ height: z.number().optional(),
120
+ });
121
+
122
+ export const DisplayGallerySchema = z.object({
123
+ title: z.string().optional(),
124
+ images: z.array(ImageItemSchema),
125
+ layout: z.enum(["grid", "masonry"]).default("grid"),
126
+ columns: z.number().min(2).max(5).default(3),
127
+ });
128
+
129
+ export const DisplayCarouselSchema = z.object({
130
+ title: z.string().optional(),
131
+ items: z.array(z.object({
132
+ image: z.string().url().optional(),
133
+ title: z.string(),
134
+ subtitle: z.string().optional(),
135
+ price: MoneySchema.optional(),
136
+ url: z.string().url().optional(),
137
+ badges: z.array(BadgeSchema).optional(),
138
+ })),
139
+ });
140
+
141
+ // 4. REFERENCIAS E NAVEGACAO
142
+
143
+ export const DisplaySourcesSchema = z.object({
144
+ label: z.string().default("Fontes consultadas"),
145
+ sources: z.array(z.object({
146
+ title: z.string(),
147
+ url: z.string().url(),
148
+ favicon: z.string().url().optional(),
149
+ snippet: z.string().optional(),
150
+ })),
151
+ });
152
+
153
+ export const DisplayLinkSchema = z.object({
154
+ url: z.string().url(),
155
+ title: z.string(),
156
+ description: z.string().optional(),
157
+ image: z.string().url().optional(),
158
+ favicon: z.string().url().optional(),
159
+ domain: z.string().optional(),
160
+ });
161
+
162
+ export const DisplayMapSchema = z.object({
163
+ title: z.string().optional(),
164
+ pins: z.array(z.object({
165
+ lat: z.number(),
166
+ lng: z.number(),
167
+ label: z.string().optional(),
168
+ address: z.string().optional(),
169
+ })),
170
+ zoom: z.number().min(1).max(20).default(14),
171
+ });
172
+
173
+ // 5. DOCUMENTOS E ARQUIVOS
174
+
175
+ export const DisplayFileSchema = z.object({
176
+ name: z.string(),
177
+ type: z.string(),
178
+ size: z.number().optional(),
179
+ url: z.string().url().optional(),
180
+ preview: z.string().optional(),
181
+ });
182
+
183
+ export const DisplayCodeSchema = z.object({
184
+ language: z.string(),
185
+ code: z.string(),
186
+ title: z.string().optional(),
187
+ lineNumbers: z.boolean().default(true),
188
+ });
189
+
190
+ export const DisplaySpreadsheetSchema = z.object({
191
+ title: z.string().optional(),
192
+ headers: z.array(z.string()),
193
+ rows: z.array(z.array(z.union([z.string(), z.number(), z.null()]))),
194
+ format: z.object({
195
+ moneyColumns: z.array(z.number()).optional(),
196
+ percentColumns: z.array(z.number()).optional(),
197
+ }).optional(),
198
+ });
199
+
200
+ // 6. INTERATIVO
201
+
202
+ export const DisplayStepsSchema = z.object({
203
+ title: z.string().optional(),
204
+ steps: z.array(z.object({
205
+ title: z.string(),
206
+ description: z.string().optional(),
207
+ status: z.enum(["completed", "current", "pending"]).default("pending"),
208
+ })),
209
+ orientation: z.enum(["vertical", "horizontal"]).default("vertical"),
210
+ });
211
+
212
+ export const DisplayAlertSchema = z.object({
213
+ variant: z.enum(["info", "warning", "error", "success"]),
214
+ title: z.string().optional(),
215
+ message: z.string(),
216
+ icon: z.string().optional(),
217
+ });
218
+
219
+ export const DisplayChoicesSchema = z.object({
220
+ question: z.string().optional(),
221
+ choices: z.array(z.object({
222
+ id: z.string(),
223
+ label: z.string(),
224
+ description: z.string().optional(),
225
+ icon: z.string().optional(),
226
+ })),
227
+ layout: z.enum(["buttons", "cards", "list"]).default("buttons"),
228
+ });
229
+
230
+ // --- Registry (mapa nome → schema) ---
231
+
232
+ export const DisplayToolRegistry = {
233
+ display_metric: DisplayMetricSchema,
234
+ display_chart: DisplayChartSchema,
235
+ display_table: DisplayTableSchema,
236
+ display_progress: DisplayProgressSchema,
237
+ display_product: DisplayProductSchema,
238
+ display_comparison: DisplayComparisonSchema,
239
+ display_price: DisplayPriceSchema,
240
+ display_image: DisplayImageSchema,
241
+ display_gallery: DisplayGallerySchema,
242
+ display_carousel: DisplayCarouselSchema,
243
+ display_sources: DisplaySourcesSchema,
244
+ display_link: DisplayLinkSchema,
245
+ display_map: DisplayMapSchema,
246
+ display_file: DisplayFileSchema,
247
+ display_code: DisplayCodeSchema,
248
+ display_spreadsheet: DisplaySpreadsheetSchema,
249
+ display_steps: DisplayStepsSchema,
250
+ display_alert: DisplayAlertSchema,
251
+ display_choices: DisplayChoicesSchema,
252
+ } as const;
253
+
254
+ export type DisplayToolName = keyof typeof DisplayToolRegistry;
255
+
256
+ // --- Tipos inferidos ---
257
+
258
+ export type DisplayMetric = z.infer<typeof DisplayMetricSchema>;
259
+ export type DisplayChart = z.infer<typeof DisplayChartSchema>;
260
+ export type DisplayTable = z.infer<typeof DisplayTableSchema>;
261
+ export type DisplayProgress = z.infer<typeof DisplayProgressSchema>;
262
+ export type DisplayProduct = z.infer<typeof DisplayProductSchema>;
263
+ export type DisplayComparison = z.infer<typeof DisplayComparisonSchema>;
264
+ export type DisplayPrice = z.infer<typeof DisplayPriceSchema>;
265
+ export type DisplayImage = z.infer<typeof DisplayImageSchema>;
266
+ export type DisplayGallery = z.infer<typeof DisplayGallerySchema>;
267
+ export type DisplayCarousel = z.infer<typeof DisplayCarouselSchema>;
268
+ export type DisplaySources = z.infer<typeof DisplaySourcesSchema>;
269
+ export type DisplayLink = z.infer<typeof DisplayLinkSchema>;
270
+ export type DisplayMap = z.infer<typeof DisplayMapSchema>;
271
+ export type DisplayFile = z.infer<typeof DisplayFileSchema>;
272
+ export type DisplayCode = z.infer<typeof DisplayCodeSchema>;
273
+ export type DisplaySpreadsheet = z.infer<typeof DisplaySpreadsheetSchema>;
274
+ export type DisplaySteps = z.infer<typeof DisplayStepsSchema>;
275
+ export type DisplayAlert = z.infer<typeof DisplayAlertSchema>;
276
+ export type DisplayChoices = z.infer<typeof DisplayChoicesSchema>;