238 lines
7.3 KiB
TypeScript
238 lines
7.3 KiB
TypeScript
// src/agent/modify.ts
|
|
// Utilities for modifying agent configuration
|
|
|
|
import type {
|
|
AgentState,
|
|
AnthropicModelSettings,
|
|
GoogleAIModelSettings,
|
|
OpenAIModelSettings,
|
|
} from "@letta-ai/letta-client/resources/agents/agents";
|
|
import type { LlmConfig } from "@letta-ai/letta-client/resources/models/models";
|
|
import { ANTHROPIC_PROVIDER_NAME } from "../providers/anthropic-provider";
|
|
import { getClient } from "./client";
|
|
|
|
type ModelSettings =
|
|
| OpenAIModelSettings
|
|
| AnthropicModelSettings
|
|
| GoogleAIModelSettings
|
|
| Record<string, unknown>;
|
|
|
|
/**
|
|
* Builds model_settings from updateArgs based on provider type.
|
|
* Always ensures parallel_tool_calls is enabled.
|
|
*/
|
|
function buildModelSettings(
|
|
modelHandle: string,
|
|
updateArgs?: Record<string, unknown>,
|
|
): ModelSettings {
|
|
const isOpenAI = modelHandle.startsWith("openai/");
|
|
// Include our custom Anthropic OAuth provider (claude-pro-max)
|
|
const isAnthropic =
|
|
modelHandle.startsWith("anthropic/") ||
|
|
modelHandle.startsWith(`${ANTHROPIC_PROVIDER_NAME}/`);
|
|
const isGoogleAI = modelHandle.startsWith("google_ai/");
|
|
const isGoogleVertex = modelHandle.startsWith("google_vertex/");
|
|
const isOpenRouter = modelHandle.startsWith("openrouter/");
|
|
|
|
let settings: ModelSettings;
|
|
|
|
if (isOpenAI || isOpenRouter) {
|
|
const openaiSettings: OpenAIModelSettings = {
|
|
provider_type: "openai",
|
|
parallel_tool_calls: true,
|
|
};
|
|
if (updateArgs?.reasoning_effort) {
|
|
openaiSettings.reasoning = {
|
|
reasoning_effort: updateArgs.reasoning_effort as
|
|
| "none"
|
|
| "minimal"
|
|
| "low"
|
|
| "medium"
|
|
| "high",
|
|
};
|
|
}
|
|
settings = openaiSettings;
|
|
} else if (isAnthropic) {
|
|
const anthropicSettings: AnthropicModelSettings = {
|
|
provider_type: "anthropic",
|
|
parallel_tool_calls: true,
|
|
};
|
|
// Build thinking config if either enable_reasoner or max_reasoning_tokens is specified
|
|
if (
|
|
updateArgs?.enable_reasoner !== undefined ||
|
|
typeof updateArgs?.max_reasoning_tokens === "number"
|
|
) {
|
|
anthropicSettings.thinking = {
|
|
type: updateArgs?.enable_reasoner === false ? "disabled" : "enabled",
|
|
...(typeof updateArgs?.max_reasoning_tokens === "number" && {
|
|
budget_tokens: updateArgs.max_reasoning_tokens,
|
|
}),
|
|
};
|
|
}
|
|
settings = anthropicSettings;
|
|
} else if (isGoogleAI) {
|
|
const googleSettings: GoogleAIModelSettings & { temperature?: number } = {
|
|
provider_type: "google_ai",
|
|
parallel_tool_calls: true,
|
|
};
|
|
if (updateArgs?.thinking_budget !== undefined) {
|
|
googleSettings.thinking_config = {
|
|
thinking_budget: updateArgs.thinking_budget as number,
|
|
};
|
|
}
|
|
if (typeof updateArgs?.temperature === "number") {
|
|
googleSettings.temperature = updateArgs.temperature as number;
|
|
}
|
|
settings = googleSettings;
|
|
} else if (isGoogleVertex) {
|
|
// Vertex AI uses the same Google provider on the backend; only the handle differs.
|
|
const googleVertexSettings: Record<string, unknown> = {
|
|
provider_type: "google_vertex",
|
|
parallel_tool_calls: true,
|
|
};
|
|
if (updateArgs?.thinking_budget !== undefined) {
|
|
(googleVertexSettings as Record<string, unknown>).thinking_config = {
|
|
thinking_budget: updateArgs.thinking_budget as number,
|
|
};
|
|
}
|
|
if (typeof updateArgs?.temperature === "number") {
|
|
(googleVertexSettings as Record<string, unknown>).temperature =
|
|
updateArgs.temperature as number;
|
|
}
|
|
settings = googleVertexSettings;
|
|
} else {
|
|
// For BYOK/unknown providers, return generic settings with parallel_tool_calls
|
|
settings = {};
|
|
}
|
|
|
|
// Apply max_output_tokens for all providers if specified
|
|
if (typeof updateArgs?.max_output_tokens === "number") {
|
|
(settings as Record<string, unknown>).max_output_tokens =
|
|
updateArgs.max_output_tokens;
|
|
}
|
|
|
|
return settings;
|
|
}
|
|
|
|
/**
|
|
* Updates an agent's model and model settings.
|
|
*
|
|
* Uses the new model_settings field instead of deprecated llm_config.
|
|
*
|
|
* @param agentId - The agent ID
|
|
* @param modelHandle - The model handle (e.g., "anthropic/claude-sonnet-4-5-20250929")
|
|
* @param updateArgs - Additional config args (context_window, reasoning_effort, enable_reasoner, etc.)
|
|
* @param preserveParallelToolCalls - If true, preserves the parallel_tool_calls setting when updating the model
|
|
* @returns The updated LLM configuration from the server
|
|
*/
|
|
export async function updateAgentLLMConfig(
|
|
agentId: string,
|
|
modelHandle: string,
|
|
updateArgs?: Record<string, unknown>,
|
|
): Promise<LlmConfig> {
|
|
const client = await getClient();
|
|
|
|
const modelSettings = buildModelSettings(modelHandle, updateArgs);
|
|
const contextWindow = updateArgs?.context_window as number | undefined;
|
|
const hasModelSettings = Object.keys(modelSettings).length > 0;
|
|
|
|
await client.agents.update(agentId, {
|
|
model: modelHandle,
|
|
...(hasModelSettings && { model_settings: modelSettings }),
|
|
...(contextWindow && { context_window_limit: contextWindow }),
|
|
});
|
|
|
|
const finalAgent = await client.agents.retrieve(agentId);
|
|
return finalAgent.llm_config;
|
|
}
|
|
|
|
export interface SystemPromptUpdateResult {
|
|
success: boolean;
|
|
message: string;
|
|
}
|
|
|
|
/**
|
|
* Updates an agent's system prompt with raw content.
|
|
*
|
|
* @param agentId - The agent ID
|
|
* @param systemPromptContent - The raw system prompt content to update
|
|
* @returns Result with success status and message
|
|
*/
|
|
export async function updateAgentSystemPromptRaw(
|
|
agentId: string,
|
|
systemPromptContent: string,
|
|
): Promise<SystemPromptUpdateResult> {
|
|
try {
|
|
const client = await getClient();
|
|
|
|
await client.agents.update(agentId, {
|
|
system: systemPromptContent,
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
message: "System prompt updated successfully",
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
message: `Failed to update system prompt: ${error instanceof Error ? error.message : String(error)}`,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Result from updating a system prompt on an agent
|
|
*/
|
|
export interface UpdateSystemPromptResult {
|
|
success: boolean;
|
|
message: string;
|
|
agent: AgentState | null;
|
|
}
|
|
|
|
/**
|
|
* Updates an agent's system prompt by ID or subagent name.
|
|
* Resolves the ID to content, updates the agent, and returns the refreshed agent state.
|
|
*
|
|
* @param agentId - The agent ID to update
|
|
* @param systemPromptId - System prompt ID (e.g., "codex") or subagent name (e.g., "explore")
|
|
* @returns Result with success status, message, and updated agent state
|
|
*/
|
|
export async function updateAgentSystemPrompt(
|
|
agentId: string,
|
|
systemPromptId: string,
|
|
): Promise<UpdateSystemPromptResult> {
|
|
try {
|
|
const { resolveSystemPrompt } = await import("./promptAssets");
|
|
const systemPromptContent = await resolveSystemPrompt(systemPromptId);
|
|
|
|
const updateResult = await updateAgentSystemPromptRaw(
|
|
agentId,
|
|
systemPromptContent,
|
|
);
|
|
if (!updateResult.success) {
|
|
return {
|
|
success: false,
|
|
message: updateResult.message,
|
|
agent: null,
|
|
};
|
|
}
|
|
|
|
// Re-fetch agent to get updated state
|
|
const client = await getClient();
|
|
const agent = await client.agents.retrieve(agentId);
|
|
|
|
return {
|
|
success: true,
|
|
message: "System prompt applied successfully",
|
|
agent,
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
message: `Failed to apply system prompt: ${error instanceof Error ? error.message : String(error)}`,
|
|
agent: null,
|
|
};
|
|
}
|
|
}
|