@betterlu/agentlu 1.7.1 → 1.8.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
@@ -10,7 +10,7 @@ const client = new OpenAI({ baseURL: "http://localhost:8912/v1", apiKey });
10
10
  const r = await client.responses.create({ model: "fast", input: "hi" });
11
11
  ```
12
12
 
13
- This client adds get-or-create registration, typed `runJson` / `runText`, use-case catalogs, background `wait`, client-side `timeoutMs`, and auto-retry on `409 thread_busy`.
13
+ This client adds get-or-create registration, typed `runJson` / `runText`, use-case catalogs, the two `/v1` tiers (`llm.*` / `agent.*`), error classification (`err.kind`, `isUnavailable`), background `wait`, bulk uploads, client-side `timeoutMs`, and auto-retry on `409 thread_busy`.
14
14
 
15
15
  ## Install
16
16
 
@@ -33,6 +33,76 @@ const { data, response } = await alu.run({
33
33
  console.log(response.output_text);
34
34
  ```
35
35
 
36
+ ## Two tiers
37
+
38
+ `/v1` has two documented tiers, and 1.8 exposes both as namespaces. Pick one and
39
+ stay there (see the `agentlu-api` skill for the full contract):
40
+
41
+ | | `alu.llm.*` (direct) | `alu.agent.*` (agent) |
42
+ |---|---|---|
43
+ | What runs | one profile-free provider call | profile + tool families + agent loop |
44
+ | Session | none (`store: false`) | a real thread you can continue |
45
+ | Tools | none | the closed family set (+ MCP for `api`) |
46
+ | Use it for | extraction, classification, short answers | research, phone/browser work, context |
47
+
48
+ ```ts
49
+ // Direct tier.
50
+ const { text } = await alu.llm.text("Name three colours.");
51
+ const { data } = await alu.llm.json<{ items: string[] }>(
52
+ { type: "object", properties: { items: { type: "array", items: { type: "string" } } }, required: ["items"] },
53
+ "two milk, one bread",
54
+ );
55
+ for await (const event of alu.llm.stream("Count to three")) console.log(event.type);
56
+
57
+ // Agent tier (default agent `api`; pin fast/research/… or "<app>/<name>").
58
+ const run = await alu.agent.run({ agent: "fast", input: "what changed in node 24?" });
59
+ const done = await alu.agent.background({ input: "research this deeply" }, { timeoutMs: 600_000 });
60
+ ```
61
+
62
+ `alu.run` / `runJson` / `runText` / `stream` remain the agent-tier aliases they
63
+ always were - nothing breaks by upgrading.
64
+
65
+ ## Errors: `kind` and fail-open
66
+
67
+ Every `AgentLuError` carries a coarse `kind`, and `isUnavailable(err)` is the
68
+ predicate for "the service could not answer right now" (offline, 5xx,
69
+ saturation, timeout, rate limit):
70
+
71
+ ```ts
72
+ import { isUnavailable } from "@betterlu/agentlu";
73
+
74
+ try {
75
+ await alu.llm.text(input);
76
+ } catch (err) {
77
+ if (isUnavailable(err)) return fallbackAnswer(); // degrade gracefully
78
+ throw err;
79
+ }
80
+ ```
81
+
82
+ | `err.kind` | Meaning |
83
+ |---|---|
84
+ | `unavailable` | offline / upstream 5xx / saturated - retry or degrade |
85
+ | `timeout` | the run or request hit its deadline |
86
+ | `rate_limited` | HTTP 429 or a retryable 409 (`thread_busy`) - back off (the SDK already retries with jitter) |
87
+ | `invalid_request` | the caller must fix the request (4xx) |
88
+ | `schema` | structured output failed or never validated |
89
+ | `incomplete` | the run finished without a complete answer |
90
+ | `internal` | anything else, including a cancelled run and the `systemone_unconfigured` config boundary (never retried, so it surfaces instead of degrading) |
91
+
92
+ ## Bulk uploads and background runs
93
+
94
+ ```ts
95
+ const files = await alu.uploadMany(photos, { concurrency: 4 });
96
+ await alu.agent.run({ input: "catalog these", fileIds: files.map((f) => f.id) });
97
+
98
+ const finished = await alu.useCase("myapp-long").runBackground({ input }, { intervalMs: 2_000 });
99
+ ```
100
+
101
+ `uploadMany` preserves input order and bounds parallelism (default 3).
102
+ `runBackground` = enqueue (`background: true`) + `responses.wait`; it resolves
103
+ with the terminal Response row (check `status`; a failed run is not an
104
+ exception there).
105
+
36
106
  ## JSON results
