twelveai 0.3.1 → 0.4.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/README.md +37 -0
- package/dist/chunk-6RZKLIGP.js +248 -0
- package/dist/cli.cjs +471 -0
- package/dist/cli.d.cts +13 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +204 -0
- package/dist/client-C5vzks2N.d.cts +325 -0
- package/dist/client-C5vzks2N.d.ts +325 -0
- package/dist/index.cjs +85 -6
- package/dist/index.d.cts +2 -201
- package/dist/index.d.ts +2 -201
- package/dist/index.js +5 -166
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -91,6 +91,43 @@ if (res.pendingConfirmation) {
|
|
|
91
91
|
}
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
+
## Manage your workspace from code or the terminal
|
|
95
|
+
|
|
96
|
+
Everything the console does is also available programmatically (same API key):
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
await twelve.manage.plugins.install('bills')
|
|
100
|
+
await twelve.manage.agents.create({
|
|
101
|
+
intent: 'loan_request',
|
|
102
|
+
keywords: ['loan', 'borrow'],
|
|
103
|
+
systemPrompt: 'You help customers with loan questions. Call list_loans and answer only from what it returns.',
|
|
104
|
+
tools: ['list_loans'],
|
|
105
|
+
})
|
|
106
|
+
await twelve.manage.tools.create({
|
|
107
|
+
name: 'list_loans',
|
|
108
|
+
description: "List this customer's active loans.",
|
|
109
|
+
sideEffect: 'read',
|
|
110
|
+
method: 'GET',
|
|
111
|
+
url: 'https://api.yourbank.com/v1/customers/{customer_id}/loans',
|
|
112
|
+
sampleResponse: { status: 'success', data: { loans: [] } },
|
|
113
|
+
})
|
|
114
|
+
await twelve.manage.settings.update({ webhookUrl: 'https://api.yourapp.com/hooks' })
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Namespaces: `agents`, `tools`, `plugins`, `customers`, `settings`, `billing`, `usage`, plus `twelve.api(method, path, body?)` for anything else.
|
|
118
|
+
|
|
119
|
+
Or use the CLI (ships with this package):
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
export TWELVE_API_KEY=sk-...
|
|
123
|
+
npx -p twelveai twelveai plugins install bills
|
|
124
|
+
npx -p twelveai twelveai agents list
|
|
125
|
+
npx -p twelveai twelveai chat "what's my balance?" --customer cus_123 --sandbox
|
|
126
|
+
npx -p twelveai twelveai usage
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Run `twelveai help` for the full command list.
|
|
130
|
+
|
|
94
131
|
## Sandbox
|
|
95
132
|
|
|
96
133
|
Add `sandbox: true` and every tool returns realistic sample data - nothing real is called or moved. Perfect for CI and demos.
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// src/manage.ts
|
|
2
|
+
function buildManage(request) {
|
|
3
|
+
return {
|
|
4
|
+
agents: {
|
|
5
|
+
list: () => request("GET", "/v1/agents"),
|
|
6
|
+
create: (input) => request("POST", "/v1/agents", { ...input }),
|
|
7
|
+
update: (intent, patch) => request("PUT", "/v1/agents", { intent, ...patch }),
|
|
8
|
+
enable: (intent) => request("PUT", "/v1/agents", { intent, enabled: true }),
|
|
9
|
+
disable: (intent) => request("PUT", "/v1/agents", { intent, enabled: false }),
|
|
10
|
+
delete: (intent) => request("DELETE", `/v1/agents/${encodeURIComponent(intent)}`)
|
|
11
|
+
},
|
|
12
|
+
tools: {
|
|
13
|
+
list: () => request("GET", "/v1/tools"),
|
|
14
|
+
create: (input) => request("POST", "/v1/tools", { ...input }),
|
|
15
|
+
bind: (name, binding) => request("PUT", `/v1/tools/${encodeURIComponent(name)}`, { ...binding }),
|
|
16
|
+
test: (name) => request("POST", `/v1/tools/${encodeURIComponent(name)}/test`),
|
|
17
|
+
importOpenApi: (input) => request("POST", "/v1/tools/import", { ...input })
|
|
18
|
+
},
|
|
19
|
+
plugins: {
|
|
20
|
+
list: () => request("GET", "/v1/plugins"),
|
|
21
|
+
install: (id) => request("POST", `/v1/plugins/${encodeURIComponent(id)}/install`),
|
|
22
|
+
uninstall: (id) => request("POST", `/v1/plugins/${encodeURIComponent(id)}/uninstall`)
|
|
23
|
+
},
|
|
24
|
+
customers: {
|
|
25
|
+
list: (opts = {}) => request("GET", `/v1/customers?limit=${opts.limit ?? 100}${opts.range ? `&range=${opts.range}` : ""}`),
|
|
26
|
+
upsert: (input) => request("POST", "/v1/customers", { ...input }),
|
|
27
|
+
get: (externalId) => request("GET", `/v1/customers/${encodeURIComponent(externalId)}`)
|
|
28
|
+
},
|
|
29
|
+
settings: {
|
|
30
|
+
get: () => request("GET", "/v1/settings"),
|
|
31
|
+
update: (patch) => request("PUT", "/v1/settings", patch)
|
|
32
|
+
},
|
|
33
|
+
billing: {
|
|
34
|
+
get: () => request("GET", "/v1/billing"),
|
|
35
|
+
topup: (amountNgn, email) => request("POST", "/v1/billing/topup", { amount: amountNgn, ...email ? { email } : {} })
|
|
36
|
+
},
|
|
37
|
+
usage: {
|
|
38
|
+
summary: () => request("GET", "/v1/usage"),
|
|
39
|
+
events: (limit = 20) => request("GET", `/v1/usage/events?limit=${limit}`),
|
|
40
|
+
event: (id) => request("GET", `/v1/usage/events/${id}`),
|
|
41
|
+
analytics: (days = 30) => request("GET", `/v1/usage/analytics?days=${days}`)
|
|
42
|
+
},
|
|
43
|
+
manifest: () => request("GET", "/v1/manifest")
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/client.ts
|
|
48
|
+
var DEFAULT_BASE_URL = "https://ai.twelveai.app";
|
|
49
|
+
var TwelveAI = class {
|
|
50
|
+
apiKey;
|
|
51
|
+
baseUrl;
|
|
52
|
+
fetchImpl;
|
|
53
|
+
clientAuth;
|
|
54
|
+
maxHandoffRounds;
|
|
55
|
+
/**
|
|
56
|
+
* Everything the console can do, from code: agents, tools, plugins,
|
|
57
|
+
* customers, settings, billing, usage. `twelve.manage.plugins.install('bills')`.
|
|
58
|
+
*/
|
|
59
|
+
manage;
|
|
60
|
+
constructor(options) {
|
|
61
|
+
if (!options?.apiKey) throw new Error("TwelveAI: apiKey is required.");
|
|
62
|
+
this.apiKey = options.apiKey;
|
|
63
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
64
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
65
|
+
this.clientAuth = options.clientAuth;
|
|
66
|
+
this.maxHandoffRounds = options.maxHandoffRounds ?? 3;
|
|
67
|
+
if (typeof this.fetchImpl !== "function") {
|
|
68
|
+
throw new Error("TwelveAI: global fetch is unavailable - pass options.fetch (Node 18+ has fetch built in).");
|
|
69
|
+
}
|
|
70
|
+
this.manage = buildManage((method, path, body) => this.api(method, path, body));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Low-level authenticated call to any engine endpoint. Returns
|
|
74
|
+
* `{ ok, status, ...payload }`; never throws on HTTP errors.
|
|
75
|
+
*/
|
|
76
|
+
async api(method, path, body) {
|
|
77
|
+
try {
|
|
78
|
+
const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
79
|
+
method,
|
|
80
|
+
headers: { "content-type": "application/json", "x-api-key": this.apiKey },
|
|
81
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
82
|
+
});
|
|
83
|
+
const data = await res.json().catch(() => ({}));
|
|
84
|
+
return {
|
|
85
|
+
...data,
|
|
86
|
+
ok: res.ok && data.ok !== false,
|
|
87
|
+
status: res.status,
|
|
88
|
+
...res.ok ? {} : { error: data.error ?? `Engine returned ${res.status}` }
|
|
89
|
+
};
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return { ok: false, status: 0, error: error?.message || "Could not reach the engine." };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Send one chat turn and return the completed result. Read hand-offs are
|
|
96
|
+
* auto-executed (see class docs); pass `autoExecute: false` to get the raw
|
|
97
|
+
* paused response instead, or `onHandoff` to execute them yourself.
|
|
98
|
+
*/
|
|
99
|
+
async chat(input, opts = {}) {
|
|
100
|
+
let res = await this.post("/v1/chat", this.chatBody(input));
|
|
101
|
+
if (opts.autoExecute === false) return res;
|
|
102
|
+
return this.completeHandoffs(res, input, opts.onHandoff);
|
|
103
|
+
}
|
|
104
|
+
/** Resume a paused turn with tool results your system produced. */
|
|
105
|
+
async resume(continuation, toolResults, input = {}) {
|
|
106
|
+
return this.post("/v1/chat", { ...this.chatBody(input), continuation, toolResults });
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Approve a pending action (after your PIN/OTP step) - resends the turn with
|
|
110
|
+
* `confirmed: true` so the engine proceeds.
|
|
111
|
+
*/
|
|
112
|
+
async confirm(continuation, input = {}) {
|
|
113
|
+
return this.chat({ message: "yes", ...input, continuation, confirmed: true });
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Level-0 integration: classify a message WITHOUT running the conversation.
|
|
117
|
+
* Returns the intent, a deterministic confidence score, and cheap extracted
|
|
118
|
+
* entities (amount / account number / phone) - you keep your existing flows
|
|
119
|
+
* and make the call yourself. Free: no tools run, nothing is stored.
|
|
120
|
+
*/
|
|
121
|
+
async classify(input) {
|
|
122
|
+
try {
|
|
123
|
+
const res = await this.fetchImpl(`${this.baseUrl}/v1/classify`, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: { "content-type": "application/json", "x-api-key": this.apiKey },
|
|
126
|
+
body: JSON.stringify({ message: input.message, ...input.reasoning ? { reasoning: input.reasoning } : {} })
|
|
127
|
+
});
|
|
128
|
+
const data = await res.json().catch(() => ({}));
|
|
129
|
+
return {
|
|
130
|
+
intent: null,
|
|
131
|
+
label: null,
|
|
132
|
+
confidence: 0,
|
|
133
|
+
alternatives: [],
|
|
134
|
+
entities: {},
|
|
135
|
+
...data,
|
|
136
|
+
// pass every field through (matchedKeywords, score, margin, classifyOnly, ...)
|
|
137
|
+
ok: res.ok && data.ok !== false,
|
|
138
|
+
status: res.status,
|
|
139
|
+
...res.ok ? {} : { error: data.error ?? `Engine returned ${res.status}` }
|
|
140
|
+
};
|
|
141
|
+
} catch (error) {
|
|
142
|
+
return { ok: false, intent: null, label: null, confidence: 0, alternatives: [], entities: {}, status: 0, error: error?.message || "Could not reach the engine." };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/* ------------------------------ internals ------------------------------ */
|
|
146
|
+
chatBody(input) {
|
|
147
|
+
const body = {};
|
|
148
|
+
if (input.message !== void 0) body.message = input.message;
|
|
149
|
+
if (input.customerId !== void 0) body.customerId = input.customerId;
|
|
150
|
+
if (input.continuation) body.continuation = input.continuation;
|
|
151
|
+
if (input.confirmed !== void 0) body.confirmed = input.confirmed;
|
|
152
|
+
if (input.sandbox !== void 0) body.sandbox = input.sandbox;
|
|
153
|
+
if (input.channel) body.channel = input.channel;
|
|
154
|
+
if (input.intent) body.intent = input.intent;
|
|
155
|
+
if (input.tier) body.tier = input.tier;
|
|
156
|
+
if (input.reasoning) body.reasoning = input.reasoning;
|
|
157
|
+
if (input.customerToken) body.customerToken = input.customerToken;
|
|
158
|
+
if (input.attachments?.length) body.attachments = input.attachments;
|
|
159
|
+
if (input.metadata) body.metadata = input.metadata;
|
|
160
|
+
return body;
|
|
161
|
+
}
|
|
162
|
+
async post(path, body) {
|
|
163
|
+
try {
|
|
164
|
+
const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
165
|
+
method: "POST",
|
|
166
|
+
headers: { "content-type": "application/json", "x-api-key": this.apiKey },
|
|
167
|
+
body: JSON.stringify(body)
|
|
168
|
+
});
|
|
169
|
+
const data = await res.json().catch(() => ({}));
|
|
170
|
+
return {
|
|
171
|
+
ok: res.ok && data.ok !== false,
|
|
172
|
+
message: data.message ?? null,
|
|
173
|
+
intent: data.intent ?? null,
|
|
174
|
+
toolCalls: data.toolCalls ?? [],
|
|
175
|
+
pendingConfirmation: data.pendingConfirmation ?? null,
|
|
176
|
+
pendingToolCalls: data.pendingToolCalls ?? null,
|
|
177
|
+
continuation: data.continuation ?? null,
|
|
178
|
+
...data,
|
|
179
|
+
status: res.status,
|
|
180
|
+
...res.ok ? {} : { error: data.error ?? `Engine returned ${res.status}` }
|
|
181
|
+
};
|
|
182
|
+
} catch (error) {
|
|
183
|
+
return {
|
|
184
|
+
ok: false,
|
|
185
|
+
status: 0,
|
|
186
|
+
message: null,
|
|
187
|
+
intent: null,
|
|
188
|
+
toolCalls: [],
|
|
189
|
+
pendingConfirmation: null,
|
|
190
|
+
pendingToolCalls: null,
|
|
191
|
+
continuation: null,
|
|
192
|
+
error: error?.message || "Could not reach the engine."
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Execute client-fetch hand-offs and resume until the turn completes. Only
|
|
198
|
+
* calls that carry a resolved `request` are auto-executed - a hand-off
|
|
199
|
+
* without one (e.g. a money move awaiting your PIN flow) stops the loop and
|
|
200
|
+
* is returned to you untouched.
|
|
201
|
+
*/
|
|
202
|
+
async completeHandoffs(res, input, onHandoff) {
|
|
203
|
+
const executed = [];
|
|
204
|
+
for (let round = 0; round < this.maxHandoffRounds; round++) {
|
|
205
|
+
const calls = res.pendingToolCalls ?? [];
|
|
206
|
+
if (!calls.length || !res.continuation) break;
|
|
207
|
+
if (!calls.every((c) => c.request?.url)) break;
|
|
208
|
+
const results = [];
|
|
209
|
+
for (const call of calls) {
|
|
210
|
+
const result = onHandoff ? await onHandoff(call) : await this.performHandoff(call);
|
|
211
|
+
executed.push({
|
|
212
|
+
name: call.name,
|
|
213
|
+
method: call.request.method,
|
|
214
|
+
url: call.request.url,
|
|
215
|
+
ok: result?.ok !== false
|
|
216
|
+
});
|
|
217
|
+
results.push({ id: call.id, result });
|
|
218
|
+
}
|
|
219
|
+
const next = await this.resume(res.continuation, results, input);
|
|
220
|
+
if (next.error) break;
|
|
221
|
+
res = next;
|
|
222
|
+
}
|
|
223
|
+
if (executed.length) res.executedHandoffs = executed;
|
|
224
|
+
return res;
|
|
225
|
+
}
|
|
226
|
+
/** Default hand-off executor: perform the request with your clientAuth headers. */
|
|
227
|
+
async performHandoff(call) {
|
|
228
|
+
const req = call.request;
|
|
229
|
+
try {
|
|
230
|
+
const extra = typeof this.clientAuth === "function" ? await this.clientAuth() : this.clientAuth ?? {};
|
|
231
|
+
const res = await this.fetchImpl(req.url, {
|
|
232
|
+
method: req.method,
|
|
233
|
+
headers: { "content-type": "application/json", ...req.headers ?? {}, ...extra },
|
|
234
|
+
...req.body !== void 0 ? { body: JSON.stringify(req.body) } : {}
|
|
235
|
+
});
|
|
236
|
+
const body = await res.json().catch(() => ({}));
|
|
237
|
+
if (!res.ok) return { ok: false, error: `Endpoint returned ${res.status}` };
|
|
238
|
+
return { ok: true, data: body.data ?? body };
|
|
239
|
+
} catch (error) {
|
|
240
|
+
return { ok: false, error: error?.message || "Request failed." };
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
export {
|
|
246
|
+
buildManage,
|
|
247
|
+
TwelveAI
|
|
248
|
+
};
|