@pentatonic-ai/ai-agent-sdk 0.3.0-beta.3
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/LICENSE +21 -0
- package/README.md +401 -0
- package/bin/cli.js +371 -0
- package/build.js +23 -0
- package/dist/index.cjs +699 -0
- package/dist/index.js +677 -0
- package/dist/pentatonic_agent_events-0.2.0b1-py3-none-any.whl +0 -0
- package/dist/pentatonic_agent_events-0.2.0b1.tar.gz +0 -0
- package/dist/pentatonic_agent_events-0.3.0b1-py3-none-any.whl +0 -0
- package/dist/pentatonic_agent_events-0.3.0b1.tar.gz +0 -0
- package/dist/pentatonic_agent_events-0.3.0b2-py3-none-any.whl +0 -0
- package/dist/pentatonic_agent_events-0.3.0b2.tar.gz +0 -0
- package/dist/pentatonic_agent_events-0.3.0b3-py3-none-any.whl +0 -0
- package/dist/pentatonic_agent_events-0.3.0b3.tar.gz +0 -0
- package/package.json +64 -0
- package/src/client.js +60 -0
- package/src/index.js +4 -0
- package/src/normalizer.js +111 -0
- package/src/session.js +181 -0
- package/src/tracking.js +119 -0
- package/src/transport.js +48 -0
- package/src/wrapper.js +329 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createInterface } from "readline";
|
|
4
|
+
import { execFileSync } from "child_process";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_ENDPOINT = "https://api.pentatonic.com";
|
|
7
|
+
|
|
8
|
+
function parseArgs() {
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const flags = {};
|
|
11
|
+
for (let i = 0; i < args.length; i++) {
|
|
12
|
+
if (args[i] === "--endpoint" && args[i + 1]) {
|
|
13
|
+
flags.endpoint = args[i + 1];
|
|
14
|
+
i++;
|
|
15
|
+
} else if (args[i].startsWith("--endpoint=")) {
|
|
16
|
+
flags.endpoint = args[i].split("=")[1];
|
|
17
|
+
} else if (!args[i].startsWith("--")) {
|
|
18
|
+
flags.command = args[i];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return flags;
|
|
22
|
+
}
|
|
23
|
+
const POLL_INTERVAL_MS = 3000;
|
|
24
|
+
const POLL_TIMEOUT_MS = 300000; // 5 minutes
|
|
25
|
+
|
|
26
|
+
let rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
27
|
+
|
|
28
|
+
function ask(question) {
|
|
29
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function askSecret(question) {
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
// Close readline so it stops echoing input
|
|
35
|
+
rl.close();
|
|
36
|
+
|
|
37
|
+
process.stdout.write(question);
|
|
38
|
+
const stdin = process.stdin;
|
|
39
|
+
if (stdin.isTTY) stdin.setRawMode(true);
|
|
40
|
+
stdin.resume();
|
|
41
|
+
|
|
42
|
+
let input = "";
|
|
43
|
+
const onData = (ch) => {
|
|
44
|
+
const c = ch.toString();
|
|
45
|
+
if (c === "\n" || c === "\r") {
|
|
46
|
+
stdin.removeListener("data", onData);
|
|
47
|
+
if (stdin.isTTY) stdin.setRawMode(false);
|
|
48
|
+
stdin.pause();
|
|
49
|
+
process.stdout.write("\n");
|
|
50
|
+
// Recreate readline for subsequent prompts
|
|
51
|
+
rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
52
|
+
resolve(input);
|
|
53
|
+
} else if (c === "\u007f" || c === "\b") {
|
|
54
|
+
if (input.length > 0) {
|
|
55
|
+
input = input.slice(0, -1);
|
|
56
|
+
process.stdout.write("\b \b");
|
|
57
|
+
}
|
|
58
|
+
} else if (c === "\u0003") {
|
|
59
|
+
process.exit(1);
|
|
60
|
+
} else {
|
|
61
|
+
input += c;
|
|
62
|
+
process.stdout.write("*");
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
stdin.on("data", onData);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function askChoice(question, choices) {
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
const choiceStr = choices.map((c, i) => ` ${i + 1}) ${c}`).join("\n");
|
|
72
|
+
process.stdout.write(`${question}\n${choiceStr}\n`);
|
|
73
|
+
rl.question("? Choice: ", (answer) => {
|
|
74
|
+
const idx = parseInt(answer, 10) - 1;
|
|
75
|
+
if (idx >= 0 && idx < choices.length) {
|
|
76
|
+
resolve(choices[idx]);
|
|
77
|
+
} else {
|
|
78
|
+
const match = choices.find(
|
|
79
|
+
(c) => c.toLowerCase() === answer.trim().toLowerCase()
|
|
80
|
+
);
|
|
81
|
+
resolve(match || choices[0]);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function spinner(text) {
|
|
88
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
89
|
+
let i = 0;
|
|
90
|
+
const id = setInterval(() => {
|
|
91
|
+
process.stdout.write(`\r${frames[i++ % frames.length]} ${text}`);
|
|
92
|
+
}, 80);
|
|
93
|
+
return {
|
|
94
|
+
stop(result) {
|
|
95
|
+
clearInterval(id);
|
|
96
|
+
process.stdout.write(`\r✓ ${result}\n`);
|
|
97
|
+
},
|
|
98
|
+
fail(msg) {
|
|
99
|
+
clearInterval(id);
|
|
100
|
+
process.stdout.write(`\r✗ ${msg}\n`);
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function httpPost(url, body) {
|
|
106
|
+
const response = await fetch(url, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: { "Content-Type": "application/json" },
|
|
109
|
+
body: JSON.stringify(body),
|
|
110
|
+
});
|
|
111
|
+
const data = await response.json();
|
|
112
|
+
return { status: response.status, ok: response.ok, data };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function graphql(endpoint, token, query, variables) {
|
|
116
|
+
const response = await fetch(`${endpoint}/api/graphql`, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: {
|
|
119
|
+
"Content-Type": "application/json",
|
|
120
|
+
Authorization: `Bearer ${token}`,
|
|
121
|
+
},
|
|
122
|
+
body: JSON.stringify({ query, variables }),
|
|
123
|
+
});
|
|
124
|
+
const data = await response.json();
|
|
125
|
+
if (data.errors?.length) {
|
|
126
|
+
throw new Error(data.errors[0].message);
|
|
127
|
+
}
|
|
128
|
+
return data.data;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function toClientId(companyName) {
|
|
132
|
+
return companyName
|
|
133
|
+
.toLowerCase()
|
|
134
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
135
|
+
.replace(/^-|-$/g, "");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function main() {
|
|
139
|
+
const flags = parseArgs();
|
|
140
|
+
const TES_ENDPOINT = flags.endpoint || DEFAULT_ENDPOINT;
|
|
141
|
+
|
|
142
|
+
if (flags.command !== "init") {
|
|
143
|
+
console.log(`
|
|
144
|
+
@pentatonic-ai/ai-agent-sdk
|
|
145
|
+
|
|
146
|
+
Usage:
|
|
147
|
+
npx @pentatonic-ai/ai-agent-sdk init Set up account and install SDK
|
|
148
|
+
npx @pentatonic-ai/ai-agent-sdk init --endpoint URL Use a custom TES endpoint
|
|
149
|
+
|
|
150
|
+
For docs, see https://api.pentatonic.com
|
|
151
|
+
`);
|
|
152
|
+
process.exit(0);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const isLocal = /^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?(\/|$)/.test(TES_ENDPOINT);
|
|
156
|
+
if (!TES_ENDPOINT.startsWith("https://") && !isLocal) {
|
|
157
|
+
console.error(`\n Error: endpoint must use https:// (http:// is only allowed for localhost)\n`);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(`\n Welcome to Pentatonic AI Events SDK`);
|
|
162
|
+
if (TES_ENDPOINT !== DEFAULT_ENDPOINT) {
|
|
163
|
+
console.log(` Using endpoint: ${TES_ENDPOINT}`);
|
|
164
|
+
}
|
|
165
|
+
console.log("");
|
|
166
|
+
|
|
167
|
+
// Collect info
|
|
168
|
+
const email = await ask("? Email: ");
|
|
169
|
+
const clientId = toClientId(await ask("? Client ID: "));
|
|
170
|
+
const password = await askSecret("? Password: ");
|
|
171
|
+
const region = await askChoice("? Region:", ["EU", "US"]);
|
|
172
|
+
|
|
173
|
+
// Try login first — account may already be verified from a previous run
|
|
174
|
+
let accessToken = null;
|
|
175
|
+
const loginSpinner = spinner("Checking for existing account...");
|
|
176
|
+
try {
|
|
177
|
+
const { ok, data } = await httpPost(
|
|
178
|
+
`${TES_ENDPOINT}/api/enrollment/login`,
|
|
179
|
+
{ email, password, clientId }
|
|
180
|
+
);
|
|
181
|
+
if (ok && data.tokens?.accessToken) {
|
|
182
|
+
accessToken = data.tokens.accessToken;
|
|
183
|
+
loginSpinner.stop("Account already verified!");
|
|
184
|
+
} else {
|
|
185
|
+
loginSpinner.stop("No existing account found.");
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
loginSpinner.stop("No existing account found.");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// If not already verified, submit enrollment
|
|
192
|
+
if (!accessToken) {
|
|
193
|
+
const enrollSpinner = spinner("Creating account...");
|
|
194
|
+
try {
|
|
195
|
+
const { ok, data } = await httpPost(
|
|
196
|
+
`${TES_ENDPOINT}/api/enrollment/submit`,
|
|
197
|
+
{
|
|
198
|
+
clientId,
|
|
199
|
+
companyName: clientId,
|
|
200
|
+
industryType: "technology",
|
|
201
|
+
authProvider: "native",
|
|
202
|
+
adminEmail: email,
|
|
203
|
+
adminPassword: password,
|
|
204
|
+
region: region.toLowerCase(),
|
|
205
|
+
}
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
if (!ok) {
|
|
209
|
+
const errors = data.errors || {};
|
|
210
|
+
const isPending =
|
|
211
|
+
errors.clientId?.includes("already pending") ||
|
|
212
|
+
errors.adminEmail?.includes("already has a pending");
|
|
213
|
+
const isAlreadyRegistered =
|
|
214
|
+
errors.clientId?.includes("already registered");
|
|
215
|
+
|
|
216
|
+
if (isPending) {
|
|
217
|
+
enrollSpinner.stop("Enrollment already pending — waiting for verification.");
|
|
218
|
+
} else if (isAlreadyRegistered) {
|
|
219
|
+
enrollSpinner.fail(
|
|
220
|
+
"This client ID is already registered.\n" +
|
|
221
|
+
" If you belong to this organization, ask your admin to invite you.\n" +
|
|
222
|
+
" Then run this command again — it will log you in automatically."
|
|
223
|
+
);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
} else {
|
|
226
|
+
enrollSpinner.fail(
|
|
227
|
+
data.message || Object.values(errors).join(", ") || "Enrollment failed"
|
|
228
|
+
);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
enrollSpinner.stop("Account created! Check your email to verify.");
|
|
233
|
+
}
|
|
234
|
+
} catch (err) {
|
|
235
|
+
enrollSpinner.fail(`Failed to connect: ${err.message}`);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Poll for verification
|
|
240
|
+
console.log("\n Waiting for email verification...");
|
|
241
|
+
console.log(" (Check your inbox and click the verification link)\n");
|
|
242
|
+
|
|
243
|
+
const pollSpinner = spinner("Waiting for verification...");
|
|
244
|
+
const startTime = Date.now();
|
|
245
|
+
|
|
246
|
+
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
247
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
const { ok, data } = await httpPost(
|
|
251
|
+
`${TES_ENDPOINT}/api/enrollment/login`,
|
|
252
|
+
{ email, password, clientId }
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
if (ok && data.tokens?.accessToken) {
|
|
256
|
+
accessToken = data.tokens.accessToken;
|
|
257
|
+
pollSpinner.stop("Email verified!");
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
} catch {
|
|
261
|
+
// Not verified yet, keep polling
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!accessToken) {
|
|
266
|
+
pollSpinner.fail(
|
|
267
|
+
"Verification timed out. Run `npx @pentatonic-ai/ai-agent-sdk init` again — it will resume where you left off."
|
|
268
|
+
);
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Get API key — use the service token created during enrollment,
|
|
274
|
+
// or create a new one if not available (e.g., existing account login)
|
|
275
|
+
const keySpinner = spinner("Getting API key...");
|
|
276
|
+
try {
|
|
277
|
+
let apiKey;
|
|
278
|
+
|
|
279
|
+
// Try to retrieve the enrollment service token first (created during verification)
|
|
280
|
+
try {
|
|
281
|
+
const tokenRes = await fetch(
|
|
282
|
+
`${TES_ENDPOINT}/api/enrollment/service-token?client_id=${clientId}`
|
|
283
|
+
);
|
|
284
|
+
if (tokenRes.ok) {
|
|
285
|
+
const tokenData = await tokenRes.json();
|
|
286
|
+
if (tokenData.token) {
|
|
287
|
+
apiKey = tokenData.token;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
} catch {
|
|
291
|
+
// Service token not available, will create one
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Fallback: create a new token via GraphQL
|
|
295
|
+
if (!apiKey) {
|
|
296
|
+
const result = await graphql(
|
|
297
|
+
TES_ENDPOINT,
|
|
298
|
+
accessToken,
|
|
299
|
+
`mutation CreateApiToken($clientId: String!, $input: CreateApiTokenInput!) {
|
|
300
|
+
createClientApiToken(clientId: $clientId, input: $input) {
|
|
301
|
+
success
|
|
302
|
+
plainTextToken
|
|
303
|
+
}
|
|
304
|
+
}`,
|
|
305
|
+
{
|
|
306
|
+
clientId,
|
|
307
|
+
input: {
|
|
308
|
+
name: "ai-events-sdk",
|
|
309
|
+
role: "agent-events",
|
|
310
|
+
},
|
|
311
|
+
}
|
|
312
|
+
);
|
|
313
|
+
apiKey = result.createClientApiToken.plainTextToken;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
keySpinner.stop("API key ready!");
|
|
317
|
+
|
|
318
|
+
// Print credentials
|
|
319
|
+
const clientEndpoint =
|
|
320
|
+
TES_ENDPOINT === DEFAULT_ENDPOINT
|
|
321
|
+
? `https://${clientId}.api.pentatonic.com`
|
|
322
|
+
: TES_ENDPOINT;
|
|
323
|
+
|
|
324
|
+
console.log("\n Add these to your environment:\n");
|
|
325
|
+
console.log(` TES_ENDPOINT=${clientEndpoint}`);
|
|
326
|
+
console.log(` TES_CLIENT_ID=${clientId}`);
|
|
327
|
+
console.log(` TES_API_KEY=${apiKey}`);
|
|
328
|
+
console.log("");
|
|
329
|
+
|
|
330
|
+
// Install SDK
|
|
331
|
+
const installChoice = await askChoice("Install SDK:", [
|
|
332
|
+
"npm install @pentatonic-ai/ai-agent-sdk",
|
|
333
|
+
"pip install pentatonic-ai-agent-sdk",
|
|
334
|
+
"Skip — I'll install manually",
|
|
335
|
+
]);
|
|
336
|
+
|
|
337
|
+
if (installChoice.startsWith("npm")) {
|
|
338
|
+
const installSpinner = spinner("Installing @pentatonic-ai/ai-agent-sdk...");
|
|
339
|
+
try {
|
|
340
|
+
execFileSync("npm", ["install", "@pentatonic-ai/ai-agent-sdk"], { stdio: "pipe" });
|
|
341
|
+
installSpinner.stop("@pentatonic-ai/ai-agent-sdk installed!");
|
|
342
|
+
} catch {
|
|
343
|
+
installSpinner.fail("Install failed. Run manually: npm install @pentatonic-ai/ai-agent-sdk");
|
|
344
|
+
}
|
|
345
|
+
} else if (installChoice.startsWith("pip")) {
|
|
346
|
+
const installSpinner = spinner("Installing pentatonic-ai-agent-sdk...");
|
|
347
|
+
try {
|
|
348
|
+
execFileSync("pip", ["install", "pentatonic-ai-agent-sdk"], { stdio: "pipe" });
|
|
349
|
+
installSpinner.stop("pentatonic-ai-agent-sdk installed!");
|
|
350
|
+
} catch {
|
|
351
|
+
installSpinner.fail("Install failed. Run manually: pip install pentatonic-ai-agent-sdk");
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
console.log("\n Install later with:");
|
|
355
|
+
console.log(" npm install @pentatonic-ai/ai-agent-sdk");
|
|
356
|
+
console.log(" pip install pentatonic-ai-agent-sdk");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
console.log(" You're ready! See docs at https://api.pentatonic.com\n");
|
|
360
|
+
} catch (err) {
|
|
361
|
+
keySpinner.fail(`Failed to generate key: ${err.message}`);
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
rl.close();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
main().catch((err) => {
|
|
369
|
+
console.error(`\n Error: ${err.message}\n`);
|
|
370
|
+
process.exit(1);
|
|
371
|
+
});
|
package/build.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { build } from "esbuild";
|
|
2
|
+
|
|
3
|
+
// ESM
|
|
4
|
+
await build({
|
|
5
|
+
entryPoints: ["src/index.js"],
|
|
6
|
+
outfile: "dist/index.js",
|
|
7
|
+
format: "esm",
|
|
8
|
+
bundle: true,
|
|
9
|
+
platform: "neutral",
|
|
10
|
+
target: "es2020",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// CJS
|
|
14
|
+
await build({
|
|
15
|
+
entryPoints: ["src/index.js"],
|
|
16
|
+
outfile: "dist/index.cjs",
|
|
17
|
+
format: "cjs",
|
|
18
|
+
bundle: true,
|
|
19
|
+
platform: "neutral",
|
|
20
|
+
target: "es2020",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
console.log("Built dist/index.js (ESM) and dist/index.cjs (CJS)");
|