@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.
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +463 -0
- package/dist/context/compaction.d.ts +27 -0
- package/dist/context/compaction.js +219 -0
- package/dist/context/models.d.ts +6 -0
- package/dist/context/models.js +41 -0
- package/dist/context/tokenizer.d.ts +5 -0
- package/dist/context/tokenizer.js +11 -0
- package/dist/context/usage.d.ts +11 -0
- package/dist/context/usage.js +49 -0
- package/dist/display-schemas.d.ts +1865 -0
- package/dist/display-schemas.js +219 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +28 -0
- package/dist/middleware/logging.d.ts +2 -0
- package/dist/middleware/logging.js +32 -0
- package/dist/prompts/assembly.d.ts +13 -0
- package/dist/prompts/assembly.js +229 -0
- package/dist/providers.d.ts +19 -0
- package/dist/providers.js +44 -0
- package/dist/proxy.d.ts +2 -0
- package/dist/proxy.js +103 -0
- package/dist/schemas.d.ts +228 -0
- package/dist/schemas.js +51 -0
- package/dist/session.d.ts +7 -0
- package/dist/session.js +102 -0
- package/dist/structured.d.ts +18 -0
- package/dist/structured.js +38 -0
- package/dist/tool-repair.d.ts +21 -0
- package/dist/tool-repair.js +72 -0
- package/dist/tools/api-spec.d.ts +4 -0
- package/dist/tools/api-spec.js +123 -0
- package/dist/tools/apply-patch.d.ts +484 -0
- package/dist/tools/apply-patch.js +157 -0
- package/dist/tools/ask-user.d.ts +14 -0
- package/dist/tools/ask-user.js +27 -0
- package/dist/tools/bash.d.ts +550 -0
- package/dist/tools/bash.js +43 -0
- package/dist/tools/batch.d.ts +13 -0
- package/dist/tools/batch.js +84 -0
- package/dist/tools/brave-search.d.ts +6 -0
- package/dist/tools/brave-search.js +19 -0
- package/dist/tools/code-search.d.ts +20 -0
- package/dist/tools/code-search.js +42 -0
- package/dist/tools/diagnostics.d.ts +4 -0
- package/dist/tools/diagnostics.js +69 -0
- package/dist/tools/display.d.ts +483 -0
- package/dist/tools/display.js +77 -0
- package/dist/tools/edit.d.ts +682 -0
- package/dist/tools/edit.js +47 -0
- package/dist/tools/glob.d.ts +4 -0
- package/dist/tools/glob.js +42 -0
- package/dist/tools/grep.d.ts +6 -0
- package/dist/tools/grep.js +69 -0
- package/dist/tools/http-request.d.ts +7 -0
- package/dist/tools/http-request.js +98 -0
- package/dist/tools/index.d.ts +1611 -0
- package/dist/tools/index.js +46 -0
- package/dist/tools/job-tools.d.ts +24 -0
- package/dist/tools/job-tools.js +67 -0
- package/dist/tools/list-dir.d.ts +5 -0
- package/dist/tools/list-dir.js +79 -0
- package/dist/tools/multi-edit.d.ts +814 -0
- package/dist/tools/multi-edit.js +57 -0
- package/dist/tools/read.d.ts +5 -0
- package/dist/tools/read.js +33 -0
- package/dist/tools/task.d.ts +21 -0
- package/dist/tools/task.js +51 -0
- package/dist/tools/todo.d.ts +14 -0
- package/dist/tools/todo.js +60 -0
- package/dist/tools/web-fetch.d.ts +4 -0
- package/dist/tools/web-fetch.js +126 -0
- package/dist/tools/web-search.d.ts +22 -0
- package/dist/tools/web-search.js +48 -0
- package/dist/tools/write.d.ts +550 -0
- package/dist/tools/write.js +30 -0
- package/dist/types.d.ts +201 -0
- package/dist/types.js +1 -0
- package/package.json +43 -0
- package/src/agent.ts +520 -0
- package/src/context/compaction.ts +265 -0
- package/src/context/models.ts +42 -0
- package/src/context/tokenizer.ts +12 -0
- package/src/context/usage.ts +65 -0
- package/src/display-schemas.ts +276 -0
- package/src/index.ts +43 -0
- package/src/middleware/logging.ts +37 -0
- package/src/prompts/assembly.ts +263 -0
- package/src/prompts/identity.md +10 -0
- package/src/prompts/patterns.md +7 -0
- package/src/prompts/safety.md +7 -0
- package/src/prompts/tool-guide.md +9 -0
- package/src/prompts/tools/bash.md +7 -0
- package/src/prompts/tools/edit.md +7 -0
- package/src/prompts/tools/glob.md +7 -0
- package/src/prompts/tools/grep.md +7 -0
- package/src/prompts/tools/read.md +7 -0
- package/src/prompts/tools/write.md +7 -0
- package/src/providers.ts +58 -0
- package/src/proxy.ts +101 -0
- package/src/schemas.ts +58 -0
- package/src/session.ts +110 -0
- package/src/structured.ts +65 -0
- package/src/tool-repair.ts +92 -0
- package/src/tools/api-spec.ts +158 -0
- package/src/tools/apply-patch.ts +188 -0
- package/src/tools/ask-user.ts +40 -0
- package/src/tools/bash.ts +51 -0
- package/src/tools/batch.ts +103 -0
- package/src/tools/brave-search.ts +24 -0
- package/src/tools/code-search.ts +69 -0
- package/src/tools/diagnostics.ts +93 -0
- package/src/tools/display.ts +105 -0
- package/src/tools/edit.ts +55 -0
- package/src/tools/glob.ts +46 -0
- package/src/tools/grep.ts +68 -0
- package/src/tools/http-request.ts +103 -0
- package/src/tools/index.ts +48 -0
- package/src/tools/job-tools.ts +84 -0
- package/src/tools/list-dir.ts +102 -0
- package/src/tools/multi-edit.ts +65 -0
- package/src/tools/read.ts +40 -0
- package/src/tools/task.ts +71 -0
- package/src/tools/todo.ts +82 -0
- package/src/tools/web-fetch.ts +155 -0
- package/src/tools/web-search.ts +75 -0
- package/src/tools/write.ts +34 -0
- package/src/types.ts +145 -0
- 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>;
|