nuabase 2.3.5 → 2.3.11

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/CHANGELOG.md ADDED
@@ -0,0 +1,26 @@
1
+ # nuabase
2
+
3
+ ## 2.3.11
4
+
5
+ ### Patch Changes
6
+
7
+ - Ship the changelog in the npm package
8
+
9
+ ## 2.3.10
10
+
11
+ ### Patch Changes
12
+
13
+ - 0303881: The "fast" model alias now uses `openai/gpt-oss-120b` on Groq instead of the preview `qwen/qwen3.8-27b`. On Groq models that support strict structured outputs, casts now enforce the output schema natively instead of describing it in the prompt.
14
+
15
+ ## 2.3.9
16
+
17
+ ### Patch Changes
18
+
19
+ - Format the release script with Prettier
20
+
21
+ ## 2.3.8
22
+
23
+ ### Patch Changes
24
+
25
+ - Publish the packed tarball with npm, so 2FA is approved in the browser
26
+ Release nuabase through changesets and the release train
package/README.md CHANGED
@@ -172,7 +172,7 @@ Every call returns a discriminated union. When `success` is `false`, you get an
172
172
  | `engine` | `'http'` \| `'local-agent'` | Whether an LLM provider's API or a coding agent on this machine answered. |
173
173
  | `schemaEnforcement` | `'in-prompt'` \| `'native'` | Whether the schema was described in the prompt or enforced by the engine. Output is validated either way. |
174
174
  | `provider` | `string` | `engine: 'http'` only. The LLM provider. |
175
- | `agent` | `'claude-code'` \| `'codex'` | `engine: 'local-agent'` only. Which coding agent answered. |
175
+ | `agent` | `'claude-code'` \| `'codex'` \| `'pi'` | `engine: 'local-agent'` only. Which coding agent answered. |
176
176
  | `costUsdEstimate` | `number \| undefined` | `engine: 'local-agent'` only. The agent's own estimate. On a subscription this is not what you pay. |
177
177
 
178
178
  **Error**
@@ -233,13 +233,13 @@ const agents = await detectLocalAgents();
233
233
  // [{ agent: 'claude-code', installed: true, loggedIn: true, version: '...', binaryPath: '...' }, ...]
