ql-agent-sdk 0.0.0-stage → 1.0.1
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 +848 -2
- package/dist/index.cjs +516 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +416 -0
- package/dist/index.d.ts +416 -0
- package/dist/index.js +493 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -4
package/README.md
CHANGED
|
@@ -1,3 +1,849 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ql-agent-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript SDK for the **Quanton Labs** agent platform API (v1). Integrate your application with 8 specialized AI agents, capabilities, workflows, and background tasks.
|
|
4
|
+
|
|
5
|
+
- **Zero runtime dependencies** — built on native `fetch` and Web Streams
|
|
6
|
+
- **Full TypeScript** — all request/response types exported
|
|
7
|
+
- **ESM + CJS** — works in Node, Bun, Deno, Next.js, and edge runtimes
|
|
8
|
+
|
|
9
|
+
> Base URL defaults to `https://app.quantonlabs.com`. Override with `baseUrl` option or the `QL_BASE_URL` environment variable.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install ql-agent-sdk
|
|
17
|
+
# or
|
|
18
|
+
pnpm add ql-agent-sdk
|
|
19
|
+
# or
|
|
20
|
+
yarn add ql-agent-sdk
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Requires **Node.js 18+** (native `fetch`).
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { QLClient } from "ql-agent-sdk";
|
|
31
|
+
|
|
32
|
+
const ql = new QLClient({ apiKey: process.env.QL_API_KEY! });
|
|
33
|
+
|
|
34
|
+
// Discover available agents
|
|
35
|
+
const { agents } = await ql.agents.list();
|
|
36
|
+
console.log(agents.map((a) => a.id));
|
|
37
|
+
// → ["governing", "finance", "marketing", "inventory", "customer", "developer", "operations", "research"]
|
|
38
|
+
|
|
39
|
+
// Chat with an agent (non-streaming)
|
|
40
|
+
const reply = await ql.agents.chat({
|
|
41
|
+
agentId: "governing",
|
|
42
|
+
messages: [{ role: "user", content: "Summarize this quarter's revenue." }],
|
|
43
|
+
});
|
|
44
|
+
console.log(reply.content);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
const ql = new QLClient({
|
|
53
|
+
apiKey, // required — your Bearer API key (ql_live_...)
|
|
54
|
+
baseUrl, // optional — defaults to QL_BASE_URL or https://app.quantonlabs.com
|
|
55
|
+
fetch: customFetch, // optional — override for tests/proxies/custom runtimes
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The `apiKey` is held in a private field. It is **never** returned by the client, included in error messages, or logged.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Authentication
|
|
64
|
+
|
|
65
|
+
All API calls use **Bearer token authentication** with your API key:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
Authorization: Bearer ql_live_your_key_here
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Create keys in your Quanton Labs dashboard → **API Keys** page. Each key is scoped to a single workspace.
|
|
72
|
+
|
|
73
|
+
### Scope Requirements
|
|
74
|
+
|
|
75
|
+
| Scope | Grants access to |
|
|
76
|
+
|-------|-----------------|
|
|
77
|
+
| `workspace` | `ql.workspace.get()` |
|
|
78
|
+
| `agents` | `ql.agents.list()`, `ql.agents.get()` |
|
|
79
|
+
| `capabilities` | `ql.agents.capabilities()`, `ql.agents.runCapability()` |
|
|
80
|
+
| `chat` | `ql.agents.chat()`, `ql.agents.chatStream()` |
|
|
81
|
+
| `messages` | `ql.agents.history()` |
|
|
82
|
+
| `workflows` | `ql.workflows.run()`, `ql.workflows.getRun()` |
|
|
83
|
+
| `tasks` | `ql.tasks.create()`, `ql.tasks.get()` |
|
|
84
|
+
| `*` | All of the above (default for new keys) |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## API Reference
|
|
89
|
+
|
|
90
|
+
### Response Envelope
|
|
91
|
+
|
|
92
|
+
All non-streaming endpoints return a consistent envelope:
|
|
93
|
+
|
|
94
|
+
**Success:**
|
|
95
|
+
```json
|
|
96
|
+
{ "ok": true, "data": { ... } }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Error:**
|
|
100
|
+
```json
|
|
101
|
+
{ "ok": false, "error": { "code": "not_found", "message": "Agent not found: foo" } }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The SDK unwraps `data` for you and throws a typed `QLError` on errors. You never see the envelope directly.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### Workspace
|
|
109
|
+
|
|
110
|
+
#### `ql.workspace.get()` → `Promise<Workspace>`
|
|
111
|
+
|
|
112
|
+
**Scope:** `workspace`
|
|
113
|
+
|
|
114
|
+
Returns the workspace bound to your API key.
|
|
115
|
+
|
|
116
|
+
**Response (200):**
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"_id": "6adf926f6f87c9ab86f32c0e",
|
|
120
|
+
"name": "Robin",
|
|
121
|
+
"plan": "standard",
|
|
122
|
+
"createdAt": "2026-10-26T17:48:31.038Z",
|
|
123
|
+
"updatedAt": "2026-10-27T11:56:44.074Z"
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**TypeScript:**
|
|
128
|
+
```ts
|
|
129
|
+
const ws = await ql.workspace.get();
|
|
130
|
+
ws._id; // string
|
|
131
|
+
ws.name; // string
|
|
132
|
+
ws.plan; // "standard" | "premium" | "enterprise" | undefined
|
|
133
|
+
ws.createdAt; // string (ISO 8601)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### Agents
|
|
139
|
+
|
|
140
|
+
#### `ql.agents.list()` → `Promise<AgentListResult>`
|
|
141
|
+
|
|
142
|
+
**Scope:** `agents`
|
|
143
|
+
|
|
144
|
+
Lists all 8 available agents with their capability counts.
|
|
145
|
+
|
|
146
|
+
**Response (200):**
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"agents": [
|
|
150
|
+
{
|
|
151
|
+
"id": "governing",
|
|
152
|
+
"name": "Governing Agent",
|
|
153
|
+
"domain": "orchestration",
|
|
154
|
+
"description": "Coordinates all other agents...",
|
|
155
|
+
"icon": "brain",
|
|
156
|
+
"tags": ["coordination", "reasoning"],
|
|
157
|
+
"capabilityCount": 20
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### `ql.agents.get(agentId)` → `Promise<AgentDetail>`
|
|
164
|
+
|
|
165
|
+
**Scope:** `agents`
|
|
166
|
+
|
|
167
|
+
Returns an agent's details and full capability list.
|
|
168
|
+
|
|
169
|
+
**Parameters:**
|
|
170
|
+
- `agentId: string` — agent slug (e.g. `"finance"`, `"governing"`) or ObjectId
|
|
171
|
+
|
|
172
|
+
**Response (200):**
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"agent": {
|
|
176
|
+
"id": "finance",
|
|
177
|
+
"name": "Finance Agent",
|
|
178
|
+
"domain": "finance",
|
|
179
|
+
"description": "Handles financial analysis...",
|
|
180
|
+
"icon": "dollar-sign",
|
|
181
|
+
"tags": ["accounting", "budgeting"]
|
|
182
|
+
},
|
|
183
|
+
"capabilities": [
|
|
184
|
+
{
|
|
185
|
+
"id": "invoice",
|
|
186
|
+
"name": "Invoice Generator",
|
|
187
|
+
"description": "Generate formatted invoices from data",
|
|
188
|
+
"category": "documents",
|
|
189
|
+
"params": [
|
|
190
|
+
{ "key": "data", "label": "Data", "kind": "textarea", "required": true }
|
|
191
|
+
],
|
|
192
|
+
"templates": [
|
|
193
|
+
{ "label": "Monthly Report", "values": { "data": "Revenue | 12000\nCosts | 4000" } }
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Errors:**
|
|
201
|
+
- `404 not_found` — agent slug doesn't exist
|
|
202
|
+
|
|
203
|
+
#### `ql.agents.capabilities(agentId)` → `Promise<CapabilitiesResult>`
|
|
204
|
+
|
|
205
|
+
**Scope:** `capabilities`
|
|
206
|
+
|
|
207
|
+
Returns just the capability list for an agent (lighter than `get`).
|
|
208
|
+
|
|
209
|
+
**Response (200):**
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"agentId": "finance",
|
|
213
|
+
"capabilities": [ ... ]
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### `ql.agents.runCapability(agentId, input)` → `Promise<RunCapabilityResult>`
|
|
218
|
+
|
|
219
|
+
**Scope:** `capabilities`
|
|
220
|
+
|
|
221
|
+
Executes a specific capability (feature) on an agent.
|
|
222
|
+
|
|
223
|
+
**Parameters:**
|
|
224
|
+
- `agentId: string` — agent slug
|
|
225
|
+
- `input.capId: string` — capability ID (from the capabilities list)
|
|
226
|
+
- `input.params?: Record<string, unknown>` — capability-specific parameters
|
|
227
|
+
|
|
228
|
+
**Response (200):**
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"result": {
|
|
232
|
+
"type": "html",
|
|
233
|
+
"html": "<div class=\"invoice\">...</div>"
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The `result` is a **discriminated union** — check `result.type`:
|
|
239
|
+
|
|
240
|
+
| `type` | Fields | When |
|
|
241
|
+
|--------|--------|------|
|
|
242
|
+
| `"text"` | `content: string` | Plain text output |
|
|
243
|
+
| `"json"` | `data: unknown` | Structured JSON |
|
|
244
|
+
| `"html"` | `html: string` | HTML content (renderable) |
|
|
245
|
+
| `"file"` | `url, filename, mime` | Generated file (image, PDF, etc.) |
|
|
246
|
+
|
|
247
|
+
**Example:**
|
|
248
|
+
```ts
|
|
249
|
+
const { result } = await ql.agents.runCapability("finance", {
|
|
250
|
+
capId: "invoice",
|
|
251
|
+
params: { data: "Revenue | 12000\nCosts | 4000" },
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
if (result.type === "html") {
|
|
255
|
+
document.body.innerHTML = result.html;
|
|
256
|
+
} else if (result.type === "json") {
|
|
257
|
+
console.log(result.data);
|
|
258
|
+
} else if (result.type === "text") {
|
|
259
|
+
console.log(result.content);
|
|
260
|
+
} else if (result.type === "file") {
|
|
261
|
+
window.open(result.url); // download
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
### Chat
|
|
268
|
+
|
|
269
|
+
#### `ql.agents.chat(input)` → `Promise<ChatResult>`
|
|
270
|
+
|
|
271
|
+
**Scope:** `chat`
|
|
272
|
+
|
|
273
|
+
Non-streaming chat with an agent. Waits for the full response.
|
|
274
|
+
|
|
275
|
+
**Parameters:**
|
|
276
|
+
- `agentId: string` — agent slug
|
|
277
|
+
- `messages: ChatMessage[]` — conversation history (newest last)
|
|
278
|
+
- `userId?: string` — optional internal user ID (defaults to workspace)
|
|
279
|
+
- `response_format?: object` — optional OpenRouter format hint
|
|
280
|
+
|
|
281
|
+
**Response (200):**
|
|
282
|
+
```json
|
|
283
|
+
{
|
|
284
|
+
"content": "Based on your data, Q3 revenue was $120K...",
|
|
285
|
+
"toolCalls": 2,
|
|
286
|
+
"iterations": 3,
|
|
287
|
+
"duration": "4.21s"
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
| Field | Type | Description |
|
|
292
|
+
|-------|------|-------------|
|
|
293
|
+
| `content` | `string` | The agent's full text response |
|
|
294
|
+
| `toolCalls` | `number` | How many tool calls were made during reasoning |
|
|
295
|
+
| `iterations` | `number` | Tool loop iterations |
|
|
296
|
+
| `duration` | `string` | Human-readable execution time |
|
|
297
|
+
|
|
298
|
+
**Example:**
|
|
299
|
+
```ts
|
|
300
|
+
const reply = await ql.agents.chat({
|
|
301
|
+
agentId: "finance",
|
|
302
|
+
messages: [
|
|
303
|
+
{ role: "user", content: "What was our profit margin last month?" },
|
|
304
|
+
],
|
|
305
|
+
});
|
|
306
|
+
console.log(reply.content);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
#### `ql.agents.chatStream(input, opts?)` → `AsyncIterable<ChatStreamEvent>`
|
|
312
|
+
|
|
313
|
+
**Scope:** `chat`
|
|
314
|
+
|
|
315
|
+
Streaming chat — yields incremental events as the agent responds.
|
|
316
|
+
|
|
317
|
+
**Parameters:**
|
|
318
|
+
- Same as `chat()` above
|
|
319
|
+
- `opts.signal?: AbortSignal` — abort the stream at any time
|
|
320
|
+
|
|
321
|
+
**Yielded events:**
|
|
322
|
+
```ts
|
|
323
|
+
type ChatStreamEvent =
|
|
324
|
+
| { type: "delta"; content?: string; reasoning?: string } // incremental piece
|
|
325
|
+
| { type: "done"; content: string; reasoning?: string }; // final accumulated text
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
| Event | When | Fields |
|
|
329
|
+
|-------|------|--------|
|
|
330
|
+
| `delta` | For each token/chunk from the model | `content` (text delta), `reasoning` (thinking delta, if model supports it) |
|
|
331
|
+
| `done` | Once at the end | `content` (full accumulated text), `reasoning` (full reasoning, if any) |
|
|
332
|
+
|
|
333
|
+
**Example:**
|
|
334
|
+
```ts
|
|
335
|
+
import { QLClient } from "ql-agent-sdk";
|
|
336
|
+
|
|
337
|
+
const ql = new QLClient({ apiKey: process.env.QL_API_KEY! });
|
|
338
|
+
const controller = new AbortController();
|
|
339
|
+
|
|
340
|
+
// Optional: auto-abort after 30s
|
|
341
|
+
const timer = setTimeout(() => controller.abort(), 30_000);
|
|
342
|
+
|
|
343
|
+
let full = "";
|
|
344
|
+
try {
|
|
345
|
+
for await (const ev of ql.agents.chatStream(
|
|
346
|
+
{ agentId: "governing", messages: [{ role: "user", content: "Explain quantum computing" }] },
|
|
347
|
+
{ signal: controller.signal },
|
|
348
|
+
)) {
|
|
349
|
+
if (ev.type === "delta") {
|
|
350
|
+
process.stdout.write(ev.content ?? ""); // print incrementally
|
|
351
|
+
full += ev.content ?? "";
|
|
352
|
+
} else if (ev.type === "done") {
|
|
353
|
+
full = ev.content; // final text (replaces accumulated)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
} catch (err) {
|
|
357
|
+
if ((err as any)?.code === "aborted") console.log("\n[stopped by user]");
|
|
358
|
+
} finally {
|
|
359
|
+
clearTimeout(timer);
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**SSE wire format** (if you ever need to parse it manually):
|
|
364
|
+
```
|
|
365
|
+
data: {"id":"gen-...","choices":[{"delta":{"content":"Hel"}}]}
|
|
366
|
+
|
|
367
|
+
data: {"id":"gen-...","choices":[{"delta":{"content":"lo"}}]}
|
|
368
|
+
|
|
369
|
+
data: [DONE]
|
|
370
|
+
|
|
371
|
+
```
|
|
372
|
+
Events are separated by blank lines. `data: [DONE]` signals the end.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
### History / Messages
|
|
377
|
+
|
|
378
|
+
#### `ql.agents.history(agentId, opts?)` → `Promise<HistoryPage>`
|
|
379
|
+
|
|
380
|
+
**Scope:** `messages`
|
|
381
|
+
|
|
382
|
+
Returns paginated conversation history for an agent (newest first).
|
|
383
|
+
|
|
384
|
+
**Parameters:**
|
|
385
|
+
- `agentId: string` — agent slug
|
|
386
|
+
- `opts.limit?: number` — page size, 1–100 (default 20)
|
|
387
|
+
- `opts.before?: string` — cursor from previous page's `nextBefore`
|
|
388
|
+
- `opts.userId?: string` — optional filter by user
|
|
389
|
+
|
|
390
|
+
**Response (200):**
|
|
391
|
+
```json
|
|
392
|
+
{
|
|
393
|
+
"agentId": "finance",
|
|
394
|
+
"limit": 20,
|
|
395
|
+
"nextBefore": "6ae0d96274f1cf895ef06e0c",
|
|
396
|
+
"items": [
|
|
397
|
+
{
|
|
398
|
+
"id": "6ae0d963...",
|
|
399
|
+
"role": "user",
|
|
400
|
+
"content": "What was our profit margin?",
|
|
401
|
+
"toolCalls": [],
|
|
402
|
+
"status": "completed",
|
|
403
|
+
"timestamp": "2026-10-27T14:32:00.000Z"
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"id": "6ae0d962...",
|
|
407
|
+
"role": "assistant",
|
|
408
|
+
"content": "Based on your records...",
|
|
409
|
+
"reasoning": "Let me check the financial data...",
|
|
410
|
+
"toolCalls": [{ "name": "db_query", ... }],
|
|
411
|
+
"status": "completed",
|
|
412
|
+
"timestamp": "2026-10-27T14:32:05.000Z"
|
|
413
|
+
}
|
|
414
|
+
]
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
| Field | Description |
|
|
419
|
+
|-------|-------------|
|
|
420
|
+
| `nextBefore` | Cursor for next page. `null` when no more pages. |
|
|
421
|
+
| `items[].role` | `"user"` or `"assistant"` |
|
|
422
|
+
| `items[].content` | Message text (may be null for tool-only messages) |
|
|
423
|
+
| `items[].reasoning` | Model's thinking (if supported by the model) |
|
|
424
|
+
| `items[].toolCalls` | Array of tool invocations made during this message |
|
|
425
|
+
| `items[].status` | `"completed"`, `"processing"`, or `"failed"` |
|
|
426
|
+
|
|
427
|
+
**Walking all pages:**
|
|
428
|
+
```ts
|
|
429
|
+
const allMessages = [];
|
|
430
|
+
let before: string | undefined;
|
|
431
|
+
|
|
432
|
+
do {
|
|
433
|
+
const page = await ql.agents.history("finance", { limit: 50, before });
|
|
434
|
+
allMessages.push(...page.items);
|
|
435
|
+
before = page.nextBefore ?? undefined; // null when exhausted
|
|
436
|
+
} while (before);
|
|
437
|
+
|
|
438
|
+
console.log(`${allMessages.length} total messages`);
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
### Workflows
|
|
444
|
+
|
|
445
|
+
#### `ql.workflows.run(workflowId, input?)` → `Promise<WorkflowRunStart>`
|
|
446
|
+
|
|
447
|
+
**Scope:** `workflows`
|
|
448
|
+
|
|
449
|
+
Triggers a workflow execution. Returns immediately with a run ID (HTTP 202).
|
|
450
|
+
|
|
451
|
+
**Parameters:**
|
|
452
|
+
- `workflowId: string` — the workflow's ObjectId
|
|
453
|
+
- `input?: { input?: Record<string, unknown> }` — optional input payload for the workflow
|
|
454
|
+
|
|
455
|
+
**Response (202):**
|
|
456
|
+
```json
|
|
457
|
+
{
|
|
458
|
+
"runId": "6ae101c8...",
|
|
459
|
+
"status": "running"
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**Errors:**
|
|
464
|
+
- `400 validation_error` — invalid workflow ID format
|
|
465
|
+
- `404 not_found` — workflow doesn't exist in this workspace
|
|
466
|
+
|
|
467
|
+
#### `ql.workflows.getRun(runId)` → `Promise<WorkflowRun>`
|
|
468
|
+
|
|
469
|
+
**Scope:** `workflows`
|
|
470
|
+
|
|
471
|
+
Polls a workflow run's status.
|
|
472
|
+
|
|
473
|
+
**Response (200):**
|
|
474
|
+
```json
|
|
475
|
+
{
|
|
476
|
+
"runId": "6ae101c8...",
|
|
477
|
+
"status": "completed",
|
|
478
|
+
"output": { "result": "..." },
|
|
479
|
+
"error": null,
|
|
480
|
+
"durationMs": 4523,
|
|
481
|
+
"startedAt": "2026-10-27T14:32:00.000Z",
|
|
482
|
+
"finishedAt": "2026-10-27T14:32:04.523Z"
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
| `status` value | Meaning |
|
|
487
|
+
|----------------|---------|
|
|
488
|
+
| `"queued"` | Waiting to start |
|
|
489
|
+
| `"running"` | Currently executing |
|
|
490
|
+
| `"completed"` | Finished successfully — check `output` |
|
|
491
|
+
| `"failed"` | Execution error — check `error` |
|
|
492
|
+
| `"cancelled"` | Was cancelled |
|
|
493
|
+
|
|
494
|
+
**Polling example:**
|
|
495
|
+
```ts
|
|
496
|
+
const start = await ql.workflows.run("6adf929d6f87c9ab86f32c19", { input: { a: 1 } });
|
|
497
|
+
|
|
498
|
+
// Poll until done
|
|
499
|
+
let run;
|
|
500
|
+
do {
|
|
501
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
502
|
+
run = await ql.workflows.getRun(start.runId);
|
|
503
|
+
} while (run.status === "running" || run.status === "queued");
|
|
504
|
+
|
|
505
|
+
if (run.status === "completed") console.log(run.output);
|
|
506
|
+
else if (run.status === "failed") console.error(run.error);
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
### Tasks (Background)
|
|
512
|
+
|
|
513
|
+
#### `ql.tasks.create(input)` → `Promise<TaskCreated>`
|
|
514
|
+
|
|
515
|
+
**Scope:** `tasks`
|
|
516
|
+
|
|
517
|
+
Creates a background task. The task **starts executing immediately** on the server — you don't need to call another endpoint.
|
|
518
|
+
|
|
519
|
+
**Parameters:**
|
|
520
|
+
- `agentId: string` — which agent handles this task
|
|
521
|
+
- `title: string` — human-readable name
|
|
522
|
+
- `description?: string` — optional longer description
|
|
523
|
+
- `input?: unknown` — payload for the agent (string, or `{ message }`, or `{ prompt }`)
|
|
524
|
+
- `priority?: "low" | "medium" | "high" | "urgent"` — scheduling priority
|
|
525
|
+
|
|
526
|
+
**Response (201):**
|
|
527
|
+
```json
|
|
528
|
+
{
|
|
529
|
+
"taskId": "6ae101b7...",
|
|
530
|
+
"status": "queued"
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
**Errors:**
|
|
535
|
+
- `400 validation_error` — missing `agentId` or `title`
|
|
536
|
+
- `404 not_found` — unknown agent slug
|
|
537
|
+
|
|
538
|
+
#### `ql.tasks.get(taskId)` → `Promise<Task>`
|
|
539
|
+
|
|
540
|
+
**Scope:** `tasks`
|
|
541
|
+
|
|
542
|
+
Single poll of a task's current state.
|
|
543
|
+
|
|
544
|
+
**Response (200):**
|
|
545
|
+
```json
|
|
546
|
+
{
|
|
547
|
+
"taskId": "6ae101b7...",
|
|
548
|
+
"status": "completed",
|
|
549
|
+
"title": "Monthly Report",
|
|
550
|
+
"progress": {
|
|
551
|
+
"current": 100,
|
|
552
|
+
"total": 100,
|
|
553
|
+
"message": "Completed",
|
|
554
|
+
"updatedAt": "2026-10-27T19:53:23.117Z"
|
|
555
|
+
},
|
|
556
|
+
"result": "Here is your monthly report...",
|
|
557
|
+
"output": "Here is your monthly report...",
|
|
558
|
+
"error": null,
|
|
559
|
+
"createdAt": "2026-10-27T19:53:16.756Z",
|
|
560
|
+
"completedAt": "2026-10-27T19:53:23.117Z"
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
| `status` value | Meaning | Check field |
|
|
565
|
+
|----------------|---------|-------------|
|
|
566
|
+
| `"queued"` | Waiting to start | — |
|
|
567
|
+
| `"running"` | In progress | `progress.message` for current step |
|
|
568
|
+
| `"retrying"` | Failed, will retry automatically | `error` for what went wrong |
|
|
569
|
+
| `"completed"` | Done successfully | `result` / `output` for the answer |
|
|
570
|
+
| `"failed"` | Permanently failed (max retries) | `error` for the reason |
|
|
571
|
+
| `"cancelled"` | Was cancelled | — |
|
|
572
|
+
|
|
573
|
+
#### `ql.tasks.handle(taskId)` → `TaskHandle`
|
|
574
|
+
|
|
575
|
+
Returns a handle with `poll()` and `wait()` methods.
|
|
576
|
+
|
|
577
|
+
**`handle.poll(signal?)`** — single fetch, returns current `Task` snapshot.
|
|
578
|
+
|
|
579
|
+
**`handle.wait(opts?)`** — polls until terminal status with backoff.
|
|
580
|
+
|
|
581
|
+
| Option | Default | Description |
|
|
582
|
+
|--------|---------|-------------|
|
|
583
|
+
| `intervalMs` | 2000 | Initial delay between polls (ms) |
|
|
584
|
+
| `maxIntervalMs` | 15000 | Backoff cap (grows 1.5× per poll) |
|
|
585
|
+
| `timeoutMs` | undefined (wait forever) | Throw after this many ms total |
|
|
586
|
+
| `signal` | undefined | AbortSignal for cancellation |
|
|
587
|
+
| `onTick` | undefined | Callback after each poll with latest snapshot |
|
|
588
|
+
|
|
589
|
+
**Terminal states returned by `wait()`:**
|
|
590
|
+
- `task.status === "completed"` → check `task.result`
|
|
591
|
+
- `task.status === "failed"` → check `task.error`
|
|
592
|
+
- `task.status === "cancelled"` → task was cancelled server-side
|
|
593
|
+
|
|
594
|
+
**Non-terminal exits (throws QLError):**
|
|
595
|
+
- `err.code === "timeout"` → exceeded `timeoutMs`
|
|
596
|
+
- `err.code === "aborted"` → `signal` was aborted
|
|
597
|
+
|
|
598
|
+
**Example:**
|
|
599
|
+
```ts
|
|
600
|
+
const created = await ql.tasks.create({
|
|
601
|
+
agentId: "finance",
|
|
602
|
+
title: "Quarterly Report",
|
|
603
|
+
input: { message: "Generate a Q3 financial summary from our database." },
|
|
604
|
+
priority: "high",
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
const handle = ql.tasks.handle(created.taskId);
|
|
608
|
+
|
|
609
|
+
try {
|
|
610
|
+
const done = await handle.wait({
|
|
611
|
+
intervalMs: 2000,
|
|
612
|
+
maxIntervalMs: 10000,
|
|
613
|
+
timeoutMs: 5 * 60_000, // 5 minute timeout
|
|
614
|
+
onTick: (task) => {
|
|
615
|
+
if (task.status === "running" && task.progress?.message) {
|
|
616
|
+
process.stdout.write(`\r${task.progress.message}`);
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
if (done.status === "completed") {
|
|
622
|
+
console.log("\n✓ Done:", done.result);
|
|
623
|
+
} else if (done.status === "failed") {
|
|
624
|
+
console.error("✗ Failed:", done.error);
|
|
625
|
+
}
|
|
626
|
+
} catch (err) {
|
|
627
|
+
if ((err as any).code === "timeout") console.error("Timed out after 5 min");
|
|
628
|
+
if ((err as any).code === "aborted") console.log("Cancelled by user");
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
> **Note on execution:** Tasks execute in the server's process. If the server restarts mid-execution, the task will be recovered and retried on next startup (up to `maxRetries` times). For mission-critical workloads with strict durability requirements, consider a persistent queue backend.
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
## Error Handling
|
|
637
|
+
|
|
638
|
+
All API failures throw a typed **`QLError`**:
|
|
639
|
+
|
|
640
|
+
```ts
|
|
641
|
+
import { QLClient, QLError } from "ql-agent-sdk";
|
|
642
|
+
|
|
643
|
+
try {
|
|
644
|
+
await ql.agents.chat({ agentId: "finance", messages: [...] });
|
|
645
|
+
} catch (err) {
|
|
646
|
+
if (err instanceof QLError) {
|
|
647
|
+
console.error(err.status); // HTTP status code (401, 403, 404, etc.)
|
|
648
|
+
console.error(err.code); // stable string code (see table below)
|
|
649
|
+
console.error(err.message); // human-readable (never includes API key)
|
|
650
|
+
|
|
651
|
+
// Convenience booleans:
|
|
652
|
+
err.isAuthError; // true for 401/403
|
|
653
|
+
err.isAborted; // true for abort
|
|
654
|
+
err.isTimeout; // true for wait() timeout
|
|
655
|
+
err.isNetworkError; // true for connection failures
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
### Error Code Reference
|
|
661
|
+
|
|
662
|
+
| HTTP Status | `code` | When it happens |
|
|
663
|
+
|-------------|--------|-----------------|
|
|
664
|
+
| 401 | `unauthorized` | Missing, invalid, or revoked API key |
|
|
665
|
+
| 403 | `forbidden` | Key lacks the required scope for this endpoint |
|
|
666
|
+
| 404 | `not_found` | Resource doesn't exist in your workspace (agent, workflow, task) |
|
|
667
|
+
| 409 | `conflict` | Conflict (e.g. duplicate resource) |
|
|
668
|
+
| 400 | `validation_error` | Malformed request body, missing required fields, invalid params |
|
|
669
|
+
| 500 | `internal_error` | Server-side error (AI provider failure, DB issue, etc.) |
|
|
670
|
+
| — | `network` | No HTTP response received (connection refused, DNS failure, timeout) |
|
|
671
|
+
| — | `aborted` | Caller aborted via `AbortSignal` |
|
|
672
|
+
| — | `timeout` | `wait()` exceeded its `timeoutMs` budget |
|
|
673
|
+
|
|
674
|
+
### Common Error Scenarios
|
|
675
|
+
|
|
676
|
+
```ts
|
|
677
|
+
// 1. Expired/revoked key
|
|
678
|
+
try {
|
|
679
|
+
await ql.workspace.get();
|
|
680
|
+
} catch (e) {
|
|
681
|
+
if (e instanceof QLError && e.code === "unauthorized") {
|
|
682
|
+
console.log("API key is invalid or revoked. Generate a new one in your dashboard.");
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// 2. Key doesn't have chat scope
|
|
687
|
+
try {
|
|
688
|
+
await ql.agents.chat({ agentId: "finance", messages: [...] });
|
|
689
|
+
} catch (e) {
|
|
690
|
+
if (e instanceof QLError && e.code === "forbidden") {
|
|
691
|
+
console.log("This key needs the 'chat' scope. Create a new key with broader scopes.");
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// 3. AI provider error (transient)
|
|
696
|
+
try {
|
|
697
|
+
await ql.agents.chat({ agentId: "governing", messages: [...] });
|
|
698
|
+
} catch (e) {
|
|
699
|
+
if (e instanceof QLError && e.status === 500) {
|
|
700
|
+
console.log("AI service temporarily unavailable. Retry later.");
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
---
|
|
706
|
+
|
|
707
|
+
## TypeScript
|
|
708
|
+
|
|
709
|
+
Full type declarations are included. All public types are exported:
|
|
710
|
+
|
|
711
|
+
```ts
|
|
712
|
+
import type {
|
|
713
|
+
QLClient,
|
|
714
|
+
QLClientOptions,
|
|
715
|
+
QLError,
|
|
716
|
+
// Request types
|
|
717
|
+
ChatInput,
|
|
718
|
+
RunCapabilityInput,
|
|
719
|
+
TaskCreateInput,
|
|
720
|
+
WaitOptions,
|
|
721
|
+
HistoryParams,
|
|
722
|
+
WorkflowRunInput,
|
|
723
|
+
// Response types
|
|
724
|
+
Workspace,
|
|
725
|
+
AgentSummary,
|
|
726
|
+
AgentDetail,
|
|
727
|
+
CapabilityInfo,
|
|
728
|
+
CapabilityResult,
|
|
729
|
+
ChatResult,
|
|
730
|
+
ChatStreamEvent,
|
|
731
|
+
HistoryPage,
|
|
732
|
+
MessageItem,
|
|
733
|
+
WorkflowRun,
|
|
734
|
+
Task,
|
|
735
|
+
TaskCreated,
|
|
736
|
+
// Constants
|
|
737
|
+
API_ERROR_CODES,
|
|
738
|
+
} from "ql-agent-sdk";
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
---
|
|
742
|
+
|
|
743
|
+
## Using in Next.js App Router
|
|
744
|
+
|
|
745
|
+
The SDK is safe to use **server-side** in Route Handlers or Server Components:
|
|
746
|
+
|
|
747
|
+
```ts
|
|
748
|
+
// app/api/agent/route.ts
|
|
749
|
+
import { QLClient, QLError } from "ql-agent-sdk";
|
|
750
|
+
|
|
751
|
+
export const runtime = "nodejs"; // uses native fetch + Web Streams
|
|
752
|
+
|
|
753
|
+
const ql = new QLClient({ apiKey: process.env.QL_API_KEY! });
|
|
754
|
+
|
|
755
|
+
export async function POST(req: Request) {
|
|
756
|
+
const { message } = await req.json();
|
|
757
|
+
|
|
758
|
+
try {
|
|
759
|
+
const reply = await ql.agents.chat({
|
|
760
|
+
agentId: "governing",
|
|
761
|
+
messages: [{ role: "user", content: message }],
|
|
762
|
+
});
|
|
763
|
+
return Response.json({ text: reply.content });
|
|
764
|
+
} catch (err) {
|
|
765
|
+
if (err instanceof QLError) {
|
|
766
|
+
return Response.json({ error: err.message }, { status: err.status ?? 500 });
|
|
767
|
+
}
|
|
768
|
+
throw err;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
For **streaming** in a Route Handler:
|
|
774
|
+
```ts
|
|
775
|
+
export async function POST(req: Request) {
|
|
776
|
+
const { message } = await req.json();
|
|
777
|
+
const ql = new QLClient({ apiKey: process.env.QL_API_KEY! });
|
|
778
|
+
|
|
779
|
+
const stream = new ReadableStream({
|
|
780
|
+
async start(controller) {
|
|
781
|
+
try {
|
|
782
|
+
for await (const ev of ql.agents.chatStream({
|
|
783
|
+
agentId: "governing",
|
|
784
|
+
messages: [{ role: "user", content: message }],
|
|
785
|
+
})) {
|
|
786
|
+
if (ev.type === "delta" && ev.content) {
|
|
787
|
+
controller.enqueue(new TextEncoder().encode(ev.content));
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
} finally {
|
|
791
|
+
controller.close();
|
|
792
|
+
}
|
|
793
|
+
},
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
return new Response(stream, { headers: { "Content-Type": "text/plain" } });
|
|
797
|
+
}
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
---
|
|
801
|
+
|
|
802
|
+
## Agent Reference
|
|
803
|
+
|
|
804
|
+
The platform includes 8 specialized agents. Each has 20 capabilities in its domain:
|
|
805
|
+
|
|
806
|
+
| ID | Domain | Examples |
|
|
807
|
+
|----|--------|----------|
|
|
808
|
+
| `governing` | Orchestration | Coordinates other agents, complex multi-step reasoning |
|
|
809
|
+
| `finance` | Finance | Invoices, P&L, budgeting, cash flow, tax estimates |
|
|
810
|
+
| `marketing` | Marketing | Social media content, ad copy, SEO, campaign planning |
|
|
811
|
+
| `inventory` | Supply Chain | Stock levels, reorder points, supplier management |
|
|
812
|
+
| `customer` | Customer Success | Support drafts, onboarding sequences, churn analysis |
|
|
813
|
+
| `developer` | Engineering | Code generation, API docs, bug triage, test writing |
|
|
814
|
+
| `operations` | Operations | SOPs, process mapping, KPI dashboards, compliance |
|
|
815
|
+
| `research` | Research | Market analysis, competitor intel, literature reviews |
|
|
816
|
+
|
|
817
|
+
Discover available capabilities at runtime:
|
|
818
|
+
```ts
|
|
819
|
+
const detail = await ql.agents.get("finance");
|
|
820
|
+
console.log(detail.capabilities.map((c) => c.id));
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
---
|
|
824
|
+
|
|
825
|
+
## Environment Variables
|
|
826
|
+
|
|
827
|
+
| Variable | Purpose |
|
|
828
|
+
|----------|---------|
|
|
829
|
+
| `QL_API_KEY` | Your API key (convention — the SDK reads it from the `apiKey` option, not env directly) |
|
|
830
|
+
| `QL_BASE_URL` | Override the base URL (useful for staging/self-hosted) |
|
|
831
|
+
|
|
832
|
+
---
|
|
833
|
+
|
|
834
|
+
## Troubleshooting
|
|
835
|
+
|
|
836
|
+
| Symptom | Cause | Fix |
|
|
837
|
+
|---------|-------|-----|
|
|
838
|
+
| `QLError: unauthorized` | Key is invalid, revoked, or expired | Generate a new key in your dashboard |
|
|
839
|
+
| `QLError: forbidden` | Key scope doesn't cover the endpoint | Create a key with the required scope or `*` |
|
|
840
|
+
| `QLError: not_found` (agent) | Typo in agent slug | Use `ql.agents.list()` to see valid IDs |
|
|
841
|
+
| `QLError: network` | Can't reach the API | Check your network / base URL / firewall |
|
|
842
|
+
| Task stuck in `"running"` | Server restarted mid-execution | Wait for stale recovery (≤5 min) or poll again later |
|
|
843
|
+
| Stream produces no output | Model returned empty response | Check `reasoning` field or try a different agent |
|
|
844
|
+
|
|
845
|
+
---
|
|
846
|
+
|
|
847
|
+
## License
|
|
848
|
+
|
|
849
|
+
MIT
|