@juno-ai/bind 9.0.0 → 11.0.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.
Files changed (46) hide show
  1. package/README.md +375 -15
  2. package/contracts/index.d.ts +1 -1
  3. package/contracts/index.js +1 -1
  4. package/contracts/turn.d.ts +77 -2
  5. package/contracts/turn.js +35 -2
  6. package/index.d.ts +6 -2
  7. package/index.js +6 -2
  8. package/loop/index.d.ts +2 -1
  9. package/loop/index.js +1 -1
  10. package/loop/tool-loop.d.ts +117 -12
  11. package/loop/tool-loop.js +242 -67
  12. package/package.json +10 -2
  13. package/plugins/dispatch.d.ts +130 -0
  14. package/plugins/dispatch.js +241 -0
  15. package/plugins/index.d.ts +2 -0
  16. package/plugins/index.js +2 -0
  17. package/plugins/tool-message.d.ts +23 -0
  18. package/plugins/tool-message.js +31 -0
  19. package/skills/activation.d.ts +64 -0
  20. package/skills/activation.js +39 -0
  21. package/skills/admission.d.ts +61 -0
  22. package/skills/admission.js +41 -0
  23. package/skills/catalog.d.ts +54 -0
  24. package/skills/catalog.js +77 -0
  25. package/skills/discovery.d.ts +82 -0
  26. package/skills/discovery.js +91 -0
  27. package/skills/index.d.ts +19 -0
  28. package/skills/index.js +19 -0
  29. package/skills/refs.d.ts +21 -0
  30. package/skills/refs.js +27 -0
  31. package/skills/registry.d.ts +57 -0
  32. package/skills/registry.js +94 -0
  33. package/skills/resolve.d.ts +89 -0
  34. package/skills/resolve.js +124 -0
  35. package/skills/sha.d.ts +53 -0
  36. package/skills/sha.js +60 -0
  37. package/skills/sha256.d.ts +38 -0
  38. package/skills/sha256.js +122 -0
  39. package/skills/skill-md.d.ts +73 -0
  40. package/skills/skill-md.js +149 -0
  41. package/skills/types.d.ts +174 -0
  42. package/skills/types.js +55 -0
  43. package/testing/index.d.ts +153 -0
  44. package/testing/index.js +188 -0
  45. package/tools/control-chars.d.ts +23 -0
  46. package/tools/control-chars.js +35 -0
package/index.js CHANGED
@@ -11,9 +11,12 @@
11
11
  * coalesced heartbeat, failure classification, tool-batch pooling, child-run
12
12
  * lineage and admission), the streaming-completion watchdog and completion
13
13
  * defect detection (`src/completion/`), transcript validation/healing, provider
14
- * tool-schema sanitization, and the plugin/tool vocabulary with its registry and
14
+ * tool-schema sanitization, the plugin/tool vocabulary with its registry and
15
15
  * progressive-disclosure activation — generic over the host's invocation
16
- * context. What is NOT here is
16
+ * context and the skill vocabulary that applies the same disclosure to
17
+ * instructions (`src/skills/` — the code-skill registry, the `SKILL.md` codec,
18
+ * the content hash that makes a replay honest, the catalog budget, the
19
+ * active-instruction resolver and its activation controller). What is NOT here is
17
20
  * the run driver: starting a run, recording what it did, and delivering its
18
21
  * output. See the README for the rest of what is deliberately absent.
19
22
  */
@@ -24,4 +27,5 @@ export * from "./run/index.js";
24
27
  export * from "./transcript/index.js";
25
28
  export * from "./tools/index.js";
26
29
  export * from "./plugins/index.js";
30
+ export * from "./skills/index.js";
27
31
  export * from "./loop/index.js";
package/loop/index.d.ts CHANGED
@@ -1 +1,2 @@
1
- export { runToolLoop, type ToolLoopParams, type ToolLoopState, type ToolLoopTurn, type ToolCallOutcome, type CompactionApplied, type RunStatus, } from "./tool-loop.js";
1
+ export type { StopReason, RunStats } from "../contracts/turn.js";
2
+ export { runToolLoop, MissingActivationPortError, type ToolLoopParams, type ToolLoopResult, type ToolLoopState, type ToolLoopTurn, type ToolCallOutcome, type CompactionApplied, type RunStatus, } from "./tool-loop.js";
package/loop/index.js CHANGED
@@ -1 +1 @@
1
- export { runToolLoop, } from "./tool-loop.js";
1
+ export { runToolLoop, MissingActivationPortError, } from "./tool-loop.js";
@@ -1,4 +1,5 @@
1
1
  import type OpenAI from "openai";
