@vizuh/sabi 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,24 @@
1
- # @vizuh/sabi: Command Code adapter
2
-
3
- This npm artifact is one Sabi adapter, not the whole Sabi product. Sabi also supports a local
4
- OpenAI-compatible proxy for OpenCode, Hermes, Prime Agent, Kilo and other clients, plus an
5
- experimental controller surface for Claude Code, Codex and Orca. See the
6
- [adapter directory](../../../docs/adapters/README.md) for the product map and evidence boundaries.
1
+ # @vizuh/sabi: Sabi inference adapter (Command Code mod)
2
+
3
+ Sabi is harness-independent adaptive inference scheduling for coding-agent trajectories:
4
+ per-round routing of model, effort and provider from trajectory evidence, while each host
5
+ harness keeps its own loop. Sabi promotes no single harness; every host below is a
6
+ peer surface with its own install path.
7
+
8
+ This npm artifact is the Command Code mod — one Sabi surface, not the whole product.
9
+ See the [adapter directory](../../../docs/adapters/README.md) for the product map and
10
+ evidence boundaries.
11
+
12
+ | Harness | Sabi surface | Install |
13
+ | --- | --- | --- |
14
+ | Claude Code | Controller hooks (task/session, fail-open) | `npm install --global @vizuh/sabi-controller` then `sabi setup` |
15
+ | Codex | Controller hooks (task/session, fail-open) | `npm install --global @vizuh/sabi-controller` then `sabi setup` |
16
+ | Command Code | This package: in-process mod, per-round model + reasoning effort | `cmd mods add -g npm:@vizuh/sabi` |
17
+ | Hermes | Local proxy + native `llm_request` middleware | Checkout-based setup in [the install guide](../../../docs/install.md) |
18
+ | Oh My Pi | Local proxy via OpenAI-compatible extension provider | Checkout: `omp --extension packages/adapters/oh-my-pi/src/sabi-extension.mjs --model sabi/sabi-code` |
19
+ | OpenCode | Local proxy, plus optional controller hook | Checkout-based proxy (`npm start` + `npm run connect:opencode`); hooks via `@vizuh/sabi-controller` |
20
+ | Orca | Controller plugin + inventory/dispatch bridge | `npm install --global @vizuh/sabi-controller` then `sabi setup` |
21
+ | DeepSeek Harness, Kilo, Cline, Prime Agent | Proxy or bundle paths | See the [adapter directory](../../../docs/adapters/README.md) |
7
22
 
