feat: cache init tool upserts (#217)
This commit is contained in:
@@ -8,7 +8,7 @@ import type { AgentProvenance } from "./agent/create";
|
||||
import { LETTA_CLOUD_API_URL } from "./auth/oauth";
|
||||
import { permissionMode } from "./permissions/mode";
|
||||
import { settingsManager } from "./settings-manager";
|
||||
import { loadTools, upsertToolsToServer } from "./tools/manager";
|
||||
import { loadTools, upsertToolsIfNeeded } from "./tools/manager";
|
||||
|
||||
function printHelp() {
|
||||
// Keep this plaintext (no colors) so output pipes cleanly
|
||||
@@ -431,7 +431,7 @@ async function main() {
|
||||
);
|
||||
await loadTools(modelForTools);
|
||||
const client = await getClient();
|
||||
await upsertToolsToServer(client);
|
||||
await upsertToolsIfNeeded(client, baseURL);
|
||||
|
||||
const { handleHeadlessCommand } = await import("./headless");
|
||||
await handleHeadlessCommand(process.argv, specifiedModel, skillsDirectory);
|
||||
@@ -573,7 +573,7 @@ async function main() {
|
||||
}
|
||||
|
||||
setLoadingState("upserting");
|
||||
await upsertToolsToServer(client);
|
||||
await upsertToolsIfNeeded(client, baseURL);
|
||||
|
||||
// Handle --link/--unlink after upserting tools
|
||||
if (shouldLink || shouldUnlink) {
|
||||
|
||||
@@ -19,6 +19,8 @@ export interface Settings {
|
||||
refreshToken?: string;
|
||||
tokenExpiresAt?: number; // Unix timestamp in milliseconds
|
||||
deviceId?: string;
|
||||
// Tool upsert cache: maps serverUrl -> hash of upserted tools
|
||||
toolUpsertHashes?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface ProjectSettings {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import type Letta from "@letta-ai/letta-client";
|
||||
import {
|
||||
AuthenticationError,
|
||||
@@ -586,6 +587,58 @@ export async function upsertToolsToServer(client: Letta): Promise<void> {
|
||||
await attemptUpsert();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a hash of all currently loaded tools for cache invalidation.
|
||||
* Includes tool names and schemas to detect any changes.
|
||||
*/
|
||||
export function computeToolsHash(): string {
|
||||
const toolData = Array.from(toolRegistry.entries())
|
||||
.sort(([a], [b]) => a.localeCompare(b)) // deterministic order
|
||||
.map(([name, tool]) => ({
|
||||
name,
|
||||
serverName: getServerToolName(name),
|
||||
schema: tool.schema,
|
||||
}));
|
||||
|
||||
return createHash("sha256")
|
||||
.update(JSON.stringify(toolData))
|
||||
.digest("hex")
|
||||
.slice(0, 16); // short hash is sufficient
|
||||
}
|
||||
|
||||
/**
|
||||
* Upserts tools only if the tool definitions have changed since last upsert.
|
||||
* Uses a hash of loaded tools cached in settings to skip redundant upserts.
|
||||
*
|
||||
* @param client - Letta client instance
|
||||
* @param serverUrl - The server URL (used as cache key)
|
||||
* @returns true if upsert was performed, false if skipped
|
||||
*/
|
||||
export async function upsertToolsIfNeeded(
|
||||
client: Letta,
|
||||
serverUrl: string,
|
||||
): Promise<boolean> {
|
||||
const currentHash = computeToolsHash();
|
||||
|
||||
const { settingsManager } = await import("../settings-manager");
|
||||
const cachedHashes = settingsManager.getSetting("toolUpsertHashes") || {};
|
||||
|
||||
if (cachedHashes[serverUrl] === currentHash) {
|
||||
// Tools unchanged, skip upsert
|
||||
return false;
|
||||
}
|
||||
|
||||
// Perform upsert
|
||||
await upsertToolsToServer(client);
|
||||
|
||||
// Save new hash
|
||||
settingsManager.updateSettings({
|
||||
toolUpsertHashes: { ...cachedHashes, [serverUrl]: currentHash },
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to clip tool return text to a reasonable display size
|
||||
* Used by UI components to truncate long responses for display
|
||||
|
||||
Reference in New Issue
Block a user