2
+ import { type RunStats, type StopReason } from "../contracts/turn.js";
2
3
  /**
3
4
  * The agent iteration engine: call the model, run the tools it asked for,
4
5
  * repeat until it stops asking. Everything that *happens* as a result — status
@@ -19,11 +20,13 @@ import type OpenAI from "openai";
19
20
  * than thrown. Without it they are invisible — the model sees them, your
20
21
  * logs do not.
21
22
  *
22
- * Turn accounting is deliberately the flat usage the loop needs to run
23
- * (`ToolLoopTurn`), not the richer `ModelTurnResult` in `@juno-ai/bind/contracts`
24
- * with its timings. The two describe the same event at different resolutions
25
- * and converge when the loop learns to accumulate `RunStats` directly; until
26
- * then a host that wants throughput metrics folds them alongside.
23
+ * Turn accounting is the flat usage a host's `callModel` returns
24
+ * (`ToolLoopTurn`), not the richer `ModelTurnResult` in
25
+ * `@juno-ai/bind/contracts` with its timings. The loop bridges them: it
26
+ * measures each `callModel` with an injectable `now` and folds the pair
27
+ * through `accumulateTurn`, so `runToolLoop` returns a full `RunStats`
28
+ * without a host changing the shape it already returns. `ttftMs` is the one
29
+ * field that cannot cross — only the transport sees the first byte.
27
30
  */
28
31
  /** One model completion's message + the provider usage the loop accounts for. */
29
32
  export interface ToolLoopTurn {
@@ -31,6 +34,12 @@ export interface ToolLoopTurn {
31
34
  inputTokens: number;
32
35
  outputTokens: number;
33
36
  costCents: number;
37
+ /**
38
+ * Provider-reported cached input tokens, when the transport can report them.
39
+ * Optional — omit it (or pass `null`) and the run's `cachedInputTokens` total
40
+ * simply does not count this turn, rather than counting it as a zero.
41
+ */
42
+ cachedInputTokens?: number | null;
34
43
  }
35
44
  /**
36
45
  * Outcome of running one tool call inside an assistant `tool_calls` batch.
@@ -82,6 +91,8 @@ export interface CompactionApplied {
82
91
  inputTokens: number;
83
92
  outputTokens: number;
84
93
  costCents: number;
94
+ /** Provider-reported cached input tokens for the compaction call, if known. */
95
+ cachedInputTokens?: number | null;
85
96
  /** Persist the compacted session + activity row. Runs after accounting. */
86
97
  persist: () => Promise<void>;
87
98
  }
@@ -123,10 +134,79 @@ export interface ToolLoopState {
123
134
  };
124
135
  }
125
136
  export type RunStatus = "thinking" | "thinking_with_tools" | "executing_tools";
