nuabase 2.3.11 → 3.0.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +233 -200
  3. package/dist/chunks/local-agent-Bj-QKikS.d.cts +270 -0
  4. package/dist/chunks/local-agent-Bj-QKikS.d.mts +270 -0
  5. package/dist/cjs/chunks/strict-json-schema-bKwaMWm1.cjs +145 -0
  6. package/dist/cjs/index.cjs +989 -11504
  7. package/dist/cjs/local-agent.cjs +819 -877
  8. package/dist/esm/chunks/strict-json-schema-Du2LLrVP.mjs +138 -0
  9. package/dist/esm/index.mjs +987 -11502
  10. package/dist/esm/local-agent.mjs +792 -878
  11. package/dist/index.d.cts +94 -0
  12. package/dist/index.d.mts +94 -0
  13. package/dist/local-agent.d.cts +150 -0
  14. package/dist/local-agent.d.mts +150 -0
  15. package/package.json +12 -17
  16. package/dist/cjs/chunks/strict-json-schema-DUE7RTxG.cjs +0 -177
  17. package/dist/esm/chunks/strict-json-schema-DI9qiH5h.mjs +0 -174
  18. package/dist/types/__tests__/helpers/client-factory.d.ts +0 -39
  19. package/dist/types/__tests__/helpers/index.d.ts +0 -2
  20. package/dist/types/backend/direct.d.ts +0 -29
  21. package/dist/types/backend/gateway.d.ts +0 -24
  22. package/dist/types/backend/types.d.ts +0 -68
  23. package/dist/types/index.d.cts +0 -7
  24. package/dist/types/index.d.mts +0 -7
  25. package/dist/types/lib/auth.d.ts +0 -14
  26. package/dist/types/lib/env.d.ts +0 -1
  27. package/dist/types/lib/error-response.d.ts +0 -6
  28. package/dist/types/lib/error-utils.d.ts +0 -6
  29. package/dist/types/local-agent.d.cts +0 -2
  30. package/dist/types/local-agent.d.mts +0 -2
  31. package/dist/types/nua-llm-core/index.d.ts +0 -23
  32. package/dist/types/nua-llm-core/lib/error-utils.d.ts +0 -6
  33. package/dist/types/nua-llm-core/lib/logger.d.ts +0 -44
  34. package/dist/types/nua-llm-core/lib/nua-errors.d.ts +0 -12
  35. package/dist/types/nua-llm-core/lib/prompt-template-utils.d.ts +0 -2
  36. package/dist/types/nua-llm-core/lib/schema-utils.d.ts +0 -1
  37. package/dist/types/nua-llm-core/lib/stable-stringify.d.ts +0 -1
  38. package/dist/types/nua-llm-core/lib/strict-json-schema.d.ts +0 -7
  39. package/dist/types/nua-llm-core/lib/types.d.ts +0 -8
  40. package/dist/types/nua-llm-core/lib/validate-mappable-input-data.d.ts +0 -4
  41. package/dist/types/nua-llm-core/local-agent.d.ts +0 -7
  42. package/dist/types/nua-llm-core/modules/agent/agent-loop.d.ts +0 -10
  43. package/dist/types/nua-llm-core/modules/agent/agent-turn.d.ts +0 -17
  44. package/dist/types/nua-llm-core/modules/agent/provider-formatters.d.ts +0 -22
  45. package/dist/types/nua-llm-core/modules/agent/run-agent.d.ts +0 -7
  46. package/dist/types/nua-llm-core/modules/agent/types.d.ts +0 -90
  47. package/dist/types/nua-llm-core/modules/cast/cast-request.d.ts +0 -32
  48. package/dist/types/nua-llm-core/modules/cast/envelope.d.ts +0 -9
  49. package/dist/types/nua-llm-core/modules/cast/list-schema.d.ts +0 -32
  50. package/dist/types/nua-llm-core/modules/cast/prompts/list-prompt.d.ts +0 -3
  51. package/dist/types/nua-llm-core/modules/cast/prompts/render-prompt.d.ts +0 -7
  52. package/dist/types/nua-llm-core/modules/cast/prompts/value-prompt.d.ts +0 -3
  53. package/dist/types/nua-llm-core/modules/cast/run-cast.d.ts +0 -44
  54. package/dist/types/nua-llm-core/modules/engine/engine-router.d.ts +0 -23
  55. package/dist/types/nua-llm-core/modules/engine/http/http-engine.d.ts +0 -13
  56. package/dist/types/nua-llm-core/modules/engine/http/provider-call.d.ts +0 -33
  57. package/dist/types/nua-llm-core/modules/engine/http/provider-config.d.ts +0 -70
  58. package/dist/types/nua-llm-core/modules/engine/http/provider-engines.d.ts +0 -35
  59. package/dist/types/nua-llm-core/modules/engine/llm-engine.d.ts +0 -73
  60. package/dist/types/nua-llm-core/modules/engine/local-agent/adapters/claude-code.d.ts +0 -2
  61. package/dist/types/nua-llm-core/modules/engine/local-agent/adapters/codex.d.ts +0 -2
  62. package/dist/types/nua-llm-core/modules/engine/local-agent/adapters/failure-classification.d.ts +0 -4
  63. package/dist/types/nua-llm-core/modules/engine/local-agent/adapters/index.d.ts +0 -3
  64. package/dist/types/nua-llm-core/modules/engine/local-agent/adapters/pi.d.ts +0 -2
  65. package/dist/types/nua-llm-core/modules/engine/local-agent/adapters/types.d.ts +0 -61
  66. package/dist/types/nua-llm-core/modules/engine/local-agent/agent-id.d.ts +0 -4
  67. package/dist/types/nua-llm-core/modules/engine/local-agent/create-local-agent.d.ts +0 -36
  68. package/dist/types/nua-llm-core/modules/engine/local-agent/detect.d.ts +0 -20
  69. package/dist/types/nua-llm-core/modules/engine/local-agent/local-agent-engine.d.ts +0 -32
  70. package/dist/types/nua-llm-core/modules/engine/local-agent/local-agent.d.ts +0 -25
  71. package/dist/types/nua-llm-core/modules/engine/local-agent/process-runner.d.ts +0 -16
  72. package/dist/types/nua-llm-core/modules/engine/local-agent/semaphore.d.ts +0 -8
  73. package/dist/types/nua-llm-core/modules/execution/llm-response-extraction.d.ts +0 -5
  74. package/dist/types/nua-llm-core/modules/json-schema-validation/ajv-instances.d.ts +0 -12
  75. package/dist/types/nua-llm-core/modules/json-schema-validation/nua-json-schema-value-validation.d.ts +0 -10
  76. package/dist/types/nua-llm-core/modules/json-schema-validation/validate-json-schema.d.ts +0 -2
  77. package/dist/types/nua-llm-core/modules/model-info/index.d.ts +0 -15
  78. package/dist/types/nua-llm-core/modules/streaming/sse-parser.d.ts +0 -7
  79. package/dist/types/nua-llm-core/nua-llm-client.d.ts +0 -37
  80. package/dist/types/nua.d.ts +0 -42
