@betterlu/agentlu 1.8.0 → 1.9.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
@@ -54,7 +54,7 @@ const { data } = await alu.llm.json<{ items: string[] }>(
54
54
  );
55
55
  for await (const event of alu.llm.stream("Count to three")) console.log(event.type);
56
56
 
57
- // Agent tier (default agent `api`; pin fast/research/… or "<app>/<name>").
57
+ // Agent tier (default agent `api`; pin fast/thorough/… or "<app>/<name>").
58
58
  const run = await alu.agent.run({ agent: "fast", input: "what changed in node 24?" });
59
59
  const done = await alu.agent.background({ input: "research this deeply" }, { timeoutMs: 600_000 });
60
60
  ```
@@ -89,6 +89,27 @@ try {
89
89
  | `incomplete` | the run finished without a complete answer |
90
90
  | `internal` | anything else, including a cancelled run and the `systemone_unconfigured` config boundary (never retried, so it surfaces instead of degrading) |
91
91
 
92
+ ## Waiting for agentlu on boot
93
+
94
+ Apps that PM2 starts alongside agentlu (reboot, `pm2 restart agentlu`) should
95
+ wait for it before registering, in the background, so they don't warn on the
96
+ first `ECONNREFUSED`. `waitForReady` polls `/health` with backoff while it is
97
+ unreachable and resolves `true`, or `false` at the deadline. It never throws.
98
+
99
+ ```ts
100
+ void (async () => {
101
+ const up = await alu.waitForReady({
102
+ timeoutMs: 90_000,
103
+ onWait: ({ attempt }) => attempt === 1 && log.info("waiting for agentlu"),
104
+ });
105
+ try {
106
+ await useCases.ensure();
107
+ } catch (err) {
108
+ log.warn("agentlu register failed", { err, waited: up });
109
+ }
110
+ })();
111
+ ```
112
+
92
113
  ## Bulk uploads and background runs
93
114
 
94
115
  ```ts
