d25d482dc2
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
166 lines
4.6 KiB
TypeScript
166 lines
4.6 KiB
TypeScript
import { db } from '@sim/db'
|
|
import { skill } from '@sim/db/schema'
|
|
import { createLogger } from '@sim/logger'
|
|
import { and, eq, inArray } from 'drizzle-orm'
|
|
import { getBuiltinSkillById, getBuiltinSkillByName } from '@/lib/workflows/skills/builtin-skills'
|
|
import type { SkillInput } from '@/executor/handlers/agent/types'
|
|
|
|
const logger = createLogger('SkillsResolver')
|
|
|
|
function escapeXml(str: string): string {
|
|
return str
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
}
|
|
|
|
interface SkillMetadata {
|
|
name: string
|
|
description: string
|
|
}
|
|
|
|
/**
|
|
* Fetch skill metadata (name + description) for system prompt injection.
|
|
* Only returns lightweight data so the LLM knows what skills are available.
|
|
*/
|
|
export async function resolveSkillMetadata(
|
|
skillInputs: SkillInput[],
|
|
workspaceId: string
|
|
): Promise<SkillMetadata[]> {
|
|
if (!skillInputs.length || !workspaceId) return []
|
|
|
|
const metadata: SkillMetadata[] = []
|
|
const dbSkillIds: string[] = []
|
|
for (const input of skillInputs) {
|
|
const builtin = getBuiltinSkillById(input.skillId)
|
|
if (builtin) {
|
|
metadata.push({ name: builtin.name, description: builtin.description })
|
|
} else {
|
|
dbSkillIds.push(input.skillId)
|
|
}
|
|
}
|
|
|
|
if (dbSkillIds.length === 0) return metadata
|
|
|
|
try {
|
|
const rows = await db
|
|
.select({ name: skill.name, description: skill.description })
|
|
.from(skill)
|
|
.where(and(eq(skill.workspaceId, workspaceId), inArray(skill.id, dbSkillIds)))
|
|
|
|
return [...metadata, ...rows]
|
|
} catch (error) {
|
|
logger.error('Failed to resolve skill metadata', { error, dbSkillIds, workspaceId })
|
|
return metadata
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch full skill content for a load_skill tool response.
|
|
* Called when the LLM decides a skill is relevant and invokes load_skill.
|
|
*/
|
|
export async function resolveSkillContent(
|
|
skillName: string,
|
|
workspaceId: string
|
|
): Promise<string | null> {
|
|
if (!skillName || !workspaceId) return null
|
|
|
|
const builtin = getBuiltinSkillByName(skillName)
|
|
if (builtin) return builtin.content
|
|
|
|
try {
|
|
const rows = await db
|
|
.select({ content: skill.content, name: skill.name })
|
|
.from(skill)
|
|
.where(and(eq(skill.workspaceId, workspaceId), eq(skill.name, skillName)))
|
|
.limit(1)
|
|
|
|
if (rows.length === 0) {
|
|
logger.warn('Skill not found', { skillName, workspaceId })
|
|
return null
|
|
}
|
|
|
|
return rows[0].content
|
|
} catch (error) {
|
|
logger.error('Failed to resolve skill content', { error, skillName, workspaceId })
|
|
return null
|
|
}
|
|
}
|
|
|
|
export async function resolveSkillContentById(
|
|
skillId: string,
|
|
workspaceId: string
|
|
): Promise<{ name: string; content: string } | null> {
|
|
if (!skillId || !workspaceId) return null
|
|
|
|
const builtin = getBuiltinSkillById(skillId)
|
|
if (builtin) return { name: builtin.name, content: builtin.content }
|
|
|
|
try {
|
|
const rows = await db
|
|
.select({ content: skill.content, name: skill.name })
|
|
.from(skill)
|
|
.where(and(eq(skill.workspaceId, workspaceId), eq(skill.id, skillId)))
|
|
.limit(1)
|
|
|
|
if (rows.length === 0) {
|
|
logger.warn('Skill not found', { skillId, workspaceId })
|
|
return null
|
|
}
|
|
|
|
return rows[0]
|
|
} catch (error) {
|
|
logger.error('Failed to resolve skill content', { error, skillId, workspaceId })
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build the system prompt section that lists available skills.
|
|
* Uses XML format per the agentskills.io integration guide.
|
|
*/
|
|
export function buildSkillsSystemPromptSection(skills: SkillMetadata[]): string {
|
|
if (!skills.length) return ''
|
|
|
|
const skillEntries = skills
|
|
.map(
|
|
(s) =>
|
|
` <skill name="${escapeXml(s.name)}">\n <description>${escapeXml(s.description)}</description>\n </skill>`
|
|
)
|
|
.join('\n')
|
|
|
|
return [
|
|
'',
|
|
'You have access to the following skills. Use the load_skill tool to activate a skill when relevant.',
|
|
'',
|
|
'<available_skills>',
|
|
skillEntries,
|
|
'</available_skills>',
|
|
].join('\n')
|
|
}
|
|
|
|
/**
|
|
* Build the load_skill tool definition for injection into the tools array.
|
|
* Returns a ProviderToolConfig-compatible object so all providers can process it.
|
|
*/
|
|
export function buildLoadSkillTool(skillNames: string[]) {
|
|
return {
|
|
id: 'load_skill',
|
|
name: 'load_skill',
|
|
description: `Load a skill to get specialized instructions. Available skills: ${skillNames.join(', ')}`,
|
|
params: {},
|
|
parameters: {
|
|
type: 'object',
|
|
properties: {
|
|
skill_name: {
|
|
type: 'string',
|
|
description: 'Name of the skill to load',
|
|
enum: skillNames,
|
|
},
|
|
},
|
|
required: ['skill_name'],
|
|
},
|
|
}
|
|
}
|