package/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # nuabase
2
2
 
3
+ ## 3.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - bump version for release
8
+ Move build and test instructions out of the README
9
+ Type list() rows by the output's name
10
+ Answer a terminal window that didn't open as open_failed
11
+ Let local agents take an effort, and share one process limit across models
12
+ Keep a schema's refs resolvable inside the cast envelope
13
+ Validate cast results with Zod instead of AJV
14
+ Develop nuabase against zod 4.6.7
15
+ Say why a cast failed in the engine's own words
16
+ Start an agent session in one call: startAgentSession()
17
+ Bundle nuabase's type declarations in the same build as its JavaScript
18
+ Start interactive coding-agent sessions from nuabase/local-agent
19
+ Name edge runtimes, not browsers, as the reason to avoid node: imports
20
+ Move the Nuabase Gateway out of nua-llm
21
+ Rewrite nuabase's README around Quick Start and reference tables
22
+ Describe nuabase's data flow at the top of its README
23
+ Reject a @nuabase/gateway import in nuabase, and mark unpublished packages private
24
+ Have Nua call nua-llm-core's client directly
25
+ Split the Nuabase API client into @nuabase/gateway
26
+ Use provider prompt caching for casts and agents
27
+ Report provider prompt-cache tokens in usage
28
+ Add DeepSeek as a provider and move aliases to current models
29
+ Send Gemini thought signatures back with function calls
30
+
31
+ ## 3.0.0
32
+
33
+ ### Major Changes
34
+
35
+ - be95150: `nuabase` now only runs casts in your process, on LLM providers' APIs with your keys (`Nua.direct({ providers })`) or on a coding agent on your machine (`Nua.direct({ localAgent })`).
36
+
37
+ Removed: `Nua.gateway()`, `nua.queueGet()`, `nua.queueList()`, and the `GatewayConfig`, `GatewayMeta` and `QueueResult` types. A result's `source` is now always `'direct'`.
38
+
39
+ Also removed: the `LlmBackend`, `CastValueParams` and `CastArrayParams` types. `Nua`'s constructor is private, so nothing could implement or pass them.
40
+
41
+ `nuabase` now needs Node.js 18 or later (it said `>=16.6.0`), for the global `fetch` it calls LLM providers with.
42
+
43
+ ### Minor Changes
44
+
45
+ - 9ed74c9: `nuabase/local-agent` can now start an interactive coding-agent session in the user's project, for the user to carry on in a terminal: `startAgentSession()` writes the first message and a shell launcher that starts Claude Code, Codex or Pi on it (in the agent's auto mode with `approval: 'auto'`), and on macOS opens it in a new terminal window. It answers with what it did: `opened`, `command` (the user runs it in a terminal), `open_failed` (the files are written but the window didn't open, with the error) or `refused` with the reason. `agentSessionMode()` says beforehand which of these the platform allows.
46
+ - 61eaba5: A failed cast now says why on its own: `reason` is the last attempt's failure in the engine's own words (a coding agent's or provider's message, fit to show a user), and `attempts` is how many were made (0 when the request was refused before any). `error` stays the one-line summary for logs. Coding-agent failures no longer prefix the agent's name (`claude: `) to its message, and Codex's API errors are reported by their message instead of as JSON.
47
+ - 5028584: DeepSeek is now a direct provider: pass `providers: { deepseek: { apiKey } }` and name a DeepSeek model such as `deepseek-flash` or `deepseek-v4-pro`. DeepSeek requests run with thinking turned off, and schemas are described in the prompt because DeepSeek has no strict structured outputs. A new `deepseek` alias uses DeepSeek V4 Pro, directly or through OpenRouter.
48
+
49
+ The model aliases now point at current models: `gemini` → Gemini 3.8 Flash, `qwen` → Qwen3.8 2.4T A95B, `kimi` → Kimi K3, `sonnet` → Claude Sonnet 5.5, `opus` → Claude Opus 5.5, `gpt` → GPT-6.1 Sol, and the OpenRouter fallback for `fast` → GLM-5.3 Flash.
50
+ - 25569d9: Local agents take an `effort` option (`'low' | 'medium' | 'high'`), passed to the CLI's own setting: Claude Code `--effort`, Codex `model_reasoning_effort`, Pi `--thinking`. Left out, the CLI's default applies. Codex casts no longer run at low reasoning effort unless you ask for it with `effort: 'low'`.
51
+ - 25569d9: `LocalAgent.with({ model, effort, timeoutMs })` gives the same agent with other call options, sharing its executable, environment, logger and `concurrency` limit. One `localAgent()` per CLI can now serve casts on several of its models, such as Codex models that have no alias, without each getting its own limit on processes running at once.
52
+ - 449e580: `usage` now reports `promptCacheReadTokens` and `promptCacheWriteTokens` when the provider does: the prompt tokens it read from or wrote to its prompt cache. They are part of `promptTokens`. Reported for OpenRouter, DeepSeek and Gemini, and for the Claude Code, Codex and Pi local agents.
53
+ - 3abfeee: Casts and agents now make use of provider prompt caching. Claude on OpenRouter caches the prompt, instructions and schema of a cast, so repeat calls of the same cast read them at about a tenth of the price, and agent turns read the conversation the previous turn sent. Casts through OpenRouter stay on one upstream provider, so models that cache automatically (DeepSeek, GLM, Kimi, GPT) reuse it too.
54
+ - 4f38416: Results are now parsed with your Zod schema instead of being checked against its JSON Schema with AJV. `data` is Zod's parsed output: `.default()`s are filled in, `.transform()`s run, `.refine()` checks apply (a failing answer is retried), and unknown object keys are dropped. The JSON Schema shown to the model now describes the schema's input side, so schemas with `.transform()` no longer throw. AJV is no longer bundled.
55
+
56
+ ### Patch Changes
57
+
58
+ - b9b5da9: Type declarations now resolve under every module resolution, including `node16`/`nodenext` from ES modules and CommonJS, where nuabase's exports used to type as `any`. Each entry point ships one self-contained `.d.mts` and `.d.cts`, bundled by the same build as the JavaScript. `LlmProviderId` and `Logger` are now exported. The ES module build imports Node's built-ins directly instead of through `createRequire`.
59
+ - dec76aa: Schemas with `$ref`s, such as those using `.meta({ id })` or recursive schemas, now work with native schema enforcement. The schema sent to the engine had refs that pointed nowhere, so engines either rejected it or did not enforce it.
60
+ - 267e07a: Tool-calling agents on Gemini 3 models now work: Gemini's thought signatures are sent back with each function call, which Gemini 3 and later require.
61
+ - 3bbdc4c: `list()` rows are typed by the output's name when it is a literal: with `output: { name: 'account', schema: z.string() }`, each row is `{ id: string | number; account: string }` instead of a row whose every field has the output's type.
62
+
3
63
  ## 2.3.11
4
64
 
5
65
  ### Patch Changes
package/README.md CHANGED
@@ -1,76 +1,38 @@
1
- ## Nuabase TypeScript SDK
1
+ # Nuabase TypeScript SDK
2
2
 
3
- Nuabase turns LLM prompts into type-safe functions in under five lines of code. Set up your free account now at [Nuabase](https://nuabase.com).
3
+ Nuabase turns LLM prompts and Zod schemas into type-safe functions. Pass a prompt, input data, and a Zod schema; receive validated, typed data back.
4
4
 
5
- With Nuabase, you write a prompt, specify the output schema, and get back a well-typed function. You can invoke it with input data, like any other code in your application, and receive data that matches the exact schema you specified.
5
+ Every call runs directly in your process—either against the LLM provider API you choose, using your API keys, or via a local coding agent CLI already installed on your machine (`claude`, `codex`, or `pi`).
6
6
 
7
- Example use-cases:
7
+ **Common use cases:**
8
+ - Extract structured fields from free-form text to auto-fill complex forms
9
+ - Enrich arrays of items (e.g., adding nutrition facts to a list of foods)
10
+ - Classify and tag sales leads with company metadata
8
11
 
9
- - Provide free-form text input for faster filling of complex HTML forms
10
- - Enrich a list of food items with nutrition facts
11
- - Tag sales leads with more details about the company
12
-
13
- Behind the scenes, Nuabase runs the LLM transformation as async jobs, and once ready, call your server API with the results using Webhooks. It can also stream the result directly to your front-end using SSE. You can also see all requests and responses as they happen in the [Nuabase Console](https://console.nuabase.com). All outputs are guaranteed to match the schema you specified.
14
-
15
- One major reason I built Nuabase is the need for granular, row-level caching. For example, let's say you want to classify your bank transaction entries, and map them to your chart of accounts. With Nuabase's row-level caching, it will only send new entries to the LLM. Any specific entry that it has seen before will be returned from the cache. This also means you can make LLM requests multiple times with identical values, and after the first time, they will return immediately without needing to go through the LLM.
16
-
17
- ## Usage at a glance
18
-
19
- **1. Input.** Start with the data you want to send to the LLM:
20
-
21
- ```ts
22
- const leads = [{ id: 'lead-101', notes: 'Growth-stage SaaS, ~80 employees, wants a demo.' }];
23
- ```
24
-
25
- **2. Desired shape.** Describe the structure you expect back:
26
-
27
- ```ts
28
- const LeadInsights = z.object({
29
- industry: z.string(),
30
- company_size: z.enum(['SMB', 'Mid-market', 'Enterprise']),
31
- });
32
- ```
33
-
34
- **3. Call the API.** Use `list()` for array operations:
35
-
36
- ```ts
37
- const nua = Nua.gateway({ apiKey: 'YOUR_API_KEY' });
38
-
39
- const result = await nua.list('Classify each lead with industry and company_size bucket.', {
40
- input: leads,
41
- primaryKey: 'id',
42
- output: { name: 'leadInsights', schema: LeadInsights },
43
- });
44
- ```
45
-
46
- **4. Use the result.** Check for success and access typed data:
47
-
48
- ```ts
49
- if (!result.success) throw new Error(result.error);
50
- console.log(result.data[0].leadInsights);
51
- // -> { industry: 'Software', company_size: 'Mid-market' }
52
- ```
12
+ ---
53
13
 
54
14
  ## Quick Start
55
15
 
56
- 1. Add the Nuabase SDK along with Zod:
16
+ ### 1. Install
57
17
 
58
18
  ```bash
59
19
  npm install nuabase zod
60
20
  ```
61
21
 
62
- 2. Get your API key from the [Nuabase Console](https://console.nuabase.com/dashboard/api-keys/new).
63
-
64
- 3. Set `NUABASE_API_KEY` environment variable, or pass it directly in `Nua.gateway({ apiKey: "KEY" })`:
22
+ ### 2. Initialize and Call
65
23
 
66
- ## Lead Enrichment Example
24
+ Configure `Nua.direct()` with an LLM provider API key (or a [local agent](#2-local-coding-agent-nodejs-only)), define your expected Zod schema, and call `get()` or `list()`:
67
25
 
68
26
  ```ts
69
27
  import { Nua } from 'nuabase';
70
28
  import { z } from 'zod';
71
29
 
72
- const nua = Nua.gateway({ apiKey: 'API-KEY' });
30
+ // 1. Initialize client
31
+ const nua = Nua.direct({
32
+ providers: { openrouter: { apiKey: process.env.OPENROUTER_API_KEY! } },
33
+ });
73
34
 
35
+ // 2. Define output schema
74
36
  const LeadInsights = z.object({
75
37
  company_name: z.string(),
76
38
  industry: z.string(),
@@ -78,7 +40,8 @@ const LeadInsights = z.object({
78
40
  recommended_follow_up: z.enum(['Call', 'Email', 'Event']),
79
41
  });
80
42
 
81
- const rows = [
43
+ // 3. Prepare input data
44
+ const leads = [
82
45
  {
83
46
  leadId: 'lead-101',
84
47
  name: 'Acme Analytics',
@@ -89,34 +52,24 @@ const rows = [
89
52
  name: 'Bright Logistics',
90
53
  notes: 'Regional freight operator asking for pricing via email. 12 depots across the Midwest.',
91
54
  },
92
- {
93
- leadId: 'lead-103',
94
- name: 'Nimbus Retail',
95
- notes:
96
- 'Enterprise retailer. Mentioned they will send the procurement team to our booth at NRF.',
97
- },
98
55
  ];
99
56
 
100
- const response = await nua.list(
101
- 'Summarize each inbound lead by extracting company_name, industry, company_size bucket (SMB, Mid-market, Enterprise), and the recommended_follow_up channel (Call, Email, or Event) based on their notes.',
57
+ // 4. Execute batch request (sends all rows in a single LLM request)
58
+ const result = await nua.list(
59
+ 'Extract company_name, industry, company_size, and recommended_follow_up from each lead.',
102
60
  {
103
- input: rows,
61
+ input: leads,
104
62
  primaryKey: 'leadId',
105
63
  output: { name: 'leadInsights', schema: LeadInsights },
106
64
  }
107
65
  );
108
66
 
109
- if (!response.success) {
110
- console.error(response.error);
111
- return;
67
+ // 5. Access typed output
68
+ if (!result.success) {
69
+ throw new Error(result.error);
112
70
  }
113
71
 
114
- response.data.forEach(({ leadId, leadInsights }) => {
115
- // leadInsights satisfies the LeadInsights schema
116
- console.log(leadId, leadInsights.company_name, leadInsights.industry, leadInsights.company_size);
117
- });
118
-
119
- console.log(response.data);
72
+ console.log(result.data);
120
73
  // -> [
121
74
  // {
122
75
  // leadId: 'lead-101',
@@ -128,200 +81,280 @@ console.log(response.data);
128
81
  // },
129
82
  // },
130
83
  // // ...
131
- // ];
84
+ // ]
132
85
  ```
133
86
 
134
- The default invocation waits for completion and returns the full result. Use `nua.queueList()` to submit an asynchronous job and receive streaming updates via SSE.
87
+ ---
135
88
 
136
- ## Why Nuabase
89
+ ## Core Methods
137
90
 
138
- - Type-safe from prompt to runtime: Zod schemas compile to JSON Schema, and the SDK re-validates every response before you see it.
139
- - Zero glue code: Nuabase runs the queueing, retries, webhooks, SSE streaming, and polling so your application code stays clean.
140
- - Built-in performance: granular caching, cost attribution, and usage metrics come for free on every request.
141
- - Production visibility: every run shows up in the Nuabase dashboard with logs, prompt/response history, and tracing ids.
91
+ ### `nua.get(prompt, options?)`
142
92
 
143
- ## API Response
93
+ Generates a single value. Omit `options` to receive a plain `string`, or pass an `output` schema for a typed object or primitive.
144
94
 
145
- Every call returns a discriminated union. When `success` is `false`, you get an error object. Otherwise `success` is `true`, and `data` contains your typed output along with metadata.
95
+ ```ts
96
+ // Plain string response
97
+ const joke = await nua.get('Tell me a one-line joke');
98
+ if (joke.success) console.log(joke.data); // string
146
99
 
147
- **Success**
100
+ // Typed structured response
101
+ const address = await nua.get('Extract the postal address', {
102
+ input: 'Ship to 123 Main St, Springfield, IL 62701',
103
+ output: { name: 'address', schema: AddressSchema },
104
+ });
105
+ if (address.success) console.log(address.data); // z.infer<typeof AddressSchema>
106
+ ```
148
107
 
149
- | Field | Type | Description |
150
- | ----------- | --------------------------------------------------------------------- | -------------------------------------------------- |
151
- | `success` | `true` | Indicates that the request completed successfully. |
152
- | `data` | `T` (for `get`) or `Array<{ [pk]: value; [output]: T }>` (for `list`) | The typed result matching your schema. |
153
- | `usage` | `{ promptTokens, completionTokens, totalTokens }` | Token usage statistics for the LLM request. |
154
- | `model` | `string` | The LLM model used for this request. |
155
- | `latencyMs` | `number` | Request latency in milliseconds. |
156
- | `source` | `'gateway'` \| `'direct'` | Which backend processed the request. |
157
- | `meta` | `object` | Additional metadata (varies by source). |
108
+ **Options:**
158
109
 
159
- **Gateway-specific metadata** (`meta` when `source` is `'gateway'`):
110
+ * `input?: unknown` – Context or data to include with the prompt.
111
+ * `output: { name: string; schema: ZodSchema }` – Identifier and Zod schema for the output.
112
+ * `model?: ModelInput` – Override the client's default model for this call.
160
113
 
161
- | Field | Type | Description |
162
- | ------------ | --------- | ----------------------------------------- |
163
- | `requestId` | `string` | Unique identifier for tracing. |
164
- | `cached` | `boolean` | Whether the result was served from cache. |
165
- | `llmUsage` | `object` | Token usage from LLM calls. |
166
- | `cacheUsage` | `object` | Token usage served from cache. |
114
+ ### `nua.list(prompt, options)`
167
115
 
168
- **Direct-specific metadata** (`meta` when `source` is `'direct'`):
116
+ Processes an array of records in a **single request**. Each returned row carries the `primaryKey` value the model echoed back, next to the output. Rows can come back in a different order than the input, so match them to your input by key.
169
117
 
170
- | Field | Type | Description |
171
- | ------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------- |
172
- | `engine` | `'http'` \| `'local-agent'` | Whether an LLM provider's API or a coding agent on this machine answered. |
173
- | `schemaEnforcement` | `'in-prompt'` \| `'native'` | Whether the schema was described in the prompt or enforced by the engine. Output is validated either way. |
174
- | `provider` | `string` | `engine: 'http'` only. The LLM provider. |
175
- | `agent` | `'claude-code'` \| `'codex'` \| `'pi'` | `engine: 'local-agent'` only. Which coding agent answered. |
176
- | `costUsdEstimate` | `number \| undefined` | `engine: 'local-agent'` only. The agent's own estimate. On a subscription this is not what you pay. |
118
+ ```ts
119
+ const result = await nua.list('Classify each lead', {
120
+ input: leads,
121
+ primaryKey: 'id',
122
+ output: { name: 'classification', schema: ClassificationSchema },
123
+ });
177
124
 
178
- **Error**
125
+ if (result.success) {
126
+ result.data.forEach((row) => console.log(row.id, row.classification));
127
+ }
128
+ ```
179
129
 
180
- | Field | Type | Description |
181
- | ----------- | ------------------------- | ----------------------------------- |
182
- | `success` | `false` | Indicates the request failed. |
183
- | `error` | `string` | Message describing the error. |
184
- | `source` | `'gateway'` \| `'direct'` | Which backend returned the error. |
185
- | `latencyMs` | `number` | Time until failure in milliseconds. |
130
+ **Options:**
186
131
 
187
- ## SDK API
132
+ * `input: Array<Record<string, unknown>>` – Array of objects to process.
133
+ * `primaryKey: string` – Unique key on each input object. Each output row carries it, so you can match rows to inputs.
134
+ * `output: { name: string; schema: ZodSchema }` – Property name and Zod schema attached to each returned row.
135
+ * `model?: ModelInput` – Override the client's default model for this call.
188
136
 
189
- ### Constructors
137
+ ---
190
138
 
191
- **`Nua.gateway(config?)`**
192
- Creates a client that connects to the Nuabase API gateway. Use this for production deployments with caching, request tracking, and async job support.
139
+ ## Client Configuration (`Nua.direct`)
193
140
 
194
- - `apiKey?: string` – API key for authentication. Defaults to `process.env.NUABASE_API_KEY`.
195
- - `baseUrl?: string` – Override the API host. Defaults to `https://api.nuabase.com`.
141
+ `Nua.direct(config)` runs in-process using one of two execution engines: **HTTP Providers** or a **Local Coding Agent**.
196
142
 
197
- **`Nua.direct(config)`**
198
- Creates a client that runs casts in this process instead of through the gateway. Use this for serverless, edge functions, or CLI tools where you don't need the gateway infrastructure. It runs on one of two kinds of engine, chosen by the config.
143
+ ### 1. HTTP Providers
199
144
 
200
- With API keys for LLM providers:
145
+ Call LLM APIs directly using your own API keys:
201
146
 
202
- - `providers: { groq?: { apiKey: string }, cerebras?: { apiKey: string }, gemini?: { apiKey: string }, openrouter?: { apiKey: string } }` – Provider credentials.
203
- - `model?: ModelInput` – Optional default model. Omit it to use `{ alias: 'fast' }`.
147
+ ```ts
148
+ const nua = Nua.direct({
149
+ providers: {
150
+ groq: { apiKey: '...' },
151
+ cerebras: { apiKey: '...' },
152
+ gemini: { apiKey: '...' },
153
+ openrouter: { apiKey: '...' },
154
+ deepseek: { apiKey: '...' },
155
+ },
156
+ model: { alias: 'fast' }, // optional default; defaults to { alias: 'fast' }
157
+ });
158
+ ```
204
159
 
205
- Model inputs are objects, not provider-prefixed strings:
160
+ **Specifying Models:**
161
+ Models can be set globally on the client or per-call using either a provider-specific model object or an alias. Each alias lists provider models in a fixed order and uses the first one whose provider you supplied an API key for: `{ alias: 'fast' }` tries Cerebras, then Groq, then OpenRouter. If you have a key for none of an alias's providers, the call fails. Some aliases, such as `sonnet`, only go through OpenRouter.
206
162
 
207
163
  ```ts
208
- await nua.get('Extract the company name', {
164
+ // Specific provider and model
165
+ await nua.get('Extract company name', {
209
166
  output: { name: 'companyName', schema: z.string() },
210
- model: { provider: 'openrouter', model: 'z-ai/glm-5.2' },
167
+ model: { provider: 'openrouter', model: 'z-ai/glm-5.3-flash' },
211
168
  });
212
169
 
213
- await nua.get('Extract the company name', {
170
+ // Provider-agnostic alias
171
+ await nua.get('Extract company name', {
214
172
  output: { name: 'companyName', schema: z.string() },
215
173
  model: { alias: 'fast' },
216
174
  });
217
175
  ```
218
176
 
219
- With a coding agent on this machine (Node.js only):
177
+ ### 2. Local Coding Agent (Node.js only)
178
+
179
+ Run prompts through a coding agent CLI already installed and logged in on your machine (`claude -p`, `codex exec`, or `pi --mode json`). This uses your existing CLI login or subscription—no API key required.
220
180
 
221
- - `localAgent: LocalAgent` – Created with `localAgent()` or `findLocalAgent()` from `nuabase/local-agent`.
181
+ Import helpers from `nuabase/local-agent` (kept at a separate entry point so the main `nuabase` entry point stays free of Node built-ins, which edge runtimes such as Cloudflare Workers or Vercel Edge lack):
222
182
 
223
183
  ```ts
224
184
  import { Nua } from 'nuabase';
225
- import { detectLocalAgents, findLocalAgent, localAgent } from 'nuabase/local-agent';
185
+ import { localAgent, findLocalAgent, detectLocalAgents } from 'nuabase/local-agent';
226
186
 
227
- const nua = Nua.direct({ localAgent: localAgent({ agent: 'claude-code', model: 'haiku' }) });
187
+ // Option A: Target a specific agent
188
+ const nua = Nua.direct({
189
+ localAgent: localAgent({ agent: 'claude-code', model: 'haiku' }),
190
+ });
228
191
 
229
- // Or use the first agent that is installed and logged in:
230
- const auto = Nua.direct({ localAgent: await findLocalAgent() });
192
+ // Option B: Automatically pick the first installed & logged-in agent (checks Claude Code first)
193
+ const autoNua = Nua.direct({
194
+ localAgent: await findLocalAgent(),
195
+ });
231
196
 
197
+ // Optional: Inspect available agents on the machine (makes no model calls)
232
198
  const agents = await detectLocalAgents();
233
- // [{ agent: 'claude-code', installed: true, loggedIn: true, version: '...', binaryPath: '...' }, ...]
199
+ // -> [{ agent: 'claude-code', installed: true, loggedIn: true, version: '...', binaryPath: '...' }, ...]
234
200
  ```
235
201
 
236
- This runs a coding-agent CLI already installed and logged in on this machine, in its headless mode (`claude -p`, `codex exec`, or `pi --mode json`). Calls use the user's own logged-in subscription or provider login, so no API key is needed. `get()` and `list()` behave as with providers: the output is validated against your schema and typed the same way. Queue operations are not supported. `nuabase/local-agent` is a separate entry point so that browser bundles of `nuabase` never include it.
202
+ **Agent Functions & Options:**
237
203
 
238
- The agent is set up once, when you create it. If you install an agent or log in after that, create a new one.
204
+ * `localAgent(config)` – Configures a named agent (`'claude-code' | 'codex' | 'pi'`). Throws immediately if the executable isn't found; fails on the first call if not logged in. Note: Agent lookup happens once at creation.
205
+ * `findLocalAgent(options?)` – Checks version and login status across supported CLIs (without making model calls) and resolves to the first ready agent, or rejects if none are available.
239
206
 
240
- **`localAgent(config)`** sets up a named agent. It throws right away if the agent's executable cannot be found; if the agent is not logged in, the first call fails with that error.
207
+ | Option | Type | Default | Description |
208
+ | --- | --- | --- | --- |
209
+ | `agent` | `'claude-code' \| 'codex' \| 'pi'` | Required for `localAgent()` | Which CLI agent to run. |
210
+ | `binaryPath` | `string` | Looked up on `PATH` | Path to executable (`localAgent` only). On Windows, point to a native executable (`.cmd` shims are not supported). |
211
+ | `model` | `string` | Agent's default | The agent's own model name for calls that name no model. Claude Code accepts aliases (`'haiku'`, `'sonnet'`, `'opus'`), and so does a per-call `model: { alias }`. Codex and Pi have no aliases: Codex uses its own model names; Pi uses `'provider/id'` (e.g., `'openrouter/z-ai/glm-5.3-flash'`). |
212
+ | `effort` | `'low' \| 'medium' \| 'high'` | Agent's default | How hard the model thinks, as the CLI's own setting: Claude Code `--effort`, Codex `model_reasoning_effort`, Pi `--thinking`. |
213
+ | `timeoutMs` | `number` | `180000` | Maximum execution time per call in milliseconds. |
214
+ | `concurrency` | `number` | `2` | Maximum concurrent agent processes. |
215
+ | `auth` | `'subscription' \| 'inherit'` | `'subscription'` | `'subscription'` strips env vars like `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` so calls bill your logged-in plan instead of API keys. |
216
+ | `logger` | object | `console` | Object with `debug`, `info`, `warn`, and `error` methods for call and retry logs. |
241
217
 
242
- - `agent: 'claude-code' | 'codex' | 'pi'`
243
- - `binaryPath?: string` – Path to the agent executable. Defaults to looking it up on `PATH`. On Windows, point this at a native executable; `.cmd` shims are not supported.
244
- - and the options below.
218
+ `agent.with({ model, effort, timeoutMs })` returns the same agent with other call options. It shares the agent's executable, environment, logger and `concurrency` limit, so one `localAgent()` per CLI can serve casts on several of its models without running more processes at once than the limit allows. Options left out keep the agent's; a `model` or `effort` given as `undefined` is the CLI's default.
245
219
 
246
- **`findLocalAgent(options?)`** returns a promise of the first agent that is installed and logged in, trying Claude Code first. It rejects when there is none. Checking runs each CLI's version and login-status commands, which make no model calls.
247
-
248
- Options for both:
249
-
250
- - `model?: string` – The agent's own model name, e.g. `'haiku'` for Claude Code, for calls that name no model. Defaults to the agent's default. Per-call `model: { alias }` works for aliases the agent understands (`haiku`, `sonnet`, `opus` for Claude Code). Codex and Pi have no aliases: pass one of their own model names, and for Pi a `provider/id` name such as `'openrouter/z-ai/glm-5.2'`.
251
- - `timeoutMs?: number` – Per-call limit. Defaults to `180000`.
252
- - `concurrency?: number` – Maximum agent processes at once. Defaults to `2`.
253
- - `auth?: 'subscription' | 'inherit'` – `subscription` (default) hides `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` and similar variables from the agent so it bills the logged-in plan rather than an API key.
254
- - `logger?` – An object with `debug`, `info`, `warn` and `error` methods, where calls and retries are logged. Defaults to the console.
220
+ ```ts
221
+ const codex = localAgent({ agent: 'codex', concurrency: 2 });
222
+ const quick = Nua.direct({ localAgent: codex.with({ model: 'gpt-5.6-terra', effort: 'low' }) });
223
+ const careful = Nua.direct({ localAgent: codex.with({ model: 'gpt-5.6-sol', timeoutMs: 300_000 }) });
224
+ // Both run on the same two processes at most.
225
+ ```
255
226
 
256
- How it works: each call runs the agent in a fresh, empty temporary directory with its tools, project settings and MCP servers turned off. The schema is wrapped as `{ value: ... }` (the agents require an object at the top level) and passed to the agent's own schema option. Codex only accepts OpenAI strict-mode schemas, so a schema with optional properties or open-ended objects is instead described in the prompt, and the output is validated as usual. Pi has no schema flag at all, so its schemas are always described in the prompt. If an agent rejects a schema, the call is retried with the schema in the prompt. `maxTokens` has no CLI equivalent and is ignored. Calls count against the user's plan limits; check each vendor's terms before offering this to other users.
227
+ > **Note:** Local agent calls count against your personal subscription limits. Check each vendor's terms before exposing local-agent execution to end users.
257
228
 
258
- ### Methods
229
+ ### Interactive Agent Sessions (Node.js only)
259
230
 
260
- **`nua.get(prompt)`**
261
- Returns a simple string response from the LLM.
231
+ Hand a prompt to the user's coding agent as an interactive session they carry on in a terminal, in their own project (unlike casts, which run in an empty sandbox):
262
232
 
263
233
  ```ts
264
- const result = await nua.get('Tell me a joke');
265
- if (result.success) console.log(result.data); // string
234
+ import { startAgentSession } from 'nuabase/local-agent';
235
+
236
+ const session = await startAgentSession({
237
+ agent: 'claude-code',
238
+ cwd: '/path/to/project',
239
+ model: 'opus',
240
+ approval: 'auto',
241
+ prompt: 'Fix the failing import described in notes.md',
242
+ dir: '/path/to/project/tmp/agent-sessions',
243
+ });
244
+
245
+ switch (session.kind) {
246
+ case 'opened': // macOS: a new terminal window is running the agent
247
+ break;
248
+ case 'command': // elsewhere: the user starts it
249
+ console.log(`Run in a terminal: ${session.files.command}`);
250
+ break;
251
+ case 'open_failed': // macOS, but the window didn't open (session.error); the files are written
252
+ console.log(`Couldn't open a terminal (${session.error}). Run: ${session.files.command}`);
253
+ break;
254
+ case 'refused': // 'unsupported_platform' | 'prompt_too_long' | 'agent_not_found'; nothing written
255
+ console.error(session.message);
256
+ }
266
257
  ```
267
258
 
268
- **`nua.get(prompt, options)`**
269
- Returns a typed single value. Parameters:
259
+ It writes the prompt to its own file (`0600`) and a launcher (`0700`) that passes it to the agent as one argument after `--`. Quotes, backticks and `$NAMES` in the prompt reach the agent as written, and the prompt can't set the agent's flags or run commands. Neither file is ever overwritten or deleted. On macOS the launcher opens in a new window of the user's terminal app (`open`, which needs no Automation permission).
270
260
 
271
- - `prompt: string` – Natural-language instructions sent to the LLM.
272
- - `options.input?: unknown` – Optional input data to include in the prompt.
273
- - `options.output: { name: string; schema: ZodSchema }` – Output name and Zod schema.
261
+ Expected failures come back as `kind: 'refused'`, with nothing written: Windows, which can't run the launcher (`unsupported_platform`); a prompt of 128 KiB or more on Linux, which caps a single argument at that size (`prompt_too_long`); and an agent that isn't on `PATH` when no `binaryPath` is given (`agent_not_found`). It throws only for an unknown `agent` or a `name` already used.
274
262
 
275
- ```ts
276
- const result = await nua.get('Extract the address', {
277
- input: '123 Main St, Springfield, IL 62701',
278
- output: { name: 'address', schema: AddressSchema },
279
- });
280
- if (result.success) console.log(result.data); // typed as z.infer<typeof AddressSchema>
281
- ```
263
+ `agentSessionMode(platform?)` says beforehand how a session would start, for showing the right button: `'terminal'` on macOS, `'command'` on other POSIX systems, `'none'` on Windows.
282
264
 
283
- **`nua.list(prompt, options)`**
284
- Processes an array of items. Parameters:
265
+ | Option | Type | Default | Description |
266
+ | --- | --- | --- | --- |
267
+ | `agent` | `'claude-code' \| 'codex' \| 'pi'` | Required | Which CLI agent to start. |
268
+ | `binaryPath` | `string` | Looked up on `PATH` | Path to the executable. The launcher runs it by this absolute path, whatever the terminal's `PATH`. |
269
+ | `cwd` | `string` | Required | The project the agent works in. It loads the project's `AGENTS.md` / `CLAUDE.md`. |
270
+ | `model` | `string` | Agent's default | The agent's own model name. |
271
+ | `approval` | `'ask' \| 'auto'` | `'ask'` | `'auto'` starts the agent's auto mode (Claude Code `--permission-mode auto`, Codex `--approve-for-me`, Pi `--approve`). Its reviewer, not the user, approves routine edits and commands, and stops risky ones. |
272
+ | `prompt` | `string` | Required | The session's first message. |
273
+ | `dir` | `string` | Required | Where both files are written. Created if missing. |
274
+ | `name` | `string` | `<UTC time>-<agent>-<4 hex digits>` | The files' name without extension. Neither file may exist yet. |
275
+ | `platform` | `NodeJS.Platform` | `process.platform` | The platform the launcher is for. |
285
276
 
286
- - `prompt: string` – Natural-language instructions for each item.
287
- - `options.input: Array<Record<string, unknown>>` – Rows to process.
288
- - `options.primaryKey: string` – Field that uniquely identifies each row.
289
- - `options.output: { name: string; schema: ZodSchema }` – Output configuration.
277
+ ---
290
278
 
291
- ```ts
292
- const result = await nua.list('Classify each lead', {
293
- input: leads,
294
- primaryKey: 'id',
295
- output: { name: 'classification', schema: ClassificationSchema },
296
- });
297
- if (result.success) {
298
- result.data.forEach((row) => console.log(row.id, row.classification));
299
- }
300
- ```
279
+ ## Response Format & Error Handling
280
+
281
+ Every `get()` and `list()` call returns a discriminated union (`result.success`). Every failure of the LLM call itself comes back as `success: false`.
282
+
283
+ The call rejects (throws) only for invalid arguments, before anything is sent: an empty prompt, a non-array `input`, an empty `primaryKey`, or a Zod schema that JSON Schema can't express (such as `z.date()`).
284
+
285
+ ### Success (`result.success === true`)
286
+
287
+ | Field | Type | Description |
288
+ | --- | --- | --- |
289
+ | `success` | `true` | Request succeeded and passed schema validation. |
290
+ | `data` | `T` or `Array<{ [pk]: K } & { [output]: T }>` | Validated output matching your schema. |
291
+ | `usage` | `{ promptTokens, completionTokens, totalTokens, promptCacheReadTokens?, promptCacheWriteTokens? }` | Token usage stats (`promptCache*` included when reported by the provider as subsets of `promptTokens`). |
292
+ | `model` | `string` | What answered: `provider:model` (e.g., `openrouter:z-ai/glm-5.3-flash`) or `agent:model` (e.g., `claude-code:haiku`; `default` when the agent's model isn't known). |
293
+ | `latencyMs` | `number` | Total request duration in milliseconds, including retries. |
294
+ | `source` | `'direct'` | Indicates the request executed in-process. |
295
+ | `meta` | `DirectMeta` | Execution details (see table below). |
296
+
297
+ **`DirectMeta` (`result.meta`):**
298
+
299
+ | Field | Type | Description |
300
+ | --- | --- | --- |
301
+ | `engine` | `'http' \| 'local-agent'` | Execution engine used. |
302
+ | `schemaEnforcement` | `'native' \| 'in-prompt'` | Whether the engine enforced the schema natively or via prompt instructions. The output is validated either way. |
303
+ | `provider` | `string` | *(`engine: 'http'` only)* LLM provider name. |
304
+ | `agent` | `'claude-code' \| 'codex' \| 'pi'` | *(`engine: 'local-agent'` only)* Local agent CLI used. |
305
+ | `model` | `string \| undefined` | The provider's model, or the model the agent reported or was asked for (`undefined` when neither is known). |
306
+ | `costUsdEstimate` | `number \| undefined` | *(`engine: 'local-agent'` only)* The CLI's estimated API cost (does not reflect subscription billing). |
307
+
308
+ ### Error (`result.success === false`)
309
+
310
+ | Field | Type | Description |
311
+ | --- | --- | --- |
312
+ | `success` | `false` | Request failed (see retries below). |
313
+ | `error` | `string` | Error description. |
314
+ | `source` | `'direct'` | Indicates the request executed in-process. |
315
+ | `latencyMs` | `number` | Time elapsed until failure in milliseconds. |
316
+
317
+ > **💡 Tip: Maximize Prompt Caching**
318
+ > Keep your `prompt` string static across calls and pass dynamic values via `input`. Nuabase places the prompt and schema at the beginning of the payload so providers can cache that prefix. Cache reads are heavily discounted—whereas on providers like Claude, changing the prompt prefix on every call incurs a 25% cache-write premium without ever getting a cache hit.
319
+
320
+ ---
321
+
322
+ ## How It Works Under the Hood
301
323
 
302
- **`nua.queueGet(prompt, options?)`** _(Gateway mode only)_
303
- Submits an async job for single-value operations. Returns `{ success: true, jobId }`.
324
+ Each `get()` or `list()` call converts your Zod schema to JSON Schema to tell the model the shape, sends it alongside your prompt and input data, and parses the returned payload with your Zod schema. `data` is Zod's parsed output: `.default()`s are filled in, `.transform()`s run, `.refine()` checks apply, and unknown object keys are dropped. The JSON Schema describes what the schema accepts (its input side), so `.transform()` is fine; checks it can't express, such as `.refine()`, aren't shown to the model, but a payload that fails them is retried like any other invalid output.
304
325
 
305
- **`nua.queueList(prompt, options)`** _(Gateway mode only)_
306
- Submits an async job for array operations. Returns `{ success: true, jobId }`.
326
+ Transient errors and outputs that fail validation are **automatically retried up to 2 times**. Fatal errors fail at once: a bad API key, an agent that isn't logged in, an exhausted quota, a timeout, or a model none of your providers serves.
307
327
 
308
- ## Next Steps
328
+ ### HTTP Provider Endpoints
309
329
 
310
- - Create an API key or inspect request logs in the [Nuabase Console](https://console.nuabase.com/).
311
- - Explore more SDK usage patterns (SSE streaming, webhooks, caching) in the full docs at https://docs.nuabase.com.
330
+ When using `Nua.direct({ providers })`, your process makes 1 HTTPS request per call (plus up to 2 retries) directly to the selected provider:
312
331
 
313
- ## Support
332
+ | Provider | Endpoint |
333
+ | --- | --- |
334
+ | **Groq** | `https://api.groq.com/openai/v1/chat/completions` |
335
+ | **Cerebras** | `https://api.cerebras.ai/v1/chat/completions` |
336
+ | **Gemini** | `https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent` |
337
+ | **OpenRouter** | `https://openrouter.ai/api/v1/chat/completions` |
338
+ | **DeepSeek** | `https://api.deepseek.com/chat/completions` |
314
339
 
315
- Email us at [hello@nuabase.com](mailto:hello@nuabase.com). On X at [@NuabaseHQ](x.com/NuabaseHQ).
340
+ OpenRouter is itself a router: it forwards each request to a provider that hosts the model.
316
341
 
317
- ## Tests
342
+ ### Local Agent Sandboxing & Schema Handling
318
343
 
319
- See [TESTING.md](TESTING.md)
344
+ When using `Nua.direct({ localAgent })`, each call spawns the CLI in a fresh, empty temporary directory with all tools, project settings, and MCP servers disabled.
320
345
 
321
- ## Credits
346
+ * **Schema wrapping:** Because agent CLIs require a top-level object, Nuabase wraps your schema as `{ value: ... }` and passes it to the CLI's schema flag.
347
+ * **Fallback to in-prompt schemas:**
348
+ * **Codex** only accepts OpenAI strict-mode schemas; schemas with optional properties or open-ended objects are injected into the prompt instead.
349
+ * **Pi** has no native schema flag, so schemas are always described in the prompt.
350
+ * If any agent rejects a native schema flag, Nuabase automatically retries with the schema injected into the prompt. The output is parsed with your Zod schema either way.
322
351
 
323
- The template for this package comes from https://github.com/rtivital/ts-package-template
352
+ ---
324
353
 
325
- ## License
354
+ ## Resources & Community
326
355
 
327
- MIT
356
+ * **Documentation:** [docs.nuabase.com](https://docs.nuabase.com)
357
+ * **Testing Guide:** [TESTING.md](TESTING.md)
358
+ * **Support:** [hello@nuabase.com](mailto:hello@nuabase.com) | [@NuabaseHQ on X](https://x.com/NuabaseHQ)
359
+ * **Credits:** Package template based on [rtivital/ts-package-template](https://github.com/rtivital/ts-package-template)
360
+ * **License:** MIT