@@ -11,7 +11,7 @@
11
11
  export interface WireCreateResponse {
12
12
  /** Execution backend. */
13
13
  "backend"?: "pi";
14
- /** Model id, or an agent name (stock OpenAI clients can only set `model`). Harness-qualified ids run other runtimes: `google/<id>` selects an Antigravity model (defaults to the read-only `google-research` agent unless `agent` names another Google mode). See GET /v1/models and GET /v1/models?harness=google. */
14
+ /** Model id, or an agent name (stock OpenAI clients can only set `model`). Harness-qualified ids run other runtimes: `google/<id>` selects an Antigravity model on the Google agent. See GET /v1/models and GET /v1/models?harness=google. */
15
15
  "model"?: string;
16
16
  "input"?: string | Array<{
17
17
  "role": "user" | "assistant" | "system" | "developer";
@@ -70,8 +70,8 @@ export interface WireCreateResponse {
70
70
  "tool_choice"?: "auto" | "none" | "required";
71
71
  "metadata"?: Record<string, string>;
72
72
  "include"?: Array<"reasoning" | "rounds" | "subagents" | "artifacts">;
73
- /** Which agent runs the request. The recommended general machine agent is `api`, which has no persona: `instructions` replace its system prompt. Pin `fast` for the chat-tuned version. Use `android` for the shared Android phone. `direct` selects the direct tier: the profile-free `api` agent with no tools and no session (see the Tiers section). An `<app>/<name>` id runs an agent the caller's app declared at registration. */
74
- "agent"?: "auto" | "fast" | "thorough" | "assistant" | "research" | "swarm" | "code" | "google" | "google-research" | "google-image" | "image" | "mealu" | "kitchen" | "waterlu" | "money" | "storylu" | "nestlu" | "android" | "api" | "digest" | "cradle" | "roleplay" | "stage" | "custom" | "direct" | string;
73
+ /** Which agent runs the request. The recommended general machine agent is `api`, which has no persona: `instructions` replace its system prompt. Pin `fast` for the chat-tuned version. `direct` selects the direct tier: the profile-free `api` agent with no tools and no session (see the Tiers section). `household/<slug>` runs a household agent built in the Agents studio (list them at GET /v1/agents). An `<app>/<name>` id runs an agent the caller's app declared at registration. `research` is an alias for `thorough` with Report output (a sourced report artifact). Retired ids (`mealu`, `nestlu`, `kitchen`, `waterlu`, `money`, `swarm`, `storylu`, `google-research`, `google-image`, `android`) return HTTP 400 `unknown_agent` with a pointer to their successor; the Android phone is now the `android` tool family on `assistant`. */
74
+ "agent"?: "auto" | "fast" | "thorough" | "assistant" | "code" | "google" | "image" | "api" | "digest" | "cradle" | "roleplay" | "custom" | "direct" | string | "mealu" | "nestlu" | "kitchen" | "waterlu" | "money" | "swarm" | "stage" | "storylu" | "google-research" | "google-image" | "android" | "research";
75
75
  /** Thinking level. Explicit `effort: "off"` is honored; other requests are clamped to the selected model's supported capabilities. The SDK does not promise provider benchmark parity. */
76
76
  "reasoning"?: {
77
77
  "effort"?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
@@ -84,7 +84,6 @@ export interface WireCreateResponse {
84
84
  "person"?: string | null;
85
85
  /** person = attribute the thread to the human matching `person` (shows in their sidebar). Requires the client flag `allow_person_threads`; without it a stored/threaded request is rejected with HTTP 403 `person_threads_not_allowed`. A sessionless `store: false` run creates no thread, so there is nothing to attribute and the flag is not consulted. */
86
86
  "owner"?: "client" | "person";
87
- "project"?: string;
88
87
  /** Opaque caller-side identity (one of your users or devices). Stored on the response and, for stored runs, its thread; filter with `?end_user=`. */
89
88
  "end_user"?: string;
90
89
  }
@@ -150,7 +149,7 @@ export interface WireRegisterClientRequest {
150
149
  /** Run defaults applied when a request omits the field: request > registered client default > use-case tier > agent/profile default. The built-in `store` default is true. Register one client per use-case (e.g. mealu-enrich) with its system prompt and tools baked in. */
151
150
  "defaults"?: {
152
151
  "backend"?: "pi";
153
- "agent"?: "auto" | "fast" | "thorough" | "assistant" | "research" | "swarm" | "code" | "google" | "google-research" | "google-image" | "image" | "mealu" | "kitchen" | "waterlu" | "money" | "storylu" | "nestlu" | "android" | "api" | "digest" | "cradle" | "roleplay" | "stage" | "custom" | "direct" | string;
152
+ "agent"?: "auto" | "fast" | "thorough" | "assistant" | "code" | "google" | "image" | "api" | "digest" | "cradle" | "roleplay" | "custom" | "direct" | string | "mealu" | "nestlu" | "kitchen" | "waterlu" | "money" | "swarm" | "stage" | "storylu" | "google-research" | "google-image" | "android" | "research";
154
153
  "model"?: string;
155
154
  "instructions"?: string;
156
155
  "temperature"?: number;
@@ -200,8 +199,8 @@ export interface WireRegisterClientRequest {
200
199
  /** Run defaults applied when a request omits the field: request > registered client default > use-case tier > agent/profile default. The built-in `store` default is true. Register one client per use-case (e.g. mealu-enrich) with its system prompt and tools baked in. */
201
200
  export interface WireClientDefaults {
202
201
  "backend"?: "pi";
203
- /** Which agent runs the request. The recommended general machine agent is `api`, which has no persona: `instructions` replace its system prompt. Pin `fast` for the chat-tuned version. Use `android` for the shared Android phone. `direct` selects the direct tier: the profile-free `api` agent with no tools and no session (see the Tiers section). An `<app>/<name>` id runs an agent the caller's app declared at registration. */
204
- "agent"?: "auto" | "fast" | "thorough" | "assistant" | "research" | "swarm" | "code" | "google" | "google-research" | "google-image" | "image" | "mealu" | "kitchen" | "waterlu" | "money" | "storylu" | "nestlu" | "android" | "api" | "digest" | "cradle" | "roleplay" | "stage" | "custom" | "direct" | string;
202
+ /** Which agent runs the request. The recommended general machine agent is `api`, which has no persona: `instructions` replace its system prompt. Pin `fast` for the chat-tuned version. `direct` selects the direct tier: the profile-free `api` agent with no tools and no session (see the Tiers section). `household/<slug>` runs a household agent built in the Agents studio (list them at GET /v1/agents). An `<app>/<name>` id runs an agent the caller's app declared at registration. `research` is an alias for `thorough` with Report output (a sourced report artifact). Retired ids (`mealu`, `nestlu`, `kitchen`, `waterlu`, `money`, `swarm`, `storylu`, `google-research`, `google-image`, `android`) return HTTP 400 `unknown_agent` with a pointer to their successor; the Android phone is now the `android` tool family on `assistant`. */
203
+ "agent"?: "auto" | "fast" | "thorough" | "assistant" | "code" | "google" | "image" | "api" | "digest" | "cradle" | "roleplay" | "custom" | "direct" | string | "mealu" | "nestlu" | "kitchen" | "waterlu" | "money" | "swarm" | "stage" | "storylu" | "google-research" | "google-image" | "android" | "research";
205
204
  /** ModelConfig id (GET /v1/models). Stock clients may also use an agent name here; prefer explicit `agent` for machine calls. */
206
205
  "model"?: string;
207
206
  /** System prompt for every run of this client. */
@@ -264,7 +263,7 @@ export interface WireAppAgentDeclaration {
264
263
  }
265
264
  export interface WireCreateThreadRequest {
266
265
  "title"?: string;
267
- "agent"?: "auto" | "fast" | "thorough" | "assistant" | "research" | "swarm" | "code" | "google" | "google-research" | "google-image" | "image" | "mealu" | "kitchen" | "waterlu" | "money" | "storylu" | "nestlu" | "android" | "api" | "digest" | "cradle" | "roleplay" | "stage" | "custom";
266
+ "agent"?: "auto" | "fast" | "thorough" | "assistant" | "code" | "google" | "image" | "api" | "digest" | "cradle" | "roleplay" | "custom" | "research" | string;
268
267
  "model"?: string;
269
268
  "person"?: string | null;
270
269
  "owner"?: "client" | "person";
@@ -300,7 +299,7 @@ export interface WireResponse {
300
299
  "created_at": number;
301
300
  "status": "queued" | "in_progress" | "completed" | "failed" | "incomplete" | "cancelled";
302
301
  "backend": "pi";
303
- "agent": "auto" | "fast" | "thorough" | "assistant" | "research" | "swarm" | "code" | "google" | "google-research" | "google-image" | "image" | "mealu" | "kitchen" | "waterlu" | "money" | "storylu" | "nestlu" | "android" | "api" | "digest" | "cradle" | "roleplay" | "stage" | "custom";
302
+ "agent": "auto" | "fast" | "thorough" | "assistant" | "code" | "google" | "image" | "api" | "digest" | "cradle" | "roleplay" | "custom";
304
303
  "model": string;
305
304
  "conversation"?: {
306
305
  "id": string;
@@ -411,7 +410,7 @@ export interface WireResponse {
411
410
  };
412
411
  "android"?: {
413
412
  "sessionId"?: string;
414
- "username"?: "larry" | "misa";
413
+ "username"?: string;
415
414
  "ownerId"?: string;
416
415
  "status": "active" | "needs_human" | "human_control" | "idle" | "closed" | "interrupted" | "reserved";
417
416
  "action"?: string;
@@ -487,7 +486,7 @@ export interface WireThread {
487
486
  "created_at": number;
488
487
  "updated_at": number;
489
488
  "title": string;
490
- "agent": "auto" | "fast" | "thorough" | "assistant" | "research" | "swarm" | "code" | "google" | "google-research" | "google-image" | "image" | "mealu" | "kitchen" | "waterlu" | "money" | "storylu" | "nestlu" | "android" | "api" | "digest" | "cradle" | "roleplay" | "stage" | "custom";
489
+ "agent": "auto" | "fast" | "thorough" | "assistant" | "code" | "google" | "image" | "api" | "digest" | "cradle" | "roleplay" | "custom";
491
490
  "model": string | null;
492
491
  /** "api" for threads created through /v1; "chat" when a human started it. */
493
492
  "origin": string;
@@ -526,7 +525,7 @@ export interface WireClient {
526
525
  "allow_person_threads"?: boolean;
527
526
  "defaults"?: {
528
527
  "backend"?: "pi";
529
- "agent"?: "auto" | "fast" | "thorough" | "assistant" | "research" | "swarm" | "code" | "google" | "google-research" | "google-image" | "image" | "mealu" | "kitchen" | "waterlu" | "money" | "storylu" | "nestlu" | "android" | "api" | "digest" | "cradle" | "roleplay" | "stage" | "custom" | "direct" | string;
528
+ "agent"?: "auto" | "fast" | "thorough" | "assistant" | "code" | "google" | "image" | "api" | "digest" | "cradle" | "roleplay" | "custom" | "direct" | string | "mealu" | "nestlu" | "kitchen" | "waterlu" | "money" | "swarm" | "stage" | "storylu" | "google-research" | "google-image" | "android" | "research";
530
529
  "model"?: string;
531
530
  "instructions"?: string;
532
531
  "temperature"?: number;
@@ -592,7 +591,8 @@ export interface WireError {
592
591
  };
593
592
  }
594
593
  export interface WireAgentInfo {
595
- "id": "auto" | "fast" | "thorough" | "assistant" | "research" | "swarm" | "code" | "google" | "google-research" | "google-image" | "image" | "mealu" | "kitchen" | "waterlu" | "money" | "storylu" | "nestlu" | "android" | "api" | "digest" | "cradle" | "roleplay" | "stage" | "custom";
594
+ /** A builtin agent id, or `household/<slug>` for an agent built in the household studio. */
595
+ "id": string;
596
596
  "object": "agent";
597
597
  "profile": string;
598
598
  "description": string;
@@ -604,6 +604,8 @@ export interface WireAgentInfo {
604
604
  "long_running": boolean;
605
605
  /** Runtime that serves this agent. */
606
606
  "harness": "pi" | "google";
607
+ /** Household agents only: the capability ids the agent was built with. */
608
+ "capabilities"?: string[];
607
609
  }
608
610
  export interface WireModelInfo {
609
611
  "id": string;
@@ -616,6 +618,8 @@ export interface WireModelInfo {
616
618
  "default": boolean;
617
619
  /** Runtime that serves this model. */
618
620
  "harness": "pi" | "google" | "systemone";
621
+ /** Reasoning levels the model accepts (google models: the values the ACP thought_level option advertises). */
622
+ "efforts"?: Array<"off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max">;
619
623
  /** Present on decision models: how to call them. */
620
624
  "decision"?: {
621
625
  "endpoint": "/v1/decisions";
@@ -646,7 +650,7 @@ export interface WireResponseList {
646
650
  "created_at": number;
647
651
  "status": "queued" | "in_progress" | "completed" | "failed" | "incomplete" | "cancelled";
648
652
  "backend": "pi";
649
- "agent": "auto" | "fast" | "thorough" | "assistant" | "research" | "swarm" | "code" | "google" | "google-research" | "google-image" | "image" | "mealu" | "kitchen" | "waterlu" | "money" | "storylu" | "nestlu" | "android" | "api" | "digest" | "cradle" | "roleplay" | "stage" | "custom";
653
+ "agent": "auto" | "fast" | "thorough" | "assistant" | "code" | "google" | "image" | "api" | "digest" | "cradle" | "roleplay" | "custom";
650
654
  "model": string;
651
655
  "conversation"?: {
652
656
  "id": string;
@@ -752,7 +756,7 @@ export interface WireResponseList {
752
756
  };
753
757
  "android"?: {
754
758
  "sessionId"?: string;
755
- "username"?: "larry" | "misa";
759
+ "username"?: string;
756
760
  "ownerId"?: string;
757
761
  "status": "active" | "needs_human" | "human_control" | "idle" | "closed" | "interrupted" | "reserved";
758
762
  "action"?: string;
@@ -778,7 +782,7 @@ export interface WireThreadList {
778
782
  "created_at": number;
779
783
  "updated_at": number;
780
784
  "title": string;
781
- "agent": "auto" | "fast" | "thorough" | "assistant" | "research" | "swarm" | "code" | "google" | "google-research" | "google-image" | "image" | "mealu" | "kitchen" | "waterlu" | "money" | "storylu" | "nestlu" | "android" | "api" | "digest" | "cradle" | "roleplay" | "stage" | "custom";
785
+ "agent": "auto" | "fast" | "thorough" | "assistant" | "code" | "google" | "image" | "api" | "digest" | "cradle" | "roleplay" | "custom";
782
786
  "model": string | null;
783
787
  "origin": string;
784
788
  "end_user"?: string | null;
@@ -832,7 +836,7 @@ export interface WireFileList {
832
836
  export interface WireAgentList {
833
837
  "object": "list";
834
838
  "data": Array<{
835
- "id": "auto" | "fast" | "thorough" | "assistant" | "research" | "swarm" | "code" | "google" | "google-research" | "google-image" | "image" | "mealu" | "kitchen" | "waterlu" | "money" | "storylu" | "nestlu" | "android" | "api" | "digest" | "cradle" | "roleplay" | "stage" | "custom";
839
+ "id": string;
836
840
  "object": "agent";
837
841
  "profile": string;
838
842
  "description": string;
@@ -841,6 +845,7 @@ export interface WireAgentList {
841
845
  "supports_images": boolean;
842
846
  "long_running": boolean;
843
847
  "harness": "pi" | "google";
848
+ "capabilities"?: string[];
844
849
  }>;
845
850
  /** Opaque keyset cursor; null on the last page. */
846
851
  "next_cursor": string | null;
@@ -856,6 +861,7 @@ export interface WireModelList {
856
861
  "available": boolean;
857
862
  "default": boolean;
858
863
  "harness": "pi" | "google" | "systemone";
864
+ "efforts"?: Array<"off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max">;
859
865
  "decision"?: {
860
866
  "endpoint": "/v1/decisions";
861
867
  "question_types": Array<"noul" | "choice" | "score">;
package/dist/index.d.ts CHANGED
@@ -85,7 +85,7 @@ export interface AndroidOperation {
85
85
  }
86
86
  export interface AndroidActivity {
87
87
  sessionId: string;
88
- username: "larry" | "misa";
88
+ username: string;
89
89
  ownerId?: string;
90
90
  status: AndroidStatus;
91
91
  action?: string;
@@ -313,6 +313,17 @@ export type UseCaseCatalog<T extends Record<string, UseCaseDeclaration>> = {
313
313
  /** Idempotent get-or-create for every use-case. Safe to call on boot. */
314
314
  ensure: () => Promise<void>;
315
315
  };
316
+ export interface WaitForReadyOptions {
317
+ /** Give up after this long. Default 90 s. */
318
+ timeoutMs?: number;
319
+ /** Called before each backoff while the service is unreachable (log at info). */
320
+ onWait?: (info: {
321
+ attempt: number;
322
+ elapsedMs: number;
323
+ error: AgentLuError;
324
+ }) => void;
325
+ signal?: AbortSignal;
326
+ }
316
327
  export interface RunOptions {
317
328
  backend?: Backend;
318
329
  agent?: string;
@@ -590,7 +601,7 @@ export declare class AgentLu {
590
601
  /**
591
602
  * Agent tier: a profile, tool families, threads and background runs. The
592
603
  * default agent is `api` (no persona - `instructions` are the system prompt);
593
- * pin `fast`/`research`/... or an app-owned `<app>/<name>` id when needed.
604
+ * pin `fast`/`thorough`/... or an app-owned `<app>/<name>` id when needed.
594
605
  */
595
606
  readonly agent: AgentTierApi;
596
607
  /** Build a direct-tier run body from a short-form `llm.*` request. */
@@ -666,6 +677,14 @@ export declare class AgentLu {
666
677
  models(): Promise<unknown>;
667
678
  tools(): Promise<unknown>;
668
679
  health(): Promise<unknown>;
680
+ /**
681
+ * Wait until the service answers `/health`, retrying while it is unreachable
682
+ * (offline, 502/503/504, timeout) with jittered backoff. Resolves `true` once
683
+ * it answers and `false` at the deadline or on abort; never throws. Call it
684
+ * on boot before `ensure()`/`register()` so an app started alongside agentlu
685
+ * waits quietly instead of warning on the first ECONNREFUSED.
686
+ */
687
+ waitForReady(opts?: WaitForReadyOptions): Promise<boolean>;
669
688
  private request;
670
689
  private raw;
671
690
  private rawOnce;
package/dist/index.js CHANGED
@@ -431,7 +431,7 @@ export class AgentLu {
431
431
  /**
432
432
  * Agent tier: a profile, tool families, threads and background runs. The
433
433
  * default agent is `api` (no persona - `instructions` are the system prompt);
434
- * pin `fast`/`research`/... or an app-owned `<app>/<name>` id when needed.
434
+ * pin `fast`/`thorough`/... or an app-owned `<app>/<name>` id when needed.
435
435
  */
436
436
  agent = {
437
437
  run: (opts) => this.run(withAgentTierDefault(opts)),
@@ -768,6 +768,44 @@ export class AgentLu {
768
768
  async health() {
769
769
  return this.request("GET", "/health", undefined, { auth: false });
770
770
  }
771
+ /**
772
+ * Wait until the service answers `/health`, retrying while it is unreachable
773
+ * (offline, 502/503/504, timeout) with jittered backoff. Resolves `true` once
774
+ * it answers and `false` at the deadline or on abort; never throws. Call it
775
+ * on boot before `ensure()`/`register()` so an app started alongside agentlu
776
+ * waits quietly instead of warning on the first ECONNREFUSED.
777
+ */
778
+ async waitForReady(opts = {}) {
779
+ const timeoutMs = opts.timeoutMs ?? 90_000;
780
+ const start = Date.now();
781
+ for (let attempt = 1;; attempt++) {
782
+ if (opts.signal?.aborted)
783
+ return false;
784
+ try {
785
+ await this.request("GET", "/health", undefined, {
786
+ auth: false,
787
+ maxAttempts: 1,
788
+ timeoutMs: 5_000,
789
+ signal: opts.signal,
790
+ });
791
+ return true;
792
+ }
793
+ catch (e) {
794
+ if (opts.signal?.aborted)
795
+ return false;
796
+ const err = e instanceof AgentLuError ? e : null;
797
+ // Any other answer (401, 404, ...) means the server is up.
798
+ if (!err || (err.kind !== "unavailable" && err.kind !== "timeout"))
799
+ return true;
800
+ const elapsedMs = Date.now() - start;
801
+ if (elapsedMs >= timeoutMs)
802
+ return false;
803
+ opts.onWait?.({ attempt, elapsedMs, error: err });
804
+ const delay = Math.min(5_000, 500 * 2 ** (attempt - 1)) * (0.75 + Math.random() * 0.5);
805
+ await sleep(Math.min(delay, timeoutMs - elapsedMs));
806
+ }
807
+ }
808
+ }
771
809
  async request(method, path, body, opts) {
772
810
  const res = await this.raw(method, path, body, opts);
773
811
  if (res.status === 204)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@betterlu/agentlu",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
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",