@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 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
- constructor(status: number, code: string, message: string, retryable: boolean);
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
- constructor(status, code, message, retryable) {
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` is a correctness bound, not a rate limit. Retrying with
151
- // a short backoff is almost always the right thing, so the SDK does it.
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 < 4; 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 && e.code === "thread_busy" && attempt < 3) {
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(500 * 2 ** attempt);
194
+ await sleep(retryDelayMs(e, attempt));
165
195
  continue;
166
196
  }
167
197
  throw e;
168
198
  }
169
199
  }
170
- throw last ?? new AgentLuError(409, "thread_busy", "thread busy", true);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@betterlu/agentlu",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Client for the agentlu service API (/v1). Or just use the openai SDK pointed at http://localhost:8912/v1.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",