@outputai/llm 0.1.11 → 0.1.12-dev.d521efb.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/skill.d.ts ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * An instruction package that an agent can load on demand via the load_skill tool.
3
+ *
4
+ * Skills are declared in prompt frontmatter (as file paths) or passed inline
5
+ * to agent(). The LLM sees skill names and descriptions in `{{ _system_skills }}`
6
+ * and calls `load_skill` to retrieve full instructions when needed.
7
+ */
8
+ export type Skill = {
9
+ name: string;
10
+ description: string;
11
+ instructions: string;
12
+ };
13
+
14
+ /**
15
+ * The skills argument for agent(). Either a static list or a function
16
+ * that receives the agent's input and returns skills dynamically.
17
+ */
18
+ export type SkillsArg<Input = unknown> = Skill[] |
19
+ ( ( input: Input ) => Skill[] | Promise<Skill[]> );
20
+
21
+ /**
22
+ * Create an inline skill instruction package.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const researchSkill = skill( {
27
+ * name: 'web_research',
28
+ * description: 'Search and synthesize web information',
29
+ * instructions: '# Web Research\n1. Break into queries\n2. Search\n3. Cite sources'
30
+ * } );
31
+ * ```
32
+ */
33
+ export declare function skill( params: {
34
+ name: string;
35
+ description?: string;
36
+ instructions: string;
37
+ } ): Skill;
38
+
39
+ /** Load a single skill from a markdown file. */
40
+ export declare function loadSkillFile( filePath: string ): Skill;
41
+
42
+ /** Load skills from an array of file/directory paths, resolved relative to promptDir. */
43
+ export declare function loadPromptSkills( skillPaths: string | string[], promptDir: string ): Skill[];
44
+
45
+ /** Build the `{{ _system_skills }}` template variable listing available skills. */
46
+ export declare function buildSystemSkillsVar( skills: Skill[] ): string;
47
+
48
+ /** Build the `load_skill` AI SDK tool that returns full instructions for a named skill. */
49
+ export declare function buildLoadSkillTool( skills: Skill[] ): import( 'ai' ).Tool;
package/src/skill.js ADDED
@@ -0,0 +1,109 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
+ import { join, resolve, basename } from 'node:path';
3
+ import matter from 'gray-matter';
4
+ import { tool } from 'ai';
5
+ import { z, ValidationError, FatalError } from '@outputai/core';
6
+
7
+ /**
8
+ * Create an inline skill instruction package.
9
+ *
10
+ * @param {object} params
11
+ * @param {string} params.name - Skill identifier
12
+ * @param {string} [params.description] - When to use this skill (defaults to name)
13
+ * @param {string} params.instructions - Full instructions returned when LLM calls load_skill
14
+ * @returns {{ name: string, description: string, instructions: string }}
15
+ */
16
+ export function skill( { name, description, instructions } ) {
17
+ if ( !name ) {
18
+ throw new ValidationError( 'skill() requires a name' );
19
+ }
20
+ if ( !instructions ) {
21
+ throw new ValidationError( 'skill() requires instructions' );
22
+ }
23
+ return { name, description: description ?? name, instructions };
24
+ }
25
+
26
+ /**
27
+ * Load a single skill from a markdown file.
28
+ * Frontmatter may provide `name` and `description`; body becomes the instructions.
29
+ *
30
+ * @param {string} filePath - Absolute path to the .md skill file
31
+ * @returns {{ name: string, description: string, instructions: string }}
32
+ */
33
+ export const loadSkillFile = filePath => {
34
+ const raw = readFileSync( filePath, 'utf-8' );
35
+ const { data, content } = matter( raw );
36
+ return {
37
+ name: data.name ?? basename( filePath, '.md' ),
38
+ description: data.description ?? basename( filePath, '.md' ),
39
+ instructions: content.trim()
40
+ };
41
+ };
42
+
43
+ /**
44
+ * Load skills from an array of paths (files or directories of .md files).
45
+ * Paths are resolved relative to `promptDir`.
46
+ *
47
+ * @param {string[]} skillPaths - Paths from prompt frontmatter (may be files or directories)
48
+ * @param {string} promptDir - Base directory for resolving relative paths
49
+ * @returns {{ name: string, description: string, instructions: string }[]}
50
+ */
51
+ export const loadPromptSkills = ( skillPaths, promptDir ) => {
52
+ const paths = Array.isArray( skillPaths ) ? skillPaths : [ skillPaths ];
53
+ return paths.flatMap( skillPath => {
54
+ const resolved = resolve( promptDir, skillPath );
55
+ if ( !existsSync( resolved ) ) {
56
+ throw new FatalError( `Skill path not found: "${skillPath}" (resolved to "${resolved}")` );
57
+ }
58
+ if ( statSync( resolved ).isDirectory() ) {
59
+ return readdirSync( resolved )
60
+ .filter( f => f.endsWith( '.md' ) )
61
+ .sort()
62
+ .map( f => loadSkillFile( join( resolved, f ) ) );
63
+ }
64
+ return [ loadSkillFile( resolved ) ];
65
+ } );
66
+ };
67
+
68
+ /**
69
+ * Load skills from a colocated `skills/` directory next to the prompt file.
70
+ * Returns empty array if the directory doesn't exist or has no .md files.
71
+ *
72
+ * @param {string} promptDir - Directory containing the prompt file
73
+ * @returns {{ name: string, description: string, instructions: string }[]}
74
+ */
75
+ export const loadColocatedSkills = promptDir => {
76
+ const skillsDir = resolve( promptDir, 'skills' );
77
+ if ( !existsSync( skillsDir ) || !statSync( skillsDir ).isDirectory() ) {
78
+ return [];
79
+ }
80
+ return loadPromptSkills( [ './skills/' ], promptDir );
81
+ };
82
+
83
+ /**
84
+ * Build the skills system message content listing available skills.
85
+ *
86
+ * @param {{ name: string, description: string }[]} skills
87
+ * @returns {string}
88
+ */
89
+ export const buildSystemSkillsVar = skills =>
90
+ 'Available skills (use load_skill to get full instructions):\n' +
91
+ skills.map( s => `- ${s.name}: ${s.description}` ).join( '\n' );
92
+
93
+ /**
94
+ * Build the `load_skill` AI SDK tool that the LLM calls to retrieve full skill instructions.
95
+ *
96
+ * @param {{ name: string, instructions: string }[]} skills
97
+ * @returns {import('ai').Tool}
98
+ */
99
+ export const buildLoadSkillTool = skills => tool( {
100
+ description: 'Get detailed instructions for a named skill',
101
+ inputSchema: z.object( { name: z.string().describe( 'Name of the skill to load' ) } ),
102
+ execute: ( { name } ) => {
103
+ const sk = skills.find( s => s.name === name );
104
+ if ( !sk ) {
105
+ return `Skill "${name}" not found. Available: ${skills.map( s => s.name ).join( ', ' )}`;
106
+ }
107
+ return sk.instructions;
108
+ }
109
+ } );
@@ -0,0 +1,30 @@
1
+ import { Tracing, emitEvent } from '@outputai/core/sdk_activity_integration';
2
+ import { calculateLLMCallCost } from './cost/index.js';
3
+
4
+ export const startTrace = ( name, details ) => {
5
+ const traceId = `${name}-${Date.now()}`;
6
+ Tracing.addEventStart( { kind: 'llm', name, id: traceId, details } );
7
+ return traceId;
8
+ };
9
+
10
+ export const endTraceWithError = ( traceId, error ) => {
11
+ Tracing.addEventError( { id: traceId, details: error } );
12
+ };
13
+
14
+ export const endTraceWithSuccess = async ( traceId, modelId, response, extraDetails = {} ) => {
15
+ const { text: result, totalUsage: usage, providerMetadata } = response;
16
+ const cost = await calculateLLMCallCost( { usage, modelId } );
17
+ emitEvent( 'llm:call_cost', { modelId, cost, usage } );
18
+ Tracing.addEventEnd( { id: traceId, details: { result, usage, cost, providerMetadata, ...extraDetails } } );
19
+ };
20
+
21
+ export const traceStreamCallbacks = ( traceId, modelId, { onFinish: userOnFinish, onError: userOnError } = {} ) => ( {
22
+ async onFinish( response ) {
23
+ await endTraceWithSuccess( traceId, modelId, response );
24
+ userOnFinish?.( response );
25
+ },
26
+ onError( event ) {
27
+ Tracing.addEventError( { id: traceId, details: event.error } );
28
+ userOnError?.( event );
29
+ }
30
+ } );