langtrain 0.1.18 → 0.1.20

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.
@@ -146,12 +146,31 @@ export async function handleAgentList(client: AgentClient) {
146
146
  await handleAgentRun(client, agentId as string, agents.find(a => a.id === agentId)?.name || 'Agent');
147
147
  }
148
148
 
149
- async function handleAgentRun(client: AgentClient, agentId: string, agentName: string) {
149
+ export async function handleAgentRun(client: AgentClient, agentId: string, agentName: string, initialMessage?: string) {
150
150
  intro(bgCyan(black(` Chatting with ${agentName} `)));
151
151
  console.log(gray('Type "exit" to quit conversation.'));
152
152
 
153
153
  let conversationId: string | undefined = undefined;
154
154
 
155
+ // Send initial message if provided
156
+ if (initialMessage) {
157
+ const s = spinner();
158
+ s.start('Agent is analyzing...');
159
+ try {
160
+ const result = await client.execute(agentId, { prompt: initialMessage }, [], conversationId);
161
+ s.stop();
162
+ if ((result.output as any)?.response) {
163
+ console.log(gradient.pastel(`Agent: ${(result.output as any).response}`));
164
+ } else {
165
+ console.log(gradient.pastel(`Agent: ${JSON.stringify(result.output)}`));
166
+ }
167
+ conversationId = result.conversation_id;
168
+ } catch (e: any) {
169
+ s.stop(red('Error running agent.'));
170
+ console.error(e);
171
+ }
172
+ }
173
+
155
174
  while (true) {
156
175
  const input = await text({
157
176
  message: 'You:',
@@ -168,8 +187,8 @@ async function handleAgentRun(client: AgentClient, agentId: string, agentName: s
168
187
  const result = await client.execute(agentId, { prompt: input }, [], conversationId);
169
188
  s.stop();
170
189
 
171
- if (result.output && result.output.response) {
172
- console.log(gradient.pastel(`Agent: ${result.output.response}`));
190
+ if ((result.output as any)?.response) {
191
+ console.log(gradient.pastel(`Agent: ${(result.output as any).response}`));
173
192
  } else {
174
193
  console.log(gradient.pastel(`Agent: ${JSON.stringify(result.output)}`));
175
194
  }
@@ -0,0 +1,207 @@
1
+ import { text, select, confirm, isCancel, cancel, spinner, intro, red, green, yellow, gray } from '../ui';
2
+ import { getConfig } from '../config';
3
+ import { FileClient, AgentClient, GuardrailClient } from '../../index';
4
+ import { handleAgentRun } from './agent';
5
+ import fs from 'fs';
6
+
7
+ export async function handleDataUpload(client: FileClient) {
8
+ const config = getConfig();
9
+ let workspaceId = config.workspace_id;
10
+
11
+ if (!workspaceId) {
12
+ // Optional: ask for workspace ID or try to infer?
13
+ // For upload, workspace_id is often optional (inferred from API key's default workspace)
14
+ // But let's ask if user wants to specify.
15
+ }
16
+
17
+ const filePath = await text({
18
+ message: 'Path to file:',
19
+ placeholder: './dataset.jsonl',
20
+ validate(value) {
21
+ if (!value) return 'Required';
22
+ if (!fs.existsSync(value)) return 'File not found';
23
+ }
24
+ });
25
+
26
+ if (isCancel(filePath)) return;
27
+
28
+ const purpose = await select({
29
+ message: 'File Purpose:',
30
+ options: [
31
+ { value: 'fine-tune', label: 'Fine-tuning (JSONL)' },
32
+ { value: 'vision-tune', label: 'Vision Tuning (Image/Zip)' },
33
+ { value: 'agent-knowledge', label: 'Agent Knowledge' }
34
+ ]
35
+ });
36
+
37
+ if (isCancel(purpose)) return;
38
+
39
+ const s = spinner();
40
+ s.start('Uploading file...');
41
+
42
+ try {
43
+ const result = await client.upload(filePath as string, workspaceId, purpose as string);
44
+ s.stop(green('File uploaded successfully!'));
45
+ console.log(gray(`ID: ${result.id}`));
46
+ console.log(gray(`Name: ${result.filename}`));
47
+ console.log(gray(`Bytes: ${result.bytes}`));
48
+ } catch (e: any) {
49
+ s.stop(red(`Upload failed: ${e.message}`));
50
+ }
51
+ }
52
+
53
+ export async function handleDataList(client: FileClient) {
54
+ const config = getConfig();
55
+ let workspaceId = config.workspace_id;
56
+
57
+ if (!workspaceId) {
58
+ // Try without workspace ID (some APIs return user's files)
59
+ // or ask
60
+ workspaceId = await text({ message: 'Enter Workspace ID (optional):', initialValue: '' });
61
+ if (isCancel(workspaceId)) return;
62
+ }
63
+
64
+ const s = spinner();
65
+ s.start('Fetching files...');
66
+
67
+ try {
68
+ const files = await client.list(workspaceId as string);
69
+ s.stop(`Found ${files.length} files`);
70
+
71
+ if (files.length === 0) {
72
+ console.log(yellow('No files found.'));
73
+ return;
74
+ }
75
+
76
+ const file = await select({
77
+ message: 'Select file to analyze (or cancel to exit):',
78
+ options: files.map(f => ({ value: f.id, label: `${f.filename} (${formatBytes(f.bytes)})` }))
79
+ });
80
+
81
+ if (isCancel(file)) return;
82
+
83
+ await handleDataAnalyze(client, file as string);
84
+
85
+ } catch (e: any) {
86
+ s.stop(red(`Failed to list files: ${e.message}`));
87
+ }
88
+ }
89
+
90
+ export async function handleDataAnalyze(client: FileClient, fileId: string) {
91
+ const config = getConfig();
92
+ const s2 = spinner();
93
+ s2.start('Connecting to Data Analyst...');
94
+
95
+ try {
96
+ const agentClient = new AgentClient({ apiKey: config.apiKey || '', baseUrl: config.baseUrl });
97
+ const agents = await agentClient.list();
98
+ // Check name (name is optional in type but we know it exists for system agent)
99
+ let analyst = agents.find(a => a.name && a.name === "Langtrain Data Analyst");
100
+
101
+ if (!analyst) {
102
+ s2.stop(yellow('Data Analyst agent (System) not found. Please contact admin to provision it.'));
103
+ return;
104
+ }
105
+
106
+ s2.stop(green('Connected to Data Analyst.'));
107
+
108
+ console.log(gray(`\nAnalyzing dataset ${fileId}...\n`));
109
+
110
+ await handleAgentRun(agentClient, analyst.id, analyst.name, `Please analyze the dataset with ID: ${fileId}`);
111
+
112
+ } catch (e: any) {
113
+ s2.stop(red(`Failed to connect: ${e.message}`));
114
+ }
115
+ }
116
+
117
+ export async function handleDataRefine(client: FileClient, fileId?: string) {
118
+ const config = getConfig();
119
+ const gClient = new GuardrailClient({ apiKey: config.apiKey || '', baseUrl: config.baseUrl });
120
+
121
+ // 0. Select File if not provided
122
+ if (!fileId) {
123
+ const s = spinner();
124
+ s.start('Fetching files...');
125
+ try {
126
+ // Need workspace ID logic similar to List?
127
+ // client.list takes workspaceId.
128
+ const wId = config.workspace_id || '';
129
+ const files = await client.list(wId);
130
+ s.stop(`Found ${files.length} files`);
131
+
132
+ if (files.length === 0) {
133
+ console.log(yellow('No files found. Upload one first.'));
134
+ return;
135
+ }
136
+
137
+ const selection = await select({
138
+ message: 'Select file to refine:',
139
+ options: files.map(f => ({ value: f.id, label: `${f.filename} (${formatBytes(f.bytes)})` }))
140
+ });
141
+
142
+ if (isCancel(selection)) return;
143
+ fileId = selection as string;
144
+
145
+ } catch (e: any) {
146
+ s.stop(red(`Failed to fetch files: ${e.message}`));
147
+ return;
148
+ }
149
+ }
150
+
151
+ // 1. Select Guardrail
152
+ const s = spinner();
153
+ s.start('Fetching guardrails...');
154
+ let guardId = "";
155
+ try {
156
+ const guards = await gClient.list();
157
+ s.stop(`Found ${guards.length} guardrails`);
158
+
159
+ if (guards.length === 0) {
160
+ console.log(yellow('No guardrails found. Please create one first using "lt guardrails create".'));
161
+ return;
162
+ }
163
+
164
+ const selection = await select({
165
+ message: 'Select a Guardrail to apply:',
166
+ options: guards.map((g: any) => ({
167
+ value: g.id,
168
+ label: g.name,
169
+ hint: g.description
170
+ }))
171
+ });
172
+
173
+ if (isCancel(selection)) return;
174
+ guardId = selection as string;
175
+
176
+ } catch (e: any) {
177
+ s.stop(red(`Failed to fetch guardrails: ${e.message}`));
178
+ return;
179
+ }
180
+
181
+ // 2. Apply
182
+ const s2 = spinner();
183
+ s2.start('Applying guardrail (filtering dataset)...');
184
+
185
+ try {
186
+ const result = await gClient.apply(fileId, guardId);
187
+ s2.stop(green('Dataset refined successfully!'));
188
+
189
+ console.log(gray('Stats:'));
190
+ console.log(`Original Rows: ${result.original_rows}`);
191
+ console.log(`Filtered Rows: ${result.filtered_rows}`);
192
+ console.log(red(`Removed: ${result.removed_rows} rows`));
193
+ console.log(green(`New Dataset ID: ${result.new_dataset_id}`));
194
+
195
+ } catch (e: any) {
196
+ s2.stop(red(`Failed to refine dataset: ${e.message}`));
197
+ }
198
+ }
199
+
200
+ function formatBytes(bytes: number, decimals = 2) {
201
+ if (!+bytes) return '0 Bytes';
202
+ const k = 1024;
203
+ const dm = decimals < 0 ? 0 : decimals;
204
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
205
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
206
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
207
+ }
@@ -0,0 +1,62 @@
1
+ import { intro, outro, spinner, green, red, yellow, showSuccess } from '../ui';
2
+ import { getConfig } from '../config';
3
+ import { AgentClient, AgentCreate } from '../../index';
4
+
5
+ export async function handleDeploy(client: AgentClient) {
6
+ intro('Deploying configuration to Langtrain Cloud...');
7
+
8
+ const config = getConfig();
9
+ const agents = config.agents || [];
10
+
11
+ if (agents.length === 0) {
12
+ intro(yellow('No agents found in langtrain.config.json'));
13
+ return;
14
+ }
15
+
16
+ // Iterate and deploy
17
+ for (const agentConfig of agents) {
18
+ const s = spinner();
19
+ s.start(`Deploying agent: ${agentConfig.name}...`);
20
+
21
+ try {
22
+ // Check if agent exists (by name? logic needed)
23
+ // Ideally we store ID in config after create, but for now let's just create new or try to find by name.
24
+ // Listing all agents is expensive if many, but safe for now.
25
+ const existingAgents = await client.list();
26
+ const existing = existingAgents.find(a => a.name === agentConfig.name);
27
+
28
+ if (existing) {
29
+ // Update (Note: SDK didn't expose update in my view_file, assuming create or need to add update)
30
+ // If update not available, we skip or warn.
31
+ // Let's assume we can't update yet as per SDK view.
32
+ // So we just skip if exists.
33
+ s.stop(yellow(`Agent ${agentConfig.name} already exists (ID: ${existing.id}). Skipping update (not supported yet).`));
34
+ } else {
35
+ // Create
36
+ const payload: AgentCreate = {
37
+ workspace_id: config.workspace_id || (existingAgents[0]?.workspace_id) || '', // Need a way to get workspace!
38
+ name: agentConfig.name,
39
+ description: agentConfig.description,
40
+ config: agentConfig.config
41
+ };
42
+
43
+ // Fallback for workspace_id
44
+ if (!payload.workspace_id) {
45
+ // Try to get from first agent or error
46
+ // Realistically, user needs to set workspace_id in config or we infer from API key scope.
47
+ // Let's warn.
48
+ s.stop(red(`Failed: Workspace ID missing in config for ${agentConfig.name}`));
49
+ continue;
50
+ }
51
+
52
+ await client.create(payload);
53
+ s.stop(green(`Agent ${agentConfig.name} deployed successfully!`));
54
+ }
55
+
56
+ } catch (e: any) {
57
+ s.stop(red(`Failed to deploy ${agentConfig.name}: ${e.message}`));
58
+ }
59
+ }
60
+
61
+ showSuccess('Deployment complete.');
62
+ }
@@ -0,0 +1,42 @@
1
+ import { intro, outro, spinner, green, red, yellow, showInfo, gray } from '../ui';
2
+ import { AgentClient } from '../../index';
3
+ import { handleDeploy } from './deploy';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+
7
+ export async function handleDev(client: AgentClient) {
8
+ intro('Starting Langtrain Development Server...');
9
+
10
+ const configPath = path.join(process.cwd(), 'langtrain.config.json');
11
+ if (!fs.existsSync(configPath)) {
12
+ intro(red('langtrain.config.json not found. Run "lt init" first.'));
13
+ return;
14
+ }
15
+
16
+ console.log(gray(`Watching ${configPath} for changes...`));
17
+
18
+ let isDeploying = false;
19
+
20
+ // Initial Deploy
21
+ await handleDeploy(client);
22
+
23
+ fs.watch(configPath, async (eventType) => {
24
+ if (eventType === 'change' && !isDeploying) {
25
+ isDeploying = true;
26
+ console.log(yellow('Configuration changed. Redeploying...'));
27
+ // Wait a bit for file write to complete
28
+ await new Promise(r => setTimeout(r, 500));
29
+ try {
30
+ await handleDeploy(client);
31
+ } catch (e: any) {
32
+ console.error(red(`Deploy failed: ${e.message}`));
33
+ } finally {
34
+ isDeploying = false;
35
+ console.log(gray(`Watching ${configPath}...`));
36
+ }
37
+ }
38
+ });
39
+
40
+ // Keep process alive
41
+ await new Promise(() => { });
42
+ }
@@ -0,0 +1,54 @@
1
+ import { intro, outro, showSuccess, showError, showWarning, showInfo, spinner, colors } from '../ui';
2
+ import { getConfig } from '../config';
3
+ import { getSubscription } from '../auth';
4
+ import os from 'os';
5
+
6
+ export async function handleDoctor() {
7
+ intro('Running Langtrain Doctor...');
8
+
9
+ const s = spinner();
10
+ let issues = 0;
11
+
12
+ // 1. Check Node Environment
13
+ s.start('Checking Node.js environment...');
14
+ const nodeVersion = process.version;
15
+ const platform = os.platform();
16
+ const arch = os.arch();
17
+
18
+ if (parseInt(nodeVersion.replace('v', '').split('.')[0]) < 18) {
19
+ s.stop(colors.red(`Node.js version ${nodeVersion} is outdated. Please upgrade to v18+.`));
20
+ issues++;
21
+ } else {
22
+ s.stop(`Node.js ${nodeVersion} (${platform} ${arch})`);
23
+ }
24
+
25
+ // 2. Check Configuration
26
+ s.start('Checking configuration...');
27
+ const config = getConfig();
28
+ if (!config.apiKey) {
29
+ s.stop(colors.yellow('API Key is missing. Run `langtrain login` or set LANGTRAIN_API_KEY.'));
30
+ issues++;
31
+ } else {
32
+ s.stop('Configuration found.');
33
+
34
+ // 3. Check API Connectivity
35
+ s.start('Checking API connectivity...');
36
+ try {
37
+ const plan = await getSubscription(config.apiKey);
38
+ s.stop(`Connected to Langtrain Cloud (Plan: ${colors.green(plan?.plan || 'unknown')})`);
39
+ } catch (e: any) {
40
+ s.stop(colors.red(`Failed to connect to Langtrain Cloud: ${e.message}`));
41
+ issues++;
42
+ }
43
+ }
44
+
45
+ console.log(''); // Spacer
46
+
47
+ if (issues === 0) {
48
+ showSuccess('Your Langtrain environment is healthy! Ready to build.');
49
+ } else {
50
+ showWarning(`Found ${issues} issue(s). Please resolve them for the best experience.`);
51
+ }
52
+
53
+ outro('Doctor check complete.');
54
+ }
@@ -0,0 +1,89 @@
1
+ import { intro, outro, spinner, green, red, yellow, showInfo, gray, showSuccess } from '../ui';
2
+ import { SecretClient } from '../../index';
3
+ import { text, confirm, select, isCancel, cancel } from '../ui';
4
+ import { getConfig } from '../config';
5
+
6
+ export async function handleEnvList(client: SecretClient) {
7
+ const s = spinner();
8
+ s.start('Fetching secrets...');
9
+ const config = getConfig();
10
+ try {
11
+ const secrets = await client.list(config.workspace_id);
12
+ s.stop(`Found ${secrets.length} secrets`);
13
+
14
+ if (secrets.length === 0) {
15
+ console.log(gray('No secrets found. Use "lt env set" to add one.'));
16
+ return;
17
+ }
18
+
19
+ console.log(gray('------------------------------------------------'));
20
+ secrets.forEach(sec => {
21
+ console.log(`${sec.key.padEnd(30)} ${gray('******')}`);
22
+ });
23
+ console.log(gray('------------------------------------------------'));
24
+
25
+ } catch (e: any) {
26
+ s.stop(red(`Failed to list secrets: ${e.message}`));
27
+ }
28
+ }
29
+
30
+ export async function handleEnvSet(client: SecretClient, keyVal?: string) {
31
+ let key = '';
32
+ let value = '';
33
+
34
+ if (keyVal && keyVal.includes('=')) {
35
+ const parts = keyVal.split('=');
36
+ key = parts[0];
37
+ value = parts.slice(1).join('=');
38
+ } else {
39
+ key = await text({ message: 'Secret Key:', placeholder: 'OPENAI_API_KEY' }) as string;
40
+ if (isCancel(key)) return;
41
+
42
+ value = await text({ message: 'Secret Value:', placeholder: 'sk-...' }) as string;
43
+ if (isCancel(value)) return;
44
+ }
45
+
46
+ const s = spinner();
47
+ s.start(`Setting ${key}...`);
48
+ const config = getConfig();
49
+
50
+ try {
51
+ await client.set(key, value, config.workspace_id);
52
+ s.stop(green(`Secret ${key} set successfully.`));
53
+ } catch (e: any) {
54
+ s.stop(red(`Failed to set secret: ${e.message}`));
55
+ }
56
+ }
57
+
58
+ // Interactive menu for env
59
+ export async function handleEnvMenu(client: SecretClient) {
60
+ const action = await select({
61
+ message: 'Manage Secrets',
62
+ options: [
63
+ { value: 'list', label: 'List Secrets' },
64
+ { value: 'set', label: 'Set Secret' },
65
+ { value: 'remove', label: 'Remove Secret' },
66
+ { value: 'back', label: 'Back' }
67
+ ]
68
+ });
69
+
70
+ if (isCancel(action) || action === 'back') return;
71
+
72
+ if (action === 'list') await handleEnvList(client);
73
+ if (action === 'set') await handleEnvSet(client);
74
+ if (action === 'remove') {
75
+ const key = await text({ message: 'Key to remove:' });
76
+ if (!isCancel(key)) {
77
+ const s = spinner();
78
+ s.start('Removing...');
79
+ try {
80
+ // need workspace_id
81
+ const config = getConfig();
82
+ await client.delete(key as string, config.workspace_id);
83
+ s.stop(green('Removed.'));
84
+ } catch (e: any) {
85
+ s.stop(red(`Failed: ${e.message}`));
86
+ }
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,100 @@
1
+ import { text, select, confirm, isCancel, cancel, spinner, intro, red, green, yellow, gray } from '../ui';
2
+ import { getConfig } from '../config';
3
+ import { GuardrailClient } from '../../index';
4
+
5
+ export async function handleGuardrailList(client: any) { // using any to match index signature, but we instantiate specific client inside
6
+ const config = getConfig();
7
+ const gClient = new GuardrailClient({ apiKey: config.apiKey || '', baseUrl: config.baseUrl });
8
+
9
+ const s = spinner();
10
+ s.start('Fetching guardrails...');
11
+
12
+ try {
13
+ const guards = await gClient.list();
14
+ s.stop(`Found ${guards.length} guardrails`);
15
+
16
+ if (guards.length === 0) {
17
+ console.log(yellow('No guardrails found. Create one with "lt guardrails create".'));
18
+ return;
19
+ }
20
+
21
+ guards.forEach((g: any) => {
22
+ console.log(green(`• ${g.name}`) + gray(` (ID: ${g.id})`));
23
+ if (g.description) console.log(gray(` ${g.description}`));
24
+ console.log(gray(` Config: PII=${g.config.pii_enabled}, MinLen=${g.config.min_length}`));
25
+ console.log('');
26
+ });
27
+
28
+ } catch (e: any) {
29
+ s.stop(red(`Failed to list guardrails: ${e.message}`));
30
+ }
31
+ }
32
+
33
+ export async function handleGuardrailCreate(client: any) {
34
+ const config = getConfig();
35
+ const gClient = new GuardrailClient({ apiKey: config.apiKey || '', baseUrl: config.baseUrl });
36
+
37
+ intro('Create a new Data Guardrail');
38
+
39
+ const name = await text({
40
+ message: 'Guardrail Name:',
41
+ placeholder: 'e.g. Strict Safety Policy',
42
+ validate(value) {
43
+ if (!value) return 'Name is required';
44
+ }
45
+ });
46
+ if (isCancel(name)) return;
47
+
48
+ const description = await text({
49
+ message: 'Description (optional):',
50
+ placeholder: 'Filters PII and short text',
51
+ });
52
+ if (isCancel(description)) return;
53
+
54
+ // Interactive Config
55
+ const minLen = await text({
56
+ message: 'Minimum Text Length (0 for no limit):',
57
+ initialValue: '0',
58
+ validate(value) {
59
+ if (isNaN(Number(value))) return 'Must be a number';
60
+ }
61
+ });
62
+ if (isCancel(minLen)) return;
63
+
64
+ const enablePii = await confirm({
65
+ message: 'Enable PII Filtering (Email/Phone)?',
66
+ initialValue: false
67
+ });
68
+ if (isCancel(enablePii)) return;
69
+
70
+ const patterns = await text({
71
+ message: 'Regex Patterns to Block (comma separated, optional):',
72
+ placeholder: 'e.g. bad_word, another_one',
73
+ });
74
+ if (isCancel(patterns)) return;
75
+
76
+ const s = spinner();
77
+ s.start('Creating guardrail...');
78
+
79
+ try {
80
+ const regexList = (patterns as string).split(',').map(p => p.trim()).filter(p => p.length > 0);
81
+
82
+ const payload = {
83
+ name,
84
+ description,
85
+ config: {
86
+ min_length: Number(minLen),
87
+ pii_enabled: enablePii,
88
+ regex_patterns: regexList,
89
+ profanity_enabled: false
90
+ }
91
+ };
92
+
93
+ const result = await gClient.create(payload);
94
+ s.stop(green(`Guardrail "${result.name}" created successfully!`));
95
+ console.log(gray(`ID: ${result.id}`));
96
+
97
+ } catch (e: any) {
98
+ s.stop(red(`Failed to create guardrail: ${e.message}`));
99
+ }
100
+ }