bare-agent 0.38.1 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bareagent.context.md +6 -5
- package/examples/README.md +1 -1
- package/index.d.ts +54 -0
- package/index.js +15 -0
- package/package.json +1 -1
- package/src/provider-anthropic.js +15 -6
- package/src/provider-clipipe-tools.d.ts +3 -5
- package/src/provider-clipipe-tools.js +19 -11
- package/src/provider-clipipe.d.ts +3 -2
- package/src/provider-clipipe.js +30 -9
- package/src/provider-gemini.d.ts +2 -2
- package/src/provider-gemini.js +8 -1
- package/src/provider-ollama.js +9 -4
- package/src/provider-openai.d.ts +2 -2
- package/src/provider-openai.js +8 -1
- package/src/provider-usage.d.ts +24 -0
- package/src/provider-usage.js +34 -0
- package/src/recurse-retrieval.d.ts +1 -1
- package/src/recurse-retrieval.js +2 -2
- package/src/recurse.js +1 -1
- package/types/index.d.ts +3 -1
package/README.md
CHANGED
|
@@ -135,7 +135,7 @@ console.log(result.count, result.matchedIds); // a code-derived count + the id
|
|
|
135
135
|
|
|
136
136
|
**Deps:** none required — the core imports nothing. Optional peers: `bareguard ^0.9.0` (governance), `better-sqlite3` (SQLite store); optional: `cron-parser`, `barebrowse`, `baremobile`, `wearehere`.
|
|
137
137
|
|
|
138
|
-
This table is the map, not the manual — per-component wiring and API detail live in the [Integration Guide](bareagent.context.md) and [Usage Guide](docs/
|
|
138
|
+
This table is the map, not the manual — per-component wiring and API detail live in the [Integration Guide](bareagent.context.md) and [Usage Guide](docs/archive/usage-guide.md).
|
|
139
139
|
|
|
140
140
|
---
|
|
141
141
|
|
|
@@ -281,7 +281,7 @@ All wrappers support optional event streaming for intermediate results. See [`co
|
|
|
281
281
|
|
|
282
282
|
Aurora replaced ~400 lines of hand-rolled orchestration with ~60 lines of bare-agent wiring — zero workarounds, zero framework plumbing, 100% domain logic.
|
|
283
283
|
|
|
284
|
-
For wiring recipes and API details, see the **[Integration Guide](bareagent.context.md)** (LLM-optimized). For the full human guide — usage patterns, composition examples, and what bare-agent deliberately doesn't build in (with recipes to do it yourself), see the **[Usage Guide](docs/
|
|
284
|
+
For wiring recipes and API details, see the **[Integration Guide](bareagent.context.md)** (LLM-optimized). For the full human guide — usage patterns, composition examples, and what bare-agent deliberately doesn't build in (with recipes to do it yourself), see the **[Usage Guide](docs/archive/usage-guide.md)**. For error reference, see **[Error Guide](docs/product/errors.md)**. For release history, see **[CHANGELOG](CHANGELOG.md)**.
|
|
285
285
|
|
|
286
286
|
## The bare ecosystem
|
|
287
287
|
|
package/bareagent.context.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# bareagent — Integration Guide
|
|
2
2
|
|
|
3
3
|
> For AI assistants and developers wiring bareagent into a project.
|
|
4
|
-
> v0.
|
|
4
|
+
> v0.40.0 | Node.js >= 18 | zero required deps (`bareguard >=0.9.0 <0.14.0` optional peer for governance) | Apache 2.0
|
|
5
5
|
>
|
|
6
|
-
> Full human guide with composition examples, design philosophy, and recipes: [Usage Guide](docs/
|
|
6
|
+
> Full human guide with composition examples, design philosophy, and recipes: [Usage Guide](docs/archive/usage-guide.md)
|
|
7
7
|
|
|
8
8
|
## What this is
|
|
9
9
|
|
|
@@ -891,7 +891,7 @@ All return `{ text, toolCalls, usage: { inputTokens, outputTokens }, model?, cos
|
|
|
891
891
|
|
|
892
892
|
**Plaintext-key warning (Unreleased):** the OpenAI provider's `baseUrl` accepts `http://` (for local/OpenAI-compatible endpoints), but a `Bearer` key sent over plaintext http to a **non-loopback** host is exposed on the wire. The provider now warns once when that happens. Loopback hosts (`localhost`/`127.0.0.0/8`/`::1` — local proxies, Ollama-style endpoints) stay silent, since that's the legitimate keyless-local case. The header is **not** stripped (some local proxies want a key), so use `https` for any remote endpoint, or drop `apiKey` when the local endpoint needs none.
|
|
893
893
|
|
|
894
|
-
**Cost estimation:** Loop automatically estimates USD cost per run based on model and token usage. The `cost` field appears in every `loop.run()` result and in `loop:done` stream events. **Pricing honesty (BA-21, v0.37.0):** a token-bearing round is ALWAYS priced (guesstimate-and-run — a null/unknown model no longer forces `unpriced`); rates resolve as caller-supplied `new Loop({ rates: { in, out } })` (authoritative) → a recognized Claude tier from the model id (`haiku`/`sonnet`) → the **Sonnet-tier** ceiling default ($0.003/$0.015 per 1K, no per-model table). Every metering payload carries a `rateSource: 'provider' | 'caller' | 'tier' | 'default' | null` field so a consumer can tell an authoritative price from a guess — and (BA-21 follow-up, v0.38.0) a recognized-tier guess (`'tier'`) from a blind ceiling fallback (`'default'`); a round priced off either guesstimate emits ONE loud `console.warn` per Loop instance naming the actual source (silenced by passing `rates`). To set your own rates, construct `new Loop({ rates })` — there is no longer a `COST_PER_1K` table to edit. The model is resolved as `result.model || provider.model` (v0.16.1+) — providers now echo the model in their `generate()` result, so cost accounting holds even when `provider.model` is absent or varies per response, e.g. behind `FallbackProvider` or `CircuitBreaker.wrapProvider` (the wrapper also preserves `model`/`name` passthrough props). Wire `onLlmResult` (via `wireGate`) and a `budget.maxCostUsd` cap then halts on token-heavy workloads too.
|
|
894
|
+
**Cost estimation:** Loop automatically estimates USD cost per run based on model and token usage. The `cost` field appears in every `loop.run()` result and in `loop:done` stream events. **Pricing honesty (BA-21, v0.37.0):** a token-bearing round is ALWAYS priced (guesstimate-and-run — a null/unknown model no longer forces `unpriced`); rates resolve as caller-supplied `new Loop({ rates: { in, out } })` (authoritative) → a recognized Claude tier from the model id (`haiku`/`sonnet`) → the **Sonnet-tier** ceiling default ($0.003/$0.015 per 1K, no per-model table). Every metering payload carries a `rateSource: 'provider' | 'caller' | 'tier' | 'default' | null` field so a consumer can tell an authoritative price from a guess — and (BA-21 follow-up, v0.38.0) a recognized-tier guess (`'tier'`) from a blind ceiling fallback (`'default'`); a round priced off either guesstimate emits ONE loud `console.warn` per Loop instance naming the actual source (silenced by passing `rates`). To set your own rates, construct `new Loop({ rates })` — there is no longer a `COST_PER_1K` table to edit. The model is resolved as `result.model || provider.model` (v0.16.1+) — providers now echo the model in their `generate()` result, so cost accounting holds even when `provider.model` is absent or varies per response, e.g. behind `FallbackProvider` or `CircuitBreaker.wrapProvider` (the wrapper also preserves `model`/`name` passthrough props). Wire `onLlmResult` (via `wireGate`) and a `budget.maxCostUsd` cap then halts on token-heavy workloads too. **Usage-null boundary (BA-24, v0.39.0):** `GenerateResult.usage` is `Usage | null` — every provider (incl. native CLIPipe session-close) now surfaces `usage: null` when the raw API response carried NO usage block, instead of manufacturing an all-zeros object. A manufactured zero object used to sail past `resolveRoundCost`'s honest-null guard and get priced as a confident $0 (`rateSource:'tier'`/`'default'`) — an unpriceable round now stays unpriced. A *present* block with an explicit `0` field (e.g. a cache-only round) is unaffected and still prices normally.
|
|
895
895
|
|
|
896
896
|
## Store options
|
|
897
897
|
|
|
@@ -991,7 +991,7 @@ These are deliberately NOT in bare-agent. Don't look for them — build them fro
|
|
|
991
991
|
| **Heartbeat (ambient awareness)** | "Check if anything needs attention" scope is your domain | Scheduler recurring job where the LLM triages: `scheduler.add({ type: 'recurring', schedule: '30m', action: 'Check if anything needs attention' })`. |
|
|
992
992
|
| **Cron** | **This IS built in** | Scheduler supports cron expressions (requires `cron-parser` peer dep) and relative schedules (`5s`, `30m`, `2h`, `1d`) natively. |
|
|
993
993
|
|
|
994
|
-
For full recipes with code examples, see `docs/
|
|
994
|
+
For full recipes with code examples, see `docs/archive/usage-guide.md` § "Patterns, Not Features".
|
|
995
995
|
|
|
996
996
|
## Production usage
|
|
997
997
|
|
|
@@ -1010,7 +1010,7 @@ For full recipes with code examples, see `docs/02-features/usage-guide.md` § "P
|
|
|
1010
1010
|
| Stream | — | — (deferred) |
|
|
1011
1011
|
| CLIPipe | ✓ | — |
|
|
1012
1012
|
|
|
1013
|
-
Both projects kept their own memory/store implementations. Neither needed multi-agent routing. Full multis eval: `docs/
|
|
1013
|
+
Both projects kept their own memory/store implementations. Neither needed multi-agent routing. Full multis eval: `docs/logs/bareagent-eval-multis.md`.
|
|
1014
1014
|
|
|
1015
1015
|
## Examples
|
|
1016
1016
|
|
|
@@ -1048,6 +1048,7 @@ Stale example removed in 0.10.4: `examples/mcp-bridge-gov.js` (used a hard-coded
|
|
|
1048
1048
|
17. **Halt-path `msgs` is sealed (v0.10.3+)** — when Loop catches `HaltError` mid-round, every dangling assistant `tool_calls.id` from the halted round gets a synthetic `{ role:'tool', tool_call_id, content: '[halted:<rule>]' }` appended so the returned `result.msgs` is valid OpenAI shape. Safe to feed back into another provider call without protocol errors. The `[halted:<rule>]` tag is lowercase — distinct from the legacy `[HALT:]` deny strings (removed in 0.10.0, do not match on the old form).
|
|
1049
1049
|
18. **`HaltError` with no `rule` resolves to `halt:unknown` (v0.10.3+)** — `new HaltError('msg')` without a `{ rule }` option still produces a stable `result.error = 'halt:unknown'` and `loop:done{halted:true, rule:'unknown'}`. Pre-0.10.3 produced the literal `'halt:null'` which broke string-matching consumers. The `_reportError('halt', ...)` extra carries the same `rule:'unknown'` token.
|
|
1050
1050
|
19. **Pricing is a two-tier guesstimate, not a per-model table (BA-21, v0.37.0)** — a recognized Claude tier in the model id prices at `haiku` ($0.001/$0.005) or `sonnet` ($0.003/$0.015) per 1K; anything else (incl. a null/unknown model) falls through to the **Sonnet-tier ceiling default** ($0.003/$0.015) and still gets priced (`rateSource: 'default'`), never silently `unpriced`. Both guesstimate cases emit one `console.warn` per Loop instance. The `rateSource` on every metering payload distinguishes them: `'tier'` (a recognized haiku/sonnet match — a confident guess) vs `'default'` (the blind ceiling for an unrecognized/absent model), alongside `'provider'`/`'caller'` (authoritative) and `null` (unpriced). If you use a model whose real rates differ and care about `result.cost` accuracy or `budget.maxCostUsd` enforcement via `onLlmResult`, pass `new Loop({ rates: { in, out, cacheReadMult?, cacheWriteMult? } })` — that's authoritative (`rateSource: 'caller'`) and silences the warning. There is no `COST_PER_1K` table to edit.
|
|
1051
|
+
20. **A `GenerateResult` with `usage: null` means the provider reported no usage block at all — not a round that used zero tokens (BA-24, v0.39.0)** — every http/CLI provider used to coalesce an absent usage block into a truthy all-zeros `Usage` object (`field || 0` applied unconditionally), which `resolveRoundCost` then priced as a confident $0 instead of treating it as unpriceable. `GenerateResult.usage` is now typed `Usage | null`; `null` fires only when the raw API response carried no usage signal at all (checked field-by-field via `hasUsageSignal`, `src/provider-usage.js`), never on a present block whose fields are legitimately `0` (a cache-only round still prices normally). If your own code reads `result.usage.inputTokens` directly, guard for `null` first.
|
|
1051
1052
|
|
|
1052
1053
|
## Cross-language SDKs
|
|
1053
1054
|
|
package/examples/README.md
CHANGED
|
@@ -12,4 +12,4 @@ Runnable reference scripts for bare-agent. Each is self-contained — the top-of
|
|
|
12
12
|
| [`replay-job.js`](replay-job.js) | Supervised replay POC: record a browser task once with the LLM driving, then replay against fresh snapshots with the LLM as locator-only. Falls back to full reasoning when the locator misses, and patches the trace. |
|
|
13
13
|
| [`litectx-as-store.mjs`](litectx-as-store.mjs) | RT-3 Store mount: swap the zero-dep `JsonFileStore` for litectx's ranked, graph-aware recall in one line — the host code never changes. Runs the JsonFileStore half always; runs the litectx half if `litectx` is installed, else prints the one-line swap. |
|
|
14
14
|
|
|
15
|
-
For wiring recipes and API details see the [Integration Guide](../bareagent.context.md); for usage patterns and design philosophy see the [Usage Guide](../docs/
|
|
15
|
+
For wiring recipes and API details see the [Integration Guide](../bareagent.context.md); for usage patterns and design philosophy see the [Usage Guide](../docs/archive/usage-guide.md).
|
package/index.d.ts
CHANGED
|
@@ -1,3 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export the shared cross-cutting type shapes so adopters can name them when
|
|
3
|
+
* annotating their own wiring (tools arrays, providers, ctx, metrics). Declared
|
|
4
|
+
* in `types/index.d.ts`; surfaced here on the root entry so `types` resolves them.
|
|
5
|
+
*/
|
|
6
|
+
export type ToolDef = import("./types").ToolDef;
|
|
7
|
+
/**
|
|
8
|
+
* Re-export the shared cross-cutting type shapes so adopters can name them when
|
|
9
|
+
* annotating their own wiring (tools arrays, providers, ctx, metrics). Declared
|
|
10
|
+
* in `types/index.d.ts`; surfaced here on the root entry so `types` resolves them.
|
|
11
|
+
*/
|
|
12
|
+
export type ToolCall = import("./types").ToolCall;
|
|
13
|
+
/**
|
|
14
|
+
* Re-export the shared cross-cutting type shapes so adopters can name them when
|
|
15
|
+
* annotating their own wiring (tools arrays, providers, ctx, metrics). Declared
|
|
16
|
+
* in `types/index.d.ts`; surfaced here on the root entry so `types` resolves them.
|
|
17
|
+
*/
|
|
18
|
+
export type Message = import("./types").Message;
|
|
19
|
+
/**
|
|
20
|
+
* Re-export the shared cross-cutting type shapes so adopters can name them when
|
|
21
|
+
* annotating their own wiring (tools arrays, providers, ctx, metrics). Declared
|
|
22
|
+
* in `types/index.d.ts`; surfaced here on the root entry so `types` resolves them.
|
|
23
|
+
*/
|
|
24
|
+
export type Provider = import("./types").Provider;
|
|
25
|
+
/**
|
|
26
|
+
* Re-export the shared cross-cutting type shapes so adopters can name them when
|
|
27
|
+
* annotating their own wiring (tools arrays, providers, ctx, metrics). Declared
|
|
28
|
+
* in `types/index.d.ts`; surfaced here on the root entry so `types` resolves them.
|
|
29
|
+
*/
|
|
30
|
+
export type GenerateResult = import("./types").GenerateResult;
|
|
31
|
+
/**
|
|
32
|
+
* Re-export the shared cross-cutting type shapes so adopters can name them when
|
|
33
|
+
* annotating their own wiring (tools arrays, providers, ctx, metrics). Declared
|
|
34
|
+
* in `types/index.d.ts`; surfaced here on the root entry so `types` resolves them.
|
|
35
|
+
*/
|
|
36
|
+
export type Usage = import("./types").Usage;
|
|
37
|
+
/**
|
|
38
|
+
* Re-export the shared cross-cutting type shapes so adopters can name them when
|
|
39
|
+
* annotating their own wiring (tools arrays, providers, ctx, metrics). Declared
|
|
40
|
+
* in `types/index.d.ts`; surfaced here on the root entry so `types` resolves them.
|
|
41
|
+
*/
|
|
42
|
+
export type RunMetrics = import("./types").RunMetrics;
|
|
43
|
+
/**
|
|
44
|
+
* Re-export the shared cross-cutting type shapes so adopters can name them when
|
|
45
|
+
* annotating their own wiring (tools arrays, providers, ctx, metrics). Declared
|
|
46
|
+
* in `types/index.d.ts`; surfaced here on the root entry so `types` resolves them.
|
|
47
|
+
*/
|
|
48
|
+
export type Store = import("./types").Store;
|
|
49
|
+
/**
|
|
50
|
+
* Re-export the shared cross-cutting type shapes so adopters can name them when
|
|
51
|
+
* annotating their own wiring (tools arrays, providers, ctx, metrics). Declared
|
|
52
|
+
* in `types/index.d.ts`; surfaced here on the root entry so `types` resolves them.
|
|
53
|
+
*/
|
|
54
|
+
export type Ctx = import("./types").Ctx;
|
|
1
55
|
import { Loop } from "./src/loop";
|
|
2
56
|
import { Planner } from "./src/planner";
|
|
3
57
|
import { Evaluator } from "./src/evaluator";
|
package/index.js
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Re-export the shared cross-cutting type shapes so adopters can name them when
|
|
5
|
+
* annotating their own wiring (tools arrays, providers, ctx, metrics). Declared
|
|
6
|
+
* in `types/index.d.ts`; surfaced here on the root entry so `types` resolves them.
|
|
7
|
+
* @typedef {import('./types').ToolDef} ToolDef
|
|
8
|
+
* @typedef {import('./types').ToolCall} ToolCall
|
|
9
|
+
* @typedef {import('./types').Message} Message
|
|
10
|
+
* @typedef {import('./types').Provider} Provider
|
|
11
|
+
* @typedef {import('./types').GenerateResult} GenerateResult
|
|
12
|
+
* @typedef {import('./types').Usage} Usage
|
|
13
|
+
* @typedef {import('./types').RunMetrics} RunMetrics
|
|
14
|
+
* @typedef {import('./types').Store} Store
|
|
15
|
+
* @typedef {import('./types').Ctx} Ctx
|
|
16
|
+
*/
|
|
17
|
+
|
|
3
18
|
const { Loop } = require('./src/loop');
|
|
4
19
|
const { Planner } = require('./src/planner');
|
|
5
20
|
const { Evaluator } = require('./src/evaluator');
|
package/package.json
CHANGED
|
@@ -6,6 +6,11 @@ const { ProviderError } = require('./errors');
|
|
|
6
6
|
const { requestWithTemperatureFallback } = require('./provider-temperature');
|
|
7
7
|
const { normalizeStopReason } = require('./provider-stop-reason');
|
|
8
8
|
const { resolveTimeoutMs, applyRequestBounds } = require('./provider-http');
|
|
9
|
+
const { hasUsageSignal } = require('./provider-usage');
|
|
10
|
+
|
|
11
|
+
// BA-24: the raw Anthropic usage field names. Presence of any (even value 0) means the API reported a
|
|
12
|
+
// usage signal → build the object; absence of all means no signal → surface null (unpriceable).
|
|
13
|
+
const ANTHROPIC_USAGE_KEYS = ['input_tokens', 'output_tokens', 'cache_read_input_tokens', 'cache_creation_input_tokens'];
|
|
9
14
|
|
|
10
15
|
/** @param {string} hostname @returns {boolean} */
|
|
11
16
|
function isLoopbackHost(hostname) {
|
|
@@ -191,12 +196,16 @@ class AnthropicProvider {
|
|
|
191
196
|
}),
|
|
192
197
|
// Anthropic's `input_tokens` is ALREADY the uncached remainder (cached tokens are reported
|
|
193
198
|
// separately, not folded in — verified live), so no subtraction here, unlike OpenAI/Gemini.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
199
|
+
// BA-24: honest null when the API returned no usage block (or an empty one) — do NOT manufacture
|
|
200
|
+
// an all-zeros object, which launders an unpriceable round into a $0 PRICED one.
|
|
201
|
+
usage: hasUsageSignal(data.usage, ANTHROPIC_USAGE_KEYS)
|
|
202
|
+
? {
|
|
203
|
+
inputTokens: data.usage.input_tokens || 0,
|
|
204
|
+
outputTokens: data.usage.output_tokens || 0,
|
|
205
|
+
cacheReadTokens: data.usage.cache_read_input_tokens || 0,
|
|
206
|
+
cacheCreationTokens: data.usage.cache_creation_input_tokens || 0,
|
|
207
|
+
}
|
|
208
|
+
: null,
|
|
200
209
|
...(temperatureDropped && { temperatureDropped: true }),
|
|
201
210
|
};
|
|
202
211
|
}
|
|
@@ -5,12 +5,10 @@ export type ParsedEnvelope = {
|
|
|
5
5
|
toolName?: string | undefined;
|
|
6
6
|
toolArguments?: Record<string, any> | undefined;
|
|
7
7
|
answer?: string | undefined;
|
|
8
|
-
usage: import("../types").Usage;
|
|
8
|
+
usage: import("../types").Usage | null;
|
|
9
9
|
model?: string | null | undefined;
|
|
10
10
|
costUsd?: number | undefined;
|
|
11
11
|
};
|
|
12
|
-
/** @typedef {import('../types').Message} Message */
|
|
13
|
-
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
14
12
|
/**
|
|
15
13
|
* The JSON envelope the CLI is constrained to (claude `--json-schema`). `tool_call` carries the
|
|
16
14
|
* name + args; `final_answer` carries prose. A closed `action` enum is the discriminator.
|
|
@@ -49,10 +47,10 @@ export function renderTranscript(messages: Message[]): string;
|
|
|
49
47
|
* preset so the claude usage contract (token tiers, `modelUsage` first-key, `total_cost_usd`) lives
|
|
50
48
|
* in exactly ONE place — a future CLI format change touches this function, not two copies.
|
|
51
49
|
* @param {any} outer - the parsed outer CLI envelope (already validated non-null by the caller).
|
|
52
|
-
* @returns {{usage: import('../types').Usage, model: string|null, costUsd?: number}}
|
|
50
|
+
* @returns {{usage: import('../types').Usage|null, model: string|null, costUsd?: number}}
|
|
53
51
|
*/
|
|
54
52
|
export function mapClaudeMeta(outer: any): {
|
|
55
|
-
usage: import("../types").Usage;
|
|
53
|
+
usage: import("../types").Usage | null;
|
|
56
54
|
model: string | null;
|
|
57
55
|
costUsd?: number;
|
|
58
56
|
};
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { ProviderError } = require('./errors');
|
|
4
|
+
const { hasUsageSignal } = require('./provider-usage');
|
|
4
5
|
|
|
5
6
|
/** @typedef {import('../types').Message} Message */
|
|
6
7
|
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
7
8
|
|
|
9
|
+
// BA-24: raw claude-CLI envelope usage fields. Any present (even 0) ⇒ a usage signal; none ⇒ null.
|
|
10
|
+
const CLAUDE_USAGE_KEYS = ['input_tokens', 'output_tokens', 'cache_read_input_tokens', 'cache_creation_input_tokens'];
|
|
11
|
+
|
|
8
12
|
// CLIPipe tool-mode support (v0.32.0). A subscription CLI (`claude -p`, …) is a plain
|
|
9
13
|
// TURN-provider: it takes text and returns text, with no native channel for a caller's tools. This
|
|
10
14
|
// module adds Option C — SCHEMA-VALIDATED TOOL EMULATION: the caller's tools are described in the
|
|
@@ -124,23 +128,27 @@ function renderTranscript(messages) {
|
|
|
124
128
|
* preset so the claude usage contract (token tiers, `modelUsage` first-key, `total_cost_usd`) lives
|
|
125
129
|
* in exactly ONE place — a future CLI format change touches this function, not two copies.
|
|
126
130
|
* @param {any} outer - the parsed outer CLI envelope (already validated non-null by the caller).
|
|
127
|
-
* @returns {{usage: import('../types').Usage, model: string|null, costUsd?: number}}
|
|
131
|
+
* @returns {{usage: import('../types').Usage|null, model: string|null, costUsd?: number}}
|
|
128
132
|
*/
|
|
129
133
|
function mapClaudeMeta(outer) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
// BA-24: an ABSENT/empty usage block ⇒ usage null (unpriceable), not a manufactured all-zeros object.
|
|
135
|
+
// The absent-block case is usually rescued by an authoritative `total_cost_usd` (a subscription run
|
|
136
|
+
// reports one even at $0 marginal → priced at source 'provider'); when the CLI omits BOTH, the round
|
|
137
|
+
// is honestly unpriced instead of a silent $0. Same principle line 136 already applies to the cache
|
|
138
|
+
// tiers, lifted one level up to the whole block. A present block with an explicit 0 field stays priced.
|
|
139
|
+
const raw = hasUsageSignal(outer.usage, CLAUDE_USAGE_KEYS) ? outer.usage : null;
|
|
140
|
+
/** @type {import('../types').Usage|null} */
|
|
141
|
+
const usage = raw
|
|
142
|
+
? { inputTokens: Number(raw.input_tokens) || 0, outputTokens: Number(raw.output_tokens) || 0 }
|
|
143
|
+
: null;
|
|
136
144
|
// Absent cache tiers mean the model didn't cache — omit rather than emit a synthetic 0 (per Usage docs).
|
|
137
|
-
if (Number.isFinite(
|
|
138
|
-
if (Number.isFinite(
|
|
145
|
+
if (usage && Number.isFinite(raw.cache_read_input_tokens)) usage.cacheReadTokens = raw.cache_read_input_tokens;
|
|
146
|
+
if (usage && Number.isFinite(raw.cache_creation_input_tokens)) usage.cacheCreationTokens = raw.cache_creation_input_tokens;
|
|
139
147
|
// `modelUsage` is an object keyed by model id (e.g. {"claude-opus-4-8[1m]": {...}}) — take the first key.
|
|
140
148
|
const model = (outer.modelUsage && typeof outer.modelUsage === 'object')
|
|
141
149
|
? (Object.keys(outer.modelUsage)[0] ?? null)
|
|
142
150
|
: null;
|
|
143
|
-
/** @type {{usage: import('../types').Usage, model: string|null, costUsd?: number}} */
|
|
151
|
+
/** @type {{usage: import('../types').Usage|null, model: string|null, costUsd?: number}} */
|
|
144
152
|
const meta = { usage, model };
|
|
145
153
|
// The CLI's own price is authoritative (a subscription run reports an equivalent cost even at $0
|
|
146
154
|
// marginal) — feeds bareguard's USD axis with no local rate table. Only a finite number counts.
|
|
@@ -154,7 +162,7 @@ function mapClaudeMeta(outer) {
|
|
|
154
162
|
* @property {string} [toolName]
|
|
155
163
|
* @property {Record<string, any>} [toolArguments]
|
|
156
164
|
* @property {string} [answer]
|
|
157
|
-
* @property {import('../types').Usage} usage
|
|
165
|
+
* @property {import('../types').Usage|null} usage
|
|
158
166
|
* @property {string|null} [model]
|
|
159
167
|
* @property {number} [costUsd]
|
|
160
168
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type Message = import("../types").Message;
|
|
2
2
|
export type ToolDef = import("../types").ToolDef;
|
|
3
3
|
export type GenerateResult = import("../types").GenerateResult;
|
|
4
|
+
export type ParsedEnvelope = import("./provider-clipipe-tools.js").ParsedEnvelope;
|
|
4
5
|
export type CLIPipeOptions = {
|
|
5
6
|
/**
|
|
6
7
|
* - CLI command to spawn (required).
|
|
@@ -142,11 +143,11 @@ export class CLIPipeProvider {
|
|
|
142
143
|
toolProtocol: {
|
|
143
144
|
name: string;
|
|
144
145
|
turnArgs(systemPrompt: string): string[];
|
|
145
|
-
parseResult(stdout: string): ParsedEnvelope;
|
|
146
|
+
parseResult(stdout: string): import("./provider-clipipe-tools").ParsedEnvelope;
|
|
146
147
|
probe: {
|
|
147
148
|
system: string;
|
|
148
149
|
user: string;
|
|
149
|
-
isCapable: (parsed: ParsedEnvelope) => boolean;
|
|
150
|
+
isCapable: (parsed: import("./provider-clipipe-tools").ParsedEnvelope) => boolean;
|
|
150
151
|
};
|
|
151
152
|
} | null;
|
|
152
153
|
probeCapability: boolean;
|
package/src/provider-clipipe.js
CHANGED
|
@@ -8,6 +8,12 @@ const { createBridge, resolveSessionError, runSession } = require('./provider-cl
|
|
|
8
8
|
/** @typedef {import('../types').Message} Message */
|
|
9
9
|
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
10
10
|
/** @typedef {import('../types').GenerateResult} GenerateResult */
|
|
11
|
+
// `toolProtocol`'s inferred type references ParsedEnvelope, which is declared in
|
|
12
|
+
// provider-clipipe-tools.js. Without this alias the name is not in scope when tsc
|
|
13
|
+
// emits this file's .d.ts, so the declaration shipped a bare `ParsedEnvelope` that
|
|
14
|
+
// adopters could not resolve (TS2304) — invisible here because our own tsconfig
|
|
15
|
+
// sets skipLibCheck.
|
|
16
|
+
/** @typedef {import('./provider-clipipe-tools.js').ParsedEnvelope} ParsedEnvelope */
|
|
11
17
|
|
|
12
18
|
/**
|
|
13
19
|
* @typedef {object} CLIPipeOptions
|
|
@@ -217,13 +223,16 @@ class CLIPipeProvider {
|
|
|
217
223
|
text: '',
|
|
218
224
|
toolCalls: [],
|
|
219
225
|
...partial,
|
|
220
|
-
|
|
226
|
+
// BA-24: a parse fn that reports usage is trusted (missing tiers fill to 0); one that reports
|
|
227
|
+
// none surfaces null (unpriceable) rather than a manufactured all-zeros object.
|
|
228
|
+
usage: partial.usage ? { inputTokens: 0, outputTokens: 0, ...(/** @type {any} */ (partial.usage)) } : null,
|
|
221
229
|
};
|
|
222
230
|
}
|
|
231
|
+
// BA-24: raw text mode carries NO token data ever — honest null (unpriceable), not a synthetic $0.
|
|
223
232
|
return {
|
|
224
233
|
text: stdout,
|
|
225
234
|
toolCalls: [],
|
|
226
|
-
usage:
|
|
235
|
+
usage: null,
|
|
227
236
|
};
|
|
228
237
|
}
|
|
229
238
|
|
|
@@ -333,12 +342,22 @@ class CLIPipeProvider {
|
|
|
333
342
|
// captures a turn the CLI billed but never emitted as an event (measured — a bounded session's
|
|
334
343
|
// cut-off turn). Summing the per-turn records is the fallback for a session we killed before its
|
|
335
344
|
// result event. Either way the arithmetic is per-TURN, never per block-event (BA-17).
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
345
|
+
// BA-24: key on the NORMALIZED meta.usage (a signal-bearing block), not the raw r.final.usage — an
|
|
346
|
+
// absent/empty raw block now normalizes to null, so fall back to the per-turn sum rather than null.
|
|
347
|
+
// The per-turn sum is a real signal ONLY when turns actually streamed; a session that died before
|
|
348
|
+
// any turn (zero-turn native session, no final usage block) has NOTHING to sum, and reducing over
|
|
349
|
+
// an empty array would MANUFACTURE a truthy all-zeros object — the exact absence→$0-priced laundering
|
|
350
|
+
// BA-24 eliminates at every other site. So absence (no meta.usage AND no turns) surfaces null.
|
|
351
|
+
const usage = (meta && meta.usage)
|
|
352
|
+
? meta.usage
|
|
353
|
+
: (r.turns.length > 0
|
|
354
|
+
? r.turns.reduce((/** @type {any} */ a, t) => ({
|
|
355
|
+
inputTokens: a.inputTokens + (t.inputTokens || 0),
|
|
356
|
+
outputTokens: a.outputTokens + (t.outputTokens || 0),
|
|
357
|
+
cacheReadTokens: a.cacheReadTokens + (t.cacheReadTokens || 0),
|
|
358
|
+
cacheCreationTokens: a.cacheCreationTokens + (t.cacheCreationTokens || 0),
|
|
359
|
+
}), { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0 })
|
|
360
|
+
: null);
|
|
342
361
|
|
|
343
362
|
const { stopReason, error } = resolveSessionError({
|
|
344
363
|
// A bridge/guard terminal is more specific than the turn backstop, so it wins the tag.
|
|
@@ -362,7 +381,9 @@ class CLIPipeProvider {
|
|
|
362
381
|
// real but SHORT of the session total. Sending the difference makes a gate's token axis add up
|
|
363
382
|
// to exactly what the CLI itself reports — where sending the total would double-count everything
|
|
364
383
|
// already streamed, and sending zero would leave the axis quietly under-fed.
|
|
365
|
-
|
|
384
|
+
// BA-24: a null usage (zero-turn session, no reported block) has no residual to reconcile — pass
|
|
385
|
+
// the honest null through to the meter rather than dereferencing null in subtractUsage.
|
|
386
|
+
const residual = usage ? subtractUsage(usage, r.turns) : null;
|
|
366
387
|
if (this.onTurn) {
|
|
367
388
|
try {
|
|
368
389
|
await this.onTurn({
|
package/src/provider-gemini.d.ts
CHANGED
|
@@ -72,9 +72,9 @@ export class GeminiProvider {
|
|
|
72
72
|
* (total = prompt + candidates + thoughts — confirmed against live usageMetadata). Implicit caching
|
|
73
73
|
* has no separate write tier → cacheCreationTokens 0.
|
|
74
74
|
* @param {any} u - raw `data.usageMetadata`
|
|
75
|
-
* @returns {import('../types').Usage}
|
|
75
|
+
* @returns {import('../types').Usage|null}
|
|
76
76
|
*/
|
|
77
|
-
_normalizeUsage(u: any): import("../types").Usage;
|
|
77
|
+
_normalizeUsage(u: any): import("../types").Usage | null;
|
|
78
78
|
/**
|
|
79
79
|
* @param {string} path
|
|
80
80
|
* @param {Record<string, any>} body
|
package/src/provider-gemini.js
CHANGED
|
@@ -6,6 +6,10 @@ const { ProviderError } = require('./errors');
|
|
|
6
6
|
const { requestWithTemperatureFallback } = require('./provider-temperature');
|
|
7
7
|
const { normalizeStopReason } = require('./provider-stop-reason');
|
|
8
8
|
const { resolveTimeoutMs, applyRequestBounds } = require('./provider-http');
|
|
9
|
+
const { hasUsageSignal } = require('./provider-usage');
|
|
10
|
+
|
|
11
|
+
// BA-24: raw Gemini usageMetadata fields. Any present (even 0) ⇒ a usage signal; none ⇒ null.
|
|
12
|
+
const GEMINI_USAGE_KEYS = ['promptTokenCount', 'candidatesTokenCount', 'thoughtsTokenCount', 'cachedContentTokenCount'];
|
|
9
13
|
|
|
10
14
|
/** @typedef {import('../types').Message} Message */
|
|
11
15
|
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
@@ -168,9 +172,12 @@ class GeminiProvider {
|
|
|
168
172
|
* (total = prompt + candidates + thoughts — confirmed against live usageMetadata). Implicit caching
|
|
169
173
|
* has no separate write tier → cacheCreationTokens 0.
|
|
170
174
|
* @param {any} u - raw `data.usageMetadata`
|
|
171
|
-
* @returns {import('../types').Usage}
|
|
175
|
+
* @returns {import('../types').Usage|null}
|
|
172
176
|
*/
|
|
173
177
|
_normalizeUsage(u) {
|
|
178
|
+
// BA-24: no usageMetadata (or an empty one) ⇒ null, not an all-zeros object (which would launder an
|
|
179
|
+
// unpriceable round into a $0 PRICED one). A present block with an explicit 0 field stays priced.
|
|
180
|
+
if (!hasUsageSignal(u, GEMINI_USAGE_KEYS)) return null;
|
|
174
181
|
const cacheRead = u?.cachedContentTokenCount || 0;
|
|
175
182
|
return {
|
|
176
183
|
inputTokens: Math.max(0, (u?.promptTokenCount || 0) - cacheRead),
|
package/src/provider-ollama.js
CHANGED
|
@@ -5,6 +5,10 @@ const { ProviderError } = require('./errors');
|
|
|
5
5
|
const { requestWithTemperatureFallback } = require('./provider-temperature');
|
|
6
6
|
const { normalizeStopReason } = require('./provider-stop-reason');
|
|
7
7
|
const { resolveTimeoutMs, applyRequestBounds } = require('./provider-http');
|
|
8
|
+
const { hasUsageSignal } = require('./provider-usage');
|
|
9
|
+
|
|
10
|
+
// BA-24: raw Ollama usage fields. Any present (even 0) ⇒ a usage signal; none ⇒ null (unpriceable).
|
|
11
|
+
const OLLAMA_USAGE_KEYS = ['prompt_eval_count', 'eval_count'];
|
|
8
12
|
|
|
9
13
|
/** @typedef {import('../types').Message} Message */
|
|
10
14
|
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
@@ -101,10 +105,11 @@ class OllamaProvider {
|
|
|
101
105
|
// Like Gemini, Ollama has NO tool_use done_reason — a complete tool call returns `stop` (measured).
|
|
102
106
|
// `hasToolCalls` lets the normalizer say so, instead of reporting a tool round as a clean finish.
|
|
103
107
|
stopReason: normalizeStopReason(data.done_reason, 'ollama', { hasToolCalls: toolCalls.length > 0 }),
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
// BA-24: honest null when the response carried no token counts — do NOT manufacture an all-zeros
|
|
109
|
+
// object (which launders an unpriceable round into a $0 PRICED one).
|
|
110
|
+
usage: hasUsageSignal(data, OLLAMA_USAGE_KEYS)
|
|
111
|
+
? { inputTokens: data.prompt_eval_count || 0, outputTokens: data.eval_count || 0 }
|
|
112
|
+
: null,
|
|
108
113
|
...(temperatureDropped && { temperatureDropped: true }),
|
|
109
114
|
};
|
|
110
115
|
}
|
package/src/provider-openai.d.ts
CHANGED
|
@@ -82,9 +82,9 @@ export class OpenAIProvider {
|
|
|
82
82
|
* remainder (else the cached tokens are double-counted and priced at the full input rate, a ~2x
|
|
83
83
|
* over-charge on a warm prompt). OpenAI has no separate cache-write tier → cacheCreationTokens 0.
|
|
84
84
|
* @param {any} u - raw `data.usage`
|
|
85
|
-
* @returns {import('../types').Usage}
|
|
85
|
+
* @returns {import('../types').Usage|null}
|
|
86
86
|
*/
|
|
87
|
-
_normalizeUsage(u: any): import("../types").Usage;
|
|
87
|
+
_normalizeUsage(u: any): import("../types").Usage | null;
|
|
88
88
|
/**
|
|
89
89
|
* @param {string} path
|
|
90
90
|
* @param {Record<string, any>} body
|
package/src/provider-openai.js
CHANGED
|
@@ -6,6 +6,10 @@ const { ProviderError } = require('./errors');
|
|
|
6
6
|
const { requestWithTemperatureFallback } = require('./provider-temperature');
|
|
7
7
|
const { normalizeStopReason } = require('./provider-stop-reason');
|
|
8
8
|
const { resolveTimeoutMs, applyRequestBounds } = require('./provider-http');
|
|
9
|
+
const { hasUsageSignal } = require('./provider-usage');
|
|
10
|
+
|
|
11
|
+
// BA-24: raw OpenAI usage fields. Any present (even 0) ⇒ a usage signal; none ⇒ null (unpriceable).
|
|
12
|
+
const OPENAI_USAGE_KEYS = ['prompt_tokens', 'completion_tokens', 'prompt_tokens_details'];
|
|
9
13
|
|
|
10
14
|
/** @typedef {import('../types').Message} Message */
|
|
11
15
|
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
@@ -126,9 +130,12 @@ class OpenAIProvider {
|
|
|
126
130
|
* remainder (else the cached tokens are double-counted and priced at the full input rate, a ~2x
|
|
127
131
|
* over-charge on a warm prompt). OpenAI has no separate cache-write tier → cacheCreationTokens 0.
|
|
128
132
|
* @param {any} u - raw `data.usage`
|
|
129
|
-
* @returns {import('../types').Usage}
|
|
133
|
+
* @returns {import('../types').Usage|null}
|
|
130
134
|
*/
|
|
131
135
|
_normalizeUsage(u) {
|
|
136
|
+
// BA-24: no usage block (or an empty one) ⇒ null, not an all-zeros object (which would launder an
|
|
137
|
+
// unpriceable round into a $0 PRICED one). A present block with an explicit 0 field stays priced.
|
|
138
|
+
if (!hasUsageSignal(u, OPENAI_USAGE_KEYS)) return null;
|
|
132
139
|
const cacheRead = u?.prompt_tokens_details?.cached_tokens || 0;
|
|
133
140
|
return {
|
|
134
141
|
inputTokens: Math.max(0, (u?.prompt_tokens || 0) - cacheRead),
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BA-24: the discriminator between an ABSENT usage block and a PRESENT one whose token fields are
|
|
3
|
+
* legitimately 0.
|
|
4
|
+
*
|
|
5
|
+
* A provider that returns no usage information (the API sent a 200 with no `usage` block, or an empty
|
|
6
|
+
* `{}`) must surface `usage: null` so the round reads as UNPRICEABLE — `resolveRoundCost`'s
|
|
7
|
+
* `if (!usage) return {cost:null, source:null}` branch (the honest-null contract) only fires on a
|
|
8
|
+
* falsy usage. Coalescing an absent block into an all-zeros object (`data.usage?.field || 0` built
|
|
9
|
+
* unconditionally) launders that unknown into a $0 PRICED round — worse, at the confident `'tier'`
|
|
10
|
+
* rate label, so a consumer filtering for `'default'` guesses never sees it. That laundering was the
|
|
11
|
+
* BA-23 fix relocated one layer up: loop.js stopped feeding stale usage to the resolver, but every
|
|
12
|
+
* http provider still MANUFACTURED a truthy zero object, so the null branch stayed unreachable on the
|
|
13
|
+
* paid path.
|
|
14
|
+
*
|
|
15
|
+
* Per-field `|| 0` stays correct (a real round can report an individual tier as 0); it is the coalescing
|
|
16
|
+
* of the WHOLE block's absence into a default object that is the bug. A present block with an explicit
|
|
17
|
+
* zero — e.g. a cache-only round: `input_tokens:0` + `cache_read_input_tokens>0` — carries a signal and
|
|
18
|
+
* stays priced, because that field is present (`!= null`), even at value 0.
|
|
19
|
+
*
|
|
20
|
+
* @param {any} block - raw provider usage object (`data.usage` / `usageMetadata` / the CLI envelope's `usage`)
|
|
21
|
+
* @param {string[]} keys - the recognized raw field names for this provider
|
|
22
|
+
* @returns {boolean} true iff `block` is an object carrying at least one recognized field as a non-null value
|
|
23
|
+
*/
|
|
24
|
+
export function hasUsageSignal(block: any, keys: string[]): boolean;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BA-24: the discriminator between an ABSENT usage block and a PRESENT one whose token fields are
|
|
5
|
+
* legitimately 0.
|
|
6
|
+
*
|
|
7
|
+
* A provider that returns no usage information (the API sent a 200 with no `usage` block, or an empty
|
|
8
|
+
* `{}`) must surface `usage: null` so the round reads as UNPRICEABLE — `resolveRoundCost`'s
|
|
9
|
+
* `if (!usage) return {cost:null, source:null}` branch (the honest-null contract) only fires on a
|
|
10
|
+
* falsy usage. Coalescing an absent block into an all-zeros object (`data.usage?.field || 0` built
|
|
11
|
+
* unconditionally) launders that unknown into a $0 PRICED round — worse, at the confident `'tier'`
|
|
12
|
+
* rate label, so a consumer filtering for `'default'` guesses never sees it. That laundering was the
|
|
13
|
+
* BA-23 fix relocated one layer up: loop.js stopped feeding stale usage to the resolver, but every
|
|
14
|
+
* http provider still MANUFACTURED a truthy zero object, so the null branch stayed unreachable on the
|
|
15
|
+
* paid path.
|
|
16
|
+
*
|
|
17
|
+
* Per-field `|| 0` stays correct (a real round can report an individual tier as 0); it is the coalescing
|
|
18
|
+
* of the WHOLE block's absence into a default object that is the bug. A present block with an explicit
|
|
19
|
+
* zero — e.g. a cache-only round: `input_tokens:0` + `cache_read_input_tokens>0` — carries a signal and
|
|
20
|
+
* stays priced, because that field is present (`!= null`), even at value 0.
|
|
21
|
+
*
|
|
22
|
+
* @param {any} block - raw provider usage object (`data.usage` / `usageMetadata` / the CLI envelope's `usage`)
|
|
23
|
+
* @param {string[]} keys - the recognized raw field names for this provider
|
|
24
|
+
* @returns {boolean} true iff `block` is an object carrying at least one recognized field as a non-null value
|
|
25
|
+
*/
|
|
26
|
+
function hasUsageSignal(block, keys) {
|
|
27
|
+
if (!block || typeof block !== 'object') return false;
|
|
28
|
+
for (const k of keys) {
|
|
29
|
+
if (block[k] != null) return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { hasUsageSignal };
|
|
@@ -143,7 +143,7 @@ export function buildScanTool(corpus: Slice[] | (() => Promise<Slice[]>), opts:
|
|
|
143
143
|
/**
|
|
144
144
|
* Build a SCAN slice-source backed by a litectx-RESIDENT corpus (facts/episodes the agent already accrued) —
|
|
145
145
|
* the §10-step-7 deferral un-blocked by litectx 0.26's `enumerate` verb (spec:
|
|
146
|
-
* docs/
|
|
146
|
+
* docs/archive/prd.md). Returns the generic async slice-source recurse's scan reads: a
|
|
147
147
|
* `() => Promise<Slice[]>` that pages through EVERY row of `kind` via `enumerate` (exhaustive — the rank-free
|
|
148
148
|
* read `recall` structurally cannot do) and maps each to `{id: item.path, text: item.body}`. recurse stays
|
|
149
149
|
* litectx-agnostic: it depends on this source SHAPE, never on litectx (same stance as `remember`'s Store
|
package/src/recurse-retrieval.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
//
|
|
17
17
|
// THE CORPUS FOR SCAN IS A GENERIC ARRAY SLICE-SOURCE (`opts.corpus`), NOT litectx: litectx has no exhaustive,
|
|
18
18
|
// rank-free enumerate verb today (every read is FTS-gated). The "corpus that already LIVES in litectx" case
|
|
19
|
-
// waits on the litectx `enumerate` verb (docs/
|
|
19
|
+
// waits on the litectx `enumerate` verb (docs/archive/prd.md) and drops in behind this
|
|
20
20
|
// same slice-source socket with ZERO recurse changes — the same backend-agnostic stance as `remember`'s Store
|
|
21
21
|
// socket. Composes AROUND a Loop; NEVER imported by loop.js.
|
|
22
22
|
|
|
@@ -326,7 +326,7 @@ const ENUM_PAGE = 200;
|
|
|
326
326
|
/**
|
|
327
327
|
* Build a SCAN slice-source backed by a litectx-RESIDENT corpus (facts/episodes the agent already accrued) —
|
|
328
328
|
* the §10-step-7 deferral un-blocked by litectx 0.26's `enumerate` verb (spec:
|
|
329
|
-
* docs/
|
|
329
|
+
* docs/archive/prd.md). Returns the generic async slice-source recurse's scan reads: a
|
|
330
330
|
* `() => Promise<Slice[]>` that pages through EVERY row of `kind` via `enumerate` (exhaustive — the rank-free
|
|
331
331
|
* read `recall` structurally cannot do) and maps each to `{id: item.path, text: item.body}`. recurse stays
|
|
332
332
|
* litectx-agnostic: it depends on this source SHAPE, never on litectx (same stance as `remember`'s Store
|
package/src/recurse.js
CHANGED
|
@@ -1037,7 +1037,7 @@ async function recurseRefineLeaf(task, ctx, opts, state) {
|
|
|
1037
1037
|
* never folded into the count as a zero; a governance HaltError mid-scan → clean incomplete.
|
|
1038
1038
|
*
|
|
1039
1039
|
* The corpus is the generic array slice-source `opts.corpus`. Absent it, scan has nothing to read — litectx's
|
|
1040
|
-
* resident-corpus enumerate path is deferred (docs/
|
|
1040
|
+
* resident-corpus enumerate path is deferred (docs/archive/prd.md) — so we return an
|
|
1041
1041
|
* honest incomplete, never a fabricated zero.
|
|
1042
1042
|
* @param {string} task
|
|
1043
1043
|
* @param {RecurseCtx} ctx
|
package/types/index.d.ts
CHANGED
|
@@ -86,7 +86,9 @@ export interface ToolCall {
|
|
|
86
86
|
export interface GenerateResult {
|
|
87
87
|
text: string;
|
|
88
88
|
toolCalls: ToolCall[];
|
|
89
|
-
usage
|
|
89
|
+
/** Normalized token usage, or `null` when the provider reported NO usage block (BA-24) — an
|
|
90
|
+
* unpriceable round, distinct from a present block whose fields are legitimately 0. */
|
|
91
|
+
usage: Usage | null;
|
|
90
92
|
/** Model id the response was produced by; preferred over Provider.model for cost accounting. */
|
|
91
93
|
model?: string | null;
|
|
92
94
|
/**
|