refactor(integrations): shared agent plugin runtimes and native adapters (#7203)
This commit is contained in:
@@ -1,43 +1,7 @@
|
||||
interface MemoryLike {
|
||||
id: string;
|
||||
memory?: string;
|
||||
categories?: string[];
|
||||
createdAt?: Date | string;
|
||||
}
|
||||
|
||||
export function formatAge(date: Date | string): string {
|
||||
const d = typeof date === "string" ? new Date(date) : date;
|
||||
const ms = Date.now() - d.getTime();
|
||||
const minutes = Math.floor(ms / 60_000);
|
||||
if (minutes < 60) return `${minutes}m ago`;
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours < 24) return `${hours}h ago`;
|
||||
const days = Math.floor(hours / 24);
|
||||
return `${days}d ago`;
|
||||
}
|
||||
|
||||
export function formatMemoryCompact(mem: MemoryLike): string {
|
||||
const cat = mem.categories?.[0] ?? "uncategorized";
|
||||
const age = mem.createdAt ? ` (${formatAge(mem.createdAt)})` : "";
|
||||
return `[${cat}] ${mem.memory ?? "(empty)"}${age} [mem0:${mem.id}]`;
|
||||
}
|
||||
|
||||
export function formatMemoryList(memories: MemoryLike[]): string {
|
||||
if (memories.length === 0) return "No memories found.";
|
||||
return memories
|
||||
.map((m, i) => `${i + 1}. ${formatMemoryCompact(m)}`)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
export function groupByCategory(
|
||||
memories: MemoryLike[],
|
||||
): Map<string, MemoryLike[]> {
|
||||
const groups = new Map<string, MemoryLike[]>();
|
||||
for (const m of memories) {
|
||||
const cat = m.categories?.[0] ?? "uncategorized";
|
||||
const list = groups.get(cat) ?? [];
|
||||
list.push(m);
|
||||
groups.set(cat, list);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
export {
|
||||
formatAge,
|
||||
formatMemoryCompact,
|
||||
formatMemoryList,
|
||||
groupByCategory,
|
||||
} from "../../../agent-plugin-core/typescript/src/formatting.ts";
|
||||
export type { MemoryLike } from "../../../agent-plugin-core/typescript/src/formatting.ts";
|
||||
|
||||
@@ -63,8 +63,8 @@ describe("resolveSearchFilters", () => {
|
||||
expect(resolveSearchFilters("session", ctx)).toEqual({ user_id: "u1", app_id: "a1", run_id: "r1" });
|
||||
});
|
||||
|
||||
it("uses wildcard app_id for global scope", () => {
|
||||
expect(resolveSearchFilters("global", ctx)).toEqual({ user_id: "u1", app_id: "*" });
|
||||
it("limits global scope to the configured user", () => {
|
||||
expect(resolveSearchFilters("global", ctx)).toEqual({ user_id: "u1" });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@ import * as path from "node:path";
|
||||
import * as crypto from "node:crypto";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import type { Scope, ScopeContext } from "../types.ts";
|
||||
import {
|
||||
scopeAddParams,
|
||||
scopeSearchFilters,
|
||||
} from "../../../agent-plugin-core/typescript/src/scoping.ts";
|
||||
|
||||
export function detectAppId(cwd: string): string {
|
||||
try {
|
||||
@@ -26,26 +30,12 @@ export function resolveSearchFilters(
|
||||
scope: Scope,
|
||||
ctx: ScopeContext,
|
||||
): Record<string, string> {
|
||||
switch (scope) {
|
||||
case "project":
|
||||
return { user_id: ctx.userId, app_id: ctx.appId };
|
||||
case "session":
|
||||
return { user_id: ctx.userId, app_id: ctx.appId, run_id: ctx.runId };
|
||||
case "global":
|
||||
return { user_id: ctx.userId, app_id: "*" };
|
||||
}
|
||||
return scopeSearchFilters(scope, ctx);
|
||||
}
|
||||
|
||||
export function resolveAddParams(
|
||||
scope: Scope,
|
||||
ctx: ScopeContext,
|
||||
): Record<string, string> {
|
||||
switch (scope) {
|
||||
case "project":
|
||||
return { userId: ctx.userId, appId: ctx.appId };
|
||||
case "session":
|
||||
return { userId: ctx.userId, appId: ctx.appId, runId: ctx.runId };
|
||||
case "global":
|
||||
return { userId: ctx.userId };
|
||||
}
|
||||
return scopeAddParams(scope, ctx);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { resolveToolScope } from "../../../agent-plugin-core/typescript/src/scoping.ts";
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
import { Type } from "typebox";
|
||||
import { StringEnum } from "@earendil-works/pi-ai";
|
||||
@@ -17,6 +18,10 @@ interface MemoryResult {
|
||||
const MAX_OUTPUT_LINES = 200;
|
||||
const MAX_OUTPUT_BYTES = 50_000;
|
||||
|
||||
function normalizeMemoryId(id: string): string {
|
||||
return id.replace(/^\[?mem0:([0-9a-f-]{36})\]?$/i, "$1");
|
||||
}
|
||||
|
||||
function truncateOutput(text: string): string {
|
||||
const lines = text.split("\n");
|
||||
if (lines.length <= MAX_OUTPUT_LINES && text.length <= MAX_OUTPUT_BYTES) {
|
||||
@@ -50,7 +55,7 @@ export function buildToolExecute(
|
||||
defaultScope: Scope,
|
||||
) {
|
||||
return async (params: ToolParams, signal?: AbortSignal) => {
|
||||
const scope = params.scope ?? defaultScope;
|
||||
const scope = resolveToolScope(params.scope, defaultScope);
|
||||
|
||||
switch (params.action) {
|
||||
case "search": {
|
||||
@@ -96,18 +101,19 @@ export function buildToolExecute(
|
||||
if (signal?.aborted) throw new Error("Cancelled");
|
||||
if (!params.memory_id) throw new Error("memory_id is required for update");
|
||||
if (!params.content) throw new Error("content is required for update");
|
||||
const updateResult = await mem0.update(params.memory_id, { text: params.content });
|
||||
const memoryId = normalizeMemoryId(params.memory_id);
|
||||
const updateResult = await mem0.update(memoryId, { text: params.content });
|
||||
const res = updateResult as MemoryResult;
|
||||
return {
|
||||
content: [{ type: "text" as const, text: res.status ?? "Memory updated." }],
|
||||
details: { memoryId: params.memory_id },
|
||||
details: { memoryId },
|
||||
};
|
||||
}
|
||||
|
||||
case "delete": {
|
||||
if (signal?.aborted) throw new Error("Cancelled");
|
||||
if (!params.memory_id) throw new Error("memory_id is required for delete");
|
||||
const result = await mem0.delete(params.memory_id);
|
||||
const result = await mem0.delete(normalizeMemoryId(params.memory_id));
|
||||
return {
|
||||
content: [{ type: "text" as const, text: result.message ?? "Memory deleted." }],
|
||||
details: {},
|
||||
@@ -145,7 +151,7 @@ export function registerMemoryTool(
|
||||
'For multi-part or comparative questions, run several searches with different phrasings and combine the results before answering -- one search is rarely enough',
|
||||
'Use mem0_memory with action "add" to save important facts, preferences, goals, decisions, or lessons the user shares',
|
||||
'Use mem0_memory with action "update" to modify an existing memory — requires memory_id and content. Preserves the memory ID',
|
||||
"Always use the default project scope unless the user EXPLICITLY asks to search across all projects — only then use scope \"global\"",
|
||||
"Always use the default project scope unless the user EXPLICITLY asks to search across all projects — only after the user selects /mem0-scope global use scope \"global\"",
|
||||
"Do NOT pass scope at all for normal queries — omitting it uses the project default automatically",
|
||||
],
|
||||
parameters: Type.Object({
|
||||
|
||||
Reference in New Issue
Block a user