@indemn/cli 1.0.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/.claude-plugin/plugin.json +13 -0
- package/.mcp.json +9 -0
- package/README.md +225 -0
- package/dist/cli/agents.d.ts +3 -0
- package/dist/cli/agents.d.ts.map +1 -0
- package/dist/cli/agents.js +187 -0
- package/dist/cli/agents.js.map +1 -0
- package/dist/cli/auth.d.ts +3 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +161 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/chat.d.ts +3 -0
- package/dist/cli/chat.d.ts.map +1 -0
- package/dist/cli/chat.js +142 -0
- package/dist/cli/chat.js.map +1 -0
- package/dist/cli/config.d.ts +3 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +119 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/eval.d.ts +3 -0
- package/dist/cli/eval.d.ts.map +1 -0
- package/dist/cli/eval.js +717 -0
- package/dist/cli/eval.js.map +1 -0
- package/dist/cli/functions.d.ts +3 -0
- package/dist/cli/functions.d.ts.map +1 -0
- package/dist/cli/functions.js +346 -0
- package/dist/cli/functions.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +64 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/kb.d.ts +3 -0
- package/dist/cli/kb.d.ts.map +1 -0
- package/dist/cli/kb.js +377 -0
- package/dist/cli/kb.js.map +1 -0
- package/dist/cli/utils/output.d.ts +6 -0
- package/dist/cli/utils/output.d.ts.map +1 -0
- package/dist/cli/utils/output.js +36 -0
- package/dist/cli/utils/output.js.map +1 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +15 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +4 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +814 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/sdk/agents.d.ts +13 -0
- package/dist/sdk/agents.d.ts.map +1 -0
- package/dist/sdk/agents.js +27 -0
- package/dist/sdk/agents.js.map +1 -0
- package/dist/sdk/auth.d.ts +18 -0
- package/dist/sdk/auth.d.ts.map +1 -0
- package/dist/sdk/auth.js +98 -0
- package/dist/sdk/auth.js.map +1 -0
- package/dist/sdk/chat.d.ts +17 -0
- package/dist/sdk/chat.d.ts.map +1 -0
- package/dist/sdk/chat.js +113 -0
- package/dist/sdk/chat.js.map +1 -0
- package/dist/sdk/client.d.ts +36 -0
- package/dist/sdk/client.d.ts.map +1 -0
- package/dist/sdk/client.js +219 -0
- package/dist/sdk/client.js.map +1 -0
- package/dist/sdk/config.d.ts +11 -0
- package/dist/sdk/config.d.ts.map +1 -0
- package/dist/sdk/config.js +20 -0
- package/dist/sdk/config.js.map +1 -0
- package/dist/sdk/eval.d.ts +13 -0
- package/dist/sdk/eval.d.ts.map +1 -0
- package/dist/sdk/eval.js +36 -0
- package/dist/sdk/eval.js.map +1 -0
- package/dist/sdk/functions.d.ts +30 -0
- package/dist/sdk/functions.d.ts.map +1 -0
- package/dist/sdk/functions.js +59 -0
- package/dist/sdk/functions.js.map +1 -0
- package/dist/sdk/index.d.ts +12 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +12 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/sdk/kb.d.ts +30 -0
- package/dist/sdk/kb.d.ts.map +1 -0
- package/dist/sdk/kb.js +102 -0
- package/dist/sdk/kb.js.map +1 -0
- package/dist/sdk/rubrics.d.ts +14 -0
- package/dist/sdk/rubrics.d.ts.map +1 -0
- package/dist/sdk/rubrics.js +29 -0
- package/dist/sdk/rubrics.js.map +1 -0
- package/dist/sdk/test-sets.d.ts +14 -0
- package/dist/sdk/test-sets.d.ts.map +1 -0
- package/dist/sdk/test-sets.js +29 -0
- package/dist/sdk/test-sets.js.map +1 -0
- package/dist/sdk/types.d.ts +305 -0
- package/dist/sdk/types.d.ts.map +1 -0
- package/dist/sdk/types.js +3 -0
- package/dist/sdk/types.js.map +1 -0
- package/package.json +40 -0
- package/skills/agent-setup/SKILL.md +131 -0
- package/skills/eval-orchestration/SKILL.md +257 -0
- package/skills/rubric-creation/SKILL.md +163 -0
- package/skills/test-set-creation/SKILL.md +250 -0
- package/src/cli/agents.ts +206 -0
- package/src/cli/auth.ts +170 -0
- package/src/cli/chat.ts +161 -0
- package/src/cli/config.ts +131 -0
- package/src/cli/eval.ts +750 -0
- package/src/cli/functions.ts +330 -0
- package/src/cli/index.ts +68 -0
- package/src/cli/kb.ts +412 -0
- package/src/cli/utils/output.ts +42 -0
- package/src/mcp/server.ts +17 -0
- package/src/mcp/tools.ts +971 -0
- package/src/sdk/agents.ts +47 -0
- package/src/sdk/auth.ts +132 -0
- package/src/sdk/chat.ts +133 -0
- package/src/sdk/client.ts +238 -0
- package/src/sdk/config.ts +28 -0
- package/src/sdk/eval.ts +57 -0
- package/src/sdk/functions.ts +103 -0
- package/src/sdk/index.ts +11 -0
- package/src/sdk/kb.ts +183 -0
- package/src/sdk/rubrics.ts +35 -0
- package/src/sdk/test-sets.ts +35 -0
- package/src/sdk/types.ts +347 -0
- package/test/fixtures/test-rubric.json +66 -0
- package/test/fixtures/test-testset.json +55 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { IndemnClient } from './client.js';
|
|
2
|
+
import type { Agent, CreateAgentInput, UpdateAgentInput } from './types.js';
|
|
3
|
+
|
|
4
|
+
export class AgentsSDK {
|
|
5
|
+
constructor(private client: IndemnClient) {}
|
|
6
|
+
|
|
7
|
+
async listAgents(): Promise<Agent[]> {
|
|
8
|
+
return this.client.copilotGet<Agent[]>(
|
|
9
|
+
'/organization/:org_id/ai-studio/v2/bots',
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async getAgent(id: string): Promise<Agent> {
|
|
14
|
+
const result = await this.client.copilotGet<Agent | Agent[]>(
|
|
15
|
+
`/organization/:org_id/ai-studio/v2/bots/${id}`,
|
|
16
|
+
);
|
|
17
|
+
// v2 endpoint returns array even for single bot
|
|
18
|
+
return Array.isArray(result) ? result[0] : result;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async createAgent(input: CreateAgentInput): Promise<Agent> {
|
|
22
|
+
return this.client.copilotPost<Agent>(
|
|
23
|
+
'/organization/:org_id/ai-studio/bots',
|
|
24
|
+
{ name: input.name, channels: input.channels || ['WEB'] },
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async updateAgent(id: string, input: UpdateAgentInput): Promise<Agent> {
|
|
29
|
+
return this.client.copilotPut<Agent>(
|
|
30
|
+
`/organization/:org_id/ai-studio/bots/${id}`,
|
|
31
|
+
input,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async deleteAgent(id: string): Promise<void> {
|
|
36
|
+
await this.client.copilotDelete(
|
|
37
|
+
`/organization/:org_id/ai-studio/bots/${id}`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async cloneAgent(id: string): Promise<Agent> {
|
|
42
|
+
return this.client.copilotPost<Agent>(
|
|
43
|
+
'/organization/:org_id/ai-studio/v2/bots/clone',
|
|
44
|
+
{ source_id_bot: id },
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/sdk/auth.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { initializeApp, getApp } from 'firebase/app';
|
|
2
|
+
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';
|
|
3
|
+
import { IndemnClient } from './client.js';
|
|
4
|
+
import type { Environment, EnvironmentHosts } from './types.js';
|
|
5
|
+
|
|
6
|
+
// Firebase client configs per environment — public configuration (not secrets),
|
|
7
|
+
// bundled the same way the Copilot Dashboard bundles them via envsubst
|
|
8
|
+
const FIREBASE_CONFIGS: Record<Environment, Record<string, string>> = {
|
|
9
|
+
dev: {
|
|
10
|
+
apiKey: 'AIzaSyB8bA3w8oaMbyMxYA7lyQy_OvrPcPfW7x8',
|
|
11
|
+
authDomain: 'agent-449107.firebaseapp.com',
|
|
12
|
+
projectId: 'gmail-agent-449107',
|
|
13
|
+
storageBucket: 'gmail-agent-449107.firebasestorage.app',
|
|
14
|
+
messagingSenderId: '1066107427076',
|
|
15
|
+
appId: '1:1066107427076:web:f57925047f40a6a76a5851',
|
|
16
|
+
},
|
|
17
|
+
production: {
|
|
18
|
+
apiKey: 'AIzaSyBkj3ny3Gm7DapoeiTi3wt_9eG8hnlqqyc',
|
|
19
|
+
authDomain: 'prod-gemini-470505.firebaseapp.com',
|
|
20
|
+
projectId: 'prod-gemini-470505',
|
|
21
|
+
storageBucket: 'prod-gemini-470505.firebasestorage.app',
|
|
22
|
+
messagingSenderId: '334963688118',
|
|
23
|
+
appId: '1:334963688118:web:21670b3a97547d4b231c17',
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export async function login(email: string, password: string, env: Environment = 'dev', opts?: {
|
|
28
|
+
mfaCodePrompt?: () => Promise<string>;
|
|
29
|
+
}): Promise<{
|
|
30
|
+
apiKey: string;
|
|
31
|
+
orgId: string;
|
|
32
|
+
userId: string;
|
|
33
|
+
environment: Environment;
|
|
34
|
+
}> {
|
|
35
|
+
// Step 1: Firebase client SDK auth (environment-specific config, separate app per env)
|
|
36
|
+
const firebaseConfig = FIREBASE_CONFIGS[env];
|
|
37
|
+
const appName = `indemn-cli-${env}`;
|
|
38
|
+
let app;
|
|
39
|
+
try { app = getApp(appName); } catch { app = initializeApp(firebaseConfig, appName); }
|
|
40
|
+
const auth = getAuth(app);
|
|
41
|
+
const userCredential = await signInWithEmailAndPassword(auth, email, password);
|
|
42
|
+
const idToken = await userCredential.user.getIdToken();
|
|
43
|
+
|
|
44
|
+
// Step 2: Exchange Firebase ID token for Copilot Server JWT
|
|
45
|
+
const client = new IndemnClient({ environment: env });
|
|
46
|
+
const signinResponse = await client.copilotPostUnauth<{
|
|
47
|
+
success: boolean;
|
|
48
|
+
requiresMfa: boolean;
|
|
49
|
+
mfaSessionId?: string;
|
|
50
|
+
mfaEmail?: string;
|
|
51
|
+
token?: string;
|
|
52
|
+
user?: { _id: string; email: string };
|
|
53
|
+
}>('/auth/firebase/signin', { idToken });
|
|
54
|
+
|
|
55
|
+
let jwt: string;
|
|
56
|
+
let userId: string;
|
|
57
|
+
|
|
58
|
+
if (signinResponse.requiresMfa) {
|
|
59
|
+
// MFA required — prompt for code and verify
|
|
60
|
+
if (!opts?.mfaCodePrompt) {
|
|
61
|
+
throw new Error('MFA is required but no code prompt handler was provided.');
|
|
62
|
+
}
|
|
63
|
+
const code = await opts.mfaCodePrompt();
|
|
64
|
+
const mfaResponse = await client.copilotPostUnauth<{
|
|
65
|
+
success: boolean;
|
|
66
|
+
token: string;
|
|
67
|
+
user: { _id: string; email: string };
|
|
68
|
+
}>('/auth/firebase/mfa/verify', {
|
|
69
|
+
sessionId: signinResponse.mfaSessionId,
|
|
70
|
+
code,
|
|
71
|
+
});
|
|
72
|
+
jwt = mfaResponse.token.replace(/^JWT\s+/i, '');
|
|
73
|
+
userId = mfaResponse.user._id;
|
|
74
|
+
} else {
|
|
75
|
+
// No MFA — direct JWT
|
|
76
|
+
jwt = signinResponse.token!.replace(/^JWT\s+/i, '');
|
|
77
|
+
userId = signinResponse.user!._id;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Step 3: Generate persistent API key using the JWT
|
|
81
|
+
const apiKeyResponse = await client.copilotPostUnauth<{
|
|
82
|
+
_id: string;
|
|
83
|
+
key: string;
|
|
84
|
+
name: string;
|
|
85
|
+
org_id: string;
|
|
86
|
+
}>('/auth/api-keys', { name: `indemn-cli-${Date.now()}` }, {
|
|
87
|
+
'Authorization': `jwt ${jwt}`,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (!apiKeyResponse.org_id) {
|
|
91
|
+
throw new Error('Login succeeded but no organization found. Contact your admin to be added to an organization.');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const config = {
|
|
95
|
+
api_key: apiKeyResponse.key,
|
|
96
|
+
org_id: apiKeyResponse.org_id,
|
|
97
|
+
user_id: userId,
|
|
98
|
+
environment: env,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Step 4: Save to config file
|
|
102
|
+
IndemnClient.saveConfig(config);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
apiKey: config.api_key,
|
|
106
|
+
orgId: config.org_id,
|
|
107
|
+
userId: config.user_id,
|
|
108
|
+
environment: env,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function logout(): void {
|
|
113
|
+
IndemnClient.clearConfig();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function whoami(): {
|
|
117
|
+
apiKey: string;
|
|
118
|
+
orgId: string;
|
|
119
|
+
userId: string;
|
|
120
|
+
environment: Environment;
|
|
121
|
+
hosts: EnvironmentHosts;
|
|
122
|
+
} | null {
|
|
123
|
+
const config = IndemnClient.loadConfig();
|
|
124
|
+
if (!config || !config.api_key) return null;
|
|
125
|
+
return {
|
|
126
|
+
apiKey: config.api_key.substring(0, 12) + '...',
|
|
127
|
+
orgId: config.org_id,
|
|
128
|
+
userId: config.user_id,
|
|
129
|
+
environment: config.environment,
|
|
130
|
+
hosts: config.hosts[config.environment],
|
|
131
|
+
};
|
|
132
|
+
}
|
package/src/sdk/chat.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { io, Socket } from 'socket.io-client';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import type { ChatMessage, SessionRequest, UserMessage } from './types.js';
|
|
4
|
+
|
|
5
|
+
export class ChatSession extends EventEmitter {
|
|
6
|
+
private socket: Socket | null = null;
|
|
7
|
+
private sessionId: string | null = null;
|
|
8
|
+
private userId: string;
|
|
9
|
+
private botType: string;
|
|
10
|
+
private middlewareUrl: string;
|
|
11
|
+
private connected = false;
|
|
12
|
+
private streamBuffers: Map<string, string> = new Map();
|
|
13
|
+
|
|
14
|
+
constructor(botType: string, middlewareUrl: string, sessionId?: string) {
|
|
15
|
+
super();
|
|
16
|
+
this.botType = botType;
|
|
17
|
+
this.middlewareUrl = middlewareUrl;
|
|
18
|
+
this.userId = `cli-user-${Date.now()}`;
|
|
19
|
+
this.sessionId = sessionId || null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async connect(): Promise<string> {
|
|
23
|
+
return new Promise<string>((resolve, reject) => {
|
|
24
|
+
const timeout = setTimeout(() => {
|
|
25
|
+
reject(new Error('Connection timed out after 10s'));
|
|
26
|
+
this.disconnect();
|
|
27
|
+
}, 10000);
|
|
28
|
+
|
|
29
|
+
this.socket = io(this.middlewareUrl, {
|
|
30
|
+
transports: ['websocket'],
|
|
31
|
+
reconnection: true,
|
|
32
|
+
reconnectionAttempts: 3,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Handle stream_bot_uttered events before session_confirm (greeting streams first)
|
|
36
|
+
this.socket.on('stream_bot_uttered', (data: ChatMessage) => {
|
|
37
|
+
this.handleStreamMessage(data);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
this.socket.on('connect', () => {
|
|
41
|
+
const request: SessionRequest = {
|
|
42
|
+
session_id: this.sessionId,
|
|
43
|
+
bot_type: this.botType,
|
|
44
|
+
user_id: this.userId,
|
|
45
|
+
customData: {},
|
|
46
|
+
};
|
|
47
|
+
this.socket!.emit('session_request', request);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
this.socket.on('session_confirm', (data: { session_id: string }) => {
|
|
51
|
+
clearTimeout(timeout);
|
|
52
|
+
this.sessionId = data.session_id;
|
|
53
|
+
this.connected = true;
|
|
54
|
+
this.emit('connected', this.sessionId);
|
|
55
|
+
resolve(this.sessionId);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
this.socket.on('bot_uttered', (data: ChatMessage) => {
|
|
59
|
+
if (data.text) {
|
|
60
|
+
this.emit('message', data.text);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
this.socket.on('connect_error', (err: Error) => {
|
|
65
|
+
clearTimeout(timeout);
|
|
66
|
+
reject(new Error(`Connection failed: ${err.message}`));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
this.socket.on('disconnect', (reason: string) => {
|
|
70
|
+
this.connected = false;
|
|
71
|
+
this.emit('disconnected', reason);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private handleStreamMessage(data: ChatMessage): void {
|
|
77
|
+
if (!data.streamId) return;
|
|
78
|
+
|
|
79
|
+
const streamId = data.streamId;
|
|
80
|
+
|
|
81
|
+
if (data.isFirstChunk) {
|
|
82
|
+
this.streamBuffers.set(streamId, '');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (data.text) {
|
|
86
|
+
const current = this.streamBuffers.get(streamId) || '';
|
|
87
|
+
this.streamBuffers.set(streamId, current + data.text);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.emit('stream', data);
|
|
91
|
+
|
|
92
|
+
if (data.streamFinished) {
|
|
93
|
+
const fullText = this.streamBuffers.get(streamId) || '';
|
|
94
|
+
this.streamBuffers.delete(streamId);
|
|
95
|
+
if (fullText) {
|
|
96
|
+
this.emit('message', fullText);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
send(message: string): void {
|
|
102
|
+
if (!this.socket || !this.connected || !this.sessionId) {
|
|
103
|
+
throw new Error('Not connected. Call connect() first.');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const payload: UserMessage = {
|
|
107
|
+
message,
|
|
108
|
+
messageType: 'text',
|
|
109
|
+
bot_type: this.botType,
|
|
110
|
+
user_id: this.userId,
|
|
111
|
+
session_id: this.sessionId,
|
|
112
|
+
is_user_message_sent: true,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
this.socket.emit('user_uttered', payload);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
disconnect(): void {
|
|
119
|
+
if (this.socket) {
|
|
120
|
+
if (this.sessionId) {
|
|
121
|
+
this.socket.emit('session_closed', { session_id: this.sessionId });
|
|
122
|
+
}
|
|
123
|
+
this.socket.disconnect();
|
|
124
|
+
this.socket = null;
|
|
125
|
+
}
|
|
126
|
+
this.connected = false;
|
|
127
|
+
this.sessionId = null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
getSessionId(): string | null {
|
|
131
|
+
return this.sessionId;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
import type { ApiResponse, Environment, EnvironmentHosts, IndemnConfig } from './types.js';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_HOSTS: Record<Environment, EnvironmentHosts> = {
|
|
5
|
+
dev: {
|
|
6
|
+
copilot: 'https://devcopilot.indemn.ai/api',
|
|
7
|
+
evaluations: 'https://devevaluations.indemn.ai',
|
|
8
|
+
middleware: 'https://devproxy.indemn.ai',
|
|
9
|
+
},
|
|
10
|
+
production: {
|
|
11
|
+
copilot: 'https://copilot.indemn.ai/api',
|
|
12
|
+
evaluations: 'https://evaluations.indemn.ai',
|
|
13
|
+
middleware: 'https://proxy.indemn.ai',
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export class IndemnClient {
|
|
18
|
+
private apiKey: string;
|
|
19
|
+
private orgId: string;
|
|
20
|
+
private environment: Environment;
|
|
21
|
+
private hosts: EnvironmentHosts;
|
|
22
|
+
|
|
23
|
+
constructor(opts?: { apiKey?: string; orgId?: string; environment?: Environment }) {
|
|
24
|
+
// Priority: explicit opts > env vars > config file
|
|
25
|
+
const config = IndemnClient.loadConfig();
|
|
26
|
+
|
|
27
|
+
this.apiKey = opts?.apiKey || process.env.INDEMN_API_KEY || config?.api_key || '';
|
|
28
|
+
this.orgId = opts?.orgId || process.env.INDEMN_ORG_ID || config?.org_id || '';
|
|
29
|
+
this.environment = opts?.environment || (process.env.INDEMN_ENV as Environment) || config?.environment || 'dev';
|
|
30
|
+
const defaultHosts = DEFAULT_HOSTS[this.environment];
|
|
31
|
+
const configHosts = config?.hosts?.[this.environment];
|
|
32
|
+
this.hosts = {
|
|
33
|
+
copilot: process.env.INDEMN_COPILOT_URL || configHosts?.copilot || defaultHosts.copilot,
|
|
34
|
+
evaluations: process.env.INDEMN_EVALS_URL || configHosts?.evaluations || defaultHosts.evaluations,
|
|
35
|
+
middleware: process.env.INDEMN_MIDDLEWARE_URL || configHosts?.middleware || defaultHosts.middleware,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static getConfigStore(): Conf<IndemnConfig> {
|
|
40
|
+
return new Conf<IndemnConfig>({
|
|
41
|
+
projectName: 'indemn',
|
|
42
|
+
configName: 'config',
|
|
43
|
+
cwd: `${process.env.HOME}/.indemn`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static loadConfig(): IndemnConfig | null {
|
|
48
|
+
try {
|
|
49
|
+
const store = IndemnClient.getConfigStore();
|
|
50
|
+
const apiKey = store.get('api_key');
|
|
51
|
+
if (!apiKey) return null;
|
|
52
|
+
return {
|
|
53
|
+
api_key: apiKey,
|
|
54
|
+
org_id: store.get('org_id', ''),
|
|
55
|
+
user_id: store.get('user_id', ''),
|
|
56
|
+
environment: store.get('environment', 'dev'),
|
|
57
|
+
hosts: store.get('hosts', DEFAULT_HOSTS),
|
|
58
|
+
};
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static saveConfig(config: Partial<IndemnConfig>): void {
|
|
65
|
+
const store = IndemnClient.getConfigStore();
|
|
66
|
+
for (const [key, value] of Object.entries(config)) {
|
|
67
|
+
store.set(key as keyof IndemnConfig, value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static clearConfig(): void {
|
|
72
|
+
const store = IndemnClient.getConfigStore();
|
|
73
|
+
store.clear();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getEnvironment(): Environment { return this.environment; }
|
|
77
|
+
getOrgId(): string { return this.orgId; }
|
|
78
|
+
getApiKey(): string { return this.apiKey; }
|
|
79
|
+
getHosts(): EnvironmentHosts { return this.hosts; }
|
|
80
|
+
|
|
81
|
+
private ensureAuth(): void {
|
|
82
|
+
if (!this.apiKey) {
|
|
83
|
+
throw new Error('Not authenticated. Run `indemn login` first.');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private buildCopilotUrl(path: string): string {
|
|
88
|
+
// Inject org_id into path templates
|
|
89
|
+
const resolvedPath = path.replace(':org_id', this.orgId);
|
|
90
|
+
return `${this.hosts.copilot}${resolvedPath}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private buildEvalsUrl(path: string): string {
|
|
94
|
+
return `${this.hosts.evaluations}${path}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Copilot Server requests (with /api/ prefix and org_id injection)
|
|
98
|
+
async copilotGet<T = unknown>(path: string, query?: Record<string, string>): Promise<T> {
|
|
99
|
+
this.ensureAuth();
|
|
100
|
+
const url = new URL(this.buildCopilotUrl(path));
|
|
101
|
+
if (query) {
|
|
102
|
+
for (const [k, v] of Object.entries(query)) {
|
|
103
|
+
if (v !== undefined && v !== null) url.searchParams.set(k, v);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const res = await fetch(url.toString(), {
|
|
107
|
+
headers: {
|
|
108
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
109
|
+
'Content-Type': 'application/json',
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
return this.handleCopilotResponse<T>(res);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async copilotPost<T = unknown>(path: string, body?: unknown): Promise<T> {
|
|
116
|
+
this.ensureAuth();
|
|
117
|
+
const res = await fetch(this.buildCopilotUrl(path), {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: {
|
|
120
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
121
|
+
'Content-Type': 'application/json',
|
|
122
|
+
},
|
|
123
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
124
|
+
});
|
|
125
|
+
return this.handleCopilotResponse<T>(res);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async copilotPut<T = unknown>(path: string, body?: unknown): Promise<T> {
|
|
129
|
+
this.ensureAuth();
|
|
130
|
+
const res = await fetch(this.buildCopilotUrl(path), {
|
|
131
|
+
method: 'PUT',
|
|
132
|
+
headers: {
|
|
133
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
134
|
+
'Content-Type': 'application/json',
|
|
135
|
+
},
|
|
136
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
137
|
+
});
|
|
138
|
+
return this.handleCopilotResponse<T>(res);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async copilotDelete<T = unknown>(path: string): Promise<T> {
|
|
142
|
+
this.ensureAuth();
|
|
143
|
+
const res = await fetch(this.buildCopilotUrl(path), {
|
|
144
|
+
method: 'DELETE',
|
|
145
|
+
headers: {
|
|
146
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
147
|
+
'Content-Type': 'application/json',
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
return this.handleCopilotResponse<T>(res);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Evaluations Service requests (no /api/ prefix, no org_id, no auth in Phase 1)
|
|
154
|
+
async evalsGet<T = unknown>(path: string, query?: Record<string, string>): Promise<T> {
|
|
155
|
+
const url = new URL(this.buildEvalsUrl(path));
|
|
156
|
+
if (query) {
|
|
157
|
+
for (const [k, v] of Object.entries(query)) {
|
|
158
|
+
if (v !== undefined && v !== null) url.searchParams.set(k, v);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const res = await fetch(url.toString(), {
|
|
162
|
+
headers: { 'Content-Type': 'application/json' },
|
|
163
|
+
});
|
|
164
|
+
return this.handleEvalsResponse<T>(res);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async evalsPost<T = unknown>(path: string, body?: unknown): Promise<T> {
|
|
168
|
+
const res = await fetch(this.buildEvalsUrl(path), {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
headers: { 'Content-Type': 'application/json' },
|
|
171
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
172
|
+
});
|
|
173
|
+
return this.handleEvalsResponse<T>(res);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async evalsPut<T = unknown>(path: string, body?: unknown): Promise<T> {
|
|
177
|
+
const res = await fetch(this.buildEvalsUrl(path), {
|
|
178
|
+
method: 'PUT',
|
|
179
|
+
headers: { 'Content-Type': 'application/json' },
|
|
180
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
181
|
+
});
|
|
182
|
+
return this.handleEvalsResponse<T>(res);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async evalsDelete<T = unknown>(path: string): Promise<T> {
|
|
186
|
+
const res = await fetch(this.buildEvalsUrl(path), {
|
|
187
|
+
method: 'DELETE',
|
|
188
|
+
headers: { 'Content-Type': 'application/json' },
|
|
189
|
+
});
|
|
190
|
+
return this.handleEvalsResponse<T>(res);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Unauthenticated Copilot Server requests (for login flow)
|
|
194
|
+
async copilotPostUnauth<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T> {
|
|
195
|
+
const res = await fetch(`${this.hosts.copilot}${path}`, {
|
|
196
|
+
method: 'POST',
|
|
197
|
+
headers: {
|
|
198
|
+
'Content-Type': 'application/json',
|
|
199
|
+
...headers,
|
|
200
|
+
},
|
|
201
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
202
|
+
});
|
|
203
|
+
return this.handleCopilotResponse<T>(res);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private async handleCopilotResponse<T>(res: Response): Promise<T> {
|
|
207
|
+
if (res.status === 401) {
|
|
208
|
+
throw new Error('Authentication failed. Run `indemn login` to re-authenticate.');
|
|
209
|
+
}
|
|
210
|
+
if (res.status === 403) {
|
|
211
|
+
throw new Error('Access denied. You may not have permission for this operation.');
|
|
212
|
+
}
|
|
213
|
+
if (res.status === 404) {
|
|
214
|
+
throw new Error('Resource not found.');
|
|
215
|
+
}
|
|
216
|
+
if (!res.ok) {
|
|
217
|
+
const text = await res.text();
|
|
218
|
+
throw new Error(`API error (${res.status}): ${text}`);
|
|
219
|
+
}
|
|
220
|
+
const json = await res.json() as ApiResponse<T>;
|
|
221
|
+
// Copilot Server wraps responses in { success, data }
|
|
222
|
+
if (json.success !== undefined) {
|
|
223
|
+
if (!json.success) {
|
|
224
|
+
throw new Error(json.msg || 'API request failed');
|
|
225
|
+
}
|
|
226
|
+
return (json.data !== undefined ? json.data : json) as T;
|
|
227
|
+
}
|
|
228
|
+
return json as T;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private async handleEvalsResponse<T>(res: Response): Promise<T> {
|
|
232
|
+
if (!res.ok) {
|
|
233
|
+
const text = await res.text();
|
|
234
|
+
throw new Error(`Evaluations API error (${res.status}): ${text}`);
|
|
235
|
+
}
|
|
236
|
+
return res.json() as Promise<T>;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { IndemnClient } from './client.js';
|
|
2
|
+
import type { BotConfiguration, SetConfigInput } from './types.js';
|
|
3
|
+
|
|
4
|
+
export class ConfigSDK {
|
|
5
|
+
constructor(private client: IndemnClient) {}
|
|
6
|
+
|
|
7
|
+
async getConfig(agentId: string): Promise<BotConfiguration> {
|
|
8
|
+
return this.client.copilotGet<BotConfiguration>(
|
|
9
|
+
`/organization/:org_id/ai-studio/bots/${agentId}/configurations`,
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async setConfig(agentId: string, data: SetConfigInput): Promise<BotConfiguration> {
|
|
14
|
+
return this.client.copilotPut<BotConfiguration>(
|
|
15
|
+
`/organization/:org_id/ai-studio/bots/${agentId}/configurations`,
|
|
16
|
+
data,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async getPrompt(agentId: string): Promise<string | undefined> {
|
|
21
|
+
const config = await this.getConfig(agentId);
|
|
22
|
+
return config.ai_config?.system_prompt;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async setPrompt(agentId: string, prompt: string): Promise<BotConfiguration> {
|
|
26
|
+
return this.setConfig(agentId, { system_prompt: prompt });
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/sdk/eval.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { IndemnClient } from './client.js';
|
|
2
|
+
import type {
|
|
3
|
+
EvaluationTriggerInput,
|
|
4
|
+
EvaluationTriggerResponse,
|
|
5
|
+
EvaluationRun,
|
|
6
|
+
EvaluationResult,
|
|
7
|
+
BotContext,
|
|
8
|
+
} from './types.js';
|
|
9
|
+
|
|
10
|
+
export class EvalSDK {
|
|
11
|
+
constructor(private client: IndemnClient) {}
|
|
12
|
+
|
|
13
|
+
async triggerEvaluation(input: EvaluationTriggerInput): Promise<EvaluationTriggerResponse> {
|
|
14
|
+
return this.client.evalsPost<EvaluationTriggerResponse>('/api/v1/evaluations', input);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async listRuns(agentId?: string): Promise<EvaluationRun[]> {
|
|
18
|
+
const query = agentId ? { agent_id: agentId } : undefined;
|
|
19
|
+
return this.client.evalsGet<EvaluationRun[]>('/api/v1/runs', query);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async getRunStatus(runId: string): Promise<EvaluationRun> {
|
|
23
|
+
return this.client.evalsGet<EvaluationRun>(`/api/v1/runs/${runId}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async getRunResults(runId: string): Promise<EvaluationResult[]> {
|
|
27
|
+
return this.client.evalsGet<EvaluationResult[]>(`/api/v1/runs/${runId}/results`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async getBotContext(botId: string): Promise<BotContext> {
|
|
31
|
+
return this.client.evalsGet<BotContext>(`/api/v1/bots/${botId}/context`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async pollRunUntilComplete(
|
|
35
|
+
runId: string,
|
|
36
|
+
intervalMs = 5000,
|
|
37
|
+
timeoutMs = 300000,
|
|
38
|
+
): Promise<EvaluationRun> {
|
|
39
|
+
const startTime = Date.now();
|
|
40
|
+
|
|
41
|
+
while (true) {
|
|
42
|
+
const run = await this.getRunStatus(runId);
|
|
43
|
+
|
|
44
|
+
if (run.status === 'completed' || run.status === 'failed') {
|
|
45
|
+
return run;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (Date.now() - startTime >= timeoutMs) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Evaluation run ${runId} did not complete within ${timeoutMs / 1000}s (last status: ${run.status})`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|