234
234
  ```
235
235
 
236
- This runs a coding-agent CLI already installed and logged in on this machine, in its headless mode (`claude -p` or `codex exec`). Calls use the user's own Claude or ChatGPT subscription, 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.
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.
237
237
 
238
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.
239
239
 
240
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.
241
241
 
242
- - `agent: 'claude-code' | 'codex'`
242
+ - `agent: 'claude-code' | 'codex' | 'pi'`
243
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
244
  - and the options below.
245
245
 
@@ -247,13 +247,13 @@ The agent is set up once, when you create it. If you install an agent or log in
247
247
 
248
248
  Options for both:
249
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).
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
251
  - `timeoutMs?: number` – Per-call limit. Defaults to `180000`.
252
252
  - `concurrency?: number` – Maximum agent processes at once. Defaults to `2`.
253
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
254
  - `logger?` – An object with `debug`, `info`, `warn` and `error` methods, where calls and retries are logged. Defaults to the console.
255
255
 
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. 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.
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.
257
257
 
258
258
  ### Methods
259
259
 
@@ -87,4 +87,91 @@ function requireLogger() {
87
87
  return logger;
88
88
  }
89
89
 
90
+ var strictJsonSchema = {};
91
+
92
+ var hasRequiredStrictJsonSchema;
93
+ function requireStrictJsonSchema() {
94
+ if (hasRequiredStrictJsonSchema) return strictJsonSchema;
95
+ hasRequiredStrictJsonSchema = 1;
96
+ Object.defineProperty(strictJsonSchema, "__esModule", { value: true });
97
+ strictJsonSchema.toStrictSchema = toStrictSchema;
98
+ const STRICT_UNSUPPORTED_KEYWORDS = /* @__PURE__ */ new Set([
99
+ "patternProperties",
100
+ "propertyNames",
101
+ "unevaluatedProperties",
102
+ "dependentRequired",
103
+ "dependentSchemas",
104
+ "if",
105
+ "then",
106
+ "else",
107
+ "not",
108
+ "allOf",
109
+ "oneOf"
110
+ ]);
111
+ function toStrictSchema(schema) {
112
+ return strictNode(schema);
113
+ }
114
+ function strictNode(node) {
115
+ if (typeof node !== "object" || node === null || Array.isArray(node)) {
116
+ return null;
117
+ }
118
+ const schema = node;
119
+ for (const key of Object.keys(schema)) {
120
+ if (STRICT_UNSUPPORTED_KEYWORDS.has(key))
121
+ return null;
122
+ }
123
+ const result = { ...schema };
124
+ delete result.$schema;
125
+ const isObject = schema.type === "object" || Array.isArray(schema.type) && schema.type.includes("object") || schema.properties !== void 0;
126
+ if (isObject) {
127
+ const properties = schema.properties ?? {};
128
+ if (schema.additionalProperties !== void 0 && schema.additionalProperties !== false) {
129
+ return null;
130
+ }
131
+ const required = new Set(schema.required ?? []);
132
+ const shapedProperties = {};
133
+ for (const [name, child] of Object.entries(properties)) {
134
+ if (!required.has(name))
135
+ return null;
136
+ const shaped = strictNode(child);
137
+ if (!shaped)
138
+ return null;
139
+ shapedProperties[name] = shaped;
140
+ }
141
+ result.properties = shapedProperties;
142
+ result.required = Object.keys(shapedProperties);
143
+ result.additionalProperties = false;
144
+ }
145
+ if (schema.items !== void 0) {
146
+ const shaped = strictNode(schema.items);
147
+ if (!shaped)
148
+ return null;
149
+ result.items = shaped;
150
+ }
151
+ if (schema.prefixItems !== void 0)
152
+ return null;
153
+ if (schema.anyOf !== void 0) {
154
+ const variants = schema.anyOf.map(strictNode);
155
+ if (variants.some((v) => v === null))
156
+ return null;
157
+ result.anyOf = variants;
158
+ }
159
+ for (const defsKey of ["$defs", "definitions"]) {
160
+ if (schema[defsKey] !== void 0) {
161
+ const defs = {};
162
+ for (const [name, child] of Object.entries(schema[defsKey])) {
163
+ const shaped = strictNode(child);
164
+ if (!shaped)
165
+ return null;
166
+ defs[name] = shaped;
167
+ }
168
+ result[defsKey] = defs;
169
+ }
170
+ }
171
+ return result;
172
+ }
173
+ return strictJsonSchema;
174
+ }
175
+
90
176
  exports.requireLogger = requireLogger;
177
+ exports.requireStrictJsonSchema = requireStrictJsonSchema;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var logger = require('./chunks/logger-Co5uZG0A.cjs');
3
+ var strictJsonSchema = require('./chunks/strict-json-schema-DUE7RTxG.cjs');
4
4
  var zod = require('zod');
5
5
 
6
6
  var dist = {};
@@ -9841,7 +9841,7 @@ function requireProviderConfig() {
9841
9841
  };
9842
9842
  };
9843
9843
  exports$1.normalizeGeminiUsage = normalizeGeminiUsage;
9844
- const buildOpenAiStyleRequest = (baseUrl) => ({ prompt, model, maxTokens, assistantPrefillPrompt }, apiKey) => {
9844
+ const buildOpenAiStyleRequest = (baseUrl) => ({ prompt, model, maxTokens, assistantPrefillPrompt, responseSchema }, apiKey) => {
9845
9845
  const messages = [];
9846
9846
  messages.push({ role: "user", content: prompt });
9847
9847
  if (assistantPrefillPrompt) {
@@ -9854,7 +9854,13 @@ function requireProviderConfig() {
9854
9854
  max_completion_tokens: maxTokens,
9855
9855
  top_p: 1,
9856
9856
  stream: false,
9857
- stop: null
9857
+ stop: null,
9858
+ ...responseSchema && {
9859
+ response_format: {
9860
+ type: "json_schema",
9861
+ json_schema: { name: "response", strict: true, schema: responseSchema }
9862
+ }
9863
+ }
9858
9864
  };
9859
9865
  const headers = {
9860
9866
  "Content-Type": "application/json",
@@ -9981,6 +9987,12 @@ function requireProviderConfig() {
9981
9987
  providerId: "groq",
9982
9988
  apiOperation: "chat/completions",
9983
9989
  errorLabel: "Groq",
9990
+ // https://console.groq.com/docs/structured-outputs
9991
+ strictSchemaModels: /* @__PURE__ */ new Set([
9992
+ "openai/gpt-oss-120b",
9993
+ "openai/gpt-oss-20b",
9994
+ "qwen/qwen3.8-27b"
9995
+ ]),
9984
9996
  buildRequest: buildOpenAiStyleRequest("https://api.groq.com/openai/v1/chat/completions"),
9985
9997
  parseResponse: parseOpenAiStyleResponse("Groq")
9986
9998
  },
@@ -10028,7 +10040,7 @@ function requireModelInfo() {
10028
10040
  exports$1.MODEL_ALIASES = {
10029
10041
  fast: [
10030
10042
  { provider: "cerebras", model: "qwen-3.8-27b" },
10031
- { provider: "groq", model: "qwen/qwen3.8-27b" },
10043
+ { provider: "groq", model: "openai/gpt-oss-120b" },
10032
10044
  { provider: "openrouter", model: "z-ai/glm-5.2" }
10033
10045
  ],
10034
10046
  gemini: [
@@ -10128,7 +10140,7 @@ function requireProviderCall() {
10128
10140
  hasRequiredProviderCall = 1;
10129
10141
  Object.defineProperty(providerCall, "__esModule", { value: true });
10130
10142
  providerCall.callProvider = callProvider;
10131
- const logger_1 = logger.requireLogger();
10143
+ const logger_1 = strictJsonSchema.requireLogger();
10132
10144
  const provider_config_1 = requireProviderConfig();
10133
10145
  async function callProvider(call, logger) {
10134
10146
  const { target, apiOperation, request } = call;
@@ -10181,6 +10193,7 @@ function requireHttpEngine() {
10181
10193
  hasRequiredHttpEngine = 1;
10182
10194
  Object.defineProperty(httpEngine, "__esModule", { value: true });
10183
10195
  httpEngine.HttpEngine = void 0;
10196
+ const strict_json_schema_1 = strictJsonSchema.requireStrictJsonSchema();
10184
10197
  const provider_call_1 = requireProviderCall();
10185
10198
  const provider_config_1 = requireProviderConfig();
10186
10199
  const FATAL_HTTP_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
@@ -10189,35 +10202,55 @@ function requireHttpEngine() {
10189
10202
  this.target = target;
10190
10203
  this.logger = logger;
10191
10204
  }
10192
- /** Provider APIs are given the schema in the prompt; none is enforced natively yet. */
10193
- nativeSchema() {
10194
- return null;
10205
+ /** Enforced in strict mode on models whose provider supports it; other models get the schema in the prompt. */
10206
+ nativeSchema(envelopeSchema) {
10207
+ const { provider, model } = this.target;
10208
+ if (!provider_config_1.providerConfigs[provider].strictSchemaModels?.has(model))
10209
+ return null;
10210
+ return (0, strict_json_schema_1.toStrictSchema)(envelopeSchema);
10195
10211
  }
10196
10212
  async attempt(request) {
10197
10213
  const { provider, model, apiKey } = this.target;
10198
10214
  const config = provider_config_1.providerConfigs[provider];
10215
+ const responseSchema = request.enforcement.kind === "native" ? request.enforcement.schema : void 0;
10199
10216
  const call = await (0, provider_call_1.callProvider)({
10200
10217
  target: this.target,
10201
10218
  apiOperation: config.apiOperation,
10202
10219
  maxTokens: request.maxTokens,
10203
- request: config.buildRequest({ prompt: request.prompt, model, maxTokens: request.maxTokens }, apiKey),
10220
+ request: config.buildRequest({ prompt: request.prompt, model, maxTokens: request.maxTokens, responseSchema }, apiKey),
10204
10221
  parseResponse: config.parseResponse,
10205
10222
  responseLength: (reply) => reply.text.length,
10206
10223
  usage: (reply) => reply.usage
10207
10224
  }, this.logger);
10208
10225
  if (call.kind === "failed") {
10209
- return { kind: "failed", failure: failureForStatus(call.status, call.message) };
10226
+ return { kind: "failed", failure: failureForStatus(call.status, call.message, responseSchema !== void 0) };
10227
+ }
10228
+ const answer = responseSchema ? readEnvelope(call.reply.text) : { kind: "text", text: call.reply.text };
10229
+ if (!answer) {
10230
+ return {
10231
+ kind: "failed",
10232
+ failure: { kind: "transient", message: `Structured output is not valid JSON: ${call.reply.text}` }
10233
+ };
10210
10234
  }
10211
10235
  return {
10212
10236
  kind: "answered",
10213
- answer: { kind: "text", text: call.reply.text },
10237
+ answer,
10214
10238
  usage: call.reply.usage,
10215
10239
  origin: { engine: "http", provider, model }
10216
10240
  };
10217
10241
  }
10218
10242
  }
10219
10243
  httpEngine.HttpEngine = HttpEngine;
10220
- function failureForStatus(status, message) {
10244
+ function readEnvelope(text) {
10245
+ try {
10246
+ return { kind: "structured", envelope: JSON.parse(text) };
10247
+ } catch {
10248
+ return null;
10249
+ }
10250
+ }
10251
+ function failureForStatus(status, message, sentSchema) {
10252
+ if (sentSchema && status === 400)
10253
+ return { kind: "schema-rejected", message };
10221
10254
  const fatal = status !== void 0 && FATAL_HTTP_STATUSES.has(status);
10222
10255
  return { kind: fatal ? "fatal" : "transient", message };
10223
10256
  }
@@ -10231,7 +10264,7 @@ function requireProviderEngines() {
10231
10264
  Object.defineProperty(providerEngines, "__esModule", { value: true });
10232
10265
  providerEngines.ProviderEngines = void 0;
10233
10266
  providerEngines.providerEngines = providerEngines$1;
10234
- const logger_1 = logger.requireLogger();
10267
+ const logger_1 = strictJsonSchema.requireLogger();
10235
10268
  const nua_errors_1 = requireNuaErrors();
10236
10269
  const model_info_1 = requireModelInfo();
10237
10270
  const http_engine_1 = requireHttpEngine();
@@ -11178,7 +11211,7 @@ function requireDist() {
11178
11211
  Object.defineProperty(exports$1, "__esModule", { value: true });
11179
11212
  exports$1.runAgentLoop = exports$1.runAgent = exports$1.parseListSchema = exports$1.listSchema = exports$1.stableStringify = exports$1.validateJsonSchema = exports$1.providerEngines = exports$1.providerConfigs = exports$1.normalizedUsageZero = exports$1.LLM_PROVIDER_IDS = void 0;
11180
11213
  __exportStar(requireNuaLlmClient(), exports$1);
11181
- __exportStar(logger.requireLogger(), exports$1);
11214
+ __exportStar(strictJsonSchema.requireLogger(), exports$1);
11182
11215
  __exportStar(requireTypes(), exports$1);
11183
11216
  __exportStar(requireNuaErrors(), exports$1);
11184
11217
  var provider_config_1 = requireProviderConfig();
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var logger = require('./chunks/logger-Co5uZG0A.cjs');
3
+ var strictJsonSchema = require('./chunks/strict-json-schema-DUE7RTxG.cjs');
4
4
  var node_module = require('node:module');
5
5
 
6
6
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -168,99 +168,13 @@ function requireClaudeCode() {
168
168
 
169
169
  var codex = {};
170
170
 
171
- var strictJsonSchema = {};
172
-
173
- var hasRequiredStrictJsonSchema;
174
- function requireStrictJsonSchema() {
175
- if (hasRequiredStrictJsonSchema) return strictJsonSchema;
176
- hasRequiredStrictJsonSchema = 1;
177
- Object.defineProperty(strictJsonSchema, "__esModule", { value: true });
178
- strictJsonSchema.toStrictSchema = toStrictSchema;
179
- const STRICT_UNSUPPORTED_KEYWORDS = /* @__PURE__ */ new Set([
180
- "patternProperties",
181
- "propertyNames",
182
- "unevaluatedProperties",
183
- "dependentRequired",
184
- "dependentSchemas",
185
- "if",
186
- "then",
187
- "else",
188
- "not",
189
- "allOf",
190
- "oneOf"
191
- ]);
192
- function toStrictSchema(schema) {
193
- return strictNode(schema);
194
- }
195
- function strictNode(node) {
196
- if (typeof node !== "object" || node === null || Array.isArray(node)) {
197
- return null;
198
- }
199
- const schema = node;
200
- for (const key of Object.keys(schema)) {
201
- if (STRICT_UNSUPPORTED_KEYWORDS.has(key))
202
- return null;
203
- }
204
- const result = { ...schema };
205
- delete result.$schema;
206
- const isObject = schema.type === "object" || Array.isArray(schema.type) && schema.type.includes("object") || schema.properties !== void 0;
207
- if (isObject) {
208
- const properties = schema.properties ?? {};
209
- if (schema.additionalProperties !== void 0 && schema.additionalProperties !== false) {
210
- return null;
211
- }
212
- const required = new Set(schema.required ?? []);
213
- const shapedProperties = {};
214
- for (const [name, child] of Object.entries(properties)) {
215
- if (!required.has(name))
216
- return null;
217
- const shaped = strictNode(child);
218
- if (!shaped)
219
- return null;
220
- shapedProperties[name] = shaped;
221
- }
222
- result.properties = shapedProperties;
223
- result.required = Object.keys(shapedProperties);
224
- result.additionalProperties = false;
225
- }
226
- if (schema.items !== void 0) {
227
- const shaped = strictNode(schema.items);
228
- if (!shaped)
229
- return null;
230
- result.items = shaped;
231
- }
232
- if (schema.prefixItems !== void 0)
233
- return null;
234
- if (schema.anyOf !== void 0) {
235
- const variants = schema.anyOf.map(strictNode);
236
- if (variants.some((v) => v === null))
237
- return null;
238
- result.anyOf = variants;
239
- }
240
- for (const defsKey of ["$defs", "definitions"]) {
241
- if (schema[defsKey] !== void 0) {
242
- const defs = {};
243
- for (const [name, child] of Object.entries(schema[defsKey])) {
244
- const shaped = strictNode(child);
245
- if (!shaped)
246
- return null;
247
- defs[name] = shaped;
248
- }
249
- result[defsKey] = defs;
250
- }
251
- }
252
- return result;
253
- }
254
- return strictJsonSchema;
255
- }
256
-
257
171
  var hasRequiredCodex;
258
172
  function requireCodex() {
259
173
  if (hasRequiredCodex) return codex;
260
174
  hasRequiredCodex = 1;
261
175
  Object.defineProperty(codex, "__esModule", { value: true });
262
176
  codex.codexAdapter = void 0;
263
- const strict_json_schema_1 = requireStrictJsonSchema();
177
+ const strict_json_schema_1 = strictJsonSchema.requireStrictJsonSchema();
264
178
  const failure_classification_1 = requireFailureClassification();
265
179
  const SCHEMA_FILE = "output-schema.json";
266
180
  const LAST_MESSAGE_FILE = "last-message.txt";
@@ -367,6 +281,161 @@ ${result.stderr}`.trim();
367
281
  return codex;