37
107
 
38
108
  `runJson` returns only a completed response with the server-provided `output_parsed` value. It preserves JSON `null`, `false`, `0`, and empty arrays. A missing parsed value, failed response, incomplete response, cancellation, or queued response throws an SDK error instead of parsing `output_text`. `background: true` is rejected by this helper; use raw `run` when polling is intended.
@@ -75,7 +145,55 @@ const { data } = await mealEnricher.runJson<{ meal: string }>({ input: "two eggs
75
145
 
76
146
  Set a field on `run` or `runJson` to override its registered default. `strict: false` still requires parsed JSON but does not require that value to match the schema.
77
147
 
78
- Do **not** put `maxOutputTokens` on client defaults — the server ignores it. Pass `max_output_tokens` (or `maxOutputTokens`) on the run.
148
+ `priority` declares the use-case tier and opts the client into its defaults
149
+ (`instant` = fast models/45s/2k/1 attempt, `standard` = 3 min/2 attempts, `deep`
150
+ = strongest/10 min/8k/3 attempts). A client with no declared priority keeps the
151
+ pre-tier envelope, so adding it is always a deliberate change:
152
+
153
+ ```ts
154
+ const parse = alu.useCase("myapp-parse", {
155
+ app: "myapp",
156
+ priority: "instant",
157
+ use_case_profile: { outputKind: "json_schema" },
158
+ defaults: { agent: "api", store: false, tools: [] },
159
+ });
160
+ ```
161
+
162
+ `maxOutputTokens` on client defaults is honored for defaults written from this
163
+ release on: a value stored earlier stays inert until the client re-declares its
164
+ defaults (or the console edits them). Prefer a request-level `maxOutputTokens`
165
+ when you only need it for one call.
166
+
167
+ ### App-owned agents
168
+
169
+ An app can declare its own agents at registration and call them as
170
+ `agent: "<app>/<name>"` from any client of the same app. The declaration's
171
+ instructions become the agent's profile body (caller `instructions` are
172
+ appended below them), and `tools` limits the families it may use.
173
+
174
+ `app` is slugified to the address grammar (`NestLu` → `nestlu`, `my app` →
175
+ `my-app`), and the server enforces the naming convention that keeps apps from
176
+ overwriting each other: the declaring client's name must own the app (`nestlu`
177
+ or `nestlu-...`). Any other declaration - or an address that can never parse -
178
+ is refused with a `warnings` entry on the registration response; the
179
+ registration itself still succeeds and the stored agents stay untouched. An
180
+ explicit request `agent`/`model` always wins over a client-default app agent:
181
+
182
+ ```ts
183
+ const chat = alu.useCase("nestlu-chat", {
184
+ app: "nestlu",
185
+ agents: [
186
+ {
187
+ name: "chat",
188
+ instructions: "You are the NestLu baby shopping-list assistant …", // the old pi/agents/nestlu.md body
189
+ tools: ["hublu", "web_search", "fetch_content"],
190
+ model: "MiniMax-M3",
191
+ temperature: 0.3,
192
+ },
193
+ ],
194
+ });
195
+ await chat.run({ agent: "nestlu/chat", input: "还缺什么?" });
196
+ ```
79
197
 
80
198
  Several use-cases sharing one client:
81
199