pi-nebius 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/CONTRIBUTING.md +39 -0
- package/LICENSE +21 -0
- package/README.md +239 -0
- package/SECURITY.md +47 -0
- package/benchmarks/add-api-endpoint/benchmark.yaml +13 -0
- package/benchmarks/add-api-endpoint/fixture/app.mjs +6 -0
- package/benchmarks/add-api-endpoint/fixture/app.test.mjs +8 -0
- package/benchmarks/add-api-endpoint/fixture/package.json +8 -0
- package/benchmarks/add-api-endpoint/validation/check.test.mjs +37 -0
- package/benchmarks/fix-auth-bug/benchmark.yaml +14 -0
- package/benchmarks/fix-auth-bug/fixture/auth.mjs +4 -0
- package/benchmarks/fix-auth-bug/fixture/auth.test.mjs +26 -0
- package/benchmarks/fix-auth-bug/fixture/package.json +8 -0
- package/benchmarks/fix-auth-bug/validation/check.test.mjs +25 -0
- package/benchmarks/multi-file-feature/benchmark.yaml +15 -0
- package/benchmarks/multi-file-feature/fixture/package.json +8 -0
- package/benchmarks/multi-file-feature/fixture/routes.mjs +11 -0
- package/benchmarks/multi-file-feature/fixture/routes.test.mjs +14 -0
- package/benchmarks/multi-file-feature/fixture/serialize.mjs +3 -0
- package/benchmarks/multi-file-feature/fixture/store.mjs +10 -0
- package/benchmarks/multi-file-feature/validation/check.test.mjs +52 -0
- package/benchmarks/refactor-module/benchmark.yaml +12 -0
- package/benchmarks/refactor-module/fixture/invoice.mjs +8 -0
- package/benchmarks/refactor-module/fixture/invoice.test.mjs +9 -0
- package/benchmarks/refactor-module/fixture/package.json +8 -0
- package/benchmarks/refactor-module/validation/check.test.mjs +36 -0
- package/dist/benchmark/cli.js +112 -0
- package/dist/benchmark/command.js +194 -0
- package/dist/benchmark/definition.js +109 -0
- package/dist/benchmark/host-worker.js +14 -0
- package/dist/benchmark/instrumentation.js +296 -0
- package/dist/benchmark/metrics.js +78 -0
- package/dist/benchmark/process.js +122 -0
- package/dist/benchmark/project.js +70 -0
- package/dist/benchmark/report.js +94 -0
- package/dist/benchmark/runner.js +376 -0
- package/dist/benchmark/types.js +1 -0
- package/dist/benchmark/worker.js +134 -0
- package/dist/benchmark/workspace.js +55 -0
- package/dist/discovery.js +154 -0
- package/dist/errors.js +32 -0
- package/dist/index.js +86 -0
- package/dist/model-settings-command.js +130 -0
- package/dist/model-settings.js +101 -0
- package/dist/models.js +62 -0
- package/dist/provider.js +48 -0
- package/docs/benchmark-research.md +35 -0
- package/docs/benchmarking.md +253 -0
- package/docs/security-review.md +49 -0
- package/docs/validation.md +51 -0
- package/examples/models.json +31 -0
- package/package.json +74 -0
- package/src/benchmark/cli.ts +118 -0
- package/src/benchmark/command.ts +218 -0
- package/src/benchmark/definition.ts +112 -0
- package/src/benchmark/host-worker.ts +14 -0
- package/src/benchmark/instrumentation.ts +298 -0
- package/src/benchmark/metrics.ts +101 -0
- package/src/benchmark/process.ts +120 -0
- package/src/benchmark/project.ts +71 -0
- package/src/benchmark/report.ts +111 -0
- package/src/benchmark/runner.ts +458 -0
- package/src/benchmark/types.ts +180 -0
- package/src/benchmark/worker.ts +150 -0
- package/src/benchmark/workspace.ts +64 -0
- package/src/discovery.ts +176 -0
- package/src/errors.ts +32 -0
- package/src/index.ts +96 -0
- package/src/model-settings-command.ts +151 -0
- package/src/model-settings.ts +129 -0
- package/src/models.ts +73 -0
- package/src/provider.ts +63 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Agentic coding benchmarks with Pi + Nebius
|
|
2
|
+
|
|
3
|
+
This tool measures how a model completes a coding task **through Pi's full agent/tool loop**. It observes Pi; it does not implement an agent, alter model payloads, compress context, rewrite tool results, optimize prompts, or route between models.
|
|
4
|
+
|
|
5
|
+
A traditional inference benchmark measures `prompt → model → response`. Here the unit is:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
task → Pi → model → tool → model → tool → … → deterministic validation
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Tokens/second alone cannot describe task efficiency. A slower model that solves a task in five turns can finish sooner than a faster model that needs twenty turns. Results therefore expose success, time, cumulative usage, and tools separately. There is no composite score or LLM judge.
|
|
12
|
+
|
|
13
|
+
## Run inside Pi
|
|
14
|
+
|
|
15
|
+
Use `/nebius-benchmark --models ID_A,ID_B --runs 3` to enter your own prompt in an editor. Without `--models`, it uses the currently selected Nebius model. Each run starts from a snapshot of the current project. Results appear in Pi; `/nebius-benchmark cancel` stops the run. See [the README](../README.md#benchmark-inside-pi) for snapshot exclusions and limits.
|
|
16
|
+
|
|
17
|
+
Custom prompts have `definition.validationMode: "none"`, `validation.checked: false`, and no correctness verdict. `success` remains false because no validator established success; aggregate `successRate` is null. A null `failure` means execution finished without an error, not that the task was solved. The report labels this FINISHED and explicitly says correctness was not checked. Existing validated task reports keep their success semantics.
|
|
18
|
+
|
|
19
|
+
Use `--task fix-auth-bug` (or another bundled task name) to run a task with deterministic validators instead. Both paths run on the installed host Pi SDK without a compiler or development dependencies.
|
|
20
|
+
|
|
21
|
+
## Standalone CLI: install and run
|
|
22
|
+
|
|
23
|
+
Requirements: **macOS or Linux**, **Node 22.19+**, and the tested **Pi 0.85.1** packages. The source checkout pins development versions; retain `package-lock.json` for reproducibility.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cd /path/to/pi-nebius
|
|
27
|
+
npm ci
|
|
28
|
+
npm run build
|
|
29
|
+
export NEBIUS_API_KEY="your-key"
|
|
30
|
+
|
|
31
|
+
npm run benchmark -- \
|
|
32
|
+
--benchmark benchmarks/fix-auth-bug \
|
|
33
|
+
--models 'EXACT_NEBIUS_MODEL_A,EXACT_NEBIUS_MODEL_B' \
|
|
34
|
+
--runs 3 \
|
|
35
|
+
--timeout 120 \
|
|
36
|
+
--output benchmark-results/auth-experiment
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Alternatively, after `npm install --global .` from this checkout, use `pi-nebius benchmark ...`. The package is not published to npm. The compiled command can also be invoked with `node dist/benchmark/cli.js benchmark ...`.
|
|
40
|
+
|
|
41
|
+
Model IDs are exact Nebius IDs such as `vendor/model`, without an extra `nebius/` prefix. The CLI performs one fresh authenticated discovery before the experiment and freezes the selected model definitions. It does not auto-select a model. Discovery and model availability are outside the measured runs. Models absent from discovery fail preflight explicitly.
|
|
42
|
+
|
|
43
|
+
| Option | Meaning |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| `--benchmark` | Directory containing `benchmark.yaml`, or a YAML/JSON file |
|
|
46
|
+
| `--models` | Comma-separated exact IDs, each used for every repetition |
|
|
47
|
+
| `--runs` | Repetitions per model; default 1 |
|
|
48
|
+
| `--timeout` | Agent-process deadline in seconds; overrides the definition |
|
|
49
|
+
| `--output` | New directory; existing directories are rejected |
|
|
50
|
+
| `--concurrency` | **1 only in v1**, for identical unmodified Pi system prompts |
|
|
51
|
+
|
|
52
|
+
Runs are scheduled round-robin: A1, B1, A2, B2. Ctrl-C stops the active session, retains its partial measurements, writes a cancelled result set, and does not start pending runs. Exit codes: 0 when all runs pass, 1 when any run fails, 2 for configuration/preflight errors, 130 for cancellation. Input/configuration errors before a run begins do not create a complete result set.
|
|
53
|
+
|
|
54
|
+
### Why v1 is sequential
|
|
55
|
+
|
|
56
|
+
Pi embeds an absolute working directory in its system prompt. Different simultaneous workspace paths therefore produce different prompts even with identical SDK settings. Rather than modify that prompt, this runner creates each fresh copy at **the same active path**, archives the finished workspace, removes the active copy, and only then starts the next run. The complete prompt hash is recorded and tested for equality within an experiment.
|
|
57
|
+
|
|
58
|
+
Parallel runs would need an additional isolation layer exposing the same absolute path in each process namespace. That is not implemented in v1. Even with that layer, concurrency can distort latency through provider load, rate limits, shared serving infrastructure, and local CPU contention. Sequential execution is the baseline for latency comparisons. Absolute paths—and therefore prompt hashes—can differ between separate experiments; compare the recorded metadata.
|
|
59
|
+
|
|
60
|
+
## Architecture
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
CLI / runner
|
|
64
|
+
├─ parse benchmark definitions
|
|
65
|
+
├─ freeze fixture + trusted validators
|
|
66
|
+
└─ for each model/repetition:
|
|
67
|
+
fresh active workspace + private Pi config
|
|
68
|
+
→ separate Node worker
|
|
69
|
+
→ createAgentSession() + existing Nebius provider
|
|
70
|
+
→ normal Pi agent/tools
|
|
71
|
+
→ stop worker and tool descendants
|
|
72
|
+
→ deterministic validators
|
|
73
|
+
→ archived files + trace + run.json
|
|
74
|
+
→ results.json + aggregate terminal report
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Pi's public `createAgentSession`, `DefaultResourceLoader`, `SettingsManager.inMemory`, `ModelRuntime`, and `SessionManager.inMemory` establish each session. A native provider is registered through an inline extension, using the existing `nebiusProvider` adapter. `session.prompt`, `session.subscribe`, and `session.abort` drive and observe it. `before_agent_start` records the system-prompt hash and returns nothing. Instrumentation never returns a modified event/payload.
|
|
78
|
+
|
|
79
|
+
The provider's supported `fetch` injection observes every Chat Completions HTTP attempt, including adapter retries and Pi's own compaction requests. It forwards request arguments and response bytes unchanged. The observer reads only usage and small response metadata fields from the streaming protocol; Pi still parses content, constructs tools, executes them, and manages the conversation.
|
|
80
|
+
|
|
81
|
+
No user-installed extensions, skills, themes, prompt templates, context files, saved sessions, or `models.json` settings enter a run. This defines the clean benchmark configuration, identically for all models. Pi's own default retries and automatic compaction remain **unchanged**, are recorded, and their requests count toward token usage. There is no benchmark-added compression/pruning. Pi can clamp its default thinking level to a model's capabilities; the effective setting and model definition are recorded rather than forced into unsupported behavior.
|
|
82
|
+
|
|
83
|
+
## Definitions and fixtures
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
benchmarks/my-task/
|
|
87
|
+
benchmark.yaml
|
|
88
|
+
fixture/ # the agent's starting files
|
|
89
|
+
validation/ # trusted checks, outside its workspace
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```yaml
|
|
93
|
+
schemaVersion: 1
|
|
94
|
+
name: my-task
|
|
95
|
+
task: |
|
|
96
|
+
Find and fix the bug. Preserve the public API.
|
|
97
|
+
fixture: fixture
|
|
98
|
+
validationDirectory: validation
|
|
99
|
+
tools: [read, bash, edit, write]
|
|
100
|
+
timeout: 600
|
|
101
|
+
validationTimeout: 60
|
|
102
|
+
validation:
|
|
103
|
+
- command: node
|
|
104
|
+
args: [--test, "{validation}/check.test.mjs"]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The parser rejects unknown fields, duplicate YAML keys, aliases, invalid deadlines, and paths escaping the definition directory. `systemPrompt` is optional; when omitted the normal Pi prompt is used. Supplying it is an explicit benchmark configuration applied equally to all runs—not a per-model optimization.
|
|
108
|
+
|
|
109
|
+
Validation accepts command/args objects (recommended), or strings such as `npm test` executed through `/bin/sh -c`. `{validation}` expands to the restored trusted-validator directory. Avoid shell-string interpolation for paths with spaces; use argument arrays. Validation runs with the agent workspace as cwd. All commands must exit zero. Output, exit code, signal, elapsed time, timeout, and truncation state are recorded; output is capped at 256 KiB per command.
|
|
110
|
+
|
|
111
|
+
Optional `setup` commands run on each fresh copy before Pi starts, with `validationTimeout` as their individual deadline. For dependency installs, supply lockfiles and use reproducible commands such as `npm ci`. The included fixtures have **no external dependencies or setup**. Setup duration is included in run wall time, not agent wall time. The agent deadline excludes validation; each validator has its own deadline.
|
|
112
|
+
|
|
113
|
+
The fixture snapshot excludes `.git` metadata, rejects symlinks/special files and `.env` credential files, preserves executable file bits and empty directories, and is SHA-256 hashed. Files are copied, never hard-linked. `node_modules` is not silently excluded: prefer setup commands to committing dependency trees. Generated files with links/special entries may prevent archival/hashing; any failure is recorded explicitly.
|
|
114
|
+
|
|
115
|
+
Trusted validators are copied from the original snapshot after the agent exits, and their hash is checked before and after validation. Agents may run the fixture's visible tests while working; modifying those tests does not replace the trusted checks. Generic `npm test` commands still depend on the workspace's scripts/tests, so use external validators when test tampering would matter.
|
|
116
|
+
|
|
117
|
+
**Filesystem isolation is not an OS security sandbox.** Pi's ordinary tools retain host access. Copies prevent ordinary run-to-run contamination; they do not constrain a deliberately escaping shell command. Use a disposable machine/container for untrusted tasks. v1 does not implement a container orchestrator. The API key goes to the worker over IPC, remains in memory, and is absent from tool/validation environments, arguments, and configuration files.
|
|
118
|
+
|
|
119
|
+
### Included suite
|
|
120
|
+
|
|
121
|
+
| Benchmark | Task and trusted checks |
|
|
122
|
+
| --- | --- |
|
|
123
|
+
| `fix-auth-bug` | Correct seconds/milliseconds expiry handling, boundary behavior, revocation, and invalid timestamps |
|
|
124
|
+
| `add-api-endpoint` | Add `/api/sum` with numeric validation, method handling, JSON responses, and preserved health route |
|
|
125
|
+
| `refactor-module` | Extract a shared subtotal helper while preserving calculations and avoiding duplicated reductions |
|
|
126
|
+
| `multi-file-feature` | Persist task completion across store, serializer, and PATCH/GET routes |
|
|
127
|
+
|
|
128
|
+
Tests verify all four original fixtures fail trusted validation and reference solutions pass. Refactor checks include a narrow structural assertion in addition to behavior. These are small deterministic acceptance tests, not a claim to detect arbitrary adversarial solutions or prove general correctness.
|
|
129
|
+
|
|
130
|
+
## Usage and cumulative tokens
|
|
131
|
+
|
|
132
|
+
Nebius usage is authoritative. The runner records each response's `prompt_tokens`, `completion_tokens`, optional `prompt_tokens_details.cached_tokens`, and optional `completion_tokens_details.reasoning_tokens`. It does not run a local tokenizer. Repeated cumulative snapshots within one stream replace the request's snapshot; they are not added together.
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
Request Input Output
|
|
136
|
+
1 4,200 800
|
|
137
|
+
2 9,100 1,200
|
|
138
|
+
3 17,300 1,100
|
|
139
|
+
4 29,400 2,000
|
|
140
|
+
TOTAL 60,000 5,100
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The final submitted prompt had 29,400 tokens, but the agent consumed **60,000 input tokens** across requests. `prompt_tokens` includes cached input; adding cached tokens again would double-count it. Likewise, reasoning tokens are a subset of completion tokens and must not be added again.
|
|
144
|
+
|
|
145
|
+
`tokens.cumulativeInputTokens` and `tokens.cumulativeOutputTokens` sum every physical request with complete reported usage, including internal summarization/compaction. If any request lacks the required usage fields, the corresponding total is `null`. `observedInputTokens`, `observedOutputTokens`, `requestsWithUsage`, and `usageComplete` preserve the known partial measurement. Failed requests, disconnected streams, and timeouts are not assumed free. Unreported cached/reasoning fields remain `null`, not zero.
|
|
146
|
+
|
|
147
|
+
### Amplification and context size
|
|
148
|
+
|
|
149
|
+
`lastRequestInputTokens` is the provider-reported input size of the last **agent** request, excluding compaction requests. It is not the final conversation size after the model's last output. Exact final context size is unavailable without another provider measurement or local estimation, so `finalContextSizeTokens` and the requested final-context-based `inputAmplification` are explicitly `null`.
|
|
150
|
+
|
|
151
|
+
The separately named descriptive ratio is:
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
inputAmplificationVsLastRequest = cumulativeInputTokens / lastRequestInputTokens
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
It describes total input sent relative to the last submitted agent prompt, only when the numerator and denominator are known and the denominator is positive. For the example: `60,000 / 29,400 ≈ 2.04×`. Compaction, caching, model tokenizers, and retries affect this ratio; it is not a measure of wasted tokens or a score.
|
|
158
|
+
|
|
159
|
+
## Metric boundaries
|
|
160
|
+
|
|
161
|
+
| Metric | Measurement |
|
|
162
|
+
| --- | --- |
|
|
163
|
+
| `success` | All configured validation passes, with no timeout/cancellation or unrecovered agent/API error |
|
|
164
|
+
| `modelRequests` | Observed physical Chat Completions attempts, including retries |
|
|
165
|
+
| `agentTurns` | Pi `turn_start` events; can differ from request count |
|
|
166
|
+
| `toolCalls` | Tool calls declared in completed assistant messages |
|
|
167
|
+
| `toolErrors` | Tool results marked `isError`; a recovered tool error does not by itself fail a run |
|
|
168
|
+
| Individual tools | Name/ID, execution start/end, error flag and redacted failure details; no arguments or successful outputs |
|
|
169
|
+
| `wallTimeMs` | From fresh-run setup through Pi, validation and archival; excludes initial discovery/snapshot and final report persistence |
|
|
170
|
+
| `agentWallTimeMs` | Worker spawn through shutdown, including Pi initialization and cancellation cleanup |
|
|
171
|
+
| `modelRequestWallTimeMs` | Union of completed client-observed request intervals; null when an observed request has no endpoint |
|
|
172
|
+
| `toolExecutionTimeMs` | Union of completed tool-execution intervals; null when an execution is still open |
|
|
173
|
+
| `timeToFirstContentMs` | First observed text/reasoning/tool delta relative to agent start; a client-visible latency, not exact first-token timing |
|
|
174
|
+
| Per-request timing | Monotonic worker-relative start, first content, and completion; differences provide request latencies |
|
|
175
|
+
| `modelGenerationTimeMs` | Null: server generation cannot be separated from queuing, networking, and stream consumption |
|
|
176
|
+
| `agentOverheadTimeMs` | Null: subtracting potentially overlapping timings would not isolate pure agent overhead |
|
|
177
|
+
|
|
178
|
+
Instrumentation itself has overhead, including IPC and trace writes. The HTTP observer is backpressure-aware and byte-preserving, but its timing is not server telemetry. Interrupted tool executions may lack complete duration/error information. Counts always describe observed events, not unseen activity before an unresponsive worker was killed.
|
|
179
|
+
|
|
180
|
+
Failure categories include `validation_failed`, `timeout`, `model_api_error`, `rate_limit`, `tool_error`, `agent_error`, `context_limit`, `cancelled`, and `unknown`. Timeout/cancellation take priority. `tool_error` means failed validation accompanied by tool errors; it does not prove the errors caused the failure. Original redacted details and individual HTTP statuses remain available. Recovered attempt failures remain in the trace even when the task ultimately succeeds.
|
|
181
|
+
|
|
182
|
+
## Results and reproducibility
|
|
183
|
+
|
|
184
|
+
```text
|
|
185
|
+
OUTPUT/
|
|
186
|
+
results.json # versioned experiment + all runs + aggregates
|
|
187
|
+
snapshot/fixture/ # frozen inputs
|
|
188
|
+
snapshot/validation/ # frozen trusted checks
|
|
189
|
+
runs/001-MODEL_HASH/
|
|
190
|
+
run.json # individual result, including request/tool arrays
|
|
191
|
+
trace.jsonl # incremental observation snapshots; survives worker failure
|
|
192
|
+
workspace/ # archived agent files
|
|
193
|
+
validation/ # exact checks executed
|
|
194
|
+
pi/ # isolated Pi config, without credentials or saved conversation
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`schemaVersion: 2` applies to result documents and journal entries. Each run includes:
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"schemaVersion": 2,
|
|
202
|
+
"benchmark": "fix-auth-bug",
|
|
203
|
+
"model": "vendor/model",
|
|
204
|
+
"modelRevision": null,
|
|
205
|
+
"run": 1,
|
|
206
|
+
"success": true,
|
|
207
|
+
"failure": null,
|
|
208
|
+
"modelRequests": 2,
|
|
209
|
+
"agentTurns": 2,
|
|
210
|
+
"toolCalls": 1,
|
|
211
|
+
"toolErrors": 0,
|
|
212
|
+
"tokens": {
|
|
213
|
+
"cumulativeInputTokens": 320,
|
|
214
|
+
"cumulativeOutputTokens": 60,
|
|
215
|
+
"lastRequestInputTokens": 220,
|
|
216
|
+
"finalContextSizeTokens": null
|
|
217
|
+
},
|
|
218
|
+
"observation": {
|
|
219
|
+
"requests": [
|
|
220
|
+
{"request": 1, "usage": {"inputTokens": 100, "outputTokens": 40}},
|
|
221
|
+
{"request": 2, "usage": {"inputTokens": 220, "outputTokens": 20}}
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
This is an abridged illustrative schema, not a live model result. The complete TypeScript contract is [types.ts](../src/benchmark/types.ts).
|
|
228
|
+
|
|
229
|
+
Metadata includes timestamp, Pi/package/Node versions, OS/architecture, benchmark definition/hash, fixture hash (including paths/content/executable bits), validator hash, frozen model definitions, effective Pi settings, system-prompt hashes. `.git` history is not required: the fixture content hash identifies the actual inputs. Response `model`, request ID, and `system_fingerprint` are captured when present. A fingerprint is **not** asserted to be a model revision; `modelRevision` remains null because Nebius's inspected catalog has no documented exact revision identifier.
|
|
230
|
+
|
|
231
|
+
The terminal report places metrics down rows and models across columns. It shows success counts, median task duration, mean input/output tokens, turns and tools, observed TTFT, end-to-end output throughput, and failure diagnostics. Observed TTFT is the first agent request’s first content timestamp minus that request’s start timestamp, aggregated as the median across runs with known timing. It includes network latency but excludes worker startup; the first nonempty text, reasoning, or tool-function delta counts, while role-only and empty tool headers do not. Streaming chunks may contain multiple tokens, so this is a client-observed approximation of TTFT, not a server token-generation timestamp. Throughput is the sum of output tokens divided by the sum of complete task durations, including network, tools, and validation. It includes failed runs and is unknown if any run lacks output usage or a positive duration. This is not pure model decoding speed. Existing JSON request traces contain the timestamps needed to calculate observed TTFT. JSON also contains means, medians, min/max, population standard deviations, and known/missing counts. Failed runs remain in aggregates; unknown values are excluded from arithmetic, never converted to zero. Every individual result remains available. `results.json` is atomically updated after each completed run and on handled cancellation. An abrupt kill of the runner itself may leave status `running`; journals preserve observations already received, but resumable execution is not implemented.
|
|
232
|
+
|
|
233
|
+
Traces omit prompts, model text, tool arguments, and successful tool output. Error details and validator logs are retained with the known API key redacted. This cannot identify arbitrary secrets embedded in your own fixtures/logs; use credential-free fixtures. Workspace archives intentionally contain the task's output files.
|
|
234
|
+
|
|
235
|
+
## Verification and live acceptance
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
npm ci
|
|
239
|
+
npm run typecheck
|
|
240
|
+
npm run lint
|
|
241
|
+
npm test
|
|
242
|
+
npm run benchmark:demo
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The demo runs real Pi SDK sessions and file tools, with **scripted mock responses**. It exercises two mock models × two repetitions and leaves inspectable results in `benchmark-results/mock-demo-*`. It is not evidence about any Nebius-hosted model's performance.
|
|
246
|
+
|
|
247
|
+
The regression suite covers parsing, isolation, event instrumentation, fragmented SSE observation, cumulative usage, aggregation, validation, JSON round trips, API errors, graceful cancellation, hard timeout, CLI input handling, all four fixture validators, and real Pi tool loops. Provider-extension tests also remain in the suite.
|
|
248
|
+
|
|
249
|
+
No live benchmark has been run in this environment: `NEBIUS_API_KEY` was unavailable. To complete that check, choose two tool-capable IDs from your discovered catalog and run the first command above with `--runs 1`. Tests certify the measurement wiring, not the capabilities, stability, or billing behavior of every hosted model.
|
|
250
|
+
|
|
251
|
+
See [benchmark-research.md](benchmark-research.md) for inspected source APIs and Nebius methodology.
|
|
252
|
+
|
|
253
|
+
Result schema version 2 removes monetary fields from version 1 (per-run estimates, pricing snapshots, aggregate costs, and the pricing hash). Existing saved results are not rewritten. Task definitions still use schema version 1.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Local security and release review
|
|
2
|
+
|
|
3
|
+
Reviewed 2026-09-16 on macOS, Node 22.22.3, Pi 0.85.1. Scope: first-party provider,
|
|
4
|
+
discovery/cache, benchmark runner/worker, process execution, parsing, filesystem
|
|
5
|
+
isolation, credential handling, dependencies, packaging, and workflows. This targeted
|
|
6
|
+
engineering review is not an independent penetration test or audit certification.
|
|
7
|
+
|
|
8
|
+
## Findings addressed
|
|
9
|
+
|
|
10
|
+
| Finding | Resolution | Evidence |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| Different fixture/validator path strings could alias or nest | Compare resolved directories and reject overlap | Tests for relative aliases, symlink aliases and nesting |
|
|
13
|
+
| Output parent symlinks could bypass lexical containment checks | Resolve existing ancestors before creating directories | Test for fixture alias with nonexistent output descendants |
|
|
14
|
+
| Fixture root could itself be a symlink | Require a real directory at the copy/hash root | Root-link regression test |
|
|
15
|
+
| `.env` variants and common key files were not ignored | Ignore `.env.*`, `*.pem`, `*.key`, logs and OS metadata | Git-visible source inspection |
|
|
16
|
+
| No repository CI/release controls | SHA-pinned read-only CI, dependency updates, security scans, version consistency, release PRs and automated releases | Local workflow/version validation |
|
|
17
|
+
| Accidental npm publication was possible | `private: true`, enforced by version checker; no publish workflow | Package metadata check |
|
|
18
|
+
|
|
19
|
+
These path fixes protect against static aliases/configuration mistakes, not a malicious
|
|
20
|
+
process concurrently changing files or using Pi's shell tools outside the workspace.
|
|
21
|
+
|
|
22
|
+
## Executed verification
|
|
23
|
+
|
|
24
|
+
- `npm audit --json`: zero known vulnerabilities, including development dependencies.
|
|
25
|
+
- Gitleaks 8.30.1: no findings in Git-visible first-party files, including untracked files.
|
|
26
|
+
There are no commits, so history scanning was explicitly skipped. CI fetches full history.
|
|
27
|
+
- actionlint 1.7.12: workflow structure and expressions passed. Optional ShellCheck/Pyflakes
|
|
28
|
+
integration is disabled consistently; those separate tools were not run.
|
|
29
|
+
- Security-tool binaries were downloaded from official releases and SHA-256 verified.
|
|
30
|
+
- Type checking, Biome lint, build, and 35 tests passed; none skipped.
|
|
31
|
+
- A clean tarball install with Pi 0.85.1 peers passed compiled extension import and CLI checks.
|
|
32
|
+
- Manifest/lockfile/changelog agreement and matching/mismatched tag checks passed.
|
|
33
|
+
- New-file whitespace checks reported no errors.
|
|
34
|
+
|
|
35
|
+
## Remaining boundaries
|
|
36
|
+
|
|
37
|
+
Pi tools, setup commands and validators retain host access. Workspace copies are not an
|
|
38
|
+
OS sandbox; use a disposable environment for untrusted tasks. A same-user attacker can
|
|
39
|
+
escape copies or change trusted files. See [SECURITY.md](../SECURITY.md).
|
|
40
|
+
|
|
41
|
+
Known credentials are redacted from benchmark traces, but arbitrary secrets in fixtures,
|
|
42
|
+
errors, validation logs and archived files cannot be identified reliably. Normal interactive
|
|
43
|
+
Pi inherits its shell environment. Dependency install scripts execute during source installs.
|
|
44
|
+
|
|
45
|
+
Scanners only cover known patterns/advisories; clean output is not proof of security.
|
|
46
|
+
No live Nebius acceptance was possible without credentials. GitHub Actions have not run
|
|
47
|
+
on GitHub; the configured OS/Node matrix is not claimed as remotely verified. No repository,
|
|
48
|
+
commit, tag, release or npm publication was created. Branch/tag rules, push protection and
|
|
49
|
+
private reporting must be enabled after repository creation.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Validation record
|
|
2
|
+
|
|
3
|
+
Environment: macOS, Node.js **22.22.3**, published Pi / pi-ai **0.85.1**. Date: **2026-09-16**.
|
|
4
|
+
|
|
5
|
+
## Executed checks
|
|
6
|
+
|
|
7
|
+
- `npm install`: dependencies installed; npm audit reported zero vulnerabilities.
|
|
8
|
+
- `npm run typecheck`: passed against the published Pi types.
|
|
9
|
+
- `npm run lint`: passed (Biome recommended rules and formatting).
|
|
10
|
+
- `npm test`: **35 tests passed**, no skipped tests (19 provider tests and 16 benchmark tests). Requires permission to bind a local loopback socket.
|
|
11
|
+
- `npm pack --dry-run --json`: passed; package includes compiled CLI, extension source, four benchmark fixtures and validators, README, documentation, example configuration, and MIT license; excludes test transport, node_modules, and credentials.
|
|
12
|
+
- New-file diffs and package contents reviewed. This is a new standalone repository; no release was published.
|
|
13
|
+
|
|
14
|
+
## What the tests establish
|
|
15
|
+
|
|
16
|
+
The production provider delegates to Pi's actual adapter. Mocked HTTP adapter tests cover streaming text, system messages, temperature, maximum output tokens, usage totals, reasoning effort/content/replay, finish reasons, HTTP 401/404/429/500/503 errors, retry-header preservation, and AbortSignal propagation.
|
|
17
|
+
|
|
18
|
+
Discovery tests cover basic/rich model lists, mapping, duplicate and non-text model filtering, invalid JSON/schema, missing credentials, authenticated requests, cache TTL and permissions, key isolation, forced/empty refresh, stale fallback, 401/403 invalidation, corrupt/unwritable caches, bounded responses, and cancellation.
|
|
19
|
+
|
|
20
|
+
The **real bundled Pi CLI** is run in an isolated temporary directory with this package loaded by `-e`. A test-only transport routes its requests to a local HTTP server; no genuine API key is used. The test verifies:
|
|
21
|
+
|
|
22
|
+
1. The async extension loads discovered models before `--list-models`.
|
|
23
|
+
2. A subsequent Pi invocation selects the discovered model and reuses its cache.
|
|
24
|
+
3. The HTTP response streams a fragmented `write` function call.
|
|
25
|
+
4. Pi actually writes `nebius-test.txt` with `Hello from Nebius`.
|
|
26
|
+
5. Pi sends an assistant tool call and matching tool-result message in the next HTTP request.
|
|
27
|
+
6. The model's mocked final response completes the turn.
|
|
28
|
+
7. A disappeared model yields actionable diagnostics while preserving the server error and retry information.
|
|
29
|
+
8. `/nebius-refresh` bypasses a fresh cache.
|
|
30
|
+
9. Standard `models.json` can add offline models alongside the native catalog.
|
|
31
|
+
10. Missing credentials produce setup guidance without crashing Pi or requesting discovery.
|
|
32
|
+
|
|
33
|
+
These establish provider integration and agent/tool transport. They do **not** establish a live model's willingness or ability to call tools.
|
|
34
|
+
|
|
35
|
+
## Benchmark verification
|
|
36
|
+
|
|
37
|
+
The final type check, Biome lint, build, and complete 35-test regression suite passed. Benchmark tests cover definition parsing, copy isolation, event instrumentation, byte-preserving streaming observation, cumulative tokens, repeated-run aggregation, validation, failure classification, JSON serialization, cancellation, and hard worker timeouts. All four original fixtures fail their trusted checks; all four reference solutions pass.
|
|
38
|
+
|
|
39
|
+
`npm run benchmark:demo` passed with **two mock models × two runs** through real Pi SDK sessions. All four runs passed deterministic validation and shared one system-prompt hash. Each recorded two HTTP requests, two turns, one executed write tool, 320 cumulative input tokens, 60 output tokens, 50 cached input tokens, and seven reasoning tokens. These are scripted measurements, not hosted-model performance.
|
|
40
|
+
|
|
41
|
+
The retained local result is `benchmark-results/mock-demo-1789550582003/results.json`, with individual runs, traces, and archived workspaces alongside it. Result files are excluded from the distributable package. The demo initially encountered a sandbox IPC permission error and passed after running with the required permission; package inspection similarly required access to npm's cache.
|
|
42
|
+
|
|
43
|
+
Source changes and package contents were reviewed. v1 supports sequential runs only to preserve Pi's unmodified absolute-path system prompt; exact final context size, server generation time, pure agent overhead, and exact model revisions remain unavailable. See [benchmarking.md](benchmarking.md) for measurement boundaries and live acceptance instructions.
|
|
44
|
+
|
|
45
|
+
## Not executed
|
|
46
|
+
|
|
47
|
+
- **Authenticated Nebius discovery and live inference**: no `NEBIUS_API_KEY` was available. The unauthenticated model endpoint returned the expected credentials error; that is not a live inference test.
|
|
48
|
+
- **Interactive TUI visual testing**: normal model availability was exercised through Pi's real CLI/registry, not a screenshot of the terminal picker.
|
|
49
|
+
- **Every hosted model**, reasoning template, or vision input: requires model-specific live validation. No such claim is made.
|
|
50
|
+
|
|
51
|
+
To complete the live acceptance check, set `NEBIUS_API_KEY` and `NEBIUS_MODEL` locally and run `npm run test:live` from the source checkout. That script uses the genuine Pi CLI against Token Factory, validates a successful write-tool event, a final model response, and exact file contents, then removes its scratch directory. It may incur inference charges.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"providers": {
|
|
3
|
+
"nebius": {
|
|
4
|
+
"models": [
|
|
5
|
+
{
|
|
6
|
+
"id": "REPLACE_WITH_EXACT_NEBIUS_MODEL_ID",
|
|
7
|
+
"name": "My verified Nebius model",
|
|
8
|
+
"reasoning": false,
|
|
9
|
+
"input": ["text"],
|
|
10
|
+
"contextWindow": 32768,
|
|
11
|
+
"maxTokens": 4096,
|
|
12
|
+
"compat": {
|
|
13
|
+
"supportsStore": false,
|
|
14
|
+
"supportsDeveloperRole": false,
|
|
15
|
+
"supportsStrictMode": false,
|
|
16
|
+
"supportsOpenAIGrammarTools": false,
|
|
17
|
+
"supportsReasoningEffort": false,
|
|
18
|
+
"supportsUsageInStreaming": true,
|
|
19
|
+
"maxTokensField": "max_tokens"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"modelOverrides": {
|
|
24
|
+
"REPLACE_WITH_EXACT_NEBIUS_MODEL_ID": {
|
|
25
|
+
"contextWindow": 32768,
|
|
26
|
+
"maxTokens": 4096
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-nebius",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Nebius Token Factory provider and agentic coding benchmarks for Pi",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/PeterHdd/pi-nebius.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/PeterHdd/pi-nebius#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/PeterHdd/pi-nebius/issues"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"pi-nebius": "dist/benchmark/cli.js"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"keywords": [
|
|
19
|
+
"pi-package",
|
|
20
|
+
"nebius",
|
|
21
|
+
"token-factory"
|
|
22
|
+
],
|
|
23
|
+
"files": [
|
|
24
|
+
"src",
|
|
25
|
+
"dist",
|
|
26
|
+
"benchmarks",
|
|
27
|
+
"examples",
|
|
28
|
+
"docs",
|
|
29
|
+
"README.md",
|
|
30
|
+
"CHANGELOG.md",
|
|
31
|
+
"CONTRIBUTING.md",
|
|
32
|
+
"SECURITY.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"pi": {
|
|
36
|
+
"extensions": [
|
|
37
|
+
"./src/index.ts"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=22.19.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@earendil-works/pi-ai": "*",
|
|
45
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@earendil-works/pi-ai": "0.85.1",
|
|
49
|
+
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
50
|
+
"@types/node": "^22.0.0",
|
|
51
|
+
"@biomejs/biome": "^2.2.0",
|
|
52
|
+
"tsx": "^4.20.0",
|
|
53
|
+
"typescript": "^5.9.0"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"yaml": "^2.8.1"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsc -p tsconfig.build.json",
|
|
60
|
+
"version:check": "node scripts/check-version.mjs",
|
|
61
|
+
"security:check": "node scripts/security-check.mjs",
|
|
62
|
+
"package:check": "npm run install:check && npm run build && node scripts/check-package.mjs",
|
|
63
|
+
"install:check": "node scripts/check-install.mjs",
|
|
64
|
+
"prepack": "npm run build",
|
|
65
|
+
"pretest": "npm run build",
|
|
66
|
+
"typecheck": "tsc --noEmit",
|
|
67
|
+
"lint": "biome check .",
|
|
68
|
+
"test": "node --import tsx --test tests/*.test.ts tests/benchmark/*.test.ts",
|
|
69
|
+
"benchmark": "node dist/benchmark/cli.js benchmark",
|
|
70
|
+
"benchmark:demo": "npm run build && tsx tests/benchmark/demo.ts",
|
|
71
|
+
"test:live": "node --import tsx tests/live.ts",
|
|
72
|
+
"check": "npm run typecheck && npm run lint && npm test"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
import { parseArgs } from "node:util";
|
|
7
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { discoverModels, MISSING_KEY } from "../discovery.ts";
|
|
9
|
+
import { applyModelSettings, loadSettings, settingsPath } from "../model-settings.ts";
|
|
10
|
+
import { loadDefinition, positive } from "./definition.ts";
|
|
11
|
+
import { redactor } from "./instrumentation.ts";
|
|
12
|
+
import { terminalReport } from "./report.ts";
|
|
13
|
+
import { runBenchmark } from "./runner.ts";
|
|
14
|
+
|
|
15
|
+
const HELP = `Usage: pi-nebius benchmark --benchmark DIRECTORY --models ID,ID [options]
|
|
16
|
+
|
|
17
|
+
--benchmark PATH Directory with benchmark.yaml, or YAML/JSON definition file
|
|
18
|
+
--models IDS Comma-separated, exact Nebius model IDs (no automatic selection)
|
|
19
|
+
--runs N Repetitions per model (default: 1)
|
|
20
|
+
--timeout SECONDS Per-agent deadline, including worker/session startup
|
|
21
|
+
--output PATH New result directory (must not already exist)
|
|
22
|
+
--concurrency N v1 supports 1 only: identical Pi prompts without cwd rewriting
|
|
23
|
+
--help Show this help
|
|
24
|
+
|
|
25
|
+
Each run receives a fresh fixture copy and an isolated Pi configuration.
|
|
26
|
+
Pi tools have normal host access: filesystem copies are not a security sandbox.
|
|
27
|
+
Use a disposable machine/container for untrusted fixtures or model-generated shell commands.
|
|
28
|
+
No API key or prompt/response/tool content is retained in request traces.
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
async function main() {
|
|
32
|
+
const args = process.argv.slice(2);
|
|
33
|
+
if (args[0] === "--help" || args[0] === "-h" || args.length === 0) {
|
|
34
|
+
console.log(HELP);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (args.shift() !== "benchmark")
|
|
38
|
+
throw new Error("Expected the benchmark subcommand. Use --help.");
|
|
39
|
+
const { values } = parseArgs({
|
|
40
|
+
args,
|
|
41
|
+
strict: true,
|
|
42
|
+
options: {
|
|
43
|
+
benchmark: { type: "string" },
|
|
44
|
+
models: { type: "string" },
|
|
45
|
+
runs: { type: "string" },
|
|
46
|
+
timeout: { type: "string" },
|
|
47
|
+
output: { type: "string" },
|
|
48
|
+
concurrency: { type: "string" },
|
|
49
|
+
help: { type: "boolean", short: "h" },
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
if (values.help) {
|
|
53
|
+
console.log(HELP);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!values.benchmark || !values.models) throw new Error("--benchmark and --models are required");
|
|
57
|
+
const runs = positive(Number(values.runs ?? 1), "runs", 1000);
|
|
58
|
+
const concurrency = positive(Number(values.concurrency ?? 1), "concurrency", 1);
|
|
59
|
+
const ids = values.models.split(",").map((id) => id.trim());
|
|
60
|
+
if (ids.some((id) => !id) || new Set(ids).size !== ids.length)
|
|
61
|
+
throw new Error("--models must contain nonempty, unique exact IDs");
|
|
62
|
+
if (runs * ids.length > 10000) throw new Error("At most 10,000 runs per invocation");
|
|
63
|
+
const { definition, directory } = await loadDefinition(values.benchmark);
|
|
64
|
+
if (values.timeout) definition.timeout = positive(Number(values.timeout), "timeout");
|
|
65
|
+
const apiKey = process.env.NEBIUS_API_KEY?.trim();
|
|
66
|
+
if (!apiKey) throw new Error(MISSING_KEY);
|
|
67
|
+
const cache = await mkdtemp(join(tmpdir(), "pi-nebius-benchmark-discovery-"));
|
|
68
|
+
const discovered = await discoverModels({ apiKey, agentDir: cache, force: true }).finally(() =>
|
|
69
|
+
rm(cache, { recursive: true, force: true }),
|
|
70
|
+
);
|
|
71
|
+
if (discovered.warning) process.stderr.write(`${discovered.warning}\n`);
|
|
72
|
+
const modelSettings = await loadSettings(settingsPath(getAgentDir()));
|
|
73
|
+
const models = ids.map((id) => {
|
|
74
|
+
const model = discovered.models.find((candidate) => candidate.id === id);
|
|
75
|
+
if (!model)
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Requested model was not discovered: ${id}. Check its exact ID and your access.`,
|
|
78
|
+
);
|
|
79
|
+
return applyModelSettings(model, modelSettings[id]);
|
|
80
|
+
});
|
|
81
|
+
const output = resolve(
|
|
82
|
+
values.output ??
|
|
83
|
+
join(
|
|
84
|
+
"benchmark-results",
|
|
85
|
+
`${new Date().toISOString().replaceAll(":", "-")}-${randomUUID().slice(0, 8)}`,
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
const controller = new AbortController();
|
|
89
|
+
const abort = () => controller.abort();
|
|
90
|
+
process.once("SIGINT", abort);
|
|
91
|
+
process.once("SIGTERM", abort);
|
|
92
|
+
try {
|
|
93
|
+
const results = await runBenchmark({
|
|
94
|
+
definition,
|
|
95
|
+
directory,
|
|
96
|
+
models,
|
|
97
|
+
modelSettings,
|
|
98
|
+
runs,
|
|
99
|
+
concurrency,
|
|
100
|
+
output,
|
|
101
|
+
apiKey,
|
|
102
|
+
signal: controller.signal,
|
|
103
|
+
onRun: (run) =>
|
|
104
|
+
process.stderr.write(`${run.model} #${run.run}: ${run.success ? "PASS" : run.failure}\n`),
|
|
105
|
+
});
|
|
106
|
+
console.log(terminalReport(results));
|
|
107
|
+
console.log(`Results: ${join(output, "results.json")}`);
|
|
108
|
+
process.exitCode =
|
|
109
|
+
results.status === "cancelled" ? 130 : results.runs.every((run) => run.success) ? 0 : 1;
|
|
110
|
+
} finally {
|
|
111
|
+
process.removeListener("SIGINT", abort);
|
|
112
|
+
process.removeListener("SIGTERM", abort);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
void main().catch((error) => {
|
|
116
|
+
console.error(redactor([process.env.NEBIUS_API_KEY ?? ""])(String(error)));
|
|
117
|
+
process.exitCode = 2;
|
|
118
|
+
});
|