8
23
  Adaptive inference scheduling for [Command Code](https://commandcode.ai): a mod that plans each
9
24
  continuing round, including model and reasoning effort, from the trajectory's own state (tool calls and
@@ -17,7 +32,7 @@ The two paths are independent:
17
32
 
18
33
  - The Command Code mod needs no Sabi provider key or proxy. It routes the subscription already
19
34
  available to Command Code.
20
- - The local proxy works with OpenCode, Hermes, Kilo, and other OpenAI-compatible clients. It uses
35
+ - The local proxy works with Hermes, Oh My Pi, OpenCode, and other OpenAI-compatible clients (including Cline, Kilo and Prime Agent paths). It uses
21
36
  OpenRouter, Ollama, or another configured upstream. In the shipped default the OpenRouter
22
37
  upstream is **free-models-only** (`paidModelsAllowed: false`): a priced model id is refused
23
38
  before the request leaves the process, so the proxy cannot spend on its own.
package/mod/sabi.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // @vizuh/sabi 0.2.0 — generated by pack.mjs from packages/adapters/command-code/mod/sabi.ts.
1
+ // @vizuh/sabi 0.2.1 — generated by pack.mjs from packages/adapters/command-code/mod/sabi.ts.
2
2
  // Source and docs: https://github.com/vizuh/sabi
3
3
 
4
4
  // packages/core/src/telemetry.ts
@@ -323,7 +323,11 @@ var TRANSPORT_LIMIT_PATTERNS = [
323
323
  { re: /too many requests/i, label: "rate-limited" },
324
324
  { re: /\b(?:session|usage|weekly|monthly|daily|hourly|subscription|plan)\s+limit\b/i, label: "quota-exceeded" },
325
325
  { re: /quota[- ]?exceeded/i, label: "quota-exceeded" },
326
- { re: /insufficient_quota|insufficient quota/i, label: "quota-exceeded" }
326
+ { re: /insufficient_quota|insufficient quota/i, label: "quota-exceeded" },
327
+ // A dropped connection is the transport failing. Without this, the wording a client prints after
328
+ // a transport deadline reads as a task failure and the next round escalates straight back to the
329
+ // tier that just timed out — ten consecutive 120s rounds in one real session.
330
+ { re: /socket (?:connection )?(?:was )?closed|socket hang up|ECONNRESET|other side closed|connection reset/i, label: "connection-closed" }
327
331
  ];
328
332
  var TRANSPORT_PATTERNS = [
329
333
  { re: /\b429\b/, label: "rate-limited" },
@@ -863,6 +867,12 @@ function validateConfig(value, source = "<inline>") {
863
867
  if (upstream.paidModelsAllowed !== void 0 && typeof upstream.paidModelsAllowed !== "boolean") {
864
868
  throw new Error(`Sabi config ${source}: upstream '${name}'.paidModelsAllowed must be a boolean`);
865
869
  }
870
+ if (upstream.auth !== void 0 && upstream.auth !== "passthrough") {
871
+ throw new Error(`Sabi config ${source}: upstream '${name}'.auth must be 'passthrough'`);
872
+ }
873
+ if (upstream.auth === "passthrough" && upstream.apiKey !== void 0 && upstream.apiKey !== false) {
874
+ throw new Error(`Sabi config ${source}: upstream '${name}' is auth:passthrough and must not declare apiKey`);
875
+ }
866
876
  }
867
877
  if (!Object.keys(models).length) throw new Error(`Sabi config ${source}: no models declared`);
868
878
  for (const [name, model] of Object.entries(models)) {
@@ -904,6 +914,21 @@ function validateConfig(value, source = "<inline>") {
904
914
  throw new Error(`Sabi config ${source}: transportFallback.enabled must be a boolean`);
905
915
  }
906
916
  }
917
+ const passthrough = config.passthrough;
918
+ if (passthrough !== void 0) {
919
+ if (!isObject(passthrough)) throw new Error(`Sabi config ${source}: passthrough must be an object`);
920
+ for (const field of Object.keys(passthrough)) {
921
+ if (field !== "alias") throw new Error(`Sabi config ${source}: passthrough.${field} is not a supported field`);
922
+ }
923
+ if (passthrough.alias !== void 0) {
924
+ if (typeof passthrough.alias !== "string" || !passthrough.alias.trim()) {
925
+ throw new Error(`Sabi config ${source}: passthrough.alias must be a nonempty string`);
926
+ }
927
+ if (aliases[passthrough.alias] !== "auto") {
928
+ throw new Error(`Sabi config ${source}: passthrough.alias '${passthrough.alias}' must name an alias targeting 'auto'`);
929
+ }
930
+ }
931
+ }
907
932
  const judge = config.judge;
908
933
  if (judge !== void 0) {
909
934
  if (typeof judge !== "object" || judge === null || typeof judge.enabled !== "boolean") {
@@ -1210,6 +1235,9 @@ function sanitizeDecisionRecord(record, config) {
1210
1235
  state,
1211
1236
  cache: record.cache,
1212
1237
  judge: record.judge,
1238
+ // Effort labels are short provider-declared strings, never transcript text; still bounded.
1239
+ ...typeof record.effort === "string" && record.effort.trim() ? { effort: record.effort.trim().slice(0, 64) } : {},
1240
+ ...record.effortSource === "client" || record.effortSource === "scheduled" || record.effortSource === "unspecified" ? { effortSource: record.effortSource } : {},
1213
1241
  usage: record.usage,
1214
1242
  cost: record.cost,
1215
1243
  latencyMs: record.latencyMs,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vizuh/sabi",
3
- "version": "0.2.0",
4
- "description": "Adaptive inference scheduling for Command Code: one bundled mod that routes each continuing round by model, effort and trajectory state.",
3
+ "version": "0.2.1",
4
+ "description": "Sabi inference adapter for Command Code, part of harness-independent adaptive routing for Claude Code, Codex, Command Code, Hermes, Oh My Pi, OpenCode and Orca.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -11,13 +11,20 @@
11
11
  },
12
12
  "homepage": "https://github.com/vizuh/sabi#readme",
13
13
  "keywords": [
14
- "commandcode",
14
+ "sabi",
15
+ "ai-agents",
16
+ "claude-code",
17
+ "codex",
15
18
  "command-code",
19
+ "commandcode",
20
+ "hermes",
21
+ "inference-scheduling",
22
+ "llm-routing",
16
23
  "mod",
17
- "llm",
18
- "routing",
19
- "ai-agents",
20
- "inference-scheduling"
24
+ "oh-my-pi",
25
+ "opencode",
26
+ "openai-compatible",
27
+ "orca"
21
28
  ],
22
29
  "publishConfig": {
23
30
  "access": "public"
package/sabi.config.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "provenance": "Operator rule (2026-09-22): OpenRouter may serve FREE models only. The openrouter upstream declares paidModelsAllowed:false, so any model whose price is unknown or non-zero is refused at dispatch (packages/core/src/compatibility.ts) — a config mistake cannot spend money. Every openrouter model id below carries the :free variant and cost 0. Ids, context windows, output ceilings, modalities and zero pricing verified live from https://openrouter.ai/api/v1/models and a real completion per id on 2026-09-22. Jev (TypeSafe judge) is a separate upstream and unaffected by this rule.",
2
+ "provenance": "Operator rule (2026-09-22): OpenRouter may serve FREE models only. The openrouter upstream declares paidModelsAllowed:false, so any model whose price is unknown or non-zero is refused at dispatch (packages/core/src/compatibility.ts) — a config mistake cannot spend money. Every openrouter model id below carries the :free variant and cost 0. Ids, context windows, output ceilings, modalities and zero pricing verified live from https://openrouter.ai/api/v1/models and a real completion per id on 2026-09-22. Jev (TypeSafe judge) is a separate upstream and unaffected by this rule. Operator rule (2026-09-23): Claude Code and Codex are used through their own subscriptions — Sabi installs hooks into them and never writes a provider base URL, an API key or a model override into either harness, so neither can become per-token spend through Sabi. Guarded by packages/controller/test/hooks.test.ts.",
3
3
  "server": { "host": "127.0.0.1", "port": 8787 },
4
4
  "upstreams": {
5
5
  "openrouter": {
@@ -47,6 +47,7 @@
47
47
  "upstream": "ollama",
48
48
  "model": "qwen2.5-coder:7b",
49
49
  "contextWindow": 32768,
50
+ "maxOutputTokens": 32768,
50
51
  "capabilities": { "inputModalities": ["text"] },
51
52
  "cost": { "input": 0, "output": 0 }
52
53
  }