fix: Model flag parsing and model args (#42)
This commit is contained in:
@@ -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
64
src/agent/model.ts
Normal 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;
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
36
src/model.ts
36
src/model.ts
@@ -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");
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user