@navels/neal 0.1.0 → 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/README.md +206 -209
- package/SECURITY.md +16 -19
- package/dist/neal/adjudicator/{blocked-adjudicator.js → consultant.js} +21 -21
- package/dist/neal/adjudicator/execute.js +33 -33
- package/dist/neal/adjudicator/final-completion.js +2 -31
- package/dist/neal/adjudicator/planning.js +264 -24
- package/dist/neal/agents/prompts.js +7 -15
- package/dist/neal/agents/rounds.js +51 -73
- package/dist/neal/agents/schemas.js +615 -502
- package/dist/neal/agents/structured-json.js +36 -0
- package/dist/neal/blocked-guidance.js +16 -9
- package/dist/neal/cli.js +2 -2
- package/dist/neal/commands/check.js +2 -2
- package/dist/neal/commands/compat.js +100 -36
- package/dist/neal/commands/interactive-activity.js +2 -2
- package/dist/neal/commands/recovery-guidance.js +20 -8
- package/dist/neal/commands/runtime.js +4 -2
- package/dist/neal/config.js +40 -7
- package/dist/neal/context/inline-review-context.js +13 -28
- package/dist/neal/context/reviewer-context.js +19 -14
- package/dist/neal/eval/reviewer-eval-manifest.js +86 -0
- package/dist/neal/eval/reviewer-eval.js +155 -0
- package/dist/neal/git.js +9 -3
- package/dist/neal/interactive-controls.js +5 -0
- package/dist/neal/orchestrator/completion.js +215 -113
- package/dist/neal/orchestrator/failures.js +7 -0
- package/dist/neal/orchestrator/notifications.js +9 -9
- package/dist/neal/orchestrator/phases/planning.js +124 -112
- package/dist/neal/orchestrator/phases/recovery.js +151 -115
- package/dist/neal/orchestrator/phases/review.js +28 -1
- package/dist/neal/orchestrator/split-plan.js +12 -11
- package/dist/neal/orchestrator/transitions.js +36 -71
- package/dist/neal/orchestrator.js +3 -0
- package/dist/neal/plan-doc.js +24 -1
- package/dist/neal/plan-queue.js +29 -1
- package/dist/neal/prompts/assert-builder.js +8 -1
- package/dist/neal/prompts/execute.js +11 -19
- package/dist/neal/prompts/planning.js +64 -55
- package/dist/neal/prompts/review-doctrine.js +13 -50
- package/dist/neal/prompts/specialized.js +28 -22
- package/dist/neal/prompts/specs.js +223 -17
- package/dist/neal/providers/agent-settings-isolation.js +35 -0
- package/dist/neal/providers/anthropic-claude.js +309 -248
- package/dist/neal/providers/detection.js +3 -7
- package/dist/neal/providers/git-config-isolation.js +83 -0
- package/dist/neal/providers/openai-codex.js +102 -204
- package/dist/neal/providers/{generic-agentic-tools.js → openai-compatible-tools.js} +11 -5
- package/dist/neal/providers/openai-compatible.js +1079 -403
- package/dist/neal/providers/openrouter-routing.js +68 -0
- package/dist/neal/providers/pricing.js +124 -0
- package/dist/neal/providers/rate-card.js +2275 -0
- package/dist/neal/providers/registry.js +6 -17
- package/dist/neal/providers/telemetry.js +4 -0
- package/dist/neal/providers/types.js +12 -0
- package/dist/neal/resume-decision.js +7 -6
- package/dist/neal/resume-planner.js +9 -0
- package/dist/neal/retrospective.js +36 -7
- package/dist/neal/review-debt.js +30 -0
- package/dist/neal/review-findings/provider.js +17 -29
- package/dist/neal/review.js +17 -1
- package/dist/neal/run-metrics.js +74 -9
- package/dist/neal/state-invariants.js +11 -3
- package/dist/neal/state-views.js +27 -5
- package/dist/neal/state.js +59 -15
- package/dist/neal/status.js +28 -1
- package/dist/neal/support.js +3 -3
- package/docs/README.md +22 -18
- package/docs/{ADJUDICATOR_INVENTORY.md → adjudicator-inventory.md} +58 -57
- package/docs/architecture.md +79 -83
- package/docs/automation.md +11 -11
- package/docs/compat.md +91 -61
- package/docs/compatible-models.md +145 -110
- package/docs/demo.md +15 -5
- package/docs/maintenance.md +74 -32
- package/docs/plan-format.md +61 -33
- package/docs/prompt-evals.md +96 -0
- package/docs/{PROMPT_SPECS.md → prompt-specs.md} +52 -48
- package/docs/providers.md +330 -283
- package/docs/release.md +43 -40
- package/docs/review-convergence.md +246 -0
- package/docs/state-machine.md +61 -59
- package/docs/storage.md +40 -31
- package/docs/troubleshooting.md +32 -29
- package/examples/compat/add-edit-verify/PLAN.md +2 -0
- package/examples/compat/add-edit-verify/broken.diff +2 -2
- package/examples/compat/add-edit-verify/good.diff +2 -2
- package/examples/compat/add-edit-verify/src/add.js +1 -1
- package/examples/compat/manifest.json +25 -29
- package/examples/compat/sum-grep-edit/PLAN.md +33 -0
- package/examples/compat/sum-grep-edit/broken.diff +12 -0
- package/examples/compat/sum-grep-edit/good.diff +12 -0
- package/examples/compat/sum-grep-edit/package.json +5 -0
- package/examples/compat/sum-grep-edit/src/numbers.js +9 -0
- package/examples/compat/sum-grep-edit/test/numbers.test.js +15 -0
- package/examples/issue-triage-js/README.md +0 -15
- package/examples/reviewer-eval/clean-doc/base/src/clamp.ts +3 -0
- package/examples/reviewer-eval/clean-doc/change.diff +9 -0
- package/examples/reviewer-eval/clean-extract/base/src/greet.ts +3 -0
- package/examples/reviewer-eval/clean-extract/change.diff +13 -0
- package/examples/reviewer-eval/dropped-error/base/src/load.ts +4 -0
- package/examples/reviewer-eval/dropped-error/change.diff +15 -0
- package/examples/reviewer-eval/manifest.json +45 -0
- package/examples/reviewer-eval/off-by-one/base/src/paginate.ts +5 -0
- package/examples/reviewer-eval/off-by-one/change.diff +11 -0
- package/{config.yml → neal.yml} +1 -1
- package/package.json +20 -15
- package/dist/neal/providers/generic-agentic.js +0 -1238
- package/docs/comparison.md +0 -105
- package/examples/compat/is-even-add-test/PLAN.md +0 -30
- package/examples/compat/is-even-add-test/broken.diff +0 -11
- package/examples/compat/is-even-add-test/good.diff +0 -11
- package/examples/compat/is-even-add-test/package.json +0 -5
- package/examples/compat/is-even-add-test/src/is-even.js +0 -3
- package/examples/compat/is-even-add-test/test/is-even.test.js +0 -9
- package/examples/compat/reverse-grep-edit/PLAN.md +0 -32
- package/examples/compat/reverse-grep-edit/broken.diff +0 -12
- package/examples/compat/reverse-grep-edit/good.diff +0 -12
- package/examples/compat/reverse-grep-edit/package.json +0 -5
- package/examples/compat/reverse-grep-edit/src/strings.js +0 -10
- package/examples/compat/reverse-grep-edit/test/strings.test.js +0 -15
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenRouter request routing for the OpenAI-compatible adapter
|
|
3
|
+
* (`openai-compatible`).
|
|
4
|
+
*
|
|
5
|
+
* OpenRouter serves a single model slug from many independent backends whose
|
|
6
|
+
* capabilities differ — some don't support structured outputs (`json_schema`)
|
|
7
|
+
* at all, others are lower-fidelity quantizations. OpenRouter picks a backend
|
|
8
|
+
* per request, so a slug that drove neal's loop yesterday can fail today with
|
|
9
|
+
* no slug change: it just got routed to a backend that can't honor the
|
|
10
|
+
* parameters neal sends. That is exactly the drift that broke the coder's
|
|
11
|
+
* structured-output cells in the live smoke.
|
|
12
|
+
*
|
|
13
|
+
* `require_parameters: true` tells OpenRouter to only route to backends that
|
|
14
|
+
* support every parameter in the request. Since neal's coder sends
|
|
15
|
+
* `response_format: { type: 'json_schema' }`, this excludes the backends that
|
|
16
|
+
* can't do structured output and keeps a slug's behavior stable across
|
|
17
|
+
* requests — so when the compat whitelist says a slug works, it keeps working.
|
|
18
|
+
*
|
|
19
|
+
* It is applied as a default (no config knob) whenever the endpoint is
|
|
20
|
+
* OpenRouter, and is a no-op for every other OpenAI-compatible endpoint, which
|
|
21
|
+
* would reject or ignore the OpenRouter-specific `provider` field.
|
|
22
|
+
*/
|
|
23
|
+
const OPENROUTER_HOST = 'openrouter.ai';
|
|
24
|
+
export function isOpenRouterBaseUrl(baseUrl) {
|
|
25
|
+
let host;
|
|
26
|
+
try {
|
|
27
|
+
host = new URL(baseUrl).host.toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return host === OPENROUTER_HOST || host.endsWith(`.${OPENROUTER_HOST}`);
|
|
33
|
+
}
|
|
34
|
+
// Add `provider.require_parameters: true` to a chat-completion request body,
|
|
35
|
+
// leaving an already-present `provider` block untouched (a caller that set its
|
|
36
|
+
// own routing wins). Anything that isn't a JSON object body passes through
|
|
37
|
+
// unchanged, so a non-chat request or an opaque body is never corrupted.
|
|
38
|
+
export function injectRequireParameters(init) {
|
|
39
|
+
if (!init || typeof init.body !== 'string') {
|
|
40
|
+
return init;
|
|
41
|
+
}
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(init.body);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return init;
|
|
48
|
+
}
|
|
49
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
50
|
+
return init;
|
|
51
|
+
}
|
|
52
|
+
const body = parsed;
|
|
53
|
+
if ('provider' in body) {
|
|
54
|
+
return init;
|
|
55
|
+
}
|
|
56
|
+
return { ...init, body: JSON.stringify({ ...body, provider: { require_parameters: true } }) };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Wrap `inner` so requests to OpenRouter carry `provider.require_parameters:
|
|
60
|
+
* true`. Returns `inner` unchanged for non-OpenRouter base URLs so no other
|
|
61
|
+
* endpoint sees the OpenRouter-specific field.
|
|
62
|
+
*/
|
|
63
|
+
export function withOpenRouterRouting(baseUrl, inner) {
|
|
64
|
+
if (!isOpenRouterBaseUrl(baseUrl)) {
|
|
65
|
+
return inner;
|
|
66
|
+
}
|
|
67
|
+
return (input, init) => inner(input, injectRequireParameters(init));
|
|
68
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Shared pricing type and rate math for token-only provider adapters
|
|
2
|
+
// (openai-compatible and openai-codex). Those adapters report
|
|
3
|
+
// token counts but not dollars. Cost is resolved per bucket in this order:
|
|
4
|
+
// operator-configured per-million rates under
|
|
5
|
+
// `providers.openai_compatible.pricing` (the override tier), else the vendored
|
|
6
|
+
// published rate card (`rate-card.ts`, keyed by exact model slug), else
|
|
7
|
+
// tokens-only (`null` — Neal never invents dollars). The Claude adapter does
|
|
8
|
+
// not use this: it passes through the provider-reported `total_cost_usd`
|
|
9
|
+
// instead, and that provider-reported cost always wins upstream (it never
|
|
10
|
+
// reaches `resolveRateCost`).
|
|
11
|
+
import { RATE_CARD } from './rate-card.js';
|
|
12
|
+
// Same coercion as `numberValue` in run-metrics.ts: non-number or non-finite
|
|
13
|
+
// values normalize to 0 so the arithmetic never yields NaN.
|
|
14
|
+
function num(value) {
|
|
15
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Rate-compute the USD cost of one turn's token usage.
|
|
19
|
+
*
|
|
20
|
+
* Field-to-rate mapping (kept explicit so the math is reproducible from the
|
|
21
|
+
* source alone):
|
|
22
|
+
*
|
|
23
|
+
* - `totalInput = input_tokens + inputTokens` — the reported prompt/input
|
|
24
|
+
* count. For OpenAI-compatible Chat Completions this count is *inclusive of*
|
|
25
|
+
* cached tokens (`prompt_tokens` already contains
|
|
26
|
+
* `prompt_tokens_details.cached_tokens`; the AI SDK surfaces these as
|
|
27
|
+
* `inputTokens` and `cachedInputTokens`).
|
|
28
|
+
* - `cachedInput = cached_input_tokens + cachedInputTokens +
|
|
29
|
+
* cache_read_input_tokens + cacheReadInputTokens` — tokens billed at the
|
|
30
|
+
* cached rate.
|
|
31
|
+
* - `billedUncachedInput = max(0, totalInput - cachedInput)` — cached tokens
|
|
32
|
+
* are subtracted from the inclusive total so a cached token is billed once,
|
|
33
|
+
* at the cached rate, never also at the full input rate. The `max(0, ...)`
|
|
34
|
+
* clamp is the defined handling for inconsistent counts (cached reported
|
|
35
|
+
* greater than total): treat the excess as fully cached rather than emitting a
|
|
36
|
+
* negative term.
|
|
37
|
+
* - `output = output_tokens + outputTokens` — already includes reasoning
|
|
38
|
+
* tokens for these providers, so reasoning output is not added separately.
|
|
39
|
+
* - Cache-creation tokens (`cache_creation_input_tokens`) are an Anthropic-only
|
|
40
|
+
* concept billed via provider-reported cost, not by these rates, so they are
|
|
41
|
+
* intentionally excluded here.
|
|
42
|
+
*
|
|
43
|
+
* Returns 0 (never NaN) when no tokens are present.
|
|
44
|
+
*/
|
|
45
|
+
export function computeRateCostUsd(usage, pricing) {
|
|
46
|
+
const value = (usage && typeof usage === 'object' && !Array.isArray(usage)
|
|
47
|
+
? usage
|
|
48
|
+
: {});
|
|
49
|
+
const totalInput = num(value.input_tokens) + num(value.inputTokens);
|
|
50
|
+
const cachedInput = num(value.cached_input_tokens) +
|
|
51
|
+
num(value.cachedInputTokens) +
|
|
52
|
+
num(value.cache_read_input_tokens) +
|
|
53
|
+
num(value.cacheReadInputTokens);
|
|
54
|
+
const billedUncachedInput = Math.max(0, totalInput - cachedInput);
|
|
55
|
+
const output = num(value.output_tokens) + num(value.outputTokens);
|
|
56
|
+
return ((billedUncachedInput / 1e6) * pricing.inputPerMillion +
|
|
57
|
+
(cachedInput / 1e6) * pricing.cachedInputPerMillion +
|
|
58
|
+
(output / 1e6) * pricing.outputPerMillion);
|
|
59
|
+
}
|
|
60
|
+
const PER_TOKEN_TO_PER_MILLION = 1e6;
|
|
61
|
+
function isFiniteNonNegativeNumber(value) {
|
|
62
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolve a model slug to per-million pricing from the vendored rate card.
|
|
66
|
+
*
|
|
67
|
+
* Exact-match only: the slug is looked up (after trimming surrounding
|
|
68
|
+
* whitespace) as a literal key. No provider prefix is stripped and there is no
|
|
69
|
+
* basename fallback, so a slash-qualified slug like `local/gpt-5.5`,
|
|
70
|
+
* `azure/<deployment>`, or a finetune whose basename collides with a listed
|
|
71
|
+
* model does NOT inherit that model's price — pricing an unlisted slug would
|
|
72
|
+
* invent dollars, which the never-invent contract forbids. Provider-qualified
|
|
73
|
+
* slugs are priced only when the card carries that exact key (LiteLLM lists many
|
|
74
|
+
* `vendor/model` keys directly).
|
|
75
|
+
*
|
|
76
|
+
* Returns null for a non-string / empty / whitespace-only model, for an unknown
|
|
77
|
+
* slug, and for a card entry whose input or output cost is not a finite
|
|
78
|
+
* non-negative number (a partial entry is a miss, never a partial rate). The
|
|
79
|
+
* per-token rates are converted to per-million. The cached rate uses
|
|
80
|
+
* `cacheReadInputTokenCost` when it is a finite non-negative number, else falls
|
|
81
|
+
* back to the input rate (no published cache discount means cached reads are
|
|
82
|
+
* billed at the input rate — never free, never an invented discount).
|
|
83
|
+
*/
|
|
84
|
+
export function lookupCardPricing(model, card = RATE_CARD) {
|
|
85
|
+
if (typeof model !== 'string') {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const key = model.trim();
|
|
89
|
+
if (key === '') {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const entry = card[key];
|
|
93
|
+
if (entry === undefined) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
if (!isFiniteNonNegativeNumber(entry.inputCostPerToken) || !isFiniteNonNegativeNumber(entry.outputCostPerToken)) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const cachedPerToken = isFiniteNonNegativeNumber(entry.cacheReadInputTokenCost)
|
|
100
|
+
? entry.cacheReadInputTokenCost
|
|
101
|
+
: entry.inputCostPerToken;
|
|
102
|
+
return {
|
|
103
|
+
inputPerMillion: entry.inputCostPerToken * PER_TOKEN_TO_PER_MILLION,
|
|
104
|
+
cachedInputPerMillion: cachedPerToken * PER_TOKEN_TO_PER_MILLION,
|
|
105
|
+
outputPerMillion: entry.outputCostPerToken * PER_TOKEN_TO_PER_MILLION,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resolve a turn's rate-computed cost for a single provider/role bucket.
|
|
110
|
+
*
|
|
111
|
+
* Resolution order per bucket: operator-configured `configPricing` (the
|
|
112
|
+
* override tier) beats the vendored card (`lookupCardPricing(model)`). Returns
|
|
113
|
+
* null when neither yields pricing (tokens-only). When pricing is found, returns
|
|
114
|
+
* the flat base-tier computation from `computeRateCostUsd` tagged
|
|
115
|
+
* `costSource: 'rate'`; no tiered/threshold logic is applied, so a card-listed
|
|
116
|
+
* model is priced at its published base rates regardless of prompt length.
|
|
117
|
+
*/
|
|
118
|
+
export function resolveRateCost(args) {
|
|
119
|
+
const pricing = args.configPricing ?? lookupCardPricing(args.model, args.card ?? RATE_CARD);
|
|
120
|
+
if (!pricing) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return { costUsd: computeRateCostUsd(args.usage, pricing), costSource: 'rate' };
|
|
124
|
+
}
|