@skanl/brambo-adapter-cli 0.1.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/LICENSE +21 -0
- package/README.md +183 -0
- package/dist/catalogue.d.ts +57 -0
- package/dist/catalogue.js +62 -0
- package/dist/executors/claude-code.d.ts +3 -0
- package/dist/executors/claude-code.js +125 -0
- package/dist/executors/codex.d.ts +3 -0
- package/dist/executors/codex.js +89 -0
- package/dist/executors/opencode.d.ts +3 -0
- package/dist/executors/opencode.js +74 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +7 -0
- package/dist/node-child-spawner.d.ts +13 -0
- package/dist/node-child-spawner.js +287 -0
- package/dist/plugin.d.ts +88 -0
- package/dist/plugin.js +181 -0
- package/dist/spawn-seam.d.ts +38 -0
- package/dist/spawn-seam.js +5 -0
- package/dist/traits.d.ts +179 -0
- package/dist/traits.js +589 -0
- package/package.json +55 -0
package/dist/traits.d.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { ExecutorAdapter, UsageReport } from '@skanl/brambo-contracts';
|
|
2
|
+
import type { ChildProcessSpawner } from './spawn-seam.ts';
|
|
3
|
+
/**
|
|
4
|
+
* How the prompt reaches the executor.
|
|
5
|
+
* - `stdin`: written to the child's stdin, which is then closed. Long prompts
|
|
6
|
+
* can never hit a command-line length limit this way.
|
|
7
|
+
* - `argument`: appended as the FINAL argv entry; stdin is closed immediately
|
|
8
|
+
* so an executor that also reads stdin never blocks waiting for input.
|
|
9
|
+
*/
|
|
10
|
+
export type PromptDelivery = 'stdin' | 'argument';
|
|
11
|
+
/**
|
|
12
|
+
* Shape of what the executor prints on stdout.
|
|
13
|
+
* - `single-object`: the whole stream is one JSON object.
|
|
14
|
+
* - `jsonl`: newline-delimited event objects; the result is somewhere inside.
|
|
15
|
+
*/
|
|
16
|
+
export type PayloadShape = 'single-object' | 'jsonl';
|
|
17
|
+
/** Equality test over a property path, used to discriminate payload records. */
|
|
18
|
+
export interface PathMatch {
|
|
19
|
+
readonly path: readonly string[];
|
|
20
|
+
readonly equals: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Where a vendor publishes its OWN per-window quota utilisation, and under which
|
|
24
|
+
* spellings (Story M15.A).
|
|
25
|
+
*
|
|
26
|
+
* Trait DATA rather than field names in the engine, for the reason the whole
|
|
27
|
+
* file exists: a fourth executor with a usage surface of its own must arrive as
|
|
28
|
+
* a record, never as an edit here. `path` resolves to the vendor's MAP of named
|
|
29
|
+
* windows — the keys of that map are the window names brambo reports, so the
|
|
30
|
+
* vocabulary is the vendor's and brambo names nothing.
|
|
31
|
+
*/
|
|
32
|
+
export interface UsageWindowTraits {
|
|
33
|
+
/** Which record carries the surface, e.g. `type == "rate_limit_event"`. */
|
|
34
|
+
readonly when: PathMatch;
|
|
35
|
+
/** Path to the map of NAMED windows inside that record. */
|
|
36
|
+
readonly path: readonly string[];
|
|
37
|
+
/** The vendor's key for the utilisation figure inside one window. */
|
|
38
|
+
readonly utilizationKey: string;
|
|
39
|
+
/** The vendor's key for the reset instant inside one window. */
|
|
40
|
+
readonly resetsAtKey: string;
|
|
41
|
+
}
|
|
42
|
+
export interface ExecutorOutputTraits {
|
|
43
|
+
readonly payload: PayloadShape;
|
|
44
|
+
/** Property path to the result text inside a payload record. */
|
|
45
|
+
readonly resultPath: readonly string[];
|
|
46
|
+
/**
|
|
47
|
+
* Extra condition a record must satisfy to count as THE result. Without it
|
|
48
|
+
* any record carrying `resultPath` qualifies — which for Codex would accept
|
|
49
|
+
* a reasoning item and answer with chain-of-thought.
|
|
50
|
+
*/
|
|
51
|
+
readonly resultWhen?: PathMatch;
|
|
52
|
+
/**
|
|
53
|
+
* Extra condition a record must satisfy before `errorFlagPath` and
|
|
54
|
+
* `errorStatusPrefix` are read off it. Without it EVERY record in a stream is
|
|
55
|
+
* examined, which is the widening a single-object payload never had.
|
|
56
|
+
*
|
|
57
|
+
* Claude's `--output-format json` printed exactly one object, so "the record
|
|
58
|
+
* that could report failure" and "the result" were the same record by
|
|
59
|
+
* construction. Its stream prints `system`, `assistant` and `hook_*` events
|
|
60
|
+
* beside the result, all of them carrying a `subtype` of their own — so
|
|
61
|
+
* without this the `error` prefix would be tested against a vocabulary that is
|
|
62
|
+
* not the result's, and the envelope would no longer be the one the old mode
|
|
63
|
+
* produced. That equivalence is the story's own acceptance criterion.
|
|
64
|
+
*/
|
|
65
|
+
readonly failureWhen?: PathMatch;
|
|
66
|
+
/** Path to a boolean flag that marks an executor-reported failure. */
|
|
67
|
+
readonly errorFlagPath?: readonly string[];
|
|
68
|
+
/** Path to a status string reported in failure messages. */
|
|
69
|
+
readonly statusPath?: readonly string[];
|
|
70
|
+
/** A status starting with this prefix is a failure even on exit code 0. */
|
|
71
|
+
readonly errorStatusPrefix?: string;
|
|
72
|
+
/** Path to the failure detail. Defaults to `resultPath`. */
|
|
73
|
+
readonly errorMessagePath?: readonly string[];
|
|
74
|
+
/** Extra payload paths copied into `envelope.data` under the given keys. */
|
|
75
|
+
readonly metadata?: Readonly<Record<string, readonly string[]>>;
|
|
76
|
+
/**
|
|
77
|
+
* Which records report usage. Required whenever `usagePaths` is declared.
|
|
78
|
+
*
|
|
79
|
+
* `usagePaths` is summed across EVERY matching record (see below), so the
|
|
80
|
+
* discriminator is what bounds the sum: without it, any record whose shape
|
|
81
|
+
* happens to carry the same paths joins the bill and the run is double-charged.
|
|
82
|
+
* It is `resultWhen` for the accounting half, and it exists for the same reason.
|
|
83
|
+
*/
|
|
84
|
+
readonly usageWhen?: PathMatch;
|
|
85
|
+
/**
|
|
86
|
+
* Paths to the vendor's OWN numeric usage fields, summed into `data.usage`.
|
|
87
|
+
*
|
|
88
|
+
* A separate channel from `metadata` rather than a loosening of it: `metadata`
|
|
89
|
+
* keeps strings, several suites assert exactly that, and a usage figure is a
|
|
90
|
+
* NUMBER an accounting seam adds up. Widening the shared channel to carry both
|
|
91
|
+
* would have made every existing metadata key silently numeric-capable too.
|
|
92
|
+
*
|
|
93
|
+
* A list, because no vendor reports one total for a run: claude-code reports
|
|
94
|
+
* disjoint components (uncached input, cache creation, cache read, output) that
|
|
95
|
+
* only mean something added together. Summing figures a vendor printed is not
|
|
96
|
+
* brambo counting tokens — nothing here estimates, tokenizes or infers; every
|
|
97
|
+
* term is a number the tool itself emitted.
|
|
98
|
+
*
|
|
99
|
+
* Summed ACROSS records as well as within one, because a vendor that works in
|
|
100
|
+
* steps bills every step. Measured on opencode 1.18.23 with a three-step task:
|
|
101
|
+
* `step_finish` carried 42770, 42875 and 43025, each equal to its OWN
|
|
102
|
+
* components rather than to a running total. Taking the last one billed 43025
|
|
103
|
+
* of 128670 — and a task whose last step is trivial bills almost nothing.
|
|
104
|
+
*
|
|
105
|
+
* The record carrying usage is usually NOT the record carrying the result —
|
|
106
|
+
* codex reports it on `turn.completed` and opencode on `step_finish` — so it is
|
|
107
|
+
* resolved by its own scan, keyed on `usageWhen`.
|
|
108
|
+
*
|
|
109
|
+
* Fails CLOSED as a whole: if any matching record fails to resolve every path
|
|
110
|
+
* to a finite non-negative number, the run reports NO figure at all rather than
|
|
111
|
+
* a sum missing a term. A partial bill is a wrong bill; an absent one is a case
|
|
112
|
+
* the pipeline already handles by keeping the estimate.
|
|
113
|
+
*/
|
|
114
|
+
readonly usagePaths?: readonly (readonly string[])[];
|
|
115
|
+
/**
|
|
116
|
+
* The vendor's own QUOTA surface, if it publishes one. Absent means it does
|
|
117
|
+
* not, and absence is reported as absence with that reason — never as a zero.
|
|
118
|
+
*
|
|
119
|
+
* Read from the LAST matching record and reported through
|
|
120
|
+
* `CliExecutorAdapterOptions.onUsageObservation`, never through the envelope:
|
|
121
|
+
* the envelope this story switched Claude's stream mode under has to stay
|
|
122
|
+
* byte-for-byte the one the single-object mode produced.
|
|
123
|
+
*/
|
|
124
|
+
readonly usageWindows?: UsageWindowTraits;
|
|
125
|
+
}
|
|
126
|
+
export interface ExecutorTraits {
|
|
127
|
+
/** Stable identity of the executor, independent of which binary path runs it. */
|
|
128
|
+
readonly executorId: string;
|
|
129
|
+
/** Default binary; `CliExecutorAdapterOptions.command` overrides it. */
|
|
130
|
+
readonly command: string;
|
|
131
|
+
/** Fixed argv, placed before the prompt when `promptDelivery` is `argument`. */
|
|
132
|
+
readonly args: readonly string[];
|
|
133
|
+
readonly promptDelivery: PromptDelivery;
|
|
134
|
+
/**
|
|
135
|
+
* Emitted between `args` and the prompt so a prompt starting with `-` is not
|
|
136
|
+
* parsed as a flag. Omit only for a CLI that has no such separator.
|
|
137
|
+
*/
|
|
138
|
+
readonly promptArgSeparator?: string;
|
|
139
|
+
readonly output: ExecutorOutputTraits;
|
|
140
|
+
}
|
|
141
|
+
export interface CliExecutorAdapterOptions {
|
|
142
|
+
/**
|
|
143
|
+
* Child-process seam. Defaults to the real Node spawner; tests inject fakes
|
|
144
|
+
* here so suites never touch the actual binary.
|
|
145
|
+
*/
|
|
146
|
+
readonly spawner?: ChildProcessSpawner;
|
|
147
|
+
/** Overrides the trait's command, e.g. an absolute path to the binary. */
|
|
148
|
+
readonly command?: string;
|
|
149
|
+
/** Receives per-run timing, including NFR-9 spawn-overhead instrumentation. */
|
|
150
|
+
readonly onTiming?: (timing: AdapterTiming) => void;
|
|
151
|
+
/**
|
|
152
|
+
* Receives the vendor's own quota reading for THIS run, when the trait record
|
|
153
|
+
* declares a surface to read it from (Story M15.A).
|
|
154
|
+
*
|
|
155
|
+
* A callback and not an envelope field, because the envelope had to stay
|
|
156
|
+
* identical across the mode switch that made the reading possible. Called at
|
|
157
|
+
* most once per run, and only on a run whose child actually printed something
|
|
158
|
+
* — a spawn that never happened has nothing to observe, which is different
|
|
159
|
+
* from an executor that ran and said nothing.
|
|
160
|
+
*/
|
|
161
|
+
readonly onUsageObservation?: (report: UsageReport) => void;
|
|
162
|
+
}
|
|
163
|
+
export interface AdapterTiming {
|
|
164
|
+
/** Milliseconds spent inside run() before the child was handed to the OS. */
|
|
165
|
+
readonly spawnSetupMs: number;
|
|
166
|
+
/** Total wall time of the run() call. */
|
|
167
|
+
readonly runMs: number;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* An adapter that also answers WHICH executor it drives. `command` can be
|
|
171
|
+
* overridden per instance (an absolute path, a shim), so it is not an identity;
|
|
172
|
+
* `executorId` is.
|
|
173
|
+
*/
|
|
174
|
+
export interface CliExecutorAdapter extends ExecutorAdapter {
|
|
175
|
+
readonly executorId: string;
|
|
176
|
+
}
|
|
177
|
+
/** The engine-owned `data` key a settled cost is read back from. */
|
|
178
|
+
export declare const USAGE_DATA_KEY = "usage";
|
|
179
|
+
export declare function createCliExecutorAdapter(traits: ExecutorTraits, options?: CliExecutorAdapterOptions): CliExecutorAdapter;
|