368
282
  }
369
283
 
284
+ var pi = {};
285
+
286
+ var hasRequiredPi;
287
+ function requirePi() {
288
+ if (hasRequiredPi) return pi;
289
+ hasRequiredPi = 1;
290
+ Object.defineProperty(pi, "__esModule", { value: true });
291
+ pi.piAdapter = void 0;
292
+ const failure_classification_1 = requireFailureClassification();
293
+ const SYSTEM_PROMPT = [
294
+ "You are a data transformation function called by a program.",
295
+ "You have no tools. Follow the instructions in the user message exactly",
296
+ "and reply only with the requested JSON."
297
+ ].join(" ");
298
+ pi.piAdapter = {
299
+ id: "pi",
300
+ binaryName: "pi",
301
+ // Every environment credential Pi's providers recognize. Under `auth:
302
+ // "subscription"` these are removed so Pi bills its own `auth.json` login.
303
+ apiKeyEnvVars: [
304
+ "AI_GATEWAY_API_KEY",
305
+ "ANT_LING_API_KEY",
306
+ "ANTHROPIC_API_KEY",
307
+ "ANTHROPIC_AUTH_TOKEN",
308
+ "ANTHROPIC_OAUTH_TOKEN",
309
+ "AWS_ACCESS_KEY_ID",
310
+ "AWS_SECRET_ACCESS_KEY",
311
+ "AWS_SESSION_TOKEN",
312
+ "AZURE_OPENAI_API_KEY",
313
+ "BASETEN_API_KEY",
314
+ "CEREBRAS_API_KEY",
315
+ "CLOUDFLARE_API_KEY",
316
+ "COPILOT_GITHUB_TOKEN",
317
+ "DEEPSEEK_API_KEY",
318
+ "FIREWORKS_API_KEY",
319
+ "GEMINI_API_KEY",
320
+ "GOOGLE_API_KEY",
321
+ "GOOGLE_CLOUD_API_KEY",
322
+ "GROQ_API_KEY",
323
+ "HF_TOKEN",
324
+ "KIMI_API_KEY",
325
+ "META_API_KEY",
326
+ "MINIMAX_API_KEY",
327
+ "MINIMAX_CN_API_KEY",
328
+ "MISTRAL_API_KEY",
329
+ "MOONSHOT_API_KEY",
330
+ "NVIDIA_API_KEY",
331
+ "OPENAI_API_KEY",
332
+ "OPENCODE_API_KEY",
333
+ "OPENROUTER_API_KEY",
334
+ "QWEN_TOKEN_PLAN_API_KEY",
335
+ "QWEN_TOKEN_PLAN_CN_API_KEY",
336
+ "RADIUS_API_KEY",
337
+ "TOGETHER_API_KEY",
338
+ "XAI_API_KEY",
339
+ "XIAOMI_API_KEY",
340
+ "XIAOMI_TOKEN_PLAN_AMS_API_KEY",
341
+ "XIAOMI_TOKEN_PLAN_CN_API_KEY",
342
+ "XIAOMI_TOKEN_PLAN_SGP_API_KEY",
343
+ "ZAI_API_KEY",
344
+ "ZAI_CODING_CN_API_KEY"
345
+ ],
346
+ // Pi uses its own `provider/id` model names; set one with localAgent({ model }).
347
+ modelAliases: {},
348
+ nativeSchema() {
349
+ return null;
350
+ },
351
+ buildInvocation(request, model) {
352
+ const args = [
353
+ "--mode",
354
+ "json",
355
+ "--no-session",
356
+ "--no-tools",
357
+ "--no-builtin-tools",
358
+ "--no-extensions",
359
+ "--no-skills",
360
+ "--no-prompt-templates",
361
+ "--no-themes",
362
+ "--no-context-files",
363
+ "--system-prompt",
364
+ SYSTEM_PROMPT,
365
+ "--offline"
366
+ ];
367
+ if (model !== void 0) {
368
+ args.push("--model", model);
369
+ }
370
+ return { args, stdin: request.prompt };
371
+ },
372
+ readResult(result, request) {
373
+ const events = parseEvents(result.stdout);
374
+ const assistant = events.filter((event) => event.type === "message_end" && event.message?.role === "assistant").map((event) => event.message).pop();
375
+ if (assistant && (assistant.stopReason === "error" || assistant.errorMessage)) {
376
+ return failed((0, failure_classification_1.classifyAgentFailure)("pi", assistant.errorMessage ?? "pi stopped with an error"));
377
+ }
378
+ const retryFailed = events.find((event) => event.type === "auto_retry_end" && event.success === false);
379
+ if (retryFailed) {
380
+ return failed((0, failure_classification_1.classifyAgentFailure)("pi", retryFailed.finalError ?? "auto retry failed"));
381
+ }
382
+ if (!assistant) {
383
+ return failed((0, failure_classification_1.classifyAgentFailure)("pi", `exited with code ${result.exitCode} without a reply: ${(0, failure_classification_1.stderrTail)(result)}`));
384
+ }
385
+ const text = (assistant.content ?? []).filter((block) => block.type === "text").map((block) => block.text ?? "").join("");
386
+ const usage = assistant.usage ?? {};
387
+ const promptTokens = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
388
+ const completionTokens = usage.output ?? 0;
389
+ const reported = {
390
+ usage: { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens },
391
+ model: assistant.model,
392
+ costUsdEstimate: usage.cost?.total
393
+ };
394
+ if (request.enforcement.kind === "in-prompt") {
395
+ return { kind: "answered", answer: { kind: "text", text }, ...reported };
396
+ }
397
+ let envelope;
398
+ try {
399
+ envelope = JSON.parse(text);
400
+ } catch {
401
+ return failed({ kind: "transient", message: `pi reply is not JSON: ${text}` });
402
+ }
403
+ return { kind: "answered", answer: { kind: "structured", envelope }, ...reported };
404
+ },
405
+ // Pi has no provider-agnostic login check: `pi auth check` requires --provider
406
+ // or --model, and the provider differs per user (it is not known until Pi's
407
+ // settings are read). Report installed Pi as logged in; the first real call
408
+ // surfaces any auth error, which classifyAgentFailure marks fatal.
409
+ loginStatusArgs: ["--version"],
410
+ readLoginStatus(result) {
411
+ return {
412
+ loggedIn: result.exitCode === 0,
413
+ detail: "installed; auth is verified on the first call"
414
+ };
415
+ }
416
+ };
417
+ function failed(failure) {
418
+ return { kind: "failed", failure };
419
+ }
420
+ function parseEvents(stdout) {
421
+ const events = [];
422
+ for (const line of stdout.split("\n")) {
423
+ const trimmed = line.replace(/\r$/, "").trim();
424
+ if (!trimmed.startsWith("{"))
425
+ continue;
426
+ try {
427
+ const parsed = JSON.parse(trimmed);
428
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
429
+ events.push(parsed);
430
+ }
431
+ } catch {
432
+ }
433
+ }
434
+ return events;
435
+ }
436
+ return pi;
437
+ }
438
+
370
439
  var hasRequiredAdapters;