137
+ /**
138
+ * A tool asked the loop to activate a plugin or an instruction module, and the
139
+ * port that would do it was not wired.
140
+ *
141
+ * Reported through `onToolCallRejected` rather than thrown: the call itself
142
+ * succeeded and its tool message is already correct, so failing the run would
143
+ * be worse than the missing activation. But it must not be silent — before
144
+ * these ports were optional this was a compile error, and the runtime symptom
145
+ * (an agent that keeps loading a plugin it never receives) points nowhere near
146
+ * the cause.
147
+ *
148
+ * Match on `error.name === "MissingActivationPortError"` rather than
149
+ * `instanceof` if you consume this package from a projected or re-bundled copy
150
+ * — two copies of a class in one module graph make `instanceof` silently
151
+ * false, and this package is Copybara-projected and republished.
152
+ */
153
+ export declare class MissingActivationPortError extends Error {
154
+ readonly port: "activatePlugins" | "activateSkills";
155
+ readonly name = "MissingActivationPortError";
156
+ constructor(port: "activatePlugins" | "activateSkills");
157
+ }
158
+ /**
159
+ * What a completed loop reports back.
160
+ *
161
+ * Returned rather than folded into `ToolLoopState` because these are the run's
162
+ * *conclusion*, not its live progress: a caller reads `state` mid-run for a
163
+ * heartbeat, and reads this once, after. Before it existed every host
164
+ * re-derived the outcome from three flags and an iteration count, and each got
165
+ * a slightly different answer.
166
+ */
167
+ export interface ToolLoopResult {
168
+ /**
169
+ * Why the loop stopped. See {@link StopReason} — `deadline` arrives as a
170
+ * thrown error rather than a value, and is never returned here.
171
+ *
172
+ * `aborted` means only "the batch signal was set, or `shouldStop` said so".
173
+ * It does not say *which*, because a combined signal cannot. A host that
174
+ * needs timeout-vs-cancellation reaches for its own `RunDeadline` (and
175
+ * `classifyRunFailure`), not for this value.
176
+ */
177
+ readonly stopReason: StopReason;
178
+ /**
179
+ * Cumulative accounting for the run: turns, dispatched tool calls, tokens
180
+ * (including provider-reported cached input), cost, and the model-time vs
181
+ * tool-time split with a per-tool breakdown.
182
+ *
183
+ * **`stats` is this invocation's contribution alone**; `state` is whatever
184
+ * the caller seeded plus that. They match only when the caller seeded zeros
185
+ * — a host resuming a run seeds `state` from the stored totals, and then
186
+ * `state.costCents` is the run's lifetime cost while `stats.costCents` is
187
+ * this leg's. Bill from whichever you mean, and do not substitute one for
188
+ * the other.
189
+ *
190
+ * They also differ on tool calls on purpose: `state.toolCalls` counts what
191
+ * the model *requested* (it drives a live progress indicator, so it has to
192
+ * rise the moment a batch is dispatched), while `stats.toolCalls` counts
193
+ * what actually *ran*. An aborted batch is exactly the gap between them.
194
+ *
195
+ * Both are lost if the loop throws — a deadline, a cancellation, or a fatal
196
+ * tool error leaves no return value, so `state` (which is mutated in place)
197
+ * is the only accounting that survives those exits.
198
+ */
199
+ readonly stats: RunStats;
200
+ }
126
201
  export interface ToolLoopParams {
127
202
  state: ToolLoopState;
128
- /** Active plugin set — read to build tool defs, grown by `activatePlugins`. */
129
- activePlugins: Set<string>;
203
+ /**
204
+ * Active plugin set. The loop does not read it — `buildTools` and
205
+ * `activatePlugins` are the host's own closures over it — so it is optional
206
+ * and passing one is purely a convenience for a host that likes threading it
207
+ * through explicitly.
208
+ */
209
+ activePlugins?: Set<string>;
130
210
  maxIterations: number;
131
211
  /** Call the model with the current transcript + tool defs. `onOutputProgress`
132
212
  * (optional) receives a running estimate of THIS call's output tokens as the
@@ -137,15 +217,38 @@ export interface ToolLoopParams {
137
217
  buildTools: () => OpenAI.ChatCompletionTool[];
138
218
  /** Execute one tool call → the `tool` message + control signals. */
139
219
  runToolCall: (toolCall: OpenAI.ChatCompletionMessageToolCall) => Promise<ToolCallOutcome>;
140
- /** Activate newly loaded plugins (mutate the catalog/active set). */
141
- activatePlugins: (pluginNames: string[]) => void;
220
+ /**
221
+ * Activate newly loaded plugins (mutate the catalog/active set). Optional:
222
+ * a host with a fixed tool surface has nothing to activate, and requiring an
223
+ * empty function from it bought nothing.
224
+ */
225
+ activatePlugins?: (pluginNames: string[]) => void;
142
226
  /**
143
227
  * Activate newly loaded skills: inject their bodies into the system prompt's
144
228
  * instructions section and refresh the catalog. Async because a host may
145
229
  * re-read the module body from storage. Expected to no-op for a ref the agent
146
- * cannot access — the loop does not pre-validate them.
230
+ * cannot access — the loop does not pre-validate them. Optional, as above.
231
+ */
232
+ activateSkills?: (skillRefs: string[]) => Promise<void> | void;
233
+ /**
234
+ * Clock for the model-time and tool-time measurements in
235
+ * {@link ToolLoopResult.stats}. Defaults to `Date.now`, the package's one
236
+ * sanctioned ambient-clock exception; inject a fake to make timing
237
+ * assertions deterministic.
238
+ *
239
+ * **Must be a real monotonic clock when tools can run concurrently.** Each
240
+ * call records `now()` at dispatch and again when it settles, so a shared
241
+ * counter that only advances when some *other* call asks it to will charge
242
+ * one tool for another's time. A cooperatively-advanced fake is fine for a
243
+ * serial batch (`runsSerially`), and fine for the model-time figures always.
244
+ *
245
+ * Measured *around* `callModel`, so the number includes whatever that
246
+ * function does internally — a defect retry, a fallback provider, a routing
247
+ * hop. That is the honest figure for cost-and-latency accounting: it is what
248
+ * the turn actually took. A host that wants the successful attempt's
249
+ * generation time alone already has it, inside its own transport.
147
250
  */
148
- activateSkills: (skillRefs: string[]) => Promise<void> | void;
251
+ now?: () => number;
149
252
  /**
150
253
  * Must this call run on its own, before the rest of its batch?
151
254
  *
@@ -270,5 +373,7 @@ export interface ToolLoopParams {
270
373
  * Mutates `state` (messages + token accumulators) in place. That is deliberate
271
374
  * rather than a return value: a caller's heartbeat reads live totals off it
272
375
  * mid-loop, which a returned result could not provide until the run ended.
376
+ * The {@link ToolLoopResult} it *returns* is the complementary half — the
377
+ * run's conclusion, which only exists once the loop is over.
273
378
  */
274
- export declare function runToolLoop(params: ToolLoopParams): Promise<void>;
379
+ export declare function runToolLoop(params: ToolLoopParams): Promise<ToolLoopResult>;