@betterlu/agentlu 1.0.0 → 1.1.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/dist/index.d.ts +73 -1
- package/dist/index.js +116 -8
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -14,13 +14,29 @@ export declare class AgentLuError extends Error {
|
|
|
14
14
|
readonly status: number;
|
|
15
15
|
readonly code: string;
|
|
16
16
|
readonly retryable: boolean;
|
|
17
|
-
|
|
17
|
+
/** Parsed Retry-After header, in milliseconds, when present. */
|
|
18
|
+
readonly retryAfterMs?: number | undefined;
|
|
19
|
+
constructor(status: number, code: string, message: string, retryable: boolean,
|
|
20
|
+
/** Parsed Retry-After header, in milliseconds, when present. */
|
|
21
|
+
retryAfterMs?: number | undefined);
|
|
18
22
|
}
|
|
19
23
|
export interface AgentLuOptions {
|
|
20
24
|
baseUrl?: string;
|
|
21
25
|
apiKey?: string;
|
|
22
26
|
fetch?: typeof fetch;
|
|
23
27
|
}
|
|
28
|
+
export interface ClientDefaults {
|
|
29
|
+
agent?: string;
|
|
30
|
+
model?: string;
|
|
31
|
+
instructions?: string;
|
|
32
|
+
temperature?: number;
|
|
33
|
+
maxOutputTokens?: number;
|
|
34
|
+
tools?: Array<string | {
|
|
35
|
+
type: string;
|
|
36
|
+
allowed_tools?: string[];
|
|
37
|
+
}>;
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
}
|
|
24
40
|
export interface RunOptions {
|
|
25
41
|
agent?: string;
|
|
26
42
|
model?: string;
|
|
@@ -33,11 +49,17 @@ export interface RunOptions {
|
|
|
33
49
|
conversation?: string;
|
|
34
50
|
store?: boolean;
|
|
35
51
|
person?: string | null;
|
|
52
|
+
owner?: "client" | "person";
|
|
53
|
+
title?: string;
|
|
36
54
|
timeout_ms?: number;
|
|
37
55
|
include?: string[];
|
|
56
|
+
temperature?: number;
|
|
57
|
+
max_output_tokens?: number;
|
|
38
58
|
/** JSON Schema. When set, `data` on the result is the validated object. */
|
|
39
59
|
schema?: Record<string, unknown>;
|
|
40
60
|
schemaName?: string;
|
|
61
|
+
/** Queue a background run instead of waiting. Returns the queued response. */
|
|
62
|
+
background?: boolean;
|
|
41
63
|
}
|
|
42
64
|
export interface ResponseObject {
|
|
43
65
|
id: string;
|
|
@@ -66,10 +88,27 @@ export declare class AgentLu {
|
|
|
66
88
|
register(name: string, opts?: {
|
|
67
89
|
person?: string | null;
|
|
68
90
|
instructions?: string | null;
|
|
91
|
+
defaults?: ClientDefaults;
|
|
69
92
|
}): Promise<ResponseObject & {
|
|
70
93
|
api_key?: string;
|
|
71
94
|
name: string;
|
|
72
95
|
}>;
|
|
96
|
+
/**
|
|
97
|
+
* A runner bound to one registered use-case client.
|
|
98
|
+
*
|
|
99
|
+
* The use-case's defaults (system prompt, tools, temperature, ...) live
|
|
100
|
+
* server-side on the client, so `run()` only needs the input. Registering a
|
|
101
|
+
* use-case is registering a client - `alu.useCase("mealu-enrich", {...})`.
|
|
102
|
+
*/
|
|
103
|
+
useCase(name: string, opts?: {
|
|
104
|
+
person?: string | null;
|
|
105
|
+
defaults?: ClientDefaults;
|
|
106
|
+
}): UseCaseRunner;
|
|
107
|
+
/** Register a use-case client and resolve to its api key. */
|
|
108
|
+
useCaseClient(name: string, opts?: {
|
|
109
|
+
person?: string | null;
|
|
110
|
+
defaults?: ClientDefaults;
|
|
111
|
+
}): Promise<string>;
|
|
73
112
|
me(): Promise<unknown>;
|
|
74
113
|
/**
|
|
75
114
|
* One-shot helper. When `schema` is given, returns the validated object as
|
|
@@ -107,3 +146,36 @@ export declare class AgentLu {
|
|
|
107
146
|
private request;
|
|
108
147
|
private raw;
|
|
109
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* A runner bound to one registered use-case (one service client + its
|
|
151
|
+
* server-side defaults). Holds its own key so multiple use-cases can share
|
|
152
|
+
* one parent AgentLu without clobbering each other.
|
|
153
|
+
*
|
|
154
|
+
* const alu = new AgentLu();
|
|
155
|
+
* const enrich = alu.useCase("mealu-enrich", {
|
|
156
|
+
* defaults: { instructions: "Parse meals...", temperature: 0.2 },
|
|
157
|
+
* });
|
|
158
|
+
* const { data } = await enrich.run({ input: "two eggs and toast" });
|
|
159
|
+
*/
|
|
160
|
+
export declare class UseCaseRunner {
|
|
161
|
+
private readonly parent;
|
|
162
|
+
readonly name: string;
|
|
163
|
+
private readonly registerOpts;
|
|
164
|
+
private apiKey;
|
|
165
|
+
private ready;
|
|
166
|
+
constructor(parent: AgentLu, name: string, opts?: {
|
|
167
|
+
person?: string | null;
|
|
168
|
+
defaults?: ClientDefaults;
|
|
169
|
+
});
|
|
170
|
+
/** Idempotent get-or-create; safe to call before every run. */
|
|
171
|
+
private ensure;
|
|
172
|
+
/**
|
|
173
|
+
* Run with this use-case's key. Request fields that are set override the
|
|
174
|
+
* use-case's registered defaults; unset fields inherit them server-side.
|
|
175
|
+
*/
|
|
176
|
+
run<T = unknown>(opts: RunOptions): Promise<{
|
|
177
|
+
data?: T;
|
|
178
|
+
response: ResponseObject;
|
|
179
|
+
}>;
|
|
180
|
+
stream(body: Record<string, unknown>): Promise<AsyncGenerator<Record<string, unknown>, any, any>>;
|
|
181
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -14,11 +14,15 @@ export class AgentLuError extends Error {
|
|
|
14
14
|
status;
|
|
15
15
|
code;
|
|
16
16
|
retryable;
|
|
17
|
-
|
|
17
|
+
retryAfterMs;
|
|
18
|
+
constructor(status, code, message, retryable,
|
|
19
|
+
/** Parsed Retry-After header, in milliseconds, when present. */
|
|
20
|
+
retryAfterMs) {
|
|
18
21
|
super(message);
|
|
19
22
|
this.status = status;
|
|
20
23
|
this.code = code;
|
|
21
24
|
this.retryable = retryable;
|
|
25
|
+
this.retryAfterMs = retryAfterMs;
|
|
22
26
|
this.name = "AgentLuError";
|
|
23
27
|
}
|
|
24
28
|
}
|
|
@@ -41,6 +45,23 @@ export class AgentLu {
|
|
|
41
45
|
this.apiKey = res.api_key;
|
|
42
46
|
return res;
|
|
43
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* A runner bound to one registered use-case client.
|
|
50
|
+
*
|
|
51
|
+
* The use-case's defaults (system prompt, tools, temperature, ...) live
|
|
52
|
+
* server-side on the client, so `run()` only needs the input. Registering a
|
|
53
|
+
* use-case is registering a client - `alu.useCase("mealu-enrich", {...})`.
|
|
54
|
+
*/
|
|
55
|
+
useCase(name, opts) {
|
|
56
|
+
return new UseCaseRunner(this, name, opts);
|
|
57
|
+
}
|
|
58
|
+
/** Register a use-case client and resolve to its api key. */
|
|
59
|
+
async useCaseClient(name, opts) {
|
|
60
|
+
const res = await this.request("POST", "/clients", { name, ...opts }, { auth: false });
|
|
61
|
+
if (!res.api_key)
|
|
62
|
+
throw new AgentLuError(500, "internal_error", "register returned no key", true);
|
|
63
|
+
return res.api_key;
|
|
64
|
+
}
|
|
44
65
|
async me() {
|
|
45
66
|
return this.request("GET", "/me");
|
|
46
67
|
}
|
|
@@ -59,8 +80,13 @@ export class AgentLu {
|
|
|
59
80
|
conversation: opts.conversation,
|
|
60
81
|
store: opts.store,
|
|
61
82
|
person: opts.person,
|
|
83
|
+
owner: opts.owner,
|
|
84
|
+
title: opts.title,
|
|
62
85
|
timeout_ms: opts.timeout_ms,
|
|
63
86
|
include: opts.include,
|
|
87
|
+
temperature: opts.temperature,
|
|
88
|
+
max_output_tokens: opts.max_output_tokens,
|
|
89
|
+
background: opts.background,
|
|
64
90
|
};
|
|
65
91
|
if (opts.schema) {
|
|
66
92
|
body.text = {
|
|
@@ -147,10 +173,11 @@ export class AgentLu {
|
|
|
147
173
|
return this.request("GET", "/health", undefined, { auth: false });
|
|
148
174
|
}
|
|
149
175
|
async request(method, path, body, opts) {
|
|
150
|
-
// `409 thread_busy`
|
|
151
|
-
//
|
|
176
|
+
// `409 thread_busy` and `503 too_many_active_runs` are correctness bounds,
|
|
177
|
+
// not rate limits. Retrying with backoff is almost always the right thing,
|
|
178
|
+
// so the SDK does it - honoring Retry-After when the server sends one.
|
|
152
179
|
let last;
|
|
153
|
-
for (let attempt = 0; attempt <
|
|
180
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
154
181
|
try {
|
|
155
182
|
const res = await this.raw(method, path, body, opts);
|
|
156
183
|
if (res.status === 204)
|
|
@@ -159,15 +186,17 @@ export class AgentLu {
|
|
|
159
186
|
return json;
|
|
160
187
|
}
|
|
161
188
|
catch (e) {
|
|
162
|
-
if (e instanceof AgentLuError &&
|
|
189
|
+
if (e instanceof AgentLuError &&
|
|
190
|
+
(e.code === "thread_busy" || e.code === "too_many_active_runs" || e.status === 503) &&
|
|
191
|
+
attempt < 4) {
|
|
163
192
|
last = e;
|
|
164
|
-
await sleep(
|
|
193
|
+
await sleep(retryDelayMs(e, attempt));
|
|
165
194
|
continue;
|
|
166
195
|
}
|
|
167
196
|
throw e;
|
|
168
197
|
}
|
|
169
198
|
}
|
|
170
|
-
throw last ?? new AgentLuError(
|
|
199
|
+
throw last ?? new AgentLuError(503, "too_many_active_runs", "too many retries", true);
|
|
171
200
|
}
|
|
172
201
|
async raw(method, path, body, opts) {
|
|
173
202
|
const headers = {};
|
|
@@ -196,11 +225,90 @@ export class AgentLu {
|
|
|
196
225
|
catch {
|
|
197
226
|
/* keep defaults */
|
|
198
227
|
}
|
|
199
|
-
throw new AgentLuError(res.status, code, message, retryable);
|
|
228
|
+
throw new AgentLuError(res.status, code, message, retryable, parseRetryAfter(res));
|
|
200
229
|
}
|
|
201
230
|
return res;
|
|
202
231
|
}
|
|
203
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* A runner bound to one registered use-case (one service client + its
|
|
235
|
+
* server-side defaults). Holds its own key so multiple use-cases can share
|
|
236
|
+
* one parent AgentLu without clobbering each other.
|
|
237
|
+
*
|
|
238
|
+
* const alu = new AgentLu();
|
|
239
|
+
* const enrich = alu.useCase("mealu-enrich", {
|
|
240
|
+
* defaults: { instructions: "Parse meals...", temperature: 0.2 },
|
|
241
|
+
* });
|
|
242
|
+
* const { data } = await enrich.run({ input: "two eggs and toast" });
|
|
243
|
+
*/
|
|
244
|
+
export class UseCaseRunner {
|
|
245
|
+
parent;
|
|
246
|
+
name;
|
|
247
|
+
registerOpts;
|
|
248
|
+
apiKey = null;
|
|
249
|
+
ready = null;
|
|
250
|
+
constructor(parent, name, opts) {
|
|
251
|
+
this.parent = parent;
|
|
252
|
+
this.name = name;
|
|
253
|
+
this.registerOpts = opts ?? {};
|
|
254
|
+
}
|
|
255
|
+
/** Idempotent get-or-create; safe to call before every run. */
|
|
256
|
+
ensure() {
|
|
257
|
+
if (this.apiKey)
|
|
258
|
+
return Promise.resolve();
|
|
259
|
+
if (!this.ready) {
|
|
260
|
+
this.ready = this.parent
|
|
261
|
+
.useCaseClient(this.name, this.registerOpts)
|
|
262
|
+
.then((key) => {
|
|
263
|
+
this.apiKey = key;
|
|
264
|
+
})
|
|
265
|
+
.catch((e) => {
|
|
266
|
+
this.ready = null;
|
|
267
|
+
throw e;
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
return this.ready;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Run with this use-case's key. Request fields that are set override the
|
|
274
|
+
* use-case's registered defaults; unset fields inherit them server-side.
|
|
275
|
+
*/
|
|
276
|
+
async run(opts) {
|
|
277
|
+
await this.ensure();
|
|
278
|
+
const parentKey = this.parent.apiKey;
|
|
279
|
+
this.parent.apiKey = this.apiKey ?? undefined;
|
|
280
|
+
try {
|
|
281
|
+
return await this.parent.run(opts);
|
|
282
|
+
}
|
|
283
|
+
finally {
|
|
284
|
+
this.parent.apiKey = parentKey;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async stream(body) {
|
|
288
|
+
await this.ensure();
|
|
289
|
+
const parentKey = this.parent.apiKey;
|
|
290
|
+
this.parent.apiKey = this.apiKey ?? undefined;
|
|
291
|
+
try {
|
|
292
|
+
return await this.parent.responses.stream(body);
|
|
293
|
+
}
|
|
294
|
+
finally {
|
|
295
|
+
this.parent.apiKey = parentKey;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function parseRetryAfter(res) {
|
|
300
|
+
const header = res.headers.get("retry-after");
|
|
301
|
+
if (!header)
|
|
302
|
+
return undefined;
|
|
303
|
+
const seconds = Number(header);
|
|
304
|
+
return Number.isFinite(seconds) ? seconds * 1000 : undefined;
|
|
305
|
+
}
|
|
306
|
+
function retryDelayMs(err, attempt) {
|
|
307
|
+
if (err.retryAfterMs !== undefined) {
|
|
308
|
+
return Math.min(10_000, Math.max(250, err.retryAfterMs));
|
|
309
|
+
}
|
|
310
|
+
return Math.min(10_000, 500 * 2 ** attempt);
|
|
311
|
+
}
|
|
204
312
|
function sleep(ms) {
|
|
205
313
|
return new Promise((r) => setTimeout(r, ms));
|
|
206
314
|
}
|
package/package.json
CHANGED