371
440
  function requireAdapters() {
372
441
  if (hasRequiredAdapters) return adapters;
@@ -375,9 +444,11 @@ function requireAdapters() {
375
444
  adapters.AGENT_ADAPTERS = void 0;
376
445
  const claude_code_1 = requireClaudeCode();
377
446
  const codex_1 = requireCodex();
447
+ const pi_1 = requirePi();
378
448
  adapters.AGENT_ADAPTERS = {
379
449
  "claude-code": claude_code_1.claudeCodeAdapter,
380
- codex: codex_1.codexAdapter
450
+ codex: codex_1.codexAdapter,
451
+ pi: pi_1.piAdapter
381
452
  };
382
453
  return adapters;
383
454
  }
@@ -392,7 +463,7 @@ function requireAgentId() {
392
463
  Object.defineProperty(exports$1, "__esModule", { value: true });
393
464
  exports$1.LOCAL_AGENT_IDS = void 0;
394
465
  exports$1.isLocalAgentId = isLocalAgentId;
395
- exports$1.LOCAL_AGENT_IDS = ["claude-code", "codex"];
466
+ exports$1.LOCAL_AGENT_IDS = ["claude-code", "codex", "pi"];
396
467
  const LOCAL_AGENT_ID_SET = new Set(exports$1.LOCAL_AGENT_IDS);
397
468
  function isLocalAgentId(value) {
398
469
  return typeof value === "string" && LOCAL_AGENT_ID_SET.has(value);
@@ -624,7 +695,7 @@ function requireLocalAgentEngine() {
624
695
  hasRequiredLocalAgentEngine = 1;
625
696
  Object.defineProperty(localAgentEngine, "__esModule", { value: true });
626
697
  localAgentEngine.LocalAgentEngine = void 0;
627
- const logger_1 = logger.requireLogger();
698
+ const logger_1 = strictJsonSchema.requireLogger();
628
699
  const process_runner_1 = requireProcessRunner();
629
700
  class LocalAgentEngine {
630
701
  constructor(runner, model) {
@@ -783,7 +854,7 @@ function requireCreateLocalAgent() {
783
854
  Object.defineProperty(createLocalAgent, "__esModule", { value: true });
784
855
  createLocalAgent.localAgent = localAgent;
785
856
  createLocalAgent.findLocalAgent = findLocalAgent;
786
- const logger_1 = logger.requireLogger();
857
+ const logger_1 = strictJsonSchema.requireLogger();
787
858
  const adapters_1 = requireAdapters();
788
859
  const agent_id_1 = requireAgentId();
789
860
  const detect_1 = requireDetect();
@@ -85,4 +85,90 @@ function requireLogger() {
85
85
  return logger;
86
86
  }
87
87
 
88
- export { requireLogger as r };
88
+ var strictJsonSchema = {};
89
+
90
+ var hasRequiredStrictJsonSchema;
91
+ function requireStrictJsonSchema() {
92
+ if (hasRequiredStrictJsonSchema) return strictJsonSchema;
93
+ hasRequiredStrictJsonSchema = 1;
94
+ Object.defineProperty(strictJsonSchema, "__esModule", { value: true });
95
+ strictJsonSchema.toStrictSchema = toStrictSchema;
96
+ const STRICT_UNSUPPORTED_KEYWORDS = /* @__PURE__ */ new Set([
97
+ "patternProperties",
98
+ "propertyNames",
99
+ "unevaluatedProperties",
100
+ "dependentRequired",
101
+ "dependentSchemas",
102
+ "if",
103
+ "then",
104
+ "else",
105
+ "not",
106
+ "allOf",
107
+ "oneOf"
108
+ ]);
109
+ function toStrictSchema(schema) {
110
+ return strictNode(schema);
111
+ }
112
+ function strictNode(node) {
113
+ if (typeof node !== "object" || node === null || Array.isArray(node)) {
114
+ return null;
115
+ }
116
+ const schema = node;
117
+ for (const key of Object.keys(schema)) {
118
+ if (STRICT_UNSUPPORTED_KEYWORDS.has(key))
119
+ return null;
120
+ }
121
+ const result = { ...schema };
122
+ delete result.$schema;
123
+ const isObject = schema.type === "object" || Array.isArray(schema.type) && schema.type.includes("object") || schema.properties !== void 0;
124
+ if (isObject) {
125
+ const properties = schema.properties ?? {};
126
+ if (schema.additionalProperties !== void 0 && schema.additionalProperties !== false) {
127
+ return null;
128
+ }
129
+ const required = new Set(schema.required ?? []);
130
+ const shapedProperties = {};
131
+ for (const [name, child] of Object.entries(properties)) {
132
+ if (!required.has(name))
133
+ return null;
134
+ const shaped = strictNode(child);
135
+ if (!shaped)
136
+ return null;
137
+ shapedProperties[name] = shaped;
138
+ }
139
+ result.properties = shapedProperties;
140
+ result.required = Object.keys(shapedProperties);
141
+ result.additionalProperties = false;
142
+ }
143
+ if (schema.items !== void 0) {
144
+ const shaped = strictNode(schema.items);
145
+ if (!shaped)
146
+ return null;
147
+ result.items = shaped;
148
+ }
149
+ if (schema.prefixItems !== void 0)
150
+ return null;
151
+ if (schema.anyOf !== void 0) {
152
+ const variants = schema.anyOf.map(strictNode);
153
+ if (variants.some((v) => v === null))
154
+ return null;
155
+ result.anyOf = variants;
156
+ }
157
+ for (const defsKey of ["$defs", "definitions"]) {
158
+ if (schema[defsKey] !== void 0) {
159
+ const defs = {};
160
+ for (const [name, child] of Object.entries(schema[defsKey])) {
161
+ const shaped = strictNode(child);
162
+ if (!shaped)
163
+ return null;
164
+ defs[name] = shaped;
165
+ }
166
+ result[defsKey] = defs;
167
+ }
168
+ }
169
+ return result;
170
+ }
171
+ return strictJsonSchema;
172
+ }
173
+
174
+ export { requireStrictJsonSchema as a, requireLogger as r };
@@ -1,4 +1,4 @@
1
- import { r as requireLogger } from './chunks/logger-DnkHzkX_.mjs';
1
+ import { r as requireLogger, a as requireStrictJsonSchema } from './chunks/strict-json-schema-DI9qiH5h.mjs';
2
2
  import { z } from 'zod';
3
3
  export { z } from 'zod';
4
4
 
@@ -9840,7 +9840,7 @@ function requireProviderConfig() {
9840
9840
  };
9841
9841
  };
9842
9842
  exports$1.normalizeGeminiUsage = normalizeGeminiUsage;
9843
- const buildOpenAiStyleRequest = (baseUrl) => ({ prompt, model, maxTokens, assistantPrefillPrompt }, apiKey) => {
9843
+ const buildOpenAiStyleRequest = (baseUrl) => ({ prompt, model, maxTokens, assistantPrefillPrompt, responseSchema }, apiKey) => {
9844
9844
  const messages = [];
9845
9845
  messages.push({ role: "user", content: prompt });
9846
9846
  if (assistantPrefillPrompt) {
@@ -9853,7 +9853,13 @@ function requireProviderConfig() {
9853
9853
  max_completion_tokens: maxTokens,
9854
9854
  top_p: 1,
9855
9855
  stream: false,
9856
- stop: null
9856
+ stop: null,
9857
+ ...responseSchema && {
9858
+ response_format: {
9859
+ type: "json_schema",
9860
+ json_schema: { name: "response", strict: true, schema: responseSchema }
9861
+ }
9862
+ }
9857
9863
  };
9858
9864
  const headers = {
9859
9865
  "Content-Type": "application/json",
@@ -9980,6 +9986,12 @@ function requireProviderConfig() {
9980
9986
  providerId: "groq",
9981
9987
  apiOperation: "chat/completions",
9982
9988
  errorLabel: "Groq",
9989
+ // https://console.groq.com/docs/structured-outputs
9990
+ strictSchemaModels: /* @__PURE__ */ new Set([
9991
+ "openai/gpt-oss-120b",
9992
+ "openai/gpt-oss-20b",
9993
+ "qwen/qwen3.8-27b"
9994
+ ]),
9983
9995
  buildRequest: buildOpenAiStyleRequest("https://api.groq.com/openai/v1/chat/completions"),
9984
9996
  parseResponse: parseOpenAiStyleResponse("Groq")
9985
9997
  },
@@ -10027,7 +10039,7 @@ function requireModelInfo() {
10027
10039
  exports$1.MODEL_ALIASES = {
10028
10040
  fast: [
10029
10041
  { provider: "cerebras", model: "qwen-3.8-27b" },
10030
- { provider: "groq", model: "qwen/qwen3.8-27b" },
10042
+ { provider: "groq", model: "openai/gpt-oss-120b" },
10031
10043
  { provider: "openrouter", model: "z-ai/glm-5.2" }
10032
10044
  ],
10033
10045
  gemini: [
@@ -10180,6 +10192,7 @@ function requireHttpEngine() {
10180
10192
  hasRequiredHttpEngine = 1;
10181
10193
  Object.defineProperty(httpEngine, "__esModule", { value: true });
10182
10194
  httpEngine.HttpEngine = void 0;
10195
+ const strict_json_schema_1 = requireStrictJsonSchema();
10183
10196
  const provider_call_1 = requireProviderCall();
10184
10197
  const provider_config_1 = requireProviderConfig();
10185
10198
  const FATAL_HTTP_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
@@ -10188,35 +10201,55 @@ function requireHttpEngine() {
10188
10201
  this.target = target;
10189
10202
  this.logger = logger;
10190
10203
  }
10191
- /** Provider APIs are given the schema in the prompt; none is enforced natively yet. */
10192
- nativeSchema() {
10193
- return null;
10204
+ /** Enforced in strict mode on models whose provider supports it; other models get the schema in the prompt. */
10205
+ nativeSchema(envelopeSchema) {
10206
+ const { provider, model } = this.target;
10207
+ if (!provider_config_1.providerConfigs[provider].strictSchemaModels?.has(model))
10208
+ return null;
10209
+ return (0, strict_json_schema_1.toStrictSchema)(envelopeSchema);
10194
10210
  }
10195
10211
  async attempt(request) {
10196
10212
  const { provider, model, apiKey } = this.target;
10197
10213
  const config = provider_config_1.providerConfigs[provider];
10214
+ const responseSchema = request.enforcement.kind === "native" ? request.enforcement.schema : void 0;
10198
10215
  const call = await (0, provider_call_1.callProvider)({
10199
10216
  target: this.target,
10200
10217
  apiOperation: config.apiOperation,
10201
10218
  maxTokens: request.maxTokens,
10202
- request: config.buildRequest({ prompt: request.prompt, model, maxTokens: request.maxTokens }, apiKey),
10219
+ request: config.buildRequest({ prompt: request.prompt, model, maxTokens: request.maxTokens, responseSchema }, apiKey),
10203
10220
  parseResponse: config.parseResponse,
10204
10221
  responseLength: (reply) => reply.text.length,
10205
10222
  usage: (reply) => reply.usage
10206
10223
  }, this.logger);
10207
10224
  if (call.kind === "failed") {
10208
- return { kind: "failed", failure: failureForStatus(call.status, call.message) };
10225
+ return { kind: "failed", failure: failureForStatus(call.status, call.message, responseSchema !== void 0) };
10226
+ }
10227
+ const answer = responseSchema ? readEnvelope(call.reply.text) : { kind: "text", text: call.reply.text };
10228
+ if (!answer) {
10229
+ return {
10230
+ kind: "failed",
10231
+ failure: { kind: "transient", message: `Structured output is not valid JSON: ${call.reply.text}` }
10232
+ };
10209
10233
  }
10210
10234
  return {
10211
10235
  kind: "answered",
10212
- answer: { kind: "text", text: call.reply.text },
10236
+ answer,
10213
10237
  usage: call.reply.usage,
10214
10238
  origin: { engine: "http", provider, model }
10215
10239
  };
10216
10240
  }
10217
10241
  }
10218
10242
  httpEngine.HttpEngine = HttpEngine;
10219
- function failureForStatus(status, message) {
10243
+ function readEnvelope(text) {
10244
+ try {
10245
+ return { kind: "structured", envelope: JSON.parse(text) };
10246
+ } catch {
10247
+ return null;
10248
+ }
10249
+ }
10250
+ function failureForStatus(status, message, sentSchema) {
10251
+ if (sentSchema && status === 400)
10252
+ return { kind: "schema-rejected", message };
10220
10253
  const fatal = status !== void 0 && FATAL_HTTP_STATUSES.has(status);
10221
10254
  return { kind: fatal ? "fatal" : "transient", message };
10222
10255
  }
@@ -1,4 +1,4 @@
1
- import { r as requireLogger } from './chunks/logger-DnkHzkX_.mjs';
1
+ import { a as requireStrictJsonSchema, r as requireLogger } from './chunks/strict-json-schema-DI9qiH5h.mjs';
2
2
  import { createRequire } from 'node:module';
3
3
 
4
4
  var localAgent$1 = {};
@@ -165,92 +165,6 @@ function requireClaudeCode() {
165
165
 
166
166
  var codex = {};
167
167
 
168
- var strictJsonSchema = {};
169
-
170
- var hasRequiredStrictJsonSchema;
171
- function requireStrictJsonSchema() {
172
- if (hasRequiredStrictJsonSchema) return strictJsonSchema;
173
- hasRequiredStrictJsonSchema = 1;
174
- Object.defineProperty(strictJsonSchema, "__esModule", { value: true });
175
- strictJsonSchema.toStrictSchema = toStrictSchema;
176
- const STRICT_UNSUPPORTED_KEYWORDS = /* @__PURE__ */ new Set([
177
- "patternProperties",
178
- "propertyNames",
179
- "unevaluatedProperties",
180
- "dependentRequired",
181
- "dependentSchemas",
182
- "if",
183
- "then",
184
- "else",
185
- "not",
186
- "allOf",
187
- "oneOf"
188
- ]);
189
- function toStrictSchema(schema) {
190
- return strictNode(schema);
191
- }
192
- function strictNode(node) {
193
- if (typeof node !== "object" || node === null || Array.isArray(node)) {
194
- return null;
195
- }
196
- const schema = node;
197
- for (const key of Object.keys(schema)) {
198
- if (STRICT_UNSUPPORTED_KEYWORDS.has(key))
199
- return null;
200
- }
201
- const result = { ...schema };
202
- delete result.$schema;
203
- const isObject = schema.type === "object" || Array.isArray(schema.type) && schema.type.includes("object") || schema.properties !== void 0;
204
- if (isObject) {
205
- const properties = schema.properties ?? {};
206
- if (schema.additionalProperties !== void 0 && schema.additionalProperties !== false) {
207
- return null;
208
- }
209
- const required = new Set(schema.required ?? []);
210
- const shapedProperties = {};
211
- for (const [name, child] of Object.entries(properties)) {
212
- if (!required.has(name))
213
- return null;
214
- const shaped = strictNode(child);
215
- if (!shaped)
216
- return null;
217
- shapedProperties[name] = shaped;
218
- }
219
- result.properties = shapedProperties;
220
- result.required = Object.keys(shapedProperties);
221
- result.additionalProperties = false;
222
- }
223
- if (schema.items !== void 0) {
224
- const shaped = strictNode(schema.items);
225
- if (!shaped)
226
- return null;
227
- result.items = shaped;
228
- }
229
- if (schema.prefixItems !== void 0)
230
- return null;
231
- if (schema.anyOf !== void 0) {
232
- const variants = schema.anyOf.map(strictNode);
233
- if (variants.some((v) => v === null))
234
- return null;
235
- result.anyOf = variants;
236
- }
237
- for (const defsKey of ["$defs", "definitions"]) {
238
- if (schema[defsKey] !== void 0) {
239
- const defs = {};
240
- for (const [name, child] of Object.entries(schema[defsKey])) {
241
- const shaped = strictNode(child);
242
- if (!shaped)
243
- return null;
244
- defs[name] = shaped;
245
- }
246
- result[defsKey] = defs;
247
- }
248
- }
249
- return result;
250
- }
251
- return strictJsonSchema;
252
- }
253
-
254
168
  var hasRequiredCodex;
255
169
  function requireCodex() {
256
170
  if (hasRequiredCodex) return codex;
@@ -364,6 +278,161 @@ ${result.stderr}`.trim();
364
278
  return codex;
365
279
  }
366
280
 
281
+ var pi = {};
282
+
283
+ var hasRequiredPi;
284
+ function requirePi() {
285
+ if (hasRequiredPi) return pi;
286
+ hasRequiredPi = 1;
287
+ Object.defineProperty(pi, "__esModule", { value: true });
288
+ pi.piAdapter = void 0;
289
+ const failure_classification_1 = requireFailureClassification();
290
+ const SYSTEM_PROMPT = [
291
+ "You are a data transformation function called by a program.",
292
+ "You have no tools. Follow the instructions in the user message exactly",
293
+ "and reply only with the requested JSON."
294
+ ].join(" ");
295
+ pi.piAdapter = {
296
+ id: "pi",
297
+ binaryName: "pi",
298
+ // Every environment credential Pi's providers recognize. Under `auth:
299
+ // "subscription"` these are removed so Pi bills its own `auth.json` login.
300
+ apiKeyEnvVars: [
301
+ "AI_GATEWAY_API_KEY",
302
+ "ANT_LING_API_KEY",
303
+ "ANTHROPIC_API_KEY",
304
+ "ANTHROPIC_AUTH_TOKEN",
305
+ "ANTHROPIC_OAUTH_TOKEN",
306
+ "AWS_ACCESS_KEY_ID",
307
+ "AWS_SECRET_ACCESS_KEY",
308
+ "AWS_SESSION_TOKEN",
309
+ "AZURE_OPENAI_API_KEY",
310
+ "BASETEN_API_KEY",
311
+ "CEREBRAS_API_KEY",
312
+ "CLOUDFLARE_API_KEY",
313
+ "COPILOT_GITHUB_TOKEN",
314
+ "DEEPSEEK_API_KEY",
315
+ "FIREWORKS_API_KEY",
316
+ "GEMINI_API_KEY",
317
+ "GOOGLE_API_KEY",
318
+ "GOOGLE_CLOUD_API_KEY",
319
+ "GROQ_API_KEY",
320
+ "HF_TOKEN",
321
+ "KIMI_API_KEY",
322
+ "META_API_KEY",
323
+ "MINIMAX_API_KEY",
324
+ "MINIMAX_CN_API_KEY",
325
+ "MISTRAL_API_KEY",
326
+ "MOONSHOT_API_KEY",
327
+ "NVIDIA_API_KEY",
328
+ "OPENAI_API_KEY",
329
+ "OPENCODE_API_KEY",
330
+ "OPENROUTER_API_KEY",
331
+ "QWEN_TOKEN_PLAN_API_KEY",
332
+ "QWEN_TOKEN_PLAN_CN_API_KEY",
333
+ "RADIUS_API_KEY",
334
+ "TOGETHER_API_KEY",
335
+ "XAI_API_KEY",
336
+ "XIAOMI_API_KEY",
337
+ "XIAOMI_TOKEN_PLAN_AMS_API_KEY",
338
+ "XIAOMI_TOKEN_PLAN_CN_API_KEY",
339
+ "XIAOMI_TOKEN_PLAN_SGP_API_KEY",
340
+ "ZAI_API_KEY",
341
+ "ZAI_CODING_CN_API_KEY"
342
+ ],
343
+ // Pi uses its own `provider/id` model names; set one with localAgent({ model }).
344
+ modelAliases: {},
345
+ nativeSchema() {
346
+ return null;
347
+ },
348
+ buildInvocation(request, model) {
349
+ const args = [
350
+ "--mode",
351
+ "json",
352
+ "--no-session",
353
+ "--no-tools",
354
+ "--no-builtin-tools",
355
+ "--no-extensions",
356
+ "--no-skills",
357
+ "--no-prompt-templates",
358
+ "--no-themes",
359
+ "--no-context-files",
360
+ "--system-prompt",
361
+ SYSTEM_PROMPT,
362
+ "--offline"
363
+ ];
364
+ if (model !== void 0) {
365
+ args.push("--model", model);
366
+ }
367
+ return { args, stdin: request.prompt };
368
+ },
369
+ readResult(result, request) {
370
+ const events = parseEvents(result.stdout);
371
+ const assistant = events.filter((event) => event.type === "message_end" && event.message?.role === "assistant").map((event) => event.message).pop();
372
+ if (assistant && (assistant.stopReason === "error" || assistant.errorMessage)) {
373
+ return failed((0, failure_classification_1.classifyAgentFailure)("pi", assistant.errorMessage ?? "pi stopped with an error"));
374
+ }
375
+ const retryFailed = events.find((event) => event.type === "auto_retry_end" && event.success === false);
376
+ if (retryFailed) {
377
+ return failed((0, failure_classification_1.classifyAgentFailure)("pi", retryFailed.finalError ?? "auto retry failed"));
378
+ }
379
+ if (!assistant) {
380
+ return failed((0, failure_classification_1.classifyAgentFailure)("pi", `exited with code ${result.exitCode} without a reply: ${(0, failure_classification_1.stderrTail)(result)}`));
381
+ }
382
+ const text = (assistant.content ?? []).filter((block) => block.type === "text").map((block) => block.text ?? "").join("");
383
+ const usage = assistant.usage ?? {};
384
+ const promptTokens = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
385
+ const completionTokens = usage.output ?? 0;
386
+ const reported = {
387
+ usage: { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens },
388
+ model: assistant.model,
389
+ costUsdEstimate: usage.cost?.total
390
+ };
391
+ if (request.enforcement.kind === "in-prompt") {
392
+ return { kind: "answered", answer: { kind: "text", text }, ...reported };
393
+ }
394
+ let envelope;
395
+ try {
396
+ envelope = JSON.parse(text);
397
+ } catch {
398
+ return failed({ kind: "transient", message: `pi reply is not JSON: ${text}` });
399
+ }
400
+ return { kind: "answered", answer: { kind: "structured", envelope }, ...reported };
401
+ },
402
+ // Pi has no provider-agnostic login check: `pi auth check` requires --provider
403
+ // or --model, and the provider differs per user (it is not known until Pi's
404
+ // settings are read). Report installed Pi as logged in; the first real call
405
+ // surfaces any auth error, which classifyAgentFailure marks fatal.
406
+ loginStatusArgs: ["--version"],
407
+ readLoginStatus(result) {
408
+ return {
409
+ loggedIn: result.exitCode === 0,
410
+ detail: "installed; auth is verified on the first call"
411
+ };
412
+ }
413
+ };
414
+ function failed(failure) {
415
+ return { kind: "failed", failure };
416
+ }
417
+ function parseEvents(stdout) {
418
+ const events = [];
419
+ for (const line of stdout.split("\n")) {
420
+ const trimmed = line.replace(/\r$/, "").trim();
421
+ if (!trimmed.startsWith("{"))
422
+ continue;
423
+ try {
424
+ const parsed = JSON.parse(trimmed);
425
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
426
+ events.push(parsed);
427
+ }
428
+ } catch {
429
+ }
430
+ }
431
+ return events;
432
+ }
433
+ return pi;
434
+ }
435
+
367
436
  var hasRequiredAdapters;
368
437
  function requireAdapters() {
369
438
  if (hasRequiredAdapters) return adapters;
@@ -372,9 +441,11 @@ function requireAdapters() {
372
441
  adapters.AGENT_ADAPTERS = void 0;
373
442
  const claude_code_1 = requireClaudeCode();
374
443
  const codex_1 = requireCodex();
444
+ const pi_1 = requirePi();
375
445
  adapters.AGENT_ADAPTERS = {
376
446
  "claude-code": claude_code_1.claudeCodeAdapter,
377
- codex: codex_1.codexAdapter
447
+ codex: codex_1.codexAdapter,
448
+ pi: pi_1.piAdapter
378
449
  };
379
450
  return adapters;
380
451
  }
@@ -389,7 +460,7 @@ function requireAgentId() {
389
460
  Object.defineProperty(exports$1, "__esModule", { value: true });
390
461
  exports$1.LOCAL_AGENT_IDS = void 0;
391
462
  exports$1.isLocalAgentId = isLocalAgentId;
392
- exports$1.LOCAL_AGENT_IDS = ["claude-code", "codex"];
463
+ exports$1.LOCAL_AGENT_IDS = ["claude-code", "codex", "pi"];
393
464
  const LOCAL_AGENT_ID_SET = new Set(exports$1.LOCAL_AGENT_IDS);
394
465
  function isLocalAgentId(value) {
395
466
  return typeof value === "string" && LOCAL_AGENT_ID_SET.has(value);
@@ -1,4 +1,5 @@
1
1
  import { Logger } from "../../../lib/logger";
2
+ import { JsonSchema } from "../../../lib/schema-utils";
2
3
  import { AttemptOutcome, AttemptRequest, LlmEngine } from "../llm-engine";
3
4
  import { ProviderTarget } from "./provider-call";
4
5
  /** Answers cast attempts on one model of an LLM provider's HTTP API. */
@@ -6,7 +7,7 @@ export declare class HttpEngine implements LlmEngine {
6
7
  private readonly target;
7
8
  private readonly logger;
8
9
  constructor(target: ProviderTarget, logger: Logger);
9
- /** Provider APIs are given the schema in the prompt; none is enforced natively yet. */
10
- nativeSchema(): null;
10
+ /** Enforced in strict mode on models whose provider supports it; other models get the schema in the prompt. */
11
+ nativeSchema(envelopeSchema: JsonSchema): JsonSchema | null;
11
12
  attempt(request: AttemptRequest): Promise<AttemptOutcome>;
12
13
  }
@@ -1,3 +1,4 @@
1
+ import { JsonSchema } from "../../../lib/schema-utils";
1
2
  export type ProviderRequestBase = {
2
3
  url: string;
3
4
  method?: string;
@@ -15,6 +16,8 @@ export interface ProviderRequestOptions {
15
16
  model: string;
16
17
  maxTokens: number;
17
18
  assistantPrefillPrompt?: string;
19
+ /** A JSON schema the provider enforces on the reply, in strict mode. */
20
+ responseSchema?: JsonSchema;
18
21
  }
19
22
  export type NormalizedUsage = {
20
23
  promptTokens: number;
@@ -30,6 +33,8 @@ export interface ProviderConfig {
30
33
  providerId: LlmProviderId;
31
34
  apiOperation: string;
32
35
  errorLabel: string;
36
+ /** Models that can enforce a response JSON schema in strict mode. */
37
+ strictSchemaModels?: ReadonlySet<string>;
33
38
  buildRequest(options: ProviderRequestOptions, apiKey: string): ProviderRequestBase;
34
39
  parseResponse(response: Response): Promise<ProviderParsedResponse>;
35
40
  }
@@ -0,0 +1,2 @@
1
+ import { AgentAdapter } from "./types";
2
+ export declare const piAdapter: AgentAdapter;
@@ -1,4 +1,4 @@
1
1
  /** Supported coding-agent CLIs, in the order findLocalAgent() tries them. */
2
- export declare const LOCAL_AGENT_IDS: readonly ["claude-code", "codex"];
2
+ export declare const LOCAL_AGENT_IDS: readonly ["claude-code", "codex", "pi"];
3
3
  export type LocalAgentId = (typeof LOCAL_AGENT_IDS)[number];
4
4
  export declare function isLocalAgentId(value: unknown): value is LocalAgentId;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuabase",
3
3
  "description": "Nuabase TypeScript client to create typed functions from LLM prompts",
4
- "version": "2.3.5",
4
+ "version": "2.3.11",
5
5
  "type": "module",
6
6
  "author": "Jasim A Basheer <jasim@nuabase.com>",
7
7
  "engines": {
@@ -59,7 +59,6 @@
59
59
  "zod": "^4.1.5"
60
60
  },
61
61
  "devDependencies": {
62
- "ajv": "^8.17.1",
63
62
  "@eslint/js": "^9.35.0",
64
63
  "@ianvs/prettier-plugin-sort-imports": "^4.7.0",
65
64
  "@rollup/plugin-commonjs": "^28.0.6",
@@ -70,29 +69,23 @@
70
69
  "@types/jest": "^30.0.0",
71
70
  "@types/node": "^24.3.3",
72
71
  "@types/signale": "^1",
73
- "@types/yargs": "^17.0.33",
74
- "chalk": "^5.6.2",
72
+ "ajv": "^8.17.1",
75
73
  "esbuild": "^0.25.9",
76
74
  "eslint": "^9.35.0",
77
75
  "eventsource": "^4.0.0",
78
76
  "fs-extra": "^11.3.1",
79
77
  "jest": "^30.1.3",
80
78
  "knip": "^5.63.1",
81
- "new-github-release-url": "^2.0.0",
82
- "open": "^10.2.0",
83
79
  "prettier": "^3.6.2",
84
80
  "rimraf": "^6.0.1",
85
81
  "rollup": "^4.50.1",
86
82
  "rollup-plugin-esbuild": "^6.2.1",
87
83
  "rollup-plugin-node-externals": "^8.1.1",
88
84
  "signale": "^1.4.0",
89
- "simple-git": "^3.28.0",
90
85
  "ts-jest": "^29.4.1",
91
86
  "tsx": "^4.20.5",
92
87
  "typescript": "^5.9.2",
93
88
  "typescript-eslint": "^8.43.0",
94
- "version-next": "^1.0.2",
95
- "yargs": "^18.0.0",
96
89
  "zod": "^4.1.5",
97
90
  "zx": "^8.8.1",
98
91
  "nua-llm-core": "1.0.0"
@@ -108,7 +101,7 @@
108
101
  "lint": "eslint --cache .",
109
102
  "jest": "jest",
110
103
  "test": "pnpm run prettier:check && pnpm run typecheck && pnpm run lint && pnpm run jest && pnpm run build && pnpm run pack:test",
111
- "release": "pnpm test && tsx scripts/release",
104
+ "release": "tsx scripts/release",
112
105
  "knip": "knip"
113
106
  }
114
107
  }