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