feat(Fase2): context injection pi→agy — environment, session_state, durable_memory, web_context
Best practice context engineering (Lost in the Middle, TACL 2024; tag XML Gemini): - Tool agy inietta automaticamente un blocco di contesto prima della richiesta utente (che resta SEMPRE per ultima, anti lost-in-the-middle). - <environment_snapshot>: cwd, OS, data/ora, git branch + file modificati. - <session_state>: ultimi messaggi utente pi compatti (non il transcript grezzo). - <durable_memory>: archivio memory.json auto-aggiornato oltre una soglia di turni. - <web_context>: ricerca web Strada A via Gemini API (googleSearch grounding) con webSearch=auto|on. - Token cap ~1500 (contextTokens), configurabile. - Nuovi parametri tool agy: injectContext (default true), webSearch (override). - Nuove chiavi config: contextInject, contextTokens, webSearch. - README aggiornato.
This commit is contained in:
@@ -71,6 +71,28 @@ Il comando `/agy:status` apre un overlay TUI con lo stato dell'estensione:
|
|||||||
|
|
||||||
Chiudi con `Enter` o `Esc`.
|
Chiudi con `Enter` o `Esc`.
|
||||||
|
|
||||||
|
### Context Injection (Fase 2) — `pi → agy`
|
||||||
|
|
||||||
|
Quando il tool `agy` (o altri con l'opzione `injectContext`) invia un prompt a
|
||||||
|
Gemini, l'estensione **inietta automaticamente** un blocco di contesto prima
|
||||||
|
della richiesta utente, seguendo le best practice di context engineering:
|
||||||
|
|
||||||
|
```
|
||||||
|
<environment_snapshot> ← cwd, OS, data/ora, git branch + file modificati
|
||||||
|
<session_state> ← ultimi messaggi utente pi (compatti, non il transcript)
|
||||||
|
<durable_memory> ← archivio fatti chiave (memory.json, auto-aggiornato)
|
||||||
|
<web_context> ← risultati ricerca web (Strada A, se pertinente)
|
||||||
|
--------------------------------
|
||||||
|
[ RICHIESTA UTENTE ] ← sempre per ultima (anti lost-in-the-middle)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Tag XML** e richiesta per ultima (evidenze “Lost in the Middle”, TACL 2024)
|
||||||
|
- **Token cap** default ~1500 (chiave `contextTokens`)
|
||||||
|
- **Memoria durevole** automatica: oltre una soglia di turni, i punti chiave
|
||||||
|
vengono condensati in `~/.config/agy-pi/memory.json` invece di rigirare tutto
|
||||||
|
- **Ricerca web** (Strada A): con `webSearch=auto|on` l'estensione cerca con
|
||||||
|
l'API Gemini (`googleSearch` grounding) e inietta i risultati nel `<web_context>`
|
||||||
|
|
||||||
| Chiave | Default | Descrizione |
|
| Chiave | Default | Descrizione |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `geminiApiKey` | — | Chiave API Google Gemini (STT/TTS) |
|
| `geminiApiKey` | — | Chiave API Google Gemini (STT/TTS) |
|
||||||
@@ -85,6 +107,9 @@ Chiudi con `Enter` o `Esc`.
|
|||||||
| `agyBin` | `agy` | Path del binario agy |
|
| `agyBin` | `agy` | Path del binario agy |
|
||||||
| `agyDefaultModel` | — | Modello predefinito per le chiamate agy |
|
| `agyDefaultModel` | — | Modello predefinito per le chiamate agy |
|
||||||
| `agyTimeoutMs` | `180000` | Timeout esecuzione agy (ms) |
|
| `agyTimeoutMs` | `180000` | Timeout esecuzione agy (ms) |
|
||||||
|
| `contextInject` | `true` | Iniezione contesto nel prompt agy |
|
||||||
|
| `contextTokens` | `1500` | Token cap per il contesto iniettato |
|
||||||
|
| `webSearch` | `auto` | Ricerca web Strada A: `auto` \| `on` \| `off` |
|
||||||
|
|
||||||
Le variabili d'ambiente (`GEMINI_API_KEY`, `AGY_STT_BACKEND`, ecc.) hanno
|
Le variabili d'ambiente (`GEMINI_API_KEY`, `AGY_STT_BACKEND`, ecc.) hanno
|
||||||
priorità sul file di config quando impostate.
|
priorità sul file di config quando impostate.
|
||||||
|
|||||||
+286
-3
@@ -49,6 +49,10 @@ interface AgyConfig {
|
|||||||
agyBin?: string;
|
agyBin?: string;
|
||||||
agyDefaultModel?: string;
|
agyDefaultModel?: string;
|
||||||
agyTimeoutMs?: number;
|
agyTimeoutMs?: number;
|
||||||
|
// Fase 2 — Context Injection
|
||||||
|
contextInject?: boolean; // on|off
|
||||||
|
contextTokens?: number; // token cap per il contesto iniettato
|
||||||
|
webSearch?: string; // auto|on|off — Strada A: pi cerca + inietta
|
||||||
}
|
}
|
||||||
|
|
||||||
const CONFIG_DEFAULTS: AgyConfig = {
|
const CONFIG_DEFAULTS: AgyConfig = {
|
||||||
@@ -60,6 +64,9 @@ const CONFIG_DEFAULTS: AgyConfig = {
|
|||||||
ttsNotify: true,
|
ttsNotify: true,
|
||||||
ttsModel: "gemini-2.5-flash-preview-tts",
|
ttsModel: "gemini-2.5-flash-preview-tts",
|
||||||
agyTimeoutMs: 180_000,
|
agyTimeoutMs: 180_000,
|
||||||
|
contextInject: true,
|
||||||
|
contextTokens: 1500,
|
||||||
|
webSearch: "auto",
|
||||||
};
|
};
|
||||||
|
|
||||||
function loadConfig(): AgyConfig {
|
function loadConfig(): AgyConfig {
|
||||||
@@ -89,8 +96,8 @@ function getConfig(key: keyof AgyConfig): string | undefined {
|
|||||||
|
|
||||||
function setConfig(key: keyof AgyConfig, value: string) {
|
function setConfig(key: keyof AgyConfig, value: string) {
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
const numKeys: (keyof AgyConfig)[] = ["sttMaxDuration", "agyTimeoutMs"];
|
const numKeys: (keyof AgyConfig)[] = ["sttMaxDuration", "agyTimeoutMs", "contextTokens"];
|
||||||
const boolKeys: (keyof AgyConfig)[] = ["ttsNotify"];
|
const boolKeys: (keyof AgyConfig)[] = ["ttsNotify", "contextInject"];
|
||||||
if (numKeys.includes(key)) {
|
if (numKeys.includes(key)) {
|
||||||
(cfg as any)[key] = Number(value);
|
(cfg as any)[key] = Number(value);
|
||||||
} else if (boolKeys.includes(key)) {
|
} else if (boolKeys.includes(key)) {
|
||||||
@@ -256,10 +263,270 @@ interface AgyExecOptions {
|
|||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
outputDir?: string;
|
outputDir?: string;
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
|
contextBlock?: string; // Fase 2: contesto iniettato prima della richiesta
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Fase 2 — Context Injection (pi → agy)
|
||||||
|
// Best practice: tag XML, richiesta per ultima (anti lost-in-the-middle),
|
||||||
|
// prefisso stabile/cache, token cap, memoria durevole automatica, niente segreti.
|
||||||
|
// =========================================================================
|
||||||
|
const MEMORY_FILE = path.join(CONFIG_DIR, "memory.json");
|
||||||
|
|
||||||
|
interface DurableMemory {
|
||||||
|
goal?: string;
|
||||||
|
decisions: string[];
|
||||||
|
conventions: string[];
|
||||||
|
openQuestions: string[];
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMemory(): DurableMemory {
|
||||||
|
try {
|
||||||
|
const raw = JSON.parse(fs.readFileSync(MEMORY_FILE, "utf8"));
|
||||||
|
return {
|
||||||
|
decisions: Array.isArray(raw.decisions) ? raw.decisions : [],
|
||||||
|
conventions: Array.isArray(raw.conventions) ? raw.conventions : [],
|
||||||
|
openQuestions: Array.isArray(raw.openQuestions) ? raw.openQuestions : [],
|
||||||
|
goal: typeof raw.goal === "string" ? raw.goal : undefined,
|
||||||
|
updatedAt: raw.updatedAt ?? "",
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return { decisions: [], conventions: [], openQuestions: [], updatedAt: "" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveMemory(mem: DurableMemory) {
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
||||||
|
fs.writeFileSync(
|
||||||
|
MEMORY_FILE,
|
||||||
|
JSON.stringify({ ...mem, updatedAt: new Date().toISOString() }, null, 2),
|
||||||
|
{ mode: 0o600 },
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
/* ignora */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-update: quando la conversazione pi cresce oltre la soglia, condensa i
|
||||||
|
// punti chiave recenti in durable facts invece di rigirare tutto il transcript.
|
||||||
|
function autoUpdateMemory(ctx: any, force = false) {
|
||||||
|
try {
|
||||||
|
const mem = loadMemory();
|
||||||
|
const entries = ctx?.sessionManager?.getEntries?.() ?? [];
|
||||||
|
if (force || entries.length > 24) {
|
||||||
|
const userTexts: string[] = [];
|
||||||
|
for (const e of entries) {
|
||||||
|
if (e.type === "message" && e.message?.role === "user") {
|
||||||
|
const c = e.message.content;
|
||||||
|
const text =
|
||||||
|
typeof c === "string"
|
||||||
|
? c
|
||||||
|
: Array.isArray(c)
|
||||||
|
? c.map((b: any) => b.text ?? "").join(" ")
|
||||||
|
: "";
|
||||||
|
if (text && text.length > 20) userTexts.push(text.slice(0, 160));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mem.goal && userTexts.length) mem.goal = userTexts[0];
|
||||||
|
// ultima decisione/azione chiave (ultimo user prompt significativo)
|
||||||
|
if (userTexts.length > 1) {
|
||||||
|
const last = userTexts[userTexts.length - 1];
|
||||||
|
const lastShort = last.length > 100 ? last.slice(0, 100) : last;
|
||||||
|
if (!mem.decisions.includes(lastShort)) {
|
||||||
|
mem.decisions.push(lastShort);
|
||||||
|
if (mem.decisions.length > 8) mem.decisions.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
saveMemory(mem);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* ignora */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGitInfo(cwd: string): { branch: string; dirty: boolean; modified: string[] } {
|
||||||
|
try {
|
||||||
|
const branch =
|
||||||
|
execFileSync("git", ["-C", cwd, "rev-parse", "--abbrev-ref", "HEAD"], {
|
||||||
|
encoding: "utf8",
|
||||||
|
})
|
||||||
|
.trim() || "(nessun branch)";
|
||||||
|
const porcelain = execFileSync("git", ["-C", cwd, "status", "--porcelain"], {
|
||||||
|
encoding: "utf8",
|
||||||
|
})
|
||||||
|
.split("\n")
|
||||||
|
.filter(Boolean);
|
||||||
|
return {
|
||||||
|
branch,
|
||||||
|
dirty: porcelain.length > 0,
|
||||||
|
modified: porcelain.slice(0, 8).map((l) => l.slice(0, 70)),
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return { branch: "(no git)", dirty: false, modified: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estrae lo stato recente della conversazione pi (ultimi N messaggi utente)
|
||||||
|
// e lo compatta — NON il transcript grezzo (lost-in-the-middle, token bloat).
|
||||||
|
function extractSessionState(ctx: any): string {
|
||||||
|
try {
|
||||||
|
const entries = ctx?.sessionManager?.getEntries?.() ?? [];
|
||||||
|
const userTexts: string[] = [];
|
||||||
|
for (let i = entries.length - 1; i >= 0 && userTexts.length < 3; i--) {
|
||||||
|
const e = entries[i];
|
||||||
|
if (e.type === "message" && e.message?.role === "user") {
|
||||||
|
const c = e.message.content;
|
||||||
|
const text =
|
||||||
|
typeof c === "string"
|
||||||
|
? c
|
||||||
|
: Array.isArray(c)
|
||||||
|
? c.map((b: any) => b.text ?? "").join(" ")
|
||||||
|
: "";
|
||||||
|
if (text) userTexts.push(text.replace(/\s+/g, " ").trim().slice(0, 200));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return userTexts.reverse().join("\n");
|
||||||
|
} catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cerca sul web tramite Gemini API (googleSearch grounding) e restituisce i
|
||||||
|
// risultati iniettabili. È la Strada A: l'estensione cerca, poi inietta.
|
||||||
|
async function webSearchGemini(query: string, signal?: AbortSignal): Promise<string> {
|
||||||
|
let key = getConfig("geminiApiKey") ?? process.env.GEMINI_API_KEY ?? "";
|
||||||
|
if (!key) {
|
||||||
|
try {
|
||||||
|
key = fs.readFileSync(path.join(AGY_CHAT_DIR, "gemini-key"), "utf8").trim();
|
||||||
|
} catch {
|
||||||
|
/* ignora */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!key) return "";
|
||||||
|
const model = process.env.AGY_GEMINI_MODEL ?? "gemini-3.5-flash";
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${encodeURIComponent(key)}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
signal,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
contents: [{ role: "user", parts: [{ text: query }] }],
|
||||||
|
tools: [{ googleSearch: {} }],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!res.ok) return "";
|
||||||
|
const data: any = await res.json();
|
||||||
|
const text = data?.candidates?.[0]?.content?.parts?.map((p: any) => p.text ?? "").join(" ") ?? "";
|
||||||
|
return text.trim().slice(0, 600);
|
||||||
|
} catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function needsWebSearch(prompt: string): boolean {
|
||||||
|
const mode = getConfig("webSearch") ?? "auto";
|
||||||
|
if (mode === "off") return false;
|
||||||
|
if (mode === "on") return true;
|
||||||
|
// auto: domande informative/tecniche che beneficiano di dati aggiornati
|
||||||
|
return /cerca|ricerca|latest|ultim|aggiorna|versione|documentaz|news|novit|differenza|confronta|quanto |come funzion|quando|perché|cos'è|chi è|release|prezzo|alternativ/i.test(
|
||||||
|
prompt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const estTokens = (s: string) => Math.ceil(s.length / 4);
|
||||||
|
function truncToTokens(s: string, limit: number): string {
|
||||||
|
if (estTokens(s) <= limit) return s;
|
||||||
|
return s.slice(0, limit * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemblea del blocco di contesto con tag XML e token cap (~1500).
|
||||||
|
// La richiesta utente va SEMPRE dopo (anti lost-in-the-middle).
|
||||||
|
async function buildContextBlock(
|
||||||
|
ctx: any,
|
||||||
|
prompt: string,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
): Promise<string> {
|
||||||
|
if (getConfig("contextInject") === "off") return "";
|
||||||
|
const budget = Number(getConfig("contextTokens") ?? 1500) || 1500;
|
||||||
|
const cwd = ctx?.sessionManager?.getCwd?.() ?? process.cwd();
|
||||||
|
const git = getGitInfo(cwd);
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
const os = `${process.platform} ${process.arch}`;
|
||||||
|
|
||||||
|
const parts: { label: string; xml: string; body: string }[] = [];
|
||||||
|
|
||||||
|
// --- <environment_snapshot> ---
|
||||||
|
const envBody = [
|
||||||
|
`cwd: ${cwd}`,
|
||||||
|
`os: ${os}`,
|
||||||
|
`time: ${now}`,
|
||||||
|
`git_branch: ${git.branch}${git.dirty ? " (dirty)" : ""}`,
|
||||||
|
git.modified.length ? `modified: ${git.modified.join(" | ")}` : "",
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n");
|
||||||
|
parts.push({ label: "env", xml: "environment_snapshot", body: envBody });
|
||||||
|
|
||||||
|
// --- <session_state> ---
|
||||||
|
const sessionState = extractSessionState(ctx);
|
||||||
|
if (sessionState) {
|
||||||
|
parts.push({ label: "session", xml: "session_state", body: sessionState });
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- <durable_memory> ---
|
||||||
|
const mem = loadMemory();
|
||||||
|
const memLines = [
|
||||||
|
mem.goal ? `goal: ${mem.goal.slice(0, 120)}` : "",
|
||||||
|
mem.decisions.length ? `decisions: ${mem.decisions.join(" | ")}` : "",
|
||||||
|
mem.conventions.length ? `conventions: ${mem.conventions.join(" | ")}` : "",
|
||||||
|
mem.openQuestions.length ? `open_questions: ${mem.openQuestions.join(" | ")}` : "",
|
||||||
|
].filter(Boolean);
|
||||||
|
if (memLines.length) {
|
||||||
|
parts.push({ label: "memory", xml: "durable_memory", body: memLines.join("\n") });
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- <web_context> (Strada A) ---
|
||||||
|
if (needsWebSearch(prompt)) {
|
||||||
|
const web = await webSearchGemini(prompt.slice(0, 300), signal);
|
||||||
|
if (web) parts.push({ label: "web", xml: "web_context", body: web });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token cap: budget totale ~1500. Assegna in modo proporzionale.
|
||||||
|
const blocks: string[] = [];
|
||||||
|
let used = 0;
|
||||||
|
const envBudget = Math.min(300, budget);
|
||||||
|
const sessionBudget = Math.min(500, budget);
|
||||||
|
const memBudget = Math.min(400, budget);
|
||||||
|
const webBudget = Math.min(300, budget);
|
||||||
|
const budgets: Record<string, number> = {
|
||||||
|
env: envBudget,
|
||||||
|
session: sessionBudget,
|
||||||
|
memory: memBudget,
|
||||||
|
web: webBudget,
|
||||||
|
};
|
||||||
|
for (const p of parts) {
|
||||||
|
const cap = budgets[p.label] ?? 200;
|
||||||
|
if (used >= budget) break;
|
||||||
|
const body = truncToTokens(p.body, cap);
|
||||||
|
if (!body) continue;
|
||||||
|
blocks.push(`<${p.xml}>
|
||||||
|
${body}
|
||||||
|
</${p.xml}>`);
|
||||||
|
used += estTokens(body) + 6;
|
||||||
|
}
|
||||||
|
if (!blocks.length) return "";
|
||||||
|
return `[CONTEXT_AGENTE] Contesto ambiente e stato corrente per la risposta. Usa solo se pertinente; la richiesta dell'utente è sotto.
|
||||||
|
${blocks.join("\n\n")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function executeAgy(opts: AgyExecOptions) {
|
async function executeAgy(opts: AgyExecOptions) {
|
||||||
const args: string[] = ["-p", opts.prompt];
|
const finalPrompt = opts.contextBlock ? `${opts.contextBlock}\n\n${opts.prompt}` : opts.prompt;
|
||||||
|
const args: string[] = ["-p", finalPrompt];
|
||||||
|
|
||||||
// add-dir per i file
|
// add-dir per i file
|
||||||
const dirs = new Set<string>();
|
const dirs = new Set<string>();
|
||||||
@@ -845,10 +1112,22 @@ export default function agyExtension(pi: ExtensionAPI) {
|
|||||||
addDir: Type.Optional(Type.String({ description: "Cartella da aggiungere al workspace agy." })),
|
addDir: Type.Optional(Type.String({ description: "Cartella da aggiungere al workspace agy." })),
|
||||||
filePath: Type.Optional(Type.String({ description: "File (immagine/audio/video) da analizzare." })),
|
filePath: Type.Optional(Type.String({ description: "File (immagine/audio/video) da analizzare." })),
|
||||||
yolo: Type.Optional(Type.Boolean({ description: "true per --dangerously-skip-permissions." })),
|
yolo: Type.Optional(Type.Boolean({ description: "true per --dangerously-skip-permissions." })),
|
||||||
|
injectContext: Type.Optional(
|
||||||
|
Type.Boolean({ description: "Inietta contesto ambiente/sessione/web nel prompt (default true)." }),
|
||||||
|
),
|
||||||
|
webSearch: Type.Optional(
|
||||||
|
Type.Boolean({ description: "Forza/non forzare la ricerca web (override di webSearch config)." }),
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
||||||
const p = params as any;
|
const p = params as any;
|
||||||
toolUpdate(onUpdate, `agy: ${p.mode === "image" ? "generazione immagine" : "elaborazione"}...`);
|
toolUpdate(onUpdate, `agy: ${p.mode === "image" ? "generazione immagine" : "elaborazione"}...`);
|
||||||
|
// Fase 2: costruzione contesto iniettato (ambiente + sessione + memoria + web)
|
||||||
|
const wantInject = p.injectContext ?? true;
|
||||||
|
const prevWs = getConfig("webSearch");
|
||||||
|
if (typeof p.webSearch === "boolean") setConfig("webSearch", p.webSearch ? "on" : "off");
|
||||||
|
const contextBlock = wantInject ? await buildContextBlock(ctx, p.prompt, signal) : "";
|
||||||
|
if (typeof p.webSearch === "boolean" && prevWs) setConfig("webSearch", prevWs);
|
||||||
const res = await executeAgy({
|
const res = await executeAgy({
|
||||||
prompt: p.prompt,
|
prompt: p.prompt,
|
||||||
mode: p.mode,
|
mode: p.mode,
|
||||||
@@ -859,6 +1138,7 @@ export default function agyExtension(pi: ExtensionAPI) {
|
|||||||
filePaths: p.filePath ? [p.filePath] : [],
|
filePaths: p.filePath ? [p.filePath] : [],
|
||||||
yolo: p.yolo,
|
yolo: p.yolo,
|
||||||
signal,
|
signal,
|
||||||
|
contextBlock,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: res.text }],
|
content: [{ type: "text", text: res.text }],
|
||||||
@@ -1591,6 +1871,9 @@ export default function agyExtension(pi: ExtensionAPI) {
|
|||||||
{ key: "agyBin", desc: "Path del binario agy" },
|
{ key: "agyBin", desc: "Path del binario agy" },
|
||||||
{ key: "agyDefaultModel", desc: "Modello predefinito per le chiamate agy" },
|
{ key: "agyDefaultModel", desc: "Modello predefinito per le chiamate agy" },
|
||||||
{ key: "agyTimeoutMs", desc: "Timeout esecuzione agy (ms)" },
|
{ key: "agyTimeoutMs", desc: "Timeout esecuzione agy (ms)" },
|
||||||
|
{ key: "contextInject", desc: "Iniezione contesto nel prompt agy: true | false" },
|
||||||
|
{ key: "contextTokens", desc: "Token cap per il contesto iniettato (default 1500)" },
|
||||||
|
{ key: "webSearch", desc: "Ricerca web Strada A: auto | on | off" },
|
||||||
];
|
];
|
||||||
|
|
||||||
pi.registerCommand("agy:config", {
|
pi.registerCommand("agy:config", {
|
||||||
|
|||||||
Reference in New Issue
Block a user