fix: Model flag parsing and model args (#42)

This commit is contained in:
Devansh Jain
2025-10-30 20:23:21 -07:00
committed by GitHub
parent 77d78c22da
commit 32a3f7c7ab
7 changed files with 106 additions and 60 deletions

View File

@@ -7,7 +7,6 @@ import type {
Block,
CreateBlock,
} from "@letta-ai/letta-client/resources/agents/agents";
import { formatAvailableModels, resolveModel } from "../model";
import {
loadProjectSettings,
updateProjectSettings,
@@ -16,12 +15,15 @@ import { loadSettings, updateSettings } from "../settings";
import { getToolNames } from "../tools/manager";
import { getClient } from "./client";
import { getDefaultMemoryBlocks } from "./memory";
import { formatAvailableModels, resolveModel } from "./model";
import { updateAgentLLMConfig } from "./modify";
import { SYSTEM_PROMPT } from "./promptAssets";
export async function createAgent(
name = "letta-cli-agent",
model?: string,
embeddingModel = "openai/text-embedding-3-small",
updateArgs?: Record<string, unknown>,
) {
// Resolve model identifier to handle
let modelHandle: string;
@@ -168,5 +170,13 @@ export async function createAgent(
include_base_tool_rules: false,
initial_message_sequence: [],
});
// Apply updateArgs if provided (e.g., reasoningEffort, contextWindow, etc.)
if (updateArgs && Object.keys(updateArgs).length > 0) {
await updateAgentLLMConfig(agent.id, modelHandle, updateArgs);
// Refresh agent state to get updated config
return await client.agents.retrieve(agent.id);
}
return agent; // { id, ... }
}

64
src/agent/model.ts Normal file
View File

@@ -0,0 +1,64 @@
/**
* Model resolution and handling utilities
*/
import modelsData from "../models.json";
export const models = modelsData;
/**
* Resolve a model by ID or handle
* @param modelIdentifier - Can be either a model ID (e.g., "opus") or a full handle (e.g., "anthropic/claude-opus-4-1-20250805")
* @returns The model handle if found, null otherwise
*/
export function resolveModel(modelIdentifier: string): string | null {
const byId = models.find((m) => m.id === modelIdentifier);
if (byId) return byId.handle;
const byHandle = models.find((m) => m.handle === modelIdentifier);
if (byHandle) return byHandle.handle;
return null;
}
/**
* Get the default model handle
*/
export function getDefaultModel(): string {
const defaultModel = models.find((m) => m.isDefault);
return defaultModel?.handle || models[0].handle;
}
/**
* Format available models for error messages
*/
export function formatAvailableModels(): string {
return models.map((m) => ` ${m.id.padEnd(20)} ${m.handle}`).join("\n");
}
/**
* Get model info by ID or handle
* @param modelIdentifier - Can be either a model ID (e.g., "opus") or a full handle (e.g., "anthropic/claude-opus-4-1-20250805")
* @returns The model info if found, null otherwise
*/
export function getModelInfo(modelIdentifier: string) {
const byId = models.find((m) => m.id === modelIdentifier);
if (byId) return byId;
const byHandle = models.find((m) => m.handle === modelIdentifier);
if (byHandle) return byHandle;
return null;
}
/**
* Get updateArgs for a model by ID or handle
* @param modelIdentifier - Can be either a model ID (e.g., "opus") or a full handle (e.g., "anthropic/claude-opus-4-1-20250805")
* @returns The updateArgs if found, undefined otherwise
*/
export function getModelUpdateArgs(
modelIdentifier?: string,
): Record<string, unknown> | undefined {
if (!modelIdentifier) return undefined;
const modelInfo = getModelInfo(modelIdentifier);
return modelInfo?.updateArgs;
}

View File

@@ -1,7 +1,7 @@
// Import useInput from vendored Ink for bracketed paste support
import { Box, Text, useInput } from "ink";
import { useState } from "react";
import { models } from "../../model";
import { models } from "../../agent/model";
import { colors } from "./colors";
interface ModelSelectorProps {

View File

@@ -8,6 +8,7 @@ import type { StopReasonType } from "@letta-ai/letta-client/resources/runs/runs"
import { getClient } from "./agent/client";
import { createAgent } from "./agent/create";
import { sendMessageStream } from "./agent/message";
import { getModelUpdateArgs } from "./agent/model";
import { SessionStats } from "./agent/stats";
import { createBuffers, toLines } from "./cli/helpers/accumulator";
import { safeJsonParseOr } from "./cli/helpers/safeJsonParse";
@@ -25,6 +26,8 @@ export async function handleHeadlessCommand(argv: string[], model?: string) {
continue: { type: "boolean", short: "c" },
new: { type: "boolean" },
agent: { type: "string", short: "a" },
model: { type: "string", short: "m" },
prompt: { type: "boolean", short: "p" },
"output-format": { type: "string" },
},
strict: false,
@@ -70,7 +73,8 @@ export async function handleHeadlessCommand(argv: string[], model?: string) {
// Priority 2: Check if --new flag was passed (skip all resume logic)
if (!agent && forceNew) {
agent = await createAgent(undefined, model);
const updateArgs = getModelUpdateArgs(model);
agent = await createAgent(undefined, model, undefined, updateArgs);
}
// Priority 3: Try to resume from project settings (.letta/settings.local.json)
@@ -101,7 +105,8 @@ export async function handleHeadlessCommand(argv: string[], model?: string) {
// Priority 5: Create a new agent
if (!agent) {
agent = await createAgent(undefined, model);
const updateArgs = getModelUpdateArgs(model);
agent = await createAgent(undefined, model, undefined, updateArgs);
}
// Save agent ID to both project and global settings

View File

@@ -218,6 +218,7 @@ async function main() {
setLoadingState("initializing");
const { createAgent } = await import("./agent/create");
const { getModelUpdateArgs } = await import("./agent/model");
const { updateSettings, loadProjectSettings, updateProjectSettings } =
await import("./settings");
@@ -238,7 +239,8 @@ async function main() {
// Priority 2: Check if --new flag was passed (skip all resume logic)
if (!agent && forceNew) {
// Create new agent, don't check any lastAgent fields
agent = await createAgent(undefined, model);
const updateArgs = getModelUpdateArgs(model);
agent = await createAgent(undefined, model, undefined, updateArgs);
}
// Priority 3: Try to resume from project settings (.letta/settings.local.json)
@@ -270,7 +272,8 @@ async function main() {
// Priority 5: Create a new agent
if (!agent) {
agent = await createAgent(undefined, model);
const updateArgs = getModelUpdateArgs(model);
agent = await createAgent(undefined, model, undefined, updateArgs);
}
// Save agent ID to both project and global settings

View File

@@ -1,36 +0,0 @@
/**
* Model resolution and handling utilities
*/
import modelsData from "./models.json";
export const models = modelsData;
/**
* Resolve a model by ID or handle
* @param modelIdentifier - Can be either a model ID (e.g., "opus") or a full handle (e.g., "anthropic/claude-opus-4-1-20250805")
* @returns The model handle if found, null otherwise
*/
export function resolveModel(modelIdentifier: string): string | null {
const byId = models.find((m) => m.id === modelIdentifier);
if (byId) return byId.handle;
const byHandle = models.find((m) => m.handle === modelIdentifier);
if (byHandle) return byHandle.handle;
return null;
}
/**
* Get the default model handle
*/
export function getDefaultModel(): string {
const defaultModel = models.find((m) => m.isDefault);
return defaultModel?.handle || models[0].handle;
}
/**
* Format available models for error messages
*/
export function formatAvailableModels(): string {
return models.map((m) => ` ${m.id.padEnd(20)} ${m.handle}`).join("\n");
}

View File

@@ -5,21 +5,21 @@
"label": "Claude Sonnet 4.5 (default)",
"description": "The recommended default model (currently Sonnet 4.5)",
"isDefault": true,
"updateArgs": { "contextWindow": 180000 }
"updateArgs": { "context_window": 180000 }
},
{
"id": "opus",
"handle": "anthropic/claude-opus-4-1-20250805",
"label": "Claude Opus 4.1",
"description": "Anthropic's smartest (and slowest) model",
"updateArgs": { "contextWindow": 180000 }
"updateArgs": { "context_window": 180000 }
},
{
"id": "haiku",
"handle": "anthropic/claude-haiku-4-5-20251001",
"label": "Claude Haiku 4.5",
"description": "Anthropic's fastest model",
"updateArgs": { "contextWindow": 180000 }
"updateArgs": { "context_window": 180000 }
},
{
"id": "gpt-5-codex",
@@ -27,9 +27,9 @@
"label": "GPT-5-Codex",
"description": "A variant of GPT-5 optimized for agentic coding",
"updateArgs": {
"reasoningEffort": "medium",
"reasoning_effort": "medium",
"verbosity": "medium",
"contextWindow": 272000
"context_window": 272000
}
},
{
@@ -38,7 +38,7 @@
"label": "GLM-4.6",
"description": "The best open weights coding model",
"updateArgs": {
"contextWindow": 200000
"context_window": 200000
}
},
{
@@ -47,9 +47,9 @@
"label": "GPT-5 (minimal)",
"description": "OpenAI's latest model (limited reasoning, fastest GPT-5 option)",
"updateArgs": {
"reasoningEffort": "minimal",
"reasoning_effort": "minimal",
"verbosity": "medium",
"contextWindow": 272000
"context_window": 272000
}
},
{
@@ -58,9 +58,9 @@
"label": "GPT-5 (low)",
"description": "OpenAI's latest model (some reasoning enabled)",
"updateArgs": {
"reasoningEffort": "low",
"reasoning_effort": "low",
"verbosity": "medium",
"contextWindow": 272000
"context_window": 272000
}
},
{
@@ -69,9 +69,9 @@
"label": "GPT-5 (medium)",
"description": "OpenAI's latest model (using their recommended reasoning level)",
"updateArgs": {
"reasoningEffort": "medium",
"reasoning_effort": "medium",
"verbosity": "medium",
"contextWindow": 272000
"context_window": 272000
}
},
{
@@ -80,9 +80,9 @@
"label": "GPT-5 (high)",
"description": "OpenAI's latest model (maximum reasoning depth)",
"updateArgs": {
"reasoningEffort": "high",
"reasoning_effort": "high",
"verbosity": "medium",
"contextWindow": 272000
"context_window": 272000
}
},
{
@@ -90,27 +90,27 @@
"handle": "google_ai/gemini-2.5-flash",
"label": "Gemini 2.5 Flash",
"description": "Google's fastest model",
"updateArgs": { "contextWindow": 180000 }
"updateArgs": { "context_window": 180000 }
},
{
"id": "gemini-pro",
"handle": "google_ai/gemini-2.5-pro",
"label": "Gemini 2.5 Pro",
"description": "Google's smartest model",
"updateArgs": { "contextWindow": 180000 }
"updateArgs": { "context_window": 180000 }
},
{
"id": "gpt-4.1",
"handle": "openai/gpt-4.1",
"label": "GPT-4.1",
"description": "OpenAI's most recent non-reasoner model",
"updateArgs": { "contextWindow": 180000 }
"updateArgs": { "context_window": 180000 }
},
{
"id": "o4-mini",
"handle": "openai/o4-mini",
"label": "o4-mini",
"description": "OpenAI's latest o-series reasoning model",
"updateArgs": { "contextWindow": 180000 }
"updateArgs": { "context_window": 180000 }
}
]