feat: add client side skills (#1320)

Co-authored-by: Letta Code <noreply@letta.com>
This commit is contained in:
Sarah Wooders
2026-03-10 13:18:14 -07:00
committed by GitHub
parent 87312720d5
commit e82a2d33f8
25 changed files with 377 additions and 151 deletions

View File

@@ -8,6 +8,7 @@ import type {
ApprovalCreate,
LettaStreamingResponse,
} from "@letta-ai/letta-client/resources/agents/messages";
import type { MessageCreateParams as ConversationMessageCreateParams } from "@letta-ai/letta-client/resources/conversations/messages";
import {
type ClientTool,
captureToolExecutionContext,
@@ -19,6 +20,7 @@ import {
normalizeOutgoingApprovalMessages,
} from "./approval-result-normalization";
import { getClient } from "./client";
import { buildClientSkillsPayload } from "./clientSkills";
const streamRequestStartTimes = new WeakMap<object, number>();
const streamToolContextIds = new WeakMap<object, string>();
@@ -60,6 +62,9 @@ export function buildConversationMessagesCreateRequestBody(
messages: Array<MessageCreate | ApprovalCreate>,
opts: SendMessageStreamOptions = { streamTokens: true, background: true },
clientTools: ClientTool[],
clientSkills: NonNullable<
ConversationMessageCreateParams["client_skills"]
> = [],
) {
const isDefaultConversation = conversationId === "default";
if (isDefaultConversation && !opts.agentId) {
@@ -77,6 +82,7 @@ export function buildConversationMessagesCreateRequestBody(
stream_tokens: opts.streamTokens ?? true,
include_pings: true,
background: opts.background ?? true,
client_skills: clientSkills,
client_tools: clientTools,
include_compaction_messages: true,
...(isDefaultConversation ? { agent_id: opts.agentId } : {}),
@@ -113,6 +119,10 @@ export async function sendMessageStream(
// This prevents sending messages with stale tools during a switch
await waitForToolsetReady();
const { clientTools, contextId } = captureToolExecutionContext();
const { clientSkills, errors: clientSkillDiscoveryErrors } =
await buildClientSkillsPayload({
agentId: opts.agentId,
});
const resolvedConversationId = conversationId;
const requestBody = buildConversationMessagesCreateRequestBody(
@@ -120,12 +130,30 @@ export async function sendMessageStream(
messages,
opts,
clientTools,
clientSkills,
);
if (process.env.DEBUG) {
console.log(
`[DEBUG] sendMessageStream: conversationId=${conversationId}, agentId=${opts.agentId ?? "(none)"}`,
);
const formattedSkills = clientSkills.map(
(skill) => `${skill.name} (${skill.location})`,
);
console.log(
`[DEBUG] sendMessageStream: client_skills (${clientSkills.length}) ${
formattedSkills.length > 0 ? formattedSkills.join(", ") : "(none)"
}`,
);
if (clientSkillDiscoveryErrors.length > 0) {
for (const error of clientSkillDiscoveryErrors) {
console.warn(
`[DEBUG] sendMessageStream: client_skills discovery error at ${error.path}: ${error.message}`,
);
}
}
}
const extraHeaders: Record<string, string> = {};