ql-agent-sdk 0.0.0-stage → 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/README.md CHANGED
@@ -1,3 +1,247 @@
1
- # Temporary Holding Version
1
+ # ql-agent-sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ TypeScript SDK for the **Quanton Labs** agent platform API (v1). Integrates your application with the 8 specialized agents, capabilities, workflows, and background tasks. Zero runtime dependencies — built on native `fetch` and Web Streams.
4
+
5
+ > Base URL defaults to `https://app.quantonlabs.com`. Override with `baseUrl` or the `QL_BASE_URL` env var.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install ql-agent-sdk
11
+ # or
12
+ pnpm add ql-agent-sdk
13
+ ```
14
+
15
+ Requires **Node.js 18+** (native `fetch`). Works in Node, Bun, Deno, and browser/edge runtimes that provide `fetch` and Web Streams.
16
+
17
+ ## Quick start
18
+
19
+ ```ts
20
+ import { QLClient } from "ql-agent-sdk";
21
+
22
+ const ql = new QLClient({ apiKey: process.env.QL_API_KEY! });
23
+
24
+ // Discover agents
25
+ const { agents } = await ql.agents.list();
26
+ console.log(agents.map((a) => a.id));
27
+
28
+ // Chat with an agent (non-streaming)
29
+ const reply = await ql.agents.chat({
30
+ agentId: "governing",
31
+ messages: [{ role: "user", content: "Summarize this quarter's revenue." }],
32
+ });
33
+ console.log(reply.content);
34
+ ```
35
+
36
+ ## Configuration
37
+
38
+ ```ts
39
+ new QLClient({
40
+ apiKey, // required — your Bearer API key (never logged/exposed)
41
+ baseUrl, // optional — defaults to QL_BASE_URL or https://app.quantonlabs.com
42
+ fetch: customFetch, // optional — override for tests/proxies/custom runtimes
43
+ });
44
+ ```
45
+
46
+ `apiKey` is held in a private field and is never returned by the client, included in errors, or logged.
47
+
48
+ ## API reference
49
+
50
+ ### Workspace
51
+ ```ts
52
+ const ws = await ql.workspace.get(); // -> { _id, name, plan, createdAt, updatedAt }
53
+ ```
54
+
55
+ ### Agents
56
+ ```ts
57
+ const { agents } = await ql.agents.list(); // AgentListResult
58
+ const detail = await ql.agents.get("finance"); // AgentDetail (agent + capabilities)
59
+ const caps = await ql.agents.capabilities("finance"); // CapabilitiesResult
60
+
61
+ // Run a capability ("feature"). The result is a discriminated union:
62
+ const { result } = await ql.agents.runCapability("finance", {
63
+ capId: "invoice",
64
+ params: { data: "Revenue | 12000\nCosts | 4000" },
65
+ });
66
+ if (result.type === "html") render(result.html);
67
+ else if (result.type === "json") use(result.data);
68
+ else if (result.type === "text") print(result.content);
69
+ // result.type === "file" -> { url, filename, mime }
70
+
71
+ // Chat (non-streaming)
72
+ const out = await ql.agents.chat({ agentId: "marketing", messages: [...] }); // ChatResult
73
+ ```
74
+
75
+ ### Streaming chat (SSE)
76
+ `agents.chatStream` returns an **async iterable** of events. Stop it with an `AbortController`.
77
+
78
+ ```ts
79
+ import { QLClient } from "ql-agent-sdk";
80
+
81
+ const ql = new QLClient({ apiKey: process.env.QL_API_KEY! });
82
+ const controller = new AbortController();
83
+
84
+ // e.g. abort after 30s or on user action
85
+ const timer = setTimeout(() => controller.abort(), 30_000);
86
+
87
+ let full = "";
88
+ try {
89
+ for await (const ev of ql.agents.chatStream(
90
+ { agentId: "governing", messages: [{ role: "user", content: "Hello" }] },
91
+ { signal: controller.signal },
92
+ )) {
93
+ if (ev.type === "delta") {
94
+ process.stdout.write(ev.content ?? ""); // incremental text
95
+ full += ev.content ?? "";
96
+ } else if (ev.type === "done") {
97
+ full = ev.content; // final accumulated text
98
+ }
99
+ }
100
+ } catch (err) {
101
+ if ((err as any)?.code === "aborted") console.log("stopped by user");
102
+ } finally {
103
+ clearTimeout(timer);
104
+ }
105
+ ```
106
+
107
+ The stream is robust to chunk boundaries, CRLF/LF endings, multiple events per chunk, a final event without a trailing newline, `[DONE]`, and malformed JSON (skipped).
108
+
109
+ ### History / pagination
110
+ `agents.history` returns newest-first pages. Walk them with the `nextBefore` cursor until it is `null`.
111
+
112
+ ```ts
113
+ const ids: string[] = [];
114
+ let before: string | undefined;
115
+ do {
116
+ const page = await ql.agents.history("finance", { limit: 50, before });
117
+ for (const m of page.items) ids.push(m.id);
118
+ before = page.nextBefore ?? undefined; // null when exhausted
119
+ } while (before);
120
+ ```
121
+
122
+ ### Workflows
123
+ ```ts
124
+ const start = await ql.workflows.run("6adf929d6f87c9ab86f32c19", { input: { a: 1 } }); // 202 -> { runId, status: "running" }
125
+ const run = await ql.workflows.getRun(start.runId); // poll -> WorkflowRun (status/output/error/durationMs/...)
126
+ ```
127
+
128
+ ### Tasks (background)
129
+ ```ts
130
+ const created = await ql.tasks.create({ agentId: "finance", title: "Monthly report", priority: "high" }); // 201 -> { taskId, status }
131
+ const handle = ql.tasks.handle(created.taskId);
132
+
133
+ // One-shot poll
134
+ const snapshot = await handle.poll();
135
+
136
+ // Wait until terminal (completed/failed/cancelled) with backoff + timeout + cancellation
137
+ const done = await handle.wait({
138
+ intervalMs: 2000, // initial delay
139
+ maxIntervalMs: 15000, // backoff cap
140
+ timeoutMs: 5 * 60_000, // throw QLError(code="timeout") if exceeded
141
+ signal: controller.signal, // throw QLError(code="aborted") if aborted
142
+ });
143
+ ```
144
+
145
+ > **Task execution limitation:** `tasks.create` only **enqueues** a task. It may remain in the `queued` state until an existing worker/execution mechanism on the backend processes it — creating a task via this SDK does **not** trigger execution. `wait()` simply polls until the task reaches a terminal status (or you time out / abort).
146
+
147
+ ## Errors
148
+
149
+ All failures throw a typed `QLError`:
150
+
151
+ ```ts
152
+ import { QLClient, QLError } from "ql-agent-sdk";
153
+
154
+ try {
155
+ await ql.agents.chat({ ... });
156
+ } catch (err) {
157
+ if (err instanceof QLError) {
158
+ err.status; // HTTP status (401/403/404/409/400/500) when from an HTTP response
159
+ err.code; // stable code: unauthorized | forbidden | not_found | conflict |
160
+ // validation_error | internal_error | network | aborted | timeout
161
+ err.message; // human-readable (never includes the API key)
162
+ err.isAuthError / err.isAborted / err.isTimeout / err.isNetworkError
163
+ }
164
+ }
165
+ ```
166
+
167
+ | HTTP status | `code` | Meaning |
168
+ |---|---|---|
169
+ | 401 | `unauthorized` | missing/invalid/revoked API key |
170
+ | 403 | `forbidden` | key lacks the required scope |
171
+ | 404 | `not_found` | resource not found in this workspace |
172
+ | 409 | `conflict` | conflict (e.g. duplicate) |
173
+ | 400 | `validation_error` | bad request / invalid params |
174
+ | 5xx | `internal_error` | server error |
175
+
176
+ Transport-level failures map to `network` (no HTTP response), `aborted` (caller aborted), and `timeout` (`wait()` exceeded its budget).
177
+
178
+ ## TypeScript
179
+
180
+ The package ships full type declarations (`dist/index.d.ts` / `.d.cts`). All request/response types are exported:
181
+
182
+ ```ts
183
+ import type {
184
+ QLClientOptions, ChatInput, ChatResult, ChatStreamEvent,
185
+ AgentSummary, CapabilityInfo, CapabilityResult, HistoryPage,
186
+ WorkflowRun, Task, TaskCreateInput, WaitOptions, QLError,
187
+ } from "ql-agent-sdk";
188
+ ```
189
+
190
+ ## Using it in a Next.js App Router (server-side)
191
+
192
+ In a **Route Handler** (`app/api/.../route.ts`) or a server Component — the SDK is safe to use on the server:
193
+
194
+ ```ts
195
+ // app/api/agent/route.ts
196
+ import { QLClient, QLError } from "ql-agent-sdk";
197
+
198
+ export const runtime = "nodejs"; // uses native fetch + Web Streams
199
+
200
+ const ql = new QLClient({ apiKey: process.env.QL_API_KEY! });
201
+
202
+ export async function POST(req: Request) {
203
+ try {
204
+ const { prompt } = await req.json();
205
+ const res = await ql.agents.chat({
206
+ agentId: "governing",
207
+ messages: [{ role: "user", content: prompt }],
208
+ });
209
+ return Response.json({ ok: true, content: res.content });
210
+ } catch (err) {
211
+ if (err instanceof QLError) {
212
+ return Response.json({ ok: false, code: err.code, message: err.message }, { status: err.status ?? 500 });
213
+ }
214
+ throw err;
215
+ }
216
+ }
217
+ ```
218
+
219
+ For **streaming** to the browser from a Route Handler, forward the SDK's events as an SSE `ReadableStream` (see the streaming example above for consuming them).
220
+
221
+ ## Development
222
+
223
+ ```bash
224
+ npm install
225
+ npm run typecheck # tsc --noEmit
226
+ npm run lint # eslint (typescript-eslint)
227
+ npm test # vitest (unit: fetch, errors, SSE, polling, timeout, cancellation, pagination)
228
+ npm run build # tsup -> dist (ESM + CJS + .d.ts)
229
+ ```
230
+
231
+ ## Publishing
232
+
233
+ This package is **not** published. To publish when ready (requires an npm account/org):
234
+
235
+ ```bash
236
+ npm login
237
+ # from packages/sdk:
238
+ npm run build
239
+ npm publish --access public # or --access restricted for a scoped/owned package
240
+ ```
241
+
242
+ To install from a local tarball instead of publishing:
243
+
244
+ ```bash
245
+ cd packages/sdk && npm pack # -> ql-agent-sdk-0.1.0.tgz
246
+ npm install /absolute/path/to/ql-agent-sdk-0.1.0.tgz